Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/phpmyadmin/phpmyadmin.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CONTRIBUTING.md21
-rw-r--r--ChangeLog112
-rw-r--r--LICENSE340
-rw-r--r--README104
-rw-r--r--browse_foreigners.php66
-rw-r--r--changelog.php153
-rw-r--r--chk_rel.php15
-rw-r--r--composer.json27
-rw-r--r--config.sample.inc.php152
-rw-r--r--db_create.php136
-rw-r--r--db_datadict.php291
-rw-r--r--db_events.php26
-rw-r--r--db_export.php94
-rw-r--r--db_import.php25
-rw-r--r--db_operations.php300
-rw-r--r--db_printview.php180
-rw-r--r--db_qbe.php78
-rw-r--r--db_routines.php27
-rw-r--r--db_search.php62
-rw-r--r--db_sql.php44
-rw-r--r--db_structure.php317
-rw-r--r--db_tracking.php252
-rw-r--r--db_triggers.php25
-rw-r--r--error_report.php95
-rw-r--r--examples/config.manyhosts.inc.php51
-rw-r--r--examples/openid.php160
-rw-r--r--examples/signon-script.php29
-rw-r--r--examples/signon.php69
-rw-r--r--examples/swekey.sample.conf44
-rw-r--r--export.php1032
-rw-r--r--favicon.icobin0 -> 18902 bytes
-rw-r--r--file_echo.php71
-rw-r--r--gis_data_editor.php418
-rw-r--r--import.php642
-rw-r--r--import_status.php93
-rw-r--r--index.php667
-rw-r--r--js/OpenStreetMap.js126
-rw-r--r--js/ajax.js867
-rw-r--r--js/canvg/MIT-LICENSE.txt22
-rw-r--r--js/canvg/canvg.js2509
-rw-r--r--js/chart.js546
-rw-r--r--js/codemirror/LICENSE23
-rw-r--r--js/codemirror/addon/runmode/runmode.js56
-rw-r--r--js/codemirror/lib/codemirror.js5799
-rw-r--r--js/codemirror/mode/sql/sql.js349
-rw-r--r--js/common.js300
-rw-r--r--js/config.js798
-rw-r--r--js/cross_framing_protection.js9
-rw-r--r--js/db_operations.js114
-rw-r--r--js/db_search.js236
-rw-r--r--js/db_structure.js385
-rw-r--r--js/doclinks.js365
-rw-r--r--js/error_report.js310
-rw-r--r--js/export.js286
-rw-r--r--js/functions.js4109
-rw-r--r--js/get_image.js.php141
-rw-r--r--js/get_scripts.js.php45
-rw-r--r--js/gis_data_editor.js395
-rw-r--r--js/import.js117
-rw-r--r--js/indexes.js209
-rw-r--r--js/jqplot/excanvas.js1438
-rw-r--r--js/jqplot/jquery.jqplot.js11381
-rw-r--r--js/jqplot/plugins/jqplot.barRenderer.js797
-rw-r--r--js/jqplot/plugins/jqplot.byteFormatter.js46
-rw-r--r--js/jqplot/plugins/jqplot.canvasAxisLabelRenderer.js203
-rw-r--r--js/jqplot/plugins/jqplot.canvasTextRenderer.js449
-rw-r--r--js/jqplot/plugins/jqplot.categoryAxisRenderer.js673
-rw-r--r--js/jqplot/plugins/jqplot.cursor.js1108
-rw-r--r--js/jqplot/plugins/jqplot.dateAxisRenderer.js737
-rw-r--r--js/jqplot/plugins/jqplot.highlighter.js465
-rw-r--r--js/jqplot/plugins/jqplot.pieRenderer.js904
-rw-r--r--js/jqplot/plugins/jqplot.pointLabels.js379
-rw-r--r--js/jquery/MIT-LICENSE.txt21
-rw-r--r--js/jquery/jquery-1.8.3.min.js2
-rw-r--r--js/jquery/jquery-ui-1.9.2.custom.min.js6
-rw-r--r--js/jquery/jquery-ui-timepicker-addon.js2128
-rw-r--r--js/jquery/jquery.ba-hashchange-1.3.js390
-rw-r--r--js/jquery/jquery.cookie.js91
-rw-r--r--js/jquery/jquery.debounce-1.0.5.js71
-rw-r--r--js/jquery/jquery.event.drag-2.2.js402
-rw-r--r--js/jquery/jquery.fullscreen.js60
-rw-r--r--js/jquery/jquery.json-2.4.js199
-rw-r--r--js/jquery/jquery.menuResizer-1.0.js181
-rw-r--r--js/jquery/jquery.mousewheel.js84
-rw-r--r--js/jquery/jquery.sortableTable.js272
-rw-r--r--js/jquery/jquery.sprintf.js68
-rw-r--r--js/jquery/jquery.svg.js1394
-rw-r--r--js/jquery/jquery.tablesorter.js1033
-rw-r--r--js/jquery/src/README9
-rw-r--r--js/jquery/src/jquery-ui/jquery.ui.accordion.js731
-rw-r--r--js/jquery/src/jquery-ui/jquery.ui.autocomplete.js602
-rw-r--r--js/jquery/src/jquery-ui/jquery.ui.button.js418
-rw-r--r--js/jquery/src/jquery-ui/jquery.ui.core.js356
-rw-r--r--js/jquery/src/jquery-ui/jquery.ui.datepicker.js1846
-rw-r--r--js/jquery/src/jquery-ui/jquery.ui.dialog.js858
-rw-r--r--js/jquery/src/jquery-ui/jquery.ui.draggable.js836
-rw-r--r--js/jquery/src/jquery-ui/jquery.ui.droppable.js294
-rw-r--r--js/jquery/src/jquery-ui/jquery.ui.effect-blind.js82
-rw-r--r--js/jquery/src/jquery-ui/jquery.ui.effect-bounce.js113
-rw-r--r--js/jquery/src/jquery-ui/jquery.ui.effect-clip.js67
-rw-r--r--js/jquery/src/jquery-ui/jquery.ui.effect-drop.js65
-rw-r--r--js/jquery/src/jquery-ui/jquery.ui.effect-explode.js97
-rw-r--r--js/jquery/src/jquery-ui/jquery.ui.effect-fade.js30
-rw-r--r--js/jquery/src/jquery-ui/jquery.ui.effect-fold.js76
-rw-r--r--js/jquery/src/jquery-ui/jquery.ui.effect-highlight.js50
-rw-r--r--js/jquery/src/jquery-ui/jquery.ui.effect-pulsate.js63
-rw-r--r--js/jquery/src/jquery-ui/jquery.ui.effect-scale.js318
-rw-r--r--js/jquery/src/jquery-ui/jquery.ui.effect-shake.js74
-rw-r--r--js/jquery/src/jquery-ui/jquery.ui.effect-slide.js64
-rw-r--r--js/jquery/src/jquery-ui/jquery.ui.effect-transfer.js47
-rw-r--r--js/jquery/src/jquery-ui/jquery.ui.effect.js1276
-rw-r--r--js/jquery/src/jquery-ui/jquery.ui.menu.js610
-rw-r--r--js/jquery/src/jquery-ui/jquery.ui.mouse.js169
-rw-r--r--js/jquery/src/jquery-ui/jquery.ui.position.js517
-rw-r--r--js/jquery/src/jquery-ui/jquery.ui.progressbar.js105
-rw-r--r--js/jquery/src/jquery-ui/jquery.ui.resizable.js801
-rw-r--r--js/jquery/src/jquery-ui/jquery.ui.selectable.js261
-rw-r--r--js/jquery/src/jquery-ui/jquery.ui.slider.js644
-rw-r--r--js/jquery/src/jquery-ui/jquery.ui.sortable.js1096
-rw-r--r--js/jquery/src/jquery-ui/jquery.ui.spinner.js478
-rw-r--r--js/jquery/src/jquery-ui/jquery.ui.tabs.js1366
-rw-r--r--js/jquery/src/jquery-ui/jquery.ui.tooltip.js398
-rw-r--r--js/jquery/src/jquery-ui/jquery.ui.widget.js528
-rw-r--r--js/jquery/src/jquery/ajax.js855
-rw-r--r--js/jquery/src/jquery/ajax/jsonp.js80
-rw-r--r--js/jquery/src/jquery/ajax/script.js57
-rw-r--r--js/jquery/src/jquery/ajax/xhr.js111
-rw-r--r--js/jquery/src/jquery/attributes.js502
-rw-r--r--js/jquery/src/jquery/callbacks.js197
-rw-r--r--js/jquery/src/jquery/core.js846
-rw-r--r--js/jquery/src/jquery/css.js563
-rw-r--r--js/jquery/src/jquery/data.js356
-rw-r--r--js/jquery/src/jquery/deferred.js141
-rw-r--r--js/jquery/src/jquery/deprecated.js11
-rw-r--r--js/jquery/src/jquery/dimensions.js41
-rw-r--r--js/jquery/src/jquery/effects.js730
-rw-r--r--js/jquery/src/jquery/event-alias.js32
-rw-r--r--js/jquery/src/jquery/event.js829
-rw-r--r--js/jquery/src/jquery/intro.js54
-rw-r--r--js/jquery/src/jquery/manipulation.js577
-rw-r--r--js/jquery/src/jquery/offset.js167
-rw-r--r--js/jquery/src/jquery/outro.js6
-rw-r--r--js/jquery/src/jquery/queue.js145
-rw-r--r--js/jquery/src/jquery/selector-native.js164
-rw-r--r--js/jquery/src/jquery/serialize.js99
-rw-r--r--js/jquery/src/jquery/sizzle-jquery.js7
-rw-r--r--js/jquery/src/jquery/support.js113
-rw-r--r--js/jquery/src/jquery/traversing.js283
-rw-r--r--js/jquery/src/jquery/wrap.js69
-rw-r--r--js/keyhandler.js118
-rw-r--r--js/line_counts.php154
-rw-r--r--js/makegrid.js1914
-rw-r--r--js/messages.php576
-rw-r--r--js/navigation.js1207
-rw-r--r--js/openlayers/OpenLayers.js2681
-rw-r--r--js/openlayers/img/blank.gifbin0 -> 42 bytes
-rw-r--r--js/openlayers/img/cloud-popup-relative.pngbin0 -> 3177 bytes
-rw-r--r--js/openlayers/img/drag-rectangle-off.pngbin0 -> 1202 bytes
-rw-r--r--js/openlayers/img/drag-rectangle-on.pngbin0 -> 1218 bytes
-rw-r--r--js/openlayers/img/east-mini.pngbin0 -> 451 bytes
-rw-r--r--js/openlayers/img/layer-switcher-maximize.pngbin0 -> 451 bytes
-rw-r--r--js/openlayers/img/layer-switcher-minimize.pngbin0 -> 249 bytes
-rw-r--r--js/openlayers/img/marker-blue.pngbin0 -> 992 bytes
-rw-r--r--js/openlayers/img/marker-gold.pngbin0 -> 831 bytes
-rw-r--r--js/openlayers/img/marker-green.pngbin0 -> 967 bytes
-rw-r--r--js/openlayers/img/marker.pngbin0 -> 606 bytes
-rw-r--r--js/openlayers/img/measuring-stick-off.pngbin0 -> 3343 bytes
-rw-r--r--js/openlayers/img/measuring-stick-on.pngbin0 -> 3816 bytes
-rw-r--r--js/openlayers/img/north-mini.pngbin0 -> 484 bytes
-rw-r--r--js/openlayers/img/panning-hand-off.pngbin0 -> 3875 bytes
-rw-r--r--js/openlayers/img/panning-hand-on.pngbin0 -> 3977 bytes
-rw-r--r--js/openlayers/img/slider.pngbin0 -> 285 bytes
-rw-r--r--js/openlayers/img/south-mini.pngbin0 -> 481 bytes
-rw-r--r--js/openlayers/img/west-mini.pngbin0 -> 453 bytes
-rw-r--r--js/openlayers/img/zoom-minus-mini.pngbin0 -> 359 bytes
-rw-r--r--js/openlayers/img/zoom-plus-mini.pngbin0 -> 489 bytes
-rw-r--r--js/openlayers/img/zoom-world-mini.pngbin0 -> 1072 bytes
-rw-r--r--js/openlayers/img/zoombar.pngbin0 -> 463 bytes
-rw-r--r--js/openlayers/theme/default/framedCloud.css0
-rw-r--r--js/openlayers/theme/default/google.css10
-rw-r--r--js/openlayers/theme/default/ie6-style.css7
-rw-r--r--js/openlayers/theme/default/img/add_point_off.pngbin0 -> 1616 bytes
-rw-r--r--js/openlayers/theme/default/img/add_point_on.pngbin0 -> 1464 bytes
-rw-r--r--js/openlayers/theme/default/img/blank.gifbin0 -> 42 bytes
-rw-r--r--js/openlayers/theme/default/img/close.gifbin0 -> 1078 bytes
-rw-r--r--js/openlayers/theme/default/img/drag-rectangle-off.pngbin0 -> 1202 bytes
-rw-r--r--js/openlayers/theme/default/img/drag-rectangle-on.pngbin0 -> 1218 bytes
-rw-r--r--js/openlayers/theme/default/img/draw_line_off.pngbin0 -> 1567 bytes
-rw-r--r--js/openlayers/theme/default/img/draw_line_on.pngbin0 -> 1399 bytes
-rw-r--r--js/openlayers/theme/default/img/draw_point_off.pngbin0 -> 1612 bytes
-rw-r--r--js/openlayers/theme/default/img/draw_point_on.pngbin0 -> 1460 bytes
-rw-r--r--js/openlayers/theme/default/img/draw_polygon_off.pngbin0 -> 1546 bytes
-rw-r--r--js/openlayers/theme/default/img/draw_polygon_on.pngbin0 -> 1407 bytes
-rw-r--r--js/openlayers/theme/default/img/editing_tool_bar.pngbin0 -> 3901 bytes
-rw-r--r--js/openlayers/theme/default/img/move_feature_off.pngbin0 -> 1543 bytes
-rw-r--r--js/openlayers/theme/default/img/move_feature_on.pngbin0 -> 1379 bytes
-rw-r--r--js/openlayers/theme/default/img/navigation_history.pngbin0 -> 7021 bytes
-rw-r--r--js/openlayers/theme/default/img/overview_replacement.gifbin0 -> 79 bytes
-rw-r--r--js/openlayers/theme/default/img/pan-panel-NOALPHA.pngbin0 -> 566 bytes
-rw-r--r--js/openlayers/theme/default/img/pan-panel.pngbin0 -> 1287 bytes
-rw-r--r--js/openlayers/theme/default/img/pan_off.pngbin0 -> 1696 bytes
-rw-r--r--js/openlayers/theme/default/img/pan_on.pngbin0 -> 1568 bytes
-rw-r--r--js/openlayers/theme/default/img/panning-hand-off.pngbin0 -> 3875 bytes
-rw-r--r--js/openlayers/theme/default/img/panning-hand-on.pngbin0 -> 3977 bytes
-rw-r--r--js/openlayers/theme/default/img/remove_point_off.pngbin0 -> 1612 bytes
-rw-r--r--js/openlayers/theme/default/img/remove_point_on.pngbin0 -> 1464 bytes
-rw-r--r--js/openlayers/theme/default/img/ruler.pngbin0 -> 1211 bytes
-rw-r--r--js/openlayers/theme/default/img/save_features_off.pngbin0 -> 357 bytes
-rw-r--r--js/openlayers/theme/default/img/save_features_on.pngbin0 -> 364 bytes
-rw-r--r--js/openlayers/theme/default/img/view_next_off.pngbin0 -> 1644 bytes
-rw-r--r--js/openlayers/theme/default/img/view_next_on.pngbin0 -> 1686 bytes
-rw-r--r--js/openlayers/theme/default/img/view_previous_off.pngbin0 -> 1553 bytes
-rw-r--r--js/openlayers/theme/default/img/view_previous_on.pngbin0 -> 1592 bytes
-rw-r--r--js/openlayers/theme/default/img/zoom-panel-NOALPHA.pngbin0 -> 1173 bytes
-rw-r--r--js/openlayers/theme/default/img/zoom-panel.pngbin0 -> 1624 bytes
-rw-r--r--js/openlayers/theme/default/style.css397
-rw-r--r--js/pmd/ajax.js60
-rw-r--r--js/pmd/history.js787
-rw-r--r--js/pmd/iecanvas.js151
-rw-r--r--js/pmd/init.js31
-rw-r--r--js/pmd/move.js1279
-rw-r--r--js/querywindow.js25
-rw-r--r--js/replication.js72
-rw-r--r--js/rte.js930
-rw-r--r--js/server_databases.js130
-rw-r--r--js/server_plugins.js30
-rw-r--r--js/server_privileges.js687
-rw-r--r--js/server_status_advisor.js93
-rw-r--r--js/server_status_monitor.js2126
-rw-r--r--js/server_status_queries.js40
-rw-r--r--js/server_status_sorter.js99
-rw-r--r--js/server_status_variables.js110
-rw-r--r--js/server_user_groups.js42
-rw-r--r--js/server_variables.js157
-rw-r--r--js/sql.js492
-rw-r--r--js/tbl_change.js526
-rw-r--r--js/tbl_chart.js325
-rw-r--r--js/tbl_find_replace.js47
-rw-r--r--js/tbl_gis_visualization.js352
-rw-r--r--js/tbl_relation.js135
-rw-r--r--js/tbl_select.js227
-rw-r--r--js/tbl_structure.js504
-rw-r--r--js/tbl_zoom_plot_jqplot.js622
-rw-r--r--js/tracekit/tracekit.js1114
-rw-r--r--libraries/Advisor.class.php517
-rw-r--r--libraries/Config.class.php1912
-rw-r--r--libraries/DBQbe.class.php1374
-rw-r--r--libraries/DatabaseInterface.class.php2353
-rw-r--r--libraries/DbSearch.class.php500
-rw-r--r--libraries/DisplayResults.class.php6038
-rw-r--r--libraries/Error.class.php415
-rw-r--r--libraries/Error_Handler.class.php413
-rw-r--r--libraries/File.class.php865
-rw-r--r--libraries/Footer.class.php336
-rw-r--r--libraries/Header.class.php720
-rw-r--r--libraries/Index.class.php924
-rw-r--r--libraries/List.class.php132
-rw-r--r--libraries/List_Database.class.php218
-rw-r--r--libraries/Menu.class.php588
-rw-r--r--libraries/Message.class.php749
-rw-r--r--libraries/OutputBuffering.class.php142
-rw-r--r--libraries/PDF.class.php145
-rw-r--r--libraries/PMA.php110
-rw-r--r--libraries/Partition.class.php79
-rw-r--r--libraries/RecentTable.class.php220
-rw-r--r--libraries/Response.class.php380
-rw-r--r--libraries/Scripts.class.php248
-rw-r--r--libraries/ServerStatusData.class.php407
-rw-r--r--libraries/StorageEngine.class.php434
-rw-r--r--libraries/String.class.php261
-rw-r--r--libraries/StringAbstractType.class.php35
-rw-r--r--libraries/StringByte.int.php60
-rw-r--r--libraries/StringCType.class.php110
-rw-r--r--libraries/StringMB.class.php92
-rw-r--r--libraries/StringNative.class.php87
-rw-r--r--libraries/StringNativeType.class.php138
-rw-r--r--libraries/StringType.int.php85
-rw-r--r--libraries/Table.class.php1713
-rw-r--r--libraries/TableSearch.class.php1469
-rw-r--r--libraries/Theme.class.php485
-rw-r--r--libraries/Theme_Manager.class.php461
-rw-r--r--libraries/Tracker.class.php1087
-rw-r--r--libraries/Types.class.php986
-rw-r--r--libraries/Util.class.php4366
-rw-r--r--libraries/advisory_rules.txt470
-rw-r--r--libraries/bfShapeFiles/ShapeFile.lib.php682
-rw-r--r--libraries/bookmark.lib.php216
-rw-r--r--libraries/browse_foreigners.lib.php415
-rw-r--r--libraries/build_html_for_db.lib.php182
-rw-r--r--libraries/charset_conversion.lib.php114
-rw-r--r--libraries/check_user_privileges.lib.php178
-rw-r--r--libraries/cleanup.lib.php50
-rw-r--r--libraries/common.inc.php1150
-rw-r--r--libraries/config.default.php2856
-rw-r--r--libraries/config.values.php263
-rw-r--r--libraries/config/ConfigFile.class.php538
-rw-r--r--libraries/config/Form.class.php219
-rw-r--r--libraries/config/FormDisplay.class.php830
-rw-r--r--libraries/config/FormDisplay.tpl.php489
-rw-r--r--libraries/config/Validator.class.php598
-rw-r--r--libraries/config/config_functions.lib.php55
-rw-r--r--libraries/config/messages.inc.php548
-rw-r--r--libraries/config/setup.forms.php378
-rw-r--r--libraries/config/user_preferences.forms.php276
-rw-r--r--libraries/core.lib.php827
-rw-r--r--libraries/create_addfield.lib.php325
-rw-r--r--libraries/data_dictionary_relations.lib.php167
-rw-r--r--libraries/database_interface.inc.php87
-rw-r--r--libraries/db_common.inc.php100
-rw-r--r--libraries/db_info.inc.php248
-rw-r--r--libraries/db_table_exists.lib.php104
-rw-r--r--libraries/dbi/DBIDrizzle.class.php712
-rw-r--r--libraries/dbi/DBIDummy.class.php869
-rw-r--r--libraries/dbi/DBIExtension.int.php248
-rw-r--r--libraries/dbi/DBIMysql.class.php570
-rw-r--r--libraries/dbi/DBIMysqli.class.php764
-rw-r--r--libraries/dbi/drizzle-wrappers.lib.php435
-rw-r--r--libraries/display_change_password.lib.php98
-rw-r--r--libraries/display_create_database.lib.php63
-rw-r--r--libraries/display_create_table.lib.php81
-rw-r--r--libraries/display_export.inc.php72
-rw-r--r--libraries/display_export.lib.php697
-rw-r--r--libraries/display_git_revision.lib.php83
-rw-r--r--libraries/display_import.inc.php54
-rw-r--r--libraries/display_import.lib.php586
-rw-r--r--libraries/display_import_ajax.lib.php129
-rw-r--r--libraries/display_select_lang.lib.php98
-rw-r--r--libraries/display_structure.inc.php267
-rw-r--r--libraries/engines/bdb.lib.php84
-rw-r--r--libraries/engines/berkeleydb.lib.php24
-rw-r--r--libraries/engines/binlog.lib.php28
-rw-r--r--libraries/engines/innobase.lib.php25
-rw-r--r--libraries/engines/innodb.lib.php411
-rw-r--r--libraries/engines/memory.lib.php34
-rw-r--r--libraries/engines/merge.lib.php21
-rw-r--r--libraries/engines/mrg_myisam.lib.php33
-rw-r--r--libraries/engines/myisam.lib.php69
-rw-r--r--libraries/engines/ndbcluster.lib.php55
-rw-r--r--libraries/engines/pbxt.lib.php167
-rw-r--r--libraries/engines/performance_schema.lib.php28
-rw-r--r--libraries/error.inc.php59
-rw-r--r--libraries/error_report.lib.php358
-rw-r--r--libraries/file_listing.lib.php99
-rw-r--r--libraries/gis/pma_gis_factory.php63
-rw-r--r--libraries/gis/pma_gis_geometry.php361
-rw-r--r--libraries/gis/pma_gis_geometrycollection.php336
-rw-r--r--libraries/gis/pma_gis_linestring.php298
-rw-r--r--libraries/gis/pma_gis_multilinestring.php370
-rw-r--r--libraries/gis/pma_gis_multipoint.php343
-rw-r--r--libraries/gis/pma_gis_multipolygon.php527
-rw-r--r--libraries/gis/pma_gis_point.php294
-rw-r--r--libraries/gis/pma_gis_polygon.php549
-rw-r--r--libraries/gis/pma_gis_visualization.php503
-rw-r--r--libraries/iconv_wrapper.lib.php120
-rw-r--r--libraries/import.lib.php1282
-rw-r--r--libraries/index.lib.php49
-rw-r--r--libraries/information_schema_relations.lib.php332
-rw-r--r--libraries/insert_edit.lib.php2920
-rw-r--r--libraries/ip_allow_deny.lib.php334
-rw-r--r--libraries/js_escape.lib.php133
-rw-r--r--libraries/kanji-encoding.lib.php168
-rw-r--r--libraries/language_stats.inc.php80
-rw-r--r--libraries/logging.lib.php30
-rw-r--r--libraries/mime.lib.php34
-rw-r--r--libraries/mult_submits.inc.php254
-rw-r--r--libraries/mult_submits.lib.php627
-rw-r--r--libraries/mysql_charsets.inc.php143
-rw-r--r--libraries/mysql_charsets.lib.php354
-rw-r--r--libraries/navigation/Navigation.class.php211
-rw-r--r--libraries/navigation/NavigationHeader.class.php310
-rw-r--r--libraries/navigation/NavigationTree.class.php1167
-rw-r--r--libraries/navigation/NodeFactory.class.php97
-rw-r--r--libraries/navigation/Nodes/Node.class.php441
-rw-r--r--libraries/navigation/Nodes/Node_Column.class.php46
-rw-r--r--libraries/navigation/Nodes/Node_Column_Container.class.php57
-rw-r--r--libraries/navigation/Nodes/Node_Database.class.php327
-rw-r--r--libraries/navigation/Nodes/Node_DatabaseChild.class.php52
-rw-r--r--libraries/navigation/Nodes/Node_Database_Container.class.php49
-rw-r--r--libraries/navigation/Nodes/Node_Event.class.php57
-rw-r--r--libraries/navigation/Nodes/Node_Event_Container.class.php54
-rw-r--r--libraries/navigation/Nodes/Node_Function.class.php57
-rw-r--r--libraries/navigation/Nodes/Node_Function_Container.class.php53
-rw-r--r--libraries/navigation/Nodes/Node_Index.class.php45
-rw-r--r--libraries/navigation/Nodes/Node_Index_Container.class.php55
-rw-r--r--libraries/navigation/Nodes/Node_Procedure.class.php57
-rw-r--r--libraries/navigation/Nodes/Node_Procedure_Container.class.php55
-rw-r--r--libraries/navigation/Nodes/Node_Table.class.php186
-rw-r--r--libraries/navigation/Nodes/Node_Table_Container.class.php60
-rw-r--r--libraries/navigation/Nodes/Node_Trigger.class.php45
-rw-r--r--libraries/navigation/Nodes/Node_Trigger_Container.class.php55
-rw-r--r--libraries/navigation/Nodes/Node_View.class.php57
-rw-r--r--libraries/navigation/Nodes/Node_View_Container.class.php60
-rw-r--r--libraries/opendocument.lib.php171
-rw-r--r--libraries/operations.lib.php1619
-rw-r--r--libraries/parse_analyze.inc.php144
-rw-r--r--libraries/php-gettext/gettext.inc536
-rw-r--r--libraries/php-gettext/gettext.php432
-rw-r--r--libraries/php-gettext/streams.php167
-rw-r--r--libraries/phpseclib/Crypt/AES.php612
-rw-r--r--libraries/phpseclib/Crypt/Rijndael.php1479
-rw-r--r--libraries/plugin_interface.lib.php507
-rw-r--r--libraries/plugins/AuthenticationPlugin.class.php51
-rw-r--r--libraries/plugins/ExportPlugin.class.php206
-rw-r--r--libraries/plugins/ImportPlugin.class.php59
-rw-r--r--libraries/plugins/PluginManager.class.php132
-rw-r--r--libraries/plugins/PluginObserver.class.php91
-rw-r--r--libraries/plugins/TransformationsInterface.int.php49
-rw-r--r--libraries/plugins/TransformationsPlugin.class.php51
-rw-r--r--libraries/plugins/UploadInterface.int.php36
-rw-r--r--libraries/plugins/auth/AuthenticationConfig.class.php174
-rw-r--r--libraries/plugins/auth/AuthenticationCookie.class.php807
-rw-r--r--libraries/plugins/auth/AuthenticationHttp.class.php261
-rw-r--r--libraries/plugins/auth/AuthenticationSignon.class.php299
-rw-r--r--libraries/plugins/auth/recaptchalib.php277
-rw-r--r--libraries/plugins/auth/swekey/authentication.inc.php172
-rw-r--r--libraries/plugins/auth/swekey/musbe-ca.crt25
-rw-r--r--libraries/plugins/auth/swekey/swekey.auth.lib.php302
-rw-r--r--libraries/plugins/auth/swekey/swekey.php522
-rw-r--r--libraries/plugins/export/ExportCodegen.class.php423
-rw-r--r--libraries/plugins/export/ExportCsv.class.php321
-rw-r--r--libraries/plugins/export/ExportExcel.class.php105
-rw-r--r--libraries/plugins/export/ExportHtmlword.class.php647
-rw-r--r--libraries/plugins/export/ExportJson.class.php210
-rw-r--r--libraries/plugins/export/ExportLatex.class.php657
-rw-r--r--libraries/plugins/export/ExportMediawiki.class.php366
-rw-r--r--libraries/plugins/export/ExportOds.class.php336
-rw-r--r--libraries/plugins/export/ExportOdt.class.php734
-rw-r--r--libraries/plugins/export/ExportPdf.class.php276
-rw-r--r--libraries/plugins/export/ExportPhparray.class.php229
-rw-r--r--libraries/plugins/export/ExportSql.class.php2017
-rw-r--r--libraries/plugins/export/ExportTexytext.class.php576
-rw-r--r--libraries/plugins/export/ExportXml.class.php546
-rw-r--r--libraries/plugins/export/ExportYaml.class.php216
-rw-r--r--libraries/plugins/export/PMA_ExportPdf.class.php426
-rw-r--r--libraries/plugins/export/README276
-rw-r--r--libraries/plugins/export/TableProperty.class.php286
-rw-r--r--libraries/plugins/import/AbstractImportCsv.class.php90
-rw-r--r--libraries/plugins/import/ImportCsv.class.php608
-rw-r--r--libraries/plugins/import/ImportLdi.class.php174
-rw-r--r--libraries/plugins/import/ImportMediawiki.class.php573
-rw-r--r--libraries/plugins/import/ImportOds.class.php415
-rw-r--r--libraries/plugins/import/ImportShp.class.php335
-rw-r--r--libraries/plugins/import/ImportSql.class.php447
-rw-r--r--libraries/plugins/import/ImportXml.class.php379
-rw-r--r--libraries/plugins/import/README172
-rw-r--r--libraries/plugins/import/ShapeFile.class.php102
-rw-r--r--libraries/plugins/import/ShapeRecord.class.php161
-rw-r--r--libraries/plugins/import/upload/UploadApc.class.php84
-rw-r--r--libraries/plugins/import/upload/UploadNoplugin.class.php64
-rw-r--r--libraries/plugins/import/upload/UploadProgress.class.php94
-rw-r--r--libraries/plugins/import/upload/UploadSession.class.php96
-rw-r--r--libraries/plugins/transformations/Application_Octetstream_Download.class.php43
-rw-r--r--libraries/plugins/transformations/Application_Octetstream_Hex.class.php44
-rw-r--r--libraries/plugins/transformations/Image_JPEG_Inline.class.php44
-rw-r--r--libraries/plugins/transformations/Image_JPEG_Link.class.php44
-rw-r--r--libraries/plugins/transformations/Image_PNG_Inline.class.php44
-rw-r--r--libraries/plugins/transformations/README4
-rw-r--r--libraries/plugins/transformations/TEMPLATE46
-rw-r--r--libraries/plugins/transformations/TEMPLATE_ABSTRACT89
-rw-r--r--libraries/plugins/transformations/Text_Plain_Append.class.php45
-rw-r--r--libraries/plugins/transformations/Text_Plain_Dateformat.class.php44
-rw-r--r--libraries/plugins/transformations/Text_Plain_External.class.php44
-rw-r--r--libraries/plugins/transformations/Text_Plain_Formatted.class.php44
-rw-r--r--libraries/plugins/transformations/Text_Plain_Imagelink.class.php44
-rw-r--r--libraries/plugins/transformations/Text_Plain_Link.class.php44
-rw-r--r--libraries/plugins/transformations/Text_Plain_Longtoipv4.class.php44
-rw-r--r--libraries/plugins/transformations/Text_Plain_Sql.class.php44
-rw-r--r--libraries/plugins/transformations/Text_Plain_Substring.class.php44
-rw-r--r--libraries/plugins/transformations/abstract/AppendTransformationsPlugin.class.php84
-rw-r--r--libraries/plugins/transformations/abstract/DateFormatTransformationsPlugin.class.php178
-rw-r--r--libraries/plugins/transformations/abstract/DownloadTransformationsPlugin.class.php110
-rw-r--r--libraries/plugins/transformations/abstract/ExternalTransformationsPlugin.class.php182
-rw-r--r--libraries/plugins/transformations/abstract/FormattedTransformationsPlugin.class.php80
-rw-r--r--libraries/plugins/transformations/abstract/HexTransformationsPlugin.class.php91
-rw-r--r--libraries/plugins/transformations/abstract/ImageLinkTransformationsPlugin.class.php87
-rw-r--r--libraries/plugins/transformations/abstract/InlineTransformationsPlugin.class.php101
-rw-r--r--libraries/plugins/transformations/abstract/LongToIPv4TransformationsPlugin.class.php83
-rw-r--r--libraries/plugins/transformations/abstract/SQLTransformationsPlugin.class.php82
-rw-r--r--libraries/plugins/transformations/abstract/SubstringTransformationsPlugin.class.php118
-rw-r--r--libraries/plugins/transformations/abstract/TextImageLinkTransformationsPlugin.class.php96
-rw-r--r--libraries/plugins/transformations/abstract/TextLinkTransformationsPlugin.class.php98
-rw-r--r--libraries/plugins/transformations/generator_main_class.sh16
-rw-r--r--libraries/plugins/transformations/generator_plugin.sh64
-rw-r--r--libraries/pmd_common.php284
-rw-r--r--libraries/properties/PropertyItem.class.php49
-rw-r--r--libraries/properties/options/OptionsPropertyGroup.class.php102
-rw-r--r--libraries/properties/options/OptionsPropertyItem.class.php127
-rw-r--r--libraries/properties/options/OptionsPropertyOneItem.class.php172
-rw-r--r--libraries/properties/options/groups/OptionsPropertyMainGroup.class.php35
-rw-r--r--libraries/properties/options/groups/OptionsPropertyRootGroup.class.php35
-rw-r--r--libraries/properties/options/groups/OptionsPropertySubgroup.class.php68
-rw-r--r--libraries/properties/options/items/BoolPropertyItem.class.php35
-rw-r--r--libraries/properties/options/items/DocPropertyItem.class.php35
-rw-r--r--libraries/properties/options/items/HiddenPropertyItem.class.php35
-rw-r--r--libraries/properties/options/items/MessageOnlyPropertyItem.class.php35
-rw-r--r--libraries/properties/options/items/NumberPropertyItem.class.php35
-rw-r--r--libraries/properties/options/items/RadioPropertyItem.class.php35
-rw-r--r--libraries/properties/options/items/SelectPropertyItem.class.php35
-rw-r--r--libraries/properties/options/items/TextPropertyItem.class.php35
-rw-r--r--libraries/properties/plugins/ExportPluginProperties.class.php214
-rw-r--r--libraries/properties/plugins/ImportPluginProperties.class.php184
-rw-r--r--libraries/properties/plugins/PluginPropertyItem.class.php36
-rw-r--r--libraries/relation.lib.php1497
-rw-r--r--libraries/relation_cleanup.lib.php183
-rw-r--r--libraries/replication.inc.php288
-rw-r--r--libraries/replication_gui.lib.php1071
-rw-r--r--libraries/rte/rte_events.lib.php637
-rw-r--r--libraries/rte/rte_export.lib.php122
-rw-r--r--libraries/rte/rte_footer.lib.php127
-rw-r--r--libraries/rte/rte_list.lib.php386
-rw-r--r--libraries/rte/rte_main.inc.php96
-rw-r--r--libraries/rte/rte_routines.lib.php1702
-rw-r--r--libraries/rte/rte_triggers.lib.php486
-rw-r--r--libraries/rte/rte_words.lib.php69
-rw-r--r--libraries/sanitizing.lib.php191
-rw-r--r--libraries/schema/Dia_Relation_Schema.class.php847
-rw-r--r--libraries/schema/Eps_Relation_Schema.class.php973
-rw-r--r--libraries/schema/Export_Relation_Schema.class.php251
-rw-r--r--libraries/schema/Pdf_Relation_Schema.class.php1467
-rw-r--r--libraries/schema/Svg_Relation_Schema.class.php1014
-rw-r--r--libraries/schema/User_Schema.class.php979
-rw-r--r--libraries/select_lang.lib.php571
-rw-r--r--libraries/select_server.lib.php111
-rw-r--r--libraries/server_bin_log.lib.php245
-rw-r--r--libraries/server_collations.lib.php110
-rw-r--r--libraries/server_common.inc.php51
-rw-r--r--libraries/server_common.lib.php67
-rw-r--r--libraries/server_databases.lib.php509
-rw-r--r--libraries/server_engines.lib.php146
-rw-r--r--libraries/server_plugins.lib.php205
-rw-r--r--libraries/server_privileges.lib.php4235
-rw-r--r--libraries/server_status.lib.php546
-rw-r--r--libraries/server_status_advisor.lib.php70
-rw-r--r--libraries/server_status_monitor.lib.php798
-rw-r--r--libraries/server_status_queries.lib.php153
-rw-r--r--libraries/server_status_variables.lib.php765
-rw-r--r--libraries/server_user_groups.lib.php355
-rw-r--r--libraries/server_users.lib.php55
-rw-r--r--libraries/server_variables.lib.php1684
-rw-r--r--libraries/session.inc.php125
-rw-r--r--libraries/special_schema_links.lib.php406
-rw-r--r--libraries/sql.lib.php2387
-rw-r--r--libraries/sql_query_form.lib.php520
-rw-r--r--libraries/sqlparser.data.php990
-rw-r--r--libraries/sqlparser.lib.php2807
-rw-r--r--libraries/sqlvalidator.class.php457
-rw-r--r--libraries/sqlvalidator.lib.php107
-rw-r--r--libraries/string.inc.php18
-rw-r--r--libraries/structure.lib.php2673
-rw-r--r--libraries/sysinfo.lib.php361
-rw-r--r--libraries/tbl_chart.lib.php295
-rw-r--r--libraries/tbl_columns_definition_form.inc.php159
-rw-r--r--libraries/tbl_columns_definition_form.lib.php1283
-rw-r--r--libraries/tbl_common.inc.php63
-rw-r--r--libraries/tbl_gis_visualization.lib.php358
-rw-r--r--libraries/tbl_indexes.lib.php406
-rw-r--r--libraries/tbl_info.inc.php105
-rw-r--r--libraries/tbl_printview.lib.php600
-rw-r--r--libraries/tbl_relation.lib.php1036
-rw-r--r--libraries/tbl_tracking.lib.php1210
-rw-r--r--libraries/tbl_views.lib.php159
-rw-r--r--libraries/tcpdf/LICENSE.TXT858
-rw-r--r--libraries/tcpdf/README.TXT111
-rw-r--r--libraries/tcpdf/config/tcpdf_config.php219
-rw-r--r--libraries/tcpdf/fonts/dejavu-fonts-ttf-2.33/LICENSE99
-rw-r--r--libraries/tcpdf/fonts/dejavusans.ctg.zbin0 -> 10120 bytes
-rw-r--r--libraries/tcpdf/fonts/dejavusans.php15
-rw-r--r--libraries/tcpdf/fonts/dejavusans.zbin0 -> 361229 bytes
-rw-r--r--libraries/tcpdf/fonts/dejavusansb.ctg.zbin0 -> 9854 bytes
-rw-r--r--libraries/tcpdf/fonts/dejavusansb.php15
-rw-r--r--libraries/tcpdf/fonts/dejavusansb.zbin0 -> 333391 bytes
-rw-r--r--libraries/tcpdf/fonts/helvetica.php13
-rw-r--r--libraries/tcpdf/include/tcpdf_colors.php462
-rw-r--r--libraries/tcpdf/include/tcpdf_font_data.php18447
-rw-r--r--libraries/tcpdf/include/tcpdf_fonts.php2562
-rw-r--r--libraries/tcpdf/include/tcpdf_images.php355
-rw-r--r--libraries/tcpdf/include/tcpdf_static.php2837
-rw-r--r--libraries/tcpdf/tcpdf.php24121
-rw-r--r--libraries/tcpdf/tcpdf_autoconfig.php233
-rw-r--r--libraries/transformations.lib.php419
-rw-r--r--libraries/url_generating.lib.php303
-rw-r--r--libraries/user_preferences.inc.php71
-rw-r--r--libraries/user_preferences.lib.php302
-rw-r--r--libraries/vendor_config.php83
-rw-r--r--libraries/zip.lib.php211
-rw-r--r--libraries/zip_extension.lib.php188
-rw-r--r--license.php31
-rw-r--r--locale/ar/LC_MESSAGES/phpmyadmin.mobin0 -> 111731 bytes
-rw-r--r--locale/bg/LC_MESSAGES/phpmyadmin.mobin0 -> 223616 bytes
-rw-r--r--locale/bn/LC_MESSAGES/phpmyadmin.mobin0 -> 423933 bytes
-rw-r--r--locale/ca/LC_MESSAGES/phpmyadmin.mobin0 -> 298023 bytes
-rw-r--r--locale/cs/LC_MESSAGES/phpmyadmin.mobin0 -> 307610 bytes
-rw-r--r--locale/da/LC_MESSAGES/phpmyadmin.mobin0 -> 302049 bytes
-rw-r--r--locale/de/LC_MESSAGES/phpmyadmin.mobin0 -> 316071 bytes
-rw-r--r--locale/el/LC_MESSAGES/phpmyadmin.mobin0 -> 424358 bytes
-rw-r--r--locale/en_GB/LC_MESSAGES/phpmyadmin.mobin0 -> 290452 bytes
-rw-r--r--locale/es/LC_MESSAGES/phpmyadmin.mobin0 -> 320282 bytes
-rw-r--r--locale/et/LC_MESSAGES/phpmyadmin.mobin0 -> 297377 bytes
-rw-r--r--locale/fi/LC_MESSAGES/phpmyadmin.mobin0 -> 195174 bytes
-rw-r--r--locale/fr/LC_MESSAGES/phpmyadmin.mobin0 -> 318921 bytes
-rw-r--r--locale/gl/LC_MESSAGES/phpmyadmin.mobin0 -> 306061 bytes
-rw-r--r--locale/hi/LC_MESSAGES/phpmyadmin.mobin0 -> 167058 bytes
-rw-r--r--locale/hu/LC_MESSAGES/phpmyadmin.mobin0 -> 314365 bytes
-rw-r--r--locale/ia/LC_MESSAGES/phpmyadmin.mobin0 -> 107660 bytes
-rw-r--r--locale/id/LC_MESSAGES/phpmyadmin.mobin0 -> 205341 bytes
-rw-r--r--locale/it/LC_MESSAGES/phpmyadmin.mobin0 -> 279429 bytes
-rw-r--r--locale/ja/LC_MESSAGES/phpmyadmin.mobin0 -> 311621 bytes
-rw-r--r--locale/ko/LC_MESSAGES/phpmyadmin.mobin0 -> 215386 bytes
-rw-r--r--locale/lt/LC_MESSAGES/phpmyadmin.mobin0 -> 148485 bytes
-rw-r--r--locale/nb/LC_MESSAGES/phpmyadmin.mobin0 -> 179028 bytes
-rw-r--r--locale/nl/LC_MESSAGES/phpmyadmin.mobin0 -> 310556 bytes
-rw-r--r--locale/pl/LC_MESSAGES/phpmyadmin.mobin0 -> 292529 bytes
-rw-r--r--locale/pt/LC_MESSAGES/phpmyadmin.mobin0 -> 169065 bytes
-rw-r--r--locale/pt_BR/LC_MESSAGES/phpmyadmin.mobin0 -> 313684 bytes
-rw-r--r--locale/ro/LC_MESSAGES/phpmyadmin.mobin0 -> 160369 bytes
-rw-r--r--locale/ru/LC_MESSAGES/phpmyadmin.mobin0 -> 394719 bytes
-rw-r--r--locale/si/LC_MESSAGES/phpmyadmin.mobin0 -> 296932 bytes
-rw-r--r--locale/sk/LC_MESSAGES/phpmyadmin.mobin0 -> 230489 bytes
-rw-r--r--locale/sl/LC_MESSAGES/phpmyadmin.mobin0 -> 305108 bytes
-rw-r--r--locale/sr@latin/LC_MESSAGES/phpmyadmin.mobin0 -> 155730 bytes
-rw-r--r--locale/sv/LC_MESSAGES/phpmyadmin.mobin0 -> 301594 bytes
-rw-r--r--locale/th/LC_MESSAGES/phpmyadmin.mobin0 -> 135909 bytes
-rw-r--r--locale/tr/LC_MESSAGES/phpmyadmin.mobin0 -> 314124 bytes
-rw-r--r--locale/uk/LC_MESSAGES/phpmyadmin.mobin0 -> 366123 bytes
-rw-r--r--locale/uz/LC_MESSAGES/phpmyadmin.mobin0 -> 174395 bytes
-rw-r--r--locale/uz@latin/LC_MESSAGES/phpmyadmin.mobin0 -> 133098 bytes
-rw-r--r--locale/zh_CN/LC_MESSAGES/phpmyadmin.mobin0 -> 267548 bytes
-rw-r--r--locale/zh_TW/LC_MESSAGES/phpmyadmin.mobin0 -> 286126 bytes
-rw-r--r--navigation.php71
-rw-r--r--phpinfo.php21
-rw-r--r--phpmyadmin.css.php31
-rw-r--r--phpunit.xml.nocoverage49
-rw-r--r--pmd_display_field.php61
-rw-r--r--pmd_general.php877
-rw-r--r--pmd_pdf.php163
-rw-r--r--pmd_relation_new.php127
-rw-r--r--pmd_relation_upd.php69
-rw-r--r--pmd_save_pos.php90
-rw-r--r--prefs_forms.php92
-rw-r--r--prefs_manage.php333
-rw-r--r--print.css92
-rw-r--r--querywindow.php207
-rw-r--r--robots.txt2
-rw-r--r--schema_edit.php129
-rw-r--r--schema_export.php74
-rw-r--r--server_binlog.php54
-rw-r--r--server_collations.php39
-rw-r--r--server_databases.php118
-rw-r--r--server_engines.php34
-rw-r--r--server_export.php29
-rw-r--r--server_import.php27
-rw-r--r--server_plugins.php59
-rw-r--r--server_privileges.php380
-rw-r--r--server_replication.php83
-rw-r--r--server_sql.php31
-rw-r--r--server_status.php54
-rw-r--r--server_status_advisor.php39
-rw-r--r--server_status_monitor.php116
-rw-r--r--server_status_queries.php53
-rw-r--r--server_status_variables.php56
-rw-r--r--server_user_groups.php64
-rw-r--r--server_variables.php60
-rw-r--r--show_config_errors.php40
-rw-r--r--sql.php199
-rw-r--r--tbl_addfield.php117
-rw-r--r--tbl_change.php214
-rw-r--r--tbl_chart.php143
-rw-r--r--tbl_create.php94
-rw-r--r--tbl_export.php87
-rw-r--r--tbl_find_replace.php63
-rw-r--r--tbl_get_field.php55
-rw-r--r--tbl_gis_visualization.php119
-rw-r--r--tbl_import.php31
-rw-r--r--tbl_indexes.php48
-rw-r--r--tbl_move_copy.php104
-rw-r--r--tbl_operations.php408
-rw-r--r--tbl_printview.php73
-rw-r--r--tbl_relation.php160
-rw-r--r--tbl_replace.php402
-rw-r--r--tbl_row_action.php136
-rw-r--r--tbl_select.php71
-rw-r--r--tbl_sql.php51
-rw-r--r--tbl_structure.php164
-rw-r--r--tbl_tracking.php153
-rw-r--r--tbl_triggers.php10
-rw-r--r--tbl_zoom_select.php163
-rw-r--r--themes.php32
-rw-r--r--themes/dot.gifbin0 -> 43 bytes
-rw-r--r--themes/original/css/common.css.php2564
-rw-r--r--themes/original/css/navigation.css.php296
-rw-r--r--themes/original/img/ajax_clock_small.gifbin0 -> 1810 bytes
-rw-r--r--themes/original/img/arrow_ltr.pngbin0 -> 139 bytes
-rw-r--r--themes/original/img/arrow_rtl.pngbin0 -> 136 bytes
-rw-r--r--themes/original/img/b_bookmark.pngbin0 -> 252 bytes
-rw-r--r--themes/original/img/b_browse.pngbin0 -> 157 bytes
-rw-r--r--themes/original/img/b_calendar.pngbin0 -> 203 bytes
-rw-r--r--themes/original/img/b_chart.pngbin0 -> 402 bytes
-rw-r--r--themes/original/img/b_close.pngbin0 -> 180 bytes
-rw-r--r--themes/original/img/b_column_add.pngbin0 -> 534 bytes
-rw-r--r--themes/original/img/b_comment.pngbin0 -> 363 bytes
-rw-r--r--themes/original/img/b_dbstatistics.pngbin0 -> 157 bytes
-rw-r--r--themes/original/img/b_deltbl.pngbin0 -> 239 bytes
-rw-r--r--themes/original/img/b_docs.pngbin0 -> 184 bytes
-rw-r--r--themes/original/img/b_drop.pngbin0 -> 184 bytes
-rw-r--r--themes/original/img/b_edit.pngbin0 -> 305 bytes
-rw-r--r--themes/original/img/b_empty.pngbin0 -> 186 bytes
-rw-r--r--themes/original/img/b_engine.pngbin0 -> 232 bytes
-rw-r--r--themes/original/img/b_event_add.pngbin0 -> 863 bytes
-rw-r--r--themes/original/img/b_events.pngbin0 -> 783 bytes
-rw-r--r--themes/original/img/b_export.pngbin0 -> 199 bytes
-rw-r--r--themes/original/img/b_find_replace.pngbin0 -> 1209 bytes
-rw-r--r--themes/original/img/b_ftext.pngbin0 -> 175 bytes
-rw-r--r--themes/original/img/b_globe.gifbin0 -> 1045 bytes
-rw-r--r--themes/original/img/b_group.pngbin0 -> 796 bytes
-rw-r--r--themes/original/img/b_help.pngbin0 -> 145 bytes
-rw-r--r--themes/original/img/b_home.pngbin0 -> 238 bytes
-rw-r--r--themes/original/img/b_import.pngbin0 -> 202 bytes
-rw-r--r--themes/original/img/b_index.pngbin0 -> 207 bytes
-rw-r--r--themes/original/img/b_index_add.pngbin0 -> 429 bytes
-rw-r--r--themes/original/img/b_info.pngbin0 -> 147 bytes
-rw-r--r--themes/original/img/b_inline_edit.pngbin0 -> 302 bytes
-rw-r--r--themes/original/img/b_insrow.pngbin0 -> 183 bytes
-rw-r--r--themes/original/img/b_minus.pngbin0 -> 110 bytes
-rw-r--r--themes/original/img/b_more.pngbin0 -> 132 bytes
-rw-r--r--themes/original/img/b_move.pngbin0 -> 168 bytes
-rw-r--r--themes/original/img/b_newdb.pngbin0 -> 260 bytes
-rw-r--r--themes/original/img/b_newtbl.pngbin0 -> 264 bytes
-rw-r--r--themes/original/img/b_nextpage.pngbin0 -> 373 bytes
-rw-r--r--themes/original/img/b_plus.pngbin0 -> 115 bytes
-rw-r--r--themes/original/img/b_primary.pngbin0 -> 278 bytes
-rw-r--r--themes/original/img/b_print.pngbin0 -> 396 bytes
-rw-r--r--themes/original/img/b_props.pngbin0 -> 188 bytes
-rw-r--r--themes/original/img/b_relations.pngbin0 -> 172 bytes
-rw-r--r--themes/original/img/b_routine_add.pngbin0 -> 409 bytes
-rw-r--r--themes/original/img/b_routines.pngbin0 -> 310 bytes
-rw-r--r--themes/original/img/b_save.pngbin0 -> 844 bytes
-rw-r--r--themes/original/img/b_sbrowse.pngbin0 -> 122 bytes
-rw-r--r--themes/original/img/b_search.pngbin0 -> 442 bytes
-rw-r--r--themes/original/img/b_selboard.pngbin0 -> 175 bytes
-rw-r--r--themes/original/img/b_select.pngbin0 -> 387 bytes
-rw-r--r--themes/original/img/b_snewtbl.pngbin0 -> 168 bytes
-rw-r--r--themes/original/img/b_spatial.pngbin0 -> 379 bytes
-rw-r--r--themes/original/img/b_sql.pngbin0 -> 208 bytes
-rw-r--r--themes/original/img/b_sqlhelp.pngbin0 -> 173 bytes
-rw-r--r--themes/original/img/b_table_add.pngbin0 -> 368 bytes
-rw-r--r--themes/original/img/b_tblanalyse.pngbin0 -> 188 bytes
-rw-r--r--themes/original/img/b_tblexport.pngbin0 -> 176 bytes
-rw-r--r--themes/original/img/b_tblimport.pngbin0 -> 174 bytes
-rw-r--r--themes/original/img/b_tblops.pngbin0 -> 220 bytes
-rw-r--r--themes/original/img/b_tbloptimize.pngbin0 -> 198 bytes
-rw-r--r--themes/original/img/b_tipp.pngbin0 -> 201 bytes
-rw-r--r--themes/original/img/b_trigger_add.pngbin0 -> 615 bytes
-rw-r--r--themes/original/img/b_triggers.pngbin0 -> 494 bytes
-rw-r--r--themes/original/img/b_undo.pngbin0 -> 1306 bytes
-rw-r--r--themes/original/img/b_unique.pngbin0 -> 175 bytes
-rw-r--r--themes/original/img/b_usradd.pngbin0 -> 352 bytes
-rw-r--r--themes/original/img/b_usrcheck.pngbin0 -> 259 bytes
-rw-r--r--themes/original/img/b_usrdrop.pngbin0 -> 289 bytes
-rw-r--r--themes/original/img/b_usredit.pngbin0 -> 339 bytes
-rw-r--r--themes/original/img/b_usrlist.pngbin0 -> 262 bytes
-rw-r--r--themes/original/img/b_view.pngbin0 -> 646 bytes
-rw-r--r--themes/original/img/b_view_add.pngbin0 -> 726 bytes
-rw-r--r--themes/original/img/b_views.pngbin0 -> 326 bytes
-rw-r--r--themes/original/img/bd_browse.pngbin0 -> 157 bytes
-rw-r--r--themes/original/img/bd_deltbl.pngbin0 -> 278 bytes
-rw-r--r--themes/original/img/bd_drop.pngbin0 -> 205 bytes
-rw-r--r--themes/original/img/bd_edit.pngbin0 -> 226 bytes
-rw-r--r--themes/original/img/bd_empty.pngbin0 -> 186 bytes
-rw-r--r--themes/original/img/bd_export.pngbin0 -> 183 bytes
-rw-r--r--themes/original/img/bd_ftext.pngbin0 -> 175 bytes
-rw-r--r--themes/original/img/bd_index.pngbin0 -> 207 bytes
-rw-r--r--themes/original/img/bd_insrow.pngbin0 -> 224 bytes
-rw-r--r--themes/original/img/bd_nextpage.pngbin0 -> 110 bytes
-rw-r--r--themes/original/img/bd_primary.pngbin0 -> 257 bytes
-rw-r--r--themes/original/img/bd_sbrowse.pngbin0 -> 122 bytes
-rw-r--r--themes/original/img/bd_select.pngbin0 -> 375 bytes
-rw-r--r--themes/original/img/bd_spatial.pngbin0 -> 344 bytes
-rw-r--r--themes/original/img/bd_unique.pngbin0 -> 175 bytes
-rw-r--r--themes/original/img/cleardot.gifbin0 -> 43 bytes
-rw-r--r--themes/original/img/col_drop.pngbin0 -> 132 bytes
-rw-r--r--themes/original/img/col_pointer.pngbin0 -> 101 bytes
-rw-r--r--themes/original/img/col_pointer_ver.pngbin0 -> 111 bytes
-rw-r--r--themes/original/img/east-mini.pngbin0 -> 322 bytes
-rw-r--r--themes/original/img/error.icobin0 -> 1150 bytes
-rw-r--r--themes/original/img/eye.pngbin0 -> 721 bytes
-rw-r--r--themes/original/img/eye_grey.pngbin0 -> 330 bytes
-rw-r--r--themes/original/img/lightbulb.pngbin0 -> 782 bytes
-rw-r--r--themes/original/img/lightbulb_off.pngbin0 -> 700 bytes
-rw-r--r--themes/original/img/logo_left.pngbin0 -> 6796 bytes
-rw-r--r--themes/original/img/logo_right.pngbin0 -> 4548 bytes
-rw-r--r--themes/original/img/more.pngbin0 -> 117 bytes
-rw-r--r--themes/original/img/new_data.pngbin0 -> 264 bytes
-rw-r--r--themes/original/img/new_data_hovered.pngbin0 -> 264 bytes
-rw-r--r--themes/original/img/new_data_selected.pngbin0 -> 179 bytes
-rw-r--r--themes/original/img/new_data_selected_hovered.pngbin0 -> 181 bytes
-rw-r--r--themes/original/img/new_struct.pngbin0 -> 301 bytes
-rw-r--r--themes/original/img/new_struct_hovered.pngbin0 -> 301 bytes
-rw-r--r--themes/original/img/new_struct_selected.pngbin0 -> 189 bytes
-rw-r--r--themes/original/img/new_struct_selected_hovered.pngbin0 -> 196 bytes
-rw-r--r--themes/original/img/north-mini.pngbin0 -> 327 bytes
-rw-r--r--themes/original/img/pause.pngbin0 -> 271 bytes
-rw-r--r--themes/original/img/play.pngbin0 -> 373 bytes
-rw-r--r--themes/original/img/s_asc.pngbin0 -> 128 bytes
-rw-r--r--themes/original/img/s_asci.pngbin0 -> 136 bytes
-rw-r--r--themes/original/img/s_cancel.pngbin0 -> 310 bytes
-rw-r--r--themes/original/img/s_cog.pngbin0 -> 222 bytes
-rw-r--r--themes/original/img/s_db.pngbin0 -> 180 bytes
-rw-r--r--themes/original/img/s_desc.pngbin0 -> 137 bytes
-rw-r--r--themes/original/img/s_error.pngbin0 -> 173 bytes
-rw-r--r--themes/original/img/s_error2.pngbin0 -> 152 bytes
-rw-r--r--themes/original/img/s_fulltext.pngbin0 -> 193 bytes
-rw-r--r--themes/original/img/s_host.pngbin0 -> 209 bytes
-rw-r--r--themes/original/img/s_info.pngbin0 -> 147 bytes
-rw-r--r--themes/original/img/s_lang.pngbin0 -> 281 bytes
-rw-r--r--themes/original/img/s_loggoff.pngbin0 -> 163 bytes
-rw-r--r--themes/original/img/s_notice.pngbin0 -> 151 bytes
-rw-r--r--themes/original/img/s_partialtext.pngbin0 -> 196 bytes
-rw-r--r--themes/original/img/s_passwd.pngbin0 -> 353 bytes
-rw-r--r--themes/original/img/s_really.pngbin0 -> 145 bytes
-rw-r--r--themes/original/img/s_reload.pngbin0 -> 285 bytes
-rw-r--r--themes/original/img/s_replication.pngbin0 -> 424 bytes
-rw-r--r--themes/original/img/s_rights.pngbin0 -> 355 bytes
-rw-r--r--themes/original/img/s_sortable.pngbin0 -> 132 bytes
-rw-r--r--themes/original/img/s_status.pngbin0 -> 198 bytes
-rw-r--r--themes/original/img/s_success.pngbin0 -> 589 bytes
-rw-r--r--themes/original/img/s_sync.pngbin0 -> 498 bytes
-rw-r--r--themes/original/img/s_tbl.pngbin0 -> 142 bytes
-rw-r--r--themes/original/img/s_theme.pngbin0 -> 546 bytes
-rw-r--r--themes/original/img/s_top.pngbin0 -> 443 bytes
-rw-r--r--themes/original/img/s_vars.pngbin0 -> 190 bytes
-rw-r--r--themes/original/img/s_views.pngbin0 -> 235 bytes
-rw-r--r--themes/original/img/south-mini.pngbin0 -> 335 bytes
-rw-r--r--themes/original/img/spacer.pngbin0 -> 84 bytes
-rw-r--r--themes/original/img/sprites.pngbin0 -> 20509 bytes
-rw-r--r--themes/original/img/toggle-ltr.pngbin0 -> 177 bytes
-rw-r--r--themes/original/img/toggle-rtl.pngbin0 -> 177 bytes
-rw-r--r--themes/original/img/vertical_line.pngbin0 -> 68 bytes
-rw-r--r--themes/original/img/west-mini.pngbin0 -> 328 bytes
-rw-r--r--themes/original/img/window-new.pngbin0 -> 484 bytes
-rw-r--r--themes/original/img/zoom-minus-mini.pngbin0 -> 247 bytes
-rw-r--r--themes/original/img/zoom-plus-mini.pngbin0 -> 329 bytes
-rw-r--r--themes/original/img/zoom-world-mini.pngbin0 -> 808 bytes
-rw-r--r--themes/original/info.inc.php15
-rw-r--r--themes/original/jquery/images/ui-bg_flat_0_aaaaaa_40x100.pngbin0 -> 180 bytes
-rw-r--r--themes/original/jquery/images/ui-bg_flat_75_ffffff_40x100.pngbin0 -> 178 bytes
-rw-r--r--themes/original/jquery/images/ui-bg_glass_55_fbf9ee_1x400.pngbin0 -> 120 bytes
-rw-r--r--themes/original/jquery/images/ui-bg_glass_65_ffffff_1x400.pngbin0 -> 105 bytes
-rw-r--r--themes/original/jquery/images/ui-bg_glass_75_dadada_1x400.pngbin0 -> 159 bytes
-rw-r--r--themes/original/jquery/images/ui-bg_glass_75_e6e6e6_1x400.pngbin0 -> 110 bytes
-rw-r--r--themes/original/jquery/images/ui-bg_glass_95_fef1ec_1x400.pngbin0 -> 119 bytes
-rw-r--r--themes/original/jquery/images/ui-bg_highlight-soft_75_cccccc_1x100.pngbin0 -> 101 bytes
-rw-r--r--themes/original/jquery/images/ui-icons_222222_256x240.pngbin0 -> 4369 bytes
-rw-r--r--themes/original/jquery/images/ui-icons_2e83ff_256x240.pngbin0 -> 4369 bytes
-rw-r--r--themes/original/jquery/images/ui-icons_454545_256x240.pngbin0 -> 4369 bytes
-rw-r--r--themes/original/jquery/images/ui-icons_888888_256x240.pngbin0 -> 4369 bytes
-rw-r--r--themes/original/jquery/images/ui-icons_cd0a0a_256x240.pngbin0 -> 4369 bytes
-rw-r--r--themes/original/jquery/jquery-ui-1.9.2.custom.css462
-rw-r--r--themes/original/layout.inc.php110
-rw-r--r--themes/original/screen.pngbin0 -> 53043 bytes
-rw-r--r--themes/original/sprites.lib.php660
-rw-r--r--themes/pmahomme/css/codemirror.css.php303
-rw-r--r--themes/pmahomme/css/common.css.php2797
-rw-r--r--themes/pmahomme/css/enum_editor.css.php80
-rw-r--r--themes/pmahomme/css/gis.css.php65
-rw-r--r--themes/pmahomme/css/jqplot.css.php277
-rw-r--r--themes/pmahomme/css/navigation.css.php285
-rw-r--r--themes/pmahomme/css/pmd.css.php530
-rw-r--r--themes/pmahomme/css/resizable-menu.css.php57
-rw-r--r--themes/pmahomme/css/rte.css.php43
-rw-r--r--themes/pmahomme/img/ajax_clock_small.gifbin0 -> 1810 bytes
-rw-r--r--themes/pmahomme/img/arrow_ltr.pngbin0 -> 139 bytes
-rw-r--r--themes/pmahomme/img/arrow_rtl.pngbin0 -> 136 bytes
-rw-r--r--themes/pmahomme/img/asc_order.pngbin0 -> 182 bytes
-rw-r--r--themes/pmahomme/img/b_bookmark.pngbin0 -> 677 bytes
-rw-r--r--themes/pmahomme/img/b_browse.pngbin0 -> 536 bytes
-rw-r--r--themes/pmahomme/img/b_calendar.pngbin0 -> 638 bytes
-rw-r--r--themes/pmahomme/img/b_chart.pngbin0 -> 504 bytes
-rw-r--r--themes/pmahomme/img/b_close.pngbin0 -> 180 bytes
-rw-r--r--themes/pmahomme/img/b_column_add.pngbin0 -> 534 bytes
-rw-r--r--themes/pmahomme/img/b_comment.pngbin0 -> 454 bytes
-rw-r--r--themes/pmahomme/img/b_dbstatistics.pngbin0 -> 504 bytes
-rw-r--r--themes/pmahomme/img/b_deltbl.pngbin0 -> 623 bytes
-rw-r--r--themes/pmahomme/img/b_docs.pngbin0 -> 756 bytes
-rw-r--r--themes/pmahomme/img/b_docsql.pngbin0 -> 188 bytes
-rw-r--r--themes/pmahomme/img/b_drop.pngbin0 -> 687 bytes
-rw-r--r--themes/pmahomme/img/b_edit.pngbin0 -> 407 bytes
-rw-r--r--themes/pmahomme/img/b_empty.pngbin0 -> 615 bytes
-rw-r--r--themes/pmahomme/img/b_engine.pngbin0 -> 431 bytes
-rw-r--r--themes/pmahomme/img/b_event_add.pngbin0 -> 863 bytes
-rw-r--r--themes/pmahomme/img/b_events.pngbin0 -> 783 bytes
-rw-r--r--themes/pmahomme/img/b_export.pngbin0 -> 606 bytes
-rw-r--r--themes/pmahomme/img/b_find_replace.pngbin0 -> 883 bytes
-rw-r--r--themes/pmahomme/img/b_firstpage.pngbin0 -> 754 bytes
-rw-r--r--themes/pmahomme/img/b_ftext.pngbin0 -> 576 bytes
-rw-r--r--themes/pmahomme/img/b_globe.gifbin0 -> 1045 bytes
-rw-r--r--themes/pmahomme/img/b_group.pngbin0 -> 796 bytes
-rw-r--r--themes/pmahomme/img/b_help.pngbin0 -> 740 bytes
-rw-r--r--themes/pmahomme/img/b_home.pngbin0 -> 801 bytes
-rw-r--r--themes/pmahomme/img/b_import.pngbin0 -> 592 bytes
-rw-r--r--themes/pmahomme/img/b_index.pngbin0 -> 708 bytes
-rw-r--r--themes/pmahomme/img/b_index_add.pngbin0 -> 839 bytes
-rw-r--r--themes/pmahomme/img/b_info.pngbin0 -> 147 bytes
-rw-r--r--themes/pmahomme/img/b_inline_edit.pngbin0 -> 621 bytes
-rw-r--r--themes/pmahomme/img/b_insrow.pngbin0 -> 183 bytes
-rw-r--r--themes/pmahomme/img/b_lastpage.pngbin0 -> 746 bytes
-rw-r--r--themes/pmahomme/img/b_minus.pngbin0 -> 224 bytes
-rw-r--r--themes/pmahomme/img/b_more.pngbin0 -> 132 bytes
-rw-r--r--themes/pmahomme/img/b_move.pngbin0 -> 520 bytes
-rw-r--r--themes/pmahomme/img/b_newdb.pngbin0 -> 650 bytes
-rw-r--r--themes/pmahomme/img/b_newtbl.pngbin0 -> 264 bytes
-rw-r--r--themes/pmahomme/img/b_nextpage.pngbin0 -> 373 bytes
-rw-r--r--themes/pmahomme/img/b_pdfdoc.pngbin0 -> 595 bytes
-rw-r--r--themes/pmahomme/img/b_plus.pngbin0 -> 237 bytes
-rw-r--r--themes/pmahomme/img/b_prevpage.pngbin0 -> 369 bytes
-rw-r--r--themes/pmahomme/img/b_primary.pngbin0 -> 619 bytes
-rw-r--r--themes/pmahomme/img/b_print.pngbin0 -> 702 bytes
-rw-r--r--themes/pmahomme/img/b_props.pngbin0 -> 655 bytes
-rw-r--r--themes/pmahomme/img/b_relations.pngbin0 -> 172 bytes
-rw-r--r--themes/pmahomme/img/b_routine_add.pngbin0 -> 409 bytes
-rw-r--r--themes/pmahomme/img/b_routines.pngbin0 -> 310 bytes
-rw-r--r--themes/pmahomme/img/b_save.pngbin0 -> 585 bytes
-rw-r--r--themes/pmahomme/img/b_sbrowse.pngbin0 -> 536 bytes
-rw-r--r--themes/pmahomme/img/b_sdb.pngbin0 -> 148 bytes
-rw-r--r--themes/pmahomme/img/b_search.pngbin0 -> 578 bytes
-rw-r--r--themes/pmahomme/img/b_selboard.pngbin0 -> 676 bytes
-rw-r--r--themes/pmahomme/img/b_select.pngbin0 -> 645 bytes
-rw-r--r--themes/pmahomme/img/b_snewtbl.pngbin0 -> 717 bytes
-rw-r--r--themes/pmahomme/img/b_spatial.pngbin0 -> 379 bytes
-rw-r--r--themes/pmahomme/img/b_sql.pngbin0 -> 723 bytes
-rw-r--r--themes/pmahomme/img/b_sqldoc.pngbin0 -> 194 bytes
-rw-r--r--themes/pmahomme/img/b_sqlhelp.pngbin0 -> 590 bytes
-rw-r--r--themes/pmahomme/img/b_table_add.pngbin0 -> 676 bytes
-rw-r--r--themes/pmahomme/img/b_tblanalyse.pngbin0 -> 188 bytes
-rw-r--r--themes/pmahomme/img/b_tblexport.pngbin0 -> 606 bytes
-rw-r--r--themes/pmahomme/img/b_tblimport.pngbin0 -> 592 bytes
-rw-r--r--themes/pmahomme/img/b_tblops.pngbin0 -> 618 bytes
-rw-r--r--themes/pmahomme/img/b_tbloptimize.pngbin0 -> 198 bytes
-rw-r--r--themes/pmahomme/img/b_tipp.pngbin0 -> 764 bytes
-rw-r--r--themes/pmahomme/img/b_trigger_add.pngbin0 -> 615 bytes
-rw-r--r--themes/pmahomme/img/b_triggers.pngbin0 -> 494 bytes
-rw-r--r--themes/pmahomme/img/b_undo.pngbin0 -> 1306 bytes
-rw-r--r--themes/pmahomme/img/b_unique.pngbin0 -> 615 bytes
-rw-r--r--themes/pmahomme/img/b_usradd.pngbin0 -> 750 bytes
-rw-r--r--themes/pmahomme/img/b_usrcheck.pngbin0 -> 773 bytes
-rw-r--r--themes/pmahomme/img/b_usrdrop.pngbin0 -> 756 bytes
-rw-r--r--themes/pmahomme/img/b_usredit.pngbin0 -> 845 bytes
-rw-r--r--themes/pmahomme/img/b_usrlist.pngbin0 -> 774 bytes
-rw-r--r--themes/pmahomme/img/b_view.pngbin0 -> 646 bytes
-rw-r--r--themes/pmahomme/img/b_view_add.pngbin0 -> 766 bytes
-rw-r--r--themes/pmahomme/img/b_views.pngbin0 -> 630 bytes
-rw-r--r--themes/pmahomme/img/bd_browse.pngbin0 -> 431 bytes
-rw-r--r--themes/pmahomme/img/bd_deltbl.pngbin0 -> 510 bytes
-rw-r--r--themes/pmahomme/img/bd_drop.pngbin0 -> 560 bytes
-rw-r--r--themes/pmahomme/img/bd_edit.pngbin0 -> 271 bytes
-rw-r--r--themes/pmahomme/img/bd_empty.pngbin0 -> 511 bytes
-rw-r--r--themes/pmahomme/img/bd_export.pngbin0 -> 326 bytes
-rw-r--r--themes/pmahomme/img/bd_firstpage.pngbin0 -> 613 bytes
-rw-r--r--themes/pmahomme/img/bd_ftext.pngbin0 -> 473 bytes
-rw-r--r--themes/pmahomme/img/bd_index.pngbin0 -> 560 bytes
-rw-r--r--themes/pmahomme/img/bd_insrow.pngbin0 -> 224 bytes
-rw-r--r--themes/pmahomme/img/bd_lastpage.pngbin0 -> 603 bytes
-rw-r--r--themes/pmahomme/img/bd_nextpage.pngbin0 -> 302 bytes
-rw-r--r--themes/pmahomme/img/bd_prevpage.pngbin0 -> 303 bytes
-rw-r--r--themes/pmahomme/img/bd_primary.pngbin0 -> 476 bytes
-rw-r--r--themes/pmahomme/img/bd_sbrowse.pngbin0 -> 431 bytes
-rw-r--r--themes/pmahomme/img/bd_select.pngbin0 -> 536 bytes
-rw-r--r--themes/pmahomme/img/bd_spatial.pngbin0 -> 375 bytes
-rw-r--r--themes/pmahomme/img/bd_unique.pngbin0 -> 508 bytes
-rw-r--r--themes/pmahomme/img/col_drop.pngbin0 -> 132 bytes
-rw-r--r--themes/pmahomme/img/col_pointer.pngbin0 -> 113 bytes
-rw-r--r--themes/pmahomme/img/col_pointer_ver.pngbin0 -> 117 bytes
-rw-r--r--themes/pmahomme/img/database.pngbin0 -> 353 bytes
-rw-r--r--themes/pmahomme/img/east-mini.pngbin0 -> 322 bytes
-rw-r--r--themes/pmahomme/img/error.icobin0 -> 5430 bytes
-rw-r--r--themes/pmahomme/img/eye.pngbin0 -> 721 bytes
-rw-r--r--themes/pmahomme/img/eye_grey.pngbin0 -> 330 bytes
-rw-r--r--themes/pmahomme/img/item.pngbin0 -> 134 bytes
-rw-r--r--themes/pmahomme/img/left_nav_bg.pngbin0 -> 297 bytes
-rw-r--r--themes/pmahomme/img/lightbulb.pngbin0 -> 782 bytes
-rw-r--r--themes/pmahomme/img/lightbulb_off.pngbin0 -> 700 bytes
-rw-r--r--themes/pmahomme/img/logo_left.pngbin0 -> 2327 bytes
-rw-r--r--themes/pmahomme/img/logo_right.pngbin0 -> 4548 bytes
-rw-r--r--themes/pmahomme/img/more.pngbin0 -> 117 bytes
-rw-r--r--themes/pmahomme/img/new_data.pngbin0 -> 264 bytes
-rw-r--r--themes/pmahomme/img/new_data_hovered.pngbin0 -> 264 bytes
-rw-r--r--themes/pmahomme/img/new_data_selected.pngbin0 -> 179 bytes
-rw-r--r--themes/pmahomme/img/new_data_selected_hovered.pngbin0 -> 181 bytes
-rw-r--r--themes/pmahomme/img/new_struct.pngbin0 -> 301 bytes
-rw-r--r--themes/pmahomme/img/new_struct_hovered.pngbin0 -> 301 bytes
-rw-r--r--themes/pmahomme/img/new_struct_selected.pngbin0 -> 189 bytes
-rw-r--r--themes/pmahomme/img/new_struct_selected_hovered.pngbin0 -> 196 bytes
-rw-r--r--themes/pmahomme/img/north-mini.pngbin0 -> 327 bytes
-rw-r--r--themes/pmahomme/img/pause.pngbin0 -> 271 bytes
-rw-r--r--themes/pmahomme/img/php_sym.pngbin0 -> 156 bytes
-rw-r--r--themes/pmahomme/img/play.pngbin0 -> 373 bytes
-rw-r--r--themes/pmahomme/img/pma_logo2.pngbin0 -> 1424 bytes
-rw-r--r--themes/pmahomme/img/pmd/1.pngbin0 -> 101 bytes
-rw-r--r--themes/pmahomme/img/pmd/2.pngbin0 -> 169 bytes
-rw-r--r--themes/pmahomme/img/pmd/2leftarrow.pngbin0 -> 702 bytes
-rw-r--r--themes/pmahomme/img/pmd/2leftarrow_m.pngbin0 -> 666 bytes
-rw-r--r--themes/pmahomme/img/pmd/2rightarrow.pngbin0 -> 737 bytes
-rw-r--r--themes/pmahomme/img/pmd/2rightarrow_m.pngbin0 -> 699 bytes
-rw-r--r--themes/pmahomme/img/pmd/3.pngbin0 -> 175 bytes
-rw-r--r--themes/pmahomme/img/pmd/4.pngbin0 -> 181 bytes
-rw-r--r--themes/pmahomme/img/pmd/5.pngbin0 -> 90 bytes
-rw-r--r--themes/pmahomme/img/pmd/6.pngbin0 -> 98 bytes
-rw-r--r--themes/pmahomme/img/pmd/7.pngbin0 -> 105 bytes
-rw-r--r--themes/pmahomme/img/pmd/8.pngbin0 -> 88 bytes
-rw-r--r--themes/pmahomme/img/pmd/FieldKey_small.pngbin0 -> 229 bytes
-rw-r--r--themes/pmahomme/img/pmd/Field_small.pngbin0 -> 275 bytes
-rw-r--r--themes/pmahomme/img/pmd/Field_small_char.pngbin0 -> 154 bytes
-rw-r--r--themes/pmahomme/img/pmd/Field_small_date.pngbin0 -> 156 bytes
-rw-r--r--themes/pmahomme/img/pmd/Field_small_int.pngbin0 -> 145 bytes
-rw-r--r--themes/pmahomme/img/pmd/Header.pngbin0 -> 125 bytes
-rw-r--r--themes/pmahomme/img/pmd/Header_Linked.pngbin0 -> 108 bytes
-rw-r--r--themes/pmahomme/img/pmd/and_icon.pngbin0 -> 792 bytes
-rw-r--r--themes/pmahomme/img/pmd/ang_direct.pngbin0 -> 679 bytes
-rw-r--r--themes/pmahomme/img/pmd/bord.pngbin0 -> 87 bytes
-rw-r--r--themes/pmahomme/img/pmd/bottom.pngbin0 -> 734 bytes
-rw-r--r--themes/pmahomme/img/pmd/def.pngbin0 -> 670 bytes
-rw-r--r--themes/pmahomme/img/pmd/display_field.pngbin0 -> 685 bytes
-rw-r--r--themes/pmahomme/img/pmd/downarrow1.pngbin0 -> 758 bytes
-rw-r--r--themes/pmahomme/img/pmd/downarrow2.pngbin0 -> 772 bytes
-rw-r--r--themes/pmahomme/img/pmd/downarrow2_m.pngbin0 -> 711 bytes
-rw-r--r--themes/pmahomme/img/pmd/exec.pngbin0 -> 796 bytes
-rw-r--r--themes/pmahomme/img/pmd/exec_small.pngbin0 -> 161 bytes
-rw-r--r--themes/pmahomme/img/pmd/exitFullscreen.pngbin0 -> 444 bytes
-rw-r--r--themes/pmahomme/img/pmd/favicon.icobin0 -> 1150 bytes
-rw-r--r--themes/pmahomme/img/pmd/grid.pngbin0 -> 688 bytes
-rw-r--r--themes/pmahomme/img/pmd/help.pngbin0 -> 714 bytes
-rw-r--r--themes/pmahomme/img/pmd/help_relation.pngbin0 -> 920 bytes
-rw-r--r--themes/pmahomme/img/pmd/left_panel_butt.pngbin0 -> 129 bytes
-rw-r--r--themes/pmahomme/img/pmd/left_panel_tab.pngbin0 -> 133 bytes
-rw-r--r--themes/pmahomme/img/pmd/minus.pngbin0 -> 1004 bytes
-rw-r--r--themes/pmahomme/img/pmd/or_icon.pngbin0 -> 611 bytes
-rw-r--r--themes/pmahomme/img/pmd/pdf.pngbin0 -> 905 bytes
-rw-r--r--themes/pmahomme/img/pmd/plus.pngbin0 -> 969 bytes
-rw-r--r--themes/pmahomme/img/pmd/query_builder.pngbin0 -> 685 bytes
-rw-r--r--themes/pmahomme/img/pmd/relation.pngbin0 -> 295 bytes
-rw-r--r--themes/pmahomme/img/pmd/reload.pngbin0 -> 874 bytes
-rw-r--r--themes/pmahomme/img/pmd/resize.pngbin0 -> 121 bytes
-rw-r--r--themes/pmahomme/img/pmd/rightarrow1.pngbin0 -> 746 bytes
-rw-r--r--themes/pmahomme/img/pmd/rightarrow2.pngbin0 -> 757 bytes
-rw-r--r--themes/pmahomme/img/pmd/save.pngbin0 -> 409 bytes
-rw-r--r--themes/pmahomme/img/pmd/small_tab.pngbin0 -> 180 bytes
-rw-r--r--themes/pmahomme/img/pmd/table.pngbin0 -> 163 bytes
-rw-r--r--themes/pmahomme/img/pmd/toggle_lines.pngbin0 -> 630 bytes
-rw-r--r--themes/pmahomme/img/pmd/top_panel.pngbin0 -> 173 bytes
-rw-r--r--themes/pmahomme/img/pmd/uparrow2_m.pngbin0 -> 729 bytes
-rw-r--r--themes/pmahomme/img/pmd/viewInFullscreen.pngbin0 -> 472 bytes
-rw-r--r--themes/pmahomme/img/s_asc.pngbin0 -> 169 bytes
-rw-r--r--themes/pmahomme/img/s_asci.pngbin0 -> 184 bytes
-rw-r--r--themes/pmahomme/img/s_attention.pngbin0 -> 629 bytes
-rw-r--r--themes/pmahomme/img/s_cancel.pngbin0 -> 575 bytes
-rw-r--r--themes/pmahomme/img/s_cancel2.pngbin0 -> 305 bytes
-rw-r--r--themes/pmahomme/img/s_cog.pngbin0 -> 475 bytes
-rw-r--r--themes/pmahomme/img/s_db.pngbin0 -> 353 bytes
-rw-r--r--themes/pmahomme/img/s_desc.pngbin0 -> 182 bytes
-rw-r--r--themes/pmahomme/img/s_error.pngbin0 -> 664 bytes
-rw-r--r--themes/pmahomme/img/s_error2.pngbin0 -> 152 bytes
-rw-r--r--themes/pmahomme/img/s_fulltext.pngbin0 -> 193 bytes
-rw-r--r--themes/pmahomme/img/s_host.pngbin0 -> 655 bytes
-rw-r--r--themes/pmahomme/img/s_info.pngbin0 -> 740 bytes
-rw-r--r--themes/pmahomme/img/s_lang.pngbin0 -> 755 bytes
-rw-r--r--themes/pmahomme/img/s_loggoff.pngbin0 -> 651 bytes
-rw-r--r--themes/pmahomme/img/s_notice.pngbin0 -> 629 bytes
-rw-r--r--themes/pmahomme/img/s_okay.pngbin0 -> 772 bytes
-rw-r--r--themes/pmahomme/img/s_partialtext.pngbin0 -> 196 bytes
-rw-r--r--themes/pmahomme/img/s_passwd.pngbin0 -> 353 bytes
-rw-r--r--themes/pmahomme/img/s_process.pngbin0 -> 475 bytes
-rw-r--r--themes/pmahomme/img/s_really.pngbin0 -> 145 bytes
-rw-r--r--themes/pmahomme/img/s_reload.pngbin0 -> 582 bytes
-rw-r--r--themes/pmahomme/img/s_replication.pngbin0 -> 424 bytes
-rw-r--r--themes/pmahomme/img/s_rights.pngbin0 -> 503 bytes
-rw-r--r--themes/pmahomme/img/s_sortable.pngbin0 -> 234 bytes
-rw-r--r--themes/pmahomme/img/s_status.pngbin0 -> 637 bytes
-rw-r--r--themes/pmahomme/img/s_success.pngbin0 -> 552 bytes
-rw-r--r--themes/pmahomme/img/s_sync.pngbin0 -> 498 bytes
-rw-r--r--themes/pmahomme/img/s_tbl.pngbin0 -> 684 bytes
-rw-r--r--themes/pmahomme/img/s_theme.pngbin0 -> 828 bytes
-rw-r--r--themes/pmahomme/img/s_top.pngbin0 -> 443 bytes
-rw-r--r--themes/pmahomme/img/s_vars.pngbin0 -> 614 bytes
-rw-r--r--themes/pmahomme/img/s_views.pngbin0 -> 630 bytes
-rw-r--r--themes/pmahomme/img/south-mini.pngbin0 -> 335 bytes
-rw-r--r--themes/pmahomme/img/spacer.pngbin0 -> 84 bytes
-rw-r--r--themes/pmahomme/img/sprites.pngbin0 -> 41626 bytes
-rw-r--r--themes/pmahomme/img/toggle-ltr.pngbin0 -> 414 bytes
-rw-r--r--themes/pmahomme/img/toggle-rtl.pngbin0 -> 414 bytes
-rw-r--r--themes/pmahomme/img/vertical_line.pngbin0 -> 68 bytes
-rw-r--r--themes/pmahomme/img/west-mini.pngbin0 -> 328 bytes
-rw-r--r--themes/pmahomme/img/window-new.pngbin0 -> 484 bytes
-rw-r--r--themes/pmahomme/img/zoom-minus-mini.pngbin0 -> 247 bytes
-rw-r--r--themes/pmahomme/img/zoom-plus-mini.pngbin0 -> 329 bytes
-rw-r--r--themes/pmahomme/img/zoom-world-mini.pngbin0 -> 808 bytes
-rw-r--r--themes/pmahomme/info.inc.php21
-rw-r--r--themes/pmahomme/jquery/images/ui-bg_flat_0_aaaaaa_40x100.pngbin0 -> 180 bytes
-rw-r--r--themes/pmahomme/jquery/images/ui-bg_flat_75_ffffff_40x100.pngbin0 -> 178 bytes
-rw-r--r--themes/pmahomme/jquery/images/ui-bg_glass_55_fbf9ee_1x400.pngbin0 -> 120 bytes
-rw-r--r--themes/pmahomme/jquery/images/ui-bg_glass_65_ffffff_1x400.pngbin0 -> 105 bytes
-rw-r--r--themes/pmahomme/jquery/images/ui-bg_glass_75_dadada_1x400.pngbin0 -> 159 bytes
-rw-r--r--themes/pmahomme/jquery/images/ui-bg_glass_75_e6e6e6_1x400.pngbin0 -> 110 bytes
-rw-r--r--themes/pmahomme/jquery/images/ui-bg_glass_95_fef1ec_1x400.pngbin0 -> 119 bytes
-rw-r--r--themes/pmahomme/jquery/images/ui-bg_highlight-soft_75_cccccc_1x100.pngbin0 -> 101 bytes
-rw-r--r--themes/pmahomme/jquery/images/ui-icons_222222_256x240.pngbin0 -> 4369 bytes
-rw-r--r--themes/pmahomme/jquery/images/ui-icons_2e83ff_256x240.pngbin0 -> 4369 bytes
-rw-r--r--themes/pmahomme/jquery/images/ui-icons_454545_256x240.pngbin0 -> 4369 bytes
-rw-r--r--themes/pmahomme/jquery/images/ui-icons_888888_256x240.pngbin0 -> 4369 bytes
-rw-r--r--themes/pmahomme/jquery/images/ui-icons_cd0a0a_256x240.pngbin0 -> 4369 bytes
-rw-r--r--themes/pmahomme/jquery/jquery-ui-1.9.2.custom.css462
-rw-r--r--themes/pmahomme/layout.inc.php112
-rw-r--r--themes/pmahomme/screen.pngbin0 -> 63100 bytes
-rw-r--r--themes/pmahomme/sprites.lib.php750
-rw-r--r--themes/sprites.css.php82
-rw-r--r--themes/svg_gradient.php53
-rw-r--r--transformation_overview.php59
-rw-r--r--transformation_wrapper.php148
-rw-r--r--url.php23
-rw-r--r--user_password.php242
-rw-r--r--version_check.php26
-rw-r--r--view_create.php286
-rw-r--r--view_operations.php139
-rw-r--r--webapp.php55
1122 files changed, 290348 insertions, 0 deletions
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000000..62c9798053
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,21 @@
+# Contributing to phpMyAdmin
+
+As an open source project, phpMyAdmin welcomes contributions of many forms.
+
+## Bug reporting
+
+Please report [bugs on SourceForge.net][1].
+
+[1]: https://sourceforge.net/p/phpmyadmin/bugs/new/
+
+## Patches submission
+
+Patches are welcome either as [pull requests on GitHub][2].
+
+[2]: https://github.com/phpmyadmin/phpmyadmin/pulls
+
+## More information
+
+You can find more information on our website:
+
+http://www.phpmyadmin.net/home_page/improve.php
diff --git a/ChangeLog b/ChangeLog
new file mode 100644
index 0000000000..1f28774db5
--- /dev/null
+++ b/ChangeLog
@@ -0,0 +1,112 @@
+phpMyAdmin - ChangeLog
+======================
+
+4.1.12.0 (2014-03-27)
+- bug #4334 Add event : datepicker won't open
+- bug #4338 Fix missing value error while executing SQL query
+- TCPDF library is now optional dependency
+- bug #4326 Cannot find the import plugins which start with uppercase 'I'
+
+4.1.11.0 (2014-03-23)
+- bug #4335 reCaptcha problem (4.1.10 regression)
+
+4.1.10.0 (2014-03-22)
+- bug #4301 Grid edit: "SELECT" query is replaced by "UPDATE" query after edit
+- bug #4278 reCaptcha re-login requires double effort
+- bug #4324 Datepicker not showing up on insert page
+- bug #3991 Problem selecting item in select boxes with the ENTER keystroke in some browsers
+- bug #4323 QueryWindow ignores CodeMirror
+- bug None of the live charts shown on "Status -> Monitor" (Chrome)
+
+4.1.9.0 (2014-03-06)
+- bug #4279 CTRL + up or down moves two fields (part one)
+- bug #4294 output as text radio clickable for "OpenDocument Text" export
+- bug #4297 DROP DATABASE tick box in export no longer works
+- bug #4291 Unable to export comments in OpenDocument text format
+- bug #4299 Deletion even when the user says "No" to the confirmation message
+- bug #4303 "New" link in navi panel is shown even if no privileges
+- bug #4302 Some params are being omitted from microhistory
+- bug #4298 Missing validation on Import CSV: "Columns enclosed with" and "Columns escaped with"
+- bug #4040 Fatal error while resetting settings
+- bug #4305 JS error when editing procedure from nav panel
+- bug #4308 Edit routine form submitting when pressing enter
+- bug #4307 Nav: "Columns" won't expand with specific schema
+
+4.1.8.0 (2014-02-22)
+- bug #4276 Login loop on session expiry
+- bug #4249 Incorrect number of result rows for SQL with subqueries
+- bug #4275 Broken Link to php extension manual
+- bug #4053 List of procedures is not displayed after executing with Enter
+- bug #4081 Setup page content shifted to the right edge of its tabs
+- bug #4284 Reordering a column erases comments for other columns
+- bug #4286 Open "Browse" in a new tab
+- bug #4287 Printview - Always one column too much
+- bug #4288 Expand database (+ icon) after timeout doesn't do anything
+- bug #4285 Fixed CSS for setup
+- Fixed altering table to DOUBLE/FLOAT field
+- bug #4292 Success message and failure message being shown together
+- bug #4293 opening new tab (using selflink) for import.php based actions
+ results in error and logout
+
+4.1.7.0 (2014-02-09)
+- bug #4245 initial Browse query does not match sorting order
+- bug #4250 Notice on export page
+- bug #4253 "New" text in navigation frame acts like a database
+- bug #4262 Cannot define a column with fractional seconds
+- bug #4265 Missing datepicker icon for DATETIME(length)
+- bug #4257 Hide fractional seconds when applicable
+- bug #4264 Uncheck "Ignore" while inserting, upon leaving a textarea
+- bug #4260 reCaptcha is ignoring language settings
+- bug #4259 reCaptcha sound session expired problem
+- bug #4263 Japanese character encoding not working properly when exporting
+- bug #4269 Notice on table relation page
+- bug #4270 Bad text-color for table comments
+- bug #4272 Incorrect tabindex
+- bug #4271 Query by example and the second criteria line
+- bug #4242 Wildcard-containing only_db failure in sidebar
+
+4.1.6.0 (2014-01-26)
+- bug #4232 User not found after creating the user
+- bug #4241 Confusing dialog when trying to create an already existing user
+- bug #4239 Missing LIMIT clause for some queries
+- rfe #1489 Do not show create icon when user has no privileges
+- bug #4218 Chrome behavior with date fields
+- bug #3579 NOW() function incorrectly selected (regression)
+- bug #4244 Advisor complaints about MariaDB 10.x is version less than 5.1
+- bug #3889 When login fails and error display is active, login data is
+displayed (regression)
+- bug #4247 open_basedir warnings on export page
+- bug #4013 AJAX request waiting until version info is retrieved
+- bug #4248 js error when changing number of columns in status monitor
+
+4.1.5.0 (2014-01-17)
+- bug #3780 Allow aborting loading pages
+- bug #4223 Database list: Create database misses collation column
+- bug #4224 Empty table names when a table is "inuse"
+- bug #4225 Partition maintenance broken
+- bug #4219 Table list (left panel) does not reload when table renamed
+- bug #4230 "in use" displayed for all views in database print view
+- bug #4226 Notice: Undefined index: pma_config_loading
+- bug #4221 Bzip2 export cannot be directly imported (so withdraw bz2 export)
+- bug #4204 Reloading user privileges hides user groups submenu
+- bug #4231 DATE columns quick edit decrement by one day
+
+4.1.4.0 (2014-01-07)
+- bug #3840 (additional fix) When exporting to gzip format, the data is compressed 2 times
+- bug #4209 Missing compression in one case
+- bug #4208 Can't browse tables after sorting on columns with fieldnames that have a '-'
+- bug #4184 Switch to wrong page after adding an index
+- bug #3885 Additional fix for this bug
+- bug #4212 Table "disappears" if it has the same name as its tablegroup
+- bug #4213 Datetime Quick Edit decrements by one day
+- bug #4217 Current value not highlighted when browsing foreign values
+- bug #4220 Incorrect key values in foreign key browser
+- bug #4215 MariaDB 5.5: error in Drizzle detection
+
+ --- Older ChangeLogs can be found on our project website ---
+ http://www.phpmyadmin.net/old-stuff/ChangeLogs/
+
+# vim: et ts=4 sw=4 sts=4
+# vim: ft=changelog fenc=utf-8
+# vim: fde=getline(v\:lnum-1)=~'^\\s*$'&&getline(v\:lnum)=~'\\S'?'>1'\:1&&v\:lnum>4&&getline(v\:lnum)!~'^#'
+# vim: fdn=1 fdm=expr
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000000..3912109b5c
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,340 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+ 51 Franklin St, 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 Library 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 St, 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 Library General
+Public License instead of this License.
diff --git a/README b/README
new file mode 100644
index 0000000000..8e09950f7a
--- /dev/null
+++ b/README
@@ -0,0 +1,104 @@
+phpMyAdmin - Readme
+===================
+
+Version 4.1.12
+
+A set of PHP-scripts to manage MySQL over the web.
+
+http://www.phpmyadmin.net/
+
+Copyright
+---------
+
+Copyright (C) 1998-2000
+ Tobias Ratschiller <tobias_at_ratschiller.com>
+
+Copyright (C) 2001-2014
+ Marc Delisle <marc_at_infomarc.info>
+ Olivier Müller <om_at_omnis.ch>
+ Robin Johnson <robbat2_at_users.sourceforge.net>
+ Alexander M. Turek <me_at_derrabus.de>
+ Michal ÄŒihaÅ™ <michal_at_cihar.com>
+ Garvin Hicking <me_at_supergarv.de>
+ Michael Keck <mkkeck_at_users.sourceforge.net>
+ Sebastian Mendel <cybot_tm_at_users.sourceforge.net>
+ [check documentation for more details]
+
+License
+-------
+
+This program is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License version 2, as published by the
+Free Software Foundation.
+
+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, see <http://www.gnu.org/licenses/>.
+
+Third party licenses
+--------------------
+
+phpMyAdmin includes several third party libraries which come under their
+respective licenses.
+
+jQuery's license, which is where we got the files under js/jquery/ is
+(MIT|GPL), a copy of each license is available in this repository (GPL
+is available as LICENSE, MIT as js/jquery/MIT-LICENSE.txt).
+
+TCPDF which is located under libraries/tcpdf is released under GPL
+version 3 and the license is available as libraries/tcpdf/LICENSE.TXT.
+
+DejaVu fonts which are located under libraries/tcpdf/fonts/ and their
+license is documented in
+libraries/tcpdf/fonts/dejavu-fonts-ttf-2.33/LICENSE.
+
+PHP-gettext which is located under libraries/php-gettext/ is released
+under GPL version 2 license which is available in the LICENSE file.
+
+Requirements
+------------
+
+* PHP 5.3 or later
+* MySQL 5.0 or later
+* a web-browser (doh!)
+
+Summary
+-------
+
+phpMyAdmin is intended to handle the administration of MySQL over the web.
+For a summary of features, please see the documentation in the doc folder.
+
+Download
+--------
+
+You can get the newest version at http://www.phpmyadmin.net/.
+
+More Information
+----------------
+
+Please see the documentation in the doc folder.
+
+Support
+-------
+
+See reference about support forums under http://www.phpmyadmin.net/
+
+
+Enjoy!
+------
+
+The phpMyAdmin Devel team
+
+
+PS:
+
+Please, don't send us emails with question like "How do I compile PHP with
+MySQL-support". We just don't have the time to be your free help desk.
+
+Please send your questions to the appropriate mailing lists / forums. Before
+contacting us, please read the documentation (especially the FAQ part).
+
diff --git a/browse_foreigners.php b/browse_foreigners.php
new file mode 100644
index 0000000000..038435036f
--- /dev/null
+++ b/browse_foreigners.php
@@ -0,0 +1,66 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * display selection for relational field values
+ *
+ * @package PhpMyAdmin
+ */
+
+require_once 'libraries/common.inc.php';
+require_once 'libraries/transformations.lib.php';
+require_once 'libraries/browse_foreigners.lib.php';
+
+/**
+ * Sets globals from $_REQUEST
+ */
+$request_params = array(
+ 'data',
+ 'field'
+);
+
+foreach ($request_params as $one_request_param) {
+ if (isset($_REQUEST[$one_request_param])) {
+ $GLOBALS[$one_request_param] = $_REQUEST[$one_request_param];
+ }
+}
+
+PMA_Util::checkParameters(array('db', 'table', 'field'));
+
+$response = PMA_Response::getInstance();
+$response->getFooter()->setMinimal();
+$header = $response->getHeader();
+$header->disableMenu();
+$header->setBodyId('body_browse_foreigners');
+
+/**
+ * Displays the frame
+ */
+
+$cfgRelation = PMA_getRelationsParam();
+$foreigners = ($cfgRelation['relwork'] ? PMA_getForeigners($db, $table) : false);
+$foreign_limit = PMA_getForeignLimit(
+ isset($foreign_navig) ? $foreign_navig : null
+);
+
+$foreignData = PMA_getForeignData(
+ $foreigners, $_REQUEST['field'], true,
+ isset($_REQUEST['foreign_filter'])
+ ? $_REQUEST['foreign_filter']
+ : '',
+ isset($foreign_limit) ? $foreign_limit : null
+);
+
+
+$code = PMA_getJsScriptToHandleSelectRelationalFields();
+
+$header->getScripts()->addCode($code);
+
+// HTML output
+$html = PMA_getHtmlForRelationalFieldSelection(
+ $db, $table, $_REQUEST['field'], $foreignData,
+ isset($fieldkey) ? $fieldkey : null,
+ isset($data) ? $data : null
+);
+
+$response->addHtml($html);
+?>
diff --git a/changelog.php b/changelog.php
new file mode 100644
index 0000000000..5066ffe03e
--- /dev/null
+++ b/changelog.php
@@ -0,0 +1,153 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Simple script to set correct charset for changelog
+ *
+ * @package PhpMyAdmin
+ */
+
+/**
+ * Gets core libraries and defines some variables
+ */
+require 'libraries/common.inc.php';
+
+$response = PMA_Response::getInstance();
+$response->disable();
+
+$filename = CHANGELOG_FILE;
+
+/**
+ * Read changelog.
+ */
+// Check if the file is available, some distributions remove these.
+if (is_readable($filename)) {
+
+ // Test if the if is in a compressed format
+ if (substr($filename, -3) == '.gz') {
+ ob_start();
+ readgzfile($filename);
+ $changelog = ob_get_contents();
+ ob_end_clean();
+ } else {
+ $changelog = file_get_contents($filename);
+ }
+} else {
+ printf(
+ __('The %s file is not available on this system, please visit www.phpmyadmin.net for more information.'),
+ $filename
+ );
+ exit;
+}
+
+/**
+ * Whole changelog in variable.
+ */
+$changelog = htmlspecialchars($changelog);
+
+$tracker_url = 'https://sourceforge.net/support/tracker.php?aid=\\1';
+$tracker_url_bug = 'https://sourceforge.net/p/phpmyadmin/bugs/\\1/';
+$tracker_url_rfe = 'https://sourceforge.net/p/phpmyadmin/feature-requests/\\1/';
+$tracker_url_patch = 'https://sourceforge.net/p/phpmyadmin/patches/\\1/';
+$github_url = 'https://github.com/phpmyadmin/phpmyadmin/';
+$faq_url = 'http://docs.phpmyadmin.net/en/latest/faq.html';
+
+$replaces = array(
+ '@(http://[./a-zA-Z0-9.-_-]*[/a-zA-Z0-9_])@'
+ => '<a href="\\1">\\1</a>',
+
+ // sourceforge users
+ '/([0-9]{4}-[0-9]{2}-[0-9]{2}) (.+[^ ]) +&lt;(.*)@users.sourceforge.net&gt;/i'
+ => '\\1 <a href="https://sourceforge.net/users/\\3/">\\2</a>',
+ '/thanks to ([^\(\r\n]+) \(([-\w]+)\)/i'
+ => 'thanks to <a href="https://sourceforge.net/users/\\2/">\\1</a>',
+ '/thanks to ([^\(\r\n]+) -\s+([-\w]+)/i'
+ => 'thanks to <a href="https://sourceforge.net/users/\\2/">\\1</a>',
+
+ // mail address
+ '/([0-9]{4}-[0-9]{2}-[0-9]{2}) (.+[^ ]) +&lt;(.*@.*)&gt;/i'
+ => '\\1 <a href="mailto:\\3">\\2</a>',
+
+ // linking patches
+ '/patch\s*#?([0-9]{6,})/i'
+ => '<a href="' . $tracker_url . '">patch #\\1</a>',
+
+ // linking RFE
+ '/(?:rfe|feature)\s*#?([0-9]{6,})/i'
+ => '<a href="https://sourceforge.net/support/tracker.php?aid=\\1">RFE #\\1</a>',
+
+ // linking files
+ '/(\s+)([\\/a-z_0-9\.]+\.(?:php3?|html|pl|js|sh))/i'
+ => '\\1<a href="' . $github_url . 'commits/HEAD/\\2">\\2</a>',
+
+ // FAQ entries
+ '/FAQ ([0-9]+)\.([0-9a-z]+)/i'
+ => '<a href="' . $faq_url . '#faq\\1-\\2">FAQ \\1.\\2</a>',
+
+ // linking bugs
+ '/bug\s*#?([0-9]{6,})/i'
+ => '<a href="https://sourceforge.net/support/tracker.php?aid=\\1">bug #\\1</a>',
+
+ // all other 6+ digit numbers are treated as bugs
+ '/(?<!bug|RFE|patch) #?([0-9]{6,})/i'
+ => '<a href="' . $tracker_url . '">bug #\\1</a>',
+
+ // transitioned SF.net project bug/rfe/patch links
+ // by the time we reach 6-digit numbers, we can probably retire the above links
+ '/patch\s*#?([0-9]{4,5}) /i'
+ => '<a href="' . $tracker_url_patch . '">patch #\\1</a> ',
+ '/(?:rfe|feature)\s*#?([0-9]{4,5}) /i'
+ => '<a href="' . $tracker_url_rfe . '">RFE #\\1</a> ',
+ '/bug\s*#?([0-9]{4,5}) /i'
+ => '<a href="' . $tracker_url_bug . '">bug #\\1</a> ',
+ '/(?<!bug|RFE|patch) #?([0-9]{4,5}) /i'
+ => '<a href="' . $tracker_url_bug . '">bug #\\1</a> ',
+
+ // CVE/CAN entries
+ '/((CAN|CVE)-[0-9]+-[0-9]+)/'
+ => '<a href="http://cve.mitre.org/cgi-bin/cvename.cgi?name=\\1">\\1</a>',
+
+ // PMASAentries
+ '/(PMASA-[0-9]+-[0-9]+)/'
+ => '<a href="http://www.phpmyadmin.net/home_page/security/\\1.php">\\1</a>',
+
+ // Highlight releases (with links)
+ '/([0-9]+)\.([0-9]+)\.([0-9]+)\.0 (\([0-9-]+\))/'
+ => '<a name="\\1_\\2_\\3"></a>'
+ . '<a href="' . $github_url . 'commits/RELEASE_\\1_\\2_\\3">'
+ . '\\1.\\2.\\3.0 \\4</a>',
+ '/([0-9]+)\.([0-9]+)\.([0-9]+)\.([1-9][0-9]*) (\([0-9-]+\))/'
+ => '<a name="\\1_\\2_\\3_\\4"></a>'
+ . '<a href="' . $github_url . 'commits/RELEASE_\\1_\\2_\\3_\\4">'
+ . '\\1.\\2.\\3.\\4 \\5</a>',
+
+ // Highlight releases (not linkable)
+ '/( ### )(.*)/'
+ => '\\1<b>\\2</b>',
+
+);
+
+header('Content-type: text/html; charset=utf-8');
+?>
+<!DOCTYPE HTML>
+<html lang="en" dir="ltr">
+<head>
+ <link rel="icon" href="favicon.ico" type="image/x-icon" />
+ <link rel="shortcut icon" href="favicon.ico" type="image/x-icon" />
+ <title>phpMyAdmin - ChangeLog</title>
+ <meta charset="utf-8" />
+</head>
+<body>
+<h1>phpMyAdmin - ChangeLog</h1>
+<?php
+echo '<pre>';
+echo preg_replace(array_keys($replaces), $replaces, $changelog);
+echo '</pre>';
+?>
+<script type="text/javascript">
+var links = document.getElementsByTagName("a");
+for(var i = 0; i < links.length; i++) {
+ links[i].target = "_blank";
+}
+</script>
+</body>
+</html>
diff --git a/chk_rel.php b/chk_rel.php
new file mode 100644
index 0000000000..915e8b24b4
--- /dev/null
+++ b/chk_rel.php
@@ -0,0 +1,15 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Displays status of phpMyAdmin configuration storage
+ *
+ * @package PhpMyAdmin
+ */
+
+require_once 'libraries/common.inc.php';
+$response = PMA_Response::getInstance();
+$response->addHTML(
+ PMA_getRelationsParamDiagnostic(PMA_getRelationsParam())
+);
+
+?>
diff --git a/composer.json b/composer.json
new file mode 100644
index 0000000000..7b58ed7f5a
--- /dev/null
+++ b/composer.json
@@ -0,0 +1,27 @@
+{
+ "name": "phpmyadmin/phpmyadmin",
+ "type": "application",
+ "description": "MySQL web administration tool",
+ "keywords": ["phpmyadmin","mysql","web"],
+ "homepage": "http://www.phpmyadmin.net/",
+ "license": "GPL-2.0+",
+ "authors": [
+ {
+ "name": "The phpMyAdmin Team",
+ "email": "phpmyadmin-devel@lists.sourceforge.net",
+ "homepage": "http://www.phpmyadmin.net/home_page/team.php"
+ }
+ ],
+ "support": {
+ "forum": "https://sourceforge.net/p/phpmyadmin/discussion/Help",
+ "issues": "https://sourceforge.net/p/phpmyadmin/bugs/",
+ "wiki": "http://wiki.phpmyadmin.net/",
+ "source": "https://github.com/phpmyadmin/phpmyadmin"
+ },
+ "require": {
+ "php": ">=5.3.0"
+ },
+ "require-dev": {
+ "satooshi/php-coveralls": "dev-master"
+ }
+}
diff --git a/config.sample.inc.php b/config.sample.inc.php
new file mode 100644
index 0000000000..07047acbeb
--- /dev/null
+++ b/config.sample.inc.php
@@ -0,0 +1,152 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * phpMyAdmin sample configuration, you can use it as base for
+ * manual configuration. For easier setup you can use setup/
+ *
+ * All directives are explained in documentation in the doc/ folder
+ * or at <http://docs.phpmyadmin.net/>.
+ *
+ * @package PhpMyAdmin
+ */
+
+/*
+ * This is needed for cookie based authentication to encrypt password in
+ * cookie
+ */
+$cfg['blowfish_secret'] = 'a8b7c6d'; /* YOU MUST FILL IN THIS FOR COOKIE AUTH! */
+
+/*
+ * Servers configuration
+ */
+$i = 0;
+
+/*
+ * First server
+ */
+$i++;
+/* Authentication type */
+$cfg['Servers'][$i]['auth_type'] = 'cookie';
+/* Server parameters */
+$cfg['Servers'][$i]['host'] = 'localhost';
+$cfg['Servers'][$i]['connect_type'] = 'tcp';
+$cfg['Servers'][$i]['compress'] = false;
+/* Select mysql if your server does not have mysqli */
+$cfg['Servers'][$i]['extension'] = 'mysqli';
+$cfg['Servers'][$i]['AllowNoPassword'] = false;
+
+/*
+ * phpMyAdmin configuration storage settings.
+ */
+
+/* User used to manipulate with storage */
+// $cfg['Servers'][$i]['controlhost'] = '';
+// $cfg['Servers'][$i]['controlport'] = '';
+// $cfg['Servers'][$i]['controluser'] = 'pma';
+// $cfg['Servers'][$i]['controlpass'] = 'pmapass';
+
+/* Storage database and tables */
+// $cfg['Servers'][$i]['pmadb'] = 'phpmyadmin';
+// $cfg['Servers'][$i]['bookmarktable'] = 'pma__bookmark';
+// $cfg['Servers'][$i]['relation'] = 'pma__relation';
+// $cfg['Servers'][$i]['table_info'] = 'pma__table_info';
+// $cfg['Servers'][$i]['table_coords'] = 'pma__table_coords';
+// $cfg['Servers'][$i]['pdf_pages'] = 'pma__pdf_pages';
+// $cfg['Servers'][$i]['column_info'] = 'pma__column_info';
+// $cfg['Servers'][$i]['history'] = 'pma__history';
+// $cfg['Servers'][$i]['table_uiprefs'] = 'pma__table_uiprefs';
+// $cfg['Servers'][$i]['tracking'] = 'pma__tracking';
+// $cfg['Servers'][$i]['designer_coords'] = 'pma__designer_coords';
+// $cfg['Servers'][$i]['userconfig'] = 'pma__userconfig';
+// $cfg['Servers'][$i]['recent'] = 'pma__recent';
+// $cfg['Servers'][$i]['users'] = 'pma__users';
+// $cfg['Servers'][$i]['usergroups'] = 'pma__usergroups';
+// $cfg['Servers'][$i]['navigationhiding'] = 'pma__navigationhiding';
+/* Contrib / Swekey authentication */
+// $cfg['Servers'][$i]['auth_swekey_config'] = '/etc/swekey-pma.conf';
+
+/*
+ * End of servers configuration
+ */
+
+/*
+ * Directories for saving/loading files from server
+ */
+$cfg['UploadDir'] = '';
+$cfg['SaveDir'] = '';
+
+/**
+ * Defines whether a user should be displayed a "show all (records)"
+ * button in browse mode or not.
+ * default = false
+ */
+//$cfg['ShowAll'] = true;
+
+/**
+ * Number of rows displayed when browsing a result set. If the result
+ * set contains more rows, "Previous" and "Next".
+ * default = 30
+ */
+//$cfg['MaxRows'] = 50;
+
+/**
+ * disallow editing of binary fields
+ * valid values are:
+ * false allow editing
+ * 'blob' allow editing except for BLOB fields
+ * 'noblob' disallow editing except for BLOB fields
+ * 'all' disallow editing
+ * default = blob
+ */
+//$cfg['ProtectBinary'] = 'false';
+
+/**
+ * Default language to use, if not browser-defined or user-defined
+ * (you find all languages in the locale folder)
+ * uncomment the desired line:
+ * default = 'en'
+ */
+//$cfg['DefaultLang'] = 'en';
+//$cfg['DefaultLang'] = 'de';
+
+/**
+ * default display direction (horizontal|vertical|horizontalflipped)
+ */
+//$cfg['DefaultDisplay'] = 'vertical';
+
+
+/**
+ * How many columns should be used for table display of a database?
+ * (a value larger than 1 results in some information being hidden)
+ * default = 1
+ */
+//$cfg['PropertiesNumColumns'] = 2;
+
+/**
+ * Set to true if you want DB-based query history.If false, this utilizes
+ * JS-routines to display query history (lost by window close)
+ *
+ * This requires configuration storage enabled, see above.
+ * default = false
+ */
+//$cfg['QueryHistoryDB'] = true;
+
+/**
+ * When using DB-based query history, how many entries should be kept?
+ *
+ * default = 25
+ */
+//$cfg['QueryHistoryMax'] = 100;
+
+/**
+ * Should error reporting be enabled for JavaScript errors
+ *
+ * default = 'ask'
+ */
+//$cfg['SendErrorReports'] = 'ask';
+
+/*
+ * You can find more configuration options in the documentation
+ * in the doc/ folder or at <http://docs.phpmyadmin.net/>.
+ */
+?>
diff --git a/db_create.php b/db_create.php
new file mode 100644
index 0000000000..85443a466e
--- /dev/null
+++ b/db_create.php
@@ -0,0 +1,136 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Database creating page
+ *
+ * @package PhpMyAdmin
+ */
+
+/**
+ * Gets some core libraries
+ */
+require_once 'libraries/common.inc.php';
+
+require_once 'libraries/mysql_charsets.inc.php';
+if (! PMA_DRIZZLE) {
+ include_once 'libraries/replication.inc.php';
+}
+require 'libraries/build_html_for_db.lib.php';
+
+/**
+ * Defines the url to return to in case of error in a sql statement
+ */
+$err_url = 'index.php?' . PMA_URL_getCommon();
+
+/**
+ * Builds and executes the db creation sql query
+ */
+$sql_query = 'CREATE DATABASE ' . PMA_Util::backquote($_POST['new_db']);
+if (! empty($_POST['db_collation'])) {
+ list($db_charset) = explode('_', $_POST['db_collation']);
+ if (in_array($db_charset, $mysql_charsets)
+ && in_array($_POST['db_collation'], $mysql_collations[$db_charset])
+ ) {
+ $sql_query .= ' DEFAULT'
+ . PMA_generateCharsetQueryPart($_POST['db_collation']);
+ }
+ $db_collation_for_ajax = $_POST['db_collation'];
+ unset($db_charset);
+}
+$sql_query .= ';';
+
+$result = $GLOBALS['dbi']->tryQuery($sql_query);
+
+if (! $result) {
+ $message = PMA_Message::rawError($GLOBALS['dbi']->getError());
+ // avoid displaying the not-created db name in header or navi panel
+ $GLOBALS['db'] = '';
+ $GLOBALS['table'] = '';
+
+ /**
+ * If in an Ajax request, just display the message with {@link PMA_Response}
+ */
+ if ($GLOBALS['is_ajax_request'] == true) {
+ $response = PMA_Response::getInstance();
+ $response->isSuccess(false);
+ $response->addJSON('message', $message);
+ } else {
+ include_once 'index.php';
+ }
+} else {
+ $message = PMA_Message::success(__('Database %1$s has been created.'));
+ $message->addParam($_POST['new_db']);
+ $GLOBALS['db'] = $_POST['new_db'];
+
+ /**
+ * If in an Ajax request, build the output and send it
+ */
+ if ($GLOBALS['is_ajax_request'] == true) {
+ //Construct the html for the new database, so that it can be appended to
+ // the list of databases on server_databases.php
+
+ /**
+ * Build the array to be passed to {@link PMA_URL_getCommon}
+ * to generate the links
+ *
+ * @global array $GLOBALS['db_url_params']
+ * @name $db_url_params
+ */
+ $db_url_params['db'] = $_POST['new_db'];
+
+ $is_superuser = $GLOBALS['dbi']->isSuperuser();
+ $column_order = PMA_getColumnOrder();
+ $url_query = PMA_URL_getCommon($_POST['new_db']);
+
+ /**
+ * String that will contain the output HTML
+ * @name $new_db_string
+ */
+ $new_db_string = '<tr>';
+
+ if (empty($db_collation_for_ajax)) {
+ $db_collation_for_ajax = PMA_getServerCollation();
+ }
+
+ // $dbstats comes from the create table dialog
+ if (! empty($dbstats)) {
+ $current = array(
+ 'SCHEMA_NAME' => $_POST['new_db'],
+ 'DEFAULT_COLLATION_NAME' => $db_collation_for_ajax,
+ 'SCHEMA_TABLES' => '0',
+ 'SCHEMA_TABLE_ROWS' => '0',
+ 'SCHEMA_DATA_LENGTH' => '0',
+ 'SCHEMA_MAX_DATA_LENGTH' => '0',
+ 'SCHEMA_INDEX_LENGTH' => '0',
+ 'SCHEMA_LENGTH' => '0',
+ 'SCHEMA_DATA_FREE' => '0'
+ );
+ } else {
+ $current = array(
+ 'SCHEMA_NAME' => $_POST['new_db'],
+ 'DEFAULT_COLLATION_NAME' => $db_collation_for_ajax
+ );
+ }
+
+ list($column_order, $generated_html) = PMA_buildHtmlForDb(
+ $current, $is_superuser, $url_query,
+ $column_order, $replication_types, $replication_info
+ );
+ $new_db_string .= $generated_html;
+
+ $new_db_string .= '</tr>';
+
+ $response = PMA_Response::getInstance();
+ $response->addJSON('message', $message);
+ $response->addJSON('new_db_string', $new_db_string);
+ $response->addJSON(
+ 'sql_query',
+ PMA_Util::getMessage(
+ null, $sql_query, 'success'
+ )
+ );
+ } else {
+ include_once '' . $cfg['DefaultTabDatabase'];
+ }
+}
+?>
diff --git a/db_datadict.php b/db_datadict.php
new file mode 100644
index 0000000000..f08a5c4779
--- /dev/null
+++ b/db_datadict.php
@@ -0,0 +1,291 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Renders data dictionary
+ *
+ * @package PhpMyAdmin
+ */
+
+/**
+ * Gets the variables sent or posted to this script, then displays headers
+ */
+require_once 'libraries/common.inc.php';
+
+if (! isset($selected_tbl)) {
+ include 'libraries/db_common.inc.php';
+ include 'libraries/db_info.inc.php';
+}
+
+$response = PMA_Response::getInstance();
+$header = $response->getHeader();
+$header->enablePrintView();
+
+/**
+ * Gets the relations settings
+ */
+$cfgRelation = PMA_getRelationsParam();
+
+require_once 'libraries/transformations.lib.php';
+require_once 'libraries/Index.class.php';
+
+/**
+ * Check parameters
+ */
+PMA_Util::checkParameters(array('db'));
+
+/**
+ * Defines the url to return to in case of error in a sql statement
+ */
+if (strlen($table)) {
+ $err_url = 'tbl_sql.php?' . PMA_URL_getCommon($db, $table);
+} else {
+ $err_url = 'db_sql.php?' . PMA_URL_getCommon($db);
+}
+
+if ($cfgRelation['commwork']) {
+ $comment = PMA_getDbComment($db);
+
+ /**
+ * Displays DB comment
+ */
+ if ($comment) {
+ echo '<p>' . __('Database comment: ')
+ . '<i>' . htmlspecialchars($comment) . '</i></p>';
+ } // end if
+}
+
+/**
+ * Selects the database and gets tables names
+ */
+$GLOBALS['dbi']->selectDb($db);
+$tables = $GLOBALS['dbi']->getTables($db);
+
+$count = 0;
+foreach ($tables as $table) {
+ $comments = PMA_getComments($db, $table);
+
+ echo '<div>' . "\n";
+
+ echo '<h2>' . htmlspecialchars($table) . '</h2>' . "\n";
+
+ /**
+ * Gets table informations
+ */
+ $show_comment = PMA_Table::sGetStatusInfo($db, $table, 'TABLE_COMMENT');
+
+ /**
+ * Gets table keys and retains them
+ */
+
+ $GLOBALS['dbi']->selectDb($db);
+ $indexes = $GLOBALS['dbi']->getTableIndexes($db, $table);
+ $primary = '';
+ $indexes = array();
+ $lastIndex = '';
+ $indexes_info = array();
+ $indexes_data = array();
+ $pk_array = array(); // will be use to emphasis prim. keys in the table
+ // view
+ foreach ($indexes as $row) {
+ // Backups the list of primary keys
+ if ($row['Key_name'] == 'PRIMARY') {
+ $primary .= $row['Column_name'] . ', ';
+ $pk_array[$row['Column_name']] = 1;
+ }
+ // Retains keys informations
+ if ($row['Key_name'] != $lastIndex) {
+ $indexes[] = $row['Key_name'];
+ $lastIndex = $row['Key_name'];
+ }
+ $indexes_info[$row['Key_name']]['Sequences'][] = $row['Seq_in_index'];
+ $indexes_info[$row['Key_name']]['Non_unique'] = $row['Non_unique'];
+ if (isset($row['Cardinality'])) {
+ $indexes_info[$row['Key_name']]['Cardinality'] = $row['Cardinality'];
+ }
+ // I don't know what does following column mean....
+ // $indexes_info[$row['Key_name']]['Packed'] = $row['Packed'];
+
+ $indexes_info[$row['Key_name']]['Comment'] = $row['Comment'];
+
+ $indexes_data[$row['Key_name']][$row['Seq_in_index']]['Column_name']
+ = $row['Column_name'];
+ if (isset($row['Sub_part'])) {
+ $indexes_data[$row['Key_name']][$row['Seq_in_index']]['Sub_part']
+ = $row['Sub_part'];
+ }
+
+ } // end while
+
+ /**
+ * Gets columns properties
+ */
+ $columns = $GLOBALS['dbi']->getColumns($db, $table);
+
+ if (PMA_MYSQL_INT_VERSION < 50025) {
+ // We need this to correctly learn if a TIMESTAMP is NOT NULL, since
+ // SHOW FULL COLUMNS or INFORMATION_SCHEMA incorrectly says NULL
+ // and SHOW CREATE TABLE says NOT NULL
+ // http://bugs.mysql.com/20910.
+
+ $show_create_table_query = 'SHOW CREATE TABLE '
+ . PMA_Util::backquote($db) . '.'
+ . PMA_Util::backquote($table);
+ $show_create_table = $GLOBALS['dbi']->fetchValue(
+ $show_create_table_query, 0, 1
+ );
+ $analyzed_sql = PMA_SQP_analyze(PMA_SQP_parse($show_create_table));
+ }
+
+ // Check if we can use Relations
+ if (!empty($cfgRelation['relation'])) {
+ // Find which tables are related with the current one and write it in
+ // an array
+ $res_rel = PMA_getForeigners($db, $table);
+
+ if (count($res_rel) > 0) {
+ $have_rel = true;
+ } else {
+ $have_rel = false;
+ }
+ } else {
+ $have_rel = false;
+ } // end if
+
+
+ /**
+ * Displays the comments of the table if MySQL >= 3.23
+ */
+ if (!empty($show_comment)) {
+ echo __('Table comments:') . ' ';
+ echo htmlspecialchars($show_comment) . '<br /><br />';
+ }
+
+ /**
+ * Displays the table structure
+ */
+
+ echo '<table width="100%" class="print">';
+ echo '<tr><th width="50">' . __('Column') . '</th>';
+ echo '<th width="80">' . __('Type') . '</th>';
+ echo '<th width="40">' . __('Null') . '</th>';
+ echo '<th width="70">' . __('Default') . '</th>';
+ if ($have_rel) {
+ echo ' <th>' . __('Links to') . '</th>' . "\n";
+ }
+ echo ' <th>' . __('Comments') . '</th>' . "\n";
+ if ($cfgRelation['mimework']) {
+ echo ' <th>MIME</th>' . "\n";
+ }
+ echo '</tr>';
+ $odd_row = true;
+ foreach ($columns as $row) {
+
+ if ($row['Null'] == '') {
+ $row['Null'] = 'NO';
+ }
+ $extracted_columnspec
+ = PMA_Util::extractColumnSpec($row['Type']);
+
+ // reformat mysql query output
+ // set or enum types: slashes single quotes inside options
+ if ('set' == $extracted_columnspec['type']
+ || 'enum' == $extracted_columnspec['type']
+ ) {
+ $type_nowrap = '';
+
+ } else {
+ $type_nowrap = ' class="nowrap"';
+ }
+ $type = htmlspecialchars($extracted_columnspec['print_type']);
+ $attribute = $extracted_columnspec['attribute'];
+ if (! isset($row['Default'])) {
+ if ($row['Null'] != 'NO') {
+ $row['Default'] = '<i>NULL</i>';
+ }
+ } else {
+ $row['Default'] = htmlspecialchars($row['Default']);
+ }
+ $column_name = $row['Field'];
+
+ $tmp_column = $analyzed_sql[0]['create_table_fields'][$column_name];
+ if (PMA_MYSQL_INT_VERSION < 50025
+ && ! empty($tmp_column['type'])
+ && $tmp_column['type'] == 'TIMESTAMP'
+ && $tmp_column['timestamp_not_null']
+ ) {
+ // here, we have a TIMESTAMP that SHOW FULL COLUMNS reports as
+ // having the NULL attribute, but SHOW CREATE TABLE says the
+ // contrary. Believe the latter.
+ /**
+ * @todo merge this logic with the one in tbl_structure.php
+ * or move it in a function similar to $GLOBALS['dbi']->getColumnsFull()
+ * but based on SHOW CREATE TABLE because information_schema
+ * cannot be trusted in this case (MySQL bug)
+ */
+ $row['Null'] = 'NO';
+ }
+ echo '<tr class="';
+ echo $odd_row ? 'odd' : 'even'; $odd_row = ! $odd_row;
+ echo '">';
+ echo '<td class="nowrap">';
+
+ if (isset($pk_array[$row['Field']])) {
+ echo '<u>' . htmlspecialchars($column_name) . '</u>';
+ } else {
+ echo htmlspecialchars($column_name);
+ }
+ echo '</td>';
+ echo '<td' . $type_nowrap . ' lang="en" dir="ltr">' . $type . '</td>';
+ echo '<td>';
+ echo (($row['Null'] == 'NO') ? __('No') : __('Yes'));
+ echo '</td>';
+ echo '<td class="nowrap">';
+ if (isset($row['Default'])) {
+ echo $row['Default'];
+ }
+ echo '</td>';
+
+ if ($have_rel) {
+ echo ' <td>';
+ if (isset($res_rel[$column_name])) {
+ echo htmlspecialchars(
+ $res_rel[$column_name]['foreign_table']
+ . ' -> '
+ . $res_rel[$column_name]['foreign_field']
+ );
+ }
+ echo '</td>' . "\n";
+ }
+ echo ' <td>';
+ if (isset($comments[$column_name])) {
+ echo htmlspecialchars($comments[$column_name]);
+ }
+ echo '</td>' . "\n";
+ if ($cfgRelation['mimework']) {
+ $mime_map = PMA_getMIME($db, $table, true);
+
+ echo ' <td>';
+ if (isset($mime_map[$column_name])) {
+ echo htmlspecialchars(
+ str_replace('_', '/', $mime_map[$column_name]['mimetype'])
+ );
+ }
+ echo '</td>' . "\n";
+ }
+ echo '</tr>';
+ } // end foreach
+ $count++;
+ echo '</table>';
+ // display indexes information
+ if (count(PMA_Index::getFromTable($table, $db)) > 0) {
+ echo PMA_Index::getView($table, $db, true);
+ }
+ echo '</div>';
+} //ends main while
+
+/**
+ * Displays the footer
+ */
+echo PMA_Util::getButton();
+
+?>
diff --git a/db_events.php b/db_events.php
new file mode 100644
index 0000000000..917778bc35
--- /dev/null
+++ b/db_events.php
@@ -0,0 +1,26 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Events management.
+ *
+ * @package PhpMyAdmin
+ */
+
+/**
+ * Include required files
+ */
+require_once 'libraries/common.inc.php';
+require_once 'libraries/Util.class.php';
+
+/**
+ * Include all other files
+ */
+require_once 'libraries/rte/rte_events.lib.php';
+
+/**
+ * Do the magic
+ */
+$_PMA_RTE = 'EVN';
+require_once 'libraries/rte/rte_main.inc.php';
+
+?>
diff --git a/db_export.php b/db_export.php
new file mode 100644
index 0000000000..5313e0e022
--- /dev/null
+++ b/db_export.php
@@ -0,0 +1,94 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * dumps a database
+ *
+ * @package PhpMyAdmin
+ */
+
+/**
+ * Gets some core libraries
+ */
+require_once 'libraries/common.inc.php';
+
+$response = PMA_Response::getInstance();
+$header = $response->getHeader();
+$scripts = $header->getScripts();
+$scripts->addFile('export.js');
+
+// $sub_part is also used in db_info.inc.php to see if we are coming from
+// db_export.php, in which case we don't obey $cfg['MaxTableList']
+$sub_part = '_export';
+require_once 'libraries/db_common.inc.php';
+$url_query .= '&amp;goto=db_export.php';
+require_once 'libraries/db_info.inc.php';
+
+/**
+ * Displays the form
+ */
+$export_page_title = __('View dump (schema) of database');
+
+// exit if no tables in db found
+if ($num_tables < 1) {
+ PMA_Message::error(__('No tables found in database.'))->display();
+ exit;
+} // end if
+
+$multi_values = '<div>';
+$multi_values .= '<a href="#"';
+$multi_values .= ' onclick="setSelectOptions(\'dump\', \'table_select[]\', true);'
+ . ' return false;">';
+$multi_values .= __('Select All');
+$multi_values .= '</a>';
+$multi_values .= ' / ';
+$multi_values .= '<a href="#"';
+$multi_values .= ' onclick="setSelectOptions(\'dump\', \'table_select[]\', false);'
+ . ' return false;">';
+$multi_values .= __('Unselect All');
+$multi_values .= '</a><br />';
+
+$multi_values .= '<select name="table_select[]" id="table_select" size="10"'
+ . ' multiple="multiple">';
+$multi_values .= "\n";
+
+// when called by libraries/mult_submits.inc.php
+if (!empty($_POST['selected_tbl']) && empty($table_select)) {
+ $table_select = $_POST['selected_tbl'];
+}
+
+// Check if the selected tables are defined in $_GET
+// (from clicking Back button on export.php)
+if (isset($_GET['table_select'])) {
+ $_GET['table_select'] = urldecode($_GET['table_select']);
+ $_GET['table_select'] = explode(",", $_GET['table_select']);
+}
+
+foreach ($tables as $each_table) {
+ if (isset($_GET['table_select'])) {
+ if (in_array($each_table['Name'], $_GET['table_select'])) {
+ $is_selected = ' selected="selected"';
+ } else {
+ $is_selected = '';
+ }
+ } elseif (isset($table_select)) {
+ if (in_array($each_table['Name'], $table_select)) {
+ $is_selected = ' selected="selected"';
+ } else {
+ $is_selected = '';
+ }
+ } else {
+ $is_selected = ' selected="selected"';
+ }
+ $table_html = htmlspecialchars($each_table['Name']);
+ $multi_values .= ' <option value="' . $table_html . '"'
+ . $is_selected . '>'
+ . str_replace(' ', '&nbsp;', $table_html) . '</option>' . "\n";
+} // end for
+
+$multi_values .= "\n";
+$multi_values .= '</select></div>';
+
+$export_type = 'database';
+require_once 'libraries/display_export.inc.php';
+
+?>
diff --git a/db_import.php b/db_import.php
new file mode 100644
index 0000000000..70425b2183
--- /dev/null
+++ b/db_import.php
@@ -0,0 +1,25 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Database import page
+ *
+ * @package PhpMyAdmin
+ */
+
+require_once 'libraries/common.inc.php';
+
+$response = PMA_Response::getInstance();
+$header = $response->getHeader();
+$scripts = $header->getScripts();
+$scripts->addFile('import.js');
+
+/**
+ * Gets tables informations and displays top links
+ */
+require 'libraries/db_common.inc.php';
+require 'libraries/db_info.inc.php';
+
+$import_type = 'database';
+require 'libraries/display_import.inc.php';
+
+?>
diff --git a/db_operations.php b/db_operations.php
new file mode 100644
index 0000000000..6d0db1982a
--- /dev/null
+++ b/db_operations.php
@@ -0,0 +1,300 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * handles miscellaneous db operations:
+ * - move/rename
+ * - copy
+ * - changing collation
+ * - changing comment
+ * - adding tables
+ * - viewing PDF schemas
+ *
+ * @package PhpMyAdmin
+ */
+
+/**
+ * requirements
+ */
+require_once 'libraries/common.inc.php';
+require_once 'libraries/mysql_charsets.inc.php';
+
+/**
+ * functions implementation for this script
+ */
+require_once 'libraries/operations.lib.php';
+
+// add a javascript file for jQuery functions to handle Ajax actions
+$response = PMA_Response::getInstance();
+$header = $response->getHeader();
+$scripts = $header->getScripts();
+$scripts->addFile('db_operations.js');
+
+/**
+ * Rename/move or copy database
+ */
+if (strlen($db)
+ && (! empty($_REQUEST['db_rename']) || ! empty($_REQUEST['db_copy']))
+) {
+ if (! empty($_REQUEST['db_rename'])) {
+ $move = true;
+ } else {
+ $move = false;
+ }
+
+ if (! isset($_REQUEST['newname']) || ! strlen($_REQUEST['newname'])) {
+ $message = PMA_Message::error(__('The database name is empty!'));
+ } else {
+ $sql_query = ''; // in case target db exists
+ $_error = false;
+ if ($move
+ || (isset($_REQUEST['create_database_before_copying'])
+ && $_REQUEST['create_database_before_copying'])
+ ) {
+ $sql_query = PMA_getSqlQueryAndCreateDbBeforeCopy();
+ }
+
+ // here I don't use DELIMITER because it's not part of the
+ // language; I have to send each statement one by one
+
+ // to avoid selecting alternatively the current and new db
+ // we would need to modify the CREATE definitions to qualify
+ // the db name
+ PMA_runProcedureAndFunctionDefinitions($db);
+
+ // go back to current db, just in case
+ $GLOBALS['dbi']->selectDb($db);
+
+ $tables_full = $GLOBALS['dbi']->getTablesFull($db);
+
+ include_once "libraries/plugin_interface.lib.php";
+ // remove all foreign key constraints, otherwise we can get errors
+ $export_sql_plugin = PMA_getPlugin(
+ "export",
+ "sql",
+ 'libraries/plugins/export/',
+ array(
+ 'single_table' => isset($single_table),
+ 'export_type' => 'database'
+ )
+ );
+ $GLOBALS['sql_constraints_query_full_db']
+ = PMA_getSqlConstraintsQueryForFullDb(
+ $tables_full, $export_sql_plugin, $move, $db
+ );
+
+ $views = PMA_getViewsAndCreateSqlViewStandIn(
+ $tables_full, $export_sql_plugin, $db
+ );
+
+ list($sql_query, $_error) = PMA_getSqlQueryForCopyTable(
+ $tables_full, $sql_query, $move, $db
+ );
+
+ // handle the views
+ if (! $_error) {
+ $_error = PMA_handleTheViews($views, $move, $db);
+ }
+ unset($views);
+
+ // now that all tables exist, create all the accumulated constraints
+ if (! $_error && count($GLOBALS['sql_constraints_query_full_db']) > 0) {
+ PMA_createAllAccumulatedConstraints();
+ }
+
+ if (! PMA_DRIZZLE && PMA_MYSQL_INT_VERSION >= 50100) {
+ // here DELIMITER is not used because it's not part of the
+ // language; each statement is sent one by one
+
+ PMA_runEventDefinitionsForDb($db);
+ }
+
+ // go back to current db, just in case
+ $GLOBALS['dbi']->selectDb($db);
+
+ // Duplicate the bookmarks for this db (done once for each db)
+ PMA_duplicateBookmarks($_error, $db);
+
+ if (! $_error && $move) {
+ /**
+ * cleanup pmadb stuff for this db
+ */
+ include_once 'libraries/relation_cleanup.lib.php';
+ PMA_relationsCleanupDatabase($db);
+
+ // if someday the RENAME DATABASE reappears, do not DROP
+ $local_query = 'DROP DATABASE ' . PMA_Util::backquote($db) . ';';
+ $sql_query .= "\n" . $local_query;
+ $GLOBALS['dbi']->query($local_query);
+
+ $message = PMA_Message::success(
+ __('Database %1$s has been renamed to %2$s')
+ );
+ $message->addParam($db);
+ $message->addParam($_REQUEST['newname']);
+ } elseif (! $_error) {
+ $message = PMA_Message::success(
+ __('Database %1$s has been copied to %2$s')
+ );
+ $message->addParam($db);
+ $message->addParam($_REQUEST['newname']);
+ }
+ $reload = true;
+
+ /* Change database to be used */
+ if (! $_error && $move) {
+ $db = $_REQUEST['newname'];
+ } elseif (! $_error) {
+ if (isset($_REQUEST['switch_to_new'])
+ && $_REQUEST['switch_to_new'] == 'true'
+ ) {
+ $GLOBALS['PMA_Config']->setCookie('pma_switch_to_new', 'true');
+ $db = $_REQUEST['newname'];
+ } else {
+ $GLOBALS['PMA_Config']->setCookie('pma_switch_to_new', '');
+ }
+ }
+
+ if ($_error && ! isset($message)) {
+ $message = PMA_Message::error();
+ }
+ }
+
+ /**
+ * Database has been successfully renamed/moved. If in an Ajax request,
+ * generate the output with {@link PMA_Response} and exit
+ */
+ if ($GLOBALS['is_ajax_request'] == true) {
+ $response = PMA_Response::getInstance();
+ $response->isSuccess($message->isSuccess());
+ $response->addJSON('message', $message);
+ $response->addJSON('newname', $_REQUEST['newname']);
+ $response->addJSON(
+ 'sql_query',
+ PMA_Util::getMessage(null, $sql_query)
+ );
+ $response->addJSON('db', $db);
+ exit;
+ }
+}
+
+/**
+ * Settings for relations stuff
+ */
+
+$cfgRelation = PMA_getRelationsParam();
+
+/**
+ * Check if comments were updated
+ * (must be done before displaying the menu tabs)
+ */
+if (isset($_REQUEST['comment'])) {
+ PMA_setDbComment($db, $_REQUEST['comment']);
+}
+
+require 'libraries/db_common.inc.php';
+$url_query .= '&amp;goto=db_operations.php';
+
+// Gets the database structure
+$sub_part = '_structure';
+require 'libraries/db_info.inc.php';
+echo "\n";
+
+if (isset($message)) {
+ echo PMA_Util::getMessage($message, $sql_query);
+ unset($message);
+}
+
+$_REQUEST['db_collation'] = PMA_getDbCollation($db);
+$is_information_schema = $GLOBALS['dbi']->isSystemSchema($db);
+
+$response->addHTML('<div id="boxContainer" data-box-width="300">');
+
+if (!$is_information_schema) {
+ if ($cfgRelation['commwork']) {
+ /**
+ * database comment
+ */
+ $response->addHTML(PMA_getHtmlForDatabaseComment($db));
+ }
+
+ $response->addHTML('<div class="operations_half_width">');
+ ob_start();
+ include 'libraries/display_create_table.lib.php';
+ $content = ob_get_contents();
+ ob_end_clean();
+ $response->addHTML($content);
+ $response->addHTML('</div>');
+
+ /**
+ * rename database
+ */
+ if ($db != 'mysql') {
+ $response->addHTML(PMA_getHtmlForRenameDatabase($db));
+ }
+
+ // Drop link if allowed
+ // Don't even try to drop information_schema.
+ // You won't be able to. Believe me. You won't.
+ // Don't allow to easily drop mysql database, RFE #1327514.
+ if (($is_superuser || $GLOBALS['cfg']['AllowUserDropDatabase'])
+ && ! $db_is_information_schema
+ && (PMA_DRIZZLE || $db != 'mysql')
+ ) {
+ $response->addHTML(PMA_getHtmlForDropDatabaseLink($db));
+ }
+ /**
+ * Copy database
+ */
+ $response->addHTML(PMA_getHtmlForCopyDatabase($db));
+
+ /**
+ * Change database charset
+ */
+ $response->addHTML(PMA_getHtmlForChangeDatabaseCharset($db, $table));
+
+ if ($num_tables > 0
+ && ! $cfgRelation['allworks']
+ && $cfg['PmaNoRelation_DisableWarning'] == false
+ ) {
+ $message = PMA_Message::notice(
+ __('The phpMyAdmin configuration storage has been deactivated. To find out why click %shere%s.')
+ );
+ $message->addParam(
+ '<a href="' . $cfg['PmaAbsoluteUri']
+ . 'chk_rel.php?' . $url_query . '">',
+ false
+ );
+ $message->addParam('</a>', false);
+ /* Show error if user has configured something, notice elsewhere */
+ if (!empty($cfg['Servers'][$server]['pmadb'])) {
+ $message->isError(true);
+ }
+ $response->addHTML('<div class="operations_full_width">');
+ $response->addHTML($message->getDisplay());
+ $response->addHTML('</div>');
+ } // end if
+} // end if (!$is_information_schema)
+
+$response->addHTML('</div>');
+
+// not sure about displaying the PDF dialog in case db is information_schema
+if ($cfgRelation['pdfwork'] && $num_tables > 0) {
+ // We only show this if we find something in the new pdf_pages table
+ $test_query = '
+ SELECT *
+ FROM ' . PMA_Util::backquote($GLOBALS['cfgRelation']['db'])
+ . '.' . PMA_Util::backquote($cfgRelation['pdf_pages']) . '
+ WHERE db_name = \'' . PMA_Util::sqlAddSlashes($db) . '\'';
+ $test_rs = PMA_queryAsControlUser(
+ $test_query,
+ null,
+ PMA_DatabaseInterface::QUERY_STORE
+ );
+
+ /*
+ * Export Relational Schema View
+ */
+ $response->addHTML(PMA_getHtmlForExportRelationalSchemaView($url_query));
+} // end if
+
+?>
diff --git a/db_printview.php b/db_printview.php
new file mode 100644
index 0000000000..137bdaaf78
--- /dev/null
+++ b/db_printview.php
@@ -0,0 +1,180 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Print view of a database
+ *
+ * @package PhpMyAdmin
+ */
+
+/**
+ *
+ */
+require_once 'libraries/common.inc.php';
+
+$response = PMA_Response::getInstance();
+$header = $response->getHeader();
+$header->enablePrintView();
+
+PMA_Util::checkParameters(array('db'));
+
+/**
+ * Defines the url to return to in case of error in a sql statement
+ */
+$err_url = 'db_sql.php?' . PMA_URL_getCommon($db);
+
+/**
+ * Settings for relations stuff
+ */
+$cfgRelation = PMA_getRelationsParam();
+
+/**
+ * If there is at least one table, displays the printer friendly view, else
+ * an error message
+ */
+$tables = $GLOBALS['dbi']->getTablesFull($db);
+$num_tables = count($tables);
+
+echo '<br />';
+
+// 1. No table
+if ($num_tables == 0) {
+ echo __('No tables found in database.');
+} else {
+ // 2. Shows table information
+ echo '<table>';
+ echo '<thead>';
+ echo '<tr>';
+ echo '<th>' . __('Table') . '</th>';
+ echo '<th>' . __('Rows') . '</th>';
+ echo '<th>' . __('Type') . '</th>';
+ if ($cfg['ShowStats']) {
+ echo '<th>' . __('Size') . '</th>';
+ }
+ echo '<th>' . __('Comments') . '</th>';
+ echo '</tr>';
+ echo '</thead>';
+ echo '<tbody>';
+ $sum_entries = $sum_size = 0;
+ $odd_row = true;
+ foreach ($tables as $sts_data) {
+ if (PMA_Table::isMerge($db, $sts_data['TABLE_NAME'])
+ || strtoupper($sts_data['ENGINE']) == 'FEDERATED'
+ ) {
+ $merged_size = true;
+ } else {
+ $merged_size = false;
+ }
+ $sum_entries += $sts_data['TABLE_ROWS'];
+ echo '<tr class="' . ($odd_row ? 'odd' : 'even') . '">';
+ echo '<th>';
+ echo htmlspecialchars($sts_data['TABLE_NAME']);
+ echo '</th>';
+
+ if (isset($sts_data['TABLE_ROWS'])) {
+ echo '<td class="right">';
+ if ($merged_size) {
+ echo '<i>';
+ echo PMA_Util::formatNumber($sts_data['TABLE_ROWS'], 0);
+ echo '</i>';
+ } else {
+ echo PMA_Util::formatNumber($sts_data['TABLE_ROWS'], 0);
+ }
+ echo '</td>';
+ echo '<td class="nowrap">';
+ echo $sts_data['ENGINE'];
+ echo '</td>';
+ if ($cfg['ShowStats']) {
+ $tblsize = $sts_data['Data_length'] + $sts_data['Index_length'];
+ $sum_size += $tblsize;
+ list($formated_size, $unit)
+ = PMA_Util::formatByteDown($tblsize, 3, 1);
+ echo '<td class="right nowrap">';
+ echo $formated_size . ' ' . $unit;
+ echo '</td>';
+ } // end if
+ } else {
+ echo '<td colspan="3" class="center">';
+ if (! PMA_Table::isView($db, $sts_data['TABLE_NAME'])) {
+ echo __('in use');
+ }
+ echo '</td>';
+ }
+ echo '<td>';
+ if (! empty($sts_data['Comment'])) {
+ echo htmlspecialchars($sts_data['Comment']);
+ $needs_break = '<br />';
+ } else {
+ $needs_break = '';
+ }
+
+ if (! empty($sts_data['Create_time'])
+ || ! empty($sts_data['Update_time'])
+ || ! empty($sts_data['Check_time'])
+ ) {
+ echo $needs_break;
+ echo '<table width="100%">';
+
+ if (! empty($sts_data['Create_time'])) {
+ echo '<tr>';
+ echo '<td class="right">' . __('Creation:') . '</td>';
+ echo '<td class="right">';
+ echo PMA_Util::localisedDate(strtotime($sts_data['Create_time']));
+ echo '</td>';
+ echo '</tr>';
+ }
+
+ if (! empty($sts_data['Update_time'])) {
+ echo '<tr>';
+ echo '<td class="right">' . __('Last update:') . '</td>';
+ echo '<td class="right">';
+ echo PMA_Util::localisedDate(strtotime($sts_data['Update_time']));
+ echo '</td>';
+ echo '</tr>';
+ }
+
+ if (! empty($sts_data['Check_time'])) {
+ echo '<tr>';
+ echo '<td class="right">' . __('Last check:') . '</td>';
+ echo '<td class="right">';
+ echo PMA_Util::localisedDate(strtotime($sts_data['Check_time']));
+ echo '</td>';
+ echo '</tr>';
+ }
+ echo '</table>';
+ }
+ echo '</td>';
+ echo '</tr>';
+ }
+ echo '<tr>';
+ echo '<th class="center">';
+ printf(
+ _ngettext('%s table', '%s tables', $num_tables),
+ PMA_Util::formatNumber($num_tables, 0)
+ );
+ echo '</th>';
+ echo '<th class="right nowrap">';
+ echo PMA_Util::formatNumber($sum_entries, 0);
+ echo '</th>';
+ echo '<th class="center">';
+ echo '--';
+ echo '</th>';
+ if ($cfg['ShowStats']) {
+ list($sum_formated, $unit)
+ = PMA_Util::formatByteDown($sum_size, 3, 1);
+ echo '<th class="right nowrap">';
+ echo $sum_formated . ' ' . $unit;
+ echo '</th>';
+ }
+ echo '<th></th>';
+ echo '</tr>';
+ echo '</tbody>';
+ echo '</table>';
+}
+
+/**
+ * Displays the footer
+ */
+echo PMA_Util::getButton();
+
+echo "<div id='PMA_disable_floating_menubar'></div>\n";
+?>
diff --git a/db_qbe.php b/db_qbe.php
new file mode 100644
index 0000000000..7fad34d72d
--- /dev/null
+++ b/db_qbe.php
@@ -0,0 +1,78 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * query by example the whole database
+ *
+ * @package PhpMyAdmin
+ */
+
+/**
+ * requirements
+ */
+require_once 'libraries/common.inc.php';
+require_once 'libraries/DBQbe.class.php';
+require_once 'libraries/bookmark.lib.php';
+require_once 'libraries/sql.lib.php';
+
+$response = PMA_Response::getInstance();
+
+// Gets the relation settings
+$cfgRelation = PMA_getRelationsParam();
+
+/**
+ * A query has been submitted -> (maybe) execute it
+ */
+$message_to_display = false;
+if (isset($_REQUEST['submit_sql']) && ! empty($sql_query)) {
+ if (! preg_match('@^SELECT@i', $sql_query)) {
+ $message_to_display = true;
+ } else {
+ $goto = 'db_sql.php';
+
+ // Parse and analyze the query
+ include_once 'libraries/parse_analyze.inc.php';
+
+ PMA_executeQueryAndSendQueryResponse(
+ $analyzed_sql_results, false, $_REQUEST['db'], null, null, null, null,
+ false, null, null, null, null, $goto, $pmaThemeImage, null, null, null,
+ $sql_query, null, null
+ );
+ }
+}
+
+$sub_part = '_qbe';
+require 'libraries/db_common.inc.php';
+$url_query .= '&amp;goto=db_qbe.php';
+$url_params['goto'] = 'db_qbe.php';
+require 'libraries/db_info.inc.php';
+
+if ($message_to_display) {
+ PMA_Message::error(__('You have to choose at least one column to display'))->display();
+}
+unset($message_to_display);
+
+// create new qbe search instance
+$db_qbe = new PMA_DBQbe($GLOBALS['db']);
+
+/**
+ * Displays the Query by example form
+ */
+if ($cfgRelation['designerwork']) {
+ $url = 'pmd_general.php' . PMA_URL_getCommon(
+ array_merge(
+ $url_params,
+ array('query' => 1)
+ )
+ );
+ $response->addHTML(
+ PMA_Message::notice(
+ sprintf(
+ __('Switch to %svisual builder%s'),
+ '<a href="' . $url . '">',
+ '</a>'
+ )
+ )
+ );
+}
+$response->addHTML($db_qbe->getSelectionForm($cfgRelation));
+?>
diff --git a/db_routines.php b/db_routines.php
new file mode 100644
index 0000000000..a58d4c0a21
--- /dev/null
+++ b/db_routines.php
@@ -0,0 +1,27 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Routines management.
+ *
+ * @package PhpMyAdmin
+ */
+
+/**
+ * Include required files
+ */
+require_once 'libraries/common.inc.php';
+require_once 'libraries/Util.class.php';
+require_once 'libraries/mysql_charsets.inc.php';
+
+/**
+ * Include all other files
+ */
+require_once 'libraries/rte/rte_routines.lib.php';
+
+/**
+ * Do the magic
+ */
+$_PMA_RTE = 'RTN';
+require_once 'libraries/rte/rte_main.inc.php';
+
+?>
diff --git a/db_search.php b/db_search.php
new file mode 100644
index 0000000000..793fe91e88
--- /dev/null
+++ b/db_search.php
@@ -0,0 +1,62 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * searchs the entire database
+ *
+ * @todo make use of UNION when searching multiple tables
+ * @todo display executed query, optional?
+ * @package PhpMyAdmin
+ */
+
+/**
+ * Gets some core libraries
+ */
+require_once 'libraries/common.inc.php';
+require_once 'libraries/DbSearch.class.php';
+
+$response = PMA_Response::getInstance();
+$header = $response->getHeader();
+$scripts = $header->getScripts();
+$scripts->addFile('db_search.js');
+$scripts->addFile('sql.js');
+$scripts->addFile('makegrid.js');
+$scripts->addFile('jquery/jquery-ui-timepicker-addon.js');
+
+require 'libraries/db_common.inc.php';
+
+// If config variable $GLOBALS['cfg']['Usedbsearch'] is on false : exit.
+if (! $GLOBALS['cfg']['UseDbSearch']) {
+ PMA_Util::mysqlDie(
+ __('Access denied'), '', false, $err_url
+ );
+} // end if
+$url_query .= '&amp;goto=db_search.php';
+$url_params['goto'] = 'db_search.php';
+
+// Create a database search instance
+$db_search = new PMA_DbSearch($GLOBALS['db']);
+
+// Display top links if we are not in an Ajax request
+if ( $GLOBALS['is_ajax_request'] != true) {
+ include 'libraries/db_info.inc.php';
+}
+
+// Main search form has been submitted, get results
+if (isset($_REQUEST['submit_search'])) {
+ $response->addHTML($db_search->getSearchResults());
+} else {
+ $response->addHTML('<div id="searchresults"></div>');
+}
+
+// If we are in an Ajax request, we need to exit after displaying all the HTML
+if ($GLOBALS['is_ajax_request'] == true && empty($_REQUEST['ajax_page_request'])) {
+ exit;
+}
+
+// Display the search form
+$response->addHTML('<div id="togglesearchresultsdiv">'
+ . '<a id="togglesearchresultlink"></a></div>'
+ . '<br class="clearfloat" />');
+$response->addHTML($db_search->getSelectionForm($url_params));
+$response->addHTML($db_search->_getResultDivs());
+?>
diff --git a/db_sql.php b/db_sql.php
new file mode 100644
index 0000000000..67bbbb59d6
--- /dev/null
+++ b/db_sql.php
@@ -0,0 +1,44 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Database SQL executor
+ *
+ * @package PhpMyAdmin
+ */
+
+/**
+ *
+ */
+require_once 'libraries/common.inc.php';
+
+/**
+ * Runs common work
+ */
+$response = PMA_Response::getInstance();
+$header = $response->getHeader();
+$scripts = $header->getScripts();
+$scripts->addFile('functions.js');
+$scripts->addFile('makegrid.js');
+$scripts->addFile('sql.js');
+
+require 'libraries/db_common.inc.php';
+require_once 'libraries/sql_query_form.lib.php';
+
+// After a syntax error, we return to this script
+// with the typed query in the textarea.
+$goto = 'db_sql.php';
+$back = 'db_sql.php';
+
+/**
+ * Query box, bookmark, insert data from textfile
+ */
+$response->addHTML(
+ PMA_getHtmlForSqlQueryForm(
+ true, false,
+ isset($_REQUEST['delimiter'])
+ ? htmlspecialchars($_REQUEST['delimiter'])
+ : ';'
+ )
+);
+
+?>
diff --git a/db_structure.php b/db_structure.php
new file mode 100644
index 0000000000..0eed9ad925
--- /dev/null
+++ b/db_structure.php
@@ -0,0 +1,317 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Database structure manipulation
+ *
+ * @package PhpMyAdmin
+ */
+
+/**
+ *
+ */
+require_once 'libraries/common.inc.php';
+
+/**
+ * Function implementations for this script
+ */
+require_once 'libraries/structure.lib.php';
+
+$response = PMA_Response::getInstance();
+$header = $response->getHeader();
+$scripts = $header->getScripts();
+$scripts->addFile('db_structure.js');
+$scripts->addFile('tbl_change.js');
+$scripts->addFile('jquery/jquery-ui-timepicker-addon.js');
+
+// Drops/deletes/etc. multiple tables if required
+if ((!empty($_POST['submit_mult']) && isset($_POST['selected_tbl']))
+ || isset($_POST['mult_btn'])
+) {
+ $action = 'db_structure.php';
+ $err_url = 'db_structure.php?'. PMA_URL_getCommon($db);
+
+ // see bug #2794840; in this case, code path is:
+ // db_structure.php -> libraries/mult_submits.inc.php -> sql.php
+ // -> db_structure.php and if we got an error on the multi submit,
+ // we must display it here and not call again mult_submits.inc.php
+ if (! isset($_POST['error']) || false === $_POST['error']) {
+ include 'libraries/mult_submits.inc.php';
+ }
+ if (empty($_POST['message'])) {
+ $_POST['message'] = PMA_Message::success();
+ }
+}
+require 'libraries/db_common.inc.php';
+$url_query .= '&amp;goto=db_structure.php';
+
+// Gets the database structure
+$sub_part = '_structure';
+require 'libraries/db_info.inc.php';
+
+if (!PMA_DRIZZLE) {
+ include_once 'libraries/replication.inc.php';
+} else {
+ $server_slave_status = false;
+}
+
+require_once 'libraries/bookmark.lib.php';
+
+require_once 'libraries/mysql_charsets.inc.php';
+$db_collation = PMA_getDbCollation($db);
+
+$titles = PMA_Util::buildActionTitles();
+
+// 1. No tables
+
+if ($num_tables == 0) {
+ $response->addHTML(
+ '<p>' . __('No tables found in database.') . '</p>' . "\n"
+ );
+ if (empty($db_is_information_schema)) {
+ ob_start();
+ include 'libraries/display_create_table.lib.php';
+ $content = ob_get_contents();
+ ob_end_clean();
+ $response->addHTML($content);
+ unset($content);
+ } // end if (Create Table dialog)
+ exit;
+}
+
+// else
+// 2. Shows table informations
+
+/**
+ * Displays the tables list
+ */
+$response->addHTML('<div id="tableslistcontainer">');
+$_url_params = array(
+ 'pos' => $pos,
+ 'db' => $db);
+
+// Add the sort options if they exists
+if (isset($_REQUEST['sort'])) {
+ $_url_params['sort'] = $_REQUEST['sort'];
+}
+
+if (isset($_REQUEST['sort_order'])) {
+ $_url_params['sort_order'] = $_REQUEST['sort_order'];
+}
+
+$response->addHTML(
+ PMA_Util::getListNavigator(
+ $total_num_tables, $pos, $_url_params, 'db_structure.php',
+ 'frame_content', $GLOBALS['cfg']['MaxTableList']
+ )
+);
+
+// tables form
+$response->addHTML(
+ '<form method="post" action="db_structure.php" '
+ . 'name="tablesForm" id="tablesForm">'
+);
+
+$response->addHTML(PMA_URL_getHiddenInputs($db));
+
+$response->addHTML(
+ PMA_tableHeader($db_is_information_schema, $server_slave_status)
+);
+
+$i = $sum_entries = 0;
+$overhead_check = '';
+$create_time_all = '';
+$update_time_all = '';
+$check_time_all = '';
+$num_columns = $cfg['PropertiesNumColumns'] > 1
+ ? ceil($num_tables / $cfg['PropertiesNumColumns']) + 1
+ : 0;
+$row_count = 0;
+$sum_size = (double) 0;
+$overhead_size = (double) 0;
+
+$hidden_fields = array();
+$odd_row = true;
+$sum_row_count_pre = '';
+
+foreach ($tables as $keyname => $current_table) {
+ // Get valid statistics whatever is the table type
+
+ $drop_query = '';
+ $drop_message = '';
+ $overhead = '';
+
+ $table_is_view = false;
+ $table_encoded = urlencode($current_table['TABLE_NAME']);
+ // Sets parameters for links
+ $tbl_url_query = $url_query . '&amp;table=' . $table_encoded;
+ // do not list the previous table's size info for a view
+
+ list($current_table, $formatted_size, $unit, $formatted_overhead,
+ $overhead_unit, $overhead_size, $table_is_view, $sum_size)
+ = PMA_getStuffForEngineTypeTable(
+ $current_table, $db_is_information_schema,
+ $is_show_stats, $table_is_view, $sum_size, $overhead_size
+ );
+
+ if (! PMA_Table::isMerge($db, $current_table['TABLE_NAME'])) {
+ $sum_entries += $current_table['TABLE_ROWS'];
+ }
+
+ if (isset($current_table['Collation'])) {
+ $collation = '<dfn title="'
+ . PMA_getCollationDescr($current_table['Collation']) . '">'
+ . $current_table['Collation'] . '</dfn>';
+ } else {
+ $collation = '---';
+ }
+
+ if ($is_show_stats) {
+ if ($formatted_overhead != '') {
+ $overhead = '<a href="tbl_structure.php?'
+ . $tbl_url_query . '#showusage">'
+ . '<span>' . $formatted_overhead . '</span>'
+ . '<span class="unit">' . $overhead_unit . '</span>'
+ . '</a>' . "\n";
+ $overhead_check .=
+ "markAllRows('row_tbl_" . ($i + 1) . "');";
+ } else {
+ $overhead = '-';
+ }
+ } // end if
+
+ unset($showtable);
+
+ if ($GLOBALS['cfg']['ShowDbStructureCreation']) {
+ list($create_time, $create_time_all) = PMA_getTimeForCreateUpdateCheck(
+ $current_table, 'Create_time', $create_time_all
+ );
+ }
+
+ if ($GLOBALS['cfg']['ShowDbStructureLastUpdate']) {
+ // $showtable might already be set from ShowDbStructureCreation, see above
+ list($update_time, $update_time_all) = PMA_getTimeForCreateUpdateCheck(
+ $current_table, 'Update_time', $update_time_all
+ );
+ }
+
+ if ($GLOBALS['cfg']['ShowDbStructureLastCheck']) {
+ // $showtable might already be set from ShowDbStructureCreation, see above
+ list($check_time, $check_time_all) = PMA_getTimeForCreateUpdateCheck(
+ $current_table, 'Check_time', $check_time_all
+ );
+ }
+
+ list($alias, $truename) = PMA_getAliasAndTrueName(
+ $tooltip_aliasname, $current_table, $tooltip_truename
+ );
+
+ $i++;
+
+ $row_count++;
+ if ($table_is_view) {
+ $hidden_fields[] = '<input type="hidden" name="views[]" value="'
+ . htmlspecialchars($current_table['TABLE_NAME']) . '" />';
+ }
+
+ /*
+ * Always activate links for Browse, Search and Empty, even if
+ * the icons are greyed, because
+ * 1. for views, we don't know the number of rows at this point
+ * 2. for tables, another source could have populated them since the
+ * page was generated
+ *
+ * I could have used the PHP ternary conditional operator but I find
+ * the code easier to read without this operator.
+ */
+ list($browse_table, $search_table, $browse_table_label, $empty_table,
+ $tracking_icon) = PMA_getHtmlForActionLinks(
+ $current_table, $table_is_view, $tbl_url_query,
+ $titles, $truename, $db_is_information_schema, $url_query
+ );
+
+ if (! $db_is_information_schema) {
+ list($drop_query, $drop_message)
+ = PMA_getTableDropQueryAndMessage($table_is_view, $current_table);
+ }
+
+ if ($num_columns > 0
+ && $num_tables > $num_columns
+ && ($row_count % $num_columns) == 0
+ ) {
+ $row_count = 1;
+ $odd_row = true;
+
+ $response->addHTML(
+ '</tr></tbody></table>'
+ );
+
+ $response->addHTML(PMA_tableHeader(false, $server_slave_status));
+ }
+
+ list($do, $ignored) = PMA_getServerSlaveStatus(
+ $server_slave_status, $truename
+ );
+
+ list($html_output, $odd_row) = PMA_getHtmlForStructureTableRow(
+ $i, $odd_row, $table_is_view, $current_table,
+ $browse_table_label, $tracking_icon, $server_slave_status,
+ $browse_table, $tbl_url_query, $search_table, $db_is_information_schema,
+ $titles, $empty_table, $drop_query, $drop_message, $collation,
+ $formatted_size, $unit, $overhead,
+ (isset ($create_time) ? $create_time : ''),
+ (isset ($update_time) ? $update_time : ''),
+ (isset ($check_time) ? $check_time : ''),
+ $is_show_stats, $ignored, $do, $colspan_for_structure
+ );
+ $response->addHTML($html_output);
+
+} // end foreach
+
+// Show Summary
+$response->addHTML('</tbody>');
+$response->addHTML(
+ PMA_getHtmlBodyForTableSummary(
+ $num_tables, $server_slave_status, $db_is_information_schema, $sum_entries,
+ $db_collation, $is_show_stats, $sum_size, $overhead_size, $create_time_all,
+ $update_time_all, $check_time_all, $sum_row_count_pre
+ )
+);
+$response->addHTML('</table>');
+//check all
+$response->addHTML(
+ PMA_getHtmlForCheckAllTables(
+ $pmaThemeImage, $text_dir, $overhead_check,
+ $db_is_information_schema, $hidden_fields
+ )
+);
+$response->addHTML('</form>'); //end of form
+
+// display again the table list navigator
+$response->addHTML(
+ PMA_Util::getListNavigator(
+ $total_num_tables, $pos, $_url_params, 'db_structure.php',
+ 'frame_content', $GLOBALS['cfg']['MaxTableList']
+ )
+);
+
+$response->addHTML('</div><hr />');
+
+/**
+ * Work on the database
+ */
+/* DATABASE WORK */
+/* Printable view of a table */
+$response->addHTML(
+ PMA_getHtmlForTablePrintViewLink($url_query)
+ . PMA_getHtmlForDataDictionaryLink($url_query)
+);
+
+if (empty($db_is_information_schema)) {
+ ob_start();
+ include 'libraries/display_create_table.lib.php';
+ $content = ob_get_contents();
+ ob_end_clean();
+ $response->addHTML($content);
+} // end if (Create Table dialog)
+
+?>
diff --git a/db_tracking.php b/db_tracking.php
new file mode 100644
index 0000000000..bcd552c13b
--- /dev/null
+++ b/db_tracking.php
@@ -0,0 +1,252 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Tracking configuration for database
+ *
+ * @package PhpMyAdmin
+ */
+
+/**
+ * Run common work
+ */
+require_once 'libraries/common.inc.php';
+
+//Get some js files needed for Ajax requests
+$response = PMA_Response::getInstance();
+$header = $response->getHeader();
+$scripts = $header->getScripts();
+$scripts->addFile('db_structure.js');
+
+/**
+ * If we are not in an Ajax request, then do the common work and show the links etc.
+ */
+require 'libraries/db_common.inc.php';
+$url_query .= '&amp;goto=tbl_tracking.php&amp;back=db_tracking.php';
+
+// Get the database structure
+$sub_part = '_structure';
+require 'libraries/db_info.inc.php';
+
+// Work to do?
+// (here, do not use $_REQUEST['db] as it can be crafted)
+if (isset($_REQUEST['delete_tracking']) && isset($_REQUEST['table'])) {
+ PMA_Tracker::deleteTracking($GLOBALS['db'], $_REQUEST['table']);
+
+ /**
+ * If in an Ajax request, generate the success message and use
+ * {@link PMA_Response()} to send the output
+ */
+ if ($GLOBALS['is_ajax_request'] == true) {
+ $response = PMA_Response::getInstance();
+ $response->addJSON('message', PMA_Message::success());
+ exit;
+ }
+}
+
+// Get tracked data about the database
+$data = PMA_Tracker::getTrackedData($_REQUEST['db'], '', '1');
+
+// No tables present and no log exist
+if ($num_tables == 0 && count($data['ddlog']) == 0) {
+ echo '<p>' . __('No tables found in database.') . '</p>' . "\n";
+
+ if (empty($db_is_information_schema)) {
+ include 'libraries/display_create_table.lib.php';
+ }
+ exit;
+}
+
+// ---------------------------------------------------------------------------
+
+// Prepare statement to get HEAD version
+$all_tables_query = ' SELECT table_name, MAX(version) as version FROM ' .
+ PMA_Util::backquote($GLOBALS['cfg']['Server']['pmadb']) . '.' .
+ PMA_Util::backquote($GLOBALS['cfg']['Server']['tracking']) .
+ ' WHERE db_name = \'' . PMA_Util::sqlAddSlashes($_REQUEST['db']) . '\' ' .
+ ' GROUP BY table_name' .
+ ' ORDER BY table_name ASC';
+
+$all_tables_result = PMA_queryAsControlUser($all_tables_query);
+
+// If a HEAD version exists
+if ($GLOBALS['dbi']->numRows($all_tables_result) > 0) {
+ ?>
+ <div id="tracked_tables">
+ <h3><?php echo __('Tracked tables');?></h3>
+
+ <table id="versions" class="data">
+ <thead>
+ <tr>
+ <th><?php echo __('Database');?></th>
+ <th><?php echo __('Table');?></th>
+ <th><?php echo __('Last version');?></th>
+ <th><?php echo __('Created');?></th>
+ <th><?php echo __('Updated');?></th>
+ <th><?php echo __('Status');?></th>
+ <th><?php echo __('Action');?></th>
+ <th><?php echo __('Show');?></th>
+ </tr>
+ </thead>
+ <tbody>
+ <?php
+
+ // Print out information about versions
+
+ $drop_image_or_text = '';
+ if (PMA_Util::showIcons('ActionLinksMode')) {
+ $drop_image_or_text .= PMA_Util::getImage(
+ 'b_drop.png',
+ __('Delete tracking data for this table')
+ );
+ }
+ if (PMA_Util::showText('ActionLinksMode')) {
+ $drop_image_or_text .= __('Drop');
+ }
+
+ $style = 'odd';
+ while ($one_result = $GLOBALS['dbi']->fetchArray($all_tables_result)) {
+ list($table_name, $version_number) = $one_result;
+ $table_query = ' SELECT * FROM ' .
+ PMA_Util::backquote($GLOBALS['cfg']['Server']['pmadb']) . '.' .
+ PMA_Util::backquote($GLOBALS['cfg']['Server']['tracking']) .
+ ' WHERE `db_name` = \'' . PMA_Util::sqlAddSlashes($_REQUEST['db'])
+ . '\' AND `table_name` = \'' . PMA_Util::sqlAddSlashes($table_name)
+ . '\' AND `version` = \'' . $version_number . '\'';
+
+ $table_result = PMA_queryAsControlUser($table_query);
+ $version_data = $GLOBALS['dbi']->fetchArray($table_result);
+
+ if ($version_data['tracking_active'] == 1) {
+ $version_status = __('active');
+ } else {
+ $version_status = __('not active');
+ }
+ $tmp_link = 'tbl_tracking.php?' . $url_query . '&amp;table='
+ . htmlspecialchars($version_data['table_name']);
+ $delete_link = 'db_tracking.php?' . $url_query . '&amp;table='
+ . htmlspecialchars($version_data['table_name'])
+ . '&amp;delete_tracking=true&amp';
+ ?>
+ <tr class="noclick <?php echo $style;?>">
+ <td><?php echo htmlspecialchars($version_data['db_name']);?></td>
+ <td><?php echo htmlspecialchars($version_data['table_name']);?></td>
+ <td><?php echo $version_data['version'];?></td>
+ <td><?php echo $version_data['date_created'];?></td>
+ <td><?php echo $version_data['date_updated'];?></td>
+ <td><?php echo $version_status;?></td>
+ <td>
+ <a class="drop_tracking_anchor ajax" href="<?php echo $delete_link;?>" >
+ <?php echo $drop_image_or_text; ?></a>
+ <?php
+ echo '</td>'
+ . '<td>'
+ . '<a href="' . $tmp_link . '">' . __('Versions') . '</a>'
+ . '&nbsp;|&nbsp;'
+ . '<a href="' . $tmp_link . '&amp;report=true&amp;version='
+ . $version_data['version'] . '">' . __('Tracking report') . '</a>'
+ . '&nbsp;|&nbsp;'
+ . '<a href="' . $tmp_link . '&amp;snapshot=true&amp;version='
+ . $version_data['version'] . '">' . __('Structure snapshot')
+ . '</a>'
+ . '</td>'
+ . '</tr>';
+ if ($style == 'even') {
+ $style = 'odd';
+ } else {
+ $style = 'even';
+ }
+ }
+ unset($tmp_link);
+ ?>
+ </tbody>
+ </table>
+ </div>
+ <?php
+}
+
+$sep = $GLOBALS['cfg']['NavigationTreeTableSeparator'];
+
+// Get list of tables
+$table_list = PMA_Util::getTableList($GLOBALS['db']);
+
+// For each table try to get the tracking version
+foreach ($table_list as $key => $value) {
+ // If $value is a table group.
+ if (array_key_exists(('is' . $sep . 'group'), $value)
+ && $value['is' . $sep . 'group']
+ ) {
+ foreach ($value as $temp_table) {
+ // If $temp_table is a table with the value for 'Name' is set,
+ // rather than a propery of the table group.
+ if (is_array($temp_table)
+ && array_key_exists('Name', $temp_table)
+ ) {
+ $tracking_version = PMA_Tracker::getVersion(
+ $GLOBALS['db'],
+ $temp_table['Name']
+ );
+ if ($tracking_version == -1) {
+ $my_tables[] = $temp_table['Name'];
+ }
+ }
+ }
+ } else { // If $value is a table.
+ if (PMA_Tracker::getVersion($GLOBALS['db'], $value['Name']) == -1) {
+ $my_tables[] = $value['Name'];
+ }
+ }
+}
+
+// If untracked tables exist
+if (isset($my_tables)) {
+ ?>
+ <h3><?php echo __('Untracked tables');?></h3>
+
+ <table id="noversions" class="data">
+ <thead>
+ <tr>
+ <th style="width: 300px"><?php echo __('Table');?></th>
+ <th></th>
+ </tr>
+ </thead>
+ <tbody>
+ <?php
+ // Print out list of untracked tables
+
+ $style = 'odd';
+
+ foreach ($my_tables as $key => $tablename) {
+ if (PMA_Tracker::getVersion($GLOBALS['db'], $tablename) == -1) {
+ $my_link = '<a href="tbl_tracking.php?' . $url_query
+ . '&amp;table=' . htmlspecialchars($tablename) .'">';
+ $my_link .= PMA_Util::getIcon('eye.png', __('Track table'));
+ $my_link .= '</a>';
+ ?>
+ <tr class="noclick <?php echo $style;?>">
+ <td><?php echo htmlspecialchars($tablename);?></td>
+ <td><?php echo $my_link;?></td>
+ </tr>
+ <?php
+ if ($style == 'even') {
+ $style = 'odd';
+ } else {
+ $style = 'even';
+ }
+ }
+ }
+ ?>
+ </tbody>
+ </table>
+ <?php
+}
+// If available print out database log
+if (count($data['ddlog']) > 0) {
+ $log = '';
+ foreach ($data['ddlog'] as $entry) {
+ $log .= '# ' . $entry['date'] . ' ' . $entry['username'] . "\n"
+ . $entry['statement'] . "\n";
+ }
+ echo PMA_Util::getMessage(__('Database Log'), $log);
+}
+
+?>
diff --git a/db_triggers.php b/db_triggers.php
new file mode 100644
index 0000000000..d1a2a8c6b2
--- /dev/null
+++ b/db_triggers.php
@@ -0,0 +1,25 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Triggers management.
+ *
+ * @package PhpMyAdmin
+ */
+
+/**
+ * Include required files
+ */
+require_once 'libraries/common.inc.php';
+
+/**
+ * Include all other files
+ */
+require_once 'libraries/rte/rte_triggers.lib.php';
+
+/**
+ * Do the magic
+ */
+$_PMA_RTE = 'TRI';
+require_once 'libraries/rte/rte_main.inc.php';
+
+?>
diff --git a/error_report.php b/error_report.php
new file mode 100644
index 0000000000..3ed045bcc7
--- /dev/null
+++ b/error_report.php
@@ -0,0 +1,95 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Handle error report submission
+ *
+ * @package PhpMyAdmin
+ */
+require_once 'libraries/common.inc.php';
+require_once 'libraries/error_report.lib.php';
+
+$response = PMA_Response::getInstance();
+
+if (isset($_REQUEST['send_error_report'])
+ && $_REQUEST['send_error_report'] == true
+) {
+ $server_response = PMA_sendErrorReport(PMA_getReportData(false));
+
+ if ($server_response === false) {
+ $success = false;
+ } else {
+ $decoded_response = json_decode($server_response, true);
+ $success = !empty($decoded_response) ? $decoded_response["success"] : false;
+ }
+
+ if (isset($_REQUEST['automatic'])
+ && $_REQUEST['automatic'] === "true"
+ ) {
+ if ($success) {
+ $response->addJSON(
+ 'message',
+ PMA_Message::error(
+ __(
+ 'An error has been detected and an error report has been '
+ .'automatically submitted based on your settings.'
+ )
+ . '<br />'
+ . __('You may want to refresh the page.')
+ )
+ );
+ } else {
+ $response->addJSON(
+ 'message',
+ PMA_Message::error(
+ __(
+ 'An error has been detected and an error report has been '
+ . 'generated but failed to be sent.'
+ )
+ . ' '
+ . __(
+ 'If you experience any '
+ . 'problems please submit a bug report manually.'
+ )
+ . '<br />'
+ . __('You may want to refresh the page.')
+ )
+ );
+ }
+ } else {
+ if ($success) {
+ $response->addJSON(
+ 'message',
+ PMA_Message::success(
+ __('Thank you for submitting this report.')
+ . '<br />'
+ . __('You may want to refresh the page.')
+ )
+ );
+ } else {
+ $response->addJSON(
+ 'message',
+ PMA_Message::error(
+ __('Thank you for submitting this report.')
+ . ' '
+ . __('Unfortunately the submission failed.')
+ . ' '
+ . __(
+ 'If you experience any '
+ . 'problems please submit a bug report manually.'
+ )
+ . '<br />'
+ . __('You may want to refresh the page.')
+ )
+ );
+ }
+ if (isset($_REQUEST['always_send'])
+ && $_REQUEST['always_send'] === "true"
+ ) {
+ PMA_persistOption("SendErrorReports", "always", "ask");
+ }
+ }
+} elseif (! empty($_REQUEST['get_settings'])) {
+ $response->addJSON('report_setting', $GLOBALS['cfg']['SendErrorReports']);
+} else {
+ $response->addHTML(PMA_getErrorReportForm());
+}
diff --git a/examples/config.manyhosts.inc.php b/examples/config.manyhosts.inc.php
new file mode 100644
index 0000000000..3ab6572bcc
--- /dev/null
+++ b/examples/config.manyhosts.inc.php
@@ -0,0 +1,51 @@
+<?php
+/**
+ * This example configuration shows how to configure phpMyAdmin for
+ * many hosts that all have identical configuration otherwise. To add
+ * a new host, just drop it into $hosts below. Contributed by
+ * Matthew Hawkins.
+ *
+ * @package PhpMyAdmin
+ * @subpackage Example
+ */
+
+$i=0;
+$hosts = array (
+ "foo.example.com",
+ "bar.example.com",
+ "baz.example.com",
+ "quux.example.com",
+);
+
+foreach ($hosts as $host) {
+ $i++;
+ $cfg['Servers'][$i]['host'] = $host;
+ $cfg['Servers'][$i]['port'] = '';
+ $cfg['Servers'][$i]['socket'] = '';
+ $cfg['Servers'][$i]['connect_type'] = 'tcp';
+ $cfg['Servers'][$i]['extension'] = 'mysql';
+ $cfg['Servers'][$i]['compress'] = false;
+ $cfg['Servers'][$i]['controluser'] = 'pma';
+ $cfg['Servers'][$i]['controlpass'] = 'pmapass';
+ $cfg['Servers'][$i]['auth_type'] = 'cookie';
+ $cfg['Servers'][$i]['user'] = '';
+ $cfg['Servers'][$i]['password'] = '';
+ $cfg['Servers'][$i]['only_db'] = '';
+ $cfg['Servers'][$i]['verbose'] = '';
+ $cfg['Servers'][$i]['pmadb'] = 'phpmyadmin';
+ $cfg['Servers'][$i]['bookmarktable'] = 'pma__bookmark';
+ $cfg['Servers'][$i]['relation'] = 'pma__relation';
+ $cfg['Servers'][$i]['table_info'] = 'pma__table_info';
+ $cfg['Servers'][$i]['table_coords'] = 'pma__table_coords';
+ $cfg['Servers'][$i]['pdf_pages'] = 'pma__pdf_pages';
+ $cfg['Servers'][$i]['column_info'] = 'pma__column_info';
+ $cfg['Servers'][$i]['history'] = 'pma__history';
+ $cfg['Servers'][$i]['table_uiprefs'] = 'pma__table_uiprefs';
+ $cfg['Servers'][$i]['tracking'] = 'pma__tracking';
+ $cfg['Servers'][$i]['designer_coords'] = 'pma__designer_coords';
+ $cfg['Servers'][$i]['userconfig'] = 'pma__userconfig';
+ $cfg['Servers'][$i]['recent'] = 'pma__recent';
+ $cfg['Servers'][$i]['users'] = 'pma__users';
+ $cfg['Servers'][$i]['usergroups'] = 'pma__usergroups';
+ $cfg['Servers'][$i]['navigationhiding'] = 'pma__navigationhiding';
+}
diff --git a/examples/openid.php b/examples/openid.php
new file mode 100644
index 0000000000..a99603de4b
--- /dev/null
+++ b/examples/openid.php
@@ -0,0 +1,160 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Single signon for phpMyAdmin using OpenID
+ *
+ * This is just example how to use single signon with phpMyAdmin, it is
+ * not intended to be perfect code and look, only shows how you can
+ * integrate this functionality in your application.
+ *
+ * It uses OpenID pear package, see http://pear.php.net/package/OpenID
+ *
+ * User first authenticates using OpenID and based on content of $AUTH_MAP
+ * the login information is passed to phpMyAdmin in session data.
+ *
+ * @package PhpMyAdmin
+ * @subpackage Example
+ */
+
+if (false === @include_once 'OpenID/RelyingParty.php') {
+ exit;
+}
+
+/**
+ * Map of authenticated users to MySQL user/password pairs.
+ */
+$AUTH_MAP = array(
+ 'http://launchpad.net/~username' => array(
+ 'user' => 'root',
+ 'password' => '',
+ ),
+ );
+
+/**
+ * Simple function to show HTML page with given content.
+ *
+ * @param string $contents Content to include in page
+ *
+ * @return void
+ */
+function Show_page($contents)
+{
+ header('Content-Type: text/html; charset=utf-8');
+ echo '<?xml version="1.0" encoding="utf-8"?>' . "\n";
+ ?>
+ <!DOCTYPE HTML>
+ <html lang="en" dir="ltr">
+ <head>
+ <link rel="icon" href="../favicon.ico" type="image/x-icon" />
+ <link rel="shortcut icon" href="../favicon.ico" type="image/x-icon" />
+ <meta charset="utf-8" />
+ <title>phpMyAdmin OpenID signon example</title>
+ </head>
+ <body>
+ <?php
+ if (isset($_SESSION) && isset($_SESSION['PMA_single_signon_error_message'])) {
+ echo '<p class="error">' . $_SESSION['PMA_single_signon_message'] . '</p>';
+ unset($_SESSION['PMA_single_signon_message']);
+ }
+ echo $contents;
+ ?>
+ </body>
+ </html>
+ <?php
+}
+
+/* Need to have cookie visible from parent directory */
+session_set_cookie_params(0, '/', '', 0);
+/* Create signon session */
+$session_name = 'SignonSession';
+session_name($session_name);
+session_start();
+
+// Determine realm and return_to
+$base = 'http';
+if (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') {
+ $base .= 's';
+}
+$base .= '://' . $_SERVER['SERVER_NAME'] . ':' . $_SERVER['SERVER_PORT'];
+
+$realm = $base . '/';
+$returnTo = $base . dirname($_SERVER['PHP_SELF']);
+if ($returnTo[strlen($returnTo) - 1] != '/') {
+ $returnTo .= '/';
+}
+$returnTo .= 'openid.php';
+
+/* Display form */
+if (!count($_GET) && !count($_POST) || isset($_GET['phpMyAdmin'])) {
+ /* Show simple form */
+ $content = '<form action="openid.php" method="post">
+OpenID: <input type="text" name="identifier" /><br />
+<input type="submit" name="start" />
+</form>
+</body>
+</html>';
+ Show_page($content);
+ exit;
+}
+
+/* Grab identifier */
+if (isset($_POST['identifier'])) {
+ $identifier = $_POST['identifier'];
+} else if (isset($_SESSION['identifier'])) {
+ $identifier = $_SESSION['identifier'];
+} else {
+ $identifier = null;
+}
+
+/* Create OpenID object */
+try {
+ $o = new OpenID_RelyingParty($returnTo, $realm, $identifier);
+} catch (OpenID_Exception $e) {
+ $contents = "<div class='relyingparty_results'>\n";
+ $contents .= "<pre>" . $e->getMessage() . "</pre>\n";
+ $contents .= "</div class='relyingparty_results'>";
+ Show_page($contents);
+ exit;
+}
+
+/* Redirect to OpenID provider */
+if (isset($_POST['start'])) {
+ try {
+ $authRequest = $o->prepare();
+ } catch (OpenID_Exception $e) {
+ $contents = "<div class='relyingparty_results'>\n";
+ $contents .= "<pre>" . $e->getMessage() . "</pre>\n";
+ $contents .= "</div class='relyingparty_results'>";
+ Show_page($contents);
+ exit;
+ }
+
+ $url = $authRequest->getAuthorizeURL();
+
+ header("Location: $url");
+ exit;
+} else {
+ /* Grab query string */
+ if (!count($_POST)) {
+ list(, $queryString) = explode('?', $_SERVER['REQUEST_URI']);
+ } else {
+ // I hate php sometimes
+ $queryString = file_get_contents('php://input');
+ }
+
+ /* Check reply */
+ $message = new OpenID_Message($queryString, OpenID_Message::FORMAT_HTTP);
+
+ $id = $message->get('openid.claimed_id');
+
+ if (!empty($id) && isset($AUTH_MAP[$id])) {
+ $_SESSION['PMA_single_signon_user'] = $AUTH_MAP[$id]['user'];
+ $_SESSION['PMA_single_signon_password'] = $AUTH_MAP[$id]['password'];
+ session_write_close();
+ /* Redirect to phpMyAdmin (should use absolute URL here!) */
+ header('Location: ../index.php');
+ } else {
+ Show_page('<p>User not allowed!</p>');
+ exit;
+ }
+}
diff --git a/examples/signon-script.php b/examples/signon-script.php
new file mode 100644
index 0000000000..e333f2ee1b
--- /dev/null
+++ b/examples/signon-script.php
@@ -0,0 +1,29 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Single signon for phpMyAdmin
+ *
+ * This is just example how to use script based single signon with
+ * phpMyAdmin, it is not intended to be perfect code and look, only
+ * shows how you can integrate this functionality in your application.
+ *
+ * @package PhpMyAdmin
+ * @subpackage Example
+ */
+
+
+/**
+ * This function returns username and password.
+ *
+ * It can optionally use configured username as parameter.
+ *
+ * @param string $user User name
+ *
+ * @return array
+ */
+function get_login_credentials($user)
+{
+ return array('root', '');
+}
+
+?>
diff --git a/examples/signon.php b/examples/signon.php
new file mode 100644
index 0000000000..57e989d315
--- /dev/null
+++ b/examples/signon.php
@@ -0,0 +1,69 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Single signon for phpMyAdmin
+ *
+ * This is just example how to use session based single signon with
+ * phpMyAdmin, it is not intended to be perfect code and look, only
+ * shows how you can integrate this functionality in your application.
+ *
+ * @package PhpMyAdmin
+ * @subpackage Example
+ */
+
+/* Need to have cookie visible from parent directory */
+session_set_cookie_params(0, '/', '', 0);
+/* Create signon session */
+$session_name = 'SignonSession';
+session_name($session_name);
+session_start();
+
+/* Was data posted? */
+if (isset($_POST['user'])) {
+ /* Store there credentials */
+ $_SESSION['PMA_single_signon_user'] = $_POST['user'];
+ $_SESSION['PMA_single_signon_password'] = $_POST['password'];
+ $_SESSION['PMA_single_signon_host'] = $_POST['host'];
+ $_SESSION['PMA_single_signon_port'] = $_POST['port'];
+ /* Update another field of server configuration */
+ $_SESSION['PMA_single_signon_cfgupdate'] = array('verbose' => 'Signon test');
+ $id = session_id();
+ /* Close that session */
+ session_write_close();
+ /* Redirect to phpMyAdmin (should use absolute URL here!) */
+ header('Location: ../index.php');
+} else {
+ /* Show simple form */
+ header('Content-Type: text/html; charset=utf-8');
+ echo '<?xml version="1.0" encoding="utf-8"?>' . "\n";
+ ?>
+ <!DOCTYPE HTML>
+ <html lang="en" dir="ltr">
+ <head>
+ <link rel="icon" href="../favicon.ico" type="image/x-icon" />
+ <link rel="shortcut icon" href="../favicon.ico" type="image/x-icon" />
+ <meta charset="utf-8" />
+ <title>phpMyAdmin single signon example</title>
+ </head>
+ <body>
+ <?php
+ if (isset($_SESSION['PMA_single_signon_error_message'])) {
+ echo '<p class="error">';
+ echo $_SESSION['PMA_single_signon_error_message'];
+ echo '</p>';
+ }
+ ?>
+ <form action="signon.php" method="post">
+ Username: <input type="text" name="user" /><br />
+ Password: <input type="password" name="password" /><br />
+ Host: (will use the one from config.inc.php by default)
+ <input type="text" name="host" /><br />
+ Port: (will use the one from config.inc.php by default)
+ <input type="text" name="port" /><br />
+ <input type="submit" />
+ </form>
+ </body>
+ </html>
+ <?php
+}
+?>
diff --git a/examples/swekey.sample.conf b/examples/swekey.sample.conf
new file mode 100644
index 0000000000..ebf1aedf02
--- /dev/null
+++ b/examples/swekey.sample.conf
@@ -0,0 +1,44 @@
+# This is a typical file used to enable Swekey hardware authentication.
+#
+# To activate the Swekey authentication add the following line to your config.inc.php file.
+# $cfg['Servers'][$i]['auth_swekey_config'] = '/etc/swekey-pma.conf';
+# Then rename this file "swekey-pma.conf" and copy it to the /etc directory.
+# Add all the Swekey ids you want to grant access to in the file.
+# After each Swekey id put the corresponding user name.
+#
+# If you don't know the id of a Swekey just visit http://www.swekey.com?sel=support
+# while your Swekey is connected.
+#
+# If you need to purchase a Swekey please visit http://phpmyadmin.net/auth_key
+# since this link provides funding to PhpMyAdmin.
+#
+
+0000000000000000000000000000763A:root
+000000000000000000000000000089E4:steve
+0000000000000000000000000000231E:scott
+
+#
+# It is recommended to include the following lines to contact the
+# authentication servers in SSL mode.
+#
+
+SERVER_CHECK=https://auth-check-ssl.musbe.net
+SERVER_RNDTOKEN=https://auth-rnd-gen-ssl.musbe.net
+SERVER_STATUS=https://auth-status-ssl.musbe.net
+
+#
+# The path of the root certificate file used to ensure a secure
+# communication with the authentication servers in SSL mode.
+# If not specified, will use musbe-ca.crt found in your
+# phpMyAdmin/libraries/auth/swekey.
+#
+
+#CA_FILE=/var/http-root/phpmyadmin/libraries/auth/swekey/musbe-ca.crt
+
+#
+# If your server receives many login requests, you can enable the random
+# token caching to accelerate the authentication process.
+# Token caching is enabled by default.
+#
+
+#ENABLE_TOKEN_CACHE=0
diff --git a/export.php b/export.php
new file mode 100644
index 0000000000..cac8b3c8b0
--- /dev/null
+++ b/export.php
@@ -0,0 +1,1032 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Main export handling code
+ *
+ * @package PhpMyAdmin
+ */
+
+/**
+ * Get the variables sent or posted to this script and a core script
+ */
+if (!defined('TESTSUITE')) {
+ /**
+ * If we are sending the export file (as opposed to just displaying it
+ * as text), we have to bypass the usual PMA_Response mechanism
+ */
+ if (isset($_POST['output_format']) && $_POST['output_format'] == 'sendit') {
+ define('PMA_BYPASS_GET_INSTANCE', 1);
+ }
+ include_once 'libraries/common.inc.php';
+ include_once 'libraries/zip.lib.php';
+ include_once 'libraries/plugin_interface.lib.php';
+
+ //check if it's the GET request to check export time out
+ if (isset($_GET['check_time_out'])) {
+ if (isset($_SESSION['pma_export_error'])) {
+ $err = $_SESSION['pma_export_error'];
+ unset($_SESSION['pma_export_error']);
+ echo $err;
+ } else {
+ echo "success";
+ }
+ exit;
+ }
+ /**
+ * Sets globals from $_POST
+ *
+ * - Please keep the parameters in order of their appearance in the form
+ * - Some of these parameters are not used, as the code below directly
+ * verifies from the superglobal $_POST or $_REQUEST
+ */
+ $post_params = array(
+ 'db',
+ 'table',
+ 'single_table',
+ 'export_type',
+ 'export_method',
+ 'quick_or_custom',
+ 'db_select',
+ 'table_select',
+ 'limit_to',
+ 'limit_from',
+ 'allrows',
+ 'output_format',
+ 'filename_template',
+ 'remember_template',
+ 'charset_of_file',
+ 'compression',
+ 'what',
+ 'knjenc',
+ 'xkana',
+ 'htmlword_structure_or_data',
+ 'htmlword_null',
+ 'htmlword_columns',
+ 'mediawiki_structure_or_data',
+ 'mediawiki_caption',
+ 'pdf_report_title',
+ 'pdf_structure_or_data',
+ 'odt_structure_or_data',
+ 'odt_relation',
+ 'odt_comments',
+ 'odt_mime',
+ 'odt_columns',
+ 'odt_null',
+ 'codegen_structure_or_data',
+ 'codegen_format',
+ 'excel_null',
+ 'excel_removeCRLF',
+ 'excel_columns',
+ 'excel_edition',
+ 'excel_structure_or_data',
+ 'yaml_structure_or_data',
+ 'ods_null',
+ 'ods_structure_or_data',
+ 'ods_columns',
+ 'json_structure_or_data',
+ 'xml_structure_or_data',
+ 'xml_export_functions',
+ 'xml_export_procedures',
+ 'xml_export_tables',
+ 'xml_export_triggers',
+ 'xml_export_views',
+ 'xml_export_contents',
+ 'texytext_structure_or_data',
+ 'texytext_columns',
+ 'texytext_null',
+ 'phparray_structure_or_data',
+ 'sql_include_comments',
+ 'sql_header_comment',
+ 'sql_dates',
+ 'sql_relation',
+ 'sql_mime',
+ 'sql_use_transaction',
+ 'sql_disable_fk',
+ 'sql_compatibility',
+ 'sql_structure_or_data',
+ 'sql_create_database',
+ 'sql_drop_table',
+ 'sql_procedure_function',
+ 'sql_create_table_statements',
+ 'sql_if_not_exists',
+ 'sql_auto_increment',
+ 'sql_backquotes',
+ 'sql_truncate',
+ 'sql_delayed',
+ 'sql_ignore',
+ 'sql_type',
+ 'sql_insert_syntax',
+ 'sql_max_query_size',
+ 'sql_hex_for_blob',
+ 'sql_utc_time',
+ 'sql_drop_database',
+ 'csv_separator',
+ 'csv_enclosed',
+ 'csv_escaped',
+ 'csv_terminated',
+ 'csv_null',
+ 'csv_removeCRLF',
+ 'csv_columns',
+ 'csv_structure_or_data',
+ // csv_replace should have been here but we use it directly from $_POST
+ 'latex_caption',
+ 'latex_structure_or_data',
+ 'latex_structure_caption',
+ 'latex_structure_continued_caption',
+ 'latex_structure_label',
+ 'latex_relation',
+ 'latex_comments',
+ 'latex_mime',
+ 'latex_columns',
+ 'latex_data_caption',
+ 'latex_data_continued_caption',
+ 'latex_data_label',
+ 'latex_null'
+ );
+
+ foreach ($post_params as $one_post_param) {
+ if (isset($_POST[$one_post_param])) {
+ $GLOBALS[$one_post_param] = $_POST[$one_post_param];
+ }
+ }
+
+ // sanitize this parameter which will be used below in a file inclusion
+ $what = PMA_securePath($what);
+
+ PMA_Util::checkParameters(array('what', 'export_type'));
+
+ // export class instance, not array of properties, as before
+ $export_plugin = PMA_getPlugin(
+ "export",
+ $what,
+ 'libraries/plugins/export/',
+ array(
+ 'export_type' => $export_type,
+ 'single_table' => isset($single_table)
+ )
+ );
+
+ // Backward compatbility
+ $type = $what;
+
+ // Check export type
+ if (! isset($export_plugin)) {
+ PMA_fatalError(__('Bad type!'));
+ }
+
+ /**
+ * valid compression methods
+ */
+ $compression_methods = array(
+ 'zip',
+ 'gzip'
+ );
+
+ /**
+ * init and variable checking
+ */
+ $compression = false;
+ $onserver = false;
+ $save_on_server = false;
+ $buffer_needed = false;
+
+ // Is it a quick or custom export?
+ if ($_REQUEST['quick_or_custom'] == 'quick') {
+ $quick_export = true;
+ } else {
+ $quick_export = false;
+ }
+
+ if ($_REQUEST['output_format'] == 'astext') {
+ $asfile = false;
+ } else {
+ $asfile = true;
+ if (in_array($_REQUEST['compression'], $compression_methods)) {
+ $compression = $_REQUEST['compression'];
+ $buffer_needed = true;
+ }
+ if (($quick_export && ! empty($_REQUEST['quick_export_onserver']))
+ || (! $quick_export && ! empty($_REQUEST['onserver']))
+ ) {
+ if ($quick_export) {
+ $onserver = $_REQUEST['quick_export_onserver'];
+ } else {
+ $onserver = $_REQUEST['onserver'];
+ }
+ // Will we save dump on server?
+ $save_on_server = ! empty($cfg['SaveDir']) && $onserver;
+ }
+ }
+
+ // Does export require to be into file?
+ if ($export_plugin->getProperties()->getForceFile() != null && ! $asfile) {
+ $message = PMA_Message::error(
+ __('Selected export type has to be saved in file!')
+ );
+ if ($export_type == 'server') {
+ $active_page = 'server_export.php';
+ include 'server_export.php';
+ } elseif ($export_type == 'database') {
+ $active_page = 'db_export.php';
+ include 'db_export.php';
+ } else {
+ $active_page = 'tbl_export.php';
+ include 'tbl_export.php';
+ }
+ exit();
+ }
+
+ // Generate error url and check for needed variables
+ if ($export_type == 'server') {
+ $err_url = 'server_export.php?' . PMA_URL_getCommon();
+ } elseif ($export_type == 'database' && strlen($db)) {
+ $err_url = 'db_export.php?' . PMA_URL_getCommon($db);
+ // Check if we have something to export
+ if (isset($table_select)) {
+ $tables = $table_select;
+ } else {
+ $tables = array();
+ }
+ } elseif ($export_type == 'table' && strlen($db) && strlen($table)) {
+ $err_url = 'tbl_export.php?' . PMA_URL_getCommon($db, $table);
+ } else {
+ PMA_fatalError(__('Bad parameters!'));
+ }
+
+ /**
+ * Increase time limit for script execution and initializes some variables
+ */
+ @set_time_limit($cfg['ExecTimeLimit']);
+ if (! empty($cfg['MemoryLimit'])) {
+ @ini_set('memory_limit', $cfg['MemoryLimit']);
+ }
+ register_shutdown_function('PMA_shutdown');
+ // Start with empty buffer
+ $dump_buffer = '';
+ $dump_buffer_len = 0;
+
+ // We send fake headers to avoid browser timeout when buffering
+ $time_start = time();
+}
+
+/**
+ * Sets a session variable upon a possible fatal error during export
+ *
+ * @return void
+ */
+function PMA_shutdown()
+{
+ $a = error_get_last();
+ if ($a != null && strpos($a['message'], "execution time")) {
+ //write in partially downloaded file for future reference of user
+ print_r($a);
+ //set session variable to check if there was error while exporting
+ $_SESSION['pma_export_error'] = $a['message'];
+ }
+}
+/**
+ * Detect ob_gzhandler
+ *
+ * @return bool
+ */
+function PMA_isGzHandlerEnabled()
+{
+ return in_array('ob_gzhandler', ob_list_handlers());
+}
+
+/**
+ * Detect whether gzencode is needed; it might not be needed if
+ * the server is already compressing by itself
+ *
+ * @return bool Whether gzencode is needed
+ */
+function PMA_gzencodeNeeded()
+{
+ /*
+ * We should gzencode only if the function exists
+ * but we don't want to compress twice, therefore
+ * gzencode only if transparent compression is not enabled
+ * and gz compression was not asked via $cfg['OBGzip']
+ * but transparent compression does not apply when saving to server
+ */
+ if (@function_exists('gzencode')
+ && ((! @ini_get('zlib.output_compression')
+ && ! PMA_isGzHandlerEnabled())
+ || $GLOBALS['save_on_server'])
+ ) {
+ return true;
+ } else {
+ return false;
+ }
+}
+
+/**
+ * Output handler for all exports, if needed buffering, it stores data into
+ * $dump_buffer, otherwise it prints thems out.
+ *
+ * @param string $line the insert statement
+ *
+ * @return bool Whether output succeeded
+ */
+function PMA_exportOutputHandler($line)
+{
+ global $time_start, $dump_buffer, $dump_buffer_len, $save_filename;
+
+ // Kanji encoding convert feature
+ if ($GLOBALS['output_kanji_conversion']) {
+ $line = PMA_Kanji_strConv(
+ $line,
+ $GLOBALS['knjenc'],
+ isset($GLOBALS['xkana']) ? $GLOBALS['xkana'] : ''
+ );
+ }
+ // If we have to buffer data, we will perform everything at once at the end
+ if ($GLOBALS['buffer_needed']) {
+
+ $dump_buffer .= $line;
+ if ($GLOBALS['onfly_compression']) {
+
+ $dump_buffer_len += strlen($line);
+
+ if ($dump_buffer_len > $GLOBALS['memory_limit']) {
+ if ($GLOBALS['output_charset_conversion']) {
+ $dump_buffer = PMA_convertString(
+ 'utf-8',
+ $GLOBALS['charset_of_file'],
+ $dump_buffer
+ );
+ }
+ if ($GLOBALS['compression'] == 'gzip'
+ && PMA_gzencodeNeeded()
+ ) {
+ // as a gzipped file
+ // without the optional parameter level because it bugs
+ $dump_buffer = gzencode($dump_buffer);
+ }
+ if ($GLOBALS['save_on_server']) {
+ $write_result = @fwrite($GLOBALS['file_handle'], $dump_buffer);
+ if ($write_result != strlen($dump_buffer)) {
+ $GLOBALS['message'] = PMA_Message::error(
+ __('Insufficient space to save the file %s.')
+ );
+ $GLOBALS['message']->addParam($save_filename);
+ return false;
+ }
+ } else {
+ echo $dump_buffer;
+ }
+ $dump_buffer = '';
+ $dump_buffer_len = 0;
+ }
+ } else {
+ $time_now = time();
+ if ($time_start >= $time_now + 30) {
+ $time_start = $time_now;
+ header('X-pmaPing: Pong');
+ } // end if
+ }
+ } else {
+ if ($GLOBALS['asfile']) {
+ if ($GLOBALS['output_charset_conversion']) {
+ $line = PMA_convertString(
+ 'utf-8',
+ $GLOBALS['charset_of_file'],
+ $line
+ );
+ }
+ if ($GLOBALS['save_on_server'] && strlen($line) > 0) {
+ $write_result = @fwrite($GLOBALS['file_handle'], $line);
+ if (! $write_result || ($write_result != strlen($line))) {
+ $GLOBALS['message'] = PMA_Message::error(
+ __('Insufficient space to save the file %s.')
+ );
+ $GLOBALS['message']->addParam($save_filename);
+ return false;
+ }
+ $time_now = time();
+ if ($time_start >= $time_now + 30) {
+ $time_start = $time_now;
+ header('X-pmaPing: Pong');
+ } // end if
+ } else {
+ // We export as file - output normally
+ echo $line;
+ }
+ } else {
+ // We export as html - replace special chars
+ echo htmlspecialchars($line);
+ }
+ }
+ return true;
+} // end of the 'PMA_exportOutputHandler()' function
+
+// Defines the default <CR><LF> format.
+// For SQL always use \n as MySQL wants this on all platforms.
+if (!defined('TESTSUITE')) {
+ if ($what == 'sql') {
+ $crlf = "\n";
+ } else {
+ $crlf = PMA_Util::whichCrlf();
+ }
+
+ $output_kanji_conversion = function_exists('PMA_Kanji_strConv')
+ && $type != 'xls';
+
+ // Do we need to convert charset?
+ $output_charset_conversion = $asfile
+ && $GLOBALS['PMA_recoding_engine'] != PMA_CHARSET_NONE
+ && isset($charset_of_file) && $charset_of_file != 'utf-8'
+ && $type != 'xls';
+
+ // Use on the fly compression?
+ $onfly_compression = $GLOBALS['cfg']['CompressOnFly']
+ && $compression == 'gzip';
+ if ($onfly_compression) {
+ $memory_limit = trim(@ini_get('memory_limit'));
+ $memory_limit_num = (int)substr($memory_limit, 0, -1);
+ // 2 MB as default
+ if (empty($memory_limit) || '-1' == $memory_limit) {
+ $memory_limit = 2 * 1024 * 1024;
+ } elseif (strtolower(substr($memory_limit, -1)) == 'm') {
+ $memory_limit = $memory_limit_num * 1024 * 1024;
+ } elseif (strtolower(substr($memory_limit, -1)) == 'k') {
+ $memory_limit = $memory_limit_num * 1024;
+ } elseif (strtolower(substr($memory_limit, -1)) == 'g') {
+ $memory_limit = $memory_limit_num * 1024 * 1024 * 1024;
+ } else {
+ $memory_limit = (int)$memory_limit;
+ }
+
+ // Some of memory is needed for other things and as threshold.
+ // During export I had allocated (see memory_get_usage function)
+ // approx 1.2MB so this comes from that.
+ if ($memory_limit > 1500000) {
+ $memory_limit -= 1500000;
+ }
+
+ // Some memory is needed for compression, assume 1/3
+ $memory_limit /= 8;
+ }
+
+ // Generate filename and mime type if needed
+ if ($asfile) {
+ $pma_uri_parts = parse_url($cfg['PmaAbsoluteUri']);
+ if ($export_type == 'server') {
+ if (isset($remember_template)) {
+ $GLOBALS['PMA_Config']->setUserValue(
+ 'pma_server_filename_template',
+ 'Export/file_template_server',
+ $filename_template
+ );
+ }
+ } elseif ($export_type == 'database') {
+ if (isset($remember_template)) {
+ $GLOBALS['PMA_Config']->setUserValue(
+ 'pma_db_filename_template',
+ 'Export/file_template_database',
+ $filename_template
+ );
+ }
+ } else {
+ if (isset($remember_template)) {
+ $GLOBALS['PMA_Config']->setUserValue(
+ 'pma_table_filename_template',
+ 'Export/file_template_table',
+ $filename_template
+ );
+ }
+ }
+ $filename = PMA_Util::expandUserString($filename_template);
+ // remove dots in filename (coming from either the template or already
+ // part of the filename) to avoid a remote code execution vulnerability
+ $filename = PMA_sanitizeFilename($filename, $replaceDots = true);
+
+ // Grab basic dump extension and mime type
+ // Check if the user already added extension;
+ // get the substring where the extension would be if it was included
+ $extension_start_pos = strlen($filename) - strlen(
+ $export_plugin->getProperties()->getExtension()
+ ) - 1;
+ $user_extension = substr($filename, $extension_start_pos, strlen($filename));
+ $required_extension = "." . $export_plugin->getProperties()->getExtension();
+ if (strtolower($user_extension) != $required_extension) {
+ $filename .= $required_extension;
+ }
+ $mime_type = $export_plugin->getProperties()->getMimeType();
+
+ // If dump is going to be compressed, set correct mime_type and add
+ // compression to extension
+ if ($compression == 'gzip') {
+ $filename .= '.gz';
+ $mime_type = 'application/x-gzip';
+ } elseif ($compression == 'zip') {
+ $filename .= '.zip';
+ $mime_type = 'application/zip';
+ }
+ }
+
+ // Open file on server if needed
+ if ($save_on_server) {
+ $save_filename = PMA_Util::userDir($cfg['SaveDir'])
+ . preg_replace('@[/\\\\]@', '_', $filename);
+ unset($message);
+ if (file_exists($save_filename)
+ && ((! $quick_export && empty($_REQUEST['onserverover']))
+ || ($quick_export
+ && $_REQUEST['quick_export_onserverover'] != 'saveitover'))
+ ) {
+ $message = PMA_Message::error(
+ __(
+ 'File %s already exists on server, '
+ . 'change filename or check overwrite option.'
+ )
+ );
+ $message->addParam($save_filename);
+ } else {
+ if (is_file($save_filename) && ! is_writable($save_filename)) {
+ $message = PMA_Message::error(
+ __(
+ 'The web server does not have permission '
+ . 'to save the file %s.'
+ )
+ );
+ $message->addParam($save_filename);
+ } else {
+ if (! $file_handle = @fopen($save_filename, 'w')) {
+ $message = PMA_Message::error(
+ __(
+ 'The web server does not have permission '
+ . 'to save the file %s.'
+ )
+ );
+ $message->addParam($save_filename);
+ }
+ }
+ }
+ if (isset($message)) {
+ if ($export_type == 'server') {
+ $active_page = 'server_export.php';
+ include 'server_export.php';
+ } elseif ($export_type == 'database') {
+ $active_page = 'db_export.php';
+ include 'db_export.php';
+ } else {
+ $active_page = 'tbl_export.php';
+ include 'tbl_export.php';
+ }
+ exit();
+ }
+ }
+
+ /**
+ * Send headers depending on whether the user chose to download a dump file
+ * or not
+ */
+ if (! $save_on_server) {
+ if ($asfile) {
+ // Download
+ // (avoid rewriting data containing HTML with anchors and forms;
+ // this was reported to happen under Plesk)
+ @ini_set('url_rewriter.tags', '');
+ $filename = PMA_sanitizeFilename($filename);
+
+ PMA_downloadHeader($filename, $mime_type);
+ } else {
+ // HTML
+ if ($export_type == 'database') {
+ $num_tables = count($tables);
+ if ($num_tables == 0) {
+ $message = PMA_Message::error(
+ __('No tables found in database.')
+ );
+ $active_page = 'db_export.php';
+ include 'db_export.php';
+ exit();
+ }
+ }
+ $backup_cfgServer = $cfg['Server'];
+ $cfg['Server'] = $backup_cfgServer;
+ unset($backup_cfgServer);
+ echo "\n" . '<div style="text-align: ' . $cell_align_left . '">' . "\n";
+ //echo ' <pre>' . "\n";
+
+ /**
+ * Displays a back button with all the $_REQUEST data in the URL
+ * (store in a variable to also display after the textarea)
+ */
+ $back_button = '<p>[ <a href="';
+ if ($export_type == 'server') {
+ $back_button .= 'server_export.php?' . PMA_URL_getCommon();
+ } elseif ($export_type == 'database') {
+ $back_button .= 'db_export.php?' . PMA_URL_getCommon($db);
+ } else {
+ $back_button .= 'tbl_export.php?' . PMA_URL_getCommon($db, $table);
+ }
+
+ // Convert the multiple select elements from an array to a string
+ if ($export_type == 'server' && isset($_REQUEST['db_select'])) {
+ $_REQUEST['db_select'] = implode(",", $_REQUEST['db_select']);
+ } elseif ($export_type == 'database'
+ && isset($_REQUEST['table_select'])
+ ) {
+ $_REQUEST['table_select'] = implode(",", $_REQUEST['table_select']);
+ }
+
+ foreach ($_REQUEST as $name => $value) {
+ $back_button .= '&amp;' . urlencode($name) . '=' . urlencode($value);
+ }
+ $back_button .= '&amp;repopulate=1">Back</a> ]</p>';
+
+ echo $back_button;
+ echo '<form name="nofunction">' . "\n";
+ echo '<textarea name="sqldump" cols="50" rows="30" '
+ . 'id="textSQLDUMP" wrap="OFF">' . "\n";
+ } // end download
+ }
+
+ // Fake loop just to allow skip of remain of this code by break, I'd really
+ // need exceptions here :-)
+ do {
+
+ // Add possibly some comments to export
+ if (! $export_plugin->exportHeader($db)) {
+ break;
+ }
+
+ // Will we need relation & co. setup?
+ $do_relation = isset($GLOBALS[$what . '_relation']);
+ $do_comments = isset($GLOBALS[$what . '_include_comments'])
+ || isset($GLOBALS[$what . '_comments']) ;
+ $do_mime = isset($GLOBALS[$what . '_mime']);
+ if ($do_relation || $do_comments || $do_mime) {
+ $cfgRelation = PMA_getRelationsParam();
+ }
+ if ($do_mime) {
+ include_once 'libraries/transformations.lib.php';
+ }
+
+ // Include dates in export?
+ $do_dates = isset($GLOBALS[$what . '_dates']);
+
+ $whatStrucOrData = $GLOBALS[$what . '_structure_or_data'];
+
+ /**
+ * Builds the dump
+ */
+ // Gets the number of tables if a dump of a database has been required
+ if ($export_type == 'server') {
+ if (isset($db_select)) {
+ $tmp_select = implode($db_select, '|');
+ $tmp_select = '|' . $tmp_select . '|';
+ }
+ // Walk over databases
+ foreach ($GLOBALS['pma']->databases as $current_db) {
+ if (isset($tmp_select)
+ && strpos(' ' . $tmp_select, '|' . $current_db . '|')
+ ) {
+ if (! $export_plugin->exportDBHeader($current_db)) {
+ break 2;
+ }
+ if (! $export_plugin->exportDBCreate($current_db)) {
+ break 2;
+ }
+ if (method_exists($export_plugin, 'exportRoutines')
+ && strpos($whatStrucOrData, 'structure') !== false
+ && isset($GLOBALS['sql_procedure_function'])
+ ) {
+ $export_plugin->exportRoutines($current_db);
+ }
+
+ $tables = $GLOBALS['dbi']->getTables($current_db);
+ $views = array();
+ foreach ($tables as $table) {
+ // if this is a view, collect it for later;
+ // views must be exported after the tables
+ $is_view = PMA_Table::isView($current_db, $table);
+ if ($is_view) {
+ $views[] = $table;
+ }
+ if ($whatStrucOrData == 'structure'
+ || $whatStrucOrData == 'structure_and_data'
+ ) {
+ // for a view, export a stand-in definition of the table
+ // to resolve view dependencies
+ if (! $export_plugin->exportStructure(
+ $current_db, $table, $crlf, $err_url,
+ $is_view ? 'stand_in' : 'create_table', $export_type,
+ $do_relation, $do_comments, $do_mime, $do_dates
+ )) {
+ break 3;
+ }
+ }
+ // if this is a view or a merge table, don't export data
+ if (($whatStrucOrData == 'data'
+ || $whatStrucOrData == 'structure_and_data')
+ && ! ($is_view || PMA_Table::isMerge($current_db, $table))
+ ) {
+ $local_query = 'SELECT * FROM '
+ . PMA_Util::backquote($current_db)
+ . '.' . PMA_Util::backquote($table);
+ if (! $export_plugin->exportData(
+ $current_db, $table, $crlf, $err_url, $local_query
+ )) {
+ break 3;
+ }
+ }
+ // now export the triggers (needs to be done after the data
+ // because triggers can modify already imported tables)
+ if ($whatStrucOrData == 'structure'
+ || $whatStrucOrData == 'structure_and_data'
+ ) {
+ if (! $export_plugin->exportStructure(
+ $current_db, $table, $crlf, $err_url,
+ 'triggers', $export_type,
+ $do_relation, $do_comments, $do_mime, $do_dates
+ )) {
+ break 2;
+ }
+ }
+ }
+ foreach ($views as $view) {
+ // no data export for a view
+ if ($whatStrucOrData == 'structure'
+ || $whatStrucOrData == 'structure_and_data'
+ ) {
+ if (! $export_plugin->exportStructure(
+ $current_db, $view, $crlf, $err_url,
+ 'create_view', $export_type,
+ $do_relation, $do_comments, $do_mime, $do_dates
+ )) {
+ break 3;
+ }
+ }
+ }
+ if (! $export_plugin->exportDBFooter($current_db)) {
+ break 2;
+ }
+ }
+ }
+ } elseif ($export_type == 'database') {
+ if (! $export_plugin->exportDBHeader($db)) {
+ break;
+ }
+ if (! $export_plugin->exportDBCreate($db)) {
+ break;
+ }
+
+ if (method_exists($export_plugin, 'exportRoutines')
+ && strpos($GLOBALS['sql_structure_or_data'], 'structure') !== false
+ && isset($GLOBALS['sql_procedure_function'])
+ ) {
+ $export_plugin->exportRoutines($db);
+ }
+
+ $i = 0;
+ $views = array();
+
+ // $tables contains the choices from the user (via $table_select)
+ foreach ($tables as $table) {
+ // if this is a view, collect it for later;
+ // views must be exported after the tables
+ $is_view = PMA_Table::isView($db, $table);
+ if ($is_view) {
+ $views[] = $table;
+ }
+ if ($whatStrucOrData == 'structure'
+ || $whatStrucOrData == 'structure_and_data'
+ ) {
+ // for a view, export a stand-in definition of the table
+ // to resolve view dependencies
+ if (! $export_plugin->exportStructure(
+ $db, $table, $crlf, $err_url,
+ $is_view ? 'stand_in' : 'create_table', $export_type,
+ $do_relation, $do_comments, $do_mime, $do_dates
+ )) {
+ break 2;
+ }
+ }
+ // if this is a view or a merge table, don't export data
+ if (($whatStrucOrData == 'data'
+ || $whatStrucOrData == 'structure_and_data')
+ && ! ($is_view || PMA_Table::isMerge($db, $table))
+ ) {
+ $local_query = 'SELECT * FROM ' . PMA_Util::backquote($db)
+ . '.' . PMA_Util::backquote($table);
+ if (! $export_plugin->exportData(
+ $db, $table, $crlf, $err_url, $local_query
+ )) {
+ break 2;
+ }
+ }
+ // now export the triggers (needs to be done after the data because
+ // triggers can modify already imported tables)
+ if ($whatStrucOrData == 'structure'
+ || $whatStrucOrData == 'structure_and_data'
+ ) {
+ if (! $export_plugin->exportStructure(
+ $db, $table, $crlf, $err_url,
+ 'triggers', $export_type,
+ $do_relation, $do_comments, $do_mime, $do_dates
+ )) {
+ break 2;
+ }
+ }
+ }
+ foreach ($views as $view) {
+ // no data export for a view
+ if ($whatStrucOrData == 'structure'
+ || $whatStrucOrData == 'structure_and_data'
+ ) {
+ if (! $export_plugin->exportStructure(
+ $db, $view, $crlf, $err_url,
+ 'create_view', $export_type,
+ $do_relation, $do_comments, $do_mime, $do_dates
+ )) {
+ break 2;
+ }
+ }
+ }
+
+ if (! $export_plugin->exportDBFooter($db)) {
+ break;
+ }
+ } else {
+ if (! $export_plugin->exportDBHeader($db)) {
+ break;
+ }
+ // We export just one table
+ // $allrows comes from the form when "Dump all rows" has been selected
+ if (isset($allrows)
+ && $allrows == '0'
+ && $limit_to > 0
+ && $limit_from >= 0
+ ) {
+ $add_query = ' LIMIT '
+ . (($limit_from > 0) ? $limit_from . ', ' : '')
+ . $limit_to;
+ } else {
+ $add_query = '';
+ }
+
+ $is_view = PMA_Table::isView($db, $table);
+ if ($whatStrucOrData == 'structure'
+ || $whatStrucOrData == 'structure_and_data'
+ ) {
+ if (! $export_plugin->exportStructure(
+ $db, $table, $crlf, $err_url,
+ $is_view ? 'create_view' : 'create_table', $export_type,
+ $do_relation, $do_comments, $do_mime, $do_dates
+ )) {
+ break;
+ }
+ }
+ // If this is an export of a single view, we have to export data;
+ // for example, a PDF report
+ // if it is a merge table, no data is exported
+ if (($whatStrucOrData == 'data'
+ || $whatStrucOrData == 'structure_and_data')
+ && ! PMA_Table::isMerge($db, $table)
+ ) {
+ if (! empty($sql_query)) {
+ // only preg_replace if needed
+ if (! empty($add_query)) {
+ // remove trailing semicolon before adding a LIMIT
+ $sql_query = preg_replace('%;\s*$%', '', $sql_query);
+ }
+ $local_query = $sql_query . $add_query;
+ $GLOBALS['dbi']->selectDb($db);
+ } else {
+ $local_query = 'SELECT * FROM ' . PMA_Util::backquote($db)
+ . '.' . PMA_Util::backquote($table) . $add_query;
+ }
+ if (! $export_plugin->exportData(
+ $db, $table, $crlf, $err_url, $local_query
+ )) {
+ break;
+ }
+ }
+ // now export the triggers (needs to be done after the data because
+ // triggers can modify already imported tables)
+ if ($whatStrucOrData == 'structure'
+ || $whatStrucOrData == 'structure_and_data'
+ ) {
+ if (! $export_plugin->exportStructure(
+ $db, $table, $crlf, $err_url,
+ 'triggers', $export_type,
+ $do_relation, $do_comments, $do_mime, $do_dates
+ )) {
+ break 2;
+ }
+ }
+ if (! $export_plugin->exportDBFooter($db)) {
+ break;
+ }
+ }
+ if (! $export_plugin->exportFooter()) {
+ break;
+ }
+
+ } while (false);
+ // End of fake loop
+
+ if ($save_on_server && isset($message)) {
+ if ($export_type == 'server') {
+ $active_page = 'server_export.php';
+ include 'server_export.php';
+ } elseif ($export_type == 'database') {
+ $active_page = 'db_export.php';
+ include 'db_export.php';
+ } else {
+ $active_page = 'tbl_export.php';
+ include 'tbl_export.php';
+ }
+ exit();
+ }
+
+ /**
+ * Send the dump as a file...
+ */
+ if (! empty($asfile)) {
+ // Convert the charset if required.
+ if ($output_charset_conversion) {
+ $dump_buffer = PMA_convertString(
+ 'utf-8',
+ $GLOBALS['charset_of_file'],
+ $dump_buffer
+ );
+ }
+
+ // Do the compression
+ // 1. as a zipped file
+ if ($compression == 'zip') {
+ if (@function_exists('gzcompress')) {
+ $zipfile = new ZipFile();
+ $zipfile->addFile($dump_buffer, substr($filename, 0, -4));
+ $dump_buffer = $zipfile->file();
+ }
+ } elseif ($compression == 'gzip' && PMA_gzencodeNeeded()) {
+ // 3. as a gzipped file
+ // without the optional parameter level because it bugs
+ $dump_buffer = gzencode($dump_buffer);
+ }
+
+ /* If we saved on server, we have to close file now */
+ if ($save_on_server) {
+ $write_result = @fwrite($file_handle, $dump_buffer);
+ fclose($file_handle);
+ if (strlen($dump_buffer) > 0
+ && (! $write_result || ($write_result != strlen($dump_buffer)))
+ ) {
+ $message = new PMA_Message(
+ __('Insufficient space to save the file %s.'),
+ PMA_Message::ERROR,
+ $save_filename
+ );
+ } else {
+ $message = new PMA_Message(
+ __('Dump has been saved to file %s.'),
+ PMA_Message::SUCCESS,
+ $save_filename
+ );
+ }
+
+ if ($export_type == 'server') {
+ $active_page = 'server_export.php';
+ include_once 'server_export.php';
+ } elseif ($export_type == 'database') {
+ $active_page = 'db_export.php';
+ include_once 'db_export.php';
+ } else {
+ $active_page = 'tbl_export.php';
+ include_once 'tbl_export.php';
+ }
+ exit();
+ } else {
+ echo $dump_buffer;
+ }
+ } else {
+ /**
+ * Displays the dump...
+ *
+ * Close the html tags and add the footers if dump is displayed on screen
+ */
+ echo '</textarea>' . "\n"
+ . ' </form>' . "\n";
+ echo $back_button;
+
+ echo "\n";
+ echo '</div>' . "\n";
+ echo "\n";
+ ?>
+ <script type="text/javascript">
+ //<![CDATA[
+ var $body = $("body");
+ $("#textSQLDUMP")
+ .width($body.width() - 50)
+ .height($body.height() - 100);
+ //]]>
+ </script>
+ <?php
+ } // end if
+}
+?>
diff --git a/favicon.ico b/favicon.ico
new file mode 100644
index 0000000000..2352b5fa4e
--- /dev/null
+++ b/favicon.ico
Binary files differ
diff --git a/file_echo.php b/file_echo.php
new file mode 100644
index 0000000000..d910b74e28
--- /dev/null
+++ b/file_echo.php
@@ -0,0 +1,71 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * "Echo" service to allow force downloading of exported charts (png or svg)
+ * and server status monitor settings
+ *
+ * @package PhpMyAdmin
+ */
+
+define('PMA_MINIMUM_COMMON', true);
+require_once 'libraries/common.inc.php';
+
+/* For chart exporting */
+if (isset($_REQUEST['filename']) && isset($_REQUEST['image'])) {
+ $allowed = array(
+ 'image/png' => 'png',
+ 'image/svg+xml' => 'svg',
+ );
+
+ /* Check whether MIME type is allowed */
+ if (! isset($allowed[$_REQUEST['type']])) {
+ PMA_fatalError(__('Invalid export type'));
+ }
+
+ /*
+ * Check file name to match mime type and not contain new lines
+ * to prevent response splitting.
+ */
+ $extension = $allowed[$_REQUEST['type']];
+ $valid_match = '/^[^\n\r]*\.' . $extension . '$/';
+ if (! preg_match($valid_match, $_REQUEST['filename'])) {
+ if (! preg_match('/^[^\n\r]*$/', $_REQUEST['filename'])) {
+ /* Filename is unsafe, discard it */
+ $filename = 'download.' . $extension;
+ } else {
+ /* Add extension */
+ $filename = $_REQUEST['filename'] . '.' . $extension;
+ }
+ } else {
+ /* Filename from request should be safe here */
+ $filename = $_REQUEST['filename'];
+ }
+
+ /* Decode data */
+ if ($extension != 'svg') {
+ $data = substr($_REQUEST['image'], strpos($_REQUEST['image'], ',') + 1);
+ $data = base64_decode($data);
+ } else {
+ $data = $_REQUEST['image'];
+ }
+
+ /* Send download header */
+ PMA_downloadHeader($filename, $_REQUEST['type'], strlen($data));
+
+ /* Send data */
+ echo $data;
+
+} else if (isset($_REQUEST['monitorconfig'])) {
+ /* For monitor chart config export */
+ PMA_downloadHeader('monitor.cfg', 'application/force-download');
+ echo urldecode($_REQUEST['monitorconfig']);
+
+} else if (isset($_REQUEST['import'])) {
+ /* For monitor chart config import */
+ header('Content-type: text/plain');
+ if (!file_exists($_FILES['file']['tmp_name'])) {
+ exit();
+ }
+ echo file_get_contents($_FILES['file']['tmp_name']);
+}
+?>
diff --git a/gis_data_editor.php b/gis_data_editor.php
new file mode 100644
index 0000000000..621dbfa9aa
--- /dev/null
+++ b/gis_data_editor.php
@@ -0,0 +1,418 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Editor for Geometry data types.
+ *
+ * @package PhpMyAdmin
+ */
+
+/**
+ * Escapes special characters if the variable is set.
+ * Returns an empty string otherwise.
+ *
+ * @param string $variable variable to be escaped
+ *
+ * @return string escaped variable
+ */
+function escape($variable)
+{
+ return isset($variable) ? htmlspecialchars($variable) : '';
+}
+
+require_once 'libraries/common.inc.php';
+require_once 'libraries/gis/pma_gis_factory.php';
+require_once 'libraries/gis/pma_gis_visualization.php';
+require_once 'libraries/tbl_gis_visualization.lib.php';
+
+// Get data if any posted
+$gis_data = array();
+if (PMA_isValid($_REQUEST['gis_data'], 'array')) {
+ $gis_data = $_REQUEST['gis_data'];
+}
+
+$gis_types = array(
+ 'POINT',
+ 'MULTIPOINT',
+ 'LINESTRING',
+ 'MULTILINESTRING',
+ 'POLYGON',
+ 'MULTIPOLYGON',
+ 'GEOMETRYCOLLECTION'
+);
+
+// Extract type from the initial call and make sure that it's a valid one.
+// Extract from field's values if availbale, if not use the column type passed.
+if (! isset($gis_data['gis_type'])) {
+ if (isset($_REQUEST['type']) && $_REQUEST['type'] != '') {
+ $gis_data['gis_type'] = strtoupper($_REQUEST['type']);
+ }
+ if (isset($_REQUEST['value']) && trim($_REQUEST['value']) != '') {
+ $start = (substr($_REQUEST['value'], 0, 1) == "'") ? 1 : 0;
+ $gis_data['gis_type'] = substr(
+ $_REQUEST['value'], $start, strpos($_REQUEST['value'], "(") - $start
+ );
+ }
+ if ((! isset($gis_data['gis_type']))
+ || (! in_array($gis_data['gis_type'], $gis_types))
+ ) {
+ $gis_data['gis_type'] = $gis_types[0];
+ }
+}
+$geom_type = $gis_data['gis_type'];
+
+// Generate parameters from value passed.
+$gis_obj = PMA_GIS_Factory::factory($geom_type);
+if (isset($_REQUEST['value'])) {
+ $gis_data = array_merge(
+ $gis_data, $gis_obj->generateParams($_REQUEST['value'])
+ );
+}
+
+// Generate Well Known Text
+$srid = (isset($gis_data['srid']) && $gis_data['srid'] != '')
+ ? htmlspecialchars($gis_data['srid']) : 0;
+$wkt = $gis_obj->generateWkt($gis_data, 0);
+$wkt_with_zero = $gis_obj->generateWkt($gis_data, 0, '0');
+$result = "'" . $wkt . "'," . $srid;
+
+// Generate PNG or SVG based visualization
+$format = (PMA_USR_BROWSER_AGENT == 'IE' && PMA_USR_BROWSER_VER <= 8)
+ ? 'png' : 'svg';
+$visualizationSettings = array(
+ 'width' => 450,
+ 'height' => 300,
+ 'spatialColumn' => 'wkt'
+);
+$data = array(array('wkt' => $wkt_with_zero, 'srid' => $srid));
+$visualization = PMA_GIS_visualizationResults(
+ $data, $visualizationSettings, $format
+);
+$open_layers = PMA_GIS_visualizationResults($data, $visualizationSettings, 'ol');
+
+// If the call is to update the WKT and visualization make an AJAX response
+if (isset($_REQUEST['generate']) && $_REQUEST['generate'] == true) {
+ $extra_data = array(
+ 'result' => $result,
+ 'visualization' => $visualization,
+ 'openLayers' => $open_layers,
+ );
+ $response = PMA_Response::getInstance();
+ $response->addJSON($extra_data);
+ exit;
+}
+
+ob_start();
+
+echo '<form id="gis_data_editor_form" action="gis_data_editor.php" method="post">';
+echo '<input type="hidden" id="pmaThemeImage"'
+ . ' value="' . $GLOBALS['pmaThemeImage'] . '" />';
+echo '<div id="gis_data_editor">';
+
+echo '<h3>';
+printf(
+ __('Value for the column "%s"'),
+ htmlspecialchars($_REQUEST['field'])
+);
+echo '</h3>';
+
+echo '<input type="hidden" name="field" value="'
+ . htmlspecialchars($_REQUEST['field']) . '" />';
+// The input field to which the final result should be added
+// and corresponding null checkbox
+if (isset($_REQUEST['input_name'])) {
+ echo '<input type="hidden" name="input_name" value="'
+ . htmlspecialchars($_REQUEST['input_name']) . '" />';
+}
+echo PMA_URL_getHiddenInputs();
+
+echo '<!-- Visualization section -->';
+echo '<div id="placeholder" style="width:450px;height:300px;'
+ . ($srid != 0 ? 'display:none;' : '') . '">';
+echo $visualization;
+echo '</div>';
+
+echo '<div id="openlayersmap" style="width:450px;height:300px;'
+ . ($srid == 0 ? 'display:none;' : '') . '">';
+echo '</div>';
+
+echo '<div class="choice" style="float:right;clear:right;">';
+echo '<input type="checkbox" id="choice" value="useBaseLayer"'
+ . ($srid != 0 ? ' checked="checked"' : '') . '/>';
+echo '<label for="choice">' . __("Use OpenStreetMaps as Base Layer") . '</label>';
+echo '</div>';
+
+echo '<script language="javascript" type="text/javascript">';
+echo $open_layers;
+echo '</script>';
+echo '<!-- End of visualization section -->';
+
+
+echo '<!-- Header section - Inclueds GIS type selector and input field for SRID -->';
+echo '<div id="gis_data_header">';
+echo '<select name="gis_data[gis_type]" class="gis_type">';
+foreach ($gis_types as $gis_type) {
+ echo '<option value="' . $gis_type . '"';
+ if ($geom_type == $gis_type) {
+ echo ' selected="selected"';
+ }
+ echo '>' . $gis_type . '</option>';
+}
+echo '</select>';
+echo '&nbsp;&nbsp;&nbsp;&nbsp;';
+/* l10n: Spatial Reference System Identifier */
+echo '<label for="srid">' . __('SRID:') . '</label>';
+echo '<input name="gis_data[srid]" type="text" value="' . $srid . '" />';
+echo '</div>';
+echo '<!-- End of header section -->';
+
+echo '<!-- Data section -->';
+echo '<div id="gis_data">';
+
+$geom_count = 1;
+if ($geom_type == 'GEOMETRYCOLLECTION') {
+ $geom_count = (isset($gis_data[$geom_type]['geom_count']))
+ ? $gis_data[$geom_type]['geom_count'] : 1;
+ if (isset($gis_data[$geom_type]['add_geom'])) {
+ $geom_count++;
+ }
+ echo '<input type="hidden" name="gis_data[GEOMETRYCOLLECTION][geom_count]"'
+ . ' value="' . $geom_count . '" />';
+}
+
+for ($a = 0; $a < $geom_count; $a++) {
+
+ if ($geom_type == 'GEOMETRYCOLLECTION') {
+ echo '<br/><br/>';
+ printf(__('Geometry %d:'), $a + 1);
+ echo '<br/>';
+ if (isset($gis_data[$a]['gis_type'])) {
+ $type = $gis_data[$a]['gis_type'];
+ } else {
+ $type = $gis_types[0];
+ }
+ echo '<select name="gis_data[' . $a . '][gis_type]" class="gis_type">';
+ foreach (array_slice($gis_types, 0, 6) as $gis_type) {
+ echo '<option value="' . $gis_type . '"';
+ if ($type == $gis_type) {
+ echo ' selected="selected"';
+ }
+ echo '>' . $gis_type . '</option>';
+ }
+ echo '</select>';
+ } else {
+ $type = $geom_type;
+ }
+
+ if ($type == 'POINT') {
+ echo '<br/>';
+ echo __('Point:');
+ echo '<label for="x">' . __("X") . '</label>';
+ echo '<input name="gis_data[' . $a . '][POINT][x]" type="text"'
+ . ' value="' . escape($gis_data[$a]['POINT']['x']) . '" />';
+ echo '<label for="y">' . __("Y") . '</label>';
+ echo '<input name="gis_data[' . $a . '][POINT][y]" type="text"'
+ . ' value="' . escape($gis_data[$a]['POINT']['y']) . '" />';
+
+ } elseif ($type == 'MULTIPOINT' || $type == 'LINESTRING') {
+ $no_of_points = isset($gis_data[$a][$type]['no_of_points'])
+ ? $gis_data[$a][$type]['no_of_points'] : 1;
+ if ($type == 'LINESTRING' && $no_of_points < 2) {
+ $no_of_points = 2;
+ }
+ if ($type == 'MULTIPOINT' && $no_of_points < 1) {
+ $no_of_points = 1;
+ }
+
+ if (isset($gis_data[$a][$type]['add_point'])) {
+ $no_of_points++;
+ }
+ echo '<input type="hidden" value="' . $no_of_points . '"'
+ . ' name="gis_data[' . $a . '][' . $type . '][no_of_points]" />';
+
+ for ($i = 0; $i < $no_of_points; $i++) {
+ echo '<br/>';
+ printf(__('Point %d'), $i + 1);
+ echo ': ';
+ echo '<label for="x">' . __("X") . '</label>';
+ echo '<input type="text"'
+ . ' name="gis_data[' . $a . '][' . $type . '][' . $i . '][x]"'
+ . ' value="' . escape($gis_data[$a][$type][$i]['x']) . '" />';
+ echo '<label for="y">' . __("Y") . '</label>';
+ echo '<input type="text"'
+ . ' name="gis_data[' . $a . '][' . $type . '][' . $i . '][y]"'
+ . ' value="' . escape($gis_data[$a][$type][$i]['y']). '" />';
+ }
+ echo '<input type="submit"'
+ . ' name="gis_data[' . $a . '][' . $type . '][add_point]"'
+ . ' class="add addPoint" value="' . __("Add a point") . '" />';
+
+ } elseif ($type == 'MULTILINESTRING' || $type == 'POLYGON') {
+ $no_of_lines = isset($gis_data[$a][$type]['no_of_lines'])
+ ? $gis_data[$a][$type]['no_of_lines'] : 1;
+ if ($no_of_lines < 1) {
+ $no_of_lines = 1;
+ }
+ if (isset($gis_data[$a][$type]['add_line'])) {
+ $no_of_lines++;
+ }
+ echo '<input type="hidden" value="' . $no_of_lines . '"'
+ . ' name="gis_data[' . $a . '][' . $type . '][no_of_lines]" />';
+
+ for ($i = 0; $i < $no_of_lines; $i++) {
+ echo '<br/>';
+ if ($type == 'MULTILINESTRING') {
+ printf(__('Linestring %d:'), $i + 1);
+ } else {
+ if ($i == 0) {
+ echo __('Outer ring:');
+ } else {
+ printf(__('Inner ring %d:'), $i);
+ }
+ }
+
+ $no_of_points = isset($gis_data[$a][$type][$i]['no_of_points'])
+ ? $gis_data[$a][$type][$i]['no_of_points'] : 2;
+ if ($type == 'MULTILINESTRING' && $no_of_points < 2) {
+ $no_of_points = 2;
+ }
+ if ($type == 'POLYGON' && $no_of_points < 4) {
+ $no_of_points = 4;
+ }
+ if (isset($gis_data[$a][$type][$i]['add_point'])) {
+ $no_of_points++;
+ }
+ echo '<input type="hidden" value="' . $no_of_points . '"'
+ . ' name="gis_data[' . $a . '][' . $type . '][' . $i . '][no_of_points]" />';
+
+ for ($j = 0; $j < $no_of_points; $j++) {
+ echo('<br/>');
+ printf(__('Point %d'), $j + 1);
+ echo ': ';
+ echo '<label for="x">' . __("X") . '</label>';
+ echo '<input type="text"'
+ . ' name="gis_data[' . $a . '][' . $type . '][' . $i . '][' . $j . '][x]"'
+ . ' value="' . escape($gis_data[$a][$type][$i][$j]['x']) . '" />';
+ echo '<label for="y">' . __("Y") . '</label>';
+ echo '<input type="text"'
+ . ' name="gis_data[' . $a . '][' . $type . '][' . $i . '][' . $j . '][y]"'
+ . ' value="' . escape($gis_data[$a][$type][$i][$j]['x']) . '" />';
+ }
+ echo '<input type="submit"'
+ . ' name="gis_data[' . $a . '][' . $type . '][' . $i . '][add_point]"'
+ . ' class="add addPoint" value="' . __("Add a point") . '" />';
+ }
+ $caption = ($type == 'MULTILINESTRING')
+ ? __('Add a linestring')
+ : __('Add an inner ring');
+ echo '<br/>';
+ echo '<input type="submit"'
+ . ' name="gis_data[' . $a . '][' . $type . '][add_line]"'
+ . ' class="add addLine" value="' . $caption . '" />';
+
+ } elseif ($type == 'MULTIPOLYGON') {
+ $no_of_polygons = isset($gis_data[$a][$type]['no_of_polygons'])
+ ? $gis_data[$a][$type]['no_of_polygons'] : 1;
+ if ($no_of_polygons < 1) {
+ $no_of_polygons = 1;
+ }
+ if (isset($gis_data[$a][$type]['add_polygon'])) {
+ $no_of_polygons++;
+ }
+ echo '<input type="hidden"'
+ . ' name="gis_data[' . $a . '][' . $type . '][no_of_polygons]"'
+ . ' value="' . $no_of_polygons . '" />';
+
+ for ($k = 0; $k < $no_of_polygons; $k++) {
+ echo '<br/>';
+ printf(__('Polygon %d:'), $k + 1);
+ $no_of_lines = isset($gis_data[$a][$type][$k]['no_of_lines'])
+ ? $gis_data[$a][$type][$k]['no_of_lines'] : 1;
+ if ($no_of_lines < 1) {
+ $no_of_lines = 1;
+ }
+ if (isset($gis_data[$a][$type][$k]['add_line'])) {
+ $no_of_lines++;
+ }
+ echo '<input type="hidden"'
+ . ' name="gis_data[' . $a . '][' . $type . '][' . $k . '][no_of_lines]"'
+ . ' value="' . $no_of_lines . '" />';
+
+ for ($i = 0; $i < $no_of_lines; $i++) {
+ echo '<br/><br/>';
+ if ($i == 0) {
+ echo __('Outer ring:');
+ } else {
+ printf(__('Inner ring %d:'), $i);
+ }
+
+ $no_of_points = isset($gis_data[$a][$type][$k][$i]['no_of_points'])
+ ? $gis_data[$a][$type][$k][$i]['no_of_points'] : 4;
+ if ($no_of_points < 4) {
+ $no_of_points = 4;
+ }
+ if (isset($gis_data[$a][$type][$k][$i]['add_point'])) {
+ $no_of_points++;
+ }
+ echo '<input type="hidden"'
+ . ' name="gis_data[' . $a . '][' . $type . '][' . $k . '][' . $i . '][no_of_points]"'
+ . ' value="' . $no_of_points . '" />';
+
+ for ($j = 0; $j < $no_of_points; $j++) {
+ echo '<br/>';
+ printf(__('Point %d'), $j + 1);
+ echo ': ';
+ echo '<label for="x">' . __("X") . '</label>';
+ echo '<input type="text"'
+ . ' name="gis_data[' . $a . '][' . $type . '][' . $k . '][' . $i . '][' . $j . '][x]"'
+ . ' value="' . escape($gis_data[$a][$type][$k][$i][$j]['x']). '" />';
+ echo '<label for="y">' . __("Y") . '</label>';
+ echo '<input type="text"'
+ . ' name="gis_data[' . $a . '][' . $type . '][' . $k . '][' . $i . '][' . $j . '][y]"'
+ . ' value="' . escape($gis_data[$a][$type][$k][$i][$j]['y']) . '" />';
+ }
+ echo '<input type="submit"'
+ . ' name="gis_data[' . $a . '][' . $type . '][' . $k . '][' . $i . '][add_point]"'
+ . ' class="add addPoint" value="' . __("Add a point") . '" />';
+ }
+ echo '<br/>';
+ echo '<input type="submit"'
+ . ' name="gis_data[' . $a . '][' . $type . '][' . $k . '][add_line]"'
+ . ' class="add addLine" value="' . __('Add an inner ring'). '" />';
+ echo '<br/>';
+ }
+ echo '<br/>';
+ echo '<input type="submit"'
+ . ' name="gis_data[' . $a . '][' . $type . '][add_polygon]"'
+ . ' class="add addPolygon" value="' . __('Add a polygon') . '" />';
+ }
+}
+if ($geom_type == 'GEOMETRYCOLLECTION') {
+ echo '<br/><br/>';
+ echo '<input type="submit" name="gis_data[GEOMETRYCOLLECTION][add_geom]"'
+ . 'class="add addGeom" value="' . __("Add geometry") . '" />';
+}
+echo '</div>';
+echo '<!-- End of data section -->';
+
+echo '<br/>';
+echo '<input type="submit" name="gis_data[save]" value="' . __('Go') . '" />';
+
+echo '<div id="gis_data_output">';
+echo '<h3>' . __('Output') . '</h3>';
+echo '<p>';
+echo __(
+ 'Choose "GeomFromText" from the "Function" column and paste the'
+ . ' string below into the "Value" field'
+);
+echo '</p>';
+echo '<textarea id="gis_data_textarea" cols="95" rows="5">';
+echo $result;
+echo '</textarea>';
+echo '</div>';
+
+echo '</div>';
+echo '</form>';
+
+PMA_Response::getInstance()->addJSON('gis_editor', ob_get_contents());
+ob_end_clean();
+?>
diff --git a/import.php b/import.php
new file mode 100644
index 0000000000..85a78142ad
--- /dev/null
+++ b/import.php
@@ -0,0 +1,642 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Core script for import, this is just the glue around all other stuff
+ *
+ * @package PhpMyAdmin
+ */
+
+/**
+ * Get the variables sent or posted to this script and a core script
+ */
+require_once 'libraries/common.inc.php';
+require_once 'libraries/sql.lib.php';
+require_once 'libraries/bookmark.lib.php';
+//require_once 'libraries/display_import_functions.lib.php';
+
+if (isset($_REQUEST['show_as_php'])) {
+ $GLOBALS['show_as_php'] = $_REQUEST['show_as_php'];
+}
+
+/**
+ * Sets globals from $_POST
+ */
+$post_params = array(
+ 'action_bookmark',
+ 'allow_interrupt',
+ 'bkm_label',
+ 'bookmark_variable',
+ 'charset_of_file',
+ 'format',
+ 'id_bookmark',
+ 'import_type',
+ 'is_js_confirmed',
+ 'MAX_FILE_SIZE',
+ 'message_to_show',
+ 'noplugin',
+ 'skip_queries',
+ 'local_import_file'
+);
+
+// TODO: adapt full list of allowed parameters, as in export.php
+foreach ($post_params as $one_post_param) {
+ if (isset($_POST[$one_post_param])) {
+ $GLOBALS[$one_post_param] = $_POST[$one_post_param];
+ }
+}
+
+// reset import messages for ajax request
+$_SESSION['Import_message']['message'] = null;
+$_SESSION['Import_message']['go_back_url'] = null;
+// default values
+$GLOBALS['reload'] = false;
+
+// Use to identify curren cycle is executing
+// a multiquery statement or stored routine
+if (!isset($_SESSION['is_multi_query'])) {
+ $_SESSION['is_multi_query'] = false;
+}
+
+// Are we just executing plain query or sql file?
+// (eg. non import, but query box/window run)
+if (! empty($sql_query)) {
+ // run SQL query
+ $import_text = $sql_query;
+ $import_type = 'query';
+ $format = 'sql';
+
+ // refresh navigation and main panels
+ if (preg_match('/^(DROP)\s+(VIEW|TABLE|DATABASE|SCHEMA)\s+/i', $sql_query)) {
+ $GLOBALS['reload'] = true;
+ }
+
+ // refresh navigation panel only
+ if (preg_match(
+ '/^(CREATE|ALTER)\s+(VIEW|TABLE|DATABASE|SCHEMA)\s+/i',
+ $sql_query
+ )) {
+ $ajax_reload['reload'] = true;
+ }
+
+ // do a dynamic reload if table is RENAMED
+ // (by sending the instruction to the AJAX response handler)
+ if (preg_match(
+ '/^RENAME\s+TABLE\s+(.*?)\s+TO\s+(.*?)($|;|\s)/i',
+ $sql_query,
+ $rename_table_names
+ )) {
+ $ajax_reload['table_name'] = PMA_Util::unQuote($rename_table_names[2]);
+ $ajax_reload['reload'] = true;
+ }
+
+ $sql_query = '';
+} elseif (! empty($sql_localfile)) {
+ // run SQL file on server
+ $local_import_file = $sql_localfile;
+ $import_type = 'queryfile';
+ $format = 'sql';
+ unset($sql_localfile);
+} elseif (! empty($sql_file)) {
+ // run uploaded SQL file
+ $import_file = $sql_file;
+ $import_type = 'queryfile';
+ $format = 'sql';
+ unset($sql_file);
+} elseif (! empty($id_bookmark)) {
+ // run bookmark
+ $import_type = 'query';
+ $format = 'sql';
+}
+
+// If we didn't get any parameters, either user called this directly, or
+// upload limit has been reached, let's assume the second possibility.
+;
+if ($_POST == array() && $_GET == array()) {
+ $message = PMA_Message::error(
+ __('You probably tried to upload a file that is too large. Please refer to %sdocumentation%s for a workaround for this limit.')
+ );
+ $message->addParam('[doc@faq1-16]');
+ $message->addParam('[/doc]');
+
+ // so we can obtain the message
+ $_SESSION['Import_message']['message'] = $message->getDisplay();
+ $_SESSION['Import_message']['go_back_url'] = $goto;
+
+ $message->display();
+ exit; // the footer is displayed automatically
+}
+
+/**
+ * Sets globals from $_POST patterns, for import plugins
+ * We only need to load the selected plugin
+ */
+
+if (! in_array(
+ $format,
+ array(
+ 'csv',
+ 'ldi',
+ 'mediawiki',
+ 'ods',
+ 'shp',
+ 'sql',
+ 'xml'
+ )
+)
+) {
+ // this should not happen for a normal user
+ // but only during an attack
+ PMA_fatalError('Incorrect format parameter');
+}
+
+$post_patterns = array(
+ '/^force_file_/',
+ '/^'. $format . '_/'
+);
+foreach (array_keys($_POST) as $post_key) {
+ foreach ($post_patterns as $one_post_pattern) {
+ if (preg_match($one_post_pattern, $post_key)) {
+ $GLOBALS[$post_key] = $_POST[$post_key];
+ }
+ }
+}
+
+// Check needed parameters
+PMA_Util::checkParameters(array('import_type', 'format'));
+
+// We don't want anything special in format
+$format = PMA_securePath($format);
+
+// Import functions
+require_once 'libraries/import.lib.php';
+
+// Create error and goto url
+if ($import_type == 'table') {
+ $err_url = 'tbl_import.php?' . PMA_URL_getCommon($db, $table);
+ $_SESSION['Import_message']['go_back_url'] = $err_url;
+ $goto = 'tbl_import.php';
+} elseif ($import_type == 'database') {
+ $err_url = 'db_import.php?' . PMA_URL_getCommon($db);
+ $_SESSION['Import_message']['go_back_url'] = $err_url;
+ $goto = 'db_import.php';
+} elseif ($import_type == 'server') {
+ $err_url = 'server_import.php?' . PMA_URL_getCommon();
+ $_SESSION['Import_message']['go_back_url'] = $err_url;
+ $goto = 'server_import.php';
+} else {
+ if (empty($goto) || !preg_match('@^(server|db|tbl)(_[a-z]*)*\.php$@i', $goto)) {
+ if (strlen($table) && strlen($db)) {
+ $goto = 'tbl_structure.php';
+ } elseif (strlen($db)) {
+ $goto = 'db_structure.php';
+ } else {
+ $goto = 'server_sql.php';
+ }
+ }
+ if (strlen($table) && strlen($db)) {
+ $common = PMA_URL_getCommon($db, $table);
+ } elseif (strlen($db)) {
+ $common = PMA_URL_getCommon($db);
+ } else {
+ $common = PMA_URL_getCommon();
+ }
+ $err_url = $goto . '?' . $common
+ . (preg_match('@^tbl_[a-z]*\.php$@', $goto)
+ ? '&amp;table=' . htmlspecialchars($table)
+ : '');
+ $_SESSION['Import_message']['go_back_url'] = $err_url;
+}
+
+
+if (strlen($db)) {
+ $GLOBALS['dbi']->selectDb($db);
+}
+
+@set_time_limit($cfg['ExecTimeLimit']);
+if (! empty($cfg['MemoryLimit'])) {
+ @ini_set('memory_limit', $cfg['MemoryLimit']);
+}
+
+$timestamp = time();
+if (isset($allow_interrupt)) {
+ $maximum_time = ini_get('max_execution_time');
+} else {
+ $maximum_time = 0;
+}
+
+// set default values
+$timeout_passed = false;
+$error = false;
+$read_multiply = 1;
+$finished = false;
+$offset = 0;
+$max_sql_len = 0;
+$file_to_unlink = '';
+$sql_query = '';
+$sql_query_disabled = false;
+$go_sql = false;
+$executed_queries = 0;
+$run_query = true;
+$charset_conversion = false;
+$reset_charset = false;
+$bookmark_created = false;
+
+// Bookmark Support: get a query back from bookmark if required
+if (! empty($id_bookmark)) {
+ $id_bookmark = (int)$id_bookmark;
+ include_once 'libraries/bookmark.lib.php';
+ switch ($action_bookmark) {
+ case 0: // bookmarked query that have to be run
+ $import_text = PMA_Bookmark_get(
+ $db,
+ $id_bookmark,
+ 'id',
+ isset($action_bookmark_all)
+ );
+ if (isset($bookmark_variable) && ! empty($bookmark_variable)) {
+ $import_text = preg_replace(
+ '|/\*(.*)\[VARIABLE\](.*)\*/|imsU',
+ '${1}' . PMA_Util::sqlAddSlashes($bookmark_variable) . '${2}',
+ $import_text
+ );
+ }
+
+ // refresh navigation and main panels
+ if (preg_match(
+ '/^(DROP)\s+(VIEW|TABLE|DATABASE|SCHEMA)\s+/i',
+ $import_text
+ )) {
+ $GLOBALS['reload'] = true;
+ }
+
+ // refresh navigation panel only
+ if (preg_match(
+ '/^(CREATE|ALTER)\s+(VIEW|TABLE|DATABASE|SCHEMA)\s+/i',
+ $import_text
+ )
+ ) {
+ $ajax_reload['reload'] = true;
+ }
+ break;
+ case 1: // bookmarked query that have to be displayed
+ $import_text = PMA_Bookmark_get($db, $id_bookmark);
+ if ($GLOBALS['is_ajax_request'] == true) {
+ $message = PMA_Message::success(__('Showing bookmark'));
+ $response = PMA_Response::getInstance();
+ $response->isSuccess($message->isSuccess());
+ $response->addJSON('message', $message);
+ $response->addJSON('sql_query', $import_text);
+ $response->addJSON('action_bookmark', $action_bookmark);
+ exit;
+ } else {
+ $run_query = false;
+ }
+ break;
+ case 2: // bookmarked query that have to be deleted
+ $import_text = PMA_Bookmark_get($db, $id_bookmark);
+ PMA_Bookmark_delete($db, $id_bookmark);
+ if ($GLOBALS['is_ajax_request'] == true) {
+ $message = PMA_Message::success(__('The bookmark has been deleted.'));
+ $response = PMA_Response::getInstance();
+ $response->isSuccess($message->isSuccess());
+ $response->addJSON('message', $message);
+ $response->addJSON('action_bookmark', $action_bookmark);
+ $response->addJSON('id_bookmark', $id_bookmark);
+ exit;
+ } else {
+ $run_query = false;
+ $error = true; // this is kind of hack to skip processing the query
+ }
+ break;
+ }
+} // end bookmarks reading
+
+// Do no run query if we show PHP code
+if (isset($GLOBALS['show_as_php'])) {
+ $run_query = false;
+ $go_sql = true;
+}
+
+// We can not read all at once, otherwise we can run out of memory
+$memory_limit = trim(@ini_get('memory_limit'));
+// 2 MB as default
+if (empty($memory_limit)) {
+ $memory_limit = 2 * 1024 * 1024;
+}
+// In case no memory limit we work on 10MB chunks
+if ($memory_limit == -1) {
+ $memory_limit = 10 * 1024 * 1024;
+}
+
+// Calculate value of the limit
+if (strtolower(substr($memory_limit, -1)) == 'm') {
+ $memory_limit = (int)substr($memory_limit, 0, -1) * 1024 * 1024;
+} elseif (strtolower(substr($memory_limit, -1)) == 'k') {
+ $memory_limit = (int)substr($memory_limit, 0, -1) * 1024;
+} elseif (strtolower(substr($memory_limit, -1)) == 'g') {
+ $memory_limit = (int)substr($memory_limit, 0, -1) * 1024 * 1024 * 1024;
+} else {
+ $memory_limit = (int)$memory_limit;
+}
+
+// Just to be sure, there might be lot of memory needed for uncompression
+$read_limit = $memory_limit / 8;
+
+// handle filenames
+if (isset($_FILES['import_file'])) {
+ $import_file = $_FILES['import_file']['tmp_name'];
+}
+if (! empty($local_import_file) && ! empty($cfg['UploadDir'])) {
+
+ // sanitize $local_import_file as it comes from a POST
+ $local_import_file = PMA_securePath($local_import_file);
+
+ $import_file = PMA_Util::userDir($cfg['UploadDir'])
+ . $local_import_file;
+
+} elseif (empty($import_file) || ! is_uploaded_file($import_file)) {
+ $import_file = 'none';
+}
+
+// Do we have file to import?
+
+if ($import_file != 'none' && ! $error) {
+ // work around open_basedir and other limitations
+ $open_basedir = @ini_get('open_basedir');
+
+ // If we are on a server with open_basedir, we must move the file
+ // before opening it. The doc explains how to create the "./tmp"
+ // directory
+
+ if (! empty($open_basedir)) {
+
+ $tmp_subdir = (PMA_IS_WINDOWS ? '.\\tmp\\' : 'tmp/');
+
+ if (is_writable($tmp_subdir)) {
+
+
+ $import_file_new = $tmp_subdir . basename($import_file) . uniqid();
+ if (move_uploaded_file($import_file, $import_file_new)) {
+ $import_file = $import_file_new;
+ $file_to_unlink = $import_file_new;
+ }
+
+ $size = filesize($import_file);
+ }
+ }
+
+ /**
+ * Handle file compression
+ * @todo duplicate code exists in File.class.php
+ */
+ $compression = PMA_detectCompression($import_file);
+ if ($compression === false) {
+ $message = PMA_Message::error(__('File could not be read'));
+ $error = true;
+ } else {
+ switch ($compression) {
+ case 'application/bzip2':
+ if ($cfg['BZipDump'] && @function_exists('bzopen')) {
+ $import_handle = @bzopen($import_file, 'r');
+ } else {
+ $message = PMA_Message::error(
+ __('You attempted to load file with unsupported compression (%s). Either support for it is not implemented or disabled by your configuration.')
+ );
+ $message->addParam($compression);
+ $error = true;
+ }
+ break;
+ case 'application/gzip':
+ if ($cfg['GZipDump'] && @function_exists('gzopen')) {
+ $import_handle = @gzopen($import_file, 'r');
+ } else {
+ $message = PMA_Message::error(
+ __('You attempted to load file with unsupported compression (%s). Either support for it is not implemented or disabled by your configuration.')
+ );
+ $message->addParam($compression);
+ $error = true;
+ }
+ break;
+ case 'application/zip':
+ if ($cfg['ZipDump'] && @function_exists('zip_open')) {
+ /**
+ * Load interface for zip extension.
+ */
+ include_once 'libraries/zip_extension.lib.php';
+ $result = PMA_getZipContents($import_file);
+ if (! empty($result['error'])) {
+ $message = PMA_Message::rawError($result['error']);
+ $error = true;
+ } else {
+ $import_text = $result['data'];
+ }
+ } else {
+ $message = PMA_Message::error(
+ __('You attempted to load file with unsupported compression (%s). Either support for it is not implemented or disabled by your configuration.')
+ );
+ $message->addParam($compression);
+ $error = true;
+ }
+ break;
+ case 'none':
+ $import_handle = @fopen($import_file, 'r');
+ break;
+ default:
+ $message = PMA_Message::error(
+ __('You attempted to load file with unsupported compression (%s). Either support for it is not implemented or disabled by your configuration.')
+ );
+ $message->addParam($compression);
+ $error = true;
+ break;
+ }
+ }
+ // use isset() because zip compression type does not use a handle
+ if (! $error && isset($import_handle) && $import_handle === false) {
+ $message = PMA_Message::error(__('File could not be read'));
+ $error = true;
+ }
+} elseif (! $error) {
+ if (! isset($import_text) || empty($import_text)) {
+ $message = PMA_Message::error(
+ __('No data was received to import. Either no file name was submitted, or the file size exceeded the maximum size permitted by your PHP configuration. See [doc@faq1-16]FAQ 1.16[/doc].')
+ );
+ $error = true;
+ }
+}
+
+// so we can obtain the message
+//$_SESSION['Import_message'] = $message->getDisplay();
+
+// Convert the file's charset if necessary
+if ($GLOBALS['PMA_recoding_engine'] != PMA_CHARSET_NONE && isset($charset_of_file)) {
+ if ($charset_of_file != 'utf-8') {
+ $charset_conversion = true;
+ }
+} elseif (isset($charset_of_file) && $charset_of_file != 'utf8') {
+ if (PMA_DRIZZLE) {
+ // Drizzle doesn't support other character sets,
+ // so we can't fallback to SET NAMES - throw an error
+ $error = true;
+ $message = PMA_Message::error(
+ __('Cannot convert file\'s character set without character set conversion library')
+ );
+ } else {
+ $GLOBALS['dbi']->query('SET NAMES \'' . $charset_of_file . '\'');
+ // We can not show query in this case, it is in different charset
+ $sql_query_disabled = true;
+ $reset_charset = true;
+ }
+}
+
+// Something to skip?
+if (! $error && isset($skip)) {
+ $original_skip = $skip;
+ while ($skip > 0) {
+ PMA_importGetNextChunk($skip < $read_limit ? $skip : $read_limit);
+ // Disable read progresivity, otherwise we eat all memory!
+ $read_multiply = 1;
+ $skip -= $read_limit;
+ }
+ unset($skip);
+}
+
+// This array contain the data like numberof valid sql queries in the statement
+// and complete valid sql statement (which affected for rows)
+$sql_data = array('valid_sql' => array(), 'valid_queries' => 0);
+
+if (! $error) {
+ // Check for file existance
+ include_once "libraries/plugin_interface.lib.php";
+ $import_plugin = PMA_getPlugin(
+ "import",
+ $format,
+ 'libraries/plugins/import/',
+ $import_type
+ );
+ if ($import_plugin == null) {
+ $error = true;
+ $message = PMA_Message::error(
+ __('Could not load import plugins, please check your installation!')
+ );
+ } else {
+ // Do the real import
+ $import_plugin->doImport($sql_data);
+ }
+}
+
+if (! $error && false !== $import_handle && null !== $import_handle) {
+ fclose($import_handle);
+}
+
+// Cleanup temporary file
+if ($file_to_unlink != '') {
+ unlink($file_to_unlink);
+}
+
+// Reset charset back, if we did some changes
+if ($reset_charset) {
+ $GLOBALS['dbi']->query('SET CHARACTER SET utf8');
+ $GLOBALS['dbi']->query(
+ 'SET SESSION collation_connection =\'' . $collation_connection . '\''
+ );
+}
+
+// Show correct message
+if (! empty($id_bookmark) && $action_bookmark == 2) {
+ $message = PMA_Message::success(__('The bookmark has been deleted.'));
+ $display_query = $import_text;
+ $error = false; // unset error marker, it was used just to skip processing
+} elseif (! empty($id_bookmark) && $action_bookmark == 1) {
+ $message = PMA_Message::notice(__('Showing bookmark'));
+} elseif ($bookmark_created) {
+ $special_message = '[br]' . sprintf(
+ __('Bookmark %s created'),
+ htmlspecialchars($bkm_label)
+ );
+} elseif ($finished && ! $error) {
+ if ($import_type == 'query') {
+ $message = PMA_Message::success();
+ } else {
+ $message = PMA_Message::success(
+ '<em>'
+ . __('Import has been successfully finished, %d queries executed.')
+ . '</em>'
+ );
+ $message->addParam($executed_queries);
+
+ if ($import_notice) {
+ $message->addString($import_notice);
+ }
+ if (isset($local_import_file)) {
+ $message->addString('(' . htmlspecialchars($local_import_file) . ')');
+ } else {
+ $message->addString('(' . htmlspecialchars($_FILES['import_file']['name']) . ')');
+ }
+ }
+}
+
+// Did we hit timeout? Tell it user.
+if ($timeout_passed) {
+ $message = PMA_Message::error(
+ __('Script timeout passed, if you want to finish import, please resubmit same file and import will resume.')
+ );
+ if ($offset == 0 || (isset($original_skip) && $original_skip == $offset)) {
+ $message->addString(
+ __('However on last run no data has been parsed, this usually means phpMyAdmin won\'t be able to finish this import unless you increase php time limits.')
+ );
+ }
+}
+
+// if there is any message, copy it into $_SESSION as well,
+// so we can obtain it by AJAX call
+if (isset($message)) {
+ $_SESSION['Import_message']['message'] = $message->getDisplay();
+}
+// Parse and analyze the query, for correct db and table name
+// in case of a query typed in the query window
+// (but if the query is too large, in case of an imported file, the parser
+// can choke on it so avoid parsing)
+if (strlen($sql_query) <= $GLOBALS['cfg']['MaxCharactersInDisplayedSQL']) {
+ include_once 'libraries/parse_analyze.inc.php';
+}
+
+// There was an error?
+if (isset($my_die)) {
+ foreach ($my_die as $key => $die) {
+ PMA_Util::mysqlDie(
+ $die['error'], $die['sql'], '', $err_url, $error
+ );
+ }
+}
+
+if ($go_sql) {
+ // parse sql query
+ include_once 'libraries/parse_analyze.inc.php';
+
+ if (isset($ajax_reload) && $ajax_reload['reload'] === true) {
+ $response = PMA_Response::getInstance();
+ $response->addJSON('ajax_reload', $ajax_reload);
+ }
+ PMA_executeQueryAndSendQueryResponse(
+ $analyzed_sql_results, false, $db, $table, null, $import_text, null,
+ $analyzed_sql_results['is_affected'], null,
+ null, null, null, $goto, $pmaThemeImage, null, null, null, $sql_query,
+ null, null
+ );
+} else if ($result) {
+ $response = PMA_Response::getInstance();
+ $response->isSuccess(true);
+ $response->addJSON('message', PMA_Message::success($msg));
+ $response->addJSON(
+ 'sql_query',
+ PMA_Util::getMessage($msg, $sql_query, 'success')
+ );
+} else if ($result == false) {
+ $response = PMA_Response::getInstance();
+ $response->isSuccess(false);
+ $response->addJSON('message', PMA_Message::error($msg));
+} else {
+ $active_page = $goto;
+ include '' . $goto;
+}
+?>
diff --git a/import_status.php b/import_status.php
new file mode 100644
index 0000000000..f31e424f38
--- /dev/null
+++ b/import_status.php
@@ -0,0 +1,93 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Import progress bar backend
+ *
+ * @package PhpMyAdmin
+ */
+
+/* PHP 5.4 stores upload progress data only in the default session.
+ * After calling session_name(), we won't find the progress data anymore.
+ *
+ * https://bugs.php.net/bug.php?id=64075
+ *
+ * The bug should be somewhere in
+ * https://github.com/php/php-src/blob/master/ext/session/session.c#L2342
+ *
+ * Until this is fixed, we need to load the default session to load the data,
+ * export the upload progress information from there,
+ * and re-import after switching to our session.
+ */
+
+if (version_compare(PHP_VERSION, '5.4.0', '>=')
+ && ini_get('session.upload_progress.enabled')
+) {
+
+ $sessionupload = array();
+ define('UPLOAD_PREFIX', ini_get('session.upload_progress.prefix'));
+
+ session_start();
+ foreach ($_SESSION as $key => $value) {
+ // only copy session-prefixed data
+ if (substr($key, 0, strlen(UPLOAD_PREFIX)) == UPLOAD_PREFIX) {
+ $sessionupload[$key] = $value;
+ }
+ }
+ // PMA will kill all variables, so let's use a constant
+ define('SESSIONUPLOAD', serialize($sessionupload));
+ session_write_close();
+
+ session_name('phpMyAdmin');
+ session_id($_COOKIE['phpMyAdmin']);
+}
+
+define('PMA_MINIMUM_COMMON', 1);
+
+require_once 'libraries/common.inc.php';
+require_once 'libraries/display_import_ajax.lib.php';
+
+if (defined('SESSIONUPLOAD')) {
+ // write sessionupload back into the loaded PMA session
+
+ $sessionupload = unserialize(SESSIONUPLOAD);
+ foreach ($sessionupload as $key => $value) {
+ $_SESSION[$key] = $value;
+ }
+
+ // remove session upload data that are not set anymore
+ foreach ($_SESSION as $key => $value) {
+ if (substr($key, 0, strlen(UPLOAD_PREFIX)) == UPLOAD_PREFIX
+ && ! isset($sessionupload[$key])
+ ) {
+ unset($_SESSION[$key]);
+ }
+ }
+}
+
+// AJAX requests can't be cached!
+PMA_noCacheHeader();
+
+// $_GET["message"] is used for asking for an import message
+if (isset($_GET["message"]) && $_GET["message"]) {
+
+ header('Content-type: text/html');
+
+ // wait 0.3 sec before we check for $_SESSION variable,
+ // which is set inside import.php
+ usleep(300000);
+
+ // wait until message is available
+ while ($_SESSION['Import_message']['message'] == null) {
+ usleep(250000); // 0.25 sec
+ }
+
+ echo $_SESSION['Import_message']['message'];
+ echo '<fieldset class="tblFooters">' . "\n";
+ echo ' [ <a href="' . $_SESSION['Import_message']['go_back_url']
+ . '">' . __('Back') . '</a> ]' . "\n";
+ echo '</fieldset>'."\n";
+
+} else {
+ PMA_importAjaxStatus($_GET["id"]);
+}
+?>
diff --git a/index.php b/index.php
new file mode 100644
index 0000000000..63963cedc9
--- /dev/null
+++ b/index.php
@@ -0,0 +1,667 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Main loader script
+ *
+ * @package PhpMyAdmin
+ */
+
+/**
+ * Gets some core libraries and displays a top message if required
+ */
+require_once 'libraries/common.inc.php';
+
+/**
+ * display Git revision if requested
+ */
+require_once 'libraries/display_git_revision.lib.php';
+
+/**
+ * pass variables to child pages
+ */
+$drops = array(
+ 'lang',
+ 'server',
+ 'collation_connection',
+ 'db',
+ 'table'
+);
+foreach ($drops as $each_drop) {
+ if (array_key_exists($each_drop, $_GET)) {
+ unset($_GET[$each_drop]);
+ }
+}
+unset($drops, $each_drop);
+
+/*
+ * Black list of all scripts to which front-end must submit data.
+ * Such scripts must not be loaded on home page.
+ *
+ */
+ $target_blacklist = array (
+ 'import.php', 'export.php'
+ );
+
+// If we have a valid target, let's load that script instead
+if (! empty($_REQUEST['target'])
+ && is_string($_REQUEST['target'])
+ && ! preg_match('/^index/', $_REQUEST['target'])
+ && ! in_array($_REQUEST['target'], $target_blacklist ) // Check if target is not in blacklist.
+ && in_array($_REQUEST['target'], $goto_whitelist)
+) {
+ include $_REQUEST['target'];
+ exit;
+}
+
+/**
+ * Check if it is an ajax request to reload the recent tables list.
+ */
+require_once 'libraries/RecentTable.class.php';
+if ($GLOBALS['is_ajax_request'] && ! empty($_REQUEST['recent_table'])) {
+ $response = PMA_Response::getInstance();
+ $response->addJSON(
+ 'options',
+ PMA_RecentTable::getInstance()->getHtmlSelectOption()
+ );
+ exit;
+}
+
+if ($GLOBALS['PMA_Config']->isGitRevision()) {
+ if (isset($_REQUEST['git_revision']) && $GLOBALS['is_ajax_request'] == true) {
+ PMA_printGitRevision();
+ exit;
+ }
+ echo '<div id="is_git_revision"></div>';
+}
+
+// Handles some variables that may have been sent by the calling script
+$GLOBALS['db'] = '';
+$GLOBALS['table'] = '';
+$show_query = '1';
+
+// Any message to display?
+if (! empty($message)) {
+ echo PMA_Util::getMessage($message);
+ unset($message);
+}
+
+$common_url_query = PMA_URL_getCommon('', '');
+
+// when $server > 0, a server has been chosen so we can display
+// all MySQL-related information
+if ($server > 0) {
+ include 'libraries/server_common.inc.php';
+ include 'libraries/StorageEngine.class.php';
+
+ // Use the verbose name of the server instead of the hostname
+ // if a value is set
+ $server_info = '';
+ if (! empty($cfg['Server']['verbose'])) {
+ $server_info .= htmlspecialchars($cfg['Server']['verbose']);
+ if ($GLOBALS['cfg']['ShowServerInfo']) {
+ $server_info .= ' (';
+ }
+ }
+ if ($GLOBALS['cfg']['ShowServerInfo'] || empty($cfg['Server']['verbose'])) {
+ $server_info .= $GLOBALS['dbi']->getHostInfo();
+ }
+ if (! empty($cfg['Server']['verbose']) && $GLOBALS['cfg']['ShowServerInfo']) {
+ $server_info .= ')';
+ }
+ $mysql_cur_user_and_host = $GLOBALS['dbi']->fetchValue('SELECT USER();');
+
+ // should we add the port info here?
+ $short_server_info = (!empty($GLOBALS['cfg']['Server']['verbose'])
+ ? $GLOBALS['cfg']['Server']['verbose']
+ : $GLOBALS['cfg']['Server']['host']);
+}
+
+echo '<div id="maincontainer">' . "\n";
+echo '<div id="main_pane_left">';
+if ($server > 0 || count($cfg['Servers']) > 1
+) {
+ if ($cfg['DBG']['demo']) {
+ echo '<div class="group">';
+ echo '<h2>' . __('phpMyAdmin Demo Server') . '</h2>';
+ echo '<p style="margin: 0.5em 1em 0.5em 1em">';
+ printf(
+ __(
+ 'You are using the demo server. You can do anything here, but '
+ . 'please do not change root, debian-sys-maint and pma users. '
+ . 'More information is available at %s.'
+ ),
+ '<a href="http://demo.phpmyadmin.net/">demo.phpmyadmin.net</a>'
+ );
+ echo '</p>';
+ echo '</div>';
+ }
+ echo '<div class="group">';
+ echo '<h2>' . __('General Settings') . '</h2>';
+ echo '<ul>';
+
+ /**
+ * Displays the MySQL servers choice form
+ */
+ if ($cfg['ServerDefault'] == 0
+ || (! $cfg['NavigationDisplayServers']
+ && (count($cfg['Servers']) > 1
+ || ($server == 0 && count($cfg['Servers']) == 1)))
+ ) {
+ echo '<li id="li_select_server" class="no_bullets" >';
+ include_once 'libraries/select_server.lib.php';
+ echo PMA_Util::getImage('s_host.png') . " " . PMA_selectServer(true, true);
+ echo '</li>';
+ }
+
+ /**
+ * Displays the mysql server related links
+ */
+ if ($server > 0 && ! PMA_DRIZZLE) {
+ include_once 'libraries/check_user_privileges.lib.php';
+
+ // Logout for advanced authentication
+ if ($cfg['Server']['auth_type'] != 'config') {
+ if ($cfg['ShowChgPassword']) {
+ $conditional_class = 'ajax';
+ PMA_printListItem(
+ PMA_Util::getImage('s_passwd.png') . " " . __('Change password'),
+ 'li_change_password',
+ 'user_password.php?' . $common_url_query,
+ null,
+ null,
+ 'change_password_anchor',
+ "no_bullets",
+ $conditional_class
+ );
+ }
+ } // end if
+ echo ' <li id="li_select_mysql_collation" class="no_bullets" >';
+ echo ' <form method="post" action="index.php">' . "\n"
+ . PMA_URL_getHiddenInputs(null, null, 4, 'collation_connection')
+ . ' <label for="select_collation_connection">' . "\n"
+ . ' '. PMA_Util::getImage('s_asci.png') . " "
+ . __('Server connection collation') . "\n"
+ // put the doc link in the form so that it appears on the same line
+ . PMA_Util::showMySQLDocu('Charset-connection')
+ . ': ' . "\n"
+ . ' </label>' . "\n"
+
+ . PMA_generateCharsetDropdownBox(
+ PMA_CSDROPDOWN_COLLATION,
+ 'collation_connection',
+ 'select_collation_connection',
+ $collation_connection,
+ true,
+ 4,
+ true
+ )
+ . ' </form>' . "\n"
+ . ' </li>' . "\n";
+ } // end of if ($server > 0 && !PMA_DRIZZLE)
+ echo '</ul>';
+ echo '</div>';
+}
+
+echo '<div class="group">';
+echo '<h2>' . __('Appearance Settings') . '</h2>';
+echo ' <ul>';
+
+// Displays language selection combo
+if (empty($cfg['Lang']) && count($GLOBALS['available_languages']) > 1) {
+ echo '<li id="li_select_lang" class="no_bullets">';
+ include_once 'libraries/display_select_lang.lib.php';
+ echo PMA_Util::getImage('s_lang.png') . " " . PMA_getLanguageSelectorHtml();
+ echo '</li>';
+}
+
+// ThemeManager if available
+
+if ($GLOBALS['cfg']['ThemeManager']) {
+ echo '<li id="li_select_theme" class="no_bullets">';
+ echo PMA_Util::getImage('s_theme.png') . " "
+ . $_SESSION['PMA_Theme_Manager']->getHtmlSelectBox();
+ echo '</li>';
+}
+echo '<li id="li_select_fontsize">';
+echo PMA_Config::getFontsizeForm();
+echo '</li>';
+
+echo '</ul>';
+
+// User preferences
+
+if ($server > 0) {
+ echo '<ul>';
+ PMA_printListItem(
+ PMA_Util::getImage('b_tblops.png')." " .__('More settings'),
+ 'li_user_preferences',
+ 'prefs_manage.php?' . $common_url_query,
+ null,
+ null,
+ null,
+ "no_bullets"
+ );
+ echo '</ul>';
+}
+
+echo '</div>';
+
+
+echo '</div>';
+echo '<div id="main_pane_right">';
+
+
+if ($server > 0 && $GLOBALS['cfg']['ShowServerInfo']) {
+
+ echo '<div class="group">';
+ echo '<h2>' . __('Database server') . '</h2>';
+ echo '<ul>' . "\n";
+ PMA_printListItem(
+ __('Server:') . ' ' . $server_info,
+ 'li_server_info'
+ );
+ PMA_printListItem(
+ __('Server type:') . ' ' . PMA_Util::getServerType(),
+ 'li_server_type'
+ );
+ PMA_printListItem(
+ __('Server version:')
+ . ' '
+ . PMA_MYSQL_STR_VERSION . ' - ' . PMA_MYSQL_VERSION_COMMENT,
+ 'li_server_version'
+ );
+ PMA_printListItem(
+ __('Protocol version:') . ' ' . $GLOBALS['dbi']->getProtoInfo(),
+ 'li_mysql_proto'
+ );
+ PMA_printListItem(
+ __('User:') . ' ' . htmlspecialchars($mysql_cur_user_and_host),
+ 'li_user_info'
+ );
+
+ echo ' <li id="li_select_mysql_charset">';
+ echo ' ' . __('Server charset:') . ' '
+ . ' <span lang="en" dir="ltr">';
+ if (! PMA_DRIZZLE) {
+ echo ' ' . $mysql_charsets_descriptions[$mysql_charset_map['utf-8']];
+ }
+ echo ' (' . $mysql_charset_map['utf-8'] . ')'
+ . ' </span>'
+ . ' </li>'
+ . ' </ul>'
+ . ' </div>';
+}
+
+if ($GLOBALS['cfg']['ShowServerInfo'] || $GLOBALS['cfg']['ShowPhpInfo']) {
+ echo '<div class="group">';
+ echo '<h2>' . __('Web server') . '</h2>';
+ echo '<ul>';
+ if ($GLOBALS['cfg']['ShowServerInfo']) {
+ PMA_printListItem($_SERVER['SERVER_SOFTWARE'], 'li_web_server_software');
+
+ if ($server > 0) {
+ $client_version_str = $GLOBALS['dbi']->getClientInfo();
+ if (preg_match('#\d+\.\d+\.\d+#', $client_version_str)
+ && in_array(
+ $GLOBALS['cfg']['Server']['extension'],
+ array('mysql', 'mysqli')
+ )
+ ) {
+ $client_version_str = 'libmysql - ' . $client_version_str;
+ }
+ PMA_printListItem(
+ __('Database client version:') . ' ' . $client_version_str,
+ 'li_mysql_client_version'
+ );
+
+ $php_ext_string = __('PHP extension:') . ' '
+ . $GLOBALS['cfg']['Server']['extension'] . ' ';
+ if (!empty($GLOBALS['cfg']['Server']['extension'])) {
+ $php_ext_string .= PMA_Util::showPHPDocu('book.' . $GLOBALS['cfg']['Server']['extension'] . '.php' );
+ }
+ else {
+ $php_ext_string .= __('None');
+ }
+
+ PMA_printListItem(
+ $php_ext_string,
+ 'li_used_php_extension'
+ );
+ }
+ }
+
+ if ($cfg['ShowPhpInfo']) {
+ PMA_printListItem(
+ __('Show PHP information'),
+ 'li_phpinfo',
+ 'phpinfo.php?' . $common_url_query,
+ null,
+ '_blank'
+ );
+ }
+ echo ' </ul>';
+ echo ' </div>';
+}
+
+echo '<div class="group pmagroup">';
+echo '<h2>phpMyAdmin</h2>';
+echo '<ul>';
+$class = null;
+// We rely on CSP to allow access to http://www.phpmyadmin.net, but IE lacks
+// support here and does not allow request to http once using https.
+if ($GLOBALS['cfg']['VersionCheck']
+ && (! $GLOBALS['PMA_Config']->get('is_https') || PMA_USR_BROWSER_AGENT != 'IE')
+) {
+ $class = 'jsversioncheck';
+}
+PMA_printListItem(
+ __('Version information:') . ' ' . PMA_VERSION,
+ 'li_pma_version',
+ null,
+ null,
+ null,
+ null,
+ $class
+);
+PMA_printListItem(
+ __('Documentation'),
+ 'li_pma_docs',
+ PMA_Util::getDocuLink('index'),
+ null,
+ '_blank'
+);
+PMA_printListItem(
+ __('Wiki'),
+ 'li_pma_wiki',
+ PMA_linkURL('http://wiki.phpmyadmin.net/'),
+ null,
+ '_blank'
+);
+
+// does not work if no target specified, don't know why
+PMA_printListItem(
+ __('Official Homepage'),
+ 'li_pma_homepage',
+ PMA_linkURL('http://www.phpMyAdmin.net/'),
+ null,
+ '_blank'
+);
+PMA_printListItem(
+ __('Contribute'),
+ 'li_pma_contribute',
+ PMA_linkURL('http://www.phpmyadmin.net/home_page/improve.php'),
+ null,
+ '_blank'
+);
+PMA_printListItem(
+ __('Get support'),
+ 'li_pma_support',
+ PMA_linkURL('http://www.phpmyadmin.net/home_page/support.php'),
+ null,
+ '_blank'
+);
+PMA_printListItem(
+ __('List of changes'),
+ 'li_pma_changes',
+ PMA_linkURL('changelog.php'),
+ null,
+ '_blank'
+);
+echo ' </ul>';
+echo ' </div>';
+
+echo '</div>';
+
+echo '</div>';
+
+/**
+ * Warning if using the default MySQL privileged account
+ */
+if ($server != 0
+ && $cfg['Server']['user'] == 'root'
+ && $cfg['Server']['password'] == ''
+) {
+ trigger_error(
+ __(
+ 'Your configuration file contains settings (root with no password)'
+ . ' that correspond to the default MySQL privileged account.'
+ . ' Your MySQL server is running with this default, is open to'
+ . ' intrusion, and you really should fix this security hole by'
+ . ' setting a password for user \'root\'.'
+ ),
+ E_USER_WARNING
+ );
+}
+
+/**
+ * As we try to handle charsets by ourself, mbstring overloads just
+ * break it, see bug 1063821.
+ */
+if (@extension_loaded('mbstring') && @ini_get('mbstring.func_overload') > 1) {
+ trigger_error(
+ __(
+ 'You have enabled mbstring.func_overload in your PHP '
+ . 'configuration. This option is incompatible with phpMyAdmin '
+ . 'and might cause some data to be corrupted!'
+ ),
+ E_USER_WARNING
+ );
+}
+
+/**
+ * mbstring is used for handling multibyte inside parser, so it is good
+ * to tell user something might be broken without it, see bug #1063149.
+ */
+if (! @extension_loaded('mbstring')) {
+ trigger_error(
+ __(
+ 'The mbstring PHP extension was not found and you seem to be using'
+ . ' a multibyte charset. Without the mbstring extension phpMyAdmin'
+ . ' is unable to split strings correctly and it may result in'
+ . ' unexpected results.'
+ ),
+ E_USER_WARNING
+ );
+}
+
+/**
+ * Check whether session.gc_maxlifetime limits session validity.
+ */
+$gc_time = (int)@ini_get('session.gc_maxlifetime');
+if ($gc_time < $GLOBALS['cfg']['LoginCookieValidity'] ) {
+ trigger_error(
+ __('Your PHP parameter [a@http://php.net/manual/en/session.configuration.php#ini.session.gc-maxlifetime@_blank]session.gc_maxlifetime[/a] is lower than cookie validity configured in phpMyAdmin, because of this, your login will expire sooner than configured in phpMyAdmin.'),
+ E_USER_WARNING
+ );
+}
+
+/**
+ * Check whether LoginCookieValidity is limited by LoginCookieStore.
+ */
+if ($GLOBALS['cfg']['LoginCookieStore'] != 0
+ && $GLOBALS['cfg']['LoginCookieStore'] < $GLOBALS['cfg']['LoginCookieValidity']
+) {
+ trigger_error(
+ __('Login cookie store is lower than cookie validity configured in phpMyAdmin, because of this, your login will expire sooner than configured in phpMyAdmin.'),
+ E_USER_WARNING
+ );
+}
+
+/**
+ * Check if user does not have defined blowfish secret and it is being used.
+ */
+if (! empty($_SESSION['auto_blowfish_secret'])
+ && empty($GLOBALS['cfg']['blowfish_secret'])
+) {
+ trigger_error(
+ __('The configuration file now needs a secret passphrase (blowfish_secret).'),
+ E_USER_WARNING
+ );
+}
+
+/**
+ * Check for existence of config directory which should not exist in
+ * production environment.
+ */
+if (file_exists('config')) {
+ trigger_error(
+ __('Directory [code]config[/code], which is used by the setup script, still exists in your phpMyAdmin directory. It is strongly recommended to remove it once phpMyAdmin has been configured. Otherwise the security of your server may be compromised by unauthorized people downloading your configuration.'),
+ E_USER_WARNING
+ );
+}
+
+if ($server > 0) {
+ $cfgRelation = PMA_getRelationsParam();
+ if (! $cfgRelation['allworks']
+ && $cfg['PmaNoRelation_DisableWarning'] == false
+ ) {
+ $msg = PMA_Message::notice(__('The phpMyAdmin configuration storage is not completely configured, some extended features have been deactivated. To find out why click %shere%s.'));
+ $msg->addParam(
+ '<a href="' . $cfg['PmaAbsoluteUri'] . 'chk_rel.php?'
+ . $common_url_query . '">',
+ false
+ );
+ $msg->addParam('</a>', false);
+ /* Show error if user has configured something, notice elsewhere */
+ if (!empty($cfg['Servers'][$server]['pmadb'])) {
+ $msg->isError(true);
+ }
+ $msg->display();
+ } // end if
+}
+
+/**
+ * Warning about different MySQL library and server version
+ * (a difference on the third digit does not count).
+ * If someday there is a constant that we can check about mysqlnd,
+ * we can use it instead of strpos().
+ * If no default server is set, $GLOBALS['dbi'] is not defined yet.
+ * Drizzle can speak MySQL protocol, so don't warn about version mismatch for
+ * Drizzle servers.
+ */
+if (isset($GLOBALS['dbi'])
+ && !PMA_DRIZZLE
+ && $cfg['ServerLibraryDifference_DisableWarning'] == false
+) {
+ $_client_info = $GLOBALS['dbi']->getClientInfo();
+ if ($server > 0
+ && strpos($_client_info, 'mysqlnd') === false
+ && substr(PMA_MYSQL_CLIENT_API, 0, 3) != substr(PMA_MYSQL_INT_VERSION, 0, 3)
+ ) {
+ trigger_error(
+ PMA_sanitize(
+ sprintf(
+ __('Your PHP MySQL library version %s differs from your MySQL server version %s. This may cause unpredictable behavior.'),
+ $_client_info,
+ substr(
+ PMA_MYSQL_STR_VERSION,
+ 0,
+ strpos(PMA_MYSQL_STR_VERSION . '-', '-')
+ )
+ )
+ ),
+ E_USER_NOTICE
+ );
+ }
+ unset($_client_info);
+}
+
+/**
+ * Warning about Suhosin only if its simulation mode is not enabled
+ */
+if ($cfg['SuhosinDisableWarning'] == false
+ && @ini_get('suhosin.request.max_value_length')
+ && @ini_get('suhosin.simulation') == '0'
+) {
+ trigger_error(
+ sprintf(
+ __('Server running with Suhosin. Please refer to %sdocumentation%s for possible issues.'),
+ '[doc@faq1-38]',
+ '[/doc]'
+ ),
+ E_USER_WARNING
+ );
+}
+
+/**
+ * Warning about mcrypt.
+ */
+if (! function_exists('mcrypt_encrypt')
+ && ! $GLOBALS['cfg']['McryptDisableWarning']
+) {
+ PMA_warnMissingExtension('mcrypt');
+}
+
+/**
+ * Warning about incomplete translations.
+ *
+ * The data file is created while creating release by ./scripts/remove-incomplete-mo
+ */
+if (file_exists('libraries/language_stats.inc.php')) {
+ include 'libraries/language_stats.inc.php';
+ /*
+ * This message is intentionally not translated, because we're
+ * handling incomplete translations here and focus on english
+ * speaking users.
+ */
+ if (isset($GLOBALS['language_stats'][$lang])
+ && $GLOBALS['language_stats'][$lang] < $cfg['TranslationWarningThreshold']
+ ) {
+ trigger_error(
+ 'You are using an incomplete translation, please help to make it '
+ . 'better by [a@http://www.phpmyadmin.net/home_page/improve.php'
+ . '#translate@_blank]contributing[/a].',
+ E_USER_NOTICE
+ );
+ }
+}
+
+/**
+ * prints list item for main page
+ *
+ * @param string $name displayed text
+ * @param string $id id, used for css styles
+ * @param string $url make item as link with $url as target
+ * @param string $mysql_help_page display a link to MySQL's manual
+ * @param string $target special target for $url
+ * @param string $a_id id for the anchor,
+ * used for jQuery to hook in functions
+ * @param string $class class for the li element
+ * @param string $a_class class for the anchor element
+ *
+ * @return void
+ */
+function PMA_printListItem($name, $id = null, $url = null,
+ $mysql_help_page = null, $target = null, $a_id = null, $class = null,
+ $a_class = null
+) {
+ echo '<li id="' . $id . '"';
+ if (null !== $class) {
+ echo ' class="' . $class . '"';
+ }
+ echo '>';
+ if (null !== $url) {
+ echo '<a href="' . $url . '"';
+ if (null !== $target) {
+ echo ' target="' . $target . '"';
+ }
+ if (null != $a_id) {
+ echo ' id="' . $a_id .'"';
+ }
+ if (null != $a_class) {
+ echo ' class="' . $a_class .'"';
+ }
+ echo '>';
+ }
+
+ echo $name;
+
+ if (null !== $url) {
+ echo '</a>' . "\n";
+ }
+ if (null !== $mysql_help_page) {
+ echo PMA_Util::showMySQLDocu($mysql_help_page);
+ }
+ echo '</li>';
+}
+?>
diff --git a/js/OpenStreetMap.js b/js/OpenStreetMap.js
new file mode 100644
index 0000000000..51694bc86c
--- /dev/null
+++ b/js/OpenStreetMap.js
@@ -0,0 +1,126 @@
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Namespace: Util.OSM
+ */
+OpenLayers.Util.OSM = {};
+
+/**
+ * Constant: MISSING_TILE_URL
+ * {String} URL of image to display for missing tiles
+ */
+OpenLayers.Util.OSM.MISSING_TILE_URL = "http://www.openstreetmap.org/openlayers/img/404.png";
+
+/**
+ * Property: originalOnImageLoadError
+ * {Function} Original onImageLoadError function.
+ */
+OpenLayers.Util.OSM.originalOnImageLoadError = OpenLayers.Util.onImageLoadError;
+
+/**
+ * Function: onImageLoadError
+ */
+OpenLayers.Util.onImageLoadError = function () {
+ if (this.src.match(/^http:\/\/[abc]\.[a-z]+\.openstreetmap\.org\//)) {
+ this.src = OpenLayers.Util.OSM.MISSING_TILE_URL;
+ } else if (this.src.match(/^http:\/\/[def]\.tah\.openstreetmap\.org\//)) {
+ // do nothing - this layer is transparent
+ } else {
+ OpenLayers.Util.OSM.originalOnImageLoadError;
+ }
+};
+
+/**
+ * Class: OpenLayers.Layer.OSM.Mapnik
+ *
+ * Inherits from:
+ * - <OpenLayers.Layer.OSM>
+ */
+OpenLayers.Layer.OSM.Mapnik = OpenLayers.Class(OpenLayers.Layer.OSM, {
+ /**
+ * Constructor: OpenLayers.Layer.OSM.Mapnik
+ *
+ * Parameters:
+ * name - {String}
+ * options - {Object} Hashtable of extra options to tag onto the layer
+ */
+ initialize: function (name, options) {
+ var url = [
+ "http://a.tile.openstreetmap.org/${z}/${x}/${y}.png",
+ "http://b.tile.openstreetmap.org/${z}/${x}/${y}.png",
+ "http://c.tile.openstreetmap.org/${z}/${x}/${y}.png"
+ ];
+ options = OpenLayers.Util.extend({
+ numZoomLevels: 19,
+ buffer: 0,
+ transitionEffect: "resize"
+ }, options);
+ var newArguments = [name, url, options];
+ OpenLayers.Layer.OSM.prototype.initialize.apply(this, newArguments);
+ },
+
+ CLASS_NAME: "OpenLayers.Layer.OSM.Mapnik"
+});
+
+/**
+ * Class: OpenLayers.Layer.OSM.Osmarender
+ *
+ * Inherits from:
+ * - <OpenLayers.Layer.OSM>
+ */
+OpenLayers.Layer.OSM.Osmarender = OpenLayers.Class(OpenLayers.Layer.OSM, {
+ /**
+ * Constructor: OpenLayers.Layer.OSM.Osmarender
+ *
+ * Parameters:
+ * name - {String}
+ * options - {Object} Hashtable of extra options to tag onto the layer
+ */
+ initialize: function (name, options) {
+ var url = [
+ "http://a.tah.openstreetmap.org/Tiles/tile/${z}/${x}/${y}.png",
+ "http://b.tah.openstreetmap.org/Tiles/tile/${z}/${x}/${y}.png",
+ "http://c.tah.openstreetmap.org/Tiles/tile/${z}/${x}/${y}.png"
+ ];
+ options = OpenLayers.Util.extend({
+ numZoomLevels: 18,
+ buffer: 0,
+ transitionEffect: "resize"
+ }, options);
+ var newArguments = [name, url, options];
+ OpenLayers.Layer.OSM.prototype.initialize.apply(this, newArguments);
+ },
+
+ CLASS_NAME: "OpenLayers.Layer.OSM.Osmarender"
+});
+
+/**
+ * Class: OpenLayers.Layer.OSM.CycleMap
+ *
+ * Inherits from:
+ * - <OpenLayers.Layer.OSM>
+ */
+OpenLayers.Layer.OSM.CycleMap = OpenLayers.Class(OpenLayers.Layer.OSM, {
+ /**
+ * Constructor: OpenLayers.Layer.OSM.CycleMap
+ *
+ * Parameters:
+ * name - {String}
+ * options - {Object} Hashtable of extra options to tag onto the layer
+ */
+ initialize: function (name, options) {
+ var url = [
+ "http://a.tile.opencyclemap.org/cycle/${z}/${x}/${y}.png",
+ "http://b.tile.opencyclemap.org/cycle/${z}/${x}/${y}.png",
+ "http://c.tile.opencyclemap.org/cycle/${z}/${x}/${y}.png"
+ ];
+ options = OpenLayers.Util.extend({
+ numZoomLevels: 19,
+ buffer: 0,
+ transitionEffect: "resize"
+ }, options);
+ var newArguments = [name, url, options];
+ OpenLayers.Layer.OSM.prototype.initialize.apply(this, newArguments);
+ },
+
+ CLASS_NAME: "OpenLayers.Layer.OSM.CycleMap"
+});
diff --git a/js/ajax.js b/js/ajax.js
new file mode 100644
index 0000000000..303a6eea78
--- /dev/null
+++ b/js/ajax.js
@@ -0,0 +1,867 @@
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * This object handles ajax requests for pages. It also
+ * handles the reloading of the main menu and scripts.
+ */
+var AJAX = {
+ /**
+ * @var bool active Whether we are busy
+ */
+ active: false,
+ /**
+ * @var object source The object whose event initialized the request
+ */
+ source: null,
+ /**
+ * @var object xhr A reference to the ajax request that is currently running
+ */
+ xhr: null,
+ /**
+ * @var function Callback to execute after a successful request
+ * Used by PMA_commonFunctions from common.js
+ */
+ _callback: function () {},
+ /**
+ * @var bool _debug Makes noise in your Firebug console
+ */
+ _debug: false,
+ /**
+ * @var object $msgbox A reference to a jQuery object that links to a message
+ * box that is generated by PMA_ajaxShowMessage()
+ */
+ $msgbox: null,
+ /**
+ * Given the filename of a script, returns a hash to be
+ * used to refer to all the events registered for the file
+ *
+ * @param string key The filename for which to get the event name
+ *
+ * @return int
+ */
+ hash: function (key) {
+ /* http://burtleburtle.net/bob/hash/doobs.html#one */
+ key += "";
+ var len = key.length, hash = 0, i = 0;
+ for (; i < len; ++i) {
+ hash += key.charCodeAt(i);
+ hash += (hash << 10);
+ hash ^= (hash >> 6);
+ }
+ hash += (hash << 3);
+ hash ^= (hash >> 11);
+ hash += (hash << 15);
+ return Math.abs(hash);
+ },
+ /**
+ * Registers an onload event for a file
+ *
+ * @param string file The filename for which to register the event
+ * @param function func The function to execute when the page is ready
+ *
+ * @return self For chaining
+ */
+ registerOnload: function (file, func) {
+ var eventName = 'onload_' + AJAX.hash(file);
+ $(document).bind(eventName, func);
+ if (this._debug) {
+ console.log(
+ // no need to translate
+ "Registered event " + eventName + " for file " + file
+ );
+ }
+ return this;
+ },
+ /**
+ * Registers a teardown event for a file. This is useful to execute functions
+ * that unbind events for page elements that are about to be removed.
+ *
+ * @param string file The filename for which to register the event
+ * @param function func The function to execute when
+ * the page is about to be torn down
+ *
+ * @return self For chaining
+ */
+ registerTeardown: function (file, func) {
+ var eventName = 'teardown_' + AJAX.hash(file);
+ $(document).bind(eventName, func);
+ if (this._debug) {
+ console.log(
+ // no need to translate
+ "Registered event " + eventName + " for file " + file
+ );
+ }
+ return this;
+ },
+ /**
+ * Called when a page has finished loading, once for every
+ * file that registered to the onload event of that file.
+ *
+ * @param string file The filename for which to fire the event
+ *
+ * @return void
+ */
+ fireOnload: function (file) {
+ var eventName = 'onload_' + AJAX.hash(file);
+ $(document).trigger(eventName);
+ if (this._debug) {
+ console.log(
+ // no need to translate
+ "Fired event " + eventName + " for file " + file
+ );
+ }
+ },
+ /**
+ * Called just before a page is torn down, once for every
+ * file that registered to the teardown event of that file.
+ *
+ * @param string file The filename for which to fire the event
+ *
+ * @return void
+ */
+ fireTeardown: function (file) {
+ var eventName = 'teardown_' + AJAX.hash(file);
+ $(document).triggerHandler(eventName);
+ if (this._debug) {
+ console.log(
+ // no need to translate
+ "Fired event " + eventName + " for file " + file
+ );
+ }
+ },
+ /**
+ * Event handler for clicks on links and form submissions
+ *
+ * @param object e Event data
+ *
+ * @return void
+ */
+ requestHandler: function (event) {
+ // In some cases we don't want to handle the request here and either
+ // leave the browser deal with it natively (e.g: file download)
+ // or leave an existing ajax event handler present elsewhere deal with it
+ var href = $(this).attr('href');
+ if (typeof event != 'undefined' && (event.shiftKey || event.ctrlKey)) {
+ return true;
+ } else if ($(this).attr('target')) {
+ return true;
+ } else if ($(this).hasClass('ajax') || $(this).hasClass('disableAjax')) {
+ return true;
+ } else if (href && href.match(/^#/)) {
+ return true;
+ } else if (href && href.match(/^mailto/)) {
+ return true;
+ } else if ($(this).hasClass('ui-datepicker-next') ||
+ $(this).hasClass('ui-datepicker-prev')
+ ) {
+ return true;
+ }
+
+ if (typeof event != 'undefined') {
+ event.preventDefault();
+ event.stopImmediatePropagation();
+ }
+ if (AJAX.active === true) {
+ // Cancel the old request if abortable, when the user requests
+ // something else. Otherwise silently bail out, as there is already
+ // a request well in progress.
+ if (AJAX.xhr) {
+ //In case of a link request, attempt aborting
+ AJAX.xhr.abort();
+ if(AJAX.xhr.status === 0 && AJAX.xhr.statusText === 'abort') {
+ //If aborted
+ AJAX.$msgbox = PMA_ajaxShowMessage(PMA_messages.strAbortedRequest);
+ AJAX.active = false;
+ AJAX.xhr = null;
+ } else {
+ //If can't abort
+ return false;
+ }
+ } else {
+ //In case submitting a form, don't attempt aborting
+ return false;
+ }
+ }
+
+ AJAX.source = $(this);
+
+ $('html, body').animate({scrollTop: 0}, 'fast');
+
+ var isLink = !! href || false;
+ var url = isLink ? href : $(this).attr('action');
+ var params = 'ajax_request=true&ajax_page_request=true';
+ if (! isLink) {
+ params += '&' + $(this).serialize();
+ }
+ // Add a list of menu hashes that we have in the cache to the request
+ params += AJAX.cache.menus.getRequestParam();
+
+ if (AJAX._debug) {
+ console.log("Loading: " + url); // no need to translate
+ }
+
+ if (isLink) {
+ AJAX.active = true;
+ AJAX.$msgbox = PMA_ajaxShowMessage();
+ //Save reference for the new link request
+ AJAX.xhr = $.get(url, params, AJAX.responseHandler);
+ } else {
+ /**
+ * Manually fire the onsubmit event for the form, if any.
+ * The event was saved in the jQuery data object by an onload
+ * handler defined below. Workaround for bug #3583316
+ */
+ var onsubmit = $(this).data('onsubmit');
+ // Submit the request if there is no onsubmit handler
+ // or if it returns a value that evaluates to true
+ if (typeof onsubmit !== 'function' || onsubmit.apply(this, [event])) {
+ AJAX.active = true;
+ AJAX.$msgbox = PMA_ajaxShowMessage();
+ $.post(url, params, AJAX.responseHandler);
+ }
+ }
+ },
+ /**
+ * Called after the request that was initiated by this.requestHandler()
+ * has completed successfully or with a caught error. For completely
+ * failed requests or requests with uncaught errors, see the .ajaxError
+ * handler at the bottom of this file.
+ *
+ * To refer to self use 'AJAX', instead of 'this' as this function
+ * is called in the jQuery context.
+ *
+ * @param object e Event data
+ *
+ * @return void
+ */
+ responseHandler: function (data) {
+ if (data.success) {
+ $table_clone = false;
+ PMA_ajaxRemoveMessage(AJAX.$msgbox);
+
+ if (data._redirect) {
+ PMA_ajaxShowMessage(data._redirect, false);
+ AJAX.active = false;
+ AJAX.xhr = null;
+ return;
+ }
+
+ AJAX.scriptHandler.reset(function () {
+ if (data._reloadNavigation) {
+ PMA_reloadNavigation();
+ }
+ if (data._reloadQuerywindow) {
+ var params = data._reloadQuerywindow;
+ PMA_querywindow.reload(
+ params.db,
+ params.table,
+ params.sql_query
+ );
+ }
+ if (data._focusQuerywindow) {
+ PMA_querywindow.focus(
+ data._focusQuerywindow
+ );
+ }
+ if (data._title) {
+ $('title').replaceWith(data._title);
+ }
+ if (data._menu) {
+ AJAX.cache.menus.replace(data._menu);
+ AJAX.cache.menus.add(data._menuHash, data._menu);
+ } else if (data._menuHash) {
+ AJAX.cache.menus.replace(AJAX.cache.menus.get(data._menuHash));
+ }
+
+ // Remove all containers that may have
+ // been added outside of #page_content
+ $('body').children()
+ .not('#pma_navigation')
+ .not('#floating_menubar')
+ .not('#goto_pagetop')
+ .not('#page_content')
+ .not('#selflink')
+ .not('#session_debug')
+ .not('#pma_header')
+ .not('#pma_footer')
+ .not('#pma_demo')
+ .remove();
+ // Replace #page_content with new content
+ if (data.message && data.message.length > 0) {
+ $('#page_content').replaceWith(
+ "<div id='page_content'>" + data.message + "</div>"
+ );
+ PMA_highlightSQL($('#page_content'));
+ }
+
+ if (data._selflink) {
+
+ var source = data._selflink.split('?')[0];
+ //Check for faulty links
+ if (source == "import.php") {
+ var replacement = "tbl_sql.php";
+ data._selflink = data._selflink.replace(source,replacement);
+ }
+ $('#selflink > a').attr('href', data._selflink);
+ }
+ if (data._scripts) {
+ AJAX.scriptHandler.load(data._scripts, data._params.token);
+ }
+ if (data._selflink && data._scripts && data._menuHash && data._params) {
+ AJAX.cache.add(
+ data._selflink,
+ data._scripts,
+ data._menuHash,
+ data._params,
+ AJAX.source.attr('rel')
+ );
+ }
+ if (data._params) {
+ PMA_commonParams.setAll(data._params);
+ }
+ if (data._displayMessage) {
+ $('#page_content').prepend(data._displayMessage);
+ PMA_highlightSQL($('#page_content'));
+ }
+
+ $('#pma_errors').remove();
+ if (data._errors) {
+ $('<div/>', {id : 'pma_errors'})
+ .insertAfter('#selflink')
+ .append(data._errors);
+ }
+
+ if (typeof AJAX._callback === 'function') {
+ AJAX._callback.call();
+ }
+ AJAX._callback = function () {};
+ });
+ } else {
+ PMA_ajaxShowMessage(data.error, false);
+ AJAX.active = false;
+ AJAX.xhr = null;
+ }
+ },
+ /**
+ * This object is in charge of downloading scripts,
+ * keeping track of what's downloaded and firing
+ * the onload event for them when the page is ready.
+ */
+ scriptHandler: {
+ /**
+ * @var array _scripts The list of files already downloaded
+ */
+ _scripts: [],
+ /**
+ * @var array _scriptsToBeLoaded The list of files that
+ * need to be downloaded
+ */
+ _scriptsToBeLoaded: [],
+ /**
+ * @var array _scriptsToBeFired The list of files for which
+ * to fire the onload event
+ */
+ _scriptsToBeFired: [],
+ /**
+ * Records that a file has been downloaded
+ *
+ * @param string file The filename
+ * @param string fire Whether this file will be registering
+ * onload/teardown events
+ *
+ * @return self For chaining
+ */
+ add: function (file, fire) {
+ this._scripts.push(file);
+ if (fire) {
+ // Record whether to fire any events for the file
+ // This is necessary to correctly tear down the initial page
+ this._scriptsToBeFired.push(file);
+ }
+ return this;
+ },
+ /**
+ * Download a list of js files in one request
+ *
+ * @param array files An array of filenames and flags
+ *
+ * @return void
+ */
+ load: function (files, token) {
+ var self = this;
+ self._scriptsToBeLoaded = [];
+ self._scriptsToBeFired = [];
+ for (var i in files) {
+ self._scriptsToBeLoaded.push(files[i].name);
+ if (files[i].fire) {
+ self._scriptsToBeFired.push(files[i].name);
+ }
+ }
+ // Generate a request string
+ var request = [];
+ var needRequest = false;
+ for (var index in self._scriptsToBeLoaded) {
+ var script = self._scriptsToBeLoaded[index];
+ // Only for scripts that we don't already have
+ if ($.inArray(script, self._scripts) == -1) {
+ needRequest = true;
+ this.add(script);
+ request.push("scripts[]=" + script);
+ }
+ }
+ request.push("token=" + token);
+ request.push("call_done=1");
+ // Download the composite js file, if necessary
+ if (needRequest) {
+ this.appendScript("js/get_scripts.js.php?" + request.join("&"));
+ } else {
+ self.done();
+ }
+ },
+ /**
+ * Called whenever all files are loaded
+ *
+ * @return void
+ */
+ done: function () {
+ if (typeof ErrorReport !== 'undefined') {
+ ErrorReport.wrap_global_functions();
+ }
+ for (var i in this._scriptsToBeFired) {
+ AJAX.fireOnload(this._scriptsToBeFired[i]);
+ }
+ AJAX.active = false;
+ },
+ /**
+ * Appends a script element to the head to load the scripts
+ *
+ * @return void
+ */
+ appendScript: function (url) {
+ var head = document.head || document.getElementsByTagName('head')[0];
+ var script = document.createElement('script');
+ script.type = 'text/javascript';
+ script.src = url;
+ head.appendChild(script);
+ },
+ /**
+ * Fires all the teardown event handlers for the current page
+ * and rebinds all forms and links to the request handler
+ *
+ * @param function callback The callback to call after resetting
+ *
+ * @return void
+ */
+ reset: function (callback) {
+ for (var i in this._scriptsToBeFired) {
+ AJAX.fireTeardown(this._scriptsToBeFired[i]);
+ }
+ this._scriptsToBeFired = [];
+ /**
+ * Re-attach a generic event handler to clicks
+ * on pages and submissions of forms
+ */
+ $('a').die('click').live('click', AJAX.requestHandler);
+ $('form').die('submit').live('submit', AJAX.requestHandler);
+ AJAX.cache.update();
+ callback();
+ }
+ }
+};
+
+/**
+ * Here we register a function that will remove the onsubmit event from all
+ * forms that will be handled by the generic page loader. We then save this
+ * event handler in the "jQuery data", so that we can fire it up later in
+ * AJAX.requestHandler().
+ *
+ * See bug #3583316
+ */
+AJAX.registerOnload('functions.js', function () {
+ // Registering the onload event for functions.js
+ // ensures that it will be fired for all pages
+ $('form').not('.ajax').not('.disableAjax').each(function () {
+ if ($(this).attr('onsubmit')) {
+ $(this).data('onsubmit', this.onsubmit).attr('onsubmit', '');
+ }
+ });
+});
+
+/**
+ * An implementation of a client-side page cache.
+ * This object also uses the cache to provide a simple microhistory,
+ * that is the ability to use the back and forward buttons in the browser
+ */
+AJAX.cache = {
+ /**
+ * @var int The maximum number of pages to keep in the cache
+ */
+ MAX: 6,
+ /**
+ * @var object A hash used to prime the cache with data about the initially
+ * loaded page. This is set in the footer, and then loaded
+ * by a double-queued event further down this file.
+ */
+ primer: {},
+ /**
+ * @var array Stores the content of the cached pages
+ */
+ pages: [],
+ /**
+ * @var int The index of the currently loaded page
+ * This is used to know at which point in the history we are
+ */
+ current: 0,
+ /**
+ * Saves a new page in the cache
+ *
+ * @param string hash The hash part of the url that is being loaded
+ * @param array scripts A list of scripts that is requured for the page
+ * @param string menu A hash that links to a menu stored
+ * in a dedicated menu cache
+ * @param array params A list of parameters used by PMA_commonParams()
+ * @param string rel A relationship to the current page:
+ * 'samepage': Forces the response to be treated as
+ * the same page as the current one
+ * 'newpage': Forces the response to be treated as
+ * a new page
+ * undefined: Default behaviour, 'samepage' if the
+ * selflinks of the two pages are the same.
+ * 'newpage' otherwise
+ *
+ * @return void
+ */
+ add: function (hash, scripts, menu, params, rel) {
+ if (this.pages.length > AJAX.cache.MAX) {
+ // Trim the cache, to the maximum number of allowed entries
+ // This way we will have a cached menu for every page
+ for (var i = 0; i < this.pages.length - this.MAX; i++) {
+ delete this.pages[i];
+ }
+ }
+ while (this.current < this.pages.length) {
+ // trim the cache if we went back in the history
+ // and are now going forward again
+ this.pages.pop();
+ }
+ if (rel === 'newpage' ||
+ (
+ typeof rel === 'undefined' && (
+ typeof this.pages[this.current - 1] === 'undefined' ||
+ this.pages[this.current - 1].hash !== hash
+ )
+ )
+ ) {
+ this.pages.push({
+ hash: hash,
+ content: $('#page_content').html(),
+ scripts: scripts,
+ selflink: $('#selflink').html(),
+ menu: menu,
+ params: params
+ });
+ AJAX.setUrlHash(this.current, hash);
+ this.current++;
+ }
+ },
+ /**
+ * Restores a page from the cache. This is called when the hash
+ * part of the url changes and it's structure appears to be valid
+ *
+ * @param string index Which page from the history to load
+ *
+ * @return void
+ */
+ navigate: function (index) {
+ if (typeof this.pages[index] === 'undefined'
+ || typeof this.pages[index].content === 'undefined'
+ || typeof this.pages[index].menu === 'undefined'
+ || ! AJAX.cache.menus.get(this.pages[index].menu)
+ ) {
+ PMA_ajaxShowMessage(
+ '<div class="error">' + PMA_messages.strInvalidPage + '</div>',
+ false
+ );
+ } else {
+ AJAX.active = true;
+ var record = this.pages[index];
+ AJAX.scriptHandler.reset(function () {
+ $('#page_content').html(record.content);
+ $('#selflink').html(record.selflink);
+ AJAX.cache.menus.replace(AJAX.cache.menus.get(record.menu));
+ PMA_commonParams.setAll(record.params);
+ AJAX.scriptHandler.load(record.scripts, record.params ? record.params.token : PMA_commonParams.get('token'));
+ AJAX.cache.current = ++index;
+ });
+ }
+ },
+ /**
+ * Resaves the content of the current page in the cache.
+ * Necessary in order not to show the user some outdated version of the page
+ *
+ * @return void
+ */
+ update: function () {
+ var page = this.pages[this.current - 1];
+ if (page) {
+ page.content = $('#page_content').html();
+ }
+ },
+ /**
+ * @var object Dedicated menu cache
+ */
+ menus: {
+ /**
+ * Returns the number of items in an associative array
+ *
+ * @return int
+ */
+ size: function (obj) {
+ var size = 0, key;
+ for (key in obj) {
+ if (obj.hasOwnProperty(key)) {
+ size++;
+ }
+ }
+ return size;
+ },
+ /**
+ * @var hash Stores the content of the cached menus
+ */
+ data: {},
+ /**
+ * Saves a new menu in the cache
+ *
+ * @param string hash The hash (trimmed md5) of the menu to be saved
+ * @param string content The HTML code of the menu to be saved
+ *
+ * @return void
+ */
+ add: function (hash, content) {
+ if (this.size(this.data) > AJAX.cache.MAX) {
+ // when the cache grows, we remove the oldest entry
+ var oldest, key, init = 0;
+ for (var i in this.data) {
+ if (this.data[i]) {
+ if (! init || this.data[i].timestamp.getTime() < oldest.getTime()) {
+ oldest = this.data[i].timestamp;
+ key = i;
+ init = 1;
+ }
+ }
+ }
+ delete this.data[key];
+ }
+ this.data[hash] = {
+ content: content,
+ timestamp: new Date()
+ };
+ },
+ /**
+ * Retrieves a menu given its hash
+ *
+ * @param string hash The hash of the menu to be retrieved
+ *
+ * @return string
+ */
+ get: function (hash) {
+ if (this.data[hash]) {
+ return this.data[hash].content;
+ } else {
+ // This should never happen as long as the number of stored menus
+ // is larger or equal to the number of pages in the page cache
+ return '';
+ }
+ },
+ /**
+ * Prepares part of the parameter string used during page requests,
+ * this is necessary to tell the server which menus we have in the cache
+ *
+ * @return string
+ */
+ getRequestParam: function () {
+ var param = '';
+ var menuHashes = [];
+ for (var i in this.data) {
+ menuHashes.push(i);
+ }
+ var menuHashesParam = menuHashes.join('-');
+ if (menuHashesParam) {
+ param = '&menuHashes=' + menuHashesParam;
+ }
+ return param;
+ },
+ /**
+ * Replaces the menu with new content
+ *
+ * @return void
+ */
+ replace: function (content) {
+ $('#floating_menubar').html(content)
+ // Remove duplicate wrapper
+ // TODO: don't send it in the response
+ .children().first().remove();
+ $('#topmenu').menuResizer(PMA_mainMenuResizerCallback);
+ }
+ }
+};
+
+/**
+ * URL hash management module.
+ * Allows direct bookmarking and microhistory.
+ */
+AJAX.setUrlHash = (function (jQuery, window) {
+ "use strict";
+ /**
+ * Indictaes whether we have already completed
+ * the initialisation of the hash
+ *
+ * @access private
+ */
+ var ready = false;
+ /**
+ * Stores a hash that needed to be set when we were not ready
+ *
+ * @access private
+ */
+ var savedHash = "";
+ /**
+ * Flag to indicate if the change of hash was triggered
+ * by a user pressing the back/forward button or if
+ * the change was triggered internally
+ *
+ * @access private
+ */
+ var userChange = true;
+
+ // Fix favicon disappearing in Firefox when setting location.hash
+ function resetFavicon() {
+ if (jQuery.browser.mozilla) {
+ // Move the link tags for the favicon to the bottom
+ // of the head element to force a reload of the favicon
+ $('head > link[href=favicon\\.ico]').appendTo('head');
+ }
+ }
+
+ /**
+ * Sets the hash part of the URL
+ *
+ * @access public
+ */
+ function setUrlHash(index, hash) {
+ /*
+ * Known problem:
+ * Setting hash leads to reload in webkit:
+ * http://www.quirksmode.org/bugreports/archives/2005/05/Safari_13_visual_anomaly_with_windowlocationhref.html
+ *
+ * so we expect that users are not running an ancient Safari version
+ */
+
+ userChange = false;
+ if (ready) {
+ window.location.hash = "PMAURL-" + index + ":" + hash;
+ resetFavicon();
+ } else {
+ savedHash = "PMAURL-" + index + ":" + hash;
+ }
+ }
+ /**
+ * Start initialisation
+ */
+ if (window.location.hash.substring(0, 8) == '#PMAURL-') {
+ // We have a valid hash, let's redirect the user
+ // to the page that it's pointing to
+ window.location = window.location.hash.substring(
+ window.location.hash.indexOf(':') + 1
+ );
+ } else {
+ // We don't have a valid hash, so we'll set it up
+ // when the page finishes loading
+ jQuery(function () {
+ /* Check if we should set URL */
+ if (savedHash !== "") {
+ window.location.hash = savedHash;
+ savedHash = "";
+ resetFavicon();
+ }
+ // Indicate that we're done initialising
+ ready = true;
+ });
+ }
+ /**
+ * Register an event handler for when the url hash changes
+ */
+ jQuery(function () {
+ jQuery(window).hashchange(function () {
+ if (userChange === false) {
+ // Ignore internally triggered hash changes
+ userChange = true;
+ } else if (/^#PMAURL-\d+:/.test(window.location.hash)) {
+ // Change page if the hash changed was triggered by a user action
+ var index = window.location.hash.substring(
+ 8, window.location.hash.indexOf(':')
+ );
+ AJAX.cache.navigate(index);
+ }
+ });
+ });
+ /**
+ * Publicly exposes a reference to the otherwise private setUrlHash function
+ */
+ return setUrlHash;
+})(jQuery, window);
+
+/**
+ * Page load event handler
+ */
+$(function () {
+ // Add the menu from the initial page into the cache
+ // The cache primer is set by the footer class
+ if (AJAX.cache.primer.url) {
+ AJAX.cache.menus.add(
+ AJAX.cache.primer.menuHash,
+ $('<div></div>')
+ .append('<div></div>')
+ .append($('#serverinfo').clone())
+ .append($('#topmenucontainer').clone())
+ .html()
+ );
+ }
+ $(function () {
+ // Queue up this event twice to make sure that we get a copy
+ // of the page after all other onload events have been fired
+ if (AJAX.cache.primer.url) {
+ AJAX.cache.add(
+ AJAX.cache.primer.url,
+ AJAX.cache.primer.scripts,
+ AJAX.cache.primer.menuHash
+ );
+ }
+ });
+});
+
+/**
+ * Attach a generic event handler to clicks
+ * on pages and submissions of forms
+ */
+$('a').live('click', AJAX.requestHandler);
+$('form').live('submit', AJAX.requestHandler);
+
+/**
+ * Gracefully handle fatal server errors
+ * (e.g: 500 - Internal server error)
+ */
+$(document).ajaxError(function (event, request, settings) {
+ if (request.status !== 0) { // Don't handle aborted requests
+ var errorCode = $.sprintf(PMA_messages.strErrorCode, request.status);
+ var errorText = $.sprintf(PMA_messages.strErrorText, request.statusText);
+ PMA_ajaxShowMessage(
+ '<div class="error">' +
+ PMA_messages.strErrorProcessingRequest +
+ '<div>' + errorCode + '</div>' +
+ '<div>' + errorText + '</div>' +
+ '</div>',
+ false
+ );
+ AJAX.active = false;
+ }
+});
diff --git a/js/canvg/MIT-LICENSE.txt b/js/canvg/MIT-LICENSE.txt
new file mode 100644
index 0000000000..40f19bd70a
--- /dev/null
+++ b/js/canvg/MIT-LICENSE.txt
@@ -0,0 +1,22 @@
+Copyright (c) 2010-2011 Gabe Lerner (gabelerner@gmail.com) - http://code.google.com/p/canvg/
+
+ Permission is hereby granted, free of charge, to any person
+ obtaining a copy of this software and associated documentation
+ files (the "Software"), to deal in the Software without
+ restriction, including without limitation the rights to use,
+ copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the
+ Software is furnished to do so, subject to the following
+ conditions:
+
+ The above copyright notice and this permission notice shall be
+ included in all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file
diff --git a/js/canvg/canvg.js b/js/canvg/canvg.js
new file mode 100644
index 0000000000..dbc5a9dd01
--- /dev/null
+++ b/js/canvg/canvg.js
@@ -0,0 +1,2509 @@
+/*
+ * canvg.js - Javascript SVG parser and renderer on Canvas
+ * MIT Licensed
+ * Gabe Lerner (gabelerner@gmail.com)
+ * http://code.google.com/p/canvg/
+ *
+ * Requires: rgbcolor.js - http://www.phpied.com/rgb-color-parser-in-javascript/
+ */
+if(!window.console) {
+ window.console = {};
+ window.console.log = function(str) {};
+ window.console.dir = function(str) {};
+}
+
+// <3 IE
+if(!Array.indexOf){
+ Array.prototype.indexOf = function(obj){
+ for(var i=0; i<this.length; i++){
+ if(this[i]==obj){
+ return i;
+ }
+ }
+ return -1;
+ }
+}
+
+(function(){
+ // canvg(target, s)
+ // empty parameters: replace all 'svg' elements on page with 'canvas' elements
+ // target: canvas element or the id of a canvas element
+ // s: svg string or url to svg file
+ // opts: optional hash of options
+ // ignoreMouse: true => ignore mouse events
+ // ignoreAnimation: true => ignore animations
+ // ignoreDimensions: true => does not try to resize canvas
+ // ignoreClear: true => does not clear canvas
+ // offsetX: int => draws at a x offset
+ // offsetY: int => draws at a y offset
+ // scaleWidth: int => scales horizontally to width
+ // scaleHeight: int => scales vertically to height
+ // renderCallback: function => will call the function after the first render is completed
+ // forceRedraw: function => will call the function on every frame, if it returns true, will redraw
+ this.canvg = function (target, s, opts) {
+ // no parameters
+ if (target == null && s == null && opts == null) {
+ var svgTags = document.getElementsByTagName('svg');
+ for (var i=0; i<svgTags.length; i++) {
+ var svgTag = svgTags[i];
+ var c = document.createElement('canvas');
+ c.width = svgTag.clientWidth;
+ c.height = svgTag.clientHeight;
+ svgTag.parentNode.insertBefore(c, svgTag);
+ svgTag.parentNode.removeChild(svgTag);
+ var div = document.createElement('div');
+ div.appendChild(svgTag);
+ canvg(c, div.innerHTML);
+ }
+ return;
+ }
+
+ if (typeof target == 'string') {
+ target = document.getElementById(target);
+ }
+
+ // reuse class per canvas
+ var svg;
+ if (target.svg == null) {
+ svg = build();
+ target.svg = svg;
+ }
+ else {
+ svg = target.svg;
+ svg.stop();
+ }
+ svg.opts = opts;
+
+ var ctx = target.getContext('2d');
+ if (s.substr(0,1) == '<') {
+ // load from xml string
+ svg.loadXml(ctx, s);
+ }
+ else {
+ // load from url
+ svg.load(ctx, s);
+ }
+ }
+
+ function build() {
+ var svg = { };
+
+ svg.FRAMERATE = 30;
+
+ // globals
+ svg.init = function(ctx) {
+ svg.Definitions = {};
+ svg.Styles = {};
+ svg.Animations = [];
+ svg.Images = [];
+ svg.ctx = ctx;
+ svg.ViewPort = new (function () {
+ this.viewPorts = [];
+ this.SetCurrent = function(width, height) { this.viewPorts.push({ width: width, height: height }); }
+ this.RemoveCurrent = function() { this.viewPorts.pop(); }
+ this.Current = function() { return this.viewPorts[this.viewPorts.length - 1]; }
+ this.width = function() { return this.Current().width; }
+ this.height = function() { return this.Current().height; }
+ this.ComputeSize = function(d) {
+ if (d != null && typeof(d) == 'number') return d;
+ if (d == 'x') return this.width();
+ if (d == 'y') return this.height();
+ return Math.sqrt(Math.pow(this.width(), 2) + Math.pow(this.height(), 2)) / Math.sqrt(2);
+ }
+ });
+ }
+ svg.init();
+
+ // images loaded
+ svg.ImagesLoaded = function() {
+ for (var i=0; i<svg.Images.length; i++) {
+ if (!svg.Images[i].loaded) return false;
+ }
+ return true;
+ }
+
+ // trim
+ svg.trim = function(s) { return s.replace(/^\s+|\s+$/g, ''); }
+
+ // compress spaces
+ svg.compressSpaces = function(s) { return s.replace(/[\s\r\t\n]+/gm,' '); }
+
+ // ajax
+ svg.ajax = function(url) {
+ var AJAX;
+ if(window.XMLHttpRequest){AJAX=new XMLHttpRequest();}
+ else{AJAX=new ActiveXObject('Microsoft.XMLHTTP');}
+ if(AJAX){
+ AJAX.open('GET',url,false);
+ AJAX.send(null);
+ return AJAX.responseText;
+ }
+ return null;
+ }
+
+ // parse xml
+ svg.parseXml = function(xml) {
+ if (window.DOMParser)
+ {
+ var parser = new DOMParser();
+ return parser.parseFromString(xml, 'text/xml');
+ }
+ else
+ {
+ xml = xml.replace(/<!DOCTYPE svg[^>]*>/, '');
+ var xmlDoc = new ActiveXObject('Microsoft.XMLDOM');
+ xmlDoc.async = 'false';
+ xmlDoc.loadXML(xml);
+ return xmlDoc;
+ }
+ }
+
+ svg.Property = function(name, value) {
+ this.name = name;
+ this.value = value;
+
+ this.hasValue = function() {
+ return (this.value != null && this.value != '');
+ }
+
+ // return the numerical value of the property
+ this.numValue = function() {
+ if (!this.hasValue()) return 0;
+
+ var n = parseFloat(this.value);
+ if ((this.value + '').match(/%$/)) {
+ n = n / 100.0;
+ }
+ return n;
+ }
+
+ this.valueOrDefault = function(def) {
+ if (this.hasValue()) return this.value;
+ return def;
+ }
+
+ this.numValueOrDefault = function(def) {
+ if (this.hasValue()) return this.numValue();
+ return def;
+ }
+
+ /* EXTENSIONS */
+ var that = this;
+
+ // color extensions
+ this.Color = {
+ // augment the current color value with the opacity
+ addOpacity: function(opacity) {
+ var newValue = that.value;
+ if (opacity != null && opacity != '') {
+ var color = new RGBColor(that.value);
+ if (color.ok) {
+ newValue = 'rgba(' + color.r + ', ' + color.g + ', ' + color.b + ', ' + opacity + ')';
+ }
+ }
+ return new svg.Property(that.name, newValue);
+ }
+ }
+
+ // definition extensions
+ this.Definition = {
+ // get the definition from the definitions table
+ getDefinition: function() {
+ var name = that.value.replace(/^(url\()?#([^\)]+)\)?$/, '$2');
+ return svg.Definitions[name];
+ },
+
+ isUrl: function() {
+ return that.value.indexOf('url(') == 0
+ },
+
+ getFillStyle: function(e) {
+ var def = this.getDefinition();
+
+ // gradient
+ if (def != null && def.createGradient) {
+ return def.createGradient(svg.ctx, e);
+ }
+
+ // pattern
+ if (def != null && def.createPattern) {
+ return def.createPattern(svg.ctx, e);
+ }
+
+ return null;
+ }
+ }
+
+ // length extensions
+ this.Length = {
+ DPI: function(viewPort) {
+ return 96.0; // TODO: compute?
+ },
+
+ EM: function(viewPort) {
+ var em = 12;
+
+ var fontSize = new svg.Property('fontSize', svg.Font.Parse(svg.ctx.font).fontSize);
+ if (fontSize.hasValue()) em = fontSize.Length.toPixels(viewPort);
+
+ return em;
+ },
+
+ // get the length as pixels
+ toPixels: function(viewPort) {
+ if (!that.hasValue()) return 0;
+ var s = that.value+'';
+ if (s.match(/em$/)) return that.numValue() * this.EM(viewPort);
+ if (s.match(/ex$/)) return that.numValue() * this.EM(viewPort) / 2.0;
+ if (s.match(/px$/)) return that.numValue();
+ if (s.match(/pt$/)) return that.numValue() * 1.25;
+ if (s.match(/pc$/)) return that.numValue() * 15;
+ if (s.match(/cm$/)) return that.numValue() * this.DPI(viewPort) / 2.54;
+ if (s.match(/mm$/)) return that.numValue() * this.DPI(viewPort) / 25.4;
+ if (s.match(/in$/)) return that.numValue() * this.DPI(viewPort);
+ if (s.match(/%$/)) return that.numValue() * svg.ViewPort.ComputeSize(viewPort);
+ return that.numValue();
+ }
+ }
+
+ // time extensions
+ this.Time = {
+ // get the time as milliseconds
+ toMilliseconds: function() {
+ if (!that.hasValue()) return 0;
+ var s = that.value+'';
+ if (s.match(/s$/)) return that.numValue() * 1000;
+ if (s.match(/ms$/)) return that.numValue();
+ return that.numValue();
+ }
+ }
+
+ // angle extensions
+ this.Angle = {
+ // get the angle as radians
+ toRadians: function() {
+ if (!that.hasValue()) return 0;
+ var s = that.value+'';
+ if (s.match(/deg$/)) return that.numValue() * (Math.PI / 180.0);
+ if (s.match(/grad$/)) return that.numValue() * (Math.PI / 200.0);
+ if (s.match(/rad$/)) return that.numValue();
+ return that.numValue() * (Math.PI / 180.0);
+ }
+ }
+ }
+
+ // fonts
+ svg.Font = new (function() {
+ this.Styles = ['normal','italic','oblique','inherit'];
+ this.Variants = ['normal','small-caps','inherit'];
+ this.Weights = ['normal','bold','bolder','lighter','100','200','300','400','500','600','700','800','900','inherit'];
+
+ this.CreateFont = function(fontStyle, fontVariant, fontWeight, fontSize, fontFamily, inherit) {
+ var f = inherit != null ? this.Parse(inherit) : this.CreateFont('', '', '', '', '', svg.ctx.font);
+ return {
+ fontFamily: fontFamily || f.fontFamily,
+ fontSize: fontSize || f.fontSize,
+ fontStyle: fontStyle || f.fontStyle,
+ fontWeight: fontWeight || f.fontWeight,
+ fontVariant: fontVariant || f.fontVariant,
+ toString: function () { return [this.fontStyle, this.fontVariant, this.fontWeight, this.fontSize, this.fontFamily].join(' ') }
+ }
+ }
+
+ var that = this;
+ this.Parse = function(s) {
+ var f = {};
+ var d = svg.trim(svg.compressSpaces(s || '')).split(' ');
+ var set = { fontSize: false, fontStyle: false, fontWeight: false, fontVariant: false }
+ var ff = '';
+ for (var i=0; i<d.length; i++) {
+ if (!set.fontStyle && that.Styles.indexOf(d[i]) != -1) { if (d[i] != 'inherit') f.fontStyle = d[i]; set.fontStyle = true; }
+ else if (!set.fontVariant && that.Variants.indexOf(d[i]) != -1) { if (d[i] != 'inherit') f.fontVariant = d[i]; set.fontStyle = set.fontVariant = true; }
+ else if (!set.fontWeight && that.Weights.indexOf(d[i]) != -1) { if (d[i] != 'inherit') f.fontWeight = d[i]; set.fontStyle = set.fontVariant = set.fontWeight = true; }
+ else if (!set.fontSize) { if (d[i] != 'inherit') f.fontSize = d[i].split('/')[0]; set.fontStyle = set.fontVariant = set.fontWeight = set.fontSize = true; }
+ else { if (d[i] != 'inherit') ff += d[i]; }
+ } if (ff != '') f.fontFamily = ff;
+ return f;
+ }
+ });
+
+ // points and paths
+ svg.ToNumberArray = function(s) {
+ var a = svg.trim(svg.compressSpaces((s || '').replace(/,/g, ' '))).split(' ');
+ for (var i=0; i<a.length; i++) {
+ a[i] = parseFloat(a[i]);
+ }
+ return a;
+ }
+ svg.Point = function(x, y) {
+ this.x = x;
+ this.y = y;
+
+ this.angleTo = function(p) {
+ return Math.atan2(p.y - this.y, p.x - this.x);
+ }
+
+ this.applyTransform = function(v) {
+ var xp = this.x * v[0] + this.y * v[2] + v[4];
+ var yp = this.x * v[1] + this.y * v[3] + v[5];
+ this.x = xp;
+ this.y = yp;
+ }
+ }
+ svg.CreatePoint = function(s) {
+ var a = svg.ToNumberArray(s);
+ return new svg.Point(a[0], a[1]);
+ }
+ svg.CreatePath = function(s) {
+ var a = svg.ToNumberArray(s);
+ var path = [];
+ for (var i=0; i<a.length; i+=2) {
+ path.push(new svg.Point(a[i], a[i+1]));
+ }
+ return path;
+ }
+
+ // bounding box
+ svg.BoundingBox = function(x1, y1, x2, y2) { // pass in initial points if you want
+ this.x1 = Number.NaN;
+ this.y1 = Number.NaN;
+ this.x2 = Number.NaN;
+ this.y2 = Number.NaN;
+
+ this.x = function() { return this.x1; }
+ this.y = function() { return this.y1; }
+ this.width = function() { return this.x2 - this.x1; }
+ this.height = function() { return this.y2 - this.y1; }
+
+ this.addPoint = function(x, y) {
+ if (x != null) {
+ if (isNaN(this.x1) || isNaN(this.x2)) {
+ this.x1 = x;
+ this.x2 = x;
+ }
+ if (x < this.x1) this.x1 = x;
+ if (x > this.x2) this.x2 = x;
+ }
+
+ if (y != null) {
+ if (isNaN(this.y1) || isNaN(this.y2)) {
+ this.y1 = y;
+ this.y2 = y;
+ }
+ if (y < this.y1) this.y1 = y;
+ if (y > this.y2) this.y2 = y;
+ }
+ }
+ this.addX = function(x) { this.addPoint(x, null); }
+ this.addY = function(y) { this.addPoint(null, y); }
+
+ this.addBoundingBox = function(bb) {
+ this.addPoint(bb.x1, bb.y1);
+ this.addPoint(bb.x2, bb.y2);
+ }
+
+ this.addQuadraticCurve = function(p0x, p0y, p1x, p1y, p2x, p2y) {
+ var cp1x = p0x + 2/3 * (p1x - p0x); // CP1 = QP0 + 2/3 *(QP1-QP0)
+ var cp1y = p0y + 2/3 * (p1y - p0y); // CP1 = QP0 + 2/3 *(QP1-QP0)
+ var cp2x = cp1x + 1/3 * (p2x - p0x); // CP2 = CP1 + 1/3 *(QP2-QP0)
+ var cp2y = cp1y + 1/3 * (p2y - p0y); // CP2 = CP1 + 1/3 *(QP2-QP0)
+ this.addBezierCurve(p0x, p0y, cp1x, cp2x, cp1y, cp2y, p2x, p2y);
+ }
+
+ this.addBezierCurve = function(p0x, p0y, p1x, p1y, p2x, p2y, p3x, p3y) {
+ // from http://blog.hackers-cafe.net/2009/06/how-to-calculate-bezier-curves-bounding.html
+ var p0 = [p0x, p0y], p1 = [p1x, p1y], p2 = [p2x, p2y], p3 = [p3x, p3y];
+ this.addPoint(p0[0], p0[1]);
+ this.addPoint(p3[0], p3[1]);
+
+ for (i=0; i<=1; i++) {
+ var f = function(t) {
+ return Math.pow(1-t, 3) * p0[i]
+ + 3 * Math.pow(1-t, 2) * t * p1[i]
+ + 3 * (1-t) * Math.pow(t, 2) * p2[i]
+ + Math.pow(t, 3) * p3[i];
+ }
+
+ var b = 6 * p0[i] - 12 * p1[i] + 6 * p2[i];
+ var a = -3 * p0[i] + 9 * p1[i] - 9 * p2[i] + 3 * p3[i];
+ var c = 3 * p1[i] - 3 * p0[i];
+
+ if (a == 0) {
+ if (b == 0) continue;
+ var t = -c / b;
+ if (0 < t && t < 1) {
+ if (i == 0) this.addX(f(t));
+ if (i == 1) this.addY(f(t));
+ }
+ continue;
+ }
+
+ var b2ac = Math.pow(b, 2) - 4 * c * a;
+ if (b2ac < 0) continue;
+ var t1 = (-b + Math.sqrt(b2ac)) / (2 * a);
+ if (0 < t1 && t1 < 1) {
+ if (i == 0) this.addX(f(t1));
+ if (i == 1) this.addY(f(t1));
+ }
+ var t2 = (-b - Math.sqrt(b2ac)) / (2 * a);
+ if (0 < t2 && t2 < 1) {
+ if (i == 0) this.addX(f(t2));
+ if (i == 1) this.addY(f(t2));
+ }
+ }
+ }
+
+ this.isPointInBox = function(x, y) {
+ return (this.x1 <= x && x <= this.x2 && this.y1 <= y && y <= this.y2);
+ }
+
+ this.addPoint(x1, y1);
+ this.addPoint(x2, y2);
+ }
+
+ // transforms
+ svg.Transform = function(v) {
+ var that = this;
+ this.Type = {}
+
+ // translate
+ this.Type.translate = function(s) {
+ this.p = svg.CreatePoint(s);
+ this.apply = function(ctx) {
+ ctx.translate(this.p.x || 0.0, this.p.y || 0.0);
+ }
+ this.applyToPoint = function(p) {
+ p.applyTransform([1, 0, 0, 1, this.p.x || 0.0, this.p.y || 0.0]);
+ }
+ }
+
+ // rotate
+ this.Type.rotate = function(s) {
+ var a = svg.ToNumberArray(s);
+ this.angle = new svg.Property('angle', a[0]);
+ this.cx = a[1] || 0;
+ this.cy = a[2] || 0;
+ this.apply = function(ctx) {
+ ctx.translate(this.cx, this.cy);
+ ctx.rotate(this.angle.Angle.toRadians());
+ ctx.translate(-this.cx, -this.cy);
+ }
+ this.applyToPoint = function(p) {
+ var a = this.angle.Angle.toRadians();
+ p.applyTransform([1, 0, 0, 1, this.p.x || 0.0, this.p.y || 0.0]);
+ p.applyTransform([Math.cos(a), Math.sin(a), -Math.sin(a), Math.cos(a), 0, 0]);
+ p.applyTransform([1, 0, 0, 1, -this.p.x || 0.0, -this.p.y || 0.0]);
+ }
+ }
+
+ this.Type.scale = function(s) {
+ this.p = svg.CreatePoint(s);
+ this.apply = function(ctx) {
+ ctx.scale(this.p.x || 1.0, this.p.y || this.p.x || 1.0);
+ }
+ this.applyToPoint = function(p) {
+ p.applyTransform([this.p.x || 0.0, 0, 0, this.p.y || 0.0, 0, 0]);
+ }
+ }
+
+ this.Type.matrix = function(s) {
+ this.m = svg.ToNumberArray(s);
+ this.apply = function(ctx) {
+ ctx.transform(this.m[0], this.m[1], this.m[2], this.m[3], this.m[4], this.m[5]);
+ }
+ this.applyToPoint = function(p) {
+ p.applyTransform(this.m);
+ }
+ }
+
+ this.Type.SkewBase = function(s) {
+ this.base = that.Type.matrix;
+ this.base(s);
+ this.angle = new svg.Property('angle', s);
+ }
+ this.Type.SkewBase.prototype = new this.Type.matrix;
+
+ this.Type.skewX = function(s) {
+ this.base = that.Type.SkewBase;
+ this.base(s);
+ this.m = [1, 0, Math.tan(this.angle.Angle.toRadians()), 1, 0, 0];
+ }
+ this.Type.skewX.prototype = new this.Type.SkewBase;
+
+ this.Type.skewY = function(s) {
+ this.base = that.Type.SkewBase;
+ this.base(s);
+ this.m = [1, Math.tan(this.angle.Angle.toRadians()), 0, 1, 0, 0];
+ }
+ this.Type.skewY.prototype = new this.Type.SkewBase;
+
+ this.transforms = [];
+
+ this.apply = function(ctx) {
+ for (var i=0; i<this.transforms.length; i++) {
+ this.transforms[i].apply(ctx);
+ }
+ }
+
+ this.applyToPoint = function(p) {
+ for (var i=0; i<this.transforms.length; i++) {
+ this.transforms[i].applyToPoint(p);
+ }
+ }
+
+ var data = v.split(/\s(?=[a-z])/);
+ for (var i=0; i<data.length; i++) {
+ var type = data[i].split('(')[0];
+ var s = data[i].split('(')[1].replace(')','');
+ var transform = new this.Type[type](s);
+ this.transforms.push(transform);
+ }
+ }
+
+ // aspect ratio
+ svg.AspectRatio = function(ctx, aspectRatio, width, desiredWidth, height, desiredHeight, minX, minY, refX, refY) {
+ // aspect ratio - http://www.w3.org/TR/SVG/coords.html#PreserveAspectRatioAttribute
+ aspectRatio = svg.compressSpaces(aspectRatio);
+ aspectRatio = aspectRatio.replace(/^defer\s/,''); // ignore defer
+ var align = aspectRatio.split(' ')[0] || 'xMidYMid';
+ var meetOrSlice = aspectRatio.split(' ')[1] || 'meet';
+
+ // calculate scale
+ var scaleX = width / desiredWidth;
+ var scaleY = height / desiredHeight;
+ var scaleMin = Math.min(scaleX, scaleY);
+ var scaleMax = Math.max(scaleX, scaleY);
+ if (meetOrSlice == 'meet') { desiredWidth *= scaleMin; desiredHeight *= scaleMin; }
+ if (meetOrSlice == 'slice') { desiredWidth *= scaleMax; desiredHeight *= scaleMax; }
+
+ refX = new svg.Property('refX', refX);
+ refY = new svg.Property('refY', refY);
+ if (refX.hasValue() && refY.hasValue()) {
+ ctx.translate(-scaleMin * refX.Length.toPixels('x'), -scaleMin * refY.Length.toPixels('y'));
+ }
+ else {
+ // align
+ if (align.match(/^xMid/) && ((meetOrSlice == 'meet' && scaleMin == scaleY) || (meetOrSlice == 'slice' && scaleMax == scaleY))) ctx.translate(width / 2.0 - desiredWidth / 2.0, 0);
+ if (align.match(/YMid$/) && ((meetOrSlice == 'meet' && scaleMin == scaleX) || (meetOrSlice == 'slice' && scaleMax == scaleX))) ctx.translate(0, height / 2.0 - desiredHeight / 2.0);
+ if (align.match(/^xMax/) && ((meetOrSlice == 'meet' && scaleMin == scaleY) || (meetOrSlice == 'slice' && scaleMax == scaleY))) ctx.translate(width - desiredWidth, 0);
+ if (align.match(/YMax$/) && ((meetOrSlice == 'meet' && scaleMin == scaleX) || (meetOrSlice == 'slice' && scaleMax == scaleX))) ctx.translate(0, height - desiredHeight);
+ }
+
+ // scale
+ if (align == 'none') ctx.scale(scaleX, scaleY);
+ else if (meetOrSlice == 'meet') ctx.scale(scaleMin, scaleMin);
+ else if (meetOrSlice == 'slice') ctx.scale(scaleMax, scaleMax);
+
+ // translate
+ ctx.translate(minX == null ? 0 : -minX, minY == null ? 0 : -minY);
+ }
+
+ // elements
+ svg.Element = {}
+
+ svg.Element.ElementBase = function(node) {
+ this.attributes = {};
+ this.styles = {};
+ this.children = [];
+
+ // get or create attribute
+ this.attribute = function(name, createIfNotExists) {
+ var a = this.attributes[name];
+ if (a != null) return a;
+
+ a = new svg.Property(name, '');
+ if (createIfNotExists == true) this.attributes[name] = a;
+ return a;
+ }
+
+ // get or create style
+ this.style = function(name, createIfNotExists) {
+ var s = this.styles[name];
+ if (s != null) return s;
+
+ var a = this.attribute(name);
+ if (a != null && a.hasValue()) {
+ return a;
+ }
+
+ s = new svg.Property(name, '');
+ if (createIfNotExists == true) this.styles[name] = s;
+ return s;
+ }
+
+ // base render
+ this.render = function(ctx) {
+ // don't render display=none
+ if (this.attribute('display').value == 'none') return;
+
+ ctx.save();
+ this.setContext(ctx);
+ this.renderChildren(ctx);
+ this.clearContext(ctx);
+ ctx.restore();
+ }
+
+ // base set context
+ this.setContext = function(ctx) {
+ // OVERRIDE ME!
+ }
+
+ // base clear context
+ this.clearContext = function(ctx) {
+ // OVERRIDE ME!
+ }
+
+ // base render children
+ this.renderChildren = function(ctx) {
+ for (var i=0; i<this.children.length; i++) {
+ this.children[i].render(ctx);
+ }
+ }
+
+ this.addChild = function(childNode, create) {
+ var child = childNode;
+ if (create) child = svg.CreateElement(childNode);
+ child.parent = this;
+ this.children.push(child);
+ }
+
+ if (node != null && node.nodeType == 1) { //ELEMENT_NODE
+ // add children
+ for (var i=0; i<node.childNodes.length; i++) {
+ var childNode = node.childNodes[i];
+ if (childNode.nodeType == 1) this.addChild(childNode, true); //ELEMENT_NODE
+ }
+
+ // add attributes
+ for (var i=0; i<node.attributes.length; i++) {
+ var attribute = node.attributes[i];
+ this.attributes[attribute.nodeName] = new svg.Property(attribute.nodeName, attribute.nodeValue);
+ }
+
+ // add tag styles
+ var styles = svg.Styles[this.type];
+ if (styles != null) {
+ for (var name in styles) {
+ this.styles[name] = styles[name];
+ }
+ }
+
+ // add class styles
+ if (this.attribute('class').hasValue()) {
+ var classes = svg.compressSpaces(this.attribute('class').value).split(' ');
+ for (var j=0; j<classes.length; j++) {
+ styles = svg.Styles['.'+classes[j]];
+ if (styles != null) {
+ for (var name in styles) {
+ this.styles[name] = styles[name];
+ }
+ }
+ }
+ }
+
+ // add inline styles
+ if (this.attribute('style').hasValue()) {
+ var styles = this.attribute('style').value.split(';');
+ for (var i=0; i<styles.length; i++) {
+ if (svg.trim(styles[i]) != '') {
+ var style = styles[i].split(':');
+ var name = svg.trim(style[0]);
+ var value = svg.trim(style[1]);
+ this.styles[name] = new svg.Property(name, value);
+ }
+ }
+ }
+
+ // add id
+ if (this.attribute('id').hasValue()) {
+ if (svg.Definitions[this.attribute('id').value] == null) {
+ svg.Definitions[this.attribute('id').value] = this;
+ }
+ }
+ }
+ }
+
+ svg.Element.RenderedElementBase = function(node) {
+ this.base = svg.Element.ElementBase;
+ this.base(node);
+
+ this.setContext = function(ctx) {
+ // fill
+ if (this.style('fill').Definition.isUrl()) {
+ var fs = this.style('fill').Definition.getFillStyle(this);
+ if (fs != null) ctx.fillStyle = fs;
+ }
+ else if (this.style('fill').hasValue()) {
+ var fillStyle = this.style('fill');
+ if (this.style('fill-opacity').hasValue()) fillStyle = fillStyle.Color.addOpacity(this.style('fill-opacity').value);
+ ctx.fillStyle = (fillStyle.value == 'none' ? 'rgba(0,0,0,0)' : fillStyle.value);
+ }
+
+ // stroke
+ if (this.style('stroke').Definition.isUrl()) {
+ var fs = this.style('stroke').Definition.getFillStyle(this);
+ if (fs != null) ctx.strokeStyle = fs;
+ }
+ else if (this.style('stroke').hasValue()) {
+ var strokeStyle = this.style('stroke');
+ if (this.style('stroke-opacity').hasValue()) strokeStyle = strokeStyle.Color.addOpacity(this.style('stroke-opacity').value);
+ ctx.strokeStyle = (strokeStyle.value == 'none' ? 'rgba(0,0,0,0)' : strokeStyle.value);
+ }
+ if (this.style('stroke-width').hasValue()) ctx.lineWidth = this.style('stroke-width').Length.toPixels();
+ if (this.style('stroke-linecap').hasValue()) ctx.lineCap = this.style('stroke-linecap').value;
+ if (this.style('stroke-linejoin').hasValue()) ctx.lineJoin = this.style('stroke-linejoin').value;
+ if (this.style('stroke-miterlimit').hasValue()) ctx.miterLimit = this.style('stroke-miterlimit').value;
+
+ // font
+ if (typeof(ctx.font) != 'undefined') {
+ ctx.font = svg.Font.CreateFont(
+ this.style('font-style').value,
+ this.style('font-variant').value,
+ this.style('font-weight').value,
+ this.style('font-size').hasValue() ? this.style('font-size').Length.toPixels() + 'px' : '',
+ this.style('font-family').value).toString();
+ }
+
+ // transform
+ if (this.attribute('transform').hasValue()) {
+ var transform = new svg.Transform(this.attribute('transform').value);
+ transform.apply(ctx);
+ }
+
+ // clip
+ if (this.attribute('clip-path').hasValue()) {
+ var clip = this.attribute('clip-path').Definition.getDefinition();
+ if (clip != null) clip.apply(ctx);
+ }
+
+ // opacity
+ if (this.style('opacity').hasValue()) {
+ ctx.globalAlpha = this.style('opacity').numValue();
+ }
+ }
+ }
+ svg.Element.RenderedElementBase.prototype = new svg.Element.ElementBase;
+
+ svg.Element.PathElementBase = function(node) {
+ this.base = svg.Element.RenderedElementBase;
+ this.base(node);
+
+ this.path = function(ctx) {
+ if (ctx != null) ctx.beginPath();
+ return new svg.BoundingBox();
+ }
+
+ this.renderChildren = function(ctx) {
+ this.path(ctx);
+ svg.Mouse.checkPath(this, ctx);
+ if (ctx.fillStyle != '') ctx.fill();
+ if (ctx.strokeStyle != '') ctx.stroke();
+
+ var markers = this.getMarkers();
+ if (markers != null) {
+ if (this.attribute('marker-start').Definition.isUrl()) {
+ var marker = this.attribute('marker-start').Definition.getDefinition();
+ marker.render(ctx, markers[0][0], markers[0][1]);
+ }
+ if (this.attribute('marker-mid').Definition.isUrl()) {
+ var marker = this.attribute('marker-mid').Definition.getDefinition();
+ for (var i=1;i<markers.length-1;i++) {
+ marker.render(ctx, markers[i][0], markers[i][1]);
+ }
+ }
+ if (this.attribute('marker-end').Definition.isUrl()) {
+ var marker = this.attribute('marker-end').Definition.getDefinition();
+ marker.render(ctx, markers[markers.length-1][0], markers[markers.length-1][1]);
+ }
+ }
+ }
+
+ this.getBoundingBox = function() {
+ return this.path();
+ }
+
+ this.getMarkers = function() {
+ return null;
+ }
+ }
+ svg.Element.PathElementBase.prototype = new svg.Element.RenderedElementBase;
+
+ // svg element
+ svg.Element.svg = function(node) {
+ this.base = svg.Element.RenderedElementBase;
+ this.base(node);
+
+ this.baseClearContext = this.clearContext;
+ this.clearContext = function(ctx) {
+ this.baseClearContext(ctx);
+ svg.ViewPort.RemoveCurrent();
+ }
+
+ this.baseSetContext = this.setContext;
+ this.setContext = function(ctx) {
+ // initial values
+ ctx.strokeStyle = 'rgba(0,0,0,0)';
+ ctx.lineCap = 'butt';
+ ctx.lineJoin = 'miter';
+ ctx.miterLimit = 4;
+
+ this.baseSetContext(ctx);
+
+ // create new view port
+ if (this.attribute('x').hasValue() && this.attribute('y').hasValue()) {
+ ctx.translate(this.attribute('x').Length.toPixels('x'), this.attribute('y').Length.toPixels('y'));
+ }
+
+ var width = svg.ViewPort.width();
+ var height = svg.ViewPort.height();
+ if (this.attribute('width').hasValue() && this.attribute('height').hasValue()) {
+ width = this.attribute('width').Length.toPixels('x');
+ height = this.attribute('height').Length.toPixels('y');
+
+ var x = 0;
+ var y = 0;
+ if (this.attribute('refX').hasValue() && this.attribute('refY').hasValue()) {
+ x = -this.attribute('refX').Length.toPixels('x');
+ y = -this.attribute('refY').Length.toPixels('y');
+ }
+
+ ctx.beginPath();
+ ctx.moveTo(x, y);
+ ctx.lineTo(width, y);
+ ctx.lineTo(width, height);
+ ctx.lineTo(x, height);
+ ctx.closePath();
+ ctx.clip();
+ }
+ svg.ViewPort.SetCurrent(width, height);
+
+ // viewbox
+ if (this.attribute('viewBox').hasValue()) {
+ var viewBox = svg.ToNumberArray(this.attribute('viewBox').value);
+ var minX = viewBox[0];
+ var minY = viewBox[1];
+ width = viewBox[2];
+ height = viewBox[3];
+
+ svg.AspectRatio(ctx,
+ this.attribute('preserveAspectRatio').value,
+ svg.ViewPort.width(),
+ width,
+ svg.ViewPort.height(),
+ height,
+ minX,
+ minY,
+ this.attribute('refX').value,
+ this.attribute('refY').value);
+
+ svg.ViewPort.RemoveCurrent();
+ svg.ViewPort.SetCurrent(viewBox[2], viewBox[3]);
+ }
+ }
+ }
+ svg.Element.svg.prototype = new svg.Element.RenderedElementBase;
+
+ // rect element
+ svg.Element.rect = function(node) {
+ this.base = svg.Element.PathElementBase;
+ this.base(node);
+
+ this.path = function(ctx) {
+ var x = this.attribute('x').Length.toPixels('x');
+ var y = this.attribute('y').Length.toPixels('y');
+ var width = this.attribute('width').Length.toPixels('x');
+ var height = this.attribute('height').Length.toPixels('y');
+ var rx = this.attribute('rx').Length.toPixels('x');
+ var ry = this.attribute('ry').Length.toPixels('y');
+ if (this.attribute('rx').hasValue() && !this.attribute('ry').hasValue()) ry = rx;
+ if (this.attribute('ry').hasValue() && !this.attribute('rx').hasValue()) rx = ry;
+
+ if (ctx != null) {
+ ctx.beginPath();
+ ctx.moveTo(x + rx, y);
+ ctx.lineTo(x + width - rx, y);
+ ctx.quadraticCurveTo(x + width, y, x + width, y + ry)
+ ctx.lineTo(x + width, y + height - ry);
+ ctx.quadraticCurveTo(x + width, y + height, x + width - rx, y + height)
+ ctx.lineTo(x + rx, y + height);
+ ctx.quadraticCurveTo(x, y + height, x, y + height - ry)
+ ctx.lineTo(x, y + ry);
+ ctx.quadraticCurveTo(x, y, x + rx, y)
+ ctx.closePath();
+ }
+
+ return new svg.BoundingBox(x, y, x + width, y + height);
+ }
+ }
+ svg.Element.rect.prototype = new svg.Element.PathElementBase;
+
+ // circle element
+ svg.Element.circle = function(node) {
+ this.base = svg.Element.PathElementBase;
+ this.base(node);
+
+ this.path = function(ctx) {
+ var cx = this.attribute('cx').Length.toPixels('x');
+ var cy = this.attribute('cy').Length.toPixels('y');
+ var r = this.attribute('r').Length.toPixels();
+
+ if (ctx != null) {
+ ctx.beginPath();
+ ctx.arc(cx, cy, r, 0, Math.PI * 2, true);
+ ctx.closePath();
+ }
+
+ return new svg.BoundingBox(cx - r, cy - r, cx + r, cy + r);
+ }
+ }
+ svg.Element.circle.prototype = new svg.Element.PathElementBase;
+
+ // ellipse element
+ svg.Element.ellipse = function(node) {
+ this.base = svg.Element.PathElementBase;
+ this.base(node);
+
+ this.path = function(ctx) {
+ var KAPPA = 4 * ((Math.sqrt(2) - 1) / 3);
+ var rx = this.attribute('rx').Length.toPixels('x');
+ var ry = this.attribute('ry').Length.toPixels('y');
+ var cx = this.attribute('cx').Length.toPixels('x');
+ var cy = this.attribute('cy').Length.toPixels('y');
+
+ if (ctx != null) {
+ ctx.beginPath();
+ ctx.moveTo(cx, cy - ry);
+ ctx.bezierCurveTo(cx + (KAPPA * rx), cy - ry, cx + rx, cy - (KAPPA * ry), cx + rx, cy);
+ ctx.bezierCurveTo(cx + rx, cy + (KAPPA * ry), cx + (KAPPA * rx), cy + ry, cx, cy + ry);
+ ctx.bezierCurveTo(cx - (KAPPA * rx), cy + ry, cx - rx, cy + (KAPPA * ry), cx - rx, cy);
+ ctx.bezierCurveTo(cx - rx, cy - (KAPPA * ry), cx - (KAPPA * rx), cy - ry, cx, cy - ry);
+ ctx.closePath();
+ }
+
+ return new svg.BoundingBox(cx - rx, cy - ry, cx + rx, cy + ry);
+ }
+ }
+ svg.Element.ellipse.prototype = new svg.Element.PathElementBase;
+
+ // line element
+ svg.Element.line = function(node) {
+ this.base = svg.Element.PathElementBase;
+ this.base(node);
+
+ this.getPoints = function() {
+ return [
+ new svg.Point(this.attribute('x1').Length.toPixels('x'), this.attribute('y1').Length.toPixels('y')),
+ new svg.Point(this.attribute('x2').Length.toPixels('x'), this.attribute('y2').Length.toPixels('y'))];
+ }
+
+ this.path = function(ctx) {
+ var points = this.getPoints();
+
+ if (ctx != null) {
+ ctx.beginPath();
+ ctx.moveTo(points[0].x, points[0].y);
+ ctx.lineTo(points[1].x, points[1].y);
+ }
+
+ return new svg.BoundingBox(points[0].x, points[0].y, points[1].x, points[1].y);
+ }
+
+ this.getMarkers = function() {
+ var points = this.getPoints();
+ var a = points[0].angleTo(points[1]);
+ return [[points[0], a], [points[1], a]];
+ }
+ }
+ svg.Element.line.prototype = new svg.Element.PathElementBase;
+
+ // polyline element
+ svg.Element.polyline = function(node) {
+ this.base = svg.Element.PathElementBase;
+ this.base(node);
+
+ this.points = svg.CreatePath(this.attribute('points').value);
+ this.path = function(ctx) {
+ var bb = new svg.BoundingBox(this.points[0].x, this.points[0].y);
+ if (ctx != null) {
+ ctx.beginPath();
+ ctx.moveTo(this.points[0].x, this.points[0].y);
+ }
+ for (var i=1; i<this.points.length; i++) {
+ bb.addPoint(this.points[i].x, this.points[i].y);
+ if (ctx != null) ctx.lineTo(this.points[i].x, this.points[i].y);
+ }
+ return bb;
+ }
+
+ this.getMarkers = function() {
+ var markers = [];
+ for (var i=0; i<this.points.length - 1; i++) {
+ markers.push([this.points[i], this.points[i].angleTo(this.points[i+1])]);
+ }
+ markers.push([this.points[this.points.length-1], markers[markers.length-1][1]]);
+ return markers;
+ }
+ }
+ svg.Element.polyline.prototype = new svg.Element.PathElementBase;
+
+ // polygon element
+ svg.Element.polygon = function(node) {
+ this.base = svg.Element.polyline;
+ this.base(node);
+
+ this.basePath = this.path;
+ this.path = function(ctx) {
+ var bb = this.basePath(ctx);
+ if (ctx != null) {
+ ctx.lineTo(this.points[0].x, this.points[0].y);
+ ctx.closePath();
+ }
+ return bb;
+ }
+ }
+ svg.Element.polygon.prototype = new svg.Element.polyline;
+
+ // path element
+ svg.Element.path = function(node) {
+ this.base = svg.Element.PathElementBase;
+ this.base(node);
+
+ var d = this.attribute('d').value;
+ // TODO: floating points, convert to real lexer based on http://www.w3.org/TR/SVG11/paths.html#PathDataBNF
+ d = d.replace(/,/gm,' '); // get rid of all commas
+ d = d.replace(/([MmZzLlHhVvCcSsQqTtAa])([MmZzLlHhVvCcSsQqTtAa])/gm,'$1 $2'); // separate commands from commands
+ d = d.replace(/([MmZzLlHhVvCcSsQqTtAa])([MmZzLlHhVvCcSsQqTtAa])/gm,'$1 $2'); // separate commands from commands
+ d = d.replace(/([MmZzLlHhVvCcSsQqTtAa])([^\s])/gm,'$1 $2'); // separate commands from points
+ d = d.replace(/([^\s])([MmZzLlHhVvCcSsQqTtAa])/gm,'$1 $2'); // separate commands from points
+ d = d.replace(/([0-9])([+\-])/gm,'$1 $2'); // separate digits when no comma
+ d = d.replace(/(\.[0-9]*)(\.)/gm,'$1 $2'); // separate digits when no comma
+ d = d.replace(/([Aa](\s+[0-9]+){3})\s+([01])\s*([01])/gm,'$1 $3 $4 '); // shorthand elliptical arc path syntax
+ d = svg.compressSpaces(d); // compress multiple spaces
+ d = svg.trim(d);
+ this.PathParser = new (function(d) {
+ this.tokens = d.split(' ');
+
+ this.reset = function() {
+ this.i = -1;
+ this.command = '';
+ this.previousCommand = '';
+ this.start = new svg.Point(0, 0);
+ this.control = new svg.Point(0, 0);
+ this.current = new svg.Point(0, 0);
+ this.points = [];
+ this.angles = [];
+ }
+
+ this.isEnd = function() {
+ return this.i >= this.tokens.length - 1;
+ }
+
+ this.isCommandOrEnd = function() {
+ if (this.isEnd()) return true;
+ return this.tokens[this.i + 1].match(/[A-Za-z]/) != null;
+ }
+
+ this.isRelativeCommand = function() {
+ return this.command == this.command.toLowerCase();
+ }
+
+ this.getToken = function() {
+ this.i = this.i + 1;
+ return this.tokens[this.i];
+ }
+
+ this.getScalar = function() {
+ return parseFloat(this.getToken());
+ }
+
+ this.nextCommand = function() {
+ this.previousCommand = this.command;
+ this.command = this.getToken();
+ }
+
+ this.getPoint = function() {
+ var p = new svg.Point(this.getScalar(), this.getScalar());
+ return this.makeAbsolute(p);
+ }
+
+ this.getAsControlPoint = function() {
+ var p = this.getPoint();
+ this.control = p;
+ return p;
+ }
+
+ this.getAsCurrentPoint = function() {
+ var p = this.getPoint();
+ this.current = p;
+ return p;
+ }
+
+ this.getReflectedControlPoint = function() {
+ if (this.previousCommand.toLowerCase() != 'c' && this.previousCommand.toLowerCase() != 's') {
+ return this.current;
+ }
+
+ // reflect point
+ var p = new svg.Point(2 * this.current.x - this.control.x, 2 * this.current.y - this.control.y);
+ return p;
+ }
+
+ this.makeAbsolute = function(p) {
+ if (this.isRelativeCommand()) {
+ p.x = this.current.x + p.x;
+ p.y = this.current.y + p.y;
+ }
+ return p;
+ }
+
+ this.addMarker = function(p, from) {
+ this.addMarkerAngle(p, from == null ? null : from.angleTo(p));
+ }
+
+ this.addMarkerAngle = function(p, a) {
+ this.points.push(p);
+ this.angles.push(a);
+ }
+
+ this.getMarkerPoints = function() { return this.points; }
+ this.getMarkerAngles = function() {
+ for (var i=0; i<this.angles.length; i++) {
+ if (this.angles[i] == null) {
+ for (var j=i+1; j<this.angles.length; j++) {
+ if (this.angles[j] != null) {
+ this.angles[i] = this.angles[j];
+ break;
+ }
+ }
+ }
+ }
+ return this.angles;
+ }
+ })(d);
+
+ this.path = function(ctx) {
+ var pp = this.PathParser;
+ pp.reset();
+
+ var bb = new svg.BoundingBox();
+
+ if(this.attribute('visibility').value=='hidden') return;
+
+ if (ctx != null) ctx.beginPath();
+ while (!pp.isEnd()) {
+ pp.nextCommand();
+ switch (pp.command.toUpperCase()) {
+ case 'M':
+ var p = pp.getAsCurrentPoint();
+ pp.addMarker(p);
+ bb.addPoint(p.x, p.y);
+ if (ctx != null) ctx.moveTo(p.x, p.y);
+ pp.start = pp.current;
+ while (!pp.isCommandOrEnd()) {
+ var p = pp.getAsCurrentPoint();
+ pp.addMarker(p);
+ bb.addPoint(p.x, p.y);
+ if (ctx != null) ctx.lineTo(p.x, p.y);
+ }
+ break;
+ case 'L':
+ while (!pp.isCommandOrEnd()) {
+ var c = pp.current;
+ var p = pp.getAsCurrentPoint();
+ pp.addMarker(p, c);
+ bb.addPoint(p.x, p.y);
+ if (ctx != null) ctx.lineTo(p.x, p.y);
+ }
+ break;
+ case 'H':
+ while (!pp.isCommandOrEnd()) {
+ var newP = new svg.Point((pp.isRelativeCommand() ? pp.current.x : 0) + pp.getScalar(), pp.current.y);
+ pp.addMarker(newP, pp.current);
+ pp.current = newP;
+ bb.addPoint(pp.current.x, pp.current.y);
+ if (ctx != null) ctx.lineTo(pp.current.x, pp.current.y);
+ }
+ break;
+ case 'V':
+ while (!pp.isCommandOrEnd()) {
+ var newP = new svg.Point(pp.current.x, (pp.isRelativeCommand() ? pp.current.y : 0) + pp.getScalar());
+ pp.addMarker(newP, pp.current);
+ pp.current = newP;
+ bb.addPoint(pp.current.x, pp.current.y);
+ if (ctx != null) ctx.lineTo(pp.current.x, pp.current.y);
+ }
+ break;
+ case 'C':
+ while (!pp.isCommandOrEnd()) {
+ var curr = pp.current;
+ var p1 = pp.getPoint();
+ var cntrl = pp.getAsControlPoint();
+ var cp = pp.getAsCurrentPoint();
+ pp.addMarker(cp, cntrl);
+ bb.addBezierCurve(curr.x, curr.y, p1.x, p1.y, cntrl.x, cntrl.y, cp.x, cp.y);
+ if (ctx != null) ctx.bezierCurveTo(p1.x, p1.y, cntrl.x, cntrl.y, cp.x, cp.y);
+ }
+ break;
+ case 'S':
+ while (!pp.isCommandOrEnd()) {
+ var curr = pp.current;
+ var p1 = pp.getReflectedControlPoint();
+ var cntrl = pp.getAsControlPoint();
+ var cp = pp.getAsCurrentPoint();
+ pp.addMarker(cp, cntrl);
+ bb.addBezierCurve(curr.x, curr.y, p1.x, p1.y, cntrl.x, cntrl.y, cp.x, cp.y);
+ if (ctx != null) ctx.bezierCurveTo(p1.x, p1.y, cntrl.x, cntrl.y, cp.x, cp.y);
+ }
+ break;
+ case 'Q':
+ while (!pp.isCommandOrEnd()) {
+ var curr = pp.current;
+ var cntrl = pp.getAsControlPoint();
+ var cp = pp.getAsCurrentPoint();
+ pp.addMarker(cp, cntrl);
+ bb.addQuadraticCurve(curr.x, curr.y, cntrl.x, cntrl.y, cp.x, cp.y);
+ if (ctx != null) ctx.quadraticCurveTo(cntrl.x, cntrl.y, cp.x, cp.y);
+ }
+ break;
+ case 'T':
+ while (!pp.isCommandOrEnd()) {
+ var curr = pp.current;
+ var cntrl = pp.getReflectedControlPoint();
+ pp.control = cntrl;
+ var cp = pp.getAsCurrentPoint();
+ pp.addMarker(cp, cntrl);
+ bb.addQuadraticCurve(curr.x, curr.y, cntrl.x, cntrl.y, cp.x, cp.y);
+ if (ctx != null) ctx.quadraticCurveTo(cntrl.x, cntrl.y, cp.x, cp.y);
+ }
+ break;
+ case 'A':
+ while (!pp.isCommandOrEnd()) {
+ var curr = pp.current;
+ var rx = pp.getScalar();
+ var ry = pp.getScalar();
+ var xAxisRotation = pp.getScalar() * (Math.PI / 180.0);
+ var largeArcFlag = pp.getScalar();
+ var sweepFlag = pp.getScalar();
+ var cp = pp.getAsCurrentPoint();
+
+ // Conversion from endpoint to center parameterization
+ // http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes
+ // x1', y1'
+ var currp = new svg.Point(
+ Math.cos(xAxisRotation) * (curr.x - cp.x) / 2.0 + Math.sin(xAxisRotation) * (curr.y - cp.y) / 2.0,
+ -Math.sin(xAxisRotation) * (curr.x - cp.x) / 2.0 + Math.cos(xAxisRotation) * (curr.y - cp.y) / 2.0
+ );
+ // adjust radii
+ var l = Math.pow(currp.x,2)/Math.pow(rx,2)+Math.pow(currp.y,2)/Math.pow(ry,2);
+ if (l > 1) {
+ rx *= Math.sqrt(l);
+ ry *= Math.sqrt(l);
+ }
+ // cx', cy'
+ var s = (largeArcFlag == sweepFlag ? -1 : 1) * Math.sqrt(
+ ((Math.pow(rx,2)*Math.pow(ry,2))-(Math.pow(rx,2)*Math.pow(currp.y,2))-(Math.pow(ry,2)*Math.pow(currp.x,2))) /
+ (Math.pow(rx,2)*Math.pow(currp.y,2)+Math.pow(ry,2)*Math.pow(currp.x,2))
+ );
+ if (isNaN(s)) s = 0;
+ var cpp = new svg.Point(s * rx * currp.y / ry, s * -ry * currp.x / rx);
+ // cx, cy
+ var centp = new svg.Point(
+ (curr.x + cp.x) / 2.0 + Math.cos(xAxisRotation) * cpp.x - Math.sin(xAxisRotation) * cpp.y,
+ (curr.y + cp.y) / 2.0 + Math.sin(xAxisRotation) * cpp.x + Math.cos(xAxisRotation) * cpp.y
+ );
+ // vector magnitude
+ var m = function(v) { return Math.sqrt(Math.pow(v[0],2) + Math.pow(v[1],2)); }
+ // ratio between two vectors
+ var r = function(u, v) { return (u[0]*v[0]+u[1]*v[1]) / (m(u)*m(v)) }
+ // angle between two vectors
+ var a = function(u, v) { return (u[0]*v[1] < u[1]*v[0] ? -1 : 1) * Math.acos(r(u,v)); }
+ // initial angle
+ var a1 = a([1,0], [(currp.x-cpp.x)/rx,(currp.y-cpp.y)/ry]);
+ // angle delta
+ var u = [(currp.x-cpp.x)/rx,(currp.y-cpp.y)/ry];
+ var v = [(-currp.x-cpp.x)/rx,(-currp.y-cpp.y)/ry];
+ var ad = a(u, v);
+ if (r(u,v) <= -1) ad = Math.PI;
+ if (r(u,v) >= 1) ad = 0;
+
+ if (sweepFlag == 0 && ad > 0) ad = ad - 2 * Math.PI;
+ if (sweepFlag == 1 && ad < 0) ad = ad + 2 * Math.PI;
+
+ // for markers
+ var halfWay = new svg.Point(
+ centp.x - rx * Math.cos((a1 + ad) / 2),
+ centp.y - ry * Math.sin((a1 + ad) / 2)
+ );
+ pp.addMarkerAngle(halfWay, (a1 + ad) / 2 + (sweepFlag == 0 ? 1 : -1) * Math.PI / 2);
+ pp.addMarkerAngle(cp, ad + (sweepFlag == 0 ? 1 : -1) * Math.PI / 2);
+
+ bb.addPoint(cp.x, cp.y); // TODO: this is too naive, make it better
+ if (ctx != null) {
+ var r = rx > ry ? rx : ry;
+ var sx = rx > ry ? 1 : rx / ry;
+ var sy = rx > ry ? ry / rx : 1;
+
+ ctx.translate(centp.x, centp.y);
+ ctx.rotate(xAxisRotation);
+ ctx.scale(sx, sy);
+ ctx.arc(0, 0, r, a1, a1 + ad, 1 - sweepFlag);
+ ctx.scale(1/sx, 1/sy);
+ ctx.rotate(-xAxisRotation);
+ ctx.translate(-centp.x, -centp.y);
+ }
+ }
+ break;
+ case 'Z':
+ if (ctx != null) ctx.closePath();
+ pp.current = pp.start;
+ }
+ }
+
+ return bb;
+ }
+
+ this.getMarkers = function() {
+ var points = this.PathParser.getMarkerPoints();
+ var angles = this.PathParser.getMarkerAngles();
+
+ var markers = [];
+ for (var i=0; i<points.length; i++) {
+ markers.push([points[i], angles[i]]);
+ }
+ return markers;
+ }
+ }
+ svg.Element.path.prototype = new svg.Element.PathElementBase;
+
+ // pattern element
+ svg.Element.pattern = function(node) {
+ this.base = svg.Element.ElementBase;
+ this.base(node);
+
+ this.createPattern = function(ctx, element) {
+ // render me using a temporary svg element
+ var tempSvg = new svg.Element.svg();
+ tempSvg.attributes['viewBox'] = new svg.Property('viewBox', this.attribute('viewBox').value);
+ tempSvg.attributes['x'] = new svg.Property('x', this.attribute('x').value);
+ tempSvg.attributes['y'] = new svg.Property('y', this.attribute('y').value);
+ tempSvg.attributes['width'] = new svg.Property('width', this.attribute('width').value);
+ tempSvg.attributes['height'] = new svg.Property('height', this.attribute('height').value);
+ tempSvg.children = this.children;
+
+ var c = document.createElement('canvas');
+ c.width = this.attribute('width').Length.toPixels();
+ c.height = this.attribute('height').Length.toPixels();
+ tempSvg.render(c.getContext('2d'));
+ return ctx.createPattern(c, 'repeat');
+ }
+ }
+ svg.Element.pattern.prototype = new svg.Element.ElementBase;
+
+ // marker element
+ svg.Element.marker = function(node) {
+ this.base = svg.Element.ElementBase;
+ this.base(node);
+
+ this.baseRender = this.render;
+ this.render = function(ctx, point, angle) {
+ ctx.translate(point.x, point.y);
+ if (this.attribute('orient').valueOrDefault('auto') == 'auto') ctx.rotate(angle);
+ if (this.attribute('markerUnits').valueOrDefault('strokeWidth') == 'strokeWidth') ctx.scale(ctx.lineWidth, ctx.lineWidth);
+ ctx.save();
+
+ // render me using a temporary svg element
+ var tempSvg = new svg.Element.svg();
+ tempSvg.attributes['viewBox'] = new svg.Property('viewBox', this.attribute('viewBox').value);
+ tempSvg.attributes['refX'] = new svg.Property('refX', this.attribute('refX').value);
+ tempSvg.attributes['refY'] = new svg.Property('refY', this.attribute('refY').value);
+ tempSvg.attributes['width'] = new svg.Property('width', this.attribute('markerWidth').value);
+ tempSvg.attributes['height'] = new svg.Property('height', this.attribute('markerHeight').value);
+ tempSvg.attributes['fill'] = new svg.Property('fill', this.attribute('fill').valueOrDefault('black'));
+ tempSvg.attributes['stroke'] = new svg.Property('stroke', this.attribute('stroke').valueOrDefault('none'));
+ tempSvg.children = this.children;
+ tempSvg.render(ctx);
+
+ ctx.restore();
+ if (this.attribute('markerUnits').valueOrDefault('strokeWidth') == 'strokeWidth') ctx.scale(1/ctx.lineWidth, 1/ctx.lineWidth);
+ if (this.attribute('orient').valueOrDefault('auto') == 'auto') ctx.rotate(-angle);
+ ctx.translate(-point.x, -point.y);
+ }
+ }
+ svg.Element.marker.prototype = new svg.Element.ElementBase;
+
+ // definitions element
+ svg.Element.defs = function(node) {
+ this.base = svg.Element.ElementBase;
+ this.base(node);
+
+ this.render = function(ctx) {
+ // NOOP
+ }
+ }
+ svg.Element.defs.prototype = new svg.Element.ElementBase;
+
+ // base for gradients
+ svg.Element.GradientBase = function(node) {
+ this.base = svg.Element.ElementBase;
+ this.base(node);
+
+ this.gradientUnits = this.attribute('gradientUnits').valueOrDefault('objectBoundingBox');
+
+ this.stops = [];
+ for (var i=0; i<this.children.length; i++) {
+ var child = this.children[i];
+ this.stops.push(child);
+ }
+
+ this.getGradient = function() {
+ // OVERRIDE ME!
+ }
+
+ this.createGradient = function(ctx, element) {
+ var stopsContainer = this;
+ if (this.attribute('xlink:href').hasValue()) {
+ stopsContainer = this.attribute('xlink:href').Definition.getDefinition();
+ }
+
+ var g = this.getGradient(ctx, element);
+ for (var i=0; i<stopsContainer.stops.length; i++) {
+ g.addColorStop(stopsContainer.stops[i].offset, stopsContainer.stops[i].color);
+ }
+ return g;
+ }
+ }
+ svg.Element.GradientBase.prototype = new svg.Element.ElementBase;
+
+ // linear gradient element
+ svg.Element.linearGradient = function(node) {
+ this.base = svg.Element.GradientBase;
+ this.base(node);
+
+ this.getGradient = function(ctx, element) {
+ var bb = element.getBoundingBox();
+
+ var x1 = (this.gradientUnits == 'objectBoundingBox'
+ ? bb.x() + bb.width() * this.attribute('x1').numValue()
+ : this.attribute('x1').Length.toPixels('x'));
+ var y1 = (this.gradientUnits == 'objectBoundingBox'
+ ? bb.y() + bb.height() * this.attribute('y1').numValue()
+ : this.attribute('y1').Length.toPixels('y'));
+ var x2 = (this.gradientUnits == 'objectBoundingBox'
+ ? bb.x() + bb.width() * this.attribute('x2').numValue()
+ : this.attribute('x2').Length.toPixels('x'));
+ var y2 = (this.gradientUnits == 'objectBoundingBox'
+ ? bb.y() + bb.height() * this.attribute('y2').numValue()
+ : this.attribute('y2').Length.toPixels('y'));
+
+ var p1 = new svg.Point(x1, y1);
+ var p2 = new svg.Point(x2, y2);
+ if (this.attribute('gradientTransform').hasValue()) {
+ var transform = new svg.Transform(this.attribute('gradientTransform').value);
+ transform.applyToPoint(p1);
+ transform.applyToPoint(p2);
+ }
+
+ return ctx.createLinearGradient(p1.x, p1.y, p2.x, p2.y);
+ }
+ }
+ svg.Element.linearGradient.prototype = new svg.Element.GradientBase;
+
+ // radial gradient element
+ svg.Element.radialGradient = function(node) {
+ this.base = svg.Element.GradientBase;
+ this.base(node);
+
+ this.getGradient = function(ctx, element) {
+ var bb = element.getBoundingBox();
+
+ var cx = (this.gradientUnits == 'objectBoundingBox'
+ ? bb.x() + bb.width() * this.attribute('cx').numValue()
+ : this.attribute('cx').Length.toPixels('x'));
+ var cy = (this.gradientUnits == 'objectBoundingBox'
+ ? bb.y() + bb.height() * this.attribute('cy').numValue()
+ : this.attribute('cy').Length.toPixels('y'));
+
+ var fx = cx;
+ var fy = cy;
+ if (this.attribute('fx').hasValue()) {
+ fx = (this.gradientUnits == 'objectBoundingBox'
+ ? bb.x() + bb.width() * this.attribute('fx').numValue()
+ : this.attribute('fx').Length.toPixels('x'));
+ }
+ if (this.attribute('fy').hasValue()) {
+ fy = (this.gradientUnits == 'objectBoundingBox'
+ ? bb.y() + bb.height() * this.attribute('fy').numValue()
+ : this.attribute('fy').Length.toPixels('y'));
+ }
+
+ var r = (this.gradientUnits == 'objectBoundingBox'
+ ? (bb.width() + bb.height()) / 2.0 * this.attribute('r').numValue()
+ : this.attribute('r').Length.toPixels());
+
+ var c = new svg.Point(cx, cy);
+ var f = new svg.Point(fx, fy);
+ if (this.attribute('gradientTransform').hasValue()) {
+ var transform = new svg.Transform(this.attribute('gradientTransform').value);
+ transform.applyToPoint(c);
+ transform.applyToPoint(f);
+
+ for (var i=0; i<transform.transforms.length; i++) {
+ // average the scaling part of the transform, apply to radius
+ var scale1 = transform.transforms[i].m[0];
+ var scale2 = transform.transforms[i].m[3];
+ r = r * ((scale1 + scale2) / 2.0);
+ }
+ }
+
+ return ctx.createRadialGradient(f.x, f.y, 0, c.x, c.y, r);
+ }
+ }
+ svg.Element.radialGradient.prototype = new svg.Element.GradientBase;
+
+ // gradient stop element
+ svg.Element.stop = function(node) {
+ this.base = svg.Element.ElementBase;
+ this.base(node);
+
+ this.offset = this.attribute('offset').numValue();
+
+ var stopColor = this.style('stop-color');
+ if (this.style('stop-opacity').hasValue()) stopColor = stopColor.Color.addOpacity(this.style('stop-opacity').value);
+ this.color = stopColor.value;
+ }
+ svg.Element.stop.prototype = new svg.Element.ElementBase;
+
+ // animation base element
+ svg.Element.AnimateBase = function(node) {
+ this.base = svg.Element.ElementBase;
+ this.base(node);
+
+ svg.Animations.push(this);
+
+ this.duration = 0.0;
+ this.begin = this.attribute('begin').Time.toMilliseconds();
+ this.maxDuration = this.begin + this.attribute('dur').Time.toMilliseconds();
+
+ this.getProperty = function() {
+ var attributeType = this.attribute('attributeType').value;
+ var attributeName = this.attribute('attributeName').value;
+
+ if (attributeType == 'CSS') {
+ return this.parent.style(attributeName, true);
+ }
+ return this.parent.attribute(attributeName, true);
+ };
+
+ this.initialValue = null;
+ this.removed = false;
+
+ this.calcValue = function() {
+ // OVERRIDE ME!
+ return '';
+ }
+
+ this.update = function(delta) {
+ // set initial value
+ if (this.initialValue == null) {
+ this.initialValue = this.getProperty().value;
+ }
+
+ // if we're past the end time
+ if (this.duration > this.maxDuration) {
+ // loop for indefinitely repeating animations
+ if (this.attribute('repeatCount').value == 'indefinite') {
+ this.duration = 0.0
+ }
+ else if (this.attribute('fill').valueOrDefault('remove') == 'remove' && !this.removed) {
+ this.removed = true;
+ this.getProperty().value = this.initialValue;
+ return true;
+ }
+ else {
+ return false; // no updates made
+ }
+ }
+ this.duration = this.duration + delta;
+
+ // if we're past the begin time
+ var updated = false;
+ if (this.begin < this.duration) {
+ var newValue = this.calcValue(); // tween
+
+ if (this.attribute('type').hasValue()) {
+ // for transform, etc.
+ var type = this.attribute('type').value;
+ newValue = type + '(' + newValue + ')';
+ }
+
+ this.getProperty().value = newValue;
+ updated = true;
+ }
+
+ return updated;
+ }
+
+ // fraction of duration we've covered
+ this.progress = function() {
+ return ((this.duration - this.begin) / (this.maxDuration - this.begin));
+ }
+ }
+ svg.Element.AnimateBase.prototype = new svg.Element.ElementBase;
+
+ // animate element
+ svg.Element.animate = function(node) {
+ this.base = svg.Element.AnimateBase;
+ this.base(node);
+
+ this.calcValue = function() {
+ var from = this.attribute('from').numValue();
+ var to = this.attribute('to').numValue();
+
+ // tween value linearly
+ return from + (to - from) * this.progress();
+ };
+ }
+ svg.Element.animate.prototype = new svg.Element.AnimateBase;
+
+ // animate color element
+ svg.Element.animateColor = function(node) {
+ this.base = svg.Element.AnimateBase;
+ this.base(node);
+
+ this.calcValue = function() {
+ var from = new RGBColor(this.attribute('from').value);
+ var to = new RGBColor(this.attribute('to').value);
+
+ if (from.ok && to.ok) {
+ // tween color linearly
+ var r = from.r + (to.r - from.r) * this.progress();
+ var g = from.g + (to.g - from.g) * this.progress();
+ var b = from.b + (to.b - from.b) * this.progress();
+ return 'rgb('+parseInt(r,10)+','+parseInt(g,10)+','+parseInt(b,10)+')';
+ }
+ return this.attribute('from').value;
+ };
+ }
+ svg.Element.animateColor.prototype = new svg.Element.AnimateBase;
+
+ // animate transform element
+ svg.Element.animateTransform = function(node) {
+ this.base = svg.Element.animate;
+ this.base(node);
+ }
+ svg.Element.animateTransform.prototype = new svg.Element.animate;
+
+ // text element
+ svg.Element.text = function(node) {
+ this.base = svg.Element.RenderedElementBase;
+ this.base(node);
+
+ if (node != null) {
+ // add children
+ this.children = [];
+ for (var i=0; i<node.childNodes.length; i++) {
+ var childNode = node.childNodes[i];
+ if (childNode.nodeType == 1) { // capture tspan and tref nodes
+ this.addChild(childNode, true);
+ }
+ else if (childNode.nodeType == 3) { // capture text
+ this.addChild(new svg.Element.tspan(childNode), false);
+ }
+ }
+ }
+
+ this.baseSetContext = this.setContext;
+ this.setContext = function(ctx) {
+ this.baseSetContext(ctx);
+ if (this.attribute('text-anchor').hasValue()) {
+ var textAnchor = this.attribute('text-anchor').value;
+ ctx.textAlign = textAnchor == 'middle' ? 'center' : textAnchor;
+ }
+ if (this.attribute('alignment-baseline').hasValue()) ctx.textBaseline = this.attribute('alignment-baseline').value;
+ }
+
+ this.renderChildren = function(ctx) {
+ if(this.attribute('visibility').value=='hidden') return;
+
+ var x = this.attribute('x').Length.toPixels('x');
+ var y = this.attribute('y').Length.toPixels('y');
+
+ for (var i=0; i<this.children.length; i++) {
+ var child = this.children[i];
+
+ if (child.attribute('x').hasValue()) {
+ child.x = child.attribute('x').Length.toPixels('x');
+ }
+ else {
+ if (child.attribute('dx').hasValue()) x += child.attribute('dx').Length.toPixels('x');
+ child.x = x;
+ x += child.measureText(ctx);
+ }
+
+ if (child.attribute('y').hasValue()) {
+ child.y = child.attribute('y').Length.toPixels('y');
+ }
+ else {
+ if (child.attribute('dy').hasValue()) y += child.attribute('dy').Length.toPixels('y');
+ child.y = y;
+ }
+
+ child.render(ctx);
+ }
+ }
+ }
+ svg.Element.text.prototype = new svg.Element.RenderedElementBase;
+
+ // text base
+ svg.Element.TextElementBase = function(node) {
+ this.base = svg.Element.RenderedElementBase;
+ this.base(node);
+
+ this.renderChildren = function(ctx) {
+ ctx.fillText(svg.compressSpaces(this.getText()), this.x, this.y);
+ }
+
+ this.getText = function() {
+ // OVERRIDE ME
+ }
+
+ this.measureText = function(ctx) {
+ var textToMeasure = svg.compressSpaces(this.getText());
+ if (!ctx.measureText) return textToMeasure.length * 10;
+ return ctx.measureText(textToMeasure).width;
+ }
+ }
+ svg.Element.TextElementBase.prototype = new svg.Element.RenderedElementBase;
+
+ // tspan
+ svg.Element.tspan = function(node) {
+ this.base = svg.Element.TextElementBase;
+ this.base(node);
+
+ // TEXT ELEMENT
+ this.text = node.nodeType == 3 ? node.nodeValue : node.childNodes[0].nodeValue;
+ this.getText = function() {
+ return this.text;
+ }
+ }
+ svg.Element.tspan.prototype = new svg.Element.TextElementBase;
+
+ // tref
+ svg.Element.tref = function(node) {
+ this.base = svg.Element.TextElementBase;
+ this.base(node);
+
+ this.getText = function() {
+ var element = this.attribute('xlink:href').Definition.getDefinition();
+ if (element != null) return element.children[0].getText();
+ }
+ }
+ svg.Element.tref.prototype = new svg.Element.TextElementBase;
+
+ // a element
+ svg.Element.a = function(node) {
+ this.base = svg.Element.TextElementBase;
+ this.base(node);
+
+ this.hasText = true;
+ for (var i=0; i<node.childNodes.length; i++) {
+ if (node.childNodes[i].nodeType != 3) this.hasText = false;
+ }
+
+ // this might contain text
+ this.text = this.hasText ? node.childNodes[0].nodeValue : '';
+ this.getText = function() {
+ return this.text;
+ }
+
+ this.baseRenderChildren = this.renderChildren;
+ this.renderChildren = function(ctx) {
+ if (this.hasText) {
+ // render as text element
+ this.baseRenderChildren(ctx);
+ var fontSize = new svg.Property('fontSize', svg.Font.Parse(svg.ctx.font).fontSize);
+ svg.Mouse.checkBoundingBox(this, new svg.BoundingBox(this.x, this.y - fontSize.Length.toPixels('y'), this.x + this.measureText(ctx), this.y));
+ }
+ else {
+ // render as temporary group
+ var g = new svg.Element.g();
+ g.children = this.children;
+ g.parent = this;
+ g.render(ctx);
+ }
+ }
+
+ this.onclick = function() {
+ window.open(this.attribute('xlink:href').value);
+ }
+
+ this.onmousemove = function() {
+ svg.ctx.canvas.style.cursor = 'pointer';
+ }
+ }
+ svg.Element.a.prototype = new svg.Element.TextElementBase;
+
+ // image element
+ svg.Element.image = function(node) {
+ this.base = svg.Element.RenderedElementBase;
+ this.base(node);
+
+ svg.Images.push(this);
+ this.img = document.createElement('img');
+ this.loaded = false;
+ var that = this;
+ this.img.onload = function() { that.loaded = true; }
+ this.img.src = this.attribute('xlink:href').value;
+
+ this.renderChildren = function(ctx) {
+ var x = this.attribute('x').Length.toPixels('x');
+ var y = this.attribute('y').Length.toPixels('y');
+
+ var width = this.attribute('width').Length.toPixels('x');
+ var height = this.attribute('height').Length.toPixels('y');
+ if (width == 0 || height == 0) return;
+
+ ctx.save();
+ ctx.translate(x, y);
+ svg.AspectRatio(ctx,
+ this.attribute('preserveAspectRatio').value,
+ width,
+ this.img.width,
+ height,
+ this.img.height,
+ 0,
+ 0);
+ ctx.drawImage(this.img, 0, 0);
+ ctx.restore();
+ }
+ }
+ svg.Element.image.prototype = new svg.Element.RenderedElementBase;
+
+ // group element
+ svg.Element.g = function(node) {
+ this.base = svg.Element.RenderedElementBase;
+ this.base(node);
+
+ this.getBoundingBox = function() {
+ var bb = new svg.BoundingBox();
+ for (var i=0; i<this.children.length; i++) {
+ bb.addBoundingBox(this.children[i].getBoundingBox());
+ }
+ return bb;
+ };
+ }
+ svg.Element.g.prototype = new svg.Element.RenderedElementBase;
+
+ // symbol element
+ svg.Element.symbol = function(node) {
+ this.base = svg.Element.RenderedElementBase;
+ this.base(node);
+
+ this.baseSetContext = this.setContext;
+ this.setContext = function(ctx) {
+ this.baseSetContext(ctx);
+
+ // viewbox
+ if (this.attribute('viewBox').hasValue()) {
+ var viewBox = svg.ToNumberArray(this.attribute('viewBox').value);
+ var minX = viewBox[0];
+ var minY = viewBox[1];
+ width = viewBox[2];
+ height = viewBox[3];
+
+ svg.AspectRatio(ctx,
+ this.attribute('preserveAspectRatio').value,
+ this.attribute('width').Length.toPixels('x'),
+ width,
+ this.attribute('height').Length.toPixels('y'),
+ height,
+ minX,
+ minY);
+
+ svg.ViewPort.SetCurrent(viewBox[2], viewBox[3]);
+ }
+ }
+ }
+ svg.Element.symbol.prototype = new svg.Element.RenderedElementBase;
+
+ // style element
+ svg.Element.style = function(node) {
+ this.base = svg.Element.ElementBase;
+ this.base(node);
+
+ var css = node.childNodes[0].nodeValue;
+ css = css.replace(/(\/\*([^*]|[\r\n]|(\*+([^*\/]|[\r\n])))*\*+\/)|(\/\/.*)/gm, ''); // remove comments
+ css = svg.compressSpaces(css); // replace whitespace
+ var cssDefs = css.split('}');
+ for (var i=0; i<cssDefs.length; i++) {
+ if (svg.trim(cssDefs[i]) != '') {
+ var cssDef = cssDefs[i].split('{');
+ var cssClasses = cssDef[0].split(',');
+ var cssProps = cssDef[1].split(';');
+ for (var j=0; j<cssClasses.length; j++) {
+ var cssClass = svg.trim(cssClasses[j]);
+ if (cssClass != '') {
+ var props = {};
+ for (var k=0; k<cssProps.length; k++) {
+ var prop = cssProps[k].split(':');
+ var name = prop[0];
+ var value = prop[1];
+ if (name != null && value != null) {
+ props[svg.trim(prop[0])] = new svg.Property(svg.trim(prop[0]), svg.trim(prop[1]));
+ }
+ }
+ svg.Styles[cssClass] = props;
+ }
+ }
+ }
+ }
+ }
+ svg.Element.style.prototype = new svg.Element.ElementBase;
+
+ // use element
+ svg.Element.use = function(node) {
+ this.base = svg.Element.RenderedElementBase;
+ this.base(node);
+
+ this.baseSetContext = this.setContext;
+ this.setContext = function(ctx) {
+ this.baseSetContext(ctx);
+ if (this.attribute('x').hasValue()) ctx.translate(this.attribute('x').Length.toPixels('x'), 0);
+ if (this.attribute('y').hasValue()) ctx.translate(0, this.attribute('y').Length.toPixels('y'));
+ }
+
+ this.getDefinition = function() {
+ var element = this.attribute('xlink:href').Definition.getDefinition();
+ if (this.attribute('width').hasValue()) element.attribute('width', true).value = this.attribute('width').value;
+ if (this.attribute('height').hasValue()) element.attribute('height', true).value = this.attribute('height').value;
+ return element;
+ }
+
+ this.path = function(ctx) {
+ var element = this.getDefinition();
+ if (element != null) element.path(ctx);
+ }
+
+ this.renderChildren = function(ctx) {
+ var element = this.getDefinition();
+ if (element != null) element.render(ctx);
+ }
+ }
+ svg.Element.use.prototype = new svg.Element.RenderedElementBase;
+
+ // clip element
+ svg.Element.clipPath = function(node) {
+ this.base = svg.Element.ElementBase;
+ this.base(node);
+
+ this.apply = function(ctx) {
+ for (var i=0; i<this.children.length; i++) {
+ if (this.children[i].path) {
+ this.children[i].path(ctx);
+ ctx.clip();
+ }
+ }
+ }
+ }
+ svg.Element.clipPath.prototype = new svg.Element.ElementBase;
+
+ // title element, do nothing
+ svg.Element.title = function(node) {
+ }
+ svg.Element.title.prototype = new svg.Element.ElementBase;
+
+ // desc element, do nothing
+ svg.Element.desc = function(node) {
+ }
+ svg.Element.desc.prototype = new svg.Element.ElementBase;
+
+ svg.Element.MISSING = function(node) {
+ console.log('ERROR: Element \'' + node.nodeName + '\' not yet implemented.');
+ }
+ svg.Element.MISSING.prototype = new svg.Element.ElementBase;
+
+ // element factory
+ svg.CreateElement = function(node) {
+ var className = node.nodeName.replace(/^[^:]+:/,'');
+ var e = null;
+ if (typeof(svg.Element[className]) != 'undefined') {
+ e = new svg.Element[className](node);
+ }
+ else {
+ e = new svg.Element.MISSING(node);
+ }
+
+ e.type = node.nodeName;
+ return e;
+ }
+
+ // load from url
+ svg.load = function(ctx, url) {
+ svg.loadXml(ctx, svg.ajax(url));
+ }
+
+ // load from xml
+ svg.loadXml = function(ctx, xml) {
+ svg.init(ctx);
+
+ var mapXY = function(p) {
+ var e = ctx.canvas;
+ while (e) {
+ p.x -= e.offsetLeft;
+ p.y -= e.offsetTop;
+ e = e.offsetParent;
+ }
+ if (window.scrollX) p.x += window.scrollX;
+ if (window.scrollY) p.y += window.scrollY;
+ return p;
+ }
+
+ // bind mouse
+ if (svg.opts == null || svg.opts['ignoreMouse'] != true) {
+ ctx.canvas.onclick = function(e) {
+ var p = mapXY(new svg.Point(e != null ? e.clientX : event.clientX, e != null ? e.clientY : event.clientY));
+ svg.Mouse.onclick(p.x, p.y);
+ };
+ ctx.canvas.onmousemove = function(e) {
+ var p = mapXY(new svg.Point(e != null ? e.clientX : event.clientX, e != null ? e.clientY : event.clientY));
+ svg.Mouse.onmousemove(p.x, p.y);
+ };
+ }
+
+ var dom = svg.parseXml(xml);
+ var e = svg.CreateElement(dom.documentElement);
+
+ // render loop
+ var isFirstRender = true;
+ var draw = function() {
+ if (svg.opts == null || svg.opts['ignoreDimensions'] != true) {
+ // set canvas size
+ if (e.style('width').hasValue()) {
+ ctx.canvas.width = e.style('width').Length.toPixels(ctx.canvas.parentNode.clientWidth);
+ }
+ if (e.style('height').hasValue()) {
+ ctx.canvas.height = e.style('height').Length.toPixels(ctx.canvas.parentNode.clientHeight);
+ }
+ }
+ svg.ViewPort.SetCurrent(ctx.canvas.clientWidth, ctx.canvas.clientHeight);
+
+ if (svg.opts != null && svg.opts['offsetX'] != null) e.attribute('x', true).value = svg.opts['offsetX'];
+ if (svg.opts != null && svg.opts['offsetY'] != null) e.attribute('y', true).value = svg.opts['offsetY'];
+ if (svg.opts != null && svg.opts['scaleWidth'] != null && svg.opts['scaleHeight'] != null) {
+ e.attribute('width', true).value = svg.opts['scaleWidth'];
+ e.attribute('height', true).value = svg.opts['scaleHeight'];
+ e.attribute('viewBox', true).value = '0 0 ' + ctx.canvas.clientWidth + ' ' + ctx.canvas.clientHeight;
+ e.attribute('preserveAspectRatio', true).value = 'none';
+ }
+
+ // clear and render
+ if (svg.opts == null || svg.opts['ignoreClear'] != true) {
+ ctx.clearRect(0, 0, ctx.canvas.clientWidth, ctx.canvas.clientHeight);
+ }
+ e.render(ctx);
+ if (isFirstRender) {
+ isFirstRender = false;
+ if (svg.opts != null && typeof(svg.opts['renderCallback']) == 'function') svg.opts['renderCallback']();
+ }
+ }
+
+ var waitingForImages = true;
+ if (svg.ImagesLoaded()) {
+ waitingForImages = false;
+ draw();
+ }
+ svg.intervalID = setInterval(function() {
+ var needUpdate = false;
+
+ if (waitingForImages && svg.ImagesLoaded()) {
+ waitingForImages = false;
+ needUpdate = true;
+ }
+
+ // need update from mouse events?
+ if (svg.opts == null || svg.opts['ignoreMouse'] != true) {
+ needUpdate = needUpdate | svg.Mouse.hasEvents();
+ }
+
+ // need update from animations?
+ if (svg.opts == null || svg.opts['ignoreAnimation'] != true) {
+ for (var i=0; i<svg.Animations.length; i++) {
+ needUpdate = needUpdate | svg.Animations[i].update(1000 / svg.FRAMERATE);
+ }
+ }
+
+ // need update from redraw?
+ if (svg.opts != null && typeof(svg.opts['forceRedraw']) == 'function') {
+ if (svg.opts['forceRedraw']() == true) needUpdate = true;
+ }
+
+ // render if needed
+ if (needUpdate) {
+ draw();
+ svg.Mouse.runEvents(); // run and clear our events
+ }
+ }, 1000 / svg.FRAMERATE);
+ }
+
+ svg.stop = function() {
+ if (svg.intervalID) {
+ clearInterval(svg.intervalID);
+ }
+ }
+
+ svg.Mouse = new (function() {
+ this.events = [];
+ this.hasEvents = function() { return this.events.length != 0; }
+
+ this.onclick = function(x, y) {
+ this.events.push({ type: 'onclick', x: x, y: y,
+ run: function(e) { if (e.onclick) e.onclick(); }
+ });
+ }
+
+ this.onmousemove = function(x, y) {
+ this.events.push({ type: 'onmousemove', x: x, y: y,
+ run: function(e) { if (e.onmousemove) e.onmousemove(); }
+ });
+ }
+
+ this.eventElements = [];
+
+ this.checkPath = function(element, ctx) {
+ for (var i=0; i<this.events.length; i++) {
+ var e = this.events[i];
+ if (ctx.isPointInPath && ctx.isPointInPath(e.x, e.y)) this.eventElements[i] = element;
+ }
+ }
+
+ this.checkBoundingBox = function(element, bb) {
+ for (var i=0; i<this.events.length; i++) {
+ var e = this.events[i];
+ if (bb.isPointInBox(e.x, e.y)) this.eventElements[i] = element;
+ }
+ }
+
+ this.runEvents = function() {
+ svg.ctx.canvas.style.cursor = '';
+
+ for (var i=0; i<this.events.length; i++) {
+ var e = this.events[i];
+ var element = this.eventElements[i];
+ while (element) {
+ e.run(element);
+ element = element.parent;
+ }
+ }
+
+ // done running, clear
+ this.events = [];
+ this.eventElements = [];
+ }
+ });
+
+ return svg;
+ }
+})();
+
+if (CanvasRenderingContext2D) {
+ CanvasRenderingContext2D.prototype.drawSvg = function(s, dx, dy, dw, dh) {
+ canvg(this.canvas, s, {
+ ignoreMouse: true,
+ ignoreAnimation: true,
+ ignoreDimensions: true,
+ ignoreClear: true,
+ offsetX: dx,
+ offsetY: dy,
+ scaleWidth: dw,
+ scaleHeight: dh
+ });
+ }
+}
+
+/**
+ * A class to parse color values
+ * @author Stoyan Stefanov <sstoo@gmail.com>
+ * @link http://www.phpied.com/rgb-color-parser-in-javascript/
+ * @license Use it if you like it
+ */
+function RGBColor(color_string)
+{
+ this.ok = false;
+
+ // strip any leading #
+ if (color_string.charAt(0) == '#') { // remove # if any
+ color_string = color_string.substr(1,6);
+ }
+
+ color_string = color_string.replace(/ /g,'');
+ color_string = color_string.toLowerCase();
+
+ // before getting into regexps, try simple matches
+ // and overwrite the input
+ var simple_colors = {
+ 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',
+ darkkhaki: 'bdb76b',
+ darkmagenta: '8b008b',
+ darkolivegreen: '556b2f',
+ darkorange: 'ff8c00',
+ darkorchid: '9932cc',
+ darkred: '8b0000',
+ darksalmon: 'e9967a',
+ darkseagreen: '8fbc8f',
+ darkslateblue: '483d8b',
+ darkslategray: '2f4f4f',
+ darkturquoise: '00ced1',
+ darkviolet: '9400d3',
+ deeppink: 'ff1493',
+ deepskyblue: '00bfff',
+ dimgray: '696969',
+ dodgerblue: '1e90ff',
+ feldspar: 'd19275',
+ firebrick: 'b22222',
+ floralwhite: 'fffaf0',
+ forestgreen: '228b22',
+ fuchsia: 'ff00ff',
+ gainsboro: 'dcdcdc',
+ ghostwhite: 'f8f8ff',
+ gold: 'ffd700',
+ goldenrod: 'daa520',
+ gray: '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',
+ lightgrey: 'd3d3d3',
+ lightgreen: '90ee90',
+ lightpink: 'ffb6c1',
+ lightsalmon: 'ffa07a',
+ lightseagreen: '20b2aa',
+ lightskyblue: '87cefa',
+ lightslateblue: '8470ff',
+ lightslategray: '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',
+ snow: 'fffafa',
+ springgreen: '00ff7f',
+ steelblue: '4682b4',
+ tan: 'd2b48c',
+ teal: '008080',
+ thistle: 'd8bfd8',
+ tomato: 'ff6347',
+ turquoise: '40e0d0',
+ violet: 'ee82ee',
+ violetred: 'd02090',
+ wheat: 'f5deb3',
+ white: 'ffffff',
+ whitesmoke: 'f5f5f5',
+ yellow: 'ffff00',
+ yellowgreen: '9acd32'
+ };
+ for (var key in simple_colors) {
+ if (color_string == key) {
+ color_string = simple_colors[key];
+ }
+ }
+ // emd of simple type-in colors
+
+ // array of color definition objects
+ var color_defs = [
+ {
+ re: /^rgb\((\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3})\)$/,
+ example: ['rgb(123, 234, 45)', 'rgb(255,234,245)'],
+ process: function (bits){
+ return [
+ parseInt(bits[1]),
+ parseInt(bits[2]),
+ parseInt(bits[3])
+ ];
+ }
+ },
+ {
+ re: /^(\w{2})(\w{2})(\w{2})$/,
+ example: ['#00ff00', '336699'],
+ process: function (bits){
+ return [
+ parseInt(bits[1], 16),
+ parseInt(bits[2], 16),
+ parseInt(bits[3], 16)
+ ];
+ }
+ },
+ {
+ re: /^(\w{1})(\w{1})(\w{1})$/,
+ example: ['#fb0', 'f0f'],
+ process: function (bits){
+ return [
+ parseInt(bits[1] + bits[1], 16),
+ parseInt(bits[2] + bits[2], 16),
+ parseInt(bits[3] + bits[3], 16)
+ ];
+ }
+ }
+ ];
+
+ // search through the definitions to find a match
+ for (var i = 0; i < color_defs.length; i++) {
+ var re = color_defs[i].re;
+ var processor = color_defs[i].process;
+ var bits = re.exec(color_string);
+ if (bits) {
+ channels = processor(bits);
+ this.r = channels[0];
+ this.g = channels[1];
+ this.b = channels[2];
+ this.ok = true;
+ }
+
+ }
+
+ // validate/cleanup values
+ this.r = (this.r < 0 || isNaN(this.r)) ? 0 : ((this.r > 255) ? 255 : this.r);
+ this.g = (this.g < 0 || isNaN(this.g)) ? 0 : ((this.g > 255) ? 255 : this.g);
+ this.b = (this.b < 0 || isNaN(this.b)) ? 0 : ((this.b > 255) ? 255 : this.b);
+
+ // some getters
+ this.toRGB = function () {
+ return 'rgb(' + this.r + ', ' + this.g + ', ' + this.b + ')';
+ }
+ this.toHex = function () {
+ var r = this.r.toString(16);
+ var g = this.g.toString(16);
+ var b = this.b.toString(16);
+ if (r.length == 1) r = '0' + r;
+ if (g.length == 1) g = '0' + g;
+ if (b.length == 1) b = '0' + b;
+ return '#' + r + g + b;
+ }
+
+ // help
+ this.getHelpXML = function () {
+
+ var examples = new Array();
+ // add regexps
+ for (var i = 0; i < color_defs.length; i++) {
+ var example = color_defs[i].example;
+ for (var j = 0; j < example.length; j++) {
+ examples[examples.length] = example[j];
+ }
+ }
+ // add type-in colors
+ for (var sc in simple_colors) {
+ examples[examples.length] = sc;
+ }
+
+ var xml = document.createElement('ul');
+ xml.setAttribute('id', 'rgbcolor-examples');
+ for (var i = 0; i < examples.length; i++) {
+ try {
+ var list_item = document.createElement('li');
+ var list_color = new RGBColor(examples[i]);
+ var example_div = document.createElement('div');
+ example_div.style.cssText =
+ 'margin: 3px; '
+ + 'border: 1px solid black; '
+ + 'background:' + list_color.toHex() + '; '
+ + 'color:' + list_color.toHex()
+ ;
+ example_div.appendChild(document.createTextNode('test'));
+ var list_item_value = document.createTextNode(
+ ' ' + examples[i] + ' -> ' + list_color.toRGB() + ' -> ' + list_color.toHex()
+ );
+ list_item.appendChild(example_div);
+ list_item.appendChild(list_item_value);
+ xml.appendChild(list_item);
+
+ } catch(e){}
+ }
+ return xml;
+
+ }
+
+} \ No newline at end of file
diff --git a/js/chart.js b/js/chart.js
new file mode 100644
index 0000000000..fb0d9af584
--- /dev/null
+++ b/js/chart.js
@@ -0,0 +1,546 @@
+/**
+ * Chart type enumerations
+ */
+var ChartType = {
+ LINE : 'line',
+ SPLINE : 'spline',
+ AREA : 'area',
+ BAR : 'bar',
+ COLUMN : 'column',
+ PIE : 'pie',
+ TIMELINE: 'timeline'
+};
+
+/**
+ * Abstract chart factory which defines the contract for chart factories
+ */
+var ChartFactory = function () {
+};
+ChartFactory.prototype = {
+ createChart : function (type, options) {
+ throw new Error("createChart must be implemented by a subclass");
+ }
+};
+
+/**
+ * Abstract chart which defines the contract for charts
+ *
+ * @param elementId
+ * id of the div element the chart is drawn in
+ */
+var Chart = function (elementId) {
+ this.elementId = elementId;
+};
+Chart.prototype = {
+ draw : function (data, options) {
+ throw new Error("draw must be implemented by a subclass");
+ },
+ redraw : function (options) {
+ throw new Error("redraw must be implemented by a subclass");
+ },
+ destroy : function () {
+ throw new Error("destroy must be implemented by a subclass");
+ }
+};
+
+/**
+ * Abstract representation of charts that operates on DataTable where,<br />
+ * <ul>
+ * <li>First column provides index to the data.</li>
+ * <li>Each subsequent columns are of type
+ * <code>ColumnType.NUMBER<code> and represents a data series.</li>
+ * </ul>
+ * Line chart, area chart, bar chart, column chart are typical examples.
+ *
+ * @param elementId
+ * id of the div element the chart is drawn in
+ */
+var BaseChart = function (elementId) {
+ Chart.call(this, elementId);
+};
+BaseChart.prototype = new Chart();
+BaseChart.prototype.constructor = BaseChart;
+BaseChart.prototype.validateColumns = function (dataTable) {
+ var columns = dataTable.getColumns();
+ if (columns.length < 2) {
+ throw new Error("Minimum of two columns are required for this chart");
+ }
+ for (var i = 1; i < columns.length; i++) {
+ if (columns[i].type != ColumnType.NUMBER) {
+ throw new Error("Column " + (i + 1) + " should be of type 'Number'");
+ }
+ }
+ return true;
+};
+
+/**
+ * Abstract pie chart
+ *
+ * @param elementId
+ * id of the div element the chart is drawn in
+ */
+var PieChart = function (elementId) {
+ BaseChart.call(this, elementId);
+};
+PieChart.prototype = new BaseChart();
+PieChart.prototype.constructor = PieChart;
+PieChart.prototype.validateColumns = function (dataTable) {
+ var columns = dataTable.getColumns();
+ if (columns.length > 2) {
+ throw new Error("Pie charts can draw only one series");
+ }
+ return BaseChart.prototype.validateColumns.call(this, dataTable);
+};
+
+/**
+ * Abstract timeline chart
+ *
+ * @param elementId
+ * id of the div element the chart is drawn in
+ */
+var TimelineChart = function (elementId) {
+ BaseChart.call(this, elementId);
+};
+TimelineChart.prototype = new BaseChart();
+TimelineChart.prototype.constructor = TimelineChart;
+TimelineChart.prototype.validateColumns = function (dataTable) {
+ var result = BaseChart.prototype.validateColumns.call(this, dataTable);
+ if (result) {
+ var columns = dataTable.getColumns();
+ if (columns[0].type != ColumnType.DATE) {
+ throw new Error("First column of timeline chart need to be a date column");
+ }
+ }
+ return result;
+};
+
+/**
+ * The data table contains column information and data for the chart.
+ */
+var DataTable = function () {
+ var columns = [];
+ var data;
+
+ this.addColumn = function (type, name) {
+ columns.push({
+ 'type' : type,
+ 'name' : name
+ });
+ };
+
+ this.getColumns = function () {
+ return columns;
+ };
+
+ this.setData = function (rows) {
+ data = rows;
+ fillMissingValues();
+ };
+
+ this.getData = function () {
+ return data;
+ };
+
+ var fillMissingValues = function () {
+ if (columns.length === 0) {
+ throw new Error("Set columns first");
+ }
+ var row, column;
+ for (var i = 0; i < data.length; i++) {
+ row = data[i];
+ if (row.length > columns.length) {
+ row.splice(columns.length - 1, row.length - columns.length);
+ } else if (row.length < columns.length) {
+ for (var j = row.length; j < columns.length; j++) {
+ row.push(null);
+ }
+ }
+ }
+ };
+};
+
+/**
+ * Column type enumeration
+ */
+var ColumnType = {
+ STRING : 'string',
+ NUMBER : 'number',
+ BOOLEAN : 'boolean',
+ DATE : 'date'
+};
+
+/*******************************************************************************
+ * JQPlot specific code
+ ******************************************************************************/
+
+/**
+ * Chart factory that returns JQPlotCharts
+ */
+var JQPlotChartFactory = function () {
+};
+JQPlotChartFactory.prototype = new ChartFactory();
+JQPlotChartFactory.prototype.createChart = function (type, elementId) {
+ var chart;
+ switch (type) {
+ case ChartType.LINE:
+ chart = new JQPlotLineChart(elementId);
+ break;
+ case ChartType.SPLINE:
+ chart = new JQPlotSplineChart(elementId);
+ break;
+ case ChartType.TIMELINE:
+ chart = new JQPlotTimelineChart(elementId);
+ break;
+ case ChartType.AREA:
+ chart = new JQPlotAreaChart(elementId);
+ break;
+ case ChartType.BAR:
+ chart = new JQPlotBarChart(elementId);
+ break;
+ case ChartType.COLUMN:
+ chart = new JQPlotColumnChart(elementId);
+ break;
+ case ChartType.PIE:
+ chart = new JQPlotPieChart(elementId);
+ break;
+ }
+
+ return chart;
+};
+
+/**
+ * Abstract JQplot chart
+ *
+ * @param elementId
+ * id of the div element the chart is drawn in
+ */
+var JQPlotChart = function (elementId) {
+ Chart.call(this, elementId);
+ this.plot = null;
+ this.validator;
+};
+JQPlotChart.prototype = new Chart();
+JQPlotChart.prototype.constructor = JQPlotChart;
+JQPlotChart.prototype.draw = function (data, options) {
+ if (this.validator.validateColumns(data)) {
+ this.plot = $.jqplot(this.elementId, this.prepareData(data), this
+ .populateOptions(data, options));
+ }
+};
+JQPlotChart.prototype.destroy = function () {
+ if (this.plot !== null) {
+ this.plot.destroy();
+ }
+};
+JQPlotChart.prototype.redraw = function (options) {
+ if (this.plot !== null) {
+ this.plot.replot(options);
+ }
+};
+JQPlotChart.prototype.populateOptions = function (dataTable, options) {
+ throw new Error("populateOptions must be implemented by a subclass");
+};
+JQPlotChart.prototype.prepareData = function (dataTable) {
+ throw new Error("prepareData must be implemented by a subclass");
+};
+
+/**
+ * JQPlot line chart
+ *
+ * @param elementId
+ * id of the div element the chart is drawn in
+ */
+var JQPlotLineChart = function (elementId) {
+ JQPlotChart.call(this, elementId);
+ this.validator = BaseChart.prototype;
+};
+JQPlotLineChart.prototype = new JQPlotChart();
+JQPlotLineChart.prototype.constructor = JQPlotLineChart;
+
+JQPlotLineChart.prototype.populateOptions = function (dataTable, options) {
+ var columns = dataTable.getColumns();
+ var optional = {
+ axes : {
+ xaxis : {
+ label : columns[0].name,
+ renderer : $.jqplot.CategoryAxisRenderer,
+ ticks : []
+ },
+ yaxis : {
+ label : (columns.length == 2 ? columns[1].name : 'Values'),
+ labelRenderer : $.jqplot.CanvasAxisLabelRenderer
+ }
+ },
+ series : []
+ };
+ $.extend(true, optional, options);
+
+ if (optional.series.length === 0) {
+ for (var i = 1; i < columns.length; i++) {
+ optional.series.push({
+ label : columns[i].name.toString()
+ });
+ }
+ }
+ if (optional.axes.xaxis.ticks.length === 0) {
+ var data = dataTable.getData();
+ for (var i = 0; i < data.length; i++) {
+ optional.axes.xaxis.ticks.push(data[i][0].toString());
+ }
+ }
+ return optional;
+};
+
+JQPlotLineChart.prototype.prepareData = function (dataTable) {
+ var data = dataTable.getData(), row;
+ var retData = [], retRow;
+ for (var i = 0; i < data.length; i++) {
+ row = data[i];
+ for (var j = 1; j < row.length; j++) {
+ retRow = retData[j - 1];
+ if (retRow === undefined) {
+ retRow = [];
+ retData[j - 1] = retRow;
+ }
+ retRow.push(row[j]);
+ }
+ }
+ return retData;
+};
+
+/**
+ * JQPlot spline chart
+ *
+ * @param elementId
+ * id of the div element the chart is drawn in
+ */
+var JQPlotSplineChart = function (elementId) {
+ JQPlotLineChart.call(this, elementId);
+};
+JQPlotSplineChart.prototype = new JQPlotLineChart();
+JQPlotSplineChart.prototype.constructor = JQPlotSplineChart;
+
+JQPlotSplineChart.prototype.populateOptions = function (dataTable, options) {
+ var optional = {};
+ var opt = JQPlotLineChart.prototype.populateOptions.call(this, dataTable,
+ options);
+ var compulsory = {
+ seriesDefaults : {
+ rendererOptions : {
+ smooth : true
+ }
+ }
+ };
+ $.extend(true, optional, opt, compulsory);
+ return optional;
+};
+
+/**
+ * JQPlot timeline chart
+ *
+ * @param elementId
+ * id of the div element the chart is drawn in
+ */
+var JQPlotTimelineChart = function (elementId) {
+ JQPlotLineChart.call(this, elementId);
+ this.validator = TimelineChart.prototype;
+};
+JQPlotTimelineChart.prototype = new JQPlotLineChart();
+JQPlotTimelineChart.prototype.constructor = JQPlotAreaChart;
+
+JQPlotTimelineChart.prototype.populateOptions = function (dataTable, options) {
+ var optional = {
+ axes : {
+ xaxis : {
+ tickOptions : {
+ formatString: '%b %#d, %y'
+ }
+ }
+ }
+ };
+ var opt = JQPlotLineChart.prototype.populateOptions.call(this, dataTable, options);
+ var compulsory = {
+ axes : {
+ xaxis : {
+ renderer : $.jqplot.DateAxisRenderer
+ }
+ }
+ };
+ $.extend(true, optional, opt, compulsory);
+ return optional;
+};
+
+JQPlotTimelineChart.prototype.prepareData = function (dataTable) {
+ var data = dataTable.getData(), row, d;
+ var retData = [], retRow;
+ for (var i = 0; i < data.length; i++) {
+ row = data[i];
+ d = row[0];
+ for (var j = 1; j < row.length; j++) {
+ retRow = retData[j - 1];
+ if (retRow === undefined) {
+ retRow = [];
+ retData[j - 1] = retRow;
+ }
+ if (d !== null) {
+ retRow.push([d.getTime(), row[j]]);
+ }
+ }
+ }
+ return retData;
+};
+
+/**
+ * JQPlot area chart
+ *
+ * @param elementId
+ * id of the div element the chart is drawn in
+ */
+var JQPlotAreaChart = function (elementId) {
+ JQPlotLineChart.call(this, elementId);
+};
+JQPlotAreaChart.prototype = new JQPlotLineChart();
+JQPlotAreaChart.prototype.constructor = JQPlotAreaChart;
+
+JQPlotAreaChart.prototype.populateOptions = function (dataTable, options) {
+ var optional = {
+ seriesDefaults : {
+ fillToZero : true
+ }
+ };
+ var opt = JQPlotLineChart.prototype.populateOptions.call(this, dataTable,
+ options);
+ var compulsory = {
+ seriesDefaults : {
+ fill : true
+ }
+ };
+ $.extend(true, optional, opt, compulsory);
+ return optional;
+};
+
+/**
+ * JQPlot column chart
+ *
+ * @param elementId
+ * id of the div element the chart is drawn in
+ */
+var JQPlotColumnChart = function (elementId) {
+ JQPlotLineChart.call(this, elementId);
+};
+JQPlotColumnChart.prototype = new JQPlotLineChart();
+JQPlotColumnChart.prototype.constructor = JQPlotColumnChart;
+
+JQPlotColumnChart.prototype.populateOptions = function (dataTable, options) {
+ var optional = {
+ seriesDefaults : {
+ fillToZero : true
+ }
+ };
+ var opt = JQPlotLineChart.prototype.populateOptions.call(this, dataTable,
+ options);
+ var compulsory = {
+ seriesDefaults : {
+ renderer : $.jqplot.BarRenderer
+ }
+ };
+ $.extend(true, optional, opt, compulsory);
+ return optional;
+};
+
+/**
+ * JQPlot bar chart
+ *
+ * @param elementId
+ * id of the div element the chart is drawn in
+ */
+var JQPlotBarChart = function (elementId) {
+ JQPlotLineChart.call(this, elementId);
+};
+JQPlotBarChart.prototype = new JQPlotLineChart();
+JQPlotBarChart.prototype.constructor = JQPlotBarChart;
+
+JQPlotBarChart.prototype.populateOptions = function (dataTable, options) {
+ var columns = dataTable.getColumns();
+ var optional = {
+ axes : {
+ yaxis : {
+ label : columns[0].name,
+ labelRenderer : $.jqplot.CanvasAxisLabelRenderer,
+ renderer : $.jqplot.CategoryAxisRenderer,
+ ticks : []
+ },
+ xaxis : {
+ label : (columns.length == 2 ? columns[1].name : 'Values'),
+ labelRenderer : $.jqplot.CanvasAxisLabelRenderer
+ }
+ },
+ series : [],
+ seriesDefaults : {
+ fillToZero : true
+ }
+ };
+ var compulsory = {
+ seriesDefaults : {
+ renderer : $.jqplot.BarRenderer,
+ rendererOptions : {
+ barDirection : 'horizontal'
+ }
+ }
+ };
+ $.extend(true, optional, options, compulsory);
+
+ if (optional.axes.yaxis.ticks.length === 0) {
+ var data = dataTable.getData();
+ for (var i = 0; i < data.length; i++) {
+ optional.axes.yaxis.ticks.push(data[i][0].toString());
+ }
+ }
+ if (optional.series.length === 0) {
+ for (var i = 1; i < columns.length; i++) {
+ optional.series.push({
+ label : columns[i].name.toString()
+ });
+ }
+ }
+ return optional;
+};
+
+/**
+ * JQPlot pie chart
+ *
+ * @param elementId
+ * id of the div element the chart is drawn in
+ */
+var JQPlotPieChart = function (elementId) {
+ JQPlotChart.call(this, elementId);
+ this.validator = PieChart.prototype;
+};
+JQPlotPieChart.prototype = new JQPlotChart();
+JQPlotPieChart.prototype.constructor = JQPlotPieChart;
+
+JQPlotPieChart.prototype.populateOptions = function (dataTable, options) {
+ var optional = {
+ highlighter: {
+ formatString:'%s, %d',
+ useAxesFormatters: false
+ }
+ };
+ var compulsory = {
+ seriesDefaults : {
+ renderer : $.jqplot.PieRenderer
+ }
+ };
+ $.extend(true, optional, options, compulsory);
+ return optional;
+};
+
+JQPlotPieChart.prototype.prepareData = function (dataTable) {
+ var data = dataTable.getData(), row;
+ var retData = [];
+ for (var i = 0; i < data.length; i++) {
+ row = data[i];
+ retData.push([ row[0], row[1] ]);
+ }
+ return [ retData ];
+};
diff --git a/js/codemirror/LICENSE b/js/codemirror/LICENSE
new file mode 100644
index 0000000000..482d55eb73
--- /dev/null
+++ b/js/codemirror/LICENSE
@@ -0,0 +1,23 @@
+Copyright (C) 2013 by Marijn Haverbeke <marijnh@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.
+
+Please note that some subdirectories of the CodeMirror distribution
+include their own LICENSE files, and are released under different
+licences.
diff --git a/js/codemirror/addon/runmode/runmode.js b/js/codemirror/addon/runmode/runmode.js
new file mode 100644
index 0000000000..a7da6d718f
--- /dev/null
+++ b/js/codemirror/addon/runmode/runmode.js
@@ -0,0 +1,56 @@
+CodeMirror.runMode = function(string, modespec, callback, options) {
+ var mode = CodeMirror.getMode(CodeMirror.defaults, modespec);
+ var ie = /MSIE \d/.test(navigator.userAgent);
+ var ie_lt9 = ie && (document.documentMode == null || document.documentMode < 9);
+
+ if (callback.nodeType == 1) {
+ var tabSize = (options && options.tabSize) || CodeMirror.defaults.tabSize;
+ var node = callback, col = 0;
+ node.innerHTML = "";
+ callback = function(text, style) {
+ if (text == "\n") {
+ // Emitting LF or CRLF on IE8 or earlier results in an incorrect display.
+ // Emitting a carriage return makes everything ok.
+ node.appendChild(document.createTextNode(ie_lt9 ? '\r' : text));
+ col = 0;
+ return;
+ }
+ var content = "";
+ // replace tabs
+ for (var pos = 0;;) {
+ var idx = text.indexOf("\t", pos);
+ if (idx == -1) {
+ content += text.slice(pos);
+ col += text.length - pos;
+ break;
+ } else {
+ col += idx - pos;
+ content += text.slice(pos, idx);
+ var size = tabSize - col % tabSize;
+ col += size;
+ for (var i = 0; i < size; ++i) content += " ";
+ pos = idx + 1;
+ }
+ }
+
+ if (style) {
+ var sp = node.appendChild(document.createElement("span"));
+ sp.className = "cm-" + style.replace(/ +/g, " cm-");
+ sp.appendChild(document.createTextNode(content));
+ } else {
+ node.appendChild(document.createTextNode(content));
+ }
+ };
+ }
+
+ var lines = CodeMirror.splitLines(string), state = CodeMirror.startState(mode);
+ for (var i = 0, e = lines.length; i < e; ++i) {
+ if (i) callback("\n");
+ var stream = new CodeMirror.StringStream(lines[i]);
+ while (!stream.eol()) {
+ var style = mode.token(stream, state);
+ callback(stream.current(), style, i, stream.start);
+ stream.start = stream.pos;
+ }
+ }
+};
diff --git a/js/codemirror/lib/codemirror.js b/js/codemirror/lib/codemirror.js
new file mode 100644
index 0000000000..1d0d996310
--- /dev/null
+++ b/js/codemirror/lib/codemirror.js
@@ -0,0 +1,5799 @@
+// CodeMirror version 3.15
+//
+// CodeMirror is the only global var we claim
+window.CodeMirror = (function() {
+ "use strict";
+
+ // BROWSER SNIFFING
+
+ // Crude, but necessary to handle a number of hard-to-feature-detect
+ // bugs and behavior differences.
+ var gecko = /gecko\/\d/i.test(navigator.userAgent);
+ var ie = /MSIE \d/.test(navigator.userAgent);
+ var ie_lt8 = ie && (document.documentMode == null || document.documentMode < 8);
+ var ie_lt9 = ie && (document.documentMode == null || document.documentMode < 9);
+ var webkit = /WebKit\//.test(navigator.userAgent);
+ var qtwebkit = webkit && /Qt\/\d+\.\d+/.test(navigator.userAgent);
+ var chrome = /Chrome\//.test(navigator.userAgent);
+ var opera = /Opera\//.test(navigator.userAgent);
+ var safari = /Apple Computer/.test(navigator.vendor);
+ var khtml = /KHTML\//.test(navigator.userAgent);
+ var mac_geLion = /Mac OS X 1\d\D([7-9]|\d\d)\D/.test(navigator.userAgent);
+ var mac_geMountainLion = /Mac OS X 1\d\D([8-9]|\d\d)\D/.test(navigator.userAgent);
+ var phantom = /PhantomJS/.test(navigator.userAgent);
+
+ var ios = /AppleWebKit/.test(navigator.userAgent) && /Mobile\/\w+/.test(navigator.userAgent);
+ // This is woefully incomplete. Suggestions for alternative methods welcome.
+ var mobile = ios || /Android|webOS|BlackBerry|Opera Mini|Opera Mobi|IEMobile/i.test(navigator.userAgent);
+ var mac = ios || /Mac/.test(navigator.platform);
+ var windows = /windows/i.test(navigator.platform);
+
+ var opera_version = opera && navigator.userAgent.match(/Version\/(\d*\.\d*)/);
+ if (opera_version) opera_version = Number(opera_version[1]);
+ if (opera_version && opera_version >= 15) { opera = false; webkit = true; }
+ // Some browsers use the wrong event properties to signal cmd/ctrl on OS X
+ var flipCtrlCmd = mac && (qtwebkit || opera && (opera_version == null || opera_version < 12.11));
+ var captureMiddleClick = gecko || (ie && !ie_lt9);
+
+ // Optimize some code when these features are not used
+ var sawReadOnlySpans = false, sawCollapsedSpans = false;
+
+ // CONSTRUCTOR
+
+ function CodeMirror(place, options) {
+ if (!(this instanceof CodeMirror)) return new CodeMirror(place, options);
+
+ this.options = options = options || {};
+ // Determine effective options based on given values and defaults.
+ for (var opt in defaults) if (!options.hasOwnProperty(opt) && defaults.hasOwnProperty(opt))
+ options[opt] = defaults[opt];
+ setGuttersForLineNumbers(options);
+
+ var docStart = typeof options.value == "string" ? 0 : options.value.first;
+ var display = this.display = makeDisplay(place, docStart);
+ display.wrapper.CodeMirror = this;
+ updateGutters(this);
+ if (options.autofocus && !mobile) focusInput(this);
+
+ this.state = {keyMaps: [],
+ overlays: [],
+ modeGen: 0,
+ overwrite: false, focused: false,
+ suppressEdits: false, pasteIncoming: false,
+ draggingText: false,
+ highlight: new Delayed()};
+
+ themeChanged(this);
+ if (options.lineWrapping)
+ this.display.wrapper.className += " CodeMirror-wrap";
+
+ var doc = options.value;
+ if (typeof doc == "string") doc = new Doc(options.value, options.mode);
+ operation(this, attachDoc)(this, doc);
+
+ // Override magic textarea content restore that IE sometimes does
+ // on our hidden textarea on reload
+ if (ie) setTimeout(bind(resetInput, this, true), 20);
+
+ registerEventHandlers(this);
+ // IE throws unspecified error in certain cases, when
+ // trying to access activeElement before onload
+ var hasFocus; try { hasFocus = (document.activeElement == display.input); } catch(e) { }
+ if (hasFocus || (options.autofocus && !mobile)) setTimeout(bind(onFocus, this), 20);
+ else onBlur(this);
+
+ operation(this, function() {
+ for (var opt in optionHandlers)
+ if (optionHandlers.propertyIsEnumerable(opt))
+ optionHandlers[opt](this, options[opt], Init);
+ for (var i = 0; i < initHooks.length; ++i) initHooks[i](this);
+ })();
+ }
+
+ // DISPLAY CONSTRUCTOR
+
+ function makeDisplay(place, docStart) {
+ var d = {};
+
+ var input = d.input = elt("textarea", null, null, "position: absolute; padding: 0; width: 1px; height: 1em; outline: none; font-size: 4px;");
+ if (webkit) input.style.width = "1000px";
+ else input.setAttribute("wrap", "off");
+ // if border: 0; -- iOS fails to open keyboard (issue #1287)
+ if (ios) input.style.border = "1px solid black";
+ input.setAttribute("autocorrect", "off"); input.setAttribute("autocapitalize", "off"); input.setAttribute("spellcheck", "false");
+
+ // Wraps and hides input textarea
+ d.inputDiv = elt("div", [input], null, "overflow: hidden; position: relative; width: 3px; height: 0px;");
+ // The actual fake scrollbars.
+ d.scrollbarH = elt("div", [elt("div", null, null, "height: 1px")], "CodeMirror-hscrollbar");
+ d.scrollbarV = elt("div", [elt("div", null, null, "width: 1px")], "CodeMirror-vscrollbar");
+ d.scrollbarFiller = elt("div", null, "CodeMirror-scrollbar-filler");
+ d.gutterFiller = elt("div", null, "CodeMirror-gutter-filler");
+ // DIVs containing the selection and the actual code
+ d.lineDiv = elt("div", null, "CodeMirror-code");
+ d.selectionDiv = elt("div", null, null, "position: relative; z-index: 1");
+ // Blinky cursor, and element used to ensure cursor fits at the end of a line
+ d.cursor = elt("div", "\u00a0", "CodeMirror-cursor");
+ // Secondary cursor, shown when on a 'jump' in bi-directional text
+ d.otherCursor = elt("div", "\u00a0", "CodeMirror-cursor CodeMirror-secondarycursor");
+ // Used to measure text size
+ d.measure = elt("div", null, "CodeMirror-measure");
+ // Wraps everything that needs to exist inside the vertically-padded coordinate system
+ d.lineSpace = elt("div", [d.measure, d.selectionDiv, d.lineDiv, d.cursor, d.otherCursor],
+ null, "position: relative; outline: none");
+ // Moved around its parent to cover visible view
+ d.mover = elt("div", [elt("div", [d.lineSpace], "CodeMirror-lines")], null, "position: relative");
+ // Set to the height of the text, causes scrolling
+ d.sizer = elt("div", [d.mover], "CodeMirror-sizer");
+ // D is needed because behavior of elts with overflow: auto and padding is inconsistent across browsers
+ d.heightForcer = elt("div", null, null, "position: absolute; height: " + scrollerCutOff + "px; width: 1px;");
+ // Will contain the gutters, if any
+ d.gutters = elt("div", null, "CodeMirror-gutters");
+ d.lineGutter = null;
+ // Provides scrolling
+ d.scroller = elt("div", [d.sizer, d.heightForcer, d.gutters], "CodeMirror-scroll");
+ d.scroller.setAttribute("tabIndex", "-1");
+ // The element in which the editor lives.
+ d.wrapper = elt("div", [d.inputDiv, d.scrollbarH, d.scrollbarV,
+ d.scrollbarFiller, d.gutterFiller, d.scroller], "CodeMirror");
+ // Work around IE7 z-index bug
+ if (ie_lt8) { d.gutters.style.zIndex = -1; d.scroller.style.paddingRight = 0; }
+ if (place.appendChild) place.appendChild(d.wrapper); else place(d.wrapper);
+
+ // Needed to hide big blue blinking cursor on Mobile Safari
+ if (ios) input.style.width = "0px";
+ if (!webkit) d.scroller.draggable = true;
+ // Needed to handle Tab key in KHTML
+ if (khtml) { d.inputDiv.style.height = "1px"; d.inputDiv.style.position = "absolute"; }
+ // Need to set a minimum width to see the scrollbar on IE7 (but must not set it on IE8).
+ else if (ie_lt8) d.scrollbarH.style.minWidth = d.scrollbarV.style.minWidth = "18px";
+
+ // Current visible range (may be bigger than the view window).
+ d.viewOffset = d.lastSizeC = 0;
+ d.showingFrom = d.showingTo = docStart;
+
+ // Used to only resize the line number gutter when necessary (when
+ // the amount of lines crosses a boundary that makes its width change)
+ d.lineNumWidth = d.lineNumInnerWidth = d.lineNumChars = null;
+ // See readInput and resetInput
+ d.prevInput = "";
+ // Set to true when a non-horizontal-scrolling widget is added. As
+ // an optimization, widget aligning is skipped when d is false.
+ d.alignWidgets = false;
+ // Flag that indicates whether we currently expect input to appear
+ // (after some event like 'keypress' or 'input') and are polling
+ // intensively.
+ d.pollingFast = false;
+ // Self-resetting timeout for the poller
+ d.poll = new Delayed();
+
+ d.cachedCharWidth = d.cachedTextHeight = null;
+ d.measureLineCache = [];
+ d.measureLineCachePos = 0;
+
+ // Tracks when resetInput has punted to just putting a short
+ // string instead of the (large) selection.
+ d.inaccurateSelection = false;
+
+ // Tracks the maximum line length so that the horizontal scrollbar
+ // can be kept static when scrolling.
+ d.maxLine = null;
+ d.maxLineLength = 0;
+ d.maxLineChanged = false;
+
+ // Used for measuring wheel scrolling granularity
+ d.wheelDX = d.wheelDY = d.wheelStartX = d.wheelStartY = null;
+
+ return d;
+ }
+
+ // STATE UPDATES
+
+ // Used to get the editor into a consistent state again when options change.
+
+ function loadMode(cm) {
+ cm.doc.mode = CodeMirror.getMode(cm.options, cm.doc.modeOption);
+ cm.doc.iter(function(line) {
+ if (line.stateAfter) line.stateAfter = null;
+ if (line.styles) line.styles = null;
+ });
+ cm.doc.frontier = cm.doc.first;
+ startWorker(cm, 100);
+ cm.state.modeGen++;
+ if (cm.curOp) regChange(cm);
+ }
+
+ function wrappingChanged(cm) {
+ if (cm.options.lineWrapping) {
+ cm.display.wrapper.className += " CodeMirror-wrap";
+ cm.display.sizer.style.minWidth = "";
+ } else {
+ cm.display.wrapper.className = cm.display.wrapper.className.replace(" CodeMirror-wrap", "");
+ computeMaxLength(cm);
+ }
+ estimateLineHeights(cm);
+ regChange(cm);
+ clearCaches(cm);
+ setTimeout(function(){updateScrollbars(cm);}, 100);
+ }
+
+ function estimateHeight(cm) {
+ var th = textHeight(cm.display), wrapping = cm.options.lineWrapping;
+ var perLine = wrapping && Math.max(5, cm.display.scroller.clientWidth / charWidth(cm.display) - 3);
+ return function(line) {
+ if (lineIsHidden(cm.doc, line))
+ return 0;
+ else if (wrapping)
+ return (Math.ceil(line.text.length / perLine) || 1) * th;
+ else
+ return th;
+ };
+ }
+
+ function estimateLineHeights(cm) {
+ var doc = cm.doc, est = estimateHeight(cm);
+ doc.iter(function(line) {
+ var estHeight = est(line);
+ if (estHeight != line.height) updateLineHeight(line, estHeight);
+ });
+ }
+
+ function keyMapChanged(cm) {
+ var map = keyMap[cm.options.keyMap], style = map.style;
+ cm.display.wrapper.className = cm.display.wrapper.className.replace(/\s*cm-keymap-\S+/g, "") +
+ (style ? " cm-keymap-" + style : "");
+ cm.state.disableInput = map.disableInput;
+ }
+
+ function themeChanged(cm) {
+ cm.display.wrapper.className = cm.display.wrapper.className.replace(/\s*cm-s-\S+/g, "") +
+ cm.options.theme.replace(/(^|\s)\s*/g, " cm-s-");
+ clearCaches(cm);
+ }
+
+ function guttersChanged(cm) {
+ updateGutters(cm);
+ regChange(cm);
+ setTimeout(function(){alignHorizontally(cm);}, 20);
+ }
+
+ function updateGutters(cm) {
+ var gutters = cm.display.gutters, specs = cm.options.gutters;
+ removeChildren(gutters);
+ for (var i = 0; i < specs.length; ++i) {
+ var gutterClass = specs[i];
+ var gElt = gutters.appendChild(elt("div", null, "CodeMirror-gutter " + gutterClass));
+ if (gutterClass == "CodeMirror-linenumbers") {
+ cm.display.lineGutter = gElt;
+ gElt.style.width = (cm.display.lineNumWidth || 1) + "px";
+ }
+ }
+ gutters.style.display = i ? "" : "none";
+ }
+
+ function lineLength(doc, line) {
+ if (line.height == 0) return 0;
+ var len = line.text.length, merged, cur = line;
+ while (merged = collapsedSpanAtStart(cur)) {
+ var found = merged.find();
+ cur = getLine(doc, found.from.line);
+ len += found.from.ch - found.to.ch;
+ }
+ cur = line;
+ while (merged = collapsedSpanAtEnd(cur)) {
+ var found = merged.find();
+ len -= cur.text.length - found.from.ch;
+ cur = getLine(doc, found.to.line);
+ len += cur.text.length - found.to.ch;
+ }
+ return len;
+ }
+
+ function computeMaxLength(cm) {
+ var d = cm.display, doc = cm.doc;
+ d.maxLine = getLine(doc, doc.first);
+ d.maxLineLength = lineLength(doc, d.maxLine);
+ d.maxLineChanged = true;
+ doc.iter(function(line) {
+ var len = lineLength(doc, line);
+ if (len > d.maxLineLength) {
+ d.maxLineLength = len;
+ d.maxLine = line;
+ }
+ });
+ }
+
+ // Make sure the gutters options contains the element
+ // "CodeMirror-linenumbers" when the lineNumbers option is true.
+ function setGuttersForLineNumbers(options) {
+ var found = false;
+ for (var i = 0; i < options.gutters.length; ++i) {
+ if (options.gutters[i] == "CodeMirror-linenumbers") {
+ if (options.lineNumbers) found = true;
+ else options.gutters.splice(i--, 1);
+ }
+ }
+ if (!found && options.lineNumbers)
+ options.gutters.push("CodeMirror-linenumbers");
+ }
+
+ // SCROLLBARS
+
+ // Re-synchronize the fake scrollbars with the actual size of the
+ // content. Optionally force a scrollTop.
+ function updateScrollbars(cm) {
+ var d = cm.display, docHeight = cm.doc.height;
+ var totalHeight = docHeight + paddingVert(d);
+ d.sizer.style.minHeight = d.heightForcer.style.top = totalHeight + "px";
+ d.gutters.style.height = Math.max(totalHeight, d.scroller.clientHeight - scrollerCutOff) + "px";
+ var scrollHeight = Math.max(totalHeight, d.scroller.scrollHeight);
+ var needsH = d.scroller.scrollWidth > (d.scroller.clientWidth + 1);
+ var needsV = scrollHeight > (d.scroller.clientHeight + 1);
+ if (needsV) {
+ d.scrollbarV.style.display = "block";
+ d.scrollbarV.style.bottom = needsH ? scrollbarWidth(d.measure) + "px" : "0";
+ d.scrollbarV.firstChild.style.height =
+ (scrollHeight - d.scroller.clientHeight + d.scrollbarV.clientHeight) + "px";
+ } else d.scrollbarV.style.display = "";
+ if (needsH) {
+ d.scrollbarH.style.display = "block";
+ d.scrollbarH.style.right = needsV ? scrollbarWidth(d.measure) + "px" : "0";
+ d.scrollbarH.firstChild.style.width =
+ (d.scroller.scrollWidth - d.scroller.clientWidth + d.scrollbarH.clientWidth) + "px";
+ } else d.scrollbarH.style.display = "";
+ if (needsH && needsV) {
+ d.scrollbarFiller.style.display = "block";
+ d.scrollbarFiller.style.height = d.scrollbarFiller.style.width = scrollbarWidth(d.measure) + "px";
+ } else d.scrollbarFiller.style.display = "";
+ if (needsH && cm.options.coverGutterNextToScrollbar && cm.options.fixedGutter) {
+ d.gutterFiller.style.display = "block";
+ d.gutterFiller.style.height = scrollbarWidth(d.measure) + "px";
+ d.gutterFiller.style.width = d.gutters.offsetWidth + "px";
+ } else d.gutterFiller.style.display = "";
+
+ if (mac_geLion && scrollbarWidth(d.measure) === 0)
+ d.scrollbarV.style.minWidth = d.scrollbarH.style.minHeight = mac_geMountainLion ? "18px" : "12px";
+ }
+
+ function visibleLines(display, doc, viewPort) {
+ var top = display.scroller.scrollTop, height = display.wrapper.clientHeight;
+ if (typeof viewPort == "number") top = viewPort;
+ else if (viewPort) {top = viewPort.top; height = viewPort.bottom - viewPort.top;}
+ top = Math.floor(top - paddingTop(display));
+ var bottom = Math.ceil(top + height);
+ return {from: lineAtHeight(doc, top), to: lineAtHeight(doc, bottom)};
+ }
+
+ // LINE NUMBERS
+
+ function alignHorizontally(cm) {
+ var display = cm.display;
+ if (!display.alignWidgets && (!display.gutters.firstChild || !cm.options.fixedGutter)) return;
+ var comp = compensateForHScroll(display) - display.scroller.scrollLeft + cm.doc.scrollLeft;
+ var gutterW = display.gutters.offsetWidth, l = comp + "px";
+ for (var n = display.lineDiv.firstChild; n; n = n.nextSibling) if (n.alignable) {
+ for (var i = 0, a = n.alignable; i < a.length; ++i) a[i].style.left = l;
+ }
+ if (cm.options.fixedGutter)
+ display.gutters.style.left = (comp + gutterW) + "px";
+ }
+
+ function maybeUpdateLineNumberWidth(cm) {
+ if (!cm.options.lineNumbers) return false;
+ var doc = cm.doc, last = lineNumberFor(cm.options, doc.first + doc.size - 1), display = cm.display;
+ if (last.length != display.lineNumChars) {
+ var test = display.measure.appendChild(elt("div", [elt("div", last)],
+ "CodeMirror-linenumber CodeMirror-gutter-elt"));
+ var innerW = test.firstChild.offsetWidth, padding = test.offsetWidth - innerW;
+ display.lineGutter.style.width = "";
+ display.lineNumInnerWidth = Math.max(innerW, display.lineGutter.offsetWidth - padding);
+ display.lineNumWidth = display.lineNumInnerWidth + padding;
+ display.lineNumChars = display.lineNumInnerWidth ? last.length : -1;
+ display.lineGutter.style.width = display.lineNumWidth + "px";
+ return true;
+ }
+ return false;
+ }
+
+ function lineNumberFor(options, i) {
+ return String(options.lineNumberFormatter(i + options.firstLineNumber));
+ }
+ function compensateForHScroll(display) {
+ return getRect(display.scroller).left - getRect(display.sizer).left;
+ }
+
+ // DISPLAY DRAWING
+
+ function updateDisplay(cm, changes, viewPort, forced) {
+ var oldFrom = cm.display.showingFrom, oldTo = cm.display.showingTo, updated;
+ var visible = visibleLines(cm.display, cm.doc, viewPort);
+ for (;;) {
+ if (!updateDisplayInner(cm, changes, visible, forced)) break;
+ forced = false;
+ updated = true;
+ updateSelection(cm);
+ updateScrollbars(cm);
+
+ // Clip forced viewport to actual scrollable area
+ if (viewPort)
+ viewPort = Math.min(cm.display.scroller.scrollHeight - cm.display.scroller.clientHeight,
+ typeof viewPort == "number" ? viewPort : viewPort.top);
+ visible = visibleLines(cm.display, cm.doc, viewPort);
+ if (visible.from >= cm.display.showingFrom && visible.to <= cm.display.showingTo)
+ break;
+ changes = [];
+ }
+
+ if (updated) {
+ signalLater(cm, "update", cm);
+ if (cm.display.showingFrom != oldFrom || cm.display.showingTo != oldTo)
+ signalLater(cm, "viewportChange", cm, cm.display.showingFrom, cm.display.showingTo);
+ }
+ return updated;
+ }
+
+ // Uses a set of changes plus the current scroll position to
+ // determine which DOM updates have to be made, and makes the
+ // updates.
+ function updateDisplayInner(cm, changes, visible, forced) {
+ var display = cm.display, doc = cm.doc;
+ if (!display.wrapper.clientWidth) {
+ display.showingFrom = display.showingTo = doc.first;
+ display.viewOffset = 0;
+ return;
+ }
+
+ // Bail out if the visible area is already rendered and nothing changed.
+ if (!forced && changes.length == 0 &&
+ visible.from > display.showingFrom && visible.to < display.showingTo)
+ return;
+
+ if (maybeUpdateLineNumberWidth(cm))
+ changes = [{from: doc.first, to: doc.first + doc.size}];
+ var gutterW = display.sizer.style.marginLeft = display.gutters.offsetWidth + "px";
+ display.scrollbarH.style.left = cm.options.fixedGutter ? gutterW : "0";
+
+ // Used to determine which lines need their line numbers updated
+ var positionsChangedFrom = Infinity;
+ if (cm.options.lineNumbers)
+ for (var i = 0; i < changes.length; ++i)
+ if (changes[i].diff) { positionsChangedFrom = changes[i].from; break; }
+
+ var end = doc.first + doc.size;
+ var from = Math.max(visible.from - cm.options.viewportMargin, doc.first);
+ var to = Math.min(end, visible.to + cm.options.viewportMargin);
+ if (display.showingFrom < from && from - display.showingFrom < 20) from = Math.max(doc.first, display.showingFrom);
+ if (display.showingTo > to && display.showingTo - to < 20) to = Math.min(end, display.showingTo);
+ if (sawCollapsedSpans) {
+ from = lineNo(visualLine(doc, getLine(doc, from)));
+ while (to < end && lineIsHidden(doc, getLine(doc, to))) ++to;
+ }
+
+ // Create a range of theoretically intact lines, and punch holes
+ // in that using the change info.
+ var intact = [{from: Math.max(display.showingFrom, doc.first),
+ to: Math.min(display.showingTo, end)}];
+ if (intact[0].from >= intact[0].to) intact = [];
+ else intact = computeIntact(intact, changes);
+ // When merged lines are present, we might have to reduce the
+ // intact ranges because changes in continued fragments of the
+ // intact lines do require the lines to be redrawn.
+ if (sawCollapsedSpans)
+ for (var i = 0; i < intact.length; ++i) {
+ var range = intact[i], merged;
+ while (merged = collapsedSpanAtEnd(getLine(doc, range.to - 1))) {
+ var newTo = merged.find().from.line;
+ if (newTo > range.from) range.to = newTo;
+ else { intact.splice(i--, 1); break; }
+ }
+ }
+
+ // Clip off the parts that won't be visible
+ var intactLines = 0;
+ for (var i = 0; i < intact.length; ++i) {
+ var range = intact[i];
+ if (range.from < from) range.from = from;
+ if (range.to > to) range.to = to;
+ if (range.from >= range.to) intact.splice(i--, 1);
+ else intactLines += range.to - range.from;
+ }
+ if (!forced && intactLines == to - from && from == display.showingFrom && to == display.showingTo) {
+ updateViewOffset(cm);
+ return;
+ }
+ intact.sort(function(a, b) {return a.from - b.from;});
+
+ // Avoid crashing on IE's "unspecified error" when in iframes
+ try {
+ var focused = document.activeElement;
+ } catch(e) {}
+ if (intactLines < (to - from) * .7) display.lineDiv.style.display = "none";
+ patchDisplay(cm, from, to, intact, positionsChangedFrom);
+ display.lineDiv.style.display = "";
+ if (focused && document.activeElement != focused && focused.offsetHeight) focused.focus();
+
+ var different = from != display.showingFrom || to != display.showingTo ||
+ display.lastSizeC != display.wrapper.clientHeight;
+ // This is just a bogus formula that detects when the editor is
+ // resized or the font size changes.
+ if (different) {
+ display.lastSizeC = display.wrapper.clientHeight;
+ startWorker(cm, 400);
+ }
+ display.showingFrom = from; display.showingTo = to;
+
+ updateHeightsInViewport(cm);
+ updateViewOffset(cm);
+
+ return true;
+ }
+
+ function updateHeightsInViewport(cm) {
+ var display = cm.display;
+ var prevBottom = display.lineDiv.offsetTop;
+ for (var node = display.lineDiv.firstChild, height; node; node = node.nextSibling) if (node.lineObj) {
+ if (ie_lt8) {
+ var bot = node.offsetTop + node.offsetHeight;
+ height = bot - prevBottom;
+ prevBottom = bot;
+ } else {
+ var box = getRect(node);
+ height = box.bottom - box.top;
+ }
+ var diff = node.lineObj.height - height;
+ if (height < 2) height = textHeight(display);
+ if (diff > .001 || diff < -.001) {
+ updateLineHeight(node.lineObj, height);
+ var widgets = node.lineObj.widgets;
+ if (widgets) for (var i = 0; i < widgets.length; ++i)
+ widgets[i].height = widgets[i].node.offsetHeight;
+ }
+ }
+ }
+
+ function updateViewOffset(cm) {
+ var off = cm.display.viewOffset = heightAtLine(cm, getLine(cm.doc, cm.display.showingFrom));
+ // Position the mover div to align with the current virtual scroll position
+ cm.display.mover.style.top = off + "px";
+ }
+
+ function computeIntact(intact, changes) {
+ for (var i = 0, l = changes.length || 0; i < l; ++i) {
+ var change = changes[i], intact2 = [], diff = change.diff || 0;
+ for (var j = 0, l2 = intact.length; j < l2; ++j) {
+ var range = intact[j];
+ if (change.to <= range.from && change.diff) {
+ intact2.push({from: range.from + diff, to: range.to + diff});
+ } else if (change.to <= range.from || change.from >= range.to) {
+ intact2.push(range);
+ } else {
+ if (change.from > range.from)
+ intact2.push({from: range.from, to: change.from});
+ if (change.to < range.to)
+ intact2.push({from: change.to + diff, to: range.to + diff});
+ }
+ }
+ intact = intact2;
+ }
+ return intact;
+ }
+
+ function getDimensions(cm) {
+ var d = cm.display, left = {}, width = {};
+ for (var n = d.gutters.firstChild, i = 0; n; n = n.nextSibling, ++i) {
+ left[cm.options.gutters[i]] = n.offsetLeft;
+ width[cm.options.gutters[i]] = n.offsetWidth;
+ }
+ return {fixedPos: compensateForHScroll(d),
+ gutterTotalWidth: d.gutters.offsetWidth,
+ gutterLeft: left,
+ gutterWidth: width,
+ wrapperWidth: d.wrapper.clientWidth};
+ }
+
+ function patchDisplay(cm, from, to, intact, updateNumbersFrom) {
+ var dims = getDimensions(cm);
+ var display = cm.display, lineNumbers = cm.options.lineNumbers;
+ if (!intact.length && (!webkit || !cm.display.currentWheelTarget))
+ removeChildren(display.lineDiv);
+ var container = display.lineDiv, cur = container.firstChild;
+
+ function rm(node) {
+ var next = node.nextSibling;
+ if (webkit && mac && cm.display.currentWheelTarget == node) {
+ node.style.display = "none";
+ node.lineObj = null;
+ } else {
+ node.parentNode.removeChild(node);
+ }
+ return next;
+ }
+
+ var nextIntact = intact.shift(), lineN = from;
+ cm.doc.iter(from, to, function(line) {
+ if (nextIntact && nextIntact.to == lineN) nextIntact = intact.shift();
+ if (lineIsHidden(cm.doc, line)) {
+ if (line.height != 0) updateLineHeight(line, 0);
+ if (line.widgets && cur.previousSibling) for (var i = 0; i < line.widgets.length; ++i) {
+ var w = line.widgets[i];
+ if (w.showIfHidden) {
+ var prev = cur.previousSibling;
+ if (/pre/i.test(prev.nodeName)) {
+ var wrap = elt("div", null, null, "position: relative");
+ prev.parentNode.replaceChild(wrap, prev);
+ wrap.appendChild(prev);
+ prev = wrap;
+ }
+ var wnode = prev.appendChild(elt("div", [w.node], "CodeMirror-linewidget"));
+ if (!w.handleMouseEvents) wnode.ignoreEvents = true;
+ positionLineWidget(w, wnode, prev, dims);
+ }
+ }
+ } else if (nextIntact && nextIntact.from <= lineN && nextIntact.to > lineN) {
+ // This line is intact. Skip to the actual node. Update its
+ // line number if needed.
+ while (cur.lineObj != line) cur = rm(cur);
+ if (lineNumbers && updateNumbersFrom <= lineN && cur.lineNumber)
+ setTextContent(cur.lineNumber, lineNumberFor(cm.options, lineN));
+ cur = cur.nextSibling;
+ } else {
+ // For lines with widgets, make an attempt to find and reuse
+ // the existing element, so that widgets aren't needlessly
+ // removed and re-inserted into the dom
+ if (line.widgets) for (var j = 0, search = cur, reuse; search && j < 20; ++j, search = search.nextSibling)
+ if (search.lineObj == line && /div/i.test(search.nodeName)) { reuse = search; break; }
+ // This line needs to be generated.
+ var lineNode = buildLineElement(cm, line, lineN, dims, reuse);
+ if (lineNode != reuse) {
+ container.insertBefore(lineNode, cur);
+ } else {
+ while (cur != reuse) cur = rm(cur);
+ cur = cur.nextSibling;
+ }
+
+ lineNode.lineObj = line;
+ }
+ ++lineN;
+ });
+ while (cur) cur = rm(cur);
+ }
+
+ function buildLineElement(cm, line, lineNo, dims, reuse) {
+ var lineElement = lineContent(cm, line);
+ var markers = line.gutterMarkers, display = cm.display, wrap;
+
+ if (!cm.options.lineNumbers && !markers && !line.bgClass && !line.wrapClass && !line.widgets)
+ return lineElement;
+
+ // Lines with gutter elements, widgets or a background class need
+ // to be wrapped again, and have the extra elements added to the
+ // wrapper div
+
+ if (reuse) {
+ reuse.alignable = null;
+ var isOk = true, widgetsSeen = 0, insertBefore = null;
+ for (var n = reuse.firstChild, next; n; n = next) {
+ next = n.nextSibling;
+ if (!/\bCodeMirror-linewidget\b/.test(n.className)) {
+ reuse.removeChild(n);
+ } else {
+ for (var i = 0; i < line.widgets.length; ++i) {
+ var widget = line.widgets[i];
+ if (widget.node == n.firstChild) {
+ if (!widget.above && !insertBefore) insertBefore = n;
+ positionLineWidget(widget, n, reuse, dims);
+ ++widgetsSeen;
+ break;
+ }
+ }
+ if (i == line.widgets.length) { isOk = false; break; }
+ }
+ }
+ reuse.insertBefore(lineElement, insertBefore);
+ if (isOk && widgetsSeen == line.widgets.length) {
+ wrap = reuse;
+ reuse.className = line.wrapClass || "";
+ }
+ }
+ if (!wrap) {
+ wrap = elt("div", null, line.wrapClass, "position: relative");
+ wrap.appendChild(lineElement);
+ }
+ // Kludge to make sure the styled element lies behind the selection (by z-index)
+ if (line.bgClass)
+ wrap.insertBefore(elt("div", null, line.bgClass + " CodeMirror-linebackground"), wrap.firstChild);
+ if (cm.options.lineNumbers || markers) {
+ var gutterWrap = wrap.insertBefore(elt("div", null, null, "position: absolute; left: " +
+ (cm.options.fixedGutter ? dims.fixedPos : -dims.gutterTotalWidth) + "px"),
+ wrap.firstChild);
+ if (cm.options.fixedGutter) (wrap.alignable || (wrap.alignable = [])).push(gutterWrap);
+ if (cm.options.lineNumbers && (!markers || !markers["CodeMirror-linenumbers"]))
+ wrap.lineNumber = gutterWrap.appendChild(
+ elt("div", lineNumberFor(cm.options, lineNo),
+ "CodeMirror-linenumber CodeMirror-gutter-elt",
+ "left: " + dims.gutterLeft["CodeMirror-linenumbers"] + "px; width: "
+ + display.lineNumInnerWidth + "px"));
+ if (markers)
+ for (var k = 0; k < cm.options.gutters.length; ++k) {
+ var id = cm.options.gutters[k], found = markers.hasOwnProperty(id) && markers[id];
+ if (found)
+ gutterWrap.appendChild(elt("div", [found], "CodeMirror-gutter-elt", "left: " +
+ dims.gutterLeft[id] + "px; width: " + dims.gutterWidth[id] + "px"));
+ }
+ }
+ if (ie_lt8) wrap.style.zIndex = 2;
+ if (line.widgets && wrap != reuse) for (var i = 0, ws = line.widgets; i < ws.length; ++i) {
+ var widget = ws[i], node = elt("div", [widget.node], "CodeMirror-linewidget");
+ if (!widget.handleMouseEvents) node.ignoreEvents = true;
+ positionLineWidget(widget, node, wrap, dims);
+ if (widget.above)
+ wrap.insertBefore(node, cm.options.lineNumbers && line.height != 0 ? gutterWrap : lineElement);
+ else
+ wrap.appendChild(node);
+ signalLater(widget, "redraw");
+ }
+ return wrap;
+ }
+
+ function positionLineWidget(widget, node, wrap, dims) {
+ if (widget.noHScroll) {
+ (wrap.alignable || (wrap.alignable = [])).push(node);
+ var width = dims.wrapperWidth;
+ node.style.left = dims.fixedPos + "px";
+ if (!widget.coverGutter) {
+ width -= dims.gutterTotalWidth;
+ node.style.paddingLeft = dims.gutterTotalWidth + "px";
+ }
+ node.style.width = width + "px";
+ }
+ if (widget.coverGutter) {
+ node.style.zIndex = 5;
+ node.style.position = "relative";
+ if (!widget.noHScroll) node.style.marginLeft = -dims.gutterTotalWidth + "px";
+ }
+ }
+
+ // SELECTION / CURSOR
+
+ function updateSelection(cm) {
+ var display = cm.display;
+ var collapsed = posEq(cm.doc.sel.from, cm.doc.sel.to);
+ if (collapsed || cm.options.showCursorWhenSelecting)
+ updateSelectionCursor(cm);
+ else
+ display.cursor.style.display = display.otherCursor.style.display = "none";
+ if (!collapsed)
+ updateSelectionRange(cm);
+ else
+ display.selectionDiv.style.display = "none";
+
+ // Move the hidden textarea near the cursor to prevent scrolling artifacts
+ if (cm.options.moveInputWithCursor) {
+ var headPos = cursorCoords(cm, cm.doc.sel.head, "div");
+ var wrapOff = getRect(display.wrapper), lineOff = getRect(display.lineDiv);
+ display.inputDiv.style.top = Math.max(0, Math.min(display.wrapper.clientHeight - 10,
+ headPos.top + lineOff.top - wrapOff.top)) + "px";
+ display.inputDiv.style.left = Math.max(0, Math.min(display.wrapper.clientWidth - 10,
+ headPos.left + lineOff.left - wrapOff.left)) + "px";
+ }
+ }
+
+ // No selection, plain cursor
+ function updateSelectionCursor(cm) {
+ var display = cm.display, pos = cursorCoords(cm, cm.doc.sel.head, "div");
+ display.cursor.style.left = pos.left + "px";
+ display.cursor.style.top = pos.top + "px";
+ display.cursor.style.height = Math.max(0, pos.bottom - pos.top) * cm.options.cursorHeight + "px";
+ display.cursor.style.display = "";
+
+ if (pos.other) {
+ display.otherCursor.style.display = "";
+ display.otherCursor.style.left = pos.other.left + "px";
+ display.otherCursor.style.top = pos.other.top + "px";
+ display.otherCursor.style.height = (pos.other.bottom - pos.other.top) * .85 + "px";
+ } else { display.otherCursor.style.display = "none"; }
+ }
+
+ // Highlight selection
+ function updateSelectionRange(cm) {
+ var display = cm.display, doc = cm.doc, sel = cm.doc.sel;
+ var fragment = document.createDocumentFragment();
+ var clientWidth = display.lineSpace.offsetWidth, pl = paddingLeft(cm.display);
+
+ function add(left, top, width, bottom) {
+ if (top < 0) top = 0;
+ fragment.appendChild(elt("div", null, "CodeMirror-selected", "position: absolute; left: " + left +
+ "px; top: " + top + "px; width: " + (width == null ? clientWidth - left : width) +
+ "px; height: " + (bottom - top) + "px"));
+ }
+
+ function drawForLine(line, fromArg, toArg) {
+ var lineObj = getLine(doc, line);
+ var lineLen = lineObj.text.length;
+ var start, end;
+ function coords(ch, bias) {
+ return charCoords(cm, Pos(line, ch), "div", lineObj, bias);
+ }
+
+ iterateBidiSections(getOrder(lineObj), fromArg || 0, toArg == null ? lineLen : toArg, function(from, to, dir) {
+ var leftPos = coords(from, "left"), rightPos, left, right;
+ if (from == to) {
+ rightPos = leftPos;
+ left = right = leftPos.left;
+ } else {
+ rightPos = coords(to - 1, "right");
+ if (dir == "rtl") { var tmp = leftPos; leftPos = rightPos; rightPos = tmp; }
+ left = leftPos.left;
+ right = rightPos.right;
+ }
+ if (fromArg == null && from == 0) left = pl;
+ if (rightPos.top - leftPos.top > 3) { // Different lines, draw top part
+ add(left, leftPos.top, null, leftPos.bottom);
+ left = pl;
+ if (leftPos.bottom < rightPos.top) add(left, leftPos.bottom, null, rightPos.top);
+ }
+ if (toArg == null && to == lineLen) right = clientWidth;
+ if (!start || leftPos.top < start.top || leftPos.top == start.top && leftPos.left < start.left)
+ start = leftPos;
+ if (!end || rightPos.bottom > end.bottom || rightPos.bottom == end.bottom && rightPos.right > end.right)
+ end = rightPos;
+ if (left < pl + 1) left = pl;
+ add(left, rightPos.top, right - left, rightPos.bottom);
+ });
+ return {start: start, end: end};
+ }
+
+ if (sel.from.line == sel.to.line) {
+ drawForLine(sel.from.line, sel.from.ch, sel.to.ch);
+ } else {
+ var fromLine = getLine(doc, sel.from.line), toLine = getLine(doc, sel.to.line);
+ var singleVLine = visualLine(doc, fromLine) == visualLine(doc, toLine);
+ var leftEnd = drawForLine(sel.from.line, sel.from.ch, singleVLine ? fromLine.text.length : null).end;
+ var rightStart = drawForLine(sel.to.line, singleVLine ? 0 : null, sel.to.ch).start;
+ if (singleVLine) {
+ if (leftEnd.top < rightStart.top - 2) {
+ add(leftEnd.right, leftEnd.top, null, leftEnd.bottom);
+ add(pl, rightStart.top, rightStart.left, rightStart.bottom);
+ } else {
+ add(leftEnd.right, leftEnd.top, rightStart.left - leftEnd.right, leftEnd.bottom);
+ }
+ }
+ if (leftEnd.bottom < rightStart.top)
+ add(pl, leftEnd.bottom, null, rightStart.top);
+ }
+
+ removeChildrenAndAdd(display.selectionDiv, fragment);
+ display.selectionDiv.style.display = "";
+ }
+
+ // Cursor-blinking
+ function restartBlink(cm) {
+ if (!cm.state.focused) return;
+ var display = cm.display;
+ clearInterval(display.blinker);
+ var on = true;
+ display.cursor.style.visibility = display.otherCursor.style.visibility = "";
+ display.blinker = setInterval(function() {
+ display.cursor.style.visibility = display.otherCursor.style.visibility = (on = !on) ? "" : "hidden";
+ }, cm.options.cursorBlinkRate);
+ }
+
+ // HIGHLIGHT WORKER
+
+ function startWorker(cm, time) {
+ if (cm.doc.mode.startState && cm.doc.frontier < cm.display.showingTo)
+ cm.state.highlight.set(time, bind(highlightWorker, cm));
+ }
+
+ function highlightWorker(cm) {
+ var doc = cm.doc;
+ if (doc.frontier < doc.first) doc.frontier = doc.first;
+ if (doc.frontier >= cm.display.showingTo) return;
+ var end = +new Date + cm.options.workTime;
+ var state = copyState(doc.mode, getStateBefore(cm, doc.frontier));
+ var changed = [], prevChange;
+ doc.iter(doc.frontier, Math.min(doc.first + doc.size, cm.display.showingTo + 500), function(line) {
+ if (doc.frontier >= cm.display.showingFrom) { // Visible
+ var oldStyles = line.styles;
+ line.styles = highlightLine(cm, line, state);
+ var ischange = !oldStyles || oldStyles.length != line.styles.length;
+ for (var i = 0; !ischange && i < oldStyles.length; ++i) ischange = oldStyles[i] != line.styles[i];
+ if (ischange) {
+ if (prevChange && prevChange.end == doc.frontier) prevChange.end++;
+ else changed.push(prevChange = {start: doc.frontier, end: doc.frontier + 1});
+ }
+ line.stateAfter = copyState(doc.mode, state);
+ } else {
+ processLine(cm, line, state);
+ line.stateAfter = doc.frontier % 5 == 0 ? copyState(doc.mode, state) : null;
+ }
+ ++doc.frontier;
+ if (+new Date > end) {
+ startWorker(cm, cm.options.workDelay);
+ return true;
+ }
+ });
+ if (changed.length)
+ operation(cm, function() {
+ for (var i = 0; i < changed.length; ++i)
+ regChange(this, changed[i].start, changed[i].end);
+ })();
+ }
+
+ // Finds the line to start with when starting a parse. Tries to
+ // find a line with a stateAfter, so that it can start with a
+ // valid state. If that fails, it returns the line with the
+ // smallest indentation, which tends to need the least context to
+ // parse correctly.
+ function findStartLine(cm, n, precise) {
+ var minindent, minline, doc = cm.doc;
+ for (var search = n, lim = n - 100; search > lim; --search) {
+ if (search <= doc.first) return doc.first;
+ var line = getLine(doc, search - 1);
+ if (line.stateAfter && (!precise || search <= doc.frontier)) return search;
+ var indented = countColumn(line.text, null, cm.options.tabSize);
+ if (minline == null || minindent > indented) {
+ minline = search - 1;
+ minindent = indented;
+ }
+ }
+ return minline;
+ }
+
+ function getStateBefore(cm, n, precise) {
+ var doc = cm.doc, display = cm.display;
+ if (!doc.mode.startState) return true;
+ var pos = findStartLine(cm, n, precise), state = pos > doc.first && getLine(doc, pos-1).stateAfter;
+ if (!state) state = startState(doc.mode);
+ else state = copyState(doc.mode, state);
+ doc.iter(pos, n, function(line) {
+ processLine(cm, line, state);
+ var save = pos == n - 1 || pos % 5 == 0 || pos >= display.showingFrom && pos < display.showingTo;
+ line.stateAfter = save ? copyState(doc.mode, state) : null;
+ ++pos;
+ });
+ return state;
+ }
+
+ // POSITION MEASUREMENT
+
+ function paddingTop(display) {return display.lineSpace.offsetTop;}
+ function paddingVert(display) {return display.mover.offsetHeight - display.lineSpace.offsetHeight;}
+ function paddingLeft(display) {
+ var e = removeChildrenAndAdd(display.measure, elt("pre", null, null, "text-align: left")).appendChild(elt("span", "x"));
+ return e.offsetLeft;
+ }
+
+ function measureChar(cm, line, ch, data, bias) {
+ var dir = -1;
+ data = data || measureLine(cm, line);
+
+ for (var pos = ch;; pos += dir) {
+ var r = data[pos];
+ if (r) break;
+ if (dir < 0 && pos == 0) dir = 1;
+ }
+ bias = pos > ch ? "left" : pos < ch ? "right" : bias;
+ if (bias == "left" && r.leftSide) r = r.leftSide;
+ else if (bias == "right" && r.rightSide) r = r.rightSide;
+ return {left: pos < ch ? r.right : r.left,
+ right: pos > ch ? r.left : r.right,
+ top: r.top,
+ bottom: r.bottom};
+ }
+
+ function findCachedMeasurement(cm, line) {
+ var cache = cm.display.measureLineCache;
+ for (var i = 0; i < cache.length; ++i) {
+ var memo = cache[i];
+ if (memo.text == line.text && memo.markedSpans == line.markedSpans &&
+ cm.display.scroller.clientWidth == memo.width &&
+ memo.classes == line.textClass + "|" + line.bgClass + "|" + line.wrapClass)
+ return memo;
+ }
+ }
+
+ function clearCachedMeasurement(cm, line) {
+ var exists = findCachedMeasurement(cm, line);
+ if (exists) exists.text = exists.measure = exists.markedSpans = null;
+ }
+
+ function measureLine(cm, line) {
+ // First look in the cache
+ var cached = findCachedMeasurement(cm, line);
+ if (cached) return cached.measure;
+
+ // Failing that, recompute and store result in cache
+ var measure = measureLineInner(cm, line);
+ var cache = cm.display.measureLineCache;
+ var memo = {text: line.text, width: cm.display.scroller.clientWidth,
+ markedSpans: line.markedSpans, measure: measure,
+ classes: line.textClass + "|" + line.bgClass + "|" + line.wrapClass};
+ if (cache.length == 16) cache[++cm.display.measureLineCachePos % 16] = memo;
+ else cache.push(memo);
+ return measure;
+ }
+
+ function measureLineInner(cm, line) {
+ var display = cm.display, measure = emptyArray(line.text.length);
+ var pre = lineContent(cm, line, measure, true);
+
+ // IE does not cache element positions of inline elements between
+ // calls to getBoundingClientRect. This makes the loop below,
+ // which gathers the positions of all the characters on the line,
+ // do an amount of layout work quadratic to the number of
+ // characters. When line wrapping is off, we try to improve things
+ // by first subdividing the line into a bunch of inline blocks, so
+ // that IE can reuse most of the layout information from caches
+ // for those blocks. This does interfere with line wrapping, so it
+ // doesn't work when wrapping is on, but in that case the
+ // situation is slightly better, since IE does cache line-wrapping
+ // information and only recomputes per-line.
+ if (ie && !ie_lt8 && !cm.options.lineWrapping && pre.childNodes.length > 100) {
+ var fragment = document.createDocumentFragment();
+ var chunk = 10, n = pre.childNodes.length;
+ for (var i = 0, chunks = Math.ceil(n / chunk); i < chunks; ++i) {
+ var wrap = elt("div", null, null, "display: inline-block");
+ for (var j = 0; j < chunk && n; ++j) {
+ wrap.appendChild(pre.firstChild);
+ --n;
+ }
+ fragment.appendChild(wrap);
+ }
+ pre.appendChild(fragment);
+ }
+
+ removeChildrenAndAdd(display.measure, pre);
+
+ var outer = getRect(display.lineDiv);
+ var vranges = [], data = emptyArray(line.text.length), maxBot = pre.offsetHeight;
+ // Work around an IE7/8 bug where it will sometimes have randomly
+ // replaced our pre with a clone at this point.
+ if (ie_lt9 && display.measure.first != pre)
+ removeChildrenAndAdd(display.measure, pre);
+
+ function measureRect(rect) {
+ var top = rect.top - outer.top, bot = rect.bottom - outer.top;
+ if (bot > maxBot) bot = maxBot;
+ if (top < 0) top = 0;
+ for (var i = vranges.length - 2; i >= 0; i -= 2) {
+ var rtop = vranges[i], rbot = vranges[i+1];
+ if (rtop > bot || rbot < top) continue;
+ if (rtop <= top && rbot >= bot ||
+ top <= rtop && bot >= rbot ||
+ Math.min(bot, rbot) - Math.max(top, rtop) >= (bot - top) >> 1) {
+ vranges[i] = Math.min(top, rtop);
+ vranges[i+1] = Math.max(bot, rbot);
+ break;
+ }
+ }
+ if (i < 0) { i = vranges.length; vranges.push(top, bot); }
+ return {left: rect.left - outer.left,
+ right: rect.right - outer.left,
+ top: i, bottom: null};
+ }
+ function finishRect(rect) {
+ rect.bottom = vranges[rect.top+1];
+ rect.top = vranges[rect.top];
+ }
+
+ for (var i = 0, cur; i < measure.length; ++i) if (cur = measure[i]) {
+ var node = cur, rect = null;
+ // A widget might wrap, needs special care
+ if (/\bCodeMirror-widget\b/.test(cur.className) && cur.getClientRects) {
+ if (cur.firstChild.nodeType == 1) node = cur.firstChild;
+ var rects = node.getClientRects();
+ if (rects.length > 1) {
+ rect = data[i] = measureRect(rects[0]);
+ rect.rightSide = measureRect(rects[rects.length - 1]);
+ }
+ }
+ if (!rect) rect = data[i] = measureRect(getRect(node));
+ if (cur.measureRight) rect.right = getRect(cur.measureRight).left;
+ if (cur.leftSide) rect.leftSide = measureRect(getRect(cur.leftSide));
+ }
+ for (var i = 0, cur; i < data.length; ++i) if (cur = data[i]) {
+ finishRect(cur);
+ if (cur.leftSide) finishRect(cur.leftSide);
+ if (cur.rightSide) finishRect(cur.rightSide);
+ }
+ return data;
+ }
+
+ function measureLineWidth(cm, line) {
+ var hasBadSpan = false;
+ if (line.markedSpans) for (var i = 0; i < line.markedSpans; ++i) {
+ var sp = line.markedSpans[i];
+ if (sp.collapsed && (sp.to == null || sp.to == line.text.length)) hasBadSpan = true;
+ }
+ var cached = !hasBadSpan && findCachedMeasurement(cm, line);
+ if (cached) return measureChar(cm, line, line.text.length, cached.measure, "right").right;
+
+ var pre = lineContent(cm, line, null, true);
+ var end = pre.appendChild(zeroWidthElement(cm.display.measure));
+ removeChildrenAndAdd(cm.display.measure, pre);
+ return getRect(end).right - getRect(cm.display.lineDiv).left;
+ }
+
+ function clearCaches(cm) {
+ cm.display.measureLineCache.length = cm.display.measureLineCachePos = 0;
+ cm.display.cachedCharWidth = cm.display.cachedTextHeight = null;
+ if (!cm.options.lineWrapping) cm.display.maxLineChanged = true;
+ cm.display.lineNumChars = null;
+ }
+
+ function pageScrollX() { return window.pageXOffset || (document.documentElement || document.body).scrollLeft; }
+ function pageScrollY() { return window.pageYOffset || (document.documentElement || document.body).scrollTop; }
+
+ // Context is one of "line", "div" (display.lineDiv), "local"/null (editor), or "page"
+ function intoCoordSystem(cm, lineObj, rect, context) {
+ if (lineObj.widgets) for (var i = 0; i < lineObj.widgets.length; ++i) if (lineObj.widgets[i].above) {
+ var size = widgetHeight(lineObj.widgets[i]);
+ rect.top += size; rect.bottom += size;
+ }
+ if (context == "line") return rect;
+ if (!context) context = "local";
+ var yOff = heightAtLine(cm, lineObj);
+ if (context == "local") yOff += paddingTop(cm.display);
+ else yOff -= cm.display.viewOffset;
+ if (context == "page" || context == "window") {
+ var lOff = getRect(cm.display.lineSpace);
+ yOff += lOff.top + (context == "window" ? 0 : pageScrollY());
+ var xOff = lOff.left + (context == "window" ? 0 : pageScrollX());
+ rect.left += xOff; rect.right += xOff;
+ }
+ rect.top += yOff; rect.bottom += yOff;
+ return rect;
+ }
+
+ // Context may be "window", "page", "div", or "local"/null
+ // Result is in "div" coords
+ function fromCoordSystem(cm, coords, context) {
+ if (context == "div") return coords;
+ var left = coords.left, top = coords.top;
+ // First move into "page" coordinate system
+ if (context == "page") {
+ left -= pageScrollX();
+ top -= pageScrollY();
+ } else if (context == "local" || !context) {
+ var localBox = getRect(cm.display.sizer);
+ left += localBox.left;
+ top += localBox.top;
+ }
+
+ var lineSpaceBox = getRect(cm.display.lineSpace);
+ return {left: left - lineSpaceBox.left, top: top - lineSpaceBox.top};
+ }
+
+ function charCoords(cm, pos, context, lineObj, bias) {
+ if (!lineObj) lineObj = getLine(cm.doc, pos.line);
+ return intoCoordSystem(cm, lineObj, measureChar(cm, lineObj, pos.ch, null, bias), context);
+ }
+
+ function cursorCoords(cm, pos, context, lineObj, measurement) {
+ lineObj = lineObj || getLine(cm.doc, pos.line);
+ if (!measurement) measurement = measureLine(cm, lineObj);
+ function get(ch, right) {
+ var m = measureChar(cm, lineObj, ch, measurement, right ? "right" : "left");
+ if (right) m.left = m.right; else m.right = m.left;
+ return intoCoordSystem(cm, lineObj, m, context);
+ }
+ function getBidi(ch, partPos) {
+ var part = order[partPos], right = part.level % 2;
+ if (ch == bidiLeft(part) && partPos && part.level < order[partPos - 1].level) {
+ part = order[--partPos];
+ ch = bidiRight(part) - (part.level % 2 ? 0 : 1);
+ right = true;
+ } else if (ch == bidiRight(part) && partPos < order.length - 1 && part.level < order[partPos + 1].level) {
+ part = order[++partPos];
+ ch = bidiLeft(part) - part.level % 2;
+ right = false;
+ }
+ if (right && ch == part.to && ch > part.from) return get(ch - 1);
+ return get(ch, right);
+ }
+ var order = getOrder(lineObj), ch = pos.ch;
+ if (!order) return get(ch);
+ var partPos = getBidiPartAt(order, ch);
+ var val = getBidi(ch, partPos);
+ if (bidiOther != null) val.other = getBidi(ch, bidiOther);
+ return val;
+ }
+
+ function PosWithInfo(line, ch, outside, xRel) {
+ var pos = new Pos(line, ch);
+ pos.xRel = xRel;
+ if (outside) pos.outside = true;
+ return pos;
+ }
+
+ // Coords must be lineSpace-local
+ function coordsChar(cm, x, y) {
+ var doc = cm.doc;
+ y += cm.display.viewOffset;
+ if (y < 0) return PosWithInfo(doc.first, 0, true, -1);
+ var lineNo = lineAtHeight(doc, y), last = doc.first + doc.size - 1;
+ if (lineNo > last)
+ return PosWithInfo(doc.first + doc.size - 1, getLine(doc, last).text.length, true, 1);
+ if (x < 0) x = 0;
+
+ for (;;) {
+ var lineObj = getLine(doc, lineNo);
+ var found = coordsCharInner(cm, lineObj, lineNo, x, y);
+ var merged = collapsedSpanAtEnd(lineObj);
+ var mergedPos = merged && merged.find();
+ if (merged && (found.ch > mergedPos.from.ch || found.ch == mergedPos.from.ch && found.xRel > 0))
+ lineNo = mergedPos.to.line;
+ else
+ return found;
+ }
+ }
+
+ function coordsCharInner(cm, lineObj, lineNo, x, y) {
+ var innerOff = y - heightAtLine(cm, lineObj);
+ var wrongLine = false, adjust = 2 * cm.display.wrapper.clientWidth;
+ var measurement = measureLine(cm, lineObj);
+
+ function getX(ch) {
+ var sp = cursorCoords(cm, Pos(lineNo, ch), "line",
+ lineObj, measurement);
+ wrongLine = true;
+ if (innerOff > sp.bottom) return sp.left - adjust;
+ else if (innerOff < sp.top) return sp.left + adjust;
+ else wrongLine = false;
+ return sp.left;
+ }
+
+ var bidi = getOrder(lineObj), dist = lineObj.text.length;
+ var from = lineLeft(lineObj), to = lineRight(lineObj);
+ var fromX = getX(from), fromOutside = wrongLine, toX = getX(to), toOutside = wrongLine;
+
+ if (x > toX) return PosWithInfo(lineNo, to, toOutside, 1);
+ // Do a binary search between these bounds.
+ for (;;) {
+ if (bidi ? to == from || to == moveVisually(lineObj, from, 1) : to - from <= 1) {
+ var ch = x < fromX || x - fromX <= toX - x ? from : to;
+ var xDiff = x - (ch == from ? fromX : toX);
+ while (isExtendingChar.test(lineObj.text.charAt(ch))) ++ch;
+ var pos = PosWithInfo(lineNo, ch, ch == from ? fromOutside : toOutside,
+ xDiff < 0 ? -1 : xDiff ? 1 : 0);
+ return pos;
+ }
+ var step = Math.ceil(dist / 2), middle = from + step;
+ if (bidi) {
+ middle = from;
+ for (var i = 0; i < step; ++i) middle = moveVisually(lineObj, middle, 1);
+ }
+ var middleX = getX(middle);
+ if (middleX > x) {to = middle; toX = middleX; if (toOutside = wrongLine) toX += 1000; dist = step;}
+ else {from = middle; fromX = middleX; fromOutside = wrongLine; dist -= step;}
+ }
+ }
+
+ var measureText;
+ function textHeight(display) {
+ if (display.cachedTextHeight != null) return display.cachedTextHeight;
+ if (measureText == null) {
+ measureText = elt("pre");
+ // Measure a bunch of lines, for browsers that compute
+ // fractional heights.
+ for (var i = 0; i < 49; ++i) {
+ measureText.appendChild(document.createTextNode("x"));
+ measureText.appendChild(elt("br"));
+ }
+ measureText.appendChild(document.createTextNode("x"));
+ }
+ removeChildrenAndAdd(display.measure, measureText);
+ var height = measureText.offsetHeight / 50;
+ if (height > 3) display.cachedTextHeight = height;
+ removeChildren(display.measure);
+ return height || 1;
+ }
+
+ function charWidth(display) {
+ if (display.cachedCharWidth != null) return display.cachedCharWidth;
+ var anchor = elt("span", "x");
+ var pre = elt("pre", [anchor]);
+ removeChildrenAndAdd(display.measure, pre);
+ var width = anchor.offsetWidth;
+ if (width > 2) display.cachedCharWidth = width;
+ return width || 10;
+ }
+
+ // OPERATIONS
+
+ // Operations are used to wrap changes in such a way that each
+ // change won't have to update the cursor and display (which would
+ // be awkward, slow, and error-prone), but instead updates are
+ // batched and then all combined and executed at once.
+
+ var nextOpId = 0;
+ function startOperation(cm) {
+ cm.curOp = {
+ // An array of ranges of lines that have to be updated. See
+ // updateDisplay.
+ changes: [],
+ forceUpdate: false,
+ updateInput: null,
+ userSelChange: null,
+ textChanged: null,
+ selectionChanged: false,
+ cursorActivity: false,
+ updateMaxLine: false,
+ updateScrollPos: false,
+ id: ++nextOpId
+ };
+ if (!delayedCallbackDepth++) delayedCallbacks = [];
+ }
+
+ function endOperation(cm) {
+ var op = cm.curOp, doc = cm.doc, display = cm.display;
+ cm.curOp = null;
+
+ if (op.updateMaxLine) computeMaxLength(cm);
+ if (display.maxLineChanged && !cm.options.lineWrapping && display.maxLine) {
+ var width = measureLineWidth(cm, display.maxLine);
+ display.sizer.style.minWidth = Math.max(0, width + 3 + scrollerCutOff) + "px";
+ display.maxLineChanged = false;
+ var maxScrollLeft = Math.max(0, display.sizer.offsetLeft + display.sizer.offsetWidth - display.scroller.clientWidth);
+ if (maxScrollLeft < doc.scrollLeft && !op.updateScrollPos)
+ setScrollLeft(cm, Math.min(display.scroller.scrollLeft, maxScrollLeft), true);
+ }
+ var newScrollPos, updated;
+ if (op.updateScrollPos) {
+ newScrollPos = op.updateScrollPos;
+ } else if (op.selectionChanged && display.scroller.clientHeight) { // don't rescroll if not visible
+ var coords = cursorCoords(cm, doc.sel.head);
+ newScrollPos = calculateScrollPos(cm, coords.left, coords.top, coords.left, coords.bottom);
+ }
+ if (op.changes.length || op.forceUpdate || newScrollPos && newScrollPos.scrollTop != null) {
+ updated = updateDisplay(cm, op.changes, newScrollPos && newScrollPos.scrollTop, op.forceUpdate);
+ if (cm.display.scroller.offsetHeight) cm.doc.scrollTop = cm.display.scroller.scrollTop;
+ }
+ if (!updated && op.selectionChanged) updateSelection(cm);
+ if (op.updateScrollPos) {
+ display.scroller.scrollTop = display.scrollbarV.scrollTop = doc.scrollTop = newScrollPos.scrollTop;
+ display.scroller.scrollLeft = display.scrollbarH.scrollLeft = doc.scrollLeft = newScrollPos.scrollLeft;
+ alignHorizontally(cm);
+ if (op.scrollToPos)
+ scrollPosIntoView(cm, clipPos(cm.doc, op.scrollToPos), op.scrollToPosMargin);
+ } else if (newScrollPos) {
+ scrollCursorIntoView(cm);
+ }
+ if (op.selectionChanged) restartBlink(cm);
+
+ if (cm.state.focused && op.updateInput)
+ resetInput(cm, op.userSelChange);
+
+ var hidden = op.maybeHiddenMarkers, unhidden = op.maybeUnhiddenMarkers;
+ if (hidden) for (var i = 0; i < hidden.length; ++i)
+ if (!hidden[i].lines.length) signal(hidden[i], "hide");
+ if (unhidden) for (var i = 0; i < unhidden.length; ++i)
+ if (unhidden[i].lines.length) signal(unhidden[i], "unhide");
+
+ var delayed;
+ if (!--delayedCallbackDepth) {
+ delayed = delayedCallbacks;
+ delayedCallbacks = null;
+ }
+ if (op.textChanged)
+ signal(cm, "change", cm, op.textChanged);
+ if (op.cursorActivity) signal(cm, "cursorActivity", cm);
+ if (delayed) for (var i = 0; i < delayed.length; ++i) delayed[i]();
+ }
+
+ // Wraps a function in an operation. Returns the wrapped function.
+ function operation(cm1, f) {
+ return function() {
+ var cm = cm1 || this, withOp = !cm.curOp;
+ if (withOp) startOperation(cm);
+ try { var result = f.apply(cm, arguments); }
+ finally { if (withOp) endOperation(cm); }
+ return result;
+ };
+ }
+ function docOperation(f) {
+ return function() {
+ var withOp = this.cm && !this.cm.curOp, result;
+ if (withOp) startOperation(this.cm);
+ try { result = f.apply(this, arguments); }
+ finally { if (withOp) endOperation(this.cm); }
+ return result;
+ };
+ }
+ function runInOp(cm, f) {
+ var withOp = !cm.curOp, result;
+ if (withOp) startOperation(cm);
+ try { result = f(); }
+ finally { if (withOp) endOperation(cm); }
+ return result;
+ }
+
+ function regChange(cm, from, to, lendiff) {
+ if (from == null) from = cm.doc.first;
+ if (to == null) to = cm.doc.first + cm.doc.size;
+ cm.curOp.changes.push({from: from, to: to, diff: lendiff});
+ }
+
+ // INPUT HANDLING
+
+ function slowPoll(cm) {
+ if (cm.display.pollingFast) return;
+ cm.display.poll.set(cm.options.pollInterval, function() {
+ readInput(cm);
+ if (cm.state.focused) slowPoll(cm);
+ });
+ }
+
+ function fastPoll(cm) {
+ var missed = false;
+ cm.display.pollingFast = true;
+ function p() {
+ var changed = readInput(cm);
+ if (!changed && !missed) {missed = true; cm.display.poll.set(60, p);}
+ else {cm.display.pollingFast = false; slowPoll(cm);}
+ }
+ cm.display.poll.set(20, p);
+ }
+
+ // prevInput is a hack to work with IME. If we reset the textarea
+ // on every change, that breaks IME. So we look for changes
+ // compared to the previous content instead. (Modern browsers have
+ // events that indicate IME taking place, but these are not widely
+ // supported or compatible enough yet to rely on.)
+ function readInput(cm) {
+ var input = cm.display.input, prevInput = cm.display.prevInput, doc = cm.doc, sel = doc.sel;
+ if (!cm.state.focused || hasSelection(input) || isReadOnly(cm) || cm.state.disableInput) return false;
+ var text = input.value;
+ if (text == prevInput && posEq(sel.from, sel.to)) return false;
+ if (ie && !ie_lt9 && cm.display.inputHasSelection === text) {
+ resetInput(cm, true);
+ return false;
+ }
+
+ var withOp = !cm.curOp;
+ if (withOp) startOperation(cm);
+ sel.shift = false;
+ var same = 0, l = Math.min(prevInput.length, text.length);
+ while (same < l && prevInput.charCodeAt(same) == text.charCodeAt(same)) ++same;
+ var from = sel.from, to = sel.to;
+ if (same < prevInput.length)
+ from = Pos(from.line, from.ch - (prevInput.length - same));
+ else if (cm.state.overwrite && posEq(from, to) && !cm.state.pasteIncoming)
+ to = Pos(to.line, Math.min(getLine(doc, to.line).text.length, to.ch + (text.length - same)));
+
+ var updateInput = cm.curOp.updateInput;
+ var changeEvent = {from: from, to: to, text: splitLines(text.slice(same)),
+ origin: cm.state.pasteIncoming ? "paste" : "+input"};
+ makeChange(cm.doc, changeEvent, "end");
+ cm.curOp.updateInput = updateInput;
+ signalLater(cm, "inputRead", cm, changeEvent);
+
+ if (text.length > 1000 || text.indexOf("\n") > -1) input.value = cm.display.prevInput = "";
+ else cm.display.prevInput = text;
+ if (withOp) endOperation(cm);
+ cm.state.pasteIncoming = false;
+ return true;
+ }
+
+ function resetInput(cm, user) {
+ var minimal, selected, doc = cm.doc;
+ if (!posEq(doc.sel.from, doc.sel.to)) {
+ cm.display.prevInput = "";
+ minimal = hasCopyEvent &&
+ (doc.sel.to.line - doc.sel.from.line > 100 || (selected = cm.getSelection()).length > 1000);
+ var content = minimal ? "-" : selected || cm.getSelection();
+ cm.display.input.value = content;
+ if (cm.state.focused) selectInput(cm.display.input);
+ if (ie && !ie_lt9) cm.display.inputHasSelection = content;
+ } else if (user) {
+ cm.display.prevInput = cm.display.input.value = "";
+ if (ie && !ie_lt9) cm.display.inputHasSelection = null;
+ }
+ cm.display.inaccurateSelection = minimal;
+ }
+
+ function focusInput(cm) {
+ if (cm.options.readOnly != "nocursor" && (!mobile || document.activeElement != cm.display.input))
+ cm.display.input.focus();
+ }
+
+ function isReadOnly(cm) {
+ return cm.options.readOnly || cm.doc.cantEdit;
+ }
+
+ // EVENT HANDLERS
+
+ function registerEventHandlers(cm) {
+ var d = cm.display;
+ on(d.scroller, "mousedown", operation(cm, onMouseDown));
+ if (ie)
+ on(d.scroller, "dblclick", operation(cm, function(e) {
+ if (signalDOMEvent(cm, e)) return;
+ var pos = posFromMouse(cm, e);
+ if (!pos || clickInGutter(cm, e) || eventInWidget(cm.display, e)) return;
+ e_preventDefault(e);
+ var word = findWordAt(getLine(cm.doc, pos.line).text, pos);
+ extendSelection(cm.doc, word.from, word.to);
+ }));
+ else
+ on(d.scroller, "dblclick", function(e) { signalDOMEvent(cm, e) || e_preventDefault(e); });
+ on(d.lineSpace, "selectstart", function(e) {
+ if (!eventInWidget(d, e)) e_preventDefault(e);
+ });
+ // Gecko browsers fire contextmenu *after* opening the menu, at
+ // which point we can't mess with it anymore. Context menu is
+ // handled in onMouseDown for Gecko.
+ if (!captureMiddleClick) on(d.scroller, "contextmenu", function(e) {onContextMenu(cm, e);});
+
+ on(d.scroller, "scroll", function() {
+ if (d.scroller.clientHeight) {
+ setScrollTop(cm, d.scroller.scrollTop);
+ setScrollLeft(cm, d.scroller.scrollLeft, true);
+ signal(cm, "scroll", cm);
+ }
+ });
+ on(d.scrollbarV, "scroll", function() {
+ if (d.scroller.clientHeight) setScrollTop(cm, d.scrollbarV.scrollTop);
+ });
+ on(d.scrollbarH, "scroll", function() {
+ if (d.scroller.clientHeight) setScrollLeft(cm, d.scrollbarH.scrollLeft);
+ });
+
+ on(d.scroller, "mousewheel", function(e){onScrollWheel(cm, e);});
+ on(d.scroller, "DOMMouseScroll", function(e){onScrollWheel(cm, e);});
+
+ function reFocus() { if (cm.state.focused) setTimeout(bind(focusInput, cm), 0); }
+ on(d.scrollbarH, "mousedown", reFocus);
+ on(d.scrollbarV, "mousedown", reFocus);
+ // Prevent wrapper from ever scrolling
+ on(d.wrapper, "scroll", function() { d.wrapper.scrollTop = d.wrapper.scrollLeft = 0; });
+
+ var resizeTimer;
+ function onResize() {
+ if (resizeTimer == null) resizeTimer = setTimeout(function() {
+ resizeTimer = null;
+ // Might be a text scaling operation, clear size caches.
+ d.cachedCharWidth = d.cachedTextHeight = knownScrollbarWidth = null;
+ clearCaches(cm);
+ runInOp(cm, bind(regChange, cm));
+ }, 100);
+ }
+ on(window, "resize", onResize);
+ // Above handler holds on to the editor and its data structures.
+ // Here we poll to unregister it when the editor is no longer in
+ // the document, so that it can be garbage-collected.
+ function unregister() {
+ for (var p = d.wrapper.parentNode; p && p != document.body; p = p.parentNode) {}
+ if (p) setTimeout(unregister, 5000);
+ else off(window, "resize", onResize);
+ }
+ setTimeout(unregister, 5000);
+
+ on(d.input, "keyup", operation(cm, function(e) {
+ if (signalDOMEvent(cm, e) || cm.options.onKeyEvent && cm.options.onKeyEvent(cm, addStop(e))) return;
+ if (e.keyCode == 16) cm.doc.sel.shift = false;
+ }));
+ on(d.input, "input", bind(fastPoll, cm));
+ on(d.input, "keydown", operation(cm, onKeyDown));
+ on(d.input, "keypress", operation(cm, onKeyPress));
+ on(d.input, "focus", bind(onFocus, cm));
+ on(d.input, "blur", bind(onBlur, cm));
+
+ function drag_(e) {
+ if (signalDOMEvent(cm, e) || cm.options.onDragEvent && cm.options.onDragEvent(cm, addStop(e))) return;
+ e_stop(e);
+ }
+ if (cm.options.dragDrop) {
+ on(d.scroller, "dragstart", function(e){onDragStart(cm, e);});
+ on(d.scroller, "dragenter", drag_);
+ on(d.scroller, "dragover", drag_);
+ on(d.scroller, "drop", operation(cm, onDrop));
+ }
+ on(d.scroller, "paste", function(e){
+ if (eventInWidget(d, e)) return;
+ focusInput(cm);
+ fastPoll(cm);
+ });
+ on(d.input, "paste", function() {
+ cm.state.pasteIncoming = true;
+ fastPoll(cm);
+ });
+
+ function prepareCopy() {
+ if (d.inaccurateSelection) {
+ d.prevInput = "";
+ d.inaccurateSelection = false;
+ d.input.value = cm.getSelection();
+ selectInput(d.input);
+ }
+ }
+ on(d.input, "cut", prepareCopy);
+ on(d.input, "copy", prepareCopy);
+
+ // Needed to handle Tab key in KHTML
+ if (khtml) on(d.sizer, "mouseup", function() {
+ if (document.activeElement == d.input) d.input.blur();
+ focusInput(cm);
+ });
+ }
+
+ function eventInWidget(display, e) {
+ for (var n = e_target(e); n != display.wrapper; n = n.parentNode) {
+ if (!n || n.ignoreEvents || n.parentNode == display.sizer && n != display.mover) return true;
+ }
+ }
+
+ function posFromMouse(cm, e, liberal) {
+ var display = cm.display;
+ if (!liberal) {
+ var target = e_target(e);
+ if (target == display.scrollbarH || target == display.scrollbarH.firstChild ||
+ target == display.scrollbarV || target == display.scrollbarV.firstChild ||
+ target == display.scrollbarFiller || target == display.gutterFiller) return null;
+ }
+ var x, y, space = getRect(display.lineSpace);
+ // Fails unpredictably on IE[67] when mouse is dragged around quickly.
+ try { x = e.clientX; y = e.clientY; } catch (e) { return null; }
+ return coordsChar(cm, x - space.left, y - space.top);
+ }
+
+ var lastClick, lastDoubleClick;
+ function onMouseDown(e) {
+ if (signalDOMEvent(this, e)) return;
+ var cm = this, display = cm.display, doc = cm.doc, sel = doc.sel;
+ sel.shift = e.shiftKey;
+
+ if (eventInWidget(display, e)) {
+ if (!webkit) {
+ display.scroller.draggable = false;
+ setTimeout(function(){display.scroller.draggable = true;}, 100);
+ }
+ return;
+ }
+ if (clickInGutter(cm, e)) return;
+ var start = posFromMouse(cm, e);
+
+ switch (e_button(e)) {
+ case 3:
+ if (captureMiddleClick) onContextMenu.call(cm, cm, e);
+ return;
+ case 2:
+ if (start) extendSelection(cm.doc, start);
+ setTimeout(bind(focusInput, cm), 20);
+ e_preventDefault(e);
+ return;
+ }
+ // For button 1, if it was clicked inside the editor
+ // (posFromMouse returning non-null), we have to adjust the
+ // selection.
+ if (!start) {if (e_target(e) == display.scroller) e_preventDefault(e); return;}
+
+ if (!cm.state.focused) onFocus(cm);
+
+ var now = +new Date, type = "single";
+ if (lastDoubleClick && lastDoubleClick.time > now - 400 && posEq(lastDoubleClick.pos, start)) {
+ type = "triple";
+ e_preventDefault(e);
+ setTimeout(bind(focusInput, cm), 20);
+ selectLine(cm, start.line);
+ } else if (lastClick && lastClick.time > now - 400 && posEq(lastClick.pos, start)) {
+ type = "double";
+ lastDoubleClick = {time: now, pos: start};
+ e_preventDefault(e);
+ var word = findWordAt(getLine(doc, start.line).text, start);
+ extendSelection(cm.doc, word.from, word.to);
+ } else { lastClick = {time: now, pos: start}; }
+
+ var last = start;
+ if (cm.options.dragDrop && dragAndDrop && !isReadOnly(cm) && !posEq(sel.from, sel.to) &&
+ !posLess(start, sel.from) && !posLess(sel.to, start) && type == "single") {
+ var dragEnd = operation(cm, function(e2) {
+ if (webkit) display.scroller.draggable = false;
+ cm.state.draggingText = false;
+ off(document, "mouseup", dragEnd);
+ off(display.scroller, "drop", dragEnd);
+ if (Math.abs(e.clientX - e2.clientX) + Math.abs(e.clientY - e2.clientY) < 10) {
+ e_preventDefault(e2);
+ extendSelection(cm.doc, start);
+ focusInput(cm);
+ }
+ });
+ // Let the drag handler handle this.
+ if (webkit) display.scroller.draggable = true;
+ cm.state.draggingText = dragEnd;
+ // IE's approach to draggable
+ if (display.scroller.dragDrop) display.scroller.dragDrop();
+ on(document, "mouseup", dragEnd);
+ on(display.scroller, "drop", dragEnd);
+ return;
+ }
+ e_preventDefault(e);
+ if (type == "single") extendSelection(cm.doc, clipPos(doc, start));
+
+ var startstart = sel.from, startend = sel.to, lastPos = start;
+
+ function doSelect(cur) {
+ if (posEq(lastPos, cur)) return;
+ lastPos = cur;
+
+ if (type == "single") {
+ extendSelection(cm.doc, clipPos(doc, start), cur);
+ return;
+ }
+
+ startstart = clipPos(doc, startstart);
+ startend = clipPos(doc, startend);
+ if (type == "double") {
+ var word = findWordAt(getLine(doc, cur.line).text, cur);
+ if (posLess(cur, startstart)) extendSelection(cm.doc, word.from, startend);
+ else extendSelection(cm.doc, startstart, word.to);
+ } else if (type == "triple") {
+ if (posLess(cur, startstart)) extendSelection(cm.doc, startend, clipPos(doc, Pos(cur.line, 0)));
+ else extendSelection(cm.doc, startstart, clipPos(doc, Pos(cur.line + 1, 0)));
+ }
+ }
+
+ var editorSize = getRect(display.wrapper);
+ // Used to ensure timeout re-tries don't fire when another extend
+ // happened in the meantime (clearTimeout isn't reliable -- at
+ // least on Chrome, the timeouts still happen even when cleared,
+ // if the clear happens after their scheduled firing time).
+ var counter = 0;
+
+ function extend(e) {
+ var curCount = ++counter;
+ var cur = posFromMouse(cm, e, true);
+ if (!cur) return;
+ if (!posEq(cur, last)) {
+ if (!cm.state.focused) onFocus(cm);
+ last = cur;
+ doSelect(cur);
+ var visible = visibleLines(display, doc);
+ if (cur.line >= visible.to || cur.line < visible.from)
+ setTimeout(operation(cm, function(){if (counter == curCount) extend(e);}), 150);
+ } else {
+ var outside = e.clientY < editorSize.top ? -20 : e.clientY > editorSize.bottom ? 20 : 0;
+ if (outside) setTimeout(operation(cm, function() {
+ if (counter != curCount) return;
+ display.scroller.scrollTop += outside;
+ extend(e);
+ }), 50);
+ }
+ }
+
+ function done(e) {
+ counter = Infinity;
+ e_preventDefault(e);
+ focusInput(cm);
+ off(document, "mousemove", move);
+ off(document, "mouseup", up);
+ }
+
+ var move = operation(cm, function(e) {
+ if (!ie && !e_button(e)) done(e);
+ else extend(e);
+ });
+ var up = operation(cm, done);
+ on(document, "mousemove", move);
+ on(document, "mouseup", up);
+ }
+
+ function clickInGutter(cm, e) {
+ var display = cm.display;
+ try { var mX = e.clientX, mY = e.clientY; }
+ catch(e) { return false; }
+
+ if (mX >= Math.floor(getRect(display.gutters).right)) return false;
+ e_preventDefault(e);
+ if (!hasHandler(cm, "gutterClick")) return true;
+
+ var lineBox = getRect(display.lineDiv);
+ if (mY > lineBox.bottom) return true;
+ mY -= lineBox.top - display.viewOffset;
+
+ for (var i = 0; i < cm.options.gutters.length; ++i) {
+ var g = display.gutters.childNodes[i];
+ if (g && getRect(g).right >= mX) {
+ var line = lineAtHeight(cm.doc, mY);
+ var gutter = cm.options.gutters[i];
+ signalLater(cm, "gutterClick", cm, line, gutter, e);
+ break;
+ }
+ }
+ return true;
+ }
+
+ // Kludge to work around strange IE behavior where it'll sometimes
+ // re-fire a series of drag-related events right after the drop (#1551)
+ var lastDrop = 0;
+
+ function onDrop(e) {
+ var cm = this;
+ if (signalDOMEvent(cm, e) || eventInWidget(cm.display, e) || (cm.options.onDragEvent && cm.options.onDragEvent(cm, addStop(e))))
+ return;
+ e_preventDefault(e);
+ if (ie) lastDrop = +new Date;
+ var pos = posFromMouse(cm, e, true), files = e.dataTransfer.files;
+ if (!pos || isReadOnly(cm)) return;
+ if (files && files.length && window.FileReader && window.File) {
+ var n = files.length, text = Array(n), read = 0;
+ var loadFile = function(file, i) {
+ var reader = new FileReader;
+ reader.onload = function() {
+ text[i] = reader.result;
+ if (++read == n) {
+ pos = clipPos(cm.doc, pos);
+ makeChange(cm.doc, {from: pos, to: pos, text: splitLines(text.join("\n")), origin: "paste"}, "around");
+ }
+ };
+ reader.readAsText(file);
+ };
+ for (var i = 0; i < n; ++i) loadFile(files[i], i);
+ } else {
+ // Don't do a replace if the drop happened inside of the selected text.
+ if (cm.state.draggingText && !(posLess(pos, cm.doc.sel.from) || posLess(cm.doc.sel.to, pos))) {
+ cm.state.draggingText(e);
+ // Ensure the editor is re-focused
+ setTimeout(bind(focusInput, cm), 20);
+ return;
+ }
+ try {
+ var text = e.dataTransfer.getData("Text");
+ if (text) {
+ var curFrom = cm.doc.sel.from, curTo = cm.doc.sel.to;
+ setSelection(cm.doc, pos, pos);
+ if (cm.state.draggingText) replaceRange(cm.doc, "", curFrom, curTo, "paste");
+ cm.replaceSelection(text, null, "paste");
+ focusInput(cm);
+ onFocus(cm);
+ }
+ }
+ catch(e){}
+ }
+ }
+
+ function onDragStart(cm, e) {
+ if (ie && (!cm.state.draggingText || +new Date - lastDrop < 100)) { e_stop(e); return; }
+ if (signalDOMEvent(cm, e) || eventInWidget(cm.display, e)) return;
+
+ var txt = cm.getSelection();
+ e.dataTransfer.setData("Text", txt);
+
+ // Use dummy image instead of default browsers image.
+ // Recent Safari (~6.0.2) have a tendency to segfault when this happens, so we don't do it there.
+ if (e.dataTransfer.setDragImage && !safari) {
+ var img = elt("img", null, null, "position: fixed; left: 0; top: 0;");
+ if (opera) {
+ img.width = img.height = 1;
+ cm.display.wrapper.appendChild(img);
+ // Force a relayout, or Opera won't use our image for some obscure reason
+ img._top = img.offsetTop;
+ }
+ e.dataTransfer.setDragImage(img, 0, 0);
+ if (opera) img.parentNode.removeChild(img);
+ }
+ }
+
+ function setScrollTop(cm, val) {
+ if (Math.abs(cm.doc.scrollTop - val) < 2) return;
+ cm.doc.scrollTop = val;
+ if (!gecko) updateDisplay(cm, [], val);
+ if (cm.display.scroller.scrollTop != val) cm.display.scroller.scrollTop = val;
+ if (cm.display.scrollbarV.scrollTop != val) cm.display.scrollbarV.scrollTop = val;
+ if (gecko) updateDisplay(cm, []);
+ startWorker(cm, 100);
+ }
+ function setScrollLeft(cm, val, isScroller) {
+ if (isScroller ? val == cm.doc.scrollLeft : Math.abs(cm.doc.scrollLeft - val) < 2) return;
+ val = Math.min(val, cm.display.scroller.scrollWidth - cm.display.scroller.clientWidth);
+ cm.doc.scrollLeft = val;
+ alignHorizontally(cm);
+ if (cm.display.scroller.scrollLeft != val) cm.display.scroller.scrollLeft = val;
+ if (cm.display.scrollbarH.scrollLeft != val) cm.display.scrollbarH.scrollLeft = val;
+ }
+
+ // Since the delta values reported on mouse wheel events are
+ // unstandardized between browsers and even browser versions, and
+ // generally horribly unpredictable, this code starts by measuring
+ // the scroll effect that the first few mouse wheel events have,
+ // and, from that, detects the way it can convert deltas to pixel
+ // offsets afterwards.
+ //
+ // The reason we want to know the amount a wheel event will scroll
+ // is that it gives us a chance to update the display before the
+ // actual scrolling happens, reducing flickering.
+
+ var wheelSamples = 0, wheelPixelsPerUnit = null;
+ // Fill in a browser-detected starting value on browsers where we
+ // know one. These don't have to be accurate -- the result of them
+ // being wrong would just be a slight flicker on the first wheel
+ // scroll (if it is large enough).
+ if (ie) wheelPixelsPerUnit = -.53;
+ else if (gecko) wheelPixelsPerUnit = 15;
+ else if (chrome) wheelPixelsPerUnit = -.7;
+ else if (safari) wheelPixelsPerUnit = -1/3;
+
+ function onScrollWheel(cm, e) {
+ var dx = e.wheelDeltaX, dy = e.wheelDeltaY;
+ if (dx == null && e.detail && e.axis == e.HORIZONTAL_AXIS) dx = e.detail;
+ if (dy == null && e.detail && e.axis == e.VERTICAL_AXIS) dy = e.detail;
+ else if (dy == null) dy = e.wheelDelta;
+
+ var display = cm.display, scroll = display.scroller;
+ // Quit if there's nothing to scroll here
+ if (!(dx && scroll.scrollWidth > scroll.clientWidth ||
+ dy && scroll.scrollHeight > scroll.clientHeight)) return;
+
+ // Webkit browsers on OS X abort momentum scrolls when the target
+ // of the scroll event is removed from the scrollable element.
+ // This hack (see related code in patchDisplay) makes sure the
+ // element is kept around.
+ if (dy && mac && webkit) {
+ for (var cur = e.target; cur != scroll; cur = cur.parentNode) {
+ if (cur.lineObj) {
+ cm.display.currentWheelTarget = cur;
+ break;
+ }
+ }
+ }
+
+ // On some browsers, horizontal scrolling will cause redraws to
+ // happen before the gutter has been realigned, causing it to
+ // wriggle around in a most unseemly way. When we have an
+ // estimated pixels/delta value, we just handle horizontal
+ // scrolling entirely here. It'll be slightly off from native, but
+ // better than glitching out.
+ if (dx && !gecko && !opera && wheelPixelsPerUnit != null) {
+ if (dy)
+ setScrollTop(cm, Math.max(0, Math.min(scroll.scrollTop + dy * wheelPixelsPerUnit, scroll.scrollHeight - scroll.clientHeight)));
+ setScrollLeft(cm, Math.max(0, Math.min(scroll.scrollLeft + dx * wheelPixelsPerUnit, scroll.scrollWidth - scroll.clientWidth)));
+ e_preventDefault(e);
+ display.wheelStartX = null; // Abort measurement, if in progress
+ return;
+ }
+
+ if (dy && wheelPixelsPerUnit != null) {
+ var pixels = dy * wheelPixelsPerUnit;
+ var top = cm.doc.scrollTop, bot = top + display.wrapper.clientHeight;
+ if (pixels < 0) top = Math.max(0, top + pixels - 50);
+ else bot = Math.min(cm.doc.height, bot + pixels + 50);
+ updateDisplay(cm, [], {top: top, bottom: bot});
+ }
+
+ if (wheelSamples < 20) {
+ if (display.wheelStartX == null) {
+ display.wheelStartX = scroll.scrollLeft; display.wheelStartY = scroll.scrollTop;
+ display.wheelDX = dx; display.wheelDY = dy;
+ setTimeout(function() {
+ if (display.wheelStartX == null) return;
+ var movedX = scroll.scrollLeft - display.wheelStartX;
+ var movedY = scroll.scrollTop - display.wheelStartY;
+ var sample = (movedY && display.wheelDY && movedY / display.wheelDY) ||
+ (movedX && display.wheelDX && movedX / display.wheelDX);
+ display.wheelStartX = display.wheelStartY = null;
+ if (!sample) return;
+ wheelPixelsPerUnit = (wheelPixelsPerUnit * wheelSamples + sample) / (wheelSamples + 1);
+ ++wheelSamples;
+ }, 200);
+ } else {
+ display.wheelDX += dx; display.wheelDY += dy;
+ }
+ }
+ }
+
+ function doHandleBinding(cm, bound, dropShift) {
+ if (typeof bound == "string") {
+ bound = commands[bound];
+ if (!bound) return false;
+ }
+ // Ensure previous input has been read, so that the handler sees a
+ // consistent view of the document
+ if (cm.display.pollingFast && readInput(cm)) cm.display.pollingFast = false;
+ var doc = cm.doc, prevShift = doc.sel.shift, done = false;
+ try {
+ if (isReadOnly(cm)) cm.state.suppressEdits = true;
+ if (dropShift) doc.sel.shift = false;
+ done = bound(cm) != Pass;
+ } finally {
+ doc.sel.shift = prevShift;
+ cm.state.suppressEdits = false;
+ }
+ return done;
+ }
+
+ function allKeyMaps(cm) {
+ var maps = cm.state.keyMaps.slice(0);
+ if (cm.options.extraKeys) maps.push(cm.options.extraKeys);
+ maps.push(cm.options.keyMap);
+ return maps;
+ }
+
+ var maybeTransition;
+ function handleKeyBinding(cm, e) {
+ // Handle auto keymap transitions
+ var startMap = getKeyMap(cm.options.keyMap), next = startMap.auto;
+ clearTimeout(maybeTransition);
+ if (next && !isModifierKey(e)) maybeTransition = setTimeout(function() {
+ if (getKeyMap(cm.options.keyMap) == startMap) {
+ cm.options.keyMap = (next.call ? next.call(null, cm) : next);
+ keyMapChanged(cm);
+ }
+ }, 50);
+
+ var name = keyName(e, true), handled = false;
+ if (!name) return false;
+ var keymaps = allKeyMaps(cm);
+
+ if (e.shiftKey) {
+ // First try to resolve full name (including 'Shift-'). Failing
+ // that, see if there is a cursor-motion command (starting with
+ // 'go') bound to the keyname without 'Shift-'.
+ handled = lookupKey("Shift-" + name, keymaps, function(b) {return doHandleBinding(cm, b, true);})
+ || lookupKey(name, keymaps, function(b) {
+ if (typeof b == "string" ? /^go[A-Z]/.test(b) : b.motion)
+ return doHandleBinding(cm, b);
+ });
+ } else {
+ handled = lookupKey(name, keymaps, function(b) { return doHandleBinding(cm, b); });
+ }
+
+ if (handled) {
+ e_preventDefault(e);
+ restartBlink(cm);
+ if (ie_lt9) { e.oldKeyCode = e.keyCode; e.keyCode = 0; }
+ signalLater(cm, "keyHandled", cm, name, e);
+ }
+ return handled;
+ }
+
+ function handleCharBinding(cm, e, ch) {
+ var handled = lookupKey("'" + ch + "'", allKeyMaps(cm),
+ function(b) { return doHandleBinding(cm, b, true); });
+ if (handled) {
+ e_preventDefault(e);
+ restartBlink(cm);
+ signalLater(cm, "keyHandled", cm, "'" + ch + "'", e);
+ }
+ return handled;
+ }
+
+ var lastStoppedKey = null;
+ function onKeyDown(e) {
+ var cm = this;
+ if (!cm.state.focused) onFocus(cm);
+ if (ie && e.keyCode == 27) { e.returnValue = false; }
+ if (signalDOMEvent(cm, e) || cm.options.onKeyEvent && cm.options.onKeyEvent(cm, addStop(e))) return;
+ var code = e.keyCode;
+ // IE does strange things with escape.
+ cm.doc.sel.shift = code == 16 || e.shiftKey;
+ // First give onKeyEvent option a chance to handle this.
+ var handled = handleKeyBinding(cm, e);
+ if (opera) {
+ lastStoppedKey = handled ? code : null;
+ // Opera has no cut event... we try to at least catch the key combo
+ if (!handled && code == 88 && !hasCopyEvent && (mac ? e.metaKey : e.ctrlKey))
+ cm.replaceSelection("");
+ }
+ }
+
+ function onKeyPress(e) {
+ var cm = this;
+ if (signalDOMEvent(cm, e) || cm.options.onKeyEvent && cm.options.onKeyEvent(cm, addStop(e))) return;
+ var keyCode = e.keyCode, charCode = e.charCode;
+ if (opera && keyCode == lastStoppedKey) {lastStoppedKey = null; e_preventDefault(e); return;}
+ if (((opera && (!e.which || e.which < 10)) || khtml) && handleKeyBinding(cm, e)) return;
+ var ch = String.fromCharCode(charCode == null ? keyCode : charCode);
+ if (this.options.electricChars && this.doc.mode.electricChars &&
+ this.options.smartIndent && !isReadOnly(this) &&
+ this.doc.mode.electricChars.indexOf(ch) > -1)
+ setTimeout(operation(cm, function() {indentLine(cm, cm.doc.sel.to.line, "smart");}), 75);
+ if (handleCharBinding(cm, e, ch)) return;
+ if (ie && !ie_lt9) cm.display.inputHasSelection = null;
+ fastPoll(cm);
+ }
+
+ function onFocus(cm) {
+ if (cm.options.readOnly == "nocursor") return;
+ if (!cm.state.focused) {
+ signal(cm, "focus", cm);
+ cm.state.focused = true;
+ if (cm.display.wrapper.className.search(/\bCodeMirror-focused\b/) == -1)
+ cm.display.wrapper.className += " CodeMirror-focused";
+ resetInput(cm, true);
+ }
+ slowPoll(cm);
+ restartBlink(cm);
+ }
+ function onBlur(cm) {
+ if (cm.state.focused) {
+ signal(cm, "blur", cm);
+ cm.state.focused = false;
+ cm.display.wrapper.className = cm.display.wrapper.className.replace(" CodeMirror-focused", "");
+ }
+ clearInterval(cm.display.blinker);
+ setTimeout(function() {if (!cm.state.focused) cm.doc.sel.shift = false;}, 150);
+ }
+
+ var detectingSelectAll;
+ function onContextMenu(cm, e) {
+ if (signalDOMEvent(cm, e, "contextmenu")) return;
+ var display = cm.display, sel = cm.doc.sel;
+ if (eventInWidget(display, e)) return;
+
+ var pos = posFromMouse(cm, e), scrollPos = display.scroller.scrollTop;
+ if (!pos || opera) return; // Opera is difficult.
+ if (posEq(sel.from, sel.to) || posLess(pos, sel.from) || !posLess(pos, sel.to))
+ operation(cm, setSelection)(cm.doc, pos, pos);
+
+ var oldCSS = display.input.style.cssText;
+ display.inputDiv.style.position = "absolute";
+ display.input.style.cssText = "position: fixed; width: 30px; height: 30px; top: " + (e.clientY - 5) +
+ "px; left: " + (e.clientX - 5) + "px; z-index: 1000; background: white; outline: none;" +
+ "border-width: 0; outline: none; overflow: hidden; opacity: .05; -ms-opacity: .05; filter: alpha(opacity=5);";
+ focusInput(cm);
+ resetInput(cm, true);
+ // Adds "Select all" to context menu in FF
+ if (posEq(sel.from, sel.to)) display.input.value = display.prevInput = " ";
+
+ function prepareSelectAllHack() {
+ if (display.input.selectionStart != null) {
+ var extval = display.input.value = " " + (posEq(sel.from, sel.to) ? "" : display.input.value);
+ display.prevInput = " ";
+ display.input.selectionStart = 1; display.input.selectionEnd = extval.length;
+ }
+ }
+ function rehide() {
+ display.inputDiv.style.position = "relative";
+ display.input.style.cssText = oldCSS;
+ if (ie_lt9) display.scrollbarV.scrollTop = display.scroller.scrollTop = scrollPos;
+ slowPoll(cm);
+
+ // Try to detect the user choosing select-all
+ if (display.input.selectionStart != null) {
+ if (!ie || ie_lt9) prepareSelectAllHack();
+ clearTimeout(detectingSelectAll);
+ var i = 0, poll = function(){
+ if (display.prevInput == " " && display.input.selectionStart == 0)
+ operation(cm, commands.selectAll)(cm);
+ else if (i++ < 10) detectingSelectAll = setTimeout(poll, 500);
+ else resetInput(cm);
+ };
+ detectingSelectAll = setTimeout(poll, 200);
+ }
+ }
+
+ if (ie && !ie_lt9) prepareSelectAllHack();
+ if (captureMiddleClick) {
+ e_stop(e);
+ var mouseup = function() {
+ off(window, "mouseup", mouseup);
+ setTimeout(rehide, 20);
+ };
+ on(window, "mouseup", mouseup);
+ } else {
+ setTimeout(rehide, 50);
+ }
+ }
+
+ // UPDATING
+
+ var changeEnd = CodeMirror.changeEnd = function(change) {
+ if (!change.text) return change.to;
+ return Pos(change.from.line + change.text.length - 1,
+ lst(change.text).length + (change.text.length == 1 ? change.from.ch : 0));
+ };
+
+ // Make sure a position will be valid after the given change.
+ function clipPostChange(doc, change, pos) {
+ if (!posLess(change.from, pos)) return clipPos(doc, pos);
+ var diff = (change.text.length - 1) - (change.to.line - change.from.line);
+ if (pos.line > change.to.line + diff) {
+ var preLine = pos.line - diff, lastLine = doc.first + doc.size - 1;
+ if (preLine > lastLine) return Pos(lastLine, getLine(doc, lastLine).text.length);
+ return clipToLen(pos, getLine(doc, preLine).text.length);
+ }
+ if (pos.line == change.to.line + diff)
+ return clipToLen(pos, lst(change.text).length + (change.text.length == 1 ? change.from.ch : 0) +
+ getLine(doc, change.to.line).text.length - change.to.ch);
+ var inside = pos.line - change.from.line;
+ return clipToLen(pos, change.text[inside].length + (inside ? 0 : change.from.ch));
+ }
+
+ // Hint can be null|"end"|"start"|"around"|{anchor,head}
+ function computeSelAfterChange(doc, change, hint) {
+ if (hint && typeof hint == "object") // Assumed to be {anchor, head} object
+ return {anchor: clipPostChange(doc, change, hint.anchor),
+ head: clipPostChange(doc, change, hint.head)};
+
+ if (hint == "start") return {anchor: change.from, head: change.from};
+
+ var end = changeEnd(change);
+ if (hint == "around") return {anchor: change.from, head: end};
+ if (hint == "end") return {anchor: end, head: end};
+
+ // hint is null, leave the selection alone as much as possible
+ var adjustPos = function(pos) {
+ if (posLess(pos, change.from)) return pos;
+ if (!posLess(change.to, pos)) return end;
+
+ var line = pos.line + change.text.length - (change.to.line - change.from.line) - 1, ch = pos.ch;
+ if (pos.line == change.to.line) ch += end.ch - change.to.ch;
+ return Pos(line, ch);
+ };
+ return {anchor: adjustPos(doc.sel.anchor), head: adjustPos(doc.sel.head)};
+ }
+
+ function filterChange(doc, change, update) {
+ var obj = {
+ canceled: false,
+ from: change.from,
+ to: change.to,
+ text: change.text,
+ origin: change.origin,
+ cancel: function() { this.canceled = true; }
+ };
+ if (update) obj.update = function(from, to, text, origin) {
+ if (from) this.from = clipPos(doc, from);
+ if (to) this.to = clipPos(doc, to);
+ if (text) this.text = text;
+ if (origin !== undefined) this.origin = origin;
+ };
+ signal(doc, "beforeChange", doc, obj);
+ if (doc.cm) signal(doc.cm, "beforeChange", doc.cm, obj);
+
+ if (obj.canceled) return null;
+ return {from: obj.from, to: obj.to, text: obj.text, origin: obj.origin};
+ }
+
+ // Replace the range from from to to by the strings in replacement.
+ // change is a {from, to, text [, origin]} object
+ function makeChange(doc, change, selUpdate, ignoreReadOnly) {
+ if (doc.cm) {
+ if (!doc.cm.curOp) return operation(doc.cm, makeChange)(doc, change, selUpdate, ignoreReadOnly);
+ if (doc.cm.state.suppressEdits) return;
+ }
+
+ if (hasHandler(doc, "beforeChange") || doc.cm && hasHandler(doc.cm, "beforeChange")) {
+ change = filterChange(doc, change, true);
+ if (!change) return;
+ }
+
+ // Possibly split or suppress the update based on the presence
+ // of read-only spans in its range.
+ var split = sawReadOnlySpans && !ignoreReadOnly && removeReadOnlyRanges(doc, change.from, change.to);
+ if (split) {
+ for (var i = split.length - 1; i >= 1; --i)
+ makeChangeNoReadonly(doc, {from: split[i].from, to: split[i].to, text: [""]});
+ if (split.length)
+ makeChangeNoReadonly(doc, {from: split[0].from, to: split[0].to, text: change.text}, selUpdate);
+ } else {
+ makeChangeNoReadonly(doc, change, selUpdate);
+ }
+ }
+
+ function makeChangeNoReadonly(doc, change, selUpdate) {
+ var selAfter = computeSelAfterChange(doc, change, selUpdate);
+ addToHistory(doc, change, selAfter, doc.cm ? doc.cm.curOp.id : NaN);
+
+ makeChangeSingleDoc(doc, change, selAfter, stretchSpansOverChange(doc, change));
+ var rebased = [];
+
+ linkedDocs(doc, function(doc, sharedHist) {
+ if (!sharedHist && indexOf(rebased, doc.history) == -1) {
+ rebaseHist(doc.history, change);
+ rebased.push(doc.history);
+ }
+ makeChangeSingleDoc(doc, change, null, stretchSpansOverChange(doc, change));
+ });
+ }
+
+ function makeChangeFromHistory(doc, type) {
+ if (doc.cm && doc.cm.state.suppressEdits) return;
+
+ var hist = doc.history;
+ var event = (type == "undo" ? hist.done : hist.undone).pop();
+ if (!event) return;
+
+ var anti = {changes: [], anchorBefore: event.anchorAfter, headBefore: event.headAfter,
+ anchorAfter: event.anchorBefore, headAfter: event.headBefore,
+ generation: hist.generation};
+ (type == "undo" ? hist.undone : hist.done).push(anti);
+ hist.generation = event.generation || ++hist.maxGeneration;
+
+ var filter = hasHandler(doc, "beforeChange") || doc.cm && hasHandler(doc.cm, "beforeChange");
+
+ for (var i = event.changes.length - 1; i >= 0; --i) {
+ var change = event.changes[i];
+ change.origin = type;
+ if (filter && !filterChange(doc, change, false)) {
+ (type == "undo" ? hist.done : hist.undone).length = 0;
+ return;
+ }
+
+ anti.changes.push(historyChangeFromChange(doc, change));
+
+ var after = i ? computeSelAfterChange(doc, change, null)
+ : {anchor: event.anchorBefore, head: event.headBefore};
+ makeChangeSingleDoc(doc, change, after, mergeOldSpans(doc, change));
+ var rebased = [];
+
+ linkedDocs(doc, function(doc, sharedHist) {
+ if (!sharedHist && indexOf(rebased, doc.history) == -1) {
+ rebaseHist(doc.history, change);
+ rebased.push(doc.history);
+ }
+ makeChangeSingleDoc(doc, change, null, mergeOldSpans(doc, change));
+ });
+ }
+ }
+
+ function shiftDoc(doc, distance) {
+ function shiftPos(pos) {return Pos(pos.line + distance, pos.ch);}
+ doc.first += distance;
+ if (doc.cm) regChange(doc.cm, doc.first, doc.first, distance);
+ doc.sel.head = shiftPos(doc.sel.head); doc.sel.anchor = shiftPos(doc.sel.anchor);
+ doc.sel.from = shiftPos(doc.sel.from); doc.sel.to = shiftPos(doc.sel.to);
+ }
+
+ function makeChangeSingleDoc(doc, change, selAfter, spans) {
+ if (doc.cm && !doc.cm.curOp)
+ return operation(doc.cm, makeChangeSingleDoc)(doc, change, selAfter, spans);
+
+ if (change.to.line < doc.first) {
+ shiftDoc(doc, change.text.length - 1 - (change.to.line - change.from.line));
+ return;
+ }
+ if (change.from.line > doc.lastLine()) return;
+
+ // Clip the change to the size of this doc
+ if (change.from.line < doc.first) {
+ var shift = change.text.length - 1 - (doc.first - change.from.line);
+ shiftDoc(doc, shift);
+ change = {from: Pos(doc.first, 0), to: Pos(change.to.line + shift, change.to.ch),
+ text: [lst(change.text)], origin: change.origin};
+ }
+ var last = doc.lastLine();
+ if (change.to.line > last) {
+ change = {from: change.from, to: Pos(last, getLine(doc, last).text.length),
+ text: [change.text[0]], origin: change.origin};
+ }
+
+ change.removed = getBetween(doc, change.from, change.to);
+
+ if (!selAfter) selAfter = computeSelAfterChange(doc, change, null);
+ if (doc.cm) makeChangeSingleDocInEditor(doc.cm, change, spans, selAfter);
+ else updateDoc(doc, change, spans, selAfter);
+ }
+
+ function makeChangeSingleDocInEditor(cm, change, spans, selAfter) {
+ var doc = cm.doc, display = cm.display, from = change.from, to = change.to;
+
+ var recomputeMaxLength = false, checkWidthStart = from.line;
+ if (!cm.options.lineWrapping) {
+ checkWidthStart = lineNo(visualLine(doc, getLine(doc, from.line)));
+ doc.iter(checkWidthStart, to.line + 1, function(line) {
+ if (line == display.maxLine) {
+ recomputeMaxLength = true;
+ return true;
+ }
+ });
+ }
+
+ if (!posLess(doc.sel.head, change.from) && !posLess(change.to, doc.sel.head))
+ cm.curOp.cursorActivity = true;
+
+ updateDoc(doc, change, spans, selAfter, estimateHeight(cm));
+
+ if (!cm.options.lineWrapping) {
+ doc.iter(checkWidthStart, from.line + change.text.length, function(line) {
+ var len = lineLength(doc, line);
+ if (len > display.maxLineLength) {
+ display.maxLine = line;
+ display.maxLineLength = len;
+ display.maxLineChanged = true;
+ recomputeMaxLength = false;
+ }
+ });
+ if (recomputeMaxLength) cm.curOp.updateMaxLine = true;
+ }
+
+ // Adjust frontier, schedule worker
+ doc.frontier = Math.min(doc.frontier, from.line);
+ startWorker(cm, 400);
+
+ var lendiff = change.text.length - (to.line - from.line) - 1;
+ // Remember that these lines changed, for updating the display
+ regChange(cm, from.line, to.line + 1, lendiff);
+
+ if (hasHandler(cm, "change")) {
+ var changeObj = {from: from, to: to,
+ text: change.text,
+ removed: change.removed,
+ origin: change.origin};
+ if (cm.curOp.textChanged) {
+ for (var cur = cm.curOp.textChanged; cur.next; cur = cur.next) {}
+ cur.next = changeObj;
+ } else cm.curOp.textChanged = changeObj;
+ }
+ }
+
+ function replaceRange(doc, code, from, to, origin) {
+ if (!to) to = from;
+ if (posLess(to, from)) { var tmp = to; to = from; from = tmp; }
+ if (typeof code == "string") code = splitLines(code);
+ makeChange(doc, {from: from, to: to, text: code, origin: origin}, null);
+ }
+
+ // POSITION OBJECT
+
+ function Pos(line, ch) {
+ if (!(this instanceof Pos)) return new Pos(line, ch);
+ this.line = line; this.ch = ch;
+ }
+ CodeMirror.Pos = Pos;
+
+ function posEq(a, b) {return a.line == b.line && a.ch == b.ch;}
+ function posLess(a, b) {return a.line < b.line || (a.line == b.line && a.ch < b.ch);}
+ function copyPos(x) {return Pos(x.line, x.ch);}
+
+ // SELECTION
+
+ function clipLine(doc, n) {return Math.max(doc.first, Math.min(n, doc.first + doc.size - 1));}
+ function clipPos(doc, pos) {
+ if (pos.line < doc.first) return Pos(doc.first, 0);
+ var last = doc.first + doc.size - 1;
+ if (pos.line > last) return Pos(last, getLine(doc, last).text.length);
+ return clipToLen(pos, getLine(doc, pos.line).text.length);
+ }
+ function clipToLen(pos, linelen) {
+ var ch = pos.ch;
+ if (ch == null || ch > linelen) return Pos(pos.line, linelen);
+ else if (ch < 0) return Pos(pos.line, 0);
+ else return pos;
+ }
+ function isLine(doc, l) {return l >= doc.first && l < doc.first + doc.size;}
+
+ // If shift is held, this will move the selection anchor. Otherwise,
+ // it'll set the whole selection.
+ function extendSelection(doc, pos, other, bias) {
+ if (doc.sel.shift || doc.sel.extend) {
+ var anchor = doc.sel.anchor;
+ if (other) {
+ var posBefore = posLess(pos, anchor);
+ if (posBefore != posLess(other, anchor)) {
+ anchor = pos;
+ pos = other;
+ } else if (posBefore != posLess(pos, other)) {
+ pos = other;
+ }
+ }
+ setSelection(doc, anchor, pos, bias);
+ } else {
+ setSelection(doc, pos, other || pos, bias);
+ }
+ if (doc.cm) doc.cm.curOp.userSelChange = true;
+ }
+
+ function filterSelectionChange(doc, anchor, head) {
+ var obj = {anchor: anchor, head: head};
+ signal(doc, "beforeSelectionChange", doc, obj);
+ if (doc.cm) signal(doc.cm, "beforeSelectionChange", doc.cm, obj);
+ obj.anchor = clipPos(doc, obj.anchor); obj.head = clipPos(doc, obj.head);
+ return obj;
+ }
+
+ // Update the selection. Last two args are only used by
+ // updateDoc, since they have to be expressed in the line
+ // numbers before the update.
+ function setSelection(doc, anchor, head, bias, checkAtomic) {
+ if (!checkAtomic && hasHandler(doc, "beforeSelectionChange") || doc.cm && hasHandler(doc.cm, "beforeSelectionChange")) {
+ var filtered = filterSelectionChange(doc, anchor, head);
+ head = filtered.head;
+ anchor = filtered.anchor;
+ }
+
+ var sel = doc.sel;
+ sel.goalColumn = null;
+ // Skip over atomic spans.
+ if (checkAtomic || !posEq(anchor, sel.anchor))
+ anchor = skipAtomic(doc, anchor, bias, checkAtomic != "push");
+ if (checkAtomic || !posEq(head, sel.head))
+ head = skipAtomic(doc, head, bias, checkAtomic != "push");
+
+ if (posEq(sel.anchor, anchor) && posEq(sel.head, head)) return;
+
+ sel.anchor = anchor; sel.head = head;
+ var inv = posLess(head, anchor);
+ sel.from = inv ? head : anchor;
+ sel.to = inv ? anchor : head;
+
+ if (doc.cm)
+ doc.cm.curOp.updateInput = doc.cm.curOp.selectionChanged =
+ doc.cm.curOp.cursorActivity = true;
+
+ signalLater(doc, "cursorActivity", doc);
+ }
+
+ function reCheckSelection(cm) {
+ setSelection(cm.doc, cm.doc.sel.from, cm.doc.sel.to, null, "push");
+ }
+
+ function skipAtomic(doc, pos, bias, mayClear) {
+ var flipped = false, curPos = pos;
+ var dir = bias || 1;
+ doc.cantEdit = false;
+ search: for (;;) {
+ var line = getLine(doc, curPos.line);
+ if (line.markedSpans) {
+ for (var i = 0; i < line.markedSpans.length; ++i) {
+ var sp = line.markedSpans[i], m = sp.marker;
+ if ((sp.from == null || (m.inclusiveLeft ? sp.from <= curPos.ch : sp.from < curPos.ch)) &&
+ (sp.to == null || (m.inclusiveRight ? sp.to >= curPos.ch : sp.to > curPos.ch))) {
+ if (mayClear) {
+ signal(m, "beforeCursorEnter");
+ if (m.explicitlyCleared) {
+ if (!line.markedSpans) break;
+ else {--i; continue;}
+ }
+ }
+ if (!m.atomic) continue;
+ var newPos = m.find()[dir < 0 ? "from" : "to"];
+ if (posEq(newPos, curPos)) {
+ newPos.ch += dir;
+ if (newPos.ch < 0) {
+ if (newPos.line > doc.first) newPos = clipPos(doc, Pos(newPos.line - 1));
+ else newPos = null;
+ } else if (newPos.ch > line.text.length) {
+ if (newPos.line < doc.first + doc.size - 1) newPos = Pos(newPos.line + 1, 0);
+ else newPos = null;
+ }
+ if (!newPos) {
+ if (flipped) {
+ // Driven in a corner -- no valid cursor position found at all
+ // -- try again *with* clearing, if we didn't already
+ if (!mayClear) return skipAtomic(doc, pos, bias, true);
+ // Otherwise, turn off editing until further notice, and return the start of the doc
+ doc.cantEdit = true;
+ return Pos(doc.first, 0);
+ }
+ flipped = true; newPos = pos; dir = -dir;
+ }
+ }
+ curPos = newPos;
+ continue search;
+ }
+ }
+ }
+ return curPos;
+ }
+ }
+
+ // SCROLLING
+
+ function scrollCursorIntoView(cm) {
+ var coords = scrollPosIntoView(cm, cm.doc.sel.head, cm.options.cursorScrollMargin);
+ if (!cm.state.focused) return;
+ var display = cm.display, box = getRect(display.sizer), doScroll = null;
+ if (coords.top + box.top < 0) doScroll = true;
+ else if (coords.bottom + box.top > (window.innerHeight || document.documentElement.clientHeight)) doScroll = false;
+ if (doScroll != null && !phantom) {
+ var hidden = display.cursor.style.display == "none";
+ if (hidden) {
+ display.cursor.style.display = "";
+ display.cursor.style.left = coords.left + "px";
+ display.cursor.style.top = (coords.top - display.viewOffset) + "px";
+ }
+ display.cursor.scrollIntoView(doScroll);
+ if (hidden) display.cursor.style.display = "none";
+ }
+ }
+
+ function scrollPosIntoView(cm, pos, margin) {
+ if (margin == null) margin = 0;
+ for (;;) {
+ var changed = false, coords = cursorCoords(cm, pos);
+ var scrollPos = calculateScrollPos(cm, coords.left, coords.top - margin, coords.left, coords.bottom + margin);
+ var startTop = cm.doc.scrollTop, startLeft = cm.doc.scrollLeft;
+ if (scrollPos.scrollTop != null) {
+ setScrollTop(cm, scrollPos.scrollTop);
+ if (Math.abs(cm.doc.scrollTop - startTop) > 1) changed = true;
+ }
+ if (scrollPos.scrollLeft != null) {
+ setScrollLeft(cm, scrollPos.scrollLeft);
+ if (Math.abs(cm.doc.scrollLeft - startLeft) > 1) changed = true;
+ }
+ if (!changed) return coords;
+ }
+ }
+
+ function scrollIntoView(cm, x1, y1, x2, y2) {
+ var scrollPos = calculateScrollPos(cm, x1, y1, x2, y2);
+ if (scrollPos.scrollTop != null) setScrollTop(cm, scrollPos.scrollTop);
+ if (scrollPos.scrollLeft != null) setScrollLeft(cm, scrollPos.scrollLeft);
+ }
+
+ function calculateScrollPos(cm, x1, y1, x2, y2) {
+ var display = cm.display, snapMargin = textHeight(cm.display);
+ if (y1 < 0) y1 = 0;
+ var screen = display.scroller.clientHeight - scrollerCutOff, screentop = display.scroller.scrollTop, result = {};
+ var docBottom = cm.doc.height + paddingVert(display);
+ var atTop = y1 < snapMargin, atBottom = y2 > docBottom - snapMargin;
+ if (y1 < screentop) {
+ result.scrollTop = atTop ? 0 : y1;
+ } else if (y2 > screentop + screen) {
+ var newTop = Math.min(y1, (atBottom ? docBottom : y2) - screen);
+ if (newTop != screentop) result.scrollTop = newTop;
+ }
+
+ var screenw = display.scroller.clientWidth - scrollerCutOff, screenleft = display.scroller.scrollLeft;
+ x1 += display.gutters.offsetWidth; x2 += display.gutters.offsetWidth;
+ var gutterw = display.gutters.offsetWidth;
+ var atLeft = x1 < gutterw + 10;
+ if (x1 < screenleft + gutterw || atLeft) {
+ if (atLeft) x1 = 0;
+ result.scrollLeft = Math.max(0, x1 - 10 - gutterw);
+ } else if (x2 > screenw + screenleft - 3) {
+ result.scrollLeft = x2 + 10 - screenw;
+ }
+ return result;
+ }
+
+ function updateScrollPos(cm, left, top) {
+ cm.curOp.updateScrollPos = {scrollLeft: left == null ? cm.doc.scrollLeft : left,
+ scrollTop: top == null ? cm.doc.scrollTop : top};
+ }
+
+ function addToScrollPos(cm, left, top) {
+ var pos = cm.curOp.updateScrollPos || (cm.curOp.updateScrollPos = {scrollLeft: cm.doc.scrollLeft, scrollTop: cm.doc.scrollTop});
+ var scroll = cm.display.scroller;
+ pos.scrollTop = Math.max(0, Math.min(scroll.scrollHeight - scroll.clientHeight, pos.scrollTop + top));
+ pos.scrollLeft = Math.max(0, Math.min(scroll.scrollWidth - scroll.clientWidth, pos.scrollLeft + left));
+ }
+
+ // API UTILITIES
+
+ function indentLine(cm, n, how, aggressive) {
+ var doc = cm.doc;
+ if (how == null) how = "add";
+ if (how == "smart") {
+ if (!cm.doc.mode.indent) how = "prev";
+ else var state = getStateBefore(cm, n);
+ }
+
+ var tabSize = cm.options.tabSize;
+ var line = getLine(doc, n), curSpace = countColumn(line.text, null, tabSize);
+ var curSpaceString = line.text.match(/^\s*/)[0], indentation;
+ if (how == "smart") {
+ indentation = cm.doc.mode.indent(state, line.text.slice(curSpaceString.length), line.text);
+ if (indentation == Pass) {
+ if (!aggressive) return;
+ how = "prev";
+ }
+ }
+ if (how == "prev") {
+ if (n > doc.first) indentation = countColumn(getLine(doc, n-1).text, null, tabSize);
+ else indentation = 0;
+ } else if (how == "add") {
+ indentation = curSpace + cm.options.indentUnit;
+ } else if (how == "subtract") {
+ indentation = curSpace - cm.options.indentUnit;
+ } else if (typeof how == "number") {
+ indentation = curSpace + how;
+ }
+ indentation = Math.max(0, indentation);
+
+ var indentString = "", pos = 0;
+ if (cm.options.indentWithTabs)
+ for (var i = Math.floor(indentation / tabSize); i; --i) {pos += tabSize; indentString += "\t";}
+ if (pos < indentation) indentString += spaceStr(indentation - pos);
+
+ if (indentString != curSpaceString)
+ replaceRange(cm.doc, indentString, Pos(n, 0), Pos(n, curSpaceString.length), "+input");
+ line.stateAfter = null;
+ }
+
+ function changeLine(cm, handle, op) {
+ var no = handle, line = handle, doc = cm.doc;
+ if (typeof handle == "number") line = getLine(doc, clipLine(doc, handle));
+ else no = lineNo(handle);
+ if (no == null) return null;
+ if (op(line, no)) regChange(cm, no, no + 1);
+ else return null;
+ return line;
+ }
+
+ function findPosH(doc, pos, dir, unit, visually) {
+ var line = pos.line, ch = pos.ch, origDir = dir;
+ var lineObj = getLine(doc, line);
+ var possible = true;
+ function findNextLine() {
+ var l = line + dir;
+ if (l < doc.first || l >= doc.first + doc.size) return (possible = false);
+ line = l;
+ return lineObj = getLine(doc, l);
+ }
+ function moveOnce(boundToLine) {
+ var next = (visually ? moveVisually : moveLogically)(lineObj, ch, dir, true);
+ if (next == null) {
+ if (!boundToLine && findNextLine()) {
+ if (visually) ch = (dir < 0 ? lineRight : lineLeft)(lineObj);
+ else ch = dir < 0 ? lineObj.text.length : 0;
+ } else return (possible = false);
+ } else ch = next;
+ return true;
+ }
+
+ if (unit == "char") moveOnce();
+ else if (unit == "column") moveOnce(true);
+ else if (unit == "word" || unit == "group") {
+ var sawType = null, group = unit == "group";
+ for (var first = true;; first = false) {
+ if (dir < 0 && !moveOnce(!first)) break;
+ var cur = lineObj.text.charAt(ch) || "\n";
+ var type = isWordChar(cur) ? "w"
+ : !group ? null
+ : /\s/.test(cur) ? null
+ : "p";
+ if (sawType && sawType != type) {
+ if (dir < 0) {dir = 1; moveOnce();}
+ break;
+ }
+ if (type) sawType = type;
+ if (dir > 0 && !moveOnce(!first)) break;
+ }
+ }
+ var result = skipAtomic(doc, Pos(line, ch), origDir, true);
+ if (!possible) result.hitSide = true;
+ return result;
+ }
+
+ function findPosV(cm, pos, dir, unit) {
+ var doc = cm.doc, x = pos.left, y;
+ if (unit == "page") {
+ var pageSize = Math.min(cm.display.wrapper.clientHeight, window.innerHeight || document.documentElement.clientHeight);
+ y = pos.top + dir * (pageSize - (dir < 0 ? 1.5 : .5) * textHeight(cm.display));
+ } else if (unit == "line") {
+ y = dir > 0 ? pos.bottom + 3 : pos.top - 3;
+ }
+ for (;;) {
+ var target = coordsChar(cm, x, y);
+ if (!target.outside) break;
+ if (dir < 0 ? y <= 0 : y >= doc.height) { target.hitSide = true; break; }
+ y += dir * 5;
+ }
+ return target;
+ }
+
+ function findWordAt(line, pos) {
+ var start = pos.ch, end = pos.ch;
+ if (line) {
+ if ((pos.xRel < 0 || end == line.length) && start) --start; else ++end;
+ var startChar = line.charAt(start);
+ var check = isWordChar(startChar) ? isWordChar
+ : /\s/.test(startChar) ? function(ch) {return /\s/.test(ch);}
+ : function(ch) {return !/\s/.test(ch) && !isWordChar(ch);};
+ while (start > 0 && check(line.charAt(start - 1))) --start;
+ while (end < line.length && check(line.charAt(end))) ++end;
+ }
+ return {from: Pos(pos.line, start), to: Pos(pos.line, end)};
+ }
+
+ function selectLine(cm, line) {
+ extendSelection(cm.doc, Pos(line, 0), clipPos(cm.doc, Pos(line + 1, 0)));
+ }
+
+ // PROTOTYPE
+
+ // The publicly visible API. Note that operation(null, f) means
+ // 'wrap f in an operation, performed on its `this` parameter'
+
+ CodeMirror.prototype = {
+ constructor: CodeMirror,
+ focus: function(){window.focus(); focusInput(this); onFocus(this); fastPoll(this);},
+
+ setOption: function(option, value) {
+ var options = this.options, old = options[option];
+ if (options[option] == value && option != "mode") return;
+ options[option] = value;
+ if (optionHandlers.hasOwnProperty(option))
+ operation(this, optionHandlers[option])(this, value, old);
+ },
+
+ getOption: function(option) {return this.options[option];},
+ getDoc: function() {return this.doc;},
+
+ addKeyMap: function(map, bottom) {
+ this.state.keyMaps[bottom ? "push" : "unshift"](map);
+ },
+ removeKeyMap: function(map) {
+ var maps = this.state.keyMaps;
+ for (var i = 0; i < maps.length; ++i)
+ if (maps[i] == map || (typeof maps[i] != "string" && maps[i].name == map)) {
+ maps.splice(i, 1);
+ return true;
+ }
+ },
+
+ addOverlay: operation(null, function(spec, options) {
+ var mode = spec.token ? spec : CodeMirror.getMode(this.options, spec);
+ if (mode.startState) throw new Error("Overlays may not be stateful.");
+ this.state.overlays.push({mode: mode, modeSpec: spec, opaque: options && options.opaque});
+ this.state.modeGen++;
+ regChange(this);
+ }),
+ removeOverlay: operation(null, function(spec) {
+ var overlays = this.state.overlays;
+ for (var i = 0; i < overlays.length; ++i) {
+ var cur = overlays[i].modeSpec;
+ if (cur == spec || typeof spec == "string" && cur.name == spec) {
+ overlays.splice(i, 1);
+ this.state.modeGen++;
+ regChange(this);
+ return;
+ }
+ }
+ }),
+
+ indentLine: operation(null, function(n, dir, aggressive) {
+ if (typeof dir != "string" && typeof dir != "number") {
+ if (dir == null) dir = this.options.smartIndent ? "smart" : "prev";
+ else dir = dir ? "add" : "subtract";
+ }
+ if (isLine(this.doc, n)) indentLine(this, n, dir, aggressive);
+ }),
+ indentSelection: operation(null, function(how) {
+ var sel = this.doc.sel;
+ if (posEq(sel.from, sel.to)) return indentLine(this, sel.from.line, how);
+ var e = sel.to.line - (sel.to.ch ? 0 : 1);
+ for (var i = sel.from.line; i <= e; ++i) indentLine(this, i, how);
+ }),
+
+ // Fetch the parser token for a given character. Useful for hacks
+ // that want to inspect the mode state (say, for completion).
+ getTokenAt: function(pos, precise) {
+ var doc = this.doc;
+ pos = clipPos(doc, pos);
+ var state = getStateBefore(this, pos.line, precise), mode = this.doc.mode;
+ var line = getLine(doc, pos.line);
+ var stream = new StringStream(line.text, this.options.tabSize);
+ while (stream.pos < pos.ch && !stream.eol()) {
+ stream.start = stream.pos;
+ var style = mode.token(stream, state);
+ }
+ return {start: stream.start,
+ end: stream.pos,
+ string: stream.current(),
+ className: style || null, // Deprecated, use 'type' instead
+ type: style || null,
+ state: state};
+ },
+
+ getTokenTypeAt: function(pos) {
+ pos = clipPos(this.doc, pos);
+ var styles = getLineStyles(this, getLine(this.doc, pos.line));
+ var before = 0, after = (styles.length - 1) / 2, ch = pos.ch;
+ if (ch == 0) return styles[2];
+ for (;;) {
+ var mid = (before + after) >> 1;
+ if ((mid ? styles[mid * 2 - 1] : 0) >= ch) after = mid;
+ else if (styles[mid * 2 + 1] < ch) before = mid + 1;
+ else return styles[mid * 2 + 2];
+ }
+ },
+
+ getModeAt: function(pos) {
+ var mode = this.doc.mode;
+ if (!mode.innerMode) return mode;
+ return CodeMirror.innerMode(mode, this.getTokenAt(pos).state).mode;
+ },
+
+ getHelper: function(pos, type) {
+ if (!helpers.hasOwnProperty(type)) return;
+ var help = helpers[type], mode = this.getModeAt(pos);
+ return mode[type] && help[mode[type]] ||
+ mode.helperType && help[mode.helperType] ||
+ help[mode.name];
+ },
+
+ getStateAfter: function(line, precise) {
+ var doc = this.doc;
+ line = clipLine(doc, line == null ? doc.first + doc.size - 1: line);
+ return getStateBefore(this, line + 1, precise);
+ },
+
+ cursorCoords: function(start, mode) {
+ var pos, sel = this.doc.sel;
+ if (start == null) pos = sel.head;
+ else if (typeof start == "object") pos = clipPos(this.doc, start);
+ else pos = start ? sel.from : sel.to;
+ return cursorCoords(this, pos, mode || "page");
+ },
+
+ charCoords: function(pos, mode) {
+ return charCoords(this, clipPos(this.doc, pos), mode || "page");
+ },
+
+ coordsChar: function(coords, mode) {
+ coords = fromCoordSystem(this, coords, mode || "page");
+ return coordsChar(this, coords.left, coords.top);
+ },
+
+ lineAtHeight: function(height, mode) {
+ height = fromCoordSystem(this, {top: height, left: 0}, mode || "page").top;
+ return lineAtHeight(this.doc, height + this.display.viewOffset);
+ },
+ heightAtLine: function(line, mode) {
+ var end = false, last = this.doc.first + this.doc.size - 1;
+ if (line < this.doc.first) line = this.doc.first;
+ else if (line > last) { line = last; end = true; }
+ var lineObj = getLine(this.doc, line);
+ return intoCoordSystem(this, getLine(this.doc, line), {top: 0, left: 0}, mode || "page").top +
+ (end ? lineObj.height : 0);
+ },
+
+ defaultTextHeight: function() { return textHeight(this.display); },
+ defaultCharWidth: function() { return charWidth(this.display); },
+
+ setGutterMarker: operation(null, function(line, gutterID, value) {
+ return changeLine(this, line, function(line) {
+ var markers = line.gutterMarkers || (line.gutterMarkers = {});
+ markers[gutterID] = value;
+ if (!value && isEmpty(markers)) line.gutterMarkers = null;
+ return true;
+ });
+ }),
+
+ clearGutter: operation(null, function(gutterID) {
+ var cm = this, doc = cm.doc, i = doc.first;
+ doc.iter(function(line) {
+ if (line.gutterMarkers && line.gutterMarkers[gutterID]) {
+ line.gutterMarkers[gutterID] = null;
+ regChange(cm, i, i + 1);
+ if (isEmpty(line.gutterMarkers)) line.gutterMarkers = null;
+ }
+ ++i;
+ });
+ }),
+
+ addLineClass: operation(null, function(handle, where, cls) {
+ return changeLine(this, handle, function(line) {
+ var prop = where == "text" ? "textClass" : where == "background" ? "bgClass" : "wrapClass";
+ if (!line[prop]) line[prop] = cls;
+ else if (new RegExp("(?:^|\\s)" + cls + "(?:$|\\s)").test(line[prop])) return false;
+ else line[prop] += " " + cls;
+ return true;
+ });
+ }),
+
+ removeLineClass: operation(null, function(handle, where, cls) {
+ return changeLine(this, handle, function(line) {
+ var prop = where == "text" ? "textClass" : where == "background" ? "bgClass" : "wrapClass";
+ var cur = line[prop];
+ if (!cur) return false;
+ else if (cls == null) line[prop] = null;
+ else {
+ var found = cur.match(new RegExp("(?:^|\\s+)" + cls + "(?:$|\\s+)"));
+ if (!found) return false;
+ var end = found.index + found[0].length;
+ line[prop] = cur.slice(0, found.index) + (!found.index || end == cur.length ? "" : " ") + cur.slice(end) || null;
+ }
+ return true;
+ });
+ }),
+
+ addLineWidget: operation(null, function(handle, node, options) {
+ return addLineWidget(this, handle, node, options);
+ }),
+
+ removeLineWidget: function(widget) { widget.clear(); },
+
+ lineInfo: function(line) {
+ if (typeof line == "number") {
+ if (!isLine(this.doc, line)) return null;
+ var n = line;
+ line = getLine(this.doc, line);
+ if (!line) return null;
+ } else {
+ var n = lineNo(line);
+ if (n == null) return null;
+ }
+ return {line: n, handle: line, text: line.text, gutterMarkers: line.gutterMarkers,
+ textClass: line.textClass, bgClass: line.bgClass, wrapClass: line.wrapClass,
+ widgets: line.widgets};
+ },
+
+ getViewport: function() { return {from: this.display.showingFrom, to: this.display.showingTo};},
+
+ addWidget: function(pos, node, scroll, vert, horiz) {
+ var display = this.display;
+ pos = cursorCoords(this, clipPos(this.doc, pos));
+ var top = pos.bottom, left = pos.left;
+ node.style.position = "absolute";
+ display.sizer.appendChild(node);
+ if (vert == "over") {
+ top = pos.top;
+ } else if (vert == "above" || vert == "near") {
+ var vspace = Math.max(display.wrapper.clientHeight, this.doc.height),
+ hspace = Math.max(display.sizer.clientWidth, display.lineSpace.clientWidth);
+ // Default to positioning above (if specified and possible); otherwise default to positioning below
+ if ((vert == 'above' || pos.bottom + node.offsetHeight > vspace) && pos.top > node.offsetHeight)
+ top = pos.top - node.offsetHeight;
+ else if (pos.bottom + node.offsetHeight <= vspace)
+ top = pos.bottom;
+ if (left + node.offsetWidth > hspace)
+ left = hspace - node.offsetWidth;
+ }
+ node.style.top = top + "px";
+ node.style.left = node.style.right = "";
+ if (horiz == "right") {
+ left = display.sizer.clientWidth - node.offsetWidth;
+ node.style.right = "0px";
+ } else {
+ if (horiz == "left") left = 0;
+ else if (horiz == "middle") left = (display.sizer.clientWidth - node.offsetWidth) / 2;
+ node.style.left = left + "px";
+ }
+ if (scroll)
+ scrollIntoView(this, left, top, left + node.offsetWidth, top + node.offsetHeight);
+ },
+
+ triggerOnKeyDown: operation(null, onKeyDown),
+
+ execCommand: function(cmd) {return commands[cmd](this);},
+
+ findPosH: function(from, amount, unit, visually) {
+ var dir = 1;
+ if (amount < 0) { dir = -1; amount = -amount; }
+ for (var i = 0, cur = clipPos(this.doc, from); i < amount; ++i) {
+ cur = findPosH(this.doc, cur, dir, unit, visually);
+ if (cur.hitSide) break;
+ }
+ return cur;
+ },
+
+ moveH: operation(null, function(dir, unit) {
+ var sel = this.doc.sel, pos;
+ if (sel.shift || sel.extend || posEq(sel.from, sel.to))
+ pos = findPosH(this.doc, sel.head, dir, unit, this.options.rtlMoveVisually);
+ else
+ pos = dir < 0 ? sel.from : sel.to;
+ extendSelection(this.doc, pos, pos, dir);
+ }),
+
+ deleteH: operation(null, function(dir, unit) {
+ var sel = this.doc.sel;
+ if (!posEq(sel.from, sel.to)) replaceRange(this.doc, "", sel.from, sel.to, "+delete");
+ else replaceRange(this.doc, "", sel.from, findPosH(this.doc, sel.head, dir, unit, false), "+delete");
+ this.curOp.userSelChange = true;
+ }),
+
+ findPosV: function(from, amount, unit, goalColumn) {
+ var dir = 1, x = goalColumn;
+ if (amount < 0) { dir = -1; amount = -amount; }
+ for (var i = 0, cur = clipPos(this.doc, from); i < amount; ++i) {
+ var coords = cursorCoords(this, cur, "div");
+ if (x == null) x = coords.left;
+ else coords.left = x;
+ cur = findPosV(this, coords, dir, unit);
+ if (cur.hitSide) break;
+ }
+ return cur;
+ },
+
+ moveV: operation(null, function(dir, unit) {
+ var sel = this.doc.sel;
+ var pos = cursorCoords(this, sel.head, "div");
+ if (sel.goalColumn != null) pos.left = sel.goalColumn;
+ var target = findPosV(this, pos, dir, unit);
+
+ if (unit == "page") addToScrollPos(this, 0, charCoords(this, target, "div").top - pos.top);
+ extendSelection(this.doc, target, target, dir);
+ sel.goalColumn = pos.left;
+ }),
+
+ toggleOverwrite: function(value) {
+ if (value != null && value == this.state.overwrite) return;
+ if (this.state.overwrite = !this.state.overwrite)
+ this.display.cursor.className += " CodeMirror-overwrite";
+ else
+ this.display.cursor.className = this.display.cursor.className.replace(" CodeMirror-overwrite", "");
+ },
+ hasFocus: function() { return this.state.focused; },
+
+ scrollTo: operation(null, function(x, y) {
+ updateScrollPos(this, x, y);
+ }),
+ getScrollInfo: function() {
+ var scroller = this.display.scroller, co = scrollerCutOff;
+ return {left: scroller.scrollLeft, top: scroller.scrollTop,
+ height: scroller.scrollHeight - co, width: scroller.scrollWidth - co,
+ clientHeight: scroller.clientHeight - co, clientWidth: scroller.clientWidth - co};
+ },
+
+ scrollIntoView: operation(null, function(pos, margin) {
+ if (typeof pos == "number") pos = Pos(pos, 0);
+ if (!margin) margin = 0;
+ var coords = pos;
+
+ if (!pos || pos.line != null) {
+ this.curOp.scrollToPos = pos ? clipPos(this.doc, pos) : this.doc.sel.head;
+ this.curOp.scrollToPosMargin = margin;
+ coords = cursorCoords(this, this.curOp.scrollToPos);
+ }
+ var sPos = calculateScrollPos(this, coords.left, coords.top - margin, coords.right, coords.bottom + margin);
+ updateScrollPos(this, sPos.scrollLeft, sPos.scrollTop);
+ }),
+
+ setSize: operation(null, function(width, height) {
+ function interpret(val) {
+ return typeof val == "number" || /^\d+$/.test(String(val)) ? val + "px" : val;
+ }
+ if (width != null) this.display.wrapper.style.width = interpret(width);
+ if (height != null) this.display.wrapper.style.height = interpret(height);
+ if (this.options.lineWrapping)
+ this.display.measureLineCache.length = this.display.measureLineCachePos = 0;
+ this.curOp.forceUpdate = true;
+ }),
+
+ operation: function(f){return runInOp(this, f);},
+
+ refresh: operation(null, function() {
+ clearCaches(this);
+ updateScrollPos(this, this.doc.scrollLeft, this.doc.scrollTop);
+ regChange(this);
+ }),
+
+ swapDoc: operation(null, function(doc) {
+ var old = this.doc;
+ old.cm = null;
+ attachDoc(this, doc);
+ clearCaches(this);
+ resetInput(this, true);
+ updateScrollPos(this, doc.scrollLeft, doc.scrollTop);
+ return old;
+ }),
+
+ getInputField: function(){return this.display.input;},
+ getWrapperElement: function(){return this.display.wrapper;},
+ getScrollerElement: function(){return this.display.scroller;},
+ getGutterElement: function(){return this.display.gutters;}
+ };
+ eventMixin(CodeMirror);
+
+ // OPTION DEFAULTS
+
+ var optionHandlers = CodeMirror.optionHandlers = {};
+
+ // The default configuration options.
+ var defaults = CodeMirror.defaults = {};
+
+ function option(name, deflt, handle, notOnInit) {
+ CodeMirror.defaults[name] = deflt;
+ if (handle) optionHandlers[name] =
+ notOnInit ? function(cm, val, old) {if (old != Init) handle(cm, val, old);} : handle;
+ }
+
+ var Init = CodeMirror.Init = {toString: function(){return "CodeMirror.Init";}};
+
+ // These two are, on init, called from the constructor because they
+ // have to be initialized before the editor can start at all.
+ option("value", "", function(cm, val) {
+ cm.setValue(val);
+ }, true);
+ option("mode", null, function(cm, val) {
+ cm.doc.modeOption = val;
+ loadMode(cm);
+ }, true);
+
+ option("indentUnit", 2, loadMode, true);
+ option("indentWithTabs", false);
+ option("smartIndent", true);
+ option("tabSize", 4, function(cm) {
+ loadMode(cm);
+ clearCaches(cm);
+ regChange(cm);
+ }, true);
+ option("electricChars", true);
+ option("rtlMoveVisually", !windows);
+
+ option("theme", "default", function(cm) {
+ themeChanged(cm);
+ guttersChanged(cm);
+ }, true);
+ option("keyMap", "default", keyMapChanged);
+ option("extraKeys", null);
+
+ option("onKeyEvent", null);
+ option("onDragEvent", null);
+
+ option("lineWrapping", false, wrappingChanged, true);
+ option("gutters", [], function(cm) {
+ setGuttersForLineNumbers(cm.options);
+ guttersChanged(cm);
+ }, true);
+ option("fixedGutter", true, function(cm, val) {
+ cm.display.gutters.style.left = val ? compensateForHScroll(cm.display) + "px" : "0";
+ cm.refresh();
+ }, true);
+ option("coverGutterNextToScrollbar", false, updateScrollbars, true);
+ option("lineNumbers", false, function(cm) {
+ setGuttersForLineNumbers(cm.options);
+ guttersChanged(cm);
+ }, true);
+ option("firstLineNumber", 1, guttersChanged, true);
+ option("lineNumberFormatter", function(integer) {return integer;}, guttersChanged, true);
+ option("showCursorWhenSelecting", false, updateSelection, true);
+
+ option("readOnly", false, function(cm, val) {
+ if (val == "nocursor") {onBlur(cm); cm.display.input.blur();}
+ else if (!val) resetInput(cm, true);
+ });
+ option("dragDrop", true);
+
+ option("cursorBlinkRate", 530);
+ option("cursorScrollMargin", 0);
+ option("cursorHeight", 1);
+ option("workTime", 100);
+ option("workDelay", 100);
+ option("flattenSpans", true);
+ option("pollInterval", 100);
+ option("undoDepth", 40, function(cm, val){cm.doc.history.undoDepth = val;});
+ option("historyEventDelay", 500);
+ option("viewportMargin", 10, function(cm){cm.refresh();}, true);
+ option("maxHighlightLength", 10000, function(cm){loadMode(cm); cm.refresh();}, true);
+ option("moveInputWithCursor", true, function(cm, val) {
+ if (!val) cm.display.inputDiv.style.top = cm.display.inputDiv.style.left = 0;
+ });
+
+ option("tabindex", null, function(cm, val) {
+ cm.display.input.tabIndex = val || "";
+ });
+ option("autofocus", null);
+
+ // MODE DEFINITION AND QUERYING
+
+ // Known modes, by name and by MIME
+ var modes = CodeMirror.modes = {}, mimeModes = CodeMirror.mimeModes = {};
+
+ CodeMirror.defineMode = function(name, mode) {
+ if (!CodeMirror.defaults.mode && name != "null") CodeMirror.defaults.mode = name;
+ if (arguments.length > 2) {
+ mode.dependencies = [];
+ for (var i = 2; i < arguments.length; ++i) mode.dependencies.push(arguments[i]);
+ }
+ modes[name] = mode;
+ };
+
+ CodeMirror.defineMIME = function(mime, spec) {
+ mimeModes[mime] = spec;
+ };
+
+ CodeMirror.resolveMode = function(spec) {
+ if (typeof spec == "string" && mimeModes.hasOwnProperty(spec)) {
+ spec = mimeModes[spec];
+ } else if (spec && typeof spec.name == "string" && mimeModes.hasOwnProperty(spec.name)) {
+ var found = mimeModes[spec.name];
+ spec = createObj(found, spec);
+ spec.name = found.name;
+ } else if (typeof spec == "string" && /^[\w\-]+\/[\w\-]+\+xml$/.test(spec)) {
+ return CodeMirror.resolveMode("application/xml");
+ }
+ if (typeof spec == "string") return {name: spec};
+ else return spec || {name: "null"};
+ };
+
+ CodeMirror.getMode = function(options, spec) {
+ var spec = CodeMirror.resolveMode(spec);
+ var mfactory = modes[spec.name];
+ if (!mfactory) return CodeMirror.getMode(options, "text/plain");
+ var modeObj = mfactory(options, spec);
+ if (modeExtensions.hasOwnProperty(spec.name)) {
+ var exts = modeExtensions[spec.name];
+ for (var prop in exts) {
+ if (!exts.hasOwnProperty(prop)) continue;
+ if (modeObj.hasOwnProperty(prop)) modeObj["_" + prop] = modeObj[prop];
+ modeObj[prop] = exts[prop];
+ }
+ }
+ modeObj.name = spec.name;
+
+ return modeObj;
+ };
+
+ CodeMirror.defineMode("null", function() {
+ return {token: function(stream) {stream.skipToEnd();}};
+ });
+ CodeMirror.defineMIME("text/plain", "null");
+
+ var modeExtensions = CodeMirror.modeExtensions = {};
+ CodeMirror.extendMode = function(mode, properties) {
+ var exts = modeExtensions.hasOwnProperty(mode) ? modeExtensions[mode] : (modeExtensions[mode] = {});
+ copyObj(properties, exts);
+ };
+
+ // EXTENSIONS
+
+ CodeMirror.defineExtension = function(name, func) {
+ CodeMirror.prototype[name] = func;
+ };
+ CodeMirror.defineDocExtension = function(name, func) {
+ Doc.prototype[name] = func;
+ };
+ CodeMirror.defineOption = option;
+
+ var initHooks = [];
+ CodeMirror.defineInitHook = function(f) {initHooks.push(f);};
+
+ var helpers = CodeMirror.helpers = {};
+ CodeMirror.registerHelper = function(type, name, value) {
+ if (!helpers.hasOwnProperty(type)) helpers[type] = CodeMirror[type] = {};
+ helpers[type][name] = value;
+ };
+
+ // UTILITIES
+
+ CodeMirror.isWordChar = isWordChar;
+
+ // MODE STATE HANDLING
+
+ // Utility functions for working with state. Exported because modes
+ // sometimes need to do this.
+ function copyState(mode, state) {
+ if (state === true) return state;
+ if (mode.copyState) return mode.copyState(state);
+ var nstate = {};
+ for (var n in state) {
+ var val = state[n];
+ if (val instanceof Array) val = val.concat([]);
+ nstate[n] = val;
+ }
+ return nstate;
+ }
+ CodeMirror.copyState = copyState;
+
+ function startState(mode, a1, a2) {
+ return mode.startState ? mode.startState(a1, a2) : true;
+ }
+ CodeMirror.startState = startState;
+
+ CodeMirror.innerMode = function(mode, state) {
+ while (mode.innerMode) {
+ var info = mode.innerMode(state);
+ if (!info || info.mode == mode) break;
+ state = info.state;
+ mode = info.mode;
+ }
+ return info || {mode: mode, state: state};
+ };
+
+ // STANDARD COMMANDS
+
+ var commands = CodeMirror.commands = {
+ selectAll: function(cm) {cm.setSelection(Pos(cm.firstLine(), 0), Pos(cm.lastLine()));},
+ killLine: function(cm) {
+ var from = cm.getCursor(true), to = cm.getCursor(false), sel = !posEq(from, to);
+ if (!sel && cm.getLine(from.line).length == from.ch)
+ cm.replaceRange("", from, Pos(from.line + 1, 0), "+delete");
+ else cm.replaceRange("", from, sel ? to : Pos(from.line), "+delete");
+ },
+ deleteLine: function(cm) {
+ var l = cm.getCursor().line;
+ cm.replaceRange("", Pos(l, 0), Pos(l), "+delete");
+ },
+ delLineLeft: function(cm) {
+ var cur = cm.getCursor();
+ cm.replaceRange("", Pos(cur.line, 0), cur, "+delete");
+ },
+ undo: function(cm) {cm.undo();},
+ redo: function(cm) {cm.redo();},
+ goDocStart: function(cm) {cm.extendSelection(Pos(cm.firstLine(), 0));},
+ goDocEnd: function(cm) {cm.extendSelection(Pos(cm.lastLine()));},
+ goLineStart: function(cm) {
+ cm.extendSelection(lineStart(cm, cm.getCursor().line));
+ },
+ goLineStartSmart: function(cm) {
+ var cur = cm.getCursor(), start = lineStart(cm, cur.line);
+ var line = cm.getLineHandle(start.line);
+ var order = getOrder(line);
+ if (!order || order[0].level == 0) {
+ var firstNonWS = Math.max(0, line.text.search(/\S/));
+ var inWS = cur.line == start.line && cur.ch <= firstNonWS && cur.ch;
+ cm.extendSelection(Pos(start.line, inWS ? 0 : firstNonWS));
+ } else cm.extendSelection(start);
+ },
+ goLineEnd: function(cm) {
+ cm.extendSelection(lineEnd(cm, cm.getCursor().line));
+ },
+ goLineRight: function(cm) {
+ var top = cm.charCoords(cm.getCursor(), "div").top + 5;
+ cm.extendSelection(cm.coordsChar({left: cm.display.lineDiv.offsetWidth + 100, top: top}, "div"));
+ },
+ goLineLeft: function(cm) {
+ var top = cm.charCoords(cm.getCursor(), "div").top + 5;
+ cm.extendSelection(cm.coordsChar({left: 0, top: top}, "div"));
+ },
+ goLineUp: function(cm) {cm.moveV(-1, "line");},
+ goLineDown: function(cm) {cm.moveV(1, "line");},
+ goPageUp: function(cm) {cm.moveV(-1, "page");},
+ goPageDown: function(cm) {cm.moveV(1, "page");},
+ goCharLeft: function(cm) {cm.moveH(-1, "char");},
+ goCharRight: function(cm) {cm.moveH(1, "char");},
+ goColumnLeft: function(cm) {cm.moveH(-1, "column");},
+ goColumnRight: function(cm) {cm.moveH(1, "column");},
+ goWordLeft: function(cm) {cm.moveH(-1, "word");},
+ goGroupRight: function(cm) {cm.moveH(1, "group");},
+ goGroupLeft: function(cm) {cm.moveH(-1, "group");},
+ goWordRight: function(cm) {cm.moveH(1, "word");},
+ delCharBefore: function(cm) {cm.deleteH(-1, "char");},
+ delCharAfter: function(cm) {cm.deleteH(1, "char");},
+ delWordBefore: function(cm) {cm.deleteH(-1, "word");},
+ delWordAfter: function(cm) {cm.deleteH(1, "word");},
+ delGroupBefore: function(cm) {cm.deleteH(-1, "group");},
+ delGroupAfter: function(cm) {cm.deleteH(1, "group");},
+ indentAuto: function(cm) {cm.indentSelection("smart");},
+ indentMore: function(cm) {cm.indentSelection("add");},
+ indentLess: function(cm) {cm.indentSelection("subtract");},
+ insertTab: function(cm) {cm.replaceSelection("\t", "end", "+input");},
+ defaultTab: function(cm) {
+ if (cm.somethingSelected()) cm.indentSelection("add");
+ else cm.replaceSelection("\t", "end", "+input");
+ },
+ transposeChars: function(cm) {
+ var cur = cm.getCursor(), line = cm.getLine(cur.line);
+ if (cur.ch > 0 && cur.ch < line.length - 1)
+ cm.replaceRange(line.charAt(cur.ch) + line.charAt(cur.ch - 1),
+ Pos(cur.line, cur.ch - 1), Pos(cur.line, cur.ch + 1));
+ },
+ newlineAndIndent: function(cm) {
+ operation(cm, function() {
+ cm.replaceSelection("\n", "end", "+input");
+ cm.indentLine(cm.getCursor().line, null, true);
+ })();
+ },
+ toggleOverwrite: function(cm) {cm.toggleOverwrite();}
+ };
+
+ // STANDARD KEYMAPS
+
+ var keyMap = CodeMirror.keyMap = {};
+ keyMap.basic = {
+ "Left": "goCharLeft", "Right": "goCharRight", "Up": "goLineUp", "Down": "goLineDown",
+ "End": "goLineEnd", "Home": "goLineStartSmart", "PageUp": "goPageUp", "PageDown": "goPageDown",
+ "Delete": "delCharAfter", "Backspace": "delCharBefore", "Tab": "defaultTab", "Shift-Tab": "indentAuto",
+ "Enter": "newlineAndIndent", "Insert": "toggleOverwrite"
+ };
+ // Note that the save and find-related commands aren't defined by
+ // default. Unknown commands are simply ignored.
+ keyMap.pcDefault = {
+ "Ctrl-A": "selectAll", "Ctrl-D": "deleteLine", "Ctrl-Z": "undo", "Shift-Ctrl-Z": "redo", "Ctrl-Y": "redo",
+ "Ctrl-Home": "goDocStart", "Alt-Up": "goDocStart", "Ctrl-End": "goDocEnd", "Ctrl-Down": "goDocEnd",
+ "Ctrl-Left": "goGroupLeft", "Ctrl-Right": "goGroupRight", "Alt-Left": "goLineStart", "Alt-Right": "goLineEnd",
+ "Ctrl-Backspace": "delGroupBefore", "Ctrl-Delete": "delGroupAfter", "Ctrl-S": "save", "Ctrl-F": "find",
+ "Ctrl-G": "findNext", "Shift-Ctrl-G": "findPrev", "Shift-Ctrl-F": "replace", "Shift-Ctrl-R": "replaceAll",
+ "Ctrl-[": "indentLess", "Ctrl-]": "indentMore",
+ fallthrough: "basic"
+ };
+ keyMap.macDefault = {
+ "Cmd-A": "selectAll", "Cmd-D": "deleteLine", "Cmd-Z": "undo", "Shift-Cmd-Z": "redo", "Cmd-Y": "redo",
+ "Cmd-Up": "goDocStart", "Cmd-End": "goDocEnd", "Cmd-Down": "goDocEnd", "Alt-Left": "goGroupLeft",
+ "Alt-Right": "goGroupRight", "Cmd-Left": "goLineStart", "Cmd-Right": "goLineEnd", "Alt-Backspace": "delGroupBefore",
+ "Ctrl-Alt-Backspace": "delGroupAfter", "Alt-Delete": "delGroupAfter", "Cmd-S": "save", "Cmd-F": "find",
+ "Cmd-G": "findNext", "Shift-Cmd-G": "findPrev", "Cmd-Alt-F": "replace", "Shift-Cmd-Alt-F": "replaceAll",
+ "Cmd-[": "indentLess", "Cmd-]": "indentMore", "Cmd-Backspace": "delLineLeft",
+ fallthrough: ["basic", "emacsy"]
+ };
+ keyMap["default"] = mac ? keyMap.macDefault : keyMap.pcDefault;
+ keyMap.emacsy = {
+ "Ctrl-F": "goCharRight", "Ctrl-B": "goCharLeft", "Ctrl-P": "goLineUp", "Ctrl-N": "goLineDown",
+ "Alt-F": "goWordRight", "Alt-B": "goWordLeft", "Ctrl-A": "goLineStart", "Ctrl-E": "goLineEnd",
+ "Ctrl-V": "goPageDown", "Shift-Ctrl-V": "goPageUp", "Ctrl-D": "delCharAfter", "Ctrl-H": "delCharBefore",
+ "Alt-D": "delWordAfter", "Alt-Backspace": "delWordBefore", "Ctrl-K": "killLine", "Ctrl-T": "transposeChars"
+ };
+
+ // KEYMAP DISPATCH
+
+ function getKeyMap(val) {
+ if (typeof val == "string") return keyMap[val];
+ else return val;
+ }
+
+ function lookupKey(name, maps, handle) {
+ function lookup(map) {
+ map = getKeyMap(map);
+ var found = map[name];
+ if (found === false) return "stop";
+ if (found != null && handle(found)) return true;
+ if (map.nofallthrough) return "stop";
+
+ var fallthrough = map.fallthrough;
+ if (fallthrough == null) return false;
+ if (Object.prototype.toString.call(fallthrough) != "[object Array]")
+ return lookup(fallthrough);
+ for (var i = 0, e = fallthrough.length; i < e; ++i) {
+ var done = lookup(fallthrough[i]);
+ if (done) return done;
+ }
+ return false;
+ }
+
+ for (var i = 0; i < maps.length; ++i) {
+ var done = lookup(maps[i]);
+ if (done) return done != "stop";
+ }
+ }
+ function isModifierKey(event) {
+ var name = keyNames[event.keyCode];
+ return name == "Ctrl" || name == "Alt" || name == "Shift" || name == "Mod";
+ }
+ function keyName(event, noShift) {
+ if (opera && event.keyCode == 34 && event["char"]) return false;
+ var name = keyNames[event.keyCode];
+ if (name == null || event.altGraphKey) return false;
+ if (event.altKey) name = "Alt-" + name;
+ if (flipCtrlCmd ? event.metaKey : event.ctrlKey) name = "Ctrl-" + name;
+ if (flipCtrlCmd ? event.ctrlKey : event.metaKey) name = "Cmd-" + name;
+ if (!noShift && event.shiftKey) name = "Shift-" + name;
+ return name;
+ }
+ CodeMirror.lookupKey = lookupKey;
+ CodeMirror.isModifierKey = isModifierKey;
+ CodeMirror.keyName = keyName;
+
+ // FROMTEXTAREA
+
+ CodeMirror.fromTextArea = function(textarea, options) {
+ if (!options) options = {};
+ options.value = textarea.value;
+ if (!options.tabindex && textarea.tabindex)
+ options.tabindex = textarea.tabindex;
+ if (!options.placeholder && textarea.placeholder)
+ options.placeholder = textarea.placeholder;
+ // Set autofocus to true if this textarea is focused, or if it has
+ // autofocus and no other element is focused.
+ if (options.autofocus == null) {
+ var hasFocus = document.body;
+ // doc.activeElement occasionally throws on IE
+ try { hasFocus = document.activeElement; } catch(e) {}
+ options.autofocus = hasFocus == textarea ||
+ textarea.getAttribute("autofocus") != null && hasFocus == document.body;
+ }
+
+ function save() {textarea.value = cm.getValue();}
+ if (textarea.form) {
+ on(textarea.form, "submit", save);
+ // Deplorable hack to make the submit method do the right thing.
+ if (!options.leaveSubmitMethodAlone) {
+ var form = textarea.form, realSubmit = form.submit;
+ try {
+ var wrappedSubmit = form.submit = function() {
+ save();
+ form.submit = realSubmit;
+ form.submit();
+ form.submit = wrappedSubmit;
+ };
+ } catch(e) {}
+ }
+ }
+
+ textarea.style.display = "none";
+ var cm = CodeMirror(function(node) {
+ textarea.parentNode.insertBefore(node, textarea.nextSibling);
+ }, options);
+ cm.save = save;
+ cm.getTextArea = function() { return textarea; };
+ cm.toTextArea = function() {
+ save();
+ textarea.parentNode.removeChild(cm.getWrapperElement());
+ textarea.style.display = "";
+ if (textarea.form) {
+ off(textarea.form, "submit", save);
+ if (typeof textarea.form.submit == "function")
+ textarea.form.submit = realSubmit;
+ }
+ };
+ return cm;
+ };
+
+ // STRING STREAM
+
+ // Fed to the mode parsers, provides helper functions to make
+ // parsers more succinct.
+
+ // The character stream used by a mode's parser.
+ function StringStream(string, tabSize) {
+ this.pos = this.start = 0;
+ this.string = string;
+ this.tabSize = tabSize || 8;
+ this.lastColumnPos = this.lastColumnValue = 0;
+ }
+
+ StringStream.prototype = {
+ eol: function() {return this.pos >= this.string.length;},
+ sol: function() {return this.pos == 0;},
+ peek: function() {return this.string.charAt(this.pos) || undefined;},
+ next: function() {
+ if (this.pos < this.string.length)
+ return this.string.charAt(this.pos++);
+ },
+ eat: function(match) {
+ var ch = this.string.charAt(this.pos);
+ if (typeof match == "string") var ok = ch == match;
+ else var ok = ch && (match.test ? match.test(ch) : match(ch));
+ if (ok) {++this.pos; return ch;}
+ },
+ eatWhile: function(match) {
+ var start = this.pos;
+ while (this.eat(match)){}
+ return this.pos > start;
+ },
+ eatSpace: function() {
+ var start = this.pos;
+ while (/[\s\u00a0]/.test(this.string.charAt(this.pos))) ++this.pos;
+ return this.pos > start;
+ },
+ skipToEnd: function() {this.pos = this.string.length;},
+ skipTo: function(ch) {
+ var found = this.string.indexOf(ch, this.pos);
+ if (found > -1) {this.pos = found; return true;}
+ },
+ backUp: function(n) {this.pos -= n;},
+ column: function() {
+ if (this.lastColumnPos < this.start) {
+ this.lastColumnValue = countColumn(this.string, this.start, this.tabSize, this.lastColumnPos, this.lastColumnValue);
+ this.lastColumnPos = this.start;
+ }
+ return this.lastColumnValue;
+ },
+ indentation: function() {return countColumn(this.string, null, this.tabSize);},
+ match: function(pattern, consume, caseInsensitive) {
+ if (typeof pattern == "string") {
+ var cased = function(str) {return caseInsensitive ? str.toLowerCase() : str;};
+ var substr = this.string.substr(this.pos, pattern.length);
+ if (cased(substr) == cased(pattern)) {
+ if (consume !== false) this.pos += pattern.length;
+ return true;
+ }
+ } else {
+ var match = this.string.slice(this.pos).match(pattern);
+ if (match && match.index > 0) return null;
+ if (match && consume !== false) this.pos += match[0].length;
+ return match;
+ }
+ },
+ current: function(){return this.string.slice(this.start, this.pos);}
+ };
+ CodeMirror.StringStream = StringStream;
+
+ // TEXTMARKERS
+
+ function TextMarker(doc, type) {
+ this.lines = [];
+ this.type = type;
+ this.doc = doc;
+ }
+ CodeMirror.TextMarker = TextMarker;
+ eventMixin(TextMarker);
+
+ TextMarker.prototype.clear = function() {
+ if (this.explicitlyCleared) return;
+ var cm = this.doc.cm, withOp = cm && !cm.curOp;
+ if (withOp) startOperation(cm);
+ if (hasHandler(this, "clear")) {
+ var found = this.find();
+ if (found) signalLater(this, "clear", found.from, found.to);
+ }
+ var min = null, max = null;
+ for (var i = 0; i < this.lines.length; ++i) {
+ var line = this.lines[i];
+ var span = getMarkedSpanFor(line.markedSpans, this);
+ if (span.to != null) max = lineNo(line);
+ line.markedSpans = removeMarkedSpan(line.markedSpans, span);
+ if (span.from != null)
+ min = lineNo(line);
+ else if (this.collapsed && !lineIsHidden(this.doc, line) && cm)
+ updateLineHeight(line, textHeight(cm.display));
+ }
+ if (cm && this.collapsed && !cm.options.lineWrapping) for (var i = 0; i < this.lines.length; ++i) {
+ var visual = visualLine(cm.doc, this.lines[i]), len = lineLength(cm.doc, visual);
+ if (len > cm.display.maxLineLength) {
+ cm.display.maxLine = visual;
+ cm.display.maxLineLength = len;
+ cm.display.maxLineChanged = true;
+ }
+ }
+
+ if (min != null && cm) regChange(cm, min, max + 1);
+ this.lines.length = 0;
+ this.explicitlyCleared = true;
+ if (this.atomic && this.doc.cantEdit) {
+ this.doc.cantEdit = false;
+ if (cm) reCheckSelection(cm);
+ }
+ if (withOp) endOperation(cm);
+ };
+
+ TextMarker.prototype.find = function() {
+ var from, to;
+ for (var i = 0; i < this.lines.length; ++i) {
+ var line = this.lines[i];
+ var span = getMarkedSpanFor(line.markedSpans, this);
+ if (span.from != null || span.to != null) {
+ var found = lineNo(line);
+ if (span.from != null) from = Pos(found, span.from);
+ if (span.to != null) to = Pos(found, span.to);
+ }
+ }
+ if (this.type == "bookmark") return from;
+ return from && {from: from, to: to};
+ };
+
+ TextMarker.prototype.changed = function() {
+ var pos = this.find(), cm = this.doc.cm;
+ if (!pos || !cm) return;
+ var line = getLine(this.doc, pos.from.line);
+ clearCachedMeasurement(cm, line);
+ if (pos.from.line >= cm.display.showingFrom && pos.from.line < cm.display.showingTo) {
+ for (var node = cm.display.lineDiv.firstChild; node; node = node.nextSibling) if (node.lineObj == line) {
+ if (node.offsetHeight != line.height) updateLineHeight(line, node.offsetHeight);
+ break;
+ }
+ runInOp(cm, function() {
+ cm.curOp.selectionChanged = cm.curOp.forceUpdate = cm.curOp.updateMaxLine = true;
+ });
+ }
+ };
+
+ TextMarker.prototype.attachLine = function(line) {
+ if (!this.lines.length && this.doc.cm) {
+ var op = this.doc.cm.curOp;
+ if (!op.maybeHiddenMarkers || indexOf(op.maybeHiddenMarkers, this) == -1)
+ (op.maybeUnhiddenMarkers || (op.maybeUnhiddenMarkers = [])).push(this);
+ }
+ this.lines.push(line);
+ };
+ TextMarker.prototype.detachLine = function(line) {
+ this.lines.splice(indexOf(this.lines, line), 1);
+ if (!this.lines.length && this.doc.cm) {
+ var op = this.doc.cm.curOp;
+ (op.maybeHiddenMarkers || (op.maybeHiddenMarkers = [])).push(this);
+ }
+ };
+
+ function markText(doc, from, to, options, type) {
+ if (options && options.shared) return markTextShared(doc, from, to, options, type);
+ if (doc.cm && !doc.cm.curOp) return operation(doc.cm, markText)(doc, from, to, options, type);
+
+ var marker = new TextMarker(doc, type);
+ if (type == "range" && !posLess(from, to)) return marker;
+ if (options) copyObj(options, marker);
+ if (marker.replacedWith) {
+ marker.collapsed = true;
+ marker.replacedWith = elt("span", [marker.replacedWith], "CodeMirror-widget");
+ if (!options.handleMouseEvents) marker.replacedWith.ignoreEvents = true;
+ }
+ if (marker.collapsed) sawCollapsedSpans = true;
+
+ if (marker.addToHistory)
+ addToHistory(doc, {from: from, to: to, origin: "markText"},
+ {head: doc.sel.head, anchor: doc.sel.anchor}, NaN);
+
+ var curLine = from.line, size = 0, collapsedAtStart, collapsedAtEnd, cm = doc.cm, updateMaxLine;
+ doc.iter(curLine, to.line + 1, function(line) {
+ if (cm && marker.collapsed && !cm.options.lineWrapping && visualLine(doc, line) == cm.display.maxLine)
+ updateMaxLine = true;
+ var span = {from: null, to: null, marker: marker};
+ size += line.text.length;
+ if (curLine == from.line) {span.from = from.ch; size -= from.ch;}
+ if (curLine == to.line) {span.to = to.ch; size -= line.text.length - to.ch;}
+ if (marker.collapsed) {
+ if (curLine == to.line) collapsedAtEnd = collapsedSpanAt(line, to.ch);
+ if (curLine == from.line) collapsedAtStart = collapsedSpanAt(line, from.ch);
+ else updateLineHeight(line, 0);
+ }
+ addMarkedSpan(line, span);
+ ++curLine;
+ });
+ if (marker.collapsed) doc.iter(from.line, to.line + 1, function(line) {
+ if (lineIsHidden(doc, line)) updateLineHeight(line, 0);
+ });
+
+ if (marker.clearOnEnter) on(marker, "beforeCursorEnter", function() { marker.clear(); });
+
+ if (marker.readOnly) {
+ sawReadOnlySpans = true;
+ if (doc.history.done.length || doc.history.undone.length)
+ doc.clearHistory();
+ }
+ if (marker.collapsed) {
+ if (collapsedAtStart != collapsedAtEnd)
+ throw new Error("Inserting collapsed marker overlapping an existing one");
+ marker.size = size;
+ marker.atomic = true;
+ }
+ if (cm) {
+ if (updateMaxLine) cm.curOp.updateMaxLine = true;
+ if (marker.className || marker.title || marker.startStyle || marker.endStyle || marker.collapsed)
+ regChange(cm, from.line, to.line + 1);
+ if (marker.atomic) reCheckSelection(cm);
+ }
+ return marker;
+ }
+
+ // SHARED TEXTMARKERS
+
+ function SharedTextMarker(markers, primary) {
+ this.markers = markers;
+ this.primary = primary;
+ for (var i = 0, me = this; i < markers.length; ++i) {
+ markers[i].parent = this;
+ on(markers[i], "clear", function(){me.clear();});
+ }
+ }
+ CodeMirror.SharedTextMarker = SharedTextMarker;
+ eventMixin(SharedTextMarker);
+
+ SharedTextMarker.prototype.clear = function() {
+ if (this.explicitlyCleared) return;
+ this.explicitlyCleared = true;
+ for (var i = 0; i < this.markers.length; ++i)
+ this.markers[i].clear();
+ signalLater(this, "clear");
+ };
+ SharedTextMarker.prototype.find = function() {
+ return this.primary.find();
+ };
+
+ function markTextShared(doc, from, to, options, type) {
+ options = copyObj(options);
+ options.shared = false;
+ var markers = [markText(doc, from, to, options, type)], primary = markers[0];
+ var widget = options.replacedWith;
+ linkedDocs(doc, function(doc) {
+ if (widget) options.replacedWith = widget.cloneNode(true);
+ markers.push(markText(doc, clipPos(doc, from), clipPos(doc, to), options, type));
+ for (var i = 0; i < doc.linked.length; ++i)
+ if (doc.linked[i].isParent) return;
+ primary = lst(markers);
+ });
+ return new SharedTextMarker(markers, primary);
+ }
+
+ // TEXTMARKER SPANS
+
+ function getMarkedSpanFor(spans, marker) {
+ if (spans) for (var i = 0; i < spans.length; ++i) {
+ var span = spans[i];
+ if (span.marker == marker) return span;
+ }
+ }
+ function removeMarkedSpan(spans, span) {
+ for (var r, i = 0; i < spans.length; ++i)
+ if (spans[i] != span) (r || (r = [])).push(spans[i]);
+ return r;
+ }
+ function addMarkedSpan(line, span) {
+ line.markedSpans = line.markedSpans ? line.markedSpans.concat([span]) : [span];
+ span.marker.attachLine(line);
+ }
+
+ function markedSpansBefore(old, startCh, isInsert) {
+ if (old) for (var i = 0, nw; i < old.length; ++i) {
+ var span = old[i], marker = span.marker;
+ var startsBefore = span.from == null || (marker.inclusiveLeft ? span.from <= startCh : span.from < startCh);
+ if (startsBefore || marker.type == "bookmark" && span.from == startCh && (!isInsert || !span.marker.insertLeft)) {
+ var endsAfter = span.to == null || (marker.inclusiveRight ? span.to >= startCh : span.to > startCh);
+ (nw || (nw = [])).push({from: span.from,
+ to: endsAfter ? null : span.to,
+ marker: marker});
+ }
+ }
+ return nw;
+ }
+
+ function markedSpansAfter(old, endCh, isInsert) {
+ if (old) for (var i = 0, nw; i < old.length; ++i) {
+ var span = old[i], marker = span.marker;
+ var endsAfter = span.to == null || (marker.inclusiveRight ? span.to >= endCh : span.to > endCh);
+ if (endsAfter || marker.type == "bookmark" && span.from == endCh && (!isInsert || span.marker.insertLeft)) {
+ var startsBefore = span.from == null || (marker.inclusiveLeft ? span.from <= endCh : span.from < endCh);
+ (nw || (nw = [])).push({from: startsBefore ? null : span.from - endCh,
+ to: span.to == null ? null : span.to - endCh,
+ marker: marker});
+ }
+ }
+ return nw;
+ }
+
+ function stretchSpansOverChange(doc, change) {
+ var oldFirst = isLine(doc, change.from.line) && getLine(doc, change.from.line).markedSpans;
+ var oldLast = isLine(doc, change.to.line) && getLine(doc, change.to.line).markedSpans;
+ if (!oldFirst && !oldLast) return null;
+
+ var startCh = change.from.ch, endCh = change.to.ch, isInsert = posEq(change.from, change.to);
+ // Get the spans that 'stick out' on both sides
+ var first = markedSpansBefore(oldFirst, startCh, isInsert);
+ var last = markedSpansAfter(oldLast, endCh, isInsert);
+
+ // Next, merge those two ends
+ var sameLine = change.text.length == 1, offset = lst(change.text).length + (sameLine ? startCh : 0);
+ if (first) {
+ // Fix up .to properties of first
+ for (var i = 0; i < first.length; ++i) {
+ var span = first[i];
+ if (span.to == null) {
+ var found = getMarkedSpanFor(last, span.marker);
+ if (!found) span.to = startCh;
+ else if (sameLine) span.to = found.to == null ? null : found.to + offset;
+ }
+ }
+ }
+ if (last) {
+ // Fix up .from in last (or move them into first in case of sameLine)
+ for (var i = 0; i < last.length; ++i) {
+ var span = last[i];
+ if (span.to != null) span.to += offset;
+ if (span.from == null) {
+ var found = getMarkedSpanFor(first, span.marker);
+ if (!found) {
+ span.from = offset;
+ if (sameLine) (first || (first = [])).push(span);
+ }
+ } else {
+ span.from += offset;
+ if (sameLine) (first || (first = [])).push(span);
+ }
+ }
+ }
+ if (sameLine && first) {
+ // Make sure we didn't create any zero-length spans
+ for (var i = 0; i < first.length; ++i)
+ if (first[i].from != null && first[i].from == first[i].to && first[i].marker.type != "bookmark")
+ first.splice(i--, 1);
+ if (!first.length) first = null;
+ }
+
+ var newMarkers = [first];
+ if (!sameLine) {
+ // Fill gap with whole-line-spans
+ var gap = change.text.length - 2, gapMarkers;
+ if (gap > 0 && first)
+ for (var i = 0; i < first.length; ++i)
+ if (first[i].to == null)
+ (gapMarkers || (gapMarkers = [])).push({from: null, to: null, marker: first[i].marker});
+ for (var i = 0; i < gap; ++i)
+ newMarkers.push(gapMarkers);
+ newMarkers.push(last);
+ }
+ return newMarkers;
+ }
+
+ function mergeOldSpans(doc, change) {
+ var old = getOldSpans(doc, change);
+ var stretched = stretchSpansOverChange(doc, change);
+ if (!old) return stretched;
+ if (!stretched) return old;
+
+ for (var i = 0; i < old.length; ++i) {
+ var oldCur = old[i], stretchCur = stretched[i];
+ if (oldCur && stretchCur) {
+ spans: for (var j = 0; j < stretchCur.length; ++j) {
+ var span = stretchCur[j];
+ for (var k = 0; k < oldCur.length; ++k)
+ if (oldCur[k].marker == span.marker) continue spans;
+ oldCur.push(span);
+ }
+ } else if (stretchCur) {
+ old[i] = stretchCur;
+ }
+ }
+ return old;
+ }
+
+ function removeReadOnlyRanges(doc, from, to) {
+ var markers = null;
+ doc.iter(from.line, to.line + 1, function(line) {
+ if (line.markedSpans) for (var i = 0; i < line.markedSpans.length; ++i) {
+ var mark = line.markedSpans[i].marker;
+ if (mark.readOnly && (!markers || indexOf(markers, mark) == -1))
+ (markers || (markers = [])).push(mark);
+ }
+ });
+ if (!markers) return null;
+ var parts = [{from: from, to: to}];
+ for (var i = 0; i < markers.length; ++i) {
+ var mk = markers[i], m = mk.find();
+ for (var j = 0; j < parts.length; ++j) {
+ var p = parts[j];
+ if (posLess(p.to, m.from) || posLess(m.to, p.from)) continue;
+ var newParts = [j, 1];
+ if (posLess(p.from, m.from) || !mk.inclusiveLeft && posEq(p.from, m.from))
+ newParts.push({from: p.from, to: m.from});
+ if (posLess(m.to, p.to) || !mk.inclusiveRight && posEq(p.to, m.to))
+ newParts.push({from: m.to, to: p.to});
+ parts.splice.apply(parts, newParts);
+ j += newParts.length - 1;
+ }
+ }
+ return parts;
+ }
+
+ function collapsedSpanAt(line, ch) {
+ var sps = sawCollapsedSpans && line.markedSpans, found;
+ if (sps) for (var sp, i = 0; i < sps.length; ++i) {
+ sp = sps[i];
+ if (!sp.marker.collapsed) continue;
+ if ((sp.from == null || sp.from < ch) &&
+ (sp.to == null || sp.to > ch) &&
+ (!found || found.width < sp.marker.width))
+ found = sp.marker;
+ }
+ return found;
+ }
+ function collapsedSpanAtStart(line) { return collapsedSpanAt(line, -1); }
+ function collapsedSpanAtEnd(line) { return collapsedSpanAt(line, line.text.length + 1); }
+
+ function visualLine(doc, line) {
+ var merged;
+ while (merged = collapsedSpanAtStart(line))
+ line = getLine(doc, merged.find().from.line);
+ return line;
+ }
+
+ function lineIsHidden(doc, line) {
+ var sps = sawCollapsedSpans && line.markedSpans;
+ if (sps) for (var sp, i = 0; i < sps.length; ++i) {
+ sp = sps[i];
+ if (!sp.marker.collapsed) continue;
+ if (sp.from == null) return true;
+ if (sp.marker.replacedWith) continue;
+ if (sp.from == 0 && sp.marker.inclusiveLeft && lineIsHiddenInner(doc, line, sp))
+ return true;
+ }
+ }
+ function lineIsHiddenInner(doc, line, span) {
+ if (span.to == null) {
+ var end = span.marker.find().to, endLine = getLine(doc, end.line);
+ return lineIsHiddenInner(doc, endLine, getMarkedSpanFor(endLine.markedSpans, span.marker));
+ }
+ if (span.marker.inclusiveRight && span.to == line.text.length)
+ return true;
+ for (var sp, i = 0; i < line.markedSpans.length; ++i) {
+ sp = line.markedSpans[i];
+ if (sp.marker.collapsed && !sp.marker.replacedWith && sp.from == span.to &&
+ (sp.marker.inclusiveLeft || span.marker.inclusiveRight) &&
+ lineIsHiddenInner(doc, line, sp)) return true;
+ }
+ }
+
+ function detachMarkedSpans(line) {
+ var spans = line.markedSpans;
+ if (!spans) return;
+ for (var i = 0; i < spans.length; ++i)
+ spans[i].marker.detachLine(line);
+ line.markedSpans = null;
+ }
+
+ function attachMarkedSpans(line, spans) {
+ if (!spans) return;
+ for (var i = 0; i < spans.length; ++i)
+ spans[i].marker.attachLine(line);
+ line.markedSpans = spans;
+ }
+
+ // LINE WIDGETS
+
+ var LineWidget = CodeMirror.LineWidget = function(cm, node, options) {
+ if (options) for (var opt in options) if (options.hasOwnProperty(opt))
+ this[opt] = options[opt];
+ this.cm = cm;
+ this.node = node;
+ };
+ eventMixin(LineWidget);
+ function widgetOperation(f) {
+ return function() {
+ var withOp = !this.cm.curOp;
+ if (withOp) startOperation(this.cm);
+ try {var result = f.apply(this, arguments);}
+ finally {if (withOp) endOperation(this.cm);}
+ return result;
+ };
+ }
+ LineWidget.prototype.clear = widgetOperation(function() {
+ var ws = this.line.widgets, no = lineNo(this.line);
+ if (no == null || !ws) return;
+ for (var i = 0; i < ws.length; ++i) if (ws[i] == this) ws.splice(i--, 1);
+ if (!ws.length) this.line.widgets = null;
+ var aboveVisible = heightAtLine(this.cm, this.line) < this.cm.doc.scrollTop;
+ updateLineHeight(this.line, Math.max(0, this.line.height - widgetHeight(this)));
+ if (aboveVisible) addToScrollPos(this.cm, 0, -this.height);
+ regChange(this.cm, no, no + 1);
+ });
+ LineWidget.prototype.changed = widgetOperation(function() {
+ var oldH = this.height;
+ this.height = null;
+ var diff = widgetHeight(this) - oldH;
+ if (!diff) return;
+ updateLineHeight(this.line, this.line.height + diff);
+ var no = lineNo(this.line);
+ regChange(this.cm, no, no + 1);
+ });
+
+ function widgetHeight(widget) {
+ if (widget.height != null) return widget.height;
+ if (!widget.node.parentNode || widget.node.parentNode.nodeType != 1)
+ removeChildrenAndAdd(widget.cm.display.measure, elt("div", [widget.node], null, "position: relative"));
+ return widget.height = widget.node.offsetHeight;
+ }
+
+ function addLineWidget(cm, handle, node, options) {
+ var widget = new LineWidget(cm, node, options);
+ if (widget.noHScroll) cm.display.alignWidgets = true;
+ changeLine(cm, handle, function(line) {
+ var widgets = line.widgets || (line.widgets = []);
+ if (widget.insertAt == null) widgets.push(widget);
+ else widgets.splice(Math.min(widgets.length - 1, Math.max(0, widget.insertAt)), 0, widget);
+ widget.line = line;
+ if (!lineIsHidden(cm.doc, line) || widget.showIfHidden) {
+ var aboveVisible = heightAtLine(cm, line) < cm.doc.scrollTop;
+ updateLineHeight(line, line.height + widgetHeight(widget));
+ if (aboveVisible) addToScrollPos(cm, 0, widget.height);
+ }
+ return true;
+ });
+ return widget;
+ }
+
+ // LINE DATA STRUCTURE
+
+ // Line objects. These hold state related to a line, including
+ // highlighting info (the styles array).
+ var Line = CodeMirror.Line = function(text, markedSpans, estimateHeight) {
+ this.text = text;
+ attachMarkedSpans(this, markedSpans);
+ this.height = estimateHeight ? estimateHeight(this) : 1;
+ };
+ eventMixin(Line);
+
+ function updateLine(line, text, markedSpans, estimateHeight) {
+ line.text = text;
+ if (line.stateAfter) line.stateAfter = null;
+ if (line.styles) line.styles = null;
+ if (line.order != null) line.order = null;
+ detachMarkedSpans(line);
+ attachMarkedSpans(line, markedSpans);
+ var estHeight = estimateHeight ? estimateHeight(line) : 1;
+ if (estHeight != line.height) updateLineHeight(line, estHeight);
+ }
+
+ function cleanUpLine(line) {
+ line.parent = null;
+ detachMarkedSpans(line);
+ }
+
+ // Run the given mode's parser over a line, update the styles
+ // array, which contains alternating fragments of text and CSS
+ // classes.
+ function runMode(cm, text, mode, state, f) {
+ var flattenSpans = mode.flattenSpans;
+ if (flattenSpans == null) flattenSpans = cm.options.flattenSpans;
+ var curStart = 0, curStyle = null;
+ var stream = new StringStream(text, cm.options.tabSize), style;
+ if (text == "" && mode.blankLine) mode.blankLine(state);
+ while (!stream.eol()) {
+ if (stream.pos > cm.options.maxHighlightLength) {
+ flattenSpans = false;
+ // Webkit seems to refuse to render text nodes longer than 57444 characters
+ stream.pos = Math.min(text.length, stream.start + 50000);
+ style = null;
+ } else {
+ style = mode.token(stream, state);
+ }
+ if (!flattenSpans || curStyle != style) {
+ if (curStart < stream.start) f(stream.start, curStyle);
+ curStart = stream.start; curStyle = style;
+ }
+ stream.start = stream.pos;
+ }
+ if (curStart < stream.pos) f(stream.pos, curStyle);
+ }
+
+ function highlightLine(cm, line, state) {
+ // A styles array always starts with a number identifying the
+ // mode/overlays that it is based on (for easy invalidation).
+ var st = [cm.state.modeGen];
+ // Compute the base array of styles
+ runMode(cm, line.text, cm.doc.mode, state, function(end, style) {st.push(end, style);});
+
+ // Run overlays, adjust style array.
+ for (var o = 0; o < cm.state.overlays.length; ++o) {
+ var overlay = cm.state.overlays[o], i = 1, at = 0;
+ runMode(cm, line.text, overlay.mode, true, function(end, style) {
+ var start = i;
+ // Ensure there's a token end at the current position, and that i points at it
+ while (at < end) {
+ var i_end = st[i];
+ if (i_end > end)
+ st.splice(i, 1, end, st[i+1], i_end);
+ i += 2;
+ at = Math.min(end, i_end);
+ }
+ if (!style) return;
+ if (overlay.opaque) {
+ st.splice(start, i - start, end, style);
+ i = start + 2;
+ } else {
+ for (; start < i; start += 2) {
+ var cur = st[start+1];
+ st[start+1] = cur ? cur + " " + style : style;
+ }
+ }
+ });
+ }
+
+ return st;
+ }
+
+ function getLineStyles(cm, line) {
+ if (!line.styles || line.styles[0] != cm.state.modeGen)
+ line.styles = highlightLine(cm, line, line.stateAfter = getStateBefore(cm, lineNo(line)));
+ return line.styles;
+ }
+
+ // Lightweight form of highlight -- proceed over this line and
+ // update state, but don't save a style array.
+ function processLine(cm, line, state) {
+ var mode = cm.doc.mode;
+ var stream = new StringStream(line.text, cm.options.tabSize);
+ if (line.text == "" && mode.blankLine) mode.blankLine(state);
+ while (!stream.eol() && stream.pos <= cm.options.maxHighlightLength) {
+ mode.token(stream, state);
+ stream.start = stream.pos;
+ }
+ }
+
+ var styleToClassCache = {};
+ function styleToClass(style) {
+ if (!style) return null;
+ return styleToClassCache[style] ||
+ (styleToClassCache[style] = "cm-" + style.replace(/ +/g, " cm-"));
+ }
+
+ function lineContent(cm, realLine, measure, copyWidgets) {
+ var merged, line = realLine, empty = true;
+ while (merged = collapsedSpanAtStart(line))
+ line = getLine(cm.doc, merged.find().from.line);
+
+ var builder = {pre: elt("pre"), col: 0, pos: 0,
+ measure: null, measuredSomething: false, cm: cm,
+ copyWidgets: copyWidgets};
+ if (line.textClass) builder.pre.className = line.textClass;
+
+ do {
+ if (line.text) empty = false;
+ builder.measure = line == realLine && measure;
+ builder.pos = 0;
+ builder.addToken = builder.measure ? buildTokenMeasure : buildToken;
+ if ((ie || webkit) && cm.getOption("lineWrapping"))
+ builder.addToken = buildTokenSplitSpaces(builder.addToken);
+ var next = insertLineContent(line, builder, getLineStyles(cm, line));
+ if (measure && line == realLine && !builder.measuredSomething) {
+ measure[0] = builder.pre.appendChild(zeroWidthElement(cm.display.measure));
+ builder.measuredSomething = true;
+ }
+ if (next) line = getLine(cm.doc, next.to.line);
+ } while (next);
+
+ if (measure && !builder.measuredSomething && !measure[0])
+ measure[0] = builder.pre.appendChild(empty ? elt("span", "\u00a0") : zeroWidthElement(cm.display.measure));
+ if (!builder.pre.firstChild && !lineIsHidden(cm.doc, realLine))
+ builder.pre.appendChild(document.createTextNode("\u00a0"));
+
+ var order;
+ // Work around problem with the reported dimensions of single-char
+ // direction spans on IE (issue #1129). See also the comment in
+ // cursorCoords.
+ if (measure && ie && (order = getOrder(line))) {
+ var l = order.length - 1;
+ if (order[l].from == order[l].to) --l;
+ var last = order[l], prev = order[l - 1];
+ if (last.from + 1 == last.to && prev && last.level < prev.level) {
+ var span = measure[builder.pos - 1];
+ if (span) span.parentNode.insertBefore(span.measureRight = zeroWidthElement(cm.display.measure),
+ span.nextSibling);
+ }
+ }
+
+ signal(cm, "renderLine", cm, realLine, builder.pre);
+ return builder.pre;
+ }
+
+ var tokenSpecialChars = /[\t\u0000-\u0019\u00ad\u200b\u2028\u2029\uFEFF]/g;
+ function buildToken(builder, text, style, startStyle, endStyle, title) {
+ if (!text) return;
+ if (!tokenSpecialChars.test(text)) {
+ builder.col += text.length;
+ var content = document.createTextNode(text);
+ } else {
+ var content = document.createDocumentFragment(), pos = 0;
+ while (true) {
+ tokenSpecialChars.lastIndex = pos;
+ var m = tokenSpecialChars.exec(text);
+ var skipped = m ? m.index - pos : text.length - pos;
+ if (skipped) {
+ content.appendChild(document.createTextNode(text.slice(pos, pos + skipped)));
+ builder.col += skipped;
+ }
+ if (!m) break;
+ pos += skipped + 1;
+ if (m[0] == "\t") {
+ var tabSize = builder.cm.options.tabSize, tabWidth = tabSize - builder.col % tabSize;
+ content.appendChild(elt("span", spaceStr(tabWidth), "cm-tab"));
+ builder.col += tabWidth;
+ } else {
+ var token = elt("span", "\u2022", "cm-invalidchar");
+ token.title = "\\u" + m[0].charCodeAt(0).toString(16);
+ content.appendChild(token);
+ builder.col += 1;
+ }
+ }
+ }
+ if (style || startStyle || endStyle || builder.measure) {
+ var fullStyle = style || "";
+ if (startStyle) fullStyle += startStyle;
+ if (endStyle) fullStyle += endStyle;
+ var token = elt("span", [content], fullStyle);
+ if (title) token.title = title;
+ return builder.pre.appendChild(token);
+ }
+ builder.pre.appendChild(content);
+ }
+
+ function buildTokenMeasure(builder, text, style, startStyle, endStyle) {
+ var wrapping = builder.cm.options.lineWrapping;
+ for (var i = 0; i < text.length; ++i) {
+ var ch = text.charAt(i), start = i == 0;
+ if (ch >= "\ud800" && ch < "\udbff" && i < text.length - 1) {
+ ch = text.slice(i, i + 2);
+ ++i;
+ } else if (i && wrapping && spanAffectsWrapping(text, i)) {
+ builder.pre.appendChild(elt("wbr"));
+ }
+ var old = builder.measure[builder.pos];
+ var span = builder.measure[builder.pos] =
+ buildToken(builder, ch, style,
+ start && startStyle, i == text.length - 1 && endStyle);
+ if (old) span.leftSide = old.leftSide || old;
+ // In IE single-space nodes wrap differently than spaces
+ // embedded in larger text nodes, except when set to
+ // white-space: normal (issue #1268).
+ if (ie && wrapping && ch == " " && i && !/\s/.test(text.charAt(i - 1)) &&
+ i < text.length - 1 && !/\s/.test(text.charAt(i + 1)))
+ span.style.whiteSpace = "normal";
+ builder.pos += ch.length;
+ }
+ if (text.length) builder.measuredSomething = true;
+ }
+
+ function buildTokenSplitSpaces(inner) {
+ function split(old) {
+ var out = " ";
+ for (var i = 0; i < old.length - 2; ++i) out += i % 2 ? " " : "\u00a0";
+ out += " ";
+ return out;
+ }
+ return function(builder, text, style, startStyle, endStyle, title) {
+ return inner(builder, text.replace(/ {3,}/, split), style, startStyle, endStyle, title);
+ };
+ }
+
+ function buildCollapsedSpan(builder, size, marker, ignoreWidget) {
+ var widget = !ignoreWidget && marker.replacedWith;
+ if (widget) {
+ if (builder.copyWidgets) widget = widget.cloneNode(true);
+ builder.pre.appendChild(widget);
+ if (builder.measure) {
+ if (size) {
+ builder.measure[builder.pos] = widget;
+ } else {
+ var elt = builder.measure[builder.pos] = zeroWidthElement(builder.cm.display.measure);
+ if (marker.type != "bookmark" || marker.insertLeft)
+ builder.pre.insertBefore(elt, widget);
+ else
+ builder.pre.appendChild(elt);
+ }
+ builder.measuredSomething = true;
+ }
+ }
+ builder.pos += size;
+ }
+
+ // Outputs a number of spans to make up a line, taking highlighting
+ // and marked text into account.
+ function insertLineContent(line, builder, styles) {
+ var spans = line.markedSpans, allText = line.text, at = 0;
+ if (!spans) {
+ for (var i = 1; i < styles.length; i+=2)
+ builder.addToken(builder, allText.slice(at, at = styles[i]), styleToClass(styles[i+1]));
+ return;
+ }
+
+ var len = allText.length, pos = 0, i = 1, text = "", style;
+ var nextChange = 0, spanStyle, spanEndStyle, spanStartStyle, title, collapsed;
+ for (;;) {
+ if (nextChange == pos) { // Update current marker set
+ spanStyle = spanEndStyle = spanStartStyle = title = "";
+ collapsed = null; nextChange = Infinity;
+ var foundBookmark = null;
+ for (var j = 0; j < spans.length; ++j) {
+ var sp = spans[j], m = sp.marker;
+ if (sp.from <= pos && (sp.to == null || sp.to > pos)) {
+ if (sp.to != null && nextChange > sp.to) { nextChange = sp.to; spanEndStyle = ""; }
+ if (m.className) spanStyle += " " + m.className;
+ if (m.startStyle && sp.from == pos) spanStartStyle += " " + m.startStyle;
+ if (m.endStyle && sp.to == nextChange) spanEndStyle += " " + m.endStyle;
+ if (m.title && !title) title = m.title;
+ if (m.collapsed && (!collapsed || collapsed.marker.size < m.size))
+ collapsed = sp;
+ } else if (sp.from > pos && nextChange > sp.from) {
+ nextChange = sp.from;
+ }
+ if (m.type == "bookmark" && sp.from == pos && m.replacedWith) foundBookmark = m;
+ }
+ if (collapsed && (collapsed.from || 0) == pos) {
+ buildCollapsedSpan(builder, (collapsed.to == null ? len : collapsed.to) - pos,
+ collapsed.marker, collapsed.from == null);
+ if (collapsed.to == null) return collapsed.marker.find();
+ }
+ if (foundBookmark && !collapsed) buildCollapsedSpan(builder, 0, foundBookmark);
+ }
+ if (pos >= len) break;
+
+ var upto = Math.min(len, nextChange);
+ while (true) {
+ if (text) {
+ var end = pos + text.length;
+ if (!collapsed) {
+ var tokenText = end > upto ? text.slice(0, upto - pos) : text;
+ builder.addToken(builder, tokenText, style ? style + spanStyle : spanStyle,
+ spanStartStyle, pos + tokenText.length == nextChange ? spanEndStyle : "", title);
+ }
+ if (end >= upto) {text = text.slice(upto - pos); pos = upto; break;}
+ pos = end;
+ spanStartStyle = "";
+ }
+ text = allText.slice(at, at = styles[i++]);
+ style = styleToClass(styles[i++]);
+ }
+ }
+ }
+
+ // DOCUMENT DATA STRUCTURE
+
+ function updateDoc(doc, change, markedSpans, selAfter, estimateHeight) {
+ function spansFor(n) {return markedSpans ? markedSpans[n] : null;}
+ function update(line, text, spans) {
+ updateLine(line, text, spans, estimateHeight);
+ signalLater(line, "change", line, change);
+ }
+
+ var from = change.from, to = change.to, text = change.text;
+ var firstLine = getLine(doc, from.line), lastLine = getLine(doc, to.line);
+ var lastText = lst(text), lastSpans = spansFor(text.length - 1), nlines = to.line - from.line;
+
+ // First adjust the line structure
+ if (from.ch == 0 && to.ch == 0 && lastText == "") {
+ // This is a whole-line replace. Treated specially to make
+ // sure line objects move the way they are supposed to.
+ for (var i = 0, e = text.length - 1, added = []; i < e; ++i)
+ added.push(new Line(text[i], spansFor(i), estimateHeight));
+ update(lastLine, lastLine.text, lastSpans);
+ if (nlines) doc.remove(from.line, nlines);
+ if (added.length) doc.insert(from.line, added);
+ } else if (firstLine == lastLine) {
+ if (text.length == 1) {
+ update(firstLine, firstLine.text.slice(0, from.ch) + lastText + firstLine.text.slice(to.ch), lastSpans);
+ } else {
+ for (var added = [], i = 1, e = text.length - 1; i < e; ++i)
+ added.push(new Line(text[i], spansFor(i), estimateHeight));
+ added.push(new Line(lastText + firstLine.text.slice(to.ch), lastSpans, estimateHeight));
+ update(firstLine, firstLine.text.slice(0, from.ch) + text[0], spansFor(0));
+ doc.insert(from.line + 1, added);
+ }
+ } else if (text.length == 1) {
+ update(firstLine, firstLine.text.slice(0, from.ch) + text[0] + lastLine.text.slice(to.ch), spansFor(0));
+ doc.remove(from.line + 1, nlines);
+ } else {
+ update(firstLine, firstLine.text.slice(0, from.ch) + text[0], spansFor(0));
+ update(lastLine, lastText + lastLine.text.slice(to.ch), lastSpans);
+ for (var i = 1, e = text.length - 1, added = []; i < e; ++i)
+ added.push(new Line(text[i], spansFor(i), estimateHeight));
+ if (nlines > 1) doc.remove(from.line + 1, nlines - 1);
+ doc.insert(from.line + 1, added);
+ }
+
+ signalLater(doc, "change", doc, change);
+ setSelection(doc, selAfter.anchor, selAfter.head, null, true);
+ }
+
+ function LeafChunk(lines) {
+ this.lines = lines;
+ this.parent = null;
+ for (var i = 0, e = lines.length, height = 0; i < e; ++i) {
+ lines[i].parent = this;
+ height += lines[i].height;
+ }
+ this.height = height;
+ }
+
+ LeafChunk.prototype = {
+ chunkSize: function() { return this.lines.length; },
+ removeInner: function(at, n) {
+ for (var i = at, e = at + n; i < e; ++i) {
+ var line = this.lines[i];
+ this.height -= line.height;
+ cleanUpLine(line);
+ signalLater(line, "delete");
+ }
+ this.lines.splice(at, n);
+ },
+ collapse: function(lines) {
+ lines.splice.apply(lines, [lines.length, 0].concat(this.lines));
+ },
+ insertInner: function(at, lines, height) {
+ this.height += height;
+ this.lines = this.lines.slice(0, at).concat(lines).concat(this.lines.slice(at));
+ for (var i = 0, e = lines.length; i < e; ++i) lines[i].parent = this;
+ },
+ iterN: function(at, n, op) {
+ for (var e = at + n; at < e; ++at)
+ if (op(this.lines[at])) return true;
+ }
+ };
+
+ function BranchChunk(children) {
+ this.children = children;
+ var size = 0, height = 0;
+ for (var i = 0, e = children.length; i < e; ++i) {
+ var ch = children[i];
+ size += ch.chunkSize(); height += ch.height;
+ ch.parent = this;
+ }
+ this.size = size;
+ this.height = height;
+ this.parent = null;
+ }
+
+ BranchChunk.prototype = {
+ chunkSize: function() { return this.size; },
+ removeInner: function(at, n) {
+ this.size -= n;
+ for (var i = 0; i < this.children.length; ++i) {
+ var child = this.children[i], sz = child.chunkSize();
+ if (at < sz) {
+ var rm = Math.min(n, sz - at), oldHeight = child.height;
+ child.removeInner(at, rm);
+ this.height -= oldHeight - child.height;
+ if (sz == rm) { this.children.splice(i--, 1); child.parent = null; }
+ if ((n -= rm) == 0) break;
+ at = 0;
+ } else at -= sz;
+ }
+ if (this.size - n < 25) {
+ var lines = [];
+ this.collapse(lines);
+ this.children = [new LeafChunk(lines)];
+ this.children[0].parent = this;
+ }
+ },
+ collapse: function(lines) {
+ for (var i = 0, e = this.children.length; i < e; ++i) this.children[i].collapse(lines);
+ },
+ insertInner: function(at, lines, height) {
+ this.size += lines.length;
+ this.height += height;
+ for (var i = 0, e = this.children.length; i < e; ++i) {
+ var child = this.children[i], sz = child.chunkSize();
+ if (at <= sz) {
+ child.insertInner(at, lines, height);
+ if (child.lines && child.lines.length > 50) {
+ while (child.lines.length > 50) {
+ var spilled = child.lines.splice(child.lines.length - 25, 25);
+ var newleaf = new LeafChunk(spilled);
+ child.height -= newleaf.height;
+ this.children.splice(i + 1, 0, newleaf);
+ newleaf.parent = this;
+ }
+ this.maybeSpill();
+ }
+ break;
+ }
+ at -= sz;
+ }
+ },
+ maybeSpill: function() {
+ if (this.children.length <= 10) return;
+ var me = this;
+ do {
+ var spilled = me.children.splice(me.children.length - 5, 5);
+ var sibling = new BranchChunk(spilled);
+ if (!me.parent) { // Become the parent node
+ var copy = new BranchChunk(me.children);
+ copy.parent = me;
+ me.children = [copy, sibling];
+ me = copy;
+ } else {
+ me.size -= sibling.size;
+ me.height -= sibling.height;
+ var myIndex = indexOf(me.parent.children, me);
+ me.parent.children.splice(myIndex + 1, 0, sibling);
+ }
+ sibling.parent = me.parent;
+ } while (me.children.length > 10);
+ me.parent.maybeSpill();
+ },
+ iterN: function(at, n, op) {
+ for (var i = 0, e = this.children.length; i < e; ++i) {
+ var child = this.children[i], sz = child.chunkSize();
+ if (at < sz) {
+ var used = Math.min(n, sz - at);
+ if (child.iterN(at, used, op)) return true;
+ if ((n -= used) == 0) break;
+ at = 0;
+ } else at -= sz;
+ }
+ }
+ };
+
+ var nextDocId = 0;
+ var Doc = CodeMirror.Doc = function(text, mode, firstLine) {
+ if (!(this instanceof Doc)) return new Doc(text, mode, firstLine);
+ if (firstLine == null) firstLine = 0;
+
+ BranchChunk.call(this, [new LeafChunk([new Line("", null)])]);
+ this.first = firstLine;
+ this.scrollTop = this.scrollLeft = 0;
+ this.cantEdit = false;
+ this.history = makeHistory();
+ this.cleanGeneration = 1;
+ this.frontier = firstLine;
+ var start = Pos(firstLine, 0);
+ this.sel = {from: start, to: start, head: start, anchor: start, shift: false, extend: false, goalColumn: null};
+ this.id = ++nextDocId;
+ this.modeOption = mode;
+
+ if (typeof text == "string") text = splitLines(text);
+ updateDoc(this, {from: start, to: start, text: text}, null, {head: start, anchor: start});
+ };
+
+ Doc.prototype = createObj(BranchChunk.prototype, {
+ constructor: Doc,
+ iter: function(from, to, op) {
+ if (op) this.iterN(from - this.first, to - from, op);
+ else this.iterN(this.first, this.first + this.size, from);
+ },
+
+ insert: function(at, lines) {
+ var height = 0;
+ for (var i = 0, e = lines.length; i < e; ++i) height += lines[i].height;
+ this.insertInner(at - this.first, lines, height);
+ },
+ remove: function(at, n) { this.removeInner(at - this.first, n); },
+
+ getValue: function(lineSep) {
+ var lines = getLines(this, this.first, this.first + this.size);
+ if (lineSep === false) return lines;
+ return lines.join(lineSep || "\n");
+ },
+ setValue: function(code) {
+ var top = Pos(this.first, 0), last = this.first + this.size - 1;
+ makeChange(this, {from: top, to: Pos(last, getLine(this, last).text.length),
+ text: splitLines(code), origin: "setValue"},
+ {head: top, anchor: top}, true);
+ },
+ replaceRange: function(code, from, to, origin) {
+ from = clipPos(this, from);
+ to = to ? clipPos(this, to) : from;
+ replaceRange(this, code, from, to, origin);
+ },
+ getRange: function(from, to, lineSep) {
+ var lines = getBetween(this, clipPos(this, from), clipPos(this, to));
+ if (lineSep === false) return lines;
+ return lines.join(lineSep || "\n");
+ },
+
+ getLine: function(line) {var l = this.getLineHandle(line); return l && l.text;},
+ setLine: function(line, text) {
+ if (isLine(this, line))
+ replaceRange(this, text, Pos(line, 0), clipPos(this, Pos(line)));
+ },
+ removeLine: function(line) {
+ if (line) replaceRange(this, "", clipPos(this, Pos(line - 1)), clipPos(this, Pos(line)));
+ else replaceRange(this, "", Pos(0, 0), clipPos(this, Pos(1, 0)));
+ },
+
+ getLineHandle: function(line) {if (isLine(this, line)) return getLine(this, line);},
+ getLineNumber: function(line) {return lineNo(line);},
+
+ getLineHandleVisualStart: function(line) {
+ if (typeof line == "number") line = getLine(this, line);
+ return visualLine(this, line);
+ },
+
+ lineCount: function() {return this.size;},
+ firstLine: function() {return this.first;},
+ lastLine: function() {return this.first + this.size - 1;},
+
+ clipPos: function(pos) {return clipPos(this, pos);},
+
+ getCursor: function(start) {
+ var sel = this.sel, pos;
+ if (start == null || start == "head") pos = sel.head;
+ else if (start == "anchor") pos = sel.anchor;
+ else if (start == "end" || start === false) pos = sel.to;
+ else pos = sel.from;
+ return copyPos(pos);
+ },
+ somethingSelected: function() {return !posEq(this.sel.head, this.sel.anchor);},
+
+ setCursor: docOperation(function(line, ch, extend) {
+ var pos = clipPos(this, typeof line == "number" ? Pos(line, ch || 0) : line);
+ if (extend) extendSelection(this, pos);
+ else setSelection(this, pos, pos);
+ }),
+ setSelection: docOperation(function(anchor, head) {
+ setSelection(this, clipPos(this, anchor), clipPos(this, head || anchor));
+ }),
+ extendSelection: docOperation(function(from, to) {
+ extendSelection(this, clipPos(this, from), to && clipPos(this, to));
+ }),
+
+ getSelection: function(lineSep) {return this.getRange(this.sel.from, this.sel.to, lineSep);},
+ replaceSelection: function(code, collapse, origin) {
+ makeChange(this, {from: this.sel.from, to: this.sel.to, text: splitLines(code), origin: origin}, collapse || "around");
+ },
+ undo: docOperation(function() {makeChangeFromHistory(this, "undo");}),
+ redo: docOperation(function() {makeChangeFromHistory(this, "redo");}),
+
+ setExtending: function(val) {this.sel.extend = val;},
+
+ historySize: function() {
+ var hist = this.history;
+ return {undo: hist.done.length, redo: hist.undone.length};
+ },
+ clearHistory: function() {this.history = makeHistory(this.history.maxGeneration);},
+
+ markClean: function() {
+ this.cleanGeneration = this.changeGeneration();
+ },
+ changeGeneration: function() {
+ this.history.lastOp = this.history.lastOrigin = null;
+ return this.history.generation;
+ },
+ isClean: function (gen) {
+ return this.history.generation == (gen || this.cleanGeneration);
+ },
+
+ getHistory: function() {
+ return {done: copyHistoryArray(this.history.done),
+ undone: copyHistoryArray(this.history.undone)};
+ },
+ setHistory: function(histData) {
+ var hist = this.history = makeHistory(this.history.maxGeneration);
+ hist.done = histData.done.slice(0);
+ hist.undone = histData.undone.slice(0);
+ },
+
+ markText: function(from, to, options) {
+ return markText(this, clipPos(this, from), clipPos(this, to), options, "range");
+ },
+ setBookmark: function(pos, options) {
+ var realOpts = {replacedWith: options && (options.nodeType == null ? options.widget : options),
+ insertLeft: options && options.insertLeft};
+ pos = clipPos(this, pos);
+ return markText(this, pos, pos, realOpts, "bookmark");
+ },
+ findMarksAt: function(pos) {
+ pos = clipPos(this, pos);
+ var markers = [], spans = getLine(this, pos.line).markedSpans;
+ if (spans) for (var i = 0; i < spans.length; ++i) {
+ var span = spans[i];
+ if ((span.from == null || span.from <= pos.ch) &&
+ (span.to == null || span.to >= pos.ch))
+ markers.push(span.marker.parent || span.marker);
+ }
+ return markers;
+ },
+ getAllMarks: function() {
+ var markers = [];
+ this.iter(function(line) {
+ var sps = line.markedSpans;
+ if (sps) for (var i = 0; i < sps.length; ++i)
+ if (sps[i].from != null) markers.push(sps[i].marker);
+ });
+ return markers;
+ },
+
+ posFromIndex: function(off) {
+ var ch, lineNo = this.first;
+ this.iter(function(line) {
+ var sz = line.text.length + 1;
+ if (sz > off) { ch = off; return true; }
+ off -= sz;
+ ++lineNo;
+ });
+ return clipPos(this, Pos(lineNo, ch));
+ },
+ indexFromPos: function (coords) {
+ coords = clipPos(this, coords);
+ var index = coords.ch;
+ if (coords.line < this.first || coords.ch < 0) return 0;
+ this.iter(this.first, coords.line, function (line) {
+ index += line.text.length + 1;
+ });
+ return index;
+ },
+
+ copy: function(copyHistory) {
+ var doc = new Doc(getLines(this, this.first, this.first + this.size), this.modeOption, this.first);
+ doc.scrollTop = this.scrollTop; doc.scrollLeft = this.scrollLeft;
+ doc.sel = {from: this.sel.from, to: this.sel.to, head: this.sel.head, anchor: this.sel.anchor,
+ shift: this.sel.shift, extend: false, goalColumn: this.sel.goalColumn};
+ if (copyHistory) {
+ doc.history.undoDepth = this.history.undoDepth;
+ doc.setHistory(this.getHistory());
+ }
+ return doc;
+ },
+
+ linkedDoc: function(options) {
+ if (!options) options = {};
+ var from = this.first, to = this.first + this.size;
+ if (options.from != null && options.from > from) from = options.from;
+ if (options.to != null && options.to < to) to = options.to;
+ var copy = new Doc(getLines(this, from, to), options.mode || this.modeOption, from);
+ if (options.sharedHist) copy.history = this.history;
+ (this.linked || (this.linked = [])).push({doc: copy, sharedHist: options.sharedHist});
+ copy.linked = [{doc: this, isParent: true, sharedHist: options.sharedHist}];
+ return copy;
+ },
+ unlinkDoc: function(other) {
+ if (other instanceof CodeMirror) other = other.doc;
+ if (this.linked) for (var i = 0; i < this.linked.length; ++i) {
+ var link = this.linked[i];
+ if (link.doc != other) continue;
+ this.linked.splice(i, 1);
+ other.unlinkDoc(this);
+ break;
+ }
+ // If the histories were shared, split them again
+ if (other.history == this.history) {
+ var splitIds = [other.id];
+ linkedDocs(other, function(doc) {splitIds.push(doc.id);}, true);
+ other.history = makeHistory();
+ other.history.done = copyHistoryArray(this.history.done, splitIds);
+ other.history.undone = copyHistoryArray(this.history.undone, splitIds);
+ }
+ },
+ iterLinkedDocs: function(f) {linkedDocs(this, f);},
+
+ getMode: function() {return this.mode;},
+ getEditor: function() {return this.cm;}
+ });
+
+ Doc.prototype.eachLine = Doc.prototype.iter;
+
+ // The Doc methods that should be available on CodeMirror instances
+ var dontDelegate = "iter insert remove copy getEditor".split(" ");
+ for (var prop in Doc.prototype) if (Doc.prototype.hasOwnProperty(prop) && indexOf(dontDelegate, prop) < 0)
+ CodeMirror.prototype[prop] = (function(method) {
+ return function() {return method.apply(this.doc, arguments);};
+ })(Doc.prototype[prop]);
+
+ eventMixin(Doc);
+
+ function linkedDocs(doc, f, sharedHistOnly) {
+ function propagate(doc, skip, sharedHist) {
+ if (doc.linked) for (var i = 0; i < doc.linked.length; ++i) {
+ var rel = doc.linked[i];
+ if (rel.doc == skip) continue;
+ var shared = sharedHist && rel.sharedHist;
+ if (sharedHistOnly && !shared) continue;
+ f(rel.doc, shared);
+ propagate(rel.doc, doc, shared);
+ }
+ }
+ propagate(doc, null, true);
+ }
+
+ function attachDoc(cm, doc) {
+ if (doc.cm) throw new Error("This document is already in use.");
+ cm.doc = doc;
+ doc.cm = cm;
+ estimateLineHeights(cm);
+ loadMode(cm);
+ if (!cm.options.lineWrapping) computeMaxLength(cm);
+ cm.options.mode = doc.modeOption;
+ regChange(cm);
+ }
+
+ // LINE UTILITIES
+
+ function getLine(chunk, n) {
+ n -= chunk.first;
+ while (!chunk.lines) {
+ for (var i = 0;; ++i) {
+ var child = chunk.children[i], sz = child.chunkSize();
+ if (n < sz) { chunk = child; break; }
+ n -= sz;
+ }
+ }
+ return chunk.lines[n];
+ }
+
+ function getBetween(doc, start, end) {
+ var out = [], n = start.line;
+ doc.iter(start.line, end.line + 1, function(line) {
+ var text = line.text;
+ if (n == end.line) text = text.slice(0, end.ch);
+ if (n == start.line) text = text.slice(start.ch);
+ out.push(text);
+ ++n;
+ });
+ return out;
+ }
+ function getLines(doc, from, to) {
+ var out = [];
+ doc.iter(from, to, function(line) { out.push(line.text); });
+ return out;
+ }
+
+ function updateLineHeight(line, height) {
+ var diff = height - line.height;
+ for (var n = line; n; n = n.parent) n.height += diff;
+ }
+
+ function lineNo(line) {
+ if (line.parent == null) return null;
+ var cur = line.parent, no = indexOf(cur.lines, line);
+ for (var chunk = cur.parent; chunk; cur = chunk, chunk = chunk.parent) {
+ for (var i = 0;; ++i) {
+ if (chunk.children[i] == cur) break;
+ no += chunk.children[i].chunkSize();
+ }
+ }
+ return no + cur.first;
+ }
+
+ function lineAtHeight(chunk, h) {
+ var n = chunk.first;
+ outer: do {
+ for (var i = 0, e = chunk.children.length; i < e; ++i) {
+ var child = chunk.children[i], ch = child.height;
+ if (h < ch) { chunk = child; continue outer; }
+ h -= ch;
+ n += child.chunkSize();
+ }
+ return n;
+ } while (!chunk.lines);
+ for (var i = 0, e = chunk.lines.length; i < e; ++i) {
+ var line = chunk.lines[i], lh = line.height;
+ if (h < lh) break;
+ h -= lh;
+ }
+ return n + i;
+ }
+
+ function heightAtLine(cm, lineObj) {
+ lineObj = visualLine(cm.doc, lineObj);
+
+ var h = 0, chunk = lineObj.parent;
+ for (var i = 0; i < chunk.lines.length; ++i) {
+ var line = chunk.lines[i];
+ if (line == lineObj) break;
+ else h += line.height;
+ }
+ for (var p = chunk.parent; p; chunk = p, p = chunk.parent) {
+ for (var i = 0; i < p.children.length; ++i) {
+ var cur = p.children[i];
+ if (cur == chunk) break;
+ else h += cur.height;
+ }
+ }
+ return h;
+ }
+
+ function getOrder(line) {
+ var order = line.order;
+ if (order == null) order = line.order = bidiOrdering(line.text);
+ return order;
+ }
+
+ // HISTORY
+
+ function makeHistory(startGen) {
+ return {
+ // Arrays of history events. Doing something adds an event to
+ // done and clears undo. Undoing moves events from done to
+ // undone, redoing moves them in the other direction.
+ done: [], undone: [], undoDepth: Infinity,
+ // Used to track when changes can be merged into a single undo
+ // event
+ lastTime: 0, lastOp: null, lastOrigin: null,
+ // Used by the isClean() method
+ generation: startGen || 1, maxGeneration: startGen || 1
+ };
+ }
+
+ function attachLocalSpans(doc, change, from, to) {
+ var existing = change["spans_" + doc.id], n = 0;
+ doc.iter(Math.max(doc.first, from), Math.min(doc.first + doc.size, to), function(line) {
+ if (line.markedSpans)
+ (existing || (existing = change["spans_" + doc.id] = {}))[n] = line.markedSpans;
+ ++n;
+ });
+ }
+
+ function historyChangeFromChange(doc, change) {
+ var from = { line: change.from.line, ch: change.from.ch };
+ var histChange = {from: from, to: changeEnd(change), text: getBetween(doc, change.from, change.to)};
+ attachLocalSpans(doc, histChange, change.from.line, change.to.line + 1);
+ linkedDocs(doc, function(doc) {attachLocalSpans(doc, histChange, change.from.line, change.to.line + 1);}, true);
+ return histChange;
+ }
+
+ function addToHistory(doc, change, selAfter, opId) {
+ var hist = doc.history;
+ hist.undone.length = 0;
+ var time = +new Date, cur = lst(hist.done);
+
+ if (cur &&
+ (hist.lastOp == opId ||
+ hist.lastOrigin == change.origin && change.origin &&
+ ((change.origin.charAt(0) == "+" && doc.cm && hist.lastTime > time - doc.cm.options.historyEventDelay) ||
+ change.origin.charAt(0) == "*"))) {
+ // Merge this change into the last event
+ var last = lst(cur.changes);
+ if (posEq(change.from, change.to) && posEq(change.from, last.to)) {
+ // Optimized case for simple insertion -- don't want to add
+ // new changesets for every character typed
+ last.to = changeEnd(change);
+ } else {
+ // Add new sub-event
+ cur.changes.push(historyChangeFromChange(doc, change));
+ }
+ cur.anchorAfter = selAfter.anchor; cur.headAfter = selAfter.head;
+ } else {
+ // Can not be merged, start a new event.
+ cur = {changes: [historyChangeFromChange(doc, change)],
+ generation: hist.generation,
+ anchorBefore: doc.sel.anchor, headBefore: doc.sel.head,
+ anchorAfter: selAfter.anchor, headAfter: selAfter.head};
+ hist.done.push(cur);
+ hist.generation = ++hist.maxGeneration;
+ while (hist.done.length > hist.undoDepth)
+ hist.done.shift();
+ }
+ hist.lastTime = time;
+ hist.lastOp = opId;
+ hist.lastOrigin = change.origin;
+ }
+
+ function removeClearedSpans(spans) {
+ if (!spans) return null;
+ for (var i = 0, out; i < spans.length; ++i) {
+ if (spans[i].marker.explicitlyCleared) { if (!out) out = spans.slice(0, i); }
+ else if (out) out.push(spans[i]);
+ }
+ return !out ? spans : out.length ? out : null;
+ }
+
+ function getOldSpans(doc, change) {
+ var found = change["spans_" + doc.id];
+ if (!found) return null;
+ for (var i = 0, nw = []; i < change.text.length; ++i)
+ nw.push(removeClearedSpans(found[i]));
+ return nw;
+ }
+
+ // Used both to provide a JSON-safe object in .getHistory, and, when
+ // detaching a document, to split the history in two
+ function copyHistoryArray(events, newGroup) {
+ for (var i = 0, copy = []; i < events.length; ++i) {
+ var event = events[i], changes = event.changes, newChanges = [];
+ copy.push({changes: newChanges, anchorBefore: event.anchorBefore, headBefore: event.headBefore,
+ anchorAfter: event.anchorAfter, headAfter: event.headAfter});
+ for (var j = 0; j < changes.length; ++j) {
+ var change = changes[j], m;
+ newChanges.push({from: change.from, to: change.to, text: change.text});
+ if (newGroup) for (var prop in change) if (m = prop.match(/^spans_(\d+)$/)) {
+ if (indexOf(newGroup, Number(m[1])) > -1) {
+ lst(newChanges)[prop] = change[prop];
+ delete change[prop];
+ }
+ }
+ }
+ }
+ return copy;
+ }
+
+ // Rebasing/resetting history to deal with externally-sourced changes
+
+ function rebaseHistSel(pos, from, to, diff) {
+ if (to < pos.line) {
+ pos.line += diff;
+ } else if (from < pos.line) {
+ pos.line = from;
+ pos.ch = 0;
+ }
+ }
+
+ // Tries to rebase an array of history events given a change in the
+ // document. If the change touches the same lines as the event, the
+ // event, and everything 'behind' it, is discarded. If the change is
+ // before the event, the event's positions are updated. Uses a
+ // copy-on-write scheme for the positions, to avoid having to
+ // reallocate them all on every rebase, but also avoid problems with
+ // shared position objects being unsafely updated.
+ function rebaseHistArray(array, from, to, diff) {
+ for (var i = 0; i < array.length; ++i) {
+ var sub = array[i], ok = true;
+ for (var j = 0; j < sub.changes.length; ++j) {
+ var cur = sub.changes[j];
+ if (!sub.copied) { cur.from = copyPos(cur.from); cur.to = copyPos(cur.to); }
+ if (to < cur.from.line) {
+ cur.from.line += diff;
+ cur.to.line += diff;
+ } else if (from <= cur.to.line) {
+ ok = false;
+ break;
+ }
+ }
+ if (!sub.copied) {
+ sub.anchorBefore = copyPos(sub.anchorBefore); sub.headBefore = copyPos(sub.headBefore);
+ sub.anchorAfter = copyPos(sub.anchorAfter); sub.readAfter = copyPos(sub.headAfter);
+ sub.copied = true;
+ }
+ if (!ok) {
+ array.splice(0, i + 1);
+ i = 0;
+ } else {
+ rebaseHistSel(sub.anchorBefore); rebaseHistSel(sub.headBefore);
+ rebaseHistSel(sub.anchorAfter); rebaseHistSel(sub.headAfter);
+ }
+ }
+ }
+
+ function rebaseHist(hist, change) {
+ var from = change.from.line, to = change.to.line, diff = change.text.length - (to - from) - 1;
+ rebaseHistArray(hist.done, from, to, diff);
+ rebaseHistArray(hist.undone, from, to, diff);
+ }
+
+ // EVENT OPERATORS
+
+ function stopMethod() {e_stop(this);}
+ // Ensure an event has a stop method.
+ function addStop(event) {
+ if (!event.stop) event.stop = stopMethod;
+ return event;
+ }
+
+ function e_preventDefault(e) {
+ if (e.preventDefault) e.preventDefault();
+ else e.returnValue = false;
+ }
+ function e_stopPropagation(e) {
+ if (e.stopPropagation) e.stopPropagation();
+ else e.cancelBubble = true;
+ }
+ function e_defaultPrevented(e) {
+ return e.defaultPrevented != null ? e.defaultPrevented : e.returnValue == false;
+ }
+ function e_stop(e) {e_preventDefault(e); e_stopPropagation(e);}
+ CodeMirror.e_stop = e_stop;
+ CodeMirror.e_preventDefault = e_preventDefault;
+ CodeMirror.e_stopPropagation = e_stopPropagation;
+
+ function e_target(e) {return e.target || e.srcElement;}
+ function e_button(e) {
+ var b = e.which;
+ if (b == null) {
+ if (e.button & 1) b = 1;
+ else if (e.button & 2) b = 3;
+ else if (e.button & 4) b = 2;
+ }
+ if (mac && e.ctrlKey && b == 1) b = 3;
+ return b;
+ }
+
+ // EVENT HANDLING
+
+ function on(emitter, type, f) {
+ if (emitter.addEventListener)
+ emitter.addEventListener(type, f, false);
+ else if (emitter.attachEvent)
+ emitter.attachEvent("on" + type, f);
+ else {
+ var map = emitter._handlers || (emitter._handlers = {});
+ var arr = map[type] || (map[type] = []);
+ arr.push(f);
+ }
+ }
+
+ function off(emitter, type, f) {
+ if (emitter.removeEventListener)
+ emitter.removeEventListener(type, f, false);
+ else if (emitter.detachEvent)
+ emitter.detachEvent("on" + type, f);
+ else {
+ var arr = emitter._handlers && emitter._handlers[type];
+ if (!arr) return;
+ for (var i = 0; i < arr.length; ++i)
+ if (arr[i] == f) { arr.splice(i, 1); break; }
+ }
+ }
+
+ function signal(emitter, type /*, values...*/) {
+ var arr = emitter._handlers && emitter._handlers[type];
+ if (!arr) return;
+ var args = Array.prototype.slice.call(arguments, 2);
+ for (var i = 0; i < arr.length; ++i) arr[i].apply(null, args);
+ }
+
+ var delayedCallbacks, delayedCallbackDepth = 0;
+ function signalLater(emitter, type /*, values...*/) {
+ var arr = emitter._handlers && emitter._handlers[type];
+ if (!arr) return;
+ var args = Array.prototype.slice.call(arguments, 2);
+ if (!delayedCallbacks) {
+ ++delayedCallbackDepth;
+ delayedCallbacks = [];
+ setTimeout(fireDelayed, 0);
+ }
+ function bnd(f) {return function(){f.apply(null, args);};};
+ for (var i = 0; i < arr.length; ++i)
+ delayedCallbacks.push(bnd(arr[i]));
+ }
+
+ function signalDOMEvent(cm, e, override) {
+ signal(cm, override || e.type, cm, e);
+ return e_defaultPrevented(e) || e.codemirrorIgnore;
+ }
+
+ function fireDelayed() {
+ --delayedCallbackDepth;
+ var delayed = delayedCallbacks;
+ delayedCallbacks = null;
+ for (var i = 0; i < delayed.length; ++i) delayed[i]();
+ }
+
+ function hasHandler(emitter, type) {
+ var arr = emitter._handlers && emitter._handlers[type];
+ return arr && arr.length > 0;
+ }
+
+ CodeMirror.on = on; CodeMirror.off = off; CodeMirror.signal = signal;
+
+ function eventMixin(ctor) {
+ ctor.prototype.on = function(type, f) {on(this, type, f);};
+ ctor.prototype.off = function(type, f) {off(this, type, f);};
+ }
+
+ // MISC UTILITIES
+
+ // Number of pixels added to scroller and sizer to hide scrollbar
+ var scrollerCutOff = 30;
+
+ // Returned or thrown by various protocols to signal 'I'm not
+ // handling this'.
+ var Pass = CodeMirror.Pass = {toString: function(){return "CodeMirror.Pass";}};
+
+ function Delayed() {this.id = null;}
+ Delayed.prototype = {set: function(ms, f) {clearTimeout(this.id); this.id = setTimeout(f, ms);}};
+
+ // Counts the column offset in a string, taking tabs into account.
+ // Used mostly to find indentation.
+ function countColumn(string, end, tabSize, startIndex, startValue) {
+ if (end == null) {
+ end = string.search(/[^\s\u00a0]/);
+ if (end == -1) end = string.length;
+ }
+ for (var i = startIndex || 0, n = startValue || 0; i < end; ++i) {
+ if (string.charAt(i) == "\t") n += tabSize - (n % tabSize);
+ else ++n;
+ }
+ return n;
+ }
+ CodeMirror.countColumn = countColumn;
+
+ var spaceStrs = [""];
+ function spaceStr(n) {
+ while (spaceStrs.length <= n)
+ spaceStrs.push(lst(spaceStrs) + " ");
+ return spaceStrs[n];
+ }
+
+ function lst(arr) { return arr[arr.length-1]; }
+
+ function selectInput(node) {
+ if (ios) { // Mobile Safari apparently has a bug where select() is broken.
+ node.selectionStart = 0;
+ node.selectionEnd = node.value.length;
+ } else {
+ // Suppress mysterious IE10 errors
+ try { node.select(); }
+ catch(_e) {}
+ }
+ }
+
+ function indexOf(collection, elt) {
+ if (collection.indexOf) return collection.indexOf(elt);
+ for (var i = 0, e = collection.length; i < e; ++i)
+ if (collection[i] == elt) return i;
+ return -1;
+ }
+
+ function createObj(base, props) {
+ function Obj() {}
+ Obj.prototype = base;
+ var inst = new Obj();
+ if (props) copyObj(props, inst);
+ return inst;
+ }
+
+ function copyObj(obj, target) {
+ if (!target) target = {};
+ for (var prop in obj) if (obj.hasOwnProperty(prop)) target[prop] = obj[prop];
+ return target;
+ }
+
+ function emptyArray(size) {
+ for (var a = [], i = 0; i < size; ++i) a.push(undefined);
+ return a;
+ }
+
+ function bind(f) {
+ var args = Array.prototype.slice.call(arguments, 1);
+ return function(){return f.apply(null, args);};
+ }
+
+ var nonASCIISingleCaseWordChar = /[\u3040-\u309f\u30a0-\u30ff\u3400-\u4db5\u4e00-\u9fcc\uac00-\ud7af]/;
+ function isWordChar(ch) {
+ return /\w/.test(ch) || ch > "\x80" &&
+ (ch.toUpperCase() != ch.toLowerCase() || nonASCIISingleCaseWordChar.test(ch));
+ }
+
+ function isEmpty(obj) {
+ for (var n in obj) if (obj.hasOwnProperty(n) && obj[n]) return false;
+ return true;
+ }
+
+ var isExtendingChar = /[\u0300-\u036F\u0483-\u0487\u0488-\u0489\u0591-\u05BD\u05BF\u05C1-\u05C2\u05C4-\u05C5\u05C7\u0610-\u061A\u064B-\u065F\u0670\u06D6-\u06DC\u06DF-\u06E4\u06E7-\u06E8\u06EA-\u06ED\uA66F\uA670-\uA672\uA674-\uA67D\uA69F\udc00-\udfff]/;
+
+ // DOM UTILITIES
+
+ function elt(tag, content, className, style) {
+ var e = document.createElement(tag);
+ if (className) e.className = className;
+ if (style) e.style.cssText = style;
+ if (typeof content == "string") setTextContent(e, content);
+ else if (content) for (var i = 0; i < content.length; ++i) e.appendChild(content[i]);
+ return e;
+ }
+
+ function removeChildren(e) {
+ for (var count = e.childNodes.length; count > 0; --count)
+ e.removeChild(e.firstChild);
+ return e;
+ }
+
+ function removeChildrenAndAdd(parent, e) {
+ return removeChildren(parent).appendChild(e);
+ }
+
+ function setTextContent(e, str) {
+ if (ie_lt9) {
+ e.innerHTML = "";
+ e.appendChild(document.createTextNode(str));
+ } else e.textContent = str;
+ }
+
+ function getRect(node) {
+ return node.getBoundingClientRect();
+ }
+ CodeMirror.replaceGetRect = function(f) { getRect = f; };
+
+ // FEATURE DETECTION
+
+ // Detect drag-and-drop
+ var dragAndDrop = function() {
+ // There is *some* kind of drag-and-drop support in IE6-8, but I
+ // couldn't get it to work yet.
+ if (ie_lt9) return false;
+ var div = elt('div');
+ return "draggable" in div || "dragDrop" in div;
+ }();
+
+ // For a reason I have yet to figure out, some browsers disallow
+ // word wrapping between certain characters *only* if a new inline
+ // element is started between them. This makes it hard to reliably
+ // measure the position of things, since that requires inserting an
+ // extra span. This terribly fragile set of tests matches the
+ // character combinations that suffer from this phenomenon on the
+ // various browsers.
+ function spanAffectsWrapping() { return false; }
+ if (gecko) // Only for "$'"
+ spanAffectsWrapping = function(str, i) {
+ return str.charCodeAt(i - 1) == 36 && str.charCodeAt(i) == 39;
+ };
+ else if (safari && !/Version\/([6-9]|\d\d)\b/.test(navigator.userAgent))
+ spanAffectsWrapping = function(str, i) {
+ return /\-[^ \-?]|\?[^ !\'\"\),.\-\/:;\?\]\}]/.test(str.slice(i - 1, i + 1));
+ };
+ else if (webkit && !/Chrome\/(?:29|[3-9]\d|\d\d\d)\./.test(navigator.userAgent))
+ spanAffectsWrapping = function(str, i) {
+ if (i > 1 && str.charCodeAt(i - 1) == 45) {
+ if (/\w/.test(str.charAt(i - 2)) && /[^\-?\.]/.test(str.charAt(i))) return true;
+ if (i > 2 && /[\d\.,]/.test(str.charAt(i - 2)) && /[\d\.,]/.test(str.charAt(i))) return false;
+ }
+ return /[~!#%&*)=+}\]|\"\.>,:;][({[<]|-[^\-?\.\u2010-\u201f\u2026]|\?[\w~`@#$%\^&*(_=+{[|><]|…[\w~`@#$%\^&*(_=+{[><]/.test(str.slice(i - 1, i + 1));
+ };
+
+ var knownScrollbarWidth;
+ function scrollbarWidth(measure) {
+ if (knownScrollbarWidth != null) return knownScrollbarWidth;
+ var test = elt("div", null, null, "width: 50px; height: 50px; overflow-x: scroll");
+ removeChildrenAndAdd(measure, test);
+ if (test.offsetWidth)
+ knownScrollbarWidth = test.offsetHeight - test.clientHeight;
+ return knownScrollbarWidth || 0;
+ }
+
+ var zwspSupported;
+ function zeroWidthElement(measure) {
+ if (zwspSupported == null) {
+ var test = elt("span", "\u200b");
+ removeChildrenAndAdd(measure, elt("span", [test, document.createTextNode("x")]));
+ if (measure.firstChild.offsetHeight != 0)
+ zwspSupported = test.offsetWidth <= 1 && test.offsetHeight > 2 && !ie_lt8;
+ }
+ if (zwspSupported) return elt("span", "\u200b");
+ else return elt("span", "\u00a0", null, "display: inline-block; width: 1px; margin-right: -1px");
+ }
+
+ // See if "".split is the broken IE version, if so, provide an
+ // alternative way to split lines.
+ var splitLines = "\n\nb".split(/\n/).length != 3 ? function(string) {
+ var pos = 0, result = [], l = string.length;
+ while (pos <= l) {
+ var nl = string.indexOf("\n", pos);
+ if (nl == -1) nl = string.length;
+ var line = string.slice(pos, string.charAt(nl - 1) == "\r" ? nl - 1 : nl);
+ var rt = line.indexOf("\r");
+ if (rt != -1) {
+ result.push(line.slice(0, rt));
+ pos += rt + 1;
+ } else {
+ result.push(line);
+ pos = nl + 1;
+ }
+ }
+ return result;
+ } : function(string){return string.split(/\r\n?|\n/);};
+ CodeMirror.splitLines = splitLines;
+
+ var hasSelection = window.getSelection ? function(te) {
+ try { return te.selectionStart != te.selectionEnd; }
+ catch(e) { return false; }
+ } : function(te) {
+ try {var range = te.ownerDocument.selection.createRange();}
+ catch(e) {}
+ if (!range || range.parentElement() != te) return false;
+ return range.compareEndPoints("StartToEnd", range) != 0;
+ };
+
+ var hasCopyEvent = (function() {
+ var e = elt("div");
+ if ("oncopy" in e) return true;
+ e.setAttribute("oncopy", "return;");
+ return typeof e.oncopy == 'function';
+ })();
+
+ // KEY NAMING
+
+ var keyNames = {3: "Enter", 8: "Backspace", 9: "Tab", 13: "Enter", 16: "Shift", 17: "Ctrl", 18: "Alt",
+ 19: "Pause", 20: "CapsLock", 27: "Esc", 32: "Space", 33: "PageUp", 34: "PageDown", 35: "End",
+ 36: "Home", 37: "Left", 38: "Up", 39: "Right", 40: "Down", 44: "PrintScrn", 45: "Insert",
+ 46: "Delete", 59: ";", 91: "Mod", 92: "Mod", 93: "Mod", 109: "-", 107: "=", 127: "Delete",
+ 186: ";", 187: "=", 188: ",", 189: "-", 190: ".", 191: "/", 192: "`", 219: "[", 220: "\\",
+ 221: "]", 222: "'", 63276: "PageUp", 63277: "PageDown", 63275: "End", 63273: "Home",
+ 63234: "Left", 63232: "Up", 63235: "Right", 63233: "Down", 63302: "Insert", 63272: "Delete"};
+ CodeMirror.keyNames = keyNames;
+ (function() {
+ // Number keys
+ for (var i = 0; i < 10; i++) keyNames[i + 48] = String(i);
+ // Alphabetic keys
+ for (var i = 65; i <= 90; i++) keyNames[i] = String.fromCharCode(i);
+ // Function keys
+ for (var i = 1; i <= 12; i++) keyNames[i + 111] = keyNames[i + 63235] = "F" + i;
+ })();
+
+ // BIDI HELPERS
+
+ function iterateBidiSections(order, from, to, f) {
+ if (!order) return f(from, to, "ltr");
+ var found = false;
+ for (var i = 0; i < order.length; ++i) {
+ var part = order[i];
+ if (part.from < to && part.to > from || from == to && part.to == from) {
+ f(Math.max(part.from, from), Math.min(part.to, to), part.level == 1 ? "rtl" : "ltr");
+ found = true;
+ }
+ }
+ if (!found) f(from, to, "ltr");
+ }
+
+ function bidiLeft(part) { return part.level % 2 ? part.to : part.from; }
+ function bidiRight(part) { return part.level % 2 ? part.from : part.to; }
+
+ function lineLeft(line) { var order = getOrder(line); return order ? bidiLeft(order[0]) : 0; }
+ function lineRight(line) {
+ var order = getOrder(line);
+ if (!order) return line.text.length;
+ return bidiRight(lst(order));
+ }
+
+ function lineStart(cm, lineN) {
+ var line = getLine(cm.doc, lineN);
+ var visual = visualLine(cm.doc, line);
+ if (visual != line) lineN = lineNo(visual);
+ var order = getOrder(visual);
+ var ch = !order ? 0 : order[0].level % 2 ? lineRight(visual) : lineLeft(visual);
+ return Pos(lineN, ch);
+ }
+ function lineEnd(cm, lineN) {
+ var merged, line;
+ while (merged = collapsedSpanAtEnd(line = getLine(cm.doc, lineN)))
+ lineN = merged.find().to.line;
+ var order = getOrder(line);
+ var ch = !order ? line.text.length : order[0].level % 2 ? lineLeft(line) : lineRight(line);
+ return Pos(lineN, ch);
+ }
+
+ function compareBidiLevel(order, a, b) {
+ var linedir = order[0].level;
+ if (a == linedir) return true;
+ if (b == linedir) return false;
+ return a < b;
+ }
+ var bidiOther;
+ function getBidiPartAt(order, pos) {
+ for (var i = 0, found; i < order.length; ++i) {
+ var cur = order[i];
+ if (cur.from < pos && cur.to > pos) { bidiOther = null; return i; }
+ if (cur.from == pos || cur.to == pos) {
+ if (found == null) {
+ found = i;
+ } else if (compareBidiLevel(order, cur.level, order[found].level)) {
+ bidiOther = found;
+ return i;
+ } else {
+ bidiOther = i;
+ return found;
+ }
+ }
+ }
+ bidiOther = null;
+ return found;
+ }
+
+ function moveInLine(line, pos, dir, byUnit) {
+ if (!byUnit) return pos + dir;
+ do pos += dir;
+ while (pos > 0 && isExtendingChar.test(line.text.charAt(pos)));
+ return pos;
+ }
+
+ // This is somewhat involved. It is needed in order to move
+ // 'visually' through bi-directional text -- i.e., pressing left
+ // should make the cursor go left, even when in RTL text. The
+ // tricky part is the 'jumps', where RTL and LTR text touch each
+ // other. This often requires the cursor offset to move more than
+ // one unit, in order to visually move one unit.
+ function moveVisually(line, start, dir, byUnit) {
+ var bidi = getOrder(line);
+ if (!bidi) return moveLogically(line, start, dir, byUnit);
+ var pos = getBidiPartAt(bidi, start), part = bidi[pos];
+ var target = moveInLine(line, start, part.level % 2 ? -dir : dir, byUnit);
+
+ for (;;) {
+ if (target > part.from && target < part.to) return target;
+ if (target == part.from || target == part.to) {
+ if (getBidiPartAt(bidi, target) == pos) return target;
+ part = bidi[pos += dir];
+ return (dir > 0) == part.level % 2 ? part.to : part.from;
+ } else {
+ part = bidi[pos += dir];
+ if (!part) return null;
+ if ((dir > 0) == part.level % 2)
+ target = moveInLine(line, part.to, -1, byUnit);
+ else
+ target = moveInLine(line, part.from, 1, byUnit);
+ }
+ }
+ }
+
+ function moveLogically(line, start, dir, byUnit) {
+ var target = start + dir;
+ if (byUnit) while (target > 0 && isExtendingChar.test(line.text.charAt(target))) target += dir;
+ return target < 0 || target > line.text.length ? null : target;
+ }
+
+ // Bidirectional ordering algorithm
+ // See http://unicode.org/reports/tr9/tr9-13.html for the algorithm
+ // that this (partially) implements.
+
+ // One-char codes used for character types:
+ // L (L): Left-to-Right
+ // R (R): Right-to-Left
+ // r (AL): Right-to-Left Arabic
+ // 1 (EN): European Number
+ // + (ES): European Number Separator
+ // % (ET): European Number Terminator
+ // n (AN): Arabic Number
+ // , (CS): Common Number Separator
+ // m (NSM): Non-Spacing Mark
+ // b (BN): Boundary Neutral
+ // s (B): Paragraph Separator
+ // t (S): Segment Separator
+ // w (WS): Whitespace
+ // N (ON): Other Neutrals
+
+ // Returns null if characters are ordered as they appear
+ // (left-to-right), or an array of sections ({from, to, level}
+ // objects) in the order in which they occur visually.
+ var bidiOrdering = (function() {
+ // Character types for codepoints 0 to 0xff
+ var lowTypes = "bbbbbbbbbtstwsbbbbbbbbbbbbbbssstwNN%%%NNNNNN,N,N1111111111NNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNbbbbbbsbbbbbbbbbbbbbbbbbbbbbbbbbb,N%%%%NNNNLNNNNN%%11NLNNN1LNNNNNLLLLLLLLLLLLLLLLLLLLLLLNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNLLLLLLLL";
+ // Character types for codepoints 0x600 to 0x6ff
+ var arabicTypes = "rrrrrrrrrrrr,rNNmmmmmmrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrmmmmmmmmmmmmmmrrrrrrrnnnnnnnnnn%nnrrrmrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrmmmmmmmmmmmmmmmmmmmNmmmmrrrrrrrrrrrrrrrrrr";
+ function charType(code) {
+ if (code <= 0xff) return lowTypes.charAt(code);
+ else if (0x590 <= code && code <= 0x5f4) return "R";
+ else if (0x600 <= code && code <= 0x6ff) return arabicTypes.charAt(code - 0x600);
+ else if (0x700 <= code && code <= 0x8ac) return "r";
+ else return "L";
+ }
+
+ var bidiRE = /[\u0590-\u05f4\u0600-\u06ff\u0700-\u08ac]/;
+ var isNeutral = /[stwN]/, isStrong = /[LRr]/, countsAsLeft = /[Lb1n]/, countsAsNum = /[1n]/;
+ // Browsers seem to always treat the boundaries of block elements as being L.
+ var outerType = "L";
+
+ return function(str) {
+ if (!bidiRE.test(str)) return false;
+ var len = str.length, types = [];
+ for (var i = 0, type; i < len; ++i)
+ types.push(type = charType(str.charCodeAt(i)));
+
+ // W1. Examine each non-spacing mark (NSM) in the level run, and
+ // change the type of the NSM to the type of the previous
+ // character. If the NSM is at the start of the level run, it will
+ // get the type of sor.
+ for (var i = 0, prev = outerType; i < len; ++i) {
+ var type = types[i];
+ if (type == "m") types[i] = prev;
+ else prev = type;
+ }
+
+ // W2. Search backwards from each instance of a European number
+ // until the first strong type (R, L, AL, or sor) is found. If an
+ // AL is found, change the type of the European number to Arabic
+ // number.
+ // W3. Change all ALs to R.
+ for (var i = 0, cur = outerType; i < len; ++i) {
+ var type = types[i];
+ if (type == "1" && cur == "r") types[i] = "n";
+ else if (isStrong.test(type)) { cur = type; if (type == "r") types[i] = "R"; }
+ }
+
+ // W4. A single European separator between two European numbers
+ // changes to a European number. A single common separator between
+ // two numbers of the same type changes to that type.
+ for (var i = 1, prev = types[0]; i < len - 1; ++i) {
+ var type = types[i];
+ if (type == "+" && prev == "1" && types[i+1] == "1") types[i] = "1";
+ else if (type == "," && prev == types[i+1] &&
+ (prev == "1" || prev == "n")) types[i] = prev;
+ prev = type;
+ }
+
+ // W5. A sequence of European terminators adjacent to European
+ // numbers changes to all European numbers.
+ // W6. Otherwise, separators and terminators change to Other
+ // Neutral.
+ for (var i = 0; i < len; ++i) {
+ var type = types[i];
+ if (type == ",") types[i] = "N";
+ else if (type == "%") {
+ for (var end = i + 1; end < len && types[end] == "%"; ++end) {}
+ var replace = (i && types[i-1] == "!") || (end < len - 1 && types[end] == "1") ? "1" : "N";
+ for (var j = i; j < end; ++j) types[j] = replace;
+ i = end - 1;
+ }
+ }
+
+ // W7. Search backwards from each instance of a European number
+ // until the first strong type (R, L, or sor) is found. If an L is
+ // found, then change the type of the European number to L.
+ for (var i = 0, cur = outerType; i < len; ++i) {
+ var type = types[i];
+ if (cur == "L" && type == "1") types[i] = "L";
+ else if (isStrong.test(type)) cur = type;
+ }
+
+ // N1. A sequence of neutrals takes the direction of the
+ // surrounding strong text if the text on both sides has the same
+ // direction. European and Arabic numbers act as if they were R in
+ // terms of their influence on neutrals. Start-of-level-run (sor)
+ // and end-of-level-run (eor) are used at level run boundaries.
+ // N2. Any remaining neutrals take the embedding direction.
+ for (var i = 0; i < len; ++i) {
+ if (isNeutral.test(types[i])) {
+ for (var end = i + 1; end < len && isNeutral.test(types[end]); ++end) {}
+ var before = (i ? types[i-1] : outerType) == "L";
+ var after = (end < len - 1 ? types[end] : outerType) == "L";
+ var replace = before || after ? "L" : "R";
+ for (var j = i; j < end; ++j) types[j] = replace;
+ i = end - 1;
+ }
+ }
+
+ // Here we depart from the documented algorithm, in order to avoid
+ // building up an actual levels array. Since there are only three
+ // levels (0, 1, 2) in an implementation that doesn't take
+ // explicit embedding into account, we can build up the order on
+ // the fly, without following the level-based algorithm.
+ var order = [], m;
+ for (var i = 0; i < len;) {
+ if (countsAsLeft.test(types[i])) {
+ var start = i;
+ for (++i; i < len && countsAsLeft.test(types[i]); ++i) {}
+ order.push({from: start, to: i, level: 0});
+ } else {
+ var pos = i, at = order.length;
+ for (++i; i < len && types[i] != "L"; ++i) {}
+ for (var j = pos; j < i;) {
+ if (countsAsNum.test(types[j])) {
+ if (pos < j) order.splice(at, 0, {from: pos, to: j, level: 1});
+ var nstart = j;
+ for (++j; j < i && countsAsNum.test(types[j]); ++j) {}
+ order.splice(at, 0, {from: nstart, to: j, level: 2});
+ pos = j;
+ } else ++j;
+ }
+ if (pos < i) order.splice(at, 0, {from: pos, to: i, level: 1});
+ }
+ }
+ if (order[0].level == 1 && (m = str.match(/^\s+/))) {
+ order[0].from = m[0].length;
+ order.unshift({from: 0, to: m[0].length, level: 0});
+ }
+ if (lst(order).level == 1 && (m = str.match(/\s+$/))) {
+ lst(order).to -= m[0].length;
+ order.push({from: len - m[0].length, to: len, level: 0});
+ }
+ if (order[0].level != lst(order).level)
+ order.push({from: len, to: len, level: order[0].level});
+
+ return order;
+ };
+ })();
+
+ // THE END
+
+ CodeMirror.version = "3.15.0";
+
+ return CodeMirror;
+})();
diff --git a/js/codemirror/mode/sql/sql.js b/js/codemirror/mode/sql/sql.js
new file mode 100644
index 0000000000..9016cc7aae
--- /dev/null
+++ b/js/codemirror/mode/sql/sql.js
@@ -0,0 +1,349 @@
+CodeMirror.defineMode("sql", function(config, parserConfig) {
+ "use strict";
+
+ var client = parserConfig.client || {},
+ atoms = parserConfig.atoms || {"false": true, "true": true, "null": true},
+ builtin = parserConfig.builtin || {},
+ keywords = parserConfig.keywords || {},
+ operatorChars = parserConfig.operatorChars || /^[*+\-%<>!=&|~^]/,
+ support = parserConfig.support || {},
+ hooks = parserConfig.hooks || {},
+ dateSQL = parserConfig.dateSQL || {"date" : true, "time" : true, "timestamp" : true};
+
+ function tokenBase(stream, state) {
+ var ch = stream.next();
+
+ // call hooks from the mime type
+ if (hooks[ch]) {
+ var result = hooks[ch](stream, state);
+ if (result !== false) return result;
+ }
+
+ if (support.hexNumber == true &&
+ ((ch == "0" && stream.match(/^[xX][0-9a-fA-F]+/))
+ || (ch == "x" || ch == "X") && stream.match(/^'[0-9a-fA-F]+'/))) {
+ // hex
+ // ref: http://dev.mysql.com/doc/refman/5.5/en/hexadecimal-literals.html
+ return "number";
+ } else if (support.binaryNumber == true &&
+ (((ch == "b" || ch == "B") && stream.match(/^'[01]+'/))
+ || (ch == "0" && stream.match(/^b[01]+/)))) {
+ // bitstring
+ // ref: http://dev.mysql.com/doc/refman/5.5/en/bit-field-literals.html
+ return "number";
+ } else if (ch.charCodeAt(0) > 47 && ch.charCodeAt(0) < 58) {
+ // numbers
+ // ref: http://dev.mysql.com/doc/refman/5.5/en/number-literals.html
+ stream.match(/^[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?/);
+ support.decimallessFloat == true && stream.eat('.');
+ return "number";
+ } else if (ch == "?" && (stream.eatSpace() || stream.eol() || stream.eat(";"))) {
+ // placeholders
+ return "variable-3";
+ } else if (ch == "'" || (ch == '"' && support.doubleQuote)) {
+ // strings
+ // ref: http://dev.mysql.com/doc/refman/5.5/en/string-literals.html
+ state.tokenize = tokenLiteral(ch);
+ return state.tokenize(stream, state);
+ } else if ((((support.nCharCast == true && (ch == "n" || ch == "N"))
+ || (support.charsetCast == true && ch == "_" && stream.match(/[a-z][a-z0-9]*/i)))
+ && (stream.peek() == "'" || stream.peek() == '"'))) {
+ // charset casting: _utf8'str', N'str', n'str'
+ // ref: http://dev.mysql.com/doc/refman/5.5/en/string-literals.html
+ return "keyword";
+ } else if (/^[\(\),\;\[\]]/.test(ch)) {
+ // no highlightning
+ return null;
+ } else if (support.commentSlashSlash && ch == "/" && stream.eat("/")) {
+ // 1-line comment
+ stream.skipToEnd();
+ return "comment";
+ } else if ((support.commentHash && ch == "#")
+ || (ch == "-" && stream.eat("-") && (!support.commentSpaceRequired || stream.eat(" ")))) {
+ // 1-line comments
+ // ref: https://kb.askmonty.org/en/comment-syntax/
+ stream.skipToEnd();
+ return "comment";
+ } else if (ch == "/" && stream.eat("*")) {
+ // multi-line comments
+ // ref: https://kb.askmonty.org/en/comment-syntax/
+ state.tokenize = tokenComment;
+ return state.tokenize(stream, state);
+ } else if (ch == ".") {
+ // .1 for 0.1
+ if (support.zerolessFloat == true && stream.match(/^(?:\d+(?:e[+-]?\d+)?)/i)) {
+ return "number";
+ }
+ // .table_name (ODBC)
+ // // ref: http://dev.mysql.com/doc/refman/5.6/en/identifier-qualifiers.html
+ if (support.ODBCdotTable == true && stream.match(/^[a-zA-Z_]+/)) {
+ return "variable-2";
+ }
+ } else if (operatorChars.test(ch)) {
+ // operators
+ stream.eatWhile(operatorChars);
+ return null;
+ } else if (ch == '{' &&
+ (stream.match(/^( )*(d|D|t|T|ts|TS)( )*'[^']*'( )*}/) || stream.match(/^( )*(d|D|t|T|ts|TS)( )*"[^"]*"( )*}/))) {
+ // dates (weird ODBC syntax)
+ // ref: http://dev.mysql.com/doc/refman/5.5/en/date-and-time-literals.html
+ return "number";
+ } else {
+ stream.eatWhile(/^[_\w\d]/);
+ var word = stream.current().toLowerCase();
+ // dates (standard SQL syntax)
+ // ref: http://dev.mysql.com/doc/refman/5.5/en/date-and-time-literals.html
+ if (dateSQL.hasOwnProperty(word) && (stream.match(/^( )+'[^']*'/) || stream.match(/^( )+"[^"]*"/)))
+ return "number";
+ if (atoms.hasOwnProperty(word)) return "atom";
+ if (builtin.hasOwnProperty(word)) return "builtin";
+ if (keywords.hasOwnProperty(word)) return "keyword";
+ if (client.hasOwnProperty(word)) return "string-2";
+ return null;
+ }
+ }
+
+ // 'string', with char specified in quote escaped by '\'
+ function tokenLiteral(quote) {
+ return function(stream, state) {
+ var escaped = false, ch;
+ while ((ch = stream.next()) != null) {
+ if (ch == quote && !escaped) {
+ state.tokenize = tokenBase;
+ break;
+ }
+ escaped = !escaped && ch == "\\";
+ }
+ return "string";
+ };
+ }
+ function tokenComment(stream, state) {
+ while (true) {
+ if (stream.skipTo("*")) {
+ stream.next();
+ if (stream.eat("/")) {
+ state.tokenize = tokenBase;
+ break;
+ }
+ } else {
+ stream.skipToEnd();
+ break;
+ }
+ }
+ return "comment";
+ }
+
+ function pushContext(stream, state, type) {
+ state.context = {
+ prev: state.context,
+ indent: stream.indentation(),
+ col: stream.column(),
+ type: type
+ };
+ }
+
+ function popContext(state) {
+ state.indent = state.context.indent;
+ state.context = state.context.prev;
+ }
+
+ return {
+ startState: function() {
+ return {tokenize: tokenBase, context: null};
+ },
+
+ token: function(stream, state) {
+ if (stream.sol()) {
+ if (state.context && state.context.align == null)
+ state.context.align = false;
+ }
+ if (stream.eatSpace()) return null;
+
+ var style = state.tokenize(stream, state);
+ if (style == "comment") return style;
+
+ if (state.context && state.context.align == null)
+ state.context.align = true;
+
+ var tok = stream.current();
+ if (tok == "(")
+ pushContext(stream, state, ")");
+ else if (tok == "[")
+ pushContext(stream, state, "]");
+ else if (state.context && state.context.type == tok)
+ popContext(state);
+ return style;
+ },
+
+ indent: function(state, textAfter) {
+ var cx = state.context;
+ if (!cx) return CodeMirror.Pass;
+ if (cx.align) return cx.col + (textAfter.charAt(0) == cx.type ? 0 : 1);
+ else return cx.indent + config.indentUnit;
+ }
+ };
+});
+
+(function() {
+ "use strict";
+
+ // `identifier`
+ function hookIdentifier(stream) {
+ // MySQL/MariaDB identifiers
+ // ref: http://dev.mysql.com/doc/refman/5.6/en/identifier-qualifiers.html
+ var ch;
+ while ((ch = stream.next()) != null) {
+ if (ch == "`" && !stream.eat("`")) return "variable-2";
+ }
+ return null;
+ }
+
+ // variable token
+ function hookVar(stream) {
+ // variables
+ // @@prefix.varName @varName
+ // varName can be quoted with ` or ' or "
+ // ref: http://dev.mysql.com/doc/refman/5.5/en/user-variables.html
+ if (stream.eat("@")) {
+ stream.match(/^session\./);
+ stream.match(/^local\./);
+ stream.match(/^global\./);
+ }
+
+ if (stream.eat("'")) {
+ stream.match(/^.*'/);
+ return "variable-2";
+ } else if (stream.eat('"')) {
+ stream.match(/^.*"/);
+ return "variable-2";
+ } else if (stream.eat("`")) {
+ stream.match(/^.*`/);
+ return "variable-2";
+ } else if (stream.match(/^[0-9a-zA-Z$\.\_]+/)) {
+ return "variable-2";
+ }
+ return null;
+ };
+
+ // short client keyword token
+ function hookClient(stream) {
+ // \N means NULL
+ // ref: http://dev.mysql.com/doc/refman/5.5/en/null-values.html
+ if (stream.eat("N")) {
+ return "atom";
+ }
+ // \g, etc
+ // ref: http://dev.mysql.com/doc/refman/5.5/en/mysql-commands.html
+ return stream.match(/^[a-zA-Z.#!?]/) ? "variable-2" : null;
+ }
+
+ // these keywords are used by all SQL dialects (however, a mode can still overwrite it)
+ var sqlKeywords = "alter and as asc between by count create delete desc distinct drop from having in insert into is join like not on or order select set table union update values where ";
+
+ // turn a space-separated list into an array
+ function set(str) {
+ var obj = {}, words = str.split(" ");
+ for (var i = 0; i < words.length; ++i) obj[words[i]] = true;
+ return obj;
+ }
+
+ // A generic SQL Mode. It's not a standard, it just try to support what is generally supported
+ CodeMirror.defineMIME("text/x-sql", {
+ name: "sql",
+ keywords: set(sqlKeywords + "begin"),
+ builtin: set("bool boolean bit blob enum long longblob longtext medium mediumblob mediumint mediumtext time timestamp tinyblob tinyint tinytext text bigint int int1 int2 int3 int4 int8 integer float float4 float8 double char varbinary varchar varcharacter precision real date datetime year unsigned signed decimal numeric"),
+ atoms: set("false true null unknown"),
+ operatorChars: /^[*+\-%<>!=]/,
+ dateSQL: set("date time timestamp"),
+ support: set("ODBCdotTable doubleQuote binaryNumber hexNumber")
+ });
+
+ CodeMirror.defineMIME("text/x-mysql", {
+ name: "sql",
+ client: set("charset clear connect edit ego exit go help nopager notee nowarning pager print prompt quit rehash source status system tee"),
+ keywords: set(sqlKeywords + "accessible action add after algorithm all analyze asensitive at authors auto_increment autocommit avg avg_row_length before binary binlog both btree cache call cascade cascaded case catalog_name chain change changed character check checkpoint checksum class_origin client_statistics close coalesce code collate collation collations column columns comment commit committed completion concurrent condition connection consistent constraint contains continue contributors convert cross current_date current_time current_timestamp current_user cursor data database databases day_hour day_microsecond day_minute day_second deallocate dec declare default delay_key_write delayed delimiter des_key_file describe deterministic dev_pop dev_samp deviance directory disable discard distinctrow div dual dumpfile each elseif enable enclosed end ends engine engines enum errors escape escaped even event events every execute exists exit explain extended fast fetch field fields first flush for force foreign found_rows full fulltext function general global grant grants group groupby_concat handler hash help high_priority hosts hour_microsecond hour_minute hour_second if ignore ignore_server_ids import index index_statistics infile inner innodb inout insensitive insert_method install interval invoker isolation iterate key keys kill language last leading leave left level limit linear lines list load local localtime localtimestamp lock logs low_priority master master_heartbeat_period master_ssl_verify_server_cert masters match max max_rows maxvalue message_text middleint migrate min min_rows minute_microsecond minute_second mod mode modifies modify mutex mysql_errno natural next no no_write_to_binlog offline offset one online open optimize option optionally out outer outfile pack_keys parser partition partitions password phase plugin plugins prepare preserve prev primary privileges procedure processlist profile profiles purge query quick range read read_write reads real rebuild recover references regexp relaylog release remove rename reorganize repair repeatable replace require resignal restrict resume return returns revoke right rlike rollback rollup row row_format rtree savepoint schedule schema schema_name schemas second_microsecond security sensitive separator serializable server session share show signal slave slow smallint snapshot soname spatial specific sql sql_big_result sql_buffer_result sql_cache sql_calc_found_rows sql_no_cache sql_small_result sqlexception sqlstate sqlwarning ssl start starting starts status std stddev stddev_pop stddev_samp storage straight_join subclass_origin sum suspend table_name table_statistics tables tablespace temporary terminated to trailing transaction trigger triggers truncate uncommitted undo uninstall unique unlock upgrade usage use use_frm user user_resources user_statistics using utc_date utc_time utc_timestamp value variables varying view views warnings when while with work write xa xor year_month zerofill begin do then else loop repeat"),
+ builtin: set("bool boolean bit blob decimal double enum float long longblob longtext medium mediumblob mediumint mediumtext time timestamp tinyblob tinyint tinytext text bigint int int1 int2 int3 int4 int8 integer float float4 float8 double char varbinary varchar varcharacter precision date datetime year unsigned signed numeric"),
+ atoms: set("false true null unknown"),
+ operatorChars: /^[*+\-%<>!=&|^]/,
+ dateSQL: set("date time timestamp"),
+ support: set("ODBCdotTable decimallessFloat zerolessFloat binaryNumber hexNumber doubleQuote nCharCast charsetCast commentHash commentSpaceRequired"),
+ hooks: {
+ "@": hookVar,
+ "`": hookIdentifier,
+ "\\": hookClient
+ }
+ });
+
+ CodeMirror.defineMIME("text/x-mariadb", {
+ name: "sql",
+ client: set("charset clear connect edit ego exit go help nopager notee nowarning pager print prompt quit rehash source status system tee"),
+ keywords: set(sqlKeywords + "accessible action add after algorithm all always analyze asensitive at authors auto_increment autocommit avg avg_row_length before binary binlog both btree cache call cascade cascaded case catalog_name chain change changed character check checkpoint checksum class_origin client_statistics close coalesce code collate collation collations column columns comment commit committed completion concurrent condition connection consistent constraint contains continue contributors convert cross current_date current_time current_timestamp current_user cursor data database databases day_hour day_microsecond day_minute day_second deallocate dec declare default delay_key_write delayed delimiter des_key_file describe deterministic dev_pop dev_samp deviance directory disable discard distinctrow div dual dumpfile each elseif enable enclosed end ends engine engines enum errors escape escaped even event events every execute exists exit explain extended fast fetch field fields first flush for force foreign found_rows full fulltext function general generated global grant grants group groupby_concat handler hard hash help high_priority hosts hour_microsecond hour_minute hour_second if ignore ignore_server_ids import index index_statistics infile inner innodb inout insensitive insert_method install interval invoker isolation iterate key keys kill language last leading leave left level limit linear lines list load local localtime localtimestamp lock logs low_priority master master_heartbeat_period master_ssl_verify_server_cert masters match max max_rows maxvalue message_text middleint migrate min min_rows minute_microsecond minute_second mod mode modifies modify mutex mysql_errno natural next no no_write_to_binlog offline offset one online open optimize option optionally out outer outfile pack_keys parser partition partitions password persistent phase plugin plugins prepare preserve prev primary privileges procedure processlist profile profiles purge query quick range read read_write reads real rebuild recover references regexp relaylog release remove rename reorganize repair repeatable replace require resignal restrict resume return returns revoke right rlike rollback rollup row row_format rtree savepoint schedule schema schema_name schemas second_microsecond security sensitive separator serializable server session share show signal slave slow smallint snapshot soft soname spatial specific sql sql_big_result sql_buffer_result sql_cache sql_calc_found_rows sql_no_cache sql_small_result sqlexception sqlstate sqlwarning ssl start starting starts status std stddev stddev_pop stddev_samp storage straight_join subclass_origin sum suspend table_name table_statistics tables tablespace temporary terminated to trailing transaction trigger triggers truncate uncommitted undo uninstall unique unlock upgrade usage use use_frm user user_resources user_statistics using utc_date utc_time utc_timestamp value variables varying view views virtual warnings when while with work write xa xor year_month zerofill begin do then else loop repeat"),
+ builtin: set("bool boolean bit blob decimal double enum float long longblob longtext medium mediumblob mediumint mediumtext time timestamp tinyblob tinyint tinytext text bigint int int1 int2 int3 int4 int8 integer float float4 float8 double char varbinary varchar varcharacter precision date datetime year unsigned signed numeric"),
+ atoms: set("false true null unknown"),
+ operatorChars: /^[*+\-%<>!=&|^]/,
+ dateSQL: set("date time timestamp"),
+ support: set("ODBCdotTable decimallessFloat zerolessFloat binaryNumber hexNumber doubleQuote nCharCast charsetCast commentHash commentSpaceRequired"),
+ hooks: {
+ "@": hookVar,
+ "`": hookIdentifier,
+ "\\": hookClient
+ }
+ });
+
+ // the query language used by Apache Cassandra is called CQL, but this mime type
+ // is called Cassandra to avoid confusion with Contextual Query Language
+ CodeMirror.defineMIME("text/x-cassandra", {
+ name: "sql",
+ client: { },
+ keywords: set("use select from using consistency where limit first reversed first and in insert into values using consistency ttl update set delete truncate begin batch apply create keyspace with columnfamily primary key index on drop alter type add any one quorum all local_quorum each_quorum"),
+ builtin: set("ascii bigint blob boolean counter decimal double float int text timestamp uuid varchar varint"),
+ atoms: set("false true"),
+ operatorChars: /^[<>=]/,
+ dateSQL: { },
+ support: set("commentSlashSlash decimallessFloat"),
+ hooks: { }
+ });
+
+ // this is based on Peter Raganitsch's 'plsql' mode
+ CodeMirror.defineMIME("text/x-plsql", {
+ name: "sql",
+ client: set("appinfo arraysize autocommit autoprint autorecovery autotrace blockterminator break btitle cmdsep colsep compatibility compute concat copycommit copytypecheck define describe echo editfile embedded escape exec execute feedback flagger flush heading headsep instance linesize lno loboffset logsource long longchunksize markup native newpage numformat numwidth pagesize pause pno recsep recsepchar release repfooter repheader serveroutput shiftinout show showmode size spool sqlblanklines sqlcase sqlcode sqlcontinue sqlnumber sqlpluscompatibility sqlprefix sqlprompt sqlterminator suffix tab term termout time timing trimout trimspool ttitle underline verify version wrap"),
+ keywords: set("abort accept access add all alter and any array arraylen as asc assert assign at attributes audit authorization avg base_table begin between binary_integer body boolean by case cast char char_base check close cluster clusters colauth column comment commit compress connect connected constant constraint crash create current currval cursor data_base database date dba deallocate debugoff debugon decimal declare default definition delay delete desc digits dispose distinct do drop else elsif enable end entry escape exception exception_init exchange exclusive exists exit external fast fetch file for force form from function generic goto grant group having identified if immediate in increment index indexes indicator initial initrans insert interface intersect into is key level library like limited local lock log logging long loop master maxextents maxtrans member minextents minus mislabel mode modify multiset new next no noaudit nocompress nologging noparallel not nowait number_base object of off offline on online only open option or order out package parallel partition pctfree pctincrease pctused pls_integer positive positiven pragma primary prior private privileges procedure public raise range raw read rebuild record ref references refresh release rename replace resource restrict return returning reverse revoke rollback row rowid rowlabel rownum rows run savepoint schema segment select separate session set share snapshot some space split sql start statement storage subtype successful synonym tabauth table tables tablespace task terminate then to trigger truncate type union unique unlimited unrecoverable unusable update use using validate value values variable view views when whenever where while with work"),
+ functions: set("abs acos add_months ascii asin atan atan2 average bfilename ceil chartorowid chr concat convert cos cosh count decode deref dual dump dup_val_on_index empty error exp false floor found glb greatest hextoraw initcap instr instrb isopen last_day least lenght lenghtb ln lower lpad ltrim lub make_ref max min mod months_between new_time next_day nextval nls_charset_decl_len nls_charset_id nls_charset_name nls_initcap nls_lower nls_sort nls_upper nlssort no_data_found notfound null nvl others power rawtohex reftohex round rowcount rowidtochar rpad rtrim sign sin sinh soundex sqlcode sqlerrm sqrt stddev substr substrb sum sysdate tan tanh to_char to_date to_label to_multi_byte to_number to_single_byte translate true trunc uid upper user userenv variance vsize"),
+ builtin: set("bfile blob character clob dec float int integer mlslabel natural naturaln nchar nclob number numeric nvarchar2 real rowtype signtype smallint string varchar varchar2"),
+ operatorChars: /^[*+\-%<>!=~]/,
+ dateSQL: set("date time timestamp"),
+ support: set("doubleQuote nCharCast zerolessFloat binaryNumber hexNumber")
+ });
+}());
+
+/*
+ How Properties of Mime Types are used by SQL Mode
+ =================================================
+
+ keywords:
+ A list of keywords you want to be highlighted.
+ functions:
+ A list of function names you want to be highlighted.
+ builtin:
+ A list of builtin types you want to be highlighted (if you want types to be of class "builtin" instead of "keyword").
+ operatorChars:
+ All characters that must be handled as operators.
+ client:
+ Commands parsed and executed by the client (not the server).
+ support:
+ A list of supported syntaxes which are not common, but are supported by more than 1 DBMS.
+ * ODBCdotTable: .tableName
+ * zerolessFloat: .1
+ * doubleQuote
+ * nCharCast: N'string'
+ * charsetCast: _utf8'string'
+ * commentHash: use # char for comments
+ * commentSlashSlash: use // for comments
+ * commentSpaceRequired: require a space after -- for comments
+ atoms:
+ Keywords that must be highlighted as atoms,. Some DBMS's support more atoms than others:
+ UNKNOWN, INFINITY, UNDERFLOW, NaN...
+ dateSQL:
+ Used for date/time SQL standard syntax, because not all DBMS's support same temporal types.
+*/
diff --git a/js/common.js b/js/common.js
new file mode 100644
index 0000000000..468036d4c6
--- /dev/null
+++ b/js/common.js
@@ -0,0 +1,300 @@
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Functionality for communicating with the querywindow
+ */
+$(function () {
+ /**
+ * Event handler for click on the open query window link
+ * in the top menu of the navigation panel
+ */
+ $('#pma_open_querywindow').click(function (event) {
+ event.preventDefault();
+ PMA_querywindow.focus();
+ });
+});
+
+/**
+ * Holds common parameters such as server, db, table, etc
+ *
+ * The content for this is normally loaded from Header.class.php or
+ * Response.class.php and executed by ajax.js
+ */
+var PMA_commonParams = (function () {
+ /**
+ * @var hash params An associative array of key value pairs
+ * @access private
+ */
+ var params = {};
+ // The returned object is the public part of the module
+ return {
+ /**
+ * Saves all the key value pair that
+ * are provided in the input array
+ *
+ * @param hash obj The input array
+ *
+ * @return void
+ */
+ setAll: function (obj) {
+ var reload = false;
+ var updateNavigation = false;
+ for (var i in obj) {
+ if (params[i] !== undefined && params[i] !== obj[i]) {
+ reload = true;
+ if (i == 'db' || i == 'table') {
+ updateNavigation = true;
+ }
+ }
+ params[i] = obj[i];
+ }
+ if (updateNavigation) {
+ PMA_showCurrentNavigation();
+ }
+ if (reload) {
+ PMA_querywindow.refresh();
+ }
+ },
+ /**
+ * Retrieves a value given its key
+ * Returns empty string for undefined values
+ *
+ * @param string name The key
+ *
+ * @return string
+ */
+ get: function (name) {
+ return params[name] || '';
+ },
+ /**
+ * Saves a single key value pair
+ *
+ * @param string name The key
+ * @param string value The value
+ *
+ * @return self For chainability
+ */
+ set: function (name, value) {
+ var updateNavigation = false;
+ if (params[name] !== undefined && params[name] !== value) {
+ PMA_querywindow.refresh();
+ if (name == 'db' || name == 'table') {
+ updateNavigation = true;
+ }
+ }
+ params[name] = value;
+ if (updateNavigation) {
+ PMA_showCurrentNavigation();
+ }
+ return this;
+ },
+ /**
+ * Returns the url query string using the saved parameters
+ *
+ * @return string
+ */
+ getUrlQuery: function () {
+ return $.sprintf(
+ '?%s&server=%s&db=%s&table=%s',
+ this.get('common_query'),
+ encodeURIComponent(this.get('server')),
+ encodeURIComponent(this.get('db')),
+ encodeURIComponent(this.get('table'))
+ );
+ }
+ };
+})();
+
+/**
+ * Holds common parameters such as server, db, table, etc
+ *
+ * The content for this is normally loaded from Header.class.php or
+ * Response.class.php and executed by ajax.js
+ */
+var PMA_commonActions = {
+ /**
+ * Saves the database name when it's changed
+ * and reloads the query window, if necessary
+ *
+ * @param string new_db The name of the new database
+ *
+ * @return void
+ */
+ setDb: function (new_db) {
+ if (new_db != PMA_commonParams.get('db')) {
+ PMA_commonParams.setAll({'db': new_db, 'table': ''});
+ }
+ },
+ /**
+ * Opens a database in the main part of the page
+ *
+ * @param string new_db The name of the new database
+ *
+ * @return void
+ */
+ openDb: function (new_db) {
+ PMA_commonParams
+ .set('db', new_db)
+ .set('table', '');
+ PMA_querywindow.refresh();
+ this.refreshMain(
+ PMA_commonParams.get('opendb_url')
+ );
+ },
+ /**
+ * Refreshes the main frame
+ *
+ * @param mixed url Undefined to refresh to the same page
+ * String to go to a different page, e.g: 'index.php'
+ *
+ * @return void
+ */
+ refreshMain: function (url, callback) {
+ if (! url) {
+ url = $('#selflink a').attr('href');
+ url = url.substring(0, url.indexOf('?'));
+ }
+ url += PMA_commonParams.getUrlQuery();
+ $('<a />', {href: url})
+ .appendTo('body')
+ .click()
+ .remove();
+ AJAX._callback = callback;
+ }
+};
+
+/**
+ * Common functions used for communicating with the querywindow
+ */
+var PMA_querywindow = (function ($, window) {
+ /**
+ * @var Object querywindow Reference to the window
+ * object of the querywindow
+ * @access private
+ */
+ var querywindow = {};
+ /**
+ * @var string queryToLoad Stores the SQL query that is to be displayed
+ * in the querywindow when it is ready
+ * @access private
+ */
+ var queryToLoad = '';
+ // The returned object is the public part of the module
+ return {
+ /**
+ * Opens the query window
+ *
+ * @param mixed url Undefined to open the default page
+ * String to go to a different
+ *
+ * @return void
+ */
+ open: function (url, sql_query) {
+ if (! url) {
+ url = 'querywindow.php' + PMA_commonParams.getUrlQuery();
+ }
+ if (sql_query) {
+ url += '&sql_query=' + encodeURIComponent(sql_query);
+ }
+
+ if (! querywindow.closed && querywindow.location) {
+ var href = querywindow.location.href;
+ if (href != url &&
+ href != PMA_commonParams.get('pma_absolute_uri') + url
+ ) {
+ if (PMA_commonParams.get('safari_browser')) {
+ querywindow.location.href = targeturl;
+ } else {
+ querywindow.location.replace(targeturl);
+ }
+ querywindow.focus();
+ }
+ } else {
+ querywindow = window.open(
+ url + '&init=1',
+ '',
+ 'toolbar=0,location=0,directories=0,status=1,' +
+ 'menubar=0,scrollbars=yes,resizable=yes,' +
+ 'width=' + PMA_commonParams.get('querywindow_width') + ',' +
+ 'height=' + PMA_commonParams.get('querywindow_height')
+ );
+ }
+ if (! querywindow.opener) {
+ querywindow.opener = window.window;
+ }
+ if (window.focus) {
+ querywindow.focus();
+ }
+ },
+ /**
+ * Opens, if necessary, focuses the query window
+ * and displays an SQL query.
+ *
+ * @param string sql_query The SQL query to display in
+ * the query window
+ *
+ * @return void
+ */
+ focus: function (sql_query) {
+ if (! querywindow || querywindow.closed || ! querywindow.location) {
+ // we need first to open the window and cannot pass the query with it
+ // as we dont know if the query exceeds max url length
+ queryToLoad = sql_query;
+ this.open(false, sql_query);
+ } else {
+ //var querywindow = querywindow;
+ var hiddenqueryform = querywindow
+ .document
+ .getElementById('hiddenqueryform');
+ if (hiddenqueryform.querydisplay_tab != 'sql') {
+ hiddenqueryform.querydisplay_tab.value = "sql";
+ hiddenqueryform.sql_query.value = sql_query;
+ $(hiddenqueryform).addClass('disableAjax');
+ hiddenqueryform.submit();
+ querywindow.focus();
+ } else {
+ querywindow.focus();
+ }
+ }
+ },
+ /**
+ * Refreshes the query window given a url
+ *
+ * @param string url Where to go to
+ *
+ * @return void
+ */
+ refresh: function (url) {
+ if (! querywindow.closed && querywindow.location) {
+ var $form = $(querywindow.document).find('#sqlqueryform');
+ if ($form.find('#checkbox_lock:checked').length === 0) {
+ PMA_querywindow.open(url);
+ }
+ }
+ },
+ /**
+ * Reloads the query window given the details
+ * of a db, a table and an sql_query
+ *
+ * @param string db The name of the database
+ * @param string table The name of the table
+ * @param string sql_query The SQL query to be displayed
+ *
+ * @return void
+ */
+ reload: function (db, table, sql_query) {
+ if (! querywindow.closed && querywindow.location) {
+ var $form = $(querywindow.document).find('#sqlqueryform');
+ if ($form.find('#checkbox_lock:checked').length === 0) {
+ var $hiddenform = $(querywindow.document)
+ .find('#hiddenqueryform');
+ $hiddenform.find('input[name=db]').val(db);
+ $hiddenform.find('input[name=table]').val(table);
+ if (sql_query) {
+ $hiddenform.find('input[name=sql_query]').val(sql_query);
+ }
+ $hiddenform.addClass('disableAjax').submit();
+ }
+ }
+ }
+ };
+})(jQuery, window);
diff --git a/js/config.js b/js/config.js
new file mode 100644
index 0000000000..9113c850ae
--- /dev/null
+++ b/js/config.js
@@ -0,0 +1,798 @@
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Functions used in configuration forms and on user preferences pages
+ */
+
+/**
+ * Unbind all event handlers before tearing down a page
+ */
+AJAX.registerTeardown('config.js', function () {
+ $('input[id], select[id], textarea[id]').unbind('change').unbind('keyup');
+ $('input[type=button][name=submit_reset]').unbind('click');
+ $('div.tabs_contents').undelegate();
+ $('#import_local_storage, #export_local_storage').unbind('click');
+ $('form.prefs-form').unbind('change').unbind('submit');
+ $('div.click-hide-message').die('click');
+ $('#prefs_autoload').find('a').unbind('click');
+});
+
+AJAX.registerOnload('config.js', function () {
+ $('#topmenu2').find('li.active a').attr('rel', 'samepage');
+ $('#topmenu2').find('li:not(.active) a').attr('rel', 'newpage');
+});
+
+// default values for fields
+var defaultValues = {};
+
+/**
+ * Returns field type
+ *
+ * @param {Element} field
+ */
+function getFieldType(field)
+{
+ field = $(field);
+ var tagName = field.prop('tagName');
+ if (tagName == 'INPUT') {
+ return field.attr('type');
+ } else if (tagName == 'SELECT') {
+ return 'select';
+ } else if (tagName == 'TEXTAREA') {
+ return 'text';
+ }
+ return '';
+}
+
+/**
+ * Sets field value
+ *
+ * value must be of type:
+ * o undefined (omitted) - restore default value (form default, not PMA default)
+ * o String - if field_type is 'text'
+ * o boolean - if field_type is 'checkbox'
+ * o Array of values - if field_type is 'select'
+ *
+ * @param {Element} field
+ * @param {String} field_type see {@link #getFieldType}
+ * @param {String|Boolean} [value]
+ */
+function setFieldValue(field, field_type, value)
+{
+ field = $(field);
+ switch (field_type) {
+ case 'text':
+ case 'number':
+ //TODO: replace to .val()
+ field.attr('value', (value !== undefined ? value : field.attr('defaultValue')));
+ break;
+ case 'checkbox':
+ //TODO: replace to .prop()
+ field.attr('checked', (value !== undefined ? value : field.attr('defaultChecked')));
+ break;
+ case 'select':
+ var options = field.prop('options');
+ var i, imax = options.length;
+ if (value === undefined) {
+ for (i = 0; i < imax; i++) {
+ options[i].selected = options[i].defaultSelected;
+ }
+ } else {
+ for (i = 0; i < imax; i++) {
+ options[i].selected = (value.indexOf(options[i].value) != -1);
+ }
+ }
+ break;
+ }
+ markField(field);
+}
+
+/**
+ * Gets field value
+ *
+ * Will return one of:
+ * o String - if type is 'text'
+ * o boolean - if type is 'checkbox'
+ * o Array of values - if type is 'select'
+ *
+ * @param {Element} field
+ * @param {String} field_type returned by {@link #getFieldType}
+ * @type Boolean|String|String[]
+ */
+function getFieldValue(field, field_type)
+{
+ field = $(field);
+ switch (field_type) {
+ case 'text':
+ case 'number':
+ return field.prop('value');
+ case 'checkbox':
+ return field.prop('checked');
+ case 'select':
+ var options = field.prop('options');
+ var i, imax = options.length, items = [];
+ for (i = 0; i < imax; i++) {
+ if (options[i].selected) {
+ items.push(options[i].value);
+ }
+ }
+ return items;
+ }
+ return null;
+}
+
+/**
+ * Returns values for all fields in fieldsets
+ */
+function getAllValues()
+{
+ var elements = $('fieldset input, fieldset select, fieldset textarea');
+ var values = {};
+ var type, value;
+ for (var i = 0; i < elements.length; i++) {
+ type = getFieldType(elements[i]);
+ value = getFieldValue(elements[i], type);
+ if (typeof value != 'undefined') {
+ // we only have single selects, fatten array
+ if (type == 'select') {
+ value = value[0];
+ }
+ values[elements[i].name] = value;
+ }
+ }
+ return values;
+}
+
+/**
+ * Checks whether field has its default value
+ *
+ * @param {Element} field
+ * @param {String} type
+ * @return boolean
+ */
+function checkFieldDefault(field, type)
+{
+ field = $(field);
+ var field_id = field.attr('id');
+ if (typeof defaultValues[field_id] == 'undefined') {
+ return true;
+ }
+ var isDefault = true;
+ var currentValue = getFieldValue(field, type);
+ if (type != 'select') {
+ isDefault = currentValue == defaultValues[field_id];
+ } else {
+ // compare arrays, will work for our representation of select values
+ if (currentValue.length != defaultValues[field_id].length) {
+ isDefault = false;
+ }
+ else {
+ for (var i = 0; i < currentValue.length; i++) {
+ if (currentValue[i] != defaultValues[field_id][i]) {
+ isDefault = false;
+ break;
+ }
+ }
+ }
+ }
+ return isDefault;
+}
+
+/**
+ * Returns element's id prefix
+ * @param {Element} element
+ */
+function getIdPrefix(element)
+{
+ return $(element).attr('id').replace(/[^-]+$/, '');
+}
+
+// ------------------------------------------------------------------
+// Form validation and field operations
+//
+
+// form validator assignments
+var validate = {};
+
+// form validator list
+var validators = {
+ // regexp: numeric value
+ _regexp_numeric: /^[0-9]+$/,
+ // regexp: extract parts from PCRE expression
+ _regexp_pcre_extract: /(.)(.*)\1(.*)?/,
+ /**
+ * Validates positive number
+ *
+ * @param {boolean} isKeyUp
+ */
+ PMA_validatePositiveNumber: function (isKeyUp) {
+ if (isKeyUp && this.value === '') {
+ return true;
+ }
+ var result = this.value != '0' && validators._regexp_numeric.test(this.value);
+ return result ? true : PMA_messages.error_nan_p;
+ },
+ /**
+ * Validates non-negative number
+ *
+ * @param {boolean} isKeyUp
+ */
+ PMA_validateNonNegativeNumber: function (isKeyUp) {
+ if (isKeyUp && this.value === '') {
+ return true;
+ }
+ var result = validators._regexp_numeric.test(this.value);
+ return result ? true : PMA_messages.error_nan_nneg;
+ },
+ /**
+ * Validates port number
+ *
+ * @param {boolean} isKeyUp
+ */
+ PMA_validatePortNumber: function (isKeyUp) {
+ if (this.value === '') {
+ return true;
+ }
+ var result = validators._regexp_numeric.test(this.value) && this.value != '0';
+ return result && this.value <= 65535 ? true : PMA_messages.error_incorrect_port;
+ },
+ /**
+ * Validates value according to given regular expression
+ *
+ * @param {boolean} isKeyUp
+ * @param {string} regexp
+ */
+ PMA_validateByRegex: function (isKeyUp, regexp) {
+ if (isKeyUp && this.value === '') {
+ return true;
+ }
+ // convert PCRE regexp
+ var parts = regexp.match(validators._regexp_pcre_extract);
+ var valid = this.value.match(new RegExp(parts[2], parts[3])) !== null;
+ return valid ? true : PMA_messages.error_invalid_value;
+ },
+ /**
+ * Validates upper bound for numeric inputs
+ *
+ * @param {boolean} isKeyUp
+ * @param {int} max_value
+ */
+ PMA_validateUpperBound: function (isKeyUp, max_value) {
+ var val = parseInt(this.value, 10);
+ if (isNaN(val)) {
+ return true;
+ }
+ return val <= max_value ? true : $.sprintf(PMA_messages.error_value_lte, max_value);
+ },
+ // field validators
+ _field: {
+ },
+ // fieldset validators
+ _fieldset: {
+ }
+};
+
+/**
+ * Registers validator for given field
+ *
+ * @param {String} id field id
+ * @param {String} type validator (key in validators object)
+ * @param {boolean} onKeyUp whether fire on key up
+ * @param {Array} params validation function parameters
+ */
+function validateField(id, type, onKeyUp, params)
+{
+ if (typeof validators[type] == 'undefined') {
+ return;
+ }
+ if (typeof validate[id] == 'undefined') {
+ validate[id] = [];
+ }
+ validate[id].push([type, params, onKeyUp]);
+}
+
+/**
+ * Returns valdiation functions associated with form field
+ *
+ * @param {String} field_id form field id
+ * @param {boolean} onKeyUpOnly see validateField
+ * @type Array
+ * @return array of [function, paramseters to be passed to function]
+ */
+function getFieldValidators(field_id, onKeyUpOnly)
+{
+ // look for field bound validator
+ var name = field_id.match(/[^-]+$/)[0];
+ if (typeof validators._field[name] != 'undefined') {
+ return [[validators._field[name], null]];
+ }
+
+ // look for registered validators
+ var functions = [];
+ if (typeof validate[field_id] != 'undefined') {
+ // validate[field_id]: array of [type, params, onKeyUp]
+ for (var i = 0, imax = validate[field_id].length; i < imax; i++) {
+ if (onKeyUpOnly && !validate[field_id][i][2]) {
+ continue;
+ }
+ functions.push([validators[validate[field_id][i][0]], validate[field_id][i][1]]);
+ }
+ }
+
+ return functions;
+}
+
+/**
+ * Displays errors for given form fields
+ *
+ * WARNING: created DOM elements must be identical with the ones made by
+ * display_input() in FormDisplay.tpl.php!
+ *
+ * @param {Object} error_list list of errors in the form {field id: error array}
+ */
+function displayErrors(error_list)
+{
+ for (var field_id in error_list) {
+ var errors = error_list[field_id];
+ var field = $('#' + field_id);
+ var isFieldset = field.attr('tagName') == 'FIELDSET';
+ var errorCnt;
+ if (isFieldset) {
+ errorCnt = field.find('dl.errors');
+ } else {
+ errorCnt = field.siblings('.inline_errors');
+ }
+
+ // remove empty errors (used to clear error list)
+ errors = $.grep(errors, function (item) {
+ return item !== '';
+ });
+
+ // CSS error class
+ if (!isFieldset) {
+ // checkboxes uses parent <span> for marking
+ var fieldMarker = (field.attr('type') == 'checkbox') ? field.parent() : field;
+ fieldMarker[errors.length ? 'addClass' : 'removeClass']('field-error');
+ }
+
+ if (errors.length) {
+ // if error container doesn't exist, create it
+ if (errorCnt.length === 0) {
+ if (isFieldset) {
+ errorCnt = $('<dl class="errors" />');
+ field.find('table').before(errorCnt);
+ } else {
+ errorCnt = $('<dl class="inline_errors" />');
+ field.closest('td').append(errorCnt);
+ }
+ }
+
+ var html = '';
+ for (var i = 0, imax = errors.length; i < imax; i++) {
+ html += '<dd>' + errors[i] + '</dd>';
+ }
+ errorCnt.html(html);
+ } else if (errorCnt !== null) {
+ // remove useless error container
+ errorCnt.remove();
+ }
+ }
+}
+
+/**
+ * Validates fieldset and puts errors in 'errors' object
+ *
+ * @param {Element} fieldset
+ * @param {boolean} isKeyUp
+ * @param {Object} errors
+ */
+function validate_fieldset(fieldset, isKeyUp, errors)
+{
+ fieldset = $(fieldset);
+ if (fieldset.length && typeof validators._fieldset[fieldset.attr('id')] != 'undefined') {
+ var fieldset_errors = validators._fieldset[fieldset.attr('id')].apply(fieldset[0], [isKeyUp]);
+ for (var field_id in fieldset_errors) {
+ if (typeof errors[field_id] == 'undefined') {
+ errors[field_id] = [];
+ }
+ if (typeof fieldset_errors[field_id] == 'string') {
+ fieldset_errors[field_id] = [fieldset_errors[field_id]];
+ }
+ $.merge(errors[field_id], fieldset_errors[field_id]);
+ }
+ }
+}
+
+/**
+ * Validates form field and puts errors in 'errors' object
+ *
+ * @param {Element} field
+ * @param {boolean} isKeyUp
+ * @param {Object} errors
+ */
+function validate_field(field, isKeyUp, errors)
+{
+ var args, result;
+ field = $(field);
+ var field_id = field.attr('id');
+ errors[field_id] = [];
+ var functions = getFieldValidators(field_id, isKeyUp);
+ for (var i = 0; i < functions.length; i++) {
+ if (typeof functions[i][1] !== 'undefined' && functions[i][1] !== null) {
+ args = functions[i][1].slice(0);
+ } else {
+ args = [];
+ }
+ args.unshift(isKeyUp);
+ result = functions[i][0].apply(field[0], args);
+ if (result !== true) {
+ if (typeof result == 'string') {
+ result = [result];
+ }
+ $.merge(errors[field_id], result);
+ }
+ }
+}
+
+/**
+ * Validates form field and parent fieldset
+ *
+ * @param {Element} field
+ * @param {boolean} isKeyUp
+ */
+function validate_field_and_fieldset(field, isKeyUp)
+{
+ field = $(field);
+ var errors = {};
+ validate_field(field, isKeyUp, errors);
+ validate_fieldset(field.closest('fieldset'), isKeyUp, errors);
+ displayErrors(errors);
+}
+
+/**
+ * Marks field depending on its value (system default or custom)
+ *
+ * @param {Element} field
+ */
+function markField(field)
+{
+ field = $(field);
+ var type = getFieldType(field);
+ var isDefault = checkFieldDefault(field, type);
+
+ // checkboxes uses parent <span> for marking
+ var fieldMarker = (type == 'checkbox') ? field.parent() : field;
+ setRestoreDefaultBtn(field, !isDefault);
+ fieldMarker[isDefault ? 'removeClass' : 'addClass']('custom');
+}
+
+/**
+ * Enables or disables the "restore default value" button
+ *
+ * @param {Element} field
+ * @param {boolean} display
+ */
+function setRestoreDefaultBtn(field, display)
+{
+ var el = $(field).closest('td').find('.restore-default img');
+ el[display ? 'show' : 'hide']();
+}
+
+AJAX.registerOnload('config.js', function () {
+ // register validators and mark custom values
+ var elements = $('input[id], select[id], textarea[id]');
+ $('input[id], select[id], textarea[id]').each(function () {
+ markField(this);
+ var el = $(this);
+ el.bind('change', function () {
+ validate_field_and_fieldset(this, false);
+ markField(this);
+ });
+ var tagName = el.attr('tagName');
+ // text fields can be validated after each change
+ if (tagName == 'INPUT' && el.attr('type') == 'text') {
+ el.keyup(function () {
+ validate_field_and_fieldset(el, true);
+ markField(el);
+ });
+ }
+ // disable textarea spellcheck
+ if (tagName == 'TEXTAREA') {
+ el.attr('spellcheck', false);
+ }
+ });
+
+ // check whether we've refreshed a page and browser remembered modified
+ // form values
+ var check_page_refresh = $('#check_page_refresh');
+ if (check_page_refresh.length === 0 || check_page_refresh.val() == '1') {
+ // run all field validators
+ var errors = {};
+ for (var i = 0; i < elements.length; i++) {
+ validate_field(elements[i], false, errors);
+ }
+ // run all fieldset validators
+ $('fieldset').each(function () {
+ validate_fieldset(this, false, errors);
+ });
+
+ displayErrors(errors);
+ } else if (check_page_refresh) {
+ check_page_refresh.val('1');
+ }
+});
+
+//
+// END: Form validation and field operations
+// ------------------------------------------------------------------
+
+// ------------------------------------------------------------------
+// Tabbed forms
+//
+
+/**
+ * Sets active tab
+ *
+ * @param {String} tab_id
+ */
+function setTab(tab_id)
+{
+ $('ul.tabs li').removeClass('active').find('a[href=#' + tab_id + ']').parent().addClass('active');
+ $('div.tabs_contents fieldset').hide().filter('#' + tab_id).show();
+ location.hash = 'tab_' + tab_id;
+ $('form.config-form input[name=tab_hash]').val(location.hash);
+}
+
+AJAX.registerOnload('config.js', function () {
+ var tabs = $('ul.tabs');
+ if (!tabs.length) {
+ return;
+ }
+ // add tabs events and activate one tab (the first one or indicated by location hash)
+ tabs.find('a')
+ .click(function (e) {
+ e.preventDefault();
+ setTab($(this).attr('href').substr(1));
+ })
+ .filter(':first')
+ .parent()
+ .addClass('active');
+ $('div.tabs_contents fieldset').hide().filter(':first').show();
+
+ // tab links handling, check each 200ms
+ // (works with history in FF, further browser support here would be an overkill)
+ var prev_hash;
+ var tab_check_fnc = function () {
+ if (location.hash != prev_hash) {
+ prev_hash = location.hash;
+ if (location.hash.match(/^#tab_.+/) && $('#' + location.hash.substr(5)).length) {
+ setTab(location.hash.substr(5));
+ }
+ }
+ };
+ tab_check_fnc();
+ setInterval(tab_check_fnc, 200);
+});
+
+//
+// END: Tabbed forms
+// ------------------------------------------------------------------
+
+// ------------------------------------------------------------------
+// Form reset buttons
+//
+
+AJAX.registerOnload('config.js', function () {
+ $('input[type=button][name=submit_reset]').click(function () {
+ var fields = $(this).closest('fieldset').find('input, select, textarea');
+ for (var i = 0, imax = fields.length; i < imax; i++) {
+ setFieldValue(fields[i], getFieldType(fields[i]));
+ }
+ });
+});
+
+//
+// END: Form reset buttons
+// ------------------------------------------------------------------
+
+// ------------------------------------------------------------------
+// "Restore default" and "set value" buttons
+//
+
+/**
+ * Restores field's default value
+ *
+ * @param {String} field_id
+ */
+function restoreField(field_id)
+{
+ var field = $('#' + field_id);
+ if (field.length === 0 || defaultValues[field_id] === undefined) {
+ return;
+ }
+ setFieldValue(field, getFieldType(field), defaultValues[field_id]);
+}
+
+AJAX.registerOnload('config.js', function () {
+ $('div.tabs_contents')
+ .delegate('.restore-default, .set-value', 'mouseenter', function () {
+ $(this).css('opacity', 1);
+ })
+ .delegate('.restore-default, .set-value', 'mouseleave', function () {
+ $(this).css('opacity', 0.25);
+ })
+ .delegate('.restore-default, .set-value', 'click', function (e) {
+ e.preventDefault();
+ var href = $(this).attr('href');
+ var field_sel;
+ if ($(this).hasClass('restore-default')) {
+ field_sel = href;
+ restoreField(field_sel.substr(1));
+ } else {
+ field_sel = href.match(/^[^=]+/)[0];
+ var value = href.match(/\=(.+)$/)[1];
+ setFieldValue($(field_sel), 'text', value);
+ }
+ $(field_sel).trigger('change');
+ })
+ .find('.restore-default, .set-value')
+ // inline-block for IE so opacity inheritance works
+ .css({display: 'inline-block', opacity: 0.25});
+});
+
+//
+// END: "Restore default" and "set value" buttons
+// ------------------------------------------------------------------
+
+// ------------------------------------------------------------------
+// User preferences import/export
+//
+
+AJAX.registerOnload('config.js', function () {
+ offerPrefsAutoimport();
+ var radios = $('#import_local_storage, #export_local_storage');
+ if (!radios.length) {
+ return;
+ }
+
+ // enable JavaScript dependent fields
+ radios
+ .prop('disabled', false)
+ .add('#export_text_file, #import_text_file')
+ .click(function () {
+ var enable_id = $(this).attr('id');
+ var disable_id;
+ if (enable_id.match(/local_storage$/)) {
+ disable_id = enable_id.replace(/local_storage$/, 'text_file');
+ } else {
+ disable_id = enable_id.replace(/text_file$/, 'local_storage');
+ }
+ $('#opts_' + disable_id).addClass('disabled').find('input').prop('disabled', true);
+ $('#opts_' + enable_id).removeClass('disabled').find('input').prop('disabled', false);
+ });
+
+ // detect localStorage state
+ var ls_supported = window.localStorage || false;
+ var ls_exists = ls_supported ? (window.localStorage.config || false) : false;
+ $('div.localStorage-' + (ls_supported ? 'un' : '') + 'supported').hide();
+ $('div.localStorage-' + (ls_exists ? 'empty' : 'exists')).hide();
+ if (ls_exists) {
+ updatePrefsDate();
+ }
+ $('form.prefs-form').change(function () {
+ var form = $(this);
+ var disabled = false;
+ if (!ls_supported) {
+ disabled = form.find('input[type=radio][value$=local_storage]').prop('checked');
+ } else if (!ls_exists && form.attr('name') == 'prefs_import' &&
+ $('#import_local_storage')[0].checked
+ ) {
+ disabled = true;
+ }
+ form.find('input[type=submit]').prop('disabled', disabled);
+ }).submit(function (e) {
+ var form = $(this);
+ if (form.attr('name') == 'prefs_export' && $('#export_local_storage')[0].checked) {
+ e.preventDefault();
+ // use AJAX to read JSON settings and save them
+ savePrefsToLocalStorage(form);
+ } else if (form.attr('name') == 'prefs_import' && $('#import_local_storage')[0].checked) {
+ // set 'json' input and submit form
+ form.find('input[name=json]').val(window.localStorage['config']);
+ }
+ });
+
+ $('div.click-hide-message').live('click', function () {
+ $(this)
+ .hide()
+ .parent('.group')
+ .css('height', '')
+ .next('form')
+ .show();
+ });
+});
+
+/**
+ * Saves user preferences to localStorage
+ *
+ * @param {Element} form
+ */
+function savePrefsToLocalStorage(form)
+{
+ form = $(form);
+ var submit = form.find('input[type=submit]');
+ submit.prop('disabled', true);
+ $.ajax({
+ url: 'prefs_manage.php',
+ cache: false,
+ type: 'POST',
+ data: {
+ ajax_request: true,
+ server: form.find('input[name=server]').val(),
+ token: form.find('input[name=token]').val(),
+ submit_get_json: true
+ },
+ success: function (data) {
+ if (data.success === true) {
+ window.localStorage['config'] = data.prefs;
+ window.localStorage['config_mtime'] = data.mtime;
+ window.localStorage['config_mtime_local'] = (new Date()).toUTCString();
+ updatePrefsDate();
+ $('div.localStorage-empty').hide();
+ $('div.localStorage-exists').show();
+ var group = form.parent('.group');
+ group.css('height', group.height() + 'px');
+ form.hide('fast');
+ form.prev('.click-hide-message').show('fast');
+ } else {
+ PMA_ajaxShowMessage(data.error);
+ }
+ },
+ complete: function () {
+ submit.prop('disabled', false);
+ }
+ });
+}
+
+/**
+ * Updates preferences timestamp in Import form
+ */
+function updatePrefsDate()
+{
+ var d = new Date(window.localStorage['config_mtime_local']);
+ var msg = PMA_messages.strSavedOn.replace(
+ '@DATE@',
+ PMA_formatDateTime(d)
+ );
+ $('#opts_import_local_storage div.localStorage-exists').html(msg);
+}
+
+/**
+ * Prepares message which informs that localStorage preferences are available and can be imported
+ */
+function offerPrefsAutoimport()
+{
+ var has_config = (window.localStorage || false) && (window.localStorage['config'] || false);
+ var cnt = $('#prefs_autoload');
+ if (!cnt.length || !has_config) {
+ return;
+ }
+ cnt.find('a').click(function (e) {
+ e.preventDefault();
+ var a = $(this);
+ if (a.attr('href') == '#no') {
+ cnt.remove();
+ $.post('index.php', {
+ token: cnt.find('input[name=token]').val(),
+ prefs_autoload: 'hide'
+ });
+ return;
+ }
+ cnt.find('input[name=json]').val(window.localStorage['config']);
+ cnt.find('form').submit();
+ });
+ cnt.show();
+}
+
+//
+// END: User preferences import/export
+// ------------------------------------------------------------------
diff --git a/js/cross_framing_protection.js b/js/cross_framing_protection.js
new file mode 100644
index 0000000000..5952ab7130
--- /dev/null
+++ b/js/cross_framing_protection.js
@@ -0,0 +1,9 @@
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Conditionally included if framing is not allowed
+ */
+if (self == top) {
+ document.documentElement.style.display = 'block';
+} else {
+ top.location = self.location;
+}
diff --git a/js/db_operations.js b/js/db_operations.js
new file mode 100644
index 0000000000..763040bc4f
--- /dev/null
+++ b/js/db_operations.js
@@ -0,0 +1,114 @@
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * @fileoverview function used in server privilege pages
+ * @name Database Operations
+ *
+ * @requires jQuery
+ * @requires jQueryUI
+ * @requires js/functions.js
+ *
+ */
+
+/**
+ * Ajax event handlers here for db_operations.php
+ *
+ * Actions Ajaxified here:
+ * Rename Database
+ * Copy Database
+ * Change charset
+ */
+
+/**
+ * Unbind all event handlers before tearing down a page
+ */
+AJAX.registerTeardown('db_operations.js', function () {
+ $("#rename_db_form.ajax").die('submit');
+ $("#copy_db_form.ajax").die('submit');
+ $("#change_db_charset_form.ajax").die('submit');
+});
+
+AJAX.registerOnload('db_operations.js', function () {
+
+ /**
+ * Ajax event handlers for 'Rename Database'
+ */
+ $("#rename_db_form.ajax").live('submit', function (event) {
+ event.preventDefault();
+
+ var $form = $(this);
+
+ var question = escapeHtml('CREATE DATABASE ' + $('#new_db_name').val() + ' / DROP DATABASE ' + PMA_commonParams.get('db'));
+
+ PMA_prepareForAjaxRequest($form);
+
+ $form.PMA_confirm(question, $form.attr('action'), function (url) {
+ PMA_ajaxShowMessage(PMA_messages.strRenamingDatabases, false);
+ $.get(url, $("#rename_db_form").serialize() + '&is_js_confirmed=1', function (data) {
+ if (data.success === true) {
+ PMA_ajaxShowMessage(data.message);
+ PMA_commonParams.set('db', data.newname);
+
+ PMA_reloadNavigation(function () {
+ $('#pma_navigation_tree')
+ .find("a:not('.expander')")
+ .each(function (index) {
+ var $thisAnchor = $(this);
+ if ($thisAnchor.text() == data.newname) {
+ // simulate a click on the new db name
+ // in navigation
+ $thisAnchor.trigger('click');
+ }
+ });
+ });
+ } else {
+ PMA_ajaxShowMessage(data.error, false);
+ }
+ }); // end $.get()
+ });
+ }); // end Rename Database
+
+ /**
+ * Ajax Event Handler for 'Copy Database'
+ */
+ $("#copy_db_form.ajax").live('submit', function (event) {
+ event.preventDefault();
+ PMA_ajaxShowMessage(PMA_messages.strCopyingDatabase, false);
+ var $form = $(this);
+ PMA_prepareForAjaxRequest($form);
+ $.get($form.attr('action'), $form.serialize(), function (data) {
+ // use messages that stay on screen
+ $('div.success, div.error').fadeOut();
+ if (data.success === true) {
+ if ($("#checkbox_switch").is(":checked")) {
+ PMA_commonParams.set('db', data.newname);
+ PMA_commonActions.refreshMain(false, function () {
+ PMA_ajaxShowMessage(data.message);
+ });
+ } else {
+ PMA_commonParams.set('db', data.db);
+ PMA_ajaxShowMessage(data.message);
+ }
+ PMA_reloadNavigation();
+ } else {
+ PMA_ajaxShowMessage(data.error, false);
+ }
+ }); // end $.get
+ }); // end copy database
+
+ /**
+ * Ajax Event handler for 'Change Charset' of the database
+ */
+ $("#change_db_charset_form.ajax").live('submit', function (event) {
+ event.preventDefault();
+ var $form = $(this);
+ PMA_prepareForAjaxRequest($form);
+ PMA_ajaxShowMessage(PMA_messages.strChangingCharset);
+ $.get($form.attr('action'), $form.serialize() + "&submitcollation=1", function (data) {
+ if (data.success === true) {
+ PMA_ajaxShowMessage(data.message);
+ } else {
+ PMA_ajaxShowMessage(data.error, false);
+ }
+ }); // end $.get()
+ }); // end change charset
+});
diff --git a/js/db_search.js b/js/db_search.js
new file mode 100644
index 0000000000..82fcec2ec1
--- /dev/null
+++ b/js/db_search.js
@@ -0,0 +1,236 @@
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * JavaScript functions used on Database Search page
+ *
+ * @requires jQuery
+ * @requires js/functions.js
+ *
+ * @package PhpMyAdmin
+ */
+
+/**
+ * AJAX script for the Database Search page.
+ *
+ * Actions ajaxified here:
+ * Retrieve result of SQL query
+ */
+
+/**
+ * Unbind all event handlers before tearing down a page
+ */
+AJAX.registerTeardown('db_search.js', function () {
+ $('#buttonGo').unbind('click');
+ $('#togglesearchresultlink').unbind('click');
+ $("#togglequerybox").unbind('click');
+ $('#togglesearchformlink').unbind('click');
+ $("#db_search_form.ajax").die('submit');
+});
+
+/**
+ * Loads the database search results
+ *
+ * @param result_path Url of the page to load
+ * @param table_name Name of table to browse
+ *
+ * @return nothing
+ */
+function loadResult(result_path, table_name, link)
+{
+ $(function () {
+ /** Hides the results shown by the delete criteria */
+ var $msg = PMA_ajaxShowMessage(PMA_messages.strBrowsing, false);
+ $('#sqlqueryform').hide();
+ $('#togglequerybox').hide();
+ /** Load the browse results to the page */
+ $("#table-info").show();
+ $('#table-link').attr({"href" : 'sql.php?' + link }).text(table_name);
+ var url = result_path + "#sqlqueryresults";
+ $.get(url, {'ajax_request': true, 'is_js_confirmed': true}, function (data) {
+ if (data.success) {
+ $('#browse-results').html(data.message);
+ $('html, body')
+ .animate({
+ scrollTop: $("#browse-results").offset().top
+ }, 1000);
+ PMA_ajaxRemoveMessage($msg);
+ PMA_makegrid($('#table_results')[0], true, true, true, true);
+ $('#browse-results').show();
+ } else {
+ PMA_ajaxShowMessage(data.error, false);
+ }
+ });
+ });
+}
+
+/**
+ * Delete the selected search results
+ *
+ * @param result_path Url of the page to load
+ * @param msg Text for the confirmation dialog
+ *
+ * @return nothing
+ */
+function deleteResult(result_path, msg)
+{
+ $(function () {
+ /** Hides the results shown by the browse criteria */
+ $("#table-info").hide();
+ $('#sqlqueryform').hide();
+ $('#togglequerybox').hide();
+ /** Conformation message for deletion */
+ if (confirm(msg)) {
+ var $msg = PMA_ajaxShowMessage(PMA_messages.strDeleting, false);
+ /** Load the deleted option to the page*/
+ $('#sqlqueryform').html('');
+ var url = result_path + "#result_query, #sqlqueryform";
+ $.get(url, {'ajax_request': true, 'is_js_confirmed': true},
+ function (data) {
+ if (data.success) {
+ $('#sqlqueryform').html(data.sql_query);
+ /** Refresh the search results after the deletion */
+ document.getElementById('buttonGo').click();
+ $('#togglequerybox').html(PMA_messages.strHideQueryBox);
+ /** Show the results of the deletion option */
+ $('#browse-results').hide();
+ $('#sqlqueryform').show();
+ $('#togglequerybox').show();
+ $('html, body')
+ .animate({
+ scrollTop: $("#browse-results").offset().top
+ }, 1000);
+ PMA_ajaxRemoveMessage($msg);
+ } else {
+ PMA_ajaxShowMessage(data.error, false);
+ }
+ });
+ }
+ });
+}
+
+AJAX.registerOnload('db_search.js', function () {
+ /** Hide the table link in the initial search result */
+ var icon = PMA_getImage('s_tbl.png', '', {'id': 'table-image'}).toString();
+ $("#table-info").prepend(icon).hide();
+
+ /** Hide the browse and deleted results in the new search criteria */
+ $('#buttonGo').click(function () {
+ $("#table-info").hide();
+ $('#browse-results').hide();
+ $('#sqlqueryform').hide();
+ $('#togglequerybox').hide();
+ });
+ /**
+ * Prepare a div containing a link for toggle the search results
+ */
+ $('#togglesearchresultsdiv')
+ /** don't show it until we have results on-screen */
+ .hide();
+
+ /**
+ * Changing the displayed text according to
+ * the hide/show criteria in search result forms
+ */
+ $('#togglesearchresultlink')
+ .html(PMA_messages.strHideSearchResults)
+ .bind('click', function () {
+ var $link = $(this);
+ $('#searchresults').slideToggle();
+ if ($link.text() == PMA_messages.strHideSearchResults) {
+ $link.text(PMA_messages.strShowSearchResults);
+ } else {
+ $link.text(PMA_messages.strHideSearchResults);
+ }
+ /** avoid default click action */
+ return false;
+ });
+
+ /**
+ * Prepare a div containing a link for toggle the search form,
+ * otherwise it's incorrectly displayed after a couple of clicks
+ */
+ $('#togglesearchformdiv')
+ .hide(); // don't show it until we have results on-screen
+
+ /**
+ * Changing the displayed text according to
+ * the hide/show criteria in search form
+ */
+ $("#togglequerybox")
+ .hide()
+ .bind('click', function () {
+ var $link = $(this);
+ $('#sqlqueryform').slideToggle("medium");
+ if ($link.text() == PMA_messages.strHideQueryBox) {
+ $link.text(PMA_messages.strShowQueryBox);
+ } else {
+ $link.text(PMA_messages.strHideQueryBox);
+ }
+ /** avoid default click action */
+ return false;
+ });
+
+ /** don't show it until we have results on-screen */
+
+ /**
+ * Changing the displayed text according to
+ * the hide/show criteria in search criteria form
+ */
+ $('#togglesearchformlink')
+ .html(PMA_messages.strShowSearchCriteria)
+ .bind('click', function () {
+ var $link = $(this);
+ $('#db_search_form').slideToggle();
+ if ($link.text() == PMA_messages.strHideSearchCriteria) {
+ $link.text(PMA_messages.strShowSearchCriteria);
+ } else {
+ $link.text(PMA_messages.strHideSearchCriteria);
+ }
+ /** avoid default click action */
+ return false;
+ });
+ /**
+ * Ajax Event handler for retrieving the result of an SQL Query
+ */
+ $("#db_search_form.ajax").live('submit', function (event) {
+ event.preventDefault();
+
+ var $msgbox = PMA_ajaxShowMessage(PMA_messages.strSearching, false);
+ // jQuery object to reuse
+ var $form = $(this);
+
+ PMA_prepareForAjaxRequest($form);
+
+ var url = $form.serialize() + "&submit_search=" + $("#buttonGo").val();
+ $.post($form.attr('action'), url, function (data) {
+ if (data.success === true) {
+ // found results
+ $("#searchresults").html(data.message);
+
+ $('#togglesearchresultlink')
+ // always start with the Show message
+ .text(PMA_messages.strHideSearchResults);
+ $('#togglesearchresultsdiv')
+ // now it's time to show the div containing the link
+ .show();
+ $('#searchresults').show();
+
+
+ $('#db_search_form')
+ // workaround for Chrome problem (bug #3168569)
+ .slideToggle()
+ .hide();
+ $('#togglesearchformlink')
+ // always start with the Show message
+ .text(PMA_messages.strShowSearchCriteria);
+ $('#togglesearchformdiv')
+ // now it's time to show the div containing the link
+ .show();
+ } else {
+ // error message (zero rows)
+ $("#sqlqueryresults").html(data.error);
+ }
+
+ PMA_ajaxRemoveMessage($msgbox);
+ });
+ });
+}); // end $()
diff --git a/js/db_structure.js b/js/db_structure.js
new file mode 100644
index 0000000000..63d6f43ee6
--- /dev/null
+++ b/js/db_structure.js
@@ -0,0 +1,385 @@
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * @fileoverview functions used on the database structure page
+ * @name Database Structure
+ *
+ * @requires jQuery
+ * @requires jQueryUI
+ * @required js/functions.js
+ */
+
+/**
+ * AJAX scripts for db_structure.php
+ *
+ * Actions ajaxified here:
+ * Drop Database
+ * Truncate Table
+ * Drop Table
+ *
+ */
+
+/**
+ * Unbind all event handlers before tearing down a page
+ */
+AJAX.registerTeardown('db_structure.js', function () {
+ $("span.fkc_switch").unbind('click');
+ $('#fkc_checkbox').unbind('change');
+ $("a.truncate_table_anchor.ajax").die('click');
+ $("a.drop_table_anchor.ajax").die('click');
+ $('a.drop_tracking_anchor.ajax').die('click');
+ $('#real_end_input').die('click');
+});
+
+/**
+ * Adjust number of rows and total size in the summary
+ * when truncating, creating, dropping or inserting into a table
+ */
+function PMA_adjustTotals() {
+ var byteUnits = new Array(
+ PMA_messages.strB,
+ PMA_messages.strKiB,
+ PMA_messages.strMiB,
+ PMA_messages.strGiB,
+ PMA_messages.strTiB,
+ PMA_messages.strPiB,
+ PMA_messages.strEiB
+ );
+ /**
+ * @var $allTr jQuery object that references all the rows in the list of tables
+ */
+ var $allTr = $("#tablesForm table.data tbody:first tr");
+ // New summary values for the table
+ var tableSum = $allTr.size();
+ var rowsSum = 0;
+ var sizeSum = 0;
+ var overheadSum = 0;
+ var rowSumApproximated = false;
+
+ $allTr.each(function () {
+ var $this = $(this);
+ var i, tmpVal;
+ // Get the number of rows for this SQL table
+ var strRows = $this.find('.tbl_rows').text();
+ // If the value is approximated
+ if (strRows.indexOf('~') === 0) {
+ rowSumApproximated = true;
+ // The approximated value contains a preceding ~ and a following 2 (Eg 100 --> ~1002)
+ strRows = strRows.substring(1, strRows.length - 1);
+ }
+ strRows = strRows.replace(/[,.]/g, '');
+ var intRow = parseInt(strRows, 10);
+ if (! isNaN(intRow)) {
+ rowsSum += intRow;
+ }
+ // Extract the size and overhead
+ var valSize = 0;
+ var valOverhead = 0;
+ var strSize = $.trim($this.find('.tbl_size span:not(.unit)').text());
+ var strSizeUnit = $.trim($this.find('.tbl_size span.unit').text());
+ var strOverhead = $.trim($this.find('.tbl_overhead span:not(.unit)').text());
+ var strOverheadUnit = $.trim($this.find('.tbl_overhead span.unit').text());
+ // Given a value and a unit, such as 100 and KiB, for the table size
+ // and overhead calculate their numeric values in bytes, such as 102400
+ for (i = 0; i < byteUnits.length; i++) {
+ if (strSizeUnit == byteUnits[i]) {
+ tmpVal = parseFloat(strSize);
+ valSize = tmpVal * Math.pow(1024, i);
+ break;
+ }
+ }
+ for (i = 0; i < byteUnits.length; i++) {
+ if (strOverheadUnit == byteUnits[i]) {
+ tmpVal = parseFloat(strOverhead);
+ valOverhead = tmpVal * Math.pow(1024, i);
+ break;
+ }
+ }
+ sizeSum += valSize;
+ overheadSum += valOverhead;
+ });
+ // Add some commas for readablility:
+ // 1000000 becomes 1,000,000
+ var strRowSum = rowsSum + "";
+ var regex = /(\d+)(\d{3})/;
+ while (regex.test(strRowSum)) {
+ strRowSum = strRowSum.replace(regex, '$1' + ',' + '$2');
+ }
+ // If approximated total value add ~ in front
+ if (rowSumApproximated) {
+ strRowSum = "~" + strRowSum;
+ }
+ // Calculate the magnitude for the size and overhead values
+ var size_magnitude = 0, overhead_magnitude = 0;
+ while (sizeSum >= 1024) {
+ sizeSum /= 1024;
+ size_magnitude++;
+ }
+ while (overheadSum >= 1024) {
+ overheadSum /= 1024;
+ overhead_magnitude++;
+ }
+
+ sizeSum = Math.round(sizeSum * 10) / 10;
+ overheadSum = Math.round(overheadSum * 10) / 10;
+
+ // Update summary with new data
+ var $summary = $("#tbl_summary_row");
+ $summary.find('.tbl_num').text($.sprintf(PMA_messages.strTables, tableSum));
+ $summary.find('.tbl_rows').text(strRowSum);
+ $summary.find('.tbl_size').text(sizeSum + " " + byteUnits[size_magnitude]);
+ $summary.find('.tbl_overhead').text(overheadSum + " " + byteUnits[overhead_magnitude]);
+}
+
+AJAX.registerOnload('db_structure.js', function () {
+ /**
+ * Handler for the print view multisubmit.
+ * All other multi submits can be handled via ajax, but this one needs
+ * special treatment as the results need to open in another browser window
+ */
+ $('#tablesForm').submit(function (event) {
+ var $form = $(this);
+ if ($form.find('select[name=submit_mult]').val() === 'print') {
+ event.preventDefault();
+ event.stopPropagation();
+ $('form#clone').remove();
+ var $clone = $form
+ .clone()
+ .hide()
+ .appendTo('body');
+ $clone
+ .find('select[name=submit_mult]')
+ .val('print');
+ $clone
+ .attr('target', 'printview')
+ .attr('id', 'clone')
+ .submit();
+ }
+ });
+
+ /**
+ * Event handler for 'Foreign Key Checks' disabling option
+ * in the drop table confirmation form
+ */
+ $("span.fkc_switch").click(function (event) {
+ if ($("#fkc_checkbox").prop('checked')) {
+ $("#fkc_checkbox").prop('checked', false);
+ $("#fkc_status").html(PMA_messages.strForeignKeyCheckDisabled);
+ return;
+ }
+ $("#fkc_checkbox").prop('checked', true);
+ $("#fkc_status").html(PMA_messages.strForeignKeyCheckEnabled);
+ });
+
+ $('#fkc_checkbox').change(function () {
+ if ($(this).prop("checked")) {
+ $("#fkc_status").html(PMA_messages.strForeignKeyCheckEnabled);
+ return;
+ }
+ $("#fkc_status").html(PMA_messages.strForeignKeyCheckDisabled);
+ }); // End of event handler for 'Foreign Key Check'
+
+ /**
+ * Ajax Event handler for 'Truncate Table'
+ */
+ $("a.truncate_table_anchor.ajax").live('click', function (event) {
+ event.preventDefault();
+
+ /**
+ * @var $this_anchor Object referring to the anchor clicked
+ */
+ var $this_anchor = $(this);
+
+ //extract current table name and build the question string
+ /**
+ * @var curr_table_name String containing the name of the table to be truncated
+ */
+ var curr_table_name = $this_anchor.parents('tr').children('th').children('a').text();
+ /**
+ * @var question String containing the question to be asked for confirmation
+ */
+ var question = PMA_messages.strTruncateTableStrongWarning + ' ' +
+ $.sprintf(PMA_messages.strDoYouReally, 'TRUNCATE ' + escapeHtml(curr_table_name));
+
+ $this_anchor.PMA_confirm(question, $this_anchor.attr('href'), function (url) {
+
+ PMA_ajaxShowMessage(PMA_messages.strProcessingRequest);
+
+ $.get(url, {'is_js_confirmed' : 1, 'ajax_request' : true}, function (data) {
+ if (data.success === true) {
+ PMA_ajaxShowMessage(data.message);
+ // Adjust table statistics
+ var $tr = $this_anchor.closest('tr');
+ $tr.find('.tbl_rows').text('0');
+ $tr.find('.tbl_size, .tbl_overhead').text('-');
+ //Fetch inner span of this anchor
+ //and replace the icon with its disabled version
+ var span = $this_anchor.html().replace(/b_empty/, 'bd_empty');
+ //To disable further attempts to truncate the table,
+ //replace the a element with its inner span (modified)
+ $this_anchor
+ .replaceWith(span)
+ .removeClass('truncate_table_anchor');
+ PMA_adjustTotals();
+ } else {
+ PMA_ajaxShowMessage(PMA_messages.strErrorProcessingRequest + " : " + data.error, false);
+ }
+ }); // end $.get()
+ }); //end $.PMA_confirm()
+ }); //end of Truncate Table Ajax action
+
+ /**
+ * Ajax Event handler for 'Drop Table' or 'Drop View'
+ */
+ $("a.drop_table_anchor.ajax").live('click', function (event) {
+ event.preventDefault();
+
+ var $this_anchor = $(this);
+
+ //extract current table name and build the question string
+ /**
+ * @var $curr_row Object containing reference to the current row
+ */
+ var $curr_row = $this_anchor.parents('tr');
+ /**
+ * @var curr_table_name String containing the name of the table to be truncated
+ */
+ var curr_table_name = $curr_row.children('th').children('a').text();
+ /**
+ * @var is_view Boolean telling if we have a view
+ */
+ var is_view = $curr_row.hasClass('is_view') || $this_anchor.hasClass('view');
+ /**
+ * @var question String containing the question to be asked for confirmation
+ */
+ var question;
+ if (! is_view) {
+ question = PMA_messages.strDropTableStrongWarning + ' ' +
+ $.sprintf(PMA_messages.strDoYouReally, 'DROP TABLE ' + escapeHtml(curr_table_name));
+ } else {
+ question =
+ $.sprintf(PMA_messages.strDoYouReally, 'DROP VIEW ' + escapeHtml(curr_table_name));
+ }
+
+ $this_anchor.PMA_confirm(question, $this_anchor.attr('href'), function (url) {
+
+ var $msg = PMA_ajaxShowMessage(PMA_messages.strProcessingRequest);
+
+ $.get(url, {'is_js_confirmed' : 1, 'ajax_request' : true}, function (data) {
+ if (data.success === true) {
+ PMA_ajaxShowMessage(data.message);
+ toggleRowColors($curr_row.next());
+ $curr_row.hide("medium").remove();
+ PMA_adjustTotals();
+ PMA_reloadNavigation();
+ PMA_ajaxRemoveMessage($msg);
+ } else {
+ PMA_ajaxShowMessage(PMA_messages.strErrorProcessingRequest + " : " + data.error, false);
+ }
+ }); // end $.get()
+ }); // end $.PMA_confirm()
+ }); //end of Drop Table Ajax action
+
+ /**
+ * Ajax Event handler for 'Drop tracking'
+ */
+ $('a.drop_tracking_anchor.ajax').live('click', function (event) {
+ event.preventDefault();
+
+ var $anchor = $(this);
+
+ /**
+ * @var curr_tracking_row Object containing reference to the current tracked table's row
+ */
+ var $curr_tracking_row = $anchor.parents('tr');
+ /**
+ * @var question String containing the question to be asked for confirmation
+ */
+ var question = PMA_messages.strDeleteTrackingData;
+
+ $anchor.PMA_confirm(question, $anchor.attr('href'), function (url) {
+
+ PMA_ajaxShowMessage(PMA_messages.strDeletingTrackingData);
+
+ $.get(url, {'is_js_confirmed': 1, 'ajax_request': true}, function (data) {
+ if (data.success === true) {
+ var $tracked_table = $curr_tracking_row.parents('table');
+ var table_name = $curr_tracking_row.find('td:nth-child(2)').text();
+
+ // Check how many rows will be left after we remove
+ if ($tracked_table.find('tbody tr').length === 1) {
+ // We are removing the only row it has
+ $('#tracked_tables').hide("slow").remove();
+ } else {
+ // There are more rows left after the deletion
+ toggleRowColors($curr_tracking_row.next());
+ $curr_tracking_row.hide("slow", function () {
+ $(this).remove();
+ });
+ }
+
+ // Make the removed table visible in the list of 'Untracked tables'.
+ var $untracked_table = $('table#noversions');
+
+ // This won't work if no untracked tables are there.
+ if ($untracked_table.length > 0) {
+ var $rows = $untracked_table.find('tbody tr');
+
+ $rows.each(function (index) {
+ var $row = $(this);
+ var tmp_tbl_name = $row.find('td:first-child').text();
+ var is_last_iteration = (index == ($rows.length - 1));
+
+ if (tmp_tbl_name > table_name || is_last_iteration) {
+ var $cloned = $row.clone();
+
+ // Change the table name of the cloned row.
+ $cloned.find('td:first-child').text(table_name);
+
+ // Change the link of the cloned row.
+ var new_url = $cloned
+ .find('td:nth-child(2) a')
+ .attr('href')
+ .replace('table=' + tmp_tbl_name, 'table=' + encodeURIComponent(table_name));
+ $cloned.find('td:nth-child(2) a').attr('href', new_url);
+
+ // Insert the cloned row in an appropriate location.
+ if (tmp_tbl_name > table_name) {
+ $cloned.insertBefore($row);
+ toggleRowColors($row);
+ return false;
+ } else {
+ $cloned.insertAfter($row);
+ toggleRowColors($cloned);
+ }
+ }
+ });
+ }
+
+ PMA_ajaxShowMessage(data.message);
+ } else {
+ PMA_ajaxShowMessage(PMA_messages.strErrorProcessingRequest + " : " + data.error, false);
+ }
+ }); // end $.get()
+ }); // end $.PMA_confirm()
+ }); //end Drop Tracking
+
+ //Calculate Real End for InnoDB
+ /**
+ * Ajax Event handler for calculatig the real end for a InnoDB table
+ *
+ */
+ $('#real_end_input').live('click', function (event) {
+ event.preventDefault();
+
+ /**
+ * @var question String containing the question to be asked for confirmation
+ */
+ var question = PMA_messages.strOperationTakesLongTime;
+
+ $(this).PMA_confirm(question, '', function () {
+ return true;
+ });
+ return false;
+ }); //end Calculate Real End for InnoDB
+
+}); // end $()
diff --git a/js/doclinks.js b/js/doclinks.js
new file mode 100644
index 0000000000..678e7e8857
--- /dev/null
+++ b/js/doclinks.js
@@ -0,0 +1,365 @@
+/**
+ * Definition of links to MySQL documentation.
+ */
+
+var mysql_doc_keyword = {
+ /* Multi word */
+ 'CHARACTER SET': Array('charset'),
+ 'SHOW AUTHORS': Array('show-authors'),
+ 'SHOW BINARY LOGS': Array('show-binary-logs'),
+ 'SHOW BINLOG EVENTS': Array('show-binlog-events'),
+ 'SHOW CHARACTER SET': Array('show-character-set'),
+ 'SHOW COLLATION': Array('show-collation'),
+ 'SHOW COLUMNS': Array('show-columns'),
+ 'SHOW CONTRIBUTORS': Array('show-contributors'),
+ 'SHOW CREATE DATABASE': Array('show-create-database'),
+ 'SHOW CREATE EVENT': Array('show-create-event'),
+ 'SHOW CREATE FUNCTION': Array('show-create-function'),
+ 'SHOW CREATE PROCEDURE': Array('show-create-procedure'),
+ 'SHOW CREATE TABLE': Array('show-create-table'),
+ 'SHOW CREATE TRIGGER': Array('show-create-trigger'),
+ 'SHOW CREATE VIEW': Array('show-create-view'),
+ 'SHOW DATABASES': Array('show-databases'),
+ 'SHOW ENGINE': Array('show-engine'),
+ 'SHOW ENGINES': Array('show-engines'),
+ 'SHOW ERRORS': Array('show-errors'),
+ 'SHOW EVENTS': Array('show-events'),
+ 'SHOW FUNCTION CODE': Array('show-function-code'),
+ 'SHOW FUNCTION STATUS': Array('show-function-status'),
+ 'SHOW GRANTS': Array('show-grants'),
+ 'SHOW INDEX': Array('show-index'),
+ 'SHOW MASTER STATUS': Array('show-master-status'),
+ 'SHOW OPEN TABLES': Array('show-open-tables'),
+ 'SHOW PLUGINS': Array('show-plugins'),
+ 'SHOW PRIVILEGES': Array('show-privileges'),
+ 'SHOW PROCEDURE CODE': Array('show-procedure-code'),
+ 'SHOW PROCEDURE STATUS': Array('show-procedure-status'),
+ 'SHOW PROCESSLIST': Array('show-processlist'),
+ 'SHOW PROFILE': Array('show-profile'),
+ 'SHOW PROFILES': Array('show-profiles'),
+ 'SHOW RELAYLOG EVENTS': Array('show-relaylog-events'),
+ 'SHOW SLAVE HOSTS': Array('show-slave-hosts'),
+ 'SHOW SLAVE STATUS': Array('show-slave-status'),
+ 'SHOW STATUS': Array('show-status'),
+ 'SHOW TABLE STATUS': Array('show-table-status'),
+ 'SHOW TABLES': Array('show-tables'),
+ 'SHOW TRIGGERS': Array('show-triggers'),
+ 'SHOW VARIABLES': Array('show-variables'),
+ 'SHOW WARNINGS': Array('show-warnings'),
+ 'LOAD DATA INFILE': Array('load-data'),
+ 'LOAD XML': Array('load-xml'),
+ 'LOCK TABLES': Array('lock-tables'),
+ 'UNLOCK TABLES': Array('lock-tables'),
+ 'ALTER DATABASE': Array('alter-database'),
+ 'ALTER EVENT': Array('alter-event'),
+ 'ALTER LOGFILE GROUP': Array('alter-logfile-group'),
+ 'ALTER FUNCTION': Array('alter-function'),
+ 'ALTER PROCEDURE': Array('alter-procedure'),
+ 'ALTER SERVER': Array('alter-server'),
+ 'ALTER TABLE': Array('alter-table'),
+ 'ALTER TABLESPACE': Array('alter-tablespace'),
+ 'ALTER VIEW': Array('alter-view'),
+ 'CREATE DATABASE': Array('create-database'),
+ 'CREATE EVENT': Array('create-event'),
+ 'CREATE FUNCTION': Array('create-function'),
+ 'CREATE INDEX': Array('create-index'),
+ 'CREATE LOGFILE GROUP': Array('create-logfile-group'),
+ 'CREATE PROCEDURE': Array('create-procedure'),
+ 'CREATE SERVER': Array('create-server'),
+ 'CREATE TABLE': Array('create-table'),
+ 'CREATE TABLESPACE': Array('create-tablespace'),
+ 'CREATE TRIGGER': Array('create-trigger'),
+ 'CREATE VIEW': Array('create-view'),
+ 'DROP DATABASE': Array('drop-database'),
+ 'DROP EVENT': Array('drop-event'),
+ 'DROP FUNCTION': Array('drop-function'),
+ 'DROP INDEX': Array('drop-index'),
+ 'DROP LOGFILE GROUP': Array('drop-logfile-group'),
+ 'DROP PROCEDURE': Array('drop-procedure'),
+ 'DROP SERVER': Array('drop-server'),
+ 'DROP TABLE': Array('drop-table'),
+ 'DROP TABLESPACE': Array('drop-tablespace'),
+ 'DROP TRIGGER': Array('drop-trigger'),
+ 'DROP VIEW': Array('drop-view'),
+ 'RENAME TABLE': Array('rename-table'),
+ 'TRUNCATE TABLE': Array('truncate-table'),
+
+ /* Statements */
+ 'SELECT': Array('select'),
+ 'SET': Array('set'),
+ 'EXPLAIN': Array('explain'),
+ 'DESCRIBE': Array('describe'),
+ 'DELETE': Array('delete'),
+ 'SHOW': Array('show'),
+ 'UPDATE': Array('update'),
+ 'INSERT': Array('insert'),
+ 'REPLACE': Array('replace'),
+ 'CALL': Array('call'),
+ 'DO': Array('do'),
+ 'HANDLER': Array('handler'),
+ 'COLLATE': Array('charset-collations'),
+
+ /* Functions */
+ 'ABS': Array('mathematical-functions', 'function_abs'),
+ 'ACOS': Array('mathematical-functions', 'function_acos'),
+ 'ADDDATE': Array('date-and-time-functions', 'function_adddate'),
+ 'ADDTIME': Array('date-and-time-functions', 'function_addtime'),
+ 'AES_DECRYPT': Array('encryption-functions', 'function_aes_decrypt'),
+ 'AES_ENCRYPT': Array('encryption-functions', 'function_aes_encrypt'),
+ 'AND': Array('logical-operators', 'operator_and'),
+ 'ASCII': Array('string-functions', 'function_ascii'),
+ 'ASIN': Array('mathematical-functions', 'function_asin'),
+ 'ATAN2': Array('mathematical-functions', 'function_atan2'),
+ 'ATAN': Array('mathematical-functions', 'function_atan'),
+ 'AVG': Array('group-by-functions', 'function_avg'),
+ 'BENCHMARK': Array('information-functions', 'function_benchmark'),
+ 'BIN': Array('string-functions', 'function_bin'),
+ 'BINARY': Array('cast-functions', 'operator_binary'),
+ 'BIT_AND': Array('group-by-functions', 'function_bit_and'),
+ 'BIT_COUNT': Array('bit-functions', 'function_bit_count'),
+ 'BIT_LENGTH': Array('string-functions', 'function_bit_length'),
+ 'BIT_OR': Array('group-by-functions', 'function_bit_or'),
+ 'BIT_XOR': Array('group-by-functions', 'function_bit_xor'),
+ 'CASE': Array('control-flow-functions', 'operator_case'),
+ 'CAST': Array('cast-functions', 'function_cast'),
+ 'CEIL': Array('mathematical-functions', 'function_ceil'),
+ 'CEILING': Array('mathematical-functions', 'function_ceiling'),
+ 'CHAR_LENGTH': Array('string-functions', 'function_char_length'),
+ 'CHAR': Array('string-functions', 'function_char'),
+ 'CHARACTER_LENGTH': Array('string-functions', 'function_character_length'),
+ 'CHARSET': Array('information-functions', 'function_charset'),
+ 'COALESCE': Array('comparison-operators', 'function_coalesce'),
+ 'COERCIBILITY': Array('information-functions', 'function_coercibility'),
+ 'COLLATION': Array('information-functions', 'function_collation'),
+ 'COMPRESS': Array('encryption-functions', 'function_compress'),
+ 'CONCAT_WS': Array('string-functions', 'function_concat_ws'),
+ 'CONCAT': Array('string-functions', 'function_concat'),
+ 'CONNECTION_ID': Array('information-functions', 'function_connection_id'),
+ 'CONV': Array('mathematical-functions', 'function_conv'),
+ 'CONVERT_TZ': Array('date-and-time-functions', 'function_convert_tz'),
+ 'Convert': Array('cast-functions', 'function_convert'),
+ 'COS': Array('mathematical-functions', 'function_cos'),
+ 'COT': Array('mathematical-functions', 'function_cot'),
+ 'COUNT': Array('group-by-functions', 'function_count'),
+ 'CRC32': Array('mathematical-functions', 'function_crc32'),
+ 'CURDATE': Array('date-and-time-functions', 'function_curdate'),
+ 'CURRENT_DATE': Array('date-and-time-functions', 'function_current_date'),
+ 'CURRENT_TIME': Array('date-and-time-functions', 'function_current_time'),
+ 'CURRENT_TIMESTAMP': Array('date-and-time-functions', 'function_current_timestamp'),
+ 'CURRENT_USER': Array('information-functions', 'function_current_user'),
+ 'CURTIME': Array('date-and-time-functions', 'function_curtime'),
+ 'DATABASE': Array('information-functions', 'function_database'),
+ 'DATE_ADD': Array('date-and-time-functions', 'function_date_add'),
+ 'DATE_FORMAT': Array('date-and-time-functions', 'function_date_format'),
+ 'DATE_SUB': Array('date-and-time-functions', 'function_date_sub'),
+ 'DATE': Array('date-and-time-functions', 'function_date'),
+ 'DATEDIFF': Array('date-and-time-functions', 'function_datediff'),
+ 'DAY': Array('date-and-time-functions', 'function_day'),
+ 'DAYNAME': Array('date-and-time-functions', 'function_dayname'),
+ 'DAYOFMONTH': Array('date-and-time-functions', 'function_dayofmonth'),
+ 'DAYOFWEEK': Array('date-and-time-functions', 'function_dayofweek'),
+ 'DAYOFYEAR': Array('date-and-time-functions', 'function_dayofyear'),
+ 'DECLARE': Array('declare', 'declare'),
+ 'DECODE': Array('encryption-functions', 'function_decode'),
+ 'DEFAULT': Array('miscellaneous-functions', 'function_default'),
+ 'DEGREES': Array('mathematical-functions', 'function_degrees'),
+ 'DES_DECRYPT': Array('encryption-functions', 'function_des_decrypt'),
+ 'DES_ENCRYPT': Array('encryption-functions', 'function_des_encrypt'),
+ 'DIV': Array('arithmetic-functions', 'operator_div'),
+ 'ELT': Array('string-functions', 'function_elt'),
+ 'ENCODE': Array('encryption-functions', 'function_encode'),
+ 'ENCRYPT': Array('encryption-functions', 'function_encrypt'),
+ 'EXP': Array('mathematical-functions', 'function_exp'),
+ 'EXPORT_SET': Array('string-functions', 'function_export_set'),
+ 'EXTRACT': Array('date-and-time-functions', 'function_extract'),
+ 'ExtractValue': Array('xml-functions', 'function_extractvalue'),
+ 'FIELD': Array('string-functions', 'function_field'),
+ 'FIND_IN_SET': Array('string-functions', 'function_find_in_set'),
+ 'FLOOR': Array('mathematical-functions', 'function_floor'),
+ 'FORMAT': Array('string-functions', 'function_format'),
+ 'FOUND_ROWS': Array('information-functions', 'function_found_rows'),
+ 'FROM_DAYS': Array('date-and-time-functions', 'function_from_days'),
+ 'FROM_UNIXTIME': Array('date-and-time-functions', 'function_from_unixtime'),
+ 'GET_FORMAT': Array('date-and-time-functions', 'function_get_format'),
+ 'GET_LOCK': Array('miscellaneous-functions', 'function_get_lock'),
+ 'GREATEST': Array('comparison-operators', 'function_greatest'),
+ 'GROUP_CONCAT': Array('group-by-functions', 'function_group_concat'),
+ 'HEX': Array('string-functions', 'function_hex'),
+ 'HOUR': Array('date-and-time-functions', 'function_hour'),
+ 'IF': Array('control-flow-functions', 'function_if'),
+ 'IFNULL': Array('control-flow-functions', 'function_ifnull'),
+ 'IN': Array('comparison-operators', 'function_in'),
+ 'INET_ATON': Array('miscellaneous-functions', 'function_inet_aton'),
+ 'INET_NTOA': Array('miscellaneous-functions', 'function_inet_ntoa'),
+ 'INSTR': Array('string-functions', 'function_instr'),
+ 'INTERVAL': Array('comparison-operators', 'function_interval'),
+ 'IS_FREE_LOCK': Array('miscellaneous-functions', 'function_is_free_lock'),
+ 'IS_USED_LOCK': Array('miscellaneous-functions', 'function_is_used_lock'),
+ 'IS': Array('comparison-operators', 'operator_is'),
+ 'ISNULL': Array('comparison-operators', 'function_isnull'),
+ 'LAST_DAY': Array('date-and-time-functions', 'function_last_day'),
+ 'LAST_INSERT_ID': Array('information-functions', 'function_last_insert_id'),
+ 'LCASE': Array('string-functions', 'function_lcase'),
+ 'LEAST': Array('comparison-operators', 'function_least'),
+ 'LEFT': Array('string-functions', 'function_left'),
+ 'LENGTH': Array('string-functions', 'function_length'),
+ 'LIKE': Array('string-comparison-functions', 'operator_like'),
+ 'LN': Array('mathematical-functions', 'function_ln'),
+ 'LOAD_FILE': Array('string-functions', 'function_load_file'),
+ 'LOCALTIME': Array('date-and-time-functions', 'function_localtime'),
+ 'LOCALTIMESTAMP': Array('date-and-time-functions', 'function_localtimestamp'),
+ 'LOCATE': Array('string-functions', 'function_locate'),
+ 'LOG10': Array('mathematical-functions', 'function_log10'),
+ 'LOG2': Array('mathematical-functions', 'function_log2'),
+ 'LOG': Array('mathematical-functions', 'function_log'),
+ 'LOWER': Array('string-functions', 'function_lower'),
+ 'LPAD': Array('string-functions', 'function_lpad'),
+ 'LTRIM': Array('string-functions', 'function_ltrim'),
+ 'MAKE_SET': Array('string-functions', 'function_make_set'),
+ 'MAKEDATE': Array('date-and-time-functions', 'function_makedate'),
+ 'MAKETIME': Array('date-and-time-functions', 'function_maketime'),
+ 'MASTER_POS_WAIT': Array('miscellaneous-functions', 'function_master_pos_wait'),
+ 'MATCH': Array('fulltext-search', 'function_match'),
+ 'MAX': Array('group-by-functions', 'function_max'),
+ 'MD5': Array('encryption-functions', 'function_md5'),
+ 'MICROSECOND': Array('date-and-time-functions', 'function_microsecond'),
+ 'MID': Array('string-functions', 'function_mid'),
+ 'MIN': Array('group-by-functions', 'function_min'),
+ 'MINUTE': Array('date-and-time-functions', 'function_minute'),
+ 'MOD': Array('mathematical-functions', 'function_mod'),
+ 'MONTH': Array('date-and-time-functions', 'function_month'),
+ 'MONTHNAME': Array('date-and-time-functions', 'function_monthname'),
+ 'NAME_CONST': Array('miscellaneous-functions', 'function_name_const'),
+ 'NOT': Array('logical-operators', 'operator_not'),
+ 'NOW': Array('date-and-time-functions', 'function_now'),
+ 'NULLIF': Array('control-flow-functions', 'function_nullif'),
+ 'OCT': Array('mathematical-functions', 'function_oct'),
+ 'OCTET_LENGTH': Array('string-functions', 'function_octet_length'),
+ 'OLD_PASSWORD': Array('encryption-functions', 'function_old_password'),
+ 'OR': Array('logical-operators', 'operator_or'),
+ 'ORD': Array('string-functions', 'function_ord'),
+ 'PASSWORD': Array('encryption-functions', 'function_password'),
+ 'PERIOD_ADD': Array('date-and-time-functions', 'function_period_add'),
+ 'PERIOD_DIFF': Array('date-and-time-functions', 'function_period_diff'),
+ 'PI': Array('mathematical-functions', 'function_pi'),
+ 'POSITION': Array('string-functions', 'function_position'),
+ 'POW': Array('mathematical-functions', 'function_pow'),
+ 'POWER': Array('mathematical-functions', 'function_power'),
+ 'QUARTER': Array('date-and-time-functions', 'function_quarter'),
+ 'QUOTE': Array('string-functions', 'function_quote'),
+ 'RADIANS': Array('mathematical-functions', 'function_radians'),
+ 'RAND': Array('mathematical-functions', 'function_rand'),
+ 'REGEXP': Array('regexp', 'operator_regexp'),
+ 'RELEASE_LOCK': Array('miscellaneous-functions', 'function_release_lock'),
+ 'REPEAT': Array('string-functions', 'function_repeat'),
+ 'REVERSE': Array('string-functions', 'function_reverse'),
+ 'RIGHT': Array('string-functions', 'function_right'),
+ 'RLIKE': Array('regexp', 'operator_rlike'),
+ 'ROUND': Array('mathematical-functions', 'function_round'),
+ 'ROW_COUNT': Array('information-functions', 'function_row_count'),
+ 'RPAD': Array('string-functions', 'function_rpad'),
+ 'RTRIM': Array('string-functions', 'function_rtrim'),
+ 'SCHEMA': Array('information-functions', 'function_schema'),
+ 'SEC_TO_TIME': Array('date-and-time-functions', 'function_sec_to_time'),
+ 'SECOND': Array('date-and-time-functions', 'function_second'),
+ 'SESSION_USER': Array('information-functions', 'function_session_user'),
+ 'SHA': Array('encryption-functions', 'function_sha1'),
+ 'SHA1': Array('encryption-functions', 'function_sha1'),
+ 'SIGN': Array('mathematical-functions', 'function_sign'),
+ 'SIN': Array('mathematical-functions', 'function_sin'),
+ 'SLEEP': Array('miscellaneous-functions', 'function_sleep'),
+ 'SOUNDEX': Array('string-functions', 'function_soundex'),
+ 'SPACE': Array('string-functions', 'function_space'),
+ 'SQRT': Array('mathematical-functions', 'function_sqrt'),
+ 'STD': Array('group-by-functions', 'function_std'),
+ 'STDDEV_POP': Array('group-by-functions', 'function_stddev_pop'),
+ 'STDDEV_SAMP': Array('group-by-functions', 'function_stddev_samp'),
+ 'STDDEV': Array('group-by-functions', 'function_stddev'),
+ 'STR_TO_DATE': Array('date-and-time-functions', 'function_str_to_date'),
+ 'STRCMP': Array('string-comparison-functions', 'function_strcmp'),
+ 'SUBDATE': Array('date-and-time-functions', 'function_subdate'),
+ 'SUBSTR': Array('string-functions', 'function_substr'),
+ 'SUBSTRING_INDEX': Array('string-functions', 'function_substring_index'),
+ 'SUBSTRING': Array('string-functions', 'function_substring'),
+ 'SUBTIME': Array('date-and-time-functions', 'function_subtime'),
+ 'SUM': Array('group-by-functions', 'function_sum'),
+ 'SYSDATE': Array('date-and-time-functions', 'function_sysdate'),
+ 'SYSTEM_USER': Array('information-functions', 'function_system_user'),
+ 'TAN': Array('mathematical-functions', 'function_tan'),
+ 'TIME_FORMAT': Array('date-and-time-functions', 'function_time_format'),
+ 'TIME_TO_SEC': Array('date-and-time-functions', 'function_time_to_sec'),
+ 'TIME': Array('date-and-time-functions', 'function_time'),
+ 'TIMEDIFF': Array('date-and-time-functions', 'function_timediff'),
+ 'TIMESTAMP': Array('date-and-time-functions', 'function_timestamp'),
+ 'TIMESTAMPADD': Array('date-and-time-functions', 'function_timestampadd'),
+ 'TIMESTAMPDIFF': Array('date-and-time-functions', 'function_timestampdiff'),
+ 'TO_DAYS': Array('date-and-time-functions', 'function_to_days'),
+ 'TRIM': Array('string-functions', 'function_trim'),
+ 'TRUNCATE': Array('mathematical-functions', 'function_truncate'),
+ 'UCASE': Array('string-functions', 'function_ucase'),
+ 'UNCOMPRESS': Array('encryption-functions', 'function_uncompress'),
+ 'UNCOMPRESSED_LENGTH': Array('encryption-functions', 'function_uncompressed_length'),
+ 'UNHEX': Array('string-functions', 'function_unhex'),
+ 'UNIX_TIMESTAMP': Array('date-and-time-functions', 'function_unix_timestamp'),
+ 'UpdateXML': Array('xml-functions', 'function_updatexml'),
+ 'UPPER': Array('string-functions', 'function_upper'),
+ 'USER': Array('information-functions', 'function_user'),
+ 'UTC_DATE': Array('date-and-time-functions', 'function_utc_date'),
+ 'UTC_TIME': Array('date-and-time-functions', 'function_utc_time'),
+ 'UTC_TIMESTAMP': Array('date-and-time-functions', 'function_utc_timestamp'),
+ 'UUID_SHORT': Array('miscellaneous-functions', 'function_uuid_short'),
+ 'UUID': Array('miscellaneous-functions', 'function_uuid'),
+ 'VALUES': Array('miscellaneous-functions', 'function_values'),
+ 'VAR_POP': Array('group-by-functions', 'function_var_pop'),
+ 'VAR_SAMP': Array('group-by-functions', 'function_var_samp'),
+ 'VARIANCE': Array('group-by-functions', 'function_variance'),
+ 'VERSION': Array('information-functions', 'function_version'),
+ 'WEEK': Array('date-and-time-functions', 'function_week'),
+ 'WEEKDAY': Array('date-and-time-functions', 'function_weekday'),
+ 'WEEKOFYEAR': Array('date-and-time-functions', 'function_weekofyear'),
+ 'XOR': Array('logical-operators', 'operator_xor'),
+ 'YEAR': Array('date-and-time-functions', 'function_year'),
+ 'YEARWEEK': Array('date-and-time-functions', 'function_yearweek'),
+ 'SOUNDS_LIKE': Array('string-functions', 'operator_sounds-like'),
+ 'IS_NOT_NULL': Array('comparison-operators', 'operator_is-not-null'),
+ 'IS_NOT': Array('comparison-operators', 'operator_is-not'),
+ 'IS_NULL': Array('comparison-operators', 'operator_is-null'),
+ 'NOT_LIKE': Array('string-comparison-functions', 'operator_not-like'),
+ 'NOT_REGEXP': Array('regexp', 'operator_not-regexp'),
+ 'COUNT_DISTINCT': Array('group-by-functions', 'function_count-distinct'),
+ 'NOT_IN': Array('comparison-operators', 'function_not-in')
+};
+
+var mysql_doc_builtin = {
+ 'TINYINT': Array('numeric-types'),
+ 'SMALLINT': Array('numeric-types'),
+ 'MEDIUMINT': Array('numeric-types'),
+ 'INT': Array('numeric-types'),
+ 'BIGINT': Array('numeric-types'),
+ 'DECIMAL': Array('numeric-types'),
+ 'FLOAT': Array('numeric-types'),
+ 'DOUBLE': Array('numeric-types'),
+ 'REAL': Array('numeric-types'),
+ 'BIT': Array('numeric-types'),
+ 'BOOLEAN': Array('numeric-types'),
+ 'SERIAL': Array('numeric-types'),
+ 'DATE': Array('date-and-time-types'),
+ 'DATETIME': Array('date-and-time-types'),
+ 'TIMESTAMP': Array('date-and-time-types'),
+ 'TIME': Array('date-and-time-types'),
+ 'YEAR': Array('date-and-time-types'),
+ 'CHAR': Array('string-types'),
+ 'VARCHAR': Array('string-types'),
+ 'TINYTEXT': Array('string-types'),
+ 'TEXT': Array('string-types'),
+ 'MEDIUMTEXT': Array('string-types'),
+ 'LONGTEXT': Array('string-types'),
+ 'BINARY': Array('string-types'),
+ 'VARBINARY': Array('string-types'),
+ 'TINYBLOB': Array('string-types'),
+ 'MEDIUMBLOB': Array('string-types'),
+ 'BLOB': Array('string-types'),
+ 'LONGBLOB': Array('string-types'),
+ 'ENUM': Array('string-types'),
+ 'SET': Array('string-types')
+};
diff --git a/js/error_report.js b/js/error_report.js
new file mode 100644
index 0000000000..c3765304d0
--- /dev/null
+++ b/js/error_report.js
@@ -0,0 +1,310 @@
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * general function, usually for data manipulation pages
+ *
+ */
+
+var ErrorReport = {
+ /**
+ * @var object stores the last exception info
+ */
+ _last_exception: null,
+ /**
+ * handles thrown error exceptions based on user preferences
+ *
+ * @return void
+ */
+ error_handler: function (exception) {
+ ErrorReport._last_exception = exception;
+ $.get("error_report.php", {
+ ajax_request: true,
+ token: PMA_commonParams.get('token'),
+ get_settings: true
+ }, function (data) {
+ if (!data.success === true) {
+ PMA_ajaxShowMessage(data.error, false);
+ return;
+ }
+ if (data.report_setting == "ask") {
+ ErrorReport._showErrorNotification();
+ } else if (data.report_setting == "always") {
+ report_data = ErrorReport._get_report_data(exception);
+ post_data = $.extend(report_data, {
+ send_error_report: true,
+ automatic: true
+ });
+ $.post("error_report.php", post_data, function (data) {
+ if (data.success === false) {
+ //in the case of an error, show the error message returned.
+ PMA_ajaxShowMessage(data.error, false);
+ } else {
+ PMA_ajaxShowMessage(data.message, false);
+ }
+ });
+ }
+ });
+ },
+ /**
+ * Shows the modal dialog previewing the report
+ *
+ * @param object error report info
+ *
+ * @return void
+ */
+ _showReportDialog: function (exception) {
+ var report_data = ErrorReport._get_report_data(exception);
+
+ /*Remove the hidden dialogs if there are*/
+ if ($('#error_report_dialog').length !== 0) {
+ $('#error_report_dialog').remove();
+ }
+ var $div = $('<div id="error_report_dialog"></div>');
+
+ var button_options = {};
+
+ button_options[PMA_messages.strSendErrorReport] = function () {
+ $dialog = $(this);
+ post_data = $.extend(report_data, {
+ send_error_report: true,
+ description: $("#report_description").val(),
+ always_send: $("#always_send_checkbox")[0].checked
+ });
+ $.post("error_report.php", post_data, function (data) {
+ $dialog.dialog('close');
+ if (data.success === false) {
+ //in the case of an error, show the error message returned.
+ PMA_ajaxShowMessage(data.error, false);
+ } else {
+ PMA_ajaxShowMessage(data.message, 3000);
+ }
+ });
+ };
+
+ button_options[PMA_messages.strCancel] = function () {
+ $(this).dialog('close');
+ };
+
+ $.post("error_report.php", report_data, function (data) {
+ if (data.success === false) {
+ //in the case of an error, show the error message returned.
+ PMA_ajaxShowMessage(data.error, false);
+ } else {
+ // Show dialog if the request was successful
+ $div
+ .append(data.message)
+ .dialog({
+ title: PMA_messages.strSubmitErrorReport,
+ width: 650,
+ modal: true,
+ buttons: button_options,
+ close: function () {
+ $(this).remove();
+ }
+ });
+ }
+ }); // end $.get()
+ },
+ /**
+ * Shows the small notification that asks for user permission
+ *
+ * @return void
+ */
+ _showErrorNotification: function () {
+ ErrorReport._removeErrorNotification();
+
+ $div = $(
+ '<div style="position:fixed;bottom:0;left:0;right:0;margin:0;' +
+ 'z-index:1000" class="error" id="error_notification"></div>'
+ ).append(
+ PMA_getImage("s_error.png") + PMA_messages.strErrorOccurred
+ );
+
+ $buttons = $('<div style="float:right"></div>');
+
+ button_html = '<button id="show_error_report">';
+ button_html += PMA_messages.strShowReportDetails;
+ button_html += '</button>';
+
+ button_html += '<a id="change_error_settings">';
+ button_html += PMA_getImage('s_cog.png', PMA_messages.strChangeReportSettings);
+ button_html += '</a>';
+
+ button_html += '<a href="#" id="ignore_error">';
+ button_html += PMA_getImage('b_close.png', PMA_messages.strIgnore);
+ button_html += '</a>';
+
+ $buttons.html(button_html);
+
+ $div.append($buttons);
+ $div.appendTo(document.body);
+ $("#change_error_settings").on("click", ErrorReport._redirect_to_settings);
+ $("#show_error_report").on("click", ErrorReport._createReportDialog);
+ $("#ignore_error").on("click", ErrorReport._removeErrorNotification);
+ },
+ /**
+ * Removes the notification if it was displayed before
+ *
+ * @return void
+ */
+ _removeErrorNotification: function () {
+ $("#error_notification").fadeOut(function () {
+ $(this).remove();
+ });
+ },
+ /**
+ * Shows the modal dialog previewing the report
+ *
+ * @return void
+ */
+ _createReportDialog: function () {
+ ErrorReport._removeErrorNotification();
+ ErrorReport._showReportDialog(ErrorReport._last_exception);
+ },
+ /**
+ * Returns the needed info about stored microhistory
+ *
+ * @return object
+ */
+ _get_microhistory: function () {
+ cached_pages = AJAX.cache.pages.slice(-7);
+ remove = ["common_query", "table", "db", "token", "pma_absolute_uri"];
+ return {
+ pages: cached_pages.map(function (page) {
+ simplepage = {
+ hash: page.hash
+ };
+
+ if (page.params) {
+ simplepage.params = $.extend({}, page.params);
+ $.each(simplepage.params, function (param) {
+ if ($.inArray(param, remove) != -1) {
+ delete simplepage.params[param];
+ }
+ });
+ }
+
+ return simplepage;
+ }),
+ current_index: AJAX.cache.current -
+ (AJAX.cache.pages.length - cached_pages.length)
+ };
+ },
+ /**
+ * Redirects to the settings page containing error report
+ * preferences
+ *
+ * @return void
+ */
+ _redirect_to_settings: function () {
+ window.location.href = "prefs_forms.php?token=" + PMA_commonParams.get('token');
+ },
+ /**
+ * Returns the report data to send to the server
+ *
+ * @param object exception info
+ *
+ * @return object
+ */
+ _get_report_data: function (exception) {
+ var report_data = {
+ "ajax_request": true,
+ "token": PMA_commonParams.get('token'),
+ "exception": exception,
+ "current_url": window.location.href,
+ "microhistory": ErrorReport._get_microhistory()
+ };
+ if (typeof AJAX.cache.pages[AJAX.cache.current - 1] !== 'undefined') {
+ report_data.scripts = AJAX.cache.pages[AJAX.cache.current - 1].scripts.map(
+ function (script) {
+ return script.name;
+ }
+ );
+ }
+ return report_data;
+ },
+ /**
+ * Wraps all global functions that start with PMA_
+ *
+ * @return void
+ */
+ wrap_global_functions: function () {
+ for (var key in window) {
+ var global = window[key];
+ if (typeof(global) === "function" && key.indexOf("PMA_") === 0) {
+ window[key] = ErrorReport.wrap_function(global);
+ }
+ }
+ },
+ /**
+ * Wraps given function in error reporting code and returns wrapped function
+ *
+ * @param function function to be wrapped
+ *
+ * @return function
+ */
+ wrap_function: function (func) {
+ if (!func.wrapped) {
+ var new_func = function () {
+ try {
+ return func.apply(this, arguments);
+ } catch (x) {
+ TraceKit.report(x);
+ }
+ };
+ new_func.wrapped = true;
+ //Set guid of wrapped function same as original function, so it can be removed
+ //See bug#4146 (problem with jquery draggable and sortable)
+ new_func.guid = func.guid = func.guid || new_func.guid || jQuery.guid++;
+ return new_func;
+ } else {
+ return func;
+ }
+ },
+ /**
+ * Automatically wraps the callback in AJAX.registerOnload
+ *
+ * @return void
+ */
+ _wrap_ajax_onload_callback: function () {
+ var oldOnload = AJAX.registerOnload;
+ AJAX.registerOnload = function (file, func) {
+ func = ErrorReport.wrap_function(func);
+ oldOnload.call(this, file, func);
+ };
+ },
+ /**
+ * Automatically wraps the callback in $.fn.on
+ *
+ * @return void
+ */
+ _wrap_$_on_callback: function () {
+ var oldOn = $.fn.on;
+ $.fn.on = function () {
+ for (var i = 1; i <= 3; i++) {
+ if (typeof(arguments[i]) === "function") {
+ arguments[i] = ErrorReport.wrap_function(arguments[i]);
+ break;
+ }
+ }
+ return oldOn.apply(this, arguments);
+ };
+ },
+ /**
+ * Wraps all global functions that start with PMA_
+ * also automatically wraps the callback in AJAX.registerOnload
+ *
+ * @return void
+ */
+ set_up_error_reporting: function () {
+ ErrorReport.wrap_global_functions();
+ ErrorReport._wrap_ajax_onload_callback();
+ ErrorReport._wrap_$_on_callback();
+ }
+
+};
+
+TraceKit.report.subscribe(ErrorReport.error_handler);
+ErrorReport.set_up_error_reporting();
+$(function () {
+ ErrorReport.wrap_global_functions();
+});
diff --git a/js/export.js b/js/export.js
new file mode 100644
index 0000000000..03dd329249
--- /dev/null
+++ b/js/export.js
@@ -0,0 +1,286 @@
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Functions used in the export tab
+ *
+ */
+
+/**
+ * Disables the "Dump some row(s)" sub-options
+ */
+function disable_dump_some_rows_sub_options()
+{
+ $("label[for='limit_to']").fadeTo('fast', 0.4);
+ $("label[for='limit_from']").fadeTo('fast', 0.4);
+ $("input[type='text'][name='limit_to']").prop('disabled', 'disabled');
+ $("input[type='text'][name='limit_from']").prop('disabled', 'disabled');
+}
+
+/**
+ * Enables the "Dump some row(s)" sub-options
+ */
+function enable_dump_some_rows_sub_options()
+{
+ $("label[for='limit_to']").fadeTo('fast', 1);
+ $("label[for='limit_from']").fadeTo('fast', 1);
+ $("input[type='text'][name='limit_to']").prop('disabled', '');
+ $("input[type='text'][name='limit_from']").prop('disabled', '');
+}
+
+/**
+ * Unbind all event handlers before tearing down a page
+ */
+AJAX.registerTeardown('export.js', function () {
+ $("#plugins").unbind('change');
+ $("input[type='radio'][name='sql_structure_or_data']").unbind('change');
+ $("input[type='radio'][name='latex_structure_or_data']").unbind('change');
+ $("input[type='radio'][name='odt_structure_or_data']").unbind('change');
+ $("input[type='radio'][name='texytext_structure_or_data']").unbind('change');
+ $("input[type='radio'][name='htmlword_structure_or_data']").unbind('change');
+ $("input[type='radio'][name='sql_structure_or_data']").unbind('change');
+ $("input[type='radio'][name='output_format']").unbind('change');
+ $("#checkbox_sql_include_comments").unbind('change');
+ $("#plugins").unbind('change');
+ $("input[type='radio'][name='quick_or_custom']").unbind('change');
+ $("input[type='radio'][name='allrows']").unbind('change');
+});
+
+AJAX.registerOnload('export.js', function () {
+ /**
+ * Toggles the hiding and showing of each plugin's options
+ * according to the currently selected plugin from the dropdown list
+ */
+ $("#plugins").change(function () {
+ $("#format_specific_opts div.format_specific_options").hide();
+ var selected_plugin_name = $("#plugins option:selected").val();
+ $("#" + selected_plugin_name + "_options").show();
+ });
+
+ /**
+ * Toggles the enabling and disabling of the SQL plugin's comment options that apply only when exporting structure
+ */
+ $("input[type='radio'][name='sql_structure_or_data']").change(function () {
+ var comments_are_present = $("#checkbox_sql_include_comments").prop("checked");
+ var show = $("input[type='radio'][name='sql_structure_or_data']:checked").val();
+ if (show == 'data') {
+ // disable the SQL comment options
+ if (comments_are_present) {
+ $("#checkbox_sql_dates").prop('disabled', true).parent().fadeTo('fast', 0.4);
+ }
+ $("#checkbox_sql_relation").prop('disabled', true).parent().fadeTo('fast', 0.4);
+ $("#checkbox_sql_mime").prop('disabled', true).parent().fadeTo('fast', 0.4);
+ } else {
+ // enable the SQL comment options
+ if (comments_are_present) {
+ $("#checkbox_sql_dates").removeProp('disabled').parent().fadeTo('fast', 1);
+ }
+ $("#checkbox_sql_relation").removeProp('disabled').parent().fadeTo('fast', 1);
+ $("#checkbox_sql_mime").removeProp('disabled').parent().fadeTo('fast', 1);
+ }
+ });
+});
+
+
+/**
+ * Toggles the hiding and showing of plugin structure-specific and data-specific
+ * options
+ */
+function toggle_structure_data_opts(pluginName)
+{
+ var radioFormName = pluginName + "_structure_or_data";
+ var dataDiv = "#" + pluginName + "_data";
+ var structureDiv = "#" + pluginName + "_structure";
+ var show = $("input[type='radio'][name='" + radioFormName + "']:checked").val();
+ if (show == 'data') {
+ $(dataDiv).slideDown('slow');
+ $(structureDiv).slideUp('slow');
+ } else {
+ $(structureDiv).slideDown('slow');
+ if (show == 'structure') {
+ $(dataDiv).slideUp('slow');
+ } else {
+ $(dataDiv).slideDown('slow');
+ }
+ }
+}
+
+AJAX.registerOnload('export.js', function () {
+ $("input[type='radio'][name='latex_structure_or_data']").change(function () {
+ toggle_structure_data_opts("latex");
+ });
+ $("input[type='radio'][name='odt_structure_or_data']").change(function () {
+ toggle_structure_data_opts("odt");
+ });
+ $("input[type='radio'][name='texytext_structure_or_data']").change(function () {
+ toggle_structure_data_opts("texytext");
+ });
+ $("input[type='radio'][name='htmlword_structure_or_data']").change(function () {
+ toggle_structure_data_opts("htmlword");
+ });
+ $("input[type='radio'][name='sql_structure_or_data']").change(function () {
+ toggle_structure_data_opts("sql");
+ });
+});
+
+/**
+ * Toggles the disabling of the "save to file" options
+ */
+function toggle_save_to_file()
+{
+ if (!$("#radio_dump_asfile").prop("checked")) {
+ $("#ul_save_asfile > li").fadeTo('fast', 0.4);
+ $("#ul_save_asfile > li > input").prop('disabled', true);
+ $("#ul_save_asfile > li> select").prop('disabled', true);
+ } else {
+ $("#ul_save_asfile > li").fadeTo('fast', 1);
+ $("#ul_save_asfile > li > input").prop('disabled', false);
+ $("#ul_save_asfile > li> select").prop('disabled', false);
+ }
+}
+
+AJAX.registerOnload('export.js', function () {
+ toggle_save_to_file();
+ $("input[type='radio'][name='output_format']").change(toggle_save_to_file);
+});
+
+/**
+ * For SQL plugin, toggles the disabling of the "display comments" options
+ */
+function toggle_sql_include_comments()
+{
+ $("#checkbox_sql_include_comments").change(function () {
+ if (!$("#checkbox_sql_include_comments").prop("checked")) {
+ $("#ul_include_comments > li").fadeTo('fast', 0.4);
+ $("#ul_include_comments > li > input").prop('disabled', true);
+ } else {
+ // If structure is not being exported, the comment options for structure should not be enabled
+ if ($("#radio_sql_structure_or_data_data").prop("checked")) {
+ $("#text_sql_header_comment").removeProp('disabled').parent("li").fadeTo('fast', 1);
+ } else {
+ $("#ul_include_comments > li").fadeTo('fast', 1);
+ $("#ul_include_comments > li > input").removeProp('disabled');
+ }
+ }
+ });
+}
+
+AJAX.registerOnload('export.js', function () {
+ /**
+ * For SQL plugin, if "CREATE TABLE options" is checked/unchecked, check/uncheck each of its sub-options
+ */
+ var $create = $("#checkbox_sql_create_table_statements");
+ var $create_options = $("#ul_create_table_statements input");
+ $create.change(function () {
+ $create_options.prop('checked', $(this).prop("checked"));
+ });
+ $create_options.change(function () {
+ if ($create_options.is(":checked")) {
+ $create.prop('checked', true);
+ }
+ });
+
+ /**
+ * Disables the view output as text option if the output must be saved as a file
+ */
+ $("#plugins").change(function () {
+ var active_plugin = $("#plugins option:selected").val();
+ var force_file = $("#force_file_" + active_plugin).val();
+ if (force_file == "true") {
+ if ($("#radio_dump_asfile").prop('checked') !== true) {
+ $("#radio_dump_asfile").prop('checked', true);
+ toggle_save_to_file();
+ }
+ $("#radio_view_as_text").prop('disabled', true).parent().fadeTo('fast', 0.4);
+ } else {
+ $("#radio_view_as_text").prop('disabled', false).parent().fadeTo('fast', 1);
+ }
+ });
+});
+
+/**
+ * Toggles display of options when quick and custom export are selected
+ */
+function toggle_quick_or_custom()
+{
+ if ($("#radio_custom_export").prop("checked")) {
+ $("#databases_and_tables").show();
+ $("#rows").show();
+ $("#output").show();
+ $("#format_specific_opts").show();
+ $("#output_quick_export").hide();
+ var selected_plugin_name = $("#plugins option:selected").val();
+ $("#" + selected_plugin_name + "_options").show();
+ } else {
+ $("#databases_and_tables").hide();
+ $("#rows").hide();
+ $("#output").hide();
+ $("#format_specific_opts").hide();
+ $("#output_quick_export").show();
+ }
+}
+var time_out;
+function check_time_out(time_limit)
+{
+ //margin of one second to avoid race condition to set/access session variable
+ time_limit = time_limit + 1;
+ var href = "export.php";
+ var params = {
+ 'ajax_request' : true,
+ 'token' : PMA_commonParams.get('token'),
+ 'check_time_out' : true
+ };
+ clearTimeout(time_out);
+ time_out = setTimeout(function(){
+ $.get(href, params, function (data) {
+ if (data['message'] !== 'success') {
+ PMA_ajaxShowMessage(
+ '<div class="error">' +
+ PMA_messages.strTimeOutError +
+ '</div>',
+ false
+ );
+ }
+ });
+ }, time_limit * 1000);
+
+}
+AJAX.registerOnload('export.js', function () {
+ $("input[type='radio'][name='quick_or_custom']").change(toggle_quick_or_custom);
+
+ /**
+ * Sets up the interface for Javascript-enabled browsers since the default is for
+ * Javascript-disabled browsers
+ * TODO: drop non-JS behaviour
+ */
+ if ($("input[type='hidden'][name='export_method']").val() != "custom-no-form") {
+ $("#quick_or_custom").show();
+ }
+ $("#scroll_to_options_msg").hide();
+ $("#format_specific_opts div.format_specific_options")
+ .hide()
+ .css({
+ "border": 0,
+ "margin": 0,
+ "padding": 0
+ })
+ .find("h3")
+ .remove();
+ toggle_quick_or_custom();
+ toggle_structure_data_opts($("select#plugins").val());
+ toggle_sql_include_comments();
+
+ /**
+ * Initially disables the "Dump some row(s)" sub-options
+ */
+ disable_dump_some_rows_sub_options();
+
+ /**
+ * Disables the "Dump some row(s)" sub-options when it is not selected
+ */
+ $("input[type='radio'][name='allrows']").change(function () {
+ if ($("input[type='radio'][name='allrows']").prop("checked")) {
+ enable_dump_some_rows_sub_options();
+ } else {
+ disable_dump_some_rows_sub_options();
+ }
+ });
+});
diff --git a/js/functions.js b/js/functions.js
new file mode 100644
index 0000000000..ba9a89c212
--- /dev/null
+++ b/js/functions.js
@@ -0,0 +1,4109 @@
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * general function, usually for data manipulation pages
+ *
+ */
+
+/**
+ * @var $table_clone reference to the action links on the tbl_structure page
+ */
+var $table_clone = false;
+
+/**
+ * @var sql_box_locked lock for the sqlbox textarea in the querybox/querywindow
+ */
+var sql_box_locked = false;
+
+/**
+ * @var array holds elements which content should only selected once
+ */
+var only_once_elements = [];
+
+/**
+ * @var int ajax_message_count Number of AJAX messages shown since page load
+ */
+var ajax_message_count = 0;
+
+/**
+ * @var codemirror_editor object containing CodeMirror editor of the query editor in SQL tab
+ */
+var codemirror_editor = false;
+
+/**
+ * @var codemirror_editor object containing CodeMirror editor of the inline query editor
+ */
+var codemirror_inline_editor = false;
+
+/**
+ * @var chart_activeTimeouts object active timeouts that refresh the charts. When disabling a realtime chart, this can be used to stop the continuous ajax requests
+ */
+var chart_activeTimeouts = {};
+
+/**
+ * Make sure that ajax requests will not be cached
+ * by appending a random variable to their parameters
+ */
+$.ajaxPrefilter(function (options, originalOptions, jqXHR) {
+ var nocache = new Date().getTime() + "" + Math.floor(Math.random() * 1000000);
+ if (typeof options.data == "string") {
+ options.data += "&_nocache=" + nocache;
+ } else if (typeof options.data == "object") {
+ options.data = $.extend(originalOptions.data, {'_nocache' : nocache});
+ }
+});
+
+/**
+ * Add a hidden field to the form to indicate that this will be an
+ * Ajax request (only if this hidden field does not exist)
+ *
+ * @param object the form
+ */
+function PMA_prepareForAjaxRequest($form)
+{
+ if (! $form.find('input:hidden').is('#ajax_request_hidden')) {
+ $form.append('<input type="hidden" id="ajax_request_hidden" name="ajax_request" value="true" />');
+ }
+}
+
+/**
+ * Generate a new password and copy it to the password input areas
+ *
+ * @param object the form that holds the password fields
+ *
+ * @return boolean always true
+ */
+function suggestPassword(passwd_form)
+{
+ // restrict the password to just letters and numbers to avoid problems:
+ // "editors and viewers regard the password as multiple words and
+ // things like double click no longer work"
+ var pwchars = "abcdefhjmnpqrstuvwxyz23456789ABCDEFGHJKLMNPQRSTUVWYXZ";
+ var passwordlength = 16; // do we want that to be dynamic? no, keep it simple :)
+ var passwd = passwd_form.generated_pw;
+ passwd.value = '';
+
+ for (var i = 0; i < passwordlength; i++) {
+ passwd.value += pwchars.charAt(Math.floor(Math.random() * pwchars.length));
+ }
+ passwd_form.text_pma_pw.value = passwd.value;
+ passwd_form.text_pma_pw2.value = passwd.value;
+ return true;
+}
+
+/**
+ * Version string to integer conversion.
+ */
+function parseVersionString(str)
+{
+ if (typeof(str) != 'string') { return false; }
+ var add = 0;
+ // Parse possible alpha/beta/rc/
+ var state = str.split('-');
+ if (state.length >= 2) {
+ if (state[1].substr(0, 2) == 'rc') {
+ add = - 20 - parseInt(state[1].substr(2), 10);
+ } else if (state[1].substr(0, 4) == 'beta') {
+ add = - 40 - parseInt(state[1].substr(4), 10);
+ } else if (state[1].substr(0, 5) == 'alpha') {
+ add = - 60 - parseInt(state[1].substr(5), 10);
+ } else if (state[1].substr(0, 3) == 'dev') {
+ /* We don't handle dev, it's git snapshot */
+ add = 0;
+ }
+ }
+ // Parse version
+ var x = str.split('.');
+ // Use 0 for non existing parts
+ var maj = parseInt(x[0], 10) || 0;
+ var min = parseInt(x[1], 10) || 0;
+ var pat = parseInt(x[2], 10) || 0;
+ var hotfix = parseInt(x[3], 10) || 0;
+ return maj * 100000000 + min * 1000000 + pat * 10000 + hotfix * 100 + add;
+}
+
+/**
+ * Indicates current available version on main page.
+ */
+function PMA_current_version(data)
+{
+ if (data && data.version && data.date) {
+ var current = parseVersionString(pmaversion);
+ var latest = parseVersionString(data.version);
+ var version_information_message = '<span>'
+ + PMA_messages.strLatestAvailable
+ + ' ' + escapeHtml(data.version)
+ + '</span>';
+ if (latest > current) {
+ var message = $.sprintf(
+ PMA_messages.strNewerVersion,
+ escapeHtml(data.version),
+ escapeHtml(data.date)
+ );
+ var htmlClass = 'notice';
+ if (Math.floor(latest / 10000) === Math.floor(current / 10000)) {
+ /* Security update */
+ htmlClass = 'error';
+ }
+ $('#maincontainer').after('<div class="' + htmlClass + '">' + message + '</div>');
+ }
+ if (latest === current) {
+ version_information_message = ' (' + PMA_messages.strUpToDate + ')';
+ }
+ $('#li_pma_version span').remove();
+ $('#li_pma_version').append(version_information_message);
+ }
+}
+
+/**
+ * Loads Git revision data from ajax for index.php
+ */
+function PMA_display_git_revision()
+{
+ $('#is_git_revision').remove();
+ $('#li_pma_version_git').remove();
+ $.get(
+ "index.php",
+ {
+ "server": PMA_commonParams.get('server'),
+ "token": PMA_commonParams.get('token'),
+ "git_revision": true,
+ "ajax_request": true
+ },
+ function (data) {
+ if (data.success === true) {
+ $(data.message).insertAfter('#li_pma_version');
+ }
+ }
+ );
+}
+
+/**
+ * for libraries/display_change_password.lib.php
+ * libraries/user_password.php
+ *
+ */
+
+function displayPasswordGenerateButton()
+{
+ $('#tr_element_before_generate_password').parent().append('<tr class="vmiddle"><td>' + PMA_messages.strGeneratePassword + '</td><td><input type="button" class="button" id="button_generate_password" value="' + PMA_messages.strGenerate + '" onclick="suggestPassword(this.form)" /><input type="text" name="generated_pw" id="generated_pw" /></td></tr>');
+ $('#div_element_before_generate_password').parent().append('<div class="item"><label for="button_generate_password">' + PMA_messages.strGeneratePassword + ':</label><span class="options"><input type="button" class="button" id="button_generate_password" value="' + PMA_messages.strGenerate + '" onclick="suggestPassword(this.form)" /></span><input type="text" name="generated_pw" id="generated_pw" /></div>');
+}
+
+/*
+ * Adds a date/time picker to an element
+ *
+ * @param object $this_element a jQuery object pointing to the element
+ */
+function PMA_addDatepicker($this_element, options)
+{
+ var showTimeOption = false;
+ if ($this_element.is('.datetimefield')) {
+ showTimeOption = true;
+ }
+
+ var defaultOptions = {
+ showOn: 'button',
+ buttonImage: themeCalendarImage, // defined in js/messages.php
+ buttonImageOnly: true,
+ stepMinutes: 1,
+ stepHours: 1,
+ showSecond: true,
+ showMillisec: true,
+ showMicrosec: true,
+ showTimepicker: showTimeOption,
+ showButtonPanel: false,
+ dateFormat: 'yy-mm-dd', // yy means year with four digits
+ timeFormat: 'HH:mm:ss.lc',
+ constrainInput: false,
+ altFieldTimeOnly: false,
+ showAnim: '',
+ beforeShow: function (input, inst) {
+ // Remember that we came from the datepicker; this is used
+ // in tbl_change.js by verificationsAfterFieldChange()
+ $this_element.data('comes_from', 'datepicker');
+
+ // Fix wrong timepicker z-index, doesn't work without timeout
+ setTimeout(function () {
+ $('#ui-timepicker-div').css('z-index', $('#ui-datepicker-div').css('z-index'));
+ }, 0);
+ },
+ onClose: function (dateText, dp_inst) {
+ // The value is no more from the date picker
+ $this_element.data('comes_from', '');
+ }
+ };
+ if (showTimeOption || (typeof(options) != 'undefined' && options.showTimepicker)) {
+ $this_element.datetimepicker($.extend(defaultOptions, options));
+ } else {
+ $this_element.datepicker($.extend(defaultOptions, options));
+ }
+}
+
+/**
+ * selects the content of a given object, f.e. a textarea
+ *
+ * @param object element element of which the content will be selected
+ * @param var lock variable which holds the lock for this element
+ * or true, if no lock exists
+ * @param boolean only_once if true this is only done once
+ * f.e. only on first focus
+ */
+function selectContent(element, lock, only_once)
+{
+ if (only_once && only_once_elements[element.name]) {
+ return;
+ }
+
+ only_once_elements[element.name] = true;
+
+ if (lock) {
+ return;
+ }
+
+ element.select();
+}
+
+/**
+ * Displays a confirmation box before submitting a "DROP/DELETE/ALTER" query.
+ * This function is called while clicking links
+ *
+ * @param object the link
+ * @param object the sql query to submit
+ *
+ * @return boolean whether to run the query or not
+ */
+function confirmLink(theLink, theSqlQuery)
+{
+ // Confirmation is not required in the configuration file
+ // or browser is Opera (crappy js implementation)
+ if (PMA_messages.strDoYouReally === '' || typeof(window.opera) != 'undefined') {
+ return true;
+ }
+
+ var is_confirmed = confirm($.sprintf(PMA_messages.strDoYouReally, theSqlQuery));
+ if (is_confirmed) {
+ if ($(theLink).hasClass('formLinkSubmit')) {
+ var name = 'is_js_confirmed';
+ if ($(theLink).attr('href').indexOf('usesubform') != -1) {
+ name = 'subform[' + $(theLink).attr('href').substr('#').match(/usesubform\[(\d+)\]/i)[1] + '][is_js_confirmed]';
+ }
+
+ $(theLink).parents('form').append('<input type="hidden" name="' + name + '" value="1" />');
+ } else if (typeof(theLink.href) != 'undefined') {
+ theLink.href += '&is_js_confirmed=1';
+ } else if (typeof(theLink.form) != 'undefined') {
+ theLink.form.action += '?is_js_confirmed=1';
+ }
+ }
+
+ return is_confirmed;
+} // end of the 'confirmLink()' function
+
+/**
+ * Displays an error message if a "DROP DATABASE" statement is submitted
+ * while it isn't allowed, else confirms a "DROP/DELETE/ALTER" query before
+ * sumitting it if required.
+ * This function is called by the 'checkSqlQuery()' js function.
+ *
+ * @param object the form
+ * @param object the sql query textarea
+ *
+ * @return boolean whether to run the query or not
+ *
+ * @see checkSqlQuery()
+ */
+function confirmQuery(theForm1, sqlQuery1)
+{
+ // Confirmation is not required in the configuration file
+ if (PMA_messages.strDoYouReally === '') {
+ return true;
+ }
+
+ // "DROP DATABASE" statement isn't allowed
+ if (PMA_messages.strNoDropDatabases !== '') {
+ var drop_re = new RegExp('(^|;)\\s*DROP\\s+(IF EXISTS\\s+)?DATABASE\\s', 'i');
+ if (drop_re.test(sqlQuery1.value)) {
+ alert(PMA_messages.strNoDropDatabases);
+ theForm1.reset();
+ sqlQuery1.focus();
+ return false;
+ } // end if
+ } // end if
+
+ // Confirms a "DROP/DELETE/ALTER/TRUNCATE" statement
+ //
+ // TODO: find a way (if possible) to use the parser-analyser
+ // for this kind of verification
+ // For now, I just added a ^ to check for the statement at
+ // beginning of expression
+
+ var do_confirm_re_0 = new RegExp('^\\s*DROP\\s+(IF EXISTS\\s+)?(TABLE|DATABASE|PROCEDURE)\\s', 'i');
+ var do_confirm_re_1 = new RegExp('^\\s*ALTER\\s+TABLE\\s+((`[^`]+`)|([A-Za-z0-9_$]+))\\s+DROP\\s', 'i');
+ var do_confirm_re_2 = new RegExp('^\\s*DELETE\\s+FROM\\s', 'i');
+ var do_confirm_re_3 = new RegExp('^\\s*TRUNCATE\\s', 'i');
+
+ if (do_confirm_re_0.test(sqlQuery1.value) ||
+ do_confirm_re_1.test(sqlQuery1.value) ||
+ do_confirm_re_2.test(sqlQuery1.value) ||
+ do_confirm_re_3.test(sqlQuery1.value)) {
+ var message;
+ if (sqlQuery1.value.length > 100) {
+ message = sqlQuery1.value.substr(0, 100) + '\n ...';
+ } else {
+ message = sqlQuery1.value;
+ }
+ var is_confirmed = confirm($.sprintf(PMA_messages.strDoYouReally, message));
+ // statement is confirmed -> update the
+ // "is_js_confirmed" form field so the confirm test won't be
+ // run on the server side and allows to submit the form
+ if (is_confirmed) {
+ theForm1.elements['is_js_confirmed'].value = 1;
+ return true;
+ }
+ // statement is rejected -> do not submit the form
+ else {
+ window.focus();
+ sqlQuery1.focus();
+ return false;
+ } // end if (handle confirm box result)
+ } // end if (display confirm box)
+
+ return true;
+} // end of the 'confirmQuery()' function
+
+/**
+ * Displays an error message if the user submitted the sql query form with no
+ * sql query, else checks for "DROP/DELETE/ALTER" statements
+ *
+ * @param object the form
+ *
+ * @return boolean always false
+ *
+ * @see confirmQuery()
+ */
+function checkSqlQuery(theForm)
+{
+ // get the textarea element containing the query
+ if (codemirror_editor) {
+ codemirror_editor.save();
+ var sqlQuery = codemirror_editor.display.input;
+ sqlQuery.value = codemirror_editor.getValue();
+ } else {
+ var sqlQuery = theForm.elements['sql_query'];
+ }
+ var isEmpty = 1;
+ var space_re = new RegExp('\\s+');
+ if (typeof(theForm.elements['sql_file']) != 'undefined' &&
+ theForm.elements['sql_file'].value.replace(space_re, '') !== '') {
+ return true;
+ }
+ if (typeof(theForm.elements['sql_localfile']) != 'undefined' &&
+ theForm.elements['sql_localfile'].value.replace(space_re, '') !== '') {
+ return true;
+ }
+ if (isEmpty && typeof(theForm.elements['id_bookmark']) != 'undefined' &&
+ (theForm.elements['id_bookmark'].value !== null || theForm.elements['id_bookmark'].value !== '') &&
+ theForm.elements['id_bookmark'].selectedIndex !== 0) {
+ return true;
+ }
+ // Checks for "DROP/DELETE/ALTER" statements
+ if (sqlQuery.value.replace(space_re, '') !== '') {
+ if (confirmQuery(theForm, sqlQuery)) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+ theForm.reset();
+ isEmpty = 1;
+
+ if (isEmpty) {
+ sqlQuery.select();
+ alert(PMA_messages.strFormEmpty);
+ sqlQuery.focus();
+ return false;
+ }
+
+ return true;
+} // end of the 'checkSqlQuery()' function
+
+/**
+ * Check if a form's element is empty.
+ * An element containing only spaces is also considered empty
+ *
+ * @param object the form
+ * @param string the name of the form field to put the focus on
+ *
+ * @return boolean whether the form field is empty or not
+ */
+function emptyCheckTheField(theForm, theFieldName)
+{
+ var theField = theForm.elements[theFieldName];
+ var space_re = new RegExp('\\s+');
+ return (theField.value.replace(space_re, '') === '') ? 1 : 0;
+} // end of the 'emptyCheckTheField()' function
+
+
+/**
+ * Check whether a form field is empty or not
+ *
+ * @param object the form
+ * @param string the name of the form field to put the focus on
+ *
+ * @return boolean whether the form field is empty or not
+ */
+function emptyFormElements(theForm, theFieldName)
+{
+ var theField = theForm.elements[theFieldName];
+ var isEmpty = emptyCheckTheField(theForm, theFieldName);
+
+
+ return isEmpty;
+} // end of the 'emptyFormElements()' function
+
+
+/**
+ * Ensures a value submitted in a form is numeric and is in a range
+ *
+ * @param object the form
+ * @param string the name of the form field to check
+ * @param integer the minimum authorized value
+ * @param integer the maximum authorized value
+ *
+ * @return boolean whether a valid number has been submitted or not
+ */
+function checkFormElementInRange(theForm, theFieldName, message, min, max)
+{
+ var theField = theForm.elements[theFieldName];
+ var val = parseInt(theField.value, 10);
+
+ if (typeof(min) == 'undefined') {
+ min = 0;
+ }
+ if (typeof(max) == 'undefined') {
+ max = Number.MAX_VALUE;
+ }
+
+ // It's not a number
+ if (isNaN(val)) {
+ theField.select();
+ alert(PMA_messages.strEnterValidNumber);
+ theField.focus();
+ return false;
+ }
+ // It's a number but it is not between min and max
+ else if (val < min || val > max) {
+ theField.select();
+ alert($.sprintf(message, val));
+ theField.focus();
+ return false;
+ }
+ // It's a valid number
+ else {
+ theField.value = val;
+ }
+ return true;
+
+} // end of the 'checkFormElementInRange()' function
+
+
+function checkTableEditForm(theForm, fieldsCnt)
+{
+ // TODO: avoid sending a message if user just wants to add a line
+ // on the form but has not completed at least one field name
+
+ var atLeastOneField = 0;
+ var i, elm, elm2, elm3, val, id;
+
+ for (i = 0; i < fieldsCnt; i++) {
+ id = "#field_" + i + "_2";
+ elm = $(id);
+ val = elm.val();
+ if (val == 'VARCHAR' || val == 'CHAR' || val == 'BIT' || val == 'VARBINARY' || val == 'BINARY') {
+ elm2 = $("#field_" + i + "_3");
+ val = parseInt(elm2.val(), 10);
+ elm3 = $("#field_" + i + "_1");
+ if (isNaN(val) && elm3.val() !== "") {
+ elm2.select();
+ alert(PMA_messages.strEnterValidLength);
+ elm2.focus();
+ return false;
+ }
+ }
+
+ if (atLeastOneField === 0) {
+ id = "field_" + i + "_1";
+ if (!emptyCheckTheField(theForm, id)) {
+ atLeastOneField = 1;
+ }
+ }
+ }
+ if (atLeastOneField === 0) {
+ var theField = theForm.elements["field_0_1"];
+ alert(PMA_messages.strFormEmpty);
+ theField.focus();
+ return false;
+ }
+
+ // at least this section is under jQuery
+ if ($("input.textfield[name='table']").val() === "") {
+ alert(PMA_messages.strFormEmpty);
+ $("input.textfield[name='table']").focus();
+ return false;
+ }
+
+
+ return true;
+} // enf of the 'checkTableEditForm()' function
+
+/**
+ * Unbind all event handlers before tearing down a page
+ */
+AJAX.registerTeardown('functions.js', function () {
+ $('input:checkbox.checkall').die('click');
+});
+AJAX.registerOnload('functions.js', function () {
+ /**
+ * Row marking in horizontal mode (use "live" so that it works also for
+ * next pages reached via AJAX); a tr may have the class noclick to remove
+ * this behavior.
+ */
+
+ $('input:checkbox.checkall').live('click', function (e) {
+ var $tr = $(this).closest('tr');
+
+ // make the table unselectable (to prevent default highlighting when shift+click)
+ //$tr.parents('table').noSelect();
+
+ if (!e.shiftKey || last_clicked_row == -1) {
+ // usual click
+
+ // XXX: FF fires two click events for <label> (label and checkbox), so we need to handle this differently
+ var $checkbox = $tr.find(':checkbox');
+ if ($checkbox.length) {
+ // checkbox in a row, add or remove class depending on checkbox state
+ var checked = $checkbox.prop('checked');
+ if (!$(e.target).is(':checkbox, label')) {
+ checked = !checked;
+ $checkbox.prop('checked', checked).trigger('change');
+ }
+ if (checked) {
+ $tr.addClass('marked');
+ } else {
+ $tr.removeClass('marked');
+ }
+ last_click_checked = checked;
+ } else {
+ // normal data table, just toggle class
+ $tr.toggleClass('marked');
+ last_click_checked = false;
+ }
+
+ // remember the last clicked row
+ last_clicked_row = last_click_checked ? $('tr.odd:not(.noclick), tr.even:not(.noclick)').index($tr) : -1;
+ last_shift_clicked_row = -1;
+ } else {
+ // handle the shift click
+ PMA_clearSelection();
+ var start, end;
+
+ // clear last shift click result
+ if (last_shift_clicked_row >= 0) {
+ if (last_shift_clicked_row >= last_clicked_row) {
+ start = last_clicked_row;
+ end = last_shift_clicked_row;
+ } else {
+ start = last_shift_clicked_row;
+ end = last_clicked_row;
+ }
+ $tr.parent().find('tr.odd:not(.noclick), tr.even:not(.noclick)')
+ .slice(start, end + 1)
+ .removeClass('marked')
+ .find(':checkbox')
+ .prop('checked', false)
+ .trigger('change');
+ }
+
+ // handle new shift click
+ var curr_row = $('tr.odd:not(.noclick), tr.even:not(.noclick)').index($tr);
+ if (curr_row >= last_clicked_row) {
+ start = last_clicked_row;
+ end = curr_row;
+ } else {
+ start = curr_row;
+ end = last_clicked_row;
+ }
+ $tr.parent().find('tr.odd:not(.noclick), tr.even:not(.noclick)')
+ .slice(start, end + 1)
+ .addClass('marked')
+ .find(':checkbox')
+ .prop('checked', true)
+ .trigger('change');
+
+ // remember the last shift clicked row
+ last_shift_clicked_row = curr_row;
+ }
+ });
+
+ addDateTimePicker();
+
+ /**
+ * Add attribute to text boxes for iOS devices (based on bugID: 3508912)
+ */
+ if (navigator.userAgent.match(/(iphone|ipod|ipad)/i)) {
+ $('input[type=text]').attr('autocapitalize', 'off').attr('autocorrect', 'off');
+ }
+});
+
+/**
+ * True if last click is to check a row.
+ */
+var last_click_checked = false;
+
+/**
+ * Zero-based index of last clicked row.
+ * Used to handle the shift + click event in the code above.
+ */
+var last_clicked_row = -1;
+
+/**
+ * Zero-based index of last shift clicked row.
+ */
+var last_shift_clicked_row = -1;
+
+/**
+ * Row highlighting in horizontal mode (use "live"
+ * so that it works also for pages reached via AJAX)
+ */
+/*AJAX.registerOnload('functions.js', function () {
+ $('tr.odd, tr.even').live('hover',function (event) {
+ var $tr = $(this);
+ $tr.toggleClass('hover',event.type=='mouseover');
+ $tr.children().toggleClass('hover',event.type=='mouseover');
+ });
+})*/
+
+/**
+ * This array is used to remember mark status of rows in browse mode
+ */
+var marked_row = [];
+
+/**
+ * marks all rows and selects its first checkbox inside the given element
+ * the given element is usaly a table or a div containing the table or tables
+ *
+ * @param container DOM element
+ */
+function markAllRows(container_id)
+{
+
+ $("#" + container_id).find("input:checkbox:enabled").prop('checked', true)
+ .trigger("change")
+ .parents("tr").addClass("marked");
+ return true;
+}
+
+/**
+ * marks all rows and selects its first checkbox inside the given element
+ * the given element is usaly a table or a div containing the table or tables
+ *
+ * @param container DOM element
+ */
+function unMarkAllRows(container_id)
+{
+
+ $("#" + container_id).find("input:checkbox:enabled").prop('checked', false)
+ .trigger("change")
+ .parents("tr").removeClass("marked");
+ return true;
+}
+
+/**
+ * Checks/unchecks all checkbox in given conainer (f.e. a form, fieldset or div)
+ *
+ * @param string container_id the container id
+ * @param boolean state new value for checkbox (true or false)
+ * @return boolean always true
+ */
+function setCheckboxes(container_id, state)
+{
+
+ $("#" + container_id).find("input:checkbox").prop('checked', state);
+ return true;
+} // end of the 'setCheckboxes()' function
+
+/**
+ * Checks/unchecks all options of a <select> element
+ *
+ * @param string the form name
+ * @param string the element name
+ * @param boolean whether to check or to uncheck options
+ *
+ * @return boolean always true
+ */
+function setSelectOptions(the_form, the_select, do_check)
+{
+ $("form[name='" + the_form + "'] select[name='" + the_select + "']").find("option").prop('selected', do_check);
+ return true;
+} // end of the 'setSelectOptions()' function
+
+/**
+ * Sets current value for query box.
+ */
+function setQuery(query)
+{
+ if (codemirror_editor) {
+ codemirror_editor.setValue(query);
+ codemirror_editor.focus();
+ } else {
+ document.sqlform.sql_query.value = query;
+ document.sqlform.sql_query.focus();
+ }
+}
+
+
+/**
+ * Create quick sql statements.
+ *
+ */
+function insertQuery(queryType)
+{
+ if (queryType == "clear") {
+ setQuery('');
+ return;
+ }
+
+ var query = "";
+ var myListBox = document.sqlform.dummy;
+ var table = document.sqlform.table.value;
+
+ if (myListBox.options.length > 0) {
+ sql_box_locked = true;
+ var chaineAj = "";
+ var valDis = "";
+ var editDis = "";
+ var NbSelect = 0;
+ for (var i = 0; i < myListBox.options.length; i++) {
+ NbSelect++;
+ if (NbSelect > 1) {
+ chaineAj += ", ";
+ valDis += ",";
+ editDis += ",";
+ }
+ chaineAj += myListBox.options[i].value;
+ valDis += "[value-" + NbSelect + "]";
+ editDis += myListBox.options[i].value + "=[value-" + NbSelect + "]";
+ }
+ if (queryType == "selectall") {
+ query = "SELECT * FROM `" + table + "` WHERE 1";
+ } else if (queryType == "select") {
+ query = "SELECT " + chaineAj + " FROM `" + table + "` WHERE 1";
+ } else if (queryType == "insert") {
+ query = "INSERT INTO `" + table + "`(" + chaineAj + ") VALUES (" + valDis + ")";
+ } else if (queryType == "update") {
+ query = "UPDATE `" + table + "` SET " + editDis + " WHERE 1";
+ } else if (queryType == "delete") {
+ query = "DELETE FROM `" + table + "` WHERE 1";
+ }
+ setQuery(query);
+ sql_box_locked = false;
+ }
+}
+
+
+/**
+ * Inserts multiple fields.
+ *
+ */
+function insertValueQuery()
+{
+ var myQuery = document.sqlform.sql_query;
+ var myListBox = document.sqlform.dummy;
+
+ if (myListBox.options.length > 0) {
+ sql_box_locked = true;
+ var chaineAj = "";
+ var NbSelect = 0;
+ for (var i = 0; i < myListBox.options.length; i++) {
+ if (myListBox.options[i].selected) {
+ NbSelect++;
+ if (NbSelect > 1) {
+ chaineAj += ", ";
+ }
+ chaineAj += myListBox.options[i].value;
+ }
+ }
+
+ /* CodeMirror support */
+ if (codemirror_editor) {
+ codemirror_editor.replaceSelection(chaineAj);
+ //IE support
+ } else if (document.selection) {
+ myQuery.focus();
+ var sel = document.selection.createRange();
+ sel.text = chaineAj;
+ document.sqlform.insert.focus();
+ }
+ //MOZILLA/NETSCAPE support
+ else if (document.sqlform.sql_query.selectionStart || document.sqlform.sql_query.selectionStart == "0") {
+ var startPos = document.sqlform.sql_query.selectionStart;
+ var endPos = document.sqlform.sql_query.selectionEnd;
+ var chaineSql = document.sqlform.sql_query.value;
+
+ myQuery.value = chaineSql.substring(0, startPos) + chaineAj + chaineSql.substring(endPos, chaineSql.length);
+ } else {
+ myQuery.value += chaineAj;
+ }
+ sql_box_locked = false;
+ }
+}
+
+/**
+ * Add a date/time picker to each element that needs it
+ * (only when jquery-ui-timepicker-addon.js is loaded)
+ */
+function addDateTimePicker() {
+ if ($.timepicker !== undefined) {
+ $('input.datefield, input.datetimefield').each(function () {
+
+ no_decimals = $(this).parent().data('decimals');
+ var showMillisec = false;
+ var showMicrosec = false;
+ var timeFormat = 'HH:mm:ss';
+ // check for decimal places of seconds
+ if (($(this).parent().data('decimals') > 0) && ($(this).parent().data('type').indexOf('time') != -1)){
+ showMillisec = true;
+ timeFormat = 'HH:mm:ss.lc';
+ if ($(this).parent().data('decimals') > 3) {
+ showMicrosec = true;
+ }
+ }
+ PMA_addDatepicker($(this), {
+ showMillisec: showMillisec,
+ showMicrosec: showMicrosec,
+ timeFormat: timeFormat,
+ });
+ })
+ }
+}
+
+/**
+ * Refresh/resize the WYSIWYG scratchboard
+ */
+function refreshLayout()
+{
+ var $elm = $('#pdflayout');
+ var orientation = $('#orientation_opt').val();
+ var paper = 'A4';
+ if ($('#paper_opt').length == 1) {
+ paper = $('#paper_opt').val();
+ }
+ var posa = 'y';
+ var posb = 'x';
+ if (orientation == 'P') {
+ posa = 'x';
+ posb = 'y';
+ }
+ $elm.css('width', pdfPaperSize(paper, posa) + 'px');
+ $elm.css('height', pdfPaperSize(paper, posb) + 'px');
+}
+
+/**
+ * Initializes positions of elements.
+ */
+function TableDragInit() {
+ $('.pdflayout_table').each(function () {
+ var $this = $(this);
+ var number = $this.data('number');
+ var x = $('#c_table_' + number + '_x').val();
+ var y = $('#c_table_' + number + '_y').val();
+ $this.css('left', x + 'px');
+ $this.css('top', y + 'px');
+ /* Make elements draggable */
+ $this.draggable({
+ containment: "parent",
+ drag: function (evt, ui) {
+ var number = $this.data('number');
+ $('#c_table_' + number + '_x').val(parseInt(ui.position.left, 10));
+ $('#c_table_' + number + '_y').val(parseInt(ui.position.top, 10));
+ }
+ });
+ });
+}
+
+/**
+ * Resets drag and drop positions.
+ */
+function resetDrag() {
+ $('.pdflayout_table').each(function () {
+ var $this = $(this);
+ var x = $this.data('x');
+ var y = $this.data('y');
+ $this.css('left', x + 'px');
+ $this.css('top', y + 'px');
+ });
+}
+
+/**
+ * User schema handlers.
+ */
+$(function () {
+ /* Move in scratchboard on manual change */
+ $('.position-change').live('change', function () {
+ var $this = $(this);
+ var $elm = $('#table_' + $this.data('number'));
+ $elm.css($this.data('axis'), $this.val() + 'px');
+ });
+ /* Refresh on paper size/orientation change */
+ $('.paper-change').live('change', function () {
+ var $elm = $('#pdflayout');
+ if ($elm.css('visibility') == 'visible') {
+ refreshLayout();
+ TableDragInit();
+ }
+ });
+ /* Show/hide the WYSIWYG scratchboard */
+ $('#toggle-dragdrop').live('click', function () {
+ var $elm = $('#pdflayout');
+ if ($elm.css('visibility') == 'hidden') {
+ refreshLayout();
+ TableDragInit();
+ $elm.css('visibility', 'visible');
+ $elm.css('display', 'block');
+ $('#showwysiwyg').val('1');
+ } else {
+ $elm.css('visibility', 'hidden');
+ $elm.css('display', 'none');
+ $('#showwysiwyg').val('0');
+ }
+ });
+ /* Reset scratchboard */
+ $('#reset-dragdrop').live('click', function () {
+ resetDrag();
+ });
+});
+
+/**
+ * Returns paper sizes for a given format
+ */
+function pdfPaperSize(format, axis)
+{
+ switch (format.toUpperCase()) {
+ case '4A0':
+ if (axis == 'x') {
+ return 4767.87;
+ } else {
+ return 6740.79;
+ }
+ break;
+ case '2A0':
+ if (axis == 'x') {
+ return 3370.39;
+ } else {
+ return 4767.87;
+ }
+ break;
+ case 'A0':
+ if (axis == 'x') {
+ return 2383.94;
+ } else {
+ return 3370.39;
+ }
+ break;
+ case 'A1':
+ if (axis == 'x') {
+ return 1683.78;
+ } else {
+ return 2383.94;
+ }
+ break;
+ case 'A2':
+ if (axis == 'x') {
+ return 1190.55;
+ } else {
+ return 1683.78;
+ }
+ break;
+ case 'A3':
+ if (axis == 'x') {
+ return 841.89;
+ } else {
+ return 1190.55;
+ }
+ break;
+ case 'A4':
+ if (axis == 'x') {
+ return 595.28;
+ } else {
+ return 841.89;
+ }
+ break;
+ case 'A5':
+ if (axis == 'x') {
+ return 419.53;
+ } else {
+ return 595.28;
+ }
+ break;
+ case 'A6':
+ if (axis == 'x') {
+ return 297.64;
+ } else {
+ return 419.53;
+ }
+ break;
+ case 'A7':
+ if (axis == 'x') {
+ return 209.76;
+ } else {
+ return 297.64;
+ }
+ break;
+ case 'A8':
+ if (axis == 'x') {
+ return 147.40;
+ } else {
+ return 209.76;
+ }
+ break;
+ case 'A9':
+ if (axis == 'x') {
+ return 104.88;
+ } else {
+ return 147.40;
+ }
+ break;
+ case 'A10':
+ if (axis == 'x') {
+ return 73.70;
+ } else {
+ return 104.88;
+ }
+ break;
+ case 'B0':
+ if (axis == 'x') {
+ return 2834.65;
+ } else {
+ return 4008.19;
+ }
+ break;
+ case 'B1':
+ if (axis == 'x') {
+ return 2004.09;
+ } else {
+ return 2834.65;
+ }
+ break;
+ case 'B2':
+ if (axis == 'x') {
+ return 1417.32;
+ } else {
+ return 2004.09;
+ }
+ break;
+ case 'B3':
+ if (axis == 'x') {
+ return 1000.63;
+ } else {
+ return 1417.32;
+ }
+ break;
+ case 'B4':
+ if (axis == 'x') {
+ return 708.66;
+ } else {
+ return 1000.63;
+ }
+ break;
+ case 'B5':
+ if (axis == 'x') {
+ return 498.90;
+ } else {
+ return 708.66;
+ }
+ break;
+ case 'B6':
+ if (axis == 'x') {
+ return 354.33;
+ } else {
+ return 498.90;
+ }
+ break;
+ case 'B7':
+ if (axis == 'x') {
+ return 249.45;
+ } else {
+ return 354.33;
+ }
+ break;
+ case 'B8':
+ if (axis == 'x') {
+ return 175.75;
+ } else {
+ return 249.45;
+ }
+ break;
+ case 'B9':
+ if (axis == 'x') {
+ return 124.72;
+ } else {
+ return 175.75;
+ }
+ break;
+ case 'B10':
+ if (axis == 'x') {
+ return 87.87;
+ } else {
+ return 124.72;
+ }
+ break;
+ case 'C0':
+ if (axis == 'x') {
+ return 2599.37;
+ } else {
+ return 3676.54;
+ }
+ break;
+ case 'C1':
+ if (axis == 'x') {
+ return 1836.85;
+ } else {
+ return 2599.37;
+ }
+ break;
+ case 'C2':
+ if (axis == 'x') {
+ return 1298.27;
+ } else {
+ return 1836.85;
+ }
+ break;
+ case 'C3':
+ if (axis == 'x') {
+ return 918.43;
+ } else {
+ return 1298.27;
+ }
+ break;
+ case 'C4':
+ if (axis == 'x') {
+ return 649.13;
+ } else {
+ return 918.43;
+ }
+ break;
+ case 'C5':
+ if (axis == 'x') {
+ return 459.21;
+ } else {
+ return 649.13;
+ }
+ break;
+ case 'C6':
+ if (axis == 'x') {
+ return 323.15;
+ } else {
+ return 459.21;
+ }
+ break;
+ case 'C7':
+ if (axis == 'x') {
+ return 229.61;
+ } else {
+ return 323.15;
+ }
+ break;
+ case 'C8':
+ if (axis == 'x') {
+ return 161.57;
+ } else {
+ return 229.61;
+ }
+ break;
+ case 'C9':
+ if (axis == 'x') {
+ return 113.39;
+ } else {
+ return 161.57;
+ }
+ break;
+ case 'C10':
+ if (axis == 'x') {
+ return 79.37;
+ } else {
+ return 113.39;
+ }
+ break;
+ case 'RA0':
+ if (axis == 'x') {
+ return 2437.80;
+ } else {
+ return 3458.27;
+ }
+ break;
+ case 'RA1':
+ if (axis == 'x') {
+ return 1729.13;
+ } else {
+ return 2437.80;
+ }
+ break;
+ case 'RA2':
+ if (axis == 'x') {
+ return 1218.90;
+ } else {
+ return 1729.13;
+ }
+ break;
+ case 'RA3':
+ if (axis == 'x') {
+ return 864.57;
+ } else {
+ return 1218.90;
+ }
+ break;
+ case 'RA4':
+ if (axis == 'x') {
+ return 609.45;
+ } else {
+ return 864.57;
+ }
+ break;
+ case 'SRA0':
+ if (axis == 'x') {
+ return 2551.18;
+ } else {
+ return 3628.35;
+ }
+ break;
+ case 'SRA1':
+ if (axis == 'x') {
+ return 1814.17;
+ } else {
+ return 2551.18;
+ }
+ break;
+ case 'SRA2':
+ if (axis == 'x') {
+ return 1275.59;
+ } else {
+ return 1814.17;
+ }
+ break;
+ case 'SRA3':
+ if (axis == 'x') {
+ return 907.09;
+ } else {
+ return 1275.59;
+ }
+ break;
+ case 'SRA4':
+ if (axis == 'x') {
+ return 637.80;
+ } else {
+ return 907.09;
+ }
+ break;
+ case 'LETTER':
+ if (axis == 'x') {
+ return 612.00;
+ } else {
+ return 792.00;
+ }
+ break;
+ case 'LEGAL':
+ if (axis == 'x') {
+ return 612.00;
+ } else {
+ return 1008.00;
+ }
+ break;
+ case 'EXECUTIVE':
+ if (axis == 'x') {
+ return 521.86;
+ } else {
+ return 756.00;
+ }
+ break;
+ case 'FOLIO':
+ if (axis == 'x') {
+ return 612.00;
+ } else {
+ return 936.00;
+ }
+ break;
+ } // end switch
+
+ return 0;
+}
+
+/**
+ * Unbind all event handlers before tearing down a page
+ */
+AJAX.registerTeardown('functions.js', function () {
+ $("a.inline_edit_sql").die('click');
+ $("input#sql_query_edit_save").die('click');
+ $("input#sql_query_edit_discard").die('click');
+ $('input.sqlbutton').unbind('click');
+ $("#export_type").unbind('change');
+ $('#sqlquery').unbind('keydown');
+ $('#sql_query_edit').unbind('keydown');
+
+ if (codemirror_inline_editor) {
+ // Copy the sql query to the text area to preserve it.
+ $('#sql_query_edit').text(codemirror_inline_editor.getValue());
+ $(codemirror_inline_editor.getWrapperElement()).unbind('keydown');
+ codemirror_inline_editor.toTextArea();
+ codemirror_inline_editor = false;
+ }
+ if (codemirror_editor) {
+ $(codemirror_editor.getWrapperElement()).unbind('keydown');
+ }
+});
+
+/**
+ * Jquery Coding for inline editing SQL_QUERY
+ */
+AJAX.registerOnload('functions.js', function () {
+ // If we are coming back to the page by clicking forward button
+ // of the browser, bind the code mirror to inline query editor.
+ bindCodeMirrorToInlineEditor();
+ $("a.inline_edit_sql").live('click', function () {
+ if ($('#sql_query_edit').length) {
+ // An inline query editor is already open,
+ // we don't want another copy of it
+ return false;
+ }
+
+ var $form = $(this).prev('form');
+ var sql_query = $form.find("input[name='sql_query']").val();
+ var $inner_sql = $(this).parent().prev().find('code.sql');
+ var old_text = $inner_sql.html();
+
+ var new_content = "<textarea name=\"sql_query_edit\" id=\"sql_query_edit\">" + sql_query + "</textarea>\n";
+ new_content += "<input type=\"submit\" id=\"sql_query_edit_save\" class=\"button btnSave\" value=\"" + PMA_messages.strGo + "\"/>\n";
+ new_content += "<input type=\"button\" id=\"sql_query_edit_discard\" class=\"button btnDiscard\" value=\"" + PMA_messages.strCancel + "\"/>\n";
+ var $editor_area = $('div#inline_editor');
+ if ($editor_area.length === 0) {
+ $editor_area = $('<div id="inline_editor_outer"></div>');
+ $editor_area.insertBefore($inner_sql);
+ }
+ $editor_area.html(new_content);
+ $inner_sql.hide();
+
+ bindCodeMirrorToInlineEditor();
+ return false;
+ });
+
+ $("input#sql_query_edit_save").live('click', function () {
+ $(".success").hide();
+ //hide already existing success message
+ var sql_query;
+ if (codemirror_inline_editor) {
+ codemirror_inline_editor.save();
+ sql_query = codemirror_inline_editor.getValue();
+ } else {
+ sql_query = $(this).prev().val();
+ }
+
+ var $form = $("a.inline_edit_sql").prev('form');
+ var $fake_form = $('<form>', {action: 'import.php', method: 'post'})
+ .append($form.find("input[name=server], input[name=db], input[name=table], input[name=token]").clone())
+ .append($('<input/>', {type: 'hidden', name: 'show_query', value: 1}))
+ .append($('<input/>', {type: 'hidden', name: 'is_js_confirmed', value: 0}))
+ .append($('<input/>', {type: 'hidden', name: 'sql_query', value: sql_query}));
+ if (! checkSqlQuery($fake_form[0])) {
+ return false;
+ }
+ $fake_form.appendTo($('body')).submit();
+ });
+
+ $("input#sql_query_edit_discard").live('click', function () {
+ $('div#inline_editor_outer').siblings('code.sql').show();
+ $('div#inline_editor_outer').remove();
+ });
+
+ $('input.sqlbutton').click(function (evt) {
+ insertQuery(evt.target.id);
+ return false;
+ });
+
+ $("#export_type").change(function () {
+ if ($("#export_type").val() == 'svg') {
+ $("#show_grid_opt").prop("disabled", true);
+ $("#orientation_opt").prop("disabled", true);
+ $("#with_doc").prop("disabled", true);
+ $("#show_table_dim_opt").removeProp("disabled");
+ $("#all_tables_same_width").removeProp("disabled");
+ $("#paper_opt").removeProp("disabled");
+ $("#show_color_opt").removeProp("disabled");
+ //$(this).css("background-color","yellow");
+ } else if ($("#export_type").val() == 'dia') {
+ $("#show_grid_opt").prop("disabled", true);
+ $("#with_doc").prop("disabled", true);
+ $("#show_table_dim_opt").prop("disabled", true);
+ $("#all_tables_same_width").prop("disabled", true);
+ $("#paper_opt").removeProp("disabled");
+ $("#show_color_opt").removeProp("disabled");
+ $("#orientation_opt").removeProp("disabled");
+ } else if ($("#export_type").val() == 'eps') {
+ $("#show_grid_opt").prop("disabled", true);
+ $("#orientation_opt").removeProp("disabled");
+ $("#with_doc").prop("disabled", true);
+ $("#show_table_dim_opt").prop("disabled", true);
+ $("#all_tables_same_width").prop("disabled", true);
+ $("#paper_opt").prop("disabled", true);
+ $("#show_color_opt").prop("disabled", true);
+ } else if ($("#export_type").val() == 'pdf') {
+ $("#show_grid_opt").removeProp("disabled");
+ $("#orientation_opt").removeProp("disabled");
+ $("#with_doc").removeProp("disabled");
+ $("#show_table_dim_opt").removeProp("disabled");
+ $("#all_tables_same_width").removeProp("disabled");
+ $("#paper_opt").removeProp("disabled");
+ $("#show_color_opt").removeProp("disabled");
+ } else {
+ // nothing
+ }
+ });
+
+ if ($('#input_username')) {
+ if ($('#input_username').val() === '') {
+ $('#input_username').focus();
+ } else {
+ $('#input_password').focus();
+ }
+ }
+});
+
+/**
+ * Binds the CodeMirror to the text area used to inline edit a query.
+ */
+function bindCodeMirrorToInlineEditor() {
+ var $inline_editor = $('#sql_query_edit');
+ if ($inline_editor.length > 0) {
+ if (typeof CodeMirror !== 'undefined') {
+ var height = $('#sql_query_edit').css('height');
+ codemirror_inline_editor = CodeMirror.fromTextArea($inline_editor[0], {
+ lineNumbers: true,
+ matchBrackets: true,
+ indentUnit: 4,
+ mode: "text/x-mysql",
+ lineWrapping: true
+ });
+ codemirror_inline_editor.getScrollerElement().style.height = height;
+ codemirror_inline_editor.refresh();
+ codemirror_inline_editor.focus();
+ $(codemirror_inline_editor.getWrapperElement()).bind(
+ 'keydown',
+ catchKeypressesFromSqlTextboxes
+ );
+ } else {
+ $inline_editor.focus().bind(
+ 'keydown',
+ catchKeypressesFromSqlTextboxes
+ );
+ }
+ }
+}
+
+function catchKeypressesFromSqlTextboxes(event) {
+ // ctrl-enter is 10 in chrome and ie, but 13 in ff
+ if (event.ctrlKey && (event.keyCode == 13 || event.keyCode == 10)) {
+ if ($('#sql_query_edit').length > 0) {
+ $("#sql_query_edit_save").trigger('click');
+ } else if ($('#sqlquery').length > 0) {
+ $("#button_submit_query").trigger('click');
+ }
+ }
+}
+
+/**
+ * Adds doc link to single highlighted SQL element
+ */
+function PMA_doc_add($elm, params)
+{
+ var url = $.sprintf(
+ mysql_doc_template,
+ params[0]
+ );
+ if (params.length > 1) {
+ url += '#' + params[1];
+ }
+ var content = $elm.text();
+ $elm.text('');
+ $elm.append('<a target="mysql_doc" class="cm-sql-doc" href="' + url + '">' + content + '</a>');
+}
+
+/**
+ * Generates doc links for keywords inside highlighted SQL
+ */
+function PMA_doc_keyword(idx, elm)
+{
+ var $elm = $(elm);
+ /* Skip already processed ones */
+ if ($elm.find('a').length > 0) {
+ return;
+ }
+ var keyword = $elm.text().toUpperCase();
+ var $next = $elm.next('.cm-keyword');
+ if ($next) {
+ var next_keyword = $next.text().toUpperCase();
+ var full = keyword + ' ' + next_keyword;
+
+ var $next2 = $next.next('.cm-keyword');
+ if ($next2) {
+ var next2_keyword = $next2.text().toUpperCase();
+ var full2 = full + ' ' + next2_keyword;
+ if (full2 in mysql_doc_keyword) {
+ PMA_doc_add($elm, mysql_doc_keyword[full2]);
+ PMA_doc_add($next, mysql_doc_keyword[full2]);
+ PMA_doc_add($next2, mysql_doc_keyword[full2]);
+ return;
+ }
+ }
+ if (full in mysql_doc_keyword) {
+ PMA_doc_add($elm, mysql_doc_keyword[full]);
+ PMA_doc_add($next, mysql_doc_keyword[full]);
+ return;
+ }
+ }
+ if (keyword in mysql_doc_keyword) {
+ PMA_doc_add($elm, mysql_doc_keyword[keyword]);
+ }
+}
+
+/**
+ * Generates doc links for builtins inside highlighted SQL
+ */
+function PMA_doc_builtin(idx, elm)
+{
+ var $elm = $(elm);
+ var builtin = $elm.text().toUpperCase();
+ if (builtin in mysql_doc_builtin) {
+ PMA_doc_add($elm, mysql_doc_builtin[builtin]);
+ }
+}
+
+/**
+ * Higlights SQL using CodeMirror.
+ */
+function PMA_highlightSQL(base)
+{
+ var $elm = base.find('code.sql');
+ $elm.each(function () {
+ var $sql = $(this);
+ var $pre = $sql.find('pre');
+ /* We only care about visible elements to avoid double processing */
+ if ($pre.is(":visible")) {
+ var $highlight = $('<div class="sql-highlight cm-s-default"></div>');
+ $sql.append($highlight);
+ if (typeof CodeMirror != 'undefined') {
+ CodeMirror.runMode($sql.text(), 'text/x-mysql', $highlight[0]);
+ $pre.hide();
+ $highlight.find('.cm-keyword').each(PMA_doc_keyword);
+ $highlight.find('.cm-builtin').each(PMA_doc_builtin);
+ }
+ }
+ });
+}
+
+/**
+ * Show a message on the top of the page for an Ajax request
+ *
+ * Sample usage:
+ *
+ * 1) var $msg = PMA_ajaxShowMessage();
+ * This will show a message that reads "Loading...". Such a message will not
+ * disappear automatically and cannot be dismissed by the user. To remove this
+ * message either the PMA_ajaxRemoveMessage($msg) function must be called or
+ * another message must be show with PMA_ajaxShowMessage() function.
+ *
+ * 2) var $msg = PMA_ajaxShowMessage(PMA_messages.strProcessingRequest);
+ * This is a special case. The behaviour is same as above,
+ * just with a different message
+ *
+ * 3) var $msg = PMA_ajaxShowMessage('The operation was successful');
+ * This will show a message that will disappear automatically and it can also
+ * be dismissed by the user.
+ *
+ * 4) var $msg = PMA_ajaxShowMessage('Some error', false);
+ * This will show a message that will not disappear automatically, but it
+ * can be dismissed by the user after he has finished reading it.
+ *
+ * @param string message string containing the message to be shown.
+ * optional, defaults to 'Loading...'
+ * @param mixed timeout number of milliseconds for the message to be visible
+ * optional, defaults to 5000. If set to 'false', the
+ * notification will never disappear
+ * @return jQuery object jQuery Element that holds the message div
+ * this object can be passed to PMA_ajaxRemoveMessage()
+ * to remove the notification
+ */
+function PMA_ajaxShowMessage(message, timeout)
+{
+ /**
+ * @var self_closing Whether the notification will automatically disappear
+ */
+ var self_closing = true;
+ /**
+ * @var dismissable Whether the user will be able to remove
+ * the notification by clicking on it
+ */
+ var dismissable = true;
+ // Handle the case when a empty data.message is passed.
+ // We don't want the empty message
+ if (message === '') {
+ return true;
+ } else if (! message) {
+ // If the message is undefined, show the default
+ message = PMA_messages.strLoading;
+ dismissable = false;
+ self_closing = false;
+ } else if (message == PMA_messages.strProcessingRequest) {
+ // This is another case where the message should not disappear
+ dismissable = false;
+ self_closing = false;
+ }
+ // Figure out whether (or after how long) to remove the notification
+ if (timeout === undefined) {
+ timeout = 5000;
+ } else if (timeout === false) {
+ self_closing = false;
+ }
+ // Create a parent element for the AJAX messages, if necessary
+ if ($('#loading_parent').length === 0) {
+ $('<div id="loading_parent"></div>')
+ .prependTo("body");
+ }
+ // Update message count to create distinct message elements every time
+ ajax_message_count++;
+ // Remove all old messages, if any
+ $("span.ajax_notification[id^=ajax_message_num]").remove();
+ /**
+ * @var $retval a jQuery object containing the reference
+ * to the created AJAX message
+ */
+ var $retval = $(
+ '<span class="ajax_notification" id="ajax_message_num_' +
+ ajax_message_count +
+ '"></span>'
+ )
+ .hide()
+ .appendTo("#loading_parent")
+ .html(message)
+ .show();
+ // If the notification is self-closing we should create a callback to remove it
+ if (self_closing) {
+ $retval
+ .delay(timeout)
+ .fadeOut('medium', function () {
+ if ($(this).is(':data(tooltip)')) {
+ $(this).tooltip('destroy');
+ }
+ // Remove the notification
+ $(this).remove();
+ });
+ }
+ // If the notification is dismissable we need to add the relevant class to it
+ // and add a tooltip so that the users know that it can be removed
+ if (dismissable) {
+ $retval.addClass('dismissable').css('cursor', 'pointer');
+ /**
+ * Add a tooltip to the notification to let the user know that (s)he
+ * can dismiss the ajax notification by clicking on it.
+ */
+ PMA_tooltip(
+ $retval,
+ 'span',
+ PMA_messages.strDismiss
+ );
+ }
+ PMA_highlightSQL($retval);
+
+ return $retval;
+}
+
+/**
+ * Removes the message shown for an Ajax operation when it's completed
+ *
+ * @param jQuery object jQuery Element that holds the notification
+ *
+ * @return nothing
+ */
+function PMA_ajaxRemoveMessage($this_msgbox)
+{
+ if ($this_msgbox !== undefined && $this_msgbox instanceof jQuery) {
+ $this_msgbox
+ .stop(true, true)
+ .fadeOut('medium');
+ if ($this_msgbox.is(':data(tooltip)')) {
+ $this_msgbox.tooltip('destroy');
+ } else {
+ $this_msgbox.remove();
+ }
+ }
+}
+
+// This event only need to be fired once after the initial page load
+$(function () {
+ /**
+ * Allows the user to dismiss a notification
+ * created with PMA_ajaxShowMessage()
+ */
+ $('span.ajax_notification.dismissable').live('click', function () {
+ PMA_ajaxRemoveMessage($(this));
+ });
+ /**
+ * The below two functions hide the "Dismiss notification" tooltip when a user
+ * is hovering a link or button that is inside an ajax message
+ */
+ $('span.ajax_notification a, span.ajax_notification button, span.ajax_notification input')
+ .live('mouseover', function () {
+ if ($(this).parents('span.ajax_notification').is(':data(tooltip)')) {
+ $(this).parents('span.ajax_notification').tooltip('disable');
+ }
+ })
+ .live('mouseout', function () {
+ if ($(this).parents('span.ajax_notification').is(':data(tooltip)')) {
+ $(this).parents('span.ajax_notification').tooltip('enable');
+ }
+ });
+});
+
+/**
+ * Hides/shows the "Open in ENUM/SET editor" message, depending on the data type of the column currently selected
+ */
+function PMA_showNoticeForEnum(selectElement)
+{
+ var enum_notice_id = selectElement.attr("id").split("_")[1];
+ enum_notice_id += "_" + (parseInt(selectElement.attr("id").split("_")[2], 10) + 1);
+ var selectedType = selectElement.val();
+ if (selectedType == "ENUM" || selectedType == "SET") {
+ $("p#enum_notice_" + enum_notice_id).show();
+ } else {
+ $("p#enum_notice_" + enum_notice_id).hide();
+ }
+}
+
+/*
+ * Creates a Profiling Chart with jqplot. Used in sql.js
+ * and in server_status_monitor.js
+ */
+function PMA_createProfilingChartJqplot(target, data)
+{
+ return $.jqplot(target, [data],
+ {
+ seriesDefaults: {
+ renderer: $.jqplot.PieRenderer,
+ rendererOptions: {
+ showDataLabels: true
+ }
+ },
+ highlighter: {
+ show: true,
+ tooltipLocation: 'se',
+ sizeAdjust: 0,
+ tooltipAxes: 'pieref',
+ useAxesFormatters: false,
+ formatString: '%s, %.9Ps'
+ },
+ legend: {
+ show: true,
+ location: 'e'
+ },
+ // from http://tango.freedesktop.org/Tango_Icon_Theme_Guidelines#Color_Palette
+ seriesColors: [
+ '#fce94f',
+ '#fcaf3e',
+ '#e9b96e',
+ '#8ae234',
+ '#729fcf',
+ '#ad7fa8',
+ '#ef2929',
+ '#eeeeec',
+ '#888a85',
+ '#c4a000',
+ '#ce5c00',
+ '#8f5902',
+ '#4e9a06',
+ '#204a87',
+ '#5c3566',
+ '#a40000',
+ '#babdb6',
+ '#2e3436'
+ ]
+ }
+ );
+}
+
+/**
+ * Formats a profiling duration nicely (in us and ms time).
+ * Used in server_status_monitor.js
+ *
+ * @param integer Number to be formatted, should be in the range of microsecond to second
+ * @param integer Accuracy, how many numbers right to the comma should be
+ * @return string The formatted number
+ */
+function PMA_prettyProfilingNum(num, acc)
+{
+ if (!acc) {
+ acc = 2;
+ }
+ acc = Math.pow(10, acc);
+ if (num * 1000 < 0.1) {
+ num = Math.round(acc * (num * 1000 * 1000)) / acc + 'µ';
+ } else if (num < 0.1) {
+ num = Math.round(acc * (num * 1000)) / acc + 'm';
+ } else {
+ num = Math.round(acc * num) / acc;
+ }
+
+ return num + 's';
+}
+
+
+/**
+ * Formats a SQL Query nicely with newlines and indentation. Depends on Codemirror and MySQL Mode!
+ *
+ * @param string Query to be formatted
+ * @return string The formatted query
+ */
+function PMA_SQLPrettyPrint(string)
+{
+ if (typeof CodeMirror == 'undefined') {
+ return string;
+ }
+
+ var mode = CodeMirror.getMode({}, "text/x-mysql");
+ var stream = new CodeMirror.StringStream(string);
+ var state = mode.startState();
+ var token, tokens = [];
+ var output = '';
+ var tabs = function (cnt) {
+ var ret = '';
+ for (var i = 0; i < 4 * cnt; i++) {
+ ret += " ";
+ }
+ return ret;
+ };
+
+ // "root-level" statements
+ var statements = {
+ 'select': ['select', 'from', 'on', 'where', 'having', 'limit', 'order by', 'group by'],
+ 'update': ['update', 'set', 'where'],
+ 'insert into': ['insert into', 'values']
+ };
+ // don't put spaces before these tokens
+ var spaceExceptionsBefore = {';': true, ',': true, '.': true, '(': true};
+ // don't put spaces after these tokens
+ var spaceExceptionsAfter = {'.': true};
+
+ // Populate tokens array
+ var str = '';
+ while (! stream.eol()) {
+ stream.start = stream.pos;
+ token = mode.token(stream, state);
+ if (token !== null) {
+ tokens.push([token, stream.current().toLowerCase()]);
+ }
+ }
+
+ var currentStatement = tokens[0][1];
+
+ if (! statements[currentStatement]) {
+ return string;
+ }
+ // Holds all currently opened code blocks (statement, function or generic)
+ var blockStack = [];
+ // Holds the type of block from last iteration (the current is in blockStack[0])
+ var previousBlock;
+ // If a new code block is found, newBlock contains its type for one iteration and vice versa for endBlock
+ var newBlock, endBlock;
+ // How much to indent in the current line
+ var indentLevel = 0;
+ // Holds the "root-level" statements
+ var statementPart, lastStatementPart = statements[currentStatement][0];
+
+ blockStack.unshift('statement');
+
+ // Iterate through every token and format accordingly
+ for (var i = 0; i < tokens.length; i++) {
+ previousBlock = blockStack[0];
+
+ // New block => push to stack
+ if (tokens[i][1] == '(') {
+ if (i < tokens.length - 1 && tokens[i + 1][0] == 'statement-verb') {
+ blockStack.unshift(newBlock = 'statement');
+ } else if (i > 0 && tokens[i - 1][0] == 'builtin') {
+ blockStack.unshift(newBlock = 'function');
+ } else {
+ blockStack.unshift(newBlock = 'generic');
+ }
+ } else {
+ newBlock = null;
+ }
+
+ // Block end => pop from stack
+ if (tokens[i][1] == ')') {
+ endBlock = blockStack[0];
+ blockStack.shift();
+ } else {
+ endBlock = null;
+ }
+
+ // A subquery is starting
+ if (i > 0 && newBlock == 'statement') {
+ indentLevel++;
+ output += "\n" + tabs(indentLevel) + tokens[i][1] + ' ' + tokens[i + 1][1].toUpperCase() + "\n" + tabs(indentLevel + 1);
+ currentStatement = tokens[i + 1][1];
+ i++;
+ continue;
+ }
+
+ // A subquery is ending
+ if (endBlock == 'statement' && indentLevel > 0) {
+ output += "\n" + tabs(indentLevel);
+ indentLevel--;
+ }
+
+ // One less indentation for statement parts (from, where, order by, etc.) and a newline
+ statementPart = statements[currentStatement].indexOf(tokens[i][1]);
+ if (statementPart != -1) {
+ if (i > 0) {
+ output += "\n";
+ }
+ output += tabs(indentLevel) + tokens[i][1].toUpperCase();
+ output += "\n" + tabs(indentLevel + 1);
+ lastStatementPart = tokens[i][1];
+ }
+ // Normal indentatin and spaces for everything else
+ else {
+ if (! spaceExceptionsBefore[tokens[i][1]] &&
+ ! (i > 0 && spaceExceptionsAfter[tokens[i - 1][1]]) &&
+ output.charAt(output.length - 1) != ' ') {
+ output += " ";
+ }
+ if (tokens[i][0] == 'keyword') {
+ output += tokens[i][1].toUpperCase();
+ } else {
+ output += tokens[i][1];
+ }
+ }
+
+ // split columns in select and 'update set' clauses, but only inside statements blocks
+ if ((lastStatementPart == 'select' || lastStatementPart == 'where' || lastStatementPart == 'set') &&
+ tokens[i][1] == ',' && blockStack[0] == 'statement') {
+
+ output += "\n" + tabs(indentLevel + 1);
+ }
+
+ // split conditions in where clauses, but only inside statements blocks
+ if (lastStatementPart == 'where' &&
+ (tokens[i][1] == 'and' || tokens[i][1] == 'or' || tokens[i][1] == 'xor')) {
+
+ if (blockStack[0] == 'statement') {
+ output += "\n" + tabs(indentLevel + 1);
+ }
+ // Todo: Also split and or blocks in newlines & identation++
+ //if (blockStack[0] == 'generic')
+ // output += ...
+ }
+ }
+ return output;
+}
+
+/**
+ * jQuery function that uses jQueryUI's dialogs to confirm with user. Does not
+ * return a jQuery object yet and hence cannot be chained
+ *
+ * @param string question
+ * @param string url URL to be passed to the callbackFn to make
+ * an Ajax call to
+ * @param function callbackFn callback to execute after user clicks on OK
+ */
+
+jQuery.fn.PMA_confirm = function (question, url, callbackFn) {
+ var confirmState = PMA_commonParams.get('confirm');
+ // when the Confirm directive is set to false in config.inc.php
+ // and not changed in user prefs, confirmState is ""
+ // when it's unticked in user prefs, confirmState is 1
+ if (confirmState === "" || confirmState === "1") {
+ // user does not want to confirm
+ if ($.isFunction(callbackFn)) {
+ callbackFn.call(this, url);
+ return true;
+ }
+ }
+ if (PMA_messages.strDoYouReally === '') {
+ return true;
+ }
+
+ /**
+ * @var button_options Object that stores the options passed to jQueryUI
+ * dialog
+ */
+ var button_options = {};
+ button_options[PMA_messages.strOK] = function () {
+ $(this).dialog("close");
+
+ if ($.isFunction(callbackFn)) {
+ callbackFn.call(this, url);
+ }
+ };
+ button_options[PMA_messages.strCancel] = function () {
+ $(this).dialog("close");
+ };
+
+ $('<div/>', {'id': 'confirm_dialog'})
+ .prepend(question)
+ .dialog({
+ buttons: button_options,
+ close: function () {
+ $(this).remove();
+ },
+ modal: true
+ });
+};
+
+/**
+ * jQuery function to sort a table's body after a new row has been appended to it.
+ * Also fixes the even/odd classes of the table rows at the end.
+ *
+ * @param string text_selector string to select the sortKey's text
+ *
+ * @return jQuery Object for chaining purposes
+ */
+jQuery.fn.PMA_sort_table = function (text_selector) {
+ return this.each(function () {
+
+ /**
+ * @var table_body Object referring to the table's <tbody> element
+ */
+ var table_body = $(this);
+ /**
+ * @var rows Object referring to the collection of rows in {@link table_body}
+ */
+ var rows = $(this).find('tr').get();
+
+ //get the text of the field that we will sort by
+ $.each(rows, function (index, row) {
+ row.sortKey = $.trim($(row).find(text_selector).text().toLowerCase());
+ });
+
+ //get the sorted order
+ rows.sort(function (a, b) {
+ if (a.sortKey < b.sortKey) {
+ return -1;
+ }
+ if (a.sortKey > b.sortKey) {
+ return 1;
+ }
+ return 0;
+ });
+
+ //pull out each row from the table and then append it according to it's order
+ $.each(rows, function (index, row) {
+ $(table_body).append(row);
+ row.sortKey = null;
+ });
+
+ //Re-check the classes of each row
+ $(this).find('tr:odd')
+ .removeClass('even').addClass('odd')
+ .end()
+ .find('tr:even')
+ .removeClass('odd').addClass('even');
+ });
+};
+
+/**
+ * Unbind all event handlers before tearing down a page
+ */
+AJAX.registerTeardown('functions.js', function () {
+ $("#create_table_form_minimal.ajax").die('submit');
+ $("form.create_table_form.ajax").die('submit');
+ $("form.create_table_form.ajax input[name=submit_num_fields]").die('click');
+ $("form.create_table_form.ajax input").die('keyup');
+});
+
+/**
+ * jQuery coding for 'Create Table'. Used on db_operations.php,
+ * db_structure.php and db_tracking.php (i.e., wherever
+ * libraries/display_create_table.lib.php is used)
+ *
+ * Attach Ajax Event handlers for Create Table
+ */
+AJAX.registerOnload('functions.js', function () {
+ /**
+ * Attach event handler for submission of create table form (save)
+ */
+ $("form.create_table_form.ajax").live('submit', function (event) {
+ event.preventDefault();
+
+ /**
+ * @var the_form object referring to the create table form
+ */
+ var $form = $(this);
+
+ /*
+ * First validate the form; if there is a problem, avoid submitting it
+ *
+ * checkTableEditForm() needs a pure element and not a jQuery object,
+ * this is why we pass $form[0] as a parameter (the jQuery object
+ * is actually an array of DOM elements)
+ */
+
+ if (checkTableEditForm($form[0], $form.find('input[name=orig_num_fields]').val())) {
+ PMA_ajaxShowMessage(PMA_messages.strProcessingRequest);
+ PMA_prepareForAjaxRequest($form);
+ //User wants to submit the form
+ $.post($form.attr('action'), $form.serialize() + "&do_save_data=1", function (data) {
+ if (data.success === true) {
+ $('#properties_message')
+ .removeClass('error')
+ .html('');
+ PMA_ajaxShowMessage(data.message);
+ // Only if the create table dialog (distinct panel) exists
+ if ($("#create_table_dialog").length > 0) {
+ $("#create_table_dialog").dialog("close").remove();
+ }
+ $('#tableslistcontainer').before(data.formatted_sql);
+
+ /**
+ * @var tables_table Object referring to the <tbody> element that holds the list of tables
+ */
+ var tables_table = $("#tablesForm").find("tbody").not("#tbl_summary_row");
+ // this is the first table created in this db
+ if (tables_table.length === 0) {
+ PMA_commonActions.refreshMain(
+ PMA_commonParams.get('opendb_url')
+ );
+ } else {
+ /**
+ * @var curr_last_row Object referring to the last <tr> element in {@link tables_table}
+ */
+ var curr_last_row = $(tables_table).find('tr:last');
+ /**
+ * @var curr_last_row_index_string String containing the index of {@link curr_last_row}
+ */
+ var curr_last_row_index_string = $(curr_last_row).find('input:checkbox').attr('id').match(/\d+/)[0];
+ /**
+ * @var curr_last_row_index Index of {@link curr_last_row}
+ */
+ var curr_last_row_index = parseFloat(curr_last_row_index_string);
+ /**
+ * @var new_last_row_index Index of the new row to be appended to {@link tables_table}
+ */
+ var new_last_row_index = curr_last_row_index + 1;
+ /**
+ * @var new_last_row_id String containing the id of the row to be appended to {@link tables_table}
+ */
+ var new_last_row_id = 'checkbox_tbl_' + new_last_row_index;
+
+ data.new_table_string = data.new_table_string.replace(/checkbox_tbl_/, new_last_row_id);
+ //append to table
+ $(data.new_table_string)
+ .appendTo(tables_table);
+
+ //Sort the table
+ $(tables_table).PMA_sort_table('th');
+
+ // Adjust summary row
+ PMA_adjustTotals();
+ }
+
+ //Refresh navigation as a new table has been added
+ PMA_reloadNavigation();
+ } else {
+ PMA_ajaxShowMessage(
+ '<div class="error">' + data.error + '</div>',
+ false
+ );
+ }
+ }); // end $.post()
+ } // end if (checkTableEditForm() )
+ }); // end create table form (save)
+
+ /**
+ * Attach event handler for create table form (add fields)
+ */
+ $("form.create_table_form.ajax input[name=submit_num_fields]").live('click', function (event) {
+ event.preventDefault();
+ /**
+ * @var the_form object referring to the create table form
+ */
+ var $form = $(this).closest('form');
+
+ var $msgbox = PMA_ajaxShowMessage(PMA_messages.strProcessingRequest);
+ PMA_prepareForAjaxRequest($form);
+
+ //User wants to add more fields to the table
+ $.post($form.attr('action'), $form.serialize() + "&submit_num_fields=1", function (data) {
+ if (data.success) {
+ $("#page_content").html(data.message);
+ PMA_highlightSQL($('#page_content'));
+ PMA_verifyColumnsProperties();
+ PMA_ajaxRemoveMessage($msgbox);
+ } else {
+ PMA_ajaxShowMessage(data.error);
+ }
+ }); //end $.post()
+ }); // end create table form (add fields)
+
+ $("form.create_table_form.ajax input").live('keydown', function (event) {
+ if (event.keyCode == 13) {
+ event.preventDefault();
+ event.stopImmediatePropagation();
+ $(this)
+ .closest('form')
+ .append('<input type="hidden" name="do_save_data" value="1" />')
+ .submit();
+ }
+ });
+});
+
+
+/**
+ * Unbind all event handlers before tearing down a page
+ */
+AJAX.registerTeardown('functions.js', function () {
+ $("#copyTable.ajax").die('submit');
+ $("#moveTableForm").die('submit');
+ $("#tableOptionsForm").die('submit');
+ $("#tbl_maintenance li a.maintain_action.ajax").die('click');
+});
+/**
+ * jQuery coding for 'Table operations'. Used on tbl_operations.php
+ * Attach Ajax Event handlers for Table operations
+ */
+AJAX.registerOnload('functions.js', function () {
+ /**
+ *Ajax action for submitting the "Copy table"
+ **/
+ $("#copyTable.ajax").live('submit', function (event) {
+ event.preventDefault();
+ var $form = $(this);
+ PMA_prepareForAjaxRequest($form);
+ $.post($form.attr('action'), $form.serialize() + "&submit_copy=Go", function (data) {
+ if (data.success === true) {
+ if ($form.find("input[name='switch_to_new']").prop('checked')) {
+ PMA_commonParams.set(
+ 'db',
+ data.db
+ );
+ PMA_commonParams.set(
+ 'table',
+ $form.find("input[name='new_name']").val()
+ );
+ PMA_commonActions.refreshMain(false, function () {
+ PMA_ajaxShowMessage(data.message);
+ });
+ } else {
+ PMA_ajaxShowMessage(data.message);
+ }
+ // Refresh navigation when the table is copied
+ PMA_reloadNavigation();
+ } else {
+ PMA_ajaxShowMessage(data.error, false);
+ }
+ }); // end $.post()
+ });//end of copyTable ajax submit
+
+ /**
+ *Ajax action for submitting the "Move table"
+ */
+ $("#moveTableForm").live('submit', function (event) {
+ event.preventDefault();
+ var $form = $(this);
+ var db = $form.find('select[name=target_db]').val();
+ var tbl = $form.find('input[name=new_name]').val();
+ PMA_prepareForAjaxRequest($form);
+ $.post($form.attr('action'), $form.serialize() + "&submit_move=1", function (data) {
+ if (data.success === true) {
+ PMA_commonParams.set('db', db);
+ PMA_commonParams.set('table', tbl);
+ PMA_commonActions.refreshMain(false, function () {
+ PMA_ajaxShowMessage(data.message);
+ });
+ // Refresh navigation when the table is copied
+ PMA_reloadNavigation();
+ } else {
+ PMA_ajaxShowMessage(data.error, false);
+ }
+ }); // end $.post()
+ });
+
+ /**
+ * Ajax action for submitting the "Table options"
+ */
+ $("#tableOptionsForm").live('submit', function (event) {
+ event.preventDefault();
+ event.stopPropagation();
+ var $form = $(this);
+ var $tblNameField = $form.find('input[name=new_name]');
+ if ($tblNameField.val() !== $tblNameField[0].defaultValue) {
+ // reload page and navigation if the table has been renamed
+ PMA_prepareForAjaxRequest($form);
+ var tbl = $tblNameField.val();
+ $.post($form.attr('action'), $form.serialize(), function (data) {
+ if (data.success === true) {
+ PMA_commonParams.set('table', tbl);
+ PMA_commonActions.refreshMain(false, function () {
+ $('#page_content').html(data.message);
+ PMA_highlightSQL($('#page_content'));
+ });
+ } else {
+ PMA_ajaxShowMessage(data.error, false);
+ }
+ }); // end $.post()
+ } else {
+ $form.removeClass('ajax').submit().addClass('ajax');
+ }
+ });
+
+ /**
+ *Ajax events for actions in the "Table maintenance"
+ **/
+ $("#tbl_maintenance li a.maintain_action.ajax").live('click', function (event) {
+ event.preventDefault();
+ if ($("#sqlqueryresults").length !== 0) {
+ $("#sqlqueryresults").remove();
+ }
+ if ($("#result_query").length !== 0) {
+ $("#result_query").remove();
+ }
+ //variables which stores the common attributes
+ $.post($(this).attr('href'), { ajax_request: 1 }, function (data) {
+ function scrollToTop() {
+ $('html, body').animate({ scrollTop: 0 });
+ }
+ if (data.success === true && data.sql_query !== undefined) {
+ PMA_ajaxShowMessage(data.message);
+ $("<div id='sqlqueryresults' class='ajax'></div>").prependTo("#page_content");
+ $("#sqlqueryresults").html(data.sql_query);
+ PMA_highlightSQL($('#page_content'));
+ scrollToTop();
+ } else if (data.success === true) {
+ var $temp_div = $("<div id='temp_div'></div>");
+ $temp_div.html(data.message);
+ var $success = $temp_div.find("#result_query .success");
+ PMA_ajaxShowMessage($success);
+ $("<div id='sqlqueryresults' class='ajax'></div>").prependTo("#page_content");
+ $("#sqlqueryresults").html(data.message);
+ PMA_highlightSQL($('#page_content'));
+ PMA_init_slider();
+ $("#sqlqueryresults").children("fieldset,br").remove();
+ scrollToTop();
+ } else {
+ var $temp_div = $("<div id='temp_div'></div>");
+ $temp_div.html(data.error);
+ var $error = $temp_div.find("code").addClass("error");
+ PMA_ajaxShowMessage($error, false);
+ }
+ }); // end $.post()
+ });//end of table maintanance ajax click
+}); //end $(document).ready for 'Table operations'
+
+/**
+ * Unbind all event handlers before tearing down a page
+ */
+AJAX.registerTeardown('functions.js', function () {
+ $("#drop_db_anchor.ajax").die('click');
+});
+/**
+ * Attach Ajax event handlers for Drop Database. Moved here from db_structure.js
+ * as it was also required on db_create.php
+ */
+AJAX.registerOnload('functions.js', function () {
+ $("#drop_db_anchor.ajax").live('click', function (event) {
+ event.preventDefault();
+ /**
+ * @var question String containing the question to be asked for confirmation
+ */
+ var question = PMA_messages.strDropDatabaseStrongWarning + ' ';
+ question += $.sprintf(
+ PMA_messages.strDoYouReally,
+ 'DROP DATABASE ' + escapeHtml(PMA_commonParams.get('db'))
+ );
+ $(this).PMA_confirm(question, $(this).attr('href'), function (url) {
+ PMA_ajaxShowMessage(PMA_messages.strProcessingRequest);
+ $.get(url, {'is_js_confirmed': '1', 'ajax_request': true}, function (data) {
+ if (data.success) {
+ //Database deleted successfully, refresh both the frames
+ PMA_reloadNavigation();
+ PMA_commonParams.set('db', '');
+ PMA_commonActions.refreshMain(
+ 'server_databases.php',
+ function () {
+ PMA_ajaxShowMessage(data.message);
+ }
+ );
+ } else {
+ PMA_ajaxShowMessage(data.error, false);
+ }
+ });
+ });
+ });
+}); // end of $() for Drop Database
+
+/**
+ * Validates the password field in a form
+ *
+ * @see PMA_messages.strPasswordEmpty
+ * @see PMA_messages.strPasswordNotSame
+ * @param object $the_form The form to be validated
+ * @return bool
+ */
+function PMA_checkPassword($the_form)
+{
+ // Did the user select 'no password'?
+ if ($the_form.find('#nopass_1').is(':checked')) {
+ return true;
+ } else {
+ var $pred = $the_form.find('#select_pred_password');
+ if ($pred.length && ($pred.val() == 'none' || $pred.val() == 'keep')) {
+ return true;
+ }
+ }
+
+ var $password = $the_form.find('input[name=pma_pw]');
+ var $password_repeat = $the_form.find('input[name=pma_pw2]');
+ var alert_msg = false;
+
+ if ($password.val() === '') {
+ alert_msg = PMA_messages.strPasswordEmpty;
+ } else if ($password.val() != $password_repeat.val()) {
+ alert_msg = PMA_messages.strPasswordNotSame;
+ }
+
+ if (alert_msg) {
+ alert(alert_msg);
+ $password.val('');
+ $password_repeat.val('');
+ $password.focus();
+ return false;
+ }
+ return true;
+}
+
+/**
+ * Unbind all event handlers before tearing down a page
+ */
+AJAX.registerTeardown('functions.js', function () {
+ $('#change_password_anchor.ajax').die('click');
+});
+/**
+ * Attach Ajax event handlers for 'Change Password' on index.php
+ */
+AJAX.registerOnload('functions.js', function () {
+
+ /**
+ * Attach Ajax event handler on the change password anchor
+ */
+ $('#change_password_anchor.ajax').live('click', function (event) {
+ event.preventDefault();
+
+ var $msgbox = PMA_ajaxShowMessage();
+
+ /**
+ * @var button_options Object containing options to be passed to jQueryUI's dialog
+ */
+ var button_options = {};
+ button_options[PMA_messages.strGo] = function () {
+
+ event.preventDefault();
+
+ /**
+ * @var $the_form Object referring to the change password form
+ */
+ var $the_form = $("#change_password_form");
+
+ if (! PMA_checkPassword($the_form)) {
+ return false;
+ }
+
+ /**
+ * @var this_value String containing the value of the submit button.
+ * Need to append this for the change password form on Server Privileges
+ * page to work
+ */
+ var this_value = $(this).val();
+
+ var $msgbox = PMA_ajaxShowMessage(PMA_messages.strProcessingRequest);
+ $the_form.append('<input type="hidden" name="ajax_request" value="true" />');
+
+ $.post($the_form.attr('action'), $the_form.serialize() + '&change_pw=' + this_value, function (data) {
+ if (data.success === true) {
+ $("#page_content").prepend(data.message);
+ PMA_highlightSQL($('#page_content'));
+ $("#change_password_dialog").hide().remove();
+ $("#edit_user_dialog").dialog("close").remove();
+ PMA_ajaxRemoveMessage($msgbox);
+ }
+ else {
+ PMA_ajaxShowMessage(data.error, false);
+ }
+ }); // end $.post()
+ };
+
+ button_options[PMA_messages.strCancel] = function () {
+ $(this).dialog('close');
+ };
+ $.get($(this).attr('href'), {'ajax_request': true}, function (data) {
+ if (data.success) {
+ $('<div id="change_password_dialog"></div>')
+ .dialog({
+ title: PMA_messages.strChangePassword,
+ width: 600,
+ close: function (ev, ui) {
+ $(this).remove();
+ },
+ buttons : button_options,
+ modal: true
+ })
+ .append(data.message);
+ // for this dialog, we remove the fieldset wrapping due to double headings
+ $("fieldset#fieldset_change_password")
+ .find("legend").remove().end()
+ .find("table.noclick").unwrap().addClass("some-margin")
+ .find("input#text_pma_pw").focus();
+ displayPasswordGenerateButton();
+ $('#fieldset_change_password_footer').hide();
+ PMA_ajaxRemoveMessage($msgbox);
+ $('#change_password_form').bind('submit', function (e) {
+ e.preventDefault();
+ $(this)
+ .closest('.ui-dialog')
+ .find('.ui-dialog-buttonpane .ui-button')
+ .first()
+ .click();
+ });
+ } else {
+ PMA_ajaxShowMessage(data.error, false);
+ }
+ }); // end $.get()
+ }); // end handler for change password anchor
+}); // end $() for Change Password
+
+/**
+ * Unbind all event handlers before tearing down a page
+ */
+AJAX.registerTeardown('functions.js', function () {
+ $("select.column_type").die('change');
+ $("select.default_type").die('change');
+ $('input.allow_null').die('change');
+});
+/**
+ * Toggle the hiding/showing of the "Open in ENUM/SET editor" message when
+ * the page loads and when the selected data type changes
+ */
+AJAX.registerOnload('functions.js', function () {
+ // is called here for normal page loads and also when opening
+ // the Create table dialog
+ PMA_verifyColumnsProperties();
+ //
+ // needs live() to work also in the Create Table dialog
+ $("select.column_type").live('change', function () {
+ PMA_showNoticeForEnum($(this));
+ });
+ $("select.default_type").live('change', function () {
+ PMA_hideShowDefaultValue($(this));
+ });
+ $('input.allow_null').live('change', function () {
+ PMA_validateDefaultValue($(this));
+ });
+});
+
+function PMA_verifyColumnsProperties()
+{
+ $("select.column_type").each(function () {
+ PMA_showNoticeForEnum($(this));
+ });
+ $("select.default_type").each(function () {
+ PMA_hideShowDefaultValue($(this));
+ });
+}
+
+/**
+ * Hides/shows the default value input field, depending on the default type
+ * Ticks the NULL checkbox if NULL is chosen as default value.
+ */
+function PMA_hideShowDefaultValue($default_type)
+{
+ if ($default_type.val() == 'USER_DEFINED') {
+ $default_type.siblings('.default_value').show().focus();
+ } else {
+ $default_type.siblings('.default_value').hide();
+ if ($default_type.val() == 'NULL') {
+ var $null_checkbox = $default_type.closest('tr').find('.allow_null');
+ $null_checkbox.prop('checked', true);
+ }
+ }
+}
+
+/**
+ * If the column does not allow NULL values, makes sure that default is not NULL
+ */
+function PMA_validateDefaultValue($null_checkbox)
+{
+ if (! $null_checkbox.prop('checked')) {
+ var $default = $null_checkbox.closest('tr').find('.default_type');
+ if ($default.val() == 'NULL') {
+ $default.val('NONE');
+ }
+ }
+}
+
+
+/**
+ * Unbind all event handlers before tearing down a page
+ */
+AJAX.registerTeardown('functions.js', function () {
+ $("a.open_enum_editor").die('click');
+ $("input.add_value").die('click');
+ $("#enum_editor td.drop").die('click');
+});
+/**
+ * @var $enum_editor_dialog An object that points to the jQuery
+ * dialog of the ENUM/SET editor
+ */
+var $enum_editor_dialog = null;
+/**
+ * Opens the ENUM/SET editor and controls its functions
+ */
+AJAX.registerOnload('functions.js', function () {
+ $("a.open_enum_editor").live('click', function () {
+ // Get the name of the column that is being edited
+ var colname = $(this).closest('tr').find('input:first').val();
+ var title;
+ var i;
+ // And use it to make up a title for the page
+ if (colname.length < 1) {
+ title = PMA_messages.enum_newColumnVals;
+ } else {
+ title = PMA_messages.enum_columnVals.replace(
+ /%s/,
+ '"' + decodeURIComponent(colname) + '"'
+ );
+ }
+ // Get the values as a string
+ var inputstring = $(this)
+ .closest('td')
+ .find("input")
+ .val();
+ // Escape html entities
+ inputstring = $('<div/>')
+ .text(inputstring)
+ .html();
+ // Parse the values, escaping quotes and
+ // slashes on the fly, into an array
+ var values = [];
+ var in_string = false;
+ var curr, next, buffer = '';
+ for (i = 0; i < inputstring.length; i++) {
+ curr = inputstring.charAt(i);
+ next = i == inputstring.length ? '' : inputstring.charAt(i + 1);
+ if (! in_string && curr == "'") {
+ in_string = true;
+ } else if (in_string && curr == "\\" && next == "\\") {
+ buffer += "&#92;";
+ i++;
+ } else if (in_string && next == "'" && (curr == "'" || curr == "\\")) {
+ buffer += "&#39;";
+ i++;
+ } else if (in_string && curr == "'") {
+ in_string = false;
+ values.push(buffer);
+ buffer = '';
+ } else if (in_string) {
+ buffer += curr;
+ }
+ }
+ if (buffer.length > 0) {
+ // The leftovers in the buffer are the last value (if any)
+ values.push(buffer);
+ }
+ var fields = '';
+ // If there are no values, maybe the user is about to make a
+ // new list so we add a few for him/her to get started with.
+ if (values.length === 0) {
+ values.push('', '', '', '');
+ }
+ // Add the parsed values to the editor
+ var drop_icon = PMA_getImage('b_drop.png');
+ for (i = 0; i < values.length; i++) {
+ fields += "<tr><td>" +
+ "<input type='text' value='" + values[i] + "'/>" +
+ "</td><td class='drop'>" +
+ drop_icon +
+ "</td></tr>";
+ }
+ /**
+ * @var dialog HTML code for the ENUM/SET dialog
+ */
+ var dialog = "<div id='enum_editor'>" +
+ "<fieldset>" +
+ "<legend>" + title + "</legend>" +
+ "<p>" + PMA_getImage('s_notice.png') +
+ PMA_messages.enum_hint + "</p>" +
+ "<table class='values'>" + fields + "</table>" +
+ "</fieldset><fieldset class='tblFooters'>" +
+ "<table class='add'><tr><td>" +
+ "<div class='slider'></div>" +
+ "</td><td>" +
+ "<form><div><input type='submit' class='add_value' value='" +
+ $.sprintf(PMA_messages.enum_addValue, 1) +
+ "'/></div></form>" +
+ "</td></tr></table>" +
+ "<input type='hidden' value='" + // So we know which column's data is being edited
+ $(this).closest('td').find("input").attr("id") +
+ "' />" +
+ "</fieldset>" +
+ "</div>";
+ /**
+ * @var Defines functions to be called when the buttons in
+ * the buttonOptions jQuery dialog bar are pressed
+ */
+ var buttonOptions = {};
+ buttonOptions[PMA_messages.strGo] = function () {
+ // When the submit button is clicked,
+ // put the data back into the original form
+ var value_array = [];
+ $(this).find(".values input").each(function (index, elm) {
+ var val = elm.value.replace(/\\/g, '\\\\').replace(/'/g, "''");
+ value_array.push("'" + val + "'");
+ });
+ // get the Length/Values text field where this value belongs
+ var values_id = $(this).find("input[type='hidden']").val();
+ $("input#" + values_id).val(value_array.join(","));
+ $(this).dialog("close");
+ };
+ buttonOptions[PMA_messages.strClose] = function () {
+ $(this).dialog("close");
+ };
+ // Show the dialog
+ var width = parseInt(
+ (parseInt($('html').css('font-size'), 10) / 13) * 340,
+ 10
+ );
+ if (! width) {
+ width = 340;
+ }
+ $enum_editor_dialog = $(dialog).dialog({
+ minWidth: width,
+ modal: true,
+ title: PMA_messages.enum_editor,
+ buttons: buttonOptions,
+ open: function () {
+ // Focus the "Go" button after opening the dialog
+ $(this).closest('.ui-dialog').find('.ui-dialog-buttonpane button:first').focus();
+ },
+ close: function () {
+ $(this).remove();
+ }
+ });
+ // slider for choosing how many fields to add
+ $enum_editor_dialog.find(".slider").slider({
+ animate: true,
+ range: "min",
+ value: 1,
+ min: 1,
+ max: 9,
+ slide: function (event, ui) {
+ $(this).closest('table').find('input[type=submit]').val(
+ $.sprintf(PMA_messages.enum_addValue, ui.value)
+ );
+ }
+ });
+ // Focus the slider, otherwise it looks nearly transparent
+ $('a.ui-slider-handle').addClass('ui-state-focus');
+ return false;
+ });
+
+ // When "add a new value" is clicked, append an empty text field
+ $("input.add_value").live('click', function (e) {
+ e.preventDefault();
+ var num_new_rows = $enum_editor_dialog.find("div.slider").slider('value');
+ while (num_new_rows--) {
+ $enum_editor_dialog.find('.values')
+ .append(
+ "<tr style='display: none;'><td>" +
+ "<input type='text' />" +
+ "</td><td class='drop'>" +
+ PMA_getImage('b_drop.png') +
+ "</td></tr>"
+ )
+ .find('tr:last')
+ .show('fast');
+ }
+ });
+
+ // Removes the specified row from the enum editor
+ $("#enum_editor td.drop").live('click', function () {
+ $(this).closest('tr').hide('fast', function () {
+ $(this).remove();
+ });
+ });
+});
+
+/**
+ * Ensures indexes names are valid according to their type and, for a primary
+ * key, lock index name to 'PRIMARY'
+ * @param string form_id Variable which parses the form name as
+ * the input
+ * @return boolean false if there is no index form, true else
+ */
+function checkIndexName(form_id)
+{
+ if ($("#" + form_id).length === 0) {
+ return false;
+ }
+
+ // Gets the elements pointers
+ var $the_idx_name = $("#input_index_name");
+ var $the_idx_type = $("#select_index_type");
+
+ // Index is a primary key
+ if ($the_idx_type.find("option:selected").val() == 'PRIMARY') {
+ $the_idx_name.val('PRIMARY');
+ $the_idx_name.prop("disabled", true);
+ }
+
+ // Other cases
+ else {
+ if ($the_idx_name.val() == 'PRIMARY') {
+ $the_idx_name.val("");
+ }
+ $the_idx_name.prop("disabled", false);
+ }
+
+ return true;
+} // end of the 'checkIndexName()' function
+
+AJAX.registerTeardown('functions.js', function () {
+ $('#index_frm input[type=submit]').die('click');
+});
+AJAX.registerOnload('functions.js', function () {
+ /**
+ * Handler for adding more columns to an index in the editor
+ */
+ $('#index_frm input[type=submit]').live('click', function (event) {
+ event.preventDefault();
+ var rows_to_add = $(this)
+ .closest('fieldset')
+ .find('.slider')
+ .slider('value');
+ while (rows_to_add--) {
+ var $newrow = $('#index_columns')
+ .find('tbody > tr:first')
+ .clone()
+ .appendTo(
+ $('#index_columns').find('tbody')
+ );
+ $newrow.find(':input').each(function () {
+ $(this).val('');
+ });
+ // focus index size input on column picked
+ $newrow.find('select').change(function () {
+ if ($(this).find("option:selected").val() === '') {
+ return true;
+ }
+ $(this).closest("tr").find("input").focus();
+ });
+ }
+ });
+});
+
+function indexEditorDialog(url, title, callback_success, callback_failure)
+{
+ /*Remove the hidden dialogs if there are*/
+ if ($('#edit_index_dialog').length !== 0) {
+ $('#edit_index_dialog').remove();
+ }
+ var $div = $('<div id="edit_index_dialog"></div>');
+
+ /**
+ * @var button_options Object that stores the options
+ * passed to jQueryUI dialog
+ */
+ var button_options = {};
+ button_options[PMA_messages.strGo] = function () {
+ /**
+ * @var the_form object referring to the export form
+ */
+ var $form = $("#index_frm");
+ var $msgbox = PMA_ajaxShowMessage(PMA_messages['strProcessingRequest']);
+ PMA_prepareForAjaxRequest($form);
+ //User wants to submit the form
+ $.post($form.attr('action'), $form.serialize() + "&do_save_data=1", function (data) {
+ if ($("#sqlqueryresults").length !== 0) {
+ $("#sqlqueryresults").remove();
+ }
+ if (data.success === true) {
+ PMA_ajaxShowMessage(data.message);
+ if ($('#result_query').length) {
+ $('#result_query').remove();
+ }
+ if (data.sql_query) {
+ $('<div id="result_query"></div>')
+ .html(data.sql_query)
+ .prependTo('#page_content');
+ PMA_highlightSQL($('#page_content'));
+ }
+ $("#result_query .notice").remove();
+ $("#result_query").prepend(data.message);
+ /*Reload the field form*/
+ $("#table_index").remove();
+ var $temp_div = $("<div id='temp_div'><div>").append(data.index_table);
+ $temp_div.find("#table_index").insertAfter("#index_header");
+ if ($("#edit_index_dialog").length > 0) {
+ $("#edit_index_dialog").dialog("close");
+ }
+ $('div.no_indexes_defined').hide();
+ if (callback_success) {
+ callback_success();
+ }
+ PMA_reloadNavigation();
+ } else {
+ var $temp_div = $("<div id='temp_div'><div>").append(data.error);
+ var $error;
+ if ($temp_div.find(".error code").length !== 0) {
+ $error = $temp_div.find(".error code").addClass("error");
+ } else {
+ $error = $temp_div;
+ }
+ if (callback_failure) {
+ callback_failure();
+ }
+ PMA_ajaxShowMessage($error, false);
+ }
+ }); // end $.post()
+ };
+ button_options[PMA_messages.strCancel] = function () {
+ $(this).dialog('close');
+ };
+ var $msgbox = PMA_ajaxShowMessage();
+ $.get("tbl_indexes.php", url, function (data) {
+ if (data.success === false) {
+ //in the case of an error, show the error message returned.
+ PMA_ajaxShowMessage(data.error, false);
+ } else {
+ PMA_ajaxRemoveMessage($msgbox);
+ // Show dialog if the request was successful
+ $div
+ .append(data.message)
+ .dialog({
+ title: title,
+ width: 450,
+ open: PMA_verifyColumnsProperties,
+ modal: true,
+ buttons: button_options,
+ close: function () {
+ $(this).remove();
+ }
+ });
+ checkIndexType();
+ checkIndexName("index_frm");
+ PMA_showHints($div);
+ // Add a slider for selecting how many columns to add to the index
+ $div.find('.slider').slider({
+ animate: true,
+ value: 1,
+ min: 1,
+ max: 16,
+ slide: function (event, ui) {
+ $(this).closest('fieldset').find('input[type=submit]').val(
+ $.sprintf(PMA_messages.strAddToIndex, ui.value)
+ );
+ }
+ });
+ // focus index size input on column picked
+ $div.find('table#index_columns select').change(function () {
+ if ($(this).find("option:selected").val() === '') {
+ return true;
+ }
+ $(this).closest("tr").find("input").focus();
+ });
+ // Focus the slider, otherwise it looks nearly transparent
+ $('a.ui-slider-handle').addClass('ui-state-focus');
+ // set focus on index name input, if empty
+ var input = $div.find('input#input_index_name');
+ input.val() || input.focus();
+ }
+ }); // end $.get()
+}
+
+/**
+ * Function to display tooltips that were
+ * generated on the PHP side by PMA_Util::showHint()
+ *
+ * @param object $div a div jquery object which specifies the
+ * domain for searching for tooltips. If we
+ * omit this parameter the function searches
+ * in the whole body
+ **/
+function PMA_showHints($div)
+{
+ if ($div === undefined || ! $div instanceof jQuery || $div.length === 0) {
+ $div = $("body");
+ }
+ $div.find('.pma_hint').each(function () {
+ PMA_tooltip(
+ $(this).children('img'),
+ 'img',
+ $(this).children('span').html()
+ );
+ });
+}
+
+AJAX.registerOnload('functions.js', function () {
+ PMA_showHints();
+});
+
+function PMA_mainMenuResizerCallback() {
+ // 5 px margin for jumping menu in Chrome
+ return $(document.body).width() - 5;
+}
+// This must be fired only once after the inital page load
+$(function () {
+ // Initialise the menu resize plugin
+ $('#topmenu').menuResizer(PMA_mainMenuResizerCallback);
+ // register resize event
+ $(window).resize(function () {
+ $('#topmenu').menuResizer('resize');
+ });
+});
+
+/**
+ * Get the row number from the classlist (for example, row_1)
+ */
+function PMA_getRowNumber(classlist)
+{
+ return parseInt(classlist.split(/\s+row_/)[1], 10);
+}
+
+/**
+ * Changes status of slider
+ */
+function PMA_set_status_label($element)
+{
+ var text;
+ if ($element.css('display') == 'none') {
+ text = '+ ';
+ } else {
+ text = '- ';
+ }
+ $element.closest('.slide-wrapper').prev().find('span').text(text);
+}
+
+/**
+ * var toggleButton This is a function that creates a toggle
+ * sliding button given a jQuery reference
+ * to the correct DOM element
+ */
+var toggleButton = function ($obj) {
+ // In rtl mode the toggle switch is flipped horizontally
+ // so we need to take that into account
+ var right;
+ if ($('span.text_direction', $obj).text() == 'ltr') {
+ right = 'right';
+ } else {
+ right = 'left';
+ }
+ /**
+ * var h Height of the button, used to scale the
+ * background image and position the layers
+ */
+ var h = $obj.height();
+ $('img', $obj).height(h);
+ $('table', $obj).css('bottom', h - 1);
+ /**
+ * var on Width of the "ON" part of the toggle switch
+ * var off Width of the "OFF" part of the toggle switch
+ */
+ var on = $('td.toggleOn', $obj).width();
+ var off = $('td.toggleOff', $obj).width();
+ // Make the "ON" and "OFF" parts of the switch the same size
+ // + 2 pixels to avoid overflowed
+ $('td.toggleOn > div', $obj).width(Math.max(on, off) + 2);
+ $('td.toggleOff > div', $obj).width(Math.max(on, off) + 2);
+ /**
+ * var w Width of the central part of the switch
+ */
+ var w = parseInt(($('img', $obj).height() / 16) * 22, 10);
+ // Resize the central part of the switch on the top
+ // layer to match the background
+ $('table td:nth-child(2) > div', $obj).width(w);
+ /**
+ * var imgw Width of the background image
+ * var tblw Width of the foreground layer
+ * var offset By how many pixels to move the background
+ * image, so that it matches the top layer
+ */
+ var imgw = $('img', $obj).width();
+ var tblw = $('table', $obj).width();
+ var offset = parseInt(((imgw - tblw) / 2), 10);
+ // Move the background to match the layout of the top layer
+ $obj.find('img').css(right, offset);
+ /**
+ * var offw Outer width of the "ON" part of the toggle switch
+ * var btnw Outer width of the central part of the switch
+ */
+ var offw = $('td.toggleOff', $obj).outerWidth();
+ var btnw = $('table td:nth-child(2)', $obj).outerWidth();
+ // Resize the main div so that exactly one side of
+ // the switch plus the central part fit into it.
+ $obj.width(offw + btnw + 2);
+ /**
+ * var move How many pixels to move the
+ * switch by when toggling
+ */
+ var move = $('td.toggleOff', $obj).outerWidth();
+ // If the switch is initialized to the
+ // OFF state we need to move it now.
+ if ($('div.container', $obj).hasClass('off')) {
+ if (right == 'right') {
+ $('div.container', $obj).animate({'left': '-=' + move + 'px'}, 0);
+ } else {
+ $('div.container', $obj).animate({'left': '+=' + move + 'px'}, 0);
+ }
+ }
+ // Attach an 'onclick' event to the switch
+ $('div.container', $obj).click(function () {
+ if ($(this).hasClass('isActive')) {
+ return false;
+ } else {
+ $(this).addClass('isActive');
+ }
+ var $msg = PMA_ajaxShowMessage();
+ var $container = $(this);
+ var callback = $('span.callback', this).text();
+ var operator, url, removeClass, addClass;
+ // Perform the actual toggle
+ if ($(this).hasClass('on')) {
+ if (right == 'right') {
+ operator = '-=';
+ } else {
+ operator = '+=';
+ }
+ url = $(this).find('td.toggleOff > span').text();
+ removeClass = 'on';
+ addClass = 'off';
+ } else {
+ if (right == 'right') {
+ operator = '+=';
+ } else {
+ operator = '-=';
+ }
+ url = $(this).find('td.toggleOn > span').text();
+ removeClass = 'off';
+ addClass = 'on';
+ }
+ $.post(url, {'ajax_request': true}, function (data) {
+ if (data.success === true) {
+ PMA_ajaxRemoveMessage($msg);
+ $container
+ .removeClass(removeClass)
+ .addClass(addClass)
+ .animate({'left': operator + move + 'px'}, function () {
+ $container.removeClass('isActive');
+ });
+ eval(callback);
+ } else {
+ PMA_ajaxShowMessage(data.error, false);
+ $container.removeClass('isActive');
+ }
+ });
+ });
+};
+
+/**
+ * Unbind all event handlers before tearing down a page
+ */
+AJAX.registerTeardown('functions.js', function () {
+ $('div.container').unbind('click');
+});
+/**
+ * Initialise all toggle buttons
+ */
+AJAX.registerOnload('functions.js', function () {
+ $('div.toggleAjax').each(function () {
+ var $button = $(this).show();
+ $button.find('img').each(function () {
+ if (this.complete) {
+ toggleButton($button);
+ } else {
+ $(this).load(function () {
+ toggleButton($button);
+ });
+ }
+ });
+ });
+});
+
+/**
+ * Unbind all event handlers before tearing down a page
+ */
+AJAX.registerTeardown('functions.js', function () {
+ $('.vpointer').die('hover');
+ $('.vmarker').die('click');
+ $('#pageselector').die('change');
+ $('a.formLinkSubmit').die('click');
+ $('#update_recent_tables').unbind('ready');
+});
+/**
+ * Vertical pointer
+ */
+AJAX.registerOnload('functions.js', function () {
+ $('.vpointer').live('hover',
+ //handlerInOut
+ function (e) {
+ var $this_td = $(this);
+ var row_num = PMA_getRowNumber($this_td.attr('class'));
+ // for all td of the same vertical row, toggle hover
+ $('.vpointer').filter('.row_' + row_num).toggleClass('hover');
+ }
+ );
+
+
+ /**
+ * Vertical marker
+ */
+ $('.vmarker').live('click', function (e) {
+ // do not trigger when clicked on anchor
+ if ($(e.target).is('a, img, a *')) {
+ return;
+ }
+
+ var $this_td = $(this);
+ var row_num = PMA_getRowNumber($this_td.attr('class'));
+
+ // XXX: FF fires two click events for <label> (label and checkbox), so we need to handle this differently
+ var $checkbox = $('.vmarker').filter('.row_' + row_num + ':first').find(':checkbox');
+ if ($checkbox.length) {
+ // checkbox in a row, add or remove class depending on checkbox state
+ var checked = $checkbox.prop('checked');
+ if (!$(e.target).is(':checkbox, label')) {
+ checked = !checked;
+ $checkbox.prop('checked', checked);
+ }
+ // for all td of the same vertical row, toggle the marked class
+ if (checked) {
+ $('.vmarker').filter('.row_' + row_num).addClass('marked');
+ } else {
+ $('.vmarker').filter('.row_' + row_num).removeClass('marked');
+ }
+ } else {
+ // normaln data table, just toggle class
+ $('.vmarker').filter('.row_' + row_num).toggleClass('marked');
+ }
+ });
+
+ /**
+ * Autosubmit page selector
+ */
+ $('select.pageselector').live('change', function (event) {
+ event.stopPropagation();
+ // Check where to load the new content
+ if ($(this).closest("#pma_navigation").length === 0) {
+ // For the main page we don't need to do anything,
+ $(this).closest("form").submit();
+ } else {
+ // but for the navigation we need to manually replace the content
+ PMA_navigationTreePagination($(this));
+ }
+ });
+
+ /**
+ * Load version information asynchronously.
+ */
+ if ($('li.jsversioncheck').length > 0) {
+ $.getJSON('version_check.php', {}, PMA_current_version);
+ }
+
+ if ($('#is_git_revision').length > 0) {
+ setTimeout(PMA_display_git_revision, 10);
+ }
+
+ /**
+ * Slider effect.
+ */
+ PMA_init_slider();
+
+ /**
+ * Enables the text generated by PMA_Util::linkOrButton() to be clickable
+ */
+ $('a.formLinkSubmit').live('click', function (e) {
+
+ if ($(this).attr('href').indexOf('=') != -1) {
+ var data = $(this).attr('href').substr($(this).attr('href').indexOf('#') + 1).split('=', 2);
+ $(this).parents('form').append('<input type="hidden" name="' + data[0] + '" value="' + data[1] + '"/>');
+ }
+ $(this).parents('form').submit();
+ return false;
+ });
+
+ if ($('#update_recent_tables').length) {
+ $.get(
+ $('#update_recent_tables').attr('href'),
+ function (data) {
+ if (data.success === true) {
+ $('#recentTable').html(data.options);
+ }
+ }
+ );
+ }
+
+}); // end of $()
+
+
+/**
+ * Initializes slider effect.
+ */
+function PMA_init_slider()
+{
+ $('div.pma_auto_slider').each(function () {
+ var $this = $(this);
+ if ($this.data('slider_init_done')) {
+ return;
+ }
+ var $wrapper = $('<div>', {'class': 'slide-wrapper'});
+ $wrapper.toggle($this.is(':visible'));
+ $('<a>', {href: '#' + this.id, "class": 'ajax'})
+ .text(this.title)
+ .prepend($('<span>'))
+ .insertBefore($this)
+ .click(function () {
+ var $wrapper = $this.closest('.slide-wrapper');
+ var visible = $this.is(':visible');
+ if (!visible) {
+ $wrapper.show();
+ }
+ $this[visible ? 'hide' : 'show']('blind', function () {
+ $wrapper.toggle(!visible);
+ PMA_set_status_label($this);
+ });
+ return false;
+ });
+ $this.wrap($wrapper);
+ PMA_set_status_label($this);
+ $this.data('slider_init_done', 1);
+ });
+}
+
+/**
+ * Initializes slider effect.
+ */
+AJAX.registerOnload('functions.js', function () {
+ PMA_init_slider();
+});
+
+/**
+ * Restores sliders to the state they were in before initialisation.
+ */
+AJAX.registerTeardown('functions.js', function () {
+ $('div.pma_auto_slider').each(function () {
+ var $this = $(this);
+ $this.removeData();
+ $this.parent().replaceWith($this);
+ $this.parent().children('a').remove();
+ });
+});
+
+/**
+ * Creates a message inside an object with a sliding effect
+ *
+ * @param msg A string containing the text to display
+ * @param $obj a jQuery object containing the reference
+ * to the element where to put the message
+ * This is optional, if no element is
+ * provided, one will be created below the
+ * navigation links at the top of the page
+ *
+ * @return bool True on success, false on failure
+ */
+function PMA_slidingMessage(msg, $obj)
+{
+ if (msg === undefined || msg.length === 0) {
+ // Don't show an empty message
+ return false;
+ }
+ if ($obj === undefined || ! $obj instanceof jQuery || $obj.length === 0) {
+ // If the second argument was not supplied,
+ // we might have to create a new DOM node.
+ if ($('#PMA_slidingMessage').length === 0) {
+ $('#page_content').prepend(
+ '<span id="PMA_slidingMessage" ' +
+ 'style="display: inline-block;"></span>'
+ );
+ }
+ $obj = $('#PMA_slidingMessage');
+ }
+ if ($obj.has('div').length > 0) {
+ // If there already is a message inside the
+ // target object, we must get rid of it
+ $obj
+ .find('div')
+ .first()
+ .fadeOut(function () {
+ $obj
+ .children()
+ .remove();
+ $obj
+ .append('<div>' + msg + '</div>');
+ // highlight any sql before taking height;
+ PMA_highlightSQL($obj);
+ $obj.find('div')
+ .first()
+ .hide();
+ $obj
+ .animate({
+ height: $obj.find('div').first().height()
+ })
+ .find('div')
+ .first()
+ .fadeIn();
+ });
+ } else {
+ // Object does not already have a message
+ // inside it, so we simply slide it down
+ $obj.width('100%')
+ .html('<div>' + msg + '</div>');
+ // highlight any sql before taking height;
+ PMA_highlightSQL($obj);
+ var h = $obj
+ .find('div')
+ .first()
+ .hide()
+ .height();
+ $obj
+ .find('div')
+ .first()
+ .css('height', 0)
+ .show()
+ .animate({
+ height: h
+ }, function () {
+ // Set the height of the parent
+ // to the height of the child
+ $obj
+ .height(
+ $obj
+ .find('div')
+ .first()
+ .height()
+ );
+ });
+ }
+ return true;
+} // end PMA_slidingMessage()
+
+/**
+ * Unbind all event handlers before tearing down a page
+ */
+AJAX.registerTeardown('functions.js', function () {
+ $("#drop_tbl_anchor.ajax").die('click');
+ $("#drop_view_anchor.ajax").die('click');
+ $("#truncate_tbl_anchor.ajax").die('click');
+});
+/**
+ * Attach Ajax event handlers for Drop Table.
+ */
+AJAX.registerOnload('functions.js', function () {
+ $("#drop_tbl_anchor.ajax").live('click', function (event) {
+ event.preventDefault();
+ /**
+ * @var question String containing the question to be asked for confirmation
+ */
+ var question = PMA_messages.strDropTableStrongWarning + ' ';
+ question += $.sprintf(
+ PMA_messages.strDoYouReally,
+ 'DROP TABLE ' + PMA_commonParams.get('table')
+ );
+
+ $(this).PMA_confirm(question, $(this).attr('href'), function (url) {
+
+ var $msgbox = PMA_ajaxShowMessage(PMA_messages.strProcessingRequest);
+ $.get(url, {'is_js_confirmed': '1', 'ajax_request': true}, function (data) {
+ if (data.success === true) {
+ PMA_ajaxRemoveMessage($msgbox);
+ // Table deleted successfully, refresh both the frames
+ PMA_reloadNavigation();
+ PMA_commonParams.set('table', '');
+ PMA_commonActions.refreshMain(
+ PMA_commonParams.get('opendb_url'),
+ function () {
+ PMA_ajaxShowMessage(data.message);
+ }
+ );
+ } else {
+ PMA_ajaxShowMessage(data.error, false);
+ }
+ }); // end $.get()
+ }); // end $.PMA_confirm()
+ }); //end of Drop Table Ajax action
+
+ $("#drop_view_anchor.ajax").live('click', function (event) {
+ event.preventDefault();
+ /**
+ * @var question String containing the question to be asked for confirmation
+ */
+ var question = PMA_messages.strDropTableStrongWarning + ' ';
+ question += $.sprintf(
+ PMA_messages.strDoYouReally,
+ 'DROP VIEW ' + PMA_commonParams.get('table')
+ );
+
+ $(this).PMA_confirm(question, $(this).attr('href'), function (url) {
+
+ var $msgbox = PMA_ajaxShowMessage(PMA_messages.strProcessingRequest);
+ $.get(url, {'is_js_confirmed': '1', 'ajax_request': true}, function (data) {
+ if (data.success === true) {
+ PMA_ajaxRemoveMessage($msgbox);
+ // Table deleted successfully, refresh both the frames
+ PMA_reloadNavigation();
+ PMA_commonParams.set('table', '');
+ PMA_commonActions.refreshMain(
+ PMA_commonParams.get('opendb_url'),
+ function () {
+ PMA_ajaxShowMessage(data.message);
+ }
+ );
+ } else {
+ PMA_ajaxShowMessage(data.error, false);
+ }
+ }); // end $.get()
+ }); // end $.PMA_confirm()
+ }); //end of Drop View Ajax action
+
+ $("#truncate_tbl_anchor.ajax").live('click', function (event) {
+ event.preventDefault();
+ /**
+ * @var question String containing the question to be asked for confirmation
+ */
+ var question = PMA_messages.strTruncateTableStrongWarning + ' ';
+ question += $.sprintf(
+ PMA_messages.strDoYouReally,
+ 'TRUNCATE ' + PMA_commonParams.get('table')
+ );
+ $(this).PMA_confirm(question, $(this).attr('href'), function (url) {
+ PMA_ajaxShowMessage(PMA_messages.strProcessingRequest);
+ $.get(url, {'is_js_confirmed': '1', 'ajax_request': true}, function (data) {
+ if ($("#sqlqueryresults").length !== 0) {
+ $("#sqlqueryresults").remove();
+ }
+ if ($("#result_query").length !== 0) {
+ $("#result_query").remove();
+ }
+ if (data.success === true) {
+ PMA_ajaxShowMessage(data.message);
+ $("<div id='sqlqueryresults'></div>").prependTo("#page_content");
+ $("#sqlqueryresults").html(data.sql_query);
+ PMA_highlightSQL($('#page_content'));
+ } else {
+ PMA_ajaxShowMessage(data.error, false);
+ }
+ }); // end $.get()
+ }); // end $.PMA_confirm()
+ }); //end of Truncate Table Ajax action
+}); // end of $() for Truncate Table
+
+/**
+ * Attach CodeMirror2 editor to SQL edit area.
+ */
+AJAX.registerOnload('functions.js', function () {
+ var $elm = $('#sqlquery');
+ if ($elm.length > 0) {
+ if (typeof CodeMirror != 'undefined') {
+ // for codemirror
+ codemirror_editor = CodeMirror.fromTextArea($elm[0], {
+ lineNumbers: true,
+ matchBrackets: true,
+ indentUnit: 4,
+ mode: "text/x-mysql",
+ lineWrapping: true
+ });
+ codemirror_editor.focus();
+ $(codemirror_editor.getWrapperElement()).bind(
+ 'keydown',
+ catchKeypressesFromSqlTextboxes
+ );
+ } else {
+ // without codemirror
+ $elm.focus().bind('keydown', catchKeypressesFromSqlTextboxes);
+ }
+ }
+ PMA_highlightSQL($('body'));
+});
+AJAX.registerTeardown('functions.js', function () {
+ if (codemirror_editor) {
+ $('#sqlquery').text(codemirror_editor.getValue());
+ codemirror_editor.toTextArea();
+ codemirror_editor = false;
+ }
+});
+
+/**
+ * jQuery plugin to cancel selection in HTML code.
+ */
+(function ($) {
+ $.fn.noSelect = function (p) { //no select plugin by Paulo P.Marinas
+ var prevent = (p === null) ? true : p;
+ if (prevent) {
+ return this.each(function () {
+ if ($.browser.msie || $.browser.safari) {
+ $(this).bind('selectstart', function () {
+ return false;
+ });
+ } else if ($.browser.mozilla) {
+ $(this).css('MozUserSelect', 'none');
+ $('body').trigger('focus');
+ } else if ($.browser.opera) {
+ $(this).bind('mousedown', function () {
+ return false;
+ });
+ } else {
+ $(this).attr('unselectable', 'on');
+ }
+ });
+ } else {
+ return this.each(function () {
+ if ($.browser.msie || $.browser.safari) {
+ $(this).unbind('selectstart');
+ } else if ($.browser.mozilla) {
+ $(this).css('MozUserSelect', 'inherit');
+ } else if ($.browser.opera) {
+ $(this).unbind('mousedown');
+ } else {
+ $(this).removeAttr('unselectable');
+ }
+ });
+ }
+ }; //end noSelect
+})(jQuery);
+
+/**
+ * jQuery plugin to correctly filter input fields by value, needed
+ * because some nasty values may break selector syntax
+ */
+(function ($) {
+ $.fn.filterByValue = function (value) {
+ return this.filter(function () {
+ return $(this).val() === value;
+ });
+ };
+})(jQuery);
+
+/**
+ * Create a jQuery UI tooltip
+ *
+ * @param $elements jQuery object representing the elements
+ * @param item the item
+ * (see http://api.jqueryui.com/tooltip/#option-items)
+ * @param myContent content of the tooltip
+ * @param additionalOptions to override the default options
+ *
+ */
+function PMA_tooltip($elements, item, myContent, additionalOptions)
+{
+ if ($('#no_hint').length > 0) {
+ return;
+ }
+
+ var defaultOptions = {
+ content: myContent,
+ items: item,
+ tooltipClass: "tooltip",
+ track: true,
+ show: false,
+ hide: false
+ };
+
+ $elements.tooltip($.extend(true, defaultOptions, additionalOptions));
+}
+
+/**
+ * Return value of a cell in a table.
+ */
+function PMA_getCellValue(td) {
+ var $td = $(td);
+ if ($td.is('.null')) {
+ return '';
+ } else if (! $td.is('.to_be_saved') && $td.data('original_data')) {
+ return $td.data('original_data');
+ } else {
+ return $td.text();
+ }
+}
+
+/**
+ * Unbind all event handlers before tearing down a page
+ */
+AJAX.registerTeardown('functions.js', function () {
+ $('a.themeselect').die('click');
+ $('.autosubmit').die('change');
+ $('a.take_theme').unbind('click');
+});
+
+AJAX.registerOnload('functions.js', function () {
+ /**
+ * Theme selector.
+ */
+ $('a.themeselect').live('click', function (e) {
+ window.open(
+ e.target,
+ 'themes',
+ 'left=10,top=20,width=510,height=350,scrollbars=yes,status=yes,resizable=yes'
+ );
+ return false;
+ });
+
+ /**
+ * Automatic form submission on change.
+ */
+ $('.autosubmit').live('change', function (e) {
+ $(this).closest('form').submit();
+ });
+
+ /**
+ * Theme changer.
+ */
+ $('a.take_theme').click(function (e) {
+ var what = this.name;
+ if (window.opener && window.opener.document.forms['setTheme'].elements['set_theme']) {
+ window.opener.document.forms['setTheme'].elements['set_theme'].value = what;
+ window.opener.document.forms['setTheme'].submit();
+ window.close();
+ return false;
+ }
+ return true;
+ });
+});
+
+/**
+ * Clear text selection
+ */
+function PMA_clearSelection() {
+ if (document.selection && document.selection.empty) {
+ document.selection.empty();
+ } else if (window.getSelection) {
+ var sel = window.getSelection();
+ if (sel.empty) {
+ sel.empty();
+ }
+ if (sel.removeAllRanges) {
+ sel.removeAllRanges();
+ }
+ }
+}
+
+/**
+ * HTML escaping
+ */
+function escapeHtml(unsafe) {
+ return unsafe
+ .replace(/&/g, "&amp;")
+ .replace(/</g, "&lt;")
+ .replace(/>/g, "&gt;")
+ .replace(/"/g, "&quot;")
+ .replace(/'/g, "&#039;");
+}
+
+/**
+ * Print button
+ */
+function printPage()
+{
+ // Do print the page
+ if (typeof(window.print) != 'undefined') {
+ window.print();
+ }
+}
+
+/**
+ * Unbind all event handlers before tearing down a page
+ */
+AJAX.registerTeardown('functions.js', function () {
+ $('input#print').unbind('click');
+ $('a.create_view.ajax').die('click');
+ $('#createViewDialog').find('input, select').die('keydown');
+});
+
+AJAX.registerOnload('functions.js', function () {
+ $('input#print').click(printPage);
+ /**
+ * Ajaxification for the "Create View" action
+ */
+ $('a.create_view.ajax').live('click', function (e) {
+ e.preventDefault();
+ PMA_createViewDialog($(this));
+ });
+ /**
+ * Attach Ajax event handlers for input fields in the editor
+ * and used to submit the Ajax request when the ENTER key is pressed.
+ */
+ $('#createViewDialog').find('input, select').live('keydown', function (e) {
+ if (e.which === 13) { // 13 is the ENTER key
+ e.preventDefault();
+
+ // with preventing default, selection by <select> tag
+ // was also prevented in IE
+ $(this).blur();
+
+ $(this).closest('.ui-dialog').find('.ui-button:first').click();
+ }
+ }); // end $.live()
+
+ var $elm = $('textarea[name="view[as]"]');
+ if ($elm.length > 0) {
+ if (typeof CodeMirror != 'undefined') {
+ syntaxHighlighter = CodeMirror.fromTextArea(
+ $elm[0],
+ {
+ lineNumbers: true,
+ matchBrackets: true,
+ indentUnit: 4,
+ mode: "text/x-mysql",
+ lineWrapping: true
+ }
+ );
+ }
+ }
+});
+
+function PMA_createViewDialog($this)
+{
+ var $msg = PMA_ajaxShowMessage();
+ var syntaxHighlighter = null;
+ $.get($this.attr('href') + '&ajax_request=1&ajax_dialog=1', function (data) {
+ if (data.success === true) {
+ PMA_ajaxRemoveMessage($msg);
+ var buttonOptions = {};
+ buttonOptions[PMA_messages.strGo] = function () {
+ if (typeof CodeMirror !== 'undefined') {
+ syntaxHighlighter.save();
+ }
+ $msg = PMA_ajaxShowMessage();
+ $.get('view_create.php', $('#createViewDialog').find('form').serialize(), function (data) {
+ PMA_ajaxRemoveMessage($msg);
+ if (data.success === true) {
+ $('#createViewDialog').dialog("close");
+ $('#result_query').html(data.message);
+ PMA_reloadNavigation();
+ } else {
+ PMA_ajaxShowMessage(data.error, false);
+ }
+ });
+ };
+ buttonOptions[PMA_messages.strClose] = function () {
+ $(this).dialog("close");
+ };
+ var $dialog = $('<div/>').attr('id', 'createViewDialog').append(data.message).dialog({
+ width: 600,
+ minWidth: 400,
+ modal: true,
+ buttons: buttonOptions,
+ title: PMA_messages.strCreateView,
+ close: function () {
+ $(this).remove();
+ }
+ });
+ // Attach syntax highlited editor
+ if (typeof CodeMirror !== 'undefined') {
+ var $elm = $dialog.find('textarea');
+ var opts = {lineNumbers: true, matchBrackets: true, indentUnit: 4, mode: "text/x-mysql", lineWrapping: true};
+ syntaxHighlighter = CodeMirror.fromTextArea($elm[0], opts);
+ }
+ $('input:visible[type=text]', $dialog).first().focus();
+ } else {
+ PMA_ajaxShowMessage(data.error);
+ }
+ });
+}
+
+/**
+ * Makes the breadcrumbs and the menu bar float at the top of the viewport
+ */
+$(function () {
+ if ($("#floating_menubar").length && $('#PMA_disable_floating_menubar').length === 0) {
+ var left = $('html').attr('dir') == 'ltr' ? 'left' : 'right';
+ $("#floating_menubar")
+ .css('margin-' + left, $('#pma_navigation').width() + $('#pma_navigation_resizer').width())
+ .css(left, 0)
+ .css({
+ 'position': 'fixed',
+ 'top': 0,
+ 'width': '100%',
+ 'z-index': 500
+ })
+ .append($('#serverinfo'))
+ .append($('#topmenucontainer'));
+ // Allow the DOM to render, then adjust the padding on the body
+ setTimeout(function () {
+ $('body').css(
+ 'padding-top',
+ $('#floating_menubar').outerHeight(true)
+ );
+ $('#topmenu').menuResizer('resize');
+ }, 4);
+ }
+});
+
+/**
+ * Scrolls the page to the top if clicking the serverinfo bar
+ */
+$(function () {
+ $(document).delegate("#serverinfo, #goto_pagetop", "click", function (event) {
+ event.preventDefault();
+ $('html, body').animate({scrollTop: 0}, 'fast');
+ });
+});
+
+var checkboxes_sel = "input.checkall:checkbox:enabled";
+/**
+ * Watches checkboxes in a form to set the checkall box accordingly
+ */
+var checkboxes_changed = function () {
+ var $form = $(this.form);
+ // total number of checkboxes in current form
+ var total_boxes = $form.find(checkboxes_sel).length;
+ // number of checkboxes checked in current form
+ var checked_boxes = $form.find(checkboxes_sel + ":checked").length;
+ var $checkall = $form.find("input.checkall_box");
+ if (total_boxes == checked_boxes) {
+ $checkall.prop({checked: true, indeterminate: false});
+ }
+ else if (checked_boxes > 0) {
+ $checkall.prop({checked: true, indeterminate: true});
+ }
+ else {
+ $checkall.prop({checked: false, indeterminate: false});
+ }
+};
+$(checkboxes_sel).live("change", checkboxes_changed);
+
+$("input.checkall_box").live("change", function () {
+ var is_checked = $(this).is(":checked");
+ $(this.form).find(checkboxes_sel).prop("checked", is_checked)
+ .parents("tr").toggleClass("marked", is_checked);
+});
+
+/**
+ * Toggles row colors of a set of 'tr' elements starting from a given element
+ *
+ * @param $start Starting element
+ */
+function toggleRowColors($start)
+{
+ for (var $curr_row = $start; $curr_row.length > 0; $curr_row = $curr_row.next()) {
+ if ($curr_row.hasClass('odd')) {
+ $curr_row.removeClass('odd').addClass('even');
+ } else if ($curr_row.hasClass('even')) {
+ $curr_row.removeClass('even').addClass('odd');
+ }
+ }
+}
+
+/**
+ * Formats a byte number to human-readable form
+ *
+ * @param bytes the bytes to format
+ * @param optional subdecimals the number of digits after the point
+ * @param optional pointchar the char to use as decimal point
+ */
+function formatBytes(bytes, subdecimals, pointchar) {
+ if (!subdecimals) {
+ subdecimals = 0;
+ }
+ if (!pointchar) {
+ pointchar = '.';
+ }
+ var units = ['B', 'KiB', 'MiB', 'GiB'];
+ for (var i = 0; bytes > 1024 && i < units.length; i++) {
+ bytes /= 1024;
+ }
+ var factor = Math.pow(10, subdecimals);
+ bytes = Math.round(bytes * factor) / factor;
+ bytes = bytes.toString().split('.').join(pointchar);
+ return bytes + ' ' + units[i];
+}
+
+AJAX.registerOnload('functions.js', function () {
+ /**
+ * Opens pma more themes link in themes browser, in new window instead of popup
+ * This way, we don't break HTML validity
+ */
+ $("a._blank").prop("target", "_blank");
+ /**
+ * Reveal the login form to users with JS enabled
+ * and focus the appropriate input field
+ */
+ var $loginform = $('#loginform');
+ if ($loginform.length) {
+ $loginform.find('.js-show').show();
+ if ($('#input_username').val()) {
+ $('#input_password').focus();
+ } else {
+ $('#input_username').focus();
+ }
+ }
+});
+
+/**
+ * When user gets an ajax session expiry message, we show a login link
+ */
+$('a.login-link').live('click', function (e) {
+ e.preventDefault();
+ window.location.reload(true);
+});
+
+/**
+ * Dynamically adjust the width of the boxes
+ * on the table and db operations pages
+ */
+(function () {
+ function DynamicBoxes() {
+ var $boxContainer = $('#boxContainer');
+ if ($boxContainer.length) {
+ var minWidth = $boxContainer.data('box-width');
+ var viewport = $(window).width() - $('#pma_navigation').width();
+ var slots = Math.floor(viewport / minWidth);
+ $boxContainer.children()
+ .each(function () {
+ if (viewport < minWidth) {
+ $(this).width(minWidth);
+ } else {
+ $(this).css('width', ((1 / slots) * 100) + "%");
+ }
+ })
+ .removeClass('clearfloat')
+ .filter(':nth-child(' + slots + 'n+1)')
+ .addClass('clearfloat');
+ }
+ }
+ AJAX.registerOnload('functions.js', function () {
+ DynamicBoxes();
+ });
+ $(function () {
+ $(window).resize(DynamicBoxes);
+ });
+})();
+
+/**
+ * Formats timestamp for display
+ */
+function PMA_formatDateTime(date, seconds) {
+ var result = $.datepicker.formatDate('yy-mm-dd', date);
+ var timefmt = 'HH:mm';
+ if (seconds) {
+ timefmt = 'HH:mm:ss';
+ }
+ return result + ' ' + $.datepicker.formatTime(
+ timefmt, {
+ hour: date.getHours(),
+ minute: date.getMinutes(),
+ second: date.getSeconds()
+ }
+ );
+}
diff --git a/js/get_image.js.php b/js/get_image.js.php
new file mode 100644
index 0000000000..c541bdd496
--- /dev/null
+++ b/js/get_image.js.php
@@ -0,0 +1,141 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Provides the functionality for retreiving images
+ * which may be actual images or an icon from a sprite
+ *
+ * @package PhpMyAdmin
+ */
+chdir('..');
+
+// Send correct type:
+header('Content-Type: text/javascript; charset=UTF-8');
+header('Expires: ' . gmdate('D, d M Y H:i:s', time() + 3600) . ' GMT');
+
+// Avoid loading the full common.inc.php because this would add many
+// non-js-compatible stuff like DOCTYPE
+define('PMA_MINIMUM_COMMON', true);
+require_once './libraries/common.inc.php';
+
+// Get the data for the sprites, if it's available
+if (is_readable($_SESSION['PMA_Theme']->getPath() . '/sprites.lib.php')) {
+ include $_SESSION['PMA_Theme']->getPath() . '/sprites.lib.php';
+}
+$sprites = array();
+if (function_exists('PMA_sprites')) {
+ $sprites = PMA_sprites();
+}
+// We only need the keys from the array of sprites data,
+// since they contain the (partial) class names
+$keys = array();
+foreach ($sprites as $key => $value) {
+ $keys[] = "'$key'";
+}
+
+?>
+/**
+ * Returns an HTML IMG tag for a particular image from a theme,
+ * which may be an actual file or an icon from a sprite
+ *
+ * @param string image The name of the file to get
+ * @param string alternate Used to set 'alt' and 'title' attributes of the image
+ * @param object attributes An associative array of other attributes
+ *
+ * @return Object The requested image, this object has two methods:
+ * .toString() - Returns the IMG tag for the requested image
+ * .attr(name) - Returns a particular attribute of the IMG
+ * tag given it's name
+ * .attr(name, value) - Sets a particular attribute of the IMG
+ * tag to the given value
+ * And one property:
+ * .isSprite - Whether the image is a sprite or not
+ */
+function PMA_getImage(image, alternate, attributes) {
+ var in_array = function (needle, haystack) {
+ for (var i in haystack) {
+ if (haystack[i] == needle) {
+ return true;
+ }
+ }
+ return false;
+ };
+ var sprites = [
+ <?php echo implode($keys, ",\n ") . "\n"; ?>
+ ];
+ // custom image object, it will eventually be returned by this functions
+ var retval = {
+ data: {
+ // this is private
+ alt: '',
+ title: '',
+ src: (typeof PMA_TEST_THEME == 'undefined' ? '' : '../')
+ + 'themes/dot.gif'
+ },
+ isSprite: true,
+ attr: function (name, value) {
+ if (value == undefined) {
+ if (this.data[name] == undefined) {
+ return '';
+ } else {
+ return this.data[name];
+ }
+ } else {
+ this.data[name] = value;
+ }
+ },
+ toString: function () {
+ var retval = '<' + 'img';
+ for (var i in this.data) {
+ retval += ' ' + i + '="' + this.data[i] + '"';
+ }
+ retval += ' /' + '>';
+ return retval;
+ }
+ };
+ // initialise missing parameters
+ if (attributes == undefined) {
+ attributes = {};
+ }
+ if (alternate == undefined) {
+ alternate = '';
+ }
+ // set alt
+ if (attributes.alt != undefined) {
+ retval.attr('alt', attributes.alt);
+ } else {
+ retval.attr('alt', alternate);
+ }
+ // set title
+ if (attributes.title != undefined) {
+ retval.attr('title', attributes.title);
+ } else {
+ retval.attr('title', alternate);
+ }
+ // set src
+ var klass = image.replace('.gif', '').replace('.png', '');
+ if (in_array(klass, sprites)) {
+ // it's an icon from a sprite
+ retval.attr('class', 'icon ic_' + klass);
+ } else {
+ // it's an image file
+ retval.isSprite = false;
+ retval.attr(
+ 'src',
+ "<?php echo $_SESSION['PMA_Theme']->getImgPath(); ?>" + image
+ );
+ }
+ // set all other attrubutes
+ for (var i in attributes) {
+ if (i == 'src') {
+ // do not allow to override the 'src' attribute
+ continue;
+ } else if (i == 'class') {
+ retval.attr(i, retval.attr('class') + ' ' + attributes[i]);
+ } else {
+ retval.attr(i, attributes[i]);
+ }
+ }
+
+ return retval;
+}
+//
diff --git a/js/get_scripts.js.php b/js/get_scripts.js.php
new file mode 100644
index 0000000000..fdeddf4c3b
--- /dev/null
+++ b/js/get_scripts.js.php
@@ -0,0 +1,45 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Concatenates several js files to reduce the number of
+ * http requests sent to the server
+ *
+ * @package PhpMyAdmin
+ */
+
+chdir('..');
+
+// Close session early as we won't write anything there
+session_write_close();
+
+// Send correct type
+header('Content-Type: text/javascript; charset=UTF-8');
+// Enable browser cache for 1 hour
+header('Expires: ' . gmdate('D, d M Y H:i:s', time() + 3600) . ' GMT');
+
+if (! empty($_GET['scripts']) && is_array($_GET['scripts'])) {
+ foreach ($_GET['scripts'] as $script) {
+ // Sanitise filename
+ $script_name = 'js';
+
+ $path = explode("/", $script);
+ foreach ($path as $index => $filename) {
+ // Allow alphanumeric, "." and "-" chars only, no files starting
+ // with .
+ if (preg_match("@^[\w][\w\.-]+$@", $filename)) {
+ $script_name .= DIRECTORY_SEPARATOR . $filename;
+ }
+ }
+
+ // Output file contents
+ if (preg_match("@\.js$@", $script_name) && is_readable($script_name)) {
+ readfile($script_name);
+ echo ";\n\n";
+ }
+ }
+}
+
+if (isset($_GET['call_done'])) {
+ echo "AJAX.scriptHandler.done();";
+}
+?>
diff --git a/js/gis_data_editor.js b/js/gis_data_editor.js
new file mode 100644
index 0000000000..bdd896459c
--- /dev/null
+++ b/js/gis_data_editor.js
@@ -0,0 +1,395 @@
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * @fileoverview functions used in GIS data editor
+ *
+ * @requires jQuery
+ *
+ */
+
+var gisEditorLoaded = false;
+
+/**
+ * Closes the GIS data editor and perform necessary clean up work.
+ */
+function closeGISEditor() {
+ $("#popup_background").fadeOut("fast");
+ $("#gis_editor").fadeOut("fast", function () {
+ $(this).empty();
+ });
+}
+
+/**
+ * Prepares the HTML recieved via AJAX.
+ */
+function prepareJSVersion() {
+ // Change the text on the submit button
+ $("#gis_editor input[name='gis_data[save]']")
+ .val(PMA_messages.strCopy)
+ .insertAfter($('#gis_data_textarea'))
+ .before('<br/><br/>');
+
+ // Add close and cancel links
+ $('#gis_data_editor').prepend('<a class="close_gis_editor" href="#">' + PMA_messages.strClose + '</a>');
+ $('<a class="cancel_gis_editor" href="#"> ' + PMA_messages.strCancel + '</a>')
+ .insertAfter($("input[name='gis_data[save]']"));
+
+ // Remove the unnecessary text
+ $('div#gis_data_output p').remove();
+
+ // Remove 'add' buttons and add links
+ $('#gis_editor input.add').each(function (e) {
+ var $button = $(this);
+ $button.addClass('addJs').removeClass('add');
+ var classes = $button.attr('class');
+ $button.replaceWith(
+ '<a class="' + classes +
+ '" name="' + $button.attr('name') +
+ '" href="#">+ ' + $button.val() + '</a>'
+ );
+ });
+}
+
+/**
+ * Returns the HTML for a data point.
+ *
+ * @param pointNumber point number
+ * @param prefix prefix of the name
+ * @returns the HTML for a data point
+ */
+function addDataPoint(pointNumber, prefix) {
+ return '<br/>' +
+ $.sprintf(PMA_messages.strPointN, (pointNumber + 1)) + ': ' +
+ '<label for="x">' + PMA_messages.strX + '</label>' +
+ '<input type="text" name="' + prefix + '[' + pointNumber + '][x]" value=""/>' +
+ '<label for="y">' + PMA_messages.strY + '</label>' +
+ '<input type="text" name="' + prefix + '[' + pointNumber + '][y]" value=""/>';
+}
+
+/**
+ * Initialize the visualization in the GIS data editor.
+ */
+function initGISEditorVisualization() {
+ // Loads either SVG or OSM visualization based on the choice
+ selectVisualization();
+ // Adds necessary styles to the div that coontains the openStreetMap
+ styleOSM();
+ // Loads the SVG element and make a reference to it
+ loadSVG();
+ // Adds controllers for zooming and panning
+ addZoomPanControllers();
+ zoomAndPan();
+}
+
+/**
+ * Loads JavaScript files and the GIS editor.
+ *
+ * @param value current value of the geometry field
+ * @param field field name
+ * @param type geometry type
+ * @param input_name name of the input field
+ * @param token token
+ */
+function loadJSAndGISEditor(value, field, type, input_name, token) {
+ var head = document.getElementsByTagName('head')[0];
+ var script;
+
+ // Loads a set of small JS file needed for the GIS editor
+ var smallScripts = [ 'js/jquery/jquery.svg.js',
+ 'js/jquery/jquery.mousewheel.js',
+ 'js/jquery/jquery.event.drag-2.2.js',
+ 'js/tbl_gis_visualization.js' ];
+
+ for (var i = 0; i < smallScripts.length; i++) {
+ script = document.createElement('script');
+ script.type = 'text/javascript';
+ script.src = smallScripts[i];
+ head.appendChild(script);
+ }
+
+ // OpenLayers.js is BIG and takes time. So asynchronous loading would not work.
+ // Load the JS and do a callback to load the content for the GIS Editor.
+ script = document.createElement('script');
+ script.type = 'text/javascript';
+
+ script.onreadystatechange = function () {
+ if (this.readyState == 'complete') {
+ loadGISEditor(value, field, type, input_name, token);
+ }
+ };
+ script.onload = function () {
+ loadGISEditor(value, field, type, input_name, token);
+ };
+
+ script.src = 'js/openlayers/OpenLayers.js';
+ head.appendChild(script);
+
+ gisEditorLoaded = true;
+}
+
+/**
+ * Loads the GIS editor via AJAX
+ *
+ * @param value current value of the geometry field
+ * @param field field name
+ * @param type geometry type
+ * @param input_name name of the input field
+ * @param token token
+ */
+function loadGISEditor(value, field, type, input_name, token) {
+
+ var $gis_editor = $("#gis_editor");
+ $.post('gis_data_editor.php', {
+ 'field' : field,
+ 'value' : value,
+ 'type' : type,
+ 'input_name' : input_name,
+ 'get_gis_editor' : true,
+ 'token' : token,
+ 'ajax_request': true
+ }, function (data) {
+ if (data.success === true) {
+ $gis_editor.html(data.gis_editor);
+ initGISEditorVisualization();
+ prepareJSVersion();
+ } else {
+ PMA_ajaxShowMessage(data.error, false);
+ }
+ }, 'json');
+}
+
+/**
+ * Opens up the dialog for the GIS data editor.
+ */
+function openGISEditor() {
+
+ // Center the popup
+ var windowWidth = document.documentElement.clientWidth;
+ var windowHeight = document.documentElement.clientHeight;
+ var popupWidth = windowWidth * 0.9;
+ var popupHeight = windowHeight * 0.9;
+ var popupOffsetTop = windowHeight / 2 - popupHeight / 2;
+ var popupOffsetLeft = windowWidth / 2 - popupWidth / 2;
+
+ var $gis_editor = $("#gis_editor");
+ var $backgrouond = $("#popup_background");
+
+ $gis_editor.css({"top": popupOffsetTop, "left": popupOffsetLeft, "width": popupWidth, "height": popupHeight});
+ $backgrouond.css({"opacity" : "0.7"});
+
+ $gis_editor.append(
+ '<div id="gis_data_editor">' +
+ '<img class="ajaxIcon" id="loadingMonitorIcon" src="' +
+ pmaThemeImage + 'ajax_clock_small.gif" alt=""/>' +
+ '</div>'
+ );
+
+ // Make it appear
+ $backgrouond.fadeIn("fast");
+ $gis_editor.fadeIn("fast");
+}
+
+/**
+ * Prepare and insert the GIS data in Well Known Text format
+ * to the input field.
+ */
+function insertDataAndClose() {
+ var $form = $('form#gis_data_editor_form');
+ var input_name = $form.find("input[name='input_name']").val();
+
+ $.post('gis_data_editor.php', $form.serialize() + "&generate=true&ajax_request=true", function (data) {
+ if (data.success === true) {
+ $("input[name='" + input_name + "']").val(data.result);
+ } else {
+ PMA_ajaxShowMessage(data.error, false);
+ }
+ }, 'json');
+ closeGISEditor();
+}
+
+/**
+ * Unbind all event handlers before tearing down a page
+ */
+AJAX.registerTeardown('gis_data_editor.js', function () {
+ $("#gis_editor input[name='gis_data[save]']").die('click');
+ $('#gis_editor').die('submit');
+ $('#gis_editor').find("input[type='text']").die('change');
+ $("#gis_editor select.gis_type").die('change');
+ $('#gis_editor a.close_gis_editor, #gis_editor a.cancel_gis_editor').die('click');
+ $('#gis_editor a.addJs.addPoint').die('click');
+ $('#gis_editor a.addLine.addJs').die('click');
+ $('#gis_editor a.addJs.addPolygon').die('click');
+ $('#gis_editor a.addJs.addGeom').die('click');
+});
+
+AJAX.registerOnload('gis_data_editor.js', function () {
+
+ // Remove the class that is added due to the URL being too long.
+ $('span.open_gis_editor a').removeClass('formLinkSubmit');
+
+ /**
+ * Prepares and insert the GIS data to the input field on clicking 'copy'.
+ */
+ $("#gis_editor input[name='gis_data[save]']").live('click', function (event) {
+ event.preventDefault();
+ insertDataAndClose();
+ });
+
+ /**
+ * Prepares and insert the GIS data to the input field on pressing 'enter'.
+ */
+ $('#gis_editor').live('submit', function (event) {
+ event.preventDefault();
+ insertDataAndClose();
+ });
+
+ /**
+ * Trigger asynchronous calls on data change and update the output.
+ */
+ $('#gis_editor').find("input[type='text']").live('change', function () {
+ var $form = $('form#gis_data_editor_form');
+ $.post('gis_data_editor.php', $form.serialize() + "&generate=true&ajax_request=true", function (data) {
+ if (data.success === true) {
+ $('#gis_data_textarea').val(data.result);
+ $('#placeholder').empty().removeClass('hasSVG').html(data.visualization);
+ $('#openlayersmap').empty();
+ eval(data.openLayers);
+ initGISEditorVisualization();
+ } else {
+ PMA_ajaxShowMessage(data.error, false);
+ }
+ }, 'json');
+ });
+
+ /**
+ * Update the form on change of the GIS type.
+ */
+ $("#gis_editor select.gis_type").live('change', function (event) {
+ var $gis_editor = $("#gis_editor");
+ var $form = $('form#gis_data_editor_form');
+
+ $.post('gis_data_editor.php', $form.serialize() + "&get_gis_editor=true&ajax_request=true", function (data) {
+ if (data.success === true) {
+ $gis_editor.html(data.gis_editor);
+ initGISEditorVisualization();
+ prepareJSVersion();
+ } else {
+ PMA_ajaxShowMessage(data.error, false);
+ }
+ }, 'json');
+ });
+
+ /**
+ * Handles closing of the GIS data editor.
+ */
+ $('#gis_editor a.close_gis_editor, #gis_editor a.cancel_gis_editor').live('click', function () {
+ closeGISEditor();
+ });
+
+ /**
+ * Handles adding data points
+ */
+ $('#gis_editor a.addJs.addPoint').live('click', function () {
+ var $a = $(this);
+ var name = $a.attr('name');
+ // Eg. name = gis_data[0][MULTIPOINT][add_point] => prefix = gis_data[0][MULTIPOINT]
+ var prefix = name.substr(0, name.length - 11);
+ // Find the number of points
+ var $noOfPointsInput = $("input[name='" + prefix + "[no_of_points]" + "']");
+ var noOfPoints = parseInt($noOfPointsInput.val(), 10);
+ // Add the new data point
+ var html = addDataPoint(noOfPoints, prefix);
+ $a.before(html);
+ $noOfPointsInput.val(noOfPoints + 1);
+ });
+
+ /**
+ * Handles adding linestrings and inner rings
+ */
+ $('#gis_editor a.addLine.addJs').live('click', function () {
+ var $a = $(this);
+ var name = $a.attr('name');
+
+ // Eg. name = gis_data[0][MULTILINESTRING][add_line] => prefix = gis_data[0][MULTILINESTRING]
+ var prefix = name.substr(0, name.length - 10);
+ var type = prefix.slice(prefix.lastIndexOf('[') + 1, prefix.lastIndexOf(']'));
+
+ // Find the number of lines
+ var $noOfLinesInput = $("input[name='" + prefix + "[no_of_lines]" + "']");
+ var noOfLines = parseInt($noOfLinesInput.val(), 10);
+
+ // Add the new linesting of inner ring based on the type
+ var html = '<br/>';
+ var noOfPoints;
+ if (type == 'MULTILINESTRING') {
+ html += PMA_messages.strLineString + ' ' + (noOfLines + 1) + ':';
+ noOfPoints = 2;
+ } else {
+ html += PMA_messages.strInnerRing + ' ' + noOfLines + ':';
+ noOfPoints = 4;
+ }
+ html += '<input type="hidden" name="' + prefix + '[' + noOfLines + '][no_of_points]" value="' + noOfPoints + '"/>';
+ for (var i = 0; i < noOfPoints; i++) {
+ html += addDataPoint(i, (prefix + '[' + noOfLines + ']'));
+ }
+ html += '<a class="addPoint addJs" name="' + prefix + '[' + noOfLines + '][add_point]" href="#">+ ' +
+ PMA_messages.strAddPoint + '</a><br/>';
+
+ $a.before(html);
+ $noOfLinesInput.val(noOfLines + 1);
+ });
+
+ /**
+ * Handles adding polygons
+ */
+ $('#gis_editor a.addJs.addPolygon').live('click', function () {
+ var $a = $(this);
+ var name = $a.attr('name');
+ // Eg. name = gis_data[0][MULTIPOLYGON][add_polygon] => prefix = gis_data[0][MULTIPOLYGON]
+ var prefix = name.substr(0, name.length - 13);
+ // Find the number of polygons
+ var $noOfPolygonsInput = $("input[name='" + prefix + "[no_of_polygons]" + "']");
+ var noOfPolygons = parseInt($noOfPolygonsInput.val(), 10);
+
+ // Add the new polygon
+ var html = PMA_messages.strPolygon + ' ' + (noOfPolygons + 1) + ':<br/>';
+ html += '<input type="hidden" name="' + prefix + '[' + noOfPolygons + '][no_of_lines]" value="1"/>' +
+ '<br/>' + PMA_messages.strOuterRing + ':' +
+ '<input type="hidden" name="' + prefix + '[' + noOfPolygons + '][0][no_of_points]" value="4"/>';
+ for (var i = 0; i < 4; i++) {
+ html += addDataPoint(i, (prefix + '[' + noOfPolygons + '][0]'));
+ }
+ html += '<a class="addPoint addJs" name="' + prefix + '[' + noOfPolygons + '][0][add_point]" href="#">+ ' +
+ PMA_messages.strAddPoint + '</a><br/>' +
+ '<a class="addLine addJs" name="' + prefix + '[' + noOfPolygons + '][add_line]" href="#">+ ' +
+ PMA_messages.strAddInnerRing + '</a><br/><br/>';
+
+ $a.before(html);
+ $noOfPolygonsInput.val(noOfPolygons + 1);
+ });
+
+ /**
+ * Handles adding geoms
+ */
+ $('#gis_editor a.addJs.addGeom').live('click', function () {
+ var $a = $(this);
+ var prefix = 'gis_data[GEOMETRYCOLLECTION]';
+ // Find the number of geoms
+ var $noOfGeomsInput = $("input[name='" + prefix + "[geom_count]" + "']");
+ var noOfGeoms = parseInt($noOfGeomsInput.val(), 10);
+
+ var html1 = PMA_messages.strGeometry + ' ' + (noOfGeoms + 1) + ':<br/>';
+ var $geomType = $("select[name='gis_data[" + (noOfGeoms - 1) + "][gis_type]']").clone();
+ $geomType.attr('name', 'gis_data[' + noOfGeoms + '][gis_type]').val('POINT');
+ var html2 = '<br/>' + PMA_messages.strPoint + ' :' +
+ '<label for="x"> ' + PMA_messages.strX + ' </label>' +
+ '<input type="text" name="gis_data[' + noOfGeoms + '][POINT][x]" value=""/>' +
+ '<label for="y"> ' + PMA_messages.strY + ' </label>' +
+ '<input type="text" name="gis_data[' + noOfGeoms + '][POINT][y]" value=""/>' +
+ '<br/><br/>';
+
+ $a.before(html1);
+ $geomType.insertBefore($a);
+ $a.before(html2);
+ $noOfGeomsInput.val(noOfGeoms + 1);
+ });
+});
diff --git a/js/import.js b/js/import.js
new file mode 100644
index 0000000000..e97af14c49
--- /dev/null
+++ b/js/import.js
@@ -0,0 +1,117 @@
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Functions used in the import tab
+ *
+ */
+
+
+/**
+ * Toggles the hiding and showing of each plugin's options
+ * according to the currently selected plugin from the dropdown list
+ */
+function changePluginOpts()
+{
+ $("#format_specific_opts div.format_specific_options").each(function () {
+ $(this).hide();
+ });
+ var selected_plugin_name = $("#plugins option:selected").val();
+ $("#" + selected_plugin_name + "_options").fadeIn('slow');
+ if (selected_plugin_name == "csv") {
+ $("#import_notification").text(PMA_messages.strImportCSV);
+ } else {
+ $("#import_notification").text("");
+ }
+}
+
+/**
+ * Toggles the hiding and showing of each plugin's options and sets the selected value
+ * in the plugin dropdown list according to the format of the selected file
+ */
+function matchFile(fname)
+{
+ var fname_array = fname.toLowerCase().split(".");
+ var len = fname_array.length;
+ if (len !== 0) {
+ var extension = fname_array[len - 1];
+ if (extension == "gz" || extension == "bz2" || extension == "zip") {
+ len--;
+ }
+ // Only toggle if the format of the file can be imported
+ if ($("select[name='format'] option").filterByValue(fname_array[len - 1]).length == 1) {
+ $("select[name='format'] option").filterByValue(fname_array[len - 1]).prop('selected', true);
+ changePluginOpts();
+ }
+ }
+}
+
+/**
+ * Unbind all event handlers before tearing down a page
+ */
+AJAX.registerTeardown('import.js', function () {
+ $("#plugins").unbind('change');
+ $("#input_import_file").unbind('change');
+ $("#select_local_import_file").unbind('change');
+ $("#input_import_file").unbind('change').unbind('focus');
+ $("#select_local_import_file").unbind('focus');
+ $("#text_csv_enclosed").add("#text_csv_escaped").unbind('keyup');
+});
+
+AJAX.registerOnload('import.js', function () {
+ // Initially display the options for the selected plugin
+ changePluginOpts();
+
+ // Whenever the selected plugin changes, change the options displayed
+ $("#plugins").change(function () {
+ changePluginOpts();
+ });
+
+ $("#input_import_file").change(function () {
+ matchFile($(this).val());
+ });
+
+ $("#select_local_import_file").change(function () {
+ matchFile($(this).val());
+ });
+
+ /*
+ * When the "Browse the server" form is clicked or the "Select from the web server upload directory"
+ * form is clicked, the radio button beside it becomes selected and the other form becomes disabled.
+ */
+ $("#input_import_file").bind("focus change", function () {
+ $("#radio_import_file").prop('checked', true);
+ $("#radio_local_import_file").prop('checked', false);
+ });
+ $("#select_local_import_file").focus(function () {
+ $("#radio_local_import_file").prop('checked', true);
+ $("#radio_import_file").prop('checked', false);
+ });
+
+ /**
+ * Set up the interface for Javascript-enabled browsers since the default is for
+ * Javascript-disabled browsers
+ */
+ $("#scroll_to_options_msg").hide();
+ $("#format_specific_opts div.format_specific_options")
+ .css({
+ "border": 0,
+ "margin": 0,
+ "padding": 0
+ })
+ .find("h3")
+ .remove();
+ //$("form[name=import] *").unwrap();
+
+ /**
+ * for input element text_csv_enclosed and text_csv_escaped allow just one character to enter.
+ * as mysql allows just one character for these fields,
+ * if first character is escape then allow two including escape character.
+ */
+ $("#text_csv_enclosed").add("#text_csv_escaped").bind('keyup', function() {
+ if($(this).val().length === 2 && $(this).val().charAt(0) !== "\\") {
+ $(this).val($(this).val().substring(0, 1));
+ return false;
+ }
+ return true;
+ });
+
+});
diff --git a/js/indexes.js b/js/indexes.js
new file mode 100644
index 0000000000..b016f19463
--- /dev/null
+++ b/js/indexes.js
@@ -0,0 +1,209 @@
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * @fileoverview function used for index manipulation pages
+ * @name Table Structure
+ *
+ * @requires jQuery
+ * @requires jQueryUI
+ * @required js/functions.js
+ */
+
+/**
+ * Hides/shows the inputs and submits appropriately depending
+ * on whether the index type chosen is 'SPATIAL' or not.
+ */
+function checkIndexType()
+{
+ /**
+ * @var Object Dropdown to select the index type.
+ */
+ $select_index_type = $('#select_index_type');
+ /**
+ * @var Object Table header for the size column.
+ */
+ $size_header = $('#index_columns thead tr th:nth-child(2)');
+ /**
+ * @var Object Inputs to specify the columns for the index.
+ */
+ $column_inputs = $('select[name="index[columns][names][]"]');
+ /**
+ * @var Object Inputs to specify sizes for columns of the index.
+ */
+ $size_inputs = $('input[name="index[columns][sub_parts][]"]');
+ /**
+ * @var Object Footer containg the controllers to add more columns
+ */
+ $add_more = $('#index_frm .tblFooters');
+
+ if ($select_index_type.val() == 'SPATIAL') {
+ // Disable and hide the size column
+ $size_header.hide();
+ $size_inputs.each(function () {
+ $(this)
+ .prop('disabled', true)
+ .parent('td').hide();
+ });
+
+ // Disable and hide the columns of the index other than the first one
+ var initial = true;
+ $column_inputs.each(function () {
+ $column_input = $(this);
+ if (! initial) {
+ $column_input
+ .prop('disabled', true)
+ .parent('td').hide();
+ } else {
+ initial = false;
+ }
+ });
+
+ // Hide controllers to add more columns
+ $add_more.hide();
+ } else {
+ // Enable and show the size column
+ $size_header.show();
+ $size_inputs.each(function () {
+ $(this)
+ .prop('disabled', false)
+ .parent('td').show();
+ });
+
+ // Enable and show the columns of the index
+ $column_inputs.each(function () {
+ $(this)
+ .prop('disabled', false)
+ .parent('td').show();
+ });
+
+ // Show controllers to add more columns
+ $add_more.show();
+ }
+}
+
+/**
+ * Unbind all event handlers before tearing down a page
+ */
+AJAX.registerTeardown('indexes.js', function () {
+ $('#select_index_type').die('change');
+ $('a.drop_primary_key_index_anchor.ajax').die('click');
+ $("#table_index tbody tr td.edit_index.ajax, #indexes .add_index.ajax").die('click');
+ $('#index_frm input[type=submit]').die('click');
+});
+
+/**
+ * @description <p>Ajax scripts for table index page</p>
+ *
+ * Actions ajaxified here:
+ * <ul>
+ * <li>Showing/hiding inputs depending on the index type chosen</li>
+ * <li>create/edit/drop indexes</li>
+ * </ul>
+ */
+AJAX.registerOnload('indexes.js', function () {
+ checkIndexType();
+ checkIndexName("index_frm");
+ $('#select_index_type').live('change', function (event) {
+ event.preventDefault();
+ checkIndexType();
+ checkIndexName("index_frm");
+ });
+
+ /**
+ * Ajax Event handler for 'Drop Index'
+ */
+ $('a.drop_primary_key_index_anchor.ajax').live('click', function (event) {
+ event.preventDefault();
+ var $anchor = $(this);
+ /**
+ * @var $curr_row Object containing reference to the current field's row
+ */
+ var $curr_row = $anchor.parents('tr');
+ /** @var Number of columns in the key */
+ var rows = $anchor.parents('td').attr('rowspan') || 1;
+ /** @var Rows that should be hidden */
+ var $rows_to_hide = $curr_row;
+ for (var i = 1, $last_row = $curr_row.next(); i < rows; i++, $last_row = $last_row.next()) {
+ $rows_to_hide = $rows_to_hide.add($last_row);
+ }
+
+ var question = escapeHtml(
+ $curr_row.children('td')
+ .children('.drop_primary_key_index_msg')
+ .val()
+ );
+
+ $anchor.PMA_confirm(question, $anchor.attr('href'), function (url) {
+ var $msg = PMA_ajaxShowMessage(PMA_messages.strDroppingPrimaryKeyIndex, false);
+ $.get(url, {'is_js_confirmed': 1, 'ajax_request': true}, function (data) {
+ if (data.success === true) {
+ PMA_ajaxRemoveMessage($msg);
+ var $table_ref = $rows_to_hide.closest('table');
+ if ($rows_to_hide.length == $table_ref.find('tbody > tr').length) {
+ // We are about to remove all rows from the table
+ $table_ref.hide('medium', function () {
+ $('div.no_indexes_defined').show('medium');
+ $rows_to_hide.remove();
+ });
+ $table_ref.siblings('div.notice').hide('medium');
+ } else {
+ // We are removing some of the rows only
+ toggleRowColors($rows_to_hide.last().next());
+ $rows_to_hide.hide("medium", function () {
+ $(this).remove();
+ });
+ }
+ if ($('#result_query').length) {
+ $('#result_query').remove();
+ }
+ if (data.sql_query) {
+ $('<div id="result_query"></div>')
+ .html(data.sql_query)
+ .prependTo('#page_content');
+ PMA_highlightSQL($('#page_content'));
+ }
+ PMA_commonActions.refreshMain(false, function () {
+ $("a.ajax[href^=#indexes]").click();
+ });
+ PMA_reloadNavigation();
+ } else {
+ PMA_ajaxShowMessage(PMA_messages.strErrorProcessingRequest + " : " + data.error, false);
+ }
+ }); // end $.get()
+ }); // end $.PMA_confirm()
+ }); //end Drop Primary Key/Index
+
+ /**
+ *Ajax event handler for index edit
+ **/
+ $("#table_index tbody tr td.edit_index.ajax, #indexes .add_index.ajax").live('click', function (event) {
+ event.preventDefault();
+ var url, title;
+ if ($(this).find("a").length === 0) {
+ // Add index
+ var valid = checkFormElementInRange(
+ $(this).closest('form')[0],
+ 'added_fields',
+ 'Column count has to be larger than zero.'
+ );
+ if (! valid) {
+ return;
+ }
+ url = $(this).closest('form').serialize();
+ title = PMA_messages.strAddIndex;
+ } else {
+ // Edit index
+ url = $(this).find("a").attr("href");
+ if (url.substring(0, 16) == "tbl_indexes.php?") {
+ url = url.substring(16, url.length);
+ }
+ title = PMA_messages.strEditIndex;
+ }
+ url += "&ajax_request=true";
+ indexEditorDialog(url, title, function () {
+ // refresh the page using ajax
+ PMA_commonActions.refreshMain(false, function () {
+ $("a.ajax[href^=#indexes]").click();
+ });
+ });
+ });
+});
diff --git a/js/jqplot/excanvas.js b/js/jqplot/excanvas.js
new file mode 100644
index 0000000000..4ca9653fcd
--- /dev/null
+++ b/js/jqplot/excanvas.js
@@ -0,0 +1,1438 @@
+// Memory Leaks patch from http://explorercanvas.googlecode.com/svn/trunk/
+// svn : r73
+// ------------------------------------------------------------------
+// Copyright 2006 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+
+// Known Issues:
+//
+// * Patterns only support repeat.
+// * Radial gradient are not implemented. The VML version of these look very
+// different from the canvas one.
+// * Clipping paths are not implemented.
+// * Coordsize. The width and height attribute have higher priority than the
+// width and height style values which isn't correct.
+// * Painting mode isn't implemented.
+// * Canvas width/height should is using content-box by default. IE in
+// Quirks mode will draw the canvas using border-box. Either change your
+// doctype to HTML5
+// (http://www.whatwg.org/specs/web-apps/current-work/#the-doctype)
+// or use Box Sizing Behavior from WebFX
+// (http://webfx.eae.net/dhtml/boxsizing/boxsizing.html)
+// * Non uniform scaling does not correctly scale strokes.
+// * Optimize. There is always room for speed improvements.
+
+// Only add this code if we do not already have a canvas implementation
+if (!document.createElement('canvas').getContext) {
+
+(function() {
+
+ // alias some functions to make (compiled) code shorter
+ var m = Math;
+ var mr = m.round;
+ var ms = m.sin;
+ var mc = m.cos;
+ var abs = m.abs;
+ var sqrt = m.sqrt;
+
+ // this is used for sub pixel precision
+ var Z = 10;
+ var Z2 = Z / 2;
+
+ var IE_VERSION = +navigator.userAgent.match(/MSIE ([\d.]+)?/)[1];
+
+ /**
+ * This funtion is assigned to the <canvas> elements as element.getContext().
+ * @this {HTMLElement}
+ * @return {CanvasRenderingContext2D_}
+ */
+ function getContext() {
+ return this.context_ ||
+ (this.context_ = new CanvasRenderingContext2D_(this));
+ }
+
+ var slice = Array.prototype.slice;
+
+ /**
+ * Binds a function to an object. The returned function will always use the
+ * passed in {@code obj} as {@code this}.
+ *
+ * Example:
+ *
+ * g = bind(f, obj, a, b)
+ * g(c, d) // will do f.call(obj, a, b, c, d)
+ *
+ * @param {Function} f The function to bind the object to
+ * @param {Object} obj The object that should act as this when the function
+ * is called
+ * @param {*} var_args Rest arguments that will be used as the initial
+ * arguments when the function is called
+ * @return {Function} A new function that has bound this
+ */
+ function bind(f, obj, var_args) {
+ var a = slice.call(arguments, 2);
+ return function() {
+ return f.apply(obj, a.concat(slice.call(arguments)));
+ };
+ }
+
+ function encodeHtmlAttribute(s) {
+ return String(s).replace(/&/g, '&amp;').replace(/"/g, '&quot;');
+ }
+
+ function addNamespace(doc, prefix, urn) {
+ if (!doc.namespaces[prefix]) {
+ doc.namespaces.add(prefix, urn, '#default#VML');
+ }
+ }
+
+ function addNamespacesAndStylesheet(doc) {
+ addNamespace(doc, 'g_vml_', 'urn:schemas-microsoft-com:vml');
+ addNamespace(doc, 'g_o_', 'urn:schemas-microsoft-com:office:office');
+
+ // Setup default CSS. Only add one style sheet per document
+ if (!doc.styleSheets['ex_canvas_']) {
+ var ss = doc.createStyleSheet();
+ ss.owningElement.id = 'ex_canvas_';
+ ss.cssText = 'canvas{display:inline-block;overflow:hidden;' +
+ // default size is 300x150 in Gecko and Opera
+ 'text-align:left;width:300px;height:150px}';
+ }
+ }
+
+ // Add namespaces and stylesheet at startup.
+ addNamespacesAndStylesheet(document);
+
+ var G_vmlCanvasManager_ = {
+ init: function(opt_doc) {
+ var doc = opt_doc || document;
+ // Create a dummy element so that IE will allow canvas elements to be
+ // recognized.
+ doc.createElement('canvas');
+ doc.attachEvent('onreadystatechange', bind(this.init_, this, doc));
+ },
+
+ init_: function(doc) {
+ // find all canvas elements
+ var els = doc.getElementsByTagName('canvas');
+ for (var i = 0; i < els.length; i++) {
+ this.initElement(els[i]);
+ }
+ },
+
+ /**
+ * Public initializes a canvas element so that it can be used as canvas
+ * element from now on. This is called automatically before the page is
+ * loaded but if you are creating elements using createElement you need to
+ * make sure this is called on the element.
+ * @param {HTMLElement} el The canvas element to initialize.
+ * @return {HTMLElement} the element that was created.
+ */
+ initElement: function(el) {
+ if (!el.getContext) {
+ el.getContext = getContext;
+
+ // Add namespaces and stylesheet to document of the element.
+ addNamespacesAndStylesheet(el.ownerDocument);
+
+ // Remove fallback content. There is no way to hide text nodes so we
+ // just remove all childNodes. We could hide all elements and remove
+ // text nodes but who really cares about the fallback content.
+ el.innerHTML = '';
+
+ // do not use inline function because that will leak memory
+ el.attachEvent('onpropertychange', onPropertyChange);
+ el.attachEvent('onresize', onResize);
+
+ var attrs = el.attributes;
+ if (attrs.width && attrs.width.specified) {
+ // TODO: use runtimeStyle and coordsize
+ // el.getContext().setWidth_(attrs.width.nodeValue);
+ el.style.width = attrs.width.nodeValue + 'px';
+ } else {
+ el.width = el.clientWidth;
+ }
+ if (attrs.height && attrs.height.specified) {
+ // TODO: use runtimeStyle and coordsize
+ // el.getContext().setHeight_(attrs.height.nodeValue);
+ el.style.height = attrs.height.nodeValue + 'px';
+ } else {
+ el.height = el.clientHeight;
+ }
+ //el.getContext().setCoordsize_()
+ }
+ return el;
+ },
+
+ // Memory Leaks patch : see http://code.google.com/p/explorercanvas/issues/detail?id=82
+ uninitElement: function(el){
+ if (el.getContext) {
+ var ctx = el.getContext();
+ delete ctx.element_;
+ delete ctx.canvas;
+ el.innerHTML = "";
+ //el.outerHTML = "";
+ el.context_ = null;
+ el.getContext = null;
+ el.detachEvent("onpropertychange", onPropertyChange);
+ el.detachEvent("onresize", onResize);
+ }
+ }
+ };
+
+ function onPropertyChange(e) {
+ var el = e.srcElement;
+
+ switch (e.propertyName) {
+ case 'width':
+ el.getContext().clearRect();
+ el.style.width = el.attributes.width.nodeValue + 'px';
+ // In IE8 this does not trigger onresize.
+ el.firstChild.style.width = el.clientWidth + 'px';
+ break;
+ case 'height':
+ el.getContext().clearRect();
+ el.style.height = el.attributes.height.nodeValue + 'px';
+ el.firstChild.style.height = el.clientHeight + 'px';
+ break;
+ }
+ }
+
+ function onResize(e) {
+ var el = e.srcElement;
+ if (el.firstChild) {
+ el.firstChild.style.width = el.clientWidth + 'px';
+ el.firstChild.style.height = el.clientHeight + 'px';
+ }
+ }
+
+ G_vmlCanvasManager_.init();
+
+ // precompute "00" to "FF"
+ var decToHex = [];
+ for (var i = 0; i < 16; i++) {
+ for (var j = 0; j < 16; j++) {
+ decToHex[i * 16 + j] = i.toString(16) + j.toString(16);
+ }
+ }
+
+ function createMatrixIdentity() {
+ return [
+ [1, 0, 0],
+ [0, 1, 0],
+ [0, 0, 1]
+ ];
+ }
+
+ function matrixMultiply(m1, m2) {
+ var result = createMatrixIdentity();
+
+ for (var x = 0; x < 3; x++) {
+ for (var y = 0; y < 3; y++) {
+ var sum = 0;
+
+ for (var z = 0; z < 3; z++) {
+ sum += m1[x][z] * m2[z][y];
+ }
+
+ result[x][y] = sum;
+ }
+ }
+ return result;
+ }
+
+ function copyState(o1, o2) {
+ o2.fillStyle = o1.fillStyle;
+ o2.lineCap = o1.lineCap;
+ o2.lineJoin = o1.lineJoin;
+ o2.lineWidth = o1.lineWidth;
+ o2.miterLimit = o1.miterLimit;
+ o2.shadowBlur = o1.shadowBlur;
+ o2.shadowColor = o1.shadowColor;
+ o2.shadowOffsetX = o1.shadowOffsetX;
+ o2.shadowOffsetY = o1.shadowOffsetY;
+ o2.strokeStyle = o1.strokeStyle;
+ o2.globalAlpha = o1.globalAlpha;
+ o2.font = o1.font;
+ o2.textAlign = o1.textAlign;
+ o2.textBaseline = o1.textBaseline;
+ o2.arcScaleX_ = o1.arcScaleX_;
+ o2.arcScaleY_ = o1.arcScaleY_;
+ o2.lineScale_ = o1.lineScale_;
+ }
+
+ var colorData = {
+ aliceblue: '#F0F8FF',
+ antiquewhite: '#FAEBD7',
+ aquamarine: '#7FFFD4',
+ azure: '#F0FFFF',
+ beige: '#F5F5DC',
+ bisque: '#FFE4C4',
+ black: '#000000',
+ blanchedalmond: '#FFEBCD',
+ 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',
+ gainsboro: '#DCDCDC',
+ ghostwhite: '#F8F8FF',
+ gold: '#FFD700',
+ goldenrod: '#DAA520',
+ grey: '#808080',
+ 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',
+ lightgreen: '#90EE90',
+ lightgrey: '#D3D3D3',
+ lightpink: '#FFB6C1',
+ lightsalmon: '#FFA07A',
+ lightseagreen: '#20B2AA',
+ lightskyblue: '#87CEFA',
+ lightslategray: '#778899',
+ lightslategrey: '#778899',
+ lightsteelblue: '#B0C4DE',
+ lightyellow: '#FFFFE0',
+ limegreen: '#32CD32',
+ linen: '#FAF0E6',
+ magenta: '#FF00FF',
+ 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',
+ oldlace: '#FDF5E6',
+ 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',
+ rosybrown: '#BC8F8F',
+ royalblue: '#4169E1',
+ saddlebrown: '#8B4513',
+ salmon: '#FA8072',
+ sandybrown: '#F4A460',
+ seagreen: '#2E8B57',
+ seashell: '#FFF5EE',
+ sienna: '#A0522D',
+ skyblue: '#87CEEB',
+ slateblue: '#6A5ACD',
+ slategray: '#708090',
+ slategrey: '#708090',
+ snow: '#FFFAFA',
+ springgreen: '#00FF7F',
+ steelblue: '#4682B4',
+ tan: '#D2B48C',
+ thistle: '#D8BFD8',
+ tomato: '#FF6347',
+ turquoise: '#40E0D0',
+ violet: '#EE82EE',
+ wheat: '#F5DEB3',
+ whitesmoke: '#F5F5F5',
+ yellowgreen: '#9ACD32'
+ };
+
+
+ function getRgbHslContent(styleString) {
+ var start = styleString.indexOf('(', 3);
+ var end = styleString.indexOf(')', start + 1);
+ var parts = styleString.substring(start + 1, end).split(',');
+ // add alpha if needed
+ if (parts.length != 4 || styleString.charAt(3) != 'a') {
+ parts[3] = 1;
+ }
+ return parts;
+ }
+
+ function percent(s) {
+ return parseFloat(s) / 100;
+ }
+
+ function clamp(v, min, max) {
+ return Math.min(max, Math.max(min, v));
+ }
+
+ function hslToRgb(parts){
+ var r, g, b, h, s, l;
+ h = parseFloat(parts[0]) / 360 % 360;
+ if (h < 0)
+ h++;
+ s = clamp(percent(parts[1]), 0, 1);
+ l = clamp(percent(parts[2]), 0, 1);
+ if (s == 0) {
+ r = g = b = l; // achromatic
+ } else {
+ var q = l < 0.5 ? l * (1 + s) : l + s - l * s;
+ var p = 2 * l - q;
+ r = hueToRgb(p, q, h + 1 / 3);
+ g = hueToRgb(p, q, h);
+ b = hueToRgb(p, q, h - 1 / 3);
+ }
+
+ return '#' + decToHex[Math.floor(r * 255)] +
+ decToHex[Math.floor(g * 255)] +
+ decToHex[Math.floor(b * 255)];
+ }
+
+ function hueToRgb(m1, m2, h) {
+ if (h < 0)
+ h++;
+ if (h > 1)
+ h--;
+
+ if (6 * h < 1)
+ return m1 + (m2 - m1) * 6 * h;
+ else if (2 * h < 1)
+ return m2;
+ else if (3 * h < 2)
+ return m1 + (m2 - m1) * (2 / 3 - h) * 6;
+ else
+ return m1;
+ }
+
+ var processStyleCache = {};
+
+ function processStyle(styleString) {
+ if (styleString in processStyleCache) {
+ return processStyleCache[styleString];
+ }
+
+ var str, alpha = 1;
+
+ styleString = String(styleString);
+ if (styleString.charAt(0) == '#') {
+ str = styleString;
+ } else if (/^rgb/.test(styleString)) {
+ var parts = getRgbHslContent(styleString);
+ var str = '#', n;
+ for (var i = 0; i < 3; i++) {
+ if (parts[i].indexOf('%') != -1) {
+ n = Math.floor(percent(parts[i]) * 255);
+ } else {
+ n = +parts[i];
+ }
+ str += decToHex[clamp(n, 0, 255)];
+ }
+ alpha = +parts[3];
+ } else if (/^hsl/.test(styleString)) {
+ var parts = getRgbHslContent(styleString);
+ str = hslToRgb(parts);
+ alpha = parts[3];
+ } else {
+ str = colorData[styleString] || styleString;
+ }
+ return processStyleCache[styleString] = {color: str, alpha: alpha};
+ }
+
+ var DEFAULT_STYLE = {
+ style: 'normal',
+ variant: 'normal',
+ weight: 'normal',
+ size: 10,
+ family: 'sans-serif'
+ };
+
+ // Internal text style cache
+ var fontStyleCache = {};
+
+ function processFontStyle(styleString) {
+ if (fontStyleCache[styleString]) {
+ return fontStyleCache[styleString];
+ }
+
+ var el = document.createElement('div');
+ var style = el.style;
+ try {
+ style.font = styleString;
+ } catch (ex) {
+ // Ignore failures to set to invalid font.
+ }
+
+ return fontStyleCache[styleString] = {
+ style: style.fontStyle || DEFAULT_STYLE.style,
+ variant: style.fontVariant || DEFAULT_STYLE.variant,
+ weight: style.fontWeight || DEFAULT_STYLE.weight,
+ size: style.fontSize || DEFAULT_STYLE.size,
+ family: style.fontFamily || DEFAULT_STYLE.family
+ };
+ }
+
+ function getComputedStyle(style, element) {
+ var computedStyle = {};
+
+ for (var p in style) {
+ computedStyle[p] = style[p];
+ }
+
+ // Compute the size
+ var canvasFontSize = parseFloat(element.currentStyle.fontSize),
+ fontSize = parseFloat(style.size);
+
+ if (typeof style.size == 'number') {
+ computedStyle.size = style.size;
+ } else if (style.size.indexOf('px') != -1) {
+ computedStyle.size = fontSize;
+ } else if (style.size.indexOf('em') != -1) {
+ computedStyle.size = canvasFontSize * fontSize;
+ } else if(style.size.indexOf('%') != -1) {
+ computedStyle.size = (canvasFontSize / 100) * fontSize;
+ } else if (style.size.indexOf('pt') != -1) {
+ computedStyle.size = fontSize / .75;
+ } else {
+ computedStyle.size = canvasFontSize;
+ }
+
+ // Different scaling between normal text and VML text. This was found using
+ // trial and error to get the same size as non VML text.
+ computedStyle.size *= 0.981;
+
+ // Fix for VML handling of bare font family names. Add a '' around font family names.
+ computedStyle.family = "'" + computedStyle.family.replace(/(\'|\")/g,'').replace(/\s*,\s*/g, "', '") + "'";
+
+ return computedStyle;
+ }
+
+ function buildStyle(style) {
+ return style.style + ' ' + style.variant + ' ' + style.weight + ' ' +
+ style.size + 'px ' + style.family;
+ }
+
+ var lineCapMap = {
+ 'butt': 'flat',
+ 'round': 'round'
+ };
+
+ function processLineCap(lineCap) {
+ return lineCapMap[lineCap] || 'square';
+ }
+
+ /**
+ * This class implements CanvasRenderingContext2D interface as described by
+ * the WHATWG.
+ * @param {HTMLElement} canvasElement The element that the 2D context should
+ * be associated with
+ */
+ function CanvasRenderingContext2D_(canvasElement) {
+ this.m_ = createMatrixIdentity();
+
+ this.mStack_ = [];
+ this.aStack_ = [];
+ this.currentPath_ = [];
+
+ // Canvas context properties
+ this.strokeStyle = '#000';
+ this.fillStyle = '#000';
+
+ this.lineWidth = 1;
+ this.lineJoin = 'miter';
+ this.lineCap = 'butt';
+ this.miterLimit = Z * 1;
+ this.globalAlpha = 1;
+ this.font = '10px sans-serif';
+ this.textAlign = 'left';
+ this.textBaseline = 'alphabetic';
+ this.canvas = canvasElement;
+
+ var cssText = 'width:' + canvasElement.clientWidth + 'px;height:' +
+ canvasElement.clientHeight + 'px;overflow:hidden;position:absolute';
+ var el = canvasElement.ownerDocument.createElement('div');
+ el.style.cssText = cssText;
+ canvasElement.appendChild(el);
+
+ var overlayEl = el.cloneNode(false);
+ // Use a non transparent background.
+ overlayEl.style.backgroundColor = 'red';
+ overlayEl.style.filter = 'alpha(opacity=0)';
+ canvasElement.appendChild(overlayEl);
+
+ this.element_ = el;
+ this.arcScaleX_ = 1;
+ this.arcScaleY_ = 1;
+ this.lineScale_ = 1;
+ }
+
+ var contextPrototype = CanvasRenderingContext2D_.prototype;
+ contextPrototype.clearRect = function() {
+ if (this.textMeasureEl_) {
+ this.textMeasureEl_.removeNode(true);
+ this.textMeasureEl_ = null;
+ }
+ this.element_.innerHTML = '';
+ };
+
+ contextPrototype.beginPath = function() {
+ // TODO: Branch current matrix so that save/restore has no effect
+ // as per safari docs.
+ this.currentPath_ = [];
+ };
+
+ contextPrototype.moveTo = function(aX, aY) {
+ var p = getCoords(this, aX, aY);
+ this.currentPath_.push({type: 'moveTo', x: p.x, y: p.y});
+ this.currentX_ = p.x;
+ this.currentY_ = p.y;
+ };
+
+ contextPrototype.lineTo = function(aX, aY) {
+ var p = getCoords(this, aX, aY);
+ this.currentPath_.push({type: 'lineTo', x: p.x, y: p.y});
+
+ this.currentX_ = p.x;
+ this.currentY_ = p.y;
+ };
+
+ contextPrototype.bezierCurveTo = function(aCP1x, aCP1y,
+ aCP2x, aCP2y,
+ aX, aY) {
+ var p = getCoords(this, aX, aY);
+ var cp1 = getCoords(this, aCP1x, aCP1y);
+ var cp2 = getCoords(this, aCP2x, aCP2y);
+ bezierCurveTo(this, cp1, cp2, p);
+ };
+
+ // Helper function that takes the already fixed cordinates.
+ function bezierCurveTo(self, cp1, cp2, p) {
+ self.currentPath_.push({
+ type: 'bezierCurveTo',
+ cp1x: cp1.x,
+ cp1y: cp1.y,
+ cp2x: cp2.x,
+ cp2y: cp2.y,
+ x: p.x,
+ y: p.y
+ });
+ self.currentX_ = p.x;
+ self.currentY_ = p.y;
+ }
+
+ contextPrototype.quadraticCurveTo = function(aCPx, aCPy, aX, aY) {
+ // the following is lifted almost directly from
+ // http://developer.mozilla.org/en/docs/Canvas_tutorial:Drawing_shapes
+
+ var cp = getCoords(this, aCPx, aCPy);
+ var p = getCoords(this, aX, aY);
+
+ var cp1 = {
+ x: this.currentX_ + 2.0 / 3.0 * (cp.x - this.currentX_),
+ y: this.currentY_ + 2.0 / 3.0 * (cp.y - this.currentY_)
+ };
+ var cp2 = {
+ x: cp1.x + (p.x - this.currentX_) / 3.0,
+ y: cp1.y + (p.y - this.currentY_) / 3.0
+ };
+
+ bezierCurveTo(this, cp1, cp2, p);
+ };
+
+ contextPrototype.arc = function(aX, aY, aRadius,
+ aStartAngle, aEndAngle, aClockwise) {
+ aRadius *= Z;
+ var arcType = aClockwise ? 'at' : 'wa';
+
+ var xStart = aX + mc(aStartAngle) * aRadius - Z2;
+ var yStart = aY + ms(aStartAngle) * aRadius - Z2;
+
+ var xEnd = aX + mc(aEndAngle) * aRadius - Z2;
+ var yEnd = aY + ms(aEndAngle) * aRadius - Z2;
+
+ // IE won't render arches drawn counter clockwise if xStart == xEnd.
+ if (xStart == xEnd && !aClockwise) {
+ xStart += 0.125; // Offset xStart by 1/80 of a pixel. Use something
+ // that can be represented in binary
+ }
+
+ var p = getCoords(this, aX, aY);
+ var pStart = getCoords(this, xStart, yStart);
+ var pEnd = getCoords(this, xEnd, yEnd);
+
+ this.currentPath_.push({type: arcType,
+ x: p.x,
+ y: p.y,
+ radius: aRadius,
+ xStart: pStart.x,
+ yStart: pStart.y,
+ xEnd: pEnd.x,
+ yEnd: pEnd.y});
+
+ };
+
+ contextPrototype.rect = function(aX, aY, aWidth, aHeight) {
+ this.moveTo(aX, aY);
+ this.lineTo(aX + aWidth, aY);
+ this.lineTo(aX + aWidth, aY + aHeight);
+ this.lineTo(aX, aY + aHeight);
+ this.closePath();
+ };
+
+ contextPrototype.strokeRect = function(aX, aY, aWidth, aHeight) {
+ var oldPath = this.currentPath_;
+ this.beginPath();
+
+ this.moveTo(aX, aY);
+ this.lineTo(aX + aWidth, aY);
+ this.lineTo(aX + aWidth, aY + aHeight);
+ this.lineTo(aX, aY + aHeight);
+ this.closePath();
+ this.stroke();
+
+ this.currentPath_ = oldPath;
+ };
+
+ contextPrototype.fillRect = function(aX, aY, aWidth, aHeight) {
+ var oldPath = this.currentPath_;
+ this.beginPath();
+
+ this.moveTo(aX, aY);
+ this.lineTo(aX + aWidth, aY);
+ this.lineTo(aX + aWidth, aY + aHeight);
+ this.lineTo(aX, aY + aHeight);
+ this.closePath();
+ this.fill();
+
+ this.currentPath_ = oldPath;
+ };
+
+ contextPrototype.createLinearGradient = function(aX0, aY0, aX1, aY1) {
+ var gradient = new CanvasGradient_('gradient');
+ gradient.x0_ = aX0;
+ gradient.y0_ = aY0;
+ gradient.x1_ = aX1;
+ gradient.y1_ = aY1;
+ return gradient;
+ };
+
+ contextPrototype.createRadialGradient = function(aX0, aY0, aR0,
+ aX1, aY1, aR1) {
+ var gradient = new CanvasGradient_('gradientradial');
+ gradient.x0_ = aX0;
+ gradient.y0_ = aY0;
+ gradient.r0_ = aR0;
+ gradient.x1_ = aX1;
+ gradient.y1_ = aY1;
+ gradient.r1_ = aR1;
+ return gradient;
+ };
+
+ contextPrototype.drawImage = function(image, var_args) {
+ var dx, dy, dw, dh, sx, sy, sw, sh;
+
+ // to find the original width we overide the width and height
+ var oldRuntimeWidth = image.runtimeStyle.width;
+ var oldRuntimeHeight = image.runtimeStyle.height;
+ image.runtimeStyle.width = 'auto';
+ image.runtimeStyle.height = 'auto';
+
+ // get the original size
+ var w = image.width;
+ var h = image.height;
+
+ // and remove overides
+ image.runtimeStyle.width = oldRuntimeWidth;
+ image.runtimeStyle.height = oldRuntimeHeight;
+
+ if (arguments.length == 3) {
+ dx = arguments[1];
+ dy = arguments[2];
+ sx = sy = 0;
+ sw = dw = w;
+ sh = dh = h;
+ } else if (arguments.length == 5) {
+ dx = arguments[1];
+ dy = arguments[2];
+ dw = arguments[3];
+ dh = arguments[4];
+ sx = sy = 0;
+ sw = w;
+ sh = h;
+ } else if (arguments.length == 9) {
+ sx = arguments[1];
+ sy = arguments[2];
+ sw = arguments[3];
+ sh = arguments[4];
+ dx = arguments[5];
+ dy = arguments[6];
+ dw = arguments[7];
+ dh = arguments[8];
+ } else {
+ throw Error('Invalid number of arguments');
+ }
+
+ var d = getCoords(this, dx, dy);
+
+ var w2 = sw / 2;
+ var h2 = sh / 2;
+
+ var vmlStr = [];
+
+ var W = 10;
+ var H = 10;
+
+ // For some reason that I've now forgotten, using divs didn't work
+ vmlStr.push(' <g_vml_:group',
+ ' coordsize="', Z * W, ',', Z * H, '"',
+ ' coordorigin="0,0"' ,
+ ' style="width:', W, 'px;height:', H, 'px;position:absolute;');
+
+ // If filters are necessary (rotation exists), create them
+ // filters are bog-slow, so only create them if abbsolutely necessary
+ // The following check doesn't account for skews (which don't exist
+ // in the canvas spec (yet) anyway.
+
+ if (this.m_[0][0] != 1 || this.m_[0][1] ||
+ this.m_[1][1] != 1 || this.m_[1][0]) {
+ var filter = [];
+
+ // Note the 12/21 reversal
+ filter.push('M11=', this.m_[0][0], ',',
+ 'M12=', this.m_[1][0], ',',
+ 'M21=', this.m_[0][1], ',',
+ 'M22=', this.m_[1][1], ',',
+ 'Dx=', mr(d.x / Z), ',',
+ 'Dy=', mr(d.y / Z), '');
+
+ // Bounding box calculation (need to minimize displayed area so that
+ // filters don't waste time on unused pixels.
+ var max = d;
+ var c2 = getCoords(this, dx + dw, dy);
+ var c3 = getCoords(this, dx, dy + dh);
+ var c4 = getCoords(this, dx + dw, dy + dh);
+
+ max.x = m.max(max.x, c2.x, c3.x, c4.x);
+ max.y = m.max(max.y, c2.y, c3.y, c4.y);
+
+ vmlStr.push('padding:0 ', mr(max.x / Z), 'px ', mr(max.y / Z),
+ 'px 0;filter:progid:DXImageTransform.Microsoft.Matrix(',
+ filter.join(''), ", sizingmethod='clip');");
+
+ } else {
+ vmlStr.push('top:', mr(d.y / Z), 'px;left:', mr(d.x / Z), 'px;');
+ }
+
+ vmlStr.push(' ">' ,
+ '<g_vml_:image src="', image.src, '"',
+ ' style="width:', Z * dw, 'px;',
+ ' height:', Z * dh, 'px"',
+ ' cropleft="', sx / w, '"',
+ ' croptop="', sy / h, '"',
+ ' cropright="', (w - sx - sw) / w, '"',
+ ' cropbottom="', (h - sy - sh) / h, '"',
+ ' />',
+ '</g_vml_:group>');
+
+ this.element_.insertAdjacentHTML('BeforeEnd', vmlStr.join(''));
+ };
+
+ contextPrototype.stroke = function(aFill) {
+ var lineStr = [];
+ var lineOpen = false;
+
+ var W = 10;
+ var H = 10;
+
+ lineStr.push('<g_vml_:shape',
+ ' filled="', !!aFill, '"',
+ ' style="position:absolute;width:', W, 'px;height:', H, 'px;"',
+ ' coordorigin="0,0"',
+ ' coordsize="', Z * W, ',', Z * H, '"',
+ ' stroked="', !aFill, '"',
+ ' path="');
+
+ var newSeq = false;
+ var min = {x: null, y: null};
+ var max = {x: null, y: null};
+
+ for (var i = 0; i < this.currentPath_.length; i++) {
+ var p = this.currentPath_[i];
+ var c;
+
+ switch (p.type) {
+ case 'moveTo':
+ c = p;
+ lineStr.push(' m ', mr(p.x), ',', mr(p.y));
+ break;
+ case 'lineTo':
+ lineStr.push(' l ', mr(p.x), ',', mr(p.y));
+ break;
+ case 'close':
+ lineStr.push(' x ');
+ p = null;
+ break;
+ case 'bezierCurveTo':
+ lineStr.push(' c ',
+ mr(p.cp1x), ',', mr(p.cp1y), ',',
+ mr(p.cp2x), ',', mr(p.cp2y), ',',
+ mr(p.x), ',', mr(p.y));
+ break;
+ case 'at':
+ case 'wa':
+ lineStr.push(' ', p.type, ' ',
+ mr(p.x - this.arcScaleX_ * p.radius), ',',
+ mr(p.y - this.arcScaleY_ * p.radius), ' ',
+ mr(p.x + this.arcScaleX_ * p.radius), ',',
+ mr(p.y + this.arcScaleY_ * p.radius), ' ',
+ mr(p.xStart), ',', mr(p.yStart), ' ',
+ mr(p.xEnd), ',', mr(p.yEnd));
+ break;
+ }
+
+
+ // TODO: Following is broken for curves due to
+ // move to proper paths.
+
+ // Figure out dimensions so we can do gradient fills
+ // properly
+ if (p) {
+ if (min.x == null || p.x < min.x) {
+ min.x = p.x;
+ }
+ if (max.x == null || p.x > max.x) {
+ max.x = p.x;
+ }
+ if (min.y == null || p.y < min.y) {
+ min.y = p.y;
+ }
+ if (max.y == null || p.y > max.y) {
+ max.y = p.y;
+ }
+ }
+ }
+ lineStr.push(' ">');
+
+ if (!aFill) {
+ appendStroke(this, lineStr);
+ } else {
+ appendFill(this, lineStr, min, max);
+ }
+
+ lineStr.push('</g_vml_:shape>');
+
+ this.element_.insertAdjacentHTML('beforeEnd', lineStr.join(''));
+ };
+
+ function appendStroke(ctx, lineStr) {
+ var a = processStyle(ctx.strokeStyle);
+ var color = a.color;
+ var opacity = a.alpha * ctx.globalAlpha;
+ var lineWidth = ctx.lineScale_ * ctx.lineWidth;
+
+ // VML cannot correctly render a line if the width is less than 1px.
+ // In that case, we dilute the color to make the line look thinner.
+ if (lineWidth < 1) {
+ opacity *= lineWidth;
+ }
+
+ lineStr.push(
+ '<g_vml_:stroke',
+ ' opacity="', opacity, '"',
+ ' joinstyle="', ctx.lineJoin, '"',
+ ' miterlimit="', ctx.miterLimit, '"',
+ ' endcap="', processLineCap(ctx.lineCap), '"',
+ ' weight="', lineWidth, 'px"',
+ ' color="', color, '" />'
+ );
+ }
+
+ function appendFill(ctx, lineStr, min, max) {
+ var fillStyle = ctx.fillStyle;
+ var arcScaleX = ctx.arcScaleX_;
+ var arcScaleY = ctx.arcScaleY_;
+ var width = max.x - min.x;
+ var height = max.y - min.y;
+ if (fillStyle instanceof CanvasGradient_) {
+ // TODO: Gradients transformed with the transformation matrix.
+ var angle = 0;
+ var focus = {x: 0, y: 0};
+
+ // additional offset
+ var shift = 0;
+ // scale factor for offset
+ var expansion = 1;
+
+ if (fillStyle.type_ == 'gradient') {
+ var x0 = fillStyle.x0_ / arcScaleX;
+ var y0 = fillStyle.y0_ / arcScaleY;
+ var x1 = fillStyle.x1_ / arcScaleX;
+ var y1 = fillStyle.y1_ / arcScaleY;
+ var p0 = getCoords(ctx, x0, y0);
+ var p1 = getCoords(ctx, x1, y1);
+ var dx = p1.x - p0.x;
+ var dy = p1.y - p0.y;
+ angle = Math.atan2(dx, dy) * 180 / Math.PI;
+
+ // The angle should be a non-negative number.
+ if (angle < 0) {
+ angle += 360;
+ }
+
+ // Very small angles produce an unexpected result because they are
+ // converted to a scientific notation string.
+ if (angle < 1e-6) {
+ angle = 0;
+ }
+ } else {
+ var p0 = getCoords(ctx, fillStyle.x0_, fillStyle.y0_);
+ focus = {
+ x: (p0.x - min.x) / width,
+ y: (p0.y - min.y) / height
+ };
+
+ width /= arcScaleX * Z;
+ height /= arcScaleY * Z;
+ var dimension = m.max(width, height);
+ shift = 2 * fillStyle.r0_ / dimension;
+ expansion = 2 * fillStyle.r1_ / dimension - shift;
+ }
+
+ // We need to sort the color stops in ascending order by offset,
+ // otherwise IE won't interpret it correctly.
+ var stops = fillStyle.colors_;
+ stops.sort(function(cs1, cs2) {
+ return cs1.offset - cs2.offset;
+ });
+
+ var length = stops.length;
+ var color1 = stops[0].color;
+ var color2 = stops[length - 1].color;
+ var opacity1 = stops[0].alpha * ctx.globalAlpha;
+ var opacity2 = stops[length - 1].alpha * ctx.globalAlpha;
+
+ var colors = [];
+ for (var i = 0; i < length; i++) {
+ var stop = stops[i];
+ colors.push(stop.offset * expansion + shift + ' ' + stop.color);
+ }
+
+ // When colors attribute is used, the meanings of opacity and o:opacity2
+ // are reversed.
+ lineStr.push('<g_vml_:fill type="', fillStyle.type_, '"',
+ ' method="none" focus="100%"',
+ ' color="', color1, '"',
+ ' color2="', color2, '"',
+ ' colors="', colors.join(','), '"',
+ ' opacity="', opacity2, '"',
+ ' g_o_:opacity2="', opacity1, '"',
+ ' angle="', angle, '"',
+ ' focusposition="', focus.x, ',', focus.y, '" />');
+ } else if (fillStyle instanceof CanvasPattern_) {
+ if (width && height) {
+ var deltaLeft = -min.x;
+ var deltaTop = -min.y;
+ lineStr.push('<g_vml_:fill',
+ ' position="',
+ deltaLeft / width * arcScaleX * arcScaleX, ',',
+ deltaTop / height * arcScaleY * arcScaleY, '"',
+ ' type="tile"',
+ // TODO: Figure out the correct size to fit the scale.
+ //' size="', w, 'px ', h, 'px"',
+ ' src="', fillStyle.src_, '" />');
+ }
+ } else {
+ var a = processStyle(ctx.fillStyle);
+ var color = a.color;
+ var opacity = a.alpha * ctx.globalAlpha;
+ lineStr.push('<g_vml_:fill color="', color, '" opacity="', opacity,
+ '" />');
+ }
+ }
+
+ contextPrototype.fill = function() {
+ this.stroke(true);
+ };
+
+ contextPrototype.closePath = function() {
+ this.currentPath_.push({type: 'close'});
+ };
+
+ function getCoords(ctx, aX, aY) {
+ var m = ctx.m_;
+ return {
+ x: Z * (aX * m[0][0] + aY * m[1][0] + m[2][0]) - Z2,
+ y: Z * (aX * m[0][1] + aY * m[1][1] + m[2][1]) - Z2
+ };
+ };
+
+ contextPrototype.save = function() {
+ var o = {};
+ copyState(this, o);
+ this.aStack_.push(o);
+ this.mStack_.push(this.m_);
+ this.m_ = matrixMultiply(createMatrixIdentity(), this.m_);
+ };
+
+ contextPrototype.restore = function() {
+ if (this.aStack_.length) {
+ copyState(this.aStack_.pop(), this);
+ this.m_ = this.mStack_.pop();
+ }
+ };
+
+ function matrixIsFinite(m) {
+ return isFinite(m[0][0]) && isFinite(m[0][1]) &&
+ isFinite(m[1][0]) && isFinite(m[1][1]) &&
+ isFinite(m[2][0]) && isFinite(m[2][1]);
+ }
+
+ function setM(ctx, m, updateLineScale) {
+ if (!matrixIsFinite(m)) {
+ return;
+ }
+ ctx.m_ = m;
+
+ if (updateLineScale) {
+ // Get the line scale.
+ // Determinant of this.m_ means how much the area is enlarged by the
+ // transformation. So its square root can be used as a scale factor
+ // for width.
+ var det = m[0][0] * m[1][1] - m[0][1] * m[1][0];
+ ctx.lineScale_ = sqrt(abs(det));
+ }
+ }
+
+ contextPrototype.translate = function(aX, aY) {
+ var m1 = [
+ [1, 0, 0],
+ [0, 1, 0],
+ [aX, aY, 1]
+ ];
+
+ setM(this, matrixMultiply(m1, this.m_), false);
+ };
+
+ contextPrototype.rotate = function(aRot) {
+ var c = mc(aRot);
+ var s = ms(aRot);
+
+ var m1 = [
+ [c, s, 0],
+ [-s, c, 0],
+ [0, 0, 1]
+ ];
+
+ setM(this, matrixMultiply(m1, this.m_), false);
+ };
+
+ contextPrototype.scale = function(aX, aY) {
+ this.arcScaleX_ *= aX;
+ this.arcScaleY_ *= aY;
+ var m1 = [
+ [aX, 0, 0],
+ [0, aY, 0],
+ [0, 0, 1]
+ ];
+
+ setM(this, matrixMultiply(m1, this.m_), true);
+ };
+
+ contextPrototype.transform = function(m11, m12, m21, m22, dx, dy) {
+ var m1 = [
+ [m11, m12, 0],
+ [m21, m22, 0],
+ [dx, dy, 1]
+ ];
+
+ setM(this, matrixMultiply(m1, this.m_), true);
+ };
+
+ contextPrototype.setTransform = function(m11, m12, m21, m22, dx, dy) {
+ var m = [
+ [m11, m12, 0],
+ [m21, m22, 0],
+ [dx, dy, 1]
+ ];
+
+ setM(this, m, true);
+ };
+
+ /**
+ * The text drawing function.
+ * The maxWidth argument isn't taken in account, since no browser supports
+ * it yet.
+ */
+ contextPrototype.drawText_ = function(text, x, y, maxWidth, stroke) {
+ var m = this.m_,
+ delta = 1000,
+ left = 0,
+ right = delta,
+ offset = {x: 0, y: 0},
+ lineStr = [];
+
+ var fontStyle = getComputedStyle(processFontStyle(this.font), this.element_);
+
+ var fontStyleString = buildStyle(fontStyle);
+
+ var elementStyle = this.element_.currentStyle;
+ var textAlign = this.textAlign.toLowerCase();
+ switch (textAlign) {
+ case 'left':
+ case 'center':
+ case 'right':
+ break;
+ case 'end':
+ textAlign = elementStyle.direction == 'ltr' ? 'right' : 'left';
+ break;
+ case 'start':
+ textAlign = elementStyle.direction == 'rtl' ? 'right' : 'left';
+ break;
+ default:
+ textAlign = 'left';
+ }
+
+ // 1.75 is an arbitrary number, as there is no info about the text baseline
+ switch (this.textBaseline) {
+ case 'hanging':
+ case 'top':
+ offset.y = fontStyle.size / 1.75;
+ break;
+ case 'middle':
+ break;
+ default:
+ case null:
+ case 'alphabetic':
+ case 'ideographic':
+ case 'bottom':
+ offset.y = -fontStyle.size / 2.25;
+ break;
+ }
+
+ switch(textAlign) {
+ case 'right':
+ left = delta;
+ right = 0.05;
+ break;
+ case 'center':
+ left = right = delta / 2;
+ break;
+ }
+
+ var d = getCoords(this, x + offset.x, y + offset.y);
+
+ lineStr.push('<g_vml_:line from="', -left ,' 0" to="', right ,' 0.05" ',
+ ' coordsize="100 100" coordorigin="0 0"',
+ ' filled="', !stroke, '" stroked="', !!stroke,
+ '" style="position:absolute;width:1px;height:1px;">');
+
+ if (stroke) {
+ appendStroke(this, lineStr);
+ } else {
+ // TODO: Fix the min and max params.
+ appendFill(this, lineStr, {x: -left, y: 0},
+ {x: right, y: fontStyle.size});
+ }
+
+ var skewM = m[0][0].toFixed(3) + ',' + m[1][0].toFixed(3) + ',' +
+ m[0][1].toFixed(3) + ',' + m[1][1].toFixed(3) + ',0,0';
+
+ var skewOffset = mr(d.x / Z + 1 - m[0][0]) + ',' + mr(d.y / Z - 2 * m[1][0]);
+
+
+ lineStr.push('<g_vml_:skew on="t" matrix="', skewM ,'" ',
+ ' offset="', skewOffset, '" origin="', left ,' 0" />',
+ '<g_vml_:path textpathok="true" />',
+ '<g_vml_:textpath on="true" string="',
+ encodeHtmlAttribute(text),
+ '" style="v-text-align:', textAlign,
+ ';font:', encodeHtmlAttribute(fontStyleString),
+ '" /></g_vml_:line>');
+
+ this.element_.insertAdjacentHTML('beforeEnd', lineStr.join(''));
+ };
+
+ contextPrototype.fillText = function(text, x, y, maxWidth) {
+ this.drawText_(text, x, y, maxWidth, false);
+ };
+
+ contextPrototype.strokeText = function(text, x, y, maxWidth) {
+ this.drawText_(text, x, y, maxWidth, true);
+ };
+
+ contextPrototype.measureText = function(text) {
+ if (!this.textMeasureEl_) {
+ var s = '<span style="position:absolute;' +
+ 'top:-20000px;left:0;padding:0;margin:0;border:none;' +
+ 'white-space:pre;"></span>';
+ this.element_.insertAdjacentHTML('beforeEnd', s);
+ this.textMeasureEl_ = this.element_.lastChild;
+ }
+ var doc = this.element_.ownerDocument;
+ this.textMeasureEl_.innerHTML = '';
+ this.textMeasureEl_.style.font = this.font;
+ // Don't use innerHTML or innerText because they allow markup/whitespace.
+ this.textMeasureEl_.appendChild(doc.createTextNode(text));
+ return {width: this.textMeasureEl_.offsetWidth};
+ };
+
+ /******** STUBS ********/
+ contextPrototype.clip = function() {
+ // TODO: Implement
+ };
+
+ contextPrototype.arcTo = function() {
+ // TODO: Implement
+ };
+
+ contextPrototype.createPattern = function(image, repetition) {
+ return new CanvasPattern_(image, repetition);
+ };
+
+ // Gradient / Pattern Stubs
+ function CanvasGradient_(aType) {
+ this.type_ = aType;
+ this.x0_ = 0;
+ this.y0_ = 0;
+ this.r0_ = 0;
+ this.x1_ = 0;
+ this.y1_ = 0;
+ this.r1_ = 0;
+ this.colors_ = [];
+ }
+
+ CanvasGradient_.prototype.addColorStop = function(aOffset, aColor) {
+ aColor = processStyle(aColor);
+ this.colors_.push({offset: aOffset,
+ color: aColor.color,
+ alpha: aColor.alpha});
+ };
+
+ function CanvasPattern_(image, repetition) {
+ assertImageIsValid(image);
+ switch (repetition) {
+ case 'repeat':
+ case null:
+ case '':
+ this.repetition_ = 'repeat';
+ break;
+ case 'repeat-x':
+ case 'repeat-y':
+ case 'no-repeat':
+ this.repetition_ = repetition;
+ break;
+ default:
+ throwException('SYNTAX_ERR');
+ }
+
+ this.src_ = image.src;
+ this.width_ = image.width;
+ this.height_ = image.height;
+ }
+
+ function throwException(s) {
+ throw new DOMException_(s);
+ }
+
+ function assertImageIsValid(img) {
+ if (!img || img.nodeType != 1 || img.tagName != 'IMG') {
+ throwException('TYPE_MISMATCH_ERR');
+ }
+ if (img.readyState != 'complete') {
+ throwException('INVALID_STATE_ERR');
+ }
+ }
+
+ function DOMException_(s) {
+ this.code = this[s];
+ this.message = s +': DOM Exception ' + this.code;
+ }
+ var p = DOMException_.prototype = new Error;
+ p.INDEX_SIZE_ERR = 1;
+ p.DOMSTRING_SIZE_ERR = 2;
+ p.HIERARCHY_REQUEST_ERR = 3;
+ p.WRONG_DOCUMENT_ERR = 4;
+ p.INVALID_CHARACTER_ERR = 5;
+ p.NO_DATA_ALLOWED_ERR = 6;
+ p.NO_MODIFICATION_ALLOWED_ERR = 7;
+ p.NOT_FOUND_ERR = 8;
+ p.NOT_SUPPORTED_ERR = 9;
+ p.INUSE_ATTRIBUTE_ERR = 10;
+ p.INVALID_STATE_ERR = 11;
+ p.SYNTAX_ERR = 12;
+ p.INVALID_MODIFICATION_ERR = 13;
+ p.NAMESPACE_ERR = 14;
+ p.INVALID_ACCESS_ERR = 15;
+ p.VALIDATION_ERR = 16;
+ p.TYPE_MISMATCH_ERR = 17;
+
+ // set up externs
+ G_vmlCanvasManager = G_vmlCanvasManager_;
+ CanvasRenderingContext2D = CanvasRenderingContext2D_;
+ CanvasGradient = CanvasGradient_;
+ CanvasPattern = CanvasPattern_;
+ DOMException = DOMException_;
+ G_vmlCanvasManager._version = 888;
+})();
+
+} // if
diff --git a/js/jqplot/jquery.jqplot.js b/js/jqplot/jquery.jqplot.js
new file mode 100644
index 0000000000..cded47ab72
--- /dev/null
+++ b/js/jqplot/jquery.jqplot.js
@@ -0,0 +1,11381 @@
+/**
+ * Title: jqPlot Charts
+ *
+ * Pure JavaScript plotting plugin for jQuery.
+ *
+ * About: Version
+ *
+ * version: 1.0.4
+ * revision: 1121
+ *
+ * About: Copyright & License
+ *
+ * Copyright (c) 2009-2012 Chris Leonello
+ * jqPlot is currently available for use in all personal or commercial projects
+ * under both the MIT and GPL version 2.0 licenses. This means that you can
+ * choose the license that best suits your project and use it accordingly.
+ *
+ * See <GPL Version 2> and <MIT License> contained within this distribution for further information.
+ *
+ * The author would appreciate an email letting him know of any substantial
+ * use of jqPlot. You can reach the author at: chris at jqplot dot com
+ * or see http://www.jqplot.com/info.php. This is, of course, not required.
+ *
+ * If you are feeling kind and generous, consider supporting the project by
+ * making a donation at: http://www.jqplot.com/donate.php.
+ *
+ * sprintf functions contained in jqplot.sprintf.js by Ash Searle:
+ *
+ * version 2007.04.27
+ * author Ash Searle
+ * http://hexmen.com/blog/2007/03/printf-sprintf/
+ * http://hexmen.com/js/sprintf.js
+ * The author (Ash Searle) has placed this code in the public domain:
+ * "This code is unrestricted: you are free to use it however you like."
+ *
+ *
+ * About: Introduction
+ *
+ * jqPlot requires jQuery (1.4+ required for certain features). jQuery 1.4.2 is included in the distribution.
+ * To use jqPlot include jQuery, the jqPlot jQuery plugin, the jqPlot css file and optionally
+ * the excanvas script for IE support in your web page:
+ *
+ * > <!--[if lt IE 9]><script language="javascript" type="text/javascript" src="excanvas.js"></script><![endif]-->
+ * > <script language="javascript" type="text/javascript" src="jquery-1.4.4.min.js"></script>
+ * > <script language="javascript" type="text/javascript" src="jquery.jqplot.min.js"></script>
+ * > <link rel="stylesheet" type="text/css" href="jquery.jqplot.css" />
+ *
+ * jqPlot can be customized by overriding the defaults of any of the objects which make
+ * up the plot. The general usage of jqplot is:
+ *
+ * > chart = $.jqplot('targetElemId', [dataArray,...], {optionsObject});
+ *
+ * The options available to jqplot are detailed in <jqPlot Options> in the jqPlotOptions.txt file.
+ *
+ * An actual call to $.jqplot() may look like the
+ * examples below:
+ *
+ * > chart = $.jqplot('chartdiv', [[[1, 2],[3,5.12],[5,13.1],[7,33.6],[9,85.9],[11,219.9]]]);
+ *
+ * or
+ *
+ * > dataArray = [34,12,43,55,77];
+ * > chart = $.jqplot('targetElemId', [dataArray, ...], {title:'My Plot', axes:{yaxis:{min:20, max:100}}});
+ *
+ * For more inforrmation, see <jqPlot Usage>.
+ *
+ * About: Usage
+ *
+ * See <jqPlot Usage>
+ *
+ * About: Available Options
+ *
+ * See <jqPlot Options> for a list of options available thorugh the options object (not complete yet!)
+ *
+ * About: Options Usage
+ *
+ * See <Options Tutorial>
+ *
+ * About: Changes
+ *
+ * See <Change Log>
+ *
+ */
+
+(function($) {
+ // make sure undefined is undefined
+ var undefined;
+
+ $.fn.emptyForce = function() {
+ for ( var i = 0, elem; (elem = $(this)[i]) != null; i++ ) {
+ // Remove element nodes and prevent memory leaks
+ if ( elem.nodeType === 1 ) {
+ $.cleanData( elem.getElementsByTagName("*") );
+ }
+
+ // Remove any remaining nodes
+ if ($.jqplot.use_excanvas) {
+ elem.outerHTML = "";
+ }
+ else {
+ while ( elem.firstChild ) {
+ elem.removeChild( elem.firstChild );
+ }
+ }
+
+ elem = null;
+ }
+
+ return $(this);
+ };
+
+ $.fn.removeChildForce = function(parent) {
+ while ( parent.firstChild ) {
+ this.removeChildForce( parent.firstChild );
+ parent.removeChild( parent.firstChild );
+ }
+ };
+
+ $.fn.jqplot = function() {
+ var datas = [];
+ var options = [];
+ // see how many data arrays we have
+ for (var i=0, l=arguments.length; i<l; i++) {
+ if ($.isArray(arguments[i])) {
+ datas.push(arguments[i]);
+ }
+ else if ($.isPlainObject(arguments[i])) {
+ options.push(arguments[i]);
+ }
+ }
+
+ return this.each(function(index) {
+ var tid,
+ plot,
+ $this = $(this),
+ dl = datas.length,
+ ol = options.length,
+ data,
+ opts;
+
+ if (index < dl) {
+ data = datas[index];
+ }
+ else {
+ data = dl ? datas[dl-1] : null;
+ }
+
+ if (index < ol) {
+ opts = options[index];
+ }
+ else {
+ opts = ol ? options[ol-1] : null;
+ }
+
+ // does el have an id?
+ // if not assign it one.
+ tid = $this.attr('id');
+ if (tid === undefined) {
+ tid = 'jqplot_target_' + $.jqplot.targetCounter++;
+ $this.attr('id', tid);
+ }
+
+ plot = $.jqplot(tid, data, opts);
+
+ $this.data('jqplot', plot);
+ });
+ };
+
+
+ /**
+ * Namespace: $.jqplot
+ * jQuery function called by the user to create a plot.
+ *
+ * Parameters:
+ * target - ID of target element to render the plot into.
+ * data - an array of data series.
+ * options - user defined options object. See the individual classes for available options.
+ *
+ * Properties:
+ * config - object to hold configuration information for jqPlot plot object.
+ *
+ * attributes:
+ * enablePlugins - False to disable plugins by default. Plugins must then be explicitly
+ * enabled in the individual plot options. Default: false.
+ * This property sets the "show" property of certain plugins to true or false.
+ * Only plugins that can be immediately active upon loading are affected. This includes
+ * non-renderer plugins like cursor, dragable, highlighter, and trendline.
+ * defaultHeight - Default height for plots where no css height specification exists. This
+ * is a jqplot wide default.
+ * defaultWidth - Default height for plots where no css height specification exists. This
+ * is a jqplot wide default.
+ */
+
+ $.jqplot = function(target, data, options) {
+ var _data = null, _options = null;
+
+ if (arguments.length === 3) {
+ _data = data;
+ _options = options;
+ }
+
+ else if (arguments.length === 2) {
+ if ($.isArray(data)) {
+ _data = data;
+ }
+
+ else if ($.isPlainObject(data)) {
+ _options = data;
+ }
+ }
+
+ if (_data === null && _options !== null && _options.data) {
+ _data = _options.data;
+ }
+
+ var plot = new jqPlot();
+ // remove any error class that may be stuck on target.
+ $('#'+target).removeClass('jqplot-error');
+
+ if ($.jqplot.config.catchErrors) {
+ try {
+ plot.init(target, _data, _options);
+ plot.draw();
+ plot.themeEngine.init.call(plot);
+ return plot;
+ }
+ catch(e) {
+ var msg = $.jqplot.config.errorMessage || e.message;
+ $('#'+target).append('<div class="jqplot-error-message">'+msg+'</div>');
+ $('#'+target).addClass('jqplot-error');
+ document.getElementById(target).style.background = $.jqplot.config.errorBackground;
+ document.getElementById(target).style.border = $.jqplot.config.errorBorder;
+ document.getElementById(target).style.fontFamily = $.jqplot.config.errorFontFamily;
+ document.getElementById(target).style.fontSize = $.jqplot.config.errorFontSize;
+ document.getElementById(target).style.fontStyle = $.jqplot.config.errorFontStyle;
+ document.getElementById(target).style.fontWeight = $.jqplot.config.errorFontWeight;
+ }
+ }
+ else {
+ plot.init(target, _data, _options);
+ plot.draw();
+ plot.themeEngine.init.call(plot);
+ return plot;
+ }
+ };
+
+ $.jqplot.version = "1.0.4";
+ $.jqplot.revision = "1121";
+
+ $.jqplot.targetCounter = 1;
+
+ // canvas manager to reuse canvases on the plot.
+ // Should help solve problem of canvases not being freed and
+ // problem of waiting forever for firefox to decide to free memory.
+ $.jqplot.CanvasManager = function() {
+ // canvases are managed globally so that they can be reused
+ // across plots after they have been freed
+ if (typeof $.jqplot.CanvasManager.canvases == 'undefined') {
+ $.jqplot.CanvasManager.canvases = [];
+ $.jqplot.CanvasManager.free = [];
+ }
+
+ var myCanvases = [];
+
+ this.getCanvas = function() {
+ var canvas;
+ var makeNew = true;
+
+ if (!$.jqplot.use_excanvas) {
+ for (var i = 0, l = $.jqplot.CanvasManager.canvases.length; i < l; i++) {
+ if ($.jqplot.CanvasManager.free[i] === true) {
+ makeNew = false;
+ canvas = $.jqplot.CanvasManager.canvases[i];
+ // $(canvas).removeClass('jqplot-canvasManager-free').addClass('jqplot-canvasManager-inuse');
+ $.jqplot.CanvasManager.free[i] = false;
+ myCanvases.push(i);
+ break;
+ }
+ }
+ }
+
+ if (makeNew) {
+ canvas = document.createElement('canvas');
+ myCanvases.push($.jqplot.CanvasManager.canvases.length);
+ $.jqplot.CanvasManager.canvases.push(canvas);
+ $.jqplot.CanvasManager.free.push(false);
+ }
+
+ return canvas;
+ };
+
+ // this method has to be used after settings the dimesions
+ // on the element returned by getCanvas()
+ this.initCanvas = function(canvas) {
+ if ($.jqplot.use_excanvas) {
+ return window.G_vmlCanvasManager.initElement(canvas);
+ }
+ return canvas;
+ };
+
+ this.freeAllCanvases = function() {
+ for (var i = 0, l=myCanvases.length; i < l; i++) {
+ this.freeCanvas(myCanvases[i]);
+ }
+ myCanvases = [];
+ };
+
+ this.freeCanvas = function(idx) {
+ if ($.jqplot.use_excanvas && window.G_vmlCanvasManager.uninitElement !== undefined) {
+ // excanvas can't be reused, but properly unset
+ window.G_vmlCanvasManager.uninitElement($.jqplot.CanvasManager.canvases[idx]);
+ $.jqplot.CanvasManager.canvases[idx] = null;
+ }
+ else {
+ var canvas = $.jqplot.CanvasManager.canvases[idx];
+ canvas.getContext('2d').clearRect(0, 0, canvas.width, canvas.height);
+ $(canvas).unbind().removeAttr('class').removeAttr('style');
+ // Style attributes seemed to be still hanging around. wierd. Some ticks
+ // still retained a left: 0px attribute after reusing a canvas.
+ $(canvas).css({left: '', top: '', position: ''});
+ // setting size to 0 may save memory of unused canvases?
+ canvas.width = 0;
+ canvas.height = 0;
+ $.jqplot.CanvasManager.free[idx] = true;
+ }
+ };
+
+ };
+
+
+ // Convienence function that won't hang IE or FF without FireBug.
+ $.jqplot.log = function() {
+ if (window.console) {
+ window.console.log.apply(window.console, arguments);
+ }
+ };
+
+ $.jqplot.config = {
+ addDomReference: false,
+ enablePlugins:false,
+ defaultHeight:300,
+ defaultWidth:400,
+ UTCAdjust:false,
+ timezoneOffset: new Date(new Date().getTimezoneOffset() * 60000),
+ errorMessage: '',
+ errorBackground: '',
+ errorBorder: '',
+ errorFontFamily: '',
+ errorFontSize: '',
+ errorFontStyle: '',
+ errorFontWeight: '',
+ catchErrors: false,
+ defaultTickFormatString: "%.1f",
+ defaultColors: [ "#4bb2c5", "#EAA228", "#c5b47f", "#579575", "#839557", "#958c12", "#953579", "#4b5de4", "#d8b83f", "#ff5800", "#0085cc", "#c747a3", "#cddf54", "#FBD178", "#26B4E3", "#bd70c7"],
+ defaultNegativeColors: [ "#498991", "#C08840", "#9F9274", "#546D61", "#646C4A", "#6F6621", "#6E3F5F", "#4F64B0", "#A89050", "#C45923", "#187399", "#945381", "#959E5C", "#C7AF7B", "#478396", "#907294"],
+ dashLength: 4,
+ gapLength: 4,
+ dotGapLength: 2.5,
+ srcLocation: 'jqplot/src/',
+ pluginLocation: 'jqplot/src/plugins/'
+ };
+
+
+ $.jqplot.arrayMax = function( array ){
+ return Math.max.apply( Math, array );
+ };
+
+ $.jqplot.arrayMin = function( array ){
+ return Math.min.apply( Math, array );
+ };
+
+ $.jqplot.enablePlugins = $.jqplot.config.enablePlugins;
+
+ // canvas related tests taken from modernizer:
+ // Copyright (c) 2009 - 2010 Faruk Ates.
+ // http://www.modernizr.com
+
+ $.jqplot.support_canvas = function() {
+ if (typeof $.jqplot.support_canvas.result == 'undefined') {
+ $.jqplot.support_canvas.result = !!document.createElement('canvas').getContext;
+ }
+ return $.jqplot.support_canvas.result;
+ };
+
+ $.jqplot.support_canvas_text = function() {
+ if (typeof $.jqplot.support_canvas_text.result == 'undefined') {
+ if (window.G_vmlCanvasManager !== undefined && window.G_vmlCanvasManager._version > 887) {
+ $.jqplot.support_canvas_text.result = true;
+ }
+ else {
+ $.jqplot.support_canvas_text.result = !!(document.createElement('canvas').getContext && typeof document.createElement('canvas').getContext('2d').fillText == 'function');
+ }
+
+ }
+ return $.jqplot.support_canvas_text.result;
+ };
+
+ $.jqplot.use_excanvas = ($.browser.msie && !$.jqplot.support_canvas()) ? true : false;
+
+ /**
+ *
+ * Hooks: jqPlot Pugin Hooks
+ *
+ * $.jqplot.preInitHooks - called before initialization.
+ * $.jqplot.postInitHooks - called after initialization.
+ * $.jqplot.preParseOptionsHooks - called before user options are parsed.
+ * $.jqplot.postParseOptionsHooks - called after user options are parsed.
+ * $.jqplot.preDrawHooks - called before plot draw.
+ * $.jqplot.postDrawHooks - called after plot draw.
+ * $.jqplot.preDrawSeriesHooks - called before each series is drawn.
+ * $.jqplot.postDrawSeriesHooks - called after each series is drawn.
+ * $.jqplot.preDrawLegendHooks - called before the legend is drawn.
+ * $.jqplot.addLegendRowHooks - called at the end of legend draw, so plugins
+ * can add rows to the legend table.
+ * $.jqplot.preSeriesInitHooks - called before series is initialized.
+ * $.jqplot.postSeriesInitHooks - called after series is initialized.
+ * $.jqplot.preParseSeriesOptionsHooks - called before series related options
+ * are parsed.
+ * $.jqplot.postParseSeriesOptionsHooks - called after series related options
+ * are parsed.
+ * $.jqplot.eventListenerHooks - called at the end of plot drawing, binds
+ * listeners to the event canvas which lays on top of the grid area.
+ * $.jqplot.preDrawSeriesShadowHooks - called before series shadows are drawn.
+ * $.jqplot.postDrawSeriesShadowHooks - called after series shadows are drawn.
+ *
+ */
+
+ $.jqplot.preInitHooks = [];
+ $.jqplot.postInitHooks = [];
+ $.jqplot.preParseOptionsHooks = [];
+ $.jqplot.postParseOptionsHooks = [];
+ $.jqplot.preDrawHooks = [];
+ $.jqplot.postDrawHooks = [];
+ $.jqplot.preDrawSeriesHooks = [];
+ $.jqplot.postDrawSeriesHooks = [];
+ $.jqplot.preDrawLegendHooks = [];
+ $.jqplot.addLegendRowHooks = [];
+ $.jqplot.preSeriesInitHooks = [];
+ $.jqplot.postSeriesInitHooks = [];
+ $.jqplot.preParseSeriesOptionsHooks = [];
+ $.jqplot.postParseSeriesOptionsHooks = [];
+ $.jqplot.eventListenerHooks = [];
+ $.jqplot.preDrawSeriesShadowHooks = [];
+ $.jqplot.postDrawSeriesShadowHooks = [];
+
+ // A superclass holding some common properties and methods.
+ $.jqplot.ElemContainer = function() {
+ this._elem;
+ this._plotWidth;
+ this._plotHeight;
+ this._plotDimensions = {height:null, width:null};
+ };
+
+ $.jqplot.ElemContainer.prototype.createElement = function(el, offsets, clss, cssopts, attrib) {
+ this._offsets = offsets;
+ var klass = clss || 'jqplot';
+ var elem = document.createElement(el);
+ this._elem = $(elem);
+ this._elem.addClass(klass);
+ this._elem.css(cssopts);
+ this._elem.attr(attrib);
+ // avoid memory leak;
+ elem = null;
+ return this._elem;
+ };
+
+ $.jqplot.ElemContainer.prototype.getWidth = function() {
+ if (this._elem) {
+ return this._elem.outerWidth(true);
+ }
+ else {
+ return null;
+ }
+ };
+
+ $.jqplot.ElemContainer.prototype.getHeight = function() {
+ if (this._elem) {
+ return this._elem.outerHeight(true);
+ }
+ else {
+ return null;
+ }
+ };
+
+ $.jqplot.ElemContainer.prototype.getPosition = function() {
+ if (this._elem) {
+ return this._elem.position();
+ }
+ else {
+ return {top:null, left:null, bottom:null, right:null};
+ }
+ };
+
+ $.jqplot.ElemContainer.prototype.getTop = function() {
+ return this.getPosition().top;
+ };
+
+ $.jqplot.ElemContainer.prototype.getLeft = function() {
+ return this.getPosition().left;
+ };
+
+ $.jqplot.ElemContainer.prototype.getBottom = function() {
+ return this._elem.css('bottom');
+ };
+
+ $.jqplot.ElemContainer.prototype.getRight = function() {
+ return this._elem.css('right');
+ };
+
+
+ /**
+ * Class: Axis
+ * An individual axis object. Cannot be instantiated directly, but created
+ * by the Plot oject. Axis properties can be set or overriden by the
+ * options passed in from the user.
+ *
+ */
+ function Axis(name) {
+ $.jqplot.ElemContainer.call(this);
+ // Group: Properties
+ //
+ // Axes options are specified within an axes object at the top level of the
+ // plot options like so:
+ // > {
+ // > axes: {
+ // > xaxis: {min: 5},
+ // > yaxis: {min: 2, max: 8, numberTicks:4},
+ // > x2axis: {pad: 1.5},
+ // > y2axis: {ticks:[22, 44, 66, 88]}
+ // > }
+ // > }
+ // There are 2 x axes, 'xaxis' and 'x2axis', and
+ // 9 yaxes, 'yaxis', 'y2axis'. 'y3axis', ... Any or all of which may be specified.
+ this.name = name;
+ this._series = [];
+ // prop: show
+ // Wether to display the axis on the graph.
+ this.show = false;
+ // prop: tickRenderer
+ // A class of a rendering engine for creating the ticks labels displayed on the plot,
+ // See <$.jqplot.AxisTickRenderer>.
+ this.tickRenderer = $.jqplot.AxisTickRenderer;
+ // prop: tickOptions
+ // Options that will be passed to the tickRenderer, see <$.jqplot.AxisTickRenderer> options.
+ this.tickOptions = {};
+ // prop: labelRenderer
+ // A class of a rendering engine for creating an axis label.
+ this.labelRenderer = $.jqplot.AxisLabelRenderer;
+ // prop: labelOptions
+ // Options passed to the label renderer.
+ this.labelOptions = {};
+ // prop: label
+ // Label for the axis
+ this.label = null;
+ // prop: showLabel
+ // true to show the axis label.
+ this.showLabel = true;
+ // prop: min
+ // minimum value of the axis (in data units, not pixels).
+ this.min = null;
+ // prop: max
+ // maximum value of the axis (in data units, not pixels).
+ this.max = null;
+ // prop: autoscale
+ // DEPRECATED
+ // the default scaling algorithm produces superior results.
+ this.autoscale = false;
+ // prop: pad
+ // Padding to extend the range above and below the data bounds.
+ // The data range is multiplied by this factor to determine minimum and maximum axis bounds.
+ // A value of 0 will be interpreted to mean no padding, and pad will be set to 1.0.
+ this.pad = 1.2;
+ // prop: padMax
+ // Padding to extend the range above data bounds.
+ // The top of the data range is multiplied by this factor to determine maximum axis bounds.
+ // A value of 0 will be interpreted to mean no padding, and padMax will be set to 1.0.
+ this.padMax = null;
+ // prop: padMin
+ // Padding to extend the range below data bounds.
+ // The bottom of the data range is multiplied by this factor to determine minimum axis bounds.
+ // A value of 0 will be interpreted to mean no padding, and padMin will be set to 1.0.
+ this.padMin = null;
+ // prop: ticks
+ // 1D [val, val, ...] or 2D [[val, label], [val, label], ...] array of ticks for the axis.
+ // If no label is specified, the value is formatted into an appropriate label.
+ this.ticks = [];
+ // prop: numberTicks
+ // Desired number of ticks. Default is to compute automatically.
+ this.numberTicks;
+ // prop: tickInterval
+ // number of units between ticks. Mutually exclusive with numberTicks.
+ this.tickInterval;
+ // prop: renderer
+ // A class of a rendering engine that handles tick generation,
+ // scaling input data to pixel grid units and drawing the axis element.
+ this.renderer = $.jqplot.LinearAxisRenderer;
+ // prop: rendererOptions
+ // renderer specific options. See <$.jqplot.LinearAxisRenderer> for options.
+ this.rendererOptions = {};
+ // prop: showTicks
+ // Wether to show the ticks (both marks and labels) or not.
+ // Will not override showMark and showLabel options if specified on the ticks themselves.
+ this.showTicks = true;
+ // prop: showTickMarks
+ // Wether to show the tick marks (line crossing grid) or not.
+ // Overridden by showTicks and showMark option of tick itself.
+ this.showTickMarks = true;
+ // prop: showMinorTicks
+ // Wether or not to show minor ticks. This is renderer dependent.
+ this.showMinorTicks = true;
+ // prop: drawMajorGridlines
+ // True to draw gridlines for major axis ticks.
+ this.drawMajorGridlines = true;
+ // prop: drawMinorGridlines
+ // True to draw gridlines for minor ticks.
+ this.drawMinorGridlines = false;
+ // prop: drawMajorTickMarks
+ // True to draw tick marks for major axis ticks.
+ this.drawMajorTickMarks = true;
+ // prop: drawMinorTickMarks
+ // True to draw tick marks for minor ticks. This is renderer dependent.
+ this.drawMinorTickMarks = true;
+ // prop: useSeriesColor
+ // Use the color of the first series associated with this axis for the
+ // tick marks and line bordering this axis.
+ this.useSeriesColor = false;
+ // prop: borderWidth
+ // width of line stroked at the border of the axis. Defaults
+ // to the width of the grid boarder.
+ this.borderWidth = null;
+ // prop: borderColor
+ // color of the border adjacent to the axis. Defaults to grid border color.
+ this.borderColor = null;
+ // prop: scaleToHiddenSeries
+ // True to include hidden series when computing axes bounds and scaling.
+ this.scaleToHiddenSeries = false;
+ // minimum and maximum values on the axis.
+ this._dataBounds = {min:null, max:null};
+ // statistics (min, max, mean) as well as actual data intervals for each series attached to axis.
+ // holds collection of {intervals:[], min:, max:, mean: } objects for each series on axis.
+ this._intervalStats = [];
+ // pixel position from the top left of the min value and max value on the axis.
+ this._offsets = {min:null, max:null};
+ this._ticks=[];
+ this._label = null;
+ // prop: syncTicks
+ // true to try and synchronize tick spacing across multiple axes so that ticks and
+ // grid lines line up. This has an impact on autoscaling algorithm, however.
+ // In general, autoscaling an individual axis will work better if it does not
+ // have to sync ticks.
+ this.syncTicks = null;
+ // prop: tickSpacing
+ // Approximate pixel spacing between ticks on graph. Used during autoscaling.
+ // This number will be an upper bound, actual spacing will be less.
+ this.tickSpacing = 75;
+ // Properties to hold the original values for min, max, ticks, tickInterval and numberTicks
+ // so they can be restored if altered by plugins.
+ this._min = null;
+ this._max = null;
+ this._tickInterval = null;
+ this._numberTicks = null;
+ this.__ticks = null;
+ // hold original user options.
+ this._options = {};
+ }
+
+ Axis.prototype = new $.jqplot.ElemContainer();
+ Axis.prototype.constructor = Axis;
+
+ Axis.prototype.init = function() {
+ if ($.isFunction(this.renderer)) {
+ this.renderer = new this.renderer();
+ }
+ // set the axis name
+ this.tickOptions.axis = this.name;
+ // if showMark or showLabel tick options not specified, use value of axis option.
+ // showTicks overrides showTickMarks.
+ if (this.tickOptions.showMark == null) {
+ this.tickOptions.showMark = this.showTicks;
+ }
+ if (this.tickOptions.showMark == null) {
+ this.tickOptions.showMark = this.showTickMarks;
+ }
+ if (this.tickOptions.showLabel == null) {
+ this.tickOptions.showLabel = this.showTicks;
+ }
+
+ if (this.label == null || this.label == '') {
+ this.showLabel = false;
+ }
+ else {
+ this.labelOptions.label = this.label;
+ }
+ if (this.showLabel == false) {
+ this.labelOptions.show = false;
+ }
+ // set the default padMax, padMin if not specified
+ // special check, if no padding desired, padding
+ // should be set to 1.0
+ if (this.pad == 0) {
+ this.pad = 1.0;
+ }
+ if (this.padMax == 0) {
+ this.padMax = 1.0;
+ }
+ if (this.padMin == 0) {
+ this.padMin = 1.0;
+ }
+ if (this.padMax == null) {
+ this.padMax = (this.pad-1)/2 + 1;
+ }
+ if (this.padMin == null) {
+ this.padMin = (this.pad-1)/2 + 1;
+ }
+ // now that padMin and padMax are correctly set, reset pad in case user has supplied
+ // padMin and/or padMax
+ this.pad = this.padMax + this.padMin - 1;
+ if (this.min != null || this.max != null) {
+ this.autoscale = false;
+ }
+ // if not set, sync ticks for y axes but not x by default.
+ if (this.syncTicks == null && this.name.indexOf('y') > -1) {
+ this.syncTicks = true;
+ }
+ else if (this.syncTicks == null){
+ this.syncTicks = false;
+ }
+ this.renderer.init.call(this, this.rendererOptions);
+
+ };
+
+ Axis.prototype.draw = function(ctx, plot) {
+ // Memory Leaks patch
+ if (this.__ticks) {
+ this.__ticks = null;
+ }
+
+ return this.renderer.draw.call(this, ctx, plot);
+
+ };
+
+ Axis.prototype.set = function() {
+ this.renderer.set.call(this);
+ };
+
+ Axis.prototype.pack = function(pos, offsets) {
+ if (this.show) {
+ this.renderer.pack.call(this, pos, offsets);
+ }
+ // these properties should all be available now.
+ if (this._min == null) {
+ this._min = this.min;
+ this._max = this.max;
+ this._tickInterval = this.tickInterval;
+ this._numberTicks = this.numberTicks;
+ this.__ticks = this._ticks;
+ }
+ };
+
+ // reset the axis back to original values if it has been scaled, zoomed, etc.
+ Axis.prototype.reset = function() {
+ this.renderer.reset.call(this);
+ };
+
+ Axis.prototype.resetScale = function(opts) {
+ $.extend(true, this, {min: null, max: null, numberTicks: null, tickInterval: null, _ticks: [], ticks: []}, opts);
+ this.resetDataBounds();
+ };
+
+ Axis.prototype.resetDataBounds = function() {
+ // Go through all the series attached to this axis and find
+ // the min/max bounds for this axis.
+ var db = this._dataBounds;
+ db.min = null;
+ db.max = null;
+ var l, s, d;
+ // check for when to force min 0 on bar series plots.
+ var doforce = (this.show) ? true : false;
+ for (var i=0; i<this._series.length; i++) {
+ s = this._series[i];
+ if (s.show || this.scaleToHiddenSeries) {
+ d = s._plotData;
+ if (s._type === 'line' && s.renderer.bands.show && this.name.charAt(0) !== 'x') {
+ d = [[0, s.renderer.bands._min], [1, s.renderer.bands._max]];
+ }
+
+ var minyidx = 1, maxyidx = 1;
+
+ if (s._type != null && s._type == 'ohlc') {
+ minyidx = 3;
+ maxyidx = 2;
+ }
+
+ for (var j=0, l=d.length; j<l; j++) {
+ if (this.name == 'xaxis' || this.name == 'x2axis') {
+ if ((d[j][0] != null && d[j][0] < db.min) || db.min == null) {
+ db.min = d[j][0];
+ }
+ if ((d[j][0] != null && d[j][0] > db.max) || db.max == null) {
+ db.max = d[j][0];
+ }
+ }
+ else {
+ if ((d[j][minyidx] != null && d[j][minyidx] < db.min) || db.min == null) {
+ db.min = d[j][minyidx];
+ }
+ if ((d[j][maxyidx] != null && d[j][maxyidx] > db.max) || db.max == null) {
+ db.max = d[j][maxyidx];
+ }
+ }
+ }
+
+ // Hack to not pad out bottom of bar plots unless user has specified a padding.
+ // every series will have a chance to set doforce to false. once it is set to
+ // false, it cannot be reset to true.
+ // If any series attached to axis is not a bar, wont force 0.
+ if (doforce && s.renderer.constructor !== $.jqplot.BarRenderer) {
+ doforce = false;
+ }
+
+ else if (doforce && this._options.hasOwnProperty('forceTickAt0') && this._options.forceTickAt0 == false) {
+ doforce = false;
+ }
+
+ else if (doforce && s.renderer.constructor === $.jqplot.BarRenderer) {
+ if (s.barDirection == 'vertical' && this.name != 'xaxis' && this.name != 'x2axis') {
+ if (this._options.pad != null || this._options.padMin != null) {
+ doforce = false;
+ }
+ }
+
+ else if (s.barDirection == 'horizontal' && (this.name == 'xaxis' || this.name == 'x2axis')) {
+ if (this._options.pad != null || this._options.padMin != null) {
+ doforce = false;
+ }
+ }
+
+ }
+ }
+ }
+
+ if (doforce && this.renderer.constructor === $.jqplot.LinearAxisRenderer && db.min >= 0) {
+ this.padMin = 1.0;
+ this.forceTickAt0 = true;
+ }
+ };
+
+ /**
+ * Class: Legend
+ * Legend object. Cannot be instantiated directly, but created
+ * by the Plot oject. Legend properties can be set or overriden by the
+ * options passed in from the user.
+ */
+ function Legend(options) {
+ $.jqplot.ElemContainer.call(this);
+ // Group: Properties
+
+ // prop: show
+ // Wether to display the legend on the graph.
+ this.show = false;
+ // prop: location
+ // Placement of the legend. one of the compass directions: nw, n, ne, e, se, s, sw, w
+ this.location = 'ne';
+ // prop: labels
+ // Array of labels to use. By default the renderer will look for labels on the series.
+ // Labels specified in this array will override labels specified on the series.
+ this.labels = [];
+ // prop: showLabels
+ // true to show the label text on the legend.
+ this.showLabels = true;
+ // prop: showSwatch
+ // true to show the color swatches on the legend.
+ this.showSwatches = true;
+ // prop: placement
+ // "insideGrid" places legend inside the grid area of the plot.
+ // "outsideGrid" places the legend outside the grid but inside the plot container,
+ // shrinking the grid to accomodate the legend.
+ // "inside" synonym for "insideGrid",
+ // "outside" places the legend ouside the grid area, but does not shrink the grid which
+ // can cause the legend to overflow the plot container.
+ this.placement = "insideGrid";
+ // prop: xoffset
+ // DEPRECATED. Set the margins on the legend using the marginTop, marginLeft, etc.
+ // properties or via CSS margin styling of the .jqplot-table-legend class.
+ this.xoffset = 0;
+ // prop: yoffset
+ // DEPRECATED. Set the margins on the legend using the marginTop, marginLeft, etc.
+ // properties or via CSS margin styling of the .jqplot-table-legend class.
+ this.yoffset = 0;
+ // prop: border
+ // css spec for the border around the legend box.
+ this.border;
+ // prop: background
+ // css spec for the background of the legend box.
+ this.background;
+ // prop: textColor
+ // css color spec for the legend text.
+ this.textColor;
+ // prop: fontFamily
+ // css font-family spec for the legend text.
+ this.fontFamily;
+ // prop: fontSize
+ // css font-size spec for the legend text.
+ this.fontSize ;
+ // prop: rowSpacing
+ // css padding-top spec for the rows in the legend.
+ this.rowSpacing = '0.5em';
+ // renderer
+ // A class that will create a DOM object for the legend,
+ // see <$.jqplot.TableLegendRenderer>.
+ this.renderer = $.jqplot.TableLegendRenderer;
+ // prop: rendererOptions
+ // renderer specific options passed to the renderer.
+ this.rendererOptions = {};
+ // prop: predraw
+ // Wether to draw the legend before the series or not.
+ // Used with series specific legend renderers for pie, donut, mekko charts, etc.
+ this.preDraw = false;
+ // prop: marginTop
+ // CSS margin for the legend DOM element. This will set an element
+ // CSS style for the margin which will override any style sheet setting.
+ // The default will be taken from the stylesheet.
+ this.marginTop = null;
+ // prop: marginRight
+ // CSS margin for the legend DOM element. This will set an element
+ // CSS style for the margin which will override any style sheet setting.
+ // The default will be taken from the stylesheet.
+ this.marginRight = null;
+ // prop: marginBottom
+ // CSS margin for the legend DOM element. This will set an element
+ // CSS style for the margin which will override any style sheet setting.
+ // The default will be taken from the stylesheet.
+ this.marginBottom = null;
+ // prop: marginLeft
+ // CSS margin for the legend DOM element. This will set an element
+ // CSS style for the margin which will override any style sheet setting.
+ // The default will be taken from the stylesheet.
+ this.marginLeft = null;
+ // prop: escapeHtml
+ // True to escape special characters with their html entity equivalents
+ // in legend text. "<" becomes &lt; and so on, so html tags are not rendered.
+ this.escapeHtml = false;
+ this._series = [];
+
+ $.extend(true, this, options);
+ }
+
+ Legend.prototype = new $.jqplot.ElemContainer();
+ Legend.prototype.constructor = Legend;
+
+ Legend.prototype.setOptions = function(options) {
+ $.extend(true, this, options);
+
+ // Try to emulate deprecated behaviour
+ // if user has specified xoffset or yoffset, copy these to
+ // the margin properties.
+
+ if (this.placement == 'inside') {
+ this.placement = 'insideGrid';
+ }
+
+ if (this.xoffset >0) {
+ if (this.placement == 'insideGrid') {
+ switch (this.location) {
+ case 'nw':
+ case 'w':
+ case 'sw':
+ if (this.marginLeft == null) {
+ this.marginLeft = this.xoffset + 'px';
+ }
+ this.marginRight = '0px';
+ break;
+ case 'ne':
+ case 'e':
+ case 'se':
+ default:
+ if (this.marginRight == null) {
+ this.marginRight = this.xoffset + 'px';
+ }
+ this.marginLeft = '0px';
+ break;
+ }
+ }
+ else if (this.placement == 'outside') {
+ switch (this.location) {
+ case 'nw':
+ case 'w':
+ case 'sw':
+ if (this.marginRight == null) {
+ this.marginRight = this.xoffset + 'px';
+ }
+ this.marginLeft = '0px';
+ break;
+ case 'ne':
+ case 'e':
+ case 'se':
+ default:
+ if (this.marginLeft == null) {
+ this.marginLeft = this.xoffset + 'px';
+ }
+ this.marginRight = '0px';
+ break;
+ }
+ }
+ this.xoffset = 0;
+ }
+
+ if (this.yoffset >0) {
+ if (this.placement == 'outside') {
+ switch (this.location) {
+ case 'sw':
+ case 's':
+ case 'se':
+ if (this.marginTop == null) {
+ this.marginTop = this.yoffset + 'px';
+ }
+ this.marginBottom = '0px';
+ break;
+ case 'ne':
+ case 'n':
+ case 'nw':
+ default:
+ if (this.marginBottom == null) {
+ this.marginBottom = this.yoffset + 'px';
+ }
+ this.marginTop = '0px';
+ break;
+ }
+ }
+ else if (this.placement == 'insideGrid') {
+ switch (this.location) {
+ case 'sw':
+ case 's':
+ case 'se':
+ if (this.marginBottom == null) {
+ this.marginBottom = this.yoffset + 'px';
+ }
+ this.marginTop = '0px';
+ break;
+ case 'ne':
+ case 'n':
+ case 'nw':
+ default:
+ if (this.marginTop == null) {
+ this.marginTop = this.yoffset + 'px';
+ }
+ this.marginBottom = '0px';
+ break;
+ }
+ }
+ this.yoffset = 0;
+ }
+
+ // TO-DO:
+ // Handle case where offsets are < 0.
+ //
+ };
+
+ Legend.prototype.init = function() {
+ if ($.isFunction(this.renderer)) {
+ this.renderer = new this.renderer();
+ }
+ this.renderer.init.call(this, this.rendererOptions);
+ };
+
+ Legend.prototype.draw = function(offsets, plot) {
+ for (var i=0; i<$.jqplot.preDrawLegendHooks.length; i++){
+ $.jqplot.preDrawLegendHooks[i].call(this, offsets);
+ }
+ return this.renderer.draw.call(this, offsets, plot);
+ };
+
+ Legend.prototype.pack = function(offsets) {
+ this.renderer.pack.call(this, offsets);
+ };
+
+ /**
+ * Class: Title
+ * Plot Title object. Cannot be instantiated directly, but created
+ * by the Plot oject. Title properties can be set or overriden by the
+ * options passed in from the user.
+ *
+ * Parameters:
+ * text - text of the title.
+ */
+ function Title(text) {
+ $.jqplot.ElemContainer.call(this);
+ // Group: Properties
+
+ // prop: text
+ // text of the title;
+ this.text = text;
+ // prop: show
+ // wether or not to show the title
+ this.show = true;
+ // prop: fontFamily
+ // css font-family spec for the text.
+ this.fontFamily;
+ // prop: fontSize
+ // css font-size spec for the text.
+ this.fontSize ;
+ // prop: textAlign
+ // css text-align spec for the text.
+ this.textAlign;
+ // prop: textColor
+ // css color spec for the text.
+ this.textColor;
+ // prop: renderer
+ // A class for creating a DOM element for the title,
+ // see <$.jqplot.DivTitleRenderer>.
+ this.renderer = $.jqplot.DivTitleRenderer;
+ // prop: rendererOptions
+ // renderer specific options passed to the renderer.
+ this.rendererOptions = {};
+ // prop: escapeHtml
+ // True to escape special characters with their html entity equivalents
+ // in title text. "<" becomes &lt; and so on, so html tags are not rendered.
+ this.escapeHtml = false;
+ }
+
+ Title.prototype = new $.jqplot.ElemContainer();
+ Title.prototype.constructor = Title;
+
+ Title.prototype.init = function() {
+ if ($.isFunction(this.renderer)) {
+ this.renderer = new this.renderer();
+ }
+ this.renderer.init.call(this, this.rendererOptions);
+ };
+
+ Title.prototype.draw = function(width) {
+ return this.renderer.draw.call(this, width);
+ };
+
+ Title.prototype.pack = function() {
+ this.renderer.pack.call(this);
+ };
+
+
+ /**
+ * Class: Series
+ * An individual data series object. Cannot be instantiated directly, but created
+ * by the Plot oject. Series properties can be set or overriden by the
+ * options passed in from the user.
+ */
+ function Series(options) {
+ options = options || {};
+ $.jqplot.ElemContainer.call(this);
+ // Group: Properties
+ // Properties will be assigned from a series array at the top level of the
+ // options. If you had two series and wanted to change the color and line
+ // width of the first and set the second to use the secondary y axis with
+ // no shadow and supply custom labels for each:
+ // > {
+ // > series:[
+ // > {color: '#ff4466', lineWidth: 5, label:'good line'},
+ // > {yaxis: 'y2axis', shadow: false, label:'bad line'}
+ // > ]
+ // > }
+
+ // prop: show
+ // wether or not to draw the series.
+ this.show = true;
+ // prop: xaxis
+ // which x axis to use with this series, either 'xaxis' or 'x2axis'.
+ this.xaxis = 'xaxis';
+ this._xaxis;
+ // prop: yaxis
+ // which y axis to use with this series, either 'yaxis' or 'y2axis'.
+ this.yaxis = 'yaxis';
+ this._yaxis;
+ this.gridBorderWidth = 2.0;
+ // prop: renderer
+ // A class of a renderer which will draw the series,
+ // see <$.jqplot.LineRenderer>.
+ this.renderer = $.jqplot.LineRenderer;
+ // prop: rendererOptions
+ // Options to pass on to the renderer.
+ this.rendererOptions = {};
+ this.data = [];
+ this.gridData = [];
+ // prop: label
+ // Line label to use in the legend.
+ this.label = '';
+ // prop: showLabel
+ // true to show label for this series in the legend.
+ this.showLabel = true;
+ // prop: color
+ // css color spec for the series
+ this.color;
+ // prop: negativeColor
+ // css color spec used for filled (area) plots that are filled to zero and
+ // the "useNegativeColors" option is true.
+ this.negativeColor;
+ // prop: lineWidth
+ // width of the line in pixels. May have different meanings depending on renderer.
+ this.lineWidth = 2.5;
+ // prop: lineJoin
+ // Canvas lineJoin style between segments of series.
+ this.lineJoin = 'round';
+ // prop: lineCap
+ // Canvas lineCap style at ends of line.
+ this.lineCap = 'round';
+ // prop: linePattern
+ // line pattern 'dashed', 'dotted', 'solid', some combination
+ // of '-' and '.' characters such as '.-.' or a numerical array like
+ // [draw, skip, draw, skip, ...] such as [1, 10] to draw a dotted line,
+ // [1, 10, 20, 10] to draw a dot-dash line, and so on.
+ this.linePattern = 'solid';
+ this.shadow = true;
+ // prop: shadowAngle
+ // Shadow angle in degrees
+ this.shadowAngle = 45;
+ // prop: shadowOffset
+ // Shadow offset from line in pixels
+ this.shadowOffset = 1.25;
+ // prop: shadowDepth
+ // Number of times shadow is stroked, each stroke offset shadowOffset from the last.
+ this.shadowDepth = 3;
+ // prop: shadowAlpha
+ // Alpha channel transparency of shadow. 0 = transparent.
+ this.shadowAlpha = '0.1';
+ // prop: breakOnNull
+ // Wether line segments should be be broken at null value.
+ // False will join point on either side of line.
+ this.breakOnNull = false;
+ // prop: markerRenderer
+ // A class of a renderer which will draw marker (e.g. circle, square, ...) at the data points,
+ // see <$.jqplot.MarkerRenderer>.
+ this.markerRenderer = $.jqplot.MarkerRenderer;
+ // prop: markerOptions
+ // renderer specific options to pass to the markerRenderer,
+ // see <$.jqplot.MarkerRenderer>.
+ this.markerOptions = {};
+ // prop: showLine
+ // wether to actually draw the line or not. Series will still be renderered, even if no line is drawn.
+ this.showLine = true;
+ // prop: showMarker
+ // wether or not to show the markers at the data points.
+ this.showMarker = true;
+ // prop: index
+ // 0 based index of this series in the plot series array.
+ this.index;
+ // prop: fill
+ // true or false, wether to fill under lines or in bars.
+ // May not be implemented in all renderers.
+ this.fill = false;
+ // prop: fillColor
+ // CSS color spec to use for fill under line. Defaults to line color.
+ this.fillColor;
+ // prop: fillAlpha
+ // Alpha transparency to apply to the fill under the line.
+ // Use this to adjust alpha separate from fill color.
+ this.fillAlpha;
+ // prop: fillAndStroke
+ // If true will stroke the line (with color this.color) as well as fill under it.
+ // Applies only when fill is true.
+ this.fillAndStroke = false;
+ // prop: disableStack
+ // true to not stack this series with other series in the plot.
+ // To render properly, non-stacked series must come after any stacked series
+ // in the plot's data series array. So, the plot's data series array would look like:
+ // > [stackedSeries1, stackedSeries2, ..., nonStackedSeries1, nonStackedSeries2, ...]
+ // disableStack will put a gap in the stacking order of series, and subsequent
+ // stacked series will not fill down through the non-stacked series and will
+ // most likely not stack properly on top of the non-stacked series.
+ this.disableStack = false;
+ // _stack is set by the Plot if the plot is a stacked chart.
+ // will stack lines or bars on top of one another to build a "mountain" style chart.
+ // May not be implemented in all renderers.
+ this._stack = false;
+ // prop: neighborThreshold
+ // how close or far (in pixels) the cursor must be from a point marker to detect the point.
+ this.neighborThreshold = 4;
+ // prop: fillToZero
+ // true will force bar and filled series to fill toward zero on the fill Axis.
+ this.fillToZero = false;
+ // prop: fillToValue
+ // fill a filled series to this value on the fill axis.
+ // Works in conjunction with fillToZero, so that must be true.
+ this.fillToValue = 0;
+ // prop: fillAxis
+ // Either 'x' or 'y'. Which axis to fill the line toward if fillToZero is true.
+ // 'y' means fill up/down to 0 on the y axis for this series.
+ this.fillAxis = 'y';
+ // prop: useNegativeColors
+ // true to color negative values differently in filled and bar charts.
+ this.useNegativeColors = true;
+ this._stackData = [];
+ // _plotData accounts for stacking. If plots not stacked, _plotData and data are same. If
+ // stacked, _plotData is accumulation of stacking data.
+ this._plotData = [];
+ // _plotValues hold the individual x and y values that will be plotted for this series.
+ this._plotValues = {x:[], y:[]};
+ // statistics about the intervals between data points. Used for auto scaling.
+ this._intervals = {x:{}, y:{}};
+ // data from the previous series, for stacked charts.
+ this._prevPlotData = [];
+ this._prevGridData = [];
+ this._stackAxis = 'y';
+ this._primaryAxis = '_xaxis';
+ // give each series a canvas to draw on. This should allow for redrawing speedups.
+ this.canvas = new $.jqplot.GenericCanvas();
+ this.shadowCanvas = new $.jqplot.GenericCanvas();
+ this.plugins = {};
+ // sum of y values in this series.
+ this._sumy = 0;
+ this._sumx = 0;
+ this._type = '';
+ }
+
+ Series.prototype = new $.jqplot.ElemContainer();
+ Series.prototype.constructor = Series;
+
+ Series.prototype.init = function(index, gridbw, plot) {
+ // weed out any null values in the data.
+ this.index = index;
+ this.gridBorderWidth = gridbw;
+ var d = this.data;
+ var temp = [], i, l;
+ for (i=0, l=d.length; i<l; i++) {
+ if (! this.breakOnNull) {
+ if (d[i] == null || d[i][0] == null || d[i][1] == null) {
+ continue;
+ }
+ else {
+ temp.push(d[i]);
+ }
+ }
+ else {
+ // TODO: figure out what to do with null values
+ // probably involve keeping nulls in data array
+ // and then updating renderers to break line
+ // when it hits null value.
+ // For now, just keep value.
+ temp.push(d[i]);
+ }
+ }
+ this.data = temp;
+
+ // parse the renderer options and apply default colors if not provided
+ // Set color even if not shown, so series don't change colors when other
+ // series on plot shown/hidden.
+ if (!this.color) {
+ this.color = plot.colorGenerator.get(this.index);
+ }
+ if (!this.negativeColor) {
+ this.negativeColor = plot.negativeColorGenerator.get(this.index);
+ }
+
+
+ if (!this.fillColor) {
+ this.fillColor = this.color;
+ }
+ if (this.fillAlpha) {
+ var comp = $.jqplot.normalize2rgb(this.fillColor);
+ var comp = $.jqplot.getColorComponents(comp);
+ this.fillColor = 'rgba('+comp[0]+','+comp[1]+','+comp[2]+','+this.fillAlpha+')';
+ }
+ if ($.isFunction(this.renderer)) {
+ this.renderer = new this.renderer();
+ }
+ this.renderer.init.call(this, this.rendererOptions, plot);
+ this.markerRenderer = new this.markerRenderer();
+ if (!this.markerOptions.color) {
+ this.markerOptions.color = this.color;
+ }
+ if (this.markerOptions.show == null) {
+ this.markerOptions.show = this.showMarker;
+ }
+ this.showMarker = this.markerOptions.show;
+ // the markerRenderer is called within it's own scaope, don't want to overwrite series options!!
+ this.markerRenderer.init(this.markerOptions);
+ };
+
+ // data - optional data point array to draw using this series renderer
+ // gridData - optional grid data point array to draw using this series renderer
+ // stackData - array of cumulative data for stacked plots.
+ Series.prototype.draw = function(sctx, opts, plot) {
+ var options = (opts == undefined) ? {} : opts;
+ sctx = (sctx == undefined) ? this.canvas._ctx : sctx;
+
+ var j, data, gridData;
+
+ // hooks get called even if series not shown
+ // we don't clear canvas here, it would wipe out all other series as well.
+ for (j=0; j<$.jqplot.preDrawSeriesHooks.length; j++) {
+ $.jqplot.preDrawSeriesHooks[j].call(this, sctx, options);
+ }
+ if (this.show) {
+ this.renderer.setGridData.call(this, plot);
+ if (!options.preventJqPlotSeriesDrawTrigger) {
+ $(sctx.canvas).trigger('jqplotSeriesDraw', [this.data, this.gridData]);
+ }
+ data = [];
+ if (options.data) {
+ data = options.data;
+ }
+ else if (!this._stack) {
+ data = this.data;
+ }
+ else {
+ data = this._plotData;
+ }
+ gridData = options.gridData || this.renderer.makeGridData.call(this, data, plot);
+
+ if (this._type === 'line' && this.renderer.smooth && this.renderer._smoothedData.length) {
+ gridData = this.renderer._smoothedData;
+ }
+
+ this.renderer.draw.call(this, sctx, gridData, options, plot);
+ }
+
+ for (j=0; j<$.jqplot.postDrawSeriesHooks.length; j++) {
+ $.jqplot.postDrawSeriesHooks[j].call(this, sctx, options, plot);
+ }
+
+ sctx = opts = plot = j = data = gridData = null;
+ };
+
+ Series.prototype.drawShadow = function(sctx, opts, plot) {
+ var options = (opts == undefined) ? {} : opts;
+ sctx = (sctx == undefined) ? this.shadowCanvas._ctx : sctx;
+
+ var j, data, gridData;
+
+ // hooks get called even if series not shown
+ // we don't clear canvas here, it would wipe out all other series as well.
+ for (j=0; j<$.jqplot.preDrawSeriesShadowHooks.length; j++) {
+ $.jqplot.preDrawSeriesShadowHooks[j].call(this, sctx, options);
+ }
+ if (this.shadow) {
+ this.renderer.setGridData.call(this, plot);
+
+ data = [];
+ if (options.data) {
+ data = options.data;
+ }
+ else if (!this._stack) {
+ data = this.data;
+ }
+ else {
+ data = this._plotData;
+ }
+ gridData = options.gridData || this.renderer.makeGridData.call(this, data, plot);
+
+ this.renderer.drawShadow.call(this, sctx, gridData, options, plot);
+ }
+
+ for (j=0; j<$.jqplot.postDrawSeriesShadowHooks.length; j++) {
+ $.jqplot.postDrawSeriesShadowHooks[j].call(this, sctx, options);
+ }
+
+ sctx = opts = plot = j = data = gridData = null;
+
+ };
+
+ // toggles series display on plot, e.g. show/hide series
+ Series.prototype.toggleDisplay = function(ev, callback) {
+ var s, speed;
+ if (ev.data.series) {
+ s = ev.data.series;
+ }
+ else {
+ s = this;
+ }
+
+ if (ev.data.speed) {
+ speed = ev.data.speed;
+ }
+ if (speed) {
+ // this can be tricky because series may not have a canvas element if replotting.
+ if (s.canvas._elem.is(':hidden') || !s.show) {
+ s.show = true;
+
+ s.canvas._elem.removeClass('jqplot-series-hidden');
+ if (s.shadowCanvas._elem) {
+ s.shadowCanvas._elem.fadeIn(speed);
+ }
+ s.canvas._elem.fadeIn(speed, callback);
+ s.canvas._elem.nextAll('.jqplot-point-label.jqplot-series-'+s.index).fadeIn(speed);
+ }
+ else {
+ s.show = false;
+
+ s.canvas._elem.addClass('jqplot-series-hidden');
+ if (s.shadowCanvas._elem) {
+ s.shadowCanvas._elem.fadeOut(speed);
+ }
+ s.canvas._elem.fadeOut(speed, callback);
+ s.canvas._elem.nextAll('.jqplot-point-label.jqplot-series-'+s.index).fadeOut(speed);
+ }
+ }
+ else {
+ // this can be tricky because series may not have a canvas element if replotting.
+ if (s.canvas._elem.is(':hidden') || !s.show) {
+ s.show = true;
+
+ s.canvas._elem.removeClass('jqplot-series-hidden');
+ if (s.shadowCanvas._elem) {
+ s.shadowCanvas._elem.show();
+ }
+ s.canvas._elem.show(0, callback);
+ s.canvas._elem.nextAll('.jqplot-point-label.jqplot-series-'+s.index).show();
+ }
+ else {
+ s.show = false;
+
+ s.canvas._elem.addClass('jqplot-series-hidden');
+ if (s.shadowCanvas._elem) {
+ s.shadowCanvas._elem.hide();
+ }
+ s.canvas._elem.hide(0, callback);
+ s.canvas._elem.nextAll('.jqplot-point-label.jqplot-series-'+s.index).hide();
+ }
+ }
+ };
+
+
+
+ /**
+ * Class: Grid
+ *
+ * Object representing the grid on which the plot is drawn. The grid in this
+ * context is the area bounded by the axes, the area which will contain the series.
+ * Note, the series are drawn on their own canvas.
+ * The Grid object cannot be instantiated directly, but is created by the Plot oject.
+ * Grid properties can be set or overriden by the options passed in from the user.
+ */
+ function Grid() {
+ $.jqplot.ElemContainer.call(this);
+ // Group: Properties
+
+ // prop: drawGridlines
+ // wether to draw the gridlines on the plot.
+ this.drawGridlines = true;
+ // prop: gridLineColor
+ // color of the grid lines.
+ this.gridLineColor = '#cccccc';
+ // prop: gridLineWidth
+ // width of the grid lines.
+ this.gridLineWidth = 1.0;
+ // prop: background
+ // css spec for the background color.
+ this.background = '#fffdf6';
+ // prop: borderColor
+ // css spec for the color of the grid border.
+ this.borderColor = '#999999';
+ // prop: borderWidth
+ // width of the border in pixels.
+ this.borderWidth = 2.0;
+ // prop: drawBorder
+ // True to draw border around grid.
+ this.drawBorder = true;
+ // prop: shadow
+ // wether to show a shadow behind the grid.
+ this.shadow = true;
+ // prop: shadowAngle
+ // shadow angle in degrees
+ this.shadowAngle = 45;
+ // prop: shadowOffset
+ // Offset of each shadow stroke from the border in pixels
+ this.shadowOffset = 1.5;
+ // prop: shadowWidth
+ // width of the stoke for the shadow
+ this.shadowWidth = 3;
+ // prop: shadowDepth
+ // Number of times shadow is stroked, each stroke offset shadowOffset from the last.
+ this.shadowDepth = 3;
+ // prop: shadowColor
+ // an optional css color spec for the shadow in 'rgba(n, n, n, n)' form
+ this.shadowColor = null;
+ // prop: shadowAlpha
+ // Alpha channel transparency of shadow. 0 = transparent.
+ this.shadowAlpha = '0.07';
+ this._left;
+ this._top;
+ this._right;
+ this._bottom;
+ this._width;
+ this._height;
+ this._axes = [];
+ // prop: renderer
+ // Instance of a renderer which will actually render the grid,
+ // see <$.jqplot.CanvasGridRenderer>.
+ this.renderer = $.jqplot.CanvasGridRenderer;
+ // prop: rendererOptions
+ // Options to pass on to the renderer,
+ // see <$.jqplot.CanvasGridRenderer>.
+ this.rendererOptions = {};
+ this._offsets = {top:null, bottom:null, left:null, right:null};
+ }
+
+ Grid.prototype = new $.jqplot.ElemContainer();
+ Grid.prototype.constructor = Grid;
+
+ Grid.prototype.init = function() {
+ if ($.isFunction(this.renderer)) {
+ this.renderer = new this.renderer();
+ }
+ this.renderer.init.call(this, this.rendererOptions);
+ };
+
+ Grid.prototype.createElement = function(offsets,plot) {
+ this._offsets = offsets;
+ return this.renderer.createElement.call(this, plot);
+ };
+
+ Grid.prototype.draw = function() {
+ this.renderer.draw.call(this);
+ };
+
+ $.jqplot.GenericCanvas = function() {
+ $.jqplot.ElemContainer.call(this);
+ this._ctx;
+ };
+
+ $.jqplot.GenericCanvas.prototype = new $.jqplot.ElemContainer();
+ $.jqplot.GenericCanvas.prototype.constructor = $.jqplot.GenericCanvas;
+
+ $.jqplot.GenericCanvas.prototype.createElement = function(offsets, clss, plotDimensions, plot) {
+ this._offsets = offsets;
+ var klass = 'jqplot';
+ if (clss != undefined) {
+ klass = clss;
+ }
+ var elem;
+
+ elem = plot.canvasManager.getCanvas();
+
+ // if new plotDimensions supplied, use them.
+ if (plotDimensions != null) {
+ this._plotDimensions = plotDimensions;
+ }
+
+ elem.width = this._plotDimensions.width - this._offsets.left - this._offsets.right;
+ elem.height = this._plotDimensions.height - this._offsets.top - this._offsets.bottom;
+ this._elem = $(elem);
+ this._elem.css({ position: 'absolute', left: this._offsets.left, top: this._offsets.top });
+
+ this._elem.addClass(klass);
+
+ elem = plot.canvasManager.initCanvas(elem);
+
+ elem = null;
+ return this._elem;
+ };
+
+ $.jqplot.GenericCanvas.prototype.setContext = function() {
+ this._ctx = this._elem.get(0).getContext("2d");
+ return this._ctx;
+ };
+
+ // Memory Leaks patch
+ $.jqplot.GenericCanvas.prototype.resetCanvas = function() {
+ if (this._elem) {
+ if ($.jqplot.use_excanvas && window.G_vmlCanvasManager.uninitElement !== undefined) {
+ window.G_vmlCanvasManager.uninitElement(this._elem.get(0));
+ }
+
+ //this._elem.remove();
+ this._elem.emptyForce();
+ }
+
+ this._ctx = null;
+ };
+
+ $.jqplot.HooksManager = function () {
+ this.hooks =[];
+ this.args = [];
+ };
+
+ $.jqplot.HooksManager.prototype.addOnce = function(fn, args) {
+ args = args || [];
+ var havehook = false;
+ for (var i=0, l=this.hooks.length; i<l; i++) {
+ if (this.hooks[i] == fn) {
+ havehook = true;
+ }
+ }
+ if (!havehook) {
+ this.hooks.push(fn);
+ this.args.push(args);
+ }
+ };
+
+ $.jqplot.HooksManager.prototype.add = function(fn, args) {
+ args = args || [];
+ this.hooks.push(fn);
+ this.args.push(args);
+ };
+
+ $.jqplot.EventListenerManager = function () {
+ this.hooks =[];
+ };
+
+ $.jqplot.EventListenerManager.prototype.addOnce = function(ev, fn) {
+ var havehook = false, h, i;
+ for (var i=0, l=this.hooks.length; i<l; i++) {
+ h = this.hooks[i];
+ if (h[0] == ev && h[1] == fn) {
+ havehook = true;
+ }
+ }
+ if (!havehook) {
+ this.hooks.push([ev, fn]);
+ }
+ };
+
+ $.jqplot.EventListenerManager.prototype.add = function(ev, fn) {
+ this.hooks.push([ev, fn]);
+ };
+
+
+ var _axisNames = ['yMidAxis', 'xaxis', 'yaxis', 'x2axis', 'y2axis', 'y3axis', 'y4axis', 'y5axis', 'y6axis', 'y7axis', 'y8axis', 'y9axis'];
+
+ /**
+ * Class: jqPlot
+ * Plot object returned by call to $.jqplot. Handles parsing user options,
+ * creating sub objects (Axes, legend, title, series) and rendering the plot.
+ */
+ function jqPlot() {
+ // Group: Properties
+ // These properties are specified at the top of the options object
+ // like so:
+ // > {
+ // > axesDefaults:{min:0},
+ // > series:[{color:'#6633dd'}],
+ // > title: 'A Plot'
+ // > }
+ //
+
+ // prop: animate
+ // True to animate the series on initial plot draw (renderer dependent).
+ // Actual animation functionality must be supported in the renderer.
+ this.animate = false;
+ // prop: animateReplot
+ // True to animate series after a call to the replot() method.
+ // Use with caution! Replots can happen very frequently under
+ // certain circumstances (e.g. resizing, dragging points) and
+ // animation in these situations can cause problems.
+ this.animateReplot = false;
+ // prop: axes
+ // up to 4 axes are supported, each with it's own options,
+ // See <Axis> for axis specific options.
+ this.axes = {xaxis: new Axis('xaxis'), yaxis: new Axis('yaxis'), x2axis: new Axis('x2axis'), y2axis: new Axis('y2axis'), y3axis: new Axis('y3axis'), y4axis: new Axis('y4axis'), y5axis: new Axis('y5axis'), y6axis: new Axis('y6axis'), y7axis: new Axis('y7axis'), y8axis: new Axis('y8axis'), y9axis: new Axis('y9axis'), yMidAxis: new Axis('yMidAxis')};
+ this.baseCanvas = new $.jqplot.GenericCanvas();
+ // true to intercept right click events and fire a 'jqplotRightClick' event.
+ // this will also block the context menu.
+ this.captureRightClick = false;
+ // prop: data
+ // user's data. Data should *NOT* be specified in the options object,
+ // but be passed in as the second argument to the $.jqplot() function.
+ // The data property is described here soley for reference.
+ // The data should be in the form of an array of 2D or 1D arrays like
+ // > [ [[x1, y1], [x2, y2],...], [y1, y2, ...] ].
+ this.data = [];
+ // prop: dataRenderer
+ // A callable which can be used to preprocess data passed into the plot.
+ // Will be called with 2 arguments, the plot data and a reference to the plot.
+ this.dataRenderer;
+ // prop: dataRendererOptions
+ // Options that will be passed to the dataRenderer.
+ // Can be of any type.
+ this.dataRendererOptions;
+ this.defaults = {
+ // prop: axesDefaults
+ // default options that will be applied to all axes.
+ // see <Axis> for axes options.
+ axesDefaults: {},
+ axes: {xaxis:{}, yaxis:{}, x2axis:{}, y2axis:{}, y3axis:{}, y4axis:{}, y5axis:{}, y6axis:{}, y7axis:{}, y8axis:{}, y9axis:{}, yMidAxis:{}},
+ // prop: seriesDefaults
+ // default options that will be applied to all series.
+ // see <Series> for series options.
+ seriesDefaults: {},
+ series:[]
+ };
+ // prop: defaultAxisStart
+ // 1-D data series are internally converted into 2-D [x,y] data point arrays
+ // by jqPlot. This is the default starting value for the missing x or y value.
+ // The added data will be a monotonically increasing series (e.g. [1, 2, 3, ...])
+ // starting at this value.
+ this.defaultAxisStart = 1;
+ // this.doCustomEventBinding = true;
+ // prop: drawIfHidden
+ // True to execute the draw method even if the plot target is hidden.
+ // Generally, this should be false. Most plot elements will not be sized/
+ // positioned correclty if renderered into a hidden container. To render into
+ // a hidden container, call the replot method when the container is shown.
+ this.drawIfHidden = false;
+ this.eventCanvas = new $.jqplot.GenericCanvas();
+ // prop: fillBetween
+ // Fill between 2 line series in a plot.
+ // Options object:
+ // {
+ // series1: first index (0 based) of series in fill
+ // series2: second index (0 based) of series in fill
+ // color: color of fill [default fillColor of series1]
+ // baseSeries: fill will be drawn below this series (0 based index)
+ // fill: false to turn off fill [default true].
+ // }
+ this.fillBetween = {
+ series1: null,
+ series2: null,
+ color: null,
+ baseSeries: 0,
+ fill: true
+ };
+ // prop; fontFamily
+ // css spec for the font-family attribute. Default for the entire plot.
+ this.fontFamily;
+ // prop: fontSize
+ // css spec for the font-size attribute. Default for the entire plot.
+ this.fontSize;
+ // prop: grid
+ // See <Grid> for grid specific options.
+ this.grid = new Grid();
+ // prop: legend
+ // see <$.jqplot.TableLegendRenderer>
+ this.legend = new Legend();
+ // prop: noDataIndicator
+ // Options to set up a mock plot with a data loading indicator if no data is specified.
+ this.negativeSeriesColors = $.jqplot.config.defaultNegativeColors;
+ this.noDataIndicator = {
+ show: false,
+ indicator: 'Loading Data...',
+ axes: {
+ xaxis: {
+ min: 0,
+ max: 10,
+ tickInterval: 2,
+ show: true
+ },
+ yaxis: {
+ min: 0,
+ max: 12,
+ tickInterval: 3,
+ show: true
+ }
+ }
+ };
+ // container to hold all of the merged options. Convienence for plugins.
+ this.options = {};
+ this.previousSeriesStack = [];
+ // Namespece to hold plugins. Generally non-renderer plugins add themselves to here.
+ this.plugins = {};
+ // prop: series
+ // Array of series object options.
+ // see <Series> for series specific options.
+ this.series = [];
+ // array of series indicies. Keep track of order
+ // which series canvases are displayed, lowest
+ // to highest, back to front.
+ this.seriesStack = [];
+ // prop: seriesColors
+ // Ann array of CSS color specifications that will be applied, in order,
+ // to the series in the plot. Colors will wrap around so, if their
+ // are more series than colors, colors will be reused starting at the
+ // beginning. For pie charts, this specifies the colors of the slices.
+ this.seriesColors = $.jqplot.config.defaultColors;
+ // prop: sortData
+ // false to not sort the data passed in by the user.
+ // Many bar, stakced and other graphs as well as many plugins depend on
+ // having sorted data.
+ this.sortData = true;
+ // prop: stackSeries
+ // true or false, creates a stack or "mountain" plot.
+ // Not all series renderers may implement this option.
+ this.stackSeries = false;
+ // a shortcut for axis syncTicks options. Not implemented yet.
+ this.syncXTicks = true;
+ // a shortcut for axis syncTicks options. Not implemented yet.
+ this.syncYTicks = true;
+ // the jquery object for the dom target.
+ this.target = null;
+ // The id of the dom element to render the plot into
+ this.targetId = null;
+ // prop textColor
+ // css spec for the css color attribute. Default for the entire plot.
+ this.textColor;
+ // prop: title
+ // Title object. See <Title> for specific options. As a shortcut, you
+ // can specify the title option as just a string like: title: 'My Plot'
+ // and this will create a new title object with the specified text.
+ this.title = new Title();
+ // Count how many times the draw method has been called while the plot is visible.
+ // Mostly used to test if plot has never been dran (=0), has been successfully drawn
+ // into a visible container once (=1) or draw more than once into a visible container.
+ // Can use this in tests to see if plot has been visibly drawn at least one time.
+ // After plot has been visibly drawn once, it generally doesn't need redrawn if its
+ // container is hidden and shown.
+ this._drawCount = 0;
+ // sum of y values for all series in plot.
+ // used in mekko chart.
+ this._sumy = 0;
+ this._sumx = 0;
+ // array to hold the cumulative stacked series data.
+ // used to ajust the individual series data, which won't have access to other
+ // series data.
+ this._stackData = [];
+ // array that holds the data to be plotted. This will be the series data
+ // merged with the the appropriate data from _stackData according to the stackAxis.
+ this._plotData = [];
+ this._width = null;
+ this._height = null;
+ this._plotDimensions = {height:null, width:null};
+ this._gridPadding = {top:null, right:null, bottom:null, left:null};
+ this._defaultGridPadding = {top:10, right:10, bottom:23, left:10};
+
+ this._addDomReference = $.jqplot.config.addDomReference;
+
+ this.preInitHooks = new $.jqplot.HooksManager();
+ this.postInitHooks = new $.jqplot.HooksManager();
+ this.preParseOptionsHooks = new $.jqplot.HooksManager();
+ this.postParseOptionsHooks = new $.jqplot.HooksManager();
+ this.preDrawHooks = new $.jqplot.HooksManager();
+ this.postDrawHooks = new $.jqplot.HooksManager();
+ this.preDrawSeriesHooks = new $.jqplot.HooksManager();
+ this.postDrawSeriesHooks = new $.jqplot.HooksManager();
+ this.preDrawLegendHooks = new $.jqplot.HooksManager();
+ this.addLegendRowHooks = new $.jqplot.HooksManager();
+ this.preSeriesInitHooks = new $.jqplot.HooksManager();
+ this.postSeriesInitHooks = new $.jqplot.HooksManager();
+ this.preParseSeriesOptionsHooks = new $.jqplot.HooksManager();
+ this.postParseSeriesOptionsHooks = new $.jqplot.HooksManager();
+ this.eventListenerHooks = new $.jqplot.EventListenerManager();
+ this.preDrawSeriesShadowHooks = new $.jqplot.HooksManager();
+ this.postDrawSeriesShadowHooks = new $.jqplot.HooksManager();
+
+ this.colorGenerator = new $.jqplot.ColorGenerator();
+ this.negativeColorGenerator = new $.jqplot.ColorGenerator();
+
+ this.canvasManager = new $.jqplot.CanvasManager();
+
+ this.themeEngine = new $.jqplot.ThemeEngine();
+
+ var seriesColorsIndex = 0;
+
+ // Group: methods
+ //
+ // method: init
+ // sets the plot target, checks data and applies user
+ // options to plot.
+ this.init = function(target, data, options) {
+ options = options || {};
+ for (var i=0; i<$.jqplot.preInitHooks.length; i++) {
+ $.jqplot.preInitHooks[i].call(this, target, data, options);
+ }
+
+ for (var i=0; i<this.preInitHooks.hooks.length; i++) {
+ this.preInitHooks.hooks[i].call(this, target, data, options);
+ }
+
+ this.targetId = '#'+target;
+ this.target = $('#'+target);
+
+ //////
+ // Add a reference to plot
+ //////
+ if (this._addDomReference) {
+ this.target.data('jqplot', this);
+ }
+ // remove any error class that may be stuck on target.
+ this.target.removeClass('jqplot-error');
+ if (!this.target.get(0)) {
+ throw "No plot target specified";
+ }
+
+ // make sure the target is positioned by some means and set css
+ if (this.target.css('position') == 'static') {
+ this.target.css('position', 'relative');
+ }
+ if (!this.target.hasClass('jqplot-target')) {
+ this.target.addClass('jqplot-target');
+ }
+
+ // if no height or width specified, use a default.
+ if (!this.target.height()) {
+ var h;
+ if (options && options.height) {
+ h = parseInt(options.height, 10);
+ }
+ else if (this.target.attr('data-height')) {
+ h = parseInt(this.target.attr('data-height'), 10);
+ }
+ else {
+ h = parseInt($.jqplot.config.defaultHeight, 10);
+ }
+ this._height = h;
+ this.target.css('height', h+'px');
+ }
+ else {
+ this._height = h = this.target.height();
+ }
+ if (!this.target.width()) {
+ var w;
+ if (options && options.width) {
+ w = parseInt(options.width, 10);
+ }
+ else if (this.target.attr('data-width')) {
+ w = parseInt(this.target.attr('data-width'), 10);
+ }
+ else {
+ w = parseInt($.jqplot.config.defaultWidth, 10);
+ }
+ this._width = w;
+ this.target.css('width', w+'px');
+ }
+ else {
+ this._width = w = this.target.width();
+ }
+
+ for (var i=0, l=_axisNames.length; i<l; i++) {
+ this.axes[_axisNames[i]] = new Axis(_axisNames[i]);
+ }
+
+ this._plotDimensions.height = this._height;
+ this._plotDimensions.width = this._width;
+ this.grid._plotDimensions = this._plotDimensions;
+ this.title._plotDimensions = this._plotDimensions;
+ this.baseCanvas._plotDimensions = this._plotDimensions;
+ this.eventCanvas._plotDimensions = this._plotDimensions;
+ this.legend._plotDimensions = this._plotDimensions;
+ if (this._height <=0 || this._width <=0 || !this._height || !this._width) {
+ throw "Canvas dimension not set";
+ }
+
+ if (options.dataRenderer && $.isFunction(options.dataRenderer)) {
+ if (options.dataRendererOptions) {
+ this.dataRendererOptions = options.dataRendererOptions;
+ }
+ this.dataRenderer = options.dataRenderer;
+ data = this.dataRenderer(data, this, this.dataRendererOptions);
+ }
+
+ if (options.noDataIndicator && $.isPlainObject(options.noDataIndicator)) {
+ $.extend(true, this.noDataIndicator, options.noDataIndicator);
+ }
+
+ if (data == null || $.isArray(data) == false || data.length == 0 || $.isArray(data[0]) == false || data[0].length == 0) {
+
+ if (this.noDataIndicator.show == false) {
+ throw "No Data";
+ }
+
+ else {
+ // have to be descructive here in order for plot to not try and render series.
+ // This means that $.jqplot() will have to be called again when there is data.
+ //delete options.series;
+
+ for (var ax in this.noDataIndicator.axes) {
+ for (var prop in this.noDataIndicator.axes[ax]) {
+ this.axes[ax][prop] = this.noDataIndicator.axes[ax][prop];
+ }
+ }
+
+ this.postDrawHooks.add(function() {
+ var eh = this.eventCanvas.getHeight();
+ var ew = this.eventCanvas.getWidth();
+ var temp = $('<div class="jqplot-noData-container" style="position:absolute;"></div>');
+ this.target.append(temp);
+ temp.height(eh);
+ temp.width(ew);
+ temp.css('top', this.eventCanvas._offsets.top);
+ temp.css('left', this.eventCanvas._offsets.left);
+
+ var temp2 = $('<div class="jqplot-noData-contents" style="text-align:center; position:relative; margin-left:auto; margin-right:auto;"></div>');
+ temp.append(temp2);
+ temp2.html(this.noDataIndicator.indicator);
+ var th = temp2.height();
+ var tw = temp2.width();
+ temp2.height(th);
+ temp2.width(tw);
+ temp2.css('top', (eh - th)/2 + 'px');
+ });
+
+ }
+ }
+
+ // make a copy of the data
+ this.data = $.extend(true, [], data);
+
+ this.parseOptions(options);
+
+ if (this.textColor) {
+ this.target.css('color', this.textColor);
+ }
+ if (this.fontFamily) {
+ this.target.css('font-family', this.fontFamily);
+ }
+ if (this.fontSize) {
+ this.target.css('font-size', this.fontSize);
+ }
+
+ this.title.init();
+ this.legend.init();
+ this._sumy = 0;
+ this._sumx = 0;
+ this.computePlotData();
+ for (var i=0; i<this.series.length; i++) {
+ // set default stacking order for series canvases
+ this.seriesStack.push(i);
+ this.previousSeriesStack.push(i);
+ this.series[i].shadowCanvas._plotDimensions = this._plotDimensions;
+ this.series[i].canvas._plotDimensions = this._plotDimensions;
+ for (var j=0; j<$.jqplot.preSeriesInitHooks.length; j++) {
+ $.jqplot.preSeriesInitHooks[j].call(this.series[i], target, this.data, this.options.seriesDefaults, this.options.series[i], this);
+ }
+ for (var j=0; j<this.preSeriesInitHooks.hooks.length; j++) {
+ this.preSeriesInitHooks.hooks[j].call(this.series[i], target, this.data, this.options.seriesDefaults, this.options.series[i], this);
+ }
+ // this.populatePlotData(this.series[i], i);
+ this.series[i]._plotDimensions = this._plotDimensions;
+ this.series[i].init(i, this.grid.borderWidth, this);
+ for (var j=0; j<$.jqplot.postSeriesInitHooks.length; j++) {
+ $.jqplot.postSeriesInitHooks[j].call(this.series[i], target, this.data, this.options.seriesDefaults, this.options.series[i], this);
+ }
+ for (var j=0; j<this.postSeriesInitHooks.hooks.length; j++) {
+ this.postSeriesInitHooks.hooks[j].call(this.series[i], target, this.data, this.options.seriesDefaults, this.options.series[i], this);
+ }
+ this._sumy += this.series[i]._sumy;
+ this._sumx += this.series[i]._sumx;
+ }
+
+ var name,
+ axis;
+ for (var i=0, l=_axisNames.length; i<l; i++) {
+ name = _axisNames[i];
+ axis = this.axes[name];
+ axis._plotDimensions = this._plotDimensions;
+ axis.init();
+ if (this.axes[name].borderColor == null) {
+ if (name.charAt(0) !== 'x' && axis.useSeriesColor === true && axis.show) {
+ axis.borderColor = axis._series[0].color;
+ }
+ else {
+ axis.borderColor = this.grid.borderColor;
+ }
+ }
+ }
+
+ if (this.sortData) {
+ sortData(this.series);
+ }
+ this.grid.init();
+ this.grid._axes = this.axes;
+
+ this.legend._series = this.series;
+
+ for (var i=0; i<$.jqplot.postInitHooks.length; i++) {
+ $.jqplot.postInitHooks[i].call(this, target, this.data, options);
+ }
+
+ for (var i=0; i<this.postInitHooks.hooks.length; i++) {
+ this.postInitHooks.hooks[i].call(this, target, this.data, options);
+ }
+ };
+
+ // method: resetAxesScale
+ // Reset the specified axes min, max, numberTicks and tickInterval properties to null
+ // or reset these properties on all axes if no list of axes is provided.
+ //
+ // Parameters:
+ // axes - Boolean to reset or not reset all axes or an array or object of axis names to reset.
+ this.resetAxesScale = function(axes, options) {
+ var opts = options || {};
+ var ax = axes || this.axes;
+ if (ax === true) {
+ ax = this.axes;
+ }
+ if ($.isArray(ax)) {
+ for (var i = 0; i < ax.length; i++) {
+ this.axes[ax[i]].resetScale(opts[ax[i]]);
+ }
+ }
+ else if (typeof(ax) === 'object') {
+ for (var name in ax) {
+ this.axes[name].resetScale(opts[name]);
+ }
+ }
+ };
+ // method: reInitialize
+ // reinitialize plot for replotting.
+ // not called directly.
+ this.reInitialize = function (data, opts) {
+ // Plot should be visible and have a height and width.
+ // If plot doesn't have height and width for some
+ // reason, set it by other means. Plot must not have
+ // a display:none attribute, however.
+
+ var options = $.extend(true, {}, this.options, opts);
+
+ var target = this.targetId.substr(1);
+ var tdata = (data == null) ? this.data : data;
+
+ for (var i=0; i<$.jqplot.preInitHooks.length; i++) {
+ $.jqplot.preInitHooks[i].call(this, target, tdata, options);
+ }
+
+ for (var i=0; i<this.preInitHooks.hooks.length; i++) {
+ this.preInitHooks.hooks[i].call(this, target, tdata, options);
+ }
+
+ this._height = this.target.height();
+ this._width = this.target.width();
+
+ if (this._height <=0 || this._width <=0 || !this._height || !this._width) {
+ throw "Target dimension not set";
+ }
+
+ this._plotDimensions.height = this._height;
+ this._plotDimensions.width = this._width;
+ this.grid._plotDimensions = this._plotDimensions;
+ this.title._plotDimensions = this._plotDimensions;
+ this.baseCanvas._plotDimensions = this._plotDimensions;
+ this.eventCanvas._plotDimensions = this._plotDimensions;
+ this.legend._plotDimensions = this._plotDimensions;
+
+ var name,
+ t,
+ j,
+ axis;
+
+ for (var i=0, l=_axisNames.length; i<l; i++) {
+ name = _axisNames[i];
+ axis = this.axes[name];
+
+ // Memory Leaks patch : clear ticks elements
+ t = axis._ticks;
+ for (var j = 0, tlen = t.length; j < tlen; j++) {
+ var el = t[j]._elem;
+ if (el) {
+ // if canvas renderer
+ if ($.jqplot.use_excanvas && window.G_vmlCanvasManager.uninitElement !== undefined) {
+ window.G_vmlCanvasManager.uninitElement(el.get(0));
+ }
+ el.emptyForce();
+ el = null;
+ t._elem = null;
+ }
+ }
+ t = null;
+
+ delete axis.ticks;
+ delete axis._ticks;
+ this.axes[name] = new Axis(name);
+ this.axes[name]._plotWidth = this._width;
+ this.axes[name]._plotHeight = this._height;
+ }
+
+ if (data) {
+ if (options.dataRenderer && $.isFunction(options.dataRenderer)) {
+ if (options.dataRendererOptions) {
+ this.dataRendererOptions = options.dataRendererOptions;
+ }
+ this.dataRenderer = options.dataRenderer;
+ data = this.dataRenderer(data, this, this.dataRendererOptions);
+ }
+
+ // make a copy of the data
+ this.data = $.extend(true, [], data);
+ }
+
+ if (opts) {
+ this.parseOptions(options);
+ }
+
+ this.title._plotWidth = this._width;
+
+ if (this.textColor) {
+ this.target.css('color', this.textColor);
+ }
+ if (this.fontFamily) {
+ this.target.css('font-family', this.fontFamily);
+ }
+ if (this.fontSize) {
+ this.target.css('font-size', this.fontSize);
+ }
+
+ this.title.init();
+ this.legend.init();
+ this._sumy = 0;
+ this._sumx = 0;
+
+ this.seriesStack = [];
+ this.previousSeriesStack = [];
+
+ this.computePlotData();
+ for (var i=0, l=this.series.length; i<l; i++) {
+ // set default stacking order for series canvases
+ this.seriesStack.push(i);
+ this.previousSeriesStack.push(i);
+ this.series[i].shadowCanvas._plotDimensions = this._plotDimensions;
+ this.series[i].canvas._plotDimensions = this._plotDimensions;
+ for (var j=0; j<$.jqplot.preSeriesInitHooks.length; j++) {
+ $.jqplot.preSeriesInitHooks[j].call(this.series[i], target, this.data, this.options.seriesDefaults, this.options.series[i], this);
+ }
+ for (var j=0; j<this.preSeriesInitHooks.hooks.length; j++) {
+ this.preSeriesInitHooks.hooks[j].call(this.series[i], target, this.data, this.options.seriesDefaults, this.options.series[i], this);
+ }
+ // this.populatePlotData(this.series[i], i);
+ this.series[i]._plotDimensions = this._plotDimensions;
+ this.series[i].init(i, this.grid.borderWidth, this);
+ for (var j=0; j<$.jqplot.postSeriesInitHooks.length; j++) {
+ $.jqplot.postSeriesInitHooks[j].call(this.series[i], target, this.data, this.options.seriesDefaults, this.options.series[i], this);
+ }
+ for (var j=0; j<this.postSeriesInitHooks.hooks.length; j++) {
+ this.postSeriesInitHooks.hooks[j].call(this.series[i], target, this.data, this.options.seriesDefaults, this.options.series[i], this);
+ }
+ this._sumy += this.series[i]._sumy;
+ this._sumx += this.series[i]._sumx;
+ }
+
+ for (var i=0, l=_axisNames.length; i<l; i++) {
+ name = _axisNames[i];
+ axis = this.axes[name];
+
+ axis._plotDimensions = this._plotDimensions;
+ axis.init();
+ if (axis.borderColor == null) {
+ if (name.charAt(0) !== 'x' && axis.useSeriesColor === true && axis.show) {
+ axis.borderColor = axis._series[0].color;
+ }
+ else {
+ axis.borderColor = this.grid.borderColor;
+ }
+ }
+ }
+
+ if (this.sortData) {
+ sortData(this.series);
+ }
+ this.grid.init();
+ this.grid._axes = this.axes;
+
+ this.legend._series = this.series;
+
+ for (var i=0, l=$.jqplot.postInitHooks.length; i<l; i++) {
+ $.jqplot.postInitHooks[i].call(this, target, this.data, options);
+ }
+
+ for (var i=0, l=this.postInitHooks.hooks.length; i<l; i++) {
+ this.postInitHooks.hooks[i].call(this, target, this.data, options);
+ }
+ };
+
+
+
+ // method: quickInit
+ //
+ // Quick reinitialization plot for replotting.
+ // Does not parse options ore recreate axes and series.
+ // not called directly.
+ this.quickInit = function () {
+ // Plot should be visible and have a height and width.
+ // If plot doesn't have height and width for some
+ // reason, set it by other means. Plot must not have
+ // a display:none attribute, however.
+
+ this._height = this.target.height();
+ this._width = this.target.width();
+
+ if (this._height <=0 || this._width <=0 || !this._height || !this._width) {
+ throw "Target dimension not set";
+ }
+
+ this._plotDimensions.height = this._height;
+ this._plotDimensions.width = this._width;
+ this.grid._plotDimensions = this._plotDimensions;
+ this.title._plotDimensions = this._plotDimensions;
+ this.baseCanvas._plotDimensions = this._plotDimensions;
+ this.eventCanvas._plotDimensions = this._plotDimensions;
+ this.legend._plotDimensions = this._plotDimensions;
+
+ for (var n in this.axes) {
+ this.axes[n]._plotWidth = this._width;
+ this.axes[n]._plotHeight = this._height;
+ }
+
+ this.title._plotWidth = this._width;
+
+ if (this.textColor) {
+ this.target.css('color', this.textColor);
+ }
+ if (this.fontFamily) {
+ this.target.css('font-family', this.fontFamily);
+ }
+ if (this.fontSize) {
+ this.target.css('font-size', this.fontSize);
+ }
+
+ this._sumy = 0;
+ this._sumx = 0;
+ this.computePlotData();
+ for (var i=0; i<this.series.length; i++) {
+ // this.populatePlotData(this.series[i], i);
+ if (this.series[i]._type === 'line' && this.series[i].renderer.bands.show) {
+ this.series[i].renderer.initBands.call(this.series[i], this.series[i].renderer.options, this);
+ }
+ this.series[i]._plotDimensions = this._plotDimensions;
+ this.series[i].canvas._plotDimensions = this._plotDimensions;
+ //this.series[i].init(i, this.grid.borderWidth);
+ this._sumy += this.series[i]._sumy;
+ this._sumx += this.series[i]._sumx;
+ }
+
+ var name;
+
+ for (var j=0; j<12; j++) {
+ name = _axisNames[j];
+ // Memory Leaks patch : clear ticks elements
+ var t = this.axes[name]._ticks;
+ for (var i = 0; i < t.length; i++) {
+ var el = t[i]._elem;
+ if (el) {
+ // if canvas renderer
+ if ($.jqplot.use_excanvas && window.G_vmlCanvasManager.uninitElement !== undefined) {
+ window.G_vmlCanvasManager.uninitElement(el.get(0));
+ }
+ el.emptyForce();
+ el = null;
+ t._elem = null;
+ }
+ }
+ t = null;
+
+ this.axes[name]._plotDimensions = this._plotDimensions;
+ this.axes[name]._ticks = [];
+ // this.axes[name].renderer.init.call(this.axes[name], {});
+ }
+
+ if (this.sortData) {
+ sortData(this.series);
+ }
+
+ this.grid._axes = this.axes;
+
+ this.legend._series = this.series;
+ };
+
+ // sort the series data in increasing order.
+ function sortData(series) {
+ var d, sd, pd, ppd, ret;
+ for (var i=0; i<series.length; i++) {
+ var check;
+ var bat = [series[i].data, series[i]._stackData, series[i]._plotData, series[i]._prevPlotData];
+ for (var n=0; n<4; n++) {
+ check = true;
+ d = bat[n];
+ if (series[i]._stackAxis == 'x') {
+ for (var j = 0; j < d.length; j++) {
+ if (typeof(d[j][1]) != "number") {
+ check = false;
+ break;
+ }
+ }
+ if (check) {
+ d.sort(function(a,b) { return a[1] - b[1]; });
+ }
+ }
+ else {
+ for (var j = 0; j < d.length; j++) {
+ if (typeof(d[j][0]) != "number") {
+ check = false;
+ break;
+ }
+ }
+ if (check) {
+ d.sort(function(a,b) { return a[0] - b[0]; });
+ }
+ }
+ }
+
+ }
+ }
+
+ this.computePlotData = function() {
+ this._plotData = [];
+ this._stackData = [];
+ var series,
+ index,
+ l;
+
+
+ for (index=0, l=this.series.length; index<l; index++) {
+ series = this.series[index];
+ this._plotData.push([]);
+ this._stackData.push([]);
+ var cd = series.data;
+ this._plotData[index] = $.extend(true, [], cd);
+ this._stackData[index] = $.extend(true, [], cd);
+ series._plotData = this._plotData[index];
+ series._stackData = this._stackData[index];
+ var plotValues = {x:[], y:[]};
+
+ if (this.stackSeries && !series.disableStack) {
+ series._stack = true;
+ ///////////////////////////
+ // have to check for nulls
+ ///////////////////////////
+ var sidx = (series._stackAxis === 'x') ? 0 : 1;
+
+ for (var k=0, cdl=cd.length; k<cdl; k++) {
+ var temp = cd[k][sidx];
+ if (temp == null) {
+ temp = 0;
+ }
+ this._plotData[index][k][sidx] = temp;
+ this._stackData[index][k][sidx] = temp;
+
+ if (index > 0) {
+ for (var j=index; j--;) {
+ var prevval = this._plotData[j][k][sidx];
+ // only need to sum up the stack axis column of data
+ // and only sum if it is of same sign.
+ // if previous series isn't same sign, keep looking
+ // at earlier series untill we find one of same sign.
+ if (temp * prevval >= 0) {
+ this._plotData[index][k][sidx] += prevval;
+ this._stackData[index][k][sidx] += prevval;
+ break;
+ }
+ }
+ }
+ }
+
+ }
+ else {
+ for (var i=0; i<series.data.length; i++) {
+ plotValues.x.push(series.data[i][0]);
+ plotValues.y.push(series.data[i][1]);
+ }
+ this._stackData.push(series.data);
+ this.series[index]._stackData = series.data;
+ this._plotData.push(series.data);
+ series._plotData = series.data;
+ series._plotValues = plotValues;
+ }
+ if (index>0) {
+ series._prevPlotData = this.series[index-1]._plotData;
+ }
+ series._sumy = 0;
+ series._sumx = 0;
+ for (i=series.data.length-1; i>-1; i--) {
+ series._sumy += series.data[i][1];
+ series._sumx += series.data[i][0];
+ }
+ }
+
+ };
+
+ // populate the _stackData and _plotData arrays for the plot and the series.
+ this.populatePlotData = function(series, index) {
+ // if a stacked chart, compute the stacked data
+ this._plotData = [];
+ this._stackData = [];
+ series._stackData = [];
+ series._plotData = [];
+ var plotValues = {x:[], y:[]};
+ if (this.stackSeries && !series.disableStack) {
+ series._stack = true;
+ var sidx = (series._stackAxis === 'x') ? 0 : 1;
+ // var idx = sidx ? 0 : 1;
+ // push the current data into stackData
+ //this._stackData.push(this.series[i].data);
+ var temp = $.extend(true, [], series.data);
+ // create the data that will be plotted for this series
+ var plotdata = $.extend(true, [], series.data);
+ var tempx, tempy, dval, stackval, comparator;
+ // for first series, nothing to add to stackData.
+ for (var j=0; j<index; j++) {
+ var cd = this.series[j].data;
+ for (var k=0; k<cd.length; k++) {
+ dval = cd[k];
+ tempx = (dval[0] != null) ? dval[0] : 0;
+ tempy = (dval[1] != null) ? dval[1] : 0;
+ temp[k][0] += tempx;
+ temp[k][1] += tempy;
+ stackval = (sidx) ? tempy : tempx;
+ // only need to sum up the stack axis column of data
+ // and only sum if it is of same sign.
+ if (series.data[k][sidx] * stackval >= 0) {
+ plotdata[k][sidx] += stackval;
+ }
+ }
+ }
+ for (var i=0; i<plotdata.length; i++) {
+ plotValues.x.push(plotdata[i][0]);
+ plotValues.y.push(plotdata[i][1]);
+ }
+ this._plotData.push(plotdata);
+ this._stackData.push(temp);
+ series._stackData = temp;
+ series._plotData = plotdata;
+ series._plotValues = plotValues;
+ }
+ else {
+ for (var i=0; i<series.data.length; i++) {
+ plotValues.x.push(series.data[i][0]);
+ plotValues.y.push(series.data[i][1]);
+ }
+ this._stackData.push(series.data);
+ this.series[index]._stackData = series.data;
+ this._plotData.push(series.data);
+ series._plotData = series.data;
+ series._plotValues = plotValues;
+ }
+ if (index>0) {
+ series._prevPlotData = this.series[index-1]._plotData;
+ }
+ series._sumy = 0;
+ series._sumx = 0;
+ for (i=series.data.length-1; i>-1; i--) {
+ series._sumy += series.data[i][1];
+ series._sumx += series.data[i][0];
+ }
+ };
+
+ // function to safely return colors from the color array and wrap around at the end.
+ this.getNextSeriesColor = (function(t) {
+ var idx = 0;
+ var sc = t.seriesColors;
+
+ return function () {
+ if (idx < sc.length) {
+ return sc[idx++];
+ }
+ else {
+ idx = 0;
+ return sc[idx++];
+ }
+ };
+ })(this);
+
+ this.parseOptions = function(options){
+ for (var i=0; i<this.preParseOptionsHooks.hooks.length; i++) {
+ this.preParseOptionsHooks.hooks[i].call(this, options);
+ }
+ for (var i=0; i<$.jqplot.preParseOptionsHooks.length; i++) {
+ $.jqplot.preParseOptionsHooks[i].call(this, options);
+ }
+ this.options = $.extend(true, {}, this.defaults, options);
+ var opts = this.options;
+ this.animate = opts.animate;
+ this.animateReplot = opts.animateReplot;
+ this.stackSeries = opts.stackSeries;
+ if ($.isPlainObject(opts.fillBetween)) {
+
+ var temp = ['series1', 'series2', 'color', 'baseSeries', 'fill'],
+ tempi;
+
+ for (var i=0, l=temp.length; i<l; i++) {
+ tempi = temp[i];
+ if (opts.fillBetween[tempi] != null) {
+ this.fillBetween[tempi] = opts.fillBetween[tempi];
+ }
+ }
+ }
+
+ if (opts.seriesColors) {
+ this.seriesColors = opts.seriesColors;
+ }
+ if (opts.negativeSeriesColors) {
+ this.negativeSeriesColors = opts.negativeSeriesColors;
+ }
+ if (opts.captureRightClick) {
+ this.captureRightClick = opts.captureRightClick;
+ }
+ this.defaultAxisStart = (options && options.defaultAxisStart != null) ? options.defaultAxisStart : this.defaultAxisStart;
+ this.colorGenerator.setColors(this.seriesColors);
+ this.negativeColorGenerator.setColors(this.negativeSeriesColors);
+ // var cg = new this.colorGenerator(this.seriesColors);
+ // var ncg = new this.colorGenerator(this.negativeSeriesColors);
+ // this._gridPadding = this.options.gridPadding;
+ $.extend(true, this._gridPadding, opts.gridPadding);
+ this.sortData = (opts.sortData != null) ? opts.sortData : this.sortData;
+ for (var i=0; i<12; i++) {
+ var n = _axisNames[i];
+ var axis = this.axes[n];
+ axis._options = $.extend(true, {}, opts.axesDefaults, opts.axes[n]);
+ $.extend(true, axis, opts.axesDefaults, opts.axes[n]);
+ axis._plotWidth = this._width;
+ axis._plotHeight = this._height;
+ }
+ // if (this.data.length == 0) {
+ // this.data = [];
+ // for (var i=0; i<this.options.series.length; i++) {
+ // this.data.push(this.options.series.data);
+ // }
+ // }
+
+ var normalizeData = function(data, dir, start) {
+ // return data as an array of point arrays,
+ // in form [[x1,y1...], [x2,y2...], ...]
+ var temp = [];
+ var i, l;
+ dir = dir || 'vertical';
+ if (!$.isArray(data[0])) {
+ // we have a series of scalars. One line with just y values.
+ // turn the scalar list of data into a data array of form:
+ // [[1, data[0]], [2, data[1]], ...]
+ for (i=0, l=data.length; i<l; i++) {
+ if (dir == 'vertical') {
+ temp.push([start + i, data[i]]);
+ }
+ else {
+ temp.push([data[i], start+i]);
+ }
+ }
+ }
+ else {
+ // we have a properly formatted data series, copy it.
+ $.extend(true, temp, data);
+ }
+ return temp;
+ };
+
+ var colorIndex = 0;
+ this.series = [];
+ for (var i=0; i<this.data.length; i++) {
+ var sopts = $.extend(true, {index: i}, {seriesColors:this.seriesColors, negativeSeriesColors:this.negativeSeriesColors}, this.options.seriesDefaults, this.options.series[i], {rendererOptions:{animation:{show: this.animate}}});
+ // pass in options in case something needs set prior to initialization.
+ var temp = new Series(sopts);
+ for (var j=0; j<$.jqplot.preParseSeriesOptionsHooks.length; j++) {
+ $.jqplot.preParseSeriesOptionsHooks[j].call(temp, this.options.seriesDefaults, this.options.series[i]);
+ }
+ for (var j=0; j<this.preParseSeriesOptionsHooks.hooks.length; j++) {
+ this.preParseSeriesOptionsHooks.hooks[j].call(temp, this.options.seriesDefaults, this.options.series[i]);
+ }
+ // Now go back and apply the options to the series. Really should just do this during initializaiton, but don't want to
+ // mess up preParseSeriesOptionsHooks at this point.
+ $.extend(true, temp, sopts);
+ var dir = 'vertical';
+ if (temp.renderer === $.jqplot.BarRenderer && temp.rendererOptions && temp.rendererOptions.barDirection == 'horizontal') {
+ dir = 'horizontal';
+ temp._stackAxis = 'x';
+ temp._primaryAxis = '_yaxis';
+ }
+ temp.data = normalizeData(this.data[i], dir, this.defaultAxisStart);
+ switch (temp.xaxis) {
+ case 'xaxis':
+ temp._xaxis = this.axes.xaxis;
+ break;
+ case 'x2axis':
+ temp._xaxis = this.axes.x2axis;
+ break;
+ default:
+ break;
+ }
+ temp._yaxis = this.axes[temp.yaxis];
+ temp._xaxis._series.push(temp);
+ temp._yaxis._series.push(temp);
+ if (temp.show) {
+ temp._xaxis.show = true;
+ temp._yaxis.show = true;
+ }
+ else {
+ if (temp._xaxis.scaleToHiddenSeries) {
+ temp._xaxis.show = true;
+ }
+ if (temp._yaxis.scaleToHiddenSeries) {
+ temp._yaxis.show = true;
+ }
+ }
+
+ // // parse the renderer options and apply default colors if not provided
+ // if (!temp.color && temp.show != false) {
+ // temp.color = cg.next();
+ // colorIndex = cg.getIndex() - 1;;
+ // }
+ // if (!temp.negativeColor && temp.show != false) {
+ // temp.negativeColor = ncg.get(colorIndex);
+ // ncg.setIndex(colorIndex);
+ // }
+ if (!temp.label) {
+ temp.label = 'Series '+ (i+1).toString();
+ }
+ // temp.rendererOptions.show = temp.show;
+ // $.extend(true, temp.renderer, {color:this.seriesColors[i]}, this.rendererOptions);
+ this.series.push(temp);
+ for (var j=0; j<$.jqplot.postParseSeriesOptionsHooks.length; j++) {
+ $.jqplot.postParseSeriesOptionsHooks[j].call(this.series[i], this.options.seriesDefaults, this.options.series[i]);
+ }
+ for (var j=0; j<this.postParseSeriesOptionsHooks.hooks.length; j++) {
+ this.postParseSeriesOptionsHooks.hooks[j].call(this.series[i], this.options.seriesDefaults, this.options.series[i]);
+ }
+ }
+
+ // copy the grid and title options into this object.
+ $.extend(true, this.grid, this.options.grid);
+ // if axis border properties aren't set, set default.
+ for (var i=0, l=_axisNames.length; i<l; i++) {
+ var n = _axisNames[i];
+ var axis = this.axes[n];
+ if (axis.borderWidth == null) {
+ axis.borderWidth =this.grid.borderWidth;
+ }
+ }
+
+ if (typeof this.options.title == 'string') {
+ this.title.text = this.options.title;
+ }
+ else if (typeof this.options.title == 'object') {
+ $.extend(true, this.title, this.options.title);
+ }
+ this.title._plotWidth = this._width;
+ this.legend.setOptions(this.options.legend);
+
+ for (var i=0; i<$.jqplot.postParseOptionsHooks.length; i++) {
+ $.jqplot.postParseOptionsHooks[i].call(this, options);
+ }
+ for (var i=0; i<this.postParseOptionsHooks.hooks.length; i++) {
+ this.postParseOptionsHooks.hooks[i].call(this, options);
+ }
+ };
+
+ // method: destroy
+ // Releases all resources occupied by the plot
+ this.destroy = function() {
+ this.canvasManager.freeAllCanvases();
+ if (this.eventCanvas && this.eventCanvas._elem) {
+ this.eventCanvas._elem.unbind();
+ }
+ // Couple of posts on Stack Overflow indicate that empty() doesn't
+ // always cear up the dom and release memory. Sometimes setting
+ // innerHTML property to null is needed. Particularly on IE, may
+ // have to directly set it to null, bypassing $.
+ this.target.empty();
+
+ this.target[0].innerHTML = '';
+ };
+
+ // method: replot
+ // Does a reinitialization of the plot followed by
+ // a redraw. Method could be used to interactively
+ // change plot characteristics and then replot.
+ //
+ // Parameters:
+ // options - Options used for replotting.
+ //
+ // Properties:
+ // clear - false to not clear (empty) the plot container before replotting (default: true).
+ // resetAxes - true to reset all axes min, max, numberTicks and tickInterval setting so axes will rescale themselves.
+ // optionally pass in list of axes to reset (e.g. ['xaxis', 'y2axis']) (default: false).
+ this.replot = function(options) {
+ var opts = options || {};
+ var data = opts.data || null;
+ var clear = (opts.clear === false) ? false : true;
+ var resetAxes = opts.resetAxes || false;
+ delete opts.data;
+ delete opts.clear;
+ delete opts.resetAxes;
+
+ this.target.trigger('jqplotPreReplot');
+
+ if (clear) {
+ this.destroy();
+ }
+ // if have data or other options, full reinit.
+ // otherwise, quickinit.
+ if (data || !$.isEmptyObject(opts)) {
+ this.reInitialize(data, opts);
+ }
+ else {
+ this.quickInit();
+ }
+
+ if (resetAxes) {
+ this.resetAxesScale(resetAxes, opts.axes);
+ }
+ this.draw();
+ this.target.trigger('jqplotPostReplot');
+ };
+
+ // method: redraw
+ // Empties the plot target div and redraws the plot.
+ // This enables plot data and properties to be changed
+ // and then to comletely clear the plot and redraw.
+ // redraw *will not* reinitialize any plot elements.
+ // That is, axes will not be autoscaled and defaults
+ // will not be reapplied to any plot elements. redraw
+ // is used primarily with zooming.
+ //
+ // Parameters:
+ // clear - false to not clear (empty) the plot container before redrawing (default: true).
+ this.redraw = function(clear) {
+ clear = (clear != null) ? clear : true;
+ this.target.trigger('jqplotPreRedraw');
+ if (clear) {
+ this.canvasManager.freeAllCanvases();
+ this.eventCanvas._elem.unbind();
+ // Dont think I bind any events to the target, this shouldn't be necessary.
+ // It will remove user's events.
+ // this.target.unbind();
+ this.target.empty();
+ }
+ for (var ax in this.axes) {
+ this.axes[ax]._ticks = [];
+ }
+ this.computePlotData();
+ // for (var i=0; i<this.series.length; i++) {
+ // this.populatePlotData(this.series[i], i);
+ // }
+ this._sumy = 0;
+ this._sumx = 0;
+ for (var i=0, tsl = this.series.length; i<tsl; i++) {
+ this._sumy += this.series[i]._sumy;
+ this._sumx += this.series[i]._sumx;
+ }
+ this.draw();
+ this.target.trigger('jqplotPostRedraw');
+ };
+
+ // method: draw
+ // Draws all elements of the plot into the container.
+ // Does not clear the container before drawing.
+ this.draw = function(){
+ if (this.drawIfHidden || this.target.is(':visible')) {
+ this.target.trigger('jqplotPreDraw');
+ var i,
+ j,
+ l,
+ tempseries;
+ for (i=0, l=$.jqplot.preDrawHooks.length; i<l; i++) {
+ $.jqplot.preDrawHooks[i].call(this);
+ }
+ for (i=0, l=this.preDrawHooks.length; i<l; i++) {
+ this.preDrawHooks.hooks[i].apply(this, this.preDrawSeriesHooks.args[i]);
+ }
+ // create an underlying canvas to be used for special features.
+ this.target.append(this.baseCanvas.createElement({left:0, right:0, top:0, bottom:0}, 'jqplot-base-canvas', null, this));
+ this.baseCanvas.setContext();
+ this.target.append(this.title.draw());
+ this.title.pack({top:0, left:0});
+
+ // make room for the legend between the grid and the edge.
+ // pass a dummy offsets object and a reference to the plot.
+ var legendElem = this.legend.draw({}, this);
+
+ var gridPadding = {top:0, left:0, bottom:0, right:0};
+
+ if (this.legend.placement == "outsideGrid") {
+ // temporarily append the legend to get dimensions
+ this.target.append(legendElem);
+ switch (this.legend.location) {
+ case 'n':
+ gridPadding.top += this.legend.getHeight();
+ break;
+ case 's':
+ gridPadding.bottom += this.legend.getHeight();
+ break;
+ case 'ne':
+ case 'e':
+ case 'se':
+ gridPadding.right += this.legend.getWidth();
+ break;
+ case 'nw':
+ case 'w':
+ case 'sw':
+ gridPadding.left += this.legend.getWidth();
+ break;
+ default: // same as 'ne'
+ gridPadding.right += this.legend.getWidth();
+ break;
+ }
+ legendElem = legendElem.detach();
+ }
+
+ var ax = this.axes;
+ var name;
+ // draw the yMidAxis first, so xaxis of pyramid chart can adjust itself if needed.
+ for (i=0; i<12; i++) {
+ name = _axisNames[i];
+ this.target.append(ax[name].draw(this.baseCanvas._ctx, this));
+ ax[name].set();
+ }
+ if (ax.yaxis.show) {
+ gridPadding.left += ax.yaxis.getWidth();
+ }
+ var ra = ['y2axis', 'y3axis', 'y4axis', 'y5axis', 'y6axis', 'y7axis', 'y8axis', 'y9axis'];
+ var rapad = [0, 0, 0, 0, 0, 0, 0, 0];
+ var gpr = 0;
+ var n;
+ for (n=0; n<8; n++) {
+ if (ax[ra[n]].show) {
+ gpr += ax[ra[n]].getWidth();
+ rapad[n] = gpr;
+ }
+ }
+ gridPadding.right += gpr;
+ if (ax.x2axis.show) {
+ gridPadding.top += ax.x2axis.getHeight();
+ }
+ if (this.title.show) {
+ gridPadding.top += this.title.getHeight();
+ }
+ if (ax.xaxis.show) {
+ gridPadding.bottom += ax.xaxis.getHeight();
+ }
+
+ // end of gridPadding adjustments.
+
+ // if user passed in gridDimensions option, check against calculated gridPadding
+ if (this.options.gridDimensions && $.isPlainObject(this.options.gridDimensions)) {
+ var gdw = parseInt(this.options.gridDimensions.width, 10) || 0;
+ var gdh = parseInt(this.options.gridDimensions.height, 10) || 0;
+ var widthAdj = (this._width - gridPadding.left - gridPadding.right - gdw)/2;
+ var heightAdj = (this._height - gridPadding.top - gridPadding.bottom - gdh)/2;
+
+ if (heightAdj >= 0 && widthAdj >= 0) {
+ gridPadding.top += heightAdj;
+ gridPadding.bottom += heightAdj;
+ gridPadding.left += widthAdj;
+ gridPadding.right += widthAdj;
+ }
+ }
+ var arr = ['top', 'bottom', 'left', 'right'];
+ for (var n in arr) {
+ if (this._gridPadding[arr[n]] == null && gridPadding[arr[n]] > 0) {
+ this._gridPadding[arr[n]] = gridPadding[arr[n]];
+ }
+ else if (this._gridPadding[arr[n]] == null) {
+ this._gridPadding[arr[n]] = this._defaultGridPadding[arr[n]];
+ }
+ }
+
+ var legendPadding = this._gridPadding;
+
+ if (this.legend.placement === 'outsideGrid') {
+ legendPadding = {top:this.title.getHeight(), left: 0, right: 0, bottom: 0};
+ if (this.legend.location === 's') {
+ legendPadding.left = this._gridPadding.left;
+ legendPadding.right = this._gridPadding.right;
+ }
+ }
+
+ ax.xaxis.pack({position:'absolute', bottom:this._gridPadding.bottom - ax.xaxis.getHeight(), left:0, width:this._width}, {min:this._gridPadding.left, max:this._width - this._gridPadding.right});
+ ax.yaxis.pack({position:'absolute', top:0, left:this._gridPadding.left - ax.yaxis.getWidth(), height:this._height}, {min:this._height - this._gridPadding.bottom, max: this._gridPadding.top});
+ ax.x2axis.pack({position:'absolute', top:this._gridPadding.top - ax.x2axis.getHeight(), left:0, width:this._width}, {min:this._gridPadding.left, max:this._width - this._gridPadding.right});
+ for (i=8; i>0; i--) {
+ ax[ra[i-1]].pack({position:'absolute', top:0, right:this._gridPadding.right - rapad[i-1]}, {min:this._height - this._gridPadding.bottom, max: this._gridPadding.top});
+ }
+ var ltemp = (this._width - this._gridPadding.left - this._gridPadding.right)/2.0 + this._gridPadding.left - ax.yMidAxis.getWidth()/2.0;
+ ax.yMidAxis.pack({position:'absolute', top:0, left:ltemp, zIndex:9, textAlign: 'center'}, {min:this._height - this._gridPadding.bottom, max: this._gridPadding.top});
+
+ this.target.append(this.grid.createElement(this._gridPadding, this));
+ this.grid.draw();
+
+ var series = this.series;
+ var seriesLength = series.length;
+ // put the shadow canvases behind the series canvases so shadows don't overlap on stacked bars.
+ for (i=0, l=seriesLength; i<l; i++) {
+ // draw series in order of stacking. This affects only
+ // order in which canvases are added to dom.
+ j = this.seriesStack[i];
+ this.target.append(series[j].shadowCanvas.createElement(this._gridPadding, 'jqplot-series-shadowCanvas', null, this));
+ series[j].shadowCanvas.setContext();
+ series[j].shadowCanvas._elem.data('seriesIndex', j);
+ }
+
+ for (i=0, l=seriesLength; i<l; i++) {
+ // draw series in order of stacking. This affects only
+ // order in which canvases are added to dom.
+ j = this.seriesStack[i];
+ this.target.append(series[j].canvas.createElement(this._gridPadding, 'jqplot-series-canvas', null, this));
+ series[j].canvas.setContext();
+ series[j].canvas._elem.data('seriesIndex', j);
+ }
+ // Need to use filled canvas to capture events in IE.
+ // Also, canvas seems to block selection of other elements in document on FF.
+ this.target.append(this.eventCanvas.createElement(this._gridPadding, 'jqplot-event-canvas', null, this));
+ this.eventCanvas.setContext();
+ this.eventCanvas._ctx.fillStyle = 'rgba(0,0,0,0)';
+ this.eventCanvas._ctx.fillRect(0,0,this.eventCanvas._ctx.canvas.width, this.eventCanvas._ctx.canvas.height);
+
+ // bind custom event handlers to regular events.
+ this.bindCustomEvents();
+
+ // draw legend before series if the series needs to know the legend dimensions.
+ if (this.legend.preDraw) {
+ this.eventCanvas._elem.before(legendElem);
+ this.legend.pack(legendPadding);
+ if (this.legend._elem) {
+ this.drawSeries({legendInfo:{location:this.legend.location, placement:this.legend.placement, width:this.legend.getWidth(), height:this.legend.getHeight(), xoffset:this.legend.xoffset, yoffset:this.legend.yoffset}});
+ }
+ else {
+ this.drawSeries();
+ }
+ }
+ else { // draw series before legend
+ this.drawSeries();
+ if (seriesLength) {
+ $(series[seriesLength-1].canvas._elem).after(legendElem);
+ }
+ this.legend.pack(legendPadding);
+ }
+
+ // register event listeners on the overlay canvas
+ for (var i=0, l=$.jqplot.eventListenerHooks.length; i<l; i++) {
+ // in the handler, this will refer to the eventCanvas dom element.
+ // make sure there are references back into plot objects.
+ this.eventCanvas._elem.bind($.jqplot.eventListenerHooks[i][0], {plot:this}, $.jqplot.eventListenerHooks[i][1]);
+ }
+
+ // register event listeners on the overlay canvas
+ for (var i=0, l=this.eventListenerHooks.hooks.length; i<l; i++) {
+ // in the handler, this will refer to the eventCanvas dom element.
+ // make sure there are references back into plot objects.
+ this.eventCanvas._elem.bind(this.eventListenerHooks.hooks[i][0], {plot:this}, this.eventListenerHooks.hooks[i][1]);
+ }
+
+ var fb = this.fillBetween;
+ if (fb.fill && fb.series1 !== fb.series2 && fb.series1 < seriesLength && fb.series2 < seriesLength && series[fb.series1]._type === 'line' && series[fb.series2]._type === 'line') {
+ this.doFillBetweenLines();
+ }
+
+ for (var i=0, l=$.jqplot.postDrawHooks.length; i<l; i++) {
+ $.jqplot.postDrawHooks[i].call(this);
+ }
+
+ for (var i=0, l=this.postDrawHooks.hooks.length; i<l; i++) {
+ this.postDrawHooks.hooks[i].apply(this, this.postDrawHooks.args[i]);
+ }
+
+ if (this.target.is(':visible')) {
+ this._drawCount += 1;
+ }
+
+ var temps,
+ tempr,
+ sel,
+ _els;
+ // ughh. ideally would hide all series then show them.
+ for (i=0, l=seriesLength; i<l; i++) {
+ temps = series[i];
+ tempr = temps.renderer;
+ sel = '.jqplot-point-label.jqplot-series-'+i;
+ if (tempr.animation && tempr.animation._supported && tempr.animation.show && (this._drawCount < 2 || this.animateReplot)) {
+ _els = this.target.find(sel);
+ _els.stop(true, true).hide();
+ temps.canvas._elem.stop(true, true).hide();
+ temps.shadowCanvas._elem.stop(true, true).hide();
+ temps.canvas._elem.jqplotEffect('blind', {mode: 'show', direction: tempr.animation.direction}, tempr.animation.speed);
+ temps.shadowCanvas._elem.jqplotEffect('blind', {mode: 'show', direction: tempr.animation.direction}, tempr.animation.speed);
+ _els.fadeIn(tempr.animation.speed*0.8);
+ }
+ }
+ _els = null;
+
+ this.target.trigger('jqplotPostDraw', [this]);
+ }
+ };
+
+ jqPlot.prototype.doFillBetweenLines = function () {
+ var fb = this.fillBetween;
+ var sid1 = fb.series1;
+ var sid2 = fb.series2;
+ // first series should always be lowest index
+ var id1 = (sid1 < sid2) ? sid1 : sid2;
+ var id2 = (sid2 > sid1) ? sid2 : sid1;
+
+ var series1 = this.series[id1];
+ var series2 = this.series[id2];
+
+ if (series2.renderer.smooth) {
+ var tempgd = series2.renderer._smoothedData.slice(0).reverse();
+ }
+ else {
+ var tempgd = series2.gridData.slice(0).reverse();
+ }
+
+ if (series1.renderer.smooth) {
+ var gd = series1.renderer._smoothedData.concat(tempgd);
+ }
+ else {
+ var gd = series1.gridData.concat(tempgd);
+ }
+
+ var color = (fb.color !== null) ? fb.color : this.series[sid1].fillColor;
+ var baseSeries = (fb.baseSeries !== null) ? fb.baseSeries : id1;
+
+ // now apply a fill to the shape on the lower series shadow canvas,
+ // so it is behind both series.
+ var sr = this.series[baseSeries].renderer.shapeRenderer;
+ var opts = {fillStyle: color, fill: true, closePath: true};
+ sr.draw(series1.shadowCanvas._ctx, gd, opts);
+ };
+
+ this.bindCustomEvents = function() {
+ this.eventCanvas._elem.bind('click', {plot:this}, this.onClick);
+ this.eventCanvas._elem.bind('dblclick', {plot:this}, this.onDblClick);
+ this.eventCanvas._elem.bind('mousedown', {plot:this}, this.onMouseDown);
+ this.eventCanvas._elem.bind('mousemove', {plot:this}, this.onMouseMove);
+ this.eventCanvas._elem.bind('mouseenter', {plot:this}, this.onMouseEnter);
+ this.eventCanvas._elem.bind('mouseleave', {plot:this}, this.onMouseLeave);
+ if (this.captureRightClick) {
+ this.eventCanvas._elem.bind('mouseup', {plot:this}, this.onRightClick);
+ this.eventCanvas._elem.get(0).oncontextmenu = function() {
+ return false;
+ };
+ }
+ else {
+ this.eventCanvas._elem.bind('mouseup', {plot:this}, this.onMouseUp);
+ }
+ };
+
+ function getEventPosition(ev) {
+ var plot = ev.data.plot;
+ var go = plot.eventCanvas._elem.offset();
+ var gridPos = {x:ev.pageX - go.left, y:ev.pageY - go.top};
+ var dataPos = {xaxis:null, yaxis:null, x2axis:null, y2axis:null, y3axis:null, y4axis:null, y5axis:null, y6axis:null, y7axis:null, y8axis:null, y9axis:null, yMidAxis:null};
+ var an = ['xaxis', 'yaxis', 'x2axis', 'y2axis', 'y3axis', 'y4axis', 'y5axis', 'y6axis', 'y7axis', 'y8axis', 'y9axis', 'yMidAxis'];
+ var ax = plot.axes;
+ var n, axis;
+ for (n=11; n>0; n--) {
+ axis = an[n-1];
+ if (ax[axis].show) {
+ dataPos[axis] = ax[axis].series_p2u(gridPos[axis.charAt(0)]);
+ }
+ }
+
+ return {offsets:go, gridPos:gridPos, dataPos:dataPos};
+ }
+
+
+ // function to check if event location is over a area area
+ function checkIntersection(gridpos, plot) {
+ var series = plot.series;
+ var i, j, k, s, r, x, y, theta, sm, sa, minang, maxang;
+ var d0, d, p, pp, points, bw, hp;
+ var threshold, t;
+ for (k=plot.seriesStack.length-1; k>=0; k--) {
+ i = plot.seriesStack[k];
+ s = series[i];
+ hp = s._highlightThreshold;
+ switch (s.renderer.constructor) {
+ case $.jqplot.BarRenderer:
+ x = gridpos.x;
+ y = gridpos.y;
+ for (j=0; j<s._barPoints.length; j++) {
+ points = s._barPoints[j];
+ p = s.gridData[j];
+ if (x>points[0][0] && x<points[2][0] && y>points[2][1] && y<points[0][1]) {
+ return {seriesIndex:s.index, pointIndex:j, gridData:p, data:s.data[j], points:s._barPoints[j]};
+ }
+ }
+ break;
+ case $.jqplot.PyramidRenderer:
+ x = gridpos.x;
+ y = gridpos.y;
+ for (j=0; j<s._barPoints.length; j++) {
+ points = s._barPoints[j];
+ p = s.gridData[j];
+ if (x > points[0][0] + hp[0][0] && x < points[2][0] + hp[2][0] && y > points[2][1] && y < points[0][1]) {
+ return {seriesIndex:s.index, pointIndex:j, gridData:p, data:s.data[j], points:s._barPoints[j]};
+ }
+ }
+ break;
+
+ case $.jqplot.DonutRenderer:
+ sa = s.startAngle/180*Math.PI;
+ x = gridpos.x - s._center[0];
+ y = gridpos.y - s._center[1];
+ r = Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2));
+ if (x > 0 && -y >= 0) {
+ theta = 2*Math.PI - Math.atan(-y/x);
+ }
+ else if (x > 0 && -y < 0) {
+ theta = -Math.atan(-y/x);
+ }
+ else if (x < 0) {
+ theta = Math.PI - Math.atan(-y/x);
+ }
+ else if (x == 0 && -y > 0) {
+ theta = 3*Math.PI/2;
+ }
+ else if (x == 0 && -y < 0) {
+ theta = Math.PI/2;
+ }
+ else if (x == 0 && y == 0) {
+ theta = 0;
+ }
+ if (sa) {
+ theta -= sa;
+ if (theta < 0) {
+ theta += 2*Math.PI;
+ }
+ else if (theta > 2*Math.PI) {
+ theta -= 2*Math.PI;
+ }
+ }
+
+ sm = s.sliceMargin/180*Math.PI;
+ if (r < s._radius && r > s._innerRadius) {
+ for (j=0; j<s.gridData.length; j++) {
+ minang = (j>0) ? s.gridData[j-1][1]+sm : sm;
+ maxang = s.gridData[j][1];
+ if (theta > minang && theta < maxang) {
+ return {seriesIndex:s.index, pointIndex:j, gridData:s.gridData[j], data:s.data[j]};
+ }
+ }
+ }
+ break;
+
+ case $.jqplot.PieRenderer:
+ sa = s.startAngle/180*Math.PI;
+ x = gridpos.x - s._center[0];
+ y = gridpos.y - s._center[1];
+ r = Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2));
+ if (x > 0 && -y >= 0) {
+ theta = 2*Math.PI - Math.atan(-y/x);
+ }
+ else if (x > 0 && -y < 0) {
+ theta = -Math.atan(-y/x);
+ }
+ else if (x < 0) {
+ theta = Math.PI - Math.atan(-y/x);
+ }
+ else if (x == 0 && -y > 0) {
+ theta = 3*Math.PI/2;
+ }
+ else if (x == 0 && -y < 0) {
+ theta = Math.PI/2;
+ }
+ else if (x == 0 && y == 0) {
+ theta = 0;
+ }
+ if (sa) {
+ theta -= sa;
+ if (theta < 0) {
+ theta += 2*Math.PI;
+ }
+ else if (theta > 2*Math.PI) {
+ theta -= 2*Math.PI;
+ }
+ }
+
+ sm = s.sliceMargin/180*Math.PI;
+ if (r < s._radius) {
+ for (j=0; j<s.gridData.length; j++) {
+ minang = (j>0) ? s.gridData[j-1][1]+sm : sm;
+ maxang = s.gridData[j][1];
+ if (theta > minang && theta < maxang) {
+ return {seriesIndex:s.index, pointIndex:j, gridData:s.gridData[j], data:s.data[j]};
+ }
+ }
+ }
+ break;
+
+ case $.jqplot.BubbleRenderer:
+ x = gridpos.x;
+ y = gridpos.y;
+ var ret = null;
+
+ if (s.show) {
+ for (var j=0; j<s.gridData.length; j++) {
+ p = s.gridData[j];
+ d = Math.sqrt( (x-p[0]) * (x-p[0]) + (y-p[1]) * (y-p[1]) );
+ if (d <= p[2] && (d <= d0 || d0 == null)) {
+ d0 = d;
+ ret = {seriesIndex: i, pointIndex:j, gridData:p, data:s.data[j]};
+ }
+ }
+ if (ret != null) {
+ return ret;
+ }
+ }
+ break;
+
+ case $.jqplot.FunnelRenderer:
+ x = gridpos.x;
+ y = gridpos.y;
+ var v = s._vertices,
+ vfirst = v[0],
+ vlast = v[v.length-1],
+ lex,
+ rex,
+ cv;
+
+ // equations of right and left sides, returns x, y values given height of section (y value and 2 points)
+
+ function findedge (l, p1 , p2) {
+ var m = (p1[1] - p2[1])/(p1[0] - p2[0]);
+ var b = p1[1] - m*p1[0];
+ var y = l + p1[1];
+
+ return [(y - b)/m, y];
+ }
+
+ // check each section
+ lex = findedge(y, vfirst[0], vlast[3]);
+ rex = findedge(y, vfirst[1], vlast[2]);
+ for (j=0; j<v.length; j++) {
+ cv = v[j];
+ if (y >= cv[0][1] && y <= cv[3][1] && x >= lex[0] && x <= rex[0]) {
+ return {seriesIndex:s.index, pointIndex:j, gridData:null, data:s.data[j]};
+ }
+ }
+ break;
+
+ case $.jqplot.LineRenderer:
+ x = gridpos.x;
+ y = gridpos.y;
+ r = s.renderer;
+ if (s.show) {
+ if ((s.fill || (s.renderer.bands.show && s.renderer.bands.fill)) && (!plot.plugins.highlighter || !plot.plugins.highlighter.show)) {
+ // first check if it is in bounding box
+ var inside = false;
+ if (x>s._boundingBox[0][0] && x<s._boundingBox[1][0] && y>s._boundingBox[1][1] && y<s._boundingBox[0][1]) {
+ // now check the crossing number
+
+ var numPoints = s._areaPoints.length;
+ var ii;
+ var j = numPoints-1;
+
+ for(var ii=0; ii < numPoints; ii++) {
+ var vertex1 = [s._areaPoints[ii][0], s._areaPoints[ii][1]];
+ var vertex2 = [s._areaPoints[j][0], s._areaPoints[j][1]];
+
+ if (vertex1[1] < y && vertex2[1] >= y || vertex2[1] < y && vertex1[1] >= y) {
+ if (vertex1[0] + (y - vertex1[1]) / (vertex2[1] - vertex1[1]) * (vertex2[0] - vertex1[0]) < x) {
+ inside = !inside;
+ }
+ }
+
+ j = ii;
+ }
+ }
+ if (inside) {
+ return {seriesIndex:i, pointIndex:null, gridData:s.gridData, data:s.data, points:s._areaPoints};
+ }
+ break;
+
+ }
+
+ else {
+ t = s.markerRenderer.size/2+s.neighborThreshold;
+ threshold = (t > 0) ? t : 0;
+ for (var j=0; j<s.gridData.length; j++) {
+ p = s.gridData[j];
+ // neighbor looks different to OHLC chart.
+ if (r.constructor == $.jqplot.OHLCRenderer) {
+ if (r.candleStick) {
+ var yp = s._yaxis.series_u2p;
+ if (x >= p[0]-r._bodyWidth/2 && x <= p[0]+r._bodyWidth/2 && y >= yp(s.data[j][2]) && y <= yp(s.data[j][3])) {
+ return {seriesIndex: i, pointIndex:j, gridData:p, data:s.data[j]};
+ }
+ }
+ // if an open hi low close chart
+ else if (!r.hlc){
+ var yp = s._yaxis.series_u2p;
+ if (x >= p[0]-r._tickLength && x <= p[0]+r._tickLength && y >= yp(s.data[j][2]) && y <= yp(s.data[j][3])) {
+ return {seriesIndex: i, pointIndex:j, gridData:p, data:s.data[j]};
+ }
+ }
+ // a hi low close chart
+ else {
+ var yp = s._yaxis.series_u2p;
+ if (x >= p[0]-r._tickLength && x <= p[0]+r._tickLength && y >= yp(s.data[j][1]) && y <= yp(s.data[j][2])) {
+ return {seriesIndex: i, pointIndex:j, gridData:p, data:s.data[j]};
+ }
+ }
+
+ }
+ else if (p[0] != null && p[1] != null){
+ d = Math.sqrt( (x-p[0]) * (x-p[0]) + (y-p[1]) * (y-p[1]) );
+ if (d <= threshold && (d <= d0 || d0 == null)) {
+ d0 = d;
+ return {seriesIndex: i, pointIndex:j, gridData:p, data:s.data[j]};
+ }
+ }
+ }
+ }
+ }
+ break;
+
+ default:
+ x = gridpos.x;
+ y = gridpos.y;
+ r = s.renderer;
+ if (s.show) {
+ t = s.markerRenderer.size/2+s.neighborThreshold;
+ threshold = (t > 0) ? t : 0;
+ for (var j=0; j<s.gridData.length; j++) {
+ p = s.gridData[j];
+ // neighbor looks different to OHLC chart.
+ if (r.constructor == $.jqplot.OHLCRenderer) {
+ if (r.candleStick) {
+ var yp = s._yaxis.series_u2p;
+ if (x >= p[0]-r._bodyWidth/2 && x <= p[0]+r._bodyWidth/2 && y >= yp(s.data[j][2]) && y <= yp(s.data[j][3])) {
+ return {seriesIndex: i, pointIndex:j, gridData:p, data:s.data[j]};
+ }
+ }
+ // if an open hi low close chart
+ else if (!r.hlc){
+ var yp = s._yaxis.series_u2p;
+ if (x >= p[0]-r._tickLength && x <= p[0]+r._tickLength && y >= yp(s.data[j][2]) && y <= yp(s.data[j][3])) {
+ return {seriesIndex: i, pointIndex:j, gridData:p, data:s.data[j]};
+ }
+ }
+ // a hi low close chart
+ else {
+ var yp = s._yaxis.series_u2p;
+ if (x >= p[0]-r._tickLength && x <= p[0]+r._tickLength && y >= yp(s.data[j][1]) && y <= yp(s.data[j][2])) {
+ return {seriesIndex: i, pointIndex:j, gridData:p, data:s.data[j]};
+ }
+ }
+
+ }
+ else {
+ d = Math.sqrt( (x-p[0]) * (x-p[0]) + (y-p[1]) * (y-p[1]) );
+ if (d <= threshold && (d <= d0 || d0 == null)) {
+ d0 = d;
+ return {seriesIndex: i, pointIndex:j, gridData:p, data:s.data[j]};
+ }
+ }
+ }
+ }
+ break;
+ }
+ }
+
+ return null;
+ }
+
+
+
+ this.onClick = function(ev) {
+ // Event passed in is normalized and will have data attribute.
+ // Event passed out is unnormalized.
+ var positions = getEventPosition(ev);
+ var p = ev.data.plot;
+ var neighbor = checkIntersection(positions.gridPos, p);
+ var evt = $.Event('jqplotClick');
+ evt.pageX = ev.pageX;
+ evt.pageY = ev.pageY;
+ $(this).trigger(evt, [positions.gridPos, positions.dataPos, neighbor, p]);
+ };
+
+ this.onDblClick = function(ev) {
+ // Event passed in is normalized and will have data attribute.
+ // Event passed out is unnormalized.
+ var positions = getEventPosition(ev);
+ var p = ev.data.plot;
+ var neighbor = checkIntersection(positions.gridPos, p);
+ var evt = $.Event('jqplotDblClick');
+ evt.pageX = ev.pageX;
+ evt.pageY = ev.pageY;
+ $(this).trigger(evt, [positions.gridPos, positions.dataPos, neighbor, p]);
+ };
+
+ this.onMouseDown = function(ev) {
+ var positions = getEventPosition(ev);
+ var p = ev.data.plot;
+ var neighbor = checkIntersection(positions.gridPos, p);
+ var evt = $.Event('jqplotMouseDown');
+ evt.pageX = ev.pageX;
+ evt.pageY = ev.pageY;
+ $(this).trigger(evt, [positions.gridPos, positions.dataPos, neighbor, p]);
+ };
+
+ this.onMouseUp = function(ev) {
+ var positions = getEventPosition(ev);
+ var evt = $.Event('jqplotMouseUp');
+ evt.pageX = ev.pageX;
+ evt.pageY = ev.pageY;
+ $(this).trigger(evt, [positions.gridPos, positions.dataPos, null, ev.data.plot]);
+ };
+
+ this.onRightClick = function(ev) {
+ var positions = getEventPosition(ev);
+ var p = ev.data.plot;
+ var neighbor = checkIntersection(positions.gridPos, p);
+ if (p.captureRightClick) {
+ if (ev.which == 3) {
+ var evt = $.Event('jqplotRightClick');
+ evt.pageX = ev.pageX;
+ evt.pageY = ev.pageY;
+ $(this).trigger(evt, [positions.gridPos, positions.dataPos, neighbor, p]);
+ }
+ else {
+ var evt = $.Event('jqplotMouseUp');
+ evt.pageX = ev.pageX;
+ evt.pageY = ev.pageY;
+ $(this).trigger(evt, [positions.gridPos, positions.dataPos, neighbor, p]);
+ }
+ }
+ };
+
+ this.onMouseMove = function(ev) {
+ var positions = getEventPosition(ev);
+ var p = ev.data.plot;
+ var neighbor = checkIntersection(positions.gridPos, p);
+ var evt = $.Event('jqplotMouseMove');
+ evt.pageX = ev.pageX;
+ evt.pageY = ev.pageY;
+ $(this).trigger(evt, [positions.gridPos, positions.dataPos, neighbor, p]);
+ };
+
+ this.onMouseEnter = function(ev) {
+ var positions = getEventPosition(ev);
+ var p = ev.data.plot;
+ var evt = $.Event('jqplotMouseEnter');
+ evt.pageX = ev.pageX;
+ evt.pageY = ev.pageY;
+ evt.relatedTarget = ev.relatedTarget;
+ $(this).trigger(evt, [positions.gridPos, positions.dataPos, null, p]);
+ };
+
+ this.onMouseLeave = function(ev) {
+ var positions = getEventPosition(ev);
+ var p = ev.data.plot;
+ var evt = $.Event('jqplotMouseLeave');
+ evt.pageX = ev.pageX;
+ evt.pageY = ev.pageY;
+ evt.relatedTarget = ev.relatedTarget;
+ $(this).trigger(evt, [positions.gridPos, positions.dataPos, null, p]);
+ };
+
+ // method: drawSeries
+ // Redraws all or just one series on the plot. No axis scaling
+ // is performed and no other elements on the plot are redrawn.
+ // options is an options object to pass on to the series renderers.
+ // It can be an empty object {}. idx is the series index
+ // to redraw if only one series is to be redrawn.
+ this.drawSeries = function(options, idx){
+ var i, series, ctx;
+ // if only one argument passed in and it is a number, use it ad idx.
+ idx = (typeof(options) === "number" && idx == null) ? options : idx;
+ options = (typeof(options) === "object") ? options : {};
+ // draw specified series
+ if (idx != undefined) {
+ series = this.series[idx];
+ ctx = series.shadowCanvas._ctx;
+ ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
+ series.drawShadow(ctx, options, this);
+ ctx = series.canvas._ctx;
+ ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
+ series.draw(ctx, options, this);
+ if (series.renderer.constructor == $.jqplot.BezierCurveRenderer) {
+ if (idx < this.series.length - 1) {
+ this.drawSeries(idx+1);
+ }
+ }
+ }
+
+ else {
+ // if call series drawShadow method first, in case all series shadows
+ // should be drawn before any series. This will ensure, like for
+ // stacked bar plots, that shadows don't overlap series.
+ for (i=0; i<this.series.length; i++) {
+ // first clear the canvas
+ series = this.series[i];
+ ctx = series.shadowCanvas._ctx;
+ ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
+ series.drawShadow(ctx, options, this);
+ ctx = series.canvas._ctx;
+ ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
+ series.draw(ctx, options, this);
+ }
+ }
+ options = idx = i = series = ctx = null;
+ };
+
+ // method: moveSeriesToFront
+ // This method requires jQuery 1.4+
+ // Moves the specified series canvas in front of all other series canvases.
+ // This effectively "draws" the specified series on top of all other series,
+ // although it is performed through DOM manipulation, no redrawing is performed.
+ //
+ // Parameters:
+ // idx - 0 based index of the series to move. This will be the index of the series
+ // as it was first passed into the jqplot function.
+ this.moveSeriesToFront = function (idx) {
+ idx = parseInt(idx, 10);
+ var stackIndex = $.inArray(idx, this.seriesStack);
+ // if already in front, return
+ if (stackIndex == -1) {
+ return;
+ }
+ if (stackIndex == this.seriesStack.length -1) {
+ this.previousSeriesStack = this.seriesStack.slice(0);
+ return;
+ }
+ var opidx = this.seriesStack[this.seriesStack.length -1];
+ var serelem = this.series[idx].canvas._elem.detach();
+ var shadelem = this.series[idx].shadowCanvas._elem.detach();
+ this.series[opidx].shadowCanvas._elem.after(shadelem);
+ this.series[opidx].canvas._elem.after(serelem);
+ this.previousSeriesStack = this.seriesStack.slice(0);
+ this.seriesStack.splice(stackIndex, 1);
+ this.seriesStack.push(idx);
+ };
+
+ // method: moveSeriesToBack
+ // This method requires jQuery 1.4+
+ // Moves the specified series canvas behind all other series canvases.
+ //
+ // Parameters:
+ // idx - 0 based index of the series to move. This will be the index of the series
+ // as it was first passed into the jqplot function.
+ this.moveSeriesToBack = function (idx) {
+ idx = parseInt(idx, 10);
+ var stackIndex = $.inArray(idx, this.seriesStack);
+ // if already in back, return
+ if (stackIndex == 0 || stackIndex == -1) {
+ return;
+ }
+ var opidx = this.seriesStack[0];
+ var serelem = this.series[idx].canvas._elem.detach();
+ var shadelem = this.series[idx].shadowCanvas._elem.detach();
+ this.series[opidx].shadowCanvas._elem.before(shadelem);
+ this.series[opidx].canvas._elem.before(serelem);
+ this.previousSeriesStack = this.seriesStack.slice(0);
+ this.seriesStack.splice(stackIndex, 1);
+ this.seriesStack.unshift(idx);
+ };
+
+ // method: restorePreviousSeriesOrder
+ // This method requires jQuery 1.4+
+ // Restore the series canvas order to its previous state.
+ // Useful to put a series back where it belongs after moving
+ // it to the front.
+ this.restorePreviousSeriesOrder = function () {
+ var i, j, serelem, shadelem, temp, move, keep;
+ // if no change, return.
+ if (this.seriesStack == this.previousSeriesStack) {
+ return;
+ }
+ for (i=1; i<this.previousSeriesStack.length; i++) {
+ move = this.previousSeriesStack[i];
+ keep = this.previousSeriesStack[i-1];
+ serelem = this.series[move].canvas._elem.detach();
+ shadelem = this.series[move].shadowCanvas._elem.detach();
+ this.series[keep].shadowCanvas._elem.after(shadelem);
+ this.series[keep].canvas._elem.after(serelem);
+ }
+ temp = this.seriesStack.slice(0);
+ this.seriesStack = this.previousSeriesStack.slice(0);
+ this.previousSeriesStack = temp;
+ };
+
+ // method: restoreOriginalSeriesOrder
+ // This method requires jQuery 1.4+
+ // Restore the series canvas order to its original order
+ // when the plot was created.
+ this.restoreOriginalSeriesOrder = function () {
+ var i, j, arr=[], serelem, shadelem;
+ for (i=0; i<this.series.length; i++) {
+ arr.push(i);
+ }
+ if (this.seriesStack == arr) {
+ return;
+ }
+ this.previousSeriesStack = this.seriesStack.slice(0);
+ this.seriesStack = arr;
+ for (i=1; i<this.seriesStack.length; i++) {
+ serelem = this.series[i].canvas._elem.detach();
+ shadelem = this.series[i].shadowCanvas._elem.detach();
+ this.series[i-1].shadowCanvas._elem.after(shadelem);
+ this.series[i-1].canvas._elem.after(serelem);
+ }
+ };
+
+ this.activateTheme = function (name) {
+ this.themeEngine.activate(this, name);
+ };
+ }
+
+
+ // conpute a highlight color or array of highlight colors from given colors.
+ $.jqplot.computeHighlightColors = function(colors) {
+ var ret;
+ if ($.isArray(colors)) {
+ ret = [];
+ for (var i=0; i<colors.length; i++){
+ var rgba = $.jqplot.getColorComponents(colors[i]);
+ var newrgb = [rgba[0], rgba[1], rgba[2]];
+ var sum = newrgb[0] + newrgb[1] + newrgb[2];
+ for (var j=0; j<3; j++) {
+ // when darkening, lowest color component can be is 60.
+ newrgb[j] = (sum > 660) ? newrgb[j] * 0.85 : 0.73 * newrgb[j] + 90;
+ newrgb[j] = parseInt(newrgb[j], 10);
+ (newrgb[j] > 255) ? 255 : newrgb[j];
+ }
+ // newrgb[3] = (rgba[3] > 0.4) ? rgba[3] * 0.4 : rgba[3] * 1.5;
+ // newrgb[3] = (rgba[3] > 0.5) ? 0.8 * rgba[3] - .1 : rgba[3] + 0.2;
+ newrgb[3] = 0.3 + 0.35 * rgba[3];
+ ret.push('rgba('+newrgb[0]+','+newrgb[1]+','+newrgb[2]+','+newrgb[3]+')');
+ }
+ }
+ else {
+ var rgba = $.jqplot.getColorComponents(colors);
+ var newrgb = [rgba[0], rgba[1], rgba[2]];
+ var sum = newrgb[0] + newrgb[1] + newrgb[2];
+ for (var j=0; j<3; j++) {
+ // when darkening, lowest color component can be is 60.
+ // newrgb[j] = (sum > 570) ? newrgb[j] * 0.8 : newrgb[j] + 0.3 * (255 - newrgb[j]);
+ // newrgb[j] = parseInt(newrgb[j], 10);
+ newrgb[j] = (sum > 660) ? newrgb[j] * 0.85 : 0.73 * newrgb[j] + 90;
+ newrgb[j] = parseInt(newrgb[j], 10);
+ (newrgb[j] > 255) ? 255 : newrgb[j];
+ }
+ // newrgb[3] = (rgba[3] > 0.4) ? rgba[3] * 0.4 : rgba[3] * 1.5;
+ // newrgb[3] = (rgba[3] > 0.5) ? 0.8 * rgba[3] - .1 : rgba[3] + 0.2;
+ newrgb[3] = 0.3 + 0.35 * rgba[3];
+ ret = 'rgba('+newrgb[0]+','+newrgb[1]+','+newrgb[2]+','+newrgb[3]+')';
+ }
+ return ret;
+ };
+
+ $.jqplot.ColorGenerator = function(colors) {
+ colors = colors || $.jqplot.config.defaultColors;
+ var idx = 0;
+
+ this.next = function () {
+ if (idx < colors.length) {
+ return colors[idx++];
+ }
+ else {
+ idx = 0;
+ return colors[idx++];
+ }
+ };
+
+ this.previous = function () {
+ if (idx > 0) {
+ return colors[idx--];
+ }
+ else {
+ idx = colors.length-1;
+ return colors[idx];
+ }
+ };
+
+ // get a color by index without advancing pointer.
+ this.get = function(i) {
+ var idx = i - colors.length * Math.floor(i/colors.length);
+ return colors[idx];
+ };
+
+ this.setColors = function(c) {
+ colors = c;
+ };
+
+ this.reset = function() {
+ idx = 0;
+ };
+
+ this.getIndex = function() {
+ return idx;
+ };
+
+ this.setIndex = function(index) {
+ idx = index;
+ };
+ };
+
+ // convert a hex color string to rgb string.
+ // h - 3 or 6 character hex string, with or without leading #
+ // a - optional alpha
+ $.jqplot.hex2rgb = function(h, a) {
+ h = h.replace('#', '');
+ if (h.length == 3) {
+ h = h.charAt(0)+h.charAt(0)+h.charAt(1)+h.charAt(1)+h.charAt(2)+h.charAt(2);
+ }
+ var rgb;
+ rgb = 'rgba('+parseInt(h.slice(0,2), 16)+', '+parseInt(h.slice(2,4), 16)+', '+parseInt(h.slice(4,6), 16);
+ if (a) {
+ rgb += ', '+a;
+ }
+ rgb += ')';
+ return rgb;
+ };
+
+ // convert an rgb color spec to a hex spec. ignore any alpha specification.
+ $.jqplot.rgb2hex = function(s) {
+ var pat = /rgba?\( *([0-9]{1,3}\.?[0-9]*%?) *, *([0-9]{1,3}\.?[0-9]*%?) *, *([0-9]{1,3}\.?[0-9]*%?) *(?:, *[0-9.]*)?\)/;
+ var m = s.match(pat);
+ var h = '#';
+ for (var i=1; i<4; i++) {
+ var temp;
+ if (m[i].search(/%/) != -1) {
+ temp = parseInt(255*m[i]/100, 10).toString(16);
+ if (temp.length == 1) {
+ temp = '0'+temp;
+ }
+ }
+ else {
+ temp = parseInt(m[i], 10).toString(16);
+ if (temp.length == 1) {
+ temp = '0'+temp;
+ }
+ }
+ h += temp;
+ }
+ return h;
+ };
+
+ // given a css color spec, return an rgb css color spec
+ $.jqplot.normalize2rgb = function(s, a) {
+ if (s.search(/^ *rgba?\(/) != -1) {
+ return s;
+ }
+ else if (s.search(/^ *#?[0-9a-fA-F]?[0-9a-fA-F]/) != -1) {
+ return $.jqplot.hex2rgb(s, a);
+ }
+ else {
+ throw 'invalid color spec';
+ }
+ };
+
+ // extract the r, g, b, a color components out of a css color spec.
+ $.jqplot.getColorComponents = function(s) {
+ // check to see if a color keyword.
+ s = $.jqplot.colorKeywordMap[s] || s;
+ var rgb = $.jqplot.normalize2rgb(s);
+ var pat = /rgba?\( *([0-9]{1,3}\.?[0-9]*%?) *, *([0-9]{1,3}\.?[0-9]*%?) *, *([0-9]{1,3}\.?[0-9]*%?) *,? *([0-9.]* *)?\)/;
+ var m = rgb.match(pat);
+ var ret = [];
+ for (var i=1; i<4; i++) {
+ if (m[i].search(/%/) != -1) {
+ ret[i-1] = parseInt(255*m[i]/100, 10);
+ }
+ else {
+ ret[i-1] = parseInt(m[i], 10);
+ }
+ }
+ ret[3] = parseFloat(m[4]) ? parseFloat(m[4]) : 1.0;
+ return ret;
+ };
+
+ $.jqplot.colorKeywordMap = {
+ aliceblue: 'rgb(240, 248, 255)',
+ antiquewhite: 'rgb(250, 235, 215)',
+ aqua: 'rgb( 0, 255, 255)',
+ aquamarine: 'rgb(127, 255, 212)',
+ azure: 'rgb(240, 255, 255)',
+ beige: 'rgb(245, 245, 220)',
+ bisque: 'rgb(255, 228, 196)',
+ black: 'rgb( 0, 0, 0)',
+ blanchedalmond: 'rgb(255, 235, 205)',
+ blue: 'rgb( 0, 0, 255)',
+ blueviolet: 'rgb(138, 43, 226)',
+ brown: 'rgb(165, 42, 42)',
+ burlywood: 'rgb(222, 184, 135)',
+ cadetblue: 'rgb( 95, 158, 160)',
+ chartreuse: 'rgb(127, 255, 0)',
+ chocolate: 'rgb(210, 105, 30)',
+ coral: 'rgb(255, 127, 80)',
+ cornflowerblue: 'rgb(100, 149, 237)',
+ cornsilk: 'rgb(255, 248, 220)',
+ crimson: 'rgb(220, 20, 60)',
+ cyan: 'rgb( 0, 255, 255)',
+ darkblue: 'rgb( 0, 0, 139)',
+ darkcyan: 'rgb( 0, 139, 139)',
+ darkgoldenrod: 'rgb(184, 134, 11)',
+ darkgray: 'rgb(169, 169, 169)',
+ darkgreen: 'rgb( 0, 100, 0)',
+ darkgrey: 'rgb(169, 169, 169)',
+ darkkhaki: 'rgb(189, 183, 107)',
+ darkmagenta: 'rgb(139, 0, 139)',
+ darkolivegreen: 'rgb( 85, 107, 47)',
+ darkorange: 'rgb(255, 140, 0)',
+ darkorchid: 'rgb(153, 50, 204)',
+ darkred: 'rgb(139, 0, 0)',
+ darksalmon: 'rgb(233, 150, 122)',
+ darkseagreen: 'rgb(143, 188, 143)',
+ darkslateblue: 'rgb( 72, 61, 139)',
+ darkslategray: 'rgb( 47, 79, 79)',
+ darkslategrey: 'rgb( 47, 79, 79)',
+ darkturquoise: 'rgb( 0, 206, 209)',
+ darkviolet: 'rgb(148, 0, 211)',
+ deeppink: 'rgb(255, 20, 147)',
+ deepskyblue: 'rgb( 0, 191, 255)',
+ dimgray: 'rgb(105, 105, 105)',
+ dimgrey: 'rgb(105, 105, 105)',
+ dodgerblue: 'rgb( 30, 144, 255)',
+ firebrick: 'rgb(178, 34, 34)',
+ floralwhite: 'rgb(255, 250, 240)',
+ forestgreen: 'rgb( 34, 139, 34)',
+ fuchsia: 'rgb(255, 0, 255)',
+ gainsboro: 'rgb(220, 220, 220)',
+ ghostwhite: 'rgb(248, 248, 255)',
+ gold: 'rgb(255, 215, 0)',
+ goldenrod: 'rgb(218, 165, 32)',
+ gray: 'rgb(128, 128, 128)',
+ grey: 'rgb(128, 128, 128)',
+ green: 'rgb( 0, 128, 0)',
+ greenyellow: 'rgb(173, 255, 47)',
+ honeydew: 'rgb(240, 255, 240)',
+ hotpink: 'rgb(255, 105, 180)',
+ indianred: 'rgb(205, 92, 92)',
+ indigo: 'rgb( 75, 0, 130)',
+ ivory: 'rgb(255, 255, 240)',
+ khaki: 'rgb(240, 230, 140)',
+ lavender: 'rgb(230, 230, 250)',
+ lavenderblush: 'rgb(255, 240, 245)',
+ lawngreen: 'rgb(124, 252, 0)',
+ lemonchiffon: 'rgb(255, 250, 205)',
+ lightblue: 'rgb(173, 216, 230)',
+ lightcoral: 'rgb(240, 128, 128)',
+ lightcyan: 'rgb(224, 255, 255)',
+ lightgoldenrodyellow: 'rgb(250, 250, 210)',
+ lightgray: 'rgb(211, 211, 211)',
+ lightgreen: 'rgb(144, 238, 144)',
+ lightgrey: 'rgb(211, 211, 211)',
+ lightpink: 'rgb(255, 182, 193)',
+ lightsalmon: 'rgb(255, 160, 122)',
+ lightseagreen: 'rgb( 32, 178, 170)',
+ lightskyblue: 'rgb(135, 206, 250)',
+ lightslategray: 'rgb(119, 136, 153)',
+ lightslategrey: 'rgb(119, 136, 153)',
+ lightsteelblue: 'rgb(176, 196, 222)',
+ lightyellow: 'rgb(255, 255, 224)',
+ lime: 'rgb( 0, 255, 0)',
+ limegreen: 'rgb( 50, 205, 50)',
+ linen: 'rgb(250, 240, 230)',
+ magenta: 'rgb(255, 0, 255)',
+ maroon: 'rgb(128, 0, 0)',
+ mediumaquamarine: 'rgb(102, 205, 170)',
+ mediumblue: 'rgb( 0, 0, 205)',
+ mediumorchid: 'rgb(186, 85, 211)',
+ mediumpurple: 'rgb(147, 112, 219)',
+ mediumseagreen: 'rgb( 60, 179, 113)',
+ mediumslateblue: 'rgb(123, 104, 238)',
+ mediumspringgreen: 'rgb( 0, 250, 154)',
+ mediumturquoise: 'rgb( 72, 209, 204)',
+ mediumvioletred: 'rgb(199, 21, 133)',
+ midnightblue: 'rgb( 25, 25, 112)',
+ mintcream: 'rgb(245, 255, 250)',
+ mistyrose: 'rgb(255, 228, 225)',
+ moccasin: 'rgb(255, 228, 181)',
+ navajowhite: 'rgb(255, 222, 173)',
+ navy: 'rgb( 0, 0, 128)',
+ oldlace: 'rgb(253, 245, 230)',
+ olive: 'rgb(128, 128, 0)',
+ olivedrab: 'rgb(107, 142, 35)',
+ orange: 'rgb(255, 165, 0)',
+ orangered: 'rgb(255, 69, 0)',
+ orchid: 'rgb(218, 112, 214)',
+ palegoldenrod: 'rgb(238, 232, 170)',
+ palegreen: 'rgb(152, 251, 152)',
+ paleturquoise: 'rgb(175, 238, 238)',
+ palevioletred: 'rgb(219, 112, 147)',
+ papayawhip: 'rgb(255, 239, 213)',
+ peachpuff: 'rgb(255, 218, 185)',
+ peru: 'rgb(205, 133, 63)',
+ pink: 'rgb(255, 192, 203)',
+ plum: 'rgb(221, 160, 221)',
+ powderblue: 'rgb(176, 224, 230)',
+ purple: 'rgb(128, 0, 128)',
+ red: 'rgb(255, 0, 0)',
+ rosybrown: 'rgb(188, 143, 143)',
+ royalblue: 'rgb( 65, 105, 225)',
+ saddlebrown: 'rgb(139, 69, 19)',
+ salmon: 'rgb(250, 128, 114)',
+ sandybrown: 'rgb(244, 164, 96)',
+ seagreen: 'rgb( 46, 139, 87)',
+ seashell: 'rgb(255, 245, 238)',
+ sienna: 'rgb(160, 82, 45)',
+ silver: 'rgb(192, 192, 192)',
+ skyblue: 'rgb(135, 206, 235)',
+ slateblue: 'rgb(106, 90, 205)',
+ slategray: 'rgb(112, 128, 144)',
+ slategrey: 'rgb(112, 128, 144)',
+ snow: 'rgb(255, 250, 250)',
+ springgreen: 'rgb( 0, 255, 127)',
+ steelblue: 'rgb( 70, 130, 180)',
+ tan: 'rgb(210, 180, 140)',
+ teal: 'rgb( 0, 128, 128)',
+ thistle: 'rgb(216, 191, 216)',
+ tomato: 'rgb(255, 99, 71)',
+ turquoise: 'rgb( 64, 224, 208)',
+ violet: 'rgb(238, 130, 238)',
+ wheat: 'rgb(245, 222, 179)',
+ white: 'rgb(255, 255, 255)',
+ whitesmoke: 'rgb(245, 245, 245)',
+ yellow: 'rgb(255, 255, 0)',
+ yellowgreen: 'rgb(154, 205, 50)'
+ };
+
+
+
+ // class: $.jqplot.AxisLabelRenderer
+ // Renderer to place labels on the axes.
+ $.jqplot.AxisLabelRenderer = function(options) {
+ // Group: Properties
+ $.jqplot.ElemContainer.call(this);
+ // name of the axis associated with this tick
+ this.axis;
+ // prop: show
+ // wether or not to show the tick (mark and label).
+ this.show = true;
+ // prop: label
+ // The text or html for the label.
+ this.label = '';
+ this.fontFamily = null;
+ this.fontSize = null;
+ this.textColor = null;
+ this._elem;
+ // prop: escapeHTML
+ // true to escape HTML entities in the label.
+ this.escapeHTML = false;
+
+ $.extend(true, this, options);
+ };
+
+ $.jqplot.AxisLabelRenderer.prototype = new $.jqplot.ElemContainer();
+ $.jqplot.AxisLabelRenderer.prototype.constructor = $.jqplot.AxisLabelRenderer;
+
+ $.jqplot.AxisLabelRenderer.prototype.init = function(options) {
+ $.extend(true, this, options);
+ };
+
+ $.jqplot.AxisLabelRenderer.prototype.draw = function(ctx, plot) {
+ // Memory Leaks patch
+ if (this._elem) {
+ this._elem.emptyForce();
+ this._elem = null;
+ }
+
+ this._elem = $('<div style="position:absolute;" class="jqplot-'+this.axis+'-label"></div>');
+
+ if (Number(this.label)) {
+ this._elem.css('white-space', 'nowrap');
+ }
+
+ if (!this.escapeHTML) {
+ this._elem.html(this.label);
+ }
+ else {
+ this._elem.text(this.label);
+ }
+ if (this.fontFamily) {
+ this._elem.css('font-family', this.fontFamily);
+ }
+ if (this.fontSize) {
+ this._elem.css('font-size', this.fontSize);
+ }
+ if (this.textColor) {
+ this._elem.css('color', this.textColor);
+ }
+
+ return this._elem;
+ };
+
+ $.jqplot.AxisLabelRenderer.prototype.pack = function() {
+ };
+
+ // class: $.jqplot.AxisTickRenderer
+ // A "tick" object showing the value of a tick/gridline on the plot.
+ $.jqplot.AxisTickRenderer = function(options) {
+ // Group: Properties
+ $.jqplot.ElemContainer.call(this);
+ // prop: mark
+ // tick mark on the axis. One of 'inside', 'outside', 'cross', '' or null.
+ this.mark = 'outside';
+ // name of the axis associated with this tick
+ this.axis;
+ // prop: showMark
+ // wether or not to show the mark on the axis.
+ this.showMark = true;
+ // prop: showGridline
+ // wether or not to draw the gridline on the grid at this tick.
+ this.showGridline = true;
+ // prop: isMinorTick
+ // if this is a minor tick.
+ this.isMinorTick = false;
+ // prop: size
+ // Length of the tick beyond the grid in pixels.
+ // DEPRECATED: This has been superceeded by markSize
+ this.size = 4;
+ // prop: markSize
+ // Length of the tick marks in pixels. For 'cross' style, length
+ // will be stoked above and below axis, so total length will be twice this.
+ this.markSize = 6;
+ // prop: show
+ // wether or not to show the tick (mark and label).
+ // Setting this to false requires more testing. It is recommended
+ // to set showLabel and showMark to false instead.
+ this.show = true;
+ // prop: showLabel
+ // wether or not to show the label.
+ this.showLabel = true;
+ this.label = null;
+ this.value = null;
+ this._styles = {};
+ // prop: formatter
+ // A class of a formatter for the tick text. sprintf by default.
+ this.formatter = $.jqplot.DefaultTickFormatter;
+ // prop: prefix
+ // String to prepend to the tick label.
+ // Prefix is prepended to the formatted tick label.
+ this.prefix = '';
+ // prop: suffix
+ // String to append to the tick label.
+ // Suffix is appended to the formatted tick label.
+ this.suffix = '';
+ // prop: formatString
+ // string passed to the formatter.
+ this.formatString = '';
+ // prop: fontFamily
+ // css spec for the font-family css attribute.
+ this.fontFamily;
+ // prop: fontSize
+ // css spec for the font-size css attribute.
+ this.fontSize;
+ // prop: textColor
+ // css spec for the color attribute.
+ this.textColor;
+ // prop: escapeHTML
+ // true to escape HTML entities in the label.
+ this.escapeHTML = false;
+ this._elem;
+ this._breakTick = false;
+
+ $.extend(true, this, options);
+ };
+
+ $.jqplot.AxisTickRenderer.prototype.init = function(options) {
+ $.extend(true, this, options);
+ };
+
+ $.jqplot.AxisTickRenderer.prototype = new $.jqplot.ElemContainer();
+ $.jqplot.AxisTickRenderer.prototype.constructor = $.jqplot.AxisTickRenderer;
+
+ $.jqplot.AxisTickRenderer.prototype.setTick = function(value, axisName, isMinor) {
+ this.value = value;
+ this.axis = axisName;
+ if (isMinor) {
+ this.isMinorTick = true;
+ }
+ return this;
+ };
+
+ $.jqplot.AxisTickRenderer.prototype.draw = function() {
+ if (this.label === null) {
+ this.label = this.prefix + this.formatter(this.formatString, this.value) + this.suffix;
+ }
+ var style = {position: 'absolute'};
+ if (Number(this.label)) {
+ style['whitSpace'] = 'nowrap';
+ }
+
+ // Memory Leaks patch
+ if (this._elem) {
+ this._elem.emptyForce();
+ this._elem = null;
+ }
+
+ this._elem = $(document.createElement('div'));
+ this._elem.addClass("jqplot-"+this.axis+"-tick");
+
+ if (!this.escapeHTML) {
+ this._elem.html(this.label);
+ }
+ else {
+ this._elem.text(this.label);
+ }
+
+ this._elem.css(style);
+
+ for (var s in this._styles) {
+ this._elem.css(s, this._styles[s]);
+ }
+ if (this.fontFamily) {
+ this._elem.css('font-family', this.fontFamily);
+ }
+ if (this.fontSize) {
+ this._elem.css('font-size', this.fontSize);
+ }
+ if (this.textColor) {
+ this._elem.css('color', this.textColor);
+ }
+ if (this._breakTick) {
+ this._elem.addClass('jqplot-breakTick');
+ }
+
+ return this._elem;
+ };
+
+ $.jqplot.DefaultTickFormatter = function (format, val) {
+ if (typeof val == 'number') {
+ if (!format) {
+ format = $.jqplot.config.defaultTickFormatString;
+ }
+ return $.jqplot.sprintf(format, val);
+ }
+ else {
+ return String(val);
+ }
+ };
+
+ $.jqplot.PercentTickFormatter = function (format, val) {
+ if (typeof val == 'number') {
+ val = 100 * val;
+ if (!format) {
+ format = $.jqplot.config.defaultTickFormatString;
+ }
+ return $.jqplot.sprintf(format, val);
+ }
+ else {
+ return String(val);
+ }
+ };
+
+ $.jqplot.AxisTickRenderer.prototype.pack = function() {
+ };
+
+ // Class: $.jqplot.CanvasGridRenderer
+ // The default jqPlot grid renderer, creating a grid on a canvas element.
+ // The renderer has no additional options beyond the <Grid> class.
+ $.jqplot.CanvasGridRenderer = function(){
+ this.shadowRenderer = new $.jqplot.ShadowRenderer();
+ };
+
+ // called with context of Grid object
+ $.jqplot.CanvasGridRenderer.prototype.init = function(options) {
+ this._ctx;
+ $.extend(true, this, options);
+ // set the shadow renderer options
+ var sopts = {lineJoin:'miter', lineCap:'round', fill:false, isarc:false, angle:this.shadowAngle, offset:this.shadowOffset, alpha:this.shadowAlpha, depth:this.shadowDepth, lineWidth:this.shadowWidth, closePath:false, strokeStyle:this.shadowColor};
+ this.renderer.shadowRenderer.init(sopts);
+ };
+
+ // called with context of Grid.
+ $.jqplot.CanvasGridRenderer.prototype.createElement = function(plot) {
+ var elem;
+ // Memory Leaks patch
+ if (this._elem) {
+ if ($.jqplot.use_excanvas && window.G_vmlCanvasManager.uninitElement !== undefined) {
+ elem = this._elem.get(0);
+ window.G_vmlCanvasManager.uninitElement(elem);
+ elem = null;
+ }
+
+ this._elem.emptyForce();
+ this._elem = null;
+ }
+
+ elem = plot.canvasManager.getCanvas();
+
+ var w = this._plotDimensions.width;
+ var h = this._plotDimensions.height;
+ elem.width = w;
+ elem.height = h;
+ this._elem = $(elem);
+ this._elem.addClass('jqplot-grid-canvas');
+ this._elem.css({ position: 'absolute', left: 0, top: 0 });
+
+ elem = plot.canvasManager.initCanvas(elem);
+
+ this._top = this._offsets.top;
+ this._bottom = h - this._offsets.bottom;
+ this._left = this._offsets.left;
+ this._right = w - this._offsets.right;
+ this._width = this._right - this._left;
+ this._height = this._bottom - this._top;
+ // avoid memory leak
+ elem = null;
+ return this._elem;
+ };
+
+ $.jqplot.CanvasGridRenderer.prototype.draw = function() {
+ this._ctx = this._elem.get(0).getContext("2d");
+ var ctx = this._ctx;
+ var axes = this._axes;
+ // Add the grid onto the grid canvas. This is the bottom most layer.
+ ctx.save();
+ ctx.clearRect(0, 0, this._plotDimensions.width, this._plotDimensions.height);
+ ctx.fillStyle = this.backgroundColor || this.background;
+ ctx.fillRect(this._left, this._top, this._width, this._height);
+
+ ctx.save();
+ ctx.lineJoin = 'miter';
+ ctx.lineCap = 'butt';
+ ctx.lineWidth = this.gridLineWidth;
+ ctx.strokeStyle = this.gridLineColor;
+ var b, e, s, m;
+ var ax = ['xaxis', 'yaxis', 'x2axis', 'y2axis'];
+ for (var i=4; i>0; i--) {
+ var name = ax[i-1];
+ var axis = axes[name];
+ var ticks = axis._ticks;
+ var numticks = ticks.length;
+ if (axis.show) {
+ if (axis.drawBaseline) {
+ var bopts = {};
+ if (axis.baselineWidth !== null) {
+ bopts.lineWidth = axis.baselineWidth;
+ }
+ if (axis.baselineColor !== null) {
+ bopts.strokeStyle = axis.baselineColor;
+ }
+ switch (name) {
+ case 'xaxis':
+ drawLine (this._left, this._bottom, this._right, this._bottom, bopts);
+ break;
+ case 'yaxis':
+ drawLine (this._left, this._bottom, this._left, this._top, bopts);
+ break;
+ case 'x2axis':
+ drawLine (this._left, this._bottom, this._right, this._bottom, bopts);
+ break;
+ case 'y2axis':
+ drawLine (this._right, this._bottom, this._right, this._top, bopts);
+ break;
+ }
+ }
+ for (var j=numticks; j>0; j--) {
+ var t = ticks[j-1];
+ if (t.show) {
+ var pos = Math.round(axis.u2p(t.value)) + 0.5;
+ switch (name) {
+ case 'xaxis':
+ // draw the grid line if we should
+ if (t.showGridline && this.drawGridlines && ((!t.isMinorTick && axis.drawMajorGridlines) || (t.isMinorTick && axis.drawMinorGridlines)) ) {
+ drawLine(pos, this._top, pos, this._bottom);
+ }
+ // draw the mark
+ if (t.showMark && t.mark && ((!t.isMinorTick && axis.drawMajorTickMarks) || (t.isMinorTick && axis.drawMinorTickMarks)) ) {
+ s = t.markSize;
+ m = t.mark;
+ var pos = Math.round(axis.u2p(t.value)) + 0.5;
+ switch (m) {
+ case 'outside':
+ b = this._bottom;
+ e = this._bottom+s;
+ break;
+ case 'inside':
+ b = this._bottom-s;
+ e = this._bottom;
+ break;
+ case 'cross':
+ b = this._bottom-s;
+ e = this._bottom+s;
+ break;
+ default:
+ b = this._bottom;
+ e = this._bottom+s;
+ break;
+ }
+ // draw the shadow
+ if (this.shadow) {
+ this.renderer.shadowRenderer.draw(ctx, [[pos,b],[pos,e]], {lineCap:'butt', lineWidth:this.gridLineWidth, offset:this.gridLineWidth*0.75, depth:2, fill:false, closePath:false});
+ }
+ // draw the line
+ drawLine(pos, b, pos, e);
+ }
+ break;
+ case 'yaxis':
+ // draw the grid line
+ if (t.showGridline && this.drawGridlines && ((!t.isMinorTick && axis.drawMajorGridlines) || (t.isMinorTick && axis.drawMinorGridlines)) ) {
+ drawLine(this._right, pos, this._left, pos);
+ }
+ // draw the mark
+ if (t.showMark && t.mark && ((!t.isMinorTick && axis.drawMajorTickMarks) || (t.isMinorTick && axis.drawMinorTickMarks)) ) {
+ s = t.markSize;
+ m = t.mark;
+ var pos = Math.round(axis.u2p(t.value)) + 0.5;
+ switch (m) {
+ case 'outside':
+ b = this._left-s;
+ e = this._left;
+ break;
+ case 'inside':
+ b = this._left;
+ e = this._left+s;
+ break;
+ case 'cross':
+ b = this._left-s;
+ e = this._left+s;
+ break;
+ default:
+ b = this._left-s;
+ e = this._left;
+ break;
+ }
+ // draw the shadow
+ if (this.shadow) {
+ this.renderer.shadowRenderer.draw(ctx, [[b, pos], [e, pos]], {lineCap:'butt', lineWidth:this.gridLineWidth*1.5, offset:this.gridLineWidth*0.75, fill:false, closePath:false});
+ }
+ drawLine(b, pos, e, pos, {strokeStyle:axis.borderColor});
+ }
+ break;
+ case 'x2axis':
+ // draw the grid line
+ if (t.showGridline && this.drawGridlines && ((!t.isMinorTick && axis.drawMajorGridlines) || (t.isMinorTick && axis.drawMinorGridlines)) ) {
+ drawLine(pos, this._bottom, pos, this._top);
+ }
+ // draw the mark
+ if (t.showMark && t.mark && ((!t.isMinorTick && axis.drawMajorTickMarks) || (t.isMinorTick && axis.drawMinorTickMarks)) ) {
+ s = t.markSize;
+ m = t.mark;
+ var pos = Math.round(axis.u2p(t.value)) + 0.5;
+ switch (m) {
+ case 'outside':
+ b = this._top-s;
+ e = this._top;
+ break;
+ case 'inside':
+ b = this._top;
+ e = this._top+s;
+ break;
+ case 'cross':
+ b = this._top-s;
+ e = this._top+s;
+ break;
+ default:
+ b = this._top-s;
+ e = this._top;
+ break;
+ }
+ // draw the shadow
+ if (this.shadow) {
+ this.renderer.shadowRenderer.draw(ctx, [[pos,b],[pos,e]], {lineCap:'butt', lineWidth:this.gridLineWidth, offset:this.gridLineWidth*0.75, depth:2, fill:false, closePath:false});
+ }
+ drawLine(pos, b, pos, e);
+ }
+ break;
+ case 'y2axis':
+ // draw the grid line
+ if (t.showGridline && this.drawGridlines && ((!t.isMinorTick && axis.drawMajorGridlines) || (t.isMinorTick && axis.drawMinorGridlines)) ) {
+ drawLine(this._left, pos, this._right, pos);
+ }
+ // draw the mark
+ if (t.showMark && t.mark && ((!t.isMinorTick && axis.drawMajorTickMarks) || (t.isMinorTick && axis.drawMinorTickMarks)) ) {
+ s = t.markSize;
+ m = t.mark;
+ var pos = Math.round(axis.u2p(t.value)) + 0.5;
+ switch (m) {
+ case 'outside':
+ b = this._right;
+ e = this._right+s;
+ break;
+ case 'inside':
+ b = this._right-s;
+ e = this._right;
+ break;
+ case 'cross':
+ b = this._right-s;
+ e = this._right+s;
+ break;
+ default:
+ b = this._right;
+ e = this._right+s;
+ break;
+ }
+ // draw the shadow
+ if (this.shadow) {
+ this.renderer.shadowRenderer.draw(ctx, [[b, pos], [e, pos]], {lineCap:'butt', lineWidth:this.gridLineWidth*1.5, offset:this.gridLineWidth*0.75, fill:false, closePath:false});
+ }
+ drawLine(b, pos, e, pos, {strokeStyle:axis.borderColor});
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ }
+ t = null;
+ }
+ axis = null;
+ ticks = null;
+ }
+ // Now draw grid lines for additional y axes
+ //////
+ // TO DO: handle yMidAxis
+ //////
+ ax = ['y3axis', 'y4axis', 'y5axis', 'y6axis', 'y7axis', 'y8axis', 'y9axis', 'yMidAxis'];
+ for (var i=7; i>0; i--) {
+ var axis = axes[ax[i-1]];
+ var ticks = axis._ticks;
+ if (axis.show) {
+ var tn = ticks[axis.numberTicks-1];
+ var t0 = ticks[0];
+ var left = axis.getLeft();
+ var points = [[left, tn.getTop() + tn.getHeight()/2], [left, t0.getTop() + t0.getHeight()/2 + 1.0]];
+ // draw the shadow
+ if (this.shadow) {
+ this.renderer.shadowRenderer.draw(ctx, points, {lineCap:'butt', fill:false, closePath:false});
+ }
+ // draw the line
+ drawLine(points[0][0], points[0][1], points[1][0], points[1][1], {lineCap:'butt', strokeStyle:axis.borderColor, lineWidth:axis.borderWidth});
+ // draw the tick marks
+ for (var j=ticks.length; j>0; j--) {
+ var t = ticks[j-1];
+ s = t.markSize;
+ m = t.mark;
+ var pos = Math.round(axis.u2p(t.value)) + 0.5;
+ if (t.showMark && t.mark) {
+ switch (m) {
+ case 'outside':
+ b = left;
+ e = left+s;
+ break;
+ case 'inside':
+ b = left-s;
+ e = left;
+ break;
+ case 'cross':
+ b = left-s;
+ e = left+s;
+ break;
+ default:
+ b = left;
+ e = left+s;
+ break;
+ }
+ points = [[b,pos], [e,pos]];
+ // draw the shadow
+ if (this.shadow) {
+ this.renderer.shadowRenderer.draw(ctx, points, {lineCap:'butt', lineWidth:this.gridLineWidth*1.5, offset:this.gridLineWidth*0.75, fill:false, closePath:false});
+ }
+ // draw the line
+ drawLine(b, pos, e, pos, {strokeStyle:axis.borderColor});
+ }
+ t = null;
+ }
+ t0 = null;
+ }
+ axis = null;
+ ticks = null;
+ }
+
+ ctx.restore();
+
+ function drawLine(bx, by, ex, ey, opts) {
+ ctx.save();
+ opts = opts || {};
+ if (opts.lineWidth == null || opts.lineWidth != 0){
+ $.extend(true, ctx, opts);
+ ctx.beginPath();
+ ctx.moveTo(bx, by);
+ ctx.lineTo(ex, ey);
+ ctx.stroke();
+ ctx.restore();
+ }
+ }
+
+ if (this.shadow) {
+ var points = [[this._left, this._bottom], [this._right, this._bottom], [this._right, this._top]];
+ this.renderer.shadowRenderer.draw(ctx, points);
+ }
+ // Now draw border around grid. Use axis border definitions. start at
+ // upper left and go clockwise.
+ if (this.borderWidth != 0 && this.drawBorder) {
+ drawLine (this._left, this._top, this._right, this._top, {lineCap:'round', strokeStyle:axes.x2axis.borderColor, lineWidth:axes.x2axis.borderWidth});
+ drawLine (this._right, this._top, this._right, this._bottom, {lineCap:'round', strokeStyle:axes.y2axis.borderColor, lineWidth:axes.y2axis.borderWidth});
+ drawLine (this._right, this._bottom, this._left, this._bottom, {lineCap:'round', strokeStyle:axes.xaxis.borderColor, lineWidth:axes.xaxis.borderWidth});
+ drawLine (this._left, this._bottom, this._left, this._top, {lineCap:'round', strokeStyle:axes.yaxis.borderColor, lineWidth:axes.yaxis.borderWidth});
+ }
+ // ctx.lineWidth = this.borderWidth;
+ // ctx.strokeStyle = this.borderColor;
+ // ctx.strokeRect(this._left, this._top, this._width, this._height);
+
+ ctx.restore();
+ ctx = null;
+ axes = null;
+ };
+
+ // Class: $.jqplot.DivTitleRenderer
+ // The default title renderer for jqPlot. This class has no options beyond the <Title> class.
+ $.jqplot.DivTitleRenderer = function() {
+ };
+
+ $.jqplot.DivTitleRenderer.prototype.init = function(options) {
+ $.extend(true, this, options);
+ };
+
+ $.jqplot.DivTitleRenderer.prototype.draw = function() {
+ // Memory Leaks patch
+ if (this._elem) {
+ this._elem.emptyForce();
+ this._elem = null;
+ }
+
+ var r = this.renderer;
+ var elem = document.createElement('div');
+ this._elem = $(elem);
+ this._elem.addClass('jqplot-title');
+
+ if (!this.text) {
+ this.show = false;
+ this._elem.height(0);
+ this._elem.width(0);
+ }
+ else if (this.text) {
+ var color;
+ if (this.color) {
+ color = this.color;
+ }
+ else if (this.textColor) {
+ color = this.textColor;
+ }
+
+ // don't trust that a stylesheet is present, set the position.
+ var styles = {position:'absolute', top:'0px', left:'0px'};
+
+ if (this._plotWidth) {
+ styles['width'] = this._plotWidth+'px';
+ }
+ if (this.fontSize) {
+ styles['fontSize'] = this.fontSize;
+ }
+ if (typeof this.textAlign === 'string') {
+ styles['textAlign'] = this.textAlign;
+ }
+ else {
+ styles['textAlign'] = 'center';
+ }
+ if (color) {
+ styles['color'] = color;
+ }
+ if (this.paddingBottom) {
+ styles['paddingBottom'] = this.paddingBottom;
+ }
+ if (this.fontFamily) {
+ styles['fontFamily'] = this.fontFamily;
+ }
+
+ this._elem.css(styles);
+ if (this.escapeHtml) {
+ this._elem.text(this.text);
+ }
+ else {
+ this._elem.html(this.text);
+ }
+
+
+ // styletext += (this._plotWidth) ? 'width:'+this._plotWidth+'px;' : '';
+ // styletext += (this.fontSize) ? 'font-size:'+this.fontSize+';' : '';
+ // styletext += (this.textAlign) ? 'text-align:'+this.textAlign+';' : 'text-align:center;';
+ // styletext += (color) ? 'color:'+color+';' : '';
+ // styletext += (this.paddingBottom) ? 'padding-bottom:'+this.paddingBottom+';' : '';
+ // this._elem = $('<div class="jqplot-title" style="'+styletext+'">'+this.text+'</div>');
+ // if (this.fontFamily) {
+ // this._elem.css('font-family', this.fontFamily);
+ // }
+ }
+
+ elem = null;
+
+ return this._elem;
+ };
+
+ $.jqplot.DivTitleRenderer.prototype.pack = function() {
+ // nothing to do here
+ };
+
+
+ var dotlen = 0.1;
+
+ $.jqplot.LinePattern = function (ctx, pattern) {
+
+ var defaultLinePatterns = {
+ dotted: [ dotlen, $.jqplot.config.dotGapLength ],
+ dashed: [ $.jqplot.config.dashLength, $.jqplot.config.gapLength ],
+ solid: null
+ };
+
+ if (typeof pattern === 'string') {
+ if (pattern[0] === '.' || pattern[0] === '-') {
+ var s = pattern;
+ pattern = [];
+ for (var i=0, imax=s.length; i<imax; i++) {
+ if (s[i] === '.') {
+ pattern.push( dotlen );
+ }
+ else if (s[i] === '-') {
+ pattern.push( $.jqplot.config.dashLength );
+ }
+ else {
+ continue;
+ }
+ pattern.push( $.jqplot.config.gapLength );
+ }
+ }
+ else {
+ pattern = defaultLinePatterns[pattern];
+ }
+ }
+
+ if (!(pattern && pattern.length)) {
+ return ctx;
+ }
+
+ var patternIndex = 0;
+ var patternDistance = pattern[0];
+ var px = 0;
+ var py = 0;
+ var pathx0 = 0;
+ var pathy0 = 0;
+
+ var moveTo = function (x, y) {
+ ctx.moveTo( x, y );
+ px = x;
+ py = y;
+ pathx0 = x;
+ pathy0 = y;
+ };
+
+ var lineTo = function (x, y) {
+ var scale = ctx.lineWidth;
+ var dx = x - px;
+ var dy = y - py;
+ var dist = Math.sqrt(dx*dx+dy*dy);
+ if ((dist > 0) && (scale > 0)) {
+ dx /= dist;
+ dy /= dist;
+ while (true) {
+ var dp = scale * patternDistance;
+ if (dp < dist) {
+ px += dp * dx;
+ py += dp * dy;
+ if ((patternIndex & 1) == 0) {
+ ctx.lineTo( px, py );
+ }
+ else {
+ ctx.moveTo( px, py );
+ }
+ dist -= dp;
+ patternIndex++;
+ if (patternIndex >= pattern.length) {
+ patternIndex = 0;
+ }
+ patternDistance = pattern[patternIndex];
+ }
+ else {
+ px = x;
+ py = y;
+ if ((patternIndex & 1) == 0) {
+ ctx.lineTo( px, py );
+ }
+ else {
+ ctx.moveTo( px, py );
+ }
+ patternDistance -= dist / scale;
+ break;
+ }
+ }
+ }
+ };
+
+ var beginPath = function () {
+ ctx.beginPath();
+ };
+
+ var closePath = function () {
+ lineTo( pathx0, pathy0 );
+ };
+
+ return {
+ moveTo: moveTo,
+ lineTo: lineTo,
+ beginPath: beginPath,
+ closePath: closePath
+ };
+ };
+
+ // Class: $.jqplot.LineRenderer
+ // The default line renderer for jqPlot, this class has no options beyond the <Series> class.
+ // Draws series as a line.
+ $.jqplot.LineRenderer = function(){
+ this.shapeRenderer = new $.jqplot.ShapeRenderer();
+ this.shadowRenderer = new $.jqplot.ShadowRenderer();
+ };
+
+ // called with scope of series.
+ $.jqplot.LineRenderer.prototype.init = function(options, plot) {
+ // Group: Properties
+ //
+ options = options || {};
+ this._type='line';
+ this.renderer.animation = {
+ show: false,
+ direction: 'left',
+ speed: 2500,
+ _supported: true
+ };
+ // prop: smooth
+ // True to draw a smoothed (interpolated) line through the data points
+ // with automatically computed number of smoothing points.
+ // Set to an integer number > 2 to specify number of smoothing points
+ // to use between each data point.
+ this.renderer.smooth = false; // true or a number > 2 for smoothing.
+ this.renderer.tension = null; // null to auto compute or a number typically > 6. Fewer points requires higher tension.
+ // prop: constrainSmoothing
+ // True to use a more accurate smoothing algorithm that will
+ // not overshoot any data points. False to allow overshoot but
+ // produce a smoother looking line.
+ this.renderer.constrainSmoothing = true;
+ // this is smoothed data in grid coordinates, like gridData
+ this.renderer._smoothedData = [];
+ // this is smoothed data in plot units (plot coordinates), like plotData.
+ this.renderer._smoothedPlotData = [];
+ this.renderer._hiBandGridData = [];
+ this.renderer._lowBandGridData = [];
+ this.renderer._hiBandSmoothedData = [];
+ this.renderer._lowBandSmoothedData = [];
+
+ // prop: bandData
+ // Data used to draw error bands or confidence intervals above/below a line.
+ //
+ // bandData can be input in 3 forms. jqPlot will figure out which is the
+ // low band line and which is the high band line for all forms:
+ //
+ // A 2 dimensional array like [[yl1, yl2, ...], [yu1, yu2, ...]] where
+ // [yl1, yl2, ...] are y values of the lower line and
+ // [yu1, yu2, ...] are y values of the upper line.
+ // In this case there must be the same number of y data points as data points
+ // in the series and the bands will inherit the x values of the series.
+ //
+ // A 2 dimensional array like [[[xl1, yl1], [xl2, yl2], ...], [[xh1, yh1], [xh2, yh2], ...]]
+ // where [xl1, yl1] are x,y data points for the lower line and
+ // [xh1, yh1] are x,y data points for the high line.
+ // x values do not have to correspond to the x values of the series and can
+ // be of any arbitrary length.
+ //
+ // Can be of form [[yl1, yu1], [yl2, yu2], [yl3, yu3], ...] where
+ // there must be 3 or more arrays and there must be the same number of arrays
+ // as there are data points in the series. In this case,
+ // [yl1, yu1] specifies the lower and upper y values for the 1st
+ // data point and so on. The bands will inherit the x
+ // values from the series.
+ this.renderer.bandData = [];
+
+ // Group: bands
+ // Banding around line, e.g error bands or confidence intervals.
+ this.renderer.bands = {
+ // prop: show
+ // true to show the bands. If bandData or interval is
+ // supplied, show will be set to true by default.
+ show: false,
+ hiData: [],
+ lowData: [],
+ // prop: color
+ // color of lines at top and bottom of bands [default: series color].
+ color: this.color,
+ // prop: showLines
+ // True to show lines at top and bottom of bands [default: false].
+ showLines: false,
+ // prop: fill
+ // True to fill area between bands [default: true].
+ fill: true,
+ // prop: fillColor
+ // css color spec for filled area. [default: series color].
+ fillColor: null,
+ _min: null,
+ _max: null,
+ // prop: interval
+ // User specified interval above and below line for bands [default: '3%''].
+ // Can be a value like 3 or a string like '3%'
+ // or an upper/lower array like [1, -2] or ['2%', '-1.5%']
+ interval: '3%'
+ };
+
+
+ var lopts = {highlightMouseOver: options.highlightMouseOver, highlightMouseDown: options.highlightMouseDown, highlightColor: options.highlightColor};
+
+ delete (options.highlightMouseOver);
+ delete (options.highlightMouseDown);
+ delete (options.highlightColor);
+
+ $.extend(true, this.renderer, options);
+
+ this.renderer.options = options;
+
+ // if we are given some band data, and bands aren't explicity set to false in options, turn them on.
+ if (this.renderer.bandData.length > 1 && (!options.bands || options.bands.show == null)) {
+ this.renderer.bands.show = true;
+ }
+
+ // if we are given an interval, and bands aren't explicity set to false in options, turn them on.
+ else if (options.bands && options.bands.show == null && options.bands.interval != null) {
+ this.renderer.bands.show = true;
+ }
+
+ // if plot is filled, turn off bands.
+ if (this.fill) {
+ this.renderer.bands.show = false;
+ }
+
+ if (this.renderer.bands.show) {
+ this.renderer.initBands.call(this, this.renderer.options, plot);
+ }
+
+
+ // smoothing is not compatible with stacked lines, disable
+ if (this._stack) {
+ this.renderer.smooth = false;
+ }
+
+ // set the shape renderer options
+ var opts = {lineJoin:this.lineJoin, lineCap:this.lineCap, fill:this.fill, isarc:false, strokeStyle:this.color, fillStyle:this.fillColor, lineWidth:this.lineWidth, linePattern:this.linePattern, closePath:this.fill};
+ this.renderer.shapeRenderer.init(opts);
+
+ var shadow_offset = options.shadowOffset;
+ // set the shadow renderer options
+ if (shadow_offset == null) {
+ // scale the shadowOffset to the width of the line.
+ if (this.lineWidth > 2.5) {
+ shadow_offset = 1.25 * (1 + (Math.atan((this.lineWidth/2.5))/0.785398163 - 1)*0.6);
+ // var shadow_offset = this.shadowOffset;
+ }
+ // for skinny lines, don't make such a big shadow.
+ else {
+ shadow_offset = 1.25 * Math.atan((this.lineWidth/2.5))/0.785398163;
+ }
+ }
+
+ var sopts = {lineJoin:this.lineJoin, lineCap:this.lineCap, fill:this.fill, isarc:false, angle:this.shadowAngle, offset:shadow_offset, alpha:this.shadowAlpha, depth:this.shadowDepth, lineWidth:this.lineWidth, linePattern:this.linePattern, closePath:this.fill};
+ this.renderer.shadowRenderer.init(sopts);
+ this._areaPoints = [];
+ this._boundingBox = [[],[]];
+
+ if (!this.isTrendline && this.fill || this.renderer.bands.show) {
+ // Group: Properties
+ //
+ // prop: highlightMouseOver
+ // True to highlight area on a filled plot when moused over.
+ // This must be false to enable highlightMouseDown to highlight when clicking on an area on a filled plot.
+ this.highlightMouseOver = true;
+ // prop: highlightMouseDown
+ // True to highlight when a mouse button is pressed over an area on a filled plot.
+ // This will be disabled if highlightMouseOver is true.
+ this.highlightMouseDown = false;
+ // prop: highlightColor
+ // color to use when highlighting an area on a filled plot.
+ this.highlightColor = null;
+ // if user has passed in highlightMouseDown option and not set highlightMouseOver, disable highlightMouseOver
+ if (lopts.highlightMouseDown && lopts.highlightMouseOver == null) {
+ lopts.highlightMouseOver = false;
+ }
+
+ $.extend(true, this, {highlightMouseOver: lopts.highlightMouseOver, highlightMouseDown: lopts.highlightMouseDown, highlightColor: lopts.highlightColor});
+
+ if (!this.highlightColor) {
+ var fc = (this.renderer.bands.show) ? this.renderer.bands.fillColor : this.fillColor;
+ this.highlightColor = $.jqplot.computeHighlightColors(fc);
+ }
+ // turn off (disable) the highlighter plugin
+ if (this.highlighter) {
+ this.highlighter.show = false;
+ }
+ }
+
+ if (!this.isTrendline && plot) {
+ plot.plugins.lineRenderer = {};
+ plot.postInitHooks.addOnce(postInit);
+ plot.postDrawHooks.addOnce(postPlotDraw);
+ plot.eventListenerHooks.addOnce('jqplotMouseMove', handleMove);
+ plot.eventListenerHooks.addOnce('jqplotMouseDown', handleMouseDown);
+ plot.eventListenerHooks.addOnce('jqplotMouseUp', handleMouseUp);
+ plot.eventListenerHooks.addOnce('jqplotClick', handleClick);
+ plot.eventListenerHooks.addOnce('jqplotRightClick', handleRightClick);
+ }
+
+ };
+
+ $.jqplot.LineRenderer.prototype.initBands = function(options, plot) {
+ // use bandData if no data specified in bands option
+ //var bd = this.renderer.bandData;
+ var bd = options.bandData || [];
+ var bands = this.renderer.bands;
+ bands.hiData = [];
+ bands.lowData = [];
+ var data = this.data;
+ bands._max = null;
+ bands._min = null;
+ // If 2 arrays, and each array greater than 2 elements, assume it is hi and low data bands of y values.
+ if (bd.length == 2) {
+ // Do we have an array of x,y values?
+ // like [[[1,1], [2,4], [3,3]], [[1,3], [2,6], [3,5]]]
+ if ($.isArray(bd[0][0])) {
+ // since an arbitrary array of points, spin through all of them to determine max and min lines.
+
+ var p;
+ var bdminidx = 0, bdmaxidx = 0;
+ for (var i = 0, l = bd[0].length; i<l; i++) {
+ p = bd[0][i];
+ if ((p[1] != null && p[1] > bands._max) || bands._max == null) {
+ bands._max = p[1];
+ }
+ if ((p[1] != null && p[1] < bands._min) || bands._min == null) {
+ bands._min = p[1];
+ }
+ }
+ for (var i = 0, l = bd[1].length; i<l; i++) {
+ p = bd[1][i];
+ if ((p[1] != null && p[1] > bands._max) || bands._max == null) {
+ bands._max = p[1];
+ bdmaxidx = 1;
+ }
+ if ((p[1] != null && p[1] < bands._min) || bands._min == null) {
+ bands._min = p[1];
+ bdminidx = 1;
+ }
+ }
+
+ if (bdmaxidx === bdminidx) {
+ bands.show = false;
+ }
+
+ bands.hiData = bd[bdmaxidx];
+ bands.lowData = bd[bdminidx];
+ }
+ // else data is arrays of y values
+ // like [[1,4,3], [3,6,5]]
+ // must have same number of band data points as points in series
+ else if (bd[0].length === data.length && bd[1].length === data.length) {
+ var hi = (bd[0][0] > bd[1][0]) ? 0 : 1;
+ var low = (hi) ? 0 : 1;
+ for (var i=0, l=data.length; i < l; i++) {
+ bands.hiData.push([data[i][0], bd[hi][i]]);
+ bands.lowData.push([data[i][0], bd[low][i]]);
+ }
+ }
+
+ // we don't have proper data array, don't show bands.
+ else {
+ bands.show = false;
+ }
+ }
+
+ // if more than 2 arrays, have arrays of [ylow, yhi] values.
+ // note, can't distinguish case of [[ylow, yhi], [ylow, yhi]] from [[ylow, ylow], [yhi, yhi]]
+ // this is assumed to be of the latter form.
+ else if (bd.length > 2 && !$.isArray(bd[0][0])) {
+ var hi = (bd[0][0] > bd[0][1]) ? 0 : 1;
+ var low = (hi) ? 0 : 1;
+ for (var i=0, l=bd.length; i<l; i++) {
+ bands.hiData.push([data[i][0], bd[i][hi]]);
+ bands.lowData.push([data[i][0], bd[i][low]]);
+ }
+ }
+
+ // don't have proper data, auto calculate
+ else {
+ var intrv = bands.interval;
+ var a = null;
+ var b = null;
+ var afunc = null;
+ var bfunc = null;
+
+ if ($.isArray(intrv)) {
+ a = intrv[0];
+ b = intrv[1];
+ }
+ else {
+ a = intrv;
+ }
+
+ if (isNaN(a)) {
+ // we have a string
+ if (a.charAt(a.length - 1) === '%') {
+ afunc = 'multiply';
+ a = parseFloat(a)/100 + 1;
+ }
+ }
+
+ else {
+ a = parseFloat(a);
+ afunc = 'add';
+ }
+
+ if (b !== null && isNaN(b)) {
+ // we have a string
+ if (b.charAt(b.length - 1) === '%') {
+ bfunc = 'multiply';
+ b = parseFloat(b)/100 + 1;
+ }
+ }
+
+ else if (b !== null) {
+ b = parseFloat(b);
+ bfunc = 'add';
+ }
+
+ if (a !== null) {
+ if (b === null) {
+ b = -a;
+ bfunc = afunc;
+ if (bfunc === 'multiply') {
+ b += 2;
+ }
+ }
+
+ // make sure a always applies to hi band.
+ if (a < b) {
+ var temp = a;
+ a = b;
+ b = temp;
+ temp = afunc;
+ afunc = bfunc;
+ bfunc = temp;
+ }
+
+ for (var i=0, l = data.length; i < l; i++) {
+ switch (afunc) {
+ case 'add':
+ bands.hiData.push([data[i][0], data[i][1] + a]);
+ break;
+ case 'multiply':
+ bands.hiData.push([data[i][0], data[i][1] * a]);
+ break;
+ }
+ switch (bfunc) {
+ case 'add':
+ bands.lowData.push([data[i][0], data[i][1] + b]);
+ break;
+ case 'multiply':
+ bands.lowData.push([data[i][0], data[i][1] * b]);
+ break;
+ }
+ }
+ }
+
+ else {
+ bands.show = false;
+ }
+ }
+
+ var hd = bands.hiData;
+ var ld = bands.lowData;
+ for (var i = 0, l = hd.length; i<l; i++) {
+ if ((hd[i][1] != null && hd[i][1] > bands._max) || bands._max == null) {
+ bands._max = hd[i][1];
+ }
+ }
+ for (var i = 0, l = ld.length; i<l; i++) {
+ if ((ld[i][1] != null && ld[i][1] < bands._min) || bands._min == null) {
+ bands._min = ld[i][1];
+ }
+ }
+
+ // one last check for proper data
+ // these don't apply any more since allowing arbitrary x,y values
+ // if (bands.hiData.length != bands.lowData.length) {
+ // bands.show = false;
+ // }
+
+ // if (bands.hiData.length != this.data.length) {
+ // bands.show = false;
+ // }
+
+ if (bands.fillColor === null) {
+ var c = $.jqplot.getColorComponents(bands.color);
+ // now adjust alpha to differentiate fill
+ c[3] = c[3] * 0.5;
+ bands.fillColor = 'rgba(' + c[0] +', '+ c[1] +', '+ c[2] +', '+ c[3] + ')';
+ }
+ };
+
+ function getSteps (d, f) {
+ return (3.4182054+f) * Math.pow(d, -0.3534992);
+ }
+
+ function computeSteps (d1, d2) {
+ var s = Math.sqrt(Math.pow((d2[0]- d1[0]), 2) + Math.pow ((d2[1] - d1[1]), 2));
+ return 5.7648 * Math.log(s) + 7.4456;
+ }
+
+ function tanh (x) {
+ var a = (Math.exp(2*x) - 1) / (Math.exp(2*x) + 1);
+ return a;
+ }
+
+ //////////
+ // computeConstrainedSmoothedData
+ // An implementation of the constrained cubic spline interpolation
+ // method as presented in:
+ //
+ // Kruger, CJC, Constrained Cubic Spine Interpolation for Chemical Engineering Applications
+ // http://www.korf.co.uk/spline.pdf
+ //
+ // The implementation below borrows heavily from the sample Visual Basic
+ // implementation by CJC Kruger found in http://www.korf.co.uk/spline.xls
+ //
+ /////////
+
+ // called with scope of series
+ function computeConstrainedSmoothedData (gd) {
+ var smooth = this.renderer.smooth;
+ var dim = this.canvas.getWidth();
+ var xp = this._xaxis.series_p2u;
+ var yp = this._yaxis.series_p2u;
+ var steps =null;
+ var _steps = null;
+ var dist = gd.length/dim;
+ var _smoothedData = [];
+ var _smoothedPlotData = [];
+
+ if (!isNaN(parseFloat(smooth))) {
+ steps = parseFloat(smooth);
+ }
+ else {
+ steps = getSteps(dist, 0.5);
+ }
+
+ var yy = [];
+ var xx = [];
+
+ for (var i=0, l = gd.length; i<l; i++) {
+ yy.push(gd[i][1]);
+ xx.push(gd[i][0]);
+ }
+
+ function dxx(x1, x0) {
+ if (x1 - x0 == 0) {
+ return Math.pow(10,10);
+ }
+ else {
+ return x1 - x0;
+ }
+ }
+
+ var A, B, C, D;
+ // loop through each line segment. Have # points - 1 line segments. Nmber segments starting at 1.
+ var nmax = gd.length - 1;
+ for (var num = 1, gdl = gd.length; num<gdl; num++) {
+ var gxx = [];
+ var ggxx = [];
+ // point at each end of segment.
+ for (var j = 0; j < 2; j++) {
+ var i = num - 1 + j; // point number, 0 to # points.
+
+ if (i == 0 || i == nmax) {
+ gxx[j] = Math.pow(10, 10);
+ }
+ else if (yy[i+1] - yy[i] == 0 || yy[i] - yy[i-1] == 0) {
+ gxx[j] = 0;
+ }
+ else if (((xx[i+1] - xx[i]) / (yy[i+1] - yy[i]) + (xx[i] - xx[i-1]) / (yy[i] - yy[i-1])) == 0 ) {
+ gxx[j] = 0;
+ }
+ else if ( (yy[i+1] - yy[i]) * (yy[i] - yy[i-1]) < 0 ) {
+ gxx[j] = 0;
+ }
+
+ else {
+ gxx[j] = 2 / (dxx(xx[i + 1], xx[i]) / (yy[i + 1] - yy[i]) + dxx(xx[i], xx[i - 1]) / (yy[i] - yy[i - 1]));
+ }
+ }
+
+ // Reset first derivative (slope) at first and last point
+ if (num == 1) {
+ // First point has 0 2nd derivative
+ gxx[0] = 3 / 2 * (yy[1] - yy[0]) / dxx(xx[1], xx[0]) - gxx[1] / 2;
+ }
+ else if (num == nmax) {
+ // Last point has 0 2nd derivative
+ gxx[1] = 3 / 2 * (yy[nmax] - yy[nmax - 1]) / dxx(xx[nmax], xx[nmax - 1]) - gxx[0] / 2;
+ }
+
+ // Calc second derivative at points
+ ggxx[0] = -2 * (gxx[1] + 2 * gxx[0]) / dxx(xx[num], xx[num - 1]) + 6 * (yy[num] - yy[num - 1]) / Math.pow(dxx(xx[num], xx[num - 1]), 2);
+ ggxx[1] = 2 * (2 * gxx[1] + gxx[0]) / dxx(xx[num], xx[num - 1]) - 6 * (yy[num] - yy[num - 1]) / Math.pow(dxx(xx[num], xx[num - 1]), 2);
+
+ // Calc constants for cubic interpolation
+ D = 1 / 6 * (ggxx[1] - ggxx[0]) / dxx(xx[num], xx[num - 1]);
+ C = 1 / 2 * (xx[num] * ggxx[0] - xx[num - 1] * ggxx[1]) / dxx(xx[num], xx[num - 1]);
+ B = (yy[num] - yy[num - 1] - C * (Math.pow(xx[num], 2) - Math.pow(xx[num - 1], 2)) - D * (Math.pow(xx[num], 3) - Math.pow(xx[num - 1], 3))) / dxx(xx[num], xx[num - 1]);
+ A = yy[num - 1] - B * xx[num - 1] - C * Math.pow(xx[num - 1], 2) - D * Math.pow(xx[num - 1], 3);
+
+ var increment = (xx[num] - xx[num - 1]) / steps;
+ var temp, tempx;
+
+ for (var j = 0, l = steps; j < l; j++) {
+ temp = [];
+ tempx = xx[num - 1] + j * increment;
+ temp.push(tempx);
+ temp.push(A + B * tempx + C * Math.pow(tempx, 2) + D * Math.pow(tempx, 3));
+ _smoothedData.push(temp);
+ _smoothedPlotData.push([xp(temp[0]), yp(temp[1])]);
+ }
+ }
+
+ _smoothedData.push(gd[i]);
+ _smoothedPlotData.push([xp(gd[i][0]), yp(gd[i][1])]);
+
+ return [_smoothedData, _smoothedPlotData];
+ }
+
+ ///////
+ // computeHermiteSmoothedData
+ // A hermite spline smoothing of the plot data.
+ // This implementation is derived from the one posted
+ // by krypin on the jqplot-users mailing list:
+ //
+ // http://groups.google.com/group/jqplot-users/browse_thread/thread/748be6a445723cea?pli=1
+ //
+ // with a blog post:
+ //
+ // http://blog.statscollector.com/a-plugin-renderer-for-jqplot-to-draw-a-hermite-spline/
+ //
+ // and download of the original plugin:
+ //
+ // http://blog.statscollector.com/wp-content/uploads/2010/02/jqplot.hermiteSplineRenderer.js
+ //////////
+
+ // called with scope of series
+ function computeHermiteSmoothedData (gd) {
+ var smooth = this.renderer.smooth;
+ var tension = this.renderer.tension;
+ var dim = this.canvas.getWidth();
+ var xp = this._xaxis.series_p2u;
+ var yp = this._yaxis.series_p2u;
+ var steps =null;
+ var _steps = null;
+ var a = null;
+ var a1 = null;
+ var a2 = null;
+ var slope = null;
+ var slope2 = null;
+ var temp = null;
+ var t, s, h1, h2, h3, h4;
+ var TiX, TiY, Ti1X, Ti1Y;
+ var pX, pY, p;
+ var sd = [];
+ var spd = [];
+ var dist = gd.length/dim;
+ var min, max, stretch, scale, shift;
+ var _smoothedData = [];
+ var _smoothedPlotData = [];
+ if (!isNaN(parseFloat(smooth))) {
+ steps = parseFloat(smooth);
+ }
+ else {
+ steps = getSteps(dist, 0.5);
+ }
+ if (!isNaN(parseFloat(tension))) {
+ tension = parseFloat(tension);
+ }
+
+ for (var i=0, l = gd.length-1; i < l; i++) {
+
+ if (tension === null) {
+ slope = Math.abs((gd[i+1][1] - gd[i][1]) / (gd[i+1][0] - gd[i][0]));
+
+ min = 0.3;
+ max = 0.6;
+ stretch = (max - min)/2.0;
+ scale = 2.5;
+ shift = -1.4;
+
+ temp = slope/scale + shift;
+
+ a1 = stretch * tanh(temp) - stretch * tanh(shift) + min;
+
+ // if have both left and right line segments, will use minimum tension.
+ if (i > 0) {
+ slope2 = Math.abs((gd[i][1] - gd[i-1][1]) / (gd[i][0] - gd[i-1][0]));
+ }
+ temp = slope2/scale + shift;
+
+ a2 = stretch * tanh(temp) - stretch * tanh(shift) + min;
+
+ a = (a1 + a2)/2.0;
+
+ }
+ else {
+ a = tension;
+ }
+ for (t=0; t < steps; t++) {
+ s = t / steps;
+ h1 = (1 + 2*s)*Math.pow((1-s),2);
+ h2 = s*Math.pow((1-s),2);
+ h3 = Math.pow(s,2)*(3-2*s);
+ h4 = Math.pow(s,2)*(s-1);
+
+ if (gd[i-1]) {
+ TiX = a * (gd[i+1][0] - gd[i-1][0]);
+ TiY = a * (gd[i+1][1] - gd[i-1][1]);
+ } else {
+ TiX = a * (gd[i+1][0] - gd[i][0]);
+ TiY = a * (gd[i+1][1] - gd[i][1]);
+ }
+ if (gd[i+2]) {
+ Ti1X = a * (gd[i+2][0] - gd[i][0]);
+ Ti1Y = a * (gd[i+2][1] - gd[i][1]);
+ } else {
+ Ti1X = a * (gd[i+1][0] - gd[i][0]);
+ Ti1Y = a * (gd[i+1][1] - gd[i][1]);
+ }
+
+ pX = h1*gd[i][0] + h3*gd[i+1][0] + h2*TiX + h4*Ti1X;
+ pY = h1*gd[i][1] + h3*gd[i+1][1] + h2*TiY + h4*Ti1Y;
+ p = [pX, pY];
+
+ _smoothedData.push(p);
+ _smoothedPlotData.push([xp(pX), yp(pY)]);
+ }
+ }
+ _smoothedData.push(gd[l]);
+ _smoothedPlotData.push([xp(gd[l][0]), yp(gd[l][1])]);
+
+ return [_smoothedData, _smoothedPlotData];
+ }
+
+ // setGridData
+ // converts the user data values to grid coordinates and stores them
+ // in the gridData array.
+ // Called with scope of a series.
+ $.jqplot.LineRenderer.prototype.setGridData = function(plot) {
+ // recalculate the grid data
+ var xp = this._xaxis.series_u2p;
+ var yp = this._yaxis.series_u2p;
+ var data = this._plotData;
+ var pdata = this._prevPlotData;
+ this.gridData = [];
+ this._prevGridData = [];
+ this.renderer._smoothedData = [];
+ this.renderer._smoothedPlotData = [];
+ this.renderer._hiBandGridData = [];
+ this.renderer._lowBandGridData = [];
+ this.renderer._hiBandSmoothedData = [];
+ this.renderer._lowBandSmoothedData = [];
+ var bands = this.renderer.bands;
+ var hasNull = false;
+ for (var i=0, l=data.length; i < l; i++) {
+ // if not a line series or if no nulls in data, push the converted point onto the array.
+ if (data[i][0] != null && data[i][1] != null) {
+ this.gridData.push([xp.call(this._xaxis, data[i][0]), yp.call(this._yaxis, data[i][1])]);
+ }
+ // else if there is a null, preserve it.
+ else if (data[i][0] == null) {
+ hasNull = true;
+ this.gridData.push([null, yp.call(this._yaxis, data[i][1])]);
+ }
+ else if (data[i][1] == null) {
+ hasNull = true;
+ this.gridData.push([xp.call(this._xaxis, data[i][0]), null]);
+ }
+ // if not a line series or if no nulls in data, push the converted point onto the array.
+ if (pdata[i] != null && pdata[i][0] != null && pdata[i][1] != null) {
+ this._prevGridData.push([xp.call(this._xaxis, pdata[i][0]), yp.call(this._yaxis, pdata[i][1])]);
+ }
+ // else if there is a null, preserve it.
+ else if (pdata[i] != null && pdata[i][0] == null) {
+ this._prevGridData.push([null, yp.call(this._yaxis, pdata[i][1])]);
+ }
+ else if (pdata[i] != null && pdata[i][0] != null && pdata[i][1] == null) {
+ this._prevGridData.push([xp.call(this._xaxis, pdata[i][0]), null]);
+ }
+ }
+
+ // don't do smoothing or bands on broken lines.
+ if (hasNull) {
+ this.renderer.smooth = false;
+ if (this._type === 'line') {
+ bands.show = false;
+ }
+ }
+
+ if (this._type === 'line' && bands.show) {
+ for (var i=0, l=bands.hiData.length; i<l; i++) {
+ this.renderer._hiBandGridData.push([xp.call(this._xaxis, bands.hiData[i][0]), yp.call(this._yaxis, bands.hiData[i][1])]);
+ }
+ for (var i=0, l=bands.lowData.length; i<l; i++) {
+ this.renderer._lowBandGridData.push([xp.call(this._xaxis, bands.lowData[i][0]), yp.call(this._yaxis, bands.lowData[i][1])]);
+ }
+ }
+
+ // calculate smoothed data if enough points and no nulls
+ if (this._type === 'line' && this.renderer.smooth && this.gridData.length > 2) {
+ var ret;
+ if (this.renderer.constrainSmoothing) {
+ ret = computeConstrainedSmoothedData.call(this, this.gridData);
+ this.renderer._smoothedData = ret[0];
+ this.renderer._smoothedPlotData = ret[1];
+
+ if (bands.show) {
+ ret = computeConstrainedSmoothedData.call(this, this.renderer._hiBandGridData);
+ this.renderer._hiBandSmoothedData = ret[0];
+ ret = computeConstrainedSmoothedData.call(this, this.renderer._lowBandGridData);
+ this.renderer._lowBandSmoothedData = ret[0];
+ }
+
+ ret = null;
+ }
+ else {
+ ret = computeHermiteSmoothedData.call(this, this.gridData);
+ this.renderer._smoothedData = ret[0];
+ this.renderer._smoothedPlotData = ret[1];
+
+ if (bands.show) {
+ ret = computeHermiteSmoothedData.call(this, this.renderer._hiBandGridData);
+ this.renderer._hiBandSmoothedData = ret[0];
+ ret = computeHermiteSmoothedData.call(this, this.renderer._lowBandGridData);
+ this.renderer._lowBandSmoothedData = ret[0];
+ }
+
+ ret = null;
+ }
+ }
+ };
+
+ // makeGridData
+ // converts any arbitrary data values to grid coordinates and
+ // returns them. This method exists so that plugins can use a series'
+ // linerenderer to generate grid data points without overwriting the
+ // grid data associated with that series.
+ // Called with scope of a series.
+ $.jqplot.LineRenderer.prototype.makeGridData = function(data, plot) {
+ // recalculate the grid data
+ var xp = this._xaxis.series_u2p;
+ var yp = this._yaxis.series_u2p;
+ var gd = [];
+ var pgd = [];
+ this.renderer._smoothedData = [];
+ this.renderer._smoothedPlotData = [];
+ this.renderer._hiBandGridData = [];
+ this.renderer._lowBandGridData = [];
+ this.renderer._hiBandSmoothedData = [];
+ this.renderer._lowBandSmoothedData = [];
+ var bands = this.renderer.bands;
+ var hasNull = false;
+ for (var i=0; i<data.length; i++) {
+ // if not a line series or if no nulls in data, push the converted point onto the array.
+ if (data[i][0] != null && data[i][1] != null) {
+ gd.push([xp.call(this._xaxis, data[i][0]), yp.call(this._yaxis, data[i][1])]);
+ }
+ // else if there is a null, preserve it.
+ else if (data[i][0] == null) {
+ hasNull = true;
+ gd.push([null, yp.call(this._yaxis, data[i][1])]);
+ }
+ else if (data[i][1] == null) {
+ hasNull = true;
+ gd.push([xp.call(this._xaxis, data[i][0]), null]);
+ }
+ }
+
+ // don't do smoothing or bands on broken lines.
+ if (hasNull) {
+ this.renderer.smooth = false;
+ if (this._type === 'line') {
+ bands.show = false;
+ }
+ }
+
+ if (this._type === 'line' && bands.show) {
+ for (var i=0, l=bands.hiData.length; i<l; i++) {
+ this.renderer._hiBandGridData.push([xp.call(this._xaxis, bands.hiData[i][0]), yp.call(this._yaxis, bands.hiData[i][1])]);
+ }
+ for (var i=0, l=bands.lowData.length; i<l; i++) {
+ this.renderer._lowBandGridData.push([xp.call(this._xaxis, bands.lowData[i][0]), yp.call(this._yaxis, bands.lowData[i][1])]);
+ }
+ }
+
+ if (this._type === 'line' && this.renderer.smooth && gd.length > 2) {
+ var ret;
+ if (this.renderer.constrainSmoothing) {
+ ret = computeConstrainedSmoothedData.call(this, gd);
+ this.renderer._smoothedData = ret[0];
+ this.renderer._smoothedPlotData = ret[1];
+
+ if (bands.show) {
+ ret = computeConstrainedSmoothedData.call(this, this.renderer._hiBandGridData);
+ this.renderer._hiBandSmoothedData = ret[0];
+ ret = computeConstrainedSmoothedData.call(this, this.renderer._lowBandGridData);
+ this.renderer._lowBandSmoothedData = ret[0];
+ }
+
+ ret = null;
+ }
+ else {
+ ret = computeHermiteSmoothedData.call(this, gd);
+ this.renderer._smoothedData = ret[0];
+ this.renderer._smoothedPlotData = ret[1];
+
+ if (bands.show) {
+ ret = computeHermiteSmoothedData.call(this, this.renderer._hiBandGridData);
+ this.renderer._hiBandSmoothedData = ret[0];
+ ret = computeHermiteSmoothedData.call(this, this.renderer._lowBandGridData);
+ this.renderer._lowBandSmoothedData = ret[0];
+ }
+
+ ret = null;
+ }
+ }
+ return gd;
+ };
+
+
+ // called within scope of series.
+ $.jqplot.LineRenderer.prototype.draw = function(ctx, gd, options, plot) {
+ var i;
+ // get a copy of the options, so we don't modify the original object.
+ var opts = $.extend(true, {}, options);
+ var shadow = (opts.shadow != undefined) ? opts.shadow : this.shadow;
+ var showLine = (opts.showLine != undefined) ? opts.showLine : this.showLine;
+ var fill = (opts.fill != undefined) ? opts.fill : this.fill;
+ var fillAndStroke = (opts.fillAndStroke != undefined) ? opts.fillAndStroke : this.fillAndStroke;
+ var xmin, ymin, xmax, ymax;
+ ctx.save();
+ if (gd.length) {
+ if (showLine) {
+ // if we fill, we'll have to add points to close the curve.
+ if (fill) {
+ if (this.fillToZero) {
+ // have to break line up into shapes at axis crossings
+ var negativeColor = this.negativeColor;
+ if (! this.useNegativeColors) {
+ negativeColor = opts.fillStyle;
+ }
+ var isnegative = false;
+ var posfs = opts.fillStyle;
+
+ // if stoking line as well as filling, get a copy of line data.
+ if (fillAndStroke) {
+ var fasgd = gd.slice(0);
+ }
+ // if not stacked, fill down to axis
+ if (this.index == 0 || !this._stack) {
+
+ var tempgd = [];
+ var pd = (this.renderer.smooth) ? this.renderer._smoothedPlotData : this._plotData;
+ this._areaPoints = [];
+ var pyzero = this._yaxis.series_u2p(this.fillToValue);
+ var pxzero = this._xaxis.series_u2p(this.fillToValue);
+
+ opts.closePath = true;
+
+ if (this.fillAxis == 'y') {
+ tempgd.push([gd[0][0], pyzero]);
+ this._areaPoints.push([gd[0][0], pyzero]);
+
+ for (var i=0; i<gd.length-1; i++) {
+ tempgd.push(gd[i]);
+ this._areaPoints.push(gd[i]);
+ // do we have an axis crossing?
+ if (pd[i][1] * pd[i+1][1] < 0) {
+ if (pd[i][1] < 0) {
+ isnegative = true;
+ opts.fillStyle = negativeColor;
+ }
+ else {
+ isnegative = false;
+ opts.fillStyle = posfs;
+ }
+
+ var xintercept = gd[i][0] + (gd[i+1][0] - gd[i][0]) * (pyzero-gd[i][1])/(gd[i+1][1] - gd[i][1]);
+ tempgd.push([xintercept, pyzero]);
+ this._areaPoints.push([xintercept, pyzero]);
+ // now draw this shape and shadow.
+ if (shadow) {
+ this.renderer.shadowRenderer.draw(ctx, tempgd, opts);
+ }
+ this.renderer.shapeRenderer.draw(ctx, tempgd, opts);
+ // now empty temp array and continue
+ tempgd = [[xintercept, pyzero]];
+ // this._areaPoints = [[xintercept, pyzero]];
+ }
+ }
+ if (pd[gd.length-1][1] < 0) {
+ isnegative = true;
+ opts.fillStyle = negativeColor;
+ }
+ else {
+ isnegative = false;
+ opts.fillStyle = posfs;
+ }
+ tempgd.push(gd[gd.length-1]);
+ this._areaPoints.push(gd[gd.length-1]);
+ tempgd.push([gd[gd.length-1][0], pyzero]);
+ this._areaPoints.push([gd[gd.length-1][0], pyzero]);
+ }
+ // now draw the last area.
+ if (shadow) {
+ this.renderer.shadowRenderer.draw(ctx, tempgd, opts);
+ }
+ this.renderer.shapeRenderer.draw(ctx, tempgd, opts);
+
+
+ // var gridymin = this._yaxis.series_u2p(0);
+ // // IE doesn't return new length on unshift
+ // gd.unshift([gd[0][0], gridymin]);
+ // len = gd.length;
+ // gd.push([gd[len - 1][0], gridymin]);
+ }
+ // if stacked, fill to line below
+ else {
+ var prev = this._prevGridData;
+ for (var i=prev.length; i>0; i--) {
+ gd.push(prev[i-1]);
+ // this._areaPoints.push(prev[i-1]);
+ }
+ if (shadow) {
+ this.renderer.shadowRenderer.draw(ctx, gd, opts);
+ }
+ this._areaPoints = gd;
+ this.renderer.shapeRenderer.draw(ctx, gd, opts);
+ }
+ }
+ /////////////////////////
+ // Not filled to zero
+ ////////////////////////
+ else {
+ // if stoking line as well as filling, get a copy of line data.
+ if (fillAndStroke) {
+ var fasgd = gd.slice(0);
+ }
+ // if not stacked, fill down to axis
+ if (this.index == 0 || !this._stack) {
+ // var gridymin = this._yaxis.series_u2p(this._yaxis.min) - this.gridBorderWidth / 2;
+ var gridymin = ctx.canvas.height;
+ // IE doesn't return new length on unshift
+ gd.unshift([gd[0][0], gridymin]);
+ var len = gd.length;
+ gd.push([gd[len - 1][0], gridymin]);
+ }
+ // if stacked, fill to line below
+ else {
+ var prev = this._prevGridData;
+ for (var i=prev.length; i>0; i--) {
+ gd.push(prev[i-1]);
+ }
+ }
+ this._areaPoints = gd;
+
+ if (shadow) {
+ this.renderer.shadowRenderer.draw(ctx, gd, opts);
+ }
+
+ this.renderer.shapeRenderer.draw(ctx, gd, opts);
+ }
+ if (fillAndStroke) {
+ var fasopts = $.extend(true, {}, opts, {fill:false, closePath:false});
+ this.renderer.shapeRenderer.draw(ctx, fasgd, fasopts);
+ //////////
+ // TODO: figure out some way to do shadows nicely
+ // if (shadow) {
+ // this.renderer.shadowRenderer.draw(ctx, fasgd, fasopts);
+ // }
+ // now draw the markers
+ if (this.markerRenderer.show) {
+ if (this.renderer.smooth) {
+ fasgd = this.gridData;
+ }
+ for (i=0; i<fasgd.length; i++) {
+ this.markerRenderer.draw(fasgd[i][0], fasgd[i][1], ctx, opts.markerOptions);
+ }
+ }
+ }
+ }
+ else {
+
+ if (this.renderer.bands.show) {
+ var bdat;
+ var bopts = $.extend(true, {}, opts);
+ if (this.renderer.bands.showLines) {
+ bdat = (this.renderer.smooth) ? this.renderer._hiBandSmoothedData : this.renderer._hiBandGridData;
+ this.renderer.shapeRenderer.draw(ctx, bdat, opts);
+ bdat = (this.renderer.smooth) ? this.renderer._lowBandSmoothedData : this.renderer._lowBandGridData;
+ this.renderer.shapeRenderer.draw(ctx, bdat, bopts);
+ }
+
+ if (this.renderer.bands.fill) {
+ if (this.renderer.smooth) {
+ bdat = this.renderer._hiBandSmoothedData.concat(this.renderer._lowBandSmoothedData.reverse());
+ }
+ else {
+ bdat = this.renderer._hiBandGridData.concat(this.renderer._lowBandGridData.reverse());
+ }
+ this._areaPoints = bdat;
+ bopts.closePath = true;
+ bopts.fill = true;
+ bopts.fillStyle = this.renderer.bands.fillColor;
+ this.renderer.shapeRenderer.draw(ctx, bdat, bopts);
+ }
+ }
+
+ if (shadow) {
+ this.renderer.shadowRenderer.draw(ctx, gd, opts);
+ }
+
+ this.renderer.shapeRenderer.draw(ctx, gd, opts);
+ }
+ }
+ // calculate the bounding box
+ var xmin = xmax = ymin = ymax = null;
+ for (i=0; i<this._areaPoints.length; i++) {
+ var p = this._areaPoints[i];
+ if (xmin > p[0] || xmin == null) {
+ xmin = p[0];
+ }
+ if (ymax < p[1] || ymax == null) {
+ ymax = p[1];
+ }
+ if (xmax < p[0] || xmax == null) {
+ xmax = p[0];
+ }
+ if (ymin > p[1] || ymin == null) {
+ ymin = p[1];
+ }
+ }
+
+ if (this.type === 'line' && this.renderer.bands.show) {
+ ymax = this._yaxis.series_u2p(this.renderer.bands._min);
+ ymin = this._yaxis.series_u2p(this.renderer.bands._max);
+ }
+
+ this._boundingBox = [[xmin, ymax], [xmax, ymin]];
+
+ // now draw the markers
+ if (this.markerRenderer.show && !fill) {
+ if (this.renderer.smooth) {
+ gd = this.gridData;
+ }
+ for (i=0; i<gd.length; i++) {
+ if (gd[i][0] != null && gd[i][1] != null) {
+ this.markerRenderer.draw(gd[i][0], gd[i][1], ctx, opts.markerOptions);
+ }
+ }
+ }
+ }
+
+ ctx.restore();
+ };
+
+ $.jqplot.LineRenderer.prototype.drawShadow = function(ctx, gd, options) {
+ // This is a no-op, shadows drawn with lines.
+ };
+
+ // called with scope of plot.
+ // make sure to not leave anything highlighted.
+ function postInit(target, data, options) {
+ for (var i=0; i<this.series.length; i++) {
+ if (this.series[i].renderer.constructor == $.jqplot.LineRenderer) {
+ // don't allow mouseover and mousedown at same time.
+ if (this.series[i].highlightMouseOver) {
+ this.series[i].highlightMouseDown = false;
+ }
+ }
+ }
+ }
+
+ // called within context of plot
+ // create a canvas which we can draw on.
+ // insert it before the eventCanvas, so eventCanvas will still capture events.
+ function postPlotDraw() {
+ // Memory Leaks patch
+ if (this.plugins.lineRenderer && this.plugins.lineRenderer.highlightCanvas) {
+ this.plugins.lineRenderer.highlightCanvas.resetCanvas();
+ this.plugins.lineRenderer.highlightCanvas = null;
+ }
+
+ this.plugins.lineRenderer.highlightedSeriesIndex = null;
+ this.plugins.lineRenderer.highlightCanvas = new $.jqplot.GenericCanvas();
+
+ this.eventCanvas._elem.before(this.plugins.lineRenderer.highlightCanvas.createElement(this._gridPadding, 'jqplot-lineRenderer-highlight-canvas', this._plotDimensions, this));
+ this.plugins.lineRenderer.highlightCanvas.setContext();
+ this.eventCanvas._elem.bind('mouseleave', {plot:this}, function (ev) { unhighlight(ev.data.plot); });
+ }
+
+ function highlight (plot, sidx, pidx, points) {
+ var s = plot.series[sidx];
+ var canvas = plot.plugins.lineRenderer.highlightCanvas;
+ canvas._ctx.clearRect(0,0,canvas._ctx.canvas.width, canvas._ctx.canvas.height);
+ s._highlightedPoint = pidx;
+ plot.plugins.lineRenderer.highlightedSeriesIndex = sidx;
+ var opts = {fillStyle: s.highlightColor};
+ if (s.type === 'line' && s.renderer.bands.show) {
+ opts.fill = true;
+ opts.closePath = true;
+ }
+ s.renderer.shapeRenderer.draw(canvas._ctx, points, opts);
+ canvas = null;
+ }
+
+ function unhighlight (plot) {
+ var canvas = plot.plugins.lineRenderer.highlightCanvas;
+ canvas._ctx.clearRect(0,0, canvas._ctx.canvas.width, canvas._ctx.canvas.height);
+ for (var i=0; i<plot.series.length; i++) {
+ plot.series[i]._highlightedPoint = null;
+ }
+ plot.plugins.lineRenderer.highlightedSeriesIndex = null;
+ plot.target.trigger('jqplotDataUnhighlight');
+ canvas = null;
+ }
+
+
+ function handleMove(ev, gridpos, datapos, neighbor, plot) {
+ if (neighbor) {
+ var ins = [neighbor.seriesIndex, neighbor.pointIndex, neighbor.data];
+ var evt1 = jQuery.Event('jqplotDataMouseOver');
+ evt1.pageX = ev.pageX;
+ evt1.pageY = ev.pageY;
+ plot.target.trigger(evt1, ins);
+ if (plot.series[ins[0]].highlightMouseOver && !(ins[0] == plot.plugins.lineRenderer.highlightedSeriesIndex)) {
+ var evt = jQuery.Event('jqplotDataHighlight');
+ evt.which = ev.which;
+ evt.pageX = ev.pageX;
+ evt.pageY = ev.pageY;
+ plot.target.trigger(evt, ins);
+ highlight (plot, neighbor.seriesIndex, neighbor.pointIndex, neighbor.points);
+ }
+ }
+ else if (neighbor == null) {
+ unhighlight (plot);
+ }
+ }
+
+ function handleMouseDown(ev, gridpos, datapos, neighbor, plot) {
+ if (neighbor) {
+ var ins = [neighbor.seriesIndex, neighbor.pointIndex, neighbor.data];
+ if (plot.series[ins[0]].highlightMouseDown && !(ins[0] == plot.plugins.lineRenderer.highlightedSeriesIndex)) {
+ var evt = jQuery.Event('jqplotDataHighlight');
+ evt.which = ev.which;
+ evt.pageX = ev.pageX;
+ evt.pageY = ev.pageY;
+ plot.target.trigger(evt, ins);
+ highlight (plot, neighbor.seriesIndex, neighbor.pointIndex, neighbor.points);
+ }
+ }
+ else if (neighbor == null) {
+ unhighlight (plot);
+ }
+ }
+
+ function handleMouseUp(ev, gridpos, datapos, neighbor, plot) {
+ var idx = plot.plugins.lineRenderer.highlightedSeriesIndex;
+ if (idx != null && plot.series[idx].highlightMouseDown) {
+ unhighlight(plot);
+ }
+ }
+
+ function handleClick(ev, gridpos, datapos, neighbor, plot) {
+ if (neighbor) {
+ var ins = [neighbor.seriesIndex, neighbor.pointIndex, neighbor.data];
+ var evt = jQuery.Event('jqplotDataClick');
+ evt.which = ev.which;
+ evt.pageX = ev.pageX;
+ evt.pageY = ev.pageY;
+ plot.target.trigger(evt, ins);
+ }
+ }
+
+ function handleRightClick(ev, gridpos, datapos, neighbor, plot) {
+ if (neighbor) {
+ var ins = [neighbor.seriesIndex, neighbor.pointIndex, neighbor.data];
+ var idx = plot.plugins.lineRenderer.highlightedSeriesIndex;
+ if (idx != null && plot.series[idx].highlightMouseDown) {
+ unhighlight(plot);
+ }
+ var evt = jQuery.Event('jqplotDataRightClick');
+ evt.which = ev.which;
+ evt.pageX = ev.pageX;
+ evt.pageY = ev.pageY;
+ plot.target.trigger(evt, ins);
+ }
+ }
+
+
+ // class: $.jqplot.LinearAxisRenderer
+ // The default jqPlot axis renderer, creating a numeric axis.
+ $.jqplot.LinearAxisRenderer = function() {
+ };
+
+ // called with scope of axis object.
+ $.jqplot.LinearAxisRenderer.prototype.init = function(options){
+ // prop: breakPoints
+ // EXPERIMENTAL!! Use at your own risk!
+ // Works only with linear axes and the default tick renderer.
+ // Array of [start, stop] points to create a broken axis.
+ // Broken axes have a "jump" in them, which is an immediate
+ // transition from a smaller value to a larger value.
+ // Currently, axis ticks MUST be manually assigned if using breakPoints
+ // by using the axis ticks array option.
+ this.breakPoints = null;
+ // prop: breakTickLabel
+ // Label to use at the axis break if breakPoints are specified.
+ this.breakTickLabel = "&asymp;";
+ // prop: drawBaseline
+ // True to draw the axis baseline.
+ this.drawBaseline = true;
+ // prop: baselineWidth
+ // width of the baseline in pixels.
+ this.baselineWidth = null;
+ // prop: baselineColor
+ // CSS color spec for the baseline.
+ this.baselineColor = null;
+ // prop: forceTickAt0
+ // This will ensure that there is always a tick mark at 0.
+ // If data range is strictly positive or negative,
+ // this will force 0 to be inside the axis bounds unless
+ // the appropriate axis pad (pad, padMin or padMax) is set
+ // to 0, then this will force an axis min or max value at 0.
+ // This has know effect when any of the following options
+ // are set: autoscale, min, max, numberTicks or tickInterval.
+ this.forceTickAt0 = false;
+ // prop: forceTickAt100
+ // This will ensure that there is always a tick mark at 100.
+ // If data range is strictly above or below 100,
+ // this will force 100 to be inside the axis bounds unless
+ // the appropriate axis pad (pad, padMin or padMax) is set
+ // to 0, then this will force an axis min or max value at 100.
+ // This has know effect when any of the following options
+ // are set: autoscale, min, max, numberTicks or tickInterval.
+ this.forceTickAt100 = false;
+ // prop: tickInset
+ // Controls the amount to inset the first and last ticks from
+ // the edges of the grid, in multiples of the tick interval.
+ // 0 is no inset, 0.5 is one half a tick interval, 1 is a full
+ // tick interval, etc.
+ this.tickInset = 0;
+ // prop: minorTicks
+ // Number of ticks to add between "major" ticks.
+ // Major ticks are ticks supplied by user or auto computed.
+ // Minor ticks cannot be created by user.
+ this.minorTicks = 0;
+ // prop: alignTicks
+ // true to align tick marks across opposed axes
+ // such as from the y2axis to yaxis.
+ this.alignTicks = false;
+ this._autoFormatString = '';
+ this._overrideFormatString = false;
+ this._scalefact = 1.0;
+ $.extend(true, this, options);
+ if (this.breakPoints) {
+ if (!$.isArray(this.breakPoints)) {
+ this.breakPoints = null;
+ }
+ else if (this.breakPoints.length < 2 || this.breakPoints[1] <= this.breakPoints[0]) {
+ this.breakPoints = null;
+ }
+ }
+ if (this.numberTicks != null && this.numberTicks < 2) {
+ this.numberTicks = 2;
+ }
+ this.resetDataBounds();
+ };
+
+ // called with scope of axis
+ $.jqplot.LinearAxisRenderer.prototype.draw = function(ctx, plot) {
+ if (this.show) {
+ // populate the axis label and value properties.
+ // createTicks is a method on the renderer, but
+ // call it within the scope of the axis.
+ this.renderer.createTicks.call(this, plot);
+ // fill a div with axes labels in the right direction.
+ // Need to pregenerate each axis to get it's bounds and
+ // position it and the labels correctly on the plot.
+ var dim=0;
+ var temp;
+ // Added for theming.
+ if (this._elem) {
+ // Memory Leaks patch
+ //this._elem.empty();
+ this._elem.emptyForce();
+ this._elem = null;
+ }
+
+ this._elem = $(document.createElement('div'));
+ this._elem.addClass('jqplot-axis jqplot-'+this.name);
+ this._elem.css('position', 'absolute');
+
+
+ if (this.name == 'xaxis' || this.name == 'x2axis') {
+ this._elem.width(this._plotDimensions.width);
+ }
+ else {
+ this._elem.height(this._plotDimensions.height);
+ }
+
+ // create a _label object.
+ this.labelOptions.axis = this.name;
+ this._label = new this.labelRenderer(this.labelOptions);
+ if (this._label.show) {
+ var elem = this._label.draw(ctx, plot);
+ elem.appendTo(this._elem);
+ elem = null;
+ }
+
+ var t = this._ticks;
+ var tick;
+ for (var i=0; i<t.length; i++) {
+ tick = t[i];
+ if (tick.show && tick.showLabel && (!tick.isMinorTick || this.showMinorTicks)) {
+ this._elem.append(tick.draw(ctx, plot));
+ }
+ }
+ tick = null;
+ t = null;
+ }
+ return this._elem;
+ };
+
+ // called with scope of an axis
+ $.jqplot.LinearAxisRenderer.prototype.reset = function() {
+ this.min = this._options.min;
+ this.max = this._options.max;
+ this.tickInterval = this._options.tickInterval;
+ this.numberTicks = this._options.numberTicks;
+ this._autoFormatString = '';
+ if (this._overrideFormatString && this.tickOptions && this.tickOptions.formatString) {
+ this.tickOptions.formatString = '';
+ }
+
+ // this._ticks = this.__ticks;
+ };
+
+ // called with scope of axis
+ $.jqplot.LinearAxisRenderer.prototype.set = function() {
+ var dim = 0;
+ var temp;
+ var w = 0;
+ var h = 0;
+ var lshow = (this._label == null) ? false : this._label.show;
+ if (this.show) {
+ var t = this._ticks;
+ var tick;
+ for (var i=0; i<t.length; i++) {
+ tick = t[i];
+ if (!tick._breakTick && tick.show && tick.showLabel && (!tick.isMinorTick || this.showMinorTicks)) {
+ if (this.name == 'xaxis' || this.name == 'x2axis') {
+ temp = tick._elem.outerHeight(true);
+ }
+ else {
+ temp = tick._elem.outerWidth(true);
+ }
+ if (temp > dim) {
+ dim = temp;
+ }
+ }
+ }
+ tick = null;
+ t = null;
+
+ if (lshow) {
+ w = this._label._elem.outerWidth(true);
+ h = this._label._elem.outerHeight(true);
+ }
+ if (this.name == 'xaxis') {
+ dim = dim + h;
+ this._elem.css({'height':dim+'px', left:'0px', bottom:'0px'});
+ }
+ else if (this.name == 'x2axis') {
+ dim = dim + h;
+ this._elem.css({'height':dim+'px', left:'0px', top:'0px'});
+ }
+ else if (this.name == 'yaxis') {
+ dim = dim + w;
+ this._elem.css({'width':dim+'px', left:'0px', top:'0px'});
+ if (lshow && this._label.constructor == $.jqplot.AxisLabelRenderer) {
+ this._label._elem.css('width', w+'px');
+ }
+ }
+ else {
+ dim = dim + w;
+ this._elem.css({'width':dim+'px', right:'0px', top:'0px'});
+ if (lshow && this._label.constructor == $.jqplot.AxisLabelRenderer) {
+ this._label._elem.css('width', w+'px');
+ }
+ }
+ }
+ };
+
+ // called with scope of axis
+ $.jqplot.LinearAxisRenderer.prototype.createTicks = function(plot) {
+ // we're are operating on an axis here
+ var ticks = this._ticks;
+ var userTicks = this.ticks;
+ var name = this.name;
+ // databounds were set on axis initialization.
+ var db = this._dataBounds;
+ var dim = (this.name.charAt(0) === 'x') ? this._plotDimensions.width : this._plotDimensions.height;
+ var interval;
+ var min, max;
+ var pos1, pos2;
+ var tt, i;
+ // get a copy of user's settings for min/max.
+ var userMin = this.min;
+ var userMax = this.max;
+ var userNT = this.numberTicks;
+ var userTI = this.tickInterval;
+
+ var threshold = 30;
+ this._scalefact = (Math.max(dim, threshold+1) - threshold)/300.0;
+
+ // if we already have ticks, use them.
+ // ticks must be in order of increasing value.
+
+ if (userTicks.length) {
+ // ticks could be 1D or 2D array of [val, val, ,,,] or [[val, label], [val, label], ...] or mixed
+ for (i=0; i<userTicks.length; i++){
+ var ut = userTicks[i];
+ var t = new this.tickRenderer(this.tickOptions);
+ if ($.isArray(ut)) {
+ t.value = ut[0];
+ if (this.breakPoints) {
+ if (ut[0] == this.breakPoints[0]) {
+ t.label = this.breakTickLabel;
+ t._breakTick = true;
+ t.showGridline = false;
+ t.showMark = false;
+ }
+ else if (ut[0] > this.breakPoints[0] && ut[0] <= this.breakPoints[1]) {
+ t.show = false;
+ t.showGridline = false;
+ t.label = ut[1];
+ }
+ else {
+ t.label = ut[1];
+ }
+ }
+ else {
+ t.label = ut[1];
+ }
+ t.setTick(ut[0], this.name);
+ this._ticks.push(t);
+ }
+
+ else if ($.isPlainObject(ut)) {
+ $.extend(true, t, ut);
+ t.axis = this.name;
+ this._ticks.push(t);
+ }
+
+ else {
+ t.value = ut;
+ if (this.breakPoints) {
+ if (ut == this.breakPoints[0]) {
+ t.label = this.breakTickLabel;
+ t._breakTick = true;
+ t.showGridline = false;
+ t.showMark = false;
+ }
+ else if (ut > this.breakPoints[0] && ut <= this.breakPoints[1]) {
+ t.show = false;
+ t.showGridline = false;
+ }
+ }
+ t.setTick(ut, this.name);
+ this._ticks.push(t);
+ }
+ }
+ this.numberTicks = userTicks.length;
+ this.min = this._ticks[0].value;
+ this.max = this._ticks[this.numberTicks-1].value;
+ this.tickInterval = (this.max - this.min) / (this.numberTicks - 1);
+ }
+
+ // we don't have any ticks yet, let's make some!
+ else {
+ if (name == 'xaxis' || name == 'x2axis') {
+ dim = this._plotDimensions.width;
+ }
+ else {
+ dim = this._plotDimensions.height;
+ }
+
+ var _numberTicks = this.numberTicks;
+
+ // if aligning this axis, use number of ticks from previous axis.
+ // Do I need to reset somehow if alignTicks is changed and then graph is replotted??
+ if (this.alignTicks) {
+ if (this.name === 'x2axis' && plot.axes.xaxis.show) {
+ _numberTicks = plot.axes.xaxis.numberTicks;
+ }
+ else if (this.name.charAt(0) === 'y' && this.name !== 'yaxis' && this.name !== 'yMidAxis' && plot.axes.yaxis.show) {
+ _numberTicks = plot.axes.yaxis.numberTicks;
+ }
+ }
+
+ min = ((this.min != null) ? this.min : db.min);
+ max = ((this.max != null) ? this.max : db.max);
+
+ var range = max - min;
+ var rmin, rmax;
+ var temp;
+
+ if (this.tickOptions == null || !this.tickOptions.formatString) {
+ this._overrideFormatString = true;
+ }
+
+ // Doing complete autoscaling
+ if (this.min == null || this.max == null && this.tickInterval == null && !this.autoscale) {
+ // Check if user must have tick at 0 or 100 and ensure they are in range.
+ // The autoscaling algorithm will always place ticks at 0 and 100 if they are in range.
+ if (this.forceTickAt0) {
+ if (min > 0) {
+ min = 0;
+ }
+ if (max < 0) {
+ max = 0;
+ }
+ }
+
+ if (this.forceTickAt100) {
+ if (min > 100) {
+ min = 100;
+ }
+ if (max < 100) {
+ max = 100;
+ }
+ }
+
+ var keepMin = false,
+ keepMax = false;
+
+ if (this.min != null) {
+ keepMin = true;
+ }
+
+ else if (this.max != null) {
+ keepMax = true;
+ }
+
+ // var threshold = 30;
+ // var tdim = Math.max(dim, threshold+1);
+ // this._scalefact = (tdim-threshold)/300.0;
+ var ret = $.jqplot.LinearTickGenerator(min, max, this._scalefact, _numberTicks, keepMin, keepMax);
+ // calculate a padded max and min, points should be less than these
+ // so that they aren't too close to the edges of the plot.
+ // User can adjust how much padding is allowed with pad, padMin and PadMax options.
+ // If min or max is set, don't pad that end of axis.
+ var tumin = (this.min != null) ? min : min + range*(this.padMin - 1);
+ var tumax = (this.max != null) ? max : max - range*(this.padMax - 1);
+
+ // if they're equal, we shouldn't have to do anything, right?
+ // if (min <=tumin || max >= tumax) {
+ if (min <tumin || max > tumax) {
+ tumin = (this.min != null) ? min : min - range*(this.padMin - 1);
+ tumax = (this.max != null) ? max : max + range*(this.padMax - 1);
+ ret = $.jqplot.LinearTickGenerator(tumin, tumax, this._scalefact, _numberTicks, keepMin, keepMax);
+ }
+
+ this.min = ret[0];
+ this.max = ret[1];
+ // if numberTicks specified, it should return the same.
+ this.numberTicks = ret[2];
+ this._autoFormatString = ret[3];
+ this.tickInterval = ret[4];
+ }
+
+ // User has specified some axis scale related option, can use auto algorithm
+ else {
+
+ // if min and max are same, space them out a bit
+ if (min == max) {
+ var adj = 0.05;
+ if (min > 0) {
+ adj = Math.max(Math.log(min)/Math.LN10, 0.05);
+ }
+ min -= adj;
+ max += adj;
+ }
+
+ // autoscale. Can't autoscale if min or max is supplied.
+ // Will use numberTicks and tickInterval if supplied. Ticks
+ // across multiple axes may not line up depending on how
+ // bars are to be plotted.
+ if (this.autoscale && this.min == null && this.max == null) {
+ var rrange, ti, margin;
+ var forceMinZero = false;
+ var forceZeroLine = false;
+ var intervals = {min:null, max:null, average:null, stddev:null};
+ // if any series are bars, or if any are fill to zero, and if this
+ // is the axis to fill toward, check to see if we can start axis at zero.
+ for (var i=0; i<this._series.length; i++) {
+ var s = this._series[i];
+ var faname = (s.fillAxis == 'x') ? s._xaxis.name : s._yaxis.name;
+ // check to see if this is the fill axis
+ if (this.name == faname) {
+ var vals = s._plotValues[s.fillAxis];
+ var vmin = vals[0];
+ var vmax = vals[0];
+ for (var j=1; j<vals.length; j++) {
+ if (vals[j] < vmin) {
+ vmin = vals[j];
+ }
+ else if (vals[j] > vmax) {
+ vmax = vals[j];
+ }
+ }
+ var dp = (vmax - vmin) / vmax;
+ // is this sries a bar?
+ if (s.renderer.constructor == $.jqplot.BarRenderer) {
+ // if no negative values and could also check range.
+ if (vmin >= 0 && (s.fillToZero || dp > 0.1)) {
+ forceMinZero = true;
+ }
+ else {
+ forceMinZero = false;
+ if (s.fill && s.fillToZero && vmin < 0 && vmax > 0) {
+ forceZeroLine = true;
+ }
+ else {
+ forceZeroLine = false;
+ }
+ }
+ }
+
+ // if not a bar and filling, use appropriate method.
+ else if (s.fill) {
+ if (vmin >= 0 && (s.fillToZero || dp > 0.1)) {
+ forceMinZero = true;
+ }
+ else if (vmin < 0 && vmax > 0 && s.fillToZero) {
+ forceMinZero = false;
+ forceZeroLine = true;
+ }
+ else {
+ forceMinZero = false;
+ forceZeroLine = false;
+ }
+ }
+
+ // if not a bar and not filling, only change existing state
+ // if it doesn't make sense
+ else if (vmin < 0) {
+ forceMinZero = false;
+ }
+ }
+ }
+
+ // check if we need make axis min at 0.
+ if (forceMinZero) {
+ // compute number of ticks
+ this.numberTicks = 2 + Math.ceil((dim-(this.tickSpacing-1))/this.tickSpacing);
+ this.min = 0;
+ userMin = 0;
+ // what order is this range?
+ // what tick interval does that give us?
+ ti = max/(this.numberTicks-1);
+ temp = Math.pow(10, Math.abs(Math.floor(Math.log(ti)/Math.LN10)));
+ if (ti/temp == parseInt(ti/temp, 10)) {
+ ti += temp;
+ }
+ this.tickInterval = Math.ceil(ti/temp) * temp;
+ this.max = this.tickInterval * (this.numberTicks - 1);
+ }
+
+ // check if we need to make sure there is a tick at 0.
+ else if (forceZeroLine) {
+ // compute number of ticks
+ this.numberTicks = 2 + Math.ceil((dim-(this.tickSpacing-1))/this.tickSpacing);
+ var ntmin = Math.ceil(Math.abs(min)/range*(this.numberTicks-1));
+ var ntmax = this.numberTicks - 1 - ntmin;
+ ti = Math.max(Math.abs(min/ntmin), Math.abs(max/ntmax));
+ temp = Math.pow(10, Math.abs(Math.floor(Math.log(ti)/Math.LN10)));
+ this.tickInterval = Math.ceil(ti/temp) * temp;
+ this.max = this.tickInterval * ntmax;
+ this.min = -this.tickInterval * ntmin;
+ }
+
+ // if nothing else, do autoscaling which will try to line up ticks across axes.
+ else {
+ if (this.numberTicks == null){
+ if (this.tickInterval) {
+ this.numberTicks = 3 + Math.ceil(range / this.tickInterval);
+ }
+ else {
+ this.numberTicks = 2 + Math.ceil((dim-(this.tickSpacing-1))/this.tickSpacing);
+ }
+ }
+
+ if (this.tickInterval == null) {
+ // get a tick interval
+ ti = range/(this.numberTicks - 1);
+
+ if (ti < 1) {
+ temp = Math.pow(10, Math.abs(Math.floor(Math.log(ti)/Math.LN10)));
+ }
+ else {
+ temp = 1;
+ }
+ this.tickInterval = Math.ceil(ti*temp*this.pad)/temp;
+ }
+ else {
+ temp = 1 / this.tickInterval;
+ }
+
+ // try to compute a nicer, more even tick interval
+ // temp = Math.pow(10, Math.floor(Math.log(ti)/Math.LN10));
+ // this.tickInterval = Math.ceil(ti/temp) * temp;
+ rrange = this.tickInterval * (this.numberTicks - 1);
+ margin = (rrange - range)/2;
+
+ if (this.min == null) {
+ this.min = Math.floor(temp*(min-margin))/temp;
+ }
+ if (this.max == null) {
+ this.max = this.min + rrange;
+ }
+ }
+
+ // Compute a somewhat decent format string if it is needed.
+ // get precision of interval and determine a format string.
+ var sf = $.jqplot.getSignificantFigures(this.tickInterval);
+
+ var fstr;
+
+ // if we have only a whole number, use integer formatting
+ if (sf.digitsLeft >= sf.significantDigits) {
+ fstr = '%d';
+ }
+
+ else {
+ var temp = Math.max(0, 5 - sf.digitsLeft);
+ temp = Math.min(temp, sf.digitsRight);
+ fstr = '%.'+ temp + 'f';
+ }
+
+ this._autoFormatString = fstr;
+ }
+
+ // Use the default algorithm which pads each axis to make the chart
+ // centered nicely on the grid.
+ else {
+
+ rmin = (this.min != null) ? this.min : min - range*(this.padMin - 1);
+ rmax = (this.max != null) ? this.max : max + range*(this.padMax - 1);
+ range = rmax - rmin;
+
+ if (this.numberTicks == null){
+ // if tickInterval is specified by user, we will ignore computed maximum.
+ // max will be equal or greater to fit even # of ticks.
+ if (this.tickInterval != null) {
+ this.numberTicks = Math.ceil((rmax - rmin)/this.tickInterval)+1;
+ }
+ else if (dim > 100) {
+ this.numberTicks = parseInt(3+(dim-100)/75, 10);
+ }
+ else {
+ this.numberTicks = 2;
+ }
+ }
+
+ if (this.tickInterval == null) {
+ this.tickInterval = range / (this.numberTicks-1);
+ }
+
+ if (this.max == null) {
+ rmax = rmin + this.tickInterval*(this.numberTicks - 1);
+ }
+ if (this.min == null) {
+ rmin = rmax - this.tickInterval*(this.numberTicks - 1);
+ }
+
+ // get precision of interval and determine a format string.
+ var sf = $.jqplot.getSignificantFigures(this.tickInterval);
+
+ var fstr;
+
+ // if we have only a whole number, use integer formatting
+ if (sf.digitsLeft >= sf.significantDigits) {
+ fstr = '%d';
+ }
+
+ else {
+ var temp = Math.max(0, 5 - sf.digitsLeft);
+ temp = Math.min(temp, sf.digitsRight);
+ fstr = '%.'+ temp + 'f';
+ }
+
+
+ this._autoFormatString = fstr;
+
+ this.min = rmin;
+ this.max = rmax;
+ }
+
+ if (this.renderer.constructor == $.jqplot.LinearAxisRenderer && this._autoFormatString == '') {
+ // fix for misleading tick display with small range and low precision.
+ range = this.max - this.min;
+ // figure out precision
+ var temptick = new this.tickRenderer(this.tickOptions);
+ // use the tick formatString or, the default.
+ var fs = temptick.formatString || $.jqplot.config.defaultTickFormatString;
+ var fs = fs.match($.jqplot.sprintf.regex)[0];
+ var precision = 0;
+ if (fs) {
+ if (fs.search(/[fFeEgGpP]/) > -1) {
+ var m = fs.match(/\%\.(\d{0,})?[eEfFgGpP]/);
+ if (m) {
+ precision = parseInt(m[1], 10);
+ }
+ else {
+ precision = 6;
+ }
+ }
+ else if (fs.search(/[di]/) > -1) {
+ precision = 0;
+ }
+ // fact will be <= 1;
+ var fact = Math.pow(10, -precision);
+ if (this.tickInterval < fact) {
+ // need to correct underrange
+ if (userNT == null && userTI == null) {
+ this.tickInterval = fact;
+ if (userMax == null && userMin == null) {
+ // this.min = Math.floor((this._dataBounds.min - this.tickInterval)/fact) * fact;
+ this.min = Math.floor(this._dataBounds.min/fact) * fact;
+ if (this.min == this._dataBounds.min) {
+ this.min = this._dataBounds.min - this.tickInterval;
+ }
+ // this.max = Math.ceil((this._dataBounds.max + this.tickInterval)/fact) * fact;
+ this.max = Math.ceil(this._dataBounds.max/fact) * fact;
+ if (this.max == this._dataBounds.max) {
+ this.max = this._dataBounds.max + this.tickInterval;
+ }
+ var n = (this.max - this.min)/this.tickInterval;
+ n = n.toFixed(11);
+ n = Math.ceil(n);
+ this.numberTicks = n + 1;
+ }
+ else if (userMax == null) {
+ // add one tick for top of range.
+ var n = (this._dataBounds.max - this.min) / this.tickInterval;
+ n = n.toFixed(11);
+ this.numberTicks = Math.ceil(n) + 2;
+ this.max = this.min + this.tickInterval * (this.numberTicks-1);
+ }
+ else if (userMin == null) {
+ // add one tick for bottom of range.
+ var n = (this.max - this._dataBounds.min) / this.tickInterval;
+ n = n.toFixed(11);
+ this.numberTicks = Math.ceil(n) + 2;
+ this.min = this.max - this.tickInterval * (this.numberTicks-1);
+ }
+ else {
+ // calculate a number of ticks so max is within axis scale
+ this.numberTicks = Math.ceil((userMax - userMin)/this.tickInterval) + 1;
+ // if user's min and max don't fit evenly in ticks, adjust.
+ // This takes care of cases such as user min set to 0, max set to 3.5 but tick
+ // format string set to %d (integer ticks)
+ this.min = Math.floor(userMin*Math.pow(10, precision))/Math.pow(10, precision);
+ this.max = Math.ceil(userMax*Math.pow(10, precision))/Math.pow(10, precision);
+ // this.max = this.min + this.tickInterval*(this.numberTicks-1);
+ this.numberTicks = Math.ceil((this.max - this.min)/this.tickInterval) + 1;
+ }
+ }
+ }
+ }
+ }
+
+ }
+
+ if (this._overrideFormatString && this._autoFormatString != '') {
+ this.tickOptions = this.tickOptions || {};
+ this.tickOptions.formatString = this._autoFormatString;
+ }
+
+ var t, to;
+ for (var i=0; i<this.numberTicks; i++){
+ tt = this.min + i * this.tickInterval;
+ t = new this.tickRenderer(this.tickOptions);
+ // var t = new $.jqplot.AxisTickRenderer(this.tickOptions);
+
+ t.setTick(tt, this.name);
+ this._ticks.push(t);
+
+ if (i < this.numberTicks - 1) {
+ for (var j=0; j<this.minorTicks; j++) {
+ tt += this.tickInterval/(this.minorTicks+1);
+ to = $.extend(true, {}, this.tickOptions, {name:this.name, value:tt, label:'', isMinorTick:true});
+ t = new this.tickRenderer(to);
+ this._ticks.push(t);
+ }
+ }
+ t = null;
+ }
+ }
+
+ if (this.tickInset) {
+ this.min = this.min - this.tickInset * this.tickInterval;
+ this.max = this.max + this.tickInset * this.tickInterval;
+ }
+
+ ticks = null;
+ };
+
+ // Used to reset just the values of the ticks and then repack, which will
+ // recalculate the positioning functions. It is assuemd that the
+ // number of ticks is the same and the values of the new array are at the
+ // proper interval.
+ // This method needs to be called with the scope of an axis object, like:
+ //
+ // > plot.axes.yaxis.renderer.resetTickValues.call(plot.axes.yaxis, yarr);
+ //
+ $.jqplot.LinearAxisRenderer.prototype.resetTickValues = function(opts) {
+ if ($.isArray(opts) && opts.length == this._ticks.length) {
+ var t;
+ for (var i=0; i<opts.length; i++) {
+ t = this._ticks[i];
+ t.value = opts[i];
+ t.label = t.formatter(t.formatString, opts[i]);
+ t.label = t.prefix + t.label;
+ t._elem.html(t.label);
+ }
+ t = null;
+ this.min = $.jqplot.arrayMin(opts);
+ this.max = $.jqplot.arrayMax(opts);
+ this.pack();
+ }
+ // Not implemented yet.
+ // else if ($.isPlainObject(opts)) {
+ //
+ // }
+ };
+
+ // called with scope of axis
+ $.jqplot.LinearAxisRenderer.prototype.pack = function(pos, offsets) {
+ // Add defaults for repacking from resetTickValues function.
+ pos = pos || {};
+ offsets = offsets || this._offsets;
+
+ var ticks = this._ticks;
+ var max = this.max;
+ var min = this.min;
+ var offmax = offsets.max;
+ var offmin = offsets.min;
+ var lshow = (this._label == null) ? false : this._label.show;
+
+ for (var p in pos) {
+ this._elem.css(p, pos[p]);
+ }
+
+ this._offsets = offsets;
+ // pixellength will be + for x axes and - for y axes becasue pixels always measured from top left.
+ var pixellength = offmax - offmin;
+ var unitlength = max - min;
+
+ // point to unit and unit to point conversions references to Plot DOM element top left corner.
+ if (this.breakPoints) {
+ unitlength = unitlength - this.breakPoints[1] + this.breakPoints[0];
+
+ this.p2u = function(p){
+ return (p - offmin) * unitlength / pixellength + min;
+ };
+
+ this.u2p = function(u){
+ if (u > this.breakPoints[0] && u < this.breakPoints[1]){
+ u = this.breakPoints[0];
+ }
+ if (u <= this.breakPoints[0]) {
+ return (u - min) * pixellength / unitlength + offmin;
+ }
+ else {
+ return (u - this.breakPoints[1] + this.breakPoints[0] - min) * pixellength / unitlength + offmin;
+ }
+ };
+
+ if (this.name.charAt(0) == 'x'){
+ this.series_u2p = function(u){
+ if (u > this.breakPoints[0] && u < this.breakPoints[1]){
+ u = this.breakPoints[0];
+ }
+ if (u <= this.breakPoints[0]) {
+ return (u - min) * pixellength / unitlength;
+ }
+ else {
+ return (u - this.breakPoints[1] + this.breakPoints[0] - min) * pixellength / unitlength;
+ }
+ };
+ this.series_p2u = function(p){
+ return p * unitlength / pixellength + min;
+ };
+ }
+
+ else {
+ this.series_u2p = function(u){
+ if (u > this.breakPoints[0] && u < this.breakPoints[1]){
+ u = this.breakPoints[0];
+ }
+ if (u >= this.breakPoints[1]) {
+ return (u - max) * pixellength / unitlength;
+ }
+ else {
+ return (u + this.breakPoints[1] - this.breakPoints[0] - max) * pixellength / unitlength;
+ }
+ };
+ this.series_p2u = function(p){
+ return p * unitlength / pixellength + max;
+ };
+ }
+ }
+ else {
+ this.p2u = function(p){
+ return (p - offmin) * unitlength / pixellength + min;
+ };
+
+ this.u2p = function(u){
+ return (u - min) * pixellength / unitlength + offmin;
+ };
+
+ if (this.name == 'xaxis' || this.name == 'x2axis'){
+ this.series_u2p = function(u){
+ return (u - min) * pixellength / unitlength;
+ };
+ this.series_p2u = function(p){
+ return p * unitlength / pixellength + min;
+ };
+ }
+
+ else {
+ this.series_u2p = function(u){
+ return (u - max) * pixellength / unitlength;
+ };
+ this.series_p2u = function(p){
+ return p * unitlength / pixellength + max;
+ };
+ }
+ }
+
+ if (this.show) {
+ if (this.name == 'xaxis' || this.name == 'x2axis') {
+ for (var i=0; i<ticks.length; i++) {
+ var t = ticks[i];
+ if (t.show && t.showLabel) {
+ var shim;
+
+ if (t.constructor == $.jqplot.CanvasAxisTickRenderer && t.angle) {
+ // will need to adjust auto positioning based on which axis this is.
+ var temp = (this.name == 'xaxis') ? 1 : -1;
+ switch (t.labelPosition) {
+ case 'auto':
+ // position at end
+ if (temp * t.angle < 0) {
+ shim = -t.getWidth() + t._textRenderer.height * Math.sin(-t._textRenderer.angle) / 2;
+ }
+ // position at start
+ else {
+ shim = -t._textRenderer.height * Math.sin(t._textRenderer.angle) / 2;
+ }
+ break;
+ case 'end':
+ shim = -t.getWidth() + t._textRenderer.height * Math.sin(-t._textRenderer.angle) / 2;
+ break;
+ case 'start':
+ shim = -t._textRenderer.height * Math.sin(t._textRenderer.angle) / 2;
+ break;
+ case 'middle':
+ shim = -t.getWidth()/2 + t._textRenderer.height * Math.sin(-t._textRenderer.angle) / 2;
+ break;
+ default:
+ shim = -t.getWidth()/2 + t._textRenderer.height * Math.sin(-t._textRenderer.angle) / 2;
+ break;
+ }
+ }
+ else {
+ shim = -t.getWidth()/2;
+ }
+ var val = this.u2p(t.value) + shim + 'px';
+ t._elem.css('left', val);
+ t.pack();
+ }
+ }
+ if (lshow) {
+ var w = this._label._elem.outerWidth(true);
+ this._label._elem.css('left', offmin + pixellength/2 - w/2 + 'px');
+ if (this.name == 'xaxis') {
+ this._label._elem.css('bottom', '0px');
+ }
+ else {
+ this._label._elem.css('top', '0px');
+ }
+ this._label.pack();
+ }
+ }
+ else {
+ for (var i=0; i<ticks.length; i++) {
+ var t = ticks[i];
+ if (t.show && t.showLabel) {
+ var shim;
+ if (t.constructor == $.jqplot.CanvasAxisTickRenderer && t.angle) {
+ // will need to adjust auto positioning based on which axis this is.
+ var temp = (this.name == 'yaxis') ? 1 : -1;
+ switch (t.labelPosition) {
+ case 'auto':
+ // position at end
+ case 'end':
+ if (temp * t.angle < 0) {
+ shim = -t._textRenderer.height * Math.cos(-t._textRenderer.angle) / 2;
+ }
+ else {
+ shim = -t.getHeight() + t._textRenderer.height * Math.cos(t._textRenderer.angle) / 2;
+ }
+ break;
+ case 'start':
+ if (t.angle > 0) {
+ shim = -t._textRenderer.height * Math.cos(-t._textRenderer.angle) / 2;
+ }
+ else {
+ shim = -t.getHeight() + t._textRenderer.height * Math.cos(t._textRenderer.angle) / 2;
+ }
+ break;
+ case 'middle':
+ // if (t.angle > 0) {
+ // shim = -t.getHeight()/2 + t._textRenderer.height * Math.sin(-t._textRenderer.angle) / 2;
+ // }
+ // else {
+ // shim = -t.getHeight()/2 - t._textRenderer.height * Math.sin(t._textRenderer.angle) / 2;
+ // }
+ shim = -t.getHeight()/2;
+ break;
+ default:
+ shim = -t.getHeight()/2;
+ break;
+ }
+ }
+ else {
+ shim = -t.getHeight()/2;
+ }
+
+ var val = this.u2p(t.value) + shim + 'px';
+ t._elem.css('top', val);
+ t.pack();
+ }
+ }
+ if (lshow) {
+ var h = this._label._elem.outerHeight(true);
+ this._label._elem.css('top', offmax - pixellength/2 - h/2 + 'px');
+ if (this.name == 'yaxis') {
+ this._label._elem.css('left', '0px');
+ }
+ else {
+ this._label._elem.css('right', '0px');
+ }
+ this._label.pack();
+ }
+ }
+ }
+
+ ticks = null;
+ };
+
+
+ /**
+ * The following code was generaously given to me a while back by Scott Prahl.
+ * He did a good job at computing axes min, max and number of ticks for the
+ * case where the user has not set any scale related parameters (tickInterval,
+ * numberTicks, min or max). I had ignored this use case for a long time,
+ * focusing on the more difficult case where user has set some option controlling
+ * tick generation. Anyway, about time I got this into jqPlot.
+ * Thanks Scott!!
+ */
+
+ /**
+ * Copyright (c) 2010 Scott Prahl
+ * The next three routines are currently available for use in all personal
+ * or commercial projects under both the MIT and GPL version 2.0 licenses.
+ * This means that you can choose the license that best suits your project
+ * and use it accordingly.
+ */
+
+ // A good format string depends on the interval. If the interval is greater
+ // than 1 then there is no need to show any decimal digits. If it is < 1.0, then
+ // use the magnitude of the interval to determine the number of digits to show.
+ function bestFormatString (interval)
+ {
+ var fstr;
+ interval = Math.abs(interval);
+ if (interval >= 10) {
+ fstr = '%d';
+ }
+
+ else if (interval > 1) {
+ if (interval === parseInt(interval, 10)) {
+ fstr = '%d';
+ }
+ else {
+ fstr = '%.1f';
+ }
+ }
+
+ else {
+ var expv = -Math.floor(Math.log(interval)/Math.LN10);
+ fstr = '%.' + expv + 'f';
+ }
+
+ return fstr;
+ }
+
+ var _factors = [0.1, 0.2, 0.3, 0.4, 0.5, 0.8, 1, 2, 3, 4, 5];
+
+ var _getLowerFactor = function(f) {
+ var i = _factors.indexOf(f);
+ if (i > 0) {
+ return _factors[i-1];
+ }
+ else {
+ return _factors[_factors.length - 1] / 100;
+ }
+ };
+
+ var _getHigherFactor = function(f) {
+ var i = _factors.indexOf(f);
+ if (i < _factors.length-1) {
+ return _factors[i+1];
+ }
+ else {
+ return _factors[0] * 100;
+ }
+ };
+
+ // Given a fixed minimum and maximum and a target number ot ticks
+ // figure out the best interval and
+ // return min, max, number ticks, format string and tick interval
+ function bestConstrainedInterval(min, max, nttarget) {
+ // run through possible number to ticks and see which interval is best
+ var low = Math.floor(nttarget/2);
+ var hi = Math.ceil(nttarget*1.5);
+ var badness = Number.MAX_VALUE;
+ var r = (max - min);
+ var temp;
+ var sd;
+ var bestNT;
+ var gsf = $.jqplot.getSignificantFigures;
+ var fsd;
+ var fs;
+ var currentNT;
+ var bestPrec;
+
+ for (var i=0, l=hi-low+1; i<l; i++) {
+ currentNT = low + i;
+ temp = r/(currentNT-1);
+ sd = gsf(temp);
+
+ temp = Math.abs(nttarget - currentNT) + sd.digitsRight;
+ if (temp < badness) {
+ badness = temp;
+ bestNT = currentNT;
+ bestPrec = sd.digitsRight;
+ }
+ else if (temp === badness) {
+ // let nicer ticks trump number ot ticks
+ if (sd.digitsRight < bestPrec) {
+ bestNT = currentNT;
+ bestPrec = sd.digitsRight;
+ }
+ }
+
+ }
+
+ fsd = Math.max(bestPrec, Math.max(gsf(min).digitsRight, gsf(max).digitsRight));
+ if (fsd === 0) {
+ fs = '%d';
+ }
+ else {
+ fs = '%.' + fsd + 'f';
+ }
+ temp = r / (bestNT - 1);
+ // min, max, number ticks, format string, tick interval
+ return [min, max, bestNT, fs, temp];
+ }
+
+ // This will return an interval of form 2 * 10^n, 5 * 10^n or 10 * 10^n
+ // it is based soley on the range and number of ticks. So if user specifies
+ // number of ticks, use this.
+ function bestInterval(range, numberTicks) {
+ numberTicks = numberTicks || 7;
+ var minimum = range / (numberTicks - 1);
+ var magnitude = Math.pow(10, Math.floor(Math.log(minimum) / Math.LN10));
+ var residual = minimum / magnitude;
+ var interval;
+ // "nicest" ranges are 1, 2, 5 or powers of these.
+ // for magnitudes below 1, only allow these.
+ if (magnitude < 1) {
+ if (residual > 5) {
+ interval = 10 * magnitude;
+ }
+ else if (residual > 2) {
+ interval = 5 * magnitude;
+ }
+ else if (residual > 1) {
+ interval = 2 * magnitude;
+ }
+ else {
+ interval = magnitude;
+ }
+ }
+ // for large ranges (whole integers), allow intervals like 3, 4 or powers of these.
+ // this helps a lot with poor choices for number of ticks.
+ else {
+ if (residual > 5) {
+ interval = 10 * magnitude;
+ }
+ else if (residual > 4) {
+ interval = 5 * magnitude;
+ }
+ else if (residual > 3) {
+ interval = 4 * magnitude;
+ }
+ else if (residual > 2) {
+ interval = 3 * magnitude;
+ }
+ else if (residual > 1) {
+ interval = 2 * magnitude;
+ }
+ else {
+ interval = magnitude;
+ }
+ }
+
+ return interval;
+ }
+
+ // This will return an interval of form 2 * 10^n, 5 * 10^n or 10 * 10^n
+ // it is based soley on the range of data, number of ticks must be computed later.
+ function bestLinearInterval(range, scalefact) {
+ scalefact = scalefact || 1;
+ var expv = Math.floor(Math.log(range)/Math.LN10);
+ var magnitude = Math.pow(10, expv);
+ // 0 < f < 10
+ var f = range / magnitude;
+ var fact;
+ // for large plots, scalefact will decrease f and increase number of ticks.
+ // for small plots, scalefact will increase f and decrease number of ticks.
+ f = f/scalefact;
+
+ // for large plots, smaller interval, more ticks.
+ if (f<=0.38) {
+ fact = 0.1;
+ }
+ else if (f<=1.6) {
+ fact = 0.2;
+ }
+ else if (f<=4.0) {
+ fact = 0.5;
+ }
+ else if (f<=8.0) {
+ fact = 1.0;
+ }
+ // for very small plots, larger interval, less ticks in number ticks
+ else if (f<=16.0) {
+ fact = 2;
+ }
+ else {
+ fact = 5;
+ }
+
+ return fact*magnitude;
+ }
+
+ function bestLinearComponents(range, scalefact) {
+ var expv = Math.floor(Math.log(range)/Math.LN10);
+ var magnitude = Math.pow(10, expv);
+ // 0 < f < 10
+ var f = range / magnitude;
+ var interval;
+ var fact;
+ // for large plots, scalefact will decrease f and increase number of ticks.
+ // for small plots, scalefact will increase f and decrease number of ticks.
+ f = f/scalefact;
+
+ // for large plots, smaller interval, more ticks.
+ if (f<=0.38) {
+ fact = 0.1;
+ }
+ else if (f<=1.6) {
+ fact = 0.2;
+ }
+ else if (f<=4.0) {
+ fact = 0.5;
+ }
+ else if (f<=8.0) {
+ fact = 1.0;
+ }
+ // for very small plots, larger interval, less ticks in number ticks
+ else if (f<=16.0) {
+ fact = 2;
+ }
+ // else if (f<=20.0) {
+ // fact = 3;
+ // }
+ // else if (f<=24.0) {
+ // fact = 4;
+ // }
+ else {
+ fact = 5;
+ }
+
+ interval = fact * magnitude;
+
+ return [interval, fact, magnitude];
+ }
+
+ // Given the min and max for a dataset, return suitable endpoints
+ // for the graphing, a good number for the number of ticks, and a
+ // format string so that extraneous digits are not displayed.
+ // returned is an array containing [min, max, nTicks, format]
+ $.jqplot.LinearTickGenerator = function(axis_min, axis_max, scalefact, numberTicks, keepMin, keepMax) {
+ // Set to preserve EITHER min OR max.
+ // If min is preserved, max must be free.
+ keepMin = (keepMin === null) ? false : keepMin;
+ keepMax = (keepMax === null || keepMin) ? false : keepMax;
+ // if endpoints are equal try to include zero otherwise include one
+ if (axis_min === axis_max) {
+ axis_max = (axis_max) ? 0 : 1;
+ }
+
+ scalefact = scalefact || 1.0;
+
+ // make sure range is positive
+ if (axis_max < axis_min) {
+ var a = axis_max;
+ axis_max = axis_min;
+ axis_min = a;
+ }
+
+ var r = [];
+ var ss = bestLinearInterval(axis_max - axis_min, scalefact);
+
+ var gsf = $.jqplot.getSignificantFigures;
+
+ if (numberTicks == null) {
+
+ // Figure out the axis min, max and number of ticks
+ // the min and max will be some multiple of the tick interval,
+ // 1*10^n, 2*10^n or 5*10^n. This gaurantees that, if the
+ // axis min is negative, 0 will be a tick.
+ if (!keepMin && !keepMax) {
+ r[0] = Math.floor(axis_min / ss) * ss; // min
+ r[1] = Math.ceil(axis_max / ss) * ss; // max
+ r[2] = Math.round((r[1]-r[0])/ss+1.0); // number of ticks
+ r[3] = bestFormatString(ss); // format string
+ r[4] = ss; // tick Interval
+ }
+
+ else if (keepMin) {
+ r[0] = axis_min; // min
+ r[2] = Math.ceil((axis_max - axis_min) / ss + 1.0); // number of ticks
+ r[1] = axis_min + (r[2] - 1) * ss; // max
+ var digitsMin = gsf(axis_min).digitsRight;
+ var digitsSS = gsf(ss).digitsRight;
+ if (digitsMin < digitsSS) {
+ r[3] = bestFormatString(ss); // format string
+ }
+ else {
+ r[3] = '%.' + digitsMin + 'f';
+ }
+ r[4] = ss; // tick Interval
+ }
+
+ else if (keepMax) {
+ r[1] = axis_max; // max
+ r[2] = Math.ceil((axis_max - axis_min) / ss + 1.0); // number of ticks
+ r[0] = axis_max - (r[2] - 1) * ss; // min
+ var digitsMax = gsf(axis_max).digitsRight;
+ var digitsSS = gsf(ss).digitsRight;
+ if (digitsMax < digitsSS) {
+ r[3] = bestFormatString(ss); // format string
+ }
+ else {
+ r[3] = '%.' + digitsMax + 'f';
+ }
+ r[4] = ss; // tick Interval
+ }
+ }
+
+ else {
+ var tempr = [];
+
+ // Figure out the axis min, max and number of ticks
+ // the min and max will be some multiple of the tick interval,
+ // 1*10^n, 2*10^n or 5*10^n. This gaurantees that, if the
+ // axis min is negative, 0 will be a tick.
+ tempr[0] = Math.floor(axis_min / ss) * ss; // min
+ tempr[1] = Math.ceil(axis_max / ss) * ss; // max
+ tempr[2] = Math.round((tempr[1]-tempr[0])/ss+1.0); // number of ticks
+ tempr[3] = bestFormatString(ss); // format string
+ tempr[4] = ss; // tick Interval
+
+ // first, see if we happen to get the right number of ticks
+ if (tempr[2] === numberTicks) {
+ r = tempr;
+ }
+
+ else {
+
+ var newti = bestInterval(tempr[1] - tempr[0], numberTicks);
+
+ r[0] = tempr[0]; // min
+ r[2] = numberTicks; // number of ticks
+ r[4] = newti; // tick interval
+ r[3] = bestFormatString(newti); // format string
+ r[1] = r[0] + (r[2] - 1) * r[4]; // max
+ }
+ }
+
+ return r;
+ };
+
+ $.jqplot.LinearTickGenerator.bestLinearInterval = bestLinearInterval;
+ $.jqplot.LinearTickGenerator.bestInterval = bestInterval;
+ $.jqplot.LinearTickGenerator.bestLinearComponents = bestLinearComponents;
+ $.jqplot.LinearTickGenerator.bestConstrainedInterval = bestConstrainedInterval;
+
+
+ // class: $.jqplot.MarkerRenderer
+ // The default jqPlot marker renderer, rendering the points on the line.
+ $.jqplot.MarkerRenderer = function(options){
+ // Group: Properties
+
+ // prop: show
+ // wether or not to show the marker.
+ this.show = true;
+ // prop: style
+ // One of diamond, circle, square, x, plus, dash, filledDiamond, filledCircle, filledSquare
+ this.style = 'filledCircle';
+ // prop: lineWidth
+ // size of the line for non-filled markers.
+ this.lineWidth = 2;
+ // prop: size
+ // Size of the marker (diameter or circle, length of edge of square, etc.)
+ this.size = 9.0;
+ // prop: color
+ // color of marker. Will be set to color of series by default on init.
+ this.color = '#666666';
+ // prop: shadow
+ // wether or not to draw a shadow on the line
+ this.shadow = true;
+ // prop: shadowAngle
+ // Shadow angle in degrees
+ this.shadowAngle = 45;
+ // prop: shadowOffset
+ // Shadow offset from line in pixels
+ this.shadowOffset = 1;
+ // prop: shadowDepth
+ // Number of times shadow is stroked, each stroke offset shadowOffset from the last.
+ this.shadowDepth = 3;
+ // prop: shadowAlpha
+ // Alpha channel transparency of shadow. 0 = transparent.
+ this.shadowAlpha = '0.07';
+ // prop: shadowRenderer
+ // Renderer that will draws the shadows on the marker.
+ this.shadowRenderer = new $.jqplot.ShadowRenderer();
+ // prop: shapeRenderer
+ // Renderer that will draw the marker.
+ this.shapeRenderer = new $.jqplot.ShapeRenderer();
+
+ $.extend(true, this, options);
+ };
+
+ $.jqplot.MarkerRenderer.prototype.init = function(options) {
+ $.extend(true, this, options);
+ var sdopt = {angle:this.shadowAngle, offset:this.shadowOffset, alpha:this.shadowAlpha, lineWidth:this.lineWidth, depth:this.shadowDepth, closePath:true};
+ if (this.style.indexOf('filled') != -1) {
+ sdopt.fill = true;
+ }
+ if (this.style.indexOf('ircle') != -1) {
+ sdopt.isarc = true;
+ sdopt.closePath = false;
+ }
+ this.shadowRenderer.init(sdopt);
+
+ var shopt = {fill:false, isarc:false, strokeStyle:this.color, fillStyle:this.color, lineWidth:this.lineWidth, closePath:true};
+ if (this.style.indexOf('filled') != -1) {
+ shopt.fill = true;
+ }
+ if (this.style.indexOf('ircle') != -1) {
+ shopt.isarc = true;
+ shopt.closePath = false;
+ }
+ this.shapeRenderer.init(shopt);
+ };
+
+ $.jqplot.MarkerRenderer.prototype.drawDiamond = function(x, y, ctx, fill, options) {
+ var stretch = 1.2;
+ var dx = this.size/2/stretch;
+ var dy = this.size/2*stretch;
+ var points = [[x-dx, y], [x, y+dy], [x+dx, y], [x, y-dy]];
+ if (this.shadow) {
+ this.shadowRenderer.draw(ctx, points);
+ }
+ this.shapeRenderer.draw(ctx, points, options);
+ };
+
+ $.jqplot.MarkerRenderer.prototype.drawPlus = function(x, y, ctx, fill, options) {
+ var stretch = 1.0;
+ var dx = this.size/2*stretch;
+ var dy = this.size/2*stretch;
+ var points1 = [[x, y-dy], [x, y+dy]];
+ var points2 = [[x+dx, y], [x-dx, y]];
+ var opts = $.extend(true, {}, this.options, {closePath:false});
+ if (this.shadow) {
+ this.shadowRenderer.draw(ctx, points1, {closePath:false});
+ this.shadowRenderer.draw(ctx, points2, {closePath:false});
+ }
+ this.shapeRenderer.draw(ctx, points1, opts);
+ this.shapeRenderer.draw(ctx, points2, opts);
+ };
+
+ $.jqplot.MarkerRenderer.prototype.drawX = function(x, y, ctx, fill, options) {
+ var stretch = 1.0;
+ var dx = this.size/2*stretch;
+ var dy = this.size/2*stretch;
+ var opts = $.extend(true, {}, this.options, {closePath:false});
+ var points1 = [[x-dx, y-dy], [x+dx, y+dy]];
+ var points2 = [[x-dx, y+dy], [x+dx, y-dy]];
+ if (this.shadow) {
+ this.shadowRenderer.draw(ctx, points1, {closePath:false});
+ this.shadowRenderer.draw(ctx, points2, {closePath:false});
+ }
+ this.shapeRenderer.draw(ctx, points1, opts);
+ this.shapeRenderer.draw(ctx, points2, opts);
+ };
+
+ $.jqplot.MarkerRenderer.prototype.drawDash = function(x, y, ctx, fill, options) {
+ var stretch = 1.0;
+ var dx = this.size/2*stretch;
+ var dy = this.size/2*stretch;
+ var points = [[x-dx, y], [x+dx, y]];
+ if (this.shadow) {
+ this.shadowRenderer.draw(ctx, points);
+ }
+ this.shapeRenderer.draw(ctx, points, options);
+ };
+
+ $.jqplot.MarkerRenderer.prototype.drawLine = function(p1, p2, ctx, fill, options) {
+ var points = [p1, p2];
+ if (this.shadow) {
+ this.shadowRenderer.draw(ctx, points);
+ }
+ this.shapeRenderer.draw(ctx, points, options);
+ };
+
+ $.jqplot.MarkerRenderer.prototype.drawSquare = function(x, y, ctx, fill, options) {
+ var stretch = 1.0;
+ var dx = this.size/2/stretch;
+ var dy = this.size/2*stretch;
+ var points = [[x-dx, y-dy], [x-dx, y+dy], [x+dx, y+dy], [x+dx, y-dy]];
+ if (this.shadow) {
+ this.shadowRenderer.draw(ctx, points);
+ }
+ this.shapeRenderer.draw(ctx, points, options);
+ };
+
+ $.jqplot.MarkerRenderer.prototype.drawCircle = function(x, y, ctx, fill, options) {
+ var radius = this.size/2;
+ var end = 2*Math.PI;
+ var points = [x, y, radius, 0, end, true];
+ if (this.shadow) {
+ this.shadowRenderer.draw(ctx, points);
+ }
+ this.shapeRenderer.draw(ctx, points, options);
+ };
+
+ $.jqplot.MarkerRenderer.prototype.draw = function(x, y, ctx, options) {
+ options = options || {};
+ // hack here b/c shape renderer uses canvas based color style options
+ // and marker uses css style names.
+ if (options.show == null || options.show != false) {
+ if (options.color && !options.fillStyle) {
+ options.fillStyle = options.color;
+ }
+ if (options.color && !options.strokeStyle) {
+ options.strokeStyle = options.color;
+ }
+ switch (this.style) {
+ case 'diamond':
+ this.drawDiamond(x,y,ctx, false, options);
+ break;
+ case 'filledDiamond':
+ this.drawDiamond(x,y,ctx, true, options);
+ break;
+ case 'circle':
+ this.drawCircle(x,y,ctx, false, options);
+ break;
+ case 'filledCircle':
+ this.drawCircle(x,y,ctx, true, options);
+ break;
+ case 'square':
+ this.drawSquare(x,y,ctx, false, options);
+ break;
+ case 'filledSquare':
+ this.drawSquare(x,y,ctx, true, options);
+ break;
+ case 'x':
+ this.drawX(x,y,ctx, true, options);
+ break;
+ case 'plus':
+ this.drawPlus(x,y,ctx, true, options);
+ break;
+ case 'dash':
+ this.drawDash(x,y,ctx, true, options);
+ break;
+ case 'line':
+ this.drawLine(x, y, ctx, false, options);
+ break;
+ default:
+ this.drawDiamond(x,y,ctx, false, options);
+ break;
+ }
+ }
+ };
+
+ // class: $.jqplot.shadowRenderer
+ // The default jqPlot shadow renderer, rendering shadows behind shapes.
+ $.jqplot.ShadowRenderer = function(options){
+ // Group: Properties
+
+ // prop: angle
+ // Angle of the shadow in degrees. Measured counter-clockwise from the x axis.
+ this.angle = 45;
+ // prop: offset
+ // Pixel offset at the given shadow angle of each shadow stroke from the last stroke.
+ this.offset = 1;
+ // prop: alpha
+ // alpha transparency of shadow stroke.
+ this.alpha = 0.07;
+ // prop: lineWidth
+ // width of the shadow line stroke.
+ this.lineWidth = 1.5;
+ // prop: lineJoin
+ // How line segments of the shadow are joined.
+ this.lineJoin = 'miter';
+ // prop: lineCap
+ // how ends of the shadow line are rendered.
+ this.lineCap = 'round';
+ // prop; closePath
+ // whether line path segment is closed upon itself.
+ this.closePath = false;
+ // prop: fill
+ // whether to fill the shape.
+ this.fill = false;
+ // prop: depth
+ // how many times the shadow is stroked. Each stroke will be offset by offset at angle degrees.
+ this.depth = 3;
+ this.strokeStyle = 'rgba(0,0,0,0.1)';
+ // prop: isarc
+ // wether the shadow is an arc or not.
+ this.isarc = false;
+
+ $.extend(true, this, options);
+ };
+
+ $.jqplot.ShadowRenderer.prototype.init = function(options) {
+ $.extend(true, this, options);
+ };
+
+ // function: draw
+ // draws an transparent black (i.e. gray) shadow.
+ //
+ // ctx - canvas drawing context
+ // points - array of points or [x, y, radius, start angle (rad), end angle (rad)]
+ $.jqplot.ShadowRenderer.prototype.draw = function(ctx, points, options) {
+ ctx.save();
+ var opts = (options != null) ? options : {};
+ var fill = (opts.fill != null) ? opts.fill : this.fill;
+ var fillRect = (opts.fillRect != null) ? opts.fillRect : this.fillRect;
+ var closePath = (opts.closePath != null) ? opts.closePath : this.closePath;
+ var offset = (opts.offset != null) ? opts.offset : this.offset;
+ var alpha = (opts.alpha != null) ? opts.alpha : this.alpha;
+ var depth = (opts.depth != null) ? opts.depth : this.depth;
+ var isarc = (opts.isarc != null) ? opts.isarc : this.isarc;
+ var linePattern = (opts.linePattern != null) ? opts.linePattern : this.linePattern;
+ ctx.lineWidth = (opts.lineWidth != null) ? opts.lineWidth : this.lineWidth;
+ ctx.lineJoin = (opts.lineJoin != null) ? opts.lineJoin : this.lineJoin;
+ ctx.lineCap = (opts.lineCap != null) ? opts.lineCap : this.lineCap;
+ ctx.strokeStyle = opts.strokeStyle || this.strokeStyle || 'rgba(0,0,0,'+alpha+')';
+ ctx.fillStyle = opts.fillStyle || this.fillStyle || 'rgba(0,0,0,'+alpha+')';
+ for (var j=0; j<depth; j++) {
+ var ctxPattern = $.jqplot.LinePattern(ctx, linePattern);
+ ctx.translate(Math.cos(this.angle*Math.PI/180)*offset, Math.sin(this.angle*Math.PI/180)*offset);
+ ctxPattern.beginPath();
+ if (isarc) {
+ ctx.arc(points[0], points[1], points[2], points[3], points[4], true);
+ }
+ else if (fillRect) {
+ if (fillRect) {
+ ctx.fillRect(points[0], points[1], points[2], points[3]);
+ }
+ }
+ else if (points && points.length){
+ var move = true;
+ for (var i=0; i<points.length; i++) {
+ // skip to the first non-null point and move to it.
+ if (points[i][0] != null && points[i][1] != null) {
+ if (move) {
+ ctxPattern.moveTo(points[i][0], points[i][1]);
+ move = false;
+ }
+ else {
+ ctxPattern.lineTo(points[i][0], points[i][1]);
+ }
+ }
+ else {
+ move = true;
+ }
+ }
+
+ }
+ if (closePath) {
+ ctxPattern.closePath();
+ }
+ if (fill) {
+ ctx.fill();
+ }
+ else {
+ ctx.stroke();
+ }
+ }
+ ctx.restore();
+ };
+
+ // class: $.jqplot.shapeRenderer
+ // The default jqPlot shape renderer. Given a set of points will
+ // plot them and either stroke a line (fill = false) or fill them (fill = true).
+ // If a filled shape is desired, closePath = true must also be set to close
+ // the shape.
+ $.jqplot.ShapeRenderer = function(options){
+
+ this.lineWidth = 1.5;
+ // prop: linePattern
+ // line pattern 'dashed', 'dotted', 'solid', some combination
+ // of '-' and '.' characters such as '.-.' or a numerical array like
+ // [draw, skip, draw, skip, ...] such as [1, 10] to draw a dotted line,
+ // [1, 10, 20, 10] to draw a dot-dash line, and so on.
+ this.linePattern = 'solid';
+ // prop: lineJoin
+ // How line segments of the shadow are joined.
+ this.lineJoin = 'miter';
+ // prop: lineCap
+ // how ends of the shadow line are rendered.
+ this.lineCap = 'round';
+ // prop; closePath
+ // whether line path segment is closed upon itself.
+ this.closePath = false;
+ // prop: fill
+ // whether to fill the shape.
+ this.fill = false;
+ // prop: isarc
+ // wether the shadow is an arc or not.
+ this.isarc = false;
+ // prop: fillRect
+ // true to draw shape as a filled rectangle.
+ this.fillRect = false;
+ // prop: strokeRect
+ // true to draw shape as a stroked rectangle.
+ this.strokeRect = false;
+ // prop: clearRect
+ // true to cear a rectangle.
+ this.clearRect = false;
+ // prop: strokeStyle
+ // css color spec for the stoke style
+ this.strokeStyle = '#999999';
+ // prop: fillStyle
+ // css color spec for the fill style.
+ this.fillStyle = '#999999';
+
+ $.extend(true, this, options);
+ };
+
+ $.jqplot.ShapeRenderer.prototype.init = function(options) {
+ $.extend(true, this, options);
+ };
+
+ // function: draw
+ // draws the shape.
+ //
+ // ctx - canvas drawing context
+ // points - array of points for shapes or
+ // [x, y, width, height] for rectangles or
+ // [x, y, radius, start angle (rad), end angle (rad)] for circles and arcs.
+ $.jqplot.ShapeRenderer.prototype.draw = function(ctx, points, options) {
+ ctx.save();
+ var opts = (options != null) ? options : {};
+ var fill = (opts.fill != null) ? opts.fill : this.fill;
+ var closePath = (opts.closePath != null) ? opts.closePath : this.closePath;
+ var fillRect = (opts.fillRect != null) ? opts.fillRect : this.fillRect;
+ var strokeRect = (opts.strokeRect != null) ? opts.strokeRect : this.strokeRect;
+ var clearRect = (opts.clearRect != null) ? opts.clearRect : this.clearRect;
+ var isarc = (opts.isarc != null) ? opts.isarc : this.isarc;
+ var linePattern = (opts.linePattern != null) ? opts.linePattern : this.linePattern;
+ var ctxPattern = $.jqplot.LinePattern(ctx, linePattern);
+ ctx.lineWidth = opts.lineWidth || this.lineWidth;
+ ctx.lineJoin = opts.lineJoin || this.lineJoin;
+ ctx.lineCap = opts.lineCap || this.lineCap;
+ ctx.strokeStyle = (opts.strokeStyle || opts.color) || this.strokeStyle;
+ ctx.fillStyle = opts.fillStyle || this.fillStyle;
+ ctx.beginPath();
+ if (isarc) {
+ ctx.arc(points[0], points[1], points[2], points[3], points[4], true);
+ if (closePath) {
+ ctx.closePath();
+ }
+ if (fill) {
+ ctx.fill();
+ }
+ else {
+ ctx.stroke();
+ }
+ ctx.restore();
+ return;
+ }
+ else if (clearRect) {
+ ctx.clearRect(points[0], points[1], points[2], points[3]);
+ ctx.restore();
+ return;
+ }
+ else if (fillRect || strokeRect) {
+ if (fillRect) {
+ ctx.fillRect(points[0], points[1], points[2], points[3]);
+ }
+ if (strokeRect) {
+ ctx.strokeRect(points[0], points[1], points[2], points[3]);
+ ctx.restore();
+ return;
+ }
+ }
+ else if (points && points.length){
+ var move = true;
+ for (var i=0; i<points.length; i++) {
+ // skip to the first non-null point and move to it.
+ if (points[i][0] != null && points[i][1] != null) {
+ if (move) {
+ ctxPattern.moveTo(points[i][0], points[i][1]);
+ move = false;
+ }
+ else {
+ ctxPattern.lineTo(points[i][0], points[i][1]);
+ }
+ }
+ else {
+ move = true;
+ }
+ }
+ if (closePath) {
+ ctxPattern.closePath();
+ }
+ if (fill) {
+ ctx.fill();
+ }
+ else {
+ ctx.stroke();
+ }
+ }
+ ctx.restore();
+ };
+
+ // class $.jqplot.TableLegendRenderer
+ // The default legend renderer for jqPlot.
+ $.jqplot.TableLegendRenderer = function(){
+ //
+ };
+
+ $.jqplot.TableLegendRenderer.prototype.init = function(options) {
+ $.extend(true, this, options);
+ };
+
+ $.jqplot.TableLegendRenderer.prototype.addrow = function (label, color, pad, reverse) {
+ var rs = (pad) ? this.rowSpacing+'px' : '0px';
+ var tr;
+ var td;
+ var elem;
+ var div0;
+ var div1;
+ elem = document.createElement('tr');
+ tr = $(elem);
+ tr.addClass('jqplot-table-legend');
+ elem = null;
+
+ if (reverse){
+ tr.prependTo(this._elem);
+ }
+
+ else{
+ tr.appendTo(this._elem);
+ }
+
+ if (this.showSwatches) {
+ td = $(document.createElement('td'));
+ td.addClass('jqplot-table-legend jqplot-table-legend-swatch');
+ td.css({textAlign: 'center', paddingTop: rs});
+
+ div0 = $(document.createElement('div'));
+ div0.addClass('jqplot-table-legend-swatch-outline');
+ div1 = $(document.createElement('div'));
+ div1.addClass('jqplot-table-legend-swatch');
+ div1.css({backgroundColor: color, borderColor: color});
+
+ tr.append(td.append(div0.append(div1)));
+
+ // $('<td class="jqplot-table-legend" style="text-align:center;padding-top:'+rs+';">'+
+ // '<div><div class="jqplot-table-legend-swatch" style="background-color:'+color+';border-color:'+color+';"></div>'+
+ // '</div></td>').appendTo(tr);
+ }
+ if (this.showLabels) {
+ td = $(document.createElement('td'));
+ td.addClass('jqplot-table-legend jqplot-table-legend-label');
+ td.css('paddingTop', rs);
+ tr.append(td);
+
+ // elem = $('<td class="jqplot-table-legend" style="padding-top:'+rs+';"></td>');
+ // elem.appendTo(tr);
+ if (this.escapeHtml) {
+ td.text(label);
+ }
+ else {
+ td.html(label);
+ }
+ }
+ td = null;
+ div0 = null;
+ div1 = null;
+ tr = null;
+ elem = null;
+ };
+
+ // called with scope of legend
+ $.jqplot.TableLegendRenderer.prototype.draw = function() {
+ if (this._elem) {
+ this._elem.emptyForce();
+ this._elem = null;
+ }
+
+ if (this.show) {
+ var series = this._series;
+ // make a table. one line label per row.
+ var elem = document.createElement('table');
+ this._elem = $(elem);
+ this._elem.addClass('jqplot-table-legend');
+
+ var ss = {position:'absolute'};
+ if (this.background) {
+ ss['background'] = this.background;
+ }
+ if (this.border) {
+ ss['border'] = this.border;
+ }
+ if (this.fontSize) {
+ ss['fontSize'] = this.fontSize;
+ }
+ if (this.fontFamily) {
+ ss['fontFamily'] = this.fontFamily;
+ }
+ if (this.textColor) {
+ ss['textColor'] = this.textColor;
+ }
+ if (this.marginTop != null) {
+ ss['marginTop'] = this.marginTop;
+ }
+ if (this.marginBottom != null) {
+ ss['marginBottom'] = this.marginBottom;
+ }
+ if (this.marginLeft != null) {
+ ss['marginLeft'] = this.marginLeft;
+ }
+ if (this.marginRight != null) {
+ ss['marginRight'] = this.marginRight;
+ }
+
+
+ var pad = false,
+ reverse = false,
+ s;
+ for (var i = 0; i< series.length; i++) {
+ s = series[i];
+ if (s._stack || s.renderer.constructor == $.jqplot.BezierCurveRenderer){
+ reverse = true;
+ }
+ if (s.show && s.showLabel) {
+ var lt = this.labels[i] || s.label.toString();
+ if (lt) {
+ var color = s.color;
+ if (reverse && i < series.length - 1){
+ pad = true;
+ }
+ else if (reverse && i == series.length - 1){
+ pad = false;
+ }
+ this.renderer.addrow.call(this, lt, color, pad, reverse);
+ pad = true;
+ }
+ // let plugins add more rows to legend. Used by trend line plugin.
+ for (var j=0; j<$.jqplot.addLegendRowHooks.length; j++) {
+ var item = $.jqplot.addLegendRowHooks[j].call(this, s);
+ if (item) {
+ this.renderer.addrow.call(this, item.label, item.color, pad);
+ pad = true;
+ }
+ }
+ lt = null;
+ }
+ }
+ }
+ return this._elem;
+ };
+
+ $.jqplot.TableLegendRenderer.prototype.pack = function(offsets) {
+ if (this.show) {
+ if (this.placement == 'insideGrid') {
+ switch (this.location) {
+ case 'nw':
+ var a = offsets.left;
+ var b = offsets.top;
+ this._elem.css('left', a);
+ this._elem.css('top', b);
+ break;
+ case 'n':
+ var a = (offsets.left + (this._plotDimensions.width - offsets.right))/2 - this.getWidth()/2;
+ var b = offsets.top;
+ this._elem.css('left', a);
+ this._elem.css('top', b);
+ break;
+ case 'ne':
+ var a = offsets.right;
+ var b = offsets.top;
+ this._elem.css({right:a, top:b});
+ break;
+ case 'e':
+ var a = offsets.right;
+ var b = (offsets.top + (this._plotDimensions.height - offsets.bottom))/2 - this.getHeight()/2;
+ this._elem.css({right:a, top:b});
+ break;
+ case 'se':
+ var a = offsets.right;
+ var b = offsets.bottom;
+ this._elem.css({right:a, bottom:b});
+ break;
+ case 's':
+ var a = (offsets.left + (this._plotDimensions.width - offsets.right))/2 - this.getWidth()/2;
+ var b = offsets.bottom;
+ this._elem.css({left:a, bottom:b});
+ break;
+ case 'sw':
+ var a = offsets.left;
+ var b = offsets.bottom;
+ this._elem.css({left:a, bottom:b});
+ break;
+ case 'w':
+ var a = offsets.left;
+ var b = (offsets.top + (this._plotDimensions.height - offsets.bottom))/2 - this.getHeight()/2;
+ this._elem.css({left:a, top:b});
+ break;
+ default: // same as 'se'
+ var a = offsets.right;
+ var b = offsets.bottom;
+ this._elem.css({right:a, bottom:b});
+ break;
+ }
+
+ }
+ else if (this.placement == 'outside'){
+ switch (this.location) {
+ case 'nw':
+ var a = this._plotDimensions.width - offsets.left;
+ var b = offsets.top;
+ this._elem.css('right', a);
+ this._elem.css('top', b);
+ break;
+ case 'n':
+ var a = (offsets.left + (this._plotDimensions.width - offsets.right))/2 - this.getWidth()/2;
+ var b = this._plotDimensions.height - offsets.top;
+ this._elem.css('left', a);
+ this._elem.css('bottom', b);
+ break;
+ case 'ne':
+ var a = this._plotDimensions.width - offsets.right;
+ var b = offsets.top;
+ this._elem.css({left:a, top:b});
+ break;
+ case 'e':
+ var a = this._plotDimensions.width - offsets.right;
+ var b = (offsets.top + (this._plotDimensions.height - offsets.bottom))/2 - this.getHeight()/2;
+ this._elem.css({left:a, top:b});
+ break;
+ case 'se':
+ var a = this._plotDimensions.width - offsets.right;
+ var b = offsets.bottom;
+ this._elem.css({left:a, bottom:b});
+ break;
+ case 's':
+ var a = (offsets.left + (this._plotDimensions.width - offsets.right))/2 - this.getWidth()/2;
+ var b = this._plotDimensions.height - offsets.bottom;
+ this._elem.css({left:a, top:b});
+ break;
+ case 'sw':
+ var a = this._plotDimensions.width - offsets.left;
+ var b = offsets.bottom;
+ this._elem.css({right:a, bottom:b});
+ break;
+ case 'w':
+ var a = this._plotDimensions.width - offsets.left;
+ var b = (offsets.top + (this._plotDimensions.height - offsets.bottom))/2 - this.getHeight()/2;
+ this._elem.css({right:a, top:b});
+ break;
+ default: // same as 'se'
+ var a = offsets.right;
+ var b = offsets.bottom;
+ this._elem.css({right:a, bottom:b});
+ break;
+ }
+ }
+ else {
+ switch (this.location) {
+ case 'nw':
+ this._elem.css({left:0, top:offsets.top});
+ break;
+ case 'n':
+ var a = (offsets.left + (this._plotDimensions.width - offsets.right))/2 - this.getWidth()/2;
+ this._elem.css({left: a, top:offsets.top});
+ break;
+ case 'ne':
+ this._elem.css({right:0, top:offsets.top});
+ break;
+ case 'e':
+ var b = (offsets.top + (this._plotDimensions.height - offsets.bottom))/2 - this.getHeight()/2;
+ this._elem.css({right:offsets.right, top:b});
+ break;
+ case 'se':
+ this._elem.css({right:offsets.right, bottom:offsets.bottom});
+ break;
+ case 's':
+ var a = (offsets.left + (this._plotDimensions.width - offsets.right))/2 - this.getWidth()/2;
+ this._elem.css({left: a, bottom:offsets.bottom});
+ break;
+ case 'sw':
+ this._elem.css({left:offsets.left, bottom:offsets.bottom});
+ break;
+ case 'w':
+ var b = (offsets.top + (this._plotDimensions.height - offsets.bottom))/2 - this.getHeight()/2;
+ this._elem.css({left:offsets.left, top:b});
+ break;
+ default: // same as 'se'
+ this._elem.css({right:offsets.right, bottom:offsets.bottom});
+ break;
+ }
+ }
+ }
+ };
+
+ /**
+ * Class: $.jqplot.ThemeEngine
+ * Theme Engine provides a programatic way to change some of the more
+ * common jqplot styling options such as fonts, colors and grid options.
+ * A theme engine instance is created with each plot. The theme engine
+ * manages a collection of themes which can be modified, added to, or
+ * applied to the plot.
+ *
+ * The themeEngine class is not instantiated directly.
+ * When a plot is initialized, the current plot options are scanned
+ * an a default theme named "Default" is created. This theme is
+ * used as the basis for other themes added to the theme engine and
+ * is always available.
+ *
+ * A theme is a simple javascript object with styling parameters for
+ * various entities of the plot. A theme has the form:
+ *
+ *
+ * > {
+ * > _name:f "Default",
+ * > target: {
+ * > backgroundColor: "transparent"
+ * > },
+ * > legend: {
+ * > textColor: null,
+ * > fontFamily: null,
+ * > fontSize: null,
+ * > border: null,
+ * > background: null
+ * > },
+ * > title: {
+ * > textColor: "rgb(102, 102, 102)",
+ * > fontFamily: "'Trebuchet MS',Arial,Helvetica,sans-serif",
+ * > fontSize: "19.2px",
+ * > textAlign: "center"
+ * > },
+ * > seriesStyles: {},
+ * > series: [{
+ * > color: "#4bb2c5",
+ * > lineWidth: 2.5,
+ * > linePattern: "solid",
+ * > shadow: true,
+ * > fillColor: "#4bb2c5",
+ * > showMarker: true,
+ * > markerOptions: {
+ * > color: "#4bb2c5",
+ * > show: true,
+ * > style: 'filledCircle',
+ * > lineWidth: 1.5,
+ * > size: 4,
+ * > shadow: true
+ * > }
+ * > }],
+ * > grid: {
+ * > drawGridlines: true,
+ * > gridLineColor: "#cccccc",
+ * > gridLineWidth: 1,
+ * > backgroundColor: "#fffdf6",
+ * > borderColor: "#999999",
+ * > borderWidth: 2,
+ * > shadow: true
+ * > },
+ * > axesStyles: {
+ * > label: {},
+ * > ticks: {}
+ * > },
+ * > axes: {
+ * > xaxis: {
+ * > borderColor: "#999999",
+ * > borderWidth: 2,
+ * > ticks: {
+ * > show: true,
+ * > showGridline: true,
+ * > showLabel: true,
+ * > showMark: true,
+ * > size: 4,
+ * > textColor: "",
+ * > whiteSpace: "nowrap",
+ * > fontSize: "12px",
+ * > fontFamily: "'Trebuchet MS',Arial,Helvetica,sans-serif"
+ * > },
+ * > label: {
+ * > textColor: "rgb(102, 102, 102)",
+ * > whiteSpace: "normal",
+ * > fontSize: "14.6667px",
+ * > fontFamily: "'Trebuchet MS',Arial,Helvetica,sans-serif",
+ * > fontWeight: "400"
+ * > }
+ * > },
+ * > yaxis: {
+ * > borderColor: "#999999",
+ * > borderWidth: 2,
+ * > ticks: {
+ * > show: true,
+ * > showGridline: true,
+ * > showLabel: true,
+ * > showMark: true,
+ * > size: 4,
+ * > textColor: "",
+ * > whiteSpace: "nowrap",
+ * > fontSize: "12px",
+ * > fontFamily: "'Trebuchet MS',Arial,Helvetica,sans-serif"
+ * > },
+ * > label: {
+ * > textColor: null,
+ * > whiteSpace: null,
+ * > fontSize: null,
+ * > fontFamily: null,
+ * > fontWeight: null
+ * > }
+ * > },
+ * > x2axis: {...
+ * > },
+ * > ...
+ * > y9axis: {...
+ * > }
+ * > }
+ * > }
+ *
+ * "seriesStyles" is a style object that will be applied to all series in the plot.
+ * It will forcibly override any styles applied on the individual series. "axesStyles" is
+ * a style object that will be applied to all axes in the plot. It will also forcibly
+ * override any styles on the individual axes.
+ *
+ * The example shown above has series options for a line series. Options for other
+ * series types are shown below:
+ *
+ * Bar Series:
+ *
+ * > {
+ * > color: "#4bb2c5",
+ * > seriesColors: ["#4bb2c5", "#EAA228", "#c5b47f", "#579575", "#839557", "#958c12", "#953579", "#4b5de4", "#d8b83f", "#ff5800", "#0085cc", "#c747a3", "#cddf54", "#FBD178", "#26B4E3", "#bd70c7"],
+ * > lineWidth: 2.5,
+ * > shadow: true,
+ * > barPadding: 2,
+ * > barMargin: 10,
+ * > barWidth: 15.09375,
+ * > highlightColors: ["rgb(129,201,214)", "rgb(129,201,214)", "rgb(129,201,214)", "rgb(129,201,214)", "rgb(129,201,214)", "rgb(129,201,214)", "rgb(129,201,214)", "rgb(129,201,214)"]
+ * > }
+ *
+ * Pie Series:
+ *
+ * > {
+ * > seriesColors: ["#4bb2c5", "#EAA228", "#c5b47f", "#579575", "#839557", "#958c12", "#953579", "#4b5de4", "#d8b83f", "#ff5800", "#0085cc", "#c747a3", "#cddf54", "#FBD178", "#26B4E3", "#bd70c7"],
+ * > padding: 20,
+ * > sliceMargin: 0,
+ * > fill: true,
+ * > shadow: true,
+ * > startAngle: 0,
+ * > lineWidth: 2.5,
+ * > highlightColors: ["rgb(129,201,214)", "rgb(240,189,104)", "rgb(214,202,165)", "rgb(137,180,158)", "rgb(168,180,137)", "rgb(180,174,89)", "rgb(180,113,161)", "rgb(129,141,236)", "rgb(227,205,120)", "rgb(255,138,76)", "rgb(76,169,219)", "rgb(215,126,190)", "rgb(220,232,135)", "rgb(200,167,96)", "rgb(103,202,235)", "rgb(208,154,215)"]
+ * > }
+ *
+ * Funnel Series:
+ *
+ * > {
+ * > color: "#4bb2c5",
+ * > lineWidth: 2,
+ * > shadow: true,
+ * > padding: {
+ * > top: 20,
+ * > right: 20,
+ * > bottom: 20,
+ * > left: 20
+ * > },
+ * > sectionMargin: 6,
+ * > seriesColors: ["#4bb2c5", "#EAA228", "#c5b47f", "#579575", "#839557", "#958c12", "#953579", "#4b5de4", "#d8b83f", "#ff5800", "#0085cc", "#c747a3", "#cddf54", "#FBD178", "#26B4E3", "#bd70c7"],
+ * > highlightColors: ["rgb(147,208,220)", "rgb(242,199,126)", "rgb(220,210,178)", "rgb(154,191,172)", "rgb(180,191,154)", "rgb(191,186,112)", "rgb(191,133,174)", "rgb(147,157,238)", "rgb(231,212,139)", "rgb(255,154,102)", "rgb(102,181,224)", "rgb(221,144,199)", "rgb(225,235,152)", "rgb(200,167,96)", "rgb(124,210,238)", "rgb(215,169,221)"]
+ * > }
+ *
+ */
+ $.jqplot.ThemeEngine = function(){
+ // Group: Properties
+ //
+ // prop: themes
+ // hash of themes managed by the theme engine.
+ // Indexed by theme name.
+ this.themes = {};
+ // prop: activeTheme
+ // Pointer to currently active theme
+ this.activeTheme=null;
+
+ };
+
+ // called with scope of plot
+ $.jqplot.ThemeEngine.prototype.init = function() {
+ // get the Default theme from the current plot settings.
+ var th = new $.jqplot.Theme({_name:'Default'});
+ var n, i, nn;
+
+ for (n in th.target) {
+ if (n == "textColor") {
+ th.target[n] = this.target.css('color');
+ }
+ else {
+ th.target[n] = this.target.css(n);
+ }
+ }
+
+ if (this.title.show && this.title._elem) {
+ for (n in th.title) {
+ if (n == "textColor") {
+ th.title[n] = this.title._elem.css('color');
+ }
+ else {
+ th.title[n] = this.title._elem.css(n);
+ }
+ }
+ }
+
+ for (n in th.grid) {
+ th.grid[n] = this.grid[n];
+ }
+ if (th.grid.backgroundColor == null && this.grid.background != null) {
+ th.grid.backgroundColor = this.grid.background;
+ }
+ if (this.legend.show && this.legend._elem) {
+ for (n in th.legend) {
+ if (n == 'textColor') {
+ th.legend[n] = this.legend._elem.css('color');
+ }
+ else {
+ th.legend[n] = this.legend._elem.css(n);
+ }
+ }
+ }
+ var s;
+
+ for (i=0; i<this.series.length; i++) {
+ s = this.series[i];
+ if (s.renderer.constructor == $.jqplot.LineRenderer) {
+ th.series.push(new LineSeriesProperties());
+ }
+ else if (s.renderer.constructor == $.jqplot.BarRenderer) {
+ th.series.push(new BarSeriesProperties());
+ }
+ else if (s.renderer.constructor == $.jqplot.PieRenderer) {
+ th.series.push(new PieSeriesProperties());
+ }
+ else if (s.renderer.constructor == $.jqplot.DonutRenderer) {
+ th.series.push(new DonutSeriesProperties());
+ }
+ else if (s.renderer.constructor == $.jqplot.FunnelRenderer) {
+ th.series.push(new FunnelSeriesProperties());
+ }
+ else if (s.renderer.constructor == $.jqplot.MeterGaugeRenderer) {
+ th.series.push(new MeterSeriesProperties());
+ }
+ else {
+ th.series.push({});
+ }
+ for (n in th.series[i]) {
+ th.series[i][n] = s[n];
+ }
+ }
+ var a, ax;
+ for (n in this.axes) {
+ ax = this.axes[n];
+ a = th.axes[n] = new AxisProperties();
+ a.borderColor = ax.borderColor;
+ a.borderWidth = ax.borderWidth;
+ if (ax._ticks && ax._ticks[0]) {
+ for (nn in a.ticks) {
+ if (ax._ticks[0].hasOwnProperty(nn)) {
+ a.ticks[nn] = ax._ticks[0][nn];
+ }
+ else if (ax._ticks[0]._elem){
+ a.ticks[nn] = ax._ticks[0]._elem.css(nn);
+ }
+ }
+ }
+ if (ax._label && ax._label.show) {
+ for (nn in a.label) {
+ // a.label[nn] = ax._label._elem.css(nn);
+ if (ax._label[nn]) {
+ a.label[nn] = ax._label[nn];
+ }
+ else if (ax._label._elem){
+ if (nn == 'textColor') {
+ a.label[nn] = ax._label._elem.css('color');
+ }
+ else {
+ a.label[nn] = ax._label._elem.css(nn);
+ }
+ }
+ }
+ }
+ }
+ this.themeEngine._add(th);
+ this.themeEngine.activeTheme = this.themeEngine.themes[th._name];
+ };
+ /**
+ * Group: methods
+ *
+ * method: get
+ *
+ * Get and return the named theme or the active theme if no name given.
+ *
+ * parameter:
+ *
+ * name - name of theme to get.
+ *
+ * returns:
+ *
+ * Theme instance of given name.
+ */
+ $.jqplot.ThemeEngine.prototype.get = function(name) {
+ if (!name) {
+ // return the active theme
+ return this.activeTheme;
+ }
+ else {
+ return this.themes[name];
+ }
+ };
+
+ function numericalOrder(a,b) { return a-b; }
+
+ /**
+ * method: getThemeNames
+ *
+ * Return the list of theme names in this manager in alpha-numerical order.
+ *
+ * parameter:
+ *
+ * None
+ *
+ * returns:
+ *
+ * A the list of theme names in this manager in alpha-numerical order.
+ */
+ $.jqplot.ThemeEngine.prototype.getThemeNames = function() {
+ var tn = [];
+ for (var n in this.themes) {
+ tn.push(n);
+ }
+ return tn.sort(numericalOrder);
+ };
+
+ /**
+ * method: getThemes
+ *
+ * Return a list of themes in alpha-numerical order by name.
+ *
+ * parameter:
+ *
+ * None
+ *
+ * returns:
+ *
+ * A list of themes in alpha-numerical order by name.
+ */
+ $.jqplot.ThemeEngine.prototype.getThemes = function() {
+ var tn = [];
+ var themes = [];
+ for (var n in this.themes) {
+ tn.push(n);
+ }
+ tn.sort(numericalOrder);
+ for (var i=0; i<tn.length; i++) {
+ themes.push(this.themes[tn[i]]);
+ }
+ return themes;
+ };
+
+ $.jqplot.ThemeEngine.prototype.activate = function(plot, name) {
+ // sometimes need to redraw whole plot.
+ var redrawPlot = false;
+ if (!name && this.activeTheme && this.activeTheme._name) {
+ name = this.activeTheme._name;
+ }
+ if (!this.themes.hasOwnProperty(name)) {
+ throw new Error("No theme of that name");
+ }
+ else {
+ var th = this.themes[name];
+ this.activeTheme = th;
+ var val, checkBorderColor = false, checkBorderWidth = false;
+ var arr = ['xaxis', 'x2axis', 'yaxis', 'y2axis'];
+
+ for (i=0; i<arr.length; i++) {
+ var ax = arr[i];
+ if (th.axesStyles.borderColor != null) {
+ plot.axes[ax].borderColor = th.axesStyles.borderColor;
+ }
+ if (th.axesStyles.borderWidth != null) {
+ plot.axes[ax].borderWidth = th.axesStyles.borderWidth;
+ }
+ }
+
+ for (var axname in plot.axes) {
+ var axis = plot.axes[axname];
+ if (axis.show) {
+ var thaxis = th.axes[axname] || {};
+ var thaxstyle = th.axesStyles;
+ var thax = $.jqplot.extend(true, {}, thaxis, thaxstyle);
+ val = (th.axesStyles.borderColor != null) ? th.axesStyles.borderColor : thax.borderColor;
+ if (thax.borderColor != null) {
+ axis.borderColor = thax.borderColor;
+ redrawPlot = true;
+ }
+ val = (th.axesStyles.borderWidth != null) ? th.axesStyles.borderWidth : thax.borderWidth;
+ if (thax.borderWidth != null) {
+ axis.borderWidth = thax.borderWidth;
+ redrawPlot = true;
+ }
+ if (axis._ticks && axis._ticks[0]) {
+ for (var nn in thax.ticks) {
+ // val = null;
+ // if (th.axesStyles.ticks && th.axesStyles.ticks[nn] != null) {
+ // val = th.axesStyles.ticks[nn];
+ // }
+ // else if (thax.ticks[nn] != null){
+ // val = thax.ticks[nn]
+ // }
+ val = thax.ticks[nn];
+ if (val != null) {
+ axis.tickOptions[nn] = val;
+ axis._ticks = [];
+ redrawPlot = true;
+ }
+ }
+ }
+ if (axis._label && axis._label.show) {
+ for (var nn in thax.label) {
+ // val = null;
+ // if (th.axesStyles.label && th.axesStyles.label[nn] != null) {
+ // val = th.axesStyles.label[nn];
+ // }
+ // else if (thax.label && thax.label[nn] != null){
+ // val = thax.label[nn]
+ // }
+ val = thax.label[nn];
+ if (val != null) {
+ axis.labelOptions[nn] = val;
+ redrawPlot = true;
+ }
+ }
+ }
+
+ }
+ }
+
+ for (var n in th.grid) {
+ if (th.grid[n] != null) {
+ plot.grid[n] = th.grid[n];
+ }
+ }
+ if (!redrawPlot) {
+ plot.grid.draw();
+ }
+
+ if (plot.legend.show) {
+ for (n in th.legend) {
+ if (th.legend[n] != null) {
+ plot.legend[n] = th.legend[n];
+ }
+ }
+ }
+ if (plot.title.show) {
+ for (n in th.title) {
+ if (th.title[n] != null) {
+ plot.title[n] = th.title[n];
+ }
+ }
+ }
+
+ var i;
+ for (i=0; i<th.series.length; i++) {
+ var opts = {};
+ var redrawSeries = false;
+ for (n in th.series[i]) {
+ val = (th.seriesStyles[n] != null) ? th.seriesStyles[n] : th.series[i][n];
+ if (val != null) {
+ opts[n] = val;
+ if (n == 'color') {
+ plot.series[i].renderer.shapeRenderer.fillStyle = val;
+ plot.series[i].renderer.shapeRenderer.strokeStyle = val;
+ plot.series[i][n] = val;
+ }
+ else if ((n == 'lineWidth') || (n == 'linePattern')) {
+ plot.series[i].renderer.shapeRenderer[n] = val;
+ plot.series[i][n] = val;
+ }
+ else if (n == 'markerOptions') {
+ merge (plot.series[i].markerOptions, val);
+ merge (plot.series[i].markerRenderer, val);
+ }
+ else {
+ plot.series[i][n] = val;
+ }
+ redrawPlot = true;
+ }
+ }
+ }
+
+ if (redrawPlot) {
+ plot.target.empty();
+ plot.draw();
+ }
+
+ for (n in th.target) {
+ if (th.target[n] != null) {
+ plot.target.css(n, th.target[n]);
+ }
+ }
+ }
+
+ };
+
+ $.jqplot.ThemeEngine.prototype._add = function(theme, name) {
+ if (name) {
+ theme._name = name;
+ }
+ if (!theme._name) {
+ theme._name = Date.parse(new Date());
+ }
+ if (!this.themes.hasOwnProperty(theme._name)) {
+ this.themes[theme._name] = theme;
+ }
+ else {
+ throw new Error("jqplot.ThemeEngine Error: Theme already in use");
+ }
+ };
+
+ // method remove
+ // Delete the named theme, return true on success, false on failure.
+
+
+ /**
+ * method: remove
+ *
+ * Remove the given theme from the themeEngine.
+ *
+ * parameters:
+ *
+ * name - name of the theme to remove.
+ *
+ * returns:
+ *
+ * true on success, false on failure.
+ */
+ $.jqplot.ThemeEngine.prototype.remove = function(name) {
+ if (name == 'Default') {
+ return false;
+ }
+ return delete this.themes[name];
+ };
+
+ /**
+ * method: newTheme
+ *
+ * Create a new theme based on the default theme, adding it the themeEngine.
+ *
+ * parameters:
+ *
+ * name - name of the new theme.
+ * obj - optional object of styles to be applied to this new theme.
+ *
+ * returns:
+ *
+ * new Theme object.
+ */
+ $.jqplot.ThemeEngine.prototype.newTheme = function(name, obj) {
+ if (typeof(name) == 'object') {
+ obj = obj || name;
+ name = null;
+ }
+ if (obj && obj._name) {
+ name = obj._name;
+ }
+ else {
+ name = name || Date.parse(new Date());
+ }
+ // var th = new $.jqplot.Theme(name);
+ var th = this.copy(this.themes['Default']._name, name);
+ $.jqplot.extend(th, obj);
+ return th;
+ };
+
+ // function clone(obj) {
+ // return eval(obj.toSource());
+ // }
+
+ function clone(obj){
+ if(obj == null || typeof(obj) != 'object'){
+ return obj;
+ }
+
+ var temp = new obj.constructor();
+ for(var key in obj){
+ temp[key] = clone(obj[key]);
+ }
+ return temp;
+ }
+
+ $.jqplot.clone = clone;
+
+ function merge(obj1, obj2) {
+ if (obj2 == null || typeof(obj2) != 'object') {
+ return;
+ }
+ for (var key in obj2) {
+ if (key == 'highlightColors') {
+ obj1[key] = clone(obj2[key]);
+ }
+ if (obj2[key] != null && typeof(obj2[key]) == 'object') {
+ if (!obj1.hasOwnProperty(key)) {
+ obj1[key] = {};
+ }
+ merge(obj1[key], obj2[key]);
+ }
+ else {
+ obj1[key] = obj2[key];
+ }
+ }
+ }
+
+ $.jqplot.merge = merge;
+
+ // Use the jQuery 1.3.2 extend function since behaviour in jQuery 1.4 seems problematic
+ $.jqplot.extend = function() {
+ // copy reference to target object
+ var target = arguments[0] || {}, i = 1, length = arguments.length, deep = false, options;
+
+ // Handle a deep copy situation
+ if ( typeof target === "boolean" ) {
+ deep = target;
+ target = arguments[1] || {};
+ // skip the boolean and the target
+ i = 2;
+ }
+
+ // Handle case when target is a string or something (possible in deep copy)
+ if ( typeof target !== "object" && !toString.call(target) === "[object Function]" ) {
+ target = {};
+ }
+
+ for ( ; i < length; i++ ){
+ // Only deal with non-null/undefined values
+ if ( (options = arguments[ i ]) != null ) {
+ // Extend the base object
+ for ( var name in options ) {
+ var src = target[ name ], copy = options[ name ];
+
+ // Prevent never-ending loop
+ if ( target === copy ) {
+ continue;
+ }
+
+ // Recurse if we're merging object values
+ if ( deep && copy && typeof copy === "object" && !copy.nodeType ) {
+ target[ name ] = $.jqplot.extend( deep,
+ // Never move original objects, clone them
+ src || ( copy.length != null ? [ ] : { } )
+ , copy );
+ }
+ // Don't bring in undefined values
+ else if ( copy !== undefined ) {
+ target[ name ] = copy;
+ }
+ }
+ }
+ }
+ // Return the modified object
+ return target;
+ };
+
+ /**
+ * method: rename
+ *
+ * Rename a theme.
+ *
+ * parameters:
+ *
+ * oldName - current name of the theme.
+ * newName - desired name of the theme.
+ *
+ * returns:
+ *
+ * new Theme object.
+ */
+ $.jqplot.ThemeEngine.prototype.rename = function (oldName, newName) {
+ if (oldName == 'Default' || newName == 'Default') {
+ throw new Error ("jqplot.ThemeEngine Error: Cannot rename from/to Default");
+ }
+ if (this.themes.hasOwnProperty(newName)) {
+ throw new Error ("jqplot.ThemeEngine Error: New name already in use.");
+ }
+ else if (this.themes.hasOwnProperty(oldName)) {
+ var th = this.copy (oldName, newName);
+ this.remove(oldName);
+ return th;
+ }
+ throw new Error("jqplot.ThemeEngine Error: Old name or new name invalid");
+ };
+
+ /**
+ * method: copy
+ *
+ * Create a copy of an existing theme in the themeEngine, adding it the themeEngine.
+ *
+ * parameters:
+ *
+ * sourceName - name of the existing theme.
+ * targetName - name of the copy.
+ * obj - optional object of style parameter to apply to the new theme.
+ *
+ * returns:
+ *
+ * new Theme object.
+ */
+ $.jqplot.ThemeEngine.prototype.copy = function (sourceName, targetName, obj) {
+ if (targetName == 'Default') {
+ throw new Error ("jqplot.ThemeEngine Error: Cannot copy over Default theme");
+ }
+ if (!this.themes.hasOwnProperty(sourceName)) {
+ var s = "jqplot.ThemeEngine Error: Source name invalid";
+ throw new Error(s);
+ }
+ if (this.themes.hasOwnProperty(targetName)) {
+ var s = "jqplot.ThemeEngine Error: Target name invalid";
+ throw new Error(s);
+ }
+ else {
+ var th = clone(this.themes[sourceName]);
+ th._name = targetName;
+ $.jqplot.extend(true, th, obj);
+ this._add(th);
+ return th;
+ }
+ };
+
+
+ $.jqplot.Theme = function(name, obj) {
+ if (typeof(name) == 'object') {
+ obj = obj || name;
+ name = null;
+ }
+ name = name || Date.parse(new Date());
+ this._name = name;
+ this.target = {
+ backgroundColor: null
+ };
+ this.legend = {
+ textColor: null,
+ fontFamily: null,
+ fontSize: null,
+ border: null,
+ background: null
+ };
+ this.title = {
+ textColor: null,
+ fontFamily: null,
+ fontSize: null,
+ textAlign: null
+ };
+ this.seriesStyles = {};
+ this.series = [];
+ this.grid = {
+ drawGridlines: null,
+ gridLineColor: null,
+ gridLineWidth: null,
+ backgroundColor: null,
+ borderColor: null,
+ borderWidth: null,
+ shadow: null
+ };
+ this.axesStyles = {label:{}, ticks:{}};
+ this.axes = {};
+ if (typeof(obj) == 'string') {
+ this._name = obj;
+ }
+ else if(typeof(obj) == 'object') {
+ $.jqplot.extend(true, this, obj);
+ }
+ };
+
+ var AxisProperties = function() {
+ this.borderColor = null;
+ this.borderWidth = null;
+ this.ticks = new AxisTicks();
+ this.label = new AxisLabel();
+ };
+
+ var AxisTicks = function() {
+ this.show = null;
+ this.showGridline = null;
+ this.showLabel = null;
+ this.showMark = null;
+ this.size = null;
+ this.textColor = null;
+ this.whiteSpace = null;
+ this.fontSize = null;
+ this.fontFamily = null;
+ };
+
+ var AxisLabel = function() {
+ this.textColor = null;
+ this.whiteSpace = null;
+ this.fontSize = null;
+ this.fontFamily = null;
+ this.fontWeight = null;
+ };
+
+ var LineSeriesProperties = function() {
+ this.color=null;
+ this.lineWidth=null;
+ this.linePattern=null;
+ this.shadow=null;
+ this.fillColor=null;
+ this.showMarker=null;
+ this.markerOptions = new MarkerOptions();
+ };
+
+ var MarkerOptions = function() {
+ this.show = null;
+ this.style = null;
+ this.lineWidth = null;
+ this.size = null;
+ this.color = null;
+ this.shadow = null;
+ };
+
+ var BarSeriesProperties = function() {
+ this.color=null;
+ this.seriesColors=null;
+ this.lineWidth=null;
+ this.shadow=null;
+ this.barPadding=null;
+ this.barMargin=null;
+ this.barWidth=null;
+ this.highlightColors=null;
+ };
+
+ var PieSeriesProperties = function() {
+ this.seriesColors=null;
+ this.padding=null;
+ this.sliceMargin=null;
+ this.fill=null;
+ this.shadow=null;
+ this.startAngle=null;
+ this.lineWidth=null;
+ this.highlightColors=null;
+ };
+
+ var DonutSeriesProperties = function() {
+ this.seriesColors=null;
+ this.padding=null;
+ this.sliceMargin=null;
+ this.fill=null;
+ this.shadow=null;
+ this.startAngle=null;
+ this.lineWidth=null;
+ this.innerDiameter=null;
+ this.thickness=null;
+ this.ringMargin=null;
+ this.highlightColors=null;
+ };
+
+ var FunnelSeriesProperties = function() {
+ this.color=null;
+ this.lineWidth=null;
+ this.shadow=null;
+ this.padding=null;
+ this.sectionMargin=null;
+ this.seriesColors=null;
+ this.highlightColors=null;
+ };
+
+ var MeterSeriesProperties = function() {
+ this.padding=null;
+ this.backgroundColor=null;
+ this.ringColor=null;
+ this.tickColor=null;
+ this.ringWidth=null;
+ this.intervalColors=null;
+ this.intervalInnerRadius=null;
+ this.intervalOuterRadius=null;
+ this.hubRadius=null;
+ this.needleThickness=null;
+ this.needlePad=null;
+ };
+
+
+
+
+ $.fn.jqplotChildText = function() {
+ return $(this).contents().filter(function() {
+ return this.nodeType == 3; // Node.TEXT_NODE not defined in I7
+ }).text();
+ };
+
+ // Returns font style as abbreviation for "font" property.
+ $.fn.jqplotGetComputedFontStyle = function() {
+ var css = window.getComputedStyle ? window.getComputedStyle(this[0], "") : this[0].currentStyle;
+ var attrs = css['font-style'] ? ['font-style', 'font-weight', 'font-size', 'font-family'] : ['fontStyle', 'fontWeight', 'fontSize', 'fontFamily'];
+ var style = [];
+
+ for (var i=0 ; i < attrs.length; ++i) {
+ var attr = String(css[attrs[i]]);
+
+ if (attr && attr != 'normal') {
+ style.push(attr);
+ }
+ }
+ return style.join(' ');
+ };
+
+ /**
+ * Namespace: $.fn
+ * jQuery namespace to attach functions to jQuery elements.
+ *
+ */
+
+ $.fn.jqplotToImageCanvas = function(options) {
+
+ options = options || {};
+ var x_offset = (options.x_offset == null) ? 0 : options.x_offset;
+ var y_offset = (options.y_offset == null) ? 0 : options.y_offset;
+ var backgroundColor = (options.backgroundColor == null) ? 'rgb(255,255,255)' : options.backgroundColor;
+
+ if ($(this).width() == 0 || $(this).height() == 0) {
+ return null;
+ }
+
+ // excanvas and hence IE < 9 do not support toDataURL and cannot export images.
+ if ($.jqplot.use_excanvas) {
+ return null;
+ }
+
+ var newCanvas = document.createElement("canvas");
+ var h = $(this).outerHeight(true);
+ var w = $(this).outerWidth(true);
+ var offs = $(this).offset();
+ var plotleft = offs.left;
+ var plottop = offs.top;
+ var transx = 0, transy = 0;
+
+ // have to check if any elements are hanging outside of plot area before rendering,
+ // since changing width of canvas will erase canvas.
+
+ var clses = ['jqplot-table-legend', 'jqplot-xaxis-tick', 'jqplot-x2axis-tick', 'jqplot-yaxis-tick', 'jqplot-y2axis-tick', 'jqplot-y3axis-tick',
+ 'jqplot-y4axis-tick', 'jqplot-y5axis-tick', 'jqplot-y6axis-tick', 'jqplot-y7axis-tick', 'jqplot-y8axis-tick', 'jqplot-y9axis-tick',
+ 'jqplot-xaxis-label', 'jqplot-x2axis-label', 'jqplot-yaxis-label', 'jqplot-y2axis-label', 'jqplot-y3axis-label', 'jqplot-y4axis-label',
+ 'jqplot-y5axis-label', 'jqplot-y6axis-label', 'jqplot-y7axis-label', 'jqplot-y8axis-label', 'jqplot-y9axis-label' ];
+
+ var temptop, templeft, tempbottom, tempright;
+
+ for (var i = 0; i < clses.length; i++) {
+ $(this).find('.'+clses[i]).each(function() {
+ temptop = $(this).offset().top - plottop;
+ templeft = $(this).offset().left - plotleft;
+ tempright = templeft + $(this).outerWidth(true) + transx;
+ tempbottom = temptop + $(this).outerHeight(true) + transy;
+ if (templeft < -transx) {
+ w = w - transx - templeft;
+ transx = -templeft;
+ }
+ if (temptop < -transy) {
+ h = h - transy - temptop;
+ transy = - temptop;
+ }
+ if (tempright > w) {
+ w = tempright;
+ }
+ if (tempbottom > h) {
+ h = tempbottom;
+ }
+ });
+ }
+
+ newCanvas.width = w + Number(x_offset);
+ newCanvas.height = h + Number(y_offset);
+
+ var newContext = newCanvas.getContext("2d");
+
+ newContext.save();
+ newContext.fillStyle = backgroundColor;
+ newContext.fillRect(0,0, newCanvas.width, newCanvas.height);
+ newContext.restore();
+
+ newContext.translate(transx, transy);
+ newContext.textAlign = 'left';
+ newContext.textBaseline = 'top';
+
+ function getLineheight(el) {
+ var lineheight = parseInt($(el).css('line-height'), 10);
+
+ if (isNaN(lineheight)) {
+ lineheight = parseInt($(el).css('font-size'), 10) * 1.2;
+ }
+ return lineheight;
+ }
+
+ function writeWrappedText (el, context, text, left, top, canvasWidth) {
+ var lineheight = getLineheight(el);
+ var tagwidth = $(el).innerWidth();
+ var tagheight = $(el).innerHeight();
+ var words = text.split(/\s+/);
+ var wl = words.length;
+ var w = '';
+ var breaks = [];
+ var temptop = top;
+ var templeft = left;
+
+ for (var i=0; i<wl; i++) {
+ w += words[i];
+ if (context.measureText(w).width > tagwidth) {
+ breaks.push(i);
+ w = '';
+ i--;
+ }
+ }
+ if (breaks.length === 0) {
+ // center text if necessary
+ if ($(el).css('textAlign') === 'center') {
+ templeft = left + (canvasWidth - context.measureText(w).width)/2 - transx;
+ }
+ context.fillText(text, templeft, top);
+ }
+ else {
+ w = words.slice(0, breaks[0]).join(' ');
+ // center text if necessary
+ if ($(el).css('textAlign') === 'center') {
+ templeft = left + (canvasWidth - context.measureText(w).width)/2 - transx;
+ }
+ context.fillText(w, templeft, temptop);
+ temptop += lineheight;
+ for (var i=1, l=breaks.length; i<l; i++) {
+ w = words.slice(breaks[i-1], breaks[i]).join(' ');
+ // center text if necessary
+ if ($(el).css('textAlign') === 'center') {
+ templeft = left + (canvasWidth - context.measureText(w).width)/2 - transx;
+ }
+ context.fillText(w, templeft, temptop);
+ temptop += lineheight;
+ }
+ w = words.slice(breaks[i-1], words.length).join(' ');
+ // center text if necessary
+ if ($(el).css('textAlign') === 'center') {
+ templeft = left + (canvasWidth - context.measureText(w).width)/2 - transx;
+ }
+ context.fillText(w, templeft, temptop);
+ }
+
+ }
+
+ function _jqpToImage(el, x_offset, y_offset) {
+ var tagname = el.tagName.toLowerCase();
+ var p = $(el).position();
+ var css = window.getComputedStyle ? window.getComputedStyle(el, "") : el.currentStyle; // for IE < 9
+ var left = x_offset + p.left + parseInt(css.marginLeft, 10) + parseInt(css.borderLeftWidth, 10) + parseInt(css.paddingLeft, 10);
+ var top = y_offset + p.top + parseInt(css.marginTop, 10) + parseInt(css.borderTopWidth, 10)+ parseInt(css.paddingTop, 10);
+ var w = newCanvas.width;
+ // var left = x_offset + p.left + $(el).css('marginLeft') + $(el).css('borderLeftWidth')
+
+ // somehow in here, for divs within divs, the width of the inner div should be used instead of the canvas.
+
+ if ((tagname == 'div' || tagname == 'span') && !$(el).hasClass('jqplot-highlighter-tooltip')) {
+ $(el).children().each(function() {
+ _jqpToImage(this, left, top);
+ });
+ var text = $(el).jqplotChildText();
+
+ if (text) {
+ newContext.font = $(el).jqplotGetComputedFontStyle();
+ newContext.fillStyle = $(el).css('color');
+
+ writeWrappedText(el, newContext, text, left, top, w);
+ }
+ }
+
+ // handle the standard table legend
+
+ else if (tagname === 'table' && $(el).hasClass('jqplot-table-legend')) {
+ newContext.strokeStyle = $(el).css('border-top-color');
+ newContext.fillStyle = $(el).css('background-color');
+ newContext.fillRect(left, top, $(el).innerWidth(), $(el).innerHeight());
+ if (parseInt($(el).css('border-top-width'), 10) > 0) {
+ newContext.strokeRect(left, top, $(el).innerWidth(), $(el).innerHeight());
+ }
+
+ // find all the swatches
+ $(el).find('div.jqplot-table-legend-swatch-outline').each(function() {
+ // get the first div and stroke it
+ var elem = $(this);
+ newContext.strokeStyle = elem.css('border-top-color');
+ var l = left + elem.position().left;
+ var t = top + elem.position().top;
+ newContext.strokeRect(l, t, elem.innerWidth(), elem.innerHeight());
+
+ // now fill the swatch
+
+ l += parseInt(elem.css('padding-left'), 10);
+ t += parseInt(elem.css('padding-top'), 10);
+ var h = elem.innerHeight() - 2 * parseInt(elem.css('padding-top'), 10);
+ var w = elem.innerWidth() - 2 * parseInt(elem.css('padding-left'), 10);
+
+ var swatch = elem.children('div.jqplot-table-legend-swatch');
+ newContext.fillStyle = swatch.css('background-color');
+ newContext.fillRect(l, t, w, h);
+ });
+
+ // now add text
+
+ $(el).find('td.jqplot-table-legend-label').each(function(){
+ var elem = $(this);
+ var l = left + elem.position().left;
+ var t = top + elem.position().top + parseInt(elem.css('padding-top'), 10);
+ newContext.font = elem.jqplotGetComputedFontStyle();
+ newContext.fillStyle = elem.css('color');
+ writeWrappedText(elem, newContext, elem.text(), l, t, w);
+ });
+
+ var elem = null;
+ }
+
+ else if (tagname == 'canvas') {
+ newContext.drawImage(el, left, top);
+ }
+ }
+ $(this).children().each(function() {
+ _jqpToImage(this, x_offset, y_offset);
+ });
+ return newCanvas;
+ };
+
+ // return the raw image data string.
+ // Should work on canvas supporting browsers.
+ $.fn.jqplotToImageStr = function(options) {
+ var imgCanvas = $(this).jqplotToImageCanvas(options);
+ if (imgCanvas) {
+ return imgCanvas.toDataURL("image/png");
+ }
+ else {
+ return null;
+ }
+ };
+
+ // return a DOM <img> element and return it.
+ // Should work on canvas supporting browsers.
+ $.fn.jqplotToImageElem = function(options) {
+ var elem = document.createElement("img");
+ var str = $(this).jqplotToImageStr(options);
+ elem.src = str;
+ return elem;
+ };
+
+ // return a string for an <img> element and return it.
+ // Should work on canvas supporting browsers.
+ $.fn.jqplotToImageElemStr = function(options) {
+ var str = '<img src='+$(this).jqplotToImageStr(options)+' />';
+ return str;
+ };
+
+ // Not gauranteed to work, even on canvas supporting browsers due to
+ // limitations with location.href and browser support.
+ $.fn.jqplotSaveImage = function() {
+ var imgData = $(this).jqplotToImageStr({});
+ if (imgData) {
+ window.location.href = imgData.replace("image/png", "image/octet-stream");
+ }
+
+ };
+
+ // Not gauranteed to work, even on canvas supporting browsers due to
+ // limitations with window.open and arbitrary data.
+ $.fn.jqplotViewImage = function() {
+ var imgStr = $(this).jqplotToImageElemStr({});
+ var imgData = $(this).jqplotToImageStr({});
+ if (imgStr) {
+ var w = window.open('');
+ w.document.open("image/png");
+ w.document.write(imgStr);
+ w.document.close();
+ w = null;
+ }
+ };
+
+
+
+
+ /**
+ * @description
+ * <p>Object with extended date parsing and formatting capabilities.
+ * This library borrows many concepts and ideas from the Date Instance
+ * Methods by Ken Snyder along with some parts of Ken's actual code.</p>
+ *
+ * <p>jsDate takes a different approach by not extending the built-in
+ * Date Object, improving date parsing, allowing for multiple formatting
+ * syntaxes and multiple and more easily expandable localization.</p>
+ *
+ * @author Chris Leonello
+ * @date #date#
+ * @version #VERSION#
+ * @copyright (c) 2010 Chris Leonello
+ * jsDate is currently available for use in all personal or commercial projects
+ * under both the MIT and GPL version 2.0 licenses. This means that you can
+ * choose the license that best suits your project and use it accordingly.
+ *
+ * <p>Ken's origianl Date Instance Methods and copyright notice:</p>
+ * <pre>
+ * Ken Snyder (ken d snyder at gmail dot com)
+ * 2008-09-10
+ * version 2.0.2 (http://kendsnyder.com/sandbox/date/)
+ * Creative Commons Attribution License 3.0 (http://creativecommons.org/licenses/by/3.0/)
+ * </pre>
+ *
+ * @class
+ * @name jsDate
+ * @param {String | Number | Array | Date&nbsp;Object | Options&nbsp;Object} arguments Optional arguments, either a parsable date/time string,
+ * a JavaScript timestamp, an array of numbers of form [year, month, day, hours, minutes, seconds, milliseconds],
+ * a Date object, or an options object of form {syntax: "perl", date:some Date} where all options are optional.
+ */
+
+ var jsDate = function () {
+
+ this.syntax = jsDate.config.syntax;
+ this._type = "jsDate";
+ this.proxy = new Date();
+ this.options = {};
+ this.locale = jsDate.regional.getLocale();
+ this.formatString = '';
+ this.defaultCentury = jsDate.config.defaultCentury;
+
+ switch ( arguments.length ) {
+ case 0:
+ break;
+ case 1:
+ // other objects either won't have a _type property or,
+ // if they do, it shouldn't be set to "jsDate", so
+ // assume it is an options argument.
+ if (get_type(arguments[0]) == "[object Object]" && arguments[0]._type != "jsDate") {
+ var opts = this.options = arguments[0];
+ this.syntax = opts.syntax || this.syntax;
+ this.defaultCentury = opts.defaultCentury || this.defaultCentury;
+ this.proxy = jsDate.createDate(opts.date);
+ }
+ else {
+ this.proxy = jsDate.createDate(arguments[0]);
+ }
+ break;
+ default:
+ var a = [];
+ for ( var i=0; i<arguments.length; i++ ) {
+ a.push(arguments[i]);
+ }
+ // this should be the current date/time?
+ this.proxy = new Date();
+ this.proxy.setFullYear.apply( this.proxy, a.slice(0,3) );
+ if ( a.slice(3).length ) {
+ this.proxy.setHours.apply( this.proxy, a.slice(3) );
+ }
+ break;
+ }
+ };
+
+ /**
+ * @namespace Configuration options that will be used as defaults for all instances on the page.
+ * @property {String} defaultLocale The default locale to use [en].
+ * @property {String} syntax The default syntax to use [perl].
+ * @property {Number} defaultCentury The default centry for 2 digit dates.
+ */
+ jsDate.config = {
+ defaultLocale: 'en',
+ syntax: 'perl',
+ defaultCentury: 1900
+ };
+
+ /**
+ * Add an arbitrary amount to the currently stored date
+ *
+ * @param {Number} number
+ * @param {String} unit
+ * @returns {jsDate}
+ */
+
+ jsDate.prototype.add = function(number, unit) {
+ var factor = multipliers[unit] || multipliers.day;
+ if (typeof factor == 'number') {
+ this.proxy.setTime(this.proxy.getTime() + (factor * number));
+ } else {
+ factor.add(this, number);
+ }
+ return this;
+ };
+
+ /**
+ * Create a new jqplot.date object with the same date
+ *
+ * @returns {jsDate}
+ */
+
+ jsDate.prototype.clone = function() {
+ return new jsDate(this.proxy.getTime());
+ };
+
+ /**
+ * Get the UTC TimeZone Offset of this date in milliseconds.
+ *
+ * @returns {Number}
+ */
+
+ jsDate.prototype.getUtcOffset = function() {
+ return this.proxy.getTimezoneOffset() * 60000;
+ };
+
+ /**
+ * Find the difference between this jsDate and another date.
+ *
+ * @param {String| Number| Array| jsDate&nbsp;Object| Date&nbsp;Object} dateObj
+ * @param {String} unit
+ * @param {Boolean} allowDecimal
+ * @returns {Number} Number of units difference between dates.
+ */
+
+ jsDate.prototype.diff = function(dateObj, unit, allowDecimal) {
+ // ensure we have a Date object
+ dateObj = new jsDate(dateObj);
+ if (dateObj === null) {
+ return null;
+ }
+ // get the multiplying factor integer or factor function
+ var factor = multipliers[unit] || multipliers.day;
+ if (typeof factor == 'number') {
+ // multiply
+ var unitDiff = (this.proxy.getTime() - dateObj.proxy.getTime()) / factor;
+ } else {
+ // run function
+ var unitDiff = factor.diff(this.proxy, dateObj.proxy);
+ }
+ // if decimals are not allowed, round toward zero
+ return (allowDecimal ? unitDiff : Math[unitDiff > 0 ? 'floor' : 'ceil'](unitDiff));
+ };
+
+ /**
+ * Get the abbreviated name of the current week day
+ *
+ * @returns {String}
+ */
+
+ jsDate.prototype.getAbbrDayName = function() {
+ return jsDate.regional[this.locale]["dayNamesShort"][this.proxy.getDay()];
+ };
+
+ /**
+ * Get the abbreviated name of the current month
+ *
+ * @returns {String}
+ */
+
+ jsDate.prototype.getAbbrMonthName = function() {
+ return jsDate.regional[this.locale]["monthNamesShort"][this.proxy.getMonth()];
+ };
+
+ /**
+ * Get UPPER CASE AM or PM for the current time
+ *
+ * @returns {String}
+ */
+
+ jsDate.prototype.getAMPM = function() {
+ return this.proxy.getHours() >= 12 ? 'PM' : 'AM';
+ };
+
+ /**
+ * Get lower case am or pm for the current time
+ *
+ * @returns {String}
+ */
+
+ jsDate.prototype.getAmPm = function() {
+ return this.proxy.getHours() >= 12 ? 'pm' : 'am';
+ };
+
+ /**
+ * Get the century (19 for 20th Century)
+ *
+ * @returns {Integer} Century (19 for 20th century).
+ */
+ jsDate.prototype.getCentury = function() {
+ return parseInt(this.proxy.getFullYear()/100, 10);
+ };
+
+ /**
+ * Implements Date functionality
+ */
+ jsDate.prototype.getDate = function() {
+ return this.proxy.getDate();
+ };
+
+ /**
+ * Implements Date functionality
+ */
+ jsDate.prototype.getDay = function() {
+ return this.proxy.getDay();
+ };
+
+ /**
+ * Get the Day of week 1 (Monday) thru 7 (Sunday)
+ *
+ * @returns {Integer} Day of week 1 (Monday) thru 7 (Sunday)
+ */
+ jsDate.prototype.getDayOfWeek = function() {
+ var dow = this.proxy.getDay();
+ return dow===0?7:dow;
+ };
+
+ /**
+ * Get the day of the year
+ *
+ * @returns {Integer} 1 - 366, day of the year
+ */
+ jsDate.prototype.getDayOfYear = function() {
+ var d = this.proxy;
+ var ms = d - new Date('' + d.getFullYear() + '/1/1 GMT');
+ ms += d.getTimezoneOffset()*60000;
+ d = null;
+ return parseInt(ms/60000/60/24, 10)+1;
+ };
+
+ /**
+ * Get the name of the current week day
+ *
+ * @returns {String}
+ */
+
+ jsDate.prototype.getDayName = function() {
+ return jsDate.regional[this.locale]["dayNames"][this.proxy.getDay()];
+ };
+
+ /**
+ * Get the week number of the given year, starting with the first Sunday as the first week
+ * @returns {Integer} Week number (13 for the 13th full week of the year).
+ */
+ jsDate.prototype.getFullWeekOfYear = function() {
+ var d = this.proxy;
+ var doy = this.getDayOfYear();
+ var rdow = 6-d.getDay();
+ var woy = parseInt((doy+rdow)/7, 10);
+ return woy;
+ };
+
+ /**
+ * Implements Date functionality
+ */
+ jsDate.prototype.getFullYear = function() {
+ return this.proxy.getFullYear();
+ };
+
+ /**
+ * Get the GMT offset in hours and minutes (e.g. +06:30)
+ *
+ * @returns {String}
+ */
+
+ jsDate.prototype.getGmtOffset = function() {
+ // divide the minutes offset by 60
+ var hours = this.proxy.getTimezoneOffset() / 60;
+ // decide if we are ahead of or behind GMT
+ var prefix = hours < 0 ? '+' : '-';
+ // remove the negative sign if any
+ hours = Math.abs(hours);
+ // add the +/- to the padded number of hours to : to the padded minutes
+ return prefix + addZeros(Math.floor(hours), 2) + ':' + addZeros((hours % 1) * 60, 2);
+ };
+
+ /**
+ * Implements Date functionality
+ */
+ jsDate.prototype.getHours = function() {
+ return this.proxy.getHours();
+ };
+
+ /**
+ * Get the current hour on a 12-hour scheme
+ *
+ * @returns {Integer}
+ */
+
+ jsDate.prototype.getHours12 = function() {
+ var hours = this.proxy.getHours();
+ return hours > 12 ? hours - 12 : (hours == 0 ? 12 : hours);
+ };
+
+
+ jsDate.prototype.getIsoWeek = function() {
+ var d = this.proxy;
+ var woy = d.getWeekOfYear();
+ var dow1_1 = (new Date('' + d.getFullYear() + '/1/1')).getDay();
+ // First week is 01 and not 00 as in the case of %U and %W,
+ // so we add 1 to the final result except if day 1 of the year
+ // is a Monday (then %W returns 01).
+ // We also need to subtract 1 if the day 1 of the year is
+ // Friday-Sunday, so the resulting equation becomes:
+ var idow = woy + (dow1_1 > 4 || dow1_1 <= 1 ? 0 : 1);
+ if(idow == 53 && (new Date('' + d.getFullYear() + '/12/31')).getDay() < 4)
+ {
+ idow = 1;
+ }
+ else if(idow === 0)
+ {
+ d = new jsDate(new Date('' + (d.getFullYear()-1) + '/12/31'));
+ idow = d.getIsoWeek();
+ }
+ d = null;
+ return idow;
+ };
+
+ /**
+ * Implements Date functionality
+ */
+ jsDate.prototype.getMilliseconds = function() {
+ return this.proxy.getMilliseconds();
+ };
+
+ /**
+ * Implements Date functionality
+ */
+ jsDate.prototype.getMinutes = function() {
+ return this.proxy.getMinutes();
+ };
+
+ /**
+ * Implements Date functionality
+ */
+ jsDate.prototype.getMonth = function() {
+ return this.proxy.getMonth();
+ };
+
+ /**
+ * Get the name of the current month
+ *
+ * @returns {String}
+ */
+
+ jsDate.prototype.getMonthName = function() {
+ return jsDate.regional[this.locale]["monthNames"][this.proxy.getMonth()];
+ };
+
+ /**
+ * Get the number of the current month, 1-12
+ *
+ * @returns {Integer}
+ */
+
+ jsDate.prototype.getMonthNumber = function() {
+ return this.proxy.getMonth() + 1;
+ };
+
+ /**
+ * Implements Date functionality
+ */
+ jsDate.prototype.getSeconds = function() {
+ return this.proxy.getSeconds();
+ };
+
+ /**
+ * Return a proper two-digit year integer
+ *
+ * @returns {Integer}
+ */
+
+ jsDate.prototype.getShortYear = function() {
+ return this.proxy.getYear() % 100;
+ };
+
+ /**
+ * Implements Date functionality
+ */
+ jsDate.prototype.getTime = function() {
+ return this.proxy.getTime();
+ };
+
+ /**
+ * Get the timezone abbreviation
+ *
+ * @returns {String} Abbreviation for the timezone
+ */
+ jsDate.prototype.getTimezoneAbbr = function() {
+ return this.proxy.toString().replace(/^.*\(([^)]+)\)$/, '$1');
+ };
+
+ /**
+ * Get the browser-reported name for the current timezone (e.g. MDT, Mountain Daylight Time)
+ *
+ * @returns {String}
+ */
+ jsDate.prototype.getTimezoneName = function() {
+ var match = /(?:\((.+)\)$| ([A-Z]{3}) )/.exec(this.toString());
+ return match[1] || match[2] || 'GMT' + this.getGmtOffset();
+ };
+
+ /**
+ * Implements Date functionality
+ */
+ jsDate.prototype.getTimezoneOffset = function() {
+ return this.proxy.getTimezoneOffset();
+ };
+
+
+ /**
+ * Get the week number of the given year, starting with the first Monday as the first week
+ * @returns {Integer} Week number (13 for the 13th week of the year).
+ */
+ jsDate.prototype.getWeekOfYear = function() {
+ var doy = this.getDayOfYear();
+ var rdow = 7 - this.getDayOfWeek();
+ var woy = parseInt((doy+rdow)/7, 10);
+ return woy;
+ };
+
+ /**
+ * Get the current date as a Unix timestamp
+ *
+ * @returns {Integer}
+ */
+
+ jsDate.prototype.getUnix = function() {
+ return Math.round(this.proxy.getTime() / 1000, 0);
+ };
+
+ /**
+ * Implements Date functionality
+ */
+ jsDate.prototype.getYear = function() {
+ return this.proxy.getYear();
+ };
+
+ /**
+ * Return a date one day ahead (or any other unit)
+ *
+ * @param {String} unit Optional, year | month | day | week | hour | minute | second | millisecond
+ * @returns {jsDate}
+ */
+
+ jsDate.prototype.next = function(unit) {
+ unit = unit || 'day';
+ return this.clone().add(1, unit);
+ };
+
+ /**
+ * Set the jsDate instance to a new date.
+ *
+ * @param {String | Number | Array | Date Object | jsDate Object | Options Object} arguments Optional arguments,
+ * either a parsable date/time string,
+ * a JavaScript timestamp, an array of numbers of form [year, month, day, hours, minutes, seconds, milliseconds],
+ * a Date object, jsDate Object or an options object of form {syntax: "perl", date:some Date} where all options are optional.
+ */
+ jsDate.prototype.set = function() {
+ switch ( arguments.length ) {
+ case 0:
+ this.proxy = new Date();
+ break;
+ case 1:
+ // other objects either won't have a _type property or,
+ // if they do, it shouldn't be set to "jsDate", so
+ // assume it is an options argument.
+ if (get_type(arguments[0]) == "[object Object]" && arguments[0]._type != "jsDate") {
+ var opts = this.options = arguments[0];
+ this.syntax = opts.syntax || this.syntax;
+ this.defaultCentury = opts.defaultCentury || this.defaultCentury;
+ this.proxy = jsDate.createDate(opts.date);
+ }
+ else {
+ this.proxy = jsDate.createDate(arguments[0]);
+ }
+ break;
+ default:
+ var a = [];
+ for ( var i=0; i<arguments.length; i++ ) {
+ a.push(arguments[i]);
+ }
+ // this should be the current date/time
+ this.proxy = new Date();
+ this.proxy.setFullYear.apply( this.proxy, a.slice(0,3) );
+ if ( a.slice(3).length ) {
+ this.proxy.setHours.apply( this.proxy, a.slice(3) );
+ }
+ break;
+ }
+ return this;
+ };
+
+ /**
+ * Sets the day of the month for a specified date according to local time.
+ * @param {Integer} dayValue An integer from 1 to 31, representing the day of the month.
+ */
+ jsDate.prototype.setDate = function(n) {
+ this.proxy.setDate(n);
+ return this;
+ };
+
+ /**
+ * Sets the full year for a specified date according to local time.
+ * @param {Integer} yearValue The numeric value of the year, for example, 1995.
+ * @param {Integer} monthValue Optional, between 0 and 11 representing the months January through December.
+ * @param {Integer} dayValue Optional, between 1 and 31 representing the day of the month. If you specify the dayValue parameter, you must also specify the monthValue.
+ */
+ jsDate.prototype.setFullYear = function() {
+ this.proxy.setFullYear.apply(this.proxy, arguments);
+ return this;
+ };
+
+ /**
+ * Sets the hours for a specified date according to local time.
+ *
+ * @param {Integer} hoursValue An integer between 0 and 23, representing the hour.
+ * @param {Integer} minutesValue Optional, An integer between 0 and 59, representing the minutes.
+ * @param {Integer} secondsValue Optional, An integer between 0 and 59, representing the seconds.
+ * If you specify the secondsValue parameter, you must also specify the minutesValue.
+ * @param {Integer} msValue Optional, A number between 0 and 999, representing the milliseconds.
+ * If you specify the msValue parameter, you must also specify the minutesValue and secondsValue.
+ */
+ jsDate.prototype.setHours = function() {
+ this.proxy.setHours.apply(this.proxy, arguments);
+ return this;
+ };
+
+ /**
+ * Implements Date functionality
+ */
+ jsDate.prototype.setMilliseconds = function(n) {
+ this.proxy.setMilliseconds(n);
+ return this;
+ };
+
+ /**
+ * Implements Date functionality
+ */
+ jsDate.prototype.setMinutes = function() {
+ this.proxy.setMinutes.apply(this.proxy, arguments);
+ return this;
+ };
+
+ /**
+ * Implements Date functionality
+ */
+ jsDate.prototype.setMonth = function() {
+ this.proxy.setMonth.apply(this.proxy, arguments);
+ return this;
+ };
+
+ /**
+ * Implements Date functionality
+ */
+ jsDate.prototype.setSeconds = function() {
+ this.proxy.setSeconds.apply(this.proxy, arguments);
+ return this;
+ };
+
+ /**
+ * Implements Date functionality
+ */
+ jsDate.prototype.setTime = function(n) {
+ this.proxy.setTime(n);
+ return this;
+ };
+
+ /**
+ * Implements Date functionality
+ */
+ jsDate.prototype.setYear = function() {
+ this.proxy.setYear.apply(this.proxy, arguments);
+ return this;
+ };
+
+ /**
+ * Provide a formatted string representation of this date.
+ *
+ * @param {String} formatString A format string.
+ * See: {@link jsDate.formats}.
+ * @returns {String} Date String.
+ */
+
+ jsDate.prototype.strftime = function(formatString) {
+ formatString = formatString || this.formatString || jsDate.regional[this.locale]['formatString'];
+ return jsDate.strftime(this, formatString, this.syntax);
+ };
+
+ /**
+ * Return a String representation of this jsDate object.
+ * @returns {String} Date string.
+ */
+
+ jsDate.prototype.toString = function() {
+ return this.proxy.toString();
+ };
+
+ /**
+ * Convert the current date to an 8-digit integer (%Y%m%d)
+ *
+ * @returns {Integer}
+ */
+
+ jsDate.prototype.toYmdInt = function() {
+ return (this.proxy.getFullYear() * 10000) + (this.getMonthNumber() * 100) + this.proxy.getDate();
+ };
+
+ /**
+ * @namespace Holds localizations for month/day names.
+ * <p>jsDate attempts to detect locale when loaded and defaults to 'en'.
+ * If a localization is detected which is not available, jsDate defaults to 'en'.
+ * Additional localizations can be added after jsDate loads. After adding a localization,
+ * call the jsDate.regional.getLocale() method. Currently, en, fr and de are defined.</p>
+ *
+ * <p>Localizations must be an object and have the following properties defined: monthNames, monthNamesShort, dayNames, dayNamesShort and Localizations are added like:</p>
+ * <pre class="code">
+ * jsDate.regional['en'] = {
+ * monthNames : 'January February March April May June July August September October November December'.split(' '),
+ * monthNamesShort : 'Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec'.split(' '),
+ * dayNames : 'Sunday Monday Tuesday Wednesday Thursday Friday Saturday'.split(' '),
+ * dayNamesShort : 'Sun Mon Tue Wed Thu Fri Sat'.split(' ')
+ * };
+ * </pre>
+ * <p>After adding localizations, call <code>jsDate.regional.getLocale();</code> to update the locale setting with the
+ * new localizations.</p>
+ */
+
+ jsDate.regional = {
+ 'en': {
+ monthNames: ['January','February','March','April','May','June','July','August','September','October','November','December'],
+ monthNamesShort: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun','Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
+ dayNames: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
+ dayNamesShort: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
+ formatString: '%Y-%m-%d %H:%M:%S'
+ },
+
+ 'fr': {
+ monthNames: ['Janvier','Février','Mars','Avril','Mai','Juin','Juillet','Août','Septembre','Octobre','Novembre','Décembre'],
+ monthNamesShort: ['Jan','Fév','Mar','Avr','Mai','Jun','Jul','Aoû','Sep','Oct','Nov','Déc'],
+ dayNames: ['Dimanche','Lundi','Mardi','Mercredi','Jeudi','Vendredi','Samedi'],
+ dayNamesShort: ['Dim','Lun','Mar','Mer','Jeu','Ven','Sam'],
+ formatString: '%Y-%m-%d %H:%M:%S'
+ },
+
+ 'de': {
+ monthNames: ['Januar','Februar','März','April','Mai','Juni','Juli','August','September','Oktober','November','Dezember'],
+ monthNamesShort: ['Jan','Feb','Mär','Apr','Mai','Jun','Jul','Aug','Sep','Okt','Nov','Dez'],
+ dayNames: ['Sonntag','Montag','Dienstag','Mittwoch','Donnerstag','Freitag','Samstag'],
+ dayNamesShort: ['So','Mo','Di','Mi','Do','Fr','Sa'],
+ formatString: '%Y-%m-%d %H:%M:%S'
+ },
+
+ 'es': {
+ monthNames: ['Enero','Febrero','Marzo','Abril','Mayo','Junio', 'Julio','Agosto','Septiembre','Octubre','Noviembre','Diciembre'],
+ monthNamesShort: ['Ene','Feb','Mar','Abr','May','Jun', 'Jul','Ago','Sep','Oct','Nov','Dic'],
+ dayNames: ['Domingo','Lunes','Martes','Mi&eacute;rcoles','Jueves','Viernes','S&aacute;bado'],
+ dayNamesShort: ['Dom','Lun','Mar','Mi&eacute;','Juv','Vie','S&aacute;b'],
+ formatString: '%Y-%m-%d %H:%M:%S'
+ },
+
+ 'ru': {
+ monthNames: ['Январь','Февраль','Март','Ðпрель','Май','Июнь','Июль','ÐвгуÑÑ‚','СентÑбрь','ОктÑбрь','ÐоÑбрь','Декабрь'],
+ monthNamesShort: ['Янв','Фев','Мар','Ðпр','Май','Июн','Июл','Ðвг','Сен','Окт','ÐоÑ','Дек'],
+ dayNames: ['воÑкреÑенье','понедельник','вторник','Ñреда','четверг','пÑтница','Ñуббота'],
+ dayNamesShort: ['вÑк','пнд','втр','Ñрд','чтв','птн','Ñбт'],
+ formatString: '%Y-%m-%d %H:%M:%S'
+ },
+
+ 'ar': {
+ monthNames: ['كانون الثاني', 'شباط', 'آذار', 'نيسان', 'آذار', 'حزيران','تموز', 'آب', 'أيلول', 'تشرين الأول', 'تشرين الثاني', 'كانون الأول'],
+ monthNamesShort: ['1','2','3','4','5','6','7','8','9','10','11','12'],
+ dayNames: ['السبت', 'الأحد', 'الاثنين', 'الثلاثاء', 'الأربعاء', 'الخميس', 'الجمعة'],
+ dayNamesShort: ['سبت', 'أحد', 'اثنين', 'ثلاثاء', 'أربعاء', 'خميس', 'جمعة'],
+ formatString: '%Y-%m-%d %H:%M:%S'
+ },
+
+ 'pt': {
+ monthNames: ['Janeiro','Fevereiro','Mar&ccedil;o','Abril','Maio','Junho','Julho','Agosto','Setembro','Outubro','Novembro','Dezembro'],
+ monthNamesShort: ['Jan','Fev','Mar','Abr','Mai','Jun','Jul','Ago','Set','Out','Nov','Dez'],
+ dayNames: ['Domingo','Segunda-feira','Ter&ccedil;a-feira','Quarta-feira','Quinta-feira','Sexta-feira','S&aacute;bado'],
+ dayNamesShort: ['Dom','Seg','Ter','Qua','Qui','Sex','S&aacute;b'],
+ formatString: '%Y-%m-%d %H:%M:%S'
+ },
+
+ 'pt-BR': {
+ monthNames: ['Janeiro','Fevereiro','Mar&ccedil;o','Abril','Maio','Junho', 'Julho','Agosto','Setembro','Outubro','Novembro','Dezembro'],
+ monthNamesShort: ['Jan','Fev','Mar','Abr','Mai','Jun','Jul','Ago','Set','Out','Nov','Dez'],
+ dayNames: ['Domingo','Segunda-feira','Ter&ccedil;a-feira','Quarta-feira','Quinta-feira','Sexta-feira','S&aacute;bado'],
+ dayNamesShort: ['Dom','Seg','Ter','Qua','Qui','Sex','S&aacute;b'],
+ formatString: '%Y-%m-%d %H:%M:%S'
+ }
+
+
+ };
+
+ // Set english variants to 'en'
+ jsDate.regional['en-US'] = jsDate.regional['en-GB'] = jsDate.regional['en'];
+
+ /**
+ * Try to determine the users locale based on the lang attribute of the html page. Defaults to 'en'
+ * if it cannot figure out a locale of if the locale does not have a localization defined.
+ * @returns {String} locale
+ */
+
+ jsDate.regional.getLocale = function () {
+ var l = jsDate.config.defaultLocale;
+
+ if ( document && document.getElementsByTagName('html') && document.getElementsByTagName('html')[0].lang ) {
+ l = document.getElementsByTagName('html')[0].lang;
+ if (!jsDate.regional.hasOwnProperty(l)) {
+ l = jsDate.config.defaultLocale;
+ }
+ }
+
+ return l;
+ };
+
+ // ms in day
+ var day = 24 * 60 * 60 * 1000;
+
+ // padd a number with zeros
+ var addZeros = function(num, digits) {
+ num = String(num);
+ var i = digits - num.length;
+ var s = String(Math.pow(10, i)).slice(1);
+ return s.concat(num);
+ };
+
+ // representations used for calculating differences between dates.
+ // This borrows heavily from Ken Snyder's work.
+ var multipliers = {
+ millisecond: 1,
+ second: 1000,
+ minute: 60 * 1000,
+ hour: 60 * 60 * 1000,
+ day: day,
+ week: 7 * day,
+ month: {
+ // add a number of months
+ add: function(d, number) {
+ // add any years needed (increments of 12)
+ multipliers.year.add(d, Math[number > 0 ? 'floor' : 'ceil'](number / 12));
+ // ensure that we properly wrap betwen December and January
+ // 11 % 12 = 11
+ // 12 % 12 = 0
+ var prevMonth = d.getMonth() + (number % 12);
+ if (prevMonth == 12) {
+ prevMonth = 0;
+ d.setYear(d.getFullYear() + 1);
+ } else if (prevMonth == -1) {
+ prevMonth = 11;
+ d.setYear(d.getFullYear() - 1);
+ }
+ d.setMonth(prevMonth);
+ },
+ // get the number of months between two Date objects (decimal to the nearest day)
+ diff: function(d1, d2) {
+ // get the number of years
+ var diffYears = d1.getFullYear() - d2.getFullYear();
+ // get the number of remaining months
+ var diffMonths = d1.getMonth() - d2.getMonth() + (diffYears * 12);
+ // get the number of remaining days
+ var diffDays = d1.getDate() - d2.getDate();
+ // return the month difference with the days difference as a decimal
+ return diffMonths + (diffDays / 30);
+ }
+ },
+ year: {
+ // add a number of years
+ add: function(d, number) {
+ d.setYear(d.getFullYear() + Math[number > 0 ? 'floor' : 'ceil'](number));
+ },
+ // get the number of years between two Date objects (decimal to the nearest day)
+ diff: function(d1, d2) {
+ return multipliers.month.diff(d1, d2) / 12;
+ }
+ }
+ };
+ //
+ // Alias each multiplier with an 's' to allow 'year' and 'years' for example.
+ // This comes from Ken Snyders work.
+ //
+ for (var unit in multipliers) {
+ if (unit.substring(unit.length - 1) != 's') { // IE will iterate newly added properties :|
+ multipliers[unit + 's'] = multipliers[unit];
+ }
+ }
+
+ //
+ // take a jsDate instance and a format code and return the formatted value.
+ // This is a somewhat modified version of Ken Snyder's method.
+ //
+ var format = function(d, code, syntax) {
+ // if shorcut codes are used, recursively expand those.
+ if (jsDate.formats[syntax]["shortcuts"][code]) {
+ return jsDate.strftime(d, jsDate.formats[syntax]["shortcuts"][code], syntax);
+ } else {
+ // get the format code function and addZeros() argument
+ var getter = (jsDate.formats[syntax]["codes"][code] || '').split('.');
+ var nbr = d['get' + getter[0]] ? d['get' + getter[0]]() : '';
+ if (getter[1]) {
+ nbr = addZeros(nbr, getter[1]);
+ }
+ return nbr;
+ }
+ };
+
+ /**
+ * @static
+ * Static function for convert a date to a string according to a given format. Also acts as namespace for strftime format codes.
+ * <p>strftime formatting can be accomplished without creating a jsDate object by calling jsDate.strftime():</p>
+ * <pre class="code">
+ * var formattedDate = jsDate.strftime('Feb 8, 2006 8:48:32', '%Y-%m-%d %H:%M:%S');
+ * </pre>
+ * @param {String | Number | Array | jsDate&nbsp;Object | Date&nbsp;Object} date A parsable date string, JavaScript time stamp, Array of form [year, month, day, hours, minutes, seconds, milliseconds], jsDate Object or Date object.
+ * @param {String} formatString String with embedded date formatting codes.
+ * See: {@link jsDate.formats}.
+ * @param {String} syntax Optional syntax to use [default perl].
+ * @param {String} locale Optional locale to use.
+ * @returns {String} Formatted representation of the date.
+ */
+ //
+ // Logic as implemented here is very similar to Ken Snyder's Date Instance Methods.
+ //
+ jsDate.strftime = function(d, formatString, syntax, locale) {
+ var syn = 'perl';
+ var loc = jsDate.regional.getLocale();
+
+ // check if syntax and locale are available or reversed
+ if (syntax && jsDate.formats.hasOwnProperty(syntax)) {
+ syn = syntax;
+ }
+ else if (syntax && jsDate.regional.hasOwnProperty(syntax)) {
+ loc = syntax;
+ }
+
+ if (locale && jsDate.formats.hasOwnProperty(locale)) {
+ syn = locale;
+ }
+ else if (locale && jsDate.regional.hasOwnProperty(locale)) {
+ loc = locale;
+ }
+
+ if (get_type(d) != "[object Object]" || d._type != "jsDate") {
+ d = new jsDate(d);
+ d.locale = loc;
+ }
+ if (!formatString) {
+ formatString = d.formatString || jsDate.regional[loc]['formatString'];
+ }
+ // default the format string to year-month-day
+ var source = formatString || '%Y-%m-%d',
+ result = '',
+ match;
+ // replace each format code
+ while (source.length > 0) {
+ if (match = source.match(jsDate.formats[syn].codes.matcher)) {
+ result += source.slice(0, match.index);
+ result += (match[1] || '') + format(d, match[2], syn);
+ source = source.slice(match.index + match[0].length);
+ } else {
+ result += source;
+ source = '';
+ }
+ }
+ return result;
+ };
+
+ /**
+ * @namespace
+ * Namespace to hold format codes and format shortcuts. "perl" and "php" format codes
+ * and shortcuts are defined by default. Additional codes and shortcuts can be
+ * added like:
+ *
+ * <pre class="code">
+ * jsDate.formats["perl"] = {
+ * "codes": {
+ * matcher: /someregex/,
+ * Y: "fullYear", // name of "get" method without the "get",
+ * ..., // more codes
+ * },
+ * "shortcuts": {
+ * F: '%Y-%m-%d',
+ * ..., // more shortcuts
+ * }
+ * };
+ * </pre>
+ *
+ * <p>Additionally, ISO and SQL shortcuts are defined and can be accesses via:
+ * <code>jsDate.formats.ISO</code> and <code>jsDate.formats.SQL</code>
+ */
+
+ jsDate.formats = {
+ ISO:'%Y-%m-%dT%H:%M:%S.%N%G',
+ SQL:'%Y-%m-%d %H:%M:%S'
+ };
+
+ /**
+ * Perl format codes and shortcuts for strftime.
+ *
+ * A hash (object) of codes where each code must be an array where the first member is
+ * the name of a Date.prototype or jsDate.prototype function to call
+ * and optionally a second member indicating the number to pass to addZeros()
+ *
+ * <p>The following format codes are defined:</p>
+ *
+ * <pre class="code">
+ * Code Result Description
+ * == Years ==
+ * %Y 2008 Four-digit year
+ * %y 08 Two-digit year
+ *
+ * == Months ==
+ * %m 09 Two-digit month
+ * %#m 9 One or two-digit month
+ * %B September Full month name
+ * %b Sep Abbreviated month name
+ *
+ * == Days ==
+ * %d 05 Two-digit day of month
+ * %#d 5 One or two-digit day of month
+ * %e 5 One or two-digit day of month
+ * %A Sunday Full name of the day of the week
+ * %a Sun Abbreviated name of the day of the week
+ * %w 0 Number of the day of the week (0 = Sunday, 6 = Saturday)
+ *
+ * == Hours ==
+ * %H 23 Hours in 24-hour format (two digits)
+ * %#H 3 Hours in 24-hour integer format (one or two digits)
+ * %I 11 Hours in 12-hour format (two digits)
+ * %#I 3 Hours in 12-hour integer format (one or two digits)
+ * %p PM AM or PM
+ *
+ * == Minutes ==
+ * %M 09 Minutes (two digits)
+ * %#M 9 Minutes (one or two digits)
+ *
+ * == Seconds ==
+ * %S 02 Seconds (two digits)
+ * %#S 2 Seconds (one or two digits)
+ * %s 1206567625723 Unix timestamp (Seconds past 1970-01-01 00:00:00)
+ *
+ * == Milliseconds ==
+ * %N 008 Milliseconds (three digits)
+ * %#N 8 Milliseconds (one to three digits)
+ *
+ * == Timezone ==
+ * %O 360 difference in minutes between local time and GMT
+ * %Z Mountain Standard Time Name of timezone as reported by browser
+ * %G 06:00 Hours and minutes between GMT
+ *
+ * == Shortcuts ==
+ * %F 2008-03-26 %Y-%m-%d
+ * %T 05:06:30 %H:%M:%S
+ * %X 05:06:30 %H:%M:%S
+ * %x 03/26/08 %m/%d/%y
+ * %D 03/26/08 %m/%d/%y
+ * %#c Wed Mar 26 15:31:00 2008 %a %b %e %H:%M:%S %Y
+ * %v 3-Sep-2008 %e-%b-%Y
+ * %R 15:31 %H:%M
+ * %r 03:31:00 PM %I:%M:%S %p
+ *
+ * == Characters ==
+ * %n \n Newline
+ * %t \t Tab
+ * %% % Percent Symbol
+ * </pre>
+ *
+ * <p>Formatting shortcuts that will be translated into their longer version.
+ * Be sure that format shortcuts do not refer to themselves: this will cause an infinite loop.</p>
+ *
+ * <p>Format codes and format shortcuts can be redefined after the jsDate
+ * module is imported.</p>
+ *
+ * <p>Note that if you redefine the whole hash (object), you must supply a "matcher"
+ * regex for the parser. The default matcher is:</p>
+ *
+ * <code>/()%(#?(%|[a-z]))/i</code>
+ *
+ * <p>which corresponds to the Perl syntax used by default.</p>
+ *
+ * <p>By customizing the matcher and format codes, nearly any strftime functionality is possible.</p>
+ */
+
+ jsDate.formats.perl = {
+ codes: {
+ //
+ // 2-part regex matcher for format codes
+ //
+ // first match must be the character before the code (to account for escaping)
+ // second match must be the format code character(s)
+ //
+ matcher: /()%(#?(%|[a-z]))/i,
+ // year
+ Y: 'FullYear',
+ y: 'ShortYear.2',
+ // month
+ m: 'MonthNumber.2',
+ '#m': 'MonthNumber',
+ B: 'MonthName',
+ b: 'AbbrMonthName',
+ // day
+ d: 'Date.2',
+ '#d': 'Date',
+ e: 'Date',
+ A: 'DayName',
+ a: 'AbbrDayName',
+ w: 'Day',
+ // hours
+ H: 'Hours.2',
+ '#H': 'Hours',
+ I: 'Hours12.2',
+ '#I': 'Hours12',
+ p: 'AMPM',
+ // minutes
+ M: 'Minutes.2',
+ '#M': 'Minutes',
+ // seconds
+ S: 'Seconds.2',
+ '#S': 'Seconds',
+ s: 'Unix',
+ // milliseconds
+ N: 'Milliseconds.3',
+ '#N': 'Milliseconds',
+ // timezone
+ O: 'TimezoneOffset',
+ Z: 'TimezoneName',
+ G: 'GmtOffset'
+ },
+
+ shortcuts: {
+ // date
+ F: '%Y-%m-%d',
+ // time
+ T: '%H:%M:%S',
+ X: '%H:%M:%S',
+ // local format date
+ x: '%m/%d/%y',
+ D: '%m/%d/%y',
+ // local format extended
+ '#c': '%a %b %e %H:%M:%S %Y',
+ // local format short
+ v: '%e-%b-%Y',
+ R: '%H:%M',
+ r: '%I:%M:%S %p',
+ // tab and newline
+ t: '\t',
+ n: '\n',
+ '%': '%'
+ }
+ };
+
+ /**
+ * PHP format codes and shortcuts for strftime.
+ *
+ * A hash (object) of codes where each code must be an array where the first member is
+ * the name of a Date.prototype or jsDate.prototype function to call
+ * and optionally a second member indicating the number to pass to addZeros()
+ *
+ * <p>The following format codes are defined:</p>
+ *
+ * <pre class="code">
+ * Code Result Description
+ * === Days ===
+ * %a Sun through Sat An abbreviated textual representation of the day
+ * %A Sunday - Saturday A full textual representation of the day
+ * %d 01 to 31 Two-digit day of the month (with leading zeros)
+ * %e 1 to 31 Day of the month, with a space preceding single digits.
+ * %j 001 to 366 Day of the year, 3 digits with leading zeros
+ * %u 1 - 7 (Mon - Sun) ISO-8601 numeric representation of the day of the week
+ * %w 0 - 6 (Sun - Sat) Numeric representation of the day of the week
+ *
+ * === Week ===
+ * %U 13 Full Week number, starting with the first Sunday as the first week
+ * %V 01 through 53 ISO-8601:1988 week number, starting with the first week of the year
+ * with at least 4 weekdays, with Monday being the start of the week
+ * %W 46 A numeric representation of the week of the year,
+ * starting with the first Monday as the first week
+ * === Month ===
+ * %b Jan through Dec Abbreviated month name, based on the locale
+ * %B January - December Full month name, based on the locale
+ * %h Jan through Dec Abbreviated month name, based on the locale (an alias of %b)
+ * %m 01 - 12 (Jan - Dec) Two digit representation of the month
+ *
+ * === Year ===
+ * %C 19 Two digit century (year/100, truncated to an integer)
+ * %y 09 for 2009 Two digit year
+ * %Y 2038 Four digit year
+ *
+ * === Time ===
+ * %H 00 through 23 Two digit representation of the hour in 24-hour format
+ * %I 01 through 12 Two digit representation of the hour in 12-hour format
+ * %l 1 through 12 Hour in 12-hour format, with a space preceeding single digits
+ * %M 00 through 59 Two digit representation of the minute
+ * %p AM/PM UPPER-CASE 'AM' or 'PM' based on the given time
+ * %P am/pm lower-case 'am' or 'pm' based on the given time
+ * %r 09:34:17 PM Same as %I:%M:%S %p
+ * %R 00:35 Same as %H:%M
+ * %S 00 through 59 Two digit representation of the second
+ * %T 21:34:17 Same as %H:%M:%S
+ * %X 03:59:16 Preferred time representation based on locale, without the date
+ * %z -0500 or EST Either the time zone offset from UTC or the abbreviation
+ * %Z -0500 or EST The time zone offset/abbreviation option NOT given by %z
+ *
+ * === Time and Date ===
+ * %D 02/05/09 Same as %m/%d/%y
+ * %F 2009-02-05 Same as %Y-%m-%d (commonly used in database datestamps)
+ * %s 305815200 Unix Epoch Time timestamp (same as the time() function)
+ * %x 02/05/09 Preferred date representation, without the time
+ *
+ * === Miscellaneous ===
+ * %n --- A newline character (\n)
+ * %t --- A Tab character (\t)
+ * %% --- A literal percentage character (%)
+ * </pre>
+ */
+
+ jsDate.formats.php = {
+ codes: {
+ //
+ // 2-part regex matcher for format codes
+ //
+ // first match must be the character before the code (to account for escaping)
+ // second match must be the format code character(s)
+ //
+ matcher: /()%((%|[a-z]))/i,
+ // day
+ a: 'AbbrDayName',
+ A: 'DayName',
+ d: 'Date.2',
+ e: 'Date',
+ j: 'DayOfYear.3',
+ u: 'DayOfWeek',
+ w: 'Day',
+ // week
+ U: 'FullWeekOfYear.2',
+ V: 'IsoWeek.2',
+ W: 'WeekOfYear.2',
+ // month
+ b: 'AbbrMonthName',
+ B: 'MonthName',
+ m: 'MonthNumber.2',
+ h: 'AbbrMonthName',
+ // year
+ C: 'Century.2',
+ y: 'ShortYear.2',
+ Y: 'FullYear',
+ // time
+ H: 'Hours.2',
+ I: 'Hours12.2',
+ l: 'Hours12',
+ p: 'AMPM',
+ P: 'AmPm',
+ M: 'Minutes.2',
+ S: 'Seconds.2',
+ s: 'Unix',
+ O: 'TimezoneOffset',
+ z: 'GmtOffset',
+ Z: 'TimezoneAbbr'
+ },
+
+ shortcuts: {
+ D: '%m/%d/%y',
+ F: '%Y-%m-%d',
+ T: '%H:%M:%S',
+ X: '%H:%M:%S',
+ x: '%m/%d/%y',
+ R: '%H:%M',
+ r: '%I:%M:%S %p',
+ t: '\t',
+ n: '\n',
+ '%': '%'
+ }
+ };
+ //
+ // Conceptually, the logic implemented here is similar to Ken Snyder's Date Instance Methods.
+ // I use his idea of a set of parsers which can be regular expressions or functions,
+ // iterating through those, and then seeing if Date.parse() will create a date.
+ // The parser expressions and functions are a little different and some bugs have been
+ // worked out. Also, a lot of "pre-parsing" is done to fix implementation
+ // variations of Date.parse() between browsers.
+ //
+ jsDate.createDate = function(date) {
+ // if passing in multiple arguments, try Date constructor
+ if (date == null) {
+ return new Date();
+ }
+ // If the passed value is already a date object, return it
+ if (date instanceof Date) {
+ return date;
+ }
+ // if (typeof date == 'number') return new Date(date * 1000);
+ // If the passed value is an integer, interpret it as a javascript timestamp
+ if (typeof date == 'number') {
+ return new Date(date);
+ }
+
+ // Before passing strings into Date.parse(), have to normalize them for certain conditions.
+ // If strings are not formatted staccording to the EcmaScript spec, results from Date parse will be implementation dependent.
+ //
+ // For example:
+ // * FF and Opera assume 2 digit dates are pre y2k, Chome assumes <50 is pre y2k, 50+ is 21st century.
+ // * Chrome will correctly parse '1984-1-25' into localtime, FF and Opera will not parse.
+ // * Both FF, Chrome and Opera will parse '1984/1/25' into localtime.
+
+ // remove leading and trailing spaces
+ var parsable = String(date).replace(/^\s*(.+)\s*$/g, '$1');
+
+ // replace dahses (-) with slashes (/) in dates like n[nnn]/n[n]/n[nnn]
+ parsable = parsable.replace(/^([0-9]{1,4})-([0-9]{1,2})-([0-9]{1,4})/, "$1/$2/$3");
+
+ /////////
+ // Need to check for '15-Dec-09' also.
+ // FF will not parse, but Chrome will.
+ // Chrome will set date to 2009 as well.
+ /////////
+
+ // first check for 'dd-mmm-yyyy' or 'dd/mmm/yyyy' like '15-Dec-2010'
+ parsable = parsable.replace(/^(3[01]|[0-2]?\d)[-\/]([a-z]{3,})[-\/](\d{4})/i, "$1 $2 $3");
+
+ // Now check for 'dd-mmm-yy' or 'dd/mmm/yy' and normalize years to default century.
+ var match = parsable.match(/^(3[01]|[0-2]?\d)[-\/]([a-z]{3,})[-\/](\d{2})\D*/i);
+ if (match && match.length > 3) {
+ var m3 = parseFloat(match[3]);
+ var ny = jsDate.config.defaultCentury + m3;
+ ny = String(ny);
+
+ // now replace 2 digit year with 4 digit year
+ parsable = parsable.replace(/^(3[01]|[0-2]?\d)[-\/]([a-z]{3,})[-\/](\d{2})\D*/i, match[1] +' '+ match[2] +' '+ ny);
+
+ }
+
+ // Check for '1/19/70 8:14PM'
+ // where starts with mm/dd/yy or yy/mm/dd and have something after
+ // Check if 1st postiion is greater than 31, assume it is year.
+ // Assme all 2 digit years are 1900's.
+ // Finally, change them into US style mm/dd/yyyy representations.
+ match = parsable.match(/^([0-9]{1,2})[-\/]([0-9]{1,2})[-\/]([0-9]{1,2})[^0-9]/);
+
+ function h1(parsable, match) {
+ var m1 = parseFloat(match[1]);
+ var m2 = parseFloat(match[2]);
+ var m3 = parseFloat(match[3]);
+ var cent = jsDate.config.defaultCentury;
+ var ny, nd, nm, str;
+
+ if (m1 > 31) { // first number is a year
+ nd = m3;
+ nm = m2;
+ ny = cent + m1;
+ }
+
+ else { // last number is the year
+ nd = m2;
+ nm = m1;
+ ny = cent + m3;
+ }
+
+ str = nm+'/'+nd+'/'+ny;
+
+ // now replace 2 digit year with 4 digit year
+ return parsable.replace(/^([0-9]{1,2})[-\/]([0-9]{1,2})[-\/]([0-9]{1,2})/, str);
+
+ }
+
+ if (match && match.length > 3) {
+ parsable = h1(parsable, match);
+ }
+
+ // Now check for '1/19/70' with nothing after and do as above
+ var match = parsable.match(/^([0-9]{1,2})[-\/]([0-9]{1,2})[-\/]([0-9]{1,2})$/);
+
+ if (match && match.length > 3) {
+ parsable = h1(parsable, match);
+ }
+
+
+ var i = 0;
+ var length = jsDate.matchers.length;
+ var pattern,
+ ms,
+ current = parsable,
+ obj;
+ while (i < length) {
+ ms = Date.parse(current);
+ if (!isNaN(ms)) {
+ return new Date(ms);
+ }
+ pattern = jsDate.matchers[i];
+ if (typeof pattern == 'function') {
+ obj = pattern.call(jsDate, current);
+ if (obj instanceof Date) {
+ return obj;
+ }
+ } else {
+ current = parsable.replace(pattern[0], pattern[1]);
+ }
+ i++;
+ }
+ return NaN;
+ };
+
+
+ /**
+ * @static
+ * Handy static utility function to return the number of days in a given month.
+ * @param {Integer} year Year
+ * @param {Integer} month Month (1-12)
+ * @returns {Integer} Number of days in the month.
+ */
+ //
+ // handy utility method Borrowed right from Ken Snyder's Date Instance Mehtods.
+ //
+ jsDate.daysInMonth = function(year, month) {
+ if (month == 2) {
+ return new Date(year, 1, 29).getDate() == 29 ? 29 : 28;
+ }
+ return [undefined,31,undefined,31,30,31,30,31,31,30,31,30,31][month];
+ };
+
+
+ //
+ // An Array of regular expressions or functions that will attempt to match the date string.
+ // Functions are called with scope of a jsDate instance.
+ //
+ jsDate.matchers = [
+ // convert dd.mmm.yyyy to mm/dd/yyyy (world date to US date).
+ [/(3[01]|[0-2]\d)\s*\.\s*(1[0-2]|0\d)\s*\.\s*([1-9]\d{3})/, '$2/$1/$3'],
+ // convert yyyy-mm-dd to mm/dd/yyyy (ISO date to US date).
+ [/([1-9]\d{3})\s*-\s*(1[0-2]|0\d)\s*-\s*(3[01]|[0-2]\d)/, '$2/$3/$1'],
+ // Handle 12 hour or 24 hour time with milliseconds am/pm and optional date part.
+ function(str) {
+ var match = str.match(/^(?:(.+)\s+)?([012]?\d)(?:\s*\:\s*(\d\d))?(?:\s*\:\s*(\d\d(\.\d*)?))?\s*(am|pm)?\s*$/i);
+ // opt. date hour opt. minute opt. second opt. msec opt. am or pm
+ if (match) {
+ if (match[1]) {
+ var d = this.createDate(match[1]);
+ if (isNaN(d)) {
+ return;
+ }
+ } else {
+ var d = new Date();
+ d.setMilliseconds(0);
+ }
+ var hour = parseFloat(match[2]);
+ if (match[6]) {
+ hour = match[6].toLowerCase() == 'am' ? (hour == 12 ? 0 : hour) : (hour == 12 ? 12 : hour + 12);
+ }
+ d.setHours(hour, parseInt(match[3] || 0, 10), parseInt(match[4] || 0, 10), ((parseFloat(match[5] || 0)) || 0)*1000);
+ return d;
+ }
+ else {
+ return str;
+ }
+ },
+ // Handle ISO timestamp with time zone.
+ function(str) {
+ var match = str.match(/^(?:(.+))[T|\s+]([012]\d)(?:\:(\d\d))(?:\:(\d\d))(?:\.\d+)([\+\-]\d\d\:\d\d)$/i);
+ if (match) {
+ if (match[1]) {
+ var d = this.createDate(match[1]);
+ if (isNaN(d)) {
+ return;
+ }
+ } else {
+ var d = new Date();
+ d.setMilliseconds(0);
+ }
+ var hour = parseFloat(match[2]);
+ d.setHours(hour, parseInt(match[3], 10), parseInt(match[4], 10), parseFloat(match[5])*1000);
+ return d;
+ }
+ else {
+ return str;
+ }
+ },
+ // Try to match ambiguous strings like 12/8/22.
+ // Use FF date assumption that 2 digit years are 20th century (i.e. 1900's).
+ // This may be redundant with pre processing of date already performed.
+ function(str) {
+ var match = str.match(/^([0-3]?\d)\s*[-\/.\s]{1}\s*([a-zA-Z]{3,9})\s*[-\/.\s]{1}\s*([0-3]?\d)$/);
+ if (match) {
+ var d = new Date();
+ var cent = jsDate.config.defaultCentury;
+ var m1 = parseFloat(match[1]);
+ var m3 = parseFloat(match[3]);
+ var ny, nd, nm;
+ if (m1 > 31) { // first number is a year
+ nd = m3;
+ ny = cent + m1;
+ }
+
+ else { // last number is the year
+ nd = m1;
+ ny = cent + m3;
+ }
+
+ var nm = inArray(match[2], jsDate.regional[jsDate.regional.getLocale()]["monthNamesShort"]);
+
+ if (nm == -1) {
+ nm = inArray(match[2], jsDate.regional[jsDate.regional.getLocale()]["monthNames"]);
+ }
+
+ d.setFullYear(ny, nm, nd);
+ d.setHours(0,0,0,0);
+ return d;
+ }
+
+ else {
+ return str;
+ }
+ }
+ ];
+
+ //
+ // I think John Reisig published this method on his blog, ejohn.
+ //
+ function inArray( elem, array ) {
+ if ( array.indexOf ) {
+ return array.indexOf( elem );
+ }
+
+ for ( var i = 0, length = array.length; i < length; i++ ) {
+ if ( array[ i ] === elem ) {
+ return i;
+ }
+ }
+
+ return -1;
+ }
+
+ //
+ // Thanks to Kangax, Christian Sciberras and Stack Overflow for this method.
+ //
+ function get_type(thing){
+ if(thing===null) return "[object Null]"; // special case
+ return Object.prototype.toString.call(thing);
+ }
+
+ $.jsDate = jsDate;
+
+
+ /**
+ * JavaScript printf/sprintf functions.
+ *
+ * This code has been adapted from the publicly available sprintf methods
+ * by Ash Searle. His original header follows:
+ *
+ * This code is unrestricted: you are free to use it however you like.
+ *
+ * The functions should work as expected, performing left or right alignment,
+ * truncating strings, outputting numbers with a required precision etc.
+ *
+ * For complex cases, these functions follow the Perl implementations of
+ * (s)printf, allowing arguments to be passed out-of-order, and to set the
+ * precision or length of the output based on arguments instead of fixed
+ * numbers.
+ *
+ * See http://perldoc.perl.org/functions/sprintf.html for more information.
+ *
+ * Implemented:
+ * - zero and space-padding
+ * - right and left-alignment,
+ * - base X prefix (binary, octal and hex)
+ * - positive number prefix
+ * - (minimum) width
+ * - precision / truncation / maximum width
+ * - out of order arguments
+ *
+ * Not implemented (yet):
+ * - vector flag
+ * - size (bytes, words, long-words etc.)
+ *
+ * Will not implement:
+ * - %n or %p (no pass-by-reference in JavaScript)
+ *
+ * @version 2007.04.27
+ * @author Ash Searle
+ *
+ * You can see the original work and comments on his blog:
+ * http://hexmen.com/blog/2007/03/printf-sprintf/
+ * http://hexmen.com/js/sprintf.js
+ */
+
+ /**
+ * @Modifications 2009.05.26
+ * @author Chris Leonello
+ *
+ * Added %p %P specifier
+ * Acts like %g or %G but will not add more significant digits to the output than present in the input.
+ * Example:
+ * Format: '%.3p', Input: 0.012, Output: 0.012
+ * Format: '%.3g', Input: 0.012, Output: 0.0120
+ * Format: '%.4p', Input: 12.0, Output: 12.0
+ * Format: '%.4g', Input: 12.0, Output: 12.00
+ * Format: '%.4p', Input: 4.321e-5, Output: 4.321e-5
+ * Format: '%.4g', Input: 4.321e-5, Output: 4.3210e-5
+ *
+ * Example:
+ * >>> $.jqplot.sprintf('%.2f, %d', 23.3452, 43.23)
+ * "23.35, 43"
+ * >>> $.jqplot.sprintf("no value: %n, decimal with thousands separator: %'d", 23.3452, 433524)
+ * "no value: , decimal with thousands separator: 433,524"
+ */
+ $.jqplot.sprintf = function() {
+ function pad(str, len, chr, leftJustify) {
+ var padding = (str.length >= len) ? '' : Array(1 + len - str.length >>> 0).join(chr);
+ return leftJustify ? str + padding : padding + str;
+
+ }
+
+ function thousand_separate(value) {
+ var value_str = new String(value);
+ for (var i=10; i>0; i--) {
+ if (value_str == (value_str = value_str.replace(/^(\d+)(\d{3})/, "$1"+$.jqplot.sprintf.thousandsSeparator+"$2"))) break;
+ }
+ return value_str;
+ }
+
+ function justify(value, prefix, leftJustify, minWidth, zeroPad, htmlSpace) {
+ var diff = minWidth - value.length;
+ if (diff > 0) {
+ var spchar = ' ';
+ if (htmlSpace) { spchar = '&nbsp;'; }
+ if (leftJustify || !zeroPad) {
+ value = pad(value, minWidth, spchar, leftJustify);
+ } else {
+ value = value.slice(0, prefix.length) + pad('', diff, '0', true) + value.slice(prefix.length);
+ }
+ }
+ return value;
+ }
+
+ function formatBaseX(value, base, prefix, leftJustify, minWidth, precision, zeroPad, htmlSpace) {
+ // Note: casts negative numbers to positive ones
+ var number = value >>> 0;
+ prefix = prefix && number && {'2': '0b', '8': '0', '16': '0x'}[base] || '';
+ value = prefix + pad(number.toString(base), precision || 0, '0', false);
+ return justify(value, prefix, leftJustify, minWidth, zeroPad, htmlSpace);
+ }
+
+ function formatString(value, leftJustify, minWidth, precision, zeroPad, htmlSpace) {
+ if (precision != null) {
+ value = value.slice(0, precision);
+ }
+ return justify(value, '', leftJustify, minWidth, zeroPad, htmlSpace);
+ }
+
+ var a = arguments, i = 0, format = a[i++];
+
+ return format.replace($.jqplot.sprintf.regex, function(substring, valueIndex, flags, minWidth, _, precision, type) {
+ if (substring == '%%') { return '%'; }
+
+ // parse flags
+ var leftJustify = false, positivePrefix = '', zeroPad = false, prefixBaseX = false, htmlSpace = false, thousandSeparation = false;
+ for (var j = 0; flags && j < flags.length; j++) switch (flags.charAt(j)) {
+ case ' ': positivePrefix = ' '; break;
+ case '+': positivePrefix = '+'; break;
+ case '-': leftJustify = true; break;
+ case '0': zeroPad = true; break;
+ case '#': prefixBaseX = true; break;
+ case '&': htmlSpace = true; break;
+ case '\'': thousandSeparation = true; break;
+ }
+
+ // parameters may be null, undefined, empty-string or real valued
+ // we want to ignore null, undefined and empty-string values
+
+ if (!minWidth) {
+ minWidth = 0;
+ }
+ else if (minWidth == '*') {
+ minWidth = +a[i++];
+ }
+ else if (minWidth.charAt(0) == '*') {
+ minWidth = +a[minWidth.slice(1, -1)];
+ }
+ else {
+ minWidth = +minWidth;
+ }
+
+ // Note: undocumented perl feature:
+ if (minWidth < 0) {
+ minWidth = -minWidth;
+ leftJustify = true;
+ }
+
+ if (!isFinite(minWidth)) {
+ throw new Error('$.jqplot.sprintf: (minimum-)width must be finite');
+ }
+
+ if (!precision) {
+ precision = 'fFeE'.indexOf(type) > -1 ? 6 : (type == 'd') ? 0 : void(0);
+ }
+ else if (precision == '*') {
+ precision = +a[i++];
+ }
+ else if (precision.charAt(0) == '*') {
+ precision = +a[precision.slice(1, -1)];
+ }
+ else {
+ precision = +precision;
+ }
+
+ // grab value using valueIndex if required?
+ var value = valueIndex ? a[valueIndex.slice(0, -1)] : a[i++];
+
+ switch (type) {
+ case 's': {
+ if (value == null) {
+ return '';
+ }
+ return formatString(String(value), leftJustify, minWidth, precision, zeroPad, htmlSpace);
+ }
+ case 'c': return formatString(String.fromCharCode(+value), leftJustify, minWidth, precision, zeroPad, htmlSpace);
+ case 'b': return formatBaseX(value, 2, prefixBaseX, leftJustify, minWidth, precision, zeroPad,htmlSpace);
+ case 'o': return formatBaseX(value, 8, prefixBaseX, leftJustify, minWidth, precision, zeroPad, htmlSpace);
+ case 'x': return formatBaseX(value, 16, prefixBaseX, leftJustify, minWidth, precision, zeroPad, htmlSpace);
+ case 'X': return formatBaseX(value, 16, prefixBaseX, leftJustify, minWidth, precision, zeroPad, htmlSpace).toUpperCase();
+ case 'u': return formatBaseX(value, 10, prefixBaseX, leftJustify, minWidth, precision, zeroPad, htmlSpace);
+ case 'i': {
+ var number = parseInt(+value, 10);
+ if (isNaN(number)) {
+ return '';
+ }
+ var prefix = number < 0 ? '-' : positivePrefix;
+ var number_str = thousandSeparation ? thousand_separate(String(Math.abs(number))): String(Math.abs(number));
+ value = prefix + pad(number_str, precision, '0', false);
+ //value = prefix + pad(String(Math.abs(number)), precision, '0', false);
+ return justify(value, prefix, leftJustify, minWidth, zeroPad, htmlSpace);
+ }
+ case 'd': {
+ var number = Math.round(+value);
+ if (isNaN(number)) {
+ return '';
+ }
+ var prefix = number < 0 ? '-' : positivePrefix;
+ var number_str = thousandSeparation ? thousand_separate(String(Math.abs(number))): String(Math.abs(number));
+ value = prefix + pad(number_str, precision, '0', false);
+ return justify(value, prefix, leftJustify, minWidth, zeroPad, htmlSpace);
+ }
+ case 'e':
+ case 'E':
+ case 'f':
+ case 'F':
+ case 'g':
+ case 'G':
+ {
+ var number = +value;
+ if (isNaN(number)) {
+ return '';
+ }
+ var prefix = number < 0 ? '-' : positivePrefix;
+ var method = ['toExponential', 'toFixed', 'toPrecision']['efg'.indexOf(type.toLowerCase())];
+ var textTransform = ['toString', 'toUpperCase']['eEfFgG'.indexOf(type) % 2];
+ var number_str = Math.abs(number)[method](precision);
+ number_str = thousandSeparation ? thousand_separate(number_str): number_str;
+ value = prefix + number_str;
+ var justified = justify(value, prefix, leftJustify, minWidth, zeroPad, htmlSpace)[textTransform]();
+
+ if ($.jqplot.sprintf.decimalMark !== '.' && $.jqplot.sprintf.decimalMark !== $.jqplot.sprintf.thousandsSeparator) {
+ return justified.replace(/\./, $.jqplot.sprintf.decimalMark);
+ } else {
+ return justified;
+ }
+ }
+ case 'p':
+ case 'P':
+ {
+ // make sure number is a number
+ var number = +value;
+ if (isNaN(number)) {
+ return '';
+ }
+ var prefix = number < 0 ? '-' : positivePrefix;
+
+ var parts = String(Number(Math.abs(number)).toExponential()).split(/e|E/);
+ var sd = (parts[0].indexOf('.') != -1) ? parts[0].length - 1 : parts[0].length;
+ var zeros = (parts[1] < 0) ? -parts[1] - 1 : 0;
+
+ if (Math.abs(number) < 1) {
+ if (sd + zeros <= precision) {
+ value = prefix + Math.abs(number).toPrecision(sd);
+ }
+ else {
+ if (sd <= precision - 1) {
+ value = prefix + Math.abs(number).toExponential(sd-1);
+ }
+ else {
+ value = prefix + Math.abs(number).toExponential(precision-1);
+ }
+ }
+ }
+ else {
+ var prec = (sd <= precision) ? sd : precision;
+ value = prefix + Math.abs(number).toPrecision(prec);
+ }
+ var textTransform = ['toString', 'toUpperCase']['pP'.indexOf(type) % 2];
+ return justify(value, prefix, leftJustify, minWidth, zeroPad, htmlSpace)[textTransform]();
+ }
+ case 'n': return '';
+ default: return substring;
+ }
+ });
+ };
+
+ $.jqplot.sprintf.thousandsSeparator = ',';
+ // Specifies the decimal mark for floating point values. By default a period '.'
+ // is used. If you change this value to for example a comma be sure to also
+ // change the thousands separator or else this won't work since a simple String
+ // replace is used (replacing all periods with the mark specified here).
+ $.jqplot.sprintf.decimalMark = '.';
+
+ $.jqplot.sprintf.regex = /%%|%(\d+\$)?([-+#0&\' ]*)(\*\d+\$|\*|\d+)?(\.(\*\d+\$|\*|\d+))?([nAscboxXuidfegpEGP])/g;
+
+ $.jqplot.getSignificantFigures = function(number) {
+ var parts = String(Number(Math.abs(number)).toExponential()).split(/e|E/);
+ // total significant digits
+ var sd = (parts[0].indexOf('.') != -1) ? parts[0].length - 1 : parts[0].length;
+ var zeros = (parts[1] < 0) ? -parts[1] - 1 : 0;
+ // exponent
+ var expn = parseInt(parts[1], 10);
+ // digits to the left of the decimal place
+ var dleft = (expn + 1 > 0) ? expn + 1 : 0;
+ // digits to the right of the decimal place
+ var dright = (sd <= dleft) ? 0 : sd - expn - 1;
+ return {significantDigits: sd, digitsLeft: dleft, digitsRight: dright, zeros: zeros, exponent: expn} ;
+ };
+
+ $.jqplot.getPrecision = function(number) {
+ return $.jqplot.getSignificantFigures(number).digitsRight;
+ };
+
+})(jQuery);
+
+
+ var backCompat = $.uiBackCompat !== false;
+
+ $.jqplot.effects = {
+ effect: {}
+ };
+
+ // prefix used for storing data on .data()
+ var dataSpace = "jqplot.storage.";
+
+ /******************************************************************************/
+ /*********************************** EFFECTS **********************************/
+ /******************************************************************************/
+
+ $.extend( $.jqplot.effects, {
+ version: "1.9pre",
+
+ // Saves a set of properties in a data storage
+ save: function( element, set ) {
+ for( var i=0; i < set.length; i++ ) {
+ if ( set[ i ] !== null ) {
+ element.data( dataSpace + set[ i ], element[ 0 ].style[ set[ i ] ] );
+ }
+ }
+ },
+
+ // Restores a set of previously saved properties from a data storage
+ restore: function( element, set ) {
+ for( var i=0; i < set.length; i++ ) {
+ if ( set[ i ] !== null ) {
+ element.css( set[ i ], element.data( dataSpace + set[ i ] ) );
+ }
+ }
+ },
+
+ setMode: function( el, mode ) {
+ if (mode === "toggle") {
+ mode = el.is( ":hidden" ) ? "show" : "hide";
+ }
+ return mode;
+ },
+
+ // Wraps the element around a wrapper that copies position properties
+ createWrapper: function( element ) {
+
+ // if the element is already wrapped, return it
+ if ( element.parent().is( ".ui-effects-wrapper" )) {
+ return element.parent();
+ }
+
+ // wrap the element
+ var props = {
+ width: element.outerWidth(true),
+ height: element.outerHeight(true),
+ "float": element.css( "float" )
+ },
+ wrapper = $( "<div></div>" )
+ .addClass( "ui-effects-wrapper" )
+ .css({
+ fontSize: "100%",
+ background: "transparent",
+ border: "none",
+ margin: 0,
+ padding: 0
+ }),
+ // Store the size in case width/height are defined in % - Fixes #5245
+ size = {
+ width: element.width(),
+ height: element.height()
+ },
+ active = document.activeElement;
+
+ element.wrap( wrapper );
+
+ // Fixes #7595 - Elements lose focus when wrapped.
+ if ( element[ 0 ] === active || $.contains( element[ 0 ], active ) ) {
+ $( active ).focus();
+ }
+
+ wrapper = element.parent(); //Hotfix for jQuery 1.4 since some change in wrap() seems to actually loose the reference to the wrapped element
+
+ // transfer positioning properties to the wrapper
+ if ( element.css( "position" ) === "static" ) {
+ wrapper.css({ position: "relative" });
+ element.css({ position: "relative" });
+ } else {
+ $.extend( props, {
+ position: element.css( "position" ),
+ zIndex: element.css( "z-index" )
+ });
+ $.each([ "top", "left", "bottom", "right" ], function(i, pos) {
+ props[ pos ] = element.css( pos );
+ if ( isNaN( parseInt( props[ pos ], 10 ) ) ) {
+ props[ pos ] = "auto";
+ }
+ });
+ element.css({
+ position: "relative",
+ top: 0,
+ left: 0,
+ right: "auto",
+ bottom: "auto"
+ });
+ }
+ element.css(size);
+
+ return wrapper.css( props ).show();
+ },
+
+ removeWrapper: function( element ) {
+ var active = document.activeElement;
+
+ if ( element.parent().is( ".ui-effects-wrapper" ) ) {
+ element.parent().replaceWith( element );
+
+ // Fixes #7595 - Elements lose focus when wrapped.
+ if ( element[ 0 ] === active || $.contains( element[ 0 ], active ) ) {
+ $( active ).focus();
+ }
+ }
+
+
+ return element;
+ }
+ });
+
+ // return an effect options object for the given parameters:
+ function _normalizeArguments( effect, options, speed, callback ) {
+
+ // short path for passing an effect options object:
+ if ( $.isPlainObject( effect ) ) {
+ return effect;
+ }
+
+ // convert to an object
+ effect = { effect: effect };
+
+ // catch (effect)
+ if ( options === undefined ) {
+ options = {};
+ }
+
+ // catch (effect, callback)
+ if ( $.isFunction( options ) ) {
+ callback = options;
+ speed = null;
+ options = {};
+ }
+
+ // catch (effect, speed, ?)
+ if ( $.type( options ) === "number" || $.fx.speeds[ options ]) {
+ callback = speed;
+ speed = options;
+ options = {};
+ }
+
+ // catch (effect, options, callback)
+ if ( $.isFunction( speed ) ) {
+ callback = speed;
+ speed = null;
+ }
+
+ // add options to effect
+ if ( options ) {
+ $.extend( effect, options );
+ }
+
+ speed = speed || options.duration;
+ effect.duration = $.fx.off ? 0 : typeof speed === "number"
+ ? speed : speed in $.fx.speeds ? $.fx.speeds[ speed ] : $.fx.speeds._default;
+
+ effect.complete = callback || options.complete;
+
+ return effect;
+ }
+
+ function standardSpeed( speed ) {
+ // valid standard speeds
+ if ( !speed || typeof speed === "number" || $.fx.speeds[ speed ] ) {
+ return true;
+ }
+
+ // invalid strings - treat as "normal" speed
+ if ( typeof speed === "string" && !$.jqplot.effects.effect[ speed ] ) {
+ // TODO: remove in 2.0 (#7115)
+ if ( backCompat && $.jqplot.effects[ speed ] ) {
+ return false;
+ }
+ return true;
+ }
+
+ return false;
+ }
+
+ $.fn.extend({
+ jqplotEffect: function( effect, options, speed, callback ) {
+ var args = _normalizeArguments.apply( this, arguments ),
+ mode = args.mode,
+ queue = args.queue,
+ effectMethod = $.jqplot.effects.effect[ args.effect ],
+
+ // DEPRECATED: remove in 2.0 (#7115)
+ oldEffectMethod = !effectMethod && backCompat && $.jqplot.effects[ args.effect ];
+
+ if ( $.fx.off || !( effectMethod || oldEffectMethod ) ) {
+ // delegate to the original method (e.g., .show()) if possible
+ if ( mode ) {
+ return this[ mode ]( args.duration, args.complete );
+ } else {
+ return this.each( function() {
+ if ( args.complete ) {
+ args.complete.call( this );
+ }
+ });
+ }
+ }
+
+ function run( next ) {
+ var elem = $( this ),
+ complete = args.complete,
+ mode = args.mode;
+
+ function done() {
+ if ( $.isFunction( complete ) ) {
+ complete.call( elem[0] );
+ }
+ if ( $.isFunction( next ) ) {
+ next();
+ }
+ }
+
+ // if the element is hiddden and mode is hide,
+ // or element is visible and mode is show
+ if ( elem.is( ":hidden" ) ? mode === "hide" : mode === "show" ) {
+ done();
+ } else {
+ effectMethod.call( elem[0], args, done );
+ }
+ }
+
+ // TODO: remove this check in 2.0, effectMethod will always be true
+ if ( effectMethod ) {
+ return queue === false ? this.each( run ) : this.queue( queue || "fx", run );
+ } else {
+ // DEPRECATED: remove in 2.0 (#7115)
+ return oldEffectMethod.call(this, {
+ options: args,
+ duration: args.duration,
+ callback: args.complete,
+ mode: args.mode
+ });
+ }
+ }
+ });
+
+
+
+
+ var rvertical = /up|down|vertical/,
+ rpositivemotion = /up|left|vertical|horizontal/;
+
+ $.jqplot.effects.effect.blind = function( o, done ) {
+ // Create element
+ var el = $( this ),
+ props = [ "position", "top", "bottom", "left", "right", "height", "width" ],
+ mode = $.jqplot.effects.setMode( el, o.mode || "hide" ),
+ direction = o.direction || "up",
+ vertical = rvertical.test( direction ),
+ ref = vertical ? "height" : "width",
+ ref2 = vertical ? "top" : "left",
+ motion = rpositivemotion.test( direction ),
+ animation = {},
+ show = mode === "show",
+ wrapper, distance, top;
+
+ // // if already wrapped, the wrapper's properties are my property. #6245
+ if ( el.parent().is( ".ui-effects-wrapper" ) ) {
+ $.jqplot.effects.save( el.parent(), props );
+ } else {
+ $.jqplot.effects.save( el, props );
+ }
+ el.show();
+ top = parseInt(el.css('top'), 10);
+ wrapper = $.jqplot.effects.createWrapper( el ).css({
+ overflow: "hidden"
+ });
+
+ distance = vertical ? wrapper[ ref ]() + top : wrapper[ ref ]();
+
+ animation[ ref ] = show ? String(distance) : '0';
+ if ( !motion ) {
+ el
+ .css( vertical ? "bottom" : "right", 0 )
+ .css( vertical ? "top" : "left", "" )
+ .css({ position: "absolute" });
+ animation[ ref2 ] = show ? '0' : String(distance);
+ }
+
+ // // start at 0 if we are showing
+ if ( show ) {
+ wrapper.css( ref, 0 );
+ if ( ! motion ) {
+ wrapper.css( ref2, distance );
+ }
+ }
+
+ // // Animate
+ wrapper.animate( animation, {
+ duration: o.duration,
+ easing: o.easing,
+ queue: false,
+ complete: function() {
+ if ( mode === "hide" ) {
+ el.hide();
+ }
+ $.jqplot.effects.restore( el, props );
+ $.jqplot.effects.removeWrapper( el );
+ done();
+ }
+ });
+
+ };
+
+
diff --git a/js/jqplot/plugins/jqplot.barRenderer.js b/js/jqplot/plugins/jqplot.barRenderer.js
new file mode 100644
index 0000000000..c1be235d79
--- /dev/null
+++ b/js/jqplot/plugins/jqplot.barRenderer.js
@@ -0,0 +1,797 @@
+/**
+ * jqPlot
+ * Pure JavaScript plotting plugin using jQuery
+ *
+ * Version: 1.0.4
+ * Revision: 1121
+ *
+ * Copyright (c) 2009-2012 Chris Leonello
+ * jqPlot is currently available for use in all personal or commercial projects
+ * under both the MIT (http://www.opensource.org/licenses/mit-license.php) and GPL
+ * version 2.0 (http://www.gnu.org/licenses/gpl-2.0.html) licenses. This means that you can
+ * choose the license that best suits your project and use it accordingly.
+ *
+ * Although not required, the author would appreciate an email letting him
+ * know of any substantial use of jqPlot. You can reach the author at:
+ * chris at jqplot dot com or see http://www.jqplot.com/info.php .
+ *
+ * If you are feeling kind and generous, consider supporting the project by
+ * making a donation at: http://www.jqplot.com/donate.php .
+ *
+ * sprintf functions contained in jqplot.sprintf.js by Ash Searle:
+ *
+ * version 2007.04.27
+ * author Ash Searle
+ * http://hexmen.com/blog/2007/03/printf-sprintf/
+ * http://hexmen.com/js/sprintf.js
+ * The author (Ash Searle) has placed this code in the public domain:
+ * "This code is unrestricted: you are free to use it however you like."
+ *
+ */
+(function($) {
+
+ // Class: $.jqplot.BarRenderer
+ // A plugin renderer for jqPlot to draw a bar plot.
+ // Draws series as a line.
+
+ $.jqplot.BarRenderer = function(){
+ $.jqplot.LineRenderer.call(this);
+ };
+
+ $.jqplot.BarRenderer.prototype = new $.jqplot.LineRenderer();
+ $.jqplot.BarRenderer.prototype.constructor = $.jqplot.BarRenderer;
+
+ // called with scope of series.
+ $.jqplot.BarRenderer.prototype.init = function(options, plot) {
+ // Group: Properties
+ //
+ // prop: barPadding
+ // Number of pixels between adjacent bars at the same axis value.
+ this.barPadding = 8;
+ // prop: barMargin
+ // Number of pixels between groups of bars at adjacent axis values.
+ this.barMargin = 10;
+ // prop: barDirection
+ // 'vertical' = up and down bars, 'horizontal' = side to side bars
+ this.barDirection = 'vertical';
+ // prop: barWidth
+ // Width of the bar in pixels (auto by devaul). null = calculated automatically.
+ this.barWidth = null;
+ // prop: shadowOffset
+ // offset of the shadow from the slice and offset of
+ // each succesive stroke of the shadow from the last.
+ this.shadowOffset = 2;
+ // prop: shadowDepth
+ // number of strokes to apply to the shadow,
+ // each stroke offset shadowOffset from the last.
+ this.shadowDepth = 5;
+ // prop: shadowAlpha
+ // transparency of the shadow (0 = transparent, 1 = opaque)
+ this.shadowAlpha = 0.08;
+ // prop: waterfall
+ // true to enable waterfall plot.
+ this.waterfall = false;
+ // prop: groups
+ // group bars into this many groups
+ this.groups = 1;
+ // prop: varyBarColor
+ // true to color each bar of a series separately rather than
+ // have every bar of a given series the same color.
+ // If used for non-stacked multiple series bar plots, user should
+ // specify a separate 'seriesColors' array for each series.
+ // Otherwise, each series will set their bars to the same color array.
+ // This option has no Effect for stacked bar charts and is disabled.
+ this.varyBarColor = false;
+ // prop: highlightMouseOver
+ // True to highlight slice when moused over.
+ // This must be false to enable highlightMouseDown to highlight when clicking on a slice.
+ this.highlightMouseOver = true;
+ // prop: highlightMouseDown
+ // True to highlight when a mouse button is pressed over a slice.
+ // This will be disabled if highlightMouseOver is true.
+ this.highlightMouseDown = false;
+ // prop: highlightColors
+ // an array of colors to use when highlighting a bar.
+ this.highlightColors = [];
+ // prop: transposedData
+ // NOT IMPLEMENTED YET. True if this is a horizontal bar plot and
+ // x and y values are "transposed". Tranposed, or "swapped", data is
+ // required prior to rev. 894 builds of jqPlot with horizontal bars.
+ // Allows backward compatability of bar renderer horizontal bars with
+ // old style data sets.
+ this.transposedData = true;
+ this.renderer.animation = {
+ show: false,
+ direction: 'down',
+ speed: 3000,
+ _supported: true
+ };
+ this._type = 'bar';
+
+ // if user has passed in highlightMouseDown option and not set highlightMouseOver, disable highlightMouseOver
+ if (options.highlightMouseDown && options.highlightMouseOver == null) {
+ options.highlightMouseOver = false;
+ }
+
+ //////
+ // This is probably wrong here.
+ // After going back and forth on wether renderer should be the thing
+ // or extend the thing, it seems that it it best if it is a property
+ // on the thing. This should be something that is commonized
+ // among series renderers in the future.
+ //////
+ $.extend(true, this, options);
+
+ // really should probably do this
+ $.extend(true, this.renderer, options);
+ // fill is still needed to properly draw the legend.
+ // bars have to be filled.
+ this.fill = true;
+
+ // if horizontal bar and animating, reset the default direction
+ if (this.barDirection === 'horizontal' && this.rendererOptions.animation && this.rendererOptions.animation.direction == null) {
+ this.renderer.animation.direction = 'left';
+ }
+
+ if (this.waterfall) {
+ this.fillToZero = false;
+ this.disableStack = true;
+ }
+
+ if (this.barDirection == 'vertical' ) {
+ this._primaryAxis = '_xaxis';
+ this._stackAxis = 'y';
+ this.fillAxis = 'y';
+ }
+ else {
+ this._primaryAxis = '_yaxis';
+ this._stackAxis = 'x';
+ this.fillAxis = 'x';
+ }
+ // index of the currenty highlighted point, if any
+ this._highlightedPoint = null;
+ // total number of values for all bar series, total number of bar series, and position of this series
+ this._plotSeriesInfo = null;
+ // Array of actual data colors used for each data point.
+ this._dataColors = [];
+ this._barPoints = [];
+
+ // set the shape renderer options
+ var opts = {lineJoin:'miter', lineCap:'round', fill:true, isarc:false, strokeStyle:this.color, fillStyle:this.color, closePath:this.fill};
+ this.renderer.shapeRenderer.init(opts);
+ // set the shadow renderer options
+ var sopts = {lineJoin:'miter', lineCap:'round', fill:true, isarc:false, angle:this.shadowAngle, offset:this.shadowOffset, alpha:this.shadowAlpha, depth:this.shadowDepth, closePath:this.fill};
+ this.renderer.shadowRenderer.init(sopts);
+
+ plot.postInitHooks.addOnce(postInit);
+ plot.postDrawHooks.addOnce(postPlotDraw);
+ plot.eventListenerHooks.addOnce('jqplotMouseMove', handleMove);
+ plot.eventListenerHooks.addOnce('jqplotMouseDown', handleMouseDown);
+ plot.eventListenerHooks.addOnce('jqplotMouseUp', handleMouseUp);
+ plot.eventListenerHooks.addOnce('jqplotClick', handleClick);
+ plot.eventListenerHooks.addOnce('jqplotRightClick', handleRightClick);
+ };
+
+ // called with scope of series
+ function barPreInit(target, data, seriesDefaults, options) {
+ if (this.rendererOptions.barDirection == 'horizontal') {
+ this._stackAxis = 'x';
+ this._primaryAxis = '_yaxis';
+ }
+ if (this.rendererOptions.waterfall == true) {
+ this._data = $.extend(true, [], this.data);
+ var sum = 0;
+ var pos = (!this.rendererOptions.barDirection || this.rendererOptions.barDirection === 'vertical' || this.transposedData === false) ? 1 : 0;
+ for(var i=0; i<this.data.length; i++) {
+ sum += this.data[i][pos];
+ if (i>0) {
+ this.data[i][pos] += this.data[i-1][pos];
+ }
+ }
+ this.data[this.data.length] = (pos == 1) ? [this.data.length+1, sum] : [sum, this.data.length+1];
+ this._data[this._data.length] = (pos == 1) ? [this._data.length+1, sum] : [sum, this._data.length+1];
+ }
+ if (this.rendererOptions.groups > 1) {
+ this.breakOnNull = true;
+ var l = this.data.length;
+ var skip = parseInt(l/this.rendererOptions.groups, 10);
+ var count = 0;
+ for (var i=skip; i<l; i+=skip) {
+ this.data.splice(i+count, 0, [null, null]);
+ this._plotData.splice(i+count, 0, [null, null]);
+ this._stackData.splice(i+count, 0, [null, null]);
+ count++;
+ }
+ for (i=0; i<this.data.length; i++) {
+ if (this._primaryAxis == '_xaxis') {
+ this.data[i][0] = i+1;
+ this._plotData[i][0] = i+1;
+ this._stackData[i][0] = i+1;
+ }
+ else {
+ this.data[i][1] = i+1;
+ this._plotData[i][1] = i+1;
+ this._stackData[i][1] = i+1;
+ }
+ }
+ }
+ }
+
+ $.jqplot.preSeriesInitHooks.push(barPreInit);
+
+ // needs to be called with scope of series, not renderer.
+ $.jqplot.BarRenderer.prototype.calcSeriesNumbers = function() {
+ var nvals = 0;
+ var nseries = 0;
+ var paxis = this[this._primaryAxis];
+ var s, series, pos;
+ // loop through all series on this axis
+ for (var i=0; i < paxis._series.length; i++) {
+ series = paxis._series[i];
+ if (series === this) {
+ pos = i;
+ }
+ // is the series rendered as a bar?
+ if (series.renderer.constructor == $.jqplot.BarRenderer) {
+ // gridData may not be computed yet, use data length insted
+ nvals += series.data.length;
+ nseries += 1;
+ }
+ }
+ // return total number of values for all bar series, total number of bar series, and position of this series
+ return [nvals, nseries, pos];
+ };
+
+ $.jqplot.BarRenderer.prototype.setBarWidth = function() {
+ // need to know how many data values we have on the approprate axis and figure it out.
+ var i;
+ var nvals = 0;
+ var nseries = 0;
+ var paxis = this[this._primaryAxis];
+ var s, series, pos;
+ var temp = this._plotSeriesInfo = this.renderer.calcSeriesNumbers.call(this);
+ nvals = temp[0];
+ nseries = temp[1];
+ var nticks = paxis.numberTicks;
+ var nbins = (nticks-1)/2;
+ // so, now we have total number of axis values.
+ if (paxis.name == 'xaxis' || paxis.name == 'x2axis') {
+ if (this._stack) {
+ this.barWidth = (paxis._offsets.max - paxis._offsets.min) / nvals * nseries - this.barMargin;
+ }
+ else {
+ this.barWidth = ((paxis._offsets.max - paxis._offsets.min)/nbins - this.barPadding * (nseries-1) - this.barMargin*2)/nseries;
+ // this.barWidth = (paxis._offsets.max - paxis._offsets.min) / nvals - this.barPadding - this.barMargin/nseries;
+ }
+ }
+ else {
+ if (this._stack) {
+ this.barWidth = (paxis._offsets.min - paxis._offsets.max) / nvals * nseries - this.barMargin;
+ }
+ else {
+ this.barWidth = ((paxis._offsets.min - paxis._offsets.max)/nbins - this.barPadding * (nseries-1) - this.barMargin*2)/nseries;
+ // this.barWidth = (paxis._offsets.min - paxis._offsets.max) / nvals - this.barPadding - this.barMargin/nseries;
+ }
+ }
+ return [nvals, nseries];
+ };
+
+ function computeHighlightColors (colors) {
+ var ret = [];
+ for (var i=0; i<colors.length; i++){
+ var rgba = $.jqplot.getColorComponents(colors[i]);
+ var newrgb = [rgba[0], rgba[1], rgba[2]];
+ var sum = newrgb[0] + newrgb[1] + newrgb[2];
+ for (var j=0; j<3; j++) {
+ // when darkening, lowest color component can be is 60.
+ newrgb[j] = (sum > 570) ? newrgb[j] * 0.8 : newrgb[j] + 0.3 * (255 - newrgb[j]);
+ newrgb[j] = parseInt(newrgb[j], 10);
+ }
+ ret.push('rgb('+newrgb[0]+','+newrgb[1]+','+newrgb[2]+')');
+ }
+ return ret;
+ }
+
+ function getStart(sidx, didx, comp, plot, axis) {
+ // check if sign change
+ var seriesIndex = sidx,
+ prevSeriesIndex = sidx - 1,
+ start,
+ prevVal,
+ aidx = (axis === 'x') ? 0 : 1;
+
+ // is this not the first series?
+ if (seriesIndex > 0) {
+ prevVal = plot.series[prevSeriesIndex]._plotData[didx][aidx];
+
+ // is there a sign change
+ if ((comp * prevVal) < 0) {
+ start = getStart(prevSeriesIndex, didx, comp, plot, axis);
+ }
+
+ // no sign change.
+ else {
+ start = plot.series[prevSeriesIndex].gridData[didx][aidx];
+ }
+
+ }
+
+ // if first series, return value at 0
+ else {
+
+ start = (aidx === 0) ? plot.series[seriesIndex]._xaxis.series_u2p(0) : plot.series[seriesIndex]._yaxis.series_u2p(0);
+ }
+
+ return start;
+ }
+
+
+ $.jqplot.BarRenderer.prototype.draw = function(ctx, gridData, options, plot) {
+ var i;
+ // Ughhh, have to make a copy of options b/c it may be modified later.
+ var opts = $.extend({}, options);
+ var shadow = (opts.shadow != undefined) ? opts.shadow : this.shadow;
+ var showLine = (opts.showLine != undefined) ? opts.showLine : this.showLine;
+ var fill = (opts.fill != undefined) ? opts.fill : this.fill;
+ var xaxis = this.xaxis;
+ var yaxis = this.yaxis;
+ var xp = this._xaxis.series_u2p;
+ var yp = this._yaxis.series_u2p;
+ var pointx, pointy;
+ // clear out data colors.
+ this._dataColors = [];
+ this._barPoints = [];
+
+ if (this.barWidth == null) {
+ this.renderer.setBarWidth.call(this);
+ }
+
+ var temp = this._plotSeriesInfo = this.renderer.calcSeriesNumbers.call(this);
+ var nvals = temp[0];
+ var nseries = temp[1];
+ var pos = temp[2];
+ var points = [];
+
+ if (this._stack) {
+ this._barNudge = 0;
+ }
+ else {
+ this._barNudge = (-Math.abs(nseries/2 - 0.5) + pos) * (this.barWidth + this.barPadding);
+ }
+ if (showLine) {
+ var negativeColors = new $.jqplot.ColorGenerator(this.negativeSeriesColors);
+ var positiveColors = new $.jqplot.ColorGenerator(this.seriesColors);
+ var negativeColor = negativeColors.get(this.index);
+ if (! this.useNegativeColors) {
+ negativeColor = opts.fillStyle;
+ }
+ var positiveColor = opts.fillStyle;
+ var base;
+ var xstart;
+ var ystart;
+
+ if (this.barDirection == 'vertical') {
+ for (var i=0; i<gridData.length; i++) {
+ if (!this._stack && this.data[i][1] == null) {
+ continue;
+ }
+ points = [];
+ base = gridData[i][0] + this._barNudge;
+
+ // stacked
+ if (this._stack && this._prevGridData.length) {
+ ystart = getStart(this.index, i, this._plotData[i][1], plot, 'y');
+ }
+
+ // not stacked
+ else {
+ if (this.fillToZero) {
+ ystart = this._yaxis.series_u2p(0);
+ }
+ else if (this.waterfall && i > 0 && i < this.gridData.length-1) {
+ ystart = this.gridData[i-1][1];
+ }
+ else if (this.waterfall && i == 0 && i < this.gridData.length-1) {
+ if (this._yaxis.min <= 0 && this._yaxis.max >= 0) {
+ ystart = this._yaxis.series_u2p(0);
+ }
+ else if (this._yaxis.min > 0) {
+ ystart = ctx.canvas.height;
+ }
+ else {
+ ystart = 0;
+ }
+ }
+ else if (this.waterfall && i == this.gridData.length - 1) {
+ if (this._yaxis.min <= 0 && this._yaxis.max >= 0) {
+ ystart = this._yaxis.series_u2p(0);
+ }
+ else if (this._yaxis.min > 0) {
+ ystart = ctx.canvas.height;
+ }
+ else {
+ ystart = 0;
+ }
+ }
+ else {
+ ystart = ctx.canvas.height;
+ }
+ }
+ if ((this.fillToZero && this._plotData[i][1] < 0) || (this.waterfall && this._data[i][1] < 0)) {
+ if (this.varyBarColor && !this._stack) {
+ if (this.useNegativeColors) {
+ opts.fillStyle = negativeColors.next();
+ }
+ else {
+ opts.fillStyle = positiveColors.next();
+ }
+ }
+ else {
+ opts.fillStyle = negativeColor;
+ }
+ }
+ else {
+ if (this.varyBarColor && !this._stack) {
+ opts.fillStyle = positiveColors.next();
+ }
+ else {
+ opts.fillStyle = positiveColor;
+ }
+ }
+
+ if (!this.fillToZero || this._plotData[i][1] >= 0) {
+ points.push([base-this.barWidth/2, ystart]);
+ points.push([base-this.barWidth/2, gridData[i][1]]);
+ points.push([base+this.barWidth/2, gridData[i][1]]);
+ points.push([base+this.barWidth/2, ystart]);
+ }
+ // for negative bars make sure points are always ordered clockwise
+ else {
+ points.push([base-this.barWidth/2, gridData[i][1]]);
+ points.push([base-this.barWidth/2, ystart]);
+ points.push([base+this.barWidth/2, ystart]);
+ points.push([base+this.barWidth/2, gridData[i][1]]);
+ }
+ this._barPoints.push(points);
+ // now draw the shadows if not stacked.
+ // for stacked plots, they are predrawn by drawShadow
+ if (shadow && !this._stack) {
+ var sopts = $.extend(true, {}, opts);
+ // need to get rid of fillStyle on shadow.
+ delete sopts.fillStyle;
+ this.renderer.shadowRenderer.draw(ctx, points, sopts);
+ }
+ var clr = opts.fillStyle || this.color;
+ this._dataColors.push(clr);
+ this.renderer.shapeRenderer.draw(ctx, points, opts);
+ }
+ }
+
+ else if (this.barDirection == 'horizontal'){
+ for (var i=0; i<gridData.length; i++) {
+ if (!this._stack && this.data[i][0] == null) {
+ continue;
+ }
+ points = [];
+ base = gridData[i][1] - this._barNudge;
+ xstart;
+
+ if (this._stack && this._prevGridData.length) {
+ xstart = getStart(this.index, i, this._plotData[i][0], plot, 'x');
+ }
+ // not stacked
+ else {
+ if (this.fillToZero) {
+ xstart = this._xaxis.series_u2p(0);
+ }
+ else if (this.waterfall && i > 0 && i < this.gridData.length-1) {
+ xstart = this.gridData[i-1][0];
+ }
+ else if (this.waterfall && i == 0 && i < this.gridData.length-1) {
+ if (this._xaxis.min <= 0 && this._xaxis.max >= 0) {
+ xstart = this._xaxis.series_u2p(0);
+ }
+ else if (this._xaxis.min > 0) {
+ xstart = 0;
+ }
+ else {
+ xstart = 0;
+ }
+ }
+ else if (this.waterfall && i == this.gridData.length - 1) {
+ if (this._xaxis.min <= 0 && this._xaxis.max >= 0) {
+ xstart = this._xaxis.series_u2p(0);
+ }
+ else if (this._xaxis.min > 0) {
+ xstart = 0;
+ }
+ else {
+ xstart = ctx.canvas.width;
+ }
+ }
+ else {
+ xstart = 0;
+ }
+ }
+ if ((this.fillToZero && this._plotData[i][1] < 0) || (this.waterfall && this._data[i][1] < 0)) {
+ if (this.varyBarColor && !this._stack) {
+ if (this.useNegativeColors) {
+ opts.fillStyle = negativeColors.next();
+ }
+ else {
+ opts.fillStyle = positiveColors.next();
+ }
+ }
+ }
+ else {
+ if (this.varyBarColor && !this._stack) {
+ opts.fillStyle = positiveColors.next();
+ }
+ else {
+ opts.fillStyle = positiveColor;
+ }
+ }
+
+
+ if (!this.fillToZero || this._plotData[i][0] >= 0) {
+ points.push([xstart, base + this.barWidth / 2]);
+ points.push([xstart, base - this.barWidth / 2]);
+ points.push([gridData[i][0], base - this.barWidth / 2]);
+ points.push([gridData[i][0], base + this.barWidth / 2]);
+ }
+ else {
+ points.push([gridData[i][0], base + this.barWidth / 2]);
+ points.push([gridData[i][0], base - this.barWidth / 2]);
+ points.push([xstart, base - this.barWidth / 2]);
+ points.push([xstart, base + this.barWidth / 2]);
+ }
+
+ this._barPoints.push(points);
+ // now draw the shadows if not stacked.
+ // for stacked plots, they are predrawn by drawShadow
+ if (shadow && !this._stack) {
+ var sopts = $.extend(true, {}, opts);
+ delete sopts.fillStyle;
+ this.renderer.shadowRenderer.draw(ctx, points, sopts);
+ }
+ var clr = opts.fillStyle || this.color;
+ this._dataColors.push(clr);
+ this.renderer.shapeRenderer.draw(ctx, points, opts);
+ }
+ }
+ }
+
+ if (this.highlightColors.length == 0) {
+ this.highlightColors = $.jqplot.computeHighlightColors(this._dataColors);
+ }
+
+ else if (typeof(this.highlightColors) == 'string') {
+ var temp = this.highlightColors;
+ this.highlightColors = [];
+ for (var i=0; i<this._dataColors.length; i++) {
+ this.highlightColors.push(temp);
+ }
+ }
+
+ };
+
+
+ // for stacked plots, shadows will be pre drawn by drawShadow.
+ $.jqplot.BarRenderer.prototype.drawShadow = function(ctx, gridData, options, plot) {
+ var i;
+ var opts = (options != undefined) ? options : {};
+ var shadow = (opts.shadow != undefined) ? opts.shadow : this.shadow;
+ var showLine = (opts.showLine != undefined) ? opts.showLine : this.showLine;
+ var fill = (opts.fill != undefined) ? opts.fill : this.fill;
+ var xaxis = this.xaxis;
+ var yaxis = this.yaxis;
+ var xp = this._xaxis.series_u2p;
+ var yp = this._yaxis.series_u2p;
+ var pointx, points, pointy, nvals, nseries, pos;
+
+ if (this._stack && this.shadow) {
+ if (this.barWidth == null) {
+ this.renderer.setBarWidth.call(this);
+ }
+
+ var temp = this._plotSeriesInfo = this.renderer.calcSeriesNumbers.call(this);
+ nvals = temp[0];
+ nseries = temp[1];
+ pos = temp[2];
+
+ if (this._stack) {
+ this._barNudge = 0;
+ }
+ else {
+ this._barNudge = (-Math.abs(nseries/2 - 0.5) + pos) * (this.barWidth + this.barPadding);
+ }
+ if (showLine) {
+
+ if (this.barDirection == 'vertical') {
+ for (var i=0; i<gridData.length; i++) {
+ if (this.data[i][1] == null) {
+ continue;
+ }
+ points = [];
+ var base = gridData[i][0] + this._barNudge;
+ var ystart;
+
+ if (this._stack && this._prevGridData.length) {
+ ystart = getStart(this.index, i, this._plotData[i][1], plot, 'y');
+ }
+ else {
+ if (this.fillToZero) {
+ ystart = this._yaxis.series_u2p(0);
+ }
+ else {
+ ystart = ctx.canvas.height;
+ }
+ }
+
+ points.push([base-this.barWidth/2, ystart]);
+ points.push([base-this.barWidth/2, gridData[i][1]]);
+ points.push([base+this.barWidth/2, gridData[i][1]]);
+ points.push([base+this.barWidth/2, ystart]);
+ this.renderer.shadowRenderer.draw(ctx, points, opts);
+ }
+ }
+
+ else if (this.barDirection == 'horizontal'){
+ for (var i=0; i<gridData.length; i++) {
+ if (this.data[i][0] == null) {
+ continue;
+ }
+ points = [];
+ var base = gridData[i][1] - this._barNudge;
+ var xstart;
+
+ if (this._stack && this._prevGridData.length) {
+ xstart = getStart(this.index, i, this._plotData[i][0], plot, 'x');
+ }
+ else {
+ if (this.fillToZero) {
+ xstart = this._xaxis.series_u2p(0);
+ }
+ else {
+ xstart = 0;
+ }
+ }
+
+ points.push([xstart, base+this.barWidth/2]);
+ points.push([gridData[i][0], base+this.barWidth/2]);
+ points.push([gridData[i][0], base-this.barWidth/2]);
+ points.push([xstart, base-this.barWidth/2]);
+ this.renderer.shadowRenderer.draw(ctx, points, opts);
+ }
+ }
+ }
+
+ }
+ };
+
+ function postInit(target, data, options) {
+ for (var i=0; i<this.series.length; i++) {
+ if (this.series[i].renderer.constructor == $.jqplot.BarRenderer) {
+ // don't allow mouseover and mousedown at same time.
+ if (this.series[i].highlightMouseOver) {
+ this.series[i].highlightMouseDown = false;
+ }
+ }
+ }
+ }
+
+ // called within context of plot
+ // create a canvas which we can draw on.
+ // insert it before the eventCanvas, so eventCanvas will still capture events.
+ function postPlotDraw() {
+ // Memory Leaks patch
+ if (this.plugins.barRenderer && this.plugins.barRenderer.highlightCanvas) {
+
+ this.plugins.barRenderer.highlightCanvas.resetCanvas();
+ this.plugins.barRenderer.highlightCanvas = null;
+ }
+
+ this.plugins.barRenderer = {highlightedSeriesIndex:null};
+ this.plugins.barRenderer.highlightCanvas = new $.jqplot.GenericCanvas();
+
+ this.eventCanvas._elem.before(this.plugins.barRenderer.highlightCanvas.createElement(this._gridPadding, 'jqplot-barRenderer-highlight-canvas', this._plotDimensions, this));
+ this.plugins.barRenderer.highlightCanvas.setContext();
+ this.eventCanvas._elem.bind('mouseleave', {plot:this}, function (ev) { unhighlight(ev.data.plot); });
+ }
+
+ function highlight (plot, sidx, pidx, points) {
+ var s = plot.series[sidx];
+ var canvas = plot.plugins.barRenderer.highlightCanvas;
+ canvas._ctx.clearRect(0,0,canvas._ctx.canvas.width, canvas._ctx.canvas.height);
+ s._highlightedPoint = pidx;
+ plot.plugins.barRenderer.highlightedSeriesIndex = sidx;
+ var opts = {fillStyle: s.highlightColors[pidx]};
+ s.renderer.shapeRenderer.draw(canvas._ctx, points, opts);
+ canvas = null;
+ }
+
+ function unhighlight (plot) {
+ var canvas = plot.plugins.barRenderer.highlightCanvas;
+ canvas._ctx.clearRect(0,0, canvas._ctx.canvas.width, canvas._ctx.canvas.height);
+ for (var i=0; i<plot.series.length; i++) {
+ plot.series[i]._highlightedPoint = null;
+ }
+ plot.plugins.barRenderer.highlightedSeriesIndex = null;
+ plot.target.trigger('jqplotDataUnhighlight');
+ canvas = null;
+ }
+
+
+ function handleMove(ev, gridpos, datapos, neighbor, plot) {
+ if (neighbor) {
+ var ins = [neighbor.seriesIndex, neighbor.pointIndex, neighbor.data];
+ var evt1 = jQuery.Event('jqplotDataMouseOver');
+ evt1.pageX = ev.pageX;
+ evt1.pageY = ev.pageY;
+ plot.target.trigger(evt1, ins);
+ if (plot.series[ins[0]].highlightMouseOver && !(ins[0] == plot.plugins.barRenderer.highlightedSeriesIndex && ins[1] == plot.series[ins[0]]._highlightedPoint)) {
+ var evt = jQuery.Event('jqplotDataHighlight');
+ evt.which = ev.which;
+ evt.pageX = ev.pageX;
+ evt.pageY = ev.pageY;
+ plot.target.trigger(evt, ins);
+ highlight (plot, neighbor.seriesIndex, neighbor.pointIndex, neighbor.points);
+ }
+ }
+ else if (neighbor == null) {
+ unhighlight (plot);
+ }
+ }
+
+ function handleMouseDown(ev, gridpos, datapos, neighbor, plot) {
+ if (neighbor) {
+ var ins = [neighbor.seriesIndex, neighbor.pointIndex, neighbor.data];
+ if (plot.series[ins[0]].highlightMouseDown && !(ins[0] == plot.plugins.barRenderer.highlightedSeriesIndex && ins[1] == plot.series[ins[0]]._highlightedPoint)) {
+ var evt = jQuery.Event('jqplotDataHighlight');
+ evt.which = ev.which;
+ evt.pageX = ev.pageX;
+ evt.pageY = ev.pageY;
+ plot.target.trigger(evt, ins);
+ highlight (plot, neighbor.seriesIndex, neighbor.pointIndex, neighbor.points);
+ }
+ }
+ else if (neighbor == null) {
+ unhighlight (plot);
+ }
+ }
+
+ function handleMouseUp(ev, gridpos, datapos, neighbor, plot) {
+ var idx = plot.plugins.barRenderer.highlightedSeriesIndex;
+ if (idx != null && plot.series[idx].highlightMouseDown) {
+ unhighlight(plot);
+ }
+ }
+
+ function handleClick(ev, gridpos, datapos, neighbor, plot) {
+ if (neighbor) {
+ var ins = [neighbor.seriesIndex, neighbor.pointIndex, neighbor.data];
+ var evt = jQuery.Event('jqplotDataClick');
+ evt.which = ev.which;
+ evt.pageX = ev.pageX;
+ evt.pageY = ev.pageY;
+ plot.target.trigger(evt, ins);
+ }
+ }
+
+ function handleRightClick(ev, gridpos, datapos, neighbor, plot) {
+ if (neighbor) {
+ var ins = [neighbor.seriesIndex, neighbor.pointIndex, neighbor.data];
+ var idx = plot.plugins.barRenderer.highlightedSeriesIndex;
+ if (idx != null && plot.series[idx].highlightMouseDown) {
+ unhighlight(plot);
+ }
+ var evt = jQuery.Event('jqplotDataRightClick');
+ evt.which = ev.which;
+ evt.pageX = ev.pageX;
+ evt.pageY = ev.pageY;
+ plot.target.trigger(evt, ins);
+ }
+ }
+
+
+})(jQuery); \ No newline at end of file
diff --git a/js/jqplot/plugins/jqplot.byteFormatter.js b/js/jqplot/plugins/jqplot.byteFormatter.js
new file mode 100644
index 0000000000..7a18370a75
--- /dev/null
+++ b/js/jqplot/plugins/jqplot.byteFormatter.js
@@ -0,0 +1,46 @@
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * jqplot formatter for byte values
+ *
+ * @package phpMyAdmin
+ */
+(function($) {
+ "use strict";
+ var formatByte = function (val, index) {
+ var units = [
+ PMA_messages.strB,
+ PMA_messages.strKiB,
+ PMA_messages.strMiB,
+ PMA_messages.strGiB,
+ PMA_messages.strTiB,
+ PMA_messages.strPiB,
+ PMA_messages.strEiB
+ ];
+ while (val >= 1024 && index <= 6) {
+ val /= 1024;
+ index++;
+ }
+ var format = '%.1f';
+ if (Math.floor(val) === val) {
+ format = '%.0f';
+ }
+ return $.jqplot.sprintf(
+ format + ' ' + units[index], val
+ );
+ };
+ /**
+ * The index indicates what unit the incoming data will be in.
+ * 0 for bytes, 1 for kilobytes and so on...
+ */
+ $.jqplot.byteFormatter = function (index) {
+ index = index || 0;
+ return function (format, val) {
+ if (typeof val === 'number') {
+ val = parseFloat(val) || 0;
+ return formatByte(val, index);
+ } else {
+ return String(val);
+ }
+ };
+ };
+})(jQuery);
diff --git a/js/jqplot/plugins/jqplot.canvasAxisLabelRenderer.js b/js/jqplot/plugins/jqplot.canvasAxisLabelRenderer.js
new file mode 100644
index 0000000000..18404399fd
--- /dev/null
+++ b/js/jqplot/plugins/jqplot.canvasAxisLabelRenderer.js
@@ -0,0 +1,203 @@
+/**
+ * jqPlot
+ * Pure JavaScript plotting plugin using jQuery
+ *
+ * Version: 1.0.4
+ * Revision: 1121
+ *
+ * Copyright (c) 2009-2012 Chris Leonello
+ * jqPlot is currently available for use in all personal or commercial projects
+ * under both the MIT (http://www.opensource.org/licenses/mit-license.php) and GPL
+ * version 2.0 (http://www.gnu.org/licenses/gpl-2.0.html) licenses. This means that you can
+ * choose the license that best suits your project and use it accordingly.
+ *
+ * Although not required, the author would appreciate an email letting him
+ * know of any substantial use of jqPlot. You can reach the author at:
+ * chris at jqplot dot com or see http://www.jqplot.com/info.php .
+ *
+ * If you are feeling kind and generous, consider supporting the project by
+ * making a donation at: http://www.jqplot.com/donate.php .
+ *
+ * sprintf functions contained in jqplot.sprintf.js by Ash Searle:
+ *
+ * version 2007.04.27
+ * author Ash Searle
+ * http://hexmen.com/blog/2007/03/printf-sprintf/
+ * http://hexmen.com/js/sprintf.js
+ * The author (Ash Searle) has placed this code in the public domain:
+ * "This code is unrestricted: you are free to use it however you like."
+ *
+ */
+(function($) {
+ /**
+ * Class: $.jqplot.CanvasAxisLabelRenderer
+ * Renderer to draw axis labels with a canvas element to support advanced
+ * featrues such as rotated text. This renderer uses a separate rendering engine
+ * to draw the text on the canvas. Two modes of rendering the text are available.
+ * If the browser has native font support for canvas fonts (currently Mozila 3.5
+ * and Safari 4), you can enable text rendering with the canvas fillText method.
+ * You do so by setting the "enableFontSupport" option to true.
+ *
+ * Browsers lacking native font support will have the text drawn on the canvas
+ * using the Hershey font metrics. Even if the "enableFontSupport" option is true
+ * non-supporting browsers will still render with the Hershey font.
+ *
+ */
+ $.jqplot.CanvasAxisLabelRenderer = function(options) {
+ // Group: Properties
+
+ // prop: angle
+ // angle of text, measured clockwise from x axis.
+ this.angle = 0;
+ // name of the axis associated with this tick
+ this.axis;
+ // prop: show
+ // wether or not to show the tick (mark and label).
+ this.show = true;
+ // prop: showLabel
+ // wether or not to show the label.
+ this.showLabel = true;
+ // prop: label
+ // label for the axis.
+ this.label = '';
+ // prop: fontFamily
+ // CSS spec for the font-family css attribute.
+ // Applies only to browsers supporting native font rendering in the
+ // canvas tag. Currently Mozilla 3.5 and Safari 4.
+ this.fontFamily = '"Trebuchet MS", Arial, Helvetica, sans-serif';
+ // prop: fontSize
+ // CSS spec for font size.
+ this.fontSize = '11pt';
+ // prop: fontWeight
+ // CSS spec for fontWeight: normal, bold, bolder, lighter or a number 100 - 900
+ this.fontWeight = 'normal';
+ // prop: fontStretch
+ // Multiplier to condense or expand font width.
+ // Applies only to browsers which don't support canvas native font rendering.
+ this.fontStretch = 1.0;
+ // prop: textColor
+ // css spec for the color attribute.
+ this.textColor = '#666666';
+ // prop: enableFontSupport
+ // true to turn on native canvas font support in Mozilla 3.5+ and Safari 4+.
+ // If true, label will be drawn with canvas tag native support for fonts.
+ // If false, label will be drawn with Hershey font metrics.
+ this.enableFontSupport = true;
+ // prop: pt2px
+ // Point to pixel scaling factor, used for computing height of bounding box
+ // around a label. The labels text renderer has a default setting of 1.4, which
+ // should be suitable for most fonts. Leave as null to use default. If tops of
+ // letters appear clipped, increase this. If bounding box seems too big, decrease.
+ // This is an issue only with the native font renderering capabilities of Mozilla
+ // 3.5 and Safari 4 since they do not provide a method to determine the font height.
+ this.pt2px = null;
+
+ this._elem;
+ this._ctx;
+ this._plotWidth;
+ this._plotHeight;
+ this._plotDimensions = {height:null, width:null};
+
+ $.extend(true, this, options);
+
+ if (options.angle == null && this.axis != 'xaxis' && this.axis != 'x2axis') {
+ this.angle = -90;
+ }
+
+ var ropts = {fontSize:this.fontSize, fontWeight:this.fontWeight, fontStretch:this.fontStretch, fillStyle:this.textColor, angle:this.getAngleRad(), fontFamily:this.fontFamily};
+ if (this.pt2px) {
+ ropts.pt2px = this.pt2px;
+ }
+
+ if (this.enableFontSupport) {
+ if ($.jqplot.support_canvas_text()) {
+ this._textRenderer = new $.jqplot.CanvasFontRenderer(ropts);
+ }
+
+ else {
+ this._textRenderer = new $.jqplot.CanvasTextRenderer(ropts);
+ }
+ }
+ else {
+ this._textRenderer = new $.jqplot.CanvasTextRenderer(ropts);
+ }
+ };
+
+ $.jqplot.CanvasAxisLabelRenderer.prototype.init = function(options) {
+ $.extend(true, this, options);
+ this._textRenderer.init({fontSize:this.fontSize, fontWeight:this.fontWeight, fontStretch:this.fontStretch, fillStyle:this.textColor, angle:this.getAngleRad(), fontFamily:this.fontFamily});
+ };
+
+ // return width along the x axis
+ // will check first to see if an element exists.
+ // if not, will return the computed text box width.
+ $.jqplot.CanvasAxisLabelRenderer.prototype.getWidth = function(ctx) {
+ if (this._elem) {
+ return this._elem.outerWidth(true);
+ }
+ else {
+ var tr = this._textRenderer;
+ var l = tr.getWidth(ctx);
+ var h = tr.getHeight(ctx);
+ var w = Math.abs(Math.sin(tr.angle)*h) + Math.abs(Math.cos(tr.angle)*l);
+ return w;
+ }
+ };
+
+ // return height along the y axis.
+ $.jqplot.CanvasAxisLabelRenderer.prototype.getHeight = function(ctx) {
+ if (this._elem) {
+ return this._elem.outerHeight(true);
+ }
+ else {
+ var tr = this._textRenderer;
+ var l = tr.getWidth(ctx);
+ var h = tr.getHeight(ctx);
+ var w = Math.abs(Math.cos(tr.angle)*h) + Math.abs(Math.sin(tr.angle)*l);
+ return w;
+ }
+ };
+
+ $.jqplot.CanvasAxisLabelRenderer.prototype.getAngleRad = function() {
+ var a = this.angle * Math.PI/180;
+ return a;
+ };
+
+ $.jqplot.CanvasAxisLabelRenderer.prototype.draw = function(ctx, plot) {
+ // Memory Leaks patch
+ if (this._elem) {
+ if ($.jqplot.use_excanvas && window.G_vmlCanvasManager.uninitElement !== undefined) {
+ window.G_vmlCanvasManager.uninitElement(this._elem.get(0));
+ }
+
+ this._elem.emptyForce();
+ this._elem = null;
+ }
+
+ // create a canvas here, but can't draw on it untill it is appended
+ // to dom for IE compatability.
+ var elem = plot.canvasManager.getCanvas();
+
+ this._textRenderer.setText(this.label, ctx);
+ var w = this.getWidth(ctx);
+ var h = this.getHeight(ctx);
+ elem.width = w;
+ elem.height = h;
+ elem.style.width = w;
+ elem.style.height = h;
+
+ elem = plot.canvasManager.initCanvas(elem);
+
+ this._elem = $(elem);
+ this._elem.css({ position: 'absolute'});
+ this._elem.addClass('jqplot-'+this.axis+'-label');
+
+ elem = null;
+ return this._elem;
+ };
+
+ $.jqplot.CanvasAxisLabelRenderer.prototype.pack = function() {
+ this._textRenderer.draw(this._elem.get(0).getContext("2d"), this.label);
+ };
+
+})(jQuery); \ No newline at end of file
diff --git a/js/jqplot/plugins/jqplot.canvasTextRenderer.js b/js/jqplot/plugins/jqplot.canvasTextRenderer.js
new file mode 100644
index 0000000000..53f25305ec
--- /dev/null
+++ b/js/jqplot/plugins/jqplot.canvasTextRenderer.js
@@ -0,0 +1,449 @@
+/**
+ * jqPlot
+ * Pure JavaScript plotting plugin using jQuery
+ *
+ * Version: 1.0.4
+ * Revision: 1121
+ *
+ * Copyright (c) 2009-2012 Chris Leonello
+ * jqPlot is currently available for use in all personal or commercial projects
+ * under both the MIT (http://www.opensource.org/licenses/mit-license.php) and GPL
+ * version 2.0 (http://www.gnu.org/licenses/gpl-2.0.html) licenses. This means that you can
+ * choose the license that best suits your project and use it accordingly.
+ *
+ * Although not required, the author would appreciate an email letting him
+ * know of any substantial use of jqPlot. You can reach the author at:
+ * chris at jqplot dot com or see http://www.jqplot.com/info.php .
+ *
+ * If you are feeling kind and generous, consider supporting the project by
+ * making a donation at: http://www.jqplot.com/donate.php .
+ *
+ * sprintf functions contained in jqplot.sprintf.js by Ash Searle:
+ *
+ * version 2007.04.27
+ * author Ash Searle
+ * http://hexmen.com/blog/2007/03/printf-sprintf/
+ * http://hexmen.com/js/sprintf.js
+ * The author (Ash Searle) has placed this code in the public domain:
+ * "This code is unrestricted: you are free to use it however you like."
+ *
+ * included jsDate library by Chris Leonello:
+ *
+ * Copyright (c) 2010-2012 Chris Leonello
+ *
+ * jsDate is currently available for use in all personal or commercial projects
+ * under both the MIT and GPL version 2.0 licenses. This means that you can
+ * choose the license that best suits your project and use it accordingly.
+ *
+ * jsDate borrows many concepts and ideas from the Date Instance
+ * Methods by Ken Snyder along with some parts of Ken's actual code.
+ *
+ * Ken's origianl Date Instance Methods and copyright notice:
+ *
+ * Ken Snyder (ken d snyder at gmail dot com)
+ * 2008-09-10
+ * version 2.0.2 (http://kendsnyder.com/sandbox/date/)
+ * Creative Commons Attribution License 3.0 (http://creativecommons.org/licenses/by/3.0/)
+ *
+ * jqplotToImage function based on Larry Siden's export-jqplot-to-png.js.
+ * Larry has generously given permission to adapt his code for inclusion
+ * into jqPlot.
+ *
+ * Larry's original code can be found here:
+ *
+ * https://github.com/lsiden/export-jqplot-to-png
+ *
+ *
+ */
+
+(function($) {
+ // This code is a modified version of the canvastext.js code, copyright below:
+ //
+ // This code is released to the public domain by Jim Studt, 2007.
+ // He may keep some sort of up to date copy at http://www.federated.com/~jim/canvastext/
+ //
+ $.jqplot.CanvasTextRenderer = function(options){
+ this.fontStyle = 'normal'; // normal, italic, oblique [not implemented]
+ this.fontVariant = 'normal'; // normal, small caps [not implemented]
+ this.fontWeight = 'normal'; // normal, bold, bolder, lighter, 100 - 900
+ this.fontSize = '10px';
+ this.fontFamily = 'sans-serif';
+ this.fontStretch = 1.0;
+ this.fillStyle = '#666666';
+ this.angle = 0;
+ this.textAlign = 'start';
+ this.textBaseline = 'alphabetic';
+ this.text;
+ this.width;
+ this.height;
+ this.pt2px = 1.28;
+
+ $.extend(true, this, options);
+ this.normalizedFontSize = this.normalizeFontSize(this.fontSize);
+ this.setHeight();
+ };
+
+ $.jqplot.CanvasTextRenderer.prototype.init = function(options) {
+ $.extend(true, this, options);
+ this.normalizedFontSize = this.normalizeFontSize(this.fontSize);
+ this.setHeight();
+ };
+
+ // convert css spec into point size
+ // returns float
+ $.jqplot.CanvasTextRenderer.prototype.normalizeFontSize = function(sz) {
+ sz = String(sz);
+ var n = parseFloat(sz);
+ if (sz.indexOf('px') > -1) {
+ return n/this.pt2px;
+ }
+ else if (sz.indexOf('pt') > -1) {
+ return n;
+ }
+ else if (sz.indexOf('em') > -1) {
+ return n*12;
+ }
+ else if (sz.indexOf('%') > -1) {
+ return n*12/100;
+ }
+ // default to pixels;
+ else {
+ return n/this.pt2px;
+ }
+ };
+
+
+ $.jqplot.CanvasTextRenderer.prototype.fontWeight2Float = function(w) {
+ // w = normal | bold | bolder | lighter | 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900
+ // return values adjusted for Hershey font.
+ if (Number(w)) {
+ return w/400;
+ }
+ else {
+ switch (w) {
+ case 'normal':
+ return 1;
+ break;
+ case 'bold':
+ return 1.75;
+ break;
+ case 'bolder':
+ return 2.25;
+ break;
+ case 'lighter':
+ return 0.75;
+ break;
+ default:
+ return 1;
+ break;
+ }
+ }
+ };
+
+ $.jqplot.CanvasTextRenderer.prototype.getText = function() {
+ return this.text;
+ };
+
+ $.jqplot.CanvasTextRenderer.prototype.setText = function(t, ctx) {
+ this.text = t;
+ this.setWidth(ctx);
+ return this;
+ };
+
+ $.jqplot.CanvasTextRenderer.prototype.getWidth = function(ctx) {
+ return this.width;
+ };
+
+ $.jqplot.CanvasTextRenderer.prototype.setWidth = function(ctx, w) {
+ if (!w) {
+ this.width = this.measure(ctx, this.text);
+ }
+ else {
+ this.width = w;
+ }
+ return this;
+ };
+
+ // return height in pixels.
+ $.jqplot.CanvasTextRenderer.prototype.getHeight = function(ctx) {
+ return this.height;
+ };
+
+ // w - height in pt
+ // set heigh in px
+ $.jqplot.CanvasTextRenderer.prototype.setHeight = function(w) {
+ if (!w) {
+ //height = this.fontSize /0.75;
+ this.height = this.normalizedFontSize * this.pt2px;
+ }
+ else {
+ this.height = w;
+ }
+ return this;
+ };
+
+ $.jqplot.CanvasTextRenderer.prototype.letter = function (ch)
+ {
+ return this.letters[ch];
+ };
+
+ $.jqplot.CanvasTextRenderer.prototype.ascent = function()
+ {
+ return this.normalizedFontSize;
+ };
+
+ $.jqplot.CanvasTextRenderer.prototype.descent = function()
+ {
+ return 7.0*this.normalizedFontSize/25.0;
+ };
+
+ $.jqplot.CanvasTextRenderer.prototype.measure = function(ctx, str)
+ {
+ var total = 0;
+ var len = str.length;
+
+ for (var i = 0; i < len; i++) {
+ var c = this.letter(str.charAt(i));
+ if (c) {
+ total += c.width * this.normalizedFontSize / 25.0 * this.fontStretch;
+ }
+ }
+ return total;
+ };
+
+ $.jqplot.CanvasTextRenderer.prototype.draw = function(ctx,str)
+ {
+ var x = 0;
+ // leave room at bottom for descenders.
+ var y = this.height*0.72;
+ var total = 0;
+ var len = str.length;
+ var mag = this.normalizedFontSize / 25.0;
+
+ ctx.save();
+ var tx, ty;
+
+ // 1st quadrant
+ if ((-Math.PI/2 <= this.angle && this.angle <= 0) || (Math.PI*3/2 <= this.angle && this.angle <= Math.PI*2)) {
+ tx = 0;
+ ty = -Math.sin(this.angle) * this.width;
+ }
+ // 4th quadrant
+ else if ((0 < this.angle && this.angle <= Math.PI/2) || (-Math.PI*2 <= this.angle && this.angle <= -Math.PI*3/2)) {
+ tx = Math.sin(this.angle) * this.height;
+ ty = 0;
+ }
+ // 2nd quadrant
+ else if ((-Math.PI < this.angle && this.angle < -Math.PI/2) || (Math.PI <= this.angle && this.angle <= Math.PI*3/2)) {
+ tx = -Math.cos(this.angle) * this.width;
+ ty = -Math.sin(this.angle) * this.width - Math.cos(this.angle) * this.height;
+ }
+ // 3rd quadrant
+ else if ((-Math.PI*3/2 < this.angle && this.angle < Math.PI) || (Math.PI/2 < this.angle && this.angle < Math.PI)) {
+ tx = Math.sin(this.angle) * this.height - Math.cos(this.angle)*this.width;
+ ty = -Math.cos(this.angle) * this.height;
+ }
+
+ ctx.strokeStyle = this.fillStyle;
+ ctx.fillStyle = this.fillStyle;
+ ctx.translate(tx, ty);
+ ctx.rotate(this.angle);
+ ctx.lineCap = "round";
+ // multiplier was 2.0
+ var fact = (this.normalizedFontSize > 30) ? 2.0 : 2 + (30 - this.normalizedFontSize)/20;
+ ctx.lineWidth = fact * mag * this.fontWeight2Float(this.fontWeight);
+
+ for ( var i = 0; i < len; i++) {
+ var c = this.letter( str.charAt(i));
+ if ( !c) {
+ continue;
+ }
+
+ ctx.beginPath();
+
+ var penUp = 1;
+ var needStroke = 0;
+ for ( var j = 0; j < c.points.length; j++) {
+ var a = c.points[j];
+ if ( a[0] == -1 && a[1] == -1) {
+ penUp = 1;
+ continue;
+ }
+ if ( penUp) {
+ ctx.moveTo( x + a[0]*mag*this.fontStretch, y - a[1]*mag);
+ penUp = false;
+ } else {
+ ctx.lineTo( x + a[0]*mag*this.fontStretch, y - a[1]*mag);
+ }
+ }
+ ctx.stroke();
+ x += c.width*mag*this.fontStretch;
+ }
+ ctx.restore();
+ return total;
+ };
+
+ $.jqplot.CanvasTextRenderer.prototype.letters = {
+ ' ': { width: 16, points: [] },
+ '!': { width: 10, points: [[5,21],[5,7],[-1,-1],[5,2],[4,1],[5,0],[6,1],[5,2]] },
+ '"': { width: 16, points: [[4,21],[4,14],[-1,-1],[12,21],[12,14]] },
+ '#': { width: 21, points: [[11,25],[4,-7],[-1,-1],[17,25],[10,-7],[-1,-1],[4,12],[18,12],[-1,-1],[3,6],[17,6]] },
+ '$': { width: 20, points: [[8,25],[8,-4],[-1,-1],[12,25],[12,-4],[-1,-1],[17,18],[15,20],[12,21],[8,21],[5,20],[3,18],[3,16],[4,14],[5,13],[7,12],[13,10],[15,9],[16,8],[17,6],[17,3],[15,1],[12,0],[8,0],[5,1],[3,3]] },
+ '%': { width: 24, points: [[21,21],[3,0],[-1,-1],[8,21],[10,19],[10,17],[9,15],[7,14],[5,14],[3,16],[3,18],[4,20],[6,21],[8,21],[10,20],[13,19],[16,19],[19,20],[21,21],[-1,-1],[17,7],[15,6],[14,4],[14,2],[16,0],[18,0],[20,1],[21,3],[21,5],[19,7],[17,7]] },
+ '&': { width: 26, points: [[23,12],[23,13],[22,14],[21,14],[20,13],[19,11],[17,6],[15,3],[13,1],[11,0],[7,0],[5,1],[4,2],[3,4],[3,6],[4,8],[5,9],[12,13],[13,14],[14,16],[14,18],[13,20],[11,21],[9,20],[8,18],[8,16],[9,13],[11,10],[16,3],[18,1],[20,0],[22,0],[23,1],[23,2]] },
+ '\'': { width: 10, points: [[5,19],[4,20],[5,21],[6,20],[6,18],[5,16],[4,15]] },
+ '(': { width: 14, points: [[11,25],[9,23],[7,20],[5,16],[4,11],[4,7],[5,2],[7,-2],[9,-5],[11,-7]] },
+ ')': { width: 14, points: [[3,25],[5,23],[7,20],[9,16],[10,11],[10,7],[9,2],[7,-2],[5,-5],[3,-7]] },
+ '*': { width: 16, points: [[8,21],[8,9],[-1,-1],[3,18],[13,12],[-1,-1],[13,18],[3,12]] },
+ '+': { width: 26, points: [[13,18],[13,0],[-1,-1],[4,9],[22,9]] },
+ ',': { width: 10, points: [[6,1],[5,0],[4,1],[5,2],[6,1],[6,-1],[5,-3],[4,-4]] },
+ '-': { width: 18, points: [[6,9],[12,9]] },
+ '.': { width: 10, points: [[5,2],[4,1],[5,0],[6,1],[5,2]] },
+ '/': { width: 22, points: [[20,25],[2,-7]] },
+ '0': { width: 20, points: [[9,21],[6,20],[4,17],[3,12],[3,9],[4,4],[6,1],[9,0],[11,0],[14,1],[16,4],[17,9],[17,12],[16,17],[14,20],[11,21],[9,21]] },
+ '1': { width: 20, points: [[6,17],[8,18],[11,21],[11,0]] },
+ '2': { width: 20, points: [[4,16],[4,17],[5,19],[6,20],[8,21],[12,21],[14,20],[15,19],[16,17],[16,15],[15,13],[13,10],[3,0],[17,0]] },
+ '3': { width: 20, points: [[5,21],[16,21],[10,13],[13,13],[15,12],[16,11],[17,8],[17,6],[16,3],[14,1],[11,0],[8,0],[5,1],[4,2],[3,4]] },
+ '4': { width: 20, points: [[13,21],[3,7],[18,7],[-1,-1],[13,21],[13,0]] },
+ '5': { width: 20, points: [[15,21],[5,21],[4,12],[5,13],[8,14],[11,14],[14,13],[16,11],[17,8],[17,6],[16,3],[14,1],[11,0],[8,0],[5,1],[4,2],[3,4]] },
+ '6': { width: 20, points: [[16,18],[15,20],[12,21],[10,21],[7,20],[5,17],[4,12],[4,7],[5,3],[7,1],[10,0],[11,0],[14,1],[16,3],[17,6],[17,7],[16,10],[14,12],[11,13],[10,13],[7,12],[5,10],[4,7]] },
+ '7': { width: 20, points: [[17,21],[7,0],[-1,-1],[3,21],[17,21]] },
+ '8': { width: 20, points: [[8,21],[5,20],[4,18],[4,16],[5,14],[7,13],[11,12],[14,11],[16,9],[17,7],[17,4],[16,2],[15,1],[12,0],[8,0],[5,1],[4,2],[3,4],[3,7],[4,9],[6,11],[9,12],[13,13],[15,14],[16,16],[16,18],[15,20],[12,21],[8,21]] },
+ '9': { width: 20, points: [[16,14],[15,11],[13,9],[10,8],[9,8],[6,9],[4,11],[3,14],[3,15],[4,18],[6,20],[9,21],[10,21],[13,20],[15,18],[16,14],[16,9],[15,4],[13,1],[10,0],[8,0],[5,1],[4,3]] },
+ ':': { width: 10, points: [[5,14],[4,13],[5,12],[6,13],[5,14],[-1,-1],[5,2],[4,1],[5,0],[6,1],[5,2]] },
+ ';': { width: 10, points: [[5,14],[4,13],[5,12],[6,13],[5,14],[-1,-1],[6,1],[5,0],[4,1],[5,2],[6,1],[6,-1],[5,-3],[4,-4]] },
+ '<': { width: 24, points: [[20,18],[4,9],[20,0]] },
+ '=': { width: 26, points: [[4,12],[22,12],[-1,-1],[4,6],[22,6]] },
+ '>': { width: 24, points: [[4,18],[20,9],[4,0]] },
+ '?': { width: 18, points: [[3,16],[3,17],[4,19],[5,20],[7,21],[11,21],[13,20],[14,19],[15,17],[15,15],[14,13],[13,12],[9,10],[9,7],[-1,-1],[9,2],[8,1],[9,0],[10,1],[9,2]] },
+ '@': { width: 27, points: [[18,13],[17,15],[15,16],[12,16],[10,15],[9,14],[8,11],[8,8],[9,6],[11,5],[14,5],[16,6],[17,8],[-1,-1],[12,16],[10,14],[9,11],[9,8],[10,6],[11,5],[-1,-1],[18,16],[17,8],[17,6],[19,5],[21,5],[23,7],[24,10],[24,12],[23,15],[22,17],[20,19],[18,20],[15,21],[12,21],[9,20],[7,19],[5,17],[4,15],[3,12],[3,9],[4,6],[5,4],[7,2],[9,1],[12,0],[15,0],[18,1],[20,2],[21,3],[-1,-1],[19,16],[18,8],[18,6],[19,5]] },
+ 'A': { width: 18, points: [[9,21],[1,0],[-1,-1],[9,21],[17,0],[-1,-1],[4,7],[14,7]] },
+ 'B': { width: 21, points: [[4,21],[4,0],[-1,-1],[4,21],[13,21],[16,20],[17,19],[18,17],[18,15],[17,13],[16,12],[13,11],[-1,-1],[4,11],[13,11],[16,10],[17,9],[18,7],[18,4],[17,2],[16,1],[13,0],[4,0]] },
+ 'C': { width: 21, points: [[18,16],[17,18],[15,20],[13,21],[9,21],[7,20],[5,18],[4,16],[3,13],[3,8],[4,5],[5,3],[7,1],[9,0],[13,0],[15,1],[17,3],[18,5]] },
+ 'D': { width: 21, points: [[4,21],[4,0],[-1,-1],[4,21],[11,21],[14,20],[16,18],[17,16],[18,13],[18,8],[17,5],[16,3],[14,1],[11,0],[4,0]] },
+ 'E': { width: 19, points: [[4,21],[4,0],[-1,-1],[4,21],[17,21],[-1,-1],[4,11],[12,11],[-1,-1],[4,0],[17,0]] },
+ 'F': { width: 18, points: [[4,21],[4,0],[-1,-1],[4,21],[17,21],[-1,-1],[4,11],[12,11]] },
+ 'G': { width: 21, points: [[18,16],[17,18],[15,20],[13,21],[9,21],[7,20],[5,18],[4,16],[3,13],[3,8],[4,5],[5,3],[7,1],[9,0],[13,0],[15,1],[17,3],[18,5],[18,8],[-1,-1],[13,8],[18,8]] },
+ 'H': { width: 22, points: [[4,21],[4,0],[-1,-1],[18,21],[18,0],[-1,-1],[4,11],[18,11]] },
+ 'I': { width: 8, points: [[4,21],[4,0]] },
+ 'J': { width: 16, points: [[12,21],[12,5],[11,2],[10,1],[8,0],[6,0],[4,1],[3,2],[2,5],[2,7]] },
+ 'K': { width: 21, points: [[4,21],[4,0],[-1,-1],[18,21],[4,7],[-1,-1],[9,12],[18,0]] },
+ 'L': { width: 17, points: [[4,21],[4,0],[-1,-1],[4,0],[16,0]] },
+ 'M': { width: 24, points: [[4,21],[4,0],[-1,-1],[4,21],[12,0],[-1,-1],[20,21],[12,0],[-1,-1],[20,21],[20,0]] },
+ 'N': { width: 22, points: [[4,21],[4,0],[-1,-1],[4,21],[18,0],[-1,-1],[18,21],[18,0]] },
+ 'O': { width: 22, points: [[9,21],[7,20],[5,18],[4,16],[3,13],[3,8],[4,5],[5,3],[7,1],[9,0],[13,0],[15,1],[17,3],[18,5],[19,8],[19,13],[18,16],[17,18],[15,20],[13,21],[9,21]] },
+ 'P': { width: 21, points: [[4,21],[4,0],[-1,-1],[4,21],[13,21],[16,20],[17,19],[18,17],[18,14],[17,12],[16,11],[13,10],[4,10]] },
+ 'Q': { width: 22, points: [[9,21],[7,20],[5,18],[4,16],[3,13],[3,8],[4,5],[5,3],[7,1],[9,0],[13,0],[15,1],[17,3],[18,5],[19,8],[19,13],[18,16],[17,18],[15,20],[13,21],[9,21],[-1,-1],[12,4],[18,-2]] },
+ 'R': { width: 21, points: [[4,21],[4,0],[-1,-1],[4,21],[13,21],[16,20],[17,19],[18,17],[18,15],[17,13],[16,12],[13,11],[4,11],[-1,-1],[11,11],[18,0]] },
+ 'S': { width: 20, points: [[17,18],[15,20],[12,21],[8,21],[5,20],[3,18],[3,16],[4,14],[5,13],[7,12],[13,10],[15,9],[16,8],[17,6],[17,3],[15,1],[12,0],[8,0],[5,1],[3,3]] },
+ 'T': { width: 16, points: [[8,21],[8,0],[-1,-1],[1,21],[15,21]] },
+ 'U': { width: 22, points: [[4,21],[4,6],[5,3],[7,1],[10,0],[12,0],[15,1],[17,3],[18,6],[18,21]] },
+ 'V': { width: 18, points: [[1,21],[9,0],[-1,-1],[17,21],[9,0]] },
+ 'W': { width: 24, points: [[2,21],[7,0],[-1,-1],[12,21],[7,0],[-1,-1],[12,21],[17,0],[-1,-1],[22,21],[17,0]] },
+ 'X': { width: 20, points: [[3,21],[17,0],[-1,-1],[17,21],[3,0]] },
+ 'Y': { width: 18, points: [[1,21],[9,11],[9,0],[-1,-1],[17,21],[9,11]] },
+ 'Z': { width: 20, points: [[17,21],[3,0],[-1,-1],[3,21],[17,21],[-1,-1],[3,0],[17,0]] },
+ '[': { width: 14, points: [[4,25],[4,-7],[-1,-1],[5,25],[5,-7],[-1,-1],[4,25],[11,25],[-1,-1],[4,-7],[11,-7]] },
+ '\\': { width: 14, points: [[0,21],[14,-3]] },
+ ']': { width: 14, points: [[9,25],[9,-7],[-1,-1],[10,25],[10,-7],[-1,-1],[3,25],[10,25],[-1,-1],[3,-7],[10,-7]] },
+ '^': { width: 16, points: [[6,15],[8,18],[10,15],[-1,-1],[3,12],[8,17],[13,12],[-1,-1],[8,17],[8,0]] },
+ '_': { width: 16, points: [[0,-2],[16,-2]] },
+ '`': { width: 10, points: [[6,21],[5,20],[4,18],[4,16],[5,15],[6,16],[5,17]] },
+ 'a': { width: 19, points: [[15,14],[15,0],[-1,-1],[15,11],[13,13],[11,14],[8,14],[6,13],[4,11],[3,8],[3,6],[4,3],[6,1],[8,0],[11,0],[13,1],[15,3]] },
+ 'b': { width: 19, points: [[4,21],[4,0],[-1,-1],[4,11],[6,13],[8,14],[11,14],[13,13],[15,11],[16,8],[16,6],[15,3],[13,1],[11,0],[8,0],[6,1],[4,3]] },
+ 'c': { width: 18, points: [[15,11],[13,13],[11,14],[8,14],[6,13],[4,11],[3,8],[3,6],[4,3],[6,1],[8,0],[11,0],[13,1],[15,3]] },
+ 'd': { width: 19, points: [[15,21],[15,0],[-1,-1],[15,11],[13,13],[11,14],[8,14],[6,13],[4,11],[3,8],[3,6],[4,3],[6,1],[8,0],[11,0],[13,1],[15,3]] },
+ 'e': { width: 18, points: [[3,8],[15,8],[15,10],[14,12],[13,13],[11,14],[8,14],[6,13],[4,11],[3,8],[3,6],[4,3],[6,1],[8,0],[11,0],[13,1],[15,3]] },
+ 'f': { width: 12, points: [[10,21],[8,21],[6,20],[5,17],[5,0],[-1,-1],[2,14],[9,14]] },
+ 'g': { width: 19, points: [[15,14],[15,-2],[14,-5],[13,-6],[11,-7],[8,-7],[6,-6],[-1,-1],[15,11],[13,13],[11,14],[8,14],[6,13],[4,11],[3,8],[3,6],[4,3],[6,1],[8,0],[11,0],[13,1],[15,3]] },
+ 'h': { width: 19, points: [[4,21],[4,0],[-1,-1],[4,10],[7,13],[9,14],[12,14],[14,13],[15,10],[15,0]] },
+ 'i': { width: 8, points: [[3,21],[4,20],[5,21],[4,22],[3,21],[-1,-1],[4,14],[4,0]] },
+ 'j': { width: 10, points: [[5,21],[6,20],[7,21],[6,22],[5,21],[-1,-1],[6,14],[6,-3],[5,-6],[3,-7],[1,-7]] },
+ 'k': { width: 17, points: [[4,21],[4,0],[-1,-1],[14,14],[4,4],[-1,-1],[8,8],[15,0]] },
+ 'l': { width: 8, points: [[4,21],[4,0]] },
+ 'm': { width: 30, points: [[4,14],[4,0],[-1,-1],[4,10],[7,13],[9,14],[12,14],[14,13],[15,10],[15,0],[-1,-1],[15,10],[18,13],[20,14],[23,14],[25,13],[26,10],[26,0]] },
+ 'n': { width: 19, points: [[4,14],[4,0],[-1,-1],[4,10],[7,13],[9,14],[12,14],[14,13],[15,10],[15,0]] },
+ 'o': { width: 19, points: [[8,14],[6,13],[4,11],[3,8],[3,6],[4,3],[6,1],[8,0],[11,0],[13,1],[15,3],[16,6],[16,8],[15,11],[13,13],[11,14],[8,14]] },
+ 'p': { width: 19, points: [[4,14],[4,-7],[-1,-1],[4,11],[6,13],[8,14],[11,14],[13,13],[15,11],[16,8],[16,6],[15,3],[13,1],[11,0],[8,0],[6,1],[4,3]] },
+ 'q': { width: 19, points: [[15,14],[15,-7],[-1,-1],[15,11],[13,13],[11,14],[8,14],[6,13],[4,11],[3,8],[3,6],[4,3],[6,1],[8,0],[11,0],[13,1],[15,3]] },
+ 'r': { width: 13, points: [[4,14],[4,0],[-1,-1],[4,8],[5,11],[7,13],[9,14],[12,14]] },
+ 's': { width: 17, points: [[14,11],[13,13],[10,14],[7,14],[4,13],[3,11],[4,9],[6,8],[11,7],[13,6],[14,4],[14,3],[13,1],[10,0],[7,0],[4,1],[3,3]] },
+ 't': { width: 12, points: [[5,21],[5,4],[6,1],[8,0],[10,0],[-1,-1],[2,14],[9,14]] },
+ 'u': { width: 19, points: [[4,14],[4,4],[5,1],[7,0],[10,0],[12,1],[15,4],[-1,-1],[15,14],[15,0]] },
+ 'v': { width: 16, points: [[2,14],[8,0],[-1,-1],[14,14],[8,0]] },
+ 'w': { width: 22, points: [[3,14],[7,0],[-1,-1],[11,14],[7,0],[-1,-1],[11,14],[15,0],[-1,-1],[19,14],[15,0]] },
+ 'x': { width: 17, points: [[3,14],[14,0],[-1,-1],[14,14],[3,0]] },
+ 'y': { width: 16, points: [[2,14],[8,0],[-1,-1],[14,14],[8,0],[6,-4],[4,-6],[2,-7],[1,-7]] },
+ 'z': { width: 17, points: [[14,14],[3,0],[-1,-1],[3,14],[14,14],[-1,-1],[3,0],[14,0]] },
+ '{': { width: 14, points: [[9,25],[7,24],[6,23],[5,21],[5,19],[6,17],[7,16],[8,14],[8,12],[6,10],[-1,-1],[7,24],[6,22],[6,20],[7,18],[8,17],[9,15],[9,13],[8,11],[4,9],[8,7],[9,5],[9,3],[8,1],[7,0],[6,-2],[6,-4],[7,-6],[-1,-1],[6,8],[8,6],[8,4],[7,2],[6,1],[5,-1],[5,-3],[6,-5],[7,-6],[9,-7]] },
+ '|': { width: 8, points: [[4,25],[4,-7]] },
+ '}': { width: 14, points: [[5,25],[7,24],[8,23],[9,21],[9,19],[8,17],[7,16],[6,14],[6,12],[8,10],[-1,-1],[7,24],[8,22],[8,20],[7,18],[6,17],[5,15],[5,13],[6,11],[10,9],[6,7],[5,5],[5,3],[6,1],[7,0],[8,-2],[8,-4],[7,-6],[-1,-1],[8,8],[6,6],[6,4],[7,2],[8,1],[9,-1],[9,-3],[8,-5],[7,-6],[5,-7]] },
+ '~': { width: 24, points: [[3,6],[3,8],[4,11],[6,12],[8,12],[10,11],[14,8],[16,7],[18,7],[20,8],[21,10],[-1,-1],[3,8],[4,10],[6,11],[8,11],[10,10],[14,7],[16,6],[18,6],[20,7],[21,10],[21,12]] }
+ };
+
+ $.jqplot.CanvasFontRenderer = function(options) {
+ options = options || {};
+ if (!options.pt2px) {
+ options.pt2px = 1.5;
+ }
+ $.jqplot.CanvasTextRenderer.call(this, options);
+ };
+
+ $.jqplot.CanvasFontRenderer.prototype = new $.jqplot.CanvasTextRenderer({});
+ $.jqplot.CanvasFontRenderer.prototype.constructor = $.jqplot.CanvasFontRenderer;
+
+ $.jqplot.CanvasFontRenderer.prototype.measure = function(ctx, str)
+ {
+ // var fstyle = this.fontStyle+' '+this.fontVariant+' '+this.fontWeight+' '+this.fontSize+' '+this.fontFamily;
+ var fstyle = this.fontSize+' '+this.fontFamily;
+ ctx.save();
+ ctx.font = fstyle;
+ var w = ctx.measureText(str).width;
+ ctx.restore();
+ return w;
+ };
+
+ $.jqplot.CanvasFontRenderer.prototype.draw = function(ctx, str)
+ {
+ var x = 0;
+ // leave room at bottom for descenders.
+ var y = this.height*0.72;
+ //var y = 12;
+
+ ctx.save();
+ var tx, ty;
+
+ // 1st quadrant
+ if ((-Math.PI/2 <= this.angle && this.angle <= 0) || (Math.PI*3/2 <= this.angle && this.angle <= Math.PI*2)) {
+ tx = 0;
+ ty = -Math.sin(this.angle) * this.width;
+ }
+ // 4th quadrant
+ else if ((0 < this.angle && this.angle <= Math.PI/2) || (-Math.PI*2 <= this.angle && this.angle <= -Math.PI*3/2)) {
+ tx = Math.sin(this.angle) * this.height;
+ ty = 0;
+ }
+ // 2nd quadrant
+ else if ((-Math.PI < this.angle && this.angle < -Math.PI/2) || (Math.PI <= this.angle && this.angle <= Math.PI*3/2)) {
+ tx = -Math.cos(this.angle) * this.width;
+ ty = -Math.sin(this.angle) * this.width - Math.cos(this.angle) * this.height;
+ }
+ // 3rd quadrant
+ else if ((-Math.PI*3/2 < this.angle && this.angle < Math.PI) || (Math.PI/2 < this.angle && this.angle < Math.PI)) {
+ tx = Math.sin(this.angle) * this.height - Math.cos(this.angle)*this.width;
+ ty = -Math.cos(this.angle) * this.height;
+ }
+ ctx.strokeStyle = this.fillStyle;
+ ctx.fillStyle = this.fillStyle;
+ // var fstyle = this.fontStyle+' '+this.fontVariant+' '+this.fontWeight+' '+this.fontSize+' '+this.fontFamily;
+ var fstyle = this.fontSize+' '+this.fontFamily;
+ ctx.font = fstyle;
+ ctx.translate(tx, ty);
+ ctx.rotate(this.angle);
+ ctx.fillText(str, x, y);
+ // ctx.strokeText(str, x, y);
+
+ ctx.restore();
+ };
+
+})(jQuery); \ No newline at end of file
diff --git a/js/jqplot/plugins/jqplot.categoryAxisRenderer.js b/js/jqplot/plugins/jqplot.categoryAxisRenderer.js
new file mode 100644
index 0000000000..4cea2de6ea
--- /dev/null
+++ b/js/jqplot/plugins/jqplot.categoryAxisRenderer.js
@@ -0,0 +1,673 @@
+/**
+ * jqPlot
+ * Pure JavaScript plotting plugin using jQuery
+ *
+ * Version: 1.0.4
+ * Revision: 1121
+ *
+ * Copyright (c) 2009-2012 Chris Leonello
+ * jqPlot is currently available for use in all personal or commercial projects
+ * under both the MIT (http://www.opensource.org/licenses/mit-license.php) and GPL
+ * version 2.0 (http://www.gnu.org/licenses/gpl-2.0.html) licenses. This means that you can
+ * choose the license that best suits your project and use it accordingly.
+ *
+ * Although not required, the author would appreciate an email letting him
+ * know of any substantial use of jqPlot. You can reach the author at:
+ * chris at jqplot dot com or see http://www.jqplot.com/info.php .
+ *
+ * If you are feeling kind and generous, consider supporting the project by
+ * making a donation at: http://www.jqplot.com/donate.php .
+ *
+ * sprintf functions contained in jqplot.sprintf.js by Ash Searle:
+ *
+ * version 2007.04.27
+ * author Ash Searle
+ * http://hexmen.com/blog/2007/03/printf-sprintf/
+ * http://hexmen.com/js/sprintf.js
+ * The author (Ash Searle) has placed this code in the public domain:
+ * "This code is unrestricted: you are free to use it however you like."
+ *
+ */
+(function($) {
+ /**
+ * class: $.jqplot.CategoryAxisRenderer
+ * A plugin for jqPlot to render a category style axis, with equal pixel spacing between y data values of a series.
+ *
+ * To use this renderer, include the plugin in your source
+ * > <script type="text/javascript" language="javascript" src="plugins/jqplot.categoryAxisRenderer.js"></script>
+ *
+ * and supply the appropriate options to your plot
+ *
+ * > {axes:{xaxis:{renderer:$.jqplot.CategoryAxisRenderer}}}
+ **/
+ $.jqplot.CategoryAxisRenderer = function(options) {
+ $.jqplot.LinearAxisRenderer.call(this);
+ // prop: sortMergedLabels
+ // True to sort tick labels when labels are created by merging
+ // x axis values from multiple series. That is, say you have
+ // two series like:
+ // > line1 = [[2006, 4], [2008, 9], [2009, 16]];
+ // > line2 = [[2006, 3], [2007, 7], [2008, 6]];
+ // If no label array is specified, tick labels will be collected
+ // from the x values of the series. With sortMergedLabels
+ // set to true, tick labels will be:
+ // > [2006, 2007, 2008, 2009]
+ // With sortMergedLabels set to false, tick labels will be:
+ // > [2006, 2008, 2009, 2007]
+ //
+ // Note, this property is specified on the renderOptions for the
+ // axes when creating a plot:
+ // > axes:{xaxis:{renderer:$.jqplot.CategoryAxisRenderer, rendererOptions:{sortMergedLabels:true}}}
+ this.sortMergedLabels = false;
+ };
+
+ $.jqplot.CategoryAxisRenderer.prototype = new $.jqplot.LinearAxisRenderer();
+ $.jqplot.CategoryAxisRenderer.prototype.constructor = $.jqplot.CategoryAxisRenderer;
+
+ $.jqplot.CategoryAxisRenderer.prototype.init = function(options){
+ this.groups = 1;
+ this.groupLabels = [];
+ this._groupLabels = [];
+ this._grouped = false;
+ this._barsPerGroup = null;
+ this.reverse = false;
+ // prop: tickRenderer
+ // A class of a rendering engine for creating the ticks labels displayed on the plot,
+ // See <$.jqplot.AxisTickRenderer>.
+ // this.tickRenderer = $.jqplot.AxisTickRenderer;
+ // this.labelRenderer = $.jqplot.AxisLabelRenderer;
+ $.extend(true, this, {tickOptions:{formatString:'%d'}}, options);
+ var db = this._dataBounds;
+ // Go through all the series attached to this axis and find
+ // the min/max bounds for this axis.
+ for (var i=0; i<this._series.length; i++) {
+ var s = this._series[i];
+ if (s.groups) {
+ this.groups = s.groups;
+ }
+ var d = s.data;
+
+ for (var j=0; j<d.length; j++) {
+ if (this.name == 'xaxis' || this.name == 'x2axis') {
+ if (d[j][0] < db.min || db.min == null) {
+ db.min = d[j][0];
+ }
+ if (d[j][0] > db.max || db.max == null) {
+ db.max = d[j][0];
+ }
+ }
+ else {
+ if (d[j][1] < db.min || db.min == null) {
+ db.min = d[j][1];
+ }
+ if (d[j][1] > db.max || db.max == null) {
+ db.max = d[j][1];
+ }
+ }
+ }
+ }
+
+ if (this.groupLabels.length) {
+ this.groups = this.groupLabels.length;
+ }
+ };
+
+
+ $.jqplot.CategoryAxisRenderer.prototype.createTicks = function() {
+ // we're are operating on an axis here
+ var ticks = this._ticks;
+ var userTicks = this.ticks;
+ var name = this.name;
+ // databounds were set on axis initialization.
+ var db = this._dataBounds;
+ var dim, interval;
+ var min, max;
+ var pos1, pos2;
+ var tt, i;
+
+ // if we already have ticks, use them.
+ if (userTicks.length) {
+ // adjust with blanks if we have groups
+ if (this.groups > 1 && !this._grouped) {
+ var l = userTicks.length;
+ var skip = parseInt(l/this.groups, 10);
+ var count = 0;
+ for (var i=skip; i<l; i+=skip) {
+ userTicks.splice(i+count, 0, ' ');
+ count++;
+ }
+ this._grouped = true;
+ }
+ this.min = 0.5;
+ this.max = userTicks.length + 0.5;
+ var range = this.max - this.min;
+ this.numberTicks = 2*userTicks.length + 1;
+ for (i=0; i<userTicks.length; i++){
+ tt = this.min + 2 * i * range / (this.numberTicks-1);
+ // need a marker before and after the tick
+ var t = new this.tickRenderer(this.tickOptions);
+ t.showLabel = false;
+ // t.showMark = true;
+ t.setTick(tt, this.name);
+ this._ticks.push(t);
+ var t = new this.tickRenderer(this.tickOptions);
+ t.label = userTicks[i];
+ // t.showLabel = true;
+ t.showMark = false;
+ t.showGridline = false;
+ t.setTick(tt+0.5, this.name);
+ this._ticks.push(t);
+ }
+ // now add the last tick at the end
+ var t = new this.tickRenderer(this.tickOptions);
+ t.showLabel = false;
+ // t.showMark = true;
+ t.setTick(tt+1, this.name);
+ this._ticks.push(t);
+ }
+
+ // we don't have any ticks yet, let's make some!
+ else {
+ if (name == 'xaxis' || name == 'x2axis') {
+ dim = this._plotDimensions.width;
+ }
+ else {
+ dim = this._plotDimensions.height;
+ }
+
+ // if min, max and number of ticks specified, user can't specify interval.
+ if (this.min != null && this.max != null && this.numberTicks != null) {
+ this.tickInterval = null;
+ }
+
+ // if max, min, and interval specified and interval won't fit, ignore interval.
+ if (this.min != null && this.max != null && this.tickInterval != null) {
+ if (parseInt((this.max-this.min)/this.tickInterval, 10) != (this.max-this.min)/this.tickInterval) {
+ this.tickInterval = null;
+ }
+ }
+
+ // find out how many categories are in the lines and collect labels
+ var labels = [];
+ var numcats = 0;
+ var min = 0.5;
+ var max, val;
+ var isMerged = false;
+ for (var i=0; i<this._series.length; i++) {
+ var s = this._series[i];
+ for (var j=0; j<s.data.length; j++) {
+ if (this.name == 'xaxis' || this.name == 'x2axis') {
+ val = s.data[j][0];
+ }
+ else {
+ val = s.data[j][1];
+ }
+ if ($.inArray(val, labels) == -1) {
+ isMerged = true;
+ numcats += 1;
+ labels.push(val);
+ }
+ }
+ }
+
+ if (isMerged && this.sortMergedLabels) {
+ labels.sort(function(a,b) { return a - b; });
+ }
+
+ // keep a reference to these tick labels to use for redrawing plot (see bug #57)
+ this.ticks = labels;
+
+ // now bin the data values to the right lables.
+ for (var i=0; i<this._series.length; i++) {
+ var s = this._series[i];
+ for (var j=0; j<s.data.length; j++) {
+ if (this.name == 'xaxis' || this.name == 'x2axis') {
+ val = s.data[j][0];
+ }
+ else {
+ val = s.data[j][1];
+ }
+ // for category axis, force the values into category bins.
+ // we should have the value in the label array now.
+ var idx = $.inArray(val, labels)+1;
+ if (this.name == 'xaxis' || this.name == 'x2axis') {
+ s.data[j][0] = idx;
+ }
+ else {
+ s.data[j][1] = idx;
+ }
+ }
+ }
+
+ // adjust with blanks if we have groups
+ if (this.groups > 1 && !this._grouped) {
+ var l = labels.length;
+ var skip = parseInt(l/this.groups, 10);
+ var count = 0;
+ for (var i=skip; i<l; i+=skip+1) {
+ labels[i] = ' ';
+ }
+ this._grouped = true;
+ }
+
+ max = numcats + 0.5;
+ if (this.numberTicks == null) {
+ this.numberTicks = 2*numcats + 1;
+ }
+
+ var range = max - min;
+ this.min = min;
+ this.max = max;
+ var track = 0;
+
+ // todo: adjust this so more ticks displayed.
+ var maxVisibleTicks = parseInt(3+dim/10, 10);
+ var skip = parseInt(numcats/maxVisibleTicks, 10);
+
+ if (this.tickInterval == null) {
+
+ this.tickInterval = range / (this.numberTicks-1);
+
+ }
+ // if tickInterval is specified, we will ignore any computed maximum.
+ for (var i=0; i<this.numberTicks; i++){
+ tt = this.min + i * this.tickInterval;
+ var t = new this.tickRenderer(this.tickOptions);
+ // if even tick, it isn't a category, it's a divider
+ if (i/2 == parseInt(i/2, 10)) {
+ t.showLabel = false;
+ t.showMark = true;
+ }
+ else {
+ if (skip>0 && track<skip) {
+ t.showLabel = false;
+ track += 1;
+ }
+ else {
+ t.showLabel = true;
+ track = 0;
+ }
+ t.label = t.formatter(t.formatString, labels[(i-1)/2]);
+ t.showMark = false;
+ t.showGridline = false;
+ }
+ t.setTick(tt, this.name);
+ this._ticks.push(t);
+ }
+ }
+
+ };
+
+ // called with scope of axis
+ $.jqplot.CategoryAxisRenderer.prototype.draw = function(ctx, plot) {
+ if (this.show) {
+ // populate the axis label and value properties.
+ // createTicks is a method on the renderer, but
+ // call it within the scope of the axis.
+ this.renderer.createTicks.call(this);
+ // fill a div with axes labels in the right direction.
+ // Need to pregenerate each axis to get it's bounds and
+ // position it and the labels correctly on the plot.
+ var dim=0;
+ var temp;
+ // Added for theming.
+ if (this._elem) {
+ // this._elem.empty();
+ // Memory Leaks patch
+ this._elem.emptyForce();
+ }
+
+ this._elem = this._elem || $('<div class="jqplot-axis jqplot-'+this.name+'" style="position:absolute;"></div>');
+
+ if (this.name == 'xaxis' || this.name == 'x2axis') {
+ this._elem.width(this._plotDimensions.width);
+ }
+ else {
+ this._elem.height(this._plotDimensions.height);
+ }
+
+ // create a _label object.
+ this.labelOptions.axis = this.name;
+ this._label = new this.labelRenderer(this.labelOptions);
+ if (this._label.show) {
+ var elem = this._label.draw(ctx, plot);
+ elem.appendTo(this._elem);
+ }
+
+ var t = this._ticks;
+ for (var i=0; i<t.length; i++) {
+ var tick = t[i];
+ if (tick.showLabel && (!tick.isMinorTick || this.showMinorTicks)) {
+ var elem = tick.draw(ctx, plot);
+ elem.appendTo(this._elem);
+ }
+ }
+
+ this._groupLabels = [];
+ // now make group labels
+ for (var i=0; i<this.groupLabels.length; i++)
+ {
+ var elem = $('<div style="position:absolute;" class="jqplot-'+this.name+'-groupLabel"></div>');
+ elem.html(this.groupLabels[i]);
+ this._groupLabels.push(elem);
+ elem.appendTo(this._elem);
+ }
+ }
+ return this._elem;
+ };
+
+ // called with scope of axis
+ $.jqplot.CategoryAxisRenderer.prototype.set = function() {
+ var dim = 0;
+ var temp;
+ var w = 0;
+ var h = 0;
+ var lshow = (this._label == null) ? false : this._label.show;
+ if (this.show) {
+ var t = this._ticks;
+ for (var i=0; i<t.length; i++) {
+ var tick = t[i];
+ if (tick.showLabel && (!tick.isMinorTick || this.showMinorTicks)) {
+ if (this.name == 'xaxis' || this.name == 'x2axis') {
+ temp = tick._elem.outerHeight(true);
+ }
+ else {
+ temp = tick._elem.outerWidth(true);
+ }
+ if (temp > dim) {
+ dim = temp;
+ }
+ }
+ }
+
+ var dim2 = 0;
+ for (var i=0; i<this._groupLabels.length; i++) {
+ var l = this._groupLabels[i];
+ if (this.name == 'xaxis' || this.name == 'x2axis') {
+ temp = l.outerHeight(true);
+ }
+ else {
+ temp = l.outerWidth(true);
+ }
+ if (temp > dim2) {
+ dim2 = temp;
+ }
+ }
+
+ if (lshow) {
+ w = this._label._elem.outerWidth(true);
+ h = this._label._elem.outerHeight(true);
+ }
+ if (this.name == 'xaxis') {
+ dim += dim2 + h;
+ this._elem.css({'height':dim+'px', left:'0px', bottom:'0px'});
+ }
+ else if (this.name == 'x2axis') {
+ dim += dim2 + h;
+ this._elem.css({'height':dim+'px', left:'0px', top:'0px'});
+ }
+ else if (this.name == 'yaxis') {
+ dim += dim2 + w;
+ this._elem.css({'width':dim+'px', left:'0px', top:'0px'});
+ if (lshow && this._label.constructor == $.jqplot.AxisLabelRenderer) {
+ this._label._elem.css('width', w+'px');
+ }
+ }
+ else {
+ dim += dim2 + w;
+ this._elem.css({'width':dim+'px', right:'0px', top:'0px'});
+ if (lshow && this._label.constructor == $.jqplot.AxisLabelRenderer) {
+ this._label._elem.css('width', w+'px');
+ }
+ }
+ }
+ };
+
+ // called with scope of axis
+ $.jqplot.CategoryAxisRenderer.prototype.pack = function(pos, offsets) {
+ var ticks = this._ticks;
+ var max = this.max;
+ var min = this.min;
+ var offmax = offsets.max;
+ var offmin = offsets.min;
+ var lshow = (this._label == null) ? false : this._label.show;
+ var i;
+
+ for (var p in pos) {
+ this._elem.css(p, pos[p]);
+ }
+
+ this._offsets = offsets;
+ // pixellength will be + for x axes and - for y axes becasue pixels always measured from top left.
+ var pixellength = offmax - offmin;
+ var unitlength = max - min;
+
+ if (!this.reverse) {
+ // point to unit and unit to point conversions references to Plot DOM element top left corner.
+
+ this.u2p = function(u){
+ return (u - min) * pixellength / unitlength + offmin;
+ };
+
+ this.p2u = function(p){
+ return (p - offmin) * unitlength / pixellength + min;
+ };
+
+ if (this.name == 'xaxis' || this.name == 'x2axis'){
+ this.series_u2p = function(u){
+ return (u - min) * pixellength / unitlength;
+ };
+ this.series_p2u = function(p){
+ return p * unitlength / pixellength + min;
+ };
+ }
+
+ else {
+ this.series_u2p = function(u){
+ return (u - max) * pixellength / unitlength;
+ };
+ this.series_p2u = function(p){
+ return p * unitlength / pixellength + max;
+ };
+ }
+ }
+
+ else {
+ // point to unit and unit to point conversions references to Plot DOM element top left corner.
+
+ this.u2p = function(u){
+ return offmin + (max - u) * pixellength / unitlength;
+ };
+
+ this.p2u = function(p){
+ return min + (p - offmin) * unitlength / pixellength;
+ };
+
+ if (this.name == 'xaxis' || this.name == 'x2axis'){
+ this.series_u2p = function(u){
+ return (max - u) * pixellength / unitlength;
+ };
+ this.series_p2u = function(p){
+ return p * unitlength / pixellength + max;
+ };
+ }
+
+ else {
+ this.series_u2p = function(u){
+ return (min - u) * pixellength / unitlength;
+ };
+ this.series_p2u = function(p){
+ return p * unitlength / pixellength + min;
+ };
+ }
+
+ }
+
+
+ if (this.show) {
+ if (this.name == 'xaxis' || this.name == 'x2axis') {
+ for (i=0; i<ticks.length; i++) {
+ var t = ticks[i];
+ if (t.show && t.showLabel) {
+ var shim;
+
+ if (t.constructor == $.jqplot.CanvasAxisTickRenderer && t.angle) {
+ // will need to adjust auto positioning based on which axis this is.
+ var temp = (this.name == 'xaxis') ? 1 : -1;
+ switch (t.labelPosition) {
+ case 'auto':
+ // position at end
+ if (temp * t.angle < 0) {
+ shim = -t.getWidth() + t._textRenderer.height * Math.sin(-t._textRenderer.angle) / 2;
+ }
+ // position at start
+ else {
+ shim = -t._textRenderer.height * Math.sin(t._textRenderer.angle) / 2;
+ }
+ break;
+ case 'end':
+ shim = -t.getWidth() + t._textRenderer.height * Math.sin(-t._textRenderer.angle) / 2;
+ break;
+ case 'start':
+ shim = -t._textRenderer.height * Math.sin(t._textRenderer.angle) / 2;
+ break;
+ case 'middle':
+ shim = -t.getWidth()/2 + t._textRenderer.height * Math.sin(-t._textRenderer.angle) / 2;
+ break;
+ default:
+ shim = -t.getWidth()/2 + t._textRenderer.height * Math.sin(-t._textRenderer.angle) / 2;
+ break;
+ }
+ }
+ else {
+ shim = -t.getWidth()/2;
+ }
+ var val = this.u2p(t.value) + shim + 'px';
+ t._elem.css('left', val);
+ t.pack();
+ }
+ }
+
+ var labeledge=['bottom', 0];
+ if (lshow) {
+ var w = this._label._elem.outerWidth(true);
+ this._label._elem.css('left', offmin + pixellength/2 - w/2 + 'px');
+ if (this.name == 'xaxis') {
+ this._label._elem.css('bottom', '0px');
+ labeledge = ['bottom', this._label._elem.outerHeight(true)];
+ }
+ else {
+ this._label._elem.css('top', '0px');
+ labeledge = ['top', this._label._elem.outerHeight(true)];
+ }
+ this._label.pack();
+ }
+
+ // draw the group labels
+ var step = parseInt(this._ticks.length/this.groups, 10);
+ for (i=0; i<this._groupLabels.length; i++) {
+ var mid = 0;
+ var count = 0;
+ for (var j=i*step; j<=(i+1)*step; j++) {
+ if (this._ticks[j]._elem && this._ticks[j].label != " ") {
+ var t = this._ticks[j]._elem;
+ var p = t.position();
+ mid += p.left + t.outerWidth(true)/2;
+ count++;
+ }
+ }
+ mid = mid/count;
+ this._groupLabels[i].css({'left':(mid - this._groupLabels[i].outerWidth(true)/2)});
+ this._groupLabels[i].css(labeledge[0], labeledge[1]);
+ }
+ }
+ else {
+ for (i=0; i<ticks.length; i++) {
+ var t = ticks[i];
+ if (t.show && t.showLabel) {
+ var shim;
+ if (t.constructor == $.jqplot.CanvasAxisTickRenderer && t.angle) {
+ // will need to adjust auto positioning based on which axis this is.
+ var temp = (this.name == 'yaxis') ? 1 : -1;
+ switch (t.labelPosition) {
+ case 'auto':
+ // position at end
+ case 'end':
+ if (temp * t.angle < 0) {
+ shim = -t._textRenderer.height * Math.cos(-t._textRenderer.angle) / 2;
+ }
+ else {
+ shim = -t.getHeight() + t._textRenderer.height * Math.cos(t._textRenderer.angle) / 2;
+ }
+ break;
+ case 'start':
+ if (t.angle > 0) {
+ shim = -t._textRenderer.height * Math.cos(-t._textRenderer.angle) / 2;
+ }
+ else {
+ shim = -t.getHeight() + t._textRenderer.height * Math.cos(t._textRenderer.angle) / 2;
+ }
+ break;
+ case 'middle':
+ // if (t.angle > 0) {
+ // shim = -t.getHeight()/2 + t._textRenderer.height * Math.sin(-t._textRenderer.angle) / 2;
+ // }
+ // else {
+ // shim = -t.getHeight()/2 - t._textRenderer.height * Math.sin(t._textRenderer.angle) / 2;
+ // }
+ shim = -t.getHeight()/2;
+ break;
+ default:
+ shim = -t.getHeight()/2;
+ break;
+ }
+ }
+ else {
+ shim = -t.getHeight()/2;
+ }
+
+ var val = this.u2p(t.value) + shim + 'px';
+ t._elem.css('top', val);
+ t.pack();
+ }
+ }
+
+ var labeledge=['left', 0];
+ if (lshow) {
+ var h = this._label._elem.outerHeight(true);
+ this._label._elem.css('top', offmax - pixellength/2 - h/2 + 'px');
+ if (this.name == 'yaxis') {
+ this._label._elem.css('left', '0px');
+ labeledge = ['left', this._label._elem.outerWidth(true)];
+ }
+ else {
+ this._label._elem.css('right', '0px');
+ labeledge = ['right', this._label._elem.outerWidth(true)];
+ }
+ this._label.pack();
+ }
+
+ // draw the group labels, position top here, do left after label position.
+ var step = parseInt(this._ticks.length/this.groups, 10);
+ for (i=0; i<this._groupLabels.length; i++) {
+ var mid = 0;
+ var count = 0;
+ for (var j=i*step; j<=(i+1)*step; j++) {
+ if (this._ticks[j]._elem && this._ticks[j].label != " ") {
+ var t = this._ticks[j]._elem;
+ var p = t.position();
+ mid += p.top + t.outerHeight()/2;
+ count++;
+ }
+ }
+ mid = mid/count;
+ this._groupLabels[i].css({'top':mid - this._groupLabels[i].outerHeight()/2});
+ this._groupLabels[i].css(labeledge[0], labeledge[1]);
+
+ }
+ }
+ }
+ };
+
+
+})(jQuery); \ No newline at end of file
diff --git a/js/jqplot/plugins/jqplot.cursor.js b/js/jqplot/plugins/jqplot.cursor.js
new file mode 100644
index 0000000000..0e583682a5
--- /dev/null
+++ b/js/jqplot/plugins/jqplot.cursor.js
@@ -0,0 +1,1108 @@
+/**
+ * jqPlot
+ * Pure JavaScript plotting plugin using jQuery
+ *
+ * Version: 1.0.4
+ * Revision: 1121
+ *
+ * Copyright (c) 2009-2012 Chris Leonello
+ * jqPlot is currently available for use in all personal or commercial projects
+ * under both the MIT (http://www.opensource.org/licenses/mit-license.php) and GPL
+ * version 2.0 (http://www.gnu.org/licenses/gpl-2.0.html) licenses. This means that you can
+ * choose the license that best suits your project and use it accordingly.
+ *
+ * Although not required, the author would appreciate an email letting him
+ * know of any substantial use of jqPlot. You can reach the author at:
+ * chris at jqplot dot com or see http://www.jqplot.com/info.php .
+ *
+ * If you are feeling kind and generous, consider supporting the project by
+ * making a donation at: http://www.jqplot.com/donate.php .
+ *
+ * sprintf functions contained in jqplot.sprintf.js by Ash Searle:
+ *
+ * version 2007.04.27
+ * author Ash Searle
+ * http://hexmen.com/blog/2007/03/printf-sprintf/
+ * http://hexmen.com/js/sprintf.js
+ * The author (Ash Searle) has placed this code in the public domain:
+ * "This code is unrestricted: you are free to use it however you like."
+ *
+ */
+(function($) {
+
+ /**
+ * Class: $.jqplot.Cursor
+ * Plugin class representing the cursor as displayed on the plot.
+ */
+ $.jqplot.Cursor = function(options) {
+ // Group: Properties
+ //
+ // prop: style
+ // CSS spec for cursor style
+ this.style = 'crosshair';
+ this.previousCursor = 'auto';
+ // prop: show
+ // wether to show the cursor or not.
+ this.show = $.jqplot.config.enablePlugins;
+ // prop: showTooltip
+ // show a cursor position tooltip. Location of the tooltip
+ // will be controlled by followMouse and tooltipLocation.
+ this.showTooltip = true;
+ // prop: followMouse
+ // Tooltip follows the mouse, it is not at a fixed location.
+ // Tooltip will show on the grid at the location given by
+ // tooltipLocation, offset from the grid edge by tooltipOffset.
+ this.followMouse = false;
+ // prop: tooltipLocation
+ // Where to position tooltip. If followMouse is true, this is
+ // relative to the cursor, otherwise, it is relative to the grid.
+ // One of 'n', 'ne', 'e', 'se', 's', 'sw', 'w', 'nw'
+ this.tooltipLocation = 'se';
+ // prop: tooltipOffset
+ // Pixel offset of tooltip from the grid boudaries or cursor center.
+ this.tooltipOffset = 6;
+ // prop: showTooltipGridPosition
+ // show the grid pixel coordinates of the mouse.
+ this.showTooltipGridPosition = false;
+ // prop: showTooltipUnitPosition
+ // show the unit (data) coordinates of the mouse.
+ this.showTooltipUnitPosition = true;
+ // prop: showTooltipDataPosition
+ // Used with showVerticalLine to show intersecting data points in the tooltip.
+ this.showTooltipDataPosition = false;
+ // prop: tooltipFormatString
+ // sprintf format string for the tooltip.
+ // Uses Ash Searle's javascript sprintf implementation
+ // found here: http://hexmen.com/blog/2007/03/printf-sprintf/
+ // See http://perldoc.perl.org/functions/sprintf.html for reference
+ // Note, if showTooltipDataPosition is true, the default tooltipFormatString
+ // will be set to the cursorLegendFormatString, not the default given here.
+ this.tooltipFormatString = '%.4P, %.4P';
+ // prop: useAxesFormatters
+ // Use the x and y axes formatters to format the text in the tooltip.
+ this.useAxesFormatters = true;
+ // prop: tooltipAxisGroups
+ // Show position for the specified axes.
+ // This is an array like [['xaxis', 'yaxis'], ['xaxis', 'y2axis']]
+ // Default is to compute automatically for all visible axes.
+ this.tooltipAxisGroups = [];
+ // prop: zoom
+ // Enable plot zooming.
+ this.zoom = false;
+ // zoomProxy and zoomTarget properties are not directly set by user.
+ // They Will be set through call to zoomProxy method.
+ this.zoomProxy = false;
+ this.zoomTarget = false;
+ // prop: looseZoom
+ // Will expand zoom range to provide more rounded tick values.
+ // Works only with linear, log and date axes.
+ this.looseZoom = true;
+ // prop: clickReset
+ // Will reset plot zoom if single click on plot without drag.
+ this.clickReset = false;
+ // prop: dblClickReset
+ // Will reset plot zoom if double click on plot without drag.
+ this.dblClickReset = true;
+ // prop: showVerticalLine
+ // draw a vertical line across the plot which follows the cursor.
+ // When the line is near a data point, a special legend and/or tooltip can
+ // be updated with the data values.
+ this.showVerticalLine = false;
+ // prop: showHorizontalLine
+ // draw a horizontal line across the plot which follows the cursor.
+ this.showHorizontalLine = false;
+ // prop: constrainZoomTo
+ // 'none', 'x' or 'y'
+ this.constrainZoomTo = 'none';
+ // // prop: autoscaleConstraint
+ // // when a constrained axis is specified, true will
+ // // auatoscale the adjacent axis.
+ // this.autoscaleConstraint = true;
+ this.shapeRenderer = new $.jqplot.ShapeRenderer();
+ this._zoom = {start:[], end:[], started: false, zooming:false, isZoomed:false, axes:{start:{}, end:{}}, gridpos:{}, datapos:{}};
+ this._tooltipElem;
+ this.zoomCanvas;
+ this.cursorCanvas;
+ // prop: intersectionThreshold
+ // pixel distance from data point or marker to consider cursor lines intersecting with point.
+ // If data point markers are not shown, this should be >= 1 or will often miss point intersections.
+ this.intersectionThreshold = 2;
+ // prop: showCursorLegend
+ // Replace the plot legend with an enhanced legend displaying intersection information.
+ this.showCursorLegend = false;
+ // prop: cursorLegendFormatString
+ // Format string used in the cursor legend. If showTooltipDataPosition is true,
+ // this will also be the default format string used by tooltipFormatString.
+ this.cursorLegendFormatString = $.jqplot.Cursor.cursorLegendFormatString;
+ // whether the cursor is over the grid or not.
+ this._oldHandlers = {onselectstart: null, ondrag: null, onmousedown: null};
+ // prop: constrainOutsideZoom
+ // True to limit actual zoom area to edges of grid, even when zooming
+ // outside of plot area. That is, can't zoom out by mousing outside plot.
+ this.constrainOutsideZoom = true;
+ // prop: showTooltipOutsideZoom
+ // True will keep updating the tooltip when zooming of the grid.
+ this.showTooltipOutsideZoom = false;
+ // true if mouse is over grid, false if not.
+ this.onGrid = false;
+ $.extend(true, this, options);
+ };
+
+ $.jqplot.Cursor.cursorLegendFormatString = '%s x:%s, y:%s';
+
+ // called with scope of plot
+ $.jqplot.Cursor.init = function (target, data, opts){
+ // add a cursor attribute to the plot
+ var options = opts || {};
+ this.plugins.cursor = new $.jqplot.Cursor(options.cursor);
+ var c = this.plugins.cursor;
+
+ if (c.show) {
+ $.jqplot.eventListenerHooks.push(['jqplotMouseEnter', handleMouseEnter]);
+ $.jqplot.eventListenerHooks.push(['jqplotMouseLeave', handleMouseLeave]);
+ $.jqplot.eventListenerHooks.push(['jqplotMouseMove', handleMouseMove]);
+
+ if (c.showCursorLegend) {
+ opts.legend = opts.legend || {};
+ opts.legend.renderer = $.jqplot.CursorLegendRenderer;
+ opts.legend.formatString = this.plugins.cursor.cursorLegendFormatString;
+ opts.legend.show = true;
+ }
+
+ if (c.zoom) {
+ $.jqplot.eventListenerHooks.push(['jqplotMouseDown', handleMouseDown]);
+
+ if (c.clickReset) {
+ $.jqplot.eventListenerHooks.push(['jqplotClick', handleClick]);
+ }
+
+ if (c.dblClickReset) {
+ $.jqplot.eventListenerHooks.push(['jqplotDblClick', handleDblClick]);
+ }
+ }
+
+ this.resetZoom = function() {
+ var axes = this.axes;
+ if (!c.zoomProxy) {
+ for (var ax in axes) {
+ axes[ax].reset();
+ axes[ax]._ticks = [];
+ // fake out tick creation algorithm to make sure original auto
+ // computed format string is used if _overrideFormatString is true
+ if (c._zoom.axes[ax] !== undefined) {
+ axes[ax]._autoFormatString = c._zoom.axes[ax].tickFormatString;
+ }
+ }
+ this.redraw();
+ }
+ else {
+ var ctx = this.plugins.cursor.zoomCanvas._ctx;
+ ctx.clearRect(0,0,ctx.canvas.width, ctx.canvas.height);
+ ctx = null;
+ }
+ this.plugins.cursor._zoom.isZoomed = false;
+ this.target.trigger('jqplotResetZoom', [this, this.plugins.cursor]);
+ };
+
+
+ if (c.showTooltipDataPosition) {
+ c.showTooltipUnitPosition = false;
+ c.showTooltipGridPosition = false;
+ if (options.cursor.tooltipFormatString == undefined) {
+ c.tooltipFormatString = $.jqplot.Cursor.cursorLegendFormatString;
+ }
+ }
+ }
+ };
+
+ // called with context of plot
+ $.jqplot.Cursor.postDraw = function() {
+ var c = this.plugins.cursor;
+
+ // Memory Leaks patch
+ if (c.zoomCanvas) {
+ c.zoomCanvas.resetCanvas();
+ c.zoomCanvas = null;
+ }
+
+ if (c.cursorCanvas) {
+ c.cursorCanvas.resetCanvas();
+ c.cursorCanvas = null;
+ }
+
+ if (c._tooltipElem) {
+ c._tooltipElem.emptyForce();
+ c._tooltipElem = null;
+ }
+
+
+ if (c.zoom) {
+ c.zoomCanvas = new $.jqplot.GenericCanvas();
+ this.eventCanvas._elem.before(c.zoomCanvas.createElement(this._gridPadding, 'jqplot-zoom-canvas', this._plotDimensions, this));
+ c.zoomCanvas.setContext();
+ }
+
+ var elem = document.createElement('div');
+ c._tooltipElem = $(elem);
+ elem = null;
+ c._tooltipElem.addClass('jqplot-cursor-tooltip');
+ c._tooltipElem.css({position:'absolute', display:'none'});
+
+
+ if (c.zoomCanvas) {
+ c.zoomCanvas._elem.before(c._tooltipElem);
+ }
+
+ else {
+ this.eventCanvas._elem.before(c._tooltipElem);
+ }
+
+ if (c.showVerticalLine || c.showHorizontalLine) {
+ c.cursorCanvas = new $.jqplot.GenericCanvas();
+ this.eventCanvas._elem.before(c.cursorCanvas.createElement(this._gridPadding, 'jqplot-cursor-canvas', this._plotDimensions, this));
+ c.cursorCanvas.setContext();
+ }
+
+ // if we are showing the positions in unit coordinates, and no axes groups
+ // were specified, create a default set.
+ if (c.showTooltipUnitPosition){
+ if (c.tooltipAxisGroups.length === 0) {
+ var series = this.series;
+ var s;
+ var temp = [];
+ for (var i=0; i<series.length; i++) {
+ s = series[i];
+ var ax = s.xaxis+','+s.yaxis;
+ if ($.inArray(ax, temp) == -1) {
+ temp.push(ax);
+ }
+ }
+ for (var i=0; i<temp.length; i++) {
+ c.tooltipAxisGroups.push(temp[i].split(','));
+ }
+ }
+ }
+ };
+
+ // Group: methods
+ //
+ // method: $.jqplot.Cursor.zoomProxy
+ // links targetPlot to controllerPlot so that plot zooming of
+ // targetPlot will be controlled by zooming on the controllerPlot.
+ // controllerPlot will not actually zoom, but acts as an
+ // overview plot. Note, the zoom options must be set to true for
+ // zoomProxy to work.
+ $.jqplot.Cursor.zoomProxy = function(targetPlot, controllerPlot) {
+ var tc = targetPlot.plugins.cursor;
+ var cc = controllerPlot.plugins.cursor;
+ tc.zoomTarget = true;
+ tc.zoom = true;
+ tc.style = 'auto';
+ tc.dblClickReset = false;
+ cc.zoom = true;
+ cc.zoomProxy = true;
+
+ controllerPlot.target.bind('jqplotZoom', plotZoom);
+ controllerPlot.target.bind('jqplotResetZoom', plotReset);
+
+ function plotZoom(ev, gridpos, datapos, plot, cursor) {
+ tc.doZoom(gridpos, datapos, targetPlot, cursor);
+ }
+
+ function plotReset(ev, plot, cursor) {
+ targetPlot.resetZoom();
+ }
+ };
+
+ $.jqplot.Cursor.prototype.resetZoom = function(plot, cursor) {
+ var axes = plot.axes;
+ var cax = cursor._zoom.axes;
+ if (!plot.plugins.cursor.zoomProxy && cursor._zoom.isZoomed) {
+ for (var ax in axes) {
+ // axes[ax]._ticks = [];
+ // axes[ax].min = cax[ax].min;
+ // axes[ax].max = cax[ax].max;
+ // axes[ax].numberTicks = cax[ax].numberTicks;
+ // axes[ax].tickInterval = cax[ax].tickInterval;
+ // // for date axes
+ // axes[ax].daTickInterval = cax[ax].daTickInterval;
+ axes[ax].reset();
+ axes[ax]._ticks = [];
+ // fake out tick creation algorithm to make sure original auto
+ // computed format string is used if _overrideFormatString is true
+ axes[ax]._autoFormatString = cax[ax].tickFormatString;
+ }
+ plot.redraw();
+ cursor._zoom.isZoomed = false;
+ }
+ else {
+ var ctx = cursor.zoomCanvas._ctx;
+ ctx.clearRect(0,0,ctx.canvas.width, ctx.canvas.height);
+ ctx = null;
+ }
+ plot.target.trigger('jqplotResetZoom', [plot, cursor]);
+ };
+
+ $.jqplot.Cursor.resetZoom = function(plot) {
+ plot.resetZoom();
+ };
+
+ $.jqplot.Cursor.prototype.doZoom = function (gridpos, datapos, plot, cursor) {
+ var c = cursor;
+ var axes = plot.axes;
+ var zaxes = c._zoom.axes;
+ var start = zaxes.start;
+ var end = zaxes.end;
+ var min, max, dp, span,
+ newmin, newmax, curax, _numberTicks, ret;
+ var ctx = plot.plugins.cursor.zoomCanvas._ctx;
+ // don't zoom if zoom area is too small (in pixels)
+ if ((c.constrainZoomTo == 'none' && Math.abs(gridpos.x - c._zoom.start[0]) > 6 && Math.abs(gridpos.y - c._zoom.start[1]) > 6) || (c.constrainZoomTo == 'x' && Math.abs(gridpos.x - c._zoom.start[0]) > 6) || (c.constrainZoomTo == 'y' && Math.abs(gridpos.y - c._zoom.start[1]) > 6)) {
+ if (!plot.plugins.cursor.zoomProxy) {
+ for (var ax in datapos) {
+ // make a copy of the original axes to revert back.
+ if (c._zoom.axes[ax] == undefined) {
+ c._zoom.axes[ax] = {};
+ c._zoom.axes[ax].numberTicks = axes[ax].numberTicks;
+ c._zoom.axes[ax].tickInterval = axes[ax].tickInterval;
+ // for date axes...
+ c._zoom.axes[ax].daTickInterval = axes[ax].daTickInterval;
+ c._zoom.axes[ax].min = axes[ax].min;
+ c._zoom.axes[ax].max = axes[ax].max;
+ c._zoom.axes[ax].tickFormatString = (axes[ax].tickOptions != null) ? axes[ax].tickOptions.formatString : '';
+ }
+
+
+ if ((c.constrainZoomTo == 'none') || (c.constrainZoomTo == 'x' && ax.charAt(0) == 'x') || (c.constrainZoomTo == 'y' && ax.charAt(0) == 'y')) {
+ dp = datapos[ax];
+ if (dp != null) {
+ if (dp > start[ax]) {
+ newmin = start[ax];
+ newmax = dp;
+ }
+ else {
+ span = start[ax] - dp;
+ newmin = dp;
+ newmax = start[ax];
+ }
+
+ curax = axes[ax];
+
+ _numberTicks = null;
+
+ // if aligning this axis, use number of ticks from previous axis.
+ // Do I need to reset somehow if alignTicks is changed and then graph is replotted??
+ if (curax.alignTicks) {
+ if (curax.name === 'x2axis' && plot.axes.xaxis.show) {
+ _numberTicks = plot.axes.xaxis.numberTicks;
+ }
+ else if (curax.name.charAt(0) === 'y' && curax.name !== 'yaxis' && curax.name !== 'yMidAxis' && plot.axes.yaxis.show) {
+ _numberTicks = plot.axes.yaxis.numberTicks;
+ }
+ }
+
+ if (this.looseZoom && (axes[ax].renderer.constructor === $.jqplot.LinearAxisRenderer || axes[ax].renderer.constructor === $.jqplot.LogAxisRenderer )) { //} || axes[ax].renderer.constructor === $.jqplot.DateAxisRenderer)) {
+
+ ret = $.jqplot.LinearTickGenerator(newmin, newmax, curax._scalefact, _numberTicks);
+
+ // if new minimum is less than "true" minimum of axis display, adjust it
+ if (axes[ax].tickInset && ret[0] < axes[ax].min + axes[ax].tickInset * axes[ax].tickInterval) {
+ ret[0] += ret[4];
+ ret[2] -= 1;
+ }
+
+ // if new maximum is greater than "true" max of axis display, adjust it
+ if (axes[ax].tickInset && ret[1] > axes[ax].max - axes[ax].tickInset * axes[ax].tickInterval) {
+ ret[1] -= ret[4];
+ ret[2] -= 1;
+ }
+
+ // for log axes, don't fall below current minimum, this will look bad and can't have 0 in range anyway.
+ if (axes[ax].renderer.constructor === $.jqplot.LogAxisRenderer && ret[0] < axes[ax].min) {
+ // remove a tick and shift min up
+ ret[0] += ret[4];
+ ret[2] -= 1;
+ }
+
+ axes[ax].min = ret[0];
+ axes[ax].max = ret[1];
+ axes[ax]._autoFormatString = ret[3];
+ axes[ax].numberTicks = ret[2];
+ axes[ax].tickInterval = ret[4];
+ // for date axes...
+ axes[ax].daTickInterval = [ret[4]/1000, 'seconds'];
+ }
+ else {
+ axes[ax].min = newmin;
+ axes[ax].max = newmax;
+ axes[ax].tickInterval = null;
+ axes[ax].numberTicks = null;
+ // for date axes...
+ axes[ax].daTickInterval = null;
+ }
+
+ axes[ax]._ticks = [];
+ }
+ }
+
+ // if ((c.constrainZoomTo == 'x' && ax.charAt(0) == 'y' && c.autoscaleConstraint) || (c.constrainZoomTo == 'y' && ax.charAt(0) == 'x' && c.autoscaleConstraint)) {
+ // dp = datapos[ax];
+ // if (dp != null) {
+ // axes[ax].max == null;
+ // axes[ax].min = null;
+ // }
+ // }
+ }
+ ctx.clearRect(0,0,ctx.canvas.width, ctx.canvas.height);
+ plot.redraw();
+ c._zoom.isZoomed = true;
+ ctx = null;
+ }
+ plot.target.trigger('jqplotZoom', [gridpos, datapos, plot, cursor]);
+ }
+ };
+
+ $.jqplot.preInitHooks.push($.jqplot.Cursor.init);
+ $.jqplot.postDrawHooks.push($.jqplot.Cursor.postDraw);
+
+ function updateTooltip(gridpos, datapos, plot) {
+ var c = plot.plugins.cursor;
+ var s = '';
+ var addbr = false;
+ if (c.showTooltipGridPosition) {
+ s = gridpos.x+', '+gridpos.y;
+ addbr = true;
+ }
+ if (c.showTooltipUnitPosition) {
+ var g;
+ for (var i=0; i<c.tooltipAxisGroups.length; i++) {
+ g = c.tooltipAxisGroups[i];
+ if (addbr) {
+ s += '<br />';
+ }
+ if (c.useAxesFormatters) {
+ for (var j=0; j<g.length; j++) {
+ if (j) {
+ s += ', ';
+ }
+ var af = plot.axes[g[j]]._ticks[0].formatter;
+ var afstr = plot.axes[g[j]]._ticks[0].formatString;
+ s += af(afstr, datapos[g[j]]);
+ }
+ }
+ else {
+ s += $.jqplot.sprintf(c.tooltipFormatString, datapos[g[0]], datapos[g[1]]);
+ }
+ addbr = true;
+ }
+ }
+
+ if (c.showTooltipDataPosition) {
+ var series = plot.series;
+ var ret = getIntersectingPoints(plot, gridpos.x, gridpos.y);
+ var addbr = false;
+
+ for (var i = 0; i< series.length; i++) {
+ if (series[i].show) {
+ var idx = series[i].index;
+ var label = series[i].label.toString();
+ var cellid = $.inArray(idx, ret.indices);
+ var sx = undefined;
+ var sy = undefined;
+ if (cellid != -1) {
+ var data = ret.data[cellid].data;
+ if (c.useAxesFormatters) {
+ var xf = series[i]._xaxis._ticks[0].formatter;
+ var yf = series[i]._yaxis._ticks[0].formatter;
+ var xfstr = series[i]._xaxis._ticks[0].formatString;
+ var yfstr = series[i]._yaxis._ticks[0].formatString;
+ sx = xf(xfstr, data[0]);
+ sy = yf(yfstr, data[1]);
+ }
+ else {
+ sx = data[0];
+ sy = data[1];
+ }
+ if (addbr) {
+ s += '<br />';
+ }
+ s += $.jqplot.sprintf(c.tooltipFormatString, label, sx, sy);
+ addbr = true;
+ }
+ }
+ }
+
+ }
+ c._tooltipElem.html(s);
+ }
+
+ function moveLine(gridpos, plot) {
+ var c = plot.plugins.cursor;
+ var ctx = c.cursorCanvas._ctx;
+ ctx.clearRect(0,0,ctx.canvas.width, ctx.canvas.height);
+ if (c.showVerticalLine) {
+ c.shapeRenderer.draw(ctx, [[gridpos.x, 0], [gridpos.x, ctx.canvas.height]]);
+ }
+ if (c.showHorizontalLine) {
+ c.shapeRenderer.draw(ctx, [[0, gridpos.y], [ctx.canvas.width, gridpos.y]]);
+ }
+ var ret = getIntersectingPoints(plot, gridpos.x, gridpos.y);
+ if (c.showCursorLegend) {
+ var cells = $(plot.targetId + ' td.jqplot-cursor-legend-label');
+ for (var i=0; i<cells.length; i++) {
+ var idx = $(cells[i]).data('seriesIndex');
+ var series = plot.series[idx];
+ var label = series.label.toString();
+ var cellid = $.inArray(idx, ret.indices);
+ var sx = undefined;
+ var sy = undefined;
+ if (cellid != -1) {
+ var data = ret.data[cellid].data;
+ if (c.useAxesFormatters) {
+ var xf = series._xaxis._ticks[0].formatter;
+ var yf = series._yaxis._ticks[0].formatter;
+ var xfstr = series._xaxis._ticks[0].formatString;
+ var yfstr = series._yaxis._ticks[0].formatString;
+ sx = xf(xfstr, data[0]);
+ sy = yf(yfstr, data[1]);
+ }
+ else {
+ sx = data[0];
+ sy = data[1];
+ }
+ }
+ if (plot.legend.escapeHtml) {
+ $(cells[i]).text($.jqplot.sprintf(c.cursorLegendFormatString, label, sx, sy));
+ }
+ else {
+ $(cells[i]).html($.jqplot.sprintf(c.cursorLegendFormatString, label, sx, sy));
+ }
+ }
+ }
+ ctx = null;
+ }
+
+ function getIntersectingPoints(plot, x, y) {
+ var ret = {indices:[], data:[]};
+ var s, i, d0, d, j, r, p;
+ var threshold;
+ var c = plot.plugins.cursor;
+ for (var i=0; i<plot.series.length; i++) {
+ s = plot.series[i];
+ r = s.renderer;
+ if (s.show) {
+ threshold = c.intersectionThreshold;
+ if (s.showMarker) {
+ threshold += s.markerRenderer.size/2;
+ }
+ for (var j=0; j<s.gridData.length; j++) {
+ p = s.gridData[j];
+ // check vertical line
+ if (c.showVerticalLine) {
+ if (Math.abs(x-p[0]) <= threshold) {
+ ret.indices.push(i);
+ ret.data.push({seriesIndex: i, pointIndex:j, gridData:p, data:s.data[j]});
+ }
+ }
+ }
+ }
+ }
+ return ret;
+ }
+
+ function moveTooltip(gridpos, plot) {
+ var c = plot.plugins.cursor;
+ var elem = c._tooltipElem;
+ switch (c.tooltipLocation) {
+ case 'nw':
+ var x = gridpos.x + plot._gridPadding.left - elem.outerWidth(true) - c.tooltipOffset;
+ var y = gridpos.y + plot._gridPadding.top - c.tooltipOffset - elem.outerHeight(true);
+ break;
+ case 'n':
+ var x = gridpos.x + plot._gridPadding.left - elem.outerWidth(true)/2;
+ var y = gridpos.y + plot._gridPadding.top - c.tooltipOffset - elem.outerHeight(true);
+ break;
+ case 'ne':
+ var x = gridpos.x + plot._gridPadding.left + c.tooltipOffset;
+ var y = gridpos.y + plot._gridPadding.top - c.tooltipOffset - elem.outerHeight(true);
+ break;
+ case 'e':
+ var x = gridpos.x + plot._gridPadding.left + c.tooltipOffset;
+ var y = gridpos.y + plot._gridPadding.top - elem.outerHeight(true)/2;
+ break;
+ case 'se':
+ var x = gridpos.x + plot._gridPadding.left + c.tooltipOffset;
+ var y = gridpos.y + plot._gridPadding.top + c.tooltipOffset;
+ break;
+ case 's':
+ var x = gridpos.x + plot._gridPadding.left - elem.outerWidth(true)/2;
+ var y = gridpos.y + plot._gridPadding.top + c.tooltipOffset;
+ break;
+ case 'sw':
+ var x = gridpos.x + plot._gridPadding.left - elem.outerWidth(true) - c.tooltipOffset;
+ var y = gridpos.y + plot._gridPadding.top + c.tooltipOffset;
+ break;
+ case 'w':
+ var x = gridpos.x + plot._gridPadding.left - elem.outerWidth(true) - c.tooltipOffset;
+ var y = gridpos.y + plot._gridPadding.top - elem.outerHeight(true)/2;
+ break;
+ default:
+ var x = gridpos.x + plot._gridPadding.left + c.tooltipOffset;
+ var y = gridpos.y + plot._gridPadding.top + c.tooltipOffset;
+ break;
+ }
+
+ elem.css('left', x);
+ elem.css('top', y);
+ elem = null;
+ }
+
+ function positionTooltip(plot) {
+ // fake a grid for positioning
+ var grid = plot._gridPadding;
+ var c = plot.plugins.cursor;
+ var elem = c._tooltipElem;
+ switch (c.tooltipLocation) {
+ case 'nw':
+ var a = grid.left + c.tooltipOffset;
+ var b = grid.top + c.tooltipOffset;
+ elem.css('left', a);
+ elem.css('top', b);
+ break;
+ case 'n':
+ var a = (grid.left + (plot._plotDimensions.width - grid.right))/2 - elem.outerWidth(true)/2;
+ var b = grid.top + c.tooltipOffset;
+ elem.css('left', a);
+ elem.css('top', b);
+ break;
+ case 'ne':
+ var a = grid.right + c.tooltipOffset;
+ var b = grid.top + c.tooltipOffset;
+ elem.css({right:a, top:b});
+ break;
+ case 'e':
+ var a = grid.right + c.tooltipOffset;
+ var b = (grid.top + (plot._plotDimensions.height - grid.bottom))/2 - elem.outerHeight(true)/2;
+ elem.css({right:a, top:b});
+ break;
+ case 'se':
+ var a = grid.right + c.tooltipOffset;
+ var b = grid.bottom + c.tooltipOffset;
+ elem.css({right:a, bottom:b});
+ break;
+ case 's':
+ var a = (grid.left + (plot._plotDimensions.width - grid.right))/2 - elem.outerWidth(true)/2;
+ var b = grid.bottom + c.tooltipOffset;
+ elem.css({left:a, bottom:b});
+ break;
+ case 'sw':
+ var a = grid.left + c.tooltipOffset;
+ var b = grid.bottom + c.tooltipOffset;
+ elem.css({left:a, bottom:b});
+ break;
+ case 'w':
+ var a = grid.left + c.tooltipOffset;
+ var b = (grid.top + (plot._plotDimensions.height - grid.bottom))/2 - elem.outerHeight(true)/2;
+ elem.css({left:a, top:b});
+ break;
+ default: // same as 'se'
+ var a = grid.right - c.tooltipOffset;
+ var b = grid.bottom + c.tooltipOffset;
+ elem.css({right:a, bottom:b});
+ break;
+ }
+ elem = null;
+ }
+
+ function handleClick (ev, gridpos, datapos, neighbor, plot) {
+ ev.preventDefault();
+ ev.stopImmediatePropagation();
+ var c = plot.plugins.cursor;
+ if (c.clickReset) {
+ c.resetZoom(plot, c);
+ }
+ var sel = window.getSelection;
+ if (document.selection && document.selection.empty)
+ {
+ document.selection.empty();
+ }
+ else if (sel && !sel().isCollapsed) {
+ sel().collapse();
+ }
+ return false;
+ }
+
+ function handleDblClick (ev, gridpos, datapos, neighbor, plot) {
+ ev.preventDefault();
+ ev.stopImmediatePropagation();
+ var c = plot.plugins.cursor;
+ if (c.dblClickReset) {
+ c.resetZoom(plot, c);
+ }
+ var sel = window.getSelection;
+ if (document.selection && document.selection.empty)
+ {
+ document.selection.empty();
+ }
+ else if (sel && !sel().isCollapsed) {
+ sel().collapse();
+ }
+ return false;
+ }
+
+ function handleMouseLeave(ev, gridpos, datapos, neighbor, plot) {
+ var c = plot.plugins.cursor;
+ c.onGrid = false;
+ if (c.show) {
+ $(ev.target).css('cursor', c.previousCursor);
+ if (c.showTooltip && !(c._zoom.zooming && c.showTooltipOutsideZoom && !c.constrainOutsideZoom)) {
+ c._tooltipElem.empty();
+ c._tooltipElem.hide();
+ }
+ if (c.zoom) {
+ c._zoom.gridpos = gridpos;
+ c._zoom.datapos = datapos;
+ }
+ if (c.showVerticalLine || c.showHorizontalLine) {
+ var ctx = c.cursorCanvas._ctx;
+ ctx.clearRect(0,0,ctx.canvas.width, ctx.canvas.height);
+ ctx = null;
+ }
+ if (c.showCursorLegend) {
+ var cells = $(plot.targetId + ' td.jqplot-cursor-legend-label');
+ for (var i=0; i<cells.length; i++) {
+ var idx = $(cells[i]).data('seriesIndex');
+ var series = plot.series[idx];
+ var label = series.label.toString();
+ if (plot.legend.escapeHtml) {
+ $(cells[i]).text($.jqplot.sprintf(c.cursorLegendFormatString, label, undefined, undefined));
+ }
+ else {
+ $(cells[i]).html($.jqplot.sprintf(c.cursorLegendFormatString, label, undefined, undefined));
+ }
+
+ }
+ }
+ }
+ }
+
+ function handleMouseEnter(ev, gridpos, datapos, neighbor, plot) {
+ var c = plot.plugins.cursor;
+ c.onGrid = true;
+ if (c.show) {
+ c.previousCursor = ev.target.style.cursor;
+ ev.target.style.cursor = c.style;
+ if (c.showTooltip) {
+ updateTooltip(gridpos, datapos, plot);
+ if (c.followMouse) {
+ moveTooltip(gridpos, plot);
+ }
+ else {
+ positionTooltip(plot);
+ }
+ c._tooltipElem.show();
+ }
+ if (c.showVerticalLine || c.showHorizontalLine) {
+ moveLine(gridpos, plot);
+ }
+ }
+
+ }
+
+ function handleMouseMove(ev, gridpos, datapos, neighbor, plot) {
+ var c = plot.plugins.cursor;
+ if (c.show) {
+ if (c.showTooltip) {
+ updateTooltip(gridpos, datapos, plot);
+ if (c.followMouse) {
+ moveTooltip(gridpos, plot);
+ }
+ }
+ if (c.showVerticalLine || c.showHorizontalLine) {
+ moveLine(gridpos, plot);
+ }
+ }
+ }
+
+ function getEventPosition(ev) {
+ var plot = ev.data.plot;
+ var go = plot.eventCanvas._elem.offset();
+ var gridPos = {x:ev.pageX - go.left, y:ev.pageY - go.top};
+ //////
+ // TO DO: handle yMidAxis
+ //////
+ var dataPos = {xaxis:null, yaxis:null, x2axis:null, y2axis:null, y3axis:null, y4axis:null, y5axis:null, y6axis:null, y7axis:null, y8axis:null, y9axis:null, yMidAxis:null};
+ var an = ['xaxis', 'yaxis', 'x2axis', 'y2axis', 'y3axis', 'y4axis', 'y5axis', 'y6axis', 'y7axis', 'y8axis', 'y9axis', 'yMidAxis'];
+ var ax = plot.axes;
+ var n, axis;
+ for (n=11; n>0; n--) {
+ axis = an[n-1];
+ if (ax[axis].show) {
+ dataPos[axis] = ax[axis].series_p2u(gridPos[axis.charAt(0)]);
+ }
+ }
+
+ return {offsets:go, gridPos:gridPos, dataPos:dataPos};
+ }
+
+ function handleZoomMove(ev) {
+ var plot = ev.data.plot;
+ var c = plot.plugins.cursor;
+ // don't do anything if not on grid.
+ if (c.show && c.zoom && c._zoom.started && !c.zoomTarget) {
+ ev.preventDefault();
+ var ctx = c.zoomCanvas._ctx;
+ var positions = getEventPosition(ev);
+ var gridpos = positions.gridPos;
+ var datapos = positions.dataPos;
+ c._zoom.gridpos = gridpos;
+ c._zoom.datapos = datapos;
+ c._zoom.zooming = true;
+ var xpos = gridpos.x;
+ var ypos = gridpos.y;
+ var height = ctx.canvas.height;
+ var width = ctx.canvas.width;
+ if (c.showTooltip && !c.onGrid && c.showTooltipOutsideZoom) {
+ updateTooltip(gridpos, datapos, plot);
+ if (c.followMouse) {
+ moveTooltip(gridpos, plot);
+ }
+ }
+ if (c.constrainZoomTo == 'x') {
+ c._zoom.end = [xpos, height];
+ }
+ else if (c.constrainZoomTo == 'y') {
+ c._zoom.end = [width, ypos];
+ }
+ else {
+ c._zoom.end = [xpos, ypos];
+ }
+ var sel = window.getSelection;
+ if (document.selection && document.selection.empty)
+ {
+ document.selection.empty();
+ }
+ else if (sel && !sel().isCollapsed) {
+ sel().collapse();
+ }
+ drawZoomBox.call(c);
+ ctx = null;
+ }
+ }
+
+ function handleMouseDown(ev, gridpos, datapos, neighbor, plot) {
+ var c = plot.plugins.cursor;
+ if(plot.plugins.mobile){
+ $(document).one('vmouseup.jqplot_cursor', {plot:plot}, handleMouseUp);
+ } else {
+ $(document).one('mouseup.jqplot_cursor', {plot:plot}, handleMouseUp);
+ }
+ var axes = plot.axes;
+ if (document.onselectstart != undefined) {
+ c._oldHandlers.onselectstart = document.onselectstart;
+ document.onselectstart = function () { return false; };
+ }
+ if (document.ondrag != undefined) {
+ c._oldHandlers.ondrag = document.ondrag;
+ document.ondrag = function () { return false; };
+ }
+ if (document.onmousedown != undefined) {
+ c._oldHandlers.onmousedown = document.onmousedown;
+ document.onmousedown = function () { return false; };
+ }
+ if (c.zoom) {
+ if (!c.zoomProxy) {
+ var ctx = c.zoomCanvas._ctx;
+ ctx.clearRect(0,0,ctx.canvas.width, ctx.canvas.height);
+ ctx = null;
+ }
+ if (c.constrainZoomTo == 'x') {
+ c._zoom.start = [gridpos.x, 0];
+ }
+ else if (c.constrainZoomTo == 'y') {
+ c._zoom.start = [0, gridpos.y];
+ }
+ else {
+ c._zoom.start = [gridpos.x, gridpos.y];
+ }
+ c._zoom.started = true;
+ for (var ax in datapos) {
+ // get zoom starting position.
+ c._zoom.axes.start[ax] = datapos[ax];
+ }
+ if(plot.plugins.mobile){
+ $(document).bind('vmousemove.jqplotCursor', {plot:plot}, handleZoomMove);
+ } else {
+ $(document).bind('mousemove.jqplotCursor', {plot:plot}, handleZoomMove);
+ }
+
+ }
+ }
+
+ function handleMouseUp(ev) {
+ var plot = ev.data.plot;
+ var c = plot.plugins.cursor;
+ if (c.zoom && c._zoom.zooming && !c.zoomTarget) {
+ var xpos = c._zoom.gridpos.x;
+ var ypos = c._zoom.gridpos.y;
+ var datapos = c._zoom.datapos;
+ var height = c.zoomCanvas._ctx.canvas.height;
+ var width = c.zoomCanvas._ctx.canvas.width;
+ var axes = plot.axes;
+
+ if (c.constrainOutsideZoom && !c.onGrid) {
+ if (xpos < 0) { xpos = 0; }
+ else if (xpos > width) { xpos = width; }
+ if (ypos < 0) { ypos = 0; }
+ else if (ypos > height) { ypos = height; }
+
+ for (var axis in datapos) {
+ if (datapos[axis]) {
+ if (axis.charAt(0) == 'x') {
+ datapos[axis] = axes[axis].series_p2u(xpos);
+ }
+ else {
+ datapos[axis] = axes[axis].series_p2u(ypos);
+ }
+ }
+ }
+ }
+
+ if (c.constrainZoomTo == 'x') {
+ ypos = height;
+ }
+ else if (c.constrainZoomTo == 'y') {
+ xpos = width;
+ }
+ c._zoom.end = [xpos, ypos];
+ c._zoom.gridpos = {x:xpos, y:ypos};
+
+ c.doZoom(c._zoom.gridpos, datapos, plot, c);
+ }
+ c._zoom.started = false;
+ c._zoom.zooming = false;
+
+ $(document).unbind('mousemove.jqplotCursor', handleZoomMove);
+
+ if (document.onselectstart != undefined && c._oldHandlers.onselectstart != null){
+ document.onselectstart = c._oldHandlers.onselectstart;
+ c._oldHandlers.onselectstart = null;
+ }
+ if (document.ondrag != undefined && c._oldHandlers.ondrag != null){
+ document.ondrag = c._oldHandlers.ondrag;
+ c._oldHandlers.ondrag = null;
+ }
+ if (document.onmousedown != undefined && c._oldHandlers.onmousedown != null){
+ document.onmousedown = c._oldHandlers.onmousedown;
+ c._oldHandlers.onmousedown = null;
+ }
+
+ }
+
+ function drawZoomBox() {
+ var start = this._zoom.start;
+ var end = this._zoom.end;
+ var ctx = this.zoomCanvas._ctx;
+ var l, t, h, w;
+ if (end[0] > start[0]) {
+ l = start[0];
+ w = end[0] - start[0];
+ }
+ else {
+ l = end[0];
+ w = start[0] - end[0];
+ }
+ if (end[1] > start[1]) {
+ t = start[1];
+ h = end[1] - start[1];
+ }
+ else {
+ t = end[1];
+ h = start[1] - end[1];
+ }
+ ctx.fillStyle = 'rgba(0,0,0,0.2)';
+ ctx.strokeStyle = '#999999';
+ ctx.lineWidth = 1.0;
+ ctx.clearRect(0,0,ctx.canvas.width, ctx.canvas.height);
+ ctx.fillRect(0,0,ctx.canvas.width, ctx.canvas.height);
+ ctx.clearRect(l, t, w, h);
+ // IE won't show transparent fill rect, so stroke a rect also.
+ ctx.strokeRect(l,t,w,h);
+ ctx = null;
+ }
+
+ $.jqplot.CursorLegendRenderer = function(options) {
+ $.jqplot.TableLegendRenderer.call(this, options);
+ this.formatString = '%s';
+ };
+
+ $.jqplot.CursorLegendRenderer.prototype = new $.jqplot.TableLegendRenderer();
+ $.jqplot.CursorLegendRenderer.prototype.constructor = $.jqplot.CursorLegendRenderer;
+
+ // called in context of a Legend
+ $.jqplot.CursorLegendRenderer.prototype.draw = function() {
+ if (this._elem) {
+ this._elem.emptyForce();
+ this._elem = null;
+ }
+ if (this.show) {
+ var series = this._series, s;
+ // make a table. one line label per row.
+ var elem = document.createElement('div');
+ this._elem = $(elem);
+ elem = null;
+ this._elem.addClass('jqplot-legend jqplot-cursor-legend');
+ this._elem.css('position', 'absolute');
+
+ var pad = false;
+ for (var i = 0; i< series.length; i++) {
+ s = series[i];
+ if (s.show && s.showLabel) {
+ var lt = $.jqplot.sprintf(this.formatString, s.label.toString());
+ if (lt) {
+ var color = s.color;
+ if (s._stack && !s.fill) {
+ color = '';
+ }
+ addrow.call(this, lt, color, pad, i);
+ pad = true;
+ }
+ // let plugins add more rows to legend. Used by trend line plugin.
+ for (var j=0; j<$.jqplot.addLegendRowHooks.length; j++) {
+ var item = $.jqplot.addLegendRowHooks[j].call(this, s);
+ if (item) {
+ addrow.call(this, item.label, item.color, pad);
+ pad = true;
+ }
+ }
+ }
+ }
+ series = s = null;
+ delete series;
+ delete s;
+ }
+
+ function addrow(label, color, pad, idx) {
+ var rs = (pad) ? this.rowSpacing : '0';
+ var tr = $('<tr class="jqplot-legend jqplot-cursor-legend"></tr>').appendTo(this._elem);
+ tr.data('seriesIndex', idx);
+ $('<td class="jqplot-legend jqplot-cursor-legend-swatch" style="padding-top:'+rs+';">'+
+ '<div style="border:1px solid #cccccc;padding:0.2em;">'+
+ '<div class="jqplot-cursor-legend-swatch" style="background-color:'+color+';"></div>'+
+ '</div></td>').appendTo(tr);
+ var td = $('<td class="jqplot-legend jqplot-cursor-legend-label" style="vertical-align:middle;padding-top:'+rs+';"></td>');
+ td.appendTo(tr);
+ td.data('seriesIndex', idx);
+ if (this.escapeHtml) {
+ td.text(label);
+ }
+ else {
+ td.html(label);
+ }
+ tr = null;
+ td = null;
+ }
+ return this._elem;
+ };
+
+})(jQuery);
diff --git a/js/jqplot/plugins/jqplot.dateAxisRenderer.js b/js/jqplot/plugins/jqplot.dateAxisRenderer.js
new file mode 100644
index 0000000000..ec4211d42b
--- /dev/null
+++ b/js/jqplot/plugins/jqplot.dateAxisRenderer.js
@@ -0,0 +1,737 @@
+/**
+ * jqPlot
+ * Pure JavaScript plotting plugin using jQuery
+ *
+ * Version: 1.0.4
+ * Revision: 1121
+ *
+ * Copyright (c) 2009-2012 Chris Leonello
+ * jqPlot is currently available for use in all personal or commercial projects
+ * under both the MIT (http://www.opensource.org/licenses/mit-license.php) and GPL
+ * version 2.0 (http://www.gnu.org/licenses/gpl-2.0.html) licenses. This means that you can
+ * choose the license that best suits your project and use it accordingly.
+ *
+ * Although not required, the author would appreciate an email letting him
+ * know of any substantial use of jqPlot. You can reach the author at:
+ * chris at jqplot dot com or see http://www.jqplot.com/info.php .
+ *
+ * If you are feeling kind and generous, consider supporting the project by
+ * making a donation at: http://www.jqplot.com/donate.php .
+ *
+ * sprintf functions contained in jqplot.sprintf.js by Ash Searle:
+ *
+ * version 2007.04.27
+ * author Ash Searle
+ * http://hexmen.com/blog/2007/03/printf-sprintf/
+ * http://hexmen.com/js/sprintf.js
+ * The author (Ash Searle) has placed this code in the public domain:
+ * "This code is unrestricted: you are free to use it however you like."
+ *
+ */
+(function($) {
+ /**
+ * Class: $.jqplot.DateAxisRenderer
+ * A plugin for a jqPlot to render an axis as a series of date values.
+ * This renderer has no options beyond those supplied by the <Axis> class.
+ * It supplies it's own tick formatter, so the tickOptions.formatter option
+ * should not be overridden.
+ *
+ * Thanks to Ken Synder for his enhanced Date instance methods which are
+ * included with this code <http://kendsnyder.com/sandbox/date/>.
+ *
+ * To use this renderer, include the plugin in your source
+ * > <script type="text/javascript" language="javascript" src="plugins/jqplot.dateAxisRenderer.js"></script>
+ *
+ * and supply the appropriate options to your plot
+ *
+ * > {axes:{xaxis:{renderer:$.jqplot.DateAxisRenderer}}}
+ *
+ * Dates can be passed into the axis in almost any recognizable value and
+ * will be parsed. They will be rendered on the axis in the format
+ * specified by tickOptions.formatString. e.g. tickOptions.formatString = '%Y-%m-%d'.
+ *
+ * Accecptable format codes
+ * are:
+ *
+ * > Code Result Description
+ * > == Years ==
+ * > %Y 2008 Four-digit year
+ * > %y 08 Two-digit year
+ * > == Months ==
+ * > %m 09 Two-digit month
+ * > %#m 9 One or two-digit month
+ * > %B September Full month name
+ * > %b Sep Abbreviated month name
+ * > == Days ==
+ * > %d 05 Two-digit day of month
+ * > %#d 5 One or two-digit day of month
+ * > %e 5 One or two-digit day of month
+ * > %A Sunday Full name of the day of the week
+ * > %a Sun Abbreviated name of the day of the week
+ * > %w 0 Number of the day of the week (0 = Sunday, 6 = Saturday)
+ * > %o th The ordinal suffix string following the day of the month
+ * > == Hours ==
+ * > %H 23 Hours in 24-hour format (two digits)
+ * > %#H 3 Hours in 24-hour integer format (one or two digits)
+ * > %I 11 Hours in 12-hour format (two digits)
+ * > %#I 3 Hours in 12-hour integer format (one or two digits)
+ * > %p PM AM or PM
+ * > == Minutes ==
+ * > %M 09 Minutes (two digits)
+ * > %#M 9 Minutes (one or two digits)
+ * > == Seconds ==
+ * > %S 02 Seconds (two digits)
+ * > %#S 2 Seconds (one or two digits)
+ * > %s 1206567625723 Unix timestamp (Seconds past 1970-01-01 00:00:00)
+ * > == Milliseconds ==
+ * > %N 008 Milliseconds (three digits)
+ * > %#N 8 Milliseconds (one to three digits)
+ * > == Timezone ==
+ * > %O 360 difference in minutes between local time and GMT
+ * > %Z Mountain Standard Time Name of timezone as reported by browser
+ * > %G -06:00 Hours and minutes between GMT
+ * > == Shortcuts ==
+ * > %F 2008-03-26 %Y-%m-%d
+ * > %T 05:06:30 %H:%M:%S
+ * > %X 05:06:30 %H:%M:%S
+ * > %x 03/26/08 %m/%d/%y
+ * > %D 03/26/08 %m/%d/%y
+ * > %#c Wed Mar 26 15:31:00 2008 %a %b %e %H:%M:%S %Y
+ * > %v 3-Sep-2008 %e-%b-%Y
+ * > %R 15:31 %H:%M
+ * > %r 3:31:00 PM %I:%M:%S %p
+ * > == Characters ==
+ * > %n \n Newline
+ * > %t \t Tab
+ * > %% % Percent Symbol
+ */
+ $.jqplot.DateAxisRenderer = function() {
+ $.jqplot.LinearAxisRenderer.call(this);
+ this.date = new $.jsDate();
+ };
+
+ var second = 1000;
+ var minute = 60 * second;
+ var hour = 60 * minute;
+ var day = 24 * hour;
+ var week = 7 * day;
+
+ // these are less definitive
+ var month = 30.4368499 * day;
+ var year = 365.242199 * day;
+
+ var daysInMonths = [31,28,31,30,31,30,31,30,31,30,31,30];
+ // array of consistent nice intervals. Longer intervals
+ // will depend on days in month, days in year, etc.
+ var niceFormatStrings = ['%M:%S.%#N', '%M:%S.%#N', '%M:%S.%#N', '%M:%S', '%M:%S', '%M:%S', '%M:%S', '%H:%M:%S', '%H:%M:%S', '%H:%M', '%H:%M', '%H:%M', '%H:%M', '%H:%M', '%H:%M', '%a %H:%M', '%a %H:%M', '%b %e %H:%M', '%b %e %H:%M', '%b %e %H:%M', '%b %e %H:%M', '%v', '%v', '%v', '%v', '%v', '%v', '%v'];
+ var niceIntervals = [0.1*second, 0.2*second, 0.5*second, second, 2*second, 5*second, 10*second, 15*second, 30*second, minute, 2*minute, 5*minute, 10*minute, 15*minute, 30*minute, hour, 2*hour, 4*hour, 6*hour, 8*hour, 12*hour, day, 2*day, 3*day, 4*day, 5*day, week, 2*week];
+
+ var niceMonthlyIntervals = [];
+
+ function bestDateInterval(min, max, titarget) {
+ // iterate through niceIntervals to find one closest to titarget
+ var badness = Number.MAX_VALUE;
+ var temp, bestTi, bestfmt;
+ for (var i=0, l=niceIntervals.length; i < l; i++) {
+ temp = Math.abs(titarget - niceIntervals[i]);
+ if (temp < badness) {
+ badness = temp;
+ bestTi = niceIntervals[i];
+ bestfmt = niceFormatStrings[i];
+ }
+ }
+
+ return [bestTi, bestfmt];
+ }
+
+ $.jqplot.DateAxisRenderer.prototype = new $.jqplot.LinearAxisRenderer();
+ $.jqplot.DateAxisRenderer.prototype.constructor = $.jqplot.DateAxisRenderer;
+
+ $.jqplot.DateTickFormatter = function(format, val) {
+ if (!format) {
+ format = '%Y/%m/%d';
+ }
+ return $.jsDate.strftime(val, format);
+ };
+
+ $.jqplot.DateAxisRenderer.prototype.init = function(options){
+ // prop: tickRenderer
+ // A class of a rendering engine for creating the ticks labels displayed on the plot,
+ // See <$.jqplot.AxisTickRenderer>.
+ // this.tickRenderer = $.jqplot.AxisTickRenderer;
+ // this.labelRenderer = $.jqplot.AxisLabelRenderer;
+ this.tickOptions.formatter = $.jqplot.DateTickFormatter;
+ // prop: tickInset
+ // Controls the amount to inset the first and last ticks from
+ // the edges of the grid, in multiples of the tick interval.
+ // 0 is no inset, 0.5 is one half a tick interval, 1 is a full
+ // tick interval, etc.
+ this.tickInset = 0;
+ // prop: drawBaseline
+ // True to draw the axis baseline.
+ this.drawBaseline = true;
+ // prop: baselineWidth
+ // width of the baseline in pixels.
+ this.baselineWidth = null;
+ // prop: baselineColor
+ // CSS color spec for the baseline.
+ this.baselineColor = null;
+ this.daTickInterval = null;
+ this._daTickInterval = null;
+
+ $.extend(true, this, options);
+
+ var db = this._dataBounds,
+ stats,
+ sum,
+ s,
+ d,
+ pd,
+ sd,
+ intv;
+
+ // Go through all the series attached to this axis and find
+ // the min/max bounds for this axis.
+ for (var i=0; i<this._series.length; i++) {
+ stats = {intervals:[], frequencies:{}, sortedIntervals:[], min:null, max:null, mean:null};
+ sum = 0;
+ s = this._series[i];
+ d = s.data;
+ pd = s._plotData;
+ sd = s._stackData;
+ intv = 0;
+
+ for (var j=0; j<d.length; j++) {
+ if (this.name == 'xaxis' || this.name == 'x2axis') {
+ d[j][0] = new $.jsDate(d[j][0]).getTime();
+ pd[j][0] = new $.jsDate(d[j][0]).getTime();
+ sd[j][0] = new $.jsDate(d[j][0]).getTime();
+ if ((d[j][0] != null && d[j][0] < db.min) || db.min == null) {
+ db.min = d[j][0];
+ }
+ if ((d[j][0] != null && d[j][0] > db.max) || db.max == null) {
+ db.max = d[j][0];
+ }
+ if (j>0) {
+ intv = Math.abs(d[j][0] - d[j-1][0]);
+ stats.intervals.push(intv);
+ if (stats.frequencies.hasOwnProperty(intv)) {
+ stats.frequencies[intv] += 1;
+ }
+ else {
+ stats.frequencies[intv] = 1;
+ }
+ }
+ sum += intv;
+
+ }
+ else {
+ d[j][1] = new $.jsDate(d[j][1]).getTime();
+ pd[j][1] = new $.jsDate(d[j][1]).getTime();
+ sd[j][1] = new $.jsDate(d[j][1]).getTime();
+ if ((d[j][1] != null && d[j][1] < db.min) || db.min == null) {
+ db.min = d[j][1];
+ }
+ if ((d[j][1] != null && d[j][1] > db.max) || db.max == null) {
+ db.max = d[j][1];
+ }
+ if (j>0) {
+ intv = Math.abs(d[j][1] - d[j-1][1]);
+ stats.intervals.push(intv);
+ if (stats.frequencies.hasOwnProperty(intv)) {
+ stats.frequencies[intv] += 1;
+ }
+ else {
+ stats.frequencies[intv] = 1;
+ }
+ }
+ }
+ sum += intv;
+ }
+
+ if (s.renderer.bands) {
+ if (s.renderer.bands.hiData.length) {
+ var bd = s.renderer.bands.hiData;
+ for (var j=0, l=bd.length; j < l; j++) {
+ if (this.name === 'xaxis' || this.name === 'x2axis') {
+ bd[j][0] = new $.jsDate(bd[j][0]).getTime();
+ if ((bd[j][0] != null && bd[j][0] > db.max) || db.max == null) {
+ db.max = bd[j][0];
+ }
+ }
+ else {
+ bd[j][1] = new $.jsDate(bd[j][1]).getTime();
+ if ((bd[j][1] != null && bd[j][1] > db.max) || db.max == null) {
+ db.max = bd[j][1];
+ }
+ }
+ }
+ }
+ if (s.renderer.bands.lowData.length) {
+ var bd = s.renderer.bands.lowData;
+ for (var j=0, l=bd.length; j < l; j++) {
+ if (this.name === 'xaxis' || this.name === 'x2axis') {
+ bd[j][0] = new $.jsDate(bd[j][0]).getTime();
+ if ((bd[j][0] != null && bd[j][0] < db.min) || db.min == null) {
+ db.min = bd[j][0];
+ }
+ }
+ else {
+ bd[j][1] = new $.jsDate(bd[j][1]).getTime();
+ if ((bd[j][1] != null && bd[j][1] < db.min) || db.min == null) {
+ db.min = bd[j][1];
+ }
+ }
+ }
+ }
+ }
+
+ var tempf = 0,
+ tempn=0;
+ for (var n in stats.frequencies) {
+ stats.sortedIntervals.push({interval:n, frequency:stats.frequencies[n]});
+ }
+ stats.sortedIntervals.sort(function(a, b){
+ return b.frequency - a.frequency;
+ });
+
+ stats.min = $.jqplot.arrayMin(stats.intervals);
+ stats.max = $.jqplot.arrayMax(stats.intervals);
+ stats.mean = sum/d.length;
+ this._intervalStats.push(stats);
+ stats = sum = s = d = pd = sd = null;
+ }
+ db = null;
+
+ };
+
+ // called with scope of an axis
+ $.jqplot.DateAxisRenderer.prototype.reset = function() {
+ this.min = this._options.min;
+ this.max = this._options.max;
+ this.tickInterval = this._options.tickInterval;
+ this.numberTicks = this._options.numberTicks;
+ this._autoFormatString = '';
+ if (this._overrideFormatString && this.tickOptions && this.tickOptions.formatString) {
+ this.tickOptions.formatString = '';
+ }
+ this.daTickInterval = this._daTickInterval;
+ // this._ticks = this.__ticks;
+ };
+
+ $.jqplot.DateAxisRenderer.prototype.createTicks = function(plot) {
+ // we're are operating on an axis here
+ var ticks = this._ticks;
+ var userTicks = this.ticks;
+ var name = this.name;
+ // databounds were set on axis initialization.
+ var db = this._dataBounds;
+ var iv = this._intervalStats;
+ var dim = (this.name.charAt(0) === 'x') ? this._plotDimensions.width : this._plotDimensions.height;
+ var interval;
+ var min, max;
+ var pos1, pos2;
+ var tt, i;
+ var threshold = 30;
+ var insetMult = 1;
+
+ var tickInterval = this.tickInterval;
+
+ // if we already have ticks, use them.
+ // ticks must be in order of increasing value.
+
+ min = ((this.min != null) ? new $.jsDate(this.min).getTime() : db.min);
+ max = ((this.max != null) ? new $.jsDate(this.max).getTime() : db.max);
+
+ // see if we're zooming. if we are, don't use the min and max we're given,
+ // but compute some nice ones. They will be reset later.
+
+ var cursor = plot.plugins.cursor;
+
+ if (cursor && cursor._zoom && cursor._zoom.zooming) {
+ this.min = null;
+ this.max = null;
+ }
+
+ var range = max - min;
+
+ if (this.tickOptions == null || !this.tickOptions.formatString) {
+ this._overrideFormatString = true;
+ }
+
+ if (userTicks.length) {
+ // ticks could be 1D or 2D array of [val, val, ,,,] or [[val, label], [val, label], ...] or mixed
+ for (i=0; i<userTicks.length; i++){
+ var ut = userTicks[i];
+ var t = new this.tickRenderer(this.tickOptions);
+ if (ut.constructor == Array) {
+ t.value = new $.jsDate(ut[0]).getTime();
+ t.label = ut[1];
+ if (!this.showTicks) {
+ t.showLabel = false;
+ t.showMark = false;
+ }
+ else if (!this.showTickMarks) {
+ t.showMark = false;
+ }
+ t.setTick(t.value, this.name);
+ this._ticks.push(t);
+ }
+
+ else {
+ t.value = new $.jsDate(ut).getTime();
+ if (!this.showTicks) {
+ t.showLabel = false;
+ t.showMark = false;
+ }
+ else if (!this.showTickMarks) {
+ t.showMark = false;
+ }
+ t.setTick(t.value, this.name);
+ this._ticks.push(t);
+ }
+ }
+ this.numberTicks = userTicks.length;
+ this.min = this._ticks[0].value;
+ this.max = this._ticks[this.numberTicks-1].value;
+ this.daTickInterval = [(this.max - this.min) / (this.numberTicks - 1)/1000, 'seconds'];
+ }
+
+ ////////
+ // We don't have any ticks yet, let's make some!
+ ////////
+
+ // special case when there is only one point, make three tick marks to center the point
+ else if (this.min == null && this.max == null && db.min == db.max)
+ {
+ var onePointOpts = $.extend(true, {}, this.tickOptions, {name: this.name, value: null});
+ var delta = 300000;
+ this.min = db.min - delta;
+ this.max = db.max + delta;
+ this.numberTicks = 3;
+
+ for(var i=this.min;i<=this.max;i+= delta)
+ {
+ onePointOpts.value = i;
+
+ var t = new this.tickRenderer(onePointOpts);
+
+ if (this._overrideFormatString && this._autoFormatString != '') {
+ t.formatString = this._autoFormatString;
+ }
+
+ t.showLabel = false;
+ t.showMark = false;
+
+ this._ticks.push(t);
+ }
+
+ if(this.showTicks) {
+ this._ticks[1].showLabel = true;
+ }
+ if(this.showTickMarks) {
+ this._ticks[1].showTickMarks = true;
+ }
+ }
+ // if user specified min and max are null, we set those to make best ticks.
+ else if (this.min == null && this.max == null) {
+
+ var opts = $.extend(true, {}, this.tickOptions, {name: this.name, value: null});
+
+ // want to find a nice interval
+ var nttarget,
+ titarget;
+
+ // if no tickInterval or numberTicks options specified, make a good guess.
+ if (!this.tickInterval && !this.numberTicks) {
+ var tdim = Math.max(dim, threshold+1);
+ // how many ticks to put on the axis?
+ // date labels tend to be long. If ticks not rotated,
+ // don't use too many and have a high spacing factor.
+ // If we are rotating ticks, use a lower factor.
+ var spacingFactor = 115;
+ if (this.tickRenderer === $.jqplot.CanvasAxisTickRenderer && this.tickOptions.angle) {
+ spacingFactor = 115 - 40 * Math.abs(Math.sin(this.tickOptions.angle/180*Math.PI));
+ }
+
+ nttarget = Math.ceil((tdim-threshold)/spacingFactor + 1);
+ titarget = (max - min) / (nttarget - 1);
+ }
+
+ // If tickInterval is specified, we'll try to honor it.
+ // Not gauranteed to get this interval, but we'll get as close as
+ // we can.
+ // tickInterval will be used before numberTicks, that is if
+ // both are specified, numberTicks will be ignored.
+ else if (this.tickInterval) {
+ titarget = this.tickInterval;
+ }
+
+ // if numberTicks specified, try to honor it.
+ // Not gauranteed, but will try to get close.
+ else if (this.numberTicks) {
+ nttarget = this.numberTicks;
+ titarget = (max - min) / (nttarget - 1);
+ }
+
+ // If we can use an interval of 2 weeks or less, pick best one
+ if (titarget <= 19*day) {
+ var ret = bestDateInterval(min, max, titarget);
+ var tempti = ret[0];
+ this._autoFormatString = ret[1];
+
+ min = Math.floor(min/tempti) * tempti;
+ min = new $.jsDate(min);
+ min = min.getTime() + min.getUtcOffset();
+
+ nttarget = Math.ceil((max - min) / tempti) + 1;
+ this.min = min;
+ this.max = min + (nttarget - 1) * tempti;
+
+ // if max is less than max, add an interval
+ if (this.max < max) {
+ this.max += tempti;
+ nttarget += 1;
+ }
+ this.tickInterval = tempti;
+ this.numberTicks = nttarget;
+
+ for (var i=0; i<nttarget; i++) {
+ opts.value = this.min + i * tempti;
+ t = new this.tickRenderer(opts);
+
+ if (this._overrideFormatString && this._autoFormatString != '') {
+ t.formatString = this._autoFormatString;
+ }
+ if (!this.showTicks) {
+ t.showLabel = false;
+ t.showMark = false;
+ }
+ else if (!this.showTickMarks) {
+ t.showMark = false;
+ }
+ this._ticks.push(t);
+ }
+
+ insetMult = this.tickInterval;
+ }
+
+ // should we use a monthly interval?
+ else if (titarget <= 9 * month) {
+
+ this._autoFormatString = '%v';
+
+ // how many months in an interval?
+ var intv = Math.round(titarget/month);
+ if (intv < 1) {
+ intv = 1;
+ }
+ else if (intv > 6) {
+ intv = 6;
+ }
+
+ // figure out the starting month and ending month.
+ var mstart = new $.jsDate(min).setDate(1).setHours(0,0,0,0);
+
+ // See if max ends exactly on a month
+ var tempmend = new $.jsDate(max);
+ var mend = new $.jsDate(max).setDate(1).setHours(0,0,0,0);
+
+ if (tempmend.getTime() !== mend.getTime()) {
+ mend = mend.add(1, 'month');
+ }
+
+ var nmonths = mend.diff(mstart, 'month');
+
+ nttarget = Math.ceil(nmonths/intv) + 1;
+
+ this.min = mstart.getTime();
+ this.max = mstart.clone().add((nttarget - 1) * intv, 'month').getTime();
+ this.numberTicks = nttarget;
+
+ for (var i=0; i<nttarget; i++) {
+ if (i === 0) {
+ opts.value = mstart.getTime();
+ }
+ else {
+ opts.value = mstart.add(intv, 'month').getTime();
+ }
+ t = new this.tickRenderer(opts);
+
+ if (this._overrideFormatString && this._autoFormatString != '') {
+ t.formatString = this._autoFormatString;
+ }
+ if (!this.showTicks) {
+ t.showLabel = false;
+ t.showMark = false;
+ }
+ else if (!this.showTickMarks) {
+ t.showMark = false;
+ }
+ this._ticks.push(t);
+ }
+
+ insetMult = intv * month;
+ }
+
+ // use yearly intervals
+ else {
+
+ this._autoFormatString = '%v';
+
+ // how many years in an interval?
+ var intv = Math.round(titarget/year);
+ if (intv < 1) {
+ intv = 1;
+ }
+
+ // figure out the starting and ending years.
+ var mstart = new $.jsDate(min).setMonth(0, 1).setHours(0,0,0,0);
+ var mend = new $.jsDate(max).add(1, 'year').setMonth(0, 1).setHours(0,0,0,0);
+
+ var nyears = mend.diff(mstart, 'year');
+
+ nttarget = Math.ceil(nyears/intv) + 1;
+
+ this.min = mstart.getTime();
+ this.max = mstart.clone().add((nttarget - 1) * intv, 'year').getTime();
+ this.numberTicks = nttarget;
+
+ for (var i=0; i<nttarget; i++) {
+ if (i === 0) {
+ opts.value = mstart.getTime();
+ }
+ else {
+ opts.value = mstart.add(intv, 'year').getTime();
+ }
+ t = new this.tickRenderer(opts);
+
+ if (this._overrideFormatString && this._autoFormatString != '') {
+ t.formatString = this._autoFormatString;
+ }
+ if (!this.showTicks) {
+ t.showLabel = false;
+ t.showMark = false;
+ }
+ else if (!this.showTickMarks) {
+ t.showMark = false;
+ }
+ this._ticks.push(t);
+ }
+
+ insetMult = intv * year;
+ }
+ }
+
+ ////////
+ // Some option(s) specified, work around that.
+ ////////
+
+ else {
+ if (name == 'xaxis' || name == 'x2axis') {
+ dim = this._plotDimensions.width;
+ }
+ else {
+ dim = this._plotDimensions.height;
+ }
+
+ // if min, max and number of ticks specified, user can't specify interval.
+ if (this.min != null && this.max != null && this.numberTicks != null) {
+ this.tickInterval = null;
+ }
+
+ // if user specified a tick interval, convert to usable.
+ if (this.tickInterval != null)
+ {
+ // if interval is a number or can be converted to one, use it.
+ // Assume it is in SECONDS!!!
+ if (Number(this.tickInterval)) {
+ this.daTickInterval = [Number(this.tickInterval), 'seconds'];
+ }
+ // else, parse out something we can build from.
+ else if (typeof this.tickInterval == "string") {
+ var parts = this.tickInterval.split(' ');
+ if (parts.length == 1) {
+ this.daTickInterval = [1, parts[0]];
+ }
+ else if (parts.length == 2) {
+ this.daTickInterval = [parts[0], parts[1]];
+ }
+ }
+ }
+
+ // if min and max are same, space them out a bit
+ if (min == max) {
+ var adj = 24*60*60*500; // 1/2 day
+ min -= adj;
+ max += adj;
+ }
+
+ range = max - min;
+
+ var optNumTicks = 2 + parseInt(Math.max(0, dim-100)/100, 10);
+
+
+ var rmin, rmax;
+
+ rmin = (this.min != null) ? new $.jsDate(this.min).getTime() : min - range/2*(this.padMin - 1);
+ rmax = (this.max != null) ? new $.jsDate(this.max).getTime() : max + range/2*(this.padMax - 1);
+ this.min = rmin;
+ this.max = rmax;
+ range = this.max - this.min;
+
+ if (this.numberTicks == null){
+ // if tickInterval is specified by user, we will ignore computed maximum.
+ // max will be equal or greater to fit even # of ticks.
+ if (this.daTickInterval != null) {
+ var nc = new $.jsDate(this.max).diff(this.min, this.daTickInterval[1], true);
+ this.numberTicks = Math.ceil(nc/this.daTickInterval[0]) +1;
+ // this.max = new $.jsDate(this.min).add(this.numberTicks-1, this.daTickInterval[1]).getTime();
+ this.max = new $.jsDate(this.min).add((this.numberTicks-1) * this.daTickInterval[0], this.daTickInterval[1]).getTime();
+ }
+ else if (dim > 200) {
+ this.numberTicks = parseInt(3+(dim-200)/100, 10);
+ }
+ else {
+ this.numberTicks = 2;
+ }
+ }
+
+ insetMult = range / (this.numberTicks-1)/1000;
+
+ if (this.daTickInterval == null) {
+ this.daTickInterval = [insetMult, 'seconds'];
+ }
+
+
+ for (var i=0; i<this.numberTicks; i++){
+ var min = new $.jsDate(this.min);
+ tt = min.add(i*this.daTickInterval[0], this.daTickInterval[1]).getTime();
+ var t = new this.tickRenderer(this.tickOptions);
+ // var t = new $.jqplot.AxisTickRenderer(this.tickOptions);
+ if (!this.showTicks) {
+ t.showLabel = false;
+ t.showMark = false;
+ }
+ else if (!this.showTickMarks) {
+ t.showMark = false;
+ }
+ t.setTick(tt, this.name);
+ this._ticks.push(t);
+ }
+ }
+
+ if (this.tickInset) {
+ this.min = this.min - this.tickInset * insetMult;
+ this.max = this.max + this.tickInset * insetMult;
+ }
+
+ if (this._daTickInterval == null) {
+ this._daTickInterval = this.daTickInterval;
+ }
+
+ ticks = null;
+ };
+
+})(jQuery);
+
diff --git a/js/jqplot/plugins/jqplot.highlighter.js b/js/jqplot/plugins/jqplot.highlighter.js
new file mode 100644
index 0000000000..2e8c5da936
--- /dev/null
+++ b/js/jqplot/plugins/jqplot.highlighter.js
@@ -0,0 +1,465 @@
+/**
+ * jqPlot
+ * Pure JavaScript plotting plugin using jQuery
+ *
+ * Version: 1.0.4
+ * Revision: 1121
+ *
+ * Copyright (c) 2009-2012 Chris Leonello
+ * jqPlot is currently available for use in all personal or commercial projects
+ * under both the MIT (http://www.opensource.org/licenses/mit-license.php) and GPL
+ * version 2.0 (http://www.gnu.org/licenses/gpl-2.0.html) licenses. This means that you can
+ * choose the license that best suits your project and use it accordingly.
+ *
+ * Although not required, the author would appreciate an email letting him
+ * know of any substantial use of jqPlot. You can reach the author at:
+ * chris at jqplot dot com or see http://www.jqplot.com/info.php .
+ *
+ * If you are feeling kind and generous, consider supporting the project by
+ * making a donation at: http://www.jqplot.com/donate.php .
+ *
+ * sprintf functions contained in jqplot.sprintf.js by Ash Searle:
+ *
+ * version 2007.04.27
+ * author Ash Searle
+ * http://hexmen.com/blog/2007/03/printf-sprintf/
+ * http://hexmen.com/js/sprintf.js
+ * The author (Ash Searle) has placed this code in the public domain:
+ * "This code is unrestricted: you are free to use it however you like."
+ *
+ */
+(function($) {
+ $.jqplot.eventListenerHooks.push(['jqplotMouseMove', handleMove]);
+
+ /**
+ * Class: $.jqplot.Highlighter
+ * Plugin which will highlight data points when they are moused over.
+ *
+ * To use this plugin, include the js
+ * file in your source:
+ *
+ * > <script type="text/javascript" src="plugins/jqplot.highlighter.js"></script>
+ *
+ * A tooltip providing information about the data point is enabled by default.
+ * To disable the tooltip, set "showTooltip" to false.
+ *
+ * You can control what data is displayed in the tooltip with various
+ * options. The "tooltipAxes" option controls wether the x, y or both
+ * data values are displayed.
+ *
+ * Some chart types (e.g. hi-low-close) have more than one y value per
+ * data point. To display the additional values in the tooltip, set the
+ * "yvalues" option to the desired number of y values present (3 for a hlc chart).
+ *
+ * By default, data values will be formatted with the same formatting
+ * specifiers as used to format the axis ticks. A custom format code
+ * can be supplied with the tooltipFormatString option. This will apply
+ * to all values in the tooltip.
+ *
+ * For more complete control, the "formatString" option can be set. This
+ * Allows conplete control over tooltip formatting. Values are passed to
+ * the format string in an order determined by the "tooltipAxes" and "yvalues"
+ * options. So, if you have a hi-low-close chart and you just want to display
+ * the hi-low-close values in the tooltip, you could set a formatString like:
+ *
+ * > highlighter: {
+ * > tooltipAxes: 'y',
+ * > yvalues: 3,
+ * > formatString:'<table class="jqplot-highlighter">
+ * > <tr><td>hi:</td><td>%s</td></tr>
+ * > <tr><td>low:</td><td>%s</td></tr>
+ * > <tr><td>close:</td><td>%s</td></tr></table>'
+ * > }
+ *
+ */
+ $.jqplot.Highlighter = function(options) {
+ // Group: Properties
+ //
+ //prop: show
+ // true to show the highlight.
+ this.show = $.jqplot.config.enablePlugins;
+ // prop: markerRenderer
+ // Renderer used to draw the marker of the highlighted point.
+ // Renderer will assimilate attributes from the data point being highlighted,
+ // so no attributes need set on the renderer directly.
+ // Default is to turn off shadow drawing on the highlighted point.
+ this.markerRenderer = new $.jqplot.MarkerRenderer({shadow:false});
+ // prop: showMarker
+ // true to show the marker
+ this.showMarker = true;
+ // prop: lineWidthAdjust
+ // Pixels to add to the lineWidth of the highlight.
+ this.lineWidthAdjust = 2.5;
+ // prop: sizeAdjust
+ // Pixels to add to the overall size of the highlight.
+ this.sizeAdjust = 5;
+ // prop: showTooltip
+ // Show a tooltip with data point values.
+ this.showTooltip = true;
+ // prop: tooltipLocation
+ // Where to position tooltip, 'n', 'ne', 'e', 'se', 's', 'sw', 'w', 'nw'
+ this.tooltipLocation = 'nw';
+ // prop: fadeTooltip
+ // true = fade in/out tooltip, flase = show/hide tooltip
+ this.fadeTooltip = true;
+ // prop: tooltipFadeSpeed
+ // 'slow', 'def', 'fast', or number of milliseconds.
+ this.tooltipFadeSpeed = "fast";
+ // prop: tooltipOffset
+ // Pixel offset of tooltip from the highlight.
+ this.tooltipOffset = 2;
+ // prop: tooltipAxes
+ // Which axes to display in tooltip, 'x', 'y' or 'both', 'xy' or 'yx'
+ // 'both' and 'xy' are equivalent, 'yx' reverses order of labels.
+ this.tooltipAxes = 'both';
+ // prop; tooltipSeparator
+ // String to use to separate x and y axes in tooltip.
+ this.tooltipSeparator = ', ';
+ // prop; tooltipContentEditor
+ // Function used to edit/augment/replace the formatted tooltip contents.
+ // Called as str = tooltipContentEditor(str, seriesIndex, pointIndex)
+ // where str is the generated tooltip html and seriesIndex and pointIndex identify
+ // the data point being highlighted. Should return the html for the tooltip contents.
+ this.tooltipContentEditor = null;
+ // prop: useAxesFormatters
+ // Use the x and y axes formatters to format the text in the tooltip.
+ this.useAxesFormatters = true;
+ // prop: tooltipFormatString
+ // sprintf format string for the tooltip.
+ // Uses Ash Searle's javascript sprintf implementation
+ // found here: http://hexmen.com/blog/2007/03/printf-sprintf/
+ // See http://perldoc.perl.org/functions/sprintf.html for reference.
+ // Additional "p" and "P" format specifiers added by Chris Leonello.
+ this.tooltipFormatString = '%.5P';
+ // prop: formatString
+ // alternative to tooltipFormatString
+ // will format the whole tooltip text, populating with x, y values as
+ // indicated by tooltipAxes option. So, you could have a tooltip like:
+ // 'Date: %s, number of cats: %d' to format the whole tooltip at one go.
+ // If useAxesFormatters is true, values will be formatted according to
+ // Axes formatters and you can populate your tooltip string with
+ // %s placeholders.
+ this.formatString = null;
+ // prop: yvalues
+ // Number of y values to expect in the data point array.
+ // Typically this is 1. Certain plots, like OHLC, will
+ // have more y values in each data point array.
+ this.yvalues = 1;
+ // prop: bringSeriesToFront
+ // This option requires jQuery 1.4+
+ // True to bring the series of the highlighted point to the front
+ // of other series.
+ this.bringSeriesToFront = false;
+ this._tooltipElem;
+ this.isHighlighting = false;
+ this.currentNeighbor = null;
+
+ $.extend(true, this, options);
+ };
+
+ var locations = ['nw', 'n', 'ne', 'e', 'se', 's', 'sw', 'w'];
+ var locationIndicies = {'nw':0, 'n':1, 'ne':2, 'e':3, 'se':4, 's':5, 'sw':6, 'w':7};
+ var oppositeLocations = ['se', 's', 'sw', 'w', 'nw', 'n', 'ne', 'e'];
+
+ // axis.renderer.tickrenderer.formatter
+
+ // called with scope of plot
+ $.jqplot.Highlighter.init = function (target, data, opts){
+ var options = opts || {};
+ // add a highlighter attribute to the plot
+ this.plugins.highlighter = new $.jqplot.Highlighter(options.highlighter);
+ };
+
+ // called within scope of series
+ $.jqplot.Highlighter.parseOptions = function (defaults, options) {
+ // Add a showHighlight option to the series
+ // and set it to true by default.
+ this.showHighlight = true;
+ };
+
+ // called within context of plot
+ // create a canvas which we can draw on.
+ // insert it before the eventCanvas, so eventCanvas will still capture events.
+ $.jqplot.Highlighter.postPlotDraw = function() {
+ // Memory Leaks patch
+ if (this.plugins.highlighter && this.plugins.highlighter.highlightCanvas) {
+ this.plugins.highlighter.highlightCanvas.resetCanvas();
+ this.plugins.highlighter.highlightCanvas = null;
+ }
+
+ if (this.plugins.highlighter && this.plugins.highlighter._tooltipElem) {
+ this.plugins.highlighter._tooltipElem.emptyForce();
+ this.plugins.highlighter._tooltipElem = null;
+ }
+
+ this.plugins.highlighter.highlightCanvas = new $.jqplot.GenericCanvas();
+
+ this.eventCanvas._elem.before(this.plugins.highlighter.highlightCanvas.createElement(this._gridPadding, 'jqplot-highlight-canvas', this._plotDimensions, this));
+ this.plugins.highlighter.highlightCanvas.setContext();
+
+ var elem = document.createElement('div');
+ this.plugins.highlighter._tooltipElem = $(elem);
+ elem = null;
+ this.plugins.highlighter._tooltipElem.addClass('jqplot-highlighter-tooltip');
+ this.plugins.highlighter._tooltipElem.css({position:'absolute', display:'none'});
+
+ this.eventCanvas._elem.before(this.plugins.highlighter._tooltipElem);
+ };
+
+ $.jqplot.preInitHooks.push($.jqplot.Highlighter.init);
+ $.jqplot.preParseSeriesOptionsHooks.push($.jqplot.Highlighter.parseOptions);
+ $.jqplot.postDrawHooks.push($.jqplot.Highlighter.postPlotDraw);
+
+ function draw(plot, neighbor) {
+ var hl = plot.plugins.highlighter;
+ var s = plot.series[neighbor.seriesIndex];
+ var smr = s.markerRenderer;
+ var mr = hl.markerRenderer;
+ mr.style = smr.style;
+ mr.lineWidth = smr.lineWidth + hl.lineWidthAdjust;
+ mr.size = smr.size + hl.sizeAdjust;
+ var rgba = $.jqplot.getColorComponents(smr.color);
+ var newrgb = [rgba[0], rgba[1], rgba[2]];
+ var alpha = (rgba[3] >= 0.6) ? rgba[3]*0.6 : rgba[3]*(2-rgba[3]);
+ mr.color = 'rgba('+newrgb[0]+','+newrgb[1]+','+newrgb[2]+','+alpha+')';
+ mr.init();
+ mr.draw(s.gridData[neighbor.pointIndex][0], s.gridData[neighbor.pointIndex][1], hl.highlightCanvas._ctx);
+ }
+
+ function showTooltip(plot, series, neighbor) {
+ // neighbor looks like: {seriesIndex: i, pointIndex:j, gridData:p, data:s.data[j]}
+ // gridData should be x,y pixel coords on the grid.
+ // add the plot._gridPadding to that to get x,y in the target.
+ var hl = plot.plugins.highlighter;
+ var elem = hl._tooltipElem;
+ var serieshl = series.highlighter || {};
+
+ var opts = $.extend(true, {}, hl, serieshl);
+
+ if (opts.useAxesFormatters) {
+ var xf = series._xaxis._ticks[0].formatter;
+ var yf = series._yaxis._ticks[0].formatter;
+ var xfstr = series._xaxis._ticks[0].formatString;
+ var yfstr = series._yaxis._ticks[0].formatString;
+ var str;
+ var xstr = xf(xfstr, neighbor.data[0]);
+ var ystrs = [];
+ for (var i=1; i<opts.yvalues+1; i++) {
+ ystrs.push(yf(yfstr, neighbor.data[i]));
+ }
+ if (typeof opts.formatString === 'string') {
+ switch (opts.tooltipAxes) {
+ case 'both':
+ case 'xy':
+ ystrs.unshift(xstr);
+ ystrs.unshift(opts.formatString);
+ str = $.jqplot.sprintf.apply($.jqplot.sprintf, ystrs);
+ break;
+ case 'yx':
+ ystrs.push(xstr);
+ ystrs.unshift(opts.formatString);
+ str = $.jqplot.sprintf.apply($.jqplot.sprintf, ystrs);
+ break;
+ case 'x':
+ str = $.jqplot.sprintf.apply($.jqplot.sprintf, [opts.formatString, xstr]);
+ break;
+ case 'y':
+ ystrs.unshift(opts.formatString);
+ str = $.jqplot.sprintf.apply($.jqplot.sprintf, ystrs);
+ break;
+ default: // same as xy
+ ystrs.unshift(xstr);
+ ystrs.unshift(opts.formatString);
+ str = $.jqplot.sprintf.apply($.jqplot.sprintf, ystrs);
+ break;
+ }
+ }
+ else {
+ switch (opts.tooltipAxes) {
+ case 'both':
+ case 'xy':
+ str = xstr;
+ for (var i=0; i<ystrs.length; i++) {
+ str += opts.tooltipSeparator + ystrs[i];
+ }
+ break;
+ case 'yx':
+ str = '';
+ for (var i=0; i<ystrs.length; i++) {
+ str += ystrs[i] + opts.tooltipSeparator;
+ }
+ str += xstr;
+ break;
+ case 'x':
+ str = xstr;
+ break;
+ case 'y':
+ str = ystrs.join(opts.tooltipSeparator);
+ break;
+ default: // same as 'xy'
+ str = xstr;
+ for (var i=0; i<ystrs.length; i++) {
+ str += opts.tooltipSeparator + ystrs[i];
+ }
+ break;
+
+ }
+ }
+ }
+ else {
+ var str;
+ if (typeof opts.formatString === 'string') {
+ str = $.jqplot.sprintf.apply($.jqplot.sprintf, [opts.formatString].concat(neighbor.data));
+ }
+
+ else {
+ if (opts.tooltipAxes == 'both' || opts.tooltipAxes == 'xy') {
+ str = $.jqplot.sprintf(opts.tooltipFormatString, neighbor.data[0]) + opts.tooltipSeparator + $.jqplot.sprintf(opts.tooltipFormatString, neighbor.data[1]);
+ }
+ else if (opts.tooltipAxes == 'yx') {
+ str = $.jqplot.sprintf(opts.tooltipFormatString, neighbor.data[1]) + opts.tooltipSeparator + $.jqplot.sprintf(opts.tooltipFormatString, neighbor.data[0]);
+ }
+ else if (opts.tooltipAxes == 'x') {
+ str = $.jqplot.sprintf(opts.tooltipFormatString, neighbor.data[0]);
+ }
+ else if (opts.tooltipAxes == 'y') {
+ str = $.jqplot.sprintf(opts.tooltipFormatString, neighbor.data[1]);
+ }
+ }
+ }
+ if ($.isFunction(opts.tooltipContentEditor)) {
+ // args str, seriesIndex, pointIndex are essential so the hook can look up
+ // extra data for the point.
+ str = opts.tooltipContentEditor(str, neighbor.seriesIndex, neighbor.pointIndex, plot);
+ }
+ elem.html(str);
+ var gridpos = {x:neighbor.gridData[0], y:neighbor.gridData[1]};
+ var ms = 0;
+ var fact = 0.707;
+ if (series.markerRenderer.show == true) {
+ ms = (series.markerRenderer.size + opts.sizeAdjust)/2;
+ }
+
+ var loc = locations;
+ if (series.fillToZero && series.fill && neighbor.data[1] < 0) {
+ loc = oppositeLocations;
+ }
+
+ switch (loc[locationIndicies[opts.tooltipLocation]]) {
+ case 'nw':
+ var x = gridpos.x + plot._gridPadding.left - elem.outerWidth(true) - opts.tooltipOffset - fact * ms;
+ var y = gridpos.y + plot._gridPadding.top - opts.tooltipOffset - elem.outerHeight(true) - fact * ms;
+ break;
+ case 'n':
+ var x = gridpos.x + plot._gridPadding.left - elem.outerWidth(true)/2;
+ var y = gridpos.y + plot._gridPadding.top - opts.tooltipOffset - elem.outerHeight(true) - ms;
+ break;
+ case 'ne':
+ var x = gridpos.x + plot._gridPadding.left + opts.tooltipOffset + fact * ms;
+ var y = gridpos.y + plot._gridPadding.top - opts.tooltipOffset - elem.outerHeight(true) - fact * ms;
+ break;
+ case 'e':
+ var x = gridpos.x + plot._gridPadding.left + opts.tooltipOffset + ms;
+ var y = gridpos.y + plot._gridPadding.top - elem.outerHeight(true)/2;
+ break;
+ case 'se':
+ var x = gridpos.x + plot._gridPadding.left + opts.tooltipOffset + fact * ms;
+ var y = gridpos.y + plot._gridPadding.top + opts.tooltipOffset + fact * ms;
+ break;
+ case 's':
+ var x = gridpos.x + plot._gridPadding.left - elem.outerWidth(true)/2;
+ var y = gridpos.y + plot._gridPadding.top + opts.tooltipOffset + ms;
+ break;
+ case 'sw':
+ var x = gridpos.x + plot._gridPadding.left - elem.outerWidth(true) - opts.tooltipOffset - fact * ms;
+ var y = gridpos.y + plot._gridPadding.top + opts.tooltipOffset + fact * ms;
+ break;
+ case 'w':
+ var x = gridpos.x + plot._gridPadding.left - elem.outerWidth(true) - opts.tooltipOffset - ms;
+ var y = gridpos.y + plot._gridPadding.top - elem.outerHeight(true)/2;
+ break;
+ default: // same as 'nw'
+ var x = gridpos.x + plot._gridPadding.left - elem.outerWidth(true) - opts.tooltipOffset - fact * ms;
+ var y = gridpos.y + plot._gridPadding.top - opts.tooltipOffset - elem.outerHeight(true) - fact * ms;
+ break;
+ }
+ elem.css('left', x);
+ elem.css('top', y);
+ if (opts.fadeTooltip) {
+ // Fix for stacked up animations. Thnanks Trevor!
+ elem.stop(true,true).fadeIn(opts.tooltipFadeSpeed);
+ }
+ else {
+ elem.show();
+ }
+ elem = null;
+
+ }
+
+ function handleMove(ev, gridpos, datapos, neighbor, plot) {
+ var hl = plot.plugins.highlighter;
+ var c = plot.plugins.cursor;
+ if (hl.show) {
+ if (neighbor == null && hl.isHighlighting) {
+ var evt = jQuery.Event('jqplotHighlighterUnhighlight');
+ plot.target.trigger(evt);
+
+ var ctx = hl.highlightCanvas._ctx;
+ ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
+ if (hl.fadeTooltip) {
+ hl._tooltipElem.fadeOut(hl.tooltipFadeSpeed);
+ }
+ else {
+ hl._tooltipElem.hide();
+ }
+ if (hl.bringSeriesToFront) {
+ plot.restorePreviousSeriesOrder();
+ }
+ hl.isHighlighting = false;
+ hl.currentNeighbor = null;
+ ctx = null;
+ }
+ else if (neighbor != null && plot.series[neighbor.seriesIndex].showHighlight && !hl.isHighlighting) {
+ var evt = jQuery.Event('jqplotHighlighterHighlight');
+ evt.which = ev.which;
+ evt.pageX = ev.pageX;
+ evt.pageY = ev.pageY;
+ var ins = [neighbor.seriesIndex, neighbor.pointIndex, neighbor.data, plot];
+ plot.target.trigger(evt, ins);
+
+ hl.isHighlighting = true;
+ hl.currentNeighbor = neighbor;
+ if (hl.showMarker) {
+ draw(plot, neighbor);
+ }
+ if (hl.showTooltip && (!c || !c._zoom.started)) {
+ showTooltip(plot, plot.series[neighbor.seriesIndex], neighbor);
+ }
+ if (hl.bringSeriesToFront) {
+ plot.moveSeriesToFront(neighbor.seriesIndex);
+ }
+ }
+ // check to see if we're highlighting the wrong point.
+ else if (neighbor != null && hl.isHighlighting && hl.currentNeighbor != neighbor) {
+ // highlighting the wrong point.
+
+ // if new series allows highlighting, highlight new point.
+ if (plot.series[neighbor.seriesIndex].showHighlight) {
+ var ctx = hl.highlightCanvas._ctx;
+ ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
+ hl.isHighlighting = true;
+ hl.currentNeighbor = neighbor;
+ if (hl.showMarker) {
+ draw(plot, neighbor);
+ }
+ if (hl.showTooltip && (!c || !c._zoom.started)) {
+ showTooltip(plot, plot.series[neighbor.seriesIndex], neighbor);
+ }
+ if (hl.bringSeriesToFront) {
+ plot.moveSeriesToFront(neighbor.seriesIndex);
+ }
+ }
+ }
+ }
+ }
+})(jQuery); \ No newline at end of file
diff --git a/js/jqplot/plugins/jqplot.pieRenderer.js b/js/jqplot/plugins/jqplot.pieRenderer.js
new file mode 100644
index 0000000000..2479885ce5
--- /dev/null
+++ b/js/jqplot/plugins/jqplot.pieRenderer.js
@@ -0,0 +1,904 @@
+/**
+ * jqPlot
+ * Pure JavaScript plotting plugin using jQuery
+ *
+ * Version: 1.0.4
+ * Revision: 1121
+ *
+ * Copyright (c) 2009-2012 Chris Leonello
+ * jqPlot is currently available for use in all personal or commercial projects
+ * under both the MIT (http://www.opensource.org/licenses/mit-license.php) and GPL
+ * version 2.0 (http://www.gnu.org/licenses/gpl-2.0.html) licenses. This means that you can
+ * choose the license that best suits your project and use it accordingly.
+ *
+ * Although not required, the author would appreciate an email letting him
+ * know of any substantial use of jqPlot. You can reach the author at:
+ * chris at jqplot dot com or see http://www.jqplot.com/info.php .
+ *
+ * If you are feeling kind and generous, consider supporting the project by
+ * making a donation at: http://www.jqplot.com/donate.php .
+ *
+ * sprintf functions contained in jqplot.sprintf.js by Ash Searle:
+ *
+ * version 2007.04.27
+ * author Ash Searle
+ * http://hexmen.com/blog/2007/03/printf-sprintf/
+ * http://hexmen.com/js/sprintf.js
+ * The author (Ash Searle) has placed this code in the public domain:
+ * "This code is unrestricted: you are free to use it however you like."
+ *
+ */
+(function($) {
+ /**
+ * Class: $.jqplot.PieRenderer
+ * Plugin renderer to draw a pie chart.
+ * x values, if present, will be used as slice labels.
+ * y values give slice size.
+ *
+ * To use this renderer, you need to include the
+ * pie renderer plugin, for example:
+ *
+ * > <script type="text/javascript" src="plugins/jqplot.pieRenderer.js"></script>
+ *
+ * Properties described here are passed into the $.jqplot function
+ * as options on the series renderer. For example:
+ *
+ * > plot2 = $.jqplot('chart2', [s1, s2], {
+ * > seriesDefaults: {
+ * > renderer:$.jqplot.PieRenderer,
+ * > rendererOptions:{
+ * > sliceMargin: 2,
+ * > startAngle: -90
+ * > }
+ * > }
+ * > });
+ *
+ * A pie plot will trigger events on the plot target
+ * according to user interaction. All events return the event object,
+ * the series index, the point (slice) index, and the point data for
+ * the appropriate slice.
+ *
+ * 'jqplotDataMouseOver' - triggered when user mouseing over a slice.
+ * 'jqplotDataHighlight' - triggered the first time user mouses over a slice,
+ * if highlighting is enabled.
+ * 'jqplotDataUnhighlight' - triggered when a user moves the mouse out of
+ * a highlighted slice.
+ * 'jqplotDataClick' - triggered when the user clicks on a slice.
+ * 'jqplotDataRightClick' - tiggered when the user right clicks on a slice if
+ * the "captureRightClick" option is set to true on the plot.
+ */
+ $.jqplot.PieRenderer = function(){
+ $.jqplot.LineRenderer.call(this);
+ };
+
+ $.jqplot.PieRenderer.prototype = new $.jqplot.LineRenderer();
+ $.jqplot.PieRenderer.prototype.constructor = $.jqplot.PieRenderer;
+
+ // called with scope of a series
+ $.jqplot.PieRenderer.prototype.init = function(options, plot) {
+ // Group: Properties
+ //
+ // prop: diameter
+ // Outer diameter of the pie, auto computed by default
+ this.diameter = null;
+ // prop: padding
+ // padding between the pie and plot edges, legend, etc.
+ this.padding = 20;
+ // prop: sliceMargin
+ // angular spacing between pie slices in degrees.
+ this.sliceMargin = 0;
+ // prop: fill
+ // true or false, wether to fil the slices.
+ this.fill = true;
+ // prop: shadowOffset
+ // offset of the shadow from the slice and offset of
+ // each succesive stroke of the shadow from the last.
+ this.shadowOffset = 2;
+ // prop: shadowAlpha
+ // transparency of the shadow (0 = transparent, 1 = opaque)
+ this.shadowAlpha = 0.07;
+ // prop: shadowDepth
+ // number of strokes to apply to the shadow,
+ // each stroke offset shadowOffset from the last.
+ this.shadowDepth = 5;
+ // prop: highlightMouseOver
+ // True to highlight slice when moused over.
+ // This must be false to enable highlightMouseDown to highlight when clicking on a slice.
+ this.highlightMouseOver = true;
+ // prop: highlightMouseDown
+ // True to highlight when a mouse button is pressed over a slice.
+ // This will be disabled if highlightMouseOver is true.
+ this.highlightMouseDown = false;
+ // prop: highlightColors
+ // an array of colors to use when highlighting a slice.
+ this.highlightColors = [];
+ // prop: dataLabels
+ // Either 'label', 'value', 'percent' or an array of labels to place on the pie slices.
+ // Defaults to percentage of each pie slice.
+ this.dataLabels = 'percent';
+ // prop: showDataLabels
+ // true to show data labels on slices.
+ this.showDataLabels = false;
+ // prop: dataLabelFormatString
+ // Format string for data labels. If none, '%s' is used for "label" and for arrays, '%d' for value and '%d%%' for percentage.
+ this.dataLabelFormatString = null;
+ // prop: dataLabelThreshold
+ // Threshhold in percentage (0-100) of pie area, below which no label will be displayed.
+ // This applies to all label types, not just to percentage labels.
+ this.dataLabelThreshold = 3;
+ // prop: dataLabelPositionFactor
+ // A Multiplier (0-1) of the pie radius which controls position of label on slice.
+ // Increasing will slide label toward edge of pie, decreasing will slide label toward center of pie.
+ this.dataLabelPositionFactor = 0.52;
+ // prop: dataLabelNudge
+ // Number of pixels to slide the label away from (+) or toward (-) the center of the pie.
+ this.dataLabelNudge = 2;
+ // prop: dataLabelCenterOn
+ // True to center the data label at its position.
+ // False to set the inside facing edge of the label at its position.
+ this.dataLabelCenterOn = true;
+ // prop: startAngle
+ // Angle to start drawing pie in degrees.
+ // According to orientation of canvas coordinate system:
+ // 0 = on the positive x axis
+ // -90 = on the positive y axis.
+ // 90 = on the negaive y axis.
+ // 180 or - 180 = on the negative x axis.
+ this.startAngle = 0;
+ this.tickRenderer = $.jqplot.PieTickRenderer;
+ // Used as check for conditions where pie shouldn't be drawn.
+ this._drawData = true;
+ this._type = 'pie';
+
+ // if user has passed in highlightMouseDown option and not set highlightMouseOver, disable highlightMouseOver
+ if (options.highlightMouseDown && options.highlightMouseOver == null) {
+ options.highlightMouseOver = false;
+ }
+
+ $.extend(true, this, options);
+
+ if (this.sliceMargin < 0) {
+ this.sliceMargin = 0;
+ }
+
+ this._diameter = null;
+ this._radius = null;
+ // array of [start,end] angles arrays, one for each slice. In radians.
+ this._sliceAngles = [];
+ // index of the currenty highlighted point, if any
+ this._highlightedPoint = null;
+
+ // set highlight colors if none provided
+ if (this.highlightColors.length == 0) {
+ for (var i=0; i<this.seriesColors.length; i++){
+ var rgba = $.jqplot.getColorComponents(this.seriesColors[i]);
+ var newrgb = [rgba[0], rgba[1], rgba[2]];
+ var sum = newrgb[0] + newrgb[1] + newrgb[2];
+ for (var j=0; j<3; j++) {
+ // when darkening, lowest color component can be is 60.
+ newrgb[j] = (sum > 570) ? newrgb[j] * 0.8 : newrgb[j] + 0.3 * (255 - newrgb[j]);
+ newrgb[j] = parseInt(newrgb[j], 10);
+ }
+ this.highlightColors.push('rgb('+newrgb[0]+','+newrgb[1]+','+newrgb[2]+')');
+ }
+ }
+
+ this.highlightColorGenerator = new $.jqplot.ColorGenerator(this.highlightColors);
+
+ plot.postParseOptionsHooks.addOnce(postParseOptions);
+ plot.postInitHooks.addOnce(postInit);
+ plot.eventListenerHooks.addOnce('jqplotMouseMove', handleMove);
+ plot.eventListenerHooks.addOnce('jqplotMouseDown', handleMouseDown);
+ plot.eventListenerHooks.addOnce('jqplotMouseUp', handleMouseUp);
+ plot.eventListenerHooks.addOnce('jqplotClick', handleClick);
+ plot.eventListenerHooks.addOnce('jqplotRightClick', handleRightClick);
+ plot.postDrawHooks.addOnce(postPlotDraw);
+ };
+
+ $.jqplot.PieRenderer.prototype.setGridData = function(plot) {
+ // set gridData property. This will hold angle in radians of each data point.
+ var stack = [];
+ var td = [];
+ var sa = this.startAngle/180*Math.PI;
+ var tot = 0;
+ // don't know if we have any valid data yet, so set plot to not draw.
+ this._drawData = false;
+ for (var i=0; i<this.data.length; i++){
+ if (this.data[i][1] != 0) {
+ // we have data, O.K. to draw.
+ this._drawData = true;
+ }
+ stack.push(this.data[i][1]);
+ td.push([this.data[i][0]]);
+ if (i>0) {
+ stack[i] += stack[i-1];
+ }
+ tot += this.data[i][1];
+ }
+ var fact = Math.PI*2/stack[stack.length - 1];
+
+ for (var i=0; i<stack.length; i++) {
+ td[i][1] = stack[i] * fact;
+ td[i][2] = this.data[i][1]/tot;
+ }
+ this.gridData = td;
+ };
+
+ $.jqplot.PieRenderer.prototype.makeGridData = function(data, plot) {
+ var stack = [];
+ var td = [];
+ var tot = 0;
+ var sa = this.startAngle/180*Math.PI;
+ // don't know if we have any valid data yet, so set plot to not draw.
+ this._drawData = false;
+ for (var i=0; i<data.length; i++){
+ if (this.data[i][1] != 0) {
+ // we have data, O.K. to draw.
+ this._drawData = true;
+ }
+ stack.push(data[i][1]);
+ td.push([data[i][0]]);
+ if (i>0) {
+ stack[i] += stack[i-1];
+ }
+ tot += data[i][1];
+ }
+ var fact = Math.PI*2/stack[stack.length - 1];
+
+ for (var i=0; i<stack.length; i++) {
+ td[i][1] = stack[i] * fact;
+ td[i][2] = data[i][1]/tot;
+ }
+ return td;
+ };
+
+ function calcRadiusAdjustment(ang) {
+ return Math.sin((ang - (ang-Math.PI) / 8 / Math.PI )/2.0);
+ }
+
+ function calcRPrime(ang1, ang2, sliceMargin, fill, lineWidth) {
+ var rprime = 0;
+ var ang = ang2 - ang1;
+ var absang = Math.abs(ang);
+ var sm = sliceMargin;
+ if (fill == false) {
+ sm += lineWidth;
+ }
+
+ if (sm > 0 && absang > 0.01 && absang < 6.282) {
+ rprime = parseFloat(sm) / 2.0 / calcRadiusAdjustment(ang);
+ }
+
+ return rprime;
+ }
+
+ $.jqplot.PieRenderer.prototype.drawSlice = function (ctx, ang1, ang2, color, isShadow) {
+ if (this._drawData) {
+ var r = this._radius;
+ var fill = this.fill;
+ var lineWidth = this.lineWidth;
+ var sm = this.sliceMargin;
+ if (this.fill == false) {
+ sm += this.lineWidth;
+ }
+ ctx.save();
+ ctx.translate(this._center[0], this._center[1]);
+
+ var rprime = calcRPrime(ang1, ang2, this.sliceMargin, this.fill, this.lineWidth);
+
+ var transx = rprime * Math.cos((ang1 + ang2) / 2.0);
+ var transy = rprime * Math.sin((ang1 + ang2) / 2.0);
+
+ if ((ang2 - ang1) <= Math.PI) {
+ r -= rprime;
+ }
+ else {
+ r += rprime;
+ }
+
+ ctx.translate(transx, transy);
+
+ if (isShadow) {
+ for (var i=0, l=this.shadowDepth; i<l; i++) {
+ ctx.save();
+ ctx.translate(this.shadowOffset*Math.cos(this.shadowAngle/180*Math.PI), this.shadowOffset*Math.sin(this.shadowAngle/180*Math.PI));
+ doDraw(r);
+ }
+ for (var i=0, l=this.shadowDepth; i<l; i++) {
+ ctx.restore();
+ }
+ }
+
+ else {
+ doDraw(r);
+ }
+ ctx.restore();
+ }
+
+ function doDraw (rad) {
+ // Fix for IE and Chrome that can't seem to draw circles correctly.
+ // ang2 should always be <= 2 pi since that is the way the data is converted.
+ // 2Pi = 6.2831853, Pi = 3.1415927
+ if (ang2 > 6.282 + this.startAngle) {
+ ang2 = 6.282 + this.startAngle;
+ if (ang1 > ang2) {
+ ang1 = 6.281 + this.startAngle;
+ }
+ }
+ // Fix for IE, where it can't seem to handle 0 degree angles. Also avoids
+ // ugly line on unfilled pies.
+ if (ang1 >= ang2) {
+ return;
+ }
+
+ ctx.beginPath();
+ ctx.fillStyle = color;
+ ctx.strokeStyle = color;
+ ctx.lineWidth = lineWidth;
+ ctx.arc(0, 0, rad, ang1, ang2, false);
+ ctx.lineTo(0,0);
+ ctx.closePath();
+
+ if (fill) {
+ ctx.fill();
+ }
+ else {
+ ctx.stroke();
+ }
+ }
+ };
+
+ // called with scope of series
+ $.jqplot.PieRenderer.prototype.draw = function (ctx, gd, options, plot) {
+ var i;
+ var opts = (options != undefined) ? options : {};
+ // offset and direction of offset due to legend placement
+ var offx = 0;
+ var offy = 0;
+ var trans = 1;
+ var colorGenerator = new $.jqplot.ColorGenerator(this.seriesColors);
+ if (options.legendInfo && options.legendInfo.placement == 'insideGrid') {
+ var li = options.legendInfo;
+ switch (li.location) {
+ case 'nw':
+ offx = li.width + li.xoffset;
+ break;
+ case 'w':
+ offx = li.width + li.xoffset;
+ break;
+ case 'sw':
+ offx = li.width + li.xoffset;
+ break;
+ case 'ne':
+ offx = li.width + li.xoffset;
+ trans = -1;
+ break;
+ case 'e':
+ offx = li.width + li.xoffset;
+ trans = -1;
+ break;
+ case 'se':
+ offx = li.width + li.xoffset;
+ trans = -1;
+ break;
+ case 'n':
+ offy = li.height + li.yoffset;
+ break;
+ case 's':
+ offy = li.height + li.yoffset;
+ trans = -1;
+ break;
+ default:
+ break;
+ }
+ }
+
+ var shadow = (opts.shadow != undefined) ? opts.shadow : this.shadow;
+ var fill = (opts.fill != undefined) ? opts.fill : this.fill;
+ var cw = ctx.canvas.width;
+ var ch = ctx.canvas.height;
+ var w = cw - offx - 2 * this.padding;
+ var h = ch - offy - 2 * this.padding;
+ var mindim = Math.min(w,h);
+ var d = mindim;
+
+ // Fixes issue #272. Thanks hugwijst!
+ // reset slice angles array.
+ this._sliceAngles = [];
+
+ var sm = this.sliceMargin;
+ if (this.fill == false) {
+ sm += this.lineWidth;
+ }
+
+ var rprime;
+ var maxrprime = 0;
+
+ var ang, ang1, ang2, shadowColor;
+ var sa = this.startAngle / 180 * Math.PI;
+
+ // have to pre-draw shadows, so loop throgh here and calculate some values also.
+ for (var i=0, l=gd.length; i<l; i++) {
+ ang1 = (i == 0) ? sa : gd[i-1][1] + sa;
+ ang2 = gd[i][1] + sa;
+
+ this._sliceAngles.push([ang1, ang2]);
+
+ rprime = calcRPrime(ang1, ang2, this.sliceMargin, this.fill, this.lineWidth);
+
+ if (Math.abs(ang2-ang1) > Math.PI) {
+ maxrprime = Math.max(rprime, maxrprime);
+ }
+ }
+
+ if (this.diameter != null && this.diameter > 0) {
+ this._diameter = this.diameter - 2*maxrprime;
+ }
+ else {
+ this._diameter = d - 2*maxrprime;
+ }
+
+ // Need to check for undersized pie. This can happen if
+ // plot area too small and legend is too big.
+ if (this._diameter < 6) {
+ $.jqplot.log('Diameter of pie too small, not rendering.');
+ return;
+ }
+
+ var r = this._radius = this._diameter/2;
+
+ this._center = [(cw - trans * offx)/2 + trans * offx + maxrprime * Math.cos(sa), (ch - trans*offy)/2 + trans * offy + maxrprime * Math.sin(sa)];
+
+ if (this.shadow) {
+ for (var i=0, l=gd.length; i<l; i++) {
+ shadowColor = 'rgba(0,0,0,'+this.shadowAlpha+')';
+ this.renderer.drawSlice.call (this, ctx, this._sliceAngles[i][0], this._sliceAngles[i][1], shadowColor, true);
+ }
+ }
+
+ for (var i=0; i<gd.length; i++) {
+
+ this.renderer.drawSlice.call (this, ctx, this._sliceAngles[i][0], this._sliceAngles[i][1], colorGenerator.next(), false);
+
+ if (this.showDataLabels && gd[i][2]*100 >= this.dataLabelThreshold) {
+ var fstr, avgang = (this._sliceAngles[i][0] + this._sliceAngles[i][1])/2, label;
+
+ if (this.dataLabels == 'label') {
+ fstr = this.dataLabelFormatString || '%s';
+ label = $.jqplot.sprintf(fstr, gd[i][0]);
+ }
+ else if (this.dataLabels == 'value') {
+ fstr = this.dataLabelFormatString || '%d';
+ label = $.jqplot.sprintf(fstr, this.data[i][1]);
+ }
+ else if (this.dataLabels == 'percent') {
+ fstr = this.dataLabelFormatString || '%d%%';
+ label = $.jqplot.sprintf(fstr, gd[i][2]*100);
+ }
+ else if (this.dataLabels.constructor == Array) {
+ fstr = this.dataLabelFormatString || '%s';
+ label = $.jqplot.sprintf(fstr, this.dataLabels[i]);
+ }
+
+ var fact = (this._radius ) * this.dataLabelPositionFactor + this.sliceMargin + this.dataLabelNudge;
+
+ var x = this._center[0] + Math.cos(avgang) * fact + this.canvas._offsets.left;
+ var y = this._center[1] + Math.sin(avgang) * fact + this.canvas._offsets.top;
+
+ var labelelem = $('<div class="jqplot-pie-series jqplot-data-label" style="position:absolute;">' + label + '</div>').insertBefore(plot.eventCanvas._elem);
+ if (this.dataLabelCenterOn) {
+ x -= labelelem.width()/2;
+ y -= labelelem.height()/2;
+ }
+ else {
+ x -= labelelem.width() * Math.sin(avgang/2);
+ y -= labelelem.height()/2;
+ }
+ x = Math.round(x);
+ y = Math.round(y);
+ labelelem.css({left: x, top: y});
+ }
+ }
+ };
+
+ $.jqplot.PieAxisRenderer = function() {
+ $.jqplot.LinearAxisRenderer.call(this);
+ };
+
+ $.jqplot.PieAxisRenderer.prototype = new $.jqplot.LinearAxisRenderer();
+ $.jqplot.PieAxisRenderer.prototype.constructor = $.jqplot.PieAxisRenderer;
+
+
+ // There are no traditional axes on a pie chart. We just need to provide
+ // dummy objects with properties so the plot will render.
+ // called with scope of axis object.
+ $.jqplot.PieAxisRenderer.prototype.init = function(options){
+ //
+ this.tickRenderer = $.jqplot.PieTickRenderer;
+ $.extend(true, this, options);
+ // I don't think I'm going to need _dataBounds here.
+ // have to go Axis scaling in a way to fit chart onto plot area
+ // and provide u2p and p2u functionality for mouse cursor, etc.
+ // for convienence set _dataBounds to 0 and 100 and
+ // set min/max to 0 and 100.
+ this._dataBounds = {min:0, max:100};
+ this.min = 0;
+ this.max = 100;
+ this.showTicks = false;
+ this.ticks = [];
+ this.showMark = false;
+ this.show = false;
+ };
+
+
+
+
+ $.jqplot.PieLegendRenderer = function(){
+ $.jqplot.TableLegendRenderer.call(this);
+ };
+
+ $.jqplot.PieLegendRenderer.prototype = new $.jqplot.TableLegendRenderer();
+ $.jqplot.PieLegendRenderer.prototype.constructor = $.jqplot.PieLegendRenderer;
+
+ /**
+ * Class: $.jqplot.PieLegendRenderer
+ * Legend Renderer specific to pie plots. Set by default
+ * when user creates a pie plot.
+ */
+ $.jqplot.PieLegendRenderer.prototype.init = function(options) {
+ // Group: Properties
+ //
+ // prop: numberRows
+ // Maximum number of rows in the legend. 0 or null for unlimited.
+ this.numberRows = null;
+ // prop: numberColumns
+ // Maximum number of columns in the legend. 0 or null for unlimited.
+ this.numberColumns = null;
+ $.extend(true, this, options);
+ };
+
+ // called with context of legend
+ $.jqplot.PieLegendRenderer.prototype.draw = function() {
+ var legend = this;
+ if (this.show) {
+ var series = this._series;
+
+
+ this._elem = $(document.createElement('table'));
+ this._elem.addClass('jqplot-table-legend');
+
+ var ss = {position:'absolute'};
+ if (this.background) {
+ ss['background'] = this.background;
+ }
+ if (this.border) {
+ ss['border'] = this.border;
+ }
+ if (this.fontSize) {
+ ss['fontSize'] = this.fontSize;
+ }
+ if (this.fontFamily) {
+ ss['fontFamily'] = this.fontFamily;
+ }
+ if (this.textColor) {
+ ss['textColor'] = this.textColor;
+ }
+ if (this.marginTop != null) {
+ ss['marginTop'] = this.marginTop;
+ }
+ if (this.marginBottom != null) {
+ ss['marginBottom'] = this.marginBottom;
+ }
+ if (this.marginLeft != null) {
+ ss['marginLeft'] = this.marginLeft;
+ }
+ if (this.marginRight != null) {
+ ss['marginRight'] = this.marginRight;
+ }
+
+ this._elem.css(ss);
+
+ // Pie charts legends don't go by number of series, but by number of data points
+ // in the series. Refactor things here for that.
+
+ var pad = false,
+ reverse = false,
+ nr,
+ nc;
+ var s = series[0];
+ var colorGenerator = new $.jqplot.ColorGenerator(s.seriesColors);
+
+ if (s.show) {
+ var pd = s.data;
+ if (this.numberRows) {
+ nr = this.numberRows;
+ if (!this.numberColumns){
+ nc = Math.ceil(pd.length/nr);
+ }
+ else{
+ nc = this.numberColumns;
+ }
+ }
+ else if (this.numberColumns) {
+ nc = this.numberColumns;
+ nr = Math.ceil(pd.length/this.numberColumns);
+ }
+ else {
+ nr = pd.length;
+ nc = 1;
+ }
+
+ var i, j;
+ var tr, td1, td2;
+ var lt, rs, color;
+ var idx = 0;
+ var div0, div1;
+
+ for (i=0; i<nr; i++) {
+ tr = $(document.createElement('tr'));
+ tr.addClass('jqplot-table-legend');
+
+ if (reverse){
+ tr.prependTo(this._elem);
+ }
+
+ else{
+ tr.appendTo(this._elem);
+ }
+
+ for (j=0; j<nc; j++) {
+ if (idx < pd.length){
+ lt = this.labels[idx] || pd[idx][0].toString();
+ color = colorGenerator.next();
+ if (!reverse){
+ if (i>0){
+ pad = true;
+ }
+ else{
+ pad = false;
+ }
+ }
+ else{
+ if (i == nr -1){
+ pad = false;
+ }
+ else{
+ pad = true;
+ }
+ }
+ rs = (pad) ? this.rowSpacing : '0';
+
+
+
+ td1 = $(document.createElement('td'));
+ td1.addClass('jqplot-table-legend jqplot-table-legend-swatch');
+ td1.css({textAlign: 'center', paddingTop: rs});
+
+ div0 = $(document.createElement('div'));
+ div0.addClass('jqplot-table-legend-swatch-outline');
+ div1 = $(document.createElement('div'));
+ div1.addClass('jqplot-table-legend-swatch');
+ div1.css({backgroundColor: color, borderColor: color});
+ td1.append(div0.append(div1));
+
+ td2 = $(document.createElement('td'));
+ td2.addClass('jqplot-table-legend jqplot-table-legend-label');
+ td2.css('paddingTop', rs);
+
+ if (this.escapeHtml){
+ td2.text(lt);
+ }
+ else {
+ td2.html(lt);
+ }
+ if (reverse) {
+ td2.prependTo(tr);
+ td1.prependTo(tr);
+ }
+ else {
+ td1.appendTo(tr);
+ td2.appendTo(tr);
+ }
+ pad = true;
+ }
+ idx++;
+ }
+ }
+ }
+ }
+ return this._elem;
+ };
+
+ $.jqplot.PieRenderer.prototype.handleMove = function(ev, gridpos, datapos, neighbor, plot) {
+ if (neighbor) {
+ var ins = [neighbor.seriesIndex, neighbor.pointIndex, neighbor.data];
+ plot.target.trigger('jqplotDataMouseOver', ins);
+ if (plot.series[ins[0]].highlightMouseOver && !(ins[0] == plot.plugins.pieRenderer.highlightedSeriesIndex && ins[1] == plot.series[ins[0]]._highlightedPoint)) {
+ plot.target.trigger('jqplotDataHighlight', ins);
+ highlight (plot, ins[0], ins[1]);
+ }
+ }
+ else if (neighbor == null) {
+ unhighlight (plot);
+ }
+ };
+
+
+ // this.eventCanvas._elem.bind($.jqplot.eventListenerHooks[i][0], {plot:this}, $.jqplot.eventListenerHooks[i][1]);
+
+ // setup default renderers for axes and legend so user doesn't have to
+ // called with scope of plot
+ function preInit(target, data, options) {
+ options = options || {};
+ options.axesDefaults = options.axesDefaults || {};
+ options.legend = options.legend || {};
+ options.seriesDefaults = options.seriesDefaults || {};
+ // only set these if there is a pie series
+ var setopts = false;
+ if (options.seriesDefaults.renderer == $.jqplot.PieRenderer) {
+ setopts = true;
+ }
+ else if (options.series) {
+ for (var i=0; i < options.series.length; i++) {
+ if (options.series[i].renderer == $.jqplot.PieRenderer) {
+ setopts = true;
+ }
+ }
+ }
+
+ if (setopts) {
+ options.axesDefaults.renderer = $.jqplot.PieAxisRenderer;
+ options.legend.renderer = $.jqplot.PieLegendRenderer;
+ options.legend.preDraw = true;
+ options.seriesDefaults.pointLabels = {show: false};
+ }
+ }
+
+ function postInit(target, data, options) {
+ for (var i=0; i<this.series.length; i++) {
+ if (this.series[i].renderer.constructor == $.jqplot.PieRenderer) {
+ // don't allow mouseover and mousedown at same time.
+ if (this.series[i].highlightMouseOver) {
+ this.series[i].highlightMouseDown = false;
+ }
+ }
+ }
+ }
+
+ // called with scope of plot
+ function postParseOptions(options) {
+ for (var i=0; i<this.series.length; i++) {
+ this.series[i].seriesColors = this.seriesColors;
+ this.series[i].colorGenerator = $.jqplot.colorGenerator;
+ }
+ }
+
+ function highlight (plot, sidx, pidx) {
+ var s = plot.series[sidx];
+ var canvas = plot.plugins.pieRenderer.highlightCanvas;
+ canvas._ctx.clearRect(0,0,canvas._ctx.canvas.width, canvas._ctx.canvas.height);
+ s._highlightedPoint = pidx;
+ plot.plugins.pieRenderer.highlightedSeriesIndex = sidx;
+ s.renderer.drawSlice.call(s, canvas._ctx, s._sliceAngles[pidx][0], s._sliceAngles[pidx][1], s.highlightColorGenerator.get(pidx), false);
+ }
+
+ function unhighlight (plot) {
+ var canvas = plot.plugins.pieRenderer.highlightCanvas;
+ canvas._ctx.clearRect(0,0, canvas._ctx.canvas.width, canvas._ctx.canvas.height);
+ for (var i=0; i<plot.series.length; i++) {
+ plot.series[i]._highlightedPoint = null;
+ }
+ plot.plugins.pieRenderer.highlightedSeriesIndex = null;
+ plot.target.trigger('jqplotDataUnhighlight');
+ }
+
+ function handleMove(ev, gridpos, datapos, neighbor, plot) {
+ if (neighbor) {
+ var ins = [neighbor.seriesIndex, neighbor.pointIndex, neighbor.data];
+ var evt1 = jQuery.Event('jqplotDataMouseOver');
+ evt1.pageX = ev.pageX;
+ evt1.pageY = ev.pageY;
+ plot.target.trigger(evt1, ins);
+ if (plot.series[ins[0]].highlightMouseOver && !(ins[0] == plot.plugins.pieRenderer.highlightedSeriesIndex && ins[1] == plot.series[ins[0]]._highlightedPoint)) {
+ var evt = jQuery.Event('jqplotDataHighlight');
+ evt.which = ev.which;
+ evt.pageX = ev.pageX;
+ evt.pageY = ev.pageY;
+ plot.target.trigger(evt, ins);
+ highlight (plot, ins[0], ins[1]);
+ }
+ }
+ else if (neighbor == null) {
+ unhighlight (plot);
+ }
+ }
+
+ function handleMouseDown(ev, gridpos, datapos, neighbor, plot) {
+ if (neighbor) {
+ var ins = [neighbor.seriesIndex, neighbor.pointIndex, neighbor.data];
+ if (plot.series[ins[0]].highlightMouseDown && !(ins[0] == plot.plugins.pieRenderer.highlightedSeriesIndex && ins[1] == plot.series[ins[0]]._highlightedPoint)) {
+ var evt = jQuery.Event('jqplotDataHighlight');
+ evt.which = ev.which;
+ evt.pageX = ev.pageX;
+ evt.pageY = ev.pageY;
+ plot.target.trigger(evt, ins);
+ highlight (plot, ins[0], ins[1]);
+ }
+ }
+ else if (neighbor == null) {
+ unhighlight (plot);
+ }
+ }
+
+ function handleMouseUp(ev, gridpos, datapos, neighbor, plot) {
+ var idx = plot.plugins.pieRenderer.highlightedSeriesIndex;
+ if (idx != null && plot.series[idx].highlightMouseDown) {
+ unhighlight(plot);
+ }
+ }
+
+ function handleClick(ev, gridpos, datapos, neighbor, plot) {
+ if (neighbor) {
+ var ins = [neighbor.seriesIndex, neighbor.pointIndex, neighbor.data];
+ var evt = jQuery.Event('jqplotDataClick');
+ evt.which = ev.which;
+ evt.pageX = ev.pageX;
+ evt.pageY = ev.pageY;
+ plot.target.trigger(evt, ins);
+ }
+ }
+
+ function handleRightClick(ev, gridpos, datapos, neighbor, plot) {
+ if (neighbor) {
+ var ins = [neighbor.seriesIndex, neighbor.pointIndex, neighbor.data];
+ var idx = plot.plugins.pieRenderer.highlightedSeriesIndex;
+ if (idx != null && plot.series[idx].highlightMouseDown) {
+ unhighlight(plot);
+ }
+ var evt = jQuery.Event('jqplotDataRightClick');
+ evt.which = ev.which;
+ evt.pageX = ev.pageX;
+ evt.pageY = ev.pageY;
+ plot.target.trigger(evt, ins);
+ }
+ }
+
+ // called within context of plot
+ // create a canvas which we can draw on.
+ // insert it before the eventCanvas, so eventCanvas will still capture events.
+ function postPlotDraw() {
+ // Memory Leaks patch
+ if (this.plugins.pieRenderer && this.plugins.pieRenderer.highlightCanvas) {
+ this.plugins.pieRenderer.highlightCanvas.resetCanvas();
+ this.plugins.pieRenderer.highlightCanvas = null;
+ }
+
+ this.plugins.pieRenderer = {highlightedSeriesIndex:null};
+ this.plugins.pieRenderer.highlightCanvas = new $.jqplot.GenericCanvas();
+
+ // do we have any data labels? if so, put highlight canvas before those
+ var labels = $(this.targetId+' .jqplot-data-label');
+ if (labels.length) {
+ $(labels[0]).before(this.plugins.pieRenderer.highlightCanvas.createElement(this._gridPadding, 'jqplot-pieRenderer-highlight-canvas', this._plotDimensions, this));
+ }
+ // else put highlight canvas before event canvas.
+ else {
+ this.eventCanvas._elem.before(this.plugins.pieRenderer.highlightCanvas.createElement(this._gridPadding, 'jqplot-pieRenderer-highlight-canvas', this._plotDimensions, this));
+ }
+
+ var hctx = this.plugins.pieRenderer.highlightCanvas.setContext();
+ this.eventCanvas._elem.bind('mouseleave', {plot:this}, function (ev) { unhighlight(ev.data.plot); });
+ }
+
+ $.jqplot.preInitHooks.push(preInit);
+
+ $.jqplot.PieTickRenderer = function() {
+ $.jqplot.AxisTickRenderer.call(this);
+ };
+
+ $.jqplot.PieTickRenderer.prototype = new $.jqplot.AxisTickRenderer();
+ $.jqplot.PieTickRenderer.prototype.constructor = $.jqplot.PieTickRenderer;
+
+})(jQuery);
+
+ \ No newline at end of file
diff --git a/js/jqplot/plugins/jqplot.pointLabels.js b/js/jqplot/plugins/jqplot.pointLabels.js
new file mode 100644
index 0000000000..c20a9c4423
--- /dev/null
+++ b/js/jqplot/plugins/jqplot.pointLabels.js
@@ -0,0 +1,379 @@
+/**
+ * jqPlot
+ * Pure JavaScript plotting plugin using jQuery
+ *
+ * Version: 1.0.4
+ * Revision: 1121
+ *
+ * Copyright (c) 2009-2012 Chris Leonello
+ * jqPlot is currently available for use in all personal or commercial projects
+ * under both the MIT (http://www.opensource.org/licenses/mit-license.php) and GPL
+ * version 2.0 (http://www.gnu.org/licenses/gpl-2.0.html) licenses. This means that you can
+ * choose the license that best suits your project and use it accordingly.
+ *
+ * Although not required, the author would appreciate an email letting him
+ * know of any substantial use of jqPlot. You can reach the author at:
+ * chris at jqplot dot com or see http://www.jqplot.com/info.php .
+ *
+ * If you are feeling kind and generous, consider supporting the project by
+ * making a donation at: http://www.jqplot.com/donate.php .
+ *
+ * sprintf functions contained in jqplot.sprintf.js by Ash Searle:
+ *
+ * version 2007.04.27
+ * author Ash Searle
+ * http://hexmen.com/blog/2007/03/printf-sprintf/
+ * http://hexmen.com/js/sprintf.js
+ * The author (Ash Searle) has placed this code in the public domain:
+ * "This code is unrestricted: you are free to use it however you like."
+ *
+ */
+(function($) {
+
+ /**
+ * Class: $.jqplot.PointLabels
+ * Plugin for putting labels at the data points.
+ *
+ * To use this plugin, include the js
+ * file in your source:
+ *
+ * > <script type="text/javascript" src="plugins/jqplot.pointLabels.js"></script>
+ *
+ * By default, the last value in the data ponit array in the data series is used
+ * for the label. For most series renderers, extra data can be added to the
+ * data point arrays and the last value will be used as the label.
+ *
+ * For instance,
+ * this series:
+ *
+ * > [[1,4], [3,5], [7,2]]
+ *
+ * Would, by default, use the y values in the labels.
+ * Extra data can be added to the series like so:
+ *
+ * > [[1,4,'mid'], [3 5,'hi'], [7,2,'low']]
+ *
+ * And now the point labels would be 'mid', 'low', and 'hi'.
+ *
+ * Options to the point labels and a custom labels array can be passed into the
+ * "pointLabels" option on the series option like so:
+ *
+ * > series:[{pointLabels:{
+ * > labels:['mid', 'hi', 'low'],
+ * > location:'se',
+ * > ypadding: 12
+ * > }
+ * > }]
+ *
+ * A custom labels array in the options takes precendence over any labels
+ * in the series data. If you have a custom labels array in the options,
+ * but still want to use values from the series array as labels, set the
+ * "labelsFromSeries" option to true.
+ *
+ * By default, html entities (<, >, etc.) are escaped in point labels.
+ * If you want to include actual html markup in the labels,
+ * set the "escapeHTML" option to false.
+ *
+ */
+ $.jqplot.PointLabels = function(options) {
+ // Group: Properties
+ //
+ // prop: show
+ // show the labels or not.
+ this.show = $.jqplot.config.enablePlugins;
+ // prop: location
+ // compass location where to position the label around the point.
+ // 'n', 'ne', 'e', 'se', 's', 'sw', 'w', 'nw'
+ this.location = 'n';
+ // prop: labelsFromSeries
+ // true to use labels within data point arrays.
+ this.labelsFromSeries = false;
+ // prop: seriesLabelIndex
+ // array index for location of labels within data point arrays.
+ // if null, will use the last element of the data point array.
+ this.seriesLabelIndex = null;
+ // prop: labels
+ // array of arrays of labels, one array for each series.
+ this.labels = [];
+ // actual labels that will get displayed.
+ // needed to preserve user specified labels in labels array.
+ this._labels = [];
+ // prop: stackedValue
+ // true to display value as stacked in a stacked plot.
+ // no effect if labels is specified.
+ this.stackedValue = false;
+ // prop: ypadding
+ // vertical padding in pixels between point and label
+ this.ypadding = 6;
+ // prop: xpadding
+ // horizontal padding in pixels between point and label
+ this.xpadding = 6;
+ // prop: escapeHTML
+ // true to escape html entities in the labels.
+ // If you want to include markup in the labels, set to false.
+ this.escapeHTML = true;
+ // prop: edgeTolerance
+ // Number of pixels that the label must be away from an axis
+ // boundary in order to be drawn. Negative values will allow overlap
+ // with the grid boundaries.
+ this.edgeTolerance = -5;
+ // prop: formatter
+ // A class of a formatter for the tick text. sprintf by default.
+ this.formatter = $.jqplot.DefaultTickFormatter;
+ // prop: formatString
+ // string passed to the formatter.
+ this.formatString = '';
+ // prop: hideZeros
+ // true to not show a label for a value which is 0.
+ this.hideZeros = false;
+ this._elems = [];
+
+ $.extend(true, this, options);
+ };
+
+ var locations = ['nw', 'n', 'ne', 'e', 'se', 's', 'sw', 'w'];
+ var locationIndicies = {'nw':0, 'n':1, 'ne':2, 'e':3, 'se':4, 's':5, 'sw':6, 'w':7};
+ var oppositeLocations = ['se', 's', 'sw', 'w', 'nw', 'n', 'ne', 'e'];
+
+ // called with scope of a series
+ $.jqplot.PointLabels.init = function (target, data, seriesDefaults, opts, plot){
+ var options = $.extend(true, {}, seriesDefaults, opts);
+ options.pointLabels = options.pointLabels || {};
+ if (this.renderer.constructor === $.jqplot.BarRenderer && this.barDirection === 'horizontal' && !options.pointLabels.location) {
+ options.pointLabels.location = 'e';
+ }
+ // add a pointLabels attribute to the series plugins
+ this.plugins.pointLabels = new $.jqplot.PointLabels(options.pointLabels);
+ this.plugins.pointLabels.setLabels.call(this);
+ };
+
+ // called with scope of series
+ $.jqplot.PointLabels.prototype.setLabels = function() {
+ var p = this.plugins.pointLabels;
+ var labelIdx;
+ if (p.seriesLabelIndex != null) {
+ labelIdx = p.seriesLabelIndex;
+ }
+ else if (this.renderer.constructor === $.jqplot.BarRenderer && this.barDirection === 'horizontal') {
+ labelIdx = 0;
+ }
+ else {
+ labelIdx = (this._plotData.length === 0) ? 0 : this._plotData[0].length -1;
+ }
+ p._labels = [];
+ if (p.labels.length === 0 || p.labelsFromSeries) {
+ if (p.stackedValue) {
+ if (this._plotData.length && this._plotData[0].length){
+ // var idx = p.seriesLabelIndex || this._plotData[0].length -1;
+ for (var i=0; i<this._plotData.length; i++) {
+ p._labels.push(this._plotData[i][labelIdx]);
+ }
+ }
+ }
+ else {
+ // var d = this._plotData;
+ var d = this.data;
+ if (this.renderer.constructor === $.jqplot.BarRenderer && this.waterfall) {
+ d = this._data;
+ }
+ if (d.length && d[0].length) {
+ // var idx = p.seriesLabelIndex || d[0].length -1;
+ for (var i=0; i<d.length; i++) {
+ p._labels.push(d[i][labelIdx]);
+ }
+ }
+ d = null;
+ }
+ }
+ else if (p.labels.length){
+ p._labels = p.labels;
+ }
+ };
+
+ $.jqplot.PointLabels.prototype.xOffset = function(elem, location, padding) {
+ location = location || this.location;
+ padding = padding || this.xpadding;
+ var offset;
+
+ switch (location) {
+ case 'nw':
+ offset = -elem.outerWidth(true) - this.xpadding;
+ break;
+ case 'n':
+ offset = -elem.outerWidth(true)/2;
+ break;
+ case 'ne':
+ offset = this.xpadding;
+ break;
+ case 'e':
+ offset = this.xpadding;
+ break;
+ case 'se':
+ offset = this.xpadding;
+ break;
+ case 's':
+ offset = -elem.outerWidth(true)/2;
+ break;
+ case 'sw':
+ offset = -elem.outerWidth(true) - this.xpadding;
+ break;
+ case 'w':
+ offset = -elem.outerWidth(true) - this.xpadding;
+ break;
+ default: // same as 'nw'
+ offset = -elem.outerWidth(true) - this.xpadding;
+ break;
+ }
+ return offset;
+ };
+
+ $.jqplot.PointLabels.prototype.yOffset = function(elem, location, padding) {
+ location = location || this.location;
+ padding = padding || this.xpadding;
+ var offset;
+
+ switch (location) {
+ case 'nw':
+ offset = -elem.outerHeight(true) - this.ypadding;
+ break;
+ case 'n':
+ offset = -elem.outerHeight(true) - this.ypadding;
+ break;
+ case 'ne':
+ offset = -elem.outerHeight(true) - this.ypadding;
+ break;
+ case 'e':
+ offset = -elem.outerHeight(true)/2;
+ break;
+ case 'se':
+ offset = this.ypadding;
+ break;
+ case 's':
+ offset = this.ypadding;
+ break;
+ case 'sw':
+ offset = this.ypadding;
+ break;
+ case 'w':
+ offset = -elem.outerHeight(true)/2;
+ break;
+ default: // same as 'nw'
+ offset = -elem.outerHeight(true) - this.ypadding;
+ break;
+ }
+ return offset;
+ };
+
+ // called with scope of series
+ $.jqplot.PointLabels.draw = function (sctx, options, plot) {
+ var p = this.plugins.pointLabels;
+ // set labels again in case they have changed.
+ p.setLabels.call(this);
+ // remove any previous labels
+ for (var i=0; i<p._elems.length; i++) {
+ // Memory Leaks patch
+ // p._elems[i].remove();
+ p._elems[i].emptyForce();
+ }
+ p._elems.splice(0, p._elems.length);
+
+ if (p.show) {
+ var ax = '_'+this._stackAxis+'axis';
+
+ if (!p.formatString) {
+ p.formatString = this[ax]._ticks[0].formatString;
+ p.formatter = this[ax]._ticks[0].formatter;
+ }
+
+ var pd = this._plotData;
+ var ppd = this._prevPlotData;
+ var xax = this._xaxis;
+ var yax = this._yaxis;
+ var elem, helem;
+
+ for (var i=0, l=p._labels.length; i < l; i++) {
+ var label = p._labels[i];
+
+ if (p.hideZeros && parseInt(p._labels[i], 10) == 0) {
+ label = '';
+ }
+
+ if (label != null) {
+ label = p.formatter(p.formatString, label);
+ }
+
+ helem = document.createElement('div');
+ p._elems[i] = $(helem);
+
+ elem = p._elems[i];
+
+
+ elem.addClass('jqplot-point-label jqplot-series-'+this.index+' jqplot-point-'+i);
+ elem.css('position', 'absolute');
+ elem.insertAfter(sctx.canvas);
+
+ if (p.escapeHTML) {
+ elem.text(label);
+ }
+ else {
+ elem.html(label);
+ }
+ var location = p.location;
+ if ((this.fillToZero && pd[i][1] < 0) || (this.fillToZero && this._type === 'bar' && this.barDirection === 'horizontal' && pd[i][0] < 0) || (this.waterfall && parseInt(label, 10)) < 0) {
+ location = oppositeLocations[locationIndicies[location]];
+ }
+
+
+ var ell = xax.u2p(pd[i][0]) + p.xOffset(elem, location);
+ var elt = yax.u2p(pd[i][1]) + p.yOffset(elem, location);
+
+ // we have stacked chart but are not showing stacked values,
+ // place labels in center.
+ if (this._stack && !p.stackedValue) {
+ if (this.barDirection === "vertical") {
+ elt = (this._barPoints[i][0][1] + this._barPoints[i][1][1]) / 2 + plot._gridPadding.top - 0.5 * elem.outerHeight(true);
+ }
+ else {
+ ell = (this._barPoints[i][2][0] + this._barPoints[i][0][0]) / 2 + plot._gridPadding.left - 0.5 * elem.outerWidth(true);
+ }
+ }
+
+ if (this.renderer.constructor == $.jqplot.BarRenderer) {
+ if (this.barDirection == "vertical") {
+ ell += this._barNudge;
+ }
+ else {
+ elt -= this._barNudge;
+ }
+ }
+ elem.css('left', ell);
+ elem.css('top', elt);
+ var elr = ell + elem.width();
+ var elb = elt + elem.height();
+ var et = p.edgeTolerance;
+ var scl = $(sctx.canvas).position().left;
+ var sct = $(sctx.canvas).position().top;
+ var scr = sctx.canvas.width + scl;
+ var scb = sctx.canvas.height + sct;
+ // if label is outside of allowed area, remove it
+ if (ell - et < scl || elt - et < sct || elr + et > scr || elb + et > scb) {
+ elem.remove();
+ }
+
+ elem = null;
+ helem = null;
+ }
+
+ // finally, animate them if the series is animated
+ // if (this.renderer.animation && this.renderer.animation._supported && this.renderer.animation.show && plot._drawCount < 2) {
+ // var sel = '.jqplot-point-label.jqplot-series-'+this.index;
+ // $(sel).hide();
+ // $(sel).fadeIn(1000);
+ // }
+
+ }
+ };
+
+ $.jqplot.postSeriesInitHooks.push($.jqplot.PointLabels.init);
+ $.jqplot.postDrawSeriesHooks.push($.jqplot.PointLabels.draw);
+})(jQuery); \ No newline at end of file
diff --git a/js/jquery/MIT-LICENSE.txt b/js/jquery/MIT-LICENSE.txt
new file mode 100644
index 0000000000..957f26d3e3
--- /dev/null
+++ b/js/jquery/MIT-LICENSE.txt
@@ -0,0 +1,21 @@
+Copyright 2013 jQuery Foundation and other contributors
+http://jquery.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/js/jquery/jquery-1.8.3.min.js b/js/jquery/jquery-1.8.3.min.js
new file mode 100644
index 0000000000..3883779527
--- /dev/null
+++ b/js/jquery/jquery-1.8.3.min.js
@@ -0,0 +1,2 @@
+/*! jQuery v1.8.3 jquery.com | jquery.org/license */
+(function(e,t){function _(e){var t=M[e]={};return v.each(e.split(y),function(e,n){t[n]=!0}),t}function H(e,n,r){if(r===t&&e.nodeType===1){var i="data-"+n.replace(P,"-$1").toLowerCase();r=e.getAttribute(i);if(typeof r=="string"){try{r=r==="true"?!0:r==="false"?!1:r==="null"?null:+r+""===r?+r:D.test(r)?v.parseJSON(r):r}catch(s){}v.data(e,n,r)}else r=t}return r}function B(e){var t;for(t in e){if(t==="data"&&v.isEmptyObject(e[t]))continue;if(t!=="toJSON")return!1}return!0}function et(){return!1}function tt(){return!0}function ut(e){return!e||!e.parentNode||e.parentNode.nodeType===11}function at(e,t){do e=e[t];while(e&&e.nodeType!==1);return e}function ft(e,t,n){t=t||0;if(v.isFunction(t))return v.grep(e,function(e,r){var i=!!t.call(e,r,e);return i===n});if(t.nodeType)return v.grep(e,function(e,r){return e===t===n});if(typeof t=="string"){var r=v.grep(e,function(e){return e.nodeType===1});if(it.test(t))return v.filter(t,r,!n);t=v.filter(t,r)}return v.grep(e,function(e,r){return v.inArray(e,t)>=0===n})}function lt(e){var t=ct.split("|"),n=e.createDocumentFragment();if(n.createElement)while(t.length)n.createElement(t.pop());return n}function Lt(e,t){return e.getElementsByTagName(t)[0]||e.appendChild(e.ownerDocument.createElement(t))}function At(e,t){if(t.nodeType!==1||!v.hasData(e))return;var n,r,i,s=v._data(e),o=v._data(t,s),u=s.events;if(u){delete o.handle,o.events={};for(n in u)for(r=0,i=u[n].length;r<i;r++)v.event.add(t,n,u[n][r])}o.data&&(o.data=v.extend({},o.data))}function Ot(e,t){var n;if(t.nodeType!==1)return;t.clearAttributes&&t.clearAttributes(),t.mergeAttributes&&t.mergeAttributes(e),n=t.nodeName.toLowerCase(),n==="object"?(t.parentNode&&(t.outerHTML=e.outerHTML),v.support.html5Clone&&e.innerHTML&&!v.trim(t.innerHTML)&&(t.innerHTML=e.innerHTML)):n==="input"&&Et.test(e.type)?(t.defaultChecked=t.checked=e.checked,t.value!==e.value&&(t.value=e.value)):n==="option"?t.selected=e.defaultSelected:n==="input"||n==="textarea"?t.defaultValue=e.defaultValue:n==="script"&&t.text!==e.text&&(t.text=e.text),t.removeAttribute(v.expando)}function Mt(e){return typeof e.getElementsByTagName!="undefined"?e.getElementsByTagName("*"):typeof e.querySelectorAll!="undefined"?e.querySelectorAll("*"):[]}function _t(e){Et.test(e.type)&&(e.defaultChecked=e.checked)}function Qt(e,t){if(t in e)return t;var n=t.charAt(0).toUpperCase()+t.slice(1),r=t,i=Jt.length;while(i--){t=Jt[i]+n;if(t in e)return t}return r}function Gt(e,t){return e=t||e,v.css(e,"display")==="none"||!v.contains(e.ownerDocument,e)}function Yt(e,t){var n,r,i=[],s=0,o=e.length;for(;s<o;s++){n=e[s];if(!n.style)continue;i[s]=v._data(n,"olddisplay"),t?(!i[s]&&n.style.display==="none"&&(n.style.display=""),n.style.display===""&&Gt(n)&&(i[s]=v._data(n,"olddisplay",nn(n.nodeName)))):(r=Dt(n,"display"),!i[s]&&r!=="none"&&v._data(n,"olddisplay",r))}for(s=0;s<o;s++){n=e[s];if(!n.style)continue;if(!t||n.style.display==="none"||n.style.display==="")n.style.display=t?i[s]||"":"none"}return e}function Zt(e,t,n){var r=Rt.exec(t);return r?Math.max(0,r[1]-(n||0))+(r[2]||"px"):t}function en(e,t,n,r){var i=n===(r?"border":"content")?4:t==="width"?1:0,s=0;for(;i<4;i+=2)n==="margin"&&(s+=v.css(e,n+$t[i],!0)),r?(n==="content"&&(s-=parseFloat(Dt(e,"padding"+$t[i]))||0),n!=="margin"&&(s-=parseFloat(Dt(e,"border"+$t[i]+"Width"))||0)):(s+=parseFloat(Dt(e,"padding"+$t[i]))||0,n!=="padding"&&(s+=parseFloat(Dt(e,"border"+$t[i]+"Width"))||0));return s}function tn(e,t,n){var r=t==="width"?e.offsetWidth:e.offsetHeight,i=!0,s=v.support.boxSizing&&v.css(e,"boxSizing")==="border-box";if(r<=0||r==null){r=Dt(e,t);if(r<0||r==null)r=e.style[t];if(Ut.test(r))return r;i=s&&(v.support.boxSizingReliable||r===e.style[t]),r=parseFloat(r)||0}return r+en(e,t,n||(s?"border":"content"),i)+"px"}function nn(e){if(Wt[e])return Wt[e];var t=v("<"+e+">").appendTo(i.body),n=t.css("display");t.remove();if(n==="none"||n===""){Pt=i.body.appendChild(Pt||v.extend(i.createElement("iframe"),{frameBorder:0,width:0,height:0}));if(!Ht||!Pt.createElement)Ht=(Pt.contentWindow||Pt.contentDocument).document,Ht.write("<!doctype html><html><body>"),Ht.close();t=Ht.body.appendChild(Ht.createElement(e)),n=Dt(t,"display"),i.body.removeChild(Pt)}return Wt[e]=n,n}function fn(e,t,n,r){var i;if(v.isArray(t))v.each(t,function(t,i){n||sn.test(e)?r(e,i):fn(e+"["+(typeof i=="object"?t:"")+"]",i,n,r)});else if(!n&&v.type(t)==="object")for(i in t)fn(e+"["+i+"]",t[i],n,r);else r(e,t)}function Cn(e){return function(t,n){typeof t!="string"&&(n=t,t="*");var r,i,s,o=t.toLowerCase().split(y),u=0,a=o.length;if(v.isFunction(n))for(;u<a;u++)r=o[u],s=/^\+/.test(r),s&&(r=r.substr(1)||"*"),i=e[r]=e[r]||[],i[s?"unshift":"push"](n)}}function kn(e,n,r,i,s,o){s=s||n.dataTypes[0],o=o||{},o[s]=!0;var u,a=e[s],f=0,l=a?a.length:0,c=e===Sn;for(;f<l&&(c||!u);f++)u=a[f](n,r,i),typeof u=="string"&&(!c||o[u]?u=t:(n.dataTypes.unshift(u),u=kn(e,n,r,i,u,o)));return(c||!u)&&!o["*"]&&(u=kn(e,n,r,i,"*",o)),u}function Ln(e,n){var r,i,s=v.ajaxSettings.flatOptions||{};for(r in n)n[r]!==t&&((s[r]?e:i||(i={}))[r]=n[r]);i&&v.extend(!0,e,i)}function An(e,n,r){var i,s,o,u,a=e.contents,f=e.dataTypes,l=e.responseFields;for(s in l)s in r&&(n[l[s]]=r[s]);while(f[0]==="*")f.shift(),i===t&&(i=e.mimeType||n.getResponseHeader("content-type"));if(i)for(s in a)if(a[s]&&a[s].test(i)){f.unshift(s);break}if(f[0]in r)o=f[0];else{for(s in r){if(!f[0]||e.converters[s+" "+f[0]]){o=s;break}u||(u=s)}o=o||u}if(o)return o!==f[0]&&f.unshift(o),r[o]}function On(e,t){var n,r,i,s,o=e.dataTypes.slice(),u=o[0],a={},f=0;e.dataFilter&&(t=e.dataFilter(t,e.dataType));if(o[1])for(n in e.converters)a[n.toLowerCase()]=e.converters[n];for(;i=o[++f];)if(i!=="*"){if(u!=="*"&&u!==i){n=a[u+" "+i]||a["* "+i];if(!n)for(r in a){s=r.split(" ");if(s[1]===i){n=a[u+" "+s[0]]||a["* "+s[0]];if(n){n===!0?n=a[r]:a[r]!==!0&&(i=s[0],o.splice(f--,0,i));break}}}if(n!==!0)if(n&&e["throws"])t=n(t);else try{t=n(t)}catch(l){return{state:"parsererror",error:n?l:"No conversion from "+u+" to "+i}}}u=i}return{state:"success",data:t}}function Fn(){try{return new e.XMLHttpRequest}catch(t){}}function In(){try{return new e.ActiveXObject("Microsoft.XMLHTTP")}catch(t){}}function $n(){return setTimeout(function(){qn=t},0),qn=v.now()}function Jn(e,t){v.each(t,function(t,n){var r=(Vn[t]||[]).concat(Vn["*"]),i=0,s=r.length;for(;i<s;i++)if(r[i].call(e,t,n))return})}function Kn(e,t,n){var r,i=0,s=0,o=Xn.length,u=v.Deferred().always(function(){delete a.elem}),a=function(){var t=qn||$n(),n=Math.max(0,f.startTime+f.duration-t),r=n/f.duration||0,i=1-r,s=0,o=f.tweens.length;for(;s<o;s++)f.tweens[s].run(i);return u.notifyWith(e,[f,i,n]),i<1&&o?n:(u.resolveWith(e,[f]),!1)},f=u.promise({elem:e,props:v.extend({},t),opts:v.extend(!0,{specialEasing:{}},n),originalProperties:t,originalOptions:n,startTime:qn||$n(),duration:n.duration,tweens:[],createTween:function(t,n,r){var i=v.Tween(e,f.opts,t,n,f.opts.specialEasing[t]||f.opts.easing);return f.tweens.push(i),i},stop:function(t){var n=0,r=t?f.tweens.length:0;for(;n<r;n++)f.tweens[n].run(1);return t?u.resolveWith(e,[f,t]):u.rejectWith(e,[f,t]),this}}),l=f.props;Qn(l,f.opts.specialEasing);for(;i<o;i++){r=Xn[i].call(f,e,l,f.opts);if(r)return r}return Jn(f,l),v.isFunction(f.opts.start)&&f.opts.start.call(e,f),v.fx.timer(v.extend(a,{anim:f,queue:f.opts.queue,elem:e})),f.progress(f.opts.progress).done(f.opts.done,f.opts.complete).fail(f.opts.fail).always(f.opts.always)}function Qn(e,t){var n,r,i,s,o;for(n in e){r=v.camelCase(n),i=t[r],s=e[n],v.isArray(s)&&(i=s[1],s=e[n]=s[0]),n!==r&&(e[r]=s,delete e[n]),o=v.cssHooks[r];if(o&&"expand"in o){s=o.expand(s),delete e[r];for(n in s)n in e||(e[n]=s[n],t[n]=i)}else t[r]=i}}function Gn(e,t,n){var r,i,s,o,u,a,f,l,c,h=this,p=e.style,d={},m=[],g=e.nodeType&&Gt(e);n.queue||(l=v._queueHooks(e,"fx"),l.unqueued==null&&(l.unqueued=0,c=l.empty.fire,l.empty.fire=function(){l.unqueued||c()}),l.unqueued++,h.always(function(){h.always(function(){l.unqueued--,v.queue(e,"fx").length||l.empty.fire()})})),e.nodeType===1&&("height"in t||"width"in t)&&(n.overflow=[p.overflow,p.overflowX,p.overflowY],v.css(e,"display")==="inline"&&v.css(e,"float")==="none"&&(!v.support.inlineBlockNeedsLayout||nn(e.nodeName)==="inline"?p.display="inline-block":p.zoom=1)),n.overflow&&(p.overflow="hidden",v.support.shrinkWrapBlocks||h.done(function(){p.overflow=n.overflow[0],p.overflowX=n.overflow[1],p.overflowY=n.overflow[2]}));for(r in t){s=t[r];if(Un.exec(s)){delete t[r],a=a||s==="toggle";if(s===(g?"hide":"show"))continue;m.push(r)}}o=m.length;if(o){u=v._data(e,"fxshow")||v._data(e,"fxshow",{}),"hidden"in u&&(g=u.hidden),a&&(u.hidden=!g),g?v(e).show():h.done(function(){v(e).hide()}),h.done(function(){var t;v.removeData(e,"fxshow",!0);for(t in d)v.style(e,t,d[t])});for(r=0;r<o;r++)i=m[r],f=h.createTween(i,g?u[i]:0),d[i]=u[i]||v.style(e,i),i in u||(u[i]=f.start,g&&(f.end=f.start,f.start=i==="width"||i==="height"?1:0))}}function Yn(e,t,n,r,i){return new Yn.prototype.init(e,t,n,r,i)}function Zn(e,t){var n,r={height:e},i=0;t=t?1:0;for(;i<4;i+=2-t)n=$t[i],r["margin"+n]=r["padding"+n]=e;return t&&(r.opacity=r.width=e),r}function tr(e){return v.isWindow(e)?e:e.nodeType===9?e.defaultView||e.parentWindow:!1}var n,r,i=e.document,s=e.location,o=e.navigator,u=e.jQuery,a=e.$,f=Array.prototype.push,l=Array.prototype.slice,c=Array.prototype.indexOf,h=Object.prototype.toString,p=Object.prototype.hasOwnProperty,d=String.prototype.trim,v=function(e,t){return new v.fn.init(e,t,n)},m=/[\-+]?(?:\d*\.|)\d+(?:[eE][\-+]?\d+|)/.source,g=/\S/,y=/\s+/,b=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,w=/^(?:[^#<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/,E=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,S=/^[\],:{}\s]*$/,x=/(?:^|:|,)(?:\s*\[)+/g,T=/\\(?:["\\\/bfnrt]|u[\da-fA-F]{4})/g,N=/"[^"\\\r\n]*"|true|false|null|-?(?:\d\d*\.|)\d+(?:[eE][\-+]?\d+|)/g,C=/^-ms-/,k=/-([\da-z])/gi,L=function(e,t){return(t+"").toUpperCase()},A=function(){i.addEventListener?(i.removeEventListener("DOMContentLoaded",A,!1),v.ready()):i.readyState==="complete"&&(i.detachEvent("onreadystatechange",A),v.ready())},O={};v.fn=v.prototype={constructor:v,init:function(e,n,r){var s,o,u,a;if(!e)return this;if(e.nodeType)return this.context=this[0]=e,this.length=1,this;if(typeof e=="string"){e.charAt(0)==="<"&&e.charAt(e.length-1)===">"&&e.length>=3?s=[null,e,null]:s=w.exec(e);if(s&&(s[1]||!n)){if(s[1])return n=n instanceof v?n[0]:n,a=n&&n.nodeType?n.ownerDocument||n:i,e=v.parseHTML(s[1],a,!0),E.test(s[1])&&v.isPlainObject(n)&&this.attr.call(e,n,!0),v.merge(this,e);o=i.getElementById(s[2]);if(o&&o.parentNode){if(o.id!==s[2])return r.find(e);this.length=1,this[0]=o}return this.context=i,this.selector=e,this}return!n||n.jquery?(n||r).find(e):this.constructor(n).find(e)}return v.isFunction(e)?r.ready(e):(e.selector!==t&&(this.selector=e.selector,this.context=e.context),v.makeArray(e,this))},selector:"",jquery:"1.8.3",length:0,size:function(){return this.length},toArray:function(){return l.call(this)},get:function(e){return e==null?this.toArray():e<0?this[this.length+e]:this[e]},pushStack:function(e,t,n){var r=v.merge(this.constructor(),e);return r.prevObject=this,r.context=this.context,t==="find"?r.selector=this.selector+(this.selector?" ":"")+n:t&&(r.selector=this.selector+"."+t+"("+n+")"),r},each:function(e,t){return v.each(this,e,t)},ready:function(e){return v.ready.promise().done(e),this},eq:function(e){return e=+e,e===-1?this.slice(e):this.slice(e,e+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(l.apply(this,arguments),"slice",l.call(arguments).join(","))},map:function(e){return this.pushStack(v.map(this,function(t,n){return e.call(t,n,t)}))},end:function(){return this.prevObject||this.constructor(null)},push:f,sort:[].sort,splice:[].splice},v.fn.init.prototype=v.fn,v.extend=v.fn.extend=function(){var e,n,r,i,s,o,u=arguments[0]||{},a=1,f=arguments.length,l=!1;typeof u=="boolean"&&(l=u,u=arguments[1]||{},a=2),typeof u!="object"&&!v.isFunction(u)&&(u={}),f===a&&(u=this,--a);for(;a<f;a++)if((e=arguments[a])!=null)for(n in e){r=u[n],i=e[n];if(u===i)continue;l&&i&&(v.isPlainObject(i)||(s=v.isArray(i)))?(s?(s=!1,o=r&&v.isArray(r)?r:[]):o=r&&v.isPlainObject(r)?r:{},u[n]=v.extend(l,o,i)):i!==t&&(u[n]=i)}return u},v.extend({noConflict:function(t){return e.$===v&&(e.$=a),t&&e.jQuery===v&&(e.jQuery=u),v},isReady:!1,readyWait:1,holdReady:function(e){e?v.readyWait++:v.ready(!0)},ready:function(e){if(e===!0?--v.readyWait:v.isReady)return;if(!i.body)return setTimeout(v.ready,1);v.isReady=!0;if(e!==!0&&--v.readyWait>0)return;r.resolveWith(i,[v]),v.fn.trigger&&v(i).trigger("ready").off("ready")},isFunction:function(e){return v.type(e)==="function"},isArray:Array.isArray||function(e){return v.type(e)==="array"},isWindow:function(e){return e!=null&&e==e.window},isNumeric:function(e){return!isNaN(parseFloat(e))&&isFinite(e)},type:function(e){return e==null?String(e):O[h.call(e)]||"object"},isPlainObject:function(e){if(!e||v.type(e)!=="object"||e.nodeType||v.isWindow(e))return!1;try{if(e.constructor&&!p.call(e,"constructor")&&!p.call(e.constructor.prototype,"isPrototypeOf"))return!1}catch(n){return!1}var r;for(r in e);return r===t||p.call(e,r)},isEmptyObject:function(e){var t;for(t in e)return!1;return!0},error:function(e){throw new Error(e)},parseHTML:function(e,t,n){var r;return!e||typeof e!="string"?null:(typeof t=="boolean"&&(n=t,t=0),t=t||i,(r=E.exec(e))?[t.createElement(r[1])]:(r=v.buildFragment([e],t,n?null:[]),v.merge([],(r.cacheable?v.clone(r.fragment):r.fragment).childNodes)))},parseJSON:function(t){if(!t||typeof t!="string")return null;t=v.trim(t);if(e.JSON&&e.JSON.parse)return e.JSON.parse(t);if(S.test(t.replace(T,"@").replace(N,"]").replace(x,"")))return(new Function("return "+t))();v.error("Invalid JSON: "+t)},parseXML:function(n){var r,i;if(!n||typeof n!="string")return null;try{e.DOMParser?(i=new DOMParser,r=i.parseFromString(n,"text/xml")):(r=new ActiveXObject("Microsoft.XMLDOM"),r.async="false",r.loadXML(n))}catch(s){r=t}return(!r||!r.documentElement||r.getElementsByTagName("parsererror").length)&&v.error("Invalid XML: "+n),r},noop:function(){},globalEval:function(t){t&&g.test(t)&&(e.execScript||function(t){e.eval.call(e,t)})(t)},camelCase:function(e){return e.replace(C,"ms-").replace(k,L)},nodeName:function(e,t){return e.nodeName&&e.nodeName.toLowerCase()===t.toLowerCase()},each:function(e,n,r){var i,s=0,o=e.length,u=o===t||v.isFunction(e);if(r){if(u){for(i in e)if(n.apply(e[i],r)===!1)break}else for(;s<o;)if(n.apply(e[s++],r)===!1)break}else if(u){for(i in e)if(n.call(e[i],i,e[i])===!1)break}else for(;s<o;)if(n.call(e[s],s,e[s++])===!1)break;return e},trim:d&&!d.call("\ufeff\u00a0")?function(e){return e==null?"":d.call(e)}:function(e){return e==null?"":(e+"").replace(b,"")},makeArray:function(e,t){var n,r=t||[];return e!=null&&(n=v.type(e),e.length==null||n==="string"||n==="function"||n==="regexp"||v.isWindow(e)?f.call(r,e):v.merge(r,e)),r},inArray:function(e,t,n){var r;if(t){if(c)return c.call(t,e,n);r=t.length,n=n?n<0?Math.max(0,r+n):n:0;for(;n<r;n++)if(n in t&&t[n]===e)return n}return-1},merge:function(e,n){var r=n.length,i=e.length,s=0;if(typeof r=="number")for(;s<r;s++)e[i++]=n[s];else while(n[s]!==t)e[i++]=n[s++];return e.length=i,e},grep:function(e,t,n){var r,i=[],s=0,o=e.length;n=!!n;for(;s<o;s++)r=!!t(e[s],s),n!==r&&i.push(e[s]);return i},map:function(e,n,r){var i,s,o=[],u=0,a=e.length,f=e instanceof v||a!==t&&typeof a=="number"&&(a>0&&e[0]&&e[a-1]||a===0||v.isArray(e));if(f)for(;u<a;u++)i=n(e[u],u,r),i!=null&&(o[o.length]=i);else for(s in e)i=n(e[s],s,r),i!=null&&(o[o.length]=i);return o.concat.apply([],o)},guid:1,proxy:function(e,n){var r,i,s;return typeof n=="string"&&(r=e[n],n=e,e=r),v.isFunction(e)?(i=l.call(arguments,2),s=function(){return e.apply(n,i.concat(l.call(arguments)))},s.guid=e.guid=e.guid||v.guid++,s):t},access:function(e,n,r,i,s,o,u){var a,f=r==null,l=0,c=e.length;if(r&&typeof r=="object"){for(l in r)v.access(e,n,l,r[l],1,o,i);s=1}else if(i!==t){a=u===t&&v.isFunction(i),f&&(a?(a=n,n=function(e,t,n){return a.call(v(e),n)}):(n.call(e,i),n=null));if(n)for(;l<c;l++)n(e[l],r,a?i.call(e[l],l,n(e[l],r)):i,u);s=1}return s?e:f?n.call(e):c?n(e[0],r):o},now:function(){return(new Date).getTime()}}),v.ready.promise=function(t){if(!r){r=v.Deferred();if(i.readyState==="complete")setTimeout(v.ready,1);else if(i.addEventListener)i.addEventListener("DOMContentLoaded",A,!1),e.addEventListener("load",v.ready,!1);else{i.attachEvent("onreadystatechange",A),e.attachEvent("onload",v.ready);var n=!1;try{n=e.frameElement==null&&i.documentElement}catch(s){}n&&n.doScroll&&function o(){if(!v.isReady){try{n.doScroll("left")}catch(e){return setTimeout(o,50)}v.ready()}}()}}return r.promise(t)},v.each("Boolean Number String Function Array Date RegExp Object".split(" "),function(e,t){O["[object "+t+"]"]=t.toLowerCase()}),n=v(i);var M={};v.Callbacks=function(e){e=typeof e=="string"?M[e]||_(e):v.extend({},e);var n,r,i,s,o,u,a=[],f=!e.once&&[],l=function(t){n=e.memory&&t,r=!0,u=s||0,s=0,o=a.length,i=!0;for(;a&&u<o;u++)if(a[u].apply(t[0],t[1])===!1&&e.stopOnFalse){n=!1;break}i=!1,a&&(f?f.length&&l(f.shift()):n?a=[]:c.disable())},c={add:function(){if(a){var t=a.length;(function r(t){v.each(t,function(t,n){var i=v.type(n);i==="function"?(!e.unique||!c.has(n))&&a.push(n):n&&n.length&&i!=="string"&&r(n)})})(arguments),i?o=a.length:n&&(s=t,l(n))}return this},remove:function(){return a&&v.each(arguments,function(e,t){var n;while((n=v.inArray(t,a,n))>-1)a.splice(n,1),i&&(n<=o&&o--,n<=u&&u--)}),this},has:function(e){return v.inArray(e,a)>-1},empty:function(){return a=[],this},disable:function(){return a=f=n=t,this},disabled:function(){return!a},lock:function(){return f=t,n||c.disable(),this},locked:function(){return!f},fireWith:function(e,t){return t=t||[],t=[e,t.slice?t.slice():t],a&&(!r||f)&&(i?f.push(t):l(t)),this},fire:function(){return c.fireWith(this,arguments),this},fired:function(){return!!r}};return c},v.extend({Deferred:function(e){var t=[["resolve","done",v.Callbacks("once memory"),"resolved"],["reject","fail",v.Callbacks("once memory"),"rejected"],["notify","progress",v.Callbacks("memory")]],n="pending",r={state:function(){return n},always:function(){return i.done(arguments).fail(arguments),this},then:function(){var e=arguments;return v.Deferred(function(n){v.each(t,function(t,r){var s=r[0],o=e[t];i[r[1]](v.isFunction(o)?function(){var e=o.apply(this,arguments);e&&v.isFunction(e.promise)?e.promise().done(n.resolve).fail(n.reject).progress(n.notify):n[s+"With"](this===i?n:this,[e])}:n[s])}),e=null}).promise()},promise:function(e){return e!=null?v.extend(e,r):r}},i={};return r.pipe=r.then,v.each(t,function(e,s){var o=s[2],u=s[3];r[s[1]]=o.add,u&&o.add(function(){n=u},t[e^1][2].disable,t[2][2].lock),i[s[0]]=o.fire,i[s[0]+"With"]=o.fireWith}),r.promise(i),e&&e.call(i,i),i},when:function(e){var t=0,n=l.call(arguments),r=n.length,i=r!==1||e&&v.isFunction(e.promise)?r:0,s=i===1?e:v.Deferred(),o=function(e,t,n){return function(r){t[e]=this,n[e]=arguments.length>1?l.call(arguments):r,n===u?s.notifyWith(t,n):--i||s.resolveWith(t,n)}},u,a,f;if(r>1){u=new Array(r),a=new Array(r),f=new Array(r);for(;t<r;t++)n[t]&&v.isFunction(n[t].promise)?n[t].promise().done(o(t,f,n)).fail(s.reject).progress(o(t,a,u)):--i}return i||s.resolveWith(f,n),s.promise()}}),v.support=function(){var t,n,r,s,o,u,a,f,l,c,h,p=i.createElement("div");p.setAttribute("className","t"),p.innerHTML=" <link/><table></table><a href='/a'>a</a><input type='checkbox'/>",n=p.getElementsByTagName("*"),r=p.getElementsByTagName("a")[0];if(!n||!r||!n.length)return{};s=i.createElement("select"),o=s.appendChild(i.createElement("option")),u=p.getElementsByTagName("input")[0],r.style.cssText="top:1px;float:left;opacity:.5",t={leadingWhitespace:p.firstChild.nodeType===3,tbody:!p.getElementsByTagName("tbody").length,htmlSerialize:!!p.getElementsByTagName("link").length,style:/top/.test(r.getAttribute("style")),hrefNormalized:r.getAttribute("href")==="/a",opacity:/^0.5/.test(r.style.opacity),cssFloat:!!r.style.cssFloat,checkOn:u.value==="on",optSelected:o.selected,getSetAttribute:p.className!=="t",enctype:!!i.createElement("form").enctype,html5Clone:i.createElement("nav").cloneNode(!0).outerHTML!=="<:nav></:nav>",boxModel:i.compatMode==="CSS1Compat",submitBubbles:!0,changeBubbles:!0,focusinBubbles:!1,deleteExpando:!0,noCloneEvent:!0,inlineBlockNeedsLayout:!1,shrinkWrapBlocks:!1,reliableMarginRight:!0,boxSizingReliable:!0,pixelPosition:!1},u.checked=!0,t.noCloneChecked=u.cloneNode(!0).checked,s.disabled=!0,t.optDisabled=!o.disabled;try{delete p.test}catch(d){t.deleteExpando=!1}!p.addEventListener&&p.attachEvent&&p.fireEvent&&(p.attachEvent("onclick",h=function(){t.noCloneEvent=!1}),p.cloneNode(!0).fireEvent("onclick"),p.detachEvent("onclick",h)),u=i.createElement("input"),u.value="t",u.setAttribute("type","radio"),t.radioValue=u.value==="t",u.setAttribute("checked","checked"),u.setAttribute("name","t"),p.appendChild(u),a=i.createDocumentFragment(),a.appendChild(p.lastChild),t.checkClone=a.cloneNode(!0).cloneNode(!0).lastChild.checked,t.appendChecked=u.checked,a.removeChild(u),a.appendChild(p);if(p.attachEvent)for(l in{submit:!0,change:!0,focusin:!0})f="on"+l,c=f in p,c||(p.setAttribute(f,"return;"),c=typeof p[f]=="function"),t[l+"Bubbles"]=c;return v(function(){var n,r,s,o,u="padding:0;margin:0;border:0;display:block;overflow:hidden;",a=i.getElementsByTagName("body")[0];if(!a)return;n=i.createElement("div"),n.style.cssText="visibility:hidden;border:0;width:0;height:0;position:static;top:0;margin-top:1px",a.insertBefore(n,a.firstChild),r=i.createElement("div"),n.appendChild(r),r.innerHTML="<table><tr><td></td><td>t</td></tr></table>",s=r.getElementsByTagName("td"),s[0].style.cssText="padding:0;margin:0;border:0;display:none",c=s[0].offsetHeight===0,s[0].style.display="",s[1].style.display="none",t.reliableHiddenOffsets=c&&s[0].offsetHeight===0,r.innerHTML="",r.style.cssText="box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;padding:1px;border:1px;display:block;width:4px;margin-top:1%;position:absolute;top:1%;",t.boxSizing=r.offsetWidth===4,t.doesNotIncludeMarginInBodyOffset=a.offsetTop!==1,e.getComputedStyle&&(t.pixelPosition=(e.getComputedStyle(r,null)||{}).top!=="1%",t.boxSizingReliable=(e.getComputedStyle(r,null)||{width:"4px"}).width==="4px",o=i.createElement("div"),o.style.cssText=r.style.cssText=u,o.style.marginRight=o.style.width="0",r.style.width="1px",r.appendChild(o),t.reliableMarginRight=!parseFloat((e.getComputedStyle(o,null)||{}).marginRight)),typeof r.style.zoom!="undefined"&&(r.innerHTML="",r.style.cssText=u+"width:1px;padding:1px;display:inline;zoom:1",t.inlineBlockNeedsLayout=r.offsetWidth===3,r.style.display="block",r.style.overflow="visible",r.innerHTML="<div></div>",r.firstChild.style.width="5px",t.shrinkWrapBlocks=r.offsetWidth!==3,n.style.zoom=1),a.removeChild(n),n=r=s=o=null}),a.removeChild(p),n=r=s=o=u=a=p=null,t}();var D=/(?:\{[\s\S]*\}|\[[\s\S]*\])$/,P=/([A-Z])/g;v.extend({cache:{},deletedIds:[],uuid:0,expando:"jQuery"+(v.fn.jquery+Math.random()).replace(/\D/g,""),noData:{embed:!0,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",applet:!0},hasData:function(e){return e=e.nodeType?v.cache[e[v.expando]]:e[v.expando],!!e&&!B(e)},data:function(e,n,r,i){if(!v.acceptData(e))return;var s,o,u=v.expando,a=typeof n=="string",f=e.nodeType,l=f?v.cache:e,c=f?e[u]:e[u]&&u;if((!c||!l[c]||!i&&!l[c].data)&&a&&r===t)return;c||(f?e[u]=c=v.deletedIds.pop()||v.guid++:c=u),l[c]||(l[c]={},f||(l[c].toJSON=v.noop));if(typeof n=="object"||typeof n=="function")i?l[c]=v.extend(l[c],n):l[c].data=v.extend(l[c].data,n);return s=l[c],i||(s.data||(s.data={}),s=s.data),r!==t&&(s[v.camelCase(n)]=r),a?(o=s[n],o==null&&(o=s[v.camelCase(n)])):o=s,o},removeData:function(e,t,n){if(!v.acceptData(e))return;var r,i,s,o=e.nodeType,u=o?v.cache:e,a=o?e[v.expando]:v.expando;if(!u[a])return;if(t){r=n?u[a]:u[a].data;if(r){v.isArray(t)||(t in r?t=[t]:(t=v.camelCase(t),t in r?t=[t]:t=t.split(" ")));for(i=0,s=t.length;i<s;i++)delete r[t[i]];if(!(n?B:v.isEmptyObject)(r))return}}if(!n){delete u[a].data;if(!B(u[a]))return}o?v.cleanData([e],!0):v.support.deleteExpando||u!=u.window?delete u[a]:u[a]=null},_data:function(e,t,n){return v.data(e,t,n,!0)},acceptData:function(e){var t=e.nodeName&&v.noData[e.nodeName.toLowerCase()];return!t||t!==!0&&e.getAttribute("classid")===t}}),v.fn.extend({data:function(e,n){var r,i,s,o,u,a=this[0],f=0,l=null;if(e===t){if(this.length){l=v.data(a);if(a.nodeType===1&&!v._data(a,"parsedAttrs")){s=a.attributes;for(u=s.length;f<u;f++)o=s[f].name,o.indexOf("data-")||(o=v.camelCase(o.substring(5)),H(a,o,l[o]));v._data(a,"parsedAttrs",!0)}}return l}return typeof e=="object"?this.each(function(){v.data(this,e)}):(r=e.split(".",2),r[1]=r[1]?"."+r[1]:"",i=r[1]+"!",v.access(this,function(n){if(n===t)return l=this.triggerHandler("getData"+i,[r[0]]),l===t&&a&&(l=v.data(a,e),l=H(a,e,l)),l===t&&r[1]?this.data(r[0]):l;r[1]=n,this.each(function(){var t=v(this);t.triggerHandler("setData"+i,r),v.data(this,e,n),t.triggerHandler("changeData"+i,r)})},null,n,arguments.length>1,null,!1))},removeData:function(e){return this.each(function(){v.removeData(this,e)})}}),v.extend({queue:function(e,t,n){var r;if(e)return t=(t||"fx")+"queue",r=v._data(e,t),n&&(!r||v.isArray(n)?r=v._data(e,t,v.makeArray(n)):r.push(n)),r||[]},dequeue:function(e,t){t=t||"fx";var n=v.queue(e,t),r=n.length,i=n.shift(),s=v._queueHooks(e,t),o=function(){v.dequeue(e,t)};i==="inprogress"&&(i=n.shift(),r--),i&&(t==="fx"&&n.unshift("inprogress"),delete s.stop,i.call(e,o,s)),!r&&s&&s.empty.fire()},_queueHooks:function(e,t){var n=t+"queueHooks";return v._data(e,n)||v._data(e,n,{empty:v.Callbacks("once memory").add(function(){v.removeData(e,t+"queue",!0),v.removeData(e,n,!0)})})}}),v.fn.extend({queue:function(e,n){var r=2;return typeof e!="string"&&(n=e,e="fx",r--),arguments.length<r?v.queue(this[0],e):n===t?this:this.each(function(){var t=v.queue(this,e,n);v._queueHooks(this,e),e==="fx"&&t[0]!=="inprogress"&&v.dequeue(this,e)})},dequeue:function(e){return this.each(function(){v.dequeue(this,e)})},delay:function(e,t){return e=v.fx?v.fx.speeds[e]||e:e,t=t||"fx",this.queue(t,function(t,n){var r=setTimeout(t,e);n.stop=function(){clearTimeout(r)}})},clearQueue:function(e){return this.queue(e||"fx",[])},promise:function(e,n){var r,i=1,s=v.Deferred(),o=this,u=this.length,a=function(){--i||s.resolveWith(o,[o])};typeof e!="string"&&(n=e,e=t),e=e||"fx";while(u--)r=v._data(o[u],e+"queueHooks"),r&&r.empty&&(i++,r.empty.add(a));return a(),s.promise(n)}});var j,F,I,q=/[\t\r\n]/g,R=/\r/g,U=/^(?:button|input)$/i,z=/^(?:button|input|object|select|textarea)$/i,W=/^a(?:rea|)$/i,X=/^(?:autofocus|autoplay|async|checked|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped|selected)$/i,V=v.support.getSetAttribute;v.fn.extend({attr:function(e,t){return v.access(this,v.attr,e,t,arguments.length>1)},removeAttr:function(e){return this.each(function(){v.removeAttr(this,e)})},prop:function(e,t){return v.access(this,v.prop,e,t,arguments.length>1)},removeProp:function(e){return e=v.propFix[e]||e,this.each(function(){try{this[e]=t,delete this[e]}catch(n){}})},addClass:function(e){var t,n,r,i,s,o,u;if(v.isFunction(e))return this.each(function(t){v(this).addClass(e.call(this,t,this.className))});if(e&&typeof e=="string"){t=e.split(y);for(n=0,r=this.length;n<r;n++){i=this[n];if(i.nodeType===1)if(!i.className&&t.length===1)i.className=e;else{s=" "+i.className+" ";for(o=0,u=t.length;o<u;o++)s.indexOf(" "+t[o]+" ")<0&&(s+=t[o]+" ");i.className=v.trim(s)}}}return this},removeClass:function(e){var n,r,i,s,o,u,a;if(v.isFunction(e))return this.each(function(t){v(this).removeClass(e.call(this,t,this.className))});if(e&&typeof e=="string"||e===t){n=(e||"").split(y);for(u=0,a=this.length;u<a;u++){i=this[u];if(i.nodeType===1&&i.className){r=(" "+i.className+" ").replace(q," ");for(s=0,o=n.length;s<o;s++)while(r.indexOf(" "+n[s]+" ")>=0)r=r.replace(" "+n[s]+" "," ");i.className=e?v.trim(r):""}}}return this},toggleClass:function(e,t){var n=typeof e,r=typeof t=="boolean";return v.isFunction(e)?this.each(function(n){v(this).toggleClass(e.call(this,n,this.className,t),t)}):this.each(function(){if(n==="string"){var i,s=0,o=v(this),u=t,a=e.split(y);while(i=a[s++])u=r?u:!o.hasClass(i),o[u?"addClass":"removeClass"](i)}else if(n==="undefined"||n==="boolean")this.className&&v._data(this,"__className__",this.className),this.className=this.className||e===!1?"":v._data(this,"__className__")||""})},hasClass:function(e){var t=" "+e+" ",n=0,r=this.length;for(;n<r;n++)if(this[n].nodeType===1&&(" "+this[n].className+" ").replace(q," ").indexOf(t)>=0)return!0;return!1},val:function(e){var n,r,i,s=this[0];if(!arguments.length){if(s)return n=v.valHooks[s.type]||v.valHooks[s.nodeName.toLowerCase()],n&&"get"in n&&(r=n.get(s,"value"))!==t?r:(r=s.value,typeof r=="string"?r.replace(R,""):r==null?"":r);return}return i=v.isFunction(e),this.each(function(r){var s,o=v(this);if(this.nodeType!==1)return;i?s=e.call(this,r,o.val()):s=e,s==null?s="":typeof s=="number"?s+="":v.isArray(s)&&(s=v.map(s,function(e){return e==null?"":e+""})),n=v.valHooks[this.type]||v.valHooks[this.nodeName.toLowerCase()];if(!n||!("set"in n)||n.set(this,s,"value")===t)this.value=s})}}),v.extend({valHooks:{option:{get:function(e){var t=e.attributes.value;return!t||t.specified?e.value:e.text}},select:{get:function(e){var t,n,r=e.options,i=e.selectedIndex,s=e.type==="select-one"||i<0,o=s?null:[],u=s?i+1:r.length,a=i<0?u:s?i:0;for(;a<u;a++){n=r[a];if((n.selected||a===i)&&(v.support.optDisabled?!n.disabled:n.getAttribute("disabled")===null)&&(!n.parentNode.disabled||!v.nodeName(n.parentNode,"optgroup"))){t=v(n).val();if(s)return t;o.push(t)}}return o},set:function(e,t){var n=v.makeArray(t);return v(e).find("option").each(function(){this.selected=v.inArray(v(this).val(),n)>=0}),n.length||(e.selectedIndex=-1),n}}},attrFn:{},attr:function(e,n,r,i){var s,o,u,a=e.nodeType;if(!e||a===3||a===8||a===2)return;if(i&&v.isFunction(v.fn[n]))return v(e)[n](r);if(typeof e.getAttribute=="undefined")return v.prop(e,n,r);u=a!==1||!v.isXMLDoc(e),u&&(n=n.toLowerCase(),o=v.attrHooks[n]||(X.test(n)?F:j));if(r!==t){if(r===null){v.removeAttr(e,n);return}return o&&"set"in o&&u&&(s=o.set(e,r,n))!==t?s:(e.setAttribute(n,r+""),r)}return o&&"get"in o&&u&&(s=o.get(e,n))!==null?s:(s=e.getAttribute(n),s===null?t:s)},removeAttr:function(e,t){var n,r,i,s,o=0;if(t&&e.nodeType===1){r=t.split(y);for(;o<r.length;o++)i=r[o],i&&(n=v.propFix[i]||i,s=X.test(i),s||v.attr(e,i,""),e.removeAttribute(V?i:n),s&&n in e&&(e[n]=!1))}},attrHooks:{type:{set:function(e,t){if(U.test(e.nodeName)&&e.parentNode)v.error("type property can't be changed");else if(!v.support.radioValue&&t==="radio"&&v.nodeName(e,"input")){var n=e.value;return e.setAttribute("type",t),n&&(e.value=n),t}}},value:{get:function(e,t){return j&&v.nodeName(e,"button")?j.get(e,t):t in e?e.value:null},set:function(e,t,n){if(j&&v.nodeName(e,"button"))return j.set(e,t,n);e.value=t}}},propFix:{tabindex:"tabIndex",readonly:"readOnly","for":"htmlFor","class":"className",maxlength:"maxLength",cellspacing:"cellSpacing",cellpadding:"cellPadding",rowspan:"rowSpan",colspan:"colSpan",usemap:"useMap",frameborder:"frameBorder",contenteditable:"contentEditable"},prop:function(e,n,r){var i,s,o,u=e.nodeType;if(!e||u===3||u===8||u===2)return;return o=u!==1||!v.isXMLDoc(e),o&&(n=v.propFix[n]||n,s=v.propHooks[n]),r!==t?s&&"set"in s&&(i=s.set(e,r,n))!==t?i:e[n]=r:s&&"get"in s&&(i=s.get(e,n))!==null?i:e[n]},propHooks:{tabIndex:{get:function(e){var n=e.getAttributeNode("tabindex");return n&&n.specified?parseInt(n.value,10):z.test(e.nodeName)||W.test(e.nodeName)&&e.href?0:t}}}}),F={get:function(e,n){var r,i=v.prop(e,n);return i===!0||typeof i!="boolean"&&(r=e.getAttributeNode(n))&&r.nodeValue!==!1?n.toLowerCase():t},set:function(e,t,n){var r;return t===!1?v.removeAttr(e,n):(r=v.propFix[n]||n,r in e&&(e[r]=!0),e.setAttribute(n,n.toLowerCase())),n}},V||(I={name:!0,id:!0,coords:!0},j=v.valHooks.button={get:function(e,n){var r;return r=e.getAttributeNode(n),r&&(I[n]?r.value!=="":r.specified)?r.value:t},set:function(e,t,n){var r=e.getAttributeNode(n);return r||(r=i.createAttribute(n),e.setAttributeNode(r)),r.value=t+""}},v.each(["width","height"],function(e,t){v.attrHooks[t]=v.extend(v.attrHooks[t],{set:function(e,n){if(n==="")return e.setAttribute(t,"auto"),n}})}),v.attrHooks.contenteditable={get:j.get,set:function(e,t,n){t===""&&(t="false"),j.set(e,t,n)}}),v.support.hrefNormalized||v.each(["href","src","width","height"],function(e,n){v.attrHooks[n]=v.extend(v.attrHooks[n],{get:function(e){var r=e.getAttribute(n,2);return r===null?t:r}})}),v.support.style||(v.attrHooks.style={get:function(e){return e.style.cssText.toLowerCase()||t},set:function(e,t){return e.style.cssText=t+""}}),v.support.optSelected||(v.propHooks.selected=v.extend(v.propHooks.selected,{get:function(e){var t=e.parentNode;return t&&(t.selectedIndex,t.parentNode&&t.parentNode.selectedIndex),null}})),v.support.enctype||(v.propFix.enctype="encoding"),v.support.checkOn||v.each(["radio","checkbox"],function(){v.valHooks[this]={get:function(e){return e.getAttribute("value")===null?"on":e.value}}}),v.each(["radio","checkbox"],function(){v.valHooks[this]=v.extend(v.valHooks[this],{set:function(e,t){if(v.isArray(t))return e.checked=v.inArray(v(e).val(),t)>=0}})});var $=/^(?:textarea|input|select)$/i,J=/^([^\.]*|)(?:\.(.+)|)$/,K=/(?:^|\s)hover(\.\S+|)\b/,Q=/^key/,G=/^(?:mouse|contextmenu)|click/,Y=/^(?:focusinfocus|focusoutblur)$/,Z=function(e){return v.event.special.hover?e:e.replace(K,"mouseenter$1 mouseleave$1")};v.event={add:function(e,n,r,i,s){var o,u,a,f,l,c,h,p,d,m,g;if(e.nodeType===3||e.nodeType===8||!n||!r||!(o=v._data(e)))return;r.handler&&(d=r,r=d.handler,s=d.selector),r.guid||(r.guid=v.guid++),a=o.events,a||(o.events=a={}),u=o.handle,u||(o.handle=u=function(e){return typeof v=="undefined"||!!e&&v.event.triggered===e.type?t:v.event.dispatch.apply(u.elem,arguments)},u.elem=e),n=v.trim(Z(n)).split(" ");for(f=0;f<n.length;f++){l=J.exec(n[f])||[],c=l[1],h=(l[2]||"").split(".").sort(),g=v.event.special[c]||{},c=(s?g.delegateType:g.bindType)||c,g=v.event.special[c]||{},p=v.extend({type:c,origType:l[1],data:i,handler:r,guid:r.guid,selector:s,needsContext:s&&v.expr.match.needsContext.test(s),namespace:h.join(".")},d),m=a[c];if(!m){m=a[c]=[],m.delegateCount=0;if(!g.setup||g.setup.call(e,i,h,u)===!1)e.addEventListener?e.addEventListener(c,u,!1):e.attachEvent&&e.attachEvent("on"+c,u)}g.add&&(g.add.call(e,p),p.handler.guid||(p.handler.guid=r.guid)),s?m.splice(m.delegateCount++,0,p):m.push(p),v.event.global[c]=!0}e=null},global:{},remove:function(e,t,n,r,i){var s,o,u,a,f,l,c,h,p,d,m,g=v.hasData(e)&&v._data(e);if(!g||!(h=g.events))return;t=v.trim(Z(t||"")).split(" ");for(s=0;s<t.length;s++){o=J.exec(t[s])||[],u=a=o[1],f=o[2];if(!u){for(u in h)v.event.remove(e,u+t[s],n,r,!0);continue}p=v.event.special[u]||{},u=(r?p.delegateType:p.bindType)||u,d=h[u]||[],l=d.length,f=f?new RegExp("(^|\\.)"+f.split(".").sort().join("\\.(?:.*\\.|)")+"(\\.|$)"):null;for(c=0;c<d.length;c++)m=d[c],(i||a===m.origType)&&(!n||n.guid===m.guid)&&(!f||f.test(m.namespace))&&(!r||r===m.selector||r==="**"&&m.selector)&&(d.splice(c--,1),m.selector&&d.delegateCount--,p.remove&&p.remove.call(e,m));d.length===0&&l!==d.length&&((!p.teardown||p.teardown.call(e,f,g.handle)===!1)&&v.removeEvent(e,u,g.handle),delete h[u])}v.isEmptyObject(h)&&(delete g.handle,v.removeData(e,"events",!0))},customEvent:{getData:!0,setData:!0,changeData:!0},trigger:function(n,r,s,o){if(!s||s.nodeType!==3&&s.nodeType!==8){var u,a,f,l,c,h,p,d,m,g,y=n.type||n,b=[];if(Y.test(y+v.event.triggered))return;y.indexOf("!")>=0&&(y=y.slice(0,-1),a=!0),y.indexOf(".")>=0&&(b=y.split("."),y=b.shift(),b.sort());if((!s||v.event.customEvent[y])&&!v.event.global[y])return;n=typeof n=="object"?n[v.expando]?n:new v.Event(y,n):new v.Event(y),n.type=y,n.isTrigger=!0,n.exclusive=a,n.namespace=b.join("."),n.namespace_re=n.namespace?new RegExp("(^|\\.)"+b.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,h=y.indexOf(":")<0?"on"+y:"";if(!s){u=v.cache;for(f in u)u[f].events&&u[f].events[y]&&v.event.trigger(n,r,u[f].handle.elem,!0);return}n.result=t,n.target||(n.target=s),r=r!=null?v.makeArray(r):[],r.unshift(n),p=v.event.special[y]||{};if(p.trigger&&p.trigger.apply(s,r)===!1)return;m=[[s,p.bindType||y]];if(!o&&!p.noBubble&&!v.isWindow(s)){g=p.delegateType||y,l=Y.test(g+y)?s:s.parentNode;for(c=s;l;l=l.parentNode)m.push([l,g]),c=l;c===(s.ownerDocument||i)&&m.push([c.defaultView||c.parentWindow||e,g])}for(f=0;f<m.length&&!n.isPropagationStopped();f++)l=m[f][0],n.type=m[f][1],d=(v._data(l,"events")||{})[n.type]&&v._data(l,"handle"),d&&d.apply(l,r),d=h&&l[h],d&&v.acceptData(l)&&d.apply&&d.apply(l,r)===!1&&n.preventDefault();return n.type=y,!o&&!n.isDefaultPrevented()&&(!p._default||p._default.apply(s.ownerDocument,r)===!1)&&(y!=="click"||!v.nodeName(s,"a"))&&v.acceptData(s)&&h&&s[y]&&(y!=="focus"&&y!=="blur"||n.target.offsetWidth!==0)&&!v.isWindow(s)&&(c=s[h],c&&(s[h]=null),v.event.triggered=y,s[y](),v.event.triggered=t,c&&(s[h]=c)),n.result}return},dispatch:function(n){n=v.event.fix(n||e.event);var r,i,s,o,u,a,f,c,h,p,d=(v._data(this,"events")||{})[n.type]||[],m=d.delegateCount,g=l.call(arguments),y=!n.exclusive&&!n.namespace,b=v.event.special[n.type]||{},w=[];g[0]=n,n.delegateTarget=this;if(b.preDispatch&&b.preDispatch.call(this,n)===!1)return;if(m&&(!n.button||n.type!=="click"))for(s=n.target;s!=this;s=s.parentNode||this)if(s.disabled!==!0||n.type!=="click"){u={},f=[];for(r=0;r<m;r++)c=d[r],h=c.selector,u[h]===t&&(u[h]=c.needsContext?v(h,this).index(s)>=0:v.find(h,this,null,[s]).length),u[h]&&f.push(c);f.length&&w.push({elem:s,matches:f})}d.length>m&&w.push({elem:this,matches:d.slice(m)});for(r=0;r<w.length&&!n.isPropagationStopped();r++){a=w[r],n.currentTarget=a.elem;for(i=0;i<a.matches.length&&!n.isImmediatePropagationStopped();i++){c=a.matches[i];if(y||!n.namespace&&!c.namespace||n.namespace_re&&n.namespace_re.test(c.namespace))n.data=c.data,n.handleObj=c,o=((v.event.special[c.origType]||{}).handle||c.handler).apply(a.elem,g),o!==t&&(n.result=o,o===!1&&(n.preventDefault(),n.stopPropagation()))}}return b.postDispatch&&b.postDispatch.call(this,n),n.result},props:"attrChange attrName relatedNode srcElement altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),fixHooks:{},keyHooks:{props:"char charCode key keyCode".split(" "),filter:function(e,t){return e.which==null&&(e.which=t.charCode!=null?t.charCode:t.keyCode),e}},mouseHooks:{props:"button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "),filter:function(e,n){var r,s,o,u=n.button,a=n.fromElement;return e.pageX==null&&n.clientX!=null&&(r=e.target.ownerDocument||i,s=r.documentElement,o=r.body,e.pageX=n.clientX+(s&&s.scrollLeft||o&&o.scrollLeft||0)-(s&&s.clientLeft||o&&o.clientLeft||0),e.pageY=n.clientY+(s&&s.scrollTop||o&&o.scrollTop||0)-(s&&s.clientTop||o&&o.clientTop||0)),!e.relatedTarget&&a&&(e.relatedTarget=a===e.target?n.toElement:a),!e.which&&u!==t&&(e.which=u&1?1:u&2?3:u&4?2:0),e}},fix:function(e){if(e[v.expando])return e;var t,n,r=e,s=v.event.fixHooks[e.type]||{},o=s.props?this.props.concat(s.props):this.props;e=v.Event(r);for(t=o.length;t;)n=o[--t],e[n]=r[n];return e.target||(e.target=r.srcElement||i),e.target.nodeType===3&&(e.target=e.target.parentNode),e.metaKey=!!e.metaKey,s.filter?s.filter(e,r):e},special:{load:{noBubble:!0},focus:{delegateType:"focusin"},blur:{delegateType:"focusout"},beforeunload:{setup:function(e,t,n){v.isWindow(this)&&(this.onbeforeunload=n)},teardown:function(e,t){this.onbeforeunload===t&&(this.onbeforeunload=null)}}},simulate:function(e,t,n,r){var i=v.extend(new v.Event,n,{type:e,isSimulated:!0,originalEvent:{}});r?v.event.trigger(i,null,t):v.event.dispatch.call(t,i),i.isDefaultPrevented()&&n.preventDefault()}},v.event.handle=v.event.dispatch,v.removeEvent=i.removeEventListener?function(e,t,n){e.removeEventListener&&e.removeEventListener(t,n,!1)}:function(e,t,n){var r="on"+t;e.detachEvent&&(typeof e[r]=="undefined"&&(e[r]=null),e.detachEvent(r,n))},v.Event=function(e,t){if(!(this instanceof v.Event))return new v.Event(e,t);e&&e.type?(this.originalEvent=e,this.type=e.type,this.isDefaultPrevented=e.defaultPrevented||e.returnValue===!1||e.getPreventDefault&&e.getPreventDefault()?tt:et):this.type=e,t&&v.extend(this,t),this.timeStamp=e&&e.timeStamp||v.now(),this[v.expando]=!0},v.Event.prototype={preventDefault:function(){this.isDefaultPrevented=tt;var e=this.originalEvent;if(!e)return;e.preventDefault?e.preventDefault():e.returnValue=!1},stopPropagation:function(){this.isPropagationStopped=tt;var e=this.originalEvent;if(!e)return;e.stopPropagation&&e.stopPropagation(),e.cancelBubble=!0},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=tt,this.stopPropagation()},isDefaultPrevented:et,isPropagationStopped:et,isImmediatePropagationStopped:et},v.each({mouseenter:"mouseover",mouseleave:"mouseout"},function(e,t){v.event.special[e]={delegateType:t,bindType:t,handle:function(e){var n,r=this,i=e.relatedTarget,s=e.handleObj,o=s.selector;if(!i||i!==r&&!v.contains(r,i))e.type=s.origType,n=s.handler.apply(this,arguments),e.type=t;return n}}}),v.support.submitBubbles||(v.event.special.submit={setup:function(){if(v.nodeName(this,"form"))return!1;v.event.add(this,"click._submit keypress._submit",function(e){var n=e.target,r=v.nodeName(n,"input")||v.nodeName(n,"button")?n.form:t;r&&!v._data(r,"_submit_attached")&&(v.event.add(r,"submit._submit",function(e){e._submit_bubble=!0}),v._data(r,"_submit_attached",!0))})},postDispatch:function(e){e._submit_bubble&&(delete e._submit_bubble,this.parentNode&&!e.isTrigger&&v.event.simulate("submit",this.parentNode,e,!0))},teardown:function(){if(v.nodeName(this,"form"))return!1;v.event.remove(this,"._submit")}}),v.support.changeBubbles||(v.event.special.change={setup:function(){if($.test(this.nodeName)){if(this.type==="checkbox"||this.type==="radio")v.event.add(this,"propertychange._change",function(e){e.originalEvent.propertyName==="checked"&&(this._just_changed=!0)}),v.event.add(this,"click._change",function(e){this._just_changed&&!e.isTrigger&&(this._just_changed=!1),v.event.simulate("change",this,e,!0)});return!1}v.event.add(this,"beforeactivate._change",function(e){var t=e.target;$.test(t.nodeName)&&!v._data(t,"_change_attached")&&(v.event.add(t,"change._change",function(e){this.parentNode&&!e.isSimulated&&!e.isTrigger&&v.event.simulate("change",this.parentNode,e,!0)}),v._data(t,"_change_attached",!0))})},handle:function(e){var t=e.target;if(this!==t||e.isSimulated||e.isTrigger||t.type!=="radio"&&t.type!=="checkbox")return e.handleObj.handler.apply(this,arguments)},teardown:function(){return v.event.remove(this,"._change"),!$.test(this.nodeName)}}),v.support.focusinBubbles||v.each({focus:"focusin",blur:"focusout"},function(e,t){var n=0,r=function(e){v.event.simulate(t,e.target,v.event.fix(e),!0)};v.event.special[t]={setup:function(){n++===0&&i.addEventListener(e,r,!0)},teardown:function(){--n===0&&i.removeEventListener(e,r,!0)}}}),v.fn.extend({on:function(e,n,r,i,s){var o,u;if(typeof e=="object"){typeof n!="string"&&(r=r||n,n=t);for(u in e)this.on(u,n,r,e[u],s);return this}r==null&&i==null?(i=n,r=n=t):i==null&&(typeof n=="string"?(i=r,r=t):(i=r,r=n,n=t));if(i===!1)i=et;else if(!i)return this;return s===1&&(o=i,i=function(e){return v().off(e),o.apply(this,arguments)},i.guid=o.guid||(o.guid=v.guid++)),this.each(function(){v.event.add(this,e,i,r,n)})},one:function(e,t,n,r){return this.on(e,t,n,r,1)},off:function(e,n,r){var i,s;if(e&&e.preventDefault&&e.handleObj)return i=e.handleObj,v(e.delegateTarget).off(i.namespace?i.origType+"."+i.namespace:i.origType,i.selector,i.handler),this;if(typeof e=="object"){for(s in e)this.off(s,n,e[s]);return this}if(n===!1||typeof n=="function")r=n,n=t;return r===!1&&(r=et),this.each(function(){v.event.remove(this,e,r,n)})},bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},live:function(e,t,n){return v(this.context).on(e,this.selector,t,n),this},die:function(e,t){return v(this.context).off(e,this.selector||"**",t),this},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return arguments.length===1?this.off(e,"**"):this.off(t,e||"**",n)},trigger:function(e,t){return this.each(function(){v.event.trigger(e,t,this)})},triggerHandler:function(e,t){if(this[0])return v.event.trigger(e,t,this[0],!0)},toggle:function(e){var t=arguments,n=e.guid||v.guid++,r=0,i=function(n){var i=(v._data(this,"lastToggle"+e.guid)||0)%r;return v._data(this,"lastToggle"+e.guid,i+1),n.preventDefault(),t[i].apply(this,arguments)||!1};i.guid=n;while(r<t.length)t[r++].guid=n;return this.click(i)},hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)}}),v.each("blur focus focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup error contextmenu".split(" "),function(e,t){v.fn[t]=function(e,n){return n==null&&(n=e,e=null),arguments.length>0?this.on(t,null,e,n):this.trigger(t)},Q.test(t)&&(v.event.fixHooks[t]=v.event.keyHooks),G.test(t)&&(v.event.fixHooks[t]=v.event.mouseHooks)}),function(e,t){function nt(e,t,n,r){n=n||[],t=t||g;var i,s,a,f,l=t.nodeType;if(!e||typeof e!="string")return n;if(l!==1&&l!==9)return[];a=o(t);if(!a&&!r)if(i=R.exec(e))if(f=i[1]){if(l===9){s=t.getElementById(f);if(!s||!s.parentNode)return n;if(s.id===f)return n.push(s),n}else if(t.ownerDocument&&(s=t.ownerDocument.getElementById(f))&&u(t,s)&&s.id===f)return n.push(s),n}else{if(i[2])return S.apply(n,x.call(t.getElementsByTagName(e),0)),n;if((f=i[3])&&Z&&t.getElementsByClassName)return S.apply(n,x.call(t.getElementsByClassName(f),0)),n}return vt(e.replace(j,"$1"),t,n,r,a)}function rt(e){return function(t){var n=t.nodeName.toLowerCase();return n==="input"&&t.type===e}}function it(e){return function(t){var n=t.nodeName.toLowerCase();return(n==="input"||n==="button")&&t.type===e}}function st(e){return N(function(t){return t=+t,N(function(n,r){var i,s=e([],n.length,t),o=s.length;while(o--)n[i=s[o]]&&(n[i]=!(r[i]=n[i]))})})}function ot(e,t,n){if(e===t)return n;var r=e.nextSibling;while(r){if(r===t)return-1;r=r.nextSibling}return 1}function ut(e,t){var n,r,s,o,u,a,f,l=L[d][e+" "];if(l)return t?0:l.slice(0);u=e,a=[],f=i.preFilter;while(u){if(!n||(r=F.exec(u)))r&&(u=u.slice(r[0].length)||u),a.push(s=[]);n=!1;if(r=I.exec(u))s.push(n=new m(r.shift())),u=u.slice(n.length),n.type=r[0].replace(j," ");for(o in i.filter)(r=J[o].exec(u))&&(!f[o]||(r=f[o](r)))&&(s.push(n=new m(r.shift())),u=u.slice(n.length),n.type=o,n.matches=r);if(!n)break}return t?u.length:u?nt.error(e):L(e,a).slice(0)}function at(e,t,r){var i=t.dir,s=r&&t.dir==="parentNode",o=w++;return t.first?function(t,n,r){while(t=t[i])if(s||t.nodeType===1)return e(t,n,r)}:function(t,r,u){if(!u){var a,f=b+" "+o+" ",l=f+n;while(t=t[i])if(s||t.nodeType===1){if((a=t[d])===l)return t.sizset;if(typeof a=="string"&&a.indexOf(f)===0){if(t.sizset)return t}else{t[d]=l;if(e(t,r,u))return t.sizset=!0,t;t.sizset=!1}}}else while(t=t[i])if(s||t.nodeType===1)if(e(t,r,u))return t}}function ft(e){return e.length>1?function(t,n,r){var i=e.length;while(i--)if(!e[i](t,n,r))return!1;return!0}:e[0]}function lt(e,t,n,r,i){var s,o=[],u=0,a=e.length,f=t!=null;for(;u<a;u++)if(s=e[u])if(!n||n(s,r,i))o.push(s),f&&t.push(u);return o}function ct(e,t,n,r,i,s){return r&&!r[d]&&(r=ct(r)),i&&!i[d]&&(i=ct(i,s)),N(function(s,o,u,a){var f,l,c,h=[],p=[],d=o.length,v=s||dt(t||"*",u.nodeType?[u]:u,[]),m=e&&(s||!t)?lt(v,h,e,u,a):v,g=n?i||(s?e:d||r)?[]:o:m;n&&n(m,g,u,a);if(r){f=lt(g,p),r(f,[],u,a),l=f.length;while(l--)if(c=f[l])g[p[l]]=!(m[p[l]]=c)}if(s){if(i||e){if(i){f=[],l=g.length;while(l--)(c=g[l])&&f.push(m[l]=c);i(null,g=[],f,a)}l=g.length;while(l--)(c=g[l])&&(f=i?T.call(s,c):h[l])>-1&&(s[f]=!(o[f]=c))}}else g=lt(g===o?g.splice(d,g.length):g),i?i(null,o,g,a):S.apply(o,g)})}function ht(e){var t,n,r,s=e.length,o=i.relative[e[0].type],u=o||i.relative[" "],a=o?1:0,f=at(function(e){return e===t},u,!0),l=at(function(e){return T.call(t,e)>-1},u,!0),h=[function(e,n,r){return!o&&(r||n!==c)||((t=n).nodeType?f(e,n,r):l(e,n,r))}];for(;a<s;a++)if(n=i.relative[e[a].type])h=[at(ft(h),n)];else{n=i.filter[e[a].type].apply(null,e[a].matches);if(n[d]){r=++a;for(;r<s;r++)if(i.relative[e[r].type])break;return ct(a>1&&ft(h),a>1&&e.slice(0,a-1).join("").replace(j,"$1"),n,a<r&&ht(e.slice(a,r)),r<s&&ht(e=e.slice(r)),r<s&&e.join(""))}h.push(n)}return ft(h)}function pt(e,t){var r=t.length>0,s=e.length>0,o=function(u,a,f,l,h){var p,d,v,m=[],y=0,w="0",x=u&&[],T=h!=null,N=c,C=u||s&&i.find.TAG("*",h&&a.parentNode||a),k=b+=N==null?1:Math.E;T&&(c=a!==g&&a,n=o.el);for(;(p=C[w])!=null;w++){if(s&&p){for(d=0;v=e[d];d++)if(v(p,a,f)){l.push(p);break}T&&(b=k,n=++o.el)}r&&((p=!v&&p)&&y--,u&&x.push(p))}y+=w;if(r&&w!==y){for(d=0;v=t[d];d++)v(x,m,a,f);if(u){if(y>0)while(w--)!x[w]&&!m[w]&&(m[w]=E.call(l));m=lt(m)}S.apply(l,m),T&&!u&&m.length>0&&y+t.length>1&&nt.uniqueSort(l)}return T&&(b=k,c=N),x};return o.el=0,r?N(o):o}function dt(e,t,n){var r=0,i=t.length;for(;r<i;r++)nt(e,t[r],n);return n}function vt(e,t,n,r,s){var o,u,f,l,c,h=ut(e),p=h.length;if(!r&&h.length===1){u=h[0]=h[0].slice(0);if(u.length>2&&(f=u[0]).type==="ID"&&t.nodeType===9&&!s&&i.relative[u[1].type]){t=i.find.ID(f.matches[0].replace($,""),t,s)[0];if(!t)return n;e=e.slice(u.shift().length)}for(o=J.POS.test(e)?-1:u.length-1;o>=0;o--){f=u[o];if(i.relative[l=f.type])break;if(c=i.find[l])if(r=c(f.matches[0].replace($,""),z.test(u[0].type)&&t.parentNode||t,s)){u.splice(o,1),e=r.length&&u.join("");if(!e)return S.apply(n,x.call(r,0)),n;break}}}return a(e,h)(r,t,s,n,z.test(e)),n}function mt(){}var n,r,i,s,o,u,a,f,l,c,h=!0,p="undefined",d=("sizcache"+Math.random()).replace(".",""),m=String,g=e.document,y=g.documentElement,b=0,w=0,E=[].pop,S=[].push,x=[].slice,T=[].indexOf||function(e){var t=0,n=this.length;for(;t<n;t++)if(this[t]===e)return t;return-1},N=function(e,t){return e[d]=t==null||t,e},C=function(){var e={},t=[];return N(function(n,r){return t.push(n)>i.cacheLength&&delete e[t.shift()],e[n+" "]=r},e)},k=C(),L=C(),A=C(),O="[\\x20\\t\\r\\n\\f]",M="(?:\\\\.|[-\\w]|[^\\x00-\\xa0])+",_=M.replace("w","w#"),D="([*^$|!~]?=)",P="\\["+O+"*("+M+")"+O+"*(?:"+D+O+"*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|("+_+")|)|)"+O+"*\\]",H=":("+M+")(?:\\((?:(['\"])((?:\\\\.|[^\\\\])*?)\\2|([^()[\\]]*|(?:(?:"+P+")|[^:]|\\\\.)*|.*))\\)|)",B=":(even|odd|eq|gt|lt|nth|first|last)(?:\\("+O+"*((?:-\\d)?\\d*)"+O+"*\\)|)(?=[^-]|$)",j=new RegExp("^"+O+"+|((?:^|[^\\\\])(?:\\\\.)*)"+O+"+$","g"),F=new RegExp("^"+O+"*,"+O+"*"),I=new RegExp("^"+O+"*([\\x20\\t\\r\\n\\f>+~])"+O+"*"),q=new RegExp(H),R=/^(?:#([\w\-]+)|(\w+)|\.([\w\-]+))$/,U=/^:not/,z=/[\x20\t\r\n\f]*[+~]/,W=/:not\($/,X=/h\d/i,V=/input|select|textarea|button/i,$=/\\(?!\\)/g,J={ID:new RegExp("^#("+M+")"),CLASS:new RegExp("^\\.("+M+")"),NAME:new RegExp("^\\[name=['\"]?("+M+")['\"]?\\]"),TAG:new RegExp("^("+M.replace("w","w*")+")"),ATTR:new RegExp("^"+P),PSEUDO:new RegExp("^"+H),POS:new RegExp(B,"i"),CHILD:new RegExp("^:(only|nth|first|last)-child(?:\\("+O+"*(even|odd|(([+-]|)(\\d*)n|)"+O+"*(?:([+-]|)"+O+"*(\\d+)|))"+O+"*\\)|)","i"),needsContext:new RegExp("^"+O+"*[>+~]|"+B,"i")},K=function(e){var t=g.createElement("div");try{return e(t)}catch(n){return!1}finally{t=null}},Q=K(function(e){return e.appendChild(g.createComment("")),!e.getElementsByTagName("*").length}),G=K(function(e){return e.innerHTML="<a href='#'></a>",e.firstChild&&typeof e.firstChild.getAttribute!==p&&e.firstChild.getAttribute("href")==="#"}),Y=K(function(e){e.innerHTML="<select></select>";var t=typeof e.lastChild.getAttribute("multiple");return t!=="boolean"&&t!=="string"}),Z=K(function(e){return e.innerHTML="<div class='hidden e'></div><div class='hidden'></div>",!e.getElementsByClassName||!e.getElementsByClassName("e").length?!1:(e.lastChild.className="e",e.getElementsByClassName("e").length===2)}),et=K(function(e){e.id=d+0,e.innerHTML="<a name='"+d+"'></a><div name='"+d+"'></div>",y.insertBefore(e,y.firstChild);var t=g.getElementsByName&&g.getElementsByName(d).length===2+g.getElementsByName(d+0).length;return r=!g.getElementById(d),y.removeChild(e),t});try{x.call(y.childNodes,0)[0].nodeType}catch(tt){x=function(e){var t,n=[];for(;t=this[e];e++)n.push(t);return n}}nt.matches=function(e,t){return nt(e,null,null,t)},nt.matchesSelector=function(e,t){return nt(t,null,null,[e]).length>0},s=nt.getText=function(e){var t,n="",r=0,i=e.nodeType;if(i){if(i===1||i===9||i===11){if(typeof e.textContent=="string")return e.textContent;for(e=e.firstChild;e;e=e.nextSibling)n+=s(e)}else if(i===3||i===4)return e.nodeValue}else for(;t=e[r];r++)n+=s(t);return n},o=nt.isXML=function(e){var t=e&&(e.ownerDocument||e).documentElement;return t?t.nodeName!=="HTML":!1},u=nt.contains=y.contains?function(e,t){var n=e.nodeType===9?e.documentElement:e,r=t&&t.parentNode;return e===r||!!(r&&r.nodeType===1&&n.contains&&n.contains(r))}:y.compareDocumentPosition?function(e,t){return t&&!!(e.compareDocumentPosition(t)&16)}:function(e,t){while(t=t.parentNode)if(t===e)return!0;return!1},nt.attr=function(e,t){var n,r=o(e);return r||(t=t.toLowerCase()),(n=i.attrHandle[t])?n(e):r||Y?e.getAttribute(t):(n=e.getAttributeNode(t),n?typeof e[t]=="boolean"?e[t]?t:null:n.specified?n.value:null:null)},i=nt.selectors={cacheLength:50,createPseudo:N,match:J,attrHandle:G?{}:{href:function(e){return e.getAttribute("href",2)},type:function(e){return e.getAttribute("type")}},find:{ID:r?function(e,t,n){if(typeof t.getElementById!==p&&!n){var r=t.getElementById(e);return r&&r.parentNode?[r]:[]}}:function(e,n,r){if(typeof n.getElementById!==p&&!r){var i=n.getElementById(e);return i?i.id===e||typeof i.getAttributeNode!==p&&i.getAttributeNode("id").value===e?[i]:t:[]}},TAG:Q?function(e,t){if(typeof t.getElementsByTagName!==p)return t.getElementsByTagName(e)}:function(e,t){var n=t.getElementsByTagName(e);if(e==="*"){var r,i=[],s=0;for(;r=n[s];s++)r.nodeType===1&&i.push(r);return i}return n},NAME:et&&function(e,t){if(typeof t.getElementsByName!==p)return t.getElementsByName(name)},CLASS:Z&&function(e,t,n){if(typeof t.getElementsByClassName!==p&&!n)return t.getElementsByClassName(e)}},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace($,""),e[3]=(e[4]||e[5]||"").replace($,""),e[2]==="~="&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),e[1]==="nth"?(e[2]||nt.error(e[0]),e[3]=+(e[3]?e[4]+(e[5]||1):2*(e[2]==="even"||e[2]==="odd")),e[4]=+(e[6]+e[7]||e[2]==="odd")):e[2]&&nt.error(e[0]),e},PSEUDO:function(e){var t,n;if(J.CHILD.test(e[0]))return null;if(e[3])e[2]=e[3];else if(t=e[4])q.test(t)&&(n=ut(t,!0))&&(n=t.indexOf(")",t.length-n)-t.length)&&(t=t.slice(0,n),e[0]=e[0].slice(0,n)),e[2]=t;return e.slice(0,3)}},filter:{ID:r?function(e){return e=e.replace($,""),function(t){return t.getAttribute("id")===e}}:function(e){return e=e.replace($,""),function(t){var n=typeof t.getAttributeNode!==p&&t.getAttributeNode("id");return n&&n.value===e}},TAG:function(e){return e==="*"?function(){return!0}:(e=e.replace($,"").toLowerCase(),function(t){return t.nodeName&&t.nodeName.toLowerCase()===e})},CLASS:function(e){var t=k[d][e+" "];return t||(t=new RegExp("(^|"+O+")"+e+"("+O+"|$)"))&&k(e,function(e){return t.test(e.className||typeof e.getAttribute!==p&&e.getAttribute("class")||"")})},ATTR:function(e,t,n){return function(r,i){var s=nt.attr(r,e);return s==null?t==="!=":t?(s+="",t==="="?s===n:t==="!="?s!==n:t==="^="?n&&s.indexOf(n)===0:t==="*="?n&&s.indexOf(n)>-1:t==="$="?n&&s.substr(s.length-n.length)===n:t==="~="?(" "+s+" ").indexOf(n)>-1:t==="|="?s===n||s.substr(0,n.length+1)===n+"-":!1):!0}},CHILD:function(e,t,n,r){return e==="nth"?function(e){var t,i,s=e.parentNode;if(n===1&&r===0)return!0;if(s){i=0;for(t=s.firstChild;t;t=t.nextSibling)if(t.nodeType===1){i++;if(e===t)break}}return i-=r,i===n||i%n===0&&i/n>=0}:function(t){var n=t;switch(e){case"only":case"first":while(n=n.previousSibling)if(n.nodeType===1)return!1;if(e==="first")return!0;n=t;case"last":while(n=n.nextSibling)if(n.nodeType===1)return!1;return!0}}},PSEUDO:function(e,t){var n,r=i.pseudos[e]||i.setFilters[e.toLowerCase()]||nt.error("unsupported pseudo: "+e);return r[d]?r(t):r.length>1?(n=[e,e,"",t],i.setFilters.hasOwnProperty(e.toLowerCase())?N(function(e,n){var i,s=r(e,t),o=s.length;while(o--)i=T.call(e,s[o]),e[i]=!(n[i]=s[o])}):function(e){return r(e,0,n)}):r}},pseudos:{not:N(function(e){var t=[],n=[],r=a(e.replace(j,"$1"));return r[d]?N(function(e,t,n,i){var s,o=r(e,null,i,[]),u=e.length;while(u--)if(s=o[u])e[u]=!(t[u]=s)}):function(e,i,s){return t[0]=e,r(t,null,s,n),!n.pop()}}),has:N(function(e){return function(t){return nt(e,t).length>0}}),contains:N(function(e){return function(t){return(t.textContent||t.innerText||s(t)).indexOf(e)>-1}}),enabled:function(e){return e.disabled===!1},disabled:function(e){return e.disabled===!0},checked:function(e){var t=e.nodeName.toLowerCase();return t==="input"&&!!e.checked||t==="option"&&!!e.selected},selected:function(e){return e.parentNode&&e.parentNode.selectedIndex,e.selected===!0},parent:function(e){return!i.pseudos.empty(e)},empty:function(e){var t;e=e.firstChild;while(e){if(e.nodeName>"@"||(t=e.nodeType)===3||t===4)return!1;e=e.nextSibling}return!0},header:function(e){return X.test(e.nodeName)},text:function(e){var t,n;return e.nodeName.toLowerCase()==="input"&&(t=e.type)==="text"&&((n=e.getAttribute("type"))==null||n.toLowerCase()===t)},radio:rt("radio"),checkbox:rt("checkbox"),file:rt("file"),password:rt("password"),image:rt("image"),submit:it("submit"),reset:it("reset"),button:function(e){var t=e.nodeName.toLowerCase();return t==="input"&&e.type==="button"||t==="button"},input:function(e){return V.test(e.nodeName)},focus:function(e){var t=e.ownerDocument;return e===t.activeElement&&(!t.hasFocus||t.hasFocus())&&!!(e.type||e.href||~e.tabIndex)},active:function(e){return e===e.ownerDocument.activeElement},first:st(function(){return[0]}),last:st(function(e,t){return[t-1]}),eq:st(function(e,t,n){return[n<0?n+t:n]}),even:st(function(e,t){for(var n=0;n<t;n+=2)e.push(n);return e}),odd:st(function(e,t){for(var n=1;n<t;n+=2)e.push(n);return e}),lt:st(function(e,t,n){for(var r=n<0?n+t:n;--r>=0;)e.push(r);return e}),gt:st(function(e,t,n){for(var r=n<0?n+t:n;++r<t;)e.push(r);return e})}},f=y.compareDocumentPosition?function(e,t){return e===t?(l=!0,0):(!e.compareDocumentPosition||!t.compareDocumentPosition?e.compareDocumentPosition:e.compareDocumentPosition(t)&4)?-1:1}:function(e,t){if(e===t)return l=!0,0;if(e.sourceIndex&&t.sourceIndex)return e.sourceIndex-t.sourceIndex;var n,r,i=[],s=[],o=e.parentNode,u=t.parentNode,a=o;if(o===u)return ot(e,t);if(!o)return-1;if(!u)return 1;while(a)i.unshift(a),a=a.parentNode;a=u;while(a)s.unshift(a),a=a.parentNode;n=i.length,r=s.length;for(var f=0;f<n&&f<r;f++)if(i[f]!==s[f])return ot(i[f],s[f]);return f===n?ot(e,s[f],-1):ot(i[f],t,1)},[0,0].sort(f),h=!l,nt.uniqueSort=function(e){var t,n=[],r=1,i=0;l=h,e.sort(f);if(l){for(;t=e[r];r++)t===e[r-1]&&(i=n.push(r));while(i--)e.splice(n[i],1)}return e},nt.error=function(e){throw new Error("Syntax error, unrecognized expression: "+e)},a=nt.compile=function(e,t){var n,r=[],i=[],s=A[d][e+" "];if(!s){t||(t=ut(e)),n=t.length;while(n--)s=ht(t[n]),s[d]?r.push(s):i.push(s);s=A(e,pt(i,r))}return s},g.querySelectorAll&&function(){var e,t=vt,n=/'|\\/g,r=/\=[\x20\t\r\n\f]*([^'"\]]*)[\x20\t\r\n\f]*\]/g,i=[":focus"],s=[":active"],u=y.matchesSelector||y.mozMatchesSelector||y.webkitMatchesSelector||y.oMatchesSelector||y.msMatchesSelector;K(function(e){e.innerHTML="<select><option selected=''></option></select>",e.querySelectorAll("[selected]").length||i.push("\\["+O+"*(?:checked|disabled|ismap|multiple|readonly|selected|value)"),e.querySelectorAll(":checked").length||i.push(":checked")}),K(function(e){e.innerHTML="<p test=''></p>",e.querySelectorAll("[test^='']").length&&i.push("[*^$]="+O+"*(?:\"\"|'')"),e.innerHTML="<input type='hidden'/>",e.querySelectorAll(":enabled").length||i.push(":enabled",":disabled")}),i=new RegExp(i.join("|")),vt=function(e,r,s,o,u){if(!o&&!u&&!i.test(e)){var a,f,l=!0,c=d,h=r,p=r.nodeType===9&&e;if(r.nodeType===1&&r.nodeName.toLowerCase()!=="object"){a=ut(e),(l=r.getAttribute("id"))?c=l.replace(n,"\\$&"):r.setAttribute("id",c),c="[id='"+c+"'] ",f=a.length;while(f--)a[f]=c+a[f].join("");h=z.test(e)&&r.parentNode||r,p=a.join(",")}if(p)try{return S.apply(s,x.call(h.querySelectorAll(p),0)),s}catch(v){}finally{l||r.removeAttribute("id")}}return t(e,r,s,o,u)},u&&(K(function(t){e=u.call(t,"div");try{u.call(t,"[test!='']:sizzle"),s.push("!=",H)}catch(n){}}),s=new RegExp(s.join("|")),nt.matchesSelector=function(t,n){n=n.replace(r,"='$1']");if(!o(t)&&!s.test(n)&&!i.test(n))try{var a=u.call(t,n);if(a||e||t.document&&t.document.nodeType!==11)return a}catch(f){}return nt(n,null,null,[t]).length>0})}(),i.pseudos.nth=i.pseudos.eq,i.filters=mt.prototype=i.pseudos,i.setFilters=new mt,nt.attr=v.attr,v.find=nt,v.expr=nt.selectors,v.expr[":"]=v.expr.pseudos,v.unique=nt.uniqueSort,v.text=nt.getText,v.isXMLDoc=nt.isXML,v.contains=nt.contains}(e);var nt=/Until$/,rt=/^(?:parents|prev(?:Until|All))/,it=/^.[^:#\[\.,]*$/,st=v.expr.match.needsContext,ot={children:!0,contents:!0,next:!0,prev:!0};v.fn.extend({find:function(e){var t,n,r,i,s,o,u=this;if(typeof e!="string")return v(e).filter(function(){for(t=0,n=u.length;t<n;t++)if(v.contains(u[t],this))return!0});o=this.pushStack("","find",e);for(t=0,n=this.length;t<n;t++){r=o.length,v.find(e,this[t],o);if(t>0)for(i=r;i<o.length;i++)for(s=0;s<r;s++)if(o[s]===o[i]){o.splice(i--,1);break}}return o},has:function(e){var t,n=v(e,this),r=n.length;return this.filter(function(){for(t=0;t<r;t++)if(v.contains(this,n[t]))return!0})},not:function(e){return this.pushStack(ft(this,e,!1),"not",e)},filter:function(e){return this.pushStack(ft(this,e,!0),"filter",e)},is:function(e){return!!e&&(typeof e=="string"?st.test(e)?v(e,this.context).index(this[0])>=0:v.filter(e,this).length>0:this.filter(e).length>0)},closest:function(e,t){var n,r=0,i=this.length,s=[],o=st.test(e)||typeof e!="string"?v(e,t||this.context):0;for(;r<i;r++){n=this[r];while(n&&n.ownerDocument&&n!==t&&n.nodeType!==11){if(o?o.index(n)>-1:v.find.matchesSelector(n,e)){s.push(n);break}n=n.parentNode}}return s=s.length>1?v.unique(s):s,this.pushStack(s,"closest",e)},index:function(e){return e?typeof e=="string"?v.inArray(this[0],v(e)):v.inArray(e.jquery?e[0]:e,this):this[0]&&this[0].parentNode?this.prevAll().length:-1},add:function(e,t){var n=typeof e=="string"?v(e,t):v.makeArray(e&&e.nodeType?[e]:e),r=v.merge(this.get(),n);return this.pushStack(ut(n[0])||ut(r[0])?r:v.unique(r))},addBack:function(e){return this.add(e==null?this.prevObject:this.prevObject.filter(e))}}),v.fn.andSelf=v.fn.addBack,v.each({parent:function(e){var t=e.parentNode;return t&&t.nodeType!==11?t:null},parents:function(e){return v.dir(e,"parentNode")},parentsUntil:function(e,t,n){return v.dir(e,"parentNode",n)},next:function(e){return at(e,"nextSibling")},prev:function(e){return at(e,"previousSibling")},nextAll:function(e){return v.dir(e,"nextSibling")},prevAll:function(e){return v.dir(e,"previousSibling")},nextUntil:function(e,t,n){return v.dir(e,"nextSibling",n)},prevUntil:function(e,t,n){return v.dir(e,"previousSibling",n)},siblings:function(e){return v.sibling((e.parentNode||{}).firstChild,e)},children:function(e){return v.sibling(e.firstChild)},contents:function(e){return v.nodeName(e,"iframe")?e.contentDocument||e.contentWindow.document:v.merge([],e.childNodes)}},function(e,t){v.fn[e]=function(n,r){var i=v.map(this,t,n);return nt.test(e)||(r=n),r&&typeof r=="string"&&(i=v.filter(r,i)),i=this.length>1&&!ot[e]?v.unique(i):i,this.length>1&&rt.test(e)&&(i=i.reverse()),this.pushStack(i,e,l.call(arguments).join(","))}}),v.extend({filter:function(e,t,n){return n&&(e=":not("+e+")"),t.length===1?v.find.matchesSelector(t[0],e)?[t[0]]:[]:v.find.matches(e,t)},dir:function(e,n,r){var i=[],s=e[n];while(s&&s.nodeType!==9&&(r===t||s.nodeType!==1||!v(s).is(r)))s.nodeType===1&&i.push(s),s=s[n];return i},sibling:function(e,t){var n=[];for(;e;e=e.nextSibling)e.nodeType===1&&e!==t&&n.push(e);return n}});var ct="abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",ht=/ jQuery\d+="(?:null|\d+)"/g,pt=/^\s+/,dt=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,vt=/<([\w:]+)/,mt=/<tbody/i,gt=/<|&#?\w+;/,yt=/<(?:script|style|link)/i,bt=/<(?:script|object|embed|option|style)/i,wt=new RegExp("<(?:"+ct+")[\\s/>]","i"),Et=/^(?:checkbox|radio)$/,St=/checked\s*(?:[^=]|=\s*.checked.)/i,xt=/\/(java|ecma)script/i,Tt=/^\s*<!(?:\[CDATA\[|\-\-)|[\]\-]{2}>\s*$/g,Nt={option:[1,"<select multiple='multiple'>","</select>"],legend:[1,"<fieldset>","</fieldset>"],thead:[1,"<table>","</table>"],tr:[2,"<table><tbody>","</tbody></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],col:[2,"<table><tbody></tbody><colgroup>","</colgroup></table>"],area:[1,"<map>","</map>"],_default:[0,"",""]},Ct=lt(i),kt=Ct.appendChild(i.createElement("div"));Nt.optgroup=Nt.option,Nt.tbody=Nt.tfoot=Nt.colgroup=Nt.caption=Nt.thead,Nt.th=Nt.td,v.support.htmlSerialize||(Nt._default=[1,"X<div>","</div>"]),v.fn.extend({text:function(e){return v.access(this,function(e){return e===t?v.text(this):this.empty().append((this[0]&&this[0].ownerDocument||i).createTextNode(e))},null,e,arguments.length)},wrapAll:function(e){if(v.isFunction(e))return this.each(function(t){v(this).wrapAll(e.call(this,t))});if(this[0]){var t=v(e,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&t.insertBefore(this[0]),t.map(function(){var e=this;while(e.firstChild&&e.firstChild.nodeType===1)e=e.firstChild;return e}).append(this)}return this},wrapInner:function(e){return v.isFunction(e)?this.each(function(t){v(this).wrapInner(e.call(this,t))}):this.each(function(){var t=v(this),n=t.contents();n.length?n.wrapAll(e):t.append(e)})},wrap:function(e){var t=v.isFunction(e);return this.each(function(n){v(this).wrapAll(t?e.call(this,n):e)})},unwrap:function(){return this.parent().each(function(){v.nodeName(this,"body")||v(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,!0,function(e){(this.nodeType===1||this.nodeType===11)&&this.appendChild(e)})},prepend:function(){return this.domManip(arguments,!0,function(e){(this.nodeType===1||this.nodeType===11)&&this.insertBefore(e,this.firstChild)})},before:function(){if(!ut(this[0]))return this.domManip(arguments,!1,function(e){this.parentNode.insertBefore(e,this)});if(arguments.length){var e=v.clean(arguments);return this.pushStack(v.merge(e,this),"before",this.selector)}},after:function(){if(!ut(this[0]))return this.domManip(arguments,!1,function(e){this.parentNode.insertBefore(e,this.nextSibling)});if(arguments.length){var e=v.clean(arguments);return this.pushStack(v.merge(this,e),"after",this.selector)}},remove:function(e,t){var n,r=0;for(;(n=this[r])!=null;r++)if(!e||v.filter(e,[n]).length)!t&&n.nodeType===1&&(v.cleanData(n.getElementsByTagName("*")),v.cleanData([n])),n.parentNode&&n.parentNode.removeChild(n);return this},empty:function(){var e,t=0;for(;(e=this[t])!=null;t++){e.nodeType===1&&v.cleanData(e.getElementsByTagName("*"));while(e.firstChild)e.removeChild(e.firstChild)}return this},clone:function(e,t){return e=e==null?!1:e,t=t==null?e:t,this.map(function(){return v.clone(this,e,t)})},html:function(e){return v.access(this,function(e){var n=this[0]||{},r=0,i=this.length;if(e===t)return n.nodeType===1?n.innerHTML.replace(ht,""):t;if(typeof e=="string"&&!yt.test(e)&&(v.support.htmlSerialize||!wt.test(e))&&(v.support.leadingWhitespace||!pt.test(e))&&!Nt[(vt.exec(e)||["",""])[1].toLowerCase()]){e=e.replace(dt,"<$1></$2>");try{for(;r<i;r++)n=this[r]||{},n.nodeType===1&&(v.cleanData(n.getElementsByTagName("*")),n.innerHTML=e);n=0}catch(s){}}n&&this.empty().append(e)},null,e,arguments.length)},replaceWith:function(e){return ut(this[0])?this.length?this.pushStack(v(v.isFunction(e)?e():e),"replaceWith",e):this:v.isFunction(e)?this.each(function(t){var n=v(this),r=n.html();n.replaceWith(e.call(this,t,r))}):(typeof e!="string"&&(e=v(e).detach()),this.each(function(){var t=this.nextSibling,n=this.parentNode;v(this).remove(),t?v(t).before(e):v(n).append(e)}))},detach:function(e){return this.remove(e,!0)},domManip:function(e,n,r){e=[].concat.apply([],e);var i,s,o,u,a=0,f=e[0],l=[],c=this.length;if(!v.support.checkClone&&c>1&&typeof f=="string"&&St.test(f))return this.each(function(){v(this).domManip(e,n,r)});if(v.isFunction(f))return this.each(function(i){var s=v(this);e[0]=f.call(this,i,n?s.html():t),s.domManip(e,n,r)});if(this[0]){i=v.buildFragment(e,this,l),o=i.fragment,s=o.firstChild,o.childNodes.length===1&&(o=s);if(s){n=n&&v.nodeName(s,"tr");for(u=i.cacheable||c-1;a<c;a++)r.call(n&&v.nodeName(this[a],"table")?Lt(this[a],"tbody"):this[a],a===u?o:v.clone(o,!0,!0))}o=s=null,l.length&&v.each(l,function(e,t){t.src?v.ajax?v.ajax({url:t.src,type:"GET",dataType:"script",async:!1,global:!1,"throws":!0}):v.error("no ajax"):v.globalEval((t.text||t.textContent||t.innerHTML||"").replace(Tt,"")),t.parentNode&&t.parentNode.removeChild(t)})}return this}}),v.buildFragment=function(e,n,r){var s,o,u,a=e[0];return n=n||i,n=!n.nodeType&&n[0]||n,n=n.ownerDocument||n,e.length===1&&typeof a=="string"&&a.length<512&&n===i&&a.charAt(0)==="<"&&!bt.test(a)&&(v.support.checkClone||!St.test(a))&&(v.support.html5Clone||!wt.test(a))&&(o=!0,s=v.fragments[a],u=s!==t),s||(s=n.createDocumentFragment(),v.clean(e,n,s,r),o&&(v.fragments[a]=u&&s)),{fragment:s,cacheable:o}},v.fragments={},v.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(e,t){v.fn[e]=function(n){var r,i=0,s=[],o=v(n),u=o.length,a=this.length===1&&this[0].parentNode;if((a==null||a&&a.nodeType===11&&a.childNodes.length===1)&&u===1)return o[t](this[0]),this;for(;i<u;i++)r=(i>0?this.clone(!0):this).get(),v(o[i])[t](r),s=s.concat(r);return this.pushStack(s,e,o.selector)}}),v.extend({clone:function(e,t,n){var r,i,s,o;v.support.html5Clone||v.isXMLDoc(e)||!wt.test("<"+e.nodeName+">")?o=e.cloneNode(!0):(kt.innerHTML=e.outerHTML,kt.removeChild(o=kt.firstChild));if((!v.support.noCloneEvent||!v.support.noCloneChecked)&&(e.nodeType===1||e.nodeType===11)&&!v.isXMLDoc(e)){Ot(e,o),r=Mt(e),i=Mt(o);for(s=0;r[s];++s)i[s]&&Ot(r[s],i[s])}if(t){At(e,o);if(n){r=Mt(e),i=Mt(o);for(s=0;r[s];++s)At(r[s],i[s])}}return r=i=null,o},clean:function(e,t,n,r){var s,o,u,a,f,l,c,h,p,d,m,g,y=t===i&&Ct,b=[];if(!t||typeof t.createDocumentFragment=="undefined")t=i;for(s=0;(u=e[s])!=null;s++){typeof u=="number"&&(u+="");if(!u)continue;if(typeof u=="string")if(!gt.test(u))u=t.createTextNode(u);else{y=y||lt(t),c=t.createElement("div"),y.appendChild(c),u=u.replace(dt,"<$1></$2>"),a=(vt.exec(u)||["",""])[1].toLowerCase(),f=Nt[a]||Nt._default,l=f[0],c.innerHTML=f[1]+u+f[2];while(l--)c=c.lastChild;if(!v.support.tbody){h=mt.test(u),p=a==="table"&&!h?c.firstChild&&c.firstChild.childNodes:f[1]==="<table>"&&!h?c.childNodes:[];for(o=p.length-1;o>=0;--o)v.nodeName(p[o],"tbody")&&!p[o].childNodes.length&&p[o].parentNode.removeChild(p[o])}!v.support.leadingWhitespace&&pt.test(u)&&c.insertBefore(t.createTextNode(pt.exec(u)[0]),c.firstChild),u=c.childNodes,c.parentNode.removeChild(c)}u.nodeType?b.push(u):v.merge(b,u)}c&&(u=c=y=null);if(!v.support.appendChecked)for(s=0;(u=b[s])!=null;s++)v.nodeName(u,"input")?_t(u):typeof u.getElementsByTagName!="undefined"&&v.grep(u.getElementsByTagName("input"),_t);if(n){m=function(e){if(!e.type||xt.test(e.type))return r?r.push(e.parentNode?e.parentNode.removeChild(e):e):n.appendChild(e)};for(s=0;(u=b[s])!=null;s++)if(!v.nodeName(u,"script")||!m(u))n.appendChild(u),typeof u.getElementsByTagName!="undefined"&&(g=v.grep(v.merge([],u.getElementsByTagName("script")),m),b.splice.apply(b,[s+1,0].concat(g)),s+=g.length)}return b},cleanData:function(e,t){var n,r,i,s,o=0,u=v.expando,a=v.cache,f=v.support.deleteExpando,l=v.event.special;for(;(i=e[o])!=null;o++)if(t||v.acceptData(i)){r=i[u],n=r&&a[r];if(n){if(n.events)for(s in n.events)l[s]?v.event.remove(i,s):v.removeEvent(i,s,n.handle);a[r]&&(delete a[r],f?delete i[u]:i.removeAttribute?i.removeAttribute(u):i[u]=null,v.deletedIds.push(r))}}}}),function(){var e,t;v.uaMatch=function(e){e=e.toLowerCase();var t=/(chrome)[ \/]([\w.]+)/.exec(e)||/(webkit)[ \/]([\w.]+)/.exec(e)||/(opera)(?:.*version|)[ \/]([\w.]+)/.exec(e)||/(msie) ([\w.]+)/.exec(e)||e.indexOf("compatible")<0&&/(mozilla)(?:.*? rv:([\w.]+)|)/.exec(e)||[];return{browser:t[1]||"",version:t[2]||"0"}},e=v.uaMatch(o.userAgent),t={},e.browser&&(t[e.browser]=!0,t.version=e.version),t.chrome?t.webkit=!0:t.webkit&&(t.safari=!0),v.browser=t,v.sub=function(){function e(t,n){return new e.fn.init(t,n)}v.extend(!0,e,this),e.superclass=this,e.fn=e.prototype=this(),e.fn.constructor=e,e.sub=this.sub,e.fn.init=function(r,i){return i&&i instanceof v&&!(i instanceof e)&&(i=e(i)),v.fn.init.call(this,r,i,t)},e.fn.init.prototype=e.fn;var t=e(i);return e}}();var Dt,Pt,Ht,Bt=/alpha\([^)]*\)/i,jt=/opacity=([^)]*)/,Ft=/^(top|right|bottom|left)$/,It=/^(none|table(?!-c[ea]).+)/,qt=/^margin/,Rt=new RegExp("^("+m+")(.*)$","i"),Ut=new RegExp("^("+m+")(?!px)[a-z%]+$","i"),zt=new RegExp("^([-+])=("+m+")","i"),Wt={BODY:"block"},Xt={position:"absolute",visibility:"hidden",display:"block"},Vt={letterSpacing:0,fontWeight:400},$t=["Top","Right","Bottom","Left"],Jt=["Webkit","O","Moz","ms"],Kt=v.fn.toggle;v.fn.extend({css:function(e,n){return v.access(this,function(e,n,r){return r!==t?v.style(e,n,r):v.css(e,n)},e,n,arguments.length>1)},show:function(){return Yt(this,!0)},hide:function(){return Yt(this)},toggle:function(e,t){var n=typeof e=="boolean";return v.isFunction(e)&&v.isFunction(t)?Kt.apply(this,arguments):this.each(function(){(n?e:Gt(this))?v(this).show():v(this).hide()})}}),v.extend({cssHooks:{opacity:{get:function(e,t){if(t){var n=Dt(e,"opacity");return n===""?"1":n}}}},cssNumber:{fillOpacity:!0,fontWeight:!0,lineHeight:!0,opacity:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":v.support.cssFloat?"cssFloat":"styleFloat"},style:function(e,n,r,i){if(!e||e.nodeType===3||e.nodeType===8||!e.style)return;var s,o,u,a=v.camelCase(n),f=e.style;n=v.cssProps[a]||(v.cssProps[a]=Qt(f,a)),u=v.cssHooks[n]||v.cssHooks[a];if(r===t)return u&&"get"in u&&(s=u.get(e,!1,i))!==t?s:f[n];o=typeof r,o==="string"&&(s=zt.exec(r))&&(r=(s[1]+1)*s[2]+parseFloat(v.css(e,n)),o="number");if(r==null||o==="number"&&isNaN(r))return;o==="number"&&!v.cssNumber[a]&&(r+="px");if(!u||!("set"in u)||(r=u.set(e,r,i))!==t)try{f[n]=r}catch(l){}},css:function(e,n,r,i){var s,o,u,a=v.camelCase(n);return n=v.cssProps[a]||(v.cssProps[a]=Qt(e.style,a)),u=v.cssHooks[n]||v.cssHooks[a],u&&"get"in u&&(s=u.get(e,!0,i)),s===t&&(s=Dt(e,n)),s==="normal"&&n in Vt&&(s=Vt[n]),r||i!==t?(o=parseFloat(s),r||v.isNumeric(o)?o||0:s):s},swap:function(e,t,n){var r,i,s={};for(i in t)s[i]=e.style[i],e.style[i]=t[i];r=n.call(e);for(i in t)e.style[i]=s[i];return r}}),e.getComputedStyle?Dt=function(t,n){var r,i,s,o,u=e.getComputedStyle(t,null),a=t.style;return u&&(r=u.getPropertyValue(n)||u[n],r===""&&!v.contains(t.ownerDocument,t)&&(r=v.style(t,n)),Ut.test(r)&&qt.test(n)&&(i=a.width,s=a.minWidth,o=a.maxWidth,a.minWidth=a.maxWidth=a.width=r,r=u.width,a.width=i,a.minWidth=s,a.maxWidth=o)),r}:i.documentElement.currentStyle&&(Dt=function(e,t){var n,r,i=e.currentStyle&&e.currentStyle[t],s=e.style;return i==null&&s&&s[t]&&(i=s[t]),Ut.test(i)&&!Ft.test(t)&&(n=s.left,r=e.runtimeStyle&&e.runtimeStyle.left,r&&(e.runtimeStyle.left=e.currentStyle.left),s.left=t==="fontSize"?"1em":i,i=s.pixelLeft+"px",s.left=n,r&&(e.runtimeStyle.left=r)),i===""?"auto":i}),v.each(["height","width"],function(e,t){v.cssHooks[t]={get:function(e,n,r){if(n)return e.offsetWidth===0&&It.test(Dt(e,"display"))?v.swap(e,Xt,function(){return tn(e,t,r)}):tn(e,t,r)},set:function(e,n,r){return Zt(e,n,r?en(e,t,r,v.support.boxSizing&&v.css(e,"boxSizing")==="border-box"):0)}}}),v.support.opacity||(v.cssHooks.opacity={get:function(e,t){return jt.test((t&&e.currentStyle?e.currentStyle.filter:e.style.filter)||"")?.01*parseFloat(RegExp.$1)+"":t?"1":""},set:function(e,t){var n=e.style,r=e.currentStyle,i=v.isNumeric(t)?"alpha(opacity="+t*100+")":"",s=r&&r.filter||n.filter||"";n.zoom=1;if(t>=1&&v.trim(s.replace(Bt,""))===""&&n.removeAttribute){n.removeAttribute("filter");if(r&&!r.filter)return}n.filter=Bt.test(s)?s.replace(Bt,i):s+" "+i}}),v(function(){v.support.reliableMarginRight||(v.cssHooks.marginRight={get:function(e,t){return v.swap(e,{display:"inline-block"},function(){if(t)return Dt(e,"marginRight")})}}),!v.support.pixelPosition&&v.fn.position&&v.each(["top","left"],function(e,t){v.cssHooks[t]={get:function(e,n){if(n){var r=Dt(e,t);return Ut.test(r)?v(e).position()[t]+"px":r}}}})}),v.expr&&v.expr.filters&&(v.expr.filters.hidden=function(e){return e.offsetWidth===0&&e.offsetHeight===0||!v.support.reliableHiddenOffsets&&(e.style&&e.style.display||Dt(e,"display"))==="none"},v.expr.filters.visible=function(e){return!v.expr.filters.hidden(e)}),v.each({margin:"",padding:"",border:"Width"},function(e,t){v.cssHooks[e+t]={expand:function(n){var r,i=typeof n=="string"?n.split(" "):[n],s={};for(r=0;r<4;r++)s[e+$t[r]+t]=i[r]||i[r-2]||i[0];return s}},qt.test(e)||(v.cssHooks[e+t].set=Zt)});var rn=/%20/g,sn=/\[\]$/,on=/\r?\n/g,un=/^(?:color|date|datetime|datetime-local|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,an=/^(?:select|textarea)/i;v.fn.extend({serialize:function(){return v.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?v.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||an.test(this.nodeName)||un.test(this.type))}).map(function(e,t){var n=v(this).val();return n==null?null:v.isArray(n)?v.map(n,function(e,n){return{name:t.name,value:e.replace(on,"\r\n")}}):{name:t.name,value:n.replace(on,"\r\n")}}).get()}}),v.param=function(e,n){var r,i=[],s=function(e,t){t=v.isFunction(t)?t():t==null?"":t,i[i.length]=encodeURIComponent(e)+"="+encodeURIComponent(t)};n===t&&(n=v.ajaxSettings&&v.ajaxSettings.traditional);if(v.isArray(e)||e.jquery&&!v.isPlainObject(e))v.each(e,function(){s(this.name,this.value)});else for(r in e)fn(r,e[r],n,s);return i.join("&").replace(rn,"+")};var ln,cn,hn=/#.*$/,pn=/^(.*?):[ \t]*([^\r\n]*)\r?$/mg,dn=/^(?:about|app|app\-storage|.+\-extension|file|res|widget):$/,vn=/^(?:GET|HEAD)$/,mn=/^\/\//,gn=/\?/,yn=/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,bn=/([?&])_=[^&]*/,wn=/^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+)|)|)/,En=v.fn.load,Sn={},xn={},Tn=["*/"]+["*"];try{cn=s.href}catch(Nn){cn=i.createElement("a"),cn.href="",cn=cn.href}ln=wn.exec(cn.toLowerCase())||[],v.fn.load=function(e,n,r){if(typeof e!="string"&&En)return En.apply(this,arguments);if(!this.length)return this;var i,s,o,u=this,a=e.indexOf(" ");return a>=0&&(i=e.slice(a,e.length),e=e.slice(0,a)),v.isFunction(n)?(r=n,n=t):n&&typeof n=="object"&&(s="POST"),v.ajax({url:e,type:s,dataType:"html",data:n,complete:function(e,t){r&&u.each(r,o||[e.responseText,t,e])}}).done(function(e){o=arguments,u.html(i?v("<div>").append(e.replace(yn,"")).find(i):e)}),this},v.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),function(e,t){v.fn[t]=function(e){return this.on(t,e)}}),v.each(["get","post"],function(e,n){v[n]=function(e,r,i,s){return v.isFunction(r)&&(s=s||i,i=r,r=t),v.ajax({type:n,url:e,data:r,success:i,dataType:s})}}),v.extend({getScript:function(e,n){return v.get(e,t,n,"script")},getJSON:function(e,t,n){return v.get(e,t,n,"json")},ajaxSetup:function(e,t){return t?Ln(e,v.ajaxSettings):(t=e,e=v.ajaxSettings),Ln(e,t),e},ajaxSettings:{url:cn,isLocal:dn.test(ln[1]),global:!0,type:"GET",contentType:"application/x-www-form-urlencoded; charset=UTF-8",processData:!0,async:!0,accepts:{xml:"application/xml, text/xml",html:"text/html",text:"text/plain",json:"application/json, text/javascript","*":Tn},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText"},converters:{"* text":e.String,"text html":!0,"text json":v.parseJSON,"text xml":v.parseXML},flatOptions:{context:!0,url:!0}},ajaxPrefilter:Cn(Sn),ajaxTransport:Cn(xn),ajax:function(e,n){function T(e,n,s,a){var l,y,b,w,S,T=n;if(E===2)return;E=2,u&&clearTimeout(u),o=t,i=a||"",x.readyState=e>0?4:0,s&&(w=An(c,x,s));if(e>=200&&e<300||e===304)c.ifModified&&(S=x.getResponseHeader("Last-Modified"),S&&(v.lastModified[r]=S),S=x.getResponseHeader("Etag"),S&&(v.etag[r]=S)),e===304?(T="notmodified",l=!0):(l=On(c,w),T=l.state,y=l.data,b=l.error,l=!b);else{b=T;if(!T||e)T="error",e<0&&(e=0)}x.status=e,x.statusText=(n||T)+"",l?d.resolveWith(h,[y,T,x]):d.rejectWith(h,[x,T,b]),x.statusCode(g),g=t,f&&p.trigger("ajax"+(l?"Success":"Error"),[x,c,l?y:b]),m.fireWith(h,[x,T]),f&&(p.trigger("ajaxComplete",[x,c]),--v.active||v.event.trigger("ajaxStop"))}typeof e=="object"&&(n=e,e=t),n=n||{};var r,i,s,o,u,a,f,l,c=v.ajaxSetup({},n),h=c.context||c,p=h!==c&&(h.nodeType||h instanceof v)?v(h):v.event,d=v.Deferred(),m=v.Callbacks("once memory"),g=c.statusCode||{},b={},w={},E=0,S="canceled",x={readyState:0,setRequestHeader:function(e,t){if(!E){var n=e.toLowerCase();e=w[n]=w[n]||e,b[e]=t}return this},getAllResponseHeaders:function(){return E===2?i:null},getResponseHeader:function(e){var n;if(E===2){if(!s){s={};while(n=pn.exec(i))s[n[1].toLowerCase()]=n[2]}n=s[e.toLowerCase()]}return n===t?null:n},overrideMimeType:function(e){return E||(c.mimeType=e),this},abort:function(e){return e=e||S,o&&o.abort(e),T(0,e),this}};d.promise(x),x.success=x.done,x.error=x.fail,x.complete=m.add,x.statusCode=function(e){if(e){var t;if(E<2)for(t in e)g[t]=[g[t],e[t]];else t=e[x.status],x.always(t)}return this},c.url=((e||c.url)+"").replace(hn,"").replace(mn,ln[1]+"//"),c.dataTypes=v.trim(c.dataType||"*").toLowerCase().split(y),c.crossDomain==null&&(a=wn.exec(c.url.toLowerCase()),c.crossDomain=!(!a||a[1]===ln[1]&&a[2]===ln[2]&&(a[3]||(a[1]==="http:"?80:443))==(ln[3]||(ln[1]==="http:"?80:443)))),c.data&&c.processData&&typeof c.data!="string"&&(c.data=v.param(c.data,c.traditional)),kn(Sn,c,n,x);if(E===2)return x;f=c.global,c.type=c.type.toUpperCase(),c.hasContent=!vn.test(c.type),f&&v.active++===0&&v.event.trigger("ajaxStart");if(!c.hasContent){c.data&&(c.url+=(gn.test(c.url)?"&":"?")+c.data,delete c.data),r=c.url;if(c.cache===!1){var N=v.now(),C=c.url.replace(bn,"$1_="+N);c.url=C+(C===c.url?(gn.test(c.url)?"&":"?")+"_="+N:"")}}(c.data&&c.hasContent&&c.contentType!==!1||n.contentType)&&x.setRequestHeader("Content-Type",c.contentType),c.ifModified&&(r=r||c.url,v.lastModified[r]&&x.setRequestHeader("If-Modified-Since",v.lastModified[r]),v.etag[r]&&x.setRequestHeader("If-None-Match",v.etag[r])),x.setRequestHeader("Accept",c.dataTypes[0]&&c.accepts[c.dataTypes[0]]?c.accepts[c.dataTypes[0]]+(c.dataTypes[0]!=="*"?", "+Tn+"; q=0.01":""):c.accepts["*"]);for(l in c.headers)x.setRequestHeader(l,c.headers[l]);if(!c.beforeSend||c.beforeSend.call(h,x,c)!==!1&&E!==2){S="abort";for(l in{success:1,error:1,complete:1})x[l](c[l]);o=kn(xn,c,n,x);if(!o)T(-1,"No Transport");else{x.readyState=1,f&&p.trigger("ajaxSend",[x,c]),c.async&&c.timeout>0&&(u=setTimeout(function(){x.abort("timeout")},c.timeout));try{E=1,o.send(b,T)}catch(k){if(!(E<2))throw k;T(-1,k)}}return x}return x.abort()},active:0,lastModified:{},etag:{}});var Mn=[],_n=/\?/,Dn=/(=)\?(?=&|$)|\?\?/,Pn=v.now();v.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=Mn.pop()||v.expando+"_"+Pn++;return this[e]=!0,e}}),v.ajaxPrefilter("json jsonp",function(n,r,i){var s,o,u,a=n.data,f=n.url,l=n.jsonp!==!1,c=l&&Dn.test(f),h=l&&!c&&typeof a=="string"&&!(n.contentType||"").indexOf("application/x-www-form-urlencoded")&&Dn.test(a);if(n.dataTypes[0]==="jsonp"||c||h)return s=n.jsonpCallback=v.isFunction(n.jsonpCallback)?n.jsonpCallback():n.jsonpCallback,o=e[s],c?n.url=f.replace(Dn,"$1"+s):h?n.data=a.replace(Dn,"$1"+s):l&&(n.url+=(_n.test(f)?"&":"?")+n.jsonp+"="+s),n.converters["script json"]=function(){return u||v.error(s+" was not called"),u[0]},n.dataTypes[0]="json",e[s]=function(){u=arguments},i.always(function(){e[s]=o,n[s]&&(n.jsonpCallback=r.jsonpCallback,Mn.push(s)),u&&v.isFunction(o)&&o(u[0]),u=o=t}),"script"}),v.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/javascript|ecmascript/},converters:{"text script":function(e){return v.globalEval(e),e}}}),v.ajaxPrefilter("script",function(e){e.cache===t&&(e.cache=!1),e.crossDomain&&(e.type="GET",e.global=!1)}),v.ajaxTransport("script",function(e){if(e.crossDomain){var n,r=i.head||i.getElementsByTagName("head")[0]||i.documentElement;return{send:function(s,o){n=i.createElement("script"),n.async="async",e.scriptCharset&&(n.charset=e.scriptCharset),n.src=e.url,n.onload=n.onreadystatechange=function(e,i){if(i||!n.readyState||/loaded|complete/.test(n.readyState))n.onload=n.onreadystatechange=null,r&&n.parentNode&&r.removeChild(n),n=t,i||o(200,"success")},r.insertBefore(n,r.firstChild)},abort:function(){n&&n.onload(0,1)}}}});var Hn,Bn=e.ActiveXObject?function(){for(var e in Hn)Hn[e](0,1)}:!1,jn=0;v.ajaxSettings.xhr=e.ActiveXObject?function(){return!this.isLocal&&Fn()||In()}:Fn,function(e){v.extend(v.support,{ajax:!!e,cors:!!e&&"withCredentials"in e})}(v.ajaxSettings.xhr()),v.support.ajax&&v.ajaxTransport(function(n){if(!n.crossDomain||v.support.cors){var r;return{send:function(i,s){var o,u,a=n.xhr();n.username?a.open(n.type,n.url,n.async,n.username,n.password):a.open(n.type,n.url,n.async);if(n.xhrFields)for(u in n.xhrFields)a[u]=n.xhrFields[u];n.mimeType&&a.overrideMimeType&&a.overrideMimeType(n.mimeType),!n.crossDomain&&!i["X-Requested-With"]&&(i["X-Requested-With"]="XMLHttpRequest");try{for(u in i)a.setRequestHeader(u,i[u])}catch(f){}a.send(n.hasContent&&n.data||null),r=function(e,i){var u,f,l,c,h;try{if(r&&(i||a.readyState===4)){r=t,o&&(a.onreadystatechange=v.noop,Bn&&delete Hn[o]);if(i)a.readyState!==4&&a.abort();else{u=a.status,l=a.getAllResponseHeaders(),c={},h=a.responseXML,h&&h.documentElement&&(c.xml=h);try{c.text=a.responseText}catch(p){}try{f=a.statusText}catch(p){f=""}!u&&n.isLocal&&!n.crossDomain?u=c.text?200:404:u===1223&&(u=204)}}}catch(d){i||s(-1,d)}c&&s(u,f,c,l)},n.async?a.readyState===4?setTimeout(r,0):(o=++jn,Bn&&(Hn||(Hn={},v(e).unload(Bn)),Hn[o]=r),a.onreadystatechange=r):r()},abort:function(){r&&r(0,1)}}}});var qn,Rn,Un=/^(?:toggle|show|hide)$/,zn=new RegExp("^(?:([-+])=|)("+m+")([a-z%]*)$","i"),Wn=/queueHooks$/,Xn=[Gn],Vn={"*":[function(e,t){var n,r,i=this.createTween(e,t),s=zn.exec(t),o=i.cur(),u=+o||0,a=1,f=20;if(s){n=+s[2],r=s[3]||(v.cssNumber[e]?"":"px");if(r!=="px"&&u){u=v.css(i.elem,e,!0)||n||1;do a=a||".5",u/=a,v.style(i.elem,e,u+r);while(a!==(a=i.cur()/o)&&a!==1&&--f)}i.unit=r,i.start=u,i.end=s[1]?u+(s[1]+1)*n:n}return i}]};v.Animation=v.extend(Kn,{tweener:function(e,t){v.isFunction(e)?(t=e,e=["*"]):e=e.split(" ");var n,r=0,i=e.length;for(;r<i;r++)n=e[r],Vn[n]=Vn[n]||[],Vn[n].unshift(t)},prefilter:function(e,t){t?Xn.unshift(e):Xn.push(e)}}),v.Tween=Yn,Yn.prototype={constructor:Yn,init:function(e,t,n,r,i,s){this.elem=e,this.prop=n,this.easing=i||"swing",this.options=t,this.start=this.now=this.cur(),this.end=r,this.unit=s||(v.cssNumber[n]?"":"px")},cur:function(){var e=Yn.propHooks[this.prop];return e&&e.get?e.get(this):Yn.propHooks._default.get(this)},run:function(e){var t,n=Yn.propHooks[this.prop];return this.options.duration?this.pos=t=v.easing[this.easing](e,this.options.duration*e,0,1,this.options.duration):this.pos=t=e,this.now=(this.end-this.start)*t+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),n&&n.set?n.set(this):Yn.propHooks._default.set(this),this}},Yn.prototype.init.prototype=Yn.prototype,Yn.propHooks={_default:{get:function(e){var t;return e.elem[e.prop]==null||!!e.elem.style&&e.elem.style[e.prop]!=null?(t=v.css(e.elem,e.prop,!1,""),!t||t==="auto"?0:t):e.elem[e.prop]},set:function(e){v.fx.step[e.prop]?v.fx.step[e.prop](e):e.elem.style&&(e.elem.style[v.cssProps[e.prop]]!=null||v.cssHooks[e.prop])?v.style(e.elem,e.prop,e.now+e.unit):e.elem[e.prop]=e.now}}},Yn.propHooks.scrollTop=Yn.propHooks.scrollLeft={set:function(e){e.elem.nodeType&&e.elem.parentNode&&(e.elem[e.prop]=e.now)}},v.each(["toggle","show","hide"],function(e,t){var n=v.fn[t];v.fn[t]=function(r,i,s){return r==null||typeof r=="boolean"||!e&&v.isFunction(r)&&v.isFunction(i)?n.apply(this,arguments):this.animate(Zn(t,!0),r,i,s)}}),v.fn.extend({fadeTo:function(e,t,n,r){return this.filter(Gt).css("opacity",0).show().end().animate({opacity:t},e,n,r)},animate:function(e,t,n,r){var i=v.isEmptyObject(e),s=v.speed(t,n,r),o=function(){var t=Kn(this,v.extend({},e),s);i&&t.stop(!0)};return i||s.queue===!1?this.each(o):this.queue(s.queue,o)},stop:function(e,n,r){var i=function(e){var t=e.stop;delete e.stop,t(r)};return typeof e!="string"&&(r=n,n=e,e=t),n&&e!==!1&&this.queue(e||"fx",[]),this.each(function(){var t=!0,n=e!=null&&e+"queueHooks",s=v.timers,o=v._data(this);if(n)o[n]&&o[n].stop&&i(o[n]);else for(n in o)o[n]&&o[n].stop&&Wn.test(n)&&i(o[n]);for(n=s.length;n--;)s[n].elem===this&&(e==null||s[n].queue===e)&&(s[n].anim.stop(r),t=!1,s.splice(n,1));(t||!r)&&v.dequeue(this,e)})}}),v.each({slideDown:Zn("show"),slideUp:Zn("hide"),slideToggle:Zn("toggle"),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"},fadeToggle:{opacity:"toggle"}},function(e,t){v.fn[e]=function(e,n,r){return this.animate(t,e,n,r)}}),v.speed=function(e,t,n){var r=e&&typeof e=="object"?v.extend({},e):{complete:n||!n&&t||v.isFunction(e)&&e,duration:e,easing:n&&t||t&&!v.isFunction(t)&&t};r.duration=v.fx.off?0:typeof r.duration=="number"?r.duration:r.duration in v.fx.speeds?v.fx.speeds[r.duration]:v.fx.speeds._default;if(r.queue==null||r.queue===!0)r.queue="fx";return r.old=r.complete,r.complete=function(){v.isFunction(r.old)&&r.old.call(this),r.queue&&v.dequeue(this,r.queue)},r},v.easing={linear:function(e){return e},swing:function(e){return.5-Math.cos(e*Math.PI)/2}},v.timers=[],v.fx=Yn.prototype.init,v.fx.tick=function(){var e,n=v.timers,r=0;qn=v.now();for(;r<n.length;r++)e=n[r],!e()&&n[r]===e&&n.splice(r--,1);n.length||v.fx.stop(),qn=t},v.fx.timer=function(e){e()&&v.timers.push(e)&&!Rn&&(Rn=setInterval(v.fx.tick,v.fx.interval))},v.fx.interval=13,v.fx.stop=function(){clearInterval(Rn),Rn=null},v.fx.speeds={slow:600,fast:200,_default:400},v.fx.step={},v.expr&&v.expr.filters&&(v.expr.filters.animated=function(e){return v.grep(v.timers,function(t){return e===t.elem}).length});var er=/^(?:body|html)$/i;v.fn.offset=function(e){if(arguments.length)return e===t?this:this.each(function(t){v.offset.setOffset(this,e,t)});var n,r,i,s,o,u,a,f={top:0,left:0},l=this[0],c=l&&l.ownerDocument;if(!c)return;return(r=c.body)===l?v.offset.bodyOffset(l):(n=c.documentElement,v.contains(n,l)?(typeof l.getBoundingClientRect!="undefined"&&(f=l.getBoundingClientRect()),i=tr(c),s=n.clientTop||r.clientTop||0,o=n.clientLeft||r.clientLeft||0,u=i.pageYOffset||n.scrollTop,a=i.pageXOffset||n.scrollLeft,{top:f.top+u-s,left:f.left+a-o}):f)},v.offset={bodyOffset:function(e){var t=e.offsetTop,n=e.offsetLeft;return v.support.doesNotIncludeMarginInBodyOffset&&(t+=parseFloat(v.css(e,"marginTop"))||0,n+=parseFloat(v.css(e,"marginLeft"))||0),{top:t,left:n}},setOffset:function(e,t,n){var r=v.css(e,"position");r==="static"&&(e.style.position="relative");var i=v(e),s=i.offset(),o=v.css(e,"top"),u=v.css(e,"left"),a=(r==="absolute"||r==="fixed")&&v.inArray("auto",[o,u])>-1,f={},l={},c,h;a?(l=i.position(),c=l.top,h=l.left):(c=parseFloat(o)||0,h=parseFloat(u)||0),v.isFunction(t)&&(t=t.call(e,n,s)),t.top!=null&&(f.top=t.top-s.top+c),t.left!=null&&(f.left=t.left-s.left+h),"using"in t?t.using.call(e,f):i.css(f)}},v.fn.extend({position:function(){if(!this[0])return;var e=this[0],t=this.offsetParent(),n=this.offset(),r=er.test(t[0].nodeName)?{top:0,left:0}:t.offset();return n.top-=parseFloat(v.css(e,"marginTop"))||0,n.left-=parseFloat(v.css(e,"marginLeft"))||0,r.top+=parseFloat(v.css(t[0],"borderTopWidth"))||0,r.left+=parseFloat(v.css(t[0],"borderLeftWidth"))||0,{top:n.top-r.top,left:n.left-r.left}},offsetParent:function(){return this.map(function(){var e=this.offsetParent||i.body;while(e&&!er.test(e.nodeName)&&v.css(e,"position")==="static")e=e.offsetParent;return e||i.body})}}),v.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(e,n){var r=/Y/.test(n);v.fn[e]=function(i){return v.access(this,function(e,i,s){var o=tr(e);if(s===t)return o?n in o?o[n]:o.document.documentElement[i]:e[i];o?o.scrollTo(r?v(o).scrollLeft():s,r?s:v(o).scrollTop()):e[i]=s},e,i,arguments.length,null)}}),v.each({Height:"height",Width:"width"},function(e,n){v.each({padding:"inner"+e,content:n,"":"outer"+e},function(r,i){v.fn[i]=function(i,s){var o=arguments.length&&(r||typeof i!="boolean"),u=r||(i===!0||s===!0?"margin":"border");return v.access(this,function(n,r,i){var s;return v.isWindow(n)?n.document.documentElement["client"+e]:n.nodeType===9?(s=n.documentElement,Math.max(n.body["scroll"+e],s["scroll"+e],n.body["offset"+e],s["offset"+e],s["client"+e])):i===t?v.css(n,r,i,u):v.style(n,r,i,u)},n,o?i:t,o,null)}})}),e.jQuery=e.$=v,typeof define=="function"&&define.amd&&define.amd.jQuery&&define("jquery",[],function(){return v})})(window); \ No newline at end of file
diff --git a/js/jquery/jquery-ui-1.9.2.custom.min.js b/js/jquery/jquery-ui-1.9.2.custom.min.js
new file mode 100644
index 0000000000..2dc4c48789
--- /dev/null
+++ b/js/jquery/jquery-ui-1.9.2.custom.min.js
@@ -0,0 +1,6 @@
+/*! jQuery UI - v1.9.2 - 2013-06-24
+* http://jqueryui.com
+* Includes: jquery.ui.core.js, jquery.ui.widget.js, jquery.ui.mouse.js, jquery.ui.position.js, jquery.ui.draggable.js, jquery.ui.droppable.js, jquery.ui.resizable.js, jquery.ui.selectable.js, jquery.ui.sortable.js, jquery.ui.accordion.js, jquery.ui.autocomplete.js, jquery.ui.button.js, jquery.ui.datepicker.js, jquery.ui.dialog.js, jquery.ui.menu.js, jquery.ui.progressbar.js, jquery.ui.slider.js, jquery.ui.spinner.js, jquery.ui.tabs.js, jquery.ui.tooltip.js, jquery.ui.effect.js, jquery.ui.effect-blind.js, jquery.ui.effect-bounce.js, jquery.ui.effect-clip.js, jquery.ui.effect-drop.js, jquery.ui.effect-explode.js, jquery.ui.effect-fade.js, jquery.ui.effect-fold.js, jquery.ui.effect-highlight.js, jquery.ui.effect-pulsate.js, jquery.ui.effect-scale.js, jquery.ui.effect-shake.js, jquery.ui.effect-slide.js, jquery.ui.effect-transfer.js
+* Copyright 2013 jQuery Foundation and other contributors Licensed MIT */
+
+(function(e,t){function i(t,n){var r,i,o,u=t.nodeName.toLowerCase();return"area"===u?(r=t.parentNode,i=r.name,!t.href||!i||r.nodeName.toLowerCase()!=="map"?!1:(o=e("img[usemap=#"+i+"]")[0],!!o&&s(o))):(/input|select|textarea|button|object/.test(u)?!t.disabled:"a"===u?t.href||n:n)&&s(t)}function s(t){return e.expr.filters.visible(t)&&!e(t).parents().andSelf().filter(function(){return e.css(this,"visibility")==="hidden"}).length}var n=0,r=/^ui-id-\d+$/;e.ui=e.ui||{};if(e.ui.version)return;e.extend(e.ui,{version:"1.9.2",keyCode:{BACKSPACE:8,COMMA:188,DELETE:46,DOWN:40,END:35,ENTER:13,ESCAPE:27,HOME:36,LEFT:37,NUMPAD_ADD:107,NUMPAD_DECIMAL:110,NUMPAD_DIVIDE:111,NUMPAD_ENTER:108,NUMPAD_MULTIPLY:106,NUMPAD_SUBTRACT:109,PAGE_DOWN:34,PAGE_UP:33,PERIOD:190,RIGHT:39,SPACE:32,TAB:9,UP:38}}),e.fn.extend({_focus:e.fn.focus,focus:function(t,n){return typeof t=="number"?this.each(function(){var r=this;setTimeout(function(){e(r).focus(),n&&n.call(r)},t)}):this._focus.apply(this,arguments)},scrollParent:function(){var t;return e.ui.ie&&/(static|relative)/.test(this.css("position"))||/absolute/.test(this.css("position"))?t=this.parents().filter(function(){return/(relative|absolute|fixed)/.test(e.css(this,"position"))&&/(auto|scroll)/.test(e.css(this,"overflow")+e.css(this,"overflow-y")+e.css(this,"overflow-x"))}).eq(0):t=this.parents().filter(function(){return/(auto|scroll)/.test(e.css(this,"overflow")+e.css(this,"overflow-y")+e.css(this,"overflow-x"))}).eq(0),/fixed/.test(this.css("position"))||!t.length?e(document):t},zIndex:function(n){if(n!==t)return this.css("zIndex",n);if(this.length){var r=e(this[0]),i,s;while(r.length&&r[0]!==document){i=r.css("position");if(i==="absolute"||i==="relative"||i==="fixed"){s=parseInt(r.css("zIndex"),10);if(!isNaN(s)&&s!==0)return s}r=r.parent()}}return 0},uniqueId:function(){return this.each(function(){this.id||(this.id="ui-id-"+ ++n)})},removeUniqueId:function(){return this.each(function(){r.test(this.id)&&e(this).removeAttr("id")})}}),e.extend(e.expr[":"],{data:e.expr.createPseudo?e.expr.createPseudo(function(t){return function(n){return!!e.data(n,t)}}):function(t,n,r){return!!e.data(t,r[3])},focusable:function(t){return i(t,!isNaN(e.attr(t,"tabindex")))},tabbable:function(t){var n=e.attr(t,"tabindex"),r=isNaN(n);return(r||n>=0)&&i(t,!r)}}),e(function(){var t=document.body,n=t.appendChild(n=document.createElement("div"));n.offsetHeight,e.extend(n.style,{minHeight:"100px",height:"auto",padding:0,borderWidth:0}),e.support.minHeight=n.offsetHeight===100,e.support.selectstart="onselectstart"in n,t.removeChild(n).style.display="none"}),e("<a>").outerWidth(1).jquery||e.each(["Width","Height"],function(n,r){function u(t,n,r,s){return e.each(i,function(){n-=parseFloat(e.css(t,"padding"+this))||0,r&&(n-=parseFloat(e.css(t,"border"+this+"Width"))||0),s&&(n-=parseFloat(e.css(t,"margin"+this))||0)}),n}var i=r==="Width"?["Left","Right"]:["Top","Bottom"],s=r.toLowerCase(),o={innerWidth:e.fn.innerWidth,innerHeight:e.fn.innerHeight,outerWidth:e.fn.outerWidth,outerHeight:e.fn.outerHeight};e.fn["inner"+r]=function(n){return n===t?o["inner"+r].call(this):this.each(function(){e(this).css(s,u(this,n)+"px")})},e.fn["outer"+r]=function(t,n){return typeof t!="number"?o["outer"+r].call(this,t):this.each(function(){e(this).css(s,u(this,t,!0,n)+"px")})}}),e("<a>").data("a-b","a").removeData("a-b").data("a-b")&&(e.fn.removeData=function(t){return function(n){return arguments.length?t.call(this,e.camelCase(n)):t.call(this)}}(e.fn.removeData)),function(){var t=/msie ([\w.]+)/.exec(navigator.userAgent.toLowerCase())||[];e.ui.ie=t.length?!0:!1,e.ui.ie6=parseFloat(t[1],10)===6}(),e.fn.extend({disableSelection:function(){return this.bind((e.support.selectstart?"selectstart":"mousedown")+".ui-disableSelection",function(e){e.preventDefault()})},enableSelection:function(){return this.unbind(".ui-disableSelection")}}),e.extend(e.ui,{plugin:{add:function(t,n,r){var i,s=e.ui[t].prototype;for(i in r)s.plugins[i]=s.plugins[i]||[],s.plugins[i].push([n,r[i]])},call:function(e,t,n){var r,i=e.plugins[t];if(!i||!e.element[0].parentNode||e.element[0].parentNode.nodeType===11)return;for(r=0;r<i.length;r++)e.options[i[r][0]]&&i[r][1].apply(e.element,n)}},contains:e.contains,hasScroll:function(t,n){if(e(t).css("overflow")==="hidden")return!1;var r=n&&n==="left"?"scrollLeft":"scrollTop",i=!1;return t[r]>0?!0:(t[r]=1,i=t[r]>0,t[r]=0,i)},isOverAxis:function(e,t,n){return e>t&&e<t+n},isOver:function(t,n,r,i,s,o){return e.ui.isOverAxis(t,r,s)&&e.ui.isOverAxis(n,i,o)}})})(jQuery);(function(e,t){var n=0,r=Array.prototype.slice,i=e.cleanData;e.cleanData=function(t){for(var n=0,r;(r=t[n])!=null;n++)try{e(r).triggerHandler("remove")}catch(s){}i(t)},e.widget=function(t,n,r){var i,s,o,u,a=t.split(".")[0];t=t.split(".")[1],i=a+"-"+t,r||(r=n,n=e.Widget),e.expr[":"][i.toLowerCase()]=function(t){return!!e.data(t,i)},e[a]=e[a]||{},s=e[a][t],o=e[a][t]=function(e,t){if(!this._createWidget)return new o(e,t);arguments.length&&this._createWidget(e,t)},e.extend(o,s,{version:r.version,_proto:e.extend({},r),_childConstructors:[]}),u=new n,u.options=e.widget.extend({},u.options),e.each(r,function(t,i){e.isFunction(i)&&(r[t]=function(){var e=function(){return n.prototype[t].apply(this,arguments)},r=function(e){return n.prototype[t].apply(this,e)};return function(){var t=this._super,n=this._superApply,s;return this._super=e,this._superApply=r,s=i.apply(this,arguments),this._super=t,this._superApply=n,s}}())}),o.prototype=e.widget.extend(u,{widgetEventPrefix:s?u.widgetEventPrefix:t},r,{constructor:o,namespace:a,widgetName:t,widgetBaseClass:i,widgetFullName:i}),s?(e.each(s._childConstructors,function(t,n){var r=n.prototype;e.widget(r.namespace+"."+r.widgetName,o,n._proto)}),delete s._childConstructors):n._childConstructors.push(o),e.widget.bridge(t,o)},e.widget.extend=function(n){var i=r.call(arguments,1),s=0,o=i.length,u,a;for(;s<o;s++)for(u in i[s])a=i[s][u],i[s].hasOwnProperty(u)&&a!==t&&(e.isPlainObject(a)?n[u]=e.isPlainObject(n[u])?e.widget.extend({},n[u],a):e.widget.extend({},a):n[u]=a);return n},e.widget.bridge=function(n,i){var s=i.prototype.widgetFullName||n;e.fn[n]=function(o){var u=typeof o=="string",a=r.call(arguments,1),f=this;return o=!u&&a.length?e.widget.extend.apply(null,[o].concat(a)):o,u?this.each(function(){var r,i=e.data(this,s);if(!i)return e.error("cannot call methods on "+n+" prior to initialization; "+"attempted to call method '"+o+"'");if(!e.isFunction(i[o])||o.charAt(0)==="_")return e.error("no such method '"+o+"' for "+n+" widget instance");r=i[o].apply(i,a);if(r!==i&&r!==t)return f=r&&r.jquery?f.pushStack(r.get()):r,!1}):this.each(function(){var t=e.data(this,s);t?t.option(o||{})._init():e.data(this,s,new i(o,this))}),f}},e.Widget=function(){},e.Widget._childConstructors=[],e.Widget.prototype={widgetName:"widget",widgetEventPrefix:"",defaultElement:"<div>",options:{disabled:!1,create:null},_createWidget:function(t,r){r=e(r||this.defaultElement||this)[0],this.element=e(r),this.uuid=n++,this.eventNamespace="."+this.widgetName+this.uuid,this.options=e.widget.extend({},this.options,this._getCreateOptions(),t),this.bindings=e(),this.hoverable=e(),this.focusable=e(),r!==this&&(e.data(r,this.widgetName,this),e.data(r,this.widgetFullName,this),this._on(!0,this.element,{remove:function(e){e.target===r&&this.destroy()}}),this.document=e(r.style?r.ownerDocument:r.document||r),this.window=e(this.document[0].defaultView||this.document[0].parentWindow)),this._create(),this._trigger("create",null,this._getCreateEventData()),this._init()},_getCreateOptions:e.noop,_getCreateEventData:e.noop,_create:e.noop,_init:e.noop,destroy:function(){this._destroy(),this.element.unbind(this.eventNamespace).removeData(this.widgetName).removeData(this.widgetFullName).removeData(e.camelCase(this.widgetFullName)),this.widget().unbind(this.eventNamespace).removeAttr("aria-disabled").removeClass(this.widgetFullName+"-disabled "+"ui-state-disabled"),this.bindings.unbind(this.eventNamespace),this.hoverable.removeClass("ui-state-hover"),this.focusable.removeClass("ui-state-focus")},_destroy:e.noop,widget:function(){return this.element},option:function(n,r){var i=n,s,o,u;if(arguments.length===0)return e.widget.extend({},this.options);if(typeof n=="string"){i={},s=n.split("."),n=s.shift();if(s.length){o=i[n]=e.widget.extend({},this.options[n]);for(u=0;u<s.length-1;u++)o[s[u]]=o[s[u]]||{},o=o[s[u]];n=s.pop();if(r===t)return o[n]===t?null:o[n];o[n]=r}else{if(r===t)return this.options[n]===t?null:this.options[n];i[n]=r}}return this._setOptions(i),this},_setOptions:function(e){var t;for(t in e)this._setOption(t,e[t]);return this},_setOption:function(e,t){return this.options[e]=t,e==="disabled"&&(this.widget().toggleClass(this.widgetFullName+"-disabled ui-state-disabled",!!t).attr("aria-disabled",t),this.hoverable.removeClass("ui-state-hover"),this.focusable.removeClass("ui-state-focus")),this},enable:function(){return this._setOption("disabled",!1)},disable:function(){return this._setOption("disabled",!0)},_on:function(t,n,r){var i,s=this;typeof t!="boolean"&&(r=n,n=t,t=!1),r?(n=i=e(n),this.bindings=this.bindings.add(n)):(r=n,n=this.element,i=this.widget()),e.each(r,function(r,o){function u(){if(!t&&(s.options.disabled===!0||e(this).hasClass("ui-state-disabled")))return;return(typeof o=="string"?s[o]:o).apply(s,arguments)}typeof o!="string"&&(u.guid=o.guid=o.guid||u.guid||e.guid++);var a=r.match(/^(\w+)\s*(.*)$/),f=a[1]+s.eventNamespace,l=a[2];l?i.delegate(l,f,u):n.bind(f,u)})},_off:function(e,t){t=(t||"").split(" ").join(this.eventNamespace+" ")+this.eventNamespace,e.unbind(t).undelegate(t)},_delay:function(e,t){function n(){return(typeof e=="string"?r[e]:e).apply(r,arguments)}var r=this;return setTimeout(n,t||0)},_hoverable:function(t){this.hoverable=this.hoverable.add(t),this._on(t,{mouseenter:function(t){e(t.currentTarget).addClass("ui-state-hover")},mouseleave:function(t){e(t.currentTarget).removeClass("ui-state-hover")}})},_focusable:function(t){this.focusable=this.focusable.add(t),this._on(t,{focusin:function(t){e(t.currentTarget).addClass("ui-state-focus")},focusout:function(t){e(t.currentTarget).removeClass("ui-state-focus")}})},_trigger:function(t,n,r){var i,s,o=this.options[t];r=r||{},n=e.Event(n),n.type=(t===this.widgetEventPrefix?t:this.widgetEventPrefix+t).toLowerCase(),n.target=this.element[0],s=n.originalEvent;if(s)for(i in s)i in n||(n[i]=s[i]);return this.element.trigger(n,r),!(e.isFunction(o)&&o.apply(this.element[0],[n].concat(r))===!1||n.isDefaultPrevented())}},e.each({show:"fadeIn",hide:"fadeOut"},function(t,n){e.Widget.prototype["_"+t]=function(r,i,s){typeof i=="string"&&(i={effect:i});var o,u=i?i===!0||typeof i=="number"?n:i.effect||n:t;i=i||{},typeof i=="number"&&(i={duration:i}),o=!e.isEmptyObject(i),i.complete=s,i.delay&&r.delay(i.delay),o&&e.effects&&(e.effects.effect[u]||e.uiBackCompat!==!1&&e.effects[u])?r[t](i):u!==t&&r[u]?r[u](i.duration,i.easing,s):r.queue(function(n){e(this)[t](),s&&s.call(r[0]),n()})}}),e.uiBackCompat!==!1&&(e.Widget.prototype._getCreateOptions=function(){return e.metadata&&e.metadata.get(this.element[0])[this.widgetName]})})(jQuery);(function(e,t){var n=!1;e(document).mouseup(function(e){n=!1}),e.widget("ui.mouse",{version:"1.9.2",options:{cancel:"input,textarea,button,select,option",distance:1,delay:0},_mouseInit:function(){var t=this;this.element.bind("mousedown."+this.widgetName,function(e){return t._mouseDown(e)}).bind("click."+this.widgetName,function(n){if(!0===e.data(n.target,t.widgetName+".preventClickEvent"))return e.removeData(n.target,t.widgetName+".preventClickEvent"),n.stopImmediatePropagation(),!1}),this.started=!1},_mouseDestroy:function(){this.element.unbind("."+this.widgetName),this._mouseMoveDelegate&&e(document).unbind("mousemove."+this.widgetName,this._mouseMoveDelegate).unbind("mouseup."+this.widgetName,this._mouseUpDelegate)},_mouseDown:function(t){if(n)return;this._mouseStarted&&this._mouseUp(t),this._mouseDownEvent=t;var r=this,i=t.which===1,s=typeof this.options.cancel=="string"&&t.target.nodeName?e(t.target).closest(this.options.cancel).length:!1;if(!i||s||!this._mouseCapture(t))return!0;this.mouseDelayMet=!this.options.delay,this.mouseDelayMet||(this._mouseDelayTimer=setTimeout(function(){r.mouseDelayMet=!0},this.options.delay));if(this._mouseDistanceMet(t)&&this._mouseDelayMet(t)){this._mouseStarted=this._mouseStart(t)!==!1;if(!this._mouseStarted)return t.preventDefault(),!0}return!0===e.data(t.target,this.widgetName+".preventClickEvent")&&e.removeData(t.target,this.widgetName+".preventClickEvent"),this._mouseMoveDelegate=function(e){return r._mouseMove(e)},this._mouseUpDelegate=function(e){return r._mouseUp(e)},e(document).bind("mousemove."+this.widgetName,this._mouseMoveDelegate).bind("mouseup."+this.widgetName,this._mouseUpDelegate),t.preventDefault(),n=!0,!0},_mouseMove:function(t){return!e.ui.ie||document.documentMode>=9||!!t.button?this._mouseStarted?(this._mouseDrag(t),t.preventDefault()):(this._mouseDistanceMet(t)&&this._mouseDelayMet(t)&&(this._mouseStarted=this._mouseStart(this._mouseDownEvent,t)!==!1,this._mouseStarted?this._mouseDrag(t):this._mouseUp(t)),!this._mouseStarted):this._mouseUp(t)},_mouseUp:function(t){return e(document).unbind("mousemove."+this.widgetName,this._mouseMoveDelegate).unbind("mouseup."+this.widgetName,this._mouseUpDelegate),this._mouseStarted&&(this._mouseStarted=!1,t.target===this._mouseDownEvent.target&&e.data(t.target,this.widgetName+".preventClickEvent",!0),this._mouseStop(t)),!1},_mouseDistanceMet:function(e){return Math.max(Math.abs(this._mouseDownEvent.pageX-e.pageX),Math.abs(this._mouseDownEvent.pageY-e.pageY))>=this.options.distance},_mouseDelayMet:function(e){return this.mouseDelayMet},_mouseStart:function(e){},_mouseDrag:function(e){},_mouseStop:function(e){},_mouseCapture:function(e){return!0}})})(jQuery);(function(e,t){function h(e,t,n){return[parseInt(e[0],10)*(l.test(e[0])?t/100:1),parseInt(e[1],10)*(l.test(e[1])?n/100:1)]}function p(t,n){return parseInt(e.css(t,n),10)||0}e.ui=e.ui||{};var n,r=Math.max,i=Math.abs,s=Math.round,o=/left|center|right/,u=/top|center|bottom/,a=/[\+\-]\d+%?/,f=/^\w+/,l=/%$/,c=e.fn.position;e.position={scrollbarWidth:function(){if(n!==t)return n;var r,i,s=e("<div style='display:block;width:50px;height:50px;overflow:hidden;'><div style='height:100px;width:auto;'></div></div>"),o=s.children()[0];return e("body").append(s),r=o.offsetWidth,s.css("overflow","scroll"),i=o.offsetWidth,r===i&&(i=s[0].clientWidth),s.remove(),n=r-i},getScrollInfo:function(t){var n=t.isWindow?"":t.element.css("overflow-x"),r=t.isWindow?"":t.element.css("overflow-y"),i=n==="scroll"||n==="auto"&&t.width<t.element[0].scrollWidth,s=r==="scroll"||r==="auto"&&t.height<t.element[0].scrollHeight;return{width:i?e.position.scrollbarWidth():0,height:s?e.position.scrollbarWidth():0}},getWithinInfo:function(t){var n=e(t||window),r=e.isWindow(n[0]);return{element:n,isWindow:r,offset:n.offset()||{left:0,top:0},scrollLeft:n.scrollLeft(),scrollTop:n.scrollTop(),width:r?n.width():n.outerWidth(),height:r?n.height():n.outerHeight()}}},e.fn.position=function(t){if(!t||!t.of)return c.apply(this,arguments);t=e.extend({},t);var n,l,d,v,m,g=e(t.of),y=e.position.getWithinInfo(t.within),b=e.position.getScrollInfo(y),w=g[0],E=(t.collision||"flip").split(" "),S={};return w.nodeType===9?(l=g.width(),d=g.height(),v={top:0,left:0}):e.isWindow(w)?(l=g.width(),d=g.height(),v={top:g.scrollTop(),left:g.scrollLeft()}):w.preventDefault?(t.at="left top",l=d=0,v={top:w.pageY,left:w.pageX}):(l=g.outerWidth(),d=g.outerHeight(),v=g.offset()),m=e.extend({},v),e.each(["my","at"],function(){var e=(t[this]||"").split(" "),n,r;e.length===1&&(e=o.test(e[0])?e.concat(["center"]):u.test(e[0])?["center"].concat(e):["center","center"]),e[0]=o.test(e[0])?e[0]:"center",e[1]=u.test(e[1])?e[1]:"center",n=a.exec(e[0]),r=a.exec(e[1]),S[this]=[n?n[0]:0,r?r[0]:0],t[this]=[f.exec(e[0])[0],f.exec(e[1])[0]]}),E.length===1&&(E[1]=E[0]),t.at[0]==="right"?m.left+=l:t.at[0]==="center"&&(m.left+=l/2),t.at[1]==="bottom"?m.top+=d:t.at[1]==="center"&&(m.top+=d/2),n=h(S.at,l,d),m.left+=n[0],m.top+=n[1],this.each(function(){var o,u,a=e(this),f=a.outerWidth(),c=a.outerHeight(),w=p(this,"marginLeft"),x=p(this,"marginTop"),T=f+w+p(this,"marginRight")+b.width,N=c+x+p(this,"marginBottom")+b.height,C=e.extend({},m),k=h(S.my,a.outerWidth(),a.outerHeight());t.my[0]==="right"?C.left-=f:t.my[0]==="center"&&(C.left-=f/2),t.my[1]==="bottom"?C.top-=c:t.my[1]==="center"&&(C.top-=c/2),C.left+=k[0],C.top+=k[1],e.support.offsetFractions||(C.left=s(C.left),C.top=s(C.top)),o={marginLeft:w,marginTop:x},e.each(["left","top"],function(r,i){e.ui.position[E[r]]&&e.ui.position[E[r]][i](C,{targetWidth:l,targetHeight:d,elemWidth:f,elemHeight:c,collisionPosition:o,collisionWidth:T,collisionHeight:N,offset:[n[0]+k[0],n[1]+k[1]],my:t.my,at:t.at,within:y,elem:a})}),e.fn.bgiframe&&a.bgiframe(),t.using&&(u=function(e){var n=v.left-C.left,s=n+l-f,o=v.top-C.top,u=o+d-c,h={target:{element:g,left:v.left,top:v.top,width:l,height:d},element:{element:a,left:C.left,top:C.top,width:f,height:c},horizontal:s<0?"left":n>0?"right":"center",vertical:u<0?"top":o>0?"bottom":"middle"};l<f&&i(n+s)<l&&(h.horizontal="center"),d<c&&i(o+u)<d&&(h.vertical="middle"),r(i(n),i(s))>r(i(o),i(u))?h.important="horizontal":h.important="vertical",t.using.call(this,e,h)}),a.offset(e.extend(C,{using:u}))})},e.ui.position={fit:{left:function(e,t){var n=t.within,i=n.isWindow?n.scrollLeft:n.offset.left,s=n.width,o=e.left-t.collisionPosition.marginLeft,u=i-o,a=o+t.collisionWidth-s-i,f;t.collisionWidth>s?u>0&&a<=0?(f=e.left+u+t.collisionWidth-s-i,e.left+=u-f):a>0&&u<=0?e.left=i:u>a?e.left=i+s-t.collisionWidth:e.left=i:u>0?e.left+=u:a>0?e.left-=a:e.left=r(e.left-o,e.left)},top:function(e,t){var n=t.within,i=n.isWindow?n.scrollTop:n.offset.top,s=t.within.height,o=e.top-t.collisionPosition.marginTop,u=i-o,a=o+t.collisionHeight-s-i,f;t.collisionHeight>s?u>0&&a<=0?(f=e.top+u+t.collisionHeight-s-i,e.top+=u-f):a>0&&u<=0?e.top=i:u>a?e.top=i+s-t.collisionHeight:e.top=i:u>0?e.top+=u:a>0?e.top-=a:e.top=r(e.top-o,e.top)}},flip:{left:function(e,t){var n=t.within,r=n.offset.left+n.scrollLeft,s=n.width,o=n.isWindow?n.scrollLeft:n.offset.left,u=e.left-t.collisionPosition.marginLeft,a=u-o,f=u+t.collisionWidth-s-o,l=t.my[0]==="left"?-t.elemWidth:t.my[0]==="right"?t.elemWidth:0,c=t.at[0]==="left"?t.targetWidth:t.at[0]==="right"?-t.targetWidth:0,h=-2*t.offset[0],p,d;if(a<0){p=e.left+l+c+h+t.collisionWidth-s-r;if(p<0||p<i(a))e.left+=l+c+h}else if(f>0){d=e.left-t.collisionPosition.marginLeft+l+c+h-o;if(d>0||i(d)<f)e.left+=l+c+h}},top:function(e,t){var n=t.within,r=n.offset.top+n.scrollTop,s=n.height,o=n.isWindow?n.scrollTop:n.offset.top,u=e.top-t.collisionPosition.marginTop,a=u-o,f=u+t.collisionHeight-s-o,l=t.my[1]==="top",c=l?-t.elemHeight:t.my[1]==="bottom"?t.elemHeight:0,h=t.at[1]==="top"?t.targetHeight:t.at[1]==="bottom"?-t.targetHeight:0,p=-2*t.offset[1],d,v;a<0?(v=e.top+c+h+p+t.collisionHeight-s-r,e.top+c+h+p>a&&(v<0||v<i(a))&&(e.top+=c+h+p)):f>0&&(d=e.top-t.collisionPosition.marginTop+c+h+p-o,e.top+c+h+p>f&&(d>0||i(d)<f)&&(e.top+=c+h+p))}},flipfit:{left:function(){e.ui.position.flip.left.apply(this,arguments),e.ui.position.fit.left.apply(this,arguments)},top:function(){e.ui.position.flip.top.apply(this,arguments),e.ui.position.fit.top.apply(this,arguments)}}},function(){var t,n,r,i,s,o=document.getElementsByTagName("body")[0],u=document.createElement("div");t=document.createElement(o?"div":"body"),r={visibility:"hidden",width:0,height:0,border:0,margin:0,background:"none"},o&&e.extend(r,{position:"absolute",left:"-1000px",top:"-1000px"});for(s in r)t.style[s]=r[s];t.appendChild(u),n=o||document.documentElement,n.insertBefore(t,n.firstChild),u.style.cssText="position: absolute; left: 10.7432222px;",i=e(u).offset().left,e.support.offsetFractions=i>10&&i<11,t.innerHTML="",n.removeChild(t)}(),e.uiBackCompat!==!1&&function(e){var n=e.fn.position;e.fn.position=function(r){if(!r||!r.offset)return n.call(this,r);var i=r.offset.split(" "),s=r.at.split(" ");return i.length===1&&(i[1]=i[0]),/^\d/.test(i[0])&&(i[0]="+"+i[0]),/^\d/.test(i[1])&&(i[1]="+"+i[1]),s.length===1&&(/left|center|right/.test(s[0])?s[1]="center":(s[1]=s[0],s[0]="center")),n.call(this,e.extend(r,{at:s[0]+i[0]+" "+s[1]+i[1],offset:t}))}}(jQuery)})(jQuery);(function(e,t){e.widget("ui.draggable",e.ui.mouse,{version:"1.9.2",widgetEventPrefix:"drag",options:{addClasses:!0,appendTo:"parent",axis:!1,connectToSortable:!1,containment:!1,cursor:"auto",cursorAt:!1,grid:!1,handle:!1,helper:"original",iframeFix:!1,opacity:!1,refreshPositions:!1,revert:!1,revertDuration:500,scope:"default",scroll:!0,scrollSensitivity:20,scrollSpeed:20,snap:!1,snapMode:"both",snapTolerance:20,stack:!1,zIndex:!1},_create:function(){this.options.helper=="original"&&!/^(?:r|a|f)/.test(this.element.css("position"))&&(this.element[0].style.position="relative"),this.options.addClasses&&this.element.addClass("ui-draggable"),this.options.disabled&&this.element.addClass("ui-draggable-disabled"),this._mouseInit()},_destroy:function(){this.element.removeClass("ui-draggable ui-draggable-dragging ui-draggable-disabled"),this._mouseDestroy()},_mouseCapture:function(t){var n=this.options;return this.helper||n.disabled||e(t.target).is(".ui-resizable-handle")?!1:(this.handle=this._getHandle(t),this.handle?(e(n.iframeFix===!0?"iframe":n.iframeFix).each(function(){e('<div class="ui-draggable-iframeFix" style="background: #fff;"></div>').css({width:this.offsetWidth+"px",height:this.offsetHeight+"px",position:"absolute",opacity:"0.001",zIndex:1e3}).css(e(this).offset()).appendTo("body")}),!0):!1)},_mouseStart:function(t){var n=this.options;return this.helper=this._createHelper(t),this.helper.addClass("ui-draggable-dragging"),this._cacheHelperProportions(),e.ui.ddmanager&&(e.ui.ddmanager.current=this),this._cacheMargins(),this.cssPosition=this.helper.css("position"),this.scrollParent=this.helper.scrollParent(),this.offset=this.positionAbs=this.element.offset(),this.offset={top:this.offset.top-this.margins.top,left:this.offset.left-this.margins.left},e.extend(this.offset,{click:{left:t.pageX-this.offset.left,top:t.pageY-this.offset.top},parent:this._getParentOffset(),relative:this._getRelativeOffset()}),this.originalPosition=this.position=this._generatePosition(t),this.originalPageX=t.pageX,this.originalPageY=t.pageY,n.cursorAt&&this._adjustOffsetFromHelper(n.cursorAt),n.containment&&this._setContainment(),this._trigger("start",t)===!1?(this._clear(),!1):(this._cacheHelperProportions(),e.ui.ddmanager&&!n.dropBehaviour&&e.ui.ddmanager.prepareOffsets(this,t),this._mouseDrag(t,!0),e.ui.ddmanager&&e.ui.ddmanager.dragStart(this,t),!0)},_mouseDrag:function(t,n){this.position=this._generatePosition(t),this.positionAbs=this._convertPositionTo("absolute");if(!n){var r=this._uiHash();if(this._trigger("drag",t,r)===!1)return this._mouseUp({}),!1;this.position=r.position}if(!this.options.axis||this.options.axis!="y")this.helper[0].style.left=this.position.left+"px";if(!this.options.axis||this.options.axis!="x")this.helper[0].style.top=this.position.top+"px";return e.ui.ddmanager&&e.ui.ddmanager.drag(this,t),!1},_mouseStop:function(t){var n=!1;e.ui.ddmanager&&!this.options.dropBehaviour&&(n=e.ui.ddmanager.drop(this,t)),this.dropped&&(n=this.dropped,this.dropped=!1);var r=this.element[0],i=!1;while(r&&(r=r.parentNode))r==document&&(i=!0);if(!i&&this.options.helper==="original")return!1;if(this.options.revert=="invalid"&&!n||this.options.revert=="valid"&&n||this.options.revert===!0||e.isFunction(this.options.revert)&&this.options.revert.call(this.element,n)){var s=this;e(this.helper).animate(this.originalPosition,parseInt(this.options.revertDuration,10),function(){s._trigger("stop",t)!==!1&&s._clear()})}else this._trigger("stop",t)!==!1&&this._clear();return!1},_mouseUp:function(t){return e("div.ui-draggable-iframeFix").each(function(){this.parentNode.removeChild(this)}),e.ui.ddmanager&&e.ui.ddmanager.dragStop(this,t),e.ui.mouse.prototype._mouseUp.call(this,t)},cancel:function(){return this.helper.is(".ui-draggable-dragging")?this._mouseUp({}):this._clear(),this},_getHandle:function(t){var n=!this.options.handle||!e(this.options.handle,this.element).length?!0:!1;return e(this.options.handle,this.element).find("*").andSelf().each(function(){this==t.target&&(n=!0)}),n},_createHelper:function(t){var n=this.options,r=e.isFunction(n.helper)?e(n.helper.apply(this.element[0],[t])):n.helper=="clone"?this.element.clone().removeAttr("id"):this.element;return r.parents("body").length||r.appendTo(n.appendTo=="parent"?this.element[0].parentNode:n.appendTo),r[0]!=this.element[0]&&!/(fixed|absolute)/.test(r.css("position"))&&r.css("position","absolute"),r},_adjustOffsetFromHelper:function(t){typeof t=="string"&&(t=t.split(" ")),e.isArray(t)&&(t={left:+t[0],top:+t[1]||0}),"left"in t&&(this.offset.click.left=t.left+this.margins.left),"right"in t&&(this.offset.click.left=this.helperProportions.width-t.right+this.margins.left),"top"in t&&(this.offset.click.top=t.top+this.margins.top),"bottom"in t&&(this.offset.click.top=this.helperProportions.height-t.bottom+this.margins.top)},_getParentOffset:function(){this.offsetParent=this.helper.offsetParent();var t=this.offsetParent.offset();this.cssPosition=="absolute"&&this.scrollParent[0]!=document&&e.contains(this.scrollParent[0],this.offsetParent[0])&&(t.left+=this.scrollParent.scrollLeft(),t.top+=this.scrollParent.scrollTop());if(this.offsetParent[0]==document.body||this.offsetParent[0].tagName&&this.offsetParent[0].tagName.toLowerCase()=="html"&&e.ui.ie)t={top:0,left:0};return{top:t.top+(parseInt(this.offsetParent.css("borderTopWidth"),10)||0),left:t.left+(parseInt(this.offsetParent.css("borderLeftWidth"),10)||0)}},_getRelativeOffset:function(){if(this.cssPosition=="relative"){var e=this.element.position();return{top:e.top-(parseInt(this.helper.css("top"),10)||0)+this.scrollParent.scrollTop(),left:e.left-(parseInt(this.helper.css("left"),10)||0)+this.scrollParent.scrollLeft()}}return{top:0,left:0}},_cacheMargins:function(){this.margins={left:parseInt(this.element.css("marginLeft"),10)||0,top:parseInt(this.element.css("marginTop"),10)||0,right:parseInt(this.element.css("marginRight"),10)||0,bottom:parseInt(this.element.css("marginBottom"),10)||0}},_cacheHelperProportions:function(){this.helperProportions={width:this.helper.outerWidth(),height:this.helper.outerHeight()}},_setContainment:function(){var t=this.options;t.containment=="parent"&&(t.containment=this.helper[0].parentNode);if(t.containment=="document"||t.containment=="window")this.containment=[t.containment=="document"?0:e(window).scrollLeft()-this.offset.relative.left-this.offset.parent.left,t.containment=="document"?0:e(window).scrollTop()-this.offset.relative.top-this.offset.parent.top,(t.containment=="document"?0:e(window).scrollLeft())+e(t.containment=="document"?document:window).width()-this.helperProportions.width-this.margins.left,(t.containment=="document"?0:e(window).scrollTop())+(e(t.containment=="document"?document:window).height()||document.body.parentNode.scrollHeight)-this.helperProportions.height-this.margins.top];if(!/^(document|window|parent)$/.test(t.containment)&&t.containment.constructor!=Array){var n=e(t.containment),r=n[0];if(!r)return;var i=n.offset(),s=e(r).css("overflow")!="hidden";this.containment=[(parseInt(e(r).css("borderLeftWidth"),10)||0)+(parseInt(e(r).css("paddingLeft"),10)||0),(parseInt(e(r).css("borderTopWidth"),10)||0)+(parseInt(e(r).css("paddingTop"),10)||0),(s?Math.max(r.scrollWidth,r.offsetWidth):r.offsetWidth)-(parseInt(e(r).css("borderLeftWidth"),10)||0)-(parseInt(e(r).css("paddingRight"),10)||0)-this.helperProportions.width-this.margins.left-this.margins.right,(s?Math.max(r.scrollHeight,r.offsetHeight):r.offsetHeight)-(parseInt(e(r).css("borderTopWidth"),10)||0)-(parseInt(e(r).css("paddingBottom"),10)||0)-this.helperProportions.height-this.margins.top-this.margins.bottom],this.relative_container=n}else t.containment.constructor==Array&&(this.containment=t.containment)},_convertPositionTo:function(t,n){n||(n=this.position);var r=t=="absolute"?1:-1,i=this.options,s=this.cssPosition!="absolute"||this.scrollParent[0]!=document&&!!e.contains(this.scrollParent[0],this.offsetParent[0])?this.scrollParent:this.offsetParent,o=/(html|body)/i.test(s[0].tagName);return{top:n.top+this.offset.relative.top*r+this.offset.parent.top*r-(this.cssPosition=="fixed"?-this.scrollParent.scrollTop():o?0:s.scrollTop())*r,left:n.left+this.offset.relative.left*r+this.offset.parent.left*r-(this.cssPosition=="fixed"?-this.scrollParent.scrollLeft():o?0:s.scrollLeft())*r}},_generatePosition:function(t){var n=this.options,r=this.cssPosition!="absolute"||this.scrollParent[0]!=document&&!!e.contains(this.scrollParent[0],this.offsetParent[0])?this.scrollParent:this.offsetParent,i=/(html|body)/i.test(r[0].tagName),s=t.pageX,o=t.pageY;if(this.originalPosition){var u;if(this.containment){if(this.relative_container){var a=this.relative_container.offset();u=[this.containment[0]+a.left,this.containment[1]+a.top,this.containment[2]+a.left,this.containment[3]+a.top]}else u=this.containment;t.pageX-this.offset.click.left<u[0]&&(s=u[0]+this.offset.click.left),t.pageY-this.offset.click.top<u[1]&&(o=u[1]+this.offset.click.top),t.pageX-this.offset.click.left>u[2]&&(s=u[2]+this.offset.click.left),t.pageY-this.offset.click.top>u[3]&&(o=u[3]+this.offset.click.top)}if(n.grid){var f=n.grid[1]?this.originalPageY+Math.round((o-this.originalPageY)/n.grid[1])*n.grid[1]:this.originalPageY;o=u?f-this.offset.click.top<u[1]||f-this.offset.click.top>u[3]?f-this.offset.click.top<u[1]?f+n.grid[1]:f-n.grid[1]:f:f;var l=n.grid[0]?this.originalPageX+Math.round((s-this.originalPageX)/n.grid[0])*n.grid[0]:this.originalPageX;s=u?l-this.offset.click.left<u[0]||l-this.offset.click.left>u[2]?l-this.offset.click.left<u[0]?l+n.grid[0]:l-n.grid[0]:l:l}}return{top:o-this.offset.click.top-this.offset.relative.top-this.offset.parent.top+(this.cssPosition=="fixed"?-this.scrollParent.scrollTop():i?0:r.scrollTop()),left:s-this.offset.click.left-this.offset.relative.left-this.offset.parent.left+(this.cssPosition=="fixed"?-this.scrollParent.scrollLeft():i?0:r.scrollLeft())}},_clear:function(){this.helper.removeClass("ui-draggable-dragging"),this.helper[0]!=this.element[0]&&!this.cancelHelperRemoval&&this.helper.remove(),this.helper=null,this.cancelHelperRemoval=!1},_trigger:function(t,n,r){return r=r||this._uiHash(),e.ui.plugin.call(this,t,[n,r]),t=="drag"&&(this.positionAbs=this._convertPositionTo("absolute")),e.Widget.prototype._trigger.call(this,t,n,r)},plugins:{},_uiHash:function(e){return{helper:this.helper,position:this.position,originalPosition:this.originalPosition,offset:this.positionAbs}}}),e.ui.plugin.add("draggable","connectToSortable",{start:function(t,n){var r=e(this).data("draggable"),i=r.options,s=e.extend({},n,{item:r.element});r.sortables=[],e(i.connectToSortable).each(function(){var n=e.data(this,"sortable");n&&!n.options.disabled&&(r.sortables.push({instance:n,shouldRevert:n.options.revert}),n.refreshPositions(),n._trigger("activate",t,s))})},stop:function(t,n){var r=e(this).data("draggable"),i=e.extend({},n,{item:r.element});e.each(r.sortables,function(){this.instance.isOver?(this.instance.isOver=0,r.cancelHelperRemoval=!0,this.instance.cancelHelperRemoval=!1,this.shouldRevert&&(this.instance.options.revert=!0),this.instance._mouseStop(t),this.instance.options.helper=this.instance.options._helper,r.options.helper=="original"&&this.instance.currentItem.css({top:"auto",left:"auto"})):(this.instance.cancelHelperRemoval=!1,this.instance._trigger("deactivate",t,i))})},drag:function(t,n){var r=e(this).data("draggable"),i=this,s=function(t){var n=this.offset.click.top,r=this.offset.click.left,i=this.positionAbs.top,s=this.positionAbs.left,o=t.height,u=t.width,a=t.top,f=t.left;return e.ui.isOver(i+n,s+r,a,f,o,u)};e.each(r.sortables,function(s){var o=!1,u=this;this.instance.positionAbs=r.positionAbs,this.instance.helperProportions=r.helperProportions,this.instance.offset.click=r.offset.click,this.instance._intersectsWith(this.instance.containerCache)&&(o=!0,e.each(r.sortables,function(){return this.instance.positionAbs=r.positionAbs,this.instance.helperProportions=r.helperProportions,this.instance.offset.click=r.offset.click,this!=u&&this.instance._intersectsWith(this.instance.containerCache)&&e.ui.contains(u.instance.element[0],this.instance.element[0])&&(o=!1),o})),o?(this.instance.isOver||(this.instance.isOver=1,this.instance.currentItem=e(i).clone().removeAttr("id").appendTo(this.instance.element).data("sortable-item",!0),this.instance.options._helper=this.instance.options.helper,this.instance.options.helper=function(){return n.helper[0]},t.target=this.instance.currentItem[0],this.instance._mouseCapture(t,!0),this.instance._mouseStart(t,!0,!0),this.instance.offset.click.top=r.offset.click.top,this.instance.offset.click.left=r.offset.click.left,this.instance.offset.parent.left-=r.offset.parent.left-this.instance.offset.parent.left,this.instance.offset.parent.top-=r.offset.parent.top-this.instance.offset.parent.top,r._trigger("toSortable",t),r.dropped=this.instance.element,r.currentItem=r.element,this.instance.fromOutside=r),this.instance.currentItem&&this.instance._mouseDrag(t)):this.instance.isOver&&(this.instance.isOver=0,this.instance.cancelHelperRemoval=!0,this.instance.options.revert=!1,this.instance._trigger("out",t,this.instance._uiHash(this.instance)),this.instance._mouseStop(t,!0),this.instance.options.helper=this.instance.options._helper,this.instance.currentItem.remove(),this.instance.placeholder&&this.instance.placeholder.remove(),r._trigger("fromSortable",t),r.dropped=!1)})}}),e.ui.plugin.add("draggable","cursor",{start:function(t,n){var r=e("body"),i=e(this).data("draggable").options;r.css("cursor")&&(i._cursor=r.css("cursor")),r.css("cursor",i.cursor)},stop:function(t,n){var r=e(this).data("draggable").options;r._cursor&&e("body").css("cursor",r._cursor)}}),e.ui.plugin.add("draggable","opacity",{start:function(t,n){var r=e(n.helper),i=e(this).data("draggable").options;r.css("opacity")&&(i._opacity=r.css("opacity")),r.css("opacity",i.opacity)},stop:function(t,n){var r=e(this).data("draggable").options;r._opacity&&e(n.helper).css("opacity",r._opacity)}}),e.ui.plugin.add("draggable","scroll",{start:function(t,n){var r=e(this).data("draggable");r.scrollParent[0]!=document&&r.scrollParent[0].tagName!="HTML"&&(r.overflowOffset=r.scrollParent.offset())},drag:function(t,n){var r=e(this).data("draggable"),i=r.options,s=!1;if(r.scrollParent[0]!=document&&r.scrollParent[0].tagName!="HTML"){if(!i.axis||i.axis!="x")r.overflowOffset.top+r.scrollParent[0].offsetHeight-t.pageY<i.scrollSensitivity?r.scrollParent[0].scrollTop=s=r.scrollParent[0].scrollTop+i.scrollSpeed:t.pageY-r.overflowOffset.top<i.scrollSensitivity&&(r.scrollParent[0].scrollTop=s=r.scrollParent[0].scrollTop-i.scrollSpeed);if(!i.axis||i.axis!="y")r.overflowOffset.left+r.scrollParent[0].offsetWidth-t.pageX<i.scrollSensitivity?r.scrollParent[0].scrollLeft=s=r.scrollParent[0].scrollLeft+i.scrollSpeed:t.pageX-r.overflowOffset.left<i.scrollSensitivity&&(r.scrollParent[0].scrollLeft=s=r.scrollParent[0].scrollLeft-i.scrollSpeed)}else{if(!i.axis||i.axis!="x")t.pageY-e(document).scrollTop()<i.scrollSensitivity?s=e(document).scrollTop(e(document).scrollTop()-i.scrollSpeed):e(window).height()-(t.pageY-e(document).scrollTop())<i.scrollSensitivity&&(s=e(document).scrollTop(e(document).scrollTop()+i.scrollSpeed));if(!i.axis||i.axis!="y")t.pageX-e(document).scrollLeft()<i.scrollSensitivity?s=e(document).scrollLeft(e(document).scrollLeft()-i.scrollSpeed):e(window).width()-(t.pageX-e(document).scrollLeft())<i.scrollSensitivity&&(s=e(document).scrollLeft(e(document).scrollLeft()+i.scrollSpeed))}s!==!1&&e.ui.ddmanager&&!i.dropBehaviour&&e.ui.ddmanager.prepareOffsets(r,t)}}),e.ui.plugin.add("draggable","snap",{start:function(t,n){var r=e(this).data("draggable"),i=r.options;r.snapElements=[],e(i.snap.constructor!=String?i.snap.items||":data(draggable)":i.snap).each(function(){var t=e(this),n=t.offset();this!=r.element[0]&&r.snapElements.push({item:this,width:t.outerWidth(),height:t.outerHeight(),top:n.top,left:n.left})})},drag:function(t,n){var r=e(this).data("draggable"),i=r.options,s=i.snapTolerance,o=n.offset.left,u=o+r.helperProportions.width,a=n.offset.top,f=a+r.helperProportions.height;for(var l=r.snapElements.length-1;l>=0;l--){var c=r.snapElements[l].left,h=c+r.snapElements[l].width,p=r.snapElements[l].top,d=p+r.snapElements[l].height;if(!(c-s<o&&o<h+s&&p-s<a&&a<d+s||c-s<o&&o<h+s&&p-s<f&&f<d+s||c-s<u&&u<h+s&&p-s<a&&a<d+s||c-s<u&&u<h+s&&p-s<f&&f<d+s)){r.snapElements[l].snapping&&r.options.snap.release&&r.options.snap.release.call(r.element,t,e.extend(r._uiHash(),{snapItem:r.snapElements[l].item})),r.snapElements[l].snapping=!1;continue}if(i.snapMode!="inner"){var v=Math.abs(p-f)<=s,m=Math.abs(d-a)<=s,g=Math.abs(c-u)<=s,y=Math.abs(h-o)<=s;v&&(n.position.top=r._convertPositionTo("relative",{top:p-r.helperProportions.height,left:0}).top-r.margins.top),m&&(n.position.top=r._convertPositionTo("relative",{top:d,left:0}).top-r.margins.top),g&&(n.position.left=r._convertPositionTo("relative",{top:0,left:c-r.helperProportions.width}).left-r.margins.left),y&&(n.position.left=r._convertPositionTo("relative",{top:0,left:h}).left-r.margins.left)}var b=v||m||g||y;if(i.snapMode!="outer"){var v=Math.abs(p-a)<=s,m=Math.abs(d-f)<=s,g=Math.abs(c-o)<=s,y=Math.abs(h-u)<=s;v&&(n.position.top=r._convertPositionTo("relative",{top:p,left:0}).top-r.margins.top),m&&(n.position.top=r._convertPositionTo("relative",{top:d-r.helperProportions.height,left:0}).top-r.margins.top),g&&(n.position.left=r._convertPositionTo("relative",{top:0,left:c}).left-r.margins.left),y&&(n.position.left=r._convertPositionTo("relative",{top:0,left:h-r.helperProportions.width}).left-r.margins.left)}!r.snapElements[l].snapping&&(v||m||g||y||b)&&r.options.snap.snap&&r.options.snap.snap.call(r.element,t,e.extend(r._uiHash(),{snapItem:r.snapElements[l].item})),r.snapElements[l].snapping=v||m||g||y||b}}}),e.ui.plugin.add("draggable","stack",{start:function(t,n){var r=e(this).data("draggable").options,i=e.makeArray(e(r.stack)).sort(function(t,n){return(parseInt(e(t).css("zIndex"),10)||0)-(parseInt(e(n).css("zIndex"),10)||0)});if(!i.length)return;var s=parseInt(i[0].style.zIndex)||0;e(i).each(function(e){this.style.zIndex=s+e}),this[0].style.zIndex=s+i.length}}),e.ui.plugin.add("draggable","zIndex",{start:function(t,n){var r=e(n.helper),i=e(this).data("draggable").options;r.css("zIndex")&&(i._zIndex=r.css("zIndex")),r.css("zIndex",i.zIndex)},stop:function(t,n){var r=e(this).data("draggable").options;r._zIndex&&e(n.helper).css("zIndex",r._zIndex)}})})(jQuery);(function(e,t){e.widget("ui.droppable",{version:"1.9.2",widgetEventPrefix:"drop",options:{accept:"*",activeClass:!1,addClasses:!0,greedy:!1,hoverClass:!1,scope:"default",tolerance:"intersect"},_create:function(){var t=this.options,n=t.accept;this.isover=0,this.isout=1,this.accept=e.isFunction(n)?n:function(e){return e.is(n)},this.proportions={width:this.element[0].offsetWidth,height:this.element[0].offsetHeight},e.ui.ddmanager.droppables[t.scope]=e.ui.ddmanager.droppables[t.scope]||[],e.ui.ddmanager.droppables[t.scope].push(this),t.addClasses&&this.element.addClass("ui-droppable")},_destroy:function(){var t=e.ui.ddmanager.droppables[this.options.scope];for(var n=0;n<t.length;n++)t[n]==this&&t.splice(n,1);this.element.removeClass("ui-droppable ui-droppable-disabled")},_setOption:function(t,n){t=="accept"&&(this.accept=e.isFunction(n)?n:function(e){return e.is(n)}),e.Widget.prototype._setOption.apply(this,arguments)},_activate:function(t){var n=e.ui.ddmanager.current;this.options.activeClass&&this.element.addClass(this.options.activeClass),n&&this._trigger("activate",t,this.ui(n))},_deactivate:function(t){var n=e.ui.ddmanager.current;this.options.activeClass&&this.element.removeClass(this.options.activeClass),n&&this._trigger("deactivate",t,this.ui(n))},_over:function(t){var n=e.ui.ddmanager.current;if(!n||(n.currentItem||n.element)[0]==this.element[0])return;this.accept.call(this.element[0],n.currentItem||n.element)&&(this.options.hoverClass&&this.element.addClass(this.options.hoverClass),this._trigger("over",t,this.ui(n)))},_out:function(t){var n=e.ui.ddmanager.current;if(!n||(n.currentItem||n.element)[0]==this.element[0])return;this.accept.call(this.element[0],n.currentItem||n.element)&&(this.options.hoverClass&&this.element.removeClass(this.options.hoverClass),this._trigger("out",t,this.ui(n)))},_drop:function(t,n){var r=n||e.ui.ddmanager.current;if(!r||(r.currentItem||r.element)[0]==this.element[0])return!1;var i=!1;return this.element.find(":data(droppable)").not(".ui-draggable-dragging").each(function(){var t=e.data(this,"droppable");if(t.options.greedy&&!t.options.disabled&&t.options.scope==r.options.scope&&t.accept.call(t.element[0],r.currentItem||r.element)&&e.ui.intersect(r,e.extend(t,{offset:t.element.offset()}),t.options.tolerance))return i=!0,!1}),i?!1:this.accept.call(this.element[0],r.currentItem||r.element)?(this.options.activeClass&&this.element.removeClass(this.options.activeClass),this.options.hoverClass&&this.element.removeClass(this.options.hoverClass),this._trigger("drop",t,this.ui(r)),this.element):!1},ui:function(e){return{draggable:e.currentItem||e.element,helper:e.helper,position:e.position,offset:e.positionAbs}}}),e.ui.intersect=function(t,n,r){if(!n.offset)return!1;var i=(t.positionAbs||t.position.absolute).left,s=i+t.helperProportions.width,o=(t.positionAbs||t.position.absolute).top,u=o+t.helperProportions.height,a=n.offset.left,f=a+n.proportions.width,l=n.offset.top,c=l+n.proportions.height;switch(r){case"fit":return a<=i&&s<=f&&l<=o&&u<=c;case"intersect":return a<i+t.helperProportions.width/2&&s-t.helperProportions.width/2<f&&l<o+t.helperProportions.height/2&&u-t.helperProportions.height/2<c;case"pointer":var h=(t.positionAbs||t.position.absolute).left+(t.clickOffset||t.offset.click).left,p=(t.positionAbs||t.position.absolute).top+(t.clickOffset||t.offset.click).top,d=e.ui.isOver(p,h,l,a,n.proportions.height,n.proportions.width);return d;case"touch":return(o>=l&&o<=c||u>=l&&u<=c||o<l&&u>c)&&(i>=a&&i<=f||s>=a&&s<=f||i<a&&s>f);default:return!1}},e.ui.ddmanager={current:null,droppables:{"default":[]},prepareOffsets:function(t,n){var r=e.ui.ddmanager.droppables[t.options.scope]||[],i=n?n.type:null,s=(t.currentItem||t.element).find(":data(droppable)").andSelf();e:for(var o=0;o<r.length;o++){if(r[o].options.disabled||t&&!r[o].accept.call(r[o].element[0],t.currentItem||t.element))continue;for(var u=0;u<s.length;u++)if(s[u]==r[o].element[0]){r[o].proportions.height=0;continue e}r[o].visible=r[o].element.css("display")!="none";if(!r[o].visible)continue;i=="mousedown"&&r[o]._activate.call(r[o],n),r[o].offset=r[o].element.offset(),r[o].proportions={width:r[o].element[0].offsetWidth,height:r[o].element[0].offsetHeight}}},drop:function(t,n){var r=!1;return e.each(e.ui.ddmanager.droppables[t.options.scope]||[],function(){if(!this.options)return;!this.options.disabled&&this.visible&&e.ui.intersect(t,this,this.options.tolerance)&&(r=this._drop.call(this,n)||r),!this.options.disabled&&this.visible&&this.accept.call(this.element[0],t.currentItem||t.element)&&(this.isout=1,this.isover=0,this._deactivate.call(this,n))}),r},dragStart:function(t,n){t.element.parentsUntil("body").bind("scroll.droppable",function(){t.options.refreshPositions||e.ui.ddmanager.prepareOffsets(t,n)})},drag:function(t,n){t.options.refreshPositions&&e.ui.ddmanager.prepareOffsets(t,n),e.each(e.ui.ddmanager.droppables[t.options.scope]||[],function(){if(this.options.disabled||this.greedyChild||!this.visible)return;var r=e.ui.intersect(t,this,this.options.tolerance),i=!r&&this.isover==1?"isout":r&&this.isover==0?"isover":null;if(!i)return;var s;if(this.options.greedy){var o=this.options.scope,u=this.element.parents(":data(droppable)").filter(function(){return e.data(this,"droppable").options.scope===o});u.length&&(s=e.data(u[0],"droppable"),s.greedyChild=i=="isover"?1:0)}s&&i=="isover"&&(s.isover=0,s.isout=1,s._out.call(s,n)),this[i]=1,this[i=="isout"?"isover":"isout"]=0,this[i=="isover"?"_over":"_out"].call(this,n),s&&i=="isout"&&(s.isout=0,s.isover=1,s._over.call(s,n))})},dragStop:function(t,n){t.element.parentsUntil("body").unbind("scroll.droppable"),t.options.refreshPositions||e.ui.ddmanager.prepareOffsets(t,n)}}})(jQuery);(function(e,t){e.widget("ui.resizable",e.ui.mouse,{version:"1.9.2",widgetEventPrefix:"resize",options:{alsoResize:!1,animate:!1,animateDuration:"slow",animateEasing:"swing",aspectRatio:!1,autoHide:!1,containment:!1,ghost:!1,grid:!1,handles:"e,s,se",helper:!1,maxHeight:null,maxWidth:null,minHeight:10,minWidth:10,zIndex:1e3},_create:function(){var t=this,n=this.options;this.element.addClass("ui-resizable"),e.extend(this,{_aspectRatio:!!n.aspectRatio,aspectRatio:n.aspectRatio,originalElement:this.element,_proportionallyResizeElements:[],_helper:n.helper||n.ghost||n.animate?n.helper||"ui-resizable-helper":null}),this.element[0].nodeName.match(/canvas|textarea|input|select|button|img/i)&&(this.element.wrap(e('<div class="ui-wrapper" style="overflow: hidden;"></div>').css({position:this.element.css("position"),width:this.element.outerWidth(),height:this.element.outerHeight(),top:this.element.css("top"),left:this.element.css("left")})),this.element=this.element.parent().data("resizable",this.element.data("resizable")),this.elementIsWrapper=!0,this.element.css({marginLeft:this.originalElement.css("marginLeft"),marginTop:this.originalElement.css("marginTop"),marginRight:this.originalElement.css("marginRight"),marginBottom:this.originalElement.css("marginBottom")}),this.originalElement.css({marginLeft:0,marginTop:0,marginRight:0,marginBottom:0}),this.originalResizeStyle=this.originalElement.css("resize"),this.originalElement.css("resize","none"),this._proportionallyResizeElements.push(this.originalElement.css({position:"static",zoom:1,display:"block"})),this.originalElement.css({margin:this.originalElement.css("margin")}),this._proportionallyResize()),this.handles=n.handles||(e(".ui-resizable-handle",this.element).length?{n:".ui-resizable-n",e:".ui-resizable-e",s:".ui-resizable-s",w:".ui-resizable-w",se:".ui-resizable-se",sw:".ui-resizable-sw",ne:".ui-resizable-ne",nw:".ui-resizable-nw"}:"e,s,se");if(this.handles.constructor==String){this.handles=="all"&&(this.handles="n,e,s,w,se,sw,ne,nw");var r=this.handles.split(",");this.handles={};for(var i=0;i<r.length;i++){var s=e.trim(r[i]),o="ui-resizable-"+s,u=e('<div class="ui-resizable-handle '+o+'"></div>');u.css({zIndex:n.zIndex}),"se"==s&&u.addClass("ui-icon ui-icon-gripsmall-diagonal-se"),this.handles[s]=".ui-resizable-"+s,this.element.append(u)}}this._renderAxis=function(t){t=t||this.element;for(var n in this.handles){this.handles[n].constructor==String&&(this.handles[n]=e(this.handles[n],this.element).show());if(this.elementIsWrapper&&this.originalElement[0].nodeName.match(/textarea|input|select|button/i)){var r=e(this.handles[n],this.element),i=0;i=/sw|ne|nw|se|n|s/.test(n)?r.outerHeight():r.outerWidth();var s=["padding",/ne|nw|n/.test(n)?"Top":/se|sw|s/.test(n)?"Bottom":/^e$/.test(n)?"Right":"Left"].join("");t.css(s,i),this._proportionallyResize()}if(!e(this.handles[n]).length)continue}},this._renderAxis(this.element),this._handles=e(".ui-resizable-handle",this.element).disableSelection(),this._handles.mouseover(function(){if(!t.resizing){if(this.className)var e=this.className.match(/ui-resizable-(se|sw|ne|nw|n|e|s|w)/i);t.axis=e&&e[1]?e[1]:"se"}}),n.autoHide&&(this._handles.hide(),e(this.element).addClass("ui-resizable-autohide").mouseenter(function(){if(n.disabled)return;e(this).removeClass("ui-resizable-autohide"),t._handles.show()}).mouseleave(function(){if(n.disabled)return;t.resizing||(e(this).addClass("ui-resizable-autohide"),t._handles.hide())})),this._mouseInit()},_destroy:function(){this._mouseDestroy();var t=function(t){e(t).removeClass("ui-resizable ui-resizable-disabled ui-resizable-resizing").removeData("resizable").removeData("ui-resizable").unbind(".resizable").find(".ui-resizable-handle").remove()};if(this.elementIsWrapper){t(this.element);var n=this.element;this.originalElement.css({position:n.css("position"),width:n.outerWidth(),height:n.outerHeight(),top:n.css("top"),left:n.css("left")}).insertAfter(n),n.remove()}return this.originalElement.css("resize",this.originalResizeStyle),t(this.originalElement),this},_mouseCapture:function(t){var n=!1;for(var r in this.handles)e(this.handles[r])[0]==t.target&&(n=!0);return!this.options.disabled&&n},_mouseStart:function(t){var r=this.options,i=this.element.position(),s=this.element;this.resizing=!0,this.documentScroll={top:e(document).scrollTop(),left:e(document).scrollLeft()},(s.is(".ui-draggable")||/absolute/.test(s.css("position")))&&s.css({position:"absolute",top:i.top,left:i.left}),this._renderProxy();var o=n(this.helper.css("left")),u=n(this.helper.css("top"));r.containment&&(o+=e(r.containment).scrollLeft()||0,u+=e(r.containment).scrollTop()||0),this.offset=this.helper.offset(),this.position={left:o,top:u},this.size=this._helper?{width:s.outerWidth(),height:s.outerHeight()}:{width:s.width(),height:s.height()},this.originalSize=this._helper?{width:s.outerWidth(),height:s.outerHeight()}:{width:s.width(),height:s.height()},this.originalPosition={left:o,top:u},this.sizeDiff={width:s.outerWidth()-s.width(),height:s.outerHeight()-s.height()},this.originalMousePosition={left:t.pageX,top:t.pageY},this.aspectRatio=typeof r.aspectRatio=="number"?r.aspectRatio:this.originalSize.width/this.originalSize.height||1;var a=e(".ui-resizable-"+this.axis).css("cursor");return e("body").css("cursor",a=="auto"?this.axis+"-resize":a),s.addClass("ui-resizable-resizing"),this._propagate("start",t),!0},_mouseDrag:function(e){var t=this.helper,n=this.options,r={},i=this,s=this.originalMousePosition,o=this.axis,u=e.pageX-s.left||0,a=e.pageY-s.top||0,f=this._change[o];if(!f)return!1;var l=f.apply(this,[e,u,a]);this._updateVirtualBoundaries(e.shiftKey);if(this._aspectRatio||e.shiftKey)l=this._updateRatio(l,e);return l=this._respectSize(l,e),this._propagate("resize",e),t.css({top:this.position.top+"px",left:this.position.left+"px",width:this.size.width+"px",height:this.size.height+"px"}),!this._helper&&this._proportionallyResizeElements.length&&this._proportionallyResize(),this._updateCache(l),this._trigger("resize",e,this.ui()),!1},_mouseStop:function(t){this.resizing=!1;var n=this.options,r=this;if(this._helper){var i=this._proportionallyResizeElements,s=i.length&&/textarea/i.test(i[0].nodeName),o=s&&e.ui.hasScroll(i[0],"left")?0:r.sizeDiff.height,u=s?0:r.sizeDiff.width,a={width:r.helper.width()-u,height:r.helper.height()-o},f=parseInt(r.element.css("left"),10)+(r.position.left-r.originalPosition.left)||null,l=parseInt(r.element.css("top"),10)+(r.position.top-r.originalPosition.top)||null;n.animate||this.element.css(e.extend(a,{top:l,left:f})),r.helper.height(r.size.height),r.helper.width(r.size.width),this._helper&&!n.animate&&this._proportionallyResize()}return e("body").css("cursor","auto"),this.element.removeClass("ui-resizable-resizing"),this._propagate("stop",t),this._helper&&this.helper.remove(),!1},_updateVirtualBoundaries:function(e){var t=this.options,n,i,s,o,u;u={minWidth:r(t.minWidth)?t.minWidth:0,maxWidth:r(t.maxWidth)?t.maxWidth:Infinity,minHeight:r(t.minHeight)?t.minHeight:0,maxHeight:r(t.maxHeight)?t.maxHeight:Infinity};if(this._aspectRatio||e)n=u.minHeight*this.aspectRatio,s=u.minWidth/this.aspectRatio,i=u.maxHeight*this.aspectRatio,o=u.maxWidth/this.aspectRatio,n>u.minWidth&&(u.minWidth=n),s>u.minHeight&&(u.minHeight=s),i<u.maxWidth&&(u.maxWidth=i),o<u.maxHeight&&(u.maxHeight=o);this._vBoundaries=u},_updateCache:function(e){var t=this.options;this.offset=this.helper.offset(),r(e.left)&&(this.position.left=e.left),r(e.top)&&(this.position.top=e.top),r(e.height)&&(this.size.height=e.height),r(e.width)&&(this.size.width=e.width)},_updateRatio:function(e,t){var n=this.options,i=this.position,s=this.size,o=this.axis;return r(e.height)?e.width=e.height*this.aspectRatio:r(e.width)&&(e.height=e.width/this.aspectRatio),o=="sw"&&(e.left=i.left+(s.width-e.width),e.top=null),o=="nw"&&(e.top=i.top+(s.height-e.height),e.left=i.left+(s.width-e.width)),e},_respectSize:function(e,t){var n=this.helper,i=this._vBoundaries,s=this._aspectRatio||t.shiftKey,o=this.axis,u=r(e.width)&&i.maxWidth&&i.maxWidth<e.width,a=r(e.height)&&i.maxHeight&&i.maxHeight<e.height,f=r(e.width)&&i.minWidth&&i.minWidth>e.width,l=r(e.height)&&i.minHeight&&i.minHeight>e.height;f&&(e.width=i.minWidth),l&&(e.height=i.minHeight),u&&(e.width=i.maxWidth),a&&(e.height=i.maxHeight);var c=this.originalPosition.left+this.originalSize.width,h=this.position.top+this.size.height,p=/sw|nw|w/.test(o),d=/nw|ne|n/.test(o);f&&p&&(e.left=c-i.minWidth),u&&p&&(e.left=c-i.maxWidth),l&&d&&(e.top=h-i.minHeight),a&&d&&(e.top=h-i.maxHeight);var v=!e.width&&!e.height;return v&&!e.left&&e.top?e.top=null:v&&!e.top&&e.left&&(e.left=null),e},_proportionallyResize:function(){var t=this.options;if(!this._proportionallyResizeElements.length)return;var n=this.helper||this.element;for(var r=0;r<this._proportionallyResizeElements.length;r++){var i=this._proportionallyResizeElements[r];if(!this.borderDif){var s=[i.css("borderTopWidth"),i.css("borderRightWidth"),i.css("borderBottomWidth"),i.css("borderLeftWidth")],o=[i.css("paddingTop"),i.css("paddingRight"),i.css("paddingBottom"),i.css("paddingLeft")];this.borderDif=e.map(s,function(e,t){var n=parseInt(e,10)||0,r=parseInt(o[t],10)||0;return n+r})}i.css({height:n.height()-this.borderDif[0]-this.borderDif[2]||0,width:n.width()-this.borderDif[1]-this.borderDif[3]||0})}},_renderProxy:function(){var t=this.element,n=this.options;this.elementOffset=t.offset();if(this._helper){this.helper=this.helper||e('<div style="overflow:hidden;"></div>');var r=e.ui.ie6?1:0,i=e.ui.ie6?2:-1;this.helper.addClass(this._helper).css({width:this.element.outerWidth()+i,height:this.element.outerHeight()+i,position:"absolute",left:this.elementOffset.left-r+"px",top:this.elementOffset.top-r+"px",zIndex:++n.zIndex}),this.helper.appendTo("body").disableSelection()}else this.helper=this.element},_change:{e:function(e,t,n){return{width:this.originalSize.width+t}},w:function(e,t,n){var r=this.options,i=this.originalSize,s=this.originalPosition;return{left:s.left+t,width:i.width-t}},n:function(e,t,n){var r=this.options,i=this.originalSize,s=this.originalPosition;return{top:s.top+n,height:i.height-n}},s:function(e,t,n){return{height:this.originalSize.height+n}},se:function(t,n,r){return e.extend(this._change.s.apply(this,arguments),this._change.e.apply(this,[t,n,r]))},sw:function(t,n,r){return e.extend(this._change.s.apply(this,arguments),this._change.w.apply(this,[t,n,r]))},ne:function(t,n,r){return e.extend(this._change.n.apply(this,arguments),this._change.e.apply(this,[t,n,r]))},nw:function(t,n,r){return e.extend(this._change.n.apply(this,arguments),this._change.w.apply(this,[t,n,r]))}},_propagate:function(t,n){e.ui.plugin.call(this,t,[n,this.ui()]),t!="resize"&&this._trigger(t,n,this.ui())},plugins:{},ui:function(){return{originalElement:this.originalElement,element:this.element,helper:this.helper,position:this.position,size:this.size,originalSize:this.originalSize,originalPosition:this.originalPosition}}}),e.ui.plugin.add("resizable","alsoResize",{start:function(t,n){var r=e(this).data("resizable"),i=r.options,s=function(t){e(t).each(function(){var t=e(this);t.data("resizable-alsoresize",{width:parseInt(t.width(),10),height:parseInt(t.height(),10),left:parseInt(t.css("left"),10),top:parseInt(t.css("top"),10)})})};typeof i.alsoResize=="object"&&!i.alsoResize.parentNode?i.alsoResize.length?(i.alsoResize=i.alsoResize[0],s(i.alsoResize)):e.each(i.alsoResize,function(e){s(e)}):s(i.alsoResize)},resize:function(t,n){var r=e(this).data("resizable"),i=r.options,s=r.originalSize,o=r.originalPosition,u={height:r.size.height-s.height||0,width:r.size.width-s.width||0,top:r.position.top-o.top||0,left:r.position.left-o.left||0},a=function(t,r){e(t).each(function(){var t=e(this),i=e(this).data("resizable-alsoresize"),s={},o=r&&r.length?r:t.parents(n.originalElement[0]).length?["width","height"]:["width","height","top","left"];e.each(o,function(e,t){var n=(i[t]||0)+(u[t]||0);n&&n>=0&&(s[t]=n||null)}),t.css(s)})};typeof i.alsoResize=="object"&&!i.alsoResize.nodeType?e.each(i.alsoResize,function(e,t){a(e,t)}):a(i.alsoResize)},stop:function(t,n){e(this).removeData("resizable-alsoresize")}}),e.ui.plugin.add("resizable","animate",{stop:function(t,n){var r=e(this).data("resizable"),i=r.options,s=r._proportionallyResizeElements,o=s.length&&/textarea/i.test(s[0].nodeName),u=o&&e.ui.hasScroll(s[0],"left")?0:r.sizeDiff.height,a=o?0:r.sizeDiff.width,f={width:r.size.width-a,height:r.size.height-u},l=parseInt(r.element.css("left"),10)+(r.position.left-r.originalPosition.left)||null,c=parseInt(r.element.css("top"),10)+(r.position.top-r.originalPosition.top)||null;r.element.animate(e.extend(f,c&&l?{top:c,left:l}:{}),{duration:i.animateDuration,easing:i.animateEasing,step:function(){var n={width:parseInt(r.element.css("width"),10),height:parseInt(r.element.css("height"),10),top:parseInt(r.element.css("top"),10),left:parseInt(r.element.css("left"),10)};s&&s.length&&e(s[0]).css({width:n.width,height:n.height}),r._updateCache(n),r._propagate("resize",t)}})}}),e.ui.plugin.add("resizable","containment",{start:function(t,r){var i=e(this).data("resizable"),s=i.options,o=i.element,u=s.containment,a=u instanceof e?u.get(0):/parent/.test(u)?o.parent().get(0):u;if(!a)return;i.containerElement=e(a);if(/document/.test(u)||u==document)i.containerOffset={left:0,top:0},i.containerPosition={left:0,top:0},i.parentData={element:e(document),left:0,top:0,width:e(document).width(),height:e(document).height()||document.body.parentNode.scrollHeight};else{var f=e(a),l=[];e(["Top","Right","Left","Bottom"]).each(function(e,t){l[e]=n(f.css("padding"+t))}),i.containerOffset=f.offset(),i.containerPosition=f.position(),i.containerSize={height:f.innerHeight()-l[3],width:f.innerWidth()-l[1]};var c=i.containerOffset,h=i.containerSize.height,p=i.containerSize.width,d=e.ui.hasScroll(a,"left")?a.scrollWidth:p,v=e.ui.hasScroll(a)?a.scrollHeight:h;i.parentData={element:a,left:c.left,top:c.top,width:d,height:v}}},resize:function(t,n){var r=e(this).data("resizable"),i=r.options,s=r.containerSize,o=r.containerOffset,u=r.size,a=r.position,f=r._aspectRatio||t.shiftKey,l={top:0,left:0},c=r.containerElement;c[0]!=document&&/static/.test(c.css("position"))&&(l=o),a.left<(r._helper?o.left:0)&&(r.size.width=r.size.width+(r._helper?r.position.left-o.left:r.position.left-l.left),f&&(r.size.height=r.size.width/r.aspectRatio),r.position.left=i.helper?o.left:0),a.top<(r._helper?o.top:0)&&(r.size.height=r.size.height+(r._helper?r.position.top-o.top:r.position.top),f&&(r.size.width=r.size.height*r.aspectRatio),r.position.top=r._helper?o.top:0),r.offset.left=r.parentData.left+r.position.left,r.offset.top=r.parentData.top+r.position.top;var h=Math.abs((r._helper?r.offset.left-l.left:r.offset.left-l.left)+r.sizeDiff.width),p=Math.abs((r._helper?r.offset.top-l.top:r.offset.top-o.top)+r.sizeDiff.height),d=r.containerElement.get(0)==r.element.parent().get(0),v=/relative|absolute/.test(r.containerElement.css("position"));d&&v&&(h-=r.parentData.left),h+r.size.width>=r.parentData.width&&(r.size.width=r.parentData.width-h,f&&(r.size.height=r.size.width/r.aspectRatio)),p+r.size.height>=r.parentData.height&&(r.size.height=r.parentData.height-p,f&&(r.size.width=r.size.height*r.aspectRatio))},stop:function(t,n){var r=e(this).data("resizable"),i=r.options,s=r.position,o=r.containerOffset,u=r.containerPosition,a=r.containerElement,f=e(r.helper),l=f.offset(),c=f.outerWidth()-r.sizeDiff.width,h=f.outerHeight()-r.sizeDiff.height;r._helper&&!i.animate&&/relative/.test(a.css("position"))&&e(this).css({left:l.left-u.left-o.left,width:c,height:h}),r._helper&&!i.animate&&/static/.test(a.css("position"))&&e(this).css({left:l.left-u.left-o.left,width:c,height:h})}}),e.ui.plugin.add("resizable","ghost",{start:function(t,n){var r=e(this).data("resizable"),i=r.options,s=r.size;r.ghost=r.originalElement.clone(),r.ghost.css({opacity:.25,display:"block",position:"relative",height:s.height,width:s.width,margin:0,left:0,top:0}).addClass("ui-resizable-ghost").addClass(typeof i.ghost=="string"?i.ghost:""),r.ghost.appendTo(r.helper)},resize:function(t,n){var r=e(this).data("resizable"),i=r.options;r.ghost&&r.ghost.css({position:"relative",height:r.size.height,width:r.size.width})},stop:function(t,n){var r=e(this).data("resizable"),i=r.options;r.ghost&&r.helper&&r.helper.get(0).removeChild(r.ghost.get(0))}}),e.ui.plugin.add("resizable","grid",{resize:function(t,n){var r=e(this).data("resizable"),i=r.options,s=r.size,o=r.originalSize,u=r.originalPosition,a=r.axis,f=i._aspectRatio||t.shiftKey;i.grid=typeof i.grid=="number"?[i.grid,i.grid]:i.grid;var l=Math.round((s.width-o.width)/(i.grid[0]||1))*(i.grid[0]||1),c=Math.round((s.height-o.height)/(i.grid[1]||1))*(i.grid[1]||1);/^(se|s|e)$/.test(a)?(r.size.width=o.width+l,r.size.height=o.height+c):/^(ne)$/.test(a)?(r.size.width=o.width+l,r.size.height=o.height+c,r.position.top=u.top-c):/^(sw)$/.test(a)?(r.size.width=o.width+l,r.size.height=o.height+c,r.position.left=u.left-l):(r.size.width=o.width+l,r.size.height=o.height+c,r.position.top=u.top-c,r.position.left=u.left-l)}});var n=function(e){return parseInt(e,10)||0},r=function(e){return!isNaN(parseInt(e,10))}})(jQuery);(function(e,t){e.widget("ui.selectable",e.ui.mouse,{version:"1.9.2",options:{appendTo:"body",autoRefresh:!0,distance:0,filter:"*",tolerance:"touch"},_create:function(){var t=this;this.element.addClass("ui-selectable"),this.dragged=!1;var n;this.refresh=function(){n=e(t.options.filter,t.element[0]),n.addClass("ui-selectee"),n.each(function(){var t=e(this),n=t.offset();e.data(this,"selectable-item",{element:this,$element:t,left:n.left,top:n.top,right:n.left+t.outerWidth(),bottom:n.top+t.outerHeight(),startselected:!1,selected:t.hasClass("ui-selected"),selecting:t.hasClass("ui-selecting"),unselecting:t.hasClass("ui-unselecting")})})},this.refresh(),this.selectees=n.addClass("ui-selectee"),this._mouseInit(),this.helper=e("<div class='ui-selectable-helper'></div>")},_destroy:function(){this.selectees.removeClass("ui-selectee").removeData("selectable-item"),this.element.removeClass("ui-selectable ui-selectable-disabled"),this._mouseDestroy()},_mouseStart:function(t){var n=this;this.opos=[t.pageX,t.pageY];if(this.options.disabled)return;var r=this.options;this.selectees=e(r.filter,this.element[0]),this._trigger("start",t),e(r.appendTo).append(this.helper),this.helper.css({left:t.clientX,top:t.clientY,width:0,height:0}),r.autoRefresh&&this.refresh(),this.selectees.filter(".ui-selected").each(function(){var r=e.data(this,"selectable-item");r.startselected=!0,!t.metaKey&&!t.ctrlKey&&(r.$element.removeClass("ui-selected"),r.selected=!1,r.$element.addClass("ui-unselecting"),r.unselecting=!0,n._trigger("unselecting",t,{unselecting:r.element}))}),e(t.target).parents().andSelf().each(function(){var r=e.data(this,"selectable-item");if(r){var i=!t.metaKey&&!t.ctrlKey||!r.$element.hasClass("ui-selected");return r.$element.removeClass(i?"ui-unselecting":"ui-selected").addClass(i?"ui-selecting":"ui-unselecting"),r.unselecting=!i,r.selecting=i,r.selected=i,i?n._trigger("selecting",t,{selecting:r.element}):n._trigger("unselecting",t,{unselecting:r.element}),!1}})},_mouseDrag:function(t){var n=this;this.dragged=!0;if(this.options.disabled)return;var r=this.options,i=this.opos[0],s=this.opos[1],o=t.pageX,u=t.pageY;if(i>o){var a=o;o=i,i=a}if(s>u){var a=u;u=s,s=a}return this.helper.css({left:i,top:s,width:o-i,height:u-s}),this.selectees.each(function(){var a=e.data(this,"selectable-item");if(!a||a.element==n.element[0])return;var f=!1;r.tolerance=="touch"?f=!(a.left>o||a.right<i||a.top>u||a.bottom<s):r.tolerance=="fit"&&(f=a.left>i&&a.right<o&&a.top>s&&a.bottom<u),f?(a.selected&&(a.$element.removeClass("ui-selected"),a.selected=!1),a.unselecting&&(a.$element.removeClass("ui-unselecting"),a.unselecting=!1),a.selecting||(a.$element.addClass("ui-selecting"),a.selecting=!0,n._trigger("selecting",t,{selecting:a.element}))):(a.selecting&&((t.metaKey||t.ctrlKey)&&a.startselected?(a.$element.removeClass("ui-selecting"),a.selecting=!1,a.$element.addClass("ui-selected"),a.selected=!0):(a.$element.removeClass("ui-selecting"),a.selecting=!1,a.startselected&&(a.$element.addClass("ui-unselecting"),a.unselecting=!0),n._trigger("unselecting",t,{unselecting:a.element}))),a.selected&&!t.metaKey&&!t.ctrlKey&&!a.startselected&&(a.$element.removeClass("ui-selected"),a.selected=!1,a.$element.addClass("ui-unselecting"),a.unselecting=!0,n._trigger("unselecting",t,{unselecting:a.element})))}),!1},_mouseStop:function(t){var n=this;this.dragged=!1;var r=this.options;return e(".ui-unselecting",this.element[0]).each(function(){var r=e.data(this,"selectable-item");r.$element.removeClass("ui-unselecting"),r.unselecting=!1,r.startselected=!1,n._trigger("unselected",t,{unselected:r.element})}),e(".ui-selecting",this.element[0]).each(function(){var r=e.data(this,"selectable-item");r.$element.removeClass("ui-selecting").addClass("ui-selected"),r.selecting=!1,r.selected=!0,r.startselected=!0,n._trigger("selected",t,{selected:r.element})}),this._trigger("stop",t),this.helper.remove(),!1}})})(jQuery);(function(e,t){e.widget("ui.sortable",e.ui.mouse,{version:"1.9.2",widgetEventPrefix:"sort",ready:!1,options:{appendTo:"parent",axis:!1,connectWith:!1,containment:!1,cursor:"auto",cursorAt:!1,dropOnEmpty:!0,forcePlaceholderSize:!1,forceHelperSize:!1,grid:!1,handle:!1,helper:"original",items:"> *",opacity:!1,placeholder:!1,revert:!1,scroll:!0,scrollSensitivity:20,scrollSpeed:20,scope:"default",tolerance:"intersect",zIndex:1e3},_create:function(){var e=this.options;this.containerCache={},this.element.addClass("ui-sortable"),this.refresh(),this.floating=this.items.length?e.axis==="x"||/left|right/.test(this.items[0].item.css("float"))||/inline|table-cell/.test(this.items[0].item.css("display")):!1,this.offset=this.element.offset(),this._mouseInit(),this.ready=!0},_destroy:function(){this.element.removeClass("ui-sortable ui-sortable-disabled"),this._mouseDestroy();for(var e=this.items.length-1;e>=0;e--)this.items[e].item.removeData(this.widgetName+"-item");return this},_setOption:function(t,n){t==="disabled"?(this.options[t]=n,this.widget().toggleClass("ui-sortable-disabled",!!n)):e.Widget.prototype._setOption.apply(this,arguments)},_mouseCapture:function(t,n){var r=this;if(this.reverting)return!1;if(this.options.disabled||this.options.type=="static")return!1;this._refreshItems(t);var i=null,s=e(t.target).parents().each(function(){if(e.data(this,r.widgetName+"-item")==r)return i=e(this),!1});e.data(t.target,r.widgetName+"-item")==r&&(i=e(t.target));if(!i)return!1;if(this.options.handle&&!n){var o=!1;e(this.options.handle,i).find("*").andSelf().each(function(){this==t.target&&(o=!0)});if(!o)return!1}return this.currentItem=i,this._removeCurrentsFromItems(),!0},_mouseStart:function(t,n,r){var i=this.options;this.currentContainer=this,this.refreshPositions(),this.helper=this._createHelper(t),this._cacheHelperProportions(),this._cacheMargins(),this.scrollParent=this.helper.scrollParent(),this.offset=this.currentItem.offset(),this.offset={top:this.offset.top-this.margins.top,left:this.offset.left-this.margins.left},e.extend(this.offset,{click:{left:t.pageX-this.offset.left,top:t.pageY-this.offset.top},parent:this._getParentOffset(),relative:this._getRelativeOffset()}),this.helper.css("position","absolute"),this.cssPosition=this.helper.css("position"),this.originalPosition=this._generatePosition(t),this.originalPageX=t.pageX,this.originalPageY=t.pageY,i.cursorAt&&this._adjustOffsetFromHelper(i.cursorAt),this.domPosition={prev:this.currentItem.prev()[0],parent:this.currentItem.parent()[0]},this.helper[0]!=this.currentItem[0]&&this.currentItem.hide(),this._createPlaceholder(),i.containment&&this._setContainment(),i.cursor&&(e("body").css("cursor")&&(this._storedCursor=e("body").css("cursor")),e("body").css("cursor",i.cursor)),i.opacity&&(this.helper.css("opacity")&&(this._storedOpacity=this.helper.css("opacity")),this.helper.css("opacity",i.opacity)),i.zIndex&&(this.helper.css("zIndex")&&(this._storedZIndex=this.helper.css("zIndex")),this.helper.css("zIndex",i.zIndex)),this.scrollParent[0]!=document&&this.scrollParent[0].tagName!="HTML"&&(this.overflowOffset=this.scrollParent.offset()),this._trigger("start",t,this._uiHash()),this._preserveHelperProportions||this._cacheHelperProportions();if(!r)for(var s=this.containers.length-1;s>=0;s--)this.containers[s]._trigger("activate",t,this._uiHash(this));return e.ui.ddmanager&&(e.ui.ddmanager.current=this),e.ui.ddmanager&&!i.dropBehaviour&&e.ui.ddmanager.prepareOffsets(this,t),this.dragging=!0,this.helper.addClass("ui-sortable-helper"),this._mouseDrag(t),!0},_mouseDrag:function(t){this.position=this._generatePosition(t),this.positionAbs=this._convertPositionTo("absolute"),this.lastPositionAbs||(this.lastPositionAbs=this.positionAbs);if(this.options.scroll){var n=this.options,r=!1;this.scrollParent[0]!=document&&this.scrollParent[0].tagName!="HTML"?(this.overflowOffset.top+this.scrollParent[0].offsetHeight-t.pageY<n.scrollSensitivity?this.scrollParent[0].scrollTop=r=this.scrollParent[0].scrollTop+n.scrollSpeed:t.pageY-this.overflowOffset.top<n.scrollSensitivity&&(this.scrollParent[0].scrollTop=r=this.scrollParent[0].scrollTop-n.scrollSpeed),this.overflowOffset.left+this.scrollParent[0].offsetWidth-t.pageX<n.scrollSensitivity?this.scrollParent[0].scrollLeft=r=this.scrollParent[0].scrollLeft+n.scrollSpeed:t.pageX-this.overflowOffset.left<n.scrollSensitivity&&(this.scrollParent[0].scrollLeft=r=this.scrollParent[0].scrollLeft-n.scrollSpeed)):(t.pageY-e(document).scrollTop()<n.scrollSensitivity?r=e(document).scrollTop(e(document).scrollTop()-n.scrollSpeed):e(window).height()-(t.pageY-e(document).scrollTop())<n.scrollSensitivity&&(r=e(document).scrollTop(e(document).scrollTop()+n.scrollSpeed)),t.pageX-e(document).scrollLeft()<n.scrollSensitivity?r=e(document).scrollLeft(e(document).scrollLeft()-n.scrollSpeed):e(window).width()-(t.pageX-e(document).scrollLeft())<n.scrollSensitivity&&(r=e(document).scrollLeft(e(document).scrollLeft()+n.scrollSpeed))),r!==!1&&e.ui.ddmanager&&!n.dropBehaviour&&e.ui.ddmanager.prepareOffsets(this,t)}this.positionAbs=this._convertPositionTo("absolute");if(!this.options.axis||this.options.axis!="y")this.helper[0].style.left=this.position.left+"px";if(!this.options.axis||this.options.axis!="x")this.helper[0].style.top=this.position.top+"px";for(var i=this.items.length-1;i>=0;i--){var s=this.items[i],o=s.item[0],u=this._intersectsWithPointer(s);if(!u)continue;if(s.instance!==this.currentContainer)continue;if(o!=this.currentItem[0]&&this.placeholder[u==1?"next":"prev"]()[0]!=o&&!e.contains(this.placeholder[0],o)&&(this.options.type=="semi-dynamic"?!e.contains(this.element[0],o):!0)){this.direction=u==1?"down":"up";if(this.options.tolerance!="pointer"&&!this._intersectsWithSides(s))break;this._rearrange(t,s),this._trigger("change",t,this._uiHash());break}}return this._contactContainers(t),e.ui.ddmanager&&e.ui.ddmanager.drag(this,t),this._trigger("sort",t,this._uiHash()),this.lastPositionAbs=this.positionAbs,!1},_mouseStop:function(t,n){if(!t)return;e.ui.ddmanager&&!this.options.dropBehaviour&&e.ui.ddmanager.drop(this,t);if(this.options.revert){var r=this,i=this.placeholder.offset();this.reverting=!0,e(this.helper).animate({left:i.left-this.offset.parent.left-this.margins.left+(this.offsetParent[0]==document.body?0:this.offsetParent[0].scrollLeft),top:i.top-this.offset.parent.top-this.margins.top+(this.offsetParent[0]==document.body?0:this.offsetParent[0].scrollTop)},parseInt(this.options.revert,10)||500,function(){r._clear(t)})}else this._clear(t,n);return!1},cancel:function(){if(this.dragging){this._mouseUp({target:null}),this.options.helper=="original"?this.currentItem.css(this._storedCSS).removeClass("ui-sortable-helper"):this.currentItem.show();for(var t=this.containers.length-1;t>=0;t--)this.containers[t]._trigger("deactivate",null,this._uiHash(this)),this.containers[t].containerCache.over&&(this.containers[t]._trigger("out",null,this._uiHash(this)),this.containers[t].containerCache.over=0)}return this.placeholder&&(this.placeholder[0].parentNode&&this.placeholder[0].parentNode.removeChild(this.placeholder[0]),this.options.helper!="original"&&this.helper&&this.helper[0].parentNode&&this.helper.remove(),e.extend(this,{helper:null,dragging:!1,reverting:!1,_noFinalSort:null}),this.domPosition.prev?e(this.domPosition.prev).after(this.currentItem):e(this.domPosition.parent).prepend(this.currentItem)),this},serialize:function(t){var n=this._getItemsAsjQuery(t&&t.connected),r=[];return t=t||{},e(n).each(function(){var n=(e(t.item||this).attr(t.attribute||"id")||"").match(t.expression||/(.+)[-=_](.+)/);n&&r.push((t.key||n[1]+"[]")+"="+(t.key&&t.expression?n[1]:n[2]))}),!r.length&&t.key&&r.push(t.key+"="),r.join("&")},toArray:function(t){var n=this._getItemsAsjQuery(t&&t.connected),r=[];return t=t||{},n.each(function(){r.push(e(t.item||this).attr(t.attribute||"id")||"")}),r},_intersectsWith:function(e){var t=this.positionAbs.left,n=t+this.helperProportions.width,r=this.positionAbs.top,i=r+this.helperProportions.height,s=e.left,o=s+e.width,u=e.top,a=u+e.height,f=this.offset.click.top,l=this.offset.click.left,c=r+f>u&&r+f<a&&t+l>s&&t+l<o;return this.options.tolerance=="pointer"||this.options.forcePointerForContainers||this.options.tolerance!="pointer"&&this.helperProportions[this.floating?"width":"height"]>e[this.floating?"width":"height"]?c:s<t+this.helperProportions.width/2&&n-this.helperProportions.width/2<o&&u<r+this.helperProportions.height/2&&i-this.helperProportions.height/2<a},_intersectsWithPointer:function(t){var n=this.options.axis==="x"||e.ui.isOverAxis(this.positionAbs.top+this.offset.click.top,t.top,t.height),r=this.options.axis==="y"||e.ui.isOverAxis(this.positionAbs.left+this.offset.click.left,t.left,t.width),i=n&&r,s=this._getDragVerticalDirection(),o=this._getDragHorizontalDirection();return i?this.floating?o&&o=="right"||s=="down"?2:1:s&&(s=="down"?2:1):!1},_intersectsWithSides:function(t){var n=e.ui.isOverAxis(this.positionAbs.top+this.offset.click.top,t.top+t.height/2,t.height),r=e.ui.isOverAxis(this.positionAbs.left+this.offset.click.left,t.left+t.width/2,t.width),i=this._getDragVerticalDirection(),s=this._getDragHorizontalDirection();return this.floating&&s?s=="right"&&r||s=="left"&&!r:i&&(i=="down"&&n||i=="up"&&!n)},_getDragVerticalDirection:function(){var e=this.positionAbs.top-this.lastPositionAbs.top;return e!=0&&(e>0?"down":"up")},_getDragHorizontalDirection:function(){var e=this.positionAbs.left-this.lastPositionAbs.left;return e!=0&&(e>0?"right":"left")},refresh:function(e){return this._refreshItems(e),this.refreshPositions(),this},_connectWith:function(){var e=this.options;return e.connectWith.constructor==String?[e.connectWith]:e.connectWith},_getItemsAsjQuery:function(t){var n=[],r=[],i=this._connectWith();if(i&&t)for(var s=i.length-1;s>=0;s--){var o=e(i[s]);for(var u=o.length-1;u>=0;u--){var a=e.data(o[u],this.widgetName);a&&a!=this&&!a.options.disabled&&r.push([e.isFunction(a.options.items)?a.options.items.call(a.element):e(a.options.items,a.element).not(".ui-sortable-helper").not(".ui-sortable-placeholder"),a])}}r.push([e.isFunction(this.options.items)?this.options.items.call(this.element,null,{options:this.options,item:this.currentItem}):e(this.options.items,this.element).not(".ui-sortable-helper").not(".ui-sortable-placeholder"),this]);for(var s=r.length-1;s>=0;s--)r[s][0].each(function(){n.push(this)});return e(n)},_removeCurrentsFromItems:function(){var t=this.currentItem.find(":data("+this.widgetName+"-item)");this.items=e.grep(this.items,function(e){for(var n=0;n<t.length;n++)if(t[n]==e.item[0])return!1;return!0})},_refreshItems:function(t){this.items=[],this.containers=[this];var n=this.items,r=[[e.isFunction(this.options.items)?this.options.items.call(this.element[0],t,{item:this.currentItem}):e(this.options.items,this.element),this]],i=this._connectWith();if(i&&this.ready)for(var s=i.length-1;s>=0;s--){var o=e(i[s]);for(var u=o.length-1;u>=0;u--){var a=e.data(o[u],this.widgetName);a&&a!=this&&!a.options.disabled&&(r.push([e.isFunction(a.options.items)?a.options.items.call(a.element[0],t,{item:this.currentItem}):e(a.options.items,a.element),a]),this.containers.push(a))}}for(var s=r.length-1;s>=0;s--){var f=r[s][1],l=r[s][0];for(var u=0,c=l.length;u<c;u++){var h=e(l[u]);h.data(this.widgetName+"-item",f),n.push({item:h,instance:f,width:0,height:0,left:0,top:0})}}},refreshPositions:function(t){this.offsetParent&&this.helper&&(this.offset.parent=this._getParentOffset());for(var n=this.items.length-1;n>=0;n--){var r=this.items[n];if(r.instance!=this.currentContainer&&this.currentContainer&&r.item[0]!=this.currentItem[0])continue;var i=this.options.toleranceElement?e(this.options.toleranceElement,r.item):r.item;t||(r.width=i.outerWidth(),r.height=i.outerHeight());var s=i.offset();r.left=s.left,r.top=s.top}if(this.options.custom&&this.options.custom.refreshContainers)this.options.custom.refreshContainers.call(this);else for(var n=this.containers.length-1;n>=0;n--){var s=this.containers[n].element.offset();this.containers[n].containerCache.left=s.left,this.containers[n].containerCache.top=s.top,this.containers[n].containerCache.width=this.containers[n].element.outerWidth(),this.containers[n].containerCache.height=this.containers[n].element.outerHeight()}return this},_createPlaceholder:function(t){t=t||this;var n=t.options;if(!n.placeholder||n.placeholder.constructor==String){var r=n.placeholder;n.placeholder={element:function(){var n=e(document.createElement(t.currentItem[0].nodeName)).addClass(r||t.currentItem[0].className+" ui-sortable-placeholder").removeClass("ui-sortable-helper")[0];return r||(n.style.visibility="hidden"),n},update:function(e,i){if(r&&!n.forcePlaceholderSize)return;i.height()||i.height(t.currentItem.innerHeight()-parseInt(t.currentItem.css("paddingTop")||0,10)-parseInt(t.currentItem.css("paddingBottom")||0,10)),i.width()||i.width(t.currentItem.innerWidth()-parseInt(t.currentItem.css("paddingLeft")||0,10)-parseInt(t.currentItem.css("paddingRight")||0,10))}}}t.placeholder=e(n.placeholder.element.call(t.element,t.currentItem)),t.currentItem.after(t.placeholder),n.placeholder.update(t,t.placeholder)},_contactContainers:function(t){var n=null,r=null;for(var i=this.containers.length-1;i>=0;i--){if(e.contains(this.currentItem[0],this.containers[i].element[0]))continue;if(this._intersectsWith(this.containers[i].containerCache)){if(n&&e.contains(this.containers[i].element[0],n.element[0]))continue;n=this.containers[i],r=i}else this.containers[i].containerCache.over&&(this.containers[i]._trigger("out",t,this._uiHash(this)),this.containers[i].containerCache.over=0)}if(!n)return;if(this.containers.length===1)this.containers[r]._trigger("over",t,this._uiHash(this)),this.containers[r].containerCache.over=1;else{var s=1e4,o=null,u=this.containers[r].floating?"left":"top",a=this.containers[r].floating?"width":"height",f=this.positionAbs[u]+this.offset.click[u];for(var l=this.items.length-1;l>=0;l--){if(!e.contains(this.containers[r].element[0],this.items[l].item[0]))continue;if(this.items[l].item[0]==this.currentItem[0])continue;var c=this.items[l].item.offset()[u],h=!1;Math.abs(c-f)>Math.abs(c+this.items[l][a]-f)&&(h=!0,c+=this.items[l][a]),Math.abs(c-f)<s&&(s=Math.abs(c-f),o=this.items[l],this.direction=h?"up":"down")}if(!o&&!this.options.dropOnEmpty)return;this.currentContainer=this.containers[r],o?this._rearrange(t,o,null,!0):this._rearrange(t,null,this.containers[r].element,!0),this._trigger("change",t,this._uiHash()),this.containers[r]._trigger("change",t,this._uiHash(this)),this.options.placeholder.update(this.currentContainer,this.placeholder),this.containers[r]._trigger("over",t,this._uiHash(this)),this.containers[r].containerCache.over=1}},_createHelper:function(t){var n=this.options,r=e.isFunction(n.helper)?e(n.helper.apply(this.element[0],[t,this.currentItem])):n.helper=="clone"?this.currentItem.clone():this.currentItem;return r.parents("body").length||e(n.appendTo!="parent"?n.appendTo:this.currentItem[0].parentNode)[0].appendChild(r[0]),r[0]==this.currentItem[0]&&(this._storedCSS={width:this.currentItem[0].style.width,height:this.currentItem[0].style.height,position:this.currentItem.css("position"),top:this.currentItem.css("top"),left:this.currentItem.css("left")}),(r[0].style.width==""||n.forceHelperSize)&&r.width(this.currentItem.width()),(r[0].style.height==""||n.forceHelperSize)&&r.height(this.currentItem.height()),r},_adjustOffsetFromHelper:function(t){typeof t=="string"&&(t=t.split(" ")),e.isArray(t)&&(t={left:+t[0],top:+t[1]||0}),"left"in t&&(this.offset.click.left=t.left+this.margins.left),"right"in t&&(this.offset.click.left=this.helperProportions.width-t.right+this.margins.left),"top"in t&&(this.offset.click.top=t.top+this.margins.top),"bottom"in t&&(this.offset.click.top=this.helperProportions.height-t.bottom+this.margins.top)},_getParentOffset:function(){this.offsetParent=this.helper.offsetParent();var t=this.offsetParent.offset();this.cssPosition=="absolute"&&this.scrollParent[0]!=document&&e.contains(this.scrollParent[0],this.offsetParent[0])&&(t.left+=this.scrollParent.scrollLeft(),t.top+=this.scrollParent.scrollTop());if(this.offsetParent[0]==document.body||this.offsetParent[0].tagName&&this.offsetParent[0].tagName.toLowerCase()=="html"&&e.ui.ie)t={top:0,left:0};return{top:t.top+(parseInt(this.offsetParent.css("borderTopWidth"),10)||0),left:t.left+(parseInt(this.offsetParent.css("borderLeftWidth"),10)||0)}},_getRelativeOffset:function(){if(this.cssPosition=="relative"){var e=this.currentItem.position();return{top:e.top-(parseInt(this.helper.css("top"),10)||0)+this.scrollParent.scrollTop(),left:e.left-(parseInt(this.helper.css("left"),10)||0)+this.scrollParent.scrollLeft()}}return{top:0,left:0}},_cacheMargins:function(){this.margins={left:parseInt(this.currentItem.css("marginLeft"),10)||0,top:parseInt(this.currentItem.css("marginTop"),10)||0}},_cacheHelperProportions:function(){this.helperProportions={width:this.helper.outerWidth(),height:this.helper.outerHeight()}},_setContainment:function(){var t=this.options;t.containment=="parent"&&(t.containment=this.helper[0].parentNode);if(t.containment=="document"||t.containment=="window")this.containment=[0-this.offset.relative.left-this.offset.parent.left,0-this.offset.relative.top-this.offset.parent.top,e(t.containment=="document"?document:window).width()-this.helperProportions.width-this.margins.left,(e(t.containment=="document"?document:window).height()||document.body.parentNode.scrollHeight)-this.helperProportions.height-this.margins.top];if(!/^(document|window|parent)$/.test(t.containment)){var n=e(t.containment)[0],r=e(t.containment).offset(),i=e(n).css("overflow")!="hidden";this.containment=[r.left+(parseInt(e(n).css("borderLeftWidth"),10)||0)+(parseInt(e(n).css("paddingLeft"),10)||0)-this.margins.left,r.top+(parseInt(e(n).css("borderTopWidth"),10)||0)+(parseInt(e(n).css("paddingTop"),10)||0)-this.margins.top,r.left+(i?Math.max(n.scrollWidth,n.offsetWidth):n.offsetWidth)-(parseInt(e(n).css("borderLeftWidth"),10)||0)-(parseInt(e(n).css("paddingRight"),10)||0)-this.helperProportions.width-this.margins.left,r.top+(i?Math.max(n.scrollHeight,n.offsetHeight):n.offsetHeight)-(parseInt(e(n).css("borderTopWidth"),10)||0)-(parseInt(e(n).css("paddingBottom"),10)||0)-this.helperProportions.height-this.margins.top]}},_convertPositionTo:function(t,n){n||(n=this.position);var r=t=="absolute"?1:-1,i=this.options,s=this.cssPosition!="absolute"||this.scrollParent[0]!=document&&!!e.contains(this.scrollParent[0],this.offsetParent[0])?this.scrollParent:this.offsetParent,o=/(html|body)/i.test(s[0].tagName);return{top:n.top+this.offset.relative.top*r+this.offset.parent.top*r-(this.cssPosition=="fixed"?-this.scrollParent.scrollTop():o?0:s.scrollTop())*r,left:n.left+this.offset.relative.left*r+this.offset.parent.left*r-(this.cssPosition=="fixed"?-this.scrollParent.scrollLeft():o?0:s.scrollLeft())*r}},_generatePosition:function(t){var n=this.options,r=this.cssPosition!="absolute"||this.scrollParent[0]!=document&&!!e.contains(this.scrollParent[0],this.offsetParent[0])?this.scrollParent:this.offsetParent,i=/(html|body)/i.test(r[0].tagName);this.cssPosition=="relative"&&(this.scrollParent[0]==document||this.scrollParent[0]==this.offsetParent[0])&&(this.offset.relative=this._getRelativeOffset());var s=t.pageX,o=t.pageY;if(this.originalPosition){this.containment&&(t.pageX-this.offset.click.left<this.containment[0]&&(s=this.containment[0]+this.offset.click.left),t.pageY-this.offset.click.top<this.containment[1]&&(o=this.containment[1]+this.offset.click.top),t.pageX-this.offset.click.left>this.containment[2]&&(s=this.containment[2]+this.offset.click.left),t.pageY-this.offset.click.top>this.containment[3]&&(o=this.containment[3]+this.offset.click.top));if(n.grid){var u=this.originalPageY+Math.round((o-this.originalPageY)/n.grid[1])*n.grid[1];o=this.containment?u-this.offset.click.top<this.containment[1]||u-this.offset.click.top>this.containment[3]?u-this.offset.click.top<this.containment[1]?u+n.grid[1]:u-n.grid[1]:u:u;var a=this.originalPageX+Math.round((s-this.originalPageX)/n.grid[0])*n.grid[0];s=this.containment?a-this.offset.click.left<this.containment[0]||a-this.offset.click.left>this.containment[2]?a-this.offset.click.left<this.containment[0]?a+n.grid[0]:a-n.grid[0]:a:a}}return{top:o-this.offset.click.top-this.offset.relative.top-this.offset.parent.top+(this.cssPosition=="fixed"?-this.scrollParent.scrollTop():i?0:r.scrollTop()),left:s-this.offset.click.left-this.offset.relative.left-this.offset.parent.left+(this.cssPosition=="fixed"?-this.scrollParent.scrollLeft():i?0:r.scrollLeft())}},_rearrange:function(e,t,n,r){n?n[0].appendChild(this.placeholder[0]):t.item[0].parentNode.insertBefore(this.placeholder[0],this.direction=="down"?t.item[0]:t.item[0].nextSibling),this.counter=this.counter?++this.counter:1;var i=this.counter;this._delay(function(){i==this.counter&&this.refreshPositions(!r)})},_clear:function(t,n){this.reverting=!1;var r=[];!this._noFinalSort&&this.currentItem.parent().length&&this.placeholder.before(this.currentItem),this._noFinalSort=null;if(this.helper[0]==this.currentItem[0]){for(var i in this._storedCSS)if(this._storedCSS[i]=="auto"||this._storedCSS[i]=="static")this._storedCSS[i]="";this.currentItem.css(this._storedCSS).removeClass("ui-sortable-helper")}else this.currentItem.show();this.fromOutside&&!n&&r.push(function(e){this._trigger("receive",e,this._uiHash(this.fromOutside))}),(this.fromOutside||this.domPosition.prev!=this.currentItem.prev().not(".ui-sortable-helper")[0]||this.domPosition.parent!=this.currentItem.parent()[0])&&!n&&r.push(function(e){this._trigger("update",e,this._uiHash())}),this!==this.currentContainer&&(n||(r.push(function(e){this._trigger("remove",e,this._uiHash())}),r.push(function(e){return function(t){e._trigger("receive",t,this._uiHash(this))}}.call(this,this.currentContainer)),r.push(function(e){return function(t){e._trigger("update",t,this._uiHash(this))}}.call(this,this.currentContainer))));for(var i=this.containers.length-1;i>=0;i--)n||r.push(function(e){return function(t){e._trigger("deactivate",t,this._uiHash(this))}}.call(this,this.containers[i])),this.containers[i].containerCache.over&&(r.push(function(e){return function(t){e._trigger("out",t,this._uiHash(this))}}.call(this,this.containers[i])),this.containers[i].containerCache.over=0);this._storedCursor&&e("body").css("cursor",this._storedCursor),this._storedOpacity&&this.helper.css("opacity",this._storedOpacity),this._storedZIndex&&this.helper.css("zIndex",this._storedZIndex=="auto"?"":this._storedZIndex),this.dragging=!1;if(this.cancelHelperRemoval){if(!n){this._trigger("beforeStop",t,this._uiHash());for(var i=0;i<r.length;i++)r[i].call(this,t);this._trigger("stop",t,this._uiHash())}return this.fromOutside=!1,!1}n||this._trigger("beforeStop",t,this._uiHash()),this.placeholder[0].parentNode.removeChild(this.placeholder[0]),this.helper[0]!=this.currentItem[0]&&this.helper.remove(),this.helper=null;if(!n){for(var i=0;i<r.length;i++)r[i].call(this,t);this._trigger("stop",t,this._uiHash())}return this.fromOutside=!1,!0},_trigger:function(){e.Widget.prototype._trigger.apply(this,arguments)===!1&&this.cancel()},_uiHash:function(t){var n=t||this;return{helper:n.helper,placeholder:n.placeholder||e([]),position:n.position,originalPosition:n.originalPosition,offset:n.positionAbs,item:n.currentItem,sender:t?t.element:null}}})})(jQuery);(function(e,t){var n=0,r={},i={};r.height=r.paddingTop=r.paddingBottom=r.borderTopWidth=r.borderBottomWidth="hide",i.height=i.paddingTop=i.paddingBottom=i.borderTopWidth=i.borderBottomWidth="show",e.widget("ui.accordion",{version:"1.9.2",options:{active:0,animate:{},collapsible:!1,event:"click",header:"> li > :first-child,> :not(li):even",heightStyle:"auto",icons:{activeHeader:"ui-icon-triangle-1-s",header:"ui-icon-triangle-1-e"},activate:null,beforeActivate:null},_create:function(){var t=this.accordionId="ui-accordion-"+(this.element.attr("id")||++n),r=this.options;this.prevShow=this.prevHide=e(),this.element.addClass("ui-accordion ui-widget ui-helper-reset"),this.headers=this.element.find(r.header).addClass("ui-accordion-header ui-helper-reset ui-state-default ui-corner-all"),this._hoverable(this.headers),this._focusable(this.headers),this.headers.next().addClass("ui-accordion-content ui-helper-reset ui-widget-content ui-corner-bottom").hide(),!r.collapsible&&(r.active===!1||r.active==null)&&(r.active=0),r.active<0&&(r.active+=this.headers.length),this.active=this._findActive(r.active).addClass("ui-accordion-header-active ui-state-active").toggleClass("ui-corner-all ui-corner-top"),this.active.next().addClass("ui-accordion-content-active").show(),this._createIcons(),this.refresh(),this.element.attr("role","tablist"),this.headers.attr("role","tab").each(function(n){var r=e(this),i=r.attr("id"),s=r.next(),o=s.attr("id");i||(i=t+"-header-"+n,r.attr("id",i)),o||(o=t+"-panel-"+n,s.attr("id",o)),r.attr("aria-controls",o),s.attr("aria-labelledby",i)}).next().attr("role","tabpanel"),this.headers.not(this.active).attr({"aria-selected":"false",tabIndex:-1}).next().attr({"aria-expanded":"false","aria-hidden":"true"}).hide(),this.active.length?this.active.attr({"aria-selected":"true",tabIndex:0}).next().attr({"aria-expanded":"true","aria-hidden":"false"}):this.headers.eq(0).attr("tabIndex",0),this._on(this.headers,{keydown:"_keydown"}),this._on(this.headers.next(),{keydown:"_panelKeyDown"}),this._setupEvents(r.event)},_getCreateEventData:function(){return{header:this.active,content:this.active.length?this.active.next():e()}},_createIcons:function(){var t=this.options.icons;t&&(e("<span>").addClass("ui-accordion-header-icon ui-icon "+t.header).prependTo(this.headers),this.active.children(".ui-accordion-header-icon").removeClass(t.header).addClass(t.activeHeader),this.headers.addClass("ui-accordion-icons"))},_destroyIcons:function(){this.headers.removeClass("ui-accordion-icons").children(".ui-accordion-header-icon").remove()},_destroy:function(){var e;this.element.removeClass("ui-accordion ui-widget ui-helper-reset").removeAttr("role"),this.headers.removeClass("ui-accordion-header ui-accordion-header-active ui-helper-reset ui-state-default ui-corner-all ui-state-active ui-state-disabled ui-corner-top").removeAttr("role").removeAttr("aria-selected").removeAttr("aria-controls").removeAttr("tabIndex").each(function(){/^ui-accordion/.test(this.id)&&this.removeAttribute("id")}),this._destroyIcons(),e=this.headers.next().css("display","").removeAttr("role").removeAttr("aria-expanded").removeAttr("aria-hidden").removeAttr("aria-labelledby").removeClass("ui-helper-reset ui-widget-content ui-corner-bottom ui-accordion-content ui-accordion-content-active ui-state-disabled").each(function(){/^ui-accordion/.test(this.id)&&this.removeAttribute("id")}),this.options.heightStyle!=="content"&&e.css("height","")},_setOption:function(e,t){if(e==="active"){this._activate(t);return}e==="event"&&(this.options.event&&this._off(this.headers,this.options.event),this._setupEvents(t)),this._super(e,t),e==="collapsible"&&!t&&this.options.active===!1&&this._activate(0),e==="icons"&&(this._destroyIcons(),t&&this._createIcons()),e==="disabled"&&this.headers.add(this.headers.next()).toggleClass("ui-state-disabled",!!t)},_keydown:function(t){if(t.altKey||t.ctrlKey)return;var n=e.ui.keyCode,r=this.headers.length,i=this.headers.index(t.target),s=!1;switch(t.keyCode){case n.RIGHT:case n.DOWN:s=this.headers[(i+1)%r];break;case n.LEFT:case n.UP:s=this.headers[(i-1+r)%r];break;case n.SPACE:case n.ENTER:this._eventHandler(t);break;case n.HOME:s=this.headers[0];break;case n.END:s=this.headers[r-1]}s&&(e(t.target).attr("tabIndex",-1),e(s).attr("tabIndex",0),s.focus(),t.preventDefault())},_panelKeyDown:function(t){t.keyCode===e.ui.keyCode.UP&&t.ctrlKey&&e(t.currentTarget).prev().focus()},refresh:function(){var t,n,r=this.options.heightStyle,i=this.element.parent();r==="fill"?(e.support.minHeight||(n=i.css("overflow"),i.css("overflow","hidden")),t=i.height(),this.element.siblings(":visible").each(function(){var n=e(this),r=n.css("position");if(r==="absolute"||r==="fixed")return;t-=n.outerHeight(!0)}),n&&i.css("overflow",n),this.headers.each(function(){t-=e(this).outerHeight(!0)}),this.headers.next().each(function(){e(this).height(Math.max(0,t-e(this).innerHeight()+e(this).height()))}).css("overflow","auto")):r==="auto"&&(t=0,this.headers.next().each(function(){t=Math.max(t,e(this).css("height","").height())}).height(t))},_activate:function(t){var n=this._findActive(t)[0];if(n===this.active[0])return;n=n||this.active[0],this._eventHandler({target:n,currentTarget:n,preventDefault:e.noop})},_findActive:function(t){return typeof t=="number"?this.headers.eq(t):e()},_setupEvents:function(t){var n={};if(!t)return;e.each(t.split(" "),function(e,t){n[t]="_eventHandler"}),this._on(this.headers,n)},_eventHandler:function(t){var n=this.options,r=this.active,i=e(t.currentTarget),s=i[0]===r[0],o=s&&n.collapsible,u=o?e():i.next(),a=r.next(),f={oldHeader:r,oldPanel:a,newHeader:o?e():i,newPanel:u};t.preventDefault();if(s&&!n.collapsible||this._trigger("beforeActivate",t,f)===!1)return;n.active=o?!1:this.headers.index(i),this.active=s?e():i,this._toggle(f),r.removeClass("ui-accordion-header-active ui-state-active"),n.icons&&r.children(".ui-accordion-header-icon").removeClass(n.icons.activeHeader).addClass(n.icons.header),s||(i.removeClass("ui-corner-all").addClass("ui-accordion-header-active ui-state-active ui-corner-top"),n.icons&&i.children(".ui-accordion-header-icon").removeClass(n.icons.header).addClass(n.icons.activeHeader),i.next().addClass("ui-accordion-content-active"))},_toggle:function(t){var n=t.newPanel,r=this.prevShow.length?this.prevShow:t.oldPanel;this.prevShow.add(this.prevHide).stop(!0,!0),this.prevShow=n,this.prevHide=r,this.options.animate?this._animate(n,r,t):(r.hide(),n.show(),this._toggleComplete(t)),r.attr({"aria-expanded":"false","aria-hidden":"true"}),r.prev().attr("aria-selected","false"),n.length&&r.length?r.prev().attr("tabIndex",-1):n.length&&this.headers.filter(function(){return e(this).attr("tabIndex")===0}).attr("tabIndex",-1),n.attr({"aria-expanded":"true","aria-hidden":"false"}).prev().attr({"aria-selected":"true",tabIndex:0})},_animate:function(e,t,n){var s,o,u,a=this,f=0,l=e.length&&(!t.length||e.index()<t.index()),c=this.options.animate||{},h=l&&c.down||c,p=function(){a._toggleComplete(n)};typeof h=="number"&&(u=h),typeof h=="string"&&(o=h),o=o||h.easing||c.easing,u=u||h.duration||c.duration;if(!t.length)return e.animate(i,u,o,p);if(!e.length)return t.animate(r,u,o,p);s=e.show().outerHeight(),t.animate(r,{duration:u,easing:o,step:function(e,t){t.now=Math.round(e)}}),e.hide().animate(i,{duration:u,easing:o,complete:p,step:function(e,n){n.now=Math.round(e),n.prop!=="height"?f+=n.now:a.options.heightStyle!=="content"&&(n.now=Math.round(s-t.outerHeight()-f),f=0)}})},_toggleComplete:function(e){var t=e.oldPanel;t.removeClass("ui-accordion-content-active").prev().removeClass("ui-corner-top").addClass("ui-corner-all"),t.length&&(t.parent()[0].className=t.parent()[0].className),this._trigger("activate",null,e)}}),e.uiBackCompat!==!1&&(function(e,t){e.extend(t.options,{navigation:!1,navigationFilter:function(){return this.href.toLowerCase()===location.href.toLowerCase()}});var n=t._create;t._create=function(){if(this.options.navigation){var t=this,r=this.element.find(this.options.header),i=r.next(),s=r.add(i).find("a").filter(this.options.navigationFilter)[0];s&&r.add(i).each(function(n){if(e.contains(this,s))return t.options.active=Math.floor(n/2),!1})}n.call(this)}}(jQuery,jQuery.ui.accordion.prototype),function(e,t){e.extend(t.options,{heightStyle:null,autoHeight:!0,clearStyle:!1,fillSpace:!1});var n=t._create,r=t._setOption;e.extend(t,{_create:function(){this.options.heightStyle=this.options.heightStyle||this._mergeHeightStyle(),n.call(this)},_setOption:function(e){if(e==="autoHeight"||e==="clearStyle"||e==="fillSpace")this.options.heightStyle=this._mergeHeightStyle();r.apply(this,arguments)},_mergeHeightStyle:function(){var e=this.options;if(e.fillSpace)return"fill";if(e.clearStyle)return"content";if(e.autoHeight)return"auto"}})}(jQuery,jQuery.ui.accordion.prototype),function(e,t){e.extend(t.options.icons,{activeHeader:null,headerSelected:"ui-icon-triangle-1-s"});var n=t._createIcons;t._createIcons=function(){this.options.icons&&(this.options.icons.activeHeader=this.options.icons.activeHeader||this.options.icons.headerSelected),n.call(this)}}(jQuery,jQuery.ui.accordion.prototype),function(e,t){t.activate=t._activate;var n=t._findActive;t._findActive=function(e){return e===-1&&(e=!1),e&&typeof e!="number"&&(e=this.headers.index(this.headers.filter(e)),e===-1&&(e=!1)),n.call(this,e)}}(jQuery,jQuery.ui.accordion.prototype),jQuery.ui.accordion.prototype.resize=jQuery.ui.accordion.prototype.refresh,function(e,t){e.extend(t.options,{change:null,changestart:null});var n=t._trigger;t._trigger=function(e,t,r){var i=n.apply(this,arguments);return i?(e==="beforeActivate"?i=n.call(this,"changestart",t,{oldHeader:r.oldHeader,oldContent:r.oldPanel,newHeader:r.newHeader,newContent:r.newPanel}):e==="activate"&&(i=n.call(this,"change",t,{oldHeader:r.oldHeader,oldContent:r.oldPanel,newHeader:r.newHeader,newContent:r.newPanel})),i):!1}}(jQuery,jQuery.ui.accordion.prototype),function(e,t){e.extend(t.options,{animate:null,animated:"slide"});var n=t._create;t._create=function(){var e=this.options;e.animate===null&&(e.animated?e.animated==="slide"?e.animate=300:e.animated==="bounceslide"?e.animate={duration:200,down:{easing:"easeOutBounce",duration:1e3}}:e.animate=e.animated:e.animate=!1),n.call(this)}}(jQuery,jQuery.ui.accordion.prototype))})(jQuery);(function(e,t){var n=0;e.widget("ui.autocomplete",{version:"1.9.2",defaultElement:"<input>",options:{appendTo:"body",autoFocus:!1,delay:300,minLength:1,position:{my:"left top",at:"left bottom",collision:"none"},source:null,change:null,close:null,focus:null,open:null,response:null,search:null,select:null},pending:0,_create:function(){var t,n,r;this.isMultiLine=this._isMultiLine(),this.valueMethod=this.element[this.element.is("input,textarea")?"val":"text"],this.isNewMenu=!0,this.element.addClass("ui-autocomplete-input").attr("autocomplete","off"),this._on(this.element,{keydown:function(i){if(this.element.prop("readOnly")){t=!0,r=!0,n=!0;return}t=!1,r=!1,n=!1;var s=e.ui.keyCode;switch(i.keyCode){case s.PAGE_UP:t=!0,this._move("previousPage",i);break;case s.PAGE_DOWN:t=!0,this._move("nextPage",i);break;case s.UP:t=!0,this._keyEvent("previous",i);break;case s.DOWN:t=!0,this._keyEvent("next",i);break;case s.ENTER:case s.NUMPAD_ENTER:this.menu.active&&(t=!0,i.preventDefault(),this.menu.select(i));break;case s.TAB:this.menu.active&&this.menu.select(i);break;case s.ESCAPE:this.menu.element.is(":visible")&&(this._value(this.term),this.close(i),i.preventDefault());break;default:n=!0,this._searchTimeout(i)}},keypress:function(r){if(t){t=!1,r.preventDefault();return}if(n)return;var i=e.ui.keyCode;switch(r.keyCode){case i.PAGE_UP:this._move("previousPage",r);break;case i.PAGE_DOWN:this._move("nextPage",r);break;case i.UP:this._keyEvent("previous",r);break;case i.DOWN:this._keyEvent("next",r)}},input:function(e){if(r){r=!1,e.preventDefault();return}this._searchTimeout(e)},focus:function(){this.selectedItem=null,this.previous=this._value()},blur:function(e){if(this.cancelBlur){delete this.cancelBlur;return}clearTimeout(this.searching),this.close(e),this._change(e)}}),this._initSource(),this.menu=e("<ul>").addClass("ui-autocomplete").appendTo(this.document.find(this.options.appendTo||"body")[0]).menu({input:e(),role:null}).zIndex(this.element.zIndex()+1).hide().data("menu"),this._on(this.menu.element,{mousedown:function(t){t.preventDefault(),this.cancelBlur=!0,this._delay(function(){delete this.cancelBlur});var n=this.menu.element[0];e(t.target).closest(".ui-menu-item").length||this._delay(function(){var t=this;this.document.one("mousedown",function(r){r.target!==t.element[0]&&r.target!==n&&!e.contains(n,r.target)&&t.close()})})},menufocus:function(t,n){if(this.isNewMenu){this.isNewMenu=!1;if(t.originalEvent&&/^mouse/.test(t.originalEvent.type)){this.menu.blur(),this.document.one("mousemove",function(){e(t.target).trigger(t.originalEvent)});return}}var r=n.item.data("ui-autocomplete-item")||n.item.data("item.autocomplete");!1!==this._trigger("focus",t,{item:r})?t.originalEvent&&/^key/.test(t.originalEvent.type)&&this._value(r.value):this.liveRegion.text(r.value)},menuselect:function(e,t){var n=t.item.data("ui-autocomplete-item")||t.item.data("item.autocomplete"),r=this.previous;this.element[0]!==this.document[0].activeElement&&(this.element.focus(),this.previous=r,this._delay(function(){this.previous=r,this.selectedItem=n})),!1!==this._trigger("select",e,{item:n})&&this._value(n.value),this.term=this._value(),this.close(e),this.selectedItem=n}}),this.liveRegion=e("<span>",{role:"status","aria-live":"polite"}).addClass("ui-helper-hidden-accessible").insertAfter(this.element),e.fn.bgiframe&&this.menu.element.bgiframe(),this._on(this.window,{beforeunload:function(){this.element.removeAttr("autocomplete")}})},_destroy:function(){clearTimeout(this.searching),this.element.removeClass("ui-autocomplete-input").removeAttr("autocomplete"),this.menu.element.remove(),this.liveRegion.remove()},_setOption:function(e,t){this._super(e,t),e==="source"&&this._initSource(),e==="appendTo"&&this.menu.element.appendTo(this.document.find(t||"body")[0]),e==="disabled"&&t&&this.xhr&&this.xhr.abort()},_isMultiLine:function(){return this.element.is("textarea")?!0:this.element.is("input")?!1:this.element.prop("isContentEditable")},_initSource:function(){var t,n,r=this;e.isArray(this.options.source)?(t=this.options.source,this.source=function(n,r){r(e.ui.autocomplete.filter(t,n.term))}):typeof this.options.source=="string"?(n=this.options.source,this.source=function(t,i){r.xhr&&r.xhr.abort(),r.xhr=e.ajax({url:n,data:t,dataType:"json",success:function(e){i(e)},error:function(){i([])}})}):this.source=this.options.source},_searchTimeout:function(e){clearTimeout(this.searching),this.searching=this._delay(function(){this.term!==this._value()&&(this.selectedItem=null,this.search(null,e))},this.options.delay)},search:function(e,t){e=e!=null?e:this._value(),this.term=this._value();if(e.length<this.options.minLength)return this.close(t);if(this._trigger("search",t)===!1)return;return this._search(e)},_search:function(e){this.pending++,this.element.addClass("ui-autocomplete-loading"),this.cancelSearch=!1,this.source({term:e},this._response())},_response:function(){var e=this,t=++n;return function(r){t===n&&e.__response(r),e.pending--,e.pending||e.element.removeClass("ui-autocomplete-loading")}},__response:function(e){e&&(e=this._normalize(e)),this._trigger("response",null,{content:e}),!this.options.disabled&&e&&e.length&&!this.cancelSearch?(this._suggest(e),this._trigger("open")):this._close()},close:function(e){this.cancelSearch=!0,this._close(e)},_close:function(e){this.menu.element.is(":visible")&&(this.menu.element.hide(),this.menu.blur(),this.isNewMenu=!0,this._trigger("close",e))},_change:function(e){this.previous!==this._value()&&this._trigger("change",e,{item:this.selectedItem})},_normalize:function(t){return t.length&&t[0].label&&t[0].value?t:e.map(t,function(t){return typeof t=="string"?{label:t,value:t}:e.extend({label:t.label||t.value,value:t.value||t.label},t)})},_suggest:function(t){var n=this.menu.element.empty().zIndex(this.element.zIndex()+1);this._renderMenu(n,t),this.menu.refresh(),n.show(),this._resizeMenu(),n.position(e.extend({of:this.element},this.options.position)),this.options.autoFocus&&this.menu.next()},_resizeMenu:function(){var e=this.menu.element;e.outerWidth(Math.max(e.width("").outerWidth()+1,this.element.outerWidth()))},_renderMenu:function(t,n){var r=this;e.each(n,function(e,n){r._renderItemData(t,n)})},_renderItemData:function(e,t){return this._renderItem(e,t).data("ui-autocomplete-item",t)},_renderItem:function(t,n){return e("<li>").append(e("<a>").text(n.label)).appendTo(t)},_move:function(e,t){if(!this.menu.element.is(":visible")){this.search(null,t);return}if(this.menu.isFirstItem()&&/^previous/.test(e)||this.menu.isLastItem()&&/^next/.test(e)){this._value(this.term),this.menu.blur();return}this.menu[e](t)},widget:function(){return this.menu.element},_value:function(){return this.valueMethod.apply(this.element,arguments)},_keyEvent:function(e,t){if(!this.isMultiLine||this.menu.element.is(":visible"))this._move(e,t),t.preventDefault()}}),e.extend(e.ui.autocomplete,{escapeRegex:function(e){return e.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g,"\\$&")},filter:function(t,n){var r=new RegExp(e.ui.autocomplete.escapeRegex(n),"i");return e.grep(t,function(e){return r.test(e.label||e.value||e)})}}),e.widget("ui.autocomplete",e.ui.autocomplete,{options:{messages:{noResults:"No search results.",results:function(e){return e+(e>1?" results are":" result is")+" available, use up and down arrow keys to navigate."}}},__response:function(e){var t;this._superApply(arguments);if(this.options.disabled||this.cancelSearch)return;e&&e.length?t=this.options.messages.results(e.length):t=this.options.messages.noResults,this.liveRegion.text(t)}})})(jQuery);(function(e,t){var n,r,i,s,o="ui-button ui-widget ui-state-default ui-corner-all",u="ui-state-hover ui-state-active ",a="ui-button-icons-only ui-button-icon-only ui-button-text-icons ui-button-text-icon-primary ui-button-text-icon-secondary ui-button-text-only",f=function(){var t=e(this).find(":ui-button");setTimeout(function(){t.button("refresh")},1)},l=function(t){var n=t.name,r=t.form,i=e([]);return n&&(r?i=e(r).find("[name='"+n+"']"):i=e("[name='"+n+"']",t.ownerDocument).filter(function(){return!this.form})),i};e.widget("ui.button",{version:"1.9.2",defaultElement:"<button>",options:{disabled:null,text:!0,label:null,icons:{primary:null,secondary:null}},_create:function(){this.element.closest("form").unbind("reset"+this.eventNamespace).bind("reset"+this.eventNamespace,f),typeof this.options.disabled!="boolean"?this.options.disabled=!!this.element.prop("disabled"):this.element.prop("disabled",this.options.disabled),this._determineButtonType(),this.hasTitle=!!this.buttonElement.attr("title");var t=this,u=this.options,a=this.type==="checkbox"||this.type==="radio",c=a?"":"ui-state-active",h="ui-state-focus";u.label===null&&(u.label=this.type==="input"?this.buttonElement.val():this.buttonElement.html()),this._hoverable(this.buttonElement),this.buttonElement.addClass(o).attr("role","button").bind("mouseenter"+this.eventNamespace,function(){if(u.disabled)return;this===n&&e(this).addClass("ui-state-active")}).bind("mouseleave"+this.eventNamespace,function(){if(u.disabled)return;e(this).removeClass(c)}).bind("click"+this.eventNamespace,function(e){u.disabled&&(e.preventDefault(),e.stopImmediatePropagation())}),this.element.bind("focus"+this.eventNamespace,function(){t.buttonElement.addClass(h)}).bind("blur"+this.eventNamespace,function(){t.buttonElement.removeClass(h)}),a&&(this.element.bind("change"+this.eventNamespace,function(){if(s)return;t.refresh()}),this.buttonElement.bind("mousedown"+this.eventNamespace,function(e){if(u.disabled)return;s=!1,r=e.pageX,i=e.pageY}).bind("mouseup"+this.eventNamespace,function(e){if(u.disabled)return;if(r!==e.pageX||i!==e.pageY)s=!0})),this.type==="checkbox"?this.buttonElement.bind("click"+this.eventNamespace,function(){if(u.disabled||s)return!1;e(this).toggleClass("ui-state-active"),t.buttonElement.attr("aria-pressed",t.element[0].checked)}):this.type==="radio"?this.buttonElement.bind("click"+this.eventNamespace,function(){if(u.disabled||s)return!1;e(this).addClass("ui-state-active"),t.buttonElement.attr("aria-pressed","true");var n=t.element[0];l(n).not(n).map(function(){return e(this).button("widget")[0]}).removeClass("ui-state-active").attr("aria-pressed","false")}):(this.buttonElement.bind("mousedown"+this.eventNamespace,function(){if(u.disabled)return!1;e(this).addClass("ui-state-active"),n=this,t.document.one("mouseup",function(){n=null})}).bind("mouseup"+this.eventNamespace,function(){if(u.disabled)return!1;e(this).removeClass("ui-state-active")}).bind("keydown"+this.eventNamespace,function(t){if(u.disabled)return!1;(t.keyCode===e.ui.keyCode.SPACE||t.keyCode===e.ui.keyCode.ENTER)&&e(this).addClass("ui-state-active")}).bind("keyup"+this.eventNamespace,function(){e(this).removeClass("ui-state-active")}),this.buttonElement.is("a")&&this.buttonElement.keyup(function(t){t.keyCode===e.ui.keyCode.SPACE&&e(this).click()})),this._setOption("disabled",u.disabled),this._resetButton()},_determineButtonType:function(){var e,t,n;this.element.is("[type=checkbox]")?this.type="checkbox":this.element.is("[type=radio]")?this.type="radio":this.element.is("input")?this.type="input":this.type="button",this.type==="checkbox"||this.type==="radio"?(e=this.element.parents().last(),t="label[for='"+this.element.attr("id")+"']",this.buttonElement=e.find(t),this.buttonElement.length||(e=e.length?e.siblings():this.element.siblings(),this.buttonElement=e.filter(t),this.buttonElement.length||(this.buttonElement=e.find(t))),this.element.addClass("ui-helper-hidden-accessible"),n=this.element.is(":checked"),n&&this.buttonElement.addClass("ui-state-active"),this.buttonElement.prop("aria-pressed",n)):this.buttonElement=this.element},widget:function(){return this.buttonElement},_destroy:function(){this.element.removeClass("ui-helper-hidden-accessible"),this.buttonElement.removeClass(o+" "+u+" "+a).removeAttr("role").removeAttr("aria-pressed").html(this.buttonElement.find(".ui-button-text").html()),this.hasTitle||this.buttonElement.removeAttr("title")},_setOption:function(e,t){this._super(e,t);if(e==="disabled"){t?this.element.prop("disabled",!0):this.element.prop("disabled",!1);return}this._resetButton()},refresh:function(){var t=this.element.is("input, button")?this.element.is(":disabled"):this.element.hasClass("ui-button-disabled");t!==this.options.disabled&&this._setOption("disabled",t),this.type==="radio"?l(this.element[0]).each(function(){e(this).is(":checked")?e(this).button("widget").addClass("ui-state-active").attr("aria-pressed","true"):e(this).button("widget").removeClass("ui-state-active").attr("aria-pressed","false")}):this.type==="checkbox"&&(this.element.is(":checked")?this.buttonElement.addClass("ui-state-active").attr("aria-pressed","true"):this.buttonElement.removeClass("ui-state-active").attr("aria-pressed","false"))},_resetButton:function(){if(this.type==="input"){this.options.label&&this.element.val(this.options.label);return}var t=this.buttonElement.removeClass(a),n=e("<span></span>",this.document[0]).addClass("ui-button-text").html(this.options.label).appendTo(t.empty()).text(),r=this.options.icons,i=r.primary&&r.secondary,s=[];r.primary||r.secondary?(this.options.text&&s.push("ui-button-text-icon"+(i?"s":r.primary?"-primary":"-secondary")),r.primary&&t.prepend("<span class='ui-button-icon-primary ui-icon "+r.primary+"'></span>"),r.secondary&&t.append("<span class='ui-button-icon-secondary ui-icon "+r.secondary+"'></span>"),this.options.text||(s.push(i?"ui-button-icons-only":"ui-button-icon-only"),this.hasTitle||t.attr("title",e.trim(n)))):s.push("ui-button-text-only"),t.addClass(s.join(" "))}}),e.widget("ui.buttonset",{version:"1.9.2",options:{items:"button, input[type=button], input[type=submit], input[type=reset], input[type=checkbox], input[type=radio], a, :data(button)"},_create:function(){this.element.addClass("ui-buttonset")},_init:function(){this.refresh()},_setOption:function(e,t){e==="disabled"&&this.buttons.button("option",e,t),this._super(e,t)},refresh:function(){var t=this.element.css("direction")==="rtl";this.buttons=this.element.find(this.options.items).filter(":ui-button").button("refresh").end().not(":ui-button").button().end().map(function(){return e(this).button("widget")[0]}).removeClass("ui-corner-all ui-corner-left ui-corner-right").filter(":first").addClass(t?"ui-corner-right":"ui-corner-left").end().filter(":last").addClass(t?"ui-corner-left":"ui-corner-right").end().end()},_destroy:function(){this.element.removeClass("ui-buttonset"),this.buttons.map(function(){return e(this).button("widget")[0]}).removeClass("ui-corner-left ui-corner-right").end().button("destroy")}})})(jQuery);(function($,undefined){function Datepicker(){this.debug=!1,this._curInst=null,this._keyEvent=!1,this._disabledInputs=[],this._datepickerShowing=!1,this._inDialog=!1,this._mainDivId="ui-datepicker-div",this._inlineClass="ui-datepicker-inline",this._appendClass="ui-datepicker-append",this._triggerClass="ui-datepicker-trigger",this._dialogClass="ui-datepicker-dialog",this._disableClass="ui-datepicker-disabled",this._unselectableClass="ui-datepicker-unselectable",this._currentClass="ui-datepicker-current-day",this._dayOverClass="ui-datepicker-days-cell-over",this.regional=[],this.regional[""]={closeText:"Done",prevText:"Prev",nextText:"Next",currentText:"Today",monthNames:["January","February","March","April","May","June","July","August","September","October","November","December"],monthNamesShort:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],dayNames:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],dayNamesShort:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],dayNamesMin:["Su","Mo","Tu","We","Th","Fr","Sa"],weekHeader:"Wk",dateFormat:"mm/dd/yy",firstDay:0,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""},this._defaults={showOn:"focus",showAnim:"fadeIn",showOptions:{},defaultDate:null,appendText:"",buttonText:"...",buttonImage:"",buttonImageOnly:!1,hideIfNoPrevNext:!1,navigationAsDateFormat:!1,gotoCurrent:!1,changeMonth:!1,changeYear:!1,yearRange:"c-10:c+10",showOtherMonths:!1,selectOtherMonths:!1,showWeek:!1,calculateWeek:this.iso8601Week,shortYearCutoff:"+10",minDate:null,maxDate:null,duration:"fast",beforeShowDay:null,beforeShow:null,onSelect:null,onChangeMonthYear:null,onClose:null,numberOfMonths:1,showCurrentAtPos:0,stepMonths:1,stepBigMonths:12,altField:"",altFormat:"",constrainInput:!0,showButtonPanel:!1,autoSize:!1,disabled:!1},$.extend(this._defaults,this.regional[""]),this.dpDiv=bindHover($('<div id="'+this._mainDivId+'" class="ui-datepicker ui-widget ui-widget-content ui-helper-clearfix ui-corner-all"></div>'))}function bindHover(e){var t="button, .ui-datepicker-prev, .ui-datepicker-next, .ui-datepicker-calendar td a";return e.delegate(t,"mouseout",function(){$(this).removeClass("ui-state-hover"),this.className.indexOf("ui-datepicker-prev")!=-1&&$(this).removeClass("ui-datepicker-prev-hover"),this.className.indexOf("ui-datepicker-next")!=-1&&$(this).removeClass("ui-datepicker-next-hover")}).delegate(t,"mouseover",function(){$.datepicker._isDisabledDatepicker(instActive.inline?e.parent()[0]:instActive.input[0])||($(this).parents(".ui-datepicker-calendar").find("a").removeClass("ui-state-hover"),$(this).addClass("ui-state-hover"),this.className.indexOf("ui-datepicker-prev")!=-1&&$(this).addClass("ui-datepicker-prev-hover"),this.className.indexOf("ui-datepicker-next")!=-1&&$(this).addClass("ui-datepicker-next-hover"))})}function extendRemove(e,t){$.extend(e,t);for(var n in t)if(t[n]==null||t[n]==undefined)e[n]=t[n];return e}$.extend($.ui,{datepicker:{version:"1.9.2"}});var PROP_NAME="datepicker",dpuuid=(new Date).getTime(),instActive;$.extend(Datepicker.prototype,{markerClassName:"hasDatepicker",maxRows:4,log:function(){this.debug&&console.log.apply("",arguments)},_widgetDatepicker:function(){return this.dpDiv},setDefaults:function(e){return extendRemove(this._defaults,e||{}),this},_attachDatepicker:function(target,settings){var inlineSettings=null;for(var attrName in this._defaults){var attrValue=target.getAttribute("date:"+attrName);if(attrValue){inlineSettings=inlineSettings||{};try{inlineSettings[attrName]=eval(attrValue)}catch(err){inlineSettings[attrName]=attrValue}}}var nodeName=target.nodeName.toLowerCase(),inline=nodeName=="div"||nodeName=="span";target.id||(this.uuid+=1,target.id="dp"+this.uuid);var inst=this._newInst($(target),inline);inst.settings=$.extend({},settings||{},inlineSettings||{}),nodeName=="input"?this._connectDatepicker(target,inst):inline&&this._inlineDatepicker(target,inst)},_newInst:function(e,t){var n=e[0].id.replace(/([^A-Za-z0-9_-])/g,"\\\\$1");return{id:n,input:e,selectedDay:0,selectedMonth:0,selectedYear:0,drawMonth:0,drawYear:0,inline:t,dpDiv:t?bindHover($('<div class="'+this._inlineClass+' ui-datepicker ui-widget ui-widget-content ui-helper-clearfix ui-corner-all"></div>')):this.dpDiv}},_connectDatepicker:function(e,t){var n=$(e);t.append=$([]),t.trigger=$([]);if(n.hasClass(this.markerClassName))return;this._attachments(n,t),n.addClass(this.markerClassName).keydown(this._doKeyDown).keypress(this._doKeyPress).keyup(this._doKeyUp).bind("setData.datepicker",function(e,n,r){t.settings[n]=r}).bind("getData.datepicker",function(e,n){return this._get(t,n)}),this._autoSize(t),$.data(e,PROP_NAME,t),t.settings.disabled&&this._disableDatepicker(e)},_attachments:function(e,t){var n=this._get(t,"appendText"),r=this._get(t,"isRTL");t.append&&t.append.remove(),n&&(t.append=$('<span class="'+this._appendClass+'">'+n+"</span>"),e[r?"before":"after"](t.append)),e.unbind("focus",this._showDatepicker),t.trigger&&t.trigger.remove();var i=this._get(t,"showOn");(i=="focus"||i=="both")&&e.focus(this._showDatepicker);if(i=="button"||i=="both"){var s=this._get(t,"buttonText"),o=this._get(t,"buttonImage");t.trigger=$(this._get(t,"buttonImageOnly")?$("<img/>").addClass(this._triggerClass).attr({src:o,alt:s,title:s}):$('<button type="button"></button>').addClass(this._triggerClass).html(o==""?s:$("<img/>").attr({src:o,alt:s,title:s}))),e[r?"before":"after"](t.trigger),t.trigger.click(function(){return $.datepicker._datepickerShowing&&$.datepicker._lastInput==e[0]?$.datepicker._hideDatepicker():$.datepicker._datepickerShowing&&$.datepicker._lastInput!=e[0]?($.datepicker._hideDatepicker(),$.datepicker._showDatepicker(e[0])):$.datepicker._showDatepicker(e[0]),!1})}},_autoSize:function(e){if(this._get(e,"autoSize")&&!e.inline){var t=new Date(2009,11,20),n=this._get(e,"dateFormat");if(n.match(/[DM]/)){var r=function(e){var t=0,n=0;for(var r=0;r<e.length;r++)e[r].length>t&&(t=e[r].length,n=r);return n};t.setMonth(r(this._get(e,n.match(/MM/)?"monthNames":"monthNamesShort"))),t.setDate(r(this._get(e,n.match(/DD/)?"dayNames":"dayNamesShort"))+20-t.getDay())}e.input.attr("size",this._formatDate(e,t).length)}},_inlineDatepicker:function(e,t){var n=$(e);if(n.hasClass(this.markerClassName))return;n.addClass(this.markerClassName).append(t.dpDiv).bind("setData.datepicker",function(e,n,r){t.settings[n]=r}).bind("getData.datepicker",function(e,n){return this._get(t,n)}),$.data(e,PROP_NAME,t),this._setDate(t,this._getDefaultDate(t),!0),this._updateDatepicker(t),this._updateAlternate(t),t.settings.disabled&&this._disableDatepicker(e),t.dpDiv.css("display","block")},_dialogDatepicker:function(e,t,n,r,i){var s=this._dialogInst;if(!s){this.uuid+=1;var o="dp"+this.uuid;this._dialogInput=$('<input type="text" id="'+o+'" style="position: absolute; top: -100px; width: 0px;"/>'),this._dialogInput.keydown(this._doKeyDown),$("body").append(this._dialogInput),s=this._dialogInst=this._newInst(this._dialogInput,!1),s.settings={},$.data(this._dialogInput[0],PROP_NAME,s)}extendRemove(s.settings,r||{}),t=t&&t.constructor==Date?this._formatDate(s,t):t,this._dialogInput.val(t),this._pos=i?i.length?i:[i.pageX,i.pageY]:null;if(!this._pos){var u=document.documentElement.clientWidth,a=document.documentElement.clientHeight,f=document.documentElement.scrollLeft||document.body.scrollLeft,l=document.documentElement.scrollTop||document.body.scrollTop;this._pos=[u/2-100+f,a/2-150+l]}return this._dialogInput.css("left",this._pos[0]+20+"px").css("top",this._pos[1]+"px"),s.settings.onSelect=n,this._inDialog=!0,this.dpDiv.addClass(this._dialogClass),this._showDatepicker(this._dialogInput[0]),$.blockUI&&$.blockUI(this.dpDiv),$.data(this._dialogInput[0],PROP_NAME,s),this},_destroyDatepicker:function(e){var t=$(e),n=$.data(e,PROP_NAME);if(!t.hasClass(this.markerClassName))return;var r=e.nodeName.toLowerCase();$.removeData(e,PROP_NAME),r=="input"?(n.append.remove(),n.trigger.remove(),t.removeClass(this.markerClassName).unbind("focus",this._showDatepicker).unbind("keydown",this._doKeyDown).unbind("keypress",this._doKeyPress).unbind("keyup",this._doKeyUp)):(r=="div"||r=="span")&&t.removeClass(this.markerClassName).empty()},_enableDatepicker:function(e){var t=$(e),n=$.data(e,PROP_NAME);if(!t.hasClass(this.markerClassName))return;var r=e.nodeName.toLowerCase();if(r=="input")e.disabled=!1,n.trigger.filter("button").each(function(){this.disabled=!1}).end().filter("img").css({opacity:"1.0",cursor:""});else if(r=="div"||r=="span"){var i=t.children("."+this._inlineClass);i.children().removeClass("ui-state-disabled"),i.find("select.ui-datepicker-month, select.ui-datepicker-year").prop("disabled",!1)}this._disabledInputs=$.map(this._disabledInputs,function(t){return t==e?null:t})},_disableDatepicker:function(e){var t=$(e),n=$.data(e,PROP_NAME);if(!t.hasClass(this.markerClassName))return;var r=e.nodeName.toLowerCase();if(r=="input")e.disabled=!0,n.trigger.filter("button").each(function(){this.disabled=!0}).end().filter("img").css({opacity:"0.5",cursor:"default"});else if(r=="div"||r=="span"){var i=t.children("."+this._inlineClass);i.children().addClass("ui-state-disabled"),i.find("select.ui-datepicker-month, select.ui-datepicker-year").prop("disabled",!0)}this._disabledInputs=$.map(this._disabledInputs,function(t){return t==e?null:t}),this._disabledInputs[this._disabledInputs.length]=e},_isDisabledDatepicker:function(e){if(!e)return!1;for(var t=0;t<this._disabledInputs.length;t++)if(this._disabledInputs[t]==e)return!0;return!1},_getInst:function(e){try{return $.data(e,PROP_NAME)}catch(t){throw"Missing instance data for this datepicker"}},_optionDatepicker:function(e,t,n){var r=this._getInst(e);if(arguments.length==2&&typeof t=="string")return t=="defaults"?$.extend({},$.datepicker._defaults):r?t=="all"?$.extend({},r.settings):this._get(r,t):null;var i=t||{};typeof t=="string"&&(i={},i[t]=n);if(r){this._curInst==r&&this._hideDatepicker();var s=this._getDateDatepicker(e,!0),o=this._getMinMaxDate(r,"min"),u=this._getMinMaxDate(r,"max");extendRemove(r.settings,i),o!==null&&i.dateFormat!==undefined&&i.minDate===undefined&&(r.settings.minDate=this._formatDate(r,o)),u!==null&&i.dateFormat!==undefined&&i.maxDate===undefined&&(r.settings.maxDate=this._formatDate(r,u)),this._attachments($(e),r),this._autoSize(r),this._setDate(r,s),this._updateAlternate(r),this._updateDatepicker(r)}},_changeDatepicker:function(e,t,n){this._optionDatepicker(e,t,n)},_refreshDatepicker:function(e){var t=this._getInst(e);t&&this._updateDatepicker(t)},_setDateDatepicker:function(e,t){var n=this._getInst(e);n&&(this._setDate(n,t),this._updateDatepicker(n),this._updateAlternate(n))},_getDateDatepicker:function(e,t){var n=this._getInst(e);return n&&!n.inline&&this._setDateFromField(n,t),n?this._getDate(n):null},_doKeyDown:function(e){var t=$.datepicker._getInst(e.target),n=!0,r=t.dpDiv.is(".ui-datepicker-rtl");t._keyEvent=!0;if($.datepicker._datepickerShowing)switch(e.keyCode){case 9:$.datepicker._hideDatepicker(),n=!1;break;case 13:var i=$("td."+$.datepicker._dayOverClass+":not(."+$.datepicker._currentClass+")",t.dpDiv);i[0]&&$.datepicker._selectDay(e.target,t.selectedMonth,t.selectedYear,i[0]);var s=$.datepicker._get(t,"onSelect");if(s){var o=$.datepicker._formatDate(t);s.apply(t.input?t.input[0]:null,[o,t])}else $.datepicker._hideDatepicker();return!1;case 27:$.datepicker._hideDatepicker();break;case 33:$.datepicker._adjustDate(e.target,e.ctrlKey?-$.datepicker._get(t,"stepBigMonths"):-$.datepicker._get(t,"stepMonths"),"M");break;case 34:$.datepicker._adjustDate(e.target,e.ctrlKey?+$.datepicker._get(t,"stepBigMonths"):+$.datepicker._get(t,"stepMonths"),"M");break;case 35:(e.ctrlKey||e.metaKey)&&$.datepicker._clearDate(e.target),n=e.ctrlKey||e.metaKey;break;case 36:(e.ctrlKey||e.metaKey)&&$.datepicker._gotoToday(e.target),n=e.ctrlKey||e.metaKey;break;case 37:(e.ctrlKey||e.metaKey)&&$.datepicker._adjustDate(e.target,r?1:-1,"D"),n=e.ctrlKey||e.metaKey,e.originalEvent.altKey&&$.datepicker._adjustDate(e.target,e.ctrlKey?-$.datepicker._get(t,"stepBigMonths"):-$.datepicker._get(t,"stepMonths"),"M");break;case 38:(e.ctrlKey||e.metaKey)&&$.datepicker._adjustDate(e.target,-7,"D"),n=e.ctrlKey||e.metaKey;break;case 39:(e.ctrlKey||e.metaKey)&&$.datepicker._adjustDate(e.target,r?-1:1,"D"),n=e.ctrlKey||e.metaKey,e.originalEvent.altKey&&$.datepicker._adjustDate(e.target,e.ctrlKey?+$.datepicker._get(t,"stepBigMonths"):+$.datepicker._get(t,"stepMonths"),"M");break;case 40:(e.ctrlKey||e.metaKey)&&$.datepicker._adjustDate(e.target,7,"D"),n=e.ctrlKey||e.metaKey;break;default:n=!1}else e.keyCode==36&&e.ctrlKey?$.datepicker._showDatepicker(this):n=!1;n&&(e.preventDefault(),e.stopPropagation())},_doKeyPress:function(e){var t=$.datepicker._getInst(e.target);if($.datepicker._get(t,"constrainInput")){var n=$.datepicker._possibleChars($.datepicker._get(t,"dateFormat")),r=String.fromCharCode(e.charCode==undefined?e.keyCode:e.charCode);return e.ctrlKey||e.metaKey||r<" "||!n||n.indexOf(r)>-1}},_doKeyUp:function(e){var t=$.datepicker._getInst(e.target);if(t.input.val()!=t.lastVal)try{var n=$.datepicker.parseDate($.datepicker._get(t,"dateFormat"),t.input?t.input.val():null,$.datepicker._getFormatConfig(t));n&&($.datepicker._setDateFromField(t),$.datepicker._updateAlternate(t),$.datepicker._updateDatepicker(t))}catch(r){$.datepicker.log(r)}return!0},_showDatepicker:function(e){e=e.target||e,e.nodeName.toLowerCase()!="input"&&(e=$("input",e.parentNode)[0]);if($.datepicker._isDisabledDatepicker(e)||$.datepicker._lastInput==e)return;var t=$.datepicker._getInst(e);$.datepicker._curInst&&$.datepicker._curInst!=t&&($.datepicker._curInst.dpDiv.stop(!0,!0),t&&$.datepicker._datepickerShowing&&$.datepicker._hideDatepicker($.datepicker._curInst.input[0]));var n=$.datepicker._get(t,"beforeShow"),r=n?n.apply(e,[e,t]):{};if(r===!1)return;extendRemove(t.settings,r),t.lastVal=null,$.datepicker._lastInput=e,$.datepicker._setDateFromField(t),$.datepicker._inDialog&&(e.value=""),$.datepicker._pos||($.datepicker._pos=$.datepicker._findPos(e),$.datepicker._pos[1]+=e.offsetHeight);var i=!1;$(e).parents().each(function(){return i|=$(this).css("position")=="fixed",!i});var s={left:$.datepicker._pos[0],top:$.datepicker._pos[1]};$.datepicker._pos=null,t.dpDiv.empty(),t.dpDiv.css({position:"absolute",display:"block",top:"-1000px"}),$.datepicker._updateDatepicker(t),s=$.datepicker._checkOffset(t,s,i),t.dpDiv.css({position:$.datepicker._inDialog&&$.blockUI?"static":i?"fixed":"absolute",display:"none",left:s.left+"px",top:s.top+"px"});if(!t.inline){var o=$.datepicker._get(t,"showAnim"),u=$.datepicker._get(t,"duration"),a=function(){var e=t.dpDiv.find("iframe.ui-datepicker-cover");if(!!e.length){var n=$.datepicker._getBorders(t.dpDiv);e.css({left:-n[0],top:-n[1],width:t.dpDiv.outerWidth(),height:t.dpDiv.outerHeight()})}};t.dpDiv.zIndex($(e).zIndex()+1),$.datepicker._datepickerShowing=!0,$.effects&&($.effects.effect[o]||$.effects[o])?t.dpDiv.show(o,$.datepicker._get(t,"showOptions"),u,a):t.dpDiv[o||"show"](o?u:null,a),(!o||!u)&&a(),t.input.is(":visible")&&!t.input.is(":disabled")&&t.input.focus(),$.datepicker._curInst=t}},_updateDatepicker:function(e){this.maxRows=4;var t=$.datepicker._getBorders(e.dpDiv);instActive=e,e.dpDiv.empty().append(this._generateHTML(e)),this._attachHandlers(e);var n=e.dpDiv.find("iframe.ui-datepicker-cover");!n.length||n.css({left:-t[0],top:-t[1],width:e.dpDiv.outerWidth(),height:e.dpDiv.outerHeight()}),e.dpDiv.find("."+this._dayOverClass+" a").mouseover();var r=this._getNumberOfMonths(e),i=r[1],s=17;e.dpDiv.removeClass("ui-datepicker-multi-2 ui-datepicker-multi-3 ui-datepicker-multi-4").width(""),i>1&&e.dpDiv.addClass("ui-datepicker-multi-"+i).css("width",s*i+"em"),e.dpDiv[(r[0]!=1||r[1]!=1?"add":"remove")+"Class"]("ui-datepicker-multi"),e.dpDiv[(this._get(e,"isRTL")?"add":"remove")+"Class"]("ui-datepicker-rtl"),e==$.datepicker._curInst&&$.datepicker._datepickerShowing&&e.input&&e.input.is(":visible")&&!e.input.is(":disabled")&&e.input[0]!=document.activeElement&&e.input.focus();if(e.yearshtml){var o=e.yearshtml;setTimeout(function(){o===e.yearshtml&&e.yearshtml&&e.dpDiv.find("select.ui-datepicker-year:first").replaceWith(e.yearshtml),o=e.yearshtml=null},0)}},_getBorders:function(e){var t=function(e){return{thin:1,medium:2,thick:3}[e]||e};return[parseFloat(t(e.css("border-left-width"))),parseFloat(t(e.css("border-top-width")))]},_checkOffset:function(e,t,n){var r=e.dpDiv.outerWidth(),i=e.dpDiv.outerHeight(),s=e.input?e.input.outerWidth():0,o=e.input?e.input.outerHeight():0,u=document.documentElement.clientWidth+(n?0:$(document).scrollLeft()),a=document.documentElement.clientHeight+(n?0:$(document).scrollTop());return t.left-=this._get(e,"isRTL")?r-s:0,t.left-=n&&t.left==e.input.offset().left?$(document).scrollLeft():0,t.top-=n&&t.top==e.input.offset().top+o?$(document).scrollTop():0,t.left-=Math.min(t.left,t.left+r>u&&u>r?Math.abs(t.left+r-u):0),t.top-=Math.min(t.top,t.top+i>a&&a>i?Math.abs(i+o):0),t},_findPos:function(e){var t=this._getInst(e),n=this._get(t,"isRTL");while(e&&(e.type=="hidden"||e.nodeType!=1||$.expr.filters.hidden(e)))e=e[n?"previousSibling":"nextSibling"];var r=$(e).offset();return[r.left,r.top]},_hideDatepicker:function(e){var t=this._curInst;if(!t||e&&t!=$.data(e,PROP_NAME))return;if(this._datepickerShowing){var n=this._get(t,"showAnim"),r=this._get(t,"duration"),i=function(){$.datepicker._tidyDialog(t)};$.effects&&($.effects.effect[n]||$.effects[n])?t.dpDiv.hide(n,$.datepicker._get(t,"showOptions"),r,i):t.dpDiv[n=="slideDown"?"slideUp":n=="fadeIn"?"fadeOut":"hide"](n?r:null,i),n||i(),this._datepickerShowing=!1;var s=this._get(t,"onClose");s&&s.apply(t.input?t.input[0]:null,[t.input?t.input.val():"",t]),this._lastInput=null,this._inDialog&&(this._dialogInput.css({position:"absolute",left:"0",top:"-100px"}),$.blockUI&&($.unblockUI(),$("body").append(this.dpDiv))),this._inDialog=!1}},_tidyDialog:function(e){e.dpDiv.removeClass(this._dialogClass).unbind(".ui-datepicker-calendar")},_checkExternalClick:function(e){if(!$.datepicker._curInst)return;var t=$(e.target),n=$.datepicker._getInst(t[0]);(t[0].id!=$.datepicker._mainDivId&&t.parents("#"+$.datepicker._mainDivId).length==0&&!t.hasClass($.datepicker.markerClassName)&&!t.closest("."+$.datepicker._triggerClass).length&&$.datepicker._datepickerShowing&&(!$.datepicker._inDialog||!$.blockUI)||t.hasClass($.datepicker.markerClassName)&&$.datepicker._curInst!=n)&&$.datepicker._hideDatepicker()},_adjustDate:function(e,t,n){var r=$(e),i=this._getInst(r[0]);if(this._isDisabledDatepicker(r[0]))return;this._adjustInstDate(i,t+(n=="M"?this._get(i,"showCurrentAtPos"):0),n),this._updateDatepicker(i)},_gotoToday:function(e){var t=$(e),n=this._getInst(t[0]);if(this._get(n,"gotoCurrent")&&n.currentDay)n.selectedDay=n.currentDay,n.drawMonth=n.selectedMonth=n.currentMonth,n.drawYear=n.selectedYear=n.currentYear;else{var r=new Date;n.selectedDay=r.getDate(),n.drawMonth=n.selectedMonth=r.getMonth(),n.drawYear=n.selectedYear=r.getFullYear()}this._notifyChange(n),this._adjustDate(t)},_selectMonthYear:function(e,t,n){var r=$(e),i=this._getInst(r[0]);i["selected"+(n=="M"?"Month":"Year")]=i["draw"+(n=="M"?"Month":"Year")]=parseInt(t.options[t.selectedIndex].value,10),this._notifyChange(i),this._adjustDate(r)},_selectDay:function(e,t,n,r){var i=$(e);if($(r).hasClass(this._unselectableClass)||this._isDisabledDatepicker(i[0]))return;var s=this._getInst(i[0]);s.selectedDay=s.currentDay=$("a",r).html(),s.selectedMonth=s.currentMonth=t,s.selectedYear=s.currentYear=n,this._selectDate(e,this._formatDate(s,s.currentDay,s.currentMonth,s.currentYear))},_clearDate:function(e){var t=$(e),n=this._getInst(t[0]);this._selectDate(t,"")},_selectDate:function(e,t){var n=$(e),r=this._getInst(n[0]);t=t!=null?t:this._formatDate(r),r.input&&r.input.val(t),this._updateAlternate(r);var i=this._get(r,"onSelect");i?i.apply(r.input?r.input[0]:null,[t,r]):r.input&&r.input.trigger("change"),r.inline?this._updateDatepicker(r):(this._hideDatepicker(),this._lastInput=r.input[0],typeof r.input[0]!="object"&&r.input.focus(),this._lastInput=null)},_updateAlternate:function(e){var t=this._get(e,"altField");if(t){var n=this._get(e,"altFormat")||this._get(e,"dateFormat"),r=this._getDate(e),i=this.formatDate(n,r,this._getFormatConfig(e));$(t).each(function(){$(this).val(i)})}},noWeekends:function(e){var t=e.getDay();return[t>0&&t<6,""]},iso8601Week:function(e){var t=new Date(e.getTime());t.setDate(t.getDate()+4-(t.getDay()||7));var n=t.getTime();return t.setMonth(0),t.setDate(1),Math.floor(Math.round((n-t)/864e5)/7)+1},parseDate:function(e,t,n){if(e==null||t==null)throw"Invalid arguments";t=typeof t=="object"?t.toString():t+"";if(t=="")return null;var r=(n?n.shortYearCutoff:null)||this._defaults.shortYearCutoff;r=typeof r!="string"?r:(new Date).getFullYear()%100+parseInt(r,10);var i=(n?n.dayNamesShort:null)||this._defaults.dayNamesShort,s=(n?n.dayNames:null)||this._defaults.dayNames,o=(n?n.monthNamesShort:null)||this._defaults.monthNamesShort,u=(n?n.monthNames:null)||this._defaults.monthNames,a=-1,f=-1,l=-1,c=-1,h=!1,p=function(t){var n=y+1<e.length&&e.charAt(y+1)==t;return n&&y++,n},d=function(e){var n=p(e),r=e=="@"?14:e=="!"?20:e=="y"&&n?4:e=="o"?3:2,i=new RegExp("^\\d{1,"+r+"}"),s=t.substring(g).match(i);if(!s)throw"Missing number at position "+g;return g+=s[0].length,parseInt(s[0],10)},v=function(e,n,r){var i=$.map(p(e)?r:n,function(e,t){return[[t,e]]}).sort(function(e,t){return-(e[1].length-t[1].length)}),s=-1;$.each(i,function(e,n){var r=n[1];if(t.substr(g,r.length).toLowerCase()==r.toLowerCase())return s=n[0],g+=r.length,!1});if(s!=-1)return s+1;throw"Unknown name at position "+g},m=function(){if(t.charAt(g)!=e.charAt(y))throw"Unexpected literal at position "+g;g++},g=0;for(var y=0;y<e.length;y++)if(h)e.charAt(y)=="'"&&!p("'")?h=!1:m();else switch(e.charAt(y)){case"d":l=d("d");break;case"D":v("D",i,s);break;case"o":c=d("o");break;case"m":f=d("m");break;case"M":f=v("M",o,u);break;case"y":a=d("y");break;case"@":var b=new Date(d("@"));a=b.getFullYear(),f=b.getMonth()+1,l=b.getDate();break;case"!":var b=new Date((d("!")-this._ticksTo1970)/1e4);a=b.getFullYear(),f=b.getMonth()+1,l=b.getDate();break;case"'":p("'")?m():h=!0;break;default:m()}if(g<t.length){var w=t.substr(g);if(!/^\s+/.test(w))throw"Extra/unparsed characters found in date: "+w}a==-1?a=(new Date).getFullYear():a<100&&(a+=(new Date).getFullYear()-(new Date).getFullYear()%100+(a<=r?0:-100));if(c>-1){f=1,l=c;do{var E=this._getDaysInMonth(a,f-1);if(l<=E)break;f++,l-=E}while(!0)}var b=this._daylightSavingAdjust(new Date(a,f-1,l));if(b.getFullYear()!=a||b.getMonth()+1!=f||b.getDate()!=l)throw"Invalid date";return b},ATOM:"yy-mm-dd",COOKIE:"D, dd M yy",ISO_8601:"yy-mm-dd",RFC_822:"D, d M y",RFC_850:"DD, dd-M-y",RFC_1036:"D, d M y",RFC_1123:"D, d M yy",RFC_2822:"D, d M yy",RSS:"D, d M y",TICKS:"!",TIMESTAMP:"@",W3C:"yy-mm-dd",_ticksTo1970:(718685+Math.floor(492.5)-Math.floor(19.7)+Math.floor(4.925))*24*60*60*1e7,formatDate:function(e,t,n){if(!t)return"";var r=(n?n.dayNamesShort:null)||this._defaults.dayNamesShort,i=(n?n.dayNames:null)||this._defaults.dayNames,s=(n?n.monthNamesShort:null)||this._defaults.monthNamesShort,o=(n?n.monthNames:null)||this._defaults.monthNames,u=function(t){var n=h+1<e.length&&e.charAt(h+1)==t;return n&&h++,n},a=function(e,t,n){var r=""+t;if(u(e))while(r.length<n)r="0"+r;return r},f=function(e,t,n,r){return u(e)?r[t]:n[t]},l="",c=!1;if(t)for(var h=0;h<e.length;h++)if(c)e.charAt(h)=="'"&&!u("'")?c=!1:l+=e.charAt(h);else switch(e.charAt(h)){case"d":l+=a("d",t.getDate(),2);break;case"D":l+=f("D",t.getDay(),r,i);break;case"o":l+=a("o",Math.round(((new Date(t.getFullYear(),t.getMonth(),t.getDate())).getTime()-(new Date(t.getFullYear(),0,0)).getTime())/864e5),3);break;case"m":l+=a("m",t.getMonth()+1,2);break;case"M":l+=f("M",t.getMonth(),s,o);break;case"y":l+=u("y")?t.getFullYear():(t.getYear()%100<10?"0":"")+t.getYear()%100;break;case"@":l+=t.getTime();break;case"!":l+=t.getTime()*1e4+this._ticksTo1970;break;case"'":u("'")?l+="'":c=!0;break;default:l+=e.charAt(h)}return l},_possibleChars:function(e){var t="",n=!1,r=function(t){var n=i+1<e.length&&e.charAt(i+1)==t;return n&&i++,n};for(var i=0;i<e.length;i++)if(n)e.charAt(i)=="'"&&!r("'")?n=!1:t+=e.charAt(i);else switch(e.charAt(i)){case"d":case"m":case"y":case"@":t+="0123456789";break;case"D":case"M":return null;case"'":r("'")?t+="'":n=!0;break;default:t+=e.charAt(i)}return t},_get:function(e,t){return e.settings[t]!==undefined?e.settings[t]:this._defaults[t]},_setDateFromField:function(e,t){if(e.input.val()==e.lastVal)return;var n=this._get(e,"dateFormat"),r=e.lastVal=e.input?e.input.val():null,i,s;i=s=this._getDefaultDate(e);var o=this._getFormatConfig(e);try{i=this.parseDate(n,r,o)||s}catch(u){this.log(u),r=t?"":r}e.selectedDay=i.getDate(),e.drawMonth=e.selectedMonth=i.getMonth(),e.drawYear=e.selectedYear=i.getFullYear(),e.currentDay=r?i.getDate():0,e.currentMonth=r?i.getMonth():0,e.currentYear=r?i.getFullYear():0,this._adjustInstDate(e)},_getDefaultDate:function(e){return this._restrictMinMax(e,this._determineDate(e,this._get(e,"defaultDate"),new Date))},_determineDate:function(e,t,n){var r=function(e){var t=new Date;return t.setDate(t.getDate()+e),t},i=function(t){try{return $.datepicker.parseDate($.datepicker._get(e,"dateFormat"),t,$.datepicker._getFormatConfig(e))}catch(n){}var r=(t.toLowerCase().match(/^c/)?$.datepicker._getDate(e):null)||new Date,i=r.getFullYear(),s=r.getMonth(),o=r.getDate(),u=/([+-]?[0-9]+)\s*(d|D|w|W|m|M|y|Y)?/g,a=u.exec(t);while(a){switch(a[2]||"d"){case"d":case"D":o+=parseInt(a[1],10);break;case"w":case"W":o+=parseInt(a[1],10)*7;break;case"m":case"M":s+=parseInt(a[1],10),o=Math.min(o,$.datepicker._getDaysInMonth(i,s));break;case"y":case"Y":i+=parseInt(a[1],10),o=Math.min(o,$.datepicker._getDaysInMonth(i,s))}a=u.exec(t)}return new Date(i,s,o)},s=t==null||t===""?n:typeof t=="string"?i(t):typeof t=="number"?isNaN(t)?n:r(t):new Date(t.getTime());return s=s&&s.toString()=="Invalid Date"?n:s,s&&(s.setHours(0),s.setMinutes(0),s.setSeconds(0),s.setMilliseconds(0)),this._daylightSavingAdjust(s)},_daylightSavingAdjust:function(e){return e?(e.setHours(e.getHours()>12?e.getHours()+2:0),e):null},_setDate:function(e,t,n){var r=!t,i=e.selectedMonth,s=e.selectedYear,o=this._restrictMinMax(e,this._determineDate(e,t,new Date));e.selectedDay=e.currentDay=o.getDate(),e.drawMonth=e.selectedMonth=e.currentMonth=o.getMonth(),e.drawYear=e.selectedYear=e.currentYear=o.getFullYear(),(i!=e.selectedMonth||s!=e.selectedYear)&&!n&&this._notifyChange(e),this._adjustInstDate(e),e.input&&e.input.val(r?"":this._formatDate(e))},_getDate:function(e){var t=!e.currentYear||e.input&&e.input.val()==""?null:this._daylightSavingAdjust(new Date(e.currentYear,e.currentMonth,e.currentDay));return t},_attachHandlers:function(e){var t=this._get(e,"stepMonths"),n="#"+e.id.replace(/\\\\/g,"\\");e.dpDiv.find("[data-handler]").map(function(){var e={prev:function(){window["DP_jQuery_"+dpuuid].datepicker._adjustDate(n,-t,"M")},next:function(){window["DP_jQuery_"+dpuuid].datepicker._adjustDate(n,+t,"M")},hide:function(){window["DP_jQuery_"+dpuuid].datepicker._hideDatepicker()},today:function(){window["DP_jQuery_"+dpuuid].datepicker._gotoToday(n)},selectDay:function(){return window["DP_jQuery_"+dpuuid].datepicker._selectDay(n,+this.getAttribute("data-month"),+this.getAttribute("data-year"),this),!1},selectMonth:function(){return window["DP_jQuery_"+dpuuid].datepicker._selectMonthYear(n,this,"M"),!1},selectYear:function(){return window["DP_jQuery_"+dpuuid].datepicker._selectMonthYear(n,this,"Y"),!1}};$(this).bind(this.getAttribute("data-event"),e[this.getAttribute("data-handler")])})},_generateHTML:function(e){var t=new Date;t=this._daylightSavingAdjust(new Date(t.getFullYear(),t.getMonth(),t.getDate()));var n=this._get(e,"isRTL"),r=this._get(e,"showButtonPanel"),i=this._get(e,"hideIfNoPrevNext"),s=this._get(e,"navigationAsDateFormat"),o=this._getNumberOfMonths(e),u=this._get(e,"showCurrentAtPos"),a=this._get(e,"stepMonths"),f=o[0]!=1||o[1]!=1,l=this._daylightSavingAdjust(e.currentDay?new Date(e.currentYear,e.currentMonth,e.currentDay):new Date(9999,9,9)),c=this._getMinMaxDate(e,"min"),h=this._getMinMaxDate(e,"max"),p=e.drawMonth-u,d=e.drawYear;p<0&&(p+=12,d--);if(h){var v=this._daylightSavingAdjust(new Date(h.getFullYear(),h.getMonth()-o[0]*o[1]+1,h.getDate()));v=c&&v<c?c:v;while(this._daylightSavingAdjust(new Date(d,p,1))>v)p--,p<0&&(p=11,d--)}e.drawMonth=p,e.drawYear=d;var m=this._get(e,"prevText");m=s?this.formatDate(m,this._daylightSavingAdjust(new Date(d,p-a,1)),this._getFormatConfig(e)):m;var g=this._canAdjustMonth(e,-1,d,p)?'<a class="ui-datepicker-prev ui-corner-all" data-handler="prev" data-event="click" title="'+m+'"><span class="ui-icon ui-icon-circle-triangle-'+(n?"e":"w")+'">'+m+"</span></a>":i?"":'<a class="ui-datepicker-prev ui-corner-all ui-state-disabled" title="'+m+'"><span class="ui-icon ui-icon-circle-triangle-'+(n?"e":"w")+'">'+m+"</span></a>",y=this._get(e,"nextText");y=s?this.formatDate(y,this._daylightSavingAdjust(new Date(d,p+a,1)),this._getFormatConfig(e)):y;var b=this._canAdjustMonth(e,1,d,p)?'<a class="ui-datepicker-next ui-corner-all" data-handler="next" data-event="click" title="'+y+'"><span class="ui-icon ui-icon-circle-triangle-'+(n?"w":"e")+'">'+y+"</span></a>":i?"":'<a class="ui-datepicker-next ui-corner-all ui-state-disabled" title="'+y+'"><span class="ui-icon ui-icon-circle-triangle-'+(n?"w":"e")+'">'+y+"</span></a>",w=this._get(e,"currentText"),E=this._get(e,"gotoCurrent")&&e.currentDay?l:t;w=s?this.formatDate(w,E,this._getFormatConfig(e)):w;var S=e.inline?"":'<button type="button" class="ui-datepicker-close ui-state-default ui-priority-primary ui-corner-all" data-handler="hide" data-event="click">'+this._get(e,"closeText")+"</button>",x=r?'<div class="ui-datepicker-buttonpane ui-widget-content">'+(n?S:"")+(this._isInRange(e,E)?'<button type="button" class="ui-datepicker-current ui-state-default ui-priority-secondary ui-corner-all" data-handler="today" data-event="click">'+w+"</button>":"")+(n?"":S)+"</div>":"",T=parseInt(this._get(e,"firstDay"),10);T=isNaN(T)?0:T;var N=this._get(e,"showWeek"),C=this._get(e,"dayNames"),k=this._get(e,"dayNamesShort"),L=this._get(e,"dayNamesMin"),A=this._get(e,"monthNames"),O=this._get(e,"monthNamesShort"),M=this._get(e,"beforeShowDay"),_=this._get(e,"showOtherMonths"),D=this._get(e,"selectOtherMonths"),P=this._get(e,"calculateWeek")||this.iso8601Week,H=this._getDefaultDate(e),B="";for(var j=0;j<o[0];j++){var F="";this.maxRows=4;for(var I=0;I<o[1];I++){var q=this._daylightSavingAdjust(new Date(d,p,e.selectedDay)),R=" ui-corner-all",U="";if(f){U+='<div class="ui-datepicker-group';if(o[1]>1)switch(I){case 0:U+=" ui-datepicker-group-first",R=" ui-corner-"+(n?"right":"left");break;case o[1]-1:U+=" ui-datepicker-group-last",R=" ui-corner-"+(n?"left":"right");break;default:U+=" ui-datepicker-group-middle",R=""}U+='">'}U+='<div class="ui-datepicker-header ui-widget-header ui-helper-clearfix'+R+'">'+(/all|left/.test(R)&&j==0?n?b:g:"")+(/all|right/.test(R)&&j==0?n?g:b:"")+this._generateMonthYearHeader(e,p,d,c,h,j>0||I>0,A,O)+'</div><table class="ui-datepicker-calendar"><thead>'+"<tr>";var z=N?'<th class="ui-datepicker-week-col">'+this._get(e,"weekHeader")+"</th>":"";for(var W=0;W<7;W++){var X=(W+T)%7;z+="<th"+((W+T+6)%7>=5?' class="ui-datepicker-week-end"':"")+">"+'<span title="'+C[X]+'">'+L[X]+"</span></th>"}U+=z+"</tr></thead><tbody>";var V=this._getDaysInMonth(d,p);d==e.selectedYear&&p==e.selectedMonth&&(e.selectedDay=Math.min(e.selectedDay,V));var J=(this._getFirstDayOfMonth(d,p)-T+7)%7,K=Math.ceil((J+V)/7),Q=f?this.maxRows>K?this.maxRows:K:K;this.maxRows=Q;var G=this._daylightSavingAdjust(new Date(d,p,1-J));for(var Y=0;Y<Q;Y++){U+="<tr>";var Z=N?'<td class="ui-datepicker-week-col">'+this._get(e,"calculateWeek")(G)+"</td>":"";for(var W=0;W<7;W++){var et=M?M.apply(e.input?e.input[0]:null,[G]):[!0,""],tt=G.getMonth()!=p,nt=tt&&!D||!et[0]||c&&G<c||h&&G>h;Z+='<td class="'+((W+T+6)%7>=5?" ui-datepicker-week-end":"")+(tt?" ui-datepicker-other-month":"")+(G.getTime()==q.getTime()&&p==e.selectedMonth&&e._keyEvent||H.getTime()==G.getTime()&&H.getTime()==q.getTime()?" "+this._dayOverClass:"")+(nt?" "+this._unselectableClass+" ui-state-disabled":"")+(tt&&!_?"":" "+et[1]+(G.getTime()==l.getTime()?" "+this._currentClass:"")+(G.getTime()==t.getTime()?" ui-datepicker-today":""))+'"'+((!tt||_)&&et[2]?' title="'+et[2]+'"':"")+(nt?"":' data-handler="selectDay" data-event="click" data-month="'+G.getMonth()+'" data-year="'+G.getFullYear()+'"')+">"+(tt&&!_?"&#xa0;":nt?'<span class="ui-state-default">'+G.getDate()+"</span>":'<a class="ui-state-default'+(G.getTime()==t.getTime()?" ui-state-highlight":"")+(G.getTime()==l.getTime()?" ui-state-active":"")+(tt?" ui-priority-secondary":"")+'" href="#">'+G.getDate()+"</a>")+"</td>",G.setDate(G.getDate()+1),G=this._daylightSavingAdjust(G)}U+=Z+"</tr>"}p++,p>11&&(p=0,d++),U+="</tbody></table>"+(f?"</div>"+(o[0]>0&&I==o[1]-1?'<div class="ui-datepicker-row-break"></div>':""):""),F+=U}B+=F}return B+=x+($.ui.ie6&&!e.inline?'<iframe src="javascript:false;" class="ui-datepicker-cover" frameborder="0"></iframe>':""),e._keyEvent=!1,B},_generateMonthYearHeader:function(e,t,n,r,i,s,o,u){var a=this._get(e,"changeMonth"),f=this._get(e,"changeYear"),l=this._get(e,"showMonthAfterYear"),c='<div class="ui-datepicker-title">',h="";if(s||!a)h+='<span class="ui-datepicker-month">'+o[t]+"</span>";else{var p=r&&r.getFullYear()==n,d=i&&i.getFullYear()==n;h+='<select class="ui-datepicker-month" data-handler="selectMonth" data-event="change">';for(var v=0;v<12;v++)(!p||v>=r.getMonth())&&(!d||v<=i.getMonth())&&(h+='<option value="'+v+'"'+(v==t?' selected="selected"':"")+">"+u[v]+"</option>");h+="</select>"}l||(c+=h+(s||!a||!f?"&#xa0;":""));if(!e.yearshtml){e.yearshtml="";if(s||!f)c+='<span class="ui-datepicker-year">'+n+"</span>";else{var m=this._get(e,"yearRange").split(":"),g=(new Date).getFullYear(),y=function(e){var t=e.match(/c[+-].*/)?n+parseInt(e.substring(1),10):e.match(/[+-].*/)?g+parseInt(e,10):parseInt(e,10);return isNaN(t)?g:t},b=y(m[0]),w=Math.max(b,y(m[1]||""));b=r?Math.max(b,r.getFullYear()):b,w=i?Math.min(w,i.getFullYear()):w,e.yearshtml+='<select class="ui-datepicker-year" data-handler="selectYear" data-event="change">';for(;b<=w;b++)e.yearshtml+='<option value="'+b+'"'+(b==n?' selected="selected"':"")+">"+b+"</option>";e.yearshtml+="</select>",c+=e.yearshtml,e.yearshtml=null}}return c+=this._get(e,"yearSuffix"),l&&(c+=(s||!a||!f?"&#xa0;":"")+h),c+="</div>",c},_adjustInstDate:function(e,t,n){var r=e.drawYear+(n=="Y"?t:0),i=e.drawMonth+(n=="M"?t:0),s=Math.min(e.selectedDay,this._getDaysInMonth(r,i))+(n=="D"?t:0),o=this._restrictMinMax(e,this._daylightSavingAdjust(new Date(r,i,s)));e.selectedDay=o.getDate(),e.drawMonth=e.selectedMonth=o.getMonth(),e.drawYear=e.selectedYear=o.getFullYear(),(n=="M"||n=="Y")&&this._notifyChange(e)},_restrictMinMax:function(e,t){var n=this._getMinMaxDate(e,"min"),r=this._getMinMaxDate(e,"max"),i=n&&t<n?n:t;return i=r&&i>r?r:i,i},_notifyChange:function(e){var t=this._get(e,"onChangeMonthYear");t&&t.apply(e.input?e.input[0]:null,[e.selectedYear,e.selectedMonth+1,e])},_getNumberOfMonths:function(e){var t=this._get(e,"numberOfMonths");return t==null?[1,1]:typeof t=="number"?[1,t]:t},_getMinMaxDate:function(e,t){return this._determineDate(e,this._get(e,t+"Date"),null)},_getDaysInMonth:function(e,t){return 32-this._daylightSavingAdjust(new Date(e,t,32)).getDate()},_getFirstDayOfMonth:function(e,t){return(new Date(e,t,1)).getDay()},_canAdjustMonth:function(e,t,n,r){var i=this._getNumberOfMonths(e),s=this._daylightSavingAdjust(new Date(n,r+(t<0?t:i[0]*i[1]),1));return t<0&&s.setDate(this._getDaysInMonth(s.getFullYear(),s.getMonth())),this._isInRange(e,s)},_isInRange:function(e,t){var n=this._getMinMaxDate(e,"min"),r=this._getMinMaxDate(e,"max");return(!n||t.getTime()>=n.getTime())&&(!r||t.getTime()<=r.getTime())},_getFormatConfig:function(e){var t=this._get(e,"shortYearCutoff");return t=typeof t!="string"?t:(new Date).getFullYear()%100+parseInt(t,10),{shortYearCutoff:t,dayNamesShort:this._get(e,"dayNamesShort"),dayNames:this._get(e,"dayNames"),monthNamesShort:this._get(e,"monthNamesShort"),monthNames:this._get(e,"monthNames")}},_formatDate:function(e,t,n,r){t||(e.currentDay=e.selectedDay,e.currentMonth=e.selectedMonth,e.currentYear=e.selectedYear);var i=t?typeof t=="object"?t:this._daylightSavingAdjust(new Date(r,n,t)):this._daylightSavingAdjust(new Date(e.currentYear,e.currentMonth,e.currentDay));return this.formatDate(this._get(e,"dateFormat"),i,this._getFormatConfig(e))}}),$.fn.datepicker=function(e){if(!this.length)return this;$.datepicker.initialized||($(document).mousedown($.datepicker._checkExternalClick).find(document.body).append($.datepicker.dpDiv),$.datepicker.initialized=!0);var t=Array.prototype.slice.call(arguments,1);return typeof e!="string"||e!="isDisabled"&&e!="getDate"&&e!="widget"?e=="option"&&arguments.length==2&&typeof arguments[1]=="string"?$.datepicker["_"+e+"Datepicker"].apply($.datepicker,[this[0]].concat(t)):this.each(function(){typeof e=="string"?$.datepicker["_"+e+"Datepicker"].apply($.datepicker,[this].concat(t)):$.datepicker._attachDatepicker(this,e)}):$.datepicker["_"+e+"Datepicker"].apply($.datepicker,[this[0]].concat(t))},$.datepicker=new Datepicker,$.datepicker.initialized=!1,$.datepicker.uuid=(new Date).getTime(),$.datepicker.version="1.9.2",window["DP_jQuery_"+dpuuid]=$})(jQuery);(function(e,t){var n="ui-dialog ui-widget ui-widget-content ui-corner-all ",r={buttons:!0,height:!0,maxHeight:!0,maxWidth:!0,minHeight:!0,minWidth:!0,width:!0},i={maxHeight:!0,maxWidth:!0,minHeight:!0,minWidth:!0};e.widget("ui.dialog",{version:"1.9.2",options:{autoOpen:!0,buttons:{},closeOnEscape:!0,closeText:"close",dialogClass:"",draggable:!0,hide:null,height:"auto",maxHeight:!1,maxWidth:!1,minHeight:150,minWidth:150,modal:!1,position:{my:"center",at:"center",of:window,collision:"fit",using:function(t){var n=e(this).css(t).offset().top;n<0&&e(this).css("top",t.top-n)}},resizable:!0,show:null,stack:!0,title:"",width:300,zIndex:1e3},_create:function(){this.originalTitle=this.element.attr("title"),typeof this.originalTitle!="string"&&(this.originalTitle=""),this.oldPosition={parent:this.element.parent(),index:this.element.parent().children().index(this.element)},this.options.title=this.options.title||this.originalTitle;var t=this,r=this.options,i=r.title||"&#160;",s,o,u,a,f;s=(this.uiDialog=e("<div>")).addClass(n+r.dialogClass).css({display:"none",outline:0,zIndex:r.zIndex}).attr("tabIndex",-1).keydown(function(n){r.closeOnEscape&&!n.isDefaultPrevented()&&n.keyCode&&n.keyCode===e.ui.keyCode.ESCAPE&&(t.close(n),n.preventDefault())}).mousedown(function(e){t.moveToTop(!1,e)}).appendTo("body"),this.element.show().removeAttr("title").addClass("ui-dialog-content ui-widget-content").appendTo(s),o=(this.uiDialogTitlebar=e("<div>")).addClass("ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix").bind("mousedown",function(){s.focus()}).prependTo(s),u=e("<a href='#'></a>").addClass("ui-dialog-titlebar-close ui-corner-all").attr("role","button").click(function(e){e.preventDefault(),t.close(e)}).appendTo(o),(this.uiDialogTitlebarCloseText=e("<span>")).addClass("ui-icon ui-icon-closethick").text(r.closeText).appendTo(u),a=e("<span>").uniqueId().addClass("ui-dialog-title").html(i).prependTo(o),f=(this.uiDialogButtonPane=e("<div>")).addClass("ui-dialog-buttonpane ui-widget-content ui-helper-clearfix"),(this.uiButtonSet=e("<div>")).addClass("ui-dialog-buttonset").appendTo(f),s.attr({role:"dialog","aria-labelledby":a.attr("id")}),o.find("*").add(o).disableSelection(),this._hoverable(u),this._focusable(u),r.draggable&&e.fn.draggable&&this._makeDraggable(),r.resizable&&e.fn.resizable&&this._makeResizable(),this._createButtons(r.buttons),this._isOpen=!1,e.fn.bgiframe&&s.bgiframe(),this._on(s,{keydown:function(t){if(!r.modal||t.keyCode!==e.ui.keyCode.TAB)return;var n=e(":tabbable",s),i=n.filter(":first"),o=n.filter(":last");if(t.target===o[0]&&!t.shiftKey)return i.focus(1),!1;if(t.target===i[0]&&t.shiftKey)return o.focus(1),!1}})},_init:function(){this.options.autoOpen&&this.open()},_destroy:function(){var e,t=this.oldPosition;this.overlay&&this.overlay.destroy(),this.uiDialog.hide(),this.element.removeClass("ui-dialog-content ui-widget-content").hide().appendTo("body"),this.uiDialog.remove(),this.originalTitle&&this.element.attr("title",this.originalTitle),e=t.parent.children().eq(t.index),e.length&&e[0]!==this.element[0]?e.before(this.element):t.parent.append(this.element)},widget:function(){return this.uiDialog},close:function(t){var n=this,r,i;if(!this._isOpen)return;if(!1===this._trigger("beforeClose",t))return;return this._isOpen=!1,this.overlay&&this.overlay.destroy(),this.options.hide?this._hide(this.uiDialog,this.options.hide,function(){n._trigger("close",t)}):(this.uiDialog.hide(),this._trigger("close",t)),e.ui.dialog.overlay.resize(),this.options.modal&&(r=0,e(".ui-dialog").each(function(){this!==n.uiDialog[0]&&(i=e(this).css("z-index"),isNaN(i)||(r=Math.max(r,i)))}),e.ui.dialog.maxZ=r),this},isOpen:function(){return this._isOpen},moveToTop:function(t,n){var r=this.options,i;return r.modal&&!t||!r.stack&&!r.modal?this._trigger("focus",n):(r.zIndex>e.ui.dialog.maxZ&&(e.ui.dialog.maxZ=r.zIndex),this.overlay&&(e.ui.dialog.maxZ+=1,e.ui.dialog.overlay.maxZ=e.ui.dialog.maxZ,this.overlay.$el.css("z-index",e.ui.dialog.overlay.maxZ)),i={scrollTop:this.element.scrollTop(),scrollLeft:this.element.scrollLeft()},e.ui.dialog.maxZ+=1,this.uiDialog.css("z-index",e.ui.dialog.maxZ),this.element.attr(i),this._trigger("focus",n),this)},open:function(){if(this._isOpen)return;var t,n=this.options,r=this.uiDialog;return this._size(),this._position(n.position),r.show(n.show),this.overlay=n.modal?new e.ui.dialog.overlay(this):null,this.moveToTop(!0),t=this.element.find(":tabbable"),t.length||(t=this.uiDialogButtonPane.find(":tabbable"),t.length||(t=r)),t.eq(0).focus(),this._isOpen=!0,this._trigger("open"),this},_createButtons:function(t){var n=this,r=!1;this.uiDialogButtonPane.remove(),this.uiButtonSet.empty(),typeof t=="object"&&t!==null&&e.each(t,function(){return!(r=!0)}),r?(e.each(t,function(t,r){var i,s;r=e.isFunction(r)?{click:r,text:t}:r,r=e.extend({type:"button"},r),s=r.click,r.click=function(){s.apply(n.element[0],arguments)},i=e("<button></button>",r).appendTo(n.uiButtonSet),e.fn.button&&i.button()}),this.uiDialog.addClass("ui-dialog-buttons"),this.uiDialogButtonPane.appendTo(this.uiDialog)):this.uiDialog.removeClass("ui-dialog-buttons")},_makeDraggable:function(){function r(e){return{position:e.position,offset:e.offset}}var t=this,n=this.options;this.uiDialog.draggable({cancel:".ui-dialog-content, .ui-dialog-titlebar-close",handle:".ui-dialog-titlebar",containment:"document",start:function(n,i){e(this).addClass("ui-dialog-dragging"),t._trigger("dragStart",n,r(i))},drag:function(e,n){t._trigger("drag",e,r(n))},stop:function(i,s){n.position=[s.position.left-t.document.scrollLeft(),s.position.top-t.document.scrollTop()],e(this).removeClass("ui-dialog-dragging"),t._trigger("dragStop",i,r(s)),e.ui.dialog.overlay.resize()}})},_makeResizable:function(n){function u(e){return{originalPosition:e.originalPosition,originalSize:e.originalSize,position:e.position,size:e.size}}n=n===t?this.options.resizable:n;var r=this,i=this.options,s=this.uiDialog.css("position"),o=typeof n=="string"?n:"n,e,s,w,se,sw,ne,nw";this.uiDialog.resizable({cancel:".ui-dialog-content",containment:"document",alsoResize:this.element,maxWidth:i.maxWidth,maxHeight:i.maxHeight,minWidth:i.minWidth,minHeight:this._minHeight(),handles:o,start:function(t,n){e(this).addClass("ui-dialog-resizing"),r._trigger("resizeStart",t,u(n))},resize:function(e,t){r._trigger("resize",e,u(t))},stop:function(t,n){e(this).removeClass("ui-dialog-resizing"),i.height=e(this).height(),i.width=e(this).width(),r._trigger("resizeStop",t,u(n)),e.ui.dialog.overlay.resize()}}).css("position",s).find(".ui-resizable-se").addClass("ui-icon ui-icon-grip-diagonal-se")},_minHeight:function(){var e=this.options;return e.height==="auto"?e.minHeight:Math.min(e.minHeight,e.height)},_position:function(t){var n=[],r=[0,0],i;if(t){if(typeof t=="string"||typeof t=="object"&&"0"in t)n=t.split?t.split(" "):[t[0],t[1]],n.length===1&&(n[1]=n[0]),e.each(["left","top"],function(e,t){+n[e]===n[e]&&(r[e]=n[e],n[e]=t)}),t={my:n[0]+(r[0]<0?r[0]:"+"+r[0])+" "+n[1]+(r[1]<0?r[1]:"+"+r[1]),at:n.join(" ")};t=e.extend({},e.ui.dialog.prototype.options.position,t)}else t=e.ui.dialog.prototype.options.position;i=this.uiDialog.is(":visible"),i||this.uiDialog.show(),this.uiDialog.position(t),i||this.uiDialog.hide()},_setOptions:function(t){var n=this,s={},o=!1;e.each(t,function(e,t){n._setOption(e,t),e in r&&(o=!0),e in i&&(s[e]=t)}),o&&this._size(),this.uiDialog.is(":data(resizable)")&&this.uiDialog.resizable("option",s)},_setOption:function(t,r){var i,s,o=this.uiDialog;switch(t){case"buttons":this._createButtons(r);break;case"closeText":this.uiDialogTitlebarCloseText.text(""+r);break;case"dialogClass":o.removeClass(this.options.dialogClass).addClass(n+r);break;case"disabled":r?o.addClass("ui-dialog-disabled"):o.removeClass("ui-dialog-disabled");break;case"draggable":i=o.is(":data(draggable)"),i&&!r&&o.draggable("destroy"),!i&&r&&this._makeDraggable();break;case"position":this._position(r);break;case"resizable":s=o.is(":data(resizable)"),s&&!r&&o.resizable("destroy"),s&&typeof r=="string"&&o.resizable("option","handles",r),!s&&r!==!1&&this._makeResizable(r);break;case"title":e(".ui-dialog-title",this.uiDialogTitlebar).html(""+(r||"&#160;"))}this._super(t,r)},_size:function(){var t,n,r,i=this.options,s=this.uiDialog.is(":visible");this.element.show().css({width:"auto",minHeight:0,height:0}),i.minWidth>i.width&&(i.width=i.minWidth),t=this.uiDialog.css({height:"auto",width:i.width}).outerHeight(),n=Math.max(0,i.minHeight-t),i.height==="auto"?e.support.minHeight?this.element.css({minHeight:n,height:"auto"}):(this.uiDialog.show(),r=this.element.css("height","auto").height(),s||this.uiDialog.hide(),this.element.height(Math.max(r,n))):this.element.height(Math.max(i.height-t,0)),this.uiDialog.is(":data(resizable)")&&this.uiDialog.resizable("option","minHeight",this._minHeight())}}),e.extend(e.ui.dialog,{uuid:0,maxZ:0,getTitleId:function(e){var t=e.attr("id");return t||(this.uuid+=1,t=this.uuid),"ui-dialog-title-"+t},overlay:function(t){this.$el=e.ui.dialog.overlay.create(t)}}),e.extend(e.ui.dialog.overlay,{instances:[],oldInstances:[],maxZ:0,events:e.map("focus,mousedown,mouseup,keydown,keypress,click".split(","),function(e){return e+".dialog-overlay"}).join(" "),create:function(t){this.instances.length===0&&(setTimeout(function(){e.ui.dialog.overlay.instances.length&&e(document).bind(e.ui.dialog.overlay.events,function(t){if(e(t.target).zIndex()<e.ui.dialog.overlay.maxZ)return!1})},1),e(window).bind("resize.dialog-overlay",e.ui.dialog.overlay.resize));var n=this.oldInstances.pop()||e("<div>").addClass("ui-widget-overlay");return e(document).bind("keydown.dialog-overlay",function(r){var i=e.ui.dialog.overlay.instances;i.length!==0&&i[i.length-1]===n&&t.options.closeOnEscape&&!r.isDefaultPrevented()&&r.keyCode&&r.keyCode===e.ui.keyCode.ESCAPE&&(t.close(r),r.preventDefault())}),n.appendTo(document.body).css({width:this.width(),height:this.height()}),e.fn.bgiframe&&n.bgiframe(),this.instances.push(n),n},destroy:function(t){var n=e.inArray(t,this.instances),r=0;n!==-1&&this.oldInstances.push(this.instances.splice(n,1)[0]),this.instances.length===0&&e([document,window]).unbind(".dialog-overlay"),t.height(0).width(0).remove(),e.each(this.instances,function(){r=Math.max(r,this.css("z-index"))}),this.maxZ=r},height:function(){var t,n;return e.ui.ie?(t=Math.max(document.documentElement.scrollHeight,document.body.scrollHeight),n=Math.max(document.documentElement.offsetHeight,document.body.offsetHeight),t<n?e(window).height()+"px":t+"px"):e(document).height()+"px"},width:function(){var t,n;return e.ui.ie?(t=Math.max(document.documentElement.scrollWidth,document.body.scrollWidth),n=Math.max(document.documentElement.offsetWidth,document.body.offsetWidth),t<n?e(window).width()+"px":t+"px"):e(document).width()+"px"},resize:function(){var t=e([]);e.each(e.ui.dialog.overlay.instances,function(){t=t.add(this)}),t.css({width:0,height:0}).css({width:e.ui.dialog.overlay.width(),height:e.ui.dialog.overlay.height()})}}),e.extend(e.ui.dialog.overlay.prototype,{destroy:function(){e.ui.dialog.overlay.destroy(this.$el)}})})(jQuery);(function(e,t){var n=!1;e.widget("ui.menu",{version:"1.9.2",defaultElement:"<ul>",delay:300,options:{icons:{submenu:"ui-icon-carat-1-e"},menus:"ul",position:{my:"left top",at:"right top"},role:"menu",blur:null,focus:null,select:null},_create:function(){this.activeMenu=this.element,this.element.uniqueId().addClass("ui-menu ui-widget ui-widget-content ui-corner-all").toggleClass("ui-menu-icons",!!this.element.find(".ui-icon").length).attr({role:this.options.role,tabIndex:0}).bind("click"+this.eventNamespace,e.proxy(function(e){this.options.disabled&&e.preventDefault()},this)),this.options.disabled&&this.element.addClass("ui-state-disabled").attr("aria-disabled","true"),this._on({"mousedown .ui-menu-item > a":function(e){e.preventDefault()},"click .ui-state-disabled > a":function(e){e.preventDefault()},"click .ui-menu-item:has(a)":function(t){var r=e(t.target).closest(".ui-menu-item");!n&&r.not(".ui-state-disabled").length&&(n=!0,this.select(t),r.has(".ui-menu").length?this.expand(t):this.element.is(":focus")||(this.element.trigger("focus",[!0]),this.active&&this.active.parents(".ui-menu").length===1&&clearTimeout(this.timer)))},"mouseenter .ui-menu-item":function(t){var n=e(t.currentTarget);n.siblings().children(".ui-state-active").removeClass("ui-state-active"),this.focus(t,n)},mouseleave:"collapseAll","mouseleave .ui-menu":"collapseAll",focus:function(e,t){var n=this.active||this.element.children(".ui-menu-item").eq(0);t||this.focus(e,n)},blur:function(t){this._delay(function(){e.contains(this.element[0],this.document[0].activeElement)||this.collapseAll(t)})},keydown:"_keydown"}),this.refresh(),this._on(this.document,{click:function(t){e(t.target).closest(".ui-menu").length||this.collapseAll(t),n=!1}})},_destroy:function(){this.element.removeAttr("aria-activedescendant").find(".ui-menu").andSelf().removeClass("ui-menu ui-widget ui-widget-content ui-corner-all ui-menu-icons").removeAttr("role").removeAttr("tabIndex").removeAttr("aria-labelledby").removeAttr("aria-expanded").removeAttr("aria-hidden").removeAttr("aria-disabled").removeUniqueId().show(),this.element.find(".ui-menu-item").removeClass("ui-menu-item").removeAttr("role").removeAttr("aria-disabled").children("a").removeUniqueId().removeClass("ui-corner-all ui-state-hover").removeAttr("tabIndex").removeAttr("role").removeAttr("aria-haspopup").children().each(function(){var t=e(this);t.data("ui-menu-submenu-carat")&&t.remove()}),this.element.find(".ui-menu-divider").removeClass("ui-menu-divider ui-widget-content")},_keydown:function(t){function a(e){return e.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g,"\\$&")}var n,r,i,s,o,u=!0;switch(t.keyCode){case e.ui.keyCode.PAGE_UP:this.previousPage(t);break;case e.ui.keyCode.PAGE_DOWN:this.nextPage(t);break;case e.ui.keyCode.HOME:this._move("first","first",t);break;case e.ui.keyCode.END:this._move("last","last",t);break;case e.ui.keyCode.UP:this.previous(t);break;case e.ui.keyCode.DOWN:this.next(t);break;case e.ui.keyCode.LEFT:this.collapse(t);break;case e.ui.keyCode.RIGHT:this.active&&!this.active.is(".ui-state-disabled")&&this.expand(t);break;case e.ui.keyCode.ENTER:case e.ui.keyCode.SPACE:this._activate(t);break;case e.ui.keyCode.ESCAPE:this.collapse(t);break;default:u=!1,r=this.previousFilter||"",i=String.fromCharCode(t.keyCode),s=!1,clearTimeout(this.filterTimer),i===r?s=!0:i=r+i,o=new RegExp("^"+a(i),"i"),n=this.activeMenu.children(".ui-menu-item").filter(function(){return o.test(e(this).children("a").text())}),n=s&&n.index(this.active.next())!==-1?this.active.nextAll(".ui-menu-item"):n,n.length||(i=String.fromCharCode(t.keyCode),o=new RegExp("^"+a(i),"i"),n=this.activeMenu.children(".ui-menu-item").filter(function(){return o.test(e(this).children("a").text())})),n.length?(this.focus(t,n),n.length>1?(this.previousFilter=i,this.filterTimer=this._delay(function(){delete this.previousFilter},1e3)):delete this.previousFilter):delete this.previousFilter}u&&t.preventDefault()},_activate:function(e){this.active.is(".ui-state-disabled")||(this.active.children("a[aria-haspopup='true']").length?this.expand(e):this.select(e))},refresh:function(){var t,n=this.options.icons.submenu,r=this.element.find(this.options.menus);r.filter(":not(.ui-menu)").addClass("ui-menu ui-widget ui-widget-content ui-corner-all").hide().attr({role:this.options.role,"aria-hidden":"true","aria-expanded":"false"}).each(function(){var t=e(this),r=t.prev("a"),i=e("<span>").addClass("ui-menu-icon ui-icon "+n).data("ui-menu-submenu-carat",!0);r.attr("aria-haspopup","true").prepend(i),t.attr("aria-labelledby",r.attr("id"))}),t=r.add(this.element),t.children(":not(.ui-menu-item):has(a)").addClass("ui-menu-item").attr("role","presentation").children("a").uniqueId().addClass("ui-corner-all").attr({tabIndex:-1,role:this._itemRole()}),t.children(":not(.ui-menu-item)").each(function(){var t=e(this);/[^\-—–\s]/.test(t.text())||t.addClass("ui-widget-content ui-menu-divider")}),t.children(".ui-state-disabled").attr("aria-disabled","true"),this.active&&!e.contains(this.element[0],this.active[0])&&this.blur()},_itemRole:function(){return{menu:"menuitem",listbox:"option"}[this.options.role]},focus:function(e,t){var n,r;this.blur(e,e&&e.type==="focus"),this._scrollIntoView(t),this.active=t.first(),r=this.active.children("a").addClass("ui-state-focus"),this.options.role&&this.element.attr("aria-activedescendant",r.attr("id")),this.active.parent().closest(".ui-menu-item").children("a:first").addClass("ui-state-active"),e&&e.type==="keydown"?this._close():this.timer=this._delay(function(){this._close()},this.delay),n=t.children(".ui-menu"),n.length&&/^mouse/.test(e.type)&&this._startOpening(n),this.activeMenu=t.parent(),this._trigger("focus",e,{item:t})},_scrollIntoView:function(t){var n,r,i,s,o,u;this._hasScroll()&&(n=parseFloat(e.css(this.activeMenu[0],"borderTopWidth"))||0,r=parseFloat(e.css(this.activeMenu[0],"paddingTop"))||0,i=t.offset().top-this.activeMenu.offset().top-n-r,s=this.activeMenu.scrollTop(),o=this.activeMenu.height(),u=t.height(),i<0?this.activeMenu.scrollTop(s+i):i+u>o&&this.activeMenu.scrollTop(s+i-o+u))},blur:function(e,t){t||clearTimeout(this.timer);if(!this.active)return;this.active.children("a").removeClass("ui-state-focus"),this.active=null,this._trigger("blur",e,{item:this.active})},_startOpening:function(e){clearTimeout(this.timer);if(e.attr("aria-hidden")!=="true")return;this.timer=this._delay(function(){this._close(),this._open(e)},this.delay)},_open:function(t){var n=e.extend({of:this.active},this.options.position);clearTimeout(this.timer),this.element.find(".ui-menu").not(t.parents(".ui-menu")).hide().attr("aria-hidden","true"),t.show().removeAttr("aria-hidden").attr("aria-expanded","true").position(n)},collapseAll:function(t,n){clearTimeout(this.timer),this.timer=this._delay(function(){var r=n?this.element:e(t&&t.target).closest(this.element.find(".ui-menu"));r.length||(r=this.element),this._close(r),this.blur(t),this.activeMenu=r},this.delay)},_close:function(e){e||(e=this.active?this.active.parent():this.element),e.find(".ui-menu").hide().attr("aria-hidden","true").attr("aria-expanded","false").end().find("a.ui-state-active").removeClass("ui-state-active")},collapse:function(e){var t=this.active&&this.active.parent().closest(".ui-menu-item",this.element);t&&t.length&&(this._close(),this.focus(e,t))},expand:function(e){var t=this.active&&this.active.children(".ui-menu ").children(".ui-menu-item").first();t&&t.length&&(this._open(t.parent()),this._delay(function(){this.focus(e,t)}))},next:function(e){this._move("next","first",e)},previous:function(e){this._move("prev","last",e)},isFirstItem:function(){return this.active&&!this.active.prevAll(".ui-menu-item").length},isLastItem:function(){return this.active&&!this.active.nextAll(".ui-menu-item").length},_move:function(e,t,n){var r;this.active&&(e==="first"||e==="last"?r=this.active[e==="first"?"prevAll":"nextAll"](".ui-menu-item").eq(-1):r=this.active[e+"All"](".ui-menu-item").eq(0));if(!r||!r.length||!this.active)r=this.activeMenu.children(".ui-menu-item")[t]();this.focus(n,r)},nextPage:function(t){var n,r,i;if(!this.active){this.next(t);return}if(this.isLastItem())return;this._hasScroll()?(r=this.active.offset().top,i=this.element.height(),this.active.nextAll(".ui-menu-item").each(function(){return n=e(this),n.offset().top-r-i<0}),this.focus(t,n)):this.focus(t,this.activeMenu.children(".ui-menu-item")[this.active?"last":"first"]())},previousPage:function(t){var n,r,i;if(!this.active){this.next(t);return}if(this.isFirstItem())return;this._hasScroll()?(r=this.active.offset().top,i=this.element.height(),this.active.prevAll(".ui-menu-item").each(function(){return n=e(this),n.offset().top-r+i>0}),this.focus(t,n)):this.focus(t,this.activeMenu.children(".ui-menu-item").first())},_hasScroll:function(){return this.element.outerHeight()<this.element.prop("scrollHeight")},select:function(t){this.active=this.active||e(t.target).closest(".ui-menu-item");var n={item:this.active};this.active.has(".ui-menu").length||this.collapseAll(t,!0),this._trigger("select",t,n)}})})(jQuery);(function(e,t){e.widget("ui.progressbar",{version:"1.9.2",options:{value:0,max:100},min:0,_create:function(){this.element.addClass("ui-progressbar ui-widget ui-widget-content ui-corner-all").attr({role:"progressbar","aria-valuemin":this.min,"aria-valuemax":this.options.max,"aria-valuenow":this._value()}),this.valueDiv=e("<div class='ui-progressbar-value ui-widget-header ui-corner-left'></div>").appendTo(this.element),this.oldValue=this._value(),this._refreshValue()},_destroy:function(){this.element.removeClass("ui-progressbar ui-widget ui-widget-content ui-corner-all").removeAttr("role").removeAttr("aria-valuemin").removeAttr("aria-valuemax").removeAttr("aria-valuenow"),this.valueDiv.remove()},value:function(e){return e===t?this._value():(this._setOption("value",e),this)},_setOption:function(e,t){e==="value"&&(this.options.value=t,this._refreshValue(),this._value()===this.options.max&&this._trigger("complete")),this._super(e,t)},_value:function(){var e=this.options.value;return typeof e!="number"&&(e=0),Math.min(this.options.max,Math.max(this.min,e))},_percentage:function(){return 100*this._value()/this.options.max},_refreshValue:function(){var e=this.value(),t=this._percentage();this.oldValue!==e&&(this.oldValue=e,this._trigger("change")),this.valueDiv.toggle(e>this.min).toggleClass("ui-corner-right",e===this.options.max).width(t.toFixed(0)+"%"),this.element.attr("aria-valuenow",e)}})})(jQuery);(function(e,t){var n=5;e.widget("ui.slider",e.ui.mouse,{version:"1.9.2",widgetEventPrefix:"slide",options:{animate:!1,distance:0,max:100,min:0,orientation:"horizontal",range:!1,step:1,value:0,values:null},_create:function(){var t,r,i=this.options,s=this.element.find(".ui-slider-handle").addClass("ui-state-default ui-corner-all"),o="<a class='ui-slider-handle ui-state-default ui-corner-all' href='#'></a>",u=[];this._keySliding=!1,this._mouseSliding=!1,this._animateOff=!0,this._handleIndex=null,this._detectOrientation(),this._mouseInit(),this.element.addClass("ui-slider ui-slider-"+this.orientation+" ui-widget"+" ui-widget-content"+" ui-corner-all"+(i.disabled?" ui-slider-disabled ui-disabled":"")),this.range=e([]),i.range&&(i.range===!0&&(i.values||(i.values=[this._valueMin(),this._valueMin()]),i.values.length&&i.values.length!==2&&(i.values=[i.values[0],i.values[0]])),this.range=e("<div></div>").appendTo(this.element).addClass("ui-slider-range ui-widget-header"+(i.range==="min"||i.range==="max"?" ui-slider-range-"+i.range:""))),r=i.values&&i.values.length||1;for(t=s.length;t<r;t++)u.push(o);this.handles=s.add(e(u.join("")).appendTo(this.element)),this.handle=this.handles.eq(0),this.handles.add(this.range).filter("a").click(function(e){e.preventDefault()}).mouseenter(function(){i.disabled||e(this).addClass("ui-state-hover")}).mouseleave(function(){e(this).removeClass("ui-state-hover")}).focus(function(){i.disabled?e(this).blur():(e(".ui-slider .ui-state-focus").removeClass("ui-state-focus"),e(this).addClass("ui-state-focus"))}).blur(function(){e(this).removeClass("ui-state-focus")}),this.handles.each(function(t){e(this).data("ui-slider-handle-index",t)}),this._on(this.handles,{keydown:function(t){var r,i,s,o,u=e(t.target).data("ui-slider-handle-index");switch(t.keyCode){case e.ui.keyCode.HOME:case e.ui.keyCode.END:case e.ui.keyCode.PAGE_UP:case e.ui.keyCode.PAGE_DOWN:case e.ui.keyCode.UP:case e.ui.keyCode.RIGHT:case e.ui.keyCode.DOWN:case e.ui.keyCode.LEFT:t.preventDefault();if(!this._keySliding){this._keySliding=!0,e(t.target).addClass("ui-state-active"),r=this._start(t,u);if(r===!1)return}}o=this.options.step,this.options.values&&this.options.values.length?i=s=this.values(u):i=s=this.value();switch(t.keyCode){case e.ui.keyCode.HOME:s=this._valueMin();break;case e.ui.keyCode.END:s=this._valueMax();break;case e.ui.keyCode.PAGE_UP:s=this._trimAlignValue(i+(this._valueMax()-this._valueMin())/n);break;case e.ui.keyCode.PAGE_DOWN:s=this._trimAlignValue(i-(this._valueMax()-this._valueMin())/n);break;case e.ui.keyCode.UP:case e.ui.keyCode.RIGHT:if(i===this._valueMax())return;s=this._trimAlignValue(i+o);break;case e.ui.keyCode.DOWN:case e.ui.keyCode.LEFT:if(i===this._valueMin())return;s=this._trimAlignValue(i-o)}this._slide(t,u,s)},keyup:function(t){var n=e(t.target).data("ui-slider-handle-index");this._keySliding&&(this._keySliding=!1,this._stop(t,n),this._change(t,n),e(t.target).removeClass("ui-state-active"))}}),this._refreshValue(),this._animateOff=!1},_destroy:function(){this.handles.remove(),this.range.remove(),this.element.removeClass("ui-slider ui-slider-horizontal ui-slider-vertical ui-slider-disabled ui-widget ui-widget-content ui-corner-all"),this._mouseDestroy()},_mouseCapture:function(t){var n,r,i,s,o,u,a,f,l=this,c=this.options;return c.disabled?!1:(this.elementSize={width:this.element.outerWidth(),height:this.element.outerHeight()},this.elementOffset=this.element.offset(),n={x:t.pageX,y:t.pageY},r=this._normValueFromMouse(n),i=this._valueMax()-this._valueMin()+1,this.handles.each(function(t){var n=Math.abs(r-l.values(t));i>n&&(i=n,s=e(this),o=t)}),c.range===!0&&this.values(1)===c.min&&(o+=1,s=e(this.handles[o])),u=this._start(t,o),u===!1?!1:(this._mouseSliding=!0,this._handleIndex=o,s.addClass("ui-state-active").focus(),a=s.offset(),f=!e(t.target).parents().andSelf().is(".ui-slider-handle"),this._clickOffset=f?{left:0,top:0}:{left:t.pageX-a.left-s.width()/2,top:t.pageY-a.top-s.height()/2-(parseInt(s.css("borderTopWidth"),10)||0)-(parseInt(s.css("borderBottomWidth"),10)||0)+(parseInt(s.css("marginTop"),10)||0)},this.handles.hasClass("ui-state-hover")||this._slide(t,o,r),this._animateOff=!0,!0))},_mouseStart:function(){return!0},_mouseDrag:function(e){var t={x:e.pageX,y:e.pageY},n=this._normValueFromMouse(t);return this._slide(e,this._handleIndex,n),!1},_mouseStop:function(e){return this.handles.removeClass("ui-state-active"),this._mouseSliding=!1,this._stop(e,this._handleIndex),this._change(e,this._handleIndex),this._handleIndex=null,this._clickOffset=null,this._animateOff=!1,!1},_detectOrientation:function(){this.orientation=this.options.orientation==="vertical"?"vertical":"horizontal"},_normValueFromMouse:function(e){var t,n,r,i,s;return this.orientation==="horizontal"?(t=this.elementSize.width,n=e.x-this.elementOffset.left-(this._clickOffset?this._clickOffset.left:0)):(t=this.elementSize.height,n=e.y-this.elementOffset.top-(this._clickOffset?this._clickOffset.top:0)),r=n/t,r>1&&(r=1),r<0&&(r=0),this.orientation==="vertical"&&(r=1-r),i=this._valueMax()-this._valueMin(),s=this._valueMin()+r*i,this._trimAlignValue(s)},_start:function(e,t){var n={handle:this.handles[t],value:this.value()};return this.options.values&&this.options.values.length&&(n.value=this.values(t),n.values=this.values()),this._trigger("start",e,n)},_slide:function(e,t,n){var r,i,s;this.options.values&&this.options.values.length?(r=this.values(t?0:1),this.options.values.length===2&&this.options.range===!0&&(t===0&&n>r||t===1&&n<r)&&(n=r),n!==this.values(t)&&(i=this.values(),i[t]=n,s=this._trigger("slide",e,{handle:this.handles[t],value:n,values:i}),r=this.values(t?0:1),s!==!1&&this.values(t,n,!0))):n!==this.value()&&(s=this._trigger("slide",e,{handle:this.handles[t],value:n}),s!==!1&&this.value(n))},_stop:function(e,t){var n={handle:this.handles[t],value:this.value()};this.options.values&&this.options.values.length&&(n.value=this.values(t),n.values=this.values()),this._trigger("stop",e,n)},_change:function(e,t){if(!this._keySliding&&!this._mouseSliding){var n={handle:this.handles[t],value:this.value()};this.options.values&&this.options.values.length&&(n.value=this.values(t),n.values=this.values()),this._trigger("change",e,n)}},value:function(e){if(arguments.length){this.options.value=this._trimAlignValue(e),this._refreshValue(),this._change(null,0);return}return this._value()},values:function(t,n){var r,i,s;if(arguments.length>1){this.options.values[t]=this._trimAlignValue(n),this._refreshValue(),this._change(null,t);return}if(!arguments.length)return this._values();if(!e.isArray(arguments[0]))return this.options.values&&this.options.values.length?this._values(t):this.value();r=this.options.values,i=arguments[0];for(s=0;s<r.length;s+=1)r[s]=this._trimAlignValue(i[s]),this._change(null,s);this._refreshValue()},_setOption:function(t,n){var r,i=0;e.isArray(this.options.values)&&(i=this.options.values.length),e.Widget.prototype._setOption.apply(this,arguments);switch(t){case"disabled":n?(this.handles.filter(".ui-state-focus").blur(),this.handles.removeClass("ui-state-hover"),this.handles.prop("disabled",!0),this.element.addClass("ui-disabled")):(this.handles.prop("disabled",!1),this.element.removeClass("ui-disabled"));break;case"orientation":this._detectOrientation(),this.element.removeClass("ui-slider-horizontal ui-slider-vertical").addClass("ui-slider-"+this.orientation),this._refreshValue();break;case"value":this._animateOff=!0,this._refreshValue(),this._change(null,0),this._animateOff=!1;break;case"values":this._animateOff=!0,this._refreshValue();for(r=0;r<i;r+=1)this._change(null,r);this._animateOff=!1;break;case"min":case"max":this._animateOff=!0,this._refreshValue(),this._animateOff=!1}},_value:function(){var e=this.options.value;return e=this._trimAlignValue(e),e},_values:function(e){var t,n,r;if(arguments.length)return t=this.options.values[e],t=this._trimAlignValue(t),t;n=this.options.values.slice();for(r=0;r<n.length;r+=1)n[r]=this._trimAlignValue(n[r]);return n},_trimAlignValue:function(e){if(e<=this._valueMin())return this._valueMin();if(e>=this._valueMax())return this._valueMax();var t=this.options.step>0?this.options.step:1,n=(e-this._valueMin())%t,r=e-n;return Math.abs(n)*2>=t&&(r+=n>0?t:-t),parseFloat(r.toFixed(5))},_valueMin:function(){return this.options.min},_valueMax:function(){return this.options.max},_refreshValue:function(){var t,n,r,i,s,o=this.options.range,u=this.options,a=this,f=this._animateOff?!1:u.animate,l={};this.options.values&&this.options.values.length?this.handles.each(function(r){n=(a.values(r)-a._valueMin())/(a._valueMax()-a._valueMin())*100,l[a.orientation==="horizontal"?"left":"bottom"]=n+"%",e(this).stop(1,1)[f?"animate":"css"](l,u.animate),a.options.range===!0&&(a.orientation==="horizontal"?(r===0&&a.range.stop(1,1)[f?"animate":"css"]({left:n+"%"},u.animate),r===1&&a.range[f?"animate":"css"]({width:n-t+"%"},{queue:!1,duration:u.animate})):(r===0&&a.range.stop(1,1)[f?"animate":"css"]({bottom:n+"%"},u.animate),r===1&&a.range[f?"animate":"css"]({height:n-t+"%"},{queue:!1,duration:u.animate}))),t=n}):(r=this.value(),i=this._valueMin(),s=this._valueMax(),n=s!==i?(r-i)/(s-i)*100:0,l[this.orientation==="horizontal"?"left":"bottom"]=n+"%",this.handle.stop(1,1)[f?"animate":"css"](l,u.animate),o==="min"&&this.orientation==="horizontal"&&this.range.stop(1,1)[f?"animate":"css"]({width:n+"%"},u.animate),o==="max"&&this.orientation==="horizontal"&&this.range[f?"animate":"css"]({width:100-n+"%"},{queue:!1,duration:u.animate}),o==="min"&&this.orientation==="vertical"&&this.range.stop(1,1)[f?"animate":"css"]({height:n+"%"},u.animate),o==="max"&&this.orientation==="vertical"&&this.range[f?"animate":"css"]({height:100-n+"%"},{queue:!1,duration:u.animate}))}})})(jQuery);(function(e){function t(e){return function(){var t=this.element.val();e.apply(this,arguments),this._refresh(),t!==this.element.val()&&this._trigger("change")}}e.widget("ui.spinner",{version:"1.9.2",defaultElement:"<input>",widgetEventPrefix:"spin",options:{culture:null,icons:{down:"ui-icon-triangle-1-s",up:"ui-icon-triangle-1-n"},incremental:!0,max:null,min:null,numberFormat:null,page:10,step:1,change:null,spin:null,start:null,stop:null},_create:function(){this._setOption("max",this.options.max),this._setOption("min",this.options.min),this._setOption("step",this.options.step),this._value(this.element.val(),!0),this._draw(),this._on(this._events),this._refresh(),this._on(this.window,{beforeunload:function(){this.element.removeAttr("autocomplete")}})},_getCreateOptions:function(){var t={},n=this.element;return e.each(["min","max","step"],function(e,r){var i=n.attr(r);i!==undefined&&i.length&&(t[r]=i)}),t},_events:{keydown:function(e){this._start(e)&&this._keydown(e)&&e.preventDefault()},keyup:"_stop",focus:function(){this.previous=this.element.val()},blur:function(e){if(this.cancelBlur){delete this.cancelBlur;return}this._refresh(),this.previous!==this.element.val()&&this._trigger("change",e)},mousewheel:function(e,t){if(!t)return;if(!this.spinning&&!this._start(e))return!1;this._spin((t>0?1:-1)*this.options.step,e),clearTimeout(this.mousewheelTimer),this.mousewheelTimer=this._delay(function(){this.spinning&&this._stop(e)},100),e.preventDefault()},"mousedown .ui-spinner-button":function(t){function r(){var e=this.element[0]===this.document[0].activeElement;e||(this.element.focus(),this.previous=n,this._delay(function(){this.previous=n}))}var n;n=this.element[0]===this.document[0].activeElement?this.previous:this.element.val(),t.preventDefault(),r.call(this),this.cancelBlur=!0,this._delay(function(){delete this.cancelBlur,r.call(this)});if(this._start(t)===!1)return;this._repeat(null,e(t.currentTarget).hasClass("ui-spinner-up")?1:-1,t)},"mouseup .ui-spinner-button":"_stop","mouseenter .ui-spinner-button":function(t){if(!e(t.currentTarget).hasClass("ui-state-active"))return;if(this._start(t)===!1)return!1;this._repeat(null,e(t.currentTarget).hasClass("ui-spinner-up")?1:-1,t)},"mouseleave .ui-spinner-button":"_stop"},_draw:function(){var e=this.uiSpinner=this.element.addClass("ui-spinner-input").attr("autocomplete","off").wrap(this._uiSpinnerHtml()).parent().append(this._buttonHtml());this.element.attr("role","spinbutton"),this.buttons=e.find(".ui-spinner-button").attr("tabIndex",-1).button().removeClass("ui-corner-all"),this.buttons.height()>Math.ceil(e.height()*.5)&&e.height()>0&&e.height(e.height()),this.options.disabled&&this.disable()},_keydown:function(t){var n=this.options,r=e.ui.keyCode;switch(t.keyCode){case r.UP:return this._repeat(null,1,t),!0;case r.DOWN:return this._repeat(null,-1,t),!0;case r.PAGE_UP:return this._repeat(null,n.page,t),!0;case r.PAGE_DOWN:return this._repeat(null,-n.page,t),!0}return!1},_uiSpinnerHtml:function(){return"<span class='ui-spinner ui-widget ui-widget-content ui-corner-all'></span>"},_buttonHtml:function(){return"<a class='ui-spinner-button ui-spinner-up ui-corner-tr'><span class='ui-icon "+this.options.icons.up+"'>&#9650;</span>"+"</a>"+"<a class='ui-spinner-button ui-spinner-down ui-corner-br'>"+"<span class='ui-icon "+this.options.icons.down+"'>&#9660;</span>"+"</a>"},_start:function(e){return!this.spinning&&this._trigger("start",e)===!1?!1:(this.counter||(this.counter=1),this.spinning=!0,!0)},_repeat:function(e,t,n){e=e||500,clearTimeout(this.timer),this.timer=this._delay(function(){this._repeat(40,t,n)},e),this._spin(t*this.options.step,n)},_spin:function(e,t){var n=this.value()||0;this.counter||(this.counter=1),n=this._adjustValue(n+e*this._increment(this.counter));if(!this.spinning||this._trigger("spin",t,{value:n})!==!1)this._value(n),this.counter++},_increment:function(t){var n=this.options.incremental;return n?e.isFunction(n)?n(t):Math.floor(t*t*t/5e4-t*t/500+17*t/200+1):1},_precision:function(){var e=this._precisionOf(this.options.step);return this.options.min!==null&&(e=Math.max(e,this._precisionOf(this.options.min))),e},_precisionOf:function(e){var t=e.toString(),n=t.indexOf(".");return n===-1?0:t.length-n-1},_adjustValue:function(e){var t,n,r=this.options;return t=r.min!==null?r.min:0,n=e-t,n=Math.round(n/r.step)*r.step,e=t+n,e=parseFloat(e.toFixed(this._precision())),r.max!==null&&e>r.max?r.max:r.min!==null&&e<r.min?r.min:e},_stop:function(e){if(!this.spinning)return;clearTimeout(this.timer),clearTimeout(this.mousewheelTimer),this.counter=0,this.spinning=!1,this._trigger("stop",e)},_setOption:function(e,t){if(e==="culture"||e==="numberFormat"){var n=this._parse(this.element.val());this.options[e]=t,this.element.val(this._format(n));return}(e==="max"||e==="min"||e==="step")&&typeof t=="string"&&(t=this._parse(t)),this._super(e,t),e==="disabled"&&(t?(this.element.prop("disabled",!0),this.buttons.button("disable")):(this.element.prop("disabled",!1),this.buttons.button("enable")))},_setOptions:t(function(e){this._super(e),this._value(this.element.val())}),_parse:function(e){return typeof e=="string"&&e!==""&&(e=window.Globalize&&this.options.numberFormat?Globalize.parseFloat(e,10,this.options.culture):+e),e===""||isNaN(e)?null:e},_format:function(e){return e===""?"":window.Globalize&&this.options.numberFormat?Globalize.format(e,this.options.numberFormat,this.options.culture):e},_refresh:function(){this.element.attr({"aria-valuemin":this.options.min,"aria-valuemax":this.options.max,"aria-valuenow":this._parse(this.element.val())})},_value:function(e,t){var n;e!==""&&(n=this._parse(e),n!==null&&(t||(n=this._adjustValue(n)),e=this._format(n))),this.element.val(e),this._refresh()},_destroy:function(){this.element.removeClass("ui-spinner-input").prop("disabled",!1).removeAttr("autocomplete").removeAttr("role").removeAttr("aria-valuemin").removeAttr("aria-valuemax").removeAttr("aria-valuenow"),this.uiSpinner.replaceWith(this.element)},stepUp:t(function(e){this._stepUp(e)}),_stepUp:function(e){this._spin((e||1)*this.options.step)},stepDown:t(function(e){this._stepDown(e)}),_stepDown:function(e){this._spin((e||1)*-this.options.step)},pageUp:t(function(e){this._stepUp((e||1)*this.options.page)}),pageDown:t(function(e){this._stepDown((e||1)*this.options.page)}),value:function(e){if(!arguments.length)return this._parse(this.element.val());t(this._value).call(this,e)},widget:function(){return this.uiSpinner}})})(jQuery);(function(e,t){function i(){return++n}function s(e){return e.hash.length>1&&e.href.replace(r,"")===location.href.replace(r,"").replace(/\s/g,"%20")}var n=0,r=/#.*$/;e.widget("ui.tabs",{version:"1.9.2",delay:300,options:{active:null,collapsible:!1,event:"click",heightStyle:"content",hide:null,show:null,activate:null,beforeActivate:null,beforeLoad:null,load:null},_create:function(){var t=this,n=this.options,r=n.active,i=location.hash.substring(1);this.running=!1,this.element.addClass("ui-tabs ui-widget ui-widget-content ui-corner-all").toggleClass("ui-tabs-collapsible",n.collapsible).delegate(".ui-tabs-nav > li","mousedown"+this.eventNamespace,function(t){e(this).is(".ui-state-disabled")&&t.preventDefault()}).delegate(".ui-tabs-anchor","focus"+this.eventNamespace,function(){e(this).closest("li").is(".ui-state-disabled")&&this.blur()}),this._processTabs();if(r===null){i&&this.tabs.each(function(t,n){if(e(n).attr("aria-controls")===i)return r=t,!1}),r===null&&(r=this.tabs.index(this.tabs.filter(".ui-tabs-active")));if(r===null||r===-1)r=this.tabs.length?0:!1}r!==!1&&(r=this.tabs.index(this.tabs.eq(r)),r===-1&&(r=n.collapsible?!1:0)),n.active=r,!n.collapsible&&n.active===!1&&this.anchors.length&&(n.active=0),e.isArray(n.disabled)&&(n.disabled=e.unique(n.disabled.concat(e.map(this.tabs.filter(".ui-state-disabled"),function(e){return t.tabs.index(e)}))).sort()),this.options.active!==!1&&this.anchors.length?this.active=this._findActive(this.options.active):this.active=e(),this._refresh(),this.active.length&&this.load(n.active)},_getCreateEventData:function(){return{tab:this.active,panel:this.active.length?this._getPanelForTab(this.active):e()}},_tabKeydown:function(t){var n=e(this.document[0].activeElement).closest("li"),r=this.tabs.index(n),i=!0;if(this._handlePageNav(t))return;switch(t.keyCode){case e.ui.keyCode.RIGHT:case e.ui.keyCode.DOWN:r++;break;case e.ui.keyCode.UP:case e.ui.keyCode.LEFT:i=!1,r--;break;case e.ui.keyCode.END:r=this.anchors.length-1;break;case e.ui.keyCode.HOME:r=0;break;case e.ui.keyCode.SPACE:t.preventDefault(),clearTimeout(this.activating),this._activate(r);return;case e.ui.keyCode.ENTER:t.preventDefault(),clearTimeout(this.activating),this._activate(r===this.options.active?!1:r);return;default:return}t.preventDefault(),clearTimeout(this.activating),r=this._focusNextTab(r,i),t.ctrlKey||(n.attr("aria-selected","false"),this.tabs.eq(r).attr("aria-selected","true"),this.activating=this._delay(function(){this.option("active",r)},this.delay))},_panelKeydown:function(t){if(this._handlePageNav(t))return;t.ctrlKey&&t.keyCode===e.ui.keyCode.UP&&(t.preventDefault(),this.active.focus())},_handlePageNav:function(t){if(t.altKey&&t.keyCode===e.ui.keyCode.PAGE_UP)return this._activate(this._focusNextTab(this.options.active-1,!1)),!0;if(t.altKey&&t.keyCode===e.ui.keyCode.PAGE_DOWN)return this._activate(this._focusNextTab(this.options.active+1,!0)),!0},_findNextTab:function(t,n){function i(){return t>r&&(t=0),t<0&&(t=r),t}var r=this.tabs.length-1;while(e.inArray(i(),this.options.disabled)!==-1)t=n?t+1:t-1;return t},_focusNextTab:function(e,t){return e=this._findNextTab(e,t),this.tabs.eq(e).focus(),e},_setOption:function(e,t){if(e==="active"){this._activate(t);return}if(e==="disabled"){this._setupDisabled(t);return}this._super(e,t),e==="collapsible"&&(this.element.toggleClass("ui-tabs-collapsible",t),!t&&this.options.active===!1&&this._activate(0)),e==="event"&&this._setupEvents(t),e==="heightStyle"&&this._setupHeightStyle(t)},_tabId:function(e){return e.attr("aria-controls")||"ui-tabs-"+i()},_sanitizeSelector:function(e){return e?e.replace(/[!"$%&'()*+,.\/:;<=>?@\[\]\^`{|}~]/g,"\\$&"):""},refresh:function(){var t=this.options,n=this.tablist.children(":has(a[href])");t.disabled=e.map(n.filter(".ui-state-disabled"),function(e){return n.index(e)}),this._processTabs(),t.active===!1||!this.anchors.length?(t.active=!1,this.active=e()):this.active.length&&!e.contains(this.tablist[0],this.active[0])?this.tabs.length===t.disabled.length?(t.active=!1,this.active=e()):this._activate(this._findNextTab(Math.max(0,t.active-1),!1)):t.active=this.tabs.index(this.active),this._refresh()},_refresh:function(){this._setupDisabled(this.options.disabled),this._setupEvents(this.options.event),this._setupHeightStyle(this.options.heightStyle),this.tabs.not(this.active).attr({"aria-selected":"false",tabIndex:-1}),this.panels.not(this._getPanelForTab(this.active)).hide().attr({"aria-expanded":"false","aria-hidden":"true"}),this.active.length?(this.active.addClass("ui-tabs-active ui-state-active").attr({"aria-selected":"true",tabIndex:0}),this._getPanelForTab(this.active).show().attr({"aria-expanded":"true","aria-hidden":"false"})):this.tabs.eq(0).attr("tabIndex",0)},_processTabs:function(){var t=this;this.tablist=this._getList().addClass("ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all").attr("role","tablist"),this.tabs=this.tablist.find("> li:has(a[href])").addClass("ui-state-default ui-corner-top").attr({role:"tab",tabIndex:-1}),this.anchors=this.tabs.map(function(){return e("a",this)[0]}).addClass("ui-tabs-anchor").attr({role:"presentation",tabIndex:-1}),this.panels=e(),this.anchors.each(function(n,r){var i,o,u,a=e(r).uniqueId().attr("id"),f=e(r).closest("li"),l=f.attr("aria-controls");s(r)?(i=r.hash,o=t.element.find(t._sanitizeSelector(i))):(u=t._tabId(f),i="#"+u,o=t.element.find(i),o.length||(o=t._createPanel(u),o.insertAfter(t.panels[n-1]||t.tablist)),o.attr("aria-live","polite")),o.length&&(t.panels=t.panels.add(o)),l&&f.data("ui-tabs-aria-controls",l),f.attr({"aria-controls":i.substring(1),"aria-labelledby":a}),o.attr("aria-labelledby",a)}),this.panels.addClass("ui-tabs-panel ui-widget-content ui-corner-bottom").attr("role","tabpanel")},_getList:function(){return this.element.find("ol,ul").eq(0)},_createPanel:function(t){return e("<div>").attr("id",t).addClass("ui-tabs-panel ui-widget-content ui-corner-bottom").data("ui-tabs-destroy",!0)},_setupDisabled:function(t){e.isArray(t)&&(t.length?t.length===this.anchors.length&&(t=!0):t=!1);for(var n=0,r;r=this.tabs[n];n++)t===!0||e.inArray(n,t)!==-1?e(r).addClass("ui-state-disabled").attr("aria-disabled","true"):e(r).removeClass("ui-state-disabled").removeAttr("aria-disabled");this.options.disabled=t},_setupEvents:function(t){var n={click:function(e){e.preventDefault()}};t&&e.each(t.split(" "),function(e,t){n[t]="_eventHandler"}),this._off(this.anchors.add(this.tabs).add(this.panels)),this._on(this.anchors,n),this._on(this.tabs,{keydown:"_tabKeydown"}),this._on(this.panels,{keydown:"_panelKeydown"}),this._focusable(this.tabs),this._hoverable(this.tabs)},_setupHeightStyle:function(t){var n,r,i=this.element.parent();t==="fill"?(e.support.minHeight||(r=i.css("overflow"),i.css("overflow","hidden")),n=i.height(),this.element.siblings(":visible").each(function(){var t=e(this),r=t.css("position");if(r==="absolute"||r==="fixed")return;n-=t.outerHeight(!0)}),r&&i.css("overflow",r),this.element.children().not(this.panels).each(function(){n-=e(this).outerHeight(!0)}),this.panels.each(function(){e(this).height(Math.max(0,n-e(this).innerHeight()+e(this).height()))}).css("overflow","auto")):t==="auto"&&(n=0,this.panels.each(function(){n=Math.max(n,e(this).height("").height())}).height(n))},_eventHandler:function(t){var n=this.options,r=this.active,i=e(t.currentTarget),s=i.closest("li"),o=s[0]===r[0],u=o&&n.collapsible,a=u?e():this._getPanelForTab(s),f=r.length?this._getPanelForTab(r):e(),l={oldTab:r,oldPanel:f,newTab:u?e():s,newPanel:a};t.preventDefault();if(s.hasClass("ui-state-disabled")||s.hasClass("ui-tabs-loading")||this.running||o&&!n.collapsible||this._trigger("beforeActivate",t,l)===!1)return;n.active=u?!1:this.tabs.index(s),this.active=o?e():s,this.xhr&&this.xhr.abort(),!f.length&&!a.length&&e.error("jQuery UI Tabs: Mismatching fragment identifier."),a.length&&this.load(this.tabs.index(s),t),this._toggle(t,l)},_toggle:function(t,n){function o(){r.running=!1,r._trigger("activate",t,n)}function u(){n.newTab.closest("li").addClass("ui-tabs-active ui-state-active"),i.length&&r.options.show?r._show(i,r.options.show,o):(i.show(),o())}var r=this,i=n.newPanel,s=n.oldPanel;this.running=!0,s.length&&this.options.hide?this._hide(s,this.options.hide,function(){n.oldTab.closest("li").removeClass("ui-tabs-active ui-state-active"),u()}):(n.oldTab.closest("li").removeClass("ui-tabs-active ui-state-active"),s.hide(),u()),s.attr({"aria-expanded":"false","aria-hidden":"true"}),n.oldTab.attr("aria-selected","false"),i.length&&s.length?n.oldTab.attr("tabIndex",-1):i.length&&this.tabs.filter(function(){return e(this).attr("tabIndex")===0}).attr("tabIndex",-1),i.attr({"aria-expanded":"true","aria-hidden":"false"}),n.newTab.attr({"aria-selected":"true",tabIndex:0})},_activate:function(t){var n,r=this._findActive(t);if(r[0]===this.active[0])return;r.length||(r=this.active),n=r.find(".ui-tabs-anchor")[0],this._eventHandler({target:n,currentTarget:n,preventDefault:e.noop})},_findActive:function(t){return t===!1?e():this.tabs.eq(t)},_getIndex:function(e){return typeof e=="string"&&(e=this.anchors.index(this.anchors.filter("[href$='"+e+"']"))),e},_destroy:function(){this.xhr&&this.xhr.abort(),this.element.removeClass("ui-tabs ui-widget ui-widget-content ui-corner-all ui-tabs-collapsible"),this.tablist.removeClass("ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all").removeAttr("role"),this.anchors.removeClass("ui-tabs-anchor").removeAttr("role").removeAttr("tabIndex").removeData("href.tabs").removeData("load.tabs").removeUniqueId(),this.tabs.add(this.panels).each(function(){e.data(this,"ui-tabs-destroy")?e(this).remove():e(this).removeClass("ui-state-default ui-state-active ui-state-disabled ui-corner-top ui-corner-bottom ui-widget-content ui-tabs-active ui-tabs-panel").removeAttr("tabIndex").removeAttr("aria-live").removeAttr("aria-busy").removeAttr("aria-selected").removeAttr("aria-labelledby").removeAttr("aria-hidden").removeAttr("aria-expanded").removeAttr("role")}),this.tabs.each(function(){var t=e(this),n=t.data("ui-tabs-aria-controls");n?t.attr("aria-controls",n):t.removeAttr("aria-controls")}),this.panels.show(),this.options.heightStyle!=="content"&&this.panels.css("height","")},enable:function(n){var r=this.options.disabled;if(r===!1)return;n===t?r=!1:(n=this._getIndex(n),e.isArray(r)?r=e.map(r,function(e){return e!==n?e:null}):r=e.map(this.tabs,function(e,t){return t!==n?t:null})),this._setupDisabled(r)},disable:function(n){var r=this.options.disabled;if(r===!0)return;if(n===t)r=!0;else{n=this._getIndex(n);if(e.inArray(n,r)!==-1)return;e.isArray(r)?r=e.merge([n],r).sort():r=[n]}this._setupDisabled(r)},load:function(t,n){t=this._getIndex(t);var r=this,i=this.tabs.eq(t),o=i.find(".ui-tabs-anchor"),u=this._getPanelForTab(i),a={tab:i,panel:u};if(s(o[0]))return;this.xhr=e.ajax(this._ajaxSettings(o,n,a)),this.xhr&&this.xhr.statusText!=="canceled"&&(i.addClass("ui-tabs-loading"),u.attr("aria-busy","true"),this.xhr.success(function(e){setTimeout(function(){u.html(e),r._trigger("load",n,a)},1)}).complete(function(e,t){setTimeout(function(){t==="abort"&&r.panels.stop(!1,!0),i.removeClass("ui-tabs-loading"),u.removeAttr("aria-busy"),e===r.xhr&&delete r.xhr},1)}))},_ajaxSettings:function(t,n,r){var i=this;return{url:t.attr("href"),beforeSend:function(t,s){return i._trigger("beforeLoad",n,e.extend({jqXHR:t,ajaxSettings:s},r))}}},_getPanelForTab:function(t){var n=e(t).attr("aria-controls");return this.element.find(this._sanitizeSelector("#"+n))}}),e.uiBackCompat!==!1&&(e.ui.tabs.prototype._ui=function(e,t){return{tab:e,panel:t,index:this.anchors.index(e)}},e.widget("ui.tabs",e.ui.tabs,{url:function(e,t){this.anchors.eq(e).attr("href",t)}}),e.widget("ui.tabs",e.ui.tabs,{options:{ajaxOptions:null,cache:!1},_create:function(){this._super();var t=this;this._on({tabsbeforeload:function(n,r){if(e.data(r.tab[0],"cache.tabs")){n.preventDefault();return}r.jqXHR.success(function(){t.options.cache&&e.data(r.tab[0],"cache.tabs",!0)})}})},_ajaxSettings:function(t,n,r){var i=this.options.ajaxOptions;return e.extend({},i,{error:function(e,t){try{i.error(e,t,r.tab.closest("li").index(),r.tab[0])}catch(n){}}},this._superApply(arguments))},_setOption:function(e,t){e==="cache"&&t===!1&&this.anchors.removeData("cache.tabs"),this._super(e,t)},_destroy:function(){this.anchors.removeData("cache.tabs"),this._super()},url:function(e){this.anchors.eq(e).removeData("cache.tabs"),this._superApply(arguments)}}),e.widget("ui.tabs",e.ui.tabs,{abort:function(){this.xhr&&this.xhr.abort()}}),e.widget("ui.tabs",e.ui.tabs,{options:{spinner:"<em>Loading&#8230;</em>"},_create:function(){this._super(),this._on({tabsbeforeload:function(e,t){if(e.target!==this.element[0]||!this.options.spinner)return;var n=t.tab.find("span"),r=n.html();n.html(this.options.spinner),t.jqXHR.complete(function(){n.html(r)})}})}}),e.widget("ui.tabs",e.ui.tabs,{options:{enable:null,disable:null},enable:function(t){var n=this.options,r;if(t&&n.disabled===!0||e.isArray(n.disabled)&&e.inArray(t,n.disabled)!==-1)r=!0;this._superApply(arguments),r&&this._trigger("enable",null,this._ui(this.anchors[t],this.panels[t]))},disable:function(t){var n=this.options,r;if(t&&n.disabled===!1||e.isArray(n.disabled)&&e.inArray(t,n.disabled)===-1)r=!0;this._superApply(arguments),r&&this._trigger("disable",null,this._ui(this.anchors[t],this.panels[t]))}}),e.widget("ui.tabs",e.ui.tabs,{options:{add:null,remove:null,tabTemplate:"<li><a href='#{href}'><span>#{label}</span></a></li>"},add:function(n,r,i){i===t&&(i=this.anchors.length);var s,o,u=this.options,a=e(u.tabTemplate.replace(/#\{href\}/g,n).replace(/#\{label\}/g,r)),f=n.indexOf("#")?this._tabId(a):n.replace("#","");return a.addClass("ui-state-default ui-corner-top").data("ui-tabs-destroy",!0),a.attr("aria-controls",f),s=i>=this.tabs.length,o=this.element.find("#"+f),o.length||(o=this._createPanel(f),s?i>0?o.insertAfter(this.panels.eq(-1)):o.appendTo(this.element):o.insertBefore(this.panels[i])),o.addClass("ui-tabs-panel ui-widget-content ui-corner-bottom").hide(),s?a.appendTo(this.tablist):a.insertBefore(this.tabs[i]),u.disabled=e.map(u.disabled,function(e){return e>=i?++e:e}),this.refresh(),this.tabs.length===1&&u.active===!1&&this.option("active",0),this._trigger("add",null,this._ui(this.anchors[i],this.panels[i])),this},remove:function(t){t=this._getIndex(t);var n=this.options,r=this.tabs.eq(t).remove(),i=this._getPanelForTab(r).remove();return r.hasClass("ui-tabs-active")&&this.anchors.length>2&&this._activate(t+(t+1<this.anchors.length?1:-1)),n.disabled=e.map(e.grep(n.disabled,function(e){return e!==t}),function(e){return e>=t?--e:e}),this.refresh(),this._trigger("remove",null,this._ui(r.find("a")[0],i[0])),this}}),e.widget("ui.tabs",e.ui.tabs,{length:function(){return this.anchors.length}}),e.widget("ui.tabs",e.ui.tabs,{options:{idPrefix:"ui-tabs-"},_tabId:function(t){var n=t.is("li")?t.find("a[href]"):t;return n=n[0],e(n).closest("li").attr("aria-controls")||n.title&&n.title.replace(/\s/g,"_").replace(/[^\w\u00c0-\uFFFF\-]/g,"")||this.options.idPrefix+i()}}),e.widget("ui.tabs",e.ui.tabs,{options:{panelTemplate:"<div></div>"},_createPanel:function(t){return e(this.options.panelTemplate).attr("id",t).addClass("ui-tabs-panel ui-widget-content ui-corner-bottom").data("ui-tabs-destroy",!0)}}),e.widget("ui.tabs",e.ui.tabs,{_create:function(){var e=this.options;e.active===null&&e.selected!==t&&(e.active=e.selected===-1?!1:e.selected),this._super(),e.selected=e.active,e.selected===!1&&(e.selected=-1)},_setOption:function(e,t){if(e!=="selected")return this._super(e,t);var n=this.options;this._super("active",t===-1?!1:t),n.selected=n.active,n.selected===!1&&(n.selected=-1)},_eventHandler:function(){this._superApply(arguments),this.options.selected=this.options.active,this.options.selected===!1&&(this.options.selected=-1)}}),e.widget("ui.tabs",e.ui.tabs,{options:{show:null,select:null},_create:function(){this._super(),this.options.active!==!1&&this._trigger("show",null,this._ui(this.active.find(".ui-tabs-anchor")[0],this._getPanelForTab(this.active)[0]))},_trigger:function(e,t,n){var r,i,s=this._superApply(arguments);return s?(e==="beforeActivate"?(r=n.newTab.length?n.newTab:n.oldTab,i=n.newPanel.length?n.newPanel:n.oldPanel,s=this._super("select",t,{tab:r.find(".ui-tabs-anchor")[0],panel:i[0],index:r.closest("li").index()})):e==="activate"&&n.newTab.length&&(s=this._super("show",t,{tab:n.newTab.find(".ui-tabs-anchor")[0],panel:n.newPanel[0],index:n.newTab.closest("li").index()})),s):!1}}),e.widget("ui.tabs",e.ui.tabs,{select:function(e){e=this._getIndex(e);if(e===-1){if(!this.options.collapsible||this.options.selected===-1)return;e=this.options.selected}this.anchors.eq(e).trigger(this.options.event+this.eventNamespace)}}),function(){var t=0;e.widget("ui.tabs",e.ui.tabs,{options:{cookie:null},_create:function(){var e=this.options,t;e.active==null&&e.cookie&&(t=parseInt(this._cookie(),10),t===-1&&(t=!1),e.active=t),this._super()},_cookie:function(n){var r=[this.cookie||(this.cookie=this.options.cookie.name||"ui-tabs-"+ ++t)];return arguments.length&&(r.push(n===!1?-1:n),r.push(this.options.cookie)),e.cookie.apply(null,r)},_refresh:function(){this._super(),this.options.cookie&&this._cookie(this.options.active,this.options.cookie)},_eventHandler:function(){this._superApply(arguments),this.options.cookie&&this._cookie(this.options.active,this.options.cookie)},_destroy:function(){this._super(),this.options.cookie&&this._cookie(null,this.options.cookie)}})}(),e.widget("ui.tabs",e.ui.tabs,{_trigger:function(t,n,r){var i=e.extend({},r);return t==="load"&&(i.panel=i.panel[0],i.tab=i.tab.find(".ui-tabs-anchor")[0]),this._super(t,n,i)}}),e.widget("ui.tabs",e.ui.tabs,{options:{fx:null},_getFx:function(){var t,n,r=this.options.fx;return r&&(e.isArray(r)?(t=r[0],n=r[1]):t=n=r),r?{show:n,hide:t}:null},_toggle:function(e,t){function o(){n.running=!1,n._trigger("activate",e,t)}function u(){t.newTab.closest("li").addClass("ui-tabs-active ui-state-active"),r.length&&s.show?r.animate(s.show,s.show.duration,function(){o()}):(r.show(),o())}var n=this,r=t.newPanel,i=t.oldPanel,s=this._getFx();if(!s)return this._super(e,t);n.running=!0,i.length&&s.hide?i.animate(s.hide,s.hide.duration,function(){t.oldTab.closest("li").removeClass("ui-tabs-active ui-state-active"),u()}):(t.oldTab.closest("li").removeClass("ui-tabs-active ui-state-active"),i.hide(),u())}}))})(jQuery);(function(e){function n(t,n){var r=(t.attr("aria-describedby")||"").split(/\s+/);r.push(n),t.data("ui-tooltip-id",n).attr("aria-describedby",e.trim(r.join(" ")))}function r(t){var n=t.data("ui-tooltip-id"),r=(t.attr("aria-describedby")||"").split(/\s+/),i=e.inArray(n,r);i!==-1&&r.splice(i,1),t.removeData("ui-tooltip-id"),r=e.trim(r.join(" ")),r?t.attr("aria-describedby",r):t.removeAttr("aria-describedby")}var t=0;e.widget("ui.tooltip",{version:"1.9.2",options:{content:function(){return e(this).attr("title")},hide:!0,items:"[title]:not([disabled])",position:{my:"left top+15",at:"left bottom",collision:"flipfit flip"},show:!0,tooltipClass:null,track:!1,close:null,open:null},_create:function(){this._on({mouseover:"open",focusin:"open"}),this.tooltips={},this.parents={},this.options.disabled&&this._disable()},_setOption:function(t,n){var r=this;if(t==="disabled"){this[n?"_disable":"_enable"](),this.options[t]=n;return}this._super(t,n),t==="content"&&e.each(this.tooltips,function(e,t){r._updateContent(t)})},_disable:function(){var t=this;e.each(this.tooltips,function(n,r){var i=e.Event("blur");i.target=i.currentTarget=r[0],t.close(i,!0)}),this.element.find(this.options.items).andSelf().each(function(){var t=e(this);t.is("[title]")&&t.data("ui-tooltip-title",t.attr("title")).attr("title","")})},_enable:function(){this.element.find(this.options.items).andSelf().each(function(){var t=e(this);t.data("ui-tooltip-title")&&t.attr("title",t.data("ui-tooltip-title"))})},open:function(t){var n=this,r=e(t?t.target:this.element).closest(this.options.items);if(!r.length||r.data("ui-tooltip-id"))return;r.attr("title")&&r.data("ui-tooltip-title",r.attr("title")),r.data("ui-tooltip-open",!0),t&&t.type==="mouseover"&&r.parents().each(function(){var t=e(this),r;t.data("ui-tooltip-open")&&(r=e.Event("blur"),r.target=r.currentTarget=this,n.close(r,!0)),t.attr("title")&&(t.uniqueId(),n.parents[this.id]={element:this,title:t.attr("title")},t.attr("title",""))}),this._updateContent(r,t)},_updateContent:function(e,t){var n,r=this.options.content,i=this,s=t?t.type:null;if(typeof r=="string")return this._open(t,e,r);n=r.call(e[0],function(n){if(!e.data("ui-tooltip-open"))return;i._delay(function(){t&&(t.type=s),this._open(t,e,n)})}),n&&this._open(t,e,n)},_open:function(t,r,i){function f(e){a.of=e;if(s.is(":hidden"))return;s.position(a)}var s,o,u,a=e.extend({},this.options.position);if(!i)return;s=this._find(r);if(s.length){s.find(".ui-tooltip-content").html(i);return}r.is("[title]")&&(t&&t.type==="mouseover"?r.attr("title",""):r.removeAttr("title")),s=this._tooltip(r),n(r,s.attr("id")),s.find(".ui-tooltip-content").html(i),this.options.track&&t&&/^mouse/.test(t.type)?(this._on(this.document,{mousemove:f}),f(t)):s.position(e.extend({of:r},this.options.position)),s.hide(),this._show(s,this.options.show),this.options.show&&this.options.show.delay&&(u=setInterval(function(){s.is(":visible")&&(f(a.of),clearInterval(u))},e.fx.interval)),this._trigger("open",t,{tooltip:s}),o={keyup:function(t){if(t.keyCode===e.ui.keyCode.ESCAPE){var n=e.Event(t);n.currentTarget=r[0],this.close(n,!0)}},remove:function(){this._removeTooltip(s)}};if(!t||t.type==="mouseover")o.mouseleave="close";if(!t||t.type==="focusin")o.focusout="close";this._on(!0,r,o)},close:function(t){var n=this,i=e(t?t.currentTarget:this.element),s=this._find(i);if(this.closing)return;i.data("ui-tooltip-title")&&i.attr("title",i.data("ui-tooltip-title")),r(i),s.stop(!0),this._hide(s,this.options.hide,function(){n._removeTooltip(e(this))}),i.removeData("ui-tooltip-open"),this._off(i,"mouseleave focusout keyup"),i[0]!==this.element[0]&&this._off(i,"remove"),this._off(this.document,"mousemove"),t&&t.type==="mouseleave"&&e.each(this.parents,function(t,r){e(r.element).attr("title",r.title),delete n.parents[t]}),this.closing=!0,this._trigger("close",t,{tooltip:s}),this.closing=!1},_tooltip:function(n){var r="ui-tooltip-"+t++,i=e("<div>").attr({id:r,role:"tooltip"}).addClass("ui-tooltip ui-widget ui-corner-all ui-widget-content "+(this.options.tooltipClass||""));return e("<div>").addClass("ui-tooltip-content").appendTo(i),i.appendTo(this.document[0].body),e.fn.bgiframe&&i.bgiframe(),this.tooltips[r]=n,i},_find:function(t){var n=t.data("ui-tooltip-id");return n?e("#"+n):e()},_removeTooltip:function(e){e.remove(),delete this.tooltips[e.attr("id")]},_destroy:function(){var t=this;e.each(this.tooltips,function(n,r){var i=e.Event("blur");i.target=i.currentTarget=r[0],t.close(i,!0),e("#"+n).remove(),r.data("ui-tooltip-title")&&(r.attr("title",r.data("ui-tooltip-title")),r.removeData("ui-tooltip-title"))})}})})(jQuery);jQuery.effects||function(e,t){var n=e.uiBackCompat!==!1,r="ui-effects-";e.effects={effect:{}},function(t,n){function p(e,t,n){var r=a[t.type]||{};return e==null?n||!t.def?null:t.def:(e=r.floor?~~e:parseFloat(e),isNaN(e)?t.def:r.mod?(e+r.mod)%r.mod:0>e?0:r.max<e?r.max:e)}function d(e){var n=o(),r=n._rgba=[];return e=e.toLowerCase(),h(s,function(t,i){var s,o=i.re.exec(e),a=o&&i.parse(o),f=i.space||"rgba";if(a)return s=n[f](a),n[u[f].cache]=s[u[f].cache],r=n._rgba=s._rgba,!1}),r.length?(r.join()==="0,0,0,0"&&t.extend(r,c.transparent),n):c[e]}function v(e,t,n){return n=(n+1)%1,n*6<1?e+(t-e)*n*6:n*2<1?t:n*3<2?e+(t-e)*(2/3-n)*6:e}var r="backgroundColor borderBottomColor borderLeftColor borderRightColor borderTopColor color columnRuleColor outlineColor textDecorationColor textEmphasisColor".split(" "),i=/^([\-+])=\s*(\d+\.?\d*)/,s=[{re:/rgba?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*(?:,\s*(\d+(?:\.\d+)?)\s*)?\)/,parse:function(e){return[e[1],e[2],e[3],e[4]]}},{re:/rgba?\(\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*(?:,\s*(\d+(?:\.\d+)?)\s*)?\)/,parse:function(e){return[e[1]*2.55,e[2]*2.55,e[3]*2.55,e[4]]}},{re:/#([a-f0-9]{2})([a-f0-9]{2})([a-f0-9]{2})/,parse:function(e){return[parseInt(e[1],16),parseInt(e[2],16),parseInt(e[3],16)]}},{re:/#([a-f0-9])([a-f0-9])([a-f0-9])/,parse:function(e){return[parseInt(e[1]+e[1],16),parseInt(e[2]+e[2],16),parseInt(e[3]+e[3],16)]}},{re:/hsla?\(\s*(\d+(?:\.\d+)?)\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*(?:,\s*(\d+(?:\.\d+)?)\s*)?\)/,space:"hsla",parse:function(e){return[e[1],e[2]/100,e[3]/100,e[4]]}}],o=t.Color=function(e,n,r,i){return new t.Color.fn.parse(e,n,r,i)},u={rgba:{props:{red:{idx:0,type:"byte"},green:{idx:1,type:"byte"},blue:{idx:2,type:"byte"}}},hsla:{props:{hue:{idx:0,type:"degrees"},saturation:{idx:1,type:"percent"},lightness:{idx:2,type:"percent"}}}},a={"byte":{floor:!0,max:255},percent:{max:1},degrees:{mod:360,floor:!0}},f=o.support={},l=t("<p>")[0],c,h=t.each;l.style.cssText="background-color:rgba(1,1,1,.5)",f.rgba=l.style.backgroundColor.indexOf("rgba")>-1,h(u,function(e,t){t.cache="_"+e,t.props.alpha={idx:3,type:"percent",def:1}}),o.fn=t.extend(o.prototype,{parse:function(r,i,s,a){if(r===n)return this._rgba=[null,null,null,null],this;if(r.jquery||r.nodeType)r=t(r).css(i),i=n;var f=this,l=t.type(r),v=this._rgba=[];i!==n&&(r=[r,i,s,a],l="array");if(l==="string")return this.parse(d(r)||c._default);if(l==="array")return h(u.rgba.props,function(e,t){v[t.idx]=p(r[t.idx],t)}),this;if(l==="object")return r instanceof o?h(u,function(e,t){r[t.cache]&&(f[t.cache]=r[t.cache].slice())}):h(u,function(t,n){var i=n.cache;h(n.props,function(e,t){if(!f[i]&&n.to){if(e==="alpha"||r[e]==null)return;f[i]=n.to(f._rgba)}f[i][t.idx]=p(r[e],t,!0)}),f[i]&&e.inArray(null,f[i].slice(0,3))<0&&(f[i][3]=1,n.from&&(f._rgba=n.from(f[i])))}),this},is:function(e){var t=o(e),n=!0,r=this;return h(u,function(e,i){var s,o=t[i.cache];return o&&(s=r[i.cache]||i.to&&i.to(r._rgba)||[],h(i.props,function(e,t){if(o[t.idx]!=null)return n=o[t.idx]===s[t.idx],n})),n}),n},_space:function(){var e=[],t=this;return h(u,function(n,r){t[r.cache]&&e.push(n)}),e.pop()},transition:function(e,t){var n=o(e),r=n._space(),i=u[r],s=this.alpha()===0?o("transparent"):this,f=s[i.cache]||i.to(s._rgba),l=f.slice();return n=n[i.cache],h(i.props,function(e,r){var i=r.idx,s=f[i],o=n[i],u=a[r.type]||{};if(o===null)return;s===null?l[i]=o:(u.mod&&(o-s>u.mod/2?s+=u.mod:s-o>u.mod/2&&(s-=u.mod)),l[i]=p((o-s)*t+s,r))}),this[r](l)},blend:function(e){if(this._rgba[3]===1)return this;var n=this._rgba.slice(),r=n.pop(),i=o(e)._rgba;return o(t.map(n,function(e,t){return(1-r)*i[t]+r*e}))},toRgbaString:function(){var e="rgba(",n=t.map(this._rgba,function(e,t){return e==null?t>2?1:0:e});return n[3]===1&&(n.pop(),e="rgb("),e+n.join()+")"},toHslaString:function(){var e="hsla(",n=t.map(this.hsla(),function(e,t){return e==null&&(e=t>2?1:0),t&&t<3&&(e=Math.round(e*100)+"%"),e});return n[3]===1&&(n.pop(),e="hsl("),e+n.join()+")"},toHexString:function(e){var n=this._rgba.slice(),r=n.pop();return e&&n.push(~~(r*255)),"#"+t.map(n,function(e){return e=(e||0).toString(16),e.length===1?"0"+e:e}).join("")},toString:function(){return this._rgba[3]===0?"transparent":this.toRgbaString()}}),o.fn.parse.prototype=o.fn,u.hsla.to=function(e){if(e[0]==null||e[1]==null||e[2]==null)return[null,null,null,e[3]];var t=e[0]/255,n=e[1]/255,r=e[2]/255,i=e[3],s=Math.max(t,n,r),o=Math.min(t,n,r),u=s-o,a=s+o,f=a*.5,l,c;return o===s?l=0:t===s?l=60*(n-r)/u+360:n===s?l=60*(r-t)/u+120:l=60*(t-n)/u+240,f===0||f===1?c=f:f<=.5?c=u/a:c=u/(2-a),[Math.round(l)%360,c,f,i==null?1:i]},u.hsla.from=function(e){if(e[0]==null||e[1]==null||e[2]==null)return[null,null,null,e[3]];var t=e[0]/360,n=e[1],r=e[2],i=e[3],s=r<=.5?r*(1+n):r+n-r*n,o=2*r-s;return[Math.round(v(o,s,t+1/3)*255),Math.round(v(o,s,t)*255),Math.round(v(o,s,t-1/3)*255),i]},h(u,function(e,r){var s=r.props,u=r.cache,a=r.to,f=r.from;o.fn[e]=function(e){a&&!this[u]&&(this[u]=a(this._rgba));if(e===n)return this[u].slice();var r,i=t.type(e),l=i==="array"||i==="object"?e:arguments,c=this[u].slice();return h(s,function(e,t){var n=l[i==="object"?e:t.idx];n==null&&(n=c[t.idx]),c[t.idx]=p(n,t)}),f?(r=o(f(c)),r[u]=c,r):o(c)},h(s,function(n,r){if(o.fn[n])return;o.fn[n]=function(s){var o=t.type(s),u=n==="alpha"?this._hsla?"hsla":"rgba":e,a=this[u](),f=a[r.idx],l;return o==="undefined"?f:(o==="function"&&(s=s.call(this,f),o=t.type(s)),s==null&&r.empty?this:(o==="string"&&(l=i.exec(s),l&&(s=f+parseFloat(l[2])*(l[1]==="+"?1:-1))),a[r.idx]=s,this[u](a)))}})}),h(r,function(e,n){t.cssHooks[n]={set:function(e,r){var i,s,u="";if(t.type(r)!=="string"||(i=d(r))){r=o(i||r);if(!f.rgba&&r._rgba[3]!==1){s=n==="backgroundColor"?e.parentNode:e;while((u===""||u==="transparent")&&s&&s.style)try{u=t.css(s,"backgroundColor"),s=s.parentNode}catch(a){}r=r.blend(u&&u!=="transparent"?u:"_default")}r=r.toRgbaString()}try{e.style[n]=r}catch(l){}}},t.fx.step[n]=function(e){e.colorInit||(e.start=o(e.elem,n),e.end=o(e.end),e.colorInit=!0),t.cssHooks[n].set(e.elem,e.start.transition(e.end,e.pos))}}),t.cssHooks.borderColor={expand:function(e){var t={};return h(["Top","Right","Bottom","Left"],function(n,r){t["border"+r+"Color"]=e}),t}},c=t.Color.names={aqua:"#00ffff",black:"#000000",blue:"#0000ff",fuchsia:"#ff00ff",gray:"#808080",green:"#008000",lime:"#00ff00",maroon:"#800000",navy:"#000080",olive:"#808000",purple:"#800080",red:"#ff0000",silver:"#c0c0c0",teal:"#008080",white:"#ffffff",yellow:"#ffff00",transparent:[null,null,null,0],_default:"#ffffff"}}(jQuery),function(){function i(){var t=this.ownerDocument.defaultView?this.ownerDocument.defaultView.getComputedStyle(this,null):this.currentStyle,n={},r,i;if(t&&t.length&&t[0]&&t[t[0]]){i=t.length;while(i--)r=t[i],typeof t[r]=="string"&&(n[e.camelCase(r)]=t[r])}else for(r in t)typeof t[r]=="string"&&(n[r]=t[r]);return n}function s(t,n){var i={},s,o;for(s in n)o=n[s],t[s]!==o&&!r[s]&&(e.fx.step[s]||!isNaN(parseFloat(o)))&&(i[s]=o);return i}var n=["add","remove","toggle"],r={border:1,borderBottom:1,borderColor:1,borderLeft:1,borderRight:1,borderTop:1,borderWidth:1,margin:1,padding:1};e.each(["borderLeftStyle","borderRightStyle","borderBottomStyle","borderTopStyle"],function(t,n){e.fx.step[n]=function(e){if(e.end!=="none"&&!e.setAttr||e.pos===1&&!e.setAttr)jQuery.style(e.elem,n,e.end),e.setAttr=!0}}),e.effects.animateClass=function(t,r,o,u){var a=e.speed(r,o,u);return this.queue(function(){var r=e(this),o=r.attr("class")||"",u,f=a.children?r.find("*").andSelf():r;f=f.map(function(){var t=e(this);return{el:t,start:i.call(this)}}),u=function(){e.each(n,function(e,n){t[n]&&r[n+"Class"](t[n])})},u(),f=f.map(function(){return this.end=i.call(this.el[0]),this.diff=s(this.start,this.end),this}),r.attr("class",o),f=f.map(function(){var t=this,n=e.Deferred(),r=jQuery.extend({},a,{queue:!1,complete:function(){n.resolve(t)}});return this.el.animate(this.diff,r),n.promise()}),e.when.apply(e,f.get()).done(function(){u(),e.each(arguments,function(){var t=this.el;e.each(this.diff,function(e){t.css(e,"")})}),a.complete.call(r[0])})})},e.fn.extend({_addClass:e.fn.addClass,addClass:function(t,n,r,i){return n?e.effects.animateClass.call(this,{add:t},n,r,i):this._addClass(t)},_removeClass:e.fn.removeClass,removeClass:function(t,n,r,i){return n?e.effects.animateClass.call(this,{remove:t},n,r,i):this._removeClass(t)},_toggleClass:e.fn.toggleClass,toggleClass:function(n,r,i,s,o){return typeof r=="boolean"||r===t?i?e.effects.animateClass.call(this,r?{add:n}:{remove:n},i,s,o):this._toggleClass(n,r):e.effects.animateClass.call(this,{toggle:n},r,i,s)},switchClass:function(t,n,r,i,s){return e.effects.animateClass.call(this,{add:n,remove:t},r,i,s)}})}(),function(){function i(t,n,r,i){e.isPlainObject(t)&&(n=t,t=t.effect),t={effect:t},n==null&&(n={}),e.isFunction(n)&&(i=n,r=null,n={});if(typeof n=="number"||e.fx.speeds[n])i=r,r=n,n={};return e.isFunction(r)&&(i=r,r=null),n&&e.extend(t,n),r=r||n.duration,t.duration=e.fx.off?0:typeof r=="number"?r:r in e.fx.speeds?e.fx.speeds[r]:e.fx.speeds._default,t.complete=i||n.complete,t}function s(t){return!t||typeof t=="number"||e.fx.speeds[t]?!0:typeof t=="string"&&!e.effects.effect[t]?n&&e.effects[t]?!1:!0:!1}e.extend(e.effects,{version:"1.9.2",save:function(e,t){for(var n=0;n<t.length;n++)t[n]!==null&&e.data(r+t[n],e[0].style[t[n]])},restore:function(e,n){var i,s;for(s=0;s<n.length;s++)n[s]!==null&&(i=e.data(r+n[s]),i===t&&(i=""),e.css(n[s],i))},setMode:function(e,t){return t==="toggle"&&(t=e.is(":hidden")?"show":"hide"),t},getBaseline:function(e,t){var n,r;switch(e[0]){case"top":n=0;break;case"middle":n=.5;break;case"bottom":n=1;break;default:n=e[0]/t.height}switch(e[1]){case"left":r=0;break;case"center":r=.5;break;case"right":r=1;break;default:r=e[1]/t.width}return{x:r,y:n}},createWrapper:function(t){if(t.parent().is(".ui-effects-wrapper"))return t.parent();var n={width:t.outerWidth(!0),height:t.outerHeight(!0),"float":t.css("float")},r=e("<div></div>").addClass("ui-effects-wrapper").css({fontSize:"100%",background:"transparent",border:"none",margin:0,padding:0}),i={width:t.width(),height:t.height()},s=document.activeElement;try{s.id}catch(o){s=document.body}return t.wrap(r),(t[0]===s||e.contains(t[0],s))&&e(s).focus(),r=t.parent(),t.css("position")==="static"?(r.css({position:"relative"}),t.css({position:"relative"})):(e.extend(n,{position:t.css("position"),zIndex:t.css("z-index")}),e.each(["top","left","bottom","right"],function(e,r){n[r]=t.css(r),isNaN(parseInt(n[r],10))&&(n[r]="auto")}),t.css({position:"relative",top:0,left:0,right:"auto",bottom:"auto"})),t.css(i),r.css(n).show()},removeWrapper:function(t){var n=document.activeElement;return t.parent().is(".ui-effects-wrapper")&&(t.parent().replaceWith(t),(t[0]===n||e.contains(t[0],n))&&e(n).focus()),t},setTransition:function(t,n,r,i){return i=i||{},e.each(n,function(e,n){var s=t.cssUnit(n);s[0]>0&&(i[n]=s[0]*r+s[1])}),i}}),e.fn.extend({effect:function(){function a(n){function u(){e.isFunction(i)&&i.call(r[0]),e.isFunction(n)&&n()}var r=e(this),i=t.complete,s=t.mode;(r.is(":hidden")?s==="hide":s==="show")?u():o.call(r[0],t,u)}var t=i.apply(this,arguments),r=t.mode,s=t.queue,o=e.effects.effect[t.effect],u=!o&&n&&e.effects[t.effect];return e.fx.off||!o&&!u?r?this[r](t.duration,t.complete):this.each(function(){t.complete&&t.complete.call(this)}):o?s===!1?this.each(a):this.queue(s||"fx",a):u.call(this,{options:t,duration:t.duration,callback:t.complete,mode:t.mode})},_show:e.fn.show,show:function(e){if(s(e))return this._show.apply(this,arguments);var t=i.apply(this,arguments);return t.mode="show",this.effect.call(this,t)},_hide:e.fn.hide,hide:function(e){if(s(e))return this._hide.apply(this,arguments);var t=i.apply(this,arguments);return t.mode="hide",this.effect.call(this,t)},__toggle:e.fn.toggle,toggle:function(t){if(s(t)||typeof t=="boolean"||e.isFunction(t))return this.__toggle.apply(this,arguments);var n=i.apply(this,arguments);return n.mode="toggle",this.effect.call(this,n)},cssUnit:function(t){var n=this.css(t),r=[];return e.each(["em","px","%","pt"],function(e,t){n.indexOf(t)>0&&(r=[parseFloat(n),t])}),r}})}(),function(){var t={};e.each(["Quad","Cubic","Quart","Quint","Expo"],function(e,n){t[n]=function(t){return Math.pow(t,e+2)}}),e.extend(t,{Sine:function(e){return 1-Math.cos(e*Math.PI/2)},Circ:function(e){return 1-Math.sqrt(1-e*e)},Elastic:function(e){return e===0||e===1?e:-Math.pow(2,8*(e-1))*Math.sin(((e-1)*80-7.5)*Math.PI/15)},Back:function(e){return e*e*(3*e-2)},Bounce:function(e){var t,n=4;while(e<((t=Math.pow(2,--n))-1)/11);return 1/Math.pow(4,3-n)-7.5625*Math.pow((t*3-2)/22-e,2)}}),e.each(t,function(t,n){e.easing["easeIn"+t]=n,e.easing["easeOut"+t]=function(e){return 1-n(1-e)},e.easing["easeInOut"+t]=function(e){return e<.5?n(e*2)/2:1-n(e*-2+2)/2}})}()}(jQuery);(function(e,t){var n=/up|down|vertical/,r=/up|left|vertical|horizontal/;e.effects.effect.blind=function(t,i){var s=e(this),o=["position","top","bottom","left","right","height","width"],u=e.effects.setMode(s,t.mode||"hide"),a=t.direction||"up",f=n.test(a),l=f?"height":"width",c=f?"top":"left",h=r.test(a),p={},d=u==="show",v,m,g;s.parent().is(".ui-effects-wrapper")?e.effects.save(s.parent(),o):e.effects.save(s,o),s.show(),v=e.effects.createWrapper(s).css({overflow:"hidden"}),m=v[l](),g=parseFloat(v.css(c))||0,p[l]=d?m:0,h||(s.css(f?"bottom":"right",0).css(f?"top":"left","auto").css({position:"absolute"}),p[c]=d?g:m+g),d&&(v.css(l,0),h||v.css(c,g+m)),v.animate(p,{duration:t.duration,easing:t.easing,queue:!1,complete:function(){u==="hide"&&s.hide(),e.effects.restore(s,o),e.effects.removeWrapper(s),i()}})}})(jQuery);(function(e,t){e.effects.effect.bounce=function(t,n){var r=e(this),i=["position","top","bottom","left","right","height","width"],s=e.effects.setMode(r,t.mode||"effect"),o=s==="hide",u=s==="show",a=t.direction||"up",f=t.distance,l=t.times||5,c=l*2+(u||o?1:0),h=t.duration/c,p=t.easing,d=a==="up"||a==="down"?"top":"left",v=a==="up"||a==="left",m,g,y,b=r.queue(),w=b.length;(u||o)&&i.push("opacity"),e.effects.save(r,i),r.show(),e.effects.createWrapper(r),f||(f=r[d==="top"?"outerHeight":"outerWidth"]()/3),u&&(y={opacity:1},y[d]=0,r.css("opacity",0).css(d,v?-f*2:f*2).animate(y,h,p)),o&&(f/=Math.pow(2,l-1)),y={},y[d]=0;for(m=0;m<l;m++)g={},g[d]=(v?"-=":"+=")+f,r.animate(g,h,p).animate(y,h,p),f=o?f*2:f/2;o&&(g={opacity:0},g[d]=(v?"-=":"+=")+f,r.animate(g,h,p)),r.queue(function(){o&&r.hide(),e.effects.restore(r,i),e.effects.removeWrapper(r),n()}),w>1&&b.splice.apply(b,[1,0].concat(b.splice(w,c+1))),r.dequeue()}})(jQuery);(function(e,t){e.effects.effect.clip=function(t,n){var r=e(this),i=["position","top","bottom","left","right","height","width"],s=e.effects.setMode(r,t.mode||"hide"),o=s==="show",u=t.direction||"vertical",a=u==="vertical",f=a?"height":"width",l=a?"top":"left",c={},h,p,d;e.effects.save(r,i),r.show(),h=e.effects.createWrapper(r).css({overflow:"hidden"}),p=r[0].tagName==="IMG"?h:r,d=p[f](),o&&(p.css(f,0),p.css(l,d/2)),c[f]=o?d:0,c[l]=o?0:d/2,p.animate(c,{queue:!1,duration:t.duration,easing:t.easing,complete:function(){o||r.hide(),e.effects.restore(r,i),e.effects.removeWrapper(r),n()}})}})(jQuery);(function(e,t){e.effects.effect.drop=function(t,n){var r=e(this),i=["position","top","bottom","left","right","opacity","height","width"],s=e.effects.setMode(r,t.mode||"hide"),o=s==="show",u=t.direction||"left",a=u==="up"||u==="down"?"top":"left",f=u==="up"||u==="left"?"pos":"neg",l={opacity:o?1:0},c;e.effects.save(r,i),r.show(),e.effects.createWrapper(r),c=t.distance||r[a==="top"?"outerHeight":"outerWidth"](!0)/2,o&&r.css("opacity",0).css(a,f==="pos"?-c:c),l[a]=(o?f==="pos"?"+=":"-=":f==="pos"?"-=":"+=")+c,r.animate(l,{queue:!1,duration:t.duration,easing:t.easing,complete:function(){s==="hide"&&r.hide(),e.effects.restore(r,i),e.effects.removeWrapper(r),n()}})}})(jQuery);(function(e,t){e.effects.effect.explode=function(t,n){function y(){c.push(this),c.length===r*i&&b()}function b(){s.css({visibility:"visible"}),e(c).remove(),u||s.hide(),n()}var r=t.pieces?Math.round(Math.sqrt(t.pieces)):3,i=r,s=e(this),o=e.effects.setMode(s,t.mode||"hide"),u=o==="show",a=s.show().css("visibility","hidden").offset(),f=Math.ceil(s.outerWidth()/i),l=Math.ceil(s.outerHeight()/r),c=[],h,p,d,v,m,g;for(h=0;h<r;h++){v=a.top+h*l,g=h-(r-1)/2;for(p=0;p<i;p++)d=a.left+p*f,m=p-(i-1)/2,s.clone().appendTo("body").wrap("<div></div>").css({position:"absolute",visibility:"visible",left:-p*f,top:-h*l}).parent().addClass("ui-effects-explode").css({position:"absolute",overflow:"hidden",width:f,height:l,left:d+(u?m*f:0),top:v+(u?g*l:0),opacity:u?0:1}).animate({left:d+(u?0:m*f),top:v+(u?0:g*l),opacity:u?1:0},t.duration||500,t.easing,y)}}})(jQuery);(function(e,t){e.effects.effect.fade=function(t,n){var r=e(this),i=e.effects.setMode(r,t.mode||"toggle");r.animate({opacity:i},{queue:!1,duration:t.duration,easing:t.easing,complete:n})}})(jQuery);(function(e,t){e.effects.effect.fold=function(t,n){var r=e(this),i=["position","top","bottom","left","right","height","width"],s=e.effects.setMode(r,t.mode||"hide"),o=s==="show",u=s==="hide",a=t.size||15,f=/([0-9]+)%/.exec(a),l=!!t.horizFirst,c=o!==l,h=c?["width","height"]:["height","width"],p=t.duration/2,d,v,m={},g={};e.effects.save(r,i),r.show(),d=e.effects.createWrapper(r).css({overflow:"hidden"}),v=c?[d.width(),d.height()]:[d.height(),d.width()],f&&(a=parseInt(f[1],10)/100*v[u?0:1]),o&&d.css(l?{height:0,width:a}:{height:a,width:0}),m[h[0]]=o?v[0]:a,g[h[1]]=o?v[1]:0,d.animate(m,p,t.easing).animate(g,p,t.easing,function(){u&&r.hide(),e.effects.restore(r,i),e.effects.removeWrapper(r),n()})}})(jQuery);(function(e,t){e.effects.effect.highlight=function(t,n){var r=e(this),i=["backgroundImage","backgroundColor","opacity"],s=e.effects.setMode(r,t.mode||"show"),o={backgroundColor:r.css("backgroundColor")};s==="hide"&&(o.opacity=0),e.effects.save(r,i),r.show().css({backgroundImage:"none",backgroundColor:t.color||"#ffff99"}).animate(o,{queue:!1,duration:t.duration,easing:t.easing,complete:function(){s==="hide"&&r.hide(),e.effects.restore(r,i),n()}})}})(jQuery);(function(e,t){e.effects.effect.pulsate=function(t,n){var r=e(this),i=e.effects.setMode(r,t.mode||"show"),s=i==="show",o=i==="hide",u=s||i==="hide",a=(t.times||5)*2+(u?1:0),f=t.duration/a,l=0,c=r.queue(),h=c.length,p;if(s||!r.is(":visible"))r.css("opacity",0).show(),l=1;for(p=1;p<a;p++)r.animate({opacity:l},f,t.easing),l=1-l;r.animate({opacity:l},f,t.easing),r.queue(function(){o&&r.hide(),n()}),h>1&&c.splice.apply(c,[1,0].concat(c.splice(h,a+1))),r.dequeue()}})(jQuery);(function(e,t){e.effects.effect.puff=function(t,n){var r=e(this),i=e.effects.setMode(r,t.mode||"hide"),s=i==="hide",o=parseInt(t.percent,10)||150,u=o/100,a={height:r.height(),width:r.width(),outerHeight:r.outerHeight(),outerWidth:r.outerWidth()};e.extend(t,{effect:"scale",queue:!1,fade:!0,mode:i,complete:n,percent:s?o:100,from:s?a:{height:a.height*u,width:a.width*u,outerHeight:a.outerHeight*u,outerWidth:a.outerWidth*u}}),r.effect(t)},e.effects.effect.scale=function(t,n){var r=e(this),i=e.extend(!0,{},t),s=e.effects.setMode(r,t.mode||"effect"),o=parseInt(t.percent,10)||(parseInt(t.percent,10)===0?0:s==="hide"?0:100),u=t.direction||"both",a=t.origin,f={height:r.height(),width:r.width(),outerHeight:r.outerHeight(),outerWidth:r.outerWidth()},l={y:u!=="horizontal"?o/100:1,x:u!=="vertical"?o/100:1};i.effect="size",i.queue=!1,i.complete=n,s!=="effect"&&(i.origin=a||["middle","center"],i.restore=!0),i.from=t.from||(s==="show"?{height:0,width:0,outerHeight:0,outerWidth:0}:f),i.to={height:f.height*l.y,width:f.width*l.x,outerHeight:f.outerHeight*l.y,outerWidth:f.outerWidth*l.x},i.fade&&(s==="show"&&(i.from.opacity=0,i.to.opacity=1),s==="hide"&&(i.from.opacity=1,i.to.opacity=0)),r.effect(i)},e.effects.effect.size=function(t,n){var r,i,s,o=e(this),u=["position","top","bottom","left","right","width","height","overflow","opacity"],a=["position","top","bottom","left","right","overflow","opacity"],f=["width","height","overflow"],l=["fontSize"],c=["borderTopWidth","borderBottomWidth","paddingTop","paddingBottom"],h=["borderLeftWidth","borderRightWidth","paddingLeft","paddingRight"],p=e.effects.setMode(o,t.mode||"effect"),d=t.restore||p!=="effect",v=t.scale||"both",m=t.origin||["middle","center"],g=o.css("position"),y=d?u:a,b={height:0,width:0,outerHeight:0,outerWidth:0};p==="show"&&o.show(),r={height:o.height(),width:o.width(),outerHeight:o.outerHeight(),outerWidth:o.outerWidth()},t.mode==="toggle"&&p==="show"?(o.from=t.to||b,o.to=t.from||r):(o.from=t.from||(p==="show"?b:r),o.to=t.to||(p==="hide"?b:r)),s={from:{y:o.from.height/r.height,x:o.from.width/r.width},to:{y:o.to.height/r.height,x:o.to.width/r.width}};if(v==="box"||v==="both")s.from.y!==s.to.y&&(y=y.concat(c),o.from=e.effects.setTransition(o,c,s.from.y,o.from),o.to=e.effects.setTransition(o,c,s.to.y,o.to)),s.from.x!==s.to.x&&(y=y.concat(h),o.from=e.effects.setTransition(o,h,s.from.x,o.from),o.to=e.effects.setTransition(o,h,s.to.x,o.to));(v==="content"||v==="both")&&s.from.y!==s.to.y&&(y=y.concat(l).concat(f),o.from=e.effects.setTransition(o,l,s.from.y,o.from),o.to=e.effects.setTransition(o,l,s.to.y,o.to)),e.effects.save(o,y),o.show(),e.effects.createWrapper(o),o.css("overflow","hidden").css(o.from),m&&(i=e.effects.getBaseline(m,r),o.from.top=(r.outerHeight-o.outerHeight())*i.y,o.from.left=(r.outerWidth-o.outerWidth())*i.x,o.to.top=(r.outerHeight-o.to.outerHeight)*i.y,o.to.left=(r.outerWidth-o.to.outerWidth)*i.x),o.css(o.from);if(v==="content"||v==="both")c=c.concat(["marginTop","marginBottom"]).concat(l),h=h.concat(["marginLeft","marginRight"]),f=u.concat(c).concat(h),o.find("*[width]").each(function(){var n=e(this),r={height:n.height(),width:n.width(),outerHeight:n.outerHeight(),outerWidth:n.outerWidth()};d&&e.effects.save(n,f),n.from={height:r.height*s.from.y,width:r.width*s.from.x,outerHeight:r.outerHeight*s.from.y,outerWidth:r.outerWidth*s.from.x},n.to={height:r.height*s.to.y,width:r.width*s.to.x,outerHeight:r.height*s.to.y,outerWidth:r.width*s.to.x},s.from.y!==s.to.y&&(n.from=e.effects.setTransition(n,c,s.from.y,n.from),n.to=e.effects.setTransition(n,c,s.to.y,n.to)),s.from.x!==s.to.x&&(n.from=e.effects.setTransition(n,h,s.from.x,n.from),n.to=e.effects.setTransition(n,h,s.to.x,n.to)),n.css(n.from),n.animate(n.to,t.duration,t.easing,function(){d&&e.effects.restore(n,f)})});o.animate(o.to,{queue:!1,duration:t.duration,easing:t.easing,complete:function(){o.to.opacity===0&&o.css("opacity",o.from.opacity),p==="hide"&&o.hide(),e.effects.restore(o,y),d||(g==="static"?o.css({position:"relative",top:o.to.top,left:o.to.left}):e.each(["top","left"],function(e,t){o.css(t,function(t,n){var r=parseInt(n,10),i=e?o.to.left:o.to.top;return n==="auto"?i+"px":r+i+"px"})})),e.effects.removeWrapper(o),n()}})}})(jQuery);(function(e,t){e.effects.effect.shake=function(t,n){var r=e(this),i=["position","top","bottom","left","right","height","width"],s=e.effects.setMode(r,t.mode||"effect"),o=t.direction||"left",u=t.distance||20,a=t.times||3,f=a*2+1,l=Math.round(t.duration/f),c=o==="up"||o==="down"?"top":"left",h=o==="up"||o==="left",p={},d={},v={},m,g=r.queue(),y=g.length;e.effects.save(r,i),r.show(),e.effects.createWrapper(r),p[c]=(h?"-=":"+=")+u,d[c]=(h?"+=":"-=")+u*2,v[c]=(h?"-=":"+=")+u*2,r.animate(p,l,t.easing);for(m=1;m<a;m++)r.animate(d,l,t.easing).animate(v,l,t.easing);r.animate(d,l,t.easing).animate(p,l/2,t.easing).queue(function(){s==="hide"&&r.hide(),e.effects.restore(r,i),e.effects.removeWrapper(r),n()}),y>1&&g.splice.apply(g,[1,0].concat(g.splice(y,f+1))),r.dequeue()}})(jQuery);(function(e,t){e.effects.effect.slide=function(t,n){var r=e(this),i=["position","top","bottom","left","right","width","height"],s=e.effects.setMode(r,t.mode||"show"),o=s==="show",u=t.direction||"left",a=u==="up"||u==="down"?"top":"left",f=u==="up"||u==="left",l,c={};e.effects.save(r,i),r.show(),l=t.distance||r[a==="top"?"outerHeight":"outerWidth"](!0),e.effects.createWrapper(r).css({overflow:"hidden"}),o&&r.css(a,f?isNaN(l)?"-"+l:-l:l),c[a]=(o?f?"+=":"-=":f?"-=":"+=")+l,r.animate(c,{queue:!1,duration:t.duration,easing:t.easing,complete:function(){s==="hide"&&r.hide(),e.effects.restore(r,i),e.effects.removeWrapper(r),n()}})}})(jQuery);(function(e,t){e.effects.effect.transfer=function(t,n){var r=e(this),i=e(t.to),s=i.css("position")==="fixed",o=e("body"),u=s?o.scrollTop():0,a=s?o.scrollLeft():0,f=i.offset(),l={top:f.top-u,left:f.left-a,height:i.innerHeight(),width:i.innerWidth()},c=r.offset(),h=e('<div class="ui-effects-transfer"></div>').appendTo(document.body).addClass(t.className).css({top:c.top-u,left:c.left-a,height:r.innerHeight(),width:r.innerWidth(),position:s?"fixed":"absolute"}).animate(l,t.duration,t.easing,function(){h.remove(),n()})}})(jQuery); \ No newline at end of file
diff --git a/js/jquery/jquery-ui-timepicker-addon.js b/js/jquery/jquery-ui-timepicker-addon.js
new file mode 100644
index 0000000000..2b839bf4cb
--- /dev/null
+++ b/js/jquery/jquery-ui-timepicker-addon.js
@@ -0,0 +1,2128 @@
+/*
+ * jQuery timepicker addon
+ * By: Trent Richardson [http://trentrichardson.com]
+ * Version 1.3.1
+ * Last Modified: 07/07/2013
+ *
+ * Copyright 2013 Trent Richardson
+ * You may use this project under MIT or GPL licenses.
+ * http://trentrichardson.com/Impromptu/GPL-LICENSE.txt
+ * http://trentrichardson.com/Impromptu/MIT-LICENSE.txt
+ */
+
+/*jslint evil: true, white: false, undef: false, nomen: false */
+
+(function($) {
+
+ /*
+ * Lets not redefine timepicker, Prevent "Uncaught RangeError: Maximum call stack size exceeded"
+ */
+ $.ui.timepicker = $.ui.timepicker || {};
+ if ($.ui.timepicker.version) {
+ return;
+ }
+
+ /*
+ * Extend jQueryUI, get it started with our version number
+ */
+ $.extend($.ui, {
+ timepicker: {
+ version: "1.3.1"
+ }
+ });
+
+ /*
+ * Timepicker manager.
+ * Use the singleton instance of this class, $.timepicker, to interact with the time picker.
+ * Settings for (groups of) time pickers are maintained in an instance object,
+ * allowing multiple different settings on the same page.
+ */
+ var Timepicker = function() {
+ this.regional = []; // Available regional settings, indexed by language code
+ this.regional[''] = { // Default regional settings
+ currentText: 'Now',
+ closeText: 'Done',
+ amNames: ['AM', 'A'],
+ pmNames: ['PM', 'P'],
+ timeFormat: 'HH:mm',
+ timeSuffix: '',
+ timeOnlyTitle: 'Choose Time',
+ timeText: 'Time',
+ hourText: 'Hour',
+ minuteText: 'Minute',
+ secondText: 'Second',
+ millisecText: 'Millisecond',
+ microsecText: 'Microsecond',
+ timezoneText: 'Time Zone',
+ isRTL: false
+ };
+ this._defaults = { // Global defaults for all the datetime picker instances
+ showButtonPanel: true,
+ timeOnly: false,
+ showHour: null,
+ showMinute: null,
+ showSecond: null,
+ showMillisec: null,
+ showMicrosec: null,
+ showTimezone: null,
+ showTime: true,
+ stepHour: 1,
+ stepMinute: 1,
+ stepSecond: 1,
+ stepMillisec: 1,
+ stepMicrosec: 1,
+ hour: 0,
+ minute: 0,
+ second: 0,
+ millisec: 0,
+ microsec: 0,
+ timezone: null,
+ hourMin: 0,
+ minuteMin: 0,
+ secondMin: 0,
+ millisecMin: 0,
+ microsecMin: 0,
+ hourMax: 23,
+ minuteMax: 59,
+ secondMax: 59,
+ millisecMax: 999,
+ microsecMax: 999,
+ minDateTime: null,
+ maxDateTime: null,
+ onSelect: null,
+ hourGrid: 0,
+ minuteGrid: 0,
+ secondGrid: 0,
+ millisecGrid: 0,
+ microsecGrid: 0,
+ alwaysSetTime: true,
+ separator: ' ',
+ altFieldTimeOnly: true,
+ altTimeFormat: null,
+ altSeparator: null,
+ altTimeSuffix: null,
+ pickerTimeFormat: null,
+ pickerTimeSuffix: null,
+ showTimepicker: true,
+ timezoneList: null,
+ addSliderAccess: false,
+ sliderAccessArgs: null,
+ controlType: 'slider',
+ defaultValue: null,
+ parse: 'strict'
+ };
+ $.extend(this._defaults, this.regional['']);
+ };
+
+ $.extend(Timepicker.prototype, {
+ $input: null,
+ $altInput: null,
+ $timeObj: null,
+ inst: null,
+ hour_slider: null,
+ minute_slider: null,
+ second_slider: null,
+ millisec_slider: null,
+ microsec_slider: null,
+ timezone_select: null,
+ hour: 0,
+ minute: 0,
+ second: 0,
+ millisec: 0,
+ microsec: 0,
+ timezone: null,
+ hourMinOriginal: null,
+ minuteMinOriginal: null,
+ secondMinOriginal: null,
+ millisecMinOriginal: null,
+ microsecMinOriginal: null,
+ hourMaxOriginal: null,
+ minuteMaxOriginal: null,
+ secondMaxOriginal: null,
+ millisecMaxOriginal: null,
+ microsecMaxOriginal: null,
+ ampm: '',
+ formattedDate: '',
+ formattedTime: '',
+ formattedDateTime: '',
+ timezoneList: null,
+ units: ['hour','minute','second','millisec', 'microsec'],
+ support: {},
+ control: null,
+
+ /*
+ * Override the default settings for all instances of the time picker.
+ * @param settings object - the new settings to use as defaults (anonymous object)
+ * @return the manager object
+ */
+ setDefaults: function(settings) {
+ extendRemove(this._defaults, settings || {});
+ return this;
+ },
+
+ /*
+ * Create a new Timepicker instance
+ */
+ _newInst: function($input, opts) {
+ var tp_inst = new Timepicker(),
+ inlineSettings = {},
+ fns = {},
+ overrides, i;
+
+ for (var attrName in this._defaults) {
+ if(this._defaults.hasOwnProperty(attrName)){
+ var attrValue = $input.attr('time:' + attrName);
+ if (attrValue) {
+ try {
+ inlineSettings[attrName] = eval(attrValue);
+ } catch (err) {
+ inlineSettings[attrName] = attrValue;
+ }
+ }
+ }
+ }
+
+ overrides = {
+ beforeShow: function (input, dp_inst) {
+ if ($.isFunction(tp_inst._defaults.evnts.beforeShow)) {
+ return tp_inst._defaults.evnts.beforeShow.call($input[0], input, dp_inst, tp_inst);
+ }
+ },
+ onChangeMonthYear: function (year, month, dp_inst) {
+ // Update the time as well : this prevents the time from disappearing from the $input field.
+ tp_inst._updateDateTime(dp_inst);
+ if ($.isFunction(tp_inst._defaults.evnts.onChangeMonthYear)) {
+ tp_inst._defaults.evnts.onChangeMonthYear.call($input[0], year, month, dp_inst, tp_inst);
+ }
+ },
+ onClose: function (dateText, dp_inst) {
+ if (tp_inst.timeDefined === true && $input.val() !== '') {
+ tp_inst._updateDateTime(dp_inst);
+ }
+ if ($.isFunction(tp_inst._defaults.evnts.onClose)) {
+ tp_inst._defaults.evnts.onClose.call($input[0], dateText, dp_inst, tp_inst);
+ }
+ }
+ };
+ for (i in overrides) {
+ if (overrides.hasOwnProperty(i)) {
+ fns[i] = opts[i] || null;
+ }
+ }
+
+ tp_inst._defaults = $.extend({}, this._defaults, inlineSettings, opts, overrides, {
+ evnts:fns,
+ timepicker: tp_inst // add timepicker as a property of datepicker: $.datepicker._get(dp_inst, 'timepicker');
+ });
+ tp_inst.amNames = $.map(tp_inst._defaults.amNames, function(val) {
+ return val.toUpperCase();
+ });
+ tp_inst.pmNames = $.map(tp_inst._defaults.pmNames, function(val) {
+ return val.toUpperCase();
+ });
+
+ // detect which units are supported
+ tp_inst.support = detectSupport(
+ tp_inst._defaults.timeFormat +
+ (tp_inst._defaults.pickerTimeFormat? tp_inst._defaults.pickerTimeFormat:'') +
+ (tp_inst._defaults.altTimeFormat? tp_inst._defaults.altTimeFormat:''));
+
+ // controlType is string - key to our this._controls
+ if(typeof(tp_inst._defaults.controlType) === 'string'){
+ if(tp_inst._defaults.controlType == 'slider' && typeof(jQuery.ui.slider) === 'undefined'){
+ tp_inst._defaults.controlType = 'select';
+ }
+ tp_inst.control = tp_inst._controls[tp_inst._defaults.controlType];
+ }
+ // controlType is an object and must implement create, options, value methods
+ else{
+ tp_inst.control = tp_inst._defaults.controlType;
+ }
+
+ // prep the timezone options
+ var timezoneList = [-720,-660,-600,-570,-540,-480,-420,-360,-300,-270,-240,-210,-180,-120,-60,
+ 0,60,120,180,210,240,270,300,330,345,360,390,420,480,525,540,570,600,630,660,690,720,765,780,840];
+ if (tp_inst._defaults.timezoneList !== null) {
+ timezoneList = tp_inst._defaults.timezoneList;
+ }
+ var tzl=timezoneList.length,tzi=0,tzv=null;
+ if (tzl > 0 && typeof timezoneList[0] !== 'object') {
+ for(; tzi<tzl; tzi++){
+ tzv = timezoneList[tzi];
+ timezoneList[tzi] = { value: tzv, label: $.timepicker.timezoneOffsetString(tzv, tp_inst.support.iso8601) };
+ }
+ }
+ tp_inst._defaults.timezoneList = timezoneList;
+
+ // set the default units
+ tp_inst.timezone = tp_inst._defaults.timezone !== null? $.timepicker.timezoneOffsetNumber(tp_inst._defaults.timezone) :
+ ((new Date()).getTimezoneOffset()*-1);
+ tp_inst.hour = tp_inst._defaults.hour < tp_inst._defaults.hourMin? tp_inst._defaults.hourMin :
+ tp_inst._defaults.hour > tp_inst._defaults.hourMax? tp_inst._defaults.hourMax : tp_inst._defaults.hour;
+ tp_inst.minute = tp_inst._defaults.minute < tp_inst._defaults.minuteMin? tp_inst._defaults.minuteMin :
+ tp_inst._defaults.minute > tp_inst._defaults.minuteMax? tp_inst._defaults.minuteMax : tp_inst._defaults.minute;
+ tp_inst.second = tp_inst._defaults.second < tp_inst._defaults.secondMin? tp_inst._defaults.secondMin :
+ tp_inst._defaults.second > tp_inst._defaults.secondMax? tp_inst._defaults.secondMax : tp_inst._defaults.second;
+ tp_inst.millisec = tp_inst._defaults.millisec < tp_inst._defaults.millisecMin? tp_inst._defaults.millisecMin :
+ tp_inst._defaults.millisec > tp_inst._defaults.millisecMax? tp_inst._defaults.millisecMax : tp_inst._defaults.millisec;
+ tp_inst.microsec = tp_inst._defaults.microsec < tp_inst._defaults.microsecMin? tp_inst._defaults.microsecMin :
+ tp_inst._defaults.microsec > tp_inst._defaults.microsecMax? tp_inst._defaults.microsecMax : tp_inst._defaults.microsec;
+ tp_inst.ampm = '';
+ tp_inst.$input = $input;
+
+ if (tp_inst._defaults.altField) {
+ tp_inst.$altInput = $(tp_inst._defaults.altField).css({
+ cursor: 'pointer'
+ }).focus(function() {
+ $input.trigger("focus");
+ });
+ }
+
+ if (tp_inst._defaults.minDate === 0 || tp_inst._defaults.minDateTime === 0) {
+ tp_inst._defaults.minDate = new Date();
+ }
+ if (tp_inst._defaults.maxDate === 0 || tp_inst._defaults.maxDateTime === 0) {
+ tp_inst._defaults.maxDate = new Date();
+ }
+
+ // datepicker needs minDate/maxDate, timepicker needs minDateTime/maxDateTime..
+ if (tp_inst._defaults.minDate !== undefined && tp_inst._defaults.minDate instanceof Date) {
+ tp_inst._defaults.minDateTime = new Date(tp_inst._defaults.minDate.getTime());
+ }
+ if (tp_inst._defaults.minDateTime !== undefined && tp_inst._defaults.minDateTime instanceof Date) {
+ tp_inst._defaults.minDate = new Date(tp_inst._defaults.minDateTime.getTime());
+ }
+ if (tp_inst._defaults.maxDate !== undefined && tp_inst._defaults.maxDate instanceof Date) {
+ tp_inst._defaults.maxDateTime = new Date(tp_inst._defaults.maxDate.getTime());
+ }
+ if (tp_inst._defaults.maxDateTime !== undefined && tp_inst._defaults.maxDateTime instanceof Date) {
+ tp_inst._defaults.maxDate = new Date(tp_inst._defaults.maxDateTime.getTime());
+ }
+ tp_inst.$input.bind('focus', function() {
+ tp_inst._onFocus();
+ });
+
+ return tp_inst;
+ },
+
+ /*
+ * add our sliders to the calendar
+ */
+ _addTimePicker: function(dp_inst) {
+ var currDT = (this.$altInput && this._defaults.altFieldTimeOnly) ? this.$input.val() + ' ' + this.$altInput.val() : this.$input.val();
+
+ this.timeDefined = this._parseTime(currDT);
+ this._limitMinMaxDateTime(dp_inst, false);
+ this._injectTimePicker();
+ },
+
+ /*
+ * parse the time string from input value or _setTime
+ */
+ _parseTime: function(timeString, withDate) {
+ if (!this.inst) {
+ this.inst = $.datepicker._getInst(this.$input[0]);
+ }
+
+ if (withDate || !this._defaults.timeOnly) {
+ var dp_dateFormat = $.datepicker._get(this.inst, 'dateFormat');
+ try {
+ var parseRes = parseDateTimeInternal(dp_dateFormat, this._defaults.timeFormat, timeString, $.datepicker._getFormatConfig(this.inst), this._defaults);
+ if (!parseRes.timeObj) {
+ return false;
+ }
+ $.extend(this, parseRes.timeObj);
+ } catch (err) {
+ $.timepicker.log("Error parsing the date/time string: " + err +
+ "\ndate/time string = " + timeString +
+ "\ntimeFormat = " + this._defaults.timeFormat +
+ "\ndateFormat = " + dp_dateFormat);
+ return false;
+ }
+ return true;
+ } else {
+ var timeObj = $.datepicker.parseTime(this._defaults.timeFormat, timeString, this._defaults);
+ if (!timeObj) {
+ return false;
+ }
+ $.extend(this, timeObj);
+ return true;
+ }
+ },
+
+ /*
+ * generate and inject html for timepicker into ui datepicker
+ */
+ _injectTimePicker: function() {
+ var $dp = this.inst.dpDiv,
+ o = this.inst.settings,
+ tp_inst = this,
+ litem = '',
+ uitem = '',
+ show = null,
+ max = {},
+ gridSize = {},
+ size = null,
+ i=0,
+ l=0;
+
+ // Prevent displaying twice
+ if ($dp.find("div.ui-timepicker-div").length === 0 && o.showTimepicker) {
+ var noDisplay = ' style="display:none;"',
+ html = '<div class="ui-timepicker-div'+ (o.isRTL? ' ui-timepicker-rtl' : '') +'"><dl>' + '<dt class="ui_tpicker_time_label"' + ((o.showTime) ? '' : noDisplay) + '>' + o.timeText + '</dt>' +
+ '<dd class="ui_tpicker_time"' + ((o.showTime) ? '' : noDisplay) + '></dd>';
+
+ // Create the markup
+ for(i=0,l=this.units.length; i<l; i++){
+ litem = this.units[i];
+ uitem = litem.substr(0,1).toUpperCase() + litem.substr(1);
+ show = o['show'+uitem] !== null? o['show'+uitem] : this.support[litem];
+
+ // Added by Peter Medeiros:
+ // - Figure out what the hour/minute/second max should be based on the step values.
+ // - Example: if stepMinute is 15, then minMax is 45.
+ max[litem] = parseInt((o[litem+'Max'] - ((o[litem+'Max'] - o[litem+'Min']) % o['step'+uitem])), 10);
+ gridSize[litem] = 0;
+
+ html += '<dt class="ui_tpicker_'+ litem +'_label"' + (show ? '' : noDisplay) + '>' + o[litem +'Text'] + '</dt>' +
+ '<dd class="ui_tpicker_'+ litem +'"><div class="ui_tpicker_'+ litem +'_slider"' + (show ? '' : noDisplay) + '></div>';
+
+ if (show && o[litem+'Grid'] > 0) {
+ html += '<div style="padding-left: 1px"><table class="ui-tpicker-grid-label"><tr>';
+
+ if(litem == 'hour'){
+ for (var h = o[litem+'Min']; h <= max[litem]; h += parseInt(o[litem+'Grid'], 10)) {
+ gridSize[litem]++;
+ var tmph = $.datepicker.formatTime(this.support.ampm? 'hht':'HH', {hour:h}, o);
+ html += '<td data-for="'+litem+'">' + tmph + '</td>';
+ }
+ }
+ else{
+ for (var m = o[litem+'Min']; m <= max[litem]; m += parseInt(o[litem+'Grid'], 10)) {
+ gridSize[litem]++;
+ html += '<td data-for="'+litem+'">' + ((m < 10) ? '0' : '') + m + '</td>';
+ }
+ }
+
+ html += '</tr></table></div>';
+ }
+ html += '</dd>';
+ }
+
+ // Timezone
+ var showTz = o.showTimezone !== null? o.showTimezone : this.support.timezone;
+ html += '<dt class="ui_tpicker_timezone_label"' + (showTz ? '' : noDisplay) + '>' + o.timezoneText + '</dt>';
+ html += '<dd class="ui_tpicker_timezone" ' + (showTz ? '' : noDisplay) + '></dd>';
+
+ // Create the elements from string
+ html += '</dl></div>';
+ var $tp = $(html);
+
+ // if we only want time picker...
+ if (o.timeOnly === true) {
+ $tp.prepend('<div class="ui-widget-header ui-helper-clearfix ui-corner-all">' + '<div class="ui-datepicker-title">' + o.timeOnlyTitle + '</div>' + '</div>');
+ $dp.find('.ui-datepicker-header, .ui-datepicker-calendar').hide();
+ }
+
+ // add sliders, adjust grids, add events
+ for(i=0,l=tp_inst.units.length; i<l; i++){
+ litem = tp_inst.units[i];
+ uitem = litem.substr(0,1).toUpperCase() + litem.substr(1);
+ show = o['show'+uitem] !== null? o['show'+uitem] : this.support[litem];
+
+ // add the slider
+ tp_inst[litem+'_slider'] = tp_inst.control.create(tp_inst, $tp.find('.ui_tpicker_'+litem+'_slider'), litem, tp_inst[litem], o[litem+'Min'], max[litem], o['step'+uitem]);
+
+ // adjust the grid and add click event
+ if (show && o[litem+'Grid'] > 0) {
+ size = 100 * gridSize[litem] * o[litem+'Grid'] / (max[litem] - o[litem+'Min']);
+ $tp.find('.ui_tpicker_'+litem+' table').css({
+ width: size + "%",
+ marginLeft: o.isRTL? '0' : ((size / (-2 * gridSize[litem])) + "%"),
+ marginRight: o.isRTL? ((size / (-2 * gridSize[litem])) + "%") : '0',
+ borderCollapse: 'collapse'
+ }).find("td").click(function(e){
+ var $t = $(this),
+ h = $t.html(),
+ n = parseInt(h.replace(/[^0-9]/g),10),
+ ap = h.replace(/[^apm]/ig),
+ f = $t.data('for'); // loses scope, so we use data-for
+
+ if(f == 'hour'){
+ if(ap.indexOf('p') !== -1 && n < 12){
+ n += 12;
+ }
+ else{
+ if(ap.indexOf('a') !== -1 && n === 12){
+ n = 0;
+ }
+ }
+ }
+
+ tp_inst.control.value(tp_inst, tp_inst[f+'_slider'], litem, n);
+
+ tp_inst._onTimeChange();
+ tp_inst._onSelectHandler();
+ }).css({
+ cursor: 'pointer',
+ width: (100 / gridSize[litem]) + '%',
+ textAlign: 'center',
+ overflow: 'hidden'
+ });
+ } // end if grid > 0
+ } // end for loop
+
+ // Add timezone options
+ this.timezone_select = $tp.find('.ui_tpicker_timezone').append('<select></select>').find("select");
+ $.fn.append.apply(this.timezone_select,
+ $.map(o.timezoneList, function(val, idx) {
+ return $("<option />").val(typeof val == "object" ? val.value : val).text(typeof val == "object" ? val.label : val);
+ }));
+ if (typeof(this.timezone) != "undefined" && this.timezone !== null && this.timezone !== "") {
+ var local_timezone = (new Date(this.inst.selectedYear, this.inst.selectedMonth, this.inst.selectedDay, 12)).getTimezoneOffset()*-1;
+ if (local_timezone == this.timezone) {
+ selectLocalTimezone(tp_inst);
+ } else {
+ this.timezone_select.val(this.timezone);
+ }
+ } else {
+ if (typeof(this.hour) != "undefined" && this.hour !== null && this.hour !== "") {
+ this.timezone_select.val(o.timezone);
+ } else {
+ selectLocalTimezone(tp_inst);
+ }
+ }
+ this.timezone_select.change(function() {
+ tp_inst._onTimeChange();
+ tp_inst._onSelectHandler();
+ });
+ // End timezone options
+
+ // inject timepicker into datepicker
+ var $buttonPanel = $dp.find('.ui-datepicker-buttonpane');
+ if ($buttonPanel.length) {
+ $buttonPanel.before($tp);
+ } else {
+ $dp.append($tp);
+ }
+
+ this.$timeObj = $tp.find('.ui_tpicker_time');
+
+ if (this.inst !== null) {
+ var timeDefined = this.timeDefined;
+ this._onTimeChange();
+ this.timeDefined = timeDefined;
+ }
+
+ // slideAccess integration: http://trentrichardson.com/2011/11/11/jquery-ui-sliders-and-touch-accessibility/
+ if (this._defaults.addSliderAccess) {
+ var sliderAccessArgs = this._defaults.sliderAccessArgs,
+ rtl = this._defaults.isRTL;
+ sliderAccessArgs.isRTL = rtl;
+
+ setTimeout(function() { // fix for inline mode
+ if ($tp.find('.ui-slider-access').length === 0) {
+ $tp.find('.ui-slider:visible').sliderAccess(sliderAccessArgs);
+
+ // fix any grids since sliders are shorter
+ var sliderAccessWidth = $tp.find('.ui-slider-access:eq(0)').outerWidth(true);
+ if (sliderAccessWidth) {
+ $tp.find('table:visible').each(function() {
+ var $g = $(this),
+ oldWidth = $g.outerWidth(),
+ oldMarginLeft = $g.css(rtl? 'marginRight':'marginLeft').toString().replace('%', ''),
+ newWidth = oldWidth - sliderAccessWidth,
+ newMarginLeft = ((oldMarginLeft * newWidth) / oldWidth) + '%',
+ css = { width: newWidth, marginRight: 0, marginLeft: 0 };
+ css[rtl? 'marginRight':'marginLeft'] = newMarginLeft;
+ $g.css(css);
+ });
+ }
+ }
+ }, 10);
+ }
+ // end slideAccess integration
+
+ tp_inst._limitMinMaxDateTime(this.inst, true);
+ }
+ },
+
+ /*
+ * This function tries to limit the ability to go outside the
+ * min/max date range
+ */
+ _limitMinMaxDateTime: function(dp_inst, adjustSliders) {
+ var o = this._defaults,
+ dp_date = new Date(dp_inst.selectedYear, dp_inst.selectedMonth, dp_inst.selectedDay);
+
+ if (!this._defaults.showTimepicker) {
+ return;
+ } // No time so nothing to check here
+
+ if ($.datepicker._get(dp_inst, 'minDateTime') !== null && $.datepicker._get(dp_inst, 'minDateTime') !== undefined && dp_date) {
+ var minDateTime = $.datepicker._get(dp_inst, 'minDateTime'),
+ minDateTimeDate = new Date(minDateTime.getFullYear(), minDateTime.getMonth(), minDateTime.getDate(), 0, 0, 0, 0);
+
+ if (this.hourMinOriginal === null || this.minuteMinOriginal === null || this.secondMinOriginal === null || this.millisecMinOriginal === null || this.microsecMinOriginal === null) {
+ this.hourMinOriginal = o.hourMin;
+ this.minuteMinOriginal = o.minuteMin;
+ this.secondMinOriginal = o.secondMin;
+ this.millisecMinOriginal = o.millisecMin;
+ this.microsecMinOriginal = o.microsecMin;
+ }
+
+ if (dp_inst.settings.timeOnly || minDateTimeDate.getTime() == dp_date.getTime()) {
+ this._defaults.hourMin = minDateTime.getHours();
+ if (this.hour <= this._defaults.hourMin) {
+ this.hour = this._defaults.hourMin;
+ this._defaults.minuteMin = minDateTime.getMinutes();
+ if (this.minute <= this._defaults.minuteMin) {
+ this.minute = this._defaults.minuteMin;
+ this._defaults.secondMin = minDateTime.getSeconds();
+ if (this.second <= this._defaults.secondMin) {
+ this.second = this._defaults.secondMin;
+ this._defaults.millisecMin = minDateTime.getMilliseconds();
+ if(this.millisec <= this._defaults.millisecMin) {
+ this.millisec = this._defaults.millisecMin;
+ this._defaults.microsecMin = minDateTime.getMicroseconds();
+ } else {
+ if (this.microsec < this._defaults.microsecMin) {
+ this.microsec = this._defaults.microsecMin;
+ }
+ this._defaults.microsecMin = this.microsecMinOriginal;
+ }
+ } else {
+ this._defaults.millisecMin = this.millisecMinOriginal;
+ this._defaults.microsecMin = this.microsecMinOriginal;
+ }
+ } else {
+ this._defaults.secondMin = this.secondMinOriginal;
+ this._defaults.millisecMin = this.millisecMinOriginal;
+ this._defaults.microsecMin = this.microsecMinOriginal;
+ }
+ } else {
+ this._defaults.minuteMin = this.minuteMinOriginal;
+ this._defaults.secondMin = this.secondMinOriginal;
+ this._defaults.millisecMin = this.millisecMinOriginal;
+ this._defaults.microsecMin = this.microsecMinOriginal;
+ }
+ } else {
+ this._defaults.hourMin = this.hourMinOriginal;
+ this._defaults.minuteMin = this.minuteMinOriginal;
+ this._defaults.secondMin = this.secondMinOriginal;
+ this._defaults.millisecMin = this.millisecMinOriginal;
+ this._defaults.microsecMin = this.microsecMinOriginal;
+ }
+ }
+
+ if ($.datepicker._get(dp_inst, 'maxDateTime') !== null && $.datepicker._get(dp_inst, 'maxDateTime') !== undefined && dp_date) {
+ var maxDateTime = $.datepicker._get(dp_inst, 'maxDateTime'),
+ maxDateTimeDate = new Date(maxDateTime.getFullYear(), maxDateTime.getMonth(), maxDateTime.getDate(), 0, 0, 0, 0);
+
+ if (this.hourMaxOriginal === null || this.minuteMaxOriginal === null || this.secondMaxOriginal === null || this.millisecMaxOriginal === null) {
+ this.hourMaxOriginal = o.hourMax;
+ this.minuteMaxOriginal = o.minuteMax;
+ this.secondMaxOriginal = o.secondMax;
+ this.millisecMaxOriginal = o.millisecMax;
+ this.microsecMaxOriginal = o.microsecMax;
+ }
+
+ if (dp_inst.settings.timeOnly || maxDateTimeDate.getTime() == dp_date.getTime()) {
+ this._defaults.hourMax = maxDateTime.getHours();
+ if (this.hour >= this._defaults.hourMax) {
+ this.hour = this._defaults.hourMax;
+ this._defaults.minuteMax = maxDateTime.getMinutes();
+ if (this.minute >= this._defaults.minuteMax) {
+ this.minute = this._defaults.minuteMax;
+ this._defaults.secondMax = maxDateTime.getSeconds();
+ if (this.second >= this._defaults.secondMax) {
+ this.second = this._defaults.secondMax;
+ this._defaults.millisecMax = maxDateTime.getMilliseconds();
+ if (this.millisec >= this._defaults.millisecMax) {
+ this.millisec = this._defaults.millisecMax;
+ this._defaults.microsecMax = maxDateTime.getMicroseconds();
+ } else {
+ if (this.microsec > this._defaults.microsecMax) {
+ this.microsec = this._defaults.microsecMax;
+ }
+ this._defaults.microsecMax = this.microsecMaxOriginal;
+ }
+ } else {
+ this._defaults.millisecMax = this.millisecMaxOriginal;
+ this._defaults.microsecMax = this.microsecMaxOriginal;
+ }
+ } else {
+ this._defaults.secondMax = this.secondMaxOriginal;
+ this._defaults.millisecMax = this.millisecMaxOriginal;
+ this._defaults.microsecMax = this.microsecMaxOriginal;
+ }
+ } else {
+ this._defaults.minuteMax = this.minuteMaxOriginal;
+ this._defaults.secondMax = this.secondMaxOriginal;
+ this._defaults.millisecMax = this.millisecMaxOriginal;
+ this._defaults.microsecMax = this.microsecMaxOriginal;
+ }
+ } else {
+ this._defaults.hourMax = this.hourMaxOriginal;
+ this._defaults.minuteMax = this.minuteMaxOriginal;
+ this._defaults.secondMax = this.secondMaxOriginal;
+ this._defaults.millisecMax = this.millisecMaxOriginal;
+ this._defaults.microsecMax = this.microsecMaxOriginal;
+ }
+ }
+
+ if (adjustSliders !== undefined && adjustSliders === true) {
+ var hourMax = parseInt((this._defaults.hourMax - ((this._defaults.hourMax - this._defaults.hourMin) % this._defaults.stepHour)), 10),
+ minMax = parseInt((this._defaults.minuteMax - ((this._defaults.minuteMax - this._defaults.minuteMin) % this._defaults.stepMinute)), 10),
+ secMax = parseInt((this._defaults.secondMax - ((this._defaults.secondMax - this._defaults.secondMin) % this._defaults.stepSecond)), 10),
+ millisecMax = parseInt((this._defaults.millisecMax - ((this._defaults.millisecMax - this._defaults.millisecMin) % this._defaults.stepMillisec)), 10);
+ microsecMax = parseInt((this._defaults.microsecMax - ((this._defaults.microsecMax - this._defaults.microsecMin) % this._defaults.stepMicrosec)), 10);
+
+ if (this.hour_slider) {
+ this.control.options(this, this.hour_slider, 'hour', { min: this._defaults.hourMin, max: hourMax });
+ this.control.value(this, this.hour_slider, 'hour', this.hour - (this.hour % this._defaults.stepHour));
+ }
+ if (this.minute_slider) {
+ this.control.options(this, this.minute_slider, 'minute', { min: this._defaults.minuteMin, max: minMax });
+ this.control.value(this, this.minute_slider, 'minute', this.minute - (this.minute % this._defaults.stepMinute));
+ }
+ if (this.second_slider) {
+ this.control.options(this, this.second_slider, 'second', { min: this._defaults.secondMin, max: secMax });
+ this.control.value(this, this.second_slider, 'second', this.second - (this.second % this._defaults.stepSecond));
+ }
+ if (this.millisec_slider) {
+ this.control.options(this, this.millisec_slider, 'millisec', { min: this._defaults.millisecMin, max: millisecMax });
+ this.control.value(this, this.millisec_slider, 'millisec', this.millisec - (this.millisec % this._defaults.stepMillisec));
+ }
+ if (this.microsec_slider) {
+ this.control.options(this, this.microsec_slider, 'microsec', { min: this._defaults.microsecMin, max: microsecMax });
+ this.control.value(this, this.microsec_slider, 'microsec', this.microsec - (this.microsec % this._defaults.stepMicrosec));
+ }
+ }
+
+ },
+
+ /*
+ * when a slider moves, set the internal time...
+ * on time change is also called when the time is updated in the text field
+ */
+ _onTimeChange: function() {
+ var hour = (this.hour_slider) ? this.control.value(this, this.hour_slider, 'hour') : false,
+ minute = (this.minute_slider) ? this.control.value(this, this.minute_slider, 'minute') : false,
+ second = (this.second_slider) ? this.control.value(this, this.second_slider, 'second') : false,
+ millisec = (this.millisec_slider) ? this.control.value(this, this.millisec_slider, 'millisec') : false,
+ microsec = (this.microsec_slider) ? this.control.value(this, this.microsec_slider, 'microsec') : false,
+ timezone = (this.timezone_select) ? this.timezone_select.val() : false,
+ o = this._defaults,
+ pickerTimeFormat = o.pickerTimeFormat || o.timeFormat,
+ pickerTimeSuffix = o.pickerTimeSuffix || o.timeSuffix;
+
+ if (typeof(hour) == 'object') {
+ hour = false;
+ }
+ if (typeof(minute) == 'object') {
+ minute = false;
+ }
+ if (typeof(second) == 'object') {
+ second = false;
+ }
+ if (typeof(millisec) == 'object') {
+ millisec = false;
+ }
+ if (typeof(microsec) == 'object') {
+ microsec = false;
+ }
+ if (typeof(timezone) == 'object') {
+ timezone = false;
+ }
+
+ if (hour !== false) {
+ hour = parseInt(hour, 10);
+ }
+ if (minute !== false) {
+ minute = parseInt(minute, 10);
+ }
+ if (second !== false) {
+ second = parseInt(second, 10);
+ }
+ if (millisec !== false) {
+ millisec = parseInt(millisec, 10);
+ }
+ if (microsec !== false) {
+ microsec = parseInt(microsec, 10);
+ }
+
+ var ampm = o[hour < 12 ? 'amNames' : 'pmNames'][0];
+
+ // If the update was done in the input field, the input field should not be updated.
+ // If the update was done using the sliders, update the input field.
+ var hasChanged = (hour != this.hour || minute != this.minute || second != this.second || millisec != this.millisec || microsec != this.microsec
+ || (this.ampm.length > 0 && (hour < 12) != ($.inArray(this.ampm.toUpperCase(), this.amNames) !== -1))
+ || (this.timezone !== null && timezone != this.timezone));
+
+ if (hasChanged) {
+
+ if (hour !== false) {
+ this.hour = hour;
+ }
+ if (minute !== false) {
+ this.minute = minute;
+ }
+ if (second !== false) {
+ this.second = second;
+ }
+ if (millisec !== false) {
+ this.millisec = millisec;
+ }
+ if (microsec !== false) {
+ this.microsec = microsec;
+ }
+ if (timezone !== false) {
+ this.timezone = timezone;
+ }
+
+ if (!this.inst) {
+ this.inst = $.datepicker._getInst(this.$input[0]);
+ }
+
+ this._limitMinMaxDateTime(this.inst, true);
+ }
+ if (this.support.ampm) {
+ this.ampm = ampm;
+ }
+
+ // Updates the time within the timepicker
+ this.formattedTime = $.datepicker.formatTime(o.timeFormat, this, o);
+ if (this.$timeObj) {
+ if(pickerTimeFormat === o.timeFormat){
+ this.$timeObj.text(this.formattedTime + pickerTimeSuffix);
+ }
+ else{
+ this.$timeObj.text($.datepicker.formatTime(pickerTimeFormat, this, o) + pickerTimeSuffix);
+ }
+ }
+
+ this.timeDefined = true;
+ if (hasChanged) {
+ this._updateDateTime();
+ }
+ },
+
+ /*
+ * call custom onSelect.
+ * bind to sliders slidestop, and grid click.
+ */
+ _onSelectHandler: function() {
+ var onSelect = this._defaults.onSelect || this.inst.settings.onSelect;
+ var inputEl = this.$input ? this.$input[0] : null;
+ if (onSelect && inputEl) {
+ onSelect.apply(inputEl, [this.formattedDateTime, this]);
+ }
+ },
+
+ /*
+ * update our input with the new date time..
+ */
+ _updateDateTime: function(dp_inst) {
+ dp_inst = this.inst || dp_inst;
+ //var dt = $.datepicker._daylightSavingAdjust(new Date(dp_inst.selectedYear, dp_inst.selectedMonth, dp_inst.selectedDay)),
+ var dt = $.datepicker._daylightSavingAdjust(new Date(dp_inst.currentYear, dp_inst.currentMonth, dp_inst.currentDay)),
+ dateFmt = $.datepicker._get(dp_inst, 'dateFormat'),
+ formatCfg = $.datepicker._getFormatConfig(dp_inst),
+ timeAvailable = dt !== null && this.timeDefined;
+ this.formattedDate = $.datepicker.formatDate(dateFmt, (dt === null ? new Date() : dt), formatCfg);
+ var formattedDateTime = this.formattedDate;
+
+ // if a slider was changed but datepicker doesn't have a value yet, set it
+ if(dp_inst.lastVal===""){
+ dp_inst.currentYear=dp_inst.selectedYear;
+ dp_inst.currentMonth=dp_inst.selectedMonth;
+ dp_inst.currentDay=dp_inst.selectedDay;
+ }
+
+ /*
+ * remove following lines to force every changes in date picker to change the input value
+ * Bug descriptions: when an input field has a default value, and click on the field to pop up the date picker.
+ * If the user manually empty the value in the input field, the date picker will never change selected value.
+ */
+ //if (dp_inst.lastVal !== undefined && (dp_inst.lastVal.length > 0 && this.$input.val().length === 0)) {
+ // return;
+ //}
+
+ if (this._defaults.timeOnly === true) {
+ formattedDateTime = this.formattedTime;
+ } else if (this._defaults.timeOnly !== true && (this._defaults.alwaysSetTime || timeAvailable)) {
+ formattedDateTime += this._defaults.separator + this.formattedTime + this._defaults.timeSuffix;
+ }
+
+ this.formattedDateTime = formattedDateTime;
+
+ if (!this._defaults.showTimepicker) {
+ this.$input.val(this.formattedDate);
+ } else if (this.$altInput && this._defaults.timeOnly === false && this._defaults.altFieldTimeOnly === true) {
+ this.$altInput.val(this.formattedTime);
+ this.$input.val(this.formattedDate);
+ } else if (this.$altInput) {
+ this.$input.val(formattedDateTime);
+ var altFormattedDateTime = '',
+ altSeparator = this._defaults.altSeparator ? this._defaults.altSeparator : this._defaults.separator,
+ altTimeSuffix = this._defaults.altTimeSuffix ? this._defaults.altTimeSuffix : this._defaults.timeSuffix;
+
+ if(!this._defaults.timeOnly){
+ if (this._defaults.altFormat){
+ altFormattedDateTime = $.datepicker.formatDate(this._defaults.altFormat, (dt === null ? new Date() : dt), formatCfg);
+ }
+ else{
+ altFormattedDateTime = this.formattedDate;
+ }
+
+ if (altFormattedDateTime){
+ altFormattedDateTime += altSeparator;
+ }
+ }
+
+ if(this._defaults.altTimeFormat){
+ altFormattedDateTime += $.datepicker.formatTime(this._defaults.altTimeFormat, this, this._defaults) + altTimeSuffix;
+ }
+ else{
+ altFormattedDateTime += this.formattedTime + altTimeSuffix;
+ }
+ this.$altInput.val(altFormattedDateTime);
+ } else {
+ this.$input.val(formattedDateTime);
+ }
+
+ this.$input.trigger("change");
+ },
+
+ _onFocus: function() {
+ if (!this.$input.val() && this._defaults.defaultValue) {
+ this.$input.val(this._defaults.defaultValue);
+ var inst = $.datepicker._getInst(this.$input.get(0)),
+ tp_inst = $.datepicker._get(inst, 'timepicker');
+ if (tp_inst) {
+ if (tp_inst._defaults.timeOnly && (inst.input.val() != inst.lastVal)) {
+ try {
+ $.datepicker._updateDatepicker(inst);
+ } catch (err) {
+ $.timepicker.log(err);
+ }
+ }
+ }
+ }
+ },
+
+ /*
+ * Small abstraction to control types
+ * We can add more, just be sure to follow the pattern: create, options, value
+ */
+ _controls: {
+ // slider methods
+ slider: {
+ create: function(tp_inst, obj, unit, val, min, max, step){
+ var rtl = tp_inst._defaults.isRTL; // if rtl go -60->0 instead of 0->60
+ return obj.prop('slide', null).slider({
+ orientation: "horizontal",
+ value: rtl? val*-1 : val,
+ min: rtl? max*-1 : min,
+ max: rtl? min*-1 : max,
+ step: step,
+ slide: function(event, ui) {
+ tp_inst.control.value(tp_inst, $(this), unit, rtl? ui.value*-1:ui.value);
+ tp_inst._onTimeChange();
+ },
+ stop: function(event, ui) {
+ tp_inst._onSelectHandler();
+ }
+ });
+ },
+ options: function(tp_inst, obj, unit, opts, val){
+ if(tp_inst._defaults.isRTL){
+ if(typeof(opts) == 'string'){
+ if(opts == 'min' || opts == 'max'){
+ if(val !== undefined){
+ return obj.slider(opts, val*-1);
+ }
+ return Math.abs(obj.slider(opts));
+ }
+ return obj.slider(opts);
+ }
+ var min = opts.min,
+ max = opts.max;
+ opts.min = opts.max = null;
+ if(min !== undefined){
+ opts.max = min * -1;
+ }
+ if(max !== undefined){
+ opts.min = max * -1;
+ }
+ return obj.slider(opts);
+ }
+ if(typeof(opts) == 'string' && val !== undefined){
+ return obj.slider(opts, val);
+ }
+ return obj.slider(opts);
+ },
+ value: function(tp_inst, obj, unit, val){
+ if(tp_inst._defaults.isRTL){
+ if(val !== undefined){
+ return obj.slider('value', val*-1);
+ }
+ return Math.abs(obj.slider('value'));
+ }
+ if(val !== undefined){
+ return obj.slider('value', val);
+ }
+ return obj.slider('value');
+ }
+ },
+ // select methods
+ select: {
+ create: function(tp_inst, obj, unit, val, min, max, step){
+ var sel = '<select class="ui-timepicker-select" data-unit="'+ unit +'" data-min="'+ min +'" data-max="'+ max +'" data-step="'+ step +'">',
+ format = tp_inst._defaults.pickerTimeFormat || tp_inst._defaults.timeFormat;
+
+ for(var i=min; i<=max; i+=step){
+ sel += '<option value="'+ i +'"'+ (i==val? ' selected':'') +'>';
+ if(unit == 'hour'){
+ sel += $.datepicker.formatTime($.trim(format.replace(/[^ht ]/ig,'')), {hour:i}, tp_inst._defaults);
+ }
+ else if(unit == 'millisec' || unit == 'microsec' || i >= 10){ sel += i; }
+ else {sel += '0'+ i.toString(); }
+ sel += '</option>';
+ }
+ sel += '</select>';
+
+ obj.children('select').remove();
+
+ $(sel).appendTo(obj).change(function(e){
+ tp_inst._onTimeChange();
+ tp_inst._onSelectHandler();
+ });
+
+ return obj;
+ },
+ options: function(tp_inst, obj, unit, opts, val){
+ var o = {},
+ $t = obj.children('select');
+ if(typeof(opts) == 'string'){
+ if(val === undefined){
+ return $t.data(opts);
+ }
+ o[opts] = val;
+ }
+ else{ o = opts; }
+ return tp_inst.control.create(tp_inst, obj, $t.data('unit'), $t.val(), o.min || $t.data('min'), o.max || $t.data('max'), o.step || $t.data('step'));
+ },
+ value: function(tp_inst, obj, unit, val){
+ var $t = obj.children('select');
+ if(val !== undefined){
+ return $t.val(val);
+ }
+ return $t.val();
+ }
+ }
+ } // end _controls
+
+ });
+
+ $.fn.extend({
+ /*
+ * shorthand just to use timepicker..
+ */
+ timepicker: function(o) {
+ o = o || {};
+ var tmp_args = Array.prototype.slice.call(arguments);
+
+ if (typeof o == 'object') {
+ tmp_args[0] = $.extend(o, {
+ timeOnly: true
+ });
+ }
+
+ return $(this).each(function() {
+ $.fn.datetimepicker.apply($(this), tmp_args);
+ });
+ },
+
+ /*
+ * extend timepicker to datepicker
+ */
+ datetimepicker: function(o) {
+ o = o || {};
+ var tmp_args = arguments;
+
+ if (typeof(o) == 'string') {
+ if (o == 'getDate') {
+ return $.fn.datepicker.apply($(this[0]), tmp_args);
+ } else {
+ return this.each(function() {
+ var $t = $(this);
+ $t.datepicker.apply($t, tmp_args);
+ });
+ }
+ } else {
+ return this.each(function() {
+ var $t = $(this);
+ $t.datepicker($.timepicker._newInst($t, o)._defaults);
+ });
+ }
+ }
+ });
+
+ /*
+ * Public Utility to parse date and time
+ */
+ $.datepicker.parseDateTime = function(dateFormat, timeFormat, dateTimeString, dateSettings, timeSettings) {
+ var parseRes = parseDateTimeInternal(dateFormat, timeFormat, dateTimeString, dateSettings, timeSettings);
+ if (parseRes.timeObj) {
+ var t = parseRes.timeObj;
+ parseRes.date.setHours(t.hour, t.minute, t.second, t.millisec);
+ parseRes.date.setMicroseconds(t.microsec);
+ }
+
+ return parseRes.date;
+ };
+
+ /*
+ * Public utility to parse time
+ */
+ $.datepicker.parseTime = function(timeFormat, timeString, options) {
+ var o = extendRemove(extendRemove({}, $.timepicker._defaults), options || {}),
+ iso8601 = (timeFormat.replace(/\'.*?\'/g,'').indexOf('Z') !== -1);
+
+ // Strict parse requires the timeString to match the timeFormat exactly
+ var strictParse = function(f, s, o){
+
+ // pattern for standard and localized AM/PM markers
+ var getPatternAmpm = function(amNames, pmNames) {
+ var markers = [];
+ if (amNames) {
+ $.merge(markers, amNames);
+ }
+ if (pmNames) {
+ $.merge(markers, pmNames);
+ }
+ markers = $.map(markers, function(val) {
+ return val.replace(/[.*+?|()\[\]{}\\]/g, '\\$&');
+ });
+ return '(' + markers.join('|') + ')?';
+ };
+
+ // figure out position of time elements.. cause js cant do named captures
+ var getFormatPositions = function(timeFormat) {
+ var finds = timeFormat.toLowerCase().match(/(h{1,2}|m{1,2}|s{1,2}|l{1}|c{1}|t{1,2}|z|'.*?')/g),
+ orders = {
+ h: -1,
+ m: -1,
+ s: -1,
+ l: -1,
+ c: -1,
+ t: -1,
+ z: -1
+ };
+
+ if (finds) {
+ for (var i = 0; i < finds.length; i++) {
+ if (orders[finds[i].toString().charAt(0)] == -1) {
+ orders[finds[i].toString().charAt(0)] = i + 1;
+ }
+ }
+ }
+ return orders;
+ };
+
+ var regstr = '^' + f.toString()
+ .replace(/([hH]{1,2}|mm?|ss?|[tT]{1,2}|[zZ]|[lc]|'.*?')/g, function (match) {
+ var ml = match.length;
+ switch (match.charAt(0).toLowerCase()) {
+ case 'h': return ml === 1? '(\\d?\\d)':'(\\d{'+ml+'})';
+ case 'm': return ml === 1? '(\\d?\\d)':'(\\d{'+ml+'})';
+ case 's': return ml === 1? '(\\d?\\d)':'(\\d{'+ml+'})';
+ case 'l': return '(\\d?\\d?\\d)';
+ case 'c': return '(\\d?\\d?\\d)';
+ case 'z': return '(z|[-+]\\d\\d:?\\d\\d|\\S+)?';
+ case 't': return getPatternAmpm(o.amNames, o.pmNames);
+ default: // literal escaped in quotes
+ return '(' + match.replace(/\'/g, "").replace(/(\.|\$|\^|\\|\/|\(|\)|\[|\]|\?|\+|\*)/g, function (m) { return "\\" + m; }) + ')?';
+ }
+ })
+ .replace(/\s/g, '\\s?') +
+ o.timeSuffix + '$',
+ order = getFormatPositions(f),
+ ampm = '',
+ treg;
+
+ treg = s.match(new RegExp(regstr, 'i'));
+
+ var resTime = {
+ hour: 0,
+ minute: 0,
+ second: 0,
+ millisec: 0,
+ microsec: 0
+ };
+
+ if (treg) {
+ if (order.t !== -1) {
+ if (treg[order.t] === undefined || treg[order.t].length === 0) {
+ ampm = '';
+ resTime.ampm = '';
+ } else {
+ ampm = $.inArray(treg[order.t].toUpperCase(), o.amNames) !== -1 ? 'AM' : 'PM';
+ resTime.ampm = o[ampm == 'AM' ? 'amNames' : 'pmNames'][0];
+ }
+ }
+
+ if (order.h !== -1) {
+ if (ampm == 'AM' && treg[order.h] == '12') {
+ resTime.hour = 0; // 12am = 0 hour
+ } else {
+ if (ampm == 'PM' && treg[order.h] != '12') {
+ resTime.hour = parseInt(treg[order.h], 10) + 12; // 12pm = 12 hour, any other pm = hour + 12
+ } else {
+ resTime.hour = Number(treg[order.h]);
+ }
+ }
+ }
+
+ if (order.m !== -1) {
+ resTime.minute = Number(treg[order.m]);
+ }
+ if (order.s !== -1) {
+ resTime.second = Number(treg[order.s]);
+ }
+ if (order.l !== -1) {
+ resTime.millisec = Number(treg[order.l]);
+ }
+ if (order.c !== -1) {
+ resTime.microsec = Number(treg[order.c]);
+ }
+ if (order.z !== -1 && treg[order.z] !== undefined) {
+ resTime.timezone = $.timepicker.timezoneOffsetNumber(treg[order.z]);
+ }
+
+
+ return resTime;
+ }
+ return false;
+ };// end strictParse
+
+ // First try JS Date, if that fails, use strictParse
+ var looseParse = function(f,s,o){
+ try{
+ var d = new Date('2012-01-01 '+ s);
+ if(isNaN(d.getTime())){
+ d = new Date('2012-01-01T'+ s);
+ if(isNaN(d.getTime())){
+ d = new Date('01/01/2012 '+ s);
+ if(isNaN(d.getTime())){
+ throw "Unable to parse time with native Date: "+ s;
+ }
+ }
+ }
+
+ return {
+ hour: d.getHours(),
+ minute: d.getMinutes(),
+ second: d.getSeconds(),
+ millisec: d.getMilliseconds(),
+ microsec: d.getMicroseconds(),
+ timezone: d.getTimezoneOffset()*-1
+ };
+ }
+ catch(err){
+ try{
+ return strictParse(f,s,o);
+ }
+ catch(err2){
+ $.timepicker.log("Unable to parse \ntimeString: "+ s +"\ntimeFormat: "+ f);
+ }
+ }
+ return false;
+ }; // end looseParse
+
+ if(typeof o.parse === "function"){
+ return o.parse(timeFormat, timeString, o);
+ }
+ if(o.parse === 'loose'){
+ return looseParse(timeFormat, timeString, o);
+ }
+ return strictParse(timeFormat, timeString, o);
+ };
+
+ /*
+ * Public utility to format the time
+ * format = string format of the time
+ * time = a {}, not a Date() for timezones
+ * options = essentially the regional[].. amNames, pmNames, ampm
+ */
+ $.datepicker.formatTime = function(format, time, options) {
+ options = options || {};
+ options = $.extend({}, $.timepicker._defaults, options);
+ time = $.extend({
+ hour: 0,
+ minute: 0,
+ second: 0,
+ millisec: 0,
+ timezone: 0
+ }, time);
+
+ var tmptime = format,
+ ampmName = options.amNames[0],
+ hour = parseInt(time.hour, 10);
+
+ if (hour > 11) {
+ ampmName = options.pmNames[0];
+ }
+
+ tmptime = tmptime.replace(/(?:HH?|hh?|mm?|ss?|[tT]{1,2}|[zZ]|[lc]|('.*?'|".*?"))/g, function(match) {
+ switch (match) {
+ case 'HH':
+ return ('0' + hour).slice(-2);
+ case 'H':
+ return hour;
+ case 'hh':
+ return ('0' + convert24to12(hour)).slice(-2);
+ case 'h':
+ return convert24to12(hour);
+ case 'mm':
+ return ('0' + time.minute).slice(-2);
+ case 'm':
+ return time.minute;
+ case 'ss':
+ return ('0' + time.second).slice(-2);
+ case 's':
+ return time.second;
+ case 'l':
+ return ('00' + time.millisec).slice(-3);
+ case 'c':
+ return ('00' + time.microsec).slice(-3);
+ case 'z':
+ return $.timepicker.timezoneOffsetString(time.timezone === null? options.timezone : time.timezone, false);
+ case 'Z':
+ return $.timepicker.timezoneOffsetString(time.timezone === null? options.timezone : time.timezone, true);
+ case 'T':
+ return ampmName.charAt(0).toUpperCase();
+ case 'TT':
+ return ampmName.toUpperCase();
+ case 't':
+ return ampmName.charAt(0).toLowerCase();
+ case 'tt':
+ return ampmName.toLowerCase();
+ default:
+ return match.replace(/\'/g, "") || "'";
+ }
+ });
+
+ tmptime = $.trim(tmptime);
+ return tmptime;
+ };
+
+ /*
+ * the bad hack :/ override datepicker so it doesnt close on select
+ // inspired: http://stackoverflow.com/questions/1252512/jquery-datepicker-prevent-closing-picker-when-clicking-a-date/1762378#1762378
+ */
+ $.datepicker._base_selectDate = $.datepicker._selectDate;
+ $.datepicker._selectDate = function(id, dateStr) {
+ var inst = this._getInst($(id)[0]),
+ tp_inst = this._get(inst, 'timepicker');
+
+ if (tp_inst) {
+ tp_inst._limitMinMaxDateTime(inst, true);
+ inst.inline = inst.stay_open = true;
+ //This way the onSelect handler called from calendarpicker get the full dateTime
+ this._base_selectDate(id, dateStr);
+ inst.inline = inst.stay_open = false;
+ this._notifyChange(inst);
+ this._updateDatepicker(inst);
+ } else {
+ this._base_selectDate(id, dateStr);
+ }
+ };
+
+ /*
+ * second bad hack :/ override datepicker so it triggers an event when changing the input field
+ * and does not redraw the datepicker on every selectDate event
+ */
+ $.datepicker._base_updateDatepicker = $.datepicker._updateDatepicker;
+ $.datepicker._updateDatepicker = function(inst) {
+
+ // don't popup the datepicker if there is another instance already opened
+ var input = inst.input[0];
+ if ($.datepicker._curInst && $.datepicker._curInst != inst && $.datepicker._datepickerShowing && $.datepicker._lastInput != input) {
+ return;
+ }
+
+ if (typeof(inst.stay_open) !== 'boolean' || inst.stay_open === false) {
+
+ this._base_updateDatepicker(inst);
+
+ // Reload the time control when changing something in the input text field.
+ var tp_inst = this._get(inst, 'timepicker');
+ if (tp_inst) {
+ tp_inst._addTimePicker(inst);
+ }
+ }
+ };
+
+ /*
+ * third bad hack :/ override datepicker so it allows spaces and colon in the input field
+ */
+ $.datepicker._base_doKeyPress = $.datepicker._doKeyPress;
+ $.datepicker._doKeyPress = function(event) {
+ var inst = $.datepicker._getInst(event.target),
+ tp_inst = $.datepicker._get(inst, 'timepicker');
+
+ if (tp_inst) {
+ if ($.datepicker._get(inst, 'constrainInput')) {
+ var ampm = tp_inst.support.ampm,
+ tz = tp_inst._defaults.showTimezone !== null? tp_inst._defaults.showTimezone : tp_inst.support.timezone,
+ dateChars = $.datepicker._possibleChars($.datepicker._get(inst, 'dateFormat')),
+ datetimeChars = tp_inst._defaults.timeFormat.toString()
+ .replace(/[hms]/g, '')
+ .replace(/TT/g, ampm ? 'APM' : '')
+ .replace(/Tt/g, ampm ? 'AaPpMm' : '')
+ .replace(/tT/g, ampm ? 'AaPpMm' : '')
+ .replace(/T/g, ampm ? 'AP' : '')
+ .replace(/tt/g, ampm ? 'apm' : '')
+ .replace(/t/g, ampm ? 'ap' : '') +
+ " " + tp_inst._defaults.separator +
+ tp_inst._defaults.timeSuffix +
+ (tz ? tp_inst._defaults.timezoneList.join('') : '') +
+ (tp_inst._defaults.amNames.join('')) + (tp_inst._defaults.pmNames.join('')) +
+ dateChars,
+ chr = String.fromCharCode(event.charCode === undefined ? event.keyCode : event.charCode);
+ return event.ctrlKey || (chr < ' ' || !dateChars || datetimeChars.indexOf(chr) > -1);
+ }
+ }
+
+ return $.datepicker._base_doKeyPress(event);
+ };
+
+ /*
+ * Fourth bad hack :/ override _updateAlternate function used in inline mode to init altField
+ */
+ $.datepicker._base_updateAlternate = $.datepicker._updateAlternate;
+ /* Update any alternate field to synchronise with the main field. */
+ $.datepicker._updateAlternate = function(inst) {
+ var tp_inst = this._get(inst, 'timepicker');
+ if(tp_inst){
+ var altField = tp_inst._defaults.altField;
+ if (altField) { // update alternate field too
+ var altFormat = tp_inst._defaults.altFormat || tp_inst._defaults.dateFormat,
+ date = this._getDate(inst),
+ formatCfg = $.datepicker._getFormatConfig(inst),
+ altFormattedDateTime = '',
+ altSeparator = tp_inst._defaults.altSeparator ? tp_inst._defaults.altSeparator : tp_inst._defaults.separator,
+ altTimeSuffix = tp_inst._defaults.altTimeSuffix ? tp_inst._defaults.altTimeSuffix : tp_inst._defaults.timeSuffix,
+ altTimeFormat = tp_inst._defaults.altTimeFormat !== null ? tp_inst._defaults.altTimeFormat : tp_inst._defaults.timeFormat;
+
+ altFormattedDateTime += $.datepicker.formatTime(altTimeFormat, tp_inst, tp_inst._defaults) + altTimeSuffix;
+ if(!tp_inst._defaults.timeOnly && !tp_inst._defaults.altFieldTimeOnly && date !== null){
+ if(tp_inst._defaults.altFormat){
+ altFormattedDateTime = $.datepicker.formatDate(tp_inst._defaults.altFormat, date, formatCfg) + altSeparator + altFormattedDateTime;
+ }
+ else{
+ altFormattedDateTime = tp_inst.formattedDate + altSeparator + altFormattedDateTime;
+ }
+ }
+ $(altField).val(altFormattedDateTime);
+ }
+ }
+ else{
+ $.datepicker._base_updateAlternate(inst);
+ }
+ };
+
+ /*
+ * Override key up event to sync manual input changes.
+ */
+ $.datepicker._base_doKeyUp = $.datepicker._doKeyUp;
+ $.datepicker._doKeyUp = function(event) {
+ var inst = $.datepicker._getInst(event.target),
+ tp_inst = $.datepicker._get(inst, 'timepicker');
+
+ if (tp_inst) {
+ if (tp_inst._defaults.timeOnly && (inst.input.val() != inst.lastVal)) {
+ try {
+ $.datepicker._updateDatepicker(inst);
+ } catch (err) {
+ $.timepicker.log(err);
+ }
+ }
+ }
+
+ return $.datepicker._base_doKeyUp(event);
+ };
+
+ /*
+ * override "Today" button to also grab the time.
+ */
+ $.datepicker._base_gotoToday = $.datepicker._gotoToday;
+ $.datepicker._gotoToday = function(id) {
+ var inst = this._getInst($(id)[0]),
+ $dp = inst.dpDiv;
+ this._base_gotoToday(id);
+ var tp_inst = this._get(inst, 'timepicker');
+ selectLocalTimezone(tp_inst);
+ var now = new Date();
+ this._setTime(inst, now);
+ $('.ui-datepicker-today', $dp).click();
+ };
+
+ /*
+ * Disable & enable the Time in the datetimepicker
+ */
+ $.datepicker._disableTimepickerDatepicker = function(target) {
+ var inst = this._getInst(target);
+ if (!inst) {
+ return;
+ }
+
+ var tp_inst = this._get(inst, 'timepicker');
+ $(target).datepicker('getDate'); // Init selected[Year|Month|Day]
+ if (tp_inst) {
+ tp_inst._defaults.showTimepicker = false;
+ tp_inst._updateDateTime(inst);
+ }
+ };
+
+ $.datepicker._enableTimepickerDatepicker = function(target) {
+ var inst = this._getInst(target);
+ if (!inst) {
+ return;
+ }
+
+ var tp_inst = this._get(inst, 'timepicker');
+ $(target).datepicker('getDate'); // Init selected[Year|Month|Day]
+ if (tp_inst) {
+ tp_inst._defaults.showTimepicker = true;
+ tp_inst._addTimePicker(inst); // Could be disabled on page load
+ tp_inst._updateDateTime(inst);
+ }
+ };
+
+ /*
+ * Create our own set time function
+ */
+ $.datepicker._setTime = function(inst, date) {
+ var tp_inst = this._get(inst, 'timepicker');
+ if (tp_inst) {
+ var defaults = tp_inst._defaults;
+
+ // calling _setTime with no date sets time to defaults
+ tp_inst.hour = date ? date.getHours() : defaults.hour;
+ tp_inst.minute = date ? date.getMinutes() : defaults.minute;
+ tp_inst.second = date ? date.getSeconds() : defaults.second;
+ tp_inst.millisec = date ? date.getMilliseconds() : defaults.millisec;
+ tp_inst.microsec = date ? date.getMicroseconds() : defaults.microsec;
+
+ //check if within min/max times..
+ tp_inst._limitMinMaxDateTime(inst, true);
+
+ tp_inst._onTimeChange();
+ tp_inst._updateDateTime(inst);
+ }
+ };
+
+ /*
+ * Create new public method to set only time, callable as $().datepicker('setTime', date)
+ */
+ $.datepicker._setTimeDatepicker = function(target, date, withDate) {
+ var inst = this._getInst(target);
+ if (!inst) {
+ return;
+ }
+
+ var tp_inst = this._get(inst, 'timepicker');
+
+ if (tp_inst) {
+ this._setDateFromField(inst);
+ var tp_date;
+ if (date) {
+ if (typeof date == "string") {
+ tp_inst._parseTime(date, withDate);
+ tp_date = new Date();
+ tp_date.setHours(tp_inst.hour, tp_inst.minute, tp_inst.second, tp_inst.millisec);
+ tp_date.setMicroseconds(tp_inst.microsec);
+ } else {
+ tp_date = new Date(date.getTime());
+ tp_date.setMicroseconds(date.getMicroseconds());
+ }
+ if (tp_date.toString() == 'Invalid Date') {
+ tp_date = undefined;
+ }
+ this._setTime(inst, tp_date);
+ }
+ }
+
+ };
+
+ /*
+ * override setDate() to allow setting time too within Date object
+ */
+ $.datepicker._base_setDateDatepicker = $.datepicker._setDateDatepicker;
+ $.datepicker._setDateDatepicker = function(target, date) {
+ var inst = this._getInst(target);
+ if (!inst) {
+ return;
+ }
+
+ if(typeof(date) === 'string'){
+ date = new Date(date);
+ if(!date.getTime()){
+ $.timepicker.log("Error creating Date object from string.");
+ }
+ }
+
+ var tp_inst = this._get(inst, 'timepicker');
+ var tp_date;
+ if (date instanceof Date) {
+ tp_date = new Date(date.getTime());
+ tp_date.setMicroseconds(date.getMicroseconds());
+ } else {
+ tp_date = date;
+ }
+
+ // This is important if you are using the timezone option, javascript's Date
+ // object will only return the timezone offset for the current locale, so we
+ // adjust it accordingly. If not using timezone option this won't matter..
+ // If a timezone is different in tp, keep the timezone as is
+ if(tp_inst){
+ // look out for DST if tz wasn't specified
+ if(!tp_inst.support.timezone && tp_inst._defaults.timezone === null){
+ tp_inst.timezone = tp_date.getTimezoneOffset()*-1;
+ }
+ date = $.timepicker.timezoneAdjust(date, tp_inst.timezone);
+ tp_date = $.timepicker.timezoneAdjust(tp_date, tp_inst.timezone);
+ }
+
+ this._updateDatepicker(inst);
+ this._base_setDateDatepicker.apply(this, arguments);
+ this._setTimeDatepicker(target, tp_date, true);
+ };
+
+ /*
+ * override getDate() to allow getting time too within Date object
+ */
+ $.datepicker._base_getDateDatepicker = $.datepicker._getDateDatepicker;
+ $.datepicker._getDateDatepicker = function(target, noDefault) {
+ var inst = this._getInst(target);
+ if (!inst) {
+ return;
+ }
+
+ var tp_inst = this._get(inst, 'timepicker');
+
+ if (tp_inst) {
+ // if it hasn't yet been defined, grab from field
+ if(inst.lastVal === undefined){
+ this._setDateFromField(inst, noDefault);
+ }
+
+ var date = this._getDate(inst);
+ if (date && tp_inst._parseTime($(target).val(), tp_inst.timeOnly)) {
+ date.setHours(tp_inst.hour, tp_inst.minute, tp_inst.second, tp_inst.millisec);
+ date.setMicroseconds(tp_inst.microsec);
+
+ // This is important if you are using the timezone option, javascript's Date
+ // object will only return the timezone offset for the current locale, so we
+ // adjust it accordingly. If not using timezone option this won't matter..
+ if(tp_inst.timezone != null){
+ // look out for DST if tz wasn't specified
+ if(!tp_inst.support.timezone && tp_inst._defaults.timezone === null){
+ tp_inst.timezone = date.getTimezoneOffset()*-1;
+ }
+ date = $.timepicker.timezoneAdjust(date, tp_inst.timezone);
+ }
+ }
+ return date;
+ }
+ return this._base_getDateDatepicker(target, noDefault);
+ };
+
+ /*
+ * override parseDate() because UI 1.8.14 throws an error about "Extra characters"
+ * An option in datapicker to ignore extra format characters would be nicer.
+ */
+ $.datepicker._base_parseDate = $.datepicker.parseDate;
+ $.datepicker.parseDate = function(format, value, settings) {
+ var date;
+ try {
+ date = this._base_parseDate(format, value, settings);
+ } catch (err) {
+ // Hack! The error message ends with a colon, a space, and
+ // the "extra" characters. We rely on that instead of
+ // attempting to perfectly reproduce the parsing algorithm.
+ if (err.indexOf(":") >= 0) {
+ date = this._base_parseDate(format, value.substring(0,value.length-(err.length-err.indexOf(':')-2)), settings);
+ $.timepicker.log("Error parsing the date string: " + err + "\ndate string = " + value + "\ndate format = " + format);
+ } else {
+ throw err;
+ }
+ }
+ return date;
+ };
+
+ /*
+ * override formatDate to set date with time to the input
+ */
+ $.datepicker._base_formatDate = $.datepicker._formatDate;
+ $.datepicker._formatDate = function(inst, day, month, year) {
+ var tp_inst = this._get(inst, 'timepicker');
+ if (tp_inst) {
+ tp_inst._updateDateTime(inst);
+ return tp_inst.$input.val();
+ }
+ return this._base_formatDate(inst);
+ };
+
+ /*
+ * override options setter to add time to maxDate(Time) and minDate(Time). MaxDate
+ */
+ $.datepicker._base_optionDatepicker = $.datepicker._optionDatepicker;
+ $.datepicker._optionDatepicker = function(target, name, value) {
+ var inst = this._getInst(target),
+ name_clone;
+ if (!inst) {
+ return null;
+ }
+
+ var tp_inst = this._get(inst, 'timepicker');
+ if (tp_inst) {
+ var min = null,
+ max = null,
+ onselect = null,
+ overrides = tp_inst._defaults.evnts,
+ fns = {},
+ prop;
+ if (typeof name == 'string') { // if min/max was set with the string
+ if (name === 'minDate' || name === 'minDateTime') {
+ min = value;
+ } else if (name === 'maxDate' || name === 'maxDateTime') {
+ max = value;
+ } else if (name === 'onSelect') {
+ onselect = value;
+ } else if (overrides.hasOwnProperty(name)) {
+ if (typeof (value) === 'undefined') {
+ return overrides[name];
+ }
+ fns[name] = value;
+ name_clone = {}; //empty results in exiting function after overrides updated
+ }
+ } else if (typeof name == 'object') { //if min/max was set with the JSON
+ if (name.minDate) {
+ min = name.minDate;
+ } else if (name.minDateTime) {
+ min = name.minDateTime;
+ } else if (name.maxDate) {
+ max = name.maxDate;
+ } else if (name.maxDateTime) {
+ max = name.maxDateTime;
+ }
+ for (prop in overrides) {
+ if (overrides.hasOwnProperty(prop) && name[prop]) {
+ fns[prop] = name[prop];
+ }
+ }
+ }
+ for (prop in fns) {
+ if (fns.hasOwnProperty(prop)) {
+ overrides[prop] = fns[prop];
+ if (!name_clone) { name_clone = $.extend({}, name);}
+ delete name_clone[prop];
+ }
+ }
+ if (name_clone && isEmptyObject(name_clone)) { return; }
+ if (min) { //if min was set
+ if (min === 0) {
+ min = new Date();
+ } else {
+ min = new Date(min);
+ }
+ tp_inst._defaults.minDate = min;
+ tp_inst._defaults.minDateTime = min;
+ } else if (max) { //if max was set
+ if (max === 0) {
+ max = new Date();
+ } else {
+ max = new Date(max);
+ }
+ tp_inst._defaults.maxDate = max;
+ tp_inst._defaults.maxDateTime = max;
+ } else if (onselect) {
+ tp_inst._defaults.onSelect = onselect;
+ }
+ }
+ if (value === undefined) {
+ return this._base_optionDatepicker.call($.datepicker, target, name);
+ }
+ return this._base_optionDatepicker.call($.datepicker, target, name_clone || name, value);
+ };
+
+ /*
+ * jQuery isEmptyObject does not check hasOwnProperty - if someone has added to the object prototype,
+ * it will return false for all objects
+ */
+ var isEmptyObject = function(obj) {
+ var prop;
+ for (prop in obj) {
+ if (obj.hasOwnProperty(obj)) {
+ return false;
+ }
+ }
+ return true;
+ };
+
+ /*
+ * jQuery extend now ignores nulls!
+ */
+ var extendRemove = function(target, props) {
+ $.extend(target, props);
+ for (var name in props) {
+ if (props[name] === null || props[name] === undefined) {
+ target[name] = props[name];
+ }
+ }
+ return target;
+ };
+
+ /*
+ * Determine by the time format which units are supported
+ * Returns an object of booleans for each unit
+ */
+ var detectSupport = function(timeFormat){
+ var tf = timeFormat.replace(/\'.*?\'/g,'').toLowerCase(), // removes literals
+ isIn = function(f, t){ // does the format contain the token?
+ return f.indexOf(t) !== -1? true:false;
+ };
+ return {
+ hour: isIn(tf,'h'),
+ minute: isIn(tf,'m'),
+ second: isIn(tf,'s'),
+ millisec: isIn(tf,'l'),
+ microsec: isIn(tf,'c'),
+ timezone: isIn(tf,'z'),
+ ampm: isIn(tf,'t') && isIn(timeFormat,'h'),
+ iso8601: isIn(timeFormat, 'Z')
+ };
+ };
+
+ /*
+ * Converts 24 hour format into 12 hour
+ * Returns 12 hour without leading 0
+ */
+ var convert24to12 = function(hour) {
+ if (hour > 12) {
+ hour = hour - 12;
+ }
+
+ if (hour === 0) {
+ hour = 12;
+ }
+
+ return String(hour);
+ };
+
+ /*
+ * Splits datetime string into date ans time substrings.
+ * Throws exception when date can't be parsed
+ * Returns [dateString, timeString]
+ */
+ var splitDateTime = function(dateFormat, dateTimeString, dateSettings, timeSettings) {
+ try {
+ // The idea is to get the number separator occurances in datetime and the time format requested (since time has
+ // fewer unknowns, mostly numbers and am/pm). We will use the time pattern to split.
+ var separator = timeSettings && timeSettings.separator ? timeSettings.separator : $.timepicker._defaults.separator,
+ format = timeSettings && timeSettings.timeFormat ? timeSettings.timeFormat : $.timepicker._defaults.timeFormat,
+ timeParts = format.split(separator), // how many occurances of separator may be in our format?
+ timePartsLen = timeParts.length,
+ allParts = dateTimeString.split(separator),
+ allPartsLen = allParts.length;
+
+ if (allPartsLen > 1) {
+ return [
+ allParts.splice(0,allPartsLen-timePartsLen).join(separator),
+ allParts.splice(0,timePartsLen).join(separator)
+ ];
+ }
+
+ } catch (err) {
+ $.timepicker.log('Could not split the date from the time. Please check the following datetimepicker options' +
+ "\nthrown error: " + err +
+ "\ndateTimeString" + dateTimeString +
+ "\ndateFormat = " + dateFormat +
+ "\nseparator = " + timeSettings.separator +
+ "\ntimeFormat = " + timeSettings.timeFormat);
+
+ if (err.indexOf(":") >= 0) {
+ // Hack! The error message ends with a colon, a space, and
+ // the "extra" characters. We rely on that instead of
+ // attempting to perfectly reproduce the parsing algorithm.
+ var dateStringLength = dateTimeString.length - (err.length - err.indexOf(':') - 2),
+ timeString = dateTimeString.substring(dateStringLength);
+
+ return [$.trim(dateTimeString.substring(0, dateStringLength)), $.trim(dateTimeString.substring(dateStringLength))];
+
+ } else {
+ throw err;
+ }
+ }
+ return [dateTimeString, ''];
+ };
+
+ /*
+ * Internal function to parse datetime interval
+ * Returns: {date: Date, timeObj: Object}, where
+ * date - parsed date without time (type Date)
+ * timeObj = {hour: , minute: , second: , millisec: , microsec: } - parsed time. Optional
+ */
+ var parseDateTimeInternal = function(dateFormat, timeFormat, dateTimeString, dateSettings, timeSettings) {
+ var date;
+ var splitRes = splitDateTime(dateFormat, dateTimeString, dateSettings, timeSettings);
+ date = $.datepicker._base_parseDate(dateFormat, splitRes[0], dateSettings);
+ if (splitRes[1] !== '') {
+ var timeString = splitRes[1],
+ parsedTime = $.datepicker.parseTime(timeFormat, timeString, timeSettings);
+
+ if (parsedTime === null) {
+ throw 'Wrong time format';
+ }
+ return {
+ date: date,
+ timeObj: parsedTime
+ };
+ } else {
+ return {
+ date: date
+ };
+ }
+ };
+
+ /*
+ * Internal function to set timezone_select to the local timezone
+ */
+ var selectLocalTimezone = function(tp_inst, date) {
+ if (tp_inst && tp_inst.timezone_select) {
+ var now = typeof date !== 'undefined' ? date : new Date();
+ tp_inst.timezone_select.val(now.getTimezoneOffset()*-1);
+ }
+ };
+
+ /*
+ * Create a Singleton Insance
+ */
+ $.timepicker = new Timepicker();
+
+ /**
+ * Get the timezone offset as string from a date object (eg '+0530' for UTC+5.5)
+ * @param number if not a number this value is returned
+ * @param boolean if true formats in accordance to iso8601 "+12:45"
+ * @return string
+ */
+ $.timepicker.timezoneOffsetString = function(tzMinutes, iso8601) {
+ if(isNaN(tzMinutes) || tzMinutes > 840){
+ return tzMinutes;
+ }
+
+ var off = tzMinutes,
+ minutes = off % 60,
+ hours = (off - minutes) / 60,
+ iso = iso8601? ':':'',
+ tz = (off >= 0 ? '+' : '-') + ('0' + (hours * 101).toString()).slice(-2) + iso + ('0' + (minutes * 101).toString()).slice(-2);
+
+ if(tz == '+00:00'){
+ return 'Z';
+ }
+ return tz;
+ };
+
+ /**
+ * Get the number in minutes that represents a timezone string
+ * @param string formated like "+0500", "-1245"
+ * @return number
+ */
+ $.timepicker.timezoneOffsetNumber = function(tzString) {
+ tzString = tzString.toString().replace(':',''); // excuse any iso8601, end up with "+1245"
+
+ if(tzString.toUpperCase() === 'Z'){ // if iso8601 with Z, its 0 minute offset
+ return 0;
+ }
+
+ if(!/^(\-|\+)\d{4}$/.test(tzString)){ // possibly a user defined tz, so just give it back
+ return tzString;
+ }
+
+ return ((tzString.substr(0,1) =='-'? -1 : 1) * // plus or minus
+ ((parseInt(tzString.substr(1,2),10)*60) + // hours (converted to minutes)
+ parseInt(tzString.substr(3,2),10))); // minutes
+ };
+
+ /**
+ * No way to set timezone in js Date, so we must adjust the minutes to compensate. (think setDate, getDate)
+ * @param date
+ * @param string formated like "+0500", "-1245"
+ * @return date
+ */
+ $.timepicker.timezoneAdjust = function(date, toTimezone) {
+ var toTz = $.timepicker.timezoneOffsetNumber(toTimezone);
+ if(!isNaN(toTz)){
+ date.setMinutes(date.getMinutes()*1 + (date.getTimezoneOffset()*-1 - toTz*1) );
+ }
+ return date;
+ };
+
+ /**
+ * Calls `timepicker()` on the `startTime` and `endTime` elements, and configures them to
+ * enforce date range limits.
+ * n.b. The input value must be correctly formatted (reformatting is not supported)
+ * @param Element startTime
+ * @param Element endTime
+ * @param obj options Options for the timepicker() call
+ * @return jQuery
+ */
+ $.timepicker.timeRange = function(startTime, endTime, options) {
+ return $.timepicker.handleRange('timepicker', startTime, endTime, options);
+ };
+
+ /**
+ * Calls `datetimepicker` on the `startTime` and `endTime` elements, and configures them to
+ * enforce date range limits.
+ * @param Element startTime
+ * @param Element endTime
+ * @param obj options Options for the `timepicker()` call. Also supports `reformat`,
+ * a boolean value that can be used to reformat the input values to the `dateFormat`.
+ * @param string method Can be used to specify the type of picker to be added
+ * @return jQuery
+ */
+ $.timepicker.datetimeRange = function(startTime, endTime, options) {
+ $.timepicker.handleRange('datetimepicker', startTime, endTime, options);
+ };
+
+ /**
+ * Calls `method` on the `startTime` and `endTime` elements, and configures them to
+ * enforce date range limits.
+ * @param Element startTime
+ * @param Element endTime
+ * @param obj options Options for the `timepicker()` call. Also supports `reformat`,
+ * a boolean value that can be used to reformat the input values to the `dateFormat`.
+ * @return jQuery
+ */
+ $.timepicker.dateRange = function(startTime, endTime, options) {
+ $.timepicker.handleRange('datepicker', startTime, endTime, options);
+ };
+
+ /**
+ * Calls `method` on the `startTime` and `endTime` elements, and configures them to
+ * enforce date range limits.
+ * @param string method Can be used to specify the type of picker to be added
+ * @param Element startTime
+ * @param Element endTime
+ * @param obj options Options for the `timepicker()` call. Also supports `reformat`,
+ * a boolean value that can be used to reformat the input values to the `dateFormat`.
+ * @return jQuery
+ */
+ $.timepicker.handleRange = function(method, startTime, endTime, options) {
+ options = $.extend({}, {
+ minInterval: 0, // min allowed interval in milliseconds
+ maxInterval: 0, // max allowed interval in milliseconds
+ start: {}, // options for start picker
+ end: {} // options for end picker
+ }, options);
+
+ $.fn[method].call(startTime, $.extend({
+ onClose: function(dateText, inst) {
+ checkDates($(this), endTime);
+ },
+ onSelect: function(selectedDateTime) {
+ selected($(this), endTime, 'minDate');
+ }
+ }, options, options.start));
+ $.fn[method].call(endTime, $.extend({
+ onClose: function(dateText, inst) {
+ checkDates($(this), startTime);
+ },
+ onSelect: function(selectedDateTime) {
+ selected($(this), startTime, 'maxDate');
+ }
+ }, options, options.end));
+
+ checkDates(startTime, endTime);
+ selected(startTime, endTime, 'minDate');
+ selected(endTime, startTime, 'maxDate');
+
+ function checkDates(changed, other) {
+ var startdt = startTime[method]('getDate'),
+ enddt = endTime[method]('getDate'),
+ changeddt = changed[method]('getDate');
+
+ if(startdt !== null){
+ var minDate = new Date(startdt.getTime()),
+ maxDate = new Date(startdt.getTime());
+
+ minDate.setMilliseconds(minDate.getMilliseconds() + options.minInterval);
+ maxDate.setMilliseconds(maxDate.getMilliseconds() + options.maxInterval);
+
+ if(options.minInterval > 0 && minDate > enddt){ // minInterval check
+ endTime[method]('setDate',minDate);
+ }
+ else if(options.maxInterval > 0 && maxDate < enddt){ // max interval check
+ endTime[method]('setDate',maxDate);
+ }
+ else if (startdt > enddt) {
+ other[method]('setDate',changeddt);
+ }
+ }
+ }
+
+ function selected(changed, other, option) {
+ if (!changed.val()) {
+ return;
+ }
+ var date = changed[method].call(changed, 'getDate');
+ if(date !== null && options.minInterval > 0){
+ if(option == 'minDate'){
+ date.setMilliseconds(date.getMilliseconds() + options.minInterval);
+ }
+ if(option == 'maxDate'){
+ date.setMilliseconds(date.getMilliseconds() - options.minInterval);
+ }
+ }
+ if (date.getTime) {
+ other[method].call(other, 'option', option, date);
+ }
+ }
+ return $([startTime.get(0), endTime.get(0)]);
+ };
+
+ /**
+ * Log error or data to the console during error or debugging
+ * @param Object err pass any type object to log to the console during error or debugging
+ * @return void
+ */
+ $.timepicker.log = function(err){
+ if(window.console){
+ console.log(err);
+ }
+ };
+
+ /*
+ * Microsecond support
+ */
+ if(!Date.prototype.getMicroseconds){
+ Date.prototype.microseconds = 0;
+ Date.prototype.getMicroseconds = function(){ return this.microseconds; };
+ Date.prototype.setMicroseconds = function(m){
+ this.setMilliseconds(this.getMilliseconds() + Math.floor(m/1000));
+ this.microseconds = m%1000;
+ return this;
+ };
+ }
+
+ /*
+ * Keep up with the version
+ */
+ $.timepicker.version = "1.3.1";
+
+})(jQuery); \ No newline at end of file
diff --git a/js/jquery/jquery.ba-hashchange-1.3.js b/js/jquery/jquery.ba-hashchange-1.3.js
new file mode 100644
index 0000000000..47105f4abc
--- /dev/null
+++ b/js/jquery/jquery.ba-hashchange-1.3.js
@@ -0,0 +1,390 @@
+/*!
+ * jQuery hashchange event - v1.3 - 7/21/2010
+ * http://benalman.com/projects/jquery-hashchange-plugin/
+ *
+ * Copyright (c) 2010 "Cowboy" Ben Alman
+ * Dual licensed under the MIT and GPL licenses.
+ * http://benalman.com/about/license/
+ */
+
+// Script: jQuery hashchange event
+//
+// *Version: 1.3, Last updated: 7/21/2010*
+//
+// Project Home - http://benalman.com/projects/jquery-hashchange-plugin/
+// GitHub - http://github.com/cowboy/jquery-hashchange/
+// Source - http://github.com/cowboy/jquery-hashchange/raw/master/jquery.ba-hashchange.js
+// (Minified) - http://github.com/cowboy/jquery-hashchange/raw/master/jquery.ba-hashchange.min.js (0.8kb gzipped)
+//
+// About: License
+//
+// Copyright (c) 2010 "Cowboy" Ben Alman,
+// Dual licensed under the MIT and GPL licenses.
+// http://benalman.com/about/license/
+//
+// About: Examples
+//
+// These working examples, complete with fully commented code, illustrate a few
+// ways in which this plugin can be used.
+//
+// hashchange event - http://benalman.com/code/projects/jquery-hashchange/examples/hashchange/
+// document.domain - http://benalman.com/code/projects/jquery-hashchange/examples/document_domain/
+//
+// About: Support and Testing
+//
+// Information about what version or versions of jQuery this plugin has been
+// tested with, what browsers it has been tested in, and where the unit tests
+// reside (so you can test it yourself).
+//
+// jQuery Versions - 1.2.6, 1.3.2, 1.4.1, 1.4.2
+// Browsers Tested - Internet Explorer 6-8, Firefox 2-4, Chrome 5-6, Safari 3.2-5,
+// Opera 9.6-10.60, iPhone 3.1, Android 1.6-2.2, BlackBerry 4.6-5.
+// Unit Tests - http://benalman.com/code/projects/jquery-hashchange/unit/
+//
+// About: Known issues
+//
+// While this jQuery hashchange event implementation is quite stable and
+// robust, there are a few unfortunate browser bugs surrounding expected
+// hashchange event-based behaviors, independent of any JavaScript
+// window.onhashchange abstraction. See the following examples for more
+// information:
+//
+// Chrome: Back Button - http://benalman.com/code/projects/jquery-hashchange/examples/bug-chrome-back-button/
+// Firefox: Remote XMLHttpRequest - http://benalman.com/code/projects/jquery-hashchange/examples/bug-firefox-remote-xhr/
+// WebKit: Back Button in an Iframe - http://benalman.com/code/projects/jquery-hashchange/examples/bug-webkit-hash-iframe/
+// Safari: Back Button from a different domain - http://benalman.com/code/projects/jquery-hashchange/examples/bug-safari-back-from-diff-domain/
+//
+// Also note that should a browser natively support the window.onhashchange
+// event, but not report that it does, the fallback polling loop will be used.
+//
+// About: Release History
+//
+// 1.3 - (7/21/2010) Reorganized IE6/7 Iframe code to make it more
+// "removable" for mobile-only development. Added IE6/7 document.title
+// support. Attempted to make Iframe as hidden as possible by using
+// techniques from http://www.paciellogroup.com/blog/?p=604. Added
+// support for the "shortcut" format $(window).hashchange( fn ) and
+// $(window).hashchange() like jQuery provides for built-in events.
+// Renamed jQuery.hashchangeDelay to <jQuery.fn.hashchange.delay> and
+// lowered its default value to 50. Added <jQuery.fn.hashchange.domain>
+// and <jQuery.fn.hashchange.src> properties plus document-domain.html
+// file to address access denied issues when setting document.domain in
+// IE6/7.
+// 1.2 - (2/11/2010) Fixed a bug where coming back to a page using this plugin
+// from a page on another domain would cause an error in Safari 4. Also,
+// IE6/7 Iframe is now inserted after the body (this actually works),
+// which prevents the page from scrolling when the event is first bound.
+// Event can also now be bound before DOM ready, but it won't be usable
+// before then in IE6/7.
+// 1.1 - (1/21/2010) Incorporated document.documentMode test to fix IE8 bug
+// where browser version is incorrectly reported as 8.0, despite
+// inclusion of the X-UA-Compatible IE=EmulateIE7 meta tag.
+// 1.0 - (1/9/2010) Initial Release. Broke out the jQuery BBQ event.special
+// window.onhashchange functionality into a separate plugin for users
+// who want just the basic event & back button support, without all the
+// extra awesomeness that BBQ provides. This plugin will be included as
+// part of jQuery BBQ, but also be available separately.
+
+(function($,window,undefined){
+ '$:nomunge'; // Used by YUI compressor.
+
+ // Reused string.
+ var str_hashchange = 'hashchange',
+
+ // Method / object references.
+ doc = document,
+ fake_onhashchange,
+ special = $.event.special,
+
+ // Does the browser support window.onhashchange? Note that IE8 running in
+ // IE7 compatibility mode reports true for 'onhashchange' in window, even
+ // though the event isn't supported, so also test document.documentMode.
+ doc_mode = doc.documentMode,
+ supports_onhashchange = 'on' + str_hashchange in window && ( doc_mode === undefined || doc_mode > 7 );
+
+ // Get location.hash (or what you'd expect location.hash to be) sans any
+ // leading #. Thanks for making this necessary, Firefox!
+ function get_fragment( url ) {
+ url = url || location.href;
+ return '#' + url.replace( /^[^#]*#?(.*)$/, '$1' );
+ };
+
+ // Method: jQuery.fn.hashchange
+ //
+ // Bind a handler to the window.onhashchange event or trigger all bound
+ // window.onhashchange event handlers. This behavior is consistent with
+ // jQuery's built-in event handlers.
+ //
+ // Usage:
+ //
+ // > jQuery(window).hashchange( [ handler ] );
+ //
+ // Arguments:
+ //
+ // handler - (Function) Optional handler to be bound to the hashchange
+ // event. This is a "shortcut" for the more verbose form:
+ // jQuery(window).bind( 'hashchange', handler ). If handler is omitted,
+ // all bound window.onhashchange event handlers will be triggered. This
+ // is a shortcut for the more verbose
+ // jQuery(window).trigger( 'hashchange' ). These forms are described in
+ // the <hashchange event> section.
+ //
+ // Returns:
+ //
+ // (jQuery) The initial jQuery collection of elements.
+
+ // Allow the "shortcut" format $(elem).hashchange( fn ) for binding and
+ // $(elem).hashchange() for triggering, like jQuery does for built-in events.
+ $.fn[ str_hashchange ] = function( fn ) {
+ return fn ? this.bind( str_hashchange, fn ) : this.trigger( str_hashchange );
+ };
+
+ // Property: jQuery.fn.hashchange.delay
+ //
+ // The numeric interval (in milliseconds) at which the <hashchange event>
+ // polling loop executes. Defaults to 50.
+
+ // Property: jQuery.fn.hashchange.domain
+ //
+ // If you're setting document.domain in your JavaScript, and you want hash
+ // history to work in IE6/7, not only must this property be set, but you must
+ // also set document.domain BEFORE jQuery is loaded into the page. This
+ // property is only applicable if you are supporting IE6/7 (or IE8 operating
+ // in "IE7 compatibility" mode).
+ //
+ // In addition, the <jQuery.fn.hashchange.src> property must be set to the
+ // path of the included "document-domain.html" file, which can be renamed or
+ // modified if necessary (note that the document.domain specified must be the
+ // same in both your main JavaScript as well as in this file).
+ //
+ // Usage:
+ //
+ // jQuery.fn.hashchange.domain = document.domain;
+
+ // Property: jQuery.fn.hashchange.src
+ //
+ // If, for some reason, you need to specify an Iframe src file (for example,
+ // when setting document.domain as in <jQuery.fn.hashchange.domain>), you can
+ // do so using this property. Note that when using this property, history
+ // won't be recorded in IE6/7 until the Iframe src file loads. This property
+ // is only applicable if you are supporting IE6/7 (or IE8 operating in "IE7
+ // compatibility" mode).
+ //
+ // Usage:
+ //
+ // jQuery.fn.hashchange.src = 'path/to/file.html';
+
+ $.fn[ str_hashchange ].delay = 50;
+ /*
+ $.fn[ str_hashchange ].domain = null;
+ $.fn[ str_hashchange ].src = null;
+ */
+
+ // Event: hashchange event
+ //
+ // Fired when location.hash changes. In browsers that support it, the native
+ // HTML5 window.onhashchange event is used, otherwise a polling loop is
+ // initialized, running every <jQuery.fn.hashchange.delay> milliseconds to
+ // see if the hash has changed. In IE6/7 (and IE8 operating in "IE7
+ // compatibility" mode), a hidden Iframe is created to allow the back button
+ // and hash-based history to work.
+ //
+ // Usage as described in <jQuery.fn.hashchange>:
+ //
+ // > // Bind an event handler.
+ // > jQuery(window).hashchange( function(e) {
+ // > var hash = location.hash;
+ // > ...
+ // > });
+ // >
+ // > // Manually trigger the event handler.
+ // > jQuery(window).hashchange();
+ //
+ // A more verbose usage that allows for event namespacing:
+ //
+ // > // Bind an event handler.
+ // > jQuery(window).bind( 'hashchange', function(e) {
+ // > var hash = location.hash;
+ // > ...
+ // > });
+ // >
+ // > // Manually trigger the event handler.
+ // > jQuery(window).trigger( 'hashchange' );
+ //
+ // Additional Notes:
+ //
+ // * The polling loop and Iframe are not created until at least one handler
+ // is actually bound to the 'hashchange' event.
+ // * If you need the bound handler(s) to execute immediately, in cases where
+ // a location.hash exists on page load, via bookmark or page refresh for
+ // example, use jQuery(window).hashchange() or the more verbose
+ // jQuery(window).trigger( 'hashchange' ).
+ // * The event can be bound before DOM ready, but since it won't be usable
+ // before then in IE6/7 (due to the necessary Iframe), recommended usage is
+ // to bind it inside a DOM ready handler.
+
+ // Override existing $.event.special.hashchange methods (allowing this plugin
+ // to be defined after jQuery BBQ in BBQ's source code).
+ special[ str_hashchange ] = $.extend( special[ str_hashchange ], {
+
+ // Called only when the first 'hashchange' event is bound to window.
+ setup: function() {
+ // If window.onhashchange is supported natively, there's nothing to do..
+ if ( supports_onhashchange ) { return false; }
+
+ // Otherwise, we need to create our own. And we don't want to call this
+ // until the user binds to the event, just in case they never do, since it
+ // will create a polling loop and possibly even a hidden Iframe.
+ $( fake_onhashchange.start );
+ },
+
+ // Called only when the last 'hashchange' event is unbound from window.
+ teardown: function() {
+ // If window.onhashchange is supported natively, there's nothing to do..
+ if ( supports_onhashchange ) { return false; }
+
+ // Otherwise, we need to stop ours (if possible).
+ $( fake_onhashchange.stop );
+ }
+
+ });
+
+ // fake_onhashchange does all the work of triggering the window.onhashchange
+ // event for browsers that don't natively support it, including creating a
+ // polling loop to watch for hash changes and in IE 6/7 creating a hidden
+ // Iframe to enable back and forward.
+ fake_onhashchange = (function(){
+ var self = {},
+ timeout_id,
+
+ // Remember the initial hash so it doesn't get triggered immediately.
+ last_hash = get_fragment(),
+
+ fn_retval = function(val){ return val; },
+ history_set = fn_retval,
+ history_get = fn_retval;
+
+ // Start the polling loop.
+ self.start = function() {
+ timeout_id || poll();
+ };
+
+ // Stop the polling loop.
+ self.stop = function() {
+ timeout_id && clearTimeout( timeout_id );
+ timeout_id = undefined;
+ };
+
+ // This polling loop checks every $.fn.hashchange.delay milliseconds to see
+ // if location.hash has changed, and triggers the 'hashchange' event on
+ // window when necessary.
+ function poll() {
+ var hash = get_fragment(),
+ history_hash = history_get( last_hash );
+
+ if ( hash !== last_hash ) {
+ history_set( last_hash = hash, history_hash );
+
+ $(window).trigger( str_hashchange );
+
+ } else if ( history_hash !== last_hash ) {
+ location.href = location.href.replace( /#.*/, '' ) + history_hash;
+ }
+
+ timeout_id = setTimeout( poll, $.fn[ str_hashchange ].delay );
+ };
+
+ // vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
+ // vvvvvvvvvvvvvvvvvvv REMOVE IF NOT SUPPORTING IE6/7/8 vvvvvvvvvvvvvvvvvvv
+ // vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
+ $.browser.msie && !supports_onhashchange && (function(){
+ // Not only do IE6/7 need the "magical" Iframe treatment, but so does IE8
+ // when running in "IE7 compatibility" mode.
+
+ var iframe,
+ iframe_src;
+
+ // When the event is bound and polling starts in IE 6/7, create a hidden
+ // Iframe for history handling.
+ self.start = function(){
+ if ( !iframe ) {
+ iframe_src = $.fn[ str_hashchange ].src;
+ iframe_src = iframe_src && iframe_src + get_fragment();
+
+ // Create hidden Iframe. Attempt to make Iframe as hidden as possible
+ // by using techniques from http://www.paciellogroup.com/blog/?p=604.
+ iframe = $('<iframe tabindex="-1" title="empty"/>').hide()
+
+ // When Iframe has completely loaded, initialize the history and
+ // start polling.
+ .one( 'load', function(){
+ iframe_src || history_set( get_fragment() );
+ poll();
+ })
+
+ // Load Iframe src if specified, otherwise nothing.
+ .attr( 'src', iframe_src || 'javascript:0' )
+
+ // Append Iframe after the end of the body to prevent unnecessary
+ // initial page scrolling (yes, this works).
+ .insertAfter( 'body' )[0].contentWindow;
+
+ // Whenever `document.title` changes, update the Iframe's title to
+ // prettify the back/next history menu entries. Since IE sometimes
+ // errors with "Unspecified error" the very first time this is set
+ // (yes, very useful) wrap this with a try/catch block.
+ doc.onpropertychange = function(){
+ try {
+ if ( event.propertyName === 'title' ) {
+ iframe.document.title = doc.title;
+ }
+ } catch(e) {}
+ };
+
+ }
+ };
+
+ // Override the "stop" method since an IE6/7 Iframe was created. Even
+ // if there are no longer any bound event handlers, the polling loop
+ // is still necessary for back/next to work at all!
+ self.stop = fn_retval;
+
+ // Get history by looking at the hidden Iframe's location.hash.
+ history_get = function() {
+ return get_fragment( iframe.location.href );
+ };
+
+ // Set a new history item by opening and then closing the Iframe
+ // document, *then* setting its location.hash. If document.domain has
+ // been set, update that as well.
+ history_set = function( hash, history_hash ) {
+ var iframe_doc = iframe.document,
+ domain = $.fn[ str_hashchange ].domain;
+
+ if ( hash !== history_hash ) {
+ // Update Iframe with any initial `document.title` that might be set.
+ iframe_doc.title = doc.title;
+
+ // Opening the Iframe's document after it has been closed is what
+ // actually adds a history entry.
+ iframe_doc.open();
+
+ // Set document.domain for the Iframe document as well, if necessary.
+ domain && iframe_doc.write( '<script>document.domain="' + domain + '"</script>' );
+
+ iframe_doc.close();
+
+ // Update the Iframe's hash, for great justice.
+ iframe.location.hash = hash;
+ }
+ };
+
+ })();
+ // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ // ^^^^^^^^^^^^^^^^^^^ REMOVE IF NOT SUPPORTING IE6/7/8 ^^^^^^^^^^^^^^^^^^^
+ // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+ return self;
+ })();
+
+})(jQuery,this);
diff --git a/js/jquery/jquery.cookie.js b/js/jquery/jquery.cookie.js
new file mode 100644
index 0000000000..61d3bcef2d
--- /dev/null
+++ b/js/jquery/jquery.cookie.js
@@ -0,0 +1,91 @@
+/*jslint browser: true */ /*global jQuery: true */
+
+/**
+ * jQuery Cookie plugin
+ *
+ * Copyright (c) 2010 Klaus Hartl (stilbuero.de)
+ * Dual licensed under the MIT and GPL licenses:
+ * http://www.opensource.org/licenses/mit-license.php
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ */
+
+// TODO JsDoc
+
+/**
+ * Create a cookie with the given key and value and other optional parameters.
+ *
+ * @example $.cookie('the_cookie', 'the_value');
+ * @desc Set the value of a cookie.
+ * @example $.cookie('the_cookie', 'the_value', { expires: 7, path: '/', domain: 'jquery.com', secure: true });
+ * @desc Create a cookie with all available options.
+ * @example $.cookie('the_cookie', 'the_value');
+ * @desc Create a session cookie.
+ * @example $.cookie('the_cookie', null);
+ * @desc Delete a cookie by passing null as value. Keep in mind that you have to use the same path and domain
+ * used when the cookie was set.
+ *
+ * @param String key The key of the cookie.
+ * @param String value The value of the cookie.
+ * @param Object options An object literal containing key/value pairs to provide optional cookie attributes.
+ * @option Number|Date expires Either an integer specifying the expiration date from now on in days or a Date object.
+ * If a negative value is specified (e.g. a date in the past), the cookie will be deleted.
+ * If set to null or omitted, the cookie will be a session cookie and will not be retained
+ * when the the browser exits.
+ * @option String path The value of the path atribute of the cookie (default: path of page that created the cookie).
+ * @option String domain The value of the domain attribute of the cookie (default: domain of page that created the cookie).
+ * @option Boolean secure If true, the secure attribute of the cookie will be set and the cookie transmission will
+ * require a secure protocol (like HTTPS).
+ * @type undefined
+ *
+ * @name $.cookie
+ * @cat Plugins/Cookie
+ * @author Klaus Hartl/klaus.hartl@stilbuero.de
+ */
+
+/**
+ * Get the value of a cookie with the given key.
+ *
+ * @example $.cookie('the_cookie');
+ * @desc Get the value of a cookie.
+ *
+ * @param String key The key of the cookie.
+ * @return The value of the cookie.
+ * @type String
+ *
+ * @name $.cookie
+ * @cat Plugins/Cookie
+ * @author Klaus Hartl/klaus.hartl@stilbuero.de
+ */
+jQuery.cookie = function (key, value, options) {
+
+ // key and at least value given, set cookie...
+ if (arguments.length > 1 && String(value) !== "[object Object]") {
+ options = jQuery.extend({}, options);
+
+ if (value === null || value === undefined) {
+ options.expires = -1;
+ }
+
+ if (typeof options.expires === 'number') {
+ var days = options.expires, t = options.expires = new Date();
+ t.setDate(t.getDate() + days);
+ }
+
+ value = String(value);
+
+ return (document.cookie = [
+ encodeURIComponent(key), '=',
+ options.raw ? value : encodeURIComponent(value),
+ options.expires ? '; expires=' + options.expires.toUTCString() : '', // use expires attribute, max-age is not supported by IE
+ options.path ? '; path=' + options.path : '',
+ options.domain ? '; domain=' + options.domain : '',
+ options.secure ? '; secure' : ''
+ ].join(''));
+ }
+
+ // key and possibly options given, get cookie...
+ options = value || {};
+ var result, decode = options.raw ? function (s) { return s; } : decodeURIComponent;
+ return (result = new RegExp('(?:^|; )' + encodeURIComponent(key) + '=([^;]*)').exec(document.cookie)) ? decode(result[1]) : null;
+};
diff --git a/js/jquery/jquery.debounce-1.0.5.js b/js/jquery/jquery.debounce-1.0.5.js
new file mode 100644
index 0000000000..020128cb03
--- /dev/null
+++ b/js/jquery/jquery.debounce-1.0.5.js
@@ -0,0 +1,71 @@
+/**
+ * Debounce and throttle function's decorator plugin 1.0.5
+ *
+ * Copyright (c) 2009 Filatov Dmitry (alpha@zforms.ru)
+ * Dual licensed under the MIT and GPL licenses:
+ * http://www.opensource.org/licenses/mit-license.php
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ */
+
+(function($) {
+
+$.extend({
+
+ debounce : function(fn, timeout, invokeAsap, ctx) {
+
+ if(arguments.length == 3 && typeof invokeAsap != 'boolean') {
+ ctx = invokeAsap;
+ invokeAsap = false;
+ }
+
+ var timer;
+
+ return function() {
+
+ var args = arguments;
+ ctx = ctx || this;
+
+ invokeAsap && !timer && fn.apply(ctx, args);
+
+ clearTimeout(timer);
+
+ timer = setTimeout(function() {
+ !invokeAsap && fn.apply(ctx, args);
+ timer = null;
+ }, timeout);
+
+ };
+
+ },
+
+ throttle : function(fn, timeout, ctx) {
+
+ var timer, args, needInvoke;
+
+ return function() {
+
+ args = arguments;
+ needInvoke = true;
+ ctx = ctx || this;
+
+ if(!timer) {
+ (function() {
+ if(needInvoke) {
+ fn.apply(ctx, args);
+ needInvoke = false;
+ timer = setTimeout(arguments.callee, timeout);
+ }
+ else {
+ timer = null;
+ }
+ })();
+ }
+
+ };
+
+ }
+
+});
+
+})(jQuery); \ No newline at end of file
diff --git a/js/jquery/jquery.event.drag-2.2.js b/js/jquery/jquery.event.drag-2.2.js
new file mode 100644
index 0000000000..1cda0e2166
--- /dev/null
+++ b/js/jquery/jquery.event.drag-2.2.js
@@ -0,0 +1,402 @@
+/*!
+ * jquery.event.drag - v 2.2
+ * Copyright (c) 2010 Three Dub Media - http://threedubmedia.com
+ * Open Source MIT License - http://threedubmedia.com/code/license
+ */
+// Created: 2008-06-04
+// Updated: 2012-05-21
+// REQUIRES: jquery 1.7.x
+
+;(function( $ ){
+
+// add the jquery instance method
+$.fn.drag = function( str, arg, opts ){
+ // figure out the event type
+ var type = typeof str == "string" ? str : "",
+ // figure out the event handler...
+ fn = $.isFunction( str ) ? str : $.isFunction( arg ) ? arg : null;
+ // fix the event type
+ if ( type.indexOf("drag") !== 0 )
+ type = "drag"+ type;
+ // were options passed
+ opts = ( str == fn ? arg : opts ) || {};
+ // trigger or bind event handler
+ return fn ? this.bind( type, opts, fn ) : this.trigger( type );
+};
+
+// local refs (increase compression)
+var $event = $.event,
+$special = $event.special,
+// configure the drag special event
+drag = $special.drag = {
+
+ // these are the default settings
+ defaults: {
+ which: 1, // mouse button pressed to start drag sequence
+ distance: 0, // distance dragged before dragstart
+ not: ':input', // selector to suppress dragging on target elements
+ handle: null, // selector to match handle target elements
+ relative: false, // true to use "position", false to use "offset"
+ drop: true, // false to suppress drop events, true or selector to allow
+ click: false // false to suppress click events after dragend (no proxy)
+ },
+
+ // the key name for stored drag data
+ datakey: "dragdata",
+
+ // prevent bubbling for better performance
+ noBubble: true,
+
+ // count bound related events
+ add: function( obj ){
+ // read the interaction data
+ var data = $.data( this, drag.datakey ),
+ // read any passed options
+ opts = obj.data || {};
+ // count another realted event
+ data.related += 1;
+ // extend data options bound with this event
+ // don't iterate "opts" in case it is a node
+ $.each( drag.defaults, function( key, def ){
+ if ( opts[ key ] !== undefined )
+ data[ key ] = opts[ key ];
+ });
+ },
+
+ // forget unbound related events
+ remove: function(){
+ $.data( this, drag.datakey ).related -= 1;
+ },
+
+ // configure interaction, capture settings
+ setup: function(){
+ // check for related events
+ if ( $.data( this, drag.datakey ) )
+ return;
+ // initialize the drag data with copied defaults
+ var data = $.extend({ related:0 }, drag.defaults );
+ // store the interaction data
+ $.data( this, drag.datakey, data );
+ // bind the mousedown event, which starts drag interactions
+ $event.add( this, "touchstart mousedown", drag.init, data );
+ // prevent image dragging in IE...
+ if ( this.attachEvent )
+ this.attachEvent("ondragstart", drag.dontstart );
+ },
+
+ // destroy configured interaction
+ teardown: function(){
+ var data = $.data( this, drag.datakey ) || {};
+ // check for related events
+ if ( data.related )
+ return;
+ // remove the stored data
+ $.removeData( this, drag.datakey );
+ // remove the mousedown event
+ $event.remove( this, "touchstart mousedown", drag.init );
+ // enable text selection
+ drag.textselect( true );
+ // un-prevent image dragging in IE...
+ if ( this.detachEvent )
+ this.detachEvent("ondragstart", drag.dontstart );
+ },
+
+ // initialize the interaction
+ init: function( event ){
+ // sorry, only one touch at a time
+ if ( drag.touched )
+ return;
+ // the drag/drop interaction data
+ var dd = event.data, results;
+ // check the which directive
+ if ( event.which != 0 && dd.which > 0 && event.which != dd.which )
+ return;
+ // check for suppressed selector
+ if ( $( event.target ).is( dd.not ) )
+ return;
+ // check for handle selector
+ if ( dd.handle && !$( event.target ).closest( dd.handle, event.currentTarget ).length )
+ return;
+
+ drag.touched = event.type == 'touchstart' ? this : null;
+ dd.propagates = 1;
+ dd.mousedown = this;
+ dd.interactions = [ drag.interaction( this, dd ) ];
+ dd.target = event.target;
+ dd.pageX = event.pageX;
+ dd.pageY = event.pageY;
+ dd.dragging = null;
+ // handle draginit event...
+ results = drag.hijack( event, "draginit", dd );
+ // early cancel
+ if ( !dd.propagates )
+ return;
+ // flatten the result set
+ results = drag.flatten( results );
+ // insert new interaction elements
+ if ( results && results.length ){
+ dd.interactions = [];
+ $.each( results, function(){
+ dd.interactions.push( drag.interaction( this, dd ) );
+ });
+ }
+ // remember how many interactions are propagating
+ dd.propagates = dd.interactions.length;
+ // locate and init the drop targets
+ if ( dd.drop !== false && $special.drop )
+ $special.drop.handler( event, dd );
+ // disable text selection
+ drag.textselect( false );
+ // bind additional events...
+ if ( drag.touched )
+ $event.add( drag.touched, "touchmove touchend", drag.handler, dd );
+ else
+ $event.add( document, "mousemove mouseup", drag.handler, dd );
+ // helps prevent text selection or scrolling
+ if ( !drag.touched || dd.live )
+ return false;
+ },
+
+ // returns an interaction object
+ interaction: function( elem, dd ){
+ var offset = $( elem )[ dd.relative ? "position" : "offset" ]() || { top:0, left:0 };
+ return {
+ drag: elem,
+ callback: new drag.callback(),
+ droppable: [],
+ offset: offset
+ };
+ },
+
+ // handle drag-releatd DOM events
+ handler: function( event ){
+ // read the data before hijacking anything
+ var dd = event.data;
+ // handle various events
+ switch ( event.type ){
+ // mousemove, check distance, start dragging
+ case !dd.dragging && 'touchmove':
+ event.preventDefault();
+ case !dd.dragging && 'mousemove':
+ // drag tolerance, x² + y² = distance²
+ if ( Math.pow( event.pageX-dd.pageX, 2 ) + Math.pow( event.pageY-dd.pageY, 2 ) < Math.pow( dd.distance, 2 ) )
+ break; // distance tolerance not reached
+ event.target = dd.target; // force target from "mousedown" event (fix distance issue)
+ drag.hijack( event, "dragstart", dd ); // trigger "dragstart"
+ if ( dd.propagates ) // "dragstart" not rejected
+ dd.dragging = true; // activate interaction
+ // mousemove, dragging
+ case 'touchmove':
+ event.preventDefault();
+ case 'mousemove':
+ if ( dd.dragging ){
+ // trigger "drag"
+ drag.hijack( event, "drag", dd );
+ if ( dd.propagates ){
+ // manage drop events
+ if ( dd.drop !== false && $special.drop )
+ $special.drop.handler( event, dd ); // "dropstart", "dropend"
+ break; // "drag" not rejected, stop
+ }
+ event.type = "mouseup"; // helps "drop" handler behave
+ }
+ // mouseup, stop dragging
+ case 'touchend':
+ case 'mouseup':
+ default:
+ if ( drag.touched )
+ $event.remove( drag.touched, "touchmove touchend", drag.handler ); // remove touch events
+ else
+ $event.remove( document, "mousemove mouseup", drag.handler ); // remove page events
+ if ( dd.dragging ){
+ if ( dd.drop !== false && $special.drop )
+ $special.drop.handler( event, dd ); // "drop"
+ drag.hijack( event, "dragend", dd ); // trigger "dragend"
+ }
+ drag.textselect( true ); // enable text selection
+ // if suppressing click events...
+ if ( dd.click === false && dd.dragging )
+ $.data( dd.mousedown, "suppress.click", new Date().getTime() + 5 );
+ dd.dragging = drag.touched = false; // deactivate element
+ break;
+ }
+ },
+
+ // re-use event object for custom events
+ hijack: function( event, type, dd, x, elem ){
+ // not configured
+ if ( !dd )
+ return;
+ // remember the original event and type
+ var orig = { event:event.originalEvent, type:event.type },
+ // is the event drag related or drog related?
+ mode = type.indexOf("drop") ? "drag" : "drop",
+ // iteration vars
+ result, i = x || 0, ia, $elems, callback,
+ len = !isNaN( x ) ? x : dd.interactions.length;
+ // modify the event type
+ event.type = type;
+ // remove the original event
+ event.originalEvent = null;
+ // initialize the results
+ dd.results = [];
+ // handle each interacted element
+ do if ( ia = dd.interactions[ i ] ){
+ // validate the interaction
+ if ( type !== "dragend" && ia.cancelled )
+ continue;
+ // set the dragdrop properties on the event object
+ callback = drag.properties( event, dd, ia );
+ // prepare for more results
+ ia.results = [];
+ // handle each element
+ $( elem || ia[ mode ] || dd.droppable ).each(function( p, subject ){
+ // identify drag or drop targets individually
+ callback.target = subject;
+ // force propagtion of the custom event
+ event.isPropagationStopped = function(){ return false; };
+ // handle the event
+ result = subject ? $event.dispatch.call( subject, event, callback ) : null;
+ // stop the drag interaction for this element
+ if ( result === false ){
+ if ( mode == "drag" ){
+ ia.cancelled = true;
+ dd.propagates -= 1;
+ }
+ if ( type == "drop" ){
+ ia[ mode ][p] = null;
+ }
+ }
+ // assign any dropinit elements
+ else if ( type == "dropinit" )
+ ia.droppable.push( drag.element( result ) || subject );
+ // accept a returned proxy element
+ if ( type == "dragstart" )
+ ia.proxy = $( drag.element( result ) || ia.drag )[0];
+ // remember this result
+ ia.results.push( result );
+ // forget the event result, for recycling
+ delete event.result;
+ // break on cancelled handler
+ if ( type !== "dropinit" )
+ return result;
+ });
+ // flatten the results
+ dd.results[ i ] = drag.flatten( ia.results );
+ // accept a set of valid drop targets
+ if ( type == "dropinit" )
+ ia.droppable = drag.flatten( ia.droppable );
+ // locate drop targets
+ if ( type == "dragstart" && !ia.cancelled )
+ callback.update();
+ }
+ while ( ++i < len )
+ // restore the original event & type
+ event.type = orig.type;
+ event.originalEvent = orig.event;
+ // return all handler results
+ return drag.flatten( dd.results );
+ },
+
+ // extend the callback object with drag/drop properties...
+ properties: function( event, dd, ia ){
+ var obj = ia.callback;
+ // elements
+ obj.drag = ia.drag;
+ obj.proxy = ia.proxy || ia.drag;
+ // starting mouse position
+ obj.startX = dd.pageX;
+ obj.startY = dd.pageY;
+ // current distance dragged
+ obj.deltaX = event.pageX - dd.pageX;
+ obj.deltaY = event.pageY - dd.pageY;
+ // original element position
+ obj.originalX = ia.offset.left;
+ obj.originalY = ia.offset.top;
+ // adjusted element position
+ obj.offsetX = obj.originalX + obj.deltaX;
+ obj.offsetY = obj.originalY + obj.deltaY;
+ // assign the drop targets information
+ obj.drop = drag.flatten( ( ia.drop || [] ).slice() );
+ obj.available = drag.flatten( ( ia.droppable || [] ).slice() );
+ return obj;
+ },
+
+ // determine is the argument is an element or jquery instance
+ element: function( arg ){
+ if ( arg && ( arg.jquery || arg.nodeType == 1 ) )
+ return arg;
+ },
+
+ // flatten nested jquery objects and arrays into a single dimension array
+ flatten: function( arr ){
+ return $.map( arr, function( member ){
+ return member && member.jquery ? $.makeArray( member ) :
+ member && member.length ? drag.flatten( member ) : member;
+ });
+ },
+
+ // toggles text selection attributes ON (true) or OFF (false)
+ textselect: function( bool ){
+ $( document )[ bool ? "unbind" : "bind" ]("selectstart", drag.dontstart )
+ .css("MozUserSelect", bool ? "" : "none" );
+ // .attr("unselectable", bool ? "off" : "on" )
+ document.unselectable = bool ? "off" : "on";
+ },
+
+ // suppress "selectstart" and "ondragstart" events
+ dontstart: function(){
+ return false;
+ },
+
+ // a callback instance contructor
+ callback: function(){}
+
+};
+
+// callback methods
+drag.callback.prototype = {
+ update: function(){
+ if ( $special.drop && this.available.length )
+ $.each( this.available, function( i ){
+ $special.drop.locate( this, i );
+ });
+ }
+};
+
+// patch $.event.$dispatch to allow suppressing clicks
+var $dispatch = $event.dispatch;
+$event.dispatch = function( event ){
+ if ( $.data( this, "suppress."+ event.type ) - new Date().getTime() > 0 ){
+ $.removeData( this, "suppress."+ event.type );
+ return;
+ }
+ return $dispatch.apply( this, arguments );
+};
+
+// event fix hooks for touch events...
+var touchHooks =
+$event.fixHooks.touchstart =
+$event.fixHooks.touchmove =
+$event.fixHooks.touchend =
+$event.fixHooks.touchcancel = {
+ props: "clientX clientY pageX pageY screenX screenY".split( " " ),
+ filter: function( event, orig ) {
+ if ( orig ){
+ var touched = ( orig.touches && orig.touches[0] )
+ || ( orig.changedTouches && orig.changedTouches[0] )
+ || null;
+ // iOS webkit: touchstart, touchmove, touchend
+ if ( touched )
+ $.each( touchHooks.props, function( i, prop ){
+ event[ prop ] = touched[ prop ];
+ });
+ }
+ return event;
+ }
+};
+
+// share the same special event configuration with related events...
+$special.draginit = $special.dragstart = $special.dragend = drag;
+
+})( jQuery ); \ No newline at end of file
diff --git a/js/jquery/jquery.fullscreen.js b/js/jquery/jquery.fullscreen.js
new file mode 100644
index 0000000000..248e8ffde0
--- /dev/null
+++ b/js/jquery/jquery.fullscreen.js
@@ -0,0 +1,60 @@
+// jQuery.FullScreen plugin
+
+// Triple-licensed: Public Domain, MIT and WTFPL license - share and enjoy!
+
+(function($) {
+ function isFullScreen() {
+ return document[!prefix ? 'fullScreen' :
+ 'webkit' === prefix ? 'webkitIsFullScreen' :
+ prefix + 'FullScreen'];
+ }
+ function cancelFullScreen() {
+ return document[prefix ? prefix + 'CancelFullScreen'
+ : 'cancelFullScreen']();
+ }
+
+ var supported = typeof document.cancelFullScreen !== 'undefined'
+ , prefixes = ['webkit', 'moz', 'o', 'ms', 'khtml']
+ , prefix = ''
+ , noop = function() {}
+ , i
+ ;
+
+ if (!supported) {
+ for (i = 0; prefix = prefixes[i]; i++) {
+ if (typeof document[prefix + 'CancelFullScreen'] !== 'undefined') {
+ supported = true;
+ break;
+ }
+ }
+ }
+
+ if (supported) {
+ $.fn.requestFullScreen = function() {
+ return this.each(function() {
+ return this[prefix ? prefix + 'RequestFullScreen'
+ : 'requestFullScreen']();
+ });
+ };
+ $.fn.fullScreenChange = function(fn) {
+ var ar = [prefix + 'fullscreenchange'].concat([].slice.call(arguments, 0))
+ , $e = $(this);
+ return $e.bind.apply($e, ar);
+ };
+ $.FullScreen =
+ { isFullScreen: isFullScreen
+ , cancelFullScreen: cancelFullScreen
+ , prefix: prefix
+ , supported: supported
+ };
+ }
+ else {
+ $.fn.requestFullScreen = $.fn.fullScreenChange = noop;
+ $.FullScreen =
+ { isFullScreen: function() { return false; }
+ , cancelFullScreen: noop
+ , prefix: prefix
+ , supported: supported
+ };
+ }
+})(jQuery);
diff --git a/js/jquery/jquery.json-2.4.js b/js/jquery/jquery.json-2.4.js
new file mode 100644
index 0000000000..75953f4d23
--- /dev/null
+++ b/js/jquery/jquery.json-2.4.js
@@ -0,0 +1,199 @@
+/**
+ * jQuery JSON plugin 2.4.0
+ *
+ * @author Brantley Harris, 2009-2011
+ * @author Timo Tijhof, 2011-2012
+ * @source This plugin is heavily influenced by MochiKit's serializeJSON, which is
+ * copyrighted 2005 by Bob Ippolito.
+ * @source Brantley Harris wrote this plugin. It is based somewhat on the JSON.org
+ * website's http://www.json.org/json2.js, which proclaims:
+ * "NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.", a sentiment that
+ * I uphold.
+ * @license MIT License <http://www.opensource.org/licenses/mit-license.php>
+ */
+(function ($) {
+ 'use strict';
+
+ var escape = /["\\\x00-\x1f\x7f-\x9f]/g,
+ meta = {
+ '\b': '\\b',
+ '\t': '\\t',
+ '\n': '\\n',
+ '\f': '\\f',
+ '\r': '\\r',
+ '"' : '\\"',
+ '\\': '\\\\'
+ },
+ hasOwn = Object.prototype.hasOwnProperty;
+
+ /**
+ * jQuery.toJSON
+ * Converts the given argument into a JSON representation.
+ *
+ * @param o {Mixed} The json-serializable *thing* to be converted
+ *
+ * If an object has a toJSON prototype, that will be used to get the representation.
+ * Non-integer/string keys are skipped in the object, as are keys that point to a
+ * function.
+ *
+ */
+ $.toJSON = typeof JSON === 'object' && JSON.stringify ? JSON.stringify : function (o) {
+ if (o === null) {
+ return 'null';
+ }
+
+ var pairs, k, name, val,
+ type = $.type(o);
+
+ if (type === 'undefined') {
+ return undefined;
+ }
+
+ // Also covers instantiated Number and Boolean objects,
+ // which are typeof 'object' but thanks to $.type, we
+ // catch them here. I don't know whether it is right
+ // or wrong that instantiated primitives are not
+ // exported to JSON as an {"object":..}.
+ // We choose this path because that's what the browsers did.
+ if (type === 'number' || type === 'boolean') {
+ return String(o);
+ }
+ if (type === 'string') {
+ return $.quoteString(o);
+ }
+ if (typeof o.toJSON === 'function') {
+ return $.toJSON(o.toJSON());
+ }
+ if (type === 'date') {
+ var month = o.getUTCMonth() + 1,
+ day = o.getUTCDate(),
+ year = o.getUTCFullYear(),
+ hours = o.getUTCHours(),
+ minutes = o.getUTCMinutes(),
+ seconds = o.getUTCSeconds(),
+ milli = o.getUTCMilliseconds();
+
+ if (month < 10) {
+ month = '0' + month;
+ }
+ if (day < 10) {
+ day = '0' + day;
+ }
+ if (hours < 10) {
+ hours = '0' + hours;
+ }
+ if (minutes < 10) {
+ minutes = '0' + minutes;
+ }
+ if (seconds < 10) {
+ seconds = '0' + seconds;
+ }
+ if (milli < 100) {
+ milli = '0' + milli;
+ }
+ if (milli < 10) {
+ milli = '0' + milli;
+ }
+ return '"' + year + '-' + month + '-' + day + 'T' +
+ hours + ':' + minutes + ':' + seconds +
+ '.' + milli + 'Z"';
+ }
+
+ pairs = [];
+
+ if ($.isArray(o)) {
+ for (k = 0; k < o.length; k++) {
+ pairs.push($.toJSON(o[k]) || 'null');
+ }
+ return '[' + pairs.join(',') + ']';
+ }
+
+ // Any other object (plain object, RegExp, ..)
+ // Need to do typeof instead of $.type, because we also
+ // want to catch non-plain objects.
+ if (typeof o === 'object') {
+ for (k in o) {
+ // Only include own properties,
+ // Filter out inherited prototypes
+ if (hasOwn.call(o, k)) {
+ // Keys must be numerical or string. Skip others
+ type = typeof k;
+ if (type === 'number') {
+ name = '"' + k + '"';
+ } else if (type === 'string') {
+ name = $.quoteString(k);
+ } else {
+ continue;
+ }
+ type = typeof o[k];
+
+ // Invalid values like these return undefined
+ // from toJSON, however those object members
+ // shouldn't be included in the JSON string at all.
+ if (type !== 'function' && type !== 'undefined') {
+ val = $.toJSON(o[k]);
+ pairs.push(name + ':' + val);
+ }
+ }
+ }
+ return '{' + pairs.join(',') + '}';
+ }
+ };
+
+ /**
+ * jQuery.evalJSON
+ * Evaluates a given json string.
+ *
+ * @param str {String}
+ */
+ $.evalJSON = typeof JSON === 'object' && JSON.parse ? JSON.parse : function (str) {
+ /*jshint evil: true */
+ return eval('(' + str + ')');
+ };
+
+ /**
+ * jQuery.secureEvalJSON
+ * Evals JSON in a way that is *more* secure.
+ *
+ * @param str {String}
+ */
+ $.secureEvalJSON = typeof JSON === 'object' && JSON.parse ? JSON.parse : function (str) {
+ var filtered =
+ str
+ .replace(/\\["\\\/bfnrtu]/g, '@')
+ .replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']')
+ .replace(/(?:^|:|,)(?:\s*\[)+/g, '');
+
+ if (/^[\],:{}\s]*$/.test(filtered)) {
+ /*jshint evil: true */
+ return eval('(' + str + ')');
+ }
+ throw new SyntaxError('Error parsing JSON, source is not valid.');
+ };
+
+ /**
+ * jQuery.quoteString
+ * Returns a string-repr of a string, escaping quotes intelligently.
+ * Mostly a support function for toJSON.
+ * Examples:
+ * >>> jQuery.quoteString('apple')
+ * "apple"
+ *
+ * >>> jQuery.quoteString('"Where are we going?", she asked.')
+ * "\"Where are we going?\", she asked."
+ */
+ $.quoteString = function (str) {
+ if (str.match(escape)) {
+ return '"' + str.replace(escape, function (a) {
+ var c = meta[a];
+ if (typeof c === 'string') {
+ return c;
+ }
+ c = a.charCodeAt();
+ return '\\u00' + Math.floor(c / 16).toString(16) + (c % 16).toString(16);
+ }) + '"';
+ }
+ return '"' + str + '"';
+ };
+
+}(jQuery));
diff --git a/js/jquery/jquery.menuResizer-1.0.js b/js/jquery/jquery.menuResizer-1.0.js
new file mode 100644
index 0000000000..3a53b1f227
--- /dev/null
+++ b/js/jquery/jquery.menuResizer-1.0.js
@@ -0,0 +1,181 @@
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Handles the resizing of a menu according to the available screen width
+ *
+ * Uses themes/original/css/resizable-menu.css.php
+ *
+ * To initialise:
+ * $('#myMenu').menuResizer(function () {
+ * // This function will be called to find out how much
+ * // available horizontal space there is for the menu
+ * return $('body').width() - 5; // Some extra margin for good measure
+ * });
+ *
+ * To trigger a resize operation:
+ * $('#myMenu').menuResizer('resize'); // Bind this to $(window).resize()
+ *
+ * To restore the menu to a state like before it was initialized:
+ * $('#myMenu').menuResizer('destroy');
+ *
+ * @package PhpMyAdmin
+ */
+(function ($) {
+ function MenuResizer($container, widthCalculator) {
+ var self = this;
+ self.$container = $container;
+ self.widthCalculator = widthCalculator;
+ // create submenu container
+ var link = $('<a />', {href: '#', 'class': 'tab nowrap'})
+ .text(PMA_messages['strMore'])
+ .bind('click', false); // same as event.preventDefault()
+ var img = $container.find('li img');
+ if (img.length) {
+ $(PMA_getImage('b_more.png').toString()).prependTo(link);
+ }
+ var $submenu = $('<li />', {'class': 'submenu'})
+ .append(link)
+ .append($('<ul />'))
+ .mouseenter(function() {
+ if ($(this).find('ul .tabactive').length == 0) {
+ $(this)
+ .addClass('submenuhover')
+ .find('> a')
+ .addClass('tabactive');
+ }
+ })
+ .mouseleave(function() {
+ if ($(this).find('ul .tabactive').length == 0) {
+ $(this)
+ .removeClass('submenuhover')
+ .find('> a')
+ .removeClass('tabactive');
+ }
+ });
+ $container.append($submenu);
+ setTimeout(function () {
+ self.resize();
+ }, 4);
+ }
+ MenuResizer.prototype.resize = function () {
+ var wmax = this.widthCalculator.call(this.$container);
+ var $submenu = this.$container.find('.submenu:last');
+ var submenu_w = $submenu.outerWidth(true);
+ var $submenu_ul = $submenu.find('ul');
+ var $li = this.$container.find('> li');
+ var $li2 = $submenu_ul.find('li');
+ var more_shown = $li2.length > 0;
+ // Calculate the total width used by all the shown tabs
+ var total_len = more_shown ? submenu_w : 0;
+ var l = $li.length - 1;
+ for (var i = 0; i < l; i++) {
+ total_len += $($li[i]).outerWidth(true);
+ }
+ // Now hide menu elements that don't fit into the menubar
+ var hidden = false; // Whether we have hidden any tabs
+ while (total_len >= wmax && --l >= 0) { // Process the tabs backwards
+ hidden = true;
+ var el = $($li[l]);
+ var el_width = el.outerWidth(true);
+ el.data('width', el_width);
+ if (! more_shown) {
+ total_len -= el_width;
+ el.prependTo($submenu_ul);
+ total_len += submenu_w;
+ more_shown = true;
+ } else {
+ total_len -= el_width;
+ el.prependTo($submenu_ul);
+ }
+ }
+ // If we didn't hide any tabs, then there might be some space to show some
+ if (! hidden) {
+ // Show menu elements that do fit into the menubar
+ for (var i = 0, l = $li2.length; i < l; i++) {
+ total_len += $($li2[i]).data('width');
+ // item fits or (it is the last item
+ // and it would fit if More got removed)
+ if (total_len < wmax
+ || (i == $li2.length - 1 && total_len - submenu_w < wmax)
+ ) {
+ $($li2[i]).insertBefore($submenu);
+ } else {
+ break;
+ }
+ }
+ }
+ // Show/hide the "More" tab as needed
+ if ($submenu_ul.find('li').length > 0) {
+ $submenu.addClass('shown');
+ } else {
+ $submenu.removeClass('shown');
+ }
+ if (this.$container.find('> li').length == 1) {
+ // If there is only the "More" tab left, then we need
+ // to align the submenu to the left edge of the tab
+ $submenu_ul.removeClass().addClass('only');
+ } else {
+ // Otherwise we align the submenu to the right edge of the tab
+ $submenu_ul.removeClass().addClass('notonly');
+ }
+ if ($submenu.find('.tabactive').length) {
+ $submenu
+ .addClass('active')
+ .find('> a')
+ .removeClass('tab')
+ .addClass('tabactive');
+ } else {
+ $submenu
+ .removeClass('active')
+ .find('> a')
+ .addClass('tab')
+ .removeClass('tabactive');
+ }
+ };
+ MenuResizer.prototype.destroy = function () {
+ var $submenu = this.$container.find('li.submenu').removeData();
+ $submenu.find('li').appendTo(this.$container);
+ $submenu.remove();
+ };
+
+ /** Public API */
+ var methods = {
+ init: function(widthCalculator) {
+ return this.each(function () {
+ var $this = $(this);
+ if (! $this.data('menuResizer')) {
+ $this.data(
+ 'menuResizer',
+ new MenuResizer($this, widthCalculator)
+ );
+ }
+ });
+ },
+ resize: function () {
+ return this.each(function () {
+ var self = $(this).data('menuResizer');
+ if (self) {
+ self.resize();
+ }
+ });
+ },
+ destroy: function () {
+ return this.each(function () {
+ var self = $(this).data('menuResizer');
+ if (self) {
+ self.destroy();
+ }
+ });
+ }
+ };
+
+ /** Extend jQuery */
+ $.fn.menuResizer = function(method) {
+ if (methods[method]) {
+ return methods[method].call(this);
+ } else if (typeof method === 'function') {
+ return methods.init.apply(this, [method]);
+ } else {
+ $.error('Method ' + method + ' does not exist on jQuery.menuResizer');
+ }
+ };
+})(jQuery);
diff --git a/js/jquery/jquery.mousewheel.js b/js/jquery/jquery.mousewheel.js
new file mode 100644
index 0000000000..38b60951b2
--- /dev/null
+++ b/js/jquery/jquery.mousewheel.js
@@ -0,0 +1,84 @@
+/*! Copyright (c) 2011 Brandon Aaron (http://brandonaaron.net)
+ * Licensed under the MIT License (LICENSE.txt).
+ *
+ * Thanks to: http://adomas.org/javascript-mouse-wheel/ for some pointers.
+ * Thanks to: Mathias Bank(http://www.mathias-bank.de) for a scope bug fix.
+ * Thanks to: Seamus Leahy for adding deltaX and deltaY
+ *
+ * Version: 3.0.6
+ *
+ * Requires: 1.2.2+
+ */
+
+(function($) {
+
+var types = ['DOMMouseScroll', 'mousewheel'];
+
+if ($.event.fixHooks) {
+ for ( var i=types.length; i; ) {
+ $.event.fixHooks[ types[--i] ] = $.event.mouseHooks;
+ }
+}
+
+$.event.special.mousewheel = {
+ setup: function() {
+ if ( this.addEventListener ) {
+ for ( var i=types.length; i; ) {
+ this.addEventListener( types[--i], handler, false );
+ }
+ } else {
+ this.onmousewheel = handler;
+ }
+ },
+
+ teardown: function() {
+ if ( this.removeEventListener ) {
+ for ( var i=types.length; i; ) {
+ this.removeEventListener( types[--i], handler, false );
+ }
+ } else {
+ this.onmousewheel = null;
+ }
+ }
+};
+
+$.fn.extend({
+ mousewheel: function(fn) {
+ return fn ? this.bind("mousewheel", fn) : this.trigger("mousewheel");
+ },
+
+ unmousewheel: function(fn) {
+ return this.unbind("mousewheel", fn);
+ }
+});
+
+
+function handler(event) {
+ var orgEvent = event || window.event, args = [].slice.call( arguments, 1 ), delta = 0, returnValue = true, deltaX = 0, deltaY = 0;
+ event = $.event.fix(orgEvent);
+ event.type = "mousewheel";
+
+ // Old school scrollwheel delta
+ if ( orgEvent.wheelDelta ) { delta = orgEvent.wheelDelta/120; }
+ if ( orgEvent.detail ) { delta = -orgEvent.detail/3; }
+
+ // New school multidimensional scroll (touchpads) deltas
+ deltaY = delta;
+
+ // Gecko
+ if ( orgEvent.axis !== undefined && orgEvent.axis === orgEvent.HORIZONTAL_AXIS ) {
+ deltaY = 0;
+ deltaX = -1*delta;
+ }
+
+ // Webkit
+ if ( orgEvent.wheelDeltaY !== undefined ) { deltaY = orgEvent.wheelDeltaY/120; }
+ if ( orgEvent.wheelDeltaX !== undefined ) { deltaX = -1*orgEvent.wheelDeltaX/120; }
+
+ // Add event and delta to the front of the arguments
+ args.unshift(event, delta, deltaX, deltaY);
+
+ return ($.event.dispatch || $.event.handle).apply(this, args);
+}
+
+})(jQuery);
diff --git a/js/jquery/jquery.sortableTable.js b/js/jquery/jquery.sortableTable.js
new file mode 100644
index 0000000000..1f4fc91db8
--- /dev/null
+++ b/js/jquery/jquery.sortableTable.js
@@ -0,0 +1,272 @@
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * @fileoverview A jquery plugin that allows drag&drop sorting in tables.
+ * Coded because JQuery UI sortable doesn't support tables. Also it has no animation
+ *
+ * @name Sortable Table JQuery plugin
+ *
+ * @requires jQuery
+ *
+ */
+
+/* Options:
+
+$('table').sortableTable({
+ ignoreRect: { top, left, width, height } - relative coordinates on each element. If the user clicks
+ in this area, it is not seen as a drag&drop request. Useful for toolbars etc.
+ events: {
+ start: callback function when the user starts dragging
+ drop: callback function after an element has been dropped
+ }
+})
+*/
+
+/* Commands:
+
+$('table').sortableTable('init') - equivalent to $('table').sortableTable()
+$('table').sortableTable('refresh') - if the table has been changed, refresh correctly assigns all events again
+$('table').sortableTable('destroy') - removes all events from the table
+
+*/
+
+/* Setup:
+
+ Can be applied on any table, there is just one convention.
+ Each cell (<td>) has to contain one and only one element (preferably div or span)
+ which is the actually draggable element.
+*/
+(function($) {
+ jQuery.fn.sortableTable = function(method) {
+
+ var methods = {
+ init : function(options) {
+ var tb = new sortableTableInstance(this, options);
+ tb.init();
+ $(this).data('sortableTable',tb);
+ },
+ refresh : function( ) {
+ $(this).data('sortableTable').refresh();
+ },
+ destroy : function( ) {
+ $(this).data('sortableTable').destroy();
+ }
+ };
+
+ if ( methods[method] ) {
+ return methods[method].apply( this, Array.prototype.slice.call( arguments, 1 ));
+ } else if ( typeof method === 'object' || ! method ) {
+ return methods.init.apply( this, arguments );
+ } else {
+ $.error( 'Method ' + method + ' does not exist on jQuery.sortableTable' );
+ }
+
+ function sortableTableInstance(table, options) {
+ var down = false;
+ var $draggedEl, oldCell, previewMove, id;
+
+ if(!options) options = {};
+
+ /* Mouse handlers on the child elements */
+ var onMouseUp = function(e) {
+ dropAt(e.pageX, e.pageY);
+ }
+
+ var onMouseDown = function(e) {
+ $draggedEl = $(this).children();
+ if($draggedEl.length == 0) return;
+ if(options.ignoreRect && insideRect({x: e.pageX - $draggedEl.offset().left, y: e.pageY - $draggedEl.offset().top}, options.ignoreRect)) return;
+
+ down = true;
+ oldCell = this;
+ //move(e.pageX,e.pageY);
+
+ if(options.events && options.events.start)
+ options.events.start(this);
+
+ return false;
+ }
+
+ var globalMouseMove = function(e) {
+ if(down) {
+ move(e.pageX,e.pageY);
+
+ if(inside($(oldCell), e.pageX, e.pageY)) {
+ if(previewMove != null) {
+ moveTo(previewMove);
+ previewMove = null;
+ }
+ } else
+ $(table).find('td').each(function() {
+ if(inside($(this), e.pageX, e.pageY)) {
+ if($(previewMove).attr('class') != $(this).children().first().attr('class')) {
+ if(previewMove != null) moveTo(previewMove);
+ previewMove = $(this).children().first();
+ if(previewMove.length > 0)
+ moveTo($(previewMove), { pos: {
+ top: $(oldCell).offset().top - $(previewMove).parent().offset().top,
+ left: $(oldCell).offset().left - $(previewMove).parent().offset().left
+ } });
+ }
+
+ return false;
+ }
+ });
+ }
+
+ return false;
+ }
+
+ var globalMouseOut = function() {
+ if(down) {
+ down = false;
+ if(previewMove) moveTo(previewMove);
+ moveTo($draggedEl);
+ previewMove = null;
+ }
+ }
+
+ // Initialize sortable table
+ this.init = function() {
+ id = 1;
+ // Add some required css to each child element in the <td>s
+ $(table).find('td').children().each(function() {
+ // Remove any old occurences of our added draggable-num class
+ $(this).attr('class',$(this).attr('class').replace(/\s*draggable\-\d+/g,''));
+ $(this).addClass('draggable-' + (id++));
+ });
+
+ // Mouse events
+ $(table).find('td').bind('mouseup',onMouseUp);
+ $(table).find('td').bind('mousedown',onMouseDown);
+
+ $(document).mousemove(globalMouseMove);
+ $(document).bind('mouseleave', globalMouseOut);
+ }
+
+ // Call this when the table has been updated
+ this.refresh = function() {
+ this.destroy();
+ this.init();
+ }
+
+ this.destroy = function() {
+ // Add some required css to each child element in the <td>s
+ $(table).find('td').children().each(function() {
+ // Remove any old occurences of our added draggable-num class
+ $(this).attr('class',$(this).attr('class').replace(/\s*draggable\-\d+/g,''));
+ });
+
+ // Mouse events
+ $(table).find('td').unbind('mouseup',onMouseUp)
+ $(table).find('td').unbind('mousedown',onMouseDown);
+
+ $(document).unbind('mousemove',globalMouseMove);
+ $(document).unbind('mouseleave',globalMouseOut);
+ }
+
+ function switchElement(drag, dropTo) {
+ var dragPosDiff = {
+ left: $(drag).children().first().offset().left - $(dropTo).offset().left,
+ top: $(drag).children().first().offset().top - $(dropTo).offset().top
+ };
+
+ var dropPosDiff = null;
+ if($(dropTo).children().length > 0) {
+ dropPosDiff = {
+ left: $(dropTo).children().first().offset().left - $(drag).offset().left,
+ top: $(dropTo).children().first().offset().top - $(drag).offset().top
+ };
+ }
+
+ /* I love you append(). It moves the DOM Elements so gracefully <3 */
+ // Put the element in the way to old place
+ $(drag).append($(dropTo).children().first()).children()
+ .stop(true,true)
+ .bind('mouseup',onMouseUp);
+
+ if(dropPosDiff)
+ $(drag).append($(dropTo).children().first()).children()
+ .css('left',dropPosDiff.left + 'px')
+ .css('top',dropPosDiff.top + 'px');
+
+ // Put our dragged element into the space we just freed up
+ $(dropTo).append($(drag).children().first()).children()
+ .bind('mouseup',onMouseUp)
+ .css('left',dragPosDiff.left + 'px')
+ .css('top',dragPosDiff.top + 'px');
+
+ moveTo($(dropTo).children().first(), { duration: 100 });
+ moveTo($(drag).children().first(), { duration: 100 });
+
+ if(options.events && options.events.drop) {
+ // Drop event. The drag child element is moved into the drop element
+ // and vice versa. So the parameters are switched.
+
+ // Calculate row and column index
+ colIdx = $(dropTo).prevAll().length;
+ rowIdx = $(dropTo).parent().prevAll().length;
+
+ options.events.drop(drag,dropTo, { col: colIdx, row: rowIdx });
+ }
+ }
+
+ function move(x,y) {
+ $draggedEl.offset({
+ top: Math.min($(document).height(), Math.max(0, y - $draggedEl.height()/2)),
+ left: Math.min($(document).width(), Math.max(0, x - $draggedEl.width()/2))
+ });
+ }
+
+ function inside($el, x,y) {
+ var off = $el.offset();
+ return y >= off.top && x >= off.left && x < off.left + $el.width() && y < off.top + $el.height();
+ }
+
+ function insideRect(pos, r) {
+ return pos.y > r.top && pos.x > r.left && pos.y < r.top + r.height && pos.x < r.left + r.width;
+ }
+
+ function dropAt(x,y) {
+ if(!down) return;
+ down = false;
+
+ var switched = false;
+
+ $(table).find('td').each(function() {
+ if($(this).children().first().attr('class') != $(oldCell).children().first().attr('class') && inside($(this), x, y)) {
+ switchElement(oldCell, this);
+ switched = true;
+ return;
+ }
+ });
+
+ if(!switched) {
+ if(previewMove) moveTo(previewMove);
+ moveTo($draggedEl);
+ }
+
+ previewMove = null;
+ }
+
+ function moveTo(elem, opts) {
+ if(!opts) opts = {};
+ if(!opts.pos) opts.pos = { left: 0, top: 0 };
+ if(!opts.duration) opts.duration = 200;
+
+ $(elem).css('position','relative');
+ $(elem).animate({ top: opts.pos.top, left: opts.pos.left }, {
+ duration: opts.duration,
+ complete: function() {
+ if(opts.pos.left == 0 && opts.pos.top == 0) {
+ $(elem)
+ .css('position','')
+ .css('left','')
+ .css('top','');
+ }
+ }
+ });
+ }
+ }
+ }
+
+})( jQuery ); \ No newline at end of file
diff --git a/js/jquery/jquery.sprintf.js b/js/jquery/jquery.sprintf.js
new file mode 100644
index 0000000000..3d39b76ce2
--- /dev/null
+++ b/js/jquery/jquery.sprintf.js
@@ -0,0 +1,68 @@
+/**
+ * sprintf and vsprintf for jQuery
+ * somewhat based on http://jan.moesen.nu/code/javascript/sprintf-and-printf-in-javascript/
+ *
+ * Copyright (c) 2008 Sabin Iacob (m0n5t3r) <iacobs@m0n5t3r.info>
+ * 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 3 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.
+ *
+ * @license http://www.gnu.org/licenses/gpl.html
+ * @project jquery.sprintf
+ */
+(function($){
+ var formats = {
+ 'b': function(val) {return parseInt(val, 10).toString(2);},
+ 'c': function(val) {return String.fromCharCode(parseInt(val, 10));},
+ 'd': function(val) {return parseInt(val, 10);},
+ 'u': function(val) {return Math.abs(val);},
+ 'f': function(val, p) {
+ p = parseInt(p, 10);
+ val = parseFloat(val);
+ if(isNaN(p && val)) {
+ return NaN;
+ }
+ return p && val.toFixed(p) || val;
+ },
+ 'o': function(val) {return parseInt(val, 10).toString(8);},
+ 's': function(val) {return val;},
+ 'x': function(val) {return ('' + parseInt(val, 10).toString(16)).toLowerCase();},
+ 'X': function(val) {return ('' + parseInt(val, 10).toString(16)).toUpperCase();}
+ };
+
+ var re = /%(?:(\d+)?(?:\.(\d+))?|\(([^)]+)\))([%bcdufosxX])/g;
+
+ var dispatch = function(data){
+ if(data.length == 1 && typeof data[0] == 'object') { //python-style printf
+ data = data[0];
+ return function(match, w, p, lbl, fmt, off, str) {
+ return formats[fmt](data[lbl]);
+ };
+ } else { // regular, somewhat incomplete, printf
+ var idx = 0;
+ return function(match, w, p, lbl, fmt, off, str) {
+ if(fmt == '%') {
+ return '%';
+ }
+ return formats[fmt](data[idx++], p);
+ };
+ }
+ };
+
+ $.extend({
+ sprintf: function(format) {
+ var argv = Array.apply(null, arguments).slice(1);
+ return format.replace(re, dispatch(argv));
+ },
+ vsprintf: function(format, data) {
+ return format.replace(re, dispatch(data));
+ }
+ });
+})(jQuery);
+
diff --git a/js/jquery/jquery.svg.js b/js/jquery/jquery.svg.js
new file mode 100644
index 0000000000..6a6a9b3354
--- /dev/null
+++ b/js/jquery/jquery.svg.js
@@ -0,0 +1,1394 @@
+/* http://keith-wood.name/svg.html
+ SVG for jQuery v1.4.5.
+ Written by Keith Wood (kbwood{at}iinet.com.au) August 2007.
+ Dual licensed under the GPL (http://dev.jquery.com/browser/trunk/jquery/GPL-LICENSE.txt) and
+ MIT (http://dev.jquery.com/browser/trunk/jquery/MIT-LICENSE.txt) licenses.
+ Please attribute the author if you use it. */
+
+(function($) { // Hide scope, no $ conflict
+
+/* SVG manager.
+ Use the singleton instance of this class, $.svg,
+ to interact with the SVG functionality. */
+function SVGManager() {
+ this._settings = []; // Settings to be remembered per SVG object
+ this._extensions = []; // List of SVG extensions added to SVGWrapper
+ // for each entry [0] is extension name, [1] is extension class (function)
+ // the function takes one parameter - the SVGWrapper instance
+ this.regional = []; // Localisations, indexed by language, '' for default (English)
+ this.regional[''] = {errorLoadingText: 'Error loading',
+ notSupportedText: 'This browser does not support SVG'};
+ this.local = this.regional['']; // Current localisation
+ this._uuid = new Date().getTime();
+ this._renesis = detectActiveX('RenesisX.RenesisCtrl');
+}
+
+/* Determine whether a given ActiveX control is available.
+ @param classId (string) the ID for the ActiveX control
+ @return (boolean) true if found, false if not */
+function detectActiveX(classId) {
+ try {
+ return !!(window.ActiveXObject && new ActiveXObject(classId));
+ }
+ catch (e) {
+ return false;
+ }
+}
+
+var PROP_NAME = 'svgwrapper';
+
+$.extend(SVGManager.prototype, {
+ /* Class name added to elements to indicate already configured with SVG. */
+ markerClassName: 'hasSVG',
+
+ /* SVG namespace. */
+ svgNS: 'http://www.w3.org/2000/svg',
+ /* XLink namespace. */
+ xlinkNS: 'http://www.w3.org/1999/xlink',
+
+ /* SVG wrapper class. */
+ _wrapperClass: SVGWrapper,
+
+ /* Camel-case versions of attribute names containing dashes or are reserved words. */
+ _attrNames: {class_: 'class', in_: 'in',
+ alignmentBaseline: 'alignment-baseline', baselineShift: 'baseline-shift',
+ clipPath: 'clip-path', clipRule: 'clip-rule',
+ colorInterpolation: 'color-interpolation',
+ colorInterpolationFilters: 'color-interpolation-filters',
+ colorRendering: 'color-rendering', dominantBaseline: 'dominant-baseline',
+ enableBackground: 'enable-background', fillOpacity: 'fill-opacity',
+ fillRule: 'fill-rule', floodColor: 'flood-color',
+ floodOpacity: 'flood-opacity', fontFamily: 'font-family',
+ fontSize: 'font-size', fontSizeAdjust: 'font-size-adjust',
+ fontStretch: 'font-stretch', fontStyle: 'font-style',
+ fontVariant: 'font-variant', fontWeight: 'font-weight',
+ glyphOrientationHorizontal: 'glyph-orientation-horizontal',
+ glyphOrientationVertical: 'glyph-orientation-vertical',
+ horizAdvX: 'horiz-adv-x', horizOriginX: 'horiz-origin-x',
+ imageRendering: 'image-rendering', letterSpacing: 'letter-spacing',
+ lightingColor: 'lighting-color', markerEnd: 'marker-end',
+ markerMid: 'marker-mid', markerStart: 'marker-start',
+ stopColor: 'stop-color', stopOpacity: 'stop-opacity',
+ strikethroughPosition: 'strikethrough-position',
+ strikethroughThickness: 'strikethrough-thickness',
+ strokeDashArray: 'stroke-dasharray', strokeDashOffset: 'stroke-dashoffset',
+ strokeLineCap: 'stroke-linecap', strokeLineJoin: 'stroke-linejoin',
+ strokeMiterLimit: 'stroke-miterlimit', strokeOpacity: 'stroke-opacity',
+ strokeWidth: 'stroke-width', textAnchor: 'text-anchor',
+ textDecoration: 'text-decoration', textRendering: 'text-rendering',
+ underlinePosition: 'underline-position', underlineThickness: 'underline-thickness',
+ vertAdvY: 'vert-adv-y', vertOriginY: 'vert-origin-y',
+ wordSpacing: 'word-spacing', writingMode: 'writing-mode'},
+
+ /* Add the SVG object to its container. */
+ _attachSVG: function(container, settings) {
+ var svg = (container.namespaceURI == this.svgNS ? container : null);
+ var container = (svg ? null : container);
+ if ($(container || svg).hasClass(this.markerClassName)) {
+ return;
+ }
+ if (typeof settings == 'string') {
+ settings = {loadURL: settings};
+ }
+ else if (typeof settings == 'function') {
+ settings = {onLoad: settings};
+ }
+ $(container || svg).addClass(this.markerClassName);
+ try {
+ if (!svg) {
+ svg = document.createElementNS(this.svgNS, 'svg');
+ svg.setAttribute('version', '1.1');
+ if (container.clientWidth > 0) {
+ svg.setAttribute('width', container.clientWidth);
+ }
+ if (container.clientHeight > 0) {
+ svg.setAttribute('height', container.clientHeight);
+ }
+ container.appendChild(svg);
+ }
+ this._afterLoad(container, svg, settings || {});
+ }
+ catch (e) {
+ if ($.browser.msie) {
+ if (!container.id) {
+ container.id = 'svg' + (this._uuid++);
+ }
+ this._settings[container.id] = settings;
+ container.innerHTML = '<embed type="image/svg+xml" width="100%" ' +
+ 'height="100%" src="' + (settings.initPath || '') + 'blank.svg" ' +
+ 'pluginspage="http://www.adobe.com/svg/viewer/install/main.html"/>';
+ }
+ else {
+ container.innerHTML = '<p class="svg_error">' +
+ this.local.notSupportedText + '</p>';
+ }
+ }
+ },
+
+ /* SVG callback after loading - register SVG root. */
+ _registerSVG: function() {
+ for (var i = 0; i < document.embeds.length; i++) { // Check all
+ var container = document.embeds[i].parentNode;
+ if (!$(container).hasClass($.svg.markerClassName) || // Not SVG
+ $.data(container, PROP_NAME)) { // Already done
+ continue;
+ }
+ var svg = null;
+ try {
+ svg = document.embeds[i].getSVGDocument();
+ }
+ catch(e) {
+ setTimeout($.svg._registerSVG, 250); // Renesis takes longer to load
+ return;
+ }
+ svg = (svg ? svg.documentElement : null);
+ if (svg) {
+ $.svg._afterLoad(container, svg);
+ }
+ }
+ },
+
+ /* Post-processing once loaded. */
+ _afterLoad: function(container, svg, settings) {
+ var settings = settings || this._settings[container.id];
+ this._settings[container ? container.id : ''] = null;
+ var wrapper = new this._wrapperClass(svg, container);
+ $.data(container || svg, PROP_NAME, wrapper);
+ try {
+ if (settings.loadURL) { // Load URL
+ wrapper.load(settings.loadURL, settings);
+ }
+ if (settings.settings) { // Additional settings
+ wrapper.configure(settings.settings);
+ }
+ if (settings.onLoad && !settings.loadURL) { // Onload callback
+ settings.onLoad.apply(container || svg, [wrapper]);
+ }
+ }
+ catch (e) {
+ alert(e);
+ }
+ },
+
+ /* Return the SVG wrapper created for a given container.
+ @param container (string) selector for the container or
+ (element) the container for the SVG object or
+ jQuery collection - first entry is the container
+ @return (SVGWrapper) the corresponding SVG wrapper element, or null if not attached */
+ _getSVG: function(container) {
+ container = (typeof container == 'string' ? $(container)[0] :
+ (container.jquery ? container[0] : container));
+ return $.data(container, PROP_NAME);
+ },
+
+ /* Remove the SVG functionality from a div.
+ @param container (element) the container for the SVG object */
+ _destroySVG: function(container) {
+ var $container = $(container);
+ if (!$container.hasClass(this.markerClassName)) {
+ return;
+ }
+ $container.removeClass(this.markerClassName);
+ if (container.namespaceURI != this.svgNS) {
+ $container.empty();
+ }
+ $.removeData(container, PROP_NAME);
+ },
+
+ /* Extend the SVGWrapper object with an embedded class.
+ The constructor function must take a single parameter that is
+ a reference to the owning SVG root object. This allows the
+ extension to access the basic SVG functionality.
+ @param name (string) the name of the SVGWrapper attribute to access the new class
+ @param extClass (function) the extension class constructor */
+ addExtension: function(name, extClass) {
+ this._extensions.push([name, extClass]);
+ },
+
+ /* Does this node belong to SVG?
+ @param node (element) the node to be tested
+ @return (boolean) true if an SVG node, false if not */
+ isSVGElem: function(node) {
+ return (node.nodeType == 1 && node.namespaceURI == $.svg.svgNS);
+ }
+});
+
+/* The main SVG interface, which encapsulates the SVG element.
+ Obtain a reference from $().svg('get') */
+function SVGWrapper(svg, container) {
+ this._svg = svg; // The SVG root node
+ this._container = container; // The containing div
+ for (var i = 0; i < $.svg._extensions.length; i++) {
+ var extension = $.svg._extensions[i];
+ this[extension[0]] = new extension[1](this);
+ }
+}
+
+$.extend(SVGWrapper.prototype, {
+
+ /* Retrieve the width of the SVG object. */
+ _width: function() {
+ return (this._container ? this._container.clientWidth : this._svg.width);
+ },
+
+ /* Retrieve the height of the SVG object. */
+ _height: function() {
+ return (this._container ? this._container.clientHeight : this._svg.height);
+ },
+
+ /* Retrieve the root SVG element.
+ @return the top-level SVG element */
+ root: function() {
+ return this._svg;
+ },
+
+ /* Configure a SVG node.
+ @param node (element, optional) the node to configure
+ @param settings (object) additional settings for the root
+ @param clear (boolean) true to remove existing attributes first,
+ false to add to what is already there (optional)
+ @return (SVGWrapper) this root */
+ configure: function(node, settings, clear) {
+ if (!node.nodeName) {
+ clear = settings;
+ settings = node;
+ node = this._svg;
+ }
+ if (clear) {
+ for (var i = node.attributes.length - 1; i >= 0; i--) {
+ var attr = node.attributes.item(i);
+ if (!(attr.nodeName == 'onload' || attr.nodeName == 'version' ||
+ attr.nodeName.substring(0, 5) == 'xmlns')) {
+ node.attributes.removeNamedItem(attr.nodeName);
+ }
+ }
+ }
+ for (var attrName in settings) {
+ node.setAttribute($.svg._attrNames[attrName] || attrName, settings[attrName]);
+ }
+ return this;
+ },
+
+ /* Locate a specific element in the SVG document.
+ @param id (string) the element's identifier
+ @return (element) the element reference, or null if not found */
+ getElementById: function(id) {
+ return this._svg.ownerDocument.getElementById(id);
+ },
+
+ /* Change the attributes for a SVG node.
+ @param element (SVG element) the node to change
+ @param settings (object) the new settings
+ @return (SVGWrapper) this root */
+ change: function(element, settings) {
+ if (element) {
+ for (var name in settings) {
+ if (settings[name] == null) {
+ element.removeAttribute($.svg._attrNames[name] || name);
+ }
+ else {
+ element.setAttribute($.svg._attrNames[name] || name, settings[name]);
+ }
+ }
+ }
+ return this;
+ },
+
+ /* Check for parent being absent and adjust arguments accordingly. */
+ _args: function(values, names, optSettings) {
+ names.splice(0, 0, 'parent');
+ names.splice(names.length, 0, 'settings');
+ var args = {};
+ var offset = 0;
+ if (values[0] != null && values[0].jquery) {
+ values[0] = values[0][0];
+ }
+ if (values[0] != null && !(typeof values[0] == 'object' && values[0].nodeName)) {
+ args['parent'] = null;
+ offset = 1;
+ }
+ for (var i = 0; i < values.length; i++) {
+ args[names[i + offset]] = values[i];
+ }
+ if (optSettings) {
+ $.each(optSettings, function(i, value) {
+ if (typeof args[value] == 'object') {
+ args.settings = args[value];
+ args[value] = null;
+ }
+ });
+ }
+ return args;
+ },
+
+ /* Add a title.
+ @param parent (element or jQuery) the parent node for the new title (optional)
+ @param text (string) the text of the title
+ @param settings (object) additional settings for the title (optional)
+ @return (element) the new title node */
+ title: function(parent, text, settings) {
+ var args = this._args(arguments, ['text']);
+ var node = this._makeNode(args.parent, 'title', args.settings || {});
+ node.appendChild(this._svg.ownerDocument.createTextNode(args.text));
+ return node;
+ },
+
+ /* Add a description.
+ @param parent (element or jQuery) the parent node for the new description (optional)
+ @param text (string) the text of the description
+ @param settings (object) additional settings for the description (optional)
+ @return (element) the new description node */
+ describe: function(parent, text, settings) {
+ var args = this._args(arguments, ['text']);
+ var node = this._makeNode(args.parent, 'desc', args.settings || {});
+ node.appendChild(this._svg.ownerDocument.createTextNode(args.text));
+ return node;
+ },
+
+ /* Add a definitions node.
+ @param parent (element or jQuery) the parent node for the new definitions (optional)
+ @param id (string) the ID of this definitions (optional)
+ @param settings (object) additional settings for the definitions (optional)
+ @return (element) the new definitions node */
+ defs: function(parent, id, settings) {
+ var args = this._args(arguments, ['id'], ['id']);
+ return this._makeNode(args.parent, 'defs', $.extend(
+ (args.id ? {id: args.id} : {}), args.settings || {}));
+ },
+
+ /* Add a symbol definition.
+ @param parent (element or jQuery) the parent node for the new symbol (optional)
+ @param id (string) the ID of this symbol
+ @param x1 (number) the left coordinate for this symbol
+ @param y1 (number) the top coordinate for this symbol
+ @param width (number) the width of this symbol
+ @param height (number) the height of this symbol
+ @param settings (object) additional settings for the symbol (optional)
+ @return (element) the new symbol node */
+ symbol: function(parent, id, x1, y1, width, height, settings) {
+ var args = this._args(arguments, ['id', 'x1', 'y1', 'width', 'height']);
+ return this._makeNode(args.parent, 'symbol', $.extend({id: args.id,
+ viewBox: args.x1 + ' ' + args.y1 + ' ' + args.width + ' ' + args.height},
+ args.settings || {}));
+ },
+
+ /* Add a marker definition.
+ @param parent (element or jQuery) the parent node for the new marker (optional)
+ @param id (string) the ID of this marker
+ @param refX (number) the x-coordinate for the reference point
+ @param refY (number) the y-coordinate for the reference point
+ @param mWidth (number) the marker viewport width
+ @param mHeight (number) the marker viewport height
+ @param orient (string or int) 'auto' or angle (degrees) (optional)
+ @param settings (object) additional settings for the marker (optional)
+ @return (element) the new marker node */
+ marker: function(parent, id, refX, refY, mWidth, mHeight, orient, settings) {
+ var args = this._args(arguments, ['id', 'refX', 'refY',
+ 'mWidth', 'mHeight', 'orient'], ['orient']);
+ return this._makeNode(args.parent, 'marker', $.extend(
+ {id: args.id, refX: args.refX, refY: args.refY, markerWidth: args.mWidth,
+ markerHeight: args.mHeight, orient: args.orient || 'auto'}, args.settings || {}));
+ },
+
+ /* Add a style node.
+ @param parent (element or jQuery) the parent node for the new node (optional)
+ @param styles (string) the CSS styles
+ @param settings (object) additional settings for the node (optional)
+ @return (element) the new style node */
+ style: function(parent, styles, settings) {
+ var args = this._args(arguments, ['styles']);
+ var node = this._makeNode(args.parent, 'style', $.extend(
+ {type: 'text/css'}, args.settings || {}));
+ node.appendChild(this._svg.ownerDocument.createTextNode(args.styles));
+ if ($.browser.opera) {
+ $('head').append('<style type="text/css">' + args.styles + '</style>');
+ }
+ return node;
+ },
+
+ /* Add a script node.
+ @param parent (element or jQuery) the parent node for the new node (optional)
+ @param script (string) the JavaScript code
+ @param type (string) the MIME type for the code (optional, default 'text/javascript')
+ @param settings (object) additional settings for the node (optional)
+ @return (element) the new script node */
+ script: function(parent, script, type, settings) {
+ var args = this._args(arguments, ['script', 'type'], ['type']);
+ var node = this._makeNode(args.parent, 'script', $.extend(
+ {type: args.type || 'text/javascript'}, args.settings || {}));
+ node.appendChild(this._svg.ownerDocument.createTextNode(args.script));
+ if (!$.browser.mozilla) {
+ $.globalEval(args.script);
+ }
+ return node;
+ },
+
+ /* Add a linear gradient definition.
+ Specify all of x1, y1, x2, y2 or none of them.
+ @param parent (element or jQuery) the parent node for the new gradient (optional)
+ @param id (string) the ID for this gradient
+ @param stops (string[][]) the gradient stops, each entry is
+ [0] is offset (0.0-1.0 or 0%-100%), [1] is colour,
+ [2] is opacity (optional)
+ @param x1 (number) the x-coordinate of the gradient start (optional)
+ @param y1 (number) the y-coordinate of the gradient start (optional)
+ @param x2 (number) the x-coordinate of the gradient end (optional)
+ @param y2 (number) the y-coordinate of the gradient end (optional)
+ @param settings (object) additional settings for the gradient (optional)
+ @return (element) the new gradient node */
+ linearGradient: function(parent, id, stops, x1, y1, x2, y2, settings) {
+ var args = this._args(arguments,
+ ['id', 'stops', 'x1', 'y1', 'x2', 'y2'], ['x1']);
+ var sets = $.extend({id: args.id},
+ (args.x1 != null ? {x1: args.x1, y1: args.y1, x2: args.x2, y2: args.y2} : {}));
+ return this._gradient(args.parent, 'linearGradient',
+ $.extend(sets, args.settings || {}), args.stops);
+ },
+
+ /* Add a radial gradient definition.
+ Specify all of cx, cy, r, fx, fy or none of them.
+ @param parent (element or jQuery) the parent node for the new gradient (optional)
+ @param id (string) the ID for this gradient
+ @param stops (string[][]) the gradient stops, each entry
+ [0] is offset, [1] is colour, [2] is opacity (optional)
+ @param cx (number) the x-coordinate of the largest circle centre (optional)
+ @param cy (number) the y-coordinate of the largest circle centre (optional)
+ @param r (number) the radius of the largest circle (optional)
+ @param fx (number) the x-coordinate of the gradient focus (optional)
+ @param fy (number) the y-coordinate of the gradient focus (optional)
+ @param settings (object) additional settings for the gradient (optional)
+ @return (element) the new gradient node */
+ radialGradient: function(parent, id, stops, cx, cy, r, fx, fy, settings) {
+ var args = this._args(arguments,
+ ['id', 'stops', 'cx', 'cy', 'r', 'fx', 'fy'], ['cx']);
+ var sets = $.extend({id: args.id}, (args.cx != null ?
+ {cx: args.cx, cy: args.cy, r: args.r, fx: args.fx, fy: args.fy} : {}));
+ return this._gradient(args.parent, 'radialGradient',
+ $.extend(sets, args.settings || {}), args.stops);
+ },
+
+ /* Add a gradient node. */
+ _gradient: function(parent, name, settings, stops) {
+ var node = this._makeNode(parent, name, settings);
+ for (var i = 0; i < stops.length; i++) {
+ var stop = stops[i];
+ this._makeNode(node, 'stop', $.extend(
+ {offset: stop[0], stopColor: stop[1]},
+ (stop[2] != null ? {stopOpacity: stop[2]} : {})));
+ }
+ return node;
+ },
+
+ /* Add a pattern definition.
+ Specify all of vx, vy, xwidth, vheight or none of them.
+ @param parent (element or jQuery) the parent node for the new pattern (optional)
+ @param id (string) the ID for this pattern
+ @param x (number) the x-coordinate for the left edge of the pattern
+ @param y (number) the y-coordinate for the top edge of the pattern
+ @param width (number) the width of the pattern
+ @param height (number) the height of the pattern
+ @param vx (number) the minimum x-coordinate for view box (optional)
+ @param vy (number) the minimum y-coordinate for the view box (optional)
+ @param vwidth (number) the width of the view box (optional)
+ @param vheight (number) the height of the view box (optional)
+ @param settings (object) additional settings for the pattern (optional)
+ @return (element) the new pattern node */
+ pattern: function(parent, id, x, y, width, height, vx, vy, vwidth, vheight, settings) {
+ var args = this._args(arguments, ['id', 'x', 'y', 'width', 'height',
+ 'vx', 'vy', 'vwidth', 'vheight'], ['vx']);
+ var sets = $.extend({id: args.id, x: args.x, y: args.y,
+ width: args.width, height: args.height}, (args.vx != null ?
+ {viewBox: args.vx + ' ' + args.vy + ' ' + args.vwidth + ' ' + args.vheight} : {}));
+ return this._makeNode(args.parent, 'pattern', $.extend(sets, args.settings || {}));
+ },
+
+ /* Add a clip path definition.
+ @param parent (element) the parent node for the new element (optional)
+ @param id (string) the ID for this path
+ @param units (string) either 'userSpaceOnUse' (default) or 'objectBoundingBox' (optional)
+ @return (element) the new clipPath node */
+ clipPath: function(parent, id, units, settings) {
+ var args = this._args(arguments, ['id', 'units']);
+ args.units = args.units || 'userSpaceOnUse';
+ return this._makeNode(args.parent, 'clipPath', $.extend(
+ {id: args.id, clipPathUnits: args.units}, args.settings || {}));
+ },
+
+ /* Add a mask definition.
+ @param parent (element or jQuery) the parent node for the new mask (optional)
+ @param id (string) the ID for this mask
+ @param x (number) the x-coordinate for the left edge of the mask
+ @param y (number) the y-coordinate for the top edge of the mask
+ @param width (number) the width of the mask
+ @param height (number) the height of the mask
+ @param settings (object) additional settings for the mask (optional)
+ @return (element) the new mask node */
+ mask: function(parent, id, x, y, width, height, settings) {
+ var args = this._args(arguments, ['id', 'x', 'y', 'width', 'height']);
+ return this._makeNode(args.parent, 'mask', $.extend(
+ {id: args.id, x: args.x, y: args.y, width: args.width, height: args.height},
+ args.settings || {}));
+ },
+
+ /* Create a new path object.
+ @return (SVGPath) a new path object */
+ createPath: function() {
+ return new SVGPath();
+ },
+
+ /* Create a new text object.
+ @return (SVGText) a new text object */
+ createText: function() {
+ return new SVGText();
+ },
+
+ /* Add an embedded SVG element.
+ Specify all of vx, vy, vwidth, vheight or none of them.
+ @param parent (element or jQuery) the parent node for the new node (optional)
+ @param x (number) the x-coordinate for the left edge of the node
+ @param y (number) the y-coordinate for the top edge of the node
+ @param width (number) the width of the node
+ @param height (number) the height of the node
+ @param vx (number) the minimum x-coordinate for view box (optional)
+ @param vy (number) the minimum y-coordinate for the view box (optional)
+ @param vwidth (number) the width of the view box (optional)
+ @param vheight (number) the height of the view box (optional)
+ @param settings (object) additional settings for the node (optional)
+ @return (element) the new node */
+ svg: function(parent, x, y, width, height, vx, vy, vwidth, vheight, settings) {
+ var args = this._args(arguments, ['x', 'y', 'width', 'height',
+ 'vx', 'vy', 'vwidth', 'vheight'], ['vx']);
+ var sets = $.extend({x: args.x, y: args.y, width: args.width, height: args.height},
+ (args.vx != null ? {viewBox: args.vx + ' ' + args.vy + ' ' +
+ args.vwidth + ' ' + args.vheight} : {}));
+ return this._makeNode(args.parent, 'svg', $.extend(sets, args.settings || {}));
+ },
+
+ /* Create a group.
+ @param parent (element or jQuery) the parent node for the new group (optional)
+ @param id (string) the ID of this group (optional)
+ @param settings (object) additional settings for the group (optional)
+ @return (element) the new group node */
+ group: function(parent, id, settings) {
+ var args = this._args(arguments, ['id'], ['id']);
+ return this._makeNode(args.parent, 'g', $.extend({id: args.id}, args.settings || {}));
+ },
+
+ /* Add a usage reference.
+ Specify all of x, y, width, height or none of them.
+ @param parent (element or jQuery) the parent node for the new node (optional)
+ @param x (number) the x-coordinate for the left edge of the node (optional)
+ @param y (number) the y-coordinate for the top edge of the node (optional)
+ @param width (number) the width of the node (optional)
+ @param height (number) the height of the node (optional)
+ @param ref (string) the ID of the definition node
+ @param settings (object) additional settings for the node (optional)
+ @return (element) the new node */
+ use: function(parent, x, y, width, height, ref, settings) {
+ var args = this._args(arguments, ['x', 'y', 'width', 'height', 'ref']);
+ if (typeof args.x == 'string') {
+ args.ref = args.x;
+ args.settings = args.y;
+ args.x = args.y = args.width = args.height = null;
+ }
+ var node = this._makeNode(args.parent, 'use', $.extend(
+ {x: args.x, y: args.y, width: args.width, height: args.height},
+ args.settings || {}));
+ node.setAttributeNS($.svg.xlinkNS, 'href', args.ref);
+ return node;
+ },
+
+ /* Add a link, which applies to all child elements.
+ @param parent (element or jQuery) the parent node for the new link (optional)
+ @param ref (string) the target URL
+ @param settings (object) additional settings for the link (optional)
+ @return (element) the new link node */
+ link: function(parent, ref, settings) {
+ var args = this._args(arguments, ['ref']);
+ var node = this._makeNode(args.parent, 'a', args.settings);
+ node.setAttributeNS($.svg.xlinkNS, 'href', args.ref);
+ return node;
+ },
+
+ /* Add an image.
+ @param parent (element or jQuery) the parent node for the new image (optional)
+ @param x (number) the x-coordinate for the left edge of the image
+ @param y (number) the y-coordinate for the top edge of the image
+ @param width (number) the width of the image
+ @param height (number) the height of the image
+ @param ref (string) the path to the image
+ @param settings (object) additional settings for the image (optional)
+ @return (element) the new image node */
+ image: function(parent, x, y, width, height, ref, settings) {
+ var args = this._args(arguments, ['x', 'y', 'width', 'height', 'ref']);
+ var node = this._makeNode(args.parent, 'image', $.extend(
+ {x: args.x, y: args.y, width: args.width, height: args.height},
+ args.settings || {}));
+ node.setAttributeNS($.svg.xlinkNS, 'href', args.ref);
+ return node;
+ },
+
+ /* Draw a path.
+ @param parent (element or jQuery) the parent node for the new shape (optional)
+ @param path (string or SVGPath) the path to draw
+ @param settings (object) additional settings for the shape (optional)
+ @return (element) the new shape node */
+ path: function(parent, path, settings) {
+ var args = this._args(arguments, ['path']);
+ return this._makeNode(args.parent, 'path', $.extend(
+ {d: (args.path.path ? args.path.path() : args.path)}, args.settings || {}));
+ },
+
+ /* Draw a rectangle.
+ Specify both of rx and ry or neither.
+ @param parent (element or jQuery) the parent node for the new shape (optional)
+ @param x (number) the x-coordinate for the left edge of the rectangle
+ @param y (number) the y-coordinate for the top edge of the rectangle
+ @param width (number) the width of the rectangle
+ @param height (number) the height of the rectangle
+ @param rx (number) the x-radius of the ellipse for the rounded corners (optional)
+ @param ry (number) the y-radius of the ellipse for the rounded corners (optional)
+ @param settings (object) additional settings for the shape (optional)
+ @return (element) the new shape node */
+ rect: function(parent, x, y, width, height, rx, ry, settings) {
+ var args = this._args(arguments, ['x', 'y', 'width', 'height', 'rx', 'ry'], ['rx']);
+ return this._makeNode(args.parent, 'rect', $.extend(
+ {x: args.x, y: args.y, width: args.width, height: args.height},
+ (args.rx ? {rx: args.rx, ry: args.ry} : {}), args.settings || {}));
+ },
+
+ /* Draw a circle.
+ @param parent (element or jQuery) the parent node for the new shape (optional)
+ @param cx (number) the x-coordinate for the centre of the circle
+ @param cy (number) the y-coordinate for the centre of the circle
+ @param r (number) the radius of the circle
+ @param settings (object) additional settings for the shape (optional)
+ @return (element) the new shape node */
+ circle: function(parent, cx, cy, r, settings) {
+ var args = this._args(arguments, ['cx', 'cy', 'r']);
+ return this._makeNode(args.parent, 'circle', $.extend(
+ {cx: args.cx, cy: args.cy, r: args.r}, args.settings || {}));
+ },
+
+ /* Draw an ellipse.
+ @param parent (element or jQuery) the parent node for the new shape (optional)
+ @param cx (number) the x-coordinate for the centre of the ellipse
+ @param cy (number) the y-coordinate for the centre of the ellipse
+ @param rx (number) the x-radius of the ellipse
+ @param ry (number) the y-radius of the ellipse
+ @param settings (object) additional settings for the shape (optional)
+ @return (element) the new shape node */
+ ellipse: function(parent, cx, cy, rx, ry, settings) {
+ var args = this._args(arguments, ['cx', 'cy', 'rx', 'ry']);
+ return this._makeNode(args.parent, 'ellipse', $.extend(
+ {cx: args.cx, cy: args.cy, rx: args.rx, ry: args.ry}, args.settings || {}));
+ },
+
+ /* Draw a line.
+ @param parent (element or jQuery) the parent node for the new shape (optional)
+ @param x1 (number) the x-coordinate for the start of the line
+ @param y1 (number) the y-coordinate for the start of the line
+ @param x2 (number) the x-coordinate for the end of the line
+ @param y2 (number) the y-coordinate for the end of the line
+ @param settings (object) additional settings for the shape (optional)
+ @return (element) the new shape node */
+ line: function(parent, x1, y1, x2, y2, settings) {
+ var args = this._args(arguments, ['x1', 'y1', 'x2', 'y2']);
+ return this._makeNode(args.parent, 'line', $.extend(
+ {x1: args.x1, y1: args.y1, x2: args.x2, y2: args.y2}, args.settings || {}));
+ },
+
+ /* Draw a polygonal line.
+ @param parent (element or jQuery) the parent node for the new shape (optional)
+ @param points (number[][]) the x-/y-coordinates for the points on the line
+ @param settings (object) additional settings for the shape (optional)
+ @return (element) the new shape node */
+ polyline: function(parent, points, settings) {
+ var args = this._args(arguments, ['points']);
+ return this._poly(args.parent, 'polyline', args.points, args.settings);
+ },
+
+ /* Draw a polygonal shape.
+ @param parent (element or jQuery) the parent node for the new shape (optional)
+ @param points (number[][]) the x-/y-coordinates for the points on the shape
+ @param settings (object) additional settings for the shape (optional)
+ @return (element) the new shape node */
+ polygon: function(parent, points, settings) {
+ var args = this._args(arguments, ['points']);
+ return this._poly(args.parent, 'polygon', args.points, args.settings);
+ },
+
+ /* Draw a polygonal line or shape. */
+ _poly: function(parent, name, points, settings) {
+ var ps = '';
+ for (var i = 0; i < points.length; i++) {
+ ps += points[i].join() + ' ';
+ }
+ return this._makeNode(parent, name, $.extend(
+ {points: $.trim(ps)}, settings || {}));
+ },
+
+ /* Draw text.
+ Specify both of x and y or neither of them.
+ @param parent (element or jQuery) the parent node for the text (optional)
+ @param x (number or number[]) the x-coordinate(s) for the text (optional)
+ @param y (number or number[]) the y-coordinate(s) for the text (optional)
+ @param value (string) the text content or
+ (SVGText) text with spans and references
+ @param settings (object) additional settings for the text (optional)
+ @return (element) the new text node */
+ text: function(parent, x, y, value, settings) {
+ var args = this._args(arguments, ['x', 'y', 'value']);
+ if (typeof args.x == 'string' && arguments.length < 4) {
+ args.value = args.x;
+ args.settings = args.y;
+ args.x = args.y = null;
+ }
+ return this._text(args.parent, 'text', args.value, $.extend(
+ {x: (args.x && isArray(args.x) ? args.x.join(' ') : args.x),
+ y: (args.y && isArray(args.y) ? args.y.join(' ') : args.y)},
+ args.settings || {}));
+ },
+
+ /* Draw text along a path.
+ @param parent (element or jQuery) the parent node for the text (optional)
+ @param path (string) the ID of the path
+ @param value (string) the text content or
+ (SVGText) text with spans and references
+ @param settings (object) additional settings for the text (optional)
+ @return (element) the new text node */
+ textpath: function(parent, path, value, settings) {
+ var args = this._args(arguments, ['path', 'value']);
+ var node = this._text(args.parent, 'textPath', args.value, args.settings || {});
+ node.setAttributeNS($.svg.xlinkNS, 'href', args.path);
+ return node;
+ },
+
+ /* Draw text. */
+ _text: function(parent, name, value, settings) {
+ var node = this._makeNode(parent, name, settings);
+ if (typeof value == 'string') {
+ node.appendChild(node.ownerDocument.createTextNode(value));
+ }
+ else {
+ for (var i = 0; i < value._parts.length; i++) {
+ var part = value._parts[i];
+ if (part[0] == 'tspan') {
+ var child = this._makeNode(node, part[0], part[2]);
+ child.appendChild(node.ownerDocument.createTextNode(part[1]));
+ node.appendChild(child);
+ }
+ else if (part[0] == 'tref') {
+ var child = this._makeNode(node, part[0], part[2]);
+ child.setAttributeNS($.svg.xlinkNS, 'href', part[1]);
+ node.appendChild(child);
+ }
+ else if (part[0] == 'textpath') {
+ var set = $.extend({}, part[2]);
+ set.href = null;
+ var child = this._makeNode(node, part[0], set);
+ child.setAttributeNS($.svg.xlinkNS, 'href', part[2].href);
+ child.appendChild(node.ownerDocument.createTextNode(part[1]));
+ node.appendChild(child);
+ }
+ else { // straight text
+ node.appendChild(node.ownerDocument.createTextNode(part[1]));
+ }
+ }
+ }
+ return node;
+ },
+
+ /* Add a custom SVG element.
+ @param parent (element or jQuery) the parent node for the new element (optional)
+ @param name (string) the name of the element
+ @param settings (object) additional settings for the element (optional)
+ @return (element) the new custom node */
+ other: function(parent, name, settings) {
+ var args = this._args(arguments, ['name']);
+ return this._makeNode(args.parent, args.name, args.settings || {});
+ },
+
+ /* Create a shape node with the given settings. */
+ _makeNode: function(parent, name, settings) {
+ parent = parent || this._svg;
+ var node = this._svg.ownerDocument.createElementNS($.svg.svgNS, name);
+ for (var name in settings) {
+ var value = settings[name];
+ if (value != null && value != null &&
+ (typeof value != 'string' || value != '')) {
+ node.setAttribute($.svg._attrNames[name] || name, value);
+ }
+ }
+ parent.appendChild(node);
+ return node;
+ },
+
+ /* Add an existing SVG node to the diagram.
+ @param parent (element or jQuery) the parent node for the new node (optional)
+ @param node (element) the new node to add or
+ (string) the jQuery selector for the node or
+ (jQuery collection) set of nodes to add
+ @return (SVGWrapper) this wrapper */
+ add: function(parent, node) {
+ var args = this._args((arguments.length == 1 ? [null, parent] : arguments), ['node']);
+ var svg = this;
+ args.parent = args.parent || this._svg;
+ args.node = (args.node.jquery ? args.node : $(args.node));
+ try {
+ if ($.svg._renesis) {
+ throw 'Force traversal';
+ }
+ args.parent.appendChild(args.node.cloneNode(true));
+ }
+ catch (e) {
+ args.node.each(function() {
+ var child = svg._cloneAsSVG(this);
+ if (child) {
+ args.parent.appendChild(child);
+ }
+ });
+ }
+ return this;
+ },
+
+ /* Clone an existing SVG node and add it to the diagram.
+ @param parent (element or jQuery) the parent node for the new node (optional)
+ @param node (element) the new node to add or
+ (string) the jQuery selector for the node or
+ (jQuery collection) set of nodes to add
+ @return (element[]) collection of new nodes */
+ clone: function(parent, node) {
+ var svg = this;
+ var args = this._args((arguments.length == 1 ? [null, parent] : arguments), ['node']);
+ args.parent = args.parent || this._svg;
+ args.node = (args.node.jquery ? args.node : $(args.node));
+ var newNodes = [];
+ args.node.each(function() {
+ var child = svg._cloneAsSVG(this);
+ if (child) {
+ child.id = '';
+ args.parent.appendChild(child);
+ newNodes.push(child);
+ }
+ });
+ return newNodes;
+ },
+
+ /* SVG nodes must belong to the SVG namespace, so clone and ensure this is so.
+ @param node (element) the SVG node to clone
+ @return (element) the cloned node */
+ _cloneAsSVG: function(node) {
+ var newNode = null;
+ if (node.nodeType == 1) { // element
+ newNode = this._svg.ownerDocument.createElementNS(
+ $.svg.svgNS, this._checkName(node.nodeName));
+ for (var i = 0; i < node.attributes.length; i++) {
+ var attr = node.attributes.item(i);
+ if (attr.nodeName != 'xmlns' && attr.nodeValue) {
+ if (attr.prefix == 'xlink') {
+ newNode.setAttributeNS($.svg.xlinkNS,
+ attr.localName || attr.baseName, attr.nodeValue);
+ }
+ else {
+ newNode.setAttribute(this._checkName(attr.nodeName), attr.nodeValue);
+ }
+ }
+ }
+ for (var i = 0; i < node.childNodes.length; i++) {
+ var child = this._cloneAsSVG(node.childNodes[i]);
+ if (child) {
+ newNode.appendChild(child);
+ }
+ }
+ }
+ else if (node.nodeType == 3) { // text
+ if ($.trim(node.nodeValue)) {
+ newNode = this._svg.ownerDocument.createTextNode(node.nodeValue);
+ }
+ }
+ else if (node.nodeType == 4) { // CDATA
+ if ($.trim(node.nodeValue)) {
+ try {
+ newNode = this._svg.ownerDocument.createCDATASection(node.nodeValue);
+ }
+ catch (e) {
+ newNode = this._svg.ownerDocument.createTextNode(
+ node.nodeValue.replace(/&/g, '&amp;').
+ replace(/</g, '&lt;').replace(/>/g, '&gt;'));
+ }
+ }
+ }
+ return newNode;
+ },
+
+ /* Node names must be lower case and without SVG namespace prefix. */
+ _checkName: function(name) {
+ name = (name.substring(0, 1) >= 'A' && name.substring(0, 1) <= 'Z' ?
+ name.toLowerCase() : name);
+ return (name.substring(0, 4) == 'svg:' ? name.substring(4) : name);
+ },
+
+ /* Load an external SVG document.
+ @param url (string) the location of the SVG document or
+ the actual SVG content
+ @param settings (boolean) see addTo below or
+ (function) see onLoad below or
+ (object) additional settings for the load with attributes below:
+ addTo (boolean) true to add to what's already there,
+ or false to clear the canvas first
+ changeSize (boolean) true to allow the canvas size to change,
+ or false to retain the original
+ onLoad (function) callback after the document has loaded,
+ 'this' is the container, receives SVG object and
+ optional error message as a parameter
+ parent (string or element or jQuery) the parent to load
+ into, defaults to top-level svg element
+ @return (SVGWrapper) this root */
+ load: function(url, settings) {
+ settings = (typeof settings == 'boolean' ? {addTo: settings} :
+ (typeof settings == 'function' ? {onLoad: settings} :
+ (typeof settings == 'string' ? {parent: settings} :
+ (typeof settings == 'object' && settings.nodeName ? {parent: settings} :
+ (typeof settings == 'object' && settings.jquery ? {parent: settings} :
+ settings || {})))));
+ if (!settings.parent && !settings.addTo) {
+ this.clear(false);
+ }
+ var size = [this._svg.getAttribute('width'), this._svg.getAttribute('height')];
+ var wrapper = this;
+ // Report a problem with the load
+ var reportError = function(message) {
+ message = $.svg.local.errorLoadingText + ': ' + message;
+ if (settings.onLoad) {
+ settings.onLoad.apply(wrapper._container || wrapper._svg, [wrapper, message]);
+ }
+ else {
+ wrapper.text(null, 10, 20, message);
+ }
+ };
+ // Create a DOM from SVG content
+ var loadXML4IE = function(data) {
+ var xml = new ActiveXObject('Microsoft.XMLDOM');
+ xml.validateOnParse = false;
+ xml.resolveExternals = false;
+ xml.async = false;
+ xml.loadXML(data);
+ if (xml.parseError.errorCode != 0) {
+ reportError(xml.parseError.reason);
+ return null;
+ }
+ return xml;
+ };
+ // Load the SVG DOM
+ var loadSVG = function(data) {
+ if (!data) {
+ return;
+ }
+ if (data.documentElement.nodeName != 'svg') {
+ var errors = data.getElementsByTagName('parsererror');
+ var messages = (errors.length ? errors[0].getElementsByTagName('div') : []); // Safari
+ reportError(!errors.length ? '???' :
+ (messages.length ? messages[0] : errors[0]).firstChild.nodeValue);
+ return;
+ }
+ var parent = (settings.parent ? $(settings.parent)[0] : wrapper._svg);
+ var attrs = {};
+ for (var i = 0; i < data.documentElement.attributes.length; i++) {
+ var attr = data.documentElement.attributes.item(i);
+ if (!(attr.nodeName == 'version' || attr.nodeName.substring(0, 5) == 'xmlns')) {
+ attrs[attr.nodeName] = attr.nodeValue;
+ }
+ }
+ wrapper.configure(parent, attrs, !settings.parent);
+ var nodes = data.documentElement.childNodes;
+ for (var i = 0; i < nodes.length; i++) {
+ try {
+ if ($.svg._renesis) {
+ throw 'Force traversal';
+ }
+ parent.appendChild(wrapper._svg.ownerDocument.importNode(nodes[i], true));
+ if (nodes[i].nodeName == 'script') {
+ $.globalEval(nodes[i].textContent);
+ }
+ }
+ catch (e) {
+ wrapper.add(parent, nodes[i]);
+ }
+ }
+ if (!settings.changeSize) {
+ wrapper.configure(parent, {width: size[0], height: size[1]});
+ }
+ if (settings.onLoad) {
+ settings.onLoad.apply(wrapper._container || wrapper._svg, [wrapper]);
+ }
+ };
+ if (url.match('<svg')) { // Inline SVG
+ loadSVG($.browser.msie ? loadXML4IE(url) :
+ new DOMParser().parseFromString(url, 'text/xml'));
+ }
+ else { // Remote SVG
+ $.ajax({url: url, dataType: ($.browser.msie ? 'text' : 'xml'),
+ success: function(xml) {
+ loadSVG($.browser.msie ? loadXML4IE(xml) : xml);
+ }, error: function(http, message, exc) {
+ reportError(message + (exc ? ' ' + exc.message : ''));
+ }});
+ }
+ return this;
+ },
+
+ /* Delete a specified node.
+ @param node (element or jQuery) the drawing node to remove
+ @return (SVGWrapper) this root */
+ remove: function(node) {
+ node = (node.jquery ? node[0] : node);
+ node.parentNode.removeChild(node);
+ return this;
+ },
+
+ /* Delete everything in the current document.
+ @param attrsToo (boolean) true to clear any root attributes as well,
+ false to leave them (optional)
+ @return (SVGWrapper) this root */
+ clear: function(attrsToo) {
+ if (attrsToo) {
+ this.configure({}, true);
+ }
+ while (this._svg.firstChild) {
+ this._svg.removeChild(this._svg.firstChild);
+ }
+ return this;
+ },
+
+ /* Serialise the current diagram into an SVG text document.
+ @param node (SVG element) the starting node (optional)
+ @return (string) the SVG as text */
+ toSVG: function(node) {
+ node = node || this._svg;
+ return (typeof XMLSerializer == 'undefined' ? this._toSVG(node) :
+ new XMLSerializer().serializeToString(node));
+ },
+
+ /* Serialise one node in the SVG hierarchy. */
+ _toSVG: function(node) {
+ var svgDoc = '';
+ if (!node) {
+ return svgDoc;
+ }
+ if (node.nodeType == 3) { // Text
+ svgDoc = node.nodeValue;
+ }
+ else if (node.nodeType == 4) { // CDATA
+ svgDoc = '<![CDATA[' + node.nodeValue + ']]>';
+ }
+ else { // Element
+ svgDoc = '<' + node.nodeName;
+ if (node.attributes) {
+ for (var i = 0; i < node.attributes.length; i++) {
+ var attr = node.attributes.item(i);
+ if (!($.trim(attr.nodeValue) == '' || attr.nodeValue.match(/^\[object/) ||
+ attr.nodeValue.match(/^function/))) {
+ svgDoc += ' ' + (attr.namespaceURI == $.svg.xlinkNS ? 'xlink:' : '') +
+ attr.nodeName + '="' + attr.nodeValue + '"';
+ }
+ }
+ }
+ if (node.firstChild) {
+ svgDoc += '>';
+ var child = node.firstChild;
+ while (child) {
+ svgDoc += this._toSVG(child);
+ child = child.nextSibling;
+ }
+ svgDoc += '</' + node.nodeName + '>';
+ }
+ else {
+ svgDoc += '/>';
+ }
+ }
+ return svgDoc;
+ }
+});
+
+/* Helper to generate an SVG path.
+ Obtain an instance from the SVGWrapper object.
+ String calls together to generate the path and use its value:
+ var path = root.createPath();
+ root.path(null, path.move(100, 100).line(300, 100).line(200, 300).close(), {fill: 'red'});
+ or
+ root.path(null, path.move(100, 100).line([[300, 100], [200, 300]]).close(), {fill: 'red'}); */
+function SVGPath() {
+ this._path = '';
+}
+
+$.extend(SVGPath.prototype, {
+ /* Prepare to create a new path.
+ @return (SVGPath) this path */
+ reset: function() {
+ this._path = '';
+ return this;
+ },
+
+ /* Move the pointer to a position.
+ @param x (number) x-coordinate to move to or
+ (number[][]) x-/y-coordinates to move to
+ @param y (number) y-coordinate to move to (omitted if x is array)
+ @param relative (boolean) true for coordinates relative to the current point,
+ false for coordinates being absolute
+ @return (SVGPath) this path */
+ move: function(x, y, relative) {
+ relative = (isArray(x) ? y : relative);
+ return this._coords((relative ? 'm' : 'M'), x, y);
+ },
+
+ /* Draw a line to a position.
+ @param x (number) x-coordinate to move to or
+ (number[][]) x-/y-coordinates to move to
+ @param y (number) y-coordinate to move to (omitted if x is array)
+ @param relative (boolean) true for coordinates relative to the current point,
+ false for coordinates being absolute
+ @return (SVGPath) this path */
+ line: function(x, y, relative) {
+ relative = (isArray(x) ? y : relative);
+ return this._coords((relative ? 'l' : 'L'), x, y);
+ },
+
+ /* Draw a horizontal line to a position.
+ @param x (number) x-coordinate to draw to or
+ (number[]) x-coordinates to draw to
+ @param relative (boolean) true for coordinates relative to the current point,
+ false for coordinates being absolute
+ @return (SVGPath) this path */
+ horiz: function(x, relative) {
+ this._path += (relative ? 'h' : 'H') + (isArray(x) ? x.join(' ') : x);
+ return this;
+ },
+
+ /* Draw a vertical line to a position.
+ @param y (number) y-coordinate to draw to or
+ (number[]) y-coordinates to draw to
+ @param relative (boolean) true for coordinates relative to the current point,
+ false for coordinates being absolute
+ @return (SVGPath) this path */
+ vert: function(y, relative) {
+ this._path += (relative ? 'v' : 'V') + (isArray(y) ? y.join(' ') : y);
+ return this;
+ },
+
+ /* Draw a cubic Bézier curve.
+ @param x1 (number) x-coordinate of beginning control point or
+ (number[][]) x-/y-coordinates of control and end points to draw to
+ @param y1 (number) y-coordinate of beginning control point (omitted if x1 is array)
+ @param x2 (number) x-coordinate of ending control point (omitted if x1 is array)
+ @param y2 (number) y-coordinate of ending control point (omitted if x1 is array)
+ @param x (number) x-coordinate of curve end (omitted if x1 is array)
+ @param y (number) y-coordinate of curve end (omitted if x1 is array)
+ @param relative (boolean) true for coordinates relative to the current point,
+ false for coordinates being absolute
+ @return (SVGPath) this path */
+ curveC: function(x1, y1, x2, y2, x, y, relative) {
+ relative = (isArray(x1) ? y1 : relative);
+ return this._coords((relative ? 'c' : 'C'), x1, y1, x2, y2, x, y);
+ },
+
+ /* Continue a cubic Bézier curve.
+ Starting control point is the reflection of the previous end control point.
+ @param x2 (number) x-coordinate of ending control point or
+ (number[][]) x-/y-coordinates of control and end points to draw to
+ @param y2 (number) y-coordinate of ending control point (omitted if x2 is array)
+ @param x (number) x-coordinate of curve end (omitted if x2 is array)
+ @param y (number) y-coordinate of curve end (omitted if x2 is array)
+ @param relative (boolean) true for coordinates relative to the current point,
+ false for coordinates being absolute
+ @return (SVGPath) this path */
+ smoothC: function(x2, y2, x, y, relative) {
+ relative = (isArray(x2) ? y2 : relative);
+ return this._coords((relative ? 's' : 'S'), x2, y2, x, y);
+ },
+
+ /* Draw a quadratic Bézier curve.
+ @param x1 (number) x-coordinate of control point or
+ (number[][]) x-/y-coordinates of control and end points to draw to
+ @param y1 (number) y-coordinate of control point (omitted if x1 is array)
+ @param x (number) x-coordinate of curve end (omitted if x1 is array)
+ @param y (number) y-coordinate of curve end (omitted if x1 is array)
+ @param relative (boolean) true for coordinates relative to the current point,
+ false for coordinates being absolute
+ @return (SVGPath) this path */
+ curveQ: function(x1, y1, x, y, relative) {
+ relative = (isArray(x1) ? y1 : relative);
+ return this._coords((relative ? 'q' : 'Q'), x1, y1, x, y);
+ },
+
+ /* Continue a quadratic Bézier curve.
+ Control point is the reflection of the previous control point.
+ @param x (number) x-coordinate of curve end or
+ (number[][]) x-/y-coordinates of points to draw to
+ @param y (number) y-coordinate of curve end (omitted if x is array)
+ @param relative (boolean) true for coordinates relative to the current point,
+ false for coordinates being absolute
+ @return (SVGPath) this path */
+ smoothQ: function(x, y, relative) {
+ relative = (isArray(x) ? y : relative);
+ return this._coords((relative ? 't' : 'T'), x, y);
+ },
+
+ /* Generate a path command with (a list of) coordinates. */
+ _coords: function(cmd, x1, y1, x2, y2, x3, y3) {
+ if (isArray(x1)) {
+ for (var i = 0; i < x1.length; i++) {
+ var cs = x1[i];
+ this._path += (i == 0 ? cmd : ' ') + cs[0] + ',' + cs[1] +
+ (cs.length < 4 ? '' : ' ' + cs[2] + ',' + cs[3] +
+ (cs.length < 6 ? '': ' ' + cs[4] + ',' + cs[5]));
+ }
+ }
+ else {
+ this._path += cmd + x1 + ',' + y1 +
+ (x2 == null ? '' : ' ' + x2 + ',' + y2 +
+ (x3 == null ? '' : ' ' + x3 + ',' + y3));
+ }
+ return this;
+ },
+
+ /* Draw an arc to a position.
+ @param rx (number) x-radius of arc or
+ (number/boolean[][]) x-/y-coordinates and flags for points to draw to
+ @param ry (number) y-radius of arc (omitted if rx is array)
+ @param xRotate (number) x-axis rotation (degrees, clockwise) (omitted if rx is array)
+ @param large (boolean) true to draw the large part of the arc,
+ false to draw the small part (omitted if rx is array)
+ @param clockwise (boolean) true to draw the clockwise arc,
+ false to draw the anti-clockwise arc (omitted if rx is array)
+ @param x (number) x-coordinate of arc end (omitted if rx is array)
+ @param y (number) y-coordinate of arc end (omitted if rx is array)
+ @param relative (boolean) true for coordinates relative to the current point,
+ false for coordinates being absolute
+ @return (SVGPath) this path */
+ arc: function(rx, ry, xRotate, large, clockwise, x, y, relative) {
+ relative = (isArray(rx) ? ry : relative);
+ this._path += (relative ? 'a' : 'A');
+ if (isArray(rx)) {
+ for (var i = 0; i < rx.length; i++) {
+ var cs = rx[i];
+ this._path += (i == 0 ? '' : ' ') + cs[0] + ',' + cs[1] + ' ' +
+ cs[2] + ' ' + (cs[3] ? '1' : '0') + ',' +
+ (cs[4] ? '1' : '0') + ' ' + cs[5] + ',' + cs[6];
+ }
+ }
+ else {
+ this._path += rx + ',' + ry + ' ' + xRotate + ' ' +
+ (large ? '1' : '0') + ',' + (clockwise ? '1' : '0') + ' ' + x + ',' + y;
+ }
+ return this;
+ },
+
+ /* Close the current path.
+ @return (SVGPath) this path */
+ close: function() {
+ this._path += 'z';
+ return this;
+ },
+
+ /* Return the string rendering of the specified path.
+ @return (string) stringified path */
+ path: function() {
+ return this._path;
+ }
+});
+
+SVGPath.prototype.moveTo = SVGPath.prototype.move;
+SVGPath.prototype.lineTo = SVGPath.prototype.line;
+SVGPath.prototype.horizTo = SVGPath.prototype.horiz;
+SVGPath.prototype.vertTo = SVGPath.prototype.vert;
+SVGPath.prototype.curveCTo = SVGPath.prototype.curveC;
+SVGPath.prototype.smoothCTo = SVGPath.prototype.smoothC;
+SVGPath.prototype.curveQTo = SVGPath.prototype.curveQ;
+SVGPath.prototype.smoothQTo = SVGPath.prototype.smoothQ;
+SVGPath.prototype.arcTo = SVGPath.prototype.arc;
+
+/* Helper to generate an SVG text object.
+ Obtain an instance from the SVGWrapper object.
+ String calls together to generate the text and use its value:
+ var text = root.createText();
+ root.text(null, x, y, text.string('This is ').
+ span('red', {fill: 'red'}).string('!'), {fill: 'blue'}); */
+function SVGText() {
+ this._parts = []; // The components of the text object
+}
+
+$.extend(SVGText.prototype, {
+ /* Prepare to create a new text object.
+ @return (SVGText) this text */
+ reset: function() {
+ this._parts = [];
+ return this;
+ },
+
+ /* Add a straight string value.
+ @param value (string) the actual text
+ @return (SVGText) this text object */
+ string: function(value) {
+ this._parts[this._parts.length] = ['text', value];
+ return this;
+ },
+
+ /* Add a separate text span that has its own settings.
+ @param value (string) the actual text
+ @param settings (object) the settings for this text
+ @return (SVGText) this text object */
+ span: function(value, settings) {
+ this._parts[this._parts.length] = ['tspan', value, settings];
+ return this;
+ },
+
+ /* Add a reference to a previously defined text string.
+ @param id (string) the ID of the actual text
+ @param settings (object) the settings for this text
+ @return (SVGText) this text object */
+ ref: function(id, settings) {
+ this._parts[this._parts.length] = ['tref', id, settings];
+ return this;
+ },
+
+ /* Add text drawn along a path.
+ @param id (string) the ID of the path
+ @param value (string) the actual text
+ @param settings (object) the settings for this text
+ @return (SVGText) this text object */
+ path: function(id, value, settings) {
+ this._parts[this._parts.length] = ['textpath', value,
+ $.extend({href: id}, settings || {})];
+ return this;
+ }
+});
+
+/* Attach the SVG functionality to a jQuery selection.
+ @param command (string) the command to run (optional, default 'attach')
+ @param options (object) the new settings to use for these SVG instances
+ @return jQuery (object) for chaining further calls */
+$.fn.svg = function(options) {
+ var otherArgs = Array.prototype.slice.call(arguments, 1);
+ if (typeof options == 'string' && options == 'get') {
+ return $.svg['_' + options + 'SVG'].apply($.svg, [this[0]].concat(otherArgs));
+ }
+ return this.each(function() {
+ if (typeof options == 'string') {
+ $.svg['_' + options + 'SVG'].apply($.svg, [this].concat(otherArgs));
+ }
+ else {
+ $.svg._attachSVG(this, options || {});
+ }
+ });
+};
+
+/* Determine whether an object is an array. */
+function isArray(a) {
+ return (a && a.constructor == Array);
+}
+
+// Singleton primary SVG interface
+$.svg = new SVGManager();
+
+})(jQuery);
diff --git a/js/jquery/jquery.tablesorter.js b/js/jquery/jquery.tablesorter.js
new file mode 100644
index 0000000000..909d947425
--- /dev/null
+++ b/js/jquery/jquery.tablesorter.js
@@ -0,0 +1,1033 @@
+/*
+ *
+ * TableSorter 2.0 - Client-side table sorting with ease!
+ * Version 2.0.5b
+ * @requires jQuery v1.2.3
+ *
+ * Copyright (c) 2007 Christian Bach
+ * Examples and docs at: http://tablesorter.com
+ * Dual licensed under the MIT and GPL licenses:
+ * http://www.opensource.org/licenses/mit-license.php
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ */
+/**
+ *
+ * @description Create a sortable table with multi-column sorting capabilitys
+ *
+ * @example $('table').tablesorter();
+ * @desc Create a simple tablesorter interface.
+ *
+ * @example $('table').tablesorter({ sortList:[[0,0],[1,0]] });
+ * @desc Create a tablesorter interface and sort on the first and secound column column headers.
+ *
+ * @example $('table').tablesorter({ headers: { 0: { sorter: false}, 1: {sorter: false} } });
+ *
+ * @desc Create a tablesorter interface and disableing the first and second column headers.
+ *
+ *
+ * @example $('table').tablesorter({ headers: { 0: {sorter:"integer"}, 1: {sorter:"currency"} } });
+ *
+ * @desc Create a tablesorter interface and set a column parser for the first
+ * and second column.
+ *
+ *
+ * @param Object
+ * settings An object literal containing key/value pairs to provide
+ * optional settings.
+ *
+ *
+ * @option String cssHeader (optional) A string of the class name to be appended
+ * to sortable tr elements in the thead of the table. Default value:
+ * "header"
+ *
+ * @option String cssAsc (optional) A string of the class name to be appended to
+ * sortable tr elements in the thead on a ascending sort. Default value:
+ * "headerSortUp"
+ *
+ * @option String cssDesc (optional) A string of the class name to be appended
+ * to sortable tr elements in the thead on a descending sort. Default
+ * value: "headerSortDown"
+ *
+ * @option String sortInitialOrder (optional) A string of the inital sorting
+ * order can be asc or desc. Default value: "asc"
+ *
+ * @option String sortMultisortKey (optional) A string of the multi-column sort
+ * key. Default value: "shiftKey"
+ *
+ * @option String textExtraction (optional) A string of the text-extraction
+ * method to use. For complex html structures inside td cell set this
+ * option to "complex", on large tables the complex option can be slow.
+ * Default value: "simple"
+ *
+ * @option Object headers (optional) An array containing the forces sorting
+ * rules. This option let's you specify a default sorting rule. Default
+ * value: null
+ *
+ * @option Array sortList (optional) An array containing the forces sorting
+ * rules. This option let's you specify a default sorting rule. Default
+ * value: null
+ *
+ * @option Array sortForce (optional) An array containing forced sorting rules.
+ * This option let's you specify a default sorting rule, which is
+ * prepended to user-selected rules. Default value: null
+ *
+ * @option Boolean sortLocaleCompare (optional) Boolean flag indicating whatever
+ * to use String.localeCampare method or not. Default set to true.
+ *
+ *
+ * @option Array sortAppend (optional) An array containing forced sorting rules.
+ * This option let's you specify a default sorting rule, which is
+ * appended to user-selected rules. Default value: null
+ *
+ * @option Boolean widthFixed (optional) Boolean flag indicating if tablesorter
+ * should apply fixed widths to the table columns. This is usefull when
+ * using the pager companion plugin. This options requires the dimension
+ * jquery plugin. Default value: false
+ *
+ * @option Boolean cancelSelection (optional) Boolean flag indicating if
+ * tablesorter should cancel selection of the table headers text.
+ * Default value: true
+ *
+ * @option Boolean debug (optional) Boolean flag indicating if tablesorter
+ * should display debuging information usefull for development.
+ *
+ * @type jQuery
+ *
+ * @name tablesorter
+ *
+ * @cat Plugins/Tablesorter
+ *
+ * @author Christian Bach/christian.bach@polyester.se
+ */
+
+(function ($) {
+ $.extend({
+ tablesorter: new
+ function () {
+
+ var parsers = [],
+ widgets = [];
+
+ this.defaults = {
+ cssHeader: "header",
+ cssAsc: "headerSortUp",
+ cssDesc: "headerSortDown",
+ cssChildRow: "expand-child",
+ sortInitialOrder: "asc",
+ sortMultiSortKey: "shiftKey",
+ sortForce: null,
+ sortAppend: null,
+ sortLocaleCompare: true,
+ textExtraction: "simple",
+ parsers: {}, widgets: [],
+ widgetZebra: {
+ css: ["even", "odd"]
+ }, headers: {}, widthFixed: false,
+ cancelSelection: true,
+ sortList: [],
+ headerList: [],
+ dateFormat: "us",
+ decimal: '/\.|\,/g',
+ onRenderHeader: null,
+ selectorHeaders: 'thead th',
+ debug: false
+ };
+
+ /* debuging utils */
+
+ function benchmark(s, d) {
+ log(s + "," + (new Date().getTime() - d.getTime()) + "ms");
+ }
+
+ this.benchmark = benchmark;
+
+ function log(s) {
+ if (typeof console != "undefined" && typeof console.debug != "undefined") {
+ console.log(s);
+ } else {
+ alert(s);
+ }
+ }
+
+ /* parsers utils */
+
+ function buildParserCache(table, $headers) {
+
+ if (table.config.debug) {
+ var parsersDebug = "";
+ }
+
+ if (table.tBodies.length == 0) return; // In the case of empty tables
+ var rows = table.tBodies[0].rows;
+
+ if (rows[0]) {
+
+ var list = [],
+ cells = rows[0].cells,
+ l = cells.length;
+
+ for (var i = 0; i < l; i++) {
+
+ var p = false;
+
+ if ($.metadata && ($($headers[i]).metadata() && $($headers[i]).metadata().sorter)) {
+
+ p = getParserById($($headers[i]).metadata().sorter);
+
+ } else if ((table.config.headers[i] && table.config.headers[i].sorter)) {
+
+ p = getParserById(table.config.headers[i].sorter);
+ }
+ if (!p) {
+
+ p = detectParserForColumn(table, rows, -1, i);
+ }
+
+ if (table.config.debug) {
+ parsersDebug += "column:" + i + " parser:" + p.id + "\n";
+ }
+
+ list.push(p);
+ }
+ }
+
+ if (table.config.debug) {
+ log(parsersDebug);
+ }
+
+ return list;
+ };
+
+ function detectParserForColumn(table, rows, rowIndex, cellIndex) {
+ var l = parsers.length,
+ node = false,
+ nodeValue = false,
+ keepLooking = true;
+ while (nodeValue == '' && keepLooking) {
+ rowIndex++;
+ if (rows[rowIndex]) {
+ node = getNodeFromRowAndCellIndex(rows, rowIndex, cellIndex);
+ nodeValue = trimAndGetNodeText(table.config, node);
+ if (table.config.debug) {
+ log('Checking if value was empty on row:' + rowIndex);
+ }
+ } else {
+ keepLooking = false;
+ }
+ }
+ for (var i = 1; i < l; i++) {
+ if (parsers[i].is(nodeValue, table, node)) {
+ return parsers[i];
+ }
+ }
+ // 0 is always the generic parser (text)
+ return parsers[0];
+ }
+
+ function getNodeFromRowAndCellIndex(rows, rowIndex, cellIndex) {
+ return rows[rowIndex].cells[cellIndex];
+ }
+
+ function trimAndGetNodeText(config, node) {
+ return $.trim(getElementText(config, node));
+ }
+
+ function getParserById(name) {
+ var l = parsers.length;
+ for (var i = 0; i < l; i++) {
+ if (parsers[i].id.toLowerCase() == name.toLowerCase()) {
+ return parsers[i];
+ }
+ }
+ return false;
+ }
+
+ /* utils */
+
+ function buildCache(table) {
+
+ if (table.config.debug) {
+ var cacheTime = new Date();
+ }
+
+ var totalRows = (table.tBodies[0] && table.tBodies[0].rows.length) || 0,
+ totalCells = (table.tBodies[0].rows[0] && table.tBodies[0].rows[0].cells.length) || 0,
+ parsers = table.config.parsers,
+ cache = {
+ row: [],
+ normalized: []
+ };
+
+ for (var i = 0; i < totalRows; ++i) {
+
+ /** Add the table data to main data array */
+ var c = $(table.tBodies[0].rows[i]),
+ cols = [];
+
+ // if this is a child row, add it to the last row's children and
+ // continue to the next row
+ if (c.hasClass(table.config.cssChildRow)) {
+ cache.row[cache.row.length - 1] = cache.row[cache.row.length - 1].add(c);
+ // go to the next for loop
+ continue;
+ }
+
+ cache.row.push(c);
+
+ for (var j = 0; j < totalCells; ++j) {
+ cols.push(parsers[j].format(getElementText(table.config, c[0].cells[j]), table, c[0].cells[j]));
+ }
+
+ cols.push(cache.normalized.length); // add position for rowCache
+ cache.normalized.push(cols);
+ cols = null;
+ };
+
+ if (table.config.debug) {
+ benchmark("Building cache for " + totalRows + " rows:", cacheTime);
+ }
+
+ return cache;
+ };
+
+ function getElementText(config, node) {
+
+ var text = "";
+
+ if (!node) return "";
+
+ if (!config.supportsTextContent) config.supportsTextContent = node.textContent || false;
+
+ if (config.textExtraction == "simple") {
+ if (config.supportsTextContent) {
+ text = node.textContent;
+ } else {
+ if (node.childNodes[0] && node.childNodes[0].hasChildNodes()) {
+ text = node.childNodes[0].innerHTML;
+ } else {
+ text = node.innerHTML;
+ }
+ }
+ } else {
+ if (typeof(config.textExtraction) == "function") {
+ text = config.textExtraction(node);
+ } else {
+ text = $(node).text();
+ }
+ }
+ return text;
+ }
+
+ function appendToTable(table, cache) {
+
+ if (table.config.debug) {
+ var appendTime = new Date()
+ }
+
+ var c = cache,
+ r = c.row,
+ n = c.normalized,
+ totalRows = n.length,
+ checkCell = (n[0].length - 1),
+ tableBody = $(table.tBodies[0]),
+ rows = [];
+
+
+ for (var i = 0; i < totalRows; i++) {
+ var pos = n[i][checkCell];
+
+ rows.push(r[pos]);
+
+ if (!table.config.appender) {
+
+ //var o = ;
+ var l = r[pos].length;
+ for (var j = 0; j < l; j++) {
+ tableBody[0].appendChild(r[pos][j]);
+ }
+
+ //
+ }
+ }
+
+
+
+ if (table.config.appender) {
+
+ table.config.appender(table, rows);
+ }
+
+ rows = null;
+
+ if (table.config.debug) {
+ benchmark("Rebuilt table:", appendTime);
+ }
+
+ // apply table widgets
+ applyWidget(table);
+
+ // trigger sortend
+ setTimeout(function () {
+ $(table).trigger("sortEnd");
+ }, 0);
+
+ };
+
+ function buildHeaders(table) {
+
+ if (table.config.debug) {
+ var time = new Date();
+ }
+
+ var meta = ($.metadata) ? true : false;
+
+ var header_index = computeTableHeaderCellIndexes(table);
+
+ $tableHeaders = $(table.config.selectorHeaders, table).each(function (index) {
+
+ this.column = header_index[this.parentNode.rowIndex + "-" + this.cellIndex];
+ // this.column = index;
+ this.order = formatSortingOrder(table.config.sortInitialOrder);
+
+
+ this.count = this.order;
+
+ if (checkHeaderMetadata(this) || checkHeaderOptions(table, index)) this.sortDisabled = true;
+ if (checkHeaderOptionsSortingLocked(table, index)) this.order = this.lockedOrder = checkHeaderOptionsSortingLocked(table, index);
+
+ if (!this.sortDisabled) {
+ var $th = $(this).addClass(table.config.cssHeader);
+ if (table.config.onRenderHeader) table.config.onRenderHeader.apply($th);
+ }
+
+ // add cell to headerList
+ table.config.headerList[index] = this;
+ });
+
+ if (table.config.debug) {
+ benchmark("Built headers:", time);
+ log($tableHeaders);
+ }
+
+ return $tableHeaders;
+
+ };
+
+ // from:
+ // http://www.javascripttoolbox.com/lib/table/examples.php
+ // http://www.javascripttoolbox.com/temp/table_cellindex.html
+
+
+ function computeTableHeaderCellIndexes(t) {
+ var matrix = [];
+ var lookup = {};
+ var thead = t.getElementsByTagName('THEAD')[0];
+ var trs = thead.getElementsByTagName('TR');
+
+ for (var i = 0; i < trs.length; i++) {
+ var cells = trs[i].cells;
+ for (var j = 0; j < cells.length; j++) {
+ var c = cells[j];
+
+ var rowIndex = c.parentNode.rowIndex;
+ var cellId = rowIndex + "-" + c.cellIndex;
+ var rowSpan = c.rowSpan || 1;
+ var colSpan = c.colSpan || 1
+ var firstAvailCol;
+ if (typeof(matrix[rowIndex]) == "undefined") {
+ matrix[rowIndex] = [];
+ }
+ // Find first available column in the first row
+ for (var k = 0; k < matrix[rowIndex].length + 1; k++) {
+ if (typeof(matrix[rowIndex][k]) == "undefined") {
+ firstAvailCol = k;
+ break;
+ }
+ }
+ lookup[cellId] = firstAvailCol;
+ for (var k = rowIndex; k < rowIndex + rowSpan; k++) {
+ if (typeof(matrix[k]) == "undefined") {
+ matrix[k] = [];
+ }
+ var matrixrow = matrix[k];
+ for (var l = firstAvailCol; l < firstAvailCol + colSpan; l++) {
+ matrixrow[l] = "x";
+ }
+ }
+ }
+ }
+ return lookup;
+ }
+
+ function checkCellColSpan(table, rows, row) {
+ var arr = [],
+ r = table.tHead.rows,
+ c = r[row].cells;
+
+ for (var i = 0; i < c.length; i++) {
+ var cell = c[i];
+
+ if (cell.colSpan > 1) {
+ arr = arr.concat(checkCellColSpan(table, headerArr, row++));
+ } else {
+ if (table.tHead.length == 1 || (cell.rowSpan > 1 || !r[row + 1])) {
+ arr.push(cell);
+ }
+ // headerArr[row] = (i+row);
+ }
+ }
+ return arr;
+ };
+
+ function checkHeaderMetadata(cell) {
+ if (($.metadata) && ($(cell).metadata().sorter === false)) {
+ return true;
+ };
+ return false;
+ }
+
+ function checkHeaderOptions(table, i) {
+ if ((table.config.headers[i]) && (table.config.headers[i].sorter === false)) {
+ return true;
+ };
+ return false;
+ }
+
+ function checkHeaderOptionsSortingLocked(table, i) {
+ if ((table.config.headers[i]) && (table.config.headers[i].lockedOrder)) return table.config.headers[i].lockedOrder;
+ return false;
+ }
+
+ function applyWidget(table) {
+ var c = table.config.widgets;
+ var l = c.length;
+ for (var i = 0; i < l; i++) {
+
+ getWidgetById(c[i]).format(table);
+ }
+
+ }
+
+ function getWidgetById(name) {
+ var l = widgets.length;
+ for (var i = 0; i < l; i++) {
+ if (widgets[i].id.toLowerCase() == name.toLowerCase()) {
+ return widgets[i];
+ }
+ }
+ };
+
+ function formatSortingOrder(v) {
+ if (typeof(v) != "Number") {
+ return (v.toLowerCase() == "desc") ? 1 : 0;
+ } else {
+ return (v == 1) ? 1 : 0;
+ }
+ }
+
+ function isValueInArray(v, a) {
+ var l = a.length;
+ for (var i = 0; i < l; i++) {
+ if (a[i][0] == v) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ function setHeadersCss(table, $headers, list, css) {
+ // remove all header information
+ $headers.removeClass(css[0]).removeClass(css[1]);
+
+ var h = [];
+ $headers.each(function (offset) {
+ if (!this.sortDisabled) {
+ h[this.column] = $(this);
+ }
+ });
+
+ var l = list.length;
+ for (var i = 0; i < l; i++) {
+ h[list[i][0]].addClass(css[list[i][1]]);
+ }
+ }
+
+ function fixColumnWidth(table, $headers) {
+ var c = table.config;
+ if (c.widthFixed) {
+ var colgroup = $('<colgroup>');
+ $("tr:first td", table.tBodies[0]).each(function () {
+ colgroup.append($('<col>').css('width', $(this).width()));
+ });
+ $(table).prepend(colgroup);
+ };
+ }
+
+ function updateHeaderSortCount(table, sortList) {
+ var c = table.config,
+ l = sortList.length;
+ for (var i = 0; i < l; i++) {
+ var s = sortList[i],
+ o = c.headerList[s[0]];
+ o.count = s[1];
+ o.count++;
+ }
+ }
+
+ /* sorting methods */
+
+ function multisort(table, sortList, cache) {
+
+ if (table.config.debug) {
+ var sortTime = new Date();
+ }
+
+ var dynamicExp = "var sortWrapper = function(a,b) {",
+ l = sortList.length;
+
+ // TODO: inline functions.
+ for (var i = 0; i < l; i++) {
+
+ var c = sortList[i][0];
+ var order = sortList[i][1];
+ // var s = (getCachedSortType(table.config.parsers,c) == "text") ?
+ // ((order == 0) ? "sortText" : "sortTextDesc") : ((order == 0) ?
+ // "sortNumeric" : "sortNumericDesc");
+ // var s = (table.config.parsers[c].type == "text") ? ((order == 0)
+ // ? makeSortText(c) : makeSortTextDesc(c)) : ((order == 0) ?
+ // makeSortNumeric(c) : makeSortNumericDesc(c));
+ var s = (table.config.parsers[c].type == "text") ? ((order == 0) ? makeSortFunction("text", "asc", c) : makeSortFunction("text", "desc", c)) : ((order == 0) ? makeSortFunction("numeric", "asc", c) : makeSortFunction("numeric", "desc", c));
+ var e = "e" + i;
+
+ dynamicExp += "var " + e + " = " + s; // + "(a[" + c + "],b[" + c
+ // + "]); ";
+ dynamicExp += "if(" + e + ") { return " + e + "; } ";
+ dynamicExp += "else { ";
+
+ }
+
+ // if value is the same keep orignal order
+ var orgOrderCol = cache.normalized[0].length - 1;
+ dynamicExp += "return a[" + orgOrderCol + "]-b[" + orgOrderCol + "];";
+
+ for (var i = 0; i < l; i++) {
+ dynamicExp += "}; ";
+ }
+
+ dynamicExp += "return 0; ";
+ dynamicExp += "}; ";
+
+ if (table.config.debug) {
+ benchmark("Evaling expression:" + dynamicExp, new Date());
+ }
+
+ eval(dynamicExp);
+
+ cache.normalized.sort(sortWrapper);
+
+ if (table.config.debug) {
+ benchmark("Sorting on " + sortList.toString() + " and dir " + order + " time:", sortTime);
+ }
+
+ return cache;
+ };
+
+ function makeSortFunction(type, direction, index) {
+ var a = "a[" + index + "]",
+ b = "b[" + index + "]";
+ if (type == 'text' && direction == 'asc') {
+ return "(" + a + " == " + b + " ? 0 : (" + a + " === null ? Number.POSITIVE_INFINITY : (" + b + " === null ? Number.NEGATIVE_INFINITY : (" + a + " < " + b + ") ? -1 : 1 )));";
+ } else if (type == 'text' && direction == 'desc') {
+ return "(" + a + " == " + b + " ? 0 : (" + a + " === null ? Number.POSITIVE_INFINITY : (" + b + " === null ? Number.NEGATIVE_INFINITY : (" + b + " < " + a + ") ? -1 : 1 )));";
+ } else if (type == 'numeric' && direction == 'asc') {
+ return "(" + a + " === null && " + b + " === null) ? 0 :(" + a + " === null ? Number.POSITIVE_INFINITY : (" + b + " === null ? Number.NEGATIVE_INFINITY : " + a + " - " + b + "));";
+ } else if (type == 'numeric' && direction == 'desc') {
+ return "(" + a + " === null && " + b + " === null) ? 0 :(" + a + " === null ? Number.POSITIVE_INFINITY : (" + b + " === null ? Number.NEGATIVE_INFINITY : " + b + " - " + a + "));";
+ }
+ };
+
+ function makeSortText(i) {
+ return "((a[" + i + "] < b[" + i + "]) ? -1 : ((a[" + i + "] > b[" + i + "]) ? 1 : 0));";
+ };
+
+ function makeSortTextDesc(i) {
+ return "((b[" + i + "] < a[" + i + "]) ? -1 : ((b[" + i + "] > a[" + i + "]) ? 1 : 0));";
+ };
+
+ function makeSortNumeric(i) {
+ return "a[" + i + "]-b[" + i + "];";
+ };
+
+ function makeSortNumericDesc(i) {
+ return "b[" + i + "]-a[" + i + "];";
+ };
+
+ function sortText(a, b) {
+ if (table.config.sortLocaleCompare) return a.localeCompare(b);
+ return ((a < b) ? -1 : ((a > b) ? 1 : 0));
+ };
+
+ function sortTextDesc(a, b) {
+ if (table.config.sortLocaleCompare) return b.localeCompare(a);
+ return ((b < a) ? -1 : ((b > a) ? 1 : 0));
+ };
+
+ function sortNumeric(a, b) {
+ return a - b;
+ };
+
+ function sortNumericDesc(a, b) {
+ return b - a;
+ };
+
+ function getCachedSortType(parsers, i) {
+ return parsers[i].type;
+ }; /* public methods */
+ this.construct = function (settings) {
+ return this.each(function () {
+ // if no thead or tbody quit.
+ if (!this.tHead || !this.tBodies) return;
+ // declare
+ var $this, $document, $headers, cache, config, shiftDown = 0,
+ sortOrder;
+ // new blank config object
+ this.config = {};
+ // merge and extend.
+ config = $.extend(this.config, $.tablesorter.defaults, settings);
+ // store common expression for speed
+ $this = $(this);
+ // save the settings where they read
+ $.data(this, "tablesorter", config);
+ // build headers
+ $headers = buildHeaders(this);
+ // try to auto detect column type, and store in tables config
+ this.config.parsers = buildParserCache(this, $headers);
+ // build the cache for the tbody cells
+ cache = buildCache(this);
+ // get the css class names, could be done else where.
+ var sortCSS = [config.cssDesc, config.cssAsc];
+ // fixate columns if the users supplies the fixedWidth option
+ fixColumnWidth(this);
+ // apply event handling to headers
+ // this is to big, perhaps break it out?
+ $headers.click(
+
+ function (e) {
+ var totalRows = ($this[0].tBodies[0] && $this[0].tBodies[0].rows.length) || 0;
+ if (!this.sortDisabled && totalRows > 0) {
+ // Only call sortStart if sorting is
+ // enabled.
+ $this.trigger("sortStart");
+ // store exp, for speed
+ var $cell = $(this);
+ // get current column index
+ var i = this.column;
+ // get current column sort order
+ this.order = this.count++ % 2;
+ // always sort on the locked order.
+ if(this.lockedOrder) this.order = this.lockedOrder;
+
+ // user only whants to sort on one
+ // column
+ if (!e[config.sortMultiSortKey]) {
+ // flush the sort list
+ config.sortList = [];
+ if (config.sortForce != null) {
+ var a = config.sortForce;
+ for (var j = 0; j < a.length; j++) {
+ if (a[j][0] != i) {
+ config.sortList.push(a[j]);
+ }
+ }
+ }
+ // add column to sort list
+ config.sortList.push([i, this.order]);
+ // multi column sorting
+ } else {
+ // the user has clicked on an all
+ // ready sortet column.
+ if (isValueInArray(i, config.sortList)) {
+ // revers the sorting direction
+ // for all tables.
+ for (var j = 0; j < config.sortList.length; j++) {
+ var s = config.sortList[j],
+ o = config.headerList[s[0]];
+ if (s[0] == i) {
+ o.count = s[1];
+ o.count++;
+ s[1] = o.count % 2;
+ }
+ }
+ } else {
+ // add column to sort list array
+ config.sortList.push([i, this.order]);
+ }
+ };
+ setTimeout(function () {
+ // set css for headers
+ setHeadersCss($this[0], $headers, config.sortList, sortCSS);
+ appendToTable(
+ $this[0], multisort(
+ $this[0], config.sortList, cache)
+ );
+ }, 1);
+ // stop normal event by returning false
+ return false;
+ }
+ // cancel selection
+ }).mousedown(function () {
+ if (config.cancelSelection) {
+ this.onselectstart = function () {
+ return false
+ };
+ return false;
+ }
+ });
+ // apply easy methods that trigger binded events
+ $this.bind("update", function () {
+ var me = this;
+ setTimeout(function () {
+ // rebuild parsers.
+ me.config.parsers = buildParserCache(
+ me, $headers);
+ // rebuild the cache map
+ cache = buildCache(me);
+ }, 1);
+ }).bind("updateCell", function (e, cell) {
+ var config = this.config;
+ // get position from the dom.
+ var pos = [(cell.parentNode.rowIndex - 1), cell.cellIndex];
+ // update cache
+ cache.normalized[pos[0]][pos[1]] = config.parsers[pos[1]].format(
+ getElementText(config, cell), cell);
+ }).bind("sorton", function (e, list) {
+ $(this).trigger("sortStart");
+ config.sortList = list;
+ // update and store the sortlist
+ var sortList = config.sortList;
+ // update header count index
+ updateHeaderSortCount(this, sortList);
+ // set css for headers
+ setHeadersCss(this, $headers, sortList, sortCSS);
+ // sort the table and append it to the dom
+ appendToTable(this, multisort(this, sortList, cache));
+ }).bind("appendCache", function () {
+ appendToTable(this, cache);
+ }).bind("applyWidgetId", function (e, id) {
+ getWidgetById(id).format(this);
+ }).bind("applyWidgets", function () {
+ // apply widgets
+ applyWidget(this);
+ });
+ if ($.metadata && ($(this).metadata() && $(this).metadata().sortlist)) {
+ config.sortList = $(this).metadata().sortlist;
+ }
+ // if user has supplied a sort list to constructor.
+ if (config.sortList.length > 0) {
+ $this.trigger("sorton", [config.sortList]);
+ } else {
+ // appendToTable used in sorton event already calls applyWidget
+ // apply widgets
+ applyWidget(this);
+ }
+ });
+ };
+ this.addParser = function (parser) {
+ var l = parsers.length,
+ a = true;
+ for (var i = 0; i < l; i++) {
+ if (parsers[i].id.toLowerCase() == parser.id.toLowerCase()) {
+ a = false;
+ }
+ }
+ if (a) {
+ parsers.push(parser);
+ };
+ };
+ this.addWidget = function (widget) {
+ widgets.push(widget);
+ };
+ this.formatFloat = function (s) {
+ var i = parseFloat(s);
+ return (isNaN(i)) ? 0 : i;
+ };
+ this.formatInt = function (s) {
+ var i = parseInt(s);
+ return (isNaN(i)) ? 0 : i;
+ };
+ this.isDigit = function (s, config) {
+ // replace all an wanted chars and match.
+ return /^[-+]?\d*$/.test($.trim(s.replace(/[,.']/g, '')));
+ };
+ this.clearTableBody = function (table) {
+ if ($.browser.msie) {
+ function empty() {
+ while (this.firstChild)
+ this.removeChild(this.firstChild);
+ }
+ empty.apply(table.tBodies[0]);
+ } else {
+ table.tBodies[0].innerHTML = "";
+ }
+ };
+ }
+ });
+
+ // extend plugin scope
+ $.fn.extend({
+ tablesorter: $.tablesorter.construct
+ });
+
+ // make shortcut
+ var ts = $.tablesorter;
+
+ // add default parsers
+ ts.addParser({
+ id: "text",
+ is: function (s) {
+ return true;
+ }, format: function (s) {
+ return $.trim(s.toLocaleLowerCase());
+ }, type: "text"
+ });
+
+ ts.addParser({
+ id: "digit",
+ is: function (s, table) {
+ var c = table.config;
+ return $.tablesorter.isDigit(s, c);
+ }, format: function (s) {
+ return $.tablesorter.formatFloat(s);
+ }, type: "numeric"
+ });
+
+ ts.addParser({
+ id: "currency",
+ is: function (s) {
+ return /^[£$€?.]/.test(s);
+ }, format: function (s) {
+ return $.tablesorter.formatFloat(s.replace(new RegExp(/[£$€]/g), ""));
+ }, type: "numeric"
+ });
+
+ ts.addParser({
+ id: "ipAddress",
+ is: function (s) {
+ return /^\d{2,3}[\.]\d{2,3}[\.]\d{2,3}[\.]\d{2,3}$/.test(s);
+ }, format: function (s) {
+ var a = s.split("."),
+ r = "",
+ l = a.length;
+ for (var i = 0; i < l; i++) {
+ var item = a[i];
+ if (item.length == 2) {
+ r += "0" + item;
+ } else {
+ r += item;
+ }
+ }
+ return $.tablesorter.formatFloat(r);
+ }, type: "numeric"
+ });
+
+ ts.addParser({
+ id: "url",
+ is: function (s) {
+ return /^(https?|ftp|file):\/\/$/.test(s);
+ }, format: function (s) {
+ return jQuery.trim(s.replace(new RegExp(/(https?|ftp|file):\/\//), ''));
+ }, type: "text"
+ });
+
+ ts.addParser({
+ id: "isoDate",
+ is: function (s) {
+ return /^\d{4}[\/-]\d{1,2}[\/-]\d{1,2}$/.test(s);
+ }, format: function (s) {
+ return $.tablesorter.formatFloat((s != "") ? new Date(s.replace(
+ new RegExp(/-/g), "/")).getTime() : "0");
+ }, type: "numeric"
+ });
+
+ ts.addParser({
+ id: "percent",
+ is: function (s) {
+ return /\%$/.test($.trim(s));
+ }, format: function (s) {
+ return $.tablesorter.formatFloat(s.replace(new RegExp(/%/g), ""));
+ }, type: "numeric"
+ });
+
+ ts.addParser({
+ id: "usLongDate",
+ is: function (s) {
+ return s.match(new RegExp(/^[A-Za-z]{3,10}\.? [0-9]{1,2}, ([0-9]{4}|'?[0-9]{2}) (([0-2]?[0-9]:[0-5][0-9])|([0-1]?[0-9]:[0-5][0-9]\s(AM|PM)))$/));
+ }, format: function (s) {
+ return $.tablesorter.formatFloat(new Date(s).getTime());
+ }, type: "numeric"
+ });
+
+ ts.addParser({
+ id: "shortDate",
+ is: function (s) {
+ return /\d{1,2}[\/\-]\d{1,2}[\/\-]\d{2,4}/.test(s);
+ }, format: function (s, table) {
+ var c = table.config;
+ s = s.replace(/\-/g, "/");
+ if (c.dateFormat == "us") {
+ // reformat the string in ISO format
+ s = s.replace(/(\d{1,2})[\/\-](\d{1,2})[\/\-](\d{4})/, "$3/$1/$2");
+ } else if (c.dateFormat == "uk") {
+ // reformat the string in ISO format
+ s = s.replace(/(\d{1,2})[\/\-](\d{1,2})[\/\-](\d{4})/, "$3/$2/$1");
+ } else if (c.dateFormat == "dd/mm/yy" || c.dateFormat == "dd-mm-yy") {
+ s = s.replace(/(\d{1,2})[\/\-](\d{1,2})[\/\-](\d{2})/, "$1/$2/$3");
+ }
+ return $.tablesorter.formatFloat(new Date(s).getTime());
+ }, type: "numeric"
+ });
+ ts.addParser({
+ id: "time",
+ is: function (s) {
+ return /^(([0-2]?[0-9]:[0-5][0-9])|([0-1]?[0-9]:[0-5][0-9]\s(am|pm)))$/.test(s);
+ }, format: function (s) {
+ return $.tablesorter.formatFloat(new Date("2000/01/01 " + s).getTime());
+ }, type: "numeric"
+ });
+ ts.addParser({
+ id: "metadata",
+ is: function (s) {
+ return false;
+ }, format: function (s, table, cell) {
+ var c = table.config,
+ p = (!c.parserMetadataName) ? 'sortValue' : c.parserMetadataName;
+ return $(cell).metadata()[p];
+ }, type: "numeric"
+ });
+ // add default widgets
+ ts.addWidget({
+ id: "zebra",
+ format: function (table) {
+ if (table.config.debug) {
+ var time = new Date();
+ }
+ var $tr, row = -1,
+ odd;
+ // loop through the visible rows
+ $("tr:visible", table.tBodies[0]).each(function (i) {
+ $tr = $(this);
+ // style children rows the same way the parent
+ // row was styled
+ if (!$tr.hasClass(table.config.cssChildRow)) row++;
+ odd = (row % 2 == 0);
+ $tr.removeClass(
+ table.config.widgetZebra.css[odd ? 0 : 1]).addClass(
+ table.config.widgetZebra.css[odd ? 1 : 0])
+ });
+ if (table.config.debug) {
+ $.tablesorter.benchmark("Applying Zebra widget", time);
+ }
+ }
+ });
+})(jQuery); \ No newline at end of file
diff --git a/js/jquery/src/README b/js/jquery/src/README
new file mode 100644
index 0000000000..e2cc78cc0f
--- /dev/null
+++ b/js/jquery/src/README
@@ -0,0 +1,9 @@
+jQuery and jQuery UI source
+===========================
+
+This directory contains source code for jQuery and jQuery UI.
+
+phpMyAdmin Developers: When updating the minified/concatened version of
+jQuery one directory back (i.e., in js/jquery/), make certain that the
+material under this directory is the complete, corresponding source for the
+minified/concatenated version.
diff --git a/js/jquery/src/jquery-ui/jquery.ui.accordion.js b/js/jquery/src/jquery-ui/jquery.ui.accordion.js
new file mode 100644
index 0000000000..78c95750c1
--- /dev/null
+++ b/js/jquery/src/jquery-ui/jquery.ui.accordion.js
@@ -0,0 +1,731 @@
+/*!
+ * jQuery UI Accordion @VERSION
+ * http://jqueryui.com
+ *
+ * Copyright 2012 jQuery Foundation and other contributors
+ * Released under the MIT license.
+ * http://jquery.org/license
+ *
+ * http://api.jqueryui.com/accordion/
+ *
+ * Depends:
+ * jquery.ui.core.js
+ * jquery.ui.widget.js
+ */
+(function( $, undefined ) {
+
+var uid = 0,
+ hideProps = {},
+ showProps = {};
+
+hideProps.height = hideProps.paddingTop = hideProps.paddingBottom =
+ hideProps.borderTopWidth = hideProps.borderBottomWidth = "hide";
+showProps.height = showProps.paddingTop = showProps.paddingBottom =
+ showProps.borderTopWidth = showProps.borderBottomWidth = "show";
+
+$.widget( "ui.accordion", {
+ version: "@VERSION",
+ options: {
+ active: 0,
+ animate: {},
+ collapsible: false,
+ event: "click",
+ header: "> li > :first-child,> :not(li):even",
+ heightStyle: "auto",
+ icons: {
+ activeHeader: "ui-icon-triangle-1-s",
+ header: "ui-icon-triangle-1-e"
+ },
+
+ // callbacks
+ activate: null,
+ beforeActivate: null
+ },
+
+ _create: function() {
+ var accordionId = this.accordionId = "ui-accordion-" +
+ (this.element.attr( "id" ) || ++uid),
+ options = this.options;
+
+ this.prevShow = this.prevHide = $();
+ this.element.addClass( "ui-accordion ui-widget ui-helper-reset" );
+
+ this.headers = this.element.find( options.header )
+ .addClass( "ui-accordion-header ui-helper-reset ui-state-default ui-corner-all" );
+ this._hoverable( this.headers );
+ this._focusable( this.headers );
+
+ this.headers.next()
+ .addClass( "ui-accordion-content ui-helper-reset ui-widget-content ui-corner-bottom" )
+ .hide();
+
+ // don't allow collapsible: false and active: false / null
+ if ( !options.collapsible && (options.active === false || options.active == null) ) {
+ options.active = 0;
+ }
+ // handle negative values
+ if ( options.active < 0 ) {
+ options.active += this.headers.length;
+ }
+ this.active = this._findActive( options.active )
+ .addClass( "ui-accordion-header-active ui-state-active" )
+ .toggleClass( "ui-corner-all ui-corner-top" );
+ this.active.next()
+ .addClass( "ui-accordion-content-active" )
+ .show();
+
+ this._createIcons();
+ this.refresh();
+
+ // ARIA
+ this.element.attr( "role", "tablist" );
+
+ this.headers
+ .attr( "role", "tab" )
+ .each(function( i ) {
+ var header = $( this ),
+ headerId = header.attr( "id" ),
+ panel = header.next(),
+ panelId = panel.attr( "id" );
+ if ( !headerId ) {
+ headerId = accordionId + "-header-" + i;
+ header.attr( "id", headerId );
+ }
+ if ( !panelId ) {
+ panelId = accordionId + "-panel-" + i;
+ panel.attr( "id", panelId );
+ }
+ header.attr( "aria-controls", panelId );
+ panel.attr( "aria-labelledby", headerId );
+ })
+ .next()
+ .attr( "role", "tabpanel" );
+
+ this.headers
+ .not( this.active )
+ .attr({
+ "aria-selected": "false",
+ tabIndex: -1
+ })
+ .next()
+ .attr({
+ "aria-expanded": "false",
+ "aria-hidden": "true"
+ })
+ .hide();
+
+ // make sure at least one header is in the tab order
+ if ( !this.active.length ) {
+ this.headers.eq( 0 ).attr( "tabIndex", 0 );
+ } else {
+ this.active.attr({
+ "aria-selected": "true",
+ tabIndex: 0
+ })
+ .next()
+ .attr({
+ "aria-expanded": "true",
+ "aria-hidden": "false"
+ });
+ }
+
+ this._on( this.headers, { keydown: "_keydown" });
+ this._on( this.headers.next(), { keydown: "_panelKeyDown" });
+ this._setupEvents( options.event );
+ },
+
+ _getCreateEventData: function() {
+ return {
+ header: this.active,
+ content: !this.active.length ? $() : this.active.next()
+ };
+ },
+
+ _createIcons: function() {
+ var icons = this.options.icons;
+ if ( icons ) {
+ $( "<span>" )
+ .addClass( "ui-accordion-header-icon ui-icon " + icons.header )
+ .prependTo( this.headers );
+ this.active.children( ".ui-accordion-header-icon" )
+ .removeClass( icons.header )
+ .addClass( icons.activeHeader );
+ this.headers.addClass( "ui-accordion-icons" );
+ }
+ },
+
+ _destroyIcons: function() {
+ this.headers
+ .removeClass( "ui-accordion-icons" )
+ .children( ".ui-accordion-header-icon" )
+ .remove();
+ },
+
+ _destroy: function() {
+ var contents;
+
+ // clean up main element
+ this.element
+ .removeClass( "ui-accordion ui-widget ui-helper-reset" )
+ .removeAttr( "role" );
+
+ // clean up headers
+ this.headers
+ .removeClass( "ui-accordion-header ui-accordion-header-active ui-helper-reset ui-state-default ui-corner-all ui-state-active ui-state-disabled ui-corner-top" )
+ .removeAttr( "role" )
+ .removeAttr( "aria-selected" )
+ .removeAttr( "aria-controls" )
+ .removeAttr( "tabIndex" )
+ .each(function() {
+ if ( /^ui-accordion/.test( this.id ) ) {
+ this.removeAttribute( "id" );
+ }
+ });
+ this._destroyIcons();
+
+ // clean up content panels
+ contents = this.headers.next()
+ .css( "display", "" )
+ .removeAttr( "role" )
+ .removeAttr( "aria-expanded" )
+ .removeAttr( "aria-hidden" )
+ .removeAttr( "aria-labelledby" )
+ .removeClass( "ui-helper-reset ui-widget-content ui-corner-bottom ui-accordion-content ui-accordion-content-active ui-state-disabled" )
+ .each(function() {
+ if ( /^ui-accordion/.test( this.id ) ) {
+ this.removeAttribute( "id" );
+ }
+ });
+ if ( this.options.heightStyle !== "content" ) {
+ contents.css( "height", "" );
+ }
+ },
+
+ _setOption: function( key, value ) {
+ if ( key === "active" ) {
+ // _activate() will handle invalid values and update this.options
+ this._activate( value );
+ return;
+ }
+
+ if ( key === "event" ) {
+ if ( this.options.event ) {
+ this._off( this.headers, this.options.event );
+ }
+ this._setupEvents( value );
+ }
+
+ this._super( key, value );
+
+ // setting collapsible: false while collapsed; open first panel
+ if ( key === "collapsible" && !value && this.options.active === false ) {
+ this._activate( 0 );
+ }
+
+ if ( key === "icons" ) {
+ this._destroyIcons();
+ if ( value ) {
+ this._createIcons();
+ }
+ }
+
+ // #5332 - opacity doesn't cascade to positioned elements in IE
+ // so we need to add the disabled class to the headers and panels
+ if ( key === "disabled" ) {
+ this.headers.add( this.headers.next() )
+ .toggleClass( "ui-state-disabled", !!value );
+ }
+ },
+
+ _keydown: function( event ) {
+ if ( event.altKey || event.ctrlKey ) {
+ return;
+ }
+
+ var keyCode = $.ui.keyCode,
+ length = this.headers.length,
+ currentIndex = this.headers.index( event.target ),
+ toFocus = false;
+
+ switch ( event.keyCode ) {
+ case keyCode.RIGHT:
+ case keyCode.DOWN:
+ toFocus = this.headers[ ( currentIndex + 1 ) % length ];
+ break;
+ case keyCode.LEFT:
+ case keyCode.UP:
+ toFocus = this.headers[ ( currentIndex - 1 + length ) % length ];
+ break;
+ case keyCode.SPACE:
+ case keyCode.ENTER:
+ this._eventHandler( event );
+ break;
+ case keyCode.HOME:
+ toFocus = this.headers[ 0 ];
+ break;
+ case keyCode.END:
+ toFocus = this.headers[ length - 1 ];
+ break;
+ }
+
+ if ( toFocus ) {
+ $( event.target ).attr( "tabIndex", -1 );
+ $( toFocus ).attr( "tabIndex", 0 );
+ toFocus.focus();
+ event.preventDefault();
+ }
+ },
+
+ _panelKeyDown : function( event ) {
+ if ( event.keyCode === $.ui.keyCode.UP && event.ctrlKey ) {
+ $( event.currentTarget ).prev().focus();
+ }
+ },
+
+ refresh: function() {
+ var maxHeight, overflow,
+ heightStyle = this.options.heightStyle,
+ parent = this.element.parent();
+
+
+ if ( heightStyle === "fill" ) {
+ // IE 6 treats height like minHeight, so we need to turn off overflow
+ // in order to get a reliable height
+ // we use the minHeight support test because we assume that only
+ // browsers that don't support minHeight will treat height as minHeight
+ if ( !$.support.minHeight ) {
+ overflow = parent.css( "overflow" );
+ parent.css( "overflow", "hidden");
+ }
+ maxHeight = parent.height();
+ this.element.siblings( ":visible" ).each(function() {
+ var elem = $( this ),
+ position = elem.css( "position" );
+
+ if ( position === "absolute" || position === "fixed" ) {
+ return;
+ }
+ maxHeight -= elem.outerHeight( true );
+ });
+ if ( overflow ) {
+ parent.css( "overflow", overflow );
+ }
+
+ this.headers.each(function() {
+ maxHeight -= $( this ).outerHeight( true );
+ });
+
+ this.headers.next()
+ .each(function() {
+ $( this ).height( Math.max( 0, maxHeight -
+ $( this ).innerHeight() + $( this ).height() ) );
+ })
+ .css( "overflow", "auto" );
+ } else if ( heightStyle === "auto" ) {
+ maxHeight = 0;
+ this.headers.next()
+ .each(function() {
+ maxHeight = Math.max( maxHeight, $( this ).css( "height", "" ).height() );
+ })
+ .height( maxHeight );
+ }
+ },
+
+ _activate: function( index ) {
+ var active = this._findActive( index )[ 0 ];
+
+ // trying to activate the already active panel
+ if ( active === this.active[ 0 ] ) {
+ return;
+ }
+
+ // trying to collapse, simulate a click on the currently active header
+ active = active || this.active[ 0 ];
+
+ this._eventHandler({
+ target: active,
+ currentTarget: active,
+ preventDefault: $.noop
+ });
+ },
+
+ _findActive: function( selector ) {
+ return typeof selector === "number" ? this.headers.eq( selector ) : $();
+ },
+
+ _setupEvents: function( event ) {
+ var events = {};
+ if ( !event ) {
+ return;
+ }
+ $.each( event.split(" "), function( index, eventName ) {
+ events[ eventName ] = "_eventHandler";
+ });
+ this._on( this.headers, events );
+ },
+
+ _eventHandler: function( event ) {
+ var options = this.options,
+ active = this.active,
+ clicked = $( event.currentTarget ),
+ clickedIsActive = clicked[ 0 ] === active[ 0 ],
+ collapsing = clickedIsActive && options.collapsible,
+ toShow = collapsing ? $() : clicked.next(),
+ toHide = active.next(),
+ eventData = {
+ oldHeader: active,
+ oldPanel: toHide,
+ newHeader: collapsing ? $() : clicked,
+ newPanel: toShow
+ };
+
+ event.preventDefault();
+
+ if (
+ // click on active header, but not collapsible
+ ( clickedIsActive && !options.collapsible ) ||
+ // allow canceling activation
+ ( this._trigger( "beforeActivate", event, eventData ) === false ) ) {
+ return;
+ }
+
+ options.active = collapsing ? false : this.headers.index( clicked );
+
+ // when the call to ._toggle() comes after the class changes
+ // it causes a very odd bug in IE 8 (see #6720)
+ this.active = clickedIsActive ? $() : clicked;
+ this._toggle( eventData );
+
+ // switch classes
+ // corner classes on the previously active header stay after the animation
+ active.removeClass( "ui-accordion-header-active ui-state-active" );
+ if ( options.icons ) {
+ active.children( ".ui-accordion-header-icon" )
+ .removeClass( options.icons.activeHeader )
+ .addClass( options.icons.header );
+ }
+
+ if ( !clickedIsActive ) {
+ clicked
+ .removeClass( "ui-corner-all" )
+ .addClass( "ui-accordion-header-active ui-state-active ui-corner-top" );
+ if ( options.icons ) {
+ clicked.children( ".ui-accordion-header-icon" )
+ .removeClass( options.icons.header )
+ .addClass( options.icons.activeHeader );
+ }
+
+ clicked
+ .next()
+ .addClass( "ui-accordion-content-active" );
+ }
+ },
+
+ _toggle: function( data ) {
+ var toShow = data.newPanel,
+ toHide = this.prevShow.length ? this.prevShow : data.oldPanel;
+
+ // handle activating a panel during the animation for another activation
+ this.prevShow.add( this.prevHide ).stop( true, true );
+ this.prevShow = toShow;
+ this.prevHide = toHide;
+
+ if ( this.options.animate ) {
+ this._animate( toShow, toHide, data );
+ } else {
+ toHide.hide();
+ toShow.show();
+ this._toggleComplete( data );
+ }
+
+ toHide.attr({
+ "aria-expanded": "false",
+ "aria-hidden": "true"
+ });
+ toHide.prev().attr( "aria-selected", "false" );
+ // if we're switching panels, remove the old header from the tab order
+ // if we're opening from collapsed state, remove the previous header from the tab order
+ // if we're collapsing, then keep the collapsing header in the tab order
+ if ( toShow.length && toHide.length ) {
+ toHide.prev().attr( "tabIndex", -1 );
+ } else if ( toShow.length ) {
+ this.headers.filter(function() {
+ return $( this ).attr( "tabIndex" ) === 0;
+ })
+ .attr( "tabIndex", -1 );
+ }
+
+ toShow
+ .attr({
+ "aria-expanded": "true",
+ "aria-hidden": "false"
+ })
+ .prev()
+ .attr({
+ "aria-selected": "true",
+ tabIndex: 0
+ });
+ },
+
+ _animate: function( toShow, toHide, data ) {
+ var total, easing, duration,
+ that = this,
+ adjust = 0,
+ down = toShow.length &&
+ ( !toHide.length || ( toShow.index() < toHide.index() ) ),
+ animate = this.options.animate || {},
+ options = down && animate.down || animate,
+ complete = function() {
+ that._toggleComplete( data );
+ };
+
+ if ( typeof options === "number" ) {
+ duration = options;
+ }
+ if ( typeof options === "string" ) {
+ easing = options;
+ }
+ // fall back from options to animation in case of partial down settings
+ easing = easing || options.easing || animate.easing;
+ duration = duration || options.duration || animate.duration;
+
+ if ( !toHide.length ) {
+ return toShow.animate( showProps, duration, easing, complete );
+ }
+ if ( !toShow.length ) {
+ return toHide.animate( hideProps, duration, easing, complete );
+ }
+
+ total = toShow.show().outerHeight();
+ toHide.animate( hideProps, {
+ duration: duration,
+ easing: easing,
+ step: function( now, fx ) {
+ fx.now = Math.round( now );
+ }
+ });
+ toShow
+ .hide()
+ .animate( showProps, {
+ duration: duration,
+ easing: easing,
+ complete: complete,
+ step: function( now, fx ) {
+ fx.now = Math.round( now );
+ if ( fx.prop !== "height" ) {
+ adjust += fx.now;
+ } else if ( that.options.heightStyle !== "content" ) {
+ fx.now = Math.round( total - toHide.outerHeight() - adjust );
+ adjust = 0;
+ }
+ }
+ });
+ },
+
+ _toggleComplete: function( data ) {
+ var toHide = data.oldPanel;
+
+ toHide
+ .removeClass( "ui-accordion-content-active" )
+ .prev()
+ .removeClass( "ui-corner-top" )
+ .addClass( "ui-corner-all" );
+
+ // Work around for rendering bug in IE (#5421)
+ if ( toHide.length ) {
+ toHide.parent()[0].className = toHide.parent()[0].className;
+ }
+
+ this._trigger( "activate", null, data );
+ }
+});
+
+
+
+// DEPRECATED
+if ( $.uiBackCompat !== false ) {
+ // navigation options
+ (function( $, prototype ) {
+ $.extend( prototype.options, {
+ navigation: false,
+ navigationFilter: function() {
+ return this.href.toLowerCase() === location.href.toLowerCase();
+ }
+ });
+
+ var _create = prototype._create;
+ prototype._create = function() {
+ if ( this.options.navigation ) {
+ var that = this,
+ headers = this.element.find( this.options.header ),
+ content = headers.next(),
+ current = headers.add( content )
+ .find( "a" )
+ .filter( this.options.navigationFilter )
+ [ 0 ];
+ if ( current ) {
+ headers.add( content ).each( function( index ) {
+ if ( $.contains( this, current ) ) {
+ that.options.active = Math.floor( index / 2 );
+ return false;
+ }
+ });
+ }
+ }
+ _create.call( this );
+ };
+ }( jQuery, jQuery.ui.accordion.prototype ) );
+
+ // height options
+ (function( $, prototype ) {
+ $.extend( prototype.options, {
+ heightStyle: null, // remove default so we fall back to old values
+ autoHeight: true, // use heightStyle: "auto"
+ clearStyle: false, // use heightStyle: "content"
+ fillSpace: false // use heightStyle: "fill"
+ });
+
+ var _create = prototype._create,
+ _setOption = prototype._setOption;
+
+ $.extend( prototype, {
+ _create: function() {
+ this.options.heightStyle = this.options.heightStyle ||
+ this._mergeHeightStyle();
+
+ _create.call( this );
+ },
+
+ _setOption: function( key ) {
+ if ( key === "autoHeight" || key === "clearStyle" || key === "fillSpace" ) {
+ this.options.heightStyle = this._mergeHeightStyle();
+ }
+ _setOption.apply( this, arguments );
+ },
+
+ _mergeHeightStyle: function() {
+ var options = this.options;
+
+ if ( options.fillSpace ) {
+ return "fill";
+ }
+
+ if ( options.clearStyle ) {
+ return "content";
+ }
+
+ if ( options.autoHeight ) {
+ return "auto";
+ }
+ }
+ });
+ }( jQuery, jQuery.ui.accordion.prototype ) );
+
+ // icon options
+ (function( $, prototype ) {
+ $.extend( prototype.options.icons, {
+ activeHeader: null, // remove default so we fall back to old values
+ headerSelected: "ui-icon-triangle-1-s"
+ });
+
+ var _createIcons = prototype._createIcons;
+ prototype._createIcons = function() {
+ if ( this.options.icons ) {
+ this.options.icons.activeHeader = this.options.icons.activeHeader ||
+ this.options.icons.headerSelected;
+ }
+ _createIcons.call( this );
+ };
+ }( jQuery, jQuery.ui.accordion.prototype ) );
+
+ // expanded active option, activate method
+ (function( $, prototype ) {
+ prototype.activate = prototype._activate;
+
+ var _findActive = prototype._findActive;
+ prototype._findActive = function( index ) {
+ if ( index === -1 ) {
+ index = false;
+ }
+ if ( index && typeof index !== "number" ) {
+ index = this.headers.index( this.headers.filter( index ) );
+ if ( index === -1 ) {
+ index = false;
+ }
+ }
+ return _findActive.call( this, index );
+ };
+ }( jQuery, jQuery.ui.accordion.prototype ) );
+
+ // resize method
+ jQuery.ui.accordion.prototype.resize = jQuery.ui.accordion.prototype.refresh;
+
+ // change events
+ (function( $, prototype ) {
+ $.extend( prototype.options, {
+ change: null,
+ changestart: null
+ });
+
+ var _trigger = prototype._trigger;
+ prototype._trigger = function( type, event, data ) {
+ var ret = _trigger.apply( this, arguments );
+ if ( !ret ) {
+ return false;
+ }
+
+ if ( type === "beforeActivate" ) {
+ ret = _trigger.call( this, "changestart", event, {
+ oldHeader: data.oldHeader,
+ oldContent: data.oldPanel,
+ newHeader: data.newHeader,
+ newContent: data.newPanel
+ });
+ } else if ( type === "activate" ) {
+ ret = _trigger.call( this, "change", event, {
+ oldHeader: data.oldHeader,
+ oldContent: data.oldPanel,
+ newHeader: data.newHeader,
+ newContent: data.newPanel
+ });
+ }
+ return ret;
+ };
+ }( jQuery, jQuery.ui.accordion.prototype ) );
+
+ // animated option
+ // NOTE: this only provides support for "slide", "bounceslide", and easings
+ // not the full $.ui.accordion.animations API
+ (function( $, prototype ) {
+ $.extend( prototype.options, {
+ animate: null,
+ animated: "slide"
+ });
+
+ var _create = prototype._create;
+ prototype._create = function() {
+ var options = this.options;
+ if ( options.animate === null ) {
+ if ( !options.animated ) {
+ options.animate = false;
+ } else if ( options.animated === "slide" ) {
+ options.animate = 300;
+ } else if ( options.animated === "bounceslide" ) {
+ options.animate = {
+ duration: 200,
+ down: {
+ easing: "easeOutBounce",
+ duration: 1000
+ }
+ };
+ } else {
+ options.animate = options.animated;
+ }
+ }
+
+ _create.call( this );
+ };
+ }( jQuery, jQuery.ui.accordion.prototype ) );
+}
+
+})( jQuery );
diff --git a/js/jquery/src/jquery-ui/jquery.ui.autocomplete.js b/js/jquery/src/jquery-ui/jquery.ui.autocomplete.js
new file mode 100644
index 0000000000..2d064834ce
--- /dev/null
+++ b/js/jquery/src/jquery-ui/jquery.ui.autocomplete.js
@@ -0,0 +1,602 @@
+/*!
+ * jQuery UI Autocomplete @VERSION
+ * http://jqueryui.com
+ *
+ * Copyright 2012 jQuery Foundation and other contributors
+ * Released under the MIT license.
+ * http://jquery.org/license
+ *
+ * http://api.jqueryui.com/autocomplete/
+ *
+ * Depends:
+ * jquery.ui.core.js
+ * jquery.ui.widget.js
+ * jquery.ui.position.js
+ * jquery.ui.menu.js
+ */
+(function( $, undefined ) {
+
+// used to prevent race conditions with remote data sources
+var requestIndex = 0;
+
+$.widget( "ui.autocomplete", {
+ version: "@VERSION",
+ defaultElement: "<input>",
+ options: {
+ appendTo: "body",
+ autoFocus: false,
+ delay: 300,
+ minLength: 1,
+ position: {
+ my: "left top",
+ at: "left bottom",
+ collision: "none"
+ },
+ source: null,
+
+ // callbacks
+ change: null,
+ close: null,
+ focus: null,
+ open: null,
+ response: null,
+ search: null,
+ select: null
+ },
+
+ pending: 0,
+
+ _create: function() {
+ // Some browsers only repeat keydown events, not keypress events,
+ // so we use the suppressKeyPress flag to determine if we've already
+ // handled the keydown event. #7269
+ // Unfortunately the code for & in keypress is the same as the up arrow,
+ // so we use the suppressKeyPressRepeat flag to avoid handling keypress
+ // events when we know the keydown event was used to modify the
+ // search term. #7799
+ var suppressKeyPress, suppressKeyPressRepeat, suppressInput;
+
+ this.isMultiLine = this._isMultiLine();
+ this.valueMethod = this.element[ this.element.is( "input,textarea" ) ? "val" : "text" ];
+ this.isNewMenu = true;
+
+ this.element
+ .addClass( "ui-autocomplete-input" )
+ .attr( "autocomplete", "off" );
+
+ this._on( this.element, {
+ keydown: function( event ) {
+ if ( this.element.prop( "readOnly" ) ) {
+ suppressKeyPress = true;
+ suppressInput = true;
+ suppressKeyPressRepeat = true;
+ return;
+ }
+
+ suppressKeyPress = false;
+ suppressInput = false;
+ suppressKeyPressRepeat = false;
+ var keyCode = $.ui.keyCode;
+ switch( event.keyCode ) {
+ case keyCode.PAGE_UP:
+ suppressKeyPress = true;
+ this._move( "previousPage", event );
+ break;
+ case keyCode.PAGE_DOWN:
+ suppressKeyPress = true;
+ this._move( "nextPage", event );
+ break;
+ case keyCode.UP:
+ suppressKeyPress = true;
+ this._keyEvent( "previous", event );
+ break;
+ case keyCode.DOWN:
+ suppressKeyPress = true;
+ this._keyEvent( "next", event );
+ break;
+ case keyCode.ENTER:
+ case keyCode.NUMPAD_ENTER:
+ // when menu is open and has focus
+ if ( this.menu.active ) {
+ // #6055 - Opera still allows the keypress to occur
+ // which causes forms to submit
+ suppressKeyPress = true;
+ event.preventDefault();
+ this.menu.select( event );
+ }
+ break;
+ case keyCode.TAB:
+ if ( this.menu.active ) {
+ this.menu.select( event );
+ }
+ break;
+ case keyCode.ESCAPE:
+ if ( this.menu.element.is( ":visible" ) ) {
+ this._value( this.term );
+ this.close( event );
+ // Different browsers have different default behavior for escape
+ // Single press can mean undo or clear
+ // Double press in IE means clear the whole form
+ event.preventDefault();
+ }
+ break;
+ default:
+ suppressKeyPressRepeat = true;
+ // search timeout should be triggered before the input value is changed
+ this._searchTimeout( event );
+ break;
+ }
+ },
+ keypress: function( event ) {
+ if ( suppressKeyPress ) {
+ suppressKeyPress = false;
+ event.preventDefault();
+ return;
+ }
+ if ( suppressKeyPressRepeat ) {
+ return;
+ }
+
+ // replicate some key handlers to allow them to repeat in Firefox and Opera
+ var keyCode = $.ui.keyCode;
+ switch( event.keyCode ) {
+ case keyCode.PAGE_UP:
+ this._move( "previousPage", event );
+ break;
+ case keyCode.PAGE_DOWN:
+ this._move( "nextPage", event );
+ break;
+ case keyCode.UP:
+ this._keyEvent( "previous", event );
+ break;
+ case keyCode.DOWN:
+ this._keyEvent( "next", event );
+ break;
+ }
+ },
+ input: function( event ) {
+ if ( suppressInput ) {
+ suppressInput = false;
+ event.preventDefault();
+ return;
+ }
+ this._searchTimeout( event );
+ },
+ focus: function() {
+ this.selectedItem = null;
+ this.previous = this._value();
+ },
+ blur: function( event ) {
+ if ( this.cancelBlur ) {
+ delete this.cancelBlur;
+ return;
+ }
+
+ clearTimeout( this.searching );
+ this.close( event );
+ this._change( event );
+ }
+ });
+
+ this._initSource();
+ this.menu = $( "<ul>" )
+ .addClass( "ui-autocomplete" )
+ .appendTo( this.document.find( this.options.appendTo || "body" )[ 0 ] )
+ .menu({
+ // custom key handling for now
+ input: $(),
+ // disable ARIA support, the live region takes care of that
+ role: null
+ })
+ .zIndex( this.element.zIndex() + 1 )
+ .hide()
+ .data( "menu" );
+
+ this._on( this.menu.element, {
+ mousedown: function( event ) {
+ // prevent moving focus out of the text field
+ event.preventDefault();
+
+ // IE doesn't prevent moving focus even with event.preventDefault()
+ // so we set a flag to know when we should ignore the blur event
+ this.cancelBlur = true;
+ this._delay(function() {
+ delete this.cancelBlur;
+ });
+
+ // clicking on the scrollbar causes focus to shift to the body
+ // but we can't detect a mouseup or a click immediately afterward
+ // so we have to track the next mousedown and close the menu if
+ // the user clicks somewhere outside of the autocomplete
+ var menuElement = this.menu.element[ 0 ];
+ if ( !$( event.target ).closest( ".ui-menu-item" ).length ) {
+ this._delay(function() {
+ var that = this;
+ this.document.one( "mousedown", function( event ) {
+ if ( event.target !== that.element[ 0 ] &&
+ event.target !== menuElement &&
+ !$.contains( menuElement, event.target ) ) {
+ that.close();
+ }
+ });
+ });
+ }
+ },
+ menufocus: function( event, ui ) {
+ // #7024 - Prevent accidental activation of menu items in Firefox
+ if ( this.isNewMenu ) {
+ this.isNewMenu = false;
+ if ( event.originalEvent && /^mouse/.test( event.originalEvent.type ) ) {
+ this.menu.blur();
+
+ this.document.one( "mousemove", function() {
+ $( event.target ).trigger( event.originalEvent );
+ });
+
+ return;
+ }
+ }
+
+ // back compat for _renderItem using item.autocomplete, via #7810
+ // TODO remove the fallback, see #8156
+ var item = ui.item.data( "ui-autocomplete-item" ) || ui.item.data( "item.autocomplete" );
+ if ( false !== this._trigger( "focus", event, { item: item } ) ) {
+ // use value to match what will end up in the input, if it was a key event
+ if ( event.originalEvent && /^key/.test( event.originalEvent.type ) ) {
+ this._value( item.value );
+ }
+ } else {
+ // Normally the input is populated with the item's value as the
+ // menu is navigated, causing screen readers to notice a change and
+ // announce the item. Since the focus event was canceled, this doesn't
+ // happen, so we update the live region so that screen readers can
+ // still notice the change and announce it.
+ this.liveRegion.text( item.value );
+ }
+ },
+ menuselect: function( event, ui ) {
+ // back compat for _renderItem using item.autocomplete, via #7810
+ // TODO remove the fallback, see #8156
+ var item = ui.item.data( "ui-autocomplete-item" ) || ui.item.data( "item.autocomplete" ),
+ previous = this.previous;
+
+ // only trigger when focus was lost (click on menu)
+ if ( this.element[0] !== this.document[0].activeElement ) {
+ this.element.focus();
+ this.previous = previous;
+ // #6109 - IE triggers two focus events and the second
+ // is asynchronous, so we need to reset the previous
+ // term synchronously and asynchronously :-(
+ this._delay(function() {
+ this.previous = previous;
+ this.selectedItem = item;
+ });
+ }
+
+ if ( false !== this._trigger( "select", event, { item: item } ) ) {
+ this._value( item.value );
+ }
+ // reset the term after the select event
+ // this allows custom select handling to work properly
+ this.term = this._value();
+
+ this.close( event );
+ this.selectedItem = item;
+ }
+ });
+
+ this.liveRegion = $( "<span>", {
+ role: "status",
+ "aria-live": "polite"
+ })
+ .addClass( "ui-helper-hidden-accessible" )
+ .insertAfter( this.element );
+
+ if ( $.fn.bgiframe ) {
+ this.menu.element.bgiframe();
+ }
+
+ // turning off autocomplete prevents the browser from remembering the
+ // value when navigating through history, so we re-enable autocomplete
+ // if the page is unloaded before the widget is destroyed. #7790
+ this._on( this.window, {
+ beforeunload: function() {
+ this.element.removeAttr( "autocomplete" );
+ }
+ });
+ },
+
+ _destroy: function() {
+ clearTimeout( this.searching );
+ this.element
+ .removeClass( "ui-autocomplete-input" )
+ .removeAttr( "autocomplete" );
+ this.menu.element.remove();
+ this.liveRegion.remove();
+ },
+
+ _setOption: function( key, value ) {
+ this._super( key, value );
+ if ( key === "source" ) {
+ this._initSource();
+ }
+ if ( key === "appendTo" ) {
+ this.menu.element.appendTo( this.document.find( value || "body" )[0] );
+ }
+ if ( key === "disabled" && value && this.xhr ) {
+ this.xhr.abort();
+ }
+ },
+
+ _isMultiLine: function() {
+ // Textareas are always multi-line
+ if ( this.element.is( "textarea" ) ) {
+ return true;
+ }
+ // Inputs are always single-line, even if inside a contentEditable element
+ // IE also treats inputs as contentEditable
+ if ( this.element.is( "input" ) ) {
+ return false;
+ }
+ // All other element types are determined by whether or not they're contentEditable
+ return this.element.prop( "isContentEditable" );
+ },
+
+ _initSource: function() {
+ var array, url,
+ that = this;
+ if ( $.isArray(this.options.source) ) {
+ array = this.options.source;
+ this.source = function( request, response ) {
+ response( $.ui.autocomplete.filter( array, request.term ) );
+ };
+ } else if ( typeof this.options.source === "string" ) {
+ url = this.options.source;
+ this.source = function( request, response ) {
+ if ( that.xhr ) {
+ that.xhr.abort();
+ }
+ that.xhr = $.ajax({
+ url: url,
+ data: request,
+ dataType: "json",
+ success: function( data ) {
+ response( data );
+ },
+ error: function() {
+ response( [] );
+ }
+ });
+ };
+ } else {
+ this.source = this.options.source;
+ }
+ },
+
+ _searchTimeout: function( event ) {
+ clearTimeout( this.searching );
+ this.searching = this._delay(function() {
+ // only search if the value has changed
+ if ( this.term !== this._value() ) {
+ this.selectedItem = null;
+ this.search( null, event );
+ }
+ }, this.options.delay );
+ },
+
+ search: function( value, event ) {
+ value = value != null ? value : this._value();
+
+ // always save the actual value, not the one passed as an argument
+ this.term = this._value();
+
+ if ( value.length < this.options.minLength ) {
+ return this.close( event );
+ }
+
+ if ( this._trigger( "search", event ) === false ) {
+ return;
+ }
+
+ return this._search( value );
+ },
+
+ _search: function( value ) {
+ this.pending++;
+ this.element.addClass( "ui-autocomplete-loading" );
+ this.cancelSearch = false;
+
+ this.source( { term: value }, this._response() );
+ },
+
+ _response: function() {
+ var that = this,
+ index = ++requestIndex;
+
+ return function( content ) {
+ if ( index === requestIndex ) {
+ that.__response( content );
+ }
+
+ that.pending--;
+ if ( !that.pending ) {
+ that.element.removeClass( "ui-autocomplete-loading" );
+ }
+ };
+ },
+
+ __response: function( content ) {
+ if ( content ) {
+ content = this._normalize( content );
+ }
+ this._trigger( "response", null, { content: content } );
+ if ( !this.options.disabled && content && content.length && !this.cancelSearch ) {
+ this._suggest( content );
+ this._trigger( "open" );
+ } else {
+ // use ._close() instead of .close() so we don't cancel future searches
+ this._close();
+ }
+ },
+
+ close: function( event ) {
+ this.cancelSearch = true;
+ this._close( event );
+ },
+
+ _close: function( event ) {
+ if ( this.menu.element.is( ":visible" ) ) {
+ this.menu.element.hide();
+ this.menu.blur();
+ this.isNewMenu = true;
+ this._trigger( "close", event );
+ }
+ },
+
+ _change: function( event ) {
+ if ( this.previous !== this._value() ) {
+ this._trigger( "change", event, { item: this.selectedItem } );
+ }
+ },
+
+ _normalize: function( items ) {
+ // assume all items have the right format when the first item is complete
+ if ( items.length && items[0].label && items[0].value ) {
+ return items;
+ }
+ return $.map( items, function( item ) {
+ if ( typeof item === "string" ) {
+ return {
+ label: item,
+ value: item
+ };
+ }
+ return $.extend({
+ label: item.label || item.value,
+ value: item.value || item.label
+ }, item );
+ });
+ },
+
+ _suggest: function( items ) {
+ var ul = this.menu.element
+ .empty()
+ .zIndex( this.element.zIndex() + 1 );
+ this._renderMenu( ul, items );
+ this.menu.refresh();
+
+ // size and position menu
+ ul.show();
+ this._resizeMenu();
+ ul.position( $.extend({
+ of: this.element
+ }, this.options.position ));
+
+ if ( this.options.autoFocus ) {
+ this.menu.next();
+ }
+ },
+
+ _resizeMenu: function() {
+ var ul = this.menu.element;
+ ul.outerWidth( Math.max(
+ // Firefox wraps long text (possibly a rounding bug)
+ // so we add 1px to avoid the wrapping (#7513)
+ ul.width( "" ).outerWidth() + 1,
+ this.element.outerWidth()
+ ) );
+ },
+
+ _renderMenu: function( ul, items ) {
+ var that = this;
+ $.each( items, function( index, item ) {
+ that._renderItemData( ul, item );
+ });
+ },
+
+ _renderItemData: function( ul, item ) {
+ return this._renderItem( ul, item ).data( "ui-autocomplete-item", item );
+ },
+
+ _renderItem: function( ul, item ) {
+ return $( "<li>" )
+ .append( $( "<a>" ).text( item.label ) )
+ .appendTo( ul );
+ },
+
+ _move: function( direction, event ) {
+ if ( !this.menu.element.is( ":visible" ) ) {
+ this.search( null, event );
+ return;
+ }
+ if ( this.menu.isFirstItem() && /^previous/.test( direction ) ||
+ this.menu.isLastItem() && /^next/.test( direction ) ) {
+ this._value( this.term );
+ this.menu.blur();
+ return;
+ }
+ this.menu[ direction ]( event );
+ },
+
+ widget: function() {
+ return this.menu.element;
+ },
+
+ _value: function() {
+ return this.valueMethod.apply( this.element, arguments );
+ },
+
+ _keyEvent: function( keyEvent, event ) {
+ if ( !this.isMultiLine || this.menu.element.is( ":visible" ) ) {
+ this._move( keyEvent, event );
+
+ // prevents moving cursor to beginning/end of the text field in some browsers
+ event.preventDefault();
+ }
+ }
+});
+
+$.extend( $.ui.autocomplete, {
+ escapeRegex: function( value ) {
+ return value.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g, "\\$&");
+ },
+ filter: function(array, term) {
+ var matcher = new RegExp( $.ui.autocomplete.escapeRegex(term), "i" );
+ return $.grep( array, function(value) {
+ return matcher.test( value.label || value.value || value );
+ });
+ }
+});
+
+
+// live region extension, adding a `messages` option
+// NOTE: This is an experimental API. We are still investigating
+// a full solution for string manipulation and internationalization.
+$.widget( "ui.autocomplete", $.ui.autocomplete, {
+ options: {
+ messages: {
+ noResults: "No search results.",
+ results: function( amount ) {
+ return amount + ( amount > 1 ? " results are" : " result is" ) +
+ " available, use up and down arrow keys to navigate.";
+ }
+ }
+ },
+
+ __response: function( content ) {
+ var message;
+ this._superApply( arguments );
+ if ( this.options.disabled || this.cancelSearch ) {
+ return;
+ }
+ if ( content && content.length ) {
+ message = this.options.messages.results( content.length );
+ } else {
+ message = this.options.messages.noResults;
+ }
+ this.liveRegion.text( message );
+ }
+});
+
+
+}( jQuery ));
diff --git a/js/jquery/src/jquery-ui/jquery.ui.button.js b/js/jquery/src/jquery-ui/jquery.ui.button.js
new file mode 100644
index 0000000000..673cd3fbf5
--- /dev/null
+++ b/js/jquery/src/jquery-ui/jquery.ui.button.js
@@ -0,0 +1,418 @@
+/*!
+ * jQuery UI Button @VERSION
+ * http://jqueryui.com
+ *
+ * Copyright 2012 jQuery Foundation and other contributors
+ * Released under the MIT license.
+ * http://jquery.org/license
+ *
+ * http://api.jqueryui.com/button/
+ *
+ * Depends:
+ * jquery.ui.core.js
+ * jquery.ui.widget.js
+ */
+(function( $, undefined ) {
+
+var lastActive, startXPos, startYPos, clickDragged,
+ baseClasses = "ui-button ui-widget ui-state-default ui-corner-all",
+ stateClasses = "ui-state-hover ui-state-active ",
+ typeClasses = "ui-button-icons-only ui-button-icon-only ui-button-text-icons ui-button-text-icon-primary ui-button-text-icon-secondary ui-button-text-only",
+ formResetHandler = function() {
+ var buttons = $( this ).find( ":ui-button" );
+ setTimeout(function() {
+ buttons.button( "refresh" );
+ }, 1 );
+ },
+ radioGroup = function( radio ) {
+ var name = radio.name,
+ form = radio.form,
+ radios = $( [] );
+ if ( name ) {
+ if ( form ) {
+ radios = $( form ).find( "[name='" + name + "']" );
+ } else {
+ radios = $( "[name='" + name + "']", radio.ownerDocument )
+ .filter(function() {
+ return !this.form;
+ });
+ }
+ }
+ return radios;
+ };
+
+$.widget( "ui.button", {
+ version: "@VERSION",
+ defaultElement: "<button>",
+ options: {
+ disabled: null,
+ text: true,
+ label: null,
+ icons: {
+ primary: null,
+ secondary: null
+ }
+ },
+ _create: function() {
+ this.element.closest( "form" )
+ .unbind( "reset" + this.eventNamespace )
+ .bind( "reset" + this.eventNamespace, formResetHandler );
+
+ if ( typeof this.options.disabled !== "boolean" ) {
+ this.options.disabled = !!this.element.prop( "disabled" );
+ } else {
+ this.element.prop( "disabled", this.options.disabled );
+ }
+
+ this._determineButtonType();
+ this.hasTitle = !!this.buttonElement.attr( "title" );
+
+ var that = this,
+ options = this.options,
+ toggleButton = this.type === "checkbox" || this.type === "radio",
+ activeClass = !toggleButton ? "ui-state-active" : "",
+ focusClass = "ui-state-focus";
+
+ if ( options.label === null ) {
+ options.label = (this.type === "input" ? this.buttonElement.val() : this.buttonElement.html());
+ }
+
+ this._hoverable( this.buttonElement );
+
+ this.buttonElement
+ .addClass( baseClasses )
+ .attr( "role", "button" )
+ .bind( "mouseenter" + this.eventNamespace, function() {
+ if ( options.disabled ) {
+ return;
+ }
+ if ( this === lastActive ) {
+ $( this ).addClass( "ui-state-active" );
+ }
+ })
+ .bind( "mouseleave" + this.eventNamespace, function() {
+ if ( options.disabled ) {
+ return;
+ }
+ $( this ).removeClass( activeClass );
+ })
+ .bind( "click" + this.eventNamespace, function( event ) {
+ if ( options.disabled ) {
+ event.preventDefault();
+ event.stopImmediatePropagation();
+ }
+ });
+
+ this.element
+ .bind( "focus" + this.eventNamespace, function() {
+ // no need to check disabled, focus won't be triggered anyway
+ that.buttonElement.addClass( focusClass );
+ })
+ .bind( "blur" + this.eventNamespace, function() {
+ that.buttonElement.removeClass( focusClass );
+ });
+
+ if ( toggleButton ) {
+ this.element.bind( "change" + this.eventNamespace, function() {
+ if ( clickDragged ) {
+ return;
+ }
+ that.refresh();
+ });
+ // if mouse moves between mousedown and mouseup (drag) set clickDragged flag
+ // prevents issue where button state changes but checkbox/radio checked state
+ // does not in Firefox (see ticket #6970)
+ this.buttonElement
+ .bind( "mousedown" + this.eventNamespace, function( event ) {
+ if ( options.disabled ) {
+ return;
+ }
+ clickDragged = false;
+ startXPos = event.pageX;
+ startYPos = event.pageY;
+ })
+ .bind( "mouseup" + this.eventNamespace, function( event ) {
+ if ( options.disabled ) {
+ return;
+ }
+ if ( startXPos !== event.pageX || startYPos !== event.pageY ) {
+ clickDragged = true;
+ }
+ });
+ }
+
+ if ( this.type === "checkbox" ) {
+ this.buttonElement.bind( "click" + this.eventNamespace, function() {
+ if ( options.disabled || clickDragged ) {
+ return false;
+ }
+ $( this ).toggleClass( "ui-state-active" );
+ that.buttonElement.attr( "aria-pressed", that.element[0].checked );
+ });
+ } else if ( this.type === "radio" ) {
+ this.buttonElement.bind( "click" + this.eventNamespace, function() {
+ if ( options.disabled || clickDragged ) {
+ return false;
+ }
+ $( this ).addClass( "ui-state-active" );
+ that.buttonElement.attr( "aria-pressed", "true" );
+
+ var radio = that.element[ 0 ];
+ radioGroup( radio )
+ .not( radio )
+ .map(function() {
+ return $( this ).button( "widget" )[ 0 ];
+ })
+ .removeClass( "ui-state-active" )
+ .attr( "aria-pressed", "false" );
+ });
+ } else {
+ this.buttonElement
+ .bind( "mousedown" + this.eventNamespace, function() {
+ if ( options.disabled ) {
+ return false;
+ }
+ $( this ).addClass( "ui-state-active" );
+ lastActive = this;
+ that.document.one( "mouseup", function() {
+ lastActive = null;
+ });
+ })
+ .bind( "mouseup" + this.eventNamespace, function() {
+ if ( options.disabled ) {
+ return false;
+ }
+ $( this ).removeClass( "ui-state-active" );
+ })
+ .bind( "keydown" + this.eventNamespace, function(event) {
+ if ( options.disabled ) {
+ return false;
+ }
+ if ( event.keyCode === $.ui.keyCode.SPACE || event.keyCode === $.ui.keyCode.ENTER ) {
+ $( this ).addClass( "ui-state-active" );
+ }
+ })
+ .bind( "keyup" + this.eventNamespace, function() {
+ $( this ).removeClass( "ui-state-active" );
+ });
+
+ if ( this.buttonElement.is("a") ) {
+ this.buttonElement.keyup(function(event) {
+ if ( event.keyCode === $.ui.keyCode.SPACE ) {
+ // TODO pass through original event correctly (just as 2nd argument doesn't work)
+ $( this ).click();
+ }
+ });
+ }
+ }
+
+ // TODO: pull out $.Widget's handling for the disabled option into
+ // $.Widget.prototype._setOptionDisabled so it's easy to proxy and can
+ // be overridden by individual plugins
+ this._setOption( "disabled", options.disabled );
+ this._resetButton();
+ },
+
+ _determineButtonType: function() {
+ var ancestor, labelSelector, checked;
+
+ if ( this.element.is("[type=checkbox]") ) {
+ this.type = "checkbox";
+ } else if ( this.element.is("[type=radio]") ) {
+ this.type = "radio";
+ } else if ( this.element.is("input") ) {
+ this.type = "input";
+ } else {
+ this.type = "button";
+ }
+
+ if ( this.type === "checkbox" || this.type === "radio" ) {
+ // we don't search against the document in case the element
+ // is disconnected from the DOM
+ ancestor = this.element.parents().last();
+ labelSelector = "label[for='" + this.element.attr("id") + "']";
+ this.buttonElement = ancestor.find( labelSelector );
+ if ( !this.buttonElement.length ) {
+ ancestor = ancestor.length ? ancestor.siblings() : this.element.siblings();
+ this.buttonElement = ancestor.filter( labelSelector );
+ if ( !this.buttonElement.length ) {
+ this.buttonElement = ancestor.find( labelSelector );
+ }
+ }
+ this.element.addClass( "ui-helper-hidden-accessible" );
+
+ checked = this.element.is( ":checked" );
+ if ( checked ) {
+ this.buttonElement.addClass( "ui-state-active" );
+ }
+ this.buttonElement.prop( "aria-pressed", checked );
+ } else {
+ this.buttonElement = this.element;
+ }
+ },
+
+ widget: function() {
+ return this.buttonElement;
+ },
+
+ _destroy: function() {
+ this.element
+ .removeClass( "ui-helper-hidden-accessible" );
+ this.buttonElement
+ .removeClass( baseClasses + " " + stateClasses + " " + typeClasses )
+ .removeAttr( "role" )
+ .removeAttr( "aria-pressed" )
+ .html( this.buttonElement.find(".ui-button-text").html() );
+
+ if ( !this.hasTitle ) {
+ this.buttonElement.removeAttr( "title" );
+ }
+ },
+
+ _setOption: function( key, value ) {
+ this._super( key, value );
+ if ( key === "disabled" ) {
+ if ( value ) {
+ this.element.prop( "disabled", true );
+ } else {
+ this.element.prop( "disabled", false );
+ }
+ return;
+ }
+ this._resetButton();
+ },
+
+ refresh: function() {
+ //See #8237 & #8828
+ var isDisabled = this.element.is( "input, button" ) ? this.element.is( ":disabled" ) : this.element.hasClass( "ui-button-disabled" );
+
+ if ( isDisabled !== this.options.disabled ) {
+ this._setOption( "disabled", isDisabled );
+ }
+ if ( this.type === "radio" ) {
+ radioGroup( this.element[0] ).each(function() {
+ if ( $( this ).is( ":checked" ) ) {
+ $( this ).button( "widget" )
+ .addClass( "ui-state-active" )
+ .attr( "aria-pressed", "true" );
+ } else {
+ $( this ).button( "widget" )
+ .removeClass( "ui-state-active" )
+ .attr( "aria-pressed", "false" );
+ }
+ });
+ } else if ( this.type === "checkbox" ) {
+ if ( this.element.is( ":checked" ) ) {
+ this.buttonElement
+ .addClass( "ui-state-active" )
+ .attr( "aria-pressed", "true" );
+ } else {
+ this.buttonElement
+ .removeClass( "ui-state-active" )
+ .attr( "aria-pressed", "false" );
+ }
+ }
+ },
+
+ _resetButton: function() {
+ if ( this.type === "input" ) {
+ if ( this.options.label ) {
+ this.element.val( this.options.label );
+ }
+ return;
+ }
+ var buttonElement = this.buttonElement.removeClass( typeClasses ),
+ buttonText = $( "<span></span>", this.document[0] )
+ .addClass( "ui-button-text" )
+ .html( this.options.label )
+ .appendTo( buttonElement.empty() )
+ .text(),
+ icons = this.options.icons,
+ multipleIcons = icons.primary && icons.secondary,
+ buttonClasses = [];
+
+ if ( icons.primary || icons.secondary ) {
+ if ( this.options.text ) {
+ buttonClasses.push( "ui-button-text-icon" + ( multipleIcons ? "s" : ( icons.primary ? "-primary" : "-secondary" ) ) );
+ }
+
+ if ( icons.primary ) {
+ buttonElement.prepend( "<span class='ui-button-icon-primary ui-icon " + icons.primary + "'></span>" );
+ }
+
+ if ( icons.secondary ) {
+ buttonElement.append( "<span class='ui-button-icon-secondary ui-icon " + icons.secondary + "'></span>" );
+ }
+
+ if ( !this.options.text ) {
+ buttonClasses.push( multipleIcons ? "ui-button-icons-only" : "ui-button-icon-only" );
+
+ if ( !this.hasTitle ) {
+ buttonElement.attr( "title", $.trim( buttonText ) );
+ }
+ }
+ } else {
+ buttonClasses.push( "ui-button-text-only" );
+ }
+ buttonElement.addClass( buttonClasses.join( " " ) );
+ }
+});
+
+$.widget( "ui.buttonset", {
+ version: "@VERSION",
+ options: {
+ items: "button, input[type=button], input[type=submit], input[type=reset], input[type=checkbox], input[type=radio], a, :data(button)"
+ },
+
+ _create: function() {
+ this.element.addClass( "ui-buttonset" );
+ },
+
+ _init: function() {
+ this.refresh();
+ },
+
+ _setOption: function( key, value ) {
+ if ( key === "disabled" ) {
+ this.buttons.button( "option", key, value );
+ }
+
+ this._super( key, value );
+ },
+
+ refresh: function() {
+ var rtl = this.element.css( "direction" ) === "rtl";
+
+ this.buttons = this.element.find( this.options.items )
+ .filter( ":ui-button" )
+ .button( "refresh" )
+ .end()
+ .not( ":ui-button" )
+ .button()
+ .end()
+ .map(function() {
+ return $( this ).button( "widget" )[ 0 ];
+ })
+ .removeClass( "ui-corner-all ui-corner-left ui-corner-right" )
+ .filter( ":first" )
+ .addClass( rtl ? "ui-corner-right" : "ui-corner-left" )
+ .end()
+ .filter( ":last" )
+ .addClass( rtl ? "ui-corner-left" : "ui-corner-right" )
+ .end()
+ .end();
+ },
+
+ _destroy: function() {
+ this.element.removeClass( "ui-buttonset" );
+ this.buttons
+ .map(function() {
+ return $( this ).button( "widget" )[ 0 ];
+ })
+ .removeClass( "ui-corner-left ui-corner-right" )
+ .end()
+ .button( "destroy" );
+ }
+});
+
+}( jQuery ) );
diff --git a/js/jquery/src/jquery-ui/jquery.ui.core.js b/js/jquery/src/jquery-ui/jquery.ui.core.js
new file mode 100644
index 0000000000..d5c38d5556
--- /dev/null
+++ b/js/jquery/src/jquery-ui/jquery.ui.core.js
@@ -0,0 +1,356 @@
+/*!
+ * jQuery UI Core @VERSION
+ * http://jqueryui.com
+ *
+ * Copyright 2012 jQuery Foundation and other contributors
+ * Released under the MIT license.
+ * http://jquery.org/license
+ *
+ * http://api.jqueryui.com/category/ui-core/
+ */
+(function( $, undefined ) {
+
+var uuid = 0,
+ runiqueId = /^ui-id-\d+$/;
+
+// prevent duplicate loading
+// this is only a problem because we proxy existing functions
+// and we don't want to double proxy them
+$.ui = $.ui || {};
+if ( $.ui.version ) {
+ return;
+}
+
+$.extend( $.ui, {
+ version: "@VERSION",
+
+ keyCode: {
+ BACKSPACE: 8,
+ COMMA: 188,
+ DELETE: 46,
+ DOWN: 40,
+ END: 35,
+ ENTER: 13,
+ ESCAPE: 27,
+ HOME: 36,
+ LEFT: 37,
+ NUMPAD_ADD: 107,
+ NUMPAD_DECIMAL: 110,
+ NUMPAD_DIVIDE: 111,
+ NUMPAD_ENTER: 108,
+ NUMPAD_MULTIPLY: 106,
+ NUMPAD_SUBTRACT: 109,
+ PAGE_DOWN: 34,
+ PAGE_UP: 33,
+ PERIOD: 190,
+ RIGHT: 39,
+ SPACE: 32,
+ TAB: 9,
+ UP: 38
+ }
+});
+
+// plugins
+$.fn.extend({
+ _focus: $.fn.focus,
+ focus: function( delay, fn ) {
+ return typeof delay === "number" ?
+ this.each(function() {
+ var elem = this;
+ setTimeout(function() {
+ $( elem ).focus();
+ if ( fn ) {
+ fn.call( elem );
+ }
+ }, delay );
+ }) :
+ this._focus.apply( this, arguments );
+ },
+
+ scrollParent: function() {
+ var scrollParent;
+ if (($.ui.ie && (/(static|relative)/).test(this.css('position'))) || (/absolute/).test(this.css('position'))) {
+ scrollParent = this.parents().filter(function() {
+ return (/(relative|absolute|fixed)/).test($.css(this,'position')) && (/(auto|scroll)/).test($.css(this,'overflow')+$.css(this,'overflow-y')+$.css(this,'overflow-x'));
+ }).eq(0);
+ } else {
+ scrollParent = this.parents().filter(function() {
+ return (/(auto|scroll)/).test($.css(this,'overflow')+$.css(this,'overflow-y')+$.css(this,'overflow-x'));
+ }).eq(0);
+ }
+
+ return (/fixed/).test(this.css('position')) || !scrollParent.length ? $(document) : scrollParent;
+ },
+
+ zIndex: function( zIndex ) {
+ if ( zIndex !== undefined ) {
+ return this.css( "zIndex", zIndex );
+ }
+
+ if ( this.length ) {
+ var elem = $( this[ 0 ] ), position, value;
+ while ( elem.length && elem[ 0 ] !== document ) {
+ // Ignore z-index if position is set to a value where z-index is ignored by the browser
+ // This makes behavior of this function consistent across browsers
+ // WebKit always returns auto if the element is positioned
+ position = elem.css( "position" );
+ if ( position === "absolute" || position === "relative" || position === "fixed" ) {
+ // IE returns 0 when zIndex is not specified
+ // other browsers return a string
+ // we ignore the case of nested elements with an explicit value of 0
+ // <div style="z-index: -10;"><div style="z-index: 0;"></div></div>
+ value = parseInt( elem.css( "zIndex" ), 10 );
+ if ( !isNaN( value ) && value !== 0 ) {
+ return value;
+ }
+ }
+ elem = elem.parent();
+ }
+ }
+
+ return 0;
+ },
+
+ uniqueId: function() {
+ return this.each(function() {
+ if ( !this.id ) {
+ this.id = "ui-id-" + (++uuid);
+ }
+ });
+ },
+
+ removeUniqueId: function() {
+ return this.each(function() {
+ if ( runiqueId.test( this.id ) ) {
+ $( this ).removeAttr( "id" );
+ }
+ });
+ }
+});
+
+// selectors
+function focusable( element, isTabIndexNotNaN ) {
+ var map, mapName, img,
+ nodeName = element.nodeName.toLowerCase();
+ if ( "area" === nodeName ) {
+ map = element.parentNode;
+ mapName = map.name;
+ if ( !element.href || !mapName || map.nodeName.toLowerCase() !== "map" ) {
+ return false;
+ }
+ img = $( "img[usemap=#" + mapName + "]" )[0];
+ return !!img && visible( img );
+ }
+ return ( /input|select|textarea|button|object/.test( nodeName ) ?
+ !element.disabled :
+ "a" === nodeName ?
+ element.href || isTabIndexNotNaN :
+ isTabIndexNotNaN) &&
+ // the element and all of its ancestors must be visible
+ visible( element );
+}
+
+function visible( element ) {
+ return $.expr.filters.visible( element ) &&
+ !$( element ).parents().andSelf().filter(function() {
+ return $.css( this, "visibility" ) === "hidden";
+ }).length;
+}
+
+$.extend( $.expr[ ":" ], {
+ data: $.expr.createPseudo ?
+ $.expr.createPseudo(function( dataName ) {
+ return function( elem ) {
+ return !!$.data( elem, dataName );
+ };
+ }) :
+ // support: jQuery <1.8
+ function( elem, i, match ) {
+ return !!$.data( elem, match[ 3 ] );
+ },
+
+ focusable: function( element ) {
+ return focusable( element, !isNaN( $.attr( element, "tabindex" ) ) );
+ },
+
+ tabbable: function( element ) {
+ var tabIndex = $.attr( element, "tabindex" ),
+ isTabIndexNaN = isNaN( tabIndex );
+ return ( isTabIndexNaN || tabIndex >= 0 ) && focusable( element, !isTabIndexNaN );
+ }
+});
+
+// support
+$(function() {
+ var body = document.body,
+ div = body.appendChild( div = document.createElement( "div" ) );
+
+ // access offsetHeight before setting the style to prevent a layout bug
+ // in IE 9 which causes the element to continue to take up space even
+ // after it is removed from the DOM (#8026)
+ div.offsetHeight;
+
+ $.extend( div.style, {
+ minHeight: "100px",
+ height: "auto",
+ padding: 0,
+ borderWidth: 0
+ });
+
+ $.support.minHeight = div.offsetHeight === 100;
+ $.support.selectstart = "onselectstart" in div;
+
+ // set display to none to avoid a layout bug in IE
+ // http://dev.jquery.com/ticket/4014
+ body.removeChild( div ).style.display = "none";
+});
+
+// support: jQuery <1.8
+if ( !$( "<a>" ).outerWidth( 1 ).jquery ) {
+ $.each( [ "Width", "Height" ], function( i, name ) {
+ var side = name === "Width" ? [ "Left", "Right" ] : [ "Top", "Bottom" ],
+ type = name.toLowerCase(),
+ orig = {
+ innerWidth: $.fn.innerWidth,
+ innerHeight: $.fn.innerHeight,
+ outerWidth: $.fn.outerWidth,
+ outerHeight: $.fn.outerHeight
+ };
+
+ function reduce( elem, size, border, margin ) {
+ $.each( side, function() {
+ size -= parseFloat( $.css( elem, "padding" + this ) ) || 0;
+ if ( border ) {
+ size -= parseFloat( $.css( elem, "border" + this + "Width" ) ) || 0;
+ }
+ if ( margin ) {
+ size -= parseFloat( $.css( elem, "margin" + this ) ) || 0;
+ }
+ });
+ return size;
+ }
+
+ $.fn[ "inner" + name ] = function( size ) {
+ if ( size === undefined ) {
+ return orig[ "inner" + name ].call( this );
+ }
+
+ return this.each(function() {
+ $( this ).css( type, reduce( this, size ) + "px" );
+ });
+ };
+
+ $.fn[ "outer" + name] = function( size, margin ) {
+ if ( typeof size !== "number" ) {
+ return orig[ "outer" + name ].call( this, size );
+ }
+
+ return this.each(function() {
+ $( this).css( type, reduce( this, size, true, margin ) + "px" );
+ });
+ };
+ });
+}
+
+// support: jQuery 1.6.1, 1.6.2 (http://bugs.jquery.com/ticket/9413)
+if ( $( "<a>" ).data( "a-b", "a" ).removeData( "a-b" ).data( "a-b" ) ) {
+ $.fn.removeData = (function( removeData ) {
+ return function( key ) {
+ if ( arguments.length ) {
+ return removeData.call( this, $.camelCase( key ) );
+ } else {
+ return removeData.call( this );
+ }
+ };
+ })( $.fn.removeData );
+}
+
+
+
+
+
+// deprecated
+
+(function() {
+ var uaMatch = /msie ([\w.]+)/.exec( navigator.userAgent.toLowerCase() ) || [];
+ $.ui.ie = uaMatch.length ? true : false;
+ $.ui.ie6 = parseFloat( uaMatch[ 1 ], 10 ) === 6;
+})();
+
+$.fn.extend({
+ disableSelection: function() {
+ return this.bind( ( $.support.selectstart ? "selectstart" : "mousedown" ) +
+ ".ui-disableSelection", function( event ) {
+ event.preventDefault();
+ });
+ },
+
+ enableSelection: function() {
+ return this.unbind( ".ui-disableSelection" );
+ }
+});
+
+$.extend( $.ui, {
+ // $.ui.plugin is deprecated. Use the proxy pattern instead.
+ plugin: {
+ add: function( module, option, set ) {
+ var i,
+ proto = $.ui[ module ].prototype;
+ for ( i in set ) {
+ proto.plugins[ i ] = proto.plugins[ i ] || [];
+ proto.plugins[ i ].push( [ option, set[ i ] ] );
+ }
+ },
+ call: function( instance, name, args ) {
+ var i,
+ set = instance.plugins[ name ];
+ if ( !set || !instance.element[ 0 ].parentNode || instance.element[ 0 ].parentNode.nodeType === 11 ) {
+ return;
+ }
+
+ for ( i = 0; i < set.length; i++ ) {
+ if ( instance.options[ set[ i ][ 0 ] ] ) {
+ set[ i ][ 1 ].apply( instance.element, args );
+ }
+ }
+ }
+ },
+
+ contains: $.contains,
+
+ // only used by resizable
+ hasScroll: function( el, a ) {
+
+ //If overflow is hidden, the element might have extra content, but the user wants to hide it
+ if ( $( el ).css( "overflow" ) === "hidden") {
+ return false;
+ }
+
+ var scroll = ( a && a === "left" ) ? "scrollLeft" : "scrollTop",
+ has = false;
+
+ if ( el[ scroll ] > 0 ) {
+ return true;
+ }
+
+ // TODO: determine which cases actually cause this to happen
+ // if the element doesn't have the scroll set, see if it's possible to
+ // set the scroll
+ el[ scroll ] = 1;
+ has = ( el[ scroll ] > 0 );
+ el[ scroll ] = 0;
+ return has;
+ },
+
+ // these are odd functions, fix the API or move into individual plugins
+ isOverAxis: function( x, reference, size ) {
+ //Determines when x coordinate is over "b" element axis
+ return ( x > reference ) && ( x < ( reference + size ) );
+ },
+ isOver: function( y, x, top, left, height, width ) {
+ //Determines when x, y coordinates is over "b" element
+ return $.ui.isOverAxis( y, top, height ) && $.ui.isOverAxis( x, left, width );
+ }
+});
+
+})( jQuery );
diff --git a/js/jquery/src/jquery-ui/jquery.ui.datepicker.js b/js/jquery/src/jquery-ui/jquery.ui.datepicker.js
new file mode 100644
index 0000000000..7dacbc7c1c
--- /dev/null
+++ b/js/jquery/src/jquery-ui/jquery.ui.datepicker.js
@@ -0,0 +1,1846 @@
+/*!
+ * jQuery UI Datepicker @VERSION
+ * http://jqueryui.com
+ *
+ * Copyright 2012 jQuery Foundation and other contributors
+ * Released under the MIT license.
+ * http://jquery.org/license
+ *
+ * http://api.jqueryui.com/datepicker/
+ *
+ * Depends:
+ * jquery.ui.core.js
+ */
+(function( $, undefined ) {
+
+$.extend($.ui, { datepicker: { version: "@VERSION" } });
+
+var PROP_NAME = 'datepicker';
+var dpuuid = new Date().getTime();
+var instActive;
+
+/* Date picker manager.
+ Use the singleton instance of this class, $.datepicker, to interact with the date picker.
+ Settings for (groups of) date pickers are maintained in an instance object,
+ allowing multiple different settings on the same page. */
+
+function Datepicker() {
+ this.debug = false; // Change this to true to start debugging
+ this._curInst = null; // The current instance in use
+ this._keyEvent = false; // If the last event was a key event
+ this._disabledInputs = []; // List of date picker inputs that have been disabled
+ this._datepickerShowing = false; // True if the popup picker is showing , false if not
+ this._inDialog = false; // True if showing within a "dialog", false if not
+ this._mainDivId = 'ui-datepicker-div'; // The ID of the main datepicker division
+ this._inlineClass = 'ui-datepicker-inline'; // The name of the inline marker class
+ this._appendClass = 'ui-datepicker-append'; // The name of the append marker class
+ this._triggerClass = 'ui-datepicker-trigger'; // The name of the trigger marker class
+ this._dialogClass = 'ui-datepicker-dialog'; // The name of the dialog marker class
+ this._disableClass = 'ui-datepicker-disabled'; // The name of the disabled covering marker class
+ this._unselectableClass = 'ui-datepicker-unselectable'; // The name of the unselectable cell marker class
+ this._currentClass = 'ui-datepicker-current-day'; // The name of the current day marker class
+ this._dayOverClass = 'ui-datepicker-days-cell-over'; // The name of the day hover marker class
+ this.regional = []; // Available regional settings, indexed by language code
+ this.regional[''] = { // Default regional settings
+ closeText: 'Done', // Display text for close link
+ prevText: 'Prev', // Display text for previous month link
+ nextText: 'Next', // Display text for next month link
+ currentText: 'Today', // Display text for current month link
+ monthNames: ['January','February','March','April','May','June',
+ 'July','August','September','October','November','December'], // Names of months for drop-down and formatting
+ monthNamesShort: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'], // For formatting
+ dayNames: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'], // For formatting
+ dayNamesShort: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'], // For formatting
+ dayNamesMin: ['Su','Mo','Tu','We','Th','Fr','Sa'], // Column headings for days starting at Sunday
+ weekHeader: 'Wk', // Column header for week of the year
+ dateFormat: 'mm/dd/yy', // See format options on parseDate
+ firstDay: 0, // The first day of the week, Sun = 0, Mon = 1, ...
+ isRTL: false, // True if right-to-left language, false if left-to-right
+ showMonthAfterYear: false, // True if the year select precedes month, false for month then year
+ yearSuffix: '' // Additional text to append to the year in the month headers
+ };
+ this._defaults = { // Global defaults for all the date picker instances
+ showOn: 'focus', // 'focus' for popup on focus,
+ // 'button' for trigger button, or 'both' for either
+ showAnim: 'fadeIn', // Name of jQuery animation for popup
+ showOptions: {}, // Options for enhanced animations
+ defaultDate: null, // Used when field is blank: actual date,
+ // +/-number for offset from today, null for today
+ appendText: '', // Display text following the input box, e.g. showing the format
+ buttonText: '...', // Text for trigger button
+ buttonImage: '', // URL for trigger button image
+ buttonImageOnly: false, // True if the image appears alone, false if it appears on a button
+ hideIfNoPrevNext: false, // True to hide next/previous month links
+ // if not applicable, false to just disable them
+ navigationAsDateFormat: false, // True if date formatting applied to prev/today/next links
+ gotoCurrent: false, // True if today link goes back to current selection instead
+ changeMonth: false, // True if month can be selected directly, false if only prev/next
+ changeYear: false, // True if year can be selected directly, false if only prev/next
+ yearRange: 'c-10:c+10', // Range of years to display in drop-down,
+ // either relative to today's year (-nn:+nn), relative to currently displayed year
+ // (c-nn:c+nn), absolute (nnnn:nnnn), or a combination of the above (nnnn:-n)
+ showOtherMonths: false, // True to show dates in other months, false to leave blank
+ selectOtherMonths: false, // True to allow selection of dates in other months, false for unselectable
+ showWeek: false, // True to show week of the year, false to not show it
+ calculateWeek: this.iso8601Week, // How to calculate the week of the year,
+ // takes a Date and returns the number of the week for it
+ shortYearCutoff: '+10', // Short year values < this are in the current century,
+ // > this are in the previous century,
+ // string value starting with '+' for current year + value
+ minDate: null, // The earliest selectable date, or null for no limit
+ maxDate: null, // The latest selectable date, or null for no limit
+ duration: 'fast', // Duration of display/closure
+ beforeShowDay: null, // Function that takes a date and returns an array with
+ // [0] = true if selectable, false if not, [1] = custom CSS class name(s) or '',
+ // [2] = cell title (optional), e.g. $.datepicker.noWeekends
+ beforeShow: null, // Function that takes an input field and
+ // returns a set of custom settings for the date picker
+ onSelect: null, // Define a callback function when a date is selected
+ onChangeMonthYear: null, // Define a callback function when the month or year is changed
+ onClose: null, // Define a callback function when the datepicker is closed
+ numberOfMonths: 1, // Number of months to show at a time
+ showCurrentAtPos: 0, // The position in multipe months at which to show the current month (starting at 0)
+ stepMonths: 1, // Number of months to step back/forward
+ stepBigMonths: 12, // Number of months to step back/forward for the big links
+ altField: '', // Selector for an alternate field to store selected dates into
+ altFormat: '', // The date format to use for the alternate field
+ constrainInput: true, // The input is constrained by the current date format
+ showButtonPanel: false, // True to show button panel, false to not show it
+ autoSize: false, // True to size the input for the date format, false to leave as is
+ disabled: false // The initial disabled state
+ };
+ $.extend(this._defaults, this.regional['']);
+ this.dpDiv = bindHover($('<div id="' + this._mainDivId + '" class="ui-datepicker ui-widget ui-widget-content ui-helper-clearfix ui-corner-all"></div>'));
+}
+
+$.extend(Datepicker.prototype, {
+ /* Class name added to elements to indicate already configured with a date picker. */
+ markerClassName: 'hasDatepicker',
+
+ //Keep track of the maximum number of rows displayed (see #7043)
+ maxRows: 4,
+
+ /* Debug logging (if enabled). */
+ log: function () {
+ if (this.debug)
+ console.log.apply('', arguments);
+ },
+
+ // TODO rename to "widget" when switching to widget factory
+ _widgetDatepicker: function() {
+ return this.dpDiv;
+ },
+
+ /* Override the default settings for all instances of the date picker.
+ @param settings object - the new settings to use as defaults (anonymous object)
+ @return the manager object */
+ setDefaults: function(settings) {
+ extendRemove(this._defaults, settings || {});
+ return this;
+ },
+
+ /* Attach the date picker to a jQuery selection.
+ @param target element - the target input field or division or span
+ @param settings object - the new settings to use for this date picker instance (anonymous) */
+ _attachDatepicker: function(target, settings) {
+ // check for settings on the control itself - in namespace 'date:'
+ var inlineSettings = null;
+ for (var attrName in this._defaults) {
+ var attrValue = target.getAttribute('date:' + attrName);
+ if (attrValue) {
+ inlineSettings = inlineSettings || {};
+ try {
+ inlineSettings[attrName] = eval(attrValue);
+ } catch (err) {
+ inlineSettings[attrName] = attrValue;
+ }
+ }
+ }
+ var nodeName = target.nodeName.toLowerCase();
+ var inline = (nodeName == 'div' || nodeName == 'span');
+ if (!target.id) {
+ this.uuid += 1;
+ target.id = 'dp' + this.uuid;
+ }
+ var inst = this._newInst($(target), inline);
+ inst.settings = $.extend({}, settings || {}, inlineSettings || {});
+ if (nodeName == 'input') {
+ this._connectDatepicker(target, inst);
+ } else if (inline) {
+ this._inlineDatepicker(target, inst);
+ }
+ },
+
+ /* Create a new instance object. */
+ _newInst: function(target, inline) {
+ var id = target[0].id.replace(/([^A-Za-z0-9_-])/g, '\\\\$1'); // escape jQuery meta chars
+ return {id: id, input: target, // associated target
+ selectedDay: 0, selectedMonth: 0, selectedYear: 0, // current selection
+ drawMonth: 0, drawYear: 0, // month being drawn
+ inline: inline, // is datepicker inline or not
+ dpDiv: (!inline ? this.dpDiv : // presentation div
+ bindHover($('<div class="' + this._inlineClass + ' ui-datepicker ui-widget ui-widget-content ui-helper-clearfix ui-corner-all"></div>')))};
+ },
+
+ /* Attach the date picker to an input field. */
+ _connectDatepicker: function(target, inst) {
+ var input = $(target);
+ inst.append = $([]);
+ inst.trigger = $([]);
+ if (input.hasClass(this.markerClassName))
+ return;
+ this._attachments(input, inst);
+ input.addClass(this.markerClassName).keydown(this._doKeyDown).
+ keypress(this._doKeyPress).keyup(this._doKeyUp).
+ bind("setData.datepicker", function(event, key, value) {
+ inst.settings[key] = value;
+ }).bind("getData.datepicker", function(event, key) {
+ return this._get(inst, key);
+ });
+ this._autoSize(inst);
+ $.data(target, PROP_NAME, inst);
+ //If disabled option is true, disable the datepicker once it has been attached to the input (see ticket #5665)
+ if( inst.settings.disabled ) {
+ this._disableDatepicker( target );
+ }
+ },
+
+ /* Make attachments based on settings. */
+ _attachments: function(input, inst) {
+ var appendText = this._get(inst, 'appendText');
+ var isRTL = this._get(inst, 'isRTL');
+ if (inst.append)
+ inst.append.remove();
+ if (appendText) {
+ inst.append = $('<span class="' + this._appendClass + '">' + appendText + '</span>');
+ input[isRTL ? 'before' : 'after'](inst.append);
+ }
+ input.unbind('focus', this._showDatepicker);
+ if (inst.trigger)
+ inst.trigger.remove();
+ var showOn = this._get(inst, 'showOn');
+ if (showOn == 'focus' || showOn == 'both') // pop-up date picker when in the marked field
+ input.focus(this._showDatepicker);
+ if (showOn == 'button' || showOn == 'both') { // pop-up date picker when button clicked
+ var buttonText = this._get(inst, 'buttonText');
+ var buttonImage = this._get(inst, 'buttonImage');
+ inst.trigger = $(this._get(inst, 'buttonImageOnly') ?
+ $('<img/>').addClass(this._triggerClass).
+ attr({ src: buttonImage, alt: buttonText, title: buttonText }) :
+ $('<button type="button"></button>').addClass(this._triggerClass).
+ html(buttonImage == '' ? buttonText : $('<img/>').attr(
+ { src:buttonImage, alt:buttonText, title:buttonText })));
+ input[isRTL ? 'before' : 'after'](inst.trigger);
+ inst.trigger.click(function() {
+ if ($.datepicker._datepickerShowing && $.datepicker._lastInput == input[0])
+ $.datepicker._hideDatepicker();
+ else if ($.datepicker._datepickerShowing && $.datepicker._lastInput != input[0]) {
+ $.datepicker._hideDatepicker();
+ $.datepicker._showDatepicker(input[0]);
+ } else
+ $.datepicker._showDatepicker(input[0]);
+ return false;
+ });
+ }
+ },
+
+ /* Apply the maximum length for the date format. */
+ _autoSize: function(inst) {
+ if (this._get(inst, 'autoSize') && !inst.inline) {
+ var date = new Date(2009, 12 - 1, 20); // Ensure double digits
+ var dateFormat = this._get(inst, 'dateFormat');
+ if (dateFormat.match(/[DM]/)) {
+ var findMax = function(names) {
+ var max = 0;
+ var maxI = 0;
+ for (var i = 0; i < names.length; i++) {
+ if (names[i].length > max) {
+ max = names[i].length;
+ maxI = i;
+ }
+ }
+ return maxI;
+ };
+ date.setMonth(findMax(this._get(inst, (dateFormat.match(/MM/) ?
+ 'monthNames' : 'monthNamesShort'))));
+ date.setDate(findMax(this._get(inst, (dateFormat.match(/DD/) ?
+ 'dayNames' : 'dayNamesShort'))) + 20 - date.getDay());
+ }
+ inst.input.attr('size', this._formatDate(inst, date).length);
+ }
+ },
+
+ /* Attach an inline date picker to a div. */
+ _inlineDatepicker: function(target, inst) {
+ var divSpan = $(target);
+ if (divSpan.hasClass(this.markerClassName))
+ return;
+ divSpan.addClass(this.markerClassName).append(inst.dpDiv).
+ bind("setData.datepicker", function(event, key, value){
+ inst.settings[key] = value;
+ }).bind("getData.datepicker", function(event, key){
+ return this._get(inst, key);
+ });
+ $.data(target, PROP_NAME, inst);
+ this._setDate(inst, this._getDefaultDate(inst), true);
+ this._updateDatepicker(inst);
+ this._updateAlternate(inst);
+ //If disabled option is true, disable the datepicker before showing it (see ticket #5665)
+ if( inst.settings.disabled ) {
+ this._disableDatepicker( target );
+ }
+ // Set display:block in place of inst.dpDiv.show() which won't work on disconnected elements
+ // http://bugs.jqueryui.com/ticket/7552 - A Datepicker created on a detached div has zero height
+ inst.dpDiv.css( "display", "block" );
+ },
+
+ /* Pop-up the date picker in a "dialog" box.
+ @param input element - ignored
+ @param date string or Date - the initial date to display
+ @param onSelect function - the function to call when a date is selected
+ @param settings object - update the dialog date picker instance's settings (anonymous object)
+ @param pos int[2] - coordinates for the dialog's position within the screen or
+ event - with x/y coordinates or
+ leave empty for default (screen centre)
+ @return the manager object */
+ _dialogDatepicker: function(input, date, onSelect, settings, pos) {
+ var inst = this._dialogInst; // internal instance
+ if (!inst) {
+ this.uuid += 1;
+ var id = 'dp' + this.uuid;
+ this._dialogInput = $('<input type="text" id="' + id +
+ '" style="position: absolute; top: -100px; width: 0px;"/>');
+ this._dialogInput.keydown(this._doKeyDown);
+ $('body').append(this._dialogInput);
+ inst = this._dialogInst = this._newInst(this._dialogInput, false);
+ inst.settings = {};
+ $.data(this._dialogInput[0], PROP_NAME, inst);
+ }
+ extendRemove(inst.settings, settings || {});
+ date = (date && date.constructor == Date ? this._formatDate(inst, date) : date);
+ this._dialogInput.val(date);
+
+ this._pos = (pos ? (pos.length ? pos : [pos.pageX, pos.pageY]) : null);
+ if (!this._pos) {
+ var browserWidth = document.documentElement.clientWidth;
+ var browserHeight = document.documentElement.clientHeight;
+ var scrollX = document.documentElement.scrollLeft || document.body.scrollLeft;
+ var scrollY = document.documentElement.scrollTop || document.body.scrollTop;
+ this._pos = // should use actual width/height below
+ [(browserWidth / 2) - 100 + scrollX, (browserHeight / 2) - 150 + scrollY];
+ }
+
+ // move input on screen for focus, but hidden behind dialog
+ this._dialogInput.css('left', (this._pos[0] + 20) + 'px').css('top', this._pos[1] + 'px');
+ inst.settings.onSelect = onSelect;
+ this._inDialog = true;
+ this.dpDiv.addClass(this._dialogClass);
+ this._showDatepicker(this._dialogInput[0]);
+ if ($.blockUI)
+ $.blockUI(this.dpDiv);
+ $.data(this._dialogInput[0], PROP_NAME, inst);
+ return this;
+ },
+
+ /* Detach a datepicker from its control.
+ @param target element - the target input field or division or span */
+ _destroyDatepicker: function(target) {
+ var $target = $(target);
+ var inst = $.data(target, PROP_NAME);
+ if (!$target.hasClass(this.markerClassName)) {
+ return;
+ }
+ var nodeName = target.nodeName.toLowerCase();
+ $.removeData(target, PROP_NAME);
+ if (nodeName == 'input') {
+ inst.append.remove();
+ inst.trigger.remove();
+ $target.removeClass(this.markerClassName).
+ unbind('focus', this._showDatepicker).
+ unbind('keydown', this._doKeyDown).
+ unbind('keypress', this._doKeyPress).
+ unbind('keyup', this._doKeyUp);
+ } else if (nodeName == 'div' || nodeName == 'span')
+ $target.removeClass(this.markerClassName).empty();
+ },
+
+ /* Enable the date picker to a jQuery selection.
+ @param target element - the target input field or division or span */
+ _enableDatepicker: function(target) {
+ var $target = $(target);
+ var inst = $.data(target, PROP_NAME);
+ if (!$target.hasClass(this.markerClassName)) {
+ return;
+ }
+ var nodeName = target.nodeName.toLowerCase();
+ if (nodeName == 'input') {
+ target.disabled = false;
+ inst.trigger.filter('button').
+ each(function() { this.disabled = false; }).end().
+ filter('img').css({opacity: '1.0', cursor: ''});
+ }
+ else if (nodeName == 'div' || nodeName == 'span') {
+ var inline = $target.children('.' + this._inlineClass);
+ inline.children().removeClass('ui-state-disabled');
+ inline.find("select.ui-datepicker-month, select.ui-datepicker-year").
+ prop("disabled", false);
+ }
+ this._disabledInputs = $.map(this._disabledInputs,
+ function(value) { return (value == target ? null : value); }); // delete entry
+ },
+
+ /* Disable the date picker to a jQuery selection.
+ @param target element - the target input field or division or span */
+ _disableDatepicker: function(target) {
+ var $target = $(target);
+ var inst = $.data(target, PROP_NAME);
+ if (!$target.hasClass(this.markerClassName)) {
+ return;
+ }
+ var nodeName = target.nodeName.toLowerCase();
+ if (nodeName == 'input') {
+ target.disabled = true;
+ inst.trigger.filter('button').
+ each(function() { this.disabled = true; }).end().
+ filter('img').css({opacity: '0.5', cursor: 'default'});
+ }
+ else if (nodeName == 'div' || nodeName == 'span') {
+ var inline = $target.children('.' + this._inlineClass);
+ inline.children().addClass('ui-state-disabled');
+ inline.find("select.ui-datepicker-month, select.ui-datepicker-year").
+ prop("disabled", true);
+ }
+ this._disabledInputs = $.map(this._disabledInputs,
+ function(value) { return (value == target ? null : value); }); // delete entry
+ this._disabledInputs[this._disabledInputs.length] = target;
+ },
+
+ /* Is the first field in a jQuery collection disabled as a datepicker?
+ @param target element - the target input field or division or span
+ @return boolean - true if disabled, false if enabled */
+ _isDisabledDatepicker: function(target) {
+ if (!target) {
+ return false;
+ }
+ for (var i = 0; i < this._disabledInputs.length; i++) {
+ if (this._disabledInputs[i] == target)
+ return true;
+ }
+ return false;
+ },
+
+ /* Retrieve the instance data for the target control.
+ @param target element - the target input field or division or span
+ @return object - the associated instance data
+ @throws error if a jQuery problem getting data */
+ _getInst: function(target) {
+ try {
+ return $.data(target, PROP_NAME);
+ }
+ catch (err) {
+ throw 'Missing instance data for this datepicker';
+ }
+ },
+
+ /* Update or retrieve the settings for a date picker attached to an input field or division.
+ @param target element - the target input field or division or span
+ @param name object - the new settings to update or
+ string - the name of the setting to change or retrieve,
+ when retrieving also 'all' for all instance settings or
+ 'defaults' for all global defaults
+ @param value any - the new value for the setting
+ (omit if above is an object or to retrieve a value) */
+ _optionDatepicker: function(target, name, value) {
+ var inst = this._getInst(target);
+ if (arguments.length == 2 && typeof name == 'string') {
+ return (name == 'defaults' ? $.extend({}, $.datepicker._defaults) :
+ (inst ? (name == 'all' ? $.extend({}, inst.settings) :
+ this._get(inst, name)) : null));
+ }
+ var settings = name || {};
+ if (typeof name == 'string') {
+ settings = {};
+ settings[name] = value;
+ }
+ if (inst) {
+ if (this._curInst == inst) {
+ this._hideDatepicker();
+ }
+ var date = this._getDateDatepicker(target, true);
+ var minDate = this._getMinMaxDate(inst, 'min');
+ var maxDate = this._getMinMaxDate(inst, 'max');
+ extendRemove(inst.settings, settings);
+ // reformat the old minDate/maxDate values if dateFormat changes and a new minDate/maxDate isn't provided
+ if (minDate !== null && settings['dateFormat'] !== undefined && settings['minDate'] === undefined)
+ inst.settings.minDate = this._formatDate(inst, minDate);
+ if (maxDate !== null && settings['dateFormat'] !== undefined && settings['maxDate'] === undefined)
+ inst.settings.maxDate = this._formatDate(inst, maxDate);
+ this._attachments($(target), inst);
+ this._autoSize(inst);
+ this._setDate(inst, date);
+ this._updateAlternate(inst);
+ this._updateDatepicker(inst);
+ }
+ },
+
+ // change method deprecated
+ _changeDatepicker: function(target, name, value) {
+ this._optionDatepicker(target, name, value);
+ },
+
+ /* Redraw the date picker attached to an input field or division.
+ @param target element - the target input field or division or span */
+ _refreshDatepicker: function(target) {
+ var inst = this._getInst(target);
+ if (inst) {
+ this._updateDatepicker(inst);
+ }
+ },
+
+ /* Set the dates for a jQuery selection.
+ @param target element - the target input field or division or span
+ @param date Date - the new date */
+ _setDateDatepicker: function(target, date) {
+ var inst = this._getInst(target);
+ if (inst) {
+ this._setDate(inst, date);
+ this._updateDatepicker(inst);
+ this._updateAlternate(inst);
+ }
+ },
+
+ /* Get the date(s) for the first entry in a jQuery selection.
+ @param target element - the target input field or division or span
+ @param noDefault boolean - true if no default date is to be used
+ @return Date - the current date */
+ _getDateDatepicker: function(target, noDefault) {
+ var inst = this._getInst(target);
+ if (inst && !inst.inline)
+ this._setDateFromField(inst, noDefault);
+ return (inst ? this._getDate(inst) : null);
+ },
+
+ /* Handle keystrokes. */
+ _doKeyDown: function(event) {
+ var inst = $.datepicker._getInst(event.target);
+ var handled = true;
+ var isRTL = inst.dpDiv.is('.ui-datepicker-rtl');
+ inst._keyEvent = true;
+ if ($.datepicker._datepickerShowing)
+ switch (event.keyCode) {
+ case 9: $.datepicker._hideDatepicker();
+ handled = false;
+ break; // hide on tab out
+ case 13: var sel = $('td.' + $.datepicker._dayOverClass + ':not(.' +
+ $.datepicker._currentClass + ')', inst.dpDiv);
+ if (sel[0])
+ $.datepicker._selectDay(event.target, inst.selectedMonth, inst.selectedYear, sel[0]);
+ var onSelect = $.datepicker._get(inst, 'onSelect');
+ if (onSelect) {
+ var dateStr = $.datepicker._formatDate(inst);
+
+ // trigger custom callback
+ onSelect.apply((inst.input ? inst.input[0] : null), [dateStr, inst]);
+ }
+ else
+ $.datepicker._hideDatepicker();
+ return false; // don't submit the form
+ break; // select the value on enter
+ case 27: $.datepicker._hideDatepicker();
+ break; // hide on escape
+ case 33: $.datepicker._adjustDate(event.target, (event.ctrlKey ?
+ -$.datepicker._get(inst, 'stepBigMonths') :
+ -$.datepicker._get(inst, 'stepMonths')), 'M');
+ break; // previous month/year on page up/+ ctrl
+ case 34: $.datepicker._adjustDate(event.target, (event.ctrlKey ?
+ +$.datepicker._get(inst, 'stepBigMonths') :
+ +$.datepicker._get(inst, 'stepMonths')), 'M');
+ break; // next month/year on page down/+ ctrl
+ case 35: if (event.ctrlKey || event.metaKey) $.datepicker._clearDate(event.target);
+ handled = event.ctrlKey || event.metaKey;
+ break; // clear on ctrl or command +end
+ case 36: if (event.ctrlKey || event.metaKey) $.datepicker._gotoToday(event.target);
+ handled = event.ctrlKey || event.metaKey;
+ break; // current on ctrl or command +home
+ case 37: if (event.ctrlKey || event.metaKey) $.datepicker._adjustDate(event.target, (isRTL ? +1 : -1), 'D');
+ handled = event.ctrlKey || event.metaKey;
+ // -1 day on ctrl or command +left
+ if (event.originalEvent.altKey) $.datepicker._adjustDate(event.target, (event.ctrlKey ?
+ -$.datepicker._get(inst, 'stepBigMonths') :
+ -$.datepicker._get(inst, 'stepMonths')), 'M');
+ // next month/year on alt +left on Mac
+ break;
+ case 38: if (event.ctrlKey || event.metaKey) $.datepicker._adjustDate(event.target, -7, 'D');
+ handled = event.ctrlKey || event.metaKey;
+ break; // -1 week on ctrl or command +up
+ case 39: if (event.ctrlKey || event.metaKey) $.datepicker._adjustDate(event.target, (isRTL ? -1 : +1), 'D');
+ handled = event.ctrlKey || event.metaKey;
+ // +1 day on ctrl or command +right
+ if (event.originalEvent.altKey) $.datepicker._adjustDate(event.target, (event.ctrlKey ?
+ +$.datepicker._get(inst, 'stepBigMonths') :
+ +$.datepicker._get(inst, 'stepMonths')), 'M');
+ // next month/year on alt +right
+ break;
+ case 40: if (event.ctrlKey || event.metaKey) $.datepicker._adjustDate(event.target, +7, 'D');
+ handled = event.ctrlKey || event.metaKey;
+ break; // +1 week on ctrl or command +down
+ default: handled = false;
+ }
+ else if (event.keyCode == 36 && event.ctrlKey) // display the date picker on ctrl+home
+ $.datepicker._showDatepicker(this);
+ else {
+ handled = false;
+ }
+ if (handled) {
+ event.preventDefault();
+ event.stopPropagation();
+ }
+ },
+
+ /* Filter entered characters - based on date format. */
+ _doKeyPress: function(event) {
+ var inst = $.datepicker._getInst(event.target);
+ if ($.datepicker._get(inst, 'constrainInput')) {
+ var chars = $.datepicker._possibleChars($.datepicker._get(inst, 'dateFormat'));
+ var chr = String.fromCharCode(event.charCode == undefined ? event.keyCode : event.charCode);
+ return event.ctrlKey || event.metaKey || (chr < ' ' || !chars || chars.indexOf(chr) > -1);
+ }
+ },
+
+ /* Synchronise manual entry and field/alternate field. */
+ _doKeyUp: function(event) {
+ var inst = $.datepicker._getInst(event.target);
+ if (inst.input.val() != inst.lastVal) {
+ try {
+ var date = $.datepicker.parseDate($.datepicker._get(inst, 'dateFormat'),
+ (inst.input ? inst.input.val() : null),
+ $.datepicker._getFormatConfig(inst));
+ if (date) { // only if valid
+ $.datepicker._setDateFromField(inst);
+ $.datepicker._updateAlternate(inst);
+ $.datepicker._updateDatepicker(inst);
+ }
+ }
+ catch (err) {
+ $.datepicker.log(err);
+ }
+ }
+ return true;
+ },
+
+ /* Pop-up the date picker for a given input field.
+ If false returned from beforeShow event handler do not show.
+ @param input element - the input field attached to the date picker or
+ event - if triggered by focus */
+ _showDatepicker: function(input) {
+ input = input.target || input;
+ if (input.nodeName.toLowerCase() != 'input') // find from button/image trigger
+ input = $('input', input.parentNode)[0];
+ if ($.datepicker._isDisabledDatepicker(input) || $.datepicker._lastInput == input) // already here
+ return;
+ var inst = $.datepicker._getInst(input);
+ if ($.datepicker._curInst && $.datepicker._curInst != inst) {
+ $.datepicker._curInst.dpDiv.stop(true, true);
+ if ( inst && $.datepicker._datepickerShowing ) {
+ $.datepicker._hideDatepicker( $.datepicker._curInst.input[0] );
+ }
+ }
+ var beforeShow = $.datepicker._get(inst, 'beforeShow');
+ var beforeShowSettings = beforeShow ? beforeShow.apply(input, [input, inst]) : {};
+ if(beforeShowSettings === false){
+ //false
+ return;
+ }
+ extendRemove(inst.settings, beforeShowSettings);
+ inst.lastVal = null;
+ $.datepicker._lastInput = input;
+ $.datepicker._setDateFromField(inst);
+ if ($.datepicker._inDialog) // hide cursor
+ input.value = '';
+ if (!$.datepicker._pos) { // position below input
+ $.datepicker._pos = $.datepicker._findPos(input);
+ $.datepicker._pos[1] += input.offsetHeight; // add the height
+ }
+ var isFixed = false;
+ $(input).parents().each(function() {
+ isFixed |= $(this).css('position') == 'fixed';
+ return !isFixed;
+ });
+ var offset = {left: $.datepicker._pos[0], top: $.datepicker._pos[1]};
+ $.datepicker._pos = null;
+ //to avoid flashes on Firefox
+ inst.dpDiv.empty();
+ // determine sizing offscreen
+ inst.dpDiv.css({position: 'absolute', display: 'block', top: '-1000px'});
+ $.datepicker._updateDatepicker(inst);
+ // fix width for dynamic number of date pickers
+ // and adjust position before showing
+ offset = $.datepicker._checkOffset(inst, offset, isFixed);
+ inst.dpDiv.css({position: ($.datepicker._inDialog && $.blockUI ?
+ 'static' : (isFixed ? 'fixed' : 'absolute')), display: 'none',
+ left: offset.left + 'px', top: offset.top + 'px'});
+ if (!inst.inline) {
+ var showAnim = $.datepicker._get(inst, 'showAnim');
+ var duration = $.datepicker._get(inst, 'duration');
+ var postProcess = function() {
+ var cover = inst.dpDiv.find('iframe.ui-datepicker-cover'); // IE6- only
+ if( !! cover.length ){
+ var borders = $.datepicker._getBorders(inst.dpDiv);
+ cover.css({left: -borders[0], top: -borders[1],
+ width: inst.dpDiv.outerWidth(), height: inst.dpDiv.outerHeight()});
+ }
+ };
+ inst.dpDiv.zIndex($(input).zIndex()+1);
+ $.datepicker._datepickerShowing = true;
+
+ // DEPRECATED: after BC for 1.8.x $.effects[ showAnim ] is not needed
+ if ( $.effects && ( $.effects.effect[ showAnim ] || $.effects[ showAnim ] ) )
+ inst.dpDiv.show(showAnim, $.datepicker._get(inst, 'showOptions'), duration, postProcess);
+ else
+ inst.dpDiv[showAnim || 'show']((showAnim ? duration : null), postProcess);
+ if (!showAnim || !duration)
+ postProcess();
+ if (inst.input.is(':visible') && !inst.input.is(':disabled'))
+ inst.input.focus();
+ $.datepicker._curInst = inst;
+ }
+ },
+
+ /* Generate the date picker content. */
+ _updateDatepicker: function(inst) {
+ this.maxRows = 4; //Reset the max number of rows being displayed (see #7043)
+ var borders = $.datepicker._getBorders(inst.dpDiv);
+ instActive = inst; // for delegate hover events
+ inst.dpDiv.empty().append(this._generateHTML(inst));
+ this._attachHandlers(inst);
+ var cover = inst.dpDiv.find('iframe.ui-datepicker-cover'); // IE6- only
+ if( !!cover.length ){ //avoid call to outerXXXX() when not in IE6
+ cover.css({left: -borders[0], top: -borders[1], width: inst.dpDiv.outerWidth(), height: inst.dpDiv.outerHeight()})
+ }
+ inst.dpDiv.find('.' + this._dayOverClass + ' a').mouseover();
+ var numMonths = this._getNumberOfMonths(inst);
+ var cols = numMonths[1];
+ var width = 17;
+ inst.dpDiv.removeClass('ui-datepicker-multi-2 ui-datepicker-multi-3 ui-datepicker-multi-4').width('');
+ if (cols > 1)
+ inst.dpDiv.addClass('ui-datepicker-multi-' + cols).css('width', (width * cols) + 'em');
+ inst.dpDiv[(numMonths[0] != 1 || numMonths[1] != 1 ? 'add' : 'remove') +
+ 'Class']('ui-datepicker-multi');
+ inst.dpDiv[(this._get(inst, 'isRTL') ? 'add' : 'remove') +
+ 'Class']('ui-datepicker-rtl');
+ if (inst == $.datepicker._curInst && $.datepicker._datepickerShowing && inst.input &&
+ // #6694 - don't focus the input if it's already focused
+ // this breaks the change event in IE
+ inst.input.is(':visible') && !inst.input.is(':disabled') && inst.input[0] != document.activeElement)
+ inst.input.focus();
+ // deffered render of the years select (to avoid flashes on Firefox)
+ if( inst.yearshtml ){
+ var origyearshtml = inst.yearshtml;
+ setTimeout(function(){
+ //assure that inst.yearshtml didn't change.
+ if( origyearshtml === inst.yearshtml && inst.yearshtml ){
+ inst.dpDiv.find('select.ui-datepicker-year:first').replaceWith(inst.yearshtml);
+ }
+ origyearshtml = inst.yearshtml = null;
+ }, 0);
+ }
+ },
+
+ /* Retrieve the size of left and top borders for an element.
+ @param elem (jQuery object) the element of interest
+ @return (number[2]) the left and top borders */
+ _getBorders: function(elem) {
+ var convert = function(value) {
+ return {thin: 1, medium: 2, thick: 3}[value] || value;
+ };
+ return [parseFloat(convert(elem.css('border-left-width'))),
+ parseFloat(convert(elem.css('border-top-width')))];
+ },
+
+ /* Check positioning to remain on screen. */
+ _checkOffset: function(inst, offset, isFixed) {
+ var dpWidth = inst.dpDiv.outerWidth();
+ var dpHeight = inst.dpDiv.outerHeight();
+ var inputWidth = inst.input ? inst.input.outerWidth() : 0;
+ var inputHeight = inst.input ? inst.input.outerHeight() : 0;
+ var viewWidth = document.documentElement.clientWidth + (isFixed ? 0 : $(document).scrollLeft());
+ var viewHeight = document.documentElement.clientHeight + (isFixed ? 0 : $(document).scrollTop());
+
+ offset.left -= (this._get(inst, 'isRTL') ? (dpWidth - inputWidth) : 0);
+ offset.left -= (isFixed && offset.left == inst.input.offset().left) ? $(document).scrollLeft() : 0;
+ offset.top -= (isFixed && offset.top == (inst.input.offset().top + inputHeight)) ? $(document).scrollTop() : 0;
+
+ // now check if datepicker is showing outside window viewport - move to a better place if so.
+ offset.left -= Math.min(offset.left, (offset.left + dpWidth > viewWidth && viewWidth > dpWidth) ?
+ Math.abs(offset.left + dpWidth - viewWidth) : 0);
+ offset.top -= Math.min(offset.top, (offset.top + dpHeight > viewHeight && viewHeight > dpHeight) ?
+ Math.abs(dpHeight + inputHeight) : 0);
+
+ return offset;
+ },
+
+ /* Find an object's position on the screen. */
+ _findPos: function(obj) {
+ var inst = this._getInst(obj);
+ var isRTL = this._get(inst, 'isRTL');
+ while (obj && (obj.type == 'hidden' || obj.nodeType != 1 || $.expr.filters.hidden(obj))) {
+ obj = obj[isRTL ? 'previousSibling' : 'nextSibling'];
+ }
+ var position = $(obj).offset();
+ return [position.left, position.top];
+ },
+
+ /* Hide the date picker from view.
+ @param input element - the input field attached to the date picker */
+ _hideDatepicker: function(input) {
+ var inst = this._curInst;
+ if (!inst || (input && inst != $.data(input, PROP_NAME)))
+ return;
+ if (this._datepickerShowing) {
+ var showAnim = this._get(inst, 'showAnim');
+ var duration = this._get(inst, 'duration');
+ var postProcess = function() {
+ $.datepicker._tidyDialog(inst);
+ };
+
+ // DEPRECATED: after BC for 1.8.x $.effects[ showAnim ] is not needed
+ if ( $.effects && ( $.effects.effect[ showAnim ] || $.effects[ showAnim ] ) )
+ inst.dpDiv.hide(showAnim, $.datepicker._get(inst, 'showOptions'), duration, postProcess);
+ else
+ inst.dpDiv[(showAnim == 'slideDown' ? 'slideUp' :
+ (showAnim == 'fadeIn' ? 'fadeOut' : 'hide'))]((showAnim ? duration : null), postProcess);
+ if (!showAnim)
+ postProcess();
+ this._datepickerShowing = false;
+ var onClose = this._get(inst, 'onClose');
+ if (onClose)
+ onClose.apply((inst.input ? inst.input[0] : null),
+ [(inst.input ? inst.input.val() : ''), inst]);
+ this._lastInput = null;
+ if (this._inDialog) {
+ this._dialogInput.css({ position: 'absolute', left: '0', top: '-100px' });
+ if ($.blockUI) {
+ $.unblockUI();
+ $('body').append(this.dpDiv);
+ }
+ }
+ this._inDialog = false;
+ }
+ },
+
+ /* Tidy up after a dialog display. */
+ _tidyDialog: function(inst) {
+ inst.dpDiv.removeClass(this._dialogClass).unbind('.ui-datepicker-calendar');
+ },
+
+ /* Close date picker if clicked elsewhere. */
+ _checkExternalClick: function(event) {
+ if (!$.datepicker._curInst)
+ return;
+
+ var $target = $(event.target),
+ inst = $.datepicker._getInst($target[0]);
+
+ if ( ( ( $target[0].id != $.datepicker._mainDivId &&
+ $target.parents('#' + $.datepicker._mainDivId).length == 0 &&
+ !$target.hasClass($.datepicker.markerClassName) &&
+ !$target.closest("." + $.datepicker._triggerClass).length &&
+ $.datepicker._datepickerShowing && !($.datepicker._inDialog && $.blockUI) ) ) ||
+ ( $target.hasClass($.datepicker.markerClassName) && $.datepicker._curInst != inst ) )
+ $.datepicker._hideDatepicker();
+ },
+
+ /* Adjust one of the date sub-fields. */
+ _adjustDate: function(id, offset, period) {
+ var target = $(id);
+ var inst = this._getInst(target[0]);
+ if (this._isDisabledDatepicker(target[0])) {
+ return;
+ }
+ this._adjustInstDate(inst, offset +
+ (period == 'M' ? this._get(inst, 'showCurrentAtPos') : 0), // undo positioning
+ period);
+ this._updateDatepicker(inst);
+ },
+
+ /* Action for current link. */
+ _gotoToday: function(id) {
+ var target = $(id);
+ var inst = this._getInst(target[0]);
+ if (this._get(inst, 'gotoCurrent') && inst.currentDay) {
+ inst.selectedDay = inst.currentDay;
+ inst.drawMonth = inst.selectedMonth = inst.currentMonth;
+ inst.drawYear = inst.selectedYear = inst.currentYear;
+ }
+ else {
+ var date = new Date();
+ inst.selectedDay = date.getDate();
+ inst.drawMonth = inst.selectedMonth = date.getMonth();
+ inst.drawYear = inst.selectedYear = date.getFullYear();
+ }
+ this._notifyChange(inst);
+ this._adjustDate(target);
+ },
+
+ /* Action for selecting a new month/year. */
+ _selectMonthYear: function(id, select, period) {
+ var target = $(id);
+ var inst = this._getInst(target[0]);
+ inst['selected' + (period == 'M' ? 'Month' : 'Year')] =
+ inst['draw' + (period == 'M' ? 'Month' : 'Year')] =
+ parseInt(select.options[select.selectedIndex].value,10);
+ this._notifyChange(inst);
+ this._adjustDate(target);
+ },
+
+ /* Action for selecting a day. */
+ _selectDay: function(id, month, year, td) {
+ var target = $(id);
+ if ($(td).hasClass(this._unselectableClass) || this._isDisabledDatepicker(target[0])) {
+ return;
+ }
+ var inst = this._getInst(target[0]);
+ inst.selectedDay = inst.currentDay = $('a', td).html();
+ inst.selectedMonth = inst.currentMonth = month;
+ inst.selectedYear = inst.currentYear = year;
+ this._selectDate(id, this._formatDate(inst,
+ inst.currentDay, inst.currentMonth, inst.currentYear));
+ },
+
+ /* Erase the input field and hide the date picker. */
+ _clearDate: function(id) {
+ var target = $(id);
+ var inst = this._getInst(target[0]);
+ this._selectDate(target, '');
+ },
+
+ /* Update the input field with the selected date. */
+ _selectDate: function(id, dateStr) {
+ var target = $(id);
+ var inst = this._getInst(target[0]);
+ dateStr = (dateStr != null ? dateStr : this._formatDate(inst));
+ if (inst.input)
+ inst.input.val(dateStr);
+ this._updateAlternate(inst);
+ var onSelect = this._get(inst, 'onSelect');
+ if (onSelect)
+ onSelect.apply((inst.input ? inst.input[0] : null), [dateStr, inst]); // trigger custom callback
+ else if (inst.input)
+ inst.input.trigger('change'); // fire the change event
+ if (inst.inline)
+ this._updateDatepicker(inst);
+ else {
+ this._hideDatepicker();
+ this._lastInput = inst.input[0];
+ if (typeof(inst.input[0]) != 'object')
+ inst.input.focus(); // restore focus
+ this._lastInput = null;
+ }
+ },
+
+ /* Update any alternate field to synchronise with the main field. */
+ _updateAlternate: function(inst) {
+ var altField = this._get(inst, 'altField');
+ if (altField) { // update alternate field too
+ var altFormat = this._get(inst, 'altFormat') || this._get(inst, 'dateFormat');
+ var date = this._getDate(inst);
+ var dateStr = this.formatDate(altFormat, date, this._getFormatConfig(inst));
+ $(altField).each(function() { $(this).val(dateStr); });
+ }
+ },
+
+ /* Set as beforeShowDay function to prevent selection of weekends.
+ @param date Date - the date to customise
+ @return [boolean, string] - is this date selectable?, what is its CSS class? */
+ noWeekends: function(date) {
+ var day = date.getDay();
+ return [(day > 0 && day < 6), ''];
+ },
+
+ /* Set as calculateWeek to determine the week of the year based on the ISO 8601 definition.
+ @param date Date - the date to get the week for
+ @return number - the number of the week within the year that contains this date */
+ iso8601Week: function(date) {
+ var checkDate = new Date(date.getTime());
+ // Find Thursday of this week starting on Monday
+ checkDate.setDate(checkDate.getDate() + 4 - (checkDate.getDay() || 7));
+ var time = checkDate.getTime();
+ checkDate.setMonth(0); // Compare with Jan 1
+ checkDate.setDate(1);
+ return Math.floor(Math.round((time - checkDate) / 86400000) / 7) + 1;
+ },
+
+ /* Parse a string value into a date object.
+ See formatDate below for the possible formats.
+
+ @param format string - the expected format of the date
+ @param value string - the date in the above format
+ @param settings Object - attributes include:
+ shortYearCutoff number - the cutoff year for determining the century (optional)
+ dayNamesShort string[7] - abbreviated names of the days from Sunday (optional)
+ dayNames string[7] - names of the days from Sunday (optional)
+ monthNamesShort string[12] - abbreviated names of the months (optional)
+ monthNames string[12] - names of the months (optional)
+ @return Date - the extracted date value or null if value is blank */
+ parseDate: function (format, value, settings) {
+ if (format == null || value == null)
+ throw 'Invalid arguments';
+ value = (typeof value == 'object' ? value.toString() : value + '');
+ if (value == '')
+ return null;
+ var shortYearCutoff = (settings ? settings.shortYearCutoff : null) || this._defaults.shortYearCutoff;
+ shortYearCutoff = (typeof shortYearCutoff != 'string' ? shortYearCutoff :
+ new Date().getFullYear() % 100 + parseInt(shortYearCutoff, 10));
+ var dayNamesShort = (settings ? settings.dayNamesShort : null) || this._defaults.dayNamesShort;
+ var dayNames = (settings ? settings.dayNames : null) || this._defaults.dayNames;
+ var monthNamesShort = (settings ? settings.monthNamesShort : null) || this._defaults.monthNamesShort;
+ var monthNames = (settings ? settings.monthNames : null) || this._defaults.monthNames;
+ var year = -1;
+ var month = -1;
+ var day = -1;
+ var doy = -1;
+ var literal = false;
+ // Check whether a format character is doubled
+ var lookAhead = function(match) {
+ var matches = (iFormat + 1 < format.length && format.charAt(iFormat + 1) == match);
+ if (matches)
+ iFormat++;
+ return matches;
+ };
+ // Extract a number from the string value
+ var getNumber = function(match) {
+ var isDoubled = lookAhead(match);
+ var size = (match == '@' ? 14 : (match == '!' ? 20 :
+ (match == 'y' && isDoubled ? 4 : (match == 'o' ? 3 : 2))));
+ var digits = new RegExp('^\\d{1,' + size + '}');
+ var num = value.substring(iValue).match(digits);
+ if (!num)
+ throw 'Missing number at position ' + iValue;
+ iValue += num[0].length;
+ return parseInt(num[0], 10);
+ };
+ // Extract a name from the string value and convert to an index
+ var getName = function(match, shortNames, longNames) {
+ var names = $.map(lookAhead(match) ? longNames : shortNames, function (v, k) {
+ return [ [k, v] ];
+ }).sort(function (a, b) {
+ return -(a[1].length - b[1].length);
+ });
+ var index = -1;
+ $.each(names, function (i, pair) {
+ var name = pair[1];
+ if (value.substr(iValue, name.length).toLowerCase() == name.toLowerCase()) {
+ index = pair[0];
+ iValue += name.length;
+ return false;
+ }
+ });
+ if (index != -1)
+ return index + 1;
+ else
+ throw 'Unknown name at position ' + iValue;
+ };
+ // Confirm that a literal character matches the string value
+ var checkLiteral = function() {
+ if (value.charAt(iValue) != format.charAt(iFormat))
+ throw 'Unexpected literal at position ' + iValue;
+ iValue++;
+ };
+ var iValue = 0;
+ for (var iFormat = 0; iFormat < format.length; iFormat++) {
+ if (literal)
+ if (format.charAt(iFormat) == "'" && !lookAhead("'"))
+ literal = false;
+ else
+ checkLiteral();
+ else
+ switch (format.charAt(iFormat)) {
+ case 'd':
+ day = getNumber('d');
+ break;
+ case 'D':
+ getName('D', dayNamesShort, dayNames);
+ break;
+ case 'o':
+ doy = getNumber('o');
+ break;
+ case 'm':
+ month = getNumber('m');
+ break;
+ case 'M':
+ month = getName('M', monthNamesShort, monthNames);
+ break;
+ case 'y':
+ year = getNumber('y');
+ break;
+ case '@':
+ var date = new Date(getNumber('@'));
+ year = date.getFullYear();
+ month = date.getMonth() + 1;
+ day = date.getDate();
+ break;
+ case '!':
+ var date = new Date((getNumber('!') - this._ticksTo1970) / 10000);
+ year = date.getFullYear();
+ month = date.getMonth() + 1;
+ day = date.getDate();
+ break;
+ case "'":
+ if (lookAhead("'"))
+ checkLiteral();
+ else
+ literal = true;
+ break;
+ default:
+ checkLiteral();
+ }
+ }
+ if (iValue < value.length){
+ var extra = value.substr(iValue);
+ if (!/^\s+/.test(extra)) {
+ throw "Extra/unparsed characters found in date: " + extra;
+ }
+ }
+ if (year == -1)
+ year = new Date().getFullYear();
+ else if (year < 100)
+ year += new Date().getFullYear() - new Date().getFullYear() % 100 +
+ (year <= shortYearCutoff ? 0 : -100);
+ if (doy > -1) {
+ month = 1;
+ day = doy;
+ do {
+ var dim = this._getDaysInMonth(year, month - 1);
+ if (day <= dim)
+ break;
+ month++;
+ day -= dim;
+ } while (true);
+ }
+ var date = this._daylightSavingAdjust(new Date(year, month - 1, day));
+ if (date.getFullYear() != year || date.getMonth() + 1 != month || date.getDate() != day)
+ throw 'Invalid date'; // E.g. 31/02/00
+ return date;
+ },
+
+ /* Standard date formats. */
+ ATOM: 'yy-mm-dd', // RFC 3339 (ISO 8601)
+ COOKIE: 'D, dd M yy',
+ ISO_8601: 'yy-mm-dd',
+ RFC_822: 'D, d M y',
+ RFC_850: 'DD, dd-M-y',
+ RFC_1036: 'D, d M y',
+ RFC_1123: 'D, d M yy',
+ RFC_2822: 'D, d M yy',
+ RSS: 'D, d M y', // RFC 822
+ TICKS: '!',
+ TIMESTAMP: '@',
+ W3C: 'yy-mm-dd', // ISO 8601
+
+ _ticksTo1970: (((1970 - 1) * 365 + Math.floor(1970 / 4) - Math.floor(1970 / 100) +
+ Math.floor(1970 / 400)) * 24 * 60 * 60 * 10000000),
+
+ /* Format a date object into a string value.
+ The format can be combinations of the following:
+ d - day of month (no leading zero)
+ dd - day of month (two digit)
+ o - day of year (no leading zeros)
+ oo - day of year (three digit)
+ D - day name short
+ DD - day name long
+ m - month of year (no leading zero)
+ mm - month of year (two digit)
+ M - month name short
+ MM - month name long
+ y - year (two digit)
+ yy - year (four digit)
+ @ - Unix timestamp (ms since 01/01/1970)
+ ! - Windows ticks (100ns since 01/01/0001)
+ '...' - literal text
+ '' - single quote
+
+ @param format string - the desired format of the date
+ @param date Date - the date value to format
+ @param settings Object - attributes include:
+ dayNamesShort string[7] - abbreviated names of the days from Sunday (optional)
+ dayNames string[7] - names of the days from Sunday (optional)
+ monthNamesShort string[12] - abbreviated names of the months (optional)
+ monthNames string[12] - names of the months (optional)
+ @return string - the date in the above format */
+ formatDate: function (format, date, settings) {
+ if (!date)
+ return '';
+ var dayNamesShort = (settings ? settings.dayNamesShort : null) || this._defaults.dayNamesShort;
+ var dayNames = (settings ? settings.dayNames : null) || this._defaults.dayNames;
+ var monthNamesShort = (settings ? settings.monthNamesShort : null) || this._defaults.monthNamesShort;
+ var monthNames = (settings ? settings.monthNames : null) || this._defaults.monthNames;
+ // Check whether a format character is doubled
+ var lookAhead = function(match) {
+ var matches = (iFormat + 1 < format.length && format.charAt(iFormat + 1) == match);
+ if (matches)
+ iFormat++;
+ return matches;
+ };
+ // Format a number, with leading zero if necessary
+ var formatNumber = function(match, value, len) {
+ var num = '' + value;
+ if (lookAhead(match))
+ while (num.length < len)
+ num = '0' + num;
+ return num;
+ };
+ // Format a name, short or long as requested
+ var formatName = function(match, value, shortNames, longNames) {
+ return (lookAhead(match) ? longNames[value] : shortNames[value]);
+ };
+ var output = '';
+ var literal = false;
+ if (date)
+ for (var iFormat = 0; iFormat < format.length; iFormat++) {
+ if (literal)
+ if (format.charAt(iFormat) == "'" && !lookAhead("'"))
+ literal = false;
+ else
+ output += format.charAt(iFormat);
+ else
+ switch (format.charAt(iFormat)) {
+ case 'd':
+ output += formatNumber('d', date.getDate(), 2);
+ break;
+ case 'D':
+ output += formatName('D', date.getDay(), dayNamesShort, dayNames);
+ break;
+ case 'o':
+ output += formatNumber('o',
+ Math.round((new Date(date.getFullYear(), date.getMonth(), date.getDate()).getTime() - new Date(date.getFullYear(), 0, 0).getTime()) / 86400000), 3);
+ break;
+ case 'm':
+ output += formatNumber('m', date.getMonth() + 1, 2);
+ break;
+ case 'M':
+ output += formatName('M', date.getMonth(), monthNamesShort, monthNames);
+ break;
+ case 'y':
+ output += (lookAhead('y') ? date.getFullYear() :
+ (date.getYear() % 100 < 10 ? '0' : '') + date.getYear() % 100);
+ break;
+ case '@':
+ output += date.getTime();
+ break;
+ case '!':
+ output += date.getTime() * 10000 + this._ticksTo1970;
+ break;
+ case "'":
+ if (lookAhead("'"))
+ output += "'";
+ else
+ literal = true;
+ break;
+ default:
+ output += format.charAt(iFormat);
+ }
+ }
+ return output;
+ },
+
+ /* Extract all possible characters from the date format. */
+ _possibleChars: function (format) {
+ var chars = '';
+ var literal = false;
+ // Check whether a format character is doubled
+ var lookAhead = function(match) {
+ var matches = (iFormat + 1 < format.length && format.charAt(iFormat + 1) == match);
+ if (matches)
+ iFormat++;
+ return matches;
+ };
+ for (var iFormat = 0; iFormat < format.length; iFormat++)
+ if (literal)
+ if (format.charAt(iFormat) == "'" && !lookAhead("'"))
+ literal = false;
+ else
+ chars += format.charAt(iFormat);
+ else
+ switch (format.charAt(iFormat)) {
+ case 'd': case 'm': case 'y': case '@':
+ chars += '0123456789';
+ break;
+ case 'D': case 'M':
+ return null; // Accept anything
+ case "'":
+ if (lookAhead("'"))
+ chars += "'";
+ else
+ literal = true;
+ break;
+ default:
+ chars += format.charAt(iFormat);
+ }
+ return chars;
+ },
+
+ /* Get a setting value, defaulting if necessary. */
+ _get: function(inst, name) {
+ return inst.settings[name] !== undefined ?
+ inst.settings[name] : this._defaults[name];
+ },
+
+ /* Parse existing date and initialise date picker. */
+ _setDateFromField: function(inst, noDefault) {
+ if (inst.input.val() == inst.lastVal) {
+ return;
+ }
+ var dateFormat = this._get(inst, 'dateFormat');
+ var dates = inst.lastVal = inst.input ? inst.input.val() : null;
+ var date, defaultDate;
+ date = defaultDate = this._getDefaultDate(inst);
+ var settings = this._getFormatConfig(inst);
+ try {
+ date = this.parseDate(dateFormat, dates, settings) || defaultDate;
+ } catch (event) {
+ this.log(event);
+ dates = (noDefault ? '' : dates);
+ }
+ inst.selectedDay = date.getDate();
+ inst.drawMonth = inst.selectedMonth = date.getMonth();
+ inst.drawYear = inst.selectedYear = date.getFullYear();
+ inst.currentDay = (dates ? date.getDate() : 0);
+ inst.currentMonth = (dates ? date.getMonth() : 0);
+ inst.currentYear = (dates ? date.getFullYear() : 0);
+ this._adjustInstDate(inst);
+ },
+
+ /* Retrieve the default date shown on opening. */
+ _getDefaultDate: function(inst) {
+ return this._restrictMinMax(inst,
+ this._determineDate(inst, this._get(inst, 'defaultDate'), new Date()));
+ },
+
+ /* A date may be specified as an exact value or a relative one. */
+ _determineDate: function(inst, date, defaultDate) {
+ var offsetNumeric = function(offset) {
+ var date = new Date();
+ date.setDate(date.getDate() + offset);
+ return date;
+ };
+ var offsetString = function(offset) {
+ try {
+ return $.datepicker.parseDate($.datepicker._get(inst, 'dateFormat'),
+ offset, $.datepicker._getFormatConfig(inst));
+ }
+ catch (e) {
+ // Ignore
+ }
+ var date = (offset.toLowerCase().match(/^c/) ?
+ $.datepicker._getDate(inst) : null) || new Date();
+ var year = date.getFullYear();
+ var month = date.getMonth();
+ var day = date.getDate();
+ var pattern = /([+-]?[0-9]+)\s*(d|D|w|W|m|M|y|Y)?/g;
+ var matches = pattern.exec(offset);
+ while (matches) {
+ switch (matches[2] || 'd') {
+ case 'd' : case 'D' :
+ day += parseInt(matches[1],10); break;
+ case 'w' : case 'W' :
+ day += parseInt(matches[1],10) * 7; break;
+ case 'm' : case 'M' :
+ month += parseInt(matches[1],10);
+ day = Math.min(day, $.datepicker._getDaysInMonth(year, month));
+ break;
+ case 'y': case 'Y' :
+ year += parseInt(matches[1],10);
+ day = Math.min(day, $.datepicker._getDaysInMonth(year, month));
+ break;
+ }
+ matches = pattern.exec(offset);
+ }
+ return new Date(year, month, day);
+ };
+ var newDate = (date == null || date === '' ? defaultDate : (typeof date == 'string' ? offsetString(date) :
+ (typeof date == 'number' ? (isNaN(date) ? defaultDate : offsetNumeric(date)) : new Date(date.getTime()))));
+ newDate = (newDate && newDate.toString() == 'Invalid Date' ? defaultDate : newDate);
+ if (newDate) {
+ newDate.setHours(0);
+ newDate.setMinutes(0);
+ newDate.setSeconds(0);
+ newDate.setMilliseconds(0);
+ }
+ return this._daylightSavingAdjust(newDate);
+ },
+
+ /* Handle switch to/from daylight saving.
+ Hours may be non-zero on daylight saving cut-over:
+ > 12 when midnight changeover, but then cannot generate
+ midnight datetime, so jump to 1AM, otherwise reset.
+ @param date (Date) the date to check
+ @return (Date) the corrected date */
+ _daylightSavingAdjust: function(date) {
+ if (!date) return null;
+ date.setHours(date.getHours() > 12 ? date.getHours() + 2 : 0);
+ return date;
+ },
+
+ /* Set the date(s) directly. */
+ _setDate: function(inst, date, noChange) {
+ var clear = !date;
+ var origMonth = inst.selectedMonth;
+ var origYear = inst.selectedYear;
+ var newDate = this._restrictMinMax(inst, this._determineDate(inst, date, new Date()));
+ inst.selectedDay = inst.currentDay = newDate.getDate();
+ inst.drawMonth = inst.selectedMonth = inst.currentMonth = newDate.getMonth();
+ inst.drawYear = inst.selectedYear = inst.currentYear = newDate.getFullYear();
+ if ((origMonth != inst.selectedMonth || origYear != inst.selectedYear) && !noChange)
+ this._notifyChange(inst);
+ this._adjustInstDate(inst);
+ if (inst.input) {
+ inst.input.val(clear ? '' : this._formatDate(inst));
+ }
+ },
+
+ /* Retrieve the date(s) directly. */
+ _getDate: function(inst) {
+ var startDate = (!inst.currentYear || (inst.input && inst.input.val() == '') ? null :
+ this._daylightSavingAdjust(new Date(
+ inst.currentYear, inst.currentMonth, inst.currentDay)));
+ return startDate;
+ },
+
+ /* Attach the onxxx handlers. These are declared statically so
+ * they work with static code transformers like Caja.
+ */
+ _attachHandlers: function(inst) {
+ var stepMonths = this._get(inst, 'stepMonths');
+ var id = '#' + inst.id.replace( /\\\\/g, "\\" );
+ inst.dpDiv.find('[data-handler]').map(function () {
+ var handler = {
+ prev: function () {
+ window['DP_jQuery_' + dpuuid].datepicker._adjustDate(id, -stepMonths, 'M');
+ },
+ next: function () {
+ window['DP_jQuery_' + dpuuid].datepicker._adjustDate(id, +stepMonths, 'M');
+ },
+ hide: function () {
+ window['DP_jQuery_' + dpuuid].datepicker._hideDatepicker();
+ },
+ today: function () {
+ window['DP_jQuery_' + dpuuid].datepicker._gotoToday(id);
+ },
+ selectDay: function () {
+ window['DP_jQuery_' + dpuuid].datepicker._selectDay(id, +this.getAttribute('data-month'), +this.getAttribute('data-year'), this);
+ return false;
+ },
+ selectMonth: function () {
+ window['DP_jQuery_' + dpuuid].datepicker._selectMonthYear(id, this, 'M');
+ return false;
+ },
+ selectYear: function () {
+ window['DP_jQuery_' + dpuuid].datepicker._selectMonthYear(id, this, 'Y');
+ return false;
+ }
+ };
+ $(this).bind(this.getAttribute('data-event'), handler[this.getAttribute('data-handler')]);
+ });
+ },
+
+ /* Generate the HTML for the current state of the date picker. */
+ _generateHTML: function(inst) {
+ var today = new Date();
+ today = this._daylightSavingAdjust(
+ new Date(today.getFullYear(), today.getMonth(), today.getDate())); // clear time
+ var isRTL = this._get(inst, 'isRTL');
+ var showButtonPanel = this._get(inst, 'showButtonPanel');
+ var hideIfNoPrevNext = this._get(inst, 'hideIfNoPrevNext');
+ var navigationAsDateFormat = this._get(inst, 'navigationAsDateFormat');
+ var numMonths = this._getNumberOfMonths(inst);
+ var showCurrentAtPos = this._get(inst, 'showCurrentAtPos');
+ var stepMonths = this._get(inst, 'stepMonths');
+ var isMultiMonth = (numMonths[0] != 1 || numMonths[1] != 1);
+ var currentDate = this._daylightSavingAdjust((!inst.currentDay ? new Date(9999, 9, 9) :
+ new Date(inst.currentYear, inst.currentMonth, inst.currentDay)));
+ var minDate = this._getMinMaxDate(inst, 'min');
+ var maxDate = this._getMinMaxDate(inst, 'max');
+ var drawMonth = inst.drawMonth - showCurrentAtPos;
+ var drawYear = inst.drawYear;
+ if (drawMonth < 0) {
+ drawMonth += 12;
+ drawYear--;
+ }
+ if (maxDate) {
+ var maxDraw = this._daylightSavingAdjust(new Date(maxDate.getFullYear(),
+ maxDate.getMonth() - (numMonths[0] * numMonths[1]) + 1, maxDate.getDate()));
+ maxDraw = (minDate && maxDraw < minDate ? minDate : maxDraw);
+ while (this._daylightSavingAdjust(new Date(drawYear, drawMonth, 1)) > maxDraw) {
+ drawMonth--;
+ if (drawMonth < 0) {
+ drawMonth = 11;
+ drawYear--;
+ }
+ }
+ }
+ inst.drawMonth = drawMonth;
+ inst.drawYear = drawYear;
+ var prevText = this._get(inst, 'prevText');
+ prevText = (!navigationAsDateFormat ? prevText : this.formatDate(prevText,
+ this._daylightSavingAdjust(new Date(drawYear, drawMonth - stepMonths, 1)),
+ this._getFormatConfig(inst)));
+ var prev = (this._canAdjustMonth(inst, -1, drawYear, drawMonth) ?
+ '<a class="ui-datepicker-prev ui-corner-all" data-handler="prev" data-event="click"' +
+ ' title="' + prevText + '"><span class="ui-icon ui-icon-circle-triangle-' + ( isRTL ? 'e' : 'w') + '">' + prevText + '</span></a>' :
+ (hideIfNoPrevNext ? '' : '<a class="ui-datepicker-prev ui-corner-all ui-state-disabled" title="'+ prevText +'"><span class="ui-icon ui-icon-circle-triangle-' + ( isRTL ? 'e' : 'w') + '">' + prevText + '</span></a>'));
+ var nextText = this._get(inst, 'nextText');
+ nextText = (!navigationAsDateFormat ? nextText : this.formatDate(nextText,
+ this._daylightSavingAdjust(new Date(drawYear, drawMonth + stepMonths, 1)),
+ this._getFormatConfig(inst)));
+ var next = (this._canAdjustMonth(inst, +1, drawYear, drawMonth) ?
+ '<a class="ui-datepicker-next ui-corner-all" data-handler="next" data-event="click"' +
+ ' title="' + nextText + '"><span class="ui-icon ui-icon-circle-triangle-' + ( isRTL ? 'w' : 'e') + '">' + nextText + '</span></a>' :
+ (hideIfNoPrevNext ? '' : '<a class="ui-datepicker-next ui-corner-all ui-state-disabled" title="'+ nextText + '"><span class="ui-icon ui-icon-circle-triangle-' + ( isRTL ? 'w' : 'e') + '">' + nextText + '</span></a>'));
+ var currentText = this._get(inst, 'currentText');
+ var gotoDate = (this._get(inst, 'gotoCurrent') && inst.currentDay ? currentDate : today);
+ currentText = (!navigationAsDateFormat ? currentText :
+ this.formatDate(currentText, gotoDate, this._getFormatConfig(inst)));
+ var controls = (!inst.inline ? '<button type="button" class="ui-datepicker-close ui-state-default ui-priority-primary ui-corner-all" data-handler="hide" data-event="click">' +
+ this._get(inst, 'closeText') + '</button>' : '');
+ var buttonPanel = (showButtonPanel) ? '<div class="ui-datepicker-buttonpane ui-widget-content">' + (isRTL ? controls : '') +
+ (this._isInRange(inst, gotoDate) ? '<button type="button" class="ui-datepicker-current ui-state-default ui-priority-secondary ui-corner-all" data-handler="today" data-event="click"' +
+ '>' + currentText + '</button>' : '') + (isRTL ? '' : controls) + '</div>' : '';
+ var firstDay = parseInt(this._get(inst, 'firstDay'),10);
+ firstDay = (isNaN(firstDay) ? 0 : firstDay);
+ var showWeek = this._get(inst, 'showWeek');
+ var dayNames = this._get(inst, 'dayNames');
+ var dayNamesShort = this._get(inst, 'dayNamesShort');
+ var dayNamesMin = this._get(inst, 'dayNamesMin');
+ var monthNames = this._get(inst, 'monthNames');
+ var monthNamesShort = this._get(inst, 'monthNamesShort');
+ var beforeShowDay = this._get(inst, 'beforeShowDay');
+ var showOtherMonths = this._get(inst, 'showOtherMonths');
+ var selectOtherMonths = this._get(inst, 'selectOtherMonths');
+ var calculateWeek = this._get(inst, 'calculateWeek') || this.iso8601Week;
+ var defaultDate = this._getDefaultDate(inst);
+ var html = '';
+ for (var row = 0; row < numMonths[0]; row++) {
+ var group = '';
+ this.maxRows = 4;
+ for (var col = 0; col < numMonths[1]; col++) {
+ var selectedDate = this._daylightSavingAdjust(new Date(drawYear, drawMonth, inst.selectedDay));
+ var cornerClass = ' ui-corner-all';
+ var calender = '';
+ if (isMultiMonth) {
+ calender += '<div class="ui-datepicker-group';
+ if (numMonths[1] > 1)
+ switch (col) {
+ case 0: calender += ' ui-datepicker-group-first';
+ cornerClass = ' ui-corner-' + (isRTL ? 'right' : 'left'); break;
+ case numMonths[1]-1: calender += ' ui-datepicker-group-last';
+ cornerClass = ' ui-corner-' + (isRTL ? 'left' : 'right'); break;
+ default: calender += ' ui-datepicker-group-middle'; cornerClass = ''; break;
+ }
+ calender += '">';
+ }
+ calender += '<div class="ui-datepicker-header ui-widget-header ui-helper-clearfix' + cornerClass + '">' +
+ (/all|left/.test(cornerClass) && row == 0 ? (isRTL ? next : prev) : '') +
+ (/all|right/.test(cornerClass) && row == 0 ? (isRTL ? prev : next) : '') +
+ this._generateMonthYearHeader(inst, drawMonth, drawYear, minDate, maxDate,
+ row > 0 || col > 0, monthNames, monthNamesShort) + // draw month headers
+ '</div><table class="ui-datepicker-calendar"><thead>' +
+ '<tr>';
+ var thead = (showWeek ? '<th class="ui-datepicker-week-col">' + this._get(inst, 'weekHeader') + '</th>' : '');
+ for (var dow = 0; dow < 7; dow++) { // days of the week
+ var day = (dow + firstDay) % 7;
+ thead += '<th' + ((dow + firstDay + 6) % 7 >= 5 ? ' class="ui-datepicker-week-end"' : '') + '>' +
+ '<span title="' + dayNames[day] + '">' + dayNamesMin[day] + '</span></th>';
+ }
+ calender += thead + '</tr></thead><tbody>';
+ var daysInMonth = this._getDaysInMonth(drawYear, drawMonth);
+ if (drawYear == inst.selectedYear && drawMonth == inst.selectedMonth)
+ inst.selectedDay = Math.min(inst.selectedDay, daysInMonth);
+ var leadDays = (this._getFirstDayOfMonth(drawYear, drawMonth) - firstDay + 7) % 7;
+ var curRows = Math.ceil((leadDays + daysInMonth) / 7); // calculate the number of rows to generate
+ var numRows = (isMultiMonth ? this.maxRows > curRows ? this.maxRows : curRows : curRows); //If multiple months, use the higher number of rows (see #7043)
+ this.maxRows = numRows;
+ var printDate = this._daylightSavingAdjust(new Date(drawYear, drawMonth, 1 - leadDays));
+ for (var dRow = 0; dRow < numRows; dRow++) { // create date picker rows
+ calender += '<tr>';
+ var tbody = (!showWeek ? '' : '<td class="ui-datepicker-week-col">' +
+ this._get(inst, 'calculateWeek')(printDate) + '</td>');
+ for (var dow = 0; dow < 7; dow++) { // create date picker days
+ var daySettings = (beforeShowDay ?
+ beforeShowDay.apply((inst.input ? inst.input[0] : null), [printDate]) : [true, '']);
+ var otherMonth = (printDate.getMonth() != drawMonth);
+ var unselectable = (otherMonth && !selectOtherMonths) || !daySettings[0] ||
+ (minDate && printDate < minDate) || (maxDate && printDate > maxDate);
+ tbody += '<td class="' +
+ ((dow + firstDay + 6) % 7 >= 5 ? ' ui-datepicker-week-end' : '') + // highlight weekends
+ (otherMonth ? ' ui-datepicker-other-month' : '') + // highlight days from other months
+ ((printDate.getTime() == selectedDate.getTime() && drawMonth == inst.selectedMonth && inst._keyEvent) || // user pressed key
+ (defaultDate.getTime() == printDate.getTime() && defaultDate.getTime() == selectedDate.getTime()) ?
+ // or defaultDate is current printedDate and defaultDate is selectedDate
+ ' ' + this._dayOverClass : '') + // highlight selected day
+ (unselectable ? ' ' + this._unselectableClass + ' ui-state-disabled': '') + // highlight unselectable days
+ (otherMonth && !showOtherMonths ? '' : ' ' + daySettings[1] + // highlight custom dates
+ (printDate.getTime() == currentDate.getTime() ? ' ' + this._currentClass : '') + // highlight selected day
+ (printDate.getTime() == today.getTime() ? ' ui-datepicker-today' : '')) + '"' + // highlight today (if different)
+ ((!otherMonth || showOtherMonths) && daySettings[2] ? ' title="' + daySettings[2] + '"' : '') + // cell title
+ (unselectable ? '' : ' data-handler="selectDay" data-event="click" data-month="' + printDate.getMonth() + '" data-year="' + printDate.getFullYear() + '"') + '>' + // actions
+ (otherMonth && !showOtherMonths ? '&#xa0;' : // display for other months
+ (unselectable ? '<span class="ui-state-default">' + printDate.getDate() + '</span>' : '<a class="ui-state-default' +
+ (printDate.getTime() == today.getTime() ? ' ui-state-highlight' : '') +
+ (printDate.getTime() == currentDate.getTime() ? ' ui-state-active' : '') + // highlight selected day
+ (otherMonth ? ' ui-priority-secondary' : '') + // distinguish dates from other months
+ '" href="#">' + printDate.getDate() + '</a>')) + '</td>'; // display selectable date
+ printDate.setDate(printDate.getDate() + 1);
+ printDate = this._daylightSavingAdjust(printDate);
+ }
+ calender += tbody + '</tr>';
+ }
+ drawMonth++;
+ if (drawMonth > 11) {
+ drawMonth = 0;
+ drawYear++;
+ }
+ calender += '</tbody></table>' + (isMultiMonth ? '</div>' +
+ ((numMonths[0] > 0 && col == numMonths[1]-1) ? '<div class="ui-datepicker-row-break"></div>' : '') : '');
+ group += calender;
+ }
+ html += group;
+ }
+ html += buttonPanel + ($.ui.ie6 && !inst.inline ?
+ '<iframe src="javascript:false;" class="ui-datepicker-cover" frameborder="0"></iframe>' : '');
+ inst._keyEvent = false;
+ return html;
+ },
+
+ /* Generate the month and year header. */
+ _generateMonthYearHeader: function(inst, drawMonth, drawYear, minDate, maxDate,
+ secondary, monthNames, monthNamesShort) {
+ var changeMonth = this._get(inst, 'changeMonth');
+ var changeYear = this._get(inst, 'changeYear');
+ var showMonthAfterYear = this._get(inst, 'showMonthAfterYear');
+ var html = '<div class="ui-datepicker-title">';
+ var monthHtml = '';
+ // month selection
+ if (secondary || !changeMonth)
+ monthHtml += '<span class="ui-datepicker-month">' + monthNames[drawMonth] + '</span>';
+ else {
+ var inMinYear = (minDate && minDate.getFullYear() == drawYear);
+ var inMaxYear = (maxDate && maxDate.getFullYear() == drawYear);
+ monthHtml += '<select class="ui-datepicker-month" data-handler="selectMonth" data-event="change">';
+ for (var month = 0; month < 12; month++) {
+ if ((!inMinYear || month >= minDate.getMonth()) &&
+ (!inMaxYear || month <= maxDate.getMonth()))
+ monthHtml += '<option value="' + month + '"' +
+ (month == drawMonth ? ' selected="selected"' : '') +
+ '>' + monthNamesShort[month] + '</option>';
+ }
+ monthHtml += '</select>';
+ }
+ if (!showMonthAfterYear)
+ html += monthHtml + (secondary || !(changeMonth && changeYear) ? '&#xa0;' : '');
+ // year selection
+ if ( !inst.yearshtml ) {
+ inst.yearshtml = '';
+ if (secondary || !changeYear)
+ html += '<span class="ui-datepicker-year">' + drawYear + '</span>';
+ else {
+ // determine range of years to display
+ var years = this._get(inst, 'yearRange').split(':');
+ var thisYear = new Date().getFullYear();
+ var determineYear = function(value) {
+ var year = (value.match(/c[+-].*/) ? drawYear + parseInt(value.substring(1), 10) :
+ (value.match(/[+-].*/) ? thisYear + parseInt(value, 10) :
+ parseInt(value, 10)));
+ return (isNaN(year) ? thisYear : year);
+ };
+ var year = determineYear(years[0]);
+ var endYear = Math.max(year, determineYear(years[1] || ''));
+ year = (minDate ? Math.max(year, minDate.getFullYear()) : year);
+ endYear = (maxDate ? Math.min(endYear, maxDate.getFullYear()) : endYear);
+ inst.yearshtml += '<select class="ui-datepicker-year" data-handler="selectYear" data-event="change">';
+ for (; year <= endYear; year++) {
+ inst.yearshtml += '<option value="' + year + '"' +
+ (year == drawYear ? ' selected="selected"' : '') +
+ '>' + year + '</option>';
+ }
+ inst.yearshtml += '</select>';
+
+ html += inst.yearshtml;
+ inst.yearshtml = null;
+ }
+ }
+ html += this._get(inst, 'yearSuffix');
+ if (showMonthAfterYear)
+ html += (secondary || !(changeMonth && changeYear) ? '&#xa0;' : '') + monthHtml;
+ html += '</div>'; // Close datepicker_header
+ return html;
+ },
+
+ /* Adjust one of the date sub-fields. */
+ _adjustInstDate: function(inst, offset, period) {
+ var year = inst.drawYear + (period == 'Y' ? offset : 0);
+ var month = inst.drawMonth + (period == 'M' ? offset : 0);
+ var day = Math.min(inst.selectedDay, this._getDaysInMonth(year, month)) +
+ (period == 'D' ? offset : 0);
+ var date = this._restrictMinMax(inst,
+ this._daylightSavingAdjust(new Date(year, month, day)));
+ inst.selectedDay = date.getDate();
+ inst.drawMonth = inst.selectedMonth = date.getMonth();
+ inst.drawYear = inst.selectedYear = date.getFullYear();
+ if (period == 'M' || period == 'Y')
+ this._notifyChange(inst);
+ },
+
+ /* Ensure a date is within any min/max bounds. */
+ _restrictMinMax: function(inst, date) {
+ var minDate = this._getMinMaxDate(inst, 'min');
+ var maxDate = this._getMinMaxDate(inst, 'max');
+ var newDate = (minDate && date < minDate ? minDate : date);
+ newDate = (maxDate && newDate > maxDate ? maxDate : newDate);
+ return newDate;
+ },
+
+ /* Notify change of month/year. */
+ _notifyChange: function(inst) {
+ var onChange = this._get(inst, 'onChangeMonthYear');
+ if (onChange)
+ onChange.apply((inst.input ? inst.input[0] : null),
+ [inst.selectedYear, inst.selectedMonth + 1, inst]);
+ },
+
+ /* Determine the number of months to show. */
+ _getNumberOfMonths: function(inst) {
+ var numMonths = this._get(inst, 'numberOfMonths');
+ return (numMonths == null ? [1, 1] : (typeof numMonths == 'number' ? [1, numMonths] : numMonths));
+ },
+
+ /* Determine the current maximum date - ensure no time components are set. */
+ _getMinMaxDate: function(inst, minMax) {
+ return this._determineDate(inst, this._get(inst, minMax + 'Date'), null);
+ },
+
+ /* Find the number of days in a given month. */
+ _getDaysInMonth: function(year, month) {
+ return 32 - this._daylightSavingAdjust(new Date(year, month, 32)).getDate();
+ },
+
+ /* Find the day of the week of the first of a month. */
+ _getFirstDayOfMonth: function(year, month) {
+ return new Date(year, month, 1).getDay();
+ },
+
+ /* Determines if we should allow a "next/prev" month display change. */
+ _canAdjustMonth: function(inst, offset, curYear, curMonth) {
+ var numMonths = this._getNumberOfMonths(inst);
+ var date = this._daylightSavingAdjust(new Date(curYear,
+ curMonth + (offset < 0 ? offset : numMonths[0] * numMonths[1]), 1));
+ if (offset < 0)
+ date.setDate(this._getDaysInMonth(date.getFullYear(), date.getMonth()));
+ return this._isInRange(inst, date);
+ },
+
+ /* Is the given date in the accepted range? */
+ _isInRange: function(inst, date) {
+ var minDate = this._getMinMaxDate(inst, 'min');
+ var maxDate = this._getMinMaxDate(inst, 'max');
+ return ((!minDate || date.getTime() >= minDate.getTime()) &&
+ (!maxDate || date.getTime() <= maxDate.getTime()));
+ },
+
+ /* Provide the configuration settings for formatting/parsing. */
+ _getFormatConfig: function(inst) {
+ var shortYearCutoff = this._get(inst, 'shortYearCutoff');
+ shortYearCutoff = (typeof shortYearCutoff != 'string' ? shortYearCutoff :
+ new Date().getFullYear() % 100 + parseInt(shortYearCutoff, 10));
+ return {shortYearCutoff: shortYearCutoff,
+ dayNamesShort: this._get(inst, 'dayNamesShort'), dayNames: this._get(inst, 'dayNames'),
+ monthNamesShort: this._get(inst, 'monthNamesShort'), monthNames: this._get(inst, 'monthNames')};
+ },
+
+ /* Format the given date for display. */
+ _formatDate: function(inst, day, month, year) {
+ if (!day) {
+ inst.currentDay = inst.selectedDay;
+ inst.currentMonth = inst.selectedMonth;
+ inst.currentYear = inst.selectedYear;
+ }
+ var date = (day ? (typeof day == 'object' ? day :
+ this._daylightSavingAdjust(new Date(year, month, day))) :
+ this._daylightSavingAdjust(new Date(inst.currentYear, inst.currentMonth, inst.currentDay)));
+ return this.formatDate(this._get(inst, 'dateFormat'), date, this._getFormatConfig(inst));
+ }
+});
+
+/*
+ * Bind hover events for datepicker elements.
+ * Done via delegate so the binding only occurs once in the lifetime of the parent div.
+ * Global instActive, set by _updateDatepicker allows the handlers to find their way back to the active picker.
+ */
+function bindHover(dpDiv) {
+ var selector = 'button, .ui-datepicker-prev, .ui-datepicker-next, .ui-datepicker-calendar td a';
+ return dpDiv.delegate(selector, 'mouseout', function() {
+ $(this).removeClass('ui-state-hover');
+ if (this.className.indexOf('ui-datepicker-prev') != -1) $(this).removeClass('ui-datepicker-prev-hover');
+ if (this.className.indexOf('ui-datepicker-next') != -1) $(this).removeClass('ui-datepicker-next-hover');
+ })
+ .delegate(selector, 'mouseover', function(){
+ if (!$.datepicker._isDisabledDatepicker( instActive.inline ? dpDiv.parent()[0] : instActive.input[0])) {
+ $(this).parents('.ui-datepicker-calendar').find('a').removeClass('ui-state-hover');
+ $(this).addClass('ui-state-hover');
+ if (this.className.indexOf('ui-datepicker-prev') != -1) $(this).addClass('ui-datepicker-prev-hover');
+ if (this.className.indexOf('ui-datepicker-next') != -1) $(this).addClass('ui-datepicker-next-hover');
+ }
+ });
+}
+
+/* jQuery extend now ignores nulls! */
+function extendRemove(target, props) {
+ $.extend(target, props);
+ for (var name in props)
+ if (props[name] == null || props[name] == undefined)
+ target[name] = props[name];
+ return target;
+};
+
+/* Invoke the datepicker functionality.
+ @param options string - a command, optionally followed by additional parameters or
+ Object - settings for attaching new datepicker functionality
+ @return jQuery object */
+$.fn.datepicker = function(options){
+
+ /* Verify an empty collection wasn't passed - Fixes #6976 */
+ if ( !this.length ) {
+ return this;
+ }
+
+ /* Initialise the date picker. */
+ if (!$.datepicker.initialized) {
+ $(document).mousedown($.datepicker._checkExternalClick).
+ find(document.body).append($.datepicker.dpDiv);
+ $.datepicker.initialized = true;
+ }
+
+ var otherArgs = Array.prototype.slice.call(arguments, 1);
+ if (typeof options == 'string' && (options == 'isDisabled' || options == 'getDate' || options == 'widget'))
+ return $.datepicker['_' + options + 'Datepicker'].
+ apply($.datepicker, [this[0]].concat(otherArgs));
+ if (options == 'option' && arguments.length == 2 && typeof arguments[1] == 'string')
+ return $.datepicker['_' + options + 'Datepicker'].
+ apply($.datepicker, [this[0]].concat(otherArgs));
+ return this.each(function() {
+ typeof options == 'string' ?
+ $.datepicker['_' + options + 'Datepicker'].
+ apply($.datepicker, [this].concat(otherArgs)) :
+ $.datepicker._attachDatepicker(this, options);
+ });
+};
+
+$.datepicker = new Datepicker(); // singleton instance
+$.datepicker.initialized = false;
+$.datepicker.uuid = new Date().getTime();
+$.datepicker.version = "@VERSION";
+
+// Workaround for #4055
+// Add another global to avoid noConflict issues with inline event handlers
+window['DP_jQuery_' + dpuuid] = $;
+
+})(jQuery);
diff --git a/js/jquery/src/jquery-ui/jquery.ui.dialog.js b/js/jquery/src/jquery-ui/jquery.ui.dialog.js
new file mode 100644
index 0000000000..a031eb5e0c
--- /dev/null
+++ b/js/jquery/src/jquery-ui/jquery.ui.dialog.js
@@ -0,0 +1,858 @@
+/*!
+ * jQuery UI Dialog @VERSION
+ * http://jqueryui.com
+ *
+ * Copyright 2012 jQuery Foundation and other contributors
+ * Released under the MIT license.
+ * http://jquery.org/license
+ *
+ * http://api.jqueryui.com/dialog/
+ *
+ * Depends:
+ * jquery.ui.core.js
+ * jquery.ui.widget.js
+ * jquery.ui.button.js
+ * jquery.ui.draggable.js
+ * jquery.ui.mouse.js
+ * jquery.ui.position.js
+ * jquery.ui.resizable.js
+ */
+(function( $, undefined ) {
+
+var uiDialogClasses = "ui-dialog ui-widget ui-widget-content ui-corner-all ",
+ sizeRelatedOptions = {
+ buttons: true,
+ height: true,
+ maxHeight: true,
+ maxWidth: true,
+ minHeight: true,
+ minWidth: true,
+ width: true
+ },
+ resizableRelatedOptions = {
+ maxHeight: true,
+ maxWidth: true,
+ minHeight: true,
+ minWidth: true
+ };
+
+$.widget("ui.dialog", {
+ version: "@VERSION",
+ options: {
+ autoOpen: true,
+ buttons: {},
+ closeOnEscape: true,
+ closeText: "close",
+ dialogClass: "",
+ draggable: true,
+ hide: null,
+ height: "auto",
+ maxHeight: false,
+ maxWidth: false,
+ minHeight: 150,
+ minWidth: 150,
+ modal: false,
+ position: {
+ my: "center",
+ at: "center",
+ of: window,
+ collision: "fit",
+ // ensure that the titlebar is never outside the document
+ using: function( pos ) {
+ var topOffset = $( this ).css( pos ).offset().top;
+ if ( topOffset < 0 ) {
+ $( this ).css( "top", pos.top - topOffset );
+ }
+ }
+ },
+ resizable: true,
+ show: null,
+ stack: true,
+ title: "",
+ width: 300,
+ zIndex: 1000
+ },
+
+ _create: function() {
+ this.originalTitle = this.element.attr( "title" );
+ // #5742 - .attr() might return a DOMElement
+ if ( typeof this.originalTitle !== "string" ) {
+ this.originalTitle = "";
+ }
+ this.oldPosition = {
+ parent: this.element.parent(),
+ index: this.element.parent().children().index( this.element )
+ };
+ this.options.title = this.options.title || this.originalTitle;
+ var that = this,
+ options = this.options,
+
+ title = options.title || "&#160;",
+ uiDialog,
+ uiDialogTitlebar,
+ uiDialogTitlebarClose,
+ uiDialogTitle,
+ uiDialogButtonPane;
+
+ uiDialog = ( this.uiDialog = $( "<div>" ) )
+ .addClass( uiDialogClasses + options.dialogClass )
+ .css({
+ display: "none",
+ outline: 0, // TODO: move to stylesheet
+ zIndex: options.zIndex
+ })
+ // setting tabIndex makes the div focusable
+ .attr( "tabIndex", -1)
+ .keydown(function( event ) {
+ if ( options.closeOnEscape && !event.isDefaultPrevented() && event.keyCode &&
+ event.keyCode === $.ui.keyCode.ESCAPE ) {
+ that.close( event );
+ event.preventDefault();
+ }
+ })
+ .mousedown(function( event ) {
+ that.moveToTop( false, event );
+ })
+ .appendTo( "body" );
+
+ this.element
+ .show()
+ .removeAttr( "title" )
+ .addClass( "ui-dialog-content ui-widget-content" )
+ .appendTo( uiDialog );
+
+ uiDialogTitlebar = ( this.uiDialogTitlebar = $( "<div>" ) )
+ .addClass( "ui-dialog-titlebar ui-widget-header " +
+ "ui-corner-all ui-helper-clearfix" )
+ .bind( "mousedown", function() {
+ // Dialog isn't getting focus when dragging (#8063)
+ uiDialog.focus();
+ })
+ .prependTo( uiDialog );
+
+ uiDialogTitlebarClose = $( "<a href='#'></a>" )
+ .addClass( "ui-dialog-titlebar-close ui-corner-all" )
+ .attr( "role", "button" )
+ .click(function( event ) {
+ event.preventDefault();
+ that.close( event );
+ })
+ .appendTo( uiDialogTitlebar );
+
+ ( this.uiDialogTitlebarCloseText = $( "<span>" ) )
+ .addClass( "ui-icon ui-icon-closethick" )
+ .text( options.closeText )
+ .appendTo( uiDialogTitlebarClose );
+
+ uiDialogTitle = $( "<span>" )
+ .uniqueId()
+ .addClass( "ui-dialog-title" )
+ .html( title )
+ .prependTo( uiDialogTitlebar );
+
+ uiDialogButtonPane = ( this.uiDialogButtonPane = $( "<div>" ) )
+ .addClass( "ui-dialog-buttonpane ui-widget-content ui-helper-clearfix" );
+
+ ( this.uiButtonSet = $( "<div>" ) )
+ .addClass( "ui-dialog-buttonset" )
+ .appendTo( uiDialogButtonPane );
+
+ uiDialog.attr({
+ role: "dialog",
+ "aria-labelledby": uiDialogTitle.attr( "id" )
+ });
+
+ uiDialogTitlebar.find( "*" ).add( uiDialogTitlebar ).disableSelection();
+ this._hoverable( uiDialogTitlebarClose );
+ this._focusable( uiDialogTitlebarClose );
+
+ if ( options.draggable && $.fn.draggable ) {
+ this._makeDraggable();
+ }
+ if ( options.resizable && $.fn.resizable ) {
+ this._makeResizable();
+ }
+
+ this._createButtons( options.buttons );
+ this._isOpen = false;
+
+ if ( $.fn.bgiframe ) {
+ uiDialog.bgiframe();
+ }
+
+ // prevent tabbing out of modal dialogs
+ this._on( uiDialog, { keydown: function( event ) {
+ if ( !options.modal || event.keyCode !== $.ui.keyCode.TAB ) {
+ return;
+ }
+
+ var tabbables = $( ":tabbable", uiDialog ),
+ first = tabbables.filter( ":first" ),
+ last = tabbables.filter( ":last" );
+
+ if ( event.target === last[0] && !event.shiftKey ) {
+ first.focus( 1 );
+ return false;
+ } else if ( event.target === first[0] && event.shiftKey ) {
+ last.focus( 1 );
+ return false;
+ }
+ }});
+ },
+
+ _init: function() {
+ if ( this.options.autoOpen ) {
+ this.open();
+ }
+ },
+
+ _destroy: function() {
+ var next,
+ oldPosition = this.oldPosition;
+
+ if ( this.overlay ) {
+ this.overlay.destroy();
+ }
+ this.uiDialog.hide();
+ this.element
+ .removeClass( "ui-dialog-content ui-widget-content" )
+ .hide()
+ .appendTo( "body" );
+ this.uiDialog.remove();
+
+ if ( this.originalTitle ) {
+ this.element.attr( "title", this.originalTitle );
+ }
+
+ next = oldPosition.parent.children().eq( oldPosition.index );
+ // Don't try to place the dialog next to itself (#8613)
+ if ( next.length && next[ 0 ] !== this.element[ 0 ] ) {
+ next.before( this.element );
+ } else {
+ oldPosition.parent.append( this.element );
+ }
+ },
+
+ widget: function() {
+ return this.uiDialog;
+ },
+
+ close: function( event ) {
+ var that = this,
+ maxZ, thisZ;
+
+ if ( !this._isOpen ) {
+ return;
+ }
+
+ if ( false === this._trigger( "beforeClose", event ) ) {
+ return;
+ }
+
+ this._isOpen = false;
+
+ if ( this.overlay ) {
+ this.overlay.destroy();
+ }
+
+ if ( this.options.hide ) {
+ this._hide( this.uiDialog, this.options.hide, function() {
+ that._trigger( "close", event );
+ });
+ } else {
+ this.uiDialog.hide();
+ this._trigger( "close", event );
+ }
+
+ $.ui.dialog.overlay.resize();
+
+ // adjust the maxZ to allow other modal dialogs to continue to work (see #4309)
+ if ( this.options.modal ) {
+ maxZ = 0;
+ $( ".ui-dialog" ).each(function() {
+ if ( this !== that.uiDialog[0] ) {
+ thisZ = $( this ).css( "z-index" );
+ if ( !isNaN( thisZ ) ) {
+ maxZ = Math.max( maxZ, thisZ );
+ }
+ }
+ });
+ $.ui.dialog.maxZ = maxZ;
+ }
+
+ return this;
+ },
+
+ isOpen: function() {
+ return this._isOpen;
+ },
+
+ // the force parameter allows us to move modal dialogs to their correct
+ // position on open
+ moveToTop: function( force, event ) {
+ var options = this.options,
+ saveScroll;
+
+ if ( ( options.modal && !force ) ||
+ ( !options.stack && !options.modal ) ) {
+ return this._trigger( "focus", event );
+ }
+
+ if ( options.zIndex > $.ui.dialog.maxZ ) {
+ $.ui.dialog.maxZ = options.zIndex;
+ }
+ if ( this.overlay ) {
+ $.ui.dialog.maxZ += 1;
+ $.ui.dialog.overlay.maxZ = $.ui.dialog.maxZ;
+ this.overlay.$el.css( "z-index", $.ui.dialog.overlay.maxZ );
+ }
+
+ // Save and then restore scroll
+ // Opera 9.5+ resets when parent z-index is changed.
+ // http://bugs.jqueryui.com/ticket/3193
+ saveScroll = {
+ scrollTop: this.element.scrollTop(),
+ scrollLeft: this.element.scrollLeft()
+ };
+ $.ui.dialog.maxZ += 1;
+ this.uiDialog.css( "z-index", $.ui.dialog.maxZ );
+ this.element.attr( saveScroll );
+ this._trigger( "focus", event );
+
+ return this;
+ },
+
+ open: function() {
+ if ( this._isOpen ) {
+ return;
+ }
+
+ var hasFocus,
+ options = this.options,
+ uiDialog = this.uiDialog;
+
+ this._size();
+ this._position( options.position );
+ uiDialog.show( options.show );
+ this.overlay = options.modal ? new $.ui.dialog.overlay( this ) : null;
+ this.moveToTop( true );
+
+ // set focus to the first tabbable element in the content area or the first button
+ // if there are no tabbable elements, set focus on the dialog itself
+ hasFocus = this.element.find( ":tabbable" );
+ if ( !hasFocus.length ) {
+ hasFocus = this.uiDialogButtonPane.find( ":tabbable" );
+ if ( !hasFocus.length ) {
+ hasFocus = uiDialog;
+ }
+ }
+ hasFocus.eq( 0 ).focus();
+
+ this._isOpen = true;
+ this._trigger( "open" );
+
+ return this;
+ },
+
+ _createButtons: function( buttons ) {
+ var that = this,
+ hasButtons = false;
+
+ // if we already have a button pane, remove it
+ this.uiDialogButtonPane.remove();
+ this.uiButtonSet.empty();
+
+ if ( typeof buttons === "object" && buttons !== null ) {
+ $.each( buttons, function() {
+ return !(hasButtons = true);
+ });
+ }
+ if ( hasButtons ) {
+ $.each( buttons, function( name, props ) {
+ var button, click;
+ props = $.isFunction( props ) ?
+ { click: props, text: name } :
+ props;
+ // Default to a non-submitting button
+ props = $.extend( { type: "button" }, props );
+ // Change the context for the click callback to be the main element
+ click = props.click;
+ props.click = function() {
+ click.apply( that.element[0], arguments );
+ };
+ button = $( "<button></button>", props )
+ .appendTo( that.uiButtonSet );
+ if ( $.fn.button ) {
+ button.button();
+ }
+ });
+ this.uiDialog.addClass( "ui-dialog-buttons" );
+ this.uiDialogButtonPane.appendTo( this.uiDialog );
+ } else {
+ this.uiDialog.removeClass( "ui-dialog-buttons" );
+ }
+ },
+
+ _makeDraggable: function() {
+ var that = this,
+ options = this.options;
+
+ function filteredUi( ui ) {
+ return {
+ position: ui.position,
+ offset: ui.offset
+ };
+ }
+
+ this.uiDialog.draggable({
+ cancel: ".ui-dialog-content, .ui-dialog-titlebar-close",
+ handle: ".ui-dialog-titlebar",
+ containment: "document",
+ start: function( event, ui ) {
+ $( this )
+ .addClass( "ui-dialog-dragging" );
+ that._trigger( "dragStart", event, filteredUi( ui ) );
+ },
+ drag: function( event, ui ) {
+ that._trigger( "drag", event, filteredUi( ui ) );
+ },
+ stop: function( event, ui ) {
+ options.position = [
+ ui.position.left - that.document.scrollLeft(),
+ ui.position.top - that.document.scrollTop()
+ ];
+ $( this )
+ .removeClass( "ui-dialog-dragging" );
+ that._trigger( "dragStop", event, filteredUi( ui ) );
+ $.ui.dialog.overlay.resize();
+ }
+ });
+ },
+
+ _makeResizable: function( handles ) {
+ handles = (handles === undefined ? this.options.resizable : handles);
+ var that = this,
+ options = this.options,
+ // .ui-resizable has position: relative defined in the stylesheet
+ // but dialogs have to use absolute or fixed positioning
+ position = this.uiDialog.css( "position" ),
+ resizeHandles = typeof handles === 'string' ?
+ handles :
+ "n,e,s,w,se,sw,ne,nw";
+
+ function filteredUi( ui ) {
+ return {
+ originalPosition: ui.originalPosition,
+ originalSize: ui.originalSize,
+ position: ui.position,
+ size: ui.size
+ };
+ }
+
+ this.uiDialog.resizable({
+ cancel: ".ui-dialog-content",
+ containment: "document",
+ alsoResize: this.element,
+ maxWidth: options.maxWidth,
+ maxHeight: options.maxHeight,
+ minWidth: options.minWidth,
+ minHeight: this._minHeight(),
+ handles: resizeHandles,
+ start: function( event, ui ) {
+ $( this ).addClass( "ui-dialog-resizing" );
+ that._trigger( "resizeStart", event, filteredUi( ui ) );
+ },
+ resize: function( event, ui ) {
+ that._trigger( "resize", event, filteredUi( ui ) );
+ },
+ stop: function( event, ui ) {
+ $( this ).removeClass( "ui-dialog-resizing" );
+ options.height = $( this ).height();
+ options.width = $( this ).width();
+ that._trigger( "resizeStop", event, filteredUi( ui ) );
+ $.ui.dialog.overlay.resize();
+ }
+ })
+ .css( "position", position )
+ .find( ".ui-resizable-se" )
+ .addClass( "ui-icon ui-icon-grip-diagonal-se" );
+ },
+
+ _minHeight: function() {
+ var options = this.options;
+
+ if ( options.height === "auto" ) {
+ return options.minHeight;
+ } else {
+ return Math.min( options.minHeight, options.height );
+ }
+ },
+
+ _position: function( position ) {
+ var myAt = [],
+ offset = [ 0, 0 ],
+ isVisible;
+
+ if ( position ) {
+ // deep extending converts arrays to objects in jQuery <= 1.3.2 :-(
+ // if (typeof position == 'string' || $.isArray(position)) {
+ // myAt = $.isArray(position) ? position : position.split(' ');
+
+ if ( typeof position === "string" || (typeof position === "object" && "0" in position ) ) {
+ myAt = position.split ? position.split( " " ) : [ position[ 0 ], position[ 1 ] ];
+ if ( myAt.length === 1 ) {
+ myAt[ 1 ] = myAt[ 0 ];
+ }
+
+ $.each( [ "left", "top" ], function( i, offsetPosition ) {
+ if ( +myAt[ i ] === myAt[ i ] ) {
+ offset[ i ] = myAt[ i ];
+ myAt[ i ] = offsetPosition;
+ }
+ });
+
+ position = {
+ my: myAt[0] + (offset[0] < 0 ? offset[0] : "+" + offset[0]) + " " +
+ myAt[1] + (offset[1] < 0 ? offset[1] : "+" + offset[1]),
+ at: myAt.join( " " )
+ };
+ }
+
+ position = $.extend( {}, $.ui.dialog.prototype.options.position, position );
+ } else {
+ position = $.ui.dialog.prototype.options.position;
+ }
+
+ // need to show the dialog to get the actual offset in the position plugin
+ isVisible = this.uiDialog.is( ":visible" );
+ if ( !isVisible ) {
+ this.uiDialog.show();
+ }
+ this.uiDialog.position( position );
+ if ( !isVisible ) {
+ this.uiDialog.hide();
+ }
+ },
+
+ _setOptions: function( options ) {
+ var that = this,
+ resizableOptions = {},
+ resize = false;
+
+ $.each( options, function( key, value ) {
+ that._setOption( key, value );
+
+ if ( key in sizeRelatedOptions ) {
+ resize = true;
+ }
+ if ( key in resizableRelatedOptions ) {
+ resizableOptions[ key ] = value;
+ }
+ });
+
+ if ( resize ) {
+ this._size();
+ }
+ if ( this.uiDialog.is( ":data(resizable)" ) ) {
+ this.uiDialog.resizable( "option", resizableOptions );
+ }
+ },
+
+ _setOption: function( key, value ) {
+ var isDraggable, isResizable,
+ uiDialog = this.uiDialog;
+
+ switch ( key ) {
+ case "buttons":
+ this._createButtons( value );
+ break;
+ case "closeText":
+ // ensure that we always pass a string
+ this.uiDialogTitlebarCloseText.text( "" + value );
+ break;
+ case "dialogClass":
+ uiDialog
+ .removeClass( this.options.dialogClass )
+ .addClass( uiDialogClasses + value );
+ break;
+ case "disabled":
+ if ( value ) {
+ uiDialog.addClass( "ui-dialog-disabled" );
+ } else {
+ uiDialog.removeClass( "ui-dialog-disabled" );
+ }
+ break;
+ case "draggable":
+ isDraggable = uiDialog.is( ":data(draggable)" );
+ if ( isDraggable && !value ) {
+ uiDialog.draggable( "destroy" );
+ }
+
+ if ( !isDraggable && value ) {
+ this._makeDraggable();
+ }
+ break;
+ case "position":
+ this._position( value );
+ break;
+ case "resizable":
+ // currently resizable, becoming non-resizable
+ isResizable = uiDialog.is( ":data(resizable)" );
+ if ( isResizable && !value ) {
+ uiDialog.resizable( "destroy" );
+ }
+
+ // currently resizable, changing handles
+ if ( isResizable && typeof value === "string" ) {
+ uiDialog.resizable( "option", "handles", value );
+ }
+
+ // currently non-resizable, becoming resizable
+ if ( !isResizable && value !== false ) {
+ this._makeResizable( value );
+ }
+ break;
+ case "title":
+ // convert whatever was passed in o a string, for html() to not throw up
+ $( ".ui-dialog-title", this.uiDialogTitlebar )
+ .html( "" + ( value || "&#160;" ) );
+ break;
+ }
+
+ this._super( key, value );
+ },
+
+ _size: function() {
+ /* If the user has resized the dialog, the .ui-dialog and .ui-dialog-content
+ * divs will both have width and height set, so we need to reset them
+ */
+ var nonContentHeight, minContentHeight, autoHeight,
+ options = this.options,
+ isVisible = this.uiDialog.is( ":visible" );
+
+ // reset content sizing
+ this.element.show().css({
+ width: "auto",
+ minHeight: 0,
+ height: 0
+ });
+
+ if ( options.minWidth > options.width ) {
+ options.width = options.minWidth;
+ }
+
+ // reset wrapper sizing
+ // determine the height of all the non-content elements
+ nonContentHeight = this.uiDialog.css({
+ height: "auto",
+ width: options.width
+ })
+ .outerHeight();
+ minContentHeight = Math.max( 0, options.minHeight - nonContentHeight );
+
+ if ( options.height === "auto" ) {
+ // only needed for IE6 support
+ if ( $.support.minHeight ) {
+ this.element.css({
+ minHeight: minContentHeight,
+ height: "auto"
+ });
+ } else {
+ this.uiDialog.show();
+ autoHeight = this.element.css( "height", "auto" ).height();
+ if ( !isVisible ) {
+ this.uiDialog.hide();
+ }
+ this.element.height( Math.max( autoHeight, minContentHeight ) );
+ }
+ } else {
+ this.element.height( Math.max( options.height - nonContentHeight, 0 ) );
+ }
+
+ if (this.uiDialog.is( ":data(resizable)" ) ) {
+ this.uiDialog.resizable( "option", "minHeight", this._minHeight() );
+ }
+ }
+});
+
+$.extend($.ui.dialog, {
+ uuid: 0,
+ maxZ: 0,
+
+ getTitleId: function($el) {
+ var id = $el.attr( "id" );
+ if ( !id ) {
+ this.uuid += 1;
+ id = this.uuid;
+ }
+ return "ui-dialog-title-" + id;
+ },
+
+ overlay: function( dialog ) {
+ this.$el = $.ui.dialog.overlay.create( dialog );
+ }
+});
+
+$.extend( $.ui.dialog.overlay, {
+ instances: [],
+ // reuse old instances due to IE memory leak with alpha transparency (see #5185)
+ oldInstances: [],
+ maxZ: 0,
+ events: $.map(
+ "focus,mousedown,mouseup,keydown,keypress,click".split( "," ),
+ function( event ) {
+ return event + ".dialog-overlay";
+ }
+ ).join( " " ),
+ create: function( dialog ) {
+ if ( this.instances.length === 0 ) {
+ // prevent use of anchors and inputs
+ // we use a setTimeout in case the overlay is created from an
+ // event that we're going to be cancelling (see #2804)
+ setTimeout(function() {
+ // handle $(el).dialog().dialog('close') (see #4065)
+ if ( $.ui.dialog.overlay.instances.length ) {
+ $( document ).bind( $.ui.dialog.overlay.events, function( event ) {
+ // stop events if the z-index of the target is < the z-index of the overlay
+ // we cannot return true when we don't want to cancel the event (#3523)
+ if ( $( event.target ).zIndex() < $.ui.dialog.overlay.maxZ ) {
+ return false;
+ }
+ });
+ }
+ }, 1 );
+
+ // handle window resize
+ $( window ).bind( "resize.dialog-overlay", $.ui.dialog.overlay.resize );
+ }
+
+ var $el = ( this.oldInstances.pop() || $( "<div>" ).addClass( "ui-widget-overlay" ) );
+
+ // allow closing by pressing the escape key
+ $( document ).bind( "keydown.dialog-overlay", function( event ) {
+ var instances = $.ui.dialog.overlay.instances;
+ // only react to the event if we're the top overlay
+ if ( instances.length !== 0 && instances[ instances.length - 1 ] === $el &&
+ dialog.options.closeOnEscape && !event.isDefaultPrevented() && event.keyCode &&
+ event.keyCode === $.ui.keyCode.ESCAPE ) {
+
+ dialog.close( event );
+ event.preventDefault();
+ }
+ });
+
+ $el.appendTo( document.body ).css({
+ width: this.width(),
+ height: this.height()
+ });
+
+ if ( $.fn.bgiframe ) {
+ $el.bgiframe();
+ }
+
+ this.instances.push( $el );
+ return $el;
+ },
+
+ destroy: function( $el ) {
+ var indexOf = $.inArray( $el, this.instances ),
+ maxZ = 0;
+
+ if ( indexOf !== -1 ) {
+ this.oldInstances.push( this.instances.splice( indexOf, 1 )[ 0 ] );
+ }
+
+ if ( this.instances.length === 0 ) {
+ $( [ document, window ] ).unbind( ".dialog-overlay" );
+ }
+
+ $el.height( 0 ).width( 0 ).remove();
+
+ // adjust the maxZ to allow other modal dialogs to continue to work (see #4309)
+ $.each( this.instances, function() {
+ maxZ = Math.max( maxZ, this.css( "z-index" ) );
+ });
+ this.maxZ = maxZ;
+ },
+
+ height: function() {
+ var scrollHeight,
+ offsetHeight;
+ // handle IE
+ if ( $.ui.ie ) {
+ scrollHeight = Math.max(
+ document.documentElement.scrollHeight,
+ document.body.scrollHeight
+ );
+ offsetHeight = Math.max(
+ document.documentElement.offsetHeight,
+ document.body.offsetHeight
+ );
+
+ if ( scrollHeight < offsetHeight ) {
+ return $( window ).height() + "px";
+ } else {
+ return scrollHeight + "px";
+ }
+ // handle "good" browsers
+ } else {
+ return $( document ).height() + "px";
+ }
+ },
+
+ width: function() {
+ var scrollWidth,
+ offsetWidth;
+ // handle IE
+ if ( $.ui.ie ) {
+ scrollWidth = Math.max(
+ document.documentElement.scrollWidth,
+ document.body.scrollWidth
+ );
+ offsetWidth = Math.max(
+ document.documentElement.offsetWidth,
+ document.body.offsetWidth
+ );
+
+ if ( scrollWidth < offsetWidth ) {
+ return $( window ).width() + "px";
+ } else {
+ return scrollWidth + "px";
+ }
+ // handle "good" browsers
+ } else {
+ return $( document ).width() + "px";
+ }
+ },
+
+ resize: function() {
+ /* If the dialog is draggable and the user drags it past the
+ * right edge of the window, the document becomes wider so we
+ * need to stretch the overlay. If the user then drags the
+ * dialog back to the left, the document will become narrower,
+ * so we need to shrink the overlay to the appropriate size.
+ * This is handled by shrinking the overlay before setting it
+ * to the full document size.
+ */
+ var $overlays = $( [] );
+ $.each( $.ui.dialog.overlay.instances, function() {
+ $overlays = $overlays.add( this );
+ });
+
+ $overlays.css({
+ width: 0,
+ height: 0
+ }).css({
+ width: $.ui.dialog.overlay.width(),
+ height: $.ui.dialog.overlay.height()
+ });
+ }
+});
+
+$.extend( $.ui.dialog.overlay.prototype, {
+ destroy: function() {
+ $.ui.dialog.overlay.destroy( this.$el );
+ }
+});
+
+}( jQuery ) );
diff --git a/js/jquery/src/jquery-ui/jquery.ui.draggable.js b/js/jquery/src/jquery-ui/jquery.ui.draggable.js
new file mode 100644
index 0000000000..aa62752526
--- /dev/null
+++ b/js/jquery/src/jquery-ui/jquery.ui.draggable.js
@@ -0,0 +1,836 @@
+/*!
+ * jQuery UI Draggable @VERSION
+ * http://jqueryui.com
+ *
+ * Copyright 2012 jQuery Foundation and other contributors
+ * Released under the MIT license.
+ * http://jquery.org/license
+ *
+ * http://api.jqueryui.com/draggable/
+ *
+ * Depends:
+ * jquery.ui.core.js
+ * jquery.ui.mouse.js
+ * jquery.ui.widget.js
+ */
+(function( $, undefined ) {
+
+$.widget("ui.draggable", $.ui.mouse, {
+ version: "@VERSION",
+ widgetEventPrefix: "drag",
+ options: {
+ addClasses: true,
+ appendTo: "parent",
+ axis: false,
+ connectToSortable: false,
+ containment: false,
+ cursor: "auto",
+ cursorAt: false,
+ grid: false,
+ handle: false,
+ helper: "original",
+ iframeFix: false,
+ opacity: false,
+ refreshPositions: false,
+ revert: false,
+ revertDuration: 500,
+ scope: "default",
+ scroll: true,
+ scrollSensitivity: 20,
+ scrollSpeed: 20,
+ snap: false,
+ snapMode: "both",
+ snapTolerance: 20,
+ stack: false,
+ zIndex: false
+ },
+ _create: function() {
+
+ if (this.options.helper == 'original' && !(/^(?:r|a|f)/).test(this.element.css("position")))
+ this.element[0].style.position = 'relative';
+
+ (this.options.addClasses && this.element.addClass("ui-draggable"));
+ (this.options.disabled && this.element.addClass("ui-draggable-disabled"));
+
+ this._mouseInit();
+
+ },
+
+ _destroy: function() {
+ this.element.removeClass( "ui-draggable ui-draggable-dragging ui-draggable-disabled" );
+ this._mouseDestroy();
+ },
+
+ _mouseCapture: function(event) {
+
+ var o = this.options;
+
+ // among others, prevent a drag on a resizable-handle
+ if (this.helper || o.disabled || $(event.target).is('.ui-resizable-handle'))
+ return false;
+
+ //Quit if we're not on a valid handle
+ this.handle = this._getHandle(event);
+ if (!this.handle)
+ return false;
+
+ $(o.iframeFix === true ? "iframe" : o.iframeFix).each(function() {
+ $('<div class="ui-draggable-iframeFix" style="background: #fff;"></div>')
+ .css({
+ width: this.offsetWidth+"px", height: this.offsetHeight+"px",
+ position: "absolute", opacity: "0.001", zIndex: 1000
+ })
+ .css($(this).offset())
+ .appendTo("body");
+ });
+
+ return true;
+
+ },
+
+ _mouseStart: function(event) {
+
+ var o = this.options;
+
+ //Create and append the visible helper
+ this.helper = this._createHelper(event);
+
+ this.helper.addClass("ui-draggable-dragging");
+
+ //Cache the helper size
+ this._cacheHelperProportions();
+
+ //If ddmanager is used for droppables, set the global draggable
+ if($.ui.ddmanager)
+ $.ui.ddmanager.current = this;
+
+ /*
+ * - Position generation -
+ * This block generates everything position related - it's the core of draggables.
+ */
+
+ //Cache the margins of the original element
+ this._cacheMargins();
+
+ //Store the helper's css position
+ this.cssPosition = this.helper.css("position");
+ this.scrollParent = this.helper.scrollParent();
+
+ //The element's absolute position on the page minus margins
+ this.offset = this.positionAbs = this.element.offset();
+ this.offset = {
+ top: this.offset.top - this.margins.top,
+ left: this.offset.left - this.margins.left
+ };
+
+ $.extend(this.offset, {
+ click: { //Where the click happened, relative to the element
+ left: event.pageX - this.offset.left,
+ top: event.pageY - this.offset.top
+ },
+ parent: this._getParentOffset(),
+ relative: this._getRelativeOffset() //This is a relative to absolute position minus the actual position calculation - only used for relative positioned helper
+ });
+
+ //Generate the original position
+ this.originalPosition = this.position = this._generatePosition(event);
+ this.originalPageX = event.pageX;
+ this.originalPageY = event.pageY;
+
+ //Adjust the mouse offset relative to the helper if 'cursorAt' is supplied
+ (o.cursorAt && this._adjustOffsetFromHelper(o.cursorAt));
+
+ //Set a containment if given in the options
+ if(o.containment)
+ this._setContainment();
+
+ //Trigger event + callbacks
+ if(this._trigger("start", event) === false) {
+ this._clear();
+ return false;
+ }
+
+ //Recache the helper size
+ this._cacheHelperProportions();
+
+ //Prepare the droppable offsets
+ if ($.ui.ddmanager && !o.dropBehaviour)
+ $.ui.ddmanager.prepareOffsets(this, event);
+
+
+ this._mouseDrag(event, true); //Execute the drag once - this causes the helper not to be visible before getting its correct position
+
+ //If the ddmanager is used for droppables, inform the manager that dragging has started (see #5003)
+ if ( $.ui.ddmanager ) $.ui.ddmanager.dragStart(this, event);
+
+ return true;
+ },
+
+ _mouseDrag: function(event, noPropagation) {
+
+ //Compute the helpers position
+ this.position = this._generatePosition(event);
+ this.positionAbs = this._convertPositionTo("absolute");
+
+ //Call plugins and callbacks and use the resulting position if something is returned
+ if (!noPropagation) {
+ var ui = this._uiHash();
+ if(this._trigger('drag', event, ui) === false) {
+ this._mouseUp({});
+ return false;
+ }
+ this.position = ui.position;
+ }
+
+ if(!this.options.axis || this.options.axis != "y") this.helper[0].style.left = this.position.left+'px';
+ if(!this.options.axis || this.options.axis != "x") this.helper[0].style.top = this.position.top+'px';
+ if($.ui.ddmanager) $.ui.ddmanager.drag(this, event);
+
+ return false;
+ },
+
+ _mouseStop: function(event) {
+
+ //If we are using droppables, inform the manager about the drop
+ var dropped = false;
+ if ($.ui.ddmanager && !this.options.dropBehaviour)
+ dropped = $.ui.ddmanager.drop(this, event);
+
+ //if a drop comes from outside (a sortable)
+ if(this.dropped) {
+ dropped = this.dropped;
+ this.dropped = false;
+ }
+
+ //if the original element is no longer in the DOM don't bother to continue (see #8269)
+ var element = this.element[0], elementInDom = false;
+ while ( element && (element = element.parentNode) ) {
+ if (element == document ) {
+ elementInDom = true;
+ }
+ }
+ if ( !elementInDom && this.options.helper === "original" )
+ return false;
+
+ if((this.options.revert == "invalid" && !dropped) || (this.options.revert == "valid" && dropped) || this.options.revert === true || ($.isFunction(this.options.revert) && this.options.revert.call(this.element, dropped))) {
+ var that = this;
+ $(this.helper).animate(this.originalPosition, parseInt(this.options.revertDuration, 10), function() {
+ if(that._trigger("stop", event) !== false) {
+ that._clear();
+ }
+ });
+ } else {
+ if(this._trigger("stop", event) !== false) {
+ this._clear();
+ }
+ }
+
+ return false;
+ },
+
+ _mouseUp: function(event) {
+ //Remove frame helpers
+ $("div.ui-draggable-iframeFix").each(function() {
+ this.parentNode.removeChild(this);
+ });
+
+ //If the ddmanager is used for droppables, inform the manager that dragging has stopped (see #5003)
+ if( $.ui.ddmanager ) $.ui.ddmanager.dragStop(this, event);
+
+ return $.ui.mouse.prototype._mouseUp.call(this, event);
+ },
+
+ cancel: function() {
+
+ if(this.helper.is(".ui-draggable-dragging")) {
+ this._mouseUp({});
+ } else {
+ this._clear();
+ }
+
+ return this;
+
+ },
+
+ _getHandle: function(event) {
+
+ var handle = !this.options.handle || !$(this.options.handle, this.element).length ? true : false;
+ $(this.options.handle, this.element)
+ .find("*")
+ .andSelf()
+ .each(function() {
+ if(this == event.target) handle = true;
+ });
+
+ return handle;
+
+ },
+
+ _createHelper: function(event) {
+
+ var o = this.options;
+ var helper = $.isFunction(o.helper) ? $(o.helper.apply(this.element[0], [event])) : (o.helper == 'clone' ? this.element.clone().removeAttr('id') : this.element);
+
+ if(!helper.parents('body').length)
+ helper.appendTo((o.appendTo == 'parent' ? this.element[0].parentNode : o.appendTo));
+
+ if(helper[0] != this.element[0] && !(/(fixed|absolute)/).test(helper.css("position")))
+ helper.css("position", "absolute");
+
+ return helper;
+
+ },
+
+ _adjustOffsetFromHelper: function(obj) {
+ if (typeof obj == 'string') {
+ obj = obj.split(' ');
+ }
+ if ($.isArray(obj)) {
+ obj = {left: +obj[0], top: +obj[1] || 0};
+ }
+ if ('left' in obj) {
+ this.offset.click.left = obj.left + this.margins.left;
+ }
+ if ('right' in obj) {
+ this.offset.click.left = this.helperProportions.width - obj.right + this.margins.left;
+ }
+ if ('top' in obj) {
+ this.offset.click.top = obj.top + this.margins.top;
+ }
+ if ('bottom' in obj) {
+ this.offset.click.top = this.helperProportions.height - obj.bottom + this.margins.top;
+ }
+ },
+
+ _getParentOffset: function() {
+
+ //Get the offsetParent and cache its position
+ this.offsetParent = this.helper.offsetParent();
+ var po = this.offsetParent.offset();
+
+ // This is a special case where we need to modify a offset calculated on start, since the following happened:
+ // 1. The position of the helper is absolute, so it's position is calculated based on the next positioned parent
+ // 2. The actual offset parent is a child of the scroll parent, and the scroll parent isn't the document, which means that
+ // the scroll is included in the initial calculation of the offset of the parent, and never recalculated upon drag
+ if(this.cssPosition == 'absolute' && this.scrollParent[0] != document && $.contains(this.scrollParent[0], this.offsetParent[0])) {
+ po.left += this.scrollParent.scrollLeft();
+ po.top += this.scrollParent.scrollTop();
+ }
+
+ if((this.offsetParent[0] == document.body) //This needs to be actually done for all browsers, since pageX/pageY includes this information
+ || (this.offsetParent[0].tagName && this.offsetParent[0].tagName.toLowerCase() == 'html' && $.ui.ie)) //Ugly IE fix
+ po = { top: 0, left: 0 };
+
+ return {
+ top: po.top + (parseInt(this.offsetParent.css("borderTopWidth"),10) || 0),
+ left: po.left + (parseInt(this.offsetParent.css("borderLeftWidth"),10) || 0)
+ };
+
+ },
+
+ _getRelativeOffset: function() {
+
+ if(this.cssPosition == "relative") {
+ var p = this.element.position();
+ return {
+ top: p.top - (parseInt(this.helper.css("top"),10) || 0) + this.scrollParent.scrollTop(),
+ left: p.left - (parseInt(this.helper.css("left"),10) || 0) + this.scrollParent.scrollLeft()
+ };
+ } else {
+ return { top: 0, left: 0 };
+ }
+
+ },
+
+ _cacheMargins: function() {
+ this.margins = {
+ left: (parseInt(this.element.css("marginLeft"),10) || 0),
+ top: (parseInt(this.element.css("marginTop"),10) || 0),
+ right: (parseInt(this.element.css("marginRight"),10) || 0),
+ bottom: (parseInt(this.element.css("marginBottom"),10) || 0)
+ };
+ },
+
+ _cacheHelperProportions: function() {
+ this.helperProportions = {
+ width: this.helper.outerWidth(),
+ height: this.helper.outerHeight()
+ };
+ },
+
+ _setContainment: function() {
+
+ var o = this.options;
+ if(o.containment == 'parent') o.containment = this.helper[0].parentNode;
+ if(o.containment == 'document' || o.containment == 'window') this.containment = [
+ o.containment == 'document' ? 0 : $(window).scrollLeft() - this.offset.relative.left - this.offset.parent.left,
+ o.containment == 'document' ? 0 : $(window).scrollTop() - this.offset.relative.top - this.offset.parent.top,
+ (o.containment == 'document' ? 0 : $(window).scrollLeft()) + $(o.containment == 'document' ? document : window).width() - this.helperProportions.width - this.margins.left,
+ (o.containment == 'document' ? 0 : $(window).scrollTop()) + ($(o.containment == 'document' ? document : window).height() || document.body.parentNode.scrollHeight) - this.helperProportions.height - this.margins.top
+ ];
+
+ if(!(/^(document|window|parent)$/).test(o.containment) && o.containment.constructor != Array) {
+ var c = $(o.containment);
+ var ce = c[0]; if(!ce) return;
+ var co = c.offset();
+ var over = ($(ce).css("overflow") != 'hidden');
+
+ this.containment = [
+ (parseInt($(ce).css("borderLeftWidth"),10) || 0) + (parseInt($(ce).css("paddingLeft"),10) || 0),
+ (parseInt($(ce).css("borderTopWidth"),10) || 0) + (parseInt($(ce).css("paddingTop"),10) || 0),
+ (over ? Math.max(ce.scrollWidth,ce.offsetWidth) : ce.offsetWidth) - (parseInt($(ce).css("borderLeftWidth"),10) || 0) - (parseInt($(ce).css("paddingRight"),10) || 0) - this.helperProportions.width - this.margins.left - this.margins.right,
+ (over ? Math.max(ce.scrollHeight,ce.offsetHeight) : ce.offsetHeight) - (parseInt($(ce).css("borderTopWidth"),10) || 0) - (parseInt($(ce).css("paddingBottom"),10) || 0) - this.helperProportions.height - this.margins.top - this.margins.bottom
+ ];
+ this.relative_container = c;
+
+ } else if(o.containment.constructor == Array) {
+ this.containment = o.containment;
+ }
+
+ },
+
+ _convertPositionTo: function(d, pos) {
+
+ if(!pos) pos = this.position;
+ var mod = d == "absolute" ? 1 : -1;
+ var o = this.options, scroll = this.cssPosition == 'absolute' && !(this.scrollParent[0] != document && $.contains(this.scrollParent[0], this.offsetParent[0])) ? this.offsetParent : this.scrollParent, scrollIsRootNode = (/(html|body)/i).test(scroll[0].tagName);
+
+ return {
+ top: (
+ pos.top // The absolute mouse position
+ + this.offset.relative.top * mod // Only for relative positioned nodes: Relative offset from element to offset parent
+ + this.offset.parent.top * mod // The offsetParent's offset without borders (offset + border)
+ - ( ( this.cssPosition == 'fixed' ? -this.scrollParent.scrollTop() : ( scrollIsRootNode ? 0 : scroll.scrollTop() ) ) * mod)
+ ),
+ left: (
+ pos.left // The absolute mouse position
+ + this.offset.relative.left * mod // Only for relative positioned nodes: Relative offset from element to offset parent
+ + this.offset.parent.left * mod // The offsetParent's offset without borders (offset + border)
+ - ( ( this.cssPosition == 'fixed' ? -this.scrollParent.scrollLeft() : scrollIsRootNode ? 0 : scroll.scrollLeft() ) * mod)
+ )
+ };
+
+ },
+
+ _generatePosition: function(event) {
+
+ var o = this.options, scroll = this.cssPosition == 'absolute' && !(this.scrollParent[0] != document && $.contains(this.scrollParent[0], this.offsetParent[0])) ? this.offsetParent : this.scrollParent, scrollIsRootNode = (/(html|body)/i).test(scroll[0].tagName);
+ var pageX = event.pageX;
+ var pageY = event.pageY;
+
+ /*
+ * - Position constraining -
+ * Constrain the position to a mix of grid, containment.
+ */
+
+ if(this.originalPosition) { //If we are not dragging yet, we won't check for options
+ var containment;
+ if(this.containment) {
+ if (this.relative_container){
+ var co = this.relative_container.offset();
+ containment = [ this.containment[0] + co.left,
+ this.containment[1] + co.top,
+ this.containment[2] + co.left,
+ this.containment[3] + co.top ];
+ }
+ else {
+ containment = this.containment;
+ }
+
+ if(event.pageX - this.offset.click.left < containment[0]) pageX = containment[0] + this.offset.click.left;
+ if(event.pageY - this.offset.click.top < containment[1]) pageY = containment[1] + this.offset.click.top;
+ if(event.pageX - this.offset.click.left > containment[2]) pageX = containment[2] + this.offset.click.left;
+ if(event.pageY - this.offset.click.top > containment[3]) pageY = containment[3] + this.offset.click.top;
+ }
+
+ if(o.grid) {
+ //Check for grid elements set to 0 to prevent divide by 0 error causing invalid argument errors in IE (see ticket #6950)
+ var top = o.grid[1] ? this.originalPageY + Math.round((pageY - this.originalPageY) / o.grid[1]) * o.grid[1] : this.originalPageY;
+ pageY = containment ? (!(top - this.offset.click.top < containment[1] || top - this.offset.click.top > containment[3]) ? top : (!(top - this.offset.click.top < containment[1]) ? top - o.grid[1] : top + o.grid[1])) : top;
+
+ var left = o.grid[0] ? this.originalPageX + Math.round((pageX - this.originalPageX) / o.grid[0]) * o.grid[0] : this.originalPageX;
+ pageX = containment ? (!(left - this.offset.click.left < containment[0] || left - this.offset.click.left > containment[2]) ? left : (!(left - this.offset.click.left < containment[0]) ? left - o.grid[0] : left + o.grid[0])) : left;
+ }
+
+ }
+
+ return {
+ top: (
+ pageY // The absolute mouse position
+ - this.offset.click.top // Click offset (relative to the element)
+ - this.offset.relative.top // Only for relative positioned nodes: Relative offset from element to offset parent
+ - this.offset.parent.top // The offsetParent's offset without borders (offset + border)
+ + ( ( this.cssPosition == 'fixed' ? -this.scrollParent.scrollTop() : ( scrollIsRootNode ? 0 : scroll.scrollTop() ) ))
+ ),
+ left: (
+ pageX // The absolute mouse position
+ - this.offset.click.left // Click offset (relative to the element)
+ - this.offset.relative.left // Only for relative positioned nodes: Relative offset from element to offset parent
+ - this.offset.parent.left // The offsetParent's offset without borders (offset + border)
+ + ( ( this.cssPosition == 'fixed' ? -this.scrollParent.scrollLeft() : scrollIsRootNode ? 0 : scroll.scrollLeft() ))
+ )
+ };
+
+ },
+
+ _clear: function() {
+ this.helper.removeClass("ui-draggable-dragging");
+ if(this.helper[0] != this.element[0] && !this.cancelHelperRemoval) this.helper.remove();
+ //if($.ui.ddmanager) $.ui.ddmanager.current = null;
+ this.helper = null;
+ this.cancelHelperRemoval = false;
+ },
+
+ // From now on bulk stuff - mainly helpers
+
+ _trigger: function(type, event, ui) {
+ ui = ui || this._uiHash();
+ $.ui.plugin.call(this, type, [event, ui]);
+ if(type == "drag") this.positionAbs = this._convertPositionTo("absolute"); //The absolute position has to be recalculated after plugins
+ return $.Widget.prototype._trigger.call(this, type, event, ui);
+ },
+
+ plugins: {},
+
+ _uiHash: function(event) {
+ return {
+ helper: this.helper,
+ position: this.position,
+ originalPosition: this.originalPosition,
+ offset: this.positionAbs
+ };
+ }
+
+});
+
+$.ui.plugin.add("draggable", "connectToSortable", {
+ start: function(event, ui) {
+
+ var inst = $(this).data("draggable"), o = inst.options,
+ uiSortable = $.extend({}, ui, { item: inst.element });
+ inst.sortables = [];
+ $(o.connectToSortable).each(function() {
+ var sortable = $.data(this, 'sortable');
+ if (sortable && !sortable.options.disabled) {
+ inst.sortables.push({
+ instance: sortable,
+ shouldRevert: sortable.options.revert
+ });
+ sortable.refreshPositions(); // Call the sortable's refreshPositions at drag start to refresh the containerCache since the sortable container cache is used in drag and needs to be up to date (this will ensure it's initialised as well as being kept in step with any changes that might have happened on the page).
+ sortable._trigger("activate", event, uiSortable);
+ }
+ });
+
+ },
+ stop: function(event, ui) {
+
+ //If we are still over the sortable, we fake the stop event of the sortable, but also remove helper
+ var inst = $(this).data("draggable"),
+ uiSortable = $.extend({}, ui, { item: inst.element });
+
+ $.each(inst.sortables, function() {
+ if(this.instance.isOver) {
+
+ this.instance.isOver = 0;
+
+ inst.cancelHelperRemoval = true; //Don't remove the helper in the draggable instance
+ this.instance.cancelHelperRemoval = false; //Remove it in the sortable instance (so sortable plugins like revert still work)
+
+ //The sortable revert is supported, and we have to set a temporary dropped variable on the draggable to support revert: 'valid/invalid'
+ if(this.shouldRevert) this.instance.options.revert = true;
+
+ //Trigger the stop of the sortable
+ this.instance._mouseStop(event);
+
+ this.instance.options.helper = this.instance.options._helper;
+
+ //If the helper has been the original item, restore properties in the sortable
+ if(inst.options.helper == 'original')
+ this.instance.currentItem.css({ top: 'auto', left: 'auto' });
+
+ } else {
+ this.instance.cancelHelperRemoval = false; //Remove the helper in the sortable instance
+ this.instance._trigger("deactivate", event, uiSortable);
+ }
+
+ });
+
+ },
+ drag: function(event, ui) {
+
+ var inst = $(this).data("draggable"), that = this;
+
+ var checkPos = function(o) {
+ var dyClick = this.offset.click.top, dxClick = this.offset.click.left;
+ var helperTop = this.positionAbs.top, helperLeft = this.positionAbs.left;
+ var itemHeight = o.height, itemWidth = o.width;
+ var itemTop = o.top, itemLeft = o.left;
+
+ return $.ui.isOver(helperTop + dyClick, helperLeft + dxClick, itemTop, itemLeft, itemHeight, itemWidth);
+ };
+
+ $.each(inst.sortables, function(i) {
+
+ var innermostIntersecting = false;
+ var thisSortable = this;
+ //Copy over some variables to allow calling the sortable's native _intersectsWith
+ this.instance.positionAbs = inst.positionAbs;
+ this.instance.helperProportions = inst.helperProportions;
+ this.instance.offset.click = inst.offset.click;
+
+ if(this.instance._intersectsWith(this.instance.containerCache)) {
+ innermostIntersecting = true;
+ $.each(inst.sortables, function () {
+ this.instance.positionAbs = inst.positionAbs;
+ this.instance.helperProportions = inst.helperProportions;
+ this.instance.offset.click = inst.offset.click;
+ if (this != thisSortable
+ && this.instance._intersectsWith(this.instance.containerCache)
+ && $.ui.contains(thisSortable.instance.element[0], this.instance.element[0]))
+ innermostIntersecting = false;
+ return innermostIntersecting;
+ });
+ }
+
+
+ if(innermostIntersecting) {
+ //If it intersects, we use a little isOver variable and set it once, so our move-in stuff gets fired only once
+ if(!this.instance.isOver) {
+
+ this.instance.isOver = 1;
+ //Now we fake the start of dragging for the sortable instance,
+ //by cloning the list group item, appending it to the sortable and using it as inst.currentItem
+ //We can then fire the start event of the sortable with our passed browser event, and our own helper (so it doesn't create a new one)
+ this.instance.currentItem = $(that).clone().removeAttr('id').appendTo(this.instance.element).data("sortable-item", true);
+ this.instance.options._helper = this.instance.options.helper; //Store helper option to later restore it
+ this.instance.options.helper = function() { return ui.helper[0]; };
+
+ event.target = this.instance.currentItem[0];
+ this.instance._mouseCapture(event, true);
+ this.instance._mouseStart(event, true, true);
+
+ //Because the browser event is way off the new appended portlet, we modify a couple of variables to reflect the changes
+ this.instance.offset.click.top = inst.offset.click.top;
+ this.instance.offset.click.left = inst.offset.click.left;
+ this.instance.offset.parent.left -= inst.offset.parent.left - this.instance.offset.parent.left;
+ this.instance.offset.parent.top -= inst.offset.parent.top - this.instance.offset.parent.top;
+
+ inst._trigger("toSortable", event);
+ inst.dropped = this.instance.element; //draggable revert needs that
+ //hack so receive/update callbacks work (mostly)
+ inst.currentItem = inst.element;
+ this.instance.fromOutside = inst;
+
+ }
+
+ //Provided we did all the previous steps, we can fire the drag event of the sortable on every draggable drag, when it intersects with the sortable
+ if(this.instance.currentItem) this.instance._mouseDrag(event);
+
+ } else {
+
+ //If it doesn't intersect with the sortable, and it intersected before,
+ //we fake the drag stop of the sortable, but make sure it doesn't remove the helper by using cancelHelperRemoval
+ if(this.instance.isOver) {
+
+ this.instance.isOver = 0;
+ this.instance.cancelHelperRemoval = true;
+
+ //Prevent reverting on this forced stop
+ this.instance.options.revert = false;
+
+ // The out event needs to be triggered independently
+ this.instance._trigger('out', event, this.instance._uiHash(this.instance));
+
+ this.instance._mouseStop(event, true);
+ this.instance.options.helper = this.instance.options._helper;
+
+ //Now we remove our currentItem, the list group clone again, and the placeholder, and animate the helper back to it's original size
+ this.instance.currentItem.remove();
+ if(this.instance.placeholder) this.instance.placeholder.remove();
+
+ inst._trigger("fromSortable", event);
+ inst.dropped = false; //draggable revert needs that
+ }
+
+ };
+
+ });
+
+ }
+});
+
+$.ui.plugin.add("draggable", "cursor", {
+ start: function(event, ui) {
+ var t = $('body'), o = $(this).data('draggable').options;
+ if (t.css("cursor")) o._cursor = t.css("cursor");
+ t.css("cursor", o.cursor);
+ },
+ stop: function(event, ui) {
+ var o = $(this).data('draggable').options;
+ if (o._cursor) $('body').css("cursor", o._cursor);
+ }
+});
+
+$.ui.plugin.add("draggable", "opacity", {
+ start: function(event, ui) {
+ var t = $(ui.helper), o = $(this).data('draggable').options;
+ if(t.css("opacity")) o._opacity = t.css("opacity");
+ t.css('opacity', o.opacity);
+ },
+ stop: function(event, ui) {
+ var o = $(this).data('draggable').options;
+ if(o._opacity) $(ui.helper).css('opacity', o._opacity);
+ }
+});
+
+$.ui.plugin.add("draggable", "scroll", {
+ start: function(event, ui) {
+ var i = $(this).data("draggable");
+ if(i.scrollParent[0] != document && i.scrollParent[0].tagName != 'HTML') i.overflowOffset = i.scrollParent.offset();
+ },
+ drag: function(event, ui) {
+
+ var i = $(this).data("draggable"), o = i.options, scrolled = false;
+
+ if(i.scrollParent[0] != document && i.scrollParent[0].tagName != 'HTML') {
+
+ if(!o.axis || o.axis != 'x') {
+ if((i.overflowOffset.top + i.scrollParent[0].offsetHeight) - event.pageY < o.scrollSensitivity)
+ i.scrollParent[0].scrollTop = scrolled = i.scrollParent[0].scrollTop + o.scrollSpeed;
+ else if(event.pageY - i.overflowOffset.top < o.scrollSensitivity)
+ i.scrollParent[0].scrollTop = scrolled = i.scrollParent[0].scrollTop - o.scrollSpeed;
+ }
+
+ if(!o.axis || o.axis != 'y') {
+ if((i.overflowOffset.left + i.scrollParent[0].offsetWidth) - event.pageX < o.scrollSensitivity)
+ i.scrollParent[0].scrollLeft = scrolled = i.scrollParent[0].scrollLeft + o.scrollSpeed;
+ else if(event.pageX - i.overflowOffset.left < o.scrollSensitivity)
+ i.scrollParent[0].scrollLeft = scrolled = i.scrollParent[0].scrollLeft - o.scrollSpeed;
+ }
+
+ } else {
+
+ if(!o.axis || o.axis != 'x') {
+ if(event.pageY - $(document).scrollTop() < o.scrollSensitivity)
+ scrolled = $(document).scrollTop($(document).scrollTop() - o.scrollSpeed);
+ else if($(window).height() - (event.pageY - $(document).scrollTop()) < o.scrollSensitivity)
+ scrolled = $(document).scrollTop($(document).scrollTop() + o.scrollSpeed);
+ }
+
+ if(!o.axis || o.axis != 'y') {
+ if(event.pageX - $(document).scrollLeft() < o.scrollSensitivity)
+ scrolled = $(document).scrollLeft($(document).scrollLeft() - o.scrollSpeed);
+ else if($(window).width() - (event.pageX - $(document).scrollLeft()) < o.scrollSensitivity)
+ scrolled = $(document).scrollLeft($(document).scrollLeft() + o.scrollSpeed);
+ }
+
+ }
+
+ if(scrolled !== false && $.ui.ddmanager && !o.dropBehaviour)
+ $.ui.ddmanager.prepareOffsets(i, event);
+
+ }
+});
+
+$.ui.plugin.add("draggable", "snap", {
+ start: function(event, ui) {
+
+ var i = $(this).data("draggable"), o = i.options;
+ i.snapElements = [];
+
+ $(o.snap.constructor != String ? ( o.snap.items || ':data(draggable)' ) : o.snap).each(function() {
+ var $t = $(this); var $o = $t.offset();
+ if(this != i.element[0]) i.snapElements.push({
+ item: this,
+ width: $t.outerWidth(), height: $t.outerHeight(),
+ top: $o.top, left: $o.left
+ });
+ });
+
+ },
+ drag: function(event, ui) {
+
+ var inst = $(this).data("draggable"), o = inst.options;
+ var d = o.snapTolerance;
+
+ var x1 = ui.offset.left, x2 = x1 + inst.helperProportions.width,
+ y1 = ui.offset.top, y2 = y1 + inst.helperProportions.height;
+
+ for (var i = inst.snapElements.length - 1; i >= 0; i--){
+
+ var l = inst.snapElements[i].left, r = l + inst.snapElements[i].width,
+ t = inst.snapElements[i].top, b = t + inst.snapElements[i].height;
+
+ //Yes, I know, this is insane ;)
+ if(!((l-d < x1 && x1 < r+d && t-d < y1 && y1 < b+d) || (l-d < x1 && x1 < r+d && t-d < y2 && y2 < b+d) || (l-d < x2 && x2 < r+d && t-d < y1 && y1 < b+d) || (l-d < x2 && x2 < r+d && t-d < y2 && y2 < b+d))) {
+ if(inst.snapElements[i].snapping) (inst.options.snap.release && inst.options.snap.release.call(inst.element, event, $.extend(inst._uiHash(), { snapItem: inst.snapElements[i].item })));
+ inst.snapElements[i].snapping = false;
+ continue;
+ }
+
+ if(o.snapMode != 'inner') {
+ var ts = Math.abs(t - y2) <= d;
+ var bs = Math.abs(b - y1) <= d;
+ var ls = Math.abs(l - x2) <= d;
+ var rs = Math.abs(r - x1) <= d;
+ if(ts) ui.position.top = inst._convertPositionTo("relative", { top: t - inst.helperProportions.height, left: 0 }).top - inst.margins.top;
+ if(bs) ui.position.top = inst._convertPositionTo("relative", { top: b, left: 0 }).top - inst.margins.top;
+ if(ls) ui.position.left = inst._convertPositionTo("relative", { top: 0, left: l - inst.helperProportions.width }).left - inst.margins.left;
+ if(rs) ui.position.left = inst._convertPositionTo("relative", { top: 0, left: r }).left - inst.margins.left;
+ }
+
+ var first = (ts || bs || ls || rs);
+
+ if(o.snapMode != 'outer') {
+ var ts = Math.abs(t - y1) <= d;
+ var bs = Math.abs(b - y2) <= d;
+ var ls = Math.abs(l - x1) <= d;
+ var rs = Math.abs(r - x2) <= d;
+ if(ts) ui.position.top = inst._convertPositionTo("relative", { top: t, left: 0 }).top - inst.margins.top;
+ if(bs) ui.position.top = inst._convertPositionTo("relative", { top: b - inst.helperProportions.height, left: 0 }).top - inst.margins.top;
+ if(ls) ui.position.left = inst._convertPositionTo("relative", { top: 0, left: l }).left - inst.margins.left;
+ if(rs) ui.position.left = inst._convertPositionTo("relative", { top: 0, left: r - inst.helperProportions.width }).left - inst.margins.left;
+ }
+
+ if(!inst.snapElements[i].snapping && (ts || bs || ls || rs || first))
+ (inst.options.snap.snap && inst.options.snap.snap.call(inst.element, event, $.extend(inst._uiHash(), { snapItem: inst.snapElements[i].item })));
+ inst.snapElements[i].snapping = (ts || bs || ls || rs || first);
+
+ };
+
+ }
+});
+
+$.ui.plugin.add("draggable", "stack", {
+ start: function(event, ui) {
+
+ var o = $(this).data("draggable").options;
+
+ var group = $.makeArray($(o.stack)).sort(function(a,b) {
+ return (parseInt($(a).css("zIndex"),10) || 0) - (parseInt($(b).css("zIndex"),10) || 0);
+ });
+ if (!group.length) { return; }
+
+ var min = parseInt(group[0].style.zIndex) || 0;
+ $(group).each(function(i) {
+ this.style.zIndex = min + i;
+ });
+
+ this[0].style.zIndex = min + group.length;
+
+ }
+});
+
+$.ui.plugin.add("draggable", "zIndex", {
+ start: function(event, ui) {
+ var t = $(ui.helper), o = $(this).data("draggable").options;
+ if(t.css("zIndex")) o._zIndex = t.css("zIndex");
+ t.css('zIndex', o.zIndex);
+ },
+ stop: function(event, ui) {
+ var o = $(this).data("draggable").options;
+ if(o._zIndex) $(ui.helper).css('zIndex', o._zIndex);
+ }
+});
+
+})(jQuery);
diff --git a/js/jquery/src/jquery-ui/jquery.ui.droppable.js b/js/jquery/src/jquery-ui/jquery.ui.droppable.js
new file mode 100644
index 0000000000..93e74d2114
--- /dev/null
+++ b/js/jquery/src/jquery-ui/jquery.ui.droppable.js
@@ -0,0 +1,294 @@
+/*!
+ * jQuery UI Droppable @VERSION
+ * http://jqueryui.com
+ *
+ * Copyright 2012 jQuery Foundation and other contributors
+ * Released under the MIT license.
+ * http://jquery.org/license
+ *
+ * http://api.jqueryui.com/droppable/
+ *
+ * Depends:
+ * jquery.ui.core.js
+ * jquery.ui.widget.js
+ * jquery.ui.mouse.js
+ * jquery.ui.draggable.js
+ */
+(function( $, undefined ) {
+
+$.widget("ui.droppable", {
+ version: "@VERSION",
+ widgetEventPrefix: "drop",
+ options: {
+ accept: '*',
+ activeClass: false,
+ addClasses: true,
+ greedy: false,
+ hoverClass: false,
+ scope: 'default',
+ tolerance: 'intersect'
+ },
+ _create: function() {
+
+ var o = this.options, accept = o.accept;
+ this.isover = 0; this.isout = 1;
+
+ this.accept = $.isFunction(accept) ? accept : function(d) {
+ return d.is(accept);
+ };
+
+ //Store the droppable's proportions
+ this.proportions = { width: this.element[0].offsetWidth, height: this.element[0].offsetHeight };
+
+ // Add the reference and positions to the manager
+ $.ui.ddmanager.droppables[o.scope] = $.ui.ddmanager.droppables[o.scope] || [];
+ $.ui.ddmanager.droppables[o.scope].push(this);
+
+ (o.addClasses && this.element.addClass("ui-droppable"));
+
+ },
+
+ _destroy: function() {
+ var drop = $.ui.ddmanager.droppables[this.options.scope];
+ for ( var i = 0; i < drop.length; i++ )
+ if ( drop[i] == this )
+ drop.splice(i, 1);
+
+ this.element.removeClass("ui-droppable ui-droppable-disabled");
+ },
+
+ _setOption: function(key, value) {
+
+ if(key == 'accept') {
+ this.accept = $.isFunction(value) ? value : function(d) {
+ return d.is(value);
+ };
+ }
+ $.Widget.prototype._setOption.apply(this, arguments);
+ },
+
+ _activate: function(event) {
+ var draggable = $.ui.ddmanager.current;
+ if(this.options.activeClass) this.element.addClass(this.options.activeClass);
+ (draggable && this._trigger('activate', event, this.ui(draggable)));
+ },
+
+ _deactivate: function(event) {
+ var draggable = $.ui.ddmanager.current;
+ if(this.options.activeClass) this.element.removeClass(this.options.activeClass);
+ (draggable && this._trigger('deactivate', event, this.ui(draggable)));
+ },
+
+ _over: function(event) {
+
+ var draggable = $.ui.ddmanager.current;
+ if (!draggable || (draggable.currentItem || draggable.element)[0] == this.element[0]) return; // Bail if draggable and droppable are same element
+
+ if (this.accept.call(this.element[0],(draggable.currentItem || draggable.element))) {
+ if(this.options.hoverClass) this.element.addClass(this.options.hoverClass);
+ this._trigger('over', event, this.ui(draggable));
+ }
+
+ },
+
+ _out: function(event) {
+
+ var draggable = $.ui.ddmanager.current;
+ if (!draggable || (draggable.currentItem || draggable.element)[0] == this.element[0]) return; // Bail if draggable and droppable are same element
+
+ if (this.accept.call(this.element[0],(draggable.currentItem || draggable.element))) {
+ if(this.options.hoverClass) this.element.removeClass(this.options.hoverClass);
+ this._trigger('out', event, this.ui(draggable));
+ }
+
+ },
+
+ _drop: function(event,custom) {
+
+ var draggable = custom || $.ui.ddmanager.current;
+ if (!draggable || (draggable.currentItem || draggable.element)[0] == this.element[0]) return false; // Bail if draggable and droppable are same element
+
+ var childrenIntersection = false;
+ this.element.find(":data(droppable)").not(".ui-draggable-dragging").each(function() {
+ var inst = $.data(this, 'droppable');
+ if(
+ inst.options.greedy
+ && !inst.options.disabled
+ && inst.options.scope == draggable.options.scope
+ && inst.accept.call(inst.element[0], (draggable.currentItem || draggable.element))
+ && $.ui.intersect(draggable, $.extend(inst, { offset: inst.element.offset() }), inst.options.tolerance)
+ ) { childrenIntersection = true; return false; }
+ });
+ if(childrenIntersection) return false;
+
+ if(this.accept.call(this.element[0],(draggable.currentItem || draggable.element))) {
+ if(this.options.activeClass) this.element.removeClass(this.options.activeClass);
+ if(this.options.hoverClass) this.element.removeClass(this.options.hoverClass);
+ this._trigger('drop', event, this.ui(draggable));
+ return this.element;
+ }
+
+ return false;
+
+ },
+
+ ui: function(c) {
+ return {
+ draggable: (c.currentItem || c.element),
+ helper: c.helper,
+ position: c.position,
+ offset: c.positionAbs
+ };
+ }
+
+});
+
+$.ui.intersect = function(draggable, droppable, toleranceMode) {
+
+ if (!droppable.offset) return false;
+
+ var x1 = (draggable.positionAbs || draggable.position.absolute).left, x2 = x1 + draggable.helperProportions.width,
+ y1 = (draggable.positionAbs || draggable.position.absolute).top, y2 = y1 + draggable.helperProportions.height;
+ var l = droppable.offset.left, r = l + droppable.proportions.width,
+ t = droppable.offset.top, b = t + droppable.proportions.height;
+
+ switch (toleranceMode) {
+ case 'fit':
+ return (l <= x1 && x2 <= r
+ && t <= y1 && y2 <= b);
+ break;
+ case 'intersect':
+ return (l < x1 + (draggable.helperProportions.width / 2) // Right Half
+ && x2 - (draggable.helperProportions.width / 2) < r // Left Half
+ && t < y1 + (draggable.helperProportions.height / 2) // Bottom Half
+ && y2 - (draggable.helperProportions.height / 2) < b ); // Top Half
+ break;
+ case 'pointer':
+ var draggableLeft = ((draggable.positionAbs || draggable.position.absolute).left + (draggable.clickOffset || draggable.offset.click).left),
+ draggableTop = ((draggable.positionAbs || draggable.position.absolute).top + (draggable.clickOffset || draggable.offset.click).top),
+ isOver = $.ui.isOver(draggableTop, draggableLeft, t, l, droppable.proportions.height, droppable.proportions.width);
+ return isOver;
+ break;
+ case 'touch':
+ return (
+ (y1 >= t && y1 <= b) || // Top edge touching
+ (y2 >= t && y2 <= b) || // Bottom edge touching
+ (y1 < t && y2 > b) // Surrounded vertically
+ ) && (
+ (x1 >= l && x1 <= r) || // Left edge touching
+ (x2 >= l && x2 <= r) || // Right edge touching
+ (x1 < l && x2 > r) // Surrounded horizontally
+ );
+ break;
+ default:
+ return false;
+ break;
+ }
+
+};
+
+/*
+ This manager tracks offsets of draggables and droppables
+*/
+$.ui.ddmanager = {
+ current: null,
+ droppables: { 'default': [] },
+ prepareOffsets: function(t, event) {
+
+ var m = $.ui.ddmanager.droppables[t.options.scope] || [];
+ var type = event ? event.type : null; // workaround for #2317
+ var list = (t.currentItem || t.element).find(":data(droppable)").andSelf();
+
+ droppablesLoop: for (var i = 0; i < m.length; i++) {
+
+ if(m[i].options.disabled || (t && !m[i].accept.call(m[i].element[0],(t.currentItem || t.element)))) continue; //No disabled and non-accepted
+ for (var j=0; j < list.length; j++) { if(list[j] == m[i].element[0]) { m[i].proportions.height = 0; continue droppablesLoop; } }; //Filter out elements in the current dragged item
+ m[i].visible = m[i].element.css("display") != "none"; if(!m[i].visible) continue; //If the element is not visible, continue
+
+ if(type == "mousedown") m[i]._activate.call(m[i], event); //Activate the droppable if used directly from draggables
+
+ m[i].offset = m[i].element.offset();
+ m[i].proportions = { width: m[i].element[0].offsetWidth, height: m[i].element[0].offsetHeight };
+
+ }
+
+ },
+ drop: function(draggable, event) {
+
+ var dropped = false;
+ $.each($.ui.ddmanager.droppables[draggable.options.scope] || [], function() {
+
+ if(!this.options) return;
+ if (!this.options.disabled && this.visible && $.ui.intersect(draggable, this, this.options.tolerance))
+ dropped = this._drop.call(this, event) || dropped;
+
+ if (!this.options.disabled && this.visible && this.accept.call(this.element[0],(draggable.currentItem || draggable.element))) {
+ this.isout = 1; this.isover = 0;
+ this._deactivate.call(this, event);
+ }
+
+ });
+ return dropped;
+
+ },
+ dragStart: function( draggable, event ) {
+ //Listen for scrolling so that if the dragging causes scrolling the position of the droppables can be recalculated (see #5003)
+ draggable.element.parentsUntil( "body" ).bind( "scroll.droppable", function() {
+ if( !draggable.options.refreshPositions ) $.ui.ddmanager.prepareOffsets( draggable, event );
+ });
+ },
+ drag: function(draggable, event) {
+
+ //If you have a highly dynamic page, you might try this option. It renders positions every time you move the mouse.
+ if(draggable.options.refreshPositions) $.ui.ddmanager.prepareOffsets(draggable, event);
+
+ //Run through all droppables and check their positions based on specific tolerance options
+ $.each($.ui.ddmanager.droppables[draggable.options.scope] || [], function() {
+
+ if(this.options.disabled || this.greedyChild || !this.visible) return;
+ var intersects = $.ui.intersect(draggable, this, this.options.tolerance);
+
+ var c = !intersects && this.isover == 1 ? 'isout' : (intersects && this.isover == 0 ? 'isover' : null);
+ if(!c) return;
+
+ var parentInstance;
+ if (this.options.greedy) {
+ // find droppable parents with same scope
+ var scope = this.options.scope;
+ var parent = this.element.parents(':data(droppable)').filter(function () {
+ return $.data(this, 'droppable').options.scope === scope;
+ });
+
+ if (parent.length) {
+ parentInstance = $.data(parent[0], 'droppable');
+ parentInstance.greedyChild = (c == 'isover' ? 1 : 0);
+ }
+ }
+
+ // we just moved into a greedy child
+ if (parentInstance && c == 'isover') {
+ parentInstance['isover'] = 0;
+ parentInstance['isout'] = 1;
+ parentInstance._out.call(parentInstance, event);
+ }
+
+ this[c] = 1; this[c == 'isout' ? 'isover' : 'isout'] = 0;
+ this[c == "isover" ? "_over" : "_out"].call(this, event);
+
+ // we just moved out of a greedy child
+ if (parentInstance && c == 'isout') {
+ parentInstance['isout'] = 0;
+ parentInstance['isover'] = 1;
+ parentInstance._over.call(parentInstance, event);
+ }
+ });
+
+ },
+ dragStop: function( draggable, event ) {
+ draggable.element.parentsUntil( "body" ).unbind( "scroll.droppable" );
+ //Call prepareOffsets one final time since IE does not fire return scroll events when overflow was caused by drag (see #5003)
+ if( !draggable.options.refreshPositions ) $.ui.ddmanager.prepareOffsets( draggable, event );
+ }
+};
+
+})(jQuery);
diff --git a/js/jquery/src/jquery-ui/jquery.ui.effect-blind.js b/js/jquery/src/jquery-ui/jquery.ui.effect-blind.js
new file mode 100644
index 0000000000..0ee31c078a
--- /dev/null
+++ b/js/jquery/src/jquery-ui/jquery.ui.effect-blind.js
@@ -0,0 +1,82 @@
+/*!
+ * jQuery UI Effects Blind @VERSION
+ * http://jqueryui.com
+ *
+ * Copyright 2012 jQuery Foundation and other contributors
+ * Released under the MIT license.
+ * http://jquery.org/license
+ *
+ * http://api.jqueryui.com/blind-effect/
+ *
+ * Depends:
+ * jquery.ui.effect.js
+ */
+(function( $, undefined ) {
+
+var rvertical = /up|down|vertical/,
+ rpositivemotion = /up|left|vertical|horizontal/;
+
+$.effects.effect.blind = function( o, done ) {
+ // Create element
+ var el = $( this ),
+ props = [ "position", "top", "bottom", "left", "right", "height", "width" ],
+ mode = $.effects.setMode( el, o.mode || "hide" ),
+ direction = o.direction || "up",
+ vertical = rvertical.test( direction ),
+ ref = vertical ? "height" : "width",
+ ref2 = vertical ? "top" : "left",
+ motion = rpositivemotion.test( direction ),
+ animation = {},
+ show = mode === "show",
+ wrapper, distance, margin;
+
+ // if already wrapped, the wrapper's properties are my property. #6245
+ if ( el.parent().is( ".ui-effects-wrapper" ) ) {
+ $.effects.save( el.parent(), props );
+ } else {
+ $.effects.save( el, props );
+ }
+ el.show();
+ wrapper = $.effects.createWrapper( el ).css({
+ overflow: "hidden"
+ });
+
+ distance = wrapper[ ref ]();
+ margin = parseFloat( wrapper.css( ref2 ) ) || 0;
+
+ animation[ ref ] = show ? distance : 0;
+ if ( !motion ) {
+ el
+ .css( vertical ? "bottom" : "right", 0 )
+ .css( vertical ? "top" : "left", "auto" )
+ .css({ position: "absolute" });
+
+ animation[ ref2 ] = show ? margin : distance + margin;
+ }
+
+ // start at 0 if we are showing
+ if ( show ) {
+ wrapper.css( ref, 0 );
+ if ( ! motion ) {
+ wrapper.css( ref2, margin + distance );
+ }
+ }
+
+ // Animate
+ wrapper.animate( animation, {
+ duration: o.duration,
+ easing: o.easing,
+ queue: false,
+ complete: function() {
+ if ( mode === "hide" ) {
+ el.hide();
+ }
+ $.effects.restore( el, props );
+ $.effects.removeWrapper( el );
+ done();
+ }
+ });
+
+};
+
+})(jQuery);
diff --git a/js/jquery/src/jquery-ui/jquery.ui.effect-bounce.js b/js/jquery/src/jquery-ui/jquery.ui.effect-bounce.js
new file mode 100644
index 0000000000..d36b0660fc
--- /dev/null
+++ b/js/jquery/src/jquery-ui/jquery.ui.effect-bounce.js
@@ -0,0 +1,113 @@
+/*!
+ * jQuery UI Effects Bounce @VERSION
+ * http://jqueryui.com
+ *
+ * Copyright 2012 jQuery Foundation and other contributors
+ * Released under the MIT license.
+ * http://jquery.org/license
+ *
+ * http://api.jqueryui.com/bounce-effect/
+ *
+ * Depends:
+ * jquery.ui.effect.js
+ */
+(function( $, undefined ) {
+
+$.effects.effect.bounce = function( o, done ) {
+ var el = $( this ),
+ props = [ "position", "top", "bottom", "left", "right", "height", "width" ],
+
+ // defaults:
+ mode = $.effects.setMode( el, o.mode || "effect" ),
+ hide = mode === "hide",
+ show = mode === "show",
+ direction = o.direction || "up",
+ distance = o.distance,
+ times = o.times || 5,
+
+ // number of internal animations
+ anims = times * 2 + ( show || hide ? 1 : 0 ),
+ speed = o.duration / anims,
+ easing = o.easing,
+
+ // utility:
+ ref = ( direction === "up" || direction === "down" ) ? "top" : "left",
+ motion = ( direction === "up" || direction === "left" ),
+ i,
+ upAnim,
+ downAnim,
+
+ // we will need to re-assemble the queue to stack our animations in place
+ queue = el.queue(),
+ queuelen = queue.length;
+
+ // Avoid touching opacity to prevent clearType and PNG issues in IE
+ if ( show || hide ) {
+ props.push( "opacity" );
+ }
+
+ $.effects.save( el, props );
+ el.show();
+ $.effects.createWrapper( el ); // Create Wrapper
+
+ // default distance for the BIGGEST bounce is the outer Distance / 3
+ if ( !distance ) {
+ distance = el[ ref === "top" ? "outerHeight" : "outerWidth" ]() / 3;
+ }
+
+ if ( show ) {
+ downAnim = { opacity: 1 };
+ downAnim[ ref ] = 0;
+
+ // if we are showing, force opacity 0 and set the initial position
+ // then do the "first" animation
+ el.css( "opacity", 0 )
+ .css( ref, motion ? -distance * 2 : distance * 2 )
+ .animate( downAnim, speed, easing );
+ }
+
+ // start at the smallest distance if we are hiding
+ if ( hide ) {
+ distance = distance / Math.pow( 2, times - 1 );
+ }
+
+ downAnim = {};
+ downAnim[ ref ] = 0;
+ // Bounces up/down/left/right then back to 0 -- times * 2 animations happen here
+ for ( i = 0; i < times; i++ ) {
+ upAnim = {};
+ upAnim[ ref ] = ( motion ? "-=" : "+=" ) + distance;
+
+ el.animate( upAnim, speed, easing )
+ .animate( downAnim, speed, easing );
+
+ distance = hide ? distance * 2 : distance / 2;
+ }
+
+ // Last Bounce when Hiding
+ if ( hide ) {
+ upAnim = { opacity: 0 };
+ upAnim[ ref ] = ( motion ? "-=" : "+=" ) + distance;
+
+ el.animate( upAnim, speed, easing );
+ }
+
+ el.queue(function() {
+ if ( hide ) {
+ el.hide();
+ }
+ $.effects.restore( el, props );
+ $.effects.removeWrapper( el );
+ done();
+ });
+
+ // inject all the animations we just queued to be first in line (after "inprogress")
+ if ( queuelen > 1) {
+ queue.splice.apply( queue,
+ [ 1, 0 ].concat( queue.splice( queuelen, anims + 1 ) ) );
+ }
+ el.dequeue();
+
+};
+
+})(jQuery);
diff --git a/js/jquery/src/jquery-ui/jquery.ui.effect-clip.js b/js/jquery/src/jquery-ui/jquery.ui.effect-clip.js
new file mode 100644
index 0000000000..ce4402b528
--- /dev/null
+++ b/js/jquery/src/jquery-ui/jquery.ui.effect-clip.js
@@ -0,0 +1,67 @@
+/*!
+ * jQuery UI Effects Clip @VERSION
+ * http://jqueryui.com
+ *
+ * Copyright 2012 jQuery Foundation and other contributors
+ * Released under the MIT license.
+ * http://jquery.org/license
+ *
+ * http://api.jqueryui.com/clip-effect/
+ *
+ * Depends:
+ * jquery.ui.effect.js
+ */
+(function( $, undefined ) {
+
+$.effects.effect.clip = function( o, done ) {
+ // Create element
+ var el = $( this ),
+ props = [ "position", "top", "bottom", "left", "right", "height", "width" ],
+ mode = $.effects.setMode( el, o.mode || "hide" ),
+ show = mode === "show",
+ direction = o.direction || "vertical",
+ vert = direction === "vertical",
+ size = vert ? "height" : "width",
+ position = vert ? "top" : "left",
+ animation = {},
+ wrapper, animate, distance;
+
+ // Save & Show
+ $.effects.save( el, props );
+ el.show();
+
+ // Create Wrapper
+ wrapper = $.effects.createWrapper( el ).css({
+ overflow: "hidden"
+ });
+ animate = ( el[0].tagName === "IMG" ) ? wrapper : el;
+ distance = animate[ size ]();
+
+ // Shift
+ if ( show ) {
+ animate.css( size, 0 );
+ animate.css( position, distance / 2 );
+ }
+
+ // Create Animation Object:
+ animation[ size ] = show ? distance : 0;
+ animation[ position ] = show ? 0 : distance / 2;
+
+ // Animate
+ animate.animate( animation, {
+ queue: false,
+ duration: o.duration,
+ easing: o.easing,
+ complete: function() {
+ if ( !show ) {
+ el.hide();
+ }
+ $.effects.restore( el, props );
+ $.effects.removeWrapper( el );
+ done();
+ }
+ });
+
+};
+
+})(jQuery);
diff --git a/js/jquery/src/jquery-ui/jquery.ui.effect-drop.js b/js/jquery/src/jquery-ui/jquery.ui.effect-drop.js
new file mode 100644
index 0000000000..bd196ec738
--- /dev/null
+++ b/js/jquery/src/jquery-ui/jquery.ui.effect-drop.js
@@ -0,0 +1,65 @@
+/*!
+ * jQuery UI Effects Drop @VERSION
+ * http://jqueryui.com
+ *
+ * Copyright 2012 jQuery Foundation and other contributors
+ * Released under the MIT license.
+ * http://jquery.org/license
+ *
+ * http://api.jqueryui.com/drop-effect/
+ *
+ * Depends:
+ * jquery.ui.effect.js
+ */
+(function( $, undefined ) {
+
+$.effects.effect.drop = function( o, done ) {
+
+ var el = $( this ),
+ props = [ "position", "top", "bottom", "left", "right", "opacity", "height", "width" ],
+ mode = $.effects.setMode( el, o.mode || "hide" ),
+ show = mode === "show",
+ direction = o.direction || "left",
+ ref = ( direction === "up" || direction === "down" ) ? "top" : "left",
+ motion = ( direction === "up" || direction === "left" ) ? "pos" : "neg",
+ animation = {
+ opacity: show ? 1 : 0
+ },
+ distance;
+
+ // Adjust
+ $.effects.save( el, props );
+ el.show();
+ $.effects.createWrapper( el );
+
+ distance = o.distance || el[ ref === "top" ? "outerHeight": "outerWidth" ]( true ) / 2;
+
+ if ( show ) {
+ el
+ .css( "opacity", 0 )
+ .css( ref, motion === "pos" ? -distance : distance );
+ }
+
+ // Animation
+ animation[ ref ] = ( show ?
+ ( motion === "pos" ? "+=" : "-=" ) :
+ ( motion === "pos" ? "-=" : "+=" ) ) +
+ distance;
+
+ // Animate
+ el.animate( animation, {
+ queue: false,
+ duration: o.duration,
+ easing: o.easing,
+ complete: function() {
+ if ( mode === "hide" ) {
+ el.hide();
+ }
+ $.effects.restore( el, props );
+ $.effects.removeWrapper( el );
+ done();
+ }
+ });
+};
+
+})(jQuery);
diff --git a/js/jquery/src/jquery-ui/jquery.ui.effect-explode.js b/js/jquery/src/jquery-ui/jquery.ui.effect-explode.js
new file mode 100644
index 0000000000..b31efc8c18
--- /dev/null
+++ b/js/jquery/src/jquery-ui/jquery.ui.effect-explode.js
@@ -0,0 +1,97 @@
+/*!
+ * jQuery UI Effects Explode @VERSION
+ * http://jqueryui.com
+ *
+ * Copyright 2012 jQuery Foundation and other contributors
+ * Released under the MIT license.
+ * http://jquery.org/license
+ *
+ * http://api.jqueryui.com/explode-effect/
+ *
+ * Depends:
+ * jquery.ui.effect.js
+ */
+(function( $, undefined ) {
+
+$.effects.effect.explode = function( o, done ) {
+
+ var rows = o.pieces ? Math.round( Math.sqrt( o.pieces ) ) : 3,
+ cells = rows,
+ el = $( this ),
+ mode = $.effects.setMode( el, o.mode || "hide" ),
+ show = mode === "show",
+
+ // show and then visibility:hidden the element before calculating offset
+ offset = el.show().css( "visibility", "hidden" ).offset(),
+
+ // width and height of a piece
+ width = Math.ceil( el.outerWidth() / cells ),
+ height = Math.ceil( el.outerHeight() / rows ),
+ pieces = [],
+
+ // loop
+ i, j, left, top, mx, my;
+
+ // children animate complete:
+ function childComplete() {
+ pieces.push( this );
+ if ( pieces.length === rows * cells ) {
+ animComplete();
+ }
+ }
+
+ // clone the element for each row and cell.
+ for( i = 0; i < rows ; i++ ) { // ===>
+ top = offset.top + i * height;
+ my = i - ( rows - 1 ) / 2 ;
+
+ for( j = 0; j < cells ; j++ ) { // |||
+ left = offset.left + j * width;
+ mx = j - ( cells - 1 ) / 2 ;
+
+ // Create a clone of the now hidden main element that will be absolute positioned
+ // within a wrapper div off the -left and -top equal to size of our pieces
+ el
+ .clone()
+ .appendTo( "body" )
+ .wrap( "<div></div>" )
+ .css({
+ position: "absolute",
+ visibility: "visible",
+ left: -j * width,
+ top: -i * height
+ })
+
+ // select the wrapper - make it overflow: hidden and absolute positioned based on
+ // where the original was located +left and +top equal to the size of pieces
+ .parent()
+ .addClass( "ui-effects-explode" )
+ .css({
+ position: "absolute",
+ overflow: "hidden",
+ width: width,
+ height: height,
+ left: left + ( show ? mx * width : 0 ),
+ top: top + ( show ? my * height : 0 ),
+ opacity: show ? 0 : 1
+ }).animate({
+ left: left + ( show ? 0 : mx * width ),
+ top: top + ( show ? 0 : my * height ),
+ opacity: show ? 1 : 0
+ }, o.duration || 500, o.easing, childComplete );
+ }
+ }
+
+ function animComplete() {
+ el.css({
+ visibility: "visible"
+ });
+ $( pieces ).remove();
+ if ( !show ) {
+ el.hide();
+ }
+ done();
+ }
+};
+
+})(jQuery);
diff --git a/js/jquery/src/jquery-ui/jquery.ui.effect-fade.js b/js/jquery/src/jquery-ui/jquery.ui.effect-fade.js
new file mode 100644
index 0000000000..00d823260b
--- /dev/null
+++ b/js/jquery/src/jquery-ui/jquery.ui.effect-fade.js
@@ -0,0 +1,30 @@
+/*!
+ * jQuery UI Effects Fade @VERSION
+ * http://jqueryui.com
+ *
+ * Copyright 2012 jQuery Foundation and other contributors
+ * Released under the MIT license.
+ * http://jquery.org/license
+ *
+ * http://api.jqueryui.com/fade-effect/
+ *
+ * Depends:
+ * jquery.ui.effect.js
+ */
+(function( $, undefined ) {
+
+$.effects.effect.fade = function( o, done ) {
+ var el = $( this ),
+ mode = $.effects.setMode( el, o.mode || "toggle" );
+
+ el.animate({
+ opacity: mode
+ }, {
+ queue: false,
+ duration: o.duration,
+ easing: o.easing,
+ complete: done
+ });
+};
+
+})( jQuery );
diff --git a/js/jquery/src/jquery-ui/jquery.ui.effect-fold.js b/js/jquery/src/jquery-ui/jquery.ui.effect-fold.js
new file mode 100644
index 0000000000..893a27e2c8
--- /dev/null
+++ b/js/jquery/src/jquery-ui/jquery.ui.effect-fold.js
@@ -0,0 +1,76 @@
+/*!
+ * jQuery UI Effects Fold @VERSION
+ * http://jqueryui.com
+ *
+ * Copyright 2012 jQuery Foundation and other contributors
+ * Released under the MIT license.
+ * http://jquery.org/license
+ *
+ * http://api.jqueryui.com/fold-effect/
+ *
+ * Depends:
+ * jquery.ui.effect.js
+ */
+(function( $, undefined ) {
+
+$.effects.effect.fold = function( o, done ) {
+
+ // Create element
+ var el = $( this ),
+ props = [ "position", "top", "bottom", "left", "right", "height", "width" ],
+ mode = $.effects.setMode( el, o.mode || "hide" ),
+ show = mode === "show",
+ hide = mode === "hide",
+ size = o.size || 15,
+ percent = /([0-9]+)%/.exec( size ),
+ horizFirst = !!o.horizFirst,
+ widthFirst = show !== horizFirst,
+ ref = widthFirst ? [ "width", "height" ] : [ "height", "width" ],
+ duration = o.duration / 2,
+ wrapper, distance,
+ animation1 = {},
+ animation2 = {};
+
+ $.effects.save( el, props );
+ el.show();
+
+ // Create Wrapper
+ wrapper = $.effects.createWrapper( el ).css({
+ overflow: "hidden"
+ });
+ distance = widthFirst ?
+ [ wrapper.width(), wrapper.height() ] :
+ [ wrapper.height(), wrapper.width() ];
+
+ if ( percent ) {
+ size = parseInt( percent[ 1 ], 10 ) / 100 * distance[ hide ? 0 : 1 ];
+ }
+ if ( show ) {
+ wrapper.css( horizFirst ? {
+ height: 0,
+ width: size
+ } : {
+ height: size,
+ width: 0
+ });
+ }
+
+ // Animation
+ animation1[ ref[ 0 ] ] = show ? distance[ 0 ] : size;
+ animation2[ ref[ 1 ] ] = show ? distance[ 1 ] : 0;
+
+ // Animate
+ wrapper
+ .animate( animation1, duration, o.easing )
+ .animate( animation2, duration, o.easing, function() {
+ if ( hide ) {
+ el.hide();
+ }
+ $.effects.restore( el, props );
+ $.effects.removeWrapper( el );
+ done();
+ });
+
+};
+
+})(jQuery);
diff --git a/js/jquery/src/jquery-ui/jquery.ui.effect-highlight.js b/js/jquery/src/jquery-ui/jquery.ui.effect-highlight.js
new file mode 100644
index 0000000000..99c41de2f3
--- /dev/null
+++ b/js/jquery/src/jquery-ui/jquery.ui.effect-highlight.js
@@ -0,0 +1,50 @@
+/*!
+ * jQuery UI Effects Highlight @VERSION
+ * http://jqueryui.com
+ *
+ * Copyright 2012 jQuery Foundation and other contributors
+ * Released under the MIT license.
+ * http://jquery.org/license
+ *
+ * http://api.jqueryui.com/highlight-effect/
+ *
+ * Depends:
+ * jquery.ui.effect.js
+ */
+(function( $, undefined ) {
+
+$.effects.effect.highlight = function( o, done ) {
+ var elem = $( this ),
+ props = [ "backgroundImage", "backgroundColor", "opacity" ],
+ mode = $.effects.setMode( elem, o.mode || "show" ),
+ animation = {
+ backgroundColor: elem.css( "backgroundColor" )
+ };
+
+ if (mode === "hide") {
+ animation.opacity = 0;
+ }
+
+ $.effects.save( elem, props );
+
+ elem
+ .show()
+ .css({
+ backgroundImage: "none",
+ backgroundColor: o.color || "#ffff99"
+ })
+ .animate( animation, {
+ queue: false,
+ duration: o.duration,
+ easing: o.easing,
+ complete: function() {
+ if ( mode === "hide" ) {
+ elem.hide();
+ }
+ $.effects.restore( elem, props );
+ done();
+ }
+ });
+};
+
+})(jQuery);
diff --git a/js/jquery/src/jquery-ui/jquery.ui.effect-pulsate.js b/js/jquery/src/jquery-ui/jquery.ui.effect-pulsate.js
new file mode 100644
index 0000000000..de78894bef
--- /dev/null
+++ b/js/jquery/src/jquery-ui/jquery.ui.effect-pulsate.js
@@ -0,0 +1,63 @@
+/*!
+ * jQuery UI Effects Pulsate @VERSION
+ * http://jqueryui.com
+ *
+ * Copyright 2012 jQuery Foundation and other contributors
+ * Released under the MIT license.
+ * http://jquery.org/license
+ *
+ * http://api.jqueryui.com/pulsate-effect/
+ *
+ * Depends:
+ * jquery.ui.effect.js
+ */
+(function( $, undefined ) {
+
+$.effects.effect.pulsate = function( o, done ) {
+ var elem = $( this ),
+ mode = $.effects.setMode( elem, o.mode || "show" ),
+ show = mode === "show",
+ hide = mode === "hide",
+ showhide = ( show || mode === "hide" ),
+
+ // showing or hiding leaves of the "last" animation
+ anims = ( ( o.times || 5 ) * 2 ) + ( showhide ? 1 : 0 ),
+ duration = o.duration / anims,
+ animateTo = 0,
+ queue = elem.queue(),
+ queuelen = queue.length,
+ i;
+
+ if ( show || !elem.is(":visible")) {
+ elem.css( "opacity", 0 ).show();
+ animateTo = 1;
+ }
+
+ // anims - 1 opacity "toggles"
+ for ( i = 1; i < anims; i++ ) {
+ elem.animate({
+ opacity: animateTo
+ }, duration, o.easing );
+ animateTo = 1 - animateTo;
+ }
+
+ elem.animate({
+ opacity: animateTo
+ }, duration, o.easing);
+
+ elem.queue(function() {
+ if ( hide ) {
+ elem.hide();
+ }
+ done();
+ });
+
+ // We just queued up "anims" animations, we need to put them next in the queue
+ if ( queuelen > 1 ) {
+ queue.splice.apply( queue,
+ [ 1, 0 ].concat( queue.splice( queuelen, anims + 1 ) ) );
+ }
+ elem.dequeue();
+};
+
+})(jQuery);
diff --git a/js/jquery/src/jquery-ui/jquery.ui.effect-scale.js b/js/jquery/src/jquery-ui/jquery.ui.effect-scale.js
new file mode 100644
index 0000000000..1307e77828
--- /dev/null
+++ b/js/jquery/src/jquery-ui/jquery.ui.effect-scale.js
@@ -0,0 +1,318 @@
+/*!
+ * jQuery UI Effects Scale @VERSION
+ * http://jqueryui.com
+ *
+ * Copyright 2012 jQuery Foundation and other contributors
+ * Released under the MIT license.
+ * http://jquery.org/license
+ *
+ * http://api.jqueryui.com/scale-effect/
+ *
+ * Depends:
+ * jquery.ui.effect.js
+ */
+(function( $, undefined ) {
+
+$.effects.effect.puff = function( o, done ) {
+ var elem = $( this ),
+ mode = $.effects.setMode( elem, o.mode || "hide" ),
+ hide = mode === "hide",
+ percent = parseInt( o.percent, 10 ) || 150,
+ factor = percent / 100,
+ original = {
+ height: elem.height(),
+ width: elem.width(),
+ outerHeight: elem.outerHeight(),
+ outerWidth: elem.outerWidth()
+ };
+
+ $.extend( o, {
+ effect: "scale",
+ queue: false,
+ fade: true,
+ mode: mode,
+ complete: done,
+ percent: hide ? percent : 100,
+ from: hide ?
+ original :
+ {
+ height: original.height * factor,
+ width: original.width * factor,
+ outerHeight: original.outerHeight * factor,
+ outerWidth: original.outerWidth * factor
+ }
+ });
+
+ elem.effect( o );
+};
+
+$.effects.effect.scale = function( o, done ) {
+
+ // Create element
+ var el = $( this ),
+ options = $.extend( true, {}, o ),
+ mode = $.effects.setMode( el, o.mode || "effect" ),
+ percent = parseInt( o.percent, 10 ) ||
+ ( parseInt( o.percent, 10 ) === 0 ? 0 : ( mode === "hide" ? 0 : 100 ) ),
+ direction = o.direction || "both",
+ origin = o.origin,
+ original = {
+ height: el.height(),
+ width: el.width(),
+ outerHeight: el.outerHeight(),
+ outerWidth: el.outerWidth()
+ },
+ factor = {
+ y: direction !== "horizontal" ? (percent / 100) : 1,
+ x: direction !== "vertical" ? (percent / 100) : 1
+ };
+
+ // We are going to pass this effect to the size effect:
+ options.effect = "size";
+ options.queue = false;
+ options.complete = done;
+
+ // Set default origin and restore for show/hide
+ if ( mode !== "effect" ) {
+ options.origin = origin || ["middle","center"];
+ options.restore = true;
+ }
+
+ options.from = o.from || ( mode === "show" ? {
+ height: 0,
+ width: 0,
+ outerHeight: 0,
+ outerWidth: 0
+ } : original );
+ options.to = {
+ height: original.height * factor.y,
+ width: original.width * factor.x,
+ outerHeight: original.outerHeight * factor.y,
+ outerWidth: original.outerWidth * factor.x
+ };
+
+ // Fade option to support puff
+ if ( options.fade ) {
+ if ( mode === "show" ) {
+ options.from.opacity = 0;
+ options.to.opacity = 1;
+ }
+ if ( mode === "hide" ) {
+ options.from.opacity = 1;
+ options.to.opacity = 0;
+ }
+ }
+
+ // Animate
+ el.effect( options );
+
+};
+
+$.effects.effect.size = function( o, done ) {
+
+ // Create element
+ var original, baseline, factor,
+ el = $( this ),
+ props0 = [ "position", "top", "bottom", "left", "right", "width", "height", "overflow", "opacity" ],
+
+ // Always restore
+ props1 = [ "position", "top", "bottom", "left", "right", "overflow", "opacity" ],
+
+ // Copy for children
+ props2 = [ "width", "height", "overflow" ],
+ cProps = [ "fontSize" ],
+ vProps = [ "borderTopWidth", "borderBottomWidth", "paddingTop", "paddingBottom" ],
+ hProps = [ "borderLeftWidth", "borderRightWidth", "paddingLeft", "paddingRight" ],
+
+ // Set options
+ mode = $.effects.setMode( el, o.mode || "effect" ),
+ restore = o.restore || mode !== "effect",
+ scale = o.scale || "both",
+ origin = o.origin || [ "middle", "center" ],
+ position = el.css( "position" ),
+ props = restore ? props0 : props1,
+ zero = {
+ height: 0,
+ width: 0,
+ outerHeight: 0,
+ outerWidth: 0
+ };
+
+ if ( mode === "show" ) {
+ el.show();
+ }
+ original = {
+ height: el.height(),
+ width: el.width(),
+ outerHeight: el.outerHeight(),
+ outerWidth: el.outerWidth()
+ };
+
+ if ( o.mode === "toggle" && mode === "show" ) {
+ el.from = o.to || zero;
+ el.to = o.from || original;
+ } else {
+ el.from = o.from || ( mode === "show" ? zero : original );
+ el.to = o.to || ( mode === "hide" ? zero : original );
+ }
+
+ // Set scaling factor
+ factor = {
+ from: {
+ y: el.from.height / original.height,
+ x: el.from.width / original.width
+ },
+ to: {
+ y: el.to.height / original.height,
+ x: el.to.width / original.width
+ }
+ };
+
+ // Scale the css box
+ if ( scale === "box" || scale === "both" ) {
+
+ // Vertical props scaling
+ if ( factor.from.y !== factor.to.y ) {
+ props = props.concat( vProps );
+ el.from = $.effects.setTransition( el, vProps, factor.from.y, el.from );
+ el.to = $.effects.setTransition( el, vProps, factor.to.y, el.to );
+ }
+
+ // Horizontal props scaling
+ if ( factor.from.x !== factor.to.x ) {
+ props = props.concat( hProps );
+ el.from = $.effects.setTransition( el, hProps, factor.from.x, el.from );
+ el.to = $.effects.setTransition( el, hProps, factor.to.x, el.to );
+ }
+ }
+
+ // Scale the content
+ if ( scale === "content" || scale === "both" ) {
+
+ // Vertical props scaling
+ if ( factor.from.y !== factor.to.y ) {
+ props = props.concat( cProps ).concat( props2 );
+ el.from = $.effects.setTransition( el, cProps, factor.from.y, el.from );
+ el.to = $.effects.setTransition( el, cProps, factor.to.y, el.to );
+ }
+ }
+
+ $.effects.save( el, props );
+ el.show();
+ $.effects.createWrapper( el );
+ el.css( "overflow", "hidden" ).css( el.from );
+
+ // Adjust
+ if (origin) { // Calculate baseline shifts
+ baseline = $.effects.getBaseline( origin, original );
+ el.from.top = ( original.outerHeight - el.outerHeight() ) * baseline.y;
+ el.from.left = ( original.outerWidth - el.outerWidth() ) * baseline.x;
+ el.to.top = ( original.outerHeight - el.to.outerHeight ) * baseline.y;
+ el.to.left = ( original.outerWidth - el.to.outerWidth ) * baseline.x;
+ }
+ el.css( el.from ); // set top & left
+
+ // Animate
+ if ( scale === "content" || scale === "both" ) { // Scale the children
+
+ // Add margins/font-size
+ vProps = vProps.concat([ "marginTop", "marginBottom" ]).concat(cProps);
+ hProps = hProps.concat([ "marginLeft", "marginRight" ]);
+ props2 = props0.concat(vProps).concat(hProps);
+
+ el.find( "*[width]" ).each( function(){
+ var child = $( this ),
+ c_original = {
+ height: child.height(),
+ width: child.width(),
+ outerHeight: child.outerHeight(),
+ outerWidth: child.outerWidth()
+ };
+ if (restore) {
+ $.effects.save(child, props2);
+ }
+
+ child.from = {
+ height: c_original.height * factor.from.y,
+ width: c_original.width * factor.from.x,
+ outerHeight: c_original.outerHeight * factor.from.y,
+ outerWidth: c_original.outerWidth * factor.from.x
+ };
+ child.to = {
+ height: c_original.height * factor.to.y,
+ width: c_original.width * factor.to.x,
+ outerHeight: c_original.height * factor.to.y,
+ outerWidth: c_original.width * factor.to.x
+ };
+
+ // Vertical props scaling
+ if ( factor.from.y !== factor.to.y ) {
+ child.from = $.effects.setTransition( child, vProps, factor.from.y, child.from );
+ child.to = $.effects.setTransition( child, vProps, factor.to.y, child.to );
+ }
+
+ // Horizontal props scaling
+ if ( factor.from.x !== factor.to.x ) {
+ child.from = $.effects.setTransition( child, hProps, factor.from.x, child.from );
+ child.to = $.effects.setTransition( child, hProps, factor.to.x, child.to );
+ }
+
+ // Animate children
+ child.css( child.from );
+ child.animate( child.to, o.duration, o.easing, function() {
+
+ // Restore children
+ if ( restore ) {
+ $.effects.restore( child, props2 );
+ }
+ });
+ });
+ }
+
+ // Animate
+ el.animate( el.to, {
+ queue: false,
+ duration: o.duration,
+ easing: o.easing,
+ complete: function() {
+ if ( el.to.opacity === 0 ) {
+ el.css( "opacity", el.from.opacity );
+ }
+ if( mode === "hide" ) {
+ el.hide();
+ }
+ $.effects.restore( el, props );
+ if ( !restore ) {
+
+ // we need to calculate our new positioning based on the scaling
+ if ( position === "static" ) {
+ el.css({
+ position: "relative",
+ top: el.to.top,
+ left: el.to.left
+ });
+ } else {
+ $.each([ "top", "left" ], function( idx, pos ) {
+ el.css( pos, function( _, str ) {
+ var val = parseInt( str, 10 ),
+ toRef = idx ? el.to.left : el.to.top;
+
+ // if original was "auto", recalculate the new value from wrapper
+ if ( str === "auto" ) {
+ return toRef + "px";
+ }
+
+ return val + toRef + "px";
+ });
+ });
+ }
+ }
+
+ $.effects.removeWrapper( el );
+ done();
+ }
+ });
+
+};
+
+})(jQuery);
diff --git a/js/jquery/src/jquery-ui/jquery.ui.effect-shake.js b/js/jquery/src/jquery-ui/jquery.ui.effect-shake.js
new file mode 100644
index 0000000000..4c2f9817e4
--- /dev/null
+++ b/js/jquery/src/jquery-ui/jquery.ui.effect-shake.js
@@ -0,0 +1,74 @@
+/*!
+ * jQuery UI Effects Shake @VERSION
+ * http://jqueryui.com
+ *
+ * Copyright 2012 jQuery Foundation and other contributors
+ * Released under the MIT license.
+ * http://jquery.org/license
+ *
+ * http://api.jqueryui.com/shake-effect/
+ *
+ * Depends:
+ * jquery.ui.effect.js
+ */
+(function( $, undefined ) {
+
+$.effects.effect.shake = function( o, done ) {
+
+ var el = $( this ),
+ props = [ "position", "top", "bottom", "left", "right", "height", "width" ],
+ mode = $.effects.setMode( el, o.mode || "effect" ),
+ direction = o.direction || "left",
+ distance = o.distance || 20,
+ times = o.times || 3,
+ anims = times * 2 + 1,
+ speed = Math.round(o.duration/anims),
+ ref = (direction === "up" || direction === "down") ? "top" : "left",
+ positiveMotion = (direction === "up" || direction === "left"),
+ animation = {},
+ animation1 = {},
+ animation2 = {},
+ i,
+
+ // we will need to re-assemble the queue to stack our animations in place
+ queue = el.queue(),
+ queuelen = queue.length;
+
+ $.effects.save( el, props );
+ el.show();
+ $.effects.createWrapper( el );
+
+ // Animation
+ animation[ ref ] = ( positiveMotion ? "-=" : "+=" ) + distance;
+ animation1[ ref ] = ( positiveMotion ? "+=" : "-=" ) + distance * 2;
+ animation2[ ref ] = ( positiveMotion ? "-=" : "+=" ) + distance * 2;
+
+ // Animate
+ el.animate( animation, speed, o.easing );
+
+ // Shakes
+ for ( i = 1; i < times; i++ ) {
+ el.animate( animation1, speed, o.easing ).animate( animation2, speed, o.easing );
+ }
+ el
+ .animate( animation1, speed, o.easing )
+ .animate( animation, speed / 2, o.easing )
+ .queue(function() {
+ if ( mode === "hide" ) {
+ el.hide();
+ }
+ $.effects.restore( el, props );
+ $.effects.removeWrapper( el );
+ done();
+ });
+
+ // inject all the animations we just queued to be first in line (after "inprogress")
+ if ( queuelen > 1) {
+ queue.splice.apply( queue,
+ [ 1, 0 ].concat( queue.splice( queuelen, anims + 1 ) ) );
+ }
+ el.dequeue();
+
+};
+
+})(jQuery);
diff --git a/js/jquery/src/jquery-ui/jquery.ui.effect-slide.js b/js/jquery/src/jquery-ui/jquery.ui.effect-slide.js
new file mode 100644
index 0000000000..5037256e0a
--- /dev/null
+++ b/js/jquery/src/jquery-ui/jquery.ui.effect-slide.js
@@ -0,0 +1,64 @@
+/*!
+ * jQuery UI Effects Slide @VERSION
+ * http://jqueryui.com
+ *
+ * Copyright 2012 jQuery Foundation and other contributors
+ * Released under the MIT license.
+ * http://jquery.org/license
+ *
+ * http://api.jqueryui.com/slide-effect/
+ *
+ * Depends:
+ * jquery.ui.effect.js
+ */
+(function( $, undefined ) {
+
+$.effects.effect.slide = function( o, done ) {
+
+ // Create element
+ var el = $( this ),
+ props = [ "position", "top", "bottom", "left", "right", "width", "height" ],
+ mode = $.effects.setMode( el, o.mode || "show" ),
+ show = mode === "show",
+ direction = o.direction || "left",
+ ref = (direction === "up" || direction === "down") ? "top" : "left",
+ positiveMotion = (direction === "up" || direction === "left"),
+ distance,
+ animation = {};
+
+ // Adjust
+ $.effects.save( el, props );
+ el.show();
+ distance = o.distance || el[ ref === "top" ? "outerHeight" : "outerWidth" ]( true );
+
+ $.effects.createWrapper( el ).css({
+ overflow: "hidden"
+ });
+
+ if ( show ) {
+ el.css( ref, positiveMotion ? (isNaN(distance) ? "-" + distance : -distance) : distance );
+ }
+
+ // Animation
+ animation[ ref ] = ( show ?
+ ( positiveMotion ? "+=" : "-=") :
+ ( positiveMotion ? "-=" : "+=")) +
+ distance;
+
+ // Animate
+ el.animate( animation, {
+ queue: false,
+ duration: o.duration,
+ easing: o.easing,
+ complete: function() {
+ if ( mode === "hide" ) {
+ el.hide();
+ }
+ $.effects.restore( el, props );
+ $.effects.removeWrapper( el );
+ done();
+ }
+ });
+};
+
+})(jQuery);
diff --git a/js/jquery/src/jquery-ui/jquery.ui.effect-transfer.js b/js/jquery/src/jquery-ui/jquery.ui.effect-transfer.js
new file mode 100644
index 0000000000..0bfffd7fb8
--- /dev/null
+++ b/js/jquery/src/jquery-ui/jquery.ui.effect-transfer.js
@@ -0,0 +1,47 @@
+/*!
+ * jQuery UI Effects Transfer @VERSION
+ * http://jqueryui.com
+ *
+ * Copyright 2012 jQuery Foundation and other contributors
+ * Released under the MIT license.
+ * http://jquery.org/license
+ *
+ * http://api.jqueryui.com/transfer-effect/
+ *
+ * Depends:
+ * jquery.ui.effect.js
+ */
+(function( $, undefined ) {
+
+$.effects.effect.transfer = function( o, done ) {
+ var elem = $( this ),
+ target = $( o.to ),
+ targetFixed = target.css( "position" ) === "fixed",
+ body = $("body"),
+ fixTop = targetFixed ? body.scrollTop() : 0,
+ fixLeft = targetFixed ? body.scrollLeft() : 0,
+ endPosition = target.offset(),
+ animation = {
+ top: endPosition.top - fixTop ,
+ left: endPosition.left - fixLeft ,
+ height: target.innerHeight(),
+ width: target.innerWidth()
+ },
+ startPosition = elem.offset(),
+ transfer = $( '<div class="ui-effects-transfer"></div>' )
+ .appendTo( document.body )
+ .addClass( o.className )
+ .css({
+ top: startPosition.top - fixTop ,
+ left: startPosition.left - fixLeft ,
+ height: elem.innerHeight(),
+ width: elem.innerWidth(),
+ position: targetFixed ? "fixed" : "absolute"
+ })
+ .animate( animation, o.duration, o.easing, function() {
+ transfer.remove();
+ done();
+ });
+};
+
+})(jQuery);
diff --git a/js/jquery/src/jquery-ui/jquery.ui.effect.js b/js/jquery/src/jquery-ui/jquery.ui.effect.js
new file mode 100644
index 0000000000..37ca48795d
--- /dev/null
+++ b/js/jquery/src/jquery-ui/jquery.ui.effect.js
@@ -0,0 +1,1276 @@
+/*!
+ * jQuery UI Effects @VERSION
+ * http://jqueryui.com
+ *
+ * Copyright 2012 jQuery Foundation and other contributors
+ * Released under the MIT license.
+ * http://jquery.org/license
+ *
+ * http://api.jqueryui.com/category/effects-core/
+ */
+;(jQuery.effects || (function($, undefined) {
+
+var backCompat = $.uiBackCompat !== false,
+ // prefix used for storing data on .data()
+ dataSpace = "ui-effects-";
+
+$.effects = {
+ effect: {}
+};
+
+/*!
+ * jQuery Color Animations v2.0.0
+ * http://jquery.com/
+ *
+ * Copyright 2012 jQuery Foundation and other contributors
+ * Released under the MIT license.
+ * http://jquery.org/license
+ *
+ * Date: Mon Aug 13 13:41:02 2012 -0500
+ */
+(function( jQuery, undefined ) {
+
+ var stepHooks = "backgroundColor borderBottomColor borderLeftColor borderRightColor borderTopColor color columnRuleColor outlineColor textDecorationColor textEmphasisColor".split(" "),
+
+ // plusequals test for += 100 -= 100
+ rplusequals = /^([\-+])=\s*(\d+\.?\d*)/,
+ // a set of RE's that can match strings and generate color tuples.
+ stringParsers = [{
+ re: /rgba?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*(?:,\s*(\d+(?:\.\d+)?)\s*)?\)/,
+ parse: function( execResult ) {
+ return [
+ execResult[ 1 ],
+ execResult[ 2 ],
+ execResult[ 3 ],
+ execResult[ 4 ]
+ ];
+ }
+ }, {
+ re: /rgba?\(\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*(?:,\s*(\d+(?:\.\d+)?)\s*)?\)/,
+ parse: function( execResult ) {
+ return [
+ execResult[ 1 ] * 2.55,
+ execResult[ 2 ] * 2.55,
+ execResult[ 3 ] * 2.55,
+ execResult[ 4 ]
+ ];
+ }
+ }, {
+ // this regex ignores A-F because it's compared against an already lowercased string
+ re: /#([a-f0-9]{2})([a-f0-9]{2})([a-f0-9]{2})/,
+ parse: function( execResult ) {
+ return [
+ parseInt( execResult[ 1 ], 16 ),
+ parseInt( execResult[ 2 ], 16 ),
+ parseInt( execResult[ 3 ], 16 )
+ ];
+ }
+ }, {
+ // this regex ignores A-F because it's compared against an already lowercased string
+ re: /#([a-f0-9])([a-f0-9])([a-f0-9])/,
+ parse: function( execResult ) {
+ return [
+ parseInt( execResult[ 1 ] + execResult[ 1 ], 16 ),
+ parseInt( execResult[ 2 ] + execResult[ 2 ], 16 ),
+ parseInt( execResult[ 3 ] + execResult[ 3 ], 16 )
+ ];
+ }
+ }, {
+ re: /hsla?\(\s*(\d+(?:\.\d+)?)\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*(?:,\s*(\d+(?:\.\d+)?)\s*)?\)/,
+ space: "hsla",
+ parse: function( execResult ) {
+ return [
+ execResult[ 1 ],
+ execResult[ 2 ] / 100,
+ execResult[ 3 ] / 100,
+ execResult[ 4 ]
+ ];
+ }
+ }],
+
+ // jQuery.Color( )
+ color = jQuery.Color = function( color, green, blue, alpha ) {
+ return new jQuery.Color.fn.parse( color, green, blue, alpha );
+ },
+ spaces = {
+ rgba: {
+ props: {
+ red: {
+ idx: 0,
+ type: "byte"
+ },
+ green: {
+ idx: 1,
+ type: "byte"
+ },
+ blue: {
+ idx: 2,
+ type: "byte"
+ }
+ }
+ },
+
+ hsla: {
+ props: {
+ hue: {
+ idx: 0,
+ type: "degrees"
+ },
+ saturation: {
+ idx: 1,
+ type: "percent"
+ },
+ lightness: {
+ idx: 2,
+ type: "percent"
+ }
+ }
+ }
+ },
+ propTypes = {
+ "byte": {
+ floor: true,
+ max: 255
+ },
+ "percent": {
+ max: 1
+ },
+ "degrees": {
+ mod: 360,
+ floor: true
+ }
+ },
+ support = color.support = {},
+
+ // element for support tests
+ supportElem = jQuery( "<p>" )[ 0 ],
+
+ // colors = jQuery.Color.names
+ colors,
+
+ // local aliases of functions called often
+ each = jQuery.each;
+
+// determine rgba support immediately
+supportElem.style.cssText = "background-color:rgba(1,1,1,.5)";
+support.rgba = supportElem.style.backgroundColor.indexOf( "rgba" ) > -1;
+
+// define cache name and alpha properties
+// for rgba and hsla spaces
+each( spaces, function( spaceName, space ) {
+ space.cache = "_" + spaceName;
+ space.props.alpha = {
+ idx: 3,
+ type: "percent",
+ def: 1
+ };
+});
+
+function clamp( value, prop, allowEmpty ) {
+ var type = propTypes[ prop.type ] || {};
+
+ if ( value == null ) {
+ return (allowEmpty || !prop.def) ? null : prop.def;
+ }
+
+ // ~~ is an short way of doing floor for positive numbers
+ value = type.floor ? ~~value : parseFloat( value );
+
+ // IE will pass in empty strings as value for alpha,
+ // which will hit this case
+ if ( isNaN( value ) ) {
+ return prop.def;
+ }
+
+ if ( type.mod ) {
+ // we add mod before modding to make sure that negatives values
+ // get converted properly: -10 -> 350
+ return (value + type.mod) % type.mod;
+ }
+
+ // for now all property types without mod have min and max
+ return 0 > value ? 0 : type.max < value ? type.max : value;
+}
+
+function stringParse( string ) {
+ var inst = color(),
+ rgba = inst._rgba = [];
+
+ string = string.toLowerCase();
+
+ each( stringParsers, function( i, parser ) {
+ var parsed,
+ match = parser.re.exec( string ),
+ values = match && parser.parse( match ),
+ spaceName = parser.space || "rgba";
+
+ if ( values ) {
+ parsed = inst[ spaceName ]( values );
+
+ // if this was an rgba parse the assignment might happen twice
+ // oh well....
+ inst[ spaces[ spaceName ].cache ] = parsed[ spaces[ spaceName ].cache ];
+ rgba = inst._rgba = parsed._rgba;
+
+ // exit each( stringParsers ) here because we matched
+ return false;
+ }
+ });
+
+ // Found a stringParser that handled it
+ if ( rgba.length ) {
+
+ // if this came from a parsed string, force "transparent" when alpha is 0
+ // chrome, (and maybe others) return "transparent" as rgba(0,0,0,0)
+ if ( rgba.join() === "0,0,0,0" ) {
+ jQuery.extend( rgba, colors.transparent );
+ }
+ return inst;
+ }
+
+ // named colors
+ return colors[ string ];
+}
+
+color.fn = jQuery.extend( color.prototype, {
+ parse: function( red, green, blue, alpha ) {
+ if ( red === undefined ) {
+ this._rgba = [ null, null, null, null ];
+ return this;
+ }
+ if ( red.jquery || red.nodeType ) {
+ red = jQuery( red ).css( green );
+ green = undefined;
+ }
+
+ var inst = this,
+ type = jQuery.type( red ),
+ rgba = this._rgba = [];
+
+ // more than 1 argument specified - assume ( red, green, blue, alpha )
+ if ( green !== undefined ) {
+ red = [ red, green, blue, alpha ];
+ type = "array";
+ }
+
+ if ( type === "string" ) {
+ return this.parse( stringParse( red ) || colors._default );
+ }
+
+ if ( type === "array" ) {
+ each( spaces.rgba.props, function( key, prop ) {
+ rgba[ prop.idx ] = clamp( red[ prop.idx ], prop );
+ });
+ return this;
+ }
+
+ if ( type === "object" ) {
+ if ( red instanceof color ) {
+ each( spaces, function( spaceName, space ) {
+ if ( red[ space.cache ] ) {
+ inst[ space.cache ] = red[ space.cache ].slice();
+ }
+ });
+ } else {
+ each( spaces, function( spaceName, space ) {
+ var cache = space.cache;
+ each( space.props, function( key, prop ) {
+
+ // if the cache doesn't exist, and we know how to convert
+ if ( !inst[ cache ] && space.to ) {
+
+ // if the value was null, we don't need to copy it
+ // if the key was alpha, we don't need to copy it either
+ if ( key === "alpha" || red[ key ] == null ) {
+ return;
+ }
+ inst[ cache ] = space.to( inst._rgba );
+ }
+
+ // this is the only case where we allow nulls for ALL properties.
+ // call clamp with alwaysAllowEmpty
+ inst[ cache ][ prop.idx ] = clamp( red[ key ], prop, true );
+ });
+
+ // everything defined but alpha?
+ if ( inst[ cache ] && $.inArray( null, inst[ cache ].slice( 0, 3 ) ) < 0 ) {
+ // use the default of 1
+ inst[ cache ][ 3 ] = 1;
+ if ( space.from ) {
+ inst._rgba = space.from( inst[ cache ] );
+ }
+ }
+ });
+ }
+ return this;
+ }
+ },
+ is: function( compare ) {
+ var is = color( compare ),
+ same = true,
+ inst = this;
+
+ each( spaces, function( _, space ) {
+ var localCache,
+ isCache = is[ space.cache ];
+ if (isCache) {
+ localCache = inst[ space.cache ] || space.to && space.to( inst._rgba ) || [];
+ each( space.props, function( _, prop ) {
+ if ( isCache[ prop.idx ] != null ) {
+ same = ( isCache[ prop.idx ] === localCache[ prop.idx ] );
+ return same;
+ }
+ });
+ }
+ return same;
+ });
+ return same;
+ },
+ _space: function() {
+ var used = [],
+ inst = this;
+ each( spaces, function( spaceName, space ) {
+ if ( inst[ space.cache ] ) {
+ used.push( spaceName );
+ }
+ });
+ return used.pop();
+ },
+ transition: function( other, distance ) {
+ var end = color( other ),
+ spaceName = end._space(),
+ space = spaces[ spaceName ],
+ startColor = this.alpha() === 0 ? color( "transparent" ) : this,
+ start = startColor[ space.cache ] || space.to( startColor._rgba ),
+ result = start.slice();
+
+ end = end[ space.cache ];
+ each( space.props, function( key, prop ) {
+ var index = prop.idx,
+ startValue = start[ index ],
+ endValue = end[ index ],
+ type = propTypes[ prop.type ] || {};
+
+ // if null, don't override start value
+ if ( endValue === null ) {
+ return;
+ }
+ // if null - use end
+ if ( startValue === null ) {
+ result[ index ] = endValue;
+ } else {
+ if ( type.mod ) {
+ if ( endValue - startValue > type.mod / 2 ) {
+ startValue += type.mod;
+ } else if ( startValue - endValue > type.mod / 2 ) {
+ startValue -= type.mod;
+ }
+ }
+ result[ index ] = clamp( ( endValue - startValue ) * distance + startValue, prop );
+ }
+ });
+ return this[ spaceName ]( result );
+ },
+ blend: function( opaque ) {
+ // if we are already opaque - return ourself
+ if ( this._rgba[ 3 ] === 1 ) {
+ return this;
+ }
+
+ var rgb = this._rgba.slice(),
+ a = rgb.pop(),
+ blend = color( opaque )._rgba;
+
+ return color( jQuery.map( rgb, function( v, i ) {
+ return ( 1 - a ) * blend[ i ] + a * v;
+ }));
+ },
+ toRgbaString: function() {
+ var prefix = "rgba(",
+ rgba = jQuery.map( this._rgba, function( v, i ) {
+ return v == null ? ( i > 2 ? 1 : 0 ) : v;
+ });
+
+ if ( rgba[ 3 ] === 1 ) {
+ rgba.pop();
+ prefix = "rgb(";
+ }
+
+ return prefix + rgba.join() + ")";
+ },
+ toHslaString: function() {
+ var prefix = "hsla(",
+ hsla = jQuery.map( this.hsla(), function( v, i ) {
+ if ( v == null ) {
+ v = i > 2 ? 1 : 0;
+ }
+
+ // catch 1 and 2
+ if ( i && i < 3 ) {
+ v = Math.round( v * 100 ) + "%";
+ }
+ return v;
+ });
+
+ if ( hsla[ 3 ] === 1 ) {
+ hsla.pop();
+ prefix = "hsl(";
+ }
+ return prefix + hsla.join() + ")";
+ },
+ toHexString: function( includeAlpha ) {
+ var rgba = this._rgba.slice(),
+ alpha = rgba.pop();
+
+ if ( includeAlpha ) {
+ rgba.push( ~~( alpha * 255 ) );
+ }
+
+ return "#" + jQuery.map( rgba, function( v ) {
+
+ // default to 0 when nulls exist
+ v = ( v || 0 ).toString( 16 );
+ return v.length === 1 ? "0" + v : v;
+ }).join("");
+ },
+ toString: function() {
+ return this._rgba[ 3 ] === 0 ? "transparent" : this.toRgbaString();
+ }
+});
+color.fn.parse.prototype = color.fn;
+
+// hsla conversions adapted from:
+// https://code.google.com/p/maashaack/source/browse/packages/graphics/trunk/src/graphics/colors/HUE2RGB.as?r=5021
+
+function hue2rgb( p, q, h ) {
+ h = ( h + 1 ) % 1;
+ if ( h * 6 < 1 ) {
+ return p + (q - p) * h * 6;
+ }
+ if ( h * 2 < 1) {
+ return q;
+ }
+ if ( h * 3 < 2 ) {
+ return p + (q - p) * ((2/3) - h) * 6;
+ }
+ return p;
+}
+
+spaces.hsla.to = function ( rgba ) {
+ if ( rgba[ 0 ] == null || rgba[ 1 ] == null || rgba[ 2 ] == null ) {
+ return [ null, null, null, rgba[ 3 ] ];
+ }
+ var r = rgba[ 0 ] / 255,
+ g = rgba[ 1 ] / 255,
+ b = rgba[ 2 ] / 255,
+ a = rgba[ 3 ],
+ max = Math.max( r, g, b ),
+ min = Math.min( r, g, b ),
+ diff = max - min,
+ add = max + min,
+ l = add * 0.5,
+ h, s;
+
+ if ( min === max ) {
+ h = 0;
+ } else if ( r === max ) {
+ h = ( 60 * ( g - b ) / diff ) + 360;
+ } else if ( g === max ) {
+ h = ( 60 * ( b - r ) / diff ) + 120;
+ } else {
+ h = ( 60 * ( r - g ) / diff ) + 240;
+ }
+
+ if ( l === 0 || l === 1 ) {
+ s = l;
+ } else if ( l <= 0.5 ) {
+ s = diff / add;
+ } else {
+ s = diff / ( 2 - add );
+ }
+ return [ Math.round(h) % 360, s, l, a == null ? 1 : a ];
+};
+
+spaces.hsla.from = function ( hsla ) {
+ if ( hsla[ 0 ] == null || hsla[ 1 ] == null || hsla[ 2 ] == null ) {
+ return [ null, null, null, hsla[ 3 ] ];
+ }
+ var h = hsla[ 0 ] / 360,
+ s = hsla[ 1 ],
+ l = hsla[ 2 ],
+ a = hsla[ 3 ],
+ q = l <= 0.5 ? l * ( 1 + s ) : l + s - l * s,
+ p = 2 * l - q;
+
+ return [
+ Math.round( hue2rgb( p, q, h + ( 1 / 3 ) ) * 255 ),
+ Math.round( hue2rgb( p, q, h ) * 255 ),
+ Math.round( hue2rgb( p, q, h - ( 1 / 3 ) ) * 255 ),
+ a
+ ];
+};
+
+
+each( spaces, function( spaceName, space ) {
+ var props = space.props,
+ cache = space.cache,
+ to = space.to,
+ from = space.from;
+
+ // makes rgba() and hsla()
+ color.fn[ spaceName ] = function( value ) {
+
+ // generate a cache for this space if it doesn't exist
+ if ( to && !this[ cache ] ) {
+ this[ cache ] = to( this._rgba );
+ }
+ if ( value === undefined ) {
+ return this[ cache ].slice();
+ }
+
+ var ret,
+ type = jQuery.type( value ),
+ arr = ( type === "array" || type === "object" ) ? value : arguments,
+ local = this[ cache ].slice();
+
+ each( props, function( key, prop ) {
+ var val = arr[ type === "object" ? key : prop.idx ];
+ if ( val == null ) {
+ val = local[ prop.idx ];
+ }
+ local[ prop.idx ] = clamp( val, prop );
+ });
+
+ if ( from ) {
+ ret = color( from( local ) );
+ ret[ cache ] = local;
+ return ret;
+ } else {
+ return color( local );
+ }
+ };
+
+ // makes red() green() blue() alpha() hue() saturation() lightness()
+ each( props, function( key, prop ) {
+ // alpha is included in more than one space
+ if ( color.fn[ key ] ) {
+ return;
+ }
+ color.fn[ key ] = function( value ) {
+ var vtype = jQuery.type( value ),
+ fn = ( key === "alpha" ? ( this._hsla ? "hsla" : "rgba" ) : spaceName ),
+ local = this[ fn ](),
+ cur = local[ prop.idx ],
+ match;
+
+ if ( vtype === "undefined" ) {
+ return cur;
+ }
+
+ if ( vtype === "function" ) {
+ value = value.call( this, cur );
+ vtype = jQuery.type( value );
+ }
+ if ( value == null && prop.empty ) {
+ return this;
+ }
+ if ( vtype === "string" ) {
+ match = rplusequals.exec( value );
+ if ( match ) {
+ value = cur + parseFloat( match[ 2 ] ) * ( match[ 1 ] === "+" ? 1 : -1 );
+ }
+ }
+ local[ prop.idx ] = value;
+ return this[ fn ]( local );
+ };
+ });
+});
+
+// add .fx.step functions
+each( stepHooks, function( i, hook ) {
+ jQuery.cssHooks[ hook ] = {
+ set: function( elem, value ) {
+ var parsed, curElem,
+ backgroundColor = "";
+
+ if ( jQuery.type( value ) !== "string" || ( parsed = stringParse( value ) ) ) {
+ value = color( parsed || value );
+ if ( !support.rgba && value._rgba[ 3 ] !== 1 ) {
+ curElem = hook === "backgroundColor" ? elem.parentNode : elem;
+ while (
+ (backgroundColor === "" || backgroundColor === "transparent") &&
+ curElem && curElem.style
+ ) {
+ try {
+ backgroundColor = jQuery.css( curElem, "backgroundColor" );
+ curElem = curElem.parentNode;
+ } catch ( e ) {
+ }
+ }
+
+ value = value.blend( backgroundColor && backgroundColor !== "transparent" ?
+ backgroundColor :
+ "_default" );
+ }
+
+ value = value.toRgbaString();
+ }
+ try {
+ elem.style[ hook ] = value;
+ } catch( error ) {
+ // wrapped to prevent IE from throwing errors on "invalid" values like 'auto' or 'inherit'
+ }
+ }
+ };
+ jQuery.fx.step[ hook ] = function( fx ) {
+ if ( !fx.colorInit ) {
+ fx.start = color( fx.elem, hook );
+ fx.end = color( fx.end );
+ fx.colorInit = true;
+ }
+ jQuery.cssHooks[ hook ].set( fx.elem, fx.start.transition( fx.end, fx.pos ) );
+ };
+});
+
+jQuery.cssHooks.borderColor = {
+ expand: function( value ) {
+ var expanded = {};
+
+ each( [ "Top", "Right", "Bottom", "Left" ], function( i, part ) {
+ expanded[ "border" + part + "Color" ] = value;
+ });
+ return expanded;
+ }
+};
+
+// Basic color names only.
+// Usage of any of the other color names requires adding yourself or including
+// jquery.color.svg-names.js.
+colors = jQuery.Color.names = {
+ // 4.1. Basic color keywords
+ aqua: "#00ffff",
+ black: "#000000",
+ blue: "#0000ff",
+ fuchsia: "#ff00ff",
+ gray: "#808080",
+ green: "#008000",
+ lime: "#00ff00",
+ maroon: "#800000",
+ navy: "#000080",
+ olive: "#808000",
+ purple: "#800080",
+ red: "#ff0000",
+ silver: "#c0c0c0",
+ teal: "#008080",
+ white: "#ffffff",
+ yellow: "#ffff00",
+
+ // 4.2.3. "transparent" color keyword
+ transparent: [ null, null, null, 0 ],
+
+ _default: "#ffffff"
+};
+
+})( jQuery );
+
+
+
+/******************************************************************************/
+/****************************** CLASS ANIMATIONS ******************************/
+/******************************************************************************/
+(function() {
+
+var classAnimationActions = [ "add", "remove", "toggle" ],
+ shorthandStyles = {
+ border: 1,
+ borderBottom: 1,
+ borderColor: 1,
+ borderLeft: 1,
+ borderRight: 1,
+ borderTop: 1,
+ borderWidth: 1,
+ margin: 1,
+ padding: 1
+ };
+
+$.each([ "borderLeftStyle", "borderRightStyle", "borderBottomStyle", "borderTopStyle" ], function( _, prop ) {
+ $.fx.step[ prop ] = function( fx ) {
+ if ( fx.end !== "none" && !fx.setAttr || fx.pos === 1 && !fx.setAttr ) {
+ jQuery.style( fx.elem, prop, fx.end );
+ fx.setAttr = true;
+ }
+ };
+});
+
+function getElementStyles() {
+ var style = this.ownerDocument.defaultView ?
+ this.ownerDocument.defaultView.getComputedStyle( this, null ) :
+ this.currentStyle,
+ newStyle = {},
+ key,
+ len;
+
+ // webkit enumerates style porperties
+ if ( style && style.length && style[ 0 ] && style[ style[ 0 ] ] ) {
+ len = style.length;
+ while ( len-- ) {
+ key = style[ len ];
+ if ( typeof style[ key ] === "string" ) {
+ newStyle[ $.camelCase( key ) ] = style[ key ];
+ }
+ }
+ } else {
+ for ( key in style ) {
+ if ( typeof style[ key ] === "string" ) {
+ newStyle[ key ] = style[ key ];
+ }
+ }
+ }
+
+ return newStyle;
+}
+
+
+function styleDifference( oldStyle, newStyle ) {
+ var diff = {},
+ name, value;
+
+ for ( name in newStyle ) {
+ value = newStyle[ name ];
+ if ( oldStyle[ name ] !== value ) {
+ if ( !shorthandStyles[ name ] ) {
+ if ( $.fx.step[ name ] || !isNaN( parseFloat( value ) ) ) {
+ diff[ name ] = value;
+ }
+ }
+ }
+ }
+
+ return diff;
+}
+
+$.effects.animateClass = function( value, duration, easing, callback ) {
+ var o = $.speed( duration, easing, callback );
+
+ return this.queue( function() {
+ var animated = $( this ),
+ baseClass = animated.attr( "class" ) || "",
+ applyClassChange,
+ allAnimations = o.children ? animated.find( "*" ).andSelf() : animated;
+
+ // map the animated objects to store the original styles.
+ allAnimations = allAnimations.map(function() {
+ var el = $( this );
+ return {
+ el: el,
+ start: getElementStyles.call( this )
+ };
+ });
+
+ // apply class change
+ applyClassChange = function() {
+ $.each( classAnimationActions, function(i, action) {
+ if ( value[ action ] ) {
+ animated[ action + "Class" ]( value[ action ] );
+ }
+ });
+ };
+ applyClassChange();
+
+ // map all animated objects again - calculate new styles and diff
+ allAnimations = allAnimations.map(function() {
+ this.end = getElementStyles.call( this.el[ 0 ] );
+ this.diff = styleDifference( this.start, this.end );
+ return this;
+ });
+
+ // apply original class
+ animated.attr( "class", baseClass );
+
+ // map all animated objects again - this time collecting a promise
+ allAnimations = allAnimations.map(function() {
+ var styleInfo = this,
+ dfd = $.Deferred(),
+ opts = jQuery.extend({}, o, {
+ queue: false,
+ complete: function() {
+ dfd.resolve( styleInfo );
+ }
+ });
+
+ this.el.animate( this.diff, opts );
+ return dfd.promise();
+ });
+
+ // once all animations have completed:
+ $.when.apply( $, allAnimations.get() ).done(function() {
+
+ // set the final class
+ applyClassChange();
+
+ // for each animated element,
+ // clear all css properties that were animated
+ $.each( arguments, function() {
+ var el = this.el;
+ $.each( this.diff, function(key) {
+ el.css( key, '' );
+ });
+ });
+
+ // this is guarnteed to be there if you use jQuery.speed()
+ // it also handles dequeuing the next anim...
+ o.complete.call( animated[ 0 ] );
+ });
+ });
+};
+
+$.fn.extend({
+ _addClass: $.fn.addClass,
+ addClass: function( classNames, speed, easing, callback ) {
+ return speed ?
+ $.effects.animateClass.call( this,
+ { add: classNames }, speed, easing, callback ) :
+ this._addClass( classNames );
+ },
+
+ _removeClass: $.fn.removeClass,
+ removeClass: function( classNames, speed, easing, callback ) {
+ return speed ?
+ $.effects.animateClass.call( this,
+ { remove: classNames }, speed, easing, callback ) :
+ this._removeClass( classNames );
+ },
+
+ _toggleClass: $.fn.toggleClass,
+ toggleClass: function( classNames, force, speed, easing, callback ) {
+ if ( typeof force === "boolean" || force === undefined ) {
+ if ( !speed ) {
+ // without speed parameter
+ return this._toggleClass( classNames, force );
+ } else {
+ return $.effects.animateClass.call( this,
+ (force ? { add: classNames } : { remove: classNames }),
+ speed, easing, callback );
+ }
+ } else {
+ // without force parameter
+ return $.effects.animateClass.call( this,
+ { toggle: classNames }, force, speed, easing );
+ }
+ },
+
+ switchClass: function( remove, add, speed, easing, callback) {
+ return $.effects.animateClass.call( this, {
+ add: add,
+ remove: remove
+ }, speed, easing, callback );
+ }
+});
+
+})();
+
+/******************************************************************************/
+/*********************************** EFFECTS **********************************/
+/******************************************************************************/
+
+(function() {
+
+$.extend( $.effects, {
+ version: "@VERSION",
+
+ // Saves a set of properties in a data storage
+ save: function( element, set ) {
+ for( var i=0; i < set.length; i++ ) {
+ if ( set[ i ] !== null ) {
+ element.data( dataSpace + set[ i ], element[ 0 ].style[ set[ i ] ] );
+ }
+ }
+ },
+
+ // Restores a set of previously saved properties from a data storage
+ restore: function( element, set ) {
+ var val, i;
+ for( i=0; i < set.length; i++ ) {
+ if ( set[ i ] !== null ) {
+ val = element.data( dataSpace + set[ i ] );
+ // support: jQuery 1.6.2
+ // http://bugs.jquery.com/ticket/9917
+ // jQuery 1.6.2 incorrectly returns undefined for any falsy value.
+ // We can't differentiate between "" and 0 here, so we just assume
+ // empty string since it's likely to be a more common value...
+ if ( val === undefined ) {
+ val = "";
+ }
+ element.css( set[ i ], val );
+ }
+ }
+ },
+
+ setMode: function( el, mode ) {
+ if (mode === "toggle") {
+ mode = el.is( ":hidden" ) ? "show" : "hide";
+ }
+ return mode;
+ },
+
+ // Translates a [top,left] array into a baseline value
+ // this should be a little more flexible in the future to handle a string & hash
+ getBaseline: function( origin, original ) {
+ var y, x;
+ switch ( origin[ 0 ] ) {
+ case "top": y = 0; break;
+ case "middle": y = 0.5; break;
+ case "bottom": y = 1; break;
+ default: y = origin[ 0 ] / original.height;
+ }
+ switch ( origin[ 1 ] ) {
+ case "left": x = 0; break;
+ case "center": x = 0.5; break;
+ case "right": x = 1; break;
+ default: x = origin[ 1 ] / original.width;
+ }
+ return {
+ x: x,
+ y: y
+ };
+ },
+
+ // Wraps the element around a wrapper that copies position properties
+ createWrapper: function( element ) {
+
+ // if the element is already wrapped, return it
+ if ( element.parent().is( ".ui-effects-wrapper" )) {
+ return element.parent();
+ }
+
+ // wrap the element
+ var props = {
+ width: element.outerWidth(true),
+ height: element.outerHeight(true),
+ "float": element.css( "float" )
+ },
+ wrapper = $( "<div></div>" )
+ .addClass( "ui-effects-wrapper" )
+ .css({
+ fontSize: "100%",
+ background: "transparent",
+ border: "none",
+ margin: 0,
+ padding: 0
+ }),
+ // Store the size in case width/height are defined in % - Fixes #5245
+ size = {
+ width: element.width(),
+ height: element.height()
+ },
+ active = document.activeElement;
+
+ // support: Firefox
+ // Firefox incorrectly exposes anonymous content
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=561664
+ try {
+ active.id;
+ } catch( e ) {
+ active = document.body;
+ }
+
+ element.wrap( wrapper );
+
+ // Fixes #7595 - Elements lose focus when wrapped.
+ if ( element[ 0 ] === active || $.contains( element[ 0 ], active ) ) {
+ $( active ).focus();
+ }
+
+ wrapper = element.parent(); //Hotfix for jQuery 1.4 since some change in wrap() seems to actually lose the reference to the wrapped element
+
+ // transfer positioning properties to the wrapper
+ if ( element.css( "position" ) === "static" ) {
+ wrapper.css({ position: "relative" });
+ element.css({ position: "relative" });
+ } else {
+ $.extend( props, {
+ position: element.css( "position" ),
+ zIndex: element.css( "z-index" )
+ });
+ $.each([ "top", "left", "bottom", "right" ], function(i, pos) {
+ props[ pos ] = element.css( pos );
+ if ( isNaN( parseInt( props[ pos ], 10 ) ) ) {
+ props[ pos ] = "auto";
+ }
+ });
+ element.css({
+ position: "relative",
+ top: 0,
+ left: 0,
+ right: "auto",
+ bottom: "auto"
+ });
+ }
+ element.css(size);
+
+ return wrapper.css( props ).show();
+ },
+
+ removeWrapper: function( element ) {
+ var active = document.activeElement;
+
+ if ( element.parent().is( ".ui-effects-wrapper" ) ) {
+ element.parent().replaceWith( element );
+
+ // Fixes #7595 - Elements lose focus when wrapped.
+ if ( element[ 0 ] === active || $.contains( element[ 0 ], active ) ) {
+ $( active ).focus();
+ }
+ }
+
+
+ return element;
+ },
+
+ setTransition: function( element, list, factor, value ) {
+ value = value || {};
+ $.each( list, function( i, x ) {
+ var unit = element.cssUnit( x );
+ if ( unit[ 0 ] > 0 ) {
+ value[ x ] = unit[ 0 ] * factor + unit[ 1 ];
+ }
+ });
+ return value;
+ }
+});
+
+// return an effect options object for the given parameters:
+function _normalizeArguments( effect, options, speed, callback ) {
+
+ // allow passing all options as the first parameter
+ if ( $.isPlainObject( effect ) ) {
+ options = effect;
+ effect = effect.effect;
+ }
+
+ // convert to an object
+ effect = { effect: effect };
+
+ // catch (effect, null, ...)
+ if ( options == null ) {
+ options = {};
+ }
+
+ // catch (effect, callback)
+ if ( $.isFunction( options ) ) {
+ callback = options;
+ speed = null;
+ options = {};
+ }
+
+ // catch (effect, speed, ?)
+ if ( typeof options === "number" || $.fx.speeds[ options ] ) {
+ callback = speed;
+ speed = options;
+ options = {};
+ }
+
+ // catch (effect, options, callback)
+ if ( $.isFunction( speed ) ) {
+ callback = speed;
+ speed = null;
+ }
+
+ // add options to effect
+ if ( options ) {
+ $.extend( effect, options );
+ }
+
+ speed = speed || options.duration;
+ effect.duration = $.fx.off ? 0 :
+ typeof speed === "number" ? speed :
+ speed in $.fx.speeds ? $.fx.speeds[ speed ] :
+ $.fx.speeds._default;
+
+ effect.complete = callback || options.complete;
+
+ return effect;
+}
+
+function standardSpeed( speed ) {
+ // valid standard speeds
+ if ( !speed || typeof speed === "number" || $.fx.speeds[ speed ] ) {
+ return true;
+ }
+
+ // invalid strings - treat as "normal" speed
+ if ( typeof speed === "string" && !$.effects.effect[ speed ] ) {
+ // TODO: remove in 2.0 (#7115)
+ if ( backCompat && $.effects[ speed ] ) {
+ return false;
+ }
+ return true;
+ }
+
+ return false;
+}
+
+$.fn.extend({
+ effect: function( /* effect, options, speed, callback */ ) {
+ var args = _normalizeArguments.apply( this, arguments ),
+ mode = args.mode,
+ queue = args.queue,
+ effectMethod = $.effects.effect[ args.effect ],
+
+ // DEPRECATED: remove in 2.0 (#7115)
+ oldEffectMethod = !effectMethod && backCompat && $.effects[ args.effect ];
+
+ if ( $.fx.off || !( effectMethod || oldEffectMethod ) ) {
+ // delegate to the original method (e.g., .show()) if possible
+ if ( mode ) {
+ return this[ mode ]( args.duration, args.complete );
+ } else {
+ return this.each( function() {
+ if ( args.complete ) {
+ args.complete.call( this );
+ }
+ });
+ }
+ }
+
+ function run( next ) {
+ var elem = $( this ),
+ complete = args.complete,
+ mode = args.mode;
+
+ function done() {
+ if ( $.isFunction( complete ) ) {
+ complete.call( elem[0] );
+ }
+ if ( $.isFunction( next ) ) {
+ next();
+ }
+ }
+
+ // if the element is hiddden and mode is hide,
+ // or element is visible and mode is show
+ if ( elem.is( ":hidden" ) ? mode === "hide" : mode === "show" ) {
+ done();
+ } else {
+ effectMethod.call( elem[0], args, done );
+ }
+ }
+
+ // TODO: remove this check in 2.0, effectMethod will always be true
+ if ( effectMethod ) {
+ return queue === false ? this.each( run ) : this.queue( queue || "fx", run );
+ } else {
+ // DEPRECATED: remove in 2.0 (#7115)
+ return oldEffectMethod.call(this, {
+ options: args,
+ duration: args.duration,
+ callback: args.complete,
+ mode: args.mode
+ });
+ }
+ },
+
+ _show: $.fn.show,
+ show: function( speed ) {
+ if ( standardSpeed( speed ) ) {
+ return this._show.apply( this, arguments );
+ } else {
+ var args = _normalizeArguments.apply( this, arguments );
+ args.mode = "show";
+ return this.effect.call( this, args );
+ }
+ },
+
+ _hide: $.fn.hide,
+ hide: function( speed ) {
+ if ( standardSpeed( speed ) ) {
+ return this._hide.apply( this, arguments );
+ } else {
+ var args = _normalizeArguments.apply( this, arguments );
+ args.mode = "hide";
+ return this.effect.call( this, args );
+ }
+ },
+
+ // jQuery core overloads toggle and creates _toggle
+ __toggle: $.fn.toggle,
+ toggle: function( speed ) {
+ if ( standardSpeed( speed ) || typeof speed === "boolean" || $.isFunction( speed ) ) {
+ return this.__toggle.apply( this, arguments );
+ } else {
+ var args = _normalizeArguments.apply( this, arguments );
+ args.mode = "toggle";
+ return this.effect.call( this, args );
+ }
+ },
+
+ // helper functions
+ cssUnit: function(key) {
+ var style = this.css( key ),
+ val = [];
+
+ $.each( [ "em", "px", "%", "pt" ], function( i, unit ) {
+ if ( style.indexOf( unit ) > 0 ) {
+ val = [ parseFloat( style ), unit ];
+ }
+ });
+ return val;
+ }
+});
+
+})();
+
+/******************************************************************************/
+/*********************************** EASING ***********************************/
+/******************************************************************************/
+
+(function() {
+
+// based on easing equations from Robert Penner (http://www.robertpenner.com/easing)
+
+var baseEasings = {};
+
+$.each( [ "Quad", "Cubic", "Quart", "Quint", "Expo" ], function( i, name ) {
+ baseEasings[ name ] = function( p ) {
+ return Math.pow( p, i + 2 );
+ };
+});
+
+$.extend( baseEasings, {
+ Sine: function ( p ) {
+ return 1 - Math.cos( p * Math.PI / 2 );
+ },
+ Circ: function ( p ) {
+ return 1 - Math.sqrt( 1 - p * p );
+ },
+ Elastic: function( p ) {
+ return p === 0 || p === 1 ? p :
+ -Math.pow( 2, 8 * (p - 1) ) * Math.sin( ( (p - 1) * 80 - 7.5 ) * Math.PI / 15 );
+ },
+ Back: function( p ) {
+ return p * p * ( 3 * p - 2 );
+ },
+ Bounce: function ( p ) {
+ var pow2,
+ bounce = 4;
+
+ while ( p < ( ( pow2 = Math.pow( 2, --bounce ) ) - 1 ) / 11 ) {}
+ return 1 / Math.pow( 4, 3 - bounce ) - 7.5625 * Math.pow( ( pow2 * 3 - 2 ) / 22 - p, 2 );
+ }
+});
+
+$.each( baseEasings, function( name, easeIn ) {
+ $.easing[ "easeIn" + name ] = easeIn;
+ $.easing[ "easeOut" + name ] = function( p ) {
+ return 1 - easeIn( 1 - p );
+ };
+ $.easing[ "easeInOut" + name ] = function( p ) {
+ return p < 0.5 ?
+ easeIn( p * 2 ) / 2 :
+ 1 - easeIn( p * -2 + 2 ) / 2;
+ };
+});
+
+})();
+
+})(jQuery));
diff --git a/js/jquery/src/jquery-ui/jquery.ui.menu.js b/js/jquery/src/jquery-ui/jquery.ui.menu.js
new file mode 100644
index 0000000000..45c1ec2e4b
--- /dev/null
+++ b/js/jquery/src/jquery-ui/jquery.ui.menu.js
@@ -0,0 +1,610 @@
+/*!
+ * jQuery UI Menu @VERSION
+ * http://jqueryui.com
+ *
+ * Copyright 2012 jQuery Foundation and other contributors
+ * Released under the MIT license.
+ * http://jquery.org/license
+ *
+ * http://api.jqueryui.com/menu/
+ *
+ * Depends:
+ * jquery.ui.core.js
+ * jquery.ui.widget.js
+ * jquery.ui.position.js
+ */
+(function( $, undefined ) {
+
+var mouseHandled = false;
+
+$.widget( "ui.menu", {
+ version: "@VERSION",
+ defaultElement: "<ul>",
+ delay: 300,
+ options: {
+ icons: {
+ submenu: "ui-icon-carat-1-e"
+ },
+ menus: "ul",
+ position: {
+ my: "left top",
+ at: "right top"
+ },
+ role: "menu",
+
+ // callbacks
+ blur: null,
+ focus: null,
+ select: null
+ },
+
+ _create: function() {
+ this.activeMenu = this.element;
+ this.element
+ .uniqueId()
+ .addClass( "ui-menu ui-widget ui-widget-content ui-corner-all" )
+ .toggleClass( "ui-menu-icons", !!this.element.find( ".ui-icon" ).length )
+ .attr({
+ role: this.options.role,
+ tabIndex: 0
+ })
+ // need to catch all clicks on disabled menu
+ // not possible through _on
+ .bind( "click" + this.eventNamespace, $.proxy(function( event ) {
+ if ( this.options.disabled ) {
+ event.preventDefault();
+ }
+ }, this ));
+
+ if ( this.options.disabled ) {
+ this.element
+ .addClass( "ui-state-disabled" )
+ .attr( "aria-disabled", "true" );
+ }
+
+ this._on({
+ // Prevent focus from sticking to links inside menu after clicking
+ // them (focus should always stay on UL during navigation).
+ "mousedown .ui-menu-item > a": function( event ) {
+ event.preventDefault();
+ },
+ "click .ui-state-disabled > a": function( event ) {
+ event.preventDefault();
+ },
+ "click .ui-menu-item:has(a)": function( event ) {
+ var target = $( event.target ).closest( ".ui-menu-item" );
+ if ( !mouseHandled && target.not( ".ui-state-disabled" ).length ) {
+ mouseHandled = true;
+
+ this.select( event );
+ // Open submenu on click
+ if ( target.has( ".ui-menu" ).length ) {
+ this.expand( event );
+ } else if ( !this.element.is( ":focus" ) ) {
+ // Redirect focus to the menu
+ this.element.trigger( "focus", [ true ] );
+
+ // If the active item is on the top level, let it stay active.
+ // Otherwise, blur the active item since it is no longer visible.
+ if ( this.active && this.active.parents( ".ui-menu" ).length === 1 ) {
+ clearTimeout( this.timer );
+ }
+ }
+ }
+ },
+ "mouseenter .ui-menu-item": function( event ) {
+ var target = $( event.currentTarget );
+ // Remove ui-state-active class from siblings of the newly focused menu item
+ // to avoid a jump caused by adjacent elements both having a class with a border
+ target.siblings().children( ".ui-state-active" ).removeClass( "ui-state-active" );
+ this.focus( event, target );
+ },
+ mouseleave: "collapseAll",
+ "mouseleave .ui-menu": "collapseAll",
+ focus: function( event, keepActiveItem ) {
+ // If there's already an active item, keep it active
+ // If not, activate the first item
+ var item = this.active || this.element.children( ".ui-menu-item" ).eq( 0 );
+
+ if ( !keepActiveItem ) {
+ this.focus( event, item );
+ }
+ },
+ blur: function( event ) {
+ this._delay(function() {
+ if ( !$.contains( this.element[0], this.document[0].activeElement ) ) {
+ this.collapseAll( event );
+ }
+ });
+ },
+ keydown: "_keydown"
+ });
+
+ this.refresh();
+
+ // Clicks outside of a menu collapse any open menus
+ this._on( this.document, {
+ click: function( event ) {
+ if ( !$( event.target ).closest( ".ui-menu" ).length ) {
+ this.collapseAll( event );
+ }
+
+ // Reset the mouseHandled flag
+ mouseHandled = false;
+ }
+ });
+ },
+
+ _destroy: function() {
+ // Destroy (sub)menus
+ this.element
+ .removeAttr( "aria-activedescendant" )
+ .find( ".ui-menu" ).andSelf()
+ .removeClass( "ui-menu ui-widget ui-widget-content ui-corner-all ui-menu-icons" )
+ .removeAttr( "role" )
+ .removeAttr( "tabIndex" )
+ .removeAttr( "aria-labelledby" )
+ .removeAttr( "aria-expanded" )
+ .removeAttr( "aria-hidden" )
+ .removeAttr( "aria-disabled" )
+ .removeUniqueId()
+ .show();
+
+ // Destroy menu items
+ this.element.find( ".ui-menu-item" )
+ .removeClass( "ui-menu-item" )
+ .removeAttr( "role" )
+ .removeAttr( "aria-disabled" )
+ .children( "a" )
+ .removeUniqueId()
+ .removeClass( "ui-corner-all ui-state-hover" )
+ .removeAttr( "tabIndex" )
+ .removeAttr( "role" )
+ .removeAttr( "aria-haspopup" )
+ .children().each( function() {
+ var elem = $( this );
+ if ( elem.data( "ui-menu-submenu-carat" ) ) {
+ elem.remove();
+ }
+ });
+
+ // Destroy menu dividers
+ this.element.find( ".ui-menu-divider" ).removeClass( "ui-menu-divider ui-widget-content" );
+ },
+
+ _keydown: function( event ) {
+ var match, prev, character, skip, regex,
+ preventDefault = true;
+
+ function escape( value ) {
+ return value.replace( /[\-\[\]{}()*+?.,\\\^$|#\s]/g, "\\$&" );
+ }
+
+ switch ( event.keyCode ) {
+ case $.ui.keyCode.PAGE_UP:
+ this.previousPage( event );
+ break;
+ case $.ui.keyCode.PAGE_DOWN:
+ this.nextPage( event );
+ break;
+ case $.ui.keyCode.HOME:
+ this._move( "first", "first", event );
+ break;
+ case $.ui.keyCode.END:
+ this._move( "last", "last", event );
+ break;
+ case $.ui.keyCode.UP:
+ this.previous( event );
+ break;
+ case $.ui.keyCode.DOWN:
+ this.next( event );
+ break;
+ case $.ui.keyCode.LEFT:
+ this.collapse( event );
+ break;
+ case $.ui.keyCode.RIGHT:
+ if ( this.active && !this.active.is( ".ui-state-disabled" ) ) {
+ this.expand( event );
+ }
+ break;
+ case $.ui.keyCode.ENTER:
+ case $.ui.keyCode.SPACE:
+ this._activate( event );
+ break;
+ case $.ui.keyCode.ESCAPE:
+ this.collapse( event );
+ break;
+ default:
+ preventDefault = false;
+ prev = this.previousFilter || "";
+ character = String.fromCharCode( event.keyCode );
+ skip = false;
+
+ clearTimeout( this.filterTimer );
+
+ if ( character === prev ) {
+ skip = true;
+ } else {
+ character = prev + character;
+ }
+
+ regex = new RegExp( "^" + escape( character ), "i" );
+ match = this.activeMenu.children( ".ui-menu-item" ).filter(function() {
+ return regex.test( $( this ).children( "a" ).text() );
+ });
+ match = skip && match.index( this.active.next() ) !== -1 ?
+ this.active.nextAll( ".ui-menu-item" ) :
+ match;
+
+ // If no matches on the current filter, reset to the last character pressed
+ // to move down the menu to the first item that starts with that character
+ if ( !match.length ) {
+ character = String.fromCharCode( event.keyCode );
+ regex = new RegExp( "^" + escape( character ), "i" );
+ match = this.activeMenu.children( ".ui-menu-item" ).filter(function() {
+ return regex.test( $( this ).children( "a" ).text() );
+ });
+ }
+
+ if ( match.length ) {
+ this.focus( event, match );
+ if ( match.length > 1 ) {
+ this.previousFilter = character;
+ this.filterTimer = this._delay(function() {
+ delete this.previousFilter;
+ }, 1000 );
+ } else {
+ delete this.previousFilter;
+ }
+ } else {
+ delete this.previousFilter;
+ }
+ }
+
+ if ( preventDefault ) {
+ event.preventDefault();
+ }
+ },
+
+ _activate: function( event ) {
+ if ( !this.active.is( ".ui-state-disabled" ) ) {
+ if ( this.active.children( "a[aria-haspopup='true']" ).length ) {
+ this.expand( event );
+ } else {
+ this.select( event );
+ }
+ }
+ },
+
+ refresh: function() {
+ var menus,
+ icon = this.options.icons.submenu,
+ submenus = this.element.find( this.options.menus );
+
+ // Initialize nested menus
+ submenus.filter( ":not(.ui-menu)" )
+ .addClass( "ui-menu ui-widget ui-widget-content ui-corner-all" )
+ .hide()
+ .attr({
+ role: this.options.role,
+ "aria-hidden": "true",
+ "aria-expanded": "false"
+ })
+ .each(function() {
+ var menu = $( this ),
+ item = menu.prev( "a" ),
+ submenuCarat = $( "<span>" )
+ .addClass( "ui-menu-icon ui-icon " + icon )
+ .data( "ui-menu-submenu-carat", true );
+
+ item
+ .attr( "aria-haspopup", "true" )
+ .prepend( submenuCarat );
+ menu.attr( "aria-labelledby", item.attr( "id" ) );
+ });
+
+ menus = submenus.add( this.element );
+
+ // Don't refresh list items that are already adapted
+ menus.children( ":not(.ui-menu-item):has(a)" )
+ .addClass( "ui-menu-item" )
+ .attr( "role", "presentation" )
+ .children( "a" )
+ .uniqueId()
+ .addClass( "ui-corner-all" )
+ .attr({
+ tabIndex: -1,
+ role: this._itemRole()
+ });
+
+ // Initialize unlinked menu-items containing spaces and/or dashes only as dividers
+ menus.children( ":not(.ui-menu-item)" ).each(function() {
+ var item = $( this );
+ // hyphen, em dash, en dash
+ if ( !/[^\-—–\s]/.test( item.text() ) ) {
+ item.addClass( "ui-widget-content ui-menu-divider" );
+ }
+ });
+
+ // Add aria-disabled attribute to any disabled menu item
+ menus.children( ".ui-state-disabled" ).attr( "aria-disabled", "true" );
+
+ // If the active item has been removed, blur the menu
+ if ( this.active && !$.contains( this.element[ 0 ], this.active[ 0 ] ) ) {
+ this.blur();
+ }
+ },
+
+ _itemRole: function() {
+ return {
+ menu: "menuitem",
+ listbox: "option"
+ }[ this.options.role ];
+ },
+
+ focus: function( event, item ) {
+ var nested, focused;
+ this.blur( event, event && event.type === "focus" );
+
+ this._scrollIntoView( item );
+
+ this.active = item.first();
+ focused = this.active.children( "a" ).addClass( "ui-state-focus" );
+ // Only update aria-activedescendant if there's a role
+ // otherwise we assume focus is managed elsewhere
+ if ( this.options.role ) {
+ this.element.attr( "aria-activedescendant", focused.attr( "id" ) );
+ }
+
+ // Highlight active parent menu item, if any
+ this.active
+ .parent()
+ .closest( ".ui-menu-item" )
+ .children( "a:first" )
+ .addClass( "ui-state-active" );
+
+ if ( event && event.type === "keydown" ) {
+ this._close();
+ } else {
+ this.timer = this._delay(function() {
+ this._close();
+ }, this.delay );
+ }
+
+ nested = item.children( ".ui-menu" );
+ if ( nested.length && ( /^mouse/.test( event.type ) ) ) {
+ this._startOpening(nested);
+ }
+ this.activeMenu = item.parent();
+
+ this._trigger( "focus", event, { item: item } );
+ },
+
+ _scrollIntoView: function( item ) {
+ var borderTop, paddingTop, offset, scroll, elementHeight, itemHeight;
+ if ( this._hasScroll() ) {
+ borderTop = parseFloat( $.css( this.activeMenu[0], "borderTopWidth" ) ) || 0;
+ paddingTop = parseFloat( $.css( this.activeMenu[0], "paddingTop" ) ) || 0;
+ offset = item.offset().top - this.activeMenu.offset().top - borderTop - paddingTop;
+ scroll = this.activeMenu.scrollTop();
+ elementHeight = this.activeMenu.height();
+ itemHeight = item.height();
+
+ if ( offset < 0 ) {
+ this.activeMenu.scrollTop( scroll + offset );
+ } else if ( offset + itemHeight > elementHeight ) {
+ this.activeMenu.scrollTop( scroll + offset - elementHeight + itemHeight );
+ }
+ }
+ },
+
+ blur: function( event, fromFocus ) {
+ if ( !fromFocus ) {
+ clearTimeout( this.timer );
+ }
+
+ if ( !this.active ) {
+ return;
+ }
+
+ this.active.children( "a" ).removeClass( "ui-state-focus" );
+ this.active = null;
+
+ this._trigger( "blur", event, { item: this.active } );
+ },
+
+ _startOpening: function( submenu ) {
+ clearTimeout( this.timer );
+
+ // Don't open if already open fixes a Firefox bug that caused a .5 pixel
+ // shift in the submenu position when mousing over the carat icon
+ if ( submenu.attr( "aria-hidden" ) !== "true" ) {
+ return;
+ }
+
+ this.timer = this._delay(function() {
+ this._close();
+ this._open( submenu );
+ }, this.delay );
+ },
+
+ _open: function( submenu ) {
+ var position = $.extend({
+ of: this.active
+ }, this.options.position );
+
+ clearTimeout( this.timer );
+ this.element.find( ".ui-menu" ).not( submenu.parents( ".ui-menu" ) )
+ .hide()
+ .attr( "aria-hidden", "true" );
+
+ submenu
+ .show()
+ .removeAttr( "aria-hidden" )
+ .attr( "aria-expanded", "true" )
+ .position( position );
+ },
+
+ collapseAll: function( event, all ) {
+ clearTimeout( this.timer );
+ this.timer = this._delay(function() {
+ // If we were passed an event, look for the submenu that contains the event
+ var currentMenu = all ? this.element :
+ $( event && event.target ).closest( this.element.find( ".ui-menu" ) );
+
+ // If we found no valid submenu ancestor, use the main menu to close all sub menus anyway
+ if ( !currentMenu.length ) {
+ currentMenu = this.element;
+ }
+
+ this._close( currentMenu );
+
+ this.blur( event );
+ this.activeMenu = currentMenu;
+ }, this.delay );
+ },
+
+ // With no arguments, closes the currently active menu - if nothing is active
+ // it closes all menus. If passed an argument, it will search for menus BELOW
+ _close: function( startMenu ) {
+ if ( !startMenu ) {
+ startMenu = this.active ? this.active.parent() : this.element;
+ }
+
+ startMenu
+ .find( ".ui-menu" )
+ .hide()
+ .attr( "aria-hidden", "true" )
+ .attr( "aria-expanded", "false" )
+ .end()
+ .find( "a.ui-state-active" )
+ .removeClass( "ui-state-active" );
+ },
+
+ collapse: function( event ) {
+ var newItem = this.active &&
+ this.active.parent().closest( ".ui-menu-item", this.element );
+ if ( newItem && newItem.length ) {
+ this._close();
+ this.focus( event, newItem );
+ }
+ },
+
+ expand: function( event ) {
+ var newItem = this.active &&
+ this.active
+ .children( ".ui-menu " )
+ .children( ".ui-menu-item" )
+ .first();
+
+ if ( newItem && newItem.length ) {
+ this._open( newItem.parent() );
+
+ // Delay so Firefox will not hide activedescendant change in expanding submenu from AT
+ this._delay(function() {
+ this.focus( event, newItem );
+ });
+ }
+ },
+
+ next: function( event ) {
+ this._move( "next", "first", event );
+ },
+
+ previous: function( event ) {
+ this._move( "prev", "last", event );
+ },
+
+ isFirstItem: function() {
+ return this.active && !this.active.prevAll( ".ui-menu-item" ).length;
+ },
+
+ isLastItem: function() {
+ return this.active && !this.active.nextAll( ".ui-menu-item" ).length;
+ },
+
+ _move: function( direction, filter, event ) {
+ var next;
+ if ( this.active ) {
+ if ( direction === "first" || direction === "last" ) {
+ next = this.active
+ [ direction === "first" ? "prevAll" : "nextAll" ]( ".ui-menu-item" )
+ .eq( -1 );
+ } else {
+ next = this.active
+ [ direction + "All" ]( ".ui-menu-item" )
+ .eq( 0 );
+ }
+ }
+ if ( !next || !next.length || !this.active ) {
+ next = this.activeMenu.children( ".ui-menu-item" )[ filter ]();
+ }
+
+ this.focus( event, next );
+ },
+
+ nextPage: function( event ) {
+ var item, base, height;
+
+ if ( !this.active ) {
+ this.next( event );
+ return;
+ }
+ if ( this.isLastItem() ) {
+ return;
+ }
+ if ( this._hasScroll() ) {
+ base = this.active.offset().top;
+ height = this.element.height();
+ this.active.nextAll( ".ui-menu-item" ).each(function() {
+ item = $( this );
+ return item.offset().top - base - height < 0;
+ });
+
+ this.focus( event, item );
+ } else {
+ this.focus( event, this.activeMenu.children( ".ui-menu-item" )
+ [ !this.active ? "first" : "last" ]() );
+ }
+ },
+
+ previousPage: function( event ) {
+ var item, base, height;
+ if ( !this.active ) {
+ this.next( event );
+ return;
+ }
+ if ( this.isFirstItem() ) {
+ return;
+ }
+ if ( this._hasScroll() ) {
+ base = this.active.offset().top;
+ height = this.element.height();
+ this.active.prevAll( ".ui-menu-item" ).each(function() {
+ item = $( this );
+ return item.offset().top - base + height > 0;
+ });
+
+ this.focus( event, item );
+ } else {
+ this.focus( event, this.activeMenu.children( ".ui-menu-item" ).first() );
+ }
+ },
+
+ _hasScroll: function() {
+ return this.element.outerHeight() < this.element.prop( "scrollHeight" );
+ },
+
+ select: function( event ) {
+ // TODO: It should never be possible to not have an active item at this
+ // point, but the tests don't trigger mouseenter before click.
+ this.active = this.active || $( event.target ).closest( ".ui-menu-item" );
+ var ui = { item: this.active };
+ if ( !this.active.has( ".ui-menu" ).length ) {
+ this.collapseAll( event, true );
+ }
+ this._trigger( "select", event, ui );
+ }
+});
+
+}( jQuery ));
diff --git a/js/jquery/src/jquery-ui/jquery.ui.mouse.js b/js/jquery/src/jquery-ui/jquery.ui.mouse.js
new file mode 100644
index 0000000000..cdc063f91d
--- /dev/null
+++ b/js/jquery/src/jquery-ui/jquery.ui.mouse.js
@@ -0,0 +1,169 @@
+/*!
+ * jQuery UI Mouse @VERSION
+ * http://jqueryui.com
+ *
+ * Copyright 2012 jQuery Foundation and other contributors
+ * Released under the MIT license.
+ * http://jquery.org/license
+ *
+ * http://api.jqueryui.com/mouse/
+ *
+ * Depends:
+ * jquery.ui.widget.js
+ */
+(function( $, undefined ) {
+
+var mouseHandled = false;
+$( document ).mouseup( function( e ) {
+ mouseHandled = false;
+});
+
+$.widget("ui.mouse", {
+ version: "@VERSION",
+ options: {
+ cancel: 'input,textarea,button,select,option',
+ distance: 1,
+ delay: 0
+ },
+ _mouseInit: function() {
+ var that = this;
+
+ this.element
+ .bind('mousedown.'+this.widgetName, function(event) {
+ return that._mouseDown(event);
+ })
+ .bind('click.'+this.widgetName, function(event) {
+ if (true === $.data(event.target, that.widgetName + '.preventClickEvent')) {
+ $.removeData(event.target, that.widgetName + '.preventClickEvent');
+ event.stopImmediatePropagation();
+ return false;
+ }
+ });
+
+ this.started = false;
+ },
+
+ // TODO: make sure destroying one instance of mouse doesn't mess with
+ // other instances of mouse
+ _mouseDestroy: function() {
+ this.element.unbind('.'+this.widgetName);
+ if ( this._mouseMoveDelegate ) {
+ $(document)
+ .unbind('mousemove.'+this.widgetName, this._mouseMoveDelegate)
+ .unbind('mouseup.'+this.widgetName, this._mouseUpDelegate);
+ }
+ },
+
+ _mouseDown: function(event) {
+ // don't let more than one widget handle mouseStart
+ if( mouseHandled ) { return; }
+
+ // we may have missed mouseup (out of window)
+ (this._mouseStarted && this._mouseUp(event));
+
+ this._mouseDownEvent = event;
+
+ var that = this,
+ btnIsLeft = (event.which === 1),
+ // event.target.nodeName works around a bug in IE 8 with
+ // disabled inputs (#7620)
+ elIsCancel = (typeof this.options.cancel === "string" && event.target.nodeName ? $(event.target).closest(this.options.cancel).length : false);
+ if (!btnIsLeft || elIsCancel || !this._mouseCapture(event)) {
+ return true;
+ }
+
+ this.mouseDelayMet = !this.options.delay;
+ if (!this.mouseDelayMet) {
+ this._mouseDelayTimer = setTimeout(function() {
+ that.mouseDelayMet = true;
+ }, this.options.delay);
+ }
+
+ if (this._mouseDistanceMet(event) && this._mouseDelayMet(event)) {
+ this._mouseStarted = (this._mouseStart(event) !== false);
+ if (!this._mouseStarted) {
+ event.preventDefault();
+ return true;
+ }
+ }
+
+ // Click event may never have fired (Gecko & Opera)
+ if (true === $.data(event.target, this.widgetName + '.preventClickEvent')) {
+ $.removeData(event.target, this.widgetName + '.preventClickEvent');
+ }
+
+ // these delegates are required to keep context
+ this._mouseMoveDelegate = function(event) {
+ return that._mouseMove(event);
+ };
+ this._mouseUpDelegate = function(event) {
+ return that._mouseUp(event);
+ };
+ $(document)
+ .bind('mousemove.'+this.widgetName, this._mouseMoveDelegate)
+ .bind('mouseup.'+this.widgetName, this._mouseUpDelegate);
+
+ event.preventDefault();
+
+ mouseHandled = true;
+ return true;
+ },
+
+ _mouseMove: function(event) {
+ // IE mouseup check - mouseup happened when mouse was out of window
+ if ($.ui.ie && !(document.documentMode >= 9) && !event.button) {
+ return this._mouseUp(event);
+ }
+
+ if (this._mouseStarted) {
+ this._mouseDrag(event);
+ return event.preventDefault();
+ }
+
+ if (this._mouseDistanceMet(event) && this._mouseDelayMet(event)) {
+ this._mouseStarted =
+ (this._mouseStart(this._mouseDownEvent, event) !== false);
+ (this._mouseStarted ? this._mouseDrag(event) : this._mouseUp(event));
+ }
+
+ return !this._mouseStarted;
+ },
+
+ _mouseUp: function(event) {
+ $(document)
+ .unbind('mousemove.'+this.widgetName, this._mouseMoveDelegate)
+ .unbind('mouseup.'+this.widgetName, this._mouseUpDelegate);
+
+ if (this._mouseStarted) {
+ this._mouseStarted = false;
+
+ if (event.target === this._mouseDownEvent.target) {
+ $.data(event.target, this.widgetName + '.preventClickEvent', true);
+ }
+
+ this._mouseStop(event);
+ }
+
+ return false;
+ },
+
+ _mouseDistanceMet: function(event) {
+ return (Math.max(
+ Math.abs(this._mouseDownEvent.pageX - event.pageX),
+ Math.abs(this._mouseDownEvent.pageY - event.pageY)
+ ) >= this.options.distance
+ );
+ },
+
+ _mouseDelayMet: function(event) {
+ return this.mouseDelayMet;
+ },
+
+ // These are placeholder methods, to be overriden by extending plugin
+ _mouseStart: function(event) {},
+ _mouseDrag: function(event) {},
+ _mouseStop: function(event) {},
+ _mouseCapture: function(event) { return true; }
+});
+
+})(jQuery);
diff --git a/js/jquery/src/jquery-ui/jquery.ui.position.js b/js/jquery/src/jquery-ui/jquery.ui.position.js
new file mode 100644
index 0000000000..5b595a8c0d
--- /dev/null
+++ b/js/jquery/src/jquery-ui/jquery.ui.position.js
@@ -0,0 +1,517 @@
+/*!
+ * jQuery UI Position @VERSION
+ * http://jqueryui.com
+ *
+ * Copyright 2012 jQuery Foundation and other contributors
+ * Released under the MIT license.
+ * http://jquery.org/license
+ *
+ * http://api.jqueryui.com/position/
+ */
+(function( $, undefined ) {
+
+$.ui = $.ui || {};
+
+var cachedScrollbarWidth,
+ max = Math.max,
+ abs = Math.abs,
+ round = Math.round,
+ rhorizontal = /left|center|right/,
+ rvertical = /top|center|bottom/,
+ roffset = /[\+\-]\d+%?/,
+ rposition = /^\w+/,
+ rpercent = /%$/,
+ _position = $.fn.position;
+
+function getOffsets( offsets, width, height ) {
+ return [
+ parseInt( offsets[ 0 ], 10 ) * ( rpercent.test( offsets[ 0 ] ) ? width / 100 : 1 ),
+ parseInt( offsets[ 1 ], 10 ) * ( rpercent.test( offsets[ 1 ] ) ? height / 100 : 1 )
+ ];
+}
+function parseCss( element, property ) {
+ return parseInt( $.css( element, property ), 10 ) || 0;
+}
+
+$.position = {
+ scrollbarWidth: function() {
+ if ( cachedScrollbarWidth !== undefined ) {
+ return cachedScrollbarWidth;
+ }
+ var w1, w2,
+ div = $( "<div style='display:block;width:50px;height:50px;overflow:hidden;'><div style='height:100px;width:auto;'></div></div>" ),
+ innerDiv = div.children()[0];
+
+ $( "body" ).append( div );
+ w1 = innerDiv.offsetWidth;
+ div.css( "overflow", "scroll" );
+
+ w2 = innerDiv.offsetWidth;
+
+ if ( w1 === w2 ) {
+ w2 = div[0].clientWidth;
+ }
+
+ div.remove();
+
+ return (cachedScrollbarWidth = w1 - w2);
+ },
+ getScrollInfo: function( within ) {
+ var overflowX = within.isWindow ? "" : within.element.css( "overflow-x" ),
+ overflowY = within.isWindow ? "" : within.element.css( "overflow-y" ),
+ hasOverflowX = overflowX === "scroll" ||
+ ( overflowX === "auto" && within.width < within.element[0].scrollWidth ),
+ hasOverflowY = overflowY === "scroll" ||
+ ( overflowY === "auto" && within.height < within.element[0].scrollHeight );
+ return {
+ width: hasOverflowX ? $.position.scrollbarWidth() : 0,
+ height: hasOverflowY ? $.position.scrollbarWidth() : 0
+ };
+ },
+ getWithinInfo: function( element ) {
+ var withinElement = $( element || window ),
+ isWindow = $.isWindow( withinElement[0] );
+ return {
+ element: withinElement,
+ isWindow: isWindow,
+ offset: withinElement.offset() || { left: 0, top: 0 },
+ scrollLeft: withinElement.scrollLeft(),
+ scrollTop: withinElement.scrollTop(),
+ width: isWindow ? withinElement.width() : withinElement.outerWidth(),
+ height: isWindow ? withinElement.height() : withinElement.outerHeight()
+ };
+ }
+};
+
+$.fn.position = function( options ) {
+ if ( !options || !options.of ) {
+ return _position.apply( this, arguments );
+ }
+
+ // make a copy, we don't want to modify arguments
+ options = $.extend( {}, options );
+
+ var atOffset, targetWidth, targetHeight, targetOffset, basePosition,
+ target = $( options.of ),
+ within = $.position.getWithinInfo( options.within ),
+ scrollInfo = $.position.getScrollInfo( within ),
+ targetElem = target[0],
+ collision = ( options.collision || "flip" ).split( " " ),
+ offsets = {};
+
+ if ( targetElem.nodeType === 9 ) {
+ targetWidth = target.width();
+ targetHeight = target.height();
+ targetOffset = { top: 0, left: 0 };
+ } else if ( $.isWindow( targetElem ) ) {
+ targetWidth = target.width();
+ targetHeight = target.height();
+ targetOffset = { top: target.scrollTop(), left: target.scrollLeft() };
+ } else if ( targetElem.preventDefault ) {
+ // force left top to allow flipping
+ options.at = "left top";
+ targetWidth = targetHeight = 0;
+ targetOffset = { top: targetElem.pageY, left: targetElem.pageX };
+ } else {
+ targetWidth = target.outerWidth();
+ targetHeight = target.outerHeight();
+ targetOffset = target.offset();
+ }
+ // clone to reuse original targetOffset later
+ basePosition = $.extend( {}, targetOffset );
+
+ // force my and at to have valid horizontal and vertical positions
+ // if a value is missing or invalid, it will be converted to center
+ $.each( [ "my", "at" ], function() {
+ var pos = ( options[ this ] || "" ).split( " " ),
+ horizontalOffset,
+ verticalOffset;
+
+ if ( pos.length === 1) {
+ pos = rhorizontal.test( pos[ 0 ] ) ?
+ pos.concat( [ "center" ] ) :
+ rvertical.test( pos[ 0 ] ) ?
+ [ "center" ].concat( pos ) :
+ [ "center", "center" ];
+ }
+ pos[ 0 ] = rhorizontal.test( pos[ 0 ] ) ? pos[ 0 ] : "center";
+ pos[ 1 ] = rvertical.test( pos[ 1 ] ) ? pos[ 1 ] : "center";
+
+ // calculate offsets
+ horizontalOffset = roffset.exec( pos[ 0 ] );
+ verticalOffset = roffset.exec( pos[ 1 ] );
+ offsets[ this ] = [
+ horizontalOffset ? horizontalOffset[ 0 ] : 0,
+ verticalOffset ? verticalOffset[ 0 ] : 0
+ ];
+
+ // reduce to just the positions without the offsets
+ options[ this ] = [
+ rposition.exec( pos[ 0 ] )[ 0 ],
+ rposition.exec( pos[ 1 ] )[ 0 ]
+ ];
+ });
+
+ // normalize collision option
+ if ( collision.length === 1 ) {
+ collision[ 1 ] = collision[ 0 ];
+ }
+
+ if ( options.at[ 0 ] === "right" ) {
+ basePosition.left += targetWidth;
+ } else if ( options.at[ 0 ] === "center" ) {
+ basePosition.left += targetWidth / 2;
+ }
+
+ if ( options.at[ 1 ] === "bottom" ) {
+ basePosition.top += targetHeight;
+ } else if ( options.at[ 1 ] === "center" ) {
+ basePosition.top += targetHeight / 2;
+ }
+
+ atOffset = getOffsets( offsets.at, targetWidth, targetHeight );
+ basePosition.left += atOffset[ 0 ];
+ basePosition.top += atOffset[ 1 ];
+
+ return this.each(function() {
+ var collisionPosition, using,
+ elem = $( this ),
+ elemWidth = elem.outerWidth(),
+ elemHeight = elem.outerHeight(),
+ marginLeft = parseCss( this, "marginLeft" ),
+ marginTop = parseCss( this, "marginTop" ),
+ collisionWidth = elemWidth + marginLeft + parseCss( this, "marginRight" ) + scrollInfo.width,
+ collisionHeight = elemHeight + marginTop + parseCss( this, "marginBottom" ) + scrollInfo.height,
+ position = $.extend( {}, basePosition ),
+ myOffset = getOffsets( offsets.my, elem.outerWidth(), elem.outerHeight() );
+
+ if ( options.my[ 0 ] === "right" ) {
+ position.left -= elemWidth;
+ } else if ( options.my[ 0 ] === "center" ) {
+ position.left -= elemWidth / 2;
+ }
+
+ if ( options.my[ 1 ] === "bottom" ) {
+ position.top -= elemHeight;
+ } else if ( options.my[ 1 ] === "center" ) {
+ position.top -= elemHeight / 2;
+ }
+
+ position.left += myOffset[ 0 ];
+ position.top += myOffset[ 1 ];
+
+ // if the browser doesn't support fractions, then round for consistent results
+ if ( !$.support.offsetFractions ) {
+ position.left = round( position.left );
+ position.top = round( position.top );
+ }
+
+ collisionPosition = {
+ marginLeft: marginLeft,
+ marginTop: marginTop
+ };
+
+ $.each( [ "left", "top" ], function( i, dir ) {
+ if ( $.ui.position[ collision[ i ] ] ) {
+ $.ui.position[ collision[ i ] ][ dir ]( position, {
+ targetWidth: targetWidth,
+ targetHeight: targetHeight,
+ elemWidth: elemWidth,
+ elemHeight: elemHeight,
+ collisionPosition: collisionPosition,
+ collisionWidth: collisionWidth,
+ collisionHeight: collisionHeight,
+ offset: [ atOffset[ 0 ] + myOffset[ 0 ], atOffset [ 1 ] + myOffset[ 1 ] ],
+ my: options.my,
+ at: options.at,
+ within: within,
+ elem : elem
+ });
+ }
+ });
+
+ if ( $.fn.bgiframe ) {
+ elem.bgiframe();
+ }
+
+ if ( options.using ) {
+ // adds feedback as second argument to using callback, if present
+ using = function( props ) {
+ var left = targetOffset.left - position.left,
+ right = left + targetWidth - elemWidth,
+ top = targetOffset.top - position.top,
+ bottom = top + targetHeight - elemHeight,
+ feedback = {
+ target: {
+ element: target,
+ left: targetOffset.left,
+ top: targetOffset.top,
+ width: targetWidth,
+ height: targetHeight
+ },
+ element: {
+ element: elem,
+ left: position.left,
+ top: position.top,
+ width: elemWidth,
+ height: elemHeight
+ },
+ horizontal: right < 0 ? "left" : left > 0 ? "right" : "center",
+ vertical: bottom < 0 ? "top" : top > 0 ? "bottom" : "middle"
+ };
+ if ( targetWidth < elemWidth && abs( left + right ) < targetWidth ) {
+ feedback.horizontal = "center";
+ }
+ if ( targetHeight < elemHeight && abs( top + bottom ) < targetHeight ) {
+ feedback.vertical = "middle";
+ }
+ if ( max( abs( left ), abs( right ) ) > max( abs( top ), abs( bottom ) ) ) {
+ feedback.important = "horizontal";
+ } else {
+ feedback.important = "vertical";
+ }
+ options.using.call( this, props, feedback );
+ };
+ }
+
+ elem.offset( $.extend( position, { using: using } ) );
+ });
+};
+
+$.ui.position = {
+ fit: {
+ left: function( position, data ) {
+ var within = data.within,
+ withinOffset = within.isWindow ? within.scrollLeft : within.offset.left,
+ outerWidth = within.width,
+ collisionPosLeft = position.left - data.collisionPosition.marginLeft,
+ overLeft = withinOffset - collisionPosLeft,
+ overRight = collisionPosLeft + data.collisionWidth - outerWidth - withinOffset,
+ newOverRight;
+
+ // element is wider than within
+ if ( data.collisionWidth > outerWidth ) {
+ // element is initially over the left side of within
+ if ( overLeft > 0 && overRight <= 0 ) {
+ newOverRight = position.left + overLeft + data.collisionWidth - outerWidth - withinOffset;
+ position.left += overLeft - newOverRight;
+ // element is initially over right side of within
+ } else if ( overRight > 0 && overLeft <= 0 ) {
+ position.left = withinOffset;
+ // element is initially over both left and right sides of within
+ } else {
+ if ( overLeft > overRight ) {
+ position.left = withinOffset + outerWidth - data.collisionWidth;
+ } else {
+ position.left = withinOffset;
+ }
+ }
+ // too far left -> align with left edge
+ } else if ( overLeft > 0 ) {
+ position.left += overLeft;
+ // too far right -> align with right edge
+ } else if ( overRight > 0 ) {
+ position.left -= overRight;
+ // adjust based on position and margin
+ } else {
+ position.left = max( position.left - collisionPosLeft, position.left );
+ }
+ },
+ top: function( position, data ) {
+ var within = data.within,
+ withinOffset = within.isWindow ? within.scrollTop : within.offset.top,
+ outerHeight = data.within.height,
+ collisionPosTop = position.top - data.collisionPosition.marginTop,
+ overTop = withinOffset - collisionPosTop,
+ overBottom = collisionPosTop + data.collisionHeight - outerHeight - withinOffset,
+ newOverBottom;
+
+ // element is taller than within
+ if ( data.collisionHeight > outerHeight ) {
+ // element is initially over the top of within
+ if ( overTop > 0 && overBottom <= 0 ) {
+ newOverBottom = position.top + overTop + data.collisionHeight - outerHeight - withinOffset;
+ position.top += overTop - newOverBottom;
+ // element is initially over bottom of within
+ } else if ( overBottom > 0 && overTop <= 0 ) {
+ position.top = withinOffset;
+ // element is initially over both top and bottom of within
+ } else {
+ if ( overTop > overBottom ) {
+ position.top = withinOffset + outerHeight - data.collisionHeight;
+ } else {
+ position.top = withinOffset;
+ }
+ }
+ // too far up -> align with top
+ } else if ( overTop > 0 ) {
+ position.top += overTop;
+ // too far down -> align with bottom edge
+ } else if ( overBottom > 0 ) {
+ position.top -= overBottom;
+ // adjust based on position and margin
+ } else {
+ position.top = max( position.top - collisionPosTop, position.top );
+ }
+ }
+ },
+ flip: {
+ left: function( position, data ) {
+ var within = data.within,
+ withinOffset = within.offset.left + within.scrollLeft,
+ outerWidth = within.width,
+ offsetLeft = within.isWindow ? within.scrollLeft : within.offset.left,
+ collisionPosLeft = position.left - data.collisionPosition.marginLeft,
+ overLeft = collisionPosLeft - offsetLeft,
+ overRight = collisionPosLeft + data.collisionWidth - outerWidth - offsetLeft,
+ myOffset = data.my[ 0 ] === "left" ?
+ -data.elemWidth :
+ data.my[ 0 ] === "right" ?
+ data.elemWidth :
+ 0,
+ atOffset = data.at[ 0 ] === "left" ?
+ data.targetWidth :
+ data.at[ 0 ] === "right" ?
+ -data.targetWidth :
+ 0,
+ offset = -2 * data.offset[ 0 ],
+ newOverRight,
+ newOverLeft;
+
+ if ( overLeft < 0 ) {
+ newOverRight = position.left + myOffset + atOffset + offset + data.collisionWidth - outerWidth - withinOffset;
+ if ( newOverRight < 0 || newOverRight < abs( overLeft ) ) {
+ position.left += myOffset + atOffset + offset;
+ }
+ }
+ else if ( overRight > 0 ) {
+ newOverLeft = position.left - data.collisionPosition.marginLeft + myOffset + atOffset + offset - offsetLeft;
+ if ( newOverLeft > 0 || abs( newOverLeft ) < overRight ) {
+ position.left += myOffset + atOffset + offset;
+ }
+ }
+ },
+ top: function( position, data ) {
+ var within = data.within,
+ withinOffset = within.offset.top + within.scrollTop,
+ outerHeight = within.height,
+ offsetTop = within.isWindow ? within.scrollTop : within.offset.top,
+ collisionPosTop = position.top - data.collisionPosition.marginTop,
+ overTop = collisionPosTop - offsetTop,
+ overBottom = collisionPosTop + data.collisionHeight - outerHeight - offsetTop,
+ top = data.my[ 1 ] === "top",
+ myOffset = top ?
+ -data.elemHeight :
+ data.my[ 1 ] === "bottom" ?
+ data.elemHeight :
+ 0,
+ atOffset = data.at[ 1 ] === "top" ?
+ data.targetHeight :
+ data.at[ 1 ] === "bottom" ?
+ -data.targetHeight :
+ 0,
+ offset = -2 * data.offset[ 1 ],
+ newOverTop,
+ newOverBottom;
+ if ( overTop < 0 ) {
+ newOverBottom = position.top + myOffset + atOffset + offset + data.collisionHeight - outerHeight - withinOffset;
+ if ( ( position.top + myOffset + atOffset + offset) > overTop && ( newOverBottom < 0 || newOverBottom < abs( overTop ) ) ) {
+ position.top += myOffset + atOffset + offset;
+ }
+ }
+ else if ( overBottom > 0 ) {
+ newOverTop = position.top - data.collisionPosition.marginTop + myOffset + atOffset + offset - offsetTop;
+ if ( ( position.top + myOffset + atOffset + offset) > overBottom && ( newOverTop > 0 || abs( newOverTop ) < overBottom ) ) {
+ position.top += myOffset + atOffset + offset;
+ }
+ }
+ }
+ },
+ flipfit: {
+ left: function() {
+ $.ui.position.flip.left.apply( this, arguments );
+ $.ui.position.fit.left.apply( this, arguments );
+ },
+ top: function() {
+ $.ui.position.flip.top.apply( this, arguments );
+ $.ui.position.fit.top.apply( this, arguments );
+ }
+ }
+};
+
+// fraction support test
+(function () {
+ var testElement, testElementParent, testElementStyle, offsetLeft, i,
+ body = document.getElementsByTagName( "body" )[ 0 ],
+ div = document.createElement( "div" );
+
+ //Create a "fake body" for testing based on method used in jQuery.support
+ testElement = document.createElement( body ? "div" : "body" );
+ testElementStyle = {
+ visibility: "hidden",
+ width: 0,
+ height: 0,
+ border: 0,
+ margin: 0,
+ background: "none"
+ };
+ if ( body ) {
+ $.extend( testElementStyle, {
+ position: "absolute",
+ left: "-1000px",
+ top: "-1000px"
+ });
+ }
+ for ( i in testElementStyle ) {
+ testElement.style[ i ] = testElementStyle[ i ];
+ }
+ testElement.appendChild( div );
+ testElementParent = body || document.documentElement;
+ testElementParent.insertBefore( testElement, testElementParent.firstChild );
+
+ div.style.cssText = "position: absolute; left: 10.7432222px;";
+
+ offsetLeft = $( div ).offset().left;
+ $.support.offsetFractions = offsetLeft > 10 && offsetLeft < 11;
+
+ testElement.innerHTML = "";
+ testElementParent.removeChild( testElement );
+})();
+
+// DEPRECATED
+if ( $.uiBackCompat !== false ) {
+ // offset option
+ (function( $ ) {
+ var _position = $.fn.position;
+ $.fn.position = function( options ) {
+ if ( !options || !options.offset ) {
+ return _position.call( this, options );
+ }
+ var offset = options.offset.split( " " ),
+ at = options.at.split( " " );
+ if ( offset.length === 1 ) {
+ offset[ 1 ] = offset[ 0 ];
+ }
+ if ( /^\d/.test( offset[ 0 ] ) ) {
+ offset[ 0 ] = "+" + offset[ 0 ];
+ }
+ if ( /^\d/.test( offset[ 1 ] ) ) {
+ offset[ 1 ] = "+" + offset[ 1 ];
+ }
+ if ( at.length === 1 ) {
+ if ( /left|center|right/.test( at[ 0 ] ) ) {
+ at[ 1 ] = "center";
+ } else {
+ at[ 1 ] = at[ 0 ];
+ at[ 0 ] = "center";
+ }
+ }
+ return _position.call( this, $.extend( options, {
+ at: at[ 0 ] + offset[ 0 ] + " " + at[ 1 ] + offset[ 1 ],
+ offset: undefined
+ } ) );
+ };
+ }( jQuery ) );
+}
+
+}( jQuery ) );
diff --git a/js/jquery/src/jquery-ui/jquery.ui.progressbar.js b/js/jquery/src/jquery-ui/jquery.ui.progressbar.js
new file mode 100644
index 0000000000..cb561ebc6b
--- /dev/null
+++ b/js/jquery/src/jquery-ui/jquery.ui.progressbar.js
@@ -0,0 +1,105 @@
+/*!
+ * jQuery UI Progressbar @VERSION
+ * http://jqueryui.com
+ *
+ * Copyright 2012 jQuery Foundation and other contributors
+ * Released under the MIT license.
+ * http://jquery.org/license
+ *
+ * http://api.jqueryui.com/progressbar/
+ *
+ * Depends:
+ * jquery.ui.core.js
+ * jquery.ui.widget.js
+ */
+(function( $, undefined ) {
+
+$.widget( "ui.progressbar", {
+ version: "@VERSION",
+ options: {
+ value: 0,
+ max: 100
+ },
+
+ min: 0,
+
+ _create: function() {
+ this.element
+ .addClass( "ui-progressbar ui-widget ui-widget-content ui-corner-all" )
+ .attr({
+ role: "progressbar",
+ "aria-valuemin": this.min,
+ "aria-valuemax": this.options.max,
+ "aria-valuenow": this._value()
+ });
+
+ this.valueDiv = $( "<div class='ui-progressbar-value ui-widget-header ui-corner-left'></div>" )
+ .appendTo( this.element );
+
+ this.oldValue = this._value();
+ this._refreshValue();
+ },
+
+ _destroy: function() {
+ this.element
+ .removeClass( "ui-progressbar ui-widget ui-widget-content ui-corner-all" )
+ .removeAttr( "role" )
+ .removeAttr( "aria-valuemin" )
+ .removeAttr( "aria-valuemax" )
+ .removeAttr( "aria-valuenow" );
+
+ this.valueDiv.remove();
+ },
+
+ value: function( newValue ) {
+ if ( newValue === undefined ) {
+ return this._value();
+ }
+
+ this._setOption( "value", newValue );
+ return this;
+ },
+
+ _setOption: function( key, value ) {
+ if ( key === "value" ) {
+ this.options.value = value;
+ this._refreshValue();
+ if ( this._value() === this.options.max ) {
+ this._trigger( "complete" );
+ }
+ }
+
+ this._super( key, value );
+ },
+
+ _value: function() {
+ var val = this.options.value;
+ // normalize invalid value
+ if ( typeof val !== "number" ) {
+ val = 0;
+ }
+ return Math.min( this.options.max, Math.max( this.min, val ) );
+ },
+
+ _percentage: function() {
+ return 100 * this._value() / this.options.max;
+ },
+
+ _refreshValue: function() {
+ var value = this.value(),
+ percentage = this._percentage();
+
+ if ( this.oldValue !== value ) {
+ this.oldValue = value;
+ this._trigger( "change" );
+ }
+
+ this.valueDiv
+ .toggle( value > this.min )
+ .toggleClass( "ui-corner-right", value === this.options.max )
+ .width( percentage.toFixed(0) + "%" );
+ this.element.attr( "aria-valuenow", value );
+ }
+});
+
+})( jQuery );
diff --git a/js/jquery/src/jquery-ui/jquery.ui.resizable.js b/js/jquery/src/jquery-ui/jquery.ui.resizable.js
new file mode 100644
index 0000000000..fc4868c376
--- /dev/null
+++ b/js/jquery/src/jquery-ui/jquery.ui.resizable.js
@@ -0,0 +1,801 @@
+/*!
+ * jQuery UI Resizable @VERSION
+ * http://jqueryui.com
+ *
+ * Copyright 2012 jQuery Foundation and other contributors
+ * Released under the MIT license.
+ * http://jquery.org/license
+ *
+ * http://api.jqueryui.com/resizable/
+ *
+ * Depends:
+ * jquery.ui.core.js
+ * jquery.ui.mouse.js
+ * jquery.ui.widget.js
+ */
+(function( $, undefined ) {
+
+$.widget("ui.resizable", $.ui.mouse, {
+ version: "@VERSION",
+ widgetEventPrefix: "resize",
+ options: {
+ alsoResize: false,
+ animate: false,
+ animateDuration: "slow",
+ animateEasing: "swing",
+ aspectRatio: false,
+ autoHide: false,
+ containment: false,
+ ghost: false,
+ grid: false,
+ handles: "e,s,se",
+ helper: false,
+ maxHeight: null,
+ maxWidth: null,
+ minHeight: 10,
+ minWidth: 10,
+ zIndex: 1000
+ },
+ _create: function() {
+
+ var that = this, o = this.options;
+ this.element.addClass("ui-resizable");
+
+ $.extend(this, {
+ _aspectRatio: !!(o.aspectRatio),
+ aspectRatio: o.aspectRatio,
+ originalElement: this.element,
+ _proportionallyResizeElements: [],
+ _helper: o.helper || o.ghost || o.animate ? o.helper || 'ui-resizable-helper' : null
+ });
+
+ //Wrap the element if it cannot hold child nodes
+ if(this.element[0].nodeName.match(/canvas|textarea|input|select|button|img/i)) {
+
+ //Create a wrapper element and set the wrapper to the new current internal element
+ this.element.wrap(
+ $('<div class="ui-wrapper" style="overflow: hidden;"></div>').css({
+ position: this.element.css('position'),
+ width: this.element.outerWidth(),
+ height: this.element.outerHeight(),
+ top: this.element.css('top'),
+ left: this.element.css('left')
+ })
+ );
+
+ //Overwrite the original this.element
+ this.element = this.element.parent().data(
+ "resizable", this.element.data('resizable')
+ );
+
+ this.elementIsWrapper = true;
+
+ //Move margins to the wrapper
+ this.element.css({ marginLeft: this.originalElement.css("marginLeft"), marginTop: this.originalElement.css("marginTop"), marginRight: this.originalElement.css("marginRight"), marginBottom: this.originalElement.css("marginBottom") });
+ this.originalElement.css({ marginLeft: 0, marginTop: 0, marginRight: 0, marginBottom: 0});
+
+ //Prevent Safari textarea resize
+ this.originalResizeStyle = this.originalElement.css('resize');
+ this.originalElement.css('resize', 'none');
+
+ //Push the actual element to our proportionallyResize internal array
+ this._proportionallyResizeElements.push(this.originalElement.css({ position: 'static', zoom: 1, display: 'block' }));
+
+ // avoid IE jump (hard set the margin)
+ this.originalElement.css({ margin: this.originalElement.css('margin') });
+
+ // fix handlers offset
+ this._proportionallyResize();
+
+ }
+
+ this.handles = o.handles || (!$('.ui-resizable-handle', this.element).length ? "e,s,se" : { n: '.ui-resizable-n', e: '.ui-resizable-e', s: '.ui-resizable-s', w: '.ui-resizable-w', se: '.ui-resizable-se', sw: '.ui-resizable-sw', ne: '.ui-resizable-ne', nw: '.ui-resizable-nw' });
+ if(this.handles.constructor == String) {
+
+ if(this.handles == 'all') this.handles = 'n,e,s,w,se,sw,ne,nw';
+ var n = this.handles.split(","); this.handles = {};
+
+ for(var i = 0; i < n.length; i++) {
+
+ var handle = $.trim(n[i]), hname = 'ui-resizable-'+handle;
+ var axis = $('<div class="ui-resizable-handle ' + hname + '"></div>');
+
+ // Apply zIndex to all handles - see #7960
+ axis.css({ zIndex: o.zIndex });
+
+ //TODO : What's going on here?
+ if ('se' == handle) {
+ axis.addClass('ui-icon ui-icon-gripsmall-diagonal-se');
+ };
+
+ //Insert into internal handles object and append to element
+ this.handles[handle] = '.ui-resizable-'+handle;
+ this.element.append(axis);
+ }
+
+ }
+
+ this._renderAxis = function(target) {
+
+ target = target || this.element;
+
+ for(var i in this.handles) {
+
+ if(this.handles[i].constructor == String)
+ this.handles[i] = $(this.handles[i], this.element).show();
+
+ //Apply pad to wrapper element, needed to fix axis position (textarea, inputs, scrolls)
+ if (this.elementIsWrapper && this.originalElement[0].nodeName.match(/textarea|input|select|button/i)) {
+
+ var axis = $(this.handles[i], this.element), padWrapper = 0;
+
+ //Checking the correct pad and border
+ padWrapper = /sw|ne|nw|se|n|s/.test(i) ? axis.outerHeight() : axis.outerWidth();
+
+ //The padding type i have to apply...
+ var padPos = [ 'padding',
+ /ne|nw|n/.test(i) ? 'Top' :
+ /se|sw|s/.test(i) ? 'Bottom' :
+ /^e$/.test(i) ? 'Right' : 'Left' ].join("");
+
+ target.css(padPos, padWrapper);
+
+ this._proportionallyResize();
+
+ }
+
+ //TODO: What's that good for? There's not anything to be executed left
+ if(!$(this.handles[i]).length)
+ continue;
+
+ }
+ };
+
+ //TODO: make renderAxis a prototype function
+ this._renderAxis(this.element);
+
+ this._handles = $('.ui-resizable-handle', this.element)
+ .disableSelection();
+
+ //Matching axis name
+ this._handles.mouseover(function() {
+ if (!that.resizing) {
+ if (this.className)
+ var axis = this.className.match(/ui-resizable-(se|sw|ne|nw|n|e|s|w)/i);
+ //Axis, default = se
+ that.axis = axis && axis[1] ? axis[1] : 'se';
+ }
+ });
+
+ //If we want to auto hide the elements
+ if (o.autoHide) {
+ this._handles.hide();
+ $(this.element)
+ .addClass("ui-resizable-autohide")
+ .mouseenter(function() {
+ if (o.disabled) return;
+ $(this).removeClass("ui-resizable-autohide");
+ that._handles.show();
+ })
+ .mouseleave(function(){
+ if (o.disabled) return;
+ if (!that.resizing) {
+ $(this).addClass("ui-resizable-autohide");
+ that._handles.hide();
+ }
+ });
+ }
+
+ //Initialize the mouse interaction
+ this._mouseInit();
+
+ },
+
+ _destroy: function() {
+
+ this._mouseDestroy();
+
+ var _destroy = function(exp) {
+ $(exp).removeClass("ui-resizable ui-resizable-disabled ui-resizable-resizing")
+ .removeData("resizable").removeData("ui-resizable").unbind(".resizable").find('.ui-resizable-handle').remove();
+ };
+
+ //TODO: Unwrap at same DOM position
+ if (this.elementIsWrapper) {
+ _destroy(this.element);
+ var wrapper = this.element;
+ this.originalElement.css({
+ position: wrapper.css('position'),
+ width: wrapper.outerWidth(),
+ height: wrapper.outerHeight(),
+ top: wrapper.css('top'),
+ left: wrapper.css('left')
+ }).insertAfter( wrapper );
+ wrapper.remove();
+ }
+
+ this.originalElement.css('resize', this.originalResizeStyle);
+ _destroy(this.originalElement);
+
+ return this;
+ },
+
+ _mouseCapture: function(event) {
+ var handle = false;
+ for (var i in this.handles) {
+ if ($(this.handles[i])[0] == event.target) {
+ handle = true;
+ }
+ }
+
+ return !this.options.disabled && handle;
+ },
+
+ _mouseStart: function(event) {
+
+ var o = this.options, iniPos = this.element.position(), el = this.element;
+
+ this.resizing = true;
+ this.documentScroll = { top: $(document).scrollTop(), left: $(document).scrollLeft() };
+
+ // bugfix for http://dev.jquery.com/ticket/1749
+ if (el.is('.ui-draggable') || (/absolute/).test(el.css('position'))) {
+ el.css({ position: 'absolute', top: iniPos.top, left: iniPos.left });
+ }
+
+ this._renderProxy();
+
+ var curleft = num(this.helper.css('left')), curtop = num(this.helper.css('top'));
+
+ if (o.containment) {
+ curleft += $(o.containment).scrollLeft() || 0;
+ curtop += $(o.containment).scrollTop() || 0;
+ }
+
+ //Store needed variables
+ this.offset = this.helper.offset();
+ this.position = { left: curleft, top: curtop };
+ this.size = this._helper ? { width: el.outerWidth(), height: el.outerHeight() } : { width: el.width(), height: el.height() };
+ this.originalSize = this._helper ? { width: el.outerWidth(), height: el.outerHeight() } : { width: el.width(), height: el.height() };
+ this.originalPosition = { left: curleft, top: curtop };
+ this.sizeDiff = { width: el.outerWidth() - el.width(), height: el.outerHeight() - el.height() };
+ this.originalMousePosition = { left: event.pageX, top: event.pageY };
+
+ //Aspect Ratio
+ this.aspectRatio = (typeof o.aspectRatio == 'number') ? o.aspectRatio : ((this.originalSize.width / this.originalSize.height) || 1);
+
+ var cursor = $('.ui-resizable-' + this.axis).css('cursor');
+ $('body').css('cursor', cursor == 'auto' ? this.axis + '-resize' : cursor);
+
+ el.addClass("ui-resizable-resizing");
+ this._propagate("start", event);
+ return true;
+ },
+
+ _mouseDrag: function(event) {
+
+ //Increase performance, avoid regex
+ var el = this.helper, o = this.options, props = {},
+ that = this, smp = this.originalMousePosition, a = this.axis;
+
+ var dx = (event.pageX-smp.left)||0, dy = (event.pageY-smp.top)||0;
+ var trigger = this._change[a];
+ if (!trigger) return false;
+
+ // Calculate the attrs that will be change
+ var data = trigger.apply(this, [event, dx, dy]);
+
+ // Put this in the mouseDrag handler since the user can start pressing shift while resizing
+ this._updateVirtualBoundaries(event.shiftKey);
+ if (this._aspectRatio || event.shiftKey)
+ data = this._updateRatio(data, event);
+
+ data = this._respectSize(data, event);
+
+ // plugins callbacks need to be called first
+ this._propagate("resize", event);
+
+ el.css({
+ top: this.position.top + "px", left: this.position.left + "px",
+ width: this.size.width + "px", height: this.size.height + "px"
+ });
+
+ if (!this._helper && this._proportionallyResizeElements.length)
+ this._proportionallyResize();
+
+ this._updateCache(data);
+
+ // calling the user callback at the end
+ this._trigger('resize', event, this.ui());
+
+ return false;
+ },
+
+ _mouseStop: function(event) {
+
+ this.resizing = false;
+ var o = this.options, that = this;
+
+ if(this._helper) {
+ var pr = this._proportionallyResizeElements, ista = pr.length && (/textarea/i).test(pr[0].nodeName),
+ soffseth = ista && $.ui.hasScroll(pr[0], 'left') /* TODO - jump height */ ? 0 : that.sizeDiff.height,
+ soffsetw = ista ? 0 : that.sizeDiff.width;
+
+ var s = { width: (that.helper.width() - soffsetw), height: (that.helper.height() - soffseth) },
+ left = (parseInt(that.element.css('left'), 10) + (that.position.left - that.originalPosition.left)) || null,
+ top = (parseInt(that.element.css('top'), 10) + (that.position.top - that.originalPosition.top)) || null;
+
+ if (!o.animate)
+ this.element.css($.extend(s, { top: top, left: left }));
+
+ that.helper.height(that.size.height);
+ that.helper.width(that.size.width);
+
+ if (this._helper && !o.animate) this._proportionallyResize();
+ }
+
+ $('body').css('cursor', 'auto');
+
+ this.element.removeClass("ui-resizable-resizing");
+
+ this._propagate("stop", event);
+
+ if (this._helper) this.helper.remove();
+ return false;
+
+ },
+
+ _updateVirtualBoundaries: function(forceAspectRatio) {
+ var o = this.options, pMinWidth, pMaxWidth, pMinHeight, pMaxHeight, b;
+
+ b = {
+ minWidth: isNumber(o.minWidth) ? o.minWidth : 0,
+ maxWidth: isNumber(o.maxWidth) ? o.maxWidth : Infinity,
+ minHeight: isNumber(o.minHeight) ? o.minHeight : 0,
+ maxHeight: isNumber(o.maxHeight) ? o.maxHeight : Infinity
+ };
+
+ if(this._aspectRatio || forceAspectRatio) {
+ // We want to create an enclosing box whose aspect ration is the requested one
+ // First, compute the "projected" size for each dimension based on the aspect ratio and other dimension
+ pMinWidth = b.minHeight * this.aspectRatio;
+ pMinHeight = b.minWidth / this.aspectRatio;
+ pMaxWidth = b.maxHeight * this.aspectRatio;
+ pMaxHeight = b.maxWidth / this.aspectRatio;
+
+ if(pMinWidth > b.minWidth) b.minWidth = pMinWidth;
+ if(pMinHeight > b.minHeight) b.minHeight = pMinHeight;
+ if(pMaxWidth < b.maxWidth) b.maxWidth = pMaxWidth;
+ if(pMaxHeight < b.maxHeight) b.maxHeight = pMaxHeight;
+ }
+ this._vBoundaries = b;
+ },
+
+ _updateCache: function(data) {
+ var o = this.options;
+ this.offset = this.helper.offset();
+ if (isNumber(data.left)) this.position.left = data.left;
+ if (isNumber(data.top)) this.position.top = data.top;
+ if (isNumber(data.height)) this.size.height = data.height;
+ if (isNumber(data.width)) this.size.width = data.width;
+ },
+
+ _updateRatio: function(data, event) {
+
+ var o = this.options, cpos = this.position, csize = this.size, a = this.axis;
+
+ if (isNumber(data.height)) data.width = (data.height * this.aspectRatio);
+ else if (isNumber(data.width)) data.height = (data.width / this.aspectRatio);
+
+ if (a == 'sw') {
+ data.left = cpos.left + (csize.width - data.width);
+ data.top = null;
+ }
+ if (a == 'nw') {
+ data.top = cpos.top + (csize.height - data.height);
+ data.left = cpos.left + (csize.width - data.width);
+ }
+
+ return data;
+ },
+
+ _respectSize: function(data, event) {
+
+ var el = this.helper, o = this._vBoundaries, pRatio = this._aspectRatio || event.shiftKey, a = this.axis,
+ ismaxw = isNumber(data.width) && o.maxWidth && (o.maxWidth < data.width), ismaxh = isNumber(data.height) && o.maxHeight && (o.maxHeight < data.height),
+ isminw = isNumber(data.width) && o.minWidth && (o.minWidth > data.width), isminh = isNumber(data.height) && o.minHeight && (o.minHeight > data.height);
+
+ if (isminw) data.width = o.minWidth;
+ if (isminh) data.height = o.minHeight;
+ if (ismaxw) data.width = o.maxWidth;
+ if (ismaxh) data.height = o.maxHeight;
+
+ var dw = this.originalPosition.left + this.originalSize.width, dh = this.position.top + this.size.height;
+ var cw = /sw|nw|w/.test(a), ch = /nw|ne|n/.test(a);
+
+ if (isminw && cw) data.left = dw - o.minWidth;
+ if (ismaxw && cw) data.left = dw - o.maxWidth;
+ if (isminh && ch) data.top = dh - o.minHeight;
+ if (ismaxh && ch) data.top = dh - o.maxHeight;
+
+ // fixing jump error on top/left - bug #2330
+ var isNotwh = !data.width && !data.height;
+ if (isNotwh && !data.left && data.top) data.top = null;
+ else if (isNotwh && !data.top && data.left) data.left = null;
+
+ return data;
+ },
+
+ _proportionallyResize: function() {
+
+ var o = this.options;
+ if (!this._proportionallyResizeElements.length) return;
+ var element = this.helper || this.element;
+
+ for (var i=0; i < this._proportionallyResizeElements.length; i++) {
+
+ var prel = this._proportionallyResizeElements[i];
+
+ if (!this.borderDif) {
+ var b = [prel.css('borderTopWidth'), prel.css('borderRightWidth'), prel.css('borderBottomWidth'), prel.css('borderLeftWidth')],
+ p = [prel.css('paddingTop'), prel.css('paddingRight'), prel.css('paddingBottom'), prel.css('paddingLeft')];
+
+ this.borderDif = $.map(b, function(v, i) {
+ var border = parseInt(v,10)||0, padding = parseInt(p[i],10)||0;
+ return border + padding;
+ });
+ }
+
+ prel.css({
+ height: (element.height() - this.borderDif[0] - this.borderDif[2]) || 0,
+ width: (element.width() - this.borderDif[1] - this.borderDif[3]) || 0
+ });
+
+ };
+
+ },
+
+ _renderProxy: function() {
+
+ var el = this.element, o = this.options;
+ this.elementOffset = el.offset();
+
+ if(this._helper) {
+
+ this.helper = this.helper || $('<div style="overflow:hidden;"></div>');
+
+ // fix ie6 offset TODO: This seems broken
+ var ie6offset = ($.ui.ie6 ? 1 : 0),
+ pxyoffset = ( $.ui.ie6 ? 2 : -1 );
+
+ this.helper.addClass(this._helper).css({
+ width: this.element.outerWidth() + pxyoffset,
+ height: this.element.outerHeight() + pxyoffset,
+ position: 'absolute',
+ left: this.elementOffset.left - ie6offset +'px',
+ top: this.elementOffset.top - ie6offset +'px',
+ zIndex: ++o.zIndex //TODO: Don't modify option
+ });
+
+ this.helper
+ .appendTo("body")
+ .disableSelection();
+
+ } else {
+ this.helper = this.element;
+ }
+
+ },
+
+ _change: {
+ e: function(event, dx, dy) {
+ return { width: this.originalSize.width + dx };
+ },
+ w: function(event, dx, dy) {
+ var o = this.options, cs = this.originalSize, sp = this.originalPosition;
+ return { left: sp.left + dx, width: cs.width - dx };
+ },
+ n: function(event, dx, dy) {
+ var o = this.options, cs = this.originalSize, sp = this.originalPosition;
+ return { top: sp.top + dy, height: cs.height - dy };
+ },
+ s: function(event, dx, dy) {
+ return { height: this.originalSize.height + dy };
+ },
+ se: function(event, dx, dy) {
+ return $.extend(this._change.s.apply(this, arguments), this._change.e.apply(this, [event, dx, dy]));
+ },
+ sw: function(event, dx, dy) {
+ return $.extend(this._change.s.apply(this, arguments), this._change.w.apply(this, [event, dx, dy]));
+ },
+ ne: function(event, dx, dy) {
+ return $.extend(this._change.n.apply(this, arguments), this._change.e.apply(this, [event, dx, dy]));
+ },
+ nw: function(event, dx, dy) {
+ return $.extend(this._change.n.apply(this, arguments), this._change.w.apply(this, [event, dx, dy]));
+ }
+ },
+
+ _propagate: function(n, event) {
+ $.ui.plugin.call(this, n, [event, this.ui()]);
+ (n != "resize" && this._trigger(n, event, this.ui()));
+ },
+
+ plugins: {},
+
+ ui: function() {
+ return {
+ originalElement: this.originalElement,
+ element: this.element,
+ helper: this.helper,
+ position: this.position,
+ size: this.size,
+ originalSize: this.originalSize,
+ originalPosition: this.originalPosition
+ };
+ }
+
+});
+
+/*
+ * Resizable Extensions
+ */
+
+$.ui.plugin.add("resizable", "alsoResize", {
+
+ start: function (event, ui) {
+ var that = $(this).data("resizable"), o = that.options;
+
+ var _store = function (exp) {
+ $(exp).each(function() {
+ var el = $(this);
+ el.data("resizable-alsoresize", {
+ width: parseInt(el.width(), 10), height: parseInt(el.height(), 10),
+ left: parseInt(el.css('left'), 10), top: parseInt(el.css('top'), 10)
+ });
+ });
+ };
+
+ if (typeof(o.alsoResize) == 'object' && !o.alsoResize.parentNode) {
+ if (o.alsoResize.length) { o.alsoResize = o.alsoResize[0]; _store(o.alsoResize); }
+ else { $.each(o.alsoResize, function (exp) { _store(exp); }); }
+ }else{
+ _store(o.alsoResize);
+ }
+ },
+
+ resize: function (event, ui) {
+ var that = $(this).data("resizable"), o = that.options, os = that.originalSize, op = that.originalPosition;
+
+ var delta = {
+ height: (that.size.height - os.height) || 0, width: (that.size.width - os.width) || 0,
+ top: (that.position.top - op.top) || 0, left: (that.position.left - op.left) || 0
+ },
+
+ _alsoResize = function (exp, c) {
+ $(exp).each(function() {
+ var el = $(this), start = $(this).data("resizable-alsoresize"), style = {},
+ css = c && c.length ? c : el.parents(ui.originalElement[0]).length ? ['width', 'height'] : ['width', 'height', 'top', 'left'];
+
+ $.each(css, function (i, prop) {
+ var sum = (start[prop]||0) + (delta[prop]||0);
+ if (sum && sum >= 0)
+ style[prop] = sum || null;
+ });
+
+ el.css(style);
+ });
+ };
+
+ if (typeof(o.alsoResize) == 'object' && !o.alsoResize.nodeType) {
+ $.each(o.alsoResize, function (exp, c) { _alsoResize(exp, c); });
+ }else{
+ _alsoResize(o.alsoResize);
+ }
+ },
+
+ stop: function (event, ui) {
+ $(this).removeData("resizable-alsoresize");
+ }
+});
+
+$.ui.plugin.add("resizable", "animate", {
+
+ stop: function(event, ui) {
+ var that = $(this).data("resizable"), o = that.options;
+
+ var pr = that._proportionallyResizeElements, ista = pr.length && (/textarea/i).test(pr[0].nodeName),
+ soffseth = ista && $.ui.hasScroll(pr[0], 'left') /* TODO - jump height */ ? 0 : that.sizeDiff.height,
+ soffsetw = ista ? 0 : that.sizeDiff.width;
+
+ var style = { width: (that.size.width - soffsetw), height: (that.size.height - soffseth) },
+ left = (parseInt(that.element.css('left'), 10) + (that.position.left - that.originalPosition.left)) || null,
+ top = (parseInt(that.element.css('top'), 10) + (that.position.top - that.originalPosition.top)) || null;
+
+ that.element.animate(
+ $.extend(style, top && left ? { top: top, left: left } : {}), {
+ duration: o.animateDuration,
+ easing: o.animateEasing,
+ step: function() {
+
+ var data = {
+ width: parseInt(that.element.css('width'), 10),
+ height: parseInt(that.element.css('height'), 10),
+ top: parseInt(that.element.css('top'), 10),
+ left: parseInt(that.element.css('left'), 10)
+ };
+
+ if (pr && pr.length) $(pr[0]).css({ width: data.width, height: data.height });
+
+ // propagating resize, and updating values for each animation step
+ that._updateCache(data);
+ that._propagate("resize", event);
+
+ }
+ }
+ );
+ }
+
+});
+
+$.ui.plugin.add("resizable", "containment", {
+
+ start: function(event, ui) {
+ var that = $(this).data("resizable"), o = that.options, el = that.element;
+ var oc = o.containment, ce = (oc instanceof $) ? oc.get(0) : (/parent/.test(oc)) ? el.parent().get(0) : oc;
+ if (!ce) return;
+
+ that.containerElement = $(ce);
+
+ if (/document/.test(oc) || oc == document) {
+ that.containerOffset = { left: 0, top: 0 };
+ that.containerPosition = { left: 0, top: 0 };
+
+ that.parentData = {
+ element: $(document), left: 0, top: 0,
+ width: $(document).width(), height: $(document).height() || document.body.parentNode.scrollHeight
+ };
+ }
+
+ // i'm a node, so compute top, left, right, bottom
+ else {
+ var element = $(ce), p = [];
+ $([ "Top", "Right", "Left", "Bottom" ]).each(function(i, name) { p[i] = num(element.css("padding" + name)); });
+
+ that.containerOffset = element.offset();
+ that.containerPosition = element.position();
+ that.containerSize = { height: (element.innerHeight() - p[3]), width: (element.innerWidth() - p[1]) };
+
+ var co = that.containerOffset, ch = that.containerSize.height, cw = that.containerSize.width,
+ width = ($.ui.hasScroll(ce, "left") ? ce.scrollWidth : cw ), height = ($.ui.hasScroll(ce) ? ce.scrollHeight : ch);
+
+ that.parentData = {
+ element: ce, left: co.left, top: co.top, width: width, height: height
+ };
+ }
+ },
+
+ resize: function(event, ui) {
+ var that = $(this).data("resizable"), o = that.options,
+ ps = that.containerSize, co = that.containerOffset, cs = that.size, cp = that.position,
+ pRatio = that._aspectRatio || event.shiftKey, cop = { top:0, left:0 }, ce = that.containerElement;
+
+ if (ce[0] != document && (/static/).test(ce.css('position'))) cop = co;
+
+ if (cp.left < (that._helper ? co.left : 0)) {
+ that.size.width = that.size.width + (that._helper ? (that.position.left - co.left) : (that.position.left - cop.left));
+ if (pRatio) that.size.height = that.size.width / that.aspectRatio;
+ that.position.left = o.helper ? co.left : 0;
+ }
+
+ if (cp.top < (that._helper ? co.top : 0)) {
+ that.size.height = that.size.height + (that._helper ? (that.position.top - co.top) : that.position.top);
+ if (pRatio) that.size.width = that.size.height * that.aspectRatio;
+ that.position.top = that._helper ? co.top : 0;
+ }
+
+ that.offset.left = that.parentData.left+that.position.left;
+ that.offset.top = that.parentData.top+that.position.top;
+
+ var woset = Math.abs( (that._helper ? that.offset.left - cop.left : (that.offset.left - cop.left)) + that.sizeDiff.width ),
+ hoset = Math.abs( (that._helper ? that.offset.top - cop.top : (that.offset.top - co.top)) + that.sizeDiff.height );
+
+ var isParent = that.containerElement.get(0) == that.element.parent().get(0),
+ isOffsetRelative = /relative|absolute/.test(that.containerElement.css('position'));
+
+ if(isParent && isOffsetRelative) woset -= that.parentData.left;
+
+ if (woset + that.size.width >= that.parentData.width) {
+ that.size.width = that.parentData.width - woset;
+ if (pRatio) that.size.height = that.size.width / that.aspectRatio;
+ }
+
+ if (hoset + that.size.height >= that.parentData.height) {
+ that.size.height = that.parentData.height - hoset;
+ if (pRatio) that.size.width = that.size.height * that.aspectRatio;
+ }
+ },
+
+ stop: function(event, ui){
+ var that = $(this).data("resizable"), o = that.options, cp = that.position,
+ co = that.containerOffset, cop = that.containerPosition, ce = that.containerElement;
+
+ var helper = $(that.helper), ho = helper.offset(), w = helper.outerWidth() - that.sizeDiff.width, h = helper.outerHeight() - that.sizeDiff.height;
+
+ if (that._helper && !o.animate && (/relative/).test(ce.css('position')))
+ $(this).css({ left: ho.left - cop.left - co.left, width: w, height: h });
+
+ if (that._helper && !o.animate && (/static/).test(ce.css('position')))
+ $(this).css({ left: ho.left - cop.left - co.left, width: w, height: h });
+
+ }
+});
+
+$.ui.plugin.add("resizable", "ghost", {
+
+ start: function(event, ui) {
+
+ var that = $(this).data("resizable"), o = that.options, cs = that.size;
+
+ that.ghost = that.originalElement.clone();
+ that.ghost
+ .css({ opacity: .25, display: 'block', position: 'relative', height: cs.height, width: cs.width, margin: 0, left: 0, top: 0 })
+ .addClass('ui-resizable-ghost')
+ .addClass(typeof o.ghost == 'string' ? o.ghost : '');
+
+ that.ghost.appendTo(that.helper);
+
+ },
+
+ resize: function(event, ui){
+ var that = $(this).data("resizable"), o = that.options;
+ if (that.ghost) that.ghost.css({ position: 'relative', height: that.size.height, width: that.size.width });
+ },
+
+ stop: function(event, ui){
+ var that = $(this).data("resizable"), o = that.options;
+ if (that.ghost && that.helper) that.helper.get(0).removeChild(that.ghost.get(0));
+ }
+
+});
+
+$.ui.plugin.add("resizable", "grid", {
+
+ resize: function(event, ui) {
+ var that = $(this).data("resizable"), o = that.options, cs = that.size, os = that.originalSize, op = that.originalPosition, a = that.axis, ratio = o._aspectRatio || event.shiftKey;
+ o.grid = typeof o.grid == "number" ? [o.grid, o.grid] : o.grid;
+ var ox = Math.round((cs.width - os.width) / (o.grid[0]||1)) * (o.grid[0]||1), oy = Math.round((cs.height - os.height) / (o.grid[1]||1)) * (o.grid[1]||1);
+
+ if (/^(se|s|e)$/.test(a)) {
+ that.size.width = os.width + ox;
+ that.size.height = os.height + oy;
+ }
+ else if (/^(ne)$/.test(a)) {
+ that.size.width = os.width + ox;
+ that.size.height = os.height + oy;
+ that.position.top = op.top - oy;
+ }
+ else if (/^(sw)$/.test(a)) {
+ that.size.width = os.width + ox;
+ that.size.height = os.height + oy;
+ that.position.left = op.left - ox;
+ }
+ else {
+ that.size.width = os.width + ox;
+ that.size.height = os.height + oy;
+ that.position.top = op.top - oy;
+ that.position.left = op.left - ox;
+ }
+ }
+
+});
+
+var num = function(v) {
+ return parseInt(v, 10) || 0;
+};
+
+var isNumber = function(value) {
+ return !isNaN(parseInt(value, 10));
+};
+
+})(jQuery);
diff --git a/js/jquery/src/jquery-ui/jquery.ui.selectable.js b/js/jquery/src/jquery-ui/jquery.ui.selectable.js
new file mode 100644
index 0000000000..80e32ca7a5
--- /dev/null
+++ b/js/jquery/src/jquery-ui/jquery.ui.selectable.js
@@ -0,0 +1,261 @@
+/*!
+ * jQuery UI Selectable @VERSION
+ * http://jqueryui.com
+ *
+ * Copyright 2012 jQuery Foundation and other contributors
+ * Released under the MIT license.
+ * http://jquery.org/license
+ *
+ * http://api.jqueryui.com/selectable/
+ *
+ * Depends:
+ * jquery.ui.core.js
+ * jquery.ui.mouse.js
+ * jquery.ui.widget.js
+ */
+(function( $, undefined ) {
+
+$.widget("ui.selectable", $.ui.mouse, {
+ version: "@VERSION",
+ options: {
+ appendTo: 'body',
+ autoRefresh: true,
+ distance: 0,
+ filter: '*',
+ tolerance: 'touch'
+ },
+ _create: function() {
+ var that = this;
+
+ this.element.addClass("ui-selectable");
+
+ this.dragged = false;
+
+ // cache selectee children based on filter
+ var selectees;
+ this.refresh = function() {
+ selectees = $(that.options.filter, that.element[0]);
+ selectees.addClass("ui-selectee");
+ selectees.each(function() {
+ var $this = $(this);
+ var pos = $this.offset();
+ $.data(this, "selectable-item", {
+ element: this,
+ $element: $this,
+ left: pos.left,
+ top: pos.top,
+ right: pos.left + $this.outerWidth(),
+ bottom: pos.top + $this.outerHeight(),
+ startselected: false,
+ selected: $this.hasClass('ui-selected'),
+ selecting: $this.hasClass('ui-selecting'),
+ unselecting: $this.hasClass('ui-unselecting')
+ });
+ });
+ };
+ this.refresh();
+
+ this.selectees = selectees.addClass("ui-selectee");
+
+ this._mouseInit();
+
+ this.helper = $("<div class='ui-selectable-helper'></div>");
+ },
+
+ _destroy: function() {
+ this.selectees
+ .removeClass("ui-selectee")
+ .removeData("selectable-item");
+ this.element
+ .removeClass("ui-selectable ui-selectable-disabled");
+ this._mouseDestroy();
+ },
+
+ _mouseStart: function(event) {
+ var that = this;
+
+ this.opos = [event.pageX, event.pageY];
+
+ if (this.options.disabled)
+ return;
+
+ var options = this.options;
+
+ this.selectees = $(options.filter, this.element[0]);
+
+ this._trigger("start", event);
+
+ $(options.appendTo).append(this.helper);
+ // position helper (lasso)
+ this.helper.css({
+ "left": event.clientX,
+ "top": event.clientY,
+ "width": 0,
+ "height": 0
+ });
+
+ if (options.autoRefresh) {
+ this.refresh();
+ }
+
+ this.selectees.filter('.ui-selected').each(function() {
+ var selectee = $.data(this, "selectable-item");
+ selectee.startselected = true;
+ if (!event.metaKey && !event.ctrlKey) {
+ selectee.$element.removeClass('ui-selected');
+ selectee.selected = false;
+ selectee.$element.addClass('ui-unselecting');
+ selectee.unselecting = true;
+ // selectable UNSELECTING callback
+ that._trigger("unselecting", event, {
+ unselecting: selectee.element
+ });
+ }
+ });
+
+ $(event.target).parents().andSelf().each(function() {
+ var selectee = $.data(this, "selectable-item");
+ if (selectee) {
+ var doSelect = (!event.metaKey && !event.ctrlKey) || !selectee.$element.hasClass('ui-selected');
+ selectee.$element
+ .removeClass(doSelect ? "ui-unselecting" : "ui-selected")
+ .addClass(doSelect ? "ui-selecting" : "ui-unselecting");
+ selectee.unselecting = !doSelect;
+ selectee.selecting = doSelect;
+ selectee.selected = doSelect;
+ // selectable (UN)SELECTING callback
+ if (doSelect) {
+ that._trigger("selecting", event, {
+ selecting: selectee.element
+ });
+ } else {
+ that._trigger("unselecting", event, {
+ unselecting: selectee.element
+ });
+ }
+ return false;
+ }
+ });
+
+ },
+
+ _mouseDrag: function(event) {
+ var that = this;
+ this.dragged = true;
+
+ if (this.options.disabled)
+ return;
+
+ var options = this.options;
+
+ var x1 = this.opos[0], y1 = this.opos[1], x2 = event.pageX, y2 = event.pageY;
+ if (x1 > x2) { var tmp = x2; x2 = x1; x1 = tmp; }
+ if (y1 > y2) { var tmp = y2; y2 = y1; y1 = tmp; }
+ this.helper.css({left: x1, top: y1, width: x2-x1, height: y2-y1});
+
+ this.selectees.each(function() {
+ var selectee = $.data(this, "selectable-item");
+ //prevent helper from being selected if appendTo: selectable
+ if (!selectee || selectee.element == that.element[0])
+ return;
+ var hit = false;
+ if (options.tolerance == 'touch') {
+ hit = ( !(selectee.left > x2 || selectee.right < x1 || selectee.top > y2 || selectee.bottom < y1) );
+ } else if (options.tolerance == 'fit') {
+ hit = (selectee.left > x1 && selectee.right < x2 && selectee.top > y1 && selectee.bottom < y2);
+ }
+
+ if (hit) {
+ // SELECT
+ if (selectee.selected) {
+ selectee.$element.removeClass('ui-selected');
+ selectee.selected = false;
+ }
+ if (selectee.unselecting) {
+ selectee.$element.removeClass('ui-unselecting');
+ selectee.unselecting = false;
+ }
+ if (!selectee.selecting) {
+ selectee.$element.addClass('ui-selecting');
+ selectee.selecting = true;
+ // selectable SELECTING callback
+ that._trigger("selecting", event, {
+ selecting: selectee.element
+ });
+ }
+ } else {
+ // UNSELECT
+ if (selectee.selecting) {
+ if ((event.metaKey || event.ctrlKey) && selectee.startselected) {
+ selectee.$element.removeClass('ui-selecting');
+ selectee.selecting = false;
+ selectee.$element.addClass('ui-selected');
+ selectee.selected = true;
+ } else {
+ selectee.$element.removeClass('ui-selecting');
+ selectee.selecting = false;
+ if (selectee.startselected) {
+ selectee.$element.addClass('ui-unselecting');
+ selectee.unselecting = true;
+ }
+ // selectable UNSELECTING callback
+ that._trigger("unselecting", event, {
+ unselecting: selectee.element
+ });
+ }
+ }
+ if (selectee.selected) {
+ if (!event.metaKey && !event.ctrlKey && !selectee.startselected) {
+ selectee.$element.removeClass('ui-selected');
+ selectee.selected = false;
+
+ selectee.$element.addClass('ui-unselecting');
+ selectee.unselecting = true;
+ // selectable UNSELECTING callback
+ that._trigger("unselecting", event, {
+ unselecting: selectee.element
+ });
+ }
+ }
+ }
+ });
+
+ return false;
+ },
+
+ _mouseStop: function(event) {
+ var that = this;
+
+ this.dragged = false;
+
+ var options = this.options;
+
+ $('.ui-unselecting', this.element[0]).each(function() {
+ var selectee = $.data(this, "selectable-item");
+ selectee.$element.removeClass('ui-unselecting');
+ selectee.unselecting = false;
+ selectee.startselected = false;
+ that._trigger("unselected", event, {
+ unselected: selectee.element
+ });
+ });
+ $('.ui-selecting', this.element[0]).each(function() {
+ var selectee = $.data(this, "selectable-item");
+ selectee.$element.removeClass('ui-selecting').addClass('ui-selected');
+ selectee.selecting = false;
+ selectee.selected = true;
+ selectee.startselected = true;
+ that._trigger("selected", event, {
+ selected: selectee.element
+ });
+ });
+ this._trigger("stop", event);
+
+ this.helper.remove();
+
+ return false;
+ }
+
+});
+
+})(jQuery);
diff --git a/js/jquery/src/jquery-ui/jquery.ui.slider.js b/js/jquery/src/jquery-ui/jquery.ui.slider.js
new file mode 100644
index 0000000000..18f7113d40
--- /dev/null
+++ b/js/jquery/src/jquery-ui/jquery.ui.slider.js
@@ -0,0 +1,644 @@
+/*!
+ * jQuery UI Slider @VERSION
+ * http://jqueryui.com
+ *
+ * Copyright 2012 jQuery Foundation and other contributors
+ * Released under the MIT license.
+ * http://jquery.org/license
+ *
+ * http://api.jqueryui.com/slider/
+ *
+ * Depends:
+ * jquery.ui.core.js
+ * jquery.ui.mouse.js
+ * jquery.ui.widget.js
+ */
+(function( $, undefined ) {
+
+// number of pages in a slider
+// (how many times can you page up/down to go through the whole range)
+var numPages = 5;
+
+$.widget( "ui.slider", $.ui.mouse, {
+ version: "@VERSION",
+ widgetEventPrefix: "slide",
+
+ options: {
+ animate: false,
+ distance: 0,
+ max: 100,
+ min: 0,
+ orientation: "horizontal",
+ range: false,
+ step: 1,
+ value: 0,
+ values: null
+ },
+
+ _create: function() {
+ var i, handleCount,
+ o = this.options,
+ existingHandles = this.element.find( ".ui-slider-handle" ).addClass( "ui-state-default ui-corner-all" ),
+ handle = "<a class='ui-slider-handle ui-state-default ui-corner-all' href='#'></a>",
+ handles = [];
+
+ this._keySliding = false;
+ this._mouseSliding = false;
+ this._animateOff = true;
+ this._handleIndex = null;
+ this._detectOrientation();
+ this._mouseInit();
+
+ this.element
+ .addClass( "ui-slider" +
+ " ui-slider-" + this.orientation +
+ " ui-widget" +
+ " ui-widget-content" +
+ " ui-corner-all" +
+ ( o.disabled ? " ui-slider-disabled ui-disabled" : "" ) );
+
+ this.range = $([]);
+
+ if ( o.range ) {
+ if ( o.range === true ) {
+ if ( !o.values ) {
+ o.values = [ this._valueMin(), this._valueMin() ];
+ }
+ if ( o.values.length && o.values.length !== 2 ) {
+ o.values = [ o.values[0], o.values[0] ];
+ }
+ }
+
+ this.range = $( "<div></div>" )
+ .appendTo( this.element )
+ .addClass( "ui-slider-range" +
+ // note: this isn't the most fittingly semantic framework class for this element,
+ // but worked best visually with a variety of themes
+ " ui-widget-header" +
+ ( ( o.range === "min" || o.range === "max" ) ? " ui-slider-range-" + o.range : "" ) );
+ }
+
+ handleCount = ( o.values && o.values.length ) || 1;
+
+ for ( i = existingHandles.length; i < handleCount; i++ ) {
+ handles.push( handle );
+ }
+
+ this.handles = existingHandles.add( $( handles.join( "" ) ).appendTo( this.element ) );
+
+ this.handle = this.handles.eq( 0 );
+
+ this.handles.add( this.range ).filter( "a" )
+ .click(function( event ) {
+ event.preventDefault();
+ })
+ .mouseenter(function() {
+ if ( !o.disabled ) {
+ $( this ).addClass( "ui-state-hover" );
+ }
+ })
+ .mouseleave(function() {
+ $( this ).removeClass( "ui-state-hover" );
+ })
+ .focus(function() {
+ if ( !o.disabled ) {
+ $( ".ui-slider .ui-state-focus" ).removeClass( "ui-state-focus" );
+ $( this ).addClass( "ui-state-focus" );
+ } else {
+ $( this ).blur();
+ }
+ })
+ .blur(function() {
+ $( this ).removeClass( "ui-state-focus" );
+ });
+
+ this.handles.each(function( i ) {
+ $( this ).data( "ui-slider-handle-index", i );
+ });
+
+ this._on( this.handles, {
+ keydown: function( event ) {
+ var allowed, curVal, newVal, step,
+ index = $( event.target ).data( "ui-slider-handle-index" );
+
+ switch ( event.keyCode ) {
+ case $.ui.keyCode.HOME:
+ case $.ui.keyCode.END:
+ case $.ui.keyCode.PAGE_UP:
+ case $.ui.keyCode.PAGE_DOWN:
+ case $.ui.keyCode.UP:
+ case $.ui.keyCode.RIGHT:
+ case $.ui.keyCode.DOWN:
+ case $.ui.keyCode.LEFT:
+ event.preventDefault();
+ if ( !this._keySliding ) {
+ this._keySliding = true;
+ $( event.target ).addClass( "ui-state-active" );
+ allowed = this._start( event, index );
+ if ( allowed === false ) {
+ return;
+ }
+ }
+ break;
+ }
+
+ step = this.options.step;
+ if ( this.options.values && this.options.values.length ) {
+ curVal = newVal = this.values( index );
+ } else {
+ curVal = newVal = this.value();
+ }
+
+ switch ( event.keyCode ) {
+ case $.ui.keyCode.HOME:
+ newVal = this._valueMin();
+ break;
+ case $.ui.keyCode.END:
+ newVal = this._valueMax();
+ break;
+ case $.ui.keyCode.PAGE_UP:
+ newVal = this._trimAlignValue( curVal + ( (this._valueMax() - this._valueMin()) / numPages ) );
+ break;
+ case $.ui.keyCode.PAGE_DOWN:
+ newVal = this._trimAlignValue( curVal - ( (this._valueMax() - this._valueMin()) / numPages ) );
+ break;
+ case $.ui.keyCode.UP:
+ case $.ui.keyCode.RIGHT:
+ if ( curVal === this._valueMax() ) {
+ return;
+ }
+ newVal = this._trimAlignValue( curVal + step );
+ break;
+ case $.ui.keyCode.DOWN:
+ case $.ui.keyCode.LEFT:
+ if ( curVal === this._valueMin() ) {
+ return;
+ }
+ newVal = this._trimAlignValue( curVal - step );
+ break;
+ }
+
+ this._slide( event, index, newVal );
+ },
+ keyup: function( event ) {
+ var index = $( event.target ).data( "ui-slider-handle-index" );
+
+ if ( this._keySliding ) {
+ this._keySliding = false;
+ this._stop( event, index );
+ this._change( event, index );
+ $( event.target ).removeClass( "ui-state-active" );
+ }
+ }
+ });
+
+ this._refreshValue();
+
+ this._animateOff = false;
+ },
+
+ _destroy: function() {
+ this.handles.remove();
+ this.range.remove();
+
+ this.element
+ .removeClass( "ui-slider" +
+ " ui-slider-horizontal" +
+ " ui-slider-vertical" +
+ " ui-slider-disabled" +
+ " ui-widget" +
+ " ui-widget-content" +
+ " ui-corner-all" );
+
+ this._mouseDestroy();
+ },
+
+ _mouseCapture: function( event ) {
+ var position, normValue, distance, closestHandle, index, allowed, offset, mouseOverHandle,
+ that = this,
+ o = this.options;
+
+ if ( o.disabled ) {
+ return false;
+ }
+
+ this.elementSize = {
+ width: this.element.outerWidth(),
+ height: this.element.outerHeight()
+ };
+ this.elementOffset = this.element.offset();
+
+ position = { x: event.pageX, y: event.pageY };
+ normValue = this._normValueFromMouse( position );
+ distance = this._valueMax() - this._valueMin() + 1;
+ this.handles.each(function( i ) {
+ var thisDistance = Math.abs( normValue - that.values(i) );
+ if ( distance > thisDistance ) {
+ distance = thisDistance;
+ closestHandle = $( this );
+ index = i;
+ }
+ });
+
+ // workaround for bug #3736 (if both handles of a range are at 0,
+ // the first is always used as the one with least distance,
+ // and moving it is obviously prevented by preventing negative ranges)
+ if( o.range === true && this.values(1) === o.min ) {
+ index += 1;
+ closestHandle = $( this.handles[index] );
+ }
+
+ allowed = this._start( event, index );
+ if ( allowed === false ) {
+ return false;
+ }
+ this._mouseSliding = true;
+
+ this._handleIndex = index;
+
+ closestHandle
+ .addClass( "ui-state-active" )
+ .focus();
+
+ offset = closestHandle.offset();
+ mouseOverHandle = !$( event.target ).parents().andSelf().is( ".ui-slider-handle" );
+ this._clickOffset = mouseOverHandle ? { left: 0, top: 0 } : {
+ left: event.pageX - offset.left - ( closestHandle.width() / 2 ),
+ top: event.pageY - offset.top -
+ ( closestHandle.height() / 2 ) -
+ ( parseInt( closestHandle.css("borderTopWidth"), 10 ) || 0 ) -
+ ( parseInt( closestHandle.css("borderBottomWidth"), 10 ) || 0) +
+ ( parseInt( closestHandle.css("marginTop"), 10 ) || 0)
+ };
+
+ if ( !this.handles.hasClass( "ui-state-hover" ) ) {
+ this._slide( event, index, normValue );
+ }
+ this._animateOff = true;
+ return true;
+ },
+
+ _mouseStart: function() {
+ return true;
+ },
+
+ _mouseDrag: function( event ) {
+ var position = { x: event.pageX, y: event.pageY },
+ normValue = this._normValueFromMouse( position );
+
+ this._slide( event, this._handleIndex, normValue );
+
+ return false;
+ },
+
+ _mouseStop: function( event ) {
+ this.handles.removeClass( "ui-state-active" );
+ this._mouseSliding = false;
+
+ this._stop( event, this._handleIndex );
+ this._change( event, this._handleIndex );
+
+ this._handleIndex = null;
+ this._clickOffset = null;
+ this._animateOff = false;
+
+ return false;
+ },
+
+ _detectOrientation: function() {
+ this.orientation = ( this.options.orientation === "vertical" ) ? "vertical" : "horizontal";
+ },
+
+ _normValueFromMouse: function( position ) {
+ var pixelTotal,
+ pixelMouse,
+ percentMouse,
+ valueTotal,
+ valueMouse;
+
+ if ( this.orientation === "horizontal" ) {
+ pixelTotal = this.elementSize.width;
+ pixelMouse = position.x - this.elementOffset.left - ( this._clickOffset ? this._clickOffset.left : 0 );
+ } else {
+ pixelTotal = this.elementSize.height;
+ pixelMouse = position.y - this.elementOffset.top - ( this._clickOffset ? this._clickOffset.top : 0 );
+ }
+
+ percentMouse = ( pixelMouse / pixelTotal );
+ if ( percentMouse > 1 ) {
+ percentMouse = 1;
+ }
+ if ( percentMouse < 0 ) {
+ percentMouse = 0;
+ }
+ if ( this.orientation === "vertical" ) {
+ percentMouse = 1 - percentMouse;
+ }
+
+ valueTotal = this._valueMax() - this._valueMin();
+ valueMouse = this._valueMin() + percentMouse * valueTotal;
+
+ return this._trimAlignValue( valueMouse );
+ },
+
+ _start: function( event, index ) {
+ var uiHash = {
+ handle: this.handles[ index ],
+ value: this.value()
+ };
+ if ( this.options.values && this.options.values.length ) {
+ uiHash.value = this.values( index );
+ uiHash.values = this.values();
+ }
+ return this._trigger( "start", event, uiHash );
+ },
+
+ _slide: function( event, index, newVal ) {
+ var otherVal,
+ newValues,
+ allowed;
+
+ if ( this.options.values && this.options.values.length ) {
+ otherVal = this.values( index ? 0 : 1 );
+
+ if ( ( this.options.values.length === 2 && this.options.range === true ) &&
+ ( ( index === 0 && newVal > otherVal) || ( index === 1 && newVal < otherVal ) )
+ ) {
+ newVal = otherVal;
+ }
+
+ if ( newVal !== this.values( index ) ) {
+ newValues = this.values();
+ newValues[ index ] = newVal;
+ // A slide can be canceled by returning false from the slide callback
+ allowed = this._trigger( "slide", event, {
+ handle: this.handles[ index ],
+ value: newVal,
+ values: newValues
+ } );
+ otherVal = this.values( index ? 0 : 1 );
+ if ( allowed !== false ) {
+ this.values( index, newVal, true );
+ }
+ }
+ } else {
+ if ( newVal !== this.value() ) {
+ // A slide can be canceled by returning false from the slide callback
+ allowed = this._trigger( "slide", event, {
+ handle: this.handles[ index ],
+ value: newVal
+ } );
+ if ( allowed !== false ) {
+ this.value( newVal );
+ }
+ }
+ }
+ },
+
+ _stop: function( event, index ) {
+ var uiHash = {
+ handle: this.handles[ index ],
+ value: this.value()
+ };
+ if ( this.options.values && this.options.values.length ) {
+ uiHash.value = this.values( index );
+ uiHash.values = this.values();
+ }
+
+ this._trigger( "stop", event, uiHash );
+ },
+
+ _change: function( event, index ) {
+ if ( !this._keySliding && !this._mouseSliding ) {
+ var uiHash = {
+ handle: this.handles[ index ],
+ value: this.value()
+ };
+ if ( this.options.values && this.options.values.length ) {
+ uiHash.value = this.values( index );
+ uiHash.values = this.values();
+ }
+
+ this._trigger( "change", event, uiHash );
+ }
+ },
+
+ value: function( newValue ) {
+ if ( arguments.length ) {
+ this.options.value = this._trimAlignValue( newValue );
+ this._refreshValue();
+ this._change( null, 0 );
+ return;
+ }
+
+ return this._value();
+ },
+
+ values: function( index, newValue ) {
+ var vals,
+ newValues,
+ i;
+
+ if ( arguments.length > 1 ) {
+ this.options.values[ index ] = this._trimAlignValue( newValue );
+ this._refreshValue();
+ this._change( null, index );
+ return;
+ }
+
+ if ( arguments.length ) {
+ if ( $.isArray( arguments[ 0 ] ) ) {
+ vals = this.options.values;
+ newValues = arguments[ 0 ];
+ for ( i = 0; i < vals.length; i += 1 ) {
+ vals[ i ] = this._trimAlignValue( newValues[ i ] );
+ this._change( null, i );
+ }
+ this._refreshValue();
+ } else {
+ if ( this.options.values && this.options.values.length ) {
+ return this._values( index );
+ } else {
+ return this.value();
+ }
+ }
+ } else {
+ return this._values();
+ }
+ },
+
+ _setOption: function( key, value ) {
+ var i,
+ valsLength = 0;
+
+ if ( $.isArray( this.options.values ) ) {
+ valsLength = this.options.values.length;
+ }
+
+ $.Widget.prototype._setOption.apply( this, arguments );
+
+ switch ( key ) {
+ case "disabled":
+ if ( value ) {
+ this.handles.filter( ".ui-state-focus" ).blur();
+ this.handles.removeClass( "ui-state-hover" );
+ this.handles.prop( "disabled", true );
+ this.element.addClass( "ui-disabled" );
+ } else {
+ this.handles.prop( "disabled", false );
+ this.element.removeClass( "ui-disabled" );
+ }
+ break;
+ case "orientation":
+ this._detectOrientation();
+ this.element
+ .removeClass( "ui-slider-horizontal ui-slider-vertical" )
+ .addClass( "ui-slider-" + this.orientation );
+ this._refreshValue();
+ break;
+ case "value":
+ this._animateOff = true;
+ this._refreshValue();
+ this._change( null, 0 );
+ this._animateOff = false;
+ break;
+ case "values":
+ this._animateOff = true;
+ this._refreshValue();
+ for ( i = 0; i < valsLength; i += 1 ) {
+ this._change( null, i );
+ }
+ this._animateOff = false;
+ break;
+ case "min":
+ case "max":
+ this._animateOff = true;
+ this._refreshValue();
+ this._animateOff = false;
+ break;
+ }
+ },
+
+ //internal value getter
+ // _value() returns value trimmed by min and max, aligned by step
+ _value: function() {
+ var val = this.options.value;
+ val = this._trimAlignValue( val );
+
+ return val;
+ },
+
+ //internal values getter
+ // _values() returns array of values trimmed by min and max, aligned by step
+ // _values( index ) returns single value trimmed by min and max, aligned by step
+ _values: function( index ) {
+ var val,
+ vals,
+ i;
+
+ if ( arguments.length ) {
+ val = this.options.values[ index ];
+ val = this._trimAlignValue( val );
+
+ return val;
+ } else {
+ // .slice() creates a copy of the array
+ // this copy gets trimmed by min and max and then returned
+ vals = this.options.values.slice();
+ for ( i = 0; i < vals.length; i+= 1) {
+ vals[ i ] = this._trimAlignValue( vals[ i ] );
+ }
+
+ return vals;
+ }
+ },
+
+ // returns the step-aligned value that val is closest to, between (inclusive) min and max
+ _trimAlignValue: function( val ) {
+ if ( val <= this._valueMin() ) {
+ return this._valueMin();
+ }
+ if ( val >= this._valueMax() ) {
+ return this._valueMax();
+ }
+ var step = ( this.options.step > 0 ) ? this.options.step : 1,
+ valModStep = (val - this._valueMin()) % step,
+ alignValue = val - valModStep;
+
+ if ( Math.abs(valModStep) * 2 >= step ) {
+ alignValue += ( valModStep > 0 ) ? step : ( -step );
+ }
+
+ // Since JavaScript has problems with large floats, round
+ // the final value to 5 digits after the decimal point (see #4124)
+ return parseFloat( alignValue.toFixed(5) );
+ },
+
+ _valueMin: function() {
+ return this.options.min;
+ },
+
+ _valueMax: function() {
+ return this.options.max;
+ },
+
+ _refreshValue: function() {
+ var lastValPercent, valPercent, value, valueMin, valueMax,
+ oRange = this.options.range,
+ o = this.options,
+ that = this,
+ animate = ( !this._animateOff ) ? o.animate : false,
+ _set = {};
+
+ if ( this.options.values && this.options.values.length ) {
+ this.handles.each(function( i ) {
+ valPercent = ( that.values(i) - that._valueMin() ) / ( that._valueMax() - that._valueMin() ) * 100;
+ _set[ that.orientation === "horizontal" ? "left" : "bottom" ] = valPercent + "%";
+ $( this ).stop( 1, 1 )[ animate ? "animate" : "css" ]( _set, o.animate );
+ if ( that.options.range === true ) {
+ if ( that.orientation === "horizontal" ) {
+ if ( i === 0 ) {
+ that.range.stop( 1, 1 )[ animate ? "animate" : "css" ]( { left: valPercent + "%" }, o.animate );
+ }
+ if ( i === 1 ) {
+ that.range[ animate ? "animate" : "css" ]( { width: ( valPercent - lastValPercent ) + "%" }, { queue: false, duration: o.animate } );
+ }
+ } else {
+ if ( i === 0 ) {
+ that.range.stop( 1, 1 )[ animate ? "animate" : "css" ]( { bottom: ( valPercent ) + "%" }, o.animate );
+ }
+ if ( i === 1 ) {
+ that.range[ animate ? "animate" : "css" ]( { height: ( valPercent - lastValPercent ) + "%" }, { queue: false, duration: o.animate } );
+ }
+ }
+ }
+ lastValPercent = valPercent;
+ });
+ } else {
+ value = this.value();
+ valueMin = this._valueMin();
+ valueMax = this._valueMax();
+ valPercent = ( valueMax !== valueMin ) ?
+ ( value - valueMin ) / ( valueMax - valueMin ) * 100 :
+ 0;
+ _set[ this.orientation === "horizontal" ? "left" : "bottom" ] = valPercent + "%";
+ this.handle.stop( 1, 1 )[ animate ? "animate" : "css" ]( _set, o.animate );
+
+ if ( oRange === "min" && this.orientation === "horizontal" ) {
+ this.range.stop( 1, 1 )[ animate ? "animate" : "css" ]( { width: valPercent + "%" }, o.animate );
+ }
+ if ( oRange === "max" && this.orientation === "horizontal" ) {
+ this.range[ animate ? "animate" : "css" ]( { width: ( 100 - valPercent ) + "%" }, { queue: false, duration: o.animate } );
+ }
+ if ( oRange === "min" && this.orientation === "vertical" ) {
+ this.range.stop( 1, 1 )[ animate ? "animate" : "css" ]( { height: valPercent + "%" }, o.animate );
+ }
+ if ( oRange === "max" && this.orientation === "vertical" ) {
+ this.range[ animate ? "animate" : "css" ]( { height: ( 100 - valPercent ) + "%" }, { queue: false, duration: o.animate } );
+ }
+ }
+ }
+
+});
+
+}(jQuery));
diff --git a/js/jquery/src/jquery-ui/jquery.ui.sortable.js b/js/jquery/src/jquery-ui/jquery.ui.sortable.js
new file mode 100644
index 0000000000..a2132a9bdc
--- /dev/null
+++ b/js/jquery/src/jquery-ui/jquery.ui.sortable.js
@@ -0,0 +1,1096 @@
+/*!
+ * jQuery UI Sortable @VERSION
+ * http://jqueryui.com
+ *
+ * Copyright 2012 jQuery Foundation and other contributors
+ * Released under the MIT license.
+ * http://jquery.org/license
+ *
+ * http://api.jqueryui.com/sortable/
+ *
+ * Depends:
+ * jquery.ui.core.js
+ * jquery.ui.mouse.js
+ * jquery.ui.widget.js
+ */
+(function( $, undefined ) {
+
+$.widget("ui.sortable", $.ui.mouse, {
+ version: "@VERSION",
+ widgetEventPrefix: "sort",
+ ready: false,
+ options: {
+ appendTo: "parent",
+ axis: false,
+ connectWith: false,
+ containment: false,
+ cursor: 'auto',
+ cursorAt: false,
+ dropOnEmpty: true,
+ forcePlaceholderSize: false,
+ forceHelperSize: false,
+ grid: false,
+ handle: false,
+ helper: "original",
+ items: '> *',
+ opacity: false,
+ placeholder: false,
+ revert: false,
+ scroll: true,
+ scrollSensitivity: 20,
+ scrollSpeed: 20,
+ scope: "default",
+ tolerance: "intersect",
+ zIndex: 1000
+ },
+ _create: function() {
+
+ var o = this.options;
+ this.containerCache = {};
+ this.element.addClass("ui-sortable");
+
+ //Get the items
+ this.refresh();
+
+ //Let's determine if the items are being displayed horizontally
+ this.floating = this.items.length ? o.axis === 'x' || (/left|right/).test(this.items[0].item.css('float')) || (/inline|table-cell/).test(this.items[0].item.css('display')) : false;
+
+ //Let's determine the parent's offset
+ this.offset = this.element.offset();
+
+ //Initialize mouse events for interaction
+ this._mouseInit();
+
+ //We're ready to go
+ this.ready = true
+
+ },
+
+ _destroy: function() {
+ this.element
+ .removeClass("ui-sortable ui-sortable-disabled");
+ this._mouseDestroy();
+
+ for ( var i = this.items.length - 1; i >= 0; i-- )
+ this.items[i].item.removeData(this.widgetName + "-item");
+
+ return this;
+ },
+
+ _setOption: function(key, value){
+ if ( key === "disabled" ) {
+ this.options[ key ] = value;
+
+ this.widget().toggleClass( "ui-sortable-disabled", !!value );
+ } else {
+ // Don't call widget base _setOption for disable as it adds ui-state-disabled class
+ $.Widget.prototype._setOption.apply(this, arguments);
+ }
+ },
+
+ _mouseCapture: function(event, overrideHandle) {
+ var that = this;
+
+ if (this.reverting) {
+ return false;
+ }
+
+ if(this.options.disabled || this.options.type == 'static') return false;
+
+ //We have to refresh the items data once first
+ this._refreshItems(event);
+
+ //Find out if the clicked node (or one of its parents) is a actual item in this.items
+ var currentItem = null, nodes = $(event.target).parents().each(function() {
+ if($.data(this, that.widgetName + '-item') == that) {
+ currentItem = $(this);
+ return false;
+ }
+ });
+ if($.data(event.target, that.widgetName + '-item') == that) currentItem = $(event.target);
+
+ if(!currentItem) return false;
+ if(this.options.handle && !overrideHandle) {
+ var validHandle = false;
+
+ $(this.options.handle, currentItem).find("*").andSelf().each(function() { if(this == event.target) validHandle = true; });
+ if(!validHandle) return false;
+ }
+
+ this.currentItem = currentItem;
+ this._removeCurrentsFromItems();
+ return true;
+
+ },
+
+ _mouseStart: function(event, overrideHandle, noActivation) {
+
+ var o = this.options;
+ this.currentContainer = this;
+
+ //We only need to call refreshPositions, because the refreshItems call has been moved to mouseCapture
+ this.refreshPositions();
+
+ //Create and append the visible helper
+ this.helper = this._createHelper(event);
+
+ //Cache the helper size
+ this._cacheHelperProportions();
+
+ /*
+ * - Position generation -
+ * This block generates everything position related - it's the core of draggables.
+ */
+
+ //Cache the margins of the original element
+ this._cacheMargins();
+
+ //Get the next scrolling parent
+ this.scrollParent = this.helper.scrollParent();
+
+ //The element's absolute position on the page minus margins
+ this.offset = this.currentItem.offset();
+ this.offset = {
+ top: this.offset.top - this.margins.top,
+ left: this.offset.left - this.margins.left
+ };
+
+ $.extend(this.offset, {
+ click: { //Where the click happened, relative to the element
+ left: event.pageX - this.offset.left,
+ top: event.pageY - this.offset.top
+ },
+ parent: this._getParentOffset(),
+ relative: this._getRelativeOffset() //This is a relative to absolute position minus the actual position calculation - only used for relative positioned helper
+ });
+
+ // Only after we got the offset, we can change the helper's position to absolute
+ // TODO: Still need to figure out a way to make relative sorting possible
+ this.helper.css("position", "absolute");
+ this.cssPosition = this.helper.css("position");
+
+ //Generate the original position
+ this.originalPosition = this._generatePosition(event);
+ this.originalPageX = event.pageX;
+ this.originalPageY = event.pageY;
+
+ //Adjust the mouse offset relative to the helper if 'cursorAt' is supplied
+ (o.cursorAt && this._adjustOffsetFromHelper(o.cursorAt));
+
+ //Cache the former DOM position
+ this.domPosition = { prev: this.currentItem.prev()[0], parent: this.currentItem.parent()[0] };
+
+ //If the helper is not the original, hide the original so it's not playing any role during the drag, won't cause anything bad this way
+ if(this.helper[0] != this.currentItem[0]) {
+ this.currentItem.hide();
+ }
+
+ //Create the placeholder
+ this._createPlaceholder();
+
+ //Set a containment if given in the options
+ if(o.containment)
+ this._setContainment();
+
+ if(o.cursor) { // cursor option
+ if ($('body').css("cursor")) this._storedCursor = $('body').css("cursor");
+ $('body').css("cursor", o.cursor);
+ }
+
+ if(o.opacity) { // opacity option
+ if (this.helper.css("opacity")) this._storedOpacity = this.helper.css("opacity");
+ this.helper.css("opacity", o.opacity);
+ }
+
+ if(o.zIndex) { // zIndex option
+ if (this.helper.css("zIndex")) this._storedZIndex = this.helper.css("zIndex");
+ this.helper.css("zIndex", o.zIndex);
+ }
+
+ //Prepare scrolling
+ if(this.scrollParent[0] != document && this.scrollParent[0].tagName != 'HTML')
+ this.overflowOffset = this.scrollParent.offset();
+
+ //Call callbacks
+ this._trigger("start", event, this._uiHash());
+
+ //Recache the helper size
+ if(!this._preserveHelperProportions)
+ this._cacheHelperProportions();
+
+
+ //Post 'activate' events to possible containers
+ if(!noActivation) {
+ for (var i = this.containers.length - 1; i >= 0; i--) { this.containers[i]._trigger("activate", event, this._uiHash(this)); }
+ }
+
+ //Prepare possible droppables
+ if($.ui.ddmanager)
+ $.ui.ddmanager.current = this;
+
+ if ($.ui.ddmanager && !o.dropBehaviour)
+ $.ui.ddmanager.prepareOffsets(this, event);
+
+ this.dragging = true;
+
+ this.helper.addClass("ui-sortable-helper");
+ this._mouseDrag(event); //Execute the drag once - this causes the helper not to be visible before getting its correct position
+ return true;
+
+ },
+
+ _mouseDrag: function(event) {
+
+ //Compute the helpers position
+ this.position = this._generatePosition(event);
+ this.positionAbs = this._convertPositionTo("absolute");
+
+ if (!this.lastPositionAbs) {
+ this.lastPositionAbs = this.positionAbs;
+ }
+
+ //Do scrolling
+ if(this.options.scroll) {
+ var o = this.options, scrolled = false;
+ if(this.scrollParent[0] != document && this.scrollParent[0].tagName != 'HTML') {
+
+ if((this.overflowOffset.top + this.scrollParent[0].offsetHeight) - event.pageY < o.scrollSensitivity)
+ this.scrollParent[0].scrollTop = scrolled = this.scrollParent[0].scrollTop + o.scrollSpeed;
+ else if(event.pageY - this.overflowOffset.top < o.scrollSensitivity)
+ this.scrollParent[0].scrollTop = scrolled = this.scrollParent[0].scrollTop - o.scrollSpeed;
+
+ if((this.overflowOffset.left + this.scrollParent[0].offsetWidth) - event.pageX < o.scrollSensitivity)
+ this.scrollParent[0].scrollLeft = scrolled = this.scrollParent[0].scrollLeft + o.scrollSpeed;
+ else if(event.pageX - this.overflowOffset.left < o.scrollSensitivity)
+ this.scrollParent[0].scrollLeft = scrolled = this.scrollParent[0].scrollLeft - o.scrollSpeed;
+
+ } else {
+
+ if(event.pageY - $(document).scrollTop() < o.scrollSensitivity)
+ scrolled = $(document).scrollTop($(document).scrollTop() - o.scrollSpeed);
+ else if($(window).height() - (event.pageY - $(document).scrollTop()) < o.scrollSensitivity)
+ scrolled = $(document).scrollTop($(document).scrollTop() + o.scrollSpeed);
+
+ if(event.pageX - $(document).scrollLeft() < o.scrollSensitivity)
+ scrolled = $(document).scrollLeft($(document).scrollLeft() - o.scrollSpeed);
+ else if($(window).width() - (event.pageX - $(document).scrollLeft()) < o.scrollSensitivity)
+ scrolled = $(document).scrollLeft($(document).scrollLeft() + o.scrollSpeed);
+
+ }
+
+ if(scrolled !== false && $.ui.ddmanager && !o.dropBehaviour)
+ $.ui.ddmanager.prepareOffsets(this, event);
+ }
+
+ //Regenerate the absolute position used for position checks
+ this.positionAbs = this._convertPositionTo("absolute");
+
+ //Set the helper position
+ if(!this.options.axis || this.options.axis != "y") this.helper[0].style.left = this.position.left+'px';
+ if(!this.options.axis || this.options.axis != "x") this.helper[0].style.top = this.position.top+'px';
+
+ //Rearrange
+ for (var i = this.items.length - 1; i >= 0; i--) {
+
+ //Cache variables and intersection, continue if no intersection
+ var item = this.items[i], itemElement = item.item[0], intersection = this._intersectsWithPointer(item);
+ if (!intersection) continue;
+
+ // Only put the placeholder inside the current Container, skip all
+ // items form other containers. This works because when moving
+ // an item from one container to another the
+ // currentContainer is switched before the placeholder is moved.
+ //
+ // Without this moving items in "sub-sortables" can cause the placeholder to jitter
+ // beetween the outer and inner container.
+ if (item.instance !== this.currentContainer) continue;
+
+ if (itemElement != this.currentItem[0] //cannot intersect with itself
+ && this.placeholder[intersection == 1 ? "next" : "prev"]()[0] != itemElement //no useless actions that have been done before
+ && !$.contains(this.placeholder[0], itemElement) //no action if the item moved is the parent of the item checked
+ && (this.options.type == 'semi-dynamic' ? !$.contains(this.element[0], itemElement) : true)
+ //&& itemElement.parentNode == this.placeholder[0].parentNode // only rearrange items within the same container
+ ) {
+
+ this.direction = intersection == 1 ? "down" : "up";
+
+ if (this.options.tolerance == "pointer" || this._intersectsWithSides(item)) {
+ this._rearrange(event, item);
+ } else {
+ break;
+ }
+
+ this._trigger("change", event, this._uiHash());
+ break;
+ }
+ }
+
+ //Post events to containers
+ this._contactContainers(event);
+
+ //Interconnect with droppables
+ if($.ui.ddmanager) $.ui.ddmanager.drag(this, event);
+
+ //Call callbacks
+ this._trigger('sort', event, this._uiHash());
+
+ this.lastPositionAbs = this.positionAbs;
+ return false;
+
+ },
+
+ _mouseStop: function(event, noPropagation) {
+
+ if(!event) return;
+
+ //If we are using droppables, inform the manager about the drop
+ if ($.ui.ddmanager && !this.options.dropBehaviour)
+ $.ui.ddmanager.drop(this, event);
+
+ if(this.options.revert) {
+ var that = this;
+ var cur = this.placeholder.offset();
+
+ this.reverting = true;
+
+ $(this.helper).animate({
+ left: cur.left - this.offset.parent.left - this.margins.left + (this.offsetParent[0] == document.body ? 0 : this.offsetParent[0].scrollLeft),
+ top: cur.top - this.offset.parent.top - this.margins.top + (this.offsetParent[0] == document.body ? 0 : this.offsetParent[0].scrollTop)
+ }, parseInt(this.options.revert, 10) || 500, function() {
+ that._clear(event);
+ });
+ } else {
+ this._clear(event, noPropagation);
+ }
+
+ return false;
+
+ },
+
+ cancel: function() {
+
+ if(this.dragging) {
+
+ this._mouseUp({ target: null });
+
+ if(this.options.helper == "original")
+ this.currentItem.css(this._storedCSS).removeClass("ui-sortable-helper");
+ else
+ this.currentItem.show();
+
+ //Post deactivating events to containers
+ for (var i = this.containers.length - 1; i >= 0; i--){
+ this.containers[i]._trigger("deactivate", null, this._uiHash(this));
+ if(this.containers[i].containerCache.over) {
+ this.containers[i]._trigger("out", null, this._uiHash(this));
+ this.containers[i].containerCache.over = 0;
+ }
+ }
+
+ }
+
+ if (this.placeholder) {
+ //$(this.placeholder[0]).remove(); would have been the jQuery way - unfortunately, it unbinds ALL events from the original node!
+ if(this.placeholder[0].parentNode) this.placeholder[0].parentNode.removeChild(this.placeholder[0]);
+ if(this.options.helper != "original" && this.helper && this.helper[0].parentNode) this.helper.remove();
+
+ $.extend(this, {
+ helper: null,
+ dragging: false,
+ reverting: false,
+ _noFinalSort: null
+ });
+
+ if(this.domPosition.prev) {
+ $(this.domPosition.prev).after(this.currentItem);
+ } else {
+ $(this.domPosition.parent).prepend(this.currentItem);
+ }
+ }
+
+ return this;
+
+ },
+
+ serialize: function(o) {
+
+ var items = this._getItemsAsjQuery(o && o.connected);
+ var str = []; o = o || {};
+
+ $(items).each(function() {
+ var res = ($(o.item || this).attr(o.attribute || 'id') || '').match(o.expression || (/(.+)[-=_](.+)/));
+ if(res) str.push((o.key || res[1]+'[]')+'='+(o.key && o.expression ? res[1] : res[2]));
+ });
+
+ if(!str.length && o.key) {
+ str.push(o.key + '=');
+ }
+
+ return str.join('&');
+
+ },
+
+ toArray: function(o) {
+
+ var items = this._getItemsAsjQuery(o && o.connected);
+ var ret = []; o = o || {};
+
+ items.each(function() { ret.push($(o.item || this).attr(o.attribute || 'id') || ''); });
+ return ret;
+
+ },
+
+ /* Be careful with the following core functions */
+ _intersectsWith: function(item) {
+
+ var x1 = this.positionAbs.left,
+ x2 = x1 + this.helperProportions.width,
+ y1 = this.positionAbs.top,
+ y2 = y1 + this.helperProportions.height;
+
+ var l = item.left,
+ r = l + item.width,
+ t = item.top,
+ b = t + item.height;
+
+ var dyClick = this.offset.click.top,
+ dxClick = this.offset.click.left;
+
+ var isOverElement = (y1 + dyClick) > t && (y1 + dyClick) < b && (x1 + dxClick) > l && (x1 + dxClick) < r;
+
+ if( this.options.tolerance == "pointer"
+ || this.options.forcePointerForContainers
+ || (this.options.tolerance != "pointer" && this.helperProportions[this.floating ? 'width' : 'height'] > item[this.floating ? 'width' : 'height'])
+ ) {
+ return isOverElement;
+ } else {
+
+ return (l < x1 + (this.helperProportions.width / 2) // Right Half
+ && x2 - (this.helperProportions.width / 2) < r // Left Half
+ && t < y1 + (this.helperProportions.height / 2) // Bottom Half
+ && y2 - (this.helperProportions.height / 2) < b ); // Top Half
+
+ }
+ },
+
+ _intersectsWithPointer: function(item) {
+
+ var isOverElementHeight = (this.options.axis === 'x') || $.ui.isOverAxis(this.positionAbs.top + this.offset.click.top, item.top, item.height),
+ isOverElementWidth = (this.options.axis === 'y') || $.ui.isOverAxis(this.positionAbs.left + this.offset.click.left, item.left, item.width),
+ isOverElement = isOverElementHeight && isOverElementWidth,
+ verticalDirection = this._getDragVerticalDirection(),
+ horizontalDirection = this._getDragHorizontalDirection();
+
+ if (!isOverElement)
+ return false;
+
+ return this.floating ?
+ ( ((horizontalDirection && horizontalDirection == "right") || verticalDirection == "down") ? 2 : 1 )
+ : ( verticalDirection && (verticalDirection == "down" ? 2 : 1) );
+
+ },
+
+ _intersectsWithSides: function(item) {
+
+ var isOverBottomHalf = $.ui.isOverAxis(this.positionAbs.top + this.offset.click.top, item.top + (item.height/2), item.height),
+ isOverRightHalf = $.ui.isOverAxis(this.positionAbs.left + this.offset.click.left, item.left + (item.width/2), item.width),
+ verticalDirection = this._getDragVerticalDirection(),
+ horizontalDirection = this._getDragHorizontalDirection();
+
+ if (this.floating && horizontalDirection) {
+ return ((horizontalDirection == "right" && isOverRightHalf) || (horizontalDirection == "left" && !isOverRightHalf));
+ } else {
+ return verticalDirection && ((verticalDirection == "down" && isOverBottomHalf) || (verticalDirection == "up" && !isOverBottomHalf));
+ }
+
+ },
+
+ _getDragVerticalDirection: function() {
+ var delta = this.positionAbs.top - this.lastPositionAbs.top;
+ return delta != 0 && (delta > 0 ? "down" : "up");
+ },
+
+ _getDragHorizontalDirection: function() {
+ var delta = this.positionAbs.left - this.lastPositionAbs.left;
+ return delta != 0 && (delta > 0 ? "right" : "left");
+ },
+
+ refresh: function(event) {
+ this._refreshItems(event);
+ this.refreshPositions();
+ return this;
+ },
+
+ _connectWith: function() {
+ var options = this.options;
+ return options.connectWith.constructor == String
+ ? [options.connectWith]
+ : options.connectWith;
+ },
+
+ _getItemsAsjQuery: function(connected) {
+
+ var items = [];
+ var queries = [];
+ var connectWith = this._connectWith();
+
+ if(connectWith && connected) {
+ for (var i = connectWith.length - 1; i >= 0; i--){
+ var cur = $(connectWith[i]);
+ for (var j = cur.length - 1; j >= 0; j--){
+ var inst = $.data(cur[j], this.widgetName);
+ if(inst && inst != this && !inst.options.disabled) {
+ queries.push([$.isFunction(inst.options.items) ? inst.options.items.call(inst.element) : $(inst.options.items, inst.element).not(".ui-sortable-helper").not('.ui-sortable-placeholder'), inst]);
+ }
+ };
+ };
+ }
+
+ queries.push([$.isFunction(this.options.items) ? this.options.items.call(this.element, null, { options: this.options, item: this.currentItem }) : $(this.options.items, this.element).not(".ui-sortable-helper").not('.ui-sortable-placeholder'), this]);
+
+ for (var i = queries.length - 1; i >= 0; i--){
+ queries[i][0].each(function() {
+ items.push(this);
+ });
+ };
+
+ return $(items);
+
+ },
+
+ _removeCurrentsFromItems: function() {
+
+ var list = this.currentItem.find(":data(" + this.widgetName + "-item)");
+
+ this.items = $.grep(this.items, function (item) {
+ for (var j=0; j < list.length; j++) {
+ if(list[j] == item.item[0])
+ return false;
+ };
+ return true;
+ });
+
+ },
+
+ _refreshItems: function(event) {
+
+ this.items = [];
+ this.containers = [this];
+ var items = this.items;
+ var queries = [[$.isFunction(this.options.items) ? this.options.items.call(this.element[0], event, { item: this.currentItem }) : $(this.options.items, this.element), this]];
+ var connectWith = this._connectWith();
+
+ if(connectWith && this.ready) { //Shouldn't be run the first time through due to massive slow-down
+ for (var i = connectWith.length - 1; i >= 0; i--){
+ var cur = $(connectWith[i]);
+ for (var j = cur.length - 1; j >= 0; j--){
+ var inst = $.data(cur[j], this.widgetName);
+ if(inst && inst != this && !inst.options.disabled) {
+ queries.push([$.isFunction(inst.options.items) ? inst.options.items.call(inst.element[0], event, { item: this.currentItem }) : $(inst.options.items, inst.element), inst]);
+ this.containers.push(inst);
+ }
+ };
+ };
+ }
+
+ for (var i = queries.length - 1; i >= 0; i--) {
+ var targetData = queries[i][1];
+ var _queries = queries[i][0];
+
+ for (var j=0, queriesLength = _queries.length; j < queriesLength; j++) {
+ var item = $(_queries[j]);
+
+ item.data(this.widgetName + '-item', targetData); // Data for target checking (mouse manager)
+
+ items.push({
+ item: item,
+ instance: targetData,
+ width: 0, height: 0,
+ left: 0, top: 0
+ });
+ };
+ };
+
+ },
+
+ refreshPositions: function(fast) {
+
+ //This has to be redone because due to the item being moved out/into the offsetParent, the offsetParent's position will change
+ if(this.offsetParent && this.helper) {
+ this.offset.parent = this._getParentOffset();
+ }
+
+ for (var i = this.items.length - 1; i >= 0; i--){
+ var item = this.items[i];
+
+ //We ignore calculating positions of all connected containers when we're not over them
+ if(item.instance != this.currentContainer && this.currentContainer && item.item[0] != this.currentItem[0])
+ continue;
+
+ var t = this.options.toleranceElement ? $(this.options.toleranceElement, item.item) : item.item;
+
+ if (!fast) {
+ item.width = t.outerWidth();
+ item.height = t.outerHeight();
+ }
+
+ var p = t.offset();
+ item.left = p.left;
+ item.top = p.top;
+ };
+
+ if(this.options.custom && this.options.custom.refreshContainers) {
+ this.options.custom.refreshContainers.call(this);
+ } else {
+ for (var i = this.containers.length - 1; i >= 0; i--){
+ var p = this.containers[i].element.offset();
+ this.containers[i].containerCache.left = p.left;
+ this.containers[i].containerCache.top = p.top;
+ this.containers[i].containerCache.width = this.containers[i].element.outerWidth();
+ this.containers[i].containerCache.height = this.containers[i].element.outerHeight();
+ };
+ }
+
+ return this;
+ },
+
+ _createPlaceholder: function(that) {
+ that = that || this;
+ var o = that.options;
+
+ if(!o.placeholder || o.placeholder.constructor == String) {
+ var className = o.placeholder;
+ o.placeholder = {
+ element: function() {
+
+ var el = $(document.createElement(that.currentItem[0].nodeName))
+ .addClass(className || that.currentItem[0].className+" ui-sortable-placeholder")
+ .removeClass("ui-sortable-helper")[0];
+
+ if(!className)
+ el.style.visibility = "hidden";
+
+ return el;
+ },
+ update: function(container, p) {
+
+ // 1. If a className is set as 'placeholder option, we don't force sizes - the class is responsible for that
+ // 2. The option 'forcePlaceholderSize can be enabled to force it even if a class name is specified
+ if(className && !o.forcePlaceholderSize) return;
+
+ //If the element doesn't have a actual height by itself (without styles coming from a stylesheet), it receives the inline height from the dragged item
+ if(!p.height()) { p.height(that.currentItem.innerHeight() - parseInt(that.currentItem.css('paddingTop')||0, 10) - parseInt(that.currentItem.css('paddingBottom')||0, 10)); };
+ if(!p.width()) { p.width(that.currentItem.innerWidth() - parseInt(that.currentItem.css('paddingLeft')||0, 10) - parseInt(that.currentItem.css('paddingRight')||0, 10)); };
+ }
+ };
+ }
+
+ //Create the placeholder
+ that.placeholder = $(o.placeholder.element.call(that.element, that.currentItem));
+
+ //Append it after the actual current item
+ that.currentItem.after(that.placeholder);
+
+ //Update the size of the placeholder (TODO: Logic to fuzzy, see line 316/317)
+ o.placeholder.update(that, that.placeholder);
+
+ },
+
+ _contactContainers: function(event) {
+
+ // get innermost container that intersects with item
+ var innermostContainer = null, innermostIndex = null;
+
+
+ for (var i = this.containers.length - 1; i >= 0; i--){
+
+ // never consider a container that's located within the item itself
+ if($.contains(this.currentItem[0], this.containers[i].element[0]))
+ continue;
+
+ if(this._intersectsWith(this.containers[i].containerCache)) {
+
+ // if we've already found a container and it's more "inner" than this, then continue
+ if(innermostContainer && $.contains(this.containers[i].element[0], innermostContainer.element[0]))
+ continue;
+
+ innermostContainer = this.containers[i];
+ innermostIndex = i;
+
+ } else {
+ // container doesn't intersect. trigger "out" event if necessary
+ if(this.containers[i].containerCache.over) {
+ this.containers[i]._trigger("out", event, this._uiHash(this));
+ this.containers[i].containerCache.over = 0;
+ }
+ }
+
+ }
+
+ // if no intersecting containers found, return
+ if(!innermostContainer) return;
+
+ // move the item into the container if it's not there already
+ if(this.containers.length === 1) {
+ this.containers[innermostIndex]._trigger("over", event, this._uiHash(this));
+ this.containers[innermostIndex].containerCache.over = 1;
+ } else {
+
+ //When entering a new container, we will find the item with the least distance and append our item near it
+ var dist = 10000; var itemWithLeastDistance = null;
+ var posProperty = this.containers[innermostIndex].floating ? 'left' : 'top';
+ var sizeProperty = this.containers[innermostIndex].floating ? 'width' : 'height';
+ var base = this.positionAbs[posProperty] + this.offset.click[posProperty];
+ for (var j = this.items.length - 1; j >= 0; j--) {
+ if(!$.contains(this.containers[innermostIndex].element[0], this.items[j].item[0])) continue;
+ if(this.items[j].item[0] == this.currentItem[0]) continue;
+ var cur = this.items[j].item.offset()[posProperty];
+ var nearBottom = false;
+ if(Math.abs(cur - base) > Math.abs(cur + this.items[j][sizeProperty] - base)){
+ nearBottom = true;
+ cur += this.items[j][sizeProperty];
+ }
+
+ if(Math.abs(cur - base) < dist) {
+ dist = Math.abs(cur - base); itemWithLeastDistance = this.items[j];
+ this.direction = nearBottom ? "up": "down";
+ }
+ }
+
+ if(!itemWithLeastDistance && !this.options.dropOnEmpty) //Check if dropOnEmpty is enabled
+ return;
+
+ this.currentContainer = this.containers[innermostIndex];
+ itemWithLeastDistance ? this._rearrange(event, itemWithLeastDistance, null, true) : this._rearrange(event, null, this.containers[innermostIndex].element, true);
+ this._trigger("change", event, this._uiHash());
+ this.containers[innermostIndex]._trigger("change", event, this._uiHash(this));
+
+ //Update the placeholder
+ this.options.placeholder.update(this.currentContainer, this.placeholder);
+
+ this.containers[innermostIndex]._trigger("over", event, this._uiHash(this));
+ this.containers[innermostIndex].containerCache.over = 1;
+ }
+
+
+ },
+
+ _createHelper: function(event) {
+
+ var o = this.options;
+ var helper = $.isFunction(o.helper) ? $(o.helper.apply(this.element[0], [event, this.currentItem])) : (o.helper == 'clone' ? this.currentItem.clone() : this.currentItem);
+
+ if(!helper.parents('body').length) //Add the helper to the DOM if that didn't happen already
+ $(o.appendTo != 'parent' ? o.appendTo : this.currentItem[0].parentNode)[0].appendChild(helper[0]);
+
+ if(helper[0] == this.currentItem[0])
+ this._storedCSS = { width: this.currentItem[0].style.width, height: this.currentItem[0].style.height, position: this.currentItem.css("position"), top: this.currentItem.css("top"), left: this.currentItem.css("left") };
+
+ if(helper[0].style.width == '' || o.forceHelperSize) helper.width(this.currentItem.width());
+ if(helper[0].style.height == '' || o.forceHelperSize) helper.height(this.currentItem.height());
+
+ return helper;
+
+ },
+
+ _adjustOffsetFromHelper: function(obj) {
+ if (typeof obj == 'string') {
+ obj = obj.split(' ');
+ }
+ if ($.isArray(obj)) {
+ obj = {left: +obj[0], top: +obj[1] || 0};
+ }
+ if ('left' in obj) {
+ this.offset.click.left = obj.left + this.margins.left;
+ }
+ if ('right' in obj) {
+ this.offset.click.left = this.helperProportions.width - obj.right + this.margins.left;
+ }
+ if ('top' in obj) {
+ this.offset.click.top = obj.top + this.margins.top;
+ }
+ if ('bottom' in obj) {
+ this.offset.click.top = this.helperProportions.height - obj.bottom + this.margins.top;
+ }
+ },
+
+ _getParentOffset: function() {
+
+
+ //Get the offsetParent and cache its position
+ this.offsetParent = this.helper.offsetParent();
+ var po = this.offsetParent.offset();
+
+ // This is a special case where we need to modify a offset calculated on start, since the following happened:
+ // 1. The position of the helper is absolute, so it's position is calculated based on the next positioned parent
+ // 2. The actual offset parent is a child of the scroll parent, and the scroll parent isn't the document, which means that
+ // the scroll is included in the initial calculation of the offset of the parent, and never recalculated upon drag
+ if(this.cssPosition == 'absolute' && this.scrollParent[0] != document && $.contains(this.scrollParent[0], this.offsetParent[0])) {
+ po.left += this.scrollParent.scrollLeft();
+ po.top += this.scrollParent.scrollTop();
+ }
+
+ if((this.offsetParent[0] == document.body) //This needs to be actually done for all browsers, since pageX/pageY includes this information
+ || (this.offsetParent[0].tagName && this.offsetParent[0].tagName.toLowerCase() == 'html' && $.ui.ie)) //Ugly IE fix
+ po = { top: 0, left: 0 };
+
+ return {
+ top: po.top + (parseInt(this.offsetParent.css("borderTopWidth"),10) || 0),
+ left: po.left + (parseInt(this.offsetParent.css("borderLeftWidth"),10) || 0)
+ };
+
+ },
+
+ _getRelativeOffset: function() {
+
+ if(this.cssPosition == "relative") {
+ var p = this.currentItem.position();
+ return {
+ top: p.top - (parseInt(this.helper.css("top"),10) || 0) + this.scrollParent.scrollTop(),
+ left: p.left - (parseInt(this.helper.css("left"),10) || 0) + this.scrollParent.scrollLeft()
+ };
+ } else {
+ return { top: 0, left: 0 };
+ }
+
+ },
+
+ _cacheMargins: function() {
+ this.margins = {
+ left: (parseInt(this.currentItem.css("marginLeft"),10) || 0),
+ top: (parseInt(this.currentItem.css("marginTop"),10) || 0)
+ };
+ },
+
+ _cacheHelperProportions: function() {
+ this.helperProportions = {
+ width: this.helper.outerWidth(),
+ height: this.helper.outerHeight()
+ };
+ },
+
+ _setContainment: function() {
+
+ var o = this.options;
+ if(o.containment == 'parent') o.containment = this.helper[0].parentNode;
+ if(o.containment == 'document' || o.containment == 'window') this.containment = [
+ 0 - this.offset.relative.left - this.offset.parent.left,
+ 0 - this.offset.relative.top - this.offset.parent.top,
+ $(o.containment == 'document' ? document : window).width() - this.helperProportions.width - this.margins.left,
+ ($(o.containment == 'document' ? document : window).height() || document.body.parentNode.scrollHeight) - this.helperProportions.height - this.margins.top
+ ];
+
+ if(!(/^(document|window|parent)$/).test(o.containment)) {
+ var ce = $(o.containment)[0];
+ var co = $(o.containment).offset();
+ var over = ($(ce).css("overflow") != 'hidden');
+
+ this.containment = [
+ co.left + (parseInt($(ce).css("borderLeftWidth"),10) || 0) + (parseInt($(ce).css("paddingLeft"),10) || 0) - this.margins.left,
+ co.top + (parseInt($(ce).css("borderTopWidth"),10) || 0) + (parseInt($(ce).css("paddingTop"),10) || 0) - this.margins.top,
+ co.left+(over ? Math.max(ce.scrollWidth,ce.offsetWidth) : ce.offsetWidth) - (parseInt($(ce).css("borderLeftWidth"),10) || 0) - (parseInt($(ce).css("paddingRight"),10) || 0) - this.helperProportions.width - this.margins.left,
+ co.top+(over ? Math.max(ce.scrollHeight,ce.offsetHeight) : ce.offsetHeight) - (parseInt($(ce).css("borderTopWidth"),10) || 0) - (parseInt($(ce).css("paddingBottom"),10) || 0) - this.helperProportions.height - this.margins.top
+ ];
+ }
+
+ },
+
+ _convertPositionTo: function(d, pos) {
+
+ if(!pos) pos = this.position;
+ var mod = d == "absolute" ? 1 : -1;
+ var o = this.options, scroll = this.cssPosition == 'absolute' && !(this.scrollParent[0] != document && $.contains(this.scrollParent[0], this.offsetParent[0])) ? this.offsetParent : this.scrollParent, scrollIsRootNode = (/(html|body)/i).test(scroll[0].tagName);
+
+ return {
+ top: (
+ pos.top // The absolute mouse position
+ + this.offset.relative.top * mod // Only for relative positioned nodes: Relative offset from element to offset parent
+ + this.offset.parent.top * mod // The offsetParent's offset without borders (offset + border)
+ - ( ( this.cssPosition == 'fixed' ? -this.scrollParent.scrollTop() : ( scrollIsRootNode ? 0 : scroll.scrollTop() ) ) * mod)
+ ),
+ left: (
+ pos.left // The absolute mouse position
+ + this.offset.relative.left * mod // Only for relative positioned nodes: Relative offset from element to offset parent
+ + this.offset.parent.left * mod // The offsetParent's offset without borders (offset + border)
+ - ( ( this.cssPosition == 'fixed' ? -this.scrollParent.scrollLeft() : scrollIsRootNode ? 0 : scroll.scrollLeft() ) * mod)
+ )
+ };
+
+ },
+
+ _generatePosition: function(event) {
+
+ var o = this.options, scroll = this.cssPosition == 'absolute' && !(this.scrollParent[0] != document && $.contains(this.scrollParent[0], this.offsetParent[0])) ? this.offsetParent : this.scrollParent, scrollIsRootNode = (/(html|body)/i).test(scroll[0].tagName);
+
+ // This is another very weird special case that only happens for relative elements:
+ // 1. If the css position is relative
+ // 2. and the scroll parent is the document or similar to the offset parent
+ // we have to refresh the relative offset during the scroll so there are no jumps
+ if(this.cssPosition == 'relative' && !(this.scrollParent[0] != document && this.scrollParent[0] != this.offsetParent[0])) {
+ this.offset.relative = this._getRelativeOffset();
+ }
+
+ var pageX = event.pageX;
+ var pageY = event.pageY;
+
+ /*
+ * - Position constraining -
+ * Constrain the position to a mix of grid, containment.
+ */
+
+ if(this.originalPosition) { //If we are not dragging yet, we won't check for options
+
+ if(this.containment) {
+ if(event.pageX - this.offset.click.left < this.containment[0]) pageX = this.containment[0] + this.offset.click.left;
+ if(event.pageY - this.offset.click.top < this.containment[1]) pageY = this.containment[1] + this.offset.click.top;
+ if(event.pageX - this.offset.click.left > this.containment[2]) pageX = this.containment[2] + this.offset.click.left;
+ if(event.pageY - this.offset.click.top > this.containment[3]) pageY = this.containment[3] + this.offset.click.top;
+ }
+
+ if(o.grid) {
+ var top = this.originalPageY + Math.round((pageY - this.originalPageY) / o.grid[1]) * o.grid[1];
+ pageY = this.containment ? (!(top - this.offset.click.top < this.containment[1] || top - this.offset.click.top > this.containment[3]) ? top : (!(top - this.offset.click.top < this.containment[1]) ? top - o.grid[1] : top + o.grid[1])) : top;
+
+ var left = this.originalPageX + Math.round((pageX - this.originalPageX) / o.grid[0]) * o.grid[0];
+ pageX = this.containment ? (!(left - this.offset.click.left < this.containment[0] || left - this.offset.click.left > this.containment[2]) ? left : (!(left - this.offset.click.left < this.containment[0]) ? left - o.grid[0] : left + o.grid[0])) : left;
+ }
+
+ }
+
+ return {
+ top: (
+ pageY // The absolute mouse position
+ - this.offset.click.top // Click offset (relative to the element)
+ - this.offset.relative.top // Only for relative positioned nodes: Relative offset from element to offset parent
+ - this.offset.parent.top // The offsetParent's offset without borders (offset + border)
+ + ( ( this.cssPosition == 'fixed' ? -this.scrollParent.scrollTop() : ( scrollIsRootNode ? 0 : scroll.scrollTop() ) ))
+ ),
+ left: (
+ pageX // The absolute mouse position
+ - this.offset.click.left // Click offset (relative to the element)
+ - this.offset.relative.left // Only for relative positioned nodes: Relative offset from element to offset parent
+ - this.offset.parent.left // The offsetParent's offset without borders (offset + border)
+ + ( ( this.cssPosition == 'fixed' ? -this.scrollParent.scrollLeft() : scrollIsRootNode ? 0 : scroll.scrollLeft() ))
+ )
+ };
+
+ },
+
+ _rearrange: function(event, i, a, hardRefresh) {
+
+ a ? a[0].appendChild(this.placeholder[0]) : i.item[0].parentNode.insertBefore(this.placeholder[0], (this.direction == 'down' ? i.item[0] : i.item[0].nextSibling));
+
+ //Various things done here to improve the performance:
+ // 1. we create a setTimeout, that calls refreshPositions
+ // 2. on the instance, we have a counter variable, that get's higher after every append
+ // 3. on the local scope, we copy the counter variable, and check in the timeout, if it's still the same
+ // 4. this lets only the last addition to the timeout stack through
+ this.counter = this.counter ? ++this.counter : 1;
+ var counter = this.counter;
+
+ this._delay(function() {
+ if(counter == this.counter) this.refreshPositions(!hardRefresh); //Precompute after each DOM insertion, NOT on mousemove
+ });
+
+ },
+
+ _clear: function(event, noPropagation) {
+
+ this.reverting = false;
+ // We delay all events that have to be triggered to after the point where the placeholder has been removed and
+ // everything else normalized again
+ var delayedTriggers = [];
+
+ // We first have to update the dom position of the actual currentItem
+ // Note: don't do it if the current item is already removed (by a user), or it gets reappended (see #4088)
+ if(!this._noFinalSort && this.currentItem.parent().length) this.placeholder.before(this.currentItem);
+ this._noFinalSort = null;
+
+ if(this.helper[0] == this.currentItem[0]) {
+ for(var i in this._storedCSS) {
+ if(this._storedCSS[i] == 'auto' || this._storedCSS[i] == 'static') this._storedCSS[i] = '';
+ }
+ this.currentItem.css(this._storedCSS).removeClass("ui-sortable-helper");
+ } else {
+ this.currentItem.show();
+ }
+
+ if(this.fromOutside && !noPropagation) delayedTriggers.push(function(event) { this._trigger("receive", event, this._uiHash(this.fromOutside)); });
+ if((this.fromOutside || this.domPosition.prev != this.currentItem.prev().not(".ui-sortable-helper")[0] || this.domPosition.parent != this.currentItem.parent()[0]) && !noPropagation) delayedTriggers.push(function(event) { this._trigger("update", event, this._uiHash()); }); //Trigger update callback if the DOM position has changed
+
+ // Check if the items Container has Changed and trigger appropriate
+ // events.
+ if (this !== this.currentContainer) {
+ if(!noPropagation) {
+ delayedTriggers.push(function(event) { this._trigger("remove", event, this._uiHash()); });
+ delayedTriggers.push((function(c) { return function(event) { c._trigger("receive", event, this._uiHash(this)); }; }).call(this, this.currentContainer));
+ delayedTriggers.push((function(c) { return function(event) { c._trigger("update", event, this._uiHash(this)); }; }).call(this, this.currentContainer));
+ }
+ }
+
+
+ //Post events to containers
+ for (var i = this.containers.length - 1; i >= 0; i--){
+ if(!noPropagation) delayedTriggers.push((function(c) { return function(event) { c._trigger("deactivate", event, this._uiHash(this)); }; }).call(this, this.containers[i]));
+ if(this.containers[i].containerCache.over) {
+ delayedTriggers.push((function(c) { return function(event) { c._trigger("out", event, this._uiHash(this)); }; }).call(this, this.containers[i]));
+ this.containers[i].containerCache.over = 0;
+ }
+ }
+
+ //Do what was originally in plugins
+ if(this._storedCursor) $('body').css("cursor", this._storedCursor); //Reset cursor
+ if(this._storedOpacity) this.helper.css("opacity", this._storedOpacity); //Reset opacity
+ if(this._storedZIndex) this.helper.css("zIndex", this._storedZIndex == 'auto' ? '' : this._storedZIndex); //Reset z-index
+
+ this.dragging = false;
+ if(this.cancelHelperRemoval) {
+ if(!noPropagation) {
+ this._trigger("beforeStop", event, this._uiHash());
+ for (var i=0; i < delayedTriggers.length; i++) { delayedTriggers[i].call(this, event); }; //Trigger all delayed events
+ this._trigger("stop", event, this._uiHash());
+ }
+
+ this.fromOutside = false;
+ return false;
+ }
+
+ if(!noPropagation) this._trigger("beforeStop", event, this._uiHash());
+
+ //$(this.placeholder[0]).remove(); would have been the jQuery way - unfortunately, it unbinds ALL events from the original node!
+ this.placeholder[0].parentNode.removeChild(this.placeholder[0]);
+
+ if(this.helper[0] != this.currentItem[0]) this.helper.remove(); this.helper = null;
+
+ if(!noPropagation) {
+ for (var i=0; i < delayedTriggers.length; i++) { delayedTriggers[i].call(this, event); }; //Trigger all delayed events
+ this._trigger("stop", event, this._uiHash());
+ }
+
+ this.fromOutside = false;
+ return true;
+
+ },
+
+ _trigger: function() {
+ if ($.Widget.prototype._trigger.apply(this, arguments) === false) {
+ this.cancel();
+ }
+ },
+
+ _uiHash: function(_inst) {
+ var inst = _inst || this;
+ return {
+ helper: inst.helper,
+ placeholder: inst.placeholder || $([]),
+ position: inst.position,
+ originalPosition: inst.originalPosition,
+ offset: inst.positionAbs,
+ item: inst.currentItem,
+ sender: _inst ? _inst.element : null
+ };
+ }
+
+});
+
+})(jQuery);
diff --git a/js/jquery/src/jquery-ui/jquery.ui.spinner.js b/js/jquery/src/jquery-ui/jquery.ui.spinner.js
new file mode 100644
index 0000000000..406eefb915
--- /dev/null
+++ b/js/jquery/src/jquery-ui/jquery.ui.spinner.js
@@ -0,0 +1,478 @@
+/*!
+ * jQuery UI Spinner @VERSION
+ * http://jqueryui.com
+ *
+ * Copyright 2012 jQuery Foundation and other contributors
+ * Released under the MIT license.
+ * http://jquery.org/license
+ *
+ * http://api.jqueryui.com/spinner/
+ *
+ * Depends:
+ * jquery.ui.core.js
+ * jquery.ui.widget.js
+ * jquery.ui.button.js
+ */
+(function( $ ) {
+
+function modifier( fn ) {
+ return function() {
+ var previous = this.element.val();
+ fn.apply( this, arguments );
+ this._refresh();
+ if ( previous !== this.element.val() ) {
+ this._trigger( "change" );
+ }
+ };
+}
+
+$.widget( "ui.spinner", {
+ version: "@VERSION",
+ defaultElement: "<input>",
+ widgetEventPrefix: "spin",
+ options: {
+ culture: null,
+ icons: {
+ down: "ui-icon-triangle-1-s",
+ up: "ui-icon-triangle-1-n"
+ },
+ incremental: true,
+ max: null,
+ min: null,
+ numberFormat: null,
+ page: 10,
+ step: 1,
+
+ change: null,
+ spin: null,
+ start: null,
+ stop: null
+ },
+
+ _create: function() {
+ // handle string values that need to be parsed
+ this._setOption( "max", this.options.max );
+ this._setOption( "min", this.options.min );
+ this._setOption( "step", this.options.step );
+
+ // format the value, but don't constrain
+ this._value( this.element.val(), true );
+
+ this._draw();
+ this._on( this._events );
+ this._refresh();
+
+ // turning off autocomplete prevents the browser from remembering the
+ // value when navigating through history, so we re-enable autocomplete
+ // if the page is unloaded before the widget is destroyed. #7790
+ this._on( this.window, {
+ beforeunload: function() {
+ this.element.removeAttr( "autocomplete" );
+ }
+ });
+ },
+
+ _getCreateOptions: function() {
+ var options = {},
+ element = this.element;
+
+ $.each( [ "min", "max", "step" ], function( i, option ) {
+ var value = element.attr( option );
+ if ( value !== undefined && value.length ) {
+ options[ option ] = value;
+ }
+ });
+
+ return options;
+ },
+
+ _events: {
+ keydown: function( event ) {
+ if ( this._start( event ) && this._keydown( event ) ) {
+ event.preventDefault();
+ }
+ },
+ keyup: "_stop",
+ focus: function() {
+ this.previous = this.element.val();
+ },
+ blur: function( event ) {
+ if ( this.cancelBlur ) {
+ delete this.cancelBlur;
+ return;
+ }
+
+ this._refresh();
+ if ( this.previous !== this.element.val() ) {
+ this._trigger( "change", event );
+ }
+ },
+ mousewheel: function( event, delta ) {
+ if ( !delta ) {
+ return;
+ }
+ if ( !this.spinning && !this._start( event ) ) {
+ return false;
+ }
+
+ this._spin( (delta > 0 ? 1 : -1) * this.options.step, event );
+ clearTimeout( this.mousewheelTimer );
+ this.mousewheelTimer = this._delay(function() {
+ if ( this.spinning ) {
+ this._stop( event );
+ }
+ }, 100 );
+ event.preventDefault();
+ },
+ "mousedown .ui-spinner-button": function( event ) {
+ var previous;
+
+ // We never want the buttons to have focus; whenever the user is
+ // interacting with the spinner, the focus should be on the input.
+ // If the input is focused then this.previous is properly set from
+ // when the input first received focus. If the input is not focused
+ // then we need to set this.previous based on the value before spinning.
+ previous = this.element[0] === this.document[0].activeElement ?
+ this.previous : this.element.val();
+ function checkFocus() {
+ var isActive = this.element[0] === this.document[0].activeElement;
+ if ( !isActive ) {
+ this.element.focus();
+ this.previous = previous;
+ // support: IE
+ // IE sets focus asynchronously, so we need to check if focus
+ // moved off of the input because the user clicked on the button.
+ this._delay(function() {
+ this.previous = previous;
+ });
+ }
+ }
+
+ // ensure focus is on (or stays on) the text field
+ event.preventDefault();
+ checkFocus.call( this );
+
+ // support: IE
+ // IE doesn't prevent moving focus even with event.preventDefault()
+ // so we set a flag to know when we should ignore the blur event
+ // and check (again) if focus moved off of the input.
+ this.cancelBlur = true;
+ this._delay(function() {
+ delete this.cancelBlur;
+ checkFocus.call( this );
+ });
+
+ if ( this._start( event ) === false ) {
+ return;
+ }
+
+ this._repeat( null, $( event.currentTarget ).hasClass( "ui-spinner-up" ) ? 1 : -1, event );
+ },
+ "mouseup .ui-spinner-button": "_stop",
+ "mouseenter .ui-spinner-button": function( event ) {
+ // button will add ui-state-active if mouse was down while mouseleave and kept down
+ if ( !$( event.currentTarget ).hasClass( "ui-state-active" ) ) {
+ return;
+ }
+
+ if ( this._start( event ) === false ) {
+ return false;
+ }
+ this._repeat( null, $( event.currentTarget ).hasClass( "ui-spinner-up" ) ? 1 : -1, event );
+ },
+ // TODO: do we really want to consider this a stop?
+ // shouldn't we just stop the repeater and wait until mouseup before
+ // we trigger the stop event?
+ "mouseleave .ui-spinner-button": "_stop"
+ },
+
+ _draw: function() {
+ var uiSpinner = this.uiSpinner = this.element
+ .addClass( "ui-spinner-input" )
+ .attr( "autocomplete", "off" )
+ .wrap( this._uiSpinnerHtml() )
+ .parent()
+ // add buttons
+ .append( this._buttonHtml() );
+
+ this.element.attr( "role", "spinbutton" );
+
+ // button bindings
+ this.buttons = uiSpinner.find( ".ui-spinner-button" )
+ .attr( "tabIndex", -1 )
+ .button()
+ .removeClass( "ui-corner-all" );
+
+ // IE 6 doesn't understand height: 50% for the buttons
+ // unless the wrapper has an explicit height
+ if ( this.buttons.height() > Math.ceil( uiSpinner.height() * 0.5 ) &&
+ uiSpinner.height() > 0 ) {
+ uiSpinner.height( uiSpinner.height() );
+ }
+
+ // disable spinner if element was already disabled
+ if ( this.options.disabled ) {
+ this.disable();
+ }
+ },
+
+ _keydown: function( event ) {
+ var options = this.options,
+ keyCode = $.ui.keyCode;
+
+ switch ( event.keyCode ) {
+ case keyCode.UP:
+ this._repeat( null, 1, event );
+ return true;
+ case keyCode.DOWN:
+ this._repeat( null, -1, event );
+ return true;
+ case keyCode.PAGE_UP:
+ this._repeat( null, options.page, event );
+ return true;
+ case keyCode.PAGE_DOWN:
+ this._repeat( null, -options.page, event );
+ return true;
+ }
+
+ return false;
+ },
+
+ _uiSpinnerHtml: function() {
+ return "<span class='ui-spinner ui-widget ui-widget-content ui-corner-all'></span>";
+ },
+
+ _buttonHtml: function() {
+ return "" +
+ "<a class='ui-spinner-button ui-spinner-up ui-corner-tr'>" +
+ "<span class='ui-icon " + this.options.icons.up + "'>&#9650;</span>" +
+ "</a>" +
+ "<a class='ui-spinner-button ui-spinner-down ui-corner-br'>" +
+ "<span class='ui-icon " + this.options.icons.down + "'>&#9660;</span>" +
+ "</a>";
+ },
+
+ _start: function( event ) {
+ if ( !this.spinning && this._trigger( "start", event ) === false ) {
+ return false;
+ }
+
+ if ( !this.counter ) {
+ this.counter = 1;
+ }
+ this.spinning = true;
+ return true;
+ },
+
+ _repeat: function( i, steps, event ) {
+ i = i || 500;
+
+ clearTimeout( this.timer );
+ this.timer = this._delay(function() {
+ this._repeat( 40, steps, event );
+ }, i );
+
+ this._spin( steps * this.options.step, event );
+ },
+
+ _spin: function( step, event ) {
+ var value = this.value() || 0;
+
+ if ( !this.counter ) {
+ this.counter = 1;
+ }
+
+ value = this._adjustValue( value + step * this._increment( this.counter ) );
+
+ if ( !this.spinning || this._trigger( "spin", event, { value: value } ) !== false) {
+ this._value( value );
+ this.counter++;
+ }
+ },
+
+ _increment: function( i ) {
+ var incremental = this.options.incremental;
+
+ if ( incremental ) {
+ return $.isFunction( incremental ) ?
+ incremental( i ) :
+ Math.floor( i*i*i/50000 - i*i/500 + 17*i/200 + 1 );
+ }
+
+ return 1;
+ },
+
+ _precision: function() {
+ var precision = this._precisionOf( this.options.step );
+ if ( this.options.min !== null ) {
+ precision = Math.max( precision, this._precisionOf( this.options.min ) );
+ }
+ return precision;
+ },
+
+ _precisionOf: function( num ) {
+ var str = num.toString(),
+ decimal = str.indexOf( "." );
+ return decimal === -1 ? 0 : str.length - decimal - 1;
+ },
+
+ _adjustValue: function( value ) {
+ var base, aboveMin,
+ options = this.options;
+
+ // make sure we're at a valid step
+ // - find out where we are relative to the base (min or 0)
+ base = options.min !== null ? options.min : 0;
+ aboveMin = value - base;
+ // - round to the nearest step
+ aboveMin = Math.round(aboveMin / options.step) * options.step;
+ // - rounding is based on 0, so adjust back to our base
+ value = base + aboveMin;
+
+ // fix precision from bad JS floating point math
+ value = parseFloat( value.toFixed( this._precision() ) );
+
+ // clamp the value
+ if ( options.max !== null && value > options.max) {
+ return options.max;
+ }
+ if ( options.min !== null && value < options.min ) {
+ return options.min;
+ }
+
+ return value;
+ },
+
+ _stop: function( event ) {
+ if ( !this.spinning ) {
+ return;
+ }
+
+ clearTimeout( this.timer );
+ clearTimeout( this.mousewheelTimer );
+ this.counter = 0;
+ this.spinning = false;
+ this._trigger( "stop", event );
+ },
+
+ _setOption: function( key, value ) {
+ if ( key === "culture" || key === "numberFormat" ) {
+ var prevValue = this._parse( this.element.val() );
+ this.options[ key ] = value;
+ this.element.val( this._format( prevValue ) );
+ return;
+ }
+
+ if ( key === "max" || key === "min" || key === "step" ) {
+ if ( typeof value === "string" ) {
+ value = this._parse( value );
+ }
+ }
+
+ this._super( key, value );
+
+ if ( key === "disabled" ) {
+ if ( value ) {
+ this.element.prop( "disabled", true );
+ this.buttons.button( "disable" );
+ } else {
+ this.element.prop( "disabled", false );
+ this.buttons.button( "enable" );
+ }
+ }
+ },
+
+ _setOptions: modifier(function( options ) {
+ this._super( options );
+ this._value( this.element.val() );
+ }),
+
+ _parse: function( val ) {
+ if ( typeof val === "string" && val !== "" ) {
+ val = window.Globalize && this.options.numberFormat ?
+ Globalize.parseFloat( val, 10, this.options.culture ) : +val;
+ }
+ return val === "" || isNaN( val ) ? null : val;
+ },
+
+ _format: function( value ) {
+ if ( value === "" ) {
+ return "";
+ }
+ return window.Globalize && this.options.numberFormat ?
+ Globalize.format( value, this.options.numberFormat, this.options.culture ) :
+ value;
+ },
+
+ _refresh: function() {
+ this.element.attr({
+ "aria-valuemin": this.options.min,
+ "aria-valuemax": this.options.max,
+ // TODO: what should we do with values that can't be parsed?
+ "aria-valuenow": this._parse( this.element.val() )
+ });
+ },
+
+ // update the value without triggering change
+ _value: function( value, allowAny ) {
+ var parsed;
+ if ( value !== "" ) {
+ parsed = this._parse( value );
+ if ( parsed !== null ) {
+ if ( !allowAny ) {
+ parsed = this._adjustValue( parsed );
+ }
+ value = this._format( parsed );
+ }
+ }
+ this.element.val( value );
+ this._refresh();
+ },
+
+ _destroy: function() {
+ this.element
+ .removeClass( "ui-spinner-input" )
+ .prop( "disabled", false )
+ .removeAttr( "autocomplete" )
+ .removeAttr( "role" )
+ .removeAttr( "aria-valuemin" )
+ .removeAttr( "aria-valuemax" )
+ .removeAttr( "aria-valuenow" );
+ this.uiSpinner.replaceWith( this.element );
+ },
+
+ stepUp: modifier(function( steps ) {
+ this._stepUp( steps );
+ }),
+ _stepUp: function( steps ) {
+ this._spin( (steps || 1) * this.options.step );
+ },
+
+ stepDown: modifier(function( steps ) {
+ this._stepDown( steps );
+ }),
+ _stepDown: function( steps ) {
+ this._spin( (steps || 1) * -this.options.step );
+ },
+
+ pageUp: modifier(function( pages ) {
+ this._stepUp( (pages || 1) * this.options.page );
+ }),
+
+ pageDown: modifier(function( pages ) {
+ this._stepDown( (pages || 1) * this.options.page );
+ }),
+
+ value: function( newVal ) {
+ if ( !arguments.length ) {
+ return this._parse( this.element.val() );
+ }
+ modifier( this._value ).call( this, newVal );
+ },
+
+ widget: function() {
+ return this.uiSpinner;
+ }
+});
+
+}( jQuery ) );
diff --git a/js/jquery/src/jquery-ui/jquery.ui.tabs.js b/js/jquery/src/jquery-ui/jquery.ui.tabs.js
new file mode 100644
index 0000000000..8e09764b79
--- /dev/null
+++ b/js/jquery/src/jquery-ui/jquery.ui.tabs.js
@@ -0,0 +1,1366 @@
+/*!
+ * jQuery UI Tabs @VERSION
+ * http://jqueryui.com
+ *
+ * Copyright 2012 jQuery Foundation and other contributors
+ * Released under the MIT license.
+ * http://jquery.org/license
+ *
+ * http://api.jqueryui.com/tabs/
+ *
+ * Depends:
+ * jquery.ui.core.js
+ * jquery.ui.widget.js
+ */
+(function( $, undefined ) {
+
+var tabId = 0,
+ rhash = /#.*$/;
+
+function getNextTabId() {
+ return ++tabId;
+}
+
+function isLocal( anchor ) {
+ return anchor.hash.length > 1 &&
+ anchor.href.replace( rhash, "" ) ===
+ location.href.replace( rhash, "" )
+ // support: Safari 5.1
+ // Safari 5.1 doesn't encode spaces in window.location
+ // but it does encode spaces from anchors (#8777)
+ .replace( /\s/g, "%20" );
+}
+
+$.widget( "ui.tabs", {
+ version: "@VERSION",
+ delay: 300,
+ options: {
+ active: null,
+ collapsible: false,
+ event: "click",
+ heightStyle: "content",
+ hide: null,
+ show: null,
+
+ // callbacks
+ activate: null,
+ beforeActivate: null,
+ beforeLoad: null,
+ load: null
+ },
+
+ _create: function() {
+ var that = this,
+ options = this.options,
+ active = options.active,
+ locationHash = location.hash.substring( 1 );
+
+ this.running = false;
+
+ this.element
+ .addClass( "ui-tabs ui-widget ui-widget-content ui-corner-all" )
+ .toggleClass( "ui-tabs-collapsible", options.collapsible )
+ // Prevent users from focusing disabled tabs via click
+ .delegate( ".ui-tabs-nav > li", "mousedown" + this.eventNamespace, function( event ) {
+ if ( $( this ).is( ".ui-state-disabled" ) ) {
+ event.preventDefault();
+ }
+ })
+ // support: IE <9
+ // Preventing the default action in mousedown doesn't prevent IE
+ // from focusing the element, so if the anchor gets focused, blur.
+ // We don't have to worry about focusing the previously focused
+ // element since clicking on a non-focusable element should focus
+ // the body anyway.
+ .delegate( ".ui-tabs-anchor", "focus" + this.eventNamespace, function() {
+ if ( $( this ).closest( "li" ).is( ".ui-state-disabled" ) ) {
+ this.blur();
+ }
+ });
+
+ this._processTabs();
+
+ if ( active === null ) {
+ // check the fragment identifier in the URL
+ if ( locationHash ) {
+ this.tabs.each(function( i, tab ) {
+ if ( $( tab ).attr( "aria-controls" ) === locationHash ) {
+ active = i;
+ return false;
+ }
+ });
+ }
+
+ // check for a tab marked active via a class
+ if ( active === null ) {
+ active = this.tabs.index( this.tabs.filter( ".ui-tabs-active" ) );
+ }
+
+ // no active tab, set to false
+ if ( active === null || active === -1 ) {
+ active = this.tabs.length ? 0 : false;
+ }
+ }
+
+ // handle numbers: negative, out of range
+ if ( active !== false ) {
+ active = this.tabs.index( this.tabs.eq( active ) );
+ if ( active === -1 ) {
+ active = options.collapsible ? false : 0;
+ }
+ }
+ options.active = active;
+
+ // don't allow collapsible: false and active: false
+ if ( !options.collapsible && options.active === false && this.anchors.length ) {
+ options.active = 0;
+ }
+
+ // Take disabling tabs via class attribute from HTML
+ // into account and update option properly.
+ if ( $.isArray( options.disabled ) ) {
+ options.disabled = $.unique( options.disabled.concat(
+ $.map( this.tabs.filter( ".ui-state-disabled" ), function( li ) {
+ return that.tabs.index( li );
+ })
+ ) ).sort();
+ }
+
+ // check for length avoids error when initializing empty list
+ if ( this.options.active !== false && this.anchors.length ) {
+ this.active = this._findActive( this.options.active );
+ } else {
+ this.active = $();
+ }
+
+ this._refresh();
+
+ if ( this.active.length ) {
+ this.load( options.active );
+ }
+ },
+
+ _getCreateEventData: function() {
+ return {
+ tab: this.active,
+ panel: !this.active.length ? $() : this._getPanelForTab( this.active )
+ };
+ },
+
+ _tabKeydown: function( event ) {
+ var focusedTab = $( this.document[0].activeElement ).closest( "li" ),
+ selectedIndex = this.tabs.index( focusedTab ),
+ goingForward = true;
+
+ if ( this._handlePageNav( event ) ) {
+ return;
+ }
+
+ switch ( event.keyCode ) {
+ case $.ui.keyCode.RIGHT:
+ case $.ui.keyCode.DOWN:
+ selectedIndex++;
+ break;
+ case $.ui.keyCode.UP:
+ case $.ui.keyCode.LEFT:
+ goingForward = false;
+ selectedIndex--;
+ break;
+ case $.ui.keyCode.END:
+ selectedIndex = this.anchors.length - 1;
+ break;
+ case $.ui.keyCode.HOME:
+ selectedIndex = 0;
+ break;
+ case $.ui.keyCode.SPACE:
+ // Activate only, no collapsing
+ event.preventDefault();
+ clearTimeout( this.activating );
+ this._activate( selectedIndex );
+ return;
+ case $.ui.keyCode.ENTER:
+ // Toggle (cancel delayed activation, allow collapsing)
+ event.preventDefault();
+ clearTimeout( this.activating );
+ // Determine if we should collapse or activate
+ this._activate( selectedIndex === this.options.active ? false : selectedIndex );
+ return;
+ default:
+ return;
+ }
+
+ // Focus the appropriate tab, based on which key was pressed
+ event.preventDefault();
+ clearTimeout( this.activating );
+ selectedIndex = this._focusNextTab( selectedIndex, goingForward );
+
+ // Navigating with control key will prevent automatic activation
+ if ( !event.ctrlKey ) {
+ // Update aria-selected immediately so that AT think the tab is already selected.
+ // Otherwise AT may confuse the user by stating that they need to activate the tab,
+ // but the tab will already be activated by the time the announcement finishes.
+ focusedTab.attr( "aria-selected", "false" );
+ this.tabs.eq( selectedIndex ).attr( "aria-selected", "true" );
+
+ this.activating = this._delay(function() {
+ this.option( "active", selectedIndex );
+ }, this.delay );
+ }
+ },
+
+ _panelKeydown: function( event ) {
+ if ( this._handlePageNav( event ) ) {
+ return;
+ }
+
+ // Ctrl+up moves focus to the current tab
+ if ( event.ctrlKey && event.keyCode === $.ui.keyCode.UP ) {
+ event.preventDefault();
+ this.active.focus();
+ }
+ },
+
+ // Alt+page up/down moves focus to the previous/next tab (and activates)
+ _handlePageNav: function( event ) {
+ if ( event.altKey && event.keyCode === $.ui.keyCode.PAGE_UP ) {
+ this._activate( this._focusNextTab( this.options.active - 1, false ) );
+ return true;
+ }
+ if ( event.altKey && event.keyCode === $.ui.keyCode.PAGE_DOWN ) {
+ this._activate( this._focusNextTab( this.options.active + 1, true ) );
+ return true;
+ }
+ },
+
+ _findNextTab: function( index, goingForward ) {
+ var lastTabIndex = this.tabs.length - 1;
+
+ function constrain() {
+ if ( index > lastTabIndex ) {
+ index = 0;
+ }
+ if ( index < 0 ) {
+ index = lastTabIndex;
+ }
+ return index;
+ }
+
+ while ( $.inArray( constrain(), this.options.disabled ) !== -1 ) {
+ index = goingForward ? index + 1 : index - 1;
+ }
+
+ return index;
+ },
+
+ _focusNextTab: function( index, goingForward ) {
+ index = this._findNextTab( index, goingForward );
+ this.tabs.eq( index ).focus();
+ return index;
+ },
+
+ _setOption: function( key, value ) {
+ if ( key === "active" ) {
+ // _activate() will handle invalid values and update this.options
+ this._activate( value );
+ return;
+ }
+
+ if ( key === "disabled" ) {
+ // don't use the widget factory's disabled handling
+ this._setupDisabled( value );
+ return;
+ }
+
+ this._super( key, value);
+
+ if ( key === "collapsible" ) {
+ this.element.toggleClass( "ui-tabs-collapsible", value );
+ // Setting collapsible: false while collapsed; open first panel
+ if ( !value && this.options.active === false ) {
+ this._activate( 0 );
+ }
+ }
+
+ if ( key === "event" ) {
+ this._setupEvents( value );
+ }
+
+ if ( key === "heightStyle" ) {
+ this._setupHeightStyle( value );
+ }
+ },
+
+ _tabId: function( tab ) {
+ return tab.attr( "aria-controls" ) || "ui-tabs-" + getNextTabId();
+ },
+
+ _sanitizeSelector: function( hash ) {
+ return hash ? hash.replace( /[!"$%&'()*+,.\/:;<=>?@\[\]\^`{|}~]/g, "\\$&" ) : "";
+ },
+
+ refresh: function() {
+ var options = this.options,
+ lis = this.tablist.children( ":has(a[href])" );
+
+ // get disabled tabs from class attribute from HTML
+ // this will get converted to a boolean if needed in _refresh()
+ options.disabled = $.map( lis.filter( ".ui-state-disabled" ), function( tab ) {
+ return lis.index( tab );
+ });
+
+ this._processTabs();
+
+ // was collapsed or no tabs
+ if ( options.active === false || !this.anchors.length ) {
+ options.active = false;
+ this.active = $();
+ // was active, but active tab is gone
+ } else if ( this.active.length && !$.contains( this.tablist[ 0 ], this.active[ 0 ] ) ) {
+ // all remaining tabs are disabled
+ if ( this.tabs.length === options.disabled.length ) {
+ options.active = false;
+ this.active = $();
+ // activate previous tab
+ } else {
+ this._activate( this._findNextTab( Math.max( 0, options.active - 1 ), false ) );
+ }
+ // was active, active tab still exists
+ } else {
+ // make sure active index is correct
+ options.active = this.tabs.index( this.active );
+ }
+
+ this._refresh();
+ },
+
+ _refresh: function() {
+ this._setupDisabled( this.options.disabled );
+ this._setupEvents( this.options.event );
+ this._setupHeightStyle( this.options.heightStyle );
+
+ this.tabs.not( this.active ).attr({
+ "aria-selected": "false",
+ tabIndex: -1
+ });
+ this.panels.not( this._getPanelForTab( this.active ) )
+ .hide()
+ .attr({
+ "aria-expanded": "false",
+ "aria-hidden": "true"
+ });
+
+ // Make sure one tab is in the tab order
+ if ( !this.active.length ) {
+ this.tabs.eq( 0 ).attr( "tabIndex", 0 );
+ } else {
+ this.active
+ .addClass( "ui-tabs-active ui-state-active" )
+ .attr({
+ "aria-selected": "true",
+ tabIndex: 0
+ });
+ this._getPanelForTab( this.active )
+ .show()
+ .attr({
+ "aria-expanded": "true",
+ "aria-hidden": "false"
+ });
+ }
+ },
+
+ _processTabs: function() {
+ var that = this;
+
+ this.tablist = this._getList()
+ .addClass( "ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all" )
+ .attr( "role", "tablist" );
+
+ this.tabs = this.tablist.find( "> li:has(a[href])" )
+ .addClass( "ui-state-default ui-corner-top" )
+ .attr({
+ role: "tab",
+ tabIndex: -1
+ });
+
+ this.anchors = this.tabs.map(function() {
+ return $( "a", this )[ 0 ];
+ })
+ .addClass( "ui-tabs-anchor" )
+ .attr({
+ role: "presentation",
+ tabIndex: -1
+ });
+
+ this.panels = $();
+
+ this.anchors.each(function( i, anchor ) {
+ var selector, panel, panelId,
+ anchorId = $( anchor ).uniqueId().attr( "id" ),
+ tab = $( anchor ).closest( "li" ),
+ originalAriaControls = tab.attr( "aria-controls" );
+
+ // inline tab
+ if ( isLocal( anchor ) ) {
+ selector = anchor.hash;
+ panel = that.element.find( that._sanitizeSelector( selector ) );
+ // remote tab
+ } else {
+ panelId = that._tabId( tab );
+ selector = "#" + panelId;
+ panel = that.element.find( selector );
+ if ( !panel.length ) {
+ panel = that._createPanel( panelId );
+ panel.insertAfter( that.panels[ i - 1 ] || that.tablist );
+ }
+ panel.attr( "aria-live", "polite" );
+ }
+
+ if ( panel.length) {
+ that.panels = that.panels.add( panel );
+ }
+ if ( originalAriaControls ) {
+ tab.data( "ui-tabs-aria-controls", originalAriaControls );
+ }
+ tab.attr({
+ "aria-controls": selector.substring( 1 ),
+ "aria-labelledby": anchorId
+ });
+ panel.attr( "aria-labelledby", anchorId );
+ });
+
+ this.panels
+ .addClass( "ui-tabs-panel ui-widget-content ui-corner-bottom" )
+ .attr( "role", "tabpanel" );
+ },
+
+ // allow overriding how to find the list for rare usage scenarios (#7715)
+ _getList: function() {
+ return this.element.find( "ol,ul" ).eq( 0 );
+ },
+
+ _createPanel: function( id ) {
+ return $( "<div>" )
+ .attr( "id", id )
+ .addClass( "ui-tabs-panel ui-widget-content ui-corner-bottom" )
+ .data( "ui-tabs-destroy", true );
+ },
+
+ _setupDisabled: function( disabled ) {
+ if ( $.isArray( disabled ) ) {
+ if ( !disabled.length ) {
+ disabled = false;
+ } else if ( disabled.length === this.anchors.length ) {
+ disabled = true;
+ }
+ }
+
+ // disable tabs
+ for ( var i = 0, li; ( li = this.tabs[ i ] ); i++ ) {
+ if ( disabled === true || $.inArray( i, disabled ) !== -1 ) {
+ $( li )
+ .addClass( "ui-state-disabled" )
+ .attr( "aria-disabled", "true" );
+ } else {
+ $( li )
+ .removeClass( "ui-state-disabled" )
+ .removeAttr( "aria-disabled" );
+ }
+ }
+
+ this.options.disabled = disabled;
+ },
+
+ _setupEvents: function( event ) {
+ var events = {
+ click: function( event ) {
+ event.preventDefault();
+ }
+ };
+ if ( event ) {
+ $.each( event.split(" "), function( index, eventName ) {
+ events[ eventName ] = "_eventHandler";
+ });
+ }
+
+ this._off( this.anchors.add( this.tabs ).add( this.panels ) );
+ this._on( this.anchors, events );
+ this._on( this.tabs, { keydown: "_tabKeydown" } );
+ this._on( this.panels, { keydown: "_panelKeydown" } );
+
+ this._focusable( this.tabs );
+ this._hoverable( this.tabs );
+ },
+
+ _setupHeightStyle: function( heightStyle ) {
+ var maxHeight, overflow,
+ parent = this.element.parent();
+
+ if ( heightStyle === "fill" ) {
+ // IE 6 treats height like minHeight, so we need to turn off overflow
+ // in order to get a reliable height
+ // we use the minHeight support test because we assume that only
+ // browsers that don't support minHeight will treat height as minHeight
+ if ( !$.support.minHeight ) {
+ overflow = parent.css( "overflow" );
+ parent.css( "overflow", "hidden");
+ }
+ maxHeight = parent.height();
+ this.element.siblings( ":visible" ).each(function() {
+ var elem = $( this ),
+ position = elem.css( "position" );
+
+ if ( position === "absolute" || position === "fixed" ) {
+ return;
+ }
+ maxHeight -= elem.outerHeight( true );
+ });
+ if ( overflow ) {
+ parent.css( "overflow", overflow );
+ }
+
+ this.element.children().not( this.panels ).each(function() {
+ maxHeight -= $( this ).outerHeight( true );
+ });
+
+ this.panels.each(function() {
+ $( this ).height( Math.max( 0, maxHeight -
+ $( this ).innerHeight() + $( this ).height() ) );
+ })
+ .css( "overflow", "auto" );
+ } else if ( heightStyle === "auto" ) {
+ maxHeight = 0;
+ this.panels.each(function() {
+ maxHeight = Math.max( maxHeight, $( this ).height( "" ).height() );
+ }).height( maxHeight );
+ }
+ },
+
+ _eventHandler: function( event ) {
+ var options = this.options,
+ active = this.active,
+ anchor = $( event.currentTarget ),
+ tab = anchor.closest( "li" ),
+ clickedIsActive = tab[ 0 ] === active[ 0 ],
+ collapsing = clickedIsActive && options.collapsible,
+ toShow = collapsing ? $() : this._getPanelForTab( tab ),
+ toHide = !active.length ? $() : this._getPanelForTab( active ),
+ eventData = {
+ oldTab: active,
+ oldPanel: toHide,
+ newTab: collapsing ? $() : tab,
+ newPanel: toShow
+ };
+
+ event.preventDefault();
+
+ if ( tab.hasClass( "ui-state-disabled" ) ||
+ // tab is already loading
+ tab.hasClass( "ui-tabs-loading" ) ||
+ // can't switch durning an animation
+ this.running ||
+ // click on active header, but not collapsible
+ ( clickedIsActive && !options.collapsible ) ||
+ // allow canceling activation
+ ( this._trigger( "beforeActivate", event, eventData ) === false ) ) {
+ return;
+ }
+
+ options.active = collapsing ? false : this.tabs.index( tab );
+
+ this.active = clickedIsActive ? $() : tab;
+ if ( this.xhr ) {
+ this.xhr.abort();
+ }
+
+ if ( !toHide.length && !toShow.length ) {
+ $.error( "jQuery UI Tabs: Mismatching fragment identifier." );
+ }
+
+ if ( toShow.length ) {
+ this.load( this.tabs.index( tab ), event );
+ }
+ this._toggle( event, eventData );
+ },
+
+ // handles show/hide for selecting tabs
+ _toggle: function( event, eventData ) {
+ var that = this,
+ toShow = eventData.newPanel,
+ toHide = eventData.oldPanel;
+
+ this.running = true;
+
+ function complete() {
+ that.running = false;
+ that._trigger( "activate", event, eventData );
+ }
+
+ function show() {
+ eventData.newTab.closest( "li" ).addClass( "ui-tabs-active ui-state-active" );
+
+ if ( toShow.length && that.options.show ) {
+ that._show( toShow, that.options.show, complete );
+ } else {
+ toShow.show();
+ complete();
+ }
+ }
+
+ // start out by hiding, then showing, then completing
+ if ( toHide.length && this.options.hide ) {
+ this._hide( toHide, this.options.hide, function() {
+ eventData.oldTab.closest( "li" ).removeClass( "ui-tabs-active ui-state-active" );
+ show();
+ });
+ } else {
+ eventData.oldTab.closest( "li" ).removeClass( "ui-tabs-active ui-state-active" );
+ toHide.hide();
+ show();
+ }
+
+ toHide.attr({
+ "aria-expanded": "false",
+ "aria-hidden": "true"
+ });
+ eventData.oldTab.attr( "aria-selected", "false" );
+ // If we're switching tabs, remove the old tab from the tab order.
+ // If we're opening from collapsed state, remove the previous tab from the tab order.
+ // If we're collapsing, then keep the collapsing tab in the tab order.
+ if ( toShow.length && toHide.length ) {
+ eventData.oldTab.attr( "tabIndex", -1 );
+ } else if ( toShow.length ) {
+ this.tabs.filter(function() {
+ return $( this ).attr( "tabIndex" ) === 0;
+ })
+ .attr( "tabIndex", -1 );
+ }
+
+ toShow.attr({
+ "aria-expanded": "true",
+ "aria-hidden": "false"
+ });
+ eventData.newTab.attr({
+ "aria-selected": "true",
+ tabIndex: 0
+ });
+ },
+
+ _activate: function( index ) {
+ var anchor,
+ active = this._findActive( index );
+
+ // trying to activate the already active panel
+ if ( active[ 0 ] === this.active[ 0 ] ) {
+ return;
+ }
+
+ // trying to collapse, simulate a click on the current active header
+ if ( !active.length ) {
+ active = this.active;
+ }
+
+ anchor = active.find( ".ui-tabs-anchor" )[ 0 ];
+ this._eventHandler({
+ target: anchor,
+ currentTarget: anchor,
+ preventDefault: $.noop
+ });
+ },
+
+ _findActive: function( index ) {
+ return index === false ? $() : this.tabs.eq( index );
+ },
+
+ _getIndex: function( index ) {
+ // meta-function to give users option to provide a href string instead of a numerical index.
+ if ( typeof index === "string" ) {
+ index = this.anchors.index( this.anchors.filter( "[href$='" + index + "']" ) );
+ }
+
+ return index;
+ },
+
+ _destroy: function() {
+ if ( this.xhr ) {
+ this.xhr.abort();
+ }
+
+ this.element.removeClass( "ui-tabs ui-widget ui-widget-content ui-corner-all ui-tabs-collapsible" );
+
+ this.tablist
+ .removeClass( "ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all" )
+ .removeAttr( "role" );
+
+ this.anchors
+ .removeClass( "ui-tabs-anchor" )
+ .removeAttr( "role" )
+ .removeAttr( "tabIndex" )
+ .removeData( "href.tabs" )
+ .removeData( "load.tabs" )
+ .removeUniqueId();
+
+ this.tabs.add( this.panels ).each(function() {
+ if ( $.data( this, "ui-tabs-destroy" ) ) {
+ $( this ).remove();
+ } else {
+ $( this )
+ .removeClass( "ui-state-default ui-state-active ui-state-disabled " +
+ "ui-corner-top ui-corner-bottom ui-widget-content ui-tabs-active ui-tabs-panel" )
+ .removeAttr( "tabIndex" )
+ .removeAttr( "aria-live" )
+ .removeAttr( "aria-busy" )
+ .removeAttr( "aria-selected" )
+ .removeAttr( "aria-labelledby" )
+ .removeAttr( "aria-hidden" )
+ .removeAttr( "aria-expanded" )
+ .removeAttr( "role" );
+ }
+ });
+
+ this.tabs.each(function() {
+ var li = $( this ),
+ prev = li.data( "ui-tabs-aria-controls" );
+ if ( prev ) {
+ li.attr( "aria-controls", prev );
+ } else {
+ li.removeAttr( "aria-controls" );
+ }
+ });
+
+ this.panels.show();
+
+ if ( this.options.heightStyle !== "content" ) {
+ this.panels.css( "height", "" );
+ }
+ },
+
+ enable: function( index ) {
+ var disabled = this.options.disabled;
+ if ( disabled === false ) {
+ return;
+ }
+
+ if ( index === undefined ) {
+ disabled = false;
+ } else {
+ index = this._getIndex( index );
+ if ( $.isArray( disabled ) ) {
+ disabled = $.map( disabled, function( num ) {
+ return num !== index ? num : null;
+ });
+ } else {
+ disabled = $.map( this.tabs, function( li, num ) {
+ return num !== index ? num : null;
+ });
+ }
+ }
+ this._setupDisabled( disabled );
+ },
+
+ disable: function( index ) {
+ var disabled = this.options.disabled;
+ if ( disabled === true ) {
+ return;
+ }
+
+ if ( index === undefined ) {
+ disabled = true;
+ } else {
+ index = this._getIndex( index );
+ if ( $.inArray( index, disabled ) !== -1 ) {
+ return;
+ }
+ if ( $.isArray( disabled ) ) {
+ disabled = $.merge( [ index ], disabled ).sort();
+ } else {
+ disabled = [ index ];
+ }
+ }
+ this._setupDisabled( disabled );
+ },
+
+ load: function( index, event ) {
+ index = this._getIndex( index );
+ var that = this,
+ tab = this.tabs.eq( index ),
+ anchor = tab.find( ".ui-tabs-anchor" ),
+ panel = this._getPanelForTab( tab ),
+ eventData = {
+ tab: tab,
+ panel: panel
+ };
+
+ // not remote
+ if ( isLocal( anchor[ 0 ] ) ) {
+ return;
+ }
+
+ this.xhr = $.ajax( this._ajaxSettings( anchor, event, eventData ) );
+
+ // support: jQuery <1.8
+ // jQuery <1.8 returns false if the request is canceled in beforeSend,
+ // but as of 1.8, $.ajax() always returns a jqXHR object.
+ if ( this.xhr && this.xhr.statusText !== "canceled" ) {
+ tab.addClass( "ui-tabs-loading" );
+ panel.attr( "aria-busy", "true" );
+
+ this.xhr
+ .success(function( response ) {
+ // support: jQuery <1.8
+ // http://bugs.jquery.com/ticket/11778
+ setTimeout(function() {
+ panel.html( response );
+ that._trigger( "load", event, eventData );
+ }, 1 );
+ })
+ .complete(function( jqXHR, status ) {
+ // support: jQuery <1.8
+ // http://bugs.jquery.com/ticket/11778
+ setTimeout(function() {
+ if ( status === "abort" ) {
+ that.panels.stop( false, true );
+ }
+
+ tab.removeClass( "ui-tabs-loading" );
+ panel.removeAttr( "aria-busy" );
+
+ if ( jqXHR === that.xhr ) {
+ delete that.xhr;
+ }
+ }, 1 );
+ });
+ }
+ },
+
+ // TODO: Remove this function in 1.10 when ajaxOptions is removed
+ _ajaxSettings: function( anchor, event, eventData ) {
+ var that = this;
+ return {
+ url: anchor.attr( "href" ),
+ beforeSend: function( jqXHR, settings ) {
+ return that._trigger( "beforeLoad", event,
+ $.extend( { jqXHR : jqXHR, ajaxSettings: settings }, eventData ) );
+ }
+ };
+ },
+
+ _getPanelForTab: function( tab ) {
+ var id = $( tab ).attr( "aria-controls" );
+ return this.element.find( this._sanitizeSelector( "#" + id ) );
+ }
+});
+
+// DEPRECATED
+if ( $.uiBackCompat !== false ) {
+
+ // helper method for a lot of the back compat extensions
+ $.ui.tabs.prototype._ui = function( tab, panel ) {
+ return {
+ tab: tab,
+ panel: panel,
+ index: this.anchors.index( tab )
+ };
+ };
+
+ // url method
+ $.widget( "ui.tabs", $.ui.tabs, {
+ url: function( index, url ) {
+ this.anchors.eq( index ).attr( "href", url );
+ }
+ });
+
+ // TODO: Remove _ajaxSettings() method when removing this extension
+ // ajaxOptions and cache options
+ $.widget( "ui.tabs", $.ui.tabs, {
+ options: {
+ ajaxOptions: null,
+ cache: false
+ },
+
+ _create: function() {
+ this._super();
+
+ var that = this;
+
+ this._on({ tabsbeforeload: function( event, ui ) {
+ // tab is already cached
+ if ( $.data( ui.tab[ 0 ], "cache.tabs" ) ) {
+ event.preventDefault();
+ return;
+ }
+
+ ui.jqXHR.success(function() {
+ if ( that.options.cache ) {
+ $.data( ui.tab[ 0 ], "cache.tabs", true );
+ }
+ });
+ }});
+ },
+
+ _ajaxSettings: function( anchor, event, ui ) {
+ var ajaxOptions = this.options.ajaxOptions;
+ return $.extend( {}, ajaxOptions, {
+ error: function( xhr, status ) {
+ try {
+ // Passing index avoid a race condition when this method is
+ // called after the user has selected another tab.
+ // Pass the anchor that initiated this request allows
+ // loadError to manipulate the tab content panel via $(a.hash)
+ ajaxOptions.error(
+ xhr, status, ui.tab.closest( "li" ).index(), ui.tab[ 0 ] );
+ }
+ catch ( error ) {}
+ }
+ }, this._superApply( arguments ) );
+ },
+
+ _setOption: function( key, value ) {
+ // reset cache if switching from cached to not cached
+ if ( key === "cache" && value === false ) {
+ this.anchors.removeData( "cache.tabs" );
+ }
+ this._super( key, value );
+ },
+
+ _destroy: function() {
+ this.anchors.removeData( "cache.tabs" );
+ this._super();
+ },
+
+ url: function( index ){
+ this.anchors.eq( index ).removeData( "cache.tabs" );
+ this._superApply( arguments );
+ }
+ });
+
+ // abort method
+ $.widget( "ui.tabs", $.ui.tabs, {
+ abort: function() {
+ if ( this.xhr ) {
+ this.xhr.abort();
+ }
+ }
+ });
+
+ // spinner
+ $.widget( "ui.tabs", $.ui.tabs, {
+ options: {
+ spinner: "<em>Loading&#8230;</em>"
+ },
+ _create: function() {
+ this._super();
+ this._on({
+ tabsbeforeload: function( event, ui ) {
+ // Don't react to nested tabs or tabs that don't use a spinner
+ if ( event.target !== this.element[ 0 ] ||
+ !this.options.spinner ) {
+ return;
+ }
+
+ var span = ui.tab.find( "span" ),
+ html = span.html();
+ span.html( this.options.spinner );
+ ui.jqXHR.complete(function() {
+ span.html( html );
+ });
+ }
+ });
+ }
+ });
+
+ // enable/disable events
+ $.widget( "ui.tabs", $.ui.tabs, {
+ options: {
+ enable: null,
+ disable: null
+ },
+
+ enable: function( index ) {
+ var options = this.options,
+ trigger;
+
+ if ( index && options.disabled === true ||
+ ( $.isArray( options.disabled ) && $.inArray( index, options.disabled ) !== -1 ) ) {
+ trigger = true;
+ }
+
+ this._superApply( arguments );
+
+ if ( trigger ) {
+ this._trigger( "enable", null, this._ui( this.anchors[ index ], this.panels[ index ] ) );
+ }
+ },
+
+ disable: function( index ) {
+ var options = this.options,
+ trigger;
+
+ if ( index && options.disabled === false ||
+ ( $.isArray( options.disabled ) && $.inArray( index, options.disabled ) === -1 ) ) {
+ trigger = true;
+ }
+
+ this._superApply( arguments );
+
+ if ( trigger ) {
+ this._trigger( "disable", null, this._ui( this.anchors[ index ], this.panels[ index ] ) );
+ }
+ }
+ });
+
+ // add/remove methods and events
+ $.widget( "ui.tabs", $.ui.tabs, {
+ options: {
+ add: null,
+ remove: null,
+ tabTemplate: "<li><a href='#{href}'><span>#{label}</span></a></li>"
+ },
+
+ add: function( url, label, index ) {
+ if ( index === undefined ) {
+ index = this.anchors.length;
+ }
+
+ var doInsertAfter, panel,
+ options = this.options,
+ li = $( options.tabTemplate
+ .replace( /#\{href\}/g, url )
+ .replace( /#\{label\}/g, label ) ),
+ id = !url.indexOf( "#" ) ?
+ url.replace( "#", "" ) :
+ this._tabId( li );
+
+ li.addClass( "ui-state-default ui-corner-top" ).data( "ui-tabs-destroy", true );
+ li.attr( "aria-controls", id );
+
+ doInsertAfter = index >= this.tabs.length;
+
+ // try to find an existing element before creating a new one
+ panel = this.element.find( "#" + id );
+ if ( !panel.length ) {
+ panel = this._createPanel( id );
+ if ( doInsertAfter ) {
+ if ( index > 0 ) {
+ panel.insertAfter( this.panels.eq( -1 ) );
+ } else {
+ panel.appendTo( this.element );
+ }
+ } else {
+ panel.insertBefore( this.panels[ index ] );
+ }
+ }
+ panel.addClass( "ui-tabs-panel ui-widget-content ui-corner-bottom" ).hide();
+
+ if ( doInsertAfter ) {
+ li.appendTo( this.tablist );
+ } else {
+ li.insertBefore( this.tabs[ index ] );
+ }
+
+ options.disabled = $.map( options.disabled, function( n ) {
+ return n >= index ? ++n : n;
+ });
+
+ this.refresh();
+ if ( this.tabs.length === 1 && options.active === false ) {
+ this.option( "active", 0 );
+ }
+
+ this._trigger( "add", null, this._ui( this.anchors[ index ], this.panels[ index ] ) );
+ return this;
+ },
+
+ remove: function( index ) {
+ index = this._getIndex( index );
+ var options = this.options,
+ tab = this.tabs.eq( index ).remove(),
+ panel = this._getPanelForTab( tab ).remove();
+
+ // If selected tab was removed focus tab to the right or
+ // in case the last tab was removed the tab to the left.
+ // We check for more than 2 tabs, because if there are only 2,
+ // then when we remove this tab, there will only be one tab left
+ // so we don't need to detect which tab to activate.
+ if ( tab.hasClass( "ui-tabs-active" ) && this.anchors.length > 2 ) {
+ this._activate( index + ( index + 1 < this.anchors.length ? 1 : -1 ) );
+ }
+
+ options.disabled = $.map(
+ $.grep( options.disabled, function( n ) {
+ return n !== index;
+ }),
+ function( n ) {
+ return n >= index ? --n : n;
+ });
+
+ this.refresh();
+
+ this._trigger( "remove", null, this._ui( tab.find( "a" )[ 0 ], panel[ 0 ] ) );
+ return this;
+ }
+ });
+
+ // length method
+ $.widget( "ui.tabs", $.ui.tabs, {
+ length: function() {
+ return this.anchors.length;
+ }
+ });
+
+ // panel ids (idPrefix option + title attribute)
+ $.widget( "ui.tabs", $.ui.tabs, {
+ options: {
+ idPrefix: "ui-tabs-"
+ },
+
+ _tabId: function( tab ) {
+ var a = tab.is( "li" ) ? tab.find( "a[href]" ) : tab;
+ a = a[0];
+ return $( a ).closest( "li" ).attr( "aria-controls" ) ||
+ a.title && a.title.replace( /\s/g, "_" ).replace( /[^\w\u00c0-\uFFFF\-]/g, "" ) ||
+ this.options.idPrefix + getNextTabId();
+ }
+ });
+
+ // _createPanel method
+ $.widget( "ui.tabs", $.ui.tabs, {
+ options: {
+ panelTemplate: "<div></div>"
+ },
+
+ _createPanel: function( id ) {
+ return $( this.options.panelTemplate )
+ .attr( "id", id )
+ .addClass( "ui-tabs-panel ui-widget-content ui-corner-bottom" )
+ .data( "ui-tabs-destroy", true );
+ }
+ });
+
+ // selected option
+ $.widget( "ui.tabs", $.ui.tabs, {
+ _create: function() {
+ var options = this.options;
+ if ( options.active === null && options.selected !== undefined ) {
+ options.active = options.selected === -1 ? false : options.selected;
+ }
+ this._super();
+ options.selected = options.active;
+ if ( options.selected === false ) {
+ options.selected = -1;
+ }
+ },
+
+ _setOption: function( key, value ) {
+ if ( key !== "selected" ) {
+ return this._super( key, value );
+ }
+
+ var options = this.options;
+ this._super( "active", value === -1 ? false : value );
+ options.selected = options.active;
+ if ( options.selected === false ) {
+ options.selected = -1;
+ }
+ },
+
+ _eventHandler: function() {
+ this._superApply( arguments );
+ this.options.selected = this.options.active;
+ if ( this.options.selected === false ) {
+ this.options.selected = -1;
+ }
+ }
+ });
+
+ // show and select event
+ $.widget( "ui.tabs", $.ui.tabs, {
+ options: {
+ show: null,
+ select: null
+ },
+ _create: function() {
+ this._super();
+ if ( this.options.active !== false ) {
+ this._trigger( "show", null, this._ui(
+ this.active.find( ".ui-tabs-anchor" )[ 0 ],
+ this._getPanelForTab( this.active )[ 0 ] ) );
+ }
+ },
+ _trigger: function( type, event, data ) {
+ var tab, panel,
+ ret = this._superApply( arguments );
+
+ if ( !ret ) {
+ return false;
+ }
+
+ if ( type === "beforeActivate" ) {
+ tab = data.newTab.length ? data.newTab : data.oldTab;
+ panel = data.newPanel.length ? data.newPanel : data.oldPanel;
+ ret = this._super( "select", event, {
+ tab: tab.find( ".ui-tabs-anchor" )[ 0],
+ panel: panel[ 0 ],
+ index: tab.closest( "li" ).index()
+ });
+ } else if ( type === "activate" && data.newTab.length ) {
+ ret = this._super( "show", event, {
+ tab: data.newTab.find( ".ui-tabs-anchor" )[ 0 ],
+ panel: data.newPanel[ 0 ],
+ index: data.newTab.closest( "li" ).index()
+ });
+ }
+ return ret;
+ }
+ });
+
+ // select method
+ $.widget( "ui.tabs", $.ui.tabs, {
+ select: function( index ) {
+ index = this._getIndex( index );
+ if ( index === -1 ) {
+ if ( this.options.collapsible && this.options.selected !== -1 ) {
+ index = this.options.selected;
+ } else {
+ return;
+ }
+ }
+ this.anchors.eq( index ).trigger( this.options.event + this.eventNamespace );
+ }
+ });
+
+ // cookie option
+ (function() {
+
+ var listId = 0;
+
+ $.widget( "ui.tabs", $.ui.tabs, {
+ options: {
+ cookie: null // e.g. { expires: 7, path: '/', domain: 'jquery.com', secure: true }
+ },
+ _create: function() {
+ var options = this.options,
+ active;
+ if ( options.active == null && options.cookie ) {
+ active = parseInt( this._cookie(), 10 );
+ if ( active === -1 ) {
+ active = false;
+ }
+ options.active = active;
+ }
+ this._super();
+ },
+ _cookie: function( active ) {
+ var cookie = [ this.cookie ||
+ ( this.cookie = this.options.cookie.name || "ui-tabs-" + (++listId) ) ];
+ if ( arguments.length ) {
+ cookie.push( active === false ? -1 : active );
+ cookie.push( this.options.cookie );
+ }
+ return $.cookie.apply( null, cookie );
+ },
+ _refresh: function() {
+ this._super();
+ if ( this.options.cookie ) {
+ this._cookie( this.options.active, this.options.cookie );
+ }
+ },
+ _eventHandler: function() {
+ this._superApply( arguments );
+ if ( this.options.cookie ) {
+ this._cookie( this.options.active, this.options.cookie );
+ }
+ },
+ _destroy: function() {
+ this._super();
+ if ( this.options.cookie ) {
+ this._cookie( null, this.options.cookie );
+ }
+ }
+ });
+
+ })();
+
+ // load event
+ $.widget( "ui.tabs", $.ui.tabs, {
+ _trigger: function( type, event, data ) {
+ var _data = $.extend( {}, data );
+ if ( type === "load" ) {
+ _data.panel = _data.panel[ 0 ];
+ _data.tab = _data.tab.find( ".ui-tabs-anchor" )[ 0 ];
+ }
+ return this._super( type, event, _data );
+ }
+ });
+
+ // fx option
+ // The new animation options (show, hide) conflict with the old show callback.
+ // The old fx option wins over show/hide anyway (always favor back-compat).
+ // If a user wants to use the new animation API, they must give up the old API.
+ $.widget( "ui.tabs", $.ui.tabs, {
+ options: {
+ fx: null // e.g. { height: "toggle", opacity: "toggle", duration: 200 }
+ },
+
+ _getFx: function() {
+ var hide, show,
+ fx = this.options.fx;
+
+ if ( fx ) {
+ if ( $.isArray( fx ) ) {
+ hide = fx[ 0 ];
+ show = fx[ 1 ];
+ } else {
+ hide = show = fx;
+ }
+ }
+
+ return fx ? { show: show, hide: hide } : null;
+ },
+
+ _toggle: function( event, eventData ) {
+ var that = this,
+ toShow = eventData.newPanel,
+ toHide = eventData.oldPanel,
+ fx = this._getFx();
+
+ if ( !fx ) {
+ return this._super( event, eventData );
+ }
+
+ that.running = true;
+
+ function complete() {
+ that.running = false;
+ that._trigger( "activate", event, eventData );
+ }
+
+ function show() {
+ eventData.newTab.closest( "li" ).addClass( "ui-tabs-active ui-state-active" );
+
+ if ( toShow.length && fx.show ) {
+ toShow
+ .animate( fx.show, fx.show.duration, function() {
+ complete();
+ });
+ } else {
+ toShow.show();
+ complete();
+ }
+ }
+
+ // start out by hiding, then showing, then completing
+ if ( toHide.length && fx.hide ) {
+ toHide.animate( fx.hide, fx.hide.duration, function() {
+ eventData.oldTab.closest( "li" ).removeClass( "ui-tabs-active ui-state-active" );
+ show();
+ });
+ } else {
+ eventData.oldTab.closest( "li" ).removeClass( "ui-tabs-active ui-state-active" );
+ toHide.hide();
+ show();
+ }
+ }
+ });
+}
+
+})( jQuery );
diff --git a/js/jquery/src/jquery-ui/jquery.ui.tooltip.js b/js/jquery/src/jquery-ui/jquery.ui.tooltip.js
new file mode 100644
index 0000000000..629dc08062
--- /dev/null
+++ b/js/jquery/src/jquery-ui/jquery.ui.tooltip.js
@@ -0,0 +1,398 @@
+/*!
+ * jQuery UI Tooltip @VERSION
+ * http://jqueryui.com
+ *
+ * Copyright 2012 jQuery Foundation and other contributors
+ * Released under the MIT license.
+ * http://jquery.org/license
+ *
+ * http://api.jqueryui.com/tooltip/
+ *
+ * Depends:
+ * jquery.ui.core.js
+ * jquery.ui.widget.js
+ * jquery.ui.position.js
+ */
+(function( $ ) {
+
+var increments = 0;
+
+function addDescribedBy( elem, id ) {
+ var describedby = (elem.attr( "aria-describedby" ) || "").split( /\s+/ );
+ describedby.push( id );
+ elem
+ .data( "ui-tooltip-id", id )
+ .attr( "aria-describedby", $.trim( describedby.join( " " ) ) );
+}
+
+function removeDescribedBy( elem ) {
+ var id = elem.data( "ui-tooltip-id" ),
+ describedby = (elem.attr( "aria-describedby" ) || "").split( /\s+/ ),
+ index = $.inArray( id, describedby );
+ if ( index !== -1 ) {
+ describedby.splice( index, 1 );
+ }
+
+ elem.removeData( "ui-tooltip-id" );
+ describedby = $.trim( describedby.join( " " ) );
+ if ( describedby ) {
+ elem.attr( "aria-describedby", describedby );
+ } else {
+ elem.removeAttr( "aria-describedby" );
+ }
+}
+
+$.widget( "ui.tooltip", {
+ version: "@VERSION",
+ options: {
+ content: function() {
+ return $( this ).attr( "title" );
+ },
+ hide: true,
+ // Disabled elements have inconsistent behavior across browsers (#8661)
+ items: "[title]:not([disabled])",
+ position: {
+ my: "left top+15",
+ at: "left bottom",
+ collision: "flipfit flip"
+ },
+ show: true,
+ tooltipClass: null,
+ track: false,
+
+ // callbacks
+ close: null,
+ open: null
+ },
+
+ _create: function() {
+ this._on({
+ mouseover: "open",
+ focusin: "open"
+ });
+
+ // IDs of generated tooltips, needed for destroy
+ this.tooltips = {};
+ // IDs of parent tooltips where we removed the title attribute
+ this.parents = {};
+
+ if ( this.options.disabled ) {
+ this._disable();
+ }
+ },
+
+ _setOption: function( key, value ) {
+ var that = this;
+
+ if ( key === "disabled" ) {
+ this[ value ? "_disable" : "_enable" ]();
+ this.options[ key ] = value;
+ // disable element style changes
+ return;
+ }
+
+ this._super( key, value );
+
+ if ( key === "content" ) {
+ $.each( this.tooltips, function( id, element ) {
+ that._updateContent( element );
+ });
+ }
+ },
+
+ _disable: function() {
+ var that = this;
+
+ // close open tooltips
+ $.each( this.tooltips, function( id, element ) {
+ var event = $.Event( "blur" );
+ event.target = event.currentTarget = element[0];
+ that.close( event, true );
+ });
+
+ // remove title attributes to prevent native tooltips
+ this.element.find( this.options.items ).andSelf().each(function() {
+ var element = $( this );
+ if ( element.is( "[title]" ) ) {
+ element
+ .data( "ui-tooltip-title", element.attr( "title" ) )
+ .attr( "title", "" );
+ }
+ });
+ },
+
+ _enable: function() {
+ // restore title attributes
+ this.element.find( this.options.items ).andSelf().each(function() {
+ var element = $( this );
+ if ( element.data( "ui-tooltip-title" ) ) {
+ element.attr( "title", element.data( "ui-tooltip-title" ) );
+ }
+ });
+ },
+
+ open: function( event ) {
+ var that = this,
+ target = $( event ? event.target : this.element )
+ // we need closest here due to mouseover bubbling,
+ // but always pointing at the same event target
+ .closest( this.options.items );
+
+ // No element to show a tooltip for or the tooltip is already open
+ if ( !target.length || target.data( "ui-tooltip-id" ) ) {
+ return;
+ }
+
+ if ( target.attr( "title" ) ) {
+ target.data( "ui-tooltip-title", target.attr( "title" ) );
+ }
+
+ target.data( "ui-tooltip-open", true );
+
+ // kill parent tooltips, custom or native, for hover
+ if ( event && event.type === "mouseover" ) {
+ target.parents().each(function() {
+ var parent = $( this ),
+ blurEvent;
+ if ( parent.data( "ui-tooltip-open" ) ) {
+ blurEvent = $.Event( "blur" );
+ blurEvent.target = blurEvent.currentTarget = this;
+ that.close( blurEvent, true );
+ }
+ if ( parent.attr( "title" ) ) {
+ parent.uniqueId();
+ that.parents[ this.id ] = {
+ element: this,
+ title: parent.attr( "title" )
+ };
+ parent.attr( "title", "" );
+ }
+ });
+ }
+
+ this._updateContent( target, event );
+ },
+
+ _updateContent: function( target, event ) {
+ var content,
+ contentOption = this.options.content,
+ that = this,
+ eventType = event ? event.type : null;
+
+ if ( typeof contentOption === "string" ) {
+ return this._open( event, target, contentOption );
+ }
+
+ content = contentOption.call( target[0], function( response ) {
+ // ignore async response if tooltip was closed already
+ if ( !target.data( "ui-tooltip-open" ) ) {
+ return;
+ }
+ // IE may instantly serve a cached response for ajax requests
+ // delay this call to _open so the other call to _open runs first
+ that._delay(function() {
+ // jQuery creates a special event for focusin when it doesn't
+ // exist natively. To improve performance, the native event
+ // object is reused and the type is changed. Therefore, we can't
+ // rely on the type being correct after the event finished
+ // bubbling, so we set it back to the previous value. (#8740)
+ if ( event ) {
+ event.type = eventType;
+ }
+ this._open( event, target, response );
+ });
+ });
+ if ( content ) {
+ this._open( event, target, content );
+ }
+ },
+
+ _open: function( event, target, content ) {
+ var tooltip, events, delayedShow,
+ positionOption = $.extend( {}, this.options.position );
+
+ if ( !content ) {
+ return;
+ }
+
+ // Content can be updated multiple times. If the tooltip already
+ // exists, then just update the content and bail.
+ tooltip = this._find( target );
+ if ( tooltip.length ) {
+ tooltip.find( ".ui-tooltip-content" ).html( content );
+ return;
+ }
+
+ // if we have a title, clear it to prevent the native tooltip
+ // we have to check first to avoid defining a title if none exists
+ // (we don't want to cause an element to start matching [title])
+ //
+ // We use removeAttr only for key events, to allow IE to export the correct
+ // accessible attributes. For mouse events, set to empty string to avoid
+ // native tooltip showing up (happens only when removing inside mouseover).
+ if ( target.is( "[title]" ) ) {
+ if ( event && event.type === "mouseover" ) {
+ target.attr( "title", "" );
+ } else {
+ target.removeAttr( "title" );
+ }
+ }
+
+ tooltip = this._tooltip( target );
+ addDescribedBy( target, tooltip.attr( "id" ) );
+ tooltip.find( ".ui-tooltip-content" ).html( content );
+
+ function position( event ) {
+ positionOption.of = event;
+ if ( tooltip.is( ":hidden" ) ) {
+ return;
+ }
+ tooltip.position( positionOption );
+ }
+ if ( this.options.track && event && /^mouse/.test( event.type ) ) {
+ this._on( this.document, {
+ mousemove: position
+ });
+ // trigger once to override element-relative positioning
+ position( event );
+ } else {
+ tooltip.position( $.extend({
+ of: target
+ }, this.options.position ) );
+ }
+
+ tooltip.hide();
+
+ this._show( tooltip, this.options.show );
+ // Handle tracking tooltips that are shown with a delay (#8644). As soon
+ // as the tooltip is visible, position the tooltip using the most recent
+ // event.
+ if ( this.options.show && this.options.show.delay ) {
+ delayedShow = setInterval(function() {
+ if ( tooltip.is( ":visible" ) ) {
+ position( positionOption.of );
+ clearInterval( delayedShow );
+ }
+ }, $.fx.interval );
+ }
+
+ this._trigger( "open", event, { tooltip: tooltip } );
+
+ events = {
+ keyup: function( event ) {
+ if ( event.keyCode === $.ui.keyCode.ESCAPE ) {
+ var fakeEvent = $.Event(event);
+ fakeEvent.currentTarget = target[0];
+ this.close( fakeEvent, true );
+ }
+ },
+ remove: function() {
+ this._removeTooltip( tooltip );
+ }
+ };
+ if ( !event || event.type === "mouseover" ) {
+ events.mouseleave = "close";
+ }
+ if ( !event || event.type === "focusin" ) {
+ events.focusout = "close";
+ }
+ this._on( true, target, events );
+ },
+
+ close: function( event ) {
+ var that = this,
+ target = $( event ? event.currentTarget : this.element ),
+ tooltip = this._find( target );
+
+ // disabling closes the tooltip, so we need to track when we're closing
+ // to avoid an infinite loop in case the tooltip becomes disabled on close
+ if ( this.closing ) {
+ return;
+ }
+
+ // only set title if we had one before (see comment in _open())
+ if ( target.data( "ui-tooltip-title" ) ) {
+ target.attr( "title", target.data( "ui-tooltip-title" ) );
+ }
+
+ removeDescribedBy( target );
+
+ tooltip.stop( true );
+ this._hide( tooltip, this.options.hide, function() {
+ that._removeTooltip( $( this ) );
+ });
+
+ target.removeData( "ui-tooltip-open" );
+ this._off( target, "mouseleave focusout keyup" );
+ // Remove 'remove' binding only on delegated targets
+ if ( target[0] !== this.element[0] ) {
+ this._off( target, "remove" );
+ }
+ this._off( this.document, "mousemove" );
+
+ if ( event && event.type === "mouseleave" ) {
+ $.each( this.parents, function( id, parent ) {
+ $( parent.element ).attr( "title", parent.title );
+ delete that.parents[ id ];
+ });
+ }
+
+ this.closing = true;
+ this._trigger( "close", event, { tooltip: tooltip } );
+ this.closing = false;
+ },
+
+ _tooltip: function( element ) {
+ var id = "ui-tooltip-" + increments++,
+ tooltip = $( "<div>" )
+ .attr({
+ id: id,
+ role: "tooltip"
+ })
+ .addClass( "ui-tooltip ui-widget ui-corner-all ui-widget-content " +
+ ( this.options.tooltipClass || "" ) );
+ $( "<div>" )
+ .addClass( "ui-tooltip-content" )
+ .appendTo( tooltip );
+ tooltip.appendTo( this.document[0].body );
+ if ( $.fn.bgiframe ) {
+ tooltip.bgiframe();
+ }
+ this.tooltips[ id ] = element;
+ return tooltip;
+ },
+
+ _find: function( target ) {
+ var id = target.data( "ui-tooltip-id" );
+ return id ? $( "#" + id ) : $();
+ },
+
+ _removeTooltip: function( tooltip ) {
+ tooltip.remove();
+ delete this.tooltips[ tooltip.attr( "id" ) ];
+ },
+
+ _destroy: function() {
+ var that = this;
+
+ // close open tooltips
+ $.each( this.tooltips, function( id, element ) {
+ // Delegate to close method to handle common cleanup
+ var event = $.Event( "blur" );
+ event.target = event.currentTarget = element[0];
+ that.close( event, true );
+
+ // Remove immediately; destroying an open tooltip doesn't use the
+ // hide animation
+ $( "#" + id ).remove();
+
+ // Restore the title
+ if ( element.data( "ui-tooltip-title" ) ) {
+ element.attr( "title", element.data( "ui-tooltip-title" ) );
+ element.removeData( "ui-tooltip-title" );
+ }
+ });
+ }
+});
+
+}( jQuery ) );
diff --git a/js/jquery/src/jquery-ui/jquery.ui.widget.js b/js/jquery/src/jquery-ui/jquery.ui.widget.js
new file mode 100644
index 0000000000..2533448689
--- /dev/null
+++ b/js/jquery/src/jquery-ui/jquery.ui.widget.js
@@ -0,0 +1,528 @@
+/*!
+ * jQuery UI Widget @VERSION
+ * http://jqueryui.com
+ *
+ * Copyright 2012 jQuery Foundation and other contributors
+ * Released under the MIT license.
+ * http://jquery.org/license
+ *
+ * http://api.jqueryui.com/jQuery.widget/
+ */
+(function( $, undefined ) {
+
+var uuid = 0,
+ slice = Array.prototype.slice,
+ _cleanData = $.cleanData;
+$.cleanData = function( elems ) {
+ for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) {
+ try {
+ $( elem ).triggerHandler( "remove" );
+ // http://bugs.jquery.com/ticket/8235
+ } catch( e ) {}
+ }
+ _cleanData( elems );
+};
+
+$.widget = function( name, base, prototype ) {
+ var fullName, existingConstructor, constructor, basePrototype,
+ namespace = name.split( "." )[ 0 ];
+
+ name = name.split( "." )[ 1 ];
+ fullName = namespace + "-" + name;
+
+ if ( !prototype ) {
+ prototype = base;
+ base = $.Widget;
+ }
+
+ // create selector for plugin
+ $.expr[ ":" ][ fullName.toLowerCase() ] = function( elem ) {
+ return !!$.data( elem, fullName );
+ };
+
+ $[ namespace ] = $[ namespace ] || {};
+ existingConstructor = $[ namespace ][ name ];
+ constructor = $[ namespace ][ name ] = function( options, element ) {
+ // allow instantiation without "new" keyword
+ if ( !this._createWidget ) {
+ return new constructor( options, element );
+ }
+
+ // allow instantiation without initializing for simple inheritance
+ // must use "new" keyword (the code above always passes args)
+ if ( arguments.length ) {
+ this._createWidget( options, element );
+ }
+ };
+ // extend with the existing constructor to carry over any static properties
+ $.extend( constructor, existingConstructor, {
+ version: prototype.version,
+ // copy the object used to create the prototype in case we need to
+ // redefine the widget later
+ _proto: $.extend( {}, prototype ),
+ // track widgets that inherit from this widget in case this widget is
+ // redefined after a widget inherits from it
+ _childConstructors: []
+ });
+
+ basePrototype = new base();
+ // we need to make the options hash a property directly on the new instance
+ // otherwise we'll modify the options hash on the prototype that we're
+ // inheriting from
+ basePrototype.options = $.widget.extend( {}, basePrototype.options );
+ $.each( prototype, function( prop, value ) {
+ if ( $.isFunction( value ) ) {
+ prototype[ prop ] = (function() {
+ var _super = function() {
+ return base.prototype[ prop ].apply( this, arguments );
+ },
+ _superApply = function( args ) {
+ return base.prototype[ prop ].apply( this, args );
+ };
+ return function() {
+ var __super = this._super,
+ __superApply = this._superApply,
+ returnValue;
+
+ this._super = _super;
+ this._superApply = _superApply;
+
+ returnValue = value.apply( this, arguments );
+
+ this._super = __super;
+ this._superApply = __superApply;
+
+ return returnValue;
+ };
+ })();
+ }
+ });
+ constructor.prototype = $.widget.extend( basePrototype, {
+ // TODO: remove support for widgetEventPrefix
+ // always use the name + a colon as the prefix, e.g., draggable:start
+ // don't prefix for widgets that aren't DOM-based
+ widgetEventPrefix: existingConstructor ? basePrototype.widgetEventPrefix : name
+ }, prototype, {
+ constructor: constructor,
+ namespace: namespace,
+ widgetName: name,
+ // TODO remove widgetBaseClass, see #8155
+ widgetBaseClass: fullName,
+ widgetFullName: fullName
+ });
+
+ // If this widget is being redefined then we need to find all widgets that
+ // are inheriting from it and redefine all of them so that they inherit from
+ // the new version of this widget. We're essentially trying to replace one
+ // level in the prototype chain.
+ if ( existingConstructor ) {
+ $.each( existingConstructor._childConstructors, function( i, child ) {
+ var childPrototype = child.prototype;
+
+ // redefine the child widget using the same prototype that was
+ // originally used, but inherit from the new version of the base
+ $.widget( childPrototype.namespace + "." + childPrototype.widgetName, constructor, child._proto );
+ });
+ // remove the list of existing child constructors from the old constructor
+ // so the old child constructors can be garbage collected
+ delete existingConstructor._childConstructors;
+ } else {
+ base._childConstructors.push( constructor );
+ }
+
+ $.widget.bridge( name, constructor );
+};
+
+$.widget.extend = function( target ) {
+ var input = slice.call( arguments, 1 ),
+ inputIndex = 0,
+ inputLength = input.length,
+ key,
+ value;
+ for ( ; inputIndex < inputLength; inputIndex++ ) {
+ for ( key in input[ inputIndex ] ) {
+ value = input[ inputIndex ][ key ];
+ if ( input[ inputIndex ].hasOwnProperty( key ) && value !== undefined ) {
+ // Clone objects
+ if ( $.isPlainObject( value ) ) {
+ target[ key ] = $.isPlainObject( target[ key ] ) ?
+ $.widget.extend( {}, target[ key ], value ) :
+ // Don't extend strings, arrays, etc. with objects
+ $.widget.extend( {}, value );
+ // Copy everything else by reference
+ } else {
+ target[ key ] = value;
+ }
+ }
+ }
+ }
+ return target;
+};
+
+$.widget.bridge = function( name, object ) {
+ var fullName = object.prototype.widgetFullName || name;
+ $.fn[ name ] = function( options ) {
+ var isMethodCall = typeof options === "string",
+ args = slice.call( arguments, 1 ),
+ returnValue = this;
+
+ // allow multiple hashes to be passed on init
+ options = !isMethodCall && args.length ?
+ $.widget.extend.apply( null, [ options ].concat(args) ) :
+ options;
+
+ if ( isMethodCall ) {
+ this.each(function() {
+ var methodValue,
+ instance = $.data( this, fullName );
+ if ( !instance ) {
+ return $.error( "cannot call methods on " + name + " prior to initialization; " +
+ "attempted to call method '" + options + "'" );
+ }
+ if ( !$.isFunction( instance[options] ) || options.charAt( 0 ) === "_" ) {
+ return $.error( "no such method '" + options + "' for " + name + " widget instance" );
+ }
+ methodValue = instance[ options ].apply( instance, args );
+ if ( methodValue !== instance && methodValue !== undefined ) {
+ returnValue = methodValue && methodValue.jquery ?
+ returnValue.pushStack( methodValue.get() ) :
+ methodValue;
+ return false;
+ }
+ });
+ } else {
+ this.each(function() {
+ var instance = $.data( this, fullName );
+ if ( instance ) {
+ instance.option( options || {} )._init();
+ } else {
+ $.data( this, fullName, new object( options, this ) );
+ }
+ });
+ }
+
+ return returnValue;
+ };
+};
+
+$.Widget = function( /* options, element */ ) {};
+$.Widget._childConstructors = [];
+
+$.Widget.prototype = {
+ widgetName: "widget",
+ widgetEventPrefix: "",
+ defaultElement: "<div>",
+ options: {
+ disabled: false,
+
+ // callbacks
+ create: null
+ },
+ _createWidget: function( options, element ) {
+ element = $( element || this.defaultElement || this )[ 0 ];
+ this.element = $( element );
+ this.uuid = uuid++;
+ this.eventNamespace = "." + this.widgetName + this.uuid;
+ this.options = $.widget.extend( {},
+ this.options,
+ this._getCreateOptions(),
+ options );
+
+ this.bindings = $();
+ this.hoverable = $();
+ this.focusable = $();
+
+ if ( element !== this ) {
+ // 1.9 BC for #7810
+ // TODO remove dual storage
+ $.data( element, this.widgetName, this );
+ $.data( element, this.widgetFullName, this );
+ this._on( true, this.element, {
+ remove: function( event ) {
+ if ( event.target === element ) {
+ this.destroy();
+ }
+ }
+ });
+ this.document = $( element.style ?
+ // element within the document
+ element.ownerDocument :
+ // element is window or document
+ element.document || element );
+ this.window = $( this.document[0].defaultView || this.document[0].parentWindow );
+ }
+
+ this._create();
+ this._trigger( "create", null, this._getCreateEventData() );
+ this._init();
+ },
+ _getCreateOptions: $.noop,
+ _getCreateEventData: $.noop,
+ _create: $.noop,
+ _init: $.noop,
+
+ destroy: function() {
+ this._destroy();
+ // we can probably remove the unbind calls in 2.0
+ // all event bindings should go through this._on()
+ this.element
+ .unbind( this.eventNamespace )
+ // 1.9 BC for #7810
+ // TODO remove dual storage
+ .removeData( this.widgetName )
+ .removeData( this.widgetFullName )
+ // support: jquery <1.6.3
+ // http://bugs.jquery.com/ticket/9413
+ .removeData( $.camelCase( this.widgetFullName ) );
+ this.widget()
+ .unbind( this.eventNamespace )
+ .removeAttr( "aria-disabled" )
+ .removeClass(
+ this.widgetFullName + "-disabled " +
+ "ui-state-disabled" );
+
+ // clean up events and states
+ this.bindings.unbind( this.eventNamespace );
+ this.hoverable.removeClass( "ui-state-hover" );
+ this.focusable.removeClass( "ui-state-focus" );
+ },
+ _destroy: $.noop,
+
+ widget: function() {
+ return this.element;
+ },
+
+ option: function( key, value ) {
+ var options = key,
+ parts,
+ curOption,
+ i;
+
+ if ( arguments.length === 0 ) {
+ // don't return a reference to the internal hash
+ return $.widget.extend( {}, this.options );
+ }
+
+ if ( typeof key === "string" ) {
+ // handle nested keys, e.g., "foo.bar" => { foo: { bar: ___ } }
+ options = {};
+ parts = key.split( "." );
+ key = parts.shift();
+ if ( parts.length ) {
+ curOption = options[ key ] = $.widget.extend( {}, this.options[ key ] );
+ for ( i = 0; i < parts.length - 1; i++ ) {
+ curOption[ parts[ i ] ] = curOption[ parts[ i ] ] || {};
+ curOption = curOption[ parts[ i ] ];
+ }
+ key = parts.pop();
+ if ( value === undefined ) {
+ return curOption[ key ] === undefined ? null : curOption[ key ];
+ }
+ curOption[ key ] = value;
+ } else {
+ if ( value === undefined ) {
+ return this.options[ key ] === undefined ? null : this.options[ key ];
+ }
+ options[ key ] = value;
+ }
+ }
+
+ this._setOptions( options );
+
+ return this;
+ },
+ _setOptions: function( options ) {
+ var key;
+
+ for ( key in options ) {
+ this._setOption( key, options[ key ] );
+ }
+
+ return this;
+ },
+ _setOption: function( key, value ) {
+ this.options[ key ] = value;
+
+ if ( key === "disabled" ) {
+ this.widget()
+ .toggleClass( this.widgetFullName + "-disabled ui-state-disabled", !!value )
+ .attr( "aria-disabled", value );
+ this.hoverable.removeClass( "ui-state-hover" );
+ this.focusable.removeClass( "ui-state-focus" );
+ }
+
+ return this;
+ },
+
+ enable: function() {
+ return this._setOption( "disabled", false );
+ },
+ disable: function() {
+ return this._setOption( "disabled", true );
+ },
+
+ _on: function( suppressDisabledCheck, element, handlers ) {
+ var delegateElement,
+ instance = this;
+
+ // no suppressDisabledCheck flag, shuffle arguments
+ if ( typeof suppressDisabledCheck !== "boolean" ) {
+ handlers = element;
+ element = suppressDisabledCheck;
+ suppressDisabledCheck = false;
+ }
+
+ // no element argument, shuffle and use this.element
+ if ( !handlers ) {
+ handlers = element;
+ element = this.element;
+ delegateElement = this.widget();
+ } else {
+ // accept selectors, DOM elements
+ element = delegateElement = $( element );
+ this.bindings = this.bindings.add( element );
+ }
+
+ $.each( handlers, function( event, handler ) {
+ function handlerProxy() {
+ // allow widgets to customize the disabled handling
+ // - disabled as an array instead of boolean
+ // - disabled class as method for disabling individual parts
+ if ( !suppressDisabledCheck &&
+ ( instance.options.disabled === true ||
+ $( this ).hasClass( "ui-state-disabled" ) ) ) {
+ return;
+ }
+ return ( typeof handler === "string" ? instance[ handler ] : handler )
+ .apply( instance, arguments );
+ }
+
+ // copy the guid so direct unbinding works
+ if ( typeof handler !== "string" ) {
+ handlerProxy.guid = handler.guid =
+ handler.guid || handlerProxy.guid || $.guid++;
+ }
+
+ var match = event.match( /^(\w+)\s*(.*)$/ ),
+ eventName = match[1] + instance.eventNamespace,
+ selector = match[2];
+ if ( selector ) {
+ delegateElement.delegate( selector, eventName, handlerProxy );
+ } else {
+ element.bind( eventName, handlerProxy );
+ }
+ });
+ },
+
+ _off: function( element, eventName ) {
+ eventName = (eventName || "").split( " " ).join( this.eventNamespace + " " ) + this.eventNamespace;
+ element.unbind( eventName ).undelegate( eventName );
+ },
+
+ _delay: function( handler, delay ) {
+ function handlerProxy() {
+ return ( typeof handler === "string" ? instance[ handler ] : handler )
+ .apply( instance, arguments );
+ }
+ var instance = this;
+ return setTimeout( handlerProxy, delay || 0 );
+ },
+
+ _hoverable: function( element ) {
+ this.hoverable = this.hoverable.add( element );
+ this._on( element, {
+ mouseenter: function( event ) {
+ $( event.currentTarget ).addClass( "ui-state-hover" );
+ },
+ mouseleave: function( event ) {
+ $( event.currentTarget ).removeClass( "ui-state-hover" );
+ }
+ });
+ },
+
+ _focusable: function( element ) {
+ this.focusable = this.focusable.add( element );
+ this._on( element, {
+ focusin: function( event ) {
+ $( event.currentTarget ).addClass( "ui-state-focus" );
+ },
+ focusout: function( event ) {
+ $( event.currentTarget ).removeClass( "ui-state-focus" );
+ }
+ });
+ },
+
+ _trigger: function( type, event, data ) {
+ var prop, orig,
+ callback = this.options[ type ];
+
+ data = data || {};
+ event = $.Event( event );
+ event.type = ( type === this.widgetEventPrefix ?
+ type :
+ this.widgetEventPrefix + type ).toLowerCase();
+ // the original event may come from any element
+ // so we need to reset the target on the new event
+ event.target = this.element[ 0 ];
+
+ // copy original event properties over to the new event
+ orig = event.originalEvent;
+ if ( orig ) {
+ for ( prop in orig ) {
+ if ( !( prop in event ) ) {
+ event[ prop ] = orig[ prop ];
+ }
+ }
+ }
+
+ this.element.trigger( event, data );
+ return !( $.isFunction( callback ) &&
+ callback.apply( this.element[0], [ event ].concat( data ) ) === false ||
+ event.isDefaultPrevented() );
+ }
+};
+
+$.each( { show: "fadeIn", hide: "fadeOut" }, function( method, defaultEffect ) {
+ $.Widget.prototype[ "_" + method ] = function( element, options, callback ) {
+ if ( typeof options === "string" ) {
+ options = { effect: options };
+ }
+ var hasOptions,
+ effectName = !options ?
+ method :
+ options === true || typeof options === "number" ?
+ defaultEffect :
+ options.effect || defaultEffect;
+ options = options || {};
+ if ( typeof options === "number" ) {
+ options = { duration: options };
+ }
+ hasOptions = !$.isEmptyObject( options );
+ options.complete = callback;
+ if ( options.delay ) {
+ element.delay( options.delay );
+ }
+ if ( hasOptions && $.effects && ( $.effects.effect[ effectName ] || $.uiBackCompat !== false && $.effects[ effectName ] ) ) {
+ element[ method ]( options );
+ } else if ( effectName !== method && element[ effectName ] ) {
+ element[ effectName ]( options.duration, options.easing, callback );
+ } else {
+ element.queue(function( next ) {
+ $( this )[ method ]();
+ if ( callback ) {
+ callback.call( element[ 0 ] );
+ }
+ next();
+ });
+ }
+ };
+});
+
+// DEPRECATED
+if ( $.uiBackCompat !== false ) {
+ $.Widget.prototype._getCreateOptions = function() {
+ return $.metadata && $.metadata.get( this.element[0] )[ this.widgetName ];
+ };
+}
+
+})( jQuery );
diff --git a/js/jquery/src/jquery/ajax.js b/js/jquery/src/jquery/ajax.js
new file mode 100644
index 0000000000..8facc17fe5
--- /dev/null
+++ b/js/jquery/src/jquery/ajax.js
@@ -0,0 +1,855 @@
+var
+ // Document location
+ ajaxLocParts,
+ ajaxLocation,
+
+ ajax_nonce = jQuery.now(),
+
+ ajax_rquery = /\?/,
+ rhash = /#.*$/,
+ rts = /([?&])_=[^&]*/,
+ rheaders = /^(.*?):[ \t]*([^\r\n]*)$/mg,
+ // #7653, #8125, #8152: local protocol detection
+ rlocalProtocol = /^(?:about|app|app-storage|.+-extension|file|res|widget):$/,
+ rnoContent = /^(?:GET|HEAD)$/,
+ rprotocol = /^\/\//,
+ rurl = /^([\w.+-]+:)(?:\/\/([^\/?#:]*)(?::(\d+)|)|)/,
+
+ // Keep a copy of the old load method
+ _load = jQuery.fn.load,
+
+ /* Prefilters
+ * 1) They are useful to introduce custom dataTypes (see ajax/jsonp.js for an example)
+ * 2) These are called:
+ * - BEFORE asking for a transport
+ * - AFTER param serialization (s.data is a string if s.processData is true)
+ * 3) key is the dataType
+ * 4) the catchall symbol "*" can be used
+ * 5) execution will start with transport dataType and THEN continue down to "*" if needed
+ */
+ prefilters = {},
+
+ /* Transports bindings
+ * 1) key is the dataType
+ * 2) the catchall symbol "*" can be used
+ * 3) selection will start with transport dataType and THEN go to "*" if needed
+ */
+ transports = {},
+
+ // Avoid comment-prolog char sequence (#10098); must appease lint and evade compression
+ allTypes = "*/".concat("*");
+
+// #8138, IE may throw an exception when accessing
+// a field from window.location if document.domain has been set
+try {
+ ajaxLocation = location.href;
+} catch( e ) {
+ // Use the href attribute of an A element
+ // since IE will modify it given document.location
+ ajaxLocation = document.createElement( "a" );
+ ajaxLocation.href = "";
+ ajaxLocation = ajaxLocation.href;
+}
+
+// Segment location into parts
+ajaxLocParts = rurl.exec( ajaxLocation.toLowerCase() ) || [];
+
+// Base "constructor" for jQuery.ajaxPrefilter and jQuery.ajaxTransport
+function addToPrefiltersOrTransports( structure ) {
+
+ // dataTypeExpression is optional and defaults to "*"
+ return function( dataTypeExpression, func ) {
+
+ if ( typeof dataTypeExpression !== "string" ) {
+ func = dataTypeExpression;
+ dataTypeExpression = "*";
+ }
+
+ var dataType,
+ i = 0,
+ dataTypes = dataTypeExpression.toLowerCase().match( core_rnotwhite ) || [];
+
+ if ( jQuery.isFunction( func ) ) {
+ // For each dataType in the dataTypeExpression
+ while ( (dataType = dataTypes[i++]) ) {
+ // Prepend if requested
+ if ( dataType[0] === "+" ) {
+ dataType = dataType.slice( 1 ) || "*";
+ (structure[ dataType ] = structure[ dataType ] || []).unshift( func );
+
+ // Otherwise append
+ } else {
+ (structure[ dataType ] = structure[ dataType ] || []).push( func );
+ }
+ }
+ }
+ };
+}
+
+// Base inspection function for prefilters and transports
+function inspectPrefiltersOrTransports( structure, options, originalOptions, jqXHR ) {
+
+ var inspected = {},
+ seekingTransport = ( structure === transports );
+
+ function inspect( dataType ) {
+ var selected;
+ inspected[ dataType ] = true;
+ jQuery.each( structure[ dataType ] || [], function( _, prefilterOrFactory ) {
+ var dataTypeOrTransport = prefilterOrFactory( options, originalOptions, jqXHR );
+ if( typeof dataTypeOrTransport === "string" && !seekingTransport && !inspected[ dataTypeOrTransport ] ) {
+ options.dataTypes.unshift( dataTypeOrTransport );
+ inspect( dataTypeOrTransport );
+ return false;
+ } else if ( seekingTransport ) {
+ return !( selected = dataTypeOrTransport );
+ }
+ });
+ return selected;
+ }
+
+ return inspect( options.dataTypes[ 0 ] ) || !inspected[ "*" ] && inspect( "*" );
+}
+
+// A special extend for ajax options
+// that takes "flat" options (not to be deep extended)
+// Fixes #9887
+function ajaxExtend( target, src ) {
+ var key, deep,
+ flatOptions = jQuery.ajaxSettings.flatOptions || {};
+
+ for ( key in src ) {
+ if ( src[ key ] !== undefined ) {
+ ( flatOptions[ key ] ? target : ( deep || (deep = {}) ) )[ key ] = src[ key ];
+ }
+ }
+ if ( deep ) {
+ jQuery.extend( true, target, deep );
+ }
+
+ return target;
+}
+
+jQuery.fn.load = function( url, params, callback ) {
+ if ( typeof url !== "string" && _load ) {
+ return _load.apply( this, arguments );
+ }
+
+ var selector, type, response,
+ self = this,
+ off = url.indexOf(" ");
+
+ if ( off >= 0 ) {
+ selector = url.slice( off );
+ url = url.slice( 0, off );
+ }
+
+ // If it's a function
+ if ( jQuery.isFunction( params ) ) {
+
+ // We assume that it's the callback
+ callback = params;
+ params = undefined;
+
+ // Otherwise, build a param string
+ } else if ( params && typeof params === "object" ) {
+ type = "POST";
+ }
+
+ // If we have elements to modify, make the request
+ if ( self.length > 0 ) {
+ jQuery.ajax({
+ url: url,
+
+ // if "type" variable is undefined, then "GET" method will be used
+ type: type,
+ dataType: "html",
+ data: params
+ }).done(function( responseText ) {
+
+ // Save response for use in complete callback
+ response = arguments;
+
+ self.html( selector ?
+
+ // If a selector was specified, locate the right elements in a dummy div
+ // Exclude scripts to avoid IE 'Permission Denied' errors
+ jQuery("<div>").append( jQuery.parseHTML( responseText ) ).find( selector ) :
+
+ // Otherwise use the full result
+ responseText );
+
+ }).complete( callback && function( jqXHR, status ) {
+ self.each( callback, response || [ jqXHR.responseText, status, jqXHR ] );
+ });
+ }
+
+ return this;
+};
+
+// Attach a bunch of functions for handling common AJAX events
+jQuery.each( [ "ajaxStart", "ajaxStop", "ajaxComplete", "ajaxError", "ajaxSuccess", "ajaxSend" ], function( i, type ){
+ jQuery.fn[ type ] = function( fn ){
+ return this.on( type, fn );
+ };
+});
+
+jQuery.extend({
+
+ // Counter for holding the number of active queries
+ active: 0,
+
+ // Last-Modified header cache for next request
+ lastModified: {},
+ etag: {},
+
+ ajaxSettings: {
+ url: ajaxLocation,
+ type: "GET",
+ isLocal: rlocalProtocol.test( ajaxLocParts[ 1 ] ),
+ global: true,
+ processData: true,
+ async: true,
+ contentType: "application/x-www-form-urlencoded; charset=UTF-8",
+ /*
+ timeout: 0,
+ data: null,
+ dataType: null,
+ username: null,
+ password: null,
+ cache: null,
+ throws: false,
+ traditional: false,
+ headers: {},
+ */
+
+ accepts: {
+ "*": allTypes,
+ text: "text/plain",
+ html: "text/html",
+ xml: "application/xml, text/xml",
+ json: "application/json, text/javascript"
+ },
+
+ contents: {
+ xml: /xml/,
+ html: /html/,
+ json: /json/
+ },
+
+ responseFields: {
+ xml: "responseXML",
+ text: "responseText",
+ json: "responseJSON"
+ },
+
+ // Data converters
+ // Keys separate source (or catchall "*") and destination types with a single space
+ converters: {
+
+ // Convert anything to text
+ "* text": String,
+
+ // Text to html (true = no transformation)
+ "text html": true,
+
+ // Evaluate text as a json expression
+ "text json": jQuery.parseJSON,
+
+ // Parse text as xml
+ "text xml": jQuery.parseXML
+ },
+
+ // For options that shouldn't be deep extended:
+ // you can add your own custom options here if
+ // and when you create one that shouldn't be
+ // deep extended (see ajaxExtend)
+ flatOptions: {
+ url: true,
+ context: true
+ }
+ },
+
+ // Creates a full fledged settings object into target
+ // with both ajaxSettings and settings fields.
+ // If target is omitted, writes into ajaxSettings.
+ ajaxSetup: function( target, settings ) {
+ return settings ?
+
+ // Building a settings object
+ ajaxExtend( ajaxExtend( target, jQuery.ajaxSettings ), settings ) :
+
+ // Extending ajaxSettings
+ ajaxExtend( jQuery.ajaxSettings, target );
+ },
+
+ ajaxPrefilter: addToPrefiltersOrTransports( prefilters ),
+ ajaxTransport: addToPrefiltersOrTransports( transports ),
+
+ // Main method
+ ajax: function( url, options ) {
+
+ // If url is an object, simulate pre-1.5 signature
+ if ( typeof url === "object" ) {
+ options = url;
+ url = undefined;
+ }
+
+ // Force options to be an object
+ options = options || {};
+
+ var transport,
+ // URL without anti-cache param
+ cacheURL,
+ // Response headers
+ responseHeadersString,
+ responseHeaders,
+ // timeout handle
+ timeoutTimer,
+ // Cross-domain detection vars
+ parts,
+ // To know if global events are to be dispatched
+ fireGlobals,
+ // Loop variable
+ i,
+ // Create the final options object
+ s = jQuery.ajaxSetup( {}, options ),
+ // Callbacks context
+ callbackContext = s.context || s,
+ // Context for global events is callbackContext if it is a DOM node or jQuery collection
+ globalEventContext = s.context && ( callbackContext.nodeType || callbackContext.jquery ) ?
+ jQuery( callbackContext ) :
+ jQuery.event,
+ // Deferreds
+ deferred = jQuery.Deferred(),
+ completeDeferred = jQuery.Callbacks("once memory"),
+ // Status-dependent callbacks
+ statusCode = s.statusCode || {},
+ // Headers (they are sent all at once)
+ requestHeaders = {},
+ requestHeadersNames = {},
+ // The jqXHR state
+ state = 0,
+ // Default abort message
+ strAbort = "canceled",
+ // Fake xhr
+ jqXHR = {
+ readyState: 0,
+
+ // Builds headers hashtable if needed
+ getResponseHeader: function( key ) {
+ var match;
+ if ( state === 2 ) {
+ if ( !responseHeaders ) {
+ responseHeaders = {};
+ while ( (match = rheaders.exec( responseHeadersString )) ) {
+ responseHeaders[ match[1].toLowerCase() ] = match[ 2 ];
+ }
+ }
+ match = responseHeaders[ key.toLowerCase() ];
+ }
+ return match == null ? null : match;
+ },
+
+ // Raw string
+ getAllResponseHeaders: function() {
+ return state === 2 ? responseHeadersString : null;
+ },
+
+ // Caches the header
+ setRequestHeader: function( name, value ) {
+ var lname = name.toLowerCase();
+ if ( !state ) {
+ name = requestHeadersNames[ lname ] = requestHeadersNames[ lname ] || name;
+ requestHeaders[ name ] = value;
+ }
+ return this;
+ },
+
+ // Overrides response content-type header
+ overrideMimeType: function( type ) {
+ if ( !state ) {
+ s.mimeType = type;
+ }
+ return this;
+ },
+
+ // Status-dependent callbacks
+ statusCode: function( map ) {
+ var code;
+ if ( map ) {
+ if ( state < 2 ) {
+ for ( code in map ) {
+ // Lazy-add the new callback in a way that preserves old ones
+ statusCode[ code ] = [ statusCode[ code ], map[ code ] ];
+ }
+ } else {
+ // Execute the appropriate callbacks
+ jqXHR.always( map[ jqXHR.status ] );
+ }
+ }
+ return this;
+ },
+
+ // Cancel the request
+ abort: function( statusText ) {
+ var finalText = statusText || strAbort;
+ if ( transport ) {
+ transport.abort( finalText );
+ }
+ done( 0, finalText );
+ return this;
+ }
+ };
+
+ // Attach deferreds
+ deferred.promise( jqXHR ).complete = completeDeferred.add;
+ jqXHR.success = jqXHR.done;
+ jqXHR.error = jqXHR.fail;
+
+ // Remove hash character (#7531: and string promotion)
+ // Add protocol if not provided (prefilters might expect it)
+ // Handle falsy url in the settings object (#10093: consistency with old signature)
+ // We also use the url parameter if available
+ s.url = ( ( url || s.url || ajaxLocation ) + "" ).replace( rhash, "" )
+ .replace( rprotocol, ajaxLocParts[ 1 ] + "//" );
+
+ // Alias method option to type as per ticket #12004
+ s.type = options.method || options.type || s.method || s.type;
+
+ // Extract dataTypes list
+ s.dataTypes = jQuery.trim( s.dataType || "*" ).toLowerCase().match( core_rnotwhite ) || [""];
+
+ // A cross-domain request is in order when we have a protocol:host:port mismatch
+ if ( s.crossDomain == null ) {
+ parts = rurl.exec( s.url.toLowerCase() );
+ s.crossDomain = !!( parts &&
+ ( parts[ 1 ] !== ajaxLocParts[ 1 ] || parts[ 2 ] !== ajaxLocParts[ 2 ] ||
+ ( parts[ 3 ] || ( parts[ 1 ] === "http:" ? "80" : "443" ) ) !==
+ ( ajaxLocParts[ 3 ] || ( ajaxLocParts[ 1 ] === "http:" ? "80" : "443" ) ) )
+ );
+ }
+
+ // Convert data if not already a string
+ if ( s.data && s.processData && typeof s.data !== "string" ) {
+ s.data = jQuery.param( s.data, s.traditional );
+ }
+
+ // Apply prefilters
+ inspectPrefiltersOrTransports( prefilters, s, options, jqXHR );
+
+ // If request was aborted inside a prefilter, stop there
+ if ( state === 2 ) {
+ return jqXHR;
+ }
+
+ // We can fire global events as of now if asked to
+ fireGlobals = s.global;
+
+ // Watch for a new set of requests
+ if ( fireGlobals && jQuery.active++ === 0 ) {
+ jQuery.event.trigger("ajaxStart");
+ }
+
+ // Uppercase the type
+ s.type = s.type.toUpperCase();
+
+ // Determine if request has content
+ s.hasContent = !rnoContent.test( s.type );
+
+ // Save the URL in case we're toying with the If-Modified-Since
+ // and/or If-None-Match header later on
+ cacheURL = s.url;
+
+ // More options handling for requests with no content
+ if ( !s.hasContent ) {
+
+ // If data is available, append data to url
+ if ( s.data ) {
+ cacheURL = ( s.url += ( ajax_rquery.test( cacheURL ) ? "&" : "?" ) + s.data );
+ // #9682: remove data so that it's not used in an eventual retry
+ delete s.data;
+ }
+
+ // Add anti-cache in url if needed
+ if ( s.cache === false ) {
+ s.url = rts.test( cacheURL ) ?
+
+ // If there is already a '_' parameter, set its value
+ cacheURL.replace( rts, "$1_=" + ajax_nonce++ ) :
+
+ // Otherwise add one to the end
+ cacheURL + ( ajax_rquery.test( cacheURL ) ? "&" : "?" ) + "_=" + ajax_nonce++;
+ }
+ }
+
+ // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.
+ if ( s.ifModified ) {
+ if ( jQuery.lastModified[ cacheURL ] ) {
+ jqXHR.setRequestHeader( "If-Modified-Since", jQuery.lastModified[ cacheURL ] );
+ }
+ if ( jQuery.etag[ cacheURL ] ) {
+ jqXHR.setRequestHeader( "If-None-Match", jQuery.etag[ cacheURL ] );
+ }
+ }
+
+ // Set the correct header, if data is being sent
+ if ( s.data && s.hasContent && s.contentType !== false || options.contentType ) {
+ jqXHR.setRequestHeader( "Content-Type", s.contentType );
+ }
+
+ // Set the Accepts header for the server, depending on the dataType
+ jqXHR.setRequestHeader(
+ "Accept",
+ s.dataTypes[ 0 ] && s.accepts[ s.dataTypes[0] ] ?
+ s.accepts[ s.dataTypes[0] ] + ( s.dataTypes[ 0 ] !== "*" ? ", " + allTypes + "; q=0.01" : "" ) :
+ s.accepts[ "*" ]
+ );
+
+ // Check for headers option
+ for ( i in s.headers ) {
+ jqXHR.setRequestHeader( i, s.headers[ i ] );
+ }
+
+ // Allow custom headers/mimetypes and early abort
+ if ( s.beforeSend && ( s.beforeSend.call( callbackContext, jqXHR, s ) === false || state === 2 ) ) {
+ // Abort if not done already and return
+ return jqXHR.abort();
+ }
+
+ // aborting is no longer a cancellation
+ strAbort = "abort";
+
+ // Install callbacks on deferreds
+ for ( i in { success: 1, error: 1, complete: 1 } ) {
+ jqXHR[ i ]( s[ i ] );
+ }
+
+ // Get transport
+ transport = inspectPrefiltersOrTransports( transports, s, options, jqXHR );
+
+ // If no transport, we auto-abort
+ if ( !transport ) {
+ done( -1, "No Transport" );
+ } else {
+ jqXHR.readyState = 1;
+
+ // Send global event
+ if ( fireGlobals ) {
+ globalEventContext.trigger( "ajaxSend", [ jqXHR, s ] );
+ }
+ // Timeout
+ if ( s.async && s.timeout > 0 ) {
+ timeoutTimer = setTimeout(function() {
+ jqXHR.abort("timeout");
+ }, s.timeout );
+ }
+
+ try {
+ state = 1;
+ transport.send( requestHeaders, done );
+ } catch ( e ) {
+ // Propagate exception as error if not done
+ if ( state < 2 ) {
+ done( -1, e );
+ // Simply rethrow otherwise
+ } else {
+ throw e;
+ }
+ }
+ }
+
+ // Callback for when everything is done
+ function done( status, nativeStatusText, responses, headers ) {
+ var isSuccess, success, error, response, modified,
+ statusText = nativeStatusText;
+
+ // Called once
+ if ( state === 2 ) {
+ return;
+ }
+
+ // State is "done" now
+ state = 2;
+
+ // Clear timeout if it exists
+ if ( timeoutTimer ) {
+ clearTimeout( timeoutTimer );
+ }
+
+ // Dereference transport for early garbage collection
+ // (no matter how long the jqXHR object will be used)
+ transport = undefined;
+
+ // Cache response headers
+ responseHeadersString = headers || "";
+
+ // Set readyState
+ jqXHR.readyState = status > 0 ? 4 : 0;
+
+ // Determine if successful
+ isSuccess = status >= 200 && status < 300 || status === 304;
+
+ // Get response data
+ if ( responses ) {
+ response = ajaxHandleResponses( s, jqXHR, responses );
+ }
+
+ // Convert no matter what (that way responseXXX fields are always set)
+ response = ajaxConvert( s, response, jqXHR, isSuccess );
+
+ // If successful, handle type chaining
+ if ( isSuccess ) {
+
+ // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.
+ if ( s.ifModified ) {
+ modified = jqXHR.getResponseHeader("Last-Modified");
+ if ( modified ) {
+ jQuery.lastModified[ cacheURL ] = modified;
+ }
+ modified = jqXHR.getResponseHeader("etag");
+ if ( modified ) {
+ jQuery.etag[ cacheURL ] = modified;
+ }
+ }
+
+ // if no content
+ if ( status === 204 || s.type === "HEAD" ) {
+ statusText = "nocontent";
+
+ // if not modified
+ } else if ( status === 304 ) {
+ statusText = "notmodified";
+
+ // If we have data, let's convert it
+ } else {
+ statusText = response.state;
+ success = response.data;
+ error = response.error;
+ isSuccess = !error;
+ }
+ } else {
+ // We extract error from statusText
+ // then normalize statusText and status for non-aborts
+ error = statusText;
+ if ( status || !statusText ) {
+ statusText = "error";
+ if ( status < 0 ) {
+ status = 0;
+ }
+ }
+ }
+
+ // Set data for the fake xhr object
+ jqXHR.status = status;
+ jqXHR.statusText = ( nativeStatusText || statusText ) + "";
+
+ // Success/Error
+ if ( isSuccess ) {
+ deferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] );
+ } else {
+ deferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] );
+ }
+
+ // Status-dependent callbacks
+ jqXHR.statusCode( statusCode );
+ statusCode = undefined;
+
+ if ( fireGlobals ) {
+ globalEventContext.trigger( isSuccess ? "ajaxSuccess" : "ajaxError",
+ [ jqXHR, s, isSuccess ? success : error ] );
+ }
+
+ // Complete
+ completeDeferred.fireWith( callbackContext, [ jqXHR, statusText ] );
+
+ if ( fireGlobals ) {
+ globalEventContext.trigger( "ajaxComplete", [ jqXHR, s ] );
+ // Handle the global AJAX counter
+ if ( !( --jQuery.active ) ) {
+ jQuery.event.trigger("ajaxStop");
+ }
+ }
+ }
+
+ return jqXHR;
+ },
+
+ getJSON: function( url, data, callback ) {
+ return jQuery.get( url, data, callback, "json" );
+ },
+
+ getScript: function( url, callback ) {
+ return jQuery.get( url, undefined, callback, "script" );
+ }
+});
+
+jQuery.each( [ "get", "post" ], function( i, method ) {
+ jQuery[ method ] = function( url, data, callback, type ) {
+ // shift arguments if data argument was omitted
+ if ( jQuery.isFunction( data ) ) {
+ type = type || callback;
+ callback = data;
+ data = undefined;
+ }
+
+ return jQuery.ajax({
+ url: url,
+ type: method,
+ dataType: type,
+ data: data,
+ success: callback
+ });
+ };
+});
+
+/* Handles responses to an ajax request:
+ * - finds the right dataType (mediates between content-type and expected dataType)
+ * - returns the corresponding response
+ */
+function ajaxHandleResponses( s, jqXHR, responses ) {
+
+ var ct, type, finalDataType, firstDataType,
+ contents = s.contents,
+ dataTypes = s.dataTypes;
+
+ // Remove auto dataType and get content-type in the process
+ while( dataTypes[ 0 ] === "*" ) {
+ dataTypes.shift();
+ if ( ct === undefined ) {
+ ct = s.mimeType || jqXHR.getResponseHeader("Content-Type");
+ }
+ }
+
+ // Check if we're dealing with a known content-type
+ if ( ct ) {
+ for ( type in contents ) {
+ if ( contents[ type ] && contents[ type ].test( ct ) ) {
+ dataTypes.unshift( type );
+ break;
+ }
+ }
+ }
+
+ // Check to see if we have a response for the expected dataType
+ if ( dataTypes[ 0 ] in responses ) {
+ finalDataType = dataTypes[ 0 ];
+ } else {
+ // Try convertible dataTypes
+ for ( type in responses ) {
+ if ( !dataTypes[ 0 ] || s.converters[ type + " " + dataTypes[0] ] ) {
+ finalDataType = type;
+ break;
+ }
+ if ( !firstDataType ) {
+ firstDataType = type;
+ }
+ }
+ // Or just use first one
+ finalDataType = finalDataType || firstDataType;
+ }
+
+ // If we found a dataType
+ // We add the dataType to the list if needed
+ // and return the corresponding response
+ if ( finalDataType ) {
+ if ( finalDataType !== dataTypes[ 0 ] ) {
+ dataTypes.unshift( finalDataType );
+ }
+ return responses[ finalDataType ];
+ }
+}
+
+/* Chain conversions given the request and the original response
+ * Also sets the responseXXX fields on the jqXHR instance
+ */
+function ajaxConvert( s, response, jqXHR, isSuccess ) {
+ var conv2, current, conv, tmp, prev,
+ converters = {},
+ // Work with a copy of dataTypes in case we need to modify it for conversion
+ dataTypes = s.dataTypes.slice();
+
+ // Create converters map with lowercased keys
+ if ( dataTypes[ 1 ] ) {
+ for ( conv in s.converters ) {
+ converters[ conv.toLowerCase() ] = s.converters[ conv ];
+ }
+ }
+
+ current = dataTypes.shift();
+
+ // Convert to each sequential dataType
+ while ( current ) {
+
+ if ( s.responseFields[ current ] ) {
+ jqXHR[ s.responseFields[ current ] ] = response;
+ }
+
+ // Apply the dataFilter if provided
+ if ( !prev && isSuccess && s.dataFilter ) {
+ response = s.dataFilter( response, s.dataType );
+ }
+
+ prev = current;
+ current = dataTypes.shift();
+
+ if ( current ) {
+
+ // There's only work to do if current dataType is non-auto
+ if ( current === "*" ) {
+
+ current = prev;
+
+ // Convert response if prev dataType is non-auto and differs from current
+ } else if ( prev !== "*" && prev !== current ) {
+
+ // Seek a direct converter
+ conv = converters[ prev + " " + current ] || converters[ "* " + current ];
+
+ // If none found, seek a pair
+ if ( !conv ) {
+ for ( conv2 in converters ) {
+
+ // If conv2 outputs current
+ tmp = conv2.split( " " );
+ if ( tmp[ 1 ] === current ) {
+
+ // If prev can be converted to accepted input
+ conv = converters[ prev + " " + tmp[ 0 ] ] ||
+ converters[ "* " + tmp[ 0 ] ];
+ if ( conv ) {
+ // Condense equivalence converters
+ if ( conv === true ) {
+ conv = converters[ conv2 ];
+
+ // Otherwise, insert the intermediate dataType
+ } else if ( converters[ conv2 ] !== true ) {
+ current = tmp[ 0 ];
+ dataTypes.unshift( tmp[ 1 ] );
+ }
+ break;
+ }
+ }
+ }
+ }
+
+ // Apply converter (if not an equivalence)
+ if ( conv !== true ) {
+
+ // Unless errors are allowed to bubble, catch and return them
+ if ( conv && s[ "throws" ] ) {
+ response = conv( response );
+ } else {
+ try {
+ response = conv( response );
+ } catch ( e ) {
+ return { state: "parsererror", error: conv ? e : "No conversion from " + prev + " to " + current };
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return { state: "success", data: response };
+}
diff --git a/js/jquery/src/jquery/ajax/jsonp.js b/js/jquery/src/jquery/ajax/jsonp.js
new file mode 100644
index 0000000000..b8803df444
--- /dev/null
+++ b/js/jquery/src/jquery/ajax/jsonp.js
@@ -0,0 +1,80 @@
+var oldCallbacks = [],
+ rjsonp = /(=)\?(?=&|$)|\?\?/;
+
+// Default jsonp settings
+jQuery.ajaxSetup({
+ jsonp: "callback",
+ jsonpCallback: function() {
+ var callback = oldCallbacks.pop() || ( jQuery.expando + "_" + ( ajax_nonce++ ) );
+ this[ callback ] = true;
+ return callback;
+ }
+});
+
+// Detect, normalize options and install callbacks for jsonp requests
+jQuery.ajaxPrefilter( "json jsonp", function( s, originalSettings, jqXHR ) {
+
+ var callbackName, overwritten, responseContainer,
+ jsonProp = s.jsonp !== false && ( rjsonp.test( s.url ) ?
+ "url" :
+ typeof s.data === "string" && !( s.contentType || "" ).indexOf("application/x-www-form-urlencoded") && rjsonp.test( s.data ) && "data"
+ );
+
+ // Handle iff the expected data type is "jsonp" or we have a parameter to set
+ if ( jsonProp || s.dataTypes[ 0 ] === "jsonp" ) {
+
+ // Get callback name, remembering preexisting value associated with it
+ callbackName = s.jsonpCallback = jQuery.isFunction( s.jsonpCallback ) ?
+ s.jsonpCallback() :
+ s.jsonpCallback;
+
+ // Insert callback into url or form data
+ if ( jsonProp ) {
+ s[ jsonProp ] = s[ jsonProp ].replace( rjsonp, "$1" + callbackName );
+ } else if ( s.jsonp !== false ) {
+ s.url += ( ajax_rquery.test( s.url ) ? "&" : "?" ) + s.jsonp + "=" + callbackName;
+ }
+
+ // Use data converter to retrieve json after script execution
+ s.converters["script json"] = function() {
+ if ( !responseContainer ) {
+ jQuery.error( callbackName + " was not called" );
+ }
+ return responseContainer[ 0 ];
+ };
+
+ // force json dataType
+ s.dataTypes[ 0 ] = "json";
+
+ // Install callback
+ overwritten = window[ callbackName ];
+ window[ callbackName ] = function() {
+ responseContainer = arguments;
+ };
+
+ // Clean-up function (fires after converters)
+ jqXHR.always(function() {
+ // Restore preexisting value
+ window[ callbackName ] = overwritten;
+
+ // Save back as free
+ if ( s[ callbackName ] ) {
+ // make sure that re-using the options doesn't screw things around
+ s.jsonpCallback = originalSettings.jsonpCallback;
+
+ // save the callback name for future use
+ oldCallbacks.push( callbackName );
+ }
+
+ // Call if it was a function and we have a response
+ if ( responseContainer && jQuery.isFunction( overwritten ) ) {
+ overwritten( responseContainer[ 0 ] );
+ }
+
+ responseContainer = overwritten = undefined;
+ });
+
+ // Delegate to script
+ return "script";
+ }
+});
diff --git a/js/jquery/src/jquery/ajax/script.js b/js/jquery/src/jquery/ajax/script.js
new file mode 100644
index 0000000000..fe0562a84a
--- /dev/null
+++ b/js/jquery/src/jquery/ajax/script.js
@@ -0,0 +1,57 @@
+// Install script dataType
+jQuery.ajaxSetup({
+ accepts: {
+ script: "text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"
+ },
+ contents: {
+ script: /(?:java|ecma)script/
+ },
+ converters: {
+ "text script": function( text ) {
+ jQuery.globalEval( text );
+ return text;
+ }
+ }
+});
+
+// Handle cache's special case and crossDomain
+jQuery.ajaxPrefilter( "script", function( s ) {
+ if ( s.cache === undefined ) {
+ s.cache = false;
+ }
+ if ( s.crossDomain ) {
+ s.type = "GET";
+ }
+});
+
+// Bind script tag hack transport
+jQuery.ajaxTransport( "script", function( s ) {
+ // This transport only deals with cross domain requests
+ if ( s.crossDomain ) {
+ var script, callback;
+ return {
+ send: function( _, complete ) {
+ script = jQuery("<script>").prop({
+ async: true,
+ charset: s.scriptCharset,
+ src: s.url
+ }).on(
+ "load error",
+ callback = function( evt ) {
+ script.remove();
+ callback = null;
+ if ( evt ) {
+ complete( evt.type === "error" ? 404 : 200, evt.type );
+ }
+ }
+ );
+ document.head.appendChild( script[ 0 ] );
+ },
+ abort: function() {
+ if ( callback ) {
+ callback();
+ }
+ }
+ };
+ }
+});
diff --git a/js/jquery/src/jquery/ajax/xhr.js b/js/jquery/src/jquery/ajax/xhr.js
new file mode 100644
index 0000000000..3a9c9e9686
--- /dev/null
+++ b/js/jquery/src/jquery/ajax/xhr.js
@@ -0,0 +1,111 @@
+jQuery.ajaxSettings.xhr = function() {
+ try {
+ return new XMLHttpRequest();
+ } catch( e ) {}
+};
+
+var xhrSupported = jQuery.ajaxSettings.xhr(),
+ xhrSuccessStatus = {
+ // file protocol always yields status code 0, assume 200
+ 0: 200,
+ // Support: IE9
+ // #1450: sometimes IE returns 1223 when it should be 204
+ 1223: 204
+ },
+ // Support: IE9
+ // We need to keep track of outbound xhr and abort them manually
+ // because IE is not smart enough to do it all by itself
+ xhrId = 0,
+ xhrCallbacks = {};
+
+if ( window.ActiveXObject ) {
+ jQuery( window ).on( "unload", function() {
+ for( var key in xhrCallbacks ) {
+ xhrCallbacks[ key ]();
+ }
+ xhrCallbacks = undefined;
+ });
+}
+
+jQuery.support.cors = !!xhrSupported && ( "withCredentials" in xhrSupported );
+jQuery.support.ajax = xhrSupported = !!xhrSupported;
+
+jQuery.ajaxTransport(function( options ) {
+ var callback;
+ // Cross domain only allowed if supported through XMLHttpRequest
+ if ( jQuery.support.cors || xhrSupported && !options.crossDomain ) {
+ return {
+ send: function( headers, complete ) {
+ var i, id,
+ xhr = options.xhr();
+ xhr.open( options.type, options.url, options.async, options.username, options.password );
+ // Apply custom fields if provided
+ if ( options.xhrFields ) {
+ for ( i in options.xhrFields ) {
+ xhr[ i ] = options.xhrFields[ i ];
+ }
+ }
+ // Override mime type if needed
+ if ( options.mimeType && xhr.overrideMimeType ) {
+ xhr.overrideMimeType( options.mimeType );
+ }
+ // X-Requested-With header
+ // For cross-domain requests, seeing as conditions for a preflight are
+ // akin to a jigsaw puzzle, we simply never set it to be sure.
+ // (it can always be set on a per-request basis or even using ajaxSetup)
+ // For same-domain requests, won't change header if already provided.
+ if ( !options.crossDomain && !headers["X-Requested-With"] ) {
+ headers["X-Requested-With"] = "XMLHttpRequest";
+ }
+ // Set headers
+ for ( i in headers ) {
+ xhr.setRequestHeader( i, headers[ i ] );
+ }
+ // Callback
+ callback = function( type ) {
+ return function() {
+ if ( callback ) {
+ delete xhrCallbacks[ id ];
+ callback = xhr.onload = xhr.onerror = null;
+ if ( type === "abort" ) {
+ xhr.abort();
+ } else if ( type === "error" ) {
+ complete(
+ // file protocol always yields status 0, assume 404
+ xhr.status || 404,
+ xhr.statusText
+ );
+ } else {
+ complete(
+ xhrSuccessStatus[ xhr.status ] || xhr.status,
+ xhr.statusText,
+ // Support: IE9
+ // #11426: When requesting binary data, IE9 will throw an exception
+ // on any attempt to access responseText
+ typeof xhr.responseText === "string" ? {
+ text: xhr.responseText
+ } : undefined,
+ xhr.getAllResponseHeaders()
+ );
+ }
+ }
+ };
+ };
+ // Listen to events
+ xhr.onload = callback();
+ xhr.onerror = callback("error");
+ // Create the abort callback
+ callback = xhrCallbacks[( id = xhrId++ )] = callback("abort");
+ // Do send the request
+ // This may raise an exception which is actually
+ // handled in jQuery.ajax (so no try/catch here)
+ xhr.send( options.hasContent && options.data || null );
+ },
+ abort: function() {
+ if ( callback ) {
+ callback();
+ }
+ }
+ };
+ }
+});
diff --git a/js/jquery/src/jquery/attributes.js b/js/jquery/src/jquery/attributes.js
new file mode 100644
index 0000000000..4da2db6c6b
--- /dev/null
+++ b/js/jquery/src/jquery/attributes.js
@@ -0,0 +1,502 @@
+var nodeHook, boolHook,
+ rclass = /[\t\r\n\f]/g,
+ rreturn = /\r/g,
+ rfocusable = /^(?:input|select|textarea|button)$/i;
+
+jQuery.fn.extend({
+ attr: function( name, value ) {
+ return jQuery.access( this, jQuery.attr, name, value, arguments.length > 1 );
+ },
+
+ removeAttr: function( name ) {
+ return this.each(function() {
+ jQuery.removeAttr( this, name );
+ });
+ },
+
+ prop: function( name, value ) {
+ return jQuery.access( this, jQuery.prop, name, value, arguments.length > 1 );
+ },
+
+ removeProp: function( name ) {
+ return this.each(function() {
+ delete this[ jQuery.propFix[ name ] || name ];
+ });
+ },
+
+ addClass: function( value ) {
+ var classes, elem, cur, clazz, j,
+ i = 0,
+ len = this.length,
+ proceed = typeof value === "string" && value;
+
+ if ( jQuery.isFunction( value ) ) {
+ return this.each(function( j ) {
+ jQuery( this ).addClass( value.call( this, j, this.className ) );
+ });
+ }
+
+ if ( proceed ) {
+ // The disjunction here is for better compressibility (see removeClass)
+ classes = ( value || "" ).match( core_rnotwhite ) || [];
+
+ for ( ; i < len; i++ ) {
+ elem = this[ i ];
+ cur = elem.nodeType === 1 && ( elem.className ?
+ ( " " + elem.className + " " ).replace( rclass, " " ) :
+ " "
+ );
+
+ if ( cur ) {
+ j = 0;
+ while ( (clazz = classes[j++]) ) {
+ if ( cur.indexOf( " " + clazz + " " ) < 0 ) {
+ cur += clazz + " ";
+ }
+ }
+ elem.className = jQuery.trim( cur );
+
+ }
+ }
+ }
+
+ return this;
+ },
+
+ removeClass: function( value ) {
+ var classes, elem, cur, clazz, j,
+ i = 0,
+ len = this.length,
+ proceed = arguments.length === 0 || typeof value === "string" && value;
+
+ if ( jQuery.isFunction( value ) ) {
+ return this.each(function( j ) {
+ jQuery( this ).removeClass( value.call( this, j, this.className ) );
+ });
+ }
+ if ( proceed ) {
+ classes = ( value || "" ).match( core_rnotwhite ) || [];
+
+ for ( ; i < len; i++ ) {
+ elem = this[ i ];
+ // This expression is here for better compressibility (see addClass)
+ cur = elem.nodeType === 1 && ( elem.className ?
+ ( " " + elem.className + " " ).replace( rclass, " " ) :
+ ""
+ );
+
+ if ( cur ) {
+ j = 0;
+ while ( (clazz = classes[j++]) ) {
+ // Remove *all* instances
+ while ( cur.indexOf( " " + clazz + " " ) >= 0 ) {
+ cur = cur.replace( " " + clazz + " ", " " );
+ }
+ }
+ elem.className = value ? jQuery.trim( cur ) : "";
+ }
+ }
+ }
+
+ return this;
+ },
+
+ toggleClass: function( value, stateVal ) {
+ var type = typeof value;
+
+ if ( typeof stateVal === "boolean" && type === "string" ) {
+ return stateVal ? this.addClass( value ) : this.removeClass( value );
+ }
+
+ if ( jQuery.isFunction( value ) ) {
+ return this.each(function( i ) {
+ jQuery( this ).toggleClass( value.call(this, i, this.className, stateVal), stateVal );
+ });
+ }
+
+ return this.each(function() {
+ if ( type === "string" ) {
+ // toggle individual class names
+ var className,
+ i = 0,
+ self = jQuery( this ),
+ classNames = value.match( core_rnotwhite ) || [];
+
+ while ( (className = classNames[ i++ ]) ) {
+ // check each className given, space separated list
+ if ( self.hasClass( className ) ) {
+ self.removeClass( className );
+ } else {
+ self.addClass( className );
+ }
+ }
+
+ // Toggle whole class name
+ } else if ( type === core_strundefined || type === "boolean" ) {
+ if ( this.className ) {
+ // store className if set
+ data_priv.set( this, "__className__", this.className );
+ }
+
+ // If the element has a class name or if we're passed "false",
+ // then remove the whole classname (if there was one, the above saved it).
+ // Otherwise bring back whatever was previously saved (if anything),
+ // falling back to the empty string if nothing was stored.
+ this.className = this.className || value === false ? "" : data_priv.get( this, "__className__" ) || "";
+ }
+ });
+ },
+
+ hasClass: function( selector ) {
+ var className = " " + selector + " ",
+ i = 0,
+ l = this.length;
+ for ( ; i < l; i++ ) {
+ if ( this[i].nodeType === 1 && (" " + this[i].className + " ").replace(rclass, " ").indexOf( className ) >= 0 ) {
+ return true;
+ }
+ }
+
+ return false;
+ },
+
+ val: function( value ) {
+ var hooks, ret, isFunction,
+ elem = this[0];
+
+ if ( !arguments.length ) {
+ if ( elem ) {
+ hooks = jQuery.valHooks[ elem.type ] || jQuery.valHooks[ elem.nodeName.toLowerCase() ];
+
+ if ( hooks && "get" in hooks && (ret = hooks.get( elem, "value" )) !== undefined ) {
+ return ret;
+ }
+
+ ret = elem.value;
+
+ return typeof ret === "string" ?
+ // handle most common string cases
+ ret.replace(rreturn, "") :
+ // handle cases where value is null/undef or number
+ ret == null ? "" : ret;
+ }
+
+ return;
+ }
+
+ isFunction = jQuery.isFunction( value );
+
+ return this.each(function( i ) {
+ var val;
+
+ if ( this.nodeType !== 1 ) {
+ return;
+ }
+
+ if ( isFunction ) {
+ val = value.call( this, i, jQuery( this ).val() );
+ } else {
+ val = value;
+ }
+
+ // Treat null/undefined as ""; convert numbers to string
+ if ( val == null ) {
+ val = "";
+ } else if ( typeof val === "number" ) {
+ val += "";
+ } else if ( jQuery.isArray( val ) ) {
+ val = jQuery.map(val, function ( value ) {
+ return value == null ? "" : value + "";
+ });
+ }
+
+ hooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ];
+
+ // If set returns undefined, fall back to normal setting
+ if ( !hooks || !("set" in hooks) || hooks.set( this, val, "value" ) === undefined ) {
+ this.value = val;
+ }
+ });
+ }
+});
+
+jQuery.extend({
+ valHooks: {
+ option: {
+ get: function( elem ) {
+ // attributes.value is undefined in Blackberry 4.7 but
+ // uses .value. See #6932
+ var val = elem.attributes.value;
+ return !val || val.specified ? elem.value : elem.text;
+ }
+ },
+ select: {
+ get: function( elem ) {
+ var value, option,
+ options = elem.options,
+ index = elem.selectedIndex,
+ one = elem.type === "select-one" || index < 0,
+ values = one ? null : [],
+ max = one ? index + 1 : options.length,
+ i = index < 0 ?
+ max :
+ one ? index : 0;
+
+ // Loop through all the selected options
+ for ( ; i < max; i++ ) {
+ option = options[ i ];
+
+ // IE6-9 doesn't update selected after form reset (#2551)
+ if ( ( option.selected || i === index ) &&
+ // Don't return options that are disabled or in a disabled optgroup
+ ( jQuery.support.optDisabled ? !option.disabled : option.getAttribute("disabled") === null ) &&
+ ( !option.parentNode.disabled || !jQuery.nodeName( option.parentNode, "optgroup" ) ) ) {
+
+ // Get the specific value for the option
+ value = jQuery( option ).val();
+
+ // We don't need an array for one selects
+ if ( one ) {
+ return value;
+ }
+
+ // Multi-Selects return an array
+ values.push( value );
+ }
+ }
+
+ return values;
+ },
+
+ set: function( elem, value ) {
+ var optionSet, option,
+ options = elem.options,
+ values = jQuery.makeArray( value ),
+ i = options.length;
+
+ while ( i-- ) {
+ option = options[ i ];
+ if ( (option.selected = jQuery.inArray( jQuery(option).val(), values ) >= 0) ) {
+ optionSet = true;
+ }
+ }
+
+ // force browsers to behave consistently when non-matching value is set
+ if ( !optionSet ) {
+ elem.selectedIndex = -1;
+ }
+ return values;
+ }
+ }
+ },
+
+ attr: function( elem, name, value ) {
+ var hooks, ret,
+ nType = elem.nodeType;
+
+ // don't get/set attributes on text, comment and attribute nodes
+ if ( !elem || nType === 3 || nType === 8 || nType === 2 ) {
+ return;
+ }
+
+ // Fallback to prop when attributes are not supported
+ if ( typeof elem.getAttribute === core_strundefined ) {
+ return jQuery.prop( elem, name, value );
+ }
+
+ // All attributes are lowercase
+ // Grab necessary hook if one is defined
+ if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) {
+ name = name.toLowerCase();
+ hooks = jQuery.attrHooks[ name ] ||
+ ( jQuery.expr.match.bool.test( name ) ? boolHook : nodeHook );
+ }
+
+ if ( value !== undefined ) {
+
+ if ( value === null ) {
+ jQuery.removeAttr( elem, name );
+
+ } else if ( hooks && "set" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ) {
+ return ret;
+
+ } else {
+ elem.setAttribute( name, value + "" );
+ return value;
+ }
+
+ } else if ( hooks && "get" in hooks && (ret = hooks.get( elem, name )) !== null ) {
+ return ret;
+
+ } else {
+ ret = jQuery.find.attr( elem, name );
+
+ // Non-existent attributes return null, we normalize to undefined
+ return ret == null ?
+ undefined :
+ ret;
+ }
+ },
+
+ removeAttr: function( elem, value ) {
+ var name, propName,
+ i = 0,
+ attrNames = value && value.match( core_rnotwhite );
+
+ if ( attrNames && elem.nodeType === 1 ) {
+ while ( (name = attrNames[i++]) ) {
+ propName = jQuery.propFix[ name ] || name;
+
+ // Boolean attributes get special treatment (#10870)
+ if ( jQuery.expr.match.bool.test( name ) ) {
+ // Set corresponding property to false
+ elem[ propName ] = false;
+ }
+
+ elem.removeAttribute( name );
+ }
+ }
+ },
+
+ attrHooks: {
+ type: {
+ set: function( elem, value ) {
+ if ( !jQuery.support.radioValue && value === "radio" && jQuery.nodeName(elem, "input") ) {
+ // Setting the type on a radio button after the value resets the value in IE6-9
+ // Reset value to default in case type is set after value during creation
+ var val = elem.value;
+ elem.setAttribute( "type", value );
+ if ( val ) {
+ elem.value = val;
+ }
+ return value;
+ }
+ }
+ }
+ },
+
+ propFix: {
+ "for": "htmlFor",
+ "class": "className"
+ },
+
+ prop: function( elem, name, value ) {
+ var ret, hooks, notxml,
+ nType = elem.nodeType;
+
+ // don't get/set properties on text, comment and attribute nodes
+ if ( !elem || nType === 3 || nType === 8 || nType === 2 ) {
+ return;
+ }
+
+ notxml = nType !== 1 || !jQuery.isXMLDoc( elem );
+
+ if ( notxml ) {
+ // Fix name and attach hooks
+ name = jQuery.propFix[ name ] || name;
+ hooks = jQuery.propHooks[ name ];
+ }
+
+ if ( value !== undefined ) {
+ return hooks && "set" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ?
+ ret :
+ ( elem[ name ] = value );
+
+ } else {
+ return hooks && "get" in hooks && (ret = hooks.get( elem, name )) !== null ?
+ ret :
+ elem[ name ];
+ }
+ },
+
+ propHooks: {
+ tabIndex: {
+ get: function( elem ) {
+ return elem.hasAttribute( "tabindex" ) || rfocusable.test( elem.nodeName ) || elem.href ?
+ elem.tabIndex :
+ -1;
+ }
+ }
+ }
+});
+
+// Hooks for boolean attributes
+boolHook = {
+ set: function( elem, value, name ) {
+ if ( value === false ) {
+ // Remove boolean attributes when set to false
+ jQuery.removeAttr( elem, name );
+ } else {
+ elem.setAttribute( name, name );
+ }
+ return name;
+ }
+};
+jQuery.each( jQuery.expr.match.bool.source.match( /\w+/g ), function( i, name ) {
+ var getter = jQuery.expr.attrHandle[ name ] || jQuery.find.attr;
+
+ jQuery.expr.attrHandle[ name ] = function( elem, name, isXML ) {
+ var fn = jQuery.expr.attrHandle[ name ],
+ ret = isXML ?
+ undefined :
+ /* jshint eqeqeq: false */
+ // Temporarily disable this handler to check existence
+ (jQuery.expr.attrHandle[ name ] = undefined) !=
+ getter( elem, name, isXML ) ?
+
+ name.toLowerCase() :
+ null;
+
+ // Restore handler
+ jQuery.expr.attrHandle[ name ] = fn;
+
+ return ret;
+ };
+});
+
+// Support: IE9+
+// Selectedness for an option in an optgroup can be inaccurate
+if ( !jQuery.support.optSelected ) {
+ jQuery.propHooks.selected = {
+ get: function( elem ) {
+ var parent = elem.parentNode;
+ if ( parent && parent.parentNode ) {
+ parent.parentNode.selectedIndex;
+ }
+ return null;
+ }
+ };
+}
+
+jQuery.each([
+ "tabIndex",
+ "readOnly",
+ "maxLength",
+ "cellSpacing",
+ "cellPadding",
+ "rowSpan",
+ "colSpan",
+ "useMap",
+ "frameBorder",
+ "contentEditable"
+], function() {
+ jQuery.propFix[ this.toLowerCase() ] = this;
+});
+
+// Radios and checkboxes getter/setter
+jQuery.each([ "radio", "checkbox" ], function() {
+ jQuery.valHooks[ this ] = {
+ set: function( elem, value ) {
+ if ( jQuery.isArray( value ) ) {
+ return ( elem.checked = jQuery.inArray( jQuery(elem).val(), value ) >= 0 );
+ }
+ }
+ };
+ if ( !jQuery.support.checkOn ) {
+ jQuery.valHooks[ this ].get = function( elem ) {
+ // Support: Webkit
+ // "" is returned instead of "on" if a value isn't specified
+ return elem.getAttribute("value") === null ? "on" : elem.value;
+ };
+ }
+});
diff --git a/js/jquery/src/jquery/callbacks.js b/js/jquery/src/jquery/callbacks.js
new file mode 100644
index 0000000000..84c8346667
--- /dev/null
+++ b/js/jquery/src/jquery/callbacks.js
@@ -0,0 +1,197 @@
+// String to Object options format cache
+var optionsCache = {};
+
+// Convert String-formatted options into Object-formatted ones and store in cache
+function createOptions( options ) {
+ var object = optionsCache[ options ] = {};
+ jQuery.each( options.match( core_rnotwhite ) || [], function( _, flag ) {
+ object[ flag ] = true;
+ });
+ return object;
+}
+
+/*
+ * Create a callback list using the following parameters:
+ *
+ * options: an optional list of space-separated options that will change how
+ * the callback list behaves or a more traditional option object
+ *
+ * By default a callback list will act like an event callback list and can be
+ * "fired" multiple times.
+ *
+ * Possible options:
+ *
+ * once: will ensure the callback list can only be fired once (like a Deferred)
+ *
+ * memory: will keep track of previous values and will call any callback added
+ * after the list has been fired right away with the latest "memorized"
+ * values (like a Deferred)
+ *
+ * unique: will ensure a callback can only be added once (no duplicate in the list)
+ *
+ * stopOnFalse: interrupt callings when a callback returns false
+ *
+ */
+jQuery.Callbacks = function( options ) {
+
+ // Convert options from String-formatted to Object-formatted if needed
+ // (we check in cache first)
+ options = typeof options === "string" ?
+ ( optionsCache[ options ] || createOptions( options ) ) :
+ jQuery.extend( {}, options );
+
+ var // Last fire value (for non-forgettable lists)
+ memory,
+ // Flag to know if list was already fired
+ fired,
+ // Flag to know if list is currently firing
+ firing,
+ // First callback to fire (used internally by add and fireWith)
+ firingStart,
+ // End of the loop when firing
+ firingLength,
+ // Index of currently firing callback (modified by remove if needed)
+ firingIndex,
+ // Actual callback list
+ list = [],
+ // Stack of fire calls for repeatable lists
+ stack = !options.once && [],
+ // Fire callbacks
+ fire = function( data ) {
+ memory = options.memory && data;
+ fired = true;
+ firingIndex = firingStart || 0;
+ firingStart = 0;
+ firingLength = list.length;
+ firing = true;
+ for ( ; list && firingIndex < firingLength; firingIndex++ ) {
+ if ( list[ firingIndex ].apply( data[ 0 ], data[ 1 ] ) === false && options.stopOnFalse ) {
+ memory = false; // To prevent further calls using add
+ break;
+ }
+ }
+ firing = false;
+ if ( list ) {
+ if ( stack ) {
+ if ( stack.length ) {
+ fire( stack.shift() );
+ }
+ } else if ( memory ) {
+ list = [];
+ } else {
+ self.disable();
+ }
+ }
+ },
+ // Actual Callbacks object
+ self = {
+ // Add a callback or a collection of callbacks to the list
+ add: function() {
+ if ( list ) {
+ // First, we save the current length
+ var start = list.length;
+ (function add( args ) {
+ jQuery.each( args, function( _, arg ) {
+ var type = jQuery.type( arg );
+ if ( type === "function" ) {
+ if ( !options.unique || !self.has( arg ) ) {
+ list.push( arg );
+ }
+ } else if ( arg && arg.length && type !== "string" ) {
+ // Inspect recursively
+ add( arg );
+ }
+ });
+ })( arguments );
+ // Do we need to add the callbacks to the
+ // current firing batch?
+ if ( firing ) {
+ firingLength = list.length;
+ // With memory, if we're not firing then
+ // we should call right away
+ } else if ( memory ) {
+ firingStart = start;
+ fire( memory );
+ }
+ }
+ return this;
+ },
+ // Remove a callback from the list
+ remove: function() {
+ if ( list ) {
+ jQuery.each( arguments, function( _, arg ) {
+ var index;
+ while( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) {
+ list.splice( index, 1 );
+ // Handle firing indexes
+ if ( firing ) {
+ if ( index <= firingLength ) {
+ firingLength--;
+ }
+ if ( index <= firingIndex ) {
+ firingIndex--;
+ }
+ }
+ }
+ });
+ }
+ return this;
+ },
+ // Check if a given callback is in the list.
+ // If no argument is given, return whether or not list has callbacks attached.
+ has: function( fn ) {
+ return fn ? jQuery.inArray( fn, list ) > -1 : !!( list && list.length );
+ },
+ // Remove all callbacks from the list
+ empty: function() {
+ list = [];
+ firingLength = 0;
+ return this;
+ },
+ // Have the list do nothing anymore
+ disable: function() {
+ list = stack = memory = undefined;
+ return this;
+ },
+ // Is it disabled?
+ disabled: function() {
+ return !list;
+ },
+ // Lock the list in its current state
+ lock: function() {
+ stack = undefined;
+ if ( !memory ) {
+ self.disable();
+ }
+ return this;
+ },
+ // Is it locked?
+ locked: function() {
+ return !stack;
+ },
+ // Call all callbacks with the given context and arguments
+ fireWith: function( context, args ) {
+ if ( list && ( !fired || stack ) ) {
+ args = args || [];
+ args = [ context, args.slice ? args.slice() : args ];
+ if ( firing ) {
+ stack.push( args );
+ } else {
+ fire( args );
+ }
+ }
+ return this;
+ },
+ // Call all the callbacks with the given arguments
+ fire: function() {
+ self.fireWith( this, arguments );
+ return this;
+ },
+ // To know if the callbacks have already been called at least once
+ fired: function() {
+ return !!fired;
+ }
+ };
+
+ return self;
+};
diff --git a/js/jquery/src/jquery/core.js b/js/jquery/src/jquery/core.js
new file mode 100644
index 0000000000..55a0c25d32
--- /dev/null
+++ b/js/jquery/src/jquery/core.js
@@ -0,0 +1,846 @@
+var
+ // A central reference to the root jQuery(document)
+ rootjQuery,
+
+ // The deferred used on DOM ready
+ readyList,
+
+ // Support: IE9
+ // For `typeof xmlNode.method` instead of `xmlNode.method !== undefined`
+ core_strundefined = typeof undefined,
+
+ // Use the correct document accordingly with window argument (sandbox)
+ location = window.location,
+ document = window.document,
+ docElem = document.documentElement,
+
+ // Map over jQuery in case of overwrite
+ _jQuery = window.jQuery,
+
+ // Map over the $ in case of overwrite
+ _$ = window.$,
+
+ // [[Class]] -> type pairs
+ class2type = {},
+
+ // List of deleted data cache ids, so we can reuse them
+ core_deletedIds = [],
+
+ core_version = "@VERSION",
+
+ // Save a reference to some core methods
+ core_concat = core_deletedIds.concat,
+ core_push = core_deletedIds.push,
+ core_slice = core_deletedIds.slice,
+ core_indexOf = core_deletedIds.indexOf,
+ core_toString = class2type.toString,
+ core_hasOwn = class2type.hasOwnProperty,
+ core_trim = core_version.trim,
+
+ // Define a local copy of jQuery
+ jQuery = function( selector, context ) {
+ // The jQuery object is actually just the init constructor 'enhanced'
+ return new jQuery.fn.init( selector, context, rootjQuery );
+ },
+
+ // Used for matching numbers
+ core_pnum = /[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/.source,
+
+ // Used for splitting on whitespace
+ core_rnotwhite = /\S+/g,
+
+ // A simple way to check for HTML strings
+ // Prioritize #id over <tag> to avoid XSS via location.hash (#9521)
+ // Strict HTML recognition (#11290: must start with <)
+ rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,
+
+ // Match a standalone tag
+ rsingleTag = /^<(\w+)\s*\/?>(?:<\/\1>|)$/,
+
+ // Matches dashed string for camelizing
+ rmsPrefix = /^-ms-/,
+ rdashAlpha = /-([\da-z])/gi,
+
+ // Used by jQuery.camelCase as callback to replace()
+ fcamelCase = function( all, letter ) {
+ return letter.toUpperCase();
+ },
+
+ // The ready event handler and self cleanup method
+ completed = function() {
+ document.removeEventListener( "DOMContentLoaded", completed, false );
+ window.removeEventListener( "load", completed, false );
+ jQuery.ready();
+ };
+
+jQuery.fn = jQuery.prototype = {
+ // The current version of jQuery being used
+ jquery: core_version,
+
+ constructor: jQuery,
+ init: function( selector, context, rootjQuery ) {
+ var match, elem;
+
+ // HANDLE: $(""), $(null), $(undefined), $(false)
+ if ( !selector ) {
+ return this;
+ }
+
+ // Handle HTML strings
+ if ( typeof selector === "string" ) {
+ if ( selector.charAt(0) === "<" && selector.charAt( selector.length - 1 ) === ">" && selector.length >= 3 ) {
+ // Assume that strings that start and end with <> are HTML and skip the regex check
+ match = [ null, selector, null ];
+
+ } else {
+ match = rquickExpr.exec( selector );
+ }
+
+ // Match html or make sure no context is specified for #id
+ if ( match && (match[1] || !context) ) {
+
+ // HANDLE: $(html) -> $(array)
+ if ( match[1] ) {
+ context = context instanceof jQuery ? context[0] : context;
+
+ // scripts is true for back-compat
+ jQuery.merge( this, jQuery.parseHTML(
+ match[1],
+ context && context.nodeType ? context.ownerDocument || context : document,
+ true
+ ) );
+
+ // HANDLE: $(html, props)
+ if ( rsingleTag.test( match[1] ) && jQuery.isPlainObject( context ) ) {
+ for ( match in context ) {
+ // Properties of context are called as methods if possible
+ if ( jQuery.isFunction( this[ match ] ) ) {
+ this[ match ]( context[ match ] );
+
+ // ...and otherwise set as attributes
+ } else {
+ this.attr( match, context[ match ] );
+ }
+ }
+ }
+
+ return this;
+
+ // HANDLE: $(#id)
+ } else {
+ elem = document.getElementById( match[2] );
+
+ // Check parentNode to catch when Blackberry 4.6 returns
+ // nodes that are no longer in the document #6963
+ if ( elem && elem.parentNode ) {
+ // Inject the element directly into the jQuery object
+ this.length = 1;
+ this[0] = elem;
+ }
+
+ this.context = document;
+ this.selector = selector;
+ return this;
+ }
+
+ // HANDLE: $(expr, $(...))
+ } else if ( !context || context.jquery ) {
+ return ( context || rootjQuery ).find( selector );
+
+ // HANDLE: $(expr, context)
+ // (which is just equivalent to: $(context).find(expr)
+ } else {
+ return this.constructor( context ).find( selector );
+ }
+
+ // HANDLE: $(DOMElement)
+ } else if ( selector.nodeType ) {
+ this.context = this[0] = selector;
+ this.length = 1;
+ return this;
+
+ // HANDLE: $(function)
+ // Shortcut for document ready
+ } else if ( jQuery.isFunction( selector ) ) {
+ return rootjQuery.ready( selector );
+ }
+
+ if ( selector.selector !== undefined ) {
+ this.selector = selector.selector;
+ this.context = selector.context;
+ }
+
+ return jQuery.makeArray( selector, this );
+ },
+
+ // Start with an empty selector
+ selector: "",
+
+ // The default length of a jQuery object is 0
+ length: 0,
+
+ toArray: function() {
+ return core_slice.call( this );
+ },
+
+ // Get the Nth element in the matched element set OR
+ // Get the whole matched element set as a clean array
+ get: function( num ) {
+ return num == null ?
+
+ // Return a 'clean' array
+ this.toArray() :
+
+ // Return just the object
+ ( num < 0 ? this[ this.length + num ] : this[ num ] );
+ },
+
+ // Take an array of elements and push it onto the stack
+ // (returning the new matched element set)
+ pushStack: function( elems ) {
+
+ // Build a new jQuery matched element set
+ var ret = jQuery.merge( this.constructor(), elems );
+
+ // Add the old object onto the stack (as a reference)
+ ret.prevObject = this;
+ ret.context = this.context;
+
+ // Return the newly-formed element set
+ return ret;
+ },
+
+ // Execute a callback for every element in the matched set.
+ // (You can seed the arguments with an array of args, but this is
+ // only used internally.)
+ each: function( callback, args ) {
+ return jQuery.each( this, callback, args );
+ },
+
+ ready: function( fn ) {
+ // Add the callback
+ jQuery.ready.promise().done( fn );
+
+ return this;
+ },
+
+ slice: function() {
+ return this.pushStack( core_slice.apply( this, arguments ) );
+ },
+
+ first: function() {
+ return this.eq( 0 );
+ },
+
+ last: function() {
+ return this.eq( -1 );
+ },
+
+ eq: function( i ) {
+ var len = this.length,
+ j = +i + ( i < 0 ? len : 0 );
+ return this.pushStack( j >= 0 && j < len ? [ this[j] ] : [] );
+ },
+
+ map: function( callback ) {
+ return this.pushStack( jQuery.map(this, function( elem, i ) {
+ return callback.call( elem, i, elem );
+ }));
+ },
+
+ end: function() {
+ return this.prevObject || this.constructor(null);
+ },
+
+ // For internal use only.
+ // Behaves like an Array's method, not like a jQuery method.
+ push: core_push,
+ sort: [].sort,
+ splice: [].splice
+};
+
+// Give the init function the jQuery prototype for later instantiation
+jQuery.fn.init.prototype = jQuery.fn;
+
+jQuery.extend = jQuery.fn.extend = function() {
+ var options, name, src, copy, copyIsArray, clone,
+ target = arguments[0] || {},
+ i = 1,
+ length = arguments.length,
+ deep = false;
+
+ // Handle a deep copy situation
+ if ( typeof target === "boolean" ) {
+ deep = target;
+ target = arguments[1] || {};
+ // skip the boolean and the target
+ i = 2;
+ }
+
+ // Handle case when target is a string or something (possible in deep copy)
+ if ( typeof target !== "object" && !jQuery.isFunction(target) ) {
+ target = {};
+ }
+
+ // extend jQuery itself if only one argument is passed
+ if ( length === i ) {
+ target = this;
+ --i;
+ }
+
+ for ( ; i < length; i++ ) {
+ // Only deal with non-null/undefined values
+ if ( (options = arguments[ i ]) != null ) {
+ // Extend the base object
+ for ( name in options ) {
+ src = target[ name ];
+ copy = options[ name ];
+
+ // Prevent never-ending loop
+ if ( target === copy ) {
+ continue;
+ }
+
+ // Recurse if we're merging plain objects or arrays
+ if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) {
+ if ( copyIsArray ) {
+ copyIsArray = false;
+ clone = src && jQuery.isArray(src) ? src : [];
+
+ } else {
+ clone = src && jQuery.isPlainObject(src) ? src : {};
+ }
+
+ // Never move original objects, clone them
+ target[ name ] = jQuery.extend( deep, clone, copy );
+
+ // Don't bring in undefined values
+ } else if ( copy !== undefined ) {
+ target[ name ] = copy;
+ }
+ }
+ }
+ }
+
+ // Return the modified object
+ return target;
+};
+
+jQuery.extend({
+ // Unique for each copy of jQuery on the page
+ expando: "jQuery" + ( core_version + Math.random() ).replace( /\D/g, "" ),
+
+ noConflict: function( deep ) {
+ if ( window.$ === jQuery ) {
+ window.$ = _$;
+ }
+
+ if ( deep && window.jQuery === jQuery ) {
+ window.jQuery = _jQuery;
+ }
+
+ return jQuery;
+ },
+
+ // Is the DOM ready to be used? Set to true once it occurs.
+ isReady: false,
+
+ // A counter to track how many items to wait for before
+ // the ready event fires. See #6781
+ readyWait: 1,
+
+ // Hold (or release) the ready event
+ holdReady: function( hold ) {
+ if ( hold ) {
+ jQuery.readyWait++;
+ } else {
+ jQuery.ready( true );
+ }
+ },
+
+ // Handle when the DOM is ready
+ ready: function( wait ) {
+
+ // Abort if there are pending holds or we're already ready
+ if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) {
+ return;
+ }
+
+ // Remember that the DOM is ready
+ jQuery.isReady = true;
+
+ // If a normal DOM Ready event fired, decrement, and wait if need be
+ if ( wait !== true && --jQuery.readyWait > 0 ) {
+ return;
+ }
+
+ // If there are functions bound, to execute
+ readyList.resolveWith( document, [ jQuery ] );
+
+ // Trigger any bound ready events
+ if ( jQuery.fn.trigger ) {
+ jQuery( document ).trigger("ready").off("ready");
+ }
+ },
+
+ // See test/unit/core.js for details concerning isFunction.
+ // Since version 1.3, DOM methods and functions like alert
+ // aren't supported. They return false on IE (#2968).
+ isFunction: function( obj ) {
+ return jQuery.type(obj) === "function";
+ },
+
+ isArray: Array.isArray,
+
+ isWindow: function( obj ) {
+ return obj != null && obj === obj.window;
+ },
+
+ isNumeric: function( obj ) {
+ return !isNaN( parseFloat(obj) ) && isFinite( obj );
+ },
+
+ type: function( obj ) {
+ if ( obj == null ) {
+ return String( obj );
+ }
+ // Support: Safari <= 5.1 (functionish RegExp)
+ return typeof obj === "object" || typeof obj === "function" ?
+ class2type[ core_toString.call(obj) ] || "object" :
+ typeof obj;
+ },
+
+ isPlainObject: function( obj ) {
+ // Not plain objects:
+ // - Any object or value whose internal [[Class]] property is not "[object Object]"
+ // - DOM nodes
+ // - window
+ if ( jQuery.type( obj ) !== "object" || obj.nodeType || jQuery.isWindow( obj ) ) {
+ return false;
+ }
+
+ // Support: Firefox <20
+ // The try/catch suppresses exceptions thrown when attempting to access
+ // the "constructor" property of certain host objects, ie. |window.location|
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=814622
+ try {
+ if ( obj.constructor &&
+ !core_hasOwn.call( obj.constructor.prototype, "isPrototypeOf" ) ) {
+ return false;
+ }
+ } catch ( e ) {
+ return false;
+ }
+
+ // If the function hasn't returned already, we're confident that
+ // |obj| is a plain object, created by {} or constructed with new Object
+ return true;
+ },
+
+ isEmptyObject: function( obj ) {
+ var name;
+ for ( name in obj ) {
+ return false;
+ }
+ return true;
+ },
+
+ error: function( msg ) {
+ throw new Error( msg );
+ },
+
+ // data: string of html
+ // context (optional): If specified, the fragment will be created in this context, defaults to document
+ // keepScripts (optional): If true, will include scripts passed in the html string
+ parseHTML: function( data, context, keepScripts ) {
+ if ( !data || typeof data !== "string" ) {
+ return null;
+ }
+ if ( typeof context === "boolean" ) {
+ keepScripts = context;
+ context = false;
+ }
+ context = context || document;
+
+ var parsed = rsingleTag.exec( data ),
+ scripts = !keepScripts && [];
+
+ // Single tag
+ if ( parsed ) {
+ return [ context.createElement( parsed[1] ) ];
+ }
+
+ parsed = jQuery.buildFragment( [ data ], context, scripts );
+
+ if ( scripts && scripts.length ) {
+ jQuery( scripts ).remove();
+ }
+
+ return jQuery.merge( [], parsed.childNodes );
+ },
+
+ parseJSON: JSON.parse,
+
+ // Cross-browser xml parsing
+ parseXML: function( data ) {
+ var xml, tmp;
+ if ( !data || typeof data !== "string" ) {
+ return null;
+ }
+
+ // Support: IE9
+ try {
+ tmp = new DOMParser();
+ xml = tmp.parseFromString( data , "text/xml" );
+ } catch ( e ) {
+ xml = undefined;
+ }
+
+ if ( !xml || xml.getElementsByTagName( "parsererror" ).length ) {
+ jQuery.error( "Invalid XML: " + data );
+ }
+ return xml;
+ },
+
+ noop: function() {},
+
+ // Evaluates a script in a global context
+ globalEval: function( code ) {
+ var script,
+ indirect = eval;
+
+ code = jQuery.trim( code );
+
+ if ( code ) {
+ // If the code includes a valid, prologue position
+ // strict mode pragma, execute code by injecting a
+ // script tag into the document.
+ if ( code.indexOf("use strict") === 1 ) {
+ script = document.createElement("script");
+ script.text = code;
+ document.head.appendChild( script ).parentNode.removeChild( script );
+ } else {
+ // Otherwise, avoid the DOM node creation, insertion
+ // and removal by using an indirect global eval
+ indirect( code );
+ }
+ }
+ },
+
+ // Convert dashed to camelCase; used by the css and data modules
+ // Microsoft forgot to hump their vendor prefix (#9572)
+ camelCase: function( string ) {
+ return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase );
+ },
+
+ nodeName: function( elem, name ) {
+ return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase();
+ },
+
+ // args is for internal usage only
+ each: function( obj, callback, args ) {
+ var value,
+ i = 0,
+ length = obj.length,
+ isArray = isArraylike( obj );
+
+ if ( args ) {
+ if ( isArray ) {
+ for ( ; i < length; i++ ) {
+ value = callback.apply( obj[ i ], args );
+
+ if ( value === false ) {
+ break;
+ }
+ }
+ } else {
+ for ( i in obj ) {
+ value = callback.apply( obj[ i ], args );
+
+ if ( value === false ) {
+ break;
+ }
+ }
+ }
+
+ // A special, fast, case for the most common use of each
+ } else {
+ if ( isArray ) {
+ for ( ; i < length; i++ ) {
+ value = callback.call( obj[ i ], i, obj[ i ] );
+
+ if ( value === false ) {
+ break;
+ }
+ }
+ } else {
+ for ( i in obj ) {
+ value = callback.call( obj[ i ], i, obj[ i ] );
+
+ if ( value === false ) {
+ break;
+ }
+ }
+ }
+ }
+
+ return obj;
+ },
+
+ trim: function( text ) {
+ return text == null ? "" : core_trim.call( text );
+ },
+
+ // results is for internal usage only
+ makeArray: function( arr, results ) {
+ var ret = results || [];
+
+ if ( arr != null ) {
+ if ( isArraylike( Object(arr) ) ) {
+ jQuery.merge( ret,
+ typeof arr === "string" ?
+ [ arr ] : arr
+ );
+ } else {
+ core_push.call( ret, arr );
+ }
+ }
+
+ return ret;
+ },
+
+ inArray: function( elem, arr, i ) {
+ return arr == null ? -1 : core_indexOf.call( arr, elem, i );
+ },
+
+ merge: function( first, second ) {
+ var l = second.length,
+ i = first.length,
+ j = 0;
+
+ if ( typeof l === "number" ) {
+ for ( ; j < l; j++ ) {
+ first[ i++ ] = second[ j ];
+ }
+ } else {
+ while ( second[j] !== undefined ) {
+ first[ i++ ] = second[ j++ ];
+ }
+ }
+
+ first.length = i;
+
+ return first;
+ },
+
+ grep: function( elems, callback, inv ) {
+ var retVal,
+ ret = [],
+ i = 0,
+ length = elems.length;
+ inv = !!inv;
+
+ // Go through the array, only saving the items
+ // that pass the validator function
+ for ( ; i < length; i++ ) {
+ retVal = !!callback( elems[ i ], i );
+ if ( inv !== retVal ) {
+ ret.push( elems[ i ] );
+ }
+ }
+
+ return ret;
+ },
+
+ // arg is for internal usage only
+ map: function( elems, callback, arg ) {
+ var value,
+ i = 0,
+ length = elems.length,
+ isArray = isArraylike( elems ),
+ ret = [];
+
+ // Go through the array, translating each of the items to their
+ if ( isArray ) {
+ for ( ; i < length; i++ ) {
+ value = callback( elems[ i ], i, arg );
+
+ if ( value != null ) {
+ ret[ ret.length ] = value;
+ }
+ }
+
+ // Go through every key on the object,
+ } else {
+ for ( i in elems ) {
+ value = callback( elems[ i ], i, arg );
+
+ if ( value != null ) {
+ ret[ ret.length ] = value;
+ }
+ }
+ }
+
+ // Flatten any nested arrays
+ return core_concat.apply( [], ret );
+ },
+
+ // A global GUID counter for objects
+ guid: 1,
+
+ // Bind a function to a context, optionally partially applying any
+ // arguments.
+ proxy: function( fn, context ) {
+ var tmp, args, proxy;
+
+ if ( typeof context === "string" ) {
+ tmp = fn[ context ];
+ context = fn;
+ fn = tmp;
+ }
+
+ // Quick check to determine if target is callable, in the spec
+ // this throws a TypeError, but we will just return undefined.
+ if ( !jQuery.isFunction( fn ) ) {
+ return undefined;
+ }
+
+ // Simulated bind
+ args = core_slice.call( arguments, 2 );
+ proxy = function() {
+ return fn.apply( context || this, args.concat( core_slice.call( arguments ) ) );
+ };
+
+ // Set the guid of unique handler to the same of original handler, so it can be removed
+ proxy.guid = fn.guid = fn.guid || jQuery.guid++;
+
+ return proxy;
+ },
+
+ // Multifunctional method to get and set values of a collection
+ // The value/s can optionally be executed if it's a function
+ access: function( elems, fn, key, value, chainable, emptyGet, raw ) {
+ var i = 0,
+ length = elems.length,
+ bulk = key == null;
+
+ // Sets many values
+ if ( jQuery.type( key ) === "object" ) {
+ chainable = true;
+ for ( i in key ) {
+ jQuery.access( elems, fn, i, key[i], true, emptyGet, raw );
+ }
+
+ // Sets one value
+ } else if ( value !== undefined ) {
+ chainable = true;
+
+ if ( !jQuery.isFunction( value ) ) {
+ raw = true;
+ }
+
+ if ( bulk ) {
+ // Bulk operations run against the entire set
+ if ( raw ) {
+ fn.call( elems, value );
+ fn = null;
+
+ // ...except when executing function values
+ } else {
+ bulk = fn;
+ fn = function( elem, key, value ) {
+ return bulk.call( jQuery( elem ), value );
+ };
+ }
+ }
+
+ if ( fn ) {
+ for ( ; i < length; i++ ) {
+ fn( elems[i], key, raw ? value : value.call( elems[i], i, fn( elems[i], key ) ) );
+ }
+ }
+ }
+
+ return chainable ?
+ elems :
+
+ // Gets
+ bulk ?
+ fn.call( elems ) :
+ length ? fn( elems[0], key ) : emptyGet;
+ },
+
+ now: Date.now,
+
+ // A method for quickly swapping in/out CSS properties to get correct calculations.
+ // Note: this method belongs to the css module but it's needed here for the support module.
+ // If support gets modularized, this method should be moved back to the css module.
+ swap: function( elem, options, callback, args ) {
+ var ret, name,
+ old = {};
+
+ // Remember the old values, and insert the new ones
+ for ( name in options ) {
+ old[ name ] = elem.style[ name ];
+ elem.style[ name ] = options[ name ];
+ }
+
+ ret = callback.apply( elem, args || [] );
+
+ // Revert the old values
+ for ( name in options ) {
+ elem.style[ name ] = old[ name ];
+ }
+
+ return ret;
+ }
+});
+
+jQuery.ready.promise = function( obj ) {
+ if ( !readyList ) {
+
+ readyList = jQuery.Deferred();
+
+ // Catch cases where $(document).ready() is called after the browser event has already occurred.
+ // we once tried to use readyState "interactive" here, but it caused issues like the one
+ // discovered by ChrisS here: http://bugs.jquery.com/ticket/12282#comment:15
+ if ( document.readyState === "complete" ) {
+ // Handle it asynchronously to allow scripts the opportunity to delay ready
+ setTimeout( jQuery.ready );
+
+ } else {
+
+ // Use the handy event callback
+ document.addEventListener( "DOMContentLoaded", completed, false );
+
+ // A fallback to window.onload, that will always work
+ window.addEventListener( "load", completed, false );
+ }
+ }
+ return readyList.promise( obj );
+};
+
+// Populate the class2type map
+jQuery.each("Boolean Number String Function Array Date RegExp Object Error".split(" "), function(i, name) {
+ class2type[ "[object " + name + "]" ] = name.toLowerCase();
+});
+
+function isArraylike( obj ) {
+ var length = obj.length,
+ type = jQuery.type( obj );
+
+ if ( jQuery.isWindow( obj ) ) {
+ return false;
+ }
+
+ if ( obj.nodeType === 1 && length ) {
+ return true;
+ }
+
+ return type === "array" || type !== "function" &&
+ ( length === 0 ||
+ typeof length === "number" && length > 0 && ( length - 1 ) in obj );
+}
+
+// All jQuery objects should point back to these
+rootjQuery = jQuery(document);
diff --git a/js/jquery/src/jquery/css.js b/js/jquery/src/jquery/css.js
new file mode 100644
index 0000000000..34b4d12031
--- /dev/null
+++ b/js/jquery/src/jquery/css.js
@@ -0,0 +1,563 @@
+var curCSS, iframe,
+ // swappable if display is none or starts with table except "table", "table-cell", or "table-caption"
+ // see here for display values: https://developer.mozilla.org/en-US/docs/CSS/display
+ rdisplayswap = /^(none|table(?!-c[ea]).+)/,
+ rmargin = /^margin/,
+ rnumsplit = new RegExp( "^(" + core_pnum + ")(.*)$", "i" ),
+ rnumnonpx = new RegExp( "^(" + core_pnum + ")(?!px)[a-z%]+$", "i" ),
+ rrelNum = new RegExp( "^([+-])=(" + core_pnum + ")", "i" ),
+ elemdisplay = { BODY: "block" },
+
+ cssShow = { position: "absolute", visibility: "hidden", display: "block" },
+ cssNormalTransform = {
+ letterSpacing: 0,
+ fontWeight: 400
+ },
+
+ cssExpand = [ "Top", "Right", "Bottom", "Left" ],
+ cssPrefixes = [ "Webkit", "O", "Moz", "ms" ];
+
+// return a css property mapped to a potentially vendor prefixed property
+function vendorPropName( style, name ) {
+
+ // shortcut for names that are not vendor prefixed
+ if ( name in style ) {
+ return name;
+ }
+
+ // check for vendor prefixed names
+ var capName = name.charAt(0).toUpperCase() + name.slice(1),
+ origName = name,
+ i = cssPrefixes.length;
+
+ while ( i-- ) {
+ name = cssPrefixes[ i ] + capName;
+ if ( name in style ) {
+ return name;
+ }
+ }
+
+ return origName;
+}
+
+function isHidden( elem, el ) {
+ // isHidden might be called from jQuery#filter function;
+ // in that case, element will be second argument
+ elem = el || elem;
+ return jQuery.css( elem, "display" ) === "none" || !jQuery.contains( elem.ownerDocument, elem );
+}
+
+// NOTE: we've included the "window" in window.getComputedStyle
+// because jsdom on node.js will break without it.
+function getStyles( elem ) {
+ return window.getComputedStyle( elem, null );
+}
+
+function showHide( elements, show ) {
+ var display, elem, hidden,
+ values = [],
+ index = 0,
+ length = elements.length;
+
+ for ( ; index < length; index++ ) {
+ elem = elements[ index ];
+ if ( !elem.style ) {
+ continue;
+ }
+
+ values[ index ] = data_priv.get( elem, "olddisplay" );
+ display = elem.style.display;
+ if ( show ) {
+ // Reset the inline display of this element to learn if it is
+ // being hidden by cascaded rules or not
+ if ( !values[ index ] && display === "none" ) {
+ elem.style.display = "";
+ }
+
+ // Set elements which have been overridden with display: none
+ // in a stylesheet to whatever the default browser style is
+ // for such an element
+ if ( elem.style.display === "" && isHidden( elem ) ) {
+ values[ index ] = data_priv.access( elem, "olddisplay", css_defaultDisplay(elem.nodeName) );
+ }
+ } else {
+
+ if ( !values[ index ] ) {
+ hidden = isHidden( elem );
+
+ if ( display && display !== "none" || !hidden ) {
+ data_priv.set( elem, "olddisplay", hidden ? display : jQuery.css(elem, "display") );
+ }
+ }
+ }
+ }
+
+ // Set the display of most of the elements in a second loop
+ // to avoid the constant reflow
+ for ( index = 0; index < length; index++ ) {
+ elem = elements[ index ];
+ if ( !elem.style ) {
+ continue;
+ }
+ if ( !show || elem.style.display === "none" || elem.style.display === "" ) {
+ elem.style.display = show ? values[ index ] || "" : "none";
+ }
+ }
+
+ return elements;
+}
+
+jQuery.fn.extend({
+ css: function( name, value ) {
+ return jQuery.access( this, function( elem, name, value ) {
+ var styles, len,
+ map = {},
+ i = 0;
+
+ if ( jQuery.isArray( name ) ) {
+ styles = getStyles( elem );
+ len = name.length;
+
+ for ( ; i < len; i++ ) {
+ map[ name[ i ] ] = jQuery.css( elem, name[ i ], false, styles );
+ }
+
+ return map;
+ }
+
+ return value !== undefined ?
+ jQuery.style( elem, name, value ) :
+ jQuery.css( elem, name );
+ }, name, value, arguments.length > 1 );
+ },
+ show: function() {
+ return showHide( this, true );
+ },
+ hide: function() {
+ return showHide( this );
+ },
+ toggle: function( state ) {
+ if ( typeof state === "boolean" ) {
+ return state ? this.show() : this.hide();
+ }
+
+ return this.each(function() {
+ if ( isHidden( this ) ) {
+ jQuery( this ).show();
+ } else {
+ jQuery( this ).hide();
+ }
+ });
+ }
+});
+
+jQuery.extend({
+ // Add in style property hooks for overriding the default
+ // behavior of getting and setting a style property
+ cssHooks: {
+ opacity: {
+ get: function( elem, computed ) {
+ if ( computed ) {
+ // We should always get a number back from opacity
+ var ret = curCSS( elem, "opacity" );
+ return ret === "" ? "1" : ret;
+ }
+ }
+ }
+ },
+
+ // Don't automatically add "px" to these possibly-unitless properties
+ cssNumber: {
+ "columnCount": true,
+ "fillOpacity": true,
+ "fontWeight": true,
+ "lineHeight": true,
+ "opacity": true,
+ "order": true,
+ "orphans": true,
+ "widows": true,
+ "zIndex": true,
+ "zoom": true
+ },
+
+ // Add in properties whose names you wish to fix before
+ // setting or getting the value
+ cssProps: {
+ // normalize float css property
+ "float": "cssFloat"
+ },
+
+ // Get and set the style property on a DOM Node
+ style: function( elem, name, value, extra ) {
+ // Don't set styles on text and comment nodes
+ if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) {
+ return;
+ }
+
+ // Make sure that we're working with the right name
+ var ret, type, hooks,
+ origName = jQuery.camelCase( name ),
+ style = elem.style;
+
+ name = jQuery.cssProps[ origName ] || ( jQuery.cssProps[ origName ] = vendorPropName( style, origName ) );
+
+ // gets hook for the prefixed version
+ // followed by the unprefixed version
+ hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ];
+
+ // Check if we're setting a value
+ if ( value !== undefined ) {
+ type = typeof value;
+
+ // convert relative number strings (+= or -=) to relative numbers. #7345
+ if ( type === "string" && (ret = rrelNum.exec( value )) ) {
+ value = ( ret[1] + 1 ) * ret[2] + parseFloat( jQuery.css( elem, name ) );
+ // Fixes bug #9237
+ type = "number";
+ }
+
+ // Make sure that NaN and null values aren't set. See: #7116
+ if ( value == null || type === "number" && isNaN( value ) ) {
+ return;
+ }
+
+ // If a number was passed in, add 'px' to the (except for certain CSS properties)
+ if ( type === "number" && !jQuery.cssNumber[ origName ] ) {
+ value += "px";
+ }
+
+ // Fixes #8908, it can be done more correctly by specifying setters in cssHooks,
+ // but it would mean to define eight (for every problematic property) identical functions
+ if ( !jQuery.support.clearCloneStyle && value === "" && name.indexOf("background") === 0 ) {
+ style[ name ] = "inherit";
+ }
+
+ // If a hook was provided, use that value, otherwise just set the specified value
+ if ( !hooks || !("set" in hooks) || (value = hooks.set( elem, value, extra )) !== undefined ) {
+ style[ name ] = value;
+ }
+
+ } else {
+ // If a hook was provided get the non-computed value from there
+ if ( hooks && "get" in hooks && (ret = hooks.get( elem, false, extra )) !== undefined ) {
+ return ret;
+ }
+
+ // Otherwise just get the value from the style object
+ return style[ name ];
+ }
+ },
+
+ css: function( elem, name, extra, styles ) {
+ var val, num, hooks,
+ origName = jQuery.camelCase( name );
+
+ // Make sure that we're working with the right name
+ name = jQuery.cssProps[ origName ] || ( jQuery.cssProps[ origName ] = vendorPropName( elem.style, origName ) );
+
+ // gets hook for the prefixed version
+ // followed by the unprefixed version
+ hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ];
+
+ // If a hook was provided get the computed value from there
+ if ( hooks && "get" in hooks ) {
+ val = hooks.get( elem, true, extra );
+ }
+
+ // Otherwise, if a way to get the computed value exists, use that
+ if ( val === undefined ) {
+ val = curCSS( elem, name, styles );
+ }
+
+ //convert "normal" to computed value
+ if ( val === "normal" && name in cssNormalTransform ) {
+ val = cssNormalTransform[ name ];
+ }
+
+ // Return, converting to number if forced or a qualifier was provided and val looks numeric
+ if ( extra === "" || extra ) {
+ num = parseFloat( val );
+ return extra === true || jQuery.isNumeric( num ) ? num || 0 : val;
+ }
+ return val;
+ }
+});
+
+curCSS = function( elem, name, _computed ) {
+ var width, minWidth, maxWidth,
+ computed = _computed || getStyles( elem ),
+
+ // Support: IE9
+ // getPropertyValue is only needed for .css('filter') in IE9, see #12537
+ ret = computed ? computed.getPropertyValue( name ) || computed[ name ] : undefined,
+ style = elem.style;
+
+ if ( computed ) {
+
+ if ( ret === "" && !jQuery.contains( elem.ownerDocument, elem ) ) {
+ ret = jQuery.style( elem, name );
+ }
+
+ // Support: Safari 5.1
+ // A tribute to the "awesome hack by Dean Edwards"
+ // Safari 5.1.7 (at least) returns percentage for a larger set of values, but width seems to be reliably pixels
+ // this is against the CSSOM draft spec: http://dev.w3.org/csswg/cssom/#resolved-values
+ if ( rnumnonpx.test( ret ) && rmargin.test( name ) ) {
+
+ // Remember the original values
+ width = style.width;
+ minWidth = style.minWidth;
+ maxWidth = style.maxWidth;
+
+ // Put in the new values to get a computed value out
+ style.minWidth = style.maxWidth = style.width = ret;
+ ret = computed.width;
+
+ // Revert the changed values
+ style.width = width;
+ style.minWidth = minWidth;
+ style.maxWidth = maxWidth;
+ }
+ }
+
+ return ret;
+};
+
+
+function setPositiveNumber( elem, value, subtract ) {
+ var matches = rnumsplit.exec( value );
+ return matches ?
+ // Guard against undefined "subtract", e.g., when used as in cssHooks
+ Math.max( 0, matches[ 1 ] - ( subtract || 0 ) ) + ( matches[ 2 ] || "px" ) :
+ value;
+}
+
+function augmentWidthOrHeight( elem, name, extra, isBorderBox, styles ) {
+ var i = extra === ( isBorderBox ? "border" : "content" ) ?
+ // If we already have the right measurement, avoid augmentation
+ 4 :
+ // Otherwise initialize for horizontal or vertical properties
+ name === "width" ? 1 : 0,
+
+ val = 0;
+
+ for ( ; i < 4; i += 2 ) {
+ // both box models exclude margin, so add it if we want it
+ if ( extra === "margin" ) {
+ val += jQuery.css( elem, extra + cssExpand[ i ], true, styles );
+ }
+
+ if ( isBorderBox ) {
+ // border-box includes padding, so remove it if we want content
+ if ( extra === "content" ) {
+ val -= jQuery.css( elem, "padding" + cssExpand[ i ], true, styles );
+ }
+
+ // at this point, extra isn't border nor margin, so remove border
+ if ( extra !== "margin" ) {
+ val -= jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles );
+ }
+ } else {
+ // at this point, extra isn't content, so add padding
+ val += jQuery.css( elem, "padding" + cssExpand[ i ], true, styles );
+
+ // at this point, extra isn't content nor padding, so add border
+ if ( extra !== "padding" ) {
+ val += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles );
+ }
+ }
+ }
+
+ return val;
+}
+
+function getWidthOrHeight( elem, name, extra ) {
+
+ // Start with offset property, which is equivalent to the border-box value
+ var valueIsBorderBox = true,
+ val = name === "width" ? elem.offsetWidth : elem.offsetHeight,
+ styles = getStyles( elem ),
+ isBorderBox = jQuery.support.boxSizing && jQuery.css( elem, "boxSizing", false, styles ) === "border-box";
+
+ // some non-html elements return undefined for offsetWidth, so check for null/undefined
+ // svg - https://bugzilla.mozilla.org/show_bug.cgi?id=649285
+ // MathML - https://bugzilla.mozilla.org/show_bug.cgi?id=491668
+ if ( val <= 0 || val == null ) {
+ // Fall back to computed then uncomputed css if necessary
+ val = curCSS( elem, name, styles );
+ if ( val < 0 || val == null ) {
+ val = elem.style[ name ];
+ }
+
+ // Computed unit is not pixels. Stop here and return.
+ if ( rnumnonpx.test(val) ) {
+ return val;
+ }
+
+ // we need the check for style in case a browser which returns unreliable values
+ // for getComputedStyle silently falls back to the reliable elem.style
+ valueIsBorderBox = isBorderBox && ( jQuery.support.boxSizingReliable || val === elem.style[ name ] );
+
+ // Normalize "", auto, and prepare for extra
+ val = parseFloat( val ) || 0;
+ }
+
+ // use the active box-sizing model to add/subtract irrelevant styles
+ return ( val +
+ augmentWidthOrHeight(
+ elem,
+ name,
+ extra || ( isBorderBox ? "border" : "content" ),
+ valueIsBorderBox,
+ styles
+ )
+ ) + "px";
+}
+
+// Try to determine the default display value of an element
+function css_defaultDisplay( nodeName ) {
+ var doc = document,
+ display = elemdisplay[ nodeName ];
+
+ if ( !display ) {
+ display = actualDisplay( nodeName, doc );
+
+ // If the simple way fails, read from inside an iframe
+ if ( display === "none" || !display ) {
+ // Use the already-created iframe if possible
+ iframe = ( iframe ||
+ jQuery("<iframe frameborder='0' width='0' height='0'/>")
+ .css( "cssText", "display:block !important" )
+ ).appendTo( doc.documentElement );
+
+ // Always write a new HTML skeleton so Webkit and Firefox don't choke on reuse
+ doc = ( iframe[0].contentWindow || iframe[0].contentDocument ).document;
+ doc.write("<!doctype html><html><body>");
+ doc.close();
+
+ display = actualDisplay( nodeName, doc );
+ iframe.detach();
+ }
+
+ // Store the correct default display
+ elemdisplay[ nodeName ] = display;
+ }
+
+ return display;
+}
+
+// Called ONLY from within css_defaultDisplay
+function actualDisplay( name, doc ) {
+ var elem = jQuery( doc.createElement( name ) ).appendTo( doc.body ),
+ display = jQuery.css( elem[0], "display" );
+ elem.remove();
+ return display;
+}
+
+jQuery.each([ "height", "width" ], function( i, name ) {
+ jQuery.cssHooks[ name ] = {
+ get: function( elem, computed, extra ) {
+ if ( computed ) {
+ // certain elements can have dimension info if we invisibly show them
+ // however, it must have a current display style that would benefit from this
+ return elem.offsetWidth === 0 && rdisplayswap.test( jQuery.css( elem, "display" ) ) ?
+ jQuery.swap( elem, cssShow, function() {
+ return getWidthOrHeight( elem, name, extra );
+ }) :
+ getWidthOrHeight( elem, name, extra );
+ }
+ },
+
+ set: function( elem, value, extra ) {
+ var styles = extra && getStyles( elem );
+ return setPositiveNumber( elem, value, extra ?
+ augmentWidthOrHeight(
+ elem,
+ name,
+ extra,
+ jQuery.support.boxSizing && jQuery.css( elem, "boxSizing", false, styles ) === "border-box",
+ styles
+ ) : 0
+ );
+ }
+ };
+});
+
+// These hooks cannot be added until DOM ready because the support test
+// for it is not run until after DOM ready
+jQuery(function() {
+ // Support: Android 2.3
+ if ( !jQuery.support.reliableMarginRight ) {
+ jQuery.cssHooks.marginRight = {
+ get: function( elem, computed ) {
+ if ( computed ) {
+ // Support: Android 2.3
+ // WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right
+ // Work around by temporarily setting element display to inline-block
+ return jQuery.swap( elem, { "display": "inline-block" },
+ curCSS, [ elem, "marginRight" ] );
+ }
+ }
+ };
+ }
+
+ // Webkit bug: https://bugs.webkit.org/show_bug.cgi?id=29084
+ // getComputedStyle returns percent when specified for top/left/bottom/right
+ // rather than make the css module depend on the offset module, we just check for it here
+ if ( !jQuery.support.pixelPosition && jQuery.fn.position ) {
+ jQuery.each( [ "top", "left" ], function( i, prop ) {
+ jQuery.cssHooks[ prop ] = {
+ get: function( elem, computed ) {
+ if ( computed ) {
+ computed = curCSS( elem, prop );
+ // if curCSS returns percentage, fallback to offset
+ return rnumnonpx.test( computed ) ?
+ jQuery( elem ).position()[ prop ] + "px" :
+ computed;
+ }
+ }
+ };
+ });
+ }
+
+});
+
+if ( jQuery.expr && jQuery.expr.filters ) {
+ jQuery.expr.filters.hidden = function( elem ) {
+ // Support: Opera <= 12.12
+ // Opera reports offsetWidths and offsetHeights less than zero on some elements
+ return elem.offsetWidth <= 0 && elem.offsetHeight <= 0;
+ };
+
+ jQuery.expr.filters.visible = function( elem ) {
+ return !jQuery.expr.filters.hidden( elem );
+ };
+}
+
+// These hooks are used by animate to expand properties
+jQuery.each({
+ margin: "",
+ padding: "",
+ border: "Width"
+}, function( prefix, suffix ) {
+ jQuery.cssHooks[ prefix + suffix ] = {
+ expand: function( value ) {
+ var i = 0,
+ expanded = {},
+
+ // assumes a single number if not a string
+ parts = typeof value === "string" ? value.split(" ") : [ value ];
+
+ for ( ; i < 4; i++ ) {
+ expanded[ prefix + cssExpand[ i ] + suffix ] =
+ parts[ i ] || parts[ i - 2 ] || parts[ 0 ];
+ }
+
+ return expanded;
+ }
+ };
+
+ if ( !rmargin.test( prefix ) ) {
+ jQuery.cssHooks[ prefix + suffix ].set = setPositiveNumber;
+ }
+});
diff --git a/js/jquery/src/jquery/data.js b/js/jquery/src/jquery/data.js
new file mode 100644
index 0000000000..96afe6f4e5
--- /dev/null
+++ b/js/jquery/src/jquery/data.js
@@ -0,0 +1,356 @@
+/*
+ Implementation Summary
+
+ 1. Enforce API surface and semantic compatibility with 1.9.x branch
+ 2. Improve the module's maintainability by reducing the storage
+ paths to a single mechanism.
+ 3. Use the same single mechanism to support "private" and "user" data.
+ 4. _Never_ expose "private" data to user code (TODO: Drop _data, _removeData)
+ 5. Avoid exposing implementation details on user objects (eg. expando properties)
+ 6. Provide a clear path for implementation upgrade to WeakMap in 2014
+*/
+var data_user, data_priv,
+ rbrace = /(?:\{[\s\S]*\}|\[[\s\S]*\])$/,
+ rmultiDash = /([A-Z])/g;
+
+function Data() {
+ // Support: Android < 4,
+ // Old WebKit does not have Object.preventExtensions/freeze method,
+ // return new empty object instead with no [[set]] accessor
+ Object.defineProperty( this.cache = {}, 0, {
+ get: function() {
+ return {};
+ }
+ });
+
+ this.expando = jQuery.expando + Math.random();
+}
+
+Data.uid = 1;
+
+Data.accepts = function( owner ) {
+ // Accepts only:
+ // - Node
+ // - Node.ELEMENT_NODE
+ // - Node.DOCUMENT_NODE
+ // - Object
+ // - Any
+ return owner.nodeType ?
+ owner.nodeType === 1 || owner.nodeType === 9 : true;
+};
+
+Data.prototype = {
+ key: function( owner ) {
+ // We can accept data for non-element nodes in modern browsers,
+ // but we should not, see #8335.
+ // Always return the key for a frozen object.
+ if ( !Data.accepts( owner ) ) {
+ return 0;
+ }
+
+ var descriptor = {},
+ // Check if the owner object already has a cache key
+ unlock = owner[ this.expando ];
+
+ // If not, create one
+ if ( !unlock ) {
+ unlock = Data.uid++;
+
+ // Secure it in a non-enumerable, non-writable property
+ try {
+ descriptor[ this.expando ] = { value: unlock };
+ Object.defineProperties( owner, descriptor );
+
+ // Support: Android < 4
+ // Fallback to a less secure definition
+ } catch ( e ) {
+ descriptor[ this.expando ] = unlock;
+ jQuery.extend( owner, descriptor );
+ }
+ }
+
+ // Ensure the cache object
+ if ( !this.cache[ unlock ] ) {
+ this.cache[ unlock ] = {};
+ }
+
+ return unlock;
+ },
+ set: function( owner, data, value ) {
+ var prop,
+ // There may be an unlock assigned to this node,
+ // if there is no entry for this "owner", create one inline
+ // and set the unlock as though an owner entry had always existed
+ unlock = this.key( owner ),
+ cache = this.cache[ unlock ];
+
+ // Handle: [ owner, key, value ] args
+ if ( typeof data === "string" ) {
+ cache[ data ] = value;
+
+ // Handle: [ owner, { properties } ] args
+ } else {
+ // Fresh assignments by object are shallow copied
+ if ( jQuery.isEmptyObject( cache ) ) {
+ jQuery.extend( this.cache[ unlock ], data );
+ // Otherwise, copy the properties one-by-one to the cache object
+ } else {
+ for ( prop in data ) {
+ cache[ prop ] = data[ prop ];
+ }
+ }
+ }
+ return cache;
+ },
+ get: function( owner, key ) {
+ // Either a valid cache is found, or will be created.
+ // New caches will be created and the unlock returned,
+ // allowing direct access to the newly created
+ // empty data object. A valid owner object must be provided.
+ var cache = this.cache[ this.key( owner ) ];
+
+ return key === undefined ?
+ cache : cache[ key ];
+ },
+ access: function( owner, key, value ) {
+ var stored;
+ // In cases where either:
+ //
+ // 1. No key was specified
+ // 2. A string key was specified, but no value provided
+ //
+ // Take the "read" path and allow the get method to determine
+ // which value to return, respectively either:
+ //
+ // 1. The entire cache object
+ // 2. The data stored at the key
+ //
+ if ( key === undefined ||
+ ((key && typeof key === "string") && value === undefined) ) {
+
+ stored = this.get( owner, key );
+
+ return stored !== undefined ?
+ stored : this.get( owner, jQuery.camelCase(key) );
+ }
+
+ // [*]When the key is not a string, or both a key and value
+ // are specified, set or extend (existing objects) with either:
+ //
+ // 1. An object of properties
+ // 2. A key and value
+ //
+ this.set( owner, key, value );
+
+ // Since the "set" path can have two possible entry points
+ // return the expected data based on which path was taken[*]
+ return value !== undefined ? value : key;
+ },
+ remove: function( owner, key ) {
+ var i, name, camel,
+ unlock = this.key( owner ),
+ cache = this.cache[ unlock ];
+
+ if ( key === undefined ) {
+ this.cache[ unlock ] = {};
+
+ } else {
+ // Support array or space separated string of keys
+ if ( jQuery.isArray( key ) ) {
+ // If "name" is an array of keys...
+ // When data is initially created, via ("key", "val") signature,
+ // keys will be converted to camelCase.
+ // Since there is no way to tell _how_ a key was added, remove
+ // both plain key and camelCase key. #12786
+ // This will only penalize the array argument path.
+ name = key.concat( key.map( jQuery.camelCase ) );
+ } else {
+ camel = jQuery.camelCase( key );
+ // Try the string as a key before any manipulation
+ if ( key in cache ) {
+ name = [ key, camel ];
+ } else {
+ // If a key with the spaces exists, use it.
+ // Otherwise, create an array by matching non-whitespace
+ name = camel;
+ name = name in cache ?
+ [ name ] : ( name.match( core_rnotwhite ) || [] );
+ }
+ }
+
+ i = name.length;
+ while ( i-- ) {
+ delete cache[ name[ i ] ];
+ }
+ }
+ },
+ hasData: function( owner ) {
+ return !jQuery.isEmptyObject(
+ this.cache[ owner[ this.expando ] ] || {}
+ );
+ },
+ discard: function( owner ) {
+ if ( owner[ this.expando ] ) {
+ delete this.cache[ owner[ this.expando ] ];
+ }
+ }
+};
+
+// These may be used throughout the jQuery core codebase
+data_user = new Data();
+data_priv = new Data();
+
+
+jQuery.extend({
+ acceptData: Data.accepts,
+
+ hasData: function( elem ) {
+ return data_user.hasData( elem ) || data_priv.hasData( elem );
+ },
+
+ data: function( elem, name, data ) {
+ return data_user.access( elem, name, data );
+ },
+
+ removeData: function( elem, name ) {
+ data_user.remove( elem, name );
+ },
+
+ // TODO: Now that all calls to _data and _removeData have been replaced
+ // with direct calls to data_priv methods, these can be deprecated.
+ _data: function( elem, name, data ) {
+ return data_priv.access( elem, name, data );
+ },
+
+ _removeData: function( elem, name ) {
+ data_priv.remove( elem, name );
+ }
+});
+
+jQuery.fn.extend({
+ data: function( key, value ) {
+ var attrs, name,
+ elem = this[ 0 ],
+ i = 0,
+ data = null;
+
+ // Gets all values
+ if ( key === undefined ) {
+ if ( this.length ) {
+ data = data_user.get( elem );
+
+ if ( elem.nodeType === 1 && !data_priv.get( elem, "hasDataAttrs" ) ) {
+ attrs = elem.attributes;
+ for ( ; i < attrs.length; i++ ) {
+ name = attrs[ i ].name;
+
+ if ( name.indexOf( "data-" ) === 0 ) {
+ name = jQuery.camelCase( name.slice(5) );
+ dataAttr( elem, name, data[ name ] );
+ }
+ }
+ data_priv.set( elem, "hasDataAttrs", true );
+ }
+ }
+
+ return data;
+ }
+
+ // Sets multiple values
+ if ( typeof key === "object" ) {
+ return this.each(function() {
+ data_user.set( this, key );
+ });
+ }
+
+ return jQuery.access( this, function( value ) {
+ var data,
+ camelKey = jQuery.camelCase( key );
+
+ // The calling jQuery object (element matches) is not empty
+ // (and therefore has an element appears at this[ 0 ]) and the
+ // `value` parameter was not undefined. An empty jQuery object
+ // will result in `undefined` for elem = this[ 0 ] which will
+ // throw an exception if an attempt to read a data cache is made.
+ if ( elem && value === undefined ) {
+ // Attempt to get data from the cache
+ // with the key as-is
+ data = data_user.get( elem, key );
+ if ( data !== undefined ) {
+ return data;
+ }
+
+ // Attempt to get data from the cache
+ // with the key camelized
+ data = data_user.get( elem, camelKey );
+ if ( data !== undefined ) {
+ return data;
+ }
+
+ // Attempt to "discover" the data in
+ // HTML5 custom data-* attrs
+ data = dataAttr( elem, camelKey, undefined );
+ if ( data !== undefined ) {
+ return data;
+ }
+
+ // We tried really hard, but the data doesn't exist.
+ return;
+ }
+
+ // Set the data...
+ this.each(function() {
+ // First, attempt to store a copy or reference of any
+ // data that might've been store with a camelCased key.
+ var data = data_user.get( this, camelKey );
+
+ // For HTML5 data-* attribute interop, we have to
+ // store property names with dashes in a camelCase form.
+ // This might not apply to all properties...*
+ data_user.set( this, camelKey, value );
+
+ // *... In the case of properties that might _actually_
+ // have dashes, we need to also store a copy of that
+ // unchanged property.
+ if ( key.indexOf("-") !== -1 && data !== undefined ) {
+ data_user.set( this, key, value );
+ }
+ });
+ }, null, value, arguments.length > 1, null, true );
+ },
+
+ removeData: function( key ) {
+ return this.each(function() {
+ data_user.remove( this, key );
+ });
+ }
+});
+
+function dataAttr( elem, key, data ) {
+ var name;
+
+ // If nothing was found internally, try to fetch any
+ // data from the HTML5 data-* attribute
+ if ( data === undefined && elem.nodeType === 1 ) {
+ name = "data-" + key.replace( rmultiDash, "-$1" ).toLowerCase();
+ data = elem.getAttribute( name );
+
+ if ( typeof data === "string" ) {
+ try {
+ data = data === "true" ? true :
+ data === "false" ? false :
+ data === "null" ? null :
+ // Only convert to a number if it doesn't change the string
+ +data + "" === data ? +data :
+ rbrace.test( data ) ? JSON.parse( data ) :
+ data;
+ } catch( e ) {}
+
+ // Make sure we set the data so it isn't changed later
+ data_user.set( elem, key, data );
+ } else {
+ data = undefined;
+ }
+ }
+ return data;
+}
diff --git a/js/jquery/src/jquery/deferred.js b/js/jquery/src/jquery/deferred.js
new file mode 100644
index 0000000000..0efc05dc3a
--- /dev/null
+++ b/js/jquery/src/jquery/deferred.js
@@ -0,0 +1,141 @@
+jQuery.extend({
+
+ Deferred: function( func ) {
+ var tuples = [
+ // action, add listener, listener list, final state
+ [ "resolve", "done", jQuery.Callbacks("once memory"), "resolved" ],
+ [ "reject", "fail", jQuery.Callbacks("once memory"), "rejected" ],
+ [ "notify", "progress", jQuery.Callbacks("memory") ]
+ ],
+ state = "pending",
+ promise = {
+ state: function() {
+ return state;
+ },
+ always: function() {
+ deferred.done( arguments ).fail( arguments );
+ return this;
+ },
+ then: function( /* fnDone, fnFail, fnProgress */ ) {
+ var fns = arguments;
+ return jQuery.Deferred(function( newDefer ) {
+ jQuery.each( tuples, function( i, tuple ) {
+ var action = tuple[ 0 ],
+ fn = jQuery.isFunction( fns[ i ] ) && fns[ i ];
+ // deferred[ done | fail | progress ] for forwarding actions to newDefer
+ deferred[ tuple[1] ](function() {
+ var returned = fn && fn.apply( this, arguments );
+ if ( returned && jQuery.isFunction( returned.promise ) ) {
+ returned.promise()
+ .done( newDefer.resolve )
+ .fail( newDefer.reject )
+ .progress( newDefer.notify );
+ } else {
+ newDefer[ action + "With" ]( this === promise ? newDefer.promise() : this, fn ? [ returned ] : arguments );
+ }
+ });
+ });
+ fns = null;
+ }).promise();
+ },
+ // Get a promise for this deferred
+ // If obj is provided, the promise aspect is added to the object
+ promise: function( obj ) {
+ return obj != null ? jQuery.extend( obj, promise ) : promise;
+ }
+ },
+ deferred = {};
+
+ // Keep pipe for back-compat
+ promise.pipe = promise.then;
+
+ // Add list-specific methods
+ jQuery.each( tuples, function( i, tuple ) {
+ var list = tuple[ 2 ],
+ stateString = tuple[ 3 ];
+
+ // promise[ done | fail | progress ] = list.add
+ promise[ tuple[1] ] = list.add;
+
+ // Handle state
+ if ( stateString ) {
+ list.add(function() {
+ // state = [ resolved | rejected ]
+ state = stateString;
+
+ // [ reject_list | resolve_list ].disable; progress_list.lock
+ }, tuples[ i ^ 1 ][ 2 ].disable, tuples[ 2 ][ 2 ].lock );
+ }
+
+ // deferred[ resolve | reject | notify ]
+ deferred[ tuple[0] ] = function() {
+ deferred[ tuple[0] + "With" ]( this === deferred ? promise : this, arguments );
+ return this;
+ };
+ deferred[ tuple[0] + "With" ] = list.fireWith;
+ });
+
+ // Make the deferred a promise
+ promise.promise( deferred );
+
+ // Call given func if any
+ if ( func ) {
+ func.call( deferred, deferred );
+ }
+
+ // All done!
+ return deferred;
+ },
+
+ // Deferred helper
+ when: function( subordinate /* , ..., subordinateN */ ) {
+ var i = 0,
+ resolveValues = core_slice.call( arguments ),
+ length = resolveValues.length,
+
+ // the count of uncompleted subordinates
+ remaining = length !== 1 || ( subordinate && jQuery.isFunction( subordinate.promise ) ) ? length : 0,
+
+ // the master Deferred. If resolveValues consist of only a single Deferred, just use that.
+ deferred = remaining === 1 ? subordinate : jQuery.Deferred(),
+
+ // Update function for both resolve and progress values
+ updateFunc = function( i, contexts, values ) {
+ return function( value ) {
+ contexts[ i ] = this;
+ values[ i ] = arguments.length > 1 ? core_slice.call( arguments ) : value;
+ if( values === progressValues ) {
+ deferred.notifyWith( contexts, values );
+ } else if ( !( --remaining ) ) {
+ deferred.resolveWith( contexts, values );
+ }
+ };
+ },
+
+ progressValues, progressContexts, resolveContexts;
+
+ // add listeners to Deferred subordinates; treat others as resolved
+ if ( length > 1 ) {
+ progressValues = new Array( length );
+ progressContexts = new Array( length );
+ resolveContexts = new Array( length );
+ for ( ; i < length; i++ ) {
+ if ( resolveValues[ i ] && jQuery.isFunction( resolveValues[ i ].promise ) ) {
+ resolveValues[ i ].promise()
+ .done( updateFunc( i, resolveContexts, resolveValues ) )
+ .fail( deferred.reject )
+ .progress( updateFunc( i, progressContexts, progressValues ) );
+ } else {
+ --remaining;
+ }
+ }
+ }
+
+ // if we're not waiting on anything, resolve the master
+ if ( !remaining ) {
+ deferred.resolveWith( resolveContexts, resolveValues );
+ }
+
+ return deferred.promise();
+ }
+});
diff --git a/js/jquery/src/jquery/deprecated.js b/js/jquery/src/jquery/deprecated.js
new file mode 100644
index 0000000000..40f8dd6cd5
--- /dev/null
+++ b/js/jquery/src/jquery/deprecated.js
@@ -0,0 +1,11 @@
+// Limit scope pollution from any deprecated API
+// (function() {
+
+// The number of elements contained in the matched element set
+jQuery.fn.size = function() {
+ return this.length;
+};
+
+jQuery.fn.andSelf = jQuery.fn.addBack;
+
+// })();
diff --git a/js/jquery/src/jquery/dimensions.js b/js/jquery/src/jquery/dimensions.js
new file mode 100644
index 0000000000..ae59fb05d5
--- /dev/null
+++ b/js/jquery/src/jquery/dimensions.js
@@ -0,0 +1,41 @@
+// Create innerHeight, innerWidth, height, width, outerHeight and outerWidth methods
+jQuery.each( { Height: "height", Width: "width" }, function( name, type ) {
+ jQuery.each( { padding: "inner" + name, content: type, "": "outer" + name }, function( defaultExtra, funcName ) {
+ // margin is only for outerHeight, outerWidth
+ jQuery.fn[ funcName ] = function( margin, value ) {
+ var chainable = arguments.length && ( defaultExtra || typeof margin !== "boolean" ),
+ extra = defaultExtra || ( margin === true || value === true ? "margin" : "border" );
+
+ return jQuery.access( this, function( elem, type, value ) {
+ var doc;
+
+ if ( jQuery.isWindow( elem ) ) {
+ // As of 5/8/2012 this will yield incorrect results for Mobile Safari, but there
+ // isn't a whole lot we can do. See pull request at this URL for discussion:
+ // https://github.com/jquery/jquery/pull/764
+ return elem.document.documentElement[ "client" + name ];
+ }
+
+ // Get document width or height
+ if ( elem.nodeType === 9 ) {
+ doc = elem.documentElement;
+
+ // Either scroll[Width/Height] or offset[Width/Height] or client[Width/Height],
+ // whichever is greatest
+ return Math.max(
+ elem.body[ "scroll" + name ], doc[ "scroll" + name ],
+ elem.body[ "offset" + name ], doc[ "offset" + name ],
+ doc[ "client" + name ]
+ );
+ }
+
+ return value === undefined ?
+ // Get width or height on the element, requesting but not forcing parseFloat
+ jQuery.css( elem, type, extra ) :
+
+ // Set width or height on the element
+ jQuery.style( elem, type, value, extra );
+ }, type, chainable ? margin : undefined, chainable, null );
+ };
+ });
+});
diff --git a/js/jquery/src/jquery/effects.js b/js/jquery/src/jquery/effects.js
new file mode 100644
index 0000000000..b0a9083e70
--- /dev/null
+++ b/js/jquery/src/jquery/effects.js
@@ -0,0 +1,730 @@
+var fxNow, timerId,
+ rfxtypes = /^(?:toggle|show|hide)$/,
+ rfxnum = new RegExp( "^(?:([+-])=|)(" + core_pnum + ")([a-z%]*)$", "i" ),
+ rrun = /queueHooks$/,
+ animationPrefilters = [ defaultPrefilter ],
+ tweeners = {
+ "*": [function( prop, value ) {
+ var tween = this.createTween( prop, value ),
+ target = tween.cur(),
+ parts = rfxnum.exec( value ),
+ unit = parts && parts[ 3 ] || ( jQuery.cssNumber[ prop ] ? "" : "px" ),
+
+ // Starting value computation is required for potential unit mismatches
+ start = ( jQuery.cssNumber[ prop ] || unit !== "px" && +target ) &&
+ rfxnum.exec( jQuery.css( tween.elem, prop ) ),
+ scale = 1,
+ maxIterations = 20;
+
+ if ( start && start[ 3 ] !== unit ) {
+ // Trust units reported by jQuery.css
+ unit = unit || start[ 3 ];
+
+ // Make sure we update the tween properties later on
+ parts = parts || [];
+
+ // Iteratively approximate from a nonzero starting point
+ start = +target || 1;
+
+ do {
+ // If previous iteration zeroed out, double until we get *something*
+ // Use a string for doubling factor so we don't accidentally see scale as unchanged below
+ scale = scale || ".5";
+
+ // Adjust and apply
+ start = start / scale;
+ jQuery.style( tween.elem, prop, start + unit );
+
+ // Update scale, tolerating zero or NaN from tween.cur()
+ // And breaking the loop if scale is unchanged or perfect, or if we've just had enough
+ } while ( scale !== (scale = tween.cur() / target) && scale !== 1 && --maxIterations );
+ }
+
+ // Update tween properties
+ if ( parts ) {
+ start = tween.start = +start || +target || 0;
+ tween.unit = unit;
+ // If a +=/-= token was provided, we're doing a relative animation
+ tween.end = parts[ 1 ] ?
+ start + ( parts[ 1 ] + 1 ) * parts[ 2 ] :
+ +parts[ 2 ];
+ }
+
+ return tween;
+ }]
+ };
+
+// Animations created synchronously will run synchronously
+function createFxNow() {
+ setTimeout(function() {
+ fxNow = undefined;
+ });
+ return ( fxNow = jQuery.now() );
+}
+
+function createTween( value, prop, animation ) {
+ var tween,
+ collection = ( tweeners[ prop ] || [] ).concat( tweeners[ "*" ] ),
+ index = 0,
+ length = collection.length;
+ for ( ; index < length; index++ ) {
+ if ( (tween = collection[ index ].call( animation, prop, value )) ) {
+
+ // we're done with this property
+ return tween;
+ }
+ }
+}
+
+function Animation( elem, properties, options ) {
+ var result,
+ stopped,
+ index = 0,
+ length = animationPrefilters.length,
+ deferred = jQuery.Deferred().always( function() {
+ // don't match elem in the :animated selector
+ delete tick.elem;
+ }),
+ tick = function() {
+ if ( stopped ) {
+ return false;
+ }
+ var currentTime = fxNow || createFxNow(),
+ remaining = Math.max( 0, animation.startTime + animation.duration - currentTime ),
+ // archaic crash bug won't allow us to use 1 - ( 0.5 || 0 ) (#12497)
+ temp = remaining / animation.duration || 0,
+ percent = 1 - temp,
+ index = 0,
+ length = animation.tweens.length;
+
+ for ( ; index < length ; index++ ) {
+ animation.tweens[ index ].run( percent );
+ }
+
+ deferred.notifyWith( elem, [ animation, percent, remaining ]);
+
+ if ( percent < 1 && length ) {
+ return remaining;
+ } else {
+ deferred.resolveWith( elem, [ animation ] );
+ return false;
+ }
+ },
+ animation = deferred.promise({
+ elem: elem,
+ props: jQuery.extend( {}, properties ),
+ opts: jQuery.extend( true, { specialEasing: {} }, options ),
+ originalProperties: properties,
+ originalOptions: options,
+ startTime: fxNow || createFxNow(),
+ duration: options.duration,
+ tweens: [],
+ createTween: function( prop, end ) {
+ var tween = jQuery.Tween( elem, animation.opts, prop, end,
+ animation.opts.specialEasing[ prop ] || animation.opts.easing );
+ animation.tweens.push( tween );
+ return tween;
+ },
+ stop: function( gotoEnd ) {
+ var index = 0,
+ // if we are going to the end, we want to run all the tweens
+ // otherwise we skip this part
+ length = gotoEnd ? animation.tweens.length : 0;
+ if ( stopped ) {
+ return this;
+ }
+ stopped = true;
+ for ( ; index < length ; index++ ) {
+ animation.tweens[ index ].run( 1 );
+ }
+
+ // resolve when we played the last frame
+ // otherwise, reject
+ if ( gotoEnd ) {
+ deferred.resolveWith( elem, [ animation, gotoEnd ] );
+ } else {
+ deferred.rejectWith( elem, [ animation, gotoEnd ] );
+ }
+ return this;
+ }
+ }),
+ props = animation.props;
+
+ propFilter( props, animation.opts.specialEasing );
+
+ for ( ; index < length ; index++ ) {
+ result = animationPrefilters[ index ].call( animation, elem, props, animation.opts );
+ if ( result ) {
+ return result;
+ }
+ }
+
+ jQuery.map( props, createTween, animation );
+
+ if ( jQuery.isFunction( animation.opts.start ) ) {
+ animation.opts.start.call( elem, animation );
+ }
+
+ jQuery.fx.timer(
+ jQuery.extend( tick, {
+ elem: elem,
+ anim: animation,
+ queue: animation.opts.queue
+ })
+ );
+
+ // attach callbacks from options
+ return animation.progress( animation.opts.progress )
+ .done( animation.opts.done, animation.opts.complete )
+ .fail( animation.opts.fail )
+ .always( animation.opts.always );
+}
+
+function propFilter( props, specialEasing ) {
+ var index, name, easing, value, hooks;
+
+ // camelCase, specialEasing and expand cssHook pass
+ for ( index in props ) {
+ name = jQuery.camelCase( index );
+ easing = specialEasing[ name ];
+ value = props[ index ];
+ if ( jQuery.isArray( value ) ) {
+ easing = value[ 1 ];
+ value = props[ index ] = value[ 0 ];
+ }
+
+ if ( index !== name ) {
+ props[ name ] = value;
+ delete props[ index ];
+ }
+
+ hooks = jQuery.cssHooks[ name ];
+ if ( hooks && "expand" in hooks ) {
+ value = hooks.expand( value );
+ delete props[ name ];
+
+ // not quite $.extend, this wont overwrite keys already present.
+ // also - reusing 'index' from above because we have the correct "name"
+ for ( index in value ) {
+ if ( !( index in props ) ) {
+ props[ index ] = value[ index ];
+ specialEasing[ index ] = easing;
+ }
+ }
+ } else {
+ specialEasing[ name ] = easing;
+ }
+ }
+}
+
+jQuery.Animation = jQuery.extend( Animation, {
+
+ tweener: function( props, callback ) {
+ if ( jQuery.isFunction( props ) ) {
+ callback = props;
+ props = [ "*" ];
+ } else {
+ props = props.split(" ");
+ }
+
+ var prop,
+ index = 0,
+ length = props.length;
+
+ for ( ; index < length ; index++ ) {
+ prop = props[ index ];
+ tweeners[ prop ] = tweeners[ prop ] || [];
+ tweeners[ prop ].unshift( callback );
+ }
+ },
+
+ prefilter: function( callback, prepend ) {
+ if ( prepend ) {
+ animationPrefilters.unshift( callback );
+ } else {
+ animationPrefilters.push( callback );
+ }
+ }
+});
+
+function defaultPrefilter( elem, props, opts ) {
+ /* jshint validthis: true */
+ var prop, value, toggle, tween, hooks, oldfire,
+ anim = this,
+ orig = {},
+ style = elem.style,
+ hidden = elem.nodeType && isHidden( elem ),
+ dataShow = data_priv.get( elem, "fxshow" );
+
+ // handle queue: false promises
+ if ( !opts.queue ) {
+ hooks = jQuery._queueHooks( elem, "fx" );
+ if ( hooks.unqueued == null ) {
+ hooks.unqueued = 0;
+ oldfire = hooks.empty.fire;
+ hooks.empty.fire = function() {
+ if ( !hooks.unqueued ) {
+ oldfire();
+ }
+ };
+ }
+ hooks.unqueued++;
+
+ anim.always(function() {
+ // doing this makes sure that the complete handler will be called
+ // before this completes
+ anim.always(function() {
+ hooks.unqueued--;
+ if ( !jQuery.queue( elem, "fx" ).length ) {
+ hooks.empty.fire();
+ }
+ });
+ });
+ }
+
+ // height/width overflow pass
+ if ( elem.nodeType === 1 && ( "height" in props || "width" in props ) ) {
+ // Make sure that nothing sneaks out
+ // Record all 3 overflow attributes because IE9-10 do not
+ // change the overflow attribute when overflowX and
+ // overflowY are set to the same value
+ opts.overflow = [ style.overflow, style.overflowX, style.overflowY ];
+
+ // Set display property to inline-block for height/width
+ // animations on inline elements that are having width/height animated
+ if ( jQuery.css( elem, "display" ) === "inline" &&
+ jQuery.css( elem, "float" ) === "none" ) {
+
+ style.display = "inline-block";
+ }
+ }
+
+ if ( opts.overflow ) {
+ style.overflow = "hidden";
+ anim.always(function() {
+ style.overflow = opts.overflow[ 0 ];
+ style.overflowX = opts.overflow[ 1 ];
+ style.overflowY = opts.overflow[ 2 ];
+ });
+ }
+
+
+ // show/hide pass
+ for ( prop in props ) {
+ value = props[ prop ];
+ if ( rfxtypes.exec( value ) ) {
+ delete props[ prop ];
+ toggle = toggle || value === "toggle";
+ if ( value === ( hidden ? "hide" : "show" ) ) {
+
+ // If there is dataShow left over from a stopped hide or show and we are going to proceed with show, we should pretend to be hidden
+ if ( value === "show" && dataShow && dataShow[ prop ] !== undefined ) {
+ hidden = true;
+ } else {
+ continue;
+ }
+ }
+ orig[ prop ] = dataShow && dataShow[ prop ] || jQuery.style( elem, prop );
+ }
+ }
+
+ if ( !jQuery.isEmptyObject( orig ) ) {
+ if ( dataShow ) {
+ if ( "hidden" in dataShow ) {
+ hidden = dataShow.hidden;
+ }
+ } else {
+ dataShow = data_priv.access( elem, "fxshow", {} );
+ }
+
+ // store state if its toggle - enables .stop().toggle() to "reverse"
+ if ( toggle ) {
+ dataShow.hidden = !hidden;
+ }
+ if ( hidden ) {
+ jQuery( elem ).show();
+ } else {
+ anim.done(function() {
+ jQuery( elem ).hide();
+ });
+ }
+ anim.done(function() {
+ var prop;
+
+ data_priv.remove( elem, "fxshow" );
+ for ( prop in orig ) {
+ jQuery.style( elem, prop, orig[ prop ] );
+ }
+ });
+ for ( prop in orig ) {
+ tween = createTween( hidden ? dataShow[ prop ] : 0, prop, anim );
+
+ if ( !( prop in dataShow ) ) {
+ dataShow[ prop ] = tween.start;
+ if ( hidden ) {
+ tween.end = tween.start;
+ tween.start = prop === "width" || prop === "height" ? 1 : 0;
+ }
+ }
+ }
+ }
+}
+
+function Tween( elem, options, prop, end, easing ) {
+ return new Tween.prototype.init( elem, options, prop, end, easing );
+}
+jQuery.Tween = Tween;
+
+Tween.prototype = {
+ constructor: Tween,
+ init: function( elem, options, prop, end, easing, unit ) {
+ this.elem = elem;
+ this.prop = prop;
+ this.easing = easing || "swing";
+ this.options = options;
+ this.start = this.now = this.cur();
+ this.end = end;
+ this.unit = unit || ( jQuery.cssNumber[ prop ] ? "" : "px" );
+ },
+ cur: function() {
+ var hooks = Tween.propHooks[ this.prop ];
+
+ return hooks && hooks.get ?
+ hooks.get( this ) :
+ Tween.propHooks._default.get( this );
+ },
+ run: function( percent ) {
+ var eased,
+ hooks = Tween.propHooks[ this.prop ];
+
+ if ( this.options.duration ) {
+ this.pos = eased = jQuery.easing[ this.easing ](
+ percent, this.options.duration * percent, 0, 1, this.options.duration
+ );
+ } else {
+ this.pos = eased = percent;
+ }
+ this.now = ( this.end - this.start ) * eased + this.start;
+
+ if ( this.options.step ) {
+ this.options.step.call( this.elem, this.now, this );
+ }
+
+ if ( hooks && hooks.set ) {
+ hooks.set( this );
+ } else {
+ Tween.propHooks._default.set( this );
+ }
+ return this;
+ }
+};
+
+Tween.prototype.init.prototype = Tween.prototype;
+
+Tween.propHooks = {
+ _default: {
+ get: function( tween ) {
+ var result;
+
+ if ( tween.elem[ tween.prop ] != null &&
+ (!tween.elem.style || tween.elem.style[ tween.prop ] == null) ) {
+ return tween.elem[ tween.prop ];
+ }
+
+ // passing an empty string as a 3rd parameter to .css will automatically
+ // attempt a parseFloat and fallback to a string if the parse fails
+ // so, simple values such as "10px" are parsed to Float.
+ // complex values such as "rotate(1rad)" are returned as is.
+ result = jQuery.css( tween.elem, tween.prop, "" );
+ // Empty strings, null, undefined and "auto" are converted to 0.
+ return !result || result === "auto" ? 0 : result;
+ },
+ set: function( tween ) {
+ // use step hook for back compat - use cssHook if its there - use .style if its
+ // available and use plain properties where available
+ if ( jQuery.fx.step[ tween.prop ] ) {
+ jQuery.fx.step[ tween.prop ]( tween );
+ } else if ( tween.elem.style && ( tween.elem.style[ jQuery.cssProps[ tween.prop ] ] != null || jQuery.cssHooks[ tween.prop ] ) ) {
+ jQuery.style( tween.elem, tween.prop, tween.now + tween.unit );
+ } else {
+ tween.elem[ tween.prop ] = tween.now;
+ }
+ }
+ }
+};
+
+// Support: IE9
+// Panic based approach to setting things on disconnected nodes
+
+Tween.propHooks.scrollTop = Tween.propHooks.scrollLeft = {
+ set: function( tween ) {
+ if ( tween.elem.nodeType && tween.elem.parentNode ) {
+ tween.elem[ tween.prop ] = tween.now;
+ }
+ }
+};
+
+jQuery.each([ "toggle", "show", "hide" ], function( i, name ) {
+ var cssFn = jQuery.fn[ name ];
+ jQuery.fn[ name ] = function( speed, easing, callback ) {
+ return speed == null || typeof speed === "boolean" ?
+ cssFn.apply( this, arguments ) :
+ this.animate( genFx( name, true ), speed, easing, callback );
+ };
+});
+
+jQuery.fn.extend({
+ fadeTo: function( speed, to, easing, callback ) {
+
+ // show any hidden elements after setting opacity to 0
+ return this.filter( isHidden ).css( "opacity", 0 ).show()
+
+ // animate to the value specified
+ .end().animate({ opacity: to }, speed, easing, callback );
+ },
+ animate: function( prop, speed, easing, callback ) {
+ var empty = jQuery.isEmptyObject( prop ),
+ optall = jQuery.speed( speed, easing, callback ),
+ doAnimation = function() {
+ // Operate on a copy of prop so per-property easing won't be lost
+ var anim = Animation( this, jQuery.extend( {}, prop ), optall );
+
+ // Empty animations, or finishing resolves immediately
+ if ( empty || data_priv.get( this, "finish" ) ) {
+ anim.stop( true );
+ }
+ };
+ doAnimation.finish = doAnimation;
+
+ return empty || optall.queue === false ?
+ this.each( doAnimation ) :
+ this.queue( optall.queue, doAnimation );
+ },
+ stop: function( type, clearQueue, gotoEnd ) {
+ var stopQueue = function( hooks ) {
+ var stop = hooks.stop;
+ delete hooks.stop;
+ stop( gotoEnd );
+ };
+
+ if ( typeof type !== "string" ) {
+ gotoEnd = clearQueue;
+ clearQueue = type;
+ type = undefined;
+ }
+ if ( clearQueue && type !== false ) {
+ this.queue( type || "fx", [] );
+ }
+
+ return this.each(function() {
+ var dequeue = true,
+ index = type != null && type + "queueHooks",
+ timers = jQuery.timers,
+ data = data_priv.get( this );
+
+ if ( index ) {
+ if ( data[ index ] && data[ index ].stop ) {
+ stopQueue( data[ index ] );
+ }
+ } else {
+ for ( index in data ) {
+ if ( data[ index ] && data[ index ].stop && rrun.test( index ) ) {
+ stopQueue( data[ index ] );
+ }
+ }
+ }
+
+ for ( index = timers.length; index--; ) {
+ if ( timers[ index ].elem === this && (type == null || timers[ index ].queue === type) ) {
+ timers[ index ].anim.stop( gotoEnd );
+ dequeue = false;
+ timers.splice( index, 1 );
+ }
+ }
+
+ // start the next in the queue if the last step wasn't forced
+ // timers currently will call their complete callbacks, which will dequeue
+ // but only if they were gotoEnd
+ if ( dequeue || !gotoEnd ) {
+ jQuery.dequeue( this, type );
+ }
+ });
+ },
+ finish: function( type ) {
+ if ( type !== false ) {
+ type = type || "fx";
+ }
+ return this.each(function() {
+ var index,
+ data = data_priv.get( this ),
+ queue = data[ type + "queue" ],
+ hooks = data[ type + "queueHooks" ],
+ timers = jQuery.timers,
+ length = queue ? queue.length : 0;
+
+ // enable finishing flag on private data
+ data.finish = true;
+
+ // empty the queue first
+ jQuery.queue( this, type, [] );
+
+ if ( hooks && hooks.stop ) {
+ hooks.stop.call( this, true );
+ }
+
+ // look for any active animations, and finish them
+ for ( index = timers.length; index--; ) {
+ if ( timers[ index ].elem === this && timers[ index ].queue === type ) {
+ timers[ index ].anim.stop( true );
+ timers.splice( index, 1 );
+ }
+ }
+
+ // look for any animations in the old queue and finish them
+ for ( index = 0; index < length; index++ ) {
+ if ( queue[ index ] && queue[ index ].finish ) {
+ queue[ index ].finish.call( this );
+ }
+ }
+
+ // turn off finishing flag
+ delete data.finish;
+ });
+ }
+});
+
+// Generate parameters to create a standard animation
+function genFx( type, includeWidth ) {
+ var which,
+ attrs = { height: type },
+ i = 0;
+
+ // if we include width, step value is 1 to do all cssExpand values,
+ // if we don't include width, step value is 2 to skip over Left and Right
+ includeWidth = includeWidth? 1 : 0;
+ for( ; i < 4 ; i += 2 - includeWidth ) {
+ which = cssExpand[ i ];
+ attrs[ "margin" + which ] = attrs[ "padding" + which ] = type;
+ }
+
+ if ( includeWidth ) {
+ attrs.opacity = attrs.width = type;
+ }
+
+ return attrs;
+}
+
+// Generate shortcuts for custom animations
+jQuery.each({
+ slideDown: genFx("show"),
+ slideUp: genFx("hide"),
+ slideToggle: genFx("toggle"),
+ fadeIn: { opacity: "show" },
+ fadeOut: { opacity: "hide" },
+ fadeToggle: { opacity: "toggle" }
+}, function( name, props ) {
+ jQuery.fn[ name ] = function( speed, easing, callback ) {
+ return this.animate( props, speed, easing, callback );
+ };
+});
+
+jQuery.speed = function( speed, easing, fn ) {
+ var opt = speed && typeof speed === "object" ? jQuery.extend( {}, speed ) : {
+ complete: fn || !fn && easing ||
+ jQuery.isFunction( speed ) && speed,
+ duration: speed,
+ easing: fn && easing || easing && !jQuery.isFunction( easing ) && easing
+ };
+
+ opt.duration = jQuery.fx.off ? 0 : typeof opt.duration === "number" ? opt.duration :
+ opt.duration in jQuery.fx.speeds ? jQuery.fx.speeds[ opt.duration ] : jQuery.fx.speeds._default;
+
+ // normalize opt.queue - true/undefined/null -> "fx"
+ if ( opt.queue == null || opt.queue === true ) {
+ opt.queue = "fx";
+ }
+
+ // Queueing
+ opt.old = opt.complete;
+
+ opt.complete = function() {
+ if ( jQuery.isFunction( opt.old ) ) {
+ opt.old.call( this );
+ }
+
+ if ( opt.queue ) {
+ jQuery.dequeue( this, opt.queue );
+ }
+ };
+
+ return opt;
+};
+
+jQuery.easing = {
+ linear: function( p ) {
+ return p;
+ },
+ swing: function( p ) {
+ return 0.5 - Math.cos( p*Math.PI ) / 2;
+ }
+};
+
+jQuery.timers = [];
+jQuery.fx = Tween.prototype.init;
+jQuery.fx.tick = function() {
+ var timer,
+ timers = jQuery.timers,
+ i = 0;
+
+ fxNow = jQuery.now();
+
+ for ( ; i < timers.length; i++ ) {
+ timer = timers[ i ];
+ // Checks the timer has not already been removed
+ if ( !timer() && timers[ i ] === timer ) {
+ timers.splice( i--, 1 );
+ }
+ }
+
+ if ( !timers.length ) {
+ jQuery.fx.stop();
+ }
+ fxNow = undefined;
+};
+
+jQuery.fx.timer = function( timer ) {
+ if ( timer() && jQuery.timers.push( timer ) ) {
+ jQuery.fx.start();
+ }
+};
+
+jQuery.fx.interval = 13;
+
+jQuery.fx.start = function() {
+ if ( !timerId ) {
+ timerId = setInterval( jQuery.fx.tick, jQuery.fx.interval );
+ }
+};
+
+jQuery.fx.stop = function() {
+ clearInterval( timerId );
+ timerId = null;
+};
+
+jQuery.fx.speeds = {
+ slow: 600,
+ fast: 200,
+ // Default speed
+ _default: 400
+};
+
+// Back Compat <1.8 extension point
+jQuery.fx.step = {};
+
+if ( jQuery.expr && jQuery.expr.filters ) {
+ jQuery.expr.filters.animated = function( elem ) {
+ return jQuery.grep(jQuery.timers, function( fn ) {
+ return elem === fn.elem;
+ }).length;
+ };
+}
diff --git a/js/jquery/src/jquery/event-alias.js b/js/jquery/src/jquery/event-alias.js
new file mode 100644
index 0000000000..b1a15e840e
--- /dev/null
+++ b/js/jquery/src/jquery/event-alias.js
@@ -0,0 +1,32 @@
+jQuery.each( ("blur focus focusin focusout load resize scroll unload click dblclick " +
+ "mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " +
+ "change select submit keydown keypress keyup error contextmenu").split(" "), function( i, name ) {
+
+ // Handle event binding
+ jQuery.fn[ name ] = function( data, fn ) {
+ return arguments.length > 0 ?
+ this.on( name, null, data, fn ) :
+ this.trigger( name );
+ };
+});
+
+jQuery.fn.extend({
+ hover: function( fnOver, fnOut ) {
+ return this.mouseenter( fnOver ).mouseleave( fnOut || fnOver );
+ },
+
+ bind: function( types, data, fn ) {
+ return this.on( types, null, data, fn );
+ },
+ unbind: function( types, fn ) {
+ return this.off( types, null, fn );
+ },
+
+ delegate: function( selector, types, data, fn ) {
+ return this.on( types, selector, data, fn );
+ },
+ undelegate: function( selector, types, fn ) {
+ // ( namespace ) or ( selector, types [, fn] )
+ return arguments.length === 1 ? this.off( selector, "**" ) : this.off( types, selector || "**", fn );
+ }
+});
diff --git a/js/jquery/src/jquery/event.js b/js/jquery/src/jquery/event.js
new file mode 100644
index 0000000000..9262d1d6d4
--- /dev/null
+++ b/js/jquery/src/jquery/event.js
@@ -0,0 +1,829 @@
+var rkeyEvent = /^key/,
+ rmouseEvent = /^(?:mouse|contextmenu)|click/,
+ rfocusMorph = /^(?:focusinfocus|focusoutblur)$/,
+ rtypenamespace = /^([^.]*)(?:\.(.+)|)$/;
+
+function returnTrue() {
+ return true;
+}
+
+function returnFalse() {
+ return false;
+}
+
+function safeActiveElement() {
+ try {
+ return document.activeElement;
+ } catch ( err ) { }
+}
+
+/*
+ * Helper functions for managing events -- not part of the public interface.
+ * Props to Dean Edwards' addEvent library for many of the ideas.
+ */
+jQuery.event = {
+
+ global: {},
+
+ add: function( elem, types, handler, data, selector ) {
+
+ var handleObjIn, eventHandle, tmp,
+ events, t, handleObj,
+ special, handlers, type, namespaces, origType,
+ elemData = data_priv.get( elem );
+
+ // Don't attach events to noData or text/comment nodes (but allow plain objects)
+ if ( !elemData ) {
+ return;
+ }
+
+ // Caller can pass in an object of custom data in lieu of the handler
+ if ( handler.handler ) {
+ handleObjIn = handler;
+ handler = handleObjIn.handler;
+ selector = handleObjIn.selector;
+ }
+
+ // Make sure that the handler has a unique ID, used to find/remove it later
+ if ( !handler.guid ) {
+ handler.guid = jQuery.guid++;
+ }
+
+ // Init the element's event structure and main handler, if this is the first
+ if ( !(events = elemData.events) ) {
+ events = elemData.events = {};
+ }
+ if ( !(eventHandle = elemData.handle) ) {
+ eventHandle = elemData.handle = function( e ) {
+ // Discard the second event of a jQuery.event.trigger() and
+ // when an event is called after a page has unloaded
+ return typeof jQuery !== core_strundefined && (!e || jQuery.event.triggered !== e.type) ?
+ jQuery.event.dispatch.apply( eventHandle.elem, arguments ) :
+ undefined;
+ };
+ // Add elem as a property of the handle fn to prevent a memory leak with IE non-native events
+ eventHandle.elem = elem;
+ }
+
+ // Handle multiple events separated by a space
+ types = ( types || "" ).match( core_rnotwhite ) || [""];
+ t = types.length;
+ while ( t-- ) {
+ tmp = rtypenamespace.exec( types[t] ) || [];
+ type = origType = tmp[1];
+ namespaces = ( tmp[2] || "" ).split( "." ).sort();
+
+ // There *must* be a type, no attaching namespace-only handlers
+ if ( !type ) {
+ continue;
+ }
+
+ // If event changes its type, use the special event handlers for the changed type
+ special = jQuery.event.special[ type ] || {};
+
+ // If selector defined, determine special event api type, otherwise given type
+ type = ( selector ? special.delegateType : special.bindType ) || type;
+
+ // Update special based on newly reset type
+ special = jQuery.event.special[ type ] || {};
+
+ // handleObj is passed to all event handlers
+ handleObj = jQuery.extend({
+ type: type,
+ origType: origType,
+ data: data,
+ handler: handler,
+ guid: handler.guid,
+ selector: selector,
+ needsContext: selector && jQuery.expr.match.needsContext.test( selector ),
+ namespace: namespaces.join(".")
+ }, handleObjIn );
+
+ // Init the event handler queue if we're the first
+ if ( !(handlers = events[ type ]) ) {
+ handlers = events[ type ] = [];
+ handlers.delegateCount = 0;
+
+ // Only use addEventListener if the special events handler returns false
+ if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) {
+ if ( elem.addEventListener ) {
+ elem.addEventListener( type, eventHandle, false );
+ }
+ }
+ }
+
+ if ( special.add ) {
+ special.add.call( elem, handleObj );
+
+ if ( !handleObj.handler.guid ) {
+ handleObj.handler.guid = handler.guid;
+ }
+ }
+
+ // Add to the element's handler list, delegates in front
+ if ( selector ) {
+ handlers.splice( handlers.delegateCount++, 0, handleObj );
+ } else {
+ handlers.push( handleObj );
+ }
+
+ // Keep track of which events have ever been used, for event optimization
+ jQuery.event.global[ type ] = true;
+ }
+
+ // Nullify elem to prevent memory leaks in IE
+ elem = null;
+ },
+
+ // Detach an event or set of events from an element
+ remove: function( elem, types, handler, selector, mappedTypes ) {
+
+ var j, origCount, tmp,
+ events, t, handleObj,
+ special, handlers, type, namespaces, origType,
+ elemData = data_priv.hasData( elem ) && data_priv.get( elem );
+
+ if ( !elemData || !(events = elemData.events) ) {
+ return;
+ }
+
+ // Once for each type.namespace in types; type may be omitted
+ types = ( types || "" ).match( core_rnotwhite ) || [""];
+ t = types.length;
+ while ( t-- ) {
+ tmp = rtypenamespace.exec( types[t] ) || [];
+ type = origType = tmp[1];
+ namespaces = ( tmp[2] || "" ).split( "." ).sort();
+
+ // Unbind all events (on this namespace, if provided) for the element
+ if ( !type ) {
+ for ( type in events ) {
+ jQuery.event.remove( elem, type + types[ t ], handler, selector, true );
+ }
+ continue;
+ }
+
+ special = jQuery.event.special[ type ] || {};
+ type = ( selector ? special.delegateType : special.bindType ) || type;
+ handlers = events[ type ] || [];
+ tmp = tmp[2] && new RegExp( "(^|\\.)" + namespaces.join("\\.(?:.*\\.|)") + "(\\.|$)" );
+
+ // Remove matching events
+ origCount = j = handlers.length;
+ while ( j-- ) {
+ handleObj = handlers[ j ];
+
+ if ( ( mappedTypes || origType === handleObj.origType ) &&
+ ( !handler || handler.guid === handleObj.guid ) &&
+ ( !tmp || tmp.test( handleObj.namespace ) ) &&
+ ( !selector || selector === handleObj.selector || selector === "**" && handleObj.selector ) ) {
+ handlers.splice( j, 1 );
+
+ if ( handleObj.selector ) {
+ handlers.delegateCount--;
+ }
+ if ( special.remove ) {
+ special.remove.call( elem, handleObj );
+ }
+ }
+ }
+
+ // Remove generic event handler if we removed something and no more handlers exist
+ // (avoids potential for endless recursion during removal of special event handlers)
+ if ( origCount && !handlers.length ) {
+ if ( !special.teardown || special.teardown.call( elem, namespaces, elemData.handle ) === false ) {
+ jQuery.removeEvent( elem, type, elemData.handle );
+ }
+
+ delete events[ type ];
+ }
+ }
+
+ // Remove the expando if it's no longer used
+ if ( jQuery.isEmptyObject( events ) ) {
+ delete elemData.handle;
+ data_priv.remove( elem, "events" );
+ }
+ },
+
+ trigger: function( event, data, elem, onlyHandlers ) {
+
+ var i, cur, tmp, bubbleType, ontype, handle, special,
+ eventPath = [ elem || document ],
+ type = core_hasOwn.call( event, "type" ) ? event.type : event,
+ namespaces = core_hasOwn.call( event, "namespace" ) ? event.namespace.split(".") : [];
+
+ cur = tmp = elem = elem || document;
+
+ // Don't do events on text and comment nodes
+ if ( elem.nodeType === 3 || elem.nodeType === 8 ) {
+ return;
+ }
+
+ // focus/blur morphs to focusin/out; ensure we're not firing them right now
+ if ( rfocusMorph.test( type + jQuery.event.triggered ) ) {
+ return;
+ }
+
+ if ( type.indexOf(".") >= 0 ) {
+ // Namespaced trigger; create a regexp to match event type in handle()
+ namespaces = type.split(".");
+ type = namespaces.shift();
+ namespaces.sort();
+ }
+ ontype = type.indexOf(":") < 0 && "on" + type;
+
+ // Caller can pass in a jQuery.Event object, Object, or just an event type string
+ event = event[ jQuery.expando ] ?
+ event :
+ new jQuery.Event( type, typeof event === "object" && event );
+
+ // Trigger bitmask: & 1 for native handlers; & 2 for jQuery (always true)
+ event.isTrigger = onlyHandlers ? 2 : 3;
+ event.namespace = namespaces.join(".");
+ event.namespace_re = event.namespace ?
+ new RegExp( "(^|\\.)" + namespaces.join("\\.(?:.*\\.|)") + "(\\.|$)" ) :
+ null;
+
+ // Clean up the event in case it is being reused
+ event.result = undefined;
+ if ( !event.target ) {
+ event.target = elem;
+ }
+
+ // Clone any incoming data and prepend the event, creating the handler arg list
+ data = data == null ?
+ [ event ] :
+ jQuery.makeArray( data, [ event ] );
+
+ // Allow special events to draw outside the lines
+ special = jQuery.event.special[ type ] || {};
+ if ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) {
+ return;
+ }
+
+ // Determine event propagation path in advance, per W3C events spec (#9951)
+ // Bubble up to document, then to window; watch for a global ownerDocument var (#9724)
+ if ( !onlyHandlers && !special.noBubble && !jQuery.isWindow( elem ) ) {
+
+ bubbleType = special.delegateType || type;
+ if ( !rfocusMorph.test( bubbleType + type ) ) {
+ cur = cur.parentNode;
+ }
+ for ( ; cur; cur = cur.parentNode ) {
+ eventPath.push( cur );
+ tmp = cur;
+ }
+
+ // Only add window if we got to document (e.g., not plain obj or detached DOM)
+ if ( tmp === (elem.ownerDocument || document) ) {
+ eventPath.push( tmp.defaultView || tmp.parentWindow || window );
+ }
+ }
+
+ // Fire handlers on the event path
+ i = 0;
+ while ( (cur = eventPath[i++]) && !event.isPropagationStopped() ) {
+
+ event.type = i > 1 ?
+ bubbleType :
+ special.bindType || type;
+
+ // jQuery handler
+ handle = ( data_priv.get( cur, "events" ) || {} )[ event.type ] && data_priv.get( cur, "handle" );
+ if ( handle ) {
+ handle.apply( cur, data );
+ }
+
+ // Native handler
+ handle = ontype && cur[ ontype ];
+ if ( handle && jQuery.acceptData( cur ) && handle.apply && handle.apply( cur, data ) === false ) {
+ event.preventDefault();
+ }
+ }
+ event.type = type;
+
+ // If nobody prevented the default action, do it now
+ if ( !onlyHandlers && !event.isDefaultPrevented() ) {
+
+ if ( (!special._default || special._default.apply( eventPath.pop(), data ) === false) &&
+ jQuery.acceptData( elem ) ) {
+
+ // Call a native DOM method on the target with the same name name as the event.
+ // Don't do default actions on window, that's where global variables be (#6170)
+ if ( ontype && jQuery.isFunction( elem[ type ] ) && !jQuery.isWindow( elem ) ) {
+
+ // Don't re-trigger an onFOO event when we call its FOO() method
+ tmp = elem[ ontype ];
+
+ if ( tmp ) {
+ elem[ ontype ] = null;
+ }
+
+ // Prevent re-triggering of the same event, since we already bubbled it above
+ jQuery.event.triggered = type;
+ elem[ type ]();
+ jQuery.event.triggered = undefined;
+
+ if ( tmp ) {
+ elem[ ontype ] = tmp;
+ }
+ }
+ }
+ }
+
+ return event.result;
+ },
+
+ dispatch: function( event ) {
+
+ // Make a writable jQuery.Event from the native event object
+ event = jQuery.event.fix( event );
+
+ var i, j, ret, matched, handleObj,
+ handlerQueue = [],
+ args = core_slice.call( arguments ),
+ handlers = ( data_priv.get( this, "events" ) || {} )[ event.type ] || [],
+ special = jQuery.event.special[ event.type ] || {};
+
+ // Use the fix-ed jQuery.Event rather than the (read-only) native event
+ args[0] = event;
+ event.delegateTarget = this;
+
+ // Call the preDispatch hook for the mapped type, and let it bail if desired
+ if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) {
+ return;
+ }
+
+ // Determine handlers
+ handlerQueue = jQuery.event.handlers.call( this, event, handlers );
+
+ // Run delegates first; they may want to stop propagation beneath us
+ i = 0;
+ while ( (matched = handlerQueue[ i++ ]) && !event.isPropagationStopped() ) {
+ event.currentTarget = matched.elem;
+
+ j = 0;
+ while ( (handleObj = matched.handlers[ j++ ]) && !event.isImmediatePropagationStopped() ) {
+
+ // Triggered event must either 1) have no namespace, or
+ // 2) have namespace(s) a subset or equal to those in the bound event (both can have no namespace).
+ if ( !event.namespace_re || event.namespace_re.test( handleObj.namespace ) ) {
+
+ event.handleObj = handleObj;
+ event.data = handleObj.data;
+
+ ret = ( (jQuery.event.special[ handleObj.origType ] || {}).handle || handleObj.handler )
+ .apply( matched.elem, args );
+
+ if ( ret !== undefined ) {
+ if ( (event.result = ret) === false ) {
+ event.preventDefault();
+ event.stopPropagation();
+ }
+ }
+ }
+ }
+ }
+
+ // Call the postDispatch hook for the mapped type
+ if ( special.postDispatch ) {
+ special.postDispatch.call( this, event );
+ }
+
+ return event.result;
+ },
+
+ handlers: function( event, handlers ) {
+ var i, matches, sel, handleObj,
+ handlerQueue = [],
+ delegateCount = handlers.delegateCount,
+ cur = event.target;
+
+ // Find delegate handlers
+ // Black-hole SVG <use> instance trees (#13180)
+ // Avoid non-left-click bubbling in Firefox (#3861)
+ if ( delegateCount && cur.nodeType && (!event.button || event.type !== "click") ) {
+
+ for ( ; cur !== this; cur = cur.parentNode || this ) {
+
+ // Don't process clicks on disabled elements (#6911, #8165, #11382, #11764)
+ if ( cur.disabled !== true || event.type !== "click" ) {
+ matches = [];
+ for ( i = 0; i < delegateCount; i++ ) {
+ handleObj = handlers[ i ];
+
+ // Don't conflict with Object.prototype properties (#13203)
+ sel = handleObj.selector + " ";
+
+ if ( matches[ sel ] === undefined ) {
+ matches[ sel ] = handleObj.needsContext ?
+ jQuery( sel, this ).index( cur ) >= 0 :
+ jQuery.find( sel, this, null, [ cur ] ).length;
+ }
+ if ( matches[ sel ] ) {
+ matches.push( handleObj );
+ }
+ }
+ if ( matches.length ) {
+ handlerQueue.push({ elem: cur, handlers: matches });
+ }
+ }
+ }
+ }
+
+ // Add the remaining (directly-bound) handlers
+ if ( delegateCount < handlers.length ) {
+ handlerQueue.push({ elem: this, handlers: handlers.slice( delegateCount ) });
+ }
+
+ return handlerQueue;
+ },
+
+ // Includes some event props shared by KeyEvent and MouseEvent
+ props: "altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),
+
+ fixHooks: {},
+
+ keyHooks: {
+ props: "char charCode key keyCode".split(" "),
+ filter: function( event, original ) {
+
+ // Add which for key events
+ if ( event.which == null ) {
+ event.which = original.charCode != null ? original.charCode : original.keyCode;
+ }
+
+ return event;
+ }
+ },
+
+ mouseHooks: {
+ props: "button buttons clientX clientY offsetX offsetY pageX pageY screenX screenY toElement".split(" "),
+ filter: function( event, original ) {
+ var eventDoc, doc, body,
+ button = original.button;
+
+ // Calculate pageX/Y if missing and clientX/Y available
+ if ( event.pageX == null && original.clientX != null ) {
+ eventDoc = event.target.ownerDocument || document;
+ doc = eventDoc.documentElement;
+ body = eventDoc.body;
+
+ event.pageX = original.clientX + ( doc && doc.scrollLeft || body && body.scrollLeft || 0 ) - ( doc && doc.clientLeft || body && body.clientLeft || 0 );
+ event.pageY = original.clientY + ( doc && doc.scrollTop || body && body.scrollTop || 0 ) - ( doc && doc.clientTop || body && body.clientTop || 0 );
+ }
+
+ // Add which for click: 1 === left; 2 === middle; 3 === right
+ // Note: button is not normalized, so don't use it
+ if ( !event.which && button !== undefined ) {
+ event.which = ( button & 1 ? 1 : ( button & 2 ? 3 : ( button & 4 ? 2 : 0 ) ) );
+ }
+
+ return event;
+ }
+ },
+
+ fix: function( event ) {
+ if ( event[ jQuery.expando ] ) {
+ return event;
+ }
+
+ // Create a writable copy of the event object and normalize some properties
+ var i, prop, copy,
+ type = event.type,
+ originalEvent = event,
+ fixHook = this.fixHooks[ type ];
+
+ if ( !fixHook ) {
+ this.fixHooks[ type ] = fixHook =
+ rmouseEvent.test( type ) ? this.mouseHooks :
+ rkeyEvent.test( type ) ? this.keyHooks :
+ {};
+ }
+ copy = fixHook.props ? this.props.concat( fixHook.props ) : this.props;
+
+ event = new jQuery.Event( originalEvent );
+
+ i = copy.length;
+ while ( i-- ) {
+ prop = copy[ i ];
+ event[ prop ] = originalEvent[ prop ];
+ }
+
+ // Support: Cordova 2.5 (WebKit) (#13255)
+ // All events should have a target; Cordova deviceready doesn't
+ if ( !event.target ) {
+ event.target = document;
+ }
+
+ // Support: Safari 6.0+, Chrome < 28
+ // Target should not be a text node (#504, #13143)
+ if ( event.target.nodeType === 3 ) {
+ event.target = event.target.parentNode;
+ }
+
+ return fixHook.filter? fixHook.filter( event, originalEvent ) : event;
+ },
+
+ special: {
+ load: {
+ // Prevent triggered image.load events from bubbling to window.load
+ noBubble: true
+ },
+ focus: {
+ // Fire native event if possible so blur/focus sequence is correct
+ trigger: function() {
+ if ( this !== safeActiveElement() && this.focus ) {
+ this.focus();
+ return false;
+ }
+ },
+ delegateType: "focusin"
+ },
+ blur: {
+ trigger: function() {
+ if ( this === safeActiveElement() && this.blur ) {
+ this.blur();
+ return false;
+ }
+ },
+ delegateType: "focusout"
+ },
+ click: {
+ // For checkbox, fire native event so checked state will be right
+ trigger: function() {
+ if ( this.type === "checkbox" && this.click && jQuery.nodeName( this, "input" ) ) {
+ this.click();
+ return false;
+ }
+ },
+
+ // For cross-browser consistency, don't fire native .click() on links
+ _default: function( event ) {
+ return jQuery.nodeName( event.target, "a" );
+ }
+ },
+
+ beforeunload: {
+ postDispatch: function( event ) {
+
+ // Support: Firefox 20+
+ // Firefox doesn't alert if the returnValue field is not set.
+ if ( event.result !== undefined ) {
+ event.originalEvent.returnValue = event.result;
+ }
+ }
+ }
+ },
+
+ simulate: function( type, elem, event, bubble ) {
+ // Piggyback on a donor event to simulate a different one.
+ // Fake originalEvent to avoid donor's stopPropagation, but if the
+ // simulated event prevents default then we do the same on the donor.
+ var e = jQuery.extend(
+ new jQuery.Event(),
+ event,
+ {
+ type: type,
+ isSimulated: true,
+ originalEvent: {}
+ }
+ );
+ if ( bubble ) {
+ jQuery.event.trigger( e, null, elem );
+ } else {
+ jQuery.event.dispatch.call( elem, e );
+ }
+ if ( e.isDefaultPrevented() ) {
+ event.preventDefault();
+ }
+ }
+};
+
+jQuery.removeEvent = function( elem, type, handle ) {
+ if ( elem.removeEventListener ) {
+ elem.removeEventListener( type, handle, false );
+ }
+};
+
+jQuery.Event = function( src, props ) {
+ // Allow instantiation without the 'new' keyword
+ if ( !(this instanceof jQuery.Event) ) {
+ return new jQuery.Event( src, props );
+ }
+
+ // Event object
+ if ( src && src.type ) {
+ this.originalEvent = src;
+ this.type = src.type;
+
+ // Events bubbling up the document may have been marked as prevented
+ // by a handler lower down the tree; reflect the correct value.
+ this.isDefaultPrevented = ( src.defaultPrevented ||
+ src.getPreventDefault && src.getPreventDefault() ) ? returnTrue : returnFalse;
+
+ // Event type
+ } else {
+ this.type = src;
+ }
+
+ // Put explicitly provided properties onto the event object
+ if ( props ) {
+ jQuery.extend( this, props );
+ }
+
+ // Create a timestamp if incoming event doesn't have one
+ this.timeStamp = src && src.timeStamp || jQuery.now();
+
+ // Mark it as fixed
+ this[ jQuery.expando ] = true;
+};
+
+// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding
+// http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html
+jQuery.Event.prototype = {
+ isDefaultPrevented: returnFalse,
+ isPropagationStopped: returnFalse,
+ isImmediatePropagationStopped: returnFalse,
+
+ preventDefault: function() {
+ var e = this.originalEvent;
+
+ this.isDefaultPrevented = returnTrue;
+
+ if ( e && e.preventDefault ) {
+ e.preventDefault();
+ }
+ },
+ stopPropagation: function() {
+ var e = this.originalEvent;
+
+ this.isPropagationStopped = returnTrue;
+
+ if ( e && e.stopPropagation ) {
+ e.stopPropagation();
+ }
+ },
+ stopImmediatePropagation: function() {
+ this.isImmediatePropagationStopped = returnTrue;
+ this.stopPropagation();
+ }
+};
+
+// Create mouseenter/leave events using mouseover/out and event-time checks
+// Support: Chrome 15+
+jQuery.each({
+ mouseenter: "mouseover",
+ mouseleave: "mouseout"
+}, function( orig, fix ) {
+ jQuery.event.special[ orig ] = {
+ delegateType: fix,
+ bindType: fix,
+
+ handle: function( event ) {
+ var ret,
+ target = this,
+ related = event.relatedTarget,
+ handleObj = event.handleObj;
+
+ // For mousenter/leave call the handler if related is outside the target.
+ // NB: No relatedTarget if the mouse left/entered the browser window
+ if ( !related || (related !== target && !jQuery.contains( target, related )) ) {
+ event.type = handleObj.origType;
+ ret = handleObj.handler.apply( this, arguments );
+ event.type = fix;
+ }
+ return ret;
+ }
+ };
+});
+
+// Create "bubbling" focus and blur events
+// Support: Firefox, Chrome, Safari
+if ( !jQuery.support.focusinBubbles ) {
+ jQuery.each({ focus: "focusin", blur: "focusout" }, function( orig, fix ) {
+
+ // Attach a single capturing handler while someone wants focusin/focusout
+ var attaches = 0,
+ handler = function( event ) {
+ jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ), true );
+ };
+
+ jQuery.event.special[ fix ] = {
+ setup: function() {
+ if ( attaches++ === 0 ) {
+ document.addEventListener( orig, handler, true );
+ }
+ },
+ teardown: function() {
+ if ( --attaches === 0 ) {
+ document.removeEventListener( orig, handler, true );
+ }
+ }
+ };
+ });
+}
+
+jQuery.fn.extend({
+
+ on: function( types, selector, data, fn, /*INTERNAL*/ one ) {
+ var origFn, type;
+
+ // Types can be a map of types/handlers
+ if ( typeof types === "object" ) {
+ // ( types-Object, selector, data )
+ if ( typeof selector !== "string" ) {
+ // ( types-Object, data )
+ data = data || selector;
+ selector = undefined;
+ }
+ for ( type in types ) {
+ this.on( type, selector, data, types[ type ], one );
+ }
+ return this;
+ }
+
+ if ( data == null && fn == null ) {
+ // ( types, fn )
+ fn = selector;
+ data = selector = undefined;
+ } else if ( fn == null ) {
+ if ( typeof selector === "string" ) {
+ // ( types, selector, fn )
+ fn = data;
+ data = undefined;
+ } else {
+ // ( types, data, fn )
+ fn = data;
+ data = selector;
+ selector = undefined;
+ }
+ }
+ if ( fn === false ) {
+ fn = returnFalse;
+ } else if ( !fn ) {
+ return this;
+ }
+
+ if ( one === 1 ) {
+ origFn = fn;
+ fn = function( event ) {
+ // Can use an empty set, since event contains the info
+ jQuery().off( event );
+ return origFn.apply( this, arguments );
+ };
+ // Use same guid so caller can remove using origFn
+ fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ );
+ }
+ return this.each( function() {
+ jQuery.event.add( this, types, fn, data, selector );
+ });
+ },
+ one: function( types, selector, data, fn ) {
+ return this.on( types, selector, data, fn, 1 );
+ },
+ off: function( types, selector, fn ) {
+ var handleObj, type;
+ if ( types && types.preventDefault && types.handleObj ) {
+ // ( event ) dispatched jQuery.Event
+ handleObj = types.handleObj;
+ jQuery( types.delegateTarget ).off(
+ handleObj.namespace ? handleObj.origType + "." + handleObj.namespace : handleObj.origType,
+ handleObj.selector,
+ handleObj.handler
+ );
+ return this;
+ }
+ if ( typeof types === "object" ) {
+ // ( types-object [, selector] )
+ for ( type in types ) {
+ this.off( type, selector, types[ type ] );
+ }
+ return this;
+ }
+ if ( selector === false || typeof selector === "function" ) {
+ // ( types [, fn] )
+ fn = selector;
+ selector = undefined;
+ }
+ if ( fn === false ) {
+ fn = returnFalse;
+ }
+ return this.each(function() {
+ jQuery.event.remove( this, types, fn, selector );
+ });
+ },
+
+ trigger: function( type, data ) {
+ return this.each(function() {
+ jQuery.event.trigger( type, data, this );
+ });
+ },
+ triggerHandler: function( type, data ) {
+ var elem = this[0];
+ if ( elem ) {
+ return jQuery.event.trigger( type, data, elem, true );
+ }
+ }
+});
diff --git a/js/jquery/src/jquery/intro.js b/js/jquery/src/jquery/intro.js
new file mode 100644
index 0000000000..1d1640aa3c
--- /dev/null
+++ b/js/jquery/src/jquery/intro.js
@@ -0,0 +1,54 @@
+/*!
+ * jQuery JavaScript Library v@VERSION
+ * http://jquery.com/
+ *
+ * Includes Sizzle.js
+ * http://sizzlejs.com/
+ *
+ * Copyright 2005, 2013 jQuery Foundation, Inc. and other contributors
+ * Released under the MIT license
+ * http://jquery.org/license
+ *
+ * Date: @DATE
+ */
+
+(function ( window, factory ) {
+
+ if ( typeof module === "object" && typeof module.exports === "object" ) {
+ // Expose a jQuery-making factory as module.exports in loaders that implement the Node
+ // module pattern (including browserify).
+ // This accentuates the need for a real window in the environment
+ // e.g. var jQuery = require("jquery")(window);
+ module.exports = function( w ) {
+ w = w || window;
+ if ( !w.document ) {
+ throw new Error("jQuery requires a window with a document");
+ }
+ return factory( w );
+ };
+ } else {
+ // Execute the factory to produce jQuery
+ var jQuery = factory( window );
+
+ // Register as a named AMD module, since jQuery can be concatenated with other
+ // files that may use define, but not via a proper concatenation script that
+ // understands anonymous AMD modules. A named AMD is safest and most robust
+ // way to register. Lowercase jquery is used because AMD module names are
+ // derived from file names, and jQuery is normally delivered in a lowercase
+ // file name. Do this after creating the global so that if an AMD module wants
+ // to call noConflict to hide this version of jQuery, it will work.
+ if ( typeof define === "function" && define.amd ) {
+ define( "jquery", [], function() {
+ return jQuery;
+ });
+ }
+ }
+
+// Pass this, window may not be defined yet
+}(this, function ( window ) {
+
+// Can't do this because several apps including ASP.NET trace
+// the stack via arguments.caller.callee and Firefox dies if
+// you try to trace through "use strict" call chains. (#13335)
+// Support: Firefox 18+
+//"use strict";
diff --git a/js/jquery/src/jquery/manipulation.js b/js/jquery/src/jquery/manipulation.js
new file mode 100644
index 0000000000..19494ae2dd
--- /dev/null
+++ b/js/jquery/src/jquery/manipulation.js
@@ -0,0 +1,577 @@
+var rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,
+ rtagName = /<([\w:]+)/,
+ rhtml = /<|&#?\w+;/,
+ rnoInnerhtml = /<(?:script|style|link)/i,
+ manipulation_rcheckableType = /^(?:checkbox|radio)$/i,
+ // checked="checked" or checked
+ rchecked = /checked\s*(?:[^=]|=\s*.checked.)/i,
+ rscriptType = /^$|\/(?:java|ecma)script/i,
+ rscriptTypeMasked = /^true\/(.*)/,
+ rcleanScript = /^\s*<!(?:\[CDATA\[|--)|(?:\]\]|--)>\s*$/g,
+
+ // We have to close these tags to support XHTML (#13200)
+ wrapMap = {
+
+ // Support: IE 9
+ option: [ 1, "<select multiple='multiple'>", "</select>" ],
+
+ thead: [ 1, "<table>", "</table>" ],
+ col: [ 2, "<table><colgroup>", "</colgroup></table>" ],
+ tr: [ 2, "<table><tbody>", "</tbody></table>" ],
+ td: [ 3, "<table><tbody><tr>", "</tr></tbody></table>" ],
+
+ _default: [ 0, "", "" ]
+ };
+
+// Support: IE 9
+wrapMap.optgroup = wrapMap.option;
+
+wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead;
+wrapMap.th = wrapMap.td;
+
+jQuery.fn.extend({
+ text: function( value ) {
+ return jQuery.access( this, function( value ) {
+ return value === undefined ?
+ jQuery.text( this ) :
+ this.empty().append( ( this[ 0 ] && this[ 0 ].ownerDocument || document ).createTextNode( value ) );
+ }, null, value, arguments.length );
+ },
+
+ append: function() {
+ return this.domManip( arguments, function( elem ) {
+ if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) {
+ var target = manipulationTarget( this, elem );
+ target.appendChild( elem );
+ }
+ });
+ },
+
+ prepend: function() {
+ return this.domManip( arguments, function( elem ) {
+ if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) {
+ var target = manipulationTarget( this, elem );
+ target.insertBefore( elem, target.firstChild );
+ }
+ });
+ },
+
+ before: function() {
+ return this.domManip( arguments, function( elem ) {
+ if ( this.parentNode ) {
+ this.parentNode.insertBefore( elem, this );
+ }
+ });
+ },
+
+ after: function() {
+ return this.domManip( arguments, function( elem ) {
+ if ( this.parentNode ) {
+ this.parentNode.insertBefore( elem, this.nextSibling );
+ }
+ });
+ },
+
+ // keepData is for internal use only--do not document
+ remove: function( selector, keepData ) {
+ var elem,
+ elems = selector ? jQuery.filter( selector, this ) : this,
+ i = 0;
+
+ for ( ; (elem = elems[i]) != null; i++ ) {
+ if ( !keepData && elem.nodeType === 1 ) {
+ jQuery.cleanData( getAll( elem ) );
+ }
+
+ if ( elem.parentNode ) {
+ if ( keepData && jQuery.contains( elem.ownerDocument, elem ) ) {
+ setGlobalEval( getAll( elem, "script" ) );
+ }
+ elem.parentNode.removeChild( elem );
+ }
+ }
+
+ return this;
+ },
+
+ empty: function() {
+ var elem,
+ i = 0;
+
+ for ( ; (elem = this[i]) != null; i++ ) {
+ if ( elem.nodeType === 1 ) {
+
+ // Prevent memory leaks
+ jQuery.cleanData( getAll( elem, false ) );
+
+ // Remove any remaining nodes
+ elem.textContent = "";
+ }
+ }
+
+ return this;
+ },
+
+ clone: function( dataAndEvents, deepDataAndEvents ) {
+ dataAndEvents = dataAndEvents == null ? false : dataAndEvents;
+ deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents;
+
+ return this.map( function () {
+ return jQuery.clone( this, dataAndEvents, deepDataAndEvents );
+ });
+ },
+
+ html: function( value ) {
+ return jQuery.access( this, function( value ) {
+ var elem = this[ 0 ] || {},
+ i = 0,
+ l = this.length;
+
+ if ( value === undefined && elem.nodeType === 1 ) {
+ return elem.innerHTML;
+ }
+
+ // See if we can take a shortcut and just use innerHTML
+ if ( typeof value === "string" && !rnoInnerhtml.test( value ) &&
+ !wrapMap[ ( rtagName.exec( value ) || [ "", "" ] )[ 1 ].toLowerCase() ] ) {
+
+ value = value.replace( rxhtmlTag, "<$1></$2>" );
+
+ try {
+ for ( ; i < l; i++ ) {
+ elem = this[ i ] || {};
+
+ // Remove element nodes and prevent memory leaks
+ if ( elem.nodeType === 1 ) {
+ jQuery.cleanData( getAll( elem, false ) );
+ elem.innerHTML = value;
+ }
+ }
+
+ elem = 0;
+
+ // If using innerHTML throws an exception, use the fallback method
+ } catch( e ) {}
+ }
+
+ if ( elem ) {
+ this.empty().append( value );
+ }
+ }, null, value, arguments.length );
+ },
+
+ replaceWith: function() {
+ var
+ // Snapshot the DOM in case .domManip sweeps something relevant into its fragment
+ args = jQuery.map( this, function( elem ) {
+ return [ elem.nextSibling, elem.parentNode ];
+ }),
+ i = 0;
+
+ // Make the changes, replacing each context element with the new content
+ this.domManip( arguments, function( elem ) {
+ var next = args[ i++ ],
+ parent = args[ i++ ];
+
+ if ( parent ) {
+ // Don't use the snapshot next if it has moved (#13810)
+ if ( next && next.parentNode !== parent ) {
+ next = this.nextSibling;
+ }
+ jQuery( this ).remove();
+ parent.insertBefore( elem, next );
+ }
+ // Allow new content to include elements from the context set
+ }, true );
+
+ // Force removal if there was no new content (e.g., from empty arguments)
+ return i ? this : this.remove();
+ },
+
+ detach: function( selector ) {
+ return this.remove( selector, true );
+ },
+
+ domManip: function( args, callback, allowIntersection ) {
+
+ // Flatten any nested arrays
+ args = core_concat.apply( [], args );
+
+ var fragment, first, scripts, hasScripts, node, doc,
+ i = 0,
+ l = this.length,
+ set = this,
+ iNoClone = l - 1,
+ value = args[ 0 ],
+ isFunction = jQuery.isFunction( value );
+
+ // We can't cloneNode fragments that contain checked, in WebKit
+ if ( isFunction || !( l <= 1 || typeof value !== "string" || jQuery.support.checkClone || !rchecked.test( value ) ) ) {
+ return this.each(function( index ) {
+ var self = set.eq( index );
+ if ( isFunction ) {
+ args[ 0 ] = value.call( this, index, self.html() );
+ }
+ self.domManip( args, callback, allowIntersection );
+ });
+ }
+
+ if ( l ) {
+ fragment = jQuery.buildFragment( args, this[ 0 ].ownerDocument, false, !allowIntersection && this );
+ first = fragment.firstChild;
+
+ if ( fragment.childNodes.length === 1 ) {
+ fragment = first;
+ }
+
+ if ( first ) {
+ scripts = jQuery.map( getAll( fragment, "script" ), disableScript );
+ hasScripts = scripts.length;
+
+ // Use the original fragment for the last item instead of the first because it can end up
+ // being emptied incorrectly in certain situations (#8070).
+ for ( ; i < l; i++ ) {
+ node = fragment;
+
+ if ( i !== iNoClone ) {
+ node = jQuery.clone( node, true, true );
+
+ // Keep references to cloned scripts for later restoration
+ if ( hasScripts ) {
+ // Support: QtWebKit
+ // jQuery.merge because core_push.apply(_, arraylike) throws
+ jQuery.merge( scripts, getAll( node, "script" ) );
+ }
+ }
+
+ callback.call( this[ i ], node, i );
+ }
+
+ if ( hasScripts ) {
+ doc = scripts[ scripts.length - 1 ].ownerDocument;
+
+ // Reenable scripts
+ jQuery.map( scripts, restoreScript );
+
+ // Evaluate executable scripts on first document insertion
+ for ( i = 0; i < hasScripts; i++ ) {
+ node = scripts[ i ];
+ if ( rscriptType.test( node.type || "" ) &&
+ !data_priv.access( node, "globalEval" ) && jQuery.contains( doc, node ) ) {
+
+ if ( node.src ) {
+ // Hope ajax is available...
+ jQuery._evalUrl( node.src );
+ } else {
+ jQuery.globalEval( node.textContent.replace( rcleanScript, "" ) );
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return this;
+ }
+});
+
+jQuery.each({
+ appendTo: "append",
+ prependTo: "prepend",
+ insertBefore: "before",
+ insertAfter: "after",
+ replaceAll: "replaceWith"
+}, function( name, original ) {
+ jQuery.fn[ name ] = function( selector ) {
+ var elems,
+ ret = [],
+ insert = jQuery( selector ),
+ last = insert.length - 1,
+ i = 0;
+
+ for ( ; i <= last; i++ ) {
+ elems = i === last ? this : this.clone( true );
+ jQuery( insert[ i ] )[ original ]( elems );
+
+ // Support: QtWebKit
+ // .get() because core_push.apply(_, arraylike) throws
+ core_push.apply( ret, elems.get() );
+ }
+
+ return this.pushStack( ret );
+ };
+});
+
+jQuery.extend({
+ clone: function( elem, dataAndEvents, deepDataAndEvents ) {
+ var i, l, srcElements, destElements,
+ clone = elem.cloneNode( true ),
+ inPage = jQuery.contains( elem.ownerDocument, elem );
+
+ // Support: IE >= 9
+ // Fix Cloning issues
+ if ( !jQuery.support.noCloneChecked && ( elem.nodeType === 1 || elem.nodeType === 11 ) && !jQuery.isXMLDoc( elem ) ) {
+
+ // We eschew Sizzle here for performance reasons: http://jsperf.com/getall-vs-sizzle/2
+ destElements = getAll( clone );
+ srcElements = getAll( elem );
+
+ for ( i = 0, l = srcElements.length; i < l; i++ ) {
+ fixInput( srcElements[ i ], destElements[ i ] );
+ }
+ }
+
+ // Copy the events from the original to the clone
+ if ( dataAndEvents ) {
+ if ( deepDataAndEvents ) {
+ srcElements = srcElements || getAll( elem );
+ destElements = destElements || getAll( clone );
+
+ for ( i = 0, l = srcElements.length; i < l; i++ ) {
+ cloneCopyEvent( srcElements[ i ], destElements[ i ] );
+ }
+ } else {
+ cloneCopyEvent( elem, clone );
+ }
+ }
+
+ // Preserve script evaluation history
+ destElements = getAll( clone, "script" );
+ if ( destElements.length > 0 ) {
+ setGlobalEval( destElements, !inPage && getAll( elem, "script" ) );
+ }
+
+ // Return the cloned set
+ return clone;
+ },
+
+ buildFragment: function( elems, context, scripts, selection ) {
+ var elem, tmp, tag, wrap, contains, j,
+ i = 0,
+ l = elems.length,
+ fragment = context.createDocumentFragment(),
+ nodes = [];
+
+ for ( ; i < l; i++ ) {
+ elem = elems[ i ];
+
+ if ( elem || elem === 0 ) {
+
+ // Add nodes directly
+ if ( jQuery.type( elem ) === "object" ) {
+ // Support: QtWebKit
+ // jQuery.merge because core_push.apply(_, arraylike) throws
+ jQuery.merge( nodes, elem.nodeType ? [ elem ] : elem );
+
+ // Convert non-html into a text node
+ } else if ( !rhtml.test( elem ) ) {
+ nodes.push( context.createTextNode( elem ) );
+
+ // Convert html into DOM nodes
+ } else {
+ tmp = tmp || fragment.appendChild( context.createElement("div") );
+
+ // Deserialize a standard representation
+ tag = ( rtagName.exec( elem ) || ["", ""] )[ 1 ].toLowerCase();
+ wrap = wrapMap[ tag ] || wrapMap._default;
+ tmp.innerHTML = wrap[ 1 ] + elem.replace( rxhtmlTag, "<$1></$2>" ) + wrap[ 2 ];
+
+ // Descend through wrappers to the right content
+ j = wrap[ 0 ];
+ while ( j-- ) {
+ tmp = tmp.lastChild;
+ }
+
+ // Support: QtWebKit
+ // jQuery.merge because core_push.apply(_, arraylike) throws
+ jQuery.merge( nodes, tmp.childNodes );
+
+ // Remember the top-level container
+ tmp = fragment.firstChild;
+
+ // Fixes #12346
+ // Support: Webkit, IE
+ tmp.textContent = "";
+ }
+ }
+ }
+
+ // Remove wrapper from fragment
+ fragment.textContent = "";
+
+ i = 0;
+ while ( (elem = nodes[ i++ ]) ) {
+
+ // #4087 - If origin and destination elements are the same, and this is
+ // that element, do not do anything
+ if ( selection && jQuery.inArray( elem, selection ) !== -1 ) {
+ continue;
+ }
+
+ contains = jQuery.contains( elem.ownerDocument, elem );
+
+ // Append to fragment
+ tmp = getAll( fragment.appendChild( elem ), "script" );
+
+ // Preserve script evaluation history
+ if ( contains ) {
+ setGlobalEval( tmp );
+ }
+
+ // Capture executables
+ if ( scripts ) {
+ j = 0;
+ while ( (elem = tmp[ j++ ]) ) {
+ if ( rscriptType.test( elem.type || "" ) ) {
+ scripts.push( elem );
+ }
+ }
+ }
+ }
+
+ return fragment;
+ },
+
+ cleanData: function( elems ) {
+ var data, elem, events, type, key, j,
+ special = jQuery.event.special,
+ i = 0;
+
+ for ( ; (elem = elems[ i ]) !== undefined; i++ ) {
+ if ( Data.accepts( elem ) ) {
+ key = elem[ data_priv.expando ];
+
+ if ( key && (data = data_priv.cache[ key ]) ) {
+ events = Object.keys( data.events || {} );
+ if ( events.length ) {
+ for ( j = 0; (type = events[j]) !== undefined; j++ ) {
+ if ( special[ type ] ) {
+ jQuery.event.remove( elem, type );
+
+ // This is a shortcut to avoid jQuery.event.remove's overhead
+ } else {
+ jQuery.removeEvent( elem, type, data.handle );
+ }
+ }
+ }
+ if ( data_priv.cache[ key ] ) {
+ // Discard any remaining `private` data
+ delete data_priv.cache[ key ];
+ }
+ }
+ }
+ // Discard any remaining `user` data
+ delete data_user.cache[ elem[ data_user.expando ] ];
+ }
+ },
+
+ _evalUrl: function( url ) {
+ return jQuery.ajax({
+ url: url,
+ type: "GET",
+ dataType: "script",
+ async: false,
+ global: false,
+ "throws": true
+ });
+ }
+});
+
+// Support: 1.x compatibility
+// Manipulating tables requires a tbody
+function manipulationTarget( elem, content ) {
+ return jQuery.nodeName( elem, "table" ) &&
+ jQuery.nodeName( content.nodeType === 1 ? content : content.firstChild, "tr" ) ?
+
+ elem.getElementsByTagName("tbody")[0] ||
+ elem.appendChild( elem.ownerDocument.createElement("tbody") ) :
+ elem;
+}
+
+// Replace/restore the type attribute of script elements for safe DOM manipulation
+function disableScript( elem ) {
+ elem.type = (elem.getAttribute("type") !== null) + "/" + elem.type;
+ return elem;
+}
+function restoreScript( elem ) {
+ var match = rscriptTypeMasked.exec( elem.type );
+
+ if ( match ) {
+ elem.type = match[ 1 ];
+ } else {
+ elem.removeAttribute("type");
+ }
+
+ return elem;
+}
+
+// Mark scripts as having already been evaluated
+function setGlobalEval( elems, refElements ) {
+ var l = elems.length,
+ i = 0;
+
+ for ( ; i < l; i++ ) {
+ data_priv.set(
+ elems[ i ], "globalEval", !refElements || data_priv.get( refElements[ i ], "globalEval" )
+ );
+ }
+}
+
+function cloneCopyEvent( src, dest ) {
+ var i, l, type, pdataOld, pdataCur, udataOld, udataCur, events;
+
+ if ( dest.nodeType !== 1 ) {
+ return;
+ }
+
+ // 1. Copy private data: events, handlers, etc.
+ if ( data_priv.hasData( src ) ) {
+ pdataOld = data_priv.access( src );
+ pdataCur = data_priv.set( dest, pdataOld );
+ events = pdataOld.events;
+
+ if ( events ) {
+ delete pdataCur.handle;
+ pdataCur.events = {};
+
+ for ( type in events ) {
+ for ( i = 0, l = events[ type ].length; i < l; i++ ) {
+ jQuery.event.add( dest, type, events[ type ][ i ] );
+ }
+ }
+ }
+ }
+
+ // 2. Copy user data
+ if ( data_user.hasData( src ) ) {
+ udataOld = data_user.access( src );
+ udataCur = jQuery.extend( {}, udataOld );
+
+ data_user.set( dest, udataCur );
+ }
+}
+
+
+function getAll( context, tag ) {
+ var ret = context.getElementsByTagName ? context.getElementsByTagName( tag || "*" ) :
+ context.querySelectorAll ? context.querySelectorAll( tag || "*" ) :
+ [];
+
+ return tag === undefined || tag && jQuery.nodeName( context, tag ) ?
+ jQuery.merge( [ context ], ret ) :
+ ret;
+}
+
+// Support: IE >= 9
+function fixInput( src, dest ) {
+ var nodeName = dest.nodeName.toLowerCase();
+
+ // Fails to persist the checked state of a cloned checkbox or radio button.
+ if ( nodeName === "input" && manipulation_rcheckableType.test( src.type ) ) {
+ dest.checked = src.checked;
+
+ // Fails to return the selected option to the default selected state when cloning options
+ } else if ( nodeName === "input" || nodeName === "textarea" ) {
+ dest.defaultValue = src.defaultValue;
+ }
+}
diff --git a/js/jquery/src/jquery/offset.js b/js/jquery/src/jquery/offset.js
new file mode 100644
index 0000000000..b616a49a00
--- /dev/null
+++ b/js/jquery/src/jquery/offset.js
@@ -0,0 +1,167 @@
+jQuery.fn.offset = function( options ) {
+ if ( arguments.length ) {
+ return options === undefined ?
+ this :
+ this.each(function( i ) {
+ jQuery.offset.setOffset( this, options, i );
+ });
+ }
+
+ var docElem, win,
+ elem = this[ 0 ],
+ box = { top: 0, left: 0 },
+ doc = elem && elem.ownerDocument;
+
+ if ( !doc ) {
+ return;
+ }
+
+ docElem = doc.documentElement;
+
+ // Make sure it's not a disconnected DOM node
+ if ( !jQuery.contains( docElem, elem ) ) {
+ return box;
+ }
+
+ // If we don't have gBCR, just use 0,0 rather than error
+ // BlackBerry 5, iOS 3 (original iPhone)
+ if ( typeof elem.getBoundingClientRect !== core_strundefined ) {
+ box = elem.getBoundingClientRect();
+ }
+ win = getWindow( doc );
+ return {
+ top: box.top + win.pageYOffset - docElem.clientTop,
+ left: box.left + win.pageXOffset - docElem.clientLeft
+ };
+};
+
+jQuery.offset = {
+
+ setOffset: function( elem, options, i ) {
+ var curPosition, curLeft, curCSSTop, curTop, curOffset, curCSSLeft, calculatePosition,
+ position = jQuery.css( elem, "position" ),
+ curElem = jQuery( elem ),
+ props = {};
+
+ // Set position first, in-case top/left are set even on static elem
+ if ( position === "static" ) {
+ elem.style.position = "relative";
+ }
+
+ curOffset = curElem.offset();
+ curCSSTop = jQuery.css( elem, "top" );
+ curCSSLeft = jQuery.css( elem, "left" );
+ calculatePosition = ( position === "absolute" || position === "fixed" ) && ( curCSSTop + curCSSLeft ).indexOf("auto") > -1;
+
+ // Need to be able to calculate position if either top or left is auto and position is either absolute or fixed
+ if ( calculatePosition ) {
+ curPosition = curElem.position();
+ curTop = curPosition.top;
+ curLeft = curPosition.left;
+
+ } else {
+ curTop = parseFloat( curCSSTop ) || 0;
+ curLeft = parseFloat( curCSSLeft ) || 0;
+ }
+
+ if ( jQuery.isFunction( options ) ) {
+ options = options.call( elem, i, curOffset );
+ }
+
+ if ( options.top != null ) {
+ props.top = ( options.top - curOffset.top ) + curTop;
+ }
+ if ( options.left != null ) {
+ props.left = ( options.left - curOffset.left ) + curLeft;
+ }
+
+ if ( "using" in options ) {
+ options.using.call( elem, props );
+
+ } else {
+ curElem.css( props );
+ }
+ }
+};
+
+
+jQuery.fn.extend({
+
+ position: function() {
+ if ( !this[ 0 ] ) {
+ return;
+ }
+
+ var offsetParent, offset,
+ elem = this[ 0 ],
+ parentOffset = { top: 0, left: 0 };
+
+ // Fixed elements are offset from window (parentOffset = {top:0, left: 0}, because it is it's only offset parent
+ if ( jQuery.css( elem, "position" ) === "fixed" ) {
+ // We assume that getBoundingClientRect is available when computed position is fixed
+ offset = elem.getBoundingClientRect();
+
+ } else {
+ // Get *real* offsetParent
+ offsetParent = this.offsetParent();
+
+ // Get correct offsets
+ offset = this.offset();
+ if ( !jQuery.nodeName( offsetParent[ 0 ], "html" ) ) {
+ parentOffset = offsetParent.offset();
+ }
+
+ // Add offsetParent borders
+ parentOffset.top += jQuery.css( offsetParent[ 0 ], "borderTopWidth", true );
+ parentOffset.left += jQuery.css( offsetParent[ 0 ], "borderLeftWidth", true );
+ }
+
+ // Subtract parent offsets and element margins
+ return {
+ top: offset.top - parentOffset.top - jQuery.css( elem, "marginTop", true ),
+ left: offset.left - parentOffset.left - jQuery.css( elem, "marginLeft", true )
+ };
+ },
+
+ offsetParent: function() {
+ return this.map(function() {
+ var offsetParent = this.offsetParent || docElem;
+
+ while ( offsetParent && ( !jQuery.nodeName( offsetParent, "html" ) && jQuery.css( offsetParent, "position") === "static" ) ) {
+ offsetParent = offsetParent.offsetParent;
+ }
+
+ return offsetParent || docElem;
+ });
+ }
+});
+
+
+// Create scrollLeft and scrollTop methods
+jQuery.each( {scrollLeft: "pageXOffset", scrollTop: "pageYOffset"}, function( method, prop ) {
+ var top = "pageYOffset" === prop;
+
+ jQuery.fn[ method ] = function( val ) {
+ return jQuery.access( this, function( elem, method, val ) {
+ var win = getWindow( elem );
+
+ if ( val === undefined ) {
+ return win ? win[ prop ] : elem[ method ];
+ }
+
+ if ( win ) {
+ win.scrollTo(
+ !top ? val : window.pageXOffset,
+ top ? val : window.pageYOffset
+ );
+
+ } else {
+ elem[ method ] = val;
+ }
+ }, method, val, arguments.length, null );
+ };
+});
+
+function getWindow( elem ) {
+ return jQuery.isWindow( elem ) ? elem : elem.nodeType === 9 && elem.defaultView;
+}
diff --git a/js/jquery/src/jquery/outro.js b/js/jquery/src/jquery/outro.js
new file mode 100644
index 0000000000..20cec437c8
--- /dev/null
+++ b/js/jquery/src/jquery/outro.js
@@ -0,0 +1,6 @@
+// Expose jQuery and $ identifiers, even in
+// AMD (#7102#comment:10, https://github.com/jquery/jquery/pull/557)
+// and CommonJS for browser emulators (#13566)
+return (window.jQuery = window.$ = jQuery);
+
+}));
diff --git a/js/jquery/src/jquery/queue.js b/js/jquery/src/jquery/queue.js
new file mode 100644
index 0000000000..1297bc1b4e
--- /dev/null
+++ b/js/jquery/src/jquery/queue.js
@@ -0,0 +1,145 @@
+jQuery.extend({
+ queue: function( elem, type, data ) {
+ var queue;
+
+ if ( elem ) {
+ type = ( type || "fx" ) + "queue";
+ queue = data_priv.get( elem, type );
+
+ // Speed up dequeue by getting out quickly if this is just a lookup
+ if ( data ) {
+ if ( !queue || jQuery.isArray( data ) ) {
+ queue = data_priv.access( elem, type, jQuery.makeArray(data) );
+ } else {
+ queue.push( data );
+ }
+ }
+ return queue || [];
+ }
+ },
+
+ dequeue: function( elem, type ) {
+ type = type || "fx";
+
+ var queue = jQuery.queue( elem, type ),
+ startLength = queue.length,
+ fn = queue.shift(),
+ hooks = jQuery._queueHooks( elem, type ),
+ next = function() {
+ jQuery.dequeue( elem, type );
+ };
+
+ // If the fx queue is dequeued, always remove the progress sentinel
+ if ( fn === "inprogress" ) {
+ fn = queue.shift();
+ startLength--;
+ }
+
+ if ( fn ) {
+
+ // Add a progress sentinel to prevent the fx queue from being
+ // automatically dequeued
+ if ( type === "fx" ) {
+ queue.unshift( "inprogress" );
+ }
+
+ // clear up the last queue stop function
+ delete hooks.stop;
+ fn.call( elem, next, hooks );
+ }
+
+ if ( !startLength && hooks ) {
+ hooks.empty.fire();
+ }
+ },
+
+ // not intended for public consumption - generates a queueHooks object, or returns the current one
+ _queueHooks: function( elem, type ) {
+ var key = type + "queueHooks";
+ return data_priv.get( elem, key ) || data_priv.access( elem, key, {
+ empty: jQuery.Callbacks("once memory").add(function() {
+ data_priv.remove( elem, [ type + "queue", key ] );
+ })
+ });
+ }
+});
+
+jQuery.fn.extend({
+ queue: function( type, data ) {
+ var setter = 2;
+
+ if ( typeof type !== "string" ) {
+ data = type;
+ type = "fx";
+ setter--;
+ }
+
+ if ( arguments.length < setter ) {
+ return jQuery.queue( this[0], type );
+ }
+
+ return data === undefined ?
+ this :
+ this.each(function() {
+ var queue = jQuery.queue( this, type, data );
+
+ // ensure a hooks for this queue
+ jQuery._queueHooks( this, type );
+
+ if ( type === "fx" && queue[0] !== "inprogress" ) {
+ jQuery.dequeue( this, type );
+ }
+ });
+ },
+ dequeue: function( type ) {
+ return this.each(function() {
+ jQuery.dequeue( this, type );
+ });
+ },
+ // Based off of the plugin by Clint Helfers, with permission.
+ // http://blindsignals.com/index.php/2009/07/jquery-delay/
+ delay: function( time, type ) {
+ time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time;
+ type = type || "fx";
+
+ return this.queue( type, function( next, hooks ) {
+ var timeout = setTimeout( next, time );
+ hooks.stop = function() {
+ clearTimeout( timeout );
+ };
+ });
+ },
+ clearQueue: function( type ) {
+ return this.queue( type || "fx", [] );
+ },
+ // Get a promise resolved when queues of a certain type
+ // are emptied (fx is the type by default)
+ promise: function( type, obj ) {
+ var tmp,
+ count = 1,
+ defer = jQuery.Deferred(),
+ elements = this,
+ i = this.length,
+ resolve = function() {
+ if ( !( --count ) ) {
+ defer.resolveWith( elements, [ elements ] );
+ }
+ };
+
+ if ( typeof type !== "string" ) {
+ obj = type;
+ type = undefined;
+ }
+ type = type || "fx";
+
+ while( i-- ) {
+ tmp = data_priv.get( elements[ i ], type + "queueHooks" );
+ if ( tmp && tmp.empty ) {
+ count++;
+ tmp.empty.add( resolve );
+ }
+ }
+ resolve();
+ return defer.promise( obj );
+ }
+});
diff --git a/js/jquery/src/jquery/selector-native.js b/js/jquery/src/jquery/selector-native.js
new file mode 100644
index 0000000000..58a0cec41a
--- /dev/null
+++ b/js/jquery/src/jquery/selector-native.js
@@ -0,0 +1,164 @@
+/*
+ * Optional (non-Sizzle) selector module for custom builds.
+ *
+ * Note that this DOES NOT SUPPORT many documented jQuery
+ * features in exchange for its smaller size:
+ *
+ * Attribute not equal selector
+ * Positional selectors (:first; :eq(n); :odd; etc.)
+ * Type selectors (:input; :checkbox; :button; etc.)
+ * State-based selectors (:animated; :visible; :hidden; etc.)
+ * :has(selector)
+ * :not(complex selector)
+ * custom selectors via Sizzle extensions
+ * Leading combinators (e.g., $collection.find("> *"))
+ * Reliable functionality on XML fragments
+ * Requiring all parts of a selector to match elements under context
+ * (e.g., $div.find("div > *") now matches children of $div)
+ * Matching against non-elements
+ * Reliable sorting of disconnected nodes
+ * querySelectorAll bug fixes (e.g., unreliable :focus on WebKit)
+ *
+ * If any of these are unacceptable tradeoffs, either use Sizzle or
+ * customize this stub for the project's specific needs.
+ */
+
+var selector_hasDuplicate,
+ matches = docElem.webkitMatchesSelector ||
+ docElem.mozMatchesSelector ||
+ docElem.oMatchesSelector ||
+ docElem.msMatchesSelector,
+ selector_sortOrder = function( a, b ) {
+ // Flag for duplicate removal
+ if ( a === b ) {
+ selector_hasDuplicate = true;
+ return 0;
+ }
+
+ var compare = b.compareDocumentPosition && a.compareDocumentPosition && a.compareDocumentPosition( b );
+
+ if ( compare ) {
+ // Disconnected nodes
+ if ( compare & 1 ) {
+
+ // Choose the first element that is related to our document
+ if ( a === document || jQuery.contains(document, a) ) {
+ return -1;
+ }
+ if ( b === document || jQuery.contains(document, b) ) {
+ return 1;
+ }
+
+ // Maintain original order
+ return 0;
+ }
+
+ return compare & 4 ? -1 : 1;
+ }
+
+ // Not directly comparable, sort on existence of method
+ return a.compareDocumentPosition ? -1 : 1;
+ };
+
+jQuery.extend({
+ find: function( selector, context, results, seed ) {
+ var elem, nodeType,
+ i = 0;
+
+ results = results || [];
+ context = context || document;
+
+ // Same basic safeguard as Sizzle
+ if ( !selector || typeof selector !== "string" ) {
+ return results;
+ }
+
+ // Early return if context is not an element or document
+ if ( (nodeType = context.nodeType) !== 1 && nodeType !== 9 ) {
+ return [];
+ }
+
+ if ( seed ) {
+ while ( (elem = seed[i++]) ) {
+ if ( jQuery.find.matchesSelector(elem, selector) ) {
+ results.push( elem );
+ }
+ }
+ } else {
+ jQuery.merge( results, context.querySelectorAll(selector) );
+ }
+
+ return results;
+ },
+ unique: function( results ) {
+ var elem,
+ duplicates = [],
+ i = 0,
+ j = 0;
+
+ selector_hasDuplicate = false;
+ results.sort( selector_sortOrder );
+
+ if ( selector_hasDuplicate ) {
+ while ( (elem = results[i++]) ) {
+ if ( elem === results[ i ] ) {
+ j = duplicates.push( i );
+ }
+ }
+ while ( j-- ) {
+ results.splice( duplicates[ j ], 1 );
+ }
+ }
+
+ return results;
+ },
+ text: function( elem ) {
+ var node,
+ ret = "",
+ i = 0,
+ nodeType = elem.nodeType;
+
+ if ( !nodeType ) {
+ // If no nodeType, this is expected to be an array
+ while ( (node = elem[i++]) ) {
+ // Do not traverse comment nodes
+ ret += jQuery.text( node );
+ }
+ } else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) {
+ // Use textContent for elements
+ return elem.textContent;
+ } else if ( nodeType === 3 || nodeType === 4 ) {
+ return elem.nodeValue;
+ }
+ // Do not include comment or processing instruction nodes
+
+ return ret;
+ },
+ contains: function( a, b ) {
+ var adown = a.nodeType === 9 ? a.documentElement : a,
+ bup = b && b.parentNode;
+ return a === bup || !!( bup && bup.nodeType === 1 && adown.contains(bup) );
+ },
+ isXMLDoc: function( elem ) {
+ return (elem.ownerDocument || elem).documentElement.nodeName !== "HTML";
+ },
+ expr: {
+ attrHandle: {},
+ match: {
+ bool: /^(?:checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped)$/i,
+ needsContext: /^[\x20\t\r\n\f]*[>+~]/
+ }
+ }
+});
+
+jQuery.extend( jQuery.find, {
+ matches: function( expr, elements ) {
+ return jQuery.find( expr, null, null, elements );
+ },
+ matchesSelector: function( elem, expr ) {
+ return matches.call( elem, expr );
+ },
+ attr: function( elem, name ) {
+ return elem.getAttribute( name );
+ }
+});
diff --git a/js/jquery/src/jquery/serialize.js b/js/jquery/src/jquery/serialize.js
new file mode 100644
index 0000000000..65dee4005b
--- /dev/null
+++ b/js/jquery/src/jquery/serialize.js
@@ -0,0 +1,99 @@
+var r20 = /%20/g,
+ rbracket = /\[\]$/,
+ rCRLF = /\r?\n/g,
+ rsubmitterTypes = /^(?:submit|button|image|reset|file)$/i,
+ rsubmittable = /^(?:input|select|textarea|keygen)/i;
+
+jQuery.fn.extend({
+ serialize: function() {
+ return jQuery.param( this.serializeArray() );
+ },
+ serializeArray: function() {
+ return this.map(function(){
+ // Can add propHook for "elements" to filter or add form elements
+ var elements = jQuery.prop( this, "elements" );
+ return elements ? jQuery.makeArray( elements ) : this;
+ })
+ .filter(function(){
+ var type = this.type;
+ // Use .is(":disabled") so that fieldset[disabled] works
+ return this.name && !jQuery( this ).is( ":disabled" ) &&
+ rsubmittable.test( this.nodeName ) && !rsubmitterTypes.test( type ) &&
+ ( this.checked || !manipulation_rcheckableType.test( type ) );
+ })
+ .map(function( i, elem ){
+ var val = jQuery( this ).val();
+
+ return val == null ?
+ null :
+ jQuery.isArray( val ) ?
+ jQuery.map( val, function( val ){
+ return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) };
+ }) :
+ { name: elem.name, value: val.replace( rCRLF, "\r\n" ) };
+ }).get();
+ }
+});
+
+//Serialize an array of form elements or a set of
+//key/values into a query string
+jQuery.param = function( a, traditional ) {
+ var prefix,
+ s = [],
+ add = function( key, value ) {
+ // If value is a function, invoke it and return its value
+ value = jQuery.isFunction( value ) ? value() : ( value == null ? "" : value );
+ s[ s.length ] = encodeURIComponent( key ) + "=" + encodeURIComponent( value );
+ };
+
+ // Set traditional to true for jQuery <= 1.3.2 behavior.
+ if ( traditional === undefined ) {
+ traditional = jQuery.ajaxSettings && jQuery.ajaxSettings.traditional;
+ }
+
+ // If an array was passed in, assume that it is an array of form elements.
+ if ( jQuery.isArray( a ) || ( a.jquery && !jQuery.isPlainObject( a ) ) ) {
+ // Serialize the form elements
+ jQuery.each( a, function() {
+ add( this.name, this.value );
+ });
+
+ } else {
+ // If traditional, encode the "old" way (the way 1.3.2 or older
+ // did it), otherwise encode params recursively.
+ for ( prefix in a ) {
+ buildParams( prefix, a[ prefix ], traditional, add );
+ }
+ }
+
+ // Return the resulting serialization
+ return s.join( "&" ).replace( r20, "+" );
+};
+
+function buildParams( prefix, obj, traditional, add ) {
+ var name;
+
+ if ( jQuery.isArray( obj ) ) {
+ // Serialize array item.
+ jQuery.each( obj, function( i, v ) {
+ if ( traditional || rbracket.test( prefix ) ) {
+ // Treat each array item as a scalar.
+ add( prefix, v );
+
+ } else {
+ // Item is non-scalar (array or object), encode its numeric index.
+ buildParams( prefix + "[" + ( typeof v === "object" ? i : "" ) + "]", v, traditional, add );
+ }
+ });
+
+ } else if ( !traditional && jQuery.type( obj ) === "object" ) {
+ // Serialize object item.
+ for ( name in obj ) {
+ buildParams( prefix + "[" + name + "]", obj[ name ], traditional, add );
+ }
+
+ } else {
+ // Serialize scalar item.
+ add( prefix, obj );
+ }
+}
diff --git a/js/jquery/src/jquery/sizzle-jquery.js b/js/jquery/src/jquery/sizzle-jquery.js
new file mode 100644
index 0000000000..fa31965432
--- /dev/null
+++ b/js/jquery/src/jquery/sizzle-jquery.js
@@ -0,0 +1,7 @@
+jQuery.find = Sizzle;
+jQuery.expr = Sizzle.selectors;
+jQuery.expr[":"] = jQuery.expr.pseudos;
+jQuery.unique = Sizzle.uniqueSort;
+jQuery.text = Sizzle.getText;
+jQuery.isXMLDoc = Sizzle.isXML;
+jQuery.contains = Sizzle.contains;
diff --git a/js/jquery/src/jquery/support.js b/js/jquery/src/jquery/support.js
new file mode 100644
index 0000000000..e46588dd1b
--- /dev/null
+++ b/js/jquery/src/jquery/support.js
@@ -0,0 +1,113 @@
+jQuery.support = (function( support ) {
+ var input = document.createElement("input"),
+ fragment = document.createDocumentFragment(),
+ div = document.createElement("div"),
+ select = document.createElement("select"),
+ opt = select.appendChild( document.createElement("option") );
+
+ // Finish early in limited environments
+ if ( !input.type ) {
+ return support;
+ }
+
+ input.type = "checkbox";
+
+ // Support: Safari 5.1, iOS 5.1, Android 4.x, Android 2.3
+ // Check the default checkbox/radio value ("" on old WebKit; "on" elsewhere)
+ support.checkOn = input.value !== "";
+
+ // Must access the parent to make an option select properly
+ // Support: IE9, IE10
+ support.optSelected = opt.selected;
+
+ // Will be defined later
+ support.reliableMarginRight = true;
+ support.boxSizingReliable = true;
+ support.pixelPosition = false;
+
+ // Make sure checked status is properly cloned
+ // Support: IE9, IE10
+ input.checked = true;
+ support.noCloneChecked = input.cloneNode( true ).checked;
+
+ // Make sure that the options inside disabled selects aren't marked as disabled
+ // (WebKit marks them as disabled)
+ select.disabled = true;
+ support.optDisabled = !opt.disabled;
+
+ // Check if an input maintains its value after becoming a radio
+ // Support: IE9, IE10
+ input = document.createElement("input");
+ input.value = "t";
+ input.type = "radio";
+ support.radioValue = input.value === "t";
+
+ // #11217 - WebKit loses check when the name is after the checked attribute
+ input.setAttribute( "checked", "t" );
+ input.setAttribute( "name", "t" );
+
+ fragment.appendChild( input );
+
+ // Support: Safari 5.1, Android 4.x, Android 2.3
+ // old WebKit doesn't clone checked state correctly in fragments
+ support.checkClone = fragment.cloneNode( true ).cloneNode( true ).lastChild.checked;
+
+ // Support: Firefox, Chrome, Safari
+ // Beware of CSP restrictions (https://developer.mozilla.org/en/Security/CSP)
+ support.focusinBubbles = "onfocusin" in window;
+
+ div.style.backgroundClip = "content-box";
+ div.cloneNode( true ).style.backgroundClip = "";
+ support.clearCloneStyle = div.style.backgroundClip === "content-box";
+
+ // Run tests that need a body at doc ready
+ jQuery(function() {
+ var container, marginDiv,
+ // Support: Firefox, Android 2.3 (Prefixed box-sizing versions).
+ divReset = "padding:0;margin:0;border:0;display:block;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box",
+ body = document.getElementsByTagName("body")[ 0 ];
+
+ if ( !body ) {
+ // Return for frameset docs that don't have a body
+ return;
+ }
+
+ container = document.createElement("div");
+ container.style.cssText = "border:0;width:0;height:0;position:absolute;top:0;left:-9999px;margin-top:1px";
+
+ // Check box-sizing and margin behavior.
+ body.appendChild( container ).appendChild( div );
+ div.innerHTML = "";
+ // Support: Firefox, Android 2.3 (Prefixed box-sizing versions).
+ div.style.cssText = "-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;padding:1px;border:1px;display:block;width:4px;margin-top:1%;position:absolute;top:1%";
+
+ // Workaround failing boxSizing test due to offsetWidth returning wrong value
+ // with some non-1 values of body zoom, ticket #13543
+ jQuery.swap( body, body.style.zoom != null ? { zoom: 1 } : {}, function() {
+ support.boxSizing = div.offsetWidth === 4;
+ });
+
+ // Use window.getComputedStyle because jsdom on node.js will break without it.
+ if ( window.getComputedStyle ) {
+ support.pixelPosition = ( window.getComputedStyle( div, null ) || {} ).top !== "1%";
+ support.boxSizingReliable = ( window.getComputedStyle( div, null ) || { width: "4px" } ).width === "4px";
+
+ // Support: Android 2.3
+ // Check if div with explicit width and no margin-right incorrectly
+ // gets computed margin-right based on width of container. (#3333)
+ // WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right
+ marginDiv = div.appendChild( document.createElement("div") );
+ marginDiv.style.cssText = div.style.cssText = divReset;
+ marginDiv.style.marginRight = marginDiv.style.width = "0";
+ div.style.width = "1px";
+
+ support.reliableMarginRight =
+ !parseFloat( ( window.getComputedStyle( marginDiv, null ) || {} ).marginRight );
+ }
+
+ body.removeChild( container );
+ });
+
+ return support;
+})( {} );
+
diff --git a/js/jquery/src/jquery/traversing.js b/js/jquery/src/jquery/traversing.js
new file mode 100644
index 0000000000..8e305c289a
--- /dev/null
+++ b/js/jquery/src/jquery/traversing.js
@@ -0,0 +1,283 @@
+var isSimple = /^.[^:#\[\.,]*$/,
+ rparentsprev = /^(?:parents|prev(?:Until|All))/,
+ rneedsContext = jQuery.expr.match.needsContext,
+ // methods guaranteed to produce a unique set when starting from a unique set
+ guaranteedUnique = {
+ children: true,
+ contents: true,
+ next: true,
+ prev: true
+ };
+
+jQuery.fn.extend({
+ find: function( selector ) {
+ var i,
+ ret = [],
+ self = this,
+ len = self.length;
+
+ if ( typeof selector !== "string" ) {
+ return this.pushStack( jQuery( selector ).filter(function() {
+ for ( i = 0; i < len; i++ ) {
+ if ( jQuery.contains( self[ i ], this ) ) {
+ return true;
+ }
+ }
+ }) );
+ }
+
+ for ( i = 0; i < len; i++ ) {
+ jQuery.find( selector, self[ i ], ret );
+ }
+
+ // Needed because $( selector, context ) becomes $( context ).find( selector )
+ ret = this.pushStack( len > 1 ? jQuery.unique( ret ) : ret );
+ ret.selector = this.selector ? this.selector + " " + selector : selector;
+ return ret;
+ },
+
+ has: function( target ) {
+ var targets = jQuery( target, this ),
+ l = targets.length;
+
+ return this.filter(function() {
+ var i = 0;
+ for ( ; i < l; i++ ) {
+ if ( jQuery.contains( this, targets[i] ) ) {
+ return true;
+ }
+ }
+ });
+ },
+
+ not: function( selector ) {
+ return this.pushStack( winnow(this, selector || [], true) );
+ },
+
+ filter: function( selector ) {
+ return this.pushStack( winnow(this, selector || [], false) );
+ },
+
+ is: function( selector ) {
+ return !!winnow(
+ this,
+
+ // If this is a positional/relative selector, check membership in the returned set
+ // so $("p:first").is("p:last") won't return true for a doc with two "p".
+ typeof selector === "string" && rneedsContext.test( selector ) ?
+ jQuery( selector ) :
+ selector || [],
+ false
+ ).length;
+ },
+
+ closest: function( selectors, context ) {
+ var cur,
+ i = 0,
+ l = this.length,
+ matched = [],
+ pos = ( rneedsContext.test( selectors ) || typeof selectors !== "string" ) ?
+ jQuery( selectors, context || this.context ) :
+ 0;
+
+ for ( ; i < l; i++ ) {
+ for ( cur = this[i]; cur && cur !== context; cur = cur.parentNode ) {
+ // Always skip document fragments
+ if ( cur.nodeType < 11 && (pos ?
+ pos.index(cur) > -1 :
+
+ // Don't pass non-elements to Sizzle
+ cur.nodeType === 1 &&
+ jQuery.find.matchesSelector(cur, selectors)) ) {
+
+ cur = matched.push( cur );
+ break;
+ }
+ }
+ }
+
+ return this.pushStack( matched.length > 1 ? jQuery.unique( matched ) : matched );
+ },
+
+ // Determine the position of an element within
+ // the matched set of elements
+ index: function( elem ) {
+
+ // No argument, return index in parent
+ if ( !elem ) {
+ return ( this[ 0 ] && this[ 0 ].parentNode ) ? this.first().prevAll().length : -1;
+ }
+
+ // index in selector
+ if ( typeof elem === "string" ) {
+ return core_indexOf.call( jQuery( elem ), this[ 0 ] );
+ }
+
+ // Locate the position of the desired element
+ return core_indexOf.call( this,
+
+ // If it receives a jQuery object, the first element is used
+ elem.jquery ? elem[ 0 ] : elem
+ );
+ },
+
+ add: function( selector, context ) {
+ var set = typeof selector === "string" ?
+ jQuery( selector, context ) :
+ jQuery.makeArray( selector && selector.nodeType ? [ selector ] : selector ),
+ all = jQuery.merge( this.get(), set );
+
+ return this.pushStack( jQuery.unique(all) );
+ },
+
+ addBack: function( selector ) {
+ return this.add( selector == null ?
+ this.prevObject : this.prevObject.filter(selector)
+ );
+ }
+});
+
+function sibling( cur, dir ) {
+ while ( (cur = cur[dir]) && cur.nodeType !== 1 ) {}
+
+ return cur;
+}
+
+jQuery.each({
+ parent: function( elem ) {
+ var parent = elem.parentNode;
+ return parent && parent.nodeType !== 11 ? parent : null;
+ },
+ parents: function( elem ) {
+ return jQuery.dir( elem, "parentNode" );
+ },
+ parentsUntil: function( elem, i, until ) {
+ return jQuery.dir( elem, "parentNode", until );
+ },
+ next: function( elem ) {
+ return sibling( elem, "nextSibling" );
+ },
+ prev: function( elem ) {
+ return sibling( elem, "previousSibling" );
+ },
+ nextAll: function( elem ) {
+ return jQuery.dir( elem, "nextSibling" );
+ },
+ prevAll: function( elem ) {
+ return jQuery.dir( elem, "previousSibling" );
+ },
+ nextUntil: function( elem, i, until ) {
+ return jQuery.dir( elem, "nextSibling", until );
+ },
+ prevUntil: function( elem, i, until ) {
+ return jQuery.dir( elem, "previousSibling", until );
+ },
+ siblings: function( elem ) {
+ return jQuery.sibling( ( elem.parentNode || {} ).firstChild, elem );
+ },
+ children: function( elem ) {
+ return jQuery.sibling( elem.firstChild );
+ },
+ contents: function( elem ) {
+ return elem.contentDocument || jQuery.merge( [], elem.childNodes );
+ }
+}, function( name, fn ) {
+ jQuery.fn[ name ] = function( until, selector ) {
+ var matched = jQuery.map( this, fn, until );
+
+ if ( name.slice( -5 ) !== "Until" ) {
+ selector = until;
+ }
+
+ if ( selector && typeof selector === "string" ) {
+ matched = jQuery.filter( selector, matched );
+ }
+
+ if ( this.length > 1 ) {
+ // Remove duplicates
+ if ( !guaranteedUnique[ name ] ) {
+ jQuery.unique( matched );
+ }
+
+ // Reverse order for parents* and prev-derivatives
+ if ( rparentsprev.test( name ) ) {
+ matched.reverse();
+ }
+ }
+
+ return this.pushStack( matched );
+ };
+});
+
+jQuery.extend({
+ filter: function( expr, elems, not ) {
+ var elem = elems[ 0 ];
+
+ if ( not ) {
+ expr = ":not(" + expr + ")";
+ }
+
+ return elems.length === 1 && elem.nodeType === 1 ?
+ jQuery.find.matchesSelector( elem, expr ) ? [ elem ] : [] :
+ jQuery.find.matches( expr, jQuery.grep( elems, function( elem ) {
+ return elem.nodeType === 1;
+ }));
+ },
+
+ dir: function( elem, dir, until ) {
+ var matched = [],
+ truncate = until !== undefined;
+
+ while ( (elem = elem[ dir ]) && elem.nodeType !== 9 ) {
+ if ( elem.nodeType === 1 ) {
+ if ( truncate && jQuery( elem ).is( until ) ) {
+ break;
+ }
+ matched.push( elem );
+ }
+ }
+ return matched;
+ },
+
+ sibling: function( n, elem ) {
+ var matched = [];
+
+ for ( ; n; n = n.nextSibling ) {
+ if ( n.nodeType === 1 && n !== elem ) {
+ matched.push( n );
+ }
+ }
+
+ return matched;
+ }
+});
+
+// Implement the identical functionality for filter and not
+function winnow( elements, qualifier, not ) {
+ if ( jQuery.isFunction( qualifier ) ) {
+ return jQuery.grep( elements, function( elem, i ) {
+ /* jshint -W018 */
+ return !!qualifier.call( elem, i, elem ) !== not;
+ });
+
+ }
+
+ if ( qualifier.nodeType ) {
+ return jQuery.grep( elements, function( elem ) {
+ return ( elem === qualifier ) !== not;
+ });
+
+ }
+
+ if ( typeof qualifier === "string" ) {
+ if ( isSimple.test( qualifier ) ) {
+ return jQuery.filter( qualifier, elements, not );
+ }
+
+ qualifier = jQuery.filter( qualifier, elements );
+ }
+
+ return jQuery.grep( elements, function( elem ) {
+ return ( core_indexOf.call( qualifier, elem ) >= 0 ) !== not;
+ });
+}
diff --git a/js/jquery/src/jquery/wrap.js b/js/jquery/src/jquery/wrap.js
new file mode 100644
index 0000000000..5968adbc69
--- /dev/null
+++ b/js/jquery/src/jquery/wrap.js
@@ -0,0 +1,69 @@
+jQuery.fn.extend({
+ wrapAll: function( html ) {
+ var wrap;
+
+ if ( jQuery.isFunction( html ) ) {
+ return this.each(function( i ) {
+ jQuery( this ).wrapAll( html.call(this, i) );
+ });
+ }
+
+ if ( this[ 0 ] ) {
+
+ // The elements to wrap the target around
+ wrap = jQuery( html, this[ 0 ].ownerDocument ).eq( 0 ).clone( true );
+
+ if ( this[ 0 ].parentNode ) {
+ wrap.insertBefore( this[ 0 ] );
+ }
+
+ wrap.map(function() {
+ var elem = this;
+
+ while ( elem.firstElementChild ) {
+ elem = elem.firstElementChild;
+ }
+
+ return elem;
+ }).append( this );
+ }
+
+ return this;
+ },
+
+ wrapInner: function( html ) {
+ if ( jQuery.isFunction( html ) ) {
+ return this.each(function( i ) {
+ jQuery( this ).wrapInner( html.call(this, i) );
+ });
+ }
+
+ return this.each(function() {
+ var self = jQuery( this ),
+ contents = self.contents();
+
+ if ( contents.length ) {
+ contents.wrapAll( html );
+
+ } else {
+ self.append( html );
+ }
+ });
+ },
+
+ wrap: function( html ) {
+ var isFunction = jQuery.isFunction( html );
+
+ return this.each(function( i ) {
+ jQuery( this ).wrapAll( isFunction ? html.call(this, i) : html );
+ });
+ },
+
+ unwrap: function() {
+ return this.parent().each(function() {
+ if ( !jQuery.nodeName( this, "body" ) ) {
+ jQuery( this ).replaceWith( this.childNodes );
+ }
+ }).end();
+ }
+});
diff --git a/js/keyhandler.js b/js/keyhandler.js
new file mode 100644
index 0000000000..a378249c7a
--- /dev/null
+++ b/js/keyhandler.js
@@ -0,0 +1,118 @@
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+
+// gloabl vars to hold Arrow Down event timeStamps
+var prevTimeStamp = 0;
+var curTimeStamp = 0;
+
+/**
+ * Allows moving around inputs/select by Ctrl+arrows
+ *
+ * @param object event data
+ */
+function onKeyDownArrowsHandler(e)
+{
+ e = e || window.event;
+
+ curTimeStamp = e.timeStamp;
+ if( prevTimeStamp == 0 ) {
+ prevTimeStamp = curTimeStamp;
+ }
+ else if( Math.abs(curTimeStamp-prevTimeStamp) < 150 ) {
+ // event in a very quick succession
+ return;
+ }
+ prevTimeStamp = curTimeStamp;
+
+ var o = (e.srcElement || e.target);
+ if (!o) {
+ return;
+ }
+ if (o.tagName != "TEXTAREA" && o.tagName != "INPUT" && o.tagName != "SELECT") {
+ return;
+ }
+ if (navigator.userAgent.toLowerCase().indexOf('applewebkit/') != -1) {
+ if (e.ctrlKey || e.shiftKey || !e.altKey) {
+ return;
+ }
+ } else {
+ if (!e.ctrlKey || e.shiftKey || e.altKey) {
+ return;
+ }
+ }
+ if (!o.id) {
+ return;
+ }
+
+ var pos = o.id.split("_");
+ if (pos[0] != "field" || typeof pos[2] == "undefined") {
+ return;
+ }
+
+ var x = pos[2], y = pos[1];
+
+ var nO = null;
+
+ switch (e.keyCode) {
+ case 38:
+ // up
+ y--;
+ break;
+ case 40:
+ // down
+ y++;
+ break;
+ case 37:
+ // left
+ x--;
+ break;
+ case 39:
+ // right
+ x++;
+ break;
+ default:
+ return;
+ }
+
+ var is_firefox = navigator.userAgent.toLowerCase().indexOf("firefox/") > -1;
+
+ // restore selected index, bug #3799
+ if (is_firefox && e.type == "keyup") {
+ o.selectedIndex = window["selectedIndex_" + o.id];
+ }
+
+ var id = "field_" + y + "_" + x;
+ nO = document.getElementById(id);
+ if (! nO) {
+ id = "field_" + y + "_" + x + "_0";
+ nO = document.getElementById(id);
+ }
+
+ // skip non existent fields
+ if (! nO) {
+ return;
+ }
+ if (e.type == "keydown") {
+ nO.focus();
+ if (is_firefox) {
+ window["selectedIndex_" + nO.id] = nO.selectedIndex;
+ }
+ }
+ if (nO.tagName != 'SELECT') {
+ nO.select();
+ }
+ e.returnValue = false;
+}
+
+AJAX.registerTeardown('keyhandler.js', function () {
+ $('#table_columns').die('keydown keyup');
+ $('table.insertRowTable').die('keydown keyup');
+});
+
+AJAX.registerOnload('keyhandler.js', function () {
+ $('#table_columns').live('keydown keyup', function (event) {
+ onKeyDownArrowsHandler(event.originalEvent);
+ });
+ $('table.insertRowTable').live('keydown keyup', function (event) {
+ onKeyDownArrowsHandler(event.originalEvent);
+ });
+});
diff --git a/js/line_counts.php b/js/line_counts.php
new file mode 100644
index 0000000000..a592cb5693
--- /dev/null
+++ b/js/line_counts.php
@@ -0,0 +1,154 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * An autogenerated file that stores the line counts of javascript files
+ *
+ * @package PhpMyAdmin
+ */
+
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+define('LINE_COUNTS', true);
+$LINE_COUNT = array();
+
+$LINE_COUNT["error_report.js"] = 310;
+$LINE_COUNT["functions.js"] = 4109;
+$LINE_COUNT["jquery/jquery-ui-timepicker-addon.js"] = 2127;
+$LINE_COUNT["jquery/jquery.json-2.4.js"] = 199;
+$LINE_COUNT["jquery/jquery.cookie.js"] = 91;
+$LINE_COUNT["jquery/jquery.sortableTable.js"] = 271;
+$LINE_COUNT["jquery/jquery.sprintf.js"] = 68;
+$LINE_COUNT["jquery/jquery.mousewheel.js"] = 84;
+$LINE_COUNT["jquery/jquery.svg.js"] = 1394;
+$LINE_COUNT["jquery/jquery.event.drag-2.2.js"] = 401;
+$LINE_COUNT["jquery/jquery-ui-1.9.2.custom.min.js"] = 5;
+$LINE_COUNT["jquery/jquery.debounce-1.0.5.js"] = 70;
+$LINE_COUNT["jquery/src/jquery/intro.js"] = 54;
+$LINE_COUNT["jquery/src/jquery/serialize.js"] = 99;
+$LINE_COUNT["jquery/src/jquery/traversing.js"] = 283;
+$LINE_COUNT["jquery/src/jquery/offset.js"] = 167;
+$LINE_COUNT["jquery/src/jquery/support.js"] = 113;
+$LINE_COUNT["jquery/src/jquery/manipulation.js"] = 577;
+$LINE_COUNT["jquery/src/jquery/selector-native.js"] = 164;
+$LINE_COUNT["jquery/src/jquery/deprecated.js"] = 11;
+$LINE_COUNT["jquery/src/jquery/core.js"] = 846;
+$LINE_COUNT["jquery/src/jquery/sizzle-jquery.js"] = 7;
+$LINE_COUNT["jquery/src/jquery/ajax.js"] = 855;
+$LINE_COUNT["jquery/src/jquery/dimensions.js"] = 41;
+$LINE_COUNT["jquery/src/jquery/event.js"] = 829;
+$LINE_COUNT["jquery/src/jquery/ajax/jsonp.js"] = 80;
+$LINE_COUNT["jquery/src/jquery/ajax/script.js"] = 57;
+$LINE_COUNT["jquery/src/jquery/ajax/xhr.js"] = 111;
+$LINE_COUNT["jquery/src/jquery/css.js"] = 563;
+$LINE_COUNT["jquery/src/jquery/attributes.js"] = 502;
+$LINE_COUNT["jquery/src/jquery/wrap.js"] = 69;
+$LINE_COUNT["jquery/src/jquery/deferred.js"] = 141;
+$LINE_COUNT["jquery/src/jquery/queue.js"] = 145;
+$LINE_COUNT["jquery/src/jquery/data.js"] = 356;
+$LINE_COUNT["jquery/src/jquery/outro.js"] = 6;
+$LINE_COUNT["jquery/src/jquery/event-alias.js"] = 32;
+$LINE_COUNT["jquery/src/jquery/callbacks.js"] = 197;
+$LINE_COUNT["jquery/src/jquery/effects.js"] = 730;
+$LINE_COUNT["jquery/src/jquery-ui/jquery.ui.effect-shake.js"] = 74;
+$LINE_COUNT["jquery/src/jquery-ui/jquery.ui.progressbar.js"] = 105;
+$LINE_COUNT["jquery/src/jquery-ui/jquery.ui.slider.js"] = 644;
+$LINE_COUNT["jquery/src/jquery-ui/jquery.ui.resizable.js"] = 801;
+$LINE_COUNT["jquery/src/jquery-ui/jquery.ui.widget.js"] = 528;
+$LINE_COUNT["jquery/src/jquery-ui/jquery.ui.accordion.js"] = 731;
+$LINE_COUNT["jquery/src/jquery-ui/jquery.ui.mouse.js"] = 169;
+$LINE_COUNT["jquery/src/jquery-ui/jquery.ui.effect-blind.js"] = 82;
+$LINE_COUNT["jquery/src/jquery-ui/jquery.ui.sortable.js"] = 1096;
+$LINE_COUNT["jquery/src/jquery-ui/jquery.ui.effect-drop.js"] = 65;
+$LINE_COUNT["jquery/src/jquery-ui/jquery.ui.tabs.js"] = 1366;
+$LINE_COUNT["jquery/src/jquery-ui/jquery.ui.spinner.js"] = 478;
+$LINE_COUNT["jquery/src/jquery-ui/jquery.ui.core.js"] = 356;
+$LINE_COUNT["jquery/src/jquery-ui/jquery.ui.autocomplete.js"] = 602;
+$LINE_COUNT["jquery/src/jquery-ui/jquery.ui.effect-explode.js"] = 97;
+$LINE_COUNT["jquery/src/jquery-ui/jquery.ui.selectable.js"] = 261;
+$LINE_COUNT["jquery/src/jquery-ui/jquery.ui.tooltip.js"] = 398;
+$LINE_COUNT["jquery/src/jquery-ui/jquery.ui.effect-slide.js"] = 64;
+$LINE_COUNT["jquery/src/jquery-ui/jquery.ui.effect-scale.js"] = 318;
+$LINE_COUNT["jquery/src/jquery-ui/jquery.ui.draggable.js"] = 836;
+$LINE_COUNT["jquery/src/jquery-ui/jquery.ui.droppable.js"] = 294;
+$LINE_COUNT["jquery/src/jquery-ui/jquery.ui.effect-bounce.js"] = 113;
+$LINE_COUNT["jquery/src/jquery-ui/jquery.ui.effect-clip.js"] = 67;
+$LINE_COUNT["jquery/src/jquery-ui/jquery.ui.menu.js"] = 610;
+$LINE_COUNT["jquery/src/jquery-ui/jquery.ui.dialog.js"] = 858;
+$LINE_COUNT["jquery/src/jquery-ui/jquery.ui.effect.js"] = 1276;
+$LINE_COUNT["jquery/src/jquery-ui/jquery.ui.button.js"] = 418;
+$LINE_COUNT["jquery/src/jquery-ui/jquery.ui.effect-transfer.js"] = 47;
+$LINE_COUNT["jquery/src/jquery-ui/jquery.ui.effect-pulsate.js"] = 63;
+$LINE_COUNT["jquery/src/jquery-ui/jquery.ui.effect-fold.js"] = 76;
+$LINE_COUNT["jquery/src/jquery-ui/jquery.ui.position.js"] = 517;
+$LINE_COUNT["jquery/src/jquery-ui/jquery.ui.datepicker.js"] = 1846;
+$LINE_COUNT["jquery/src/jquery-ui/jquery.ui.effect-fade.js"] = 30;
+$LINE_COUNT["jquery/src/jquery-ui/jquery.ui.effect-highlight.js"] = 50;
+$LINE_COUNT["jquery/jquery.fullscreen.js"] = 60;
+$LINE_COUNT["jquery/jquery.ba-hashchange-1.3.js"] = 390;
+$LINE_COUNT["jquery/jquery-1.8.3.min.js"] = 1;
+$LINE_COUNT["jquery/jquery.menuResizer-1.0.js"] = 181;
+$LINE_COUNT["jquery/jquery.tablesorter.js"] = 1032;
+$LINE_COUNT["indexes.js"] = 209;
+$LINE_COUNT["gis_data_editor.js"] = 395;
+$LINE_COUNT["db_search.js"] = 236;
+$LINE_COUNT["server_plugins.js"] = 30;
+$LINE_COUNT["tbl_zoom_plot_jqplot.js"] = 622;
+$LINE_COUNT["tbl_change.js"] = 526;
+$LINE_COUNT["tracekit/tracekit.js"] = 1114;
+$LINE_COUNT["tbl_select.js"] = 227;
+$LINE_COUNT["replication.js"] = 72;
+$LINE_COUNT["rte.js"] = 930;
+$LINE_COUNT["tbl_relation.js"] = 135;
+$LINE_COUNT["server_status_queries.js"] = 40;
+$LINE_COUNT["tbl_structure.js"] = 504;
+$LINE_COUNT["jqplot/plugins/jqplot.categoryAxisRenderer.js"] = 672;
+$LINE_COUNT["jqplot/plugins/jqplot.pointLabels.js"] = 378;
+$LINE_COUNT["jqplot/plugins/jqplot.dateAxisRenderer.js"] = 737;
+$LINE_COUNT["jqplot/plugins/jqplot.barRenderer.js"] = 796;
+$LINE_COUNT["jqplot/plugins/jqplot.byteFormatter.js"] = 46;
+$LINE_COUNT["jqplot/plugins/jqplot.canvasAxisLabelRenderer.js"] = 202;
+$LINE_COUNT["jqplot/plugins/jqplot.highlighter.js"] = 464;
+$LINE_COUNT["jqplot/plugins/jqplot.pieRenderer.js"] = 903;
+$LINE_COUNT["jqplot/plugins/jqplot.cursor.js"] = 1108;
+$LINE_COUNT["jqplot/plugins/jqplot.canvasTextRenderer.js"] = 448;
+$LINE_COUNT["jqplot/jquery.jqplot.js"] = 11381;
+$LINE_COUNT["jqplot/excanvas.js"] = 1438;
+$LINE_COUNT["canvg/canvg.js"] = 2508;
+$LINE_COUNT["tbl_gis_visualization.js"] = 352;
+$LINE_COUNT["server_status_advisor.js"] = 93;
+$LINE_COUNT["config.js"] = 798;
+$LINE_COUNT["server_user_groups.js"] = 42;
+$LINE_COUNT["makegrid.js"] = 1914;
+$LINE_COUNT["export.js"] = 286;
+$LINE_COUNT["doclinks.js"] = 365;
+$LINE_COUNT["server_status_monitor.js"] = 2126;
+$LINE_COUNT["ajax.js"] = 867;
+$LINE_COUNT["querywindow.js"] = 25;
+$LINE_COUNT["tbl_find_replace.js"] = 47;
+$LINE_COUNT["codemirror/addon/runmode/runmode.js"] = 56;
+$LINE_COUNT["codemirror/lib/codemirror.js"] = 5799;
+$LINE_COUNT["codemirror/mode/sql/sql.js"] = 349;
+$LINE_COUNT["sql.js"] = 492;
+$LINE_COUNT["server_databases.js"] = 130;
+$LINE_COUNT["OpenStreetMap.js"] = 126;
+$LINE_COUNT["db_structure.js"] = 385;
+$LINE_COUNT["chart.js"] = 546;
+$LINE_COUNT["tbl_chart.js"] = 325;
+$LINE_COUNT["db_operations.js"] = 114;
+$LINE_COUNT["pmd/init.js"] = 31;
+$LINE_COUNT["pmd/iecanvas.js"] = 151;
+$LINE_COUNT["pmd/history.js"] = 787;
+$LINE_COUNT["pmd/move.js"] = 1279;
+$LINE_COUNT["pmd/ajax.js"] = 60;
+$LINE_COUNT["cross_framing_protection.js"] = 9;
+$LINE_COUNT["openlayers/OpenLayers.js"] = 2680;
+$LINE_COUNT["navigation.js"] = 1207;
+$LINE_COUNT["import.js"] = 117;
+$LINE_COUNT["server_status_variables.js"] = 110;
+$LINE_COUNT["server_status_sorter.js"] = 99;
+$LINE_COUNT["common.js"] = 300;
+$LINE_COUNT["keyhandler.js"] = 118;
+$LINE_COUNT["server_privileges.js"] = 687;
+$LINE_COUNT["server_variables.js"] = 157;
diff --git a/js/makegrid.js b/js/makegrid.js
new file mode 100644
index 0000000000..19d41443ba
--- /dev/null
+++ b/js/makegrid.js
@@ -0,0 +1,1914 @@
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Create advanced table (resize, reorder, and show/hide columns; and also grid editing).
+ * This function is designed mainly for table DOM generated from browsing a table in the database.
+ * For using this function in other table DOM, you may need to:
+ * - add "draggable" class in the table header <th>, in order to make it resizable, sortable or hidable
+ * - have at least one non-"draggable" header in the table DOM for placing column visibility drop-down arrow
+ * - pass the value "false" for the parameter "enableGridEdit"
+ * - adjust other parameter value, to select which features that will be enabled
+ *
+ * @param t the table DOM element
+ * @param enableResize Optional, if false, column resizing feature will be disabled
+ * @param enableReorder Optional, if false, column reordering feature will be disabled
+ * @param enableVisib Optional, if false, show/hide column feature will be disabled
+ * @param enableGridEdit Optional, if false, grid editing feature will be disabled
+ */
+function PMA_makegrid(t, enableResize, enableReorder, enableVisib, enableGridEdit) {
+ var g = {
+ /***********
+ * Constant
+ ***********/
+ minColWidth: 15,
+
+
+ /***********
+ * Variables, assigned with default value, changed later
+ ***********/
+ actionSpan: 5, // number of colspan in Actions header in a table
+ tableCreateTime: null, // table creation time, used for saving column order and visibility to server, only available in "Browse tab"
+
+ // Column reordering variables
+ colOrder: [], // array of column order
+
+ // Column visibility variables
+ colVisib: [], // array of column visibility
+ showAllColText: '', // string, text for "show all" button under column visibility list
+ visibleHeadersCount: 0, // number of visible data headers
+
+ // Table hint variables
+ reorderHint: '', // string, hint for column reordering
+ sortHint: '', // string, hint for column sorting
+ markHint: '', // string, hint for column marking
+ copyHint: '', // string, hint for copy column name
+ showReorderHint: false,
+ showSortHint: false,
+ showMarkHint: false,
+
+ // Grid editing
+ isCellEditActive: false, // true if current focus is in edit cell
+ isEditCellTextEditable: false, // true if current edit cell is editable in the text input box (not textarea)
+ currentEditCell: null, // reference to <td> that currently being edited
+ cellEditHint: '', // hint shown when doing grid edit
+ gotoLinkText: '', // "Go to link" text
+ wasEditedCellNull: false, // true if last value of the edited cell was NULL
+ maxTruncatedLen: 0, // number of characters that can be displayed in a cell
+ saveCellsAtOnce: false, // $cfg[saveCellsAtOnce]
+ isCellEdited: false, // true if at least one cell has been edited
+ saveCellWarning: '', // string, warning text when user want to leave a page with unsaved edited data
+ lastXHR : null, // last XHR object used in AJAX request
+ isSaving: false, // true when currently saving edited data, used to handle double posting caused by pressing ENTER in grid edit text box in Chrome browser
+ alertNonUnique: '', // string, alert shown when saving edited nonunique table
+
+ // Common hidden inputs
+ token: null,
+ server: null,
+ db: null,
+ table: null,
+
+
+ /************
+ * Functions
+ ************/
+
+ /**
+ * Start to resize column. Called when clicking on column separator.
+ *
+ * @param e event
+ * @param obj dragged div object
+ */
+ dragStartRsz: function (e, obj) {
+ var n = $(g.cRsz).find('div').index(obj); // get the index of separator (i.e., column index)
+ $(obj).addClass('colborder_active');
+ g.colRsz = {
+ x0: e.pageX,
+ n: n,
+ obj: obj,
+ objLeft: $(obj).position().left,
+ objWidth: $(g.t).find('th.draggable:visible:eq(' + n + ') span').outerWidth()
+ };
+ $(document.body).css('cursor', 'col-resize').noSelect();
+ if (g.isCellEditActive) {
+ g.hideEditCell();
+ }
+ },
+
+ /**
+ * Start to reorder column. Called when clicking on table header.
+ *
+ * @param e event
+ * @param obj table header object
+ */
+ dragStartReorder: function (e, obj) {
+ // prepare the cCpy (column copy) and cPointer (column pointer) from the dragged column
+ $(g.cCpy).text($(obj).text());
+ var objPos = $(obj).position();
+ $(g.cCpy).css({
+ top: objPos.top + 20,
+ left: objPos.left,
+ height: $(obj).height(),
+ width: $(obj).width()
+ });
+ $(g.cPointer).css({
+ top: objPos.top
+ });
+
+ // get the column index, zero-based
+ var n = g.getHeaderIdx(obj);
+
+ g.colReorder = {
+ x0: e.pageX,
+ y0: e.pageY,
+ n: n,
+ newn: n,
+ obj: obj,
+ objTop: objPos.top,
+ objLeft: objPos.left
+ };
+
+ $(document.body).css('cursor', 'move').noSelect();
+ if (g.isCellEditActive) {
+ g.hideEditCell();
+ }
+ },
+
+ /**
+ * Handle mousemove event when dragging.
+ *
+ * @param e event
+ */
+ dragMove: function (e) {
+ if (g.colRsz) {
+ var dx = e.pageX - g.colRsz.x0;
+ if (g.colRsz.objWidth + dx > g.minColWidth) {
+ $(g.colRsz.obj).css('left', g.colRsz.objLeft + dx + 'px');
+ }
+ } else if (g.colReorder) {
+ // dragged column animation
+ var dx = e.pageX - g.colReorder.x0;
+ $(g.cCpy)
+ .css('left', g.colReorder.objLeft + dx)
+ .show();
+
+ // pointer animation
+ var hoveredCol = g.getHoveredCol(e);
+ if (hoveredCol) {
+ var newn = g.getHeaderIdx(hoveredCol);
+ g.colReorder.newn = newn;
+ if (newn != g.colReorder.n) {
+ // show the column pointer in the right place
+ var colPos = $(hoveredCol).position();
+ var newleft = newn < g.colReorder.n ?
+ colPos.left :
+ colPos.left + $(hoveredCol).outerWidth();
+ $(g.cPointer)
+ .css({
+ left: newleft,
+ visibility: 'visible'
+ });
+ } else {
+ // no movement to other column, hide the column pointer
+ $(g.cPointer).css('visibility', 'hidden');
+ }
+ }
+ }
+ },
+
+ /**
+ * Stop the dragging action.
+ *
+ * @param e event
+ */
+ dragEnd: function (e) {
+ if (g.colRsz) {
+ var dx = e.pageX - g.colRsz.x0;
+ var nw = g.colRsz.objWidth + dx;
+ if (nw < g.minColWidth) {
+ nw = g.minColWidth;
+ }
+ var n = g.colRsz.n;
+ // do the resizing
+ g.resize(n, nw);
+
+ g.reposRsz();
+ g.reposDrop();
+ g.colRsz = false;
+ $(g.cRsz).find('div').removeClass('colborder_active');
+ } else if (g.colReorder) {
+ // shift columns
+ if (g.colReorder.newn != g.colReorder.n) {
+ g.shiftCol(g.colReorder.n, g.colReorder.newn);
+ // assign new position
+ var objPos = $(g.colReorder.obj).position();
+ g.colReorder.objTop = objPos.top;
+ g.colReorder.objLeft = objPos.left;
+ g.colReorder.n = g.colReorder.newn;
+ // send request to server to remember the column order
+ if (g.tableCreateTime) {
+ g.sendColPrefs();
+ }
+ g.refreshRestoreButton();
+ }
+
+ // animate new column position
+ $(g.cCpy).stop(true, true)
+ .animate({
+ top: g.colReorder.objTop,
+ left: g.colReorder.objLeft
+ }, 'fast')
+ .fadeOut();
+ $(g.cPointer).css('visibility', 'hidden');
+
+ g.colReorder = false;
+ }
+ $(document.body).css('cursor', 'inherit').noSelect(false);
+ },
+
+ /**
+ * Resize column n to new width "nw"
+ *
+ * @param n zero-based column index
+ * @param nw new width of the column in pixel
+ */
+ resize: function (n, nw) {
+ $(g.t).find('tr').each(function () {
+ $(this).find('th.draggable:visible:eq(' + n + ') span,' +
+ 'td:visible:eq(' + (g.actionSpan + n) + ') span')
+ .css('width', nw);
+ });
+ },
+
+ /**
+ * Reposition column resize bars.
+ */
+ reposRsz: function () {
+ $(g.cRsz).find('div').hide();
+ var $firstRowCols = $(g.t).find('tr:first th.draggable:visible');
+ var $resizeHandles = $(g.cRsz).find('div').removeClass('condition');
+ $('table.pma_table').find('thead th:first').removeClass('before-condition');
+ for (var n = 0, l = $firstRowCols.length; n < l; n++) {
+ var $col = $($firstRowCols[n]);
+ $($resizeHandles[n]).css('left', $col.position().left + $col.outerWidth(true))
+ .show();
+ if ($col.hasClass('condition')) {
+ $($resizeHandles[n]).addClass('condition');
+ if (n > 0) {
+ $($resizeHandles[n - 1]).addClass('condition');
+ }
+ }
+ }
+ if ($($resizeHandles[0]).hasClass('condition')) {
+ $('table.pma_table').find('thead th:first').addClass('before-condition');
+ }
+ $(g.cRsz).css('height', $(g.t).height());
+ },
+
+ /**
+ * Shift column from index oldn to newn.
+ *
+ * @param oldn old zero-based column index
+ * @param newn new zero-based column index
+ */
+ shiftCol: function (oldn, newn) {
+ $(g.t).find('tr').each(function () {
+ if (newn < oldn) {
+ $(this).find('th.draggable:eq(' + newn + '),' +
+ 'td:eq(' + (g.actionSpan + newn) + ')')
+ .before($(this).find('th.draggable:eq(' + oldn + '),' +
+ 'td:eq(' + (g.actionSpan + oldn) + ')'));
+ } else {
+ $(this).find('th.draggable:eq(' + newn + '),' +
+ 'td:eq(' + (g.actionSpan + newn) + ')')
+ .after($(this).find('th.draggable:eq(' + oldn + '),' +
+ 'td:eq(' + (g.actionSpan + oldn) + ')'));
+ }
+ });
+ // reposition the column resize bars
+ g.reposRsz();
+
+ // adjust the column visibility list
+ if (newn < oldn) {
+ $(g.cList).find('.lDiv div:eq(' + newn + ')')
+ .before($(g.cList).find('.lDiv div:eq(' + oldn + ')'));
+ } else {
+ $(g.cList).find('.lDiv div:eq(' + newn + ')')
+ .after($(g.cList).find('.lDiv div:eq(' + oldn + ')'));
+ }
+ // adjust the colOrder
+ var tmp = g.colOrder[oldn];
+ g.colOrder.splice(oldn, 1);
+ g.colOrder.splice(newn, 0, tmp);
+ // adjust the colVisib
+ if (g.colVisib.length > 0) {
+ tmp = g.colVisib[oldn];
+ g.colVisib.splice(oldn, 1);
+ g.colVisib.splice(newn, 0, tmp);
+ }
+ },
+
+ /**
+ * Find currently hovered table column's header (excluding actions column).
+ *
+ * @param e event
+ * @return the hovered column's th object or undefined if no hovered column found.
+ */
+ getHoveredCol: function (e) {
+ var hoveredCol;
+ $headers = $(g.t).find('th.draggable:visible');
+ $headers.each(function () {
+ var left = $(this).offset().left;
+ var right = left + $(this).outerWidth();
+ if (left <= e.pageX && e.pageX <= right) {
+ hoveredCol = this;
+ }
+ });
+ return hoveredCol;
+ },
+
+ /**
+ * Get a zero-based index from a <th class="draggable"> tag in a table.
+ *
+ * @param obj table header <th> object
+ * @return zero-based index of the specified table header in the set of table headers (visible or not)
+ */
+ getHeaderIdx: function (obj) {
+ return $(obj).parents('tr').find('th.draggable').index(obj);
+ },
+
+ /**
+ * Reposition the columns back to normal order.
+ */
+ restoreColOrder: function () {
+ // use insertion sort, since we already have shiftCol function
+ for (var i = 1; i < g.colOrder.length; i++) {
+ var x = g.colOrder[i];
+ var j = i - 1;
+ while (j >= 0 && x < g.colOrder[j]) {
+ j--;
+ }
+ if (j != i - 1) {
+ g.shiftCol(i, j + 1);
+ }
+ }
+ if (g.tableCreateTime) {
+ // send request to server to remember the column order
+ g.sendColPrefs();
+ }
+ g.refreshRestoreButton();
+ },
+
+ /**
+ * Send column preferences (column order and visibility) to the server.
+ */
+ sendColPrefs: function () {
+ if ($(g.t).is('.ajax')) { // only send preferences if ajax class
+ var post_params = {
+ ajax_request: true,
+ db: g.db,
+ table: g.table,
+ token: g.token,
+ server: g.server,
+ set_col_prefs: true,
+ table_create_time: g.tableCreateTime
+ };
+ if (g.colOrder.length > 0) {
+ $.extend(post_params, {col_order: g.colOrder.toString()});
+ }
+ if (g.colVisib.length > 0) {
+ $.extend(post_params, {col_visib: g.colVisib.toString()});
+ }
+ $.post('sql.php', post_params, function (data) {
+ if (data.success !== true) {
+ var $temp_div = $(document.createElement('div'));
+ $temp_div.html(data.error);
+ $temp_div.addClass("error");
+ PMA_ajaxShowMessage($temp_div, false);
+ }
+ });
+ }
+ },
+
+ /**
+ * Refresh restore button state.
+ * Make restore button disabled if the table is similar with initial state.
+ */
+ refreshRestoreButton: function () {
+ // check if table state is as initial state
+ var isInitial = true;
+ for (var i = 0; i < g.colOrder.length; i++) {
+ if (g.colOrder[i] != i) {
+ isInitial = false;
+ break;
+ }
+ }
+ // check if only one visible column left
+ var isOneColumn = g.visibleHeadersCount == 1;
+ // enable or disable restore button
+ if (isInitial || isOneColumn) {
+ $('div.restore_column').hide();
+ } else {
+ $('div.restore_column').show();
+ }
+ },
+
+ /**
+ * Update current hint using the boolean values (showReorderHint, showSortHint, etc.).
+ *
+ */
+ updateHint: function () {
+ var text = '';
+ if (!g.colRsz && !g.colReorder) { // if not resizing or dragging
+ if (g.visibleHeadersCount > 1) {
+ g.showReorderHint = true;
+ }
+ if ($(t).find('th.marker').length > 0) {
+ g.showMarkHint = true;
+ }
+
+ if (g.showReorderHint && g.reorderHint) {
+ text += g.reorderHint;
+ }
+ if (g.showSortHint && g.sortHint) {
+ text += text.length > 0 ? '<br />' : '';
+ text += g.sortHint;
+ }
+ if (g.showMarkHint && g.markHint &&
+ !g.showSortHint // we do not show mark hint, when sort hint is shown
+ ) {
+ text += text.length > 0 ? '<br />' : '';
+ text += g.markHint;
+ text += text.length > 0 ? '<br />' : '';
+ text += g.copyHint;
+ }
+ }
+ return text;
+ },
+
+ /**
+ * Toggle column's visibility.
+ * After calling this function and it returns true, afterToggleCol() must be called.
+ *
+ * @return boolean True if the column is toggled successfully.
+ */
+ toggleCol: function (n) {
+ if (g.colVisib[n]) {
+ // can hide if more than one column is visible
+ if (g.visibleHeadersCount > 1) {
+ $(g.t).find('tr').each(function () {
+ $(this).find('th.draggable:eq(' + n + '),' +
+ 'td:eq(' + (g.actionSpan + n) + ')')
+ .hide();
+ });
+ g.colVisib[n] = 0;
+ $(g.cList).find('.lDiv div:eq(' + n + ') input').prop('checked', false);
+ } else {
+ // cannot hide, force the checkbox to stay checked
+ $(g.cList).find('.lDiv div:eq(' + n + ') input').prop('checked', true);
+ return false;
+ }
+ } else { // column n is not visible
+ $(g.t).find('tr').each(function () {
+ $(this).find('th.draggable:eq(' + n + '),' +
+ 'td:eq(' + (g.actionSpan + n) + ')')
+ .show();
+ });
+ g.colVisib[n] = 1;
+ $(g.cList).find('.lDiv div:eq(' + n + ') input').prop('checked', true);
+ }
+ return true;
+ },
+
+ /**
+ * This must be called if toggleCol() returns is true.
+ *
+ * This function is separated from toggleCol because, sometimes, we want to toggle
+ * some columns together at one time and do just one adjustment after it, e.g. in showAllColumns().
+ */
+ afterToggleCol: function () {
+ // some adjustments after hiding column
+ g.reposRsz();
+ g.reposDrop();
+ g.sendColPrefs();
+
+ // check visible first row headers count
+ g.visibleHeadersCount = $(g.t).find('tr:first th.draggable:visible').length;
+ g.refreshRestoreButton();
+ },
+
+ /**
+ * Show columns' visibility list.
+ *
+ * @param obj The drop down arrow of column visibility list
+ */
+ showColList: function (obj) {
+ // only show when not resizing or reordering
+ if (!g.colRsz && !g.colReorder) {
+ var pos = $(obj).position();
+ // check if the list position is too right
+ if (pos.left + $(g.cList).outerWidth(true) > $(document).width()) {
+ pos.left = $(document).width() - $(g.cList).outerWidth(true);
+ }
+ $(g.cList).css({
+ left: pos.left,
+ top: pos.top + $(obj).outerHeight(true)
+ })
+ .show();
+ $(obj).addClass('coldrop-hover');
+ }
+ },
+
+ /**
+ * Hide columns' visibility list.
+ */
+ hideColList: function () {
+ $(g.cList).hide();
+ $(g.cDrop).find('.coldrop-hover').removeClass('coldrop-hover');
+ },
+
+ /**
+ * Reposition the column visibility drop-down arrow.
+ */
+ reposDrop: function () {
+ var $th = $(t).find('th:not(.draggable)');
+ for (var i = 0; i < $th.length; i++) {
+ var $cd = $(g.cDrop).find('div:eq(' + i + ')'); // column drop-down arrow
+ var pos = $($th[i]).position();
+ $cd.css({
+ left: pos.left + $($th[i]).width() - $cd.width(),
+ top: pos.top
+ });
+ }
+ },
+
+ /**
+ * Show all hidden columns.
+ */
+ showAllColumns: function () {
+ for (var i = 0; i < g.colVisib.length; i++) {
+ if (!g.colVisib[i]) {
+ g.toggleCol(i);
+ }
+ }
+ g.afterToggleCol();
+ },
+
+ /**
+ * Show edit cell, if it can be shown
+ *
+ * @param cell <td> element to be edited
+ */
+ showEditCell: function (cell) {
+ if ($(cell).is('.grid_edit') &&
+ !g.colRsz && !g.colReorder)
+ {
+ if (!g.isCellEditActive) {
+ var $cell = $(cell);
+ // remove all edit area and hide it
+ $(g.cEdit).find('.edit_area').empty().hide();
+ // reposition the cEdit element
+ $(g.cEdit).css({
+ top: $cell.position().top,
+ left: $cell.position().left
+ })
+ .show()
+ .find('.edit_box')
+ .css({
+ width: $cell.outerWidth(),
+ height: $cell.outerHeight()
+ });
+ // fill the cell edit with text from <td>
+ var value = PMA_getCellValue(cell);
+ $(g.cEdit).find('.edit_box').val(value);
+
+ g.currentEditCell = cell;
+ $(g.cEdit).find('.edit_box').focus();
+ $(g.cEdit).find('*').removeProp('disabled');
+ }
+ }
+ },
+
+ /**
+ * Remove edit cell and the edit area, if it is shown.
+ *
+ * @param force Optional, force to hide edit cell without saving edited field.
+ * @param data Optional, data from the POST AJAX request to save the edited field
+ * or just specify "true", if we want to replace the edited field with the new value.
+ * @param field Optional, the edited <td>. If not specified, the function will
+ * use currently edited <td> from g.currentEditCell.
+ */
+ hideEditCell: function (force, data, field) {
+ if (g.isCellEditActive && !force) {
+ // cell is being edited, save or post the edited data
+ g.saveOrPostEditedCell();
+ return;
+ }
+
+ // cancel any previous request
+ if (g.lastXHR !== null) {
+ g.lastXHR.abort();
+ g.lastXHR = null;
+ }
+
+ if (data) {
+ if (g.currentEditCell) { // save value of currently edited cell
+ // replace current edited field with the new value
+ var $this_field = $(g.currentEditCell);
+ var is_null = $this_field.data('value') === null;
+ if (is_null) {
+ $this_field.find('span').html('NULL');
+ $this_field.addClass('null');
+ } else {
+ $this_field.removeClass('null');
+ var new_html = data.isNeedToRecheck
+ ? data.truncatableFieldValue
+ : $this_field.data('value');
+
+ //remove decimal places if column type not supported
+ if (($this_field.data('decimals') == 0) && ( $this_field.data('type').indexOf('time') != -1)){
+ new_html = new_html.substring(0, new_html.indexOf('.'));
+ }
+ //remove addtional decimal places
+ if (($this_field.data('decimals') > 0) && ( $this_field.data('type').indexOf('time') != -1)){
+ new_html = new_html.substring(0, new_html.length - (6 - $this_field.data('decimals')));
+ }
+ if ($this_field.is('.truncated')) {
+ if (new_html.length > g.maxTruncatedLen) {
+ new_html = new_html.substring(0, g.maxTruncatedLen) + '...';
+ }
+ }
+ $this_field.find('span').text(new_html);
+ }
+ if ($this_field.is('.bit')) {
+ $this_field.find('span').text($this_field.data('value'));
+ }
+ }
+ if (data.transformations !== undefined) {
+ $.each(data.transformations, function (cell_index, value) {
+ var $this_field = $(g.t).find('.to_be_saved:eq(' + cell_index + ')');
+ $this_field.find('span').html(value);
+ });
+ }
+ if (data.relations !== undefined) {
+ $.each(data.relations, function (cell_index, value) {
+ var $this_field = $(g.t).find('.to_be_saved:eq(' + cell_index + ')');
+ $this_field.find('span').html(value);
+ });
+ }
+
+ // refresh the grid
+ g.reposRsz();
+ g.reposDrop();
+ }
+
+ // hide the cell editing area
+ $(g.cEdit).hide();
+ $(g.cEdit).find('.edit_box').blur();
+ g.isCellEditActive = false;
+ g.currentEditCell = null;
+ // destroy datepicker in edit area, if exist
+ var $dp = $(g.cEdit).find('.hasDatepicker');
+ if ($dp.length > 0) {
+ $dp.datepicker('destroy');
+ // change the cursor in edit box back to normal
+ // (the cursor become a hand pointer when we add datepicker)
+ $(g.cEdit).find('.edit_box').css('cursor', 'inherit');
+ }
+ },
+
+ /**
+ * Show drop-down edit area when edit cell is focused.
+ */
+ showEditArea: function () {
+ if (!g.isCellEditActive) { // make sure the edit area has not been shown
+ g.isCellEditActive = true;
+ g.isEditCellTextEditable = false;
+ /**
+ * @var $td current edited cell
+ */
+ var $td = $(g.currentEditCell);
+ /**
+ * @var $editArea the editing area
+ */
+ var $editArea = $(g.cEdit).find('.edit_area');
+ /**
+ * @var where_clause WHERE clause for the edited cell
+ */
+ var where_clause = $td.parent('tr').find('.where_clause').val();
+ /**
+ * @var field_name String containing the name of this field.
+ * @see getFieldName()
+ */
+ var field_name = getFieldName($td);
+ /**
+ * @var relation_curr_value String current value of the field (for fields that are foreign keyed).
+ */
+ var relation_curr_value = $td.text();
+ /**
+ * @var relation_key_or_display_column String relational key if in 'Relational display column' mode,
+ * relational display column if in 'Relational key' mode (for fields that are foreign keyed).
+ */
+ var relation_key_or_display_column = $td.find('a').attr('title');
+ /**
+ * @var curr_value String current value of the field (for fields that are of type enum or set).
+ */
+ var curr_value = $td.find('span').text();
+
+ // empty all edit area, then rebuild it based on $td classes
+ $editArea.empty();
+ $editArea.removeClass('edit_area_right');
+
+ // add show data row link if the data resulted by 'browse distinct values' in table structure
+ if ($td.find('input').hasClass('data_browse_link')) {
+ var showDataRowLink = document.createElement('div');
+ showDataRowLink.className = 'goto_link';
+ $(showDataRowLink).append("<a href='" + $td.find('.data_browse_link').val() + "'>" + g.showDataRowLinkText + "</a>");
+ $editArea.append(showDataRowLink);
+ }
+
+ // add goto link, if this cell contains a link
+ if ($td.find('a').length > 0) {
+ var gotoLink = document.createElement('div');
+ gotoLink.className = 'goto_link';
+ $(gotoLink).append(g.gotoLinkText + ': ').append($td.find('a').clone());
+ $editArea.append(gotoLink);
+ }
+
+ g.wasEditedCellNull = false;
+ if ($td.is(':not(.not_null)')) {
+ // append a null checkbox
+ $editArea.append('<div class="null_div">Null:<input type="checkbox"></div>');
+
+ var $checkbox = $editArea.find('.null_div input');
+ // check if current <td> is NULL
+ if ($td.is('.null')) {
+ $checkbox.prop('checked', true);
+ g.wasEditedCellNull = true;
+ }
+
+ // if the select/editor is changed un-check the 'checkbox_null_<field_name>_<row_index>'.
+ if ($td.is('.enum, .set')) {
+ $editArea.find('select').live('change', function (e) {
+ $checkbox.prop('checked', false);
+ });
+ } else if ($td.is('.relation')) {
+ $editArea.find('select').live('change', function (e) {
+ $checkbox.prop('checked', false);
+ });
+ $editArea.find('.browse_foreign').live('click', function (e) {
+ $checkbox.prop('checked', false);
+ });
+ } else {
+ $(g.cEdit).find('.edit_box').live('keypress change', function (e) {
+ $checkbox.prop('checked', false);
+ });
+ // Capture ctrl+v (on IE and Chrome)
+ $(g.cEdit).find('.edit_box').live('keydown', function (e) {
+ if (e.ctrlKey && e.which == 86) {
+ $checkbox.prop('checked', false);
+ }
+ });
+ $editArea.find('textarea').live('keydown', function (e) {
+ $checkbox.prop('checked', false);
+ });
+ }
+
+ // if null checkbox is clicked empty the corresponding select/editor.
+ $checkbox.click(function (e) {
+ if ($td.is('.enum')) {
+ $editArea.find('select').val('');
+ } else if ($td.is('.set')) {
+ $editArea.find('select').find('option').each(function () {
+ var $option = $(this);
+ $option.prop('selected', false);
+ });
+ } else if ($td.is('.relation')) {
+ // if the dropdown is there to select the foreign value
+ if ($editArea.find('select').length > 0) {
+ $editArea.find('select').val('');
+ }
+ } else {
+ $editArea.find('textarea').val('');
+ }
+ $(g.cEdit).find('.edit_box').val('');
+ });
+ }
+
+ if ($td.is('.relation')) {
+ //handle relations
+ $editArea.addClass('edit_area_loading');
+
+ // initialize the original data
+ $td.data('original_data', null);
+
+ /**
+ * @var post_params Object containing parameters for the POST request
+ */
+ var post_params = {
+ 'ajax_request' : true,
+ 'get_relational_values' : true,
+ 'server' : g.server,
+ 'db' : g.db,
+ 'table' : g.table,
+ 'column' : field_name,
+ 'token' : g.token,
+ 'curr_value' : relation_curr_value,
+ 'relation_key_or_display_column' : relation_key_or_display_column
+ };
+
+ g.lastXHR = $.post('sql.php', post_params, function (data) {
+ g.lastXHR = null;
+ $editArea.removeClass('edit_area_loading');
+ if ($(data.dropdown).is('select')) {
+ // save original_data
+ var value = $(data.dropdown).val();
+ $td.data('original_data', value);
+ // update the text input field, in case where the "Relational display column" is checked
+ $(g.cEdit).find('.edit_box').val(value);
+ }
+
+ $editArea.append(data.dropdown);
+ $editArea.append('<div class="cell_edit_hint">' + g.cellEditHint + '</div>');
+
+ // for 'Browse foreign values' options,
+ // hide the value next to 'Browse foreign values' link
+ $editArea.find('span.curr_value').hide();
+ // handle update for new values selected from new window
+ $editArea.find('span.curr_value').change(function () {
+ $(g.cEdit).find('.edit_box').val($(this).text());
+ });
+ }); // end $.post()
+
+ $editArea.show();
+ $editArea.find('select').live('change', function (e) {
+ $(g.cEdit).find('.edit_box').val($(this).val());
+ });
+ g.isEditCellTextEditable = true;
+ }
+ else if ($td.is('.enum')) {
+ //handle enum fields
+ $editArea.addClass('edit_area_loading');
+
+ /**
+ * @var post_params Object containing parameters for the POST request
+ */
+ var post_params = {
+ 'ajax_request' : true,
+ 'get_enum_values' : true,
+ 'server' : g.server,
+ 'db' : g.db,
+ 'table' : g.table,
+ 'column' : field_name,
+ 'token' : g.token,
+ 'curr_value' : curr_value
+ };
+ g.lastXHR = $.post('sql.php', post_params, function (data) {
+ g.lastXHR = null;
+ $editArea.removeClass('edit_area_loading');
+ $editArea.append(data.dropdown);
+ $editArea.append('<div class="cell_edit_hint">' + g.cellEditHint + '</div>');
+ }); // end $.post()
+
+ $editArea.show();
+ $editArea.find('select').live('change', function (e) {
+ $(g.cEdit).find('.edit_box').val($(this).val());
+ });
+ }
+ else if ($td.is('.set')) {
+ //handle set fields
+ $editArea.addClass('edit_area_loading');
+
+ /**
+ * @var post_params Object containing parameters for the POST request
+ */
+ var post_params = {
+ 'ajax_request' : true,
+ 'get_set_values' : true,
+ 'server' : g.server,
+ 'db' : g.db,
+ 'table' : g.table,
+ 'column' : field_name,
+ 'token' : g.token,
+ 'curr_value' : curr_value
+ };
+
+ g.lastXHR = $.post('sql.php', post_params, function (data) {
+ g.lastXHR = null;
+ $editArea.removeClass('edit_area_loading');
+ $editArea.append(data.select);
+ $editArea.append('<div class="cell_edit_hint">' + g.cellEditHint + '</div>');
+ }); // end $.post()
+
+ $editArea.show();
+ $editArea.find('select').live('change', function (e) {
+ $(g.cEdit).find('.edit_box').val($(this).val());
+ });
+ }
+ else if ($td.is('.truncated, .transformed')) {
+ if ($td.is('.to_be_saved')) { // cell has been edited
+ var value = $td.data('value');
+ $(g.cEdit).find('.edit_box').val(value);
+ $editArea.append('<textarea></textarea>');
+ $editArea.find('textarea')
+ .val(value)
+ .live('keyup', function (e) {
+ $(g.cEdit).find('.edit_box').val($(this).val());
+ });
+ $(g.cEdit).find('.edit_box').live('keyup', function (e) {
+ $editArea.find('textarea').val($(this).val());
+ });
+ $editArea.append('<div class="cell_edit_hint">' + g.cellEditHint + '</div>');
+ } else {
+ //handle truncated/transformed values values
+ $editArea.addClass('edit_area_loading');
+
+ // initialize the original data
+ $td.data('original_data', null);
+
+ /**
+ * @var sql_query String containing the SQL query used to retrieve value of truncated/transformed data
+ */
+ var sql_query = 'SELECT `' + field_name + '` FROM `' + g.table + '` WHERE ' + PMA_urldecode(where_clause);
+
+ // Make the Ajax call and get the data, wrap it and insert it
+ g.lastXHR = $.post('sql.php', {
+ 'token' : g.token,
+ 'server' : g.server,
+ 'db' : g.db,
+ 'ajax_request' : true,
+ 'sql_query' : sql_query,
+ 'grid_edit' : true
+ }, function (data) {
+ g.lastXHR = null;
+ $editArea.removeClass('edit_area_loading');
+ if (data.success === true) {
+ if ($td.is('.truncated')) {
+ // get the truncated data length
+ g.maxTruncatedLen = $(g.currentEditCell).text().length - 3;
+ }
+
+ $td.data('original_data', data.value);
+ $(g.cEdit).find('.edit_box').val(data.value);
+ $editArea.append('<textarea></textarea>');
+ $editArea.find('textarea')
+ .val(data.value)
+ .live('keyup', function (e) {
+ $(g.cEdit).find('.edit_box').val($(this).val());
+ });
+ $(g.cEdit).find('.edit_box').live('keyup', function (e) {
+ $editArea.find('textarea').val($(this).val());
+ });
+ $editArea.append('<div class="cell_edit_hint">' + g.cellEditHint + '</div>');
+ } else {
+ PMA_ajaxShowMessage(data.error, false);
+ }
+ }); // end $.post()
+ $editArea.show();
+ }
+ g.isEditCellTextEditable = true;
+ } else if ($td.is('.datefield, .datetimefield, .timestampfield')) {
+ var $input_field = $(g.cEdit).find('.edit_box');
+
+ // remember current datetime value in $input_field, if it is not null
+ var is_null = $td.is('.null');
+ var current_datetime_value = !is_null ? $input_field.val() : '';
+
+ var showTimeOption = true;
+ if ($td.is('.datefield')) {
+ showTimeOption = false;
+ }
+
+ var showMillisec = false;
+ var showMicrosec = false;
+ var timeFormat = 'HH:mm:ss';
+ // check for decimal places of seconds
+ if (($td.data('decimals') > 0) && ($td.data('type').indexOf('time') != -1)){
+ showMillisec = true;
+ timeFormat = 'HH:mm:ss.lc';
+ if ($td.data('decimals') > 3) {
+ showMicrosec = true;
+ }
+
+ }
+
+ PMA_addDatepicker($editArea, {
+ showMillisec: showMillisec,
+ showMicrosec: showMicrosec,
+ timeFormat: timeFormat,
+ altField: $input_field,
+ showTimepicker: showTimeOption,
+ onSelect: function (dateText, inst) {
+ // remove null checkbox if it exists
+ $(g.cEdit).find('.null_div input[type=checkbox]').prop('checked', false);
+ }
+ });
+
+ // cancel any click on the datepicker element
+ $editArea.find('> *').click(function (e) {
+ e.stopPropagation();
+ });
+
+ // force to restore modified $input_field value after adding datepicker
+ // (after adding a datepicker, the input field doesn't display the time anymore, only the date)
+ if (is_null
+ || current_datetime_value == '0000-00-00'
+ || current_datetime_value == '0000-00-00 00:00:00.000000'
+ ) {
+ $input_field.val(current_datetime_value);
+ } else {
+ var date = new Date(
+ current_datetime_value.substring(0, 4),
+ parseInt(current_datetime_value.substring(5, 7)) - 1,
+ parseInt(current_datetime_value.substring(8, 10))
+ );
+ var no_decimals = $td.data('decimals');
+
+ var hour = current_datetime_value.substring(11, 13);
+ var min = current_datetime_value.substring(14, 16);
+ var sec = current_datetime_value.substring(17, 19);
+ if (current_datetime_value.match("^\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}\\.\\d{"+ no_decimals +"}$")) {
+ if (no_decimals > 3){
+ var milli = current_datetime_value.substring(20, 23);
+ var micro = current_datetime_value.substring(23);
+ for (var i = 0; i < 6-no_decimals ; i++) {
+ micro += "0";
+ }
+ }
+ if (no_decimals <= 3){
+ var milli = current_datetime_value.substring(20);
+ for (var i = 0; i < 3-no_decimals ; i++) {
+ milli += "0";
+ }
+ var micro = "000";
+ }
+
+ date.setHours(hour, min, sec, milli);
+ date.setMicroseconds(micro);
+ }
+ if (current_datetime_value.match("^\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}")) {
+ console.log("G");
+ date.setHours(hour, min, sec);
+ }
+ $editArea.datetimepicker('setDate', date);
+ }
+ $editArea.append('<div class="cell_edit_hint">' + g.cellEditHint + '</div>');
+
+ // remove {cursor: 'pointer'} added inside
+ // jquery-ui-timepicker-addon.js
+ $input_field.css('cursor', '');
+ // make the cell editable, so one can can bypass the timepicker
+ // and enter date/time value manually
+ g.isEditCellTextEditable = true;
+ } else {
+ g.isEditCellTextEditable = true;
+ // only append edit area hint if there is a null checkbox
+ if ($editArea.children().length > 0) {
+ $editArea.append('<div class="cell_edit_hint">' + g.cellEditHint + '</div>');
+ }
+ }
+ if ($(g.cEdit).offset().left + $editArea.outerWidth() > $(document.body).width()) {
+ $editArea.addClass('edit_area_right');
+ }
+ if ($editArea.children().length > 0) {
+ $editArea.show();
+ }
+ }
+ },
+
+ /**
+ * Post the content of edited cell.
+ */
+ postEditedCell: function () {
+ if (g.isSaving) {
+ return;
+ }
+ g.isSaving = true;
+
+ /**
+ * @var relation_fields Array containing the name/value pairs of relational fields
+ */
+ var relation_fields = {};
+ /**
+ * @var relational_display string 'K' if relational key, 'D' if relational display column
+ */
+ var relational_display = $("#relational_display_K").prop('checked') ? 'K' : 'D';
+ /**
+ * @var transform_fields Array containing the name/value pairs for transformed fields
+ */
+ var transform_fields = {};
+ /**
+ * @var transformation_fields Boolean, if there are any transformed fields in the edited cells
+ */
+ var transformation_fields = false;
+ /**
+ * @var full_sql_query String containing the complete SQL query to update this table
+ */
+ var full_sql_query = '';
+ /**
+ * @var rel_fields_list String, url encoded representation of {@link relations_fields}
+ */
+ var rel_fields_list = '';
+ /**
+ * @var transform_fields_list String, url encoded representation of {@link transform_fields}
+ */
+ var transform_fields_list = '';
+ /**
+ * @var where_clause Array containing where clause for updated fields
+ */
+ var full_where_clause = [];
+ /**
+ * @var is_unique Boolean, whether the rows in this table is unique or not
+ */
+ var is_unique = $('td.edit_row_anchor').is('.nonunique') ? 0 : 1;
+ /**
+ * multi edit variables
+ */
+ var me_fields_name = [];
+ var me_fields_type = [];
+ var me_fields = [];
+ var me_fields_null = [];
+
+ // alert user if edited table is not unique
+ if (!is_unique) {
+ alert(g.alertNonUnique);
+ }
+
+ // loop each edited row
+ $('td.to_be_saved').parents('tr').each(function () {
+ var $tr = $(this);
+ var where_clause = $tr.find('.where_clause').val();
+ full_where_clause.push(PMA_urldecode(where_clause));
+ var condition_array = jQuery.parseJSON($tr.find('.condition_array').val());
+
+ /**
+ * multi edit variables, for current row
+ * @TODO array indices are still not correct, they should be md5 of field's name
+ */
+ var fields_name = [];
+ var fields_type = [];
+ var fields = [];
+ var fields_null = [];
+
+ // loop each edited cell in a row
+ $tr.find('.to_be_saved').each(function () {
+ /**
+ * @var $this_field Object referring to the td that is being edited
+ */
+ var $this_field = $(this);
+
+ /**
+ * @var field_name String containing the name of this field.
+ * @see getFieldName()
+ */
+ var field_name = getFieldName($this_field);
+
+ /**
+ * @var this_field_params Array temporary storage for the name/value of current field
+ */
+ var this_field_params = {};
+
+ if ($this_field.is('.transformed')) {
+ transformation_fields = true;
+ }
+ this_field_params[field_name] = $this_field.data('value');
+
+ /**
+ * @var is_null String capturing whether 'checkbox_null_<field_name>_<row_index>' is checked.
+ */
+ var is_null = this_field_params[field_name] === null;
+
+ fields_name.push(field_name);
+
+ if (is_null) {
+ fields_null.push('on');
+ fields.push('');
+ } else {
+ if ($this_field.is('.bit')) {
+ fields_type.push('bit');
+ }
+ fields_null.push('');
+ fields.push($this_field.data('value'));
+
+ var cell_index = $this_field.index('.to_be_saved');
+ if ($this_field.is(":not(.relation, .enum, .set, .bit)")) {
+ if ($this_field.is('.transformed')) {
+ transform_fields[cell_index] = {};
+ $.extend(transform_fields[cell_index], this_field_params);
+ }
+ } else if ($this_field.is('.relation')) {
+ relation_fields[cell_index] = {};
+ $.extend(relation_fields[cell_index], this_field_params);
+ }
+ }
+ // check if edited field appears in WHERE clause
+ if (where_clause.indexOf(PMA_urlencode(field_name)) > -1) {
+ var field_str = '`' + g.table + '`.' + '`' + field_name + '`';
+ for (var field in condition_array) {
+ if (field.indexOf(field_str) > -1) {
+ condition_array[field] = is_null ? 'IS NULL' : "= '" + this_field_params[field_name].replace(/'/g, "''") + "'";
+ break;
+ }
+ }
+ }
+
+ }); // end of loop for every edited cells in a row
+
+ // save new_clause
+ var new_clause = '';
+ for (var field in condition_array) {
+ new_clause += field + ' ' + condition_array[field] + ' AND ';
+ }
+ new_clause = new_clause.substring(0, new_clause.length - 5); // remove the last AND
+ new_clause = PMA_urlencode(new_clause);
+ $tr.data('new_clause', new_clause);
+ // save condition_array
+ $tr.find('.condition_array').val(JSON.stringify(condition_array));
+
+ me_fields_name.push(fields_name);
+ me_fields_type.push(fields_type);
+ me_fields.push(fields);
+ me_fields_null.push(fields_null);
+
+ }); // end of loop for every edited rows
+
+ rel_fields_list = $.param(relation_fields);
+ transform_fields_list = $.param(transform_fields);
+
+ // Make the Ajax post after setting all parameters
+ /**
+ * @var post_params Object containing parameters for the POST request
+ */
+ var post_params = {'ajax_request' : true,
+ 'sql_query' : full_sql_query,
+ 'token' : g.token,
+ 'server' : g.server,
+ 'db' : g.db,
+ 'table' : g.table,
+ 'clause_is_unique' : is_unique,
+ 'where_clause' : full_where_clause,
+ 'fields[multi_edit]' : me_fields,
+ 'fields_name[multi_edit]' : me_fields_name,
+ 'fields_type[multi_edit]' : me_fields_type,
+ 'fields_null[multi_edit]' : me_fields_null,
+ 'rel_fields_list' : rel_fields_list,
+ 'do_transformations' : transformation_fields,
+ 'transform_fields_list' : transform_fields_list,
+ 'relational_display' : relational_display,
+ 'goto' : 'sql.php',
+ 'submit_type' : 'save'
+ };
+
+ if (!g.saveCellsAtOnce) {
+ $(g.cEdit).find('*').prop('disabled', true);
+ $(g.cEdit).find('.edit_box').addClass('edit_box_posting');
+ } else {
+ $('div.save_edited').addClass('saving_edited_data')
+ .find('input').prop('disabled', true); // disable the save button
+ }
+
+ $.ajax({
+ type: 'POST',
+ url: 'tbl_replace.php',
+ data: post_params,
+ success:
+ function (data) {
+ g.isSaving = false;
+ if (!g.saveCellsAtOnce) {
+ $(g.cEdit).find('*').removeProp('disabled');
+ $(g.cEdit).find('.edit_box').removeClass('edit_box_posting');
+ } else {
+ $('div.save_edited').removeClass('saving_edited_data')
+ .find('input').removeProp('disabled'); // enable the save button back
+ }
+ if (data.success === true) {
+ PMA_ajaxShowMessage(data.message);
+
+ // update where_clause related data in each edited row
+ $('td.to_be_saved').parents('tr').each(function () {
+ var new_clause = $(this).data('new_clause');
+ var $where_clause = $(this).find('.where_clause');
+ var old_clause = $where_clause.val();
+ var decoded_old_clause = PMA_urldecode(old_clause);
+ var decoded_new_clause = PMA_urldecode(new_clause);
+
+ $where_clause.val(new_clause);
+ // update Edit, Copy, and Delete links also
+ $(this).find('a').each(function () {
+ $(this).attr('href', $(this).attr('href').replace(old_clause, new_clause));
+ // update delete confirmation in Delete link
+ if ($(this).attr('href').indexOf('DELETE') > -1) {
+ $(this).removeAttr('onclick')
+ .unbind('click')
+ .bind('click', function () {
+ return confirmLink(this, 'DELETE FROM `' + g.db + '`.`' + g.table + '` WHERE ' +
+ decoded_new_clause + (is_unique ? '' : ' LIMIT 1'));
+ });
+ }
+ });
+ // update the multi edit checkboxes
+ $(this).find('input[type=checkbox]').each(function () {
+ var $checkbox = $(this);
+ var checkbox_name = $checkbox.attr('name');
+ var checkbox_value = $checkbox.val();
+
+ $checkbox.attr('name', checkbox_name.replace(old_clause, new_clause));
+ $checkbox.val(checkbox_value.replace(decoded_old_clause, decoded_new_clause));
+ });
+ });
+ // update the display of executed SQL query command
+ if (typeof data.sql_query != 'undefined') {
+ //extract query box
+ var $result_query = $($.parseHTML(data.sql_query));
+ var sqlOuter = $result_query.find('.sqlOuter').wrap('<p>').parent().html();
+ var tools = $result_query.find('.tools').wrap('<p>').parent().html();
+ // If two query box exists update query in second else add a second box
+ if($('#result_query').find('div.sqlOuter').length>1) {
+ $('#result_query').children(":nth-child(4)").remove();
+ $('#result_query').children(":nth-child(4)").remove();
+ $('#result_query').append(sqlOuter+tools);
+ }
+ else {
+ $('#result_query').append(sqlOuter+tools);
+ }
+ PMA_highlightSQL($('#result_query'));
+ }
+ // hide and/or update the successfully saved cells
+ g.hideEditCell(true, data);
+
+ // remove the "Save edited cells" button
+ $('div.save_edited').hide();
+ // update saved fields
+ $(g.t).find('.to_be_saved')
+ .removeClass('to_be_saved')
+ .data('value', null)
+ .data('original_data', null);
+
+ g.isCellEdited = false;
+ } else {
+ PMA_ajaxShowMessage(data.error, false);
+ }
+ }
+ }); // end $.ajax()
+ },
+
+ /**
+ * Save edited cell, so it can be posted later.
+ */
+ saveEditedCell: function () {
+ /**
+ * @var $this_field Object referring to the td that is being edited
+ */
+ var $this_field = $(g.currentEditCell);
+ var $test_element = ''; // to test the presence of a element
+
+ var need_to_post = false;
+
+ /**
+ * @var field_name String containing the name of this field.
+ * @see getFieldName()
+ */
+ var field_name = getFieldName($this_field);
+
+ /**
+ * @var this_field_params Array temporary storage for the name/value of current field
+ */
+ var this_field_params = {};
+
+ /**
+ * @var is_null String capturing whether 'checkbox_null_<field_name>_<row_index>' is checked.
+ */
+ var is_null = $(g.cEdit).find('input:checkbox').is(':checked');
+ var value;
+
+ if ($(g.cEdit).find('.edit_area').is('.edit_area_loading')) {
+ // the edit area is still loading (retrieving cell data), no need to post
+ need_to_post = false;
+ } else if (is_null) {
+ if (!g.wasEditedCellNull) {
+ this_field_params[field_name] = null;
+ need_to_post = true;
+ }
+ } else {
+ if ($this_field.is('.bit')) {
+ this_field_params[field_name] = $(g.cEdit).find('.edit_box').val();
+ } else if ($this_field.is('.set')) {
+ $test_element = $(g.cEdit).find('select');
+ this_field_params[field_name] = $test_element.map(function () {
+ return $(this).val();
+ }).get().join(",");
+ } else if ($this_field.is('.relation, .enum')) {
+ // for relation and enumeration, take the results from edit box value,
+ // because selected value from drop-down, new window or multiple
+ // selection list will always be updated to the edit box
+ this_field_params[field_name] = $(g.cEdit).find('.edit_box').val();
+ } else {
+ this_field_params[field_name] = $(g.cEdit).find('.edit_box').val();
+ }
+ if (g.wasEditedCellNull || this_field_params[field_name] != PMA_getCellValue(g.currentEditCell)) {
+ need_to_post = true;
+ }
+ }
+
+ if (need_to_post) {
+ $(g.currentEditCell).addClass('to_be_saved')
+ .data('value', this_field_params[field_name]);
+ if (g.saveCellsAtOnce) {
+ $('div.save_edited').show();
+ }
+ g.isCellEdited = true;
+ }
+
+ return need_to_post;
+ },
+
+ /**
+ * Save or post currently edited cell, depending on the "saveCellsAtOnce" configuration.
+ */
+ saveOrPostEditedCell: function () {
+ var saved = g.saveEditedCell();
+ if (!g.saveCellsAtOnce) {
+ if (saved) {
+ g.postEditedCell();
+ } else {
+ g.hideEditCell(true);
+ }
+ } else {
+ if (saved) {
+ g.hideEditCell(true, true);
+ } else {
+ g.hideEditCell(true);
+ }
+ }
+ },
+
+ /**
+ * Initialize column resize feature.
+ */
+ initColResize: function () {
+ // create column resizer div
+ g.cRsz = document.createElement('div');
+ g.cRsz.className = 'cRsz';
+
+ // get data columns in the first row of the table
+ var $firstRowCols = $(g.t).find('tr:first th.draggable');
+
+ // create column borders
+ $firstRowCols.each(function () {
+ var cb = document.createElement('div'); // column border
+ $(cb).addClass('colborder')
+ .mousedown(function (e) {
+ g.dragStartRsz(e, this);
+ });
+ $(g.cRsz).append(cb);
+ });
+ g.reposRsz();
+
+ // attach to global div
+ $(g.gDiv).prepend(g.cRsz);
+ },
+
+ /**
+ * Initialize column reordering feature.
+ */
+ initColReorder: function () {
+ g.cCpy = document.createElement('div'); // column copy, to store copy of dragged column header
+ g.cPointer = document.createElement('div'); // column pointer, used when reordering column
+
+ // adjust g.cCpy
+ g.cCpy.className = 'cCpy';
+ $(g.cCpy).hide();
+
+ // adjust g.cPointer
+ g.cPointer.className = 'cPointer';
+ $(g.cPointer).css('visibility', 'hidden'); // set visibility to hidden instead of calling hide() to force browsers to cache the image in cPointer class
+
+ // assign column reordering hint
+ g.reorderHint = PMA_messages.strColOrderHint;
+
+ // get data columns in the first row of the table
+ var $firstRowCols = $(g.t).find('tr:first th.draggable');
+
+ // initialize column order
+ $col_order = $('#col_order'); // check if column order is passed from PHP
+ if ($col_order.length > 0) {
+ g.colOrder = $col_order.val().split(',');
+ for (var i = 0; i < g.colOrder.length; i++) {
+ g.colOrder[i] = parseInt(g.colOrder[i], 10);
+ }
+ } else {
+ g.colOrder = [];
+ for (var i = 0; i < $firstRowCols.length; i++) {
+ g.colOrder.push(i);
+ }
+ }
+
+ // register events
+ $(t).find('th.draggable')
+ .mousedown(function (e) {
+ $('#sqlqueryresults').addClass("turnOffSelect");
+ if (g.visibleHeadersCount > 1) {
+ g.dragStartReorder(e, this);
+ }
+ })
+ .mouseenter(function (e) {
+ if (g.visibleHeadersCount > 1) {
+ $(this).css('cursor', 'move');
+ } else {
+ $(this).css('cursor', 'inherit');
+ }
+ })
+ .mouseleave(function (e) {
+ g.showReorderHint = false;
+ $(this).tooltip("option", {
+ content: g.updateHint()
+ });
+ })
+ .dblclick(function (e) {
+ e.preventDefault();
+ $("<div/>")
+ .prop("title", PMA_messages.strColNameCopyTitle)
+ .addClass("modal-copy")
+ .text(PMA_messages.strColNameCopyText)
+ .append(
+ $("<input/>")
+ .prop("readonly", true)
+ .val($(this).data("column"))
+ )
+ .dialog({
+ resizable: false,
+ modal: true
+ })
+ .find("input").focus().select();
+ });
+ // restore column order when the restore button is clicked
+ $('div.restore_column').click(function () {
+ g.restoreColOrder();
+ });
+
+ // attach to global div
+ $(g.gDiv).append(g.cPointer);
+ $(g.gDiv).append(g.cCpy);
+
+ // prevent default "dragstart" event when dragging a link
+ $(t).find('th a').bind('dragstart', function () {
+ return false;
+ });
+
+ // refresh the restore column button state
+ g.refreshRestoreButton();
+ },
+
+ /**
+ * Initialize column visibility feature.
+ */
+ initColVisib: function () {
+ g.cDrop = document.createElement('div'); // column drop-down arrows
+ g.cList = document.createElement('div'); // column visibility list
+
+ // adjust g.cDrop
+ g.cDrop.className = 'cDrop';
+
+ // adjust g.cList
+ g.cList.className = 'cList';
+ $(g.cList).hide();
+
+ // assign column visibility related hints
+ g.showAllColText = PMA_messages.strShowAllCol;
+
+ // get data columns in the first row of the table
+ var $firstRowCols = $(g.t).find('tr:first th.draggable');
+
+ // initialize column visibility
+ var $col_visib = $('#col_visib'); // check if column visibility is passed from PHP
+ if ($col_visib.length > 0) {
+ g.colVisib = $col_visib.val().split(',');
+ for (var i = 0; i < g.colVisib.length; i++) {
+ g.colVisib[i] = parseInt(g.colVisib[i], 10);
+ }
+ } else {
+ g.colVisib = [];
+ for (var i = 0; i < $firstRowCols.length; i++) {
+ g.colVisib.push(1);
+ }
+ }
+
+ // make sure we have more than one column
+ if ($firstRowCols.length > 1) {
+ var $colVisibTh = $(g.t).find('th:not(.draggable)');
+ PMA_tooltip(
+ $colVisibTh,
+ 'th',
+ PMA_messages.strColVisibHint
+ );
+
+ // create column visibility drop-down arrow(s)
+ $colVisibTh.each(function () {
+ var $th = $(this);
+ var cd = document.createElement('div'); // column drop-down arrow
+ var pos = $th.position();
+ $(cd).addClass('coldrop')
+ .click(function () {
+ if (g.cList.style.display == 'none') {
+ g.showColList(this);
+ } else {
+ g.hideColList();
+ }
+ });
+ $(g.cDrop).append(cd);
+ });
+
+ // add column visibility control
+ g.cList.innerHTML = '<div class="lDiv"></div>';
+ var $listDiv = $(g.cList).find('div');
+ for (var i = 0; i < $firstRowCols.length; i++) {
+ var currHeader = $firstRowCols[i];
+ var listElmt = document.createElement('div');
+ $(listElmt).text($(currHeader).text())
+ .prepend('<input type="checkbox" ' + (g.colVisib[i] ? 'checked="checked" ' : '') + '/>');
+ $listDiv.append(listElmt);
+ // add event on click
+ $(listElmt).click(function () {
+ if (g.toggleCol($(this).index())) {
+ g.afterToggleCol();
+ }
+ });
+ }
+ // add "show all column" button
+ var showAll = document.createElement('div');
+ $(showAll).addClass('showAllColBtn')
+ .text(g.showAllColText);
+ $(g.cList).append(showAll);
+ $(showAll).click(function () {
+ g.showAllColumns();
+ });
+ // prepend "show all column" button at top if the list is too long
+ if ($firstRowCols.length > 10) {
+ var clone = showAll.cloneNode(true);
+ $(g.cList).prepend(clone);
+ $(clone).click(function () {
+ g.showAllColumns();
+ });
+ }
+ }
+
+ // hide column visibility list if we move outside the list
+ $(t).find('td, th.draggable').mouseenter(function () {
+ g.hideColList();
+ });
+
+ // attach to global div
+ $(g.gDiv).append(g.cDrop);
+ $(g.gDiv).append(g.cList);
+
+ // some adjustment
+ g.reposDrop();
+ },
+
+ /**
+ * Initialize grid editing feature.
+ */
+ initGridEdit: function () {
+
+ function startGridEditing(e, cell) {
+ if (g.isCellEditActive) {
+ g.saveOrPostEditedCell();
+ } else {
+ g.showEditCell(cell);
+ }
+ e.stopPropagation();
+ }
+
+ // create cell edit wrapper element
+ g.cEdit = document.createElement('div');
+
+ // adjust g.cEdit
+ g.cEdit.className = 'cEdit';
+ $(g.cEdit).html('<textarea class="edit_box" rows="1" ></textarea><div class="edit_area" />');
+ $(g.cEdit).hide();
+
+ // assign cell editing hint
+ g.cellEditHint = PMA_messages.strCellEditHint;
+ g.saveCellWarning = PMA_messages.strSaveCellWarning;
+ g.alertNonUnique = PMA_messages.strAlertNonUnique;
+ g.gotoLinkText = PMA_messages.strGoToLink;
+ g.showDataRowLinkText = PMA_messages.strShowDataRowLink;
+
+ // initialize cell editing configuration
+ g.saveCellsAtOnce = $('#save_cells_at_once').val();
+
+ // register events
+ $(t).find('td.data.click1')
+ .click(function (e) {
+ startGridEditing(e, this);
+ // prevent default action when clicking on "link" in a table
+ if ($(e.target).is('.grid_edit a')) {
+ e.preventDefault();
+ }
+ });
+
+ $(t).find('td.data.click2')
+ .click(function (e) {
+ $cell = $(this);
+ // In the case of relational link, We want single click on the link
+ // to goto the link and double click to start grid-editing.
+ var $link = $(e.target);
+ if ($link.is('.grid_edit.relation a')) {
+ e.preventDefault();
+ // get the click count and increase
+ var clicks = $cell.data('clicks');
+ clicks = (typeof clicks === 'undefined') ? 1 : clicks + 1;
+
+ if (clicks == 1) {
+ // if there are no previous clicks,
+ // start the single click timer
+ timer = setTimeout(function () {
+ // temporarily remove ajax class so the page loader will not handle it,
+ // submit and then add it back
+ $link.removeClass('ajax');
+ AJAX.requestHandler.call($link[0]);
+ $link.addClass('ajax');
+ $cell.data('clicks', 0);
+ }, 700);
+ $cell.data('clicks', clicks);
+ $cell.data('timer', timer);
+ } else {
+ // this is a double click, cancel the single click timer
+ // and make the click count 0
+ clearTimeout($cell.data('timer'));
+ $cell.data('clicks', 0);
+ // start grid-editing
+ startGridEditing(e, this);
+ }
+ }
+ })
+ .dblclick(function (e) {
+ if ($(e.target).is('.grid_edit a')) {
+ e.preventDefault();
+ } else {
+ startGridEditing(e, this);
+ }
+ });
+
+ $(g.cEdit).find('.edit_box').focus(function (e) {
+ g.showEditArea();
+ });
+ $(g.cEdit).find('.edit_box, select').live('keydown', function (e) {
+ if (e.which == 13) {
+ // post on pressing "Enter"
+ e.preventDefault();
+ g.saveOrPostEditedCell();
+ }
+ });
+ $(g.cEdit).keydown(function (e) {
+ if (!g.isEditCellTextEditable) {
+ // prevent text editing
+ e.preventDefault();
+ }
+ });
+ $('html').click(function (e) {
+ // hide edit cell if the click is not from g.cEdit
+ if ($(e.target).parents().index(g.cEdit) == -1) {
+ g.hideEditCell();
+ }
+ }).keydown(function (e) {
+ if (e.which == 27 && g.isCellEditActive) {
+
+ // cancel on pressing "Esc"
+ g.hideEditCell(true);
+ }
+ });
+ $('div.save_edited').click(function () {
+ g.hideEditCell();
+ g.postEditedCell();
+ });
+ $(window).bind('beforeunload', function (e) {
+ if (g.isCellEdited) {
+ return g.saveCellWarning;
+ }
+ });
+
+ // attach to global div
+ $(g.gDiv).append(g.cEdit);
+
+ // add hint for grid editing feature when hovering "Edit" link in each table row
+ if (PMA_messages.strGridEditFeatureHint !== undefined) {
+ PMA_tooltip(
+ $(g.t).find('.edit_row_anchor a'),
+ 'a',
+ PMA_messages.strGridEditFeatureHint
+ );
+ }
+ }
+ };
+
+ /******************
+ * Initialize grid
+ ******************/
+
+ // wrap all data cells, except actions cell, with span
+ $(t).find('th, td:not(:has(span))')
+ .wrapInner('<span />');
+
+ // create grid elements
+ g.gDiv = document.createElement('div'); // create global div
+
+ // initialize the table variable
+ g.t = t;
+
+ // get data columns in the first row of the table
+ var $firstRowCols = $(t).find('tr:first th.draggable');
+
+ // initialize visible headers count
+ g.visibleHeadersCount = $firstRowCols.filter(':visible').length;
+
+ // assign first column (actions) span
+ if (! $(t).find('tr:first th:first').hasClass('draggable')) { // action header exist
+ g.actionSpan = $(t).find('tr:first th:first').prop('colspan');
+ } else {
+ g.actionSpan = 0;
+ }
+
+ // assign table create time
+ // #table_create_time will only available if we are in "Browse" tab
+ g.tableCreateTime = $('#table_create_time').val();
+
+ // assign the hints
+ g.sortHint = PMA_messages.strSortHint;
+ g.markHint = PMA_messages.strColMarkHint;
+ g.copyHint = PMA_messages.strColNameCopyHint;
+
+ // assign common hidden inputs
+ var $common_hidden_inputs = $('div.common_hidden_inputs');
+ g.token = $common_hidden_inputs.find('input[name=token]').val();
+ g.server = $common_hidden_inputs.find('input[name=server]').val();
+ g.db = $common_hidden_inputs.find('input[name=db]').val();
+ g.table = $common_hidden_inputs.find('input[name=table]').val();
+
+ // add table class
+ $(t).addClass('pma_table');
+
+ // add relative position to global div so that resize handlers are correctly positioned
+ $(g.gDiv).css('position', 'relative');
+
+ // link the global div
+ $(t).before(g.gDiv);
+ $(g.gDiv).append(t);
+
+ // FEATURES
+ enableResize = enableResize === undefined ? true : enableResize;
+ enableReorder = enableReorder === undefined ? true : enableReorder;
+ enableVisib = enableVisib === undefined ? true : enableVisib;
+ enableGridEdit = enableGridEdit === undefined ? true : enableGridEdit;
+ if (enableResize) {
+ g.initColResize();
+ }
+ if (enableReorder &&
+ $('table.navigation').length > 0) // disable reordering for result from EXPLAIN or SHOW syntax, which do not have a table navigation panel
+ {
+ g.initColReorder();
+ }
+ if (enableVisib) {
+ g.initColVisib();
+ }
+ if (enableGridEdit &&
+ $(t).is('.ajax')) // make sure we have the ajax class
+ {
+ g.initGridEdit();
+ }
+
+ // create tooltip for each <th> with draggable class
+ PMA_tooltip(
+ $(t).find("th.draggable"),
+ 'th',
+ g.updateHint()
+ );
+
+ // register events for hint tooltip (anchors inside draggable th)
+ $(t).find('th.draggable a')
+ .mouseenter(function (e) {
+ g.showSortHint = true;
+ $(t).find("th.draggable").tooltip("option", {
+ content: g.updateHint()
+ });
+ })
+ .mouseleave(function (e) {
+ g.showSortHint = false;
+ $(t).find("th.draggable").tooltip("option", {
+ content: g.updateHint()
+ });
+ });
+
+ // register events for dragging-related feature
+ if (enableResize || enableReorder) {
+ $(document).mousemove(function (e) {
+ g.dragMove(e);
+ });
+ $(document).mouseup(function (e) {
+ $('#sqlqueryresults').removeClass("turnOffSelect");
+ g.dragEnd(e);
+ });
+ }
+
+ // some adjustment
+ $(t).removeClass('data');
+ $(g.gDiv).addClass('data');
+}
diff --git a/js/messages.php b/js/messages.php
new file mode 100644
index 0000000000..f816b838a1
--- /dev/null
+++ b/js/messages.php
@@ -0,0 +1,576 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Exporting of translated messages from PHP to Javascript
+ *
+ * @package PhpMyAdmin
+ */
+
+chdir('..');
+
+// Send correct type:
+header('Content-Type: text/javascript; charset=UTF-8');
+
+// Cache output in client - the nocache query parameter makes sure that this
+// file is reloaded when config changes
+header('Expires: ' . gmdate('D, d M Y H:i:s', time() + 3600) . ' GMT');
+
+// Avoid loading the full common.inc.php because this would add many
+// non-js-compatible stuff like DOCTYPE
+define('PMA_MINIMUM_COMMON', true);
+define('PMA_PATH_TO_BASEDIR', '../');
+require_once './libraries/common.inc.php';
+// Close session early as we won't write anything there
+session_write_close();
+// But this one is needed for PMA_escapeJsString()
+require_once './libraries/js_escape.lib.php';
+require_once './libraries/Util.class.php';
+
+$js_messages['strNoDropDatabases'] = __('"DROP DATABASE" statements are disabled.');
+if ($cfg['AllowUserDropDatabase']) {
+ $js_messages['strNoDropDatabases'] = '';
+}
+
+/* For confirmations */
+$js_messages['strConfirm'] = __('Confirm');
+$js_messages['strDoYouReally'] = __('Do you really want to execute "%s"?');
+$js_messages['strDropDatabaseStrongWarning'] = __('You are about to DESTROY a complete database!');
+$js_messages['strDropTableStrongWarning'] = __('You are about to DESTROY a complete table!');
+$js_messages['strTruncateTableStrongWarning'] = __('You are about to TRUNCATE a complete table!');
+$js_messages['strDeleteTrackingData'] = __('Delete tracking data for this table');
+$js_messages['strDeletingTrackingData'] = __('Deleting tracking data');
+$js_messages['strDroppingPrimaryKeyIndex'] = __('Dropping Primary Key/Index');
+$js_messages['strOperationTakesLongTime'] = __('This operation could take a long time. Proceed anyway?');
+$js_messages['strDropUserGroupWarning'] = __('Do you really want to delete user group "%s"?');
+
+/* For indexes */
+$js_messages['strFormEmpty'] = __('Missing value in the form!');
+$js_messages['strEnterValidNumber'] = __('Please enter a valid number');
+$js_messages['strEnterValidLength'] = __('Please enter a valid length');
+$js_messages['strAddIndex'] = __('Add Index');
+$js_messages['strEditIndex'] = __('Edit Index');
+$js_messages['strAddToIndex'] = __('Add %s column(s) to index');
+
+/* Charts */
+/* l10n: Default label for the y-Axis of Charts */
+$js_messages['strYValues'] = __('Y Values');
+
+/* For server_privileges.js */
+$js_messages['strHostEmpty'] = __('The host name is empty!');
+$js_messages['strUserEmpty'] = __('The user name is empty!');
+$js_messages['strPasswordEmpty'] = __('The password is empty!');
+$js_messages['strPasswordNotSame'] = __('The passwords aren\'t the same!');
+$js_messages['strAddUser'] = __('Add user');
+$js_messages['strReloadingPrivileges'] = __('Reloading Privileges');
+$js_messages['strRemovingSelectedUsers'] = __('Removing Selected Users');
+$js_messages['strClose'] = __('Close');
+
+/* l10n: Other, small valued, queries */
+$js_messages['strOther'] = __('Other');
+/* l10n: Thousands separator */
+$js_messages['strThousandsSeparator'] = __(',');
+/* l10n: Decimal separator */
+$js_messages['strDecimalSeparator'] = __('.');
+
+$js_messages['strChartConnectionsTitle'] = __('Connections / Processes');
+
+/* server status monitor */
+$js_messages['strIncompatibleMonitorConfig'] = __('Local monitor configuration incompatible');
+$js_messages['strIncompatibleMonitorConfigDescription'] = __('The chart arrangement configuration in your browsers local storage is not compatible anymore to the newer version of the monitor dialog. It is very likely that your current configuration will not work anymore. Please reset your configuration to default in the <i>Settings</i> menu.');
+
+$js_messages['strQueryCacheEfficiency'] = __('Query cache efficiency');
+$js_messages['strQueryCacheUsage'] = __('Query cache usage');
+$js_messages['strQueryCacheUsed'] = __('Query cache used');
+
+$js_messages['strSystemCPUUsage'] = __('System CPU Usage');
+$js_messages['strSystemMemory'] = __('System memory');
+$js_messages['strSystemSwap'] = __('System swap');
+
+$js_messages['strAverageLoad'] = __('Average load');
+$js_messages['strTotalMemory'] = __('Total memory');
+$js_messages['strCachedMemory'] = __('Cached memory');
+$js_messages['strBufferedMemory'] = __('Buffered memory');
+$js_messages['strFreeMemory'] = __('Free memory');
+$js_messages['strUsedMemory'] = __('Used memory');
+
+$js_messages['strTotalSwap'] = __('Total Swap');
+$js_messages['strCachedSwap'] = __('Cached Swap');
+$js_messages['strUsedSwap'] = __('Used Swap');
+$js_messages['strFreeSwap'] = __('Free Swap');
+
+$js_messages['strBytesSent'] = __('Bytes sent');
+$js_messages['strBytesReceived'] = __('Bytes received');
+$js_messages['strConnections'] = __('Connections');
+$js_messages['strProcesses'] = __('Processes');
+
+/* summary row */
+$js_messages['strB'] = __('B');
+$js_messages['strKiB'] = __('KiB');
+$js_messages['strMiB'] = __('MiB');
+$js_messages['strGiB'] = __('GiB');
+$js_messages['strTiB'] = __('TiB');
+$js_messages['strPiB'] = __('PiB');
+$js_messages['strEiB'] = __('EiB');
+$js_messages['strTables'] = __('%d table(s)');
+
+/* l10n: Questions is the name of a MySQL Status variable */
+$js_messages['strQuestions'] = __('Questions');
+$js_messages['strTraffic'] = __('Traffic');
+$js_messages['strSettings'] = __('Settings');
+$js_messages['strRemoveChart'] = __('Remove chart');
+$js_messages['strEditChart'] = __('Edit title and labels');
+$js_messages['strAddChart'] = __('Add chart to grid');
+$js_messages['strClose'] = __('Close');
+$js_messages['strAddOneSeriesWarning'] = __('Please add at least one variable to the series');
+$js_messages['strNone'] = __('None');
+$js_messages['strResumeMonitor'] = __('Resume monitor');
+$js_messages['strPauseMonitor'] = __('Pause monitor');
+/* Monitor: Instructions Dialog */
+$js_messages['strBothLogOn'] = __('general_log and slow_query_log are enabled.');
+$js_messages['strGenLogOn'] = __('general_log is enabled.');
+$js_messages['strSlowLogOn'] = __('slow_query_log is enabled.');
+$js_messages['strBothLogOff'] = __('slow_query_log and general_log are disabled.');
+$js_messages['strLogOutNotTable'] = __('log_output is not set to TABLE.');
+$js_messages['strLogOutIsTable'] = __('log_output is set to TABLE.');
+$js_messages['strSmallerLongQueryTimeAdvice'] = __('slow_query_log is enabled, but the server logs only queries that take longer than %d seconds. It is advisable to set this long_query_time 0-2 seconds, depending on your system.');
+$js_messages['strLongQueryTimeSet'] = __('long_query_time is set to %d second(s).');
+$js_messages['strSettingsAppliedGlobal'] = __('Following settings will be applied globally and reset to default on server restart:');
+/* l10n: %s is FILE or TABLE */
+$js_messages['strSetLogOutput'] = __('Set log_output to %s');
+/* l10n: Enable in this context means setting a status variable to ON */
+$js_messages['strEnableVar'] = __('Enable %s');
+/* l10n: Disable in this context means setting a status variable to OFF */
+$js_messages['strDisableVar'] = __('Disable %s');
+/* l10n: %d seconds */
+$js_messages['setSetLongQueryTime'] = __('Set long_query_time to %ds');
+$js_messages['strNoSuperUser'] = __(
+ 'You can\'t change these variables. Please log in as root or contact'
+ . ' your database administrator.'
+);
+$js_messages['strChangeSettings'] = __('Change settings');
+$js_messages['strCurrentSettings'] = __('Current settings');
+
+$js_messages['strChartTitle'] = __('Chart Title');
+/* l10n: As in differential values */
+$js_messages['strDifferential'] = __('Differential');
+$js_messages['strDividedBy'] = __('Divided by %s');
+$js_messages['strUnit'] = __('Unit');
+
+$js_messages['strFromSlowLog'] = __('From slow log');
+$js_messages['strFromGeneralLog'] = __('From general log');
+$js_messages['strServerLogError'] = __(
+ 'The database name is not known for this query in the server\'s logs.'
+);
+$js_messages['strAnalysingLogsTitle'] = __('Analysing logs');
+$js_messages['strAnalysingLogs'] = __('Analysing & loading logs. This may take a while.');
+$js_messages['strCancelRequest'] = __('Cancel request');
+$js_messages['strCountColumnExplanation'] = __('This column shows the amount of identical queries that are grouped together. However only the SQL query itself has been used as a grouping criteria, so the other attributes of queries, such as start time, may differ.');
+$js_messages['strMoreCountColumnExplanation'] = __('Since grouping of INSERTs queries has been selected, INSERT queries into the same table are also being grouped together, disregarding of the inserted data.');
+$js_messages['strLogDataLoaded'] = __('Log data loaded. Queries executed in this time span:');
+
+$js_messages['strJumpToTable'] = __('Jump to Log table');
+$js_messages['strNoDataFoundTitle'] = __('No data found');
+$js_messages['strNoDataFound'] = __('Log analysed, but no data found in this time span.');
+
+$js_messages['strAnalyzing'] = __('Analyzing…');
+$js_messages['strExplainOutput'] = __('Explain output');
+$js_messages['strStatus'] = __('Status');
+$js_messages['strTime'] = __('Time');
+$js_messages['strTotalTime'] = __('Total time:');
+$js_messages['strProfilingResults'] = __('Profiling results');
+$js_messages['strTable'] = _pgettext('Display format', 'Table');
+$js_messages['strChart'] = __('Chart');
+$js_messages['strChartEdit'] = __('Edit chart');
+$js_messages['strSeries'] = __('Series');
+
+/* l10n: A collection of available filters */
+$js_messages['strFiltersForLogTable'] = __('Log table filter options');
+/* l10n: Filter as in "Start Filtering" */
+$js_messages['strFilter'] = __('Filter');
+$js_messages['strFilterByWordRegexp'] = __('Filter queries by word/regexp:');
+$js_messages['strIgnoreWhereAndGroup'] = __('Group queries, ignoring variable data in WHERE clauses');
+$js_messages['strSumRows'] = __('Sum of grouped rows:');
+$js_messages['strTotal'] = __('Total:');
+
+$js_messages['strLoadingLogs'] = __('Loading logs');
+$js_messages['strRefreshFailed'] = __('Monitor refresh failed');
+$js_messages['strInvalidResponseExplanation'] = __('While requesting new chart data the server returned an invalid response. This is most likely because your session expired. Reloading the page and reentering your credentials should help.');
+$js_messages['strReloadPage'] = __('Reload page');
+
+$js_messages['strAffectedRows'] = __('Affected rows:');
+
+$js_messages['strFailedParsingConfig'] = __(
+ 'Failed parsing config file. It doesn\'t seem to be valid JSON code.'
+);
+$js_messages['strFailedBuildingGrid'] = __('Failed building chart grid with imported config. Resetting to default config…');
+$js_messages['strImport'] = __('Import');
+$js_messages['strImportDialogTitle'] = __('Import monitor configuration');
+$js_messages['strImportDialogMessage'] = __('Please select the file you want to import');
+
+$js_messages['strAnalyzeQuery'] = __('Analyse Query');
+
+/* Server status advisor */
+
+$js_messages['strAdvisorSystem'] = __('Advisor system');
+$js_messages['strPerformanceIssues'] = __('Possible performance issues');
+$js_messages['strIssuse'] = __('Issue');
+$js_messages['strRecommendation'] = __('Recommendation');
+$js_messages['strRuleDetails'] = __('Rule details');
+$js_messages['strJustification'] = __('Justification');
+$js_messages['strFormula'] = __('Used variable / formula');
+$js_messages['strTest'] = __('Test');
+
+
+/* For inline query editing */
+$js_messages['strGo'] = __('Go');
+$js_messages['strCancel'] = __('Cancel');
+
+/* For Ajax Notifications */
+$js_messages['strLoading'] = __('Loading…');
+$js_messages['strAbortedRequest'] = __('Request Aborted!!');
+$js_messages['strProcessingRequest'] = __('Processing Request');
+$js_messages['strErrorProcessingRequest'] = __('Error in Processing Request');
+$js_messages['strErrorCode'] = __('Error code: %s');
+$js_messages['strErrorText'] = __('Error text: %s');
+$js_messages['strNoDatabasesSelected'] = __('No databases selected.');
+$js_messages['strDroppingColumn'] = __('Dropping Column');
+$js_messages['strAddingPrimaryKey'] = __('Adding Primary Key');
+$js_messages['strOK'] = __('OK');
+$js_messages['strDismiss'] = __('Click to dismiss this notification');
+
+/* For db_operations.js */
+$js_messages['strRenamingDatabases'] = __('Renaming Databases');
+$js_messages['strReloadDatabase'] = __('Reload Database');
+$js_messages['strCopyingDatabase'] = __('Copying Database');
+$js_messages['strChangingCharset'] = __('Changing Charset');
+$js_messages['strTableMustHaveAtleastOneColumn'] = __(
+ 'Table must have at least one column'
+);
+$js_messages['strYes'] = __('Yes');
+$js_messages['strNo'] = __('No');
+
+/* For db_stucture.js */
+$js_messages['strInsertTable'] = __('Insert Table');
+$js_messages['strHideIndexes'] = __('Hide indexes');
+$js_messages['strShowIndexes'] = __('Show indexes');
+$js_messages['strForeignKeyCheck'] = __('Foreign key check:');
+$js_messages['strForeignKeyCheckEnabled'] = __('(Enabled)');
+$js_messages['strForeignKeyCheckDisabled'] = __('(Disabled)');
+
+/* For db_search.js */
+$js_messages['strSearching'] = __('Searching');
+$js_messages['strHideSearchResults'] = __('Hide search results');
+$js_messages['strShowSearchResults'] = __('Show search results');
+$js_messages['strBrowsing'] = __('Browsing');
+$js_messages['strDeleting'] = __('Deleting');
+
+/* For db_routines.js */
+$js_messages['MissingReturn'] = __('The definition of a stored function must contain a RETURN statement!');
+
+/* For ENUM/SET editor*/
+$js_messages['enum_editor'] = __('ENUM/SET editor');
+$js_messages['enum_columnVals'] =__('Values for column %s');
+$js_messages['enum_newColumnVals'] = __('Values for a new column');
+$js_messages['enum_hint'] =__('Enter each value in a separate field');
+$js_messages['enum_addValue'] =__('Add %d value(s)');
+
+/* For import.js */
+$js_messages['strImportCSV'] = __('Note: If the file contains multiple tables, they will be combined into one.');
+
+/* For sql.js */
+$js_messages['strHideQueryBox'] = __('Hide query box');
+$js_messages['strShowQueryBox'] = __('Show query box');
+$js_messages['strEdit'] = __('Edit');
+$js_messages['strNoRowSelected'] = __('No rows selected');
+$js_messages['strChangeTbl'] = __('Change');
+$js_messages['strQueryExecutionTime'] = __('Query execution time');
+$js_messages['strNotValidRowNumber'] = __('%d is not valid row number.');
+
+/* For server_variables.js */
+$js_messages['strSave'] = __('Save');
+
+/* For tbl_select.js */
+$js_messages['strHideSearchCriteria'] = __('Hide search criteria');
+$js_messages['strShowSearchCriteria'] = __('Show search criteria');
+
+/* For tbl_find_replace.js */
+$js_messages['strHideFindNReplaceCriteria'] = __('Hide find and replace criteria');
+$js_messages['strShowFindNReplaceCriteria'] = __('Show find and replace criteria');
+
+/* For tbl_zoom_plot_jqplot.js */
+$js_messages['strZoomSearch'] = __('Zoom Search');
+$js_messages['strDisplayHelp'] = '<ul><li>'
+ . __('Each point represents a data row.')
+ . '</li><li>'
+ . __('Hovering over a point will show its label.')
+ . '</li><li>'
+ . __('To zoom in, select a section of the plot with the mouse.')
+ . '</li><li>'
+ . __('Click reset zoom button to come back to original state.')
+ . '</li><li>'
+ . __('Click a data point to view and possibly edit the data row.')
+ . '</li><li>'
+ . __('The plot can be resized by dragging it along the bottom right corner.')
+ . '</li></ul>';
+$js_messages['strInputNull'] = '<strong>' . __('Select two columns') . '</strong>';
+$js_messages['strSameInputs'] = '<strong>'
+ . __('Select two different columns')
+ . '</strong>';
+$js_messages['strQueryResults'] = __('Query results');
+$js_messages['strDataPointContent'] = __('Data point content');
+
+/* For tbl_change.js */
+$js_messages['strIgnore'] = __('Ignore');
+$js_messages['strCopy'] = __('Copy');
+$js_messages['strX'] = __('X');
+$js_messages['strY'] = __('Y');
+$js_messages['strPoint'] = __('Point');
+$js_messages['strPointN'] = __('Point %d');
+$js_messages['strLineString'] = __('Linestring');
+$js_messages['strPolygon'] = __('Polygon');
+$js_messages['strGeometry'] = __('Geometry');
+$js_messages['strInnerRing'] = __('Inner Ring');
+$js_messages['strOuterRing'] = __('Outer Ring');
+$js_messages['strAddPoint'] = __('Add a point');
+$js_messages['strAddInnerRing'] = __('Add an inner ring');
+$js_messages['strAddPolygon'] = __('Add a polygon');
+
+/* For tbl_structure.js */
+$js_messages['strAddColumns'] = __('Add columns');
+
+/* Designer (js/pmd/move.js) */
+$js_messages['strSelectReferencedKey'] = __('Select referenced key');
+$js_messages['strSelectForeignKey'] = __('Select Foreign Key');
+$js_messages['strPleaseSelectPrimaryOrUniqueKey'] = __('Please select the primary key or a unique key');
+$js_messages['strChangeDisplay'] = __('Choose column to display');
+$js_messages['strLeavingDesigner'] = __(
+ 'You haven\'t saved the changes in the layout. They will be lost if you'
+ . ' don\'t save them. Do you want to continue?'
+);
+
+/* Visual query builder (js/pmd/move.js) */
+$js_messages['strAddOption'] = __('Add an option for column ');
+$js_messages['strObjectsCreated'] = __('%d object(s) created');
+
+/* For makegrid.js (column reordering, show/hide column, grid editing) */
+$js_messages['strCellEditHint'] = __('Press escape to cancel editing');
+$js_messages['strSaveCellWarning'] = __('You have edited some data and they have not been saved. Are you sure you want to leave this page before saving the data?');
+$js_messages['strColOrderHint'] = __('Drag to reorder');
+$js_messages['strSortHint'] = __('Click to sort');
+$js_messages['strColMarkHint'] = __('Click to mark/unmark');
+$js_messages['strColNameCopyHint'] = __('Double-click to copy column name');
+$js_messages['strColVisibHint'] = __(
+ 'Click the drop-down arrow<br />to toggle column\'s visibility'
+);
+$js_messages['strShowAllCol'] = __('Show all');
+$js_messages['strAlertNonUnique'] = __('This table does not contain a unique column. Features related to the grid edit, checkbox, Edit, Copy and Delete links may not work after saving.');
+
+// this approach does not work when the parameter is changed via user prefs
+switch ($GLOBALS['cfg']['GridEditing']) {
+case 'double-click':
+ $js_messages['strGridEditFeatureHint'] = __('You can also edit most values<br />by double-clicking directly on them.');
+ break;
+case 'click':
+ $js_messages['strGridEditFeatureHint'] = __('You can also edit most values<br />by clicking directly on them.');
+ break;
+default:
+ break;
+}
+$js_messages['strGoToLink'] = __('Go to link');
+$js_messages['strColNameCopyTitle'] = __('Copy column name');
+$js_messages['strColNameCopyText'] = __('Right-click the column name to copy it to your clipboard.');
+$js_messages['strShowDataRowLink'] = __('Show data row(s)');
+
+/* password generation */
+$js_messages['strGeneratePassword'] = __('Generate password');
+$js_messages['strGenerate'] = __('Generate');
+$js_messages['strChangePassword'] = __('Change Password');
+
+/* navigation tabs */
+$js_messages['strMore'] = __('More');
+
+/* navigation panel */
+$js_messages['strShowPanel'] = __('Show Panel');
+$js_messages['strHidePanel'] = __('Hide Panel');
+$js_messages['strUnhideNavItem'] = __('Show hidden navigation tree items');
+
+/* microhistory */
+$js_messages['strInvalidPage'] = __('The requested page was not found in the history, it may have expired.');
+
+/* update */
+$js_messages['strNewerVersion'] = __('A newer version of phpMyAdmin is available and you should consider upgrading. The newest version is %s, released on %s.');
+/* l10n: Latest available phpMyAdmin version */
+$js_messages['strLatestAvailable'] = __(', latest stable version:');
+$js_messages['strUpToDate'] = __('up to date');
+
+$js_messages['strCreateView'] = __('Create view');
+
+/* Error Reporting */
+$js_messages['strSendErrorReport'] = __("Send Error Report");
+$js_messages['strSubmitErrorReport'] = __("Submit Error Report");
+$js_messages['strErrorOccurred'] = __(
+ "A fatal JavaScript error has occurred. Would you like to send an error report?"
+);
+$js_messages['strChangeReportSettings'] = __("Change Report Settings");
+$js_messages['strShowReportDetails'] = __("Show Report Details");
+$js_messages['strIgnore'] = __("Ignore");
+$js_messages['strTimeOutError'] = __(
+ "Your export is incomplete, due to a low execution time limit at the PHP level"
+);
+echo "var PMA_messages = new Array();\n";
+foreach ($js_messages as $name => $js_message) {
+ PMA_printJsValue("PMA_messages['" . $name . "']", $js_message);
+}
+
+/* Calendar */
+echo "var themeCalendarImage = '" . $GLOBALS['pmaThemeImage']
+ . 'b_calendar.png' . "';\n";
+
+/* Image path */
+echo "var pmaThemeImage = '" . $GLOBALS['pmaThemeImage'] . "';\n";
+
+/* Version */
+echo "var pmaversion = '" . PMA_VERSION . "';\n";
+
+echo "var mysql_doc_template = '" . PMA_Util::getMySQLDocuURL('%s') . "';\n";
+
+echo "if ($.datepicker) {\n";
+/* l10n: Display text for calendar close link */
+PMA_printJsValue("$.datepicker.regional['']['closeText']", __('Done'));
+/* l10n: Display text for previous month link in calendar */
+PMA_printJsValue(
+ "$.datepicker.regional['']['prevText']",
+ _pgettext('Previous month', 'Prev')
+);
+/* l10n: Display text for next month link in calendar */
+PMA_printJsValue(
+ "$.datepicker.regional['']['nextText']",
+ _pgettext('Next month', 'Next')
+);
+/* l10n: Display text for current month link in calendar */
+PMA_printJsValue("$.datepicker.regional['']['currentText']", __('Today'));
+PMA_printJsValue(
+ "$.datepicker.regional['']['monthNames']",
+ array(
+ __('January'),
+ __('February'),
+ __('March'),
+ __('April'),
+ __('May'),
+ __('June'),
+ __('July'),
+ __('August'),
+ __('September'),
+ __('October'),
+ __('November'),
+ __('December')
+ )
+);
+PMA_printJsValue(
+ "$.datepicker.regional['']['monthNamesShort']",
+ array(
+/* l10n: Short month name */
+ __('Jan'),
+/* l10n: Short month name */
+ __('Feb'),
+/* l10n: Short month name */
+ __('Mar'),
+/* l10n: Short month name */
+ __('Apr'),
+/* l10n: Short month name */
+ _pgettext('Short month name', 'May'),
+/* l10n: Short month name */
+ __('Jun'),
+/* l10n: Short month name */
+ __('Jul'),
+/* l10n: Short month name */
+ __('Aug'),
+/* l10n: Short month name */
+ __('Sep'),
+/* l10n: Short month name */
+ __('Oct'),
+/* l10n: Short month name */
+ __('Nov'),
+/* l10n: Short month name */
+ __('Dec')
+ )
+);
+PMA_printJsValue(
+ "$.datepicker.regional['']['dayNames']",
+ array(
+ __('Sunday'),
+ __('Monday'),
+ __('Tuesday'),
+ __('Wednesday'),
+ __('Thursday'),
+ __('Friday'),
+ __('Saturday')
+ )
+);
+PMA_printJsValue(
+ "$.datepicker.regional['']['dayNamesShort']",
+ array(
+/* l10n: Short week day name */
+ __('Sun'),
+/* l10n: Short week day name */
+ __('Mon'),
+/* l10n: Short week day name */
+ __('Tue'),
+/* l10n: Short week day name */
+ __('Wed'),
+/* l10n: Short week day name */
+ __('Thu'),
+/* l10n: Short week day name */
+ __('Fri'),
+/* l10n: Short week day name */
+ __('Sat')
+ )
+);
+PMA_printJsValue(
+ "$.datepicker.regional['']['dayNamesMin']",
+ array(
+/* l10n: Minimal week day name */
+ __('Su'),
+/* l10n: Minimal week day name */
+ __('Mo'),
+/* l10n: Minimal week day name */
+ __('Tu'),
+/* l10n: Minimal week day name */
+ __('We'),
+/* l10n: Minimal week day name */
+ __('Th'),
+/* l10n: Minimal week day name */
+ __('Fr'),
+/* l10n: Minimal week day name */
+ __('Sa')
+ )
+);
+/* l10n: Column header for week of the year in calendar */
+PMA_printJsValue("$.datepicker.regional['']['weekHeader']", __('Wk'));
+
+/* l10n: Month-year order for calendar, use either "calendar-month-year"
+ * or "calendar-year-month".
+ */
+PMA_printJsValue(
+ "$.datepicker.regional['']['showMonthAfterYear']",
+ (__('calendar-month-year') == 'calendar-year-month')
+);
+/* l10n: Year suffix for calendar, "none" is empty. */
+$year_suffix = _pgettext('Year suffix', 'none');
+PMA_printJsValue(
+ "$.datepicker.regional['']['yearSuffix']",
+ ($year_suffix == 'none' ? '' : $year_suffix)
+);
+?>
+$.extend($.datepicker._defaults, $.datepicker.regional['']);
+} /* if ($.datepicker) */
+
+<?php
+echo "if ($.timepicker) {\n";
+PMA_printJsValue("$.timepicker.regional['']['timeText']", __('Time'));
+PMA_printJsValue("$.timepicker.regional['']['hourText']", __('Hour'));
+PMA_printJsValue("$.timepicker.regional['']['minuteText']", __('Minute'));
+PMA_printJsValue("$.timepicker.regional['']['secondText']", __('Second'));
+?>
+$.extend($.timepicker._defaults, $.timepicker.regional['']);
+} /* if ($.timepicker) */
diff --git a/js/navigation.js b/js/navigation.js
new file mode 100644
index 0000000000..d5605a09de
--- /dev/null
+++ b/js/navigation.js
@@ -0,0 +1,1207 @@
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * function used in or for navigation panel
+ *
+ * @package phpMyAdmin-Navigation
+ */
+
+/**
+ * Executed on page load
+ */
+$(function () {
+ if (! $('#pma_navigation').length) {
+ // Don't bother running any code if the navigation is not even on the page
+ return;
+ }
+
+ // Do not let the page reload on submitting the fast filter
+ $(document).on('submit', '.fast_filter', function (event) {
+ event.preventDefault();
+ });
+
+ // Fire up the resize handlers
+ new ResizeHandler();
+
+ /**
+ * opens/closes (hides/shows) tree elements
+ * loads data via ajax
+ */
+ $('#pma_navigation_tree a.expander').live('click', function (event) {
+ event.preventDefault();
+ event.stopImmediatePropagation();
+ var $icon = $(this).find('img');
+ if ($icon.is('.ic_b_plus')) {
+ expandTreeNode($(this));
+ } else {
+ collapseTreeNode($(this));
+ }
+ });
+
+ /**
+ * Register event handler for click on the reload
+ * navigation icon at the top of the panel
+ */
+ $('#pma_navigation_reload').live('click', function (event) {
+ event.preventDefault();
+ // reload icon object
+ var $icon = $(this).find('img');
+ // source of the hidden throbber icon
+ var icon_throbber_src = $('#pma_navigation .throbber').attr('src');
+ // source of the reload icon
+ var icon_reload_src = $icon.attr('src');
+ // replace the source of the reload icon with the one for throbber
+ $icon.attr('src', icon_throbber_src);
+ PMA_reloadNavigation();
+ // after one second, put back the reload icon
+ setTimeout(function () {
+ $icon.attr('src', icon_reload_src);
+ }, 1000);
+ });
+
+ /**
+ * Bind all "fast filter" events
+ */
+ $('#pma_navigation_tree li.fast_filter span')
+ .live('click', PMA_fastFilter.events.clear);
+ $('#pma_navigation_tree li.fast_filter input.searchClause')
+ .live('focus', PMA_fastFilter.events.focus)
+ .live('blur', PMA_fastFilter.events.blur)
+ .live('keyup', PMA_fastFilter.events.keyup);
+
+ /**
+ * Ajax handler for pagination
+ */
+ $('#pma_navigation_tree div.pageselector a.ajax').live('click', function (event) {
+ event.preventDefault();
+ PMA_navigationTreePagination($(this));
+ });
+
+ /**
+ * Node highlighting
+ */
+ $('#pma_navigation_tree.highlight li:not(.fast_filter)').live(
+ 'mouseover',
+ function () {
+ if ($('li:visible', this).length === 0) {
+ $(this).addClass('activePointer');
+ }
+ }
+ );
+ $('#pma_navigation_tree.highlight li:not(.fast_filter)').live(
+ 'mouseout',
+ function () {
+ $(this).removeClass('activePointer');
+ }
+ );
+
+ /**
+ * Jump to recent table
+ */
+ $('#recentTable').live('change', function () {
+ if (this.value !== '') {
+ var arr = jQuery.parseJSON(this.value);
+ var $form = $(this).closest('form');
+ $form.find('input[name=db]').val(arr.db);
+ $form.find('input[name=table]').val(arr.table);
+ $form.submit();
+ }
+ });
+
+ /** Create a Routine, Trigger or Event */
+ $('li.new_procedure a.ajax, li.new_function a.ajax').live('click', function (event) {
+ event.preventDefault();
+ var dialog = new RTE.object('routine');
+ dialog.editorDialog(1, $(this));
+ });
+ $('li.new_trigger a.ajax').live('click', function (event) {
+ event.preventDefault();
+ var dialog = new RTE.object('trigger');
+ dialog.editorDialog(1, $(this));
+ });
+ $('li.new_event a.ajax').live('click', function (event) {
+ event.preventDefault();
+ var dialog = new RTE.object('event');
+ dialog.editorDialog(1, $(this));
+ });
+
+ /** Edit Routines, Triggers and Events */
+ $('li.procedure > a.ajax, li.function > a.ajax').live('click', function (event) {
+ event.preventDefault();
+ var dialog = new RTE.object('routine');
+ dialog.editorDialog(0, $(this));
+ });
+ $('li.trigger > a.ajax').live('click', function (event) {
+ event.preventDefault();
+ var dialog = new RTE.object('trigger');
+ dialog.editorDialog(0, $(this));
+ });
+ $('li.event > a.ajax').live('click', function (event) {
+ event.preventDefault();
+ var dialog = new RTE.object('event');
+ dialog.editorDialog(0, $(this));
+ });
+
+ /** Export Routines, Triggers and Events */
+ $('li.procedure div:eq(1) a.ajax img,' +
+ ' li.function div:eq(1) a.ajax img,' +
+ ' li.trigger div:eq(1) a.ajax img,' +
+ ' li.event div:eq(1) a.ajax img'
+ ).live('click', function (event) {
+ event.preventDefault();
+ var dialog = new RTE.object();
+ dialog.exportDialog($(this).parent());
+ });
+
+ /** New index */
+ $('li.new_index a.ajax').live('click', function (event) {
+ event.preventDefault();
+ var url = $(this).attr('href').substr(
+ $(this).attr('href').indexOf('?') + 1
+ ) + '&ajax_request=true';
+ var title = PMA_messages.strAddIndex;
+ indexEditorDialog(url, title);
+ });
+
+ /** Edit index */
+ $('li.index a.ajax').live('click', function (event) {
+ event.preventDefault();
+ var url = $(this).attr('href').substr(
+ $(this).attr('href').indexOf('?') + 1
+ ) + '&ajax_request=true';
+ var title = PMA_messages.strEditIndex;
+ indexEditorDialog(url, title);
+ });
+
+ /** New view */
+ $('li.new_view a.ajax').live('click', function (event) {
+ event.preventDefault();
+ PMA_createViewDialog($(this));
+ });
+
+ /** Hide navigation tree item */
+ $('a.hideNavItem.ajax').live('click', function (event) {
+ event.preventDefault();
+ $.ajax({
+ url: $(this).attr('href') + '&ajax_request=true',
+ success: function (data) {
+ if (data.success === true) {
+ PMA_reloadNavigation();
+ } else {
+ PMA_ajaxShowMessage(data.error);
+ }
+ }
+ });
+ });
+
+ /** Display a dialog to choose hidden navigation items to show */
+ $('a.showUnhide.ajax').live('click', function (event) {
+ event.preventDefault();
+ var $msg = PMA_ajaxShowMessage();
+ $.get($(this).attr('href') + '&ajax_request=1', function (data) {
+ if (data.success === true) {
+ PMA_ajaxRemoveMessage($msg);
+ var buttonOptions = {};
+ buttonOptions[PMA_messages.strClose] = function () {
+ $(this).dialog("close");
+ };
+ var $dialog = $('<div/>')
+ .attr('id', 'unhideNavItemDialog')
+ .append(data.message)
+ .dialog({
+ width: 400,
+ minWidth: 200,
+ modal: true,
+ buttons: buttonOptions,
+ title: PMA_messages.strUnhideNavItem,
+ close: function () {
+ $(this).remove();
+ }
+ });
+ } else {
+ PMA_ajaxShowMessage(data.error);
+ }
+ });
+ });
+
+ /** Show a hidden navigation tree item */
+ $('a.unhideNavItem.ajax').live('click', function (event) {
+ event.preventDefault();
+ var $tr = $(this).parents('tr');
+ var $msg = PMA_ajaxShowMessage();
+ $.ajax({
+ url: $(this).attr('href') + '&ajax_request=true',
+ success: function (data) {
+ PMA_ajaxRemoveMessage($msg);
+ if (data.success === true) {
+ $tr.remove();
+ PMA_reloadNavigation();
+ } else {
+ PMA_ajaxShowMessage(data.error);
+ }
+ }
+ });
+ });
+
+ PMA_showCurrentNavigation();
+});
+
+/**
+ * Expands a node in navigation tree.
+ *
+ * @param $expandElem expander
+ * @param callback callback function
+ *
+ * @returns void
+ */
+function expandTreeNode($expandElem, callback)
+{
+ var $children = $expandElem.closest('li').children('div.list_container');
+ var $icon = $expandElem.find('img');
+ if ($expandElem.hasClass('loaded')) {
+ if ($icon.is('.ic_b_plus')) {
+ $icon.removeClass('ic_b_plus').addClass('ic_b_minus');
+ $children.show('fast');
+ }
+ if (callback && typeof callback == 'function') {
+ callback.call();
+ }
+ } else {
+ var $throbber = $('#pma_navigation .throbber')
+ .first()
+ .clone()
+ .css({visibility: 'visible', display: 'block'})
+ .click(false);
+ $icon.hide();
+ $throbber.insertBefore($icon);
+
+ loadChildNodes($expandElem, function (data) {
+ if (data.success === true) {
+ var $destination = $expandElem.closest('li');
+ $icon.removeClass('ic_b_plus').addClass('ic_b_minus');
+ $destination
+ .children('div.list_container')
+ .show('fast');
+ if ($destination.find('ul > li').length == 1) {
+ $destination.find('ul > li')
+ .find('a.expander.container')
+ .click();
+ }
+ if (callback && typeof callback == 'function') {
+ callback.call();
+ }
+ } else {
+ PMA_ajaxShowMessage(data.error, false);
+ }
+ $icon.show();
+ $throbber.remove();
+ });
+ }
+ $expandElem.blur();
+}
+
+/**
+ * Auto-scrolls the newly chosen database
+ *
+ * @param object $element The element to set to view
+ * @param boolean $forceToTop Whether to force scroll to top
+ *
+ */
+function scrollToView($element, $forceToTop) {
+ var $container = $('#pma_navigation_tree_content');
+ var elemTop = $element.offset().top - $container.offset().top;
+ var textHeight = 20;
+ var scrollPadding = 20; // extra padding from top of bottom when scrolling to view
+ if (elemTop < 0 || $forceToTop) {
+ $container.stop().animate({
+ scrollTop: elemTop + $container.scrollTop() - scrollPadding
+ });
+ } else if (elemTop + textHeight > $container.height()) {
+ $container.stop().animate({
+ scrollTop: elemTop + textHeight - $container.height() + $container.scrollTop() + scrollPadding
+ });
+ }
+}
+
+/**
+ * Collapses a node in navigation tree.
+ *
+ * @param $expandElem expander
+ *
+ * @returns void
+ */
+function collapseTreeNode($expandElem) {
+ var $children = $expandElem.closest('li').children('div.list_container');
+ var $icon = $expandElem.find('img');
+ if ($expandElem.hasClass('loaded')) {
+ if ($icon.is('.ic_b_minus')) {
+ $icon.removeClass('ic_b_minus').addClass('ic_b_plus');
+ $children.hide('fast');
+ }
+ }
+ $expandElem.blur();
+}
+
+/**
+ * Loads child items of a node and executes a given callback
+ *
+ * @param $expandElem expander
+ * @param callback callback function
+ *
+ * @returns void
+ */
+function loadChildNodes($expandElem, callback) {
+ var $destination = $expandElem.closest('li');
+
+ var searchClause = PMA_fastFilter.getSearchClause();
+ var searchClause2 = PMA_fastFilter.getSearchClause2($expandElem);
+
+ var params = {
+ aPath: $expandElem.find('span.aPath').text(),
+ vPath: $expandElem.find('span.vPath').text(),
+ pos: $expandElem.find('span.pos').text(),
+ pos2_name: $expandElem.find('span.pos2_name').text(),
+ pos2_value: $expandElem.find('span.pos2_value').text(),
+ searchClause: searchClause,
+ searchClause2: searchClause2
+ };
+
+ var url = $('#pma_navigation').find('a.navigation_url').attr('href');
+ $.get(url, params, function (data) {
+ if (data.success === true) {
+ $expandElem.addClass('loaded');
+ $destination.find('div.list_container').remove(); // FIXME: Hack, there shouldn't be a list container there
+ $destination.append(data.message);
+ if (callback && typeof callback == 'function') {
+ callback(data);
+ }
+ } else {
+ var $throbber = $expandElem.find('img.throbber');
+ $throbber.hide();
+ $icon = $expandElem.find('img.ic_b_plus');
+ $icon.show();
+ PMA_ajaxShowMessage(data.error, false);
+ }
+ });
+}
+
+/**
+ * Expand the navigation and highlight the current database or table/view
+ *
+ * @returns void
+ */
+function PMA_showCurrentNavigation()
+{
+ var db = PMA_commonParams.get('db');
+ var table = PMA_commonParams.get('table');
+ $('#pma_navigation_tree')
+ .find('li.selected')
+ .removeClass('selected');
+ if (db) {
+ var $dbItem = findLoadedItem(
+ $('#pma_navigation_tree > div'), db, 'database', !table
+ );
+ if ($dbItem) {
+ var $expander = $dbItem.children('div:first').children('a.expander');
+ // if not loaded or loaded but collapsed
+ if (! $expander.hasClass('loaded') ||
+ $expander.find('img').is('.ic_b_plus')
+ ) {
+ expandTreeNode($expander, function () {
+ handleTableOrDb(table, $dbItem);
+ });
+ } else {
+ handleTableOrDb(table, $dbItem);
+ }
+ }
+ }
+
+ function handleTableOrDb(table, $dbItem) {
+ if (table) {
+ loadAndHighlightTableOrView($dbItem, table);
+ } else {
+ var $container = $dbItem.children('div.list_container');
+ var $tableContainer = $container.children('ul').children('li.tableContainer');
+ if ($tableContainer.length > 0) {
+ var $expander = $tableContainer.children('div:first').children('a.expander');
+ expandTreeNode($expander, function () {
+ scrollToView($dbItem, true);
+ });
+ } else {
+ scrollToView($dbItem, true);
+ }
+ }
+ }
+
+ function findLoadedItem($container, name, clazz, doSelect) {
+ var ret = false;
+ $container.children('ul').children('li').each(function () {
+ var $li = $(this);
+ // this is a navigation group, recurse
+ if ($li.is('.navGroup')) {
+ var $container = $li.children('div.list_container');
+ var $childRet = findLoadedItem(
+ $container, name, clazz, doSelect
+ );
+ if ($childRet) {
+ ret = $childRet;
+ return false;
+ }
+ } else { // this is a real navigation item
+ // name and class matches
+ if (((clazz && $li.is('.' + clazz)) || ! clazz) &&
+ $li.children('a').text() == name) {
+ if (doSelect) {
+ $li.addClass('selected');
+ }
+ // taverse up and expand and parent navigation groups
+ $li.parents('.navGroup').each(function () {
+ $cont = $(this).children('div.list_container');
+ if (! $cont.is(':visible')) {
+ $(this)
+ .children('div:first')
+ .children('a.expander')
+ .click();
+ }
+ });
+ ret = $li;
+ return false;
+ }
+ }
+ });
+ return ret;
+ }
+
+ function loadAndHighlightTableOrView($dbItem, itemName) {
+ var $container = $dbItem.children('div.list_container');
+ var $expander;
+ var $whichItem = isItemInContainer($container, itemName, 'li.table, li.view');
+ //If item already there in some container
+ if ($whichItem) {
+ //get the relevant container while may also be a subcontainer
+ var $relatedContainer = $whichItem.closest('li.subContainer').length
+ ? $whichItem.closest('li.subContainer')
+ : $dbItem;
+ $whichItem = findLoadedItem(
+ $relatedContainer.children('div.list_container'),
+ itemName, null, true
+ );
+ //Show directly
+ showTableOrView($whichItem, $relatedContainer.children('div:first').children('a.expander'));
+ //else if item not there, try loading once
+ } else {
+ var $sub_containers = $dbItem.find('.subContainer');
+ //If there are subContainers i.e. tableContainer or viewContainer
+ if($sub_containers.length > 0) {
+ var $containers = new Array();
+ $sub_containers.each(function (index) {
+ $containers[index] = $(this);
+ $expander = $containers[index]
+ .children('div:first')
+ .children('a.expander');
+ collapseTreeNode($expander);
+ loadAndShowTableOrView($expander, $containers[index], itemName);
+ });
+ // else if no subContainers
+ } else {
+ $expander = $dbItem
+ .children('div:first')
+ .children('a.expander');
+ collapseTreeNode($expander);
+ loadAndShowTableOrView($expander, $dbItem, itemName);
+ }
+ }
+ }
+
+ function loadAndShowTableOrView($expander, $relatedContainer, itemName) {
+ loadChildNodes($expander, function (data) {
+ var $whichItem = findLoadedItem(
+ $relatedContainer.children('div.list_container'),
+ itemName, null, true
+ );
+ if ($whichItem) {
+ showTableOrView($whichItem, $expander);
+ }
+ });
+ }
+
+ function showTableOrView($whichItem, $expander) {
+ expandTreeNode($expander, function (data) {
+ if ($whichItem) {
+ scrollToView($whichItem, false);
+ }
+ });
+ }
+
+ function isItemInContainer($container, name, clazz)
+ {
+ var $whichItem = null;
+ $items = $container.find(clazz);
+ var found = false;
+ $items.each(function () {
+ if ($(this).children('a').text() == name) {
+ $whichItem = $(this);
+ return false;
+ }
+ });
+ return $whichItem;
+ }
+}
+
+/**
+ * Reloads the whole navigation tree while preserving its state
+ *
+ * @param function the callback function
+ * @return void
+ */
+function PMA_reloadNavigation(callback) {
+ var params = {
+ reload: true,
+ pos: $('#pma_navigation_tree').find('a.expander:first > span.pos').text()
+ };
+ // Traverse the navigation tree backwards to generate all the actual
+ // and virtual paths, as well as the positions in the pagination at
+ // various levels, if necessary.
+ var count = 0;
+ $('#pma_navigation_tree').find('a.expander:visible').each(function () {
+ if ($(this).find('img').is('.ic_b_minus') &&
+ $(this).closest('li').find('div.list_container .ic_b_minus').length === 0
+ ) {
+ params['n' + count + '_aPath'] = $(this).find('span.aPath').text();
+ params['n' + count + '_vPath'] = $(this).find('span.vPath').text();
+
+ var pos2_name = $(this).find('span.pos2_name').text();
+ if (! pos2_name) {
+ pos2_name = $(this)
+ .parent()
+ .parent()
+ .find('span.pos2_name:last')
+ .text();
+ }
+ var pos2_value = $(this).find('span.pos2_value').text();
+ if (! pos2_value) {
+ pos2_value = $(this)
+ .parent()
+ .parent()
+ .find('span.pos2_value:last')
+ .text();
+ }
+
+ params['n' + count + '_pos2_name'] = pos2_name;
+ params['n' + count + '_pos2_value'] = pos2_value;
+
+ params['n' + count + '_pos3_name'] = $(this).find('span.pos3_name').text();
+ params['n' + count + '_pos3_value'] = $(this).find('span.pos3_value').text();
+ count++;
+ }
+ });
+ var url = $('#pma_navigation').find('a.navigation_url').attr('href');
+ $.post(url, params, function (data) {
+ if (data.success) {
+ $('#pma_navigation_tree').html(data.message).children('div').show();
+ PMA_showCurrentNavigation();
+ // Fire the callback, if any
+ if (typeof callback === 'function') {
+ callback.call();
+ }
+ } else {
+ PMA_ajaxShowMessage(data.error);
+ }
+ });
+}
+
+/**
+ * Handles any requests to change the page in a branch of a tree
+ *
+ * This can be called from link click or select change event handlers
+ *
+ * @param object $this A jQuery object that points to the element that
+ * initiated the action of changing the page
+ *
+ * @return void
+ */
+function PMA_navigationTreePagination($this)
+{
+ var $msgbox = PMA_ajaxShowMessage();
+ var isDbSelector = $this.closest('div.pageselector').is('.dbselector');
+ var url, params;
+ if ($this[0].tagName == 'A') {
+ url = $this.attr('href');
+ params = 'ajax_request=true';
+ } else { // tagName == 'SELECT'
+ url = 'navigation.php';
+ params = $this.closest("form").serialize() + '&ajax_request=true';
+ }
+ var searchClause = PMA_fastFilter.getSearchClause();
+ if (searchClause) {
+ params += '&searchClause=' + encodeURIComponent(searchClause);
+ }
+ if (isDbSelector) {
+ params += '&full=true';
+ } else {
+ var searchClause2 = PMA_fastFilter.getSearchClause2($this);
+ if (searchClause2) {
+ params += '&searchClause2=' + encodeURIComponent(searchClause2);
+ }
+ }
+ $.post(url, params, function (data) {
+ PMA_ajaxRemoveMessage($msgbox);
+ if (data.success) {
+ if (isDbSelector) {
+ var val = PMA_fastFilter.getSearchClause();
+ $('#pma_navigation_tree')
+ .html(data.message)
+ .children('div')
+ .show();
+ if (val) {
+ $('#pma_navigation_tree')
+ .find('li.fast_filter input.searchClause')
+ .val(val);
+ }
+ } else {
+ var $parent = $this.closest('div.list_container').parent();
+ var val = PMA_fastFilter.getSearchClause2($this);
+ $this.closest('div.list_container').html(
+ $(data.message).children().show()
+ );
+ if (val) {
+ $parent.find('li.fast_filter input.searchClause').val(val);
+ }
+ $parent.find('span.pos2_value:first').text(
+ $parent.find('span.pos2_value:last').text()
+ );
+ $parent.find('span.pos3_value:first').text(
+ $parent.find('span.pos3_value:last').text()
+ );
+ }
+ } else {
+ PMA_ajaxShowMessage(data.error);
+ }
+ });
+}
+
+/**
+ * @var ResizeHandler Custom object that manages the resizing of the navigation
+ *
+ * XXX: Must only be ever instanciated once
+ * XXX: Inside event handlers the 'this' object is accessed as 'event.data.resize_handler'
+ */
+var ResizeHandler = function () {
+ /**
+ * Whether the user has initiated a resize operation
+ */
+ this.active = false;
+ /**
+ * @var int panel_width Used by the collapser to know where to go
+ * back to when uncollapsing the panel
+ */
+ this.panel_width = 0;
+ /**
+ * @var string left Used to provide support for RTL languages
+ */
+ this.left = $('html').attr('dir') == 'ltr' ? 'left' : 'right';
+ /**
+ * Adjusts the width of the navigation panel to the specified value
+ *
+ * @param int pos Navigation width in pixels
+ *
+ * @return void
+ */
+ this.setWidth = function (pos) {
+ var $resizer = $('#pma_navigation_resizer');
+ var resizer_width = $resizer.width();
+ var $collapser = $('#pma_navigation_collapser');
+ $('#pma_navigation').width(pos);
+ $('body').css('margin-' + this.left, pos + 'px');
+ $("#floating_menubar")
+ .css('margin-' + this.left, (pos + resizer_width) + 'px');
+ $resizer.css(this.left, pos + 'px');
+ if (pos === 0) {
+ $collapser
+ .css(this.left, pos + resizer_width)
+ .html(this.getSymbol(pos))
+ .prop('title', PMA_messages.strShowPanel);
+ } else {
+ $collapser
+ .css(this.left, pos)
+ .html(this.getSymbol(pos))
+ .prop('title', PMA_messages.strHidePanel);
+ }
+ setTimeout(function () {
+ $(window).trigger('resize');
+ }, 4);
+ };
+ /**
+ * Returns the horizontal position of the mouse,
+ * relative to the outer side of the navigation panel
+ *
+ * @param int pos Navigation width in pixels
+ *
+ * @return void
+ */
+ this.getPos = function (event) {
+ var pos = event.pageX;
+ var windowWidth = $(window).width();
+ var windowScroll = $(window).scrollLeft();
+ pos = pos - windowScroll;
+ if (this.left != 'left') {
+ pos = windowWidth - event.pageX;
+ }
+ if (pos < 0) {
+ pos = 0;
+ } else if (pos + 100 >= windowWidth) {
+ pos = windowWidth - 100;
+ } else {
+ this.panel_width = 0;
+ }
+ return pos;
+ };
+ /**
+ * Returns the HTML code for the arrow symbol used in the collapser
+ *
+ * @param int width The width of the panel
+ *
+ * @return string
+ */
+ this.getSymbol = function (width) {
+ if (this.left == 'left') {
+ if (width === 0) {
+ return '&rarr;';
+ } else {
+ return '&larr;';
+ }
+ } else {
+ if (width === 0) {
+ return '&larr;';
+ } else {
+ return '&rarr;';
+ }
+ }
+ };
+ /**
+ * Event handler for initiating a resize of the panel
+ *
+ * @param object e Event data (contains a reference to resizeHandler)
+ *
+ * @return void
+ */
+ this.mousedown = function (event) {
+ event.preventDefault();
+ event.data.resize_handler.active = true;
+ $('body').css('cursor', 'col-resize');
+ };
+ /**
+ * Event handler for terminating a resize of the panel
+ *
+ * @param object e Event data (contains a reference to resizeHandler)
+ *
+ * @return void
+ */
+ this.mouseup = function (event) {
+ if (event.data.resize_handler.active) {
+ event.data.resize_handler.active = false;
+ $('body').css('cursor', '');
+ $.cookie('pma_navi_width', event.data.resize_handler.getPos(event));
+ $('#topmenu').menuResizer('resize');
+ }
+ };
+ /**
+ * Event handler for updating the panel during a resize operation
+ *
+ * @param object e Event data (contains a reference to resizeHandler)
+ *
+ * @return void
+ */
+ this.mousemove = function (event) {
+ if (event.data && event.data.resize_handler && event.data.resize_handler.active) {
+ event.preventDefault();
+ var pos = event.data.resize_handler.getPos(event);
+ event.data.resize_handler.setWidth(pos);
+ }
+ };
+ /**
+ * Event handler for collapsing the panel
+ *
+ * @param object e Event data (contains a reference to resizeHandler)
+ *
+ * @return void
+ */
+ this.collapse = function (event) {
+ event.preventDefault();
+ event.data.active = false;
+ var panel_width = event.data.resize_handler.panel_width;
+ var width = $('#pma_navigation').width();
+ if (width === 0 && panel_width === 0) {
+ panel_width = 240;
+ }
+ event.data.resize_handler.setWidth(panel_width);
+ event.data.resize_handler.panel_width = width;
+ };
+ /**
+ * Event handler for resizing the navigation tree height on window resize
+ *
+ * @return void
+ */
+ this.treeResize = function (event) {
+ var $nav = $("#pma_navigation"),
+ $nav_tree = $("#pma_navigation_tree"),
+ $nav_header = $("#pma_navigation_header"),
+ $nav_tree_content = $("#pma_navigation_tree_content");
+ $nav_tree.height($nav.height() - $nav_header.height());
+ if ($nav_tree_content.length > 0) {
+ $nav_tree_content.height($nav_tree.height() - $nav_tree_content.position().top);
+ $nav_tree.css({
+ 'overflow': 'hidden'
+ });
+ } else {
+ $nav_tree.css({
+ 'overflow': 'hidden',
+ 'overflow-y': 'auto'
+ });
+ }
+ };
+ /* Initialisation section begins here */
+ if ($.cookie('pma_navi_width')) {
+ // If we have a cookie, set the width of the panel to its value
+ var pos = Math.abs(parseInt($.cookie('pma_navi_width'), 10) || 0);
+ this.setWidth(pos);
+ $('#topmenu').menuResizer('resize');
+ }
+ // Register the events for the resizer and the collapser
+ $('#pma_navigation_resizer')
+ .live('mousedown', {'resize_handler': this}, this.mousedown);
+ $(document)
+ .bind('mouseup', {'resize_handler': this}, this.mouseup)
+ .bind('mousemove', {'resize_handler': this}, $.throttle(this.mousemove, 4));
+ var $collapser = $('#pma_navigation_collapser');
+ $collapser.live('click', {'resize_handler': this}, this.collapse);
+ // Add the correct arrow symbol to the collapser
+ $collapser.html(this.getSymbol($('#pma_navigation').width()));
+ // Fix navigation tree height
+ $(window).on('resize', this.treeResize);
+ // need to call this now and then, browser might decide
+ // to show/hide horizontal scrollbars depending on page content width
+ setInterval(this.treeResize, 2000);
+}; // End of ResizeHandler
+
+/**
+ * @var object PMA_fastFilter Handles the functionality that allows filtering
+ * of the items in a branch of the navigation tree
+ */
+var PMA_fastFilter = {
+ /**
+ * Construct for the asynchronous fast filter functionality
+ *
+ * @param object $this A jQuery object pointing to the list container
+ * which is the nearest parent of the fast filter
+ * @param string searchClause The query string for the filter
+ *
+ * @return new PMA_fastFilter.filter object
+ */
+ filter: function ($this, searchClause) {
+ /**
+ * @var object $this A jQuery object pointing to the list container
+ * which is the nearest parent of the fast filter
+ */
+ this.$this = $this;
+ /**
+ * @var bool searchClause The query string for the filter
+ */
+ this.searchClause = searchClause;
+ /**
+ * @var object $clone A clone of the original contents
+ * of the navigation branch before
+ * the fast filter was applied
+ */
+ this.$clone = $this.clone();
+ /**
+ * @var bool swapped Whether the user clicked on the "N other results" link
+ */
+ this.swapped = false;
+ /**
+ * @var object xhr A reference to the ajax request that is currently running
+ */
+ this.xhr = null;
+ /**
+ * @var int timeout Used to delay the request for asynchronous search
+ */
+ this.timeout = null;
+
+ var $filterInput = $this.find('li.fast_filter input.searchClause');
+ if ($filterInput.length !== 0 &&
+ $filterInput.val() !== '' &&
+ $filterInput.val() != $filterInput[0].defaultValue
+ ) {
+ this.request();
+ }
+ },
+ /**
+ * Gets the query string from the database fast filter form
+ *
+ * @return string
+ */
+ getSearchClause: function () {
+ var retval = '';
+ var $input = $('#pma_navigation_tree')
+ .find('li.fast_filter.db_fast_filter input.searchClause');
+ if ($input.length && $input.val() != $input[0].defaultValue) {
+ retval = $input.val();
+ }
+ return retval;
+ },
+ /**
+ * Gets the query string from a second level item's fast filter form
+ * The retrieval is done by trasversing the navigation tree backwards
+ *
+ * @return string
+ */
+ getSearchClause2: function ($this) {
+ var $filterContainer = $this.closest('div.list_container');
+ var $filterInput = $([]);
+ while (1) {
+ if ($filterContainer.find('li.fast_filter:not(.db_fast_filter) input.searchClause').length !== 0) {
+ $filterInput = $filterContainer.find('li.fast_filter:not(.db_fast_filter) input.searchClause');
+ break;
+ } else if (! $filterContainer.is('div.list_container')) {
+ break;
+ }
+ $filterContainer = $filterContainer
+ .parent()
+ .closest('div.list_container');
+ }
+ var searchClause2 = '';
+ if ($filterInput.length !== 0 &&
+ $filterInput.first().val() != $filterInput[0].defaultValue
+ ) {
+ searchClause2 = $filterInput.val();
+ }
+ return searchClause2;
+ },
+ /**
+ * @var hash events A list of functions that are bound to DOM events
+ * at the top of this file
+ */
+ events: {
+ focus: function (event) {
+ var $obj = $(this).closest('div.list_container');
+ if (! $obj.data('fastFilter')) {
+ $obj.data(
+ 'fastFilter',
+ new PMA_fastFilter.filter($obj, $(this).val())
+ );
+ }
+ if ($(this).val() == this.defaultValue) {
+ $(this).val('');
+ } else {
+ $(this).select();
+ }
+ },
+ blur: function (event) {
+ if ($(this).val() === '') {
+ $(this).val(this.defaultValue);
+ }
+ var $obj = $(this).closest('div.list_container');
+ if ($(this).val() == this.defaultValue && $obj.data('fastFilter')) {
+ $obj.data('fastFilter').restore();
+ }
+ },
+ keyup: function (event) {
+ var $obj = $(this).closest('div.list_container');
+ var str = '';
+ if ($(this).val() != this.defaultValue && $(this).val() !== '') {
+ $obj.find('div.pageselector').hide();
+ str = $(this).val();
+ }
+
+ /**
+ * FIXME at the server level a value match is done while on
+ * the client side it is a regex match. These two should be aligned
+ */
+
+ // regex used for filtering.
+ var regex;
+ try {
+ regex = new RegExp(str, 'i');
+ } catch (err) {
+ return;
+ }
+
+ // this is the div that houses the items to be filtered by this filter.
+ var outerContainer;
+ if ($(this).closest('li.fast_filter').is('.db_fast_filter')) {
+ outerContainer = $('#pma_navigation_tree_content');
+ } else {
+ outerContainer = $obj;
+ }
+
+ // filters items that are directly under the div as well as grouped in
+ // groups. Does not filter child items (i.e. a database search does
+ // not filter tables)
+ var item_filter = function($curr) {
+ $curr.children('ul').children('li.navGroup').each(function() {
+ $(this).children('div.list_container').each(function() {
+ item_filter($(this)); // recursive
+ });
+ });
+ $curr.children('ul').children('li').children('a').not('.container').each(function() {
+ if (regex.test($(this).text())) {
+ $(this).parent().show().removeClass('hidden');
+ } else {
+ $(this).parent().hide().addClass('hidden');
+ }
+ });
+ };
+ item_filter(outerContainer);
+
+ // hides containers that does not have any visible children
+ var container_filter = function ($curr) {
+ $curr.children('ul').children('li.navGroup').each(function() {
+ var $group = $(this);
+ $group.children('div.list_container').each(function() {
+ container_filter($(this)); // recursive
+ });
+ $group.show().removeClass('hidden');
+ if ($group.children('div.list_container').children('ul')
+ .children('li').not('.hidden').length === 0) {
+ $group.hide().addClass('hidden');
+ }
+ });
+ };
+ container_filter(outerContainer);
+
+ if ($(this).val() != this.defaultValue && $(this).val() !== '') {
+ if (! $obj.data('fastFilter')) {
+ $obj.data(
+ 'fastFilter',
+ new PMA_fastFilter.filter($obj, $(this).val())
+ );
+ } else {
+ $obj.data('fastFilter').update($(this).val());
+ }
+ } else if ($obj.data('fastFilter')) {
+ $obj.data('fastFilter').restore(true);
+ }
+ },
+ clear: function (event) {
+ event.stopPropagation();
+ // Clear the input and apply the fast filter with empty input
+ var filter = $(this).closest('div.list_container').data('fastFilter');
+ if (filter) {
+ filter.restore();
+ }
+ var value = $(this).prev()[0].defaultValue;
+ $(this).prev().val(value).trigger('keyup');
+ }
+ }
+};
+/**
+ * Handles a change in the search clause
+ *
+ * @param string searchClause The query string for the filter
+ *
+ * @return void
+ */
+PMA_fastFilter.filter.prototype.update = function (searchClause)
+{
+ if (this.searchClause != searchClause) {
+ this.searchClause = searchClause;
+ this.$this.find('.moreResults').remove();
+ this.request();
+ }
+};
+/**
+ * After a delay of 250mS, initiates a request to retrieve search results
+ * Multiple calls to this function will always abort the previous request
+ *
+ * @return void
+ */
+PMA_fastFilter.filter.prototype.request = function ()
+{
+ var self = this;
+ clearTimeout(self.timeout);
+ if (self.$this.find('li.fast_filter').find('img.throbber').length === 0) {
+ self.$this.find('li.fast_filter').append(
+ $('<div class="throbber"></div>').append(
+ $('#pma_navigation_content')
+ .find('img.throbber')
+ .clone()
+ .css({visibility: 'visible', display: 'block'})
+ )
+ );
+ }
+ self.timeout = setTimeout(function () {
+ if (self.xhr) {
+ self.xhr.abort();
+ }
+ var url = $('#pma_navigation').find('a.navigation_url').attr('href');
+ var results = self.$this.find('li:not(.hidden):not(.fast_filter):not(.navGroup)').not('[class^=new]').length;
+ var params = self.$this.find('> ul > li > form.fast_filter').first().serialize() + "&results=" + results;
+ if (self.$this.find('> ul > li > form.fast_filter:first input[name=searchClause]').length === 0) {
+ var $input = $('#pma_navigation_tree').find('li.fast_filter.db_fast_filter input.searchClause');
+ if ($input.length && $input.val() != $input[0].defaultValue) {
+ params += '&searchClause=' + encodeURIComponent($input.val());
+ }
+ }
+ self.xhr = $.ajax({
+ url: url,
+ type: 'post',
+ dataType: 'json',
+ data: params,
+ complete: function (jqXHR) {
+ var data = $.parseJSON(jqXHR.responseText);
+ self.$this.find('li.fast_filter').find('div.throbber').remove();
+ if (data && data.results) {
+ var $listItem = $('<li />', {'class': 'moreResults'})
+ .appendTo(self.$this.find('li.fast_filter'));
+ var $link = $('<a />', {href: '#'})
+ .text(data.results)
+ .appendTo($listItem)
+ .click(function (event) {
+ event.preventDefault();
+ self.swap.apply(self, [data.message]);
+ });
+ }
+ }
+ });
+ }, 250);
+};
+/**
+ * Replaces the contents of the navigation branch with the search results
+ *
+ * @param string list The search results
+ *
+ * @return void
+ */
+PMA_fastFilter.filter.prototype.swap = function (list)
+{
+ this.swapped = true;
+ this.$this
+ .html($(list).html())
+ .children()
+ .show()
+ .end()
+ .find('li.fast_filter input.searchClause')
+ .val(this.searchClause);
+ this.$this.data('fastFilter', this);
+};
+/**
+ * Restores the navigation to the original state after the fast filter is cleared
+ *
+ * @param bool focus Whether to also focus the input box of the fast filter
+ *
+ * @return void
+ */
+PMA_fastFilter.filter.prototype.restore = function (focus)
+{
+ if (this.swapped) {
+ this.swapped = false;
+ this.$this.html(this.$clone.html()).children().show();
+ this.$this.data('fastFilter', this);
+ if (focus) {
+ this.$this.find('li.fast_filter input.searchClause').focus();
+ }
+ }
+ this.searchClause = '';
+ this.$this.find('.moreResults').remove();
+ this.$this.find('div.pageselector').show();
+ this.$this.find('div.throbber').remove();
+};
diff --git a/js/openlayers/OpenLayers.js b/js/openlayers/OpenLayers.js
new file mode 100644
index 0000000000..b9efb113a2
--- /dev/null
+++ b/js/openlayers/OpenLayers.js
@@ -0,0 +1,2681 @@
+/*
+
+ OpenLayers.js -- OpenLayers Map Viewer Library
+
+ Copyright 2005-2010 OpenLayers Contributors, released under the Clear BSD
+ license. Please see http://svn.openlayers.org/trunk/openlayers/license.txt
+ for the full text of the license.
+
+ Includes compressed code under the following licenses:
+
+ (For uncompressed versions of the code used please see the
+ OpenLayers SVN repository: <http://openlayers.org/>)
+
+*/
+
+/* Contains portions of Prototype.js:
+ *
+ * Prototype JavaScript framework, version 1.4.0
+ * (c) 2005 Sam Stephenson <sam@conio.net>
+ *
+ * Prototype is freely distributable under the terms of an MIT-style license.
+ * For details, see the Prototype web site: http://prototype.conio.net/
+ *
+ *--------------------------------------------------------------------------*/
+
+/**
+*
+* Contains portions of Rico <http://openrico.org/>
+*
+* Copyright 2005 Sabre Airline Solutions
+*
+* Licensed under the Apache License, Version 2.0 (the "License"); you
+* may not use this file except in compliance with the License. You
+* may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+* implied. See the License for the specific language governing
+* permissions and limitations under the License.
+*
+**/
+
+/**
+ * Contains XMLHttpRequest.js <http://code.google.com/p/xmlhttprequest/>
+ * Copyright 2007 Sergey Ilinsky (http://www.ilinsky.com)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ */
+
+/**
+ * Contains portions of Gears <http://code.google.com/apis/gears/>
+ *
+ * Copyright 2007, Google Inc.
+ *
+ * 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 Google 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 AUTHOR ``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 AUTHOR 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.
+ *
+ * Sets up google.gears.*, which is *the only* supported way to access Gears.
+ *
+ * Circumvent this file at your own risk!
+ *
+ * In the future, Gears may automatically define google.gears.* without this
+ * file. Gears may use these objects to transparently fix bugs and compatibility
+ * issues. Applications that use the code below will continue to work seamlessly
+ * when that happens.
+ */
+var OpenLayers={singleFile:true};(function(){var singleFile=(typeof OpenLayers=="object"&&OpenLayers.singleFile);var scriptLocation="js/openlayers/";window.OpenLayers={_scriptName:(!singleFile)?"lib/OpenLayers.js":"OpenLayers.js",_getScriptLocation:function(){if(scriptLocation!=undefined){return scriptLocation;}
+scriptLocation="";var isOL=new RegExp("(^|(.*?\\/))("+OpenLayers._scriptName+")(\\?|$)");var scripts=document.getElementsByTagName('script');for(var i=0,len=scripts.length;i<len;i++){var src=scripts[i].getAttribute('src');if(src){var match=src.match(isOL);if(match){scriptLocation=match[1];break;}}}
+return scriptLocation;}};if(!singleFile){var jsfiles=new Array("OpenLayers/Util.js","OpenLayers/BaseTypes.js","OpenLayers/BaseTypes/Class.js","OpenLayers/BaseTypes/Bounds.js","OpenLayers/BaseTypes/Element.js","OpenLayers/BaseTypes/LonLat.js","OpenLayers/BaseTypes/Pixel.js","OpenLayers/BaseTypes/Size.js","OpenLayers/Console.js","OpenLayers/Tween.js","Rico/Corner.js","Rico/Color.js","OpenLayers/Ajax.js","OpenLayers/Events.js","OpenLayers/Request.js","OpenLayers/Request/XMLHttpRequest.js","OpenLayers/Projection.js","OpenLayers/Map.js","OpenLayers/Layer.js","OpenLayers/Icon.js","OpenLayers/Marker.js","OpenLayers/Marker/Box.js","OpenLayers/Popup.js","OpenLayers/Tile.js","OpenLayers/Tile/Image.js","OpenLayers/Tile/Image/IFrame.js","OpenLayers/Tile/WFS.js","OpenLayers/Layer/Image.js","OpenLayers/Layer/SphericalMercator.js","OpenLayers/Layer/EventPane.js","OpenLayers/Layer/FixedZoomLevels.js","OpenLayers/Layer/Google.js","OpenLayers/Layer/Google/v3.js","OpenLayers/Layer/VirtualEarth.js","OpenLayers/Layer/Yahoo.js","OpenLayers/Layer/HTTPRequest.js","OpenLayers/Layer/Grid.js","OpenLayers/Layer/MapGuide.js","OpenLayers/Layer/MapServer.js","OpenLayers/Layer/MapServer/Untiled.js","OpenLayers/Layer/KaMap.js","OpenLayers/Layer/KaMapCache.js","OpenLayers/Layer/MultiMap.js","OpenLayers/Layer/Markers.js","OpenLayers/Layer/Text.js","OpenLayers/Layer/WorldWind.js","OpenLayers/Layer/ArcGIS93Rest.js","OpenLayers/Layer/WMS.js","OpenLayers/Layer/WMS/Untiled.js","OpenLayers/Layer/WMS/Post.js","OpenLayers/Layer/WMTS.js","OpenLayers/Layer/ArcIMS.js","OpenLayers/Layer/GeoRSS.js","OpenLayers/Layer/Boxes.js","OpenLayers/Layer/XYZ.js","OpenLayers/Layer/TMS.js","OpenLayers/Layer/TileCache.js","OpenLayers/Layer/Zoomify.js","OpenLayers/Popup/Anchored.js","OpenLayers/Popup/AnchoredBubble.js","OpenLayers/Popup/Framed.js","OpenLayers/Popup/FramedCloud.js","OpenLayers/Feature.js","OpenLayers/Feature/Vector.js","OpenLayers/Feature/WFS.js","OpenLayers/Handler.js","OpenLayers/Handler/Click.js","OpenLayers/Handler/Hover.js","OpenLayers/Handler/Point.js","OpenLayers/Handler/Path.js","OpenLayers/Handler/Polygon.js","OpenLayers/Handler/Feature.js","OpenLayers/Handler/Drag.js","OpenLayers/Handler/RegularPolygon.js","OpenLayers/Handler/Box.js","OpenLayers/Handler/MouseWheel.js","OpenLayers/Handler/Keyboard.js","OpenLayers/Control.js","OpenLayers/Control/Attribution.js","OpenLayers/Control/Button.js","OpenLayers/Control/ZoomBox.js","OpenLayers/Control/ZoomToMaxExtent.js","OpenLayers/Control/DragPan.js","OpenLayers/Control/Navigation.js","OpenLayers/Control/MouseDefaults.js","OpenLayers/Control/MousePosition.js","OpenLayers/Control/OverviewMap.js","OpenLayers/Control/KeyboardDefaults.js","OpenLayers/Control/PanZoom.js","OpenLayers/Control/PanZoomBar.js","OpenLayers/Control/ArgParser.js","OpenLayers/Control/Permalink.js","OpenLayers/Control/Scale.js","OpenLayers/Control/ScaleLine.js","OpenLayers/Control/Snapping.js","OpenLayers/Control/Split.js","OpenLayers/Control/LayerSwitcher.js","OpenLayers/Control/DrawFeature.js","OpenLayers/Control/DragFeature.js","OpenLayers/Control/ModifyFeature.js","OpenLayers/Control/Panel.js","OpenLayers/Control/SelectFeature.js","OpenLayers/Control/NavigationHistory.js","OpenLayers/Control/Measure.js","OpenLayers/Control/WMSGetFeatureInfo.js","OpenLayers/Control/WMTSGetFeatureInfo.js","OpenLayers/Control/Graticule.js","OpenLayers/Control/TransformFeature.js","OpenLayers/Control/SLDSelect.js","OpenLayers/Geometry.js","OpenLayers/Geometry/Rectangle.js","OpenLayers/Geometry/Collection.js","OpenLayers/Geometry/Point.js","OpenLayers/Geometry/MultiPoint.js","OpenLayers/Geometry/Curve.js","OpenLayers/Geometry/LineString.js","OpenLayers/Geometry/LinearRing.js","OpenLayers/Geometry/Polygon.js","OpenLayers/Geometry/MultiLineString.js","OpenLayers/Geometry/MultiPolygon.js","OpenLayers/Geometry/Surface.js","OpenLayers/Renderer.js","OpenLayers/Renderer/Elements.js","OpenLayers/Renderer/SVG.js","OpenLayers/Renderer/Canvas.js","OpenLayers/Renderer/VML.js","OpenLayers/Layer/Vector.js","OpenLayers/Layer/Vector/RootContainer.js","OpenLayers/Strategy.js","OpenLayers/Strategy/Filter.js","OpenLayers/Strategy/Fixed.js","OpenLayers/Strategy/Cluster.js","OpenLayers/Strategy/Paging.js","OpenLayers/Strategy/BBOX.js","OpenLayers/Strategy/Save.js","OpenLayers/Strategy/Refresh.js","OpenLayers/Filter.js","OpenLayers/Filter/FeatureId.js","OpenLayers/Filter/Logical.js","OpenLayers/Filter/Comparison.js","OpenLayers/Filter/Spatial.js","OpenLayers/Protocol.js","OpenLayers/Protocol/HTTP.js","OpenLayers/Protocol/SQL.js","OpenLayers/Protocol/SQL/Gears.js","OpenLayers/Protocol/WFS.js","OpenLayers/Protocol/WFS/v1.js","OpenLayers/Protocol/WFS/v1_0_0.js","OpenLayers/Protocol/WFS/v1_1_0.js","OpenLayers/Protocol/SOS.js","OpenLayers/Protocol/SOS/v1_0_0.js","OpenLayers/Layer/PointTrack.js","OpenLayers/Layer/GML.js","OpenLayers/Style.js","OpenLayers/Style2.js","OpenLayers/StyleMap.js","OpenLayers/Rule.js","OpenLayers/Format.js","OpenLayers/Format/XML.js","OpenLayers/Format/Context.js","OpenLayers/Format/ArcXML.js","OpenLayers/Format/ArcXML/Features.js","OpenLayers/Format/GML.js","OpenLayers/Format/GML/Base.js","OpenLayers/Format/GML/v2.js","OpenLayers/Format/GML/v3.js","OpenLayers/Format/Atom.js","OpenLayers/Format/KML.js","OpenLayers/Format/GeoRSS.js","OpenLayers/Format/WFS.js","OpenLayers/Format/WFSCapabilities.js","OpenLayers/Format/WFSCapabilities/v1.js","OpenLayers/Format/WFSCapabilities/v1_0_0.js","OpenLayers/Format/WFSCapabilities/v1_1_0.js","OpenLayers/Format/WFSDescribeFeatureType.js","OpenLayers/Format/WMSDescribeLayer.js","OpenLayers/Format/WMSDescribeLayer/v1_1.js","OpenLayers/Format/WKT.js","OpenLayers/Format/OSM.js","OpenLayers/Format/GPX.js","OpenLayers/Format/Filter.js","OpenLayers/Format/Filter/v1.js","OpenLayers/Format/Filter/v1_0_0.js","OpenLayers/Format/Filter/v1_1_0.js","OpenLayers/Format/SLD.js","OpenLayers/Format/SLD/v1.js","OpenLayers/Format/SLD/v1_0_0.js","OpenLayers/Format/OWSCommon/v1.js","OpenLayers/Format/OWSCommon/v1_0_0.js","OpenLayers/Format/OWSCommon/v1_1_0.js","OpenLayers/Format/CSWGetDomain.js","OpenLayers/Format/CSWGetDomain/v2_0_2.js","OpenLayers/Format/CSWGetRecords.js","OpenLayers/Format/CSWGetRecords/v2_0_2.js","OpenLayers/Format/WFST.js","OpenLayers/Format/WFST/v1.js","OpenLayers/Format/WFST/v1_0_0.js","OpenLayers/Format/WFST/v1_1_0.js","OpenLayers/Format/Text.js","OpenLayers/Format/JSON.js","OpenLayers/Format/GeoJSON.js","OpenLayers/Format/WMC.js","OpenLayers/Format/WMC/v1.js","OpenLayers/Format/WMC/v1_0_0.js","OpenLayers/Format/WMC/v1_1_0.js","OpenLayers/Format/WMSCapabilities.js","OpenLayers/Format/WMSCapabilities/v1.js","OpenLayers/Format/WMSCapabilities/v1_1.js","OpenLayers/Format/WMSCapabilities/v1_1_0.js","OpenLayers/Format/WMSCapabilities/v1_1_1.js","OpenLayers/Format/WMSCapabilities/v1_3.js","OpenLayers/Format/WMSCapabilities/v1_3_0.js","OpenLayers/Format/WMSGetFeatureInfo.js","OpenLayers/Format/SOSCapabilities.js","OpenLayers/Format/SOSCapabilities/v1_0_0.js","OpenLayers/Format/SOSGetObservation.js","OpenLayers/Format/SOSGetFeatureOfInterest.js","OpenLayers/Format/OWSContext.js","OpenLayers/Format/OWSContext/v0_3_1.js","OpenLayers/Format/WMTSCapabilities.js","OpenLayers/Format/WMTSCapabilities/v1_0_0.js","OpenLayers/Layer/WFS.js","OpenLayers/Control/GetFeature.js","OpenLayers/Control/MouseToolbar.js","OpenLayers/Control/NavToolbar.js","OpenLayers/Control/PanPanel.js","OpenLayers/Control/Pan.js","OpenLayers/Control/ZoomIn.js","OpenLayers/Control/ZoomOut.js","OpenLayers/Control/ZoomPanel.js","OpenLayers/Control/EditingToolbar.js","OpenLayers/Symbolizer.js","OpenLayers/Symbolizer/Point.js","OpenLayers/Symbolizer/Line.js","OpenLayers/Symbolizer/Polygon.js","OpenLayers/Symbolizer/Text.js","OpenLayers/Symbolizer/Raster.js","OpenLayers/Lang.js","OpenLayers/Lang/en.js");var agent=navigator.userAgent;var docWrite=(agent.match("MSIE")||agent.match("Safari"));if(docWrite){var allScriptTags=new Array(jsfiles.length);}
+var host=OpenLayers._getScriptLocation()+"lib/";for(var i=0,len=jsfiles.length;i<len;i++){if(docWrite){allScriptTags[i]="<script src='"+host+jsfiles[i]+"'></script>";}else{var s=document.createElement("script");s.src=host+jsfiles[i];var h=document.getElementsByTagName("head").length?document.getElementsByTagName("head")[0]:document.body;h.appendChild(s);}}
+if(docWrite){document.write(allScriptTags.join(""));}}})();OpenLayers.VERSION_NUMBER="OpenLayers 2.10 -- $Revision: 10721 $";OpenLayers.String={startsWith:function(str,sub){return(str.indexOf(sub)==0);},contains:function(str,sub){return(str.indexOf(sub)!=-1);},trim:function(str){return str.replace(/^\s\s*/,'').replace(/\s\s*$/,'');},camelize:function(str){var oStringList=str.split('-');var camelizedString=oStringList[0];for(var i=1,len=oStringList.length;i<len;i++){var s=oStringList[i];camelizedString+=s.charAt(0).toUpperCase()+s.substring(1);}
+return camelizedString;},format:function(template,context,args){if(!context){context=window;}
+var replacer=function(str,match){var replacement;var subs=match.split(/\.+/);for(var i=0;i<subs.length;i++){if(i==0){replacement=context;}
+replacement=replacement[subs[i]];}
+if(typeof replacement=="function"){replacement=args?replacement.apply(null,args):replacement();}
+if(typeof replacement=='undefined'){return'undefined';}else{return replacement;}};return template.replace(OpenLayers.String.tokenRegEx,replacer);},tokenRegEx:/\$\{([\w.]+?)\}/g,numberRegEx:/^([+-]?)(?=\d|\.\d)\d*(\.\d*)?([Ee]([+-]?\d+))?$/,isNumeric:function(value){return OpenLayers.String.numberRegEx.test(value);},numericIf:function(value){return OpenLayers.String.isNumeric(value)?parseFloat(value):value;}};if(!String.prototype.startsWith){String.prototype.startsWith=function(sStart){OpenLayers.Console.warn(OpenLayers.i18n("methodDeprecated",{'newMethod':'OpenLayers.String.startsWith'}));return OpenLayers.String.startsWith(this,sStart);};}
+if(!String.prototype.contains){String.prototype.contains=function(str){OpenLayers.Console.warn(OpenLayers.i18n("methodDeprecated",{'newMethod':'OpenLayers.String.contains'}));return OpenLayers.String.contains(this,str);};}
+if(!String.prototype.trim){String.prototype.trim=function(){OpenLayers.Console.warn(OpenLayers.i18n("methodDeprecated",{'newMethod':'OpenLayers.String.trim'}));return OpenLayers.String.trim(this);};}
+if(!String.prototype.camelize){String.prototype.camelize=function(){OpenLayers.Console.warn(OpenLayers.i18n("methodDeprecated",{'newMethod':'OpenLayers.String.camelize'}));return OpenLayers.String.camelize(this);};}
+OpenLayers.Number={decimalSeparator:".",thousandsSeparator:",",limitSigDigs:function(num,sig){var fig=0;if(sig>0){fig=parseFloat(num.toPrecision(sig));}
+return fig;},format:function(num,dec,tsep,dsep){dec=(typeof dec!="undefined")?dec:0;tsep=(typeof tsep!="undefined")?tsep:OpenLayers.Number.thousandsSeparator;dsep=(typeof dsep!="undefined")?dsep:OpenLayers.Number.decimalSeparator;if(dec!=null){num=parseFloat(num.toFixed(dec));}
+var parts=num.toString().split(".");if(parts.length==1&&dec==null){dec=0;}
+var integer=parts[0];if(tsep){var thousands=/(-?[0-9]+)([0-9]{3})/;while(thousands.test(integer)){integer=integer.replace(thousands,"$1"+tsep+"$2");}}
+var str;if(dec==0){str=integer;}else{var rem=parts.length>1?parts[1]:"0";if(dec!=null){rem=rem+new Array(dec-rem.length+1).join("0");}
+str=integer+dsep+rem;}
+return str;}};if(!Number.prototype.limitSigDigs){Number.prototype.limitSigDigs=function(sig){OpenLayers.Console.warn(OpenLayers.i18n("methodDeprecated",{'newMethod':'OpenLayers.Number.limitSigDigs'}));return OpenLayers.Number.limitSigDigs(this,sig);};}
+OpenLayers.Function={bind:function(func,object){var args=Array.prototype.slice.apply(arguments,[2]);return function(){var newArgs=args.concat(Array.prototype.slice.apply(arguments,[0]));return func.apply(object,newArgs);};},bindAsEventListener:function(func,object){return function(event){return func.call(object,event||window.event);};},False:function(){return false;},True:function(){return true;}};if(!Function.prototype.bind){Function.prototype.bind=function(){OpenLayers.Console.warn(OpenLayers.i18n("methodDeprecated",{'newMethod':'OpenLayers.Function.bind'}));Array.prototype.unshift.apply(arguments,[this]);return OpenLayers.Function.bind.apply(null,arguments);};}
+if(!Function.prototype.bindAsEventListener){Function.prototype.bindAsEventListener=function(object){OpenLayers.Console.warn(OpenLayers.i18n("methodDeprecated",{'newMethod':'OpenLayers.Function.bindAsEventListener'}));return OpenLayers.Function.bindAsEventListener(this,object);};}
+OpenLayers.Array={filter:function(array,callback,caller){var selected=[];if(Array.prototype.filter){selected=array.filter(callback,caller);}else{var len=array.length;if(typeof callback!="function"){throw new TypeError();}
+for(var i=0;i<len;i++){if(i in array){var val=array[i];if(callback.call(caller,val,i,array)){selected.push(val);}}}}
+return selected;}};OpenLayers.Date={toISOString:(function(){if("toISOString"in Date.prototype){return function(date){return date.toISOString();}}else{function pad(num,len){var str=num+"";while(str.length<len){str="0"+str;}
+return str;}
+return function(date){var str;if(isNaN(date.getTime())){str="Invalid Date";}else{str=date.getUTCFullYear()+"-"+
+pad(date.getUTCMonth()+1,2)+"-"+
+pad(date.getUTCDate(),2)+"T"+
+pad(date.getUTCHours(),2)+":"+
+pad(date.getUTCMinutes(),2)+":"+
+pad(date.getUTCSeconds(),2)+"."+
+pad(date.getUTCMilliseconds(),3)+"Z";}
+return str;}}})(),parse:function(str){var date;var elapsed=Date.parse(str);if(!isNaN(elapsed)){date=new Date(elapsed);}else{var match=str.match(/^(?:(\d{4})(?:-(\d{2})(?:-(\d{2}))?)?)?(?:T(\d{1,2}):(\d{2}):(\d{2}(?:\.\d+)?)(Z|(?:[+-]\d{1,2}(?::(\d{2}))?)))?$/);var date;if(match&&(match[1]||match[7])){var year=parseInt(match[1],10)||0;var month=(parseInt(match[2],10)-1)||0;var day=parseInt(match[3],10)||1;date=new Date(Date.UTC(year,month,day));var type=match[7];if(type){var hours=parseInt(match[4],10);var minutes=parseInt(match[5],10);var secFrac=parseFloat(match[6]);var seconds=secFrac|0;var milliseconds=Math.round(1000*(secFrac-seconds));date.setUTCHours(hours,minutes,seconds,milliseconds);if(type!=="Z"){var hoursOffset=parseInt(type,10);var minutesOffset=parseInt(match[8])||0;var offset=-1000*(60*(hoursOffset*60)+minutesOffset*60);date=new Date(date.getTime()+offset);}}}else{date=new Date("invalid");}}
+return date;}};OpenLayers.Class=function(){var Class=function(){if(arguments&&arguments[0]!=OpenLayers.Class.isPrototype){this.initialize.apply(this,arguments);}};var extended={};var parent,initialize,Type;for(var i=0,len=arguments.length;i<len;++i){Type=arguments[i];if(typeof Type=="function"){if(i==0&&len>1){initialize=Type.prototype.initialize;Type.prototype.initialize=function(){};extended=new Type();if(initialize===undefined){delete Type.prototype.initialize;}else{Type.prototype.initialize=initialize;}}
+parent=Type.prototype;}else{parent=Type;}
+OpenLayers.Util.extend(extended,parent);}
+Class.prototype=extended;return Class;};OpenLayers.Class.isPrototype=function(){};OpenLayers.Class.create=function(){return function(){if(arguments&&arguments[0]!=OpenLayers.Class.isPrototype){this.initialize.apply(this,arguments);}};};OpenLayers.Class.inherit=function(){var superClass=arguments[0];var proto=new superClass(OpenLayers.Class.isPrototype);for(var i=1,len=arguments.length;i<len;i++){if(typeof arguments[i]=="function"){var mixin=arguments[i];arguments[i]=new mixin(OpenLayers.Class.isPrototype);}
+OpenLayers.Util.extend(proto,arguments[i]);}
+return proto;};OpenLayers.Util={};OpenLayers.Util.getElement=function(){var elements=[];for(var i=0,len=arguments.length;i<len;i++){var element=arguments[i];if(typeof element=='string'){element=document.getElementById(element);}
+if(arguments.length==1){return element;}
+elements.push(element);}
+return elements;};OpenLayers.Util.isElement=function(o){return!!(o&&o.nodeType===1);};if(typeof window.$==="undefined"){window.$=OpenLayers.Util.getElement;}
+OpenLayers.Util.extend=function(destination,source){destination=destination||{};if(source){for(var property in source){var value=source[property];if(value!==undefined){destination[property]=value;}}
+var sourceIsEvt=typeof window.Event=="function"&&source instanceof window.Event;if(!sourceIsEvt&&source.hasOwnProperty&&source.hasOwnProperty('toString')){destination.toString=source.toString;}}
+return destination;};OpenLayers.Util.removeItem=function(array,item){for(var i=array.length-1;i>=0;i--){if(array[i]==item){array.splice(i,1);}}
+return array;};OpenLayers.Util.clearArray=function(array){OpenLayers.Console.warn(OpenLayers.i18n("methodDeprecated",{'newMethod':'array = []'}));array.length=0;};OpenLayers.Util.indexOf=function(array,obj){if(typeof array.indexOf=="function"){return array.indexOf(obj);}else{for(var i=0,len=array.length;i<len;i++){if(array[i]==obj){return i;}}
+return-1;}};OpenLayers.Util.modifyDOMElement=function(element,id,px,sz,position,border,overflow,opacity){if(id){element.id=id;}
+if(px){element.style.left=px.x+"px";element.style.top=px.y+"px";}
+if(sz){element.style.width=sz.w+"px";element.style.height=sz.h+"px";}
+if(position){element.style.position=position;}
+if(border){element.style.border=border;}
+if(overflow){element.style.overflow=overflow;}
+if(parseFloat(opacity)>=0.0&&parseFloat(opacity)<1.0){element.style.filter='alpha(opacity='+(opacity*100)+')';element.style.opacity=opacity;}else if(parseFloat(opacity)==1.0){element.style.filter='';element.style.opacity='';}};OpenLayers.Util.createDiv=function(id,px,sz,imgURL,position,border,overflow,opacity){var dom=document.createElement('div');if(imgURL){dom.style.backgroundImage='url('+imgURL+')';}
+if(!id){id=OpenLayers.Util.createUniqueID("OpenLayersDiv");}
+if(!position){position="absolute";}
+OpenLayers.Util.modifyDOMElement(dom,id,px,sz,position,border,overflow,opacity);return dom;};OpenLayers.Util.createImage=function(id,px,sz,imgURL,position,border,opacity,delayDisplay){var image=document.createElement("img");if(!id){id=OpenLayers.Util.createUniqueID("OpenLayersDiv");}
+if(!position){position="relative";}
+OpenLayers.Util.modifyDOMElement(image,id,px,sz,position,border,null,opacity);if(delayDisplay){image.style.display="none";OpenLayers.Event.observe(image,"load",OpenLayers.Function.bind(OpenLayers.Util.onImageLoad,image));OpenLayers.Event.observe(image,"error",OpenLayers.Function.bind(OpenLayers.Util.onImageLoadError,image));}
+image.style.alt=id;image.galleryImg="no";if(imgURL){image.src=imgURL;}
+return image;};OpenLayers.Util.setOpacity=function(element,opacity){OpenLayers.Util.modifyDOMElement(element,null,null,null,null,null,null,opacity);};OpenLayers.Util.onImageLoad=function(){if(!this.viewRequestID||(this.map&&this.viewRequestID==this.map.viewRequestID)){this.style.display="";}
+OpenLayers.Element.removeClass(this,"olImageLoadError");};OpenLayers.IMAGE_RELOAD_ATTEMPTS=0;OpenLayers.Util.onImageLoadError=function(){this._attempts=(this._attempts)?(this._attempts+1):1;if(this._attempts<=OpenLayers.IMAGE_RELOAD_ATTEMPTS){var urls=this.urls;if(urls&&urls instanceof Array&&urls.length>1){var src=this.src.toString();var current_url,k;for(k=0;current_url=urls[k];k++){if(src.indexOf(current_url)!=-1){break;}}
+var guess=Math.floor(urls.length*Math.random());var new_url=urls[guess];k=0;while(new_url==current_url&&k++<4){guess=Math.floor(urls.length*Math.random());new_url=urls[guess];}
+this.src=src.replace(current_url,new_url);}else{this.src=this.src;}}else{OpenLayers.Element.addClass(this,"olImageLoadError");}
+this.style.display="";};OpenLayers.Util.alphaHackNeeded=null;OpenLayers.Util.alphaHack=function(){if(OpenLayers.Util.alphaHackNeeded==null){var arVersion=navigator.appVersion.split("MSIE");var version=parseFloat(arVersion[1]);var filter=false;try{filter=!!(document.body.filters);}catch(e){}
+OpenLayers.Util.alphaHackNeeded=(filter&&(version>=5.5)&&(version<7));}
+return OpenLayers.Util.alphaHackNeeded;};OpenLayers.Util.modifyAlphaImageDiv=function(div,id,px,sz,imgURL,position,border,sizing,opacity){OpenLayers.Util.modifyDOMElement(div,id,px,sz,position,null,null,opacity);var img=div.childNodes[0];if(imgURL){img.src=imgURL;}
+OpenLayers.Util.modifyDOMElement(img,div.id+"_innerImage",null,sz,"relative",border);if(OpenLayers.Util.alphaHack()){if(div.style.display!="none"){div.style.display="inline-block";}
+if(sizing==null){sizing="scale";}
+div.style.filter="progid:DXImageTransform.Microsoft"+".AlphaImageLoader(src='"+img.src+"', "+"sizingMethod='"+sizing+"')";if(parseFloat(div.style.opacity)>=0.0&&parseFloat(div.style.opacity)<1.0){div.style.filter+=" alpha(opacity="+div.style.opacity*100+")";}
+img.style.filter="alpha(opacity=0)";}};OpenLayers.Util.createAlphaImageDiv=function(id,px,sz,imgURL,position,border,sizing,opacity,delayDisplay){var div=OpenLayers.Util.createDiv();var img=OpenLayers.Util.createImage(null,null,null,null,null,null,null,false);div.appendChild(img);if(delayDisplay){img.style.display="none";OpenLayers.Event.observe(img,"load",OpenLayers.Function.bind(OpenLayers.Util.onImageLoad,div));OpenLayers.Event.observe(img,"error",OpenLayers.Function.bind(OpenLayers.Util.onImageLoadError,div));}
+OpenLayers.Util.modifyAlphaImageDiv(div,id,px,sz,imgURL,position,border,sizing,opacity);return div;};OpenLayers.Util.upperCaseObject=function(object){var uObject={};for(var key in object){uObject[key.toUpperCase()]=object[key];}
+return uObject;};OpenLayers.Util.applyDefaults=function(to,from){to=to||{};var fromIsEvt=typeof window.Event=="function"&&from instanceof window.Event;for(var key in from){if(to[key]===undefined||(!fromIsEvt&&from.hasOwnProperty&&from.hasOwnProperty(key)&&!to.hasOwnProperty(key))){to[key]=from[key];}}
+if(!fromIsEvt&&from&&from.hasOwnProperty&&from.hasOwnProperty('toString')&&!to.hasOwnProperty('toString')){to.toString=from.toString;}
+return to;};OpenLayers.Util.getParameterString=function(params){var paramsArray=[];for(var key in params){var value=params[key];if((value!=null)&&(typeof value!='function')){var encodedValue;if(typeof value=='object'&&value.constructor==Array){var encodedItemArray=[];var item;for(var itemIndex=0,len=value.length;itemIndex<len;itemIndex++){item=value[itemIndex];encodedItemArray.push(encodeURIComponent((item===null||item===undefined)?"":item));}
+encodedValue=encodedItemArray.join(",");}
+else{encodedValue=encodeURIComponent(value);}
+paramsArray.push(encodeURIComponent(key)+"="+encodedValue);}}
+return paramsArray.join("&");};OpenLayers.Util.urlAppend=function(url,paramStr){var newUrl=url;if(paramStr){var parts=(url+" ").split(/[?&]/);newUrl+=(parts.pop()===" "?paramStr:parts.length?"&"+paramStr:"?"+paramStr);}
+return newUrl;};OpenLayers.ImgPath='';OpenLayers.Util.getImagesLocation=function(){return OpenLayers.ImgPath||(OpenLayers._getScriptLocation()+"img/");};OpenLayers.Util.Try=function(){var returnValue=null;for(var i=0,len=arguments.length;i<len;i++){var lambda=arguments[i];try{returnValue=lambda();break;}catch(e){}}
+return returnValue;};OpenLayers.Util.getNodes=function(p,tagName){var nodes=OpenLayers.Util.Try(function(){return OpenLayers.Util._getNodes(p.documentElement.childNodes,tagName);},function(){return OpenLayers.Util._getNodes(p.childNodes,tagName);});return nodes;};OpenLayers.Util._getNodes=function(nodes,tagName){var retArray=[];for(var i=0,len=nodes.length;i<len;i++){if(nodes[i].nodeName==tagName){retArray.push(nodes[i]);}}
+return retArray;};OpenLayers.Util.getTagText=function(parent,item,index){var result=OpenLayers.Util.getNodes(parent,item);if(result&&(result.length>0))
+{if(!index){index=0;}
+if(result[index].childNodes.length>1){return result.childNodes[1].nodeValue;}
+else if(result[index].childNodes.length==1){return result[index].firstChild.nodeValue;}}else{return"";}};OpenLayers.Util.getXmlNodeValue=function(node){var val=null;OpenLayers.Util.Try(function(){val=node.text;if(!val){val=node.textContent;}
+if(!val){val=node.firstChild.nodeValue;}},function(){val=node.textContent;});return val;};OpenLayers.Util.mouseLeft=function(evt,div){var target=(evt.relatedTarget)?evt.relatedTarget:evt.toElement;while(target!=div&&target!=null){target=target.parentNode;}
+return(target!=div);};OpenLayers.Util.DEFAULT_PRECISION=14;OpenLayers.Util.toFloat=function(number,precision){if(precision==null){precision=OpenLayers.Util.DEFAULT_PRECISION;}
+var number;if(precision==0){number=parseFloat(number);}else{number=parseFloat(parseFloat(number).toPrecision(precision));}
+return number;};OpenLayers.Util.rad=function(x){return x*Math.PI/180;};OpenLayers.Util.deg=function(x){return x*180/Math.PI;};OpenLayers.Util.VincentyConstants={a:6378137,b:6356752.3142,f:1/298.257223563};OpenLayers.Util.distVincenty=function(p1,p2){var ct=OpenLayers.Util.VincentyConstants;var a=ct.a,b=ct.b,f=ct.f;var L=OpenLayers.Util.rad(p2.lon-p1.lon);var U1=Math.atan((1-f)*Math.tan(OpenLayers.Util.rad(p1.lat)));var U2=Math.atan((1-f)*Math.tan(OpenLayers.Util.rad(p2.lat)));var sinU1=Math.sin(U1),cosU1=Math.cos(U1);var sinU2=Math.sin(U2),cosU2=Math.cos(U2);var lambda=L,lambdaP=2*Math.PI;var iterLimit=20;while(Math.abs(lambda-lambdaP)>1e-12&&--iterLimit>0){var sinLambda=Math.sin(lambda),cosLambda=Math.cos(lambda);var sinSigma=Math.sqrt((cosU2*sinLambda)*(cosU2*sinLambda)+
+(cosU1*sinU2-sinU1*cosU2*cosLambda)*(cosU1*sinU2-sinU1*cosU2*cosLambda));if(sinSigma==0){return 0;}
+var cosSigma=sinU1*sinU2+cosU1*cosU2*cosLambda;var sigma=Math.atan2(sinSigma,cosSigma);var alpha=Math.asin(cosU1*cosU2*sinLambda/sinSigma);var cosSqAlpha=Math.cos(alpha)*Math.cos(alpha);var cos2SigmaM=cosSigma-2*sinU1*sinU2/cosSqAlpha;var C=f/16*cosSqAlpha*(4+f*(4-3*cosSqAlpha));lambdaP=lambda;lambda=L+(1-C)*f*Math.sin(alpha)*(sigma+C*sinSigma*(cos2SigmaM+C*cosSigma*(-1+2*cos2SigmaM*cos2SigmaM)));}
+if(iterLimit==0){return NaN;}
+var uSq=cosSqAlpha*(a*a-b*b)/(b*b);var A=1+uSq/16384*(4096+uSq*(-768+uSq*(320-175*uSq)));var B=uSq/1024*(256+uSq*(-128+uSq*(74-47*uSq)));var deltaSigma=B*sinSigma*(cos2SigmaM+B/4*(cosSigma*(-1+2*cos2SigmaM*cos2SigmaM)-
+B/6*cos2SigmaM*(-3+4*sinSigma*sinSigma)*(-3+4*cos2SigmaM*cos2SigmaM)));var s=b*A*(sigma-deltaSigma);var d=s.toFixed(3)/1000;return d;};OpenLayers.Util.destinationVincenty=function(lonlat,brng,dist){var u=OpenLayers.Util;var ct=u.VincentyConstants;var a=ct.a,b=ct.b,f=ct.f;var lon1=lonlat.lon;var lat1=lonlat.lat;var s=dist;var alpha1=u.rad(brng);var sinAlpha1=Math.sin(alpha1);var cosAlpha1=Math.cos(alpha1);var tanU1=(1-f)*Math.tan(u.rad(lat1));var cosU1=1/Math.sqrt((1+tanU1*tanU1)),sinU1=tanU1*cosU1;var sigma1=Math.atan2(tanU1,cosAlpha1);var sinAlpha=cosU1*sinAlpha1;var cosSqAlpha=1-sinAlpha*sinAlpha;var uSq=cosSqAlpha*(a*a-b*b)/(b*b);var A=1+uSq/16384*(4096+uSq*(-768+uSq*(320-175*uSq)));var B=uSq/1024*(256+uSq*(-128+uSq*(74-47*uSq)));var sigma=s/(b*A),sigmaP=2*Math.PI;while(Math.abs(sigma-sigmaP)>1e-12){var cos2SigmaM=Math.cos(2*sigma1+sigma);var sinSigma=Math.sin(sigma);var cosSigma=Math.cos(sigma);var deltaSigma=B*sinSigma*(cos2SigmaM+B/4*(cosSigma*(-1+2*cos2SigmaM*cos2SigmaM)-
+B/6*cos2SigmaM*(-3+4*sinSigma*sinSigma)*(-3+4*cos2SigmaM*cos2SigmaM)));sigmaP=sigma;sigma=s/(b*A)+deltaSigma;}
+var tmp=sinU1*sinSigma-cosU1*cosSigma*cosAlpha1;var lat2=Math.atan2(sinU1*cosSigma+cosU1*sinSigma*cosAlpha1,(1-f)*Math.sqrt(sinAlpha*sinAlpha+tmp*tmp));var lambda=Math.atan2(sinSigma*sinAlpha1,cosU1*cosSigma-sinU1*sinSigma*cosAlpha1);var C=f/16*cosSqAlpha*(4+f*(4-3*cosSqAlpha));var L=lambda-(1-C)*f*sinAlpha*(sigma+C*sinSigma*(cos2SigmaM+C*cosSigma*(-1+2*cos2SigmaM*cos2SigmaM)));var revAz=Math.atan2(sinAlpha,-tmp);return new OpenLayers.LonLat(lon1+u.deg(L),u.deg(lat2));};OpenLayers.Util.getParameters=function(url){url=url||window.location.href;var paramsString="";if(OpenLayers.String.contains(url,'?')){var start=url.indexOf('?')+1;var end=OpenLayers.String.contains(url,"#")?url.indexOf('#'):url.length;paramsString=url.substring(start,end);}
+var parameters={};var pairs=paramsString.split(/[&;]/);for(var i=0,len=pairs.length;i<len;++i){var keyValue=pairs[i].split('=');if(keyValue[0]){var key=decodeURIComponent(keyValue[0]);var value=keyValue[1]||'';value=decodeURIComponent(value.replace(/\+/g," ")).split(",");if(value.length==1){value=value[0];}
+parameters[key]=value;}}
+return parameters;};OpenLayers.Util.getArgs=function(url){OpenLayers.Console.warn(OpenLayers.i18n("methodDeprecated",{'newMethod':'OpenLayers.Util.getParameters'}));return OpenLayers.Util.getParameters(url);};OpenLayers.Util.lastSeqID=0;OpenLayers.Util.createUniqueID=function(prefix){if(prefix==null){prefix="id_";}
+OpenLayers.Util.lastSeqID+=1;return prefix+OpenLayers.Util.lastSeqID;};OpenLayers.INCHES_PER_UNIT={'inches':1.0,'ft':12.0,'mi':63360.0,'m':39.3701,'km':39370.1,'dd':4374754,'yd':36};OpenLayers.INCHES_PER_UNIT["in"]=OpenLayers.INCHES_PER_UNIT.inches;OpenLayers.INCHES_PER_UNIT["degrees"]=OpenLayers.INCHES_PER_UNIT.dd;OpenLayers.INCHES_PER_UNIT["nmi"]=1852*OpenLayers.INCHES_PER_UNIT.m;OpenLayers.METERS_PER_INCH=0.02540005080010160020;OpenLayers.Util.extend(OpenLayers.INCHES_PER_UNIT,{"Inch":OpenLayers.INCHES_PER_UNIT.inches,"Meter":1.0/OpenLayers.METERS_PER_INCH,"Foot":0.30480060960121920243/OpenLayers.METERS_PER_INCH,"IFoot":0.30480000000000000000/OpenLayers.METERS_PER_INCH,"ClarkeFoot":0.3047972651151/OpenLayers.METERS_PER_INCH,"SearsFoot":0.30479947153867624624/OpenLayers.METERS_PER_INCH,"GoldCoastFoot":0.30479971018150881758/OpenLayers.METERS_PER_INCH,"IInch":0.02540000000000000000/OpenLayers.METERS_PER_INCH,"MicroInch":0.00002540000000000000/OpenLayers.METERS_PER_INCH,"Mil":0.00000002540000000000/OpenLayers.METERS_PER_INCH,"Centimeter":0.01000000000000000000/OpenLayers.METERS_PER_INCH,"Kilometer":1000.00000000000000000000/OpenLayers.METERS_PER_INCH,"Yard":0.91440182880365760731/OpenLayers.METERS_PER_INCH,"SearsYard":0.914398414616029/OpenLayers.METERS_PER_INCH,"IndianYard":0.91439853074444079983/OpenLayers.METERS_PER_INCH,"IndianYd37":0.91439523/OpenLayers.METERS_PER_INCH,"IndianYd62":0.9143988/OpenLayers.METERS_PER_INCH,"IndianYd75":0.9143985/OpenLayers.METERS_PER_INCH,"IndianFoot":0.30479951/OpenLayers.METERS_PER_INCH,"IndianFt37":0.30479841/OpenLayers.METERS_PER_INCH,"IndianFt62":0.3047996/OpenLayers.METERS_PER_INCH,"IndianFt75":0.3047995/OpenLayers.METERS_PER_INCH,"Mile":1609.34721869443738887477/OpenLayers.METERS_PER_INCH,"IYard":0.91440000000000000000/OpenLayers.METERS_PER_INCH,"IMile":1609.34400000000000000000/OpenLayers.METERS_PER_INCH,"NautM":1852.00000000000000000000/OpenLayers.METERS_PER_INCH,"Lat-66":110943.316488932731/OpenLayers.METERS_PER_INCH,"Lat-83":110946.25736872234125/OpenLayers.METERS_PER_INCH,"Decimeter":0.10000000000000000000/OpenLayers.METERS_PER_INCH,"Millimeter":0.00100000000000000000/OpenLayers.METERS_PER_INCH,"Dekameter":10.00000000000000000000/OpenLayers.METERS_PER_INCH,"Decameter":10.00000000000000000000/OpenLayers.METERS_PER_INCH,"Hectometer":100.00000000000000000000/OpenLayers.METERS_PER_INCH,"GermanMeter":1.0000135965/OpenLayers.METERS_PER_INCH,"CaGrid":0.999738/OpenLayers.METERS_PER_INCH,"ClarkeChain":20.1166194976/OpenLayers.METERS_PER_INCH,"GunterChain":20.11684023368047/OpenLayers.METERS_PER_INCH,"BenoitChain":20.116782494375872/OpenLayers.METERS_PER_INCH,"SearsChain":20.11676512155/OpenLayers.METERS_PER_INCH,"ClarkeLink":0.201166194976/OpenLayers.METERS_PER_INCH,"GunterLink":0.2011684023368047/OpenLayers.METERS_PER_INCH,"BenoitLink":0.20116782494375872/OpenLayers.METERS_PER_INCH,"SearsLink":0.2011676512155/OpenLayers.METERS_PER_INCH,"Rod":5.02921005842012/OpenLayers.METERS_PER_INCH,"IntnlChain":20.1168/OpenLayers.METERS_PER_INCH,"IntnlLink":0.201168/OpenLayers.METERS_PER_INCH,"Perch":5.02921005842012/OpenLayers.METERS_PER_INCH,"Pole":5.02921005842012/OpenLayers.METERS_PER_INCH,"Furlong":201.1684023368046/OpenLayers.METERS_PER_INCH,"Rood":3.778266898/OpenLayers.METERS_PER_INCH,"CapeFoot":0.3047972615/OpenLayers.METERS_PER_INCH,"Brealey":375.00000000000000000000/OpenLayers.METERS_PER_INCH,"ModAmFt":0.304812252984505969011938/OpenLayers.METERS_PER_INCH,"Fathom":1.8288/OpenLayers.METERS_PER_INCH,"NautM-UK":1853.184/OpenLayers.METERS_PER_INCH,"50kilometers":50000.0/OpenLayers.METERS_PER_INCH,"150kilometers":150000.0/OpenLayers.METERS_PER_INCH});OpenLayers.Util.extend(OpenLayers.INCHES_PER_UNIT,{"mm":OpenLayers.INCHES_PER_UNIT["Meter"]/1000.0,"cm":OpenLayers.INCHES_PER_UNIT["Meter"]/100.0,"dm":OpenLayers.INCHES_PER_UNIT["Meter"]*100.0,"km":OpenLayers.INCHES_PER_UNIT["Meter"]*1000.0,"kmi":OpenLayers.INCHES_PER_UNIT["nmi"],"fath":OpenLayers.INCHES_PER_UNIT["Fathom"],"ch":OpenLayers.INCHES_PER_UNIT["IntnlChain"],"link":OpenLayers.INCHES_PER_UNIT["IntnlLink"],"us-in":OpenLayers.INCHES_PER_UNIT["inches"],"us-ft":OpenLayers.INCHES_PER_UNIT["Foot"],"us-yd":OpenLayers.INCHES_PER_UNIT["Yard"],"us-ch":OpenLayers.INCHES_PER_UNIT["GunterChain"],"us-mi":OpenLayers.INCHES_PER_UNIT["Mile"],"ind-yd":OpenLayers.INCHES_PER_UNIT["IndianYd37"],"ind-ft":OpenLayers.INCHES_PER_UNIT["IndianFt37"],"ind-ch":20.11669506/OpenLayers.METERS_PER_INCH});OpenLayers.DOTS_PER_INCH=72;OpenLayers.Util.normalizeScale=function(scale){var normScale=(scale>1.0)?(1.0/scale):scale;return normScale;};OpenLayers.Util.getResolutionFromScale=function(scale,units){var resolution;if(scale){if(units==null){units="degrees";}
+var normScale=OpenLayers.Util.normalizeScale(scale);resolution=1/(normScale*OpenLayers.INCHES_PER_UNIT[units]*OpenLayers.DOTS_PER_INCH);}
+return resolution;};OpenLayers.Util.getScaleFromResolution=function(resolution,units){if(units==null){units="degrees";}
+var scale=resolution*OpenLayers.INCHES_PER_UNIT[units]*OpenLayers.DOTS_PER_INCH;return scale;};OpenLayers.Util.safeStopPropagation=function(evt){OpenLayers.Event.stop(evt,true);};OpenLayers.Util.pagePosition=function(forElement){var valueT=0,valueL=0;var element=forElement;var child=forElement;while(element){if(element==document.body){if(OpenLayers.Element.getStyle(child,'position')=='absolute'){break;}}
+valueT+=element.offsetTop||0;valueL+=element.offsetLeft||0;child=element;try{element=element.offsetParent;}catch(e){OpenLayers.Console.error(OpenLayers.i18n("pagePositionFailed",{'elemId':element.id}));break;}}
+element=forElement;while(element){valueT-=element.scrollTop||0;valueL-=element.scrollLeft||0;element=element.parentNode;}
+return[valueL,valueT];};OpenLayers.Util.isEquivalentUrl=function(url1,url2,options){options=options||{};OpenLayers.Util.applyDefaults(options,{ignoreCase:true,ignorePort80:true,ignoreHash:true});var urlObj1=OpenLayers.Util.createUrlObject(url1,options);var urlObj2=OpenLayers.Util.createUrlObject(url2,options);for(var key in urlObj1){if(key!=="args"){if(urlObj1[key]!=urlObj2[key]){return false;}}}
+for(var key in urlObj1.args){if(urlObj1.args[key]!=urlObj2.args[key]){return false;}
+delete urlObj2.args[key];}
+for(var key in urlObj2.args){return false;}
+return true;};OpenLayers.Util.createUrlObject=function(url,options){options=options||{};if(!(/^\w+:\/\//).test(url)){var loc=window.location;var port=loc.port?":"+loc.port:"";var fullUrl=loc.protocol+"//"+loc.host.split(":").shift()+port;if(url.indexOf("/")===0){url=fullUrl+url;}else{var parts=loc.pathname.split("/");parts.pop();url=fullUrl+parts.join("/")+"/"+url;}}
+if(options.ignoreCase){url=url.toLowerCase();}
+var a=document.createElement('a');a.href=url;var urlObject={};urlObject.host=a.host.split(":").shift();urlObject.protocol=a.protocol;if(options.ignorePort80){urlObject.port=(a.port=="80"||a.port=="0")?"":a.port;}else{urlObject.port=(a.port==""||a.port=="0")?"80":a.port;}
+urlObject.hash=(options.ignoreHash||a.hash==="#")?"":a.hash;var queryString=a.search;if(!queryString){var qMark=url.indexOf("?");queryString=(qMark!=-1)?url.substr(qMark):"";}
+urlObject.args=OpenLayers.Util.getParameters(queryString);urlObject.pathname=(a.pathname.charAt(0)=="/")?a.pathname:"/"+a.pathname;return urlObject;};OpenLayers.Util.removeTail=function(url){var head=null;var qMark=url.indexOf("?");var hashMark=url.indexOf("#");if(qMark==-1){head=(hashMark!=-1)?url.substr(0,hashMark):url;}else{head=(hashMark!=-1)?url.substr(0,Math.min(qMark,hashMark)):url.substr(0,qMark);}
+return head;};OpenLayers.Util.getBrowserName=function(){var browserName="";var ua=navigator.userAgent.toLowerCase();if(ua.indexOf("opera")!=-1){browserName="opera";}else if(ua.indexOf("msie")!=-1){browserName="msie";}else if(ua.indexOf("safari")!=-1){browserName="safari";}else if(ua.indexOf("mozilla")!=-1){if(ua.indexOf("firefox")!=-1){browserName="firefox";}else{browserName="mozilla";}}
+return browserName;};OpenLayers.Util.getRenderedDimensions=function(contentHTML,size,options){var w,h;var container=document.createElement("div");container.style.visibility="hidden";var containerElement=(options&&options.containerElement)?options.containerElement:document.body;if(size){if(size.w){w=size.w;container.style.width=w+"px";}else if(size.h){h=size.h;container.style.height=h+"px";}}
+if(options&&options.displayClass){container.className=options.displayClass;}
+var content=document.createElement("div");content.innerHTML=contentHTML;content.style.overflow="visible";if(content.childNodes){for(var i=0,l=content.childNodes.length;i<l;i++){if(!content.childNodes[i].style)continue;content.childNodes[i].style.overflow="visible";}}
+container.appendChild(content);containerElement.appendChild(container);var parentHasPositionAbsolute=false;var parent=container.parentNode;while(parent&&parent.tagName.toLowerCase()!="body"){var parentPosition=OpenLayers.Element.getStyle(parent,"position");if(parentPosition=="absolute"){parentHasPositionAbsolute=true;break;}else if(parentPosition&&parentPosition!="static"){break;}
+parent=parent.parentNode;}
+if(!parentHasPositionAbsolute){container.style.position="absolute";}
+if(!w){w=parseInt(content.scrollWidth);container.style.width=w+"px";}
+if(!h){h=parseInt(content.scrollHeight);}
+container.removeChild(content);containerElement.removeChild(container);return new OpenLayers.Size(w,h);};OpenLayers.Util.getScrollbarWidth=function(){var scrollbarWidth=OpenLayers.Util._scrollbarWidth;if(scrollbarWidth==null){var scr=null;var inn=null;var wNoScroll=0;var wScroll=0;scr=document.createElement('div');scr.style.position='absolute';scr.style.top='-1000px';scr.style.left='-1000px';scr.style.width='100px';scr.style.height='50px';scr.style.overflow='hidden';inn=document.createElement('div');inn.style.width='100%';inn.style.height='200px';scr.appendChild(inn);document.body.appendChild(scr);wNoScroll=inn.offsetWidth;scr.style.overflow='scroll';wScroll=inn.offsetWidth;document.body.removeChild(document.body.lastChild);OpenLayers.Util._scrollbarWidth=(wNoScroll-wScroll);scrollbarWidth=OpenLayers.Util._scrollbarWidth;}
+return scrollbarWidth;};OpenLayers.Util.getFormattedLonLat=function(coordinate,axis,dmsOption){if(!dmsOption){dmsOption='dms';}
+var abscoordinate=Math.abs(coordinate)
+var coordinatedegrees=Math.floor(abscoordinate);var coordinateminutes=(abscoordinate-coordinatedegrees)/(1/60);var tempcoordinateminutes=coordinateminutes;coordinateminutes=Math.floor(coordinateminutes);var coordinateseconds=(tempcoordinateminutes-coordinateminutes)/(1/60);coordinateseconds=Math.round(coordinateseconds*10);coordinateseconds/=10;if(coordinatedegrees<10){coordinatedegrees="0"+coordinatedegrees;}
+var str=coordinatedegrees+"\u00B0";if(dmsOption.indexOf('dm')>=0){if(coordinateminutes<10){coordinateminutes="0"+coordinateminutes;}
+str+=coordinateminutes+"'";if(dmsOption.indexOf('dms')>=0){if(coordinateseconds<10){coordinateseconds="0"+coordinateseconds;}
+str+=coordinateseconds+'"';}}
+if(axis=="lon"){str+=coordinate<0?OpenLayers.i18n("W"):OpenLayers.i18n("E");}else{str+=coordinate<0?OpenLayers.i18n("S"):OpenLayers.i18n("N");}
+return str;};OpenLayers.Rico=new Object();OpenLayers.Rico.Corner={round:function(e,options){e=OpenLayers.Util.getElement(e);this._setOptions(options);var color=this.options.color;if(this.options.color=="fromElement"){color=this._background(e);}
+var bgColor=this.options.bgColor;if(this.options.bgColor=="fromParent"){bgColor=this._background(e.offsetParent);}
+this._roundCornersImpl(e,color,bgColor);},changeColor:function(theDiv,newColor){theDiv.style.backgroundColor=newColor;var spanElements=theDiv.parentNode.getElementsByTagName("span");for(var currIdx=0;currIdx<spanElements.length;currIdx++){spanElements[currIdx].style.backgroundColor=newColor;}},changeOpacity:function(theDiv,newOpacity){var mozillaOpacity=newOpacity;var ieOpacity='alpha(opacity='+newOpacity*100+')';theDiv.style.opacity=mozillaOpacity;theDiv.style.filter=ieOpacity;var spanElements=theDiv.parentNode.getElementsByTagName("span");for(var currIdx=0;currIdx<spanElements.length;currIdx++){spanElements[currIdx].style.opacity=mozillaOpacity;spanElements[currIdx].style.filter=ieOpacity;}},reRound:function(theDiv,options){var topRico=theDiv.parentNode.childNodes[0];var bottomRico=theDiv.parentNode.childNodes[2];theDiv.parentNode.removeChild(topRico);theDiv.parentNode.removeChild(bottomRico);this.round(theDiv.parentNode,options);},_roundCornersImpl:function(e,color,bgColor){if(this.options.border){this._renderBorder(e,bgColor);}
+if(this._isTopRounded()){this._roundTopCorners(e,color,bgColor);}
+if(this._isBottomRounded()){this._roundBottomCorners(e,color,bgColor);}},_renderBorder:function(el,bgColor){var borderValue="1px solid "+this._borderColor(bgColor);var borderL="border-left: "+borderValue;var borderR="border-right: "+borderValue;var style="style='"+borderL+";"+borderR+"'";el.innerHTML="<div "+style+">"+el.innerHTML+"</div>";},_roundTopCorners:function(el,color,bgColor){var corner=this._createCorner(bgColor);for(var i=0;i<this.options.numSlices;i++){corner.appendChild(this._createCornerSlice(color,bgColor,i,"top"));}
+el.style.paddingTop=0;el.insertBefore(corner,el.firstChild);},_roundBottomCorners:function(el,color,bgColor){var corner=this._createCorner(bgColor);for(var i=(this.options.numSlices-1);i>=0;i--){corner.appendChild(this._createCornerSlice(color,bgColor,i,"bottom"));}
+el.style.paddingBottom=0;el.appendChild(corner);},_createCorner:function(bgColor){var corner=document.createElement("div");corner.style.backgroundColor=(this._isTransparent()?"transparent":bgColor);return corner;},_createCornerSlice:function(color,bgColor,n,position){var slice=document.createElement("span");var inStyle=slice.style;inStyle.backgroundColor=color;inStyle.display="block";inStyle.height="1px";inStyle.overflow="hidden";inStyle.fontSize="1px";var borderColor=this._borderColor(color,bgColor);if(this.options.border&&n==0){inStyle.borderTopStyle="solid";inStyle.borderTopWidth="1px";inStyle.borderLeftWidth="0px";inStyle.borderRightWidth="0px";inStyle.borderBottomWidth="0px";inStyle.height="0px";inStyle.borderColor=borderColor;}
+else if(borderColor){inStyle.borderColor=borderColor;inStyle.borderStyle="solid";inStyle.borderWidth="0px 1px";}
+if(!this.options.compact&&(n==(this.options.numSlices-1))){inStyle.height="2px";}
+this._setMargin(slice,n,position);this._setBorder(slice,n,position);return slice;},_setOptions:function(options){this.options={corners:"all",color:"fromElement",bgColor:"fromParent",blend:true,border:false,compact:false};OpenLayers.Util.extend(this.options,options||{});this.options.numSlices=this.options.compact?2:4;if(this._isTransparent()){this.options.blend=false;}},_whichSideTop:function(){if(this._hasString(this.options.corners,"all","top")){return"";}
+if(this.options.corners.indexOf("tl")>=0&&this.options.corners.indexOf("tr")>=0){return"";}
+if(this.options.corners.indexOf("tl")>=0){return"left";}else if(this.options.corners.indexOf("tr")>=0){return"right";}
+return"";},_whichSideBottom:function(){if(this._hasString(this.options.corners,"all","bottom")){return"";}
+if(this.options.corners.indexOf("bl")>=0&&this.options.corners.indexOf("br")>=0){return"";}
+if(this.options.corners.indexOf("bl")>=0){return"left";}else if(this.options.corners.indexOf("br")>=0){return"right";}
+return"";},_borderColor:function(color,bgColor){if(color=="transparent"){return bgColor;}else if(this.options.border){return this.options.border;}else if(this.options.blend){return this._blend(bgColor,color);}else{return"";}},_setMargin:function(el,n,corners){var marginSize=this._marginSize(n);var whichSide=corners=="top"?this._whichSideTop():this._whichSideBottom();if(whichSide=="left"){el.style.marginLeft=marginSize+"px";el.style.marginRight="0px";}
+else if(whichSide=="right"){el.style.marginRight=marginSize+"px";el.style.marginLeft="0px";}
+else{el.style.marginLeft=marginSize+"px";el.style.marginRight=marginSize+"px";}},_setBorder:function(el,n,corners){var borderSize=this._borderSize(n);var whichSide=corners=="top"?this._whichSideTop():this._whichSideBottom();if(whichSide=="left"){el.style.borderLeftWidth=borderSize+"px";el.style.borderRightWidth="0px";}
+else if(whichSide=="right"){el.style.borderRightWidth=borderSize+"px";el.style.borderLeftWidth="0px";}
+else{el.style.borderLeftWidth=borderSize+"px";el.style.borderRightWidth=borderSize+"px";}
+if(this.options.border!=false){el.style.borderLeftWidth=borderSize+"px";el.style.borderRightWidth=borderSize+"px";}},_marginSize:function(n){if(this._isTransparent()){return 0;}
+var marginSizes=[5,3,2,1];var blendedMarginSizes=[3,2,1,0];var compactMarginSizes=[2,1];var smBlendedMarginSizes=[1,0];if(this.options.compact&&this.options.blend){return smBlendedMarginSizes[n];}else if(this.options.compact){return compactMarginSizes[n];}else if(this.options.blend){return blendedMarginSizes[n];}else{return marginSizes[n];}},_borderSize:function(n){var transparentBorderSizes=[5,3,2,1];var blendedBorderSizes=[2,1,1,1];var compactBorderSizes=[1,0];var actualBorderSizes=[0,2,0,0];if(this.options.compact&&(this.options.blend||this._isTransparent())){return 1;}else if(this.options.compact){return compactBorderSizes[n];}else if(this.options.blend){return blendedBorderSizes[n];}else if(this.options.border){return actualBorderSizes[n];}else if(this._isTransparent()){return transparentBorderSizes[n];}
+return 0;},_hasString:function(str){for(var i=1;i<arguments.length;i++)if(str.indexOf(arguments[i])>=0){return true;}return false;},_blend:function(c1,c2){var cc1=OpenLayers.Rico.Color.createFromHex(c1);cc1.blend(OpenLayers.Rico.Color.createFromHex(c2));return cc1;},_background:function(el){try{return OpenLayers.Rico.Color.createColorFromBackground(el).asHex();}catch(err){return"#ffffff";}},_isTransparent:function(){return this.options.color=="transparent";},_isTopRounded:function(){return this._hasString(this.options.corners,"all","top","tl","tr");},_isBottomRounded:function(){return this._hasString(this.options.corners,"all","bottom","bl","br");},_hasSingleTextChild:function(el){return el.childNodes.length==1&&el.childNodes[0].nodeType==3;}};(function(){if(window.google&&google.gears){return;}
+var factory=null;if(typeof GearsFactory!='undefined'){factory=new GearsFactory();}else{try{factory=new ActiveXObject('Gears.Factory');if(factory.getBuildInfo().indexOf('ie_mobile')!=-1){factory.privateSetGlobalObject(this);}}catch(e){if((typeof navigator.mimeTypes!='undefined')&&navigator.mimeTypes["application/x-googlegears"]){factory=document.createElement("object");factory.style.display="none";factory.width=0;factory.height=0;factory.type="application/x-googlegears";document.documentElement.appendChild(factory);}}}
+if(!factory){return;}
+if(!window.google){google={};}
+if(!google.gears){google.gears={factory:factory};}})();OpenLayers.Element={visible:function(element){return OpenLayers.Util.getElement(element).style.display!='none';},toggle:function(){for(var i=0,len=arguments.length;i<len;i++){var element=OpenLayers.Util.getElement(arguments[i]);var display=OpenLayers.Element.visible(element)?'hide':'show';OpenLayers.Element[display](element);}},hide:function(){for(var i=0,len=arguments.length;i<len;i++){var element=OpenLayers.Util.getElement(arguments[i]);if(element){element.style.display='none';}}},show:function(){for(var i=0,len=arguments.length;i<len;i++){var element=OpenLayers.Util.getElement(arguments[i]);if(element){element.style.display='';}}},remove:function(element){element=OpenLayers.Util.getElement(element);element.parentNode.removeChild(element);},getHeight:function(element){element=OpenLayers.Util.getElement(element);return element.offsetHeight;},getDimensions:function(element){element=OpenLayers.Util.getElement(element);if(OpenLayers.Element.getStyle(element,'display')!='none'){return{width:element.offsetWidth,height:element.offsetHeight};}
+var els=element.style;var originalVisibility=els.visibility;var originalPosition=els.position;var originalDisplay=els.display;els.visibility='hidden';els.position='absolute';els.display='';var originalWidth=element.clientWidth;var originalHeight=element.clientHeight;els.display=originalDisplay;els.position=originalPosition;els.visibility=originalVisibility;return{width:originalWidth,height:originalHeight};},hasClass:function(element,name){var names=element.className;return(!!names&&new RegExp("(^|\\s)"+name+"(\\s|$)").test(names));},addClass:function(element,name){if(!OpenLayers.Element.hasClass(element,name)){element.className+=(element.className?" ":"")+name;}
+return element;},removeClass:function(element,name){var names=element.className;if(names){element.className=OpenLayers.String.trim(names.replace(new RegExp("(^|\\s+)"+name+"(\\s+|$)")," "));}
+return element;},toggleClass:function(element,name){if(OpenLayers.Element.hasClass(element,name)){OpenLayers.Element.removeClass(element,name);}else{OpenLayers.Element.addClass(element,name);}
+return element;},getStyle:function(element,style){element=OpenLayers.Util.getElement(element);var value=null;if(element&&element.style){value=element.style[OpenLayers.String.camelize(style)];if(!value){if(document.defaultView&&document.defaultView.getComputedStyle){var css=document.defaultView.getComputedStyle(element,null);value=css?css.getPropertyValue(style):null;}else if(element.currentStyle){value=element.currentStyle[OpenLayers.String.camelize(style)];}}
+var positions=['left','top','right','bottom'];if(window.opera&&(OpenLayers.Util.indexOf(positions,style)!=-1)&&(OpenLayers.Element.getStyle(element,'position')=='static')){value='auto';}}
+return value=='auto'?null:value;}};OpenLayers.Size=OpenLayers.Class({w:0.0,h:0.0,initialize:function(w,h){this.w=parseFloat(w);this.h=parseFloat(h);},toString:function(){return("w="+this.w+",h="+this.h);},clone:function(){return new OpenLayers.Size(this.w,this.h);},equals:function(sz){var equals=false;if(sz!=null){equals=((this.w==sz.w&&this.h==sz.h)||(isNaN(this.w)&&isNaN(this.h)&&isNaN(sz.w)&&isNaN(sz.h)));}
+return equals;},CLASS_NAME:"OpenLayers.Size"});OpenLayers.Console={log:function(){},debug:function(){},info:function(){},warn:function(){},error:function(){},userError:function(error){alert(error);},assert:function(){},dir:function(){},dirxml:function(){},trace:function(){},group:function(){},groupEnd:function(){},time:function(){},timeEnd:function(){},profile:function(){},profileEnd:function(){},count:function(){},CLASS_NAME:"OpenLayers.Console"};(function(){var scripts=document.getElementsByTagName("script");for(var i=0,len=scripts.length;i<len;++i){if(scripts[i].src.indexOf("firebug.js")!=-1){if(console){OpenLayers.Util.extend(OpenLayers.Console,console);break;}}}})();OpenLayers.Icon=OpenLayers.Class({url:null,size:null,offset:null,calculateOffset:null,imageDiv:null,px:null,initialize:function(url,size,offset,calculateOffset){this.url=url;this.size=(size)?size:new OpenLayers.Size(20,20);this.offset=offset?offset:new OpenLayers.Pixel(-(this.size.w/2),-(this.size.h/2));this.calculateOffset=calculateOffset;var id=OpenLayers.Util.createUniqueID("OL_Icon_");this.imageDiv=OpenLayers.Util.createAlphaImageDiv(id);},destroy:function(){this.erase();OpenLayers.Event.stopObservingElement(this.imageDiv.firstChild);this.imageDiv.innerHTML="";this.imageDiv=null;},clone:function(){return new OpenLayers.Icon(this.url,this.size,this.offset,this.calculateOffset);},setSize:function(size){if(size!=null){this.size=size;}
+this.draw();},setUrl:function(url){if(url!=null){this.url=url;}
+this.draw();},draw:function(px){OpenLayers.Util.modifyAlphaImageDiv(this.imageDiv,null,null,this.size,this.url,"absolute");this.moveTo(px);return this.imageDiv;},erase:function(){if(this.imageDiv!=null&&this.imageDiv.parentNode!=null){OpenLayers.Element.remove(this.imageDiv);}},setOpacity:function(opacity){OpenLayers.Util.modifyAlphaImageDiv(this.imageDiv,null,null,null,null,null,null,null,opacity);},moveTo:function(px){if(px!=null){this.px=px;}
+if(this.imageDiv!=null){if(this.px==null){this.display(false);}else{if(this.calculateOffset){this.offset=this.calculateOffset(this.size);}
+var offsetPx=this.px.offset(this.offset);OpenLayers.Util.modifyAlphaImageDiv(this.imageDiv,null,offsetPx);}}},display:function(display){this.imageDiv.style.display=(display)?"":"none";},isDrawn:function(){var isDrawn=(this.imageDiv&&this.imageDiv.parentNode&&(this.imageDiv.parentNode.nodeType!=11));return isDrawn;},CLASS_NAME:"OpenLayers.Icon"});OpenLayers.Popup=OpenLayers.Class({events:null,id:"",lonlat:null,div:null,contentSize:null,size:null,contentHTML:null,backgroundColor:"",opacity:"",border:"",contentDiv:null,groupDiv:null,closeDiv:null,autoSize:false,minSize:null,maxSize:null,displayClass:"olPopup",contentDisplayClass:"olPopupContent",padding:0,disableFirefoxOverflowHack:false,fixPadding:function(){if(typeof this.padding=="number"){this.padding=new OpenLayers.Bounds(this.padding,this.padding,this.padding,this.padding);}},panMapIfOutOfView:false,keepInMap:false,closeOnMove:false,map:null,initialize:function(id,lonlat,contentSize,contentHTML,closeBox,closeBoxCallback){if(id==null){id=OpenLayers.Util.createUniqueID(this.CLASS_NAME+"_");}
+this.id=id;this.lonlat=lonlat;this.contentSize=(contentSize!=null)?contentSize:new OpenLayers.Size(OpenLayers.Popup.WIDTH,OpenLayers.Popup.HEIGHT);if(contentHTML!=null){this.contentHTML=contentHTML;}
+this.backgroundColor=OpenLayers.Popup.COLOR;this.opacity=OpenLayers.Popup.OPACITY;this.border=OpenLayers.Popup.BORDER;this.div=OpenLayers.Util.createDiv(this.id,null,null,null,null,null,"hidden");this.div.className=this.displayClass;var groupDivId=this.id+"_GroupDiv";this.groupDiv=OpenLayers.Util.createDiv(groupDivId,null,null,null,"relative",null,"hidden");var id=this.div.id+"_contentDiv";this.contentDiv=OpenLayers.Util.createDiv(id,null,this.contentSize.clone(),null,"relative");this.contentDiv.className=this.contentDisplayClass;this.groupDiv.appendChild(this.contentDiv);this.div.appendChild(this.groupDiv);if(closeBox){this.addCloseBox(closeBoxCallback);}
+this.registerEvents();},destroy:function(){this.id=null;this.lonlat=null;this.size=null;this.contentHTML=null;this.backgroundColor=null;this.opacity=null;this.border=null;if(this.closeOnMove&&this.map){this.map.events.unregister("movestart",this,this.hide);}
+this.events.destroy();this.events=null;if(this.closeDiv){OpenLayers.Event.stopObservingElement(this.closeDiv);this.groupDiv.removeChild(this.closeDiv);}
+this.closeDiv=null;this.div.removeChild(this.groupDiv);this.groupDiv=null;if(this.map!=null){this.map.removePopup(this);}
+this.map=null;this.div=null;this.autoSize=null;this.minSize=null;this.maxSize=null;this.padding=null;this.panMapIfOutOfView=null;},draw:function(px){if(px==null){if((this.lonlat!=null)&&(this.map!=null)){px=this.map.getLayerPxFromLonLat(this.lonlat);}}
+if(this.closeOnMove){this.map.events.register("movestart",this,this.hide);}
+if(!this.disableFirefoxOverflowHack&&OpenLayers.Util.getBrowserName()=='firefox'){this.map.events.register("movestart",this,function(){var style=document.defaultView.getComputedStyle(this.contentDiv,null);var currentOverflow=style.getPropertyValue("overflow");if(currentOverflow!="hidden"){this.contentDiv._oldOverflow=currentOverflow;this.contentDiv.style.overflow="hidden";}});this.map.events.register("moveend",this,function(){var oldOverflow=this.contentDiv._oldOverflow;if(oldOverflow){this.contentDiv.style.overflow=oldOverflow;this.contentDiv._oldOverflow=null;}});}
+this.moveTo(px);if(!this.autoSize&&!this.size){this.setSize(this.contentSize);}
+this.setBackgroundColor();this.setOpacity();this.setBorder();this.setContentHTML();if(this.panMapIfOutOfView){this.panIntoView();}
+return this.div;},updatePosition:function(){if((this.lonlat)&&(this.map)){var px=this.map.getLayerPxFromLonLat(this.lonlat);if(px){this.moveTo(px);}}},moveTo:function(px){if((px!=null)&&(this.div!=null)){this.div.style.left=px.x+"px";this.div.style.top=px.y+"px";}},visible:function(){return OpenLayers.Element.visible(this.div);},toggle:function(){if(this.visible()){this.hide();}else{this.show();}},show:function(){OpenLayers.Element.show(this.div);if(this.panMapIfOutOfView){this.panIntoView();}},hide:function(){OpenLayers.Element.hide(this.div);},setSize:function(contentSize){this.size=contentSize.clone();var contentDivPadding=this.getContentDivPadding();var wPadding=contentDivPadding.left+contentDivPadding.right;var hPadding=contentDivPadding.top+contentDivPadding.bottom;this.fixPadding();wPadding+=this.padding.left+this.padding.right;hPadding+=this.padding.top+this.padding.bottom;if(this.closeDiv){var closeDivWidth=parseInt(this.closeDiv.style.width);wPadding+=closeDivWidth+contentDivPadding.right;}
+this.size.w+=wPadding;this.size.h+=hPadding;if(OpenLayers.Util.getBrowserName()=="msie"){this.contentSize.w+=contentDivPadding.left+contentDivPadding.right;this.contentSize.h+=contentDivPadding.bottom+contentDivPadding.top;}
+if(this.div!=null){this.div.style.width=this.size.w+"px";this.div.style.height=this.size.h+"px";}
+if(this.contentDiv!=null){this.contentDiv.style.width=contentSize.w+"px";this.contentDiv.style.height=contentSize.h+"px";}},updateSize:function(){var preparedHTML="<div class='"+this.contentDisplayClass+"'>"+
+this.contentDiv.innerHTML+"</div>";var containerElement=(this.map)?this.map.layerContainerDiv:document.body;var realSize=OpenLayers.Util.getRenderedDimensions(preparedHTML,null,{displayClass:this.displayClass,containerElement:containerElement});var safeSize=this.getSafeContentSize(realSize);var newSize=null;if(safeSize.equals(realSize)){newSize=realSize;}else{var fixedSize=new OpenLayers.Size();fixedSize.w=(safeSize.w<realSize.w)?safeSize.w:null;fixedSize.h=(safeSize.h<realSize.h)?safeSize.h:null;if(fixedSize.w&&fixedSize.h){newSize=safeSize;}else{var clippedSize=OpenLayers.Util.getRenderedDimensions(preparedHTML,fixedSize,{displayClass:this.contentDisplayClass,containerElement:containerElement});var currentOverflow=OpenLayers.Element.getStyle(this.contentDiv,"overflow");if((currentOverflow!="hidden")&&(clippedSize.equals(safeSize))){var scrollBar=OpenLayers.Util.getScrollbarWidth();if(fixedSize.w){clippedSize.h+=scrollBar;}else{clippedSize.w+=scrollBar;}}
+newSize=this.getSafeContentSize(clippedSize);}}
+this.setSize(newSize);},setBackgroundColor:function(color){if(color!=undefined){this.backgroundColor=color;}
+if(this.div!=null){this.div.style.backgroundColor=this.backgroundColor;}},setOpacity:function(opacity){if(opacity!=undefined){this.opacity=opacity;}
+if(this.div!=null){this.div.style.opacity=this.opacity;this.div.style.filter='alpha(opacity='+this.opacity*100+')';}},setBorder:function(border){if(border!=undefined){this.border=border;}
+if(this.div!=null){this.div.style.border=this.border;}},setContentHTML:function(contentHTML){if(contentHTML!=null){this.contentHTML=contentHTML;}
+if((this.contentDiv!=null)&&(this.contentHTML!=null)&&(this.contentHTML!=this.contentDiv.innerHTML)){this.contentDiv.innerHTML=this.contentHTML;if(this.autoSize){this.registerImageListeners();this.updateSize();}}},registerImageListeners:function(){var onImgLoad=function(){this.popup.updateSize();if(this.popup.visible()&&this.popup.panMapIfOutOfView){this.popup.panIntoView();}
+OpenLayers.Event.stopObserving(this.img,"load",this.img._onImageLoad);};var images=this.contentDiv.getElementsByTagName("img");for(var i=0,len=images.length;i<len;i++){var img=images[i];if(img.width==0||img.height==0){var context={'popup':this,'img':img};img._onImgLoad=OpenLayers.Function.bind(onImgLoad,context);OpenLayers.Event.observe(img,'load',img._onImgLoad);}}},getSafeContentSize:function(size){var safeContentSize=size.clone();var contentDivPadding=this.getContentDivPadding();var wPadding=contentDivPadding.left+contentDivPadding.right;var hPadding=contentDivPadding.top+contentDivPadding.bottom;this.fixPadding();wPadding+=this.padding.left+this.padding.right;hPadding+=this.padding.top+this.padding.bottom;if(this.closeDiv){var closeDivWidth=parseInt(this.closeDiv.style.width);wPadding+=closeDivWidth+contentDivPadding.right;}
+if(this.minSize){safeContentSize.w=Math.max(safeContentSize.w,(this.minSize.w-wPadding));safeContentSize.h=Math.max(safeContentSize.h,(this.minSize.h-hPadding));}
+if(this.maxSize){safeContentSize.w=Math.min(safeContentSize.w,(this.maxSize.w-wPadding));safeContentSize.h=Math.min(safeContentSize.h,(this.maxSize.h-hPadding));}
+if(this.map&&this.map.size){var extraX=0,extraY=0;if(this.keepInMap&&!this.panMapIfOutOfView){var px=this.map.getPixelFromLonLat(this.lonlat);switch(this.relativePosition){case"tr":extraX=px.x;extraY=this.map.size.h-px.y;break;case"tl":extraX=this.map.size.w-px.x;extraY=this.map.size.h-px.y;break;case"bl":extraX=this.map.size.w-px.x;extraY=px.y;break;case"br":extraX=px.x;extraY=px.y;break;default:extraX=px.x;extraY=this.map.size.h-px.y;break;}}
+var maxY=this.map.size.h-
+this.map.paddingForPopups.top-
+this.map.paddingForPopups.bottom-
+hPadding-extraY;var maxX=this.map.size.w-
+this.map.paddingForPopups.left-
+this.map.paddingForPopups.right-
+wPadding-extraX;safeContentSize.w=Math.min(safeContentSize.w,maxX);safeContentSize.h=Math.min(safeContentSize.h,maxY);}
+return safeContentSize;},getContentDivPadding:function(){var contentDivPadding=this._contentDivPadding;if(!contentDivPadding){if(this.div.parentNode==null){this.div.style.display="none";document.body.appendChild(this.div);}
+contentDivPadding=new OpenLayers.Bounds(OpenLayers.Element.getStyle(this.contentDiv,"padding-left"),OpenLayers.Element.getStyle(this.contentDiv,"padding-bottom"),OpenLayers.Element.getStyle(this.contentDiv,"padding-right"),OpenLayers.Element.getStyle(this.contentDiv,"padding-top"));this._contentDivPadding=contentDivPadding;if(this.div.parentNode==document.body){document.body.removeChild(this.div);this.div.style.display="";}}
+return contentDivPadding;},addCloseBox:function(callback){this.closeDiv=OpenLayers.Util.createDiv(this.id+"_close",null,new OpenLayers.Size(17,17));this.closeDiv.className="olPopupCloseBox";var contentDivPadding=this.getContentDivPadding();this.closeDiv.style.right=contentDivPadding.right+"px";this.closeDiv.style.top=contentDivPadding.top+"px";this.groupDiv.appendChild(this.closeDiv);var closePopup=callback||function(e){this.hide();OpenLayers.Event.stop(e);};OpenLayers.Event.observe(this.closeDiv,"click",OpenLayers.Function.bindAsEventListener(closePopup,this));},panIntoView:function(){var mapSize=this.map.getSize();var origTL=this.map.getViewPortPxFromLayerPx(new OpenLayers.Pixel(parseInt(this.div.style.left),parseInt(this.div.style.top)));var newTL=origTL.clone();if(origTL.x<this.map.paddingForPopups.left){newTL.x=this.map.paddingForPopups.left;}else
+if((origTL.x+this.size.w)>(mapSize.w-this.map.paddingForPopups.right)){newTL.x=mapSize.w-this.map.paddingForPopups.right-this.size.w;}
+if(origTL.y<this.map.paddingForPopups.top){newTL.y=this.map.paddingForPopups.top;}else
+if((origTL.y+this.size.h)>(mapSize.h-this.map.paddingForPopups.bottom)){newTL.y=mapSize.h-this.map.paddingForPopups.bottom-this.size.h;}
+var dx=origTL.x-newTL.x;var dy=origTL.y-newTL.y;this.map.pan(dx,dy);},registerEvents:function(){this.events=new OpenLayers.Events(this,this.div,null,true);this.events.on({"mousedown":this.onmousedown,"mousemove":this.onmousemove,"mouseup":this.onmouseup,"click":this.onclick,"mouseout":this.onmouseout,"dblclick":this.ondblclick,scope:this});},onmousedown:function(evt){this.mousedown=true;OpenLayers.Event.stop(evt,true);},onmousemove:function(evt){if(this.mousedown){OpenLayers.Event.stop(evt,true);}},onmouseup:function(evt){if(this.mousedown){this.mousedown=false;OpenLayers.Event.stop(evt,true);}},onclick:function(evt){OpenLayers.Event.stop(evt,true);},onmouseout:function(evt){this.mousedown=false;},ondblclick:function(evt){OpenLayers.Event.stop(evt,true);},CLASS_NAME:"OpenLayers.Popup"});OpenLayers.Popup.WIDTH=200;OpenLayers.Popup.HEIGHT=200;OpenLayers.Popup.COLOR="white";OpenLayers.Popup.OPACITY=1;OpenLayers.Popup.BORDER="0px";OpenLayers.Protocol=OpenLayers.Class({format:null,options:null,autoDestroy:true,defaultFilter:null,initialize:function(options){options=options||{};OpenLayers.Util.extend(this,options);this.options=options;},mergeWithDefaultFilter:function(filter){var merged;if(filter&&this.defaultFilter){merged=new OpenLayers.Filter.Logical({type:OpenLayers.Filter.Logical.AND,filters:[this.defaultFilter,filter]});}else{merged=filter||this.defaultFilter||undefined;}
+return merged;},destroy:function(){this.options=null;this.format=null;},read:function(options){options=options||{};options.filter=this.mergeWithDefaultFilter(options.filter);},create:function(){},update:function(){},"delete":function(){},commit:function(){},abort:function(response){},createCallback:function(method,response,options){return OpenLayers.Function.bind(function(){method.apply(this,[response,options]);},this);},CLASS_NAME:"OpenLayers.Protocol"});OpenLayers.Protocol.Response=OpenLayers.Class({code:null,requestType:null,last:true,features:null,reqFeatures:null,priv:null,initialize:function(options){OpenLayers.Util.extend(this,options);},success:function(){return this.code>0;},CLASS_NAME:"OpenLayers.Protocol.Response"});OpenLayers.Protocol.Response.SUCCESS=1;OpenLayers.Protocol.Response.FAILURE=0;OpenLayers.Renderer=OpenLayers.Class({container:null,root:null,extent:null,locked:false,size:null,resolution:null,map:null,initialize:function(containerID,options){this.container=OpenLayers.Util.getElement(containerID);},destroy:function(){this.container=null;this.extent=null;this.size=null;this.resolution=null;this.map=null;},supported:function(){return false;},setExtent:function(extent,resolutionChanged){this.extent=extent.clone();if(resolutionChanged){this.resolution=null;}},setSize:function(size){this.size=size.clone();this.resolution=null;},getResolution:function(){this.resolution=this.resolution||this.map.getResolution();return this.resolution;},drawFeature:function(feature,style){if(style==null){style=feature.style;}
+if(feature.geometry){var bounds=feature.geometry.getBounds();if(bounds){if(!bounds.intersectsBounds(this.extent)){style={display:"none"};}
+var rendered=this.drawGeometry(feature.geometry,style,feature.id);if(style.display!="none"&&style.label&&rendered!==false){var location=feature.geometry.getCentroid();if(style.labelXOffset||style.labelYOffset){xOffset=isNaN(style.labelXOffset)?0:style.labelXOffset;yOffset=isNaN(style.labelYOffset)?0:style.labelYOffset;var res=this.getResolution();location.move(xOffset*res,yOffset*res);}
+this.drawText(feature.id,style,location);}else{this.removeText(feature.id);}
+return rendered;}}},drawGeometry:function(geometry,style,featureId){},drawText:function(featureId,style,location){},removeText:function(featureId){},clear:function(){},getFeatureIdFromEvent:function(evt){},eraseFeatures:function(features){if(!(features instanceof Array)){features=[features];}
+for(var i=0,len=features.length;i<len;++i){var feature=features[i];this.eraseGeometry(feature.geometry,feature.id);this.removeText(feature.id);}},eraseGeometry:function(geometry,featureId){},moveRoot:function(renderer){},getRenderLayerId:function(){return this.container.id;},applyDefaultSymbolizer:function(symbolizer){var result=OpenLayers.Util.extend({},OpenLayers.Renderer.defaultSymbolizer);if(symbolizer.stroke===false){delete result.strokeWidth;delete result.strokeColor;}
+if(symbolizer.fill===false){delete result.fillColor;}
+OpenLayers.Util.extend(result,symbolizer);return result;},CLASS_NAME:"OpenLayers.Renderer"});OpenLayers.Renderer.defaultSymbolizer={fillColor:"#000000",strokeColor:"#000000",strokeWidth:2,fillOpacity:1,strokeOpacity:1,pointRadius:0};OpenLayers.Strategy=OpenLayers.Class({layer:null,options:null,active:null,autoActivate:true,autoDestroy:true,initialize:function(options){OpenLayers.Util.extend(this,options);this.options=options;this.active=false;},destroy:function(){this.deactivate();this.layer=null;this.options=null;},setLayer:function(layer){this.layer=layer;},activate:function(){if(!this.active){this.active=true;return true;}
+return false;},deactivate:function(){if(this.active){this.active=false;return true;}
+return false;},CLASS_NAME:"OpenLayers.Strategy"});OpenLayers.Symbolizer=OpenLayers.Class({zIndex:0,initialize:function(config){OpenLayers.Util.extend(this,config);},clone:function(){var Type=eval(this.CLASS_NAME);return new Type(OpenLayers.Util.extend({},this));},CLASS_NAME:"OpenLayers.Symbolizer"});OpenLayers.Rico.Color=OpenLayers.Class({initialize:function(red,green,blue){this.rgb={r:red,g:green,b:blue};},setRed:function(r){this.rgb.r=r;},setGreen:function(g){this.rgb.g=g;},setBlue:function(b){this.rgb.b=b;},setHue:function(h){var hsb=this.asHSB();hsb.h=h;this.rgb=OpenLayers.Rico.Color.HSBtoRGB(hsb.h,hsb.s,hsb.b);},setSaturation:function(s){var hsb=this.asHSB();hsb.s=s;this.rgb=OpenLayers.Rico.Color.HSBtoRGB(hsb.h,hsb.s,hsb.b);},setBrightness:function(b){var hsb=this.asHSB();hsb.b=b;this.rgb=OpenLayers.Rico.Color.HSBtoRGB(hsb.h,hsb.s,hsb.b);},darken:function(percent){var hsb=this.asHSB();this.rgb=OpenLayers.Rico.Color.HSBtoRGB(hsb.h,hsb.s,Math.max(hsb.b-percent,0));},brighten:function(percent){var hsb=this.asHSB();this.rgb=OpenLayers.Rico.Color.HSBtoRGB(hsb.h,hsb.s,Math.min(hsb.b+percent,1));},blend:function(other){this.rgb.r=Math.floor((this.rgb.r+other.rgb.r)/2);this.rgb.g=Math.floor((this.rgb.g+other.rgb.g)/2);this.rgb.b=Math.floor((this.rgb.b+other.rgb.b)/2);},isBright:function(){var hsb=this.asHSB();return this.asHSB().b>0.5;},isDark:function(){return!this.isBright();},asRGB:function(){return"rgb("+this.rgb.r+","+this.rgb.g+","+this.rgb.b+")";},asHex:function(){return"#"+this.rgb.r.toColorPart()+this.rgb.g.toColorPart()+this.rgb.b.toColorPart();},asHSB:function(){return OpenLayers.Rico.Color.RGBtoHSB(this.rgb.r,this.rgb.g,this.rgb.b);},toString:function(){return this.asHex();}});OpenLayers.Rico.Color.createFromHex=function(hexCode){if(hexCode.length==4){var shortHexCode=hexCode;var hexCode='#';for(var i=1;i<4;i++){hexCode+=(shortHexCode.charAt(i)+
+shortHexCode.charAt(i));}}
+if(hexCode.indexOf('#')==0){hexCode=hexCode.substring(1);}
+var red=hexCode.substring(0,2);var green=hexCode.substring(2,4);var blue=hexCode.substring(4,6);return new OpenLayers.Rico.Color(parseInt(red,16),parseInt(green,16),parseInt(blue,16));};OpenLayers.Rico.Color.createColorFromBackground=function(elem){var actualColor=RicoUtil.getElementsComputedStyle(OpenLayers.Util.getElement(elem),"backgroundColor","background-color");if(actualColor=="transparent"&&elem.parentNode){return OpenLayers.Rico.Color.createColorFromBackground(elem.parentNode);}
+if(actualColor==null){return new OpenLayers.Rico.Color(255,255,255);}
+if(actualColor.indexOf("rgb(")==0){var colors=actualColor.substring(4,actualColor.length-1);var colorArray=colors.split(",");return new OpenLayers.Rico.Color(parseInt(colorArray[0]),parseInt(colorArray[1]),parseInt(colorArray[2]));}
+else if(actualColor.indexOf("#")==0){return OpenLayers.Rico.Color.createFromHex(actualColor);}
+else{return new OpenLayers.Rico.Color(255,255,255);}};OpenLayers.Rico.Color.HSBtoRGB=function(hue,saturation,brightness){var red=0;var green=0;var blue=0;if(saturation==0){red=parseInt(brightness*255.0+0.5);green=red;blue=red;}
+else{var h=(hue-Math.floor(hue))*6.0;var f=h-Math.floor(h);var p=brightness*(1.0-saturation);var q=brightness*(1.0-saturation*f);var t=brightness*(1.0-(saturation*(1.0-f)));switch(parseInt(h)){case 0:red=(brightness*255.0+0.5);green=(t*255.0+0.5);blue=(p*255.0+0.5);break;case 1:red=(q*255.0+0.5);green=(brightness*255.0+0.5);blue=(p*255.0+0.5);break;case 2:red=(p*255.0+0.5);green=(brightness*255.0+0.5);blue=(t*255.0+0.5);break;case 3:red=(p*255.0+0.5);green=(q*255.0+0.5);blue=(brightness*255.0+0.5);break;case 4:red=(t*255.0+0.5);green=(p*255.0+0.5);blue=(brightness*255.0+0.5);break;case 5:red=(brightness*255.0+0.5);green=(p*255.0+0.5);blue=(q*255.0+0.5);break;}}
+return{r:parseInt(red),g:parseInt(green),b:parseInt(blue)};};OpenLayers.Rico.Color.RGBtoHSB=function(r,g,b){var hue;var saturation;var brightness;var cmax=(r>g)?r:g;if(b>cmax){cmax=b;}
+var cmin=(r<g)?r:g;if(b<cmin){cmin=b;}
+brightness=cmax/255.0;if(cmax!=0){saturation=(cmax-cmin)/cmax;}else{saturation=0;}
+if(saturation==0){hue=0;}else{var redc=(cmax-r)/(cmax-cmin);var greenc=(cmax-g)/(cmax-cmin);var bluec=(cmax-b)/(cmax-cmin);if(r==cmax){hue=bluec-greenc;}else if(g==cmax){hue=2.0+redc-bluec;}else{hue=4.0+greenc-redc;}
+hue=hue/6.0;if(hue<0){hue=hue+1.0;}}
+return{h:hue,s:saturation,b:brightness};};OpenLayers.Bounds=OpenLayers.Class({left:null,bottom:null,right:null,top:null,centerLonLat:null,initialize:function(left,bottom,right,top){if(left!=null){this.left=OpenLayers.Util.toFloat(left);}
+if(bottom!=null){this.bottom=OpenLayers.Util.toFloat(bottom);}
+if(right!=null){this.right=OpenLayers.Util.toFloat(right);}
+if(top!=null){this.top=OpenLayers.Util.toFloat(top);}},clone:function(){return new OpenLayers.Bounds(this.left,this.bottom,this.right,this.top);},equals:function(bounds){var equals=false;if(bounds!=null){equals=((this.left==bounds.left)&&(this.right==bounds.right)&&(this.top==bounds.top)&&(this.bottom==bounds.bottom));}
+return equals;},toString:function(){return("left-bottom=("+this.left+","+this.bottom+")"
++" right-top=("+this.right+","+this.top+")");},toArray:function(reverseAxisOrder){if(reverseAxisOrder===true){return[this.bottom,this.left,this.top,this.right];}else{return[this.left,this.bottom,this.right,this.top];}},toBBOX:function(decimal,reverseAxisOrder){if(decimal==null){decimal=6;}
+var mult=Math.pow(10,decimal);var xmin=Math.round(this.left*mult)/mult;var ymin=Math.round(this.bottom*mult)/mult;var xmax=Math.round(this.right*mult)/mult;var ymax=Math.round(this.top*mult)/mult;if(reverseAxisOrder===true){return ymin+","+xmin+","+ymax+","+xmax;}else{return xmin+","+ymin+","+xmax+","+ymax;}},toGeometry:function(){return new OpenLayers.Geometry.Polygon([new OpenLayers.Geometry.LinearRing([new OpenLayers.Geometry.Point(this.left,this.bottom),new OpenLayers.Geometry.Point(this.right,this.bottom),new OpenLayers.Geometry.Point(this.right,this.top),new OpenLayers.Geometry.Point(this.left,this.top)])]);},getWidth:function(){return(this.right-this.left);},getHeight:function(){return(this.top-this.bottom);},getSize:function(){return new OpenLayers.Size(this.getWidth(),this.getHeight());},getCenterPixel:function(){return new OpenLayers.Pixel((this.left+this.right)/2,(this.bottom+this.top)/2);},getCenterLonLat:function(){if(!this.centerLonLat){this.centerLonLat=new OpenLayers.LonLat((this.left+this.right)/2,(this.bottom+this.top)/2);}
+return this.centerLonLat;},scale:function(ratio,origin){if(origin==null){origin=this.getCenterLonLat();}
+var origx,origy;if(origin.CLASS_NAME=="OpenLayers.LonLat"){origx=origin.lon;origy=origin.lat;}else{origx=origin.x;origy=origin.y;}
+var left=(this.left-origx)*ratio+origx;var bottom=(this.bottom-origy)*ratio+origy;var right=(this.right-origx)*ratio+origx;var top=(this.top-origy)*ratio+origy;return new OpenLayers.Bounds(left,bottom,right,top);},add:function(x,y){if((x==null)||(y==null)){var msg=OpenLayers.i18n("boundsAddError");OpenLayers.Console.error(msg);return null;}
+return new OpenLayers.Bounds(this.left+x,this.bottom+y,this.right+x,this.top+y);},extend:function(object){var bounds=null;if(object){switch(object.CLASS_NAME){case"OpenLayers.LonLat":bounds=new OpenLayers.Bounds(object.lon,object.lat,object.lon,object.lat);break;case"OpenLayers.Geometry.Point":bounds=new OpenLayers.Bounds(object.x,object.y,object.x,object.y);break;case"OpenLayers.Bounds":bounds=object;break;}
+if(bounds){this.centerLonLat=null;if((this.left==null)||(bounds.left<this.left)){this.left=bounds.left;}
+if((this.bottom==null)||(bounds.bottom<this.bottom)){this.bottom=bounds.bottom;}
+if((this.right==null)||(bounds.right>this.right)){this.right=bounds.right;}
+if((this.top==null)||(bounds.top>this.top)){this.top=bounds.top;}}}},containsLonLat:function(ll,inclusive){return this.contains(ll.lon,ll.lat,inclusive);},containsPixel:function(px,inclusive){return this.contains(px.x,px.y,inclusive);},contains:function(x,y,inclusive){if(inclusive==null){inclusive=true;}
+if(x==null||y==null){return false;}
+x=OpenLayers.Util.toFloat(x);y=OpenLayers.Util.toFloat(y);var contains=false;if(inclusive){contains=((x>=this.left)&&(x<=this.right)&&(y>=this.bottom)&&(y<=this.top));}else{contains=((x>this.left)&&(x<this.right)&&(y>this.bottom)&&(y<this.top));}
+return contains;},intersectsBounds:function(bounds,inclusive){if(inclusive==null){inclusive=true;}
+var intersects=false;var mightTouch=(this.left==bounds.right||this.right==bounds.left||this.top==bounds.bottom||this.bottom==bounds.top);if(inclusive||!mightTouch){var inBottom=(((bounds.bottom>=this.bottom)&&(bounds.bottom<=this.top))||((this.bottom>=bounds.bottom)&&(this.bottom<=bounds.top)));var inTop=(((bounds.top>=this.bottom)&&(bounds.top<=this.top))||((this.top>bounds.bottom)&&(this.top<bounds.top)));var inLeft=(((bounds.left>=this.left)&&(bounds.left<=this.right))||((this.left>=bounds.left)&&(this.left<=bounds.right)));var inRight=(((bounds.right>=this.left)&&(bounds.right<=this.right))||((this.right>=bounds.left)&&(this.right<=bounds.right)));intersects=((inBottom||inTop)&&(inLeft||inRight));}
+return intersects;},containsBounds:function(bounds,partial,inclusive){if(partial==null){partial=false;}
+if(inclusive==null){inclusive=true;}
+var bottomLeft=this.contains(bounds.left,bounds.bottom,inclusive);var bottomRight=this.contains(bounds.right,bounds.bottom,inclusive);var topLeft=this.contains(bounds.left,bounds.top,inclusive);var topRight=this.contains(bounds.right,bounds.top,inclusive);return(partial)?(bottomLeft||bottomRight||topLeft||topRight):(bottomLeft&&bottomRight&&topLeft&&topRight);},determineQuadrant:function(lonlat){var quadrant="";var center=this.getCenterLonLat();quadrant+=(lonlat.lat<center.lat)?"b":"t";quadrant+=(lonlat.lon<center.lon)?"l":"r";return quadrant;},transform:function(source,dest){this.centerLonLat=null;var ll=OpenLayers.Projection.transform({'x':this.left,'y':this.bottom},source,dest);var lr=OpenLayers.Projection.transform({'x':this.right,'y':this.bottom},source,dest);var ul=OpenLayers.Projection.transform({'x':this.left,'y':this.top},source,dest);var ur=OpenLayers.Projection.transform({'x':this.right,'y':this.top},source,dest);this.left=Math.min(ll.x,ul.x);this.bottom=Math.min(ll.y,lr.y);this.right=Math.max(lr.x,ur.x);this.top=Math.max(ul.y,ur.y);return this;},wrapDateLine:function(maxExtent,options){options=options||{};var leftTolerance=options.leftTolerance||0;var rightTolerance=options.rightTolerance||0;var newBounds=this.clone();if(maxExtent){while(newBounds.left<maxExtent.left&&(newBounds.right-rightTolerance)<=maxExtent.left){newBounds=newBounds.add(maxExtent.getWidth(),0);}
+while((newBounds.left+leftTolerance)>=maxExtent.right&&newBounds.right>maxExtent.right){newBounds=newBounds.add(-maxExtent.getWidth(),0);}}
+return newBounds;},CLASS_NAME:"OpenLayers.Bounds"});OpenLayers.Bounds.fromString=function(str){var bounds=str.split(",");return OpenLayers.Bounds.fromArray(bounds);};OpenLayers.Bounds.fromArray=function(bbox){return new OpenLayers.Bounds(parseFloat(bbox[0]),parseFloat(bbox[1]),parseFloat(bbox[2]),parseFloat(bbox[3]));};OpenLayers.Bounds.fromSize=function(size){return new OpenLayers.Bounds(0,size.h,size.w,0);};OpenLayers.Bounds.oppositeQuadrant=function(quadrant){var opp="";opp+=(quadrant.charAt(0)=='t')?'b':'t';opp+=(quadrant.charAt(1)=='l')?'r':'l';return opp;};OpenLayers.LonLat=OpenLayers.Class({lon:0.0,lat:0.0,initialize:function(lon,lat){this.lon=OpenLayers.Util.toFloat(lon);this.lat=OpenLayers.Util.toFloat(lat);},toString:function(){return("lon="+this.lon+",lat="+this.lat);},toShortString:function(){return(this.lon+", "+this.lat);},clone:function(){return new OpenLayers.LonLat(this.lon,this.lat);},add:function(lon,lat){if((lon==null)||(lat==null)){var msg=OpenLayers.i18n("lonlatAddError");OpenLayers.Console.error(msg);return null;}
+return new OpenLayers.LonLat(this.lon+OpenLayers.Util.toFloat(lon),this.lat+OpenLayers.Util.toFloat(lat));},equals:function(ll){var equals=false;if(ll!=null){equals=((this.lon==ll.lon&&this.lat==ll.lat)||(isNaN(this.lon)&&isNaN(this.lat)&&isNaN(ll.lon)&&isNaN(ll.lat)));}
+return equals;},transform:function(source,dest){var point=OpenLayers.Projection.transform({'x':this.lon,'y':this.lat},source,dest);this.lon=point.x;this.lat=point.y;return this;},wrapDateLine:function(maxExtent){var newLonLat=this.clone();if(maxExtent){while(newLonLat.lon<maxExtent.left){newLonLat.lon+=maxExtent.getWidth();}
+while(newLonLat.lon>maxExtent.right){newLonLat.lon-=maxExtent.getWidth();}}
+return newLonLat;},CLASS_NAME:"OpenLayers.LonLat"});OpenLayers.LonLat.fromString=function(str){var pair=str.split(",");return new OpenLayers.LonLat(pair[0],pair[1]);};OpenLayers.Pixel=OpenLayers.Class({x:0.0,y:0.0,initialize:function(x,y){this.x=parseFloat(x);this.y=parseFloat(y);},toString:function(){return("x="+this.x+",y="+this.y);},clone:function(){return new OpenLayers.Pixel(this.x,this.y);},equals:function(px){var equals=false;if(px!=null){equals=((this.x==px.x&&this.y==px.y)||(isNaN(this.x)&&isNaN(this.y)&&isNaN(px.x)&&isNaN(px.y)));}
+return equals;},add:function(x,y){if((x==null)||(y==null)){var msg=OpenLayers.i18n("pixelAddError");OpenLayers.Console.error(msg);return null;}
+return new OpenLayers.Pixel(this.x+x,this.y+y);},offset:function(px){var newPx=this.clone();if(px){newPx=this.add(px.x,px.y);}
+return newPx;},CLASS_NAME:"OpenLayers.Pixel"});OpenLayers.Control=OpenLayers.Class({id:null,map:null,div:null,type:null,allowSelection:false,displayClass:"",title:"",autoActivate:false,active:null,handler:null,eventListeners:null,events:null,EVENT_TYPES:["activate","deactivate"],initialize:function(options){this.displayClass=this.CLASS_NAME.replace("OpenLayers.","ol").replace(/\./g,"");OpenLayers.Util.extend(this,options);this.events=new OpenLayers.Events(this,null,this.EVENT_TYPES);if(this.eventListeners instanceof Object){this.events.on(this.eventListeners);}
+if(this.id==null){this.id=OpenLayers.Util.createUniqueID(this.CLASS_NAME+"_");}},destroy:function(){if(this.events){if(this.eventListeners){this.events.un(this.eventListeners);}
+this.events.destroy();this.events=null;}
+this.eventListeners=null;if(this.handler){this.handler.destroy();this.handler=null;}
+if(this.handlers){for(var key in this.handlers){if(this.handlers.hasOwnProperty(key)&&typeof this.handlers[key].destroy=="function"){this.handlers[key].destroy();}}
+this.handlers=null;}
+if(this.map){this.map.removeControl(this);this.map=null;}},setMap:function(map){this.map=map;if(this.handler){this.handler.setMap(map);}},draw:function(px){if(this.div==null){this.div=OpenLayers.Util.createDiv(this.id);this.div.className=this.displayClass;if(!this.allowSelection){this.div.className+=" olControlNoSelect";this.div.setAttribute("unselectable","on",0);this.div.onselectstart=OpenLayers.Function.False;}
+if(this.title!=""){this.div.title=this.title;}}
+if(px!=null){this.position=px.clone();}
+this.moveTo(this.position);return this.div;},moveTo:function(px){if((px!=null)&&(this.div!=null)){this.div.style.left=px.x+"px";this.div.style.top=px.y+"px";}},activate:function(){if(this.active){return false;}
+if(this.handler){this.handler.activate();}
+this.active=true;if(this.map){OpenLayers.Element.addClass(this.map.viewPortDiv,this.displayClass.replace(/ /g,"")+"Active");}
+this.events.triggerEvent("activate");return true;},deactivate:function(){if(this.active){if(this.handler){this.handler.deactivate();}
+this.active=false;if(this.map){OpenLayers.Element.removeClass(this.map.viewPortDiv,this.displayClass.replace(/ /g,"")+"Active");}
+this.events.triggerEvent("deactivate");return true;}
+return false;},CLASS_NAME:"OpenLayers.Control"});OpenLayers.Control.TYPE_BUTTON=1;OpenLayers.Control.TYPE_TOGGLE=2;OpenLayers.Control.TYPE_TOOL=3;OpenLayers.Lang={code:null,defaultCode:"en",getCode:function(){if(!OpenLayers.Lang.code){OpenLayers.Lang.setCode();}
+return OpenLayers.Lang.code;},setCode:function(code){var lang;if(!code){code=(OpenLayers.Util.getBrowserName()=="msie")?navigator.userLanguage:navigator.language;}
+var parts=code.split('-');parts[0]=parts[0].toLowerCase();if(typeof OpenLayers.Lang[parts[0]]=="object"){lang=parts[0];}
+if(parts[1]){var testLang=parts[0]+'-'+parts[1].toUpperCase();if(typeof OpenLayers.Lang[testLang]=="object"){lang=testLang;}}
+if(!lang){OpenLayers.Console.warn('Failed to find OpenLayers.Lang.'+parts.join("-")+' dictionary, falling back to default language');lang=OpenLayers.Lang.defaultCode;}
+OpenLayers.Lang.code=lang;},translate:function(key,context){var dictionary=OpenLayers.Lang[OpenLayers.Lang.getCode()];var message=dictionary[key];if(!message){message=key;}
+if(context){message=OpenLayers.String.format(message,context);}
+return message;}};OpenLayers.i18n=OpenLayers.Lang.translate;OpenLayers.Popup.Anchored=OpenLayers.Class(OpenLayers.Popup,{relativePosition:null,keepInMap:true,anchor:null,initialize:function(id,lonlat,contentSize,contentHTML,anchor,closeBox,closeBoxCallback){var newArguments=[id,lonlat,contentSize,contentHTML,closeBox,closeBoxCallback];OpenLayers.Popup.prototype.initialize.apply(this,newArguments);this.anchor=(anchor!=null)?anchor:{size:new OpenLayers.Size(0,0),offset:new OpenLayers.Pixel(0,0)};},destroy:function(){this.anchor=null;this.relativePosition=null;OpenLayers.Popup.prototype.destroy.apply(this,arguments);},show:function(){this.updatePosition();OpenLayers.Popup.prototype.show.apply(this,arguments);},moveTo:function(px){var oldRelativePosition=this.relativePosition;this.relativePosition=this.calculateRelativePosition(px);var newPx=this.calculateNewPx(px);var newArguments=new Array(newPx);OpenLayers.Popup.prototype.moveTo.apply(this,newArguments);if(this.relativePosition!=oldRelativePosition){this.updateRelativePosition();}},setSize:function(contentSize){OpenLayers.Popup.prototype.setSize.apply(this,arguments);if((this.lonlat)&&(this.map)){var px=this.map.getLayerPxFromLonLat(this.lonlat);this.moveTo(px);}},calculateRelativePosition:function(px){var lonlat=this.map.getLonLatFromLayerPx(px);var extent=this.map.getExtent();var quadrant=extent.determineQuadrant(lonlat);return OpenLayers.Bounds.oppositeQuadrant(quadrant);},updateRelativePosition:function(){},calculateNewPx:function(px){var newPx=px.offset(this.anchor.offset);var size=this.size||this.contentSize;var top=(this.relativePosition.charAt(0)=='t');newPx.y+=(top)?-(size.h+this.anchor.size.h):this.anchor.size.h;var left=(this.relativePosition.charAt(1)=='l');newPx.x+=(left)?-(size.w+this.anchor.size.w):this.anchor.size.w;return newPx;},CLASS_NAME:"OpenLayers.Popup.Anchored"});OpenLayers.Protocol.SOS=function(options){options=OpenLayers.Util.applyDefaults(options,OpenLayers.Protocol.SOS.DEFAULTS);var cls=OpenLayers.Protocol.SOS["v"+options.version.replace(/\./g,"_")];if(!cls){throw"Unsupported SOS version: "+options.version;}
+return new cls(options);};OpenLayers.Protocol.SOS.DEFAULTS={"version":"1.0.0"};OpenLayers.Protocol.SQL=OpenLayers.Class(OpenLayers.Protocol,{databaseName:'ol',tableName:"ol_vector_features",postReadFiltering:true,initialize:function(options){OpenLayers.Protocol.prototype.initialize.apply(this,[options]);},destroy:function(){OpenLayers.Protocol.prototype.destroy.apply(this);},supported:function(){return false;},evaluateFilter:function(feature,filter){return filter&&this.postReadFiltering?filter.evaluate(feature):true;},CLASS_NAME:"OpenLayers.Protocol.SQL"});OpenLayers.Protocol.WFS=function(options){options=OpenLayers.Util.applyDefaults(options,OpenLayers.Protocol.WFS.DEFAULTS);var cls=OpenLayers.Protocol.WFS["v"+options.version.replace(/\./g,"_")];if(!cls){throw"Unsupported WFS version: "+options.version;}
+return new cls(options);};OpenLayers.Protocol.WFS.fromWMSLayer=function(layer,options){var typeName,featurePrefix;var param=layer.params["LAYERS"];var parts=(param instanceof Array?param[0]:param).split(":");if(parts.length>1){featurePrefix=parts[0];}
+typeName=parts.pop();var protocolOptions={url:layer.url,featureType:typeName,featurePrefix:featurePrefix,srsName:layer.projection&&layer.projection.getCode()||layer.map&&layer.map.getProjectionObject().getCode(),version:"1.1.0"};return new OpenLayers.Protocol.WFS(OpenLayers.Util.applyDefaults(options,protocolOptions));};OpenLayers.Protocol.WFS.DEFAULTS={"version":"1.0.0"};OpenLayers.Renderer.Canvas=OpenLayers.Class(OpenLayers.Renderer,{canvas:null,features:null,initialize:function(containerID){OpenLayers.Renderer.prototype.initialize.apply(this,arguments);this.root=document.createElement("canvas");this.container.appendChild(this.root);this.canvas=this.root.getContext("2d");this.features={};},eraseGeometry:function(geometry,featureId){this.eraseFeatures(this.features[featureId][0]);},supported:function(){var canvas=document.createElement("canvas");return!!canvas.getContext;},setExtent:function(extent){this.extent=extent.clone();this.resolution=null;this.redraw();},setSize:function(size){this.size=size.clone();this.root.style.width=size.w+"px";this.root.style.height=size.h+"px";this.root.width=size.w;this.root.height=size.h;this.resolution=null;},drawFeature:function(feature,style){style=style||feature.style;style=this.applyDefaultSymbolizer(style);this.features[feature.id]=[feature,style];this.redraw();},drawGeometry:function(geometry,style){var className=geometry.CLASS_NAME;if((className=="OpenLayers.Geometry.Collection")||(className=="OpenLayers.Geometry.MultiPoint")||(className=="OpenLayers.Geometry.MultiLineString")||(className=="OpenLayers.Geometry.MultiPolygon")){for(var i=0;i<geometry.components.length;i++){this.drawGeometry(geometry.components[i],style);}
+return;}
+switch(geometry.CLASS_NAME){case"OpenLayers.Geometry.Point":this.drawPoint(geometry,style);break;case"OpenLayers.Geometry.LineString":this.drawLineString(geometry,style);break;case"OpenLayers.Geometry.LinearRing":this.drawLinearRing(geometry,style);break;case"OpenLayers.Geometry.Polygon":this.drawPolygon(geometry,style);break;default:break;}},drawExternalGraphic:function(pt,style){var img=new Image();if(style.graphicTitle){img.title=style.graphicTitle;}
+var width=style.graphicWidth||style.graphicHeight;var height=style.graphicHeight||style.graphicWidth;width=width?width:style.pointRadius*2;height=height?height:style.pointRadius*2;var xOffset=(style.graphicXOffset!=undefined)?style.graphicXOffset:-(0.5*width);var yOffset=(style.graphicYOffset!=undefined)?style.graphicYOffset:-(0.5*height);var context={img:img,x:(pt[0]+xOffset),y:(pt[1]+yOffset),width:width,height:height,opacity:style.graphicOpacity||style.fillOpacity,canvas:this.canvas};img.onload=OpenLayers.Function.bind(function(){this.canvas.globalAlpha=this.opacity;this.canvas.drawImage(this.img,this.x,this.y,this.width,this.height);},context);img.src=style.externalGraphic;},setCanvasStyle:function(type,style){if(type=="fill"){this.canvas.globalAlpha=style['fillOpacity'];this.canvas.fillStyle=style['fillColor'];}else if(type=="stroke"){this.canvas.globalAlpha=style['strokeOpacity'];this.canvas.strokeStyle=style['strokeColor'];this.canvas.lineWidth=style['strokeWidth'];}else{this.canvas.globalAlpha=0;this.canvas.lineWidth=1;}},drawPoint:function(geometry,style){if(style.graphic!==false){var pt=this.getLocalXY(geometry);if(style.externalGraphic){this.drawExternalGraphic(pt,style);}else{if(style.fill!==false){this.setCanvasStyle("fill",style);this.canvas.beginPath();this.canvas.arc(pt[0],pt[1],style.pointRadius,0,Math.PI*2,true);this.canvas.fill();}
+if(style.stroke!==false){this.setCanvasStyle("stroke",style);this.canvas.beginPath();this.canvas.arc(pt[0],pt[1],style.pointRadius,0,Math.PI*2,true);this.canvas.stroke();this.setCanvasStyle("reset");}}}},drawLineString:function(geometry,style){if(style.stroke!==false){this.setCanvasStyle("stroke",style);this.canvas.beginPath();var start=this.getLocalXY(geometry.components[0]);this.canvas.moveTo(start[0],start[1]);for(var i=1;i<geometry.components.length;i++){var pt=this.getLocalXY(geometry.components[i]);this.canvas.lineTo(pt[0],pt[1]);}
+this.canvas.stroke();}
+this.setCanvasStyle("reset");},drawLinearRing:function(geometry,style){if(style.fill!==false){this.setCanvasStyle("fill",style);this.canvas.beginPath();var start=this.getLocalXY(geometry.components[0]);this.canvas.moveTo(start[0],start[1]);for(var i=1;i<geometry.components.length-1;i++){var pt=this.getLocalXY(geometry.components[i]);this.canvas.lineTo(pt[0],pt[1]);}
+this.canvas.fill();}
+if(style.stroke!==false){this.setCanvasStyle("stroke",style);this.canvas.beginPath();var start=this.getLocalXY(geometry.components[0]);this.canvas.moveTo(start[0],start[1]);for(var i=1;i<geometry.components.length;i++){var pt=this.getLocalXY(geometry.components[i]);this.canvas.lineTo(pt[0],pt[1]);}
+this.canvas.stroke();}
+this.setCanvasStyle("reset");},drawPolygon:function(geometry,style){this.drawLinearRing(geometry.components[0],style);for(var i=1;i<geometry.components.length;i++){this.drawLinearRing(geometry.components[i],{fillOpacity:0,strokeWidth:0,strokeOpacity:0,strokeColor:'#000000',fillColor:'#000000'});}},drawText:function(location,style){style=OpenLayers.Util.extend({fontColor:"#000000",labelAlign:"cm"},style);var pt=this.getLocalXY(location);this.setCanvasStyle("reset");this.canvas.fillStyle=style.fontColor;this.canvas.globalAlpha=style.fontOpacity||1.0;var fontStyle=style.fontWeight+" "+style.fontSize+" "+style.fontFamily;if(this.canvas.fillText){var labelAlign=OpenLayers.Renderer.Canvas.LABEL_ALIGN[style.labelAlign[0]]||"center";this.canvas.font=fontStyle;this.canvas.textAlign=labelAlign;this.canvas.fillText(style.label,pt[0],pt[1]);}else if(this.canvas.mozDrawText){this.canvas.mozTextStyle=fontStyle;var len=this.canvas.mozMeasureText(style.label);switch(style.labelAlign[0]){case"l":break;case"r":pt[0]-=len;break;case"c":default:pt[0]-=len/2;}
+this.canvas.translate(pt[0],pt[1]);this.canvas.mozDrawText(style.label);this.canvas.translate(-1*pt[0],-1*pt[1]);}
+this.setCanvasStyle("reset");},getLocalXY:function(point){var resolution=this.getResolution();var extent=this.extent;var x=(point.x/resolution+(-extent.left/resolution));var y=((extent.top/resolution)-point.y/resolution);return[x,y];},clear:function(){this.canvas.clearRect(0,0,this.root.width,this.root.height);this.features={};},getFeatureIdFromEvent:function(evt){var loc=this.map.getLonLatFromPixel(evt.xy);var resolution=this.getResolution();var bounds=new OpenLayers.Bounds(loc.lon-resolution*5,loc.lat-resolution*5,loc.lon+resolution*5,loc.lat+resolution*5);var geom=bounds.toGeometry();for(var feat in this.features){if(!this.features.hasOwnProperty(feat)){continue;}
+if(this.features[feat][0].geometry.intersects(geom)){return feat;}}
+return null;},eraseFeatures:function(features){if(!(features instanceof Array)){features=[features];}
+for(var i=0;i<features.length;++i){delete this.features[features[i].id];}
+this.redraw();},redraw:function(){if(!this.locked){this.canvas.clearRect(0,0,this.root.width,this.root.height);var labelMap=[];var feature,style;for(var id in this.features){if(!this.features.hasOwnProperty(id)){continue;}
+feature=this.features[id][0];style=this.features[id][1];if(!feature.geometry){continue;}
+this.drawGeometry(feature.geometry,style);if(style.label){labelMap.push([feature,style]);}}
+var item;for(var i=0,len=labelMap.length;i<len;++i){item=labelMap[i];this.drawText(item[0].geometry.getCentroid(),item[1]);}}},CLASS_NAME:"OpenLayers.Renderer.Canvas"});OpenLayers.Renderer.Canvas.LABEL_ALIGN={"l":"left","r":"right"};OpenLayers.ElementsIndexer=OpenLayers.Class({maxZIndex:null,order:null,indices:null,compare:null,initialize:function(yOrdering){this.compare=yOrdering?OpenLayers.ElementsIndexer.IndexingMethods.Z_ORDER_Y_ORDER:OpenLayers.ElementsIndexer.IndexingMethods.Z_ORDER_DRAWING_ORDER;this.order=[];this.indices={};this.maxZIndex=0;},insert:function(newNode){if(this.exists(newNode)){this.remove(newNode);}
+var nodeId=newNode.id;this.determineZIndex(newNode);var leftIndex=-1;var rightIndex=this.order.length;var middle;while(rightIndex-leftIndex>1){middle=parseInt((leftIndex+rightIndex)/2);var placement=this.compare(this,newNode,OpenLayers.Util.getElement(this.order[middle]));if(placement>0){leftIndex=middle;}else{rightIndex=middle;}}
+this.order.splice(rightIndex,0,nodeId);this.indices[nodeId]=this.getZIndex(newNode);return this.getNextElement(rightIndex);},remove:function(node){var nodeId=node.id;var arrayIndex=OpenLayers.Util.indexOf(this.order,nodeId);if(arrayIndex>=0){this.order.splice(arrayIndex,1);delete this.indices[nodeId];if(this.order.length>0){var lastId=this.order[this.order.length-1];this.maxZIndex=this.indices[lastId];}else{this.maxZIndex=0;}}},clear:function(){this.order=[];this.indices={};this.maxZIndex=0;},exists:function(node){return(this.indices[node.id]!=null);},getZIndex:function(node){return node._style.graphicZIndex;},determineZIndex:function(node){var zIndex=node._style.graphicZIndex;if(zIndex==null){zIndex=this.maxZIndex;node._style.graphicZIndex=zIndex;}else if(zIndex>this.maxZIndex){this.maxZIndex=zIndex;}},getNextElement:function(index){var nextIndex=index+1;if(nextIndex<this.order.length){var nextElement=OpenLayers.Util.getElement(this.order[nextIndex]);if(nextElement==undefined){nextElement=this.getNextElement(nextIndex);}
+return nextElement;}else{return null;}},CLASS_NAME:"OpenLayers.ElementsIndexer"});OpenLayers.ElementsIndexer.IndexingMethods={Z_ORDER:function(indexer,newNode,nextNode){var newZIndex=indexer.getZIndex(newNode);var returnVal=0;if(nextNode){var nextZIndex=indexer.getZIndex(nextNode);returnVal=newZIndex-nextZIndex;}
+return returnVal;},Z_ORDER_DRAWING_ORDER:function(indexer,newNode,nextNode){var returnVal=OpenLayers.ElementsIndexer.IndexingMethods.Z_ORDER(indexer,newNode,nextNode);if(nextNode&&returnVal==0){returnVal=1;}
+return returnVal;},Z_ORDER_Y_ORDER:function(indexer,newNode,nextNode){var returnVal=OpenLayers.ElementsIndexer.IndexingMethods.Z_ORDER(indexer,newNode,nextNode);if(nextNode&&returnVal===0){var result=nextNode._boundsBottom-newNode._boundsBottom;returnVal=(result===0)?1:result;}
+return returnVal;}};OpenLayers.Renderer.Elements=OpenLayers.Class(OpenLayers.Renderer,{rendererRoot:null,root:null,vectorRoot:null,textRoot:null,xmlns:null,indexer:null,BACKGROUND_ID_SUFFIX:"_background",LABEL_ID_SUFFIX:"_label",initialize:function(containerID,options){OpenLayers.Renderer.prototype.initialize.apply(this,arguments);this.rendererRoot=this.createRenderRoot();this.root=this.createRoot("_root");this.vectorRoot=this.createRoot("_vroot");this.textRoot=this.createRoot("_troot");this.root.appendChild(this.vectorRoot);this.root.appendChild(this.textRoot);this.rendererRoot.appendChild(this.root);this.container.appendChild(this.rendererRoot);if(options&&(options.zIndexing||options.yOrdering)){this.indexer=new OpenLayers.ElementsIndexer(options.yOrdering);}},destroy:function(){this.clear();this.rendererRoot=null;this.root=null;this.xmlns=null;OpenLayers.Renderer.prototype.destroy.apply(this,arguments);},clear:function(){var child;var root=this.vectorRoot;if(root){while(child=root.firstChild){root.removeChild(child);}}
+root=this.textRoot;if(root){while(child=root.firstChild){root.removeChild(child);}}
+if(this.indexer){this.indexer.clear();}},getNodeType:function(geometry,style){},drawGeometry:function(geometry,style,featureId){var className=geometry.CLASS_NAME;var rendered=true;if((className=="OpenLayers.Geometry.Collection")||(className=="OpenLayers.Geometry.MultiPoint")||(className=="OpenLayers.Geometry.MultiLineString")||(className=="OpenLayers.Geometry.MultiPolygon")){for(var i=0,len=geometry.components.length;i<len;i++){rendered=this.drawGeometry(geometry.components[i],style,featureId)&&rendered;}
+return rendered;};rendered=false;if(style.display!="none"){if(style.backgroundGraphic){this.redrawBackgroundNode(geometry.id,geometry,style,featureId);}
+rendered=this.redrawNode(geometry.id,geometry,style,featureId);}
+if(rendered==false){var node=document.getElementById(geometry.id);if(node){if(node._style.backgroundGraphic){node.parentNode.removeChild(document.getElementById(geometry.id+this.BACKGROUND_ID_SUFFIX));}
+node.parentNode.removeChild(node);}}
+return rendered;},redrawNode:function(id,geometry,style,featureId){style=this.applyDefaultSymbolizer(style);var node=this.nodeFactory(id,this.getNodeType(geometry,style));node._featureId=featureId;node._boundsBottom=geometry.getBounds().bottom;node._geometryClass=geometry.CLASS_NAME;node._style=style;var drawResult=this.drawGeometryNode(node,geometry,style);if(drawResult===false){return false;}
+node=drawResult.node;if(this.indexer){var insert=this.indexer.insert(node);if(insert){this.vectorRoot.insertBefore(node,insert);}else{this.vectorRoot.appendChild(node);}}else{if(node.parentNode!==this.vectorRoot){this.vectorRoot.appendChild(node);}}
+this.postDraw(node);return drawResult.complete;},redrawBackgroundNode:function(id,geometry,style,featureId){var backgroundStyle=OpenLayers.Util.extend({},style);backgroundStyle.externalGraphic=backgroundStyle.backgroundGraphic;backgroundStyle.graphicXOffset=backgroundStyle.backgroundXOffset;backgroundStyle.graphicYOffset=backgroundStyle.backgroundYOffset;backgroundStyle.graphicZIndex=backgroundStyle.backgroundGraphicZIndex;backgroundStyle.graphicWidth=backgroundStyle.backgroundWidth||backgroundStyle.graphicWidth;backgroundStyle.graphicHeight=backgroundStyle.backgroundHeight||backgroundStyle.graphicHeight;backgroundStyle.backgroundGraphic=null;backgroundStyle.backgroundXOffset=null;backgroundStyle.backgroundYOffset=null;backgroundStyle.backgroundGraphicZIndex=null;return this.redrawNode(id+this.BACKGROUND_ID_SUFFIX,geometry,backgroundStyle,null);},drawGeometryNode:function(node,geometry,style){style=style||node._style;var options={'isFilled':style.fill===undefined?true:style.fill,'isStroked':style.stroke===undefined?!!style.strokeWidth:style.stroke};var drawn;switch(geometry.CLASS_NAME){case"OpenLayers.Geometry.Point":if(style.graphic===false){options.isFilled=false;options.isStroked=false;}
+drawn=this.drawPoint(node,geometry);break;case"OpenLayers.Geometry.LineString":options.isFilled=false;drawn=this.drawLineString(node,geometry);break;case"OpenLayers.Geometry.LinearRing":drawn=this.drawLinearRing(node,geometry);break;case"OpenLayers.Geometry.Polygon":drawn=this.drawPolygon(node,geometry);break;case"OpenLayers.Geometry.Surface":drawn=this.drawSurface(node,geometry);break;case"OpenLayers.Geometry.Rectangle":drawn=this.drawRectangle(node,geometry);break;default:break;}
+node._options=options;if(drawn!=false){return{node:this.setStyle(node,style,options,geometry),complete:drawn};}else{return false;}},postDraw:function(node){},drawPoint:function(node,geometry){},drawLineString:function(node,geometry){},drawLinearRing:function(node,geometry){},drawPolygon:function(node,geometry){},drawRectangle:function(node,geometry){},drawCircle:function(node,geometry){},drawSurface:function(node,geometry){},removeText:function(featureId){var label=document.getElementById(featureId+this.LABEL_ID_SUFFIX);if(label){this.textRoot.removeChild(label);}},getFeatureIdFromEvent:function(evt){var target=evt.target;var useElement=target&&target.correspondingUseElement;var node=useElement?useElement:(target||evt.srcElement);var featureId=node._featureId;return featureId;},eraseGeometry:function(geometry,featureId){if((geometry.CLASS_NAME=="OpenLayers.Geometry.MultiPoint")||(geometry.CLASS_NAME=="OpenLayers.Geometry.MultiLineString")||(geometry.CLASS_NAME=="OpenLayers.Geometry.MultiPolygon")||(geometry.CLASS_NAME=="OpenLayers.Geometry.Collection")){for(var i=0,len=geometry.components.length;i<len;i++){this.eraseGeometry(geometry.components[i],featureId);}}else{var element=OpenLayers.Util.getElement(geometry.id);if(element&&element.parentNode){if(element.geometry){element.geometry.destroy();element.geometry=null;}
+element.parentNode.removeChild(element);if(this.indexer){this.indexer.remove(element);}
+if(element._style.backgroundGraphic){var backgroundId=geometry.id+this.BACKGROUND_ID_SUFFIX;var bElem=OpenLayers.Util.getElement(backgroundId);if(bElem&&bElem.parentNode){bElem.parentNode.removeChild(bElem);}}}}},nodeFactory:function(id,type){var node=OpenLayers.Util.getElement(id);if(node){if(!this.nodeTypeCompare(node,type)){node.parentNode.removeChild(node);node=this.nodeFactory(id,type);}}else{node=this.createNode(type,id);}
+return node;},nodeTypeCompare:function(node,type){},createNode:function(type,id){},moveRoot:function(renderer){var root=this.root;if(renderer.root.parentNode==this.rendererRoot){root=renderer.root;}
+root.parentNode.removeChild(root);renderer.rendererRoot.appendChild(root);},getRenderLayerId:function(){return this.root.parentNode.parentNode.id;},isComplexSymbol:function(graphicName){return(graphicName!="circle")&&!!graphicName;},CLASS_NAME:"OpenLayers.Renderer.Elements"});OpenLayers.Renderer.symbol={"star":[350,75,379,161,469,161,397,215,423,301,350,250,277,301,303,215,231,161,321,161,350,75],"cross":[4,0,6,0,6,4,10,4,10,6,6,6,6,10,4,10,4,6,0,6,0,4,4,4,4,0],"x":[0,0,25,0,50,35,75,0,100,0,65,50,100,100,75,100,50,65,25,100,0,100,35,50,0,0],"square":[0,0,0,1,1,1,1,0,0,0],"triangle":[0,10,10,10,5,0,0,10]};OpenLayers.Strategy.Cluster=OpenLayers.Class(OpenLayers.Strategy,{distance:20,threshold:null,features:null,clusters:null,clustering:false,resolution:null,initialize:function(options){OpenLayers.Strategy.prototype.initialize.apply(this,[options]);},activate:function(){var activated=OpenLayers.Strategy.prototype.activate.call(this);if(activated){this.layer.events.on({"beforefeaturesadded":this.cacheFeatures,"moveend":this.cluster,scope:this});}
+return activated;},deactivate:function(){var deactivated=OpenLayers.Strategy.prototype.deactivate.call(this);if(deactivated){this.clearCache();this.layer.events.un({"beforefeaturesadded":this.cacheFeatures,"moveend":this.cluster,scope:this});}
+return deactivated;},cacheFeatures:function(event){var propagate=true;if(!this.clustering){this.clearCache();this.features=event.features;this.cluster();propagate=false;}
+return propagate;},clearCache:function(){this.features=null;},cluster:function(event){if((!event||event.zoomChanged)&&this.features){var resolution=this.layer.map.getResolution();if(resolution!=this.resolution||!this.clustersExist()){this.resolution=resolution;var clusters=[];var feature,clustered,cluster;for(var i=0;i<this.features.length;++i){feature=this.features[i];if(feature.geometry){clustered=false;for(var j=clusters.length-1;j>=0;--j){cluster=clusters[j];if(this.shouldCluster(cluster,feature)){this.addToCluster(cluster,feature);clustered=true;break;}}
+if(!clustered){clusters.push(this.createCluster(this.features[i]));}}}
+this.layer.removeAllFeatures();if(clusters.length>0){if(this.threshold>1){var clone=clusters.slice();clusters=[];var candidate;for(var i=0,len=clone.length;i<len;++i){candidate=clone[i];if(candidate.attributes.count<this.threshold){Array.prototype.push.apply(clusters,candidate.cluster);}else{clusters.push(candidate);}}}
+this.clustering=true;this.layer.addFeatures(clusters);this.clustering=false;}
+this.clusters=clusters;}}},clustersExist:function(){var exist=false;if(this.clusters&&this.clusters.length>0&&this.clusters.length==this.layer.features.length){exist=true;for(var i=0;i<this.clusters.length;++i){if(this.clusters[i]!=this.layer.features[i]){exist=false;break;}}}
+return exist;},shouldCluster:function(cluster,feature){var cc=cluster.geometry.getBounds().getCenterLonLat();var fc=feature.geometry.getBounds().getCenterLonLat();var distance=(Math.sqrt(Math.pow((cc.lon-fc.lon),2)+Math.pow((cc.lat-fc.lat),2))/this.resolution);return(distance<=this.distance);},addToCluster:function(cluster,feature){cluster.cluster.push(feature);cluster.attributes.count+=1;},createCluster:function(feature){var center=feature.geometry.getBounds().getCenterLonLat();var cluster=new OpenLayers.Feature.Vector(new OpenLayers.Geometry.Point(center.lon,center.lat),{count:1});cluster.cluster=[feature];return cluster;},CLASS_NAME:"OpenLayers.Strategy.Cluster"});OpenLayers.Strategy.Fixed=OpenLayers.Class(OpenLayers.Strategy,{preload:false,initialize:function(options){OpenLayers.Strategy.prototype.initialize.apply(this,[options]);},destroy:function(){OpenLayers.Strategy.prototype.destroy.apply(this,arguments);},activate:function(){if(OpenLayers.Strategy.prototype.activate.apply(this,arguments)){this.layer.events.on({"refresh":this.load,scope:this});if(this.layer.visibility==true||this.preload){this.load();}else{this.layer.events.on({"visibilitychanged":this.load,scope:this});}
+return true;}
+return false;},deactivate:function(){var deactivated=OpenLayers.Strategy.prototype.deactivate.call(this);if(deactivated){this.layer.events.un({"refresh":this.load,"visibilitychanged":this.load,scope:this});}
+return deactivated;},load:function(options){this.layer.events.triggerEvent("loadstart");this.layer.protocol.read(OpenLayers.Util.applyDefaults({callback:this.merge,filter:this.layer.filter,scope:this},options));this.layer.events.un({"visibilitychanged":this.load,scope:this});},merge:function(resp){this.layer.destroyFeatures();var features=resp.features;if(features&&features.length>0){var remote=this.layer.projection;var local=this.layer.map.getProjectionObject();if(!local.equals(remote)){var geom;for(var i=0,len=features.length;i<len;++i){geom=features[i].geometry;if(geom){geom.transform(remote,local);}}}
+this.layer.addFeatures(features);}
+this.layer.events.triggerEvent("loadend");},CLASS_NAME:"OpenLayers.Strategy.Fixed"});OpenLayers.Strategy.Paging=OpenLayers.Class(OpenLayers.Strategy,{features:null,length:10,num:null,paging:false,initialize:function(options){OpenLayers.Strategy.prototype.initialize.apply(this,[options]);},activate:function(){var activated=OpenLayers.Strategy.prototype.activate.call(this);if(activated){this.layer.events.on({"beforefeaturesadded":this.cacheFeatures,scope:this});}
+return activated;},deactivate:function(){var deactivated=OpenLayers.Strategy.prototype.deactivate.call(this);if(deactivated){this.clearCache();this.layer.events.un({"beforefeaturesadded":this.cacheFeatures,scope:this});}
+return deactivated;},cacheFeatures:function(event){if(!this.paging){this.clearCache();this.features=event.features;this.pageNext(event);}},clearCache:function(){if(this.features){for(var i=0;i<this.features.length;++i){this.features[i].destroy();}}
+this.features=null;this.num=null;},pageCount:function(){var numFeatures=this.features?this.features.length:0;return Math.ceil(numFeatures/this.length);},pageNum:function(){return this.num;},pageLength:function(newLength){if(newLength&&newLength>0){this.length=newLength;}
+return this.length;},pageNext:function(event){var changed=false;if(this.features){if(this.num===null){this.num=-1;}
+var start=(this.num+1)*this.length;changed=this.page(start,event);}
+return changed;},pagePrevious:function(){var changed=false;if(this.features){if(this.num===null){this.num=this.pageCount();}
+var start=(this.num-1)*this.length;changed=this.page(start);}
+return changed;},page:function(start,event){var changed=false;if(this.features){if(start>=0&&start<this.features.length){var num=Math.floor(start/this.length);if(num!=this.num){this.paging=true;var features=this.features.slice(start,start+this.length);this.layer.removeFeatures(this.layer.features);this.num=num;if(event&&event.features){event.features=features;}else{this.layer.addFeatures(features);}
+this.paging=false;changed=true;}}}
+return changed;},CLASS_NAME:"OpenLayers.Strategy.Paging"});OpenLayers.Strategy.Refresh=OpenLayers.Class(OpenLayers.Strategy,{force:false,interval:0,timer:null,initialize:function(options){OpenLayers.Strategy.prototype.initialize.apply(this,[options]);},activate:function(){var activated=OpenLayers.Strategy.prototype.activate.call(this);if(activated){if(this.layer.visibility===true){this.start();}
+this.layer.events.on({"visibilitychanged":this.reset,scope:this});}
+return activated;},deactivate:function(){var deactivated=OpenLayers.Strategy.prototype.deactivate.call(this);if(deactivated){this.stop();}
+return deactivated;},reset:function(){if(this.layer.visibility===true){this.start();}else{this.stop();}},start:function(){if(this.interval&&typeof this.interval==="number"&&this.interval>0){this.timer=window.setInterval(OpenLayers.Function.bind(this.refresh,this),this.interval);}},refresh:function(){if(this.layer&&this.layer.refresh&&typeof this.layer.refresh=="function"){this.layer.refresh({force:this.force});}},stop:function(){if(this.timer!==null){window.clearInterval(this.timer);this.timer=null;}},CLASS_NAME:"OpenLayers.Strategy.Refresh"});OpenLayers.Strategy.Save=OpenLayers.Class(OpenLayers.Strategy,{EVENT_TYPES:["start","success","fail"],events:null,auto:false,timer:null,initialize:function(options){OpenLayers.Strategy.prototype.initialize.apply(this,[options]);this.events=new OpenLayers.Events(this,null,this.EVENT_TYPES);},activate:function(){var activated=OpenLayers.Strategy.prototype.activate.call(this);if(activated){if(this.auto){if(typeof this.auto==="number"){this.timer=window.setInterval(OpenLayers.Function.bind(this.save,this),this.auto*1000);}else{this.layer.events.on({"featureadded":this.triggerSave,"afterfeaturemodified":this.triggerSave,scope:this});}}}
+return activated;},deactivate:function(){var deactivated=OpenLayers.Strategy.prototype.deactivate.call(this);if(deactivated){if(this.auto){if(typeof this.auto==="number"){window.clearInterval(this.timer);}else{this.layer.events.un({"featureadded":this.triggerSave,"afterfeaturemodified":this.triggerSave,scope:this});}}}
+return deactivated;},triggerSave:function(event){var feature=event.feature;if(feature.state===OpenLayers.State.INSERT||feature.state===OpenLayers.State.UPDATE||feature.state===OpenLayers.State.DELETE){this.save([event.feature]);}},save:function(features){if(!features){features=this.layer.features;}
+this.events.triggerEvent("start",{features:features});var remote=this.layer.projection;var local=this.layer.map.getProjectionObject();if(!local.equals(remote)){var len=features.length;var clones=new Array(len);var orig,clone;for(var i=0;i<len;++i){orig=features[i];clone=orig.clone();clone.fid=orig.fid;clone.state=orig.state;if(orig.url){clone.url=orig.url;}
+clone._original=orig;clone.geometry.transform(local,remote);clones[i]=clone;}
+features=clones;}
+this.layer.protocol.commit(features,{callback:this.onCommit,scope:this});},onCommit:function(response){var evt={"response":response};if(response.success()){var features=response.reqFeatures;var state,feature;var destroys=[];var insertIds=response.insertIds||[];var j=0;for(var i=0,len=features.length;i<len;++i){feature=features[i];feature=feature._original||feature;state=feature.state;if(state){if(state==OpenLayers.State.DELETE){destroys.push(feature);}else if(state==OpenLayers.State.INSERT){feature.fid=insertIds[j];++j;}
+feature.state=null;}}
+if(destroys.length>0){this.layer.destroyFeatures(destroys);}
+this.events.triggerEvent("success",evt);}else{this.events.triggerEvent("fail",evt);}},CLASS_NAME:"OpenLayers.Strategy.Save"});OpenLayers.Symbolizer.Line=OpenLayers.Class(OpenLayers.Symbolizer,{strokeColor:null,strokeOpacity:null,strokeWidth:null,strokeLinecap:null,strokeDashstyle:null,initialize:function(config){OpenLayers.Symbolizer.prototype.initialize.apply(this,arguments);},CLASS_NAME:"OpenLayers.Symbolizer.Line"});OpenLayers.Symbolizer.Point=OpenLayers.Class(OpenLayers.Symbolizer,{strokeColor:null,strokeOpacity:null,strokeWidth:null,strokeLinecap:null,strokeDashstyle:null,fillColor:null,fillOpacity:null,pointRadius:null,externalGraphic:null,graphicWidth:null,graphicHeight:null,graphicOpacity:null,graphicXOffset:null,graphicYOffset:null,rotation:null,graphicName:null,initialize:function(config){OpenLayers.Symbolizer.prototype.initialize.apply(this,arguments);},CLASS_NAME:"OpenLayers.Symbolizer.Point"});OpenLayers.Symbolizer.Polygon=OpenLayers.Class(OpenLayers.Symbolizer,{strokeColor:null,strokeOpacity:null,strokeWidth:null,strokeLinecap:null,strokeDashstyle:null,fillColor:null,fillOpacity:null,initialize:function(config){OpenLayers.Symbolizer.prototype.initialize.apply(this,arguments);},CLASS_NAME:"OpenLayers.Symbolizer.Polygon"});OpenLayers.Symbolizer.Raster=OpenLayers.Class(OpenLayers.Symbolizer,{initialize:function(config){OpenLayers.Symbolizer.prototype.initialize.apply(this,arguments);},CLASS_NAME:"OpenLayers.Symbolizer.Raster"});OpenLayers.Symbolizer.Text=OpenLayers.Class(OpenLayers.Symbolizer,{label:null,fontFamily:null,fontSize:null,fontWeight:null,fontStyle:null,initialize:function(config){OpenLayers.Symbolizer.prototype.initialize.apply(this,arguments);},CLASS_NAME:"OpenLayers.Symbolizer.Text"});OpenLayers.Tween=OpenLayers.Class({INTERVAL:10,easing:null,begin:null,finish:null,duration:null,callbacks:null,time:null,interval:null,playing:false,initialize:function(easing){this.easing=(easing)?easing:OpenLayers.Easing.Expo.easeOut;},start:function(begin,finish,duration,options){this.playing=true;this.begin=begin;this.finish=finish;this.duration=duration;this.callbacks=options.callbacks;this.time=0;if(this.interval){window.clearInterval(this.interval);this.interval=null;}
+if(this.callbacks&&this.callbacks.start){this.callbacks.start.call(this,this.begin);}
+this.interval=window.setInterval(OpenLayers.Function.bind(this.play,this),this.INTERVAL);},stop:function(){if(!this.playing){return;}
+if(this.callbacks&&this.callbacks.done){this.callbacks.done.call(this,this.finish);}
+window.clearInterval(this.interval);this.interval=null;this.playing=false;},play:function(){var value={};for(var i in this.begin){var b=this.begin[i];var f=this.finish[i];if(b==null||f==null||isNaN(b)||isNaN(f)){OpenLayers.Console.error('invalid value for Tween');}
+var c=f-b;value[i]=this.easing.apply(this,[this.time,b,c,this.duration]);}
+this.time++;if(this.callbacks&&this.callbacks.eachStep){this.callbacks.eachStep.call(this,value);}
+if(this.time>this.duration){this.stop();}},CLASS_NAME:"OpenLayers.Tween"});OpenLayers.Easing={CLASS_NAME:"OpenLayers.Easing"};OpenLayers.Easing.Linear={easeIn:function(t,b,c,d){return c*t/d+b;},easeOut:function(t,b,c,d){return c*t/d+b;},easeInOut:function(t,b,c,d){return c*t/d+b;},CLASS_NAME:"OpenLayers.Easing.Linear"};OpenLayers.Easing.Expo={easeIn:function(t,b,c,d){return(t==0)?b:c*Math.pow(2,10*(t/d-1))+b;},easeOut:function(t,b,c,d){return(t==d)?b+c:c*(-Math.pow(2,-10*t/d)+1)+b;},easeInOut:function(t,b,c,d){if(t==0)return b;if(t==d)return b+c;if((t/=d/2)<1)return c/2*Math.pow(2,10*(t-1))+b;return c/2*(-Math.pow(2,-10*--t)+2)+b;},CLASS_NAME:"OpenLayers.Easing.Expo"};OpenLayers.Easing.Quad={easeIn:function(t,b,c,d){return c*(t/=d)*t+b;},easeOut:function(t,b,c,d){return-c*(t/=d)*(t-2)+b;},easeInOut:function(t,b,c,d){if((t/=d/2)<1)return c/2*t*t+b;return-c/2*((--t)*(t-2)-1)+b;},CLASS_NAME:"OpenLayers.Easing.Quad"};OpenLayers.Control.ArgParser=OpenLayers.Class(OpenLayers.Control,{center:null,zoom:null,layers:null,displayProjection:null,initialize:function(options){OpenLayers.Control.prototype.initialize.apply(this,arguments);},setMap:function(map){OpenLayers.Control.prototype.setMap.apply(this,arguments);for(var i=0,len=this.map.controls.length;i<len;i++){var control=this.map.controls[i];if((control!=this)&&(control.CLASS_NAME=="OpenLayers.Control.ArgParser")){if(control.displayProjection!=this.displayProjection){this.displayProjection=control.displayProjection;}
+break;}}
+if(i==this.map.controls.length){var args=OpenLayers.Util.getParameters();if(args.layers){this.layers=args.layers;this.map.events.register('addlayer',this,this.configureLayers);this.configureLayers();}
+if(args.lat&&args.lon){this.center=new OpenLayers.LonLat(parseFloat(args.lon),parseFloat(args.lat));if(args.zoom){this.zoom=parseInt(args.zoom);}
+this.map.events.register('changebaselayer',this,this.setCenter);this.setCenter();}}},setCenter:function(){if(this.map.baseLayer){this.map.events.unregister('changebaselayer',this,this.setCenter);if(this.displayProjection){this.center.transform(this.displayProjection,this.map.getProjectionObject());}
+this.map.setCenter(this.center,this.zoom);}},configureLayers:function(){if(this.layers.length==this.map.layers.length){this.map.events.unregister('addlayer',this,this.configureLayers);for(var i=0,len=this.layers.length;i<len;i++){var layer=this.map.layers[i];var c=this.layers.charAt(i);if(c=="B"){this.map.setBaseLayer(layer);}else if((c=="T")||(c=="F")){layer.setVisibility(c=="T");}}}},CLASS_NAME:"OpenLayers.Control.ArgParser"});OpenLayers.Control.Attribution=OpenLayers.Class(OpenLayers.Control,{separator:", ",initialize:function(options){OpenLayers.Control.prototype.initialize.apply(this,arguments);},destroy:function(){this.map.events.un({"removelayer":this.updateAttribution,"addlayer":this.updateAttribution,"changelayer":this.updateAttribution,"changebaselayer":this.updateAttribution,scope:this});OpenLayers.Control.prototype.destroy.apply(this,arguments);},draw:function(){OpenLayers.Control.prototype.draw.apply(this,arguments);this.map.events.on({'changebaselayer':this.updateAttribution,'changelayer':this.updateAttribution,'addlayer':this.updateAttribution,'removelayer':this.updateAttribution,scope:this});this.updateAttribution();return this.div;},updateAttribution:function(){var attributions=[];if(this.map&&this.map.layers){for(var i=0,len=this.map.layers.length;i<len;i++){var layer=this.map.layers[i];if(layer.attribution&&layer.getVisibility()){if(OpenLayers.Util.indexOf(attributions,layer.attribution)===-1){attributions.push(layer.attribution);}}}
+this.div.innerHTML=attributions.join(this.separator);}},CLASS_NAME:"OpenLayers.Control.Attribution"});OpenLayers.Control.Button=OpenLayers.Class(OpenLayers.Control,{type:OpenLayers.Control.TYPE_BUTTON,trigger:function(){},CLASS_NAME:"OpenLayers.Control.Button"});OpenLayers.Control.Graticule=OpenLayers.Class(OpenLayers.Control,{autoActivate:true,intervals:[45,30,20,10,5,2,1,0.5,0.2,0.1,0.05,0.01,0.005,0.002,0.001],displayInLayerSwitcher:true,visible:true,numPoints:50,targetSize:200,layerName:null,labelled:true,labelFormat:'dm',lineSymbolizer:{strokeColor:"#333",strokeWidth:1,strokeOpacity:0.5},labelSymbolizer:{},gratLayer:null,initialize:function(options){options=options||{};options.layerName=options.layerName||OpenLayers.i18n("graticule");OpenLayers.Control.prototype.initialize.apply(this,[options]);this.labelSymbolizer.stroke=false;this.labelSymbolizer.fill=false;this.labelSymbolizer.label="${label}";this.labelSymbolizer.labelAlign="${labelAlign}";this.labelSymbolizer.labelXOffset="${xOffset}";this.labelSymbolizer.labelYOffset="${yOffset}";},destroy:function(){this.deactivate();OpenLayers.Control.prototype.destroy.apply(this,arguments);if(this.gratLayer){this.gratLayer.destroy();this.gratLayer=null;}},draw:function(){OpenLayers.Control.prototype.draw.apply(this,arguments);if(!this.gratLayer){var gratStyle=new OpenLayers.Style({},{rules:[new OpenLayers.Rule({'symbolizer':{"Point":this.labelSymbolizer,"Line":this.lineSymbolizer}})]});this.gratLayer=new OpenLayers.Layer.Vector(this.layerName,{styleMap:new OpenLayers.StyleMap({'default':gratStyle}),visibility:this.visible,displayInLayerSwitcher:this.displayInLayerSwitcher});}
+return this.div;},activate:function(){if(OpenLayers.Control.prototype.activate.apply(this,arguments)){this.map.addLayer(this.gratLayer);this.map.events.register('moveend',this,this.update);this.update();return true;}else{return false;}},deactivate:function(){if(OpenLayers.Control.prototype.deactivate.apply(this,arguments)){this.map.events.unregister('moveend',this,this.update);this.map.removeLayer(this.gratLayer);return true;}else{return false;}},update:function(){var mapBounds=this.map.getExtent();if(!mapBounds){return;}
+this.gratLayer.destroyFeatures();var llProj=new OpenLayers.Projection("EPSG:4326");var mapProj=this.map.getProjectionObject();var mapRes=this.map.getResolution();if(mapProj.proj&&mapProj.proj.projName=="longlat"){this.numPoints=1;}
+var mapCenter=this.map.getCenter();var mapCenterLL=new OpenLayers.Pixel(mapCenter.lon,mapCenter.lat);OpenLayers.Projection.transform(mapCenterLL,mapProj,llProj);var testSq=this.targetSize*mapRes;testSq*=testSq;var llInterval;for(var i=0;i<this.intervals.length;++i){llInterval=this.intervals[i];var delta=llInterval/2;var p1=mapCenterLL.offset(new OpenLayers.Pixel(-delta,-delta));var p2=mapCenterLL.offset(new OpenLayers.Pixel(delta,delta));OpenLayers.Projection.transform(p1,llProj,mapProj);OpenLayers.Projection.transform(p2,llProj,mapProj);var distSq=(p1.x-p2.x)*(p1.x-p2.x)+(p1.y-p2.y)*(p1.y-p2.y);if(distSq<=testSq){break;}}
+mapCenterLL.x=Math.floor(mapCenterLL.x/llInterval)*llInterval;mapCenterLL.y=Math.floor(mapCenterLL.y/llInterval)*llInterval;var iter=0;var centerLonPoints=[mapCenterLL.clone()];var newPoint=mapCenterLL.clone();var mapXY;do{newPoint=newPoint.offset(new OpenLayers.Pixel(0,llInterval));mapXY=OpenLayers.Projection.transform(newPoint.clone(),llProj,mapProj);centerLonPoints.unshift(newPoint);}while(mapBounds.containsPixel(mapXY)&&++iter<1000);newPoint=mapCenterLL.clone();do{newPoint=newPoint.offset(new OpenLayers.Pixel(0,-llInterval));mapXY=OpenLayers.Projection.transform(newPoint.clone(),llProj,mapProj);centerLonPoints.push(newPoint);}while(mapBounds.containsPixel(mapXY)&&++iter<1000);iter=0;var centerLatPoints=[mapCenterLL.clone()];newPoint=mapCenterLL.clone();do{newPoint=newPoint.offset(new OpenLayers.Pixel(-llInterval,0));mapXY=OpenLayers.Projection.transform(newPoint.clone(),llProj,mapProj);centerLatPoints.unshift(newPoint);}while(mapBounds.containsPixel(mapXY)&&++iter<1000);newPoint=mapCenterLL.clone();do{newPoint=newPoint.offset(new OpenLayers.Pixel(llInterval,0));mapXY=OpenLayers.Projection.transform(newPoint.clone(),llProj,mapProj);centerLatPoints.push(newPoint);}while(mapBounds.containsPixel(mapXY)&&++iter<1000);var lines=[];for(var i=0;i<centerLatPoints.length;++i){var lon=centerLatPoints[i].x;var pointList=[];var labelPoint=null;var latEnd=Math.min(centerLonPoints[0].y,90);var latStart=Math.max(centerLonPoints[centerLonPoints.length-1].y,-90);var latDelta=(latEnd-latStart)/this.numPoints;var lat=latStart;for(var j=0;j<=this.numPoints;++j){var gridPoint=new OpenLayers.Geometry.Point(lon,lat);gridPoint.transform(llProj,mapProj);pointList.push(gridPoint);lat+=latDelta;if(gridPoint.y>=mapBounds.bottom&&!labelPoint){labelPoint=gridPoint;}}
+if(this.labelled){var labelPos=new OpenLayers.Geometry.Point(labelPoint.x,mapBounds.bottom);var labelAttrs={value:lon,label:this.labelled?OpenLayers.Util.getFormattedLonLat(lon,"lon",this.labelFormat):"",labelAlign:"cb",xOffset:0,yOffset:2};this.gratLayer.addFeatures(new OpenLayers.Feature.Vector(labelPos,labelAttrs));}
+var geom=new OpenLayers.Geometry.LineString(pointList);lines.push(new OpenLayers.Feature.Vector(geom));}
+for(var j=0;j<centerLonPoints.length;++j){lat=centerLonPoints[j].y;if(lat<-90||lat>90){continue;}
+var pointList=[];var lonStart=centerLatPoints[0].x;var lonEnd=centerLatPoints[centerLatPoints.length-1].x;var lonDelta=(lonEnd-lonStart)/this.numPoints;var lon=lonStart;var labelPoint=null;for(var i=0;i<=this.numPoints;++i){var gridPoint=new OpenLayers.Geometry.Point(lon,lat);gridPoint.transform(llProj,mapProj);pointList.push(gridPoint);lon+=lonDelta;if(gridPoint.x<mapBounds.right){labelPoint=gridPoint;}}
+if(this.labelled){var labelPos=new OpenLayers.Geometry.Point(mapBounds.right,labelPoint.y);var labelAttrs={value:lat,label:this.labelled?OpenLayers.Util.getFormattedLonLat(lat,"lat",this.labelFormat):"",labelAlign:"rb",xOffset:-2,yOffset:2};this.gratLayer.addFeatures(new OpenLayers.Feature.Vector(labelPos,labelAttrs));}
+var geom=new OpenLayers.Geometry.LineString(pointList);lines.push(new OpenLayers.Feature.Vector(geom));}
+this.gratLayer.addFeatures(lines);},CLASS_NAME:"OpenLayers.Control.Graticule"});OpenLayers.Control.LayerSwitcher=OpenLayers.Class(OpenLayers.Control,{roundedCorner:true,roundedCornerColor:"darkblue",layerStates:null,layersDiv:null,baseLayersDiv:null,baseLayers:null,dataLbl:null,dataLayersDiv:null,dataLayers:null,minimizeDiv:null,maximizeDiv:null,ascending:true,initialize:function(options){OpenLayers.Control.prototype.initialize.apply(this,arguments);this.layerStates=[];},destroy:function(){OpenLayers.Event.stopObservingElement(this.div);OpenLayers.Event.stopObservingElement(this.minimizeDiv);OpenLayers.Event.stopObservingElement(this.maximizeDiv);this.clearLayersArray("base");this.clearLayersArray("data");this.map.events.un({"addlayer":this.redraw,"changelayer":this.redraw,"removelayer":this.redraw,"changebaselayer":this.redraw,scope:this});OpenLayers.Control.prototype.destroy.apply(this,arguments);},setMap:function(map){OpenLayers.Control.prototype.setMap.apply(this,arguments);this.map.events.on({"addlayer":this.redraw,"changelayer":this.redraw,"removelayer":this.redraw,"changebaselayer":this.redraw,scope:this});},draw:function(){OpenLayers.Control.prototype.draw.apply(this);this.loadContents();if(!this.outsideViewport){this.minimizeControl();}
+this.redraw();return this.div;},clearLayersArray:function(layersType){var layers=this[layersType+"Layers"];if(layers){for(var i=0,len=layers.length;i<len;i++){var layer=layers[i];OpenLayers.Event.stopObservingElement(layer.inputElem);OpenLayers.Event.stopObservingElement(layer.labelSpan);}}
+this[layersType+"LayersDiv"].innerHTML="";this[layersType+"Layers"]=[];},checkRedraw:function(){var redraw=false;if(!this.layerStates.length||(this.map.layers.length!=this.layerStates.length)){redraw=true;}else{for(var i=0,len=this.layerStates.length;i<len;i++){var layerState=this.layerStates[i];var layer=this.map.layers[i];if((layerState.name!=layer.name)||(layerState.inRange!=layer.inRange)||(layerState.id!=layer.id)||(layerState.visibility!=layer.visibility)){redraw=true;break;}}}
+return redraw;},redraw:function(){if(!this.checkRedraw()){return this.div;}
+this.clearLayersArray("base");this.clearLayersArray("data");var containsOverlays=false;var containsBaseLayers=false;var len=this.map.layers.length;this.layerStates=new Array(len);for(var i=0;i<len;i++){var layer=this.map.layers[i];this.layerStates[i]={'name':layer.name,'visibility':layer.visibility,'inRange':layer.inRange,'id':layer.id};}
+var layers=this.map.layers.slice();if(!this.ascending){layers.reverse();}
+for(var i=0,len=layers.length;i<len;i++){var layer=layers[i];var baseLayer=layer.isBaseLayer;if(layer.displayInLayerSwitcher){if(baseLayer){containsBaseLayers=true;}else{containsOverlays=true;}
+var checked=(baseLayer)?(layer==this.map.baseLayer):layer.getVisibility();var inputElem=document.createElement("input");inputElem.id=this.id+"_input_"+layer.name;inputElem.name=(baseLayer)?this.id+"_baseLayers":layer.name;inputElem.type=(baseLayer)?"radio":"checkbox";inputElem.value=layer.name;inputElem.checked=checked;inputElem.defaultChecked=checked;if(!baseLayer&&!layer.inRange){inputElem.disabled=true;}
+var context={'inputElem':inputElem,'layer':layer,'layerSwitcher':this};OpenLayers.Event.observe(inputElem,"mouseup",OpenLayers.Function.bindAsEventListener(this.onInputClick,context));var labelSpan=document.createElement("span");OpenLayers.Element.addClass(labelSpan,"labelSpan")
+if(!baseLayer&&!layer.inRange){labelSpan.style.color="gray";}
+labelSpan.innerHTML=layer.name;labelSpan.style.verticalAlign=(baseLayer)?"bottom":"baseline";OpenLayers.Event.observe(labelSpan,"click",OpenLayers.Function.bindAsEventListener(this.onInputClick,context));var br=document.createElement("br");var groupArray=(baseLayer)?this.baseLayers:this.dataLayers;groupArray.push({'layer':layer,'inputElem':inputElem,'labelSpan':labelSpan});var groupDiv=(baseLayer)?this.baseLayersDiv:this.dataLayersDiv;groupDiv.appendChild(inputElem);groupDiv.appendChild(labelSpan);groupDiv.appendChild(br);}}
+this.dataLbl.style.display=(containsOverlays)?"":"none";this.baseLbl.style.display=(containsBaseLayers)?"":"none";return this.div;},onInputClick:function(e){if(!this.inputElem.disabled){if(this.inputElem.type=="radio"){this.inputElem.checked=true;this.layer.map.setBaseLayer(this.layer);}else{this.inputElem.checked=!this.inputElem.checked;this.layerSwitcher.updateMap();}}
+OpenLayers.Event.stop(e);},onLayerClick:function(e){this.updateMap();},updateMap:function(){for(var i=0,len=this.baseLayers.length;i<len;i++){var layerEntry=this.baseLayers[i];if(layerEntry.inputElem.checked){this.map.setBaseLayer(layerEntry.layer,false);}}
+for(var i=0,len=this.dataLayers.length;i<len;i++){var layerEntry=this.dataLayers[i];layerEntry.layer.setVisibility(layerEntry.inputElem.checked);}},maximizeControl:function(e){this.div.style.width="";this.div.style.height="";this.showControls(false);if(e!=null){OpenLayers.Event.stop(e);}},minimizeControl:function(e){this.div.style.width="0px";this.div.style.height="0px";this.showControls(true);if(e!=null){OpenLayers.Event.stop(e);}},showControls:function(minimize){this.maximizeDiv.style.display=minimize?"":"none";this.minimizeDiv.style.display=minimize?"none":"";this.layersDiv.style.display=minimize?"none":"";},loadContents:function(){OpenLayers.Event.observe(this.div,"mouseup",OpenLayers.Function.bindAsEventListener(this.mouseUp,this));OpenLayers.Event.observe(this.div,"click",this.ignoreEvent);OpenLayers.Event.observe(this.div,"mousedown",OpenLayers.Function.bindAsEventListener(this.mouseDown,this));OpenLayers.Event.observe(this.div,"dblclick",this.ignoreEvent);this.layersDiv=document.createElement("div");this.layersDiv.id=this.id+"_layersDiv";OpenLayers.Element.addClass(this.layersDiv,"layersDiv");this.baseLbl=document.createElement("div");this.baseLbl.innerHTML=OpenLayers.i18n("baseLayer");OpenLayers.Element.addClass(this.baseLbl,"baseLbl");this.baseLayersDiv=document.createElement("div");OpenLayers.Element.addClass(this.baseLayersDiv,"baseLayersDiv");this.dataLbl=document.createElement("div");this.dataLbl.innerHTML=OpenLayers.i18n("overlays");OpenLayers.Element.addClass(this.dataLbl,"dataLbl");this.dataLayersDiv=document.createElement("div");OpenLayers.Element.addClass(this.dataLayersDiv,"dataLayersDiv");if(this.ascending){this.layersDiv.appendChild(this.baseLbl);this.layersDiv.appendChild(this.baseLayersDiv);this.layersDiv.appendChild(this.dataLbl);this.layersDiv.appendChild(this.dataLayersDiv);}else{this.layersDiv.appendChild(this.dataLbl);this.layersDiv.appendChild(this.dataLayersDiv);this.layersDiv.appendChild(this.baseLbl);this.layersDiv.appendChild(this.baseLayersDiv);}
+this.div.appendChild(this.layersDiv);if(this.roundedCorner){OpenLayers.Rico.Corner.round(this.div,{corners:"tl bl",bgColor:"transparent",color:this.roundedCornerColor,blend:false});OpenLayers.Rico.Corner.changeOpacity(this.layersDiv,0.75);}
+var imgLocation=OpenLayers.Util.getImagesLocation();var sz=new OpenLayers.Size(18,18);var img=imgLocation+'layer-switcher-maximize.png';this.maximizeDiv=OpenLayers.Util.createAlphaImageDiv("OpenLayers_Control_MaximizeDiv",null,sz,img,"absolute");OpenLayers.Element.addClass(this.maximizeDiv,"maximizeDiv");this.maximizeDiv.style.display="none";OpenLayers.Event.observe(this.maximizeDiv,"click",OpenLayers.Function.bindAsEventListener(this.maximizeControl,this));this.div.appendChild(this.maximizeDiv);var img=imgLocation+'layer-switcher-minimize.png';var sz=new OpenLayers.Size(18,18);this.minimizeDiv=OpenLayers.Util.createAlphaImageDiv("OpenLayers_Control_MinimizeDiv",null,sz,img,"absolute");OpenLayers.Element.addClass(this.minimizeDiv,"minimizeDiv");this.minimizeDiv.style.display="none";OpenLayers.Event.observe(this.minimizeDiv,"click",OpenLayers.Function.bindAsEventListener(this.minimizeControl,this));this.div.appendChild(this.minimizeDiv);},ignoreEvent:function(evt){OpenLayers.Event.stop(evt);},mouseDown:function(evt){this.isMouseDown=true;this.ignoreEvent(evt);},mouseUp:function(evt){if(this.isMouseDown){this.isMouseDown=false;this.ignoreEvent(evt);}},CLASS_NAME:"OpenLayers.Control.LayerSwitcher"});OpenLayers.Control.MouseDefaults=OpenLayers.Class(OpenLayers.Control,{performedDrag:false,wheelObserver:null,initialize:function(){OpenLayers.Control.prototype.initialize.apply(this,arguments);},destroy:function(){if(this.handler){this.handler.destroy();}
+this.handler=null;this.map.events.un({"click":this.defaultClick,"dblclick":this.defaultDblClick,"mousedown":this.defaultMouseDown,"mouseup":this.defaultMouseUp,"mousemove":this.defaultMouseMove,"mouseout":this.defaultMouseOut,scope:this});OpenLayers.Event.stopObserving(window,"DOMMouseScroll",this.wheelObserver);OpenLayers.Event.stopObserving(window,"mousewheel",this.wheelObserver);OpenLayers.Event.stopObserving(document,"mousewheel",this.wheelObserver);this.wheelObserver=null;OpenLayers.Control.prototype.destroy.apply(this,arguments);},draw:function(){this.map.events.on({"click":this.defaultClick,"dblclick":this.defaultDblClick,"mousedown":this.defaultMouseDown,"mouseup":this.defaultMouseUp,"mousemove":this.defaultMouseMove,"mouseout":this.defaultMouseOut,scope:this});this.registerWheelEvents();},registerWheelEvents:function(){this.wheelObserver=OpenLayers.Function.bindAsEventListener(this.onWheelEvent,this);OpenLayers.Event.observe(window,"DOMMouseScroll",this.wheelObserver);OpenLayers.Event.observe(window,"mousewheel",this.wheelObserver);OpenLayers.Event.observe(document,"mousewheel",this.wheelObserver);},defaultClick:function(evt){if(!OpenLayers.Event.isLeftClick(evt)){return;}
+var notAfterDrag=!this.performedDrag;this.performedDrag=false;return notAfterDrag;},defaultDblClick:function(evt){var newCenter=this.map.getLonLatFromViewPortPx(evt.xy);this.map.setCenter(newCenter,this.map.zoom+1);OpenLayers.Event.stop(evt);return false;},defaultMouseDown:function(evt){if(!OpenLayers.Event.isLeftClick(evt)){return;}
+this.mouseDragStart=evt.xy.clone();this.performedDrag=false;if(evt.shiftKey){this.map.div.style.cursor="crosshair";this.zoomBox=OpenLayers.Util.createDiv('zoomBox',this.mouseDragStart,null,null,"absolute","2px solid red");this.zoomBox.style.backgroundColor="white";this.zoomBox.style.filter="alpha(opacity=50)";this.zoomBox.style.opacity="0.50";this.zoomBox.style.fontSize="1px";this.zoomBox.style.zIndex=this.map.Z_INDEX_BASE["Popup"]-1;this.map.viewPortDiv.appendChild(this.zoomBox);}
+document.onselectstart=OpenLayers.Function.False;OpenLayers.Event.stop(evt);},defaultMouseMove:function(evt){this.mousePosition=evt.xy.clone();if(this.mouseDragStart!=null){if(this.zoomBox){var deltaX=Math.abs(this.mouseDragStart.x-evt.xy.x);var deltaY=Math.abs(this.mouseDragStart.y-evt.xy.y);this.zoomBox.style.width=Math.max(1,deltaX)+"px";this.zoomBox.style.height=Math.max(1,deltaY)+"px";if(evt.xy.x<this.mouseDragStart.x){this.zoomBox.style.left=evt.xy.x+"px";}
+if(evt.xy.y<this.mouseDragStart.y){this.zoomBox.style.top=evt.xy.y+"px";}}else{var deltaX=this.mouseDragStart.x-evt.xy.x;var deltaY=this.mouseDragStart.y-evt.xy.y;var size=this.map.getSize();var newXY=new OpenLayers.Pixel(size.w/2+deltaX,size.h/2+deltaY);var newCenter=this.map.getLonLatFromViewPortPx(newXY);this.map.setCenter(newCenter,null,true);this.mouseDragStart=evt.xy.clone();this.map.div.style.cursor="move";}
+this.performedDrag=true;}},defaultMouseUp:function(evt){if(!OpenLayers.Event.isLeftClick(evt)){return;}
+if(this.zoomBox){this.zoomBoxEnd(evt);}else{if(this.performedDrag){this.map.setCenter(this.map.center);}}
+document.onselectstart=null;this.mouseDragStart=null;this.map.div.style.cursor="";},defaultMouseOut:function(evt){if(this.mouseDragStart!=null&&OpenLayers.Util.mouseLeft(evt,this.map.div)){if(this.zoomBox){this.removeZoomBox();}
+this.mouseDragStart=null;}},defaultWheelUp:function(evt){if(this.map.getZoom()<=this.map.getNumZoomLevels()){this.map.setCenter(this.map.getLonLatFromPixel(evt.xy),this.map.getZoom()+1);}},defaultWheelDown:function(evt){if(this.map.getZoom()>0){this.map.setCenter(this.map.getLonLatFromPixel(evt.xy),this.map.getZoom()-1);}},zoomBoxEnd:function(evt){if(this.mouseDragStart!=null){if(Math.abs(this.mouseDragStart.x-evt.xy.x)>5||Math.abs(this.mouseDragStart.y-evt.xy.y)>5){var start=this.map.getLonLatFromViewPortPx(this.mouseDragStart);var end=this.map.getLonLatFromViewPortPx(evt.xy);var top=Math.max(start.lat,end.lat);var bottom=Math.min(start.lat,end.lat);var left=Math.min(start.lon,end.lon);var right=Math.max(start.lon,end.lon);var bounds=new OpenLayers.Bounds(left,bottom,right,top);this.map.zoomToExtent(bounds);}else{var end=this.map.getLonLatFromViewPortPx(evt.xy);this.map.setCenter(new OpenLayers.LonLat((end.lon),(end.lat)),this.map.getZoom()+1);}
+this.removeZoomBox();}},removeZoomBox:function(){this.map.viewPortDiv.removeChild(this.zoomBox);this.zoomBox=null;},onWheelEvent:function(e){var inMap=false;var elem=OpenLayers.Event.element(e);while(elem!=null){if(this.map&&elem==this.map.div){inMap=true;break;}
+elem=elem.parentNode;}
+if(inMap){var delta=0;if(!e){e=window.event;}
+if(e.wheelDelta){delta=e.wheelDelta/120;if(window.opera&&window.opera.version()<9.2){delta=-delta;}}else if(e.detail){delta=-e.detail/3;}
+if(delta){e.xy=this.mousePosition;if(delta<0){this.defaultWheelDown(e);}else{this.defaultWheelUp(e);}}
+OpenLayers.Event.stop(e);}},CLASS_NAME:"OpenLayers.Control.MouseDefaults"});OpenLayers.Control.MousePosition=OpenLayers.Class(OpenLayers.Control,{autoActivate:true,element:null,prefix:'',separator:', ',suffix:'',numDigits:5,granularity:10,emptyString:null,lastXy:null,displayProjection:null,initialize:function(options){OpenLayers.Control.prototype.initialize.apply(this,arguments);},destroy:function(){this.deactivate();OpenLayers.Control.prototype.destroy.apply(this,arguments);},activate:function(){if(OpenLayers.Control.prototype.activate.apply(this,arguments)){this.map.events.register('mousemove',this,this.redraw);this.map.events.register('mouseout',this,this.reset);this.redraw();return true;}else{return false;}},deactivate:function(){if(OpenLayers.Control.prototype.deactivate.apply(this,arguments)){this.map.events.unregister('mousemove',this,this.redraw);this.map.events.unregister('mouseout',this,this.reset);this.element.innerHTML="";return true;}else{return false;}},draw:function(){OpenLayers.Control.prototype.draw.apply(this,arguments);if(!this.element){this.div.left="";this.div.top="";this.element=this.div;}
+return this.div;},redraw:function(evt){var lonLat;if(evt==null){this.reset();return;}else{if(this.lastXy==null||Math.abs(evt.xy.x-this.lastXy.x)>this.granularity||Math.abs(evt.xy.y-this.lastXy.y)>this.granularity)
+{this.lastXy=evt.xy;return;}
+lonLat=this.map.getLonLatFromPixel(evt.xy);if(!lonLat){return;}
+if(this.displayProjection){lonLat.transform(this.map.getProjectionObject(),this.displayProjection);}
+this.lastXy=evt.xy;}
+var newHtml=this.formatOutput(lonLat);if(newHtml!=this.element.innerHTML){this.element.innerHTML=newHtml;}},reset:function(evt){if(this.emptyString!=null){this.element.innerHTML=this.emptyString;}},formatOutput:function(lonLat){var digits=parseInt(this.numDigits);var newHtml=this.prefix+
+lonLat.lon.toFixed(digits)+
+this.separator+
+lonLat.lat.toFixed(digits)+
+this.suffix;return newHtml;},CLASS_NAME:"OpenLayers.Control.MousePosition"});OpenLayers.Control.Pan=OpenLayers.Class(OpenLayers.Control,{slideFactor:50,direction:null,type:OpenLayers.Control.TYPE_BUTTON,initialize:function(direction,options){this.direction=direction;this.CLASS_NAME+=this.direction;OpenLayers.Control.prototype.initialize.apply(this,[options]);},trigger:function(){switch(this.direction){case OpenLayers.Control.Pan.NORTH:this.map.pan(0,-this.slideFactor);break;case OpenLayers.Control.Pan.SOUTH:this.map.pan(0,this.slideFactor);break;case OpenLayers.Control.Pan.WEST:this.map.pan(-this.slideFactor,0);break;case OpenLayers.Control.Pan.EAST:this.map.pan(this.slideFactor,0);break;}},CLASS_NAME:"OpenLayers.Control.Pan"});OpenLayers.Control.Pan.NORTH="North";OpenLayers.Control.Pan.SOUTH="South";OpenLayers.Control.Pan.EAST="East";OpenLayers.Control.Pan.WEST="West";OpenLayers.Control.PanZoom=OpenLayers.Class(OpenLayers.Control,{slideFactor:50,slideRatio:null,buttons:null,position:null,initialize:function(options){this.position=new OpenLayers.Pixel(OpenLayers.Control.PanZoom.X,OpenLayers.Control.PanZoom.Y);OpenLayers.Control.prototype.initialize.apply(this,arguments);},destroy:function(){OpenLayers.Control.prototype.destroy.apply(this,arguments);this.removeButtons();this.buttons=null;this.position=null;},draw:function(px){OpenLayers.Control.prototype.draw.apply(this,arguments);px=this.position;this.buttons=[];var sz=new OpenLayers.Size(18,18);var centered=new OpenLayers.Pixel(px.x+sz.w/2,px.y);this._addButton("panup","north-mini.png",centered,sz);px.y=centered.y+sz.h;this._addButton("panleft","west-mini.png",px,sz);this._addButton("panright","east-mini.png",px.add(sz.w,0),sz);this._addButton("pandown","south-mini.png",centered.add(0,sz.h*2),sz);this._addButton("zoomin","zoom-plus-mini.png",centered.add(0,sz.h*3+5),sz);this._addButton("zoomworld","zoom-world-mini.png",centered.add(0,sz.h*4+5),sz);this._addButton("zoomout","zoom-minus-mini.png",centered.add(0,sz.h*5+5),sz);return this.div;},_addButton:function(id,img,xy,sz){var imgLocation=OpenLayers.Util.getImagesLocation()+img;var btn=OpenLayers.Util.createAlphaImageDiv(this.id+"_"+id,xy,sz,imgLocation,"absolute");this.div.appendChild(btn);OpenLayers.Event.observe(btn,"mousedown",OpenLayers.Function.bindAsEventListener(this.buttonDown,btn));OpenLayers.Event.observe(btn,"dblclick",OpenLayers.Function.bindAsEventListener(this.doubleClick,btn));OpenLayers.Event.observe(btn,"click",OpenLayers.Function.bindAsEventListener(this.doubleClick,btn));btn.action=id;btn.map=this.map;if(!this.slideRatio){var slideFactorPixels=this.slideFactor;var getSlideFactor=function(){return slideFactorPixels;};}else{var slideRatio=this.slideRatio;var getSlideFactor=function(dim){return this.map.getSize()[dim]*slideRatio;};}
+btn.getSlideFactor=getSlideFactor;this.buttons.push(btn);return btn;},_removeButton:function(btn){OpenLayers.Event.stopObservingElement(btn);btn.map=null;btn.getSlideFactor=null;this.div.removeChild(btn);OpenLayers.Util.removeItem(this.buttons,btn);},removeButtons:function(){for(var i=this.buttons.length-1;i>=0;--i){this._removeButton(this.buttons[i]);}},doubleClick:function(evt){OpenLayers.Event.stop(evt);return false;},buttonDown:function(evt){if(!OpenLayers.Event.isLeftClick(evt)){return;}
+switch(this.action){case"panup":this.map.pan(0,-this.getSlideFactor("h"));break;case"pandown":this.map.pan(0,this.getSlideFactor("h"));break;case"panleft":this.map.pan(-this.getSlideFactor("w"),0);break;case"panright":this.map.pan(this.getSlideFactor("w"),0);break;case"zoomin":this.map.zoomIn();break;case"zoomout":this.map.zoomOut();break;case"zoomworld":this.map.zoomToMaxExtent();break;}
+OpenLayers.Event.stop(evt);},CLASS_NAME:"OpenLayers.Control.PanZoom"});OpenLayers.Control.PanZoom.X=4;OpenLayers.Control.PanZoom.Y=4;OpenLayers.Control.Panel=OpenLayers.Class(OpenLayers.Control,{controls:null,autoActivate:true,defaultControl:null,saveState:false,activeState:null,initialize:function(options){OpenLayers.Control.prototype.initialize.apply(this,[options]);this.controls=[];this.activeState={};},destroy:function(){OpenLayers.Control.prototype.destroy.apply(this,arguments);for(var i=this.controls.length-1;i>=0;i--){if(this.controls[i].events){this.controls[i].events.un({"activate":this.redraw,"deactivate":this.redraw,scope:this});}
+OpenLayers.Event.stopObservingElement(this.controls[i].panel_div);this.controls[i].panel_div=null;}
+this.activeState=null;},activate:function(){if(OpenLayers.Control.prototype.activate.apply(this,arguments)){var control;for(var i=0,len=this.controls.length;i<len;i++){control=this.controls[i];if(control===this.defaultControl||(this.saveState&&this.activeState[control.id])){control.activate();}}
+if(this.saveState===true){this.defaultControl=null;}
+this.redraw();return true;}else{return false;}},deactivate:function(){if(OpenLayers.Control.prototype.deactivate.apply(this,arguments)){var control;for(var i=0,len=this.controls.length;i<len;i++){control=this.controls[i];this.activeState[control.id]=control.deactivate();}
+return true;}else{return false;}},draw:function(){OpenLayers.Control.prototype.draw.apply(this,arguments);this.addControlsToMap(this.controls);return this.div;},redraw:function(){if(this.div.children.length>0){for(var l=this.div.children.length,i=l-1;i>=0;i--){this.div.removeChild(this.div.children[i]);}}
+this.div.innerHTML="";if(this.active){for(var i=0,len=this.controls.length;i<len;i++){var element=this.controls[i].panel_div;if(this.controls[i].active){element.className=this.controls[i].displayClass+"ItemActive";}else{element.className=this.controls[i].displayClass+"ItemInactive";}
+this.div.appendChild(element);}}},activateControl:function(control){if(!this.active){return false;}
+if(control.type==OpenLayers.Control.TYPE_BUTTON){control.trigger();this.redraw();return;}
+if(control.type==OpenLayers.Control.TYPE_TOGGLE){if(control.active){control.deactivate();}else{control.activate();}
+this.redraw();return;}
+var c;for(var i=0,len=this.controls.length;i<len;i++){c=this.controls[i];if(c!=control&&(c.type===OpenLayers.Control.TYPE_TOOL||c.type==null)){c.deactivate();}}
+control.activate();},addControls:function(controls){if(!(controls instanceof Array)){controls=[controls];}
+this.controls=this.controls.concat(controls);for(var i=0,len=controls.length;i<len;i++){var element=document.createElement("div");controls[i].panel_div=element;if(controls[i].title!=""){controls[i].panel_div.title=controls[i].title;}
+OpenLayers.Event.observe(controls[i].panel_div,"click",OpenLayers.Function.bind(this.onClick,this,controls[i]));OpenLayers.Event.observe(controls[i].panel_div,"dblclick",OpenLayers.Function.bind(this.onDoubleClick,this,controls[i]));OpenLayers.Event.observe(controls[i].panel_div,"mousedown",OpenLayers.Function.bindAsEventListener(OpenLayers.Event.stop));}
+if(this.map){this.addControlsToMap(controls);this.redraw();}},addControlsToMap:function(controls){var control;for(var i=0,len=controls.length;i<len;i++){control=controls[i];if(control.autoActivate===true){control.autoActivate=false;this.map.addControl(control);control.autoActivate=true;}else{this.map.addControl(control);control.deactivate();}
+control.events.on({"activate":this.redraw,"deactivate":this.redraw,scope:this});}},onClick:function(ctrl,evt){OpenLayers.Event.stop(evt?evt:window.event);this.activateControl(ctrl);},onDoubleClick:function(ctrl,evt){OpenLayers.Event.stop(evt?evt:window.event);},getControlsBy:function(property,match){var test=(typeof match.test=="function");var found=OpenLayers.Array.filter(this.controls,function(item){return item[property]==match||(test&&match.test(item[property]));});return found;},getControlsByName:function(match){return this.getControlsBy("name",match);},getControlsByClass:function(match){return this.getControlsBy("CLASS_NAME",match);},CLASS_NAME:"OpenLayers.Control.Panel"});OpenLayers.Control.Scale=OpenLayers.Class(OpenLayers.Control,{element:null,geodesic:false,initialize:function(element,options){OpenLayers.Control.prototype.initialize.apply(this,[options]);this.element=OpenLayers.Util.getElement(element);},draw:function(){OpenLayers.Control.prototype.draw.apply(this,arguments);if(!this.element){this.element=document.createElement("div");this.div.appendChild(this.element);}
+this.map.events.register('moveend',this,this.updateScale);this.updateScale();return this.div;},updateScale:function(){var scale;if(this.geodesic===true){var units=this.map.getUnits();if(!units){return;}
+var inches=OpenLayers.INCHES_PER_UNIT;scale=(this.map.getGeodesicPixelSize().w||0.000001)*inches["km"]*OpenLayers.DOTS_PER_INCH;}else{scale=this.map.getScale();}
+if(!scale){return;}
+if(scale>=9500&&scale<=950000){scale=Math.round(scale/1000)+"K";}else if(scale>=950000){scale=Math.round(scale/1000000)+"M";}else{scale=Math.round(scale);}
+this.element.innerHTML=OpenLayers.i18n("scale",{'scaleDenom':scale});},CLASS_NAME:"OpenLayers.Control.Scale"});OpenLayers.Control.ScaleLine=OpenLayers.Class(OpenLayers.Control,{maxWidth:100,topOutUnits:"km",topInUnits:"m",bottomOutUnits:"mi",bottomInUnits:"ft",eTop:null,eBottom:null,geodesic:false,initialize:function(options){OpenLayers.Control.prototype.initialize.apply(this,[options]);},draw:function(){OpenLayers.Control.prototype.draw.apply(this,arguments);if(!this.eTop){this.eTop=document.createElement("div");this.eTop.className=this.displayClass+"Top";var theLen=this.topInUnits.length;this.div.appendChild(this.eTop);if((this.topOutUnits=="")||(this.topInUnits=="")){this.eTop.style.visibility="hidden";}else{this.eTop.style.visibility="visible";}
+this.eBottom=document.createElement("div");this.eBottom.className=this.displayClass+"Bottom";this.div.appendChild(this.eBottom);if((this.bottomOutUnits=="")||(this.bottomInUnits=="")){this.eBottom.style.visibility="hidden";}else{this.eBottom.style.visibility="visible";}}
+this.map.events.register('moveend',this,this.update);this.update();return this.div;},getBarLen:function(maxLen){var digits=parseInt(Math.log(maxLen)/Math.log(10));var pow10=Math.pow(10,digits);var firstChar=parseInt(maxLen/pow10);var barLen;if(firstChar>5){barLen=5;}else if(firstChar>2){barLen=2;}else{barLen=1;}
+return barLen*pow10;},update:function(){var res=this.map.getResolution();if(!res){return;}
+var curMapUnits=this.map.getUnits();var inches=OpenLayers.INCHES_PER_UNIT;var maxSizeData=this.maxWidth*res*inches[curMapUnits];var geodesicRatio=1;if(this.geodesic===true){var maxSizeGeodesic=(this.map.getGeodesicPixelSize().w||0.000001)*this.maxWidth;var maxSizeKilometers=maxSizeData/inches["km"];geodesicRatio=maxSizeGeodesic/maxSizeKilometers;maxSizeData*=geodesicRatio;}
+var topUnits;var bottomUnits;if(maxSizeData>100000){topUnits=this.topOutUnits;bottomUnits=this.bottomOutUnits;}else{topUnits=this.topInUnits;bottomUnits=this.bottomInUnits;}
+var topMax=maxSizeData/inches[topUnits];var bottomMax=maxSizeData/inches[bottomUnits];var topRounded=this.getBarLen(topMax);var bottomRounded=this.getBarLen(bottomMax);topMax=topRounded/inches[curMapUnits]*inches[topUnits];bottomMax=bottomRounded/inches[curMapUnits]*inches[bottomUnits];var topPx=topMax/res/geodesicRatio;var bottomPx=bottomMax/res/geodesicRatio;if(this.eBottom.style.visibility=="visible"){this.eBottom.style.width=Math.round(bottomPx)+"px";this.eBottom.innerHTML=bottomRounded+" "+bottomUnits;}
+if(this.eTop.style.visibility=="visible"){this.eTop.style.width=Math.round(topPx)+"px";this.eTop.innerHTML=topRounded+" "+topUnits;}},CLASS_NAME:"OpenLayers.Control.ScaleLine"});OpenLayers.Control.ZoomIn=OpenLayers.Class(OpenLayers.Control,{type:OpenLayers.Control.TYPE_BUTTON,trigger:function(){this.map.zoomIn();},CLASS_NAME:"OpenLayers.Control.ZoomIn"});OpenLayers.Control.ZoomOut=OpenLayers.Class(OpenLayers.Control,{type:OpenLayers.Control.TYPE_BUTTON,trigger:function(){this.map.zoomOut();},CLASS_NAME:"OpenLayers.Control.ZoomOut"});OpenLayers.Control.ZoomToMaxExtent=OpenLayers.Class(OpenLayers.Control,{type:OpenLayers.Control.TYPE_BUTTON,trigger:function(){if(this.map){this.map.zoomToMaxExtent();}},CLASS_NAME:"OpenLayers.Control.ZoomToMaxExtent"});OpenLayers.Event={observers:false,KEY_BACKSPACE:8,KEY_TAB:9,KEY_RETURN:13,KEY_ESC:27,KEY_LEFT:37,KEY_UP:38,KEY_RIGHT:39,KEY_DOWN:40,KEY_DELETE:46,element:function(event){return event.target||event.srcElement;},isLeftClick:function(event){return(((event.which)&&(event.which==1))||((event.button)&&(event.button==1)));},isRightClick:function(event){return(((event.which)&&(event.which==3))||((event.button)&&(event.button==2)));},stop:function(event,allowDefault){if(!allowDefault){if(event.preventDefault){event.preventDefault();}else{event.returnValue=false;}}
+if(event.stopPropagation){event.stopPropagation();}else{event.cancelBubble=true;}},findElement:function(event,tagName){var element=OpenLayers.Event.element(event);while(element.parentNode&&(!element.tagName||(element.tagName.toUpperCase()!=tagName.toUpperCase()))){element=element.parentNode;}
+return element;},observe:function(elementParam,name,observer,useCapture){var element=OpenLayers.Util.getElement(elementParam);useCapture=useCapture||false;if(name=='keypress'&&(navigator.appVersion.match(/Konqueror|Safari|KHTML/)||element.attachEvent)){name='keydown';}
+if(!this.observers){this.observers={};}
+if(!element._eventCacheID){var idPrefix="eventCacheID_";if(element.id){idPrefix=element.id+"_"+idPrefix;}
+element._eventCacheID=OpenLayers.Util.createUniqueID(idPrefix);}
+var cacheID=element._eventCacheID;if(!this.observers[cacheID]){this.observers[cacheID]=[];}
+this.observers[cacheID].push({'element':element,'name':name,'observer':observer,'useCapture':useCapture});if(element.addEventListener){element.addEventListener(name,observer,useCapture);}else if(element.attachEvent){element.attachEvent('on'+name,observer);}},stopObservingElement:function(elementParam){var element=OpenLayers.Util.getElement(elementParam);var cacheID=element._eventCacheID;this._removeElementObservers(OpenLayers.Event.observers[cacheID]);},_removeElementObservers:function(elementObservers){if(elementObservers){for(var i=elementObservers.length-1;i>=0;i--){var entry=elementObservers[i];var args=new Array(entry.element,entry.name,entry.observer,entry.useCapture);var removed=OpenLayers.Event.stopObserving.apply(this,args);}}},stopObserving:function(elementParam,name,observer,useCapture){useCapture=useCapture||false;var element=OpenLayers.Util.getElement(elementParam);var cacheID=element._eventCacheID;if(name=='keypress'){if(navigator.appVersion.match(/Konqueror|Safari|KHTML/)||element.detachEvent){name='keydown';}}
+var foundEntry=false;var elementObservers=OpenLayers.Event.observers[cacheID];if(elementObservers){var i=0;while(!foundEntry&&i<elementObservers.length){var cacheEntry=elementObservers[i];if((cacheEntry.name==name)&&(cacheEntry.observer==observer)&&(cacheEntry.useCapture==useCapture)){elementObservers.splice(i,1);if(elementObservers.length==0){delete OpenLayers.Event.observers[cacheID];}
+foundEntry=true;break;}
+i++;}}
+if(foundEntry){if(element.removeEventListener){element.removeEventListener(name,observer,useCapture);}else if(element&&element.detachEvent){element.detachEvent('on'+name,observer);}}
+return foundEntry;},unloadCache:function(){if(OpenLayers.Event&&OpenLayers.Event.observers){for(var cacheID in OpenLayers.Event.observers){var elementObservers=OpenLayers.Event.observers[cacheID];OpenLayers.Event._removeElementObservers.apply(this,[elementObservers]);}
+OpenLayers.Event.observers=false;}},CLASS_NAME:"OpenLayers.Event"};OpenLayers.Event.observe(window,'unload',OpenLayers.Event.unloadCache,false);if(window.Event){OpenLayers.Util.applyDefaults(window.Event,OpenLayers.Event);}else{var Event=OpenLayers.Event;}
+OpenLayers.Events=OpenLayers.Class({BROWSER_EVENTS:["mouseover","mouseout","mousedown","mouseup","mousemove","click","dblclick","rightclick","dblrightclick","resize","focus","blur"],listeners:null,object:null,element:null,eventTypes:null,eventHandler:null,fallThrough:null,includeXY:false,clearMouseListener:null,initialize:function(object,element,eventTypes,fallThrough,options){OpenLayers.Util.extend(this,options);this.object=object;this.fallThrough=fallThrough;this.listeners={};this.eventHandler=OpenLayers.Function.bindAsEventListener(this.handleBrowserEvent,this);this.clearMouseListener=OpenLayers.Function.bind(this.clearMouseCache,this);this.eventTypes=[];if(eventTypes!=null){for(var i=0,len=eventTypes.length;i<len;i++){this.addEventType(eventTypes[i]);}}
+if(element!=null){this.attachToElement(element);}},destroy:function(){if(this.element){OpenLayers.Event.stopObservingElement(this.element);if(this.element.hasScrollEvent){OpenLayers.Event.stopObserving(window,"scroll",this.clearMouseListener);}}
+this.element=null;this.listeners=null;this.object=null;this.eventTypes=null;this.fallThrough=null;this.eventHandler=null;},addEventType:function(eventName){if(!this.listeners[eventName]){this.eventTypes.push(eventName);this.listeners[eventName]=[];}},attachToElement:function(element){if(this.element){OpenLayers.Event.stopObservingElement(this.element);}
+this.element=element;for(var i=0,len=this.BROWSER_EVENTS.length;i<len;i++){var eventType=this.BROWSER_EVENTS[i];this.addEventType(eventType);OpenLayers.Event.observe(element,eventType,this.eventHandler);}
+OpenLayers.Event.observe(element,"dragstart",OpenLayers.Event.stop);},on:function(object){for(var type in object){if(type!="scope"){this.register(type,object.scope,object[type]);}}},register:function(type,obj,func){if((func!=null)&&(OpenLayers.Util.indexOf(this.eventTypes,type)!=-1)){if(obj==null){obj=this.object;}
+var listeners=this.listeners[type];listeners.push({obj:obj,func:func});}},registerPriority:function(type,obj,func){if(func!=null){if(obj==null){obj=this.object;}
+var listeners=this.listeners[type];if(listeners!=null){listeners.unshift({obj:obj,func:func});}}},un:function(object){for(var type in object){if(type!="scope"){this.unregister(type,object.scope,object[type]);}}},unregister:function(type,obj,func){if(obj==null){obj=this.object;}
+var listeners=this.listeners[type];if(listeners!=null){for(var i=0,len=listeners.length;i<len;i++){if(listeners[i].obj==obj&&listeners[i].func==func){listeners.splice(i,1);break;}}}},remove:function(type){if(this.listeners[type]!=null){this.listeners[type]=[];}},triggerEvent:function(type,evt){var listeners=this.listeners[type];if(!listeners||listeners.length==0){return;}
+if(evt==null){evt={};}
+evt.object=this.object;evt.element=this.element;if(!evt.type){evt.type=type;}
+var listeners=listeners.slice(),continueChain;for(var i=0,len=listeners.length;i<len;i++){var callback=listeners[i];continueChain=callback.func.apply(callback.obj,[evt]);if((continueChain!=undefined)&&(continueChain==false)){break;}}
+if(!this.fallThrough){OpenLayers.Event.stop(evt,true);}
+return continueChain;},handleBrowserEvent:function(evt){if(this.includeXY){evt.xy=this.getMousePosition(evt);}
+this.triggerEvent(evt.type,evt);},clearMouseCache:function(){this.element.scrolls=null;this.element.lefttop=null;this.element.offsets=null;},getMousePosition:function(evt){if(!this.includeXY){this.clearMouseCache();}else if(!this.element.hasScrollEvent){OpenLayers.Event.observe(window,"scroll",this.clearMouseListener);this.element.hasScrollEvent=true;}
+if(!this.element.scrolls){this.element.scrolls=[(document.documentElement.scrollLeft||document.body.scrollLeft),(document.documentElement.scrollTop||document.body.scrollTop)];}
+if(!this.element.lefttop){this.element.lefttop=[(document.documentElement.clientLeft||0),(document.documentElement.clientTop||0)];}
+if(!this.element.offsets){this.element.offsets=OpenLayers.Util.pagePosition(this.element);this.element.offsets[0]+=this.element.scrolls[0];this.element.offsets[1]+=this.element.scrolls[1];}
+return new OpenLayers.Pixel((evt.clientX+this.element.scrolls[0])-this.element.offsets[0]
+-this.element.lefttop[0],(evt.clientY+this.element.scrolls[1])-this.element.offsets[1]
+-this.element.lefttop[1]);},CLASS_NAME:"OpenLayers.Events"});OpenLayers.Format=OpenLayers.Class({options:null,externalProjection:null,internalProjection:null,data:null,keepData:false,initialize:function(options){OpenLayers.Util.extend(this,options);this.options=options;},destroy:function(){},read:function(data){OpenLayers.Console.userError(OpenLayers.i18n("readNotImplemented"));},write:function(object){OpenLayers.Console.userError(OpenLayers.i18n("writeNotImplemented"));},CLASS_NAME:"OpenLayers.Format"});OpenLayers.Lang["ar"]=OpenLayers.Util.applyDefaults({'permalink':"وصلة دائمة",'baseLayer':"الطبقة الاساسية",'readNotImplemented':"القراءة غير محققة.",'writeNotImplemented':"الكتابة غير محققة",'errorLoadingGML':"خطأ عند تحميل المل٠جي ام ال ${url}",'scale':"النسبة = 1 : ${scaleDenom}",'W':"غ",'E':"شر",'N':"شم",'S':"ج"});OpenLayers.Lang["be-tarask"]=OpenLayers.Util.applyDefaults({'unhandledRequest':"Ðеапрацаваны вынік запыту ${statusText}",'permalink':"Ð¡Ñ‚Ð°Ð»Ð°Ñ ÑпаÑылка",'overlays':"Слаі",'baseLayer':"Базавы Ñлой",'sameProjection':"ÐглÑÐ´Ð½Ð°Ñ Ð¼Ð°Ð¿Ð° працуе толькі калі Ñна мае тую ж праекцыю, што Ñ– аÑÐ½Ð¾ÑžÐ½Ð°Ñ Ð¼Ð°Ð¿Ð°",'readNotImplemented':"ФункцыÑнальнаÑьць Ñ‡Ñ‹Ñ‚Ð°Ð½ÑŒÐ½Ñ Ð½Ñ ÑтворанаÑ.",'writeNotImplemented':"ФункцыÑнальнаÑьць запіÑу Ð½Ñ ÑтворанаÑ.",'noFID':"Ðемагчыма абнавіць магчымаÑьць, Ð´Ð»Ñ Ñкога не Ñ–Ñнуе FID.",'errorLoadingGML':"Памылка загрузкі файла GML ${url}",'browserNotSupported':"Ваш браўзÑÑ€ не падтрымлівае вÑктарную графіку. У цÑперашні момант падтрымліваюцца: ${renderers}",'componentShouldBe':"addFeatures : кампанÑнт павінен быць ${geomType}",'getFeatureError':"getFeatureFromEvent выкліканы Ð´Ð»Ñ ÑÐ»Ð¾Ñ Ð±Ñз Ñ€ÑндÑру. Звычайна гÑта азначае, што Ð’Ñ‹ зьнішчылі Ñлой, але пакінулі зьвÑзаны зь ім апрацоўшчык.",'minZoomLevelError':"УлаÑьціваÑьць minZoomLevel прызначана толькі Ð´Ð»Ñ Ð²Ñ‹ÐºÐ°Ñ€Ñ‹ÑÑ‚Ð°Ð½ÑŒÐ½Ñ Ñа ÑлаÑмі вытворнымі ад FixedZoomLevels. Тое, што гÑÑ‚Ñ‹ wfs-Ñлой правÑраецца на minZoomLevel — Ñ€Ñха прошлага. Ðле мы Ð½Ñ Ð¼Ð¾Ð¶Ð°Ð¼ выдаліць гÑтую магчымаÑьць, таму што ад Ñе залежаць Ð½ÐµÐºÐ°Ñ‚Ð¾Ñ€Ñ‹Ñ Ð·Ð°ÑÐ½Ð°Ð²Ð°Ð½Ñ‹Ñ Ð½Ð° OL даÑтаÑаваньні. Тым Ð½Ñ Ð¼ÐµÐ½Ñˆ, праверка minZoomLevel будзе Ð²Ñ‹Ð´Ð°Ð»ÐµÐ½Ð°Ñ Ñž вÑÑ€ÑÑ–Ñ– 3.0. Калі лаÑка, выкарыÑтоўваеце замеÑÑ‚ Ñе ÑžÑтаноўкі мінімальнага/макÑымальнага памераў, Ñк апіÑана тут: http://trac.openlayers.org/wiki/SettingZoomLevels",'commitSuccess':"WFS-транзакцыÑ: ПОСЬПЕХ ${response}",'commitFailed':"WFS-транзакцыÑ: ПÐМЫЛКР${response}",'googleWarning':"Ðе атрымалаÑÑ Ð·Ð°Ð³Ñ€ÑƒÐ·Ñ–Ñ†ÑŒ Ñлой Google. \x3cbr\x3e\x3cbr\x3eКаб пазбавіцца гÑтага паведамленьнÑ, выберыце новы базавы Ñлой у ÑьпіÑе Ñž верхнім правым куце.\x3cbr\x3e\x3cbr\x3e ХутчÑй за ÑžÑÑ‘, прычына Ñž тым, што Ñкрыпт бібліÑÑ‚Ñкі Google Maps Ð½Ñ Ð±Ñ‹Ñž ÑƒÐºÐ»ÑŽÑ‡Ð°Ð½Ñ‹Ñ Ð°Ð»ÑŒÐ±Ð¾ не ўтрымлівае Ñлушны API-ключ Ð´Ð»Ñ Ð’Ð°ÑˆÐ°Ð³Ð° Ñайта.\x3cbr\x3e\x3cbr\x3eРаÑпрацоўшчыкам: Ð”Ð»Ñ Ñ‚Ð°Ð³Ð¾, каб даведацца Ñк зрабіць так, каб уÑÑ‘ працавала, \x3ca href=\'http://trac.openlayers.org/wiki/Google\' target=\'_blank\'\x3eнаціÑьніце тут\x3c/a\x3e",'getLayerWarning':"Ðемагчыма загрузіць Ñлой ${layerType}.\x3cbr\x3e\x3cbr\x3eКаб пазбавіцца гÑтага паведамленьнÑ, выберыце новы базавы Ñлой у ÑьпіÑе Ñž верхнім правым куце.\x3cbr\x3e\x3cbr\x3eХутчÑй за ÑžÑÑ‘, прычына Ñž тым, што Ñкрыпт бібліÑÑ‚Ñкі ${layerLib} Ð½Ñ Ð±Ñ‹Ñž Ñлушна ўключаны.\x3cbr\x3e\x3cbr\x3eРаÑпрацоўшчыкам: Ð”Ð»Ñ Ñ‚Ð°Ð³Ð¾, каб даведацца Ñк зрабіць так, каб уÑÑ‘ працавала, \x3ca href=\'http://trac.openlayers.org/wiki/${layerLib}\' target=\'_blank\'\x3eнаціÑьніце тут\x3c/a\x3e",'scale':"Маштаб = 1 : ${scaleDenom}",'W':"З",'E':"У",'N':"Пн",'S':"Пд",'layerAlreadyAdded':"Ð’Ñ‹ паÑпрабавалі дадаць Ñлой ${layerName} на мапу, але ён ужо дададзены",'reprojectDeprecated':"Ð’Ñ‹ выкарыÑтоўваеце ÑžÑтаноўку \'reproject\' Ð´Ð»Ñ ÑÐ»Ð¾Ñ ${layerName}. ГÑÑ‚Ð°Ñ ÑžÑтаноўка зьÑўлÑецца ÑаÑтарÑлай: Ñна выкарыÑтоўвалаÑÑ Ð´Ð»Ñ Ð¿Ð°Ð´Ñ‚Ñ€Ñ‹Ð¼ÐºÑ– паказу зьвеÑтак на камÑрцыйных базавых мапах, але гÑта Ñ„ÑƒÐ½ÐºÑ†Ñ‹Ñ Ñ†Ñпер Ñ€ÑÐ°Ð»Ñ–Ð·Ð°Ð²Ð°Ð½Ð°Ñ Ñž убудаванай падтрымцы ÑÑ„Ñрычнай праекцыі ÐœÑркатара. Ð”Ð°Ð´Ð°Ñ‚ÐºÐ¾Ð²Ð°Ñ Ñ–Ð½Ñ„Ð°Ñ€Ð¼Ð°Ñ†Ñ‹Ñ Ñ‘Ñьць на http://trac.openlayers.org/wiki/SphericalMercator.",'methodDeprecated':"ГÑÑ‚Ñ‹ мÑтад ÑаÑтарÑлы Ñ– будзе выдалены Ñž вÑÑ€ÑÑ–Ñ– 3.0. Калі лаÑка, замеÑÑ‚ Ñго выкарыÑтоўвайце ${newMethod}.",'boundsAddError':"Вам неабходна падаць абодва значÑньні x Ñ– y Ð´Ð»Ñ Ñ„ÑƒÐ½ÐºÑ†Ñ‹Ñ– ÑкладаньнÑ.",'lonlatAddError':"Вам неабходна падаць абодва значÑньні lon Ñ– lat Ð´Ð»Ñ Ñ„ÑƒÐ½ÐºÑ†Ñ‹Ñ– ÑкладаньнÑ.",'pixelAddError':"Вам неабходна падаць абодва значÑньні x Ñ– y Ð´Ð»Ñ Ñ„ÑƒÐ½ÐºÑ†Ñ‹Ñ– ÑкладаньнÑ.",'unsupportedGeometryType':"Тып геамÑтрыі не падтрымліваецца: ${geomType}",'pagePositionFailed':"OpenLayers.Util.pagePosition failed: верагодна ÑлемÑнт з ідÑнтыфікатарам ${elemId} займае нÑÑлушнае меÑца.",'filterEvaluateNotImplemented':"evaluate не Ñ€Ñалізаваны Ð´Ð»Ñ Ð³Ñтага тыпу фільтру."});OpenLayers.Lang["bg"]=OpenLayers.Util.applyDefaults({'permalink':"ПоÑтоÑнна препратка",'baseLayer':"ОÑновен Ñлой",'errorLoadingGML':"Грешка при зареждане на GML файл ${url}",'scale':"Мащаб = 1 : ${scaleDenom}",'layerAlreadyAdded':"Опитахте да добавите Ñлой ${layerName} в картата, но той вече е добавен",'methodDeprecated':"Този метод е оÑтарÑл и ще бъде премахват в 3.0. ВмеÑто него използвайте ${newMethod}."});OpenLayers.Lang["br"]=OpenLayers.Util.applyDefaults({'unhandledRequest':"Distro evel reked anveret ${statusText}",'permalink':"Peurliamm",'overlays':"Gwiskadoù",'baseLayer':"Gwiskad diazez",'sameProjection':"Ne\'z ar gartenn lec\'hiañ en-dro nemet pa vez heñvel ar banndres anezhi ha hini ar gartenn bennañ",'readNotImplemented':"N\'eo ket emplementet al lenn.",'writeNotImplemented':"N\'eo ket emplementet ar skrivañ.",'noFID':"N\'haller ket hizivaat un elfenn ma n\'eus ket a niverenn-anaout (FID) eviti.",'errorLoadingGML':"Fazi e-ser kargañ ar restr GML ${url}",'browserNotSupported':"N\'eo ket skoret an daskor vektorel gant ho merdeer. Setu aze an daskorerioù skoret evit ar poent :\n${renderers}",'componentShouldBe':"addFeatures : bez\' e tlefe ar parzh besañ eus ar seurt ${geomType}",'getFeatureError':"Galvet eo bet getFeatureFromEvent called war ur gwiskad hep daskorer. Kement-se a dalvez ez eus bet freuzet ur gwiskad hag hoc\'h eus miret un embreger bennak stag outañ.",'minZoomLevelError':"Ne zleer implijout ar perzh minZoomLevel nemet evit gwiskadoù FixedZoomLevels-descendent. Ar fed ma wiria ar gwiskad WHS-se hag-eñ ez eus eus minZoomLevel zo un aspadenn gozh. Koulskoude n\'omp ket evit e ziverkañ kuit da derriñ arloadoù diazezet war OL a c\'hallfe bezañ stag outañ. Setu perak eo dispredet -- Lamet kuit e vo ar gwiriañ minZoomLevel a-is er stumm 3.0. Ober gant an arventennoù bihanañ/brasañ evel deskrivet amañ e plas : http://trac.openlayers.org/wiki/SettingZoomLevels",'commitSuccess':"Treuzgread WFS : MAT EO ${response}",'commitFailed':"Treuzgread WFS Transaction: C\'HWITET ${response}",'googleWarning':"N\'eus ket bet gallet kargañ ar gwiskad Google ent reizh.\x3cbr\x3e\x3cbr\x3eEvit en em zizober eus ar c\'hemenn-mañ, dibabit ur BaseLayer nevez en diuzer gwiskadoù er c\'horn dehoù el laez.\x3cbr\x3e\x3cbr\x3eSur a-walc\'h eo peogwir n\'eo ket bet ensoc\'het levraoueg Google Maps pe neuze ne glot ket an alc\'hwez API gant ho lec\'hienn.\x3cbr\x3e\x3cbr\x3eDiorroerien : Evit reizhañ an dra-se, \x3ca href=\'http://trac.openlayers.org/wiki/Google\' target=\'_blank\'\x3eclick here\x3c/a\x3e",'getLayerWarning':"N\'haller ket kargañ ar gwiskad ${layerType} ent reizh.\x3cbr\x3e\x3cbr\x3eEvit en em zizober eus ar c\'hemenn-mañ, dibabit ur BaseLayer nevez en diuzer gwiskadoù er c\'horn dehoù el laez.\x3cbr\x3e\x3cbr\x3eSur a-walc\'h eo peogwir n\'eo ket bet ensoc\'het mat al levraoueg ${layerLib}.\x3cbr\x3e\x3cbr\x3eDiorroerien : Evit gouzout penaos reizhañ an dra-se, \x3ca href=\'http://trac.openlayers.org/wiki/${layerLib}\' target=\'_blank\'\x3eclick here\x3c/a\x3e",'scale':"Skeul = 1 : ${scaleDenom}",'W':"K",'E':"R",'N':"N",'S':"S",'layerAlreadyAdded':"Klasket hoc\'h eus ouzhpennañ ar gwiskad : ${layerName} d\'ar gartenn, met ouzhpennet e oa bet c\'hoazh",'reprojectDeprecated':"Emaoc\'h oc\'h implijout an dibarzh \'reproject\' war ar gwiskad ${layerName}. Dispredet eo an dibarzh-mañ : bet eo hag e talveze da ziskwel roadennoù war-c\'horre kartennoù diazez kenwerzhel, un dra hag a c\'haller ober bremañ gant an arc\'hwel dre skor banndres boullek Mercator. Muioc\'h a ditouroù a c\'haller da gaout war http://trac.openlayers.org/wiki/SphericalMercator.",'methodDeprecated':"Dispredet eo an daore-se ha tennet e vo kuit eus ar stumm 3.0. Grit gant ${newMethod} e plas.",'boundsAddError':"Rekis eo tremen an div dalvoudenn x ha y d\'an arc\'hwel add.",'lonlatAddError':"Rekis eo tremen an div dalvoudenn hedred ha ledred d\'an arc\'hwel add.",'pixelAddError':"Rekis eo tremen an div dalvoudenn x ha y d\'an arc\'hwel add.",'unsupportedGeometryType':"Seurt mentoniezh anskoret : ${geomType}",'pagePositionFailed':"C\'hwitet eo OpenLayers.Util.pagePosition : marteze emañ lec\'hiet fall an elfenn id ${elemId}.",'filterEvaluateNotImplemented':"N\'eo ket bet emplementet ar priziañ evit seurt siloù c\'hoazh."});OpenLayers.Lang["el"]=OpenLayers.Util.applyDefaults({'scale':"Κλίμακα ~ 1 : ${scaleDenom}"});OpenLayers.Lang.en={'unhandledRequest':"Unhandled request return ${statusText}",'permalink':"Permalink",'overlays':"Overlays",'baseLayer':"Base Layer",'sameProjection':"The overview map only works when it is in the same projection as the main map",'readNotImplemented':"Read not implemented.",'writeNotImplemented':"Write not implemented.",'noFID':"Can't update a feature for which there is no FID.",'errorLoadingGML':"Error in loading GML file ${url}",'browserNotSupported':"Your browser does not support vector rendering. Currently supported renderers are:\n${renderers}",'componentShouldBe':"addFeatures : component should be an ${geomType}",'getFeatureError':"getFeatureFromEvent called on layer with no renderer. This usually means you "+"destroyed a layer, but not some handler which is associated with it.",'minZoomLevelError':"The minZoomLevel property is only intended for use "+"with the FixedZoomLevels-descendent layers. That this "+"wfs layer checks for minZoomLevel is a relic of the"+"past. We cannot, however, remove it without possibly "+"breaking OL based applications that may depend on it."+" Therefore we are deprecating it -- the minZoomLevel "+"check below will be removed at 3.0. Please instead "+"use min/max resolution setting as described here: "+"http://trac.openlayers.org/wiki/SettingZoomLevels",'commitSuccess':"WFS Transaction: SUCCESS ${response}",'commitFailed':"WFS Transaction: FAILED ${response}",'googleWarning':"The Google Layer was unable to load correctly.<br><br>"+"To get rid of this message, select a new BaseLayer "+"in the layer switcher in the upper-right corner.<br><br>"+"Most likely, this is because the Google Maps library "+"script was either not included, or does not contain the "+"correct API key for your site.<br><br>"+"Developers: For help getting this working correctly, "+"<a href='http://trac.openlayers.org/wiki/Google' "+"target='_blank'>click here</a>",'getLayerWarning':"The ${layerType} Layer was unable to load correctly.<br><br>"+"To get rid of this message, select a new BaseLayer "+"in the layer switcher in the upper-right corner.<br><br>"+"Most likely, this is because the ${layerLib} library "+"script was not correctly included.<br><br>"+"Developers: For help getting this working correctly, "+"<a href='http://trac.openlayers.org/wiki/${layerLib}' "+"target='_blank'>click here</a>",'scale':"Scale = 1 : ${scaleDenom}",'W':'W','E':'E','N':'N','S':'S','graticule':'Graticule','layerAlreadyAdded':"You tried to add the layer: ${layerName} to the map, but it has already been added",'reprojectDeprecated':"You are using the 'reproject' option "+"on the ${layerName} layer. This option is deprecated: "+"its use was designed to support displaying data over commercial "+"basemaps, but that functionality should now be achieved by using "+"Spherical Mercator support. More information is available from "+"http://trac.openlayers.org/wiki/SphericalMercator.",'methodDeprecated':"This method has been deprecated and will be removed in 3.0. "+"Please use ${newMethod} instead.",'boundsAddError':"You must pass both x and y values to the add function.",'lonlatAddError':"You must pass both lon and lat values to the add function.",'pixelAddError':"You must pass both x and y values to the add function.",'unsupportedGeometryType':"Unsupported geometry type: ${geomType}",'pagePositionFailed':"OpenLayers.Util.pagePosition failed: element with id ${elemId} may be misplaced.",'filterEvaluateNotImplemented':"evaluate is not implemented for this filter type.",'end':''};OpenLayers.Lang["fi"]=OpenLayers.Util.applyDefaults({'permalink':"Ikilinkki",'overlays':"Kerrokset",'baseLayer':"Peruskerros",'sameProjection':"Yleiskuvakarttaa voi käyttää vain, kun sillä on sama projektio kuin pääkartalla.",'W':"L",'E':"I",'N':"P",'S':"E"});OpenLayers.Lang["fur"]=OpenLayers.Util.applyDefaults({'permalink':"Leam Permanent",'overlays':"Livei parsore",'baseLayer':"Livel di base",'browserNotSupported':"Il to sgarfadôr nol supuarte la renderizazion vetoriâl. Al moment a son supuartâts:\n${renderers}",'scale':"Scjale = 1 : ${scaleDenom}",'W':"O",'E':"E",'N':"N",'S':"S"});OpenLayers.Lang["gl"]=OpenLayers.Util.applyDefaults({'unhandledRequest':"Solicitude non xerada; a resposta foi: ${statusText}",'permalink':"Ligazón permanente",'overlays':"Capas superpostas",'baseLayer':"Capa base",'sameProjection':"A vista xeral do mapa só funciona cando está na mesma proxección có mapa principal",'readNotImplemented':"Lectura non implementada.",'writeNotImplemented':"Escritura non implementada.",'noFID':"Non se pode actualizar a funcionalidade para a que non hai FID.",'errorLoadingGML':"Erro ao cargar o ficheiro GML ${url}",'browserNotSupported':"O seu navegador non soporta a renderización de vectores. Os renderizadores soportados actualmente son:\n${renderers}",'componentShouldBe':"addFeatures: o compoñente debera ser de tipo ${geomType}",'getFeatureError':"getFeatureFromEvent ten sido chamado a unha capa sen renderizador. Isto normalmente significa que destruíu unha capa, mais non o executador que está asociado con ela.",'minZoomLevelError':"A propiedade minZoomLevel é só para uso conxuntamente coas capas FixedZoomLevels-descendent. O feito de que esa capa wfs verifique o minZoomLevel é unha reliquia do pasado. Non podemos, con todo, eliminala sen a posibilidade de non romper as aplicacións baseadas en OL que poidan depender dela. Por iso a estamos deixando obsoleta (a comprobación minZoomLevel de embaixo será eliminada na versión 3.0). Por favor, no canto diso use o axuste de resolución mín/máx tal e como está descrito aquí: http://trac.openlayers.org/wiki/SettingZoomLevels",'commitSuccess':"Transacción WFS: ÉXITO ${response}",'commitFailed':"Transacción WFS: FALLIDA ${response}",'googleWarning':"A capa do Google non puido cargarse correctamente.\x3cbr\x3e\x3cbr\x3ePara evitar esta mensaxe, escolla unha nova capa base no seleccionador de capas na marxe superior dereita.\x3cbr\x3e\x3cbr\x3eProbablemente, isto acontece porque a escritura da libraría do Google Maps ou ben non foi incluída ou ben non contén a clave API correcta para o seu sitio.\x3cbr\x3e\x3cbr\x3eDesenvolvedores: para axudar a facer funcionar isto correctamente, \x3ca href=\'http://trac.openlayers.org/wiki/Google\' target=\'_blank\'\x3epremede aquí\x3c/a\x3e",'getLayerWarning':"A capa ${layerType} foi incapaz de cargarse correctamente.\x3cbr\x3e\x3cbr\x3ePara evitar esta mensaxe, escolla unha nova capa base no seleccionador de capas na marxe superior dereita.\x3cbr\x3e\x3cbr\x3eProbablemente, isto acontece porque a escritura da libraría ${layerLib} non foi ben incluída.\x3cbr\x3e\x3cbr\x3eDesenvolvedores: para axudar a facer funcionar isto correctamente, \x3ca href=\'http://trac.openlayers.org/wiki/${layerLib}\' target=\'_blank\'\x3epremede aquí\x3c/a\x3e",'scale':"Escala = 1 : ${scaleDenom}",'W':"O",'E':"L",'N':"N",'S':"S",'layerAlreadyAdded':"Intentou engadir a capa: ${layerName} ao mapa, pero xa fora engadida",'reprojectDeprecated':"Está usando a opción \"reproject\" na capa ${layerName}. Esta opción está obsoleta: o seu uso foi deseñado para a visualización de datos sobre mapas base comerciais, pero esta funcionalidade debera agora ser obtida utilizando a proxección Spherical Mercator. Hai dispoñible máis información en http://trac.openlayers.org/wiki/SphericalMercator.",'methodDeprecated':"Este método está obsoleto e será eliminado na versión 3.0. Por favor, no canto deste use ${newMethod}.",'boundsAddError':"Debe achegar os valores x e y á función add.",'lonlatAddError':"Debe achegar tanto o valor lon coma o lat á función add.",'pixelAddError':"Debe achegar os valores x e y á función add.",'unsupportedGeometryType':"Tipo xeométrico non soportado: ${geomType}",'pagePositionFailed':"OpenLayers.Util.pagePosition fallou: o elemento con id ${elemId} pode non estar na súa posición.",'filterEvaluateNotImplemented':"avaliar non está implementado para este tipo de filtro."});OpenLayers.Lang["gsw"]=OpenLayers.Util.applyDefaults({'unhandledRequest':"Nit behandleti Aafrogsruckmäldig ${statusText}",'permalink':"Permalink",'overlays':"Iberlagerige",'baseLayer':"Grundcharte",'sameProjection':"D Ibersichts-Charte funktioniert nume, wänn si di glych Projäktion brucht wie d Hauptcharte",'readNotImplemented':"Läse nit implementiert.",'writeNotImplemented':"Schrybe nit implementiert.",'noFID':"E Feature, wu s kei FID derfir git, cha nit aktualisiert wäre.",'errorLoadingGML':"Fähler bim Lade vu dr GML-Datei ${url}",'browserNotSupported':"Dyy Browser unterstitzt kei Vektordarstellig. Aktuäll unterstitzti Renderer:\n${renderers}",'componentShouldBe':"addFeatures : Komponänt sott dr Typ ${geomType} syy",'getFeatureError':"getFeatureFromEvent isch uf eme Layer ohni Renderer ufgruefe wore. Des heisst normalerwys, ass Du e Layer kaputt gmacht hesch, aber nit dr Handler, wu derzue ghert.",'minZoomLevelError':"D minZoomLevel-Eigeschaft isch nume dänk fir d Layer, wu vu dr FixedZoomLevels abstamme. Ass dää wfs-Layer minZoomLevel prieft, scih e Relikt us dr Vergangeheit. Mir chenne s aber nit ändere ohni OL_basierti Aawändige villicht kaputt gehn, wu dervu abhänge. Us däm Grund het die Funktion d Eigeschaft \'deprecated\' iberchuu. D minZoomLevel-Priefig unte wird in dr Version 3.0 usegnuu. Bitte verwänd statt däm e min/max-Uflesig wie s do bschriben isch: http://trac.openlayers.org/wiki/SettingZoomLevels",'commitSuccess':"WFS-Transaktion: ERFOLGRYCH ${response}",'commitFailed':"WFS-Transaktion: FÄHLGSCHLAA ${response}",'googleWarning':"Dr Google-Layer het nit korräkt chenne glade wäre.\x3cbr\x3e\x3cbr\x3eGo die Mäldig nimi z kriege, wehl e andere Hintergrundlayer us em LayerSwitcher im rächte obere Ecke.\x3cbr\x3e\x3cbr\x3eDää Fähler git s seli hyfig, wel s Skript vu dr Google-Maps-Bibliothek nit yybunde woren isch oder wel s kei giltige API-Schlissel fir Dyy URL din het.\x3cbr\x3e\x3cbr\x3eEntwickler: Fir Hilf zum korräkte Yybinde vum Google-Layer \x3ca href=\'http://trac.openlayers.org/wiki/Google\' target=\'_blank\'\x3edoo drucke\x3c/a\x3e",'getLayerWarning':"Dr ${layerType}-Layer het nit korräkt chenne glade wäre.\x3cbr\x3e\x3cbr\x3eGo die Mäldig nimi z kriege, wehl e andere Hintergrundlayer us em LayerSwitcher im rächte obere Ecke.\x3cbr\x3e\x3cbr\x3eDää Fähler git s seli hyfig, wel s Skript vu dr \'${layerLib}\'-Bibliothek nit yybunde woren isch oder wel s kei giltige API-Schlissel fir Dyy URL din het.\x3cbr\x3e\x3cbr\x3eEntwickler: Fir Hilf zum korräkte Yybinde vu Layer \x3ca href=\'http://trac.openlayers.org/wiki/${layerLib}\' target=\'_blank\'\x3edoo drucke\x3c/a\x3e",'scale':"Maßstab = 1 : ${scaleDenom}",'W':"W",'E':"O",'N':"N",'S':"S",'layerAlreadyAdded':"Du hesch versuecht dää Layer in d Charte yyzfiege: ${layerName}, aber är isch schoi yygfiegt",'reprojectDeprecated':"Du bruchsch d \'reproject\'-Option bim ${layerName}-Layer. Die Option isch nimi giltig: si isch aagleit wore go Date iber kommerziälli Grundcharte lege, aber des sott mer jetz mache mit dr Unterstitzig vu Spherical Mercator. Meh Informatione git s uf http://trac.openlayers.org/wiki/SphericalMercator.",'methodDeprecated':"Die Methode isch veraltet un wird us dr Version 3.0 usegnuu. Bitte verwäbnd statt däm ${newMethod}.",'boundsAddError':"Du muesch e x-Wärt un e y-Wärt yygee bi dr Zuefieg-Funktion",'lonlatAddError':"Du meusch e Lengi- un e Breiti-Grad yygee bi dr Zuefieg-Funktion.",'pixelAddError':"Du muesch x- un y-Wärt aagee bi dr Zuefieg-Funktion.",'unsupportedGeometryType':"Nit unterstitze Geometrii-Typ: ${geomType}",'pagePositionFailed':"OpenLayers.Util.pagePosition fählgschlaa: Elemänt mit ID ${elemId} isch villicht falsch gsetzt.",'filterEvaluateNotImplemented':"evaluiere isch nit implemäntiert in däm Filtertyp."});OpenLayers.Lang["hr"]=OpenLayers.Util.applyDefaults({'unhandledRequest':"Nepodržani zahtjev ${statusText}",'permalink':"Permalink",'overlays':"Overlays",'baseLayer':"Osnovna karta",'sameProjection':"Pregledna karta radi jedino kao je u istoj projekciji kao i glava karta",'readNotImplemented':"ÄŒitanje nije implementirano.",'writeNotImplemented':"Pisanje nije implementirano.",'noFID':"Ne mogu ažurirati znaÄajku za koju ne postoji FID.",'errorLoadingGML':"GreÅ¡ka u uÄitavanju GML datoteke ${url}",'browserNotSupported':"VaÅ¡ preglednik ne podržava vektorsko renderiranje. Trenutno podržani rendereri su: ${renderers}",'componentShouldBe':"addFeatures : komponenta bi trebala biti ${geomType}",'getFeatureError':"getFeatureFromEvent je pozvao Layer bez renderera. Ovo obiÄno znaÄi da ste uniÅ¡tiili Layer, a ne neki Handler koji je povezan s njim.",'commitSuccess':"WFS Transakcija: USPJEÅ NA ${response}",'commitFailed':"WFS Transakcija: NEUSPJEÅ NA ${response}",'scale':"Mjerilo = 1 : ${scaleDenom}",'layerAlreadyAdded':"PokuÅ¡ali ste dodati layer: ${layerName} na kartu, ali je već dodan",'methodDeprecated':"Ova metoda nije odobrena i biti će maknuta u 3.0. Koristite ${newMethod}.",'boundsAddError':"Morate dati obje vrijednosti , x i y da bi dodali funkciju.",'lonlatAddError':"Morate dati obje vrijednosti , (lon i lat) da bi dodali funkciju.",'pixelAddError':"Morate dati obje vrijednosti , x i y da bi dodali funkciju.",'unsupportedGeometryType':"Nepodržani tip geometrije: ${geomType}",'pagePositionFailed':"OpenLayers.Util.pagePosition nije uspjelo: element sa id ${elemId} može biti krivo smjeÅ¡ten."});OpenLayers.Lang["hsb"]=OpenLayers.Util.applyDefaults({'unhandledRequest':"WotmoÅ‚wa njewobdźěłaneho napraÅ¡owanja ${statusText}",'permalink':"Trajny wotkaz",'overlays':"NaworÅ¡towanja",'baseLayer':"ZakÅ‚adna runina",'sameProjection':"PÅ™ehladowa karta jenož funguje, hdyž je w samsnej projekciji kaž hÅ‚owna karta",'readNotImplemented':"ÄŒitanje njeimplementowane.",'writeNotImplemented':"Pisanje njeimplementowane.",'noFID':"Funkcija, za kotruž FID njeje, njeda so aktualizować.",'errorLoadingGML':"Zmylk pÅ™i zaÄitowanju dataje ${url}",'browserNotSupported':"Twój wobhladowak wektorowe rysowanje njepodpÄ›ruje. Tuchwilu podpÄ›rowane rysowaki su:\n${renderers}",'componentShouldBe':"addFeatures: komponenta měła ${geomType} być",'getFeatureError':"getFeatureFromEvent bu na woršće bjez rysowak zawoÅ‚any. To zwjetÅ¡a woznamjenja, zo sy worÅ¡tu zniÄiÅ‚, ale nic wobdźěłak, kotryž je z njej zwjazany.",'minZoomLevelError':"Kajkosć minZoomLevel je jenož za wužiwanje z worÅ¡tami myslena, kotrež wot FixedZoomLevels pochadźeja. Zo tuta worÅ¡ta wfs za minZoomLevel pÅ™epruwuje, je relikt zaÅ„dźenosće. Njemóžemy wÅ¡ak ju wotstronić, bjeztoho zo aplikacije, kotrež na OpenLayers bazÄ›ruja a snano tutu kajkosć wužiwaja, hižo njefunguja. Tohodla smy ju jako zestarjenu woznamjenili -- pÅ™epruwowanje za minZoomLevel budu so we wersiji 3.0 wotstronjeć. ProÅ¡u wužij mÄ›sto toho nastajenje min/max, kaž je tu wopisane: http://trac.openlayers.org/wiki/SettingZoomLevels",'commitSuccess':"WFS-Transakcija: WUSPĚŠNA ${response}",'commitFailed':"WFS-Transakcija: NJEPORADŹENA ${response}",'googleWarning':"WorÅ¡ta Google njemóžeÅ¡e so korektnje zaÄitać.\x3cbr\x3e\x3cbr\x3eZo by tutu zdźělenku wotbyÅ‚, wubjer nowy BaseLayer z wubÄ›ra worÅ¡tow horjeka naprawo.\x3cbr\x3e\x3cbr\x3eNajskerje so to stawa, dokelž skript biblioteki Google Maps pak njebu zapÅ™ijaty pak njewobsahuje korektny kluÄ API za twoje sydÅ‚o.\x3cbr\x3e\x3cbr\x3eWuwiwarjo: Za pomoc ke korektnemu fungowanju worÅ¡tow\n\x3ca href=\'http://trac.openlayers.org/wiki/Google\' target=\'_blank\'\x3etu kliknyć\x3c/a\x3e",'getLayerWarning':"WorÅ¡ta ${layerType} njemóžeÅ¡e so korektnje zaÄitać.\x3cbr\x3e\x3cbr\x3eZo by tutu zdźělenku wotbyÅ‚, wubjer nowy BaseLayer z wubÄ›ra worÅ¡tow horjeka naprawo.\x3cbr\x3e\x3cbr\x3eNajskerje so to stawa, dokelž skript biblioteki ${layerLib} njebu korektnje zapÅ™ijaty.\x3cbr\x3e\x3cbr\x3eWuwiwarjo: Za pomoc ke korektnemu fungowanju worÅ¡tow\n\x3ca href=\'http://trac.openlayers.org/wiki/${layerLib}\' target=\'_blank\'\x3etu kliknyć\x3c/a\x3e",'scale':"MÄ›ritko = 1 : ${scaleDenom}",'W':"Z",'E':"W",'N':"S",'S':"J",'layerAlreadyAdded':"Sy spytaÅ‚ runinu ${layerName} karće dodać, ale je so hižo dodaÅ‚a",'reprojectDeprecated':"WužiwaÅ¡ opciju \"reproject\" wořšty ${layerName}. Tuta opcija je zestarjena: jeje wužiwanje bÄ› myslene, zo by zwobraznjenje datow nad komercielnymi bazowymi kartami podpÄ›raÅ‚o, ale funkcionalnosć měła so nÄ›tko z pomocu Sperical Mercator docpěć. DalÅ¡e informacije steja na http://trac.openlayers.org/wiki/SphericalMercator k dispoziciji.",'methodDeprecated':"Tuta metoda je so njeschwaliÅ‚a a budźe so w 3.0 wotstronjeć. ProÅ¡u wužij ${newMethod} mÄ›sto toho.",'boundsAddError':"DyrbiÅ¡ hódnotu x kaž tež y funkciji \"add\" pÅ™epodać.",'lonlatAddError':"DyrbiÅ¡ hódnotu lon kaž tež lat funkciji \"add\" pÅ™epodać.",'pixelAddError':"DyrbiÅ¡ hódnotu x kaž tež y funkciji \"add\" pÅ™epodać.",'unsupportedGeometryType':"NjepodpÄ›rowany geometrijowy typ: ${geomType}",'pagePositionFailed':"OpenLayers.Util.pagePosition je so njeporadźiÅ‚: element z id ${elemId} bu snano wopak zamÄ›stnjeny.",'filterEvaluateNotImplemented':"wuhódnoćenje njeje za tutón filtrowy typ implementowany."});OpenLayers.Lang["hu"]=OpenLayers.Util.applyDefaults({'unhandledRequest':"Nem kezelt kérés visszatérése ${statusText}",'permalink':"Permalink",'overlays':"Rávetítések",'baseLayer':"Alapréteg",'sameProjection':"Az áttekintÅ‘ térkép csak abban az esetben működik, ha ugyanazon a vetületen van, mint a fÅ‘ térkép.",'readNotImplemented':"Olvasás nincs végrehajtva.",'writeNotImplemented':"Ãrás nincs végrehajtva.",'noFID':"Nem frissíthetÅ‘ olyan jellemzÅ‘, amely nem rendelkezik FID-del.",'errorLoadingGML':"Hiba GML-fájl betöltésekor ${url}",'browserNotSupported':"A böngészÅ‘je nem támogatja a vektoros renderelést. A jelenleg támogatott renderelÅ‘k:\n${renderers}",'componentShouldBe':"addFeatures : az összetevÅ‘nek ilyen típusúnak kell lennie: ${geomType}",'getFeatureError':"getFeatureFromEvent réteget hívott meg renderelÅ‘ nélkül. Ez rendszerint azt jelenti, hogy megsemmisített egy fóliát, de néhány ahhoz társított kezelÅ‘t nem.",'minZoomLevelError':"A minZoomLevel tulajdonságot csak a következÅ‘vel való használatra szánták: FixedZoomLevels-leszármazott fóliák. Ez azt jelenti, hogy a minZoomLevel wfs fólia jelölÅ‘négyzetei már a múlté. Mi azonban nem távolíthatjuk el annak a veszélye nélkül, hogy az esetlegesen ettÅ‘l függÅ‘ OL alapú alkalmazásokat tönkretennénk. Ezért ezt érvénytelenítjük -- a minZoomLevel az alul levÅ‘ jelölÅ‘négyzet a 3.0-s verzióból el lesz távolítva. Kérjük, helyette használja a min/max felbontás beállítást, amelyrÅ‘l az alábbi helyen talál leírást: http://trac.openlayers.org/wiki/SettingZoomLevels",'commitSuccess':"WFS tranzakció: SIKERES ${response}",'commitFailed':"WFS tranzakció: SIKERTELEN ${response}",'googleWarning':"A Google fólia betöltése sikertelen.\x3cbr\x3e\x3cbr\x3eAhhoz, hogy ez az üzenet eltűnjön, válasszon egy új BaseLayer fóliát a jobb felsÅ‘ sarokban található fóliakapcsoló segítségével.\x3cbr\x3e\x3cbr\x3eNagy valószínűséggel ez azért van, mert a Google Maps könyvtár parancsfájlja nem található, vagy nem tartalmazza az Ön oldalához tartozó megfelelÅ‘ API-kulcsot.\x3cbr\x3e\x3cbr\x3eFejlesztÅ‘knek: A helyes működtetésre vonatkozó segítség az alábbi helyen érhetÅ‘ el, \x3ca href=\'http://trac.openlayers.org/wiki/Google\' target=\'_blank\'\x3ekattintson ide\x3c/a\x3e",'getLayerWarning':"A(z) ${layerType} fólia nem töltÅ‘dött be helyesen.\x3cbr\x3e\x3cbr\x3eAhhoz, hogy ez az üzenet eltűnjön, válasszon egy új BaseLayer fóliát a jobb felsÅ‘ sarokban található fóliakapcsoló segítségével.\x3cbr\x3e\x3cbr\x3eNagy valószínűséggel ez azért van, mert a(z) ${layerLib} könyvtár parancsfájlja helytelen.\x3cbr\x3e\x3cbr\x3eFejlesztÅ‘knek: A helyes működtetésre vonatkozó segítség az alábbi helyen érhetÅ‘ el, \x3ca href=\'http://trac.openlayers.org/wiki/${layerLib}\' target=\'_blank\'\x3ekattintson ide\x3c/a\x3e",'scale':"Lépték = 1 : ${scaleDenom}",'W':"Ny",'E':"K",'N':"É",'S':"D",'layerAlreadyAdded':"Megpróbálta hozzáadni a(z) ${layerName} fóliát a térképhez, de az már hozzá van adva",'reprojectDeprecated':"Ön a \'reproject\' beállítást használja a(z) ${layerName} fólián. Ez a beállítás érvénytelen: használata az üzleti alaptérképek fölötti adatok megjelenítésének támogatására szolgált, de ezt a funkció ezentúl a Gömbi Mercator használatával érhetÅ‘ el. További információ az alábbi helyen érhetÅ‘ el: http://trac.openlayers.org/wiki/SphericalMercator",'methodDeprecated':"Ez a módszer érvénytelenítve lett és a 3.0-s verzióból el lesz távolítva. Használja a(z) ${newMethod} módszert helyette.",'boundsAddError':"Az x és y értékeknek egyaránt meg kell felelnie, hogy a funkciót hozzáadhassa.",'lonlatAddError':"A hossz. és szél. értékeknek egyaránt meg kell felelnie, hogy a funkciót hozzáadhassa.",'pixelAddError':"Az x és y értékeknek egyaránt meg kell felelnie, hogy a funkciót hozzáadhassa.",'unsupportedGeometryType':"Nem támogatott geometriatípus: ${geomType}",'pagePositionFailed':"OpenLayers.Util.pagePosition sikertelen: lehetséges, hogy a(z) ${elemId} azonosítójú elem téves helyre került.",'filterEvaluateNotImplemented':"ennél a szűrÅ‘típusnál kiértékelés nem hajtódik végre."});OpenLayers.Lang["ia"]=OpenLayers.Util.applyDefaults({'unhandledRequest':"Le responsa a un requesta non esseva maneate: ${statusText}",'permalink':"Permaligamine",'overlays':"Superpositiones",'baseLayer':"Strato de base",'sameProjection':"Le mini-carta functiona solmente si illo es in le mesme projection que le carta principal",'readNotImplemented':"Lectura non implementate.",'writeNotImplemented':"Scriptura non implementate.",'noFID':"Non pote actualisar un elemento sin FID.",'errorLoadingGML':"Error al cargamento del file GML ${url}",'browserNotSupported':"Tu navigator non supporta le rendition de vectores. Le renditores actualmente supportate es:\n${renderers}",'componentShouldBe':"addFeatures: le componente debe esser del typo ${geomType}",'getFeatureError':"getFeatureFromEvent ha essite appellate in un strato sin renditor. Isto significa generalmente que tu ha destruite un strato, ma lassava un gestor associate con illo.",'minZoomLevelError':"Le proprietate minZoomLevel es solmente pro uso con le stratos descendente de FixedZoomLevels. Le facto que iste strato WFS verifica minZoomLevel es un reliquia del passato. Nonobstante, si nos lo remove immediatemente, nos pote rumper applicationes a base de OL que depende de illo. Ergo nos lo declara obsolete; le verification de minZoomLevel in basso essera removite in version 3.0. Per favor usa in su loco le configuration de resolutiones min/max como describite a: http://trac.openlayers.org/wiki/SettingZoomLevels",'commitSuccess':"Transaction WFS: SUCCESSO ${response}",'commitFailed':"Transaction WFS: FALLEVA ${response}",'googleWarning':"Le strato Google non poteva esser cargate correctemente.\x3cbr\x3e\x3cbr\x3ePro disfacer te de iste message, selige un nove BaseLayer in le selector de strato in alto a dextra.\x3cbr\x3e\x3cbr\x3eMulto probabilemente, isto es proque le script del libreria de Google Maps non esseva includite o non contine le clave API correcte pro tu sito.\x3cbr\x3e\x3cbr\x3eDisveloppatores: Pro adjuta de corriger isto, \x3ca href=\'http://trac.openlayers.org/wiki/Google\' target=\'_blank\'\x3eclicca hic\x3c/a",'getLayerWarning':"Le strato ${layerType} non poteva esser cargate correctemente.\x3cbr\x3e\x3cbr\x3ePro disfacer te de iste message, selige un nove BaseLayer in le selector de strato in alto a dextra.\x3cbr\x3e\x3cbr\x3eMulto probabilemente, isto es proque le script del libreria de ${layerLib} non esseva correctemente includite.\x3cbr\x3e\x3cbr\x3eDisveloppatores: Pro adjuta de corriger isto, \x3ca href=\'http://trac.openlayers.org/wiki/${layerLib}\' target=\'_blank\'\x3eclicca hic\x3c/a\x3e",'scale':"Scala = 1 : ${scaleDenom}",'W':"W",'E':"E",'N':"N",'S':"S",'layerAlreadyAdded':"Tu tentava adder le strato: ${layerName} al carta, ma illo es ja presente",'reprojectDeprecated':"Tu usa le option \'reproject\' in le strato ${layerName} layer. Iste option es obsolescente: illo esseva pro poter monstrar datos super cartas de base commercial, ma iste functionalitate pote ora esser attingite con le uso de Spherical Mercator. Ulterior information es disponibile a http://trac.openlayers.org/wiki/SphericalMercator.",'methodDeprecated':"Iste methodo ha essite declarate obsolescente e essera removite in version 3.0. Per favor usa ${newMethod} in su loco.",'boundsAddError':"Tu debe passar le duo valores x e y al function add.",'lonlatAddError':"Tu debe passar le duo valores lon e lat al function add.",'pixelAddError':"Tu debe passar le duo valores x e y al function add.",'unsupportedGeometryType':"Typo de geometria non supportate: ${geomType}",'pagePositionFailed':"OpenLayers.Util.pagePosition falleva: le elemento con id ${elemId} pote esser mal placiate.",'filterEvaluateNotImplemented':"\"evaluate\" non es implementate pro iste typo de filtro."});OpenLayers.Lang["id"]=OpenLayers.Util.applyDefaults({'unhandledRequest':"Permintaan yang tak tertangani menghasilkan ${statusText}",'permalink':"Pranala permanen",'overlays':"Hamparan",'baseLayer':"Lapisan Dasar",'sameProjection':"Peta tinjauan hanya bekerja bila dalam proyeksi yang sama dengan peta utama",'readNotImplemented':"Membaca tidak diterapkan.",'writeNotImplemented':"Menyimpan tidak diterapkan.",'noFID':"Tidak dapat memperbarui fitur yang tidak memiliki FID.",'errorLoadingGML':"Kesalahan dalam memuat berkas GML ${url}",'browserNotSupported':"Peramban Anda tidak mendukung penggambaran vektor. Penggambar yang didukung saat ini adalah:\n${renderers}",'componentShouldBe':"addFeatures : komponen harus berupa ${geomType}",'getFeatureError':"getFeatureFromEvent diterapkan pada lapisan tanpa penggambar. Ini biasanya berarti Anda menghapus sebuah lapisan, tetapi tidak menghapus penangan yang terkait dengannya.",'minZoomLevelError':"Properti minZoomLevel hanya ditujukan bekerja dengan lapisan FixedZoomLevels-descendent. Pengecekan minZoomLevel oleh lapisan wfs adalah peninggalan masa lalu. Kami tidak dapat menghapusnya tanpa kemungkinan merusak aplikasi berbasis OL yang mungkin bergantung padanya. Karenanya, kami menganggapnya tidak berlaku -- Cek minZoomLevel di bawah ini akan dihapus pada 3.0. Silakan gunakan penyetelan resolusi min/maks seperti dijabarkan di sini: http://trac.openlayers.org/wiki/SettingZoomLevels",'commitSuccess':"WFS Transaksi: BERHASIL ${respon}",'commitFailed':"WFS Transaksi: GAGAL ${respon}",'googleWarning':"Lapisan Google tidak dapat dimuat dengan benar.\x3cbr\x3e\x3cbr\x3eUntuk menghilangkan pesan ini, pilih suatu BaseLayer baru melalui penukar lapisan (layer switcher) di ujung kanan atas.\x3cbr\x3e\x3cbr\x3eKemungkinan besar ini karena pustaka skrip Google Maps tidak disertakan atau tidak mengandung kunci API yang tepat untuk situs Anda.\x3cbr\x3e\x3cbr\x3ePengembang: Untuk bantuan mengatasi masalah ini, \x3ca href=\'http://trac.openlayers.org/wiki/Google\' target=\'_blank\'\x3eklik di sini\x3c/a\x3e",'getLayerWarning':"Lapisan ${layerType} tidak dapat dimuat dengan benar.\x3cbr\x3e\x3cbr\x3eUntuk menghilangkan pesan ini, pilih suatu BaseLayer baru melalui penukar lapisan (layer switcher) di ujung kanan atas.\x3cbr\x3e\x3cbr\x3eKemungkinan besar ini karena pustaka skrip Google Maps tidak disertakan dengan benar.\x3cbr\x3e\x3cbr\x3ePengembang: Untuk bantuan mengatasi masalah ini, \x3ca href=\'http://trac.openlayers.org/wiki/${layerLib}\' target=\'_blank\'\x3eklik di sini\x3c/a\x3e",'scale':"Sekala = 1 : ${scaleDenom}",'W':"B",'E':"T",'N':"U",'S':"S",'layerAlreadyAdded':"Anda mencoba menambahkan lapisan: ${layerName} ke dalam peta, tapi lapisan itu telah ditambahkan",'reprojectDeprecated':"Anda menggunakan opsi \'reproject\' pada lapisan ${layerName}. Opsi ini telah ditinggalkan: penggunaannya dirancang untuk mendukung tampilan data melalui peta dasar komersial, tapi fungsionalitas tersebut saat ini harus dilakukan dengan menggunakan dukungan Spherical Mercator. Informasi lebih lanjut tersedia di http://trac.openlayers.org/wiki/SphericalMercator.",'methodDeprecated':"Metode ini telah usang dan akan dihapus di 3.0. Sebaliknya, harap gunakan ${newMethod}.",'boundsAddError':"Anda harus memberikan kedua nilai x dan y ke fungsi penambah.",'lonlatAddError':"Anda harus memberikan kedua nilai lon dan lat ke fungsi penambah.",'pixelAddError':"Anda harus memberikan kedua nilai x dan y ke fungsi penambah.",'unsupportedGeometryType':"Tipe geometri tak didukung: ${geomType}",'pagePositionFailed':"OpenLayers.Util.pagePosition gagal: elemen dengan id ${elemId} mungkin salah tempat.",'filterEvaluateNotImplemented':"evaluasi tidak tersedia untuk tipe filter ini."});OpenLayers.Lang["io"]=OpenLayers.Util.applyDefaults({'scale':"Skalo = 1 : ${scaleDenom}"});OpenLayers.Lang["is"]=OpenLayers.Util.applyDefaults({'permalink':"Varanlegur tengill",'overlays':"Þekjur",'baseLayer':"Grunnlag",'sameProjection':"Yfirlitskortið virkar aðeins ef það er í sömu vörpun og aðalkortið",'readNotImplemented':"Skrifun er óútfærð.",'writeNotImplemented':"Lestur er óútfærður.",'errorLoadingGML':"Villa kom upp við að hlaða inn GML skránni ${url}",'scale':"Skali = 1 : ${scaleDenom}",'layerAlreadyAdded':"Þú reyndir að bæta laginu ${layerName} á kortið en það er þegar búið að bæta því við",'methodDeprecated':"Þetta fall hefur verið úrelt og verður fjarlægt í 3.0. Notaðu ${newMethod} í staðin."});OpenLayers.Lang["ja"]=OpenLayers.Util.applyDefaults({'unhandledRequest':"未処ç†ã®è¦æ±‚㯠${statusText} ã‚’è¿”ã—ã¾ã™",'permalink':"パーマリンク",'overlays':"オーãƒãƒ¼ãƒ¬ã‚¤",'baseLayer':"基底レイヤー",'sameProjection':"概観地図ã¯ãƒ¡ã‚¤ãƒ³ã®åœ°å›³ã¨åŒã˜æŠ•å½±æ³•ã‚’ã¨ã‚‹å ´åˆã®ã¿æ©Ÿèƒ½ã—ã¾ã™",'readNotImplemented':"読ã¿è¾¼ã¿ã¯å®Ÿè£…ã•ã‚Œã¦ã„ã¾ã›ã‚“。",'writeNotImplemented':"書ãè¾¼ã¿ã¯å®Ÿè£…ã•ã‚Œã¦ã„ã¾ã›ã‚“。",'noFID':"FID ã®ãªã„地物ã¯æ›´æ–°ã§ãã¾ã›ã‚“。",'errorLoadingGML':"GML ファイル ${url} ã®èª­ã¿è¾¼ã¿ã‚¨ãƒ©ãƒ¼",'browserNotSupported':"ã‚ãªãŸã®ãƒ–ラウザã¯ãƒ™ã‚¯ã‚¿ãƒ¼ã‚°ãƒ©ãƒ•ã‚£ãƒƒã‚¯ã‚¹ã®æ写ã«å¯¾å¿œã—ã¦ã„ã¾ã›ã‚“。ç¾æ™‚点ã§å¯¾å¿œã—ã¦ã„るソフトウェアã¯ä»¥ä¸‹ã®ã‚‚ã®ã§ã™ã€‚\n${renderers}",'componentShouldBe':"addFeatures: è¦ç´ ã¯ ${geomType} ã§ã‚ã‚‹ã¹ãã§ã™",'getFeatureError':"getFeatureFromEvent ãŒãƒ¬ãƒ³ãƒ€ãƒ©ãƒ¼ã®ãªã„レイヤーã‹ã‚‰å‘¼ã°ã‚Œã¾ã—ãŸã€‚通常ã€ã“ã‚Œã¯ã‚ãªãŸãŒãƒ¬ã‚¤ãƒ¤ãƒ¼ã‚’ã€ãã‚Œã«é–¢é€£ã¥ã‘られãŸã„ãã¤ã‹ã®ãƒãƒ³ãƒ‰ãƒ©ã‚’除ã„ã¦ã€ç ´å£Šã—ã¦ã—ã¾ã£ãŸã“ã¨ã‚’æ„味ã—ã¾ã™ã€‚",'minZoomLevelError':"minZoomLevel プロパティ㯠FixedZoomLevels を継承ã™ã‚‹ãƒ¬ã‚¤ãƒ¤ãƒ¼ã§ã®ä½¿ç”¨ã®ã¿ã‚’想定ã—ã¦ã„ã¾ã™ã€‚ã“ã® minZoomLevel ã«å¯¾ã™ã‚‹ WFS レイヤーã®æ¤œæŸ»ã¯æ­´å²çš„ãªã‚‚ã®ã§ã™ã€‚ã—ã‹ã—ãªãŒã‚‰ã€ã“ã®æ¤œæŸ»ã‚’除去ã™ã‚‹ã¨ãã‚Œã«ä¾å­˜ã™ã‚‹ OpenLayers ベースã®ã‚¢ãƒ—リケーションを破壊ã—ã¦ã—ã¾ã†å¯èƒ½æ€§ãŒã‚ã‚Šã¾ã™ã€‚よã£ã¦å»ƒæ­¢ãŒäºˆå®šã•ã‚Œã¦ãŠã‚Šã€ã“ã® minZoomLevel 検査ã¯ãƒãƒ¼ã‚¸ãƒ§ãƒ³3.0ã§é™¤åŽ»ã•ã‚Œã¾ã™ã€‚代ã‚ã‚Šã«ã€http://trac.openlayers.org/wiki/SettingZoomLevels ã§è§£èª¬ã•ã‚Œã¦ã„ã‚‹ã€æœ€å°ãŠã‚ˆã³æœ€å¤§è§£åƒåº¦è¨­å®šã‚’使用ã—ã¦ãã ã•ã„。",'commitSuccess':"WFS トランザクション: æˆåŠŸ ${response}",'commitFailed':"WFS トランザクション: 失敗 ${response}",'googleWarning':"Google レイヤーãŒæ­£ã—ã読ã¿è¾¼ã¿ã‚’è¡Œãˆã¾ã›ã‚“ã§ã—ãŸã€‚\x3cbr\x3e\x3cbr\x3eã“ã®ãƒ¡ãƒƒã‚»ãƒ¼ã‚¸ã‚’消ã™ã«ã¯ã€å³ä¸Šã®éš…ã«ã‚るレイヤー切り替ãˆéƒ¨åˆ†ã§æ–°ã—ã„基底レイヤーをé¸ã‚“ã§ãã ã•ã„。\x3cbr\x3e\x3cbr\x3eãŠãらãã€ã“れ㯠Google マップ用ライブラリã®ã‚¹ã‚¯ãƒªãƒ—トãŒçµ„ã¿è¾¼ã¾ã‚Œã¦ã„ãªã„ã‹ã€ã‚ãªãŸã®ã‚µã‚¤ãƒˆã«å¯¾å¿œã™ã‚‹æ­£ã—ã„ API キーãŒè¨­å®šã•ã‚Œã¦ã„ãªã„ãŸã‚ã§ã™ã€‚\x3cbr\x3e\x3cbr\x3e開発者ã®æ–¹ã¸: æ­£ã—ã„動作をã•ã›ã‚‹ãŸã‚ã«\x3ca href=\'http://trac.openlayers.org/wiki/Google\' target=\'_blank\'\x3eã“ã¡ã‚‰ã®ã‚¦ã‚£ã‚­\x3c/a\x3eã‚’å‚ç…§ã—ã¦ãã ã•ã„。",'getLayerWarning':"${layerType} レイヤーãŒæ­£ã—ã読ã¿è¾¼ã¿ã‚’è¡Œãˆã¾ã›ã‚“ã§ã—ãŸã€‚\x3cbr\x3e\x3cbr\x3eã“ã®ãƒ¡ãƒƒã‚»ãƒ¼ã‚¸ã‚’消ã™ã«ã¯ã€å³ä¸Šã®éš…ã«ã‚るレイヤー切り替ãˆéƒ¨åˆ†ã§æ–°ã—ã„基底レイヤーをé¸ã‚“ã§ãã ã•ã„。\x3cbr\x3e\x3cbr\x3eãŠãらãã€ã“れ㯠${layerLib} ライブラリã®ã‚¹ã‚¯ãƒªãƒ—トãŒæ­£ã—ã組ã¿è¾¼ã¾ã‚Œã¦ã„ãªã„ãŸã‚ã§ã™ã€‚\x3cbr\x3e\x3cbr\x3e開発者ã®æ–¹ã¸: æ­£ã—ã„動作をã•ã›ã‚‹ãŸã‚ã«\x3ca href=\'http://trac.openlayers.org/wiki/${layerLib}\' target=\'_blank\'\x3eã“ã¡ã‚‰ã®ã‚¦ã‚£ã‚­\x3c/a\x3eã‚’å‚ç…§ã—ã¦ãã ã•ã„。",'scale':"縮尺 = 1 : ${scaleDenom}",'W':"西",'E':"æ±",'N':"北",'S':"å—",'layerAlreadyAdded':"ã‚ãªãŸã¯ã€Œ${layerName}ã€ã‚’地図ã«è¿½åŠ ã—よã†ã¨è©¦ã¿ã¾ã—ãŸãŒã€ãã®ãƒ¬ã‚¤ãƒ¤ãƒ¼ã¯æ—¢ã«è¿½åŠ ã•ã‚Œã¦ã„ã¾ã™",'reprojectDeprecated':"ã‚ãªãŸã¯ã€Œ${layerName}ã€ãƒ¬ã‚¤ãƒ¤ãƒ¼ã§ reproject オプションを使ã£ã¦ã„ã¾ã™ã€‚ã“ã®ã‚ªãƒ—ションã¯å•†ç”¨ã®åŸºåº•åœ°å›³ä¸Šã«æƒ…報を表示ã™ã‚‹ç›®çš„ã§è¨­è¨ˆã•ã‚Œã¾ã—ãŸãŒã€ç¾åœ¨ã§ã¯ãã®æ©Ÿèƒ½ã¯ Spherical Mercator サãƒãƒ¼ãƒˆã‚’利用ã—ã¦å®Ÿç¾ã•ã‚Œã¦ãŠã‚Šã€ã“ã®ã‚ªãƒ—ションã®ä½¿ç”¨ã¯éžæŽ¨å¥¨ã§ã™ã€‚追加ã®æƒ…報㯠http://trac.openlayers.org/wiki/SphericalMercator ã§å…¥æ‰‹ã§ãã¾ã™ã€‚",'methodDeprecated':"ã“ã®ãƒ¡ã‚½ãƒƒãƒ‰ã¯å»ƒæ­¢ãŒäºˆå®šã•ã‚Œã¦ãŠã‚Šã€ãƒãƒ¼ã‚¸ãƒ§ãƒ³3.0ã§é™¤åŽ»ã•ã‚Œã¾ã™ã€‚代ã‚ã‚Šã« ${newMethod} を使用ã—ã¦ãã ã•ã„。",'boundsAddError':"x 㨠y 両方ã®å€¤ã‚’ add 関数ã«æ¸¡ã•ãªã‘ã‚Œã°ãªã‚Šã¾ã›ã‚“。",'lonlatAddError':"lon 㨠lat 両方ã®å€¤ã‚’ add 関数ã«æ¸¡ã•ãªã‘ã‚Œã°ãªã‚Šã¾ã›ã‚“。",'pixelAddError':"x 㨠y ã®å€¤ä¸¡æ–¹ã‚’ add 関数ã«æ¸¡ã•ãªã‘ã‚Œã°ãªã‚Šã¾ã›ã‚“。",'unsupportedGeometryType':"未対応ã®å½¢çŠ¶åž‹: ${geomType}",'pagePositionFailed':"OpenLayers.Util.pagePosition ãŒå¤±æ•—ã—ã¾ã—ãŸ: id ${elemId} ã‚’ã‚‚ã¤è¦ç´ ãŒèª¤ã£ãŸä½ç½®ã«ã‚ã‚‹å¯èƒ½æ€§ãŒã‚ã‚Šã¾ã™ã€‚",'filterEvaluateNotImplemented':"ã“ã®ãƒ•ã‚£ãƒ«ã‚¿ãƒ¼åž‹ã«ã¤ã„㦠evaluate ã¯å®Ÿè£…ã•ã‚Œã¦ã„ã¾ã›ã‚“。"});OpenLayers.Lang["km"]=OpenLayers.Util.applyDefaults({'permalink':"ážáŸ†ážŽáž—្ជាប់អចិន្ážáŸ’រៃយáŸ",'baseLayer':"ស្រទាប់បាážâ€‹",'errorLoadingGML':"កំហុសកំឡុងពáŸáž›áž•áŸ’ទុកឯកសារ GML ${url}",'scale':"មាážáŸ’រដ្ឋាន = ១ ៖ ${scaleDenom}"});OpenLayers.Lang["ksh"]=OpenLayers.Util.applyDefaults({'unhandledRequest':"Met dä Antwoot op en Aanfrooch ham_mer nix aanjefange: ${statusText}",'permalink':"Lengk op Duuer",'overlays':"Drövver jelaat",'baseLayer':"Jrund-Nivoh",'sameProjection':"De Övverseeschs_Kaat deiht et bloß, wann se de sälve Projäxjuhn bruche deiht, wi de Houp_Kaat",'readNotImplemented':"„\x3ccode lang=\"en\"\x3eread\x3c/code\x3e“ is em Projramm nit fürjesinn.",'writeNotImplemented':"„\x3ccode lang=\"en\"\x3ewrite\x3c/code\x3e“ is em Projramm nit fürjesinn.",'noFID':"En Saach, woh kein \x3ci lang=\"en\"\x3eFID\x3c/i\x3e för doh es, löht sesch nit ändere.",'errorLoadingGML':"Fähler beim \x3ci lang=\"en\"\x3eGML\x3c/i\x3e-Datei-Laade vun \x3ccode\x3e${url}\x3c/code\x3e",'browserNotSupported':"Dinge Brauser kann kein Väktore ußjävve. De Zoote Ußjaabe, di em Momang jon, sen:\n${renderers}",'componentShouldBe':"\x3ccode lang=\"en\"\x3eaddFeatures\x3c/code\x3e: dä Aandeil sullt vun dä Zoot „\x3ccode lang=\"en\"\x3e${geomType}\x3c/code\x3e“ sin.",'getFeatureError':"\x3ccode lang=\"en\"\x3egetFeatureFromEvent\x3c/code\x3e es vun enem Nivoh opjeroofe woode, woh et kei Projramm zom Ußjävve jit. Dat bedügg för jewöhnlesch, dat De e Nivoh kapott jemaat häs, ävver nit e Projramm för domet ömzejonn, wat domet verbonge es.",'minZoomLevelError':"De Eijeschaff „\x3ccode lang=\"en\"\x3eminZoomLevel\x3c/code\x3e“ es bloß doför jedaach, dat mer se met dä Nivvohß bruch, di vun \x3ccode lang=\"en\"\x3eFixedZoomLevels\x3c/code\x3e affhange don. Dat dat \x3ci lang=\"en\"\x3eWFS\x3c/i\x3e-Nivvoh övverhoup de Eijeschaff „\x3ccode lang=\"en\"\x3eminZoomLevel\x3c/code\x3e“ pröhfe deiht, es noch övveresch vun fröhjer. Mer künne dat ävver jez nit fott lohße, oohne dat mer Jevaa loufe, dat Aanwendunge vun OpenLayers nit mieh loufe, di sesch doh velleijsch noch drop am verlohße sin. Dröm sare mer, dat mer et nit mieh han welle, un de „\x3ccode lang=\"en\"\x3eminZoomLevel\x3c/code\x3e“-Eijeschaff weed hee vun de Version 3.0 af nit mieh jeprööf wäde. Nemm doför de Enstellung för de hühßte un de kleinßte Oplöhsung, esu wi et en http://trac.openlayers.org/wiki/SettingZoomLevels opjeschrevve es.",'commitSuccess':"Dä \x3ci lang=\"en\"\x3eWFS\x3c/i\x3e-Vörjang es joot jeloufe: ${response}",'commitFailed':"Dä \x3ci lang=\"en\"\x3eWFS\x3c/i\x3e-Vörjang es scheif jejange: ${response}",'googleWarning':"Dat Nivvoh \x3ccode lang=\"en\"\x3eGoogle\x3c/code\x3e kunnt nit reschtesch jelaade wääde.\x3cbr /\x3e\x3cbr /\x3eÖm hee di Nohreesch loß ze krijje, donn en ander Jrund-Nivvoh ußsöhke, rähß bovve en de Äk.\x3cbr /\x3e\x3cbr /\x3eWascheinlesch es dat wiel dat \x3ci lang=\"en\"\x3eGoogle-Maps\x3c/i\x3e-Skrepp entweeder nit reschtesch enjebonge wood, udder nit dä reschtejje \x3ci lang=\"en\"\x3eAPI\x3c/i\x3e-Schlößel för Ding Web-ßait scheke deiht.\x3cbr /\x3e\x3cbr /\x3eFör Projrammierer jidd_et Hölp do_drövver, \x3ca href=\"http://trac.openlayers.org/wiki/Google\" target=\"_blank\"\x3ewi mer dat aan et Loufe brengk\x3c/a\x3e.",'getLayerWarning':"Dat Nivvoh \x3ccode\x3e${layerType}\x3c/code\x3e kunnt nit reschtesch jelaade wääde.\x3cbr /\x3e\x3cbr /\x3eÖm hee di Nohreesch loß ze krijje, donn en ander Jrund-Nivvoh ußsöhkre, rähß bovve en de Äk.\x3cbr /\x3e\x3cbr /\x3eWascheinlesch es dat, wiel dat Skrepp \x3ccode\x3e${layerLib}\x3c/code\x3e nit reschtesch enjebonge wood.\x3cbr /\x3e\x3cbr /\x3eFör Projrammierer jidd_Et Hölp do_drövver, \x3ca href=\"http://trac.openlayers.org/wiki/${layerLib}\" target=\"_blank\"\x3ewi mer dat aan et Loufe brengk\x3c/a\x3e.",'scale':"Mohßshtaab = 1 : ${scaleDenom}",'W':"W",'E':"O",'N':"N",'S':"S",'layerAlreadyAdded':"Do häß versöhk, dat Nivvoh \x3ccode\x3e${layerName}\x3c/code\x3e en di Kaat eren ze bränge, et wohr ävver ald do dren.",'reprojectDeprecated':"Do bruchs de Ußwahl \x3ccode\x3ereproject\x3c/code\x3e op däm Nivvoh \x3ccode\x3e${layerName}\x3c/code\x3e. Di Ußwahl es nit mieh jähn jesinn. Se wohr doför jedaach, öm Date op jeschääfsmäßesch eruß jejovve Kaate bovve drop ze moole, wat ävver enzwesche besser met dä Öngershtözung för de ßfääresche Mäkaator Beldscher jeiht. Doh kanns De mieh drövver fenge op dä Sigg: http://trac.openlayers.org/wiki/SphericalMercator.",'methodDeprecated':"Hee di Metood es nim_mih aktoäll un et weed se en dä Version 3.0 nit mieh jävve. Nemm \x3ccode\x3e${newMethod}\x3c/code\x3e doföör.",'boundsAddError':"Do moß beeds vun de \x3ccode\x3ex\x3c/code\x3e un \x3ccode\x3ey\x3c/code\x3e Wääte aan de Fungkßjohn \x3ccode\x3eadd\x3c/code\x3e jävve.",'lonlatAddError':"Do moß beeds \x3ccode\x3elon\x3c/code\x3e un \x3ccode\x3elat\x3c/code\x3e aan de Fungkßjohn \x3ccode\x3eadd\x3c/code\x3e jävve.",'pixelAddError':"Do moß beeds \x3ccode\x3ex\x3c/code\x3e un \x3ccode\x3ey\x3c/code\x3e aan de Fungkßjohn \x3ccode\x3eadd\x3c/code\x3e jävve.",'unsupportedGeometryType':"De Zoot Jommetrii dom_mer nit ongershtöze: \x3ccode\x3e${geomType}\x3c/code\x3e",'pagePositionFailed':"\x3ccode lang=\"en\"\x3eOpenLayers.Util.pagePosition\x3c/code\x3e es donevve jejange: dat Denge met dä Kännong \x3ccode\x3e${elemId}\x3c/code\x3e künnt am verkeehte Plaz sin.",'filterEvaluateNotImplemented':"„\x3ccode lang=\"en\"\x3eevaluate\x3c/code\x3e“ es för di Zoot Fellter nit enjereschdt."});OpenLayers.Lang["nds"]=OpenLayers.Util.applyDefaults({'unhandledRequest':"Unbehannelt Trüchmellels för de Anfraag ${statusText}",'permalink':"Permalink",'overlays':"Overlays",'baseLayer':"Achtergrundkoort",'sameProjection':"De Översichtskoort geiht blot, wenn de sülve Projekschoon as bi de Hööftkoort bruukt warrt",'readNotImplemented':"Lesen is nich inricht.",'writeNotImplemented':"Schrieven is nich inricht.",'noFID':"En Feature, dat keen FID hett, kann nich aktuell maakt warrn.",'errorLoadingGML':"Fehler bi’t Laden vun de GML-Datei ${url}",'browserNotSupported':"Dien Browser ünnerstütt keen Vektorbiller. Ãœnnerstütt Renderers:\n${renderers}",'componentShouldBe':"addFeatures : Kumponent schull man den Typ ${geomType} hebben",'getFeatureError':"getFeatureFromEvent is von en Laag ahn Render opropen worrn. Dat bedüüdt normalerwies, dat en Laag wegmaakt worrn is, aver nich de Handler, de dor op verwiest.",'commitSuccess':"WFS-Transakschoon: hett klappt ${response}",'commitFailed':"WFS-Transakschoon: hett nich klappt ${response}",'scale':"Skaal = 1 : ${scaleDenom}",'layerAlreadyAdded':"Du versöchst de Laag „${layerName}“ to de Koort totofögen, man de is al toföögt",'methodDeprecated':"Disse Methood is oold un schall dat in 3.0 nich mehr geven. Bruuk dor man beter ${newMethod} för.",'boundsAddError':"De Weert x un y, de mööt all beid an de add-Funkschoon övergeven warrn.",'lonlatAddError':"De Weert lon un lat, de mööt all beid an de add-Funkschoon övergeven warrn.",'pixelAddError':"De Weert x un y, de mööt all beid an de add-Funkschoon övergeven warrn.",'unsupportedGeometryType':"Nich ünnerstütt Geometrie-Typ: ${geomType}",'pagePositionFailed':"OpenLayers.Util.pagePosition güng nich: Element mit de Id ${elemId} is villicht an’n verkehrten Platz."});OpenLayers.Lang["nn"]=OpenLayers.Util.applyDefaults({'scale':"Skala = 1 : ${scaleDenom}",'layerAlreadyAdded':"Du freista Ã¥ leggja til laget «${layerName}» pÃ¥ kartet, men det har alt vorte lagt til.",'boundsAddError':"Du er nøydd til Ã¥ gje bÃ¥de ein x- og ein y-verdi til «add»-funksjonen.",'lonlatAddError':"Du er nøydd til Ã¥ gje bÃ¥de lon- og lat-verdiar til «add»-funksjonen.",'pixelAddError':"Du er nøydd til Ã¥ gje bÃ¥de ein x- og ein y-verdi til «add»-funksjonen."});OpenLayers.Lang["oc"]=OpenLayers.Util.applyDefaults({'unhandledRequest':"Requèsta pas gerida, retorna ${statusText}",'permalink':"Permaligam",'overlays':"Calques",'baseLayer':"Calc de basa",'sameProjection':"La carta de situacion fonciona pas que quora sa projeccion es la meteissa que la de la carta principala",'readNotImplemented':"Lectura pas implementada.",'writeNotImplemented':"Escritura pas implementada.",'noFID':"Impossible de metre a jorn un objècte sens identificant (fid).",'errorLoadingGML':"Error al cargament del fichièr GML ${url}",'browserNotSupported':"Vòstre navegidor supòrta pas lo rendut vectorial. Los renderers actualament suportats son : \n${renderers}",'componentShouldBe':"addFeatures : lo compausant deuriá èsser de tipe ${geomType}",'getFeatureError':"getFeatureFromEvent es estat apelat sus un calc sens renderer. Aquò significa generalament qu\'avètz destruch aqueste jaç, mas qu\'avètz conservat un handler que li èra associat.",'minZoomLevelError':"La proprietat minZoomLevel deu èsser utilizada solament per de jaces FixedZoomLevels-descendent. Lo fach qu\'aqueste jaç WFS verifique la preséncia de minZoomLevel es una relica del passat. Çaquelà, la podèm suprimir sens copar d\'aplicacions que ne poirián dependre. Es per aquò que la depreciam -- la verificacion del minZoomLevel serà suprimida en version 3.0. A la plaça, mercés d\'utilizar los paramètres de resolucions min/max tal coma descrich sus : http://trac.openlayers.org/wiki/SettingZoomLevels",'commitSuccess':"Transaccion WFS : SUCCES ${response}",'commitFailed':"Transaccion WFS : FRACAS ${response}",'googleWarning':"Lo jaç Google es pas estat en mesura de se cargar corrèctament.\x3cbr\x3e\x3cbr\x3ePer suprimir aqueste messatge, causissètz una BaseLayer novèla dins lo selector de jaç en naut a drecha.\x3cbr\x3e\x3cbr\x3eAquò es possiblament causat par la non-inclusion de la librariá Google Maps, o alara perque que la clau de l\'API correspond pas a vòstre site.\x3cbr\x3e\x3cbr\x3eDesvolopaires : per saber cossí corregir aquò, \x3ca href=\'http://trac.openlayers.org/wiki/Google\' target=\'_blank\'\x3eclicatz aicí\x3c/a\x3e",'getLayerWarning':"Lo jaç ${layerType} es pas en mesura de se cargar corrèctament.\x3cbr\x3e\x3cbr\x3ePer suprimir aqueste messatge, causissètz una BaseLayer novèla dins lo selector de jaç en naut a drecha.\x3cbr\x3e\x3cbr\x3eAquò es possiblament causat per la non-inclusion de la librariá ${layerLib}.\x3cbr\x3e\x3cbr\x3eDesvolopaires : per saber cossí corregir aquí, \x3ca href=\'http://trac.openlayers.org/wiki/${layerLib}\' target=\'_blank\'\x3eclicatz aicí\x3c/a\x3e",'scale':"Escala ~ 1 : ${scaleDenom}",'W':"O",'E':"È",'N':"N",'S':"S",'layerAlreadyAdded':"Avètz ensajat d\'apondre a la carta lo calc : ${layerName}, mas ja es present",'reprojectDeprecated':"Utilizatz l\'opcion \'reproject\' sul jaç ${layerName}. Aquesta opcion es despreciada : Son usatge permetiá d\'afichar de donadas al dessús de jaces raster comercials. Aquesta foncionalitat ara es suportada en utilizant lo supòrt de la projeccion Mercator Esferica. Mai d\'informacion es disponibla sus http://trac.openlayers.org/wiki/SphericalMercator.",'methodDeprecated':"Aqueste metòde es despreciada, e serà suprimida a la version 3.0. Mercés d\'utilizar ${newMethod} a la plaça.",'boundsAddError':"Vos cal passar las doas valors x e y a la foncion add.",'lonlatAddError':"Vos cal passar las doas valors lon e lat a la foncion add.",'pixelAddError':"Vos cal passar las doas valors x e y a la foncion add.",'unsupportedGeometryType':"Tipe de geometria pas suportat : ${geomType}",'pagePositionFailed':"OpenLayers.Util.pagePosition a fracassat : l\'element d\'id ${elemId} poiriá èsser mal posicionat.",'filterEvaluateNotImplemented':"evaluar es pas encara estat implementat per aqueste tipe de filtre."});OpenLayers.Lang["pt"]=OpenLayers.Util.applyDefaults({'unhandledRequest':"Servidor devolveu erro não contemplado ${statusText}",'permalink':"Ligação permanente",'overlays':"Sobreposições",'baseLayer':"Camada Base",'sameProjection':"O mapa panorâmico só funciona quando está na mesma projeção que o mapa principal",'readNotImplemented':"Leitura não implementada.",'writeNotImplemented':"Escrita não implementada.",'noFID':"Não é possível atualizar um elemento para a qual não há FID.",'errorLoadingGML':"Erro ao carregar ficheiro GML ${url}",'browserNotSupported':"O seu navegador não suporta renderização vetorial. Actualmente os renderizadores suportados são:\n${renderers}",'componentShouldBe':"addFeatures: componente deve ser um(a) ${geomType}",'getFeatureError':"getFeatureFromEvent foi chamado numa camada sem renderizador. Isto normalmente significa que destruiu uma camada, mas não um manipulador \'\'(handler)\'\' que lhe está associado.",'minZoomLevelError':"A propriedade minZoomLevel só deve ser usada com as camadas descendentes da FixedZoomLevels. A verificação da propriedade por esta camada wfs é uma relíquia do passado. No entanto, não podemos removê-la sem correr o risco de afectar aplicações OL que dependam dela. Portanto, estamos a torná-la obsoleta -- a verificação minZoomLevel será removida na versão 3.0. Em vez dela, por favor, use as opções de resolução min/max descritas aqui: http://trac.openlayers.org/wiki/SettingZoomLevels",'commitSuccess':"Transacção WFS: SUCESSO ${response}",'commitFailed':"Transacção WFS: FALHOU ${response}",'googleWarning':"A Camada Google não foi correctamente carregada.\x3cbr\x3e\x3cbr\x3ePara deixar de receber esta mensagem, seleccione uma nova Camada-Base no \'\'switcher\'\' de camadas no canto superior direito.\x3cbr\x3e\x3cbr\x3eProvavelmente, isto acontece porque o \'\'script\'\' da biblioteca do Google Maps não foi incluído ou não contém a chave API correcta para o seu sítio.\x3cbr\x3e\x3cbr\x3eProgramadores: Para ajuda sobre como solucionar o problema \x3ca href=\'http://trac.openlayers.org/wiki/Google\' target=\'_blank\'\x3eclique aqui\x3c/a\x3e .",'getLayerWarning':"A camada ${layerType} não foi correctamente carregada.\x3cbr\x3e\x3cbr\x3ePara desactivar esta mensagem, seleccione uma nova Camada-Base no \'\'switcher\'\' de camadas no canto superior direito.\x3cbr\x3e\x3cbr\x3eProvavelmente, isto acontece porque o \'\'script\'\' da biblioteca ${layerLib} não foi incluído correctamente.\x3cbr\x3e\x3cbr\x3eProgramadores: Para ajuda sobre como solucionar o problema \x3ca href=\'http://trac.openlayers.org/wiki/${layerLib}\' target=\'_blank\'\x3eclique aqui\x3c/a\x3e .",'scale':"Escala = 1 : ${scaleDenom}",'W':"O",'E':"E",'N':"N",'S':"S",'layerAlreadyAdded':"Você tentou adicionar a camada: ${layerName} ao mapa, mas ela já tinha sido adicionada antes",'reprojectDeprecated':"Está usando a opção \'reproject\' na camada ${layerName}. Esta opção é obsoleta: foi concebida para permitir a apresentação de dados sobre mapas-base comerciais, mas esta funcionalidade é agora suportada pelo Mercator Esférico. Mais informação está disponível em http://trac.openlayers.org/wiki/SphericalMercator.",'methodDeprecated':"Este método foi declarado obsoleto e será removido na versão 3.0. Por favor, use ${newMethod} em vez disso.",'boundsAddError':"Você deve passar tanto o valor x como o y à função de adição.",'lonlatAddError':"Você deve passar tanto o valor lon como o lat à função de adição.",'pixelAddError':"Você deve passar tanto o valor x como o y à função de adição.",'unsupportedGeometryType':"Tipo de geometria não suportado: ${geomType}",'pagePositionFailed':"OpenLayers.Util.pagePosition falhou: o elemento com o id ${elemId} poderá estar mal-posicionado.",'filterEvaluateNotImplemented':"avaliar não está implementado para este tipo de filtro."});OpenLayers.Lang["ru"]=OpenLayers.Util.applyDefaults({'unhandledRequest':"Ðеобработанный Ð·Ð°Ð¿Ñ€Ð¾Ñ Ð²ÐµÑ€Ð½ÑƒÐ» ${statusText}",'permalink':"ПоÑтоÑÐ½Ð½Ð°Ñ ÑÑылка",'overlays':"Слои",'baseLayer':"ОÑновной Ñлой",'sameProjection':"ÐžÐ±Ð·Ð¾Ñ€Ð½Ð°Ñ ÐºÐ°Ñ€Ñ‚Ð° работает только тогда, когда имеет ту же проекцию, что и оÑновнаÑ",'readNotImplemented':"Чтение не реализовано.",'writeNotImplemented':"ЗапиÑÑŒ не реализована.",'noFID':"Ðевозможно обновить объект, Ð´Ð»Ñ ÐºÐ¾Ñ‚Ð¾Ñ€Ð¾Ð³Ð¾ нет FID.",'errorLoadingGML':"Ошибка при загрузке файла GML ${url}",'browserNotSupported':"Ваш браузер не поддерживает векторную графику. Ðа данный момент поддерживаютÑÑ:\n${renderers}",'componentShouldBe':"addFeatures: компонент должен быть ${geomType}",'getFeatureError':"getFeatureFromEvent вызван Ð´Ð»Ñ ÑÐ»Ð¾Ñ Ð±ÐµÐ· рендерера. Обычно Ñто говорит о том, что вы уничтожили Ñлой, но оÑтавили ÑвÑзанный Ñ Ð½Ð¸Ð¼ обработчик.",'minZoomLevelError':"СвойÑтво minZoomLevel предназначено только Ð´Ð»Ñ Ð¸ÑÐ¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ð½Ð¸Ñ Ñо ÑлоÑми, ÑвлÑющимиÑÑ Ð¿Ð¾Ñ‚Ð¾Ð¼ÐºÐ°Ð¼Ð¸ FixedZoomLevels. То, что Ñтот WFS-Ñлой проверÑетÑÑ Ð½Ð° minZoomLevel — реликт прошлого. Однако мы не можем удалить Ñту функцию, так как, возможно, от неё завиÑÑÑ‚ некоторые оÑнованные на OpenLayers приложениÑ. Ð¤ÑƒÐ½ÐºÑ†Ð¸Ñ Ð¾Ð±ÑŠÑвлена уÑтаревшей — проверка minZoomLevel будет удалена в 3.0. ПожалуйÑта, иÑпользуйте вмеÑто неё наÑтройку мин/Ð¼Ð°ÐºÑ Ñ€Ð°Ð·Ñ€ÐµÑˆÐµÐ½Ð¸Ñ, опиÑанную здеÑÑŒ: http://trac.openlayers.org/wiki/SettingZoomLevels",'commitSuccess':"Ð¢Ñ€Ð°Ð½Ð·Ð°ÐºÑ†Ð¸Ñ WFS: УСПЕШÐО ${response}",'commitFailed':"Ð¢Ñ€Ð°Ð½Ð·Ð°ÐºÑ†Ð¸Ñ WFS: ОШИБКР${response}",'googleWarning':"Слой Google не удалоÑÑŒ нормально загрузить.\x3cbr\x3e\x3cbr\x3eЧтобы избавитьÑÑ Ð¾Ñ‚ Ñтого ÑообщениÑ, выбите другой оÑновной Ñлой в переключателе в правом верхнем углу.\x3cbr\x3e\x3cbr\x3eСкорее вÑего, причина в том, что библиотека Google Maps не была включена или не Ñодержит корректного API-ключа Ð´Ð»Ñ Ð²Ð°ÑˆÐµÐ³Ð¾ Ñайта.\x3cbr\x3e\x3cbr\x3eРазработчикам: чтобы узнать, как Ñделать, чтобы вÑÑ‘ заработало, \x3ca href=\'http://trac.openlayers.org/wiki/Google\' target=\'_blank\'\x3eщёлкните тут\x3c/a\x3e",'getLayerWarning':"Слой ${layerType} не удалоÑÑŒ нормально загрузить. \x3cbr\x3e\x3cbr\x3eЧтобы избавитьÑÑ Ð¾Ñ‚ Ñтого ÑообщениÑ, выбите другой оÑновной Ñлой в переключателе в правом верхнем углу.\x3cbr\x3e\x3cbr\x3eСкорее вÑего, причина в том, что библиотека ${layerLib} не была включена или была включена некорректно.\x3cbr\x3e\x3cbr\x3eРазработчикам: чтобы узнать, как Ñделать, чтобы вÑÑ‘ заработало, \x3ca href=\'http://trac.openlayers.org/wiki/${layerLib}\' target=\'_blank\'\x3eщёлкните тут\x3c/a\x3e",'scale':"МаÑштаб = 1 : ${scaleDenom}",'W':"З",'E':"Ð’",'N':"С",'S':"Ю",'layerAlreadyAdded':"Ð’Ñ‹ попыталиÑÑŒ добавить Ñлой «${layerName}» на карту, но он уже был добавлен",'reprojectDeprecated':"Ð’Ñ‹ иÑпользуете опцию \'reproject\' Ð´Ð»Ñ ÑÐ»Ð¾Ñ ${layerName}. Эта Ð¾Ð¿Ñ†Ð¸Ñ ÑвлÑетÑÑ ÑƒÑтаревшей: ее иÑпользование предполагалоÑÑŒ Ð´Ð»Ñ Ð¿Ð¾Ð´Ð´ÐµÑ€Ð¶ÐºÐ¸ показа данных поверх коммерчеÑких базовых карт, но теперь Ñтот функционал неÑÑ‘Ñ‚ вÑÑ‚Ñ€Ð¾ÐµÐ½Ð½Ð°Ñ Ð¿Ð¾Ð´Ð´ÐµÑ€Ð¶ÐºÐ° ÑферичеÑкой проекции Меркатора. Больше Ñведений доÑтупно на http://trac.openlayers.org/wiki/SphericalMercator.",'methodDeprecated':"Этот метод ÑчитаетÑÑ ÑƒÑтаревшим и будет удалён в верÑии 3.0. ПожалуйÑта, пользуйтеÑÑŒ ${newMethod}.",'boundsAddError':"Функции add надо передавать оба значениÑ, x и y.",'lonlatAddError':"Функции add надо передавать оба значениÑ, lon и lat.",'pixelAddError':"Функции add надо передавать оба значениÑ, x и y.",'unsupportedGeometryType':"Ðеподдерживаемый тип геометрии: ${geomType}",'pagePositionFailed':"OpenLayers.Util.pagePosition failed: Ñлемент Ñ id ${elemId} может находитьÑÑ Ð½Ðµ в нужном меÑте.",'filterEvaluateNotImplemented':"evaluate не реализовано Ð´Ð»Ñ Ñ„Ð¸Ð»ÑŒÑ‚Ñ€Ð° данного типа."});OpenLayers.Lang["sk"]=OpenLayers.Util.applyDefaults({'unhandledRequest':"Neobslúžené požiadavky vracajú ${statusText}",'permalink':"Trvalý odkaz",'overlays':"Prekrytia",'baseLayer':"Základná vrstva",'sameProjection':"Prehľadová mapka funguje iba vtedy, keÄ je v rovnakej projekcii ako hlavná mapa",'readNotImplemented':"Čítanie nie je implementované.",'writeNotImplemented':"Zápis nie je implementovaný.",'noFID':"Nie je možné aktualizovaÅ¥ vlastnosÅ¥, pre ktorú neexistuje FID.",'errorLoadingGML':"Chyba pri naÄítaní súboru GML ${url}",'browserNotSupported':"Váš prehliadaÄ nepodporuje vykresľovanie vektorov. Momentálne podporované vykresľovaÄe sú:\n${renderers}",'componentShouldBe':"addFeatures: komponent by mal byÅ¥ ${geomType}",'getFeatureError':"getFeatureFromEvent bola zavolaná na vrstve bez vykresľovaÄa. To zvyÄajne znamená, že ste odstránili vrstvu, ale nie niektorú z obslúh, ktorá je s ňou asociovaná.",'minZoomLevelError':"VlastnosÅ¥ minZoomLevel je urÄený iba na použitie s vrstvami odvodenými od FixedZoomLevels. To, že táto wfs vrstva kontroluje minZoomLevel je pozostatok z minulosti. Nemôžeme ho vÅ¡ak odstrániÅ¥, aby sme sa vyhli možnému poruÅ¡eniu aplikácií založených na Open Layers, ktoré na tomto môže závisieÅ¥. Preto ho oznaÄujeme ako zavrhovaný - dolu uvedená kontrola minZoomLevel bude odstránená vo verzii 3.0. Použite prosím namiesto toho kontrolu min./max. rozlíšenia podľa tu uvedeného popisu: http://trac.openlayers.org/wiki/SettingZoomLevels",'commitSuccess':"Transakcia WFS: ÚSPEÅ Nà ${response}",'commitFailed':"Transakcia WFS: ZLYHALA ${response}",'googleWarning':"Vrstvu Google nebolo možné správne naÄítaÅ¥.\x3cbr\x3e\x3cbr\x3eAby ste sa tejto správy zbavili vyberte novú BaseLayer v prepínaÄi vrstiev v pravom hornom rohu.\x3cbr\x3e\x3cbr\x3eToto sa stalo pravdepodobne preto, že skript knižnice Google Maps buÄ nebol naÄítaný alebo neobsahuje správny kÄ¾ÃºÄ API pre vaÅ¡u lokalitu.\x3cbr\x3e\x3cbr\x3eVývojári: Tu môžete získaÅ¥ \x3ca href=\'http://trac.openlayers.org/wiki/Google\' target=\'_blank\'\x3epomoc so sfunkÄnením\x3c/a\x3e",'getLayerWarning':"Vrstvu ${layerType} nebolo možné správne naÄítaÅ¥.\x3cbr\x3e\x3cbr\x3eAby ste sa tejto správy zbavili vyberte novú BaseLayer v prepínaÄi vrstiev v pravom hornom rohu.\x3cbr\x3e\x3cbr\x3eToto sa stalo pravdepodobne preto, že skript knižnice ${layerType} buÄ nebol naÄítaný alebo neobsahuje správny kÄ¾ÃºÄ API pre vaÅ¡u lokalitu.\x3cbr\x3e\x3cbr\x3eVývojári: Tu môžete získaÅ¥ \x3ca href=\'http://trac.openlayers.org/wiki/${layerType}\' target=\'_blank\'\x3epomoc so sfunkÄnením\x3c/a\x3e",'scale':"Mierka = 1 : ${scaleDenom}",'layerAlreadyAdded':"Pokúsili ste sa do mapy pridaÅ¥ vrstvu ${layerName}, ale tá už bola pridaná",'reprojectDeprecated':"Používate voľby „reproject“ vrstvy ${layerType}. Táto voľba je zzavrhovaná: jej použitie bolo navrhnuté na podporu zobrazovania údajov nad komerÄnými základovými mapami, ale túto funkcionalitu je teraz možné dosiahnuÅ¥ pomocou Spherical Mercator. ÄŽalÅ¡ie informácie získate na stránke http://trac.openlayers.org/wiki/SphericalMercator.",'methodDeprecated':"Táto metóda je zavrhovaná a bude odstránená vo verzii 3.0. Použite prosím namiesto nej metódu ${newMethod}.",'boundsAddError':"SÄítacej funkcii musíte daÅ¥ hodnoty x aj y.",'lonlatAddError':"SÄítacej funkcii musíte daÅ¥ hodnoty lon (zem. dĺžka) aj lat (zem. šírka).",'pixelAddError':"SÄítacej funkcii musíte daÅ¥ hodnoty x aj y.",'unsupportedGeometryType':"Nepodporovaný typ geometrie: ${geomType}",'pagePositionFailed':"OpenLayers.Util.pagePosition zlyhalo: prvok s id ${elemId} môže byÅ¥ zle umiestnený.",'filterEvaluateNotImplemented':"evaluate nie je implementovaný pre tento typ filtra"});OpenLayers.Lang["te"]=OpenLayers.Util.applyDefaults({'permalink':"à°¸à±à°¥à°¿à°°à°²à°¿à°‚à°•à±",'W':"à°ª",'E':"తూ",'N':"à°‰",'S':"à°¦"});OpenLayers.Lang["vi"]=OpenLayers.Util.applyDefaults({'unhandledRequest':"Không xá»­ lý được phản hồi ${statusText} cho yêu cầu",'permalink':"Liên kết thÆ°á»ng trá»±c",'overlays':"Lấp bản đồ",'baseLayer':"Lá»›p ná»n",'sameProjection':"Bản đồ toàn cảnh chỉ hoạt Ä‘á»™ng khi cùng phép chiếu vá»›i bản đồ chính",'readNotImplemented':"ChÆ°a há»— trợ chức năng Ä‘á»c.",'writeNotImplemented':"ChÆ°a há»— trợ chức năng viết.",'noFID':"Không thể cập nhật tính năng thiếu FID.",'errorLoadingGML':"Lá»—i tải tập tin GML tại ${url}",'browserNotSupported':"Trình duyệt của bạn không há»— trợ chức năng vẽ bằng vectÆ¡. Hiện há»— trợ các bá»™ kết xuất:\n${renderers}",'componentShouldBe':"addFeatures: bá»™ phận cần phải là ${geomType}",'getFeatureError':"getFeatureFromEvent được gá»i từ lá»›p không có bá»™ kết xuất. ThÆ°á»ng thì có lẽ lá»›p bị xóa nhÆ°ng má»™t phần xá»­ lý của nó vẫn còn.",'minZoomLevelError':"Chỉ nên sá»­ dụng thuá»™c tính minZoomLevel vá»›i các lá»›p FixedZoomLevels-descendent. Việc lá»›p wfs này tìm cho minZoomLevel là di tích còn lại từ xÆ°a. Tuy nhiên, nếu chúng tôi dá»i nó thì sẽ vỡ các chÆ°Æ¡ng trình OpenLayers mà dá»±a trên nó. Bởi vậy chúng tôi phản đối sá»­ dụng nó\x26nbsp;– bÆ°á»›c tìm cho minZoomLevel sẽ được dá»i vào phiên bản 3.0. Xin sá»­ dụng thiết lập Ä‘á»™ phân tích tối thiểu / tối Ä‘a thay thế, theo hÆ°á»›ng dẫn này: http://trac.openlayers.org/wiki/SettingZoomLevels",'commitSuccess':"Giao dịch WFS: THÀNH CÔNG ${response}",'commitFailed':"Giao dịch WFS: THẤT BẠI ${response}",'googleWarning':"Không thể tải lá»›p Google đúng đắn.\x3cbr\x3e\x3cbr\x3eÄể tránh thông báo này lần sau, hãy chá»n BaseLayer má»›i dùng Ä‘iá»u khiển chá»n lá»›p ở góc trên phải.\x3cbr\x3e\x3cbr\x3eChắc script thÆ° viện Google Maps hoặc không được bao gồm hoặc không chứa khóa API hợp vá»›i website của bạn.\x3cbr\x3e\x3cbr\x3e\x3ca href=\'http://trac.openlayers.org/wiki/Google\' target=\'_blank\'\x3eTrợ giúp vá» tính năng này\x3c/a\x3e cho ngÆ°á»i phát triển.",'getLayerWarning':"Không thể tải lá»›p ${layerType} đúng đắn.\x3cbr\x3e\x3cbr\x3eÄể tránh thông báo này lần sau, hãy chá»n BaseLayer má»›i dùng Ä‘iá»u khiển chá»n lá»›p ở góc trên phải.\x3cbr\x3e\x3cbr\x3eChắc script thÆ° viện ${layerLib} không được bao gồm đúng kiểu.\x3cbr\x3e\x3cbr\x3e\x3ca href=\'http://trac.openlayers.org/wiki/${layerLib}\' target=\'_blank\'\x3eTrợ giúp vá» tính năng này\x3c/a\x3e cho ngÆ°á»i phát triển.",'scale':"Tá»· lệ = 1 : ${scaleDenom}",'W':"T",'E':"Ä",'N':"B",'S':"N",'layerAlreadyAdded':"Bạn muốn thêm lá»›p ${layerName} vào bản đồ, nhÆ°ng lá»›p này đã được thêm",'reprojectDeprecated':"Bạn Ä‘ang áp dụng chế Ä‘á»™ “reproject†vào lá»›p ${layerName}. Chế Ä‘á»™ này đã bị phản đối: nó có mục đích há»— trợ lấp dữ liệu trên các ná»n bản đồ thÆ°Æ¡ng mại; nên thá»±c hiện hiệu ứng đó dùng tính năng Mercator Hình cầu. Có sẵn thêm chi tiết tại http://trac.openlayers.org/wiki/SphericalMercator .",'methodDeprecated':"PhÆ°Æ¡ng thức này đã bị phản đối và sẽ bị dá»i vào phiên bản 3.0. Xin hãy sá»­ dụng ${newMethod} thay thế.",'boundsAddError':"Cần phải cho cả giá trị x và y vào hàm add.",'lonlatAddError':"Cần phải cho cả giá trị lon và lat vào hàm add.",'pixelAddError':"Cần phải cho cả giá trị x và y vào hàm add.",'unsupportedGeometryType':"Không há»— trợ kiểu địa lý: ${geomType}",'pagePositionFailed':"OpenLayers.Util.pagePosition bị thất bại: nguyên tố vá»›i ID ${elemId} có thể ở chá»— sai.",'filterEvaluateNotImplemented':"chÆ°a há»— trợ evaluate cho loại bá»™ lá»c này."});OpenLayers.Popup.AnchoredBubble=OpenLayers.Class(OpenLayers.Popup.Anchored,{rounded:false,initialize:function(id,lonlat,contentSize,contentHTML,anchor,closeBox,closeBoxCallback){this.padding=new OpenLayers.Bounds(0,OpenLayers.Popup.AnchoredBubble.CORNER_SIZE,0,OpenLayers.Popup.AnchoredBubble.CORNER_SIZE);OpenLayers.Popup.Anchored.prototype.initialize.apply(this,arguments);},draw:function(px){OpenLayers.Popup.Anchored.prototype.draw.apply(this,arguments);this.setContentHTML();this.setBackgroundColor();this.setOpacity();return this.div;},updateRelativePosition:function(){this.setRicoCorners();},setSize:function(contentSize){OpenLayers.Popup.Anchored.prototype.setSize.apply(this,arguments);this.setRicoCorners();},setBackgroundColor:function(color){if(color!=undefined){this.backgroundColor=color;}
+if(this.div!=null){if(this.contentDiv!=null){this.div.style.background="transparent";OpenLayers.Rico.Corner.changeColor(this.groupDiv,this.backgroundColor);}}},setOpacity:function(opacity){OpenLayers.Popup.Anchored.prototype.setOpacity.call(this,opacity);if(this.div!=null){if(this.groupDiv!=null){OpenLayers.Rico.Corner.changeOpacity(this.groupDiv,this.opacity);}}},setBorder:function(border){this.border=0;},setRicoCorners:function(){var corners=this.getCornersToRound(this.relativePosition);var options={corners:corners,color:this.backgroundColor,bgColor:"transparent",blend:false};if(!this.rounded){OpenLayers.Rico.Corner.round(this.div,options);this.rounded=true;}else{OpenLayers.Rico.Corner.reRound(this.groupDiv,options);this.setBackgroundColor();this.setOpacity();}},getCornersToRound:function(){var corners=['tl','tr','bl','br'];var corner=OpenLayers.Bounds.oppositeQuadrant(this.relativePosition);OpenLayers.Util.removeItem(corners,corner);return corners.join(" ");},CLASS_NAME:"OpenLayers.Popup.AnchoredBubble"});OpenLayers.Popup.AnchoredBubble.CORNER_SIZE=5;OpenLayers.Popup.Framed=OpenLayers.Class(OpenLayers.Popup.Anchored,{imageSrc:null,imageSize:null,isAlphaImage:false,positionBlocks:null,blocks:null,fixedRelativePosition:false,initialize:function(id,lonlat,contentSize,contentHTML,anchor,closeBox,closeBoxCallback){OpenLayers.Popup.Anchored.prototype.initialize.apply(this,arguments);if(this.fixedRelativePosition){this.updateRelativePosition();this.calculateRelativePosition=function(px){return this.relativePosition;};}
+this.contentDiv.style.position="absolute";this.contentDiv.style.zIndex=1;if(closeBox){this.closeDiv.style.zIndex=1;}
+this.groupDiv.style.position="absolute";this.groupDiv.style.top="0px";this.groupDiv.style.left="0px";this.groupDiv.style.height="100%";this.groupDiv.style.width="100%";},destroy:function(){this.imageSrc=null;this.imageSize=null;this.isAlphaImage=null;this.fixedRelativePosition=false;this.positionBlocks=null;for(var i=0;i<this.blocks.length;i++){var block=this.blocks[i];if(block.image){block.div.removeChild(block.image);}
+block.image=null;if(block.div){this.groupDiv.removeChild(block.div);}
+block.div=null;}
+this.blocks=null;OpenLayers.Popup.Anchored.prototype.destroy.apply(this,arguments);},setBackgroundColor:function(color){},setBorder:function(){},setOpacity:function(opacity){},setSize:function(contentSize){OpenLayers.Popup.Anchored.prototype.setSize.apply(this,arguments);this.updateBlocks();},updateRelativePosition:function(){this.padding=this.positionBlocks[this.relativePosition].padding;if(this.closeDiv){var contentDivPadding=this.getContentDivPadding();this.closeDiv.style.right=contentDivPadding.right+
+this.padding.right+"px";this.closeDiv.style.top=contentDivPadding.top+
+this.padding.top+"px";}
+this.updateBlocks();},calculateNewPx:function(px){var newPx=OpenLayers.Popup.Anchored.prototype.calculateNewPx.apply(this,arguments);newPx=newPx.offset(this.positionBlocks[this.relativePosition].offset);return newPx;},createBlocks:function(){this.blocks=[];var firstPosition=null;for(var key in this.positionBlocks){firstPosition=key;break;}
+var position=this.positionBlocks[firstPosition];for(var i=0;i<position.blocks.length;i++){var block={};this.blocks.push(block);var divId=this.id+'_FrameDecorationDiv_'+i;block.div=OpenLayers.Util.createDiv(divId,null,null,null,"absolute",null,"hidden",null);var imgId=this.id+'_FrameDecorationImg_'+i;var imageCreator=(this.isAlphaImage)?OpenLayers.Util.createAlphaImageDiv:OpenLayers.Util.createImage;block.image=imageCreator(imgId,null,this.imageSize,this.imageSrc,"absolute",null,null,null);block.div.appendChild(block.image);this.groupDiv.appendChild(block.div);}},updateBlocks:function(){if(!this.blocks){this.createBlocks();}
+if(this.size&&this.relativePosition){var position=this.positionBlocks[this.relativePosition];for(var i=0;i<position.blocks.length;i++){var positionBlock=position.blocks[i];var block=this.blocks[i];var l=positionBlock.anchor.left;var b=positionBlock.anchor.bottom;var r=positionBlock.anchor.right;var t=positionBlock.anchor.top;var w=(isNaN(positionBlock.size.w))?this.size.w-(r+l):positionBlock.size.w;var h=(isNaN(positionBlock.size.h))?this.size.h-(b+t):positionBlock.size.h;block.div.style.width=(w<0?0:w)+'px';block.div.style.height=(h<0?0:h)+'px';block.div.style.left=(l!=null)?l+'px':'';block.div.style.bottom=(b!=null)?b+'px':'';block.div.style.right=(r!=null)?r+'px':'';block.div.style.top=(t!=null)?t+'px':'';block.image.style.left=positionBlock.position.x+'px';block.image.style.top=positionBlock.position.y+'px';}
+this.contentDiv.style.left=this.padding.left+"px";this.contentDiv.style.top=this.padding.top+"px";}},CLASS_NAME:"OpenLayers.Popup.Framed"});OpenLayers.Projection=OpenLayers.Class({proj:null,projCode:null,initialize:function(projCode,options){OpenLayers.Util.extend(this,options);this.projCode=projCode;if(window.Proj4js){this.proj=new Proj4js.Proj(projCode);}},getCode:function(){return this.proj?this.proj.srsCode:this.projCode;},getUnits:function(){return this.proj?this.proj.units:null;},toString:function(){return this.getCode();},equals:function(projection){if(projection&&projection.getCode){return this.getCode()==projection.getCode();}else{return false;}},destroy:function(){delete this.proj;delete this.projCode;},CLASS_NAME:"OpenLayers.Projection"});OpenLayers.Projection.transforms={};OpenLayers.Projection.addTransform=function(from,to,method){if(!OpenLayers.Projection.transforms[from]){OpenLayers.Projection.transforms[from]={};}
+OpenLayers.Projection.transforms[from][to]=method;};OpenLayers.Projection.transform=function(point,source,dest){if(source.proj&&dest.proj){point=Proj4js.transform(source.proj,dest.proj,point);}else if(source&&dest&&OpenLayers.Projection.transforms[source.getCode()]&&OpenLayers.Projection.transforms[source.getCode()][dest.getCode()]){OpenLayers.Projection.transforms[source.getCode()][dest.getCode()](point);}
+return point;};OpenLayers.Protocol.WFS.v1=OpenLayers.Class(OpenLayers.Protocol,{version:null,srsName:"EPSG:4326",featureType:null,featureNS:null,geometryName:"the_geom",schema:null,featurePrefix:"feature",formatOptions:null,readFormat:null,initialize:function(options){OpenLayers.Protocol.prototype.initialize.apply(this,[options]);if(!options.format){this.format=OpenLayers.Format.WFST(OpenLayers.Util.extend({version:this.version,featureType:this.featureType,featureNS:this.featureNS,featurePrefix:this.featurePrefix,geometryName:this.geometryName,srsName:this.srsName,schema:this.schema},this.formatOptions));}
+if(!this.featureNS&&this.featurePrefix){var readNode=this.format.readNode;this.format.readNode=function(node,obj){if(!this.featureNS&&node.prefix==this.featurePrefix){this.featureNS=node.namespaceURI;this.setNamespace("feature",this.featureNS);}
+return readNode.apply(this,arguments);};}},destroy:function(){if(this.options&&!this.options.format){this.format.destroy();}
+this.format=null;OpenLayers.Protocol.prototype.destroy.apply(this);},read:function(options){OpenLayers.Protocol.prototype.read.apply(this,arguments);options=OpenLayers.Util.extend({},options);OpenLayers.Util.applyDefaults(options,this.options||{});var response=new OpenLayers.Protocol.Response({requestType:"read"});var data=OpenLayers.Format.XML.prototype.write.apply(this.format,[this.format.writeNode("wfs:GetFeature",options)]);response.priv=OpenLayers.Request.POST({url:options.url,callback:this.createCallback(this.handleRead,response,options),params:options.params,headers:options.headers,data:data});return response;},handleRead:function(response,options){if(options.callback){var request=response.priv;if(request.status>=200&&request.status<300){response.features=this.parseFeatures(request);response.code=OpenLayers.Protocol.Response.SUCCESS;}else{response.code=OpenLayers.Protocol.Response.FAILURE;}
+options.callback.call(options.scope,response);}},parseFeatures:function(request){var doc=request.responseXML;if(!doc||!doc.documentElement){doc=request.responseText;}
+if(!doc||doc.length<=0){return null;}
+return(this.readFormat!==null)?this.readFormat.read(doc):this.format.read(doc);},commit:function(features,options){options=OpenLayers.Util.extend({},options);OpenLayers.Util.applyDefaults(options,this.options);var response=new OpenLayers.Protocol.Response({requestType:"commit",reqFeatures:features});response.priv=OpenLayers.Request.POST({url:options.url,data:this.format.write(features,options),callback:this.createCallback(this.handleCommit,response,options)});return response;},handleCommit:function(response,options){if(options.callback){var request=response.priv;var data=request.responseXML;if(!data||!data.documentElement){data=request.responseText;}
+var obj=this.format.read(data)||{};response.insertIds=obj.insertIds||[];response.code=(obj.success)?OpenLayers.Protocol.Response.SUCCESS:OpenLayers.Protocol.Response.FAILURE;options.callback.call(options.scope,response);}},filterDelete:function(filter,options){options=OpenLayers.Util.extend({},options);OpenLayers.Util.applyDefaults(options,this.options);var response=new OpenLayers.Protocol.Response({requestType:"commit"});var root=this.format.createElementNSPlus("wfs:Transaction",{attributes:{service:"WFS",version:this.version}});var deleteNode=this.format.createElementNSPlus("wfs:Delete",{attributes:{typeName:(options.featureNS?this.featurePrefix+":":"")+
+options.featureType}});if(options.featureNS){deleteNode.setAttribute("xmlns:"+this.featurePrefix,options.featureNS);}
+var filterNode=this.format.writeNode("ogc:Filter",filter);deleteNode.appendChild(filterNode);root.appendChild(deleteNode);var data=OpenLayers.Format.XML.prototype.write.apply(this.format,[root]);return OpenLayers.Request.POST({url:this.url,callback:options.callback||function(){},data:data});},abort:function(response){if(response){response.priv.abort();}},CLASS_NAME:"OpenLayers.Protocol.WFS.v1"});OpenLayers.Renderer.SVG=OpenLayers.Class(OpenLayers.Renderer.Elements,{xmlns:"http://www.w3.org/2000/svg",xlinkns:"http://www.w3.org/1999/xlink",MAX_PIXEL:15000,translationParameters:null,symbolMetrics:null,isGecko:null,supportUse:null,initialize:function(containerID){if(!this.supported()){return;}
+OpenLayers.Renderer.Elements.prototype.initialize.apply(this,arguments);this.translationParameters={x:0,y:0};this.supportUse=(navigator.userAgent.toLowerCase().indexOf("applewebkit/5")==-1);this.isGecko=(navigator.userAgent.toLowerCase().indexOf("gecko/")!=-1);this.symbolMetrics={};},destroy:function(){OpenLayers.Renderer.Elements.prototype.destroy.apply(this,arguments);},supported:function(){var svgFeature="http://www.w3.org/TR/SVG11/feature#";return(document.implementation&&(document.implementation.hasFeature("org.w3c.svg","1.0")||document.implementation.hasFeature(svgFeature+"SVG","1.1")||document.implementation.hasFeature(svgFeature+"BasicStructure","1.1")));},inValidRange:function(x,y,xyOnly){var left=x+(xyOnly?0:this.translationParameters.x);var top=y+(xyOnly?0:this.translationParameters.y);return(left>=-this.MAX_PIXEL&&left<=this.MAX_PIXEL&&top>=-this.MAX_PIXEL&&top<=this.MAX_PIXEL);},setExtent:function(extent,resolutionChanged){OpenLayers.Renderer.Elements.prototype.setExtent.apply(this,arguments);var resolution=this.getResolution();var left=-extent.left/resolution;var top=extent.top/resolution;if(resolutionChanged){this.left=left;this.top=top;var extentString="0 0 "+this.size.w+" "+this.size.h;this.rendererRoot.setAttributeNS(null,"viewBox",extentString);this.translate(0,0);return true;}else{var inRange=this.translate(left-this.left,top-this.top);if(!inRange){this.setExtent(extent,true);}
+return inRange;}},translate:function(x,y){if(!this.inValidRange(x,y,true)){return false;}else{var transformString="";if(x||y){transformString="translate("+x+","+y+")";}
+this.root.setAttributeNS(null,"transform",transformString);this.translationParameters={x:x,y:y};return true;}},setSize:function(size){OpenLayers.Renderer.prototype.setSize.apply(this,arguments);this.rendererRoot.setAttributeNS(null,"width",this.size.w);this.rendererRoot.setAttributeNS(null,"height",this.size.h);},getNodeType:function(geometry,style){var nodeType=null;switch(geometry.CLASS_NAME){case"OpenLayers.Geometry.Point":if(style.externalGraphic){nodeType="image";}else if(this.isComplexSymbol(style.graphicName)){nodeType=this.supportUse===false?"svg":"use";}else{nodeType="circle";}
+break;case"OpenLayers.Geometry.Rectangle":nodeType="rect";break;case"OpenLayers.Geometry.LineString":nodeType="polyline";break;case"OpenLayers.Geometry.LinearRing":nodeType="polygon";break;case"OpenLayers.Geometry.Polygon":case"OpenLayers.Geometry.Curve":case"OpenLayers.Geometry.Surface":nodeType="path";break;default:break;}
+return nodeType;},setStyle:function(node,style,options){style=style||node._style;options=options||node._options;var r=parseFloat(node.getAttributeNS(null,"r"));var widthFactor=1;var pos;if(node._geometryClass=="OpenLayers.Geometry.Point"&&r){node.style.visibility="";if(style.graphic===false){node.style.visibility="hidden";}else if(style.externalGraphic){pos=this.getPosition(node);if(style.graphicTitle){node.setAttributeNS(null,"title",style.graphicTitle);}
+if(style.graphicWidth&&style.graphicHeight){node.setAttributeNS(null,"preserveAspectRatio","none");}
+var width=style.graphicWidth||style.graphicHeight;var height=style.graphicHeight||style.graphicWidth;width=width?width:style.pointRadius*2;height=height?height:style.pointRadius*2;var xOffset=(style.graphicXOffset!=undefined)?style.graphicXOffset:-(0.5*width);var yOffset=(style.graphicYOffset!=undefined)?style.graphicYOffset:-(0.5*height);var opacity=style.graphicOpacity||style.fillOpacity;node.setAttributeNS(null,"x",(pos.x+xOffset).toFixed());node.setAttributeNS(null,"y",(pos.y+yOffset).toFixed());node.setAttributeNS(null,"width",width);node.setAttributeNS(null,"height",height);node.setAttributeNS(this.xlinkns,"href",style.externalGraphic);node.setAttributeNS(null,"style","opacity: "+opacity);}else if(this.isComplexSymbol(style.graphicName)){var offset=style.pointRadius*3;var size=offset*2;var id=this.importSymbol(style.graphicName);pos=this.getPosition(node);widthFactor=this.symbolMetrics[id][0]*3/size;var parent=node.parentNode;var nextSibling=node.nextSibling;if(parent){parent.removeChild(node);}
+if(this.supportUse===false){var src=document.getElementById(id);node.firstChild&&node.removeChild(node.firstChild);node.appendChild(src.firstChild.cloneNode(true));node.setAttributeNS(null,"viewBox",src.getAttributeNS(null,"viewBox"));}else{node.setAttributeNS(this.xlinkns,"href","#"+id);}
+node.setAttributeNS(null,"width",size);node.setAttributeNS(null,"height",size);node.setAttributeNS(null,"x",pos.x-offset);node.setAttributeNS(null,"y",pos.y-offset);if(nextSibling){parent.insertBefore(node,nextSibling);}else if(parent){parent.appendChild(node);}}else{node.setAttributeNS(null,"r",style.pointRadius);}
+var rotation=style.rotation;if((rotation!==undefined||node._rotation!==undefined)&&pos){node._rotation=rotation;rotation|=0;if(node.nodeName!=="svg"){node.setAttributeNS(null,"transform","rotate("+rotation+" "+pos.x+" "+
+pos.y+")");}else{var metrics=this.symbolMetrics[id];node.firstChild.setAttributeNS(null,"transform","rotate("+style.rotation+" "+metrics[1]+" "+metrics[2]+")");}}}
+if(options.isFilled){node.setAttributeNS(null,"fill",style.fillColor);node.setAttributeNS(null,"fill-opacity",style.fillOpacity);}else{node.setAttributeNS(null,"fill","none");}
+if(options.isStroked){node.setAttributeNS(null,"stroke",style.strokeColor);node.setAttributeNS(null,"stroke-opacity",style.strokeOpacity);node.setAttributeNS(null,"stroke-width",style.strokeWidth*widthFactor);node.setAttributeNS(null,"stroke-linecap",style.strokeLinecap||"round");node.setAttributeNS(null,"stroke-linejoin","round");style.strokeDashstyle&&node.setAttributeNS(null,"stroke-dasharray",this.dashStyle(style,widthFactor));}else{node.setAttributeNS(null,"stroke","none");}
+if(style.pointerEvents){node.setAttributeNS(null,"pointer-events",style.pointerEvents);}
+if(style.cursor!=null){node.setAttributeNS(null,"cursor",style.cursor);}
+return node;},dashStyle:function(style,widthFactor){var w=style.strokeWidth*widthFactor;var str=style.strokeDashstyle;switch(str){case'solid':return'none';case'dot':return[1,4*w].join();case'dash':return[4*w,4*w].join();case'dashdot':return[4*w,4*w,1,4*w].join();case'longdash':return[8*w,4*w].join();case'longdashdot':return[8*w,4*w,1,4*w].join();default:return OpenLayers.String.trim(str).replace(/\s+/g,",");}},createNode:function(type,id){var node=document.createElementNS(this.xmlns,type);if(id){node.setAttributeNS(null,"id",id);}
+return node;},nodeTypeCompare:function(node,type){return(type==node.nodeName);},createRenderRoot:function(){return this.nodeFactory(this.container.id+"_svgRoot","svg");},createRoot:function(suffix){return this.nodeFactory(this.container.id+suffix,"g");},createDefs:function(){var defs=this.nodeFactory(this.container.id+"_defs","defs");this.rendererRoot.appendChild(defs);return defs;},drawPoint:function(node,geometry){return this.drawCircle(node,geometry,1);},drawCircle:function(node,geometry,radius){var resolution=this.getResolution();var x=(geometry.x/resolution+this.left);var y=(this.top-geometry.y/resolution);if(this.inValidRange(x,y)){node.setAttributeNS(null,"cx",x);node.setAttributeNS(null,"cy",y);node.setAttributeNS(null,"r",radius);return node;}else{return false;}},drawLineString:function(node,geometry){var componentsResult=this.getComponentsString(geometry.components);if(componentsResult.path){node.setAttributeNS(null,"points",componentsResult.path);return(componentsResult.complete?node:null);}else{return false;}},drawLinearRing:function(node,geometry){var componentsResult=this.getComponentsString(geometry.components);if(componentsResult.path){node.setAttributeNS(null,"points",componentsResult.path);return(componentsResult.complete?node:null);}else{return false;}},drawPolygon:function(node,geometry){var d="";var draw=true;var complete=true;var linearRingResult,path;for(var j=0,len=geometry.components.length;j<len;j++){d+=" M";linearRingResult=this.getComponentsString(geometry.components[j].components," ");path=linearRingResult.path;if(path){d+=" "+path;complete=linearRingResult.complete&&complete;}else{draw=false;}}
+d+=" z";if(draw){node.setAttributeNS(null,"d",d);node.setAttributeNS(null,"fill-rule","evenodd");return complete?node:null;}else{return false;}},drawRectangle:function(node,geometry){var resolution=this.getResolution();var x=(geometry.x/resolution+this.left);var y=(this.top-geometry.y/resolution);if(this.inValidRange(x,y)){node.setAttributeNS(null,"x",x);node.setAttributeNS(null,"y",y);node.setAttributeNS(null,"width",geometry.width/resolution);node.setAttributeNS(null,"height",geometry.height/resolution);return node;}else{return false;}},drawSurface:function(node,geometry){var d=null;var draw=true;for(var i=0,len=geometry.components.length;i<len;i++){if((i%3)==0&&(i/3)==0){var component=this.getShortString(geometry.components[i]);if(!component){draw=false;}
+d="M "+component;}else if((i%3)==1){var component=this.getShortString(geometry.components[i]);if(!component){draw=false;}
+d+=" C "+component;}else{var component=this.getShortString(geometry.components[i]);if(!component){draw=false;}
+d+=" "+component;}}
+d+=" Z";if(draw){node.setAttributeNS(null,"d",d);return node;}else{return false;}},drawText:function(featureId,style,location){var resolution=this.getResolution();var x=(location.x/resolution+this.left);var y=(location.y/resolution-this.top);var label=this.nodeFactory(featureId+this.LABEL_ID_SUFFIX,"text");var tspan=this.nodeFactory(featureId+this.LABEL_ID_SUFFIX+"_tspan","tspan");label.setAttributeNS(null,"x",x);label.setAttributeNS(null,"y",-y);if(style.fontColor){label.setAttributeNS(null,"fill",style.fontColor);}
+if(style.fontOpacity){label.setAttributeNS(null,"opacity",style.fontOpacity);}
+if(style.fontFamily){label.setAttributeNS(null,"font-family",style.fontFamily);}
+if(style.fontSize){label.setAttributeNS(null,"font-size",style.fontSize);}
+if(style.fontWeight){label.setAttributeNS(null,"font-weight",style.fontWeight);}
+if(style.labelSelect===true){label.setAttributeNS(null,"pointer-events","visible");label._featureId=featureId;tspan._featureId=featureId;tspan._geometry=location;tspan._geometryClass=location.CLASS_NAME;}else{label.setAttributeNS(null,"pointer-events","none");}
+var align=style.labelAlign||"cm";label.setAttributeNS(null,"text-anchor",OpenLayers.Renderer.SVG.LABEL_ALIGN[align[0]]||"middle");if(this.isGecko){label.setAttributeNS(null,"dominant-baseline",OpenLayers.Renderer.SVG.LABEL_ALIGN[align[1]]||"central");}else{tspan.setAttributeNS(null,"baseline-shift",OpenLayers.Renderer.SVG.LABEL_VSHIFT[align[1]]||"-35%");}
+tspan.textContent=style.label;if(!label.parentNode){label.appendChild(tspan);this.textRoot.appendChild(label);}},getComponentsString:function(components,separator){var renderCmp=[];var complete=true;var len=components.length;var strings=[];var str,component;for(var i=0;i<len;i++){component=components[i];renderCmp.push(component);str=this.getShortString(component);if(str){strings.push(str);}else{if(i>0){if(this.getShortString(components[i-1])){strings.push(this.clipLine(components[i],components[i-1]));}}
+if(i<len-1){if(this.getShortString(components[i+1])){strings.push(this.clipLine(components[i],components[i+1]));}}
+complete=false;}}
+return{path:strings.join(separator||","),complete:complete};},clipLine:function(badComponent,goodComponent){if(goodComponent.equals(badComponent)){return"";}
+var resolution=this.getResolution();var maxX=this.MAX_PIXEL-this.translationParameters.x;var maxY=this.MAX_PIXEL-this.translationParameters.y;var x1=goodComponent.x/resolution+this.left;var y1=this.top-goodComponent.y/resolution;var x2=badComponent.x/resolution+this.left;var y2=this.top-badComponent.y/resolution;var k;if(x2<-maxX||x2>maxX){k=(y2-y1)/(x2-x1);x2=x2<0?-maxX:maxX;y2=y1+(x2-x1)*k;}
+if(y2<-maxY||y2>maxY){k=(x2-x1)/(y2-y1);y2=y2<0?-maxY:maxY;x2=x1+(y2-y1)*k;}
+return x2+","+y2;},getShortString:function(point){var resolution=this.getResolution();var x=(point.x/resolution+this.left);var y=(this.top-point.y/resolution);if(this.inValidRange(x,y)){return x+","+y;}else{return false;}},getPosition:function(node){return({x:parseFloat(node.getAttributeNS(null,"cx")),y:parseFloat(node.getAttributeNS(null,"cy"))});},importSymbol:function(graphicName){if(!this.defs){this.defs=this.createDefs();}
+var id=this.container.id+"-"+graphicName;if(document.getElementById(id)!=null){return id;}
+var symbol=OpenLayers.Renderer.symbol[graphicName];if(!symbol){throw new Error(graphicName+' is not a valid symbol name');}
+var symbolNode=this.nodeFactory(id,"symbol");var node=this.nodeFactory(null,"polygon");symbolNode.appendChild(node);var symbolExtent=new OpenLayers.Bounds(Number.MAX_VALUE,Number.MAX_VALUE,0,0);var points=[];var x,y;for(var i=0;i<symbol.length;i=i+2){x=symbol[i];y=symbol[i+1];symbolExtent.left=Math.min(symbolExtent.left,x);symbolExtent.bottom=Math.min(symbolExtent.bottom,y);symbolExtent.right=Math.max(symbolExtent.right,x);symbolExtent.top=Math.max(symbolExtent.top,y);points.push(x,",",y);}
+node.setAttributeNS(null,"points",points.join(" "));var width=symbolExtent.getWidth();var height=symbolExtent.getHeight();var viewBox=[symbolExtent.left-width,symbolExtent.bottom-height,width*3,height*3];symbolNode.setAttributeNS(null,"viewBox",viewBox.join(" "));this.symbolMetrics[id]=[Math.max(width,height),symbolExtent.getCenterLonLat().lon,symbolExtent.getCenterLonLat().lat];this.defs.appendChild(symbolNode);return symbolNode.id;},getFeatureIdFromEvent:function(evt){var featureId=OpenLayers.Renderer.Elements.prototype.getFeatureIdFromEvent.apply(this,arguments);if(this.supportUse===false&&!featureId){var target=evt.target;featureId=target.parentNode&&target!=this.rendererRoot&&target.parentNode._featureId;}
+return featureId;},CLASS_NAME:"OpenLayers.Renderer.SVG"});OpenLayers.Renderer.SVG.LABEL_ALIGN={"l":"start","r":"end","b":"bottom","t":"hanging"};OpenLayers.Renderer.SVG.LABEL_VSHIFT={"t":"-70%","b":"0"};OpenLayers.Renderer.VML=OpenLayers.Class(OpenLayers.Renderer.Elements,{xmlns:"urn:schemas-microsoft-com:vml",symbolCache:{},offset:null,initialize:function(containerID){if(!this.supported()){return;}
+if(!document.namespaces.olv){document.namespaces.add("olv",this.xmlns);var style=document.createStyleSheet();var shapes=['shape','rect','oval','fill','stroke','imagedata','group','textbox'];for(var i=0,len=shapes.length;i<len;i++){style.addRule('olv\\:'+shapes[i],"behavior: url(#default#VML); "+"position: absolute; display: inline-block;");}}
+OpenLayers.Renderer.Elements.prototype.initialize.apply(this,arguments);},destroy:function(){OpenLayers.Renderer.Elements.prototype.destroy.apply(this,arguments);},supported:function(){return!!(document.namespaces);},setExtent:function(extent,resolutionChanged){OpenLayers.Renderer.Elements.prototype.setExtent.apply(this,arguments);var resolution=this.getResolution();var left=(extent.left/resolution)|0;var top=(extent.top/resolution-this.size.h)|0;if(resolutionChanged||!this.offset){this.offset={x:left,y:top};left=0;top=0;}else{left=left-this.offset.x;top=top-this.offset.y;}
+var org=left+" "+top;this.root.coordorigin=org;var roots=[this.root,this.vectorRoot,this.textRoot];var root;for(var i=0,len=roots.length;i<len;++i){root=roots[i];var size=this.size.w+" "+this.size.h;root.coordsize=size;}
+this.root.style.flip="y";return true;},setSize:function(size){OpenLayers.Renderer.prototype.setSize.apply(this,arguments);var roots=[this.rendererRoot,this.root,this.vectorRoot,this.textRoot];var w=this.size.w+"px";var h=this.size.h+"px";var root;for(var i=0,len=roots.length;i<len;++i){root=roots[i];root.style.width=w;root.style.height=h;}},getNodeType:function(geometry,style){var nodeType=null;switch(geometry.CLASS_NAME){case"OpenLayers.Geometry.Point":if(style.externalGraphic){nodeType="olv:rect";}else if(this.isComplexSymbol(style.graphicName)){nodeType="olv:shape";}else{nodeType="olv:oval";}
+break;case"OpenLayers.Geometry.Rectangle":nodeType="olv:rect";break;case"OpenLayers.Geometry.LineString":case"OpenLayers.Geometry.LinearRing":case"OpenLayers.Geometry.Polygon":case"OpenLayers.Geometry.Curve":case"OpenLayers.Geometry.Surface":nodeType="olv:shape";break;default:break;}
+return nodeType;},setStyle:function(node,style,options,geometry){style=style||node._style;options=options||node._options;var fillColor=style.fillColor;if(node._geometryClass==="OpenLayers.Geometry.Point"){if(style.externalGraphic){if(style.graphicTitle){node.title=style.graphicTitle;}
+var width=style.graphicWidth||style.graphicHeight;var height=style.graphicHeight||style.graphicWidth;width=width?width:style.pointRadius*2;height=height?height:style.pointRadius*2;var resolution=this.getResolution();var xOffset=(style.graphicXOffset!=undefined)?style.graphicXOffset:-(0.5*width);var yOffset=(style.graphicYOffset!=undefined)?style.graphicYOffset:-(0.5*height);node.style.left=(((geometry.x/resolution-this.offset.x)+xOffset)|0)+"px";node.style.top=(((geometry.y/resolution-this.offset.y)-(yOffset+height))|0)+"px";node.style.width=width+"px";node.style.height=height+"px";node.style.flip="y";fillColor="none";options.isStroked=false;}else if(this.isComplexSymbol(style.graphicName)){var cache=this.importSymbol(style.graphicName);node.path=cache.path;node.coordorigin=cache.left+","+cache.bottom;var size=cache.size;node.coordsize=size+","+size;this.drawCircle(node,geometry,style.pointRadius);node.style.flip="y";}else{this.drawCircle(node,geometry,style.pointRadius);}}
+if(options.isFilled){node.fillcolor=fillColor;}else{node.filled="false";}
+var fills=node.getElementsByTagName("fill");var fill=(fills.length==0)?null:fills[0];if(!options.isFilled){if(fill){node.removeChild(fill);}}else{if(!fill){fill=this.createNode('olv:fill',node.id+"_fill");}
+fill.opacity=style.fillOpacity;if(node._geometryClass==="OpenLayers.Geometry.Point"&&style.externalGraphic){if(style.graphicOpacity){fill.opacity=style.graphicOpacity;}
+fill.src=style.externalGraphic;fill.type="frame";if(!(style.graphicWidth&&style.graphicHeight)){fill.aspect="atmost";}}
+if(fill.parentNode!=node){node.appendChild(fill);}}
+var rotation=style.rotation;if((rotation!==undefined||node._rotation!==undefined)){node._rotation=rotation;if(style.externalGraphic){this.graphicRotate(node,xOffset,yOffset,style);fill.opacity=0;}else if(node._geometryClass==="OpenLayers.Geometry.Point"){node.style.rotation=rotation||0;}}
+var strokes=node.getElementsByTagName("stroke");var stroke=(strokes.length==0)?null:strokes[0];if(!options.isStroked){node.stroked=false;if(stroke){stroke.on=false;}}else{if(!stroke){stroke=this.createNode('olv:stroke',node.id+"_stroke");node.appendChild(stroke);}
+stroke.on=true;stroke.color=style.strokeColor;stroke.weight=style.strokeWidth+"px";stroke.opacity=style.strokeOpacity;stroke.endcap=style.strokeLinecap=='butt'?'flat':(style.strokeLinecap||'round');if(style.strokeDashstyle){stroke.dashstyle=this.dashStyle(style);}}
+if(style.cursor!="inherit"&&style.cursor!=null){node.style.cursor=style.cursor;}
+return node;},graphicRotate:function(node,xOffset,yOffset,style){var style=style||node._style;var rotation=style.rotation||0;var aspectRatio,size;if(!(style.graphicWidth&&style.graphicHeight)){var img=new Image();img.onreadystatechange=OpenLayers.Function.bind(function(){if(img.readyState=="complete"||img.readyState=="interactive"){aspectRatio=img.width/img.height;size=Math.max(style.pointRadius*2,style.graphicWidth||0,style.graphicHeight||0);xOffset=xOffset*aspectRatio;style.graphicWidth=size*aspectRatio;style.graphicHeight=size;this.graphicRotate(node,xOffset,yOffset,style);}},this);img.src=style.externalGraphic;return;}else{size=Math.max(style.graphicWidth,style.graphicHeight);aspectRatio=style.graphicWidth/style.graphicHeight;}
+var width=Math.round(style.graphicWidth||size*aspectRatio);var height=Math.round(style.graphicHeight||size);node.style.width=width+"px";node.style.height=height+"px";var image=document.getElementById(node.id+"_image");if(!image){image=this.createNode("olv:imagedata",node.id+"_image");node.appendChild(image);}
+image.style.width=width+"px";image.style.height=height+"px";image.src=style.externalGraphic;image.style.filter="progid:DXImageTransform.Microsoft.AlphaImageLoader("+"src='', sizingMethod='scale')";var rot=rotation*Math.PI/180;var sintheta=Math.sin(rot);var costheta=Math.cos(rot);var filter="progid:DXImageTransform.Microsoft.Matrix(M11="+costheta+",M12="+(-sintheta)+",M21="+sintheta+",M22="+costheta+",SizingMethod='auto expand')\n";var opacity=style.graphicOpacity||style.fillOpacity;if(opacity&&opacity!=1){filter+="progid:DXImageTransform.Microsoft.BasicImage(opacity="+
+opacity+")\n";}
+node.style.filter=filter;var centerPoint=new OpenLayers.Geometry.Point(-xOffset,-yOffset);var imgBox=new OpenLayers.Bounds(0,0,width,height).toGeometry();imgBox.rotate(style.rotation,centerPoint);var imgBounds=imgBox.getBounds();node.style.left=Math.round(parseInt(node.style.left)+imgBounds.left)+"px";node.style.top=Math.round(parseInt(node.style.top)-imgBounds.bottom)+"px";},postDraw:function(node){node.style.visibility="visible";var fillColor=node._style.fillColor;var strokeColor=node._style.strokeColor;if(fillColor=="none"&&node.fillcolor!=fillColor){node.fillcolor=fillColor;}
+if(strokeColor=="none"&&node.strokecolor!=strokeColor){node.strokecolor=strokeColor;}},setNodeDimension:function(node,geometry){var bbox=geometry.getBounds();if(bbox){var resolution=this.getResolution();var scaledBox=new OpenLayers.Bounds((bbox.left/resolution-this.offset.x)|0,(bbox.bottom/resolution-this.offset.y)|0,(bbox.right/resolution-this.offset.x)|0,(bbox.top/resolution-this.offset.y)|0);node.style.left=scaledBox.left+"px";node.style.top=scaledBox.top+"px";node.style.width=scaledBox.getWidth()+"px";node.style.height=scaledBox.getHeight()+"px";node.coordorigin=scaledBox.left+" "+scaledBox.top;node.coordsize=scaledBox.getWidth()+" "+scaledBox.getHeight();}},dashStyle:function(style){var dash=style.strokeDashstyle;switch(dash){case'solid':case'dot':case'dash':case'dashdot':case'longdash':case'longdashdot':return dash;default:var parts=dash.split(/[ ,]/);if(parts.length==2){if(1*parts[0]>=2*parts[1]){return"longdash";}
+return(parts[0]==1||parts[1]==1)?"dot":"dash";}else if(parts.length==4){return(1*parts[0]>=2*parts[1])?"longdashdot":"dashdot";}
+return"solid";}},createNode:function(type,id){var node=document.createElement(type);if(id){node.id=id;}
+node.unselectable='on';node.onselectstart=OpenLayers.Function.False;return node;},nodeTypeCompare:function(node,type){var subType=type;var splitIndex=subType.indexOf(":");if(splitIndex!=-1){subType=subType.substr(splitIndex+1);}
+var nodeName=node.nodeName;splitIndex=nodeName.indexOf(":");if(splitIndex!=-1){nodeName=nodeName.substr(splitIndex+1);}
+return(subType==nodeName);},createRenderRoot:function(){return this.nodeFactory(this.container.id+"_vmlRoot","div");},createRoot:function(suffix){return this.nodeFactory(this.container.id+suffix,"olv:group");},drawPoint:function(node,geometry){return this.drawCircle(node,geometry,1);},drawCircle:function(node,geometry,radius){if(!isNaN(geometry.x)&&!isNaN(geometry.y)){var resolution=this.getResolution();node.style.left=(((geometry.x/resolution-this.offset.x)|0)-radius)+"px";node.style.top=(((geometry.y/resolution-this.offset.y)|0)-radius)+"px";var diameter=radius*2;node.style.width=diameter+"px";node.style.height=diameter+"px";return node;}
+return false;},drawLineString:function(node,geometry){return this.drawLine(node,geometry,false);},drawLinearRing:function(node,geometry){return this.drawLine(node,geometry,true);},drawLine:function(node,geometry,closeLine){this.setNodeDimension(node,geometry);var resolution=this.getResolution();var numComponents=geometry.components.length;var parts=new Array(numComponents);var comp,x,y;for(var i=0;i<numComponents;i++){comp=geometry.components[i];x=(comp.x/resolution-this.offset.x)|0;y=(comp.y/resolution-this.offset.y)|0;parts[i]=" "+x+","+y+" l ";}
+var end=(closeLine)?" x e":" e";node.path="m"+parts.join("")+end;return node;},drawPolygon:function(node,geometry){this.setNodeDimension(node,geometry);var resolution=this.getResolution();var path=[];var linearRing,i,j,len,ilen,comp,x,y;for(j=0,len=geometry.components.length;j<len;j++){linearRing=geometry.components[j];path.push("m");for(i=0,ilen=linearRing.components.length;i<ilen;i++){comp=linearRing.components[i];x=(comp.x/resolution-this.offset.x)|0;y=(comp.y/resolution-this.offset.y)|0;path.push(" "+x+","+y);if(i==0){path.push(" l");}}
+path.push(" x ");}
+path.push("e");node.path=path.join("");return node;},drawRectangle:function(node,geometry){var resolution=this.getResolution();node.style.left=((geometry.x/resolution-this.offset.x)|0)+"px";node.style.top=((geometry.y/resolution-this.offset.y)|0)+"px";node.style.width=((geometry.width/resolution)|0)+"px";node.style.height=((geometry.height/resolution)|0)+"px";return node;},drawText:function(featureId,style,location){var label=this.nodeFactory(featureId+this.LABEL_ID_SUFFIX,"olv:rect");var textbox=this.nodeFactory(featureId+this.LABEL_ID_SUFFIX+"_textbox","olv:textbox");var resolution=this.getResolution();label.style.left=((location.x/resolution-this.offset.x)|0)+"px";label.style.top=((location.y/resolution-this.offset.y)|0)+"px";label.style.flip="y";textbox.innerText=style.label;if(style.fontColor){textbox.style.color=style.fontColor;}
+if(style.fontOpacity){textbox.style.filter='alpha(opacity='+(style.fontOpacity*100)+')';}
+if(style.fontFamily){textbox.style.fontFamily=style.fontFamily;}
+if(style.fontSize){textbox.style.fontSize=style.fontSize;}
+if(style.fontWeight){textbox.style.fontWeight=style.fontWeight;}
+if(style.labelSelect===true){label._featureId=featureId;textbox._featureId=featureId;textbox._geometry=location;textbox._geometryClass=location.CLASS_NAME;}
+textbox.style.whiteSpace="nowrap";textbox.inset="1px,0px,0px,0px";if(!label.parentNode){label.appendChild(textbox);this.textRoot.appendChild(label);}
+var align=style.labelAlign||"cm";if(align.length==1){align+="m";}
+var xshift=textbox.clientWidth*(OpenLayers.Renderer.VML.LABEL_SHIFT[align.substr(0,1)]);var yshift=textbox.clientHeight*(OpenLayers.Renderer.VML.LABEL_SHIFT[align.substr(1,1)]);label.style.left=parseInt(label.style.left)-xshift-1+"px";label.style.top=parseInt(label.style.top)+yshift+"px";},drawSurface:function(node,geometry){this.setNodeDimension(node,geometry);var resolution=this.getResolution();var path=[];var comp,x,y;for(var i=0,len=geometry.components.length;i<len;i++){comp=geometry.components[i];x=(comp.x/resolution-this.offset.x)|0;y=(comp.y/resolution-this.offset.y)|0;if((i%3)==0&&(i/3)==0){path.push("m");}else if((i%3)==1){path.push(" c");}
+path.push(" "+x+","+y);}
+path.push(" x e");node.path=path.join("");return node;},moveRoot:function(renderer){var layer=this.map.getLayer(renderer.container.id);if(layer instanceof OpenLayers.Layer.Vector.RootContainer){layer=this.map.getLayer(this.container.id);}
+layer&&layer.renderer.clear();OpenLayers.Renderer.Elements.prototype.moveRoot.apply(this,arguments);layer&&layer.redraw();},importSymbol:function(graphicName){var id=this.container.id+"-"+graphicName;var cache=this.symbolCache[id];if(cache){return cache;}
+var symbol=OpenLayers.Renderer.symbol[graphicName];if(!symbol){throw new Error(graphicName+' is not a valid symbol name');}
+var symbolExtent=new OpenLayers.Bounds(Number.MAX_VALUE,Number.MAX_VALUE,0,0);var pathitems=["m"];for(var i=0;i<symbol.length;i=i+2){var x=symbol[i];var y=symbol[i+1];symbolExtent.left=Math.min(symbolExtent.left,x);symbolExtent.bottom=Math.min(symbolExtent.bottom,y);symbolExtent.right=Math.max(symbolExtent.right,x);symbolExtent.top=Math.max(symbolExtent.top,y);pathitems.push(x);pathitems.push(y);if(i==0){pathitems.push("l");}}
+pathitems.push("x e");var path=pathitems.join(" ");var diff=(symbolExtent.getWidth()-symbolExtent.getHeight())/2;if(diff>0){symbolExtent.bottom=symbolExtent.bottom-diff;symbolExtent.top=symbolExtent.top+diff;}else{symbolExtent.left=symbolExtent.left+diff;symbolExtent.right=symbolExtent.right-diff;}
+cache={path:path,size:symbolExtent.getWidth(),left:symbolExtent.left,bottom:symbolExtent.bottom};this.symbolCache[id]=cache;return cache;},CLASS_NAME:"OpenLayers.Renderer.VML"});OpenLayers.Renderer.VML.LABEL_SHIFT={"l":0,"c":.5,"r":1,"t":0,"m":.5,"b":1};OpenLayers.Tile=OpenLayers.Class({EVENT_TYPES:["loadstart","loadend","reload","unload"],events:null,id:null,layer:null,url:null,bounds:null,size:null,position:null,isLoading:false,initialize:function(layer,position,bounds,url,size){this.layer=layer;this.position=position.clone();this.bounds=bounds.clone();this.url=url;this.size=size.clone();this.id=OpenLayers.Util.createUniqueID("Tile_");this.events=new OpenLayers.Events(this,null,this.EVENT_TYPES);},unload:function(){if(this.isLoading){this.isLoading=false;this.events.triggerEvent("unload");}},destroy:function(){this.layer=null;this.bounds=null;this.size=null;this.position=null;this.events.destroy();this.events=null;},clone:function(obj){if(obj==null){obj=new OpenLayers.Tile(this.layer,this.position,this.bounds,this.url,this.size);}
+OpenLayers.Util.applyDefaults(obj,this);return obj;},draw:function(){var maxExtent=this.layer.maxExtent;var withinMaxExtent=(maxExtent&&this.bounds.intersectsBounds(maxExtent,false));this.shouldDraw=(withinMaxExtent||this.layer.displayOutsideMaxExtent);this.clear();return this.shouldDraw;},moveTo:function(bounds,position,redraw){if(redraw==null){redraw=true;}
+this.bounds=bounds.clone();this.position=position.clone();if(redraw){this.draw();}},clear:function(){},getBoundsFromBaseLayer:function(position){var msg=OpenLayers.i18n('reprojectDeprecated',{'layerName':this.layer.name});OpenLayers.Console.warn(msg);var topLeft=this.layer.map.getLonLatFromLayerPx(position);var bottomRightPx=position.clone();bottomRightPx.x+=this.size.w;bottomRightPx.y+=this.size.h;var bottomRight=this.layer.map.getLonLatFromLayerPx(bottomRightPx);if(topLeft.lon>bottomRight.lon){if(topLeft.lon<0){topLeft.lon=-180-(topLeft.lon+180);}else{bottomRight.lon=180+bottomRight.lon+180;}}
+var bounds=new OpenLayers.Bounds(topLeft.lon,bottomRight.lat,bottomRight.lon,topLeft.lat);return bounds;},showTile:function(){if(this.shouldDraw){this.show();}},show:function(){},hide:function(){},CLASS_NAME:"OpenLayers.Tile"});OpenLayers.Control.MouseToolbar=OpenLayers.Class(OpenLayers.Control.MouseDefaults,{mode:null,buttons:null,direction:"vertical",buttonClicked:null,initialize:function(position,direction){OpenLayers.Control.prototype.initialize.apply(this,arguments);this.position=new OpenLayers.Pixel(OpenLayers.Control.MouseToolbar.X,OpenLayers.Control.MouseToolbar.Y);if(position){this.position=position;}
+if(direction){this.direction=direction;}
+this.measureDivs=[];},destroy:function(){for(var btnId in this.buttons){var btn=this.buttons[btnId];btn.map=null;btn.events.destroy();}
+OpenLayers.Control.MouseDefaults.prototype.destroy.apply(this,arguments);},draw:function(){OpenLayers.Control.prototype.draw.apply(this,arguments);OpenLayers.Control.MouseDefaults.prototype.draw.apply(this,arguments);this.buttons={};var sz=new OpenLayers.Size(28,28);var centered=new OpenLayers.Pixel(OpenLayers.Control.MouseToolbar.X,0);this._addButton("zoombox","drag-rectangle-off.png","drag-rectangle-on.png",centered,sz,"Shift->Drag to zoom to area");centered=centered.add((this.direction=="vertical"?0:sz.w),(this.direction=="vertical"?sz.h:0));this._addButton("pan","panning-hand-off.png","panning-hand-on.png",centered,sz,"Drag the map to pan.");centered=centered.add((this.direction=="vertical"?0:sz.w),(this.direction=="vertical"?sz.h:0));this.switchModeTo("pan");return this.div;},_addButton:function(id,img,activeImg,xy,sz,title){var imgLocation=OpenLayers.Util.getImagesLocation()+img;var activeImgLocation=OpenLayers.Util.getImagesLocation()+activeImg;var btn=OpenLayers.Util.createAlphaImageDiv("OpenLayers_Control_MouseToolbar_"+id,xy,sz,imgLocation,"absolute");this.div.appendChild(btn);btn.imgLocation=imgLocation;btn.activeImgLocation=activeImgLocation;btn.events=new OpenLayers.Events(this,btn,null,true);btn.events.on({"mousedown":this.buttonDown,"mouseup":this.buttonUp,"dblclick":OpenLayers.Event.stop,scope:this});btn.action=id;btn.title=title;btn.alt=title;btn.map=this.map;this.buttons[id]=btn;return btn;},buttonDown:function(evt){if(!OpenLayers.Event.isLeftClick(evt)){return;}
+this.buttonClicked=evt.element.action;OpenLayers.Event.stop(evt);},buttonUp:function(evt){if(!OpenLayers.Event.isLeftClick(evt)){return;}
+if(this.buttonClicked!=null){if(this.buttonClicked==evt.element.action){this.switchModeTo(evt.element.action);}
+OpenLayers.Event.stop(evt);this.buttonClicked=null;}},defaultDblClick:function(evt){this.switchModeTo("pan");this.performedDrag=false;var newCenter=this.map.getLonLatFromViewPortPx(evt.xy);this.map.setCenter(newCenter,this.map.zoom+1);OpenLayers.Event.stop(evt);return false;},defaultMouseDown:function(evt){if(!OpenLayers.Event.isLeftClick(evt)){return;}
+this.mouseDragStart=evt.xy.clone();this.performedDrag=false;this.startViaKeyboard=false;if(evt.shiftKey&&this.mode!="zoombox"){this.switchModeTo("zoombox");this.startViaKeyboard=true;}else if(evt.altKey&&this.mode!="measure"){this.switchModeTo("measure");}else if(!this.mode){this.switchModeTo("pan");}
+switch(this.mode){case"zoombox":this.map.div.style.cursor="crosshair";this.zoomBox=OpenLayers.Util.createDiv('zoomBox',this.mouseDragStart,null,null,"absolute","2px solid red");this.zoomBox.style.backgroundColor="white";this.zoomBox.style.filter="alpha(opacity=50)";this.zoomBox.style.opacity="0.50";this.zoomBox.style.fontSize="1px";this.zoomBox.style.zIndex=this.map.Z_INDEX_BASE["Popup"]-1;this.map.viewPortDiv.appendChild(this.zoomBox);this.performedDrag=true;break;case"measure":var distance="";if(this.measureStart){var measureEnd=this.map.getLonLatFromViewPortPx(this.mouseDragStart);distance=OpenLayers.Util.distVincenty(this.measureStart,measureEnd);distance=Math.round(distance*100)/100;distance=distance+"km";this.measureStartBox=this.measureBox;}
+this.measureStart=this.map.getLonLatFromViewPortPx(this.mouseDragStart);;this.measureBox=OpenLayers.Util.createDiv(null,this.mouseDragStart.add(-2-parseInt(this.map.layerContainerDiv.style.left),-2-parseInt(this.map.layerContainerDiv.style.top)),null,null,"absolute");this.measureBox.style.width="4px";this.measureBox.style.height="4px";this.measureBox.style.fontSize="1px";this.measureBox.style.backgroundColor="red";this.measureBox.style.zIndex=this.map.Z_INDEX_BASE["Popup"]-1;this.map.layerContainerDiv.appendChild(this.measureBox);if(distance){this.measureBoxDistance=OpenLayers.Util.createDiv(null,this.mouseDragStart.add(-2-parseInt(this.map.layerContainerDiv.style.left),2-parseInt(this.map.layerContainerDiv.style.top)),null,null,"absolute");this.measureBoxDistance.innerHTML=distance;this.measureBoxDistance.style.zIndex=this.map.Z_INDEX_BASE["Popup"]-1;this.map.layerContainerDiv.appendChild(this.measureBoxDistance);this.measureDivs.push(this.measureBoxDistance);}
+this.measureBox.style.zIndex=this.map.Z_INDEX_BASE["Popup"]-1;this.map.layerContainerDiv.appendChild(this.measureBox);this.measureDivs.push(this.measureBox);break;default:this.map.div.style.cursor="move";break;}
+document.onselectstart=OpenLayers.Function.False;OpenLayers.Event.stop(evt);},switchModeTo:function(mode){if(mode!=this.mode){if(this.mode&&this.buttons[this.mode]){OpenLayers.Util.modifyAlphaImageDiv(this.buttons[this.mode],null,null,null,this.buttons[this.mode].imgLocation);}
+if(this.mode=="measure"&&mode!="measure"){for(var i=0,len=this.measureDivs.length;i<len;i++){if(this.measureDivs[i]){this.map.layerContainerDiv.removeChild(this.measureDivs[i]);}}
+this.measureDivs=[];this.measureStart=null;}
+this.mode=mode;if(this.buttons[mode]){OpenLayers.Util.modifyAlphaImageDiv(this.buttons[mode],null,null,null,this.buttons[mode].activeImgLocation);}
+switch(this.mode){case"zoombox":this.map.div.style.cursor="crosshair";break;default:this.map.div.style.cursor="";break;}}},leaveMode:function(){this.switchModeTo("pan");},defaultMouseMove:function(evt){if(this.mouseDragStart!=null){switch(this.mode){case"zoombox":var deltaX=Math.abs(this.mouseDragStart.x-evt.xy.x);var deltaY=Math.abs(this.mouseDragStart.y-evt.xy.y);this.zoomBox.style.width=Math.max(1,deltaX)+"px";this.zoomBox.style.height=Math.max(1,deltaY)+"px";if(evt.xy.x<this.mouseDragStart.x){this.zoomBox.style.left=evt.xy.x+"px";}
+if(evt.xy.y<this.mouseDragStart.y){this.zoomBox.style.top=evt.xy.y+"px";}
+break;default:var deltaX=this.mouseDragStart.x-evt.xy.x;var deltaY=this.mouseDragStart.y-evt.xy.y;var size=this.map.getSize();var newXY=new OpenLayers.Pixel(size.w/2+deltaX,size.h/2+deltaY);var newCenter=this.map.getLonLatFromViewPortPx(newXY);this.map.setCenter(newCenter,null,true);this.mouseDragStart=evt.xy.clone();}
+this.performedDrag=true;}},defaultMouseUp:function(evt){if(!OpenLayers.Event.isLeftClick(evt)){return;}
+switch(this.mode){case"zoombox":this.zoomBoxEnd(evt);if(this.startViaKeyboard){this.leaveMode();}
+break;case"pan":if(this.performedDrag){this.map.setCenter(this.map.center);}}
+document.onselectstart=null;this.mouseDragStart=null;this.map.div.style.cursor="default";},defaultMouseOut:function(evt){if(this.mouseDragStart!=null&&OpenLayers.Util.mouseLeft(evt,this.map.div)){if(this.zoomBox){this.removeZoomBox();if(this.startViaKeyboard){this.leaveMode();}}
+this.mouseDragStart=null;this.map.div.style.cursor="default";}},defaultClick:function(evt){if(this.performedDrag){this.performedDrag=false;return false;}},CLASS_NAME:"OpenLayers.Control.MouseToolbar"});OpenLayers.Control.MouseToolbar.X=6;OpenLayers.Control.MouseToolbar.Y=300;OpenLayers.Control.NavigationHistory=OpenLayers.Class(OpenLayers.Control,{type:OpenLayers.Control.TYPE_TOGGLE,previous:null,previousOptions:null,next:null,nextOptions:null,limit:50,autoActivate:true,clearOnDeactivate:false,registry:null,nextStack:null,previousStack:null,listeners:null,restoring:false,initialize:function(options){OpenLayers.Control.prototype.initialize.apply(this,[options]);this.registry=OpenLayers.Util.extend({"moveend":this.getState},this.registry);var previousOptions={trigger:OpenLayers.Function.bind(this.previousTrigger,this),displayClass:this.displayClass+" "+this.displayClass+"Previous"};OpenLayers.Util.extend(previousOptions,this.previousOptions);this.previous=new OpenLayers.Control.Button(previousOptions);var nextOptions={trigger:OpenLayers.Function.bind(this.nextTrigger,this),displayClass:this.displayClass+" "+this.displayClass+"Next"};OpenLayers.Util.extend(nextOptions,this.nextOptions);this.next=new OpenLayers.Control.Button(nextOptions);this.clear();},onPreviousChange:function(state,length){if(state&&!this.previous.active){this.previous.activate();}else if(!state&&this.previous.active){this.previous.deactivate();}},onNextChange:function(state,length){if(state&&!this.next.active){this.next.activate();}else if(!state&&this.next.active){this.next.deactivate();}},destroy:function(){OpenLayers.Control.prototype.destroy.apply(this);this.previous.destroy();this.next.destroy();this.deactivate();for(var prop in this){this[prop]=null;}},setMap:function(map){this.map=map;this.next.setMap(map);this.previous.setMap(map);},draw:function(){OpenLayers.Control.prototype.draw.apply(this,arguments);this.next.draw();this.previous.draw();},previousTrigger:function(){var current=this.previousStack.shift();var state=this.previousStack.shift();if(state!=undefined){this.nextStack.unshift(current);this.previousStack.unshift(state);this.restoring=true;this.restore(state);this.restoring=false;this.onNextChange(this.nextStack[0],this.nextStack.length);this.onPreviousChange(this.previousStack[1],this.previousStack.length-1);}else{this.previousStack.unshift(current);}
+return state;},nextTrigger:function(){var state=this.nextStack.shift();if(state!=undefined){this.previousStack.unshift(state);this.restoring=true;this.restore(state);this.restoring=false;this.onNextChange(this.nextStack[0],this.nextStack.length);this.onPreviousChange(this.previousStack[1],this.previousStack.length-1);}
+return state;},clear:function(){this.previousStack=[];this.previous.deactivate();this.nextStack=[];this.next.deactivate();},getState:function(){return{center:this.map.getCenter(),resolution:this.map.getResolution(),projection:this.map.getProjectionObject(),units:this.map.getProjectionObject().getUnits()||this.map.units||this.map.baseLayer.units};},restore:function(state){var center,zoom;if(this.map.getProjectionObject()==state.projection){zoom=this.map.getZoomForResolution(state.resolution);center=state.center;}else{center=state.center.clone();center.transform(state.projection,this.map.getProjectionObject());var sourceUnits=state.units;var targetUnits=this.map.getProjectionObject().getUnits()||this.map.units||this.map.baseLayer.units;var resolutionFactor=sourceUnits&&targetUnits?OpenLayers.INCHES_PER_UNIT[sourceUnits]/OpenLayers.INCHES_PER_UNIT[targetUnits]:1;zoom=this.map.getZoomForResolution(resolutionFactor*state.resolution);}
+this.map.setCenter(center,zoom);},setListeners:function(){this.listeners={};for(var type in this.registry){this.listeners[type]=OpenLayers.Function.bind(function(){if(!this.restoring){var state=this.registry[type].apply(this,arguments);this.previousStack.unshift(state);if(this.previousStack.length>1){this.onPreviousChange(this.previousStack[1],this.previousStack.length-1);}
+if(this.previousStack.length>(this.limit+1)){this.previousStack.pop();}
+if(this.nextStack.length>0){this.nextStack=[];this.onNextChange(null,0);}}
+return true;},this);}},activate:function(){var activated=false;if(this.map){if(OpenLayers.Control.prototype.activate.apply(this)){if(this.listeners==null){this.setListeners();}
+for(var type in this.listeners){this.map.events.register(type,this,this.listeners[type]);}
+activated=true;if(this.previousStack.length==0){this.initStack();}}}
+return activated;},initStack:function(){if(this.map.getCenter()){this.listeners.moveend();}},deactivate:function(){var deactivated=false;if(this.map){if(OpenLayers.Control.prototype.deactivate.apply(this)){for(var type in this.listeners){this.map.events.unregister(type,this,this.listeners[type]);}
+if(this.clearOnDeactivate){this.clear();}
+deactivated=true;}}
+return deactivated;},CLASS_NAME:"OpenLayers.Control.NavigationHistory"});OpenLayers.Control.PanPanel=OpenLayers.Class(OpenLayers.Control.Panel,{slideFactor:50,initialize:function(options){OpenLayers.Control.Panel.prototype.initialize.apply(this,[options]);this.addControls([new OpenLayers.Control.Pan(OpenLayers.Control.Pan.NORTH,{slideFactor:this.slideFactor}),new OpenLayers.Control.Pan(OpenLayers.Control.Pan.SOUTH,{slideFactor:this.slideFactor}),new OpenLayers.Control.Pan(OpenLayers.Control.Pan.EAST,{slideFactor:this.slideFactor}),new OpenLayers.Control.Pan(OpenLayers.Control.Pan.WEST,{slideFactor:this.slideFactor})]);},CLASS_NAME:"OpenLayers.Control.PanPanel"});OpenLayers.Control.PanZoomBar=OpenLayers.Class(OpenLayers.Control.PanZoom,{zoomStopWidth:18,zoomStopHeight:11,slider:null,sliderEvents:null,zoombarDiv:null,divEvents:null,zoomWorldIcon:false,forceFixedZoomLevel:false,mouseDragStart:null,zoomStart:null,initialize:function(){OpenLayers.Control.PanZoom.prototype.initialize.apply(this,arguments);},destroy:function(){this._removeZoomBar();this.map.events.un({"changebaselayer":this.redraw,scope:this});OpenLayers.Control.PanZoom.prototype.destroy.apply(this,arguments);delete this.mouseDragStart;delete this.zoomStart;},setMap:function(map){OpenLayers.Control.PanZoom.prototype.setMap.apply(this,arguments);this.map.events.register("changebaselayer",this,this.redraw);},redraw:function(){if(this.div!=null){this.removeButtons();this._removeZoomBar();}
+this.draw();},draw:function(px){OpenLayers.Control.prototype.draw.apply(this,arguments);px=this.position.clone();this.buttons=[];var sz=new OpenLayers.Size(18,18);var centered=new OpenLayers.Pixel(px.x+sz.w/2,px.y);var wposition=sz.w;if(this.zoomWorldIcon){centered=new OpenLayers.Pixel(px.x+sz.w,px.y);}
+this._addButton("panup","north-mini.png",centered,sz);px.y=centered.y+sz.h;this._addButton("panleft","west-mini.png",px,sz);if(this.zoomWorldIcon){this._addButton("zoomworld","zoom-world-mini.png",px.add(sz.w,0),sz);wposition*=2;}
+this._addButton("panright","east-mini.png",px.add(wposition,0),sz);this._addButton("pandown","south-mini.png",centered.add(0,sz.h*2),sz);this._addButton("zoomin","zoom-plus-mini.png",centered.add(0,sz.h*3+5),sz);centered=this._addZoomBar(centered.add(0,sz.h*4+5));this._addButton("zoomout","zoom-minus-mini.png",centered,sz);return this.div;},_addZoomBar:function(centered){var imgLocation=OpenLayers.Util.getImagesLocation();var id=this.id+"_"+this.map.id;var zoomsToEnd=this.map.getNumZoomLevels()-1-this.map.getZoom();var slider=OpenLayers.Util.createAlphaImageDiv(id,centered.add(-1,zoomsToEnd*this.zoomStopHeight),new OpenLayers.Size(20,9),imgLocation+"slider.png","absolute");this.slider=slider;this.sliderEvents=new OpenLayers.Events(this,slider,null,true,{includeXY:true});this.sliderEvents.on({"mousedown":this.zoomBarDown,"mousemove":this.zoomBarDrag,"mouseup":this.zoomBarUp,"dblclick":this.doubleClick,"click":this.doubleClick});var sz=new OpenLayers.Size();sz.h=this.zoomStopHeight*this.map.getNumZoomLevels();sz.w=this.zoomStopWidth;var div=null;if(OpenLayers.Util.alphaHack()){var id=this.id+"_"+this.map.id;div=OpenLayers.Util.createAlphaImageDiv(id,centered,new OpenLayers.Size(sz.w,this.zoomStopHeight),imgLocation+"zoombar.png","absolute",null,"crop");div.style.height=sz.h+"px";}else{div=OpenLayers.Util.createDiv('OpenLayers_Control_PanZoomBar_Zoombar'+this.map.id,centered,sz,imgLocation+"zoombar.png");}
+this.zoombarDiv=div;this.divEvents=new OpenLayers.Events(this,div,null,true,{includeXY:true});this.divEvents.on({"mousedown":this.divClick,"mousemove":this.passEventToSlider,"dblclick":this.doubleClick,"click":this.doubleClick});this.div.appendChild(div);this.startTop=parseInt(div.style.top);this.div.appendChild(slider);this.map.events.register("zoomend",this,this.moveZoomBar);centered=centered.add(0,this.zoomStopHeight*this.map.getNumZoomLevels());return centered;},_removeZoomBar:function(){this.sliderEvents.un({"mousedown":this.zoomBarDown,"mousemove":this.zoomBarDrag,"mouseup":this.zoomBarUp,"dblclick":this.doubleClick,"click":this.doubleClick});this.sliderEvents.destroy();this.divEvents.un({"mousedown":this.divClick,"mousemove":this.passEventToSlider,"dblclick":this.doubleClick,"click":this.doubleClick});this.divEvents.destroy();this.div.removeChild(this.zoombarDiv);this.zoombarDiv=null;this.div.removeChild(this.slider);this.slider=null;this.map.events.unregister("zoomend",this,this.moveZoomBar);},passEventToSlider:function(evt){this.sliderEvents.handleBrowserEvent(evt);},divClick:function(evt){if(!OpenLayers.Event.isLeftClick(evt)){return;}
+var y=evt.xy.y;var top=OpenLayers.Util.pagePosition(evt.object)[1];var levels=(y-top)/this.zoomStopHeight;if(this.forceFixedZoomLevel||!this.map.fractionalZoom){levels=Math.floor(levels);}
+var zoom=(this.map.getNumZoomLevels()-1)-levels;zoom=Math.min(Math.max(zoom,0),this.map.getNumZoomLevels()-1);this.map.zoomTo(zoom);OpenLayers.Event.stop(evt);},zoomBarDown:function(evt){if(!OpenLayers.Event.isLeftClick(evt)){return;}
+this.map.events.on({"mousemove":this.passEventToSlider,"mouseup":this.passEventToSlider,scope:this});this.mouseDragStart=evt.xy.clone();this.zoomStart=evt.xy.clone();this.div.style.cursor="move";this.zoombarDiv.offsets=null;OpenLayers.Event.stop(evt);},zoomBarDrag:function(evt){if(this.mouseDragStart!=null){var deltaY=this.mouseDragStart.y-evt.xy.y;var offsets=OpenLayers.Util.pagePosition(this.zoombarDiv);if((evt.clientY-offsets[1])>0&&(evt.clientY-offsets[1])<parseInt(this.zoombarDiv.style.height)-2){var newTop=parseInt(this.slider.style.top)-deltaY;this.slider.style.top=newTop+"px";this.mouseDragStart=evt.xy.clone();}
+OpenLayers.Event.stop(evt);}},zoomBarUp:function(evt){if(!OpenLayers.Event.isLeftClick(evt)){return;}
+if(this.mouseDragStart){this.div.style.cursor="";this.map.events.un({"mouseup":this.passEventToSlider,"mousemove":this.passEventToSlider,scope:this});var deltaY=this.zoomStart.y-evt.xy.y;var zoomLevel=this.map.zoom;if(!this.forceFixedZoomLevel&&this.map.fractionalZoom){zoomLevel+=deltaY/this.zoomStopHeight;zoomLevel=Math.min(Math.max(zoomLevel,0),this.map.getNumZoomLevels()-1);}else{zoomLevel+=Math.round(deltaY/this.zoomStopHeight);}
+this.map.zoomTo(zoomLevel);this.mouseDragStart=null;this.zoomStart=null;OpenLayers.Event.stop(evt);}},moveZoomBar:function(){var newTop=((this.map.getNumZoomLevels()-1)-this.map.getZoom())*this.zoomStopHeight+this.startTop+1;this.slider.style.top=newTop+"px";},CLASS_NAME:"OpenLayers.Control.PanZoomBar"});OpenLayers.Control.Permalink=OpenLayers.Class(OpenLayers.Control,{argParserClass:OpenLayers.Control.ArgParser,element:null,base:'',displayProjection:null,initialize:function(element,base,options){OpenLayers.Control.prototype.initialize.apply(this,[options]);this.element=OpenLayers.Util.getElement(element);this.base=base||document.location.href;},destroy:function(){if(this.element.parentNode==this.div){this.div.removeChild(this.element);}
+this.element=null;this.map.events.unregister('moveend',this,this.updateLink);OpenLayers.Control.prototype.destroy.apply(this,arguments);},setMap:function(map){OpenLayers.Control.prototype.setMap.apply(this,arguments);for(var i=0,len=this.map.controls.length;i<len;i++){var control=this.map.controls[i];if(control.CLASS_NAME==this.argParserClass.CLASS_NAME){if(control.displayProjection!=this.displayProjection){this.displayProjection=control.displayProjection;}
+break;}}
+if(i==this.map.controls.length){this.map.addControl(new this.argParserClass({'displayProjection':this.displayProjection}));}},draw:function(){OpenLayers.Control.prototype.draw.apply(this,arguments);if(!this.element){this.div.className=this.displayClass;this.element=document.createElement("a");this.element.innerHTML=OpenLayers.i18n("permalink");this.element.href="";this.div.appendChild(this.element);}
+this.map.events.on({'moveend':this.updateLink,'changelayer':this.updateLink,'changebaselayer':this.updateLink,scope:this});this.updateLink();return this.div;},updateLink:function(){var href=this.base;if(href.indexOf('?')!=-1){href=href.substring(0,href.indexOf('?'));}
+href+='?'+OpenLayers.Util.getParameterString(this.createParams());this.element.href=href;},createParams:function(center,zoom,layers){center=center||this.map.getCenter();var params=OpenLayers.Util.getParameters(this.base);if(center){params.zoom=zoom||this.map.getZoom();var lat=center.lat;var lon=center.lon;if(this.displayProjection){var mapPosition=OpenLayers.Projection.transform({x:lon,y:lat},this.map.getProjectionObject(),this.displayProjection);lon=mapPosition.x;lat=mapPosition.y;}
+params.lat=Math.round(lat*100000)/100000;params.lon=Math.round(lon*100000)/100000;layers=layers||this.map.layers;params.layers='';for(var i=0,len=layers.length;i<len;i++){var layer=layers[i];if(layer.isBaseLayer){params.layers+=(layer==this.map.baseLayer)?"B":"0";}else{params.layers+=(layer.getVisibility())?"T":"F";}}}
+return params;},CLASS_NAME:"OpenLayers.Control.Permalink"});OpenLayers.Control.ZoomPanel=OpenLayers.Class(OpenLayers.Control.Panel,{initialize:function(options){OpenLayers.Control.Panel.prototype.initialize.apply(this,[options]);this.addControls([new OpenLayers.Control.ZoomIn(),new OpenLayers.Control.ZoomToMaxExtent(),new OpenLayers.Control.ZoomOut()]);},CLASS_NAME:"OpenLayers.Control.ZoomPanel"});OpenLayers.Format.CSWGetDomain=function(options){options=OpenLayers.Util.applyDefaults(options,OpenLayers.Format.CSWGetDomain.DEFAULTS);var cls=OpenLayers.Format.CSWGetDomain["v"+options.version.replace(/\./g,"_")];if(!cls){throw"Unsupported CSWGetDomain version: "+options.version;}
+return new cls(options);};OpenLayers.Format.CSWGetDomain.DEFAULTS={"version":"2.0.2"};OpenLayers.Format.CSWGetRecords=function(options){options=OpenLayers.Util.applyDefaults(options,OpenLayers.Format.CSWGetRecords.DEFAULTS);var cls=OpenLayers.Format.CSWGetRecords["v"+options.version.replace(/\./g,"_")];if(!cls){throw"Unsupported CSWGetRecords version: "+options.version;}
+return new cls(options);};OpenLayers.Format.CSWGetRecords.DEFAULTS={"version":"2.0.2"};OpenLayers.Format.JSON=OpenLayers.Class(OpenLayers.Format,{indent:" ",space:" ",newline:"\n",level:0,pretty:false,initialize:function(options){OpenLayers.Format.prototype.initialize.apply(this,[options]);},read:function(json,filter){try{if(/^[\],:{}\s]*$/.test(json.replace(/\\["\\\/bfnrtu]/g,'@').replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,']').replace(/(?:^|:|,)(?:\s*\[)+/g,''))){var object=eval('('+json+')');if(typeof filter==='function'){function walk(k,v){if(v&&typeof v==='object'){for(var i in v){if(v.hasOwnProperty(i)){v[i]=walk(i,v[i]);}}}
+return filter(k,v);}
+object=walk('',object);}
+if(this.keepData){this.data=object;}
+return object;}}catch(e){}
+return null;},write:function(value,pretty){this.pretty=!!pretty;var json=null;var type=typeof value;if(this.serialize[type]){try{json=this.serialize[type].apply(this,[value]);}catch(err){OpenLayers.Console.error("Trouble serializing: "+err);}}
+return json;},writeIndent:function(){var pieces=[];if(this.pretty){for(var i=0;i<this.level;++i){pieces.push(this.indent);}}
+return pieces.join('');},writeNewline:function(){return(this.pretty)?this.newline:'';},writeSpace:function(){return(this.pretty)?this.space:'';},serialize:{'object':function(object){if(object==null){return"null";}
+if(object.constructor==Date){return this.serialize.date.apply(this,[object]);}
+if(object.constructor==Array){return this.serialize.array.apply(this,[object]);}
+var pieces=['{'];this.level+=1;var key,keyJSON,valueJSON;var addComma=false;for(key in object){if(object.hasOwnProperty(key)){keyJSON=OpenLayers.Format.JSON.prototype.write.apply(this,[key,this.pretty]);valueJSON=OpenLayers.Format.JSON.prototype.write.apply(this,[object[key],this.pretty]);if(keyJSON!=null&&valueJSON!=null){if(addComma){pieces.push(',');}
+pieces.push(this.writeNewline(),this.writeIndent(),keyJSON,':',this.writeSpace(),valueJSON);addComma=true;}}}
+this.level-=1;pieces.push(this.writeNewline(),this.writeIndent(),'}');return pieces.join('');},'array':function(array){var json;var pieces=['['];this.level+=1;for(var i=0,len=array.length;i<len;++i){json=OpenLayers.Format.JSON.prototype.write.apply(this,[array[i],this.pretty]);if(json!=null){if(i>0){pieces.push(',');}
+pieces.push(this.writeNewline(),this.writeIndent(),json);}}
+this.level-=1;pieces.push(this.writeNewline(),this.writeIndent(),']');return pieces.join('');},'string':function(string){var m={'\b':'\\b','\t':'\\t','\n':'\\n','\f':'\\f','\r':'\\r','"':'\\"','\\':'\\\\'};if(/["\\\x00-\x1f]/.test(string)){return'"'+string.replace(/([\x00-\x1f\\"])/g,function(a,b){var c=m[b];if(c){return c;}
+c=b.charCodeAt();return'\\u00'+
+Math.floor(c/16).toString(16)+
+(c%16).toString(16);})+'"';}
+return'"'+string+'"';},'number':function(number){return isFinite(number)?String(number):"null";},'boolean':function(bool){return String(bool);},'date':function(date){function format(number){return(number<10)?'0'+number:number;}
+return'"'+date.getFullYear()+'-'+
+format(date.getMonth()+1)+'-'+
+format(date.getDate())+'T'+
+format(date.getHours())+':'+
+format(date.getMinutes())+':'+
+format(date.getSeconds())+'"';}},CLASS_NAME:"OpenLayers.Format.JSON"});OpenLayers.Format.WFST=function(options){options=OpenLayers.Util.applyDefaults(options,OpenLayers.Format.WFST.DEFAULTS);var cls=OpenLayers.Format.WFST["v"+options.version.replace(/\./g,"_")];if(!cls){throw"Unsupported WFST version: "+options.version;}
+return new cls(options);};OpenLayers.Format.WFST.DEFAULTS={"version":"1.0.0"};OpenLayers.Format.XML=OpenLayers.Class(OpenLayers.Format,{namespaces:null,namespaceAlias:null,defaultPrefix:null,readers:{},writers:{},xmldom:null,initialize:function(options){if(window.ActiveXObject){this.xmldom=new ActiveXObject("Microsoft.XMLDOM");}
+OpenLayers.Format.prototype.initialize.apply(this,[options]);this.namespaces=OpenLayers.Util.extend({},this.namespaces);this.namespaceAlias={};for(var alias in this.namespaces){this.namespaceAlias[this.namespaces[alias]]=alias;}},destroy:function(){this.xmldom=null;OpenLayers.Format.prototype.destroy.apply(this,arguments);},setNamespace:function(alias,uri){this.namespaces[alias]=uri;this.namespaceAlias[uri]=alias;},read:function(text){var index=text.indexOf('<');if(index>0){text=text.substring(index);}
+var node=OpenLayers.Util.Try(OpenLayers.Function.bind((function(){var xmldom;if(window.ActiveXObject&&!this.xmldom){xmldom=new ActiveXObject("Microsoft.XMLDOM");}else{xmldom=this.xmldom;}
+xmldom.loadXML(text);return xmldom;}),this),function(){return new DOMParser().parseFromString(text,'text/xml');},function(){var req=new XMLHttpRequest();req.open("GET","data:"+"text/xml"+";charset=utf-8,"+encodeURIComponent(text),false);if(req.overrideMimeType){req.overrideMimeType("text/xml");}
+req.send(null);return req.responseXML;});if(this.keepData){this.data=node;}
+return node;},write:function(node){var data;if(this.xmldom){data=node.xml;}else{var serializer=new XMLSerializer();if(node.nodeType==1){var doc=document.implementation.createDocument("","",null);if(doc.importNode){node=doc.importNode(node,true);}
+doc.appendChild(node);data=serializer.serializeToString(doc);}else{data=serializer.serializeToString(node);}}
+return data;},createElementNS:function(uri,name){var element;if(this.xmldom){if(typeof uri=="string"){element=this.xmldom.createNode(1,name,uri);}else{element=this.xmldom.createNode(1,name,"");}}else{element=document.createElementNS(uri,name);}
+return element;},createTextNode:function(text){var node;if(typeof text!=="string"){text=String(text);}
+if(this.xmldom){node=this.xmldom.createTextNode(text);}else{node=document.createTextNode(text);}
+return node;},getElementsByTagNameNS:function(node,uri,name){var elements=[];if(node.getElementsByTagNameNS){elements=node.getElementsByTagNameNS(uri,name);}else{var allNodes=node.getElementsByTagName("*");var potentialNode,fullName;for(var i=0,len=allNodes.length;i<len;++i){potentialNode=allNodes[i];fullName=(potentialNode.prefix)?(potentialNode.prefix+":"+name):name;if((name=="*")||(fullName==potentialNode.nodeName)){if((uri=="*")||(uri==potentialNode.namespaceURI)){elements.push(potentialNode);}}}}
+return elements;},getAttributeNodeNS:function(node,uri,name){var attributeNode=null;if(node.getAttributeNodeNS){attributeNode=node.getAttributeNodeNS(uri,name);}else{var attributes=node.attributes;var potentialNode,fullName;for(var i=0,len=attributes.length;i<len;++i){potentialNode=attributes[i];if(potentialNode.namespaceURI==uri){fullName=(potentialNode.prefix)?(potentialNode.prefix+":"+name):name;if(fullName==potentialNode.nodeName){attributeNode=potentialNode;break;}}}}
+return attributeNode;},getAttributeNS:function(node,uri,name){var attributeValue="";if(node.getAttributeNS){attributeValue=node.getAttributeNS(uri,name)||"";}else{var attributeNode=this.getAttributeNodeNS(node,uri,name);if(attributeNode){attributeValue=attributeNode.nodeValue;}}
+return attributeValue;},getChildValue:function(node,def){var value=def||"";if(node){for(var child=node.firstChild;child;child=child.nextSibling){switch(child.nodeType){case 3:case 4:value+=child.nodeValue;}}}
+return value;},concatChildValues:function(node,def){var value="";var child=node.firstChild;var childValue;while(child){childValue=child.nodeValue;if(childValue){value+=childValue;}
+child=child.nextSibling;}
+if(value==""&&def!=undefined){value=def;}
+return value;},isSimpleContent:function(node){var simple=true;for(var child=node.firstChild;child;child=child.nextSibling){if(child.nodeType===1){simple=false;break;}}
+return simple;},contentType:function(node){var simple=false,complex=false;var type=OpenLayers.Format.XML.CONTENT_TYPE.EMPTY;for(var child=node.firstChild;child;child=child.nextSibling){switch(child.nodeType){case 1:complex=true;break;case 8:break;default:simple=true;}
+if(complex&&simple){break;}}
+if(complex&&simple){type=OpenLayers.Format.XML.CONTENT_TYPE.MIXED;}else if(complex){return OpenLayers.Format.XML.CONTENT_TYPE.COMPLEX;}else if(simple){return OpenLayers.Format.XML.CONTENT_TYPE.SIMPLE;}
+return type;},hasAttributeNS:function(node,uri,name){var found=false;if(node.hasAttributeNS){found=node.hasAttributeNS(uri,name);}else{found=!!this.getAttributeNodeNS(node,uri,name);}
+return found;},setAttributeNS:function(node,uri,name,value){if(node.setAttributeNS){node.setAttributeNS(uri,name,value);}else{if(this.xmldom){if(uri){var attribute=node.ownerDocument.createNode(2,name,uri);attribute.nodeValue=value;node.setAttributeNode(attribute);}else{node.setAttribute(name,value);}}else{throw"setAttributeNS not implemented";}}},createElementNSPlus:function(name,options){options=options||{};var uri=options.uri||this.namespaces[options.prefix];if(!uri){var loc=name.indexOf(":");uri=this.namespaces[name.substring(0,loc)];}
+if(!uri){uri=this.namespaces[this.defaultPrefix];}
+var node=this.createElementNS(uri,name);if(options.attributes){this.setAttributes(node,options.attributes);}
+var value=options.value;if(value!=null){node.appendChild(this.createTextNode(value));}
+return node;},setAttributes:function(node,obj){var value,uri;for(var name in obj){if(obj[name]!=null&&obj[name].toString){value=obj[name].toString();uri=this.namespaces[name.substring(0,name.indexOf(":"))]||null;this.setAttributeNS(node,uri,name,value);}}},readNode:function(node,obj){if(!obj){obj={};}
+var group=this.readers[node.namespaceURI?this.namespaceAlias[node.namespaceURI]:this.defaultPrefix];if(group){var local=node.localName||node.nodeName.split(":").pop();var reader=group[local]||group["*"];if(reader){reader.apply(this,[node,obj]);}}
+return obj;},readChildNodes:function(node,obj){if(!obj){obj={};}
+var children=node.childNodes;var child;for(var i=0,len=children.length;i<len;++i){child=children[i];if(child.nodeType==1){this.readNode(child,obj);}}
+return obj;},writeNode:function(name,obj,parent){var prefix,local;var split=name.indexOf(":");if(split>0){prefix=name.substring(0,split);local=name.substring(split+1);}else{if(parent){prefix=this.namespaceAlias[parent.namespaceURI];}else{prefix=this.defaultPrefix;}
+local=name;}
+var child=this.writers[prefix][local].apply(this,[obj]);if(parent){parent.appendChild(child);}
+return child;},getChildEl:function(node,name,uri){return node&&this.getThisOrNextEl(node.firstChild,name,uri);},getNextEl:function(node,name,uri){return node&&this.getThisOrNextEl(node.nextSibling,name,uri);},getThisOrNextEl:function(node,name,uri){outer:for(var sibling=node;sibling;sibling=sibling.nextSibling){switch(sibling.nodeType){case 1:if((!name||name===(sibling.localName||sibling.nodeName.split(":").pop()))&&(!uri||uri===sibling.namespaceURI)){break outer;}
+sibling=null;break outer;case 3:if(/^\s*$/.test(sibling.nodeValue)){break;}
+case 4:case 6:case 12:case 10:case 11:sibling=null;break outer;}}
+return sibling||null;},lookupNamespaceURI:function(node,prefix){var uri=null;if(node){if(node.lookupNamespaceURI){uri=node.lookupNamespaceURI(prefix);}else{outer:switch(node.nodeType){case 1:if(node.namespaceURI!==null&&node.prefix===prefix){uri=node.namespaceURI;break outer;}
+var len=node.attributes.length;if(len){var attr;for(var i=0;i<len;++i){attr=node.attributes[i];if(attr.prefix==="xmlns"&&attr.name==="xmlns:"+prefix){uri=attr.value||null;break outer;}else if(attr.name==="xmlns"&&prefix===null){uri=attr.value||null;break outer;}}}
+uri=this.lookupNamespaceURI(node.parentNode,prefix);break outer;case 2:uri=this.lookupNamespaceURI(node.ownerElement,prefix);break outer;case 9:uri=this.lookupNamespaceURI(node.documentElement,prefix);break outer;case 6:case 12:case 10:case 11:break outer;default:uri=this.lookupNamespaceURI(node.parentNode,prefix);break outer;}}}
+return uri;},CLASS_NAME:"OpenLayers.Format.XML"});OpenLayers.Format.XML.CONTENT_TYPE={EMPTY:0,SIMPLE:1,COMPLEX:2,MIXED:3};OpenLayers.Format.XML.lookupNamespaceURI=OpenLayers.Function.bind(OpenLayers.Format.XML.prototype.lookupNamespaceURI,OpenLayers.Format.XML.prototype);OpenLayers.Handler=OpenLayers.Class({id:null,control:null,map:null,keyMask:null,active:false,evt:null,initialize:function(control,callbacks,options){OpenLayers.Util.extend(this,options);this.control=control;this.callbacks=callbacks;var map=this.map||control.map;if(map){this.setMap(map);}
+this.id=OpenLayers.Util.createUniqueID(this.CLASS_NAME+"_");},setMap:function(map){this.map=map;},checkModifiers:function(evt){if(this.keyMask==null){return true;}
+var keyModifiers=(evt.shiftKey?OpenLayers.Handler.MOD_SHIFT:0)|(evt.ctrlKey?OpenLayers.Handler.MOD_CTRL:0)|(evt.altKey?OpenLayers.Handler.MOD_ALT:0);return(keyModifiers==this.keyMask);},activate:function(){if(this.active){return false;}
+var events=OpenLayers.Events.prototype.BROWSER_EVENTS;for(var i=0,len=events.length;i<len;i++){if(this[events[i]]){this.register(events[i],this[events[i]]);}}
+this.active=true;return true;},deactivate:function(){if(!this.active){return false;}
+var events=OpenLayers.Events.prototype.BROWSER_EVENTS;for(var i=0,len=events.length;i<len;i++){if(this[events[i]]){this.unregister(events[i],this[events[i]]);}}
+this.active=false;return true;},callback:function(name,args){if(name&&this.callbacks[name]){this.callbacks[name].apply(this.control,args);}},register:function(name,method){this.map.events.registerPriority(name,this,method);this.map.events.registerPriority(name,this,this.setEvent);},unregister:function(name,method){this.map.events.unregister(name,this,method);this.map.events.unregister(name,this,this.setEvent);},setEvent:function(evt){this.evt=evt;return true;},destroy:function(){this.deactivate();this.control=this.map=null;},CLASS_NAME:"OpenLayers.Handler"});OpenLayers.Handler.MOD_NONE=0;OpenLayers.Handler.MOD_SHIFT=1;OpenLayers.Handler.MOD_CTRL=2;OpenLayers.Handler.MOD_ALT=4;OpenLayers.Map=OpenLayers.Class({Z_INDEX_BASE:{BaseLayer:100,Overlay:325,Feature:725,Popup:750,Control:1000},EVENT_TYPES:["preaddlayer","addlayer","removelayer","changelayer","movestart","move","moveend","zoomend","popupopen","popupclose","addmarker","removemarker","clearmarkers","mouseover","mouseout","mousemove","dragstart","drag","dragend","changebaselayer"],id:null,fractionalZoom:false,events:null,allOverlays:false,div:null,dragging:false,size:null,viewPortDiv:null,layerContainerOrigin:null,layerContainerDiv:null,layers:null,controls:null,popups:null,baseLayer:null,center:null,resolution:null,zoom:0,panRatio:1.5,viewRequestID:0,tileSize:null,projection:"EPSG:4326",units:'degrees',resolutions:null,maxResolution:1.40625,minResolution:null,maxScale:null,minScale:null,maxExtent:null,minExtent:null,restrictedExtent:null,numZoomLevels:16,theme:null,displayProjection:null,fallThrough:true,panTween:null,eventListeners:null,panMethod:OpenLayers.Easing.Expo.easeOut,panDuration:50,paddingForPopups:null,initialize:function(div,options){if(arguments.length===1&&typeof div==="object"){options=div;div=options&&options.div;}
+this.tileSize=new OpenLayers.Size(OpenLayers.Map.TILE_WIDTH,OpenLayers.Map.TILE_HEIGHT);this.maxExtent=new OpenLayers.Bounds(-180,-90,180,90);this.paddingForPopups=new OpenLayers.Bounds(15,15,15,15);this.theme=OpenLayers._getScriptLocation()+'theme/default/style.css';OpenLayers.Util.extend(this,options);this.layers=[];this.id=OpenLayers.Util.createUniqueID("OpenLayers.Map_");this.div=OpenLayers.Util.getElement(div);if(!this.div){this.div=document.createElement("div");this.div.style.height="1px";this.div.style.width="1px";}
+OpenLayers.Element.addClass(this.div,'olMap');var id=this.id+"_OpenLayers_ViewPort";this.viewPortDiv=OpenLayers.Util.createDiv(id,null,null,null,"relative",null,"hidden");this.viewPortDiv.style.width="100%";this.viewPortDiv.style.height="100%";this.viewPortDiv.className="olMapViewport";this.div.appendChild(this.viewPortDiv);id=this.id+"_OpenLayers_Container";this.layerContainerDiv=OpenLayers.Util.createDiv(id);this.layerContainerDiv.style.zIndex=this.Z_INDEX_BASE['Popup']-1;this.viewPortDiv.appendChild(this.layerContainerDiv);this.events=new OpenLayers.Events(this,this.div,this.EVENT_TYPES,this.fallThrough,{includeXY:true});this.updateSize();if(this.eventListeners instanceof Object){this.events.on(this.eventListeners);}
+this.events.register("movestart",this,this.updateSize);if(OpenLayers.String.contains(navigator.appName,"Microsoft")){this.events.register("resize",this,this.updateSize);}else{this.updateSizeDestroy=OpenLayers.Function.bind(this.updateSize,this);OpenLayers.Event.observe(window,'resize',this.updateSizeDestroy);}
+if(this.theme){var addNode=true;var nodes=document.getElementsByTagName('link');for(var i=0,len=nodes.length;i<len;++i){if(OpenLayers.Util.isEquivalentUrl(nodes.item(i).href,this.theme)){addNode=false;break;}}
+if(addNode){var cssNode=document.createElement('link');cssNode.setAttribute('rel','stylesheet');cssNode.setAttribute('type','text/css');cssNode.setAttribute('href',this.theme);document.getElementsByTagName('head')[0].appendChild(cssNode);}}
+if(this.controls==null){if(OpenLayers.Control!=null){this.controls=[new OpenLayers.Control.Navigation(),new OpenLayers.Control.PanZoom(),new OpenLayers.Control.ArgParser(),new OpenLayers.Control.Attribution()];}else{this.controls=[];}}
+for(var i=0,len=this.controls.length;i<len;i++){this.addControlToMap(this.controls[i]);}
+this.popups=[];this.unloadDestroy=OpenLayers.Function.bind(this.destroy,this);OpenLayers.Event.observe(window,'unload',this.unloadDestroy);if(options&&options.layers){this.addLayers(options.layers);if(options.center){this.setCenter(options.center,options.zoom);}}},render:function(div){this.div=OpenLayers.Util.getElement(div);OpenLayers.Element.addClass(this.div,'olMap');this.events.attachToElement(this.div);this.viewPortDiv.parentNode.removeChild(this.viewPortDiv);this.div.appendChild(this.viewPortDiv);this.updateSize();},unloadDestroy:null,updateSizeDestroy:null,destroy:function(){if(!this.unloadDestroy){return false;}
+if(this.panTween){this.panTween.stop();this.panTween=null;}
+OpenLayers.Event.stopObserving(window,'unload',this.unloadDestroy);this.unloadDestroy=null;if(this.updateSizeDestroy){OpenLayers.Event.stopObserving(window,'resize',this.updateSizeDestroy);}else{this.events.unregister("resize",this,this.updateSize);}
+this.paddingForPopups=null;if(this.controls!=null){for(var i=this.controls.length-1;i>=0;--i){this.controls[i].destroy();}
+this.controls=null;}
+if(this.layers!=null){for(var i=this.layers.length-1;i>=0;--i){this.layers[i].destroy(false);}
+this.layers=null;}
+if(this.viewPortDiv){this.div.removeChild(this.viewPortDiv);}
+this.viewPortDiv=null;if(this.eventListeners){this.events.un(this.eventListeners);this.eventListeners=null;}
+this.events.destroy();this.events=null;},setOptions:function(options){OpenLayers.Util.extend(this,options);},getTileSize:function(){return this.tileSize;},getBy:function(array,property,match){var test=(typeof match.test=="function");var found=OpenLayers.Array.filter(this[array],function(item){return item[property]==match||(test&&match.test(item[property]));});return found;},getLayersBy:function(property,match){return this.getBy("layers",property,match);},getLayersByName:function(match){return this.getLayersBy("name",match);},getLayersByClass:function(match){return this.getLayersBy("CLASS_NAME",match);},getControlsBy:function(property,match){return this.getBy("controls",property,match);},getControlsByClass:function(match){return this.getControlsBy("CLASS_NAME",match);},getLayer:function(id){var foundLayer=null;for(var i=0,len=this.layers.length;i<len;i++){var layer=this.layers[i];if(layer.id==id){foundLayer=layer;break;}}
+return foundLayer;},setLayerZIndex:function(layer,zIdx){layer.setZIndex(this.Z_INDEX_BASE[layer.isBaseLayer?'BaseLayer':'Overlay']
++zIdx*5);},resetLayersZIndex:function(){for(var i=0,len=this.layers.length;i<len;i++){var layer=this.layers[i];this.setLayerZIndex(layer,i);}},addLayer:function(layer){for(var i=0,len=this.layers.length;i<len;i++){if(this.layers[i]==layer){var msg=OpenLayers.i18n('layerAlreadyAdded',{'layerName':layer.name});OpenLayers.Console.warn(msg);return false;}}
+if(this.allOverlays){layer.isBaseLayer=false;}
+if(this.events.triggerEvent("preaddlayer",{layer:layer})===false){return;}
+layer.div.className="olLayerDiv";layer.div.style.overflow="";this.setLayerZIndex(layer,this.layers.length);if(layer.isFixed){this.viewPortDiv.appendChild(layer.div);}else{this.layerContainerDiv.appendChild(layer.div);}
+this.layers.push(layer);layer.setMap(this);if(layer.isBaseLayer||(this.allOverlays&&!this.baseLayer)){if(this.baseLayer==null){this.setBaseLayer(layer);}else{layer.setVisibility(false);}}else{layer.redraw();}
+this.events.triggerEvent("addlayer",{layer:layer});layer.afterAdd();},addLayers:function(layers){for(var i=0,len=layers.length;i<len;i++){this.addLayer(layers[i]);}},removeLayer:function(layer,setNewBaseLayer){if(setNewBaseLayer==null){setNewBaseLayer=true;}
+if(layer.isFixed){this.viewPortDiv.removeChild(layer.div);}else{this.layerContainerDiv.removeChild(layer.div);}
+OpenLayers.Util.removeItem(this.layers,layer);layer.removeMap(this);layer.map=null;if(this.baseLayer==layer){this.baseLayer=null;if(setNewBaseLayer){for(var i=0,len=this.layers.length;i<len;i++){var iLayer=this.layers[i];if(iLayer.isBaseLayer||this.allOverlays){this.setBaseLayer(iLayer);break;}}}}
+this.resetLayersZIndex();this.events.triggerEvent("removelayer",{layer:layer});},getNumLayers:function(){return this.layers.length;},getLayerIndex:function(layer){return OpenLayers.Util.indexOf(this.layers,layer);},setLayerIndex:function(layer,idx){var base=this.getLayerIndex(layer);if(idx<0){idx=0;}else if(idx>this.layers.length){idx=this.layers.length;}
+if(base!=idx){this.layers.splice(base,1);this.layers.splice(idx,0,layer);for(var i=0,len=this.layers.length;i<len;i++){this.setLayerZIndex(this.layers[i],i);}
+this.events.triggerEvent("changelayer",{layer:layer,property:"order"});if(this.allOverlays){if(idx===0){this.setBaseLayer(layer);}else if(this.baseLayer!==this.layers[0]){this.setBaseLayer(this.layers[0]);}}}},raiseLayer:function(layer,delta){var idx=this.getLayerIndex(layer)+delta;this.setLayerIndex(layer,idx);},setBaseLayer:function(newBaseLayer){if(newBaseLayer!=this.baseLayer){if(OpenLayers.Util.indexOf(this.layers,newBaseLayer)!=-1){var center=this.getCenter();var newResolution=OpenLayers.Util.getResolutionFromScale(this.getScale(),newBaseLayer.units);if(this.baseLayer!=null&&!this.allOverlays){this.baseLayer.setVisibility(false);}
+this.baseLayer=newBaseLayer;this.viewRequestID++;if(!this.allOverlays||this.baseLayer.visibility){this.baseLayer.setVisibility(true);}
+if(center!=null){var newZoom=this.getZoomForResolution(newResolution||this.resolution,true);this.setCenter(center,newZoom,false,true);}
+this.events.triggerEvent("changebaselayer",{layer:this.baseLayer});}}},addControl:function(control,px){this.controls.push(control);this.addControlToMap(control,px);},addControls:function(controls,pixels){var pxs=(arguments.length===1)?[]:pixels;for(var i=0,len=controls.length;i<len;i++){var ctrl=controls[i];var px=(pxs[i])?pxs[i]:null;this.addControl(ctrl,px);}},addControlToMap:function(control,px){control.outsideViewport=(control.div!=null);if(this.displayProjection&&!control.displayProjection){control.displayProjection=this.displayProjection;}
+control.setMap(this);var div=control.draw(px);if(div){if(!control.outsideViewport){div.style.zIndex=this.Z_INDEX_BASE['Control']+
+this.controls.length;this.viewPortDiv.appendChild(div);}}
+if(control.autoActivate){control.activate();}},getControl:function(id){var returnControl=null;for(var i=0,len=this.controls.length;i<len;i++){var control=this.controls[i];if(control.id==id){returnControl=control;break;}}
+return returnControl;},removeControl:function(control){if((control)&&(control==this.getControl(control.id))){if(control.div&&(control.div.parentNode==this.viewPortDiv)){this.viewPortDiv.removeChild(control.div);}
+OpenLayers.Util.removeItem(this.controls,control);}},addPopup:function(popup,exclusive){if(exclusive){for(var i=this.popups.length-1;i>=0;--i){this.removePopup(this.popups[i]);}}
+popup.map=this;this.popups.push(popup);var popupDiv=popup.draw();if(popupDiv){popupDiv.style.zIndex=this.Z_INDEX_BASE['Popup']+
+this.popups.length;this.layerContainerDiv.appendChild(popupDiv);}},removePopup:function(popup){OpenLayers.Util.removeItem(this.popups,popup);if(popup.div){try{this.layerContainerDiv.removeChild(popup.div);}
+catch(e){}}
+popup.map=null;},getSize:function(){var size=null;if(this.size!=null){size=this.size.clone();}
+return size;},updateSize:function(){var newSize=this.getCurrentSize();if(newSize&&!isNaN(newSize.h)&&!isNaN(newSize.w)){this.events.clearMouseCache();var oldSize=this.getSize();if(oldSize==null){this.size=oldSize=newSize;}
+if(!newSize.equals(oldSize)){this.size=newSize;for(var i=0,len=this.layers.length;i<len;i++){this.layers[i].onMapResize();}
+var center=this.getCenter();if(this.baseLayer!=null&&center!=null){var zoom=this.getZoom();this.zoom=null;this.setCenter(center,zoom);}}}},getCurrentSize:function(){var size=new OpenLayers.Size(this.div.clientWidth,this.div.clientHeight);if(size.w==0&&size.h==0||isNaN(size.w)&&isNaN(size.h)){size.w=this.div.offsetWidth;size.h=this.div.offsetHeight;}
+if(size.w==0&&size.h==0||isNaN(size.w)&&isNaN(size.h)){size.w=parseInt(this.div.style.width);size.h=parseInt(this.div.style.height);}
+return size;},calculateBounds:function(center,resolution){var extent=null;if(center==null){center=this.getCenter();}
+if(resolution==null){resolution=this.getResolution();}
+if((center!=null)&&(resolution!=null)){var size=this.getSize();var w_deg=size.w*resolution;var h_deg=size.h*resolution;extent=new OpenLayers.Bounds(center.lon-w_deg/2,center.lat-h_deg/2,center.lon+w_deg/2,center.lat+h_deg/2);}
+return extent;},getCenter:function(){var center=null;if(this.center){center=this.center.clone();}
+return center;},getZoom:function(){return this.zoom;},pan:function(dx,dy,options){options=OpenLayers.Util.applyDefaults(options,{animate:true,dragging:false});var centerPx=this.getViewPortPxFromLonLat(this.getCenter());var newCenterPx=centerPx.add(dx,dy);if(!options.dragging||!newCenterPx.equals(centerPx)){var newCenterLonLat=this.getLonLatFromViewPortPx(newCenterPx);if(options.animate){this.panTo(newCenterLonLat);}else{this.setCenter(newCenterLonLat,null,options.dragging);}}},panTo:function(lonlat){if(this.panMethod&&this.getExtent().scale(this.panRatio).containsLonLat(lonlat)){if(!this.panTween){this.panTween=new OpenLayers.Tween(this.panMethod);}
+var center=this.getCenter();if(lonlat.lon==center.lon&&lonlat.lat==center.lat){return;}
+var from={lon:center.lon,lat:center.lat};var to={lon:lonlat.lon,lat:lonlat.lat};this.panTween.start(from,to,this.panDuration,{callbacks:{start:OpenLayers.Function.bind(function(lonlat){this.events.triggerEvent("movestart");},this),eachStep:OpenLayers.Function.bind(function(lonlat){lonlat=new OpenLayers.LonLat(lonlat.lon,lonlat.lat);this.moveTo(lonlat,this.zoom,{'dragging':true,'noEvent':true});},this),done:OpenLayers.Function.bind(function(lonlat){lonlat=new OpenLayers.LonLat(lonlat.lon,lonlat.lat);this.moveTo(lonlat,this.zoom,{'noEvent':true});this.events.triggerEvent("moveend");},this)}});}else{this.setCenter(lonlat);}},setCenter:function(lonlat,zoom,dragging,forceZoomChange){this.moveTo(lonlat,zoom,{'dragging':dragging,'forceZoomChange':forceZoomChange,'caller':'setCenter'});},moveTo:function(lonlat,zoom,options){if(!options){options={};}
+if(zoom!=null){zoom=parseFloat(zoom);if(!this.fractionalZoom){zoom=Math.round(zoom);}}
+var dragging=options.dragging;var forceZoomChange=options.forceZoomChange;var noEvent=options.noEvent;if(this.panTween&&options.caller=="setCenter"){this.panTween.stop();}
+if(!this.center&&!this.isValidLonLat(lonlat)){lonlat=this.maxExtent.getCenterLonLat();}
+if(this.restrictedExtent!=null){if(lonlat==null){lonlat=this.getCenter();}
+if(zoom==null){zoom=this.getZoom();}
+var resolution=this.getResolutionForZoom(zoom);var extent=this.calculateBounds(lonlat,resolution);if(!this.restrictedExtent.containsBounds(extent)){var maxCenter=this.restrictedExtent.getCenterLonLat();if(extent.getWidth()>this.restrictedExtent.getWidth()){lonlat=new OpenLayers.LonLat(maxCenter.lon,lonlat.lat);}else if(extent.left<this.restrictedExtent.left){lonlat=lonlat.add(this.restrictedExtent.left-
+extent.left,0);}else if(extent.right>this.restrictedExtent.right){lonlat=lonlat.add(this.restrictedExtent.right-
+extent.right,0);}
+if(extent.getHeight()>this.restrictedExtent.getHeight()){lonlat=new OpenLayers.LonLat(lonlat.lon,maxCenter.lat);}else if(extent.bottom<this.restrictedExtent.bottom){lonlat=lonlat.add(0,this.restrictedExtent.bottom-
+extent.bottom);}
+else if(extent.top>this.restrictedExtent.top){lonlat=lonlat.add(0,this.restrictedExtent.top-
+extent.top);}}}
+var zoomChanged=forceZoomChange||((this.isValidZoomLevel(zoom))&&(zoom!=this.getZoom()));var centerChanged=(this.isValidLonLat(lonlat))&&(!lonlat.equals(this.center));if(zoomChanged||centerChanged||!dragging){if(!this.dragging&&!noEvent){this.events.triggerEvent("movestart");}
+if(centerChanged){if((!zoomChanged)&&(this.center)){this.centerLayerContainer(lonlat);}
+this.center=lonlat.clone();}
+if((zoomChanged)||(this.layerContainerOrigin==null)){this.layerContainerOrigin=this.center.clone();this.layerContainerDiv.style.left="0px";this.layerContainerDiv.style.top="0px";}
+if(zoomChanged){this.zoom=zoom;this.resolution=this.getResolutionForZoom(zoom);this.viewRequestID++;}
+var bounds=this.getExtent();if(this.baseLayer.visibility){this.baseLayer.moveTo(bounds,zoomChanged,dragging);if(dragging){this.baseLayer.events.triggerEvent("move");}else{this.baseLayer.events.triggerEvent("moveend",{"zoomChanged":zoomChanged});}}
+bounds=this.baseLayer.getExtent();for(var i=0,len=this.layers.length;i<len;i++){var layer=this.layers[i];if(layer!==this.baseLayer&&!layer.isBaseLayer){var inRange=layer.calculateInRange();if(layer.inRange!=inRange){layer.inRange=inRange;if(!inRange){layer.display(false);}
+this.events.triggerEvent("changelayer",{layer:layer,property:"visibility"});}
+if(inRange&&layer.visibility){layer.moveTo(bounds,zoomChanged,dragging);if(dragging){layer.events.triggerEvent("move");}else{layer.events.triggerEvent("moveend",{"zoomChanged":zoomChanged});}}}}
+if(zoomChanged){for(var i=0,len=this.popups.length;i<len;i++){this.popups[i].updatePosition();}}
+this.events.triggerEvent("move");if(zoomChanged){this.events.triggerEvent("zoomend");}}
+if(!dragging&&!noEvent){this.events.triggerEvent("moveend");}
+this.dragging=!!dragging;},centerLayerContainer:function(lonlat){var originPx=this.getViewPortPxFromLonLat(this.layerContainerOrigin);var newPx=this.getViewPortPxFromLonLat(lonlat);if((originPx!=null)&&(newPx!=null)){this.layerContainerDiv.style.left=Math.round(originPx.x-newPx.x)+"px";this.layerContainerDiv.style.top=Math.round(originPx.y-newPx.y)+"px";}},isValidZoomLevel:function(zoomLevel){return((zoomLevel!=null)&&(zoomLevel>=0)&&(zoomLevel<this.getNumZoomLevels()));},isValidLonLat:function(lonlat){var valid=false;if(lonlat!=null){var maxExtent=this.getMaxExtent();valid=maxExtent.containsLonLat(lonlat);}
+return valid;},getProjection:function(){var projection=this.getProjectionObject();return projection?projection.getCode():null;},getProjectionObject:function(){var projection=null;if(this.baseLayer!=null){projection=this.baseLayer.projection;}
+return projection;},getMaxResolution:function(){var maxResolution=null;if(this.baseLayer!=null){maxResolution=this.baseLayer.maxResolution;}
+return maxResolution;},getMaxExtent:function(options){var maxExtent=null;if(options&&options.restricted&&this.restrictedExtent){maxExtent=this.restrictedExtent;}else if(this.baseLayer!=null){maxExtent=this.baseLayer.maxExtent;}
+return maxExtent;},getNumZoomLevels:function(){var numZoomLevels=null;if(this.baseLayer!=null){numZoomLevels=this.baseLayer.numZoomLevels;}
+return numZoomLevels;},getExtent:function(){var extent=null;if(this.baseLayer!=null){extent=this.baseLayer.getExtent();}
+return extent;},getResolution:function(){var resolution=null;if(this.baseLayer!=null){resolution=this.baseLayer.getResolution();}else if(this.allOverlays===true&&this.layers.length>0){resolution=this.layers[0].getResolution();}
+return resolution;},getUnits:function(){var units=null;if(this.baseLayer!=null){units=this.baseLayer.units;}
+return units;},getScale:function(){var scale=null;if(this.baseLayer!=null){var res=this.getResolution();var units=this.baseLayer.units;scale=OpenLayers.Util.getScaleFromResolution(res,units);}
+return scale;},getZoomForExtent:function(bounds,closest){var zoom=null;if(this.baseLayer!=null){zoom=this.baseLayer.getZoomForExtent(bounds,closest);}
+return zoom;},getResolutionForZoom:function(zoom){var resolution=null;if(this.baseLayer){resolution=this.baseLayer.getResolutionForZoom(zoom);}
+return resolution;},getZoomForResolution:function(resolution,closest){var zoom=null;if(this.baseLayer!=null){zoom=this.baseLayer.getZoomForResolution(resolution,closest);}
+return zoom;},zoomTo:function(zoom){if(this.isValidZoomLevel(zoom)){this.setCenter(null,zoom);}},zoomIn:function(){this.zoomTo(this.getZoom()+1);},zoomOut:function(){this.zoomTo(this.getZoom()-1);},zoomToExtent:function(bounds,closest){var center=bounds.getCenterLonLat();if(this.baseLayer.wrapDateLine){var maxExtent=this.getMaxExtent();bounds=bounds.clone();while(bounds.right<bounds.left){bounds.right+=maxExtent.getWidth();}
+center=bounds.getCenterLonLat().wrapDateLine(maxExtent);}
+this.setCenter(center,this.getZoomForExtent(bounds,closest));},zoomToMaxExtent:function(options){var restricted=(options)?options.restricted:true;var maxExtent=this.getMaxExtent({'restricted':restricted});this.zoomToExtent(maxExtent);},zoomToScale:function(scale,closest){var res=OpenLayers.Util.getResolutionFromScale(scale,this.baseLayer.units);var size=this.getSize();var w_deg=size.w*res;var h_deg=size.h*res;var center=this.getCenter();var extent=new OpenLayers.Bounds(center.lon-w_deg/2,center.lat-h_deg/2,center.lon+w_deg/2,center.lat+h_deg/2);this.zoomToExtent(extent,closest);},getLonLatFromViewPortPx:function(viewPortPx){var lonlat=null;if(this.baseLayer!=null){lonlat=this.baseLayer.getLonLatFromViewPortPx(viewPortPx);}
+return lonlat;},getViewPortPxFromLonLat:function(lonlat){var px=null;if(this.baseLayer!=null){px=this.baseLayer.getViewPortPxFromLonLat(lonlat);}
+return px;},getLonLatFromPixel:function(px){return this.getLonLatFromViewPortPx(px);},getPixelFromLonLat:function(lonlat){var px=this.getViewPortPxFromLonLat(lonlat);px.x=Math.round(px.x);px.y=Math.round(px.y);return px;},getGeodesicPixelSize:function(px){var lonlat=px?this.getLonLatFromPixel(px):(this.getCenter()||new OpenLayers.LonLat(0,0));var res=this.getResolution();var left=lonlat.add(-res/2,0);var right=lonlat.add(res/2,0);var bottom=lonlat.add(0,-res/2);var top=lonlat.add(0,res/2);var dest=new OpenLayers.Projection("EPSG:4326");var source=this.getProjectionObject()||dest;if(!source.equals(dest)){left.transform(source,dest);right.transform(source,dest);bottom.transform(source,dest);top.transform(source,dest);}
+return new OpenLayers.Size(OpenLayers.Util.distVincenty(left,right),OpenLayers.Util.distVincenty(bottom,top));},getViewPortPxFromLayerPx:function(layerPx){var viewPortPx=null;if(layerPx!=null){var dX=parseInt(this.layerContainerDiv.style.left);var dY=parseInt(this.layerContainerDiv.style.top);viewPortPx=layerPx.add(dX,dY);}
+return viewPortPx;},getLayerPxFromViewPortPx:function(viewPortPx){var layerPx=null;if(viewPortPx!=null){var dX=-parseInt(this.layerContainerDiv.style.left);var dY=-parseInt(this.layerContainerDiv.style.top);layerPx=viewPortPx.add(dX,dY);if(isNaN(layerPx.x)||isNaN(layerPx.y)){layerPx=null;}}
+return layerPx;},getLonLatFromLayerPx:function(px){px=this.getViewPortPxFromLayerPx(px);return this.getLonLatFromViewPortPx(px);},getLayerPxFromLonLat:function(lonlat){var px=this.getPixelFromLonLat(lonlat);return this.getLayerPxFromViewPortPx(px);},CLASS_NAME:"OpenLayers.Map"});OpenLayers.Map.TILE_WIDTH=256;OpenLayers.Map.TILE_HEIGHT=256;OpenLayers.Marker=OpenLayers.Class({icon:null,lonlat:null,events:null,map:null,initialize:function(lonlat,icon){this.lonlat=lonlat;var newIcon=(icon)?icon:OpenLayers.Marker.defaultIcon();if(this.icon==null){this.icon=newIcon;}else{this.icon.url=newIcon.url;this.icon.size=newIcon.size;this.icon.offset=newIcon.offset;this.icon.calculateOffset=newIcon.calculateOffset;}
+this.events=new OpenLayers.Events(this,this.icon.imageDiv,null);},destroy:function(){this.erase();this.map=null;this.events.destroy();this.events=null;if(this.icon!=null){this.icon.destroy();this.icon=null;}},draw:function(px){return this.icon.draw(px);},erase:function(){if(this.icon!=null){this.icon.erase();}},moveTo:function(px){if((px!=null)&&(this.icon!=null)){this.icon.moveTo(px);}
+this.lonlat=this.map.getLonLatFromLayerPx(px);},isDrawn:function(){var isDrawn=(this.icon&&this.icon.isDrawn());return isDrawn;},onScreen:function(){var onScreen=false;if(this.map){var screenBounds=this.map.getExtent();onScreen=screenBounds.containsLonLat(this.lonlat);}
+return onScreen;},inflate:function(inflate){if(this.icon){var newSize=new OpenLayers.Size(this.icon.size.w*inflate,this.icon.size.h*inflate);this.icon.setSize(newSize);}},setOpacity:function(opacity){this.icon.setOpacity(opacity);},setUrl:function(url){this.icon.setUrl(url);},display:function(display){this.icon.display(display);},CLASS_NAME:"OpenLayers.Marker"});OpenLayers.Marker.defaultIcon=function(){var url=OpenLayers.Util.getImagesLocation()+"marker.png";var size=new OpenLayers.Size(21,25);var calculateOffset=function(size){return new OpenLayers.Pixel(-(size.w/2),-size.h);};return new OpenLayers.Icon(url,size,null,calculateOffset);};OpenLayers.Popup.FramedCloud=OpenLayers.Class(OpenLayers.Popup.Framed,{contentDisplayClass:"olFramedCloudPopupContent",autoSize:true,panMapIfOutOfView:true,imageSize:new OpenLayers.Size(1276,736),isAlphaImage:false,fixedRelativePosition:false,positionBlocks:{"tl":{'offset':new OpenLayers.Pixel(44,0),'padding':new OpenLayers.Bounds(8,40,8,9),'blocks':[{size:new OpenLayers.Size('auto','auto'),anchor:new OpenLayers.Bounds(0,51,22,0),position:new OpenLayers.Pixel(0,0)},{size:new OpenLayers.Size(22,'auto'),anchor:new OpenLayers.Bounds(null,50,0,0),position:new OpenLayers.Pixel(-1238,0)},{size:new OpenLayers.Size('auto',19),anchor:new OpenLayers.Bounds(0,32,22,null),position:new OpenLayers.Pixel(0,-631)},{size:new OpenLayers.Size(22,18),anchor:new OpenLayers.Bounds(null,32,0,null),position:new OpenLayers.Pixel(-1238,-632)},{size:new OpenLayers.Size(81,35),anchor:new OpenLayers.Bounds(null,0,0,null),position:new OpenLayers.Pixel(0,-688)}]},"tr":{'offset':new OpenLayers.Pixel(-45,0),'padding':new OpenLayers.Bounds(8,40,8,9),'blocks':[{size:new OpenLayers.Size('auto','auto'),anchor:new OpenLayers.Bounds(0,51,22,0),position:new OpenLayers.Pixel(0,0)},{size:new OpenLayers.Size(22,'auto'),anchor:new OpenLayers.Bounds(null,50,0,0),position:new OpenLayers.Pixel(-1238,0)},{size:new OpenLayers.Size('auto',19),anchor:new OpenLayers.Bounds(0,32,22,null),position:new OpenLayers.Pixel(0,-631)},{size:new OpenLayers.Size(22,19),anchor:new OpenLayers.Bounds(null,32,0,null),position:new OpenLayers.Pixel(-1238,-631)},{size:new OpenLayers.Size(81,35),anchor:new OpenLayers.Bounds(0,0,null,null),position:new OpenLayers.Pixel(-215,-687)}]},"bl":{'offset':new OpenLayers.Pixel(45,0),'padding':new OpenLayers.Bounds(8,9,8,40),'blocks':[{size:new OpenLayers.Size('auto','auto'),anchor:new OpenLayers.Bounds(0,21,22,32),position:new OpenLayers.Pixel(0,0)},{size:new OpenLayers.Size(22,'auto'),anchor:new OpenLayers.Bounds(null,21,0,32),position:new OpenLayers.Pixel(-1238,0)},{size:new OpenLayers.Size('auto',21),anchor:new OpenLayers.Bounds(0,0,22,null),position:new OpenLayers.Pixel(0,-629)},{size:new OpenLayers.Size(22,21),anchor:new OpenLayers.Bounds(null,0,0,null),position:new OpenLayers.Pixel(-1238,-629)},{size:new OpenLayers.Size(81,33),anchor:new OpenLayers.Bounds(null,null,0,0),position:new OpenLayers.Pixel(-101,-674)}]},"br":{'offset':new OpenLayers.Pixel(-44,0),'padding':new OpenLayers.Bounds(8,9,8,40),'blocks':[{size:new OpenLayers.Size('auto','auto'),anchor:new OpenLayers.Bounds(0,21,22,32),position:new OpenLayers.Pixel(0,0)},{size:new OpenLayers.Size(22,'auto'),anchor:new OpenLayers.Bounds(null,21,0,32),position:new OpenLayers.Pixel(-1238,0)},{size:new OpenLayers.Size('auto',21),anchor:new OpenLayers.Bounds(0,0,22,null),position:new OpenLayers.Pixel(0,-629)},{size:new OpenLayers.Size(22,21),anchor:new OpenLayers.Bounds(null,0,0,null),position:new OpenLayers.Pixel(-1238,-629)},{size:new OpenLayers.Size(81,33),anchor:new OpenLayers.Bounds(0,null,null,0),position:new OpenLayers.Pixel(-311,-674)}]}},minSize:new OpenLayers.Size(105,10),maxSize:new OpenLayers.Size(1200,660),initialize:function(id,lonlat,contentSize,contentHTML,anchor,closeBox,closeBoxCallback){this.imageSrc=OpenLayers.Util.getImagesLocation()+'cloud-popup-relative.png';OpenLayers.Popup.Framed.prototype.initialize.apply(this,arguments);this.contentDiv.className=this.contentDisplayClass;},destroy:function(){OpenLayers.Popup.Framed.prototype.destroy.apply(this,arguments);},CLASS_NAME:"OpenLayers.Popup.FramedCloud"});OpenLayers.Request={DEFAULT_CONFIG:{method:"GET",url:window.location.href,async:true,user:undefined,password:undefined,params:null,proxy:OpenLayers.ProxyHost,headers:{},data:null,callback:function(){},success:null,failure:null,scope:null},events:new OpenLayers.Events(this,null,["complete","success","failure"]),issue:function(config){var defaultConfig=OpenLayers.Util.extend(this.DEFAULT_CONFIG,{proxy:OpenLayers.ProxyHost});config=OpenLayers.Util.applyDefaults(config,defaultConfig);var request=new OpenLayers.Request.XMLHttpRequest();var url=config.url;if(config.params){var paramString=OpenLayers.Util.getParameterString(config.params);if(paramString.length>0){var separator=(url.indexOf('?')>-1)?'&':'?';url+=separator+paramString;}}
+if(config.proxy&&(url.indexOf("http")==0)){if(typeof config.proxy=="function"){url=config.proxy(url);}else{url=config.proxy+encodeURIComponent(url);}}
+request.open(config.method,url,config.async,config.user,config.password);for(var header in config.headers){request.setRequestHeader(header,config.headers[header]);}
+var events=this.events;var self=this;request.onreadystatechange=function(){if(request.readyState==OpenLayers.Request.XMLHttpRequest.DONE){var proceed=events.triggerEvent("complete",{request:request,config:config,requestUrl:url});if(proceed!==false){self.runCallbacks({request:request,config:config,requestUrl:url});}}};if(config.async===false){request.send(config.data);}else{window.setTimeout(function(){if(request._aborted!==true){request.send(config.data);}},0);}
+return request;},runCallbacks:function(options){var request=options.request;var config=options.config;var complete=(config.scope)?OpenLayers.Function.bind(config.callback,config.scope):config.callback;var success;if(config.success){success=(config.scope)?OpenLayers.Function.bind(config.success,config.scope):config.success;}
+var failure;if(config.failure){failure=(config.scope)?OpenLayers.Function.bind(config.failure,config.scope):config.failure;}
+complete(request);if(!request.status||(request.status>=200&&request.status<300)){this.events.triggerEvent("success",options);if(success){success(request);}}
+if(request.status&&(request.status<200||request.status>=300)){this.events.triggerEvent("failure",options);if(failure){failure(request);}}},GET:function(config){config=OpenLayers.Util.extend(config,{method:"GET"});return OpenLayers.Request.issue(config);},POST:function(config){config=OpenLayers.Util.extend(config,{method:"POST"});config.headers=config.headers?config.headers:{};if(!("CONTENT-TYPE"in OpenLayers.Util.upperCaseObject(config.headers))){config.headers["Content-Type"]="application/xml";}
+return OpenLayers.Request.issue(config);},PUT:function(config){config=OpenLayers.Util.extend(config,{method:"PUT"});config.headers=config.headers?config.headers:{};if(!("CONTENT-TYPE"in OpenLayers.Util.upperCaseObject(config.headers))){config.headers["Content-Type"]="application/xml";}
+return OpenLayers.Request.issue(config);},DELETE:function(config){config=OpenLayers.Util.extend(config,{method:"DELETE"});return OpenLayers.Request.issue(config);},HEAD:function(config){config=OpenLayers.Util.extend(config,{method:"HEAD"});return OpenLayers.Request.issue(config);},OPTIONS:function(config){config=OpenLayers.Util.extend(config,{method:"OPTIONS"});return OpenLayers.Request.issue(config);}};OpenLayers.Tile.Image=OpenLayers.Class(OpenLayers.Tile,{url:null,imgDiv:null,frame:null,layerAlphaHack:null,isBackBuffer:false,lastRatio:1,isFirstDraw:true,backBufferTile:null,initialize:function(layer,position,bounds,url,size){OpenLayers.Tile.prototype.initialize.apply(this,arguments);this.url=url;this.frame=document.createElement('div');this.frame.style.overflow='hidden';this.frame.style.position='absolute';this.layerAlphaHack=this.layer.alpha&&OpenLayers.Util.alphaHack();},destroy:function(){if(this.imgDiv!=null){if(this.layerAlphaHack){OpenLayers.Event.stopObservingElement(this.imgDiv.childNodes[0]);}
+OpenLayers.Event.stopObservingElement(this.imgDiv);if(this.imgDiv.parentNode==this.frame){this.frame.removeChild(this.imgDiv);this.imgDiv.map=null;}
+this.imgDiv.urls=null;this.imgDiv.src=OpenLayers.Util.getImagesLocation()+"blank.gif";}
+this.imgDiv=null;if((this.frame!=null)&&(this.frame.parentNode==this.layer.div)){this.layer.div.removeChild(this.frame);}
+this.frame=null;if(this.backBufferTile){this.backBufferTile.destroy();this.backBufferTile=null;}
+this.layer.events.unregister("loadend",this,this.resetBackBuffer);OpenLayers.Tile.prototype.destroy.apply(this,arguments);},clone:function(obj){if(obj==null){obj=new OpenLayers.Tile.Image(this.layer,this.position,this.bounds,this.url,this.size);}
+obj=OpenLayers.Tile.prototype.clone.apply(this,[obj]);obj.imgDiv=null;return obj;},draw:function(){if(this.layer!=this.layer.map.baseLayer&&this.layer.reproject){this.bounds=this.getBoundsFromBaseLayer(this.position);}
+var drawTile=OpenLayers.Tile.prototype.draw.apply(this,arguments);if((OpenLayers.Util.indexOf(this.layer.SUPPORTED_TRANSITIONS,this.layer.transitionEffect)!=-1)||this.layer.singleTile){if(drawTile){if(!this.backBufferTile){this.backBufferTile=this.clone();this.backBufferTile.hide();this.backBufferTile.isBackBuffer=true;this.events.register('loadend',this,this.resetBackBuffer);this.layer.events.register("loadend",this,this.resetBackBuffer);}
+this.startTransition();}else{if(this.backBufferTile){this.backBufferTile.clear();}}}else{if(drawTile&&this.isFirstDraw){this.events.register('loadend',this,this.showTile);this.isFirstDraw=false;}}
+if(!drawTile){return false;}
+if(this.isLoading){this.events.triggerEvent("reload");}else{this.isLoading=true;this.events.triggerEvent("loadstart");}
+return this.renderTile();},resetBackBuffer:function(){this.showTile();if(this.backBufferTile&&(this.isFirstDraw||!this.layer.numLoadingTiles)){this.isFirstDraw=false;var maxExtent=this.layer.maxExtent;var withinMaxExtent=(maxExtent&&this.bounds.intersectsBounds(maxExtent,false));if(withinMaxExtent){this.backBufferTile.position=this.position;this.backBufferTile.bounds=this.bounds;this.backBufferTile.size=this.size;this.backBufferTile.imageSize=this.layer.getImageSize(this.bounds)||this.size;this.backBufferTile.imageOffset=this.layer.imageOffset;this.backBufferTile.resolution=this.layer.getResolution();this.backBufferTile.renderTile();}
+this.backBufferTile.hide();}},renderTile:function(){if(this.imgDiv==null){this.initImgDiv();}
+this.imgDiv.viewRequestID=this.layer.map.viewRequestID;if(this.layer.async){this.layer.getURLasync(this.bounds,this,"url",this.positionImage);}else{if(this.layer.url instanceof Array){this.imgDiv.urls=this.layer.url.slice();}
+this.url=this.layer.getURL(this.bounds);this.positionImage();}
+return true;},positionImage:function(){if(this.layer===null){return;}
+OpenLayers.Util.modifyDOMElement(this.frame,null,this.position,this.size);var imageSize=this.layer.getImageSize(this.bounds);if(this.layerAlphaHack){OpenLayers.Util.modifyAlphaImageDiv(this.imgDiv,null,null,imageSize,this.url);}else{OpenLayers.Util.modifyDOMElement(this.imgDiv,null,null,imageSize);this.imgDiv.src=this.url;}},clear:function(){if(this.imgDiv){this.hide();if(OpenLayers.Tile.Image.useBlankTile){this.imgDiv.src=OpenLayers.Util.getImagesLocation()+"blank.gif";}}},initImgDiv:function(){var offset=this.layer.imageOffset;var size=this.layer.getImageSize(this.bounds);if(this.layerAlphaHack){this.imgDiv=OpenLayers.Util.createAlphaImageDiv(null,offset,size,null,"relative",null,null,null,true);}else{this.imgDiv=OpenLayers.Util.createImage(null,offset,size,null,"relative",null,null,true);}
+this.imgDiv.className='olTileImage';this.frame.style.zIndex=this.isBackBuffer?0:1;this.frame.appendChild(this.imgDiv);this.layer.div.appendChild(this.frame);if(this.layer.opacity!=null){OpenLayers.Util.modifyDOMElement(this.imgDiv,null,null,null,null,null,null,this.layer.opacity);}
+this.imgDiv.map=this.layer.map;var onload=function(){if(this.isLoading){this.isLoading=false;this.events.triggerEvent("loadend");}};if(this.layerAlphaHack){OpenLayers.Event.observe(this.imgDiv.childNodes[0],'load',OpenLayers.Function.bind(onload,this));}else{OpenLayers.Event.observe(this.imgDiv,'load',OpenLayers.Function.bind(onload,this));}
+var onerror=function(){if(this.imgDiv._attempts>OpenLayers.IMAGE_RELOAD_ATTEMPTS){onload.call(this);}};OpenLayers.Event.observe(this.imgDiv,"error",OpenLayers.Function.bind(onerror,this));},checkImgURL:function(){if(this.layer){var loaded=this.layerAlphaHack?this.imgDiv.firstChild.src:this.imgDiv.src;if(!OpenLayers.Util.isEquivalentUrl(loaded,this.url)){this.hide();}}},startTransition:function(){if(!this.backBufferTile||!this.backBufferTile.imgDiv){return;}
+var ratio=1;if(this.backBufferTile.resolution){ratio=this.backBufferTile.resolution/this.layer.getResolution();}
+if(ratio!=this.lastRatio){if(this.layer.transitionEffect=='resize'){var upperLeft=new OpenLayers.LonLat(this.backBufferTile.bounds.left,this.backBufferTile.bounds.top);var size=new OpenLayers.Size(this.backBufferTile.size.w*ratio,this.backBufferTile.size.h*ratio);var px=this.layer.map.getLayerPxFromLonLat(upperLeft);OpenLayers.Util.modifyDOMElement(this.backBufferTile.frame,null,px,size);var imageSize=this.backBufferTile.imageSize;imageSize=new OpenLayers.Size(imageSize.w*ratio,imageSize.h*ratio);var imageOffset=this.backBufferTile.imageOffset;if(imageOffset){imageOffset=new OpenLayers.Pixel(imageOffset.x*ratio,imageOffset.y*ratio);}
+OpenLayers.Util.modifyDOMElement(this.backBufferTile.imgDiv,null,imageOffset,imageSize);this.backBufferTile.show();}}else{if(this.layer.singleTile){this.backBufferTile.show();}else{this.backBufferTile.hide();}}
+this.lastRatio=ratio;},show:function(){this.frame.style.display='';if(OpenLayers.Util.indexOf(this.layer.SUPPORTED_TRANSITIONS,this.layer.transitionEffect)!=-1){if(navigator.userAgent.toLowerCase().indexOf("gecko")!=-1){this.frame.scrollLeft=this.frame.scrollLeft;}}},hide:function(){this.frame.style.display='none';},CLASS_NAME:"OpenLayers.Tile.Image"});OpenLayers.Tile.Image.useBlankTile=(OpenLayers.Util.getBrowserName()=="safari"||OpenLayers.Util.getBrowserName()=="opera");OpenLayers.Control.OverviewMap=OpenLayers.Class(OpenLayers.Control,{element:null,ovmap:null,size:new OpenLayers.Size(180,90),layers:null,minRectSize:15,minRectDisplayClass:"RectReplacement",minRatio:8,maxRatio:32,mapOptions:null,autoPan:false,handlers:null,resolutionFactor:1,maximized:false,initialize:function(options){this.layers=[];this.handlers={};OpenLayers.Control.prototype.initialize.apply(this,[options]);},destroy:function(){if(!this.mapDiv){return;}
+if(this.handlers.click){this.handlers.click.destroy();}
+if(this.handlers.drag){this.handlers.drag.destroy();}
+this.mapDiv.removeChild(this.extentRectangle);this.extentRectangle=null;if(this.rectEvents){this.rectEvents.destroy();this.rectEvents=null;}
+if(this.ovmap){this.ovmap.destroy();this.ovmap=null;}
+this.element.removeChild(this.mapDiv);this.mapDiv=null;this.div.removeChild(this.element);this.element=null;if(this.maximizeDiv){OpenLayers.Event.stopObservingElement(this.maximizeDiv);this.div.removeChild(this.maximizeDiv);this.maximizeDiv=null;}
+if(this.minimizeDiv){OpenLayers.Event.stopObservingElement(this.minimizeDiv);this.div.removeChild(this.minimizeDiv);this.minimizeDiv=null;}
+this.map.events.un({"moveend":this.update,"changebaselayer":this.baseLayerDraw,scope:this});OpenLayers.Control.prototype.destroy.apply(this,arguments);},draw:function(){OpenLayers.Control.prototype.draw.apply(this,arguments);if(!(this.layers.length>0)){if(this.map.baseLayer){var layer=this.map.baseLayer.clone();this.layers=[layer];}else{this.map.events.register("changebaselayer",this,this.baseLayerDraw);return this.div;}}
+this.element=document.createElement('div');this.element.className=this.displayClass+'Element';this.element.style.display='none';this.mapDiv=document.createElement('div');this.mapDiv.style.width=this.size.w+'px';this.mapDiv.style.height=this.size.h+'px';this.mapDiv.style.position='relative';this.mapDiv.style.overflow='hidden';this.mapDiv.id=OpenLayers.Util.createUniqueID('overviewMap');this.extentRectangle=document.createElement('div');this.extentRectangle.style.position='absolute';this.extentRectangle.style.zIndex=1000;this.extentRectangle.className=this.displayClass+'ExtentRectangle';this.mapDiv.appendChild(this.extentRectangle);this.element.appendChild(this.mapDiv);this.div.appendChild(this.element);if(!this.outsideViewport){this.div.className+=" "+this.displayClass+'Container';var imgLocation=OpenLayers.Util.getImagesLocation();var img=imgLocation+'layer-switcher-maximize.png';this.maximizeDiv=OpenLayers.Util.createAlphaImageDiv(this.displayClass+'MaximizeButton',null,new OpenLayers.Size(18,18),img,'absolute');this.maximizeDiv.style.display='none';this.maximizeDiv.className=this.displayClass+'MaximizeButton';OpenLayers.Event.observe(this.maximizeDiv,'click',OpenLayers.Function.bindAsEventListener(this.maximizeControl,this));this.div.appendChild(this.maximizeDiv);var img=imgLocation+'layer-switcher-minimize.png';this.minimizeDiv=OpenLayers.Util.createAlphaImageDiv('OpenLayers_Control_minimizeDiv',null,new OpenLayers.Size(18,18),img,'absolute');this.minimizeDiv.style.display='none';this.minimizeDiv.className=this.displayClass+'MinimizeButton';OpenLayers.Event.observe(this.minimizeDiv,'click',OpenLayers.Function.bindAsEventListener(this.minimizeControl,this));this.div.appendChild(this.minimizeDiv);var eventsToStop=['dblclick','mousedown'];for(var i=0,len=eventsToStop.length;i<len;i++){OpenLayers.Event.observe(this.maximizeDiv,eventsToStop[i],OpenLayers.Event.stop);OpenLayers.Event.observe(this.minimizeDiv,eventsToStop[i],OpenLayers.Event.stop);}
+this.minimizeControl();}else{this.element.style.display='';}
+if(this.map.getExtent()){this.update();}
+this.map.events.register('moveend',this,this.update);if(this.maximized){this.maximizeControl();}
+return this.div;},baseLayerDraw:function(){this.draw();this.map.events.unregister("changebaselayer",this,this.baseLayerDraw);},rectDrag:function(px){var deltaX=this.handlers.drag.last.x-px.x;var deltaY=this.handlers.drag.last.y-px.y;if(deltaX!=0||deltaY!=0){var rectTop=this.rectPxBounds.top;var rectLeft=this.rectPxBounds.left;var rectHeight=Math.abs(this.rectPxBounds.getHeight());var rectWidth=this.rectPxBounds.getWidth();var newTop=Math.max(0,(rectTop-deltaY));newTop=Math.min(newTop,this.ovmap.size.h-this.hComp-rectHeight);var newLeft=Math.max(0,(rectLeft-deltaX));newLeft=Math.min(newLeft,this.ovmap.size.w-this.wComp-rectWidth);this.setRectPxBounds(new OpenLayers.Bounds(newLeft,newTop+rectHeight,newLeft+rectWidth,newTop));}},mapDivClick:function(evt){var pxCenter=this.rectPxBounds.getCenterPixel();var deltaX=evt.xy.x-pxCenter.x;var deltaY=evt.xy.y-pxCenter.y;var top=this.rectPxBounds.top;var left=this.rectPxBounds.left;var height=Math.abs(this.rectPxBounds.getHeight());var width=this.rectPxBounds.getWidth();var newTop=Math.max(0,(top+deltaY));newTop=Math.min(newTop,this.ovmap.size.h-height);var newLeft=Math.max(0,(left+deltaX));newLeft=Math.min(newLeft,this.ovmap.size.w-width);this.setRectPxBounds(new OpenLayers.Bounds(newLeft,newTop+height,newLeft+width,newTop));this.updateMapToRect();},maximizeControl:function(e){this.element.style.display='';this.showToggle(false);if(e!=null){OpenLayers.Event.stop(e);}},minimizeControl:function(e){this.element.style.display='none';this.showToggle(true);if(e!=null){OpenLayers.Event.stop(e);}},showToggle:function(minimize){this.maximizeDiv.style.display=minimize?'':'none';this.minimizeDiv.style.display=minimize?'none':'';},update:function(){if(this.ovmap==null){this.createMap();}
+if(this.autoPan||!this.isSuitableOverview()){this.updateOverview();}
+this.updateRectToMap();},isSuitableOverview:function(){var mapExtent=this.map.getExtent();var maxExtent=this.map.maxExtent;var testExtent=new OpenLayers.Bounds(Math.max(mapExtent.left,maxExtent.left),Math.max(mapExtent.bottom,maxExtent.bottom),Math.min(mapExtent.right,maxExtent.right),Math.min(mapExtent.top,maxExtent.top));if(this.ovmap.getProjection()!=this.map.getProjection()){testExtent=testExtent.transform(this.map.getProjectionObject(),this.ovmap.getProjectionObject());}
+var resRatio=this.ovmap.getResolution()/this.map.getResolution();return((resRatio>this.minRatio)&&(resRatio<=this.maxRatio)&&(this.ovmap.getExtent().containsBounds(testExtent)));},updateOverview:function(){var mapRes=this.map.getResolution();var targetRes=this.ovmap.getResolution();var resRatio=targetRes/mapRes;if(resRatio>this.maxRatio){targetRes=this.minRatio*mapRes;}else if(resRatio<=this.minRatio){targetRes=this.maxRatio*mapRes;}
+var center;if(this.ovmap.getProjection()!=this.map.getProjection()){center=this.map.center.clone();center.transform(this.map.getProjectionObject(),this.ovmap.getProjectionObject());}else{center=this.map.center;}
+this.ovmap.setCenter(center,this.ovmap.getZoomForResolution(targetRes*this.resolutionFactor));this.updateRectToMap();},createMap:function(){var options=OpenLayers.Util.extend({controls:[],maxResolution:'auto',fallThrough:false},this.mapOptions);this.ovmap=new OpenLayers.Map(this.mapDiv,options);OpenLayers.Event.stopObserving(window,'unload',this.ovmap.unloadDestroy);this.ovmap.addLayers(this.layers);this.ovmap.zoomToMaxExtent();this.wComp=parseInt(OpenLayers.Element.getStyle(this.extentRectangle,'border-left-width'))+
+parseInt(OpenLayers.Element.getStyle(this.extentRectangle,'border-right-width'));this.wComp=(this.wComp)?this.wComp:2;this.hComp=parseInt(OpenLayers.Element.getStyle(this.extentRectangle,'border-top-width'))+
+parseInt(OpenLayers.Element.getStyle(this.extentRectangle,'border-bottom-width'));this.hComp=(this.hComp)?this.hComp:2;this.handlers.drag=new OpenLayers.Handler.Drag(this,{move:this.rectDrag,done:this.updateMapToRect},{map:this.ovmap});this.handlers.click=new OpenLayers.Handler.Click(this,{"click":this.mapDivClick},{"single":true,"double":false,"stopSingle":true,"stopDouble":true,"pixelTolerance":1,map:this.ovmap});this.handlers.click.activate();this.rectEvents=new OpenLayers.Events(this,this.extentRectangle,null,true);this.rectEvents.register("mouseover",this,function(e){if(!this.handlers.drag.active&&!this.map.dragging){this.handlers.drag.activate();}});this.rectEvents.register("mouseout",this,function(e){if(!this.handlers.drag.dragging){this.handlers.drag.deactivate();}});if(this.ovmap.getProjection()!=this.map.getProjection()){var sourceUnits=this.map.getProjectionObject().getUnits()||this.map.units||this.map.baseLayer.units;var targetUnits=this.ovmap.getProjectionObject().getUnits()||this.ovmap.units||this.ovmap.baseLayer.units;this.resolutionFactor=sourceUnits&&targetUnits?OpenLayers.INCHES_PER_UNIT[sourceUnits]/OpenLayers.INCHES_PER_UNIT[targetUnits]:1;}},updateRectToMap:function(){var bounds;if(this.ovmap.getProjection()!=this.map.getProjection()){bounds=this.map.getExtent().transform(this.map.getProjectionObject(),this.ovmap.getProjectionObject());}else{bounds=this.map.getExtent();}
+var pxBounds=this.getRectBoundsFromMapBounds(bounds);if(pxBounds){this.setRectPxBounds(pxBounds);}},updateMapToRect:function(){var lonLatBounds=this.getMapBoundsFromRectBounds(this.rectPxBounds);if(this.ovmap.getProjection()!=this.map.getProjection()){lonLatBounds=lonLatBounds.transform(this.ovmap.getProjectionObject(),this.map.getProjectionObject());}
+this.map.panTo(lonLatBounds.getCenterLonLat());},setRectPxBounds:function(pxBounds){var top=Math.max(pxBounds.top,0);var left=Math.max(pxBounds.left,0);var bottom=Math.min(pxBounds.top+Math.abs(pxBounds.getHeight()),this.ovmap.size.h-this.hComp);var right=Math.min(pxBounds.left+pxBounds.getWidth(),this.ovmap.size.w-this.wComp);var width=Math.max(right-left,0);var height=Math.max(bottom-top,0);if(width<this.minRectSize||height<this.minRectSize){this.extentRectangle.className=this.displayClass+
+this.minRectDisplayClass;var rLeft=left+(width/2)-(this.minRectSize/2);var rTop=top+(height/2)-(this.minRectSize/2);this.extentRectangle.style.top=Math.round(rTop)+'px';this.extentRectangle.style.left=Math.round(rLeft)+'px';this.extentRectangle.style.height=this.minRectSize+'px';this.extentRectangle.style.width=this.minRectSize+'px';}else{this.extentRectangle.className=this.displayClass+'ExtentRectangle';this.extentRectangle.style.top=Math.round(top)+'px';this.extentRectangle.style.left=Math.round(left)+'px';this.extentRectangle.style.height=Math.round(height)+'px';this.extentRectangle.style.width=Math.round(width)+'px';}
+this.rectPxBounds=new OpenLayers.Bounds(Math.round(left),Math.round(bottom),Math.round(right),Math.round(top));},getRectBoundsFromMapBounds:function(lonLatBounds){var leftBottomLonLat=new OpenLayers.LonLat(lonLatBounds.left,lonLatBounds.bottom);var rightTopLonLat=new OpenLayers.LonLat(lonLatBounds.right,lonLatBounds.top);var leftBottomPx=this.getOverviewPxFromLonLat(leftBottomLonLat);var rightTopPx=this.getOverviewPxFromLonLat(rightTopLonLat);var bounds=null;if(leftBottomPx&&rightTopPx){bounds=new OpenLayers.Bounds(leftBottomPx.x,leftBottomPx.y,rightTopPx.x,rightTopPx.y);}
+return bounds;},getMapBoundsFromRectBounds:function(pxBounds){var leftBottomPx=new OpenLayers.Pixel(pxBounds.left,pxBounds.bottom);var rightTopPx=new OpenLayers.Pixel(pxBounds.right,pxBounds.top);var leftBottomLonLat=this.getLonLatFromOverviewPx(leftBottomPx);var rightTopLonLat=this.getLonLatFromOverviewPx(rightTopPx);return new OpenLayers.Bounds(leftBottomLonLat.lon,leftBottomLonLat.lat,rightTopLonLat.lon,rightTopLonLat.lat);},getLonLatFromOverviewPx:function(overviewMapPx){var size=this.ovmap.size;var res=this.ovmap.getResolution();var center=this.ovmap.getExtent().getCenterLonLat();var delta_x=overviewMapPx.x-(size.w/2);var delta_y=overviewMapPx.y-(size.h/2);return new OpenLayers.LonLat(center.lon+delta_x*res,center.lat-delta_y*res);},getOverviewPxFromLonLat:function(lonlat){var res=this.ovmap.getResolution();var extent=this.ovmap.getExtent();var px=null;if(extent){px=new OpenLayers.Pixel(Math.round(1/res*(lonlat.lon-extent.left)),Math.round(1/res*(extent.top-lonlat.lat)));}
+return px;},CLASS_NAME:'OpenLayers.Control.OverviewMap'});OpenLayers.Feature=OpenLayers.Class({layer:null,id:null,lonlat:null,data:null,marker:null,popupClass:OpenLayers.Popup.AnchoredBubble,popup:null,initialize:function(layer,lonlat,data){this.layer=layer;this.lonlat=lonlat;this.data=(data!=null)?data:{};this.id=OpenLayers.Util.createUniqueID(this.CLASS_NAME+"_");},destroy:function(){if((this.layer!=null)&&(this.layer.map!=null)){if(this.popup!=null){this.layer.map.removePopup(this.popup);}}
+if(this.layer!=null&&this.marker!=null){this.layer.removeMarker(this.marker);}
+this.layer=null;this.id=null;this.lonlat=null;this.data=null;if(this.marker!=null){this.destroyMarker(this.marker);this.marker=null;}
+if(this.popup!=null){this.destroyPopup(this.popup);this.popup=null;}},onScreen:function(){var onScreen=false;if((this.layer!=null)&&(this.layer.map!=null)){var screenBounds=this.layer.map.getExtent();onScreen=screenBounds.containsLonLat(this.lonlat);}
+return onScreen;},createMarker:function(){if(this.lonlat!=null){this.marker=new OpenLayers.Marker(this.lonlat,this.data.icon);}
+return this.marker;},destroyMarker:function(){this.marker.destroy();},createPopup:function(closeBox){if(this.lonlat!=null){var id=this.id+"_popup";var anchor=(this.marker)?this.marker.icon:null;if(!this.popup){this.popup=new this.popupClass(id,this.lonlat,this.data.popupSize,this.data.popupContentHTML,anchor,closeBox);}
+if(this.data.overflow!=null){this.popup.contentDiv.style.overflow=this.data.overflow;}
+this.popup.feature=this;}
+return this.popup;},destroyPopup:function(){if(this.popup){this.popup.feature=null;this.popup.destroy();this.popup=null;}},CLASS_NAME:"OpenLayers.Feature"});OpenLayers.Format.CSWGetDomain.v2_0_2=OpenLayers.Class(OpenLayers.Format.XML,{namespaces:{xlink:"http://www.w3.org/1999/xlink",xsi:"http://www.w3.org/2001/XMLSchema-instance",csw:"http://www.opengis.net/cat/csw/2.0.2"},defaultPrefix:"csw",version:"2.0.2",schemaLocation:"http://www.opengis.net/cat/csw/2.0.2 http://schemas.opengis.net/csw/2.0.2/CSW-discovery.xsd",PropertyName:null,ParameterName:null,initialize:function(options){OpenLayers.Format.XML.prototype.initialize.apply(this,[options]);},read:function(data){if(typeof data=="string"){data=OpenLayers.Format.XML.prototype.read.apply(this,[data]);}
+if(data&&data.nodeType==9){data=data.documentElement;}
+var obj={};this.readNode(data,obj);return obj;},readers:{"csw":{"GetDomainResponse":function(node,obj){this.readChildNodes(node,obj);},"DomainValues":function(node,obj){if(!(obj.DomainValues instanceof Array)){obj.DomainValues=[];}
+var attrs=node.attributes;var domainValue={};for(var i=0,len=attrs.length;i<len;++i){domainValue[attrs[i].name]=attrs[i].nodeValue;}
+this.readChildNodes(node,domainValue);obj.DomainValues.push(domainValue);},"PropertyName":function(node,obj){obj.PropertyName=this.getChildValue(node);},"ParameterName":function(node,obj){obj.ParameterName=this.getChildValue(node);},"ListOfValues":function(node,obj){if(!(obj.ListOfValues instanceof Array)){obj.ListOfValues=[];}
+this.readChildNodes(node,obj.ListOfValues);},"Value":function(node,obj){var attrs=node.attributes;var value={}
+for(var i=0,len=attrs.length;i<len;++i){value[attrs[i].name]=attrs[i].nodeValue;}
+value.value=this.getChildValue(node);obj.push({Value:value});},"ConceptualScheme":function(node,obj){obj.ConceptualScheme={};this.readChildNodes(node,obj.ConceptualScheme);},"Name":function(node,obj){obj.Name=this.getChildValue(node);},"Document":function(node,obj){obj.Document=this.getChildValue(node);},"Authority":function(node,obj){obj.Authority=this.getChildValue(node);},"RangeOfValues":function(node,obj){obj.RangeOfValues={};this.readChildNodes(node,obj.RangeOfValues);},"MinValue":function(node,obj){var attrs=node.attributes;var value={}
+for(var i=0,len=attrs.length;i<len;++i){value[attrs[i].name]=attrs[i].nodeValue;}
+value.value=this.getChildValue(node);obj.MinValue=value;},"MaxValue":function(node,obj){var attrs=node.attributes;var value={}
+for(var i=0,len=attrs.length;i<len;++i){value[attrs[i].name]=attrs[i].nodeValue;}
+value.value=this.getChildValue(node);obj.MaxValue=value;}}},write:function(options){var node=this.writeNode("csw:GetDomain",options);return OpenLayers.Format.XML.prototype.write.apply(this,[node]);},writers:{"csw":{"GetDomain":function(options){var node=this.createElementNSPlus("csw:GetDomain",{attributes:{service:"CSW",version:this.version}});if(options.PropertyName||this.PropertyName){this.writeNode("csw:PropertyName",options.PropertyName||this.PropertyName,node);}else if(options.ParameterName||this.ParameterName){this.writeNode("csw:ParameterName",options.ParameterName||this.ParameterName,node);}
+this.readChildNodes(node,options);return node;},"PropertyName":function(value){var node=this.createElementNSPlus("csw:PropertyName",{value:value});return node;},"ParameterName":function(value){var node=this.createElementNSPlus("csw:ParameterName",{value:value});return node;}}},CLASS_NAME:"OpenLayers.Format.CSWGetDomain.v2_0_2"});OpenLayers.Format.Context=OpenLayers.Class({version:null,layerOptions:null,layerParams:null,parser:null,initialize:function(options){OpenLayers.Util.extend(this,options);this.options=options;},read:function(data,options){if(typeof data=="string"){data=OpenLayers.Format.XML.prototype.read.apply(this,[data]);}
+var root=data.documentElement;var version=this.version;if(!version){version=root.getAttribute("version");}
+var parser=this.getParser(version);var context=parser.read(data,options);var map;if(options&&options.map){this.context=context;if(options.map instanceof OpenLayers.Map){map=this.mergeContextToMap(context,options.map);}else{var mapOptions=options.map;if(OpenLayers.Util.isElement(mapOptions)||typeof mapOptions=="string"){mapOptions={div:mapOptions};}
+map=this.contextToMap(context,mapOptions);}}else{map=context;}
+return map;},getLayerFromContext:function(layerContext){var i,len;var options={queryable:layerContext.queryable,visibility:layerContext.visibility,maxExtent:layerContext.maxExtent,metadata:OpenLayers.Util.applyDefaults(layerContext.metadata,{styles:layerContext.styles}),numZoomLevels:layerContext.numZoomLevels,units:layerContext.units,isBaseLayer:layerContext.isBaseLayer,opacity:layerContext.opacity,displayInLayerSwitcher:layerContext.displayInLayerSwitcher,singleTile:layerContext.singleTile,tileSize:(layerContext.tileSize)?new OpenLayers.Size(layerContext.tileSize.width,layerContext.tileSize.height):undefined,minScale:layerContext.minScale||layerContext.maxScaleDenominator,maxScale:layerContext.maxScale||layerContext.minScaleDenominator};if(this.layerOptions){OpenLayers.Util.applyDefaults(options,this.layerOptions);}
+var params={layers:layerContext.name,transparent:layerContext.transparent,version:layerContext.version};if(layerContext.formats&&layerContext.formats.length>0){params.format=layerContext.formats[0].value;for(i=0,len=layerContext.formats.length;i<len;i++){var format=layerContext.formats[i];if(format.current==true){params.format=format.value;break;}}}
+if(layerContext.styles&&layerContext.styles.length>0){for(i=0,len=layerContext.styles.length;i<len;i++){var style=layerContext.styles[i];if(style.current==true){if(style.href){params.sld=style.href;}else if(style.body){params.sld_body=style.body;}else{params.styles=style.name;}
+break;}}}
+if(this.layerParams){OpenLayers.Util.applyDefaults(params,this.layerParams);}
+var layer=null;var service=layerContext.service;if(service==OpenLayers.Format.Context.serviceTypes.WFS){options.strategies=[new OpenLayers.Strategy.BBOX()];options.protocol=new OpenLayers.Protocol.WFS({url:layerContext.url,featurePrefix:layerContext.name.split(":")[0],featureType:layerContext.name.split(":").pop()});layer=new OpenLayers.Layer.Vector(layerContext.title||layerContext.name,options);}else if(service==OpenLayers.Format.Context.serviceTypes.KML){options.strategies=[new OpenLayers.Strategy.Fixed()];options.protocol=new OpenLayers.Protocol.HTTP({url:layerContext.url,format:new OpenLayers.Format.KML()});layer=new OpenLayers.Layer.Vector(layerContext.title||layerContext.name,options);}else if(service==OpenLayers.Format.Context.serviceTypes.GML){options.strategies=[new OpenLayers.Strategy.Fixed()];options.protocol=new OpenLayers.Protocol.HTTP({url:layerContext.url,format:new OpenLayers.Format.GML()});layer=new OpenLayers.Layer.Vector(layerContext.title||layerContext.name,options);}else if(layerContext.features){layer=new OpenLayers.Layer.Vector(layerContext.title||layerContext.name,options);layer.addFeatures(layerContext.features);}else if(layerContext.categoryLayer!==true){layer=new OpenLayers.Layer.WMS(layerContext.title||layerContext.name,layerContext.url,params,options);}
+return layer;},getLayersFromContext:function(layersContext){var layers=[];for(var i=0,len=layersContext.length;i<len;i++){var layer=this.getLayerFromContext(layersContext[i]);if(layer!==null){layers.push(layer);}}
+return layers;},contextToMap:function(context,options){options=OpenLayers.Util.applyDefaults({maxExtent:context.maxExtent,projection:context.projection},options);var map=new OpenLayers.Map(options);map.addLayers(this.getLayersFromContext(context.layersContext));map.setCenter(context.bounds.getCenterLonLat(),map.getZoomForExtent(context.bounds,true));return map;},mergeContextToMap:function(context,map){map.addLayers(this.getLayersFromContext(context.layersContext));return map;},write:function(obj,options){obj=this.toContext(obj);var version=options&&options.version;var parser=this.getParser(version);var context=parser.write(obj,options);return context;},CLASS_NAME:"OpenLayers.Format.Context"});OpenLayers.Format.Context.serviceTypes={"WMS":"urn:ogc:serviceType:WMS","WFS":"urn:ogc:serviceType:WFS","WCS":"urn:ogc:serviceType:WCS","GML":"urn:ogc:serviceType:GML","SLD":"urn:ogc:serviceType:SLD","FES":"urn:ogc:serviceType:FES","KML":"urn:ogc:serviceType:KML"};if(!OpenLayers.Format.OWSCommon){OpenLayers.Format.OWSCommon={};}
+OpenLayers.Format.OWSCommon.v1=OpenLayers.Class(OpenLayers.Format.XML,{regExes:{trimSpace:(/^\s*|\s*$/g),removeSpace:(/\s*/g),splitSpace:(/\s+/),trimComma:(/\s*,\s*/g)},readers:{"ows":{"ServiceIdentification":function(node,obj){obj.serviceIdentification={};this.readChildNodes(node,obj.serviceIdentification);},"Title":function(node,obj){obj.title=this.getChildValue(node);},"Abstract":function(node,serviceIdentification){serviceIdentification["abstract"]=this.getChildValue(node);},"Keywords":function(node,serviceIdentification){serviceIdentification.keywords={};this.readChildNodes(node,serviceIdentification.keywords);},"Keyword":function(node,keywords){keywords[this.getChildValue(node)]=true;},"ServiceType":function(node,serviceIdentification){serviceIdentification.serviceType={codeSpace:node.getAttribute('codeSpace'),value:this.getChildValue(node)};},"ServiceTypeVersion":function(node,serviceIdentification){serviceIdentification.serviceTypeVersion=this.getChildValue(node);},"Fees":function(node,serviceIdentification){serviceIdentification.fees=this.getChildValue(node);},"AccessConstraints":function(node,serviceIdentification){serviceIdentification.accessConstraints=this.getChildValue(node);},"ServiceProvider":function(node,obj){obj.serviceProvider={};this.readChildNodes(node,obj.serviceProvider);},"ProviderName":function(node,serviceProvider){serviceProvider.providerName=this.getChildValue(node);},"ProviderSite":function(node,serviceProvider){serviceProvider.providerSite=this.getAttributeNS(node,this.namespaces.xlink,"href");},"ServiceContact":function(node,serviceProvider){serviceProvider.serviceContact={};this.readChildNodes(node,serviceProvider.serviceContact);},"IndividualName":function(node,serviceContact){serviceContact.individualName=this.getChildValue(node);},"PositionName":function(node,serviceContact){serviceContact.positionName=this.getChildValue(node);},"ContactInfo":function(node,serviceContact){serviceContact.contactInfo={};this.readChildNodes(node,serviceContact.contactInfo);},"Phone":function(node,contactInfo){contactInfo.phone={};this.readChildNodes(node,contactInfo.phone);},"Voice":function(node,phone){phone.voice=this.getChildValue(node);},"Address":function(node,contactInfo){contactInfo.address={};this.readChildNodes(node,contactInfo.address);},"DeliveryPoint":function(node,address){address.deliveryPoint=this.getChildValue(node);},"City":function(node,address){address.city=this.getChildValue(node);},"AdministrativeArea":function(node,address){address.administrativeArea=this.getChildValue(node);},"PostalCode":function(node,address){address.postalCode=this.getChildValue(node);},"Country":function(node,address){address.country=this.getChildValue(node);},"ElectronicMailAddress":function(node,address){address.electronicMailAddress=this.getChildValue(node);},"Role":function(node,serviceContact){serviceContact.role=this.getChildValue(node);},"OperationsMetadata":function(node,obj){obj.operationsMetadata={};this.readChildNodes(node,obj.operationsMetadata);},"Operation":function(node,operationsMetadata){var name=node.getAttribute("name");operationsMetadata[name]={};this.readChildNodes(node,operationsMetadata[name]);},"DCP":function(node,operation){operation.dcp={};this.readChildNodes(node,operation.dcp);},"HTTP":function(node,dcp){dcp.http={};this.readChildNodes(node,dcp.http);},"Get":function(node,http){http.get=this.getAttributeNS(node,this.namespaces.xlink,"href");},"Post":function(node,http){http.post=this.getAttributeNS(node,this.namespaces.xlink,"href");},"Parameter":function(node,operation){if(!operation.parameters){operation.parameters={};}
+var name=node.getAttribute("name");operation.parameters[name]={};this.readChildNodes(node,operation.parameters[name]);},"Value":function(node,allowedValues){allowedValues[this.getChildValue(node)]=true;},"OutputFormat":function(node,obj){obj.formats.push({value:this.getChildValue(node)});this.readChildNodes(node,obj);},"WGS84BoundingBox":function(node,obj){var boundingBox={};boundingBox.crs=node.getAttribute("crs");if(obj.BoundingBox){obj.BoundingBox.push(boundingBox);}else{obj.projection=boundingBox.crs;boundingBox=obj;}
+this.readChildNodes(node,boundingBox);},"BoundingBox":function(node,obj){this.readers['ows']['WGS84BoundingBox'].apply(this,[node,obj]);},"LowerCorner":function(node,obj){var str=this.getChildValue(node).replace(this.regExes.trimSpace,"");str=str.replace(this.regExes.trimComma,",");var pointList=str.split(this.regExes.splitSpace);obj.left=pointList[0];obj.bottom=pointList[1];},"UpperCorner":function(node,obj){var str=this.getChildValue(node).replace(this.regExes.trimSpace,"");str=str.replace(this.regExes.trimComma,",");var pointList=str.split(this.regExes.splitSpace);obj.right=pointList[0];obj.top=pointList[1];obj.bounds=new OpenLayers.Bounds(obj.left,obj.bottom,obj.right,obj.top);delete obj.left;delete obj.bottom;delete obj.right;delete obj.top;}}},writers:{"ows":{"BoundingBox":function(options){var node=this.createElementNSPlus("ows:BoundingBox",{attributes:{crs:options.projection}});this.writeNode("ows:LowerCorner",options,node);this.writeNode("ows:UpperCorner",options,node);return node;},"LowerCorner":function(options){var node=this.createElementNSPlus("ows:LowerCorner",{value:options.bounds.left+" "+options.bounds.bottom});return node;},"UpperCorner":function(options){var node=this.createElementNSPlus("ows:UpperCorner",{value:options.bounds.right+" "+options.bounds.top});return node;},"Title":function(title){var node=this.createElementNSPlus("ows:Title",{value:title});return node;},"OutputFormat":function(format){var node=this.createElementNSPlus("ows:OutputFormat",{value:format});return node;}}},CLASS_NAME:"OpenLayers.Format.OWSCommon.v1"});OpenLayers.Format.SOSCapabilities=OpenLayers.Class(OpenLayers.Format.XML,{defaultVersion:"1.0.0",version:null,parser:null,initialize:function(options){OpenLayers.Format.XML.prototype.initialize.apply(this,[options]);this.options=options;},read:function(data){if(typeof data=="string"){data=OpenLayers.Format.XML.prototype.read.apply(this,[data]);}
+var root=data.documentElement;var version=this.version||root.getAttribute("version")||this.defaultVersion;if(!this.parser||this.parser.version!==version){var constr=OpenLayers.Format.SOSCapabilities["v"+version.replace(/\./g,"_")];if(!constr){throw"Can't find a SOS capabilities parser for version "+version;}
+var parser=new constr(this.options);}
+var capabilities=parser.read(data);capabilities.version=version;return capabilities;},CLASS_NAME:"OpenLayers.Format.SOSCapabilities"});OpenLayers.Format.WFSCapabilities=OpenLayers.Class(OpenLayers.Format.XML,{defaultVersion:"1.1.0",version:null,initialize:function(options){OpenLayers.Format.XML.prototype.initialize.apply(this,[options]);this.options=options;},read:function(data){if(typeof data=="string"){data=OpenLayers.Format.XML.prototype.read.apply(this,[data]);}
+var root=data.documentElement;var version=this.version;if(!version){version=root.getAttribute("version");if(!version){version=this.defaultVersion;}}
+var constr=OpenLayers.Format.WFSCapabilities["v"+version.replace(/\./g,"_")];if(!constr){throw"Can't find a WFS capabilities parser for version "+version;}
+var parser=new constr(this.options);var capabilities=parser.read(data);capabilities.version=version;return capabilities;},CLASS_NAME:"OpenLayers.Format.WFSCapabilities"});OpenLayers.Format.WFSDescribeFeatureType=OpenLayers.Class(OpenLayers.Format.XML,{namespaces:{xsd:"http://www.w3.org/2001/XMLSchema"},initialize:function(options){OpenLayers.Format.XML.prototype.initialize.apply(this,[options]);},readers:{"xsd":{"schema":function(node,obj){var complexTypes=[];var customTypes={};var schema={complexTypes:complexTypes,customTypes:customTypes};this.readChildNodes(node,schema);var attributes=node.attributes;var attr,name;for(var i=0,len=attributes.length;i<len;++i){attr=attributes[i];name=attr.name;if(name.indexOf("xmlns")==0){this.setNamespace(name.split(":")[1]||"",attr.value);}else{obj[name]=attr.value;}}
+obj.featureTypes=complexTypes;obj.targetPrefix=this.namespaceAlias[obj.targetNamespace];var complexType,customType;for(var i=0,len=complexTypes.length;i<len;++i){complexType=complexTypes[i];customType=customTypes[complexType.typeName];if(customTypes[complexType.typeName]){complexType.typeName=customType.name;}}},"complexType":function(node,obj){var complexType={"typeName":node.getAttribute("name")};this.readChildNodes(node,complexType);obj.complexTypes.push(complexType);},"complexContent":function(node,obj){this.readChildNodes(node,obj);},"extension":function(node,obj){this.readChildNodes(node,obj);},"sequence":function(node,obj){var sequence={elements:[]};this.readChildNodes(node,sequence);obj.properties=sequence.elements;},"element":function(node,obj){if(obj.elements){var element={};var attributes=node.attributes;var attr;for(var i=0,len=attributes.length;i<len;++i){attr=attributes[i];element[attr.name]=attr.value;}
+var type=element.type;if(!type){type={};this.readChildNodes(node,type);element.restriction=type;element.type=type.base;}
+var fullType=type.base||type;element.localType=fullType.split(":").pop();obj.elements.push(element);}
+if(obj.complexTypes){var type=node.getAttribute("type");var localType=type.split(":").pop();obj.customTypes[localType]={"name":node.getAttribute("name"),"type":type};}},"simpleType":function(node,obj){this.readChildNodes(node,obj);},"restriction":function(node,obj){obj.base=node.getAttribute("base");this.readRestriction(node,obj);}}},readRestriction:function(node,obj){var children=node.childNodes;var child,nodeName,value;for(var i=0,len=children.length;i<len;++i){child=children[i];if(child.nodeType==1){nodeName=child.nodeName.split(":").pop();value=child.getAttribute("value");if(!obj[nodeName]){obj[nodeName]=value;}else{if(typeof obj[nodeName]=="string"){obj[nodeName]=[obj[nodeName]];}
+obj[nodeName].push(value);}}}},read:function(data){if(typeof data=="string"){data=OpenLayers.Format.XML.prototype.read.apply(this,[data]);}
+if(data&&data.nodeType==9){data=data.documentElement;}
+var schema={};this.readNode(data,schema);return schema;},CLASS_NAME:"OpenLayers.Format.WFSDescribeFeatureType"});OpenLayers.Format.WFST.v1=OpenLayers.Class(OpenLayers.Format.XML,{namespaces:{xlink:"http://www.w3.org/1999/xlink",xsi:"http://www.w3.org/2001/XMLSchema-instance",wfs:"http://www.opengis.net/wfs",gml:"http://www.opengis.net/gml",ogc:"http://www.opengis.net/ogc"},defaultPrefix:"wfs",version:null,schemaLocations:null,srsName:null,extractAttributes:true,xy:true,stateName:null,initialize:function(options){this.stateName={};this.stateName[OpenLayers.State.INSERT]="wfs:Insert";this.stateName[OpenLayers.State.UPDATE]="wfs:Update";this.stateName[OpenLayers.State.DELETE]="wfs:Delete";OpenLayers.Format.XML.prototype.initialize.apply(this,[options]);},getSrsName:function(feature,options){var srsName=options&&options.srsName;if(!srsName){if(feature&&feature.layer){srsName=feature.layer.projection.getCode();}else{srsName=this.srsName;}}
+return srsName;},read:function(data,options){options=options||{};OpenLayers.Util.applyDefaults(options,{output:"features"});if(typeof data=="string"){data=OpenLayers.Format.XML.prototype.read.apply(this,[data]);}
+if(data&&data.nodeType==9){data=data.documentElement;}
+var obj={};if(data){this.readNode(data,obj);}
+if(obj.features&&options.output==="features"){obj=obj.features;}
+return obj;},readers:{"wfs":{"FeatureCollection":function(node,obj){obj.features=[];this.readChildNodes(node,obj);}}},write:function(features){var node=this.writeNode("wfs:Transaction",features);var value=this.schemaLocationAttr();if(value){this.setAttributeNS(node,this.namespaces["xsi"],"xsi:schemaLocation",value)}
+return OpenLayers.Format.XML.prototype.write.apply(this,[node]);},writers:{"wfs":{"GetFeature":function(options){var node=this.createElementNSPlus("wfs:GetFeature",{attributes:{service:"WFS",version:this.version,outputFormat:options&&options.outputFormat,maxFeatures:options&&options.maxFeatures,"xsi:schemaLocation":this.schemaLocationAttr(options)}});if(typeof this.featureType=="string"){this.writeNode("Query",options,node);}else{for(var i=0,len=this.featureType.length;i<len;i++){options.featureType=this.featureType[i];this.writeNode("Query",options,node);}}
+return node;},"Transaction":function(features){var node=this.createElementNSPlus("wfs:Transaction",{attributes:{service:"WFS",version:this.version}});if(features){var name,feature;for(var i=0,len=features.length;i<len;++i){feature=features[i];name=this.stateName[feature.state];if(name){this.writeNode(name,feature,node);}}}
+return node;},"Insert":function(feature){var node=this.createElementNSPlus("wfs:Insert");this.srsName=this.getSrsName(feature);this.writeNode("feature:_typeName",feature,node);return node;},"Update":function(feature){var node=this.createElementNSPlus("wfs:Update",{attributes:{typeName:(this.featureNS?this.featurePrefix+":":"")+
+this.featureType}});if(this.featureNS){node.setAttribute("xmlns:"+this.featurePrefix,this.featureNS);}
+if(this.geometryName!==null){this.writeNode("Property",{name:this.geometryName,value:feature},node);}
+for(var key in feature.attributes){if(feature.attributes[key]!==undefined){this.writeNode("Property",{name:key,value:feature.attributes[key]},node);}}
+this.writeNode("ogc:Filter",new OpenLayers.Filter.FeatureId({fids:[feature.fid]}),node);return node;},"Property":function(obj){var node=this.createElementNSPlus("wfs:Property");this.writeNode("Name",obj.name,node);if(obj.value!==null){this.writeNode("Value",obj.value,node);}
+return node;},"Name":function(name){return this.createElementNSPlus("wfs:Name",{value:name});},"Value":function(obj){var node;if(obj instanceof OpenLayers.Feature.Vector){node=this.createElementNSPlus("wfs:Value");this.srsName=this.getSrsName(obj);var geom=this.writeNode("feature:_geometry",obj.geometry).firstChild;node.appendChild(geom);}else{node=this.createElementNSPlus("wfs:Value",{value:obj});}
+return node;},"Delete":function(feature){var node=this.createElementNSPlus("wfs:Delete",{attributes:{typeName:(this.featureNS?this.featurePrefix+":":"")+
+this.featureType}});if(this.featureNS){node.setAttribute("xmlns:"+this.featurePrefix,this.featureNS);}
+this.writeNode("ogc:Filter",new OpenLayers.Filter.FeatureId({fids:[feature.fid]}),node);return node;}}},schemaLocationAttr:function(options){options=OpenLayers.Util.extend({featurePrefix:this.featurePrefix,schema:this.schema},options);var schemaLocations=OpenLayers.Util.extend({},this.schemaLocations);if(options.schema){schemaLocations[options.featurePrefix]=options.schema;}
+var parts=[];var uri;for(var key in schemaLocations){uri=this.namespaces[key];if(uri){parts.push(uri+" "+schemaLocations[key]);}}
+var value=parts.join(" ")||undefined;return value;},setFilterProperty:function(filter){if(filter.filters){for(var i=0,len=filter.filters.length;i<len;++i){this.setFilterProperty(filter.filters[i]);}}else{if(filter instanceof OpenLayers.Filter.Spatial){filter.property=this.geometryName;}}},CLASS_NAME:"OpenLayers.Format.WFST.v1"});OpenLayers.Format.WMSCapabilities=OpenLayers.Class(OpenLayers.Format.XML,{defaultVersion:"1.1.1",version:null,parser:null,initialize:function(options){OpenLayers.Format.XML.prototype.initialize.apply(this,[options]);this.options=options;},read:function(data){if(typeof data=="string"){data=OpenLayers.Format.XML.prototype.read.apply(this,[data]);}
+var root=data.documentElement;var version=this.version||root.getAttribute("version")||this.defaultVersion;if(!this.parser||this.parser.version!==version){var constr=OpenLayers.Format.WMSCapabilities["v"+version.replace(/\./g,"_")];if(!constr){throw"Can't find a WMS capabilities parser for version "+version;}
+this.parser=new constr(this.options);}
+var capabilities=this.parser.read(data);capabilities.version=version;return capabilities;},CLASS_NAME:"OpenLayers.Format.WMSCapabilities"});OpenLayers.Format.WMSDescribeLayer=OpenLayers.Class(OpenLayers.Format.XML,{defaultVersion:"1.1.1",version:null,initialize:function(options){OpenLayers.Format.XML.prototype.initialize.apply(this,[options]);this.options=options;},read:function(data){if(typeof data=="string"){data=OpenLayers.Format.XML.prototype.read.apply(this,[data]);}
+var root=data.documentElement;var version=this.version;if(!version){version=root.getAttribute("version");if(!version){version=this.defaultVersion;}}
+if(version=="1.1.1"||version=="1.1.0"){version="1.1";}
+var constructor=OpenLayers.Format.WMSDescribeLayer["v"+version.replace(/\./g,"_")];if(!constructor){throw"Can't find a WMS DescribeLayer parser for version "+
+version;}
+var parser=new constructor(this.options);var describelayer=parser.read(data);describelayer.version=version;return describelayer;},CLASS_NAME:"OpenLayers.Format.WMSDescribeLayer"});OpenLayers.Format.WMSGetFeatureInfo=OpenLayers.Class(OpenLayers.Format.XML,{layerIdentifier:'_layer',featureIdentifier:'_feature',regExes:{trimSpace:(/^\s*|\s*$/g),removeSpace:(/\s*/g),splitSpace:(/\s+/),trimComma:(/\s*,\s*/g)},gmlFormat:null,initialize:function(options){OpenLayers.Format.XML.prototype.initialize.apply(this,arguments);OpenLayers.Util.extend(this,options);this.options=options;},read:function(data){var result;if(typeof data=="string"){data=OpenLayers.Format.XML.prototype.read.apply(this,[data]);}
+var root=data.documentElement;if(root){var scope=this;var read=this["read_"+root.nodeName];if(read){result=read.call(this,root);}else{result=new OpenLayers.Format.GML((this.options?this.options:{})).read(data);}}else{result=data;}
+return result;},read_msGMLOutput:function(data){var response=[];var layerNodes=this.getSiblingNodesByTagCriteria(data,this.layerIdentifier);if(layerNodes){for(var i=0,len=layerNodes.length;i<len;++i){var node=layerNodes[i];var layerName=node.nodeName;if(node.prefix){layerName=layerName.split(':')[1];}
+var layerName=layerName.replace(this.layerIdentifier,'');var featureNodes=this.getSiblingNodesByTagCriteria(node,this.featureIdentifier);if(featureNodes){for(var j=0;j<featureNodes.length;j++){var featureNode=featureNodes[j];var geomInfo=this.parseGeometry(featureNode);var attributes=this.parseAttributes(featureNode);var feature=new OpenLayers.Feature.Vector(geomInfo.geometry,attributes,null);feature.bounds=geomInfo.bounds;feature.type=layerName;response.push(feature);}}}}
+return response;},read_FeatureInfoResponse:function(data){var response=[];var featureNodes=this.getElementsByTagNameNS(data,'*','FIELDS');for(var i=0,len=featureNodes.length;i<len;i++){var featureNode=featureNodes[i];var geom=null;var attributes={};for(var j=0,jlen=featureNode.attributes.length;j<jlen;j++){var attribute=featureNode.attributes[j];attributes[attribute.nodeName]=attribute.nodeValue;}
+response.push(new OpenLayers.Feature.Vector(geom,attributes,null));}
+return response;},getSiblingNodesByTagCriteria:function(node,criteria){var nodes=[];var children,tagName,n,matchNodes,child;if(node&&node.hasChildNodes()){children=node.childNodes;n=children.length;for(var k=0;k<n;k++){child=children[k];while(child&&child.nodeType!=1){child=child.nextSibling;k++;}
+tagName=(child?child.nodeName:'');if(tagName.length>0&&tagName.indexOf(criteria)>-1){nodes.push(child);}else{matchNodes=this.getSiblingNodesByTagCriteria(child,criteria);if(matchNodes.length>0){(nodes.length==0)?nodes=matchNodes:nodes.push(matchNodes);}}}}
+return nodes;},parseAttributes:function(node){var attributes={};if(node.nodeType==1){var children=node.childNodes;var n=children.length;for(var i=0;i<n;++i){var child=children[i];if(child.nodeType==1){var grandchildren=child.childNodes;if(grandchildren.length==1){var grandchild=grandchildren[0];if(grandchild.nodeType==3||grandchild.nodeType==4){var name=(child.prefix)?child.nodeName.split(":")[1]:child.nodeName;var value=grandchild.nodeValue.replace(this.regExes.trimSpace,"");attributes[name]=value;}}}}}
+return attributes;},parseGeometry:function(node){if(!this.gmlFormat){this.gmlFormat=new OpenLayers.Format.GML();}
+var feature=this.gmlFormat.parseFeature(node);var geometry,bounds=null;if(feature){geometry=feature.geometry&&feature.geometry.clone();bounds=feature.bounds&&feature.bounds.clone();feature.destroy();}
+return{geometry:geometry,bounds:bounds};},CLASS_NAME:"OpenLayers.Format.WMSGetFeatureInfo"});OpenLayers.Format.WMTSCapabilities=OpenLayers.Class(OpenLayers.Format.XML,{defaultVersion:"1.0.0",version:null,parser:null,yx:{"urn:ogc:def:crs:EPSG::4326":true},initialize:function(options){OpenLayers.Format.XML.prototype.initialize.apply(this,[options]);this.options=options;},read:function(data){if(typeof data=="string"){data=OpenLayers.Format.XML.prototype.read.apply(this,[data]);}
+var root=data.documentElement;var version=this.version||root.getAttribute("version")||this.defaultVersion;if(!this.parser||this.parser.version!==version){var constr=OpenLayers.Format.WMTSCapabilities["v"+version.replace(/\./g,"_")];if(!constr){throw new Error("Can't find a WMTS capabilities parser for version "+version);}
+this.parser=new constr(this.options);}
+return this.parser.read(data);},createLayer:function(capabilities,config){var layer;var required={layer:true,matrixSet:true};for(var prop in required){if(!(prop in config)){throw new Error("Missing property '"+prop+"' in layer configuration.");}}
+var contents=capabilities.contents;var matrixSet=contents.tileMatrixSets[config.matrixSet];var layers=contents.layers;var layerDef;for(var i=0,ii=contents.layers.length;i<ii;++i){if(contents.layers[i].identifier===config.layer){layerDef=contents.layers[i];break;}}
+if(layerDef&&matrixSet){var style;for(var i=0,ii=layerDef.styles.length;i<ii;++i){style=layerDef.styles[i];if(style.isDefault){break;}}
+layer=new OpenLayers.Layer.WMTS(OpenLayers.Util.applyDefaults(config,{url:capabilities.operationsMetadata.GetTile.dcp.http.get,name:layerDef.title,style:style,matrixIds:matrixSet.matrixIds}));}
+return layer;},CLASS_NAME:"OpenLayers.Format.WMTSCapabilities"});OpenLayers.Handler.Click=OpenLayers.Class(OpenLayers.Handler,{delay:300,single:true,'double':false,pixelTolerance:0,stopSingle:false,stopDouble:false,timerId:null,down:null,rightclickTimerId:null,initialize:function(control,callbacks,options){OpenLayers.Handler.prototype.initialize.apply(this,arguments);if(this.pixelTolerance!=null){this.mousedown=function(evt){this.down=evt.xy;return true;};}},mousedown:null,mouseup:function(evt){var propagate=true;if(this.checkModifiers(evt)&&this.control.handleRightClicks&&OpenLayers.Event.isRightClick(evt)){propagate=this.rightclick(evt);}
+return propagate;},rightclick:function(evt){if(this.passesTolerance(evt)){if(this.rightclickTimerId!=null){this.clearTimer();this.callback('dblrightclick',[evt]);return!this.stopDouble;}else{var clickEvent=this['double']?OpenLayers.Util.extend({},evt):this.callback('rightclick',[evt]);var delayedRightCall=OpenLayers.Function.bind(this.delayedRightCall,this,clickEvent);this.rightclickTimerId=window.setTimeout(delayedRightCall,this.delay);}}
+return!this.stopSingle;},delayedRightCall:function(evt){this.rightclickTimerId=null;if(evt){this.callback('rightclick',[evt]);}
+return!this.stopSingle;},dblclick:function(evt){if(this.passesTolerance(evt)){if(this["double"]){this.callback('dblclick',[evt]);}
+this.clearTimer();}
+return!this.stopDouble;},click:function(evt){if(this.passesTolerance(evt)){if(this.timerId!=null){this.clearTimer();}else{var clickEvent=this.single?OpenLayers.Util.extend({},evt):null;this.timerId=window.setTimeout(OpenLayers.Function.bind(this.delayedCall,this,clickEvent),this.delay);}}
+return!this.stopSingle;},passesTolerance:function(evt){var passes=true;if(this.pixelTolerance!=null&&this.down){var dpx=Math.sqrt(Math.pow(this.down.x-evt.xy.x,2)+
+Math.pow(this.down.y-evt.xy.y,2));if(dpx>this.pixelTolerance){passes=false;}}
+return passes;},clearTimer:function(){if(this.timerId!=null){window.clearTimeout(this.timerId);this.timerId=null;}
+if(this.rightclickTimerId!=null){window.clearTimeout(this.rightclickTimerId);this.rightclickTimerId=null;}},delayedCall:function(evt){this.timerId=null;if(evt){this.callback('click',[evt]);}},deactivate:function(){var deactivated=false;if(OpenLayers.Handler.prototype.deactivate.apply(this,arguments)){this.clearTimer();this.down=null;deactivated=true;}
+return deactivated;},CLASS_NAME:"OpenLayers.Handler.Click"});OpenLayers.Handler.Drag=OpenLayers.Class(OpenLayers.Handler,{started:false,stopDown:true,dragging:false,last:null,start:null,oldOnselectstart:null,interval:0,timeoutId:null,documentDrag:false,documentEvents:null,initialize:function(control,callbacks,options){OpenLayers.Handler.prototype.initialize.apply(this,arguments);},down:function(evt){},move:function(evt){},up:function(evt){},out:function(evt){},mousedown:function(evt){var propagate=true;this.dragging=false;if(this.checkModifiers(evt)&&OpenLayers.Event.isLeftClick(evt)){this.started=true;this.start=evt.xy;this.last=evt.xy;OpenLayers.Element.addClass(this.map.viewPortDiv,"olDragDown");this.down(evt);this.callback("down",[evt.xy]);OpenLayers.Event.stop(evt);if(!this.oldOnselectstart){this.oldOnselectstart=(document.onselectstart)?document.onselectstart:OpenLayers.Function.True;}
+document.onselectstart=OpenLayers.Function.False;propagate=!this.stopDown;}else{this.started=false;this.start=null;this.last=null;}
+return propagate;},mousemove:function(evt){if(this.started&&!this.timeoutId&&(evt.xy.x!=this.last.x||evt.xy.y!=this.last.y)){if(this.documentDrag===true&&this.documentEvents){if(evt.element===document){this.adjustXY(evt);this.setEvent(evt);}else{this.destroyDocumentEvents();}}
+if(this.interval>0){this.timeoutId=setTimeout(OpenLayers.Function.bind(this.removeTimeout,this),this.interval);}
+this.dragging=true;this.move(evt);this.callback("move",[evt.xy]);if(!this.oldOnselectstart){this.oldOnselectstart=document.onselectstart;document.onselectstart=OpenLayers.Function.False;}
+this.last=this.evt.xy;}
+return true;},removeTimeout:function(){this.timeoutId=null;},mouseup:function(evt){if(this.started){if(this.documentDrag===true&&this.documentEvents){this.adjustXY(evt);this.destroyDocumentEvents();}
+var dragged=(this.start!=this.last);this.started=false;this.dragging=false;OpenLayers.Element.removeClass(this.map.viewPortDiv,"olDragDown");this.up(evt);this.callback("up",[evt.xy]);if(dragged){this.callback("done",[evt.xy]);}
+document.onselectstart=this.oldOnselectstart;}
+return true;},mouseout:function(evt){if(this.started&&OpenLayers.Util.mouseLeft(evt,this.map.div)){if(this.documentDrag===true){this.documentEvents=new OpenLayers.Events(this,document,null,null,{includeXY:true});this.documentEvents.on({mousemove:this.mousemove,mouseup:this.mouseup});OpenLayers.Element.addClass(document.body,"olDragDown");}else{var dragged=(this.start!=this.last);this.started=false;this.dragging=false;OpenLayers.Element.removeClass(this.map.viewPortDiv,"olDragDown");this.out(evt);this.callback("out",[]);if(dragged){this.callback("done",[evt.xy]);}
+if(document.onselectstart){document.onselectstart=this.oldOnselectstart;}}}
+return true;},click:function(evt){return(this.start==this.last);},activate:function(){var activated=false;if(OpenLayers.Handler.prototype.activate.apply(this,arguments)){this.dragging=false;activated=true;}
+return activated;},deactivate:function(){var deactivated=false;if(OpenLayers.Handler.prototype.deactivate.apply(this,arguments)){this.started=false;this.dragging=false;this.start=null;this.last=null;deactivated=true;OpenLayers.Element.removeClass(this.map.viewPortDiv,"olDragDown");}
+return deactivated;},adjustXY:function(evt){var pos=OpenLayers.Util.pagePosition(this.map.div);evt.xy.x-=pos[0];evt.xy.y-=pos[1];},destroyDocumentEvents:function(){OpenLayers.Element.removeClass(document.body,"olDragDown");this.documentEvents.destroy();this.documentEvents=null;},CLASS_NAME:"OpenLayers.Handler.Drag"});OpenLayers.Handler.Feature=OpenLayers.Class(OpenLayers.Handler,{EVENTMAP:{'click':{'in':'click','out':'clickout'},'mousemove':{'in':'over','out':'out'},'dblclick':{'in':'dblclick','out':null},'mousedown':{'in':null,'out':null},'mouseup':{'in':null,'out':null}},feature:null,lastFeature:null,down:null,up:null,clickTolerance:4,geometryTypes:null,stopClick:true,stopDown:true,stopUp:false,initialize:function(control,layer,callbacks,options){OpenLayers.Handler.prototype.initialize.apply(this,[control,callbacks,options]);this.layer=layer;},mousedown:function(evt){this.down=evt.xy;return this.handle(evt)?!this.stopDown:true;},mouseup:function(evt){this.up=evt.xy;return this.handle(evt)?!this.stopUp:true;},click:function(evt){return this.handle(evt)?!this.stopClick:true;},mousemove:function(evt){if(!this.callbacks['over']&&!this.callbacks['out']){return true;}
+this.handle(evt);return true;},dblclick:function(evt){return!this.handle(evt);},geometryTypeMatches:function(feature){return this.geometryTypes==null||OpenLayers.Util.indexOf(this.geometryTypes,feature.geometry.CLASS_NAME)>-1;},handle:function(evt){if(this.feature&&!this.feature.layer){this.feature=null;}
+var type=evt.type;var handled=false;var previouslyIn=!!(this.feature);var click=(type=="click"||type=="dblclick");this.feature=this.layer.getFeatureFromEvent(evt);if(this.feature&&!this.feature.layer){this.feature=null;}
+if(this.lastFeature&&!this.lastFeature.layer){this.lastFeature=null;}
+if(this.feature){var inNew=(this.feature!=this.lastFeature);if(this.geometryTypeMatches(this.feature)){if(previouslyIn&&inNew){if(this.lastFeature){this.triggerCallback(type,'out',[this.lastFeature]);}
+this.triggerCallback(type,'in',[this.feature]);}else if(!previouslyIn||click){this.triggerCallback(type,'in',[this.feature]);}
+this.lastFeature=this.feature;handled=true;}else{if(this.lastFeature&&(previouslyIn&&inNew||click)){this.triggerCallback(type,'out',[this.lastFeature]);}
+this.feature=null;}}else{if(this.lastFeature&&(previouslyIn||click)){this.triggerCallback(type,'out',[this.lastFeature]);}}
+return handled;},triggerCallback:function(type,mode,args){var key=this.EVENTMAP[type][mode];if(key){if(type=='click'&&this.up&&this.down){var dpx=Math.sqrt(Math.pow(this.up.x-this.down.x,2)+
+Math.pow(this.up.y-this.down.y,2));if(dpx<=this.clickTolerance){this.callback(key,args);}}else{this.callback(key,args);}}},activate:function(){var activated=false;if(OpenLayers.Handler.prototype.activate.apply(this,arguments)){this.moveLayerToTop();this.map.events.on({"removelayer":this.handleMapEvents,"changelayer":this.handleMapEvents,scope:this});activated=true;}
+return activated;},deactivate:function(){var deactivated=false;if(OpenLayers.Handler.prototype.deactivate.apply(this,arguments)){this.moveLayerBack();this.feature=null;this.lastFeature=null;this.down=null;this.up=null;this.map.events.un({"removelayer":this.handleMapEvents,"changelayer":this.handleMapEvents,scope:this});deactivated=true;}
+return deactivated;},handleMapEvents:function(evt){if(!evt.property||evt.property=="order"){this.moveLayerToTop();}},moveLayerToTop:function(){var index=Math.max(this.map.Z_INDEX_BASE['Feature']-1,this.layer.getZIndex())+1;this.layer.setZIndex(index);},moveLayerBack:function(){var index=this.layer.getZIndex()-1;if(index>=this.map.Z_INDEX_BASE['Feature']){this.layer.setZIndex(index);}else{this.map.setLayerZIndex(this.layer,this.map.getLayerIndex(this.layer));}},CLASS_NAME:"OpenLayers.Handler.Feature"});OpenLayers.Handler.Hover=OpenLayers.Class(OpenLayers.Handler,{delay:500,pixelTolerance:null,stopMove:false,px:null,timerId:null,initialize:function(control,callbacks,options){OpenLayers.Handler.prototype.initialize.apply(this,arguments);},mousemove:function(evt){if(this.passesTolerance(evt.xy)){this.clearTimer();this.callback('move',[evt]);this.px=evt.xy;evt=OpenLayers.Util.extend({},evt);this.timerId=window.setTimeout(OpenLayers.Function.bind(this.delayedCall,this,evt),this.delay);}
+return!this.stopMove;},mouseout:function(evt){if(OpenLayers.Util.mouseLeft(evt,this.map.div)){this.clearTimer();this.callback('move',[evt]);}
+return true;},passesTolerance:function(px){var passes=true;if(this.pixelTolerance&&this.px){var dpx=Math.sqrt(Math.pow(this.px.x-px.x,2)+
+Math.pow(this.px.y-px.y,2));if(dpx<this.pixelTolerance){passes=false;}}
+return passes;},clearTimer:function(){if(this.timerId!=null){window.clearTimeout(this.timerId);this.timerId=null;}},delayedCall:function(evt){this.callback('pause',[evt]);},deactivate:function(){var deactivated=false;if(OpenLayers.Handler.prototype.deactivate.apply(this,arguments)){this.clearTimer();deactivated=true;}
+return deactivated;},CLASS_NAME:"OpenLayers.Handler.Hover"});OpenLayers.Handler.Keyboard=OpenLayers.Class(OpenLayers.Handler,{KEY_EVENTS:["keydown","keyup"],eventListener:null,initialize:function(control,callbacks,options){OpenLayers.Handler.prototype.initialize.apply(this,arguments);this.eventListener=OpenLayers.Function.bindAsEventListener(this.handleKeyEvent,this);},destroy:function(){this.deactivate();this.eventListener=null;OpenLayers.Handler.prototype.destroy.apply(this,arguments);},activate:function(){if(OpenLayers.Handler.prototype.activate.apply(this,arguments)){for(var i=0,len=this.KEY_EVENTS.length;i<len;i++){OpenLayers.Event.observe(document,this.KEY_EVENTS[i],this.eventListener);}
+return true;}else{return false;}},deactivate:function(){var deactivated=false;if(OpenLayers.Handler.prototype.deactivate.apply(this,arguments)){for(var i=0,len=this.KEY_EVENTS.length;i<len;i++){OpenLayers.Event.stopObserving(document,this.KEY_EVENTS[i],this.eventListener);}
+deactivated=true;}
+return deactivated;},handleKeyEvent:function(evt){if(this.checkModifiers(evt)){this.callback(evt.type,[evt]);}},CLASS_NAME:"OpenLayers.Handler.Keyboard"});OpenLayers.Handler.MouseWheel=OpenLayers.Class(OpenLayers.Handler,{wheelListener:null,mousePosition:null,interval:0,delta:0,cumulative:true,initialize:function(control,callbacks,options){OpenLayers.Handler.prototype.initialize.apply(this,arguments);this.wheelListener=OpenLayers.Function.bindAsEventListener(this.onWheelEvent,this);},destroy:function(){OpenLayers.Handler.prototype.destroy.apply(this,arguments);this.wheelListener=null;},onWheelEvent:function(e){if(!this.map||!this.checkModifiers(e)){return;}
+var overScrollableDiv=false;var overLayerDiv=false;var overMapDiv=false;var elem=OpenLayers.Event.element(e);while((elem!=null)&&!overMapDiv&&!overScrollableDiv){if(!overScrollableDiv){try{if(elem.currentStyle){overflow=elem.currentStyle["overflow"];}else{var style=document.defaultView.getComputedStyle(elem,null);var overflow=style.getPropertyValue("overflow");}
+overScrollableDiv=(overflow&&(overflow=="auto")||(overflow=="scroll"));}catch(err){}}
+if(!overLayerDiv){for(var i=0,len=this.map.layers.length;i<len;i++){if(elem==this.map.layers[i].div||elem==this.map.layers[i].pane){overLayerDiv=true;break;}}}
+overMapDiv=(elem==this.map.div);elem=elem.parentNode;}
+if(!overScrollableDiv&&overMapDiv){if(overLayerDiv){var delta=0;if(!e){e=window.event;}
+if(e.wheelDelta){delta=e.wheelDelta/120;if(window.opera&&window.opera.version()<9.2){delta=-delta;}}else if(e.detail){delta=-e.detail/3;}
+this.delta=this.delta+delta;if(this.interval){window.clearTimeout(this._timeoutId);this._timeoutId=window.setTimeout(OpenLayers.Function.bind(function(){this.wheelZoom(e);},this),this.interval);}else{this.wheelZoom(e);}}
+OpenLayers.Event.stop(e);}},wheelZoom:function(e){var delta=this.delta;this.delta=0;if(delta){if(this.mousePosition){e.xy=this.mousePosition;}
+if(!e.xy){e.xy=this.map.getPixelFromLonLat(this.map.getCenter());}
+if(delta<0){this.callback("down",[e,this.cumulative?delta:-1]);}else{this.callback("up",[e,this.cumulative?delta:1]);}}},mousemove:function(evt){this.mousePosition=evt.xy;},activate:function(evt){if(OpenLayers.Handler.prototype.activate.apply(this,arguments)){var wheelListener=this.wheelListener;OpenLayers.Event.observe(window,"DOMMouseScroll",wheelListener);OpenLayers.Event.observe(window,"mousewheel",wheelListener);OpenLayers.Event.observe(document,"mousewheel",wheelListener);return true;}else{return false;}},deactivate:function(evt){if(OpenLayers.Handler.prototype.deactivate.apply(this,arguments)){var wheelListener=this.wheelListener;OpenLayers.Event.stopObserving(window,"DOMMouseScroll",wheelListener);OpenLayers.Event.stopObserving(window,"mousewheel",wheelListener);OpenLayers.Event.stopObserving(document,"mousewheel",wheelListener);return true;}else{return false;}},CLASS_NAME:"OpenLayers.Handler.MouseWheel"});OpenLayers.Layer=OpenLayers.Class({id:null,name:null,div:null,opacity:null,alwaysInRange:null,EVENT_TYPES:["loadstart","loadend","loadcancel","visibilitychanged","move","moveend"],RESOLUTION_PROPERTIES:['scales','resolutions','maxScale','minScale','maxResolution','minResolution','numZoomLevels','maxZoomLevel'],events:null,map:null,isBaseLayer:false,alpha:false,displayInLayerSwitcher:true,visibility:true,attribution:null,inRange:false,imageSize:null,imageOffset:null,options:null,eventListeners:null,gutter:0,projection:null,units:null,scales:null,resolutions:null,maxExtent:null,minExtent:null,maxResolution:null,minResolution:null,numZoomLevels:null,minScale:null,maxScale:null,displayOutsideMaxExtent:false,wrapDateLine:false,transitionEffect:null,SUPPORTED_TRANSITIONS:['resize'],metadata:{},initialize:function(name,options){this.addOptions(options);this.name=name;if(this.id==null){this.id=OpenLayers.Util.createUniqueID(this.CLASS_NAME+"_");this.div=OpenLayers.Util.createDiv(this.id);this.div.style.width="100%";this.div.style.height="100%";this.div.dir="ltr";this.events=new OpenLayers.Events(this,this.div,this.EVENT_TYPES);if(this.eventListeners instanceof Object){this.events.on(this.eventListeners);}}
+if(this.wrapDateLine){this.displayOutsideMaxExtent=true;}},destroy:function(setNewBaseLayer){if(setNewBaseLayer==null){setNewBaseLayer=true;}
+if(this.map!=null){this.map.removeLayer(this,setNewBaseLayer);}
+this.projection=null;this.map=null;this.name=null;this.div=null;this.options=null;if(this.events){if(this.eventListeners){this.events.un(this.eventListeners);}
+this.events.destroy();}
+this.eventListeners=null;this.events=null;},clone:function(obj){if(obj==null){obj=new OpenLayers.Layer(this.name,this.getOptions());}
+OpenLayers.Util.applyDefaults(obj,this);obj.map=null;return obj;},getOptions:function(){var options={};for(var o in this.options){options[o]=this[o];}
+return options;},setName:function(newName){if(newName!=this.name){this.name=newName;if(this.map!=null){this.map.events.triggerEvent("changelayer",{layer:this,property:"name"});}}},addOptions:function(newOptions){if(this.options==null){this.options={};}
+OpenLayers.Util.extend(this.options,newOptions);OpenLayers.Util.extend(this,newOptions);if(typeof this.projection=="string"){this.projection=new OpenLayers.Projection(this.projection);}
+if(this.projection&&this.projection.getUnits()){this.units=this.projection.getUnits();}
+if(this.map){var properties=this.RESOLUTION_PROPERTIES.concat(["projection","units","minExtent","maxExtent"]);for(var o in newOptions){if(newOptions.hasOwnProperty(o)&&OpenLayers.Util.indexOf(properties,o)>=0){this.initResolutions();break;}}}},onMapResize:function(){},redraw:function(){var redrawn=false;if(this.map){this.inRange=this.calculateInRange();var extent=this.getExtent();if(extent&&this.inRange&&this.visibility){var zoomChanged=true;this.moveTo(extent,zoomChanged,false);this.events.triggerEvent("moveend",{"zoomChanged":zoomChanged});redrawn=true;}}
+return redrawn;},moveTo:function(bounds,zoomChanged,dragging){var display=this.visibility;if(!this.isBaseLayer){display=display&&this.inRange;}
+this.display(display);},setMap:function(map){if(this.map==null){this.map=map;this.maxExtent=this.maxExtent||this.map.maxExtent;this.minExtent=this.minExtent||this.map.minExtent;this.projection=this.projection||this.map.projection;if(typeof this.projection=="string"){this.projection=new OpenLayers.Projection(this.projection);}
+this.units=this.projection.getUnits()||this.units||this.map.units;this.initResolutions();if(!this.isBaseLayer){this.inRange=this.calculateInRange();var show=((this.visibility)&&(this.inRange));this.div.style.display=show?"":"none";}
+this.setTileSize();}},afterAdd:function(){},removeMap:function(map){},getImageSize:function(bounds){return(this.imageSize||this.tileSize);},setTileSize:function(size){var tileSize=(size)?size:((this.tileSize)?this.tileSize:this.map.getTileSize());this.tileSize=tileSize;if(this.gutter){this.imageOffset=new OpenLayers.Pixel(-this.gutter,-this.gutter);this.imageSize=new OpenLayers.Size(tileSize.w+(2*this.gutter),tileSize.h+(2*this.gutter));}},getVisibility:function(){return this.visibility;},setVisibility:function(visibility){if(visibility!=this.visibility){this.visibility=visibility;this.display(visibility);this.redraw();if(this.map!=null){this.map.events.triggerEvent("changelayer",{layer:this,property:"visibility"});}
+this.events.triggerEvent("visibilitychanged");}},display:function(display){if(display!=(this.div.style.display!="none")){this.div.style.display=(display&&this.calculateInRange())?"block":"none";}},calculateInRange:function(){var inRange=false;if(this.alwaysInRange){inRange=true;}else{if(this.map){var resolution=this.map.getResolution();inRange=((resolution>=this.minResolution)&&(resolution<=this.maxResolution));}}
+return inRange;},setIsBaseLayer:function(isBaseLayer){if(isBaseLayer!=this.isBaseLayer){this.isBaseLayer=isBaseLayer;if(this.map!=null){this.map.events.triggerEvent("changebaselayer",{layer:this});}}},initResolutions:function(){var i,len;var props={},alwaysInRange=true;for(i=0,len=this.RESOLUTION_PROPERTIES.length;i<len;i++){var p=this.RESOLUTION_PROPERTIES[i];props[p]=this.options[p];if(alwaysInRange&&this.options[p]){alwaysInRange=false;}}
+if(this.alwaysInRange==null){this.alwaysInRange=alwaysInRange;}
+if(props.resolutions==null){props.resolutions=this.resolutionsFromScales(props.scales);}
+if(props.resolutions==null){props.resolutions=this.calculateResolutions(props);}
+if(props.resolutions==null){for(i=0,len=this.RESOLUTION_PROPERTIES.length;i<len;i++){var p=this.RESOLUTION_PROPERTIES[i];props[p]=this.options[p]!=null?this.options[p]:this.map[p];}
+if(props.resolutions==null){props.resolutions=this.resolutionsFromScales(props.scales);}
+if(props.resolutions==null){props.resolutions=this.calculateResolutions(props);}}
+var maxResolution;if(this.options.maxResolution&&this.options.maxResolution!=="auto"){maxResolution=this.options.maxResolution;}
+if(this.options.minScale){maxResolution=OpenLayers.Util.getResolutionFromScale(this.options.minScale,this.units);}
+var minResolution;if(this.options.minResolution&&this.options.minResolution!=="auto"){minResolution=this.options.minResolution;}
+if(this.options.maxScale){minResolution=OpenLayers.Util.getResolutionFromScale(this.options.maxScale,this.units);}
+if(props.resolutions){props.resolutions.sort(function(a,b){return(b-a);});if(!maxResolution){maxResolution=props.resolutions[0];}
+if(!minResolution){var lastIdx=props.resolutions.length-1;minResolution=props.resolutions[lastIdx];}}
+this.resolutions=props.resolutions;if(this.resolutions){len=this.resolutions.length;this.scales=new Array(len);for(i=0;i<len;i++){this.scales[i]=OpenLayers.Util.getScaleFromResolution(this.resolutions[i],this.units);}
+this.numZoomLevels=len;}
+this.minResolution=minResolution;if(minResolution){this.maxScale=OpenLayers.Util.getScaleFromResolution(minResolution,this.units);}
+this.maxResolution=maxResolution;if(maxResolution){this.minScale=OpenLayers.Util.getScaleFromResolution(maxResolution,this.units);}},resolutionsFromScales:function(scales){if(scales==null){return;}
+var resolutions,i,len;len=scales.length;resolutions=new Array(len);for(i=0;i<len;i++){resolutions[i]=OpenLayers.Util.getResolutionFromScale(scales[i],this.units);}
+return resolutions;},calculateResolutions:function(props){var maxResolution=props.maxResolution;if(props.minScale!=null){maxResolution=OpenLayers.Util.getResolutionFromScale(props.minScale,this.units);}else if(maxResolution=="auto"&&this.maxExtent!=null){var viewSize=this.map.getSize();var wRes=this.maxExtent.getWidth()/viewSize.w;var hRes=this.maxExtent.getHeight()/viewSize.h;maxResolution=Math.max(wRes,hRes);}
+var minResolution=props.minResolution;if(props.maxScale!=null){minResolution=OpenLayers.Util.getResolutionFromScale(props.maxScale,this.units);}else if(props.minResolution=="auto"&&this.minExtent!=null){var viewSize=this.map.getSize();var wRes=this.minExtent.getWidth()/viewSize.w;var hRes=this.minExtent.getHeight()/viewSize.h;minResolution=Math.max(wRes,hRes);}
+var maxZoomLevel=props.maxZoomLevel;var numZoomLevels=props.numZoomLevels;if(typeof minResolution==="number"&&typeof maxResolution==="number"&&numZoomLevels===undefined){var ratio=maxResolution/minResolution;numZoomLevels=Math.floor(Math.log(ratio)/Math.log(2))+1;}else if(numZoomLevels===undefined&&maxZoomLevel!=null){numZoomLevels=maxZoomLevel+1;}
+if(typeof numZoomLevels!=="number"||numZoomLevels<=0||(typeof maxResolution!=="number"&&typeof minResolution!=="number")){return;}
+var resolutions=new Array(numZoomLevels);var base=2;if(typeof minResolution=="number"&&typeof maxResolution=="number"){base=Math.pow((maxResolution/minResolution),(1/(numZoomLevels-1)));}
+var i;if(typeof maxResolution==="number"){for(i=0;i<numZoomLevels;i++){resolutions[i]=maxResolution/Math.pow(base,i);}}else{for(i=0;i<numZoomLevels;i++){resolutions[numZoomLevels-1-i]=minResolution*Math.pow(base,i);}}
+return resolutions;},getResolution:function(){var zoom=this.map.getZoom();return this.getResolutionForZoom(zoom);},getExtent:function(){return this.map.calculateBounds();},getZoomForExtent:function(extent,closest){var viewSize=this.map.getSize();var idealResolution=Math.max(extent.getWidth()/viewSize.w,extent.getHeight()/viewSize.h);return this.getZoomForResolution(idealResolution,closest);},getDataExtent:function(){},getResolutionForZoom:function(zoom){zoom=Math.max(0,Math.min(zoom,this.resolutions.length-1));var resolution;if(this.map.fractionalZoom){var low=Math.floor(zoom);var high=Math.ceil(zoom);resolution=this.resolutions[low]-
+((zoom-low)*(this.resolutions[low]-this.resolutions[high]));}else{resolution=this.resolutions[Math.round(zoom)];}
+return resolution;},getZoomForResolution:function(resolution,closest){var zoom;if(this.map.fractionalZoom){var lowZoom=0;var highZoom=this.resolutions.length-1;var highRes=this.resolutions[lowZoom];var lowRes=this.resolutions[highZoom];var res;for(var i=0,len=this.resolutions.length;i<len;++i){res=this.resolutions[i];if(res>=resolution){highRes=res;lowZoom=i;}
+if(res<=resolution){lowRes=res;highZoom=i;break;}}
+var dRes=highRes-lowRes;if(dRes>0){zoom=lowZoom+((highRes-resolution)/dRes);}else{zoom=lowZoom;}}else{var diff;var minDiff=Number.POSITIVE_INFINITY;for(var i=0,len=this.resolutions.length;i<len;i++){if(closest){diff=Math.abs(this.resolutions[i]-resolution);if(diff>minDiff){break;}
+minDiff=diff;}else{if(this.resolutions[i]<resolution){break;}}}
+zoom=Math.max(0,i-1);}
+return zoom;},getLonLatFromViewPortPx:function(viewPortPx){var lonlat=null;if(viewPortPx!=null){var size=this.map.getSize();var center=this.map.getCenter();if(center){var res=this.map.getResolution();var delta_x=viewPortPx.x-(size.w/2);var delta_y=viewPortPx.y-(size.h/2);lonlat=new OpenLayers.LonLat(center.lon+delta_x*res,center.lat-delta_y*res);if(this.wrapDateLine){lonlat=lonlat.wrapDateLine(this.maxExtent);}}}
+return lonlat;},getViewPortPxFromLonLat:function(lonlat){var px=null;if(lonlat!=null){var resolution=this.map.getResolution();var extent=this.map.getExtent();px=new OpenLayers.Pixel((1/resolution*(lonlat.lon-extent.left)),(1/resolution*(extent.top-lonlat.lat)));}
+return px;},setOpacity:function(opacity){if(opacity!=this.opacity){this.opacity=opacity;for(var i=0,len=this.div.childNodes.length;i<len;++i){var element=this.div.childNodes[i].firstChild;OpenLayers.Util.modifyDOMElement(element,null,null,null,null,null,null,opacity);}
+if(this.map!=null){this.map.events.triggerEvent("changelayer",{layer:this,property:"opacity"});}}},getZIndex:function(){return this.div.style.zIndex;},setZIndex:function(zIndex){this.div.style.zIndex=zIndex;},adjustBounds:function(bounds){if(this.gutter){var mapGutter=this.gutter*this.map.getResolution();bounds=new OpenLayers.Bounds(bounds.left-mapGutter,bounds.bottom-mapGutter,bounds.right+mapGutter,bounds.top+mapGutter);}
+if(this.wrapDateLine){var wrappingOptions={'rightTolerance':this.getResolution()};bounds=bounds.wrapDateLine(this.maxExtent,wrappingOptions);}
+return bounds;},CLASS_NAME:"OpenLayers.Layer"});OpenLayers.Marker.Box=OpenLayers.Class(OpenLayers.Marker,{bounds:null,div:null,initialize:function(bounds,borderColor,borderWidth){this.bounds=bounds;this.div=OpenLayers.Util.createDiv();this.div.style.overflow='hidden';this.events=new OpenLayers.Events(this,this.div,null);this.setBorder(borderColor,borderWidth);},destroy:function(){this.bounds=null;this.div=null;OpenLayers.Marker.prototype.destroy.apply(this,arguments);},setBorder:function(color,width){if(!color){color="red";}
+if(!width){width=2;}
+this.div.style.border=width+"px solid "+color;},draw:function(px,sz){OpenLayers.Util.modifyDOMElement(this.div,null,px,sz);return this.div;},onScreen:function(){var onScreen=false;if(this.map){var screenBounds=this.map.getExtent();onScreen=screenBounds.containsBounds(this.bounds,true,true);}
+return onScreen;},display:function(display){this.div.style.display=(display)?"":"none";},CLASS_NAME:"OpenLayers.Marker.Box"});(function(){var oXMLHttpRequest=window.XMLHttpRequest;var bGecko=!!window.controllers,bIE=window.document.all&&!window.opera,bIE7=bIE&&window.navigator.userAgent.match(/MSIE ([\.0-9]+)/)&&RegExp.$1==7;function cXMLHttpRequest(){this._object=oXMLHttpRequest&&!bIE7?new oXMLHttpRequest:new window.ActiveXObject("Microsoft.XMLHTTP");this._listeners=[];};if(bGecko&&oXMLHttpRequest.wrapped)
+cXMLHttpRequest.wrapped=oXMLHttpRequest.wrapped;cXMLHttpRequest.UNSENT=0;cXMLHttpRequest.OPENED=1;cXMLHttpRequest.HEADERS_RECEIVED=2;cXMLHttpRequest.LOADING=3;cXMLHttpRequest.DONE=4;cXMLHttpRequest.prototype.readyState=cXMLHttpRequest.UNSENT;cXMLHttpRequest.prototype.responseText='';cXMLHttpRequest.prototype.responseXML=null;cXMLHttpRequest.prototype.status=0;cXMLHttpRequest.prototype.statusText='';cXMLHttpRequest.prototype.onreadystatechange=null;cXMLHttpRequest.onreadystatechange=null;cXMLHttpRequest.onopen=null;cXMLHttpRequest.onsend=null;cXMLHttpRequest.onabort=null;cXMLHttpRequest.prototype.open=function(sMethod,sUrl,bAsync,sUser,sPassword){delete this._headers;if(arguments.length<3)
+bAsync=true;this._async=bAsync;var oRequest=this,nState=this.readyState,fOnUnload;if(bIE&&bAsync){fOnUnload=function(){if(nState!=cXMLHttpRequest.DONE){fCleanTransport(oRequest);oRequest.abort();}};window.attachEvent("onunload",fOnUnload);}
+if(cXMLHttpRequest.onopen)
+cXMLHttpRequest.onopen.apply(this,arguments);if(arguments.length>4)
+this._object.open(sMethod,sUrl,bAsync,sUser,sPassword);else
+if(arguments.length>3)
+this._object.open(sMethod,sUrl,bAsync,sUser);else
+this._object.open(sMethod,sUrl,bAsync);if(!bGecko&&!bIE){this.readyState=cXMLHttpRequest.OPENED;fReadyStateChange(this);}
+this._object.onreadystatechange=function(){if(bGecko&&!bAsync)
+return;oRequest.readyState=oRequest._object.readyState;fSynchronizeValues(oRequest);if(oRequest._aborted){oRequest.readyState=cXMLHttpRequest.UNSENT;return;}
+if(oRequest.readyState==cXMLHttpRequest.DONE){fCleanTransport(oRequest);if(bIE&&bAsync)
+window.detachEvent("onunload",fOnUnload);}
+if(nState!=oRequest.readyState)
+fReadyStateChange(oRequest);nState=oRequest.readyState;}};cXMLHttpRequest.prototype.send=function(vData){if(cXMLHttpRequest.onsend)
+cXMLHttpRequest.onsend.apply(this,arguments);if(vData&&vData.nodeType){vData=window.XMLSerializer?new window.XMLSerializer().serializeToString(vData):vData.xml;if(!this._headers["Content-Type"])
+this._object.setRequestHeader("Content-Type","application/xml");}
+this._object.send(vData);if(bGecko&&!this._async){this.readyState=cXMLHttpRequest.OPENED;fSynchronizeValues(this);while(this.readyState<cXMLHttpRequest.DONE){this.readyState++;fReadyStateChange(this);if(this._aborted)
+return;}}};cXMLHttpRequest.prototype.abort=function(){if(cXMLHttpRequest.onabort)
+cXMLHttpRequest.onabort.apply(this,arguments);if(this.readyState>cXMLHttpRequest.UNSENT)
+this._aborted=true;this._object.abort();fCleanTransport(this);};cXMLHttpRequest.prototype.getAllResponseHeaders=function(){return this._object.getAllResponseHeaders();};cXMLHttpRequest.prototype.getResponseHeader=function(sName){return this._object.getResponseHeader(sName);};cXMLHttpRequest.prototype.setRequestHeader=function(sName,sValue){if(!this._headers)
+this._headers={};this._headers[sName]=sValue;return this._object.setRequestHeader(sName,sValue);};cXMLHttpRequest.prototype.addEventListener=function(sName,fHandler,bUseCapture){for(var nIndex=0,oListener;oListener=this._listeners[nIndex];nIndex++)
+if(oListener[0]==sName&&oListener[1]==fHandler&&oListener[2]==bUseCapture)
+return;this._listeners.push([sName,fHandler,bUseCapture]);};cXMLHttpRequest.prototype.removeEventListener=function(sName,fHandler,bUseCapture){for(var nIndex=0,oListener;oListener=this._listeners[nIndex];nIndex++)
+if(oListener[0]==sName&&oListener[1]==fHandler&&oListener[2]==bUseCapture)
+break;if(oListener)
+this._listeners.splice(nIndex,1);};cXMLHttpRequest.prototype.dispatchEvent=function(oEvent){var oEventPseudo={'type':oEvent.type,'target':this,'currentTarget':this,'eventPhase':2,'bubbles':oEvent.bubbles,'cancelable':oEvent.cancelable,'timeStamp':oEvent.timeStamp,'stopPropagation':function(){},'preventDefault':function(){},'initEvent':function(){}};if(oEventPseudo.type=="readystatechange"&&this.onreadystatechange)
+(this.onreadystatechange.handleEvent||this.onreadystatechange).apply(this,[oEventPseudo]);for(var nIndex=0,oListener;oListener=this._listeners[nIndex];nIndex++)
+if(oListener[0]==oEventPseudo.type&&!oListener[2])
+(oListener[1].handleEvent||oListener[1]).apply(this,[oEventPseudo]);};cXMLHttpRequest.prototype.toString=function(){return'['+"object"+' '+"XMLHttpRequest"+']';};cXMLHttpRequest.toString=function(){return'['+"XMLHttpRequest"+']';};function fReadyStateChange(oRequest){if(cXMLHttpRequest.onreadystatechange)
+cXMLHttpRequest.onreadystatechange.apply(oRequest);oRequest.dispatchEvent({'type':"readystatechange",'bubbles':false,'cancelable':false,'timeStamp':new Date+0});};function fGetDocument(oRequest){var oDocument=oRequest.responseXML,sResponse=oRequest.responseText;if(bIE&&sResponse&&oDocument&&!oDocument.documentElement&&oRequest.getResponseHeader("Content-Type").match(/[^\/]+\/[^\+]+\+xml/)){oDocument=new window.ActiveXObject("Microsoft.XMLDOM");oDocument.async=false;oDocument.validateOnParse=false;oDocument.loadXML(sResponse);}
+if(oDocument)
+if((bIE&&oDocument.parseError!=0)||!oDocument.documentElement||(oDocument.documentElement&&oDocument.documentElement.tagName=="parsererror"))
+return null;return oDocument;};function fSynchronizeValues(oRequest){try{oRequest.responseText=oRequest._object.responseText;}catch(e){}
+try{oRequest.responseXML=fGetDocument(oRequest._object);}catch(e){}
+try{oRequest.status=oRequest._object.status;}catch(e){}
+try{oRequest.statusText=oRequest._object.statusText;}catch(e){}};function fCleanTransport(oRequest){oRequest._object.onreadystatechange=new window.Function;};if(!window.Function.prototype.apply){window.Function.prototype.apply=function(oRequest,oArguments){if(!oArguments)
+oArguments=[];oRequest.__func=this;oRequest.__func(oArguments[0],oArguments[1],oArguments[2],oArguments[3],oArguments[4]);delete oRequest.__func;};};OpenLayers.Request.XMLHttpRequest=cXMLHttpRequest;})();OpenLayers.Tile.Image.IFrame=OpenLayers.Class(OpenLayers.Tile.Image,{initialize:function(layer,position,bounds,url,size){OpenLayers.Tile.Image.prototype.initialize.apply(this,arguments);this.layerAlphaHack=false;},destroy:function(){if(this.imgDiv!=null){OpenLayers.Event.stopObservingElement(this.imgDiv.firstChild);}
+OpenLayers.Tile.Image.prototype.destroy.apply(this,arguments);},clear:function(){if(this.imgDiv){var iFrame=this.imgDiv.firstChild;OpenLayers.Event.stopObservingElement(iFrame);this.imgDiv.removeChild(iFrame);}},clone:function(obj){if(obj==null){obj=new OpenLayers.Tile.Image.IFrame(this.layer,this.position,this.bounds,this.url,this.size);}
+obj=OpenLayers.Tile.Image.prototype.clone.apply(this,[obj]);return obj;},renderTile:function(){if(OpenLayers.Tile.Image.prototype.renderTile.apply(this,arguments)){var form=this.createRequestForm();this.imgDiv.appendChild(form);form.submit();this.imgDiv.removeChild(form);}},initImgDiv:function(){this.imgDiv=this.createImgDiv();OpenLayers.Util.modifyDOMElement(this.imgDiv,this.id,null,this.layer.getImageSize(),"relative");this.imgDiv.className='olTileImage';this.frame.appendChild(this.imgDiv);this.layer.div.appendChild(this.frame);if(this.layer.opacity!=null){OpenLayers.Util.modifyDOMElement(this.imgDiv,null,null,null,null,null,null,this.layer.opacity);}
+this.imgDiv.map=this.layer.map;},createImgDiv:function(){var eventPane=document.createElement("div");if(OpenLayers.Util.getBrowserName()=="msie"){eventPane.style.backgroundColor='#FFFFFF';eventPane.style.filter='chroma(color=#FFFFFF)';}
+OpenLayers.Util.modifyDOMElement(eventPane,null,new OpenLayers.Pixel(0,0),this.layer.getImageSize(),"absolute");var imgDiv=document.createElement("div");imgDiv.appendChild(eventPane);return imgDiv;},createIFrame:function(){var id=this.id+'_iFrame';var iframe;if(OpenLayers.Util.getBrowserName()=="msie"){iframe=document.createElement('<iframe name="'+id+'">');iframe.style.backgroundColor='#FFFFFF';iframe.style.filter='chroma(color=#FFFFFF)';}
+else{iframe=document.createElement('iframe');iframe.style.backgroundColor='transparent';iframe.name=id;}
+iframe.id=id;iframe.scrolling='no';iframe.marginWidth='0px';iframe.marginHeight='0px';iframe.frameBorder='0';OpenLayers.Util.modifyDOMElement(iframe,id,new OpenLayers.Pixel(0,0),this.layer.getImageSize(),"absolute");var onload=function(){this.show();if(this.isLoading){this.isLoading=false;this.events.triggerEvent("loadend");}};OpenLayers.Event.observe(iframe,'load',OpenLayers.Function.bind(onload,this));return iframe;},createRequestForm:function(){var form=document.createElement('form');form.method='POST';var cacheId=this.layer.params["_OLSALT"];cacheId=(cacheId?cacheId+"_":"")+this.bounds.toBBOX();form.action=OpenLayers.Util.urlAppend(this.layer.url,cacheId);this.imgDiv.insertBefore(this.createIFrame(),this.imgDiv.firstChild);form.target=this.id+'_iFrame';var imageSize=this.layer.getImageSize();var params=OpenLayers.Util.extend({"BBOX":this.encodeBBOX?this.bounds.toBBOX():this.bounds.toArray(),"WIDTH":imageSize.w,"HEIGHT":imageSize.h},this.layer.params);for(var par in params){var field=document.createElement('input');field.type='hidden';field.name=par;field.value=params[par];form.appendChild(field);}
+return form;},CLASS_NAME:"OpenLayers.Tile.Image.IFrame"});OpenLayers.ProxyHost="";OpenLayers.nullHandler=function(request){OpenLayers.Console.userError(OpenLayers.i18n("unhandledRequest",{'statusText':request.statusText}));};OpenLayers.loadURL=function(uri,params,caller,onComplete,onFailure){if(typeof params=='string'){params=OpenLayers.Util.getParameters(params);}
+var success=(onComplete)?onComplete:OpenLayers.nullHandler;var failure=(onFailure)?onFailure:OpenLayers.nullHandler;return OpenLayers.Request.GET({url:uri,params:params,success:success,failure:failure,scope:caller});};OpenLayers.parseXMLString=function(text){var index=text.indexOf('<');if(index>0){text=text.substring(index);}
+var ajaxResponse=OpenLayers.Util.Try(function(){var xmldom=new ActiveXObject('Microsoft.XMLDOM');xmldom.loadXML(text);return xmldom;},function(){return new DOMParser().parseFromString(text,'text/xml');},function(){var req=new XMLHttpRequest();req.open("GET","data:"+"text/xml"+";charset=utf-8,"+encodeURIComponent(text),false);if(req.overrideMimeType){req.overrideMimeType("text/xml");}
+req.send(null);return req.responseXML;});return ajaxResponse;};OpenLayers.Ajax={emptyFunction:function(){},getTransport:function(){return OpenLayers.Util.Try(function(){return new XMLHttpRequest();},function(){return new ActiveXObject('Msxml2.XMLHTTP');},function(){return new ActiveXObject('Microsoft.XMLHTTP');})||false;},activeRequestCount:0};OpenLayers.Ajax.Responders={responders:[],register:function(responderToAdd){for(var i=0;i<this.responders.length;i++){if(responderToAdd==this.responders[i]){return;}}
+this.responders.push(responderToAdd);},unregister:function(responderToRemove){OpenLayers.Util.removeItem(this.reponders,responderToRemove);},dispatch:function(callback,request,transport){var responder;for(var i=0;i<this.responders.length;i++){responder=this.responders[i];if(responder[callback]&&typeof responder[callback]=='function'){try{responder[callback].apply(responder,[request,transport]);}catch(e){}}}}};OpenLayers.Ajax.Responders.register({onCreate:function(){OpenLayers.Ajax.activeRequestCount++;},onComplete:function(){OpenLayers.Ajax.activeRequestCount--;}});OpenLayers.Ajax.Base=OpenLayers.Class({initialize:function(options){this.options={method:'post',asynchronous:true,contentType:'application/xml',parameters:''};OpenLayers.Util.extend(this.options,options||{});this.options.method=this.options.method.toLowerCase();if(typeof this.options.parameters=='string'){this.options.parameters=OpenLayers.Util.getParameters(this.options.parameters);}}});OpenLayers.Ajax.Request=OpenLayers.Class(OpenLayers.Ajax.Base,{_complete:false,initialize:function(url,options){OpenLayers.Ajax.Base.prototype.initialize.apply(this,[options]);if(OpenLayers.ProxyHost&&OpenLayers.String.startsWith(url,"http")){url=OpenLayers.ProxyHost+encodeURIComponent(url);}
+this.transport=OpenLayers.Ajax.getTransport();this.request(url);},request:function(url){this.url=url;this.method=this.options.method;var params=OpenLayers.Util.extend({},this.options.parameters);if(this.method!='get'&&this.method!='post'){params['_method']=this.method;this.method='post';}
+this.parameters=params;if(params=OpenLayers.Util.getParameterString(params)){if(this.method=='get'){this.url+=((this.url.indexOf('?')>-1)?'&':'?')+params;}else if(/Konqueror|Safari|KHTML/.test(navigator.userAgent)){params+='&_=';}}
+try{var response=new OpenLayers.Ajax.Response(this);if(this.options.onCreate){this.options.onCreate(response);}
+OpenLayers.Ajax.Responders.dispatch('onCreate',this,response);this.transport.open(this.method.toUpperCase(),this.url,this.options.asynchronous);if(this.options.asynchronous){window.setTimeout(OpenLayers.Function.bind(this.respondToReadyState,this,1),10);}
+this.transport.onreadystatechange=OpenLayers.Function.bind(this.onStateChange,this);this.setRequestHeaders();this.body=this.method=='post'?(this.options.postBody||params):null;this.transport.send(this.body);if(!this.options.asynchronous&&this.transport.overrideMimeType){this.onStateChange();}}catch(e){this.dispatchException(e);}},onStateChange:function(){var readyState=this.transport.readyState;if(readyState>1&&!((readyState==4)&&this._complete)){this.respondToReadyState(this.transport.readyState);}},setRequestHeaders:function(){var headers={'X-Requested-With':'XMLHttpRequest','Accept':'text/javascript, text/html, application/xml, text/xml, */*','OpenLayers':true};if(this.method=='post'){headers['Content-type']=this.options.contentType+
+(this.options.encoding?'; charset='+this.options.encoding:'');if(this.transport.overrideMimeType&&(navigator.userAgent.match(/Gecko\/(\d{4})/)||[0,2005])[1]<2005){headers['Connection']='close';}}
+if(typeof this.options.requestHeaders=='object'){var extras=this.options.requestHeaders;if(typeof extras.push=='function'){for(var i=0,length=extras.length;i<length;i+=2){headers[extras[i]]=extras[i+1];}}else{for(var i in extras){headers[i]=extras[i];}}}
+for(var name in headers){this.transport.setRequestHeader(name,headers[name]);}},success:function(){var status=this.getStatus();return!status||(status>=200&&status<300);},getStatus:function(){try{return this.transport.status||0;}catch(e){return 0;}},respondToReadyState:function(readyState){var state=OpenLayers.Ajax.Request.Events[readyState];var response=new OpenLayers.Ajax.Response(this);if(state=='Complete'){try{this._complete=true;(this.options['on'+response.status]||this.options['on'+(this.success()?'Success':'Failure')]||OpenLayers.Ajax.emptyFunction)(response);}catch(e){this.dispatchException(e);}
+var contentType=response.getHeader('Content-type');}
+try{(this.options['on'+state]||OpenLayers.Ajax.emptyFunction)(response);OpenLayers.Ajax.Responders.dispatch('on'+state,this,response);}catch(e){this.dispatchException(e);}
+if(state=='Complete'){this.transport.onreadystatechange=OpenLayers.Ajax.emptyFunction;}},getHeader:function(name){try{return this.transport.getResponseHeader(name);}catch(e){return null;}},dispatchException:function(exception){var handler=this.options.onException;if(handler){handler(this,exception);OpenLayers.Ajax.Responders.dispatch('onException',this,exception);}else{var listener=false;var responders=OpenLayers.Ajax.Responders.responders;for(var i=0;i<responders.length;i++){if(responders[i].onException){listener=true;break;}}
+if(listener){OpenLayers.Ajax.Responders.dispatch('onException',this,exception);}else{throw exception;}}}});OpenLayers.Ajax.Request.Events=['Uninitialized','Loading','Loaded','Interactive','Complete'];OpenLayers.Ajax.Response=OpenLayers.Class({status:0,statusText:'',initialize:function(request){this.request=request;var transport=this.transport=request.transport,readyState=this.readyState=transport.readyState;if((readyState>2&&!(!!(window.attachEvent&&!window.opera)))||readyState==4){this.status=this.getStatus();this.statusText=this.getStatusText();this.responseText=transport.responseText==null?'':String(transport.responseText);}
+if(readyState==4){var xml=transport.responseXML;this.responseXML=xml===undefined?null:xml;}},getStatus:OpenLayers.Ajax.Request.prototype.getStatus,getStatusText:function(){try{return this.transport.statusText||'';}catch(e){return'';}},getHeader:OpenLayers.Ajax.Request.prototype.getHeader,getResponseHeader:function(name){return this.transport.getResponseHeader(name);}});OpenLayers.Ajax.getElementsByTagNameNS=function(parentnode,nsuri,nsprefix,tagname){var elem=null;if(parentnode.getElementsByTagNameNS){elem=parentnode.getElementsByTagNameNS(nsuri,tagname);}else{elem=parentnode.getElementsByTagName(nsprefix+':'+tagname);}
+return elem;};OpenLayers.Ajax.serializeXMLToString=function(xmldom){var serializer=new XMLSerializer();var data=serializer.serializeToString(xmldom);return data;};OpenLayers.Control.DragFeature=OpenLayers.Class(OpenLayers.Control,{geometryTypes:null,onStart:function(feature,pixel){},onDrag:function(feature,pixel){},onComplete:function(feature,pixel){},documentDrag:false,layer:null,feature:null,dragCallbacks:{},featureCallbacks:{},lastPixel:null,initialize:function(layer,options){OpenLayers.Control.prototype.initialize.apply(this,[options]);this.layer=layer;this.handlers={drag:new OpenLayers.Handler.Drag(this,OpenLayers.Util.extend({down:this.downFeature,move:this.moveFeature,up:this.upFeature,out:this.cancel,done:this.doneDragging},this.dragCallbacks),{documentDrag:this.documentDrag}),feature:new OpenLayers.Handler.Feature(this,this.layer,OpenLayers.Util.extend({over:this.overFeature,out:this.outFeature},this.featureCallbacks),{geometryTypes:this.geometryTypes})};},destroy:function(){this.layer=null;OpenLayers.Control.prototype.destroy.apply(this,[]);},activate:function(){return(this.handlers.feature.activate()&&OpenLayers.Control.prototype.activate.apply(this,arguments));},deactivate:function(){this.handlers.drag.deactivate();this.handlers.feature.deactivate();this.feature=null;this.dragging=false;this.lastPixel=null;OpenLayers.Element.removeClass(this.map.viewPortDiv,this.displayClass+"Over");return OpenLayers.Control.prototype.deactivate.apply(this,arguments);},overFeature:function(feature){if(!this.handlers.drag.dragging){this.feature=feature;this.handlers.drag.activate();this.over=true;OpenLayers.Element.addClass(this.map.viewPortDiv,this.displayClass+"Over");}else{if(this.feature.id==feature.id){this.over=true;}else{this.over=false;}}},downFeature:function(pixel){this.lastPixel=pixel;this.onStart(this.feature,pixel);},moveFeature:function(pixel){var res=this.map.getResolution();this.feature.geometry.move(res*(pixel.x-this.lastPixel.x),res*(this.lastPixel.y-pixel.y));this.layer.drawFeature(this.feature);this.lastPixel=pixel;this.onDrag(this.feature,pixel);},upFeature:function(pixel){if(!this.over){this.handlers.drag.deactivate();}},doneDragging:function(pixel){this.onComplete(this.feature,pixel);},outFeature:function(feature){if(!this.handlers.drag.dragging){this.over=false;this.handlers.drag.deactivate();OpenLayers.Element.removeClass(this.map.viewPortDiv,this.displayClass+"Over");this.feature=null;}else{if(this.feature.id==feature.id){this.over=false;}}},cancel:function(){this.handlers.drag.deactivate();this.over=false;},setMap:function(map){this.handlers.drag.setMap(map);this.handlers.feature.setMap(map);OpenLayers.Control.prototype.setMap.apply(this,arguments);},CLASS_NAME:"OpenLayers.Control.DragFeature"});OpenLayers.Control.DragPan=OpenLayers.Class(OpenLayers.Control,{type:OpenLayers.Control.TYPE_TOOL,panned:false,interval:25,documentDrag:false,draw:function(){this.handler=new OpenLayers.Handler.Drag(this,{"move":this.panMap,"done":this.panMapDone},{interval:this.interval,documentDrag:this.documentDrag});},panMap:function(xy){this.panned=true;this.map.pan(this.handler.last.x-xy.x,this.handler.last.y-xy.y,{dragging:this.handler.dragging,animate:false});},panMapDone:function(xy){if(this.panned){this.panMap(xy);this.panned=false;}},CLASS_NAME:"OpenLayers.Control.DragPan"});OpenLayers.Control.KeyboardDefaults=OpenLayers.Class(OpenLayers.Control,{autoActivate:true,slideFactor:75,initialize:function(){OpenLayers.Control.prototype.initialize.apply(this,arguments);},destroy:function(){if(this.handler){this.handler.destroy();}
+this.handler=null;OpenLayers.Control.prototype.destroy.apply(this,arguments);},draw:function(){this.handler=new OpenLayers.Handler.Keyboard(this,{"keydown":this.defaultKeyPress});},defaultKeyPress:function(evt){switch(evt.keyCode){case OpenLayers.Event.KEY_LEFT:this.map.pan(-this.slideFactor,0);break;case OpenLayers.Event.KEY_RIGHT:this.map.pan(this.slideFactor,0);break;case OpenLayers.Event.KEY_UP:this.map.pan(0,-this.slideFactor);break;case OpenLayers.Event.KEY_DOWN:this.map.pan(0,this.slideFactor);break;case 33:var size=this.map.getSize();this.map.pan(0,-0.75*size.h);break;case 34:var size=this.map.getSize();this.map.pan(0,0.75*size.h);break;case 35:var size=this.map.getSize();this.map.pan(0.75*size.w,0);break;case 36:var size=this.map.getSize();this.map.pan(-0.75*size.w,0);break;case 43:case 61:case 187:case 107:this.map.zoomIn();break;case 45:case 109:case 189:case 95:this.map.zoomOut();break;}},CLASS_NAME:"OpenLayers.Control.KeyboardDefaults"});OpenLayers.Control.WMSGetFeatureInfo=OpenLayers.Class(OpenLayers.Control,{hover:false,drillDown:false,maxFeatures:10,clickCallback:"click",layers:null,queryVisible:false,url:null,layerUrls:null,infoFormat:'text/html',vendorParams:{},format:null,formatOptions:null,handlerOptions:null,handler:null,hoverRequest:null,EVENT_TYPES:["beforegetfeatureinfo","nogetfeatureinfo","getfeatureinfo"],initialize:function(options){this.EVENT_TYPES=OpenLayers.Control.WMSGetFeatureInfo.prototype.EVENT_TYPES.concat(OpenLayers.Control.prototype.EVENT_TYPES);options=options||{};options.handlerOptions=options.handlerOptions||{};OpenLayers.Control.prototype.initialize.apply(this,[options]);if(!this.format){this.format=new OpenLayers.Format.WMSGetFeatureInfo(options.formatOptions);}
+if(this.drillDown===true){this.hover=false;}
+if(this.hover){this.handler=new OpenLayers.Handler.Hover(this,{'move':this.cancelHover,'pause':this.getInfoForHover},OpenLayers.Util.extend(this.handlerOptions.hover||{},{'delay':250}));}else{var callbacks={};callbacks[this.clickCallback]=this.getInfoForClick;this.handler=new OpenLayers.Handler.Click(this,callbacks,this.handlerOptions.click||{});}},activate:function(){if(!this.active){this.handler.activate();}
+return OpenLayers.Control.prototype.activate.apply(this,arguments);},deactivate:function(){return OpenLayers.Control.prototype.deactivate.apply(this,arguments);},getInfoForClick:function(evt){this.events.triggerEvent("beforegetfeatureinfo",{xy:evt.xy});OpenLayers.Element.addClass(this.map.viewPortDiv,"olCursorWait");this.request(evt.xy,{});},getInfoForHover:function(evt){this.events.triggerEvent("beforegetfeatureinfo",{xy:evt.xy});this.request(evt.xy,{hover:true});},cancelHover:function(){if(this.hoverRequest){this.hoverRequest.abort();this.hoverRequest=null;}},findLayers:function(){var candidates=this.layers||this.map.layers;var layers=[];var layer,url;for(var i=0,len=candidates.length;i<len;++i){layer=candidates[i];if(layer instanceof OpenLayers.Layer.WMS&&(!this.queryVisible||layer.getVisibility())){url=layer.url instanceof Array?layer.url[0]:layer.url;if(this.drillDown===false&&!this.url){this.url=url;}
+if(this.drillDown===true||this.urlMatches(url)){layers.push(layer);}}}
+return layers;},urlMatches:function(url){var matches=OpenLayers.Util.isEquivalentUrl(this.url,url);if(!matches&&this.layerUrls){for(var i=0,len=this.layerUrls.length;i<len;++i){if(OpenLayers.Util.isEquivalentUrl(this.layerUrls[i],url)){matches=true;break;}}}
+return matches;},buildWMSOptions:function(url,layers,clickPosition,format){var layerNames=[],styleNames=[];for(var i=0,len=layers.length;i<len;i++){layerNames=layerNames.concat(layers[i].params.LAYERS);styleNames=styleNames.concat(this.getStyleNames(layers[i]));}
+var params=OpenLayers.Util.extend({service:"WMS",version:layers[0].params.VERSION,request:"GetFeatureInfo",layers:layerNames,query_layers:layerNames,styles:styleNames,bbox:this.map.getExtent().toBBOX(null,layers[0].reverseAxisOrder()),feature_count:this.maxFeatures,height:this.map.getSize().h,width:this.map.getSize().w,format:format,info_format:this.infoFormat},(parseFloat(layers[0].params.VERSION)>=1.3)?{crs:this.map.getProjection(),i:clickPosition.x,j:clickPosition.y}:{srs:this.map.getProjection(),x:clickPosition.x,y:clickPosition.y});OpenLayers.Util.applyDefaults(params,this.vendorParams);return{url:url,params:OpenLayers.Util.upperCaseObject(params),callback:function(request){this.handleResponse(clickPosition,request);},scope:this};},getStyleNames:function(layer){var styleNames;if(layer.params.STYLES){styleNames=layer.params.STYLES;}else{if(layer.params.LAYERS instanceof Array){styleNames=new Array(layer.params.LAYERS.length);}else{styleNames=layer.params.LAYERS.replace(/[^,]/g,"");}}
+return styleNames;},request:function(clickPosition,options){var layers=this.findLayers();if(layers.length==0){this.events.triggerEvent("nogetfeatureinfo");OpenLayers.Element.removeClass(this.map.viewPortDiv,"olCursorWait");return;}
+options=options||{};if(this.drillDown===false){var wmsOptions=this.buildWMSOptions(this.url,layers,clickPosition,layers[0].params.FORMAT);var request=OpenLayers.Request.GET(wmsOptions);if(options.hover===true){this.hoverRequest=request;}}else{this._requestCount=0;this._numRequests=0;this.features=[];var services={},url;for(var i=0,len=layers.length;i<len;i++){var layer=layers[i];var service,found=false;url=layer.url instanceof Array?layer.url[0]:layer.url;if(url in services){services[url].push(layer);}else{this._numRequests++;services[url]=[layer];}}
+var layers;for(var url in services){layers=services[url];var wmsOptions=this.buildWMSOptions(url,layers,clickPosition,layers[0].params.FORMAT);OpenLayers.Request.GET(wmsOptions);}}},triggerGetFeatureInfo:function(request,xy,features){this.events.triggerEvent("getfeatureinfo",{text:request.responseText,features:features,request:request,xy:xy});OpenLayers.Element.removeClass(this.map.viewPortDiv,"olCursorWait");},handleResponse:function(xy,request){var doc=request.responseXML;if(!doc||!doc.documentElement){doc=request.responseText;}
+var features=this.format.read(doc);if(this.drillDown===false){this.triggerGetFeatureInfo(request,xy,features);}else{this._requestCount++;this._features=(this._features||[]).concat(features);if(this._requestCount===this._numRequests){this.triggerGetFeatureInfo(request,xy,this._features.concat());delete this._features;delete this._requestCount;delete this._numRequests;}}},CLASS_NAME:"OpenLayers.Control.WMSGetFeatureInfo"});OpenLayers.Control.WMTSGetFeatureInfo=OpenLayers.Class(OpenLayers.Control,{hover:false,requestEncoding:"KVP",drillDown:false,maxFeatures:10,clickCallback:"click",layers:null,queryVisible:true,infoFormat:'text/html',vendorParams:{},format:null,formatOptions:null,handlerOptions:null,handler:null,hoverRequest:null,EVENT_TYPES:["beforegetfeatureinfo","getfeatureinfo","exception"],pending:0,initialize:function(options){this.EVENT_TYPES=OpenLayers.Control.WMTSGetFeatureInfo.prototype.EVENT_TYPES.concat(OpenLayers.Control.prototype.EVENT_TYPES);options=options||{};options.handlerOptions=options.handlerOptions||{};OpenLayers.Control.prototype.initialize.apply(this,[options]);if(!this.format){this.format=new OpenLayers.Format.WMSGetFeatureInfo(options.formatOptions);}
+if(this.drillDown===true){this.hover=false;}
+if(this.hover){this.handler=new OpenLayers.Handler.Hover(this,{move:this.cancelHover,pause:this.getInfoForHover},OpenLayers.Util.extend(this.handlerOptions.hover||{},{delay:250}));}else{var callbacks={};callbacks[this.clickCallback]=this.getInfoForClick;this.handler=new OpenLayers.Handler.Click(this,callbacks,this.handlerOptions.click||{});}},activate:function(){if(!this.active){this.handler.activate();}
+return OpenLayers.Control.prototype.activate.apply(this,arguments);},deactivate:function(){return OpenLayers.Control.prototype.deactivate.apply(this,arguments);},getInfoForClick:function(evt){this.request(evt.xy,{});},getInfoForHover:function(evt){this.request(evt.xy,{hover:true});},cancelHover:function(){if(this.hoverRequest){--this.pending;if(this.pending<=0){OpenLayers.Element.removeClass(this.map.viewPortDiv,"olCursorWait");this.pending=0;}
+this.hoverRequest.abort();this.hoverRequest=null;}},findLayers:function(){var candidates=this.layers||this.map.layers;var layers=[];var layer;for(var i=candidates.length-1;i>=0;--i){layer=candidates[i];if(layer instanceof OpenLayers.Layer.WMTS&&layer.requestEncoding===this.requestEncoding&&(!this.queryVisible||layer.getVisibility())){layers.push(layer);if(!this.drillDown||this.hover){break;}}}
+return layers;},buildRequestOptions:function(layer,xy){var loc=this.map.getLonLatFromPixel(xy);var getTileUrl=layer.getURL(new OpenLayers.Bounds(loc.lon,loc.lat,loc.lon,loc.lat));var params=OpenLayers.Util.getParameters(getTileUrl);var tileInfo=layer.getTileInfo(loc);OpenLayers.Util.extend(params,{service:"WMTS",version:layer.version,request:"GetFeatureInfo",infoFormat:this.infoFormat,i:tileInfo.i,j:tileInfo.j});OpenLayers.Util.applyDefaults(params,this.vendorParams);return{url:layer.url instanceof Array?layer.url[0]:layer.url,params:OpenLayers.Util.upperCaseObject(params),callback:function(request){this.handleResponse(xy,request,layer);},scope:this};},request:function(xy,options){options=options||{};var layers=this.findLayers();if(layers.length>0){var issue,layer;for(var i=0,len=layers.length;i<len;i++){layer=layers[i];issue=this.events.triggerEvent("beforegetfeatureinfo",{xy:xy,layer:layer});if(issue!==false){++this.pending;var requestOptions=this.buildRequestOptions(layer,xy);var request=OpenLayers.Request.GET(requestOptions);if(options.hover===true){this.hoverRequest=request;}}}
+if(this.pending>0){OpenLayers.Element.addClass(this.map.viewPortDiv,"olCursorWait");}}},handleResponse:function(xy,request,layer){--this.pending;if(this.pending<=0){OpenLayers.Element.removeClass(this.map.viewPortDiv,"olCursorWait");this.pending=0;}
+if(request.status&&(request.status<200||request.status>=300)){this.events.triggerEvent("exception",{xy:xy,request:request,layer:layer});}else{var doc=request.responseXML;if(!doc||!doc.documentElement){doc=request.responseText;}
+var features,except;try{features=this.format.read(doc);}catch(error){except=true;this.events.triggerEvent("exception",{xy:xy,request:request,error:error,layer:layer});}
+if(!except){this.events.triggerEvent("getfeatureinfo",{text:request.responseText,features:features,request:request,xy:xy,layer:layer});}}},setMap:function(map){this.handler.setMap(map);OpenLayers.Control.prototype.setMap.apply(this,arguments);},CLASS_NAME:"OpenLayers.Control.WMTSGetFeatureInfo"});OpenLayers.State={UNKNOWN:'Unknown',INSERT:'Insert',UPDATE:'Update',DELETE:'Delete'};OpenLayers.Feature.Vector=OpenLayers.Class(OpenLayers.Feature,{fid:null,geometry:null,attributes:null,bounds:null,state:null,style:null,url:null,renderIntent:"default",initialize:function(geometry,attributes,style){OpenLayers.Feature.prototype.initialize.apply(this,[null,null,attributes]);this.lonlat=null;this.geometry=geometry?geometry:null;this.state=null;this.attributes={};if(attributes){this.attributes=OpenLayers.Util.extend(this.attributes,attributes);}
+this.style=style?style:null;},destroy:function(){if(this.layer){this.layer.removeFeatures(this);this.layer=null;}
+this.geometry=null;OpenLayers.Feature.prototype.destroy.apply(this,arguments);},clone:function(){return new OpenLayers.Feature.Vector(this.geometry?this.geometry.clone():null,this.attributes,this.style);},onScreen:function(boundsOnly){var onScreen=false;if(this.layer&&this.layer.map){var screenBounds=this.layer.map.getExtent();if(boundsOnly){var featureBounds=this.geometry.getBounds();onScreen=screenBounds.intersectsBounds(featureBounds);}else{var screenPoly=screenBounds.toGeometry();onScreen=screenPoly.intersects(this.geometry);}}
+return onScreen;},getVisibility:function(){return!(this.style&&this.style.display=='none'||!this.layer||this.layer&&this.layer.styleMap&&this.layer.styleMap.createSymbolizer(this,this.renderIntent).display=='none'||this.layer&&!this.layer.getVisibility());},createMarker:function(){return null;},destroyMarker:function(){},createPopup:function(){return null;},atPoint:function(lonlat,toleranceLon,toleranceLat){var atPoint=false;if(this.geometry){atPoint=this.geometry.atPoint(lonlat,toleranceLon,toleranceLat);}
+return atPoint;},destroyPopup:function(){},move:function(location){if(!this.layer||!this.geometry.move){return;}
+var pixel;if(location.CLASS_NAME=="OpenLayers.LonLat"){pixel=this.layer.getViewPortPxFromLonLat(location);}else{pixel=location;}
+var lastPixel=this.layer.getViewPortPxFromLonLat(this.geometry.getBounds().getCenterLonLat());var res=this.layer.map.getResolution();this.geometry.move(res*(pixel.x-lastPixel.x),res*(lastPixel.y-pixel.y));this.layer.drawFeature(this);return lastPixel;},toState:function(state){if(state==OpenLayers.State.UPDATE){switch(this.state){case OpenLayers.State.UNKNOWN:case OpenLayers.State.DELETE:this.state=state;break;case OpenLayers.State.UPDATE:case OpenLayers.State.INSERT:break;}}else if(state==OpenLayers.State.INSERT){switch(this.state){case OpenLayers.State.UNKNOWN:break;default:this.state=state;break;}}else if(state==OpenLayers.State.DELETE){switch(this.state){case OpenLayers.State.INSERT:break;case OpenLayers.State.DELETE:break;case OpenLayers.State.UNKNOWN:case OpenLayers.State.UPDATE:this.state=state;break;}}else if(state==OpenLayers.State.UNKNOWN){this.state=state;}},CLASS_NAME:"OpenLayers.Feature.Vector"});OpenLayers.Feature.Vector.style={'default':{fillColor:"#ee9900",fillOpacity:0.4,hoverFillColor:"white",hoverFillOpacity:0.8,strokeColor:"#ee9900",strokeOpacity:1,strokeWidth:1,strokeLinecap:"round",strokeDashstyle:"solid",hoverStrokeColor:"red",hoverStrokeOpacity:1,hoverStrokeWidth:0.2,pointRadius:6,hoverPointRadius:1,hoverPointUnit:"%",pointerEvents:"visiblePainted",cursor:"inherit"},'select':{fillColor:"blue",fillOpacity:0.4,hoverFillColor:"white",hoverFillOpacity:0.8,strokeColor:"blue",strokeOpacity:1,strokeWidth:2,strokeLinecap:"round",strokeDashstyle:"solid",hoverStrokeColor:"red",hoverStrokeOpacity:1,hoverStrokeWidth:0.2,pointRadius:6,hoverPointRadius:1,hoverPointUnit:"%",pointerEvents:"visiblePainted",cursor:"pointer"},'temporary':{fillColor:"#66cccc",fillOpacity:0.2,hoverFillColor:"white",hoverFillOpacity:0.8,strokeColor:"#66cccc",strokeOpacity:1,strokeLinecap:"round",strokeWidth:2,strokeDashstyle:"solid",hoverStrokeColor:"red",hoverStrokeOpacity:1,hoverStrokeWidth:0.2,pointRadius:6,hoverPointRadius:1,hoverPointUnit:"%",pointerEvents:"visiblePainted",cursor:"inherit"},'delete':{display:"none"}};OpenLayers.Feature.WFS=OpenLayers.Class(OpenLayers.Feature,{initialize:function(layer,xmlNode){var newArguments=arguments;var data=this.processXMLNode(xmlNode);newArguments=new Array(layer,data.lonlat,data);OpenLayers.Feature.prototype.initialize.apply(this,newArguments);this.createMarker();this.layer.addMarker(this.marker);},destroy:function(){if(this.marker!=null){this.layer.removeMarker(this.marker);}
+OpenLayers.Feature.prototype.destroy.apply(this,arguments);},processXMLNode:function(xmlNode){var point=OpenLayers.Ajax.getElementsByTagNameNS(xmlNode,"http://www.opengis.net/gml","gml","Point");var text=OpenLayers.Util.getXmlNodeValue(OpenLayers.Ajax.getElementsByTagNameNS(point[0],"http://www.opengis.net/gml","gml","coordinates")[0]);var floats=text.split(",");return{lonlat:new OpenLayers.LonLat(parseFloat(floats[0]),parseFloat(floats[1])),id:null};},CLASS_NAME:"OpenLayers.Feature.WFS"});OpenLayers.Format.OWSCommon.v1_0_0=OpenLayers.Class(OpenLayers.Format.OWSCommon.v1,{namespaces:{ows:"http://www.opengis.net/ows/1.0",xlink:"http://www.w3.org/1999/xlink"},readers:{"ows":OpenLayers.Format.OWSCommon.v1.prototype.readers["ows"]},writers:{"ows":OpenLayers.Format.OWSCommon.v1.prototype.writers["ows"]},CLASS_NAME:"OpenLayers.Format.OWSCommon.v1_1_0"});OpenLayers.Format.OWSCommon.v1_1_0=OpenLayers.Class(OpenLayers.Format.OWSCommon.v1,{namespaces:{ows:"http://www.opengis.net/ows/1.1",xlink:"http://www.w3.org/1999/xlink"},readers:{"ows":OpenLayers.Util.applyDefaults({"AllowedValues":function(node,parameter){parameter.allowedValues={};this.readChildNodes(node,parameter.allowedValues);},"AnyValue":function(node,parameter){parameter.anyValue=true;},"Range":function(node,allowedValues){allowedValues.range={};this.readChildNodes(node,allowedValues.range);},"MinimumValue":function(node,range){range.minValue=this.getChildValue(node);},"MaximumValue":function(node,range){range.maxValue=this.getChildValue(node);},"Identifier":function(node,obj){obj.identifier=this.getChildValue(node);},"SupportedCRS":function(node,obj){obj.supportedCRS=this.getChildValue(node);}},OpenLayers.Format.OWSCommon.v1.prototype.readers["ows"])},CLASS_NAME:"OpenLayers.Format.OWSCommon.v1_1_0"});OpenLayers.Format.OWSContext=OpenLayers.Class(OpenLayers.Format.Context,{defaultVersion:"0.3.1",getParser:function(version){var v=version||this.version||this.defaultVersion;if(v==="0.3.0"){v=this.defaultVersion;}
+if(!this.parser||this.parser.VERSION!=v){var format=OpenLayers.Format.OWSContext["v"+v.replace(/\./g,"_")];if(!format){throw"Can't find a OWSContext parser for version "+v;}
+this.parser=new format(this.options);}
+return this.parser;},toContext:function(obj){var context={};if(obj.CLASS_NAME=="OpenLayers.Map"){context.bounds=obj.getExtent();context.maxExtent=obj.maxExtent;context.projection=obj.projection;context.size=obj.getSize();context.layers=obj.layers;}
+return context;},CLASS_NAME:"OpenLayers.Format.OWSContext"});OpenLayers.Format.WFSCapabilities.v1=OpenLayers.Class(OpenLayers.Format.WFSCapabilities,{initialize:function(options){OpenLayers.Format.XML.prototype.initialize.apply(this,[options]);this.options=options;},read:function(data){if(typeof data=="string"){data=OpenLayers.Format.XML.prototype.read.apply(this,[data]);}
+var capabilities={};var root=data.documentElement;this.runChildNodes(capabilities,root);return capabilities;},runChildNodes:function(obj,node){var children=node.childNodes;var childNode,processor;for(var i=0;i<children.length;++i){childNode=children[i];if(childNode.nodeType==1){processor=this["read_cap_"+childNode.nodeName];if(processor){processor.apply(this,[obj,childNode]);}}}},read_cap_FeatureTypeList:function(request,node){var featureTypeList={featureTypes:[]};this.runChildNodes(featureTypeList,node);request.featureTypeList=featureTypeList;},read_cap_FeatureType:function(featureTypeList,node,parentLayer){var featureType={};this.runChildNodes(featureType,node);featureTypeList.featureTypes.push(featureType);},read_cap_Name:function(obj,node){var name=this.getChildValue(node);if(name){var parts=name.split(":");obj.name=parts.pop();if(parts.length>0){obj.featureNS=this.lookupNamespaceURI(node,parts[0]);}}},read_cap_Title:function(obj,node){var title=this.getChildValue(node);if(title){obj.title=title;}},read_cap_Abstract:function(obj,node){var abst=this.getChildValue(node);if(abst){obj["abstract"]=abst;}},CLASS_NAME:"OpenLayers.Format.WFSCapabilities.v1"});OpenLayers.Format.WMC=OpenLayers.Class(OpenLayers.Format.Context,{defaultVersion:"1.1.0",getParser:function(version){var v=version||this.version||this.defaultVersion;if(!this.parser||this.parser.VERSION!=v){var format=OpenLayers.Format.WMC["v"+v.replace(/\./g,"_")];if(!format){throw"Can't find a WMC parser for version "+v;}
+this.parser=new format(this.options);}
+return this.parser;},layerToContext:function(layer){var parser=this.getParser();var layerContext={queryable:layer.queryable,visibility:layer.visibility,name:layer.params["LAYERS"],title:layer.name,metadataURL:layer.metadataURL,version:layer.params["VERSION"],url:layer.url,maxExtent:layer.maxExtent,transparent:layer.params["TRANSPARENT"],numZoomLevels:layer.numZoomLevels,units:layer.units,isBaseLayer:layer.isBaseLayer,opacity:layer.opacity,displayInLayerSwitcher:layer.displayInLayerSwitcher,singleTile:layer.singleTile,tileSize:(layer.singleTile||!layer.tileSize)?undefined:{width:layer.tileSize.w,height:layer.tileSize.h},minScale:(layer.options.resolutions||layer.options.scales||layer.options.maxResolution||layer.options.minScale)?layer.minScale:undefined,maxScale:(layer.options.resolutions||layer.options.scales||layer.options.minResolution||layer.options.maxScale)?layer.maxScale:undefined,formats:[{value:layer.params["FORMAT"],current:true}],styles:[{href:layer.params["SLD"],body:layer.params["SLD_BODY"],name:layer.params["STYLES"]||parser.defaultStyleName,title:parser.defaultStyleTitle,current:true}]};return layerContext;},toContext:function(obj){var context={};var layers=obj.layers;if(obj.CLASS_NAME=="OpenLayers.Map"){context.bounds=obj.getExtent();context.maxExtent=obj.maxExtent;context.projection=obj.projection;context.size=obj.getSize();}
+else{OpenLayers.Util.applyDefaults(context,obj);if(context.layers!=undefined){delete(context.layers);}}
+if(context.layersContext==undefined){context.layersContext=[];}
+if(layers!=undefined&&layers instanceof Array){for(var i=0,len=layers.length;i<len;i++){var layer=layers[i];if(layer instanceof OpenLayers.Layer.WMS){context.layersContext.push(this.layerToContext(layer));}}}
+return context;},CLASS_NAME:"OpenLayers.Format.WMC"});OpenLayers.Format.WMSCapabilities.v1=OpenLayers.Class(OpenLayers.Format.XML,{namespaces:{wms:"http://www.opengis.net/wms",xlink:"http://www.w3.org/1999/xlink",xsi:"http://www.w3.org/2001/XMLSchema-instance"},defaultPrefix:"wms",initialize:function(options){OpenLayers.Format.XML.prototype.initialize.apply(this,[options]);},read:function(data){if(typeof data=="string"){data=OpenLayers.Format.XML.prototype.read.apply(this,[data]);}
+if(data&&data.nodeType==9){data=data.documentElement;}
+var capabilities={};this.readNode(data,capabilities);this.postProcessLayers(capabilities);return capabilities;},postProcessLayers:function(capabilities){if(capabilities.capability){capabilities.capability.layers=[];var layers=capabilities.capability.nestedLayers;for(var i=0,len=layers.length;i<len;++i){var layer=layers[i];this.processLayer(capabilities.capability,layer);}}},processLayer:function(capability,layer,parentLayer){if(layer.formats===undefined){layer.formats=capability.request.getmap.formats;}
+if(parentLayer){layer.styles=layer.styles.concat(parentLayer.styles);var attributes=["queryable","cascaded","fixedWidth","fixedHeight","opaque","noSubsets","llbbox","minScale","maxScale","attribution"];var complexAttr=["srs","bbox","dimensions","authorityURLs"];var key;for(var j=0;j<attributes.length;j++){key=attributes[j];if(key in parentLayer){if(layer[key]==null){layer[key]=parentLayer[key];}
+if(layer[key]==null){var intAttr=["cascaded","fixedWidth","fixedHeight"];var boolAttr=["queryable","opaque","noSubsets"];if(OpenLayers.Util.indexOf(intAttr,key)!=-1){layer[key]=0;}
+if(OpenLayers.Util.indexOf(boolAttr,key)!=-1){layer[key]=false;}}}}
+for(var j=0;j<complexAttr.length;j++){key=complexAttr[j];layer[key]=OpenLayers.Util.extend(layer[key],parentLayer[key]);}}
+for(var i=0,len=layer.nestedLayers.length;i<len;i++){var childLayer=layer.nestedLayers[i];this.processLayer(capability,childLayer,layer);}
+if(layer.name){capability.layers.push(layer);}},readers:{"wms":{"Service":function(node,obj){obj.service={};this.readChildNodes(node,obj.service);},"Name":function(node,obj){obj.name=this.getChildValue(node);},"Title":function(node,obj){obj.title=this.getChildValue(node);},"Abstract":function(node,obj){obj["abstract"]=this.getChildValue(node);},"BoundingBox":function(node,obj){var bbox={};bbox.bbox=[parseFloat(node.getAttribute("minx")),parseFloat(node.getAttribute("miny")),parseFloat(node.getAttribute("maxx")),parseFloat(node.getAttribute("maxy"))];var res={x:parseFloat(node.getAttribute("resx")),y:parseFloat(node.getAttribute("resy"))};if(!(isNaN(res.x)&&isNaN(res.y))){bbox.res=res;}
+return bbox;},"OnlineResource":function(node,obj){obj.href=this.getAttributeNS(node,this.namespaces.xlink,"href");},"ContactInformation":function(node,obj){obj.contactInformation={};this.readChildNodes(node,obj.contactInformation);},"ContactPersonPrimary":function(node,obj){obj.personPrimary={};this.readChildNodes(node,obj.personPrimary);},"ContactPerson":function(node,obj){obj.person=this.getChildValue(node);},"ContactOrganization":function(node,obj){obj.organization=this.getChildValue(node);},"ContactPosition":function(node,obj){obj.position=this.getChildValue(node);},"ContactAddress":function(node,obj){obj.contactAddress={};this.readChildNodes(node,obj.contactAddress);},"AddressType":function(node,obj){obj.type=this.getChildValue(node);},"Address":function(node,obj){obj.address=this.getChildValue(node);},"City":function(node,obj){obj.city=this.getChildValue(node);},"StateOrProvince":function(node,obj){obj.stateOrProvince=this.getChildValue(node);},"PostCode":function(node,obj){obj.postcode=this.getChildValue(node);},"Country":function(node,obj){obj.country=this.getChildValue(node);},"ContactVoiceTelephone":function(node,obj){obj.phone=this.getChildValue(node);},"ContactFacsimileTelephone":function(node,obj){obj.fax=this.getChildValue(node);},"ContactElectronicMailAddress":function(node,obj){obj.email=this.getChildValue(node);},"Fees":function(node,obj){var fees=this.getChildValue(node);if(fees&&fees.toLowerCase()!="none"){obj.fees=fees;}},"AccessConstraints":function(node,obj){var constraints=this.getChildValue(node);if(constraints&&constraints.toLowerCase()!="none"){obj.accessConstraints=constraints;}},"Capability":function(node,obj){obj.capability={nestedLayers:[]};this.readChildNodes(node,obj.capability);},"Request":function(node,obj){obj.request={};this.readChildNodes(node,obj.request);},"GetCapabilities":function(node,obj){obj.getcapabilities={formats:[]};this.readChildNodes(node,obj.getcapabilities);},"Format":function(node,obj){if(obj.formats instanceof Array){obj.formats.push(this.getChildValue(node));}else{obj.format=this.getChildValue(node);}},"DCPType":function(node,obj){this.readChildNodes(node,obj);},"HTTP":function(node,obj){this.readChildNodes(node,obj);},"Get":function(node,obj){this.readChildNodes(node,obj);},"Post":function(node,obj){this.readChildNodes(node,obj);},"GetMap":function(node,obj){obj.getmap={formats:[]};this.readChildNodes(node,obj.getmap);},"GetFeatureInfo":function(node,obj){obj.getfeatureinfo={formats:[]};this.readChildNodes(node,obj.getfeatureinfo);},"Exception":function(node,obj){obj.exception={formats:[]};this.readChildNodes(node,obj.exception);},"Layer":function(node,obj){var attrNode=node.getAttributeNode("queryable");var queryable=(attrNode&&attrNode.specified)?node.getAttribute("queryable"):null;attrNode=node.getAttributeNode("cascaded");var cascaded=(attrNode&&attrNode.specified)?node.getAttribute("cascaded"):null;attrNode=node.getAttributeNode("opaque");var opaque=(attrNode&&attrNode.specified)?node.getAttribute('opaque'):null;var noSubsets=node.getAttribute('noSubsets');var fixedWidth=node.getAttribute('fixedWidth');var fixedHeight=node.getAttribute('fixedHeight');var layer={nestedLayers:[],styles:[],srs:{},metadataURLs:[],bbox:{},dimensions:{},authorityURLs:{},identifiers:{},keywords:[],queryable:(queryable&&queryable!=="")?(queryable==="1"||queryable==="true"):null,cascaded:(cascaded!==null)?parseInt(cascaded):null,opaque:opaque?(opaque==="1"||opaque==="true"):null,noSubsets:(noSubsets!==null)?(noSubsets==="1"||noSubsets==="true"):null,fixedWidth:(fixedWidth!=null)?parseInt(fixedWidth):null,fixedHeight:(fixedHeight!=null)?parseInt(fixedHeight):null};obj.nestedLayers.push(layer);this.readChildNodes(node,layer);if(layer.name){var parts=layer.name.split(":");if(parts.length>0){layer.prefix=parts[0];}}},"Attribution":function(node,obj){obj.attribution={};this.readChildNodes(node,obj.attribution);},"LogoURL":function(node,obj){obj.logo={width:node.getAttribute("width"),height:node.getAttribute("height")};this.readChildNodes(node,obj.logo);},"Style":function(node,obj){var style={};obj.styles.push(style);this.readChildNodes(node,style);},"LegendURL":function(node,obj){var legend={width:node.getAttribute("width"),height:node.getAttribute("height")};obj.legend=legend;this.readChildNodes(node,legend);},"MetadataURL":function(node,obj){var metadataURL={type:node.getAttribute("type")};obj.metadataURLs.push(metadataURL);this.readChildNodes(node,metadataURL);},"DataURL":function(node,obj){obj.dataURL={};this.readChildNodes(node,obj.dataURL);},"FeatureListURL":function(node,obj){obj.featureListURL={};this.readChildNodes(node,obj.featureListURL);},"AuthorityURL":function(node,obj){var name=node.getAttribute("name");var authority={};this.readChildNodes(node,authority);obj.authorityURLs[name]=authority.href;},"Identifier":function(node,obj){var authority=node.getAttribute("authority");obj.identifiers[authority]=this.getChildValue(node);},"KeywordList":function(node,obj){this.readChildNodes(node,obj);},"SRS":function(node,obj){obj.srs[this.getChildValue(node)]=true;}}},CLASS_NAME:"OpenLayers.Format.WMSCapabilities.v1"});OpenLayers.Format.WMSDescribeLayer.v1_1=OpenLayers.Class(OpenLayers.Format.WMSDescribeLayer,{initialize:function(options){OpenLayers.Format.WMSDescribeLayer.prototype.initialize.apply(this,[options]);},read:function(data){if(typeof data=="string"){data=OpenLayers.Format.XML.prototype.read.apply(this,[data]);}
+var root=data.documentElement;var children=root.childNodes;var describelayer=[];var childNode,nodeName;for(var i=0;i<children.length;++i){childNode=children[i];nodeName=childNode.nodeName;if(nodeName=='LayerDescription'){var layerName=childNode.getAttribute('name');var owsType='';var owsURL='';var typeName='';if(childNode.getAttribute('owsType')){owsType=childNode.getAttribute('owsType');owsURL=childNode.getAttribute('owsURL');}else{if(childNode.getAttribute('wfs')!=''){owsType='WFS';owsURL=childNode.getAttribute('wfs');}else if(childNode.getAttribute('wcs')!=''){owsType='WCS';owsURL=childNode.getAttribute('wcs');}}
+var query=childNode.getElementsByTagName('Query');if(query.length>0){typeName=query[0].getAttribute('typeName');if(!typeName){typeName=query[0].getAttribute('typename');}}
+describelayer.push({layerName:layerName,owsType:owsType,owsURL:owsURL,typeName:typeName});}}
+return describelayer;},CLASS_NAME:"OpenLayers.Format.WMSDescribeLayer.v1_1"});OpenLayers.Handler.Box=OpenLayers.Class(OpenLayers.Handler,{dragHandler:null,boxDivClassName:'olHandlerBoxZoomBox',boxCharacteristics:null,initialize:function(control,callbacks,options){OpenLayers.Handler.prototype.initialize.apply(this,arguments);var callbacks={"down":this.startBox,"move":this.moveBox,"out":this.removeBox,"up":this.endBox};this.dragHandler=new OpenLayers.Handler.Drag(this,callbacks,{keyMask:this.keyMask});},destroy:function(){if(this.dragHandler){this.dragHandler.destroy();this.dragHandler=null;}
+OpenLayers.Handler.prototype.destroy.apply(this,arguments);},setMap:function(map){OpenLayers.Handler.prototype.setMap.apply(this,arguments);if(this.dragHandler){this.dragHandler.setMap(map);}},startBox:function(xy){this.zoomBox=OpenLayers.Util.createDiv('zoomBox',this.dragHandler.start);this.zoomBox.className=this.boxDivClassName;this.zoomBox.style.zIndex=this.map.Z_INDEX_BASE["Popup"]-1;this.map.viewPortDiv.appendChild(this.zoomBox);OpenLayers.Element.addClass(this.map.viewPortDiv,"olDrawBox");},moveBox:function(xy){var startX=this.dragHandler.start.x;var startY=this.dragHandler.start.y;var deltaX=Math.abs(startX-xy.x);var deltaY=Math.abs(startY-xy.y);this.zoomBox.style.width=Math.max(1,deltaX)+"px";this.zoomBox.style.height=Math.max(1,deltaY)+"px";this.zoomBox.style.left=xy.x<startX?xy.x+"px":startX+"px";this.zoomBox.style.top=xy.y<startY?xy.y+"px":startY+"px";var box=this.getBoxCharacteristics();if(box.newBoxModel){if(xy.x>startX){this.zoomBox.style.width=Math.max(1,deltaX-box.xOffset)+"px";}
+if(xy.y>startY){this.zoomBox.style.height=Math.max(1,deltaY-box.yOffset)+"px";}}},endBox:function(end){var result;if(Math.abs(this.dragHandler.start.x-end.x)>5||Math.abs(this.dragHandler.start.y-end.y)>5){var start=this.dragHandler.start;var top=Math.min(start.y,end.y);var bottom=Math.max(start.y,end.y);var left=Math.min(start.x,end.x);var right=Math.max(start.x,end.x);result=new OpenLayers.Bounds(left,bottom,right,top);}else{result=this.dragHandler.start.clone();}
+this.removeBox();this.callback("done",[result]);},removeBox:function(){this.map.viewPortDiv.removeChild(this.zoomBox);this.zoomBox=null;this.boxCharacteristics=null;OpenLayers.Element.removeClass(this.map.viewPortDiv,"olDrawBox");},activate:function(){if(OpenLayers.Handler.prototype.activate.apply(this,arguments)){this.dragHandler.activate();return true;}else{return false;}},deactivate:function(){if(OpenLayers.Handler.prototype.deactivate.apply(this,arguments)){this.dragHandler.deactivate();return true;}else{return false;}},getBoxCharacteristics:function(){if(!this.boxCharacteristics){var xOffset=parseInt(OpenLayers.Element.getStyle(this.zoomBox,"border-left-width"))+parseInt(OpenLayers.Element.getStyle(this.zoomBox,"border-right-width"))+1;var yOffset=parseInt(OpenLayers.Element.getStyle(this.zoomBox,"border-top-width"))+parseInt(OpenLayers.Element.getStyle(this.zoomBox,"border-bottom-width"))+1;var newBoxModel=OpenLayers.Util.getBrowserName()=="msie"?document.compatMode!="BackCompat":true;this.boxCharacteristics={xOffset:xOffset,yOffset:yOffset,newBoxModel:newBoxModel};}
+return this.boxCharacteristics;},CLASS_NAME:"OpenLayers.Handler.Box"});OpenLayers.Handler.RegularPolygon=OpenLayers.Class(OpenLayers.Handler.Drag,{sides:4,radius:null,snapAngle:null,snapToggle:'shiftKey',layerOptions:null,persist:false,irregular:false,angle:null,fixedRadius:false,feature:null,layer:null,origin:null,initialize:function(control,callbacks,options){if(!(options&&options.layerOptions&&options.layerOptions.styleMap)){this.style=OpenLayers.Util.extend(OpenLayers.Feature.Vector.style['default'],{});}
+OpenLayers.Handler.prototype.initialize.apply(this,[control,callbacks,options]);this.options=(options)?options:{};},setOptions:function(newOptions){OpenLayers.Util.extend(this.options,newOptions);OpenLayers.Util.extend(this,newOptions);},activate:function(){var activated=false;if(OpenLayers.Handler.prototype.activate.apply(this,arguments)){var options=OpenLayers.Util.extend({displayInLayerSwitcher:false,calculateInRange:OpenLayers.Function.True},this.layerOptions);this.layer=new OpenLayers.Layer.Vector(this.CLASS_NAME,options);this.map.addLayer(this.layer);activated=true;}
+return activated;},deactivate:function(){var deactivated=false;if(OpenLayers.Handler.Drag.prototype.deactivate.apply(this,arguments)){if(this.dragging){this.cancel();}
+if(this.layer.map!=null){this.layer.destroy(false);if(this.feature){this.feature.destroy();}}
+this.layer=null;this.feature=null;deactivated=true;}
+return deactivated;},down:function(evt){this.fixedRadius=!!(this.radius);var maploc=this.map.getLonLatFromPixel(evt.xy);this.origin=new OpenLayers.Geometry.Point(maploc.lon,maploc.lat);if(!this.fixedRadius||this.irregular){this.radius=this.map.getResolution();}
+if(this.persist){this.clear();}
+this.feature=new OpenLayers.Feature.Vector();this.createGeometry();this.callback("create",[this.origin,this.feature]);this.layer.addFeatures([this.feature],{silent:true});this.layer.drawFeature(this.feature,this.style);},move:function(evt){var maploc=this.map.getLonLatFromPixel(evt.xy);var point=new OpenLayers.Geometry.Point(maploc.lon,maploc.lat);if(this.irregular){var ry=Math.sqrt(2)*Math.abs(point.y-this.origin.y)/2;this.radius=Math.max(this.map.getResolution()/2,ry);}else if(this.fixedRadius){this.origin=point;}else{this.calculateAngle(point,evt);this.radius=Math.max(this.map.getResolution()/2,point.distanceTo(this.origin));}
+this.modifyGeometry();if(this.irregular){var dx=point.x-this.origin.x;var dy=point.y-this.origin.y;var ratio;if(dy==0){ratio=dx/(this.radius*Math.sqrt(2));}else{ratio=dx/dy;}
+this.feature.geometry.resize(1,this.origin,ratio);this.feature.geometry.move(dx/2,dy/2);}
+this.layer.drawFeature(this.feature,this.style);},up:function(evt){this.finalize();if(this.start==this.last){this.callback("done",[evt.xy]);}},out:function(evt){this.finalize();},createGeometry:function(){this.angle=Math.PI*((1/this.sides)-(1/2));if(this.snapAngle){this.angle+=this.snapAngle*(Math.PI/180);}
+this.feature.geometry=OpenLayers.Geometry.Polygon.createRegularPolygon(this.origin,this.radius,this.sides,this.snapAngle);},modifyGeometry:function(){var angle,point;var ring=this.feature.geometry.components[0];if(ring.components.length!=(this.sides+1)){this.createGeometry();ring=this.feature.geometry.components[0];}
+for(var i=0;i<this.sides;++i){point=ring.components[i];angle=this.angle+(i*2*Math.PI/this.sides);point.x=this.origin.x+(this.radius*Math.cos(angle));point.y=this.origin.y+(this.radius*Math.sin(angle));point.clearBounds();}},calculateAngle:function(point,evt){var alpha=Math.atan2(point.y-this.origin.y,point.x-this.origin.x);if(this.snapAngle&&(this.snapToggle&&!evt[this.snapToggle])){var snapAngleRad=(Math.PI/180)*this.snapAngle;this.angle=Math.round(alpha/snapAngleRad)*snapAngleRad;}else{this.angle=alpha;}},cancel:function(){this.callback("cancel",null);this.finalize();},finalize:function(){this.origin=null;this.radius=this.options.radius;},clear:function(){if(this.layer){this.layer.renderer.clear();this.layer.destroyFeatures();}},callback:function(name,args){if(this.callbacks[name]){this.callbacks[name].apply(this.control,[this.feature.geometry.clone()]);}
+if(!this.persist&&(name=="done"||name=="cancel")){this.clear();}},CLASS_NAME:"OpenLayers.Handler.RegularPolygon"});OpenLayers.Layer.EventPane=OpenLayers.Class(OpenLayers.Layer,{smoothDragPan:true,isBaseLayer:true,isFixed:true,pane:null,mapObject:null,initialize:function(name,options){OpenLayers.Layer.prototype.initialize.apply(this,arguments);if(this.pane==null){this.pane=OpenLayers.Util.createDiv(this.div.id+"_EventPane");}},destroy:function(){this.mapObject=null;this.pane=null;OpenLayers.Layer.prototype.destroy.apply(this,arguments);},setMap:function(map){OpenLayers.Layer.prototype.setMap.apply(this,arguments);this.pane.style.zIndex=parseInt(this.div.style.zIndex)+1;this.pane.style.display=this.div.style.display;this.pane.style.width="100%";this.pane.style.height="100%";if(OpenLayers.Util.getBrowserName()=="msie"){this.pane.style.background="url("+OpenLayers.Util.getImagesLocation()+"blank.gif)";}
+if(this.isFixed){this.map.viewPortDiv.appendChild(this.pane);}else{this.map.layerContainerDiv.appendChild(this.pane);}
+this.loadMapObject();if(this.mapObject==null){this.loadWarningMessage();}},removeMap:function(map){if(this.pane&&this.pane.parentNode){this.pane.parentNode.removeChild(this.pane);}
+OpenLayers.Layer.prototype.removeMap.apply(this,arguments);},loadWarningMessage:function(){this.div.style.backgroundColor="darkblue";var viewSize=this.map.getSize();var msgW=Math.min(viewSize.w,300);var msgH=Math.min(viewSize.h,200);var size=new OpenLayers.Size(msgW,msgH);var centerPx=new OpenLayers.Pixel(viewSize.w/2,viewSize.h/2);var topLeft=centerPx.add(-size.w/2,-size.h/2);var div=OpenLayers.Util.createDiv(this.name+"_warning",topLeft,size,null,null,null,"auto");div.style.padding="7px";div.style.backgroundColor="yellow";div.innerHTML=this.getWarningHTML();this.div.appendChild(div);},getWarningHTML:function(){return"";},display:function(display){OpenLayers.Layer.prototype.display.apply(this,arguments);this.pane.style.display=this.div.style.display;},setZIndex:function(zIndex){OpenLayers.Layer.prototype.setZIndex.apply(this,arguments);this.pane.style.zIndex=parseInt(this.div.style.zIndex)+1;},moveTo:function(bounds,zoomChanged,dragging){OpenLayers.Layer.prototype.moveTo.apply(this,arguments);if(this.mapObject!=null){var newCenter=this.map.getCenter();var newZoom=this.map.getZoom();if(newCenter!=null){var moOldCenter=this.getMapObjectCenter();var oldCenter=this.getOLLonLatFromMapObjectLonLat(moOldCenter);var moOldZoom=this.getMapObjectZoom();var oldZoom=this.getOLZoomFromMapObjectZoom(moOldZoom);if(!(newCenter.equals(oldCenter))||!(newZoom==oldZoom)){if(dragging&&this.dragPanMapObject&&this.smoothDragPan){var oldPx=this.map.getViewPortPxFromLonLat(oldCenter);var newPx=this.map.getViewPortPxFromLonLat(newCenter);this.dragPanMapObject(newPx.x-oldPx.x,oldPx.y-newPx.y);}else{var center=this.getMapObjectLonLatFromOLLonLat(newCenter);var zoom=this.getMapObjectZoomFromOLZoom(newZoom);this.setMapObjectCenter(center,zoom,dragging);}}}}},getLonLatFromViewPortPx:function(viewPortPx){var lonlat=null;if((this.mapObject!=null)&&(this.getMapObjectCenter()!=null)){var moPixel=this.getMapObjectPixelFromOLPixel(viewPortPx);var moLonLat=this.getMapObjectLonLatFromMapObjectPixel(moPixel);lonlat=this.getOLLonLatFromMapObjectLonLat(moLonLat);}
+return lonlat;},getViewPortPxFromLonLat:function(lonlat){var viewPortPx=null;if((this.mapObject!=null)&&(this.getMapObjectCenter()!=null)){var moLonLat=this.getMapObjectLonLatFromOLLonLat(lonlat);var moPixel=this.getMapObjectPixelFromMapObjectLonLat(moLonLat);viewPortPx=this.getOLPixelFromMapObjectPixel(moPixel);}
+return viewPortPx;},getOLLonLatFromMapObjectLonLat:function(moLonLat){var olLonLat=null;if(moLonLat!=null){var lon=this.getLongitudeFromMapObjectLonLat(moLonLat);var lat=this.getLatitudeFromMapObjectLonLat(moLonLat);olLonLat=new OpenLayers.LonLat(lon,lat);}
+return olLonLat;},getMapObjectLonLatFromOLLonLat:function(olLonLat){var moLatLng=null;if(olLonLat!=null){moLatLng=this.getMapObjectLonLatFromLonLat(olLonLat.lon,olLonLat.lat);}
+return moLatLng;},getOLPixelFromMapObjectPixel:function(moPixel){var olPixel=null;if(moPixel!=null){var x=this.getXFromMapObjectPixel(moPixel);var y=this.getYFromMapObjectPixel(moPixel);olPixel=new OpenLayers.Pixel(x,y);}
+return olPixel;},getMapObjectPixelFromOLPixel:function(olPixel){var moPixel=null;if(olPixel!=null){moPixel=this.getMapObjectPixelFromXY(olPixel.x,olPixel.y);}
+return moPixel;},CLASS_NAME:"OpenLayers.Layer.EventPane"});OpenLayers.Layer.FixedZoomLevels=OpenLayers.Class({initialize:function(){},initResolutions:function(){var props=new Array('minZoomLevel','maxZoomLevel','numZoomLevels');for(var i=0,len=props.length;i<len;i++){var property=props[i];this[property]=(this.options[property]!=null)?this.options[property]:this.map[property];}
+if((this.minZoomLevel==null)||(this.minZoomLevel<this.MIN_ZOOM_LEVEL)){this.minZoomLevel=this.MIN_ZOOM_LEVEL;}
+var desiredZoomLevels;var limitZoomLevels=this.MAX_ZOOM_LEVEL-this.minZoomLevel+1;if(((this.options.numZoomLevels==null)&&(this.options.maxZoomLevel!=null))||((this.numZoomLevels==null)&&(this.maxZoomLevel!=null))){desiredZoomLevels=this.maxZoomLevel-this.minZoomLevel+1;}else{desiredZoomLevels=this.numZoomLevels;}
+if(desiredZoomLevels!=null){this.numZoomLevels=Math.min(desiredZoomLevels,limitZoomLevels);}else{this.numZoomLevels=limitZoomLevels;}
+this.maxZoomLevel=this.minZoomLevel+this.numZoomLevels-1;if(this.RESOLUTIONS!=null){var resolutionsIndex=0;this.resolutions=[];for(var i=this.minZoomLevel;i<=this.maxZoomLevel;i++){this.resolutions[resolutionsIndex++]=this.RESOLUTIONS[i];}
+this.maxResolution=this.resolutions[0];this.minResolution=this.resolutions[this.resolutions.length-1];}},getResolution:function(){if(this.resolutions!=null){return OpenLayers.Layer.prototype.getResolution.apply(this,arguments);}else{var resolution=null;var viewSize=this.map.getSize();var extent=this.getExtent();if((viewSize!=null)&&(extent!=null)){resolution=Math.max(extent.getWidth()/viewSize.w,extent.getHeight()/viewSize.h);}
+return resolution;}},getExtent:function(){var extent=null;var size=this.map.getSize();var tlPx=new OpenLayers.Pixel(0,0);var tlLL=this.getLonLatFromViewPortPx(tlPx);var brPx=new OpenLayers.Pixel(size.w,size.h);var brLL=this.getLonLatFromViewPortPx(brPx);if((tlLL!=null)&&(brLL!=null)){extent=new OpenLayers.Bounds(tlLL.lon,brLL.lat,brLL.lon,tlLL.lat);}
+return extent;},getZoomForResolution:function(resolution){if(this.resolutions!=null){return OpenLayers.Layer.prototype.getZoomForResolution.apply(this,arguments);}else{var extent=OpenLayers.Layer.prototype.getExtent.apply(this,[]);return this.getZoomForExtent(extent);}},getOLZoomFromMapObjectZoom:function(moZoom){var zoom=null;if(moZoom!=null){zoom=moZoom-this.minZoomLevel;}
+return zoom;},getMapObjectZoomFromOLZoom:function(olZoom){var zoom=null;if(olZoom!=null){zoom=olZoom+this.minZoomLevel;}
+return zoom;},CLASS_NAME:"OpenLayers.Layer.FixedZoomLevels"});OpenLayers.Layer.HTTPRequest=OpenLayers.Class(OpenLayers.Layer,{URL_HASH_FACTOR:(Math.sqrt(5)-1)/2,url:null,params:null,reproject:false,initialize:function(name,url,params,options){var newArguments=arguments;newArguments=[name,options];OpenLayers.Layer.prototype.initialize.apply(this,newArguments);this.url=url;this.params=OpenLayers.Util.extend({},params);},destroy:function(){this.url=null;this.params=null;OpenLayers.Layer.prototype.destroy.apply(this,arguments);},clone:function(obj){if(obj==null){obj=new OpenLayers.Layer.HTTPRequest(this.name,this.url,this.params,this.getOptions());}
+obj=OpenLayers.Layer.prototype.clone.apply(this,[obj]);return obj;},setUrl:function(newUrl){this.url=newUrl;},mergeNewParams:function(newParams){this.params=OpenLayers.Util.extend(this.params,newParams);var ret=this.redraw();if(this.map!=null){this.map.events.triggerEvent("changelayer",{layer:this,property:"params"});}
+return ret;},redraw:function(force){if(force){return this.mergeNewParams({"_olSalt":Math.random()});}else{return OpenLayers.Layer.prototype.redraw.apply(this,[]);}},selectUrl:function(paramString,urls){var product=1;for(var i=0,len=paramString.length;i<len;i++){product*=paramString.charCodeAt(i)*this.URL_HASH_FACTOR;product-=Math.floor(product);}
+return urls[Math.floor(product*urls.length)];},getFullRequestString:function(newParams,altUrl){var url=altUrl||this.url;var allParams=OpenLayers.Util.extend({},this.params);allParams=OpenLayers.Util.extend(allParams,newParams);var paramsString=OpenLayers.Util.getParameterString(allParams);if(url instanceof Array){url=this.selectUrl(paramsString,url);}
+var urlParams=OpenLayers.Util.upperCaseObject(OpenLayers.Util.getParameters(url));for(var key in allParams){if(key.toUpperCase()in urlParams){delete allParams[key];}}
+paramsString=OpenLayers.Util.getParameterString(allParams);return OpenLayers.Util.urlAppend(url,paramsString);},CLASS_NAME:"OpenLayers.Layer.HTTPRequest"});OpenLayers.Layer.Image=OpenLayers.Class(OpenLayers.Layer,{isBaseLayer:true,url:null,extent:null,size:null,tile:null,aspectRatio:null,initialize:function(name,url,extent,size,options){this.url=url;this.extent=extent;this.maxExtent=extent;this.size=size;OpenLayers.Layer.prototype.initialize.apply(this,[name,options]);this.aspectRatio=(this.extent.getHeight()/this.size.h)/(this.extent.getWidth()/this.size.w);},destroy:function(){if(this.tile){this.removeTileMonitoringHooks(this.tile);this.tile.destroy();this.tile=null;}
+OpenLayers.Layer.prototype.destroy.apply(this,arguments);},clone:function(obj){if(obj==null){obj=new OpenLayers.Layer.Image(this.name,this.url,this.extent,this.size,this.getOptions());}
+obj=OpenLayers.Layer.prototype.clone.apply(this,[obj]);return obj;},setMap:function(map){if(this.options.maxResolution==null){this.options.maxResolution=this.aspectRatio*this.extent.getWidth()/this.size.w;}
+OpenLayers.Layer.prototype.setMap.apply(this,arguments);},moveTo:function(bounds,zoomChanged,dragging){OpenLayers.Layer.prototype.moveTo.apply(this,arguments);var firstRendering=(this.tile==null);if(zoomChanged||firstRendering){this.setTileSize();var ul=new OpenLayers.LonLat(this.extent.left,this.extent.top);var ulPx=this.map.getLayerPxFromLonLat(ul);if(firstRendering){this.tile=new OpenLayers.Tile.Image(this,ulPx,this.extent,null,this.tileSize);this.addTileMonitoringHooks(this.tile);}else{this.tile.size=this.tileSize.clone();this.tile.position=ulPx.clone();}
+this.tile.draw();}},setTileSize:function(){var tileWidth=this.extent.getWidth()/this.map.getResolution();var tileHeight=this.extent.getHeight()/this.map.getResolution();this.tileSize=new OpenLayers.Size(tileWidth,tileHeight);},addTileMonitoringHooks:function(tile){tile.onLoadStart=function(){this.events.triggerEvent("loadstart");};tile.events.register("loadstart",this,tile.onLoadStart);tile.onLoadEnd=function(){this.events.triggerEvent("loadend");};tile.events.register("loadend",this,tile.onLoadEnd);tile.events.register("unload",this,tile.onLoadEnd);},removeTileMonitoringHooks:function(tile){tile.unload();tile.events.un({"loadstart":tile.onLoadStart,"loadend":tile.onLoadEnd,"unload":tile.onLoadEnd,scope:this});},setUrl:function(newUrl){this.url=newUrl;this.tile.draw();},getURL:function(bounds){return this.url;},CLASS_NAME:"OpenLayers.Layer.Image"});OpenLayers.Layer.Markers=OpenLayers.Class(OpenLayers.Layer,{isBaseLayer:false,markers:null,drawn:false,initialize:function(name,options){OpenLayers.Layer.prototype.initialize.apply(this,arguments);this.markers=[];},destroy:function(){this.clearMarkers();this.markers=null;OpenLayers.Layer.prototype.destroy.apply(this,arguments);},setOpacity:function(opacity){if(opacity!=this.opacity){this.opacity=opacity;for(var i=0,len=this.markers.length;i<len;i++){this.markers[i].setOpacity(this.opacity);}}},moveTo:function(bounds,zoomChanged,dragging){OpenLayers.Layer.prototype.moveTo.apply(this,arguments);if(zoomChanged||!this.drawn){for(var i=0,len=this.markers.length;i<len;i++){this.drawMarker(this.markers[i]);}
+this.drawn=true;}},addMarker:function(marker){this.markers.push(marker);if(this.opacity!=null){marker.setOpacity(this.opacity);}
+if(this.map&&this.map.getExtent()){marker.map=this.map;this.drawMarker(marker);}},removeMarker:function(marker){if(this.markers&&this.markers.length){OpenLayers.Util.removeItem(this.markers,marker);marker.erase();}},clearMarkers:function(){if(this.markers!=null){while(this.markers.length>0){this.removeMarker(this.markers[0]);}}},drawMarker:function(marker){var px=this.map.getLayerPxFromLonLat(marker.lonlat);if(px==null){marker.display(false);}else{if(!marker.isDrawn()){var markerImg=marker.draw(px);this.div.appendChild(markerImg);}else if(marker.icon){marker.icon.moveTo(px);}}},getDataExtent:function(){var maxExtent=null;if(this.markers&&(this.markers.length>0)){var maxExtent=new OpenLayers.Bounds();for(var i=0,len=this.markers.length;i<len;i++){var marker=this.markers[i];maxExtent.extend(marker.lonlat);}}
+return maxExtent;},CLASS_NAME:"OpenLayers.Layer.Markers"});OpenLayers.Layer.SphericalMercator={getExtent:function(){var extent=null;if(this.sphericalMercator){extent=this.map.calculateBounds();}else{extent=OpenLayers.Layer.FixedZoomLevels.prototype.getExtent.apply(this);}
+return extent;},getLonLatFromViewPortPx:function(viewPortPx){return OpenLayers.Layer.prototype.getLonLatFromViewPortPx.apply(this,arguments);},getViewPortPxFromLonLat:function(lonlat){return OpenLayers.Layer.prototype.getViewPortPxFromLonLat.apply(this,arguments);},initMercatorParameters:function(){this.RESOLUTIONS=[];var maxResolution=156543.0339;for(var zoom=0;zoom<=this.MAX_ZOOM_LEVEL;++zoom){this.RESOLUTIONS[zoom]=maxResolution/Math.pow(2,zoom);}
+this.units="m";this.projection=this.projection||"EPSG:900913";},forwardMercator:function(lon,lat){var x=lon*20037508.34/180;var y=Math.log(Math.tan((90+lat)*Math.PI/360))/(Math.PI/180);y=y*20037508.34/180;return new OpenLayers.LonLat(x,y);},inverseMercator:function(x,y){var lon=(x/20037508.34)*180;var lat=(y/20037508.34)*180;lat=180/Math.PI*(2*Math.atan(Math.exp(lat*Math.PI/180))-Math.PI/2);return new OpenLayers.LonLat(lon,lat);},projectForward:function(point){var lonlat=OpenLayers.Layer.SphericalMercator.forwardMercator(point.x,point.y);point.x=lonlat.lon;point.y=lonlat.lat;return point;},projectInverse:function(point){var lonlat=OpenLayers.Layer.SphericalMercator.inverseMercator(point.x,point.y);point.x=lonlat.lon;point.y=lonlat.lat;return point;}};OpenLayers.Projection.addTransform("EPSG:4326","EPSG:900913",OpenLayers.Layer.SphericalMercator.projectForward);OpenLayers.Projection.addTransform("EPSG:900913","EPSG:4326",OpenLayers.Layer.SphericalMercator.projectInverse);OpenLayers.Tile.WFS=OpenLayers.Class(OpenLayers.Tile,{features:null,url:null,request:null,initialize:function(layer,position,bounds,url,size){OpenLayers.Tile.prototype.initialize.apply(this,arguments);this.url=url;this.features=[];},destroy:function(){OpenLayers.Tile.prototype.destroy.apply(this,arguments);this.destroyAllFeatures();this.features=null;this.url=null;if(this.request){this.request.abort();this.request=null;}},clear:function(){this.destroyAllFeatures();},draw:function(){if(OpenLayers.Tile.prototype.draw.apply(this,arguments)){if(this.isLoading){this.events.triggerEvent("reload");}else{this.isLoading=true;this.events.triggerEvent("loadstart");}
+this.loadFeaturesForRegion(this.requestSuccess);}},loadFeaturesForRegion:function(success,failure){if(this.request){this.request.abort();}
+this.request=OpenLayers.Request.GET({url:this.url,success:success,failure:failure,scope:this});},requestSuccess:function(request){if(this.features){var doc=request.responseXML;if(!doc||!doc.documentElement){doc=request.responseText;}
+if(this.layer.vectorMode){this.layer.addFeatures(this.layer.formatObject.read(doc));}else{var xml=new OpenLayers.Format.XML();if(typeof doc=="string"){doc=xml.read(doc);}
+var resultFeatures=xml.getElementsByTagNameNS(doc,"http://www.opengis.net/gml","featureMember");this.addResults(resultFeatures);}}
+if(this.events){this.events.triggerEvent("loadend");}
+this.request=null;},addResults:function(results){for(var i=0;i<results.length;i++){var feature=new this.layer.featureClass(this.layer,results[i]);this.features.push(feature);}},destroyAllFeatures:function(){while(this.features.length>0){var feature=this.features.shift();feature.destroy();}},CLASS_NAME:"OpenLayers.Tile.WFS"});OpenLayers.Control.DrawFeature=OpenLayers.Class(OpenLayers.Control,{layer:null,callbacks:null,EVENT_TYPES:["featureadded"],multi:false,featureAdded:function(){},handlerOptions:null,initialize:function(layer,handler,options){this.EVENT_TYPES=OpenLayers.Control.DrawFeature.prototype.EVENT_TYPES.concat(OpenLayers.Control.prototype.EVENT_TYPES);OpenLayers.Control.prototype.initialize.apply(this,[options]);this.callbacks=OpenLayers.Util.extend({done:this.drawFeature,modify:function(vertex,feature){this.layer.events.triggerEvent("sketchmodified",{vertex:vertex,feature:feature});},create:function(vertex,feature){this.layer.events.triggerEvent("sketchstarted",{vertex:vertex,feature:feature});}},this.callbacks);this.layer=layer;this.handlerOptions=this.handlerOptions||{};if(!("multi"in this.handlerOptions)){this.handlerOptions.multi=this.multi;}
+var sketchStyle=this.layer.styleMap&&this.layer.styleMap.styles.temporary;if(sketchStyle){this.handlerOptions.layerOptions=OpenLayers.Util.applyDefaults(this.handlerOptions.layerOptions,{styleMap:new OpenLayers.StyleMap({"default":sketchStyle})});}
+this.handler=new handler(this,this.callbacks,this.handlerOptions);},drawFeature:function(geometry){var feature=new OpenLayers.Feature.Vector(geometry);var proceed=this.layer.events.triggerEvent("sketchcomplete",{feature:feature});if(proceed!==false){feature.state=OpenLayers.State.INSERT;this.layer.addFeatures([feature]);this.featureAdded(feature);this.events.triggerEvent("featureadded",{feature:feature});}},CLASS_NAME:"OpenLayers.Control.DrawFeature"});OpenLayers.Control.Measure=OpenLayers.Class(OpenLayers.Control,{EVENT_TYPES:['measure','measurepartial'],handlerOptions:null,callbacks:null,displaySystem:'metric',geodesic:false,displaySystemUnits:{geographic:['dd'],english:['mi','ft','in'],metric:['km','m']},partialDelay:300,delayedTrigger:null,persist:false,initialize:function(handler,options){this.EVENT_TYPES=OpenLayers.Control.Measure.prototype.EVENT_TYPES.concat(OpenLayers.Control.prototype.EVENT_TYPES);OpenLayers.Control.prototype.initialize.apply(this,[options]);this.callbacks=OpenLayers.Util.extend({done:this.measureComplete,point:this.measurePartial},this.callbacks);this.handlerOptions=OpenLayers.Util.extend({persist:this.persist},this.handlerOptions);this.handler=new handler(this,this.callbacks,this.handlerOptions);},cancel:function(){this.handler.cancel();},updateHandler:function(handler,options){var active=this.active;if(active){this.deactivate();}
+this.handler=new handler(this,this.callbacks,options);if(active){this.activate();}},measureComplete:function(geometry){if(this.delayedTrigger){window.clearTimeout(this.delayedTrigger);}
+this.measure(geometry,"measure");},measurePartial:function(point,geometry){if(geometry.getLength()>0){geometry=geometry.clone();this.delayedTrigger=window.setTimeout(OpenLayers.Function.bind(function(){this.measure(geometry,"measurepartial");},this),this.partialDelay);}},measure:function(geometry,eventType){var stat,order;if(geometry.CLASS_NAME.indexOf('LineString')>-1){stat=this.getBestLength(geometry);order=1;}else{stat=this.getBestArea(geometry);order=2;}
+this.events.triggerEvent(eventType,{measure:stat[0],units:stat[1],order:order,geometry:geometry});},getBestArea:function(geometry){var units=this.displaySystemUnits[this.displaySystem];var unit,area;for(var i=0,len=units.length;i<len;++i){unit=units[i];area=this.getArea(geometry,unit);if(area>1){break;}}
+return[area,unit];},getArea:function(geometry,units){var area,geomUnits;if(this.geodesic){area=geometry.getGeodesicArea(this.map.getProjectionObject());geomUnits="m";}else{area=geometry.getArea();geomUnits=this.map.getUnits();}
+var inPerDisplayUnit=OpenLayers.INCHES_PER_UNIT[units];if(inPerDisplayUnit){var inPerMapUnit=OpenLayers.INCHES_PER_UNIT[geomUnits];area*=Math.pow((inPerMapUnit/inPerDisplayUnit),2);}
+return area;},getBestLength:function(geometry){var units=this.displaySystemUnits[this.displaySystem];var unit,length;for(var i=0,len=units.length;i<len;++i){unit=units[i];length=this.getLength(geometry,unit);if(length>1){break;}}
+return[length,unit];},getLength:function(geometry,units){var length,geomUnits;if(this.geodesic){length=geometry.getGeodesicLength(this.map.getProjectionObject());geomUnits="m";}else{length=geometry.getLength();geomUnits=this.map.getUnits();}
+var inPerDisplayUnit=OpenLayers.INCHES_PER_UNIT[units];if(inPerDisplayUnit){var inPerMapUnit=OpenLayers.INCHES_PER_UNIT[geomUnits];length*=(inPerMapUnit/inPerDisplayUnit);}
+return length;},CLASS_NAME:"OpenLayers.Control.Measure"});OpenLayers.Control.ZoomBox=OpenLayers.Class(OpenLayers.Control,{type:OpenLayers.Control.TYPE_TOOL,out:false,alwaysZoom:false,draw:function(){this.handler=new OpenLayers.Handler.Box(this,{done:this.zoomBox},{keyMask:this.keyMask});},zoomBox:function(position){if(position instanceof OpenLayers.Bounds){var bounds;if(!this.out){var minXY=this.map.getLonLatFromPixel(new OpenLayers.Pixel(position.left,position.bottom));var maxXY=this.map.getLonLatFromPixel(new OpenLayers.Pixel(position.right,position.top));bounds=new OpenLayers.Bounds(minXY.lon,minXY.lat,maxXY.lon,maxXY.lat);}else{var pixWidth=Math.abs(position.right-position.left);var pixHeight=Math.abs(position.top-position.bottom);var zoomFactor=Math.min((this.map.size.h/pixHeight),(this.map.size.w/pixWidth));var extent=this.map.getExtent();var center=this.map.getLonLatFromPixel(position.getCenterPixel());var xmin=center.lon-(extent.getWidth()/2)*zoomFactor;var xmax=center.lon+(extent.getWidth()/2)*zoomFactor;var ymin=center.lat-(extent.getHeight()/2)*zoomFactor;var ymax=center.lat+(extent.getHeight()/2)*zoomFactor;bounds=new OpenLayers.Bounds(xmin,ymin,xmax,ymax);}
+var lastZoom=this.map.getZoom();this.map.zoomToExtent(bounds);if(lastZoom==this.map.getZoom()&&this.alwaysZoom==true){this.map.zoomTo(lastZoom+(this.out?-1:1));}}else{if(!this.out){this.map.setCenter(this.map.getLonLatFromPixel(position),this.map.getZoom()+1);}else{this.map.setCenter(this.map.getLonLatFromPixel(position),this.map.getZoom()-1);}}},CLASS_NAME:"OpenLayers.Control.ZoomBox"});OpenLayers.Format.WFSCapabilities.v1_0_0=OpenLayers.Class(OpenLayers.Format.WFSCapabilities.v1,{initialize:function(options){OpenLayers.Format.WFSCapabilities.v1.prototype.initialize.apply(this,[options]);},read_cap_Service:function(capabilities,node){var service={};this.runChildNodes(service,node);capabilities.service=service;},read_cap_Fees:function(service,node){var fees=this.getChildValue(node);if(fees&&fees.toLowerCase()!="none"){service.fees=fees;}},read_cap_AccessConstraints:function(service,node){var constraints=this.getChildValue(node);if(constraints&&constraints.toLowerCase()!="none"){service.accessConstraints=constraints;}},read_cap_OnlineResource:function(service,node){var onlineResource=this.getChildValue(node);if(onlineResource&&onlineResource.toLowerCase()!="none"){service.onlineResource=onlineResource;}},read_cap_Keywords:function(service,node){var keywords=this.getChildValue(node);if(keywords&&keywords.toLowerCase()!="none"){service.keywords=keywords.split(', ');}},read_cap_Capability:function(capabilities,node){var capability={};this.runChildNodes(capability,node);capabilities.capability=capability;},read_cap_Request:function(obj,node){var request={};this.runChildNodes(request,node);obj.request=request;},read_cap_GetFeature:function(request,node){var getfeature={href:{},formats:[]};this.runChildNodes(getfeature,node);request.getfeature=getfeature;},read_cap_ResultFormat:function(obj,node){var children=node.childNodes;var childNode;for(var i=0;i<children.length;i++){childNode=children[i];if(childNode.nodeType==1){obj.formats.push(childNode.nodeName);}}},read_cap_DCPType:function(obj,node){this.runChildNodes(obj,node);},read_cap_HTTP:function(obj,node){this.runChildNodes(obj.href,node);},read_cap_Get:function(obj,node){obj.get=node.getAttribute("onlineResource");},read_cap_Post:function(obj,node){obj.post=node.getAttribute("onlineResource");},CLASS_NAME:"OpenLayers.Format.WFSCapabilities.v1_0_0"});OpenLayers.Format.WFSCapabilities.v1_1_0=OpenLayers.Class(OpenLayers.Format.WFSCapabilities.v1,{initialize:function(options){OpenLayers.Format.WFSCapabilities.v1.prototype.initialize.apply(this,[options]);},CLASS_NAME:"OpenLayers.Format.WFSCapabilities.v1_1_0"});OpenLayers.Format.WKT=OpenLayers.Class(OpenLayers.Format,{initialize:function(options){this.regExes={'typeStr':/^\s*(\w+)\s*\(\s*(.*)\s*\)\s*$/,'spaces':/\s+/,'parenComma':/\)\s*,\s*\(/,'doubleParenComma':/\)\s*\)\s*,\s*\(\s*\(/,'trimParens':/^\s*\(?(.*?)\)?\s*$/};OpenLayers.Format.prototype.initialize.apply(this,[options]);},read:function(wkt){var features,type,str;var matches=this.regExes.typeStr.exec(wkt);if(matches){type=matches[1].toLowerCase();str=matches[2];if(this.parse[type]){features=this.parse[type].apply(this,[str]);}
+if(this.internalProjection&&this.externalProjection){if(features&&features.CLASS_NAME=="OpenLayers.Feature.Vector"){features.geometry.transform(this.externalProjection,this.internalProjection);}else if(features&&type!="geometrycollection"&&typeof features=="object"){for(var i=0,len=features.length;i<len;i++){var component=features[i];component.geometry.transform(this.externalProjection,this.internalProjection);}}}}
+return features;},write:function(features){var collection,geometry,type,data,isCollection;if(features.constructor==Array){collection=features;isCollection=true;}else{collection=[features];isCollection=false;}
+var pieces=[];if(isCollection){pieces.push('GEOMETRYCOLLECTION(');}
+for(var i=0,len=collection.length;i<len;++i){if(isCollection&&i>0){pieces.push(',');}
+geometry=collection[i].geometry;type=geometry.CLASS_NAME.split('.')[2].toLowerCase();if(!this.extract[type]){return null;}
+if(this.internalProjection&&this.externalProjection){geometry=geometry.clone();geometry.transform(this.internalProjection,this.externalProjection);}
+data=this.extract[type].apply(this,[geometry]);pieces.push(type.toUpperCase()+'('+data+')');}
+if(isCollection){pieces.push(')');}
+return pieces.join('');},extract:{'point':function(point){return point.x+' '+point.y;},'multipoint':function(multipoint){var array=[];for(var i=0,len=multipoint.components.length;i<len;++i){array.push('('+
+this.extract.point.apply(this,[multipoint.components[i]])+')');}
+return array.join(',');},'linestring':function(linestring){var array=[];for(var i=0,len=linestring.components.length;i<len;++i){array.push(this.extract.point.apply(this,[linestring.components[i]]));}
+return array.join(',');},'multilinestring':function(multilinestring){var array=[];for(var i=0,len=multilinestring.components.length;i<len;++i){array.push('('+
+this.extract.linestring.apply(this,[multilinestring.components[i]])+')');}
+return array.join(',');},'polygon':function(polygon){var array=[];for(var i=0,len=polygon.components.length;i<len;++i){array.push('('+
+this.extract.linestring.apply(this,[polygon.components[i]])+')');}
+return array.join(',');},'multipolygon':function(multipolygon){var array=[];for(var i=0,len=multipolygon.components.length;i<len;++i){array.push('('+
+this.extract.polygon.apply(this,[multipolygon.components[i]])+')');}
+return array.join(',');}},parse:{'point':function(str){var coords=OpenLayers.String.trim(str).split(this.regExes.spaces);return new OpenLayers.Feature.Vector(new OpenLayers.Geometry.Point(coords[0],coords[1]));},'multipoint':function(str){var point;var points=OpenLayers.String.trim(str).split(this.regExes.parenComma);var components=[];for(var i=0,len=points.length;i<len;++i){point=points[i].replace(this.regExes.trimParens,'$1');components.push(this.parse.point.apply(this,[point]).geometry);}
+return new OpenLayers.Feature.Vector(new OpenLayers.Geometry.MultiPoint(components));},'linestring':function(str){var points=OpenLayers.String.trim(str).split(',');var components=[];for(var i=0,len=points.length;i<len;++i){components.push(this.parse.point.apply(this,[points[i]]).geometry);}
+return new OpenLayers.Feature.Vector(new OpenLayers.Geometry.LineString(components));},'multilinestring':function(str){var line;var lines=OpenLayers.String.trim(str).split(this.regExes.parenComma);var components=[];for(var i=0,len=lines.length;i<len;++i){line=lines[i].replace(this.regExes.trimParens,'$1');components.push(this.parse.linestring.apply(this,[line]).geometry);}
+return new OpenLayers.Feature.Vector(new OpenLayers.Geometry.MultiLineString(components));},'polygon':function(str){var ring,linestring,linearring;var rings=OpenLayers.String.trim(str).split(this.regExes.parenComma);var components=[];for(var i=0,len=rings.length;i<len;++i){ring=rings[i].replace(this.regExes.trimParens,'$1');linestring=this.parse.linestring.apply(this,[ring]).geometry;linearring=new OpenLayers.Geometry.LinearRing(linestring.components);components.push(linearring);}
+return new OpenLayers.Feature.Vector(new OpenLayers.Geometry.Polygon(components));},'multipolygon':function(str){var polygon;var polygons=OpenLayers.String.trim(str).split(this.regExes.doubleParenComma);var components=[];for(var i=0,len=polygons.length;i<len;++i){polygon=polygons[i].replace(this.regExes.trimParens,'$1');components.push(this.parse.polygon.apply(this,[polygon]).geometry);}
+return new OpenLayers.Feature.Vector(new OpenLayers.Geometry.MultiPolygon(components));},'geometrycollection':function(str){str=str.replace(/,\s*([A-Za-z])/g,'|$1');var wktArray=OpenLayers.String.trim(str).split('|');var components=[];for(var i=0,len=wktArray.length;i<len;++i){components.push(OpenLayers.Format.WKT.prototype.read.apply(this,[wktArray[i]]));}
+return components;}},CLASS_NAME:"OpenLayers.Format.WKT"});OpenLayers.Format.WMC.v1=OpenLayers.Class(OpenLayers.Format.XML,{namespaces:{ol:"http://openlayers.org/context",wmc:"http://www.opengis.net/context",sld:"http://www.opengis.net/sld",xlink:"http://www.w3.org/1999/xlink",xsi:"http://www.w3.org/2001/XMLSchema-instance"},schemaLocation:"",getNamespacePrefix:function(uri){var prefix=null;if(uri==null){prefix=this.namespaces[this.defaultPrefix];}else{for(prefix in this.namespaces){if(this.namespaces[prefix]==uri){break;}}}
+return prefix;},defaultPrefix:"wmc",rootPrefix:null,defaultStyleName:"",defaultStyleTitle:"Default",initialize:function(options){OpenLayers.Format.XML.prototype.initialize.apply(this,[options]);},read:function(data){if(typeof data=="string"){data=OpenLayers.Format.XML.prototype.read.apply(this,[data]);}
+var root=data.documentElement;this.rootPrefix=root.prefix;var context={version:root.getAttribute("version")};this.runChildNodes(context,root);return context;},runChildNodes:function(obj,node){var children=node.childNodes;var childNode,processor,prefix,local;for(var i=0,len=children.length;i<len;++i){childNode=children[i];if(childNode.nodeType==1){prefix=this.getNamespacePrefix(childNode.namespaceURI);local=childNode.nodeName.split(":").pop();processor=this["read_"+prefix+"_"+local];if(processor){processor.apply(this,[obj,childNode]);}}}},read_wmc_General:function(context,node){this.runChildNodes(context,node);},read_wmc_BoundingBox:function(context,node){context.projection=node.getAttribute("SRS");context.bounds=new OpenLayers.Bounds(parseFloat(node.getAttribute("minx")),parseFloat(node.getAttribute("miny")),parseFloat(node.getAttribute("maxx")),parseFloat(node.getAttribute("maxy")));},read_wmc_LayerList:function(context,node){context.layersContext=[];this.runChildNodes(context,node);},read_wmc_Layer:function(context,node){var layerContext={visibility:(node.getAttribute("hidden")!="1"),queryable:(node.getAttribute("queryable")=="1"),formats:[],styles:[]};this.runChildNodes(layerContext,node);context.layersContext.push(layerContext);},read_wmc_Extension:function(obj,node){this.runChildNodes(obj,node);},read_ol_units:function(layerContext,node){layerContext.units=this.getChildValue(node);},read_ol_maxExtent:function(obj,node){var bounds=new OpenLayers.Bounds(node.getAttribute("minx"),node.getAttribute("miny"),node.getAttribute("maxx"),node.getAttribute("maxy"));obj.maxExtent=bounds;},read_ol_transparent:function(layerContext,node){layerContext.transparent=this.getChildValue(node);},read_ol_numZoomLevels:function(layerContext,node){layerContext.numZoomLevels=parseInt(this.getChildValue(node));},read_ol_opacity:function(layerContext,node){layerContext.opacity=parseFloat(this.getChildValue(node));},read_ol_singleTile:function(layerContext,node){layerContext.singleTile=(this.getChildValue(node)=="true");},read_ol_tileSize:function(layerContext,node){var obj={"width":node.getAttribute("width"),"height":node.getAttribute("height")};layerContext.tileSize=obj;},read_ol_isBaseLayer:function(layerContext,node){layerContext.isBaseLayer=(this.getChildValue(node)=="true");},read_ol_displayInLayerSwitcher:function(layerContext,node){layerContext.displayInLayerSwitcher=(this.getChildValue(node)=="true");},read_wmc_Server:function(layerContext,node){layerContext.version=node.getAttribute("version");var server={};var links=node.getElementsByTagName("OnlineResource");if(links.length>0){this.read_wmc_OnlineResource(server,links[0]);}
+layerContext.url=server.href;},read_wmc_FormatList:function(layerContext,node){this.runChildNodes(layerContext,node);},read_wmc_Format:function(layerContext,node){var format={value:this.getChildValue(node)};if(node.getAttribute("current")=="1"){format.current=true;}
+layerContext.formats.push(format);},read_wmc_StyleList:function(layerContext,node){this.runChildNodes(layerContext,node);},read_wmc_Style:function(layerContext,node){var style={};this.runChildNodes(style,node);if(node.getAttribute("current")=="1"){style.current=true;}
+layerContext.styles.push(style);},read_wmc_SLD:function(style,node){this.runChildNodes(style,node);},read_sld_StyledLayerDescriptor:function(sld,node){var xml=OpenLayers.Format.XML.prototype.write.apply(this,[node]);sld.body=xml;},read_wmc_OnlineResource:function(obj,node){obj.href=this.getAttributeNS(node,this.namespaces.xlink,"href");},read_wmc_Name:function(obj,node){var name=this.getChildValue(node);if(name){obj.name=name;}},read_wmc_Title:function(obj,node){var title=this.getChildValue(node);if(title){obj.title=title;}},read_wmc_MetadataURL:function(layerContext,node){var metadataURL={};var links=node.getElementsByTagName("OnlineResource");if(links.length>0){this.read_wmc_OnlineResource(metadataURL,links[0]);}
+layerContext.metadataURL=metadataURL.href;},read_wmc_Abstract:function(obj,node){var abst=this.getChildValue(node);if(abst){obj["abstract"]=abst;}},read_wmc_LegendURL:function(style,node){var legend={width:node.getAttribute('width'),height:node.getAttribute('height')};var links=node.getElementsByTagName("OnlineResource");if(links.length>0){this.read_wmc_OnlineResource(legend,links[0]);}
+style.legend=legend;},write:function(context,options){var root=this.createElementDefaultNS("ViewContext");this.setAttributes(root,{version:this.VERSION,id:(options&&typeof options.id=="string")?options.id:OpenLayers.Util.createUniqueID("OpenLayers_Context_")});this.setAttributeNS(root,this.namespaces.xsi,"xsi:schemaLocation",this.schemaLocation);root.appendChild(this.write_wmc_General(context));root.appendChild(this.write_wmc_LayerList(context));return OpenLayers.Format.XML.prototype.write.apply(this,[root]);},createElementDefaultNS:function(name,childValue,attributes){var node=this.createElementNS(this.namespaces[this.defaultPrefix],name);if(childValue){node.appendChild(this.createTextNode(childValue));}
+if(attributes){this.setAttributes(node,attributes);}
+return node;},setAttributes:function(node,obj){var value;for(var name in obj){value=obj[name].toString();if(value.match(/[A-Z]/)){this.setAttributeNS(node,null,name,value);}else{node.setAttribute(name,value);}}},write_wmc_General:function(context){var node=this.createElementDefaultNS("General");if(context.size){node.appendChild(this.createElementDefaultNS("Window",null,{width:context.size.w,height:context.size.h}));}
+var bounds=context.bounds;node.appendChild(this.createElementDefaultNS("BoundingBox",null,{minx:bounds.left.toPrecision(18),miny:bounds.bottom.toPrecision(18),maxx:bounds.right.toPrecision(18),maxy:bounds.top.toPrecision(18),SRS:context.projection}));node.appendChild(this.createElementDefaultNS("Title",context.title));node.appendChild(this.write_ol_MapExtension(context));return node;},write_ol_MapExtension:function(context){var node=this.createElementDefaultNS("Extension");var bounds=context.maxExtent;if(bounds){var maxExtent=this.createElementNS(this.namespaces.ol,"ol:maxExtent");this.setAttributes(maxExtent,{minx:bounds.left.toPrecision(18),miny:bounds.bottom.toPrecision(18),maxx:bounds.right.toPrecision(18),maxy:bounds.top.toPrecision(18)});node.appendChild(maxExtent);}
+return node;},write_wmc_LayerList:function(context){var list=this.createElementDefaultNS("LayerList");for(var i=0,len=context.layersContext.length;i<len;++i){list.appendChild(this.write_wmc_Layer(context.layersContext[i]));}
+return list;},write_wmc_Layer:function(context){var node=this.createElementDefaultNS("Layer",null,{queryable:context.queryable?"1":"0",hidden:context.visibility?"0":"1"});node.appendChild(this.write_wmc_Server(context));node.appendChild(this.createElementDefaultNS("Name",context.name));node.appendChild(this.createElementDefaultNS("Title",context.title));if(context.metadataURL){node.appendChild(this.write_wmc_MetadataURL(context.metadataURL));}
+return node;},write_wmc_LayerExtension:function(context){var node=this.createElementDefaultNS("Extension");var bounds=context.maxExtent;var maxExtent=this.createElementNS(this.namespaces.ol,"ol:maxExtent");this.setAttributes(maxExtent,{minx:bounds.left.toPrecision(18),miny:bounds.bottom.toPrecision(18),maxx:bounds.right.toPrecision(18),maxy:bounds.top.toPrecision(18)});node.appendChild(maxExtent);if(context.tileSize&&!context.singleTile){var size=this.createElementNS(this.namespaces.ol,"ol:tileSize");this.setAttributes(size,context.tileSize);node.appendChild(size);}
+var properties=["transparent","numZoomLevels","units","isBaseLayer","opacity","displayInLayerSwitcher","singleTile"];var child;for(var i=0,len=properties.length;i<len;++i){child=this.createOLPropertyNode(context,properties[i]);if(child){node.appendChild(child);}}
+return node;},createOLPropertyNode:function(obj,prop){var node=null;if(obj[prop]!=null){node=this.createElementNS(this.namespaces.ol,"ol:"+prop);node.appendChild(this.createTextNode(obj[prop].toString()));}
+return node;},write_wmc_Server:function(context){var node=this.createElementDefaultNS("Server");this.setAttributes(node,{service:"OGC:WMS",version:context.version});node.appendChild(this.write_wmc_OnlineResource(context.url));return node;},write_wmc_MetadataURL:function(metadataURL){var node=this.createElementDefaultNS("MetadataURL");node.appendChild(this.write_wmc_OnlineResource(metadataURL));return node;},write_wmc_FormatList:function(context){var node=this.createElementDefaultNS("FormatList");for(var i=0,len=context.formats.length;i<len;i++){var format=context.formats[i];node.appendChild(this.createElementDefaultNS("Format",format.value,(format.current&&format.current==true)?{current:"1"}:null));}
+return node;},write_wmc_StyleList:function(layer){var node=this.createElementDefaultNS("StyleList");var styles=layer.styles;if(styles&&styles instanceof Array){var sld;for(var i=0,len=styles.length;i<len;i++){var s=styles[i];var style=this.createElementDefaultNS("Style",null,(s.current&&s.current==true)?{current:"1"}:null);if(s.href){sld=this.createElementDefaultNS("SLD");var link=this.write_wmc_OnlineResource(s.href);sld.appendChild(link);sld.appendChild(this.createElementDefaultNS("Name",s.name));if(s.title){sld.appendChild(this.createElementDefaultNS("Title",s.title));}
+style.appendChild(sld);}else if(s.body){sld=this.createElementDefaultNS("SLD");var doc=OpenLayers.Format.XML.prototype.read.apply(this,[s.body]);var imported=doc.documentElement;if(sld.ownerDocument&&sld.ownerDocument.importNode){imported=sld.ownerDocument.importNode(imported,true);}
+sld.appendChild(imported);sld.appendChild(this.createElementDefaultNS("Name",s.name));if(s.title){sld.appendChild(this.createElementDefaultNS("Title",s.title));}
+style.appendChild(sld);}else{style.appendChild(this.createElementDefaultNS("Name",s.name));style.appendChild(this.createElementDefaultNS("Title",s.title));if(s['abstract']){style.appendChild(this.createElementDefaultNS("Abstract",s['abstract']));}}
+node.appendChild(style);}}
+return node;},write_wmc_OnlineResource:function(href){var node=this.createElementDefaultNS("OnlineResource");this.setAttributeNS(node,this.namespaces.xlink,"xlink:type","simple");this.setAttributeNS(node,this.namespaces.xlink,"xlink:href",href);return node;},CLASS_NAME:"OpenLayers.Format.WMC.v1"});OpenLayers.Format.WMSCapabilities.v1_1=OpenLayers.Class(OpenLayers.Format.WMSCapabilities.v1,{readers:{"wms":OpenLayers.Util.applyDefaults({"WMT_MS_Capabilities":function(node,obj){this.readChildNodes(node,obj);},"Keyword":function(node,obj){if(obj.keywords){obj.keywords.push(this.getChildValue(node));}},"DescribeLayer":function(node,obj){obj.describelayer={formats:[]};this.readChildNodes(node,obj.describelayer);},"GetLegendGraphic":function(node,obj){obj.getlegendgraphic={formats:[]};this.readChildNodes(node,obj.getlegendgraphic);},"GetStyles":function(node,obj){obj.getstyles={formats:[]};this.readChildNodes(node,obj.getstyles);},"PutStyles":function(node,obj){obj.putstyles={formats:[]};this.readChildNodes(node,obj.putstyles);},"UserDefinedSymbolization":function(node,obj){var userSymbols={supportSLD:parseInt(node.getAttribute("SupportSLD"))==1,userLayer:parseInt(node.getAttribute("UserLayer"))==1,userStyle:parseInt(node.getAttribute("UserStyle"))==1,remoteWFS:parseInt(node.getAttribute("RemoteWFS"))==1};obj.userSymbols=userSymbols;},"LatLonBoundingBox":function(node,obj){obj.llbbox=[parseFloat(node.getAttribute("minx")),parseFloat(node.getAttribute("miny")),parseFloat(node.getAttribute("maxx")),parseFloat(node.getAttribute("maxy"))];},"BoundingBox":function(node,obj){var bbox=OpenLayers.Format.WMSCapabilities.v1.prototype.readers["wms"].BoundingBox.apply(this,[node,obj]);bbox.srs=node.getAttribute("SRS");obj.bbox[bbox.srs]=bbox;},"ScaleHint":function(node,obj){var min=node.getAttribute("min");var max=node.getAttribute("max");var rad2=Math.pow(2,0.5);var ipm=OpenLayers.INCHES_PER_UNIT["m"];obj.maxScale=parseFloat(((min/rad2)*ipm*OpenLayers.DOTS_PER_INCH).toPrecision(13));obj.minScale=parseFloat(((max/rad2)*ipm*OpenLayers.DOTS_PER_INCH).toPrecision(13));},"Dimension":function(node,obj){var name=node.getAttribute("name").toLowerCase();var dim={name:name,units:node.getAttribute("units"),unitsymbol:node.getAttribute("unitSymbol")};obj.dimensions[dim.name]=dim;},"Extent":function(node,obj){var name=node.getAttribute("name").toLowerCase();if(name in obj["dimensions"]){var extent=obj.dimensions[name];extent.nearestVal=node.getAttribute("nearestValue")==="1";extent.multipleVal=node.getAttribute("multipleValues")==="1";extent.current=node.getAttribute("current")==="1";extent["default"]=node.getAttribute("default")||"";var values=this.getChildValue(node);extent.values=values.split(",");}}},OpenLayers.Format.WMSCapabilities.v1.prototype.readers["wms"])},CLASS_NAME:"OpenLayers.Format.WMSCapabilities.v1_1"});OpenLayers.Format.WMSCapabilities.v1_3=OpenLayers.Class(OpenLayers.Format.WMSCapabilities.v1,{readers:{"wms":OpenLayers.Util.applyDefaults({"WMS_Capabilities":function(node,obj){this.readChildNodes(node,obj);},"LayerLimit":function(node,obj){obj.layerLimit=parseInt(this.getChildValue(node));},"MaxWidth":function(node,obj){obj.maxWidth=parseInt(this.getChildValue(node));},"MaxHeight":function(node,obj){obj.maxHeight=parseInt(this.getChildValue(node));},"BoundingBox":function(node,obj){var bbox=OpenLayers.Format.WMSCapabilities.v1.prototype.readers["wms"].BoundingBox.apply(this,[node,obj]);bbox.srs=node.getAttribute("CRS");obj.bbox[bbox.srs]=bbox;},"CRS":function(node,obj){this.readers.wms.SRS.apply(this,[node,obj]);},"EX_GeographicBoundingBox":function(node,obj){obj.llbbox=[];this.readChildNodes(node,obj.llbbox);},"westBoundLongitude":function(node,obj){obj[0]=this.getChildValue(node);},"eastBoundLongitude":function(node,obj){obj[2]=this.getChildValue(node);},"southBoundLatitude":function(node,obj){obj[1]=this.getChildValue(node);},"northBoundLatitude":function(node,obj){obj[3]=this.getChildValue(node);},"MinScaleDenominator":function(node,obj){obj.maxScale=parseFloat(this.getChildValue(node)).toPrecision(16);},"MaxScaleDenominator":function(node,obj){obj.minScale=parseFloat(this.getChildValue(node)).toPrecision(16);},"Dimension":function(node,obj){var name=node.getAttribute("name").toLowerCase();var dim={name:name,units:node.getAttribute("units"),unitsymbol:node.getAttribute("unitSymbol"),nearestVal:node.getAttribute("nearestValue")==="1",multipleVal:node.getAttribute("multipleValues")==="1","default":node.getAttribute("default")||"",current:node.getAttribute("current")==="1",values:this.getChildValue(node).split(",")};obj.dimensions[dim.name]=dim;},"Keyword":function(node,obj){var keyword={value:this.getChildValue(node),vocabulary:node.getAttribute("vocabulary")};if(obj.keywords){obj.keywords.push(keyword);}}},OpenLayers.Format.WMSCapabilities.v1.prototype.readers["wms"]),"sld":{"UserDefinedSymbolization":function(node,obj){this.readers.wms.UserDefinedSymbolization.apply(this,[node,obj]);obj.userSymbols.inlineFeature=parseInt(node.getAttribute("InlineFeature"))==1;obj.userSymbols.remoteWCS=parseInt(node.getAttribute("RemoteWCS"))==1;},"DescribeLayer":function(node,obj){this.readers.wms.DescribeLayer.apply(this,[node,obj]);},"GetLegendGraphic":function(node,obj){this.readers.wms.GetLegendGraphic.apply(this,[node,obj]);}}},CLASS_NAME:"OpenLayers.Format.WMSCapabilities.v1_3"});OpenLayers.Format.WMTSCapabilities.v1_0_0=OpenLayers.Class(OpenLayers.Format.OWSCommon.v1_1_0,{version:"1.0.0",namespaces:{ows:"http://www.opengis.net/ows/1.1",wmts:"http://www.opengis.net/wmts/1.0",xlink:"http://www.w3.org/1999/xlink"},yx:null,defaultPrefix:"wmts",initialize:function(options){OpenLayers.Format.XML.prototype.initialize.apply(this,[options]);this.options=options;var yx=OpenLayers.Util.extend({},OpenLayers.Format.WMTSCapabilities.prototype.yx);this.yx=OpenLayers.Util.extend(yx,this.yx);},read:function(data){if(typeof data=="string"){data=OpenLayers.Format.XML.prototype.read.apply(this,[data]);}
+if(data&&data.nodeType==9){data=data.documentElement;}
+var capabilities={};this.readNode(data,capabilities);capabilities.version=this.version;return capabilities;},readers:{"wmts":{"Capabilities":function(node,obj){this.readChildNodes(node,obj);},"Contents":function(node,obj){obj.contents={};obj.contents.layers=[];obj.contents.tileMatrixSets={};this.readChildNodes(node,obj.contents);},"Layer":function(node,obj){var layer={styles:[],formats:[],tileMatrixSetLinks:[]};layer.layers=[];this.readChildNodes(node,layer);obj.layers.push(layer);},"Style":function(node,obj){var style={};style.isDefault=(node.getAttribute("isDefault")==="true");this.readChildNodes(node,style);obj.styles.push(style);},"Format":function(node,obj){obj.formats.push(this.getChildValue(node));},"TileMatrixSetLink":function(node,obj){var tileMatrixSetLink={};this.readChildNodes(node,tileMatrixSetLink);obj.tileMatrixSetLinks.push(tileMatrixSetLink);},"TileMatrixSet":function(node,obj){if(obj.layers){var tileMatrixSet={matrixIds:[]};this.readChildNodes(node,tileMatrixSet);obj.tileMatrixSets[tileMatrixSet.identifier]=tileMatrixSet;}else{obj.tileMatrixSet=this.getChildValue(node);}},"TileMatrix":function(node,obj){var tileMatrix={supportedCRS:obj.supportedCRS};this.readChildNodes(node,tileMatrix);obj.matrixIds.push(tileMatrix);},"ScaleDenominator":function(node,obj){obj.scaleDenominator=parseFloat(this.getChildValue(node));},"TopLeftCorner":function(node,obj){var topLeftCorner=this.getChildValue(node);var coords=topLeftCorner.split(" ");var yx;if(obj.supportedCRS){var crs=obj.supportedCRS.replace(/urn:ogc:def:crs:(\w+):.+:(\w+)$/,"urn:ogc:def:crs:$1::$2");yx=!!this.yx[crs];}
+if(yx){obj.topLeftCorner=new OpenLayers.LonLat(coords[1],coords[0]);}else{obj.topLeftCorner=new OpenLayers.LonLat(coords[0],coords[1]);}},"TileWidth":function(node,obj){obj.tileWidth=parseInt(this.getChildValue(node));},"TileHeight":function(node,obj){obj.tileHeight=parseInt(this.getChildValue(node));},"MatrixWidth":function(node,obj){obj.matrixWidth=parseInt(this.getChildValue(node));},"MatrixHeight":function(node,obj){obj.matrixHeight=parseInt(this.getChildValue(node));},"WSDL":function(node,obj){obj.wsdl={};obj.wsdl.href=node.getAttribute("xlink:href");},"ServiceMetadataURL":function(node,obj){obj.serviceMetadataUrl={};obj.serviceMetadataUrl.href=node.getAttribute("xlink:href");}},"ows":OpenLayers.Format.OWSCommon.v1_1_0.prototype.readers["ows"]},CLASS_NAME:"OpenLayers.Format.WMTSCapabilities.v1_0_0"});OpenLayers.Layer.Boxes=OpenLayers.Class(OpenLayers.Layer.Markers,{initialize:function(name,options){OpenLayers.Layer.Markers.prototype.initialize.apply(this,arguments);},drawMarker:function(marker){var bounds=marker.bounds;var topleft=this.map.getLayerPxFromLonLat(new OpenLayers.LonLat(bounds.left,bounds.top));var botright=this.map.getLayerPxFromLonLat(new OpenLayers.LonLat(bounds.right,bounds.bottom));if(botright==null||topleft==null){marker.display(false);}else{var sz=new OpenLayers.Size(Math.max(1,botright.x-topleft.x),Math.max(1,botright.y-topleft.y));var markerDiv=marker.draw(topleft,sz);if(!marker.drawn){this.div.appendChild(markerDiv);marker.drawn=true;}}},removeMarker:function(marker){OpenLayers.Util.removeItem(this.markers,marker);if((marker.div!=null)&&(marker.div.parentNode==this.div)){this.div.removeChild(marker.div);}},CLASS_NAME:"OpenLayers.Layer.Boxes"});OpenLayers.Layer.GeoRSS=OpenLayers.Class(OpenLayers.Layer.Markers,{location:null,features:null,formatOptions:null,selectedFeature:null,icon:null,popupSize:null,useFeedTitle:true,initialize:function(name,location,options){OpenLayers.Layer.Markers.prototype.initialize.apply(this,[name,options]);this.location=location;this.features=[];},destroy:function(){OpenLayers.Layer.Markers.prototype.destroy.apply(this,arguments);this.clearFeatures();this.features=null;},loadRSS:function(){if(!this.loaded){this.events.triggerEvent("loadstart");OpenLayers.Request.GET({url:this.location,success:this.parseData,scope:this});this.loaded=true;}},moveTo:function(bounds,zoomChanged,minor){OpenLayers.Layer.Markers.prototype.moveTo.apply(this,arguments);if(this.visibility&&!this.loaded){this.loadRSS();}},parseData:function(ajaxRequest){var doc=ajaxRequest.responseXML;if(!doc||!doc.documentElement){doc=OpenLayers.Format.XML.prototype.read(ajaxRequest.responseText);}
+if(this.useFeedTitle){var name=null;try{name=doc.getElementsByTagNameNS('*','title')[0].firstChild.nodeValue;}
+catch(e){name=doc.getElementsByTagName('title')[0].firstChild.nodeValue;}
+if(name){this.setName(name);}}
+var options={};OpenLayers.Util.extend(options,this.formatOptions);if(this.map&&!this.projection.equals(this.map.getProjectionObject())){options.externalProjection=this.projection;options.internalProjection=this.map.getProjectionObject();}
+var format=new OpenLayers.Format.GeoRSS(options);var features=format.read(doc);for(var i=0,len=features.length;i<len;i++){var data={};var feature=features[i];if(!feature.geometry){continue;}
+var title=feature.attributes.title?feature.attributes.title:"Untitled";var description=feature.attributes.description?feature.attributes.description:"No description.";var link=feature.attributes.link?feature.attributes.link:"";var location=feature.geometry.getBounds().getCenterLonLat();data.icon=this.icon==null?OpenLayers.Marker.defaultIcon():this.icon.clone();data.popupSize=this.popupSize?this.popupSize.clone():new OpenLayers.Size(250,120);if(title||description){data.title=title;data.description=description;var contentHTML='<div class="olLayerGeoRSSClose">[x]</div>';contentHTML+='<div class="olLayerGeoRSSTitle">';if(link){contentHTML+='<a class="link" href="'+link+'" target="_blank">';}
+contentHTML+=title;if(link){contentHTML+='</a>';}
+contentHTML+='</div>';contentHTML+='<div style="" class="olLayerGeoRSSDescription">';contentHTML+=description;contentHTML+='</div>';data['popupContentHTML']=contentHTML;}
+var feature=new OpenLayers.Feature(this,location,data);this.features.push(feature);var marker=feature.createMarker();marker.events.register('click',feature,this.markerClick);this.addMarker(marker);}
+this.events.triggerEvent("loadend");},markerClick:function(evt){var sameMarkerClicked=(this==this.layer.selectedFeature);this.layer.selectedFeature=(!sameMarkerClicked)?this:null;for(var i=0,len=this.layer.map.popups.length;i<len;i++){this.layer.map.removePopup(this.layer.map.popups[i]);}
+if(!sameMarkerClicked){var popup=this.createPopup();OpenLayers.Event.observe(popup.div,"click",OpenLayers.Function.bind(function(){for(var i=0,len=this.layer.map.popups.length;i<len;i++){this.layer.map.removePopup(this.layer.map.popups[i]);}},this));this.layer.map.addPopup(popup);}
+OpenLayers.Event.stop(evt);},clearFeatures:function(){if(this.features!=null){while(this.features.length>0){var feature=this.features[0];OpenLayers.Util.removeItem(this.features,feature);feature.destroy();}}},CLASS_NAME:"OpenLayers.Layer.GeoRSS"});OpenLayers.Layer.Google=OpenLayers.Class(OpenLayers.Layer.EventPane,OpenLayers.Layer.FixedZoomLevels,{MIN_ZOOM_LEVEL:0,MAX_ZOOM_LEVEL:21,RESOLUTIONS:[1.40625,0.703125,0.3515625,0.17578125,0.087890625,0.0439453125,0.02197265625,0.010986328125,0.0054931640625,0.00274658203125,0.001373291015625,0.0006866455078125,0.00034332275390625,0.000171661376953125,0.0000858306884765625,0.00004291534423828125,0.00002145767211914062,0.00001072883605957031,0.00000536441802978515,0.00000268220901489257,0.0000013411045074462891,0.00000067055225372314453],type:null,wrapDateLine:true,sphericalMercator:false,version:null,initialize:function(name,options){options=options||{};if(!options.version){options.version=typeof GMap2==="function"?"2":"3";}
+var mixin=OpenLayers.Layer.Google["v"+
+options.version.replace(/\./g,"_")];if(mixin){OpenLayers.Util.applyDefaults(options,mixin);}else{throw"Unsupported Google Maps API version: "+options.version;}
+OpenLayers.Util.applyDefaults(options,mixin.DEFAULTS);if(options.maxExtent){options.maxExtent=options.maxExtent.clone();}
+OpenLayers.Layer.EventPane.prototype.initialize.apply(this,[name,options]);OpenLayers.Layer.FixedZoomLevels.prototype.initialize.apply(this,[name,options]);if(this.sphericalMercator){OpenLayers.Util.extend(this,OpenLayers.Layer.SphericalMercator);this.initMercatorParameters();}},clone:function(){return new OpenLayers.Layer.Google(this.name,this.getOptions());},setVisibility:function(visible){var opacity=this.opacity==null?1:this.opacity;OpenLayers.Layer.EventPane.prototype.setVisibility.apply(this,arguments);this.setOpacity(opacity);},display:function(visible){if(!this._dragging){this.setGMapVisibility(visible);}
+OpenLayers.Layer.EventPane.prototype.display.apply(this,arguments);},moveTo:function(bounds,zoomChanged,dragging){this._dragging=dragging;OpenLayers.Layer.EventPane.prototype.moveTo.apply(this,arguments);delete this._dragging;},setOpacity:function(opacity){if(opacity!==this.opacity){if(this.map!=null){this.map.events.triggerEvent("changelayer",{layer:this,property:"opacity"});}
+this.opacity=opacity;}
+if(this.getVisibility()){var container=this.getMapContainer();OpenLayers.Util.modifyDOMElement(container,null,null,null,null,null,null,opacity);}},destroy:function(){if(this.map){this.setGMapVisibility(false);var cache=OpenLayers.Layer.Google.cache[this.map.id];if(cache&&cache.count<=1){this.removeGMapElements();}}
+OpenLayers.Layer.EventPane.prototype.destroy.apply(this,arguments);},removeGMapElements:function(){var cache=OpenLayers.Layer.Google.cache[this.map.id];if(cache){var container=this.mapObject&&this.getMapContainer();if(container&&container.parentNode){container.parentNode.removeChild(container);}
+var termsOfUse=cache.termsOfUse;if(termsOfUse&&termsOfUse.parentNode){termsOfUse.parentNode.removeChild(termsOfUse);}
+var poweredBy=cache.poweredBy;if(poweredBy&&poweredBy.parentNode){poweredBy.parentNode.removeChild(poweredBy);}}},removeMap:function(map){if(this.visibility&&this.mapObject){this.setGMapVisibility(false);}
+var cache=OpenLayers.Layer.Google.cache[map.id];if(cache){if(cache.count<=1){this.removeGMapElements();delete OpenLayers.Layer.Google.cache[map.id];}else{--cache.count;}}
+delete this.termsOfUse;delete this.poweredBy;delete this.mapObject;delete this.dragObject;OpenLayers.Layer.EventPane.prototype.removeMap.apply(this,arguments);},getOLBoundsFromMapObjectBounds:function(moBounds){var olBounds=null;if(moBounds!=null){var sw=moBounds.getSouthWest();var ne=moBounds.getNorthEast();if(this.sphericalMercator){sw=this.forwardMercator(sw.lng(),sw.lat());ne=this.forwardMercator(ne.lng(),ne.lat());}else{sw=new OpenLayers.LonLat(sw.lng(),sw.lat());ne=new OpenLayers.LonLat(ne.lng(),ne.lat());}
+olBounds=new OpenLayers.Bounds(sw.lon,sw.lat,ne.lon,ne.lat);}
+return olBounds;},getWarningHTML:function(){return OpenLayers.i18n("googleWarning");},getMapObjectCenter:function(){return this.mapObject.getCenter();},getMapObjectZoom:function(){return this.mapObject.getZoom();},getLongitudeFromMapObjectLonLat:function(moLonLat){return this.sphericalMercator?this.forwardMercator(moLonLat.lng(),moLonLat.lat()).lon:moLonLat.lng();},getLatitudeFromMapObjectLonLat:function(moLonLat){var lat=this.sphericalMercator?this.forwardMercator(moLonLat.lng(),moLonLat.lat()).lat:moLonLat.lat();return lat;},getXFromMapObjectPixel:function(moPixel){return moPixel.x;},getYFromMapObjectPixel:function(moPixel){return moPixel.y;},CLASS_NAME:"OpenLayers.Layer.Google"});OpenLayers.Layer.Google.cache={};OpenLayers.Layer.Google.v2={termsOfUse:null,poweredBy:null,dragObject:null,loadMapObject:function(){if(!this.type){this.type=G_NORMAL_MAP;}
+var mapObject,termsOfUse,poweredBy;var cache=OpenLayers.Layer.Google.cache[this.map.id];if(cache){mapObject=cache.mapObject;termsOfUse=cache.termsOfUse;poweredBy=cache.poweredBy;++cache.count;}else{var container=this.map.viewPortDiv;var div=document.createElement("div");div.id=this.map.id+"_GMap2Container";div.style.position="absolute";div.style.width="100%";div.style.height="100%";container.appendChild(div);try{mapObject=new GMap2(div);termsOfUse=div.lastChild;container.appendChild(termsOfUse);termsOfUse.style.zIndex="1100";termsOfUse.style.right="";termsOfUse.style.bottom="";termsOfUse.className="olLayerGoogleCopyright";poweredBy=div.lastChild;container.appendChild(poweredBy);poweredBy.style.zIndex="1100";poweredBy.style.right="";poweredBy.style.bottom="";poweredBy.className="olLayerGooglePoweredBy gmnoprint";}catch(e){throw(e);}
+OpenLayers.Layer.Google.cache[this.map.id]={mapObject:mapObject,termsOfUse:termsOfUse,poweredBy:poweredBy,count:1};}
+this.mapObject=mapObject;this.termsOfUse=termsOfUse;this.poweredBy=poweredBy;if(OpenLayers.Util.indexOf(this.mapObject.getMapTypes(),this.type)===-1){this.mapObject.addMapType(this.type);}
+if(typeof mapObject.getDragObject=="function"){this.dragObject=mapObject.getDragObject();}else{this.dragPanMapObject=null;}
+if(this.isBaseLayer===false){this.setGMapVisibility(this.div.style.display!=="none");}},onMapResize:function(){if(this.visibility&&this.mapObject.isLoaded()){this.mapObject.checkResize();}else{if(!this._resized){var layer=this;var handle=GEvent.addListener(this.mapObject,"load",function(){GEvent.removeListener(handle);delete layer._resized;layer.mapObject.checkResize();layer.moveTo(layer.map.getCenter(),layer.map.getZoom());});}
+this._resized=true;}},setGMapVisibility:function(visible){var cache=OpenLayers.Layer.Google.cache[this.map.id];if(cache){var container=this.mapObject.getContainer();if(visible===true){this.mapObject.setMapType(this.type);container.style.display="";this.termsOfUse.style.left="";this.termsOfUse.style.display="";this.poweredBy.style.display="";cache.displayed=this.id;}else{if(cache.displayed===this.id){delete cache.displayed;}
+if(!cache.displayed){container.style.display="none";this.termsOfUse.style.display="none";this.termsOfUse.style.left="-9999px";this.poweredBy.style.display="none";}}}},getMapContainer:function(){return this.mapObject.getContainer();},getMapObjectBoundsFromOLBounds:function(olBounds){var moBounds=null;if(olBounds!=null){var sw=this.sphericalMercator?this.inverseMercator(olBounds.bottom,olBounds.left):new OpenLayers.LonLat(olBounds.bottom,olBounds.left);var ne=this.sphericalMercator?this.inverseMercator(olBounds.top,olBounds.right):new OpenLayers.LonLat(olBounds.top,olBounds.right);moBounds=new GLatLngBounds(new GLatLng(sw.lat,sw.lon),new GLatLng(ne.lat,ne.lon));}
+return moBounds;},setMapObjectCenter:function(center,zoom){this.mapObject.setCenter(center,zoom);},dragPanMapObject:function(dX,dY){this.dragObject.moveBy(new GSize(-dX,dY));},getMapObjectLonLatFromMapObjectPixel:function(moPixel){return this.mapObject.fromContainerPixelToLatLng(moPixel);},getMapObjectPixelFromMapObjectLonLat:function(moLonLat){return this.mapObject.fromLatLngToContainerPixel(moLonLat);},getMapObjectZoomFromMapObjectBounds:function(moBounds){return this.mapObject.getBoundsZoomLevel(moBounds);},getMapObjectLonLatFromLonLat:function(lon,lat){var gLatLng;if(this.sphericalMercator){var lonlat=this.inverseMercator(lon,lat);gLatLng=new GLatLng(lonlat.lat,lonlat.lon);}else{gLatLng=new GLatLng(lat,lon);}
+return gLatLng;},getMapObjectPixelFromXY:function(x,y){return new GPoint(x,y);}};OpenLayers.Layer.Grid=OpenLayers.Class(OpenLayers.Layer.HTTPRequest,{tileSize:null,grid:null,singleTile:false,ratio:1.5,buffer:2,numLoadingTiles:0,initialize:function(name,url,params,options){OpenLayers.Layer.HTTPRequest.prototype.initialize.apply(this,arguments);this.events.addEventType("tileloaded");this.grid=[];},destroy:function(){this.clearGrid();this.grid=null;this.tileSize=null;OpenLayers.Layer.HTTPRequest.prototype.destroy.apply(this,arguments);},clearGrid:function(){if(this.grid){for(var iRow=0,len=this.grid.length;iRow<len;iRow++){var row=this.grid[iRow];for(var iCol=0,clen=row.length;iCol<clen;iCol++){var tile=row[iCol];this.removeTileMonitoringHooks(tile);tile.destroy();}}
+this.grid=[];}},clone:function(obj){if(obj==null){obj=new OpenLayers.Layer.Grid(this.name,this.url,this.params,this.getOptions());}
+obj=OpenLayers.Layer.HTTPRequest.prototype.clone.apply(this,[obj]);if(this.tileSize!=null){obj.tileSize=this.tileSize.clone();}
+obj.grid=[];return obj;},moveTo:function(bounds,zoomChanged,dragging){OpenLayers.Layer.HTTPRequest.prototype.moveTo.apply(this,arguments);bounds=bounds||this.map.getExtent();if(bounds!=null){var forceReTile=!this.grid.length||zoomChanged;var tilesBounds=this.getTilesBounds();if(this.singleTile){if(forceReTile||(!dragging&&!tilesBounds.containsBounds(bounds))){this.initSingleTile(bounds);}}else{if(forceReTile||!tilesBounds.containsBounds(bounds,true)){this.initGriddedTiles(bounds);}else{this.moveGriddedTiles(bounds);}}}},setTileSize:function(size){if(this.singleTile){size=this.map.getSize();size.h=parseInt(size.h*this.ratio);size.w=parseInt(size.w*this.ratio);}
+OpenLayers.Layer.HTTPRequest.prototype.setTileSize.apply(this,[size]);},getGridBounds:function(){var msg="The getGridBounds() function is deprecated. It will be "+"removed in 3.0. Please use getTilesBounds() instead.";OpenLayers.Console.warn(msg);return this.getTilesBounds();},getTilesBounds:function(){var bounds=null;if(this.grid.length){var bottom=this.grid.length-1;var bottomLeftTile=this.grid[bottom][0];var right=this.grid[0].length-1;var topRightTile=this.grid[0][right];bounds=new OpenLayers.Bounds(bottomLeftTile.bounds.left,bottomLeftTile.bounds.bottom,topRightTile.bounds.right,topRightTile.bounds.top);}
+return bounds;},initSingleTile:function(bounds){var center=bounds.getCenterLonLat();var tileWidth=bounds.getWidth()*this.ratio;var tileHeight=bounds.getHeight()*this.ratio;var tileBounds=new OpenLayers.Bounds(center.lon-(tileWidth/2),center.lat-(tileHeight/2),center.lon+(tileWidth/2),center.lat+(tileHeight/2));var ul=new OpenLayers.LonLat(tileBounds.left,tileBounds.top);var px=this.map.getLayerPxFromLonLat(ul);if(!this.grid.length){this.grid[0]=[];}
+var tile=this.grid[0][0];if(!tile){tile=this.addTile(tileBounds,px);this.addTileMonitoringHooks(tile);tile.draw();this.grid[0][0]=tile;}else{tile.moveTo(tileBounds,px);}
+this.removeExcessTiles(1,1);},calculateGridLayout:function(bounds,extent,resolution){var tilelon=resolution*this.tileSize.w;var tilelat=resolution*this.tileSize.h;var offsetlon=bounds.left-extent.left;var tilecol=Math.floor(offsetlon/tilelon)-this.buffer;var tilecolremain=offsetlon/tilelon-tilecol;var tileoffsetx=-tilecolremain*this.tileSize.w;var tileoffsetlon=extent.left+tilecol*tilelon;var offsetlat=bounds.top-(extent.bottom+tilelat);var tilerow=Math.ceil(offsetlat/tilelat)+this.buffer;var tilerowremain=tilerow-offsetlat/tilelat;var tileoffsety=-tilerowremain*this.tileSize.h;var tileoffsetlat=extent.bottom+tilerow*tilelat;return{tilelon:tilelon,tilelat:tilelat,tileoffsetlon:tileoffsetlon,tileoffsetlat:tileoffsetlat,tileoffsetx:tileoffsetx,tileoffsety:tileoffsety};},initGriddedTiles:function(bounds){var viewSize=this.map.getSize();var minRows=Math.ceil(viewSize.h/this.tileSize.h)+
+Math.max(1,2*this.buffer);var minCols=Math.ceil(viewSize.w/this.tileSize.w)+
+Math.max(1,2*this.buffer);var extent=this.getMaxExtent();var resolution=this.map.getResolution();var tileLayout=this.calculateGridLayout(bounds,extent,resolution);var tileoffsetx=Math.round(tileLayout.tileoffsetx);var tileoffsety=Math.round(tileLayout.tileoffsety);var tileoffsetlon=tileLayout.tileoffsetlon;var tileoffsetlat=tileLayout.tileoffsetlat;var tilelon=tileLayout.tilelon;var tilelat=tileLayout.tilelat;this.origin=new OpenLayers.Pixel(tileoffsetx,tileoffsety);var startX=tileoffsetx;var startLon=tileoffsetlon;var rowidx=0;var layerContainerDivLeft=parseInt(this.map.layerContainerDiv.style.left);var layerContainerDivTop=parseInt(this.map.layerContainerDiv.style.top);do{var row=this.grid[rowidx++];if(!row){row=[];this.grid.push(row);}
+tileoffsetlon=startLon;tileoffsetx=startX;var colidx=0;do{var tileBounds=new OpenLayers.Bounds(tileoffsetlon,tileoffsetlat,tileoffsetlon+tilelon,tileoffsetlat+tilelat);var x=tileoffsetx;x-=layerContainerDivLeft;var y=tileoffsety;y-=layerContainerDivTop;var px=new OpenLayers.Pixel(x,y);var tile=row[colidx++];if(!tile){tile=this.addTile(tileBounds,px);this.addTileMonitoringHooks(tile);row.push(tile);}else{tile.moveTo(tileBounds,px,false);}
+tileoffsetlon+=tilelon;tileoffsetx+=this.tileSize.w;}while((tileoffsetlon<=bounds.right+tilelon*this.buffer)||colidx<minCols);tileoffsetlat-=tilelat;tileoffsety+=this.tileSize.h;}while((tileoffsetlat>=bounds.bottom-tilelat*this.buffer)||rowidx<minRows);this.removeExcessTiles(rowidx,colidx);this.spiralTileLoad();},getMaxExtent:function(){return this.maxExtent;},spiralTileLoad:function(){var tileQueue=[];var directions=["right","down","left","up"];var iRow=0;var iCell=-1;var direction=OpenLayers.Util.indexOf(directions,"right");var directionsTried=0;while(directionsTried<directions.length){var testRow=iRow;var testCell=iCell;switch(directions[direction]){case"right":testCell++;break;case"down":testRow++;break;case"left":testCell--;break;case"up":testRow--;break;}
+var tile=null;if((testRow<this.grid.length)&&(testRow>=0)&&(testCell<this.grid[0].length)&&(testCell>=0)){tile=this.grid[testRow][testCell];}
+if((tile!=null)&&(!tile.queued)){tileQueue.unshift(tile);tile.queued=true;directionsTried=0;iRow=testRow;iCell=testCell;}else{direction=(direction+1)%4;directionsTried++;}}
+for(var i=0,len=tileQueue.length;i<len;i++){var tile=tileQueue[i];tile.draw();tile.queued=false;}},addTile:function(bounds,position){},addTileMonitoringHooks:function(tile){tile.onLoadStart=function(){if(this.numLoadingTiles==0){this.events.triggerEvent("loadstart");}
+this.numLoadingTiles++;};tile.events.register("loadstart",this,tile.onLoadStart);tile.onLoadEnd=function(){this.numLoadingTiles--;this.events.triggerEvent("tileloaded");if(this.numLoadingTiles==0){this.events.triggerEvent("loadend");}};tile.events.register("loadend",this,tile.onLoadEnd);tile.events.register("unload",this,tile.onLoadEnd);},removeTileMonitoringHooks:function(tile){tile.unload();tile.events.un({"loadstart":tile.onLoadStart,"loadend":tile.onLoadEnd,"unload":tile.onLoadEnd,scope:this});},moveGriddedTiles:function(bounds){var buffer=this.buffer||1;while(true){var tlLayer=this.grid[0][0].position;var tlViewPort=this.map.getViewPortPxFromLayerPx(tlLayer);if(tlViewPort.x>-this.tileSize.w*(buffer-1)){this.shiftColumn(true);}else if(tlViewPort.x<-this.tileSize.w*buffer){this.shiftColumn(false);}else if(tlViewPort.y>-this.tileSize.h*(buffer-1)){this.shiftRow(true);}else if(tlViewPort.y<-this.tileSize.h*buffer){this.shiftRow(false);}else{break;}};},shiftRow:function(prepend){var modelRowIndex=(prepend)?0:(this.grid.length-1);var grid=this.grid;var modelRow=grid[modelRowIndex];var resolution=this.map.getResolution();var deltaY=(prepend)?-this.tileSize.h:this.tileSize.h;var deltaLat=resolution*-deltaY;var row=(prepend)?grid.pop():grid.shift();for(var i=0,len=modelRow.length;i<len;i++){var modelTile=modelRow[i];var bounds=modelTile.bounds.clone();var position=modelTile.position.clone();bounds.bottom=bounds.bottom+deltaLat;bounds.top=bounds.top+deltaLat;position.y=position.y+deltaY;row[i].moveTo(bounds,position);}
+if(prepend){grid.unshift(row);}else{grid.push(row);}},shiftColumn:function(prepend){var deltaX=(prepend)?-this.tileSize.w:this.tileSize.w;var resolution=this.map.getResolution();var deltaLon=resolution*deltaX;for(var i=0,len=this.grid.length;i<len;i++){var row=this.grid[i];var modelTileIndex=(prepend)?0:(row.length-1);var modelTile=row[modelTileIndex];var bounds=modelTile.bounds.clone();var position=modelTile.position.clone();bounds.left=bounds.left+deltaLon;bounds.right=bounds.right+deltaLon;position.x=position.x+deltaX;var tile=prepend?this.grid[i].pop():this.grid[i].shift();tile.moveTo(bounds,position);if(prepend){row.unshift(tile);}else{row.push(tile);}}},removeExcessTiles:function(rows,columns){while(this.grid.length>rows){var row=this.grid.pop();for(var i=0,l=row.length;i<l;i++){var tile=row[i];this.removeTileMonitoringHooks(tile);tile.destroy();}}
+while(this.grid[0].length>columns){for(var i=0,l=this.grid.length;i<l;i++){var row=this.grid[i];var tile=row.pop();this.removeTileMonitoringHooks(tile);tile.destroy();}}},onMapResize:function(){if(this.singleTile){this.clearGrid();this.setTileSize();}},getTileBounds:function(viewPortPx){var maxExtent=this.maxExtent;var resolution=this.getResolution();var tileMapWidth=resolution*this.tileSize.w;var tileMapHeight=resolution*this.tileSize.h;var mapPoint=this.getLonLatFromViewPortPx(viewPortPx);var tileLeft=maxExtent.left+(tileMapWidth*Math.floor((mapPoint.lon-
+maxExtent.left)/tileMapWidth));var tileBottom=maxExtent.bottom+(tileMapHeight*Math.floor((mapPoint.lat-
+maxExtent.bottom)/tileMapHeight));return new OpenLayers.Bounds(tileLeft,tileBottom,tileLeft+tileMapWidth,tileBottom+tileMapHeight);},CLASS_NAME:"OpenLayers.Layer.Grid"});OpenLayers.Layer.MultiMap=OpenLayers.Class(OpenLayers.Layer.EventPane,OpenLayers.Layer.FixedZoomLevels,{MIN_ZOOM_LEVEL:1,MAX_ZOOM_LEVEL:17,RESOLUTIONS:[9,1.40625,0.703125,0.3515625,0.17578125,0.087890625,0.0439453125,0.02197265625,0.010986328125,0.0054931640625,0.00274658203125,0.001373291015625,0.0006866455078125,0.00034332275390625,0.000171661376953125,0.0000858306884765625,0.00004291534423828125],type:null,initialize:function(name,options){OpenLayers.Layer.EventPane.prototype.initialize.apply(this,arguments);OpenLayers.Layer.FixedZoomLevels.prototype.initialize.apply(this,arguments);if(this.sphericalMercator){OpenLayers.Util.extend(this,OpenLayers.Layer.SphericalMercator);this.initMercatorParameters();this.RESOLUTIONS.unshift(10);}},loadMapObject:function(){try{this.mapObject=new MultimapViewer(this.div);}catch(e){}},getWarningHTML:function(){return OpenLayers.i18n("getLayerWarning",{'layerType':"MM",'layerLib':"MultiMap"});},setMapObjectCenter:function(center,zoom){this.mapObject.goToPosition(center,zoom);},getMapObjectCenter:function(){return this.mapObject.getCurrentPosition();},getMapObjectZoom:function(){return this.mapObject.getZoomFactor();},getMapObjectLonLatFromMapObjectPixel:function(moPixel){moPixel.x=moPixel.x-(this.map.getSize().w/2);moPixel.y=moPixel.y-(this.map.getSize().h/2);return this.mapObject.getMapPositionAt(moPixel);},getMapObjectPixelFromMapObjectLonLat:function(moLonLat){return this.mapObject.geoPosToContainerPixels(moLonLat);},getLongitudeFromMapObjectLonLat:function(moLonLat){return this.sphericalMercator?this.forwardMercator(moLonLat.lon,moLonLat.lat).lon:moLonLat.lon;},getLatitudeFromMapObjectLonLat:function(moLonLat){return this.sphericalMercator?this.forwardMercator(moLonLat.lon,moLonLat.lat).lat:moLonLat.lat;},getMapObjectLonLatFromLonLat:function(lon,lat){var mmLatLon;if(this.sphericalMercator){var lonlat=this.inverseMercator(lon,lat);mmLatLon=new MMLatLon(lonlat.lat,lonlat.lon);}else{mmLatLon=new MMLatLon(lat,lon);}
+return mmLatLon;},getXFromMapObjectPixel:function(moPixel){return moPixel.x;},getYFromMapObjectPixel:function(moPixel){return moPixel.y;},getMapObjectPixelFromXY:function(x,y){return new MMPoint(x,y);},CLASS_NAME:"OpenLayers.Layer.MultiMap"});OpenLayers.Layer.VirtualEarth=OpenLayers.Class(OpenLayers.Layer.EventPane,OpenLayers.Layer.FixedZoomLevels,{MIN_ZOOM_LEVEL:1,MAX_ZOOM_LEVEL:19,RESOLUTIONS:[1.40625,0.703125,0.3515625,0.17578125,0.087890625,0.0439453125,0.02197265625,0.010986328125,0.0054931640625,0.00274658203125,0.001373291015625,0.0006866455078125,0.00034332275390625,0.000171661376953125,0.0000858306884765625,0.00004291534423828125,0.00002145767211914062,0.00001072883605957031,0.00000536441802978515],type:null,wrapDateLine:true,sphericalMercator:false,animationEnabled:true,initialize:function(name,options){OpenLayers.Layer.EventPane.prototype.initialize.apply(this,arguments);OpenLayers.Layer.FixedZoomLevels.prototype.initialize.apply(this,arguments);if(this.sphericalMercator){OpenLayers.Util.extend(this,OpenLayers.Layer.SphericalMercator);this.initMercatorParameters();}},loadMapObject:function(){var veDiv=OpenLayers.Util.createDiv(this.name);var sz=this.map.getSize();veDiv.style.width=sz.w+"px";veDiv.style.height=sz.h+"px";this.div.appendChild(veDiv);try{this.mapObject=new VEMap(this.name);}catch(e){}
+if(this.mapObject!=null){try{this.mapObject.LoadMap(null,null,this.type,true);this.mapObject.AttachEvent("onmousedown",OpenLayers.Function.True);}catch(e){}
+this.mapObject.HideDashboard();if(typeof this.mapObject.SetAnimationEnabled=="function"){this.mapObject.SetAnimationEnabled(this.animationEnabled);}}
+if(!this.mapObject||!this.mapObject.vemapcontrol||!this.mapObject.vemapcontrol.PanMap||(typeof this.mapObject.vemapcontrol.PanMap!="function")){this.dragPanMapObject=null;}},onMapResize:function(){this.mapObject.Resize(this.map.size.w,this.map.size.h);},getWarningHTML:function(){return OpenLayers.i18n("getLayerWarning",{'layerType':'VE','layerLib':'VirtualEarth'});},setMapObjectCenter:function(center,zoom){this.mapObject.SetCenterAndZoom(center,zoom);},getMapObjectCenter:function(){return this.mapObject.GetCenter();},dragPanMapObject:function(dX,dY){this.mapObject.vemapcontrol.PanMap(dX,-dY);},getMapObjectZoom:function(){return this.mapObject.GetZoomLevel();},getMapObjectLonLatFromMapObjectPixel:function(moPixel){return(typeof VEPixel!='undefined')?this.mapObject.PixelToLatLong(moPixel):this.mapObject.PixelToLatLong(moPixel.x,moPixel.y);},getMapObjectPixelFromMapObjectLonLat:function(moLonLat){return this.mapObject.LatLongToPixel(moLonLat);},getLongitudeFromMapObjectLonLat:function(moLonLat){return this.sphericalMercator?this.forwardMercator(moLonLat.Longitude,moLonLat.Latitude).lon:moLonLat.Longitude;},getLatitudeFromMapObjectLonLat:function(moLonLat){return this.sphericalMercator?this.forwardMercator(moLonLat.Longitude,moLonLat.Latitude).lat:moLonLat.Latitude;},getMapObjectLonLatFromLonLat:function(lon,lat){var veLatLong;if(this.sphericalMercator){var lonlat=this.inverseMercator(lon,lat);veLatLong=new VELatLong(lonlat.lat,lonlat.lon);}else{veLatLong=new VELatLong(lat,lon);}
+return veLatLong;},getXFromMapObjectPixel:function(moPixel){return moPixel.x;},getYFromMapObjectPixel:function(moPixel){return moPixel.y;},getMapObjectPixelFromXY:function(x,y){return(typeof VEPixel!='undefined')?new VEPixel(x,y):new Msn.VE.Pixel(x,y);},CLASS_NAME:"OpenLayers.Layer.VirtualEarth"});OpenLayers.Layer.Yahoo=OpenLayers.Class(OpenLayers.Layer.EventPane,OpenLayers.Layer.FixedZoomLevels,{MIN_ZOOM_LEVEL:0,MAX_ZOOM_LEVEL:17,RESOLUTIONS:[1.40625,0.703125,0.3515625,0.17578125,0.087890625,0.0439453125,0.02197265625,0.010986328125,0.0054931640625,0.00274658203125,0.001373291015625,0.0006866455078125,0.00034332275390625,0.000171661376953125,0.0000858306884765625,0.00004291534423828125,0.00002145767211914062,0.00001072883605957031],type:null,wrapDateLine:true,sphericalMercator:false,initialize:function(name,options){OpenLayers.Layer.EventPane.prototype.initialize.apply(this,arguments);OpenLayers.Layer.FixedZoomLevels.prototype.initialize.apply(this,arguments);if(this.sphericalMercator){OpenLayers.Util.extend(this,OpenLayers.Layer.SphericalMercator);this.initMercatorParameters();}},loadMapObject:function(){try{var size=this.getMapObjectSizeFromOLSize(this.map.getSize());this.mapObject=new YMap(this.div,this.type,size);this.mapObject.disableKeyControls();this.mapObject.disableDragMap();if(!this.mapObject.moveByXY||(typeof this.mapObject.moveByXY!="function")){this.dragPanMapObject=null;}}catch(e){}},onMapResize:function(){try{var size=this.getMapObjectSizeFromOLSize(this.map.getSize());this.mapObject.resizeTo(size);}catch(e){}},setMap:function(map){OpenLayers.Layer.EventPane.prototype.setMap.apply(this,arguments);this.map.events.register("moveend",this,this.fixYahooEventPane);},fixYahooEventPane:function(){var yahooEventPane=OpenLayers.Util.getElement("ygddfdiv");if(yahooEventPane!=null){if(yahooEventPane.parentNode!=null){yahooEventPane.parentNode.removeChild(yahooEventPane);}
+this.map.events.unregister("moveend",this,this.fixYahooEventPane);}},getWarningHTML:function(){return OpenLayers.i18n("getLayerWarning",{'layerType':'Yahoo','layerLib':'Yahoo'});},getOLZoomFromMapObjectZoom:function(moZoom){var zoom=null;if(moZoom!=null){zoom=OpenLayers.Layer.FixedZoomLevels.prototype.getOLZoomFromMapObjectZoom.apply(this,[moZoom]);zoom=18-zoom;}
+return zoom;},getMapObjectZoomFromOLZoom:function(olZoom){var zoom=null;if(olZoom!=null){zoom=OpenLayers.Layer.FixedZoomLevels.prototype.getMapObjectZoomFromOLZoom.apply(this,[olZoom]);zoom=18-zoom;}
+return zoom;},setMapObjectCenter:function(center,zoom){this.mapObject.drawZoomAndCenter(center,zoom);},getMapObjectCenter:function(){return this.mapObject.getCenterLatLon();},dragPanMapObject:function(dX,dY){this.mapObject.moveByXY({'x':-dX,'y':dY});},getMapObjectZoom:function(){return this.mapObject.getZoomLevel();},getMapObjectLonLatFromMapObjectPixel:function(moPixel){return this.mapObject.convertXYLatLon(moPixel);},getMapObjectPixelFromMapObjectLonLat:function(moLonLat){return this.mapObject.convertLatLonXY(moLonLat);},getLongitudeFromMapObjectLonLat:function(moLonLat){return this.sphericalMercator?this.forwardMercator(moLonLat.Lon,moLonLat.Lat).lon:moLonLat.Lon;},getLatitudeFromMapObjectLonLat:function(moLonLat){return this.sphericalMercator?this.forwardMercator(moLonLat.Lon,moLonLat.Lat).lat:moLonLat.Lat;},getMapObjectLonLatFromLonLat:function(lon,lat){var yLatLong;if(this.sphericalMercator){var lonlat=this.inverseMercator(lon,lat);yLatLong=new YGeoPoint(lonlat.lat,lonlat.lon);}else{yLatLong=new YGeoPoint(lat,lon);}
+return yLatLong;},getXFromMapObjectPixel:function(moPixel){return moPixel.x;},getYFromMapObjectPixel:function(moPixel){return moPixel.y;},getMapObjectPixelFromXY:function(x,y){return new YCoordPoint(x,y);},getMapObjectSizeFromOLSize:function(olSize){return new YSize(olSize.w,olSize.h);},CLASS_NAME:"OpenLayers.Layer.Yahoo"});OpenLayers.Style=OpenLayers.Class({id:null,name:null,title:null,description:null,layerName:null,isDefault:false,rules:null,context:null,defaultStyle:null,defaultsPerSymbolizer:false,propertyStyles:null,initialize:function(style,options){OpenLayers.Util.extend(this,options);this.rules=[];if(options&&options.rules){this.addRules(options.rules);}
+this.setDefaultStyle(style||OpenLayers.Feature.Vector.style["default"]);this.id=OpenLayers.Util.createUniqueID(this.CLASS_NAME+"_");},destroy:function(){for(var i=0,len=this.rules.length;i<len;i++){this.rules[i].destroy();this.rules[i]=null;}
+this.rules=null;this.defaultStyle=null;},createSymbolizer:function(feature){var style=this.defaultsPerSymbolizer?{}:this.createLiterals(OpenLayers.Util.extend({},this.defaultStyle),feature);var rules=this.rules;var rule,context;var elseRules=[];var appliedRules=false;for(var i=0,len=rules.length;i<len;i++){rule=rules[i];var applies=rule.evaluate(feature);if(applies){if(rule instanceof OpenLayers.Rule&&rule.elseFilter){elseRules.push(rule);}else{appliedRules=true;this.applySymbolizer(rule,style,feature);}}}
+if(appliedRules==false&&elseRules.length>0){appliedRules=true;for(var i=0,len=elseRules.length;i<len;i++){this.applySymbolizer(elseRules[i],style,feature);}}
+if(rules.length>0&&appliedRules==false){style.display="none";}
+return style;},applySymbolizer:function(rule,style,feature){var symbolizerPrefix=feature.geometry?this.getSymbolizerPrefix(feature.geometry):OpenLayers.Style.SYMBOLIZER_PREFIXES[0];var symbolizer=rule.symbolizer[symbolizerPrefix]||rule.symbolizer;if(this.defaultsPerSymbolizer===true){var defaults=this.defaultStyle;OpenLayers.Util.applyDefaults(symbolizer,{pointRadius:defaults.pointRadius});if(symbolizer.stroke===true||symbolizer.graphic===true){OpenLayers.Util.applyDefaults(symbolizer,{strokeWidth:defaults.strokeWidth,strokeColor:defaults.strokeColor,strokeOpacity:defaults.strokeOpacity,strokeDashstyle:defaults.strokeDashstyle,strokeLinecap:defaults.strokeLinecap});}
+if(symbolizer.fill===true||symbolizer.graphic===true){OpenLayers.Util.applyDefaults(symbolizer,{fillColor:defaults.fillColor,fillOpacity:defaults.fillOpacity});}
+if(symbolizer.graphic===true){OpenLayers.Util.applyDefaults(symbolizer,{pointRadius:this.defaultStyle.pointRadius,externalGraphic:this.defaultStyle.externalGraphic,graphicName:this.defaultStyle.graphicName,graphicOpacity:this.defaultStyle.graphicOpacity,graphicWidth:this.defaultStyle.graphicWidth,graphicHeight:this.defaultStyle.graphicHeight,graphicXOffset:this.defaultStyle.graphicXOffset,graphicYOffset:this.defaultStyle.graphicYOffset});}}
+return this.createLiterals(OpenLayers.Util.extend(style,symbolizer),feature);},createLiterals:function(style,feature){var context=OpenLayers.Util.extend({},feature.attributes||feature.data);OpenLayers.Util.extend(context,this.context);for(var i in this.propertyStyles){style[i]=OpenLayers.Style.createLiteral(style[i],context,feature,i);}
+return style;},findPropertyStyles:function(){var propertyStyles={};var style=this.defaultStyle;this.addPropertyStyles(propertyStyles,style);var rules=this.rules;var symbolizer,value;for(var i=0,len=rules.length;i<len;i++){symbolizer=rules[i].symbolizer;for(var key in symbolizer){value=symbolizer[key];if(typeof value=="object"){this.addPropertyStyles(propertyStyles,value);}else{this.addPropertyStyles(propertyStyles,symbolizer);break;}}}
+return propertyStyles;},addPropertyStyles:function(propertyStyles,symbolizer){var property;for(var key in symbolizer){property=symbolizer[key];if(typeof property=="string"&&property.match(/\$\{\w+\}/)){propertyStyles[key]=true;}}
+return propertyStyles;},addRules:function(rules){Array.prototype.push.apply(this.rules,rules);this.propertyStyles=this.findPropertyStyles();},setDefaultStyle:function(style){this.defaultStyle=style;this.propertyStyles=this.findPropertyStyles();},getSymbolizerPrefix:function(geometry){var prefixes=OpenLayers.Style.SYMBOLIZER_PREFIXES;for(var i=0,len=prefixes.length;i<len;i++){if(geometry.CLASS_NAME.indexOf(prefixes[i])!=-1){return prefixes[i];}}},clone:function(){var options=OpenLayers.Util.extend({},this);if(this.rules){options.rules=[];for(var i=0,len=this.rules.length;i<len;++i){options.rules.push(this.rules[i].clone());}}
+options.context=this.context&&OpenLayers.Util.extend({},this.context);var defaultStyle=OpenLayers.Util.extend({},this.defaultStyle);return new OpenLayers.Style(defaultStyle,options);},CLASS_NAME:"OpenLayers.Style"});OpenLayers.Style.createLiteral=function(value,context,feature,property){if(typeof value=="string"&&value.indexOf("${")!=-1){value=OpenLayers.String.format(value,context,[feature,property]);value=(isNaN(value)||!value)?value:parseFloat(value);}
+return value;};OpenLayers.Style.SYMBOLIZER_PREFIXES=['Point','Line','Polygon','Text','Raster'];OpenLayers.Control.Navigation=OpenLayers.Class(OpenLayers.Control,{dragPan:null,dragPanOptions:null,documentDrag:false,zoomBox:null,zoomBoxEnabled:true,zoomWheelEnabled:true,mouseWheelOptions:null,handleRightClicks:false,zoomBoxKeyMask:OpenLayers.Handler.MOD_SHIFT,autoActivate:true,initialize:function(options){this.handlers={};OpenLayers.Control.prototype.initialize.apply(this,arguments);},destroy:function(){this.deactivate();if(this.dragPan){this.dragPan.destroy();}
+this.dragPan=null;if(this.zoomBox){this.zoomBox.destroy();}
+this.zoomBox=null;OpenLayers.Control.prototype.destroy.apply(this,arguments);},activate:function(){this.dragPan.activate();if(this.zoomWheelEnabled){this.handlers.wheel.activate();}
+this.handlers.click.activate();if(this.zoomBoxEnabled){this.zoomBox.activate();}
+return OpenLayers.Control.prototype.activate.apply(this,arguments);},deactivate:function(){this.zoomBox.deactivate();this.dragPan.deactivate();this.handlers.click.deactivate();this.handlers.wheel.deactivate();return OpenLayers.Control.prototype.deactivate.apply(this,arguments);},draw:function(){if(this.handleRightClicks){this.map.viewPortDiv.oncontextmenu=OpenLayers.Function.False;}
+var clickCallbacks={'dblclick':this.defaultDblClick,'dblrightclick':this.defaultDblRightClick};var clickOptions={'double':true,'stopDouble':true};this.handlers.click=new OpenLayers.Handler.Click(this,clickCallbacks,clickOptions);this.dragPan=new OpenLayers.Control.DragPan(OpenLayers.Util.extend({map:this.map,documentDrag:this.documentDrag},this.dragPanOptions));this.zoomBox=new OpenLayers.Control.ZoomBox({map:this.map,keyMask:this.zoomBoxKeyMask});this.dragPan.draw();this.zoomBox.draw();this.handlers.wheel=new OpenLayers.Handler.MouseWheel(this,{"up":this.wheelUp,"down":this.wheelDown},this.mouseWheelOptions);},defaultDblClick:function(evt){var newCenter=this.map.getLonLatFromViewPortPx(evt.xy);this.map.setCenter(newCenter,this.map.zoom+1);},defaultDblRightClick:function(evt){var newCenter=this.map.getLonLatFromViewPortPx(evt.xy);this.map.setCenter(newCenter,this.map.zoom-1);},wheelChange:function(evt,deltaZ){var currentZoom=this.map.getZoom();var newZoom=this.map.getZoom()+Math.round(deltaZ);newZoom=Math.max(newZoom,0);newZoom=Math.min(newZoom,this.map.getNumZoomLevels());if(newZoom===currentZoom){return;}
+var size=this.map.getSize();var deltaX=size.w/2-evt.xy.x;var deltaY=evt.xy.y-size.h/2;var newRes=this.map.baseLayer.getResolutionForZoom(newZoom);var zoomPoint=this.map.getLonLatFromPixel(evt.xy);var newCenter=new OpenLayers.LonLat(zoomPoint.lon+deltaX*newRes,zoomPoint.lat+deltaY*newRes);this.map.setCenter(newCenter,newZoom);},wheelUp:function(evt,delta){this.wheelChange(evt,delta||1);},wheelDown:function(evt,delta){this.wheelChange(evt,delta||-1);},disableZoomBox:function(){this.zoomBoxEnabled=false;this.zoomBox.deactivate();},enableZoomBox:function(){this.zoomBoxEnabled=true;if(this.active){this.zoomBox.activate();}},disableZoomWheel:function(){this.zoomWheelEnabled=false;this.handlers.wheel.deactivate();},enableZoomWheel:function(){this.zoomWheelEnabled=true;if(this.active){this.handlers.wheel.activate();}},CLASS_NAME:"OpenLayers.Control.Navigation"});OpenLayers.Filter=OpenLayers.Class({initialize:function(options){OpenLayers.Util.extend(this,options);},destroy:function(){},evaluate:function(context){return true;},clone:function(){return null;},CLASS_NAME:"OpenLayers.Filter"});OpenLayers.Format.WMC.v1_0_0=OpenLayers.Class(OpenLayers.Format.WMC.v1,{VERSION:"1.0.0",schemaLocation:"http://www.opengis.net/context http://schemas.opengis.net/context/1.0.0/context.xsd",initialize:function(options){OpenLayers.Format.WMC.v1.prototype.initialize.apply(this,[options]);},write_wmc_Layer:function(context){var node=OpenLayers.Format.WMC.v1.prototype.write_wmc_Layer.apply(this,[context]);node.appendChild(this.write_wmc_FormatList(context));node.appendChild(this.write_wmc_StyleList(context));node.appendChild(this.write_wmc_LayerExtension(context));},CLASS_NAME:"OpenLayers.Format.WMC.v1_0_0"});OpenLayers.Format.WMC.v1_1_0=OpenLayers.Class(OpenLayers.Format.WMC.v1,{VERSION:"1.1.0",schemaLocation:"http://www.opengis.net/context http://schemas.opengis.net/context/1.1.0/context.xsd",initialize:function(options){OpenLayers.Format.WMC.v1.prototype.initialize.apply(this,[options]);},read_sld_MinScaleDenominator:function(layerContext,node){var minScaleDenominator=parseFloat(this.getChildValue(node));if(minScaleDenominator>0){layerContext.maxScale=minScaleDenominator;}},read_sld_MaxScaleDenominator:function(layerContext,node){layerContext.minScale=parseFloat(this.getChildValue(node));},write_wmc_Layer:function(context){var node=OpenLayers.Format.WMC.v1.prototype.write_wmc_Layer.apply(this,[context]);if(context.maxScale){var minSD=this.createElementNS(this.namespaces.sld,"sld:MinScaleDenominator");minSD.appendChild(this.createTextNode(context.maxScale.toPrecision(16)));node.appendChild(minSD);}
+if(context.minScale){var maxSD=this.createElementNS(this.namespaces.sld,"sld:MaxScaleDenominator");maxSD.appendChild(this.createTextNode(context.minScale.toPrecision(16)));node.appendChild(maxSD);}
+node.appendChild(this.write_wmc_FormatList(context));node.appendChild(this.write_wmc_StyleList(context));node.appendChild(this.write_wmc_LayerExtension(context));return node;},CLASS_NAME:"OpenLayers.Format.WMC.v1_1_0"});OpenLayers.Format.WMSCapabilities.v1_1_0=OpenLayers.Class(OpenLayers.Format.WMSCapabilities.v1_1,{version:"1.1.0",initialize:function(options){OpenLayers.Format.WMSCapabilities.v1_1.prototype.initialize.apply(this,[options]);},readers:{"wms":OpenLayers.Util.applyDefaults({"SRS":function(node,obj){var srs=this.getChildValue(node);var values=srs.split(/ +/);for(var i=0,len=values.length;i<len;i++){obj.srs[values[i]]=true;}}},OpenLayers.Format.WMSCapabilities.v1_1.prototype.readers["wms"])},CLASS_NAME:"OpenLayers.Format.WMSCapabilities.v1_1_0"});OpenLayers.Format.WMSCapabilities.v1_1_1=OpenLayers.Class(OpenLayers.Format.WMSCapabilities.v1_1,{version:"1.1.1",initialize:function(options){OpenLayers.Format.WMSCapabilities.v1_1.prototype.initialize.apply(this,[options]);},readers:{"wms":OpenLayers.Util.applyDefaults({"SRS":function(node,obj){obj.srs[this.getChildValue(node)]=true;}},OpenLayers.Format.WMSCapabilities.v1_1.prototype.readers["wms"])},CLASS_NAME:"OpenLayers.Format.WMSCapabilities.v1_1_1"});OpenLayers.Format.WMSCapabilities.v1_3_0=OpenLayers.Class(OpenLayers.Format.WMSCapabilities.v1_3,{version:"1.3.0",CLASS_NAME:"OpenLayers.Format.WMSCapabilities.v1_3_0"});OpenLayers.Geometry=OpenLayers.Class({id:null,parent:null,bounds:null,initialize:function(){this.id=OpenLayers.Util.createUniqueID(this.CLASS_NAME+"_");},destroy:function(){this.id=null;this.bounds=null;},clone:function(){return new OpenLayers.Geometry();},setBounds:function(bounds){if(bounds){this.bounds=bounds.clone();}},clearBounds:function(){this.bounds=null;if(this.parent){this.parent.clearBounds();}},extendBounds:function(newBounds){var bounds=this.getBounds();if(!bounds){this.setBounds(newBounds);}else{this.bounds.extend(newBounds);}},getBounds:function(){if(this.bounds==null){this.calculateBounds();}
+return this.bounds;},calculateBounds:function(){},distanceTo:function(geometry,options){},getVertices:function(nodes){},atPoint:function(lonlat,toleranceLon,toleranceLat){var atPoint=false;var bounds=this.getBounds();if((bounds!=null)&&(lonlat!=null)){var dX=(toleranceLon!=null)?toleranceLon:0;var dY=(toleranceLat!=null)?toleranceLat:0;var toleranceBounds=new OpenLayers.Bounds(this.bounds.left-dX,this.bounds.bottom-dY,this.bounds.right+dX,this.bounds.top+dY);atPoint=toleranceBounds.containsLonLat(lonlat);}
+return atPoint;},getLength:function(){return 0.0;},getArea:function(){return 0.0;},getCentroid:function(){return null;},toString:function(){return OpenLayers.Format.WKT.prototype.write(new OpenLayers.Feature.Vector(this));},CLASS_NAME:"OpenLayers.Geometry"});OpenLayers.Geometry.fromWKT=function(wkt){var format=arguments.callee.format;if(!format){format=new OpenLayers.Format.WKT();arguments.callee.format=format;}
+var geom;var result=format.read(wkt);if(result instanceof OpenLayers.Feature.Vector){geom=result.geometry;}else if(result instanceof Array){var len=result.length;var components=new Array(len);for(var i=0;i<len;++i){components[i]=result[i].geometry;}
+geom=new OpenLayers.Geometry.Collection(components);}
+return geom;};OpenLayers.Geometry.segmentsIntersect=function(seg1,seg2,options){var point=options&&options.point;var tolerance=options&&options.tolerance;var intersection=false;var x11_21=seg1.x1-seg2.x1;var y11_21=seg1.y1-seg2.y1;var x12_11=seg1.x2-seg1.x1;var y12_11=seg1.y2-seg1.y1;var y22_21=seg2.y2-seg2.y1;var x22_21=seg2.x2-seg2.x1;var d=(y22_21*x12_11)-(x22_21*y12_11);var n1=(x22_21*y11_21)-(y22_21*x11_21);var n2=(x12_11*y11_21)-(y12_11*x11_21);if(d==0){if(n1==0&&n2==0){intersection=true;}}else{var along1=n1/d;var along2=n2/d;if(along1>=0&&along1<=1&&along2>=0&&along2<=1){if(!point){intersection=true;}else{var x=seg1.x1+(along1*x12_11);var y=seg1.y1+(along1*y12_11);intersection=new OpenLayers.Geometry.Point(x,y);}}}
+if(tolerance){var dist;if(intersection){if(point){var segs=[seg1,seg2];var seg,x,y;outer:for(var i=0;i<2;++i){seg=segs[i];for(var j=1;j<3;++j){x=seg["x"+j];y=seg["y"+j];dist=Math.sqrt(Math.pow(x-intersection.x,2)+
+Math.pow(y-intersection.y,2));if(dist<tolerance){intersection.x=x;intersection.y=y;break outer;}}}}}else{var segs=[seg1,seg2];var source,target,x,y,p,result;outer:for(var i=0;i<2;++i){source=segs[i];target=segs[(i+1)%2];for(var j=1;j<3;++j){p={x:source["x"+j],y:source["y"+j]};result=OpenLayers.Geometry.distanceToSegment(p,target);if(result.distance<tolerance){if(point){intersection=new OpenLayers.Geometry.Point(p.x,p.y);}else{intersection=true;}
+break outer;}}}}}
+return intersection;};OpenLayers.Geometry.distanceToSegment=function(point,segment){var x0=point.x;var y0=point.y;var x1=segment.x1;var y1=segment.y1;var x2=segment.x2;var y2=segment.y2;var dx=x2-x1;var dy=y2-y1;var along=((dx*(x0-x1))+(dy*(y0-y1)))/(Math.pow(dx,2)+Math.pow(dy,2));var x,y;if(along<=0.0){x=x1;y=y1;}else if(along>=1.0){x=x2;y=y2;}else{x=x1+along*dx;y=y1+along*dy;}
+return{distance:Math.sqrt(Math.pow(x-x0,2)+Math.pow(y-y0,2)),x:x,y:y};};OpenLayers.Layer.ArcGIS93Rest=OpenLayers.Class(OpenLayers.Layer.Grid,{DEFAULT_PARAMS:{format:"png"},isBaseLayer:true,initialize:function(name,url,params,options){var newArguments=[];params=OpenLayers.Util.upperCaseObject(params);newArguments.push(name,url,params,options);OpenLayers.Layer.Grid.prototype.initialize.apply(this,newArguments);OpenLayers.Util.applyDefaults(this.params,OpenLayers.Util.upperCaseObject(this.DEFAULT_PARAMS));if(this.params.TRANSPARENT&&this.params.TRANSPARENT.toString().toLowerCase()=="true"){if((options==null)||(!options.isBaseLayer)){this.isBaseLayer=false;}
+if(this.params.FORMAT=="jpg"){this.params.FORMAT=OpenLayers.Util.alphaHack()?"gif":"png";}}},destroy:function(){OpenLayers.Layer.Grid.prototype.destroy.apply(this,arguments);},clone:function(obj){if(obj==null){obj=new OpenLayers.Layer.ArcGIS93Rest(this.name,this.url,this.params,this.getOptions());}
+obj=OpenLayers.Layer.Grid.prototype.clone.apply(this,[obj]);return obj;},getURL:function(bounds){bounds=this.adjustBounds(bounds);var projWords=this.projection.getCode().split(":");var srid=projWords[projWords.length-1];var imageSize=this.getImageSize();var newParams={'BBOX':bounds.toBBOX(),'SIZE':imageSize.w+","+imageSize.h,'F':"image",'BBOXSR':srid,'IMAGESR':srid};if(this.layerDefs){var layerDefStrList=[];var layerID;for(layerID in this.layerDefs){if(this.layerDefs.hasOwnProperty(layerID)){if(this.layerDefs[layerID]){layerDefStrList.push(layerID);layerDefStrList.push(":");layerDefStrList.push(this.layerDefs[layerID]);layerDefStrList.push(";");}}}
+if(layerDefStrList.length>0){newParams['LAYERDEFS']=layerDefStrList.join("");}}
+var requestString=this.getFullRequestString(newParams);return requestString;},setLayerFilter:function(id,queryDef){if(!this.layerDefs){this.layerDefs={};}
+if(queryDef){this.layerDefs[id]=queryDef;}else{delete this.layerDefs[id];}},clearLayerFilter:function(id){if(id){delete this.layerDefs[id];}else{delete this.layerDefs;}},mergeNewParams:function(newParams){var upperParams=OpenLayers.Util.upperCaseObject(newParams);var newArguments=[upperParams];return OpenLayers.Layer.Grid.prototype.mergeNewParams.apply(this,newArguments);},addTile:function(bounds,position){return new OpenLayers.Tile.Image(this,position,bounds,null,this.tileSize);},CLASS_NAME:"OpenLayers.Layer.ArcGIS93Rest"});OpenLayers.Layer.Google.v3={DEFAULTS:{maxExtent:new OpenLayers.Bounds(-128*156543.0339,-128*156543.0339,128*156543.0339,128*156543.0339),sphericalMercator:true,maxResolution:156543.0339,units:"m",projection:"EPSG:900913"},loadMapObject:function(){if(!this.type){this.type=google.maps.MapTypeId.ROADMAP;}
+var mapObject;var cache=OpenLayers.Layer.Google.cache[this.map.id];if(cache){mapObject=cache.mapObject;++cache.count;}else{var container=this.map.viewPortDiv;var div=document.createElement("div");div.id=this.map.id+"_GMapContainer";div.style.position="absolute";div.style.width="100%";div.style.height="100%";container.appendChild(div);var center=this.map.getCenter();mapObject=new google.maps.Map(div,{center:center?new google.maps.LatLng(center.lat,center.lon):new google.maps.LatLng(0,0),zoom:this.map.getZoom()||0,mapTypeId:this.type,disableDefaultUI:true,keyboardShortcuts:false,draggable:false,disableDoubleClickZoom:true,scrollwheel:false});cache={mapObject:mapObject,count:1};OpenLayers.Layer.Google.cache[this.map.id]=cache;this.repositionListener=google.maps.event.addListenerOnce(mapObject,"center_changed",OpenLayers.Function.bind(this.repositionMapElements,this));}
+this.mapObject=mapObject;this.setGMapVisibility(this.visibility);},repositionMapElements:function(){google.maps.event.trigger(this.mapObject,"resize");var div=this.mapObject.getDiv().firstChild;if(!div||div.childNodes.length<3){this.repositionTimer=window.setTimeout(OpenLayers.Function.bind(this.repositionMapElements,this),250);return false;}
+var cache=OpenLayers.Layer.Google.cache[this.map.id];var container=this.map.viewPortDiv;var termsOfUse=div.lastChild;container.appendChild(termsOfUse);termsOfUse.style.zIndex="1100";termsOfUse.style.bottom="";termsOfUse.className="olLayerGoogleCopyright olLayerGoogleV3";termsOfUse.style.display="";cache.termsOfUse=termsOfUse;var poweredBy=div.lastChild;container.appendChild(poweredBy);poweredBy.style.zIndex="1100";poweredBy.style.bottom="";poweredBy.className="olLayerGooglePoweredBy olLayerGoogleV3 gmnoprint";poweredBy.style.display="";cache.poweredBy=poweredBy;this.setGMapVisibility(this.visibility);},onMapResize:function(){if(this.visibility){google.maps.event.trigger(this.mapObject,"resize");}else{if(!this._resized){var layer=this;google.maps.event.addListenerOnce(this.mapObject,"tilesloaded",function(){delete layer._resized;google.maps.event.trigger(layer.mapObject,"resize");layer.moveTo(layer.map.getCenter(),layer.map.getZoom());});}
+this._resized=true;}},setGMapVisibility:function(visible){var cache=OpenLayers.Layer.Google.cache[this.map.id];if(cache){var type=this.type;var layers=this.map.layers;var layer;for(var i=layers.length-1;i>=0;--i){layer=layers[i];if(layer instanceof OpenLayers.Layer.Google&&layer.visibility===true&&layer.inRange===true){type=layer.type;visible=true;break;}}
+var container=this.mapObject.getDiv();if(visible===true){this.mapObject.setMapTypeId(type);container.style.left="";if(cache.termsOfUse&&cache.termsOfUse.style){cache.termsOfUse.style.left="";cache.termsOfUse.style.display="";cache.poweredBy.style.display="";}
+cache.displayed=this.id;}else{delete cache.displayed;container.style.left="-9999px";if(cache.termsOfUse&&cache.termsOfUse.style){cache.termsOfUse.style.display="none";cache.termsOfUse.style.left="-9999px";cache.poweredBy.style.display="none";}}}},getMapContainer:function(){return this.mapObject.getDiv();},getMapObjectBoundsFromOLBounds:function(olBounds){var moBounds=null;if(olBounds!=null){var sw=this.sphericalMercator?this.inverseMercator(olBounds.bottom,olBounds.left):new OpenLayers.LonLat(olBounds.bottom,olBounds.left);var ne=this.sphericalMercator?this.inverseMercator(olBounds.top,olBounds.right):new OpenLayers.LonLat(olBounds.top,olBounds.right);moBounds=new google.maps.LatLngBounds(new google.maps.LatLng(sw.lat,sw.lon),new google.maps.LatLng(ne.lat,ne.lon));}
+return moBounds;},getMapObjectLonLatFromMapObjectPixel:function(moPixel){var size=this.map.getSize();var lon=this.getLongitudeFromMapObjectLonLat(this.mapObject.center);var lat=this.getLatitudeFromMapObjectLonLat(this.mapObject.center);var res=this.map.getResolution();var delta_x=moPixel.x-(size.w/2);var delta_y=moPixel.y-(size.h/2);var lonlat=new OpenLayers.LonLat(lon+delta_x*res,lat-delta_y*res);if(this.wrapDateLine){lonlat=lonlat.wrapDateLine(this.maxExtent);}
+return this.getMapObjectLonLatFromLonLat(lonlat.lon,lonlat.lat);},getMapObjectPixelFromMapObjectLonLat:function(moLonLat){var lon=this.getLongitudeFromMapObjectLonLat(moLonLat);var lat=this.getLatitudeFromMapObjectLonLat(moLonLat);var res=this.map.getResolution();var extent=this.map.getExtent();var px=new OpenLayers.Pixel((1/res*(lon-extent.left)),(1/res*(extent.top-lat)));return this.getMapObjectPixelFromXY(px.x,px.y);},setMapObjectCenter:function(center,zoom){this.mapObject.setOptions({center:center,zoom:zoom});},getMapObjectZoomFromMapObjectBounds:function(moBounds){return this.mapObject.getBoundsZoomLevel(moBounds);},getMapObjectLonLatFromLonLat:function(lon,lat){var gLatLng;if(this.sphericalMercator){var lonlat=this.inverseMercator(lon,lat);gLatLng=new google.maps.LatLng(lonlat.lat,lonlat.lon);}else{gLatLng=new google.maps.LatLng(lat,lon);}
+return gLatLng;},getMapObjectPixelFromXY:function(x,y){return new google.maps.Point(x,y);},destroy:function(){if(this.repositionListener){google.maps.event.removeListener(this.repositionListener);}
+if(this.repositionTimer){window.clearTimeout(this.repositionTimer);}
+OpenLayers.Layer.Google.prototype.destroy.apply(this,arguments);}};OpenLayers.Layer.KaMap=OpenLayers.Class(OpenLayers.Layer.Grid,{isBaseLayer:true,units:null,resolution:OpenLayers.DOTS_PER_INCH,DEFAULT_PARAMS:{i:'jpeg',map:''},initialize:function(name,url,params,options){var newArguments=[];newArguments.push(name,url,params,options);OpenLayers.Layer.Grid.prototype.initialize.apply(this,newArguments);this.params=OpenLayers.Util.applyDefaults(this.params,this.DEFAULT_PARAMS);},getURL:function(bounds){bounds=this.adjustBounds(bounds);var mapRes=this.map.getResolution();var scale=Math.round((this.map.getScale()*10000))/10000;var pX=Math.round(bounds.left/mapRes);var pY=-Math.round(bounds.top/mapRes);return this.getFullRequestString({t:pY,l:pX,s:scale});},addTile:function(bounds,position){var url=this.getURL(bounds);return new OpenLayers.Tile.Image(this,position,bounds,url,this.tileSize);},calculateGridLayout:function(bounds,extent,resolution){var tilelon=resolution*this.tileSize.w;var tilelat=resolution*this.tileSize.h;var offsetlon=bounds.left;var tilecol=Math.floor(offsetlon/tilelon)-this.buffer;var tilecolremain=offsetlon/tilelon-tilecol;var tileoffsetx=-tilecolremain*this.tileSize.w;var tileoffsetlon=tilecol*tilelon;var offsetlat=bounds.top;var tilerow=Math.ceil(offsetlat/tilelat)+this.buffer;var tilerowremain=tilerow-offsetlat/tilelat;var tileoffsety=-(tilerowremain+1)*this.tileSize.h;var tileoffsetlat=tilerow*tilelat;return{tilelon:tilelon,tilelat:tilelat,tileoffsetlon:tileoffsetlon,tileoffsetlat:tileoffsetlat,tileoffsetx:tileoffsetx,tileoffsety:tileoffsety};},clone:function(obj){if(obj==null){obj=new OpenLayers.Layer.KaMap(this.name,this.url,this.params,this.getOptions());}
+obj=OpenLayers.Layer.Grid.prototype.clone.apply(this,[obj]);if(this.tileSize!=null){obj.tileSize=this.tileSize.clone();}
+obj.grid=[];return obj;},getTileBounds:function(viewPortPx){var resolution=this.getResolution();var tileMapWidth=resolution*this.tileSize.w;var tileMapHeight=resolution*this.tileSize.h;var mapPoint=this.getLonLatFromViewPortPx(viewPortPx);var tileLeft=tileMapWidth*Math.floor(mapPoint.lon/tileMapWidth);var tileBottom=tileMapHeight*Math.floor(mapPoint.lat/tileMapHeight);return new OpenLayers.Bounds(tileLeft,tileBottom,tileLeft+tileMapWidth,tileBottom+tileMapHeight);},CLASS_NAME:"OpenLayers.Layer.KaMap"});OpenLayers.Layer.MapGuide=OpenLayers.Class(OpenLayers.Layer.Grid,{isBaseLayer:true,useHttpTile:false,singleTile:false,useOverlay:false,useAsyncOverlay:true,TILE_PARAMS:{operation:'GETTILEIMAGE',version:'1.2.0'},SINGLE_TILE_PARAMS:{operation:'GETMAPIMAGE',format:'PNG',locale:'en',clip:'1',version:'1.0.0'},OVERLAY_PARAMS:{operation:'GETDYNAMICMAPOVERLAYIMAGE',format:'PNG',locale:'en',clip:'1',version:'2.0.0'},FOLDER_PARAMS:{tileColumnsPerFolder:30,tileRowsPerFolder:30,format:'png',querystring:null},defaultSize:new OpenLayers.Size(300,300),initialize:function(name,url,params,options){OpenLayers.Layer.Grid.prototype.initialize.apply(this,arguments);if(options==null||options.isBaseLayer==null){this.isBaseLayer=((this.transparent!="true")&&(this.transparent!=true));}
+if(options&&options.useOverlay!=null){this.useOverlay=options.useOverlay;}
+if(this.singleTile){if(this.useOverlay){OpenLayers.Util.applyDefaults(this.params,this.OVERLAY_PARAMS);if(!this.useAsyncOverlay){this.params.version="1.0.0";}}else{OpenLayers.Util.applyDefaults(this.params,this.SINGLE_TILE_PARAMS);}}else{if(this.useHttpTile){OpenLayers.Util.applyDefaults(this.params,this.FOLDER_PARAMS);}else{OpenLayers.Util.applyDefaults(this.params,this.TILE_PARAMS);}
+this.setTileSize(this.defaultSize);}},clone:function(obj){if(obj==null){obj=new OpenLayers.Layer.MapGuide(this.name,this.url,this.params,this.getOptions());}
+obj=OpenLayers.Layer.Grid.prototype.clone.apply(this,[obj]);return obj;},addTile:function(bounds,position){return new OpenLayers.Tile.Image(this,position,bounds,null,this.tileSize);},getURL:function(bounds){var url;var center=bounds.getCenterLonLat();var mapSize=this.map.getSize();if(this.singleTile){var params={setdisplaydpi:OpenLayers.DOTS_PER_INCH,setdisplayheight:mapSize.h*this.ratio,setdisplaywidth:mapSize.w*this.ratio,setviewcenterx:center.lon,setviewcentery:center.lat,setviewscale:this.map.getScale()};if(this.useOverlay&&!this.useAsyncOverlay){var getVisParams={};getVisParams=OpenLayers.Util.extend(getVisParams,params);getVisParams.operation="GETVISIBLEMAPEXTENT";getVisParams.version="1.0.0";getVisParams.session=this.params.session;getVisParams.mapName=this.params.mapName;getVisParams.format='text/xml';url=this.getFullRequestString(getVisParams);OpenLayers.Request.GET({url:url,async:false});}
+url=this.getFullRequestString(params);}else{var currentRes=this.map.getResolution();var colidx=Math.floor((bounds.left-this.maxExtent.left)/currentRes);colidx=Math.round(colidx/this.tileSize.w);var rowidx=Math.floor((this.maxExtent.top-bounds.top)/currentRes);rowidx=Math.round(rowidx/this.tileSize.h);if(this.useHttpTile){url=this.getImageFilePath({tilecol:colidx,tilerow:rowidx,scaleindex:this.resolutions.length-this.map.zoom-1});}else{url=this.getFullRequestString({tilecol:colidx,tilerow:rowidx,scaleindex:this.resolutions.length-this.map.zoom-1});}}
+return url;},getFullRequestString:function(newParams,altUrl){var url=(altUrl==null)?this.url:altUrl;if(typeof url=="object"){url=url[Math.floor(Math.random()*url.length)];}
+var requestString=url;var allParams=OpenLayers.Util.extend({},this.params);allParams=OpenLayers.Util.extend(allParams,newParams);var urlParams=OpenLayers.Util.upperCaseObject(OpenLayers.Util.getParameters(url));for(var key in allParams){if(key.toUpperCase()in urlParams){delete allParams[key];}}
+var paramsString=OpenLayers.Util.getParameterString(allParams);paramsString=paramsString.replace(/,/g,"+");if(paramsString!=""){var lastServerChar=url.charAt(url.length-1);if((lastServerChar=="&")||(lastServerChar=="?")){requestString+=paramsString;}else{if(url.indexOf('?')==-1){requestString+='?'+paramsString;}else{requestString+='&'+paramsString;}}}
+return requestString;},getImageFilePath:function(newParams,altUrl){var url=(altUrl==null)?this.url:altUrl;if(typeof url=="object"){url=url[Math.floor(Math.random()*url.length)];}
+var requestString=url;var tileRowGroup="";var tileColGroup="";if(newParams.tilerow<0){tileRowGroup='-';}
+if(newParams.tilerow==0){tileRowGroup+='0';}else{tileRowGroup+=Math.floor(Math.abs(newParams.tilerow/this.params.tileRowsPerFolder))*this.params.tileRowsPerFolder;}
+if(newParams.tilecol<0){tileColGroup='-';}
+if(newParams.tilecol==0){tileColGroup+='0';}else{tileColGroup+=Math.floor(Math.abs(newParams.tilecol/this.params.tileColumnsPerFolder))*this.params.tileColumnsPerFolder;}
+var tilePath='/S'+Math.floor(newParams.scaleindex)
++'/'+this.params.basemaplayergroupname
++'/R'+tileRowGroup
++'/C'+tileColGroup
++'/'+(newParams.tilerow%this.params.tileRowsPerFolder)
++'_'+(newParams.tilecol%this.params.tileColumnsPerFolder)
++'.'+this.params.format;if(this.params.querystring){tilePath+="?"+this.params.querystring;}
+requestString+=tilePath;return requestString;},calculateGridLayout:function(bounds,extent,resolution){var tilelon=resolution*this.tileSize.w;var tilelat=resolution*this.tileSize.h;var offsetlon=bounds.left-extent.left;var tilecol=Math.floor(offsetlon/tilelon)-this.buffer;var tilecolremain=offsetlon/tilelon-tilecol;var tileoffsetx=-tilecolremain*this.tileSize.w;var tileoffsetlon=extent.left+tilecol*tilelon;var offsetlat=extent.top-bounds.top+tilelat;var tilerow=Math.floor(offsetlat/tilelat)-this.buffer;var tilerowremain=tilerow-offsetlat/tilelat;var tileoffsety=tilerowremain*this.tileSize.h;var tileoffsetlat=extent.top-tilelat*tilerow;return{tilelon:tilelon,tilelat:tilelat,tileoffsetlon:tileoffsetlon,tileoffsetlat:tileoffsetlat,tileoffsetx:tileoffsetx,tileoffsety:tileoffsety};},CLASS_NAME:"OpenLayers.Layer.MapGuide"});OpenLayers.Layer.MapServer=OpenLayers.Class(OpenLayers.Layer.Grid,{DEFAULT_PARAMS:{mode:"map",map_imagetype:"png"},initialize:function(name,url,params,options){var newArguments=[];newArguments.push(name,url,params,options);OpenLayers.Layer.Grid.prototype.initialize.apply(this,newArguments);this.params=OpenLayers.Util.applyDefaults(this.params,this.DEFAULT_PARAMS);if(options==null||options.isBaseLayer==null){this.isBaseLayer=((this.params.transparent!="true")&&(this.params.transparent!=true));}},clone:function(obj){if(obj==null){obj=new OpenLayers.Layer.MapServer(this.name,this.url,this.params,this.getOptions());}
+obj=OpenLayers.Layer.Grid.prototype.clone.apply(this,[obj]);return obj;},addTile:function(bounds,position){return new OpenLayers.Tile.Image(this,position,bounds,null,this.tileSize);},getURL:function(bounds){bounds=this.adjustBounds(bounds);var extent=[bounds.left,bounds.bottom,bounds.right,bounds.top];var imageSize=this.getImageSize();var url=this.getFullRequestString({mapext:extent,imgext:extent,map_size:[imageSize.w,imageSize.h],imgx:imageSize.w/2,imgy:imageSize.h/2,imgxy:[imageSize.w,imageSize.h]});return url;},getFullRequestString:function(newParams,altUrl){var url=(altUrl==null)?this.url:altUrl;var allParams=OpenLayers.Util.extend({},this.params);allParams=OpenLayers.Util.extend(allParams,newParams);var paramsString=OpenLayers.Util.getParameterString(allParams);if(url instanceof Array){url=this.selectUrl(paramsString,url);}
+var urlParams=OpenLayers.Util.upperCaseObject(OpenLayers.Util.getParameters(url));for(var key in allParams){if(key.toUpperCase()in urlParams){delete allParams[key];}}
+paramsString=OpenLayers.Util.getParameterString(allParams);var requestString=url;paramsString=paramsString.replace(/,/g,"+");if(paramsString!=""){var lastServerChar=url.charAt(url.length-1);if((lastServerChar=="&")||(lastServerChar=="?")){requestString+=paramsString;}else{if(url.indexOf('?')==-1){requestString+='?'+paramsString;}else{requestString+='&'+paramsString;}}}
+return requestString;},CLASS_NAME:"OpenLayers.Layer.MapServer"});OpenLayers.Layer.TMS=OpenLayers.Class(OpenLayers.Layer.Grid,{serviceVersion:"1.0.0",isBaseLayer:true,tileOrigin:null,serverResolutions:null,zoomOffset:0,initialize:function(name,url,options){var newArguments=[];newArguments.push(name,url,{},options);OpenLayers.Layer.Grid.prototype.initialize.apply(this,newArguments);},destroy:function(){OpenLayers.Layer.Grid.prototype.destroy.apply(this,arguments);},clone:function(obj){if(obj==null){obj=new OpenLayers.Layer.TMS(this.name,this.url,this.getOptions());}
+obj=OpenLayers.Layer.Grid.prototype.clone.apply(this,[obj]);return obj;},getURL:function(bounds){bounds=this.adjustBounds(bounds);var res=this.map.getResolution();var x=Math.round((bounds.left-this.tileOrigin.lon)/(res*this.tileSize.w));var y=Math.round((bounds.bottom-this.tileOrigin.lat)/(res*this.tileSize.h));var z=this.serverResolutions!=null?OpenLayers.Util.indexOf(this.serverResolutions,res):this.map.getZoom()+this.zoomOffset;var path=this.serviceVersion+"/"+this.layername+"/"+z+"/"+x+"/"+y+"."+this.type;var url=this.url;if(url instanceof Array){url=this.selectUrl(path,url);}
+return url+path;},addTile:function(bounds,position){return new OpenLayers.Tile.Image(this,position,bounds,null,this.tileSize);},setMap:function(map){OpenLayers.Layer.Grid.prototype.setMap.apply(this,arguments);if(!this.tileOrigin){this.tileOrigin=new OpenLayers.LonLat(this.map.maxExtent.left,this.map.maxExtent.bottom);}},CLASS_NAME:"OpenLayers.Layer.TMS"});OpenLayers.Layer.TileCache=OpenLayers.Class(OpenLayers.Layer.Grid,{isBaseLayer:true,format:'image/png',serverResolutions:null,initialize:function(name,url,layername,options){this.layername=layername;OpenLayers.Layer.Grid.prototype.initialize.apply(this,[name,url,{},options]);this.extension=this.format.split('/')[1].toLowerCase();this.extension=(this.extension=='jpg')?'jpeg':this.extension;},clone:function(obj){if(obj==null){obj=new OpenLayers.Layer.TileCache(this.name,this.url,this.layername,this.getOptions());}
+obj=OpenLayers.Layer.Grid.prototype.clone.apply(this,[obj]);return obj;},getURL:function(bounds){var res=this.map.getResolution();var bbox=this.maxExtent;var size=this.tileSize;var tileX=Math.round((bounds.left-bbox.left)/(res*size.w));var tileY=Math.round((bounds.bottom-bbox.bottom)/(res*size.h));var tileZ=this.serverResolutions!=null?OpenLayers.Util.indexOf(this.serverResolutions,res):this.map.getZoom();function zeroPad(number,length){number=String(number);var zeros=[];for(var i=0;i<length;++i){zeros.push('0');}
+return zeros.join('').substring(0,length-number.length)+number;}
+var components=[this.layername,zeroPad(tileZ,2),zeroPad(parseInt(tileX/1000000),3),zeroPad((parseInt(tileX/1000)%1000),3),zeroPad((parseInt(tileX)%1000),3),zeroPad(parseInt(tileY/1000000),3),zeroPad((parseInt(tileY/1000)%1000),3),zeroPad((parseInt(tileY)%1000),3)+'.'+this.extension];var path=components.join('/');var url=this.url;if(url instanceof Array){url=this.selectUrl(path,url);}
+url=(url.charAt(url.length-1)=='/')?url:url+'/';return url+path;},addTile:function(bounds,position){var url=this.getURL(bounds);return new OpenLayers.Tile.Image(this,position,bounds,url,this.tileSize);},CLASS_NAME:"OpenLayers.Layer.TileCache"});OpenLayers.Layer.WMS=OpenLayers.Class(OpenLayers.Layer.Grid,{DEFAULT_PARAMS:{service:"WMS",version:"1.1.1",request:"GetMap",styles:"",exceptions:"application/vnd.ogc.se_inimage",format:"image/jpeg"},reproject:false,isBaseLayer:true,encodeBBOX:false,noMagic:false,yx:{'EPSG:4326':true},initialize:function(name,url,params,options){var newArguments=[];params=OpenLayers.Util.upperCaseObject(params);if(parseFloat(params.VERSION)>=1.3&&!params.EXCEPTIONS){params.EXCEPTIONS="INIMAGE";}
+newArguments.push(name,url,params,options);OpenLayers.Layer.Grid.prototype.initialize.apply(this,newArguments);OpenLayers.Util.applyDefaults(this.params,OpenLayers.Util.upperCaseObject(this.DEFAULT_PARAMS));if(!this.noMagic&&this.params.TRANSPARENT&&this.params.TRANSPARENT.toString().toLowerCase()=="true"){if((options==null)||(!options.isBaseLayer)){this.isBaseLayer=false;}
+if(this.params.FORMAT=="image/jpeg"){this.params.FORMAT=OpenLayers.Util.alphaHack()?"image/gif":"image/png";}}},destroy:function(){OpenLayers.Layer.Grid.prototype.destroy.apply(this,arguments);},clone:function(obj){if(obj==null){obj=new OpenLayers.Layer.WMS(this.name,this.url,this.params,this.getOptions());}
+obj=OpenLayers.Layer.Grid.prototype.clone.apply(this,[obj]);return obj;},reverseAxisOrder:function(){return(parseFloat(this.params.VERSION)>=1.3&&!!this.yx[this.map.getProjectionObject().getCode()]);},getURL:function(bounds){bounds=this.adjustBounds(bounds);var imageSize=this.getImageSize();var newParams={};var reverseAxisOrder=this.reverseAxisOrder();newParams.BBOX=this.encodeBBOX?bounds.toBBOX(null,reverseAxisOrder):bounds.toArray(reverseAxisOrder);newParams.WIDTH=imageSize.w;newParams.HEIGHT=imageSize.h;var requestString=this.getFullRequestString(newParams);return requestString;},addTile:function(bounds,position){return new OpenLayers.Tile.Image(this,position,bounds,null,this.tileSize);},mergeNewParams:function(newParams){var upperParams=OpenLayers.Util.upperCaseObject(newParams);var newArguments=[upperParams];return OpenLayers.Layer.Grid.prototype.mergeNewParams.apply(this,newArguments);},getFullRequestString:function(newParams,altUrl){var projectionCode=this.map.getProjection();var value=(projectionCode=="none")?null:projectionCode
+if(parseFloat(this.params.VERSION)>=1.3){this.params.CRS=value;}else{this.params.SRS=value;}
+return OpenLayers.Layer.Grid.prototype.getFullRequestString.apply(this,arguments);},CLASS_NAME:"OpenLayers.Layer.WMS"});OpenLayers.Layer.WMTS=OpenLayers.Class(OpenLayers.Layer.Grid,{isBaseLayer:true,version:"1.0.0",requestEncoding:"KVP",url:null,layer:null,matrixSet:null,style:null,format:"image/jpeg",tileOrigin:null,tileFullExtent:null,formatSuffix:null,matrixIds:null,dimensions:null,params:null,zoomOffset:0,formatSuffixMap:{"image/png":"png","image/png8":"png","image/png24":"png","image/png32":"png","png":"png","image/jpeg":"jpg","image/jpg":"jpg","jpeg":"jpg","jpg":"jpg"},matrix:null,initialize:function(config){var required={url:true,layer:true,style:true,matrixSet:true};for(var prop in required){if(!(prop in config)){throw new Error("Missing property '"+prop+"' in layer configuration.");}}
+config.params=OpenLayers.Util.upperCaseObject(config.params);var args=[config.name,config.url,config.params,config];OpenLayers.Layer.Grid.prototype.initialize.apply(this,args);if(!this.formatSuffix){this.formatSuffix=this.formatSuffixMap[this.format]||this.format.split("/").pop();}
+if(this.matrixIds){var len=this.matrixIds.length;if(len&&typeof this.matrixIds[0]==="string"){var ids=this.matrixIds;this.matrixIds=new Array(len);for(var i=0;i<len;++i){this.matrixIds[i]={identifier:ids[i]};}}}},setMap:function(){OpenLayers.Layer.Grid.prototype.setMap.apply(this,arguments);this.updateMatrixProperties();},updateMatrixProperties:function(){this.matrix=this.getMatrix();if(this.matrix){if(this.matrix.topLeftCorner){this.tileOrigin=this.matrix.topLeftCorner;}
+if(this.matrix.tileWidth&&this.matrix.tileHeight){this.tileSize=new OpenLayers.Size(this.matrix.tileWidth,this.matrix.tileHeight);}
+if(!this.tileOrigin){this.tileOrigin=new OpenLayers.LonLat(this.maxExtent.left,this.maxExtent.top);}
+if(!this.tileFullExtent){this.tileFullExtent=this.maxExtent;}}},moveTo:function(bounds,zoomChanged,dragging){if(zoomChanged||!this.matrix){this.updateMatrixProperties();}
+return OpenLayers.Layer.Grid.prototype.moveTo.apply(this,arguments);},clone:function(obj){if(obj==null){obj=new OpenLayers.Layer.WMTS(this.options);}
+obj=OpenLayers.Layer.Grid.prototype.clone.apply(this,[obj]);return obj;},getMatrix:function(){var matrix;if(!this.matrixIds||this.matrixIds.length===0){matrix={identifier:this.map.getZoom()+this.zoomOffset};}else{if("scaleDenominator"in this.matrixIds[0]){var denom=OpenLayers.METERS_PER_INCH*OpenLayers.INCHES_PER_UNIT[this.units]*this.map.getResolution()/0.28E-3;var diff=Number.POSITIVE_INFINITY;var delta;for(var i=0,ii=this.matrixIds.length;i<ii;++i){delta=Math.abs(1-(this.matrixIds[i].scaleDenominator/denom));if(delta<diff){diff=delta;matrix=this.matrixIds[i];}}}else{matrix=this.matrixIds[this.map.getZoom()+this.zoomOffset];}}
+return matrix;},getTileInfo:function(loc){var res=this.map.getResolution();var fx=(loc.lon-this.tileOrigin.lon)/(res*this.tileSize.w);var fy=(this.tileOrigin.lat-loc.lat)/(res*this.tileSize.h);var col=Math.floor(fx);var row=Math.floor(fy);return{col:col,row:row,i:Math.floor((fx-col)*this.tileSize.w),j:Math.floor((fy-row)*this.tileSize.h)};},getURL:function(bounds){bounds=this.adjustBounds(bounds);var url="";if(!this.tileFullExtent||this.tileFullExtent.intersectsBounds(bounds)){var center=bounds.getCenterLonLat();var info=this.getTileInfo(center);var matrixId=this.matrix.identifier;if(this.requestEncoding.toUpperCase()==="REST"){var path=this.version+"/"+this.layer+"/"+this.style+"/";if(this.dimensions){for(var i=0;i<this.dimensions.length;i++){if(this.params[this.dimensions[i]]){path=path+this.params[this.dimensions[i]]+"/";}}}
+path=path+this.matrixSet+"/"+this.matrix.identifier+"/"+info.row+"/"+info.col+"."+this.formatSuffix;if(this.url instanceof Array){url=this.selectUrl(path,this.url);}else{url=this.url;}
+if(!url.match(/\/$/)){url=url+"/";}
+url=url+path;}else if(this.requestEncoding.toUpperCase()==="KVP"){var params={SERVICE:"WMTS",REQUEST:"GetTile",VERSION:this.version,LAYER:this.layer,STYLE:this.style,TILEMATRIXSET:this.matrixSet,TILEMATRIX:this.matrix.identifier,TILEROW:info.row,TILECOL:info.col,FORMAT:this.format};url=OpenLayers.Layer.Grid.prototype.getFullRequestString.apply(this,[params]);}}
+return url;},mergeNewParams:function(newParams){if(this.requestEncoding.toUpperCase()==="KVP"){return OpenLayers.Layer.Grid.prototype.mergeNewParams.apply(this,[OpenLayers.Util.upperCaseObject(newParams)]);}},addTile:function(bounds,position){return new OpenLayers.Tile.Image(this,position,bounds,null,this.tileSize);},CLASS_NAME:"OpenLayers.Layer.WMTS"});OpenLayers.Layer.WorldWind=OpenLayers.Class(OpenLayers.Layer.Grid,{DEFAULT_PARAMS:{},isBaseLayer:true,lzd:null,zoomLevels:null,initialize:function(name,url,lzd,zoomLevels,params,options){this.lzd=lzd;this.zoomLevels=zoomLevels;var newArguments=[];newArguments.push(name,url,params,options);OpenLayers.Layer.Grid.prototype.initialize.apply(this,newArguments);this.params=OpenLayers.Util.applyDefaults(this.params,this.DEFAULT_PARAMS);},addTile:function(bounds,position){return new OpenLayers.Tile.Image(this,position,bounds,null,this.tileSize);},getZoom:function(){var zoom=this.map.getZoom();var extent=this.map.getMaxExtent();zoom=zoom-Math.log(this.maxResolution/(this.lzd/512))/Math.log(2);return zoom;},getURL:function(bounds){bounds=this.adjustBounds(bounds);var zoom=this.getZoom();var extent=this.map.getMaxExtent();var deg=this.lzd/Math.pow(2,this.getZoom());var x=Math.floor((bounds.left-extent.left)/deg);var y=Math.floor((bounds.bottom-extent.bottom)/deg);if(this.map.getResolution()<=(this.lzd/512)&&this.getZoom()<=this.zoomLevels){return this.getFullRequestString({L:zoom,X:x,Y:y});}else{return OpenLayers.Util.getImagesLocation()+"blank.gif";}},CLASS_NAME:"OpenLayers.Layer.WorldWind"});OpenLayers.Layer.XYZ=OpenLayers.Class(OpenLayers.Layer.Grid,{isBaseLayer:true,sphericalMercator:false,zoomOffset:0,initialize:function(name,url,options){if(options&&options.sphericalMercator||this.sphericalMercator){options=OpenLayers.Util.extend({maxExtent:new OpenLayers.Bounds(-128*156543.0339,-128*156543.0339,128*156543.0339,128*156543.0339),maxResolution:156543.0339,numZoomLevels:19,units:"m",projection:"EPSG:900913"},options);}
+url=url||this.url;name=name||this.name;var newArguments=[name,url,{},options];OpenLayers.Layer.Grid.prototype.initialize.apply(this,newArguments);},clone:function(obj){if(obj==null){obj=new OpenLayers.Layer.XYZ(this.name,this.url,this.getOptions());}
+obj=OpenLayers.Layer.Grid.prototype.clone.apply(this,[obj]);return obj;},getURL:function(bounds){var res=this.map.getResolution();var x=Math.round((bounds.left-this.maxExtent.left)/(res*this.tileSize.w));var y=Math.round((this.maxExtent.top-bounds.top)/(res*this.tileSize.h));var z=this.map.getZoom()+this.zoomOffset;var url=this.url;var s=''+x+y+z;if(url instanceof Array)
+{url=this.selectUrl(s,url);}
+var path=OpenLayers.String.format(url,{'x':x,'y':y,'z':z});return path;},addTile:function(bounds,position){return new OpenLayers.Tile.Image(this,position,bounds,null,this.tileSize);},setMap:function(map){OpenLayers.Layer.Grid.prototype.setMap.apply(this,arguments);if(!this.tileOrigin){this.tileOrigin=new OpenLayers.LonLat(this.maxExtent.left,this.maxExtent.bottom);}},CLASS_NAME:"OpenLayers.Layer.XYZ"});OpenLayers.Layer.OSM=OpenLayers.Class(OpenLayers.Layer.XYZ,{name:"OpenStreetMap",attribution:"Data CC-By-SA by <a href='http://openstreetmap.org/'>OpenStreetMap</a>",sphericalMercator:true,url:'http://tile.openstreetmap.org/${z}/${x}/${y}.png',clone:function(obj){if(obj==null){obj=new OpenLayers.Layer.OSM(this.name,this.url,this.getOptions());}
+obj=OpenLayers.Layer.XYZ.prototype.clone.apply(this,[obj]);return obj;},CLASS_NAME:"OpenLayers.Layer.OSM"});OpenLayers.Layer.Zoomify=OpenLayers.Class(OpenLayers.Layer.Grid,{url:null,size:null,isBaseLayer:true,standardTileSize:256,numberOfTiers:0,tileCountUpToTier:new Array(),tierSizeInTiles:new Array(),tierImageSize:new Array(),initialize:function(name,url,size,options){this.initializeZoomify(size);var newArguments=[];newArguments.push(name,url,size,{},options);OpenLayers.Layer.Grid.prototype.initialize.apply(this,newArguments);},initializeZoomify:function(size){var imageSize=size.clone()
+var tiles=new OpenLayers.Size(Math.ceil(imageSize.w/this.standardTileSize),Math.ceil(imageSize.h/this.standardTileSize));this.tierSizeInTiles.push(tiles);this.tierImageSize.push(imageSize);while(imageSize.w>this.standardTileSize||imageSize.h>this.standardTileSize){imageSize=new OpenLayers.Size(Math.floor(imageSize.w/2),Math.floor(imageSize.h/2));tiles=new OpenLayers.Size(Math.ceil(imageSize.w/this.standardTileSize),Math.ceil(imageSize.h/this.standardTileSize));this.tierSizeInTiles.push(tiles);this.tierImageSize.push(imageSize);}
+this.tierSizeInTiles.reverse();this.tierImageSize.reverse();this.numberOfTiers=this.tierSizeInTiles.length;this.tileCountUpToTier[0]=0;for(var i=1;i<this.numberOfTiers;i++){this.tileCountUpToTier.push(this.tierSizeInTiles[i-1].w*this.tierSizeInTiles[i-1].h+
+this.tileCountUpToTier[i-1]);}},destroy:function(){OpenLayers.Layer.Grid.prototype.destroy.apply(this,arguments);this.tileCountUpToTier.length=0
+this.tierSizeInTiles.length=0
+this.tierImageSize.length=0},clone:function(obj){if(obj==null){obj=new OpenLayers.Layer.Zoomify(this.name,this.url,this.size,this.options);}
+obj=OpenLayers.Layer.Grid.prototype.clone.apply(this,[obj]);return obj;},getURL:function(bounds){bounds=this.adjustBounds(bounds);var res=this.map.getResolution();var x=Math.round((bounds.left-this.tileOrigin.lon)/(res*this.tileSize.w));var y=Math.round((this.tileOrigin.lat-bounds.top)/(res*this.tileSize.h));var z=this.map.getZoom();var tileIndex=x+y*this.tierSizeInTiles[z].w+this.tileCountUpToTier[z];var path="TileGroup"+Math.floor((tileIndex)/256)+"/"+z+"-"+x+"-"+y+".jpg";var url=this.url;if(url instanceof Array){url=this.selectUrl(path,url);}
+return url+path;},getImageSize:function(){if(arguments.length>0){bounds=this.adjustBounds(arguments[0]);var res=this.map.getResolution();var x=Math.round((bounds.left-this.tileOrigin.lon)/(res*this.tileSize.w));var y=Math.round((this.tileOrigin.lat-bounds.top)/(res*this.tileSize.h));var z=this.map.getZoom();var w=this.standardTileSize;var h=this.standardTileSize;if(x==this.tierSizeInTiles[z].w-1){var w=this.tierImageSize[z].w%this.standardTileSize;};if(y==this.tierSizeInTiles[z].h-1){var h=this.tierImageSize[z].h%this.standardTileSize;};return(new OpenLayers.Size(w,h));}else{return this.tileSize;}},addTile:function(bounds,position){return new OpenLayers.Tile.Image(this,position,bounds,null,this.tileSize);},setMap:function(map){OpenLayers.Layer.Grid.prototype.setMap.apply(this,arguments);this.tileOrigin=new OpenLayers.LonLat(this.map.maxExtent.left,this.map.maxExtent.top);},calculateGridLayout:function(bounds,extent,resolution){var tilelon=resolution*this.tileSize.w;var tilelat=resolution*this.tileSize.h;var offsetlon=bounds.left-extent.left;var tilecol=Math.floor(offsetlon/tilelon)-this.buffer;var tilecolremain=offsetlon/tilelon-tilecol;var tileoffsetx=-tilecolremain*this.tileSize.w;var tileoffsetlon=extent.left+tilecol*tilelon;var offsetlat=extent.top-bounds.top+tilelat;var tilerow=Math.floor(offsetlat/tilelat)-this.buffer;var tilerowremain=tilerow-offsetlat/tilelat;var tileoffsety=tilerowremain*this.tileSize.h;var tileoffsetlat=extent.top-tilelat*tilerow;return{tilelon:tilelon,tilelat:tilelat,tileoffsetlon:tileoffsetlon,tileoffsetlat:tileoffsetlat,tileoffsetx:tileoffsetx,tileoffsety:tileoffsety};},CLASS_NAME:"OpenLayers.Layer.Zoomify"});OpenLayers.Protocol.SQL.Gears=OpenLayers.Class(OpenLayers.Protocol.SQL,{FID_PREFIX:'__gears_fid__',NULL_GEOMETRY:'__gears_null_geometry__',NULL_FEATURE_STATE:'__gears_null_feature_state__',jsonParser:null,wktParser:null,fidRegExp:null,saveFeatureState:true,typeOfFid:"string",db:null,initialize:function(options){if(!this.supported()){return;}
+OpenLayers.Protocol.SQL.prototype.initialize.apply(this,[options]);this.jsonParser=new OpenLayers.Format.JSON();this.wktParser=new OpenLayers.Format.WKT();this.fidRegExp=new RegExp('^'+this.FID_PREFIX);this.initializeDatabase();},initializeDatabase:function(){this.db=google.gears.factory.create('beta.database');this.db.open(this.databaseName);this.db.execute("CREATE TABLE IF NOT EXISTS "+this.tableName+" (fid TEXT UNIQUE, geometry TEXT, properties TEXT,"+" state TEXT)");},destroy:function(){this.db.close();this.db=null;this.jsonParser=null;this.wktParser=null;OpenLayers.Protocol.SQL.prototype.destroy.apply(this);},supported:function(){return!!(window.google&&google.gears);},read:function(options){OpenLayers.Protocol.prototype.read.apply(this,arguments);options=OpenLayers.Util.applyDefaults(options,this.options);var feature,features=[];var rs=this.db.execute("SELECT * FROM "+this.tableName);while(rs.isValidRow()){feature=this.unfreezeFeature(rs);if(this.evaluateFilter(feature,options.filter)){if(!options.noFeatureStateReset){feature.state=null;}
+features.push(feature);}
+rs.next();}
+rs.close();var resp=new OpenLayers.Protocol.Response({code:OpenLayers.Protocol.Response.SUCCESS,requestType:"read",features:features});if(options&&options.callback){options.callback.call(options.scope,resp);}
+return resp;},unfreezeFeature:function(row){var feature;var wkt=row.fieldByName('geometry');if(wkt==this.NULL_GEOMETRY){feature=new OpenLayers.Feature.Vector();}else{feature=this.wktParser.read(wkt);}
+feature.attributes=this.jsonParser.read(row.fieldByName('properties'));feature.fid=this.extractFidFromField(row.fieldByName('fid'));var state=row.fieldByName('state');if(state==this.NULL_FEATURE_STATE){state=null;}
+feature.state=state;return feature;},extractFidFromField:function(field){if(!field.match(this.fidRegExp)&&this.typeOfFid=="number"){field=parseFloat(field);}
+return field;},create:function(features,options){options=OpenLayers.Util.applyDefaults(options,this.options);var resp=this.createOrUpdate(features);resp.requestType="create";if(options&&options.callback){options.callback.call(options.scope,resp);}
+return resp;},update:function(features,options){options=OpenLayers.Util.applyDefaults(options,this.options);var resp=this.createOrUpdate(features);resp.requestType="update";if(options&&options.callback){options.callback.call(options.scope,resp);}
+return resp;},createOrUpdate:function(features){if(!(features instanceof Array)){features=[features];}
+var i,len=features.length,feature;var insertedFeatures=new Array(len);for(i=0;i<len;i++){feature=features[i];var params=this.freezeFeature(feature);this.db.execute("REPLACE INTO "+this.tableName+" (fid, geometry, properties, state)"+" VALUES (?, ?, ?, ?)",params);var clone=feature.clone();clone.fid=this.extractFidFromField(params[0]);insertedFeatures[i]=clone;}
+return new OpenLayers.Protocol.Response({code:OpenLayers.Protocol.Response.SUCCESS,features:insertedFeatures,reqFeatures:features});},freezeFeature:function(feature){feature.fid=feature.fid!=null?""+feature.fid:OpenLayers.Util.createUniqueID(this.FID_PREFIX);var geometry=feature.geometry!=null?feature.geometry.toString():this.NULL_GEOMETRY;var properties=this.jsonParser.write(feature.attributes);var state=this.getFeatureStateForFreeze(feature);return[feature.fid,geometry,properties,state];},getFeatureStateForFreeze:function(feature){var state;if(!this.saveFeatureState){state=this.NULL_FEATURE_STATE;}else if(this.createdOffline(feature)){state=OpenLayers.State.INSERT;}else{state=feature.state;}
+return state;},"delete":function(features,options){if(!(features instanceof Array)){features=[features];}
+options=OpenLayers.Util.applyDefaults(options,this.options);var i,len,feature;for(i=0,len=features.length;i<len;i++){feature=features[i];if(this.saveFeatureState&&!this.createdOffline(feature)){var toDelete=feature.clone();toDelete.fid=feature.fid;if(toDelete.geometry){toDelete.geometry.destroy();toDelete.geometry=null;}
+toDelete.state=feature.state;this.createOrUpdate(toDelete);}else{this.db.execute("DELETE FROM "+this.tableName+" WHERE fid = ?",[feature.fid]);}}
+var resp=new OpenLayers.Protocol.Response({code:OpenLayers.Protocol.Response.SUCCESS,requestType:"delete",reqFeatures:features});if(options&&options.callback){options.callback.call(options.scope,resp);}
+return resp;},createdOffline:function(feature){return(typeof feature.fid=="string"&&!!(feature.fid.match(this.fidRegExp)));},commit:function(features,options){var opt,resp=[],nRequests=0,nResponses=0;function callback(resp){if(++nResponses<nRequests){resp.last=false;}
+this.callUserCallback(options,resp);}
+var feature,toCreate=[],toUpdate=[],toDelete=[];for(var i=features.length-1;i>=0;i--){feature=features[i];switch(feature.state){case OpenLayers.State.INSERT:toCreate.push(feature);break;case OpenLayers.State.UPDATE:toUpdate.push(feature);break;case OpenLayers.State.DELETE:toDelete.push(feature);break;}}
+if(toCreate.length>0){nRequests++;opt=OpenLayers.Util.applyDefaults({"callback":callback,"scope":this},options.create);resp.push(this.create(toCreate,opt));}
+if(toUpdate.length>0){nRequests++;opt=OpenLayers.Util.applyDefaults({"callback":callback,"scope":this},options.update);resp.push(this.update(toUpdate,opt));}
+if(toDelete.length>0){nRequests++;opt=OpenLayers.Util.applyDefaults({"callback":callback,"scope":this},options["delete"]);resp.push(this["delete"](toDelete,opt));}
+return resp;},clear:function(){this.db.execute("DELETE FROM "+this.tableName);},callUserCallback:function(options,resp){var opt=options[resp.requestType];if(opt&&opt.callback){opt.callback.call(opt.scope,resp);}
+if(resp.last&&options.callback){options.callback.call(options.scope);}},CLASS_NAME:"OpenLayers.Protocol.SQL.Gears"});OpenLayers.Rule=OpenLayers.Class({id:null,name:null,title:null,description:null,context:null,filter:null,elseFilter:false,symbolizer:null,symbolizers:null,minScaleDenominator:null,maxScaleDenominator:null,initialize:function(options){this.symbolizer={};OpenLayers.Util.extend(this,options);if(this.symbolizers){delete this.symbolizer;}
+this.id=OpenLayers.Util.createUniqueID(this.CLASS_NAME+"_");},destroy:function(){for(var i in this.symbolizer){this.symbolizer[i]=null;}
+this.symbolizer=null;delete this.symbolizers;},evaluate:function(feature){var context=this.getContext(feature);var applies=true;if(this.minScaleDenominator||this.maxScaleDenominator){var scale=feature.layer.map.getScale();}
+if(this.minScaleDenominator){applies=scale>=OpenLayers.Style.createLiteral(this.minScaleDenominator,context);}
+if(applies&&this.maxScaleDenominator){applies=scale<OpenLayers.Style.createLiteral(this.maxScaleDenominator,context);}
+if(applies&&this.filter){if(this.filter.CLASS_NAME=="OpenLayers.Filter.FeatureId"){applies=this.filter.evaluate(feature);}else{applies=this.filter.evaluate(context);}}
+return applies;},getContext:function(feature){var context=this.context;if(!context){context=feature.attributes||feature.data;}
+if(typeof this.context=="function"){context=this.context(feature);}
+return context;},clone:function(){var options=OpenLayers.Util.extend({},this);if(this.symbolizers){var len=this.symbolizers.length;options.symbolizers=new Array(len);for(var i=0;i<len;++i){options.symbolizers[i]=this.symbolizers[i].clone();}}else{options.symbolizer={};var value,type;for(var key in this.symbolizer){value=this.symbolizer[key];type=typeof value;if(type==="object"){options.symbolizer[key]=OpenLayers.Util.extend({},value);}else if(type==="string"){options.symbolizer[key]=value;}}}
+options.filter=this.filter&&this.filter.clone();options.context=this.context&&OpenLayers.Util.extend({},this.context);return new OpenLayers.Rule(options);},CLASS_NAME:"OpenLayers.Rule"});OpenLayers.StyleMap=OpenLayers.Class({styles:null,extendDefault:true,initialize:function(style,options){this.styles={"default":new OpenLayers.Style(OpenLayers.Feature.Vector.style["default"]),"select":new OpenLayers.Style(OpenLayers.Feature.Vector.style["select"]),"temporary":new OpenLayers.Style(OpenLayers.Feature.Vector.style["temporary"]),"delete":new OpenLayers.Style(OpenLayers.Feature.Vector.style["delete"])};if(style instanceof OpenLayers.Style){this.styles["default"]=style;this.styles["select"]=style;this.styles["temporary"]=style;this.styles["delete"]=style;}else if(typeof style=="object"){for(var key in style){if(style[key]instanceof OpenLayers.Style){this.styles[key]=style[key];}else if(typeof style[key]=="object"){this.styles[key]=new OpenLayers.Style(style[key]);}else{this.styles["default"]=new OpenLayers.Style(style);this.styles["select"]=new OpenLayers.Style(style);this.styles["temporary"]=new OpenLayers.Style(style);this.styles["delete"]=new OpenLayers.Style(style);break;}}}
+OpenLayers.Util.extend(this,options);},destroy:function(){for(var key in this.styles){this.styles[key].destroy();}
+this.styles=null;},createSymbolizer:function(feature,intent){if(!feature){feature=new OpenLayers.Feature.Vector();}
+if(!this.styles[intent]){intent="default";}
+feature.renderIntent=intent;var defaultSymbolizer={};if(this.extendDefault&&intent!="default"){defaultSymbolizer=this.styles["default"].createSymbolizer(feature);}
+return OpenLayers.Util.extend(defaultSymbolizer,this.styles[intent].createSymbolizer(feature));},addUniqueValueRules:function(renderIntent,property,symbolizers,context){var rules=[];for(var value in symbolizers){rules.push(new OpenLayers.Rule({symbolizer:symbolizers[value],context:context,filter:new OpenLayers.Filter.Comparison({type:OpenLayers.Filter.Comparison.EQUAL_TO,property:property,value:value})}));}
+this.styles[renderIntent].addRules(rules);},CLASS_NAME:"OpenLayers.StyleMap"});OpenLayers.Control.NavToolbar=OpenLayers.Class(OpenLayers.Control.Panel,{initialize:function(options){OpenLayers.Control.Panel.prototype.initialize.apply(this,[options]);this.addControls([new OpenLayers.Control.Navigation(),new OpenLayers.Control.ZoomBox()]);},draw:function(){var div=OpenLayers.Control.Panel.prototype.draw.apply(this,arguments);this.activateControl(this.controls[0]);return div;},CLASS_NAME:"OpenLayers.Control.NavToolbar"});OpenLayers.Filter.Comparison=OpenLayers.Class(OpenLayers.Filter,{type:null,property:null,value:null,matchCase:true,lowerBoundary:null,upperBoundary:null,initialize:function(options){OpenLayers.Filter.prototype.initialize.apply(this,[options]);},evaluate:function(context){if(context instanceof OpenLayers.Feature.Vector){context=context.attributes;}
+var result=false;var got=context[this.property];switch(this.type){case OpenLayers.Filter.Comparison.EQUAL_TO:var exp=this.value;if(!this.matchCase&&typeof got=="string"&&typeof exp=="string"){result=(got.toUpperCase()==exp.toUpperCase());}else{result=(got==exp);}
+break;case OpenLayers.Filter.Comparison.NOT_EQUAL_TO:var exp=this.value;if(!this.matchCase&&typeof got=="string"&&typeof exp=="string"){result=(got.toUpperCase()!=exp.toUpperCase());}else{result=(got!=exp);}
+break;case OpenLayers.Filter.Comparison.LESS_THAN:result=got<this.value;break;case OpenLayers.Filter.Comparison.GREATER_THAN:result=got>this.value;break;case OpenLayers.Filter.Comparison.LESS_THAN_OR_EQUAL_TO:result=got<=this.value;break;case OpenLayers.Filter.Comparison.GREATER_THAN_OR_EQUAL_TO:result=got>=this.value;break;case OpenLayers.Filter.Comparison.BETWEEN:result=(got>=this.lowerBoundary)&&(got<=this.upperBoundary);break;case OpenLayers.Filter.Comparison.LIKE:var regexp=new RegExp(this.value,"gi");result=regexp.test(got);break;}
+return result;},value2regex:function(wildCard,singleChar,escapeChar){if(wildCard=="."){var msg="'.' is an unsupported wildCard character for "+"OpenLayers.Filter.Comparison";OpenLayers.Console.error(msg);return null;}
+wildCard=wildCard?wildCard:"*";singleChar=singleChar?singleChar:".";escapeChar=escapeChar?escapeChar:"!";this.value=this.value.replace(new RegExp("\\"+escapeChar+"(.|$)","g"),"\\$1");this.value=this.value.replace(new RegExp("\\"+singleChar,"g"),".");this.value=this.value.replace(new RegExp("\\"+wildCard,"g"),".*");this.value=this.value.replace(new RegExp("\\\\.\\*","g"),"\\"+wildCard);this.value=this.value.replace(new RegExp("\\\\\\.","g"),"\\"+singleChar);return this.value;},regex2value:function(){var value=this.value;value=value.replace(/!/g,"!!");value=value.replace(/(\\)?\\\./g,function($0,$1){return $1?$0:"!.";});value=value.replace(/(\\)?\\\*/g,function($0,$1){return $1?$0:"!*";});value=value.replace(/\\\\/g,"\\");value=value.replace(/\.\*/g,"*");return value;},clone:function(){return OpenLayers.Util.extend(new OpenLayers.Filter.Comparison(),this);},CLASS_NAME:"OpenLayers.Filter.Comparison"});OpenLayers.Filter.Comparison.EQUAL_TO="==";OpenLayers.Filter.Comparison.NOT_EQUAL_TO="!=";OpenLayers.Filter.Comparison.LESS_THAN="<";OpenLayers.Filter.Comparison.GREATER_THAN=">";OpenLayers.Filter.Comparison.LESS_THAN_OR_EQUAL_TO="<=";OpenLayers.Filter.Comparison.GREATER_THAN_OR_EQUAL_TO=">=";OpenLayers.Filter.Comparison.BETWEEN="..";OpenLayers.Filter.Comparison.LIKE="~";OpenLayers.Filter.FeatureId=OpenLayers.Class(OpenLayers.Filter,{fids:null,initialize:function(options){this.fids=[];OpenLayers.Filter.prototype.initialize.apply(this,[options]);},evaluate:function(feature){for(var i=0,len=this.fids.length;i<len;i++){var fid=feature.fid||feature.id;if(fid==this.fids[i]){return true;}}
+return false;},clone:function(){var filter=new OpenLayers.Filter.FeatureId();OpenLayers.Util.extend(filter,this);filter.fids=this.fids.slice();return filter;},CLASS_NAME:"OpenLayers.Filter.FeatureId"});OpenLayers.Filter.Logical=OpenLayers.Class(OpenLayers.Filter,{filters:null,type:null,initialize:function(options){this.filters=[];OpenLayers.Filter.prototype.initialize.apply(this,[options]);},destroy:function(){this.filters=null;OpenLayers.Filter.prototype.destroy.apply(this);},evaluate:function(context){switch(this.type){case OpenLayers.Filter.Logical.AND:for(var i=0,len=this.filters.length;i<len;i++){if(this.filters[i].evaluate(context)==false){return false;}}
+return true;case OpenLayers.Filter.Logical.OR:for(var i=0,len=this.filters.length;i<len;i++){if(this.filters[i].evaluate(context)==true){return true;}}
+return false;case OpenLayers.Filter.Logical.NOT:return(!this.filters[0].evaluate(context));}},clone:function(){var filters=[];for(var i=0,len=this.filters.length;i<len;++i){filters.push(this.filters[i].clone());}
+return new OpenLayers.Filter.Logical({type:this.type,filters:filters});},CLASS_NAME:"OpenLayers.Filter.Logical"});OpenLayers.Filter.Logical.AND="&&";OpenLayers.Filter.Logical.OR="||";OpenLayers.Filter.Logical.NOT="!";OpenLayers.Filter.Spatial=OpenLayers.Class(OpenLayers.Filter,{type:null,property:null,value:null,distance:null,distanceUnits:null,initialize:function(options){OpenLayers.Filter.prototype.initialize.apply(this,[options]);},evaluate:function(feature){var intersect=false;switch(this.type){case OpenLayers.Filter.Spatial.BBOX:case OpenLayers.Filter.Spatial.INTERSECTS:if(feature.geometry){var geom=this.value;if(this.value.CLASS_NAME=="OpenLayers.Bounds"){geom=this.value.toGeometry();}
+if(feature.geometry.intersects(geom)){intersect=true;}}
+break;default:OpenLayers.Console.error(OpenLayers.i18n("filterEvaluateNotImplemented"));break;}
+return intersect;},clone:function(){var options=OpenLayers.Util.applyDefaults({value:this.value&&this.value.clone&&this.value.clone()},this);return new OpenLayers.Filter.Spatial(options);},CLASS_NAME:"OpenLayers.Filter.Spatial"});OpenLayers.Filter.Spatial.BBOX="BBOX";OpenLayers.Filter.Spatial.INTERSECTS="INTERSECTS";OpenLayers.Filter.Spatial.DWITHIN="DWITHIN";OpenLayers.Filter.Spatial.WITHIN="WITHIN";OpenLayers.Filter.Spatial.CONTAINS="CONTAINS";OpenLayers.Geometry.Collection=OpenLayers.Class(OpenLayers.Geometry,{components:null,componentTypes:null,initialize:function(components){OpenLayers.Geometry.prototype.initialize.apply(this,arguments);this.components=[];if(components!=null){this.addComponents(components);}},destroy:function(){this.components.length=0;this.components=null;OpenLayers.Geometry.prototype.destroy.apply(this,arguments);},clone:function(){var geometry=eval("new "+this.CLASS_NAME+"()");for(var i=0,len=this.components.length;i<len;i++){geometry.addComponent(this.components[i].clone());}
+OpenLayers.Util.applyDefaults(geometry,this);return geometry;},getComponentsString:function(){var strings=[];for(var i=0,len=this.components.length;i<len;i++){strings.push(this.components[i].toShortString());}
+return strings.join(",");},calculateBounds:function(){this.bounds=null;if(this.components&&this.components.length>0){this.setBounds(this.components[0].getBounds());for(var i=1,len=this.components.length;i<len;i++){this.extendBounds(this.components[i].getBounds());}}},addComponents:function(components){if(!(components instanceof Array)){components=[components];}
+for(var i=0,len=components.length;i<len;i++){this.addComponent(components[i]);}},addComponent:function(component,index){var added=false;if(component){if(this.componentTypes==null||(OpenLayers.Util.indexOf(this.componentTypes,component.CLASS_NAME)>-1)){if(index!=null&&(index<this.components.length)){var components1=this.components.slice(0,index);var components2=this.components.slice(index,this.components.length);components1.push(component);this.components=components1.concat(components2);}else{this.components.push(component);}
+component.parent=this;this.clearBounds();added=true;}}
+return added;},removeComponents:function(components){if(!(components instanceof Array)){components=[components];}
+for(var i=components.length-1;i>=0;--i){this.removeComponent(components[i]);}},removeComponent:function(component){OpenLayers.Util.removeItem(this.components,component);this.clearBounds();},getLength:function(){var length=0.0;for(var i=0,len=this.components.length;i<len;i++){length+=this.components[i].getLength();}
+return length;},getArea:function(){var area=0.0;for(var i=0,len=this.components.length;i<len;i++){area+=this.components[i].getArea();}
+return area;},getGeodesicArea:function(projection){var area=0.0;for(var i=0,len=this.components.length;i<len;i++){area+=this.components[i].getGeodesicArea(projection);}
+return area;},getCentroid:function(weighted){if(!weighted){return this.components.length&&this.components[0].getCentroid();}
+var len=this.components.length;if(!len){return false;}
+var areas=[];var centroids=[];var areaSum=0;var minArea=Number.MAX_VALUE;var component;for(var i=0;i<len;++i){component=this.components[i];var area=component.getArea();var centroid=component.getCentroid(true);if(isNaN(area)||isNaN(centroid.x)||isNaN(centroid.y)){continue;}
+areas.push(area);areaSum+=area;minArea=(area<minArea&&area>0)?area:minArea;centroids.push(centroid);}
+len=areas.length;if(areaSum===0){for(var i=0;i<len;++i){areas[i]=1;}
+areaSum=areas.length;}else{for(var i=0;i<len;++i){areas[i]/=minArea;}
+areaSum/=minArea;}
+var xSum=0,ySum=0,centroid,area;for(var i=0;i<len;++i){centroid=centroids[i];area=areas[i];xSum+=centroid.x*area;ySum+=centroid.y*area;}
+return new OpenLayers.Geometry.Point(xSum/areaSum,ySum/areaSum);},getGeodesicLength:function(projection){var length=0.0;for(var i=0,len=this.components.length;i<len;i++){length+=this.components[i].getGeodesicLength(projection);}
+return length;},move:function(x,y){for(var i=0,len=this.components.length;i<len;i++){this.components[i].move(x,y);}},rotate:function(angle,origin){for(var i=0,len=this.components.length;i<len;++i){this.components[i].rotate(angle,origin);}},resize:function(scale,origin,ratio){for(var i=0;i<this.components.length;++i){this.components[i].resize(scale,origin,ratio);}
+return this;},distanceTo:function(geometry,options){var edge=!(options&&options.edge===false);var details=edge&&options&&options.details;var result,best,distance;var min=Number.POSITIVE_INFINITY;for(var i=0,len=this.components.length;i<len;++i){result=this.components[i].distanceTo(geometry,options);distance=details?result.distance:result;if(distance<min){min=distance;best=result;if(min==0){break;}}}
+return best;},equals:function(geometry){var equivalent=true;if(!geometry||!geometry.CLASS_NAME||(this.CLASS_NAME!=geometry.CLASS_NAME)){equivalent=false;}else if(!(geometry.components instanceof Array)||(geometry.components.length!=this.components.length)){equivalent=false;}else{for(var i=0,len=this.components.length;i<len;++i){if(!this.components[i].equals(geometry.components[i])){equivalent=false;break;}}}
+return equivalent;},transform:function(source,dest){if(source&&dest){for(var i=0,len=this.components.length;i<len;i++){var component=this.components[i];component.transform(source,dest);}
+this.bounds=null;}
+return this;},intersects:function(geometry){var intersect=false;for(var i=0,len=this.components.length;i<len;++i){intersect=geometry.intersects(this.components[i]);if(intersect){break;}}
+return intersect;},getVertices:function(nodes){var vertices=[];for(var i=0,len=this.components.length;i<len;++i){Array.prototype.push.apply(vertices,this.components[i].getVertices(nodes));}
+return vertices;},CLASS_NAME:"OpenLayers.Geometry.Collection"});OpenLayers.Geometry.Point=OpenLayers.Class(OpenLayers.Geometry,{x:null,y:null,initialize:function(x,y){OpenLayers.Geometry.prototype.initialize.apply(this,arguments);this.x=parseFloat(x);this.y=parseFloat(y);},clone:function(obj){if(obj==null){obj=new OpenLayers.Geometry.Point(this.x,this.y);}
+OpenLayers.Util.applyDefaults(obj,this);return obj;},calculateBounds:function(){this.bounds=new OpenLayers.Bounds(this.x,this.y,this.x,this.y);},distanceTo:function(geometry,options){var edge=!(options&&options.edge===false);var details=edge&&options&&options.details;var distance,x0,y0,x1,y1,result;if(geometry instanceof OpenLayers.Geometry.Point){x0=this.x;y0=this.y;x1=geometry.x;y1=geometry.y;distance=Math.sqrt(Math.pow(x0-x1,2)+Math.pow(y0-y1,2));result=!details?distance:{x0:x0,y0:y0,x1:x1,y1:y1,distance:distance};}else{result=geometry.distanceTo(this,options);if(details){result={x0:result.x1,y0:result.y1,x1:result.x0,y1:result.y0,distance:result.distance};}}
+return result;},equals:function(geom){var equals=false;if(geom!=null){equals=((this.x==geom.x&&this.y==geom.y)||(isNaN(this.x)&&isNaN(this.y)&&isNaN(geom.x)&&isNaN(geom.y)));}
+return equals;},toShortString:function(){return(this.x+", "+this.y);},move:function(x,y){this.x=this.x+x;this.y=this.y+y;this.clearBounds();},rotate:function(angle,origin){angle*=Math.PI/180;var radius=this.distanceTo(origin);var theta=angle+Math.atan2(this.y-origin.y,this.x-origin.x);this.x=origin.x+(radius*Math.cos(theta));this.y=origin.y+(radius*Math.sin(theta));this.clearBounds();},getCentroid:function(){return new OpenLayers.Geometry.Point(this.x,this.y);},resize:function(scale,origin,ratio){ratio=(ratio==undefined)?1:ratio;this.x=origin.x+(scale*ratio*(this.x-origin.x));this.y=origin.y+(scale*(this.y-origin.y));this.clearBounds();return this;},intersects:function(geometry){var intersect=false;if(geometry.CLASS_NAME=="OpenLayers.Geometry.Point"){intersect=this.equals(geometry);}else{intersect=geometry.intersects(this);}
+return intersect;},transform:function(source,dest){if((source&&dest)){OpenLayers.Projection.transform(this,source,dest);this.bounds=null;}
+return this;},getVertices:function(nodes){return[this];},CLASS_NAME:"OpenLayers.Geometry.Point"});OpenLayers.Geometry.Rectangle=OpenLayers.Class(OpenLayers.Geometry,{x:null,y:null,width:null,height:null,initialize:function(x,y,width,height){OpenLayers.Geometry.prototype.initialize.apply(this,arguments);this.x=x;this.y=y;this.width=width;this.height=height;},calculateBounds:function(){this.bounds=new OpenLayers.Bounds(this.x,this.y,this.x+this.width,this.y+this.height);},getLength:function(){var length=(2*this.width)+(2*this.height);return length;},getArea:function(){var area=this.width*this.height;return area;},CLASS_NAME:"OpenLayers.Geometry.Rectangle"});OpenLayers.Geometry.Surface=OpenLayers.Class(OpenLayers.Geometry,{initialize:function(){OpenLayers.Geometry.prototype.initialize.apply(this,arguments);},CLASS_NAME:"OpenLayers.Geometry.Surface"});OpenLayers.Layer.KaMapCache=OpenLayers.Class(OpenLayers.Layer.KaMap,{IMAGE_EXTENSIONS:{'jpeg':'jpg','gif':'gif','png':'png','png8':'png','png24':'png','dithered':'png'},DEFAULT_FORMAT:'jpeg',initialize:function(name,url,params,options){OpenLayers.Layer.KaMap.prototype.initialize.apply(this,arguments);this.extension=this.IMAGE_EXTENSIONS[this.params.i.toLowerCase()||DEFAULT_FORMAT];},getURL:function(bounds){bounds=this.adjustBounds(bounds);var mapRes=this.map.getResolution();var scale=Math.round((this.map.getScale()*10000))/10000;var pX=Math.round(bounds.left/mapRes);var pY=-Math.round(bounds.top/mapRes);var metaX=Math.floor(pX/this.tileSize.w/this.params.metaTileSize.w)*this.tileSize.w*this.params.metaTileSize.w;var metaY=Math.floor(pY/this.tileSize.h/this.params.metaTileSize.h)*this.tileSize.h*this.params.metaTileSize.h;var url=this.url;if(url instanceof Array){url=this.selectUrl(paramsString,url);}
+var components=[url,"/",this.params.map,"/",scale,"/",this.params.g.replace(/\s/g,'_'),"/def/t",metaY,"/l",metaX,"/t",pY,"l",pX,".",this.extension];return components.join("");},CLASS_NAME:"OpenLayers.Layer.KaMapCache"});OpenLayers.Layer.MapServer.Untiled=OpenLayers.Class(OpenLayers.Layer.MapServer,{singleTile:true,initialize:function(name,url,params,options){OpenLayers.Layer.MapServer.prototype.initialize.apply(this,arguments);var msg="The OpenLayers.Layer.MapServer.Untiled class is deprecated and "+"will be removed in 3.0. Instead, you should use the "+"normal OpenLayers.Layer.MapServer class, passing it the option "+"'singleTile' as true.";OpenLayers.Console.warn(msg);},clone:function(obj){if(obj==null){obj=new OpenLayers.Layer.MapServer.Untiled(this.name,this.url,this.params,this.getOptions());}
+obj=OpenLayers.Layer.MapServer.prototype.clone.apply(this,[obj]);return obj;},CLASS_NAME:"OpenLayers.Layer.MapServer.Untiled"});OpenLayers.Layer.Vector=OpenLayers.Class(OpenLayers.Layer,{EVENT_TYPES:["beforefeatureadded","beforefeaturesadded","featureadded","featuresadded","beforefeatureremoved","beforefeaturesremoved","featureremoved","featuresremoved","beforefeatureselected","featureselected","featureunselected","beforefeaturemodified","featuremodified","afterfeaturemodified","vertexmodified","sketchstarted","sketchmodified","sketchcomplete","refresh"],isBaseLayer:false,isFixed:false,isVector:true,features:null,filter:null,selectedFeatures:null,unrenderedFeatures:null,reportError:true,style:null,styleMap:null,strategies:null,protocol:null,renderers:['SVG','VML','Canvas'],renderer:null,rendererOptions:null,geometryType:null,drawn:false,initialize:function(name,options){this.EVENT_TYPES=OpenLayers.Layer.Vector.prototype.EVENT_TYPES.concat(OpenLayers.Layer.prototype.EVENT_TYPES);OpenLayers.Layer.prototype.initialize.apply(this,arguments);if(!this.renderer||!this.renderer.supported()){this.assignRenderer();}
+if(!this.renderer||!this.renderer.supported()){this.renderer=null;this.displayError();}
+if(!this.styleMap){this.styleMap=new OpenLayers.StyleMap();}
+this.features=[];this.selectedFeatures=[];this.unrenderedFeatures={};if(this.strategies){for(var i=0,len=this.strategies.length;i<len;i++){this.strategies[i].setLayer(this);}}},destroy:function(){if(this.strategies){var strategy,i,len;for(i=0,len=this.strategies.length;i<len;i++){strategy=this.strategies[i];if(strategy.autoDestroy){strategy.destroy();}}
+this.strategies=null;}
+if(this.protocol){if(this.protocol.autoDestroy){this.protocol.destroy();}
+this.protocol=null;}
+this.destroyFeatures();this.features=null;this.selectedFeatures=null;this.unrenderedFeatures=null;if(this.renderer){this.renderer.destroy();}
+this.renderer=null;this.geometryType=null;this.drawn=null;OpenLayers.Layer.prototype.destroy.apply(this,arguments);},clone:function(obj){if(obj==null){obj=new OpenLayers.Layer.Vector(this.name,this.getOptions());}
+obj=OpenLayers.Layer.prototype.clone.apply(this,[obj]);var features=this.features;var len=features.length;var clonedFeatures=new Array(len);for(var i=0;i<len;++i){clonedFeatures[i]=features[i].clone();}
+obj.features=clonedFeatures;return obj;},refresh:function(obj){if(this.calculateInRange()&&this.visibility){this.events.triggerEvent("refresh",obj);}},assignRenderer:function(){for(var i=0,len=this.renderers.length;i<len;i++){var rendererClass=this.renderers[i];var renderer=(typeof rendererClass=="function")?rendererClass:OpenLayers.Renderer[rendererClass];if(renderer&&renderer.prototype.supported()){this.renderer=new renderer(this.div,this.rendererOptions);break;}}},displayError:function(){if(this.reportError){OpenLayers.Console.userError(OpenLayers.i18n("browserNotSupported",{'renderers':this.renderers.join("\n")}));}},setMap:function(map){OpenLayers.Layer.prototype.setMap.apply(this,arguments);if(!this.renderer){this.map.removeLayer(this);}else{this.renderer.map=this.map;this.renderer.setSize(this.map.getSize());}},afterAdd:function(){if(this.strategies){var strategy,i,len;for(i=0,len=this.strategies.length;i<len;i++){strategy=this.strategies[i];if(strategy.autoActivate){strategy.activate();}}}},removeMap:function(map){this.drawn=false;if(this.strategies){var strategy,i,len;for(i=0,len=this.strategies.length;i<len;i++){strategy=this.strategies[i];if(strategy.autoActivate){strategy.deactivate();}}}},onMapResize:function(){OpenLayers.Layer.prototype.onMapResize.apply(this,arguments);this.renderer.setSize(this.map.getSize());},moveTo:function(bounds,zoomChanged,dragging){OpenLayers.Layer.prototype.moveTo.apply(this,arguments);var coordSysUnchanged=true;if(!dragging){this.renderer.root.style.visibility="hidden";this.div.style.left=-parseInt(this.map.layerContainerDiv.style.left)+"px";this.div.style.top=-parseInt(this.map.layerContainerDiv.style.top)+"px";var extent=this.map.getExtent();coordSysUnchanged=this.renderer.setExtent(extent,zoomChanged);this.renderer.root.style.visibility="visible";if(navigator.userAgent.toLowerCase().indexOf("gecko")!=-1){this.div.scrollLeft=this.div.scrollLeft;}
+if(!zoomChanged&&coordSysUnchanged){for(var i in this.unrenderedFeatures){var feature=this.unrenderedFeatures[i];this.drawFeature(feature);}}}
+if(!this.drawn||zoomChanged||!coordSysUnchanged){this.drawn=true;var feature;for(var i=0,len=this.features.length;i<len;i++){this.renderer.locked=(i!==(len-1));feature=this.features[i];this.drawFeature(feature);}}},display:function(display){OpenLayers.Layer.prototype.display.apply(this,arguments);var currentDisplay=this.div.style.display;if(currentDisplay!=this.renderer.root.style.display){this.renderer.root.style.display=currentDisplay;}},addFeatures:function(features,options){if(!(features instanceof Array)){features=[features];}
+var notify=!options||!options.silent;if(notify){var event={features:features};var ret=this.events.triggerEvent("beforefeaturesadded",event);if(ret===false){return;}
+features=event.features;}
+var featuresAdded=[];for(var i=0,len=features.length;i<len;i++){if(i!=(features.length-1)){this.renderer.locked=true;}else{this.renderer.locked=false;}
+var feature=features[i];if(this.geometryType&&!(feature.geometry instanceof this.geometryType)){var throwStr=OpenLayers.i18n('componentShouldBe',{'geomType':this.geometryType.prototype.CLASS_NAME});throw throwStr;}
+feature.layer=this;if(!feature.style&&this.style){feature.style=OpenLayers.Util.extend({},this.style);}
+if(notify){if(this.events.triggerEvent("beforefeatureadded",{feature:feature})===false){continue;};this.preFeatureInsert(feature);}
+featuresAdded.push(feature);this.features.push(feature);this.drawFeature(feature);if(notify){this.events.triggerEvent("featureadded",{feature:feature});this.onFeatureInsert(feature);}}
+if(notify){this.events.triggerEvent("featuresadded",{features:featuresAdded});}},removeFeatures:function(features,options){if(!features||features.length===0){return;}
+if(features===this.features){return this.removeAllFeatures(options);}
+if(!(features instanceof Array)){features=[features];}
+if(features===this.selectedFeatures){features=features.slice();}
+var notify=!options||!options.silent;if(notify){this.events.triggerEvent("beforefeaturesremoved",{features:features});}
+for(var i=features.length-1;i>=0;i--){if(i!=0&&features[i-1].geometry){this.renderer.locked=true;}else{this.renderer.locked=false;}
+var feature=features[i];delete this.unrenderedFeatures[feature.id];if(notify){this.events.triggerEvent("beforefeatureremoved",{feature:feature});}
+this.features=OpenLayers.Util.removeItem(this.features,feature);feature.layer=null;if(feature.geometry){this.renderer.eraseFeatures(feature);}
+if(OpenLayers.Util.indexOf(this.selectedFeatures,feature)!=-1){OpenLayers.Util.removeItem(this.selectedFeatures,feature);}
+if(notify){this.events.triggerEvent("featureremoved",{feature:feature});}}
+if(notify){this.events.triggerEvent("featuresremoved",{features:features});}},removeAllFeatures:function(options){var notify=!options||!options.silent;var features=this.features;if(notify){this.events.triggerEvent("beforefeaturesremoved",{features:features});}
+var feature;for(var i=features.length-1;i>=0;i--){feature=features[i];if(notify){this.events.triggerEvent("beforefeatureremoved",{feature:feature});}
+feature.layer=null;if(notify){this.events.triggerEvent("featureremoved",{feature:feature});}}
+this.renderer.clear();this.features=[];this.unrenderedFeatures={};this.selectedFeatures=[];if(notify){this.events.triggerEvent("featuresremoved",{features:features});}},destroyFeatures:function(features,options){var all=(features==undefined);if(all){features=this.features;}
+if(features){this.removeFeatures(features,options);for(var i=features.length-1;i>=0;i--){features[i].destroy();}}},drawFeature:function(feature,style){if(!this.drawn){return}
+if(typeof style!="object"){if(!style&&feature.state===OpenLayers.State.DELETE){style="delete";}
+var renderIntent=style||feature.renderIntent;style=feature.style||this.style;if(!style){style=this.styleMap.createSymbolizer(feature,renderIntent);}}
+if(!this.renderer.drawFeature(feature,style)){this.unrenderedFeatures[feature.id]=feature;}else{delete this.unrenderedFeatures[feature.id];};},eraseFeatures:function(features){this.renderer.eraseFeatures(features);},getFeatureFromEvent:function(evt){if(!this.renderer){OpenLayers.Console.error(OpenLayers.i18n("getFeatureError"));return null;}
+var featureId=this.renderer.getFeatureIdFromEvent(evt);return this.getFeatureById(featureId);},getFeatureBy:function(property,value){var feature=null;for(var i=0,len=this.features.length;i<len;++i){if(this.features[i][property]==value){feature=this.features[i];break;}}
+return feature;},getFeatureById:function(featureId){return this.getFeatureBy('id',featureId);},getFeatureByFid:function(featureFid){return this.getFeatureBy('fid',featureFid);},onFeatureInsert:function(feature){},preFeatureInsert:function(feature){},getDataExtent:function(){var maxExtent=null;var features=this.features;if(features&&(features.length>0)){maxExtent=new OpenLayers.Bounds();var geometry=null;for(var i=0,len=features.length;i<len;i++){geometry=features[i].geometry;if(geometry){maxExtent.extend(geometry.getBounds());}}}
+return maxExtent;},CLASS_NAME:"OpenLayers.Layer.Vector"});OpenLayers.Layer.WMS.Post=OpenLayers.Class(OpenLayers.Layer.WMS,{tileClass:null,unsupportedBrowsers:["mozilla","firefox","opera"],SUPPORTED_TRANSITIONS:[],initialize:function(name,url,params,options){var newArguments=[];newArguments.push(name,url,params,options);OpenLayers.Layer.WMS.prototype.initialize.apply(this,newArguments);this.tileClass=OpenLayers.Util.indexOf(this.unsupportedBrowsers,OpenLayers.Util.getBrowserName())!=-1?OpenLayers.Tile.Image:OpenLayers.Tile.Image.IFrame;},addTile:function(bounds,position){return new this.tileClass(this,position,bounds,null,this.tileSize);},CLASS_NAME:'OpenLayers.Layer.WMS.Post'});OpenLayers.Layer.WMS.Untiled=OpenLayers.Class(OpenLayers.Layer.WMS,{singleTile:true,initialize:function(name,url,params,options){OpenLayers.Layer.WMS.prototype.initialize.apply(this,arguments);var msg="The OpenLayers.Layer.WMS.Untiled class is deprecated and "+"will be removed in 3.0. Instead, you should use the "+"normal OpenLayers.Layer.WMS class, passing it the option "+"'singleTile' as true.";OpenLayers.Console.warn(msg);},clone:function(obj){if(obj==null){obj=new OpenLayers.Layer.WMS.Untiled(this.name,this.url,this.params,this.getOptions());}
+obj=OpenLayers.Layer.WMS.prototype.clone.apply(this,[obj]);return obj;},CLASS_NAME:"OpenLayers.Layer.WMS.Untiled"});OpenLayers.Strategy.Filter=OpenLayers.Class(OpenLayers.Strategy,{filter:null,cache:null,caching:false,initialize:function(options){OpenLayers.Strategy.prototype.initialize.apply(this,[options]);if(!this.filter||!(this.filter instanceof OpenLayers.Filter)){throw new Error("Filter strategy must be constructed with a filter");}},activate:function(){var activated=OpenLayers.Strategy.prototype.activate.apply(this,arguments);if(activated){this.cache=[];this.layer.events.on({"beforefeaturesadded":this.handleAdd,"beforefeaturesremoved":this.handleRemove,scope:this});}
+return activated;},deactivate:function(){this.cache=null;if(this.layer&&this.layer.events){this.layer.events.un({"beforefeaturesadded":this.handleAdd,"beforefeaturesremoved":this.handleRemove,scope:this});}
+return OpenLayers.Strategy.prototype.deactivate.apply(this,arguments);},handleAdd:function(event){if(!this.caching){var features=event.features;event.features=[];var feature;for(var i=0,ii=features.length;i<ii;++i){feature=features[i];if(this.filter.evaluate(feature)){event.features.push(feature);}else{this.cache.push(feature);}}}},handleRemove:function(event){if(!this.caching){this.cache=[];}},setFilter:function(filter){this.filter=filter;var previousCache=this.cache;this.cache=[];this.handleAdd({features:this.layer.features});if(this.cache.length>0){this.caching=true;this.layer.removeFeatures(this.cache.slice(),{silent:true});this.caching=false;}
+if(previousCache.length>0){var event={features:previousCache};this.handleAdd(event);this.caching=true;this.layer.addFeatures(event.features,{silent:true});this.caching=false;}},CLASS_NAME:"OpenLayers.Strategy.Filter"});OpenLayers.Style2=OpenLayers.Class({id:null,name:null,title:null,description:null,layerName:null,isDefault:false,rules:null,initialize:function(config){OpenLayers.Util.extend(this,config);this.id=OpenLayers.Util.createUniqueID(this.CLASS_NAME+"_");},destroy:function(){for(var i=0,len=this.rules.length;i<len;i++){this.rules[i].destroy();}
+delete this.rules;},clone:function(){var config=OpenLayers.Util.extend({},this);if(this.rules){config.rules=[];for(var i=0,len=this.rules.length;i<len;++i){config.rules.push(this.rules[i].clone());}}
+return new OpenLayers.Style2(config);},CLASS_NAME:"OpenLayers.Style2"});OpenLayers.Control.GetFeature=OpenLayers.Class(OpenLayers.Control,{protocol:null,multipleKey:null,toggleKey:null,modifiers:null,multiple:false,click:true,single:true,clickout:true,toggle:false,clickTolerance:5,hover:false,box:false,maxFeatures:10,features:null,hoverFeature:null,handlerOptions:null,handlers:null,hoverResponse:null,filterType:OpenLayers.Filter.Spatial.BBOX,EVENT_TYPES:["featureselected","featuresselected","featureunselected","clickout","beforefeatureselected","beforefeaturesselected","hoverfeature","outfeature"],initialize:function(options){this.EVENT_TYPES=OpenLayers.Control.GetFeature.prototype.EVENT_TYPES.concat(OpenLayers.Control.prototype.EVENT_TYPES);options.handlerOptions=options.handlerOptions||{};OpenLayers.Control.prototype.initialize.apply(this,[options]);this.features={};this.handlers={};if(this.click){this.handlers.click=new OpenLayers.Handler.Click(this,{click:this.selectClick},this.handlerOptions.click||{});}
+if(this.box){this.handlers.box=new OpenLayers.Handler.Box(this,{done:this.selectBox},OpenLayers.Util.extend(this.handlerOptions.box,{boxDivClassName:"olHandlerBoxSelectFeature"}));}
+if(this.hover){this.handlers.hover=new OpenLayers.Handler.Hover(this,{'move':this.cancelHover,'pause':this.selectHover},OpenLayers.Util.extend(this.handlerOptions.hover,{'delay':250}));}},activate:function(){if(!this.active){for(var i in this.handlers){this.handlers[i].activate();}}
+return OpenLayers.Control.prototype.activate.apply(this,arguments);},deactivate:function(){if(this.active){for(var i in this.handlers){this.handlers[i].deactivate();}}
+return OpenLayers.Control.prototype.deactivate.apply(this,arguments);},selectClick:function(evt){var bounds=this.pixelToBounds(evt.xy);this.setModifiers(evt);this.request(bounds,{single:this.single});},selectBox:function(position){var bounds;if(position instanceof OpenLayers.Bounds){var minXY=this.map.getLonLatFromPixel(new OpenLayers.Pixel(position.left,position.bottom));var maxXY=this.map.getLonLatFromPixel(new OpenLayers.Pixel(position.right,position.top));bounds=new OpenLayers.Bounds(minXY.lon,minXY.lat,maxXY.lon,maxXY.lat);}else{if(this.click){return;}
+bounds=this.pixelToBounds(position);}
+this.setModifiers(this.handlers.box.dragHandler.evt);this.request(bounds);},selectHover:function(evt){var bounds=this.pixelToBounds(evt.xy);this.request(bounds,{single:true,hover:true});},cancelHover:function(){if(this.hoverResponse){this.protocol.abort(this.hoverResponse);this.hoverResponse=null;OpenLayers.Element.removeClass(this.map.viewPortDiv,"olCursorWait");}},request:function(bounds,options){options=options||{};var filter=new OpenLayers.Filter.Spatial({type:this.filterType,value:bounds});OpenLayers.Element.addClass(this.map.viewPortDiv,"olCursorWait");var response=this.protocol.read({maxFeatures:options.single==true?this.maxFeatures:undefined,filter:filter,callback:function(result){if(result.success()){if(result.features.length){if(options.single==true){this.selectBestFeature(result.features,bounds.getCenterLonLat(),options);}else{this.select(result.features);}}else if(options.hover){this.hoverSelect();}else{this.events.triggerEvent("clickout");if(this.clickout){this.unselectAll();}}}
+OpenLayers.Element.removeClass(this.map.viewPortDiv,"olCursorWait");},scope:this});if(options.hover==true){this.hoverResponse=response;}},selectBestFeature:function(features,clickPosition,options){options=options||{};if(features.length){var point=new OpenLayers.Geometry.Point(clickPosition.lon,clickPosition.lat);var feature,resultFeature,dist;var minDist=Number.MAX_VALUE;for(var i=0;i<features.length;++i){feature=features[i];if(feature.geometry){dist=point.distanceTo(feature.geometry,{edge:false});if(dist<minDist){minDist=dist;resultFeature=feature;if(minDist==0){break;}}}}
+if(options.hover==true){this.hoverSelect(resultFeature);}else{this.select(resultFeature||features);}}},setModifiers:function(evt){this.modifiers={multiple:this.multiple||(this.multipleKey&&evt[this.multipleKey]),toggle:this.toggle||(this.toggleKey&&evt[this.toggleKey])};},select:function(features){if(!this.modifiers.multiple&&!this.modifiers.toggle){this.unselectAll();}
+if(!(features instanceof Array)){features=[features];}
+var cont=this.events.triggerEvent("beforefeaturesselected",{features:features});if(cont!==false){var selectedFeatures=[];var feature;for(var i=0,len=features.length;i<len;++i){feature=features[i];if(this.features[feature.fid||feature.id]){if(this.modifiers.toggle){this.unselect(this.features[feature.fid||feature.id]);}}else{cont=this.events.triggerEvent("beforefeatureselected",{feature:feature});if(cont!==false){this.features[feature.fid||feature.id]=feature;selectedFeatures.push(feature);this.events.triggerEvent("featureselected",{feature:feature});}}}
+this.events.triggerEvent("featuresselected",{features:selectedFeatures});}},hoverSelect:function(feature){var fid=feature?feature.fid||feature.id:null;var hfid=this.hoverFeature?this.hoverFeature.fid||this.hoverFeature.id:null;if(hfid&&hfid!=fid){this.events.triggerEvent("outfeature",{feature:this.hoverFeature});this.hoverFeature=null;}
+if(fid&&fid!=hfid){this.events.triggerEvent("hoverfeature",{feature:feature});this.hoverFeature=feature;}},unselect:function(feature){delete this.features[feature.fid||feature.id];this.events.triggerEvent("featureunselected",{feature:feature});},unselectAll:function(){for(var fid in this.features){this.unselect(this.features[fid]);}},setMap:function(map){for(var i in this.handlers){this.handlers[i].setMap(map);}
+OpenLayers.Control.prototype.setMap.apply(this,arguments);},pixelToBounds:function(pixel){var llPx=pixel.add(-this.clickTolerance/2,this.clickTolerance/2);var urPx=pixel.add(this.clickTolerance/2,-this.clickTolerance/2);var ll=this.map.getLonLatFromPixel(llPx);var ur=this.map.getLonLatFromPixel(urPx);return new OpenLayers.Bounds(ll.lon,ll.lat,ur.lon,ur.lat);},CLASS_NAME:"OpenLayers.Control.GetFeature"});OpenLayers.Control.Snapping=OpenLayers.Class(OpenLayers.Control,{EVENT_TYPES:["beforesnap","snap","unsnap"],DEFAULTS:{tolerance:10,node:true,edge:true,vertex:true},greedy:true,precedence:["node","vertex","edge"],resolution:null,geoToleranceCache:null,layer:null,feature:null,point:null,initialize:function(options){Array.prototype.push.apply(this.EVENT_TYPES,OpenLayers.Control.prototype.EVENT_TYPES);OpenLayers.Control.prototype.initialize.apply(this,[options]);this.options=options||{};if(this.options.layer){this.setLayer(this.options.layer);}
+var defaults=OpenLayers.Util.extend({},this.options.defaults);this.defaults=OpenLayers.Util.applyDefaults(defaults,this.DEFAULTS);this.setTargets(this.options.targets);if(this.targets.length===0&&this.layer){this.addTargetLayer(this.layer);}
+this.geoToleranceCache={};},setLayer:function(layer){if(this.active){this.deactivate();this.layer=layer;this.activate();}else{this.layer=layer;}},setTargets:function(targets){this.targets=[];if(targets&&targets.length){var target;for(var i=0,len=targets.length;i<len;++i){target=targets[i];if(target instanceof OpenLayers.Layer.Vector){this.addTargetLayer(target);}else{this.addTarget(target);}}}},addTargetLayer:function(layer){this.addTarget({layer:layer});},addTarget:function(target){target=OpenLayers.Util.applyDefaults(target,this.defaults);target.nodeTolerance=target.nodeTolerance||target.tolerance;target.vertexTolerance=target.vertexTolerance||target.tolerance;target.edgeTolerance=target.edgeTolerance||target.tolerance;this.targets.push(target);},removeTargetLayer:function(layer){var target;for(var i=this.targets.length-1;i>=0;--i){target=this.targets[i];if(target.layer===layer){this.removeTarget(target);}}},removeTarget:function(target){return OpenLayers.Util.removeItem(this.targets,target);},activate:function(){var activated=OpenLayers.Control.prototype.activate.call(this);if(activated){if(this.layer&&this.layer.events){this.layer.events.on({sketchstarted:this.onSketchModified,sketchmodified:this.onSketchModified,vertexmodified:this.onVertexModified,scope:this});}}
+return activated;},deactivate:function(){var deactivated=OpenLayers.Control.prototype.deactivate.call(this);if(deactivated){if(this.layer&&this.layer.events){this.layer.events.un({sketchstarted:this.onSketchModified,sketchmodified:this.onSketchModified,vertexmodified:this.onVertexModified,scope:this});}}
+this.feature=null;this.point=null;return deactivated;},onSketchModified:function(event){this.feature=event.feature;this.considerSnapping(event.vertex,event.vertex);},onVertexModified:function(event){this.feature=event.feature;var loc=this.layer.map.getLonLatFromViewPortPx(event.pixel);this.considerSnapping(event.vertex,new OpenLayers.Geometry.Point(loc.lon,loc.lat));},considerSnapping:function(point,loc){var best={rank:Number.POSITIVE_INFINITY,dist:Number.POSITIVE_INFINITY,x:null,y:null};var snapped=false;var result,target;for(var i=0,len=this.targets.length;i<len;++i){target=this.targets[i];result=this.testTarget(target,loc);if(result){if(this.greedy){best=result;best.target=target;snapped=true;break;}else{if((result.rank<best.rank)||(result.rank===best.rank&&result.dist<best.dist)){best=result;best.target=target;snapped=true;}}}}
+if(snapped){var proceed=this.events.triggerEvent("beforesnap",{point:point,x:best.x,y:best.y,distance:best.dist,layer:best.target.layer,snapType:this.precedence[best.rank]});if(proceed!==false){point.x=best.x;point.y=best.y;this.point=point;this.events.triggerEvent("snap",{point:point,snapType:this.precedence[best.rank],layer:best.target.layer,distance:best.dist});}else{snapped=false;}}
+if(this.point&&!snapped){point.x=loc.x;point.y=loc.y;this.point=null;this.events.triggerEvent("unsnap",{point:point});}},testTarget:function(target,loc){var tolerance={node:this.getGeoTolerance(target.nodeTolerance),vertex:this.getGeoTolerance(target.vertexTolerance),edge:this.getGeoTolerance(target.edgeTolerance)};var maxTolerance=Math.max(tolerance.node,tolerance.vertex,tolerance.edge);var result={rank:Number.POSITIVE_INFINITY,dist:Number.POSITIVE_INFINITY};var eligible=false;var features=target.layer.features;var feature,type,vertices,vertex,closest,dist,found;var numTypes=this.precedence.length;var ll=new OpenLayers.LonLat(loc.x,loc.y);for(var i=0,len=features.length;i<len;++i){feature=features[i];if(feature!==this.feature&&!feature._sketch&&feature.state!==OpenLayers.State.DELETE&&(!target.filter||target.filter.evaluate(feature.attributes))){if(feature.atPoint(ll,maxTolerance,maxTolerance)){for(var j=0,stop=Math.min(result.rank+1,numTypes);j<stop;++j){type=this.precedence[j];if(target[type]){if(type==="edge"){closest=feature.geometry.distanceTo(loc,{details:true});dist=closest.distance;if(dist<=tolerance[type]&&dist<result.dist){result={rank:j,dist:dist,x:closest.x0,y:closest.y0};eligible=true;break;}}else{vertices=feature.geometry.getVertices(type==="node");found=false;for(var k=0,klen=vertices.length;k<klen;++k){vertex=vertices[k];dist=vertex.distanceTo(loc);if(dist<=tolerance[type]&&(j<result.rank||(j===result.rank&&dist<result.dist))){result={rank:j,dist:dist,x:vertex.x,y:vertex.y};eligible=true;found=true;}}
+if(found){break;}}}}}}}
+return eligible?result:null;},getGeoTolerance:function(tolerance){var resolution=this.layer.map.getResolution();if(resolution!==this.resolution){this.resolution=resolution;this.geoToleranceCache={};}
+var geoTolerance=this.geoToleranceCache[tolerance];if(geoTolerance===undefined){geoTolerance=tolerance*resolution;this.geoToleranceCache[tolerance]=geoTolerance;}
+return geoTolerance;},destroy:function(){if(this.active){this.deactivate();}
+delete this.layer;delete this.targets;OpenLayers.Control.prototype.destroy.call(this);},CLASS_NAME:"OpenLayers.Control.Snapping"});OpenLayers.Format.Filter=OpenLayers.Class(OpenLayers.Format.XML,{defaultVersion:"1.0.0",version:null,parser:null,initialize:function(options){OpenLayers.Format.XML.prototype.initialize.apply(this,[options]);},write:function(filter,options){var version=(options&&options.version)||this.version||this.defaultVersion;if(!this.parser||this.parser.VERSION!=version){var format=OpenLayers.Format.Filter["v"+version.replace(/\./g,"_")];if(!format){throw"Can't find a Filter parser for version "+
+version;}
+this.parser=new format(this.options);}
+return this.parser.write(filter);},read:function(data){if(typeof data=="string"){data=OpenLayers.Format.XML.prototype.read.apply(this,[data]);}
+var version=this.version;if(!version){version=this.defaultVersion;}
+if(!this.parser||this.parser.VERSION!=version){var format=OpenLayers.Format.Filter["v"+version.replace(/\./g,"_")];if(!format){throw"Can't find a Filter parser for version "+
+version;}
+this.parser=new format(this.options);}
+var filter=this.parser.read(data);return filter;},CLASS_NAME:"OpenLayers.Format.Filter"});OpenLayers.Format.SLD=OpenLayers.Class(OpenLayers.Format.XML,{defaultVersion:"1.0.0",version:null,namedLayersAsArray:false,parser:null,initialize:function(options){OpenLayers.Format.XML.prototype.initialize.apply(this,[options]);},write:function(sld,options){var version=(options&&options.version)||this.version||this.defaultVersion;if(!this.parser||this.parser.VERSION!=version){var format=OpenLayers.Format.SLD["v"+version.replace(/\./g,"_")];if(!format){throw"Can't find a SLD parser for version "+
+version;}
+this.parser=new format(this.options);}
+var root=this.parser.write(sld);return OpenLayers.Format.XML.prototype.write.apply(this,[root]);},read:function(data,options){if(typeof data=="string"){data=OpenLayers.Format.XML.prototype.read.apply(this,[data]);}
+var root=data.documentElement;var version=this.version;if(!version){version=root.getAttribute("version");if(!version){version=this.defaultVersion;}}
+if(!this.parser||this.parser.VERSION!=version){var format=OpenLayers.Format.SLD["v"+version.replace(/\./g,"_")];if(!format){throw"Can't find a SLD parser for version "+
+version;}
+this.parser=new format(this.options);}
+var sld=this.parser.read(data,options);return sld;},CLASS_NAME:"OpenLayers.Format.SLD"});OpenLayers.Format.Text=OpenLayers.Class(OpenLayers.Format,{defaultStyle:null,extractStyles:true,initialize:function(options){options=options||{};if(options.extractStyles!==false){options.defaultStyle={'externalGraphic':OpenLayers.Util.getImagesLocation()+"marker.png",'graphicWidth':21,'graphicHeight':25,'graphicXOffset':-10.5,'graphicYOffset':-12.5};}
+OpenLayers.Format.prototype.initialize.apply(this,[options]);},read:function(text){var lines=text.split('\n');var columns;var features=[];for(var lcv=0;lcv<(lines.length-1);lcv++){var currLine=lines[lcv].replace(/^\s*/,'').replace(/\s*$/,'');if(currLine.charAt(0)!='#'){if(!columns){columns=currLine.split('\t');}else{var vals=currLine.split('\t');var geometry=new OpenLayers.Geometry.Point(0,0);var attributes={};var style=this.defaultStyle?OpenLayers.Util.applyDefaults({},this.defaultStyle):null;var icon,iconSize,iconOffset,overflow;var set=false;for(var valIndex=0;valIndex<vals.length;valIndex++){if(vals[valIndex]){if(columns[valIndex]=='point'){var coords=vals[valIndex].split(',');geometry.y=parseFloat(coords[0]);geometry.x=parseFloat(coords[1]);set=true;}else if(columns[valIndex]=='lat'){geometry.y=parseFloat(vals[valIndex]);set=true;}else if(columns[valIndex]=='lon'){geometry.x=parseFloat(vals[valIndex]);set=true;}else if(columns[valIndex]=='title')
+attributes['title']=vals[valIndex];else if(columns[valIndex]=='image'||columns[valIndex]=='icon'&&style){style['externalGraphic']=vals[valIndex];}else if(columns[valIndex]=='iconSize'&&style){var size=vals[valIndex].split(',');style['graphicWidth']=parseFloat(size[0]);style['graphicHeight']=parseFloat(size[1]);}else if(columns[valIndex]=='iconOffset'&&style){var offset=vals[valIndex].split(',');style['graphicXOffset']=parseFloat(offset[0]);style['graphicYOffset']=parseFloat(offset[1]);}else if(columns[valIndex]=='description'){attributes['description']=vals[valIndex];}else if(columns[valIndex]=='overflow'){attributes['overflow']=vals[valIndex];}else{attributes[columns[valIndex]]=vals[valIndex];}}}
+if(set){if(this.internalProjection&&this.externalProjection){geometry.transform(this.externalProjection,this.internalProjection);}
+var feature=new OpenLayers.Feature.Vector(geometry,attributes,style);features.push(feature);}}}}
+return features;},CLASS_NAME:"OpenLayers.Format.Text"});OpenLayers.Geometry.MultiPoint=OpenLayers.Class(OpenLayers.Geometry.Collection,{componentTypes:["OpenLayers.Geometry.Point"],initialize:function(components){OpenLayers.Geometry.Collection.prototype.initialize.apply(this,arguments);},addPoint:function(point,index){this.addComponent(point,index);},removePoint:function(point){this.removeComponent(point);},CLASS_NAME:"OpenLayers.Geometry.MultiPoint"});OpenLayers.Handler.Point=OpenLayers.Class(OpenLayers.Handler,{point:null,layer:null,multi:false,drawing:false,mouseDown:false,lastDown:null,lastUp:null,persist:false,layerOptions:null,initialize:function(control,callbacks,options){if(!(options&&options.layerOptions&&options.layerOptions.styleMap)){this.style=OpenLayers.Util.extend(OpenLayers.Feature.Vector.style['default'],{});}
+OpenLayers.Handler.prototype.initialize.apply(this,arguments);},activate:function(){if(!OpenLayers.Handler.prototype.activate.apply(this,arguments)){return false;}
+var options=OpenLayers.Util.extend({displayInLayerSwitcher:false,calculateInRange:OpenLayers.Function.True},this.layerOptions);this.layer=new OpenLayers.Layer.Vector(this.CLASS_NAME,options);this.map.addLayer(this.layer);return true;},createFeature:function(pixel){var lonlat=this.map.getLonLatFromPixel(pixel);this.point=new OpenLayers.Feature.Vector(new OpenLayers.Geometry.Point(lonlat.lon,lonlat.lat));this.callback("create",[this.point.geometry,this.point]);this.point.geometry.clearBounds();this.layer.addFeatures([this.point],{silent:true});},deactivate:function(){if(!OpenLayers.Handler.prototype.deactivate.apply(this,arguments)){return false;}
+if(this.drawing){this.cancel();}
+this.destroyFeature();if(this.layer.map!=null){this.layer.destroy(false);}
+this.layer=null;return true;},destroyFeature:function(){if(this.layer){this.layer.destroyFeatures();}
+this.point=null;},finalize:function(cancel){var key=cancel?"cancel":"done";this.drawing=false;this.mouseDown=false;this.lastDown=null;this.lastUp=null;this.callback(key,[this.geometryClone()]);if(cancel||!this.persist){this.destroyFeature();}},cancel:function(){this.finalize(true);},click:function(evt){OpenLayers.Event.stop(evt);return false;},dblclick:function(evt){OpenLayers.Event.stop(evt);return false;},modifyFeature:function(pixel){var lonlat=this.map.getLonLatFromPixel(pixel);this.point.geometry.x=lonlat.lon;this.point.geometry.y=lonlat.lat;this.callback("modify",[this.point.geometry,this.point]);this.point.geometry.clearBounds();this.drawFeature();},drawFeature:function(){this.layer.drawFeature(this.point,this.style);},getGeometry:function(){var geometry=this.point&&this.point.geometry;if(geometry&&this.multi){geometry=new OpenLayers.Geometry.MultiPoint([geometry]);}
+return geometry;},geometryClone:function(){var geom=this.getGeometry();return geom&&geom.clone();},mousedown:function(evt){if(!this.checkModifiers(evt)){return true;}
+if(this.lastDown&&this.lastDown.equals(evt.xy)){return true;}
+this.drawing=true;if(this.lastDown==null){if(this.persist){this.destroyFeature();}
+this.createFeature(evt.xy);}else{this.modifyFeature(evt.xy);}
+this.lastDown=evt.xy;return false;},mousemove:function(evt){if(this.drawing){this.modifyFeature(evt.xy);}
+return true;},mouseup:function(evt){if(this.drawing){this.finalize();return false;}else{return true;}},CLASS_NAME:"OpenLayers.Handler.Point"});OpenLayers.Layer.GML=OpenLayers.Class(OpenLayers.Layer.Vector,{loaded:false,format:null,formatOptions:null,initialize:function(name,url,options){var newArguments=[];newArguments.push(name,options);OpenLayers.Layer.Vector.prototype.initialize.apply(this,newArguments);this.url=url;},setVisibility:function(visibility,noEvent){OpenLayers.Layer.Vector.prototype.setVisibility.apply(this,arguments);if(this.visibility&&!this.loaded){this.loadGML();}},moveTo:function(bounds,zoomChanged,minor){OpenLayers.Layer.Vector.prototype.moveTo.apply(this,arguments);if(this.visibility&&!this.loaded){this.loadGML();}},loadGML:function(){if(!this.loaded){this.events.triggerEvent("loadstart");OpenLayers.Request.GET({url:this.url,success:this.requestSuccess,failure:this.requestFailure,scope:this});this.loaded=true;}},setUrl:function(url){this.url=url;this.destroyFeatures();this.loaded=false;this.loadGML();},requestSuccess:function(request){var doc=request.responseXML;if(!doc||!doc.documentElement){doc=request.responseText;}
+var options={};OpenLayers.Util.extend(options,this.formatOptions);if(this.map&&!this.projection.equals(this.map.getProjectionObject())){options.externalProjection=this.projection;options.internalProjection=this.map.getProjectionObject();}
+var gml=this.format?new this.format(options):new OpenLayers.Format.GML(options);this.addFeatures(gml.read(doc));this.events.triggerEvent("loadend");},requestFailure:function(request){OpenLayers.Console.userError(OpenLayers.i18n("errorLoadingGML",{'url':this.url}));this.events.triggerEvent("loadend");},CLASS_NAME:"OpenLayers.Layer.GML"});OpenLayers.Layer.PointTrack=OpenLayers.Class(OpenLayers.Layer.Vector,{dataFrom:null,initialize:function(name,options){OpenLayers.Layer.Vector.prototype.initialize.apply(this,arguments);},addNodes:function(pointFeatures){if(pointFeatures.length<2){OpenLayers.Console.error("At least two point features have to be added to create"+"a line from");return;}
+var lines=new Array(pointFeatures.length-1);var pointFeature,startPoint,endPoint;for(var i=0,len=pointFeatures.length;i<len;i++){pointFeature=pointFeatures[i];endPoint=pointFeature.geometry;if(!endPoint){var lonlat=pointFeature.lonlat;endPoint=new OpenLayers.Geometry.Point(lonlat.lon,lonlat.lat);}else if(endPoint.CLASS_NAME!="OpenLayers.Geometry.Point"){OpenLayers.Console.error("Only features with point geometries are supported.");return;}
+if(i>0){var attributes=(this.dataFrom!=null)?(pointFeatures[i+this.dataFrom].data||pointFeatures[i+this.dataFrom].attributes):null;var line=new OpenLayers.Geometry.LineString([startPoint,endPoint]);lines[i-1]=new OpenLayers.Feature.Vector(line,attributes);}
+startPoint=endPoint;}
+this.addFeatures(lines);},CLASS_NAME:"OpenLayers.Layer.PointTrack"});OpenLayers.Layer.PointTrack.dataFrom={'SOURCE_NODE':-1,'TARGET_NODE':0};OpenLayers.Layer.Vector.RootContainer=OpenLayers.Class(OpenLayers.Layer.Vector,{displayInLayerSwitcher:false,layers:null,initialize:function(name,options){OpenLayers.Layer.Vector.prototype.initialize.apply(this,arguments);},display:function(){},getFeatureFromEvent:function(evt){var layers=this.layers;var feature;for(var i=0;i<layers.length;i++){feature=layers[i].getFeatureFromEvent(evt);if(feature){return feature;}}},setMap:function(map){OpenLayers.Layer.Vector.prototype.setMap.apply(this,arguments);this.collectRoots();map.events.register("changelayer",this,this.handleChangeLayer);},removeMap:function(map){map.events.unregister("changelayer",this,this.handleChangeLayer);this.resetRoots();OpenLayers.Layer.Vector.prototype.removeMap.apply(this,arguments);},collectRoots:function(){var layer;for(var i=0;i<this.map.layers.length;++i){layer=this.map.layers[i];if(OpenLayers.Util.indexOf(this.layers,layer)!=-1){layer.renderer.moveRoot(this.renderer);}}},resetRoots:function(){var layer;for(var i=0;i<this.layers.length;++i){layer=this.layers[i];if(this.renderer&&layer.renderer.getRenderLayerId()==this.id){this.renderer.moveRoot(layer.renderer);}}},handleChangeLayer:function(evt){var layer=evt.layer;if(evt.property=="order"&&OpenLayers.Util.indexOf(this.layers,layer)!=-1){this.resetRoots();this.collectRoots();}},CLASS_NAME:"OpenLayers.Layer.Vector.RootContainer"});OpenLayers.Layer.WFS=OpenLayers.Class(OpenLayers.Layer.Vector,OpenLayers.Layer.Markers,{isBaseLayer:false,tile:null,ratio:2,DEFAULT_PARAMS:{service:"WFS",version:"1.0.0",request:"GetFeature"},featureClass:null,format:null,formatObject:null,formatOptions:null,vectorMode:true,encodeBBOX:false,extractAttributes:false,initialize:function(name,url,params,options){if(options==undefined){options={};}
+if(options.featureClass||!OpenLayers.Layer.Vector||!OpenLayers.Feature.Vector){this.vectorMode=false;}
+params=OpenLayers.Util.upperCaseObject(params);OpenLayers.Util.extend(options,{'reportError':false});var newArguments=[];newArguments.push(name,options);OpenLayers.Layer.Vector.prototype.initialize.apply(this,newArguments);if(!this.renderer||!this.vectorMode){this.vectorMode=false;if(!options.featureClass){options.featureClass=OpenLayers.Feature.WFS;}
+OpenLayers.Layer.Markers.prototype.initialize.apply(this,newArguments);}
+if(this.params&&this.params.typename&&!this.options.typename){this.options.typename=this.params.typename;}
+if(!this.options.geometry_column){this.options.geometry_column="the_geom";}
+this.params=OpenLayers.Util.applyDefaults(params,OpenLayers.Util.upperCaseObject(this.DEFAULT_PARAMS));this.url=url;},destroy:function(){if(this.vectorMode){OpenLayers.Layer.Vector.prototype.destroy.apply(this,arguments);}else{OpenLayers.Layer.Markers.prototype.destroy.apply(this,arguments);}
+if(this.tile){this.tile.destroy();}
+this.tile=null;this.ratio=null;this.featureClass=null;this.format=null;if(this.formatObject&&this.formatObject.destroy){this.formatObject.destroy();}
+this.formatObject=null;this.formatOptions=null;this.vectorMode=null;this.encodeBBOX=null;this.extractAttributes=null;},setMap:function(map){if(this.vectorMode){OpenLayers.Layer.Vector.prototype.setMap.apply(this,arguments);var options={'extractAttributes':this.extractAttributes};OpenLayers.Util.extend(options,this.formatOptions);if(this.map&&!this.projection.equals(this.map.getProjectionObject())){options.externalProjection=this.projection;options.internalProjection=this.map.getProjectionObject();}
+this.formatObject=this.format?new this.format(options):new OpenLayers.Format.GML(options);}else{OpenLayers.Layer.Markers.prototype.setMap.apply(this,arguments);}},moveTo:function(bounds,zoomChanged,dragging){if(this.vectorMode){OpenLayers.Layer.Vector.prototype.moveTo.apply(this,arguments);}else{OpenLayers.Layer.Markers.prototype.moveTo.apply(this,arguments);}
+if(dragging){return false;}
+if(zoomChanged){if(this.vectorMode){this.renderer.clear();}}
+if(this.options.minZoomLevel){OpenLayers.Console.warn(OpenLayers.i18n('minZoomLevelError'));if(this.map.getZoom()<this.options.minZoomLevel){return null;}}
+if(bounds==null){bounds=this.map.getExtent();}
+var firstRendering=(this.tile==null);var outOfBounds=(!firstRendering&&!this.tile.bounds.containsBounds(bounds));if(zoomChanged||firstRendering||(!dragging&&outOfBounds)){var center=bounds.getCenterLonLat();var tileWidth=bounds.getWidth()*this.ratio;var tileHeight=bounds.getHeight()*this.ratio;var tileBounds=new OpenLayers.Bounds(center.lon-(tileWidth/2),center.lat-(tileHeight/2),center.lon+(tileWidth/2),center.lat+(tileHeight/2));var tileSize=this.map.getSize();tileSize.w=tileSize.w*this.ratio;tileSize.h=tileSize.h*this.ratio;var ul=new OpenLayers.LonLat(tileBounds.left,tileBounds.top);var pos=this.map.getLayerPxFromLonLat(ul);var url=this.getFullRequestString();var params=null;var filter=this.params.filter||this.params.FILTER;if(filter){params={FILTER:filter};}
+else{params={BBOX:this.encodeBBOX?tileBounds.toBBOX():tileBounds.toArray()};}
+if(this.map&&!this.projection.equals(this.map.getProjectionObject())){var projectedBounds=tileBounds.clone();projectedBounds.transform(this.map.getProjectionObject(),this.projection);if(!filter){params.BBOX=this.encodeBBOX?projectedBounds.toBBOX():projectedBounds.toArray();}}
+url+="&"+OpenLayers.Util.getParameterString(params);if(!this.tile){this.tile=new OpenLayers.Tile.WFS(this,pos,tileBounds,url,tileSize);this.addTileMonitoringHooks(this.tile);this.tile.draw();}else{if(this.vectorMode){this.destroyFeatures();this.renderer.clear();}else{this.clearMarkers();}
+this.removeTileMonitoringHooks(this.tile);this.tile.destroy();this.tile=null;this.tile=new OpenLayers.Tile.WFS(this,pos,tileBounds,url,tileSize);this.addTileMonitoringHooks(this.tile);this.tile.draw();}}},addTileMonitoringHooks:function(tile){tile.onLoadStart=function(){if(this==this.layer.tile){this.layer.events.triggerEvent("loadstart");}};tile.events.register("loadstart",tile,tile.onLoadStart);tile.onLoadEnd=function(){if(this==this.layer.tile){this.layer.events.triggerEvent("tileloaded");this.layer.events.triggerEvent("loadend");}};tile.events.register("loadend",tile,tile.onLoadEnd);tile.events.register("unload",tile,tile.onLoadEnd);},removeTileMonitoringHooks:function(tile){tile.unload();tile.events.un({"loadstart":tile.onLoadStart,"loadend":tile.onLoadEnd,"unload":tile.onLoadEnd,scope:tile});},onMapResize:function(){if(this.vectorMode){OpenLayers.Layer.Vector.prototype.onMapResize.apply(this,arguments);}else{OpenLayers.Layer.Markers.prototype.onMapResize.apply(this,arguments);}},display:function(){if(this.vectorMode){OpenLayers.Layer.Vector.prototype.display.apply(this,arguments);}else{OpenLayers.Layer.Markers.prototype.display.apply(this,arguments);}},mergeNewParams:function(newParams){var upperParams=OpenLayers.Util.upperCaseObject(newParams);var newArguments=[upperParams];return OpenLayers.Layer.HTTPRequest.prototype.mergeNewParams.apply(this,newArguments);},clone:function(obj){if(obj==null){obj=new OpenLayers.Layer.WFS(this.name,this.url,this.params,this.getOptions());}
+if(this.vectorMode){obj=OpenLayers.Layer.Vector.prototype.clone.apply(this,[obj]);}else{obj=OpenLayers.Layer.Markers.prototype.clone.apply(this,[obj]);}
+return obj;},getFullRequestString:function(newParams,altUrl){var projectionCode=this.projection.getCode()||this.map.getProjection();this.params.SRS=(projectionCode=="none")?null:projectionCode;return OpenLayers.Layer.Grid.prototype.getFullRequestString.apply(this,arguments);},commit:function(){if(!this.writer){var options={};if(this.map&&!this.projection.equals(this.map.getProjectionObject())){options.externalProjection=this.projection;options.internalProjection=this.map.getProjectionObject();}
+this.writer=new OpenLayers.Format.WFS(options,this);}
+var data=this.writer.write(this.features);OpenLayers.Request.POST({url:this.url,data:data,success:this.commitSuccess,failure:this.commitFailure,scope:this});},commitSuccess:function(request){var response=request.responseText;if(response.indexOf('SUCCESS')!=-1){this.commitReport(OpenLayers.i18n("commitSuccess",{'response':response}));for(var i=0;i<this.features.length;i++){this.features[i].state=null;}}else if(response.indexOf('FAILED')!=-1||response.indexOf('Exception')!=-1){this.commitReport(OpenLayers.i18n("commitFailed",{'response':response}));}},commitFailure:function(request){},commitReport:function(string,response){OpenLayers.Console.userError(string);},refresh:function(){if(this.tile){if(this.vectorMode){this.renderer.clear();this.features.length=0;}else{this.clearMarkers();this.markers.length=0;}
+this.tile.draw();}},getDataExtent:function(){var extent;if(this.vectorMode){extent=OpenLayers.Layer.Vector.prototype.getDataExtent.apply(this);}else{extent=OpenLayers.Layer.Markers.prototype.getDataExtent.apply(this);}
+return extent;},setOpacity:function(opacity){if(this.vectorMode){OpenLayers.Layer.Vector.prototype.setOpacity.apply(this,[opacity]);}else{OpenLayers.Layer.Markers.prototype.setOpacity.apply(this,[opacity]);}},CLASS_NAME:"OpenLayers.Layer.WFS"});OpenLayers.Protocol.HTTP=OpenLayers.Class(OpenLayers.Protocol,{url:null,headers:null,params:null,callback:null,scope:null,readWithPOST:false,wildcarded:false,initialize:function(options){options=options||{};this.params={};this.headers={};OpenLayers.Protocol.prototype.initialize.apply(this,arguments);},destroy:function(){this.params=null;this.headers=null;OpenLayers.Protocol.prototype.destroy.apply(this);},read:function(options){OpenLayers.Protocol.prototype.read.apply(this,arguments);options=OpenLayers.Util.applyDefaults(options,this.options);options.params=OpenLayers.Util.applyDefaults(options.params,this.options.params);if(options.filter){options.params=this.filterToParams(options.filter,options.params);}
+var readWithPOST=(options.readWithPOST!==undefined)?options.readWithPOST:this.readWithPOST;var resp=new OpenLayers.Protocol.Response({requestType:"read"});if(readWithPOST){resp.priv=OpenLayers.Request.POST({url:options.url,callback:this.createCallback(this.handleRead,resp,options),data:OpenLayers.Util.getParameterString(options.params),headers:{"Content-Type":"application/x-www-form-urlencoded"}});}else{resp.priv=OpenLayers.Request.GET({url:options.url,callback:this.createCallback(this.handleRead,resp,options),params:options.params,headers:options.headers});}
+return resp;},handleRead:function(resp,options){this.handleResponse(resp,options);},filterToParams:function(filter,params){params=params||{};var className=filter.CLASS_NAME;var filterType=className.substring(className.lastIndexOf(".")+1);switch(filterType){case"Spatial":switch(filter.type){case OpenLayers.Filter.Spatial.BBOX:params.bbox=filter.value.toArray();break;case OpenLayers.Filter.Spatial.DWITHIN:params.tolerance=filter.distance;case OpenLayers.Filter.Spatial.WITHIN:params.lon=filter.value.x;params.lat=filter.value.y;break;default:OpenLayers.Console.warn("Unknown spatial filter type "+filter.type);}
+break;case"Comparison":var op=OpenLayers.Protocol.HTTP.COMP_TYPE_TO_OP_STR[filter.type];if(op!==undefined){var value=filter.value;if(filter.type==OpenLayers.Filter.Comparison.LIKE){value=this.regex2value(value);if(this.wildcarded){value="%"+value+"%";}}
+params[filter.property+"__"+op]=value;params.queryable=params.queryable||[];params.queryable.push(filter.property);}else{OpenLayers.Console.warn("Unknown comparison filter type "+filter.type);}
+break;case"Logical":if(filter.type===OpenLayers.Filter.Logical.AND){for(var i=0,len=filter.filters.length;i<len;i++){params=this.filterToParams(filter.filters[i],params);}}else{OpenLayers.Console.warn("Unsupported logical filter type "+filter.type);}
+break;default:OpenLayers.Console.warn("Unknown filter type "+filterType);}
+return params;},regex2value:function(value){value=value.replace(/%/g,"\\%");value=value.replace(/\\\\\.(\*)?/g,function($0,$1){return $1?$0:"\\\\_";});value=value.replace(/\\\\\.\*/g,"\\\\%");value=value.replace(/(\\)?\.(\*)?/g,function($0,$1,$2){return $1||$2?$0:"_";});value=value.replace(/(\\)?\.\*/g,function($0,$1){return $1?$0:"%";});value=value.replace(/\\\./g,".");value=value.replace(/(\\)?\\\*/g,function($0,$1){return $1?$0:"*";});return value;},create:function(features,options){options=OpenLayers.Util.applyDefaults(options,this.options);var resp=new OpenLayers.Protocol.Response({reqFeatures:features,requestType:"create"});resp.priv=OpenLayers.Request.POST({url:options.url,callback:this.createCallback(this.handleCreate,resp,options),headers:options.headers,data:this.format.write(features)});return resp;},handleCreate:function(resp,options){this.handleResponse(resp,options);},update:function(feature,options){options=options||{};var url=options.url||feature.url||this.options.url+"/"+feature.fid;options=OpenLayers.Util.applyDefaults(options,this.options);var resp=new OpenLayers.Protocol.Response({reqFeatures:feature,requestType:"update"});resp.priv=OpenLayers.Request.PUT({url:url,callback:this.createCallback(this.handleUpdate,resp,options),headers:options.headers,data:this.format.write(feature)});return resp;},handleUpdate:function(resp,options){this.handleResponse(resp,options);},"delete":function(feature,options){options=options||{};var url=options.url||feature.url||this.options.url+"/"+feature.fid;options=OpenLayers.Util.applyDefaults(options,this.options);var resp=new OpenLayers.Protocol.Response({reqFeatures:feature,requestType:"delete"});resp.priv=OpenLayers.Request.DELETE({url:url,callback:this.createCallback(this.handleDelete,resp,options),headers:options.headers});return resp;},handleDelete:function(resp,options){this.handleResponse(resp,options);},handleResponse:function(resp,options){var request=resp.priv;if(options.callback){if(request.status>=200&&request.status<300){if(resp.requestType!="delete"){resp.features=this.parseFeatures(request);}
+resp.code=OpenLayers.Protocol.Response.SUCCESS;}else{resp.code=OpenLayers.Protocol.Response.FAILURE;}
+options.callback.call(options.scope,resp);}},parseFeatures:function(request){var doc=request.responseXML;if(!doc||!doc.documentElement){doc=request.responseText;}
+if(!doc||doc.length<=0){return null;}
+return this.format.read(doc);},commit:function(features,options){options=OpenLayers.Util.applyDefaults(options,this.options);var resp=[],nResponses=0;var types={};types[OpenLayers.State.INSERT]=[];types[OpenLayers.State.UPDATE]=[];types[OpenLayers.State.DELETE]=[];var feature,list,requestFeatures=[];for(var i=0,len=features.length;i<len;++i){feature=features[i];list=types[feature.state];if(list){list.push(feature);requestFeatures.push(feature);}}
+var nRequests=(types[OpenLayers.State.INSERT].length>0?1:0)+
+types[OpenLayers.State.UPDATE].length+
+types[OpenLayers.State.DELETE].length;var success=true;var finalResponse=new OpenLayers.Protocol.Response({reqFeatures:requestFeatures});function insertCallback(response){var len=response.features?response.features.length:0;var fids=new Array(len);for(var i=0;i<len;++i){fids[i]=response.features[i].fid;}
+finalResponse.insertIds=fids;callback.apply(this,[response]);}
+function callback(response){this.callUserCallback(response,options);success=success&&response.success();nResponses++;if(nResponses>=nRequests){if(options.callback){finalResponse.code=success?OpenLayers.Protocol.Response.SUCCESS:OpenLayers.Protocol.Response.FAILURE;options.callback.apply(options.scope,[finalResponse]);}}}
+var queue=types[OpenLayers.State.INSERT];if(queue.length>0){resp.push(this.create(queue,OpenLayers.Util.applyDefaults({callback:insertCallback,scope:this},options.create)));}
+queue=types[OpenLayers.State.UPDATE];for(var i=queue.length-1;i>=0;--i){resp.push(this.update(queue[i],OpenLayers.Util.applyDefaults({callback:callback,scope:this},options.update)));}
+queue=types[OpenLayers.State.DELETE];for(var i=queue.length-1;i>=0;--i){resp.push(this["delete"](queue[i],OpenLayers.Util.applyDefaults({callback:callback,scope:this},options["delete"])));}
+return resp;},abort:function(response){if(response){response.priv.abort();}},callUserCallback:function(resp,options){var opt=options[resp.requestType];if(opt&&opt.callback){opt.callback.call(opt.scope,resp);}},CLASS_NAME:"OpenLayers.Protocol.HTTP"});(function(){var o=OpenLayers.Protocol.HTTP.COMP_TYPE_TO_OP_STR={};o[OpenLayers.Filter.Comparison.EQUAL_TO]="eq";o[OpenLayers.Filter.Comparison.NOT_EQUAL_TO]="ne";o[OpenLayers.Filter.Comparison.LESS_THAN]="lt";o[OpenLayers.Filter.Comparison.LESS_THAN_OR_EQUAL_TO]="lte";o[OpenLayers.Filter.Comparison.GREATER_THAN]="gt";o[OpenLayers.Filter.Comparison.GREATER_THAN_OR_EQUAL_TO]="gte";o[OpenLayers.Filter.Comparison.LIKE]="ilike";})();OpenLayers.Strategy.BBOX=OpenLayers.Class(OpenLayers.Strategy,{bounds:null,resolution:null,ratio:2,resFactor:null,response:null,initialize:function(options){OpenLayers.Strategy.prototype.initialize.apply(this,[options]);},activate:function(){var activated=OpenLayers.Strategy.prototype.activate.call(this);if(activated){this.layer.events.on({"moveend":this.update,scope:this});this.layer.events.on({"refresh":this.update,scope:this});}
+return activated;},deactivate:function(){var deactivated=OpenLayers.Strategy.prototype.deactivate.call(this);if(deactivated){this.layer.events.un({"moveend":this.update,scope:this});this.layer.events.un({"refresh":this.update,scope:this});}
+return deactivated;},update:function(options){var mapBounds=this.getMapBounds();if((options&&options.force)||this.invalidBounds(mapBounds)){this.calculateBounds(mapBounds);this.resolution=this.layer.map.getResolution();this.triggerRead();}},getMapBounds:function(){var bounds=this.layer.map.getExtent();if(!this.layer.projection.equals(this.layer.map.getProjectionObject())){bounds=bounds.clone().transform(this.layer.map.getProjectionObject(),this.layer.projection);}
+return bounds;},invalidBounds:function(mapBounds){if(!mapBounds){mapBounds=this.getMapBounds();}
+var invalid=!this.bounds||!this.bounds.containsBounds(mapBounds);if(!invalid&&this.resFactor){var ratio=this.resolution/this.layer.map.getResolution();invalid=(ratio>=this.resFactor||ratio<=(1/this.resFactor));}
+return invalid;},calculateBounds:function(mapBounds){if(!mapBounds){mapBounds=this.getMapBounds();}
+var center=mapBounds.getCenterLonLat();var dataWidth=mapBounds.getWidth()*this.ratio;var dataHeight=mapBounds.getHeight()*this.ratio;this.bounds=new OpenLayers.Bounds(center.lon-(dataWidth/2),center.lat-(dataHeight/2),center.lon+(dataWidth/2),center.lat+(dataHeight/2));},triggerRead:function(){if(this.response){this.layer.protocol.abort(this.response);this.layer.events.triggerEvent("loadend");}
+this.layer.events.triggerEvent("loadstart");this.response=this.layer.protocol.read({filter:this.createFilter(),callback:this.merge,scope:this});},createFilter:function(){var filter=new OpenLayers.Filter.Spatial({type:OpenLayers.Filter.Spatial.BBOX,value:this.bounds,projection:this.layer.projection});if(this.layer.filter){filter=new OpenLayers.Filter.Logical({type:OpenLayers.Filter.Logical.AND,filters:[this.layer.filter,filter]});}
+return filter;},merge:function(resp){this.layer.destroyFeatures();var features=resp.features;if(features&&features.length>0){var remote=this.layer.projection;var local=this.layer.map.getProjectionObject();if(!local.equals(remote)){var geom;for(var i=0,len=features.length;i<len;++i){geom=features[i].geometry;if(geom){geom.transform(remote,local);}}}
+this.layer.addFeatures(features);}
+this.response=null;this.layer.events.triggerEvent("loadend");},CLASS_NAME:"OpenLayers.Strategy.BBOX"});OpenLayers.Control.SelectFeature=OpenLayers.Class(OpenLayers.Control,{EVENT_TYPES:["beforefeaturehighlighted","featurehighlighted","featureunhighlighted"],multipleKey:null,toggleKey:null,multiple:false,clickout:true,toggle:false,hover:false,highlightOnly:false,box:false,onBeforeSelect:function(){},onSelect:function(){},onUnselect:function(){},scope:null,geometryTypes:null,layer:null,layers:null,callbacks:null,selectStyle:null,renderIntent:"select",handlers:null,initialize:function(layers,options){this.EVENT_TYPES=OpenLayers.Control.SelectFeature.prototype.EVENT_TYPES.concat(OpenLayers.Control.prototype.EVENT_TYPES);OpenLayers.Control.prototype.initialize.apply(this,[options]);if(this.scope===null){this.scope=this;}
+this.initLayer(layers);var callbacks={click:this.clickFeature,clickout:this.clickoutFeature};if(this.hover){callbacks.over=this.overFeature;callbacks.out=this.outFeature;}
+this.callbacks=OpenLayers.Util.extend(callbacks,this.callbacks);this.handlers={feature:new OpenLayers.Handler.Feature(this,this.layer,this.callbacks,{geometryTypes:this.geometryTypes})};if(this.box){this.handlers.box=new OpenLayers.Handler.Box(this,{done:this.selectBox},{boxDivClassName:"olHandlerBoxSelectFeature"});}},initLayer:function(layers){if(layers instanceof Array){this.layers=layers;this.layer=new OpenLayers.Layer.Vector.RootContainer(this.id+"_container",{layers:layers});}else{this.layer=layers;}},destroy:function(){if(this.active&&this.layers){this.map.removeLayer(this.layer);}
+OpenLayers.Control.prototype.destroy.apply(this,arguments);if(this.layers){this.layer.destroy();}},activate:function(){if(!this.active){if(this.layers){this.map.addLayer(this.layer);}
+this.handlers.feature.activate();if(this.box&&this.handlers.box){this.handlers.box.activate();}}
+return OpenLayers.Control.prototype.activate.apply(this,arguments);},deactivate:function(){if(this.active){this.handlers.feature.deactivate();if(this.handlers.box){this.handlers.box.deactivate();}
+if(this.layers){this.map.removeLayer(this.layer);}}
+return OpenLayers.Control.prototype.deactivate.apply(this,arguments);},unselectAll:function(options){var layers=this.layers||[this.layer];var layer,feature;for(var l=0;l<layers.length;++l){layer=layers[l];for(var i=layer.selectedFeatures.length-1;i>=0;--i){feature=layer.selectedFeatures[i];if(!options||options.except!=feature){this.unselect(feature);}}}},clickFeature:function(feature){if(!this.hover){var selected=(OpenLayers.Util.indexOf(feature.layer.selectedFeatures,feature)>-1);if(selected){if(this.toggleSelect()){this.unselect(feature);}else if(!this.multipleSelect()){this.unselectAll({except:feature});}}else{if(!this.multipleSelect()){this.unselectAll({except:feature});}
+this.select(feature);}}},multipleSelect:function(){return this.multiple||(this.handlers.feature.evt&&this.handlers.feature.evt[this.multipleKey]);},toggleSelect:function(){return this.toggle||(this.handlers.feature.evt&&this.handlers.feature.evt[this.toggleKey]);},clickoutFeature:function(feature){if(!this.hover&&this.clickout){this.unselectAll();}},overFeature:function(feature){var layer=feature.layer;if(this.hover){if(this.highlightOnly){this.highlight(feature);}else if(OpenLayers.Util.indexOf(layer.selectedFeatures,feature)==-1){this.select(feature);}}},outFeature:function(feature){if(this.hover){if(this.highlightOnly){if(feature._lastHighlighter==this.id){if(feature._prevHighlighter&&feature._prevHighlighter!=this.id){delete feature._lastHighlighter;var control=this.map.getControl(feature._prevHighlighter);if(control){control.highlight(feature);}}else{this.unhighlight(feature);}}}else{this.unselect(feature);}}},highlight:function(feature){var layer=feature.layer;var cont=this.events.triggerEvent("beforefeaturehighlighted",{feature:feature});if(cont!==false){feature._prevHighlighter=feature._lastHighlighter;feature._lastHighlighter=this.id;var style=this.selectStyle||this.renderIntent;layer.drawFeature(feature,style);this.events.triggerEvent("featurehighlighted",{feature:feature});}},unhighlight:function(feature){var layer=feature.layer;feature._lastHighlighter=feature._prevHighlighter;delete feature._prevHighlighter;layer.drawFeature(feature,feature.style||feature.layer.style||"default");this.events.triggerEvent("featureunhighlighted",{feature:feature});},select:function(feature){var cont=this.onBeforeSelect.call(this.scope,feature);var layer=feature.layer;if(cont!==false){cont=layer.events.triggerEvent("beforefeatureselected",{feature:feature});if(cont!==false){layer.selectedFeatures.push(feature);this.highlight(feature);if(!this.handlers.feature.lastFeature){this.handlers.feature.lastFeature=layer.selectedFeatures[0];}
+layer.events.triggerEvent("featureselected",{feature:feature});this.onSelect.call(this.scope,feature);}}},unselect:function(feature){var layer=feature.layer;this.unhighlight(feature);OpenLayers.Util.removeItem(layer.selectedFeatures,feature);layer.events.triggerEvent("featureunselected",{feature:feature});this.onUnselect.call(this.scope,feature);},selectBox:function(position){if(position instanceof OpenLayers.Bounds){var minXY=this.map.getLonLatFromPixel(new OpenLayers.Pixel(position.left,position.bottom));var maxXY=this.map.getLonLatFromPixel(new OpenLayers.Pixel(position.right,position.top));var bounds=new OpenLayers.Bounds(minXY.lon,minXY.lat,maxXY.lon,maxXY.lat);if(!this.multipleSelect()){this.unselectAll();}
+var prevMultiple=this.multiple;this.multiple=true;var layers=this.layers||[this.layer];var layer;for(var l=0;l<layers.length;++l){layer=layers[l];for(var i=0,len=layer.features.length;i<len;++i){var feature=layer.features[i];if(!feature.getVisibility()){continue;}
+if(this.geometryTypes==null||OpenLayers.Util.indexOf(this.geometryTypes,feature.geometry.CLASS_NAME)>-1){if(bounds.toGeometry().intersects(feature.geometry)){if(OpenLayers.Util.indexOf(layer.selectedFeatures,feature)==-1){this.select(feature);}}}}}
+this.multiple=prevMultiple;}},setMap:function(map){this.handlers.feature.setMap(map);if(this.box){this.handlers.box.setMap(map);}
+OpenLayers.Control.prototype.setMap.apply(this,arguments);},setLayer:function(layers){var isActive=this.active;this.unselectAll();this.deactivate();if(this.layers){this.layer.destroy();this.layers=null;}
+this.initLayer(layers);this.handlers.feature.layer=this.layer;if(isActive){this.activate();}},CLASS_NAME:"OpenLayers.Control.SelectFeature"});OpenLayers.Format.Filter.v1=OpenLayers.Class(OpenLayers.Format.XML,{namespaces:{ogc:"http://www.opengis.net/ogc",gml:"http://www.opengis.net/gml",xlink:"http://www.w3.org/1999/xlink",xsi:"http://www.w3.org/2001/XMLSchema-instance"},defaultPrefix:"ogc",schemaLocation:null,initialize:function(options){OpenLayers.Format.XML.prototype.initialize.apply(this,[options]);},read:function(data){var obj={};this.readers.ogc["Filter"].apply(this,[data,obj]);return obj.filter;},readers:{"ogc":{"Filter":function(node,parent){var obj={fids:[],filters:[]};this.readChildNodes(node,obj);if(obj.fids.length>0){parent.filter=new OpenLayers.Filter.FeatureId({fids:obj.fids});}else if(obj.filters.length>0){parent.filter=obj.filters[0];}},"FeatureId":function(node,obj){var fid=node.getAttribute("fid");if(fid){obj.fids.push(fid);}},"And":function(node,obj){var filter=new OpenLayers.Filter.Logical({type:OpenLayers.Filter.Logical.AND});this.readChildNodes(node,filter);obj.filters.push(filter);},"Or":function(node,obj){var filter=new OpenLayers.Filter.Logical({type:OpenLayers.Filter.Logical.OR});this.readChildNodes(node,filter);obj.filters.push(filter);},"Not":function(node,obj){var filter=new OpenLayers.Filter.Logical({type:OpenLayers.Filter.Logical.NOT});this.readChildNodes(node,filter);obj.filters.push(filter);},"PropertyIsLessThan":function(node,obj){var filter=new OpenLayers.Filter.Comparison({type:OpenLayers.Filter.Comparison.LESS_THAN});this.readChildNodes(node,filter);obj.filters.push(filter);},"PropertyIsGreaterThan":function(node,obj){var filter=new OpenLayers.Filter.Comparison({type:OpenLayers.Filter.Comparison.GREATER_THAN});this.readChildNodes(node,filter);obj.filters.push(filter);},"PropertyIsLessThanOrEqualTo":function(node,obj){var filter=new OpenLayers.Filter.Comparison({type:OpenLayers.Filter.Comparison.LESS_THAN_OR_EQUAL_TO});this.readChildNodes(node,filter);obj.filters.push(filter);},"PropertyIsGreaterThanOrEqualTo":function(node,obj){var filter=new OpenLayers.Filter.Comparison({type:OpenLayers.Filter.Comparison.GREATER_THAN_OR_EQUAL_TO});this.readChildNodes(node,filter);obj.filters.push(filter);},"PropertyIsBetween":function(node,obj){var filter=new OpenLayers.Filter.Comparison({type:OpenLayers.Filter.Comparison.BETWEEN});this.readChildNodes(node,filter);obj.filters.push(filter);},"Literal":function(node,obj){obj.value=OpenLayers.String.numericIf(this.getChildValue(node));},"PropertyName":function(node,filter){filter.property=this.getChildValue(node);},"LowerBoundary":function(node,filter){filter.lowerBoundary=OpenLayers.String.numericIf(this.readOgcExpression(node));},"UpperBoundary":function(node,filter){filter.upperBoundary=OpenLayers.String.numericIf(this.readOgcExpression(node));},"Intersects":function(node,obj){this.readSpatial(node,obj,OpenLayers.Filter.Spatial.INTERSECTS);},"Within":function(node,obj){this.readSpatial(node,obj,OpenLayers.Filter.Spatial.WITHIN);},"Contains":function(node,obj){this.readSpatial(node,obj,OpenLayers.Filter.Spatial.CONTAINS);},"DWithin":function(node,obj){this.readSpatial(node,obj,OpenLayers.Filter.Spatial.DWITHIN);},"Distance":function(node,obj){obj.distance=parseInt(this.getChildValue(node));obj.distanceUnits=node.getAttribute("units");}}},readSpatial:function(node,obj,type){var filter=new OpenLayers.Filter.Spatial({type:type});this.readChildNodes(node,filter);filter.value=filter.components[0];delete filter.components;obj.filters.push(filter);},readOgcExpression:function(node){var obj={};this.readChildNodes(node,obj);var value=obj.value;if(value===undefined){value=this.getChildValue(node);}
+return value;},write:function(filter){return this.writers.ogc["Filter"].apply(this,[filter]);},writers:{"ogc":{"Filter":function(filter){var node=this.createElementNSPlus("ogc:Filter");var sub=filter.CLASS_NAME.split(".").pop();if(sub=="FeatureId"){for(var i=0;i<filter.fids.length;++i){this.writeNode("FeatureId",filter.fids[i],node);}}else{this.writeNode(this.getFilterType(filter),filter,node);}
+return node;},"FeatureId":function(fid){return this.createElementNSPlus("ogc:FeatureId",{attributes:{fid:fid}});},"And":function(filter){var node=this.createElementNSPlus("ogc:And");var childFilter;for(var i=0;i<filter.filters.length;++i){childFilter=filter.filters[i];this.writeNode(this.getFilterType(childFilter),childFilter,node);}
+return node;},"Or":function(filter){var node=this.createElementNSPlus("ogc:Or");var childFilter;for(var i=0;i<filter.filters.length;++i){childFilter=filter.filters[i];this.writeNode(this.getFilterType(childFilter),childFilter,node);}
+return node;},"Not":function(filter){var node=this.createElementNSPlus("ogc:Not");var childFilter=filter.filters[0];this.writeNode(this.getFilterType(childFilter),childFilter,node);return node;},"PropertyIsLessThan":function(filter){var node=this.createElementNSPlus("ogc:PropertyIsLessThan");this.writeNode("PropertyName",filter,node);this.writeNode("Literal",filter.value,node);return node;},"PropertyIsGreaterThan":function(filter){var node=this.createElementNSPlus("ogc:PropertyIsGreaterThan");this.writeNode("PropertyName",filter,node);this.writeNode("Literal",filter.value,node);return node;},"PropertyIsLessThanOrEqualTo":function(filter){var node=this.createElementNSPlus("ogc:PropertyIsLessThanOrEqualTo");this.writeNode("PropertyName",filter,node);this.writeNode("Literal",filter.value,node);return node;},"PropertyIsGreaterThanOrEqualTo":function(filter){var node=this.createElementNSPlus("ogc:PropertyIsGreaterThanOrEqualTo");this.writeNode("PropertyName",filter,node);this.writeNode("Literal",filter.value,node);return node;},"PropertyIsBetween":function(filter){var node=this.createElementNSPlus("ogc:PropertyIsBetween");this.writeNode("PropertyName",filter,node);this.writeNode("LowerBoundary",filter,node);this.writeNode("UpperBoundary",filter,node);return node;},"PropertyName":function(filter){return this.createElementNSPlus("ogc:PropertyName",{value:filter.property});},"Literal":function(value){return this.createElementNSPlus("ogc:Literal",{value:value});},"LowerBoundary":function(filter){var node=this.createElementNSPlus("ogc:LowerBoundary");this.writeNode("Literal",filter.lowerBoundary,node);return node;},"UpperBoundary":function(filter){var node=this.createElementNSPlus("ogc:UpperBoundary");this.writeNode("Literal",filter.upperBoundary,node);return node;},"INTERSECTS":function(filter){return this.writeSpatial(filter,"Intersects");},"WITHIN":function(filter){return this.writeSpatial(filter,"Within");},"CONTAINS":function(filter){return this.writeSpatial(filter,"Contains");},"DWITHIN":function(filter){var node=this.writeSpatial(filter,"DWithin");this.writeNode("Distance",filter,node);return node;},"Distance":function(filter){return this.createElementNSPlus("ogc:Distance",{attributes:{units:filter.distanceUnits},value:filter.distance});}}},getFilterType:function(filter){var filterType=this.filterMap[filter.type];if(!filterType){throw"Filter writing not supported for rule type: "+filter.type;}
+return filterType;},filterMap:{"&&":"And","||":"Or","!":"Not","==":"PropertyIsEqualTo","!=":"PropertyIsNotEqualTo","<":"PropertyIsLessThan",">":"PropertyIsGreaterThan","<=":"PropertyIsLessThanOrEqualTo",">=":"PropertyIsGreaterThanOrEqualTo","..":"PropertyIsBetween","~":"PropertyIsLike","BBOX":"BBOX","DWITHIN":"DWITHIN","WITHIN":"WITHIN","CONTAINS":"CONTAINS","INTERSECTS":"INTERSECTS"},CLASS_NAME:"OpenLayers.Format.Filter.v1"});OpenLayers.Geometry.Curve=OpenLayers.Class(OpenLayers.Geometry.MultiPoint,{componentTypes:["OpenLayers.Geometry.Point"],initialize:function(points){OpenLayers.Geometry.MultiPoint.prototype.initialize.apply(this,arguments);},getLength:function(){var length=0.0;if(this.components&&(this.components.length>1)){for(var i=1,len=this.components.length;i<len;i++){length+=this.components[i-1].distanceTo(this.components[i]);}}
+return length;},getGeodesicLength:function(projection){var geom=this;if(projection){var gg=new OpenLayers.Projection("EPSG:4326");if(!gg.equals(projection)){geom=this.clone().transform(projection,gg);}}
+var length=0.0;if(geom.components&&(geom.components.length>1)){var p1,p2;for(var i=1,len=geom.components.length;i<len;i++){p1=geom.components[i-1];p2=geom.components[i];length+=OpenLayers.Util.distVincenty({lon:p1.x,lat:p1.y},{lon:p2.x,lat:p2.y});}}
+return length*1000;},CLASS_NAME:"OpenLayers.Geometry.Curve"});OpenLayers.Layer.Text=OpenLayers.Class(OpenLayers.Layer.Markers,{location:null,features:null,formatOptions:null,selectedFeature:null,initialize:function(name,options){OpenLayers.Layer.Markers.prototype.initialize.apply(this,arguments);this.features=new Array();},destroy:function(){OpenLayers.Layer.Markers.prototype.destroy.apply(this,arguments);this.clearFeatures();this.features=null;},loadText:function(){if(!this.loaded){if(this.location!=null){var onFail=function(e){this.events.triggerEvent("loadend");};this.events.triggerEvent("loadstart");OpenLayers.Request.GET({url:this.location,success:this.parseData,failure:onFail,scope:this});this.loaded=true;}}},moveTo:function(bounds,zoomChanged,minor){OpenLayers.Layer.Markers.prototype.moveTo.apply(this,arguments);if(this.visibility&&!this.loaded){this.loadText();}},parseData:function(ajaxRequest){var text=ajaxRequest.responseText;var options={};OpenLayers.Util.extend(options,this.formatOptions);if(this.map&&!this.projection.equals(this.map.getProjectionObject())){options.externalProjection=this.projection;options.internalProjection=this.map.getProjectionObject();}
+var parser=new OpenLayers.Format.Text(options);var features=parser.read(text);for(var i=0,len=features.length;i<len;i++){var data={};var feature=features[i];var location;var iconSize,iconOffset;location=new OpenLayers.LonLat(feature.geometry.x,feature.geometry.y);if(feature.style.graphicWidth&&feature.style.graphicHeight){iconSize=new OpenLayers.Size(feature.style.graphicWidth,feature.style.graphicHeight);}
+if(feature.style.graphicXOffset!==undefined&&feature.style.graphicYOffset!==undefined){iconOffset=new OpenLayers.Pixel(feature.style.graphicXOffset,feature.style.graphicYOffset);}
+if(feature.style.externalGraphic!=null){data.icon=new OpenLayers.Icon(feature.style.externalGraphic,iconSize,iconOffset);}else{data.icon=OpenLayers.Marker.defaultIcon();if(iconSize!=null){data.icon.setSize(iconSize);}}
+if((feature.attributes.title!=null)&&(feature.attributes.description!=null)){data['popupContentHTML']='<h2>'+feature.attributes.title+'</h2>'+'<p>'+feature.attributes.description+'</p>';}
+data['overflow']=feature.attributes.overflow||"auto";var markerFeature=new OpenLayers.Feature(this,location,data);this.features.push(markerFeature);var marker=markerFeature.createMarker();if((feature.attributes.title!=null)&&(feature.attributes.description!=null)){marker.events.register('click',markerFeature,this.markerClick);}
+this.addMarker(marker);}
+this.events.triggerEvent("loadend");},markerClick:function(evt){var sameMarkerClicked=(this==this.layer.selectedFeature);this.layer.selectedFeature=(!sameMarkerClicked)?this:null;for(var i=0,len=this.layer.map.popups.length;i<len;i++){this.layer.map.removePopup(this.layer.map.popups[i]);}
+if(!sameMarkerClicked){this.layer.map.addPopup(this.createPopup());}
+OpenLayers.Event.stop(evt);},clearFeatures:function(){if(this.features!=null){while(this.features.length>0){var feature=this.features[0];OpenLayers.Util.removeItem(this.features,feature);feature.destroy();}}},CLASS_NAME:"OpenLayers.Layer.Text"});OpenLayers.Control.ModifyFeature=OpenLayers.Class(OpenLayers.Control,{geometryTypes:null,clickout:true,toggle:true,standalone:false,layer:null,feature:null,vertices:null,virtualVertices:null,selectControl:null,dragControl:null,handlers:null,deleteCodes:null,virtualStyle:null,mode:null,modified:false,radiusHandle:null,dragHandle:null,onModificationStart:function(){},onModification:function(){},onModificationEnd:function(){},initialize:function(layer,options){this.layer=layer;this.vertices=[];this.virtualVertices=[];this.virtualStyle=OpenLayers.Util.extend({},this.layer.style||this.layer.styleMap.createSymbolizer());this.virtualStyle.fillOpacity=0.3;this.virtualStyle.strokeOpacity=0.3;this.deleteCodes=[46,68];this.mode=OpenLayers.Control.ModifyFeature.RESHAPE;OpenLayers.Control.prototype.initialize.apply(this,[options]);if(!(this.deleteCodes instanceof Array)){this.deleteCodes=[this.deleteCodes];}
+var control=this;var selectOptions={geometryTypes:this.geometryTypes,clickout:this.clickout,toggle:this.toggle,onBeforeSelect:this.beforeSelectFeature,onSelect:this.selectFeature,onUnselect:this.unselectFeature,scope:this};if(this.standalone===false){this.selectControl=new OpenLayers.Control.SelectFeature(layer,selectOptions);}
+var dragOptions={geometryTypes:["OpenLayers.Geometry.Point"],snappingOptions:this.snappingOptions,onStart:function(feature,pixel){control.dragStart.apply(control,[feature,pixel]);},onDrag:function(feature,pixel){control.dragVertex.apply(control,[feature,pixel]);},onComplete:function(feature){control.dragComplete.apply(control,[feature]);},featureCallbacks:{over:function(feature){if(control.standalone!==true||feature._sketch||control.feature===feature){control.dragControl.overFeature.apply(control.dragControl,[feature]);}}}};this.dragControl=new OpenLayers.Control.DragFeature(layer,dragOptions);var keyboardOptions={keydown:this.handleKeypress};this.handlers={keyboard:new OpenLayers.Handler.Keyboard(this,keyboardOptions)};},destroy:function(){this.layer=null;this.standalone||this.selectControl.destroy();this.dragControl.destroy();OpenLayers.Control.prototype.destroy.apply(this,[]);},activate:function(){return((this.standalone||this.selectControl.activate())&&this.handlers.keyboard.activate()&&OpenLayers.Control.prototype.activate.apply(this,arguments));},deactivate:function(){var deactivated=false;if(OpenLayers.Control.prototype.deactivate.apply(this,arguments)){this.layer.removeFeatures(this.vertices,{silent:true});this.layer.removeFeatures(this.virtualVertices,{silent:true});this.vertices=[];this.dragControl.deactivate();var feature=this.feature;var valid=feature&&feature.geometry&&feature.layer;if(this.standalone===false){if(valid){this.selectControl.unselect.apply(this.selectControl,[feature]);}
+this.selectControl.deactivate();}else{if(valid){this.unselectFeature(feature);}}
+this.handlers.keyboard.deactivate();deactivated=true;}
+return deactivated;},beforeSelectFeature:function(feature){return this.layer.events.triggerEvent("beforefeaturemodified",{feature:feature});},selectFeature:function(feature){this.feature=feature;this.modified=false;this.resetVertices();this.dragControl.activate();this.onModificationStart(this.feature);},unselectFeature:function(feature){this.layer.removeFeatures(this.vertices,{silent:true});this.vertices=[];this.layer.destroyFeatures(this.virtualVertices,{silent:true});this.virtualVertices=[];if(this.dragHandle){this.layer.destroyFeatures([this.dragHandle],{silent:true});delete this.dragHandle;}
+if(this.radiusHandle){this.layer.destroyFeatures([this.radiusHandle],{silent:true});delete this.radiusHandle;}
+this.feature=null;this.dragControl.deactivate();this.onModificationEnd(feature);this.layer.events.triggerEvent("afterfeaturemodified",{feature:feature,modified:this.modified});this.modified=false;},dragStart:function(feature,pixel){if(feature!=this.feature&&!feature.geometry.parent&&feature!=this.dragHandle&&feature!=this.radiusHandle){if(this.standalone===false&&this.feature){this.selectControl.clickFeature.apply(this.selectControl,[this.feature]);}
+if(this.geometryTypes==null||OpenLayers.Util.indexOf(this.geometryTypes,feature.geometry.CLASS_NAME)!=-1){this.standalone||this.selectControl.clickFeature.apply(this.selectControl,[feature]);this.dragControl.overFeature.apply(this.dragControl,[feature]);this.dragControl.lastPixel=pixel;this.dragControl.handlers.drag.started=true;this.dragControl.handlers.drag.start=pixel;this.dragControl.handlers.drag.last=pixel;}}},dragVertex:function(vertex,pixel){this.modified=true;if(this.feature.geometry.CLASS_NAME=="OpenLayers.Geometry.Point"){if(this.feature!=vertex){this.feature=vertex;}
+this.layer.events.triggerEvent("vertexmodified",{vertex:vertex.geometry,feature:this.feature,pixel:pixel});}else{if(vertex._index){vertex.geometry.parent.addComponent(vertex.geometry,vertex._index);delete vertex._index;OpenLayers.Util.removeItem(this.virtualVertices,vertex);this.vertices.push(vertex);}else if(vertex==this.dragHandle){this.layer.removeFeatures(this.vertices,{silent:true});this.vertices=[];if(this.radiusHandle){this.layer.destroyFeatures([this.radiusHandle],{silent:true});this.radiusHandle=null;}}else if(vertex!==this.radiusHandle){this.layer.events.triggerEvent("vertexmodified",{vertex:vertex.geometry,feature:this.feature,pixel:pixel});}
+if(this.virtualVertices.length>0){this.layer.destroyFeatures(this.virtualVertices,{silent:true});this.virtualVertices=[];}
+this.layer.drawFeature(this.feature,this.standalone?undefined:this.selectControl.renderIntent);}
+this.layer.drawFeature(vertex);},dragComplete:function(vertex){this.resetVertices();this.setFeatureState();this.onModification(this.feature);this.layer.events.triggerEvent("featuremodified",{feature:this.feature});},setFeatureState:function(){if(this.feature.state!=OpenLayers.State.INSERT&&this.feature.state!=OpenLayers.State.DELETE){this.feature.state=OpenLayers.State.UPDATE;}},resetVertices:function(){if(this.dragControl.feature){this.dragControl.outFeature(this.dragControl.feature);}
+if(this.vertices.length>0){this.layer.removeFeatures(this.vertices,{silent:true});this.vertices=[];}
+if(this.virtualVertices.length>0){this.layer.removeFeatures(this.virtualVertices,{silent:true});this.virtualVertices=[];}
+if(this.dragHandle){this.layer.destroyFeatures([this.dragHandle],{silent:true});this.dragHandle=null;}
+if(this.radiusHandle){this.layer.destroyFeatures([this.radiusHandle],{silent:true});this.radiusHandle=null;}
+if(this.feature&&this.feature.geometry.CLASS_NAME!="OpenLayers.Geometry.Point"){if((this.mode&OpenLayers.Control.ModifyFeature.DRAG)){this.collectDragHandle();}
+if((this.mode&(OpenLayers.Control.ModifyFeature.ROTATE|OpenLayers.Control.ModifyFeature.RESIZE))){this.collectRadiusHandle();}
+if(this.mode&OpenLayers.Control.ModifyFeature.RESHAPE){if(!(this.mode&OpenLayers.Control.ModifyFeature.RESIZE)){this.collectVertices();}}}},handleKeypress:function(evt){var code=evt.keyCode;if(this.feature&&OpenLayers.Util.indexOf(this.deleteCodes,code)!=-1){var vertex=this.dragControl.feature;if(vertex&&OpenLayers.Util.indexOf(this.vertices,vertex)!=-1&&!this.dragControl.handlers.drag.dragging&&vertex.geometry.parent){vertex.geometry.parent.removeComponent(vertex.geometry);this.layer.drawFeature(this.feature,this.standalone?undefined:this.selectControl.renderIntent);this.resetVertices();this.setFeatureState();this.onModification(this.feature);this.layer.events.triggerEvent("featuremodified",{feature:this.feature});}}},collectVertices:function(){this.vertices=[];this.virtualVertices=[];var control=this;function collectComponentVertices(geometry){var i,vertex,component,len;if(geometry.CLASS_NAME=="OpenLayers.Geometry.Point"){vertex=new OpenLayers.Feature.Vector(geometry);vertex._sketch=true;control.vertices.push(vertex);}else{var numVert=geometry.components.length;if(geometry.CLASS_NAME=="OpenLayers.Geometry.LinearRing"){numVert-=1;}
+for(i=0;i<numVert;++i){component=geometry.components[i];if(component.CLASS_NAME=="OpenLayers.Geometry.Point"){vertex=new OpenLayers.Feature.Vector(component);vertex._sketch=true;control.vertices.push(vertex);}else{collectComponentVertices(component);}}
+if(geometry.CLASS_NAME!="OpenLayers.Geometry.MultiPoint"){for(i=0,len=geometry.components.length;i<len-1;++i){var prevVertex=geometry.components[i];var nextVertex=geometry.components[i+1];if(prevVertex.CLASS_NAME=="OpenLayers.Geometry.Point"&&nextVertex.CLASS_NAME=="OpenLayers.Geometry.Point"){var x=(prevVertex.x+nextVertex.x)/2;var y=(prevVertex.y+nextVertex.y)/2;var point=new OpenLayers.Feature.Vector(new OpenLayers.Geometry.Point(x,y),null,control.virtualStyle);point.geometry.parent=geometry;point._index=i+1;point._sketch=true;control.virtualVertices.push(point);}}}}}
+collectComponentVertices.call(this,this.feature.geometry);this.layer.addFeatures(this.virtualVertices,{silent:true});this.layer.addFeatures(this.vertices,{silent:true});},collectDragHandle:function(){var geometry=this.feature.geometry;var center=geometry.getBounds().getCenterLonLat();var originGeometry=new OpenLayers.Geometry.Point(center.lon,center.lat);var origin=new OpenLayers.Feature.Vector(originGeometry);originGeometry.move=function(x,y){OpenLayers.Geometry.Point.prototype.move.call(this,x,y);geometry.move(x,y);};origin._sketch=true;this.dragHandle=origin;this.layer.addFeatures([this.dragHandle],{silent:true});},collectRadiusHandle:function(){var geometry=this.feature.geometry;var bounds=geometry.getBounds();var center=bounds.getCenterLonLat();var originGeometry=new OpenLayers.Geometry.Point(center.lon,center.lat);var radiusGeometry=new OpenLayers.Geometry.Point(bounds.right,bounds.bottom);var radius=new OpenLayers.Feature.Vector(radiusGeometry);var resize=(this.mode&OpenLayers.Control.ModifyFeature.RESIZE);var reshape=(this.mode&OpenLayers.Control.ModifyFeature.RESHAPE);var rotate=(this.mode&OpenLayers.Control.ModifyFeature.ROTATE);radiusGeometry.move=function(x,y){OpenLayers.Geometry.Point.prototype.move.call(this,x,y);var dx1=this.x-originGeometry.x;var dy1=this.y-originGeometry.y;var dx0=dx1-x;var dy0=dy1-y;if(rotate){var a0=Math.atan2(dy0,dx0);var a1=Math.atan2(dy1,dx1);var angle=a1-a0;angle*=180/Math.PI;geometry.rotate(angle,originGeometry);}
+if(resize){var scale,ratio;if(reshape){scale=dy1/dy0;ratio=(dx1/dx0)/scale;}else{var l0=Math.sqrt((dx0*dx0)+(dy0*dy0));var l1=Math.sqrt((dx1*dx1)+(dy1*dy1));scale=l1/l0;}
+geometry.resize(scale,originGeometry,ratio);}};radius._sketch=true;this.radiusHandle=radius;this.layer.addFeatures([this.radiusHandle],{silent:true});},setMap:function(map){this.standalone||this.selectControl.setMap(map);this.dragControl.setMap(map);OpenLayers.Control.prototype.setMap.apply(this,arguments);},CLASS_NAME:"OpenLayers.Control.ModifyFeature"});OpenLayers.Control.ModifyFeature.RESHAPE=1;OpenLayers.Control.ModifyFeature.RESIZE=2;OpenLayers.Control.ModifyFeature.ROTATE=4;OpenLayers.Control.ModifyFeature.DRAG=8;OpenLayers.Geometry.LineString=OpenLayers.Class(OpenLayers.Geometry.Curve,{initialize:function(points){OpenLayers.Geometry.Curve.prototype.initialize.apply(this,arguments);},removeComponent:function(point){if(this.components&&(this.components.length>2)){OpenLayers.Geometry.Collection.prototype.removeComponent.apply(this,arguments);}},intersects:function(geometry){var intersect=false;var type=geometry.CLASS_NAME;if(type=="OpenLayers.Geometry.LineString"||type=="OpenLayers.Geometry.LinearRing"||type=="OpenLayers.Geometry.Point"){var segs1=this.getSortedSegments();var segs2;if(type=="OpenLayers.Geometry.Point"){segs2=[{x1:geometry.x,y1:geometry.y,x2:geometry.x,y2:geometry.y}];}else{segs2=geometry.getSortedSegments();}
+var seg1,seg1x1,seg1x2,seg1y1,seg1y2,seg2,seg2y1,seg2y2;outer:for(var i=0,len=segs1.length;i<len;++i){seg1=segs1[i];seg1x1=seg1.x1;seg1x2=seg1.x2;seg1y1=seg1.y1;seg1y2=seg1.y2;inner:for(var j=0,jlen=segs2.length;j<jlen;++j){seg2=segs2[j];if(seg2.x1>seg1x2){break;}
+if(seg2.x2<seg1x1){continue;}
+seg2y1=seg2.y1;seg2y2=seg2.y2;if(Math.min(seg2y1,seg2y2)>Math.max(seg1y1,seg1y2)){continue;}
+if(Math.max(seg2y1,seg2y2)<Math.min(seg1y1,seg1y2)){continue;}
+if(OpenLayers.Geometry.segmentsIntersect(seg1,seg2)){intersect=true;break outer;}}}}else{intersect=geometry.intersects(this);}
+return intersect;},getSortedSegments:function(){var numSeg=this.components.length-1;var segments=new Array(numSeg),point1,point2;for(var i=0;i<numSeg;++i){point1=this.components[i];point2=this.components[i+1];if(point1.x<point2.x){segments[i]={x1:point1.x,y1:point1.y,x2:point2.x,y2:point2.y};}else{segments[i]={x1:point2.x,y1:point2.y,x2:point1.x,y2:point1.y};}}
+function byX1(seg1,seg2){return seg1.x1-seg2.x1;}
+return segments.sort(byX1);},splitWithSegment:function(seg,options){var edge=!(options&&options.edge===false);var tolerance=options&&options.tolerance;var lines=[];var verts=this.getVertices();var points=[];var intersections=[];var split=false;var vert1,vert2,point;var node,vertex,target;var interOptions={point:true,tolerance:tolerance};var result=null;for(var i=0,stop=verts.length-2;i<=stop;++i){vert1=verts[i];points.push(vert1.clone());vert2=verts[i+1];target={x1:vert1.x,y1:vert1.y,x2:vert2.x,y2:vert2.y};point=OpenLayers.Geometry.segmentsIntersect(seg,target,interOptions);if(point instanceof OpenLayers.Geometry.Point){if((point.x===seg.x1&&point.y===seg.y1)||(point.x===seg.x2&&point.y===seg.y2)||point.equals(vert1)||point.equals(vert2)){vertex=true;}else{vertex=false;}
+if(vertex||edge){if(!point.equals(intersections[intersections.length-1])){intersections.push(point.clone());}
+if(i===0){if(point.equals(vert1)){continue;}}
+if(point.equals(vert2)){continue;}
+split=true;if(!point.equals(vert1)){points.push(point);}
+lines.push(new OpenLayers.Geometry.LineString(points));points=[point.clone()];}}}
+if(split){points.push(vert2.clone());lines.push(new OpenLayers.Geometry.LineString(points));}
+if(intersections.length>0){var xDir=seg.x1<seg.x2?1:-1;var yDir=seg.y1<seg.y2?1:-1;result={lines:lines,points:intersections.sort(function(p1,p2){return(xDir*p1.x-xDir*p2.x)||(yDir*p1.y-yDir*p2.y);})};}
+return result;},split:function(target,options){var results=null;var mutual=options&&options.mutual;var sourceSplit,targetSplit,sourceParts,targetParts;if(target instanceof OpenLayers.Geometry.LineString){var verts=this.getVertices();var vert1,vert2,seg,splits,lines,point;var points=[];sourceParts=[];for(var i=0,stop=verts.length-2;i<=stop;++i){vert1=verts[i];vert2=verts[i+1];seg={x1:vert1.x,y1:vert1.y,x2:vert2.x,y2:vert2.y};targetParts=targetParts||[target];if(mutual){points.push(vert1.clone());}
+for(var j=0;j<targetParts.length;++j){splits=targetParts[j].splitWithSegment(seg,options);if(splits){lines=splits.lines;if(lines.length>0){lines.unshift(j,1);Array.prototype.splice.apply(targetParts,lines);j+=lines.length-2;}
+if(mutual){for(var k=0,len=splits.points.length;k<len;++k){point=splits.points[k];if(!point.equals(vert1)){points.push(point);sourceParts.push(new OpenLayers.Geometry.LineString(points));if(point.equals(vert2)){points=[];}else{points=[point.clone()];}}}}}}}
+if(mutual&&sourceParts.length>0&&points.length>0){points.push(vert2.clone());sourceParts.push(new OpenLayers.Geometry.LineString(points));}}else{results=target.splitWith(this,options);}
+if(targetParts&&targetParts.length>1){targetSplit=true;}else{targetParts=[];}
+if(sourceParts&&sourceParts.length>1){sourceSplit=true;}else{sourceParts=[];}
+if(targetSplit||sourceSplit){if(mutual){results=[sourceParts,targetParts];}else{results=targetParts;}}
+return results;},splitWith:function(geometry,options){return geometry.split(this,options);},getVertices:function(nodes){var vertices;if(nodes===true){vertices=[this.components[0],this.components[this.components.length-1]];}else if(nodes===false){vertices=this.components.slice(1,this.components.length-1);}else{vertices=this.components.slice();}
+return vertices;},distanceTo:function(geometry,options){var edge=!(options&&options.edge===false);var details=edge&&options&&options.details;var result,best={};var min=Number.POSITIVE_INFINITY;if(geometry instanceof OpenLayers.Geometry.Point){var segs=this.getSortedSegments();var x=geometry.x;var y=geometry.y;var seg;for(var i=0,len=segs.length;i<len;++i){seg=segs[i];result=OpenLayers.Geometry.distanceToSegment(geometry,seg);if(result.distance<min){min=result.distance;best=result;if(min===0){break;}}else{if(seg.x2>x&&((y>seg.y1&&y<seg.y2)||(y<seg.y1&&y>seg.y2))){break;}}}
+if(details){best={distance:best.distance,x0:best.x,y0:best.y,x1:x,y1:y};}else{best=best.distance;}}else if(geometry instanceof OpenLayers.Geometry.LineString){var segs0=this.getSortedSegments();var segs1=geometry.getSortedSegments();var seg0,seg1,intersection,x0,y0;var len1=segs1.length;var interOptions={point:true};outer:for(var i=0,len=segs0.length;i<len;++i){seg0=segs0[i];x0=seg0.x1;y0=seg0.y1;for(var j=0;j<len1;++j){seg1=segs1[j];intersection=OpenLayers.Geometry.segmentsIntersect(seg0,seg1,interOptions);if(intersection){min=0;best={distance:0,x0:intersection.x,y0:intersection.y,x1:intersection.x,y1:intersection.y};break outer;}else{result=OpenLayers.Geometry.distanceToSegment({x:x0,y:y0},seg1);if(result.distance<min){min=result.distance;best={distance:min,x0:x0,y0:y0,x1:result.x,y1:result.y};}}}}
+if(!details){best=best.distance;}
+if(min!==0){if(seg0){result=geometry.distanceTo(new OpenLayers.Geometry.Point(seg0.x2,seg0.y2),options);var dist=details?result.distance:result;if(dist<min){if(details){best={distance:min,x0:result.x1,y0:result.y1,x1:result.x0,y1:result.y0};}else{best=dist;}}}}}else{best=geometry.distanceTo(this,options);if(details){best={distance:best.distance,x0:best.x1,y0:best.y1,x1:best.x0,y1:best.y0};}}
+return best;},CLASS_NAME:"OpenLayers.Geometry.LineString"});OpenLayers.Control.TransformFeature=OpenLayers.Class(OpenLayers.Control,{EVENT_TYPES:["beforesetfeature","setfeature","beforetransform","transform","transformcomplete"],geometryTypes:null,layer:null,preserveAspectRatio:false,rotate:true,feature:null,renderIntent:"temporary",rotationHandleSymbolizer:null,box:null,center:null,scale:1,ratio:1,rotation:0,handles:null,rotationHandles:null,dragControl:null,initialize:function(layer,options){this.EVENT_TYPES=OpenLayers.Control.TransformFeature.prototype.EVENT_TYPES.concat(OpenLayers.Control.prototype.EVENT_TYPES);OpenLayers.Control.prototype.initialize.apply(this,[options]);this.layer=layer;if(!this.rotationHandleSymbolizer){this.rotationHandleSymbolizer={stroke:false,pointRadius:10,fillOpacity:0,cursor:"pointer"};}
+this.createBox();this.createControl();},activate:function(){var activated=false;if(OpenLayers.Control.prototype.activate.apply(this,arguments)){this.dragControl.activate();this.layer.addFeatures([this.box]);this.rotate&&this.layer.addFeatures(this.rotationHandles);this.layer.addFeatures(this.handles);activated=true;}
+return activated;},deactivate:function(){var deactivated=false;if(OpenLayers.Control.prototype.deactivate.apply(this,arguments)){this.layer.removeFeatures(this.handles);this.rotate&&this.layer.removeFeatures(this.rotationHandles);this.layer.removeFeatures([this.box]);this.dragControl.deactivate();deactivated=true;}
+return deactivated;},setMap:function(map){this.dragControl.setMap(map);OpenLayers.Control.prototype.setMap.apply(this,arguments);},setFeature:function(feature,initialParams){initialParams=OpenLayers.Util.applyDefaults(initialParams,{rotation:0,scale:1,ratio:1});var evt={feature:feature};var oldRotation=this.rotation;var oldCenter=this.center;OpenLayers.Util.extend(this,initialParams);if(this.events.triggerEvent("beforesetfeature",evt)===false){return;}
+this.feature=feature;this.activate();this._setfeature=true;var featureBounds=this.feature.geometry.getBounds();this.box.move(featureBounds.getCenterLonLat());this.box.geometry.rotate(-oldRotation,oldCenter);this._angle=0;var ll;if(this.rotation){var geom=feature.geometry.clone();geom.rotate(-this.rotation,this.center);var box=new OpenLayers.Feature.Vector(geom.getBounds().toGeometry());box.geometry.rotate(this.rotation,this.center);this.box.geometry.rotate(this.rotation,this.center);this.box.move(box.geometry.getBounds().getCenterLonLat());var llGeom=box.geometry.components[0].components[0];ll=llGeom.getBounds().getCenterLonLat();}else{ll=new OpenLayers.LonLat(featureBounds.left,featureBounds.bottom);}
+this.handles[0].move(ll);delete this._setfeature;this.events.triggerEvent("setfeature",evt);},createBox:function(){var control=this;this.center=new OpenLayers.Geometry.Point(0,0);var box=new OpenLayers.Feature.Vector(new OpenLayers.Geometry.LineString([new OpenLayers.Geometry.Point(-1,-1),new OpenLayers.Geometry.Point(0,-1),new OpenLayers.Geometry.Point(1,-1),new OpenLayers.Geometry.Point(1,0),new OpenLayers.Geometry.Point(1,1),new OpenLayers.Geometry.Point(0,1),new OpenLayers.Geometry.Point(-1,1),new OpenLayers.Geometry.Point(-1,0),new OpenLayers.Geometry.Point(-1,-1)]),null,typeof this.renderIntent=="string"?null:this.renderIntent);box.geometry.move=function(x,y){control._moving=true;OpenLayers.Geometry.LineString.prototype.move.apply(this,arguments);control.center.move(x,y);delete control._moving;};var vertexMoveFn=function(x,y){OpenLayers.Geometry.Point.prototype.move.apply(this,arguments);this._rotationHandle&&this._rotationHandle.geometry.move(x,y);this._handle.geometry.move(x,y);};var vertexResizeFn=function(scale,center,ratio){OpenLayers.Geometry.Point.prototype.resize.apply(this,arguments);this._rotationHandle&&this._rotationHandle.geometry.resize(scale,center,ratio);this._handle.geometry.resize(scale,center,ratio);};var vertexRotateFn=function(angle,center){OpenLayers.Geometry.Point.prototype.rotate.apply(this,arguments);this._rotationHandle&&this._rotationHandle.geometry.rotate(angle,center);this._handle.geometry.rotate(angle,center);};var handleMoveFn=function(x,y){var oldX=this.x,oldY=this.y;OpenLayers.Geometry.Point.prototype.move.call(this,x,y);if(control._moving){return;}
+var evt=control.dragControl.handlers.drag.evt;var preserveAspectRatio=!control._setfeature&&control.preserveAspectRatio;var reshape=!preserveAspectRatio&&!(evt&&evt.shiftKey);var oldGeom=new OpenLayers.Geometry.Point(oldX,oldY);var centerGeometry=control.center;this.rotate(-control.rotation,centerGeometry);oldGeom.rotate(-control.rotation,centerGeometry);var dx1=this.x-centerGeometry.x;var dy1=this.y-centerGeometry.y;var dx0=dx1-(this.x-oldGeom.x);var dy0=dy1-(this.y-oldGeom.y);this.x=oldX;this.y=oldY;var scale,ratio=1;if(reshape){scale=Math.abs(dy0)<0.00001?1:dy1/dy0;ratio=(Math.abs(dx0)<0.00001?1:(dx1/dx0))/scale;}else{var l0=Math.sqrt((dx0*dx0)+(dy0*dy0));var l1=Math.sqrt((dx1*dx1)+(dy1*dy1));scale=l1/l0;}
+control._moving=true;control.box.geometry.rotate(-control.rotation,centerGeometry);delete control._moving;control.box.geometry.resize(scale,centerGeometry,ratio);control.box.geometry.rotate(control.rotation,centerGeometry);control.transformFeature({scale:scale,ratio:ratio});};var rotationHandleMoveFn=function(x,y){var oldX=this.x,oldY=this.y;OpenLayers.Geometry.Point.prototype.move.call(this,x,y);if(control._moving){return;}
+var evt=control.dragControl.handlers.drag.evt;var constrain=(evt&&evt.shiftKey)?45:1;var centerGeometry=control.center;var dx1=this.x-centerGeometry.x;var dy1=this.y-centerGeometry.y;var dx0=dx1-x;var dy0=dy1-y;this.x=oldX;this.y=oldY;var a0=Math.atan2(dy0,dx0);var a1=Math.atan2(dy1,dx1);var angle=a1-a0;angle*=180/Math.PI;control._angle=(control._angle+angle)%360;var diff=control.rotation%constrain;if(Math.abs(control._angle)>=constrain||diff!==0){angle=Math.round(control._angle/constrain)*constrain-
+diff;control._angle=0;control.box.geometry.rotate(angle,centerGeometry);control.transformFeature({rotation:angle});}};var handles=new Array(8);var rotationHandles=new Array(4);var geom,handle,rotationHandle;for(var i=0;i<8;++i){geom=box.geometry.components[i];handle=new OpenLayers.Feature.Vector(geom.clone(),null,typeof this.renderIntent=="string"?null:this.renderIntent);if(i%2==0){rotationHandle=new OpenLayers.Feature.Vector(geom.clone(),null,typeof this.rotationHandleSymbolizer=="string"?null:this.rotationHandleSymbolizer);rotationHandle.geometry.move=rotationHandleMoveFn;geom._rotationHandle=rotationHandle;rotationHandles[i/2]=rotationHandle;}
+geom.move=vertexMoveFn;geom.resize=vertexResizeFn;geom.rotate=vertexRotateFn;handle.geometry.move=handleMoveFn;geom._handle=handle;handles[i]=handle;}
+this.box=box;this.rotationHandles=rotationHandles;this.handles=handles;},createControl:function(){var control=this;this.dragControl=new OpenLayers.Control.DragFeature(this.layer,{documentDrag:true,moveFeature:function(pixel){if(this.feature===control.feature){this.feature=control.box;}
+OpenLayers.Control.DragFeature.prototype.moveFeature.apply(this,arguments);},onDrag:function(feature,pixel){if(feature===control.box){control.transformFeature({center:control.center});control.drawHandles();}},onStart:function(feature,pixel){var eligible=!control.geometryTypes||OpenLayers.Util.indexOf(control.geometryTypes,feature.geometry.CLASS_NAME)!==-1;var i=OpenLayers.Util.indexOf(control.handles,feature);i+=OpenLayers.Util.indexOf(control.rotationHandles,feature);if(feature!==control.feature&&feature!==control.box&&i==-2&&eligible){control.setFeature(feature);}},onComplete:function(feature,pixel){control.events.triggerEvent("transformcomplete",{feature:control.feature});}});},drawHandles:function(){var layer=this.layer;for(var i=0;i<8;++i){if(this.rotate&&i%2===0){layer.drawFeature(this.rotationHandles[i/2],this.rotationHandleSymbolizer);}
+layer.drawFeature(this.handles[i],this.renderIntent);}},transformFeature:function(mods){if(!this._setfeature){this.scale*=(mods.scale||1);this.ratio*=(mods.ratio||1);var oldRotation=this.rotation;this.rotation=(this.rotation+(mods.rotation||0))%360;if(this.events.triggerEvent("beforetransform",mods)!==false){var feature=this.feature;var geom=feature.geometry;var center=this.center;geom.rotate(-oldRotation,center);if(mods.scale||mods.ratio){geom.resize(mods.scale,center,mods.ratio);}else if(mods.center){feature.move(mods.center.getBounds().getCenterLonLat());}
+geom.rotate(this.rotation,center);this.layer.drawFeature(feature);feature.toState(OpenLayers.State.UPDATE);this.events.triggerEvent("transform",mods);}}
+this.layer.drawFeature(this.box,this.renderIntent);this.drawHandles();},destroy:function(){var geom;for(var i=0;i<8;++i){geom=this.box.geometry.components[i];geom._handle.destroy();geom._handle=null;geom._rotationHandle&&geom._rotationHandle.destroy();geom._rotationHandle=null;}
+this.box.destroy();this.box=null;this.layer=null;this.dragControl.destroy();OpenLayers.Control.prototype.destroy.apply(this,arguments);},CLASS_NAME:"OpenLayers.Control.TransformFeature"});OpenLayers.Format.GPX=OpenLayers.Class(OpenLayers.Format.XML,{extractWaypoints:true,extractTracks:true,extractRoutes:true,extractAttributes:true,initialize:function(options){this.externalProjection=new OpenLayers.Projection("EPSG:4326");OpenLayers.Format.XML.prototype.initialize.apply(this,[options]);},read:function(doc){if(typeof doc=="string"){doc=OpenLayers.Format.XML.prototype.read.apply(this,[doc]);}
+var features=[];if(this.extractTracks){var tracks=doc.getElementsByTagName("trk");for(var i=0,len=tracks.length;i<len;i++){var attrs={};if(this.extractAttributes){attrs=this.parseAttributes(tracks[i]);}
+var segs=this.getElementsByTagNameNS(tracks[i],tracks[i].namespaceURI,"trkseg");for(var j=0,seglen=segs.length;j<seglen;j++){var track=this.extractSegment(segs[j],"trkpt");features.push(new OpenLayers.Feature.Vector(track,attrs));}}}
+if(this.extractRoutes){var routes=doc.getElementsByTagName("rte");for(var k=0,klen=routes.length;k<klen;k++){var attrs={};if(this.extractAttributes){attrs=this.parseAttributes(routes[k]);}
+var route=this.extractSegment(routes[k],"rtept");features.push(new OpenLayers.Feature.Vector(route,attrs));}}
+if(this.extractWaypoints){var waypoints=doc.getElementsByTagName("wpt");for(var l=0,len=waypoints.length;l<len;l++){var attrs={};if(this.extractAttributes){attrs=this.parseAttributes(waypoints[l]);}
+var wpt=new OpenLayers.Geometry.Point(waypoints[l].getAttribute("lon"),waypoints[l].getAttribute("lat"));features.push(new OpenLayers.Feature.Vector(wpt,attrs));}}
+if(this.internalProjection&&this.externalProjection){for(var g=0,featLength=features.length;g<featLength;g++){features[g].geometry.transform(this.externalProjection,this.internalProjection);}}
+return features;},extractSegment:function(segment,segmentType){var points=this.getElementsByTagNameNS(segment,segment.namespaceURI,segmentType);var point_features=[];for(var i=0,len=points.length;i<len;i++){point_features.push(new OpenLayers.Geometry.Point(points[i].getAttribute("lon"),points[i].getAttribute("lat")));}
+return new OpenLayers.Geometry.LineString(point_features);},parseAttributes:function(node){var attributes={};var attrNode=node.firstChild;while(attrNode){if(attrNode.nodeType==1){var value=attrNode.firstChild;if(value.nodeType==3||value.nodeType==4){name=(attrNode.prefix)?attrNode.nodeName.split(":")[1]:attrNode.nodeName;if(name!="trkseg"&&name!="rtept"){attributes[name]=value.nodeValue;}}}
+attrNode=attrNode.nextSibling;}
+return attributes;},CLASS_NAME:"OpenLayers.Format.GPX"});OpenLayers.Geometry.LinearRing=OpenLayers.Class(OpenLayers.Geometry.LineString,{componentTypes:["OpenLayers.Geometry.Point"],initialize:function(points){OpenLayers.Geometry.LineString.prototype.initialize.apply(this,arguments);},addComponent:function(point,index){var added=false;var lastPoint=this.components.pop();if(index!=null||!point.equals(lastPoint)){added=OpenLayers.Geometry.Collection.prototype.addComponent.apply(this,arguments);}
+var firstPoint=this.components[0];OpenLayers.Geometry.Collection.prototype.addComponent.apply(this,[firstPoint]);return added;},removeComponent:function(point){if(this.components.length>4){this.components.pop();OpenLayers.Geometry.Collection.prototype.removeComponent.apply(this,arguments);var firstPoint=this.components[0];OpenLayers.Geometry.Collection.prototype.addComponent.apply(this,[firstPoint]);}},move:function(x,y){for(var i=0,len=this.components.length;i<len-1;i++){this.components[i].move(x,y);}},rotate:function(angle,origin){for(var i=0,len=this.components.length;i<len-1;++i){this.components[i].rotate(angle,origin);}},resize:function(scale,origin,ratio){for(var i=0,len=this.components.length;i<len-1;++i){this.components[i].resize(scale,origin,ratio);}
+return this;},transform:function(source,dest){if(source&&dest){for(var i=0,len=this.components.length;i<len-1;i++){var component=this.components[i];component.transform(source,dest);}
+this.bounds=null;}
+return this;},getCentroid:function(){if(this.components&&(this.components.length>2)){var sumX=0.0;var sumY=0.0;for(var i=0;i<this.components.length-1;i++){var b=this.components[i];var c=this.components[i+1];sumX+=(b.x+c.x)*(b.x*c.y-c.x*b.y);sumY+=(b.y+c.y)*(b.x*c.y-c.x*b.y);}
+var area=-1*this.getArea();var x=sumX/(6*area);var y=sumY/(6*area);return new OpenLayers.Geometry.Point(x,y);}else{return null;}},getArea:function(){var area=0.0;if(this.components&&(this.components.length>2)){var sum=0.0;for(var i=0,len=this.components.length;i<len-1;i++){var b=this.components[i];var c=this.components[i+1];sum+=(b.x+c.x)*(c.y-b.y);}
+area=-sum/2.0;}
+return area;},getGeodesicArea:function(projection){var ring=this;if(projection){var gg=new OpenLayers.Projection("EPSG:4326");if(!gg.equals(projection)){ring=this.clone().transform(projection,gg);}}
+var area=0.0;var len=ring.components&&ring.components.length;if(len>2){var p1,p2;for(var i=0;i<len-1;i++){p1=ring.components[i];p2=ring.components[i+1];area+=OpenLayers.Util.rad(p2.x-p1.x)*(2+Math.sin(OpenLayers.Util.rad(p1.y))+
+Math.sin(OpenLayers.Util.rad(p2.y)));}
+area=area*6378137.0*6378137.0/2.0;}
+return area;},containsPoint:function(point){var approx=OpenLayers.Number.limitSigDigs;var digs=14;var px=approx(point.x,digs);var py=approx(point.y,digs);function getX(y,x1,y1,x2,y2){return(((x1-x2)*y)+((x2*y1)-(x1*y2)))/(y1-y2);}
+var numSeg=this.components.length-1;var start,end,x1,y1,x2,y2,cx,cy;var crosses=0;for(var i=0;i<numSeg;++i){start=this.components[i];x1=approx(start.x,digs);y1=approx(start.y,digs);end=this.components[i+1];x2=approx(end.x,digs);y2=approx(end.y,digs);if(y1==y2){if(py==y1){if(x1<=x2&&(px>=x1&&px<=x2)||x1>=x2&&(px<=x1&&px>=x2)){crosses=-1;break;}}
+continue;}
+cx=approx(getX(py,x1,y1,x2,y2),digs);if(cx==px){if(y1<y2&&(py>=y1&&py<=y2)||y1>y2&&(py<=y1&&py>=y2)){crosses=-1;break;}}
+if(cx<=px){continue;}
+if(x1!=x2&&(cx<Math.min(x1,x2)||cx>Math.max(x1,x2))){continue;}
+if(y1<y2&&(py>=y1&&py<y2)||y1>y2&&(py<y1&&py>=y2)){++crosses;}}
+var contained=(crosses==-1)?1:!!(crosses&1);return contained;},intersects:function(geometry){var intersect=false;if(geometry.CLASS_NAME=="OpenLayers.Geometry.Point"){intersect=this.containsPoint(geometry);}else if(geometry.CLASS_NAME=="OpenLayers.Geometry.LineString"){intersect=geometry.intersects(this);}else if(geometry.CLASS_NAME=="OpenLayers.Geometry.LinearRing"){intersect=OpenLayers.Geometry.LineString.prototype.intersects.apply(this,[geometry]);}else{for(var i=0,len=geometry.components.length;i<len;++i){intersect=geometry.components[i].intersects(this);if(intersect){break;}}}
+return intersect;},getVertices:function(nodes){return(nodes===true)?[]:this.components.slice(0,this.components.length-1);},CLASS_NAME:"OpenLayers.Geometry.LinearRing"});OpenLayers.Geometry.MultiLineString=OpenLayers.Class(OpenLayers.Geometry.Collection,{componentTypes:["OpenLayers.Geometry.LineString"],initialize:function(components){OpenLayers.Geometry.Collection.prototype.initialize.apply(this,arguments);},split:function(geometry,options){var results=null;var mutual=options&&options.mutual;var splits,sourceLine,sourceLines,sourceSplit,targetSplit;var sourceParts=[];var targetParts=[geometry];for(var i=0,len=this.components.length;i<len;++i){sourceLine=this.components[i];sourceSplit=false;for(var j=0;j<targetParts.length;++j){splits=sourceLine.split(targetParts[j],options);if(splits){if(mutual){sourceLines=splits[0];for(var k=0,klen=sourceLines.length;k<klen;++k){if(k===0&&sourceParts.length){sourceParts[sourceParts.length-1].addComponent(sourceLines[k]);}else{sourceParts.push(new OpenLayers.Geometry.MultiLineString([sourceLines[k]]));}}
+sourceSplit=true;splits=splits[1];}
+if(splits.length){splits.unshift(j,1);Array.prototype.splice.apply(targetParts,splits);break;}}}
+if(!sourceSplit){if(sourceParts.length){sourceParts[sourceParts.length-1].addComponent(sourceLine.clone());}else{sourceParts=[new OpenLayers.Geometry.MultiLineString(sourceLine.clone())];}}}
+if(sourceParts&&sourceParts.length>1){sourceSplit=true;}else{sourceParts=[];}
+if(targetParts&&targetParts.length>1){targetSplit=true;}else{targetParts=[];}
+if(sourceSplit||targetSplit){if(mutual){results=[sourceParts,targetParts];}else{results=targetParts;}}
+return results;},splitWith:function(geometry,options){var results=null;var mutual=options&&options.mutual;var splits,targetLine,sourceLines,sourceSplit,targetSplit,sourceParts,targetParts;if(geometry instanceof OpenLayers.Geometry.LineString){targetParts=[];sourceParts=[geometry];for(var i=0,len=this.components.length;i<len;++i){targetSplit=false;targetLine=this.components[i];for(var j=0;j<sourceParts.length;++j){splits=sourceParts[j].split(targetLine,options);if(splits){if(mutual){sourceLines=splits[0];if(sourceLines.length){sourceLines.unshift(j,1);Array.prototype.splice.apply(sourceParts,sourceLines);j+=sourceLines.length-2;}
+splits=splits[1];if(splits.length===0){splits=[targetLine.clone()];}}
+for(var k=0,klen=splits.length;k<klen;++k){if(k===0&&targetParts.length){targetParts[targetParts.length-1].addComponent(splits[k]);}else{targetParts.push(new OpenLayers.Geometry.MultiLineString([splits[k]]));}}
+targetSplit=true;}}
+if(!targetSplit){if(targetParts.length){targetParts[targetParts.length-1].addComponent(targetLine.clone());}else{targetParts=[new OpenLayers.Geometry.MultiLineString([targetLine.clone()])];}}}}else{results=geometry.split(this);}
+if(sourceParts&&sourceParts.length>1){sourceSplit=true;}else{sourceParts=[];}
+if(targetParts&&targetParts.length>1){targetSplit=true;}else{targetParts=[];}
+if(sourceSplit||targetSplit){if(mutual){results=[sourceParts,targetParts];}else{results=targetParts;}}
+return results;},CLASS_NAME:"OpenLayers.Geometry.MultiLineString"});OpenLayers.Handler.Path=OpenLayers.Class(OpenLayers.Handler.Point,{line:null,freehand:false,freehandToggle:'shiftKey',initialize:function(control,callbacks,options){OpenLayers.Handler.Point.prototype.initialize.apply(this,arguments);},createFeature:function(pixel){var lonlat=this.control.map.getLonLatFromPixel(pixel);this.point=new OpenLayers.Feature.Vector(new OpenLayers.Geometry.Point(lonlat.lon,lonlat.lat));this.line=new OpenLayers.Feature.Vector(new OpenLayers.Geometry.LineString([this.point.geometry]));this.callback("create",[this.point.geometry,this.getSketch()]);this.point.geometry.clearBounds();this.layer.addFeatures([this.line,this.point],{silent:true});},destroyFeature:function(){OpenLayers.Handler.Point.prototype.destroyFeature.apply(this);this.line=null;},removePoint:function(){if(this.point){this.layer.removeFeatures([this.point]);}},addPoint:function(pixel){this.layer.removeFeatures([this.point]);var lonlat=this.control.map.getLonLatFromPixel(pixel);this.point=new OpenLayers.Feature.Vector(new OpenLayers.Geometry.Point(lonlat.lon,lonlat.lat));this.line.geometry.addComponent(this.point.geometry,this.line.geometry.components.length);this.callback("point",[this.point.geometry,this.getGeometry()]);this.callback("modify",[this.point.geometry,this.getSketch()]);this.drawFeature();},freehandMode:function(evt){return(this.freehandToggle&&evt[this.freehandToggle])?!this.freehand:this.freehand;},modifyFeature:function(pixel){var lonlat=this.control.map.getLonLatFromPixel(pixel);this.point.geometry.x=lonlat.lon;this.point.geometry.y=lonlat.lat;this.callback("modify",[this.point.geometry,this.getSketch()]);this.point.geometry.clearBounds();this.drawFeature();},drawFeature:function(){this.layer.drawFeature(this.line,this.style);this.layer.drawFeature(this.point,this.style);},getSketch:function(){return this.line;},getGeometry:function(){var geometry=this.line&&this.line.geometry;if(geometry&&this.multi){geometry=new OpenLayers.Geometry.MultiLineString([geometry]);}
+return geometry;},mousedown:function(evt){if(this.lastDown&&this.lastDown.equals(evt.xy)){return false;}
+if(this.lastDown==null){if(this.persist){this.destroyFeature();}
+this.createFeature(evt.xy);}else if((this.lastUp==null)||!this.lastUp.equals(evt.xy)){this.addPoint(evt.xy);}
+this.mouseDown=true;this.lastDown=evt.xy;this.drawing=true;return false;},mousemove:function(evt){if(this.drawing){if(this.mouseDown&&this.freehandMode(evt)){this.addPoint(evt.xy);}else{this.modifyFeature(evt.xy);}}
+return true;},mouseup:function(evt){this.mouseDown=false;if(this.drawing){if(this.freehandMode(evt)){this.removePoint();this.finalize();}else{if(this.lastUp==null){this.addPoint(evt.xy);}
+this.lastUp=evt.xy;}
+return false;}
+return true;},dblclick:function(evt){if(!this.freehandMode(evt)){var index=this.line.geometry.components.length-1;this.line.geometry.removeComponent(this.line.geometry.components[index]);this.removePoint();this.finalize();}
+return false;},CLASS_NAME:"OpenLayers.Handler.Path"});OpenLayers.Control.Split=OpenLayers.Class(OpenLayers.Control,{EVENT_TYPES:["beforesplit","split","aftersplit"],layer:null,source:null,sourceOptions:null,tolerance:null,edge:true,deferDelete:false,mutual:true,targetFilter:null,sourceFilter:null,handler:null,initialize:function(options){Array.prototype.push.apply(this.EVENT_TYPES,OpenLayers.Control.prototype.EVENT_TYPES);OpenLayers.Control.prototype.initialize.apply(this,[options]);this.options=options||{};if(this.options.source){this.setSource(this.options.source);}},setSource:function(layer){if(this.active){this.deactivate();if(this.handler){this.handler.destroy();delete this.handler;}
+this.source=layer;this.activate();}else{this.source=layer;}},activate:function(){var activated=OpenLayers.Control.prototype.activate.call(this);if(activated){if(!this.source){if(!this.handler){this.handler=new OpenLayers.Handler.Path(this,{done:function(geometry){this.onSketchComplete({feature:new OpenLayers.Feature.Vector(geometry)});}},{layerOptions:this.sourceOptions});}
+this.handler.activate();}else if(this.source.events){this.source.events.on({sketchcomplete:this.onSketchComplete,afterfeaturemodified:this.afterFeatureModified,scope:this});}}
+return activated;},deactivate:function(){var deactivated=OpenLayers.Control.prototype.deactivate.call(this);if(deactivated){if(this.source&&this.source.events){this.layer.events.un({sketchcomplete:this.onSketchComplete,afterfeaturemodified:this.afterFeatureModified,scope:this});}}
+return deactivated;},onSketchComplete:function(event){this.feature=null;return!this.considerSplit(event.feature);},afterFeatureModified:function(event){if(event.modified){var feature=event.feature;if(feature.geometry instanceof OpenLayers.Geometry.LineString||feature.geometry instanceof OpenLayers.Geometry.MultiLineString){this.feature=event.feature;this.considerSplit(event.feature);}}},removeByGeometry:function(features,geometry){for(var i=0,len=features.length;i<len;++i){if(features[i].geometry===geometry){features.splice(i,1);break;}}},isEligible:function(target){return(target.state!==OpenLayers.State.DELETE)&&(target.geometry instanceof OpenLayers.Geometry.LineString||target.geometry instanceof OpenLayers.Geometry.MultiLineString)&&(this.feature!==target)&&(!this.targetFilter||this.targetFilter.evaluate(target.attributes));},considerSplit:function(feature){var sourceSplit=false;var targetSplit=false;if(!this.sourceFilter||this.sourceFilter.evaluate(feature.attributes)){var features=this.layer&&this.layer.features||[];var target,results,proceed;var additions=[],removals=[];var mutual=(this.layer===this.source)&&this.mutual;var options={edge:this.edge,tolerance:this.tolerance,mutual:mutual};var sourceParts=[feature.geometry];var targetFeature,targetParts;var source,parts;for(var i=0,len=features.length;i<len;++i){targetFeature=features[i];if(this.isEligible(targetFeature)){targetParts=[targetFeature.geometry];for(var j=0;j<sourceParts.length;++j){source=sourceParts[j];for(var k=0;k<targetParts.length;++k){target=targetParts[k];if(source.getBounds().intersectsBounds(target.getBounds())){results=source.split(target,options);if(results){proceed=this.events.triggerEvent("beforesplit",{source:feature,target:targetFeature});if(proceed!==false){if(mutual){parts=results[0];if(parts.length>1){parts.unshift(j,1);Array.prototype.splice.apply(sourceParts,parts);j+=parts.length-3;}
+results=results[1];}
+if(results.length>1){results.unshift(k,1);Array.prototype.splice.apply(targetParts,results);k+=results.length-3;}}}}}}
+if(targetParts&&targetParts.length>1){this.geomsToFeatures(targetFeature,targetParts);this.events.triggerEvent("split",{original:targetFeature,features:targetParts});Array.prototype.push.apply(additions,targetParts);removals.push(targetFeature);targetSplit=true;}}}
+if(sourceParts&&sourceParts.length>1){this.geomsToFeatures(feature,sourceParts);this.events.triggerEvent("split",{original:feature,features:sourceParts});Array.prototype.push.apply(additions,sourceParts);removals.push(feature);sourceSplit=true;}
+if(sourceSplit||targetSplit){if(this.deferDelete){var feat,destroys=[];for(var i=0,len=removals.length;i<len;++i){feat=removals[i];if(feat.state===OpenLayers.State.INSERT){destroys.push(feat);}else{feat.state=OpenLayers.State.DELETE;this.layer.drawFeature(feat);}}
+this.layer.destroyFeatures(destroys,{silent:true});for(var i=0,len=additions.length;i<len;++i){additions[i].state=OpenLayers.State.INSERT;}}else{this.layer.destroyFeatures(removals,{silent:true});}
+this.layer.addFeatures(additions,{silent:true});this.events.triggerEvent("aftersplit",{source:feature,features:additions});}}
+return sourceSplit;},geomsToFeatures:function(feature,geoms){var clone=feature.clone();delete clone.geometry;var newFeature;for(var i=0,len=geoms.length;i<len;++i){newFeature=clone.clone();newFeature.geometry=geoms[i];newFeature.state=OpenLayers.State.INSERT;geoms[i]=newFeature;}},destroy:function(){if(this.active){this.deactivate();}
+OpenLayers.Control.prototype.destroy.call(this);},CLASS_NAME:"OpenLayers.Control.Split"});OpenLayers.Geometry.Polygon=OpenLayers.Class(OpenLayers.Geometry.Collection,{componentTypes:["OpenLayers.Geometry.LinearRing"],initialize:function(components){OpenLayers.Geometry.Collection.prototype.initialize.apply(this,arguments);},getArea:function(){var area=0.0;if(this.components&&(this.components.length>0)){area+=Math.abs(this.components[0].getArea());for(var i=1,len=this.components.length;i<len;i++){area-=Math.abs(this.components[i].getArea());}}
+return area;},getGeodesicArea:function(projection){var area=0.0;if(this.components&&(this.components.length>0)){area+=Math.abs(this.components[0].getGeodesicArea(projection));for(var i=1,len=this.components.length;i<len;i++){area-=Math.abs(this.components[i].getGeodesicArea(projection));}}
+return area;},containsPoint:function(point){var numRings=this.components.length;var contained=false;if(numRings>0){contained=this.components[0].containsPoint(point);if(contained!==1){if(contained&&numRings>1){var hole;for(var i=1;i<numRings;++i){hole=this.components[i].containsPoint(point);if(hole){if(hole===1){contained=1;}else{contained=false;}
+break;}}}}}
+return contained;},intersects:function(geometry){var intersect=false;var i,len;if(geometry.CLASS_NAME=="OpenLayers.Geometry.Point"){intersect=this.containsPoint(geometry);}else if(geometry.CLASS_NAME=="OpenLayers.Geometry.LineString"||geometry.CLASS_NAME=="OpenLayers.Geometry.LinearRing"){for(i=0,len=this.components.length;i<len;++i){intersect=geometry.intersects(this.components[i]);if(intersect){break;}}
+if(!intersect){for(i=0,len=geometry.components.length;i<len;++i){intersect=this.containsPoint(geometry.components[i]);if(intersect){break;}}}}else{for(i=0,len=geometry.components.length;i<len;++i){intersect=this.intersects(geometry.components[i]);if(intersect){break;}}}
+if(!intersect&&geometry.CLASS_NAME=="OpenLayers.Geometry.Polygon"){var ring=this.components[0];for(i=0,len=ring.components.length;i<len;++i){intersect=geometry.containsPoint(ring.components[i]);if(intersect){break;}}}
+return intersect;},distanceTo:function(geometry,options){var edge=!(options&&options.edge===false);var result;if(!edge&&this.intersects(geometry)){result=0;}else{result=OpenLayers.Geometry.Collection.prototype.distanceTo.apply(this,[geometry,options]);}
+return result;},CLASS_NAME:"OpenLayers.Geometry.Polygon"});OpenLayers.Geometry.Polygon.createRegularPolygon=function(origin,radius,sides,rotation){var angle=Math.PI*((1/sides)-(1/2));if(rotation){angle+=(rotation/180)*Math.PI;}
+var rotatedAngle,x,y;var points=[];for(var i=0;i<sides;++i){rotatedAngle=angle+(i*2*Math.PI/sides);x=origin.x+(radius*Math.cos(rotatedAngle));y=origin.y+(radius*Math.sin(rotatedAngle));points.push(new OpenLayers.Geometry.Point(x,y));}
+var ring=new OpenLayers.Geometry.LinearRing(points);return new OpenLayers.Geometry.Polygon([ring]);};OpenLayers.Format.GeoRSS=OpenLayers.Class(OpenLayers.Format.XML,{rssns:"http://backend.userland.com/rss2",featureNS:"http://mapserver.gis.umn.edu/mapserver",georssns:"http://www.georss.org/georss",geons:"http://www.w3.org/2003/01/geo/wgs84_pos#",featureTitle:"Untitled",featureDescription:"No Description",gmlParser:null,xy:false,initialize:function(options){OpenLayers.Format.XML.prototype.initialize.apply(this,[options]);},createGeometryFromItem:function(item){var point=this.getElementsByTagNameNS(item,this.georssns,"point");var lat=this.getElementsByTagNameNS(item,this.geons,'lat');var lon=this.getElementsByTagNameNS(item,this.geons,'long');var line=this.getElementsByTagNameNS(item,this.georssns,"line");var polygon=this.getElementsByTagNameNS(item,this.georssns,"polygon");var where=this.getElementsByTagNameNS(item,this.georssns,"where");var box=this.getElementsByTagNameNS(item,this.georssns,"box");if(point.length>0||(lat.length>0&&lon.length>0)){var location;if(point.length>0){location=OpenLayers.String.trim(point[0].firstChild.nodeValue).split(/\s+/);if(location.length!=2){location=OpenLayers.String.trim(point[0].firstChild.nodeValue).split(/\s*,\s*/);}}else{location=[parseFloat(lat[0].firstChild.nodeValue),parseFloat(lon[0].firstChild.nodeValue)];}
+var geometry=new OpenLayers.Geometry.Point(parseFloat(location[1]),parseFloat(location[0]));}else if(line.length>0){var coords=OpenLayers.String.trim(this.concatChildValues(line[0])).split(/\s+/);var components=[];var point;for(var i=0,len=coords.length;i<len;i+=2){point=new OpenLayers.Geometry.Point(parseFloat(coords[i+1]),parseFloat(coords[i]));components.push(point);}
+geometry=new OpenLayers.Geometry.LineString(components);}else if(polygon.length>0){var coords=OpenLayers.String.trim(this.concatChildValues(polygon[0])).split(/\s+/);var components=[];var point;for(var i=0,len=coords.length;i<len;i+=2){point=new OpenLayers.Geometry.Point(parseFloat(coords[i+1]),parseFloat(coords[i]));components.push(point);}
+geometry=new OpenLayers.Geometry.Polygon([new OpenLayers.Geometry.LinearRing(components)]);}else if(where.length>0){if(!this.gmlParser){this.gmlParser=new OpenLayers.Format.GML({'xy':this.xy});}
+var feature=this.gmlParser.parseFeature(where[0]);geometry=feature.geometry;}else if(box.length>0){var coords=OpenLayers.String.trim(box[0].firstChild.nodeValue).split(/\s+/);var components=[];var point;if(coords.length>3){point=new OpenLayers.Geometry.Point(parseFloat(coords[1]),parseFloat(coords[0]));components.push(point);point=new OpenLayers.Geometry.Point(parseFloat(coords[1]),parseFloat(coords[2]));components.push(point);point=new OpenLayers.Geometry.Point(parseFloat(coords[3]),parseFloat(coords[2]));components.push(point);point=new OpenLayers.Geometry.Point(parseFloat(coords[3]),parseFloat(coords[0]));components.push(point);point=new OpenLayers.Geometry.Point(parseFloat(coords[1]),parseFloat(coords[0]));components.push(point);}
+geometry=new OpenLayers.Geometry.Polygon([new OpenLayers.Geometry.LinearRing(components)]);}
+if(geometry&&this.internalProjection&&this.externalProjection){geometry.transform(this.externalProjection,this.internalProjection);}
+return geometry;},createFeatureFromItem:function(item){var geometry=this.createGeometryFromItem(item);var title=this.getChildValue(item,"*","title",this.featureTitle);var description=this.getChildValue(item,"*","description",this.getChildValue(item,"*","content",this.getChildValue(item,"*","summary",this.featureDescription)));var link=this.getChildValue(item,"*","link");if(!link){try{link=this.getElementsByTagNameNS(item,"*","link")[0].getAttribute("href");}catch(e){link=null;}}
+var id=this.getChildValue(item,"*","id",null);var data={"title":title,"description":description,"link":link};var feature=new OpenLayers.Feature.Vector(geometry,data);feature.fid=id;return feature;},getChildValue:function(node,nsuri,name,def){var value;var eles=this.getElementsByTagNameNS(node,nsuri,name);if(eles&&eles[0]&&eles[0].firstChild&&eles[0].firstChild.nodeValue){value=eles[0].firstChild.nodeValue;}else{value=(def==undefined)?"":def;}
+return value;},read:function(doc){if(typeof doc=="string"){doc=OpenLayers.Format.XML.prototype.read.apply(this,[doc]);}
+var itemlist=null;itemlist=this.getElementsByTagNameNS(doc,'*','item');if(itemlist.length==0){itemlist=this.getElementsByTagNameNS(doc,'*','entry');}
+var numItems=itemlist.length;var features=new Array(numItems);for(var i=0;i<numItems;i++){features[i]=this.createFeatureFromItem(itemlist[i]);}
+return features;},write:function(features){var georss;if(features instanceof Array){georss=this.createElementNS(this.rssns,"rss");for(var i=0,len=features.length;i<len;i++){georss.appendChild(this.createFeatureXML(features[i]));}}else{georss=this.createFeatureXML(features);}
+return OpenLayers.Format.XML.prototype.write.apply(this,[georss]);},createFeatureXML:function(feature){var geometryNode=this.buildGeometryNode(feature.geometry);var featureNode=this.createElementNS(this.rssns,"item");var titleNode=this.createElementNS(this.rssns,"title");titleNode.appendChild(this.createTextNode(feature.attributes.title?feature.attributes.title:""));var descNode=this.createElementNS(this.rssns,"description");descNode.appendChild(this.createTextNode(feature.attributes.description?feature.attributes.description:""));featureNode.appendChild(titleNode);featureNode.appendChild(descNode);if(feature.attributes.link){var linkNode=this.createElementNS(this.rssns,"link");linkNode.appendChild(this.createTextNode(feature.attributes.link));featureNode.appendChild(linkNode);}
+for(var attr in feature.attributes){if(attr=="link"||attr=="title"||attr=="description"){continue;}
+var attrText=this.createTextNode(feature.attributes[attr]);var nodename=attr;if(attr.search(":")!=-1){nodename=attr.split(":")[1];}
+var attrContainer=this.createElementNS(this.featureNS,"feature:"+nodename);attrContainer.appendChild(attrText);featureNode.appendChild(attrContainer);}
+featureNode.appendChild(geometryNode);return featureNode;},buildGeometryNode:function(geometry){if(this.internalProjection&&this.externalProjection){geometry=geometry.clone();geometry.transform(this.internalProjection,this.externalProjection);}
+var node;if(geometry.CLASS_NAME=="OpenLayers.Geometry.Polygon"){node=this.createElementNS(this.georssns,'georss:polygon');node.appendChild(this.buildCoordinatesNode(geometry.components[0]));}
+else if(geometry.CLASS_NAME=="OpenLayers.Geometry.LineString"){node=this.createElementNS(this.georssns,'georss:line');node.appendChild(this.buildCoordinatesNode(geometry));}
+else if(geometry.CLASS_NAME=="OpenLayers.Geometry.Point"){node=this.createElementNS(this.georssns,'georss:point');node.appendChild(this.buildCoordinatesNode(geometry));}else{throw"Couldn't parse "+geometry.CLASS_NAME;}
+return node;},buildCoordinatesNode:function(geometry){var points=null;if(geometry.components){points=geometry.components;}
+var path;if(points){var numPoints=points.length;var parts=new Array(numPoints);for(var i=0;i<numPoints;i++){parts[i]=points[i].y+" "+points[i].x;}
+path=parts.join(" ");}else{path=geometry.y+" "+geometry.x;}
+return this.createTextNode(path);},CLASS_NAME:"OpenLayers.Format.GeoRSS"});OpenLayers.Format.KML=OpenLayers.Class(OpenLayers.Format.XML,{namespaces:{kml:"http://www.opengis.net/kml/2.2",gx:"http://www.google.com/kml/ext/2.2"},kmlns:"http://earth.google.com/kml/2.0",placemarksDesc:"No description available",foldersName:"OpenLayers export",foldersDesc:"Exported on "+new Date(),extractAttributes:true,extractStyles:false,extractTracks:false,trackAttributes:null,internalns:null,features:null,styles:null,styleBaseUrl:"",fetched:null,maxDepth:0,initialize:function(options){this.regExes={trimSpace:(/^\s*|\s*$/g),removeSpace:(/\s*/g),splitSpace:(/\s+/),trimComma:(/\s*,\s*/g),kmlColor:(/(\w{2})(\w{2})(\w{2})(\w{2})/),kmlIconPalette:(/root:\/\/icons\/palette-(\d+)(\.\w+)/),straightBracket:(/\$\[(.*?)\]/g)};this.externalProjection=new OpenLayers.Projection("EPSG:4326");OpenLayers.Format.XML.prototype.initialize.apply(this,[options]);},read:function(data){this.features=[];this.styles={};this.fetched={};var options={depth:0,styleBaseUrl:this.styleBaseUrl};return this.parseData(data,options);},parseData:function(data,options){if(typeof data=="string"){data=OpenLayers.Format.XML.prototype.read.apply(this,[data]);}
+var types=["Link","NetworkLink","Style","StyleMap","Placemark"];for(var i=0,len=types.length;i<len;++i){var type=types[i];var nodes=this.getElementsByTagNameNS(data,"*",type);if(nodes.length==0){continue;}
+switch(type.toLowerCase()){case"link":case"networklink":this.parseLinks(nodes,options);break;case"style":if(this.extractStyles){this.parseStyles(nodes,options);}
+break;case"stylemap":if(this.extractStyles){this.parseStyleMaps(nodes,options);}
+break;case"placemark":this.parseFeatures(nodes,options);break;}}
+return this.features;},parseLinks:function(nodes,options){if(options.depth>=this.maxDepth){return false;}
+var newOptions=OpenLayers.Util.extend({},options);newOptions.depth++;for(var i=0,len=nodes.length;i<len;i++){var href=this.parseProperty(nodes[i],"*","href");if(href&&!this.fetched[href]){this.fetched[href]=true;var data=this.fetchLink(href);if(data){this.parseData(data,newOptions);}}}},fetchLink:function(href){var request=OpenLayers.Request.GET({url:href,async:false});if(request){return request.responseText;}},parseStyles:function(nodes,options){for(var i=0,len=nodes.length;i<len;i++){var style=this.parseStyle(nodes[i]);if(style){var styleName=(options.styleBaseUrl||"")+"#"+style.id;this.styles[styleName]=style;}}},parseKmlColor:function(kmlColor){var color=null;if(kmlColor){var matches=kmlColor.match(this.regExes.kmlColor);if(matches){color={color:'#'+matches[4]+matches[3]+matches[2],opacity:parseInt(matches[1],16)/255};}}
+return color;},parseStyle:function(node){var style={};var types=["LineStyle","PolyStyle","IconStyle","BalloonStyle","LabelStyle"];var type,nodeList,geometry,parser;for(var i=0,len=types.length;i<len;++i){type=types[i];styleTypeNode=this.getElementsByTagNameNS(node,"*",type)[0];if(!styleTypeNode){continue;}
+switch(type.toLowerCase()){case"linestyle":var kmlColor=this.parseProperty(styleTypeNode,"*","color");var color=this.parseKmlColor(kmlColor);if(color){style["strokeColor"]=color.color;style["strokeOpacity"]=color.opacity;}
+var width=this.parseProperty(styleTypeNode,"*","width");if(width){style["strokeWidth"]=width;}
+break;case"polystyle":var kmlColor=this.parseProperty(styleTypeNode,"*","color");var color=this.parseKmlColor(kmlColor);if(color){style["fillOpacity"]=color.opacity;style["fillColor"]=color.color;}
+var fill=this.parseProperty(styleTypeNode,"*","fill");if(fill=="0"){style["fillColor"]="none";}
+var outline=this.parseProperty(styleTypeNode,"*","outline");if(outline=="0"){style["strokeWidth"]="0";}
+break;case"iconstyle":var scale=parseFloat(this.parseProperty(styleTypeNode,"*","scale")||1);var width=32*scale;var height=32*scale;var iconNode=this.getElementsByTagNameNS(styleTypeNode,"*","Icon")[0];if(iconNode){var href=this.parseProperty(iconNode,"*","href");if(href){var w=this.parseProperty(iconNode,"*","w");var h=this.parseProperty(iconNode,"*","h");var google="http://maps.google.com/mapfiles/kml";if(OpenLayers.String.startsWith(href,google)&&!w&&!h){w=64;h=64;scale=scale/2;}
+w=w||h;h=h||w;if(w){width=parseInt(w)*scale;}
+if(h){height=parseInt(h)*scale;}
+var matches=href.match(this.regExes.kmlIconPalette);if(matches){var palette=matches[1];var file_extension=matches[2];var x=this.parseProperty(iconNode,"*","x");var y=this.parseProperty(iconNode,"*","y");var posX=x?x/32:0;var posY=y?(7-y/32):7;var pos=posY*8+posX;href="http://maps.google.com/mapfiles/kml/pal"
++palette+"/icon"+pos+file_extension;}
+style["graphicOpacity"]=1;style["externalGraphic"]=href;}}
+var hotSpotNode=this.getElementsByTagNameNS(styleTypeNode,"*","hotSpot")[0];if(hotSpotNode){var x=parseFloat(hotSpotNode.getAttribute("x"));var y=parseFloat(hotSpotNode.getAttribute("y"));var xUnits=hotSpotNode.getAttribute("xunits");if(xUnits=="pixels"){style["graphicXOffset"]=-x*scale;}
+else if(xUnits=="insetPixels"){style["graphicXOffset"]=-width+(x*scale);}
+else if(xUnits=="fraction"){style["graphicXOffset"]=-width*x;}
+var yUnits=hotSpotNode.getAttribute("yunits");if(yUnits=="pixels"){style["graphicYOffset"]=-height+(y*scale)+1;}
+else if(yUnits=="insetPixels"){style["graphicYOffset"]=-(y*scale)+1;}
+else if(yUnits=="fraction"){style["graphicYOffset"]=-height*(1-y)+1;}}
+style["graphicWidth"]=width;style["graphicHeight"]=height;break;case"balloonstyle":var balloonStyle=OpenLayers.Util.getXmlNodeValue(styleTypeNode);if(balloonStyle){style["balloonStyle"]=balloonStyle.replace(this.regExes.straightBracket,"${$1}");}
+break;case"labelstyle":var kmlColor=this.parseProperty(styleTypeNode,"*","color");var color=this.parseKmlColor(kmlColor);if(color){style["fontColor"]=color.color;style["fontOpacity"]=color.opacity;}
+break;default:}}
+if(!style["strokeColor"]&&style["fillColor"]){style["strokeColor"]=style["fillColor"];}
+var id=node.getAttribute("id");if(id&&style){style.id=id;}
+return style;},parseStyleMaps:function(nodes,options){for(var i=0,len=nodes.length;i<len;i++){var node=nodes[i];var pairs=this.getElementsByTagNameNS(node,"*","Pair");var id=node.getAttribute("id");for(var j=0,jlen=pairs.length;j<jlen;j++){var pair=pairs[j];var key=this.parseProperty(pair,"*","key");var styleUrl=this.parseProperty(pair,"*","styleUrl");if(styleUrl&&key=="normal"){this.styles[(options.styleBaseUrl||"")+"#"+id]=this.styles[(options.styleBaseUrl||"")+styleUrl];}
+if(styleUrl&&key=="highlight"){}}}},parseFeatures:function(nodes,options){var features=[];for(var i=0,len=nodes.length;i<len;i++){var featureNode=nodes[i];var feature=this.parseFeature.apply(this,[featureNode]);if(feature){if(this.extractStyles&&feature.attributes&&feature.attributes.styleUrl){feature.style=this.getStyle(feature.attributes.styleUrl,options);}
+if(this.extractStyles){var inlineStyleNode=this.getElementsByTagNameNS(featureNode,"*","Style")[0];if(inlineStyleNode){var inlineStyle=this.parseStyle(inlineStyleNode);if(inlineStyle){feature.style=OpenLayers.Util.extend(feature.style,inlineStyle);}}}
+if(this.extractTracks){var tracks=this.getElementsByTagNameNS(featureNode,this.namespaces.gx,"Track");if(tracks&&tracks.length>0){var track=tracks[0];var container={features:[],feature:feature};this.readNode(track,container);if(container.features.length>0){features.push.apply(features,container.features);}}}else{features.push(feature);}}else{throw"Bad Placemark: "+i;}}
+this.features=this.features.concat(features);},readers:{"kml":{"when":function(node,container){container.whens.push(OpenLayers.Date.parse(this.getChildValue(node)));},"_trackPointAttribute":function(node,container){var name=node.nodeName.split(":").pop();container.attributes[name].push(this.getChildValue(node));}},"gx":{"Track":function(node,container){var obj={whens:[],points:[],angles:[]};if(this.trackAttributes){var name;obj.attributes={};for(var i=0,ii=this.trackAttributes.length;i<ii;++i){name=this.trackAttributes[i];obj.attributes[name]=[];if(!(name in this.readers.kml)){this.readers.kml[name]=this.readers.kml._trackPointAttribute;}}}
+this.readChildNodes(node,obj);if(obj.whens.length!==obj.points.length){throw new Error("gx:Track with unequal number of when ("+obj.whens.length+") and gx:coord ("+obj.points.length+") elements.");}
+var hasAngles=obj.angles.length>0;if(hasAngles&&obj.whens.length!==obj.angles.length){throw new Error("gx:Track with unequal number of when ("+obj.whens.length+") and gx:angles ("+obj.angles.length+") elements.");}
+var feature,point,angles;for(var i=0,ii=obj.whens.length;i<ii;++i){feature=container.feature.clone();feature.fid=container.feature.fid||container.feature.id;point=obj.points[i];feature.geometry=point;if("z"in point){feature.attributes.altitude=point.z;}
+if(this.internalProjection&&this.externalProjection){feature.geometry.transform(this.externalProjection,this.internalProjection);}
+if(this.trackAttributes){for(var j=0,jj=this.trackAttributes.length;j<jj;++j){feature.attributes[name]=obj.attributes[this.trackAttributes[j]][i];}}
+feature.attributes.when=obj.whens[i];feature.attributes.trackId=container.feature.id;if(hasAngles){angles=obj.angles[i];feature.attributes.heading=parseFloat(angles[0]);feature.attributes.tilt=parseFloat(angles[1]);feature.attributes.roll=parseFloat(angles[2]);}
+container.features.push(feature);}},"coord":function(node,container){var str=this.getChildValue(node);var coords=str.replace(this.regExes.trimSpace,"").split(/\s+/);var point=new OpenLayers.Geometry.Point(coords[0],coords[1]);if(coords.length>2){point.z=parseFloat(coords[2]);}
+container.points.push(point);},"angles":function(node,container){var str=this.getChildValue(node);var parts=str.replace(this.regExes.trimSpace,"").split(/\s+/);container.angles.push(parts);}}},parseFeature:function(node){var order=["MultiGeometry","Polygon","LineString","Point"];var type,nodeList,geometry,parser;for(var i=0,len=order.length;i<len;++i){type=order[i];this.internalns=node.namespaceURI?node.namespaceURI:this.kmlns;nodeList=this.getElementsByTagNameNS(node,this.internalns,type);if(nodeList.length>0){var parser=this.parseGeometry[type.toLowerCase()];if(parser){geometry=parser.apply(this,[nodeList[0]]);if(this.internalProjection&&this.externalProjection){geometry.transform(this.externalProjection,this.internalProjection);}}else{OpenLayers.Console.error(OpenLayers.i18n("unsupportedGeometryType",{'geomType':type}));}
+break;}}
+var attributes;if(this.extractAttributes){attributes=this.parseAttributes(node);}
+var feature=new OpenLayers.Feature.Vector(geometry,attributes);var fid=node.getAttribute("id")||node.getAttribute("name");if(fid!=null){feature.fid=fid;}
+return feature;},getStyle:function(styleUrl,options){var styleBaseUrl=OpenLayers.Util.removeTail(styleUrl);var newOptions=OpenLayers.Util.extend({},options);newOptions.depth++;newOptions.styleBaseUrl=styleBaseUrl;if(!this.styles[styleUrl]&&!OpenLayers.String.startsWith(styleUrl,"#")&&newOptions.depth<=this.maxDepth&&!this.fetched[styleBaseUrl]){var data=this.fetchLink(styleBaseUrl);if(data){this.parseData(data,newOptions);}}
+var style=OpenLayers.Util.extend({},this.styles[styleUrl]);return style;},parseGeometry:{point:function(node){var nodeList=this.getElementsByTagNameNS(node,this.internalns,"coordinates");var coords=[];if(nodeList.length>0){var coordString=nodeList[0].firstChild.nodeValue;coordString=coordString.replace(this.regExes.removeSpace,"");coords=coordString.split(",");}
+var point=null;if(coords.length>1){if(coords.length==2){coords[2]=null;}
+point=new OpenLayers.Geometry.Point(coords[0],coords[1],coords[2]);}else{throw"Bad coordinate string: "+coordString;}
+return point;},linestring:function(node,ring){var nodeList=this.getElementsByTagNameNS(node,this.internalns,"coordinates");var line=null;if(nodeList.length>0){var coordString=this.getChildValue(nodeList[0]);coordString=coordString.replace(this.regExes.trimSpace,"");coordString=coordString.replace(this.regExes.trimComma,",");var pointList=coordString.split(this.regExes.splitSpace);var numPoints=pointList.length;var points=new Array(numPoints);var coords,numCoords;for(var i=0;i<numPoints;++i){coords=pointList[i].split(",");numCoords=coords.length;if(numCoords>1){if(coords.length==2){coords[2]=null;}
+points[i]=new OpenLayers.Geometry.Point(coords[0],coords[1],coords[2]);}else{throw"Bad LineString point coordinates: "+
+pointList[i];}}
+if(numPoints){if(ring){line=new OpenLayers.Geometry.LinearRing(points);}else{line=new OpenLayers.Geometry.LineString(points);}}else{throw"Bad LineString coordinates: "+coordString;}}
+return line;},polygon:function(node){var nodeList=this.getElementsByTagNameNS(node,this.internalns,"LinearRing");var numRings=nodeList.length;var components=new Array(numRings);if(numRings>0){var ring;for(var i=0,len=nodeList.length;i<len;++i){ring=this.parseGeometry.linestring.apply(this,[nodeList[i],true]);if(ring){components[i]=ring;}else{throw"Bad LinearRing geometry: "+i;}}}
+return new OpenLayers.Geometry.Polygon(components);},multigeometry:function(node){var child,parser;var parts=[];var children=node.childNodes;for(var i=0,len=children.length;i<len;++i){child=children[i];if(child.nodeType==1){var type=(child.prefix)?child.nodeName.split(":")[1]:child.nodeName;var parser=this.parseGeometry[type.toLowerCase()];if(parser){parts.push(parser.apply(this,[child]));}}}
+return new OpenLayers.Geometry.Collection(parts);}},parseAttributes:function(node){var attributes={};var edNodes=node.getElementsByTagName("ExtendedData");if(edNodes.length){attributes=this.parseExtendedData(edNodes[0]);}
+var child,grandchildren,grandchild;var children=node.childNodes;for(var i=0,len=children.length;i<len;++i){child=children[i];if(child.nodeType==1){grandchildren=child.childNodes;if(grandchildren.length>=1&&grandchildren.length<=3){var grandchild;switch(grandchildren.length){case 1:grandchild=grandchildren[0];break;case 2:var c1=grandchildren[0];var c2=grandchildren[1];grandchild=(c1.nodeType==3||c1.nodeType==4)?c1:c2;break;case 3:default:grandchild=grandchildren[1];break;}
+if(grandchild.nodeType==3||grandchild.nodeType==4){var name=(child.prefix)?child.nodeName.split(":")[1]:child.nodeName;var value=OpenLayers.Util.getXmlNodeValue(grandchild);if(value){value=value.replace(this.regExes.trimSpace,"");attributes[name]=value;}}}}}
+return attributes;},parseExtendedData:function(node){var attributes={};var i,len,data,key;var dataNodes=node.getElementsByTagName("Data");for(i=0,len=dataNodes.length;i<len;i++){data=dataNodes[i];key=data.getAttribute("name");var ed={};var valueNode=data.getElementsByTagName("value");if(valueNode.length){ed['value']=this.getChildValue(valueNode[0]);}
+var nameNode=data.getElementsByTagName("displayName");if(nameNode.length){ed['displayName']=this.getChildValue(nameNode[0]);}
+attributes[key]=ed;}
+var simpleDataNodes=node.getElementsByTagName("SimpleData");for(i=0,len=simpleDataNodes.length;i<len;i++){var ed={};data=simpleDataNodes[i];key=data.getAttribute("name");ed['value']=this.getChildValue(data);ed['displayName']=key;attributes[key]=ed;}
+return attributes;},parseProperty:function(xmlNode,namespace,tagName){var value;var nodeList=this.getElementsByTagNameNS(xmlNode,namespace,tagName);try{value=OpenLayers.Util.getXmlNodeValue(nodeList[0]);}catch(e){value=null;}
+return value;},write:function(features){if(!(features instanceof Array)){features=[features];}
+var kml=this.createElementNS(this.kmlns,"kml");var folder=this.createFolderXML();for(var i=0,len=features.length;i<len;++i){folder.appendChild(this.createPlacemarkXML(features[i]));}
+kml.appendChild(folder);return OpenLayers.Format.XML.prototype.write.apply(this,[kml]);},createFolderXML:function(){var folder=this.createElementNS(this.kmlns,"Folder");if(this.foldersName){var folderName=this.createElementNS(this.kmlns,"name");var folderNameText=this.createTextNode(this.foldersName);folderName.appendChild(folderNameText);folder.appendChild(folderName);}
+if(this.foldersDesc){var folderDesc=this.createElementNS(this.kmlns,"description");var folderDescText=this.createTextNode(this.foldersDesc);folderDesc.appendChild(folderDescText);folder.appendChild(folderDesc);}
+return folder;},createPlacemarkXML:function(feature){var placemarkName=this.createElementNS(this.kmlns,"name");var name=feature.style&&feature.style.label?feature.style.label:feature.attributes.name||feature.id;placemarkName.appendChild(this.createTextNode(name));var placemarkDesc=this.createElementNS(this.kmlns,"description");var desc=feature.attributes.description||this.placemarksDesc;placemarkDesc.appendChild(this.createTextNode(desc));var placemarkNode=this.createElementNS(this.kmlns,"Placemark");if(feature.fid!=null){placemarkNode.setAttribute("id",feature.fid);}
+placemarkNode.appendChild(placemarkName);placemarkNode.appendChild(placemarkDesc);var geometryNode=this.buildGeometryNode(feature.geometry);placemarkNode.appendChild(geometryNode);return placemarkNode;},buildGeometryNode:function(geometry){if(this.internalProjection&&this.externalProjection){geometry=geometry.clone();geometry.transform(this.internalProjection,this.externalProjection);}
+var className=geometry.CLASS_NAME;var type=className.substring(className.lastIndexOf(".")+1);var builder=this.buildGeometry[type.toLowerCase()];var node=null;if(builder){node=builder.apply(this,[geometry]);}
+return node;},buildGeometry:{point:function(geometry){var kml=this.createElementNS(this.kmlns,"Point");kml.appendChild(this.buildCoordinatesNode(geometry));return kml;},multipoint:function(geometry){return this.buildGeometry.collection.apply(this,[geometry]);},linestring:function(geometry){var kml=this.createElementNS(this.kmlns,"LineString");kml.appendChild(this.buildCoordinatesNode(geometry));return kml;},multilinestring:function(geometry){return this.buildGeometry.collection.apply(this,[geometry]);},linearring:function(geometry){var kml=this.createElementNS(this.kmlns,"LinearRing");kml.appendChild(this.buildCoordinatesNode(geometry));return kml;},polygon:function(geometry){var kml=this.createElementNS(this.kmlns,"Polygon");var rings=geometry.components;var ringMember,ringGeom,type;for(var i=0,len=rings.length;i<len;++i){type=(i==0)?"outerBoundaryIs":"innerBoundaryIs";ringMember=this.createElementNS(this.kmlns,type);ringGeom=this.buildGeometry.linearring.apply(this,[rings[i]]);ringMember.appendChild(ringGeom);kml.appendChild(ringMember);}
+return kml;},multipolygon:function(geometry){return this.buildGeometry.collection.apply(this,[geometry]);},collection:function(geometry){var kml=this.createElementNS(this.kmlns,"MultiGeometry");var child;for(var i=0,len=geometry.components.length;i<len;++i){child=this.buildGeometryNode.apply(this,[geometry.components[i]]);if(child){kml.appendChild(child);}}
+return kml;}},buildCoordinatesNode:function(geometry){var coordinatesNode=this.createElementNS(this.kmlns,"coordinates");var path;var points=geometry.components;if(points){var point;var numPoints=points.length;var parts=new Array(numPoints);for(var i=0;i<numPoints;++i){point=points[i];parts[i]=point.x+","+point.y;}
+path=parts.join(" ");}else{path=geometry.x+","+geometry.y;}
+var txtNode=this.createTextNode(path);coordinatesNode.appendChild(txtNode);return coordinatesNode;},CLASS_NAME:"OpenLayers.Format.KML"});OpenLayers.Format.OSM=OpenLayers.Class(OpenLayers.Format.XML,{checkTags:false,interestingTagsExclude:null,areaTags:null,initialize:function(options){var layer_defaults={'interestingTagsExclude':['source','source_ref','source:ref','history','attribution','created_by'],'areaTags':['area','building','leisure','tourism','ruins','historic','landuse','military','natural','sport']};layer_defaults=OpenLayers.Util.extend(layer_defaults,options);var interesting={};for(var i=0;i<layer_defaults.interestingTagsExclude.length;i++){interesting[layer_defaults.interestingTagsExclude[i]]=true;}
+layer_defaults.interestingTagsExclude=interesting;var area={};for(var i=0;i<layer_defaults.areaTags.length;i++){area[layer_defaults.areaTags[i]]=true;}
+layer_defaults.areaTags=area;this.externalProjection=new OpenLayers.Projection("EPSG:4326");OpenLayers.Format.XML.prototype.initialize.apply(this,[layer_defaults]);},read:function(doc){if(typeof doc=="string"){doc=OpenLayers.Format.XML.prototype.read.apply(this,[doc]);}
+var nodes=this.getNodes(doc);var ways=this.getWays(doc);var feat_list=new Array(ways.length);for(var i=0;i<ways.length;i++){var point_list=new Array(ways[i].nodes.length);var poly=this.isWayArea(ways[i])?1:0;for(var j=0;j<ways[i].nodes.length;j++){var node=nodes[ways[i].nodes[j]];var point=new OpenLayers.Geometry.Point(node.lon,node.lat);point.osm_id=parseInt(ways[i].nodes[j]);point_list[j]=point;node.used=true;}
+var geometry=null;if(poly){geometry=new OpenLayers.Geometry.Polygon(new OpenLayers.Geometry.LinearRing(point_list));}else{geometry=new OpenLayers.Geometry.LineString(point_list);}
+if(this.internalProjection&&this.externalProjection){geometry.transform(this.externalProjection,this.internalProjection);}
+var feat=new OpenLayers.Feature.Vector(geometry,ways[i].tags);feat.osm_id=parseInt(ways[i].id);feat.fid="way."+feat.osm_id;feat_list[i]=feat;}
+for(var node_id in nodes){var node=nodes[node_id];if(!node.used||this.checkTags){var tags=null;if(this.checkTags){var result=this.getTags(node.node,true);if(node.used&&!result[1]){continue;}
+tags=result[0];}else{tags=this.getTags(node.node);}
+var feat=new OpenLayers.Feature.Vector(new OpenLayers.Geometry.Point(node['lon'],node['lat']),tags);if(this.internalProjection&&this.externalProjection){feat.geometry.transform(this.externalProjection,this.internalProjection);}
+feat.osm_id=parseInt(node_id);feat.fid="node."+feat.osm_id;feat_list.push(feat);}
+node.node=null;}
+return feat_list;},getNodes:function(doc){var node_list=doc.getElementsByTagName("node");var nodes={};for(var i=0;i<node_list.length;i++){var node=node_list[i];var id=node.getAttribute("id");nodes[id]={'lat':node.getAttribute("lat"),'lon':node.getAttribute("lon"),'node':node};}
+return nodes;},getWays:function(doc){var way_list=doc.getElementsByTagName("way");var return_ways=[];for(var i=0;i<way_list.length;i++){var way=way_list[i];var way_object={id:way.getAttribute("id")};way_object.tags=this.getTags(way);var node_list=way.getElementsByTagName("nd");way_object.nodes=new Array(node_list.length);for(var j=0;j<node_list.length;j++){way_object.nodes[j]=node_list[j].getAttribute("ref");}
+return_ways.push(way_object);}
+return return_ways;},getTags:function(dom_node,interesting_tags){var tag_list=dom_node.getElementsByTagName("tag");var tags={};var interesting=false;for(var j=0;j<tag_list.length;j++){var key=tag_list[j].getAttribute("k");tags[key]=tag_list[j].getAttribute("v");if(interesting_tags){if(!this.interestingTagsExclude[key]){interesting=true;}}}
+return interesting_tags?[tags,interesting]:tags;},isWayArea:function(way){var poly_shaped=false;var poly_tags=false;if(way.nodes[0]==way.nodes[way.nodes.length-1]){poly_shaped=true;}
+if(this.checkTags){for(var key in way.tags){if(this.areaTags[key]){poly_tags=true;break;}}}
+return poly_shaped&&(this.checkTags?poly_tags:true);},write:function(features){if(!(features instanceof Array)){features=[features];}
+this.osm_id=1;this.created_nodes={};var root_node=this.createElementNS(null,"osm");root_node.setAttribute("version","0.5");root_node.setAttribute("generator","OpenLayers "+OpenLayers.VERSION_NUMBER);for(var i=features.length-1;i>=0;i--){var nodes=this.createFeatureNodes(features[i]);for(var j=0;j<nodes.length;j++){root_node.appendChild(nodes[j]);}}
+return OpenLayers.Format.XML.prototype.write.apply(this,[root_node]);},createFeatureNodes:function(feature){var nodes=[];var className=feature.geometry.CLASS_NAME;var type=className.substring(className.lastIndexOf(".")+1);type=type.toLowerCase();var builder=this.createXML[type];if(builder){nodes=builder.apply(this,[feature]);}
+return nodes;},createXML:{'point':function(point){var id=null;var geometry=point.geometry?point.geometry:point;var already_exists=false;if(point.osm_id){id=point.osm_id;if(this.created_nodes[id]){already_exists=true;}}else{id=-this.osm_id;this.osm_id++;}
+if(already_exists){node=this.created_nodes[id];}else{var node=this.createElementNS(null,"node");}
+this.created_nodes[id]=node;node.setAttribute("id",id);node.setAttribute("lon",geometry.x);node.setAttribute("lat",geometry.y);if(point.attributes){this.serializeTags(point,node);}
+this.setState(point,node);return already_exists?[]:[node];},linestring:function(feature){var nodes=[];var geometry=feature.geometry;if(feature.osm_id){id=feature.osm_id;}else{id=-this.osm_id;this.osm_id++;}
+var way=this.createElementNS(null,"way");way.setAttribute("id",id);for(var i=0;i<geometry.components.length;i++){var node=this.createXML['point'].apply(this,[geometry.components[i]]);if(node.length){node=node[0];var node_ref=node.getAttribute("id");nodes.push(node);}else{node_ref=geometry.components[i].osm_id;node=this.created_nodes[node_ref];}
+this.setState(feature,node);var nd_dom=this.createElementNS(null,"nd");nd_dom.setAttribute("ref",node_ref);way.appendChild(nd_dom);}
+this.serializeTags(feature,way);nodes.push(way);return nodes;},polygon:function(feature){var attrs=OpenLayers.Util.extend({'area':'yes'},feature.attributes);var feat=new OpenLayers.Feature.Vector(feature.geometry.components[0],attrs);feat.osm_id=feature.osm_id;return this.createXML['linestring'].apply(this,[feat]);}},serializeTags:function(feature,node){for(var key in feature.attributes){var tag=this.createElementNS(null,"tag");tag.setAttribute("k",key);tag.setAttribute("v",feature.attributes[key]);node.appendChild(tag);}},setState:function(feature,node){if(feature.state){var state=null;switch(feature.state){case OpenLayers.State.UPDATE:state="modify";case OpenLayers.State.DELETE:state="delete";}
+if(state){node.setAttribute("action",state);}}},CLASS_NAME:"OpenLayers.Format.OSM"});OpenLayers.Geometry.MultiPolygon=OpenLayers.Class(OpenLayers.Geometry.Collection,{componentTypes:["OpenLayers.Geometry.Polygon"],initialize:function(components){OpenLayers.Geometry.Collection.prototype.initialize.apply(this,arguments);},CLASS_NAME:"OpenLayers.Geometry.MultiPolygon"});OpenLayers.Handler.Polygon=OpenLayers.Class(OpenLayers.Handler.Path,{polygon:null,initialize:function(control,callbacks,options){OpenLayers.Handler.Path.prototype.initialize.apply(this,arguments);},createFeature:function(pixel){var lonlat=this.control.map.getLonLatFromPixel(pixel);this.point=new OpenLayers.Feature.Vector(new OpenLayers.Geometry.Point(lonlat.lon,lonlat.lat));this.line=new OpenLayers.Feature.Vector(new OpenLayers.Geometry.LinearRing([this.point.geometry]));this.polygon=new OpenLayers.Feature.Vector(new OpenLayers.Geometry.Polygon([this.line.geometry]));this.callback("create",[this.point.geometry,this.getSketch()]);this.point.geometry.clearBounds();this.layer.addFeatures([this.polygon,this.point],{silent:true});},destroyFeature:function(){OpenLayers.Handler.Path.prototype.destroyFeature.apply(this);this.polygon=null;},drawFeature:function(){this.layer.drawFeature(this.polygon,this.style);this.layer.drawFeature(this.point,this.style);},getSketch:function(){return this.polygon;},getGeometry:function(){var geometry=this.polygon&&this.polygon.geometry;if(geometry&&this.multi){geometry=new OpenLayers.Geometry.MultiPolygon([geometry]);}
+return geometry;},dblclick:function(evt){if(!this.freehandMode(evt)){var index=this.line.geometry.components.length-2;this.line.geometry.removeComponent(this.line.geometry.components[index]);this.removePoint();this.finalize();}
+return false;},CLASS_NAME:"OpenLayers.Handler.Polygon"});OpenLayers.Control.EditingToolbar=OpenLayers.Class(OpenLayers.Control.Panel,{initialize:function(layer,options){OpenLayers.Control.Panel.prototype.initialize.apply(this,[options]);this.addControls([new OpenLayers.Control.Navigation()]);var controls=[new OpenLayers.Control.DrawFeature(layer,OpenLayers.Handler.Point,{'displayClass':'olControlDrawFeaturePoint'}),new OpenLayers.Control.DrawFeature(layer,OpenLayers.Handler.Path,{'displayClass':'olControlDrawFeaturePath'}),new OpenLayers.Control.DrawFeature(layer,OpenLayers.Handler.Polygon,{'displayClass':'olControlDrawFeaturePolygon'})];this.addControls(controls);},draw:function(){var div=OpenLayers.Control.Panel.prototype.draw.apply(this,arguments);this.activateControl(this.controls[0]);return div;},CLASS_NAME:"OpenLayers.Control.EditingToolbar"});OpenLayers.Control.SLDSelect=OpenLayers.Class(OpenLayers.Control,{EVENT_TYPES:["selected"],clearOnDeactivate:false,layers:null,callbacks:null,selectionSymbolizer:{'Polygon':{fillColor:'#FF0000',stroke:false},'Line':{strokeColor:'#FF0000',strokeWidth:2},'Point':{graphicName:'square',fillColor:'#FF0000',pointRadius:5}},layerOptions:null,handlerOptions:null,sketchStyle:null,wfsCache:{},layerCache:{},initialize:function(handler,options){this.EVENT_TYPES=OpenLayers.Control.SLDSelect.prototype.EVENT_TYPES.concat(OpenLayers.Control.prototype.EVENT_TYPES);OpenLayers.Control.prototype.initialize.apply(this,[options]);this.callbacks=OpenLayers.Util.extend({done:this.select,click:this.select},this.callbacks);this.handlerOptions=this.handlerOptions||{};this.layerOptions=OpenLayers.Util.applyDefaults(this.layerOptions,{displayInLayerSwitcher:false});if(this.sketchStyle){this.handlerOptions.layerOptions=OpenLayers.Util.applyDefaults(this.handlerOptions.layerOptions,{styleMap:new OpenLayers.StyleMap({"default":this.sketchStyle})});}
+this.handler=new handler(this,this.callbacks,this.handlerOptions);},destroy:function(){for(var key in this.layerCache){delete this.layerCache[key];}
+for(var key in this.wfsCache){delete this.wfsCache[key];}
+OpenLayers.Control.prototype.destroy.apply(this,arguments);},coupleLayerVisiblity:function(evt){this.setVisibility(evt.object.getVisibility());},createSelectionLayer:function(source){var selectionLayer;if(!this.layerCache[source.id]){selectionLayer=new OpenLayers.Layer.WMS.Post(source.name,source.url,source.params,OpenLayers.Util.applyDefaults(this.layerOptions,source.getOptions()));this.layerCache[source.id]=selectionLayer;if(this.layerOptions.displayInLayerSwitcher===false){source.events.on({"visibilitychanged":this.coupleLayerVisiblity,scope:selectionLayer});}
+this.map.addLayer(selectionLayer);}else{selectionLayer=this.layerCache[source.id];}
+return selectionLayer;},createSLD:function(layer,filters,geometryAttributes){var sld={version:"1.0.0",namedLayers:{}};var layerNames=[layer.params.LAYERS].join(",").split(",");for(var i=0,len=layerNames.length;i<len;i++){var name=layerNames[i];sld.namedLayers[name]={name:name,userStyles:[]};var symbolizer=this.selectionSymbolizer;var geometryAttribute=geometryAttributes[i];if(geometryAttribute.type.indexOf('Polygon')>=0){symbolizer={Polygon:this.selectionSymbolizer['Polygon']};}else if(geometryAttribute.type.indexOf('LineString')>=0){symbolizer={Line:this.selectionSymbolizer['Line']};}else if(geometryAttribute.type.indexOf('Point')>=0){symbolizer={Point:this.selectionSymbolizer['Point']};}
+var filter=filters[i];sld.namedLayers[name].userStyles.push({name:'default',rules:[new OpenLayers.Rule({symbolizer:symbolizer,filter:filter,maxScaleDenominator:layer.options.minScale})]});}
+return new OpenLayers.Format.SLD().write(sld);},parseDescribeLayer:function(request){var format=new OpenLayers.Format.WMSDescribeLayer();var doc=request.responseXML;if(!doc||!doc.documentElement){doc=request.responseText;}
+var describeLayer=format.read(doc);var typeNames=[];var url=null;for(var i=0,len=describeLayer.length;i<len;i++){if(describeLayer[i].owsType=="WFS"){typeNames.push(describeLayer[i].typeName);url=describeLayer[i].owsURL;}}
+var options={url:url,params:{SERVICE:"WFS",TYPENAME:typeNames.toString(),REQUEST:"DescribeFeatureType",VERSION:"1.0.0"},callback:function(request){var format=new OpenLayers.Format.WFSDescribeFeatureType();var doc=request.responseXML;if(!doc||!doc.documentElement){doc=request.responseText;}
+var describeFeatureType=format.read(doc);this.control.wfsCache[this.layer.id]=describeFeatureType;this.control._queue&&this.control.applySelection();},scope:this};OpenLayers.Request.GET(options);},getGeometryAttributes:function(layer){var result=[];var cache=this.wfsCache[layer.id];for(var i=0,len=cache.featureTypes.length;i<len;i++){var typeName=cache.featureTypes[i];var properties=typeName.properties;for(var j=0,lenj=properties.length;j<lenj;j++){var property=properties[j];var type=property.type;if((type.indexOf('LineString')>=0)||(type.indexOf('GeometryAssociationType')>=0)||(type.indexOf('GeometryPropertyType')>=0)||(type.indexOf('Point')>=0)||(type.indexOf('Polygon')>=0)){result.push(property);}}}
+return result;},activate:function(){var activated=OpenLayers.Control.prototype.activate.call(this);if(activated){for(var i=0,len=this.layers.length;i<len;i++){var layer=this.layers[i];if(layer&&!this.wfsCache[layer.id]){var options={url:layer.url,params:{SERVICE:"WMS",VERSION:layer.params.VERSION,LAYERS:layer.params.LAYERS,REQUEST:"DescribeLayer"},callback:this.parseDescribeLayer,scope:{layer:layer,control:this}};OpenLayers.Request.GET(options);}}}
+return activated;},deactivate:function(){var deactivated=OpenLayers.Control.prototype.deactivate.call(this);if(deactivated){for(var i=0,len=this.layers.length;i<len;i++){var layer=this.layers[i];if(layer&&this.clearOnDeactivate===true){var layerCache=this.layerCache;var selectionLayer=layerCache[layer.id];if(selectionLayer){layer.events.un({"visibilitychanged":this.coupleLayerVisiblity,scope:selectionLayer});selectionLayer.destroy();delete layerCache[layer.id];}}}}
+return deactivated;},setLayers:function(layers){if(this.active){this.deactivate();this.layers=layers;this.activate();}else{this.layers=layers;}},createFilter:function(geometryAttribute,geometry){var filter=null;if(this.handler instanceof OpenLayers.Handler.RegularPolygon){if(this.handler.irregular===true){filter=new OpenLayers.Filter.Spatial({type:OpenLayers.Filter.Spatial.BBOX,property:geometryAttribute.name,value:geometry.getBounds()});}else{filter=new OpenLayers.Filter.Spatial({type:OpenLayers.Filter.Spatial.INTERSECTS,property:geometryAttribute.name,value:geometry});}}else if(this.handler instanceof OpenLayers.Handler.Polygon){filter=new OpenLayers.Filter.Spatial({type:OpenLayers.Filter.Spatial.INTERSECTS,property:geometryAttribute.name,value:geometry});}else if(this.handler instanceof OpenLayers.Handler.Path){if(geometryAttribute.type.indexOf('Point')>=0){filter=new OpenLayers.Filter.Spatial({type:OpenLayers.Filter.Spatial.DWITHIN,property:geometryAttribute.name,distance:this.map.getExtent().getWidth()*0.01,distanceUnits:this.map.getUnits(),value:geometry});}else{filter=new OpenLayers.Filter.Spatial({type:OpenLayers.Filter.Spatial.INTERSECTS,property:geometryAttribute.name,value:geometry});}}else if(this.handler instanceof OpenLayers.Handler.Click){if(geometryAttribute.type.indexOf('Polygon')>=0){filter=new OpenLayers.Filter.Spatial({type:OpenLayers.Filter.Spatial.INTERSECTS,property:geometryAttribute.name,value:geometry});}else{filter=new OpenLayers.Filter.Spatial({type:OpenLayers.Filter.Spatial.DWITHIN,property:geometryAttribute.name,distance:this.map.getExtent().getWidth()*0.01,distanceUnits:this.map.getUnits(),value:geometry});}}
+return filter;},select:function(geometry){this._queue=function(){for(var i=0,len=this.layers.length;i<len;i++){var layer=this.layers[i];var geometryAttributes=this.getGeometryAttributes(layer);var filters=[];for(var j=0,lenj=geometryAttributes.length;j<lenj;j++){var geometryAttribute=geometryAttributes[j];if(geometryAttribute!==null){if(!(geometry instanceof OpenLayers.Geometry)){var point=this.map.getLonLatFromPixel(geometry.xy);geometry=new OpenLayers.Geometry.Point(point.lon,point.lat);}
+var filter=this.createFilter(geometryAttribute,geometry);if(filter!==null){filters.push(filter);}}}
+var selectionLayer=this.createSelectionLayer(layer);var sld=this.createSLD(layer,filters,geometryAttributes);this.events.triggerEvent("selected",{layer:layer,filters:filters});selectionLayer.mergeNewParams({SLD_BODY:sld});delete this._queue;}};this.applySelection();},applySelection:function(){var canApply=true;for(var i=0,len=this.layers.length;i<len;i++){if(!this.wfsCache[this.layers[i].id]){canApply=false;break;}}
+canApply&&this._queue.call(this);},CLASS_NAME:"OpenLayers.Control.SLDSelect"});OpenLayers.Format.ArcXML=OpenLayers.Class(OpenLayers.Format.XML,{fontStyleKeys:['antialiasing','blockout','font','fontcolor','fontsize','fontstyle','glowing','interval','outline','printmode','shadow','transparency'],request:null,response:null,initialize:function(options){this.request=new OpenLayers.Format.ArcXML.Request();this.response=new OpenLayers.Format.ArcXML.Response();if(options){if(options.requesttype=="feature"){this.request.get_image=null;var qry=this.request.get_feature.query;this.addCoordSys(qry.featurecoordsys,options.featureCoordSys);this.addCoordSys(qry.filtercoordsys,options.filterCoordSys);if(options.polygon){qry.isspatial=true;qry.spatialfilter.polygon=options.polygon;}else if(options.envelope){qry.isspatial=true;qry.spatialfilter.envelope={minx:0,miny:0,maxx:0,maxy:0};this.parseEnvelope(qry.spatialfilter.envelope,options.envelope);}}else if(options.requesttype=="image"){this.request.get_feature=null;var props=this.request.get_image.properties;this.parseEnvelope(props.envelope,options.envelope);this.addLayers(props.layerlist,options.layers);this.addImageSize(props.imagesize,options.tileSize);this.addCoordSys(props.featurecoordsys,options.featureCoordSys);this.addCoordSys(props.filtercoordsys,options.filterCoordSys);}else{this.request=null;}}
+OpenLayers.Format.XML.prototype.initialize.apply(this,[options]);},parseEnvelope:function(env,arr){if(arr&&arr.length==4){env.minx=arr[0];env.miny=arr[1];env.maxx=arr[2];env.maxy=arr[3];}},addLayers:function(ll,lyrs){for(var lind=0,len=lyrs.length;lind<len;lind++){ll.push(lyrs[lind]);}},addImageSize:function(imsize,olsize){if(olsize!==null){imsize.width=olsize.w;imsize.height=olsize.h;imsize.printwidth=olsize.w;imsize.printheight=olsize.h;}},addCoordSys:function(featOrFilt,fsys){if(typeof fsys=="string"){featOrFilt.id=parseInt(fsys);featOrFilt.string=fsys;}
+else if(typeof fsys=="object"&&fsys.proj!==null){featOrFilt.id=fsys.proj.srsProjNumber;featOrFilt.string=fsys.proj.srsCode;}else{featOrFilt=fsys;}},iserror:function(data){var ret=null;if(!data){ret=(this.response.error!=='');}else{data=OpenLayers.Format.XML.prototype.read.apply(this,[data]);var errorNodes=data.documentElement.getElementsByTagName("ERROR");ret=(errorNodes!==null&&errorNodes.length>0);}
+return ret;},read:function(data){if(typeof data=="string"){data=OpenLayers.Format.XML.prototype.read.apply(this,[data]);}
+var arcNode=null;if(data&&data.documentElement){if(data.documentElement.nodeName=="ARCXML"){arcNode=data.documentElement;}else{arcNode=data.documentElement.getElementsByTagName("ARCXML")[0];}}
+if(!arcNode||arcNode.firstChild.nodeName==='parsererror'){var error,source;try{error=data.firstChild.nodeValue;source=data.firstChild.childNodes[1].firstChild.nodeValue;}catch(err){}
+throw{message:"Error parsing the ArcXML request",error:error,source:source};}
+var response=this.parseResponse(arcNode);return response;},write:function(request){if(!request){request=this.request;}
+var root=this.createElementNS("","ARCXML");root.setAttribute("version","1.1");var reqElem=this.createElementNS("","REQUEST");if(request.get_image!=null){var getElem=this.createElementNS("","GET_IMAGE");reqElem.appendChild(getElem);var propElem=this.createElementNS("","PROPERTIES");getElem.appendChild(propElem);var props=request.get_image.properties;if(props.featurecoordsys!=null){var feat=this.createElementNS("","FEATURECOORDSYS");propElem.appendChild(feat);if(props.featurecoordsys.id===0){feat.setAttribute("string",props.featurecoordsys['string']);}
+else{feat.setAttribute("id",props.featurecoordsys.id);}}
+if(props.filtercoordsys!=null){var filt=this.createElementNS("","FILTERCOORDSYS");propElem.appendChild(filt);if(props.filtercoordsys.id===0){filt.setAttribute("string",props.filtercoordsys.string);}
+else{filt.setAttribute("id",props.filtercoordsys.id);}}
+if(props.envelope!=null){var env=this.createElementNS("","ENVELOPE");propElem.appendChild(env);env.setAttribute("minx",props.envelope.minx);env.setAttribute("miny",props.envelope.miny);env.setAttribute("maxx",props.envelope.maxx);env.setAttribute("maxy",props.envelope.maxy);}
+var imagesz=this.createElementNS("","IMAGESIZE");propElem.appendChild(imagesz);imagesz.setAttribute("height",props.imagesize.height);imagesz.setAttribute("width",props.imagesize.width);if(props.imagesize.height!=props.imagesize.printheight||props.imagesize.width!=props.imagesize.printwidth){imagesz.setAttribute("printheight",props.imagesize.printheight);imagesz.setArrtibute("printwidth",props.imagesize.printwidth);}
+if(props.background!=null){var backgrnd=this.createElementNS("","BACKGROUND");propElem.appendChild(backgrnd);backgrnd.setAttribute("color",props.background.color.r+","+
+props.background.color.g+","+
+props.background.color.b);if(props.background.transcolor!==null){backgrnd.setAttribute("transcolor",props.background.transcolor.r+","+
+props.background.transcolor.g+","+
+props.background.transcolor.b);}}
+if(props.layerlist!=null&&props.layerlist.length>0){var layerlst=this.createElementNS("","LAYERLIST");propElem.appendChild(layerlst);for(var ld=0;ld<props.layerlist.length;ld++){var ldef=this.createElementNS("","LAYERDEF");layerlst.appendChild(ldef);ldef.setAttribute("id",props.layerlist[ld].id);ldef.setAttribute("visible",props.layerlist[ld].visible);if(typeof props.layerlist[ld].query=="object"){var query=props.layerlist[ld].query;if(query.where.length<0){continue;}
+var queryElem=null;if(typeof query.spatialfilter=="boolean"&&query.spatialfilter){queryElem=this.createElementNS("","SPATIALQUERY");}
+else{queryElem=this.createElementNS("","QUERY");}
+queryElem.setAttribute("where",query.where);if(typeof query.accuracy=="number"&&query.accuracy>0){queryElem.setAttribute("accuracy",query.accuracy);}
+if(typeof query.featurelimit=="number"&&query.featurelimit<2000){queryElem.setAttribute("featurelimit",query.featurelimit);}
+if(typeof query.subfields=="string"&&query.subfields!="#ALL#"){queryElem.setAttribute("subfields",query.subfields);}
+if(typeof query.joinexpression=="string"&&query.joinexpression.length>0){queryElem.setAttribute("joinexpression",query.joinexpression);}
+if(typeof query.jointables=="string"&&query.jointables.length>0){queryElem.setAttribute("jointables",query.jointables);}
+ldef.appendChild(queryElem);}
+if(typeof props.layerlist[ld].renderer=="object"){this.addRenderer(ldef,props.layerlist[ld].renderer);}}}}else if(request.get_feature!=null){var getElem=this.createElementNS("","GET_FEATURES");getElem.setAttribute("outputmode","newxml");getElem.setAttribute("checkesc","true");if(request.get_feature.geometry){getElem.setAttribute("geometry",request.get_feature.geometry);}
+else{getElem.setAttribute("geometry","false");}
+if(request.get_feature.compact){getElem.setAttribute("compact",request.get_feature.compact);}
+if(request.get_feature.featurelimit=="number"){getElem.setAttribute("featurelimit",request.get_feature.featurelimit);}
+getElem.setAttribute("globalenvelope","true");reqElem.appendChild(getElem);if(request.get_feature.layer!=null&&request.get_feature.layer.length>0){var lyrElem=this.createElementNS("","LAYER");lyrElem.setAttribute("id",request.get_feature.layer);getElem.appendChild(lyrElem);}
+var fquery=request.get_feature.query;if(fquery!=null){var qElem=null;if(fquery.isspatial){qElem=this.createElementNS("","SPATIALQUERY");}else{qElem=this.createElementNS("","QUERY");}
+getElem.appendChild(qElem);if(typeof fquery.accuracy=="number"){qElem.setAttribute("accuracy",fquery.accuracy);}
+if(fquery.featurecoordsys!=null){var fcsElem1=this.createElementNS("","FEATURECOORDSYS");if(fquery.featurecoordsys.id==0){fcsElem1.setAttribute("string",fquery.featurecoordsys.string);}else{fcsElem1.setAttribute("id",fquery.featurecoordsys.id);}
+qElem.appendChild(fcsElem1);}
+if(fquery.filtercoordsys!=null){var fcsElem2=this.createElementNS("","FILTERCOORDSYS");if(fquery.filtercoordsys.id===0){fcsElem2.setAttribute("string",fquery.filtercoordsys.string);}else{fcsElem2.setAttribute("id",fquery.filtercoordsys.id);}
+qElem.appendChild(fcsElem2);}
+if(fquery.buffer>0){var bufElem=this.createElementNS("","BUFFER");bufElem.setAttribute("distance",fquery.buffer);qElem.appendChild(bufElem);}
+if(fquery.isspatial){var spfElem=this.createElementNS("","SPATIALFILTER");spfElem.setAttribute("relation",fquery.spatialfilter.relation);qElem.appendChild(spfElem);if(fquery.spatialfilter.envelope){var envElem=this.createElementNS("","ENVELOPE");envElem.setAttribute("minx",fquery.spatialfilter.envelope.minx);envElem.setAttribute("miny",fquery.spatialfilter.envelope.miny);envElem.setAttribute("maxx",fquery.spatialfilter.envelope.maxx);envElem.setAttribute("maxy",fquery.spatialfilter.envelope.maxy);spfElem.appendChild(envElem);}else if(typeof fquery.spatialfilter.polygon=="object"){spfElem.appendChild(this.writePolygonGeometry(fquery.spatialfilter.polygon));}}
+if(fquery.where!=null&&fquery.where.length>0){qElem.setAttribute("where",fquery.where);}}}
+root.appendChild(reqElem);return OpenLayers.Format.XML.prototype.write.apply(this,[root]);},addGroupRenderer:function(ldef,toprenderer){var topRelem=this.createElementNS("","GROUPRENDERER");ldef.appendChild(topRelem);for(var rind=0;rind<toprenderer.length;rind++){var renderer=toprenderer[rind];this.addRenderer(topRelem,renderer);}},addRenderer:function(topRelem,renderer){if(renderer instanceof Array){this.addGroupRenderer(topRelem,renderer);}else{var renderElem=this.createElementNS("",renderer.type.toUpperCase()+"RENDERER");topRelem.appendChild(renderElem);if(renderElem.tagName=="VALUEMAPRENDERER"){this.addValueMapRenderer(renderElem,renderer);}else if(renderElem.tagName=="VALUEMAPLABELRENDERER"){this.addValueMapLabelRenderer(renderElem,renderer);}else if(renderElem.tagName=="SIMPLELABELRENDERER"){this.addSimpleLabelRenderer(renderElem,renderer);}else if(renderElem.tagName=="SCALEDEPENDENTRENDERER"){this.addScaleDependentRenderer(renderElem,renderer);}}},addScaleDependentRenderer:function(renderElem,renderer){if(typeof renderer.lower=="string"||typeof renderer.lower=="number"){renderElem.setAttribute("lower",renderer.lower);}
+if(typeof renderer.upper=="string"||typeof renderer.upper=="number"){renderElem.setAttribute("upper",renderer.upper);}
+this.addRenderer(renderElem,renderer.renderer);},addValueMapLabelRenderer:function(renderElem,renderer){renderElem.setAttribute("lookupfield",renderer.lookupfield);renderElem.setAttribute("labelfield",renderer.labelfield);if(typeof renderer.exacts=="object"){for(var ext=0,extlen=renderer.exacts.length;ext<extlen;ext++){var exact=renderer.exacts[ext];var eelem=this.createElementNS("","EXACT");if(typeof exact.value=="string"){eelem.setAttribute("value",exact.value);}
+if(typeof exact.label=="string"){eelem.setAttribute("label",exact.label);}
+if(typeof exact.method=="string"){eelem.setAttribute("method",exact.method);}
+renderElem.appendChild(eelem);if(typeof exact.symbol=="object"){var selem=null;if(exact.symbol.type=="text"){selem=this.createElementNS("","TEXTSYMBOL");}
+if(selem!=null){var keys=this.fontStyleKeys;for(var i=0,len=keys.length;i<len;i++){var key=keys[i];if(exact.symbol[key]){selem.setAttribute(key,exact.symbol[key]);}}
+eelem.appendChild(selem);}}}}},addValueMapRenderer:function(renderElem,renderer){renderElem.setAttribute("lookupfield",renderer.lookupfield);if(typeof renderer.ranges=="object"){for(var rng=0,rnglen=renderer.ranges.length;rng<rnglen;rng++){var range=renderer.ranges[rng];var relem=this.createElementNS("","RANGE");relem.setAttribute("lower",range.lower);relem.setAttribute("upper",range.upper);renderElem.appendChild(relem);if(typeof range.symbol=="object"){var selem=null;if(range.symbol.type=="simplepolygon"){selem=this.createElementNS("","SIMPLEPOLYGONSYMBOL");}
+if(selem!=null){if(typeof range.symbol.boundarycolor=="string"){selem.setAttribute("boundarycolor",range.symbol.boundarycolor);}
+if(typeof range.symbol.fillcolor=="string"){selem.setAttribute("fillcolor",range.symbol.fillcolor);}
+if(typeof range.symbol.filltransparency=="number"){selem.setAttribute("filltransparency",range.symbol.filltransparency);}
+relem.appendChild(selem);}}}}else if(typeof renderer.exacts=="object"){for(var ext=0,extlen=renderer.exacts.length;ext<extlen;ext++){var exact=renderer.exacts[ext];var eelem=this.createElementNS("","EXACT");if(typeof exact.value=="string"){eelem.setAttribute("value",exact.value);}
+if(typeof exact.label=="string"){eelem.setAttribute("label",exact.label);}
+if(typeof exact.method=="string"){eelem.setAttribute("method",exact.method);}
+renderElem.appendChild(eelem);if(typeof exact.symbol=="object"){var selem=null;if(exact.symbol.type=="simplemarker"){selem=this.createElementNS("","SIMPLEMARKERSYMBOL");}
+if(selem!=null){if(typeof exact.symbol.antialiasing=="string"){selem.setAttribute("antialiasing",exact.symbol.antialiasing);}
+if(typeof exact.symbol.color=="string"){selem.setAttribute("color",exact.symbol.color);}
+if(typeof exact.symbol.outline=="string"){selem.setAttribute("outline",exact.symbol.outline);}
+if(typeof exact.symbol.overlap=="string"){selem.setAttribute("overlap",exact.symbol.overlap);}
+if(typeof exact.symbol.shadow=="string"){selem.setAttribute("shadow",exact.symbol.shadow);}
+if(typeof exact.symbol.transparency=="number"){selem.setAttribute("transparency",exact.symbol.transparency);}
+if(typeof exact.symbol.usecentroid=="string"){selem.setAttribute("usecentroid",exact.symbol.usecentroid);}
+if(typeof exact.symbol.width=="number"){selem.setAttribute("width",exact.symbol.width);}
+eelem.appendChild(selem);}}}}},addSimpleLabelRenderer:function(renderElem,renderer){renderElem.setAttribute("field",renderer.field);var keys=['featureweight','howmanylabels','labelbufferratio','labelpriorities','labelweight','linelabelposition','rotationalangles'];for(var i=0,len=keys.length;i<len;i++){var key=keys[i];if(renderer[key]){renderElem.setAttribute(key,renderer[key]);}}
+if(renderer.symbol.type=="text"){var symbol=renderer.symbol;var selem=this.createElementNS("","TEXTSYMBOL");renderElem.appendChild(selem);var keys=this.fontStyleKeys;for(var i=0,len=keys.length;i<len;i++){var key=keys[i];if(symbol[key]){selem.setAttribute(key,renderer[key]);}}}},writePolygonGeometry:function(polygon){if(!(polygon instanceof OpenLayers.Geometry.Polygon)){throw{message:'Cannot write polygon geometry to ArcXML with an '+
+polygon.CLASS_NAME+' object.',geometry:polygon};}
+var polyElem=this.createElementNS("","POLYGON");for(var ln=0,lnlen=polygon.components.length;ln<lnlen;ln++){var ring=polygon.components[ln];var ringElem=this.createElementNS("","RING");for(var rn=0,rnlen=ring.components.length;rn<rnlen;rn++){var point=ring.components[rn];var pointElem=this.createElementNS("","POINT");pointElem.setAttribute("x",point.x);pointElem.setAttribute("y",point.y);ringElem.appendChild(pointElem);}
+polyElem.appendChild(ringElem);}
+return polyElem;},parseResponse:function(data){if(typeof data=="string"){var newData=new OpenLayers.Format.XML();data=newData.read(data);}
+var response=new OpenLayers.Format.ArcXML.Response();var errorNode=data.getElementsByTagName("ERROR");if(errorNode!=null&&errorNode.length>0){response.error=this.getChildValue(errorNode,"Unknown error.");}else{var responseNode=data.getElementsByTagName("RESPONSE");if(responseNode==null||responseNode.length==0){response.error="No RESPONSE tag found in ArcXML response.";return response;}
+var rtype=responseNode[0].firstChild.nodeName;if(rtype=="#text"){rtype=responseNode[0].firstChild.nextSibling.nodeName;}
+if(rtype=="IMAGE"){var envelopeNode=data.getElementsByTagName("ENVELOPE");var outputNode=data.getElementsByTagName("OUTPUT");if(envelopeNode==null||envelopeNode.length==0){response.error="No ENVELOPE tag found in ArcXML response.";}else if(outputNode==null||outputNode.length==0){response.error="No OUTPUT tag found in ArcXML response.";}else{var envAttr=this.parseAttributes(envelopeNode[0]);var outputAttr=this.parseAttributes(outputNode[0]);if(typeof outputAttr.type=="string"){response.image={envelope:envAttr,output:{type:outputAttr.type,data:this.getChildValue(outputNode[0])}};}else{response.image={envelope:envAttr,output:outputAttr};}}}else if(rtype=="FEATURES"){var features=responseNode[0].getElementsByTagName("FEATURES");var featureCount=features[0].getElementsByTagName("FEATURECOUNT");response.features.featurecount=featureCount[0].getAttribute("count");if(response.features.featurecount>0){var envelope=features[0].getElementsByTagName("ENVELOPE");response.features.envelope=this.parseAttributes(envelope[0],typeof(0));var featureList=features[0].getElementsByTagName("FEATURE");for(var fn=0;fn<featureList.length;fn++){var feature=new OpenLayers.Feature.Vector();var fields=featureList[fn].getElementsByTagName("FIELD");for(var fdn=0;fdn<fields.length;fdn++){var fieldName=fields[fdn].getAttribute("name");var fieldValue=fields[fdn].getAttribute("value");feature.attributes[fieldName]=fieldValue;}
+var geom=featureList[fn].getElementsByTagName("POLYGON");if(geom.length>0){var ring=geom[0].getElementsByTagName("RING");var polys=[];for(var rn=0;rn<ring.length;rn++){var linearRings=[];linearRings.push(this.parsePointGeometry(ring[rn]));var holes=ring[rn].getElementsByTagName("HOLE");for(var hn=0;hn<holes.length;hn++){linearRings.push(this.parsePointGeometry(holes[hn]));}
+holes=null;polys.push(new OpenLayers.Geometry.Polygon(linearRings));linearRings=null;}
+ring=null;if(polys.length==1){feature.geometry=polys[0];}else
+{feature.geometry=new OpenLayers.Geometry.MultiPolygon(polys);}}
+response.features.feature.push(feature);}}}else{response.error="Unidentified response type.";}}
+return response;},parseAttributes:function(node,type){var attributes={};for(var attr=0;attr<node.attributes.length;attr++){if(type=="number"){attributes[node.attributes[attr].nodeName]=parseFloat(node.attributes[attr].nodeValue);}else{attributes[node.attributes[attr].nodeName]=node.attributes[attr].nodeValue;}}
+return attributes;},parsePointGeometry:function(node){var ringPoints=[];var coords=node.getElementsByTagName("COORDS");if(coords.length>0){var coordArr=this.getChildValue(coords[0]);coordArr=coordArr.split(/;/);for(var cn=0;cn<coordArr.length;cn++){var coordItems=coordArr[cn].split(/ /);ringPoints.push(new OpenLayers.Geometry.Point(parseFloat(coordItems[0]),parseFloat(coordItems[1])));}
+coords=null;}else{var point=node.getElementsByTagName("POINT");if(point.length>0){for(var pn=0;pn<point.length;pn++){ringPoints.push(new OpenLayers.Geometry.Point(parseFloat(point[pn].getAttribute("x")),parseFloat(point[pn].getAttribute("y"))));}}
+point=null;}
+return new OpenLayers.Geometry.LinearRing(ringPoints);},CLASS_NAME:"OpenLayers.Format.ArcXML"});OpenLayers.Format.ArcXML.Request=OpenLayers.Class({initialize:function(params){var defaults={get_image:{properties:{background:null,draw:true,envelope:{minx:0,miny:0,maxx:0,maxy:0},featurecoordsys:{id:0,string:"",datumtransformid:0,datumtransformstring:""},filtercoordsys:{id:0,string:"",datumtransformid:0,datumtransformstring:""},imagesize:{height:0,width:0,dpi:96,printheight:0,printwidth:0,scalesymbols:false},layerlist:[],output:{baseurl:"",legendbaseurl:"",legendname:"",legendpath:"",legendurl:"",name:"",path:"",type:"jpg",url:""}}},get_feature:{layer:"",query:{isspatial:false,featurecoordsys:{id:0,string:"",datumtransformid:0,datumtransformstring:""},filtercoordsys:{id:0,string:"",datumtransformid:0,datumtransformstring:""},buffer:0,where:"",spatialfilter:{relation:"envelope_intersection",envelope:null}}},environment:{separators:{cs:" ",ts:";"}},layer:[],workspaces:[]};return OpenLayers.Util.extend(this,defaults);},CLASS_NAME:"OpenLayers.Format.ArcXML.Request"});OpenLayers.Format.ArcXML.Response=OpenLayers.Class({initialize:function(params){var defaults={image:{envelope:null,output:''},features:{featurecount:0,envelope:null,feature:[]},error:''};return OpenLayers.Util.extend(this,defaults);},CLASS_NAME:"OpenLayers.Format.ArcXML.Response"});OpenLayers.Format.GML=OpenLayers.Class(OpenLayers.Format.XML,{featureNS:"http://mapserver.gis.umn.edu/mapserver",featurePrefix:"feature",featureName:"featureMember",layerName:"features",geometryName:"geometry",collectionName:"FeatureCollection",gmlns:"http://www.opengis.net/gml",extractAttributes:true,xy:true,initialize:function(options){this.regExes={trimSpace:(/^\s*|\s*$/g),removeSpace:(/\s*/g),splitSpace:(/\s+/),trimComma:(/\s*,\s*/g)};OpenLayers.Format.XML.prototype.initialize.apply(this,[options]);},read:function(data){if(typeof data=="string"){data=OpenLayers.Format.XML.prototype.read.apply(this,[data]);}
+var featureNodes=this.getElementsByTagNameNS(data.documentElement,this.gmlns,this.featureName);var features=[];for(var i=0;i<featureNodes.length;i++){var feature=this.parseFeature(featureNodes[i]);if(feature){features.push(feature);}}
+return features;},parseFeature:function(node){var order=["MultiPolygon","Polygon","MultiLineString","LineString","MultiPoint","Point","Envelope"];var type,nodeList,geometry,parser;for(var i=0;i<order.length;++i){type=order[i];nodeList=this.getElementsByTagNameNS(node,this.gmlns,type);if(nodeList.length>0){parser=this.parseGeometry[type.toLowerCase()];if(parser){geometry=parser.apply(this,[nodeList[0]]);if(this.internalProjection&&this.externalProjection){geometry.transform(this.externalProjection,this.internalProjection);}}else{OpenLayers.Console.error(OpenLayers.i18n("unsupportedGeometryType",{'geomType':type}));}
+break;}}
+var bounds;var boxNodes=this.getElementsByTagNameNS(node,this.gmlns,"Box");for(i=0;i<boxNodes.length;++i){var boxNode=boxNodes[i];var box=this.parseGeometry["box"].apply(this,[boxNode]);var parentNode=boxNode.parentNode;var parentName=parentNode.localName||parentNode.nodeName.split(":").pop();if(parentName==="boundedBy"){bounds=box;}else{geometry=box.toGeometry();}}
+var attributes;if(this.extractAttributes){attributes=this.parseAttributes(node);}
+var feature=new OpenLayers.Feature.Vector(geometry,attributes);feature.bounds=bounds;feature.gml={featureType:node.firstChild.nodeName.split(":")[1],featureNS:node.firstChild.namespaceURI,featureNSPrefix:node.firstChild.prefix};var childNode=node.firstChild;var fid;while(childNode){if(childNode.nodeType==1){fid=childNode.getAttribute("fid")||childNode.getAttribute("id");if(fid){break;}}
+childNode=childNode.nextSibling;}
+feature.fid=fid;return feature;},parseGeometry:{point:function(node){var nodeList,coordString;var coords=[];var nodeList=this.getElementsByTagNameNS(node,this.gmlns,"pos");if(nodeList.length>0){coordString=nodeList[0].firstChild.nodeValue;coordString=coordString.replace(this.regExes.trimSpace,"");coords=coordString.split(this.regExes.splitSpace);}
+if(coords.length==0){nodeList=this.getElementsByTagNameNS(node,this.gmlns,"coordinates");if(nodeList.length>0){coordString=nodeList[0].firstChild.nodeValue;coordString=coordString.replace(this.regExes.removeSpace,"");coords=coordString.split(",");}}
+if(coords.length==0){nodeList=this.getElementsByTagNameNS(node,this.gmlns,"coord");if(nodeList.length>0){var xList=this.getElementsByTagNameNS(nodeList[0],this.gmlns,"X");var yList=this.getElementsByTagNameNS(nodeList[0],this.gmlns,"Y");if(xList.length>0&&yList.length>0){coords=[xList[0].firstChild.nodeValue,yList[0].firstChild.nodeValue];}}}
+if(coords.length==2){coords[2]=null;}
+if(this.xy){return new OpenLayers.Geometry.Point(coords[0],coords[1],coords[2]);}
+else{return new OpenLayers.Geometry.Point(coords[1],coords[0],coords[2]);}},multipoint:function(node){var nodeList=this.getElementsByTagNameNS(node,this.gmlns,"Point");var components=[];if(nodeList.length>0){var point;for(var i=0;i<nodeList.length;++i){point=this.parseGeometry.point.apply(this,[nodeList[i]]);if(point){components.push(point);}}}
+return new OpenLayers.Geometry.MultiPoint(components);},linestring:function(node,ring){var nodeList,coordString;var coords=[];var points=[];nodeList=this.getElementsByTagNameNS(node,this.gmlns,"posList");if(nodeList.length>0){coordString=this.getChildValue(nodeList[0]);coordString=coordString.replace(this.regExes.trimSpace,"");coords=coordString.split(this.regExes.splitSpace);var dim=parseInt(nodeList[0].getAttribute("dimension"));var j,x,y,z;for(var i=0;i<coords.length/dim;++i){j=i*dim;x=coords[j];y=coords[j+1];z=(dim==2)?null:coords[j+2];if(this.xy){points.push(new OpenLayers.Geometry.Point(x,y,z));}else{points.push(new OpenLayers.Geometry.Point(y,x,z));}}}
+if(coords.length==0){nodeList=this.getElementsByTagNameNS(node,this.gmlns,"coordinates");if(nodeList.length>0){coordString=this.getChildValue(nodeList[0]);coordString=coordString.replace(this.regExes.trimSpace,"");coordString=coordString.replace(this.regExes.trimComma,",");var pointList=coordString.split(this.regExes.splitSpace);for(var i=0;i<pointList.length;++i){coords=pointList[i].split(",");if(coords.length==2){coords[2]=null;}
+if(this.xy){points.push(new OpenLayers.Geometry.Point(coords[0],coords[1],coords[2]));}else{points.push(new OpenLayers.Geometry.Point(coords[1],coords[0],coords[2]));}}}}
+var line=null;if(points.length!=0){if(ring){line=new OpenLayers.Geometry.LinearRing(points);}else{line=new OpenLayers.Geometry.LineString(points);}}
+return line;},multilinestring:function(node){var nodeList=this.getElementsByTagNameNS(node,this.gmlns,"LineString");var components=[];if(nodeList.length>0){var line;for(var i=0;i<nodeList.length;++i){line=this.parseGeometry.linestring.apply(this,[nodeList[i]]);if(line){components.push(line);}}}
+return new OpenLayers.Geometry.MultiLineString(components);},polygon:function(node){var nodeList=this.getElementsByTagNameNS(node,this.gmlns,"LinearRing");var components=[];if(nodeList.length>0){var ring;for(var i=0;i<nodeList.length;++i){ring=this.parseGeometry.linestring.apply(this,[nodeList[i],true]);if(ring){components.push(ring);}}}
+return new OpenLayers.Geometry.Polygon(components);},multipolygon:function(node){var nodeList=this.getElementsByTagNameNS(node,this.gmlns,"Polygon");var components=[];if(nodeList.length>0){var polygon;for(var i=0;i<nodeList.length;++i){polygon=this.parseGeometry.polygon.apply(this,[nodeList[i]]);if(polygon){components.push(polygon);}}}
+return new OpenLayers.Geometry.MultiPolygon(components);},envelope:function(node){var components=[];var coordString;var envelope;var lpoint=this.getElementsByTagNameNS(node,this.gmlns,"lowerCorner");if(lpoint.length>0){var coords=[];if(lpoint.length>0){coordString=lpoint[0].firstChild.nodeValue;coordString=coordString.replace(this.regExes.trimSpace,"");coords=coordString.split(this.regExes.splitSpace);}
+if(coords.length==2){coords[2]=null;}
+if(this.xy){var lowerPoint=new OpenLayers.Geometry.Point(coords[0],coords[1],coords[2]);}else{var lowerPoint=new OpenLayers.Geometry.Point(coords[1],coords[0],coords[2]);}}
+var upoint=this.getElementsByTagNameNS(node,this.gmlns,"upperCorner");if(upoint.length>0){var coords=[];if(upoint.length>0){coordString=upoint[0].firstChild.nodeValue;coordString=coordString.replace(this.regExes.trimSpace,"");coords=coordString.split(this.regExes.splitSpace);}
+if(coords.length==2){coords[2]=null;}
+if(this.xy){var upperPoint=new OpenLayers.Geometry.Point(coords[0],coords[1],coords[2]);}else{var upperPoint=new OpenLayers.Geometry.Point(coords[1],coords[0],coords[2]);}}
+if(lowerPoint&&upperPoint){components.push(new OpenLayers.Geometry.Point(lowerPoint.x,lowerPoint.y));components.push(new OpenLayers.Geometry.Point(upperPoint.x,lowerPoint.y));components.push(new OpenLayers.Geometry.Point(upperPoint.x,upperPoint.y));components.push(new OpenLayers.Geometry.Point(lowerPoint.x,upperPoint.y));components.push(new OpenLayers.Geometry.Point(lowerPoint.x,lowerPoint.y));var ring=new OpenLayers.Geometry.LinearRing(components);envelope=new OpenLayers.Geometry.Polygon([ring]);}
+return envelope;},box:function(node){var nodeList=this.getElementsByTagNameNS(node,this.gmlns,"coordinates");var coordString;var coords,beginPoint=null,endPoint=null;if(nodeList.length>0){coordString=nodeList[0].firstChild.nodeValue;coords=coordString.split(" ");if(coords.length==2){beginPoint=coords[0].split(",");endPoint=coords[1].split(",");}}
+if(beginPoint!==null&&endPoint!==null){return new OpenLayers.Bounds(parseFloat(beginPoint[0]),parseFloat(beginPoint[1]),parseFloat(endPoint[0]),parseFloat(endPoint[1]));}}},parseAttributes:function(node){var attributes={};var childNode=node.firstChild;var children,i,child,grandchildren,grandchild,name,value;while(childNode){if(childNode.nodeType==1){children=childNode.childNodes;for(i=0;i<children.length;++i){child=children[i];if(child.nodeType==1){grandchildren=child.childNodes;if(grandchildren.length==1){grandchild=grandchildren[0];if(grandchild.nodeType==3||grandchild.nodeType==4){name=(child.prefix)?child.nodeName.split(":")[1]:child.nodeName;value=grandchild.nodeValue.replace(this.regExes.trimSpace,"");attributes[name]=value;}}else{attributes[child.nodeName.split(":").pop()]=null;}}}
+break;}
+childNode=childNode.nextSibling;}
+return attributes;},write:function(features){if(!(features instanceof Array)){features=[features];}
+var gml=this.createElementNS("http://www.opengis.net/wfs","wfs:"+this.collectionName);for(var i=0;i<features.length;i++){gml.appendChild(this.createFeatureXML(features[i]));}
+return OpenLayers.Format.XML.prototype.write.apply(this,[gml]);},createFeatureXML:function(feature){var geometry=feature.geometry;var geometryNode=this.buildGeometryNode(geometry);var geomContainer=this.createElementNS(this.featureNS,this.featurePrefix+":"+
+this.geometryName);geomContainer.appendChild(geometryNode);var featureNode=this.createElementNS(this.gmlns,"gml:"+this.featureName);var featureContainer=this.createElementNS(this.featureNS,this.featurePrefix+":"+
+this.layerName);var fid=feature.fid||feature.id;featureContainer.setAttribute("fid",fid);featureContainer.appendChild(geomContainer);for(var attr in feature.attributes){var attrText=this.createTextNode(feature.attributes[attr]);var nodename=attr.substring(attr.lastIndexOf(":")+1);var attrContainer=this.createElementNS(this.featureNS,this.featurePrefix+":"+
+nodename);attrContainer.appendChild(attrText);featureContainer.appendChild(attrContainer);}
+featureNode.appendChild(featureContainer);return featureNode;},buildGeometryNode:function(geometry){if(this.externalProjection&&this.internalProjection){geometry=geometry.clone();geometry.transform(this.internalProjection,this.externalProjection);}
+var className=geometry.CLASS_NAME;var type=className.substring(className.lastIndexOf(".")+1);var builder=this.buildGeometry[type.toLowerCase()];return builder.apply(this,[geometry]);},buildGeometry:{point:function(geometry){var gml=this.createElementNS(this.gmlns,"gml:Point");gml.appendChild(this.buildCoordinatesNode(geometry));return gml;},multipoint:function(geometry){var gml=this.createElementNS(this.gmlns,"gml:MultiPoint");var points=geometry.components;var pointMember,pointGeom;for(var i=0;i<points.length;i++){pointMember=this.createElementNS(this.gmlns,"gml:pointMember");pointGeom=this.buildGeometry.point.apply(this,[points[i]]);pointMember.appendChild(pointGeom);gml.appendChild(pointMember);}
+return gml;},linestring:function(geometry){var gml=this.createElementNS(this.gmlns,"gml:LineString");gml.appendChild(this.buildCoordinatesNode(geometry));return gml;},multilinestring:function(geometry){var gml=this.createElementNS(this.gmlns,"gml:MultiLineString");var lines=geometry.components;var lineMember,lineGeom;for(var i=0;i<lines.length;++i){lineMember=this.createElementNS(this.gmlns,"gml:lineStringMember");lineGeom=this.buildGeometry.linestring.apply(this,[lines[i]]);lineMember.appendChild(lineGeom);gml.appendChild(lineMember);}
+return gml;},linearring:function(geometry){var gml=this.createElementNS(this.gmlns,"gml:LinearRing");gml.appendChild(this.buildCoordinatesNode(geometry));return gml;},polygon:function(geometry){var gml=this.createElementNS(this.gmlns,"gml:Polygon");var rings=geometry.components;var ringMember,ringGeom,type;for(var i=0;i<rings.length;++i){type=(i==0)?"outerBoundaryIs":"innerBoundaryIs";ringMember=this.createElementNS(this.gmlns,"gml:"+type);ringGeom=this.buildGeometry.linearring.apply(this,[rings[i]]);ringMember.appendChild(ringGeom);gml.appendChild(ringMember);}
+return gml;},multipolygon:function(geometry){var gml=this.createElementNS(this.gmlns,"gml:MultiPolygon");var polys=geometry.components;var polyMember,polyGeom;for(var i=0;i<polys.length;++i){polyMember=this.createElementNS(this.gmlns,"gml:polygonMember");polyGeom=this.buildGeometry.polygon.apply(this,[polys[i]]);polyMember.appendChild(polyGeom);gml.appendChild(polyMember);}
+return gml;},bounds:function(bounds){var gml=this.createElementNS(this.gmlns,"gml:Box");gml.appendChild(this.buildCoordinatesNode(bounds));return gml;}},buildCoordinatesNode:function(geometry){var coordinatesNode=this.createElementNS(this.gmlns,"gml:coordinates");coordinatesNode.setAttribute("decimal",".");coordinatesNode.setAttribute("cs",",");coordinatesNode.setAttribute("ts"," ");var parts=[];if(geometry instanceof OpenLayers.Bounds){parts.push(geometry.left+","+geometry.bottom);parts.push(geometry.right+","+geometry.top);}else{var points=(geometry.components)?geometry.components:[geometry];for(var i=0;i<points.length;i++){parts.push(points[i].x+","+points[i].y);}}
+var txtNode=this.createTextNode(parts.join(" "));coordinatesNode.appendChild(txtNode);return coordinatesNode;},CLASS_NAME:"OpenLayers.Format.GML"});OpenLayers.Format.GeoJSON=OpenLayers.Class(OpenLayers.Format.JSON,{ignoreExtraDims:false,initialize:function(options){OpenLayers.Format.JSON.prototype.initialize.apply(this,[options]);},read:function(json,type,filter){type=(type)?type:"FeatureCollection";var results=null;var obj=null;if(typeof json=="string"){obj=OpenLayers.Format.JSON.prototype.read.apply(this,[json,filter]);}else{obj=json;}
+if(!obj){OpenLayers.Console.error("Bad JSON: "+json);}else if(typeof(obj.type)!="string"){OpenLayers.Console.error("Bad GeoJSON - no type: "+json);}else if(this.isValidType(obj,type)){switch(type){case"Geometry":try{results=this.parseGeometry(obj);}catch(err){OpenLayers.Console.error(err);}
+break;case"Feature":try{results=this.parseFeature(obj);results.type="Feature";}catch(err){OpenLayers.Console.error(err);}
+break;case"FeatureCollection":results=[];switch(obj.type){case"Feature":try{results.push(this.parseFeature(obj));}catch(err){results=null;OpenLayers.Console.error(err);}
+break;case"FeatureCollection":for(var i=0,len=obj.features.length;i<len;++i){try{results.push(this.parseFeature(obj.features[i]));}catch(err){results=null;OpenLayers.Console.error(err);}}
+break;default:try{var geom=this.parseGeometry(obj);results.push(new OpenLayers.Feature.Vector(geom));}catch(err){results=null;OpenLayers.Console.error(err);}}
+break;}}
+return results;},isValidType:function(obj,type){var valid=false;switch(type){case"Geometry":if(OpenLayers.Util.indexOf(["Point","MultiPoint","LineString","MultiLineString","Polygon","MultiPolygon","Box","GeometryCollection"],obj.type)==-1){OpenLayers.Console.error("Unsupported geometry type: "+
+obj.type);}else{valid=true;}
+break;case"FeatureCollection":valid=true;break;default:if(obj.type==type){valid=true;}else{OpenLayers.Console.error("Cannot convert types from "+
+obj.type+" to "+type);}}
+return valid;},parseFeature:function(obj){var feature,geometry,attributes,bbox;attributes=(obj.properties)?obj.properties:{};bbox=(obj.geometry&&obj.geometry.bbox)||obj.bbox;try{geometry=this.parseGeometry(obj.geometry);}catch(err){throw err;}
+feature=new OpenLayers.Feature.Vector(geometry,attributes);if(bbox){feature.bounds=OpenLayers.Bounds.fromArray(bbox);}
+if(obj.id){feature.fid=obj.id;}
+return feature;},parseGeometry:function(obj){if(obj==null){return null;}
+var geometry,collection=false;if(obj.type=="GeometryCollection"){if(!(obj.geometries instanceof Array)){throw"GeometryCollection must have geometries array: "+obj;}
+var numGeom=obj.geometries.length;var components=new Array(numGeom);for(var i=0;i<numGeom;++i){components[i]=this.parseGeometry.apply(this,[obj.geometries[i]]);}
+geometry=new OpenLayers.Geometry.Collection(components);collection=true;}else{if(!(obj.coordinates instanceof Array)){throw"Geometry must have coordinates array: "+obj;}
+if(!this.parseCoords[obj.type.toLowerCase()]){throw"Unsupported geometry type: "+obj.type;}
+try{geometry=this.parseCoords[obj.type.toLowerCase()].apply(this,[obj.coordinates]);}catch(err){throw err;}}
+if(this.internalProjection&&this.externalProjection&&!collection){geometry.transform(this.externalProjection,this.internalProjection);}
+return geometry;},parseCoords:{"point":function(array){if(this.ignoreExtraDims==false&&array.length!=2){throw"Only 2D points are supported: "+array;}
+return new OpenLayers.Geometry.Point(array[0],array[1]);},"multipoint":function(array){var points=[];var p=null;for(var i=0,len=array.length;i<len;++i){try{p=this.parseCoords["point"].apply(this,[array[i]]);}catch(err){throw err;}
+points.push(p);}
+return new OpenLayers.Geometry.MultiPoint(points);},"linestring":function(array){var points=[];var p=null;for(var i=0,len=array.length;i<len;++i){try{p=this.parseCoords["point"].apply(this,[array[i]]);}catch(err){throw err;}
+points.push(p);}
+return new OpenLayers.Geometry.LineString(points);},"multilinestring":function(array){var lines=[];var l=null;for(var i=0,len=array.length;i<len;++i){try{l=this.parseCoords["linestring"].apply(this,[array[i]]);}catch(err){throw err;}
+lines.push(l);}
+return new OpenLayers.Geometry.MultiLineString(lines);},"polygon":function(array){var rings=[];var r,l;for(var i=0,len=array.length;i<len;++i){try{l=this.parseCoords["linestring"].apply(this,[array[i]]);}catch(err){throw err;}
+r=new OpenLayers.Geometry.LinearRing(l.components);rings.push(r);}
+return new OpenLayers.Geometry.Polygon(rings);},"multipolygon":function(array){var polys=[];var p=null;for(var i=0,len=array.length;i<len;++i){try{p=this.parseCoords["polygon"].apply(this,[array[i]]);}catch(err){throw err;}
+polys.push(p);}
+return new OpenLayers.Geometry.MultiPolygon(polys);},"box":function(array){if(array.length!=2){throw"GeoJSON box coordinates must have 2 elements";}
+return new OpenLayers.Geometry.Polygon([new OpenLayers.Geometry.LinearRing([new OpenLayers.Geometry.Point(array[0][0],array[0][1]),new OpenLayers.Geometry.Point(array[1][0],array[0][1]),new OpenLayers.Geometry.Point(array[1][0],array[1][1]),new OpenLayers.Geometry.Point(array[0][0],array[1][1]),new OpenLayers.Geometry.Point(array[0][0],array[0][1])])]);}},write:function(obj,pretty){var geojson={"type":null};if(obj instanceof Array){geojson.type="FeatureCollection";var numFeatures=obj.length;geojson.features=new Array(numFeatures);for(var i=0;i<numFeatures;++i){var element=obj[i];if(!element instanceof OpenLayers.Feature.Vector){var msg="FeatureCollection only supports collections "+"of features: "+element;throw msg;}
+geojson.features[i]=this.extract.feature.apply(this,[element]);}}else if(obj.CLASS_NAME.indexOf("OpenLayers.Geometry")==0){geojson=this.extract.geometry.apply(this,[obj]);}else if(obj instanceof OpenLayers.Feature.Vector){geojson=this.extract.feature.apply(this,[obj]);if(obj.layer&&obj.layer.projection){geojson.crs=this.createCRSObject(obj);}}
+return OpenLayers.Format.JSON.prototype.write.apply(this,[geojson,pretty]);},createCRSObject:function(object){var proj=object.layer.projection.toString();var crs={};if(proj.match(/epsg:/i)){var code=parseInt(proj.substring(proj.indexOf(":")+1));if(code==4326){crs={"type":"OGC","properties":{"urn":"urn:ogc:def:crs:OGC:1.3:CRS84"}};}else{crs={"type":"EPSG","properties":{"code":code}};}}
+return crs;},extract:{'feature':function(feature){var geom=this.extract.geometry.apply(this,[feature.geometry]);return{"type":"Feature","id":feature.fid==null?feature.id:feature.fid,"properties":feature.attributes,"geometry":geom};},'geometry':function(geometry){if(geometry==null){return null;}
+if(this.internalProjection&&this.externalProjection){geometry=geometry.clone();geometry.transform(this.internalProjection,this.externalProjection);}
+var geometryType=geometry.CLASS_NAME.split('.')[2];var data=this.extract[geometryType.toLowerCase()].apply(this,[geometry]);var json;if(geometryType=="Collection"){json={"type":"GeometryCollection","geometries":data};}else{json={"type":geometryType,"coordinates":data};}
+return json;},'point':function(point){return[point.x,point.y];},'multipoint':function(multipoint){var array=[];for(var i=0,len=multipoint.components.length;i<len;++i){array.push(this.extract.point.apply(this,[multipoint.components[i]]));}
+return array;},'linestring':function(linestring){var array=[];for(var i=0,len=linestring.components.length;i<len;++i){array.push(this.extract.point.apply(this,[linestring.components[i]]));}
+return array;},'multilinestring':function(multilinestring){var array=[];for(var i=0,len=multilinestring.components.length;i<len;++i){array.push(this.extract.linestring.apply(this,[multilinestring.components[i]]));}
+return array;},'polygon':function(polygon){var array=[];for(var i=0,len=polygon.components.length;i<len;++i){array.push(this.extract.linestring.apply(this,[polygon.components[i]]));}
+return array;},'multipolygon':function(multipolygon){var array=[];for(var i=0,len=multipolygon.components.length;i<len;++i){array.push(this.extract.polygon.apply(this,[multipolygon.components[i]]));}
+return array;},'collection':function(collection){var len=collection.components.length;var array=new Array(len);for(var i=0;i<len;++i){array[i]=this.extract.geometry.apply(this,[collection.components[i]]);}
+return array;}},CLASS_NAME:"OpenLayers.Format.GeoJSON"});OpenLayers.Format.ArcXML.Features=OpenLayers.Class(OpenLayers.Format.XML,{initialize:function(options){OpenLayers.Format.XML.prototype.initialize.apply(this,[options]);},read:function(data){var axl=new OpenLayers.Format.ArcXML();var parsed=axl.read(data);return parsed.features.feature;}});if(!OpenLayers.Format.GML){OpenLayers.Format.GML={};}
+OpenLayers.Format.GML.Base=OpenLayers.Class(OpenLayers.Format.XML,{namespaces:{gml:"http://www.opengis.net/gml",xlink:"http://www.w3.org/1999/xlink",xsi:"http://www.w3.org/2001/XMLSchema-instance",wfs:"http://www.opengis.net/wfs"},defaultPrefix:"gml",schemaLocation:null,featureType:null,featureNS:null,geometryName:"geometry",extractAttributes:true,srsName:null,xy:true,geometryTypes:null,singleFeatureType:null,regExes:{trimSpace:(/^\s*|\s*$/g),removeSpace:(/\s*/g),splitSpace:(/\s+/),trimComma:(/\s*,\s*/g)},initialize:function(options){OpenLayers.Format.XML.prototype.initialize.apply(this,[options]);this.setGeometryTypes();if(options&&options.featureNS){this.setNamespace("feature",options.featureNS);}
+this.singleFeatureType=!options||(typeof options.featureType==="string");},read:function(data){if(typeof data=="string"){data=OpenLayers.Format.XML.prototype.read.apply(this,[data]);}
+if(data&&data.nodeType==9){data=data.documentElement;}
+var features=[];this.readNode(data,{features:features});if(features.length==0){var elements=this.getElementsByTagNameNS(data,this.namespaces.gml,"featureMember");if(elements.length){for(var i=0,len=elements.length;i<len;++i){this.readNode(elements[i],{features:features});}}else{var elements=this.getElementsByTagNameNS(data,this.namespaces.gml,"featureMembers");if(elements.length){this.readNode(elements[0],{features:features});}}}
+return features;},readers:{"gml":{"featureMember":function(node,obj){this.readChildNodes(node,obj);},"featureMembers":function(node,obj){this.readChildNodes(node,obj);},"name":function(node,obj){obj.name=this.getChildValue(node);},"boundedBy":function(node,obj){var container={};this.readChildNodes(node,container);if(container.components&&container.components.length>0){obj.bounds=container.components[0];}},"Point":function(node,container){var obj={points:[]};this.readChildNodes(node,obj);if(!container.components){container.components=[];}
+container.components.push(obj.points[0]);},"coordinates":function(node,obj){var str=this.getChildValue(node).replace(this.regExes.trimSpace,"");str=str.replace(this.regExes.trimComma,",");var pointList=str.split(this.regExes.splitSpace);var coords;var numPoints=pointList.length;var points=new Array(numPoints);for(var i=0;i<numPoints;++i){coords=pointList[i].split(",");if(this.xy){points[i]=new OpenLayers.Geometry.Point(coords[0],coords[1],coords[2]);}else{points[i]=new OpenLayers.Geometry.Point(coords[1],coords[0],coords[2]);}}
+obj.points=points;},"coord":function(node,obj){var coord={};this.readChildNodes(node,coord);if(!obj.points){obj.points=[];}
+obj.points.push(new OpenLayers.Geometry.Point(coord.x,coord.y,coord.z));},"X":function(node,coord){coord.x=this.getChildValue(node);},"Y":function(node,coord){coord.y=this.getChildValue(node);},"Z":function(node,coord){coord.z=this.getChildValue(node);},"MultiPoint":function(node,container){var obj={components:[]};this.readChildNodes(node,obj);container.components=[new OpenLayers.Geometry.MultiPoint(obj.components)];},"pointMember":function(node,obj){this.readChildNodes(node,obj);},"LineString":function(node,container){var obj={};this.readChildNodes(node,obj);if(!container.components){container.components=[];}
+container.components.push(new OpenLayers.Geometry.LineString(obj.points));},"MultiLineString":function(node,container){var obj={components:[]};this.readChildNodes(node,obj);container.components=[new OpenLayers.Geometry.MultiLineString(obj.components)];},"lineStringMember":function(node,obj){this.readChildNodes(node,obj);},"Polygon":function(node,container){var obj={outer:null,inner:[]};this.readChildNodes(node,obj);obj.inner.unshift(obj.outer);if(!container.components){container.components=[];}
+container.components.push(new OpenLayers.Geometry.Polygon(obj.inner));},"LinearRing":function(node,obj){var container={};this.readChildNodes(node,container);obj.components=[new OpenLayers.Geometry.LinearRing(container.points)];},"MultiPolygon":function(node,container){var obj={components:[]};this.readChildNodes(node,obj);container.components=[new OpenLayers.Geometry.MultiPolygon(obj.components)];},"polygonMember":function(node,obj){this.readChildNodes(node,obj);},"GeometryCollection":function(node,container){var obj={components:[]};this.readChildNodes(node,obj);container.components=[new OpenLayers.Geometry.Collection(obj.components)];},"geometryMember":function(node,obj){this.readChildNodes(node,obj);}},"feature":{"*":function(node,obj){var name;var local=node.localName||node.nodeName.split(":").pop();if(obj.features){if(!this.singleFeatureType&&(OpenLayers.Util.indexOf(this.featureType,local)!==-1)){name="_typeName";}else if(local===this.featureType){name="_typeName";}}else{if(node.childNodes.length==0||(node.childNodes.length==1&&node.firstChild.nodeType==3)){if(this.extractAttributes){name="_attribute";}}else{name="_geometry";}}
+if(name){this.readers.feature[name].apply(this,[node,obj]);}},"_typeName":function(node,obj){var container={components:[],attributes:{}};this.readChildNodes(node,container);if(container.name){container.attributes.name=container.name;}
+var feature=new OpenLayers.Feature.Vector(container.components[0],container.attributes);if(!this.singleFeatureType){feature.type=node.nodeName.split(":").pop();feature.namespace=node.namespaceURI;}
+var fid=node.getAttribute("fid")||this.getAttributeNS(node,this.namespaces["gml"],"id");if(fid){feature.fid=fid;}
+if(this.internalProjection&&this.externalProjection&&feature.geometry){feature.geometry.transform(this.externalProjection,this.internalProjection);}
+if(container.bounds){feature.bounds=container.bounds;}
+obj.features.push(feature);},"_geometry":function(node,obj){this.readChildNodes(node,obj);},"_attribute":function(node,obj){var local=node.localName||node.nodeName.split(":").pop();var value=this.getChildValue(node);obj.attributes[local]=value;}},"wfs":{"FeatureCollection":function(node,obj){this.readChildNodes(node,obj);}}},write:function(features){var name;if(features instanceof Array){name="featureMembers";}else{name="featureMember";}
+var root=this.writeNode("gml:"+name,features);this.setAttributeNS(root,this.namespaces["xsi"],"xsi:schemaLocation",this.schemaLocation);return OpenLayers.Format.XML.prototype.write.apply(this,[root]);},writers:{"gml":{"featureMember":function(feature){var node=this.createElementNSPlus("gml:featureMember");this.writeNode("feature:_typeName",feature,node);return node;},"MultiPoint":function(geometry){var node=this.createElementNSPlus("gml:MultiPoint");for(var i=0;i<geometry.components.length;++i){this.writeNode("pointMember",geometry.components[i],node);}
+return node;},"pointMember":function(geometry){var node=this.createElementNSPlus("gml:pointMember");this.writeNode("Point",geometry,node);return node;},"MultiLineString":function(geometry){var node=this.createElementNSPlus("gml:MultiLineString");for(var i=0;i<geometry.components.length;++i){this.writeNode("lineStringMember",geometry.components[i],node);}
+return node;},"lineStringMember":function(geometry){var node=this.createElementNSPlus("gml:lineStringMember");this.writeNode("LineString",geometry,node);return node;},"MultiPolygon":function(geometry){var node=this.createElementNSPlus("gml:MultiPolygon");for(var i=0;i<geometry.components.length;++i){this.writeNode("polygonMember",geometry.components[i],node);}
+return node;},"polygonMember":function(geometry){var node=this.createElementNSPlus("gml:polygonMember");this.writeNode("Polygon",geometry,node);return node;},"GeometryCollection":function(geometry){var node=this.createElementNSPlus("gml:GeometryCollection");for(var i=0,len=geometry.components.length;i<len;++i){this.writeNode("geometryMember",geometry.components[i],node);}
+return node;},"geometryMember":function(geometry){var node=this.createElementNSPlus("gml:geometryMember");var child=this.writeNode("feature:_geometry",geometry);node.appendChild(child.firstChild);return node;}},"feature":{"_typeName":function(feature){var node=this.createElementNSPlus("feature:"+this.featureType,{attributes:{fid:feature.fid}});if(feature.geometry){this.writeNode("feature:_geometry",feature.geometry,node);}
+for(var name in feature.attributes){var value=feature.attributes[name];if(value!=null){this.writeNode("feature:_attribute",{name:name,value:value},node);}}
+return node;},"_geometry":function(geometry){if(this.externalProjection&&this.internalProjection){geometry=geometry.clone().transform(this.internalProjection,this.externalProjection);}
+var node=this.createElementNSPlus("feature:"+this.geometryName);var type=this.geometryTypes[geometry.CLASS_NAME];var child=this.writeNode("gml:"+type,geometry,node);if(this.srsName){child.setAttribute("srsName",this.srsName);}
+return node;},"_attribute":function(obj){return this.createElementNSPlus("feature:"+obj.name,{value:obj.value});}},"wfs":{"FeatureCollection":function(features){var node=this.createElementNSPlus("wfs:FeatureCollection");for(var i=0,len=features.length;i<len;++i){this.writeNode("gml:featureMember",features[i],node);}
+return node;}}},setGeometryTypes:function(){this.geometryTypes={"OpenLayers.Geometry.Point":"Point","OpenLayers.Geometry.MultiPoint":"MultiPoint","OpenLayers.Geometry.LineString":"LineString","OpenLayers.Geometry.MultiLineString":"MultiLineString","OpenLayers.Geometry.Polygon":"Polygon","OpenLayers.Geometry.MultiPolygon":"MultiPolygon","OpenLayers.Geometry.Collection":"GeometryCollection"};},CLASS_NAME:"OpenLayers.Format.GML.Base"});OpenLayers.Format.WFS=OpenLayers.Class(OpenLayers.Format.GML,{layer:null,wfsns:"http://www.opengis.net/wfs",ogcns:"http://www.opengis.net/ogc",initialize:function(options,layer){OpenLayers.Format.GML.prototype.initialize.apply(this,[options]);this.layer=layer;if(this.layer.featureNS){this.featureNS=this.layer.featureNS;}
+if(this.layer.options.geometry_column){this.geometryName=this.layer.options.geometry_column;}
+if(this.layer.options.typename){this.featureName=this.layer.options.typename;}},write:function(features){var transaction=this.createElementNS(this.wfsns,'wfs:Transaction');transaction.setAttribute("version","1.0.0");transaction.setAttribute("service","WFS");for(var i=0;i<features.length;i++){switch(features[i].state){case OpenLayers.State.INSERT:transaction.appendChild(this.insert(features[i]));break;case OpenLayers.State.UPDATE:transaction.appendChild(this.update(features[i]));break;case OpenLayers.State.DELETE:transaction.appendChild(this.remove(features[i]));break;}}
+return OpenLayers.Format.XML.prototype.write.apply(this,[transaction]);},createFeatureXML:function(feature){var geometryNode=this.buildGeometryNode(feature.geometry);var geomContainer=this.createElementNS(this.featureNS,"feature:"+this.geometryName);geomContainer.appendChild(geometryNode);var featureContainer=this.createElementNS(this.featureNS,"feature:"+this.featureName);featureContainer.appendChild(geomContainer);for(var attr in feature.attributes){var attrText=this.createTextNode(feature.attributes[attr]);var nodename=attr;if(attr.search(":")!=-1){nodename=attr.split(":")[1];}
+var attrContainer=this.createElementNS(this.featureNS,"feature:"+nodename);attrContainer.appendChild(attrText);featureContainer.appendChild(attrContainer);}
+return featureContainer;},insert:function(feature){var insertNode=this.createElementNS(this.wfsns,'wfs:Insert');insertNode.appendChild(this.createFeatureXML(feature));return insertNode;},update:function(feature){if(!feature.fid){OpenLayers.Console.userError(OpenLayers.i18n("noFID"));}
+var updateNode=this.createElementNS(this.wfsns,'wfs:Update');updateNode.setAttribute("typeName",this.featurePrefix+':'+this.featureName);updateNode.setAttribute("xmlns:"+this.featurePrefix,this.featureNS);var propertyNode=this.createElementNS(this.wfsns,'wfs:Property');var nameNode=this.createElementNS(this.wfsns,'wfs:Name');var txtNode=this.createTextNode(this.geometryName);nameNode.appendChild(txtNode);propertyNode.appendChild(nameNode);var valueNode=this.createElementNS(this.wfsns,'wfs:Value');var geometryNode=this.buildGeometryNode(feature.geometry);if(feature.layer){geometryNode.setAttribute("srsName",feature.layer.projection.getCode());}
+valueNode.appendChild(geometryNode);propertyNode.appendChild(valueNode);updateNode.appendChild(propertyNode);for(var propName in feature.attributes){propertyNode=this.createElementNS(this.wfsns,'wfs:Property');nameNode=this.createElementNS(this.wfsns,'wfs:Name');nameNode.appendChild(this.createTextNode(propName));propertyNode.appendChild(nameNode);valueNode=this.createElementNS(this.wfsns,'wfs:Value');valueNode.appendChild(this.createTextNode(feature.attributes[propName]));propertyNode.appendChild(valueNode);updateNode.appendChild(propertyNode);}
+var filterNode=this.createElementNS(this.ogcns,'ogc:Filter');var filterIdNode=this.createElementNS(this.ogcns,'ogc:FeatureId');filterIdNode.setAttribute("fid",feature.fid);filterNode.appendChild(filterIdNode);updateNode.appendChild(filterNode);return updateNode;},remove:function(feature){if(!feature.fid){OpenLayers.Console.userError(OpenLayers.i18n("noFID"));return false;}
+var deleteNode=this.createElementNS(this.wfsns,'wfs:Delete');deleteNode.setAttribute("typeName",this.featurePrefix+':'+this.featureName);deleteNode.setAttribute("xmlns:"+this.featurePrefix,this.featureNS);var filterNode=this.createElementNS(this.ogcns,'ogc:Filter');var filterIdNode=this.createElementNS(this.ogcns,'ogc:FeatureId');filterIdNode.setAttribute("fid",feature.fid);filterNode.appendChild(filterIdNode);deleteNode.appendChild(filterNode);return deleteNode;},destroy:function(){this.layer=null;},CLASS_NAME:"OpenLayers.Format.WFS"});OpenLayers.Layer.ArcIMS=OpenLayers.Class(OpenLayers.Layer.Grid,{DEFAULT_PARAMS:{ClientVersion:"9.2",ServiceName:''},tileSize:null,featureCoordSys:"4326",filterCoordSys:"4326",layers:null,async:true,name:"ArcIMS",isBaseLayer:true,DEFAULT_OPTIONS:{tileSize:new OpenLayers.Size(512,512),featureCoordSys:"4326",filterCoordSys:"4326",layers:null,isBaseLayer:true,async:true,name:"ArcIMS"},initialize:function(name,url,options){this.tileSize=new OpenLayers.Size(512,512);this.params=OpenLayers.Util.applyDefaults({ServiceName:options.serviceName},this.DEFAULT_PARAMS);this.options=OpenLayers.Util.applyDefaults(options,this.DEFAULT_OPTIONS);OpenLayers.Layer.Grid.prototype.initialize.apply(this,[name,url,this.params,options]);if(this.transparent){if(!this.isBaseLayer){this.isBaseLayer=false;}
+if(this.format=="image/jpeg"){this.format=OpenLayers.Util.alphaHack()?"image/gif":"image/png";}}
+if(this.options.layers===null){this.options.layers=[];}},destroy:function(){OpenLayers.Layer.Grid.prototype.destroy.apply(this,arguments);},getURL:function(bounds){var url="";bounds=this.adjustBounds(bounds);var axlReq=new OpenLayers.Format.ArcXML(OpenLayers.Util.extend(this.options,{requesttype:"image",envelope:bounds.toArray(),tileSize:this.tileSize}));var req=new OpenLayers.Request.POST({url:this.getFullRequestString(),data:axlReq.write(),async:false});if(req!=null){var doc=req.responseXML;if(!doc||!doc.documentElement){doc=req.responseText;}
+var axlResp=new OpenLayers.Format.ArcXML();var arcxml=axlResp.read(doc);url=this.getUrlOrImage(arcxml.image.output);}
+return url;},getURLasync:function(bounds,scope,prop,callback){bounds=this.adjustBounds(bounds);var axlReq=new OpenLayers.Format.ArcXML(OpenLayers.Util.extend(this.options,{requesttype:"image",envelope:bounds.toArray(),tileSize:this.tileSize}));OpenLayers.Request.POST({url:this.getFullRequestString(),async:true,data:axlReq.write(),callback:function(req){var doc=req.responseXML;if(!doc||!doc.documentElement){doc=req.responseText;}
+var axlResp=new OpenLayers.Format.ArcXML();var arcxml=axlResp.read(doc);scope[prop]=this.getUrlOrImage(arcxml.image.output);callback.apply(scope);},scope:this});},getUrlOrImage:function(output){var ret="";if(output.url){ret=output.url;}else if(output.data){ret="data:image/"+output.type+";base64,"+output.data;}
+return ret;},setLayerQuery:function(id,querydef){for(var lyr=0;lyr<this.options.layers.length;lyr++){if(id==this.options.layers[lyr].id){this.options.layers[lyr].query=querydef;return;}}
+this.options.layers.push({id:id,visible:true,query:querydef});},getFeatureInfo:function(geometry,layer,options){var buffer=options.buffer||1;var callback=options.callback||function(){};var scope=options.scope||window;var requestOptions={};OpenLayers.Util.extend(requestOptions,this.options);requestOptions.requesttype="feature";if(geometry instanceof OpenLayers.LonLat){requestOptions.polygon=null;requestOptions.envelope=[geometry.lon-buffer,geometry.lat-buffer,geometry.lon+buffer,geometry.lat+buffer];}else if(geometry instanceof OpenLayers.Geometry.Polygon){requestOptions.envelope=null;requestOptions.polygon=geometry;}
+var arcxml=new OpenLayers.Format.ArcXML(requestOptions);OpenLayers.Util.extend(arcxml.request.get_feature,options);arcxml.request.get_feature.layer=layer.id;if(typeof layer.query.accuracy=="number"){arcxml.request.get_feature.query.accuracy=layer.query.accuracy;}else{var mapCenter=this.map.getCenter();var viewPx=this.map.getViewPortPxFromLonLat(mapCenter);viewPx.x++;var mapOffCenter=this.map.getLonLatFromPixel(viewPx);arcxml.request.get_feature.query.accuracy=mapOffCenter.lon-mapCenter.lon;}
+arcxml.request.get_feature.query.where=layer.query.where;arcxml.request.get_feature.query.spatialfilter.relation="area_intersection";OpenLayers.Request.POST({url:this.getFullRequestString({'CustomService':'Query'}),data:arcxml.write(),callback:function(request){var response=arcxml.parseResponse(request.responseText);if(!arcxml.iserror()){callback.call(scope,response.features);}else{callback.call(scope,null);}}});},clone:function(obj){if(obj==null){obj=new OpenLayers.Layer.ArcIMS(this.name,this.url,this.getOptions());}
+obj=OpenLayers.Layer.Grid.prototype.clone.apply(this,[obj]);return obj;},addTile:function(bounds,position){return new OpenLayers.Tile.Image(this,position,bounds,null,this.tileSize);},CLASS_NAME:"OpenLayers.Layer.ArcIMS"});OpenLayers.Format.GML.v2=OpenLayers.Class(OpenLayers.Format.GML.Base,{schemaLocation:"http://www.opengis.net/gml http://schemas.opengis.net/gml/2.1.2/feature.xsd",initialize:function(options){OpenLayers.Format.GML.Base.prototype.initialize.apply(this,[options]);},readers:{"gml":OpenLayers.Util.applyDefaults({"outerBoundaryIs":function(node,container){var obj={};this.readChildNodes(node,obj);container.outer=obj.components[0];},"innerBoundaryIs":function(node,container){var obj={};this.readChildNodes(node,obj);container.inner.push(obj.components[0]);},"Box":function(node,container){var obj={};this.readChildNodes(node,obj);if(!container.components){container.components=[];}
+var min=obj.points[0];var max=obj.points[1];container.components.push(new OpenLayers.Bounds(min.x,min.y,max.x,max.y));}},OpenLayers.Format.GML.Base.prototype.readers["gml"]),"feature":OpenLayers.Format.GML.Base.prototype.readers["feature"],"wfs":OpenLayers.Format.GML.Base.prototype.readers["wfs"]},write:function(features){var name;if(features instanceof Array){name="wfs:FeatureCollection";}else{name="gml:featureMember";}
+var root=this.writeNode(name,features);this.setAttributeNS(root,this.namespaces["xsi"],"xsi:schemaLocation",this.schemaLocation);return OpenLayers.Format.XML.prototype.write.apply(this,[root]);},writers:{"gml":OpenLayers.Util.applyDefaults({"Point":function(geometry){var node=this.createElementNSPlus("gml:Point");this.writeNode("coordinates",[geometry],node);return node;},"coordinates":function(points){var numPoints=points.length;var parts=new Array(numPoints);var point;for(var i=0;i<numPoints;++i){point=points[i];if(this.xy){parts[i]=point.x+","+point.y;}else{parts[i]=point.y+","+point.x;}
+if(point.z!=undefined){parts[i]+=","+point.z;}}
+return this.createElementNSPlus("gml:coordinates",{attributes:{decimal:".",cs:",",ts:" "},value:(numPoints==1)?parts[0]:parts.join(" ")});},"LineString":function(geometry){var node=this.createElementNSPlus("gml:LineString");this.writeNode("coordinates",geometry.components,node);return node;},"Polygon":function(geometry){var node=this.createElementNSPlus("gml:Polygon");this.writeNode("outerBoundaryIs",geometry.components[0],node);for(var i=1;i<geometry.components.length;++i){this.writeNode("innerBoundaryIs",geometry.components[i],node);}
+return node;},"outerBoundaryIs":function(ring){var node=this.createElementNSPlus("gml:outerBoundaryIs");this.writeNode("LinearRing",ring,node);return node;},"innerBoundaryIs":function(ring){var node=this.createElementNSPlus("gml:innerBoundaryIs");this.writeNode("LinearRing",ring,node);return node;},"LinearRing":function(ring){var node=this.createElementNSPlus("gml:LinearRing");this.writeNode("coordinates",ring.components,node);return node;},"Box":function(bounds){var node=this.createElementNSPlus("gml:Box");this.writeNode("coordinates",[{x:bounds.left,y:bounds.bottom},{x:bounds.right,y:bounds.top}],node);if(this.srsName){node.setAttribute("srsName",this.srsName);}
+return node;}},OpenLayers.Format.GML.Base.prototype.writers["gml"]),"feature":OpenLayers.Format.GML.Base.prototype.writers["feature"],"wfs":OpenLayers.Format.GML.Base.prototype.writers["wfs"]},CLASS_NAME:"OpenLayers.Format.GML.v2"});OpenLayers.Format.GML.v3=OpenLayers.Class(OpenLayers.Format.GML.Base,{schemaLocation:"http://www.opengis.net/gml http://schemas.opengis.net/gml/3.1.1/profiles/gmlsfProfile/1.0.0/gmlsf.xsd",curve:false,multiCurve:true,surface:false,multiSurface:true,initialize:function(options){OpenLayers.Format.GML.Base.prototype.initialize.apply(this,[options]);},readers:{"gml":OpenLayers.Util.applyDefaults({"featureMembers":function(node,obj){this.readChildNodes(node,obj);},"Curve":function(node,container){var obj={points:[]};this.readChildNodes(node,obj);if(!container.components){container.components=[];}
+container.components.push(new OpenLayers.Geometry.LineString(obj.points));},"segments":function(node,obj){this.readChildNodes(node,obj);},"LineStringSegment":function(node,container){var obj={};this.readChildNodes(node,obj);if(obj.points){Array.prototype.push.apply(container.points,obj.points);}},"pos":function(node,obj){var str=this.getChildValue(node).replace(this.regExes.trimSpace,"");var coords=str.split(this.regExes.splitSpace);var point;if(this.xy){point=new OpenLayers.Geometry.Point(coords[0],coords[1],coords[2]);}else{point=new OpenLayers.Geometry.Point(coords[1],coords[0],coords[2]);}
+obj.points=[point];},"posList":function(node,obj){var str=this.getChildValue(node).replace(this.regExes.trimSpace,"");var coords=str.split(this.regExes.splitSpace);var dim=parseInt(node.getAttribute("dimension"))||2;var j,x,y,z;var numPoints=coords.length/dim;var points=new Array(numPoints);for(var i=0,len=coords.length;i<len;i+=dim){x=coords[i];y=coords[i+1];z=(dim==2)?undefined:coords[i+2];if(this.xy){points[i/dim]=new OpenLayers.Geometry.Point(x,y,z);}else{points[i/dim]=new OpenLayers.Geometry.Point(y,x,z);}}
+obj.points=points;},"Surface":function(node,obj){this.readChildNodes(node,obj);},"patches":function(node,obj){this.readChildNodes(node,obj);},"PolygonPatch":function(node,obj){this.readers.gml.Polygon.apply(this,[node,obj]);},"exterior":function(node,container){var obj={};this.readChildNodes(node,obj);container.outer=obj.components[0];},"interior":function(node,container){var obj={};this.readChildNodes(node,obj);container.inner.push(obj.components[0]);},"MultiCurve":function(node,container){var obj={components:[]};this.readChildNodes(node,obj);if(obj.components.length>0){container.components=[new OpenLayers.Geometry.MultiLineString(obj.components)];}},"curveMember":function(node,obj){this.readChildNodes(node,obj);},"MultiSurface":function(node,container){var obj={components:[]};this.readChildNodes(node,obj);if(obj.components.length>0){container.components=[new OpenLayers.Geometry.MultiPolygon(obj.components)];}},"surfaceMember":function(node,obj){this.readChildNodes(node,obj);},"surfaceMembers":function(node,obj){this.readChildNodes(node,obj);},"pointMembers":function(node,obj){this.readChildNodes(node,obj);},"lineStringMembers":function(node,obj){this.readChildNodes(node,obj);},"polygonMembers":function(node,obj){this.readChildNodes(node,obj);},"geometryMembers":function(node,obj){this.readChildNodes(node,obj);},"Envelope":function(node,container){var obj={points:new Array(2)};this.readChildNodes(node,obj);if(!container.components){container.components=[];}
+var min=obj.points[0];var max=obj.points[1];container.components.push(new OpenLayers.Bounds(min.x,min.y,max.x,max.y));},"lowerCorner":function(node,container){var obj={};this.readers.gml.pos.apply(this,[node,obj]);container.points[0]=obj.points[0];},"upperCorner":function(node,container){var obj={};this.readers.gml.pos.apply(this,[node,obj]);container.points[1]=obj.points[0];}},OpenLayers.Format.GML.Base.prototype.readers["gml"]),"feature":OpenLayers.Format.GML.Base.prototype.readers["feature"],"wfs":OpenLayers.Format.GML.Base.prototype.readers["wfs"]},write:function(features){var name;if(features instanceof Array){name="featureMembers";}else{name="featureMember";}
+var root=this.writeNode("gml:"+name,features);this.setAttributeNS(root,this.namespaces["xsi"],"xsi:schemaLocation",this.schemaLocation);return OpenLayers.Format.XML.prototype.write.apply(this,[root]);},writers:{"gml":OpenLayers.Util.applyDefaults({"featureMembers":function(features){var node=this.createElementNSPlus("gml:featureMembers");for(var i=0,len=features.length;i<len;++i){this.writeNode("feature:_typeName",features[i],node);}
+return node;},"Point":function(geometry){var node=this.createElementNSPlus("gml:Point");this.writeNode("pos",geometry,node);return node;},"pos":function(point){var pos=(this.xy)?(point.x+" "+point.y):(point.y+" "+point.x);return this.createElementNSPlus("gml:pos",{value:pos});},"LineString":function(geometry){var node=this.createElementNSPlus("gml:LineString");this.writeNode("posList",geometry.components,node);return node;},"Curve":function(geometry){var node=this.createElementNSPlus("gml:Curve");this.writeNode("segments",geometry,node);return node;},"segments":function(geometry){var node=this.createElementNSPlus("gml:segments");this.writeNode("LineStringSegment",geometry,node);return node;},"LineStringSegment":function(geometry){var node=this.createElementNSPlus("gml:LineStringSegment");this.writeNode("posList",geometry.components,node);return node;},"posList":function(points){var len=points.length;var parts=new Array(len);var point;for(var i=0;i<len;++i){point=points[i];if(this.xy){parts[i]=point.x+" "+point.y;}else{parts[i]=point.y+" "+point.x;}}
+return this.createElementNSPlus("gml:posList",{value:parts.join(" ")});},"Surface":function(geometry){var node=this.createElementNSPlus("gml:Surface");this.writeNode("patches",geometry,node);return node;},"patches":function(geometry){var node=this.createElementNSPlus("gml:patches");this.writeNode("PolygonPatch",geometry,node);return node;},"PolygonPatch":function(geometry){var node=this.createElementNSPlus("gml:PolygonPatch",{attributes:{interpolation:"planar"}});this.writeNode("exterior",geometry.components[0],node);for(var i=1,len=geometry.components.length;i<len;++i){this.writeNode("interior",geometry.components[i],node);}
+return node;},"Polygon":function(geometry){var node=this.createElementNSPlus("gml:Polygon");this.writeNode("exterior",geometry.components[0],node);for(var i=1,len=geometry.components.length;i<len;++i){this.writeNode("interior",geometry.components[i],node);}
+return node;},"exterior":function(ring){var node=this.createElementNSPlus("gml:exterior");this.writeNode("LinearRing",ring,node);return node;},"interior":function(ring){var node=this.createElementNSPlus("gml:interior");this.writeNode("LinearRing",ring,node);return node;},"LinearRing":function(ring){var node=this.createElementNSPlus("gml:LinearRing");this.writeNode("posList",ring.components,node);return node;},"MultiCurve":function(geometry){var node=this.createElementNSPlus("gml:MultiCurve");for(var i=0,len=geometry.components.length;i<len;++i){this.writeNode("curveMember",geometry.components[i],node);}
+return node;},"curveMember":function(geometry){var node=this.createElementNSPlus("gml:curveMember");if(this.curve){this.writeNode("Curve",geometry,node);}else{this.writeNode("LineString",geometry,node);}
+return node;},"MultiSurface":function(geometry){var node=this.createElementNSPlus("gml:MultiSurface");for(var i=0,len=geometry.components.length;i<len;++i){this.writeNode("surfaceMember",geometry.components[i],node);}
+return node;},"surfaceMember":function(polygon){var node=this.createElementNSPlus("gml:surfaceMember");if(this.surface){this.writeNode("Surface",polygon,node);}else{this.writeNode("Polygon",polygon,node);}
+return node;},"Envelope":function(bounds){var node=this.createElementNSPlus("gml:Envelope");this.writeNode("lowerCorner",bounds,node);this.writeNode("upperCorner",bounds,node);if(this.srsName){node.setAttribute("srsName",this.srsName);}
+return node;},"lowerCorner":function(bounds){var pos=(this.xy)?(bounds.left+" "+bounds.bottom):(bounds.bottom+" "+bounds.left);return this.createElementNSPlus("gml:lowerCorner",{value:pos});},"upperCorner":function(bounds){var pos=(this.xy)?(bounds.right+" "+bounds.top):(bounds.top+" "+bounds.right);return this.createElementNSPlus("gml:upperCorner",{value:pos});}},OpenLayers.Format.GML.Base.prototype.writers["gml"]),"feature":OpenLayers.Format.GML.Base.prototype.writers["feature"],"wfs":OpenLayers.Format.GML.Base.prototype.writers["wfs"]},setGeometryTypes:function(){this.geometryTypes={"OpenLayers.Geometry.Point":"Point","OpenLayers.Geometry.MultiPoint":"MultiPoint","OpenLayers.Geometry.LineString":(this.curve===true)?"Curve":"LineString","OpenLayers.Geometry.MultiLineString":(this.multiCurve===false)?"MultiLineString":"MultiCurve","OpenLayers.Geometry.Polygon":(this.surface===true)?"Surface":"Polygon","OpenLayers.Geometry.MultiPolygon":(this.multiSurface===false)?"MultiPolygon":"MultiSurface","OpenLayers.Geometry.Collection":"GeometryCollection"};},CLASS_NAME:"OpenLayers.Format.GML.v3"});OpenLayers.Format.Atom=OpenLayers.Class(OpenLayers.Format.XML,{namespaces:{atom:"http://www.w3.org/2005/Atom",georss:"http://www.georss.org/georss"},feedTitle:"untitled",defaultEntryTitle:"untitled",gmlParser:null,xy:false,initialize:function(options){OpenLayers.Format.XML.prototype.initialize.apply(this,[options]);},read:function(doc){if(typeof doc=="string"){doc=OpenLayers.Format.XML.prototype.read.apply(this,[doc]);}
+return this.parseFeatures(doc);},write:function(features){var doc;if(features instanceof Array){doc=this.createElementNSPlus("atom:feed");doc.appendChild(this.createElementNSPlus("atom:title",{value:this.feedTitle}));for(var i=0,ii=features.length;i<ii;i++){doc.appendChild(this.buildEntryNode(features[i]));}}
+else{doc=this.buildEntryNode(features);}
+return OpenLayers.Format.XML.prototype.write.apply(this,[doc]);},buildContentNode:function(content){var node=this.createElementNSPlus("atom:content",{attributes:{type:content.type||null}});if(content.src){node.setAttribute("src",content.src);}else{if(content.type=="text"||content.type==null){node.appendChild(this.createTextNode(content.value));}else if(content.type=="html"){if(typeof content.value!="string"){throw"HTML content must be in form of an escaped string";}
+node.appendChild(this.createTextNode(content.value));}else if(content.type=="xhtml"){node.appendChild(content.value);}else if(content.type=="xhtml"||content.type.match(/(\+|\/)xml$/)){node.appendChild(content.value);}
+else{node.appendChild(this.createTextNode(content.value));}}
+return node;},buildEntryNode:function(feature){var attrib=feature.attributes;var atomAttrib=attrib.atom||{};var entryNode=this.createElementNSPlus("atom:entry");if(atomAttrib.authors){var authors=atomAttrib.authors instanceof Array?atomAttrib.authors:[atomAttrib.authors];for(var i=0,ii=authors.length;i<ii;i++){entryNode.appendChild(this.buildPersonConstructNode("author",authors[i]));}}
+if(atomAttrib.categories){var categories=atomAttrib.categories instanceof Array?atomAttrib.categories:[atomAttrib.categories];var category;for(var i=0,ii=categories.length;i<ii;i++){category=categories[i];entryNode.appendChild(this.createElementNSPlus("atom:category",{attributes:{term:category.term,scheme:category.scheme||null,label:category.label||null}}));}}
+if(atomAttrib.content){entryNode.appendChild(this.buildContentNode(atomAttrib.content));}
+if(atomAttrib.contributors){var contributors=atomAttrib.contributors instanceof Array?atomAttrib.contributors:[atomAttrib.contributors];for(var i=0,ii=contributors.length;i<ii;i++){entryNode.appendChild(this.buildPersonConstructNode("contributor",contributors[i]));}}
+if(feature.fid){entryNode.appendChild(this.createElementNSPlus("atom:id",{value:feature.fid}));}
+if(atomAttrib.links){var links=atomAttrib.links instanceof Array?atomAttrib.links:[atomAttrib.links];var link;for(var i=0,ii=links.length;i<ii;i++){link=links[i];entryNode.appendChild(this.createElementNSPlus("atom:link",{attributes:{href:link.href,rel:link.rel||null,type:link.type||null,hreflang:link.hreflang||null,title:link.title||null,length:link.length||null}}));}}
+if(atomAttrib.published){entryNode.appendChild(this.createElementNSPlus("atom:published",{value:atomAttrib.published}));}
+if(atomAttrib.rights){entryNode.appendChild(this.createElementNSPlus("atom:rights",{value:atomAttrib.rights}));}
+if(atomAttrib.summary||attrib.description){entryNode.appendChild(this.createElementNSPlus("atom:summary",{value:atomAttrib.summary||attrib.description}));}
+entryNode.appendChild(this.createElementNSPlus("atom:title",{value:atomAttrib.title||attrib.title||this.defaultEntryTitle}));if(atomAttrib.updated){entryNode.appendChild(this.createElementNSPlus("atom:updated",{value:atomAttrib.updated}));}
+if(feature.geometry){var whereNode=this.createElementNSPlus("georss:where");whereNode.appendChild(this.buildGeometryNode(feature.geometry));entryNode.appendChild(whereNode);}
+return entryNode;},initGmlParser:function(){this.gmlParser=new OpenLayers.Format.GML.v3({xy:this.xy,featureNS:"http://example.com#feature",internalProjection:this.internalProjection,externalProjection:this.externalProjection});},buildGeometryNode:function(geometry){if(!this.gmlParser){this.initGmlParser();}
+var node=this.gmlParser.writeNode("feature:_geometry",geometry);return node.firstChild;},buildPersonConstructNode:function(name,value){var oNames=["uri","email"];var personNode=this.createElementNSPlus("atom:"+name);personNode.appendChild(this.createElementNSPlus("atom:name",{value:value.name}));for(var i=0,ii=oNames.length;i<ii;i++){if(value[oNames[i]]){personNode.appendChild(this.createElementNSPlus("atom:"+oNames[i],{value:value[oNames[i]]}));}}
+return personNode;},getFirstChildValue:function(node,nsuri,name,def){var value;var nodes=this.getElementsByTagNameNS(node,nsuri,name);if(nodes&&nodes.length>0){value=this.getChildValue(nodes[0],def);}else{value=def;}
+return value;},parseFeature:function(node){var atomAttrib={};var value=null;var nodes=null;var attval=null;var atomns=this.namespaces.atom;this.parsePersonConstructs(node,"author",atomAttrib);nodes=this.getElementsByTagNameNS(node,atomns,"category");if(nodes.length>0){atomAttrib.categories=[];}
+for(var i=0,ii=nodes.length;i<ii;i++){value={};value.term=nodes[i].getAttribute("term");attval=nodes[i].getAttribute("scheme");if(attval){value.scheme=attval;}
+attval=nodes[i].getAttribute("label");if(attval){value.label=attval;}
+atomAttrib.categories.push(value);}
+nodes=this.getElementsByTagNameNS(node,atomns,"content");if(nodes.length>0){value={};attval=nodes[0].getAttribute("type");if(attval){value.type=attval;}
+attval=nodes[0].getAttribute("src");if(attval){value.src=attval;}else{if(value.type=="text"||value.type=="html"||value.type==null){value.value=this.getFirstChildValue(node,atomns,"content",null);}else if(value.type=="xhtml"||value.type.match(/(\+|\/)xml$/)){value.value=this.getChildEl(nodes[0]);}else{value.value=this.getFirstChildValue(node,atomns,"content",null);}
+atomAttrib.content=value;}}
+this.parsePersonConstructs(node,"contributor",atomAttrib);atomAttrib.id=this.getFirstChildValue(node,atomns,"id",null);nodes=this.getElementsByTagNameNS(node,atomns,"link");if(nodes.length>0){atomAttrib.links=new Array(nodes.length);}
+var oAtts=["rel","type","hreflang","title","length"];for(var i=0,ii=nodes.length;i<ii;i++){value={};value.href=nodes[i].getAttribute("href");for(var j=0,jj=oAtts.length;j<jj;j++){attval=nodes[i].getAttribute(oAtts[j]);if(attval){value[oAtts[j]]=attval;}}
+atomAttrib.links[i]=value;}
+value=this.getFirstChildValue(node,atomns,"published",null);if(value){atomAttrib.published=value;}
+value=this.getFirstChildValue(node,atomns,"rights",null);if(value){atomAttrib.rights=value;}
+value=this.getFirstChildValue(node,atomns,"summary",null);if(value){atomAttrib.summary=value;}
+atomAttrib.title=this.getFirstChildValue(node,atomns,"title",null);atomAttrib.updated=this.getFirstChildValue(node,atomns,"updated",null);var featureAttrib={title:atomAttrib.title,description:atomAttrib.summary,atom:atomAttrib};var geometry=this.parseLocations(node)[0];var feature=new OpenLayers.Feature.Vector(geometry,featureAttrib);feature.fid=atomAttrib.id;return feature;},parseFeatures:function(node){var features=[];var entries=this.getElementsByTagNameNS(node,this.namespaces.atom,"entry");if(entries.length==0){entries=[node];}
+for(var i=0,ii=entries.length;i<ii;i++){features.push(this.parseFeature(entries[i]));}
+return features;},parseLocations:function(node){var georssns=this.namespaces.georss;var locations={components:[]};var where=this.getElementsByTagNameNS(node,georssns,"where");if(where&&where.length>0){if(!this.gmlParser){this.initGmlParser();}
+for(var i=0,ii=where.length;i<ii;i++){this.gmlParser.readChildNodes(where[i],locations);}}
+var components=locations.components;var point=this.getElementsByTagNameNS(node,georssns,"point");if(point&&point.length>0){for(var i=0,ii=point.length;i<ii;i++){var xy=OpenLayers.String.trim(point[i].firstChild.nodeValue).split(/\s+/);if(xy.length!=2){xy=OpenLayers.String.trim(point[i].firstChild.nodeValue).split(/\s*,\s*/);}
+components.push(new OpenLayers.Geometry.Point(parseFloat(xy[1]),parseFloat(xy[0])));}}
+var line=this.getElementsByTagNameNS(node,georssns,"line");if(line&&line.length>0){var coords;var p;var points;for(var i=0,ii=line.length;i<ii;i++){coords=OpenLayers.String.trim(line[i].firstChild.nodeValue).split(/\s+/);points=[];for(var j=0,jj=coords.length;j<jj;j+=2){p=new OpenLayers.Geometry.Point(parseFloat(coords[j+1]),parseFloat(coords[j]));points.push(p);}
+components.push(new OpenLayers.Geometry.LineString(points));}}
+var polygon=this.getElementsByTagNameNS(node,georssns,"polygon");if(polygon&&polygon.length>0){var coords;var p;var points;for(var i=0,ii=polygon.length;i<ii;i++){coords=OpenLayers.String.trim(polygon[i].firstChild.nodeValue).split(/\s+/);points=[];for(var j=0,jj=coords.length;j<jj;j+=2){p=new OpenLayers.Geometry.Point(parseFloat(coords[j+1]),parseFloat(coords[j]));points.push(p);}
+components.push(new OpenLayers.Geometry.Polygon([new OpenLayers.Geometry.LinearRing(components)]));}}
+if(this.internalProjection&&this.externalProjection){for(var i=0,ii=components.length;i<ii;i++){if(components[i]){components[i].transform(this.externalProjection,this.internalProjection);}}}
+return components;},parsePersonConstructs:function(node,name,data){var persons=[];var atomns=this.namespaces.atom;var nodes=this.getElementsByTagNameNS(node,atomns,name);var oAtts=["uri","email"];for(var i=0,ii=nodes.length;i<ii;i++){var value={};value.name=this.getFirstChildValue(nodes[i],atomns,"name",null);for(var j=0,jj=oAtts.length;j<jj;j++){var attval=this.getFirstChildValue(nodes[i],atomns,oAtts[j],null);if(attval){value[oAtts[j]]=attval;}}
+persons.push(value);}
+if(persons.length>0){data[name+"s"]=persons;}},CLASS_NAME:"OpenLayers.Format.Atom"});OpenLayers.Format.Filter.v1_0_0=OpenLayers.Class(OpenLayers.Format.GML.v2,OpenLayers.Format.Filter.v1,{VERSION:"1.0.0",schemaLocation:"http://www.opengis.net/ogc/filter/1.0.0/filter.xsd",initialize:function(options){OpenLayers.Format.GML.v2.prototype.initialize.apply(this,[options]);},readers:{"ogc":OpenLayers.Util.applyDefaults({"PropertyIsEqualTo":function(node,obj){var filter=new OpenLayers.Filter.Comparison({type:OpenLayers.Filter.Comparison.EQUAL_TO});this.readChildNodes(node,filter);obj.filters.push(filter);},"PropertyIsNotEqualTo":function(node,obj){var filter=new OpenLayers.Filter.Comparison({type:OpenLayers.Filter.Comparison.NOT_EQUAL_TO});this.readChildNodes(node,filter);obj.filters.push(filter);},"PropertyIsLike":function(node,obj){var filter=new OpenLayers.Filter.Comparison({type:OpenLayers.Filter.Comparison.LIKE});this.readChildNodes(node,filter);var wildCard=node.getAttribute("wildCard");var singleChar=node.getAttribute("singleChar");var esc=node.getAttribute("escape");filter.value2regex(wildCard,singleChar,esc);obj.filters.push(filter);}},OpenLayers.Format.Filter.v1.prototype.readers["ogc"]),"gml":OpenLayers.Format.GML.v2.prototype.readers["gml"],"feature":OpenLayers.Format.GML.v2.prototype.readers["feature"]},writers:{"ogc":OpenLayers.Util.applyDefaults({"PropertyIsEqualTo":function(filter){var node=this.createElementNSPlus("ogc:PropertyIsEqualTo");this.writeNode("PropertyName",filter,node);this.writeNode("Literal",filter.value,node);return node;},"PropertyIsNotEqualTo":function(filter){var node=this.createElementNSPlus("ogc:PropertyIsNotEqualTo");this.writeNode("PropertyName",filter,node);this.writeNode("Literal",filter.value,node);return node;},"PropertyIsLike":function(filter){var node=this.createElementNSPlus("ogc:PropertyIsLike",{attributes:{wildCard:"*",singleChar:".",escape:"!"}});this.writeNode("PropertyName",filter,node);this.writeNode("Literal",filter.regex2value(),node);return node;},"BBOX":function(filter){var node=this.createElementNSPlus("ogc:BBOX");this.writeNode("PropertyName",filter,node);var box=this.writeNode("gml:Box",filter.value,node);if(filter.projection){box.setAttribute("srsName",filter.projection);}
+return node;}},OpenLayers.Format.Filter.v1.prototype.writers["ogc"]),"gml":OpenLayers.Format.GML.v2.prototype.writers["gml"],"feature":OpenLayers.Format.GML.v2.prototype.writers["feature"]},writeSpatial:function(filter,name){var node=this.createElementNSPlus("ogc:"+name);this.writeNode("PropertyName",filter,node);var child;if(filter.value instanceof OpenLayers.Geometry){child=this.writeNode("feature:_geometry",filter.value).firstChild;}else{child=this.writeNode("gml:Box",filter.value);}
+if(filter.projection){child.setAttribute("srsName",filter.projection);}
+node.appendChild(child);return node;},CLASS_NAME:"OpenLayers.Format.Filter.v1_0_0"});OpenLayers.Format.Filter.v1_1_0=OpenLayers.Class(OpenLayers.Format.GML.v3,OpenLayers.Format.Filter.v1,{VERSION:"1.1.0",schemaLocation:"http://www.opengis.net/ogc/filter/1.1.0/filter.xsd",initialize:function(options){OpenLayers.Format.GML.v3.prototype.initialize.apply(this,[options]);},readers:{"ogc":OpenLayers.Util.applyDefaults({"PropertyIsEqualTo":function(node,obj){var matchCase=node.getAttribute("matchCase");var filter=new OpenLayers.Filter.Comparison({type:OpenLayers.Filter.Comparison.EQUAL_TO,matchCase:!(matchCase==="false"||matchCase==="0")});this.readChildNodes(node,filter);obj.filters.push(filter);},"PropertyIsNotEqualTo":function(node,obj){var matchCase=node.getAttribute("matchCase");var filter=new OpenLayers.Filter.Comparison({type:OpenLayers.Filter.Comparison.NOT_EQUAL_TO,matchCase:!(matchCase==="false"||matchCase==="0")});this.readChildNodes(node,filter);obj.filters.push(filter);},"PropertyIsLike":function(node,obj){var filter=new OpenLayers.Filter.Comparison({type:OpenLayers.Filter.Comparison.LIKE});this.readChildNodes(node,filter);var wildCard=node.getAttribute("wildCard");var singleChar=node.getAttribute("singleChar");var esc=node.getAttribute("escapeChar");filter.value2regex(wildCard,singleChar,esc);obj.filters.push(filter);}},OpenLayers.Format.Filter.v1.prototype.readers["ogc"]),"gml":OpenLayers.Format.GML.v3.prototype.readers["gml"],"feature":OpenLayers.Format.GML.v3.prototype.readers["feature"]},writers:{"ogc":OpenLayers.Util.applyDefaults({"PropertyIsEqualTo":function(filter){var node=this.createElementNSPlus("ogc:PropertyIsEqualTo",{attributes:{matchCase:filter.matchCase}});this.writeNode("PropertyName",filter,node);this.writeNode("Literal",filter.value,node);return node;},"PropertyIsNotEqualTo":function(filter){var node=this.createElementNSPlus("ogc:PropertyIsNotEqualTo",{attributes:{matchCase:filter.matchCase}});this.writeNode("PropertyName",filter,node);this.writeNode("Literal",filter.value,node);return node;},"PropertyIsLike":function(filter){var node=this.createElementNSPlus("ogc:PropertyIsLike",{attributes:{wildCard:"*",singleChar:".",escapeChar:"!"}});this.writeNode("PropertyName",filter,node);this.writeNode("Literal",filter.regex2value(),node);return node;},"BBOX":function(filter){var node=this.createElementNSPlus("ogc:BBOX");this.writeNode("PropertyName",filter,node);var box=this.writeNode("gml:Envelope",filter.value);if(filter.projection){box.setAttribute("srsName",filter.projection);}
+node.appendChild(box);return node;}},OpenLayers.Format.Filter.v1.prototype.writers["ogc"]),"gml":OpenLayers.Format.GML.v3.prototype.writers["gml"],"feature":OpenLayers.Format.GML.v3.prototype.writers["feature"]},writeSpatial:function(filter,name){var node=this.createElementNSPlus("ogc:"+name);this.writeNode("PropertyName",filter,node);var child;if(filter.value instanceof OpenLayers.Geometry){child=this.writeNode("feature:_geometry",filter.value).firstChild;}else{child=this.writeNode("gml:Envelope",filter.value);}
+if(filter.projection){child.setAttribute("srsName",filter.projection);}
+node.appendChild(child);return node;},CLASS_NAME:"OpenLayers.Format.Filter.v1_1_0"});OpenLayers.Format.SOSCapabilities.v1_0_0=OpenLayers.Class(OpenLayers.Format.SOSCapabilities,{namespaces:{ows:"http://www.opengis.net/ows/1.1",sos:"http://www.opengis.net/sos/1.0",gml:"http://www.opengis.net/gml",xlink:"http://www.w3.org/1999/xlink"},regExes:{trimSpace:(/^\s*|\s*$/g),removeSpace:(/\s*/g),splitSpace:(/\s+/),trimComma:(/\s*,\s*/g)},initialize:function(options){OpenLayers.Format.XML.prototype.initialize.apply(this,[options]);this.options=options;},read:function(data){if(typeof data=="string"){data=OpenLayers.Format.XML.prototype.read.apply(this,[data]);}
+if(data&&data.nodeType==9){data=data.documentElement;}
+var capabilities={};this.readNode(data,capabilities);return capabilities;},readers:{"gml":OpenLayers.Util.applyDefaults({"name":function(node,obj){obj.name=this.getChildValue(node);},"TimePeriod":function(node,obj){obj.timePeriod={};this.readChildNodes(node,obj.timePeriod);},"beginPosition":function(node,timePeriod){timePeriod.beginPosition=this.getChildValue(node);},"endPosition":function(node,timePeriod){timePeriod.endPosition=this.getChildValue(node);}},OpenLayers.Format.GML.v3.prototype.readers["gml"]),"sos":{"Capabilities":function(node,obj){this.readChildNodes(node,obj);},"Contents":function(node,obj){obj.contents={};this.readChildNodes(node,obj.contents);},"ObservationOfferingList":function(node,contents){contents.offeringList={};this.readChildNodes(node,contents.offeringList);},"ObservationOffering":function(node,offeringList){var id=this.getAttributeNS(node,this.namespaces.gml,"id");offeringList[id]={procedures:[],observedProperties:[],featureOfInterestIds:[],responseFormats:[],resultModels:[],responseModes:[]};this.readChildNodes(node,offeringList[id]);},"time":function(node,offering){offering.time={};this.readChildNodes(node,offering.time);},"procedure":function(node,offering){offering.procedures.push(this.getAttributeNS(node,this.namespaces.xlink,"href"));},"observedProperty":function(node,offering){offering.observedProperties.push(this.getAttributeNS(node,this.namespaces.xlink,"href"));},"featureOfInterest":function(node,offering){offering.featureOfInterestIds.push(this.getAttributeNS(node,this.namespaces.xlink,"href"));},"responseFormat":function(node,offering){offering.responseFormats.push(this.getChildValue(node));},"resultModel":function(node,offering){offering.resultModels.push(this.getChildValue(node));},"responseMode":function(node,offering){offering.responseModes.push(this.getChildValue(node));;}},"ows":OpenLayers.Format.OWSCommon.v1_1_0.prototype.readers["ows"]},CLASS_NAME:"OpenLayers.Format.SOSCapabilities.v1_0_0"});OpenLayers.Format.SOSGetFeatureOfInterest=OpenLayers.Class(OpenLayers.Format.XML,{VERSION:"1.0.0",namespaces:{sos:"http://www.opengis.net/sos/1.0",gml:"http://www.opengis.net/gml",sa:"http://www.opengis.net/sampling/1.0",xsi:"http://www.w3.org/2001/XMLSchema-instance"},schemaLocation:"http://www.opengis.net/sos/1.0 http://schemas.opengis.net/sos/1.0.0/sosAll.xsd",defaultPrefix:"sos",regExes:{trimSpace:(/^\s*|\s*$/g),removeSpace:(/\s*/g),splitSpace:(/\s+/),trimComma:(/\s*,\s*/g)},initialize:function(options){OpenLayers.Format.XML.prototype.initialize.apply(this,[options]);},read:function(data){if(typeof data=="string"){data=OpenLayers.Format.XML.prototype.read.apply(this,[data]);}
+if(data&&data.nodeType==9){data=data.documentElement;}
+var info={features:[]};this.readNode(data,info);var features=[];for(var i=0,len=info.features.length;i<len;i++){var container=info.features[i];if(this.internalProjection&&this.externalProjection&&container.components[0]){container.components[0].transform(this.externalProjection,this.internalProjection);}
+var feature=new OpenLayers.Feature.Vector(container.components[0],container.attributes);features.push(feature);}
+return features;},readers:{"sa":{"SamplingPoint":function(node,obj){if(!obj.attributes){var feature={attributes:{}};obj.features.push(feature);obj=feature;}
+obj.attributes.id=this.getAttributeNS(node,this.namespaces.gml,"id");this.readChildNodes(node,obj);},"position":function(node,obj){this.readChildNodes(node,obj);}},"gml":OpenLayers.Util.applyDefaults({"FeatureCollection":function(node,obj){this.readChildNodes(node,obj);},"featureMember":function(node,obj){var feature={attributes:{}};obj.features.push(feature);this.readChildNodes(node,feature);},"name":function(node,obj){obj.attributes.name=this.getChildValue(node);},"pos":function(node,obj){if(!this.externalProjection){this.externalProjection=new OpenLayers.Projection(node.getAttribute("srsName"));}
+OpenLayers.Format.GML.v3.prototype.readers.gml.pos.apply(this,[node,obj]);}},OpenLayers.Format.GML.v3.prototype.readers.gml)},writers:{"sos":{"GetFeatureOfInterest":function(options){var node=this.createElementNSPlus("GetFeatureOfInterest",{attributes:{version:this.VERSION,service:'SOS',"xsi:schemaLocation":this.schemaLocation}});for(var i=0,len=options.fois.length;i<len;i++){this.writeNode("FeatureOfInterestId",{foi:options.fois[i]},node);}
+return node;},"FeatureOfInterestId":function(options){var node=this.createElementNSPlus("FeatureOfInterestId",{value:options.foi});return node;}}},CLASS_NAME:"OpenLayers.Format.SOSGetFeatureOfInterest"});OpenLayers.Format.SOSGetObservation=OpenLayers.Class(OpenLayers.Format.XML,{namespaces:{ows:"http://www.opengis.net/ows",gml:"http://www.opengis.net/gml",sos:"http://www.opengis.net/sos/1.0",ogc:"http://www.opengis.net/ogc",om:"http://www.opengis.net/om/1.0",xlink:"http://www.w3.org/1999/xlink",xsi:"http://www.w3.org/2001/XMLSchema-instance"},regExes:{trimSpace:(/^\s*|\s*$/g),removeSpace:(/\s*/g),splitSpace:(/\s+/),trimComma:(/\s*,\s*/g)},VERSION:"1.0.0",schemaLocation:"http://www.opengis.net/sos/1.0 http://schemas.opengis.net/sos/1.0.0/sosGetObservation.xsd",defaultPrefix:"sos",initialize:function(options){OpenLayers.Format.XML.prototype.initialize.apply(this,[options]);},read:function(data){if(typeof data=="string"){data=OpenLayers.Format.XML.prototype.read.apply(this,[data]);}
+if(data&&data.nodeType==9){data=data.documentElement;}
+var info={measurements:[]};this.readNode(data,info);return info;},write:function(options){var node=this.writeNode("sos:GetObservation",options);node.setAttribute("xmlns:om",this.namespaces.om);this.setAttributeNS(node,this.namespaces.xsi,"xsi:schemaLocation",this.schemaLocation);return OpenLayers.Format.XML.prototype.write.apply(this,[node]);},readers:{"om":{"ObservationCollection":function(node,obj){obj.id=this.getAttributeNS(node,this.namespaces.gml,"id");this.readChildNodes(node,obj);},"member":function(node,observationCollection){this.readChildNodes(node,observationCollection);},"Measurement":function(node,observationCollection){var measurement={};observationCollection.measurements.push(measurement);this.readChildNodes(node,measurement);},"samplingTime":function(node,measurement){var samplingTime={};measurement.samplingTime=samplingTime;this.readChildNodes(node,samplingTime);},"observedProperty":function(node,measurement){measurement.observedProperty=this.getAttributeNS(node,this.namespaces.xlink,"href");this.readChildNodes(node,measurement);},"procedure":function(node,measurement){measurement.procedure=this.getAttributeNS(node,this.namespaces.xlink,"href");this.readChildNodes(node,measurement);},"result":function(node,measurement){var result={};measurement.result=result;if(this.getChildValue(node)!==''){result.value=this.getChildValue(node);result.uom=node.getAttribute("uom");}else{this.readChildNodes(node,result);}}},"gml":OpenLayers.Util.applyDefaults({"TimeInstant":function(node,samplingTime){var timeInstant={};samplingTime.timeInstant=timeInstant;this.readChildNodes(node,timeInstant);},"timePosition":function(node,timeInstant){timeInstant.timePosition=this.getChildValue(node);}},OpenLayers.Format.GML.v3.prototype.readers.gml)},writers:{"sos":{"GetObservation":function(options){var node=this.createElementNSPlus("GetObservation",{attributes:{version:this.VERSION,service:'SOS'}});this.writeNode("offering",options,node);this.writeNode("eventTime",options,node);this.writeNode("procedure",options,node);this.writeNode("observedProperty",options,node);this.writeNode("responseFormat",options,node);this.writeNode("resultModel",options,node);this.writeNode("responseMode",options,node);return node;},"responseFormat":function(options){return this.createElementNSPlus("responseFormat",{value:options.responseFormat});},"procedure":function(options){return this.createElementNSPlus("procedure",{value:options.procedure});},"offering":function(options){return this.createElementNSPlus("offering",{value:options.offering});},"observedProperty":function(options){return this.createElementNSPlus("observedProperty",{value:options.observedProperty});},"eventTime":function(options){var node=this.createElementNSPlus("eventTime");if(options.eventTime==='latest'){this.writeNode("ogc:TM_Equals",options,node);}
+return node;},"resultModel":function(options){return this.createElementNSPlus("resultModel",{value:options.resultModel});},"responseMode":function(options){return this.createElementNSPlus("responseMode",{value:options.responseMode});}},"ogc":{"TM_Equals":function(options){var node=this.createElementNSPlus("ogc:TM_Equals");this.writeNode("ogc:PropertyName",{property:"urn:ogc:data:time:iso8601"},node);if(options.eventTime==='latest'){this.writeNode("gml:TimeInstant",{value:'latest'},node);}
+return node;},"PropertyName":function(options){return this.createElementNSPlus("ogc:PropertyName",{value:options.property});}},"gml":{"TimeInstant":function(options){var node=this.createElementNSPlus("gml:TimeInstant");this.writeNode("gml:timePosition",options,node);return node;},"timePosition":function(options){var node=this.createElementNSPlus("gml:timePosition",{value:options.value});return node;}}},CLASS_NAME:"OpenLayers.Format.SOSGetObservation"});OpenLayers.Format.CSWGetRecords.v2_0_2=OpenLayers.Class(OpenLayers.Format.XML,{namespaces:{xlink:"http://www.w3.org/1999/xlink",xsi:"http://www.w3.org/2001/XMLSchema-instance",csw:"http://www.opengis.net/cat/csw/2.0.2",dc:"http://purl.org/dc/elements/1.1/",dct:"http://purl.org/dc/terms/",ows:"http://www.opengis.net/ows"},defaultPrefix:"csw",version:"2.0.2",schemaLocation:"http://www.opengis.net/cat/csw/2.0.2 http://schemas.opengis.net/csw/2.0.2/CSW-discovery.xsd",requestId:null,resultType:null,outputFormat:null,outputSchema:null,startPosition:null,maxRecords:null,DistributedSearch:null,ResponseHandler:null,Query:null,regExes:{trimSpace:(/^\s*|\s*$/g),removeSpace:(/\s*/g),splitSpace:(/\s+/),trimComma:(/\s*,\s*/g)},initialize:function(options){OpenLayers.Format.XML.prototype.initialize.apply(this,[options]);},read:function(data){if(typeof data=="string"){data=OpenLayers.Format.XML.prototype.read.apply(this,[data]);}
+if(data&&data.nodeType==9){data=data.documentElement;}
+var obj={};this.readNode(data,obj);return obj;},readers:{"csw":{"GetRecordsResponse":function(node,obj){obj.records=[];this.readChildNodes(node,obj);var version=this.getAttributeNS(node,"",'version');if(version!=""){obj.version=version;}},"RequestId":function(node,obj){obj.RequestId=this.getChildValue(node);},"SearchStatus":function(node,obj){obj.SearchStatus={};var timestamp=this.getAttributeNS(node,"",'timestamp');if(timestamp!=""){obj.SearchStatus.timestamp=timestamp;}},"SearchResults":function(node,obj){this.readChildNodes(node,obj);var attrs=node.attributes;var SearchResults={};for(var i=0,len=attrs.length;i<len;++i){if((attrs[i].name=="numberOfRecordsMatched")||(attrs[i].name=="numberOfRecordsReturned")||(attrs[i].name=="nextRecord")){SearchResults[attrs[i].name]=parseInt(attrs[i].nodeValue);}else{SearchResults[attrs[i].name]=attrs[i].nodeValue;}}
+obj.SearchResults=SearchResults;},"SummaryRecord":function(node,obj){var record={type:"SummaryRecord"};this.readChildNodes(node,record);obj.records.push(record);},"BriefRecord":function(node,obj){var record={type:"BriefRecord"};this.readChildNodes(node,record);obj.records.push(record);},"DCMIRecord":function(node,obj){var record={type:"DCMIRecord"};this.readChildNodes(node,record);obj.records.push(record);},"Record":function(node,obj){var record={type:"Record"};this.readChildNodes(node,record);obj.records.push(record);}},"dc":{"*":function(node,obj){var name=node.localName||node.nodeName.split(":").pop();if(!(obj[name]instanceof Array)){obj[name]=new Array();}
+var dc_element={};var attrs=node.attributes;for(var i=0,len=attrs.length;i<len;++i){dc_element[attrs[i].name]=attrs[i].nodeValue;}
+dc_element.value=this.getChildValue(node);obj[name].push(dc_element);}},"dct":{"*":function(node,obj){var name=node.localName||node.nodeName.split(":").pop();if(!(obj[name]instanceof Array)){obj[name]=new Array();}
+obj[name].push(this.getChildValue(node));}},"ows":OpenLayers.Util.applyDefaults({"BoundingBox":function(node,obj){if(obj.bounds){obj.BoundingBox=[{crs:obj.projection,value:[obj.bounds.left,obj.bounds.bottom,obj.bounds.right,obj.bounds.top]}];delete obj.projection;delete obj.bounds;}
+OpenLayers.Format.OWSCommon.v1_0_0.prototype.readers["ows"]["BoundingBox"].apply(this,arguments);}},OpenLayers.Format.OWSCommon.v1_0_0.prototype.readers["ows"])},write:function(options){var node=this.writeNode("csw:GetRecords",options);return OpenLayers.Format.XML.prototype.write.apply(this,[node]);},writers:{"csw":{"GetRecords":function(options){if(!options){options={};}
+var node=this.createElementNSPlus("csw:GetRecords",{attributes:{service:"CSW",version:this.version,requestId:options.requestId||this.requestId,resultType:options.resultType||this.resultType,outputFormat:options.outputFormat||this.outputFormat,outputSchema:options.outputSchema||this.outputSchema,startPosition:options.startPosition||this.startPosition,maxRecords:options.maxRecords||this.maxRecords}});if(options.DistributedSearch||this.DistributedSearch){this.writeNode("csw:DistributedSearch",options.DistributedSearch||this.DistributedSearch,node);}
+var ResponseHandler=options.ResponseHandler||this.ResponseHandler;if(ResponseHandler instanceof Array&&ResponseHandler.length>0){for(var i=0,len=ResponseHandler.length;i<len;i++){this.writeNode("csw:ResponseHandler",ResponseHandler[i],node);}}
+this.writeNode("Query",options.Query||this.Query,node);return node;},"DistributedSearch":function(options){var node=this.createElementNSPlus("csw:DistributedSearch",{attributes:{hopCount:options.hopCount}});return node;},"ResponseHandler":function(options){var node=this.createElementNSPlus("csw:ResponseHandler",{value:options.value});return node;},"Query":function(options){if(!options){options={};}
+var node=this.createElementNSPlus("csw:Query",{attributes:{typeNames:options.typeNames||"csw:Record"}});var ElementName=options.ElementName;if(ElementName instanceof Array&&ElementName.length>0){for(var i=0,len=ElementName.length;i<len;i++){this.writeNode("csw:ElementName",ElementName[i],node);}}else{this.writeNode("csw:ElementSetName",options.ElementSetName||{value:'summary'},node);}
+if(options.Constraint){this.writeNode("csw:Constraint",options.Constraint,node);}
+return node;},"ElementName":function(options){var node=this.createElementNSPlus("csw:ElementName",{value:options.value});return node;},"ElementSetName":function(options){var node=this.createElementNSPlus("csw:ElementSetName",{attributes:{typeNames:options.typeNames},value:options.value});return node;},"Constraint":function(options){var node=this.createElementNSPlus("csw:Constraint",{attributes:{version:options.version}});if(options.Filter){var format=new OpenLayers.Format.Filter({version:options.version});node.appendChild(format.write(options.Filter));}else if(options.CqlText){var child=this.createElementNSPlus("CqlText",{value:options.CqlText.value});node.appendChild(child);}
+return node;}}},CLASS_NAME:"OpenLayers.Format.CSWGetRecords.v2_0_2"});OpenLayers.Format.SLD.v1=OpenLayers.Class(OpenLayers.Format.Filter.v1_0_0,{namespaces:{sld:"http://www.opengis.net/sld",ogc:"http://www.opengis.net/ogc",gml:"http://www.opengis.net/gml",xlink:"http://www.w3.org/1999/xlink",xsi:"http://www.w3.org/2001/XMLSchema-instance"},defaultPrefix:"sld",schemaLocation:null,multipleSymbolizers:false,featureTypeCounter:null,defaultSymbolizer:{fillColor:"#808080",fillOpacity:1,strokeColor:"#000000",strokeOpacity:1,strokeWidth:1,strokeDashstyle:"solid",pointRadius:3,graphicName:"square"},initialize:function(options){OpenLayers.Format.Filter.v1_0_0.prototype.initialize.apply(this,[options]);},read:function(data,options){options=OpenLayers.Util.applyDefaults(options,this.options);var sld={namedLayers:options.namedLayersAsArray===true?[]:{}};this.readChildNodes(data,sld);return sld;},readers:OpenLayers.Util.applyDefaults({"sld":{"StyledLayerDescriptor":function(node,sld){sld.version=node.getAttribute("version");this.readChildNodes(node,sld);},"Name":function(node,obj){obj.name=this.getChildValue(node);},"Title":function(node,obj){obj.title=this.getChildValue(node);},"Abstract":function(node,obj){obj.description=this.getChildValue(node);},"NamedLayer":function(node,sld){var layer={userStyles:[],namedStyles:[]};this.readChildNodes(node,layer);for(var i=0,len=layer.userStyles.length;i<len;++i){layer.userStyles[i].layerName=layer.name;}
+if(sld.namedLayers instanceof Array){sld.namedLayers.push(layer);}else{sld.namedLayers[layer.name]=layer;}},"NamedStyle":function(node,layer){layer.namedStyles.push(this.getChildName(node.firstChild));},"UserStyle":function(node,layer){var obj={defaultsPerSymbolizer:true,rules:[]};this.featureTypeCounter=-1;this.readChildNodes(node,obj);var style;if(this.multipleSymbolizers){delete obj.defaultsPerSymbolizer;style=new OpenLayers.Style2(obj);}else{style=new OpenLayers.Style(this.defaultSymbolizer,obj);}
+layer.userStyles.push(style);},"IsDefault":function(node,style){if(this.getChildValue(node)=="1"){style.isDefault=true;}},"FeatureTypeStyle":function(node,style){++this.featureTypeCounter;var obj={rules:this.multipleSymbolizers?style.rules:[]};this.readChildNodes(node,obj);if(!this.multipleSymbolizers){style.rules=obj.rules;}},"Rule":function(node,obj){var config;if(this.multipleSymbolizers){config={symbolizers:[]};}
+var rule=new OpenLayers.Rule(config);this.readChildNodes(node,rule);obj.rules.push(rule);},"ElseFilter":function(node,rule){rule.elseFilter=true;},"MinScaleDenominator":function(node,rule){rule.minScaleDenominator=parseFloat(this.getChildValue(node));},"MaxScaleDenominator":function(node,rule){rule.maxScaleDenominator=parseFloat(this.getChildValue(node));},"TextSymbolizer":function(node,rule){var config={};this.readChildNodes(node,config);if(this.multipleSymbolizers){config.zIndex=this.featureTypeCounter;rule.symbolizers.push(new OpenLayers.Symbolizer.Text(config));}else{rule.symbolizer["Text"]=OpenLayers.Util.applyDefaults(config,rule.symbolizer["Text"]);}},"Label":function(node,symbolizer){var obj={};this.readChildNodes(node,obj);if(obj.property){symbolizer.label="${"+obj.property+"}";}else{var value=this.readOgcExpression(node);if(value){symbolizer.label=value;}}},"Font":function(node,symbolizer){this.readChildNodes(node,symbolizer);},"Halo":function(node,symbolizer){var obj={};this.readChildNodes(node,obj);symbolizer.haloRadius=obj.haloRadius;symbolizer.haloColor=obj.fillColor;symbolizer.haloOpacity=obj.fillOpacity;},"Radius":function(node,symbolizer){var radius=this.readOgcExpression(node);if(radius!=null){symbolizer.haloRadius=radius;}},"RasterSymbolizer":function(node,rule){var config={};this.readChildNodes(node,config);if(this.multipleSymbolizers){config.zIndex=this.featureTypeCounter;rule.symbolizers.push(new OpenLayers.Symbolizer.Raster(config));}else{rule.symbolizer["Raster"]=OpenLayers.Util.applyDefaults(config,rule.symbolizer["Raster"]);}},"Geometry":function(node,obj){obj.geometry={};this.readChildNodes(node,obj.geometry);},"ColorMap":function(node,symbolizer){symbolizer.colorMap=[];this.readChildNodes(node,symbolizer.colorMap);},"ColorMapEntry":function(node,colorMap){var q=node.getAttribute("quantity");var o=node.getAttribute("opacity");colorMap.push({color:node.getAttribute("color"),quantity:q!==null?parseFloat(q):undefined,label:node.getAttribute("label")||undefined,opacity:o!==null?parseFloat(o):undefined});},"LineSymbolizer":function(node,rule){var config={};this.readChildNodes(node,config);if(this.multipleSymbolizers){config.zIndex=this.featureTypeCounter;rule.symbolizers.push(new OpenLayers.Symbolizer.Line(config));}else{rule.symbolizer["Line"]=OpenLayers.Util.applyDefaults(config,rule.symbolizer["Line"]);}},"PolygonSymbolizer":function(node,rule){var config={fill:false,stroke:false};if(!this.multipleSymbolizers){config=rule.symbolizer["Polygon"]||config;}
+this.readChildNodes(node,config);if(this.multipleSymbolizers){config.zIndex=this.featureTypeCounter;rule.symbolizers.push(new OpenLayers.Symbolizer.Polygon(config));}else{rule.symbolizer["Polygon"]=config;}},"PointSymbolizer":function(node,rule){var config={fill:false,stroke:false,graphic:false};if(!this.multipleSymbolizers){config=rule.symbolizer["Point"]||config;}
+this.readChildNodes(node,config);if(this.multipleSymbolizers){config.zIndex=this.featureTypeCounter;rule.symbolizers.push(new OpenLayers.Symbolizer.Point(config));}else{rule.symbolizer["Point"]=config;}},"Stroke":function(node,symbolizer){symbolizer.stroke=true;this.readChildNodes(node,symbolizer);},"Fill":function(node,symbolizer){symbolizer.fill=true;this.readChildNodes(node,symbolizer);},"CssParameter":function(node,symbolizer){var cssProperty=node.getAttribute("name");var symProperty=this.cssMap[cssProperty];if(symProperty){var value=this.readOgcExpression(node);if(value){symbolizer[symProperty]=value;}}},"Graphic":function(node,symbolizer){symbolizer.graphic=true;var graphic={};this.readChildNodes(node,graphic);var properties=["stroke","strokeColor","strokeWidth","strokeOpacity","strokeLinecap","fill","fillColor","fillOpacity","graphicName","rotation","graphicFormat"];var prop,value;for(var i=0,len=properties.length;i<len;++i){prop=properties[i];value=graphic[prop];if(value!=undefined){symbolizer[prop]=value;}}
+if(graphic.opacity!=undefined){symbolizer.graphicOpacity=graphic.opacity;}
+if(graphic.size!=undefined){symbolizer.pointRadius=graphic.size/2;}
+if(graphic.href!=undefined){symbolizer.externalGraphic=graphic.href;}
+if(graphic.rotation!=undefined){symbolizer.rotation=graphic.rotation;}},"ExternalGraphic":function(node,graphic){this.readChildNodes(node,graphic);},"Mark":function(node,graphic){this.readChildNodes(node,graphic);},"WellKnownName":function(node,graphic){graphic.graphicName=this.getChildValue(node);},"Opacity":function(node,obj){var opacity=this.readOgcExpression(node);if(opacity){obj.opacity=opacity;}},"Size":function(node,obj){var size=this.readOgcExpression(node);if(size){obj.size=size;}},"Rotation":function(node,obj){var rotation=this.readOgcExpression(node);if(rotation){obj.rotation=rotation;}},"OnlineResource":function(node,obj){obj.href=this.getAttributeNS(node,this.namespaces.xlink,"href");},"Format":function(node,graphic){graphic.graphicFormat=this.getChildValue(node);}}},OpenLayers.Format.Filter.v1_0_0.prototype.readers),cssMap:{"stroke":"strokeColor","stroke-opacity":"strokeOpacity","stroke-width":"strokeWidth","stroke-linecap":"strokeLinecap","stroke-dasharray":"strokeDashstyle","fill":"fillColor","fill-opacity":"fillOpacity","font-family":"fontFamily","font-size":"fontSize","font-weight":"fontWeight","font-style":"fontStyle"},getCssProperty:function(sym){var css=null;for(var prop in this.cssMap){if(this.cssMap[prop]==sym){css=prop;break;}}
+return css;},getGraphicFormat:function(href){var format,regex;for(var key in this.graphicFormats){if(this.graphicFormats[key].test(href)){format=key;break;}}
+return format||this.defautlGraphicFormat;},defaultGraphicFormat:"image/png",graphicFormats:{"image/jpeg":/\.jpe?g$/i,"image/gif":/\.gif$/i,"image/png":/\.png$/i},write:function(sld){return this.writers.sld.StyledLayerDescriptor.apply(this,[sld]);},writers:OpenLayers.Util.applyDefaults({"sld":{"StyledLayerDescriptor":function(sld){var root=this.createElementNSPlus("sld:StyledLayerDescriptor",{attributes:{"version":this.VERSION,"xsi:schemaLocation":this.schemaLocation}});root.setAttribute("xmlns:ogc",this.namespaces.ogc);root.setAttribute("xmlns:gml",this.namespaces.gml);if(sld.name){this.writeNode("Name",sld.name,root);}
+if(sld.title){this.writeNode("Title",sld.title,root);}
+if(sld.description){this.writeNode("Abstract",sld.description,root);}
+if(sld.namedLayers instanceof Array){for(var i=0,len=sld.namedLayers.length;i<len;++i){this.writeNode("NamedLayer",sld.namedLayers[i],root);}}else{for(var name in sld.namedLayers){this.writeNode("NamedLayer",sld.namedLayers[name],root);}}
+return root;},"Name":function(name){return this.createElementNSPlus("sld:Name",{value:name});},"Title":function(title){return this.createElementNSPlus("sld:Title",{value:title});},"Abstract":function(description){return this.createElementNSPlus("sld:Abstract",{value:description});},"NamedLayer":function(layer){var node=this.createElementNSPlus("sld:NamedLayer");this.writeNode("Name",layer.name,node);if(layer.namedStyles){for(var i=0,len=layer.namedStyles.length;i<len;++i){this.writeNode("NamedStyle",layer.namedStyles[i],node);}}
+if(layer.userStyles){for(var i=0,len=layer.userStyles.length;i<len;++i){this.writeNode("UserStyle",layer.userStyles[i],node);}}
+return node;},"NamedStyle":function(name){var node=this.createElementNSPlus("sld:NamedStyle");this.writeNode("Name",name,node);return node;},"UserStyle":function(style){var node=this.createElementNSPlus("sld:UserStyle");if(style.name){this.writeNode("Name",style.name,node);}
+if(style.title){this.writeNode("Title",style.title,node);}
+if(style.description){this.writeNode("Abstract",style.description,node);}
+if(style.isDefault){this.writeNode("IsDefault",style.isDefault,node);}
+if(this.multipleSymbolizers&&style.rules){var rulesByZ={0:[]};var zValues=[0];var rule,ruleMap,symbolizer,zIndex,clone;for(var i=0,ii=style.rules.length;i<ii;++i){rule=style.rules[i];if(rule.symbolizers){ruleMap={};for(var j=0,jj=rule.symbolizers.length;j<jj;++j){symbolizer=rule.symbolizers[j];zIndex=symbolizer.zIndex;if(!(zIndex in ruleMap)){clone=rule.clone();clone.symbolizers=[];ruleMap[zIndex]=clone;}
+ruleMap[zIndex].symbolizers.push(symbolizer.clone());}
+for(zIndex in ruleMap){if(!(zIndex in rulesByZ)){zValues.push(zIndex);rulesByZ[zIndex]=[];}
+rulesByZ[zIndex].push(ruleMap[zIndex]);}}else{rulesByZ[0].push(rule.clone());}}
+zValues.sort();var rules;for(var i=0,ii=zValues.length;i<ii;++i){rules=rulesByZ[zValues[i]];if(rules.length>0){clone=style.clone();clone.rules=rulesByZ[zValues[i]];this.writeNode("FeatureTypeStyle",clone,node);}}}else{this.writeNode("FeatureTypeStyle",style,node);}
+return node;},"IsDefault":function(bool){return this.createElementNSPlus("sld:IsDefault",{value:(bool)?"1":"0"});},"FeatureTypeStyle":function(style){var node=this.createElementNSPlus("sld:FeatureTypeStyle");for(var i=0,len=style.rules.length;i<len;++i){this.writeNode("Rule",style.rules[i],node);}
+return node;},"Rule":function(rule){var node=this.createElementNSPlus("sld:Rule");if(rule.name){this.writeNode("Name",rule.name,node);}
+if(rule.title){this.writeNode("Title",rule.title,node);}
+if(rule.description){this.writeNode("Abstract",rule.description,node);}
+if(rule.elseFilter){this.writeNode("ElseFilter",null,node);}else if(rule.filter){this.writeNode("ogc:Filter",rule.filter,node);}
+if(rule.minScaleDenominator!=undefined){this.writeNode("MinScaleDenominator",rule.minScaleDenominator,node);}
+if(rule.maxScaleDenominator!=undefined){this.writeNode("MaxScaleDenominator",rule.maxScaleDenominator,node);}
+var type,symbolizer;if(this.multipleSymbolizers&&rule.symbolizers){var symbolizer;for(var i=0,ii=rule.symbolizers.length;i<ii;++i){symbolizer=rule.symbolizers[i];type=symbolizer.CLASS_NAME.split(".").pop();this.writeNode(type+"Symbolizer",symbolizer,node);}}else{var types=OpenLayers.Style.SYMBOLIZER_PREFIXES;for(var i=0,len=types.length;i<len;++i){type=types[i];symbolizer=rule.symbolizer[type];if(symbolizer){this.writeNode(type+"Symbolizer",symbolizer,node);}}}
+return node;},"ElseFilter":function(){return this.createElementNSPlus("sld:ElseFilter");},"MinScaleDenominator":function(scale){return this.createElementNSPlus("sld:MinScaleDenominator",{value:scale});},"MaxScaleDenominator":function(scale){return this.createElementNSPlus("sld:MaxScaleDenominator",{value:scale});},"LineSymbolizer":function(symbolizer){var node=this.createElementNSPlus("sld:LineSymbolizer");this.writeNode("Stroke",symbolizer,node);return node;},"Stroke":function(symbolizer){var node=this.createElementNSPlus("sld:Stroke");if(symbolizer.strokeColor!=undefined){this.writeNode("CssParameter",{symbolizer:symbolizer,key:"strokeColor"},node);}
+if(symbolizer.strokeOpacity!=undefined){this.writeNode("CssParameter",{symbolizer:symbolizer,key:"strokeOpacity"},node);}
+if(symbolizer.strokeWidth!=undefined){this.writeNode("CssParameter",{symbolizer:symbolizer,key:"strokeWidth"},node);}
+if(symbolizer.strokeDashstyle!=undefined&&symbolizer.strokeDashstyle!=="solid"){this.writeNode("CssParameter",{symbolizer:symbolizer,key:"strokeDashstyle"},node);}
+if(symbolizer.strokeLinecap!=undefined){this.writeNode("CssParameter",{symbolizer:symbolizer,key:"strokeLinecap"},node);}
+return node;},"CssParameter":function(obj){return this.createElementNSPlus("sld:CssParameter",{attributes:{name:this.getCssProperty(obj.key)},value:obj.symbolizer[obj.key]});},"TextSymbolizer":function(symbolizer){var node=this.createElementNSPlus("sld:TextSymbolizer");if(symbolizer.label!=null){this.writeNode("Label",symbolizer.label,node);}
+if(symbolizer.fontFamily!=null||symbolizer.fontSize!=null||symbolizer.fontWeight!=null||symbolizer.fontStyle!=null){this.writeNode("Font",symbolizer,node);}
+if(symbolizer.haloRadius!=null||symbolizer.haloColor!=null||symbolizer.haloOpacity!=null){this.writeNode("Halo",symbolizer,node);}
+if(symbolizer.fillColor!=null||symbolizer.fillOpacity!=null){this.writeNode("Fill",symbolizer,node);}
+return node;},"Font":function(symbolizer){var node=this.createElementNSPlus("sld:Font");if(symbolizer.fontFamily){this.writeNode("CssParameter",{symbolizer:symbolizer,key:"fontFamily"},node);}
+if(symbolizer.fontSize){this.writeNode("CssParameter",{symbolizer:symbolizer,key:"fontSize"},node);}
+if(symbolizer.fontWeight){this.writeNode("CssParameter",{symbolizer:symbolizer,key:"fontWeight"},node);}
+if(symbolizer.fontStyle){this.writeNode("CssParameter",{symbolizer:symbolizer,key:"fontStyle"},node);}
+return node;},"Label":function(label){var node=this.createElementNSPlus("sld:Label");var tokens=label.split("${");node.appendChild(this.createTextNode(tokens[0]));var item,last;for(var i=1,len=tokens.length;i<len;i++){item=tokens[i];last=item.indexOf("}");if(last>0){this.writeNode("ogc:PropertyName",{property:item.substring(0,last)},node);node.appendChild(this.createTextNode(item.substring(++last)));}else{node.appendChild(this.createTextNode("${"+item));}}
+return node;},"Halo":function(symbolizer){var node=this.createElementNSPlus("sld:Halo");if(symbolizer.haloRadius){this.writeNode("Radius",symbolizer.haloRadius,node);}
+if(symbolizer.haloColor||symbolizer.haloOpacity){this.writeNode("Fill",{fillColor:symbolizer.haloColor,fillOpacity:symbolizer.haloOpacity},node);}
+return node;},"Radius":function(value){return this.createElementNSPlus("sld:Radius",{value:value});},"RasterSymbolizer":function(symbolizer){var node=this.createElementNSPlus("sld:RasterSymbolizer");if(symbolizer.geometry){this.writeNode("Geometry",symbolizer.geometry,node);}
+if(symbolizer.opacity){this.writeNode("Opacity",symbolizer.opacity,node);}
+if(symbolizer.colorMap){this.writeNode("ColorMap",symbolizer.colorMap,node);}
+return node;},"Geometry":function(geometry){var node=this.createElementNSPlus("sld:Geometry");if(geometry.property){this.writeNode("ogc:PropertyName",geometry,node);}
+return node;},"ColorMap":function(colorMap){var node=this.createElementNSPlus("sld:ColorMap");for(var i=0,len=colorMap.length;i<len;++i){this.writeNode("ColorMapEntry",colorMap[i],node);}
+return node;},"ColorMapEntry":function(colorMapEntry){var node=this.createElementNSPlus("sld:ColorMapEntry");var a=colorMapEntry;node.setAttribute("color",a.color);a.opacity!==undefined&&node.setAttribute("opacity",parseFloat(a.opacity));a.quantity!==undefined&&node.setAttribute("quantity",parseFloat(a.quantity));a.label!==undefined&&node.setAttribute("label",a.label);return node;},"PolygonSymbolizer":function(symbolizer){var node=this.createElementNSPlus("sld:PolygonSymbolizer");if(symbolizer.fill!==false){this.writeNode("Fill",symbolizer,node);}
+if(symbolizer.stroke!==false){this.writeNode("Stroke",symbolizer,node);}
+return node;},"Fill":function(symbolizer){var node=this.createElementNSPlus("sld:Fill");if(symbolizer.fillColor){this.writeNode("CssParameter",{symbolizer:symbolizer,key:"fillColor"},node);}
+if(symbolizer.fillOpacity!=null){this.writeNode("CssParameter",{symbolizer:symbolizer,key:"fillOpacity"},node);}
+return node;},"PointSymbolizer":function(symbolizer){var node=this.createElementNSPlus("sld:PointSymbolizer");this.writeNode("Graphic",symbolizer,node);return node;},"Graphic":function(symbolizer){var node=this.createElementNSPlus("sld:Graphic");if(symbolizer.externalGraphic!=undefined){this.writeNode("ExternalGraphic",symbolizer,node);}else{this.writeNode("Mark",symbolizer,node);}
+if(symbolizer.graphicOpacity!=undefined){this.writeNode("Opacity",symbolizer.graphicOpacity,node);}
+if(symbolizer.pointRadius!=undefined){this.writeNode("Size",symbolizer.pointRadius*2,node);}
+if(symbolizer.rotation!=undefined){this.writeNode("Rotation",symbolizer.rotation,node);}
+return node;},"ExternalGraphic":function(symbolizer){var node=this.createElementNSPlus("sld:ExternalGraphic");this.writeNode("OnlineResource",symbolizer.externalGraphic,node);var format=symbolizer.graphicFormat||this.getGraphicFormat(symbolizer.externalGraphic);this.writeNode("Format",format,node);return node;},"Mark":function(symbolizer){var node=this.createElementNSPlus("sld:Mark");if(symbolizer.graphicName){this.writeNode("WellKnownName",symbolizer.graphicName,node);}
+if(symbolizer.fill!==false){this.writeNode("Fill",symbolizer,node);}
+if(symbolizer.stroke!==false){this.writeNode("Stroke",symbolizer,node);}
+return node;},"WellKnownName":function(name){return this.createElementNSPlus("sld:WellKnownName",{value:name});},"Opacity":function(value){return this.createElementNSPlus("sld:Opacity",{value:value});},"Size":function(value){return this.createElementNSPlus("sld:Size",{value:value});},"Rotation":function(value){return this.createElementNSPlus("sld:Rotation",{value:value});},"OnlineResource":function(href){return this.createElementNSPlus("sld:OnlineResource",{attributes:{"xlink:type":"simple","xlink:href":href}});},"Format":function(format){return this.createElementNSPlus("sld:Format",{value:format});}}},OpenLayers.Format.Filter.v1_0_0.prototype.writers),CLASS_NAME:"OpenLayers.Format.SLD.v1"});OpenLayers.Format.WFST.v1_0_0=OpenLayers.Class(OpenLayers.Format.Filter.v1_0_0,OpenLayers.Format.WFST.v1,{version:"1.0.0",srsNameInQuery:false,schemaLocations:{"wfs":"http://schemas.opengis.net/wfs/1.0.0/WFS-transaction.xsd"},initialize:function(options){OpenLayers.Format.Filter.v1_0_0.prototype.initialize.apply(this,[options]);OpenLayers.Format.WFST.v1.prototype.initialize.apply(this,[options]);},readers:{"wfs":OpenLayers.Util.applyDefaults({"WFS_TransactionResponse":function(node,obj){obj.insertIds=[];obj.success=false;this.readChildNodes(node,obj);},"InsertResult":function(node,container){var obj={fids:[]};this.readChildNodes(node,obj);container.insertIds.push(obj.fids[0]);},"TransactionResult":function(node,obj){this.readChildNodes(node,obj);},"Status":function(node,obj){this.readChildNodes(node,obj);},"SUCCESS":function(node,obj){obj.success=true;}},OpenLayers.Format.WFST.v1.prototype.readers["wfs"]),"gml":OpenLayers.Format.GML.v2.prototype.readers["gml"],"feature":OpenLayers.Format.GML.v2.prototype.readers["feature"],"ogc":OpenLayers.Format.Filter.v1_0_0.prototype.readers["ogc"]},writers:{"wfs":OpenLayers.Util.applyDefaults({"Query":function(options){options=OpenLayers.Util.extend({featureNS:this.featureNS,featurePrefix:this.featurePrefix,featureType:this.featureType,srsName:this.srsName,srsNameInQuery:this.srsNameInQuery},options);var node=this.createElementNSPlus("wfs:Query",{attributes:{typeName:(options.featureNS?options.featurePrefix+":":"")+
+options.featureType}});if(options.srsNameInQuery&&options.srsName){node.setAttribute("srsName",options.srsName);}
+if(options.featureNS){node.setAttribute("xmlns:"+options.featurePrefix,options.featureNS);}
+if(options.propertyNames){for(var i=0,len=options.propertyNames.length;i<len;i++){this.writeNode("ogc:PropertyName",{property:options.propertyNames[i]},node);}}
+if(options.filter){this.setFilterProperty(options.filter);this.writeNode("ogc:Filter",options.filter,node);}
+return node;}},OpenLayers.Format.WFST.v1.prototype.writers["wfs"]),"gml":OpenLayers.Format.GML.v2.prototype.writers["gml"],"feature":OpenLayers.Format.GML.v2.prototype.writers["feature"],"ogc":OpenLayers.Format.Filter.v1_0_0.prototype.writers["ogc"]},CLASS_NAME:"OpenLayers.Format.WFST.v1_0_0"});OpenLayers.Format.WFST.v1_1_0=OpenLayers.Class(OpenLayers.Format.Filter.v1_1_0,OpenLayers.Format.WFST.v1,{version:"1.1.0",schemaLocations:{"wfs":"http://schemas.opengis.net/wfs/1.1.0/wfs.xsd"},initialize:function(options){OpenLayers.Format.Filter.v1_1_0.prototype.initialize.apply(this,[options]);OpenLayers.Format.WFST.v1.prototype.initialize.apply(this,[options]);},readers:{"wfs":OpenLayers.Util.applyDefaults({"FeatureCollection":function(node,obj){obj.numberOfFeatures=parseInt(node.getAttribute("numberOfFeatures"));OpenLayers.Format.WFST.v1.prototype.readers["wfs"]["FeatureCollection"].apply(this,arguments);},"TransactionResponse":function(node,obj){obj.insertIds=[];obj.success=false;this.readChildNodes(node,obj);},"TransactionSummary":function(node,obj){obj.success=true;},"InsertResults":function(node,obj){this.readChildNodes(node,obj);},"Feature":function(node,container){var obj={fids:[]};this.readChildNodes(node,obj);container.insertIds.push(obj.fids[0]);}},OpenLayers.Format.WFST.v1.prototype.readers["wfs"]),"gml":OpenLayers.Format.GML.v3.prototype.readers["gml"],"feature":OpenLayers.Format.GML.v3.prototype.readers["feature"],"ogc":OpenLayers.Format.Filter.v1_1_0.prototype.readers["ogc"]},writers:{"wfs":OpenLayers.Util.applyDefaults({"GetFeature":function(options){var node=OpenLayers.Format.WFST.v1.prototype.writers["wfs"]["GetFeature"].apply(this,arguments);options&&options.resultType&&this.setAttributes(node,{resultType:options.resultType});return node;},"Query":function(options){options=OpenLayers.Util.extend({featureNS:this.featureNS,featurePrefix:this.featurePrefix,featureType:this.featureType,srsName:this.srsName},options);var node=this.createElementNSPlus("wfs:Query",{attributes:{typeName:(options.featureNS?options.featurePrefix+":":"")+
+options.featureType,srsName:options.srsName}});if(options.featureNS){node.setAttribute("xmlns:"+options.featurePrefix,options.featureNS);}
+if(options.propertyNames){for(var i=0,len=options.propertyNames.length;i<len;i++){this.writeNode("wfs:PropertyName",{property:options.propertyNames[i]},node);}}
+if(options.filter){this.setFilterProperty(options.filter);this.writeNode("ogc:Filter",options.filter,node);}
+return node;},"PropertyName":function(obj){return this.createElementNSPlus("wfs:PropertyName",{value:obj.property});}},OpenLayers.Format.WFST.v1.prototype.writers["wfs"]),"gml":OpenLayers.Format.GML.v3.prototype.writers["gml"],"feature":OpenLayers.Format.GML.v3.prototype.writers["feature"],"ogc":OpenLayers.Format.Filter.v1_1_0.prototype.writers["ogc"]},CLASS_NAME:"OpenLayers.Format.WFST.v1_1_0"});OpenLayers.Protocol.SOS.v1_0_0=OpenLayers.Class(OpenLayers.Protocol,{fois:null,formatOptions:null,initialize:function(options){OpenLayers.Protocol.prototype.initialize.apply(this,[options]);if(!options.format){this.format=new OpenLayers.Format.SOSGetFeatureOfInterest(this.formatOptions);}},destroy:function(){if(this.options&&!this.options.format){this.format.destroy();}
+this.format=null;OpenLayers.Protocol.prototype.destroy.apply(this);},read:function(options){options=OpenLayers.Util.extend({},options);OpenLayers.Util.applyDefaults(options,this.options||{});var response=new OpenLayers.Protocol.Response({requestType:"read"});var format=this.format;var data=OpenLayers.Format.XML.prototype.write.apply(format,[format.writeNode("sos:GetFeatureOfInterest",{fois:this.fois})]);response.priv=OpenLayers.Request.POST({url:options.url,callback:this.createCallback(this.handleRead,response,options),data:data});return response;},handleRead:function(response,options){if(options.callback){var request=response.priv;if(request.status>=200&&request.status<300){response.features=this.parseFeatures(request);response.code=OpenLayers.Protocol.Response.SUCCESS;}else{response.code=OpenLayers.Protocol.Response.FAILURE;}
+options.callback.call(options.scope,response);}},parseFeatures:function(request){var doc=request.responseXML;if(!doc||!doc.documentElement){doc=request.responseText;}
+if(!doc||doc.length<=0){return null;}
+return this.format.read(doc);},CLASS_NAME:"OpenLayers.Protocol.SOS.v1_0_0"});OpenLayers.Format.SLD.v1_0_0=OpenLayers.Class(OpenLayers.Format.SLD.v1,{VERSION:"1.0.0",schemaLocation:"http://www.opengis.net/sld http://schemas.opengis.net/sld/1.0.0/StyledLayerDescriptor.xsd",initialize:function(options){OpenLayers.Format.SLD.v1.prototype.initialize.apply(this,[options]);},CLASS_NAME:"OpenLayers.Format.SLD.v1_0_0"});OpenLayers.Protocol.WFS.v1_0_0=OpenLayers.Class(OpenLayers.Protocol.WFS.v1,{version:"1.0.0",CLASS_NAME:"OpenLayers.Protocol.WFS.v1_0_0"});OpenLayers.Protocol.WFS.v1_1_0=OpenLayers.Class(OpenLayers.Protocol.WFS.v1,{version:"1.1.0",CLASS_NAME:"OpenLayers.Protocol.WFS.v1_1_0"});OpenLayers.Format.OWSContext.v0_3_1=OpenLayers.Class(OpenLayers.Format.XML,{namespaces:{owc:"http://www.opengis.net/ows-context",gml:"http://www.opengis.net/gml",kml:"http://www.opengis.net/kml/2.2",ogc:"http://www.opengis.net/ogc",ows:"http://www.opengis.net/ows",sld:"http://www.opengis.net/sld",xlink:"http://www.w3.org/1999/xlink",xsi:"http://www.w3.org/2001/XMLSchema-instance"},VERSION:"0.3.1",schemaLocation:"http://www.opengis.net/ows-context http://www.ogcnetwork.net/schemas/owc/0.3.1/owsContext.xsd",defaultPrefix:"owc",extractAttributes:true,xy:true,regExes:{trimSpace:(/^\s*|\s*$/g),removeSpace:(/\s*/g),splitSpace:(/\s+/),trimComma:(/\s*,\s*/g)},featureNS:"http://mapserver.gis.umn.edu/mapserver",featureType:'vector',geometryName:'geometry',nestingLayerLookup:null,initialize:function(options){OpenLayers.Format.XML.prototype.initialize.apply(this,[options]);OpenLayers.Format.GML.v2.prototype.setGeometryTypes.call(this);},setNestingPath:function(l){if(l.layersContext){for(var i=0,len=l.layersContext.length;i<len;i++){var layerContext=l.layersContext[i];var nPath=[];var nTitle=l.title||"";if(l.metadata&&l.metadata.nestingPath){nPath=l.metadata.nestingPath.slice();}
+if(nTitle!=""){nPath.push(nTitle);}
+layerContext.metadata.nestingPath=nPath;if(layerContext.layersContext){this.setNestingPath(layerContext);}}}},decomposeNestingPath:function(nPath){var a=[];if(nPath instanceof Array){while(nPath.length>0){a.push(nPath.slice());nPath.pop();}
+a.reverse();}
+return a;},read:function(data){if(typeof data=="string"){data=OpenLayers.Format.XML.prototype.read.apply(this,[data]);}
+if(data&&data.nodeType==9){data=data.documentElement;}
+var context={};this.readNode(data,context);this.setNestingPath({layersContext:context.layersContext});var layers=[];this.processLayer(layers,context);delete context.layersContext;context.layersContext=layers;return context;},processLayer:function(layerArray,layer){if(layer.layersContext){for(var i=0,len=layer.layersContext.length;i<len;i++){var l=layer.layersContext[i];layerArray.push(l);if(l.layersContext){this.processLayer(layerArray,l);}}}},write:function(context,options){var name="OWSContext";this.nestingLayerLookup={};options=options||{};OpenLayers.Util.applyDefaults(options,context);var root=this.writeNode(name,options);this.nestingLayerLookup=null;this.setAttributeNS(root,this.namespaces["xsi"],"xsi:schemaLocation",this.schemaLocation);return OpenLayers.Format.XML.prototype.write.apply(this,[root]);},readers:{"kml":{"Document":function(node,obj){obj.features=new OpenLayers.Format.KML({kmlns:this.namespaces.kml,extractStyles:true}).read(node);}},"owc":{"OWSContext":function(node,obj){this.readChildNodes(node,obj);},"General":function(node,obj){this.readChildNodes(node,obj);},"ResourceList":function(node,obj){this.readChildNodes(node,obj);},"Layer":function(node,obj){var layerContext={metadata:{},visibility:(node.getAttribute("hidden")!="1"),queryable:(node.getAttribute("queryable")=="1"),opacity:((node.getAttribute("opacity")!=null)?parseFloat(node.getAttribute("opacity")):null),name:node.getAttribute("name"),categoryLayer:(node.getAttribute("name")==null),formats:[],styles:[]};if(!obj.layersContext){obj.layersContext=[];}
+obj.layersContext.push(layerContext);this.readChildNodes(node,layerContext);},"InlineGeometry":function(node,obj){obj.features=[];var elements=this.getElementsByTagNameNS(node,this.namespaces.gml,"featureMember");var el;if(elements.length>=1){el=elements[0];}
+if(el&&el.firstChild){var featurenode=(el.firstChild.nextSibling)?el.firstChild.nextSibling:el.firstChild;this.setNamespace("feature",featurenode.namespaceURI);this.featureType=featurenode.localName||featurenode.nodeName.split(":").pop();this.readChildNodes(node,obj);}},"Server":function(node,obj){if((!obj.service&&!obj.version)||(obj.service!=OpenLayers.Format.Context.serviceTypes.WMS)){obj.service=node.getAttribute("service");obj.version=node.getAttribute("version");this.readChildNodes(node,obj);}},"Name":function(node,obj){obj.name=this.getChildValue(node);this.readChildNodes(node,obj);},"Title":function(node,obj){obj.title=this.getChildValue(node);this.readChildNodes(node,obj);},"StyleList":function(node,obj){this.readChildNodes(node,obj.styles);},"Style":function(node,obj){var style={};obj.push(style);this.readChildNodes(node,style);},"LegendURL":function(node,obj){var legend={};obj.legend=legend;this.readChildNodes(node,legend);},"OnlineResource":function(node,obj){obj.url=this.getAttributeNS(node,this.namespaces.xlink,"href");this.readChildNodes(node,obj);}},"ows":OpenLayers.Format.OWSCommon.v1_0_0.prototype.readers.ows,"gml":OpenLayers.Format.GML.v2.prototype.readers.gml,"sld":OpenLayers.Format.SLD.v1_0_0.prototype.readers.sld,"feature":OpenLayers.Format.GML.v2.prototype.readers.feature},writers:{"owc":{"OWSContext":function(options){var node=this.createElementNSPlus("OWSContext",{attributes:{version:this.VERSION,id:options.id||OpenLayers.Util.createUniqueID("OpenLayers_OWSContext_")}});this.writeNode("General",options,node);this.writeNode("ResourceList",options,node);return node;},"General":function(options){var node=this.createElementNSPlus("General");this.writeNode("ows:BoundingBox",options,node);this.writeNode("ows:Title",options.title||'OpenLayers OWSContext',node);return node;},"ResourceList":function(options){var node=this.createElementNSPlus("ResourceList");for(var i=0,len=options.layers.length;i<len;i++){var layer=options.layers[i];var decomposedPath=this.decomposeNestingPath(layer.metadata.nestingPath);this.writeNode("_Layer",{layer:layer,subPaths:decomposedPath},node);}
+return node;},"Server":function(options){var node=this.createElementNSPlus("Server",{attributes:{version:options.version,service:options.service}});this.writeNode("OnlineResource",options,node);return node;},"OnlineResource":function(options){var node=this.createElementNSPlus("OnlineResource",{attributes:{"xlink:href":options.url}});return node;},"InlineGeometry":function(layer){var node=this.createElementNSPlus("InlineGeometry");this.writeNode("gml:boundedBy",layer.getDataExtent(),node);for(var i=0,len=layer.features.length;i<len;i++){this.writeNode("gml:featureMember",layer.features[i],node);}
+return node;},"StyleList":function(styles){var node=this.createElementNSPlus("StyleList");for(var i=0,len=styles.length;i<len;i++){this.writeNode("Style",styles[i],node);}
+return node;},"Style":function(style){var node=this.createElementNSPlus("Style");this.writeNode("Name",style,node);this.writeNode("Title",style,node);this.writeNode("LegendURL",style,node);return node;},"Name":function(obj){var node=this.createElementNSPlus("Name",{value:obj.name});return node;},"Title":function(obj){var node=this.createElementNSPlus("Title",{value:obj.title});return node;},"LegendURL":function(style){var node=this.createElementNSPlus("LegendURL");this.writeNode("OnlineResource",style.legend,node);return node;},"_WMS":function(layer){var node=this.createElementNSPlus("Layer",{attributes:{name:layer.params.LAYERS,queryable:layer.queryable?"1":"0",hidden:layer.visibility?"0":"1",opacity:layer.opacity?layer.opacity:null}});this.writeNode("ows:Title",layer.name,node);this.writeNode("ows:OutputFormat",layer.params.FORMAT,node);this.writeNode("Server",{service:OpenLayers.Format.Context.serviceTypes.WMS,version:layer.params.VERSION,url:layer.url},node);if(layer.metadata.styles&&layer.metadata.styles.length>0){this.writeNode("StyleList",layer.metadata.styles,node);}
+return node;},"_Layer":function(options){var layer,subPaths,node,title;layer=options.layer;subPaths=options.subPaths;node=null;title=null;if(subPaths.length>0){var path=subPaths[0].join("/");var index=path.lastIndexOf("/");node=this.nestingLayerLookup[path];title=(index>0)?path.substring(index+1,path.length):path;if(!node){node=this.createElementNSPlus("Layer");this.writeNode("ows:Title",title,node);this.nestingLayerLookup[path]=node;}
+options.subPaths.shift();this.writeNode("_Layer",options,node);return node;}else{if(layer instanceof OpenLayers.Layer.WMS){node=this.writeNode("_WMS",layer);}else if(layer instanceof OpenLayers.Layer.Vector){if(layer.protocol instanceof OpenLayers.Protocol.WFS.v1){node=this.writeNode("_WFS",layer);}else if(layer.protocol instanceof OpenLayers.Protocol.HTTP){if(layer.protocol.format instanceof OpenLayers.Format.GML){layer.protocol.format.version="2.1.2";node=this.writeNode("_GML",layer);}else if(layer.protocol.format instanceof OpenLayers.Format.KML){layer.protocol.format.version="2.2";node=this.writeNode("_KML",layer);}}else{this.setNamespace("feature",this.featureNS);node=this.writeNode("_InlineGeometry",layer);}}
+if(layer.options.maxScale){this.writeNode("sld:MinScaleDenominator",layer.options.maxScale,node);}
+if(layer.options.minScale){this.writeNode("sld:MaxScaleDenominator",layer.options.minScale,node);}
+this.nestingLayerLookup[layer.name]=node;return node;}},"_WFS":function(layer){var node=this.createElementNSPlus("Layer",{attributes:{name:layer.protocol.featurePrefix+":"+layer.protocol.featureType,hidden:layer.visibility?"0":"1"}});this.writeNode("ows:Title",layer.name,node);this.writeNode("Server",{service:OpenLayers.Format.Context.serviceTypes.WFS,version:layer.protocol.version,url:layer.protocol.url},node);return node;},"_InlineGeometry":function(layer){var node=this.createElementNSPlus("Layer",{attributes:{name:this.featureType,hidden:layer.visibility?"0":"1"}});this.writeNode("ows:Title",layer.name,node);this.writeNode("InlineGeometry",layer,node);return node;},"_GML":function(layer){var node=this.createElementNSPlus("Layer");this.writeNode("ows:Title",layer.name,node);this.writeNode("Server",{service:OpenLayers.Format.Context.serviceTypes.GML,url:layer.protocol.url,version:layer.protocol.format.version},node);return node;},"_KML":function(layer){var node=this.createElementNSPlus("Layer");this.writeNode("ows:Title",layer.name,node);this.writeNode("Server",{service:OpenLayers.Format.Context.serviceTypes.KML,version:layer.protocol.format.version,url:layer.protocol.url},node);return node;}},"gml":OpenLayers.Util.applyDefaults({"boundedBy":function(bounds){var node=this.createElementNSPlus("gml:boundedBy");this.writeNode("gml:Box",bounds,node);return node;}},OpenLayers.Format.GML.v2.prototype.writers.gml),"ows":OpenLayers.Format.OWSCommon.v1_0_0.prototype.writers.ows,"sld":OpenLayers.Format.SLD.v1_0_0.prototype.writers.sld,"feature":OpenLayers.Format.GML.v2.prototype.writers.feature},CLASS_NAME:"OpenLayers.Format.OWSContext.v0_3_1"});
+
+// OpenStreetMaps.js merged
+
+/**
+ * Namespace: Util.OSM
+ */
+OpenLayers.Util.OSM = {};
+
+/**
+ * Constant: MISSING_TILE_URL
+ * {String} URL of image to display for missing tiles
+ */
+OpenLayers.Util.OSM.MISSING_TILE_URL = "http://www.openstreetmap.org/openlayers/img/404.png";
+
+/**
+ * Property: originalOnImageLoadError
+ * {Function} Original onImageLoadError function.
+ */
+OpenLayers.Util.OSM.originalOnImageLoadError = OpenLayers.Util.onImageLoadError;
+
+/**
+ * Function: onImageLoadError
+ */
+OpenLayers.Util.onImageLoadError = function() {
+ if (this.src.match(/^http:\/\/[abc]\.[a-z]+\.openstreetmap\.org\//)) {
+ this.src = OpenLayers.Util.OSM.MISSING_TILE_URL;
+ } else if (this.src.match(/^http:\/\/[def]\.tah\.openstreetmap\.org\//)) {
+ // do nothing - this layer is transparent
+ } else {
+ OpenLayers.Util.OSM.originalOnImageLoadError;
+ }
+};
+
+/**
+ * Class: OpenLayers.Layer.OSM.Mapnik
+ *
+ * Inherits from:
+ * - <OpenLayers.Layer.OSM>
+ */
+OpenLayers.Layer.OSM.Mapnik = OpenLayers.Class(OpenLayers.Layer.OSM, {
+ /**
+ * Constructor: OpenLayers.Layer.OSM.Mapnik
+ *
+ * Parameters:
+ * name - {String}
+ * options - {Object} Hashtable of extra options to tag onto the layer
+ */
+ initialize: function(name, options) {
+ var url = [
+ "http://a.tile.openstreetmap.org/${z}/${x}/${y}.png",
+ "http://b.tile.openstreetmap.org/${z}/${x}/${y}.png",
+ "http://c.tile.openstreetmap.org/${z}/${x}/${y}.png"
+ ];
+ options = OpenLayers.Util.extend({
+ numZoomLevels: 19,
+ buffer: 0,
+ transitionEffect: "resize"
+ }, options);
+ var newArguments = [name, url, options];
+ OpenLayers.Layer.OSM.prototype.initialize.apply(this, newArguments);
+ },
+
+ CLASS_NAME: "OpenLayers.Layer.OSM.Mapnik"
+});
+
+/**
+ * Class: OpenLayers.Layer.OSM.Osmarender
+ *
+ * Inherits from:
+ * - <OpenLayers.Layer.OSM>
+ */
+OpenLayers.Layer.OSM.Osmarender = OpenLayers.Class(OpenLayers.Layer.OSM, {
+ /**
+ * Constructor: OpenLayers.Layer.OSM.Osmarender
+ *
+ * Parameters:
+ * name - {String}
+ * options - {Object} Hashtable of extra options to tag onto the layer
+ */
+ initialize: function(name, options) {
+ var url = [
+ "http://a.tah.openstreetmap.org/Tiles/tile/${z}/${x}/${y}.png",
+ "http://b.tah.openstreetmap.org/Tiles/tile/${z}/${x}/${y}.png",
+ "http://c.tah.openstreetmap.org/Tiles/tile/${z}/${x}/${y}.png"
+ ];
+ options = OpenLayers.Util.extend({
+ numZoomLevels: 18,
+ buffer: 0,
+ transitionEffect: "resize"
+ }, options);
+ var newArguments = [name, url, options];
+ OpenLayers.Layer.OSM.prototype.initialize.apply(this, newArguments);
+ },
+
+ CLASS_NAME: "OpenLayers.Layer.OSM.Osmarender"
+});
+
+/**
+ * Class: OpenLayers.Layer.OSM.CycleMap
+ *
+ * Inherits from:
+ * - <OpenLayers.Layer.OSM>
+ */
+OpenLayers.Layer.OSM.CycleMap = OpenLayers.Class(OpenLayers.Layer.OSM, {
+ /**
+ * Constructor: OpenLayers.Layer.OSM.CycleMap
+ *
+ * Parameters:
+ * name - {String}
+ * options - {Object} Hashtable of extra options to tag onto the layer
+ */
+ initialize: function(name, options) {
+ var url = [
+ "http://a.tile.opencyclemap.org/cycle/${z}/${x}/${y}.png",
+ "http://b.tile.opencyclemap.org/cycle/${z}/${x}/${y}.png",
+ "http://c.tile.opencyclemap.org/cycle/${z}/${x}/${y}.png"
+ ];
+ options = OpenLayers.Util.extend({
+ numZoomLevels: 19,
+ buffer: 0,
+ transitionEffect: "resize"
+ }, options);
+ var newArguments = [name, url, options];
+ OpenLayers.Layer.OSM.prototype.initialize.apply(this, newArguments);
+ },
+
+ CLASS_NAME: "OpenLayers.Layer.OSM.CycleMap"
+}); \ No newline at end of file
diff --git a/js/openlayers/img/blank.gif b/js/openlayers/img/blank.gif
new file mode 100644
index 0000000000..4bcc753a12
--- /dev/null
+++ b/js/openlayers/img/blank.gif
Binary files differ
diff --git a/js/openlayers/img/cloud-popup-relative.png b/js/openlayers/img/cloud-popup-relative.png
new file mode 100644
index 0000000000..1215a36184
--- /dev/null
+++ b/js/openlayers/img/cloud-popup-relative.png
Binary files differ
diff --git a/js/openlayers/img/drag-rectangle-off.png b/js/openlayers/img/drag-rectangle-off.png
new file mode 100644
index 0000000000..fc6daf4dc9
--- /dev/null
+++ b/js/openlayers/img/drag-rectangle-off.png
Binary files differ
diff --git a/js/openlayers/img/drag-rectangle-on.png b/js/openlayers/img/drag-rectangle-on.png
new file mode 100644
index 0000000000..7f783ce133
--- /dev/null
+++ b/js/openlayers/img/drag-rectangle-on.png
Binary files differ
diff --git a/js/openlayers/img/east-mini.png b/js/openlayers/img/east-mini.png
new file mode 100644
index 0000000000..0707567a7d
--- /dev/null
+++ b/js/openlayers/img/east-mini.png
Binary files differ
diff --git a/js/openlayers/img/layer-switcher-maximize.png b/js/openlayers/img/layer-switcher-maximize.png
new file mode 100644
index 0000000000..8d7bb167b3
--- /dev/null
+++ b/js/openlayers/img/layer-switcher-maximize.png
Binary files differ
diff --git a/js/openlayers/img/layer-switcher-minimize.png b/js/openlayers/img/layer-switcher-minimize.png
new file mode 100644
index 0000000000..e80bf213f8
--- /dev/null
+++ b/js/openlayers/img/layer-switcher-minimize.png
Binary files differ
diff --git a/js/openlayers/img/marker-blue.png b/js/openlayers/img/marker-blue.png
new file mode 100644
index 0000000000..83a90b4c85
--- /dev/null
+++ b/js/openlayers/img/marker-blue.png
Binary files differ
diff --git a/js/openlayers/img/marker-gold.png b/js/openlayers/img/marker-gold.png
new file mode 100644
index 0000000000..2ff9ec5281
--- /dev/null
+++ b/js/openlayers/img/marker-gold.png
Binary files differ
diff --git a/js/openlayers/img/marker-green.png b/js/openlayers/img/marker-green.png
new file mode 100644
index 0000000000..17168f1b91
--- /dev/null
+++ b/js/openlayers/img/marker-green.png
Binary files differ
diff --git a/js/openlayers/img/marker.png b/js/openlayers/img/marker.png
new file mode 100644
index 0000000000..ccd1913672
--- /dev/null
+++ b/js/openlayers/img/marker.png
Binary files differ
diff --git a/js/openlayers/img/measuring-stick-off.png b/js/openlayers/img/measuring-stick-off.png
new file mode 100644
index 0000000000..70c2dffb18
--- /dev/null
+++ b/js/openlayers/img/measuring-stick-off.png
Binary files differ
diff --git a/js/openlayers/img/measuring-stick-on.png b/js/openlayers/img/measuring-stick-on.png
new file mode 100644
index 0000000000..cdb8f345b9
--- /dev/null
+++ b/js/openlayers/img/measuring-stick-on.png
Binary files differ
diff --git a/js/openlayers/img/north-mini.png b/js/openlayers/img/north-mini.png
new file mode 100644
index 0000000000..a8a0b4033e
--- /dev/null
+++ b/js/openlayers/img/north-mini.png
Binary files differ
diff --git a/js/openlayers/img/panning-hand-off.png b/js/openlayers/img/panning-hand-off.png
new file mode 100644
index 0000000000..4c912aca66
--- /dev/null
+++ b/js/openlayers/img/panning-hand-off.png
Binary files differ
diff --git a/js/openlayers/img/panning-hand-on.png b/js/openlayers/img/panning-hand-on.png
new file mode 100644
index 0000000000..6094c64e7f
--- /dev/null
+++ b/js/openlayers/img/panning-hand-on.png
Binary files differ
diff --git a/js/openlayers/img/slider.png b/js/openlayers/img/slider.png
new file mode 100644
index 0000000000..23afd573bb
--- /dev/null
+++ b/js/openlayers/img/slider.png
Binary files differ
diff --git a/js/openlayers/img/south-mini.png b/js/openlayers/img/south-mini.png
new file mode 100644
index 0000000000..6c4ac8a0f1
--- /dev/null
+++ b/js/openlayers/img/south-mini.png
Binary files differ
diff --git a/js/openlayers/img/west-mini.png b/js/openlayers/img/west-mini.png
new file mode 100644
index 0000000000..db5f420ca4
--- /dev/null
+++ b/js/openlayers/img/west-mini.png
Binary files differ
diff --git a/js/openlayers/img/zoom-minus-mini.png b/js/openlayers/img/zoom-minus-mini.png
new file mode 100644
index 0000000000..f9b63aba01
--- /dev/null
+++ b/js/openlayers/img/zoom-minus-mini.png
Binary files differ
diff --git a/js/openlayers/img/zoom-plus-mini.png b/js/openlayers/img/zoom-plus-mini.png
new file mode 100644
index 0000000000..eecf2eb4bd
--- /dev/null
+++ b/js/openlayers/img/zoom-plus-mini.png
Binary files differ
diff --git a/js/openlayers/img/zoom-world-mini.png b/js/openlayers/img/zoom-world-mini.png
new file mode 100644
index 0000000000..2159dde7ba
--- /dev/null
+++ b/js/openlayers/img/zoom-world-mini.png
Binary files differ
diff --git a/js/openlayers/img/zoombar.png b/js/openlayers/img/zoombar.png
new file mode 100644
index 0000000000..959f01a93d
--- /dev/null
+++ b/js/openlayers/img/zoombar.png
Binary files differ
diff --git a/js/openlayers/theme/default/framedCloud.css b/js/openlayers/theme/default/framedCloud.css
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/openlayers/theme/default/framedCloud.css
diff --git a/js/openlayers/theme/default/google.css b/js/openlayers/theme/default/google.css
new file mode 100644
index 0000000000..3c1c1872a1
--- /dev/null
+++ b/js/openlayers/theme/default/google.css
@@ -0,0 +1,10 @@
+.olLayerGoogleCopyright {
+ right: 3px;
+ bottom: 2px;
+ left: auto;
+}
+.olLayerGooglePoweredBy {
+ left: 2px;
+ bottom: 2px;
+}
+
diff --git a/js/openlayers/theme/default/ie6-style.css b/js/openlayers/theme/default/ie6-style.css
new file mode 100644
index 0000000000..65f6b192cd
--- /dev/null
+++ b/js/openlayers/theme/default/ie6-style.css
@@ -0,0 +1,7 @@
+.olControlZoomPanel div {
+ background-image: url(img/zoom-panel-NOALPHA.png);
+}
+.olControlPanPanel div {
+ background-image: url(img/pan-panel-NOALPHA.png);
+}
+
diff --git a/js/openlayers/theme/default/img/add_point_off.png b/js/openlayers/theme/default/img/add_point_off.png
new file mode 100644
index 0000000000..aefd09cf33
--- /dev/null
+++ b/js/openlayers/theme/default/img/add_point_off.png
Binary files differ
diff --git a/js/openlayers/theme/default/img/add_point_on.png b/js/openlayers/theme/default/img/add_point_on.png
new file mode 100644
index 0000000000..1294a2c160
--- /dev/null
+++ b/js/openlayers/theme/default/img/add_point_on.png
Binary files differ
diff --git a/js/openlayers/theme/default/img/blank.gif b/js/openlayers/theme/default/img/blank.gif
new file mode 100644
index 0000000000..4bcc753a12
--- /dev/null
+++ b/js/openlayers/theme/default/img/blank.gif
Binary files differ
diff --git a/js/openlayers/theme/default/img/close.gif b/js/openlayers/theme/default/img/close.gif
new file mode 100644
index 0000000000..a8958de9b4
--- /dev/null
+++ b/js/openlayers/theme/default/img/close.gif
Binary files differ
diff --git a/js/openlayers/theme/default/img/drag-rectangle-off.png b/js/openlayers/theme/default/img/drag-rectangle-off.png
new file mode 100644
index 0000000000..fc6daf4dc9
--- /dev/null
+++ b/js/openlayers/theme/default/img/drag-rectangle-off.png
Binary files differ
diff --git a/js/openlayers/theme/default/img/drag-rectangle-on.png b/js/openlayers/theme/default/img/drag-rectangle-on.png
new file mode 100644
index 0000000000..7f783ce133
--- /dev/null
+++ b/js/openlayers/theme/default/img/drag-rectangle-on.png
Binary files differ
diff --git a/js/openlayers/theme/default/img/draw_line_off.png b/js/openlayers/theme/default/img/draw_line_off.png
new file mode 100644
index 0000000000..7f15612548
--- /dev/null
+++ b/js/openlayers/theme/default/img/draw_line_off.png
Binary files differ
diff --git a/js/openlayers/theme/default/img/draw_line_on.png b/js/openlayers/theme/default/img/draw_line_on.png
new file mode 100644
index 0000000000..ba09186c7e
--- /dev/null
+++ b/js/openlayers/theme/default/img/draw_line_on.png
Binary files differ
diff --git a/js/openlayers/theme/default/img/draw_point_off.png b/js/openlayers/theme/default/img/draw_point_off.png
new file mode 100644
index 0000000000..fde94bde24
--- /dev/null
+++ b/js/openlayers/theme/default/img/draw_point_off.png
Binary files differ
diff --git a/js/openlayers/theme/default/img/draw_point_on.png b/js/openlayers/theme/default/img/draw_point_on.png
new file mode 100644
index 0000000000..8804221850
--- /dev/null
+++ b/js/openlayers/theme/default/img/draw_point_on.png
Binary files differ
diff --git a/js/openlayers/theme/default/img/draw_polygon_off.png b/js/openlayers/theme/default/img/draw_polygon_off.png
new file mode 100644
index 0000000000..53ce9d7863
--- /dev/null
+++ b/js/openlayers/theme/default/img/draw_polygon_off.png
Binary files differ
diff --git a/js/openlayers/theme/default/img/draw_polygon_on.png b/js/openlayers/theme/default/img/draw_polygon_on.png
new file mode 100644
index 0000000000..2a3337614f
--- /dev/null
+++ b/js/openlayers/theme/default/img/draw_polygon_on.png
Binary files differ
diff --git a/js/openlayers/theme/default/img/editing_tool_bar.png b/js/openlayers/theme/default/img/editing_tool_bar.png
new file mode 100644
index 0000000000..464340efb6
--- /dev/null
+++ b/js/openlayers/theme/default/img/editing_tool_bar.png
Binary files differ
diff --git a/js/openlayers/theme/default/img/move_feature_off.png b/js/openlayers/theme/default/img/move_feature_off.png
new file mode 100644
index 0000000000..9f588dbb20
--- /dev/null
+++ b/js/openlayers/theme/default/img/move_feature_off.png
Binary files differ
diff --git a/js/openlayers/theme/default/img/move_feature_on.png b/js/openlayers/theme/default/img/move_feature_on.png
new file mode 100644
index 0000000000..072f066c30
--- /dev/null
+++ b/js/openlayers/theme/default/img/move_feature_on.png
Binary files differ
diff --git a/js/openlayers/theme/default/img/navigation_history.png b/js/openlayers/theme/default/img/navigation_history.png
new file mode 100644
index 0000000000..053d1e0d26
--- /dev/null
+++ b/js/openlayers/theme/default/img/navigation_history.png
Binary files differ
diff --git a/js/openlayers/theme/default/img/overview_replacement.gif b/js/openlayers/theme/default/img/overview_replacement.gif
new file mode 100644
index 0000000000..a82cf5fc54
--- /dev/null
+++ b/js/openlayers/theme/default/img/overview_replacement.gif
Binary files differ
diff --git a/js/openlayers/theme/default/img/pan-panel-NOALPHA.png b/js/openlayers/theme/default/img/pan-panel-NOALPHA.png
new file mode 100644
index 0000000000..2740d8baf2
--- /dev/null
+++ b/js/openlayers/theme/default/img/pan-panel-NOALPHA.png
Binary files differ
diff --git a/js/openlayers/theme/default/img/pan-panel.png b/js/openlayers/theme/default/img/pan-panel.png
new file mode 100644
index 0000000000..99101219c0
--- /dev/null
+++ b/js/openlayers/theme/default/img/pan-panel.png
Binary files differ
diff --git a/js/openlayers/theme/default/img/pan_off.png b/js/openlayers/theme/default/img/pan_off.png
new file mode 100644
index 0000000000..30b2aed4d9
--- /dev/null
+++ b/js/openlayers/theme/default/img/pan_off.png
Binary files differ
diff --git a/js/openlayers/theme/default/img/pan_on.png b/js/openlayers/theme/default/img/pan_on.png
new file mode 100644
index 0000000000..d73e7ddd78
--- /dev/null
+++ b/js/openlayers/theme/default/img/pan_on.png
Binary files differ
diff --git a/js/openlayers/theme/default/img/panning-hand-off.png b/js/openlayers/theme/default/img/panning-hand-off.png
new file mode 100644
index 0000000000..4c912aca66
--- /dev/null
+++ b/js/openlayers/theme/default/img/panning-hand-off.png
Binary files differ
diff --git a/js/openlayers/theme/default/img/panning-hand-on.png b/js/openlayers/theme/default/img/panning-hand-on.png
new file mode 100644
index 0000000000..6094c64e7f
--- /dev/null
+++ b/js/openlayers/theme/default/img/panning-hand-on.png
Binary files differ
diff --git a/js/openlayers/theme/default/img/remove_point_off.png b/js/openlayers/theme/default/img/remove_point_off.png
new file mode 100644
index 0000000000..76c8606f55
--- /dev/null
+++ b/js/openlayers/theme/default/img/remove_point_off.png
Binary files differ
diff --git a/js/openlayers/theme/default/img/remove_point_on.png b/js/openlayers/theme/default/img/remove_point_on.png
new file mode 100644
index 0000000000..0ff28fc716
--- /dev/null
+++ b/js/openlayers/theme/default/img/remove_point_on.png
Binary files differ
diff --git a/js/openlayers/theme/default/img/ruler.png b/js/openlayers/theme/default/img/ruler.png
new file mode 100644
index 0000000000..aa4883bcd4
--- /dev/null
+++ b/js/openlayers/theme/default/img/ruler.png
Binary files differ
diff --git a/js/openlayers/theme/default/img/save_features_off.png b/js/openlayers/theme/default/img/save_features_off.png
new file mode 100644
index 0000000000..2bf2906765
--- /dev/null
+++ b/js/openlayers/theme/default/img/save_features_off.png
Binary files differ
diff --git a/js/openlayers/theme/default/img/save_features_on.png b/js/openlayers/theme/default/img/save_features_on.png
new file mode 100644
index 0000000000..93c8f080bf
--- /dev/null
+++ b/js/openlayers/theme/default/img/save_features_on.png
Binary files differ
diff --git a/js/openlayers/theme/default/img/view_next_off.png b/js/openlayers/theme/default/img/view_next_off.png
new file mode 100644
index 0000000000..23c5ac1fe3
--- /dev/null
+++ b/js/openlayers/theme/default/img/view_next_off.png
Binary files differ
diff --git a/js/openlayers/theme/default/img/view_next_on.png b/js/openlayers/theme/default/img/view_next_on.png
new file mode 100644
index 0000000000..e41fb7bddf
--- /dev/null
+++ b/js/openlayers/theme/default/img/view_next_on.png
Binary files differ
diff --git a/js/openlayers/theme/default/img/view_previous_off.png b/js/openlayers/theme/default/img/view_previous_off.png
new file mode 100644
index 0000000000..b9c230f749
--- /dev/null
+++ b/js/openlayers/theme/default/img/view_previous_off.png
Binary files differ
diff --git a/js/openlayers/theme/default/img/view_previous_on.png b/js/openlayers/theme/default/img/view_previous_on.png
new file mode 100644
index 0000000000..c009c255ef
--- /dev/null
+++ b/js/openlayers/theme/default/img/view_previous_on.png
Binary files differ
diff --git a/js/openlayers/theme/default/img/zoom-panel-NOALPHA.png b/js/openlayers/theme/default/img/zoom-panel-NOALPHA.png
new file mode 100644
index 0000000000..cdde6fc7ee
--- /dev/null
+++ b/js/openlayers/theme/default/img/zoom-panel-NOALPHA.png
Binary files differ
diff --git a/js/openlayers/theme/default/img/zoom-panel.png b/js/openlayers/theme/default/img/zoom-panel.png
new file mode 100644
index 0000000000..f2c7c518d5
--- /dev/null
+++ b/js/openlayers/theme/default/img/zoom-panel.png
Binary files differ
diff --git a/js/openlayers/theme/default/style.css b/js/openlayers/theme/default/style.css
new file mode 100644
index 0000000000..0627f0e153
--- /dev/null
+++ b/js/openlayers/theme/default/style.css
@@ -0,0 +1,397 @@
+div.olMap {
+ z-index: 0;
+ padding: 0px!important;
+ margin: 0px!important;
+ cursor: default;
+}
+
+div.olMapViewport {
+ text-align: left;
+}
+
+div.olLayerDiv {
+ -moz-user-select: none;
+}
+
+.olLayerGoogleCopyright {
+ left: 2px;
+ bottom: 2px;
+}
+.olLayerGooglePoweredBy {
+ left: 2px;
+ bottom: 15px;
+}
+.olControlAttribution {
+ font-size: smaller;
+ right: 3px;
+ bottom: 4.5em;
+ position: absolute;
+ display: block;
+}
+.olControlScale {
+ right: 3px;
+ bottom: 3em;
+ display: block;
+ position: absolute;
+ font-size: smaller;
+}
+.olControlScaleLine {
+ display: block;
+ position: absolute;
+ left: 10px;
+ bottom: 15px;
+ font-size: xx-small;
+}
+.olControlScaleLineBottom {
+ border: solid 2px black;
+ border-bottom: none;
+ margin-top:-2px;
+ text-align: center;
+}
+.olControlScaleLineTop {
+ border: solid 2px black;
+ border-top: none;
+ text-align: center;
+}
+
+.olControlPermalink {
+ right: 3px;
+ bottom: 1.5em;
+ display: block;
+ position: absolute;
+ font-size: smaller;
+}
+
+div.olControlMousePosition {
+ bottom: 0em;
+ right: 3px;
+ display: block;
+ position: absolute;
+ font-family: Arial;
+ font-size: smaller;
+}
+
+.olControlOverviewMapContainer {
+ position: absolute;
+ bottom: 0px;
+ right: 0px;
+}
+
+.olControlOverviewMapElement {
+ padding: 10px 18px 10px 10px;
+ background-color: #00008B;
+ -moz-border-radius: 1em 0 0 0;
+}
+
+.olControlOverviewMapMinimizeButton {
+ right: 0px;
+ bottom: 80px;
+}
+
+.olControlOverviewMapMaximizeButton {
+ right: 0px;
+ bottom: 80px;
+}
+
+.olControlOverviewMapExtentRectangle {
+ overflow: hidden;
+ background-image: url("img/blank.gif");
+ cursor: move;
+ border: 2px dotted red;
+}
+.olControlOverviewMapRectReplacement {
+ overflow: hidden;
+ cursor: move;
+ background-image: url("img/overview_replacement.gif");
+ background-repeat: no-repeat;
+ background-position: center;
+}
+
+.olLayerGeoRSSDescription {
+ float:left;
+ width:100%;
+ overflow:auto;
+ font-size:1.0em;
+}
+.olLayerGeoRSSClose {
+ float:right;
+ color:gray;
+ font-size:1.2em;
+ margin-right:6px;
+ font-family:sans-serif;
+}
+.olLayerGeoRSSTitle {
+ float:left;font-size:1.2em;
+}
+
+.olPopupContent {
+ padding:5px;
+ overflow: auto;
+}
+.olControlNavToolbar {
+ width:0px;
+ height:0px;
+}
+.olControlNavToolbar div {
+ display:block;
+ width: 28px;
+ height: 28px;
+ top: 300px;
+ left: 6px;
+ position: relative;
+}
+
+.olControlNavigationHistory {
+ background-image: url("img/navigation_history.png");
+ background-repeat: no-repeat;
+ width: 24px;
+ height: 24px;
+
+}
+.olControlNavigationHistoryPreviousItemActive {
+ background-position: 0px 0px;
+}
+.olControlNavigationHistoryPreviousItemInactive {
+ background-position: 0px -24px;
+}
+.olControlNavigationHistoryNextItemActive {
+ background-position: -24px 0px;
+}
+.olControlNavigationHistoryNextItemInactive {
+ background-position: -24px -24px;
+}
+
+.olControlNavToolbar .olControlNavigationItemActive {
+ background-image: url("img/panning-hand-on.png");
+ background-repeat: no-repeat;
+}
+.olControlNavToolbar .olControlNavigationItemInactive {
+ background-image: url("img/panning-hand-off.png");
+ background-repeat: no-repeat;
+}
+.olControlNavToolbar .olControlZoomBoxItemActive {
+ background-image: url("img/drag-rectangle-on.png");
+ background-color: orange;
+ background-repeat: no-repeat;
+}
+.olControlNavToolbar .olControlZoomBoxItemInactive {
+ background-image: url("img/drag-rectangle-off.png");
+ background-repeat: no-repeat;
+}
+.olControlEditingToolbar {
+ float:right;
+ right: 0px;
+ height: 30px;
+ width: 200px;
+}
+.olControlEditingToolbar div {
+ background-image: url("img/editing_tool_bar.png");
+ background-repeat: no-repeat;
+ float:right;
+ width: 24px;
+ height: 24px;
+ margin: 5px;
+}
+.olControlEditingToolbar .olControlNavigationItemActive {
+ background-position: -103px -23px;
+}
+.olControlEditingToolbar .olControlNavigationItemInactive {
+ background-position: -103px -0px;
+}
+.olControlEditingToolbar .olControlDrawFeaturePointItemActive {
+ background-position: -77px -23px;
+}
+.olControlEditingToolbar .olControlDrawFeaturePointItemInactive {
+ background-position: -77px -0px;
+}
+.olControlEditingToolbar .olControlDrawFeaturePathItemInactive {
+ background-position: -51px 0px;
+}
+.olControlEditingToolbar .olControlDrawFeaturePathItemActive {
+ background-position: -51px -23px;
+}
+.olControlEditingToolbar .olControlDrawFeaturePolygonItemInactive {
+ background-position: -26px 0px;
+}
+.olControlEditingToolbar .olControlDrawFeaturePolygonItemActive {
+ background-position: -26px -23px ;
+}
+div.olControlSaveFeaturesItemActive {
+ background-image: url(img/save_features_on.png);
+ background-repeat: no-repeat;
+ background-position: 0px 1px;
+}
+div.olControlSaveFeaturesItemInactive {
+ background-image: url(img/save_features_off.png);
+ background-repeat: no-repeat;
+ background-position: 0px 1px;
+}
+
+.olHandlerBoxZoomBox {
+ border: 2px solid red;
+ position: absolute;
+ background-color: white;
+ opacity: 0.50;
+ font-size: 1px;
+ filter: alpha(opacity=50);
+}
+.olHandlerBoxSelectFeature {
+ border: 2px solid blue;
+ position: absolute;
+ background-color: white;
+ opacity: 0.50;
+ font-size: 1px;
+ filter: alpha(opacity=50);
+}
+
+.olControlPanPanel {
+ top: 10px;
+ left: 5px;
+}
+
+.olControlPanPanel div {
+ background-image: url(img/pan-panel.png);
+ height: 18px;
+ width: 18px;
+ cursor: pointer;
+ position: absolute;
+}
+
+.olControlPanPanel .olControlPanNorthItemInactive {
+ top: 0px;
+ left: 9px;
+ background-position: 0px 0px;
+}
+.olControlPanPanel .olControlPanSouthItemInactive {
+ top: 36px;
+ left: 9px;
+ background-position: 18px 0px;
+}
+.olControlPanPanel .olControlPanWestItemInactive {
+ position: absolute;
+ top: 18px;
+ left: 0px;
+ background-position: 0px 18px;
+}
+.olControlPanPanel .olControlPanEastItemInactive {
+ top: 18px;
+ left: 18px;
+ background-position: 18px 18px;
+}
+
+.olControlZoomPanel {
+ top: 71px;
+ left: 14px;
+}
+
+.olControlZoomPanel div {
+ background-image: url(img/zoom-panel.png);
+ position: absolute;
+ height: 18px;
+ width: 18px;
+ cursor: pointer;
+}
+
+.olControlZoomPanel .olControlZoomInItemInactive {
+ top: 0px;
+ left: 0px;
+ background-position: 0px 0px;
+}
+
+.olControlZoomPanel .olControlZoomToMaxExtentItemInactive {
+ top: 18px;
+ left: 0px;
+ background-position: 0px -18px;
+}
+
+.olControlZoomPanel .olControlZoomOutItemInactive {
+ top: 36px;
+ left: 0px;
+ background-position: 0px 18px;
+}
+
+.olPopupCloseBox {
+ background: url("img/close.gif") no-repeat;
+ cursor: pointer;
+}
+
+.olFramedCloudPopupContent {
+ padding: 5px;
+ overflow: auto;
+}
+
+.olControlNoSelect {
+ -moz-user-select: none;
+}
+
+.olImageLoadError {
+ background-color: pink;
+ opacity: 0.5;
+ filter: alpha(opacity=50); /* IE */
+}
+
+/**
+ * Cursor styles
+ */
+
+.olCursorWait {
+ cursor: wait;
+}
+.olDragDown {
+ cursor: move;
+}
+.olDrawBox {
+ cursor: crosshair;
+}
+.olControlDragFeatureOver {
+ cursor: move;
+}
+.olControlDragFeatureActive.olControlDragFeatureOver.olDragDown {
+ cursor: -moz-grabbing;
+}
+
+/**
+ * Layer switcher
+ */
+.olControlLayerSwitcher {
+ position: absolute;
+ top: 25px;
+ right: 0px;
+ width: 20em;
+ font-family: sans-serif;
+ font-weight: bold;
+ margin-top: 3px;
+ margin-left: 3px;
+ margin-bottom: 3px;
+ font-size: smaller;
+ color: white;
+ background-color: transparent;
+}
+
+.olControlLayerSwitcher .layersDiv {
+ padding-top: 5px;
+ padding-left: 10px;
+ padding-bottom: 5px;
+ padding-right: 75px;
+ background-color: darkblue;
+ width: 100%;
+ height: 100%;
+}
+
+.olControlLayerSwitcher .layersDiv .baseLbl,
+.olControlLayerSwitcher .layersDiv .dataLbl {
+ margin-top: 3px;
+ margin-left: 3px;
+ margin-bottom: 3px;
+}
+
+.olControlLayerSwitcher .layersDiv .baseLayersDiv,
+.olControlLayerSwitcher .layersDiv .dataLayersDiv {
+ padding-left: 10px;
+}
+
+.olControlLayerSwitcher .maximizeDiv,
+.olControlLayerSwitcher .minimizeDiv {
+ top: 5px;
+ right: 0px;
+}
diff --git a/js/pmd/ajax.js b/js/pmd/ajax.js
new file mode 100644
index 0000000000..ea764cd25f
--- /dev/null
+++ b/js/pmd/ajax.js
@@ -0,0 +1,60 @@
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ *
+ * @package PhpMyAdmin-Designer
+ */
+
+/**
+ *
+ */
+function PrintXML(data)
+{
+ var $root = $(data).find('root');
+ if ($root.length === 0) {
+ // error
+ var myWin = window.open('', 'Report', 'width=400, height=250, resizable=1, scrollbars=1, status=1');
+ var tmp = myWin.document;
+ tmp.write(data);
+ tmp.close();
+ } else {
+ // success
+ if ($root.attr('act') == 'save_pos') {
+ PMA_ajaxShowMessage($root.attr('return'));
+ } else if ($root.attr('act') == 'relation_upd') {
+ PMA_ajaxShowMessage($root.attr('return'));
+ if ($root.attr('b') == '1') {
+ contr.splice($root.attr('K'), 1);
+ Re_load();
+ }
+ } else if ($root.attr('act') == 'relation_new') {
+ PMA_ajaxShowMessage($root.attr('return'));
+ if ($root.attr('b') == '1') {
+ var i = contr.length;
+ var t1 = $root.attr('DB1') + '.' + $root.attr('T1');
+ var f1 = $root.attr('F1');
+ var t2 = $root.attr('DB2') + '.' + $root.attr('T2');
+ var f2 = $root.attr('F2');
+ contr[i] = [];
+ contr[i][''] = [];
+ contr[i][''][t2] = [];
+ contr[i][''][t2][f2] = [];
+ contr[i][''][t2][f2][0] = t1;
+ contr[i][''][t2][f2][1] = f1;
+ Re_load();
+ }
+ }
+ }
+}
+
+/**
+ *
+ */
+function makeRequest(url, parameters)
+{
+ var $msg = PMA_ajaxShowMessage();
+ $.post(url, parameters, function (data) {
+ PMA_ajaxRemoveMessage($msg);
+ PrintXML(data);
+ });
+ return true;
+}
diff --git a/js/pmd/history.js b/js/pmd/history.js
new file mode 100644
index 0000000000..3cb69d5740
--- /dev/null
+++ b/js/pmd/history.js
@@ -0,0 +1,787 @@
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * @fileoverview function used in this file builds history tab and generates query.
+ *
+ * @requires jQuery
+ * @requires moves.js
+ * @version $Id$
+ */
+
+var history_array = []; // Global array to store history objects
+var select_field = []; // Global array to store informaation for columns which are used in select clause
+var g_index;
+
+/**
+ * function for panel, hides and shows toggle_container <div>,which is for history elements uses {@link JQuery}.
+ *
+ * @param index has value 1 or 0,decides wheter to hide toggle_container on load.
+**/
+
+function panel(index)
+{
+ if (!index) {
+ $(".toggle_container").hide();
+ }
+ $("h2.tiger").click(function () {
+ $(this).toggleClass("active").next().slideToggle("slow");
+ });
+}
+
+/**
+ * To display details of obects(where,rename,Having,aggregate,groupby,orderby,having)
+ *
+ * @param index index of history_array where change is to be made
+ *
+**/
+
+function detail(index)
+{
+ var type = history_array[index].get_type();
+ var str;
+ if (type == "Where") {
+ str = 'Where ' + history_array[index].get_column_name() + history_array[index].get_obj().getrelation_operator() + history_array[index].get_obj().getquery();
+ }
+ if (type == "Rename") {
+ str = 'Rename ' + history_array[index].get_column_name() + ' To ' + history_array[index].get_obj().getrename_to();
+ }
+ if (type == "Aggregate") {
+ str = 'Select ' + history_array[index].get_obj().get_operator() + '( ' + history_array[index].get_column_name() + ' )';
+ }
+ if (type == "GroupBy") {
+ str = 'GroupBy ' + history_array[index].get_column_name();
+ }
+ if (type == "OrderBy") {
+ str = 'OrderBy ' + history_array[index].get_column_name();
+ }
+ if (type == "Having") {
+ str = 'Having ';
+ if (history_array[index].get_obj().get_operator() != 'None') {
+ str += history_array[index].get_obj().get_operator() + '( ' + history_array[index].get_column_name() + ' )';
+ str += history_array[index].get_obj().getrelation_operator() + history_array[index].get_obj().getquery();
+ } else {
+ str = 'Having ' + history_array[index].get_column_name() + history_array[index].get_obj().getrelation_operator() + history_array[index].get_obj().getquery();
+ }
+ }
+ return str;
+}
+
+/**
+ * Sorts history_array[] first,using table name as the key and then generates the HTML code for history tab,
+ * clubbing all objects of same tables together
+ * This function is called whenever changes are made in history_array[]
+ *
+ *
+ * @param {int} init starting index of unsorted array
+ * @param {int} finit last index of unsorted array
+ *
+**/
+
+function display(init, finit)
+{
+ var str, i, j, k, sto, temp;
+ // this part sorts the history array based on table name,this is needed for clubbing all object of same name together.
+ for (i = init; i < finit; i++) {
+ sto = history_array[i];
+ temp = history_array[i].get_tab();//+ '.' + history_array[i].get_obj_no(); for Self JOINS
+ for (j = 0; j < i; j++) {
+ if (temp > (history_array[j].get_tab())) {//+ '.' + history_array[j].get_obj_no())) { //for Self JOINS
+ for (k = i; k > j; k--) {
+ history_array[k] = history_array[k - 1];
+ }
+ history_array[j] = sto;
+ break;
+ }
+ }
+ }
+ // this part generates HTML code for history tab.adds delete,edit,and/or and detail features with objects.
+ str = ''; // string to store Html code for history tab
+ for (i = 0; i < history_array.length; i++) {
+ temp = history_array[i].get_tab(); //+ '.' + history_array[i].get_obj_no(); for Self JOIN
+ str += '<h2 class="tiger"><a href="#">' + temp + '</a></h2>';
+ str += '<div class="toggle_container">\n';
+ while ((history_array[i].get_tab()) == temp) { //+ '.' + history_array[i].get_obj_no()) == temp) {
+ str += '<div class="block"> <table width ="250">';
+ str += '<thead><tr><td>';
+ if (history_array[i].get_and_or()) {
+ str += '<img src="' + pmaThemeImage + 'pmd/or_icon.png" onclick="and_or(' + i + ')" title="OR"/></td>';
+ } else {
+ str += '<img src="' + pmaThemeImage + 'pmd/and_icon.png" onclick="and_or(' + i + ')" title="AND"/></td>';
+ }
+ str += '<td style="padding-left: 5px;" class="right">' + PMA_getImage('b_sbrowse.png', 'column name') + '</td><td width="175" style="padding-left: 5px">' + history_array[i].get_column_name();
+ if (history_array[i].get_type() == "GroupBy" || history_array[i].get_type() == "OrderBy") {
+ str += '</td><td class="center">' + PMA_getImage('s_info.png', detail(i)) + '<td title="' + detail(i) + '">' + history_array[i].get_type() + '</td></td><td onmouseover="this.className=\'history_table\';" onmouseout="this.className=\'history_table2\'" onclick=history_delete(' + i + ')>' + PMA_getImage('b_drop.png', 'Delete') + '</td></tr></thead>';
+ } else {
+ str += '</td><td class="center">' + PMA_getImage('s_info.png', detail(i)) + '</td><td title="' + detail(i) + '">' + history_array[i].get_type() + '</td><td <td onmouseover="this.className=\'history_table\';" onmouseout="this.className=\'history_table2\'" onclick=history_edit(' + i + ')>' + PMA_getImage('b_edit.png', PMA_messages.strEdit) + '</td><td onmouseover="this.className=\'history_table\';" onmouseout="this.className=\'history_table2\'" onclick=history_delete(' + i + ')><img src="themes/original/img/b_drop.png" title="Delete"></td></tr></thead>';
+ }
+ i++;
+ if (i >= history_array.length) {
+ break;
+ }
+ str += '</table></div><br/>';
+ }
+ i--;
+ str += '</div><br/>';
+ }
+ return str;
+}
+
+/**
+ * To change And/Or relation in history tab
+ *
+ *
+ * @param {int} index of history_array where change is to be made
+ *
+**/
+
+function and_or(index)
+{
+ if (history_array[index].get_and_or()) {
+ history_array[index].set_and_or(0);
+ } else {
+ history_array[index].set_and_or(1);
+ }
+ var existingDiv = document.getElementById('ab');
+ existingDiv.innerHTML = display(0, 0);
+ panel(1);
+}
+
+/**
+ * Deletes entry in history_array
+ *
+ * @param index index of history_array[] which is to be deleted
+ *
+**/
+
+function history_delete(index)
+{
+ for (var k = 0; k < from_array.length; k++) {
+ if (from_array[k] == history_array[index].get_tab()) {
+ from_array.splice(k, 1);
+ break;
+ }
+ }
+ history_array.splice(index, 1);
+ var existingDiv = document.getElementById('ab');
+ existingDiv.innerHTML = display(0, 0);
+ panel(1);
+}
+
+/**
+ * To show where,rename,aggregate,having forms to edit a object
+ *
+ * @param{int} index index of history_array where change is to be made
+ *
+**/
+
+function history_edit(index)
+{
+ g_index = index;
+ var type = history_array[index].get_type();
+ if (type == "Where") {
+ document.getElementById('eQuery').value = history_array[index].get_obj().getquery();
+ document.getElementById('erel_opt').value = history_array[index].get_obj().getrelation_operator();
+ document.getElementById('query_where').style.left = '530px';
+ document.getElementById('query_where').style.top = '130px';
+ document.getElementById('query_where').style.position = 'absolute';
+ document.getElementById('query_where').style.zIndex = '9';
+ document.getElementById('query_where').style.visibility = 'visible';
+ }
+ if (type == "Having") {
+ document.getElementById('hQuery').value = history_array[index].get_obj().getquery();
+ document.getElementById('hrel_opt').value = history_array[index].get_obj().getrelation_operator();
+ document.getElementById('hoperator').value = history_array[index].get_obj().get_operator();
+ document.getElementById('query_having').style.left = '530px';
+ document.getElementById('query_having').style.top = '130px';
+ document.getElementById('query_having').style.position = 'absolute';
+ document.getElementById('query_having').style.zIndex = '9';
+ document.getElementById('query_having').style.visibility = 'visible';
+ }
+ if (type == "Rename") {
+ document.getElementById('query_rename_to').style.left = '530px';
+ document.getElementById('query_rename_to').style.top = '130px';
+ document.getElementById('query_rename_to').style.position = 'absolute';
+ document.getElementById('query_rename_to').style.zIndex = '9';
+ document.getElementById('query_rename_to').style.visibility = 'visible';
+ }
+ if (type == "Aggregate") {
+ document.getElementById('query_Aggregate').style.left = '530px';
+ document.getElementById('query_Aggregate').style.top = '130px';
+ document.getElementById('query_Aggregate').style.position = 'absolute';
+ document.getElementById('query_Aggregate').style.zIndex = '9';
+ document.getElementById('query_Aggregate').style.visibility = 'visible';
+ }
+}
+
+/**
+ * Make changes in history_array when Edit button is clicked
+ * checks for the type of object and then sets the new value
+ *
+ * @param index index of history_array where change is to be made
+**/
+
+function edit(type)
+{
+ if (type == "Rename") {
+ if (document.getElementById('e_rename').value !== "") {
+ history_array[g_index].get_obj().setrename_to(document.getElementById('e_rename').value);
+ document.getElementById('e_rename').value = "";
+ }
+ document.getElementById('query_rename_to').style.visibility = 'hidden';
+ }
+ if (type == "Aggregate") {
+ if (document.getElementById('e_operator').value != '---') {
+ history_array[g_index].get_obj().set_operator(document.getElementById('e_operator').value);
+ document.getElementById('e_operator').value = '---';
+ }
+ document.getElementById('query_Aggregate').style.visibility = 'hidden';
+ }
+ if (type == "Where") {
+ if (document.getElementById('erel_opt').value != '--' && document.getElementById('eQuery').value !== "") {
+ history_array[g_index].get_obj().setquery(document.getElementById('eQuery').value);
+ history_array[g_index].get_obj().setrelation_operator(document.getElementById('erel_opt').value);
+ }
+ document.getElementById('query_where').style.visibility = 'hidden';
+ }
+ if (type == "Having") {
+ if (document.getElementById('hrel_opt').value != '--' && document.getElementById('hQuery').value !== "") {
+ history_array[g_index].get_obj().setquery(document.getElementById('hQuery').value);
+ history_array[g_index].get_obj().setrelation_operator(document.getElementById('hrel_opt').value);
+ history_array[g_index].get_obj().set_operator(document.getElementById('hoperator').value);
+ }
+ document.getElementById('query_having').style.visibility = 'hidden';
+ }
+ var existingDiv = document.getElementById('ab');
+ existingDiv.innerHTML = display(0, 0);
+ panel(1);
+}
+
+/**
+ * history object closure
+ *
+ * @param ncolumn_name name of the column on which conditions are put
+ * @param nobj object details(where,rename,orderby,groupby,aggregate)
+ * @param ntab table name of the column on which conditions are applied
+ * @param nobj_no object no used for inner join
+ * @param ntype type of object
+ *
+**/
+
+function history(ncolumn_name, nobj, ntab, nobj_no, ntype)
+{
+ var and_or;
+ var obj;
+ var tab;
+ var column_name;
+ var obj_no;
+ var type;
+ this.set_column_name = function (ncolumn_name) {
+ column_name = ncolumn_name;
+ };
+ this.get_column_name = function () {
+ return column_name;
+ };
+ this.set_and_or = function (nand_or) {
+ and_or = nand_or;
+ };
+ this.get_and_or = function () {
+ return and_or;
+ };
+ this.get_relation = function () {
+ return and_or;
+ };
+ this.set_obj = function (nobj) {
+ obj = nobj;
+ };
+ this.get_obj = function () {
+ return obj;
+ };
+ this.set_tab = function (ntab) {
+ tab = ntab;
+ };
+ this.get_tab = function () {
+ return tab;
+ };
+ this.set_obj_no = function (nobj_no) {
+ obj_no = nobj_no;
+ };
+ this.get_obj_no = function () {
+ return obj_no;
+ };
+ this.set_type = function (ntype) {
+ type = ntype;
+ };
+ this.get_type = function () {
+ return type;
+ };
+ this.set_obj_no(nobj_no);
+ this.set_tab(ntab);
+ this.set_and_or(0);
+ this.set_obj(nobj);
+ this.set_column_name(ncolumn_name);
+ this.set_type(ntype);
+}
+
+/**
+ * where object closure, makes an object with all information of where
+ *
+ * @param nrelation_operator type of relation operator to be applied
+ * @param nquery stores value of value/sub-query
+ *
+**/
+
+
+var where = function (nrelation_operator, nquery) {
+ var relation_operator;
+ var query;
+ this.setrelation_operator = function (nrelation_operator) {
+ relation_operator = nrelation_operator;
+ };
+ this.setquery = function (nquery) {
+ query = nquery;
+ };
+ this.getquery = function () {
+ return query;
+ };
+ this.getrelation_operator = function () {
+ return relation_operator;
+ };
+ this.setquery(nquery);
+ this.setrelation_operator(nrelation_operator);
+};
+
+
+/**
+ * Having object closure, makes an object with all information of where
+ *
+ * @param nrelation_operator type of relation operator to be applied
+ * @param nquery stores value of value/sub-query
+ *
+**/
+
+var having = function (nrelation_operator, nquery, noperator) {
+ var relation_operator;
+ var query;
+ var operator;
+ this.set_operator = function (noperator) {
+ operator = noperator;
+ };
+ this.setrelation_operator = function (nrelation_operator) {
+ relation_operator = nrelation_operator;
+ };
+ this.setquery = function (nquery) {
+ query = nquery;
+ };
+ this.getquery = function () {
+ return query;
+ };
+ this.getrelation_operator = function () {
+ return relation_operator;
+ };
+ this.get_operator = function () {
+ return operator;
+ };
+ this.setquery(nquery);
+ this.setrelation_operator(nrelation_operator);
+ this.set_operator(noperator);
+};
+
+/**
+ * rename object closure,makes an object with all information of rename
+ *
+ * @param nrename_to new name information
+ *
+**/
+
+var rename = function (nrename_to) {
+ var rename_to;
+ this.setrename_to = function (nrename_to) {
+ rename_to = nrename_to;
+ };
+ this.getrename_to = function () {
+ return rename_to;
+ };
+ this.setrename_to(nrename_to);
+};
+
+/**
+ * aggregate object closure
+ *
+ * @param noperator aggregte operator
+ *
+**/
+
+var aggregate = function (noperator) {
+ var operator;
+ this.set_operator = function (noperator) {
+ operator = noperator;
+ };
+ this.get_operator = function () {
+ return operator;
+ };
+ this.set_operator(noperator);
+};
+
+/**
+ * This function returns unique element from an array
+ *
+ * @param arraName array from which duplicate elem are to be removed.
+ * @return unique array
+ */
+
+function unique(arrayName)
+{
+ var newArray = [];
+uniquetop:
+ for (var i = 0; i < arrayName.length; i++) {
+ for (var j = 0; j < newArray.length; j++) {
+ if (newArray[j] == arrayName[i]) {
+ continue uniquetop;
+ }
+ }
+ newArray[newArray.length] = arrayName[i];
+ }
+ return newArray;
+}
+
+/**
+ * This function takes in array and a value as input and returns 1 if values is present in array
+ * else returns -1
+ *
+ * @param arrayName array
+ * @param value value which is to be searched in the array
+ */
+
+function found(arrayName, value)
+{
+ for (var i = 0; i < arrayName.length; i++) {
+ if (arrayName[i] == value) {
+ return 1;
+ }
+ }
+ return -1;
+}
+
+/**
+ * This function concatenates two array
+ *
+ * @params add array elements of which are pushed in
+ * @params arr array in which elemnets are added
+ */
+function add_array(add, arr)
+{
+ for (var i = 0; i < add.length; i++) {
+ arr.push(add[i]);
+ }
+ return arr;
+}
+
+/* This fucntion removes all elements present in one array from the other.
+ *
+ * @params rem array from which each element is removed from other array.
+ * @params arr array from which elements are removed.
+ *
+ */
+function remove_array(rem, arr)
+{
+ for (var i = 0; i < rem.length; i++) {
+ for (var j = 0; j < arr.length; j++) {
+ if (rem[i] == arr[j]) {
+ arr.splice(j, 1);
+ }
+ }
+ }
+ return arr;
+}
+
+/**
+ * This function builds the groupby clause from history object
+ *
+ */
+
+function query_groupby()
+{
+ var i;
+ var str = "";
+ for (i = 0; i < history_array.length;i++) {
+ if (history_array[i].get_type() == "GroupBy") {
+ str += history_array[i].get_column_name() + ", ";
+ }
+ }
+ str = str.substr(0, str.length - 1);
+ return str;
+}
+
+/**
+ * This function builds the Having clause from the history object.
+ *
+ */
+
+function query_having()
+{
+ var i;
+ var and = "(";
+ for (i = 0; i < history_array.length;i++) {
+ if (history_array[i].get_type() == "Having") {
+ if (history_array[i].get_obj().get_operator() != 'None') {
+ and += history_array[i].get_obj().get_operator() + "(" + history_array[i].get_column_name() + " ) " + history_array[i].get_obj().getrelation_operator();
+ and += " " + history_array[i].get_obj().getquery() + ", ";
+ } else {
+ and += history_array[i].get_column_name() + " " + history_array[i].get_obj().getrelation_operator() + " " + history_array[i].get_obj().getquery() + ", ";
+ }
+ }
+ }
+ if (and == "(") {
+ and = "";
+ } else {
+ and = and.substr(0, and.length - 2) + ")";
+ }
+ return and;
+}
+
+
+/**
+ * This function builds the orderby clause from the history object.
+ *
+ */
+
+function query_orderby()
+{
+ var i;
+ var str = "";
+ for (i = 0; i < history_array.length;i++) {
+ if (history_array[i].get_type() == "OrderBy") { str += history_array[i].get_column_name() + " , "; }
+ }
+ str = str.substr(0, str.length - 1);
+ return str;
+}
+
+
+/**
+ * This function builds the Where clause from the history object.
+ *
+ */
+
+function query_where()
+{
+ var i;
+ var and = "(";
+ var or = "(";
+ for (i = 0; i < history_array.length;i++) {
+ if (history_array[i].get_type() == "Where") {
+ if (history_array[i].get_and_or() === 0) {
+ and += "( " + history_array[i].get_column_name() + " " + history_array[i].get_obj().getrelation_operator() + " " + history_array[i].get_obj().getquery() + ")";
+ and += " AND ";
+ } else {
+ or += "( " + history_array[i].get_column_name() + " " + history_array[i].get_obj().getrelation_operator() + " " + history_array[i].get_obj().getquery() + ")";
+ or += " OR ";
+ }
+ }
+ }
+ if (or != "(") {
+ or = or.substring(0, (or.length - 4)) + ")";
+ } else {
+ or = "";
+ }
+ if (and != "(") {
+ and = and.substring(0, (and.length - 5)) + ")";
+ } else {
+ and = "";
+ }
+ if (or !== "") {
+ and = and + " OR " + or + " )";
+ }
+ return and;
+}
+
+function check_aggregate(id_this)
+{
+ var i;
+ for (i = 0; i < history_array.length; i++) {
+ var temp = '`' + history_array[i].get_tab() + '`.`' + history_array[i].get_column_name() + '`';
+ if (temp == id_this && history_array[i].get_type() == "Aggregate") {
+ return history_array[i].get_obj().get_operator() + '(' + id_this + ')';
+ }
+ }
+ return "";
+}
+
+function check_rename(id_this)
+{
+ var i;
+ for (i = 0; i < history_array.length; i++) {
+ var temp = '`' + history_array[i].get_tab() + '`.`' + history_array[i].get_column_name() + '`';
+ if (temp == id_this && history_array[i].get_type() == "Rename") {
+ return " AS `" + history_array[i].get_obj().getrename_to() + "`";
+ }
+ }
+ return "";
+}
+
+function gradient(id, level)
+{
+ var box = document.getElementById(id);
+ box.style.opacity = level;
+ box.style.MozOpacity = level;
+ box.style.KhtmlOpacity = level;
+ box.style.filter = "alpha(opacity=" + level * 100 + ")";
+ box.style.display = "block";
+ return;
+}
+
+
+function fadein(id)
+{
+ var level = 0;
+ while (level <= 1) {
+ setTimeout("gradient('" + id + "'," + level + ")", (level * 1000) + 10);
+ level += 0.01;
+ }
+}
+
+ /**
+ * This function builds from clause of query
+ * makes automatic joins.
+ *
+ *
+ */
+function query_from()
+{
+ var i;
+ var tab_left = [];
+ var tab_used = [];
+ var t_tab_used = [];
+ var t_tab_left = [];
+ var temp;
+ var query = "";
+ var quer = "";
+ var parts = [];
+ var t_array = [];
+ t_array = from_array;
+ var K = 0;
+ var k;
+ var key;
+ var key2;
+ var key3;
+ var parts1;
+ for (i = 0; i < history_array.length; i++) {
+ from_array.push(history_array[i].get_tab());
+ }
+ from_array = unique(from_array);
+ tab_left = from_array;
+ temp = tab_left.shift();
+ quer = temp;
+ tab_used.push(temp);
+ // if master table (key2) matches with tab used get all keys and check if tab_left matches
+ // after this check if master table (key2) matches with tab left then check if any foreign matches with master .
+ for (i = 0; i < 2; i++) {
+ for (K in contr) {
+ for (key in contr[K]) {// contr name
+ for (key2 in contr[K][key]) {// table name
+ parts = key2.split(".");
+ if (found(tab_used, parts[1]) > 0) {
+ for (key3 in contr[K][key][key2]) {
+ parts1 = contr[K][key][key2][key3][0].split(".");
+ if (found(tab_left, parts1[1]) > 0) {
+ query += "\n" + 'LEFT JOIN ';
+ query += '`' + parts1[0] + '`.`' + parts1[1] + '` ON ';
+ query += '`' + parts[1] + '`.`' + key3 + '` = ';
+ query += '`' + parts1[1] + '`.`' + contr[K][key][key2][key3][1] + '` ';
+ t_tab_left.push(parts1[1]);
+ }
+ }
+ }
+ }
+ }
+ }
+ K = 0;
+ t_tab_left = unique(t_tab_left);
+ tab_used = add_array(t_tab_left, tab_used);
+ tab_left = remove_array(t_tab_left, tab_left);
+ t_tab_left = [];
+ for (K in contr) {
+ for (key in contr[K]) {
+ for (key2 in contr[K][key]) {// table name
+ parts = key2.split(".");
+ if (found(tab_left, parts[1]) > 0) {
+ for (key3 in contr[K][key][key2]) {
+ parts1 = contr[K][key][key2][key3][0].split(".");
+ if (found(tab_used, parts1[1]) > 0) {
+ query += "\n" + 'LEFT JOIN ';
+ query += '`' + parts[0] + '`.`' + parts[1] + '` ON ';
+ query += '`' + parts1[1] + '`.`' + contr[K][key][key2][key3][1] + '` = ';
+ query += '`' + parts[1] + '`.`' + key3 + '` ';
+ t_tab_left.push(parts[1]);
+ }
+ }
+ }
+ }
+ }
+ }
+ t_tab_left = unique(t_tab_left);
+ tab_used = add_array(t_tab_left, tab_used);
+ tab_left = remove_array(t_tab_left, tab_left);
+ t_tab_left = [];
+ }
+ for (k in tab_left) {
+ quer += " , `" + tab_left[k] + "`";
+ }
+ query = quer + query;
+ from_array = t_array;
+ return query;
+}
+
+/**
+ * This function is the main function for query building.
+ * uses history object details for this.
+ *
+ * @ uses query_where()
+ * @ uses query_groupby()
+ * @ uses query_having()
+ * @ uses query_orderby()
+ *
+ * @param formtitle title for the form
+ * @param fadin
+ */
+
+function build_query(formtitle, fadin)
+{
+ var q_select = "SELECT ";
+ var temp;
+ for (var i = 0;i < select_field.length; i++) {
+ temp = check_aggregate(select_field[i]);
+ if (temp !== "") {
+ q_select += temp;
+ temp = check_rename(select_field[i]);
+ q_select += temp + ",";
+ } else {
+ temp = check_rename(select_field[i]);
+ q_select += select_field[i] + temp + ",";
+ }
+ }
+ q_select = q_select.substring(0, q_select.length - 1);
+ q_select += " FROM " + query_from();
+ if (query_where() !== "") {
+ q_select += "\n WHERE";
+ q_select += query_where();
+ }
+ if (query_groupby() !== "") { q_select += "\nGROUP BY " + query_groupby(); }
+ if (query_having() !== "") { q_select += "\nHAVING " + query_having(); }
+ if (query_orderby() !== "") { q_select += "\nORDER BY " + query_orderby(); }
+ var box = document.getElementById('box');
+ document.getElementById('filter').style.display = 'block';
+ var btitle = document.getElementById('boxtitle');
+ btitle.innerHTML = 'SELECT';//formtitle;
+ if (fadin) {
+ gradient("box", 0);
+ fadein("box");
+ } else {
+ box.style.display = 'block';
+ }
+ document.getElementById('textSqlquery').innerHTML = q_select;
+}
+
+function closebox()
+{
+ document.getElementById('box').style.display = 'none';
+ document.getElementById('filter').style.display = 'none';
+}
diff --git a/js/pmd/iecanvas.js b/js/pmd/iecanvas.js
new file mode 100644
index 0000000000..bc68ec659c
--- /dev/null
+++ b/js/pmd/iecanvas.js
@@ -0,0 +1,151 @@
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ *
+ * @package PhpMyAdmin-Designer
+ */
+
+/**
+ *
+ */
+if (document.all) { // if IE
+ document.attachEvent(
+ "onreadystatechange", // document load
+ function () {
+ if (document.readyState == "complete") {
+ var el = document.getElementById("canvas");
+ var outerHTML = el.outerHTML;
+ var newEl = document.createElement(outerHTML);
+ el.parentNode.replaceChild(newEl, el);
+ el = newEl;
+ el.getContext = function () {
+ if (this.cont) {
+ return this.cont;
+ }
+ return this.cont = new PMD_2D(this);
+ };
+
+ el.style.width = el.attributes.width.nodeValue + "px";
+ el.style.height = el.attributes.height.nodeValue + "px";
+ }
+ }
+ );
+
+ //*****************************************************************************************************
+
+ function convert_style(str) {
+ var m = [];
+ m = str.match(/.*\((\d*),(\d*),(\d*),(\d*)\)/);
+ for (var i = 1; i <= 3; i++) {
+ m[i] = (m[i] * 1).toString(16).length < 2 ? '0' + (m[i] * 1).toString(16) : (m[i] * 1).toString(16);
+ }
+ return ['#' + m[1] + m[2] + m[3], 1];
+ }
+ //------------------------------------------------------------------------------
+ function PMD_2D(th) {
+ this.element_ = th;
+ this.pmd_arr = [];
+ this.strokeStyle;
+ this.fillStyle;
+ this.lineWidth;
+
+ this.closePath = function () {
+ this.pmd_arr.push({type: "close"});
+ };
+
+ this.clearRect = function () {
+ this.element_.innerHTML = "";
+ this.pmd_arr = [];
+ };
+
+ this.beginPath = function () {
+ this.pmd_arr = [];
+ };
+
+ this.moveTo = function (aX, aY) {
+ this.pmd_arr.push({type: "moveTo", x: aX, y: aY});
+ };
+
+ this.lineTo = function (aX, aY) {
+ this.pmd_arr.push({type: "lineTo", x: aX, y: aY});
+ };
+
+ this.arc = function (aX, aY, aRadius, aStartAngle, aEndAngle, aClockwise) {
+ if (!aClockwise) {
+ var t = aStartAngle;
+ aStartAngle = aEndAngle;
+ aEndAngle = t;
+ }
+
+ var xStart = aX + (Math.cos(aStartAngle) * aRadius);
+ var yStart = aY + (Math.sin(aStartAngle) * aRadius);
+
+ var xEnd = aX + (Math.cos(aEndAngle) * aRadius);
+ var yEnd = aY + (Math.sin(aEndAngle) * aRadius);
+
+ this.pmd_arr.push({type: "arc", x: aX, y: aY,
+ radius: aRadius, xStart: xStart, yStart: yStart, xEnd: xEnd, yEnd: yEnd});
+ };
+
+ this.rect = function (aX, aY, aW, aH) {
+ this.moveTo(aX, aY);
+ this.lineTo(aX + aW, aY);
+ this.lineTo(aX + aW, aY + aH);
+ this.lineTo(aX, aY + aH);
+ this.closePath();
+ };
+
+ this.fillRect = function (aX, aY, aW, aH) {
+ this.beginPath();
+ this.moveTo(aX, aY);
+ this.lineTo(aX + aW, aY);
+ this.lineTo(aX + aW, aY + aH);
+ this.lineTo(aX, aY + aH);
+ this.closePath();
+ this.stroke(true);
+ };
+
+ this.stroke = function (aFill) {
+ var Str = [];
+ var a = convert_style(aFill ? this.fillStyle : this.strokeStyle);
+ var color = a[0];
+
+ Str.push('<v:shape',
+ ' fillcolor="', color, '"',
+ ' filled="', Boolean(aFill), '"',
+ ' style="position:absolute;width:10;height:10;"',
+ ' coordorigin="0 0" coordsize="10 10"',
+ ' stroked="', !aFill, '"',
+ ' strokeweight="', this.lineWidth, '"',
+ ' strokecolor="', color, '"',
+ ' path="');
+
+ for (var i = 0; i < this.pmd_arr.length; i++) {
+ var p = this.pmd_arr[i];
+
+ if (p.type == "moveTo") {
+ Str.push(" m ");
+ Str.push(Math.floor(p.x), ",", Math.floor(p.y));
+ } else if (p.type == "lineTo") {
+ Str.push(" l ");
+ Str.push(Math.floor(p.x), ",", Math.floor(p.y));
+ } else if (p.type == "close") {
+ Str.push(" x ");
+ } else if (p.type == "arc") {
+ Str.push(" ar ");
+ Str.push(Math.floor(p.x - p.radius), ",",
+ Math.floor(p.y - p.radius), " ",
+ Math.floor(p.x + p.radius), ",",
+ Math.floor(p.y + p.radius), " ",
+ Math.floor(p.xStart), ",", Math.floor(p.yStart), " ",
+ Math.floor(p.xEnd), ",", Math.floor(p.yEnd));
+ }
+ }
+
+ Str.push(' ">');
+ Str.push("</v:shape>");
+
+ this.element_.insertAdjacentHTML("beforeEnd", Str.join(""));
+ this.pmd_arr = [];
+ };
+ }
+}
diff --git a/js/pmd/init.js b/js/pmd/init.js
new file mode 100644
index 0000000000..d0832b0481
--- /dev/null
+++ b/js/pmd/init.js
@@ -0,0 +1,31 @@
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Initialises the data required to run PMD, then fires it up.
+ */
+
+var j_tabs, h_tabs, contr, server, db, token;
+
+AJAX.registerTeardown('pmd/init.js', function () {
+ $(".trigger").unbind('click');
+});
+
+AJAX.registerOnload('pmd/init.js', function () {
+ $(".trigger").click(function () {
+ $(".panel").toggle("fast");
+ $(this).toggleClass("active");
+ return false;
+ });
+
+ var tables_data = $.parseJSON($("#script_tables").html());
+
+ j_tabs = tables_data.j_tabs;
+ h_tabs = tables_data.h_tabs;
+ contr = $.parseJSON($("#script_contr").html());
+ display_field = $.parseJSON($("#script_display_field").html());
+
+ server = $("#script_server").html();
+ db = $("#script_db").html();
+ token = $("#script_token").html();
+
+ Main();
+});
diff --git a/js/pmd/move.js b/js/pmd/move.js
new file mode 100644
index 0000000000..0fc1a6644f
--- /dev/null
+++ b/js/pmd/move.js
@@ -0,0 +1,1279 @@
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * @package PhpMyAdmin-Designer
+ */
+
+/**
+ * init
+ */
+
+
+var _change = 0; // variable to track any change in designer layout.
+var _staying = 0; // variable to check if the user stayed after seeing the confirmation prompt.
+var show_relation_lines = true;
+
+AJAX.registerTeardown('pmd/move.js', function () {
+ if ($.FullScreen.supported) {
+ $(document).unbind($.FullScreen.prefix + 'fullscreenchange');
+ }
+});
+
+AJAX.registerOnload('pmd/move.js', function () {
+ $('#page_content').css({'margin-left': '3px'});
+ $('#exitFullscreen').hide();
+ if ($.FullScreen.supported) {
+ $(document).fullScreenChange(function () {
+ if (! $.FullScreen.isFullScreen()) {
+ $('#page_content').removeClass('content_fullscreen')
+ .css({'width': 'auto', 'height': 'auto'});
+ $('#enterFullscreen').show();
+ $('#exitFullscreen').hide();
+ Top_menu_reposition($('#key_Left_Right')[0]);
+ }
+ });
+ } else {
+ $('#enterFullscreen').hide();
+ }
+});
+
+// Below is the function to bind onbeforeunload events with the content_frame as well as the top window.
+
+/*
+FIXME: we can't register the beforeonload event because it will persist between pageloads
+
+AJAX.registerOnload('pmd/move.js', function (){
+ $(window).bind('beforeunload', function () { // onbeforeunload for the frame window.
+ if (_change == 1 && _staying === 0) {
+ return PMA_messages.strLeavingDesigner;
+ } else if (_change == 1 && _staying == 1) {
+ _staying = 0;
+ }
+ });
+ $(window).unload(function () {
+ _change = 0;
+ });
+ window.top.onbeforeunload = function () { // onbeforeunload for the browser main window.
+ if (_change == 1 && _staying === 0) {
+ _staying = 1; // Helps if the user stays on the page as there
+ setTimeout('make_zero();', 100); // is no other way of knowing whether the user stayed or not.
+ return PMA_messages.strLeavingDesigner;
+ }
+ };
+});*/
+
+function make_zero() { // Function called if the user stays after seeing the confirmation prompt.
+ _staying = 0;
+}
+
+
+var dx, dy, dy2;
+var cur_click = null;
+// update in Main()
+var sm_x = 2, sm_y = 2;
+var sm_s = 0;
+var sm_add = 10;
+var s_left = 0;
+var s_right = 0;
+var ON_relation = 0;
+var ON_grid = 0;
+var ON_display_field = 0;
+// relation_style: 0 - angular 1 - direct
+var ON_angular_direct = 1;
+var click_field = 0;
+var link_relation = "";
+var id_hint;
+var canvas_width = 0;
+var canvas_height = 0;
+var osn_tab_width = 0;
+var osn_tab_height = 0;
+var height_field = 7;
+var Glob_X, Glob_Y;
+var timeoutID;
+var layer_menu_cur_click = 0;
+var step = 10;
+var old_class;
+var from_array = [];
+var downer;
+
+//------------------------------------------------------------------------------
+//------------------------------------------------------------------------------
+//------------------------------------------------------------------------------
+
+
+//window.captureEvents(Event.MOUSEDOWN | Event.MOUSEUP);
+//---CROSS
+document.onmousedown = MouseDown;
+document.onmouseup = MouseUp;
+document.onmousemove = MouseMove;
+
+var isIE = document.all && !window.opera;
+var isNN = !document.all && document.getElementById;
+var isN4 = document.layers;
+
+if (isIE) {
+ window.onscroll = General_scroll;
+ document.onselectstart = function () {
+ return false;
+ };
+}
+
+//document.onmouseup = function (){General_scroll_end();}
+function MouseDown(e)
+{
+ var offsetx, offsety;
+ if (cur_click !== null) {
+ offsetx = isIE ? event.clientX + document.body.scrollLeft : e.pageX;
+ offsety = isIE ? event.clientY + document.body.scrollTop : e.pageY;
+ dx = offsetx - parseInt(cur_click.style.left, 10);
+ dy = offsety - parseInt(cur_click.style.top, 10);
+ //alert(" dx = " + dx + " dy = " +dy);
+ document.getElementById("canvas").style.display = 'none';
+ /*
+ var left = parseInt(cur_click.style.left, 10);
+ var top = parseInt(cur_click.style.top, 10);
+ dx = e.pageX - left;
+ dy = e.pageY - top;
+
+ alert(" dx = " + dx + " dy = " +dy);
+ */
+ cur_click.style.zIndex = 2;
+ }
+ if (layer_menu_cur_click) {
+ offsetx = e.pageX;
+ dx = offsetx - parseInt(document.getElementById("layer_menu").style.width, 10);
+ }
+}
+
+function MouseMove(e)
+{
+ //Glob_X = e.pageX;
+ //Glob_Y = e.pageY;
+ Glob_X = isIE ? event.clientX + document.body.scrollLeft : e.pageX;
+ Glob_Y = isIE ? event.clientY + document.body.scrollTop : e.pageY;
+
+ //mouseX = (bw.ns4||bw.ns6)? e.pageX: bw.ie&&bw.win&&!bw.ie4? (event.clientX-2)+document.body.scrollLeft : event.clientX+document.body.scrollLeft;
+ //mouseY = (bw.ns4||bw.ns6)? e.pageY: bw.ie&&bw.win&&!bw.ie4? (event.clientY-2)+document.body.scrollTop : event.clientY+document.body.scrollTop;
+
+ //window.status = "X = "+ Glob_X + " Y = "+ Glob_Y;
+
+ if (cur_click !== null) {
+ _change = 1;
+ var mGx = Glob_X - dx;
+ var mGy = Glob_Y - dy;
+ mGx = mGx > 0 ? mGx : 0;
+ mGy = mGy > 0 ? mGy : 0;
+
+ if (ON_grid) {
+ mGx = mGx % step < step / 2 ? mGx - mGx % step : mGx - mGx % step + step;
+ mGy = mGy % step < step / 2 ? mGy - mGy % step : mGy - mGy % step + step;
+ }
+
+ cur_click.style.left = mGx + 'px';
+ cur_click.style.top = mGy + 'px';
+ }
+
+ if (ON_relation || ON_display_field) {
+ document.getElementById('pmd_hint').style.left = (Glob_X + 20) + 'px';
+ document.getElementById('pmd_hint').style.top = (Glob_Y + 20) + 'px';
+ }
+
+ if (layer_menu_cur_click) {
+ document.getElementById("layer_menu").style.width = ((Glob_X - dx) >= 150 ? Glob_X - dx : 150) + 'px';
+ //document.getElementById("layer_menu").style.height = Glob_Y - dy>=200?Glob_Y - dy:200;
+ //document.getElementById("id_scroll_tab").style.height = Glob_Y - dy2;
+ }
+}
+
+function MouseUp(e)
+{
+ if (cur_click !== null) {
+ document.getElementById("canvas").style.display = 'inline-block';
+ Re_load();
+ cur_click.style.zIndex = 1;
+ cur_click = null;
+ }
+ layer_menu_cur_click = 0;
+ //window.releaseEvents(Event.MOUSEMOVE);
+}
+//------------------------------------------------------------------------------
+//------------------------------------------------------------------------------
+//------------------------------------------------------------------------------
+
+
+//function ToInt(s)
+//{
+// return s.substring(0,s.length-2)*1; //re = /(\d+)\w*/; newstr = str.replace(re, "$1");
+//}
+
+function Canvas_pos()
+{
+ canvas_width = document.getElementById('canvas').width = osn_tab_width - 3;
+ canvas_height = document.getElementById('canvas').height = osn_tab_height - 3;
+
+ if (isIE) {
+ document.getElementById('canvas').style.width = ((osn_tab_width - 3) ? (osn_tab_width - 3) : 0) + 'px';
+ document.getElementById('canvas').style.height = ((osn_tab_height - 3) ? (osn_tab_height - 3) : 0) + 'px';
+ }
+}
+
+function Osn_tab_pos()
+{
+ osn_tab_width = parseInt(document.getElementById('osn_tab').style.width, 10);
+ osn_tab_height = parseInt(document.getElementById('osn_tab').style.height, 10);
+}
+
+
+function Main()
+{
+ //alert( document.getElementById('osn_tab').offsetTop);
+ //---CROSS
+
+ document.getElementById("layer_menu").style.top = -1000 + 'px'; //fast scroll
+ sm_x += document.getElementById('osn_tab').offsetLeft;
+ sm_y += document.getElementById('osn_tab').offsetTop;
+ Osn_tab_pos();
+ Canvas_pos();
+ Small_tab_refresh();
+ Re_load();
+ id_hint = document.getElementById('pmd_hint');
+ if (isIE) {
+ General_scroll();
+ }
+}
+
+
+//-------------------------------- new -----------------------------------------
+function Rezize_osn_tab()
+{
+ var max_X = 0;
+ var max_Y = 0;
+ for (var key in j_tabs) {
+ var k_x = parseInt(document.getElementById(key).style.left, 10) + document.getElementById(key).offsetWidth;
+ var k_y = parseInt(document.getElementById(key).style.top, 10) + document.getElementById(key).offsetHeight;
+ max_X = max_X < k_x ? k_x : max_X;
+ max_Y = max_Y < k_y ? k_y : max_Y;
+ }
+
+ osn_tab_width = max_X + 50;
+ osn_tab_height = max_Y + 50;
+ Canvas_pos();
+ document.getElementById('osn_tab').style.width = osn_tab_width + 'px';
+ document.getElementById('osn_tab').style.height = osn_tab_height + 'px';
+}
+//------------------------------------------------------------------------------
+
+/**
+ * refreshes display, must be called after state changes
+ */
+function Re_load()
+{
+ Rezize_osn_tab();
+ var n;
+ var x1;
+ var x2;
+ var a = [];
+ var K;
+ var key;
+ var key2;
+ var key3;
+ Clear();
+ for (K in contr) {
+ for (key in contr[K]) {
+ // contr name
+ for (key2 in contr[K][key]) {
+ // table name
+ for (key3 in contr[K][key][key2]) {
+ // field name
+ if (!document.getElementById("check_vis_" + key2).checked ||
+ !document.getElementById("check_vis_" + contr[K][key][key2][key3][0]).checked) {
+ // if hide
+ continue;
+ }
+ var x1_left = document.getElementById(key2).offsetLeft + 1;
+ var x1_right = x1_left + document.getElementById(key2).offsetWidth;
+ var x2_left = document.getElementById(contr[K][key][key2][key3][0]).offsetLeft;
+ var x2_right = x2_left + document.getElementById(contr[K][key][key2][key3][0]).offsetWidth;
+ a[0] = Math.abs(x1_left - x2_left);
+ a[1] = Math.abs(x1_left - x2_right);
+ a[2] = Math.abs(x1_right - x2_left);
+ a[3] = Math.abs(x1_right - x2_right);
+ n = s_left = s_right = 0;
+ for (var i = 1; i < 4; i++) {
+ if (a[n] > a[i]) {
+ n = i;
+ }
+ }
+ if (n == 1) {
+ x1 = x1_left - sm_s;
+ x2 = x2_right + sm_s;
+ if (x1 < x2) {
+ n = 0;
+ }
+ }
+ if (n == 2) {
+ x1 = x1_right + sm_s;
+ x2 = x2_left - sm_s;
+ if (x1 > x2) {
+ n = 0;
+ }
+ }
+ if (n == 3) {
+ x1 = x1_right + sm_s;
+ x2 = x2_right + sm_s;
+ s_right = 1;
+ }
+ if (n === 0) {
+ x1 = x1_left - sm_s;
+ x2 = x2_left - sm_s;
+ s_left = 1;
+ }
+ //alert(key2 + "." + key3);
+
+ var row_offset_top = 0;
+ //alert('id_tbody_' + key2);
+ //alert(document.getElementById('id_hide_tbody_' + key2));
+ var tab_hide_button = document.getElementById('id_hide_tbody_' + key2);
+
+ //alert(tab_hide_button.innerHTML);
+ if (tab_hide_button.innerHTML == 'v') {
+ row_offset_top = document.getElementById(key2 + "." + key3).offsetTop;
+ }
+
+ var y1 = document.getElementById(key2).offsetTop
+ + row_offset_top
+ + height_field;
+ //alert(1);
+
+ row_offset_top = 0;
+ var tab_hide_button = document.getElementById('id_hide_tbody_' + contr[K][key][key2][key3][0]);
+ if (tab_hide_button.innerHTML == 'v') {
+ row_offset_top = document.getElementById(contr[K][key][key2][key3][0]
+ + '.' + contr[K][key][key2][key3][1]).offsetTop;
+ }
+
+ var y2 =
+ document.getElementById(contr[K][key][key2][key3][0]).offsetTop
+ + row_offset_top
+ + height_field;
+
+ //alert(y1 + ' - ' + key2 + "." + key3);
+ Line0(
+ x1 - sm_x,
+ y1 - sm_y,
+ x2 - sm_x,
+ y2 - sm_y,
+ getColorByTarget(contr[K][key][key2][key3][0] + '.' + contr[K][key][key2][key3][1])
+ );
+ }
+ }
+ }
+ }
+}
+
+/**
+ * draws a line from x1:y1 to x2:y2 with color
+ */
+function Line(x1, y1, x2, y2, color_line)
+{
+ var canvas = document.getElementById("canvas");
+ var ctx = canvas.getContext("2d");
+ ctx.strokeStyle = color_line;
+ ctx.lineWidth = 1;
+ ctx.beginPath();
+ ctx.moveTo(x1, y1);
+ ctx.lineTo(x2, y2);
+ ctx.stroke();
+}
+
+/**
+ * draws a relation/constraint line, whether angular or not
+ */
+function Line0(x1, y1, x2, y2, color_line)
+{
+ if (! show_relation_lines) {
+ return;
+ }
+ Circle(x1, y1, 3, 3, color_line);
+ Rect(x2 - 1, y2 - 2, 4, 4, color_line);
+
+ if (ON_angular_direct) {
+ Line2(x1, y1, x2, y2, color_line);
+ } else {
+ Line3(x1, y1, x2, y2, color_line);
+ }
+}
+
+/**
+ * draws a angualr relation/constraint line
+ */
+function Line2(x1, y1, x2, y2, color_line)
+{
+ var x1_ = x1;
+ var x2_ = x2;
+
+ if (s_right) {
+ x1_ += sm_add;
+ x2_ += sm_add;
+ } else if (s_left) {
+ x1_ -= sm_add;
+ x2_ -= sm_add;
+ } else if (x1 < x2) {
+ x1_ += sm_add;
+ x2_ -= sm_add;
+ } else {
+ x1_ -= sm_add;
+ x2_ += sm_add;
+ }
+
+ Line(x1, y1, x1_, y1, color_line);
+ Line(x2, y2, x2_, y2, color_line);
+ Line(x1_, y1, x2_, y2, color_line);
+}
+
+/**
+ * draws a relation/constraint line
+ */
+function Line3(x1, y1, x2, y2, color_line)
+{
+ var x1_ = x1;
+ var x2_ = x2;
+
+ if (s_right) {
+ if (x1 < x2) {
+ x1_ += x2 - x1 + sm_add;
+ x2_ += sm_add;
+ } else {
+ x2_ += x1 - x2 + sm_add;
+ x1_ += sm_add;
+ }
+
+ Line(x1, y1, x1_, y1, color_line);
+ Line(x2, y2, x2_, y2, color_line);
+ Line(x1_, y1, x2_, y2, color_line);
+ return;
+ }
+ if (s_left) {
+ if (x1 < x2) {
+ x2_ -= x2 - x1 + sm_add;
+ x1_ -= sm_add;
+ } else {
+ x1_ -= x1 - x2 + sm_add;
+ x2_ -= sm_add;
+ }
+
+ Line(x1, y1, x1_, y1, color_line);
+ Line(x2, y2, x2_, y2, color_line);
+ Line(x1_, y1, x2_, y2, color_line);
+ return;
+ }
+
+ var x_s = (x1 + x2) / 2;
+ Line(x1, y1, x_s, y1, color_line);
+ Line(x_s, y2, x2, y2, color_line);
+ Line(x_s, y1, x_s, y2, color_line);
+}
+
+function Circle(x, y, r, w, color)
+{
+ var ctx = document.getElementById('canvas').getContext('2d');
+ ctx.beginPath();
+ ctx.moveTo(x, y);
+ ctx.lineWidth = w;
+ ctx.strokeStyle = color;
+ ctx.arc(x, y, r, 0, 2 * Math.PI, true);
+ ctx.stroke();
+}
+
+function Clear()
+{
+ var canvas = document.getElementById("canvas");
+ var ctx = canvas.getContext("2d");
+ ctx.clearRect(0, 0, canvas_width, canvas_height);
+}
+
+function Rect(x1, y1, w, h, color)
+{
+ var ctx = document.getElementById('canvas').getContext('2d');
+ ctx.fillStyle = color;
+ ctx.fillRect(x1, y1, w, h);
+}
+//--------------------------- FULLSCREEN -------------------------------------
+function Enter_fullscreen()
+{
+ if (! $.FullScreen.isFullScreen()) {
+ $('#enterFullscreen').hide();
+ $('#exitFullscreen').show();
+ $('#page_content')
+ .addClass('content_fullscreen')
+ .css({'width': screen.width - 5, 'height': screen.height - 5})
+ .requestFullScreen();
+ Top_menu_reposition($('#key_Left_Right')[0]);
+ }
+}
+
+function Exit_fullscreen()
+{
+ if ($.FullScreen.isFullScreen()) {
+ $.FullScreen.cancelFullScreen();
+ }
+}
+//------------------------------ SAVE ------------------------------------------
+function Save(url) // (del?) no for pdf
+{
+ for (var key in j_tabs) {
+ document.getElementById('t_x_' + key + '_').value = parseInt(document.getElementById(key).style.left, 10);
+ document.getElementById('t_y_' + key + '_').value = parseInt(document.getElementById(key).style.top, 10);
+ document.getElementById('t_v_' + key + '_').value = document.getElementById('id_tbody_' + key).style.display == 'none' ? 0 : 1;
+ document.getElementById('t_h_' + key + '_').value = document.getElementById('check_vis_' + key).checked ? 1 : 0;
+ }
+ document.form1.action = url;
+ $(document.form1).submit();
+}
+
+function Get_url_pos()
+{
+ var poststr = '';
+ for (var key in j_tabs) {
+ poststr += '&t_x[' + key + ']=' + parseInt(document.getElementById(key).style.left, 10);
+ poststr += '&t_y[' + key + ']=' + parseInt(document.getElementById(key).style.top, 10);
+ poststr += '&t_v[' + key + ']=' + (document.getElementById('id_tbody_' + key).style.display == 'none' ? 0 : 1);
+ poststr += '&t_h[' + key + ']=' + (document.getElementById('check_vis_' + key).checked ? 1 : 0);
+ }
+ return poststr;
+}
+
+function Save2()
+{
+ _change = 0;
+ var poststr = 'IS_AJAX=1&server=' + server + '&db=' + db + '&token=' + token + '&die_save_pos=1';
+ poststr += Get_url_pos();
+ makeRequest('pmd_save_pos.php', poststr);
+}
+
+function Grid()
+{
+ if (!ON_grid) {
+ ON_grid = 1;
+ document.getElementById('grid_button').className = 'M_butt_Selected_down';
+ } else {
+ document.getElementById('grid_button').className = 'M_butt';
+ ON_grid = 0;
+ }
+}
+
+function Angular_direct()
+{
+ if (ON_angular_direct) {
+ ON_angular_direct = 0;
+ document.getElementById('angular_direct_button').className = 'M_butt_Selected_down';
+ } else {
+ ON_angular_direct = 1;
+ document.getElementById('angular_direct_button').className = 'M_butt';
+ }
+ Re_load();
+}
+//++++++++++++++++++++++++++++++ RELATION ++++++++++++++++++++++++++++++++++++++
+function Start_relation()
+{
+ if (ON_display_field) {
+ return;
+ }
+
+ if (!ON_relation) {
+ document.getElementById('foreign_relation').style.display = '';
+ ON_relation = 1;
+ document.getElementById('pmd_hint').innerHTML = PMA_messages.strSelectReferencedKey;
+ document.getElementById('pmd_hint').style.display = 'block';
+ document.getElementById('rel_button').className = 'M_butt_Selected_down';
+ } else {
+ document.getElementById('pmd_hint').innerHTML = "";
+ document.getElementById('pmd_hint').style.display = 'none';
+ document.getElementById('rel_button').className = 'M_butt';
+ click_field = 0;
+ ON_relation = 0;
+ }
+}
+
+function Click_field(T, f, PK) // table field
+{
+ if (ON_relation) {
+ if (!click_field) {
+ //.style.display=='none' .style.display = 'none'
+ if (!PK) {
+ alert(PMA_messages.strPleaseSelectPrimaryOrUniqueKey);
+ return;// 0;
+ }//PK
+ if (j_tabs[db + '.' + T] != '1') {
+ document.getElementById('foreign_relation').style.display = 'none';
+ }
+ click_field = 1;
+ link_relation = "T1=" + T + "&F1=" + f;
+ document.getElementById('pmd_hint').innerHTML = PMA_messages.strSelectForeignKey;
+ } else {
+ Start_relation(); // hidden hint...
+ if (j_tabs[db + '.' + T] != '1' || !PK) {
+ document.getElementById('foreign_relation').style.display = 'none';
+ }
+ var left = Glob_X - (document.getElementById('layer_new_relation').offsetWidth>>1);
+ document.getElementById('layer_new_relation').style.left = left + 'px';
+ var top = Glob_Y - document.getElementById('layer_new_relation').offsetHeight;
+ document.getElementById('layer_new_relation').style.top = top + 'px';
+ document.getElementById('layer_new_relation').style.display = 'block';
+ link_relation += '&T2=' + T + '&F2=' + f;
+ }
+ }
+
+ if (ON_display_field) {
+ // if is display field
+ if (display_field[T] == f) {
+ //alert(T);
+ //s = '';for(k in display_field)s += k + ' = ' + display_field[k] + ',';alert(s);
+ old_class = 'tab_field';
+ //display_field.splice(T, 1);
+ delete display_field[T];
+ //s = '';for(k in display_field)s += k + ' = ' + display_field[k] + ', ';alert(s);
+ //n = 0;for(k in display_field)n++;alert(n);
+ } else {
+ old_class = 'tab_field_3';
+ if (display_field[T]) {
+ document.getElementById('id_tr_' + T + '.' + display_field[T]).className = 'tab_field';
+ //display_field.splice(T, 1);
+ delete display_field[T];
+ }
+ display_field[T] = f;
+ }
+ ON_display_field = 0;
+ document.getElementById('pmd_hint').innerHTML = "";
+ document.getElementById('pmd_hint').style.display = 'none';
+ document.getElementById('display_field_button').className = 'M_butt';
+ makeRequest('pmd_display_field.php', 'T=' + T + '&F=' + f + '&server=' + server + '&db=' + db + '&token=' + token);
+ }
+}
+
+function New_relation()
+{
+ document.getElementById('layer_new_relation').style.display = 'none';
+ link_relation += '&server=' + server + '&db=' + db + '&token=' + token + '&die_save_pos=0';
+ link_relation += '&on_delete=' + document.getElementById('on_delete').value + '&on_update=' + document.getElementById('on_update').value;
+ link_relation += Get_url_pos();
+
+ //alert(link_relation);
+ makeRequest('pmd_relation_new.php', link_relation);
+}
+
+//-------------------------- create tables -------------------------------------
+
+function Start_table_new()
+{
+ PMA_commonParams.set('table', '');
+ PMA_commonActions.refreshMain('tbl_create.php');
+}
+
+function Start_tab_upd(table)
+{
+ PMA_commonParams.set('table', table);
+ PMA_commonActions.refreshMain('tbl_structure.php');
+}
+//--------------------------- hide tables --------------------------------------
+
+function Small_tab_all(id_this) // max/min all tables
+{
+ if (id_this.alt == "v") {
+ for (var key in j_tabs) {
+ if (document.getElementById('id_hide_tbody_' + key).innerHTML == "v") {
+ Small_tab(key, 0);
+ }
+ }
+ id_this.alt = ">";
+ id_this.src = pmaThemeImage + "pmd/rightarrow1.png";
+ } else {
+ for (var key in j_tabs) {
+ if (document.getElementById('id_hide_tbody_' + key).innerHTML != "v") {
+ Small_tab(key, 0);
+ }
+ }
+ id_this.alt = "v";
+ id_this.src = pmaThemeImage + "pmd/downarrow1.png";
+ }
+ Re_load();
+}
+
+function Small_tab_invert() // invert max/min all tables
+{
+ for (var key in j_tabs) {
+ Small_tab(key, 0);
+ }
+ Re_load();
+}
+
+function Relation_lines_invert()
+{
+ show_relation_lines = ! show_relation_lines;
+ Re_load();
+}
+
+function Small_tab_refresh()
+{
+ for (var key in j_tabs) {
+ if (document.getElementById('id_hide_tbody_' + key).innerHTML != "v") {
+ Small_tab(key, 0);
+ Small_tab(key, 0);
+ }
+ }
+}
+
+function Small_tab(t, re_load)
+{
+ var id = document.getElementById('id_tbody_' + t);
+ var id_this = document.getElementById('id_hide_tbody_' + t);
+ var id_t = document.getElementById(t);
+ id_t.style.width = id_t.offsetWidth + 'px';
+ if (id_this.innerHTML == "v") {
+ //---CROSS
+ id.style.display = 'none';
+ id_this.innerHTML = '>';
+ } else {
+ id.style.display = '';
+ id_this.innerHTML = 'v';
+ }
+ if (re_load) {
+ Re_load();
+ }
+}
+//------------------------------------------------------------------------------
+function Select_tab(t)
+{
+ var id_zag = document.getElementById('id_zag_' + t);
+ if (id_zag.className != 'tab_zag_3') {
+ document.getElementById('id_zag_' + t).className = 'tab_zag_2';
+ } else {
+ document.getElementById('id_zag_' + t).className = 'tab_zag';
+ }
+ //----------
+ var id_t = document.getElementById(t);
+ window.scrollTo(parseInt(id_t.style.left, 10) - 300, parseInt(id_t.style.top, 10) - 300);
+ setTimeout(
+ function () {
+ document.getElementById('id_zag_' + t).className = 'tab_zag';
+ },
+ 800
+ );
+}
+//------------------------------------------------------------------------------
+
+function Canvas_click(id)
+{
+ var n = 0;
+ var relation_name = 0;
+ var selected = 0;
+ var a = [];
+ var Key0, Key1, Key2, Key3, Key, x1, x2;
+ var K, key, key2, key3;
+ var Local_X = $.FullScreen.isFullScreen() ? Glob_X : Glob_X - document.getElementById("canvas_outer").offsetLeft;
+ var Local_Y = Glob_Y - document.getElementById("canvas_outer").offsetTop;
+ Clear();
+ for (K in contr) {
+ for (key in contr[K]) {
+ for (key2 in contr[K][key]) {
+ for (key3 in contr[K][key][key2]) {
+ if (!document.getElementById("check_vis_" + key2).checked
+ || !document.getElementById("check_vis_" + contr[K][key][key2][key3][0]).checked) {
+ continue; // if hide
+ }
+ var x1_left = document.getElementById(key2).offsetLeft + 1;//document.getElementById(key2+"."+key3).offsetLeft;
+ var x1_right = x1_left + document.getElementById(key2).offsetWidth;
+ var x2_left = document.getElementById(contr[K][key][key2][key3][0]).offsetLeft;//+document.getElementById(contr[K][key2][key3][0]+"."+contr[K][key2][key3][1]).offsetLeft
+ var x2_right = x2_left + document.getElementById(contr[K][key][key2][key3][0]).offsetWidth;
+ a[0] = Math.abs(x1_left - x2_left);
+ a[1] = Math.abs(x1_left - x2_right);
+ a[2] = Math.abs(x1_right - x2_left);
+ a[3] = Math.abs(x1_right - x2_right);
+ n = s_left = s_right = 0;
+ for (var i = 1; i < 4; i++) {
+ if (a[n] > a[i]) {
+ n = i;
+ }
+ }
+ if (n == 1) {
+ x1 = x1_left - sm_s;
+ x2 = x2_right + sm_s;
+ if (x1 < x2) {
+ n = 0;
+ }
+ }
+ if (n == 2) {
+ x1 = x1_right + sm_s;
+ x2 = x2_left - sm_s;
+ if (x1 > x2) {
+ n = 0;
+ }
+ }
+ if (n == 3) {
+ x1 = x1_right + sm_s;
+ x2 = x2_right + sm_s;
+ s_right = 1;
+ }
+ if (n === 0) {
+ x1 = x1_left - sm_s;
+ x2 = x2_left - sm_s;
+ s_left = 1;
+ }
+
+ var y1 = document.getElementById(key2).offsetTop + document.getElementById(key2 + "." + key3).offsetTop + height_field;
+ var y2 = document.getElementById(contr[K][key][key2][key3][0]).offsetTop +
+ document.getElementById(contr[K][key][key2][key3][0] + "." + contr[K][key][key2][key3][1]).offsetTop + height_field;
+ if (!selected && Local_X > x1 - 10 && Local_X < x1 + 10 && Local_Y > y1 - 7 && Local_Y < y1 + 7) {
+ Line0(x1 - sm_x, y1 - sm_y, x2 - sm_x, y2 - sm_y, "rgba(255,0,0,1)");
+ selected = 1; // Rect(x1-sm_x,y1-sm_y,10,10,"rgba(0,255,0,1)");
+ relation_name = key; //
+ Key0 = contr[K][key][key2][key3][0];
+ Key1 = contr[K][key][key2][key3][1];
+ Key2 = key2;
+ Key3 = key3;
+ Key = K;
+ } else {
+ Line0(
+ x1 - sm_x,
+ y1 - sm_y,
+ x2 - sm_x,
+ y2 - sm_y,
+ getColorByTarget(contr[K][key][key2][key3][0] + '.' + contr[K][key][key2][key3][1])
+ );
+ }
+ }
+ }
+ }
+ }
+ if (selected) {
+ // select relations
+ //alert(Key0+' - '+Key1+' - '+Key2+' - '+Key3);
+ var left = Glob_X - (document.getElementById('layer_upd_relation').offsetWidth>>1);
+ document.getElementById('layer_upd_relation').style.left = left + 'px';
+ var top = Glob_Y - document.getElementById('layer_upd_relation').offsetHeight - 10;
+ document.getElementById('layer_upd_relation').style.top = top + 'px';
+ document.getElementById('layer_upd_relation').style.display = 'block';
+ link_relation = 'T1=' + Key0 + '&F1=' + Key1 + '&T2=' + Key2 + '&F2=' + Key3 + '&K=' + Key;
+ }
+}
+
+function Upd_relation()
+{
+ document.getElementById('layer_upd_relation').style.display = 'none';
+ link_relation += '&server=' + server + '&db=' + db + '&token=' + token + '&die_save_pos=0';
+ link_relation += Get_url_pos();
+ makeRequest('pmd_relation_upd.php', link_relation);
+}
+
+function VisibleTab(id, t_n)
+{
+ if (id.checked) {
+ document.getElementById(t_n).style.display = 'block';
+ } else {
+ document.getElementById(t_n).style.display = 'none';
+ }
+ Re_load();
+}
+
+function Hide_tab_all(id_this) // max/min all tables
+{
+ if (id_this.alt == 'v') {
+ id_this.alt = '>';
+ id_this.src = pmaThemeImage + "pmd/rightarrow1.png";
+ } else {
+ id_this.alt = 'v';
+ id_this.src = pmaThemeImage + "pmd/downarrow1.png";
+ }
+ var E = document.form1;
+ for (var i = 0; i < E.elements.length; i++) {
+ if (E.elements[i].type == "checkbox" && E.elements[i].id.substring(0, 10) == 'check_vis_') {
+ if (id_this.alt == 'v') {
+ E.elements[i].checked = true;
+ document.getElementById(E.elements[i].value).style.display = '';
+ } else {
+ E.elements[i].checked = false;
+ document.getElementById(E.elements[i].value).style.display = 'none';
+ }
+ }
+ }
+ Re_load();
+}
+
+function in_array_k(x, m)
+{
+ var b = 0;
+ for (var u in m) {
+ if (x == u) {
+ b = 1;
+ break;
+ }
+ }
+ return b;
+}
+
+function No_have_constr(id_this)
+{
+ var a = [];
+ var K, key, key2, key3;
+ for (K in contr) {
+ for (key in contr[K]) {
+ // contr name
+ for (key2 in contr[K][key]) {
+ // table name
+ for (key3 in contr[K][key][key2]) {
+ // field name
+ a[key2] = a[contr[K][key][key2][key3][0]] = 1; // exist constr
+ }
+ }
+ }
+ }
+
+ if (id_this.alt == 'v') {
+ id_this.alt = '>';
+ id_this.src = pmaThemeImage + "pmd/rightarrow2.png";
+ } else {
+ id_this.alt = 'v';
+ id_this.src = pmaThemeImage + "pmd/downarrow2.png";
+ }
+ var E = document.form1;
+ for (var i = 0; i < E.elements.length; i++) {
+ if (E.elements[i].type == "checkbox" && E.elements[i].id.substring(0, 10) == 'check_vis_') {
+ if (!in_array_k(E.elements[i].value, a)) {
+ if (id_this.alt == 'v') {
+ E.elements[i].checked = true;
+ document.getElementById(E.elements[i].value).style.display = '';
+ } else {
+ E.elements[i].checked = false;
+ document.getElementById(E.elements[i].value).style.display = 'none';
+ }
+ }
+ }
+ }
+}
+
+function PDF_save()
+{
+ // var WinPDF =
+ // window.open("pmd_pdf.php?token="+token+"&db="+db,"wind1", "top=200,left=200,width=200,height=100,resizable=yes,scrollbars=yes,menubar=no");
+ Save('pmd_pdf.php?server=' + server + '&token=' + token + '&db=' + db);
+}
+
+function General_scroll()
+{
+ /*
+ if (!document.getElementById('show_relation_olways').checked) {
+ document.getElementById("canvas").style.display = 'none';
+ clearTimeout(timeoutID);
+ timeoutID = setTimeout(General_scroll_end, 500);
+ }
+ */
+ //if (timeoutID)
+ clearTimeout(timeoutID);
+ timeoutID = setTimeout(
+ function () {
+ document.getElementById('top_menu').style.left = document.body.scrollLeft + 'px';
+ document.getElementById('top_menu').style.top = document.body.scrollTop + 'px';
+ },
+ 200
+ );
+}
+
+/*
+function General_scroll_end()
+{
+ document.getElementById('layer_menu').style.left = document.body.scrollLeft;
+ document.getElementById('layer_menu').style.top = document.body.scrollTop + document.getElementById('top_menu').offsetHeight;
+ if (isIE) {
+ document.getElementById('layer_menu').style.left = document.body.scrollLeft;
+ document.getElementById('layer_menu').style.top = document.body.scrollTop + document.getElementById('top_menu').offsetHeight;
+ }
+ document.getElementById("canvas").style.display = 'block';
+}
+*/
+
+function Show_left_menu(id_this) // max/min all tables
+{
+ if (id_this.alt == "v") {
+ var pos = $("#top_menu").offset();
+ var height = $("#top_menu").height();
+ document.getElementById("layer_menu").style.top = '0px';
+ document.getElementById("layer_menu").style.display = 'block';
+ id_this.alt = ">";
+ id_this.src = pmaThemeImage + "pmd/uparrow2_m.png";
+ if (isIE) {
+ General_scroll();
+ }
+ } else {
+ document.getElementById("layer_menu").style.top = -1000 + 'px'; //fast scroll
+ document.getElementById("layer_menu").style.display = 'none';
+ id_this.alt = "v";
+ id_this.src = pmaThemeImage + "pmd/downarrow2_m.png";
+ }
+}
+//------------------------------------------------------------------------------
+function Top_menu_right(id_this)
+{
+ if (id_this.alt == ">") {
+ moveTopMenuToRight(id_this);
+ id_this.alt = "<";
+ id_this.src = pmaThemeImage + "pmd/2leftarrow_m.png";
+ } else {
+ document.getElementById('top_menu').style.paddingLeft = 0;
+ id_this.alt = ">";
+ id_this.src = pmaThemeImage + "pmd/2rightarrow_m.png";
+ }
+}
+
+function Top_menu_reposition(id_this)
+{
+ if (id_this.alt == "<") {
+ moveTopMenuToRight(id_this);
+ }
+}
+
+function moveTopMenuToRight(id_this)
+{
+ var top_menu_width = 10;
+ $('#top_menu').children().each(function () {
+ top_menu_width += $(this).outerWidth(true);
+ });
+ var offset = parseInt(document.getElementById('canvas_outer').offsetWidth - top_menu_width, 10);
+ document.getElementById('top_menu').style.paddingLeft = offset + 'px';
+}
+//------------------------------------------------------------------------------
+function Start_display_field()
+{
+ if (ON_relation) {
+ return;
+ }
+ if (!ON_display_field) {
+ ON_display_field = 1;
+ document.getElementById('pmd_hint').innerHTML = PMA_messages.strChangeDisplay;
+ document.getElementById('pmd_hint').style.display = 'block';
+ document.getElementById('display_field_button').className = 'M_butt_Selected_down';//'#FFEE99';gray #AAAAAA
+
+ if (isIE) { // correct for IE
+ document.getElementById('display_field_button').className = 'M_butt_Selected_down_IE';
+ }
+ } else {
+ document.getElementById('pmd_hint').innerHTML = "";
+ document.getElementById('pmd_hint').style.display = 'none';
+ document.getElementById('display_field_button').className = 'M_butt';
+ ON_display_field = 0;
+ }
+}
+//------------------------------------------------------------------------------
+var TargetColors = [];
+function getColorByTarget(target)
+{
+ var color = ''; //"rgba(0,100,150,1)";
+
+ for (var i in TargetColors) {
+ if (TargetColors[i][0] == target) {
+ color = TargetColors[i][1];
+ break;
+ }
+ }
+
+ if (color.length === 0) {
+ var i = TargetColors.length + 1;
+ var d = i % 6;
+ var j = (i - d) / 6;
+ j = j % 4;
+ j++;
+ var color_case = new Array(
+ new Array(1, 0, 0),
+ new Array(0, 1, 0),
+ new Array(0, 0, 1),
+ new Array(1, 1, 0),
+ new Array(1, 0, 1),
+ new Array(0, 1, 1)
+ );
+ var a = color_case[d][0];
+ var b = color_case[d][1];
+ var c = color_case[d][2];
+ var e = (1 - (j - 1) / 6);
+
+ var r = Math.round(a * 200 * e);
+ var g = Math.round(b * 200 * e);
+ var b = Math.round(c * 200 * e);
+ var color = "rgba(" + r + "," + g + "," + b + ",1)";
+
+ TargetColors.push(new Array(target, color));
+ }
+
+ return color;
+}
+
+function Click_option(id_this, column_name, table_name)
+{
+ var left = Glob_X - (document.getElementById(id_this).offsetWidth>>1);
+ document.getElementById(id_this).style.left = left + 'px';
+ // var top = Glob_Y - document.getElementById(id_this).offsetHeight - 10;
+ document.getElementById(id_this).style.top = (screen.height / 4) + 'px';
+ document.getElementById(id_this).style.display = 'block';
+ document.getElementById('option_col_name').innerHTML = '<strong>' + PMA_messages.strAddOption + '"' + column_name + '"</strong>';
+ col_name = column_name;
+ tab_name = table_name;
+}
+
+function Close_option()
+{
+ document.getElementById('pmd_optionse').style.display = 'none';
+}
+
+function Select_all(id_this, owner)
+{
+ var parent = document.form1;
+ downer = owner;
+ var i;
+ var k;
+ var tab = [];
+ for (i = 0; i < parent.elements.length; i++) {
+ if (parent.elements[i].type == "checkbox" && parent.elements[i].id.substring(0, (9 + id_this.length)) == 'select_' + id_this + '._') {
+ if (document.getElementById('select_all_' + id_this).checked === true) {
+ parent.elements[i].checked = true;
+ parent.elements[i].disabled = true;
+ var temp = '`' + id_this.substring(owner.length + 1) + '`.*';
+ } else {
+ parent.elements[i].checked = false;
+ parent.elements[i].disabled = false;
+ }
+ }
+ }
+ if (document.getElementById('select_all_' + id_this).checked === true) {
+ select_field.push('`' + id_this.substring(owner.length + 1) + '`.*');
+ tab = id_this.split(".");
+ from_array.push(tab[1]);
+ } else {
+ for (i = 0; i < select_field.length; i++) {
+ if (select_field[i] == ('`' + id_this.substring(owner.length + 1) + '`.*')) {
+ select_field.splice(i, 1);
+ }
+ }
+ for (k = 0; k < from_array.length; k++) {
+ if (from_array[k] == id_this) {
+ from_array.splice(k, 1);
+ break;
+ }
+ }
+ }
+ Re_load();
+}
+
+function Table_onover(id_this, val, buil)
+{
+ if (!val) {
+ document.getElementById("id_zag_" + id_this).className = "tab_zag_2";
+ if (buil) {
+ document.getElementById("id_zag_" + id_this + "_2").className = "tab_zag_2";
+ }
+ } else {
+ document.getElementById("id_zag_" + id_this).className = "tab_zag";
+ if (buil) {
+ document.getElementById("id_zag_" + id_this + "_2").className = "tab_zag";
+ }
+ }
+}
+
+/* This function stores selected column information in select_field[]
+ * In case column is checked it add else it deletes
+ *
+ */
+function store_column(id_this, owner, col)
+{
+ var i;
+ var k;
+ if (document.getElementById('select_' + owner + '.' + id_this + '._' + col).checked === true) {
+ select_field.push('`' + id_this + '`.`' + col + '`');
+ from_array.push(id_this);
+ } else {
+ for (i = 0; i < select_field.length; i++) {
+ if (select_field[i] == ('`' + id_this + '`.`' + col + '`')) {
+ select_field.splice(i, 1);
+ break;
+ }
+ }
+ for (k = 0; k < from_array.length; k++) {
+ if (from_array[k] == id_this) {
+ from_array.splice(k, 1);
+ break;
+ }
+ }
+ }
+}
+
+/**
+ * This function builds object and adds them to history_array
+ * first it does a few checks on each object, then makes an object(where,rename,groupby,aggregate,orderby)
+ * then a new history object is made and finally all these history objects are addded to history_array[]
+ *
+**/
+
+function add_object()
+{
+ var rel = document.getElementById('rel_opt');
+ var sum = 0;
+ var init = history_array.length;
+ if (rel.value != '--') {
+ if (document.getElementById('Query').value === "") {
+ document.getElementById('pmd_hint').innerHTML = "value/subQuery is empty";
+ document.getElementById('pmd_hint').style.display = 'block';
+ return;
+ }
+ var p = document.getElementById('Query');
+ var where_obj = new where(rel.value, p.value);//make where object
+ history_array.push(new history(col_name, where_obj, tab_name, h_tabs[downer + '.' + tab_name], "Where"));
+ sum = sum + 1;
+ rel.value = '--';
+ p.value = "";
+ }
+ if (document.getElementById('new_name').value !== "") {
+ var rename_obj = new rename(document.getElementById('new_name').value);//make Rename object
+ history_array.push(new history(col_name, rename_obj, tab_name, h_tabs[downer + '.' + tab_name], "Rename"));
+ sum = sum + 1;
+ document.getElementById('new_name').value = "";
+ }
+ if (document.getElementById('operator').value != '---') {
+ var aggregate_obj = new aggregate(document.getElementById('operator').value);
+ history_array.push(new history(col_name, aggregate_obj, tab_name, h_tabs[downer + '.' + tab_name], "Aggregate"));
+ sum = sum + 1;
+ document.getElementById('operator').value = '---';
+ //make aggregate operator
+ }
+ if (document.getElementById('groupby').checked === true) {
+ history_array.push(new history(col_name, 'GroupBy', tab_name, h_tabs[downer + '.' + tab_name], "GroupBy"));
+ sum = sum + 1;
+ document.getElementById('groupby').checked = false;
+ //make groupby
+ }
+ if (document.getElementById('h_rel_opt').value != '--') {
+ if (document.getElementById('having').value === "") {
+ document.getElementById('pmd_hint').innerHTML = "value/subQuery is empty";
+ document.getElementById('pmd_hint').style.display = 'block';
+ return;
+ }
+ var p = document.getElementById('having');
+ var where_obj = new having(
+ document.getElementById('h_rel_opt').value,
+ p.value,
+ document.getElementById('h_operator').value
+ );//make where object
+ history_array.push(new history(col_name, where_obj, tab_name, h_tabs[downer + '.' + tab_name], "Having"));
+ sum = sum + 1;
+ document.getElementById('h_rel_opt').value = '--';
+ document.getElementById('h_operator').value = '---';
+ p.value = ""; //make having
+ }
+ if (document.getElementById('orderby').checked === true) {
+ history_array.push(new history(col_name, 'OrderBy', tab_name, h_tabs[downer + '.' + tab_name], "OrderBy"));
+ sum = sum + 1;
+ document.getElementById('orderby').checked = false;
+ //make orderby
+ }
+ PMA_ajaxShowMessage($.sprintf(PMA_messages.strObjectsCreated, sum));
+ //output sum new objects created
+ var existingDiv = document.getElementById('ab');
+ existingDiv.innerHTML = display(init, history_array.length);
+ Close_option();
+ panel(0);
+}
diff --git a/js/querywindow.js b/js/querywindow.js
new file mode 100644
index 0000000000..d10069ce1b
--- /dev/null
+++ b/js/querywindow.js
@@ -0,0 +1,25 @@
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+function PMA_queryAutoCommit()
+{
+ var sqlqueryform = document.getElementById('sqlqueryform');
+ sqlqueryform.target = window.opener.frame_content.name;
+ sqlqueryform.submit();
+ return;
+}
+
+function PMA_querywindowCommit(tab)
+{
+ var $hiddenqueryform = $('#hiddenqueryform');
+ $hiddenqueryform.find("input[name='querydisplay_tab']").val(tab);
+ $hiddenqueryform.addClass('disableAjax').submit();
+ return false;
+}
+
+function PMA_querywindowSetFocus()
+{
+ $('#sqlquery').focus();
+}
+
+$(function () {
+ $('#topmenucontainer').css('padding', 0);
+});
diff --git a/js/replication.js b/js/replication.js
new file mode 100644
index 0000000000..888bd9ddf8
--- /dev/null
+++ b/js/replication.js
@@ -0,0 +1,72 @@
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * for server_replication.php
+ *
+ */
+
+var random_server_id = Math.floor(Math.random() * 10000000);
+var conf_prefix = "server-id=" + random_server_id + "\nlog_bin=mysql-bin\nlog_error=mysql-bin.err\n";
+
+function update_config()
+{
+ var conf_ignore = "binlog_ignore_db=";
+ var conf_do = "binlog_do_db=";
+ var database_list = '';
+
+ if ($('#db_select option:selected').size() === 0) {
+ $('#rep').text(conf_prefix);
+ } else if ($('#db_type option:selected').val() == 'all') {
+ $('#db_select option:selected').each(function () {
+ database_list += conf_ignore + $(this).val() + "\n";
+ });
+ $('#rep').text(conf_prefix + database_list);
+ } else {
+ $('#db_select option:selected').each(function () {
+ database_list += conf_do + $(this).val() + "\n";
+ });
+ $('#rep').text(conf_prefix + database_list);
+ }
+}
+
+/**
+ * Unbind all event handlers before tearing down a page
+ */
+AJAX.registerTeardown('replication.js', function () {
+ $('#db_type').unbind('change');
+ $('#db_select').unbind('change');
+ $('#master_status_href').unbind('click');
+ $('#master_slaves_href').unbind('click');
+ $('#slave_status_href').unbind('click');
+ $('#slave_control_href').unbind('click');
+ $('#slave_errormanagement_href').unbind('click');
+ $('#slave_synchronization_href').unbind('click');
+ $('#db_reset_href').unbind('click');
+});
+
+AJAX.registerOnload('replication.js', function () {
+ $('#rep').text(conf_prefix);
+ $('#db_type').change(update_config);
+ $('#db_select').change(update_config);
+
+ $('#master_status_href').click(function () {
+ $('#replication_master_section').toggle();
+ });
+ $('#master_slaves_href').click(function () {
+ $('#replication_slaves_section').toggle();
+ });
+ $('#slave_status_href').click(function () {
+ $('#replication_slave_section').toggle();
+ });
+ $('#slave_control_href').click(function () {
+ $('#slave_control_gui').toggle();
+ });
+ $('#slave_errormanagement_href').click(function () {
+ $('#slave_errormanagement_gui').toggle();
+ });
+ $('#slave_synchronization_href').click(function () {
+ $('#slave_synchronization_gui').toggle();
+ });
+ $('#db_reset_href').click(function () {
+ $('#db_select option:selected').prop('selected', false);
+ });
+});
diff --git a/js/rte.js b/js/rte.js
new file mode 100644
index 0000000000..c42bd97ef3
--- /dev/null
+++ b/js/rte.js
@@ -0,0 +1,930 @@
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * JavaScript functionality for Routines, Triggers and Events.
+ *
+ * @package PhpMyadmin
+ */
+/**
+ * @var RTE Contains all the JavaScript functionality
+ * for Routines, Triggers and Events
+ */
+var RTE = {
+ /**
+ * Construct for the object that provides the
+ * functionality for Routines, Triggers and Events
+ */
+ object: function (type) {
+ $.extend(this, RTE.COMMON);
+ switch (type) {
+ case 'routine':
+ $.extend(this, RTE.ROUTINE);
+ break;
+ case 'trigger':
+ // nothing extra yet for triggers
+ break;
+ case 'event':
+ $.extend(this, RTE.EVENT);
+ break;
+ default:
+ break;
+ }
+ },
+ /**
+ * @var string param_template Template for a row in the routine editor
+ */
+ param_template: ''
+};
+
+/**
+ * @var RTE.COMMON a JavaScript namespace containing the functionality
+ * for Routines, Triggers and Events
+ *
+ * This namespace is extended by the functionality required
+ * to handle a specific item (a routine, trigger or event)
+ * in the relevant javascript files in this folder
+ */
+RTE.COMMON = {
+ /**
+ * @var $ajaxDialog Query object containing the reference to the
+ * dialog that contains the editor
+ */
+ $ajaxDialog: null,
+ /**
+ * @var syntaxHiglighter Reference to the codemirror editor
+ */
+ syntaxHiglighter: null,
+ /**
+ * @var buttonOptions Object containing options for
+ * the jQueryUI dialog buttons
+ */
+ buttonOptions: {},
+ /**
+ * Validate editor form fields.
+ */
+ validate: function () {
+ /**
+ * @var $elm a jQuery object containing the reference
+ * to an element that is being validated
+ */
+ var $elm = null;
+ // Common validation. At the very least the name
+ // and the definition must be provided for an item
+ $elm = $('table.rte_table').last().find('input[name=item_name]');
+ if ($elm.val() === '') {
+ $elm.focus();
+ alert(PMA_messages.strFormEmpty);
+ return false;
+ }
+ $elm = $('table.rte_table').find('textarea[name=item_definition]');
+ if ($elm.val() === '') {
+ if (this.syntaxHiglighter !== null) {
+ this.syntaxHiglighter.focus();
+ }
+ else {
+ $('textarea[name=item_definition]').last().focus();
+ }
+ alert(PMA_messages.strFormEmpty);
+ return false;
+ }
+ // The validation has so far passed, so now
+ // we can validate item-specific fields.
+ return this.validateCustom();
+ }, // end validate()
+ /**
+ * Validate custom editor form fields.
+ * This function can be overridden by
+ * other files in this folder
+ */
+ validateCustom: function () {
+ return true;
+ }, // end validateCustom()
+ /**
+ * Execute some code after the ajax
+ * dialog for the editor is shown.
+ * This function can be overridden by
+ * other files in this folder
+ */
+ postDialogShow: function () {
+ // Nothing by default
+ }, // end postDialogShow()
+
+ exportDialog: function ($this) {
+ var $msg = PMA_ajaxShowMessage();
+ // Fire the ajax request straight away
+ $.get($this.attr('href'), {'ajax_request': true}, function (data) {
+ if (data.success === true) {
+ PMA_ajaxRemoveMessage($msg);
+ /**
+ * @var button_options Object containing options
+ * for jQueryUI dialog buttons
+ */
+ var button_options = {};
+ button_options[PMA_messages.strClose] = function () {
+ $(this).dialog("close").remove();
+ };
+ /**
+ * Display the dialog to the user
+ */
+ var $ajaxDialog = $('<div>' + data.message + '</div>').dialog({
+ width: 500,
+ buttons: button_options,
+ title: data.title
+ });
+ // Attach syntax highlighted editor to export dialog
+ /**
+ * @var $elm jQuery object containing the reference
+ * to the Export textarea.
+ */
+ var $elm = $ajaxDialog.find('textarea');
+ /**
+ * @var opts Options to pass to the codemirror editor
+ */
+ var opts = {
+ lineNumbers: true,
+ matchBrackets: true,
+ indentUnit: 4,
+ mode: "text/x-mysql",
+ lineWrapping: true
+ };
+ CodeMirror.fromTextArea($elm[0], opts);
+ } else {
+ PMA_ajaxShowMessage(data.error, false);
+ }
+ }); // end $.get()
+ }, // end exportDialog()
+ editorDialog: function (is_new, $this) {
+ var that = this;
+ /**
+ * @var $edit_row jQuery object containing the reference to
+ * the row of the the item being edited
+ * from the list of items
+ */
+ var $edit_row = null;
+ if ($this.hasClass('edit_anchor')) {
+ // Remeber the row of the item being edited for later,
+ // so that if the edit is successful, we can replace the
+ // row with info about the modified item.
+ $edit_row = $this.parents('tr');
+ }
+ /**
+ * @var $msg jQuery object containing the reference to
+ * the AJAX message shown to the user
+ */
+ var $msg = PMA_ajaxShowMessage();
+ $.get($this.attr('href'), {'ajax_request': true}, function (data) {
+ if (data.success === true) {
+ // We have successfully fetched the editor form
+ PMA_ajaxRemoveMessage($msg);
+ // Now define the function that is called when
+ // the user presses the "Go" button
+ that.buttonOptions[PMA_messages.strGo] = function () {
+ // Move the data from the codemirror editor back to the
+ // textarea, where it can be used in the form submission.
+ if (typeof CodeMirror != 'undefined') {
+ that.syntaxHiglighter.save();
+ }
+ // Validate editor and submit request, if passed.
+ if (that.validate()) {
+ /**
+ * @var data Form data to be sent in the AJAX request
+ */
+ var data = $('form.rte_form').last().serialize();
+ $msg = PMA_ajaxShowMessage(
+ PMA_messages.strProcessingRequest
+ );
+ var url = $('form.rte_form').last().attr('action');
+ $.post(url, data, function (data) {
+ if (data.success === true) {
+ // Item created successfully
+ PMA_ajaxRemoveMessage($msg);
+ PMA_slidingMessage(data.message);
+ that.$ajaxDialog.dialog('close');
+ // If we are in 'edit' mode, we must
+ // remove the reference to the old row.
+ if (mode === 'edit' && $edit_row !== null ) {
+ $edit_row.remove();
+ }
+ // Sometimes, like when moving a trigger from
+ // a table to another one, the new row should
+ // not be inserted into the list. In this case
+ // "data.insert" will be set to false.
+ if (data.insert) {
+ // Insert the new row at the correct
+ // location in the list of items
+ /**
+ * @var text Contains the name of an item from
+ * the list that is used in comparisons
+ * to find the correct location where
+ * to insert a new row.
+ */
+ var text = '';
+ /**
+ * @var inserted Whether a new item has been
+ * inserted in the list or not
+ */
+ var inserted = false;
+ $('table.data').find('tr').each(function () {
+ text = $(this)
+ .children('td')
+ .eq(0)
+ .find('strong')
+ .text()
+ .toUpperCase();
+ text = $.trim(text);
+ if (text !== '' && text > data.name) {
+ $(this).before(data.new_row);
+ inserted = true;
+ return false;
+ }
+ });
+ if (! inserted) {
+ // If we didn't manage to insert the row yet,
+ // it must belong at the end of the list,
+ // so we insert it there.
+ $('table.data').append(data.new_row);
+ }
+ // Fade-in the new row
+ $('tr.ajaxInsert')
+ .show('slow')
+ .removeClass('ajaxInsert');
+ } else if ($('table.data').find('tr').has('td').length === 0) {
+ // If we are not supposed to insert the new row,
+ // we will now check if the table is empty and
+ // needs to be hidden. This will be the case if
+ // we were editing the only item in the list,
+ // which we removed and will not be inserting
+ // something else in its place.
+ $('table.data').hide("slow", function () {
+ $('#nothing2display').show("slow");
+ });
+ }
+ // Now we have inserted the row at the correct
+ // position, but surely at least some row classes
+ // are wrong now. So we will itirate throught
+ // all rows and assign correct classes to them
+ /**
+ * @var ct Count of processed rows
+ */
+ var ct = 0;
+ /**
+ * @var rowclass Class to be attached to the row
+ * that is being processed
+ */
+ var rowclass = '';
+ $('table.data').find('tr').has('td').each(function () {
+ rowclass = (ct % 2 === 0) ? 'odd' : 'even';
+ $(this).removeClass().addClass(rowclass);
+ ct++;
+ });
+ // If this is the first item being added, remove
+ // the "No items" message and show the list.
+ if ($('table.data').find('tr').has('td').length > 0 &&
+ $('#nothing2display').is(':visible')
+ ) {
+ $('#nothing2display').hide("slow", function () {
+ $('table.data').show("slow");
+ });
+ }
+ PMA_reloadNavigation();
+ } else {
+ PMA_ajaxShowMessage(data.error, false);
+ }
+ }); // end $.post()
+ } // end "if (that.validate())"
+ }; // end of function that handles the submission of the Editor
+ that.buttonOptions[PMA_messages.strClose] = function () {
+ $(this).dialog("close");
+ };
+ /**
+ * Display the dialog to the user
+ */
+ that.$ajaxDialog = $('<div>' + data.message + '</div>').dialog({
+ width: 700,
+ minWidth: 500,
+ buttons: that.buttonOptions,
+ title: data.title,
+ modal: true,
+ close: function () {
+ $(this).remove();
+ }
+ });
+ that.$ajaxDialog.find('input[name=item_name]').focus();
+ that.$ajaxDialog.find('input.datefield, input.datetimefield').each(function () {
+ PMA_addDatepicker($(this).css('width', '95%'));
+ });
+ /**
+ * @var mode Used to remeber whether the editor is in
+ * "Edit" or "Add" mode
+ */
+ var mode = 'add';
+ if ($('input[name=editor_process_edit]').length > 0) {
+ mode = 'edit';
+ }
+ // Attach syntax highlighted editor to the definition
+ /**
+ * @var elm jQuery object containing the reference to
+ * the Definition textarea.
+ */
+ var $elm = $('textarea[name=item_definition]').last();
+ /**
+ * @var opts Options to pass to the codemirror editor
+ */
+ var opts = {
+ lineNumbers: true,
+ matchBrackets: true,
+ indentUnit: 4,
+ mode: "text/x-mysql",
+ lineWrapping: true
+ };
+ if (typeof CodeMirror != 'undefined') {
+ that.syntaxHiglighter = CodeMirror.fromTextArea($elm[0], opts);
+ }
+ // Execute item-specific code
+ that.postDialogShow(data);
+ } else {
+ PMA_ajaxShowMessage(data.error, false);
+ }
+ }); // end $.get()
+ },
+
+ dropDialog: function ($this) {
+ /**
+ * @var $curr_row Object containing reference to the current row
+ */
+ var $curr_row = $this.parents('tr');
+ /**
+ * @var question String containing the question to be asked for confirmation
+ */
+ var question = $('<div/>').text(
+ $curr_row.children('td').children('.drop_sql').html()
+ );
+ // We ask for confirmation first here, before submitting the ajax request
+ $this.PMA_confirm(question, $this.attr('href'), function (url) {
+ /**
+ * @var msg jQuery object containing the reference to
+ * the AJAX message shown to the user
+ */
+ var $msg = PMA_ajaxShowMessage(PMA_messages.strProcessingRequest);
+ $.get(url, {'is_js_confirmed': 1, 'ajax_request': true}, function (data) {
+ if (data.success === true) {
+ /**
+ * @var $table Object containing reference
+ * to the main list of elements
+ */
+ var $table = $curr_row.parent();
+ // Check how many rows will be left after we remove
+ // the one that the user has requested us to remove
+ if ($table.find('tr').length === 3) {
+ // If there are two rows left, it means that they are
+ // the header of the table and the rows that we are
+ // about to remove, so after the removal there will be
+ // nothing to show in the table, so we hide it.
+ $table.hide("slow", function () {
+ $(this).find('tr.even, tr.odd').remove();
+ $('#nothing2display').show("slow");
+ });
+ } else {
+ $curr_row.hide("slow", function () {
+ $(this).remove();
+ // Now we have removed the row from the list, but maybe
+ // some row classes are wrong now. So we will itirate
+ // throught all rows and assign correct classes to them.
+ /**
+ * @var ct Count of processed rows
+ */
+ var ct = 0;
+ /**
+ * @var rowclass Class to be attached to the row
+ * that is being processed
+ */
+ var rowclass = '';
+ $table.find('tr').has('td').each(function () {
+ rowclass = (ct % 2 === 0) ? 'odd' : 'even';
+ $(this).removeClass().addClass(rowclass);
+ ct++;
+ });
+ });
+ }
+ // Get rid of the "Loading" message
+ PMA_ajaxRemoveMessage($msg);
+ // Show the query that we just executed
+ PMA_slidingMessage(data.sql_query);
+ PMA_reloadNavigation();
+ } else {
+ PMA_ajaxShowMessage(data.error, false);
+ }
+ }); // end $.get()
+ }); // end $.PMA_confirm()
+ }
+}; // end RTE namespace
+
+/**
+ * @var RTE.EVENT JavaScript functionality for events
+ */
+RTE.EVENT = {
+ validateCustom: function () {
+ /**
+ * @var elm a jQuery object containing the reference
+ * to an element that is being validated
+ */
+ var $elm = null;
+ if (this.$ajaxDialog.find('select[name=item_type]').find(':selected').val() === 'RECURRING') {
+ // The interval field must not be empty for recurring events
+ $elm = this.$ajaxDialog.find('input[name=item_interval_value]');
+ if ($elm.val() === '') {
+ $elm.focus();
+ alert(PMA_messages.strFormEmpty);
+ return false;
+ }
+ } else {
+ // The execute_at field must not be empty for "once off" events
+ $elm = this.$ajaxDialog.find('input[name=item_execute_at]');
+ if ($elm.val() === '') {
+ $elm.focus();
+ alert(PMA_messages.strFormEmpty);
+ return false;
+ }
+ }
+ return true;
+ }
+};
+
+/**
+ * @var RTE.ROUTINE JavaScript functionality for routines
+ */
+RTE.ROUTINE = {
+ /**
+ * Overriding the postDialogShow() function defined in common.js
+ *
+ * @param data JSON-encoded data from the ajax request
+ */
+ postDialogShow: function (data) {
+ // Cache the template for a parameter table row
+ RTE.param_template = data.param_template;
+ var that = this;
+ // Make adjustments in the dialog to make it AJAX compatible
+ $('td.routine_param_remove').show();
+ $('input[name=routine_removeparameter]').remove();
+ $('input[name=routine_addparameter]').css('width', '100%');
+ // Enable/disable the 'options' dropdowns for parameters as necessary
+ $('table.routine_params_table').last().find('th[colspan=2]').attr('colspan', '1');
+ $('table.routine_params_table').last().find('tr').has('td').each(function () {
+ that.setOptionsForParameter(
+ $(this).find('select[name^=item_param_type]'),
+ $(this).find('input[name^=item_param_length]'),
+ $(this).find('select[name^=item_param_opts_text]'),
+ $(this).find('select[name^=item_param_opts_num]')
+ );
+ });
+ // Enable/disable the 'options' dropdowns for
+ // function return value as necessary
+ this.setOptionsForParameter(
+ $('table.rte_table').last().find('select[name=item_returntype]'),
+ $('table.rte_table').last().find('input[name=item_returnlength]'),
+ $('table.rte_table').last().find('select[name=item_returnopts_text]'),
+ $('table.rte_table').last().find('select[name=item_returnopts_num]')
+ );
+ },
+ /**
+ * Overriding the validateCustom() function defined in common.js
+ */
+ validateCustom: function () {
+ /**
+ * @var isSuccess Stores the outcome of the validation
+ */
+ var isSuccess = true;
+ /**
+ * @var inputname The value of the "name" attribute for
+ * the field that is being processed
+ */
+ var inputname = '';
+ this.$ajaxDialog.find('table.routine_params_table').last().find('tr').each(function () {
+ // Every parameter of a routine must have
+ // a non-empty direction, name and type
+ if (isSuccess) {
+ $(this).find(':input').each(function () {
+ inputname = $(this).attr('name');
+ if (inputname.substr(0, 14) === 'item_param_dir' ||
+ inputname.substr(0, 15) === 'item_param_name' ||
+ inputname.substr(0, 15) === 'item_param_type') {
+ if ($(this).val() === '') {
+ $(this).focus();
+ isSuccess = false;
+ return false;
+ }
+ }
+ });
+ } else {
+ return false;
+ }
+ });
+ if (! isSuccess) {
+ alert(PMA_messages.strFormEmpty);
+ return false;
+ }
+ this.$ajaxDialog.find('table.routine_params_table').last().find('tr').each(function () {
+ // SET, ENUM, VARCHAR and VARBINARY fields must have length/values
+ var $inputtyp = $(this).find('select[name^=item_param_type]');
+ var $inputlen = $(this).find('input[name^=item_param_length]');
+ if ($inputtyp.length && $inputlen.length) {
+ if (($inputtyp.val() === 'ENUM' || $inputtyp.val() === 'SET' || $inputtyp.val().substr(0, 3) === 'VAR') &&
+ $inputlen.val() === ''
+ ) {
+ $inputlen.focus();
+ isSuccess = false;
+ return false;
+ }
+ }
+ });
+ if (! isSuccess) {
+ alert(PMA_messages.strFormEmpty);
+ return false;
+ }
+ if (this.$ajaxDialog.find('select[name=item_type]').find(':selected').val() === 'FUNCTION') {
+ // The length/values of return variable for functions must
+ // be set, if the type is SET, ENUM, VARCHAR or VARBINARY.
+ var $returntyp = this.$ajaxDialog.find('select[name=item_returntype]');
+ var $returnlen = this.$ajaxDialog.find('input[name=item_returnlength]');
+ if (($returntyp.val() === 'ENUM' || $returntyp.val() === 'SET' || $returntyp.val().substr(0, 3) === 'VAR') &&
+ $returnlen.val() === ''
+ ) {
+ $returnlen.focus();
+ alert(PMA_messages.strFormEmpty);
+ return false;
+ }
+ }
+ if ($('select[name=item_type]').find(':selected').val() === 'FUNCTION') {
+ // A function must contain a RETURN statement in its definition
+ if (this.$ajaxDialog.find('table.rte_table').find('textarea[name=item_definition]').val().toUpperCase().indexOf('RETURN') < 0) {
+ this.syntaxHiglighter.focus();
+ alert(PMA_messages.MissingReturn);
+ return false;
+ }
+ }
+ return true;
+ },
+ /**
+ * Enable/disable the "options" dropdown and "length" input for
+ * parameters and the return variable in the routine editor
+ * as necessary.
+ *
+ * @param type a jQuery object containing the reference
+ * to the "Type" dropdown box
+ * @param len a jQuery object containing the reference
+ * to the "Length" input box
+ * @param text a jQuery object containing the reference
+ * to the dropdown box with options for
+ * parameters of text type
+ * @param num a jQuery object containing the reference
+ * to the dropdown box with options for
+ * parameters of numeric type
+ */
+ setOptionsForParameter: function ($type, $len, $text, $num) {
+ /**
+ * @var no_opts a jQuery object containing the reference
+ * to an element to be displayed when no
+ * options are available
+ */
+ var $no_opts = $text.parent().parent().find('.no_opts');
+ /**
+ * @var no_len a jQuery object containing the reference
+ * to an element to be displayed when no
+ * "length/values" field is available
+ */
+ var $no_len = $len.parent().parent().find('.no_len');
+
+ // Process for parameter options
+ switch ($type.val()) {
+ case 'TINYINT':
+ case 'SMALLINT':
+ case 'MEDIUMINT':
+ case 'INT':
+ case 'BIGINT':
+ case 'DECIMAL':
+ case 'FLOAT':
+ case 'DOUBLE':
+ case 'REAL':
+ $text.parent().hide();
+ $num.parent().show();
+ $no_opts.hide();
+ break;
+ case 'TINYTEXT':
+ case 'TEXT':
+ case 'MEDIUMTEXT':
+ case 'LONGTEXT':
+ case 'CHAR':
+ case 'VARCHAR':
+ case 'SET':
+ case 'ENUM':
+ $text.parent().show();
+ $num.parent().hide();
+ $no_opts.hide();
+ break;
+ default:
+ $text.parent().hide();
+ $num.parent().hide();
+ $no_opts.show();
+ break;
+ }
+ // Process for parameter length
+ switch ($type.val()) {
+ case 'DATE':
+ case 'DATETIME':
+ case 'TIME':
+ case 'TINYBLOB':
+ case 'TINYTEXT':
+ case 'BLOB':
+ case 'TEXT':
+ case 'MEDIUMBLOB':
+ case 'MEDIUMTEXT':
+ case 'LONGBLOB':
+ case 'LONGTEXT':
+ $text.closest('tr').find('a:first').hide();
+ $len.parent().hide();
+ $no_len.show();
+ break;
+ default:
+ if ($type.val() == 'ENUM' || $type.val() == 'SET') {
+ $text.closest('tr').find('a:first').show();
+ } else {
+ $text.closest('tr').find('a:first').hide();
+ }
+ $len.parent().show();
+ $no_len.hide();
+ break;
+ }
+ },
+ executeDialog: function ($this) {
+ var that = this;
+ /**
+ * @var msg jQuery object containing the reference to
+ * the AJAX message shown to the user
+ */
+ var $msg = PMA_ajaxShowMessage();
+ $.get($this.attr('href'), {'ajax_request': true}, function (data) {
+ if (data.success === true) {
+ PMA_ajaxRemoveMessage($msg);
+ // If 'data.dialog' is true we show a dialog with a form
+ // to get the input parameters for routine, otherwise
+ // we just show the results of the query
+ if (data.dialog) {
+ // Define the function that is called when
+ // the user presses the "Go" button
+ that.buttonOptions[PMA_messages.strGo] = function () {
+ /**
+ * @var data Form data to be sent in the AJAX request
+ */
+ var data = $('form.rte_form').last().serialize();
+ $msg = PMA_ajaxShowMessage(
+ PMA_messages.strProcessingRequest
+ );
+ $.post('db_routines.php', data, function (data) {
+ if (data.success === true) {
+ // Routine executed successfully
+ PMA_ajaxRemoveMessage($msg);
+ PMA_slidingMessage(data.message);
+ $ajaxDialog.dialog('close');
+ } else {
+ PMA_ajaxShowMessage(data.error, false);
+ }
+ });
+ };
+ that.buttonOptions[PMA_messages.strClose] = function () {
+ $(this).dialog("close");
+ };
+ /**
+ * Display the dialog to the user
+ */
+ $ajaxDialog = $('<div>' + data.message + '</div>').dialog({
+ width: 650,
+ buttons: that.buttonOptions,
+ title: data.title,
+ modal: true,
+ close: function () {
+ $(this).remove();
+ }
+ });
+ $ajaxDialog.find('input[name^=params]').first().focus();
+ /**
+ * Attach the datepickers to the relevant form fields
+ */
+ $ajaxDialog.find('input.datefield, input.datetimefield').each(function () {
+ PMA_addDatepicker($(this).css('width', '95%'));
+ });
+ /*
+ * Define the function if the user presses enter
+ */
+ $('form.rte_form').on('keyup', function (event) {
+ event.preventDefault();
+ if (event.keyCode === 13) {
+ /**
+ * @var data Form data to be sent in the AJAX request
+ */
+ var data = $(this).serialize();
+ $msg = PMA_ajaxShowMessage(
+ PMA_messages.strProcessingRequest
+ );
+ var url = $(this).attr('action');
+ $.post(url, data, function (data) {
+ if (data.success === true) {
+ // Routine executed successfully
+ PMA_ajaxRemoveMessage($msg);
+ PMA_slidingMessage(data.message);
+ $('form.rte_form').off('keyup');
+ $ajaxDialog.remove();
+ } else {
+ PMA_ajaxShowMessage(data.error, false);
+ }
+ });
+ }
+ });
+ } else {
+ // Routine executed successfully
+ PMA_slidingMessage(data.message);
+ }
+ } else {
+ PMA_ajaxShowMessage(data.error, false);
+ }
+ }); // end $.get()
+ }
+};
+
+/**
+ * Attach Ajax event handlers for the Routines, Triggers and Events editor
+ *
+ * FIXME: submit on ENTER keypress in dialog
+ */
+$(function () {
+ /**
+ * Attach Ajax event handlers for the Add/Edit functionality.
+ */
+ $('a.ajax.add_anchor, a.ajax.edit_anchor').live('click', function (event) {
+ event.preventDefault();
+ var type = $(this).attr('href').substr(0, $(this).attr('href').indexOf('?'));
+ if (type.indexOf('routine') != -1) {
+ type = 'routine';
+ } else if (type.indexOf('trigger') != -1) {
+ type = 'trigger';
+ } else if (type.indexOf('event') != -1) {
+ type = 'event';
+ } else {
+ type = '';
+ }
+ var dialog = new RTE.object(type);
+ dialog.editorDialog($(this).hasClass('add_anchor'), $(this));
+ }); // end $.live()
+
+ /**
+ * Attach Ajax event handlers for the Execute routine functionality
+ */
+ $('a.ajax.exec_anchor').live('click', function (event) {
+ event.preventDefault();
+ var dialog = new RTE.object('routine');
+ dialog.executeDialog($(this));
+ }); // end $.live()
+
+ /**
+ * Attach Ajax event handlers for Export of Routines, Triggers and Events
+ */
+ $('a.ajax.export_anchor').live('click', function (event) {
+ event.preventDefault();
+ var dialog = new RTE.object();
+ dialog.exportDialog($(this));
+ }); // end $.live()
+
+ /**
+ * Attach Ajax event handlers for Drop functionality
+ * of Routines, Triggers and Events.
+ */
+ $('a.ajax.drop_anchor').live('click', function (event) {
+ event.preventDefault();
+ var dialog = new RTE.object();
+ dialog.dropDialog($(this));
+ }); // end $.live()
+
+ /**
+ * Attach Ajax event handlers for the "Change event/routine type"
+ * functionality in the events editor, so that the correct
+ * rows are shown in the editor when changing the event type
+ */
+ $('select[name=item_type]').live('change', function () {
+ $(this)
+ .closest('table')
+ .find('tr.recurring_event_row, tr.onetime_event_row, tr.routine_return_row, td.routine_direction_cell')
+ .toggle();
+ }); // end $.live()
+
+ /**
+ * Attach Ajax event handlers for the "Change parameter type"
+ * functionality in the routines editor, so that the correct
+ * option/length fields, if any, are shown when changing
+ * a parameter type
+ */
+ $('select[name^=item_param_type]').live('change', function () {
+ /**
+ * @var row jQuery object containing the reference to
+ * a row in the routine parameters table
+ */
+ var $row = $(this).parents('tr').first();
+ var rte = new RTE.object('routine');
+ rte.setOptionsForParameter(
+ $row.find('select[name^=item_param_type]'),
+ $row.find('input[name^=item_param_length]'),
+ $row.find('select[name^=item_param_opts_text]'),
+ $row.find('select[name^=item_param_opts_num]')
+ );
+ }); // end $.live()
+
+ /**
+ * Attach Ajax event handlers for the "Change the type of return
+ * variable of function" functionality, so that the correct fields,
+ * if any, are shown when changing the function return type type
+ */
+ $('select[name=item_returntype]').live('change', function () {
+ var rte = new RTE.object('routine');
+ var $table = $(this).closest('table.rte_table');
+ rte.setOptionsForParameter(
+ $table.find('select[name=item_returntype]'),
+ $table.find('input[name=item_returnlength]'),
+ $table.find('select[name=item_returnopts_text]'),
+ $table.find('select[name=item_returnopts_num]')
+ );
+ }); // end $.live()
+
+ /**
+ * Attach Ajax event handlers for the "Add parameter to routine" functionality
+ */
+ $('input[name=routine_addparameter]').live('click', function (event) {
+ event.preventDefault();
+ /**
+ * @var routine_params_table jQuery object containing the reference
+ * to the routine parameters table
+ */
+ var $routine_params_table = $(this).closest('div.ui-dialog').find('.routine_params_table');
+ /**
+ * @var new_param_row A string containing the HTML code for the
+ * new row for the routine parameters table
+ */
+ var new_param_row = RTE.param_template.replace(/%s/g, $routine_params_table.find('tr').length - 1);
+ // Append the new row to the parameters table
+ $routine_params_table.append(new_param_row);
+ // Make sure that the row is correctly shown according to the type of routine
+ if ($(this).closest('div.ui-dialog').find('table.rte_table select[name=item_type]').val() === 'FUNCTION') {
+ $('tr.routine_return_row').show();
+ $('td.routine_direction_cell').hide();
+ }
+ /**
+ * @var newrow jQuery object containing the reference to the newly
+ * inserted row in the routine parameters table
+ */
+ var $newrow = $(this).closest('div.ui-dialog').find('table.routine_params_table').find('tr').has('td').last();
+ // Enable/disable the 'options' dropdowns for parameters as necessary
+ var rte = new RTE.object('routine');
+ rte.setOptionsForParameter(
+ $newrow.find('select[name^=item_param_type]'),
+ $newrow.find('input[name^=item_param_length]'),
+ $newrow.find('select[name^=item_param_opts_text]'),
+ $newrow.find('select[name^=item_param_opts_num]')
+ );
+ }); // end $.live()
+
+ /**
+ * Attach Ajax event handlers for the
+ * "Remove parameter from routine" functionality
+ */
+ $('a.routine_param_remove_anchor').live('click', function (event) {
+ event.preventDefault();
+ $(this).parent().parent().remove();
+ // After removing a parameter, the indices of the name attributes in
+ // the input fields lose the correct order and need to be reordered.
+ /**
+ * @var index Counter used for reindexing the input
+ * fields in the routine parameters table
+ */
+ var index = 0;
+ $(this).closest('div.ui-dialog').find('table.routine_params_table').find('tr').has('td').each(function () {
+ $(this).find(':input').each(function () {
+ /**
+ * @var inputname The value of the name attribute of
+ * the input field being reindexed
+ */
+ var inputname = $(this).attr('name');
+ if (inputname.substr(0, 14) === 'item_param_dir') {
+ $(this).attr('name', inputname.substr(0, 14) + '[' + index + ']');
+ } else if (inputname.substr(0, 15) === 'item_param_name') {
+ $(this).attr('name', inputname.substr(0, 15) + '[' + index + ']');
+ } else if (inputname.substr(0, 15) === 'item_param_type') {
+ $(this).attr('name', inputname.substr(0, 15) + '[' + index + ']');
+ } else if (inputname.substr(0, 17) === 'item_param_length') {
+ $(this).attr('name', inputname.substr(0, 17) + '[' + index + ']');
+ $(this).attr('id', 'item_param_length_' + index);
+ } else if (inputname.substr(0, 20) === 'item_param_opts_text') {
+ $(this).attr('name', inputname.substr(0, 20) + '[' + index + ']');
+ } else if (inputname.substr(0, 19) === 'item_param_opts_num') {
+ $(this).attr('name', inputname.substr(0, 19) + '[' + index + ']');
+ }
+ });
+ index++;
+ });
+ }); // end $.live()
+}); // end of $()
diff --git a/js/server_databases.js b/js/server_databases.js
new file mode 100644
index 0000000000..a5be29fb89
--- /dev/null
+++ b/js/server_databases.js
@@ -0,0 +1,130 @@
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * @fileoverview functions used on the server databases list page
+ * @name Server Databases
+ *
+ * @requires jQuery
+ * @requires jQueryUI
+ * @required js/functions.js
+ */
+
+/**
+ * Unbind all event handlers before tearing down a page
+ */
+AJAX.registerTeardown('server_databases.js', function () {
+ $("#dbStatsForm").die('submit');
+ $('#create_database_form.ajax').die('submit');
+});
+
+/**
+ * AJAX scripts for server_databases.php
+ *
+ * Actions ajaxified here:
+ * Drop Databases
+ *
+ */
+AJAX.registerOnload('server_databases.js', function () {
+ /**
+ * Attach Event Handler for 'Drop Databases'
+ */
+ $("#dbStatsForm").live('submit', function (event) {
+ event.preventDefault();
+
+ var $form = $(this);
+
+ /**
+ * @var selected_dbs Array containing the names of the checked databases
+ */
+ var selected_dbs = [];
+ // loop over all checked checkboxes, except the .checkall_box checkbox
+ $form.find('input:checkbox:checked:not(.checkall_box)').each(function () {
+ $(this).closest('tr').addClass('removeMe');
+ selected_dbs[selected_dbs.length] = 'DROP DATABASE `' + escapeHtml($(this).val()) + '`;';
+ });
+ if (! selected_dbs.length) {
+ PMA_ajaxShowMessage(
+ $('<div class="notice" />').text(
+ PMA_messages.strNoDatabasesSelected
+ ),
+ 2000
+ );
+ return;
+ }
+ /**
+ * @var question String containing the question to be asked for confirmation
+ */
+ var question = PMA_messages.strDropDatabaseStrongWarning + ' ' +
+ $.sprintf(PMA_messages.strDoYouReally, selected_dbs.join('<br />'));
+
+ $(this).PMA_confirm(
+ question,
+ $form.prop('action') + '?' + $(this).serialize() +
+ '&drop_selected_dbs=1&is_js_confirmed=1&ajax_request=true',
+ function (url) {
+ PMA_ajaxShowMessage(PMA_messages.strProcessingRequest, false);
+
+ $.post(url, function (data) {
+ if (data.success === true) {
+ PMA_ajaxShowMessage(data.message);
+
+ var $rowsToRemove = $form.find('tr.removeMe');
+ var $databasesCount = $('#databases_count');
+ var newCount = parseInt($databasesCount.text(), 10) - $rowsToRemove.length;
+ $databasesCount.text(newCount);
+
+ $rowsToRemove.remove();
+ $form.find('tbody').PMA_sort_table('.name');
+ if ($form.find('tbody').find('tr').length == 0) {
+ // user just dropped the last db on this page
+ PMA_commonActions.refreshMain();
+ }
+ PMA_reloadNavigation();
+ } else {
+ $form.find('tr.removeMe').removeClass('removeMe');
+ PMA_ajaxShowMessage(data.error, false);
+ }
+ }); // end $.post()
+ }
+ ); // end $.PMA_confirm()
+ }); //end of Drop Database action
+
+ /**
+ * Attach Ajax event handlers for 'Create Database'.
+ */
+ $('#create_database_form.ajax').live('submit', function (event) {
+ event.preventDefault();
+
+ var $form = $(this);
+
+ // TODO Remove this section when all browsers support HTML5 "required" property
+ var newDbNameInput = $form.find('input[name=new_db]');
+ if (newDbNameInput.val() === '') {
+ newDbNameInput.focus();
+ alert(PMA_messages.strFormEmpty);
+ return;
+ }
+ // end remove
+
+ PMA_ajaxShowMessage(PMA_messages.strProcessingRequest);
+ PMA_prepareForAjaxRequest($form);
+
+ $.post($form.attr('action'), $form.serialize(), function (data) {
+ if (data.success === true) {
+ PMA_ajaxShowMessage(data.message);
+
+ //Append database's row to table
+ $("#tabledatabases")
+ .find('tbody')
+ .append(data.new_db_string)
+ .PMA_sort_table('.name');
+
+ var $databases_count_object = $('#databases_count');
+ var databases_count = parseInt($databases_count_object.text(), 10) + 1;
+ $databases_count_object.text(databases_count);
+ PMA_reloadNavigation();
+ } else {
+ PMA_ajaxShowMessage(data.error, false);
+ }
+ }); // end $.post()
+ }); // end $().live()
+}); // end $()
diff --git a/js/server_plugins.js b/js/server_plugins.js
new file mode 100644
index 0000000000..ace009372e
--- /dev/null
+++ b/js/server_plugins.js
@@ -0,0 +1,30 @@
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Functions used in server plugins pages
+ */
+var pma_theme_image; // filled in server_plugins.php
+
+AJAX.registerOnload('server_plugins.js', function () {
+ // Add tabs
+ $('#pluginsTabs').tabs({
+ // Tab persistence
+ cookie: { name: 'pma_serverStatusTabs', expires: 1 },
+ show: function (event, ui) {
+ // Fixes line break in the menu bar when the page overflows and scrollbar appears
+ $('#topmenu').menuResizer('resize');
+ // 'Plugins' tab is too high due to hiding of 'Modules' by negative left position,
+ // hide tabs by changing display to fix it
+ $(ui.panel).closest('.ui-tabs').find('> div').not(ui.panel).css('display', 'none');
+ $(ui.panel).css('display', 'block');
+ }
+ });
+
+ // Make columns sortable, but only for tables with more than 1 data row
+ var $tables = $('#plugins_plugins table:has(tbody tr + tr)');
+ $tables.tablesorter({
+ sortList: [[0, 0]],
+ widgets: ['zebra']
+ });
+ $tables.find('thead th')
+ .append('<img class="sortableIcon" src="' + pma_theme_image + 'cleardot.gif" alt="">');
+});
diff --git a/js/server_privileges.js b/js/server_privileges.js
new file mode 100644
index 0000000000..cc7b92b905
--- /dev/null
+++ b/js/server_privileges.js
@@ -0,0 +1,687 @@
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * @fileoverview functions used in server privilege pages
+ * @name Server Privileges
+ *
+ * @requires jQuery
+ * @requires jQueryUI
+ * @requires js/functions.js
+ *
+ */
+
+/**
+ * Validates the "add a user" form
+ *
+ * @return boolean whether the form is validated or not
+ */
+function checkAddUser(the_form)
+{
+ if (the_form.elements.pred_hostname.value == 'userdefined' && the_form.elements.hostname.value === '') {
+ alert(PMA_messages.strHostEmpty);
+ the_form.elements.hostname.focus();
+ return false;
+ }
+
+ if (the_form.elements.pred_username.value == 'userdefined' && the_form.elements.username.value === '') {
+ alert(PMA_messages.strUserEmpty);
+ the_form.elements.username.focus();
+ return false;
+ }
+
+ return PMA_checkPassword($(the_form));
+} // end of the 'checkAddUser()' function
+
+/**
+ * When a new user is created and retrieved over Ajax, append the user's row to
+ * the user's table
+ *
+ * @param new_user_string the html for the new user's row
+ * @param new_user_initial the first alphabet of the user's name
+ * @param new_user_initial_string html to replace the initial for pagination
+ */
+function appendNewUser(new_user_string, new_user_initial, new_user_initial_string)
+{
+ //Append the newly retrieved user to the table now
+
+ //Calculate the index for the new row
+ var $curr_last_row = $("#usersForm").find('tbody').find('tr:last');
+ var $curr_first_row = $("#usersForm").find('tbody').find('tr:first');
+ var first_row_initial = $curr_first_row.find('label').html().substr(0, 1).toUpperCase();
+ var curr_shown_initial = $curr_last_row.find('label').html().substr(0, 1).toUpperCase();
+ var curr_last_row_index_string = $curr_last_row.find('input:checkbox').attr('id').match(/\d+/)[0];
+ var curr_last_row_index = parseFloat(curr_last_row_index_string);
+ var new_last_row_index = curr_last_row_index + 1;
+ var new_last_row_id = 'checkbox_sel_users_' + new_last_row_index;
+ var is_show_all = (first_row_initial != curr_shown_initial) ? true : false;
+
+ //Append to the table and set the id/names correctly
+ if ((curr_shown_initial == new_user_initial) || is_show_all) {
+ $(new_user_string)
+ .insertAfter($curr_last_row)
+ .find('input:checkbox')
+ .attr('id', new_last_row_id)
+ .val(function () {
+ //the insert messes up the &amp;27; part. let's fix it
+ return $(this).val().replace(/&/, '&amp;');
+ })
+ .end()
+ .find('label')
+ .attr('for', new_last_row_id)
+ .end();
+ }
+
+ //Let us sort the table alphabetically
+ $("#usersForm").find('tbody').PMA_sort_table('label');
+
+ $("#initials_table").find('td:contains(' + new_user_initial + ')')
+ .html(new_user_initial_string);
+
+ //update the checkall checkbox
+ $(checkboxes_sel).trigger("change");
+}
+
+function addUser($form)
+{
+ if (! checkAddUser($form.get(0))) {
+ return false;
+ }
+
+ //We also need to post the value of the submit button in order to get this to work correctly
+ $.post($form.attr('action'), $form.serialize() + "&adduser_submit=" + $("input[name=adduser_submit]").val(), function (data) {
+ if (data.success === true) {
+ // Refresh navigation, if we created a database with the name
+ // that is the same as the username of the new user
+ if ($('#add_user_dialog #createdb-1:checked').length) {
+ PMA_reloadNavigation();
+ }
+
+ $('#page_content').show();
+ $("#add_user_dialog").remove();
+
+ PMA_ajaxShowMessage(data.message);
+ $("#result_query").remove();
+ $('#page_content').prepend(data.sql_query);
+ PMA_highlightSQL($('#page_content'));
+ $("#result_query").css({
+ 'margin-top' : '0.5em'
+ });
+
+ //Remove the empty notice div generated due to a NULL query passed to PMA_getMessage()
+ var $notice_class = $("#result_query").find('.notice');
+ if ($notice_class.text() === '') {
+ $notice_class.remove();
+ }
+ if ($('#fieldset_add_user a.ajax').attr('name') == 'db_specific') {
+
+ /*process the fieldset_add_user attribute and get the val of privileges*/
+ var url = $('#fieldset_add_user a.ajax').attr('rel');
+
+ if (url.substring(url.length - 23, url.length) == "&goto=db_operations.php") {
+ url = url.substring(0, url.length - 23);
+ }
+ url = url + "&ajax_request=true&db_specific=true";
+
+ /* post request for get the updated userForm table */
+ $.post($form.attr('action'), url, function (priv_data) {
+
+ /*Remove the old userForm table*/
+ if ($('#userFormDiv').length !== 0) {
+ $('#userFormDiv').remove();
+ } else {
+ $("#usersForm").remove();
+ }
+ if (priv_data.success === true) {
+ $('<div id="userFormDiv"></div>')
+ .html(priv_data.user_form)
+ .insertAfter('#result_query');
+ } else {
+ PMA_ajaxShowMessage(PMA_messages.strErrorProcessingRequest + " : " + priv_data.error, false);
+ }
+ });
+ } else {
+ appendNewUser(data.new_user_string, data.new_user_initial, data.new_user_initial_string);
+ }
+ } else {
+ PMA_ajaxShowMessage(data.error, false);
+ }
+ });
+}
+
+/**
+ * AJAX scripts for server_privileges page.
+ *
+ * Actions ajaxified here:
+ * Add user
+ * Revoke a user
+ * Edit privileges
+ * Export privileges
+ * Paginate table of users
+ * Flush privileges
+ *
+ * @memberOf jQuery
+ * @name document.ready
+ */
+
+
+/**
+ * Unbind all event handlers before tearing down a page
+ */
+AJAX.registerTeardown('server_privileges.js', function () {
+ $("#fieldset_add_user_login input[name='username']").die("focusout");
+ $("#fieldset_add_user a.ajax").die("click");
+ $('form[name=usersForm]').unbind('submit');
+ $("#fieldset_delete_user_footer #buttonGo.ajax").die('click');
+ $("a.edit_user_anchor.ajax").die('click');
+ $("a.edit_user_group_anchor.ajax").die('click');
+ $("#edit_user_dialog").find("form.ajax").die('submit');
+ $("button.mult_submit[value=export]").die('click');
+ $("a.export_user_anchor.ajax").die('click');
+ $("#initials_table").find("a.ajax").die('click');
+ $('#checkbox_drop_users_db').unbind('click');
+ $(".checkall_box").die("click");
+});
+
+AJAX.registerOnload('server_privileges.js', function () {
+ /**
+ * Display a warning if there is already a user by the name entered as the username.
+ */
+ $("#fieldset_add_user_login input[name='username']").live("focusout", function () {
+ var username = $(this).val();
+ var $warning = $("#user_exists_warning");
+ if ($("#select_pred_username").val() == 'userdefined' && username !== '') {
+ var href = $("form[name='usersForm']").attr('action');
+ var params = {
+ 'ajax_request' : true,
+ 'token' : PMA_commonParams.get('token'),
+ 'validate_username' : true,
+ 'username' : username
+ };
+ $.get(href, params, function (data) {
+ if (data.user_exists) {
+ $warning.show();
+ } else {
+ $warning.hide();
+ }
+ });
+ } else {
+ $warning.hide();
+ }
+ });
+ /**
+ * AJAX event handler for 'Add a New User'
+ *
+ * @see PMA_ajaxShowMessage()
+ * @see appendNewUser()
+ * @memberOf jQuery
+ * @name add_user_click
+ *
+ */
+ $("#fieldset_add_user a.ajax").live("click", function (event) {
+ /** @lends jQuery */
+ event.preventDefault();
+ var $msgbox = PMA_ajaxShowMessage();
+
+ $.get($(this).attr("href"), {'ajax_request': true}, function (data) {
+ if (data.success === true) {
+ $('#page_content').hide();
+ var $div = $('#add_user_dialog');
+ if ($div.length === 0) {
+ $div = $('<div id="add_user_dialog" style="margin: 0.5em;"></div>')
+ .insertBefore('#page_content');
+ } else {
+ $div.empty();
+ }
+ $div.html(data.message)
+ .find("form[name=usersForm]")
+ .append('<input type="hidden" name="ajax_request" value="true" />')
+ .end();
+ PMA_highlightSQL($div);
+ displayPasswordGenerateButton();
+ PMA_showHints($div);
+ PMA_ajaxRemoveMessage($msgbox);
+ $div.find("input.autofocus").focus();
+
+ $div.find('form[name=usersForm]').bind('submit', function (event) {
+ event.preventDefault();
+ addUser($(this));
+ });
+ } else {
+ PMA_ajaxShowMessage(data.error, false);
+ }
+ }); // end $.get()
+
+ });//end of Add New User AJAX event handler
+
+ /**
+ * AJAX handler for 'Revoke User'
+ *
+ * @see PMA_ajaxShowMessage()
+ * @memberOf jQuery
+ * @name revoke_user_click
+ */
+ $("#fieldset_delete_user_footer #buttonGo.ajax").live('click', function (event) {
+ event.preventDefault();
+
+ PMA_ajaxShowMessage(PMA_messages.strRemovingSelectedUsers);
+
+ var $form = $("#usersForm");
+
+ $.post($form.attr('action'), $form.serialize() + "&delete=" + $(this).val() + "&ajax_request=true", function (data) {
+ if (data.success === true) {
+ PMA_ajaxShowMessage(data.message);
+ // Refresh navigation, if we droppped some databases with the name
+ // that is the same as the username of the deleted user
+ if ($('#checkbox_drop_users_db:checked').length) {
+ PMA_reloadNavigation();
+ }
+ //Remove the revoked user from the users list
+ $form.find("input:checkbox:checked").parents("tr").slideUp("medium", function () {
+ var this_user_initial = $(this).find('input:checkbox').val().charAt(0).toUpperCase();
+ $(this).remove();
+
+ //If this is the last user with this_user_initial, remove the link from #initials_table
+ if ($("#tableuserrights").find('input:checkbox[value^="' + this_user_initial + '"]').length === 0) {
+ $("#initials_table").find('td > a:contains(' + this_user_initial + ')').parent('td').html(this_user_initial);
+ }
+
+ //Re-check the classes of each row
+ $form
+ .find('tbody').find('tr:odd')
+ .removeClass('even').addClass('odd')
+ .end()
+ .find('tr:even')
+ .removeClass('odd').addClass('even');
+
+ //update the checkall checkbox
+ $(checkboxes_sel).trigger("change");
+ });
+ } else {
+ PMA_ajaxShowMessage(data.error, false);
+ }
+ }); // end $.post()
+ }); // end Revoke User
+
+ $("a.edit_user_group_anchor.ajax").live('click', function (event) {
+ event.preventDefault();
+ $(this).parents('tr').addClass('current_row');
+ var token = $(this).parents('form').find('input[name="token"]').val();
+ var $msg = PMA_ajaxShowMessage();
+ $.get(
+ $(this).attr('href'),
+ {
+ 'ajax_request': true,
+ 'edit_user_group_dialog': true,
+ 'token': token
+ },
+ function (data) {
+ if (data.success === true) {
+ PMA_ajaxRemoveMessage($msg);
+ var buttonOptions = {};
+ buttonOptions[PMA_messages.strGo] = function () {
+ var usrGroup = $('#changeUserGroupDialog')
+ .find('select[name="userGroup"]')
+ .val();
+ var $message = PMA_ajaxShowMessage();
+ $.get(
+ 'server_privileges.php',
+ $('#changeUserGroupDialog').find('form').serialize() + '&ajax_request=1',
+ function (data) {
+ PMA_ajaxRemoveMessage($message);
+ if (data.success === true) {
+ $("#usersForm")
+ .find('.current_row')
+ .removeClass('current_row')
+ .find('.usrGroup')
+ .text(usrGroup);
+ } else {
+ PMA_ajaxShowMessage(data.error, false);
+ $("#usersForm")
+ .find('.current_row')
+ .removeClass('current_row');
+ }
+ }
+ );
+ $(this).dialog("close");
+ };
+ buttonOptions[PMA_messages.strClose] = function () {
+ $(this).dialog("close");
+ };
+ var $dialog = $('<div/>')
+ .attr('id', 'changeUserGroupDialog')
+ .append(data.message)
+ .dialog({
+ width: 500,
+ minWidth: 300,
+ modal: true,
+ buttons: buttonOptions,
+ title: $('legend', $(data.message)).text(),
+ close: function () {
+ $(this).remove();
+ }
+ });
+ $dialog.find('legend').remove();
+ } else {
+ PMA_ajaxShowMessage(data.error, false);
+ $("#usersForm")
+ .find('.current_row')
+ .removeClass('current_row');
+ }
+ }
+ );
+ });
+
+ /**
+ * AJAX handler for 'Edit User'
+ *
+ * @see PMA_ajaxShowMessage()
+ *
+ */
+
+ /**
+ * Step 1: Load Edit User Dialog
+ * @memberOf jQuery
+ * @name edit_user_click
+ */
+ $("a.edit_user_anchor.ajax").live('click', function (event) {
+ /** @lends jQuery */
+ event.preventDefault();
+
+ var $msgbox = PMA_ajaxShowMessage();
+
+ $(this).parents('tr').addClass('current_row');
+
+ var token = $(this).parents('form').find('input[name="token"]').val();
+ $.get(
+ $(this).attr('href'),
+ {
+ 'ajax_request': true,
+ 'edit_user_dialog': true,
+ 'token': token
+ },
+ function (data) {
+ if (data.success === true) {
+ $('#page_content').hide();
+ var $div = $('#edit_user_dialog');
+ if ($div.length === 0) {
+ $div = $('<div id="edit_user_dialog" style="margin: 0.5em;"></div>')
+ .insertBefore('#page_content');
+ } else {
+ $div.empty();
+ }
+ $div.html(data.message);
+ PMA_highlightSQL($div);
+ $div = $('#edit_user_dialog');
+ displayPasswordGenerateButton();
+ $(checkboxes_sel).trigger("change");
+ PMA_ajaxRemoveMessage($msgbox);
+ PMA_showHints($div);
+ } else {
+ PMA_ajaxShowMessage(data.error, false);
+ }
+ }
+ ); // end $.get()
+ });
+
+ /**
+ * Step 2: Submit the Edit User Dialog
+ *
+ * @see PMA_ajaxShowMessage()
+ * @memberOf jQuery
+ * @name edit_user_submit
+ */
+ $("#edit_user_dialog").find("form.ajax").live('submit', function (event) {
+ /** @lends jQuery */
+ event.preventDefault();
+
+ var $t = $(this);
+
+ if ($t.is('.copyUserForm') && ! PMA_checkPassword($t)) {
+ return false;
+ }
+
+ PMA_ajaxShowMessage(PMA_messages.strProcessingRequest);
+
+ $t.append('<input type="hidden" name="ajax_request" value="true" />');
+
+ /**
+ * @var curr_submit_name name of the current button being submitted
+ */
+ var curr_submit_name = $t.find('.tblFooters').find('input:submit').attr('name');
+
+ /**
+ * @var curr_submit_value value of the current button being submitted
+ */
+ var curr_submit_value = $t.find('.tblFooters').find('input:submit').val();
+
+ // If any option other than 'keep the old one'(option 4) is chosen, we need to remove
+ // the old one from the table.
+ var $row_to_remove;
+ if (curr_submit_name == 'change_copy' &&
+ $('input[name=mode]:checked', '#fieldset_mode').val() != '4'
+ ) {
+ var old_username = $t.find('input[name="old_username"]').val();
+ var old_hostname = $t.find('input[name="old_hostname"]').val();
+ $('#usersForm tbody tr').each(function () {
+ var $tr = $(this);
+ if ($tr.find('td:nth-child(2) label').text() == old_username &&
+ $tr.find('td:nth-child(3)').text() == old_hostname
+ ) {
+ $row_to_remove = $tr;
+ return false;
+ }
+ });
+ }
+
+ $.post($t.attr('action'), $t.serialize() + '&' + curr_submit_name + '=' + curr_submit_value, function (data) {
+ if (data.success === true) {
+ $('#page_content').show();
+ $("#edit_user_dialog").remove();
+
+ PMA_ajaxShowMessage(data.message);
+
+ if (data.sql_query) {
+ $("#result_query").remove();
+ $('#page_content').prepend(data.sql_query);
+ PMA_highlightSQL($('#page_content'));
+ $("#result_query").css({
+ 'margin-top' : '0.5em'
+ });
+ var $notice_class = $("#result_query").find('.notice');
+ if ($notice_class.text() === '') {
+ $notice_class.remove();
+ }
+ } //Show SQL Query that was executed
+
+ // Remove the old row if the old user is deleted
+ if (typeof $row_to_remove != 'undefined' && $row_to_remove !== null) {
+ $row_to_remove.remove();
+ }
+
+ //Append new user if necessary
+ if (data.new_user_string) {
+ appendNewUser(data.new_user_string, data.new_user_initial, data.new_user_initial_string);
+ }
+
+ //Check if we are on the page of the db-specific privileges
+ var db_priv_page = !!($('#dbspecificuserrights').length); // the "!!" part is merely there to ensure a value of type boolean
+ // we always need to reload on the db-specific privilege page
+ // and on the global page when adjusting global privileges,
+ // but not on the global page when adjusting db-specific privileges.
+ var reload_privs = false;
+ if (data.db_specific_privs === false || (db_priv_page == data.db_specific_privs)) {
+ reload_privs = true;
+ }
+ if (data.db_wildcard_privs) {
+ reload_privs = false;
+ }
+
+ //Change privileges, if they were edited and need to be reloaded
+ if (data.new_privileges && reload_privs) {
+ $("#usersForm")
+ .find('.current_row')
+ .find('code')
+ .html(data.new_privileges);
+ }
+
+ $("#usersForm")
+ .find('.current_row')
+ .removeClass('current_row');
+ } else {
+ PMA_ajaxShowMessage(data.error, false);
+ }
+ });
+ });
+ //end Edit user
+
+ /**
+ * AJAX handler for 'Export Privileges'
+ *
+ * @see PMA_ajaxShowMessage()
+ * @memberOf jQuery
+ * @name export_user_click
+ */
+ $("button.mult_submit[value=export]").live('click', function (event) {
+ event.preventDefault();
+ // can't export if no users checked
+ if ($(this.form).find("input:checked").length === 0) {
+ return;
+ }
+ var $msgbox = PMA_ajaxShowMessage();
+ var button_options = {};
+ button_options[PMA_messages.strClose] = function () {
+ $(this).dialog("close");
+ };
+ $.post(
+ $(this.form).prop('action'),
+ $(this.form).serialize() + '&submit_mult=export&ajax_request=true',
+ function (data) {
+ if (data.success === true) {
+ var $ajaxDialog = $('<div />')
+ .append(data.message)
+ .dialog({
+ title: data.title,
+ width: 500,
+ buttons: button_options,
+ close: function () {
+ $(this).remove();
+ }
+ });
+ PMA_ajaxRemoveMessage($msgbox);
+ // Attach syntax highlited editor to export dialog
+ if (typeof CodeMirror != 'undefined') {
+ CodeMirror.fromTextArea(
+ $ajaxDialog.find('textarea')[0],
+ {
+ lineNumbers: true,
+ matchBrackets: true,
+ indentUnit: 4,
+ mode: "text/x-mysql",
+ lineWrapping: true
+ }
+ );
+ }
+ } else {
+ PMA_ajaxShowMessage(data.error, false);
+ }
+ }
+ ); //end $.post
+ });
+ // if exporting non-ajax, highlight anyways
+ if ($("textarea.export").length > 0 && typeof CodeMirror != 'undefined') {
+ CodeMirror.fromTextArea(
+ $('textarea.export')[0],
+ {
+ lineNumbers: true,
+ matchBrackets: true,
+ indentUnit: 4,
+ mode: "text/x-mysql",
+ lineWrapping: true
+ }
+ );
+ }
+
+ $("a.export_user_anchor.ajax").live('click', function (event) {
+ event.preventDefault();
+ var $msgbox = PMA_ajaxShowMessage();
+ /**
+ * @var button_options Object containing options for jQueryUI dialog buttons
+ */
+ var button_options = {};
+ button_options[PMA_messages.strClose] = function () {
+ $(this).dialog("close");
+ };
+ $.get($(this).attr('href'), {'ajax_request': true}, function (data) {
+ if (data.success === true) {
+ var $ajaxDialog = $('<div />')
+ .append(data.message)
+ .dialog({
+ title: data.title,
+ width: 500,
+ buttons: button_options,
+ close: function () {
+ $(this).remove();
+ }
+ });
+ PMA_ajaxRemoveMessage($msgbox);
+ // Attach syntax highlited editor to export dialog
+ if (typeof CodeMirror != 'undefined') {
+ CodeMirror.fromTextArea(
+ $ajaxDialog.find('textarea')[0],
+ {
+ lineNumbers: true,
+ matchBrackets: true,
+ indentUnit: 4,
+ mode: "text/x-mysql",
+ lineWrapping: true
+ }
+ );
+ }
+ } else {
+ PMA_ajaxShowMessage(data.error, false);
+ }
+ }); //end $.get
+ }); //end export privileges
+
+ /**
+ * AJAX handler to Paginate the Users Table
+ *
+ * @see PMA_ajaxShowMessage()
+ * @name paginate_users_table_click
+ * @memberOf jQuery
+ */
+ $("#initials_table").find("a.ajax").live('click', function (event) {
+ event.preventDefault();
+ var $msgbox = PMA_ajaxShowMessage();
+ $.get($(this).attr('href'), {'ajax_request' : true}, function (data) {
+ if (data.success === true) {
+ PMA_ajaxRemoveMessage($msgbox);
+ // This form is not on screen when first entering Privileges
+ // if there are more than 50 users
+ $("div.notice").remove();
+ $("#usersForm").hide("medium").remove();
+ $("#fieldset_add_user").hide("medium").remove();
+ $("#initials_table")
+ .after(data.message).show("medium")
+ .siblings("h2").not(":first").remove();
+ } else {
+ PMA_ajaxShowMessage(data.error, false);
+ }
+ }); // end $.get
+ }); // end of the paginate users table
+
+ /*
+ * Additional confirmation dialog after clicking
+ * 'Drop the databases...'
+ */
+ $('#checkbox_drop_users_db').click(function () {
+ var $this_checkbox = $(this);
+ if ($this_checkbox.is(':checked')) {
+ var is_confirmed = confirm(PMA_messages.strDropDatabaseStrongWarning + '\n' + $.sprintf(PMA_messages.strDoYouReally, 'DROP DATABASE'));
+ if (! is_confirmed) {
+ $this_checkbox.prop('checked', false);
+ }
+ }
+ });
+
+ displayPasswordGenerateButton();
+});
diff --git a/js/server_status_advisor.js b/js/server_status_advisor.js
new file mode 100644
index 0000000000..eb1a5f89fb
--- /dev/null
+++ b/js/server_status_advisor.js
@@ -0,0 +1,93 @@
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Server Status Advisor
+ *
+ * @package PhpMyAdmin
+ */
+
+/**
+ * Unbind all event handlers before tearing down a page
+ */
+AJAX.registerTeardown('server_status_advisor.js', function () {
+ $('a[href="#openAdvisorInstructions"]').unbind('click');
+ $('#statustabs_advisor').html('');
+ $('#advisorDialog').remove();
+ $('#instructionsDialog').remove();
+});
+
+AJAX.registerOnload('server_status_advisor.js', function () {
+ /**** Server config advisor ****/
+ var $dialog = $('<div />').attr('id', 'advisorDialog');
+ var $instructionsDialog = $('<div />')
+ .attr('id', 'instructionsDialog')
+ .html($('#advisorInstructionsDialog').html());
+
+ $('a[href="#openAdvisorInstructions"]').click(function () {
+ var dlgBtns = {};
+ dlgBtns[PMA_messages.strClose] = function () {
+ $(this).dialog('close');
+ };
+ $instructionsDialog.dialog({
+ title: PMA_messages.strAdvisorSystem,
+ width: 700,
+ buttons: dlgBtns
+ });
+ });
+
+ var $cnt = $('#statustabs_advisor');
+ var $tbody, $tr, str, even = true;
+
+ data = $.parseJSON($('#advisorData').text());
+ $cnt.html('');
+
+ if (data.parse.errors.length > 0) {
+ $cnt.append('<b>Rules file not well formed, following errors were found:</b><br />- ');
+ $cnt.append(data.parse.errors.join('<br/>- '));
+ $cnt.append('<p></p>');
+ }
+
+ if (data.run.errors.length > 0) {
+ $cnt.append('<b>Errors occurred while executing rule expressions:</b><br />- ');
+ $cnt.append(data.run.errors.join('<br/>- '));
+ $cnt.append('<p></p>');
+ }
+
+ if (data.run.fired.length > 0) {
+ $cnt.append('<p><b>' + PMA_messages.strPerformanceIssues + '</b></p>');
+ $cnt.append('<table class="data" id="rulesFired" border="0"><thead><tr>' +
+ '<th>' + PMA_messages.strIssuse + '</th><th>' + PMA_messages.strRecommendation +
+ '</th></tr></thead><tbody></tbody></table>');
+ $tbody = $cnt.find('table#rulesFired');
+
+ var rc_stripped;
+
+ $.each(data.run.fired, function (key, value) {
+ // recommendation may contain links, don't show those in overview table (clicking on them redirects the user)
+ rc_stripped = $.trim($('<div>').html(value.recommendation).text());
+ $tbody.append($tr = $('<tr class="linkElem noclick ' + (even ? 'even' : 'odd') + '"><td>' +
+ value.issue + '</td><td>' + rc_stripped + ' </td></tr>'));
+ even = !even;
+ $tr.data('rule', value);
+
+ $tr.click(function () {
+ var rule = $(this).data('rule');
+ $dialog
+ .dialog({title: PMA_messages.strRuleDetails})
+ .html(
+ '<p><b>' + PMA_messages.strIssuse + ':</b><br />' + rule.issue + '</p>' +
+ '<p><b>' + PMA_messages.strRecommendation + ':</b><br />' + rule.recommendation + '</p>' +
+ '<p><b>' + PMA_messages.strJustification + ':</b><br />' + rule.justification + '</p>' +
+ '<p><b>' + PMA_messages.strFormula + ':</b><br />' + rule.formula + '</p>' +
+ '<p><b>' + PMA_messages.strTest + ':</b><br />' + rule.test + '</p>'
+ );
+
+ var dlgBtns = {};
+ dlgBtns[PMA_messages.strClose] = function () {
+ $(this).dialog('close');
+ };
+
+ $dialog.dialog({ width: 600, buttons: dlgBtns });
+ });
+ });
+ }
+});
diff --git a/js/server_status_monitor.js b/js/server_status_monitor.js
new file mode 100644
index 0000000000..bbbdee4422
--- /dev/null
+++ b/js/server_status_monitor.js
@@ -0,0 +1,2126 @@
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+var runtime = {},
+ server_time_diff,
+ server_os,
+ is_superuser,
+ server_db_isLocal;
+AJAX.registerOnload('server_status_monitor.js', function () {
+ var $js_data_form = $('#js_data');
+ server_time_diff = new Date().getTime() - $js_data_form.find("input[name=server_time]").val();
+ server_os = $js_data_form.find("input[name=server_os]").val();
+ is_superuser = $js_data_form.find("input[name=is_superuser]").val();
+ server_db_isLocal = $js_data_form.find("input[name=server_db_isLocal]").val();
+});
+
+/**
+ * Unbind all event handlers before tearing down a page
+ */
+AJAX.registerTeardown('server_status_monitor.js', function () {
+ $('#emptyDialog').remove();
+ $('#addChartDialog').remove();
+ $('a.popupLink').unbind('click');
+ $('body').unbind('click');
+});
+/**
+ * Popup behaviour
+ */
+AJAX.registerOnload('server_status_monitor.js', function () {
+ $('<div />')
+ .attr('id', 'emptyDialog')
+ .appendTo('#page_content');
+ $('#addChartDialog')
+ .appendTo('#page_content');
+
+ $('a.popupLink').click(function () {
+ var $link = $(this);
+ $('div.' + $link.attr('href').substr(1))
+ .show()
+ .offset({ top: $link.offset().top + $link.height() + 5, left: $link.offset().left })
+ .addClass('openedPopup');
+
+ return false;
+ });
+ $('body').click(function (event) {
+ $('div.openedPopup').each(function () {
+ var $cnt = $(this);
+ var pos = $cnt.offset();
+ // Hide if the mouseclick is outside the popupcontent
+ if (event.pageX < pos.left ||
+ event.pageY < pos.top ||
+ event.pageX > pos.left + $cnt.outerWidth() ||
+ event.pageY > pos.top + $cnt.outerHeight()
+ ) {
+ $cnt.hide().removeClass('openedPopup');
+ }
+ });
+ });
+});
+
+AJAX.registerTeardown('server_status_monitor.js', function () {
+ $('a[href="#rearrangeCharts"], a[href="#endChartEditMode"]').unbind('click');
+ $('div.popupContent select[name="chartColumns"]').unbind('change');
+ $('div.popupContent select[name="gridChartRefresh"]').unbind('change');
+ $('a[href="#addNewChart"]').unbind('click');
+ $('a[href="#exportMonitorConfig"]').unbind('click');
+ $('a[href="#importMonitorConfig"]').unbind('click');
+ $('a[href="#clearMonitorConfig"]').unbind('click');
+ $('a[href="#pauseCharts"]').unbind('click');
+ $('a[href="#monitorInstructionsDialog"]').unbind('click');
+ $('input[name="chartType"]').unbind('click');
+ $('input[name="useDivisor"]').unbind('click');
+ $('input[name="useUnit"]').unbind('click');
+ $('select[name="varChartList"]').unbind('click');
+ $('a[href="#kibDivisor"]').unbind('click');
+ $('a[href="#mibDivisor"]').unbind('click');
+ $('a[href="#submitClearSeries"]').unbind('click');
+ $('a[href="#submitAddSeries"]').unbind('click');
+ // $("input#variableInput").destroy();
+ $('#chartPreset').unbind('click');
+ $('#chartStatusVar').unbind('click');
+ destroyGrid();
+});
+
+AJAX.registerOnload('server_status_monitor.js', function () {
+ // Show tab links
+ $('div.tabLinks').show();
+ $('#loadingMonitorIcon').remove();
+ // Codemirror is loaded on demand so we might need to initialize it
+ if (! codemirror_editor) {
+ var $elm = $('#sqlquery');
+ if ($elm.length > 0 && typeof CodeMirror != 'undefined') {
+ codemirror_editor = CodeMirror.fromTextArea(
+ $elm[0],
+ {
+ lineNumbers: true,
+ matchBrackets: true,
+ indentUnit: 4,
+ mode: "text/x-mysql",
+ lineWrapping: true
+ }
+ );
+ }
+ }
+ // Timepicker is loaded on demand so we need to initialize
+ // datetime fields from the 'load log' dialog
+ $('#logAnalyseDialog .datetimefield').each(function () {
+ PMA_addDatepicker($(this));
+ });
+
+ /**** Monitor charting implementation ****/
+ /* Saves the previous ajax response for differential values */
+ var oldChartData = null;
+ // Holds about to be created chart
+ var newChart = null;
+ var chartSpacing;
+
+ // Whenever the monitor object (runtime.charts) or the settings object
+ // (monitorSettings) changes in a way incompatible to the previous version,
+ // increase this number. It will reset the users monitor and settings object
+ // in his localStorage to the default configuration
+ var monitorProtocolVersion = '1.0';
+
+ // Runtime parameter of the monitor, is being fully set in initGrid()
+ runtime = {
+ // Holds all visible charts in the grid
+ charts: null,
+ // Stores the timeout handler so it can be cleared
+ refreshTimeout: null,
+ // Stores the GET request to refresh the charts
+ refreshRequest: null,
+ // Chart auto increment
+ chartAI: 0,
+ // To play/pause the monitor
+ redrawCharts: false,
+ // Object that contains a list of nodes that need to be retrieved
+ // from the server for chart updates
+ dataList: [],
+ // Current max points per chart (needed for auto calculation)
+ gridMaxPoints: 20,
+ // displayed time frame
+ xmin: -1,
+ xmax: -1
+ };
+ var monitorSettings = null;
+
+ var defaultMonitorSettings = {
+ columns: 3,
+ chartSize: { width: 295, height: 250 },
+ // Max points in each chart. Settings it to 'auto' sets
+ // gridMaxPoints to (chartwidth - 40) / 12
+ gridMaxPoints: 'auto',
+ /* Refresh rate of all grid charts in ms */
+ gridRefresh: 5000
+ };
+
+ // Allows drag and drop rearrange and print/edit icons on charts
+ var editMode = false;
+
+ /* List of preconfigured charts that the user may select */
+ var presetCharts = {
+ // Query cache efficiency
+ 'qce': {
+ title: PMA_messages.strQueryCacheEfficiency,
+ series: [ {
+ label: PMA_messages.strQueryCacheEfficiency
+ } ],
+ nodes: [ {
+ dataPoints: [{type: 'statusvar', name: 'Qcache_hits'}, {type: 'statusvar', name: 'Com_select'}],
+ transformFn: 'qce'
+ } ],
+ maxYLabel: 0
+ },
+ // Query cache usage
+ 'qcu': {
+ title: PMA_messages.strQueryCacheUsage,
+ series: [ {
+ label: PMA_messages.strQueryCacheUsed
+ } ],
+ nodes: [ {
+ dataPoints: [{type: 'statusvar', name: 'Qcache_free_memory'}, {type: 'servervar', name: 'query_cache_size'}],
+ transformFn: 'qcu'
+ } ],
+ maxYLabel: 0
+ }
+ };
+
+ // time span selection
+ var selectionTimeDiff = [];
+ var selectionStartX, selectionStartY, selectionEndX, selectionEndY;
+ var drawTimeSpan = false;
+
+ // chart tooltip
+ var tooltipBox;
+
+ /* Add OS specific system info charts to the preset chart list */
+ switch (server_os) {
+ case 'WINNT':
+ $.extend(presetCharts, {
+ 'cpu': {
+ title: PMA_messages.strSystemCPUUsage,
+ series: [ {
+ label: PMA_messages.strAverageLoad
+ } ],
+ nodes: [ {
+ dataPoints: [{ type: 'cpu', name: 'loadavg'}]
+ } ],
+ maxYLabel: 100
+ },
+
+ 'memory': {
+ title: PMA_messages.strSystemMemory,
+ series: [ {
+ label: PMA_messages.strTotalMemory,
+ fill: true
+ }, {
+ dataType: 'memory',
+ label: PMA_messages.strUsedMemory,
+ fill: true
+ } ],
+ nodes: [{ dataPoints: [{ type: 'memory', name: 'MemTotal' }], valueDivisor: 1024 },
+ { dataPoints: [{ type: 'memory', name: 'MemUsed' }], valueDivisor: 1024 }
+ ],
+ maxYLabel: 0
+ },
+
+ 'swap': {
+ title: PMA_messages.strSystemSwap,
+ series: [ {
+ label: PMA_messages.strTotalSwap,
+ fill: true
+ }, {
+ label: PMA_messages.strUsedSwap,
+ fill: true
+ } ],
+ nodes: [{ dataPoints: [{ type: 'memory', name: 'SwapTotal' }]},
+ { dataPoints: [{ type: 'memory', name: 'SwapUsed' }]}
+ ],
+ maxYLabel: 0
+ }
+ });
+ break;
+
+ case 'Linux':
+ $.extend(presetCharts, {
+ 'cpu': {
+ title: PMA_messages.strSystemCPUUsage,
+ series: [ {
+ label: PMA_messages.strAverageLoad
+ } ],
+ nodes: [{ dataPoints: [{ type: 'cpu', name: 'irrelevant' }], transformFn: 'cpu-linux'}],
+ maxYLabel: 0
+ },
+ 'memory': {
+ title: PMA_messages.strSystemMemory,
+ series: [
+ { label: PMA_messages.strBufferedMemory, fill: true},
+ { label: PMA_messages.strUsedMemory, fill: true},
+ { label: PMA_messages.strCachedMemory, fill: true},
+ { label: PMA_messages.strFreeMemory, fill: true}
+ ],
+ nodes: [
+ { dataPoints: [{ type: 'memory', name: 'Buffers' }], valueDivisor: 1024 },
+ { dataPoints: [{ type: 'memory', name: 'MemUsed' }], valueDivisor: 1024 },
+ { dataPoints: [{ type: 'memory', name: 'Cached' }], valueDivisor: 1024 },
+ { dataPoints: [{ type: 'memory', name: 'MemFree' }], valueDivisor: 1024 }
+ ],
+ maxYLabel: 0
+ },
+ 'swap': {
+ title: PMA_messages.strSystemSwap,
+ series: [
+ { label: PMA_messages.strCachedSwap, fill: true},
+ { label: PMA_messages.strUsedSwap, fill: true},
+ { label: PMA_messages.strFreeSwap, fill: true}
+ ],
+ nodes: [
+ { dataPoints: [{ type: 'memory', name: 'SwapCached' }], valueDivisor: 1024 },
+ { dataPoints: [{ type: 'memory', name: 'SwapUsed' }], valueDivisor: 1024 },
+ { dataPoints: [{ type: 'memory', name: 'SwapFree' }], valueDivisor: 1024 }
+ ],
+ maxYLabel: 0
+ }
+ });
+ break;
+
+ case 'SunOS':
+ $.extend(presetCharts, {
+ 'cpu': {
+ title: PMA_messages.strSystemCPUUsage,
+ series: [ {
+ label: PMA_messages.strAverageLoad
+ } ],
+ nodes: [ {
+ dataPoints: [{ type: 'cpu', name: 'loadavg'}]
+ } ],
+ maxYLabel: 0
+ },
+ 'memory': {
+ title: PMA_messages.strSystemMemory,
+ series: [
+ { label: PMA_messages.strUsedMemory, fill: true },
+ { label: PMA_messages.strFreeMemory, fill: true }
+ ],
+ nodes: [
+ { dataPoints: [{ type: 'memory', name: 'MemUsed' }], valueDivisor: 1024 },
+ { dataPoints: [{ type: 'memory', name: 'MemFree' }], valueDivisor: 1024 }
+ ],
+ maxYLabel: 0
+ },
+ 'swap': {
+ title: PMA_messages.strSystemSwap,
+ series: [
+ { label: PMA_messages.strUsedSwap, fill: true },
+ { label: PMA_messages.strFreeSwap, fill: true }
+ ],
+ nodes: [
+ { dataPoints: [{ type: 'memory', name: 'SwapUsed' }], valueDivisor: 1024 },
+ { dataPoints: [{ type: 'memory', name: 'SwapFree' }], valueDivisor: 1024 }
+ ],
+ maxYLabel: 0
+ }
+ });
+ break;
+ }
+
+ // Default setting for the chart grid
+ var defaultChartGrid = {
+ 'c0': {
+ title: PMA_messages.strQuestions,
+ series: [
+ {label: PMA_messages.strQuestions}
+ ],
+ nodes: [
+ {dataPoints: [{ type: 'statusvar', name: 'Questions' }], display: 'differential' }
+ ],
+ maxYLabel: 0
+ },
+ 'c1': {
+ title: PMA_messages.strChartConnectionsTitle,
+ series: [
+ {label: PMA_messages.strConnections},
+ {label: PMA_messages.strProcesses}
+ ],
+ nodes: [
+ {dataPoints: [{ type: 'statusvar', name: 'Connections' }], display: 'differential' },
+ {dataPoints: [{ type: 'proc', name: 'processes' }] }
+ ],
+ maxYLabel: 0
+ },
+ 'c2': {
+ title: PMA_messages.strTraffic,
+ series: [
+ {label: PMA_messages.strBytesSent},
+ {label: PMA_messages.strBytesReceived}
+ ],
+ nodes: [
+ {dataPoints: [{ type: 'statusvar', name: 'Bytes_sent' }], display: 'differential', valueDivisor: 1024 },
+ {dataPoints: [{ type: 'statusvar', name: 'Bytes_received' }], display: 'differential', valueDivisor: 1024 }
+ ],
+ maxYLabel: 0
+ }
+ };
+
+ // Server is localhost => We can add cpu/memory/swap to the default chart
+ if (server_db_isLocal) {
+ defaultChartGrid['c3'] = presetCharts['cpu'];
+ defaultChartGrid['c4'] = presetCharts['memory'];
+ defaultChartGrid['c5'] = presetCharts['swap'];
+ }
+
+ $('a[href="#rearrangeCharts"], a[href="#endChartEditMode"]').click(function (event) {
+ event.preventDefault();
+ editMode = !editMode;
+ if ($(this).attr('href') == '#endChartEditMode') {
+ editMode = false;
+ }
+
+ $('a[href="#endChartEditMode"]').toggle(editMode);
+
+ if (editMode) {
+ // Close the settings popup
+ $('div.popupContent').hide().removeClass('openedPopup');
+
+ $("#chartGrid").sortableTable({
+ ignoreRect: {
+ top: 8,
+ left: chartSize().width - 63,
+ width: 54,
+ height: 24
+ }
+ });
+
+ } else {
+ $("#chartGrid").sortableTable('destroy');
+ }
+ saveMonitor(); // Save settings
+ return false;
+ });
+
+ // global settings
+ $('div.popupContent select[name="chartColumns"]').change(function () {
+ monitorSettings.columns = parseInt(this.value, 10);
+
+ var newSize = chartSize();
+
+ // Empty cells should keep their size so you can drop onto them
+ $('#chartGrid tr td').css('width', newSize.width + 'px');
+
+ /* Reorder all charts that it fills all column cells */
+ var numColumns;
+ var $tr = $('#chartGrid tr:first');
+ var row = 0;
+ while ($tr.length !== 0) {
+ numColumns = 1;
+ // To many cells in one row => put into next row
+ $tr.find('td').each(function () {
+ if (numColumns > monitorSettings.columns) {
+ if ($tr.next().length === 0) {
+ $tr.after('<tr></tr>');
+ }
+ $tr.next().prepend($(this));
+ }
+ numColumns++;
+ });
+
+ // To little cells in one row => for each cell to little,
+ // move all cells backwards by 1
+ if ($tr.next().length > 0) {
+ var cnt = monitorSettings.columns - $tr.find('td').length;
+ for (var i = 0; i < cnt; i++) {
+ $tr.append($tr.next().find('td:first'));
+ $tr.nextAll().each(function () {
+ if ($(this).next().length !== 0) {
+ $(this).append($(this).next().find('td:first'));
+ }
+ });
+ }
+ }
+
+ $tr = $tr.next();
+ row++;
+ }
+
+ if (monitorSettings.gridMaxPoints == 'auto') {
+ runtime.gridMaxPoints = Math.round((newSize.width - 40) / 12);
+ }
+
+ runtime.xmin = new Date().getTime() - server_time_diff - runtime.gridMaxPoints * monitorSettings.gridRefresh;
+ runtime.xmax = new Date().getTime() - server_time_diff + monitorSettings.gridRefresh;
+
+ if (editMode) {
+ $("#chartGrid").sortableTable('refresh');
+ }
+
+ saveMonitor(); // Save settings
+ });
+
+ $('div.popupContent select[name="gridChartRefresh"]').change(function () {
+ monitorSettings.gridRefresh = parseInt(this.value, 10) * 1000;
+ clearTimeout(runtime.refreshTimeout);
+
+ if (runtime.refreshRequest) {
+ runtime.refreshRequest.abort();
+ }
+
+ runtime.xmin = new Date().getTime() - server_time_diff - runtime.gridMaxPoints * monitorSettings.gridRefresh;
+ // fixing chart shift towards left on refresh rate change
+ //runtime.xmax = new Date().getTime() - server_time_diff + monitorSettings.gridRefresh;
+ runtime.refreshTimeout = setTimeout(refreshChartGrid, monitorSettings.gridRefresh);
+
+ saveMonitor(); // Save settings
+ });
+
+ $('a[href="#addNewChart"]').click(function (event) {
+ event.preventDefault();
+ var dlgButtons = { };
+
+ dlgButtons[PMA_messages.strAddChart] = function () {
+ var type = $('input[name="chartType"]:checked').val();
+
+ if (type == 'preset') {
+ newChart = presetCharts[$('#addChartDialog select[name="presetCharts"]').prop('value')];
+ } else {
+ // If user builds his own chart, it's being set/updated
+ // each time he adds a series
+ // So here we only warn if he didn't add a series yet
+ if (! newChart || ! newChart.nodes || newChart.nodes.length === 0) {
+ alert(PMA_messages.strAddOneSeriesWarning);
+ return;
+ }
+ }
+
+ newChart.title = $('input[name="chartTitle"]').val();
+ // Add a cloned object to the chart grid
+ addChart($.extend(true, {}, newChart));
+
+ newChart = null;
+
+ saveMonitor(); // Save settings
+
+ $(this).dialog("close");
+ };
+
+ dlgButtons[PMA_messages.strClose] = function () {
+ newChart = null;
+ $('span#clearSeriesLink').hide();
+ $('#seriesPreview').html('');
+ $(this).dialog("close");
+ };
+
+ var $presetList = $('#addChartDialog select[name="presetCharts"]');
+ if ($presetList.html().length === 0) {
+ $.each(presetCharts, function (key, value) {
+ $presetList.append('<option value="' + key + '">' + value.title + '</option>');
+ });
+ $presetList.change(function () {
+ $('input[name="chartTitle"]').val(
+ $presetList.find(':selected').text()
+ );
+ $('#chartPreset').prop('checked', true);
+ });
+ $('#chartPreset').click(function () {
+ $('input[name="chartTitle"]').val(
+ $presetList.find(':selected').text()
+ );
+ });
+ $('#chartStatusVar').click(function () {
+ $('input[name="chartTitle"]').val(
+ $('#chartSeries').find(':selected').text().replace(/_/g, " ")
+ );
+ });
+ $('#chartSeries').change(function () {
+ $('input[name="chartTitle"]').val(
+ $('#chartSeries').find(':selected').text().replace(/_/g, " ")
+ );
+ });
+ }
+
+ $('#addChartDialog').dialog({
+ width: 'auto',
+ height: 'auto',
+ buttons: dlgButtons
+ });
+
+ $('#addChartDialog #seriesPreview').html('<i>' + PMA_messages.strNone + '</i>');
+
+ return false;
+ });
+
+ $('a[href="#exportMonitorConfig"]').click(function (event) {
+ event.preventDefault();
+ var gridCopy = {};
+ $.each(runtime.charts, function (key, elem) {
+ gridCopy[key] = {};
+ gridCopy[key].nodes = elem.nodes;
+ gridCopy[key].settings = elem.settings;
+ gridCopy[key].title = elem.title;
+ });
+ var exportData = {
+ monitorCharts: gridCopy,
+ monitorSettings: monitorSettings
+ };
+ $('<form />', {
+ "class": "disableAjax",
+ method: "post",
+ action: "file_echo.php?" + PMA_commonParams.get('common_query') + "&filename=1",
+ style: "display:none;"
+ })
+ .append(
+ $('<input />', {
+ type: "hidden",
+ name: "monitorconfig",
+ value: $.toJSON(exportData)
+ })
+ )
+ .appendTo('body')
+ .submit()
+ .remove();
+ });
+
+ $('a[href="#importMonitorConfig"]').click(function (event) {
+ event.preventDefault();
+ $('#emptyDialog').dialog({title: PMA_messages.strImportDialogTitle});
+ $('#emptyDialog').html(PMA_messages.strImportDialogMessage + ':<br/><form action="file_echo.php?' + PMA_commonParams.get('common_query') + '&import=1" method="post" enctype="multipart/form-data">' +
+ '<input type="file" name="file"> <input type="hidden" name="import" value="1"> </form>');
+
+ var dlgBtns = {};
+
+ dlgBtns[PMA_messages.strImport] = function () {
+ var $iframe, $form;
+ $('body').append($iframe = $('<iframe id="monitorConfigUpload" style="display:none;"></iframe>'));
+ var d = $iframe[0].contentWindow.document;
+ d.open();
+ d.close();
+ mew = d;
+
+ $iframe.load(function () {
+ var json;
+
+ // Try loading config
+ try {
+ var data = $('body', $('iframe#monitorConfigUpload')[0].contentWindow.document).html();
+ // Chrome wraps around '<pre style="word-wrap: break-word; white-space: pre-wrap;">' to any text content -.-
+ json = $.secureEvalJSON(data.substring(data.indexOf("{"), data.lastIndexOf("}") + 1));
+ } catch (err) {
+ alert(PMA_messages.strFailedParsingConfig);
+ $('#emptyDialog').dialog('close');
+ return;
+ }
+
+ // Basic check, is this a monitor config json?
+ if (!json || ! json.monitorCharts || ! json.monitorCharts) {
+ alert(PMA_messages.strFailedParsingConfig);
+ $('#emptyDialog').dialog('close');
+ return;
+ }
+
+ // If json ok, try applying config
+ try {
+ window.localStorage['monitorCharts'] = $.toJSON(json.monitorCharts);
+ window.localStorage['monitorSettings'] = $.toJSON(json.monitorSettings);
+ rebuildGrid();
+ } catch (err) {
+ alert(PMA_messages.strFailedBuildingGrid);
+ // If an exception is thrown, load default again
+ window.localStorage.removeItem('monitorCharts');
+ window.localStorage.removeItem('monitorSettings');
+ rebuildGrid();
+ }
+
+ $('#emptyDialog').dialog('close');
+ });
+
+ $("body", d).append($form = $('#emptyDialog').find('form'));
+ $form.submit();
+ $('#emptyDialog').append('<img class="ajaxIcon" src="' + pmaThemeImage + 'ajax_clock_small.gif" alt="">');
+ };
+
+ dlgBtns[PMA_messages.strCancel] = function () {
+ $(this).dialog('close');
+ };
+
+
+ $('#emptyDialog').dialog({
+ width: 'auto',
+ height: 'auto',
+ buttons: dlgBtns
+ });
+ });
+
+ $('a[href="#clearMonitorConfig"]').click(function (event) {
+ event.preventDefault();
+ window.localStorage.removeItem('monitorCharts');
+ window.localStorage.removeItem('monitorSettings');
+ window.localStorage.removeItem('monitorVersion');
+ $(this).hide();
+ rebuildGrid();
+ });
+
+ $('a[href="#pauseCharts"]').click(function (event) {
+ event.preventDefault();
+ runtime.redrawCharts = ! runtime.redrawCharts;
+ if (! runtime.redrawCharts) {
+ $(this).html(PMA_getImage('play.png') + ' ' + PMA_messages.strResumeMonitor);
+ } else {
+ $(this).html(PMA_getImage('pause.png') + ' ' + PMA_messages.strPauseMonitor);
+ if (! runtime.charts) {
+ initGrid();
+ $('a[href="#settingsPopup"]').show();
+ }
+ }
+ return false;
+ });
+
+ $('a[href="#monitorInstructionsDialog"]').click(function (event) {
+ event.preventDefault();
+
+ var $dialog = $('#monitorInstructionsDialog');
+
+ $dialog.dialog({
+ width: 595,
+ height: 'auto'
+ }).find('img.ajaxIcon').show();
+
+ var loadLogVars = function (getvars) {
+ var vars = { ajax_request: true, logging_vars: true };
+ if (getvars) {
+ $.extend(vars, getvars);
+ }
+
+ $.get('server_status_monitor.php?' + PMA_commonParams.get('common_query'), vars,
+ function (data) {
+ var logVars;
+ if (data.success === true) {
+ logVars = data.message;
+ } else {
+ return serverResponseError();
+ }
+ var icon = PMA_getImage('s_success.png'), msg = '', str = '';
+
+ if (logVars['general_log'] == 'ON') {
+ if (logVars['slow_query_log'] == 'ON') {
+ msg = PMA_messages.strBothLogOn;
+ } else {
+ msg = PMA_messages.strGenLogOn;
+ }
+ }
+
+ if (msg.length === 0 && logVars['slow_query_log'] == 'ON') {
+ msg = PMA_messages.strSlowLogOn;
+ }
+
+ if (msg.length === 0) {
+ icon = PMA_getImage('s_error.png');
+ msg = PMA_messages.strBothLogOff;
+ }
+
+ str = '<b>' + PMA_messages.strCurrentSettings + '</b><br/><div class="smallIndent">';
+ str += icon + msg + '<br />';
+
+ if (logVars['log_output'] != 'TABLE') {
+ str += PMA_getImage('s_error.png') + ' ' + PMA_messages.strLogOutNotTable + '<br />';
+ } else {
+ str += PMA_getImage('s_success.png') + ' ' + PMA_messages.strLogOutIsTable + '<br />';
+ }
+
+ if (logVars['slow_query_log'] == 'ON') {
+ if (logVars['long_query_time'] > 2) {
+ str += PMA_getImage('s_attention.png') + ' ';
+ str += $.sprintf(PMA_messages.strSmallerLongQueryTimeAdvice, logVars['long_query_time']);
+ str += '<br />';
+ }
+
+ if (logVars['long_query_time'] < 2) {
+ str += PMA_getImage('s_success.png') + ' ';
+ str += $.sprintf(PMA_messages.strLongQueryTimeSet, logVars['long_query_time']);
+ str += '<br />';
+ }
+ }
+
+ str += '</div>';
+
+ if (is_superuser) {
+ str += '<p></p><b>' + PMA_messages.strChangeSettings + '</b>';
+ str += '<div class="smallIndent">';
+ str += PMA_messages.strSettingsAppliedGlobal + '<br/>';
+
+ var varValue = 'TABLE';
+ if (logVars['log_output'] == 'TABLE') {
+ varValue = 'FILE';
+ }
+
+ str += '- <a class="set" href="#log_output-' + varValue + '">';
+ str += $.sprintf(PMA_messages.strSetLogOutput, varValue);
+ str += ' </a><br />';
+
+ if (logVars['general_log'] != 'ON') {
+ str += '- <a class="set" href="#general_log-ON">';
+ str += $.sprintf(PMA_messages.strEnableVar, 'general_log');
+ str += ' </a><br />';
+ } else {
+ str += '- <a class="set" href="#general_log-OFF">';
+ str += $.sprintf(PMA_messages.strDisableVar, 'general_log');
+ str += ' </a><br />';
+ }
+
+ if (logVars['slow_query_log'] != 'ON') {
+ str += '- <a class="set" href="#slow_query_log-ON">';
+ str += $.sprintf(PMA_messages.strEnableVar, 'slow_query_log');
+ str += ' </a><br />';
+ } else {
+ str += '- <a class="set" href="#slow_query_log-OFF">';
+ str += $.sprintf(PMA_messages.strDisableVar, 'slow_query_log');
+ str += ' </a><br />';
+ }
+
+ varValue = 5;
+ if (logVars['long_query_time'] > 2) {
+ varValue = 1;
+ }
+
+ str += '- <a class="set" href="#long_query_time-' + varValue + '">';
+ str += $.sprintf(PMA_messages.setSetLongQueryTime, varValue);
+ str += ' </a><br />';
+
+ } else {
+ str += PMA_messages.strNoSuperUser + '<br/>';
+ }
+
+ str += '</div>';
+
+ $dialog.find('div.monitorUse').toggle(
+ logVars['log_output'] == 'TABLE' && (logVars['slow_query_log'] == 'ON' || logVars['general_log'] == 'ON')
+ );
+
+ $dialog.find('div.ajaxContent').html(str);
+ $dialog.find('img.ajaxIcon').hide();
+ $dialog.find('a.set').click(function () {
+ var nameValue = $(this).attr('href').split('-');
+ loadLogVars({ varName: nameValue[0].substr(1), varValue: nameValue[1]});
+ $dialog.find('img.ajaxIcon').show();
+ });
+ }
+ );
+ };
+
+
+ loadLogVars();
+
+ return false;
+ });
+
+ $('input[name="chartType"]').change(function () {
+ $('#chartVariableSettings').toggle(this.checked && this.value == 'variable');
+ var title = $('input[name="chartTitle"]').val();
+ if (title == PMA_messages.strChartTitle ||
+ title == $('label[for="' + $('input[name="chartTitle"]').data('lastRadio') + '"]').text()
+ ) {
+ $('input[name="chartTitle"]')
+ .data('lastRadio', $(this).attr('id'))
+ .val($('label[for="' + $(this).attr('id') + '"]').text());
+ }
+
+ });
+
+ $('input[name="useDivisor"]').change(function () {
+ $('span.divisorInput').toggle(this.checked);
+ });
+
+ $('input[name="useUnit"]').change(function () {
+ $('span.unitInput').toggle(this.checked);
+ });
+
+ $('select[name="varChartList"]').change(function () {
+ if (this.selectedIndex !== 0) {
+ $('#variableInput').val(this.value);
+ }
+ });
+
+ $('a[href="#kibDivisor"]').click(function (event) {
+ event.preventDefault();
+ $('input[name="valueDivisor"]').val(1024);
+ $('input[name="valueUnit"]').val(PMA_messages.strKiB);
+ $('span.unitInput').toggle(true);
+ $('input[name="useUnit"]').prop('checked', true);
+ return false;
+ });
+
+ $('a[href="#mibDivisor"]').click(function (event) {
+ event.preventDefault();
+ $('input[name="valueDivisor"]').val(1024 * 1024);
+ $('input[name="valueUnit"]').val(PMA_messages.strMiB);
+ $('span.unitInput').toggle(true);
+ $('input[name="useUnit"]').prop('checked', true);
+ return false;
+ });
+
+ $('a[href="#submitClearSeries"]').click(function (event) {
+ event.preventDefault();
+ $('#seriesPreview').html('<i>' + PMA_messages.strNone + '</i>');
+ newChart = null;
+ $('#clearSeriesLink').hide();
+ });
+
+ $('a[href="#submitAddSeries"]').click(function (event) {
+ event.preventDefault();
+ if ($('#variableInput').val() === "") {
+ return false;
+ }
+
+ if (newChart === null) {
+ $('#seriesPreview').html('');
+
+ newChart = {
+ title: $('input[name="chartTitle"]').val(),
+ nodes: [],
+ series: [],
+ maxYLabel: 0
+ };
+ }
+
+ var serie = {
+ dataPoints: [{ type: 'statusvar', name: $('#variableInput').val() }],
+ display: $('input[name="differentialValue"]').prop('checked') ? 'differential' : ''
+ };
+
+ if (serie.dataPoints[0].name == 'Processes') {
+ serie.dataPoints[0].type = 'proc';
+ }
+
+ if ($('input[name="useDivisor"]').prop('checked')) {
+ serie.valueDivisor = parseInt($('input[name="valueDivisor"]').val(), 10);
+ }
+
+ if ($('input[name="useUnit"]').prop('checked')) {
+ serie.unit = $('input[name="valueUnit"]').val();
+ }
+
+ var str = serie.display == 'differential' ? ', ' + PMA_messages.strDifferential : '';
+ str += serie.valueDivisor ? (', ' + $.sprintf(PMA_messages.strDividedBy, serie.valueDivisor)) : '';
+ str += serie.unit ? (', ' + PMA_messages.strUnit + ': ' + serie.unit) : '';
+
+ var newSeries = {
+ label: $('#variableInput').val().replace(/_/g, " ")
+ };
+ newChart.series.push(newSeries);
+ $('#seriesPreview').append('- ' + newSeries.label + str + '<br/>');
+ newChart.nodes.push(serie);
+ $('#variableInput').val('');
+ $('input[name="differentialValue"]').prop('checked', true);
+ $('input[name="useDivisor"]').prop('checked', false);
+ $('input[name="useUnit"]').prop('checked', false);
+ $('input[name="useDivisor"]').trigger('change');
+ $('input[name="useUnit"]').trigger('change');
+ $('select[name="varChartList"]').get(0).selectedIndex = 0;
+
+ $('#clearSeriesLink').show();
+
+ return false;
+ });
+
+ $("#variableInput").autocomplete({
+ source: variableNames
+ });
+
+ /* Initializes the monitor, called only once */
+ function initGrid() {
+
+ var i;
+
+ /* Apply default values & config */
+ if (window.localStorage) {
+ if (window.localStorage['monitorCharts']) {
+ runtime.charts = $.parseJSON(window.localStorage['monitorCharts']);
+ }
+ if (window.localStorage['monitorSettings']) {
+ monitorSettings = $.parseJSON(window.localStorage['monitorSettings']);
+ }
+
+ $('a[href="#clearMonitorConfig"]').toggle(runtime.charts !== null);
+
+ if (runtime.charts !== null && monitorProtocolVersion != window.localStorage['monitorVersion']) {
+ $('#emptyDialog').dialog({title: PMA_messages.strIncompatibleMonitorConfig});
+ $('#emptyDialog').html(PMA_messages.strIncompatibleMonitorConfigDescription);
+
+ var dlgBtns = {};
+ dlgBtns[PMA_messages.strClose] = function () { $(this).dialog('close'); };
+
+ $('#emptyDialog').dialog({
+ width: 400,
+ buttons: dlgBtns
+ });
+ }
+ }
+
+ if (runtime.charts === null) {
+ runtime.charts = defaultChartGrid;
+ }
+ if (monitorSettings === null) {
+ monitorSettings = defaultMonitorSettings;
+ }
+
+ $('select[name="gridChartRefresh"]').val(monitorSettings.gridRefresh / 1000);
+ $('select[name="chartColumns"]').val(monitorSettings.columns);
+
+ if (monitorSettings.gridMaxPoints == 'auto') {
+ runtime.gridMaxPoints = Math.round((monitorSettings.chartSize.width - 40) / 12);
+ } else {
+ runtime.gridMaxPoints = monitorSettings.gridMaxPoints;
+ }
+
+ runtime.xmin = new Date().getTime() - server_time_diff - runtime.gridMaxPoints * monitorSettings.gridRefresh;
+ runtime.xmax = new Date().getTime() - server_time_diff + monitorSettings.gridRefresh;
+
+ /* Calculate how much spacing there is between each chart */
+ $('#chartGrid').html('<tr><td></td><td></td></tr><tr><td></td><td></td></tr>');
+ chartSpacing = {
+ width: $('#chartGrid td:nth-child(2)').offset().left -
+ $('#chartGrid td:nth-child(1)').offset().left,
+ height: $('#chartGrid tr:nth-child(2) td:nth-child(2)').offset().top -
+ $('#chartGrid tr:nth-child(1) td:nth-child(1)').offset().top
+ };
+ $('#chartGrid').html('');
+
+ /* Add all charts - in correct order */
+ var keys = [];
+ $.each(runtime.charts, function (key, value) {
+ keys.push(key);
+ });
+ keys.sort();
+ for (i = 0; i < keys.length; i++) {
+ addChart(runtime.charts[keys[i]], true);
+ }
+
+ /* Fill in missing cells */
+ var numCharts = $('#chartGrid .monitorChart').length;
+ var numMissingCells = (monitorSettings.columns - numCharts % monitorSettings.columns) % monitorSettings.columns;
+ for (i = 0; i < numMissingCells; i++) {
+ $('#chartGrid tr:last').append('<td></td>');
+ }
+
+ // Empty cells should keep their size so you can drop onto them
+ $('#chartGrid tr td').css('width', chartSize().width + 'px');
+
+ buildRequiredDataList();
+ refreshChartGrid();
+ }
+
+ /* Calls destroyGrid() and initGrid(), but before doing so it saves the chart
+ * data from each chart and restores it after the monitor is initialized again */
+ function rebuildGrid() {
+ var oldData = null;
+ if (runtime.charts) {
+ oldData = {};
+ $.each(runtime.charts, function (key, chartObj) {
+ for (var i = 0, l = chartObj.nodes.length; i < l; i++) {
+ oldData[chartObj.nodes[i].dataPoint] = [];
+ for (var j = 0, ll = chartObj.chart.series[i].data.length; j < ll; j++) {
+ oldData[chartObj.nodes[i].dataPoint].push([chartObj.chart.series[i].data[j].x, chartObj.chart.series[i].data[j].y]);
+ }
+ }
+ });
+ }
+
+ destroyGrid();
+ initGrid();
+ }
+
+ /* Calculactes the dynamic chart size that depends on the column width */
+ function chartSize() {
+ var wdt = $('#logTable').innerWidth() / monitorSettings.columns - (monitorSettings.columns - 1) * chartSpacing.width;
+ return {
+ width: wdt,
+ height: 0.75 * wdt
+ };
+ }
+
+ /* Adds a chart to the chart grid */
+ function addChart(chartObj, initialize) {
+
+ var i;
+ var settings = {
+ title: escapeHtml(chartObj.title),
+ grid: {
+ drawBorder: false,
+ shadow: false,
+ background: 'rgba(0,0,0,0)'
+ },
+ axes: {
+ xaxis: {
+ renderer: $.jqplot.DateAxisRenderer,
+ tickOptions: {
+ formatString: '%H:%M:%S',
+ showGridline: false
+ },
+ min: runtime.xmin,
+ max: runtime.xmax
+ },
+ yaxis: {
+ min: 0,
+ max: 100,
+ tickInterval: 20
+ }
+ },
+ seriesDefaults: {
+ rendererOptions: {
+ smooth: true
+ },
+ showLine: true,
+ lineWidth: 2
+ },
+ highlighter: {
+ show: true
+ }
+ };
+
+ if (settings.title === PMA_messages.strSystemCPUUsage ||
+ settings.title === PMA_messages.strQueryCacheEfficiency
+ ) {
+ settings.axes.yaxis.tickOptions = {
+ formatString: "%d %%"
+ };
+ } else if (settings.title === PMA_messages.strSystemMemory ||
+ settings.title === PMA_messages.strSystemSwap
+ ) {
+ settings.stackSeries = true;
+ settings.axes.yaxis.tickOptions = {
+ formatter: $.jqplot.byteFormatter(2) // MiB
+ };
+ } else if (settings.title === PMA_messages.strTraffic) {
+ settings.axes.yaxis.tickOptions = {
+ formatter: $.jqplot.byteFormatter(1) // KiB
+ };
+ } else if (settings.title === PMA_messages.strQuestions ||
+ settings.title === PMA_messages.strConnections
+ ) {
+ settings.axes.yaxis.tickOptions = {
+ formatter: function (format, val) {
+ if (Math.abs(val) >= 1000000) {
+ return $.jqplot.sprintf("%.3g M", val / 1000000);
+ } else if (Math.abs(val) >= 1000) {
+ return $.jqplot.sprintf("%.3g k", val / 1000);
+ } else {
+ return $.jqplot.sprintf("%d", val);
+ }
+ }
+ };
+ }
+
+ settings.series = chartObj.series;
+
+ if ($('#' + 'gridchart' + runtime.chartAI).length === 0) {
+ var numCharts = $('#chartGrid .monitorChart').length;
+
+ if (numCharts === 0 || (numCharts % monitorSettings.columns === 0)) {
+ $('#chartGrid').append('<tr></tr>');
+ }
+
+ $('#chartGrid tr:last').append(
+ '<td><div id="gridChartContainer' + runtime.chartAI + '" class="">' +
+ '<div class="ui-state-default monitorChart" id="' +
+ 'gridchart' + runtime.chartAI + '"></div></div></td>'
+ );
+ }
+
+ // Set series' data as [0,0], smooth lines won't plot with data array having null values.
+ // also chart won't plot initially with no data and data comes on refreshChartGrid()
+ var series = [];
+ for (i in chartObj.series) {
+ series.push([[0, 0]]);
+ }
+
+ // set Tooltip for each series
+ for (i in settings.series) {
+ settings.series[i].highlighter = {
+ show: true,
+ tooltipContentEditor: function (str, seriesIndex, pointIndex, plot) {
+ var j;
+ // TODO: move style to theme CSS
+ var tooltipHtml = '<div style="font-size:12px;background-color:#FFFFFF;' +
+ 'opacity:0.95;filter:alpha(opacity=95);padding:5px;">';
+ // x value i.e. time
+ var timeValue = str.split(",")[0];
+ var seriesValue;
+ tooltipHtml += 'Time: ' + timeValue;
+ tooltipHtml += '<span style="font-weight:bold;">';
+ // Add y values to the tooltip per series
+ for (j in plot.series) {
+ // get y value if present
+ if (plot.series[j].data.length > pointIndex) {
+ seriesValue = plot.series[j].data[pointIndex][1];
+ } else {
+ return;
+ }
+ var seriesLabel = plot.series[j].label;
+ var seriesColor = plot.series[j].color;
+ // format y value
+ if (plot.series[0]._yaxis.tickOptions.formatter) {
+ // using formatter function
+ seriesValue = plot.series[0]._yaxis.tickOptions.formatter('%s', seriesValue);
+ } else if (plot.series[0]._yaxis.tickOptions.formatString) {
+ // using format string
+ seriesValue = $.sprintf(plot.series[0]._yaxis.tickOptions.formatString, seriesValue);
+ }
+ tooltipHtml += '<br /><span style="color:' + seriesColor + '">' +
+ seriesLabel + ': ' + seriesValue + '</span>';
+ }
+ tooltipHtml += '</span></div>';
+ return tooltipHtml;
+ }
+ };
+ }
+
+ chartObj.chart = $.jqplot('gridchart' + runtime.chartAI, series, settings);
+ // remove [0,0] after plotting
+ for (i in chartObj.chart.series) {
+ chartObj.chart.series[i].data.shift();
+ }
+
+ var $legend = $('<div />').css('padding', '0.5em');
+ for (i in chartObj.chart.series) {
+ $legend.append(
+ $('<div />').append(
+ $('<div>').css({
+ width: '1em',
+ height: '1em',
+ background: chartObj.chart.seriesColors[i]
+ }).addClass('floatleft')
+ ).append(
+ $('<div>').text(
+ chartObj.chart.series[i].label
+ ).addClass('floatleft')
+ ).append(
+ $('<div class="clearfloat">')
+ ).addClass('floatleft')
+ );
+ }
+ $('#gridchart' + runtime.chartAI)
+ .parent()
+ .append($legend);
+
+ if (initialize !== true) {
+ runtime.charts['c' + runtime.chartAI] = chartObj;
+ buildRequiredDataList();
+ }
+
+ // time span selection
+ $('#gridchart' + runtime.chartAI).bind('jqplotMouseDown', function (ev, gridpos, datapos, neighbor, plot) {
+ drawTimeSpan = true;
+ selectionTimeDiff.push(datapos.xaxis);
+ if ($('#selection_box').length) {
+ $('#selection_box').remove();
+ }
+ selectionBox = $('<div id="selection_box" style="z-index:1000;height:250px;position:absolute;background-color:#87CEEB;opacity:0.4;filter:alpha(opacity=40);pointer-events:none;">');
+ $(document.body).append(selectionBox);
+ selectionStartX = ev.pageX;
+ selectionStartY = ev.pageY;
+ selectionBox
+ .attr({id: 'selection_box'})
+ .css({
+ top: selectionStartY - gridpos.y,
+ left: selectionStartX
+ })
+ .fadeIn();
+ });
+
+ $('#gridchart' + runtime.chartAI).bind('jqplotMouseUp', function (ev, gridpos, datapos, neighbor, plot) {
+ if (! drawTimeSpan || editMode) {
+ return;
+ }
+
+ selectionTimeDiff.push(datapos.xaxis);
+
+ if (selectionTimeDiff[1] <= selectionTimeDiff[0]) {
+ selectionTimeDiff = [];
+ return;
+ }
+ //get date from timestamp
+ var min = new Date(Math.ceil(selectionTimeDiff[0]));
+ var max = new Date(Math.ceil(selectionTimeDiff[1]));
+ PMA_getLogAnalyseDialog(min, max);
+ selectionTimeDiff = [];
+ drawTimeSpan = false;
+ });
+
+ $('#gridchart' + runtime.chartAI).bind('jqplotMouseMove', function (ev, gridpos, datapos, neighbor, plot) {
+ if (! drawTimeSpan || editMode) {
+ return;
+ }
+ if (selectionStartX !== undefined) {
+ $('#selection_box')
+ .css({
+ width: Math.ceil(ev.pageX - selectionStartX)
+ })
+ .fadeIn();
+ }
+ });
+
+ $('#gridchart' + runtime.chartAI).bind('jqplotMouseLeave', function (ev, gridpos, datapos, neighbor, plot) {
+ drawTimeSpan = false;
+ });
+
+ $(document.body).mouseup(function () {
+ if ($('#selection_box').length) {
+ selectionBox.remove();
+ }
+ });
+
+ // Edit, Print icon only in edit mode
+ $('#chartGrid div svg').find('*[zIndex=20], *[zIndex=21], *[zIndex=19]').toggle(editMode);
+
+ runtime.chartAI++;
+ }
+
+ function PMA_getLogAnalyseDialog(min, max) {
+ $('#logAnalyseDialog input[name="dateStart"]')
+ .val(PMA_formatDateTime(min, true));
+ $('#logAnalyseDialog input[name="dateEnd"]')
+ .val(PMA_formatDateTime(max, true));
+
+ var dlgBtns = { };
+
+ dlgBtns[PMA_messages.strFromSlowLog] = function () {
+ loadLog('slow', min, max);
+ $(this).dialog("close");
+ };
+
+ dlgBtns[PMA_messages.strFromGeneralLog] = function () {
+ loadLog('general', min, max);
+ $(this).dialog("close");
+ };
+
+ $('#logAnalyseDialog').dialog({
+ width: 'auto',
+ height: 'auto',
+ buttons: dlgBtns
+ });
+ }
+
+ function loadLog(type, min, max) {
+ var dateStart = Date.parse($('#logAnalyseDialog input[name="dateStart"]').prop('value')) || min;
+ var dateEnd = Date.parse($('#logAnalyseDialog input[name="dateEnd"]').prop('value')) || max;
+
+ loadLogStatistics({
+ src: type,
+ start: dateStart,
+ end: dateEnd,
+ removeVariables: $('#removeVariables').prop('checked'),
+ limitTypes: $('#limitTypes').prop('checked')
+ });
+ }
+
+ /* Called in regular intervalls, this function updates the values of each chart in the grid */
+ function refreshChartGrid() {
+ /* Send to server */
+ runtime.refreshRequest = $.post('server_status_monitor.php?' + PMA_commonParams.get('common_query'), {
+ ajax_request: true,
+ chart_data: 1,
+ type: 'chartgrid',
+ requiredData: $.toJSON(runtime.dataList)
+ }, function (data) {
+ var chartData;
+ if (data.success === true) {
+ chartData = data.message;
+ } else {
+ return serverResponseError();
+ }
+ var value, i = 0;
+ var diff;
+ var total;
+
+ /* Update values in each graph */
+ $.each(runtime.charts, function (orderKey, elem) {
+ var key = elem.chartID;
+ // If newly added chart, we have no data for it yet
+ if (! chartData[key]) {
+ return;
+ }
+ // Draw all series
+ total = 0;
+ for (var j = 0; j < elem.nodes.length; j++) {
+ // Update x-axis
+ if (i === 0 && j === 0) {
+ if (oldChartData === null) {
+ diff = chartData.x - runtime.xmax;
+ } else {
+ diff = parseInt(chartData.x - oldChartData.x, 10);
+ }
+
+ runtime.xmin += diff;
+ runtime.xmax += diff;
+ }
+
+ //elem.chart.xAxis[0].setExtremes(runtime.xmin, runtime.xmax, false);
+ /* Calculate y value */
+
+ // If transform function given, use it
+ if (elem.nodes[j].transformFn) {
+ value = chartValueTransform(
+ elem.nodes[j].transformFn,
+ chartData[key][j],
+ // Check if first iteration (oldChartData==null), or if newly added chart oldChartData[key]==null
+ (
+ oldChartData === null ||
+ oldChartData[key] === null ||
+ oldChartData[key] === undefined ? null : oldChartData[key][j]
+ )
+ );
+
+ // Otherwise use original value and apply differential and divisor if given,
+ // in this case we have only one data point per series - located at chartData[key][j][0]
+ } else {
+ value = parseFloat(chartData[key][j][0].value);
+
+ if (elem.nodes[j].display == 'differential') {
+ if (oldChartData === null ||
+ oldChartData[key] === null ||
+ oldChartData[key] === undefined
+ ) {
+ continue;
+ }
+ value -= oldChartData[key][j][0].value;
+ }
+
+ if (elem.nodes[j].valueDivisor) {
+ value = value / elem.nodes[j].valueDivisor;
+ }
+ }
+
+ // Set y value, if defined
+ if (value !== undefined) {
+ elem.chart.series[j].data.push([chartData.x, value]);
+ if (value > elem.maxYLabel) {
+ elem.maxYLabel = value;
+ } else if (elem.maxYLabel === 0) {
+ elem.maxYLabel = 0.5;
+ }
+ // free old data point values and update maxYLabel
+ if (elem.chart.series[j].data.length > runtime.gridMaxPoints &&
+ elem.chart.series[j].data[0][0] < runtime.xmin
+ ) {
+ // check if the next freeable point is highest
+ if (elem.maxYLabel <= elem.chart.series[j].data[0][1]) {
+ elem.chart.series[j].data.splice(0, elem.chart.series[j].data.length - runtime.gridMaxPoints);
+ elem.maxYLabel = getMaxYLabel(elem.chart.series[j].data);
+ } else {
+ elem.chart.series[j].data.splice(0, elem.chart.series[j].data.length - runtime.gridMaxPoints);
+ }
+ }
+ if (elem.title === PMA_messages.strSystemMemory ||
+ elem.title === PMA_messages.strSystemSwap
+ ) {
+ total += value;
+ }
+ }
+ }
+
+ // update chart options
+ // keep ticks number/positioning consistent while refreshrate changes
+ var tickInterval = (runtime.xmax - runtime.xmin) / 5;
+ elem.chart['axes']['xaxis'].ticks = [(runtime.xmax - tickInterval * 4),
+ (runtime.xmax - tickInterval * 3), (runtime.xmax - tickInterval * 2),
+ (runtime.xmax - tickInterval), runtime.xmax];
+
+ if (elem.title !== PMA_messages.strSystemCPUUsage &&
+ elem.title !== PMA_messages.strQueryCacheEfficiency &&
+ elem.title !== PMA_messages.strSystemMemory &&
+ elem.title !== PMA_messages.strSystemSwap
+ ) {
+ elem.chart['axes']['yaxis']['max'] = Math.ceil(elem.maxYLabel * 1.1);
+ elem.chart['axes']['yaxis']['tickInterval'] = Math.ceil(elem.maxYLabel * 1.1 / 5);
+ } else if (elem.title === PMA_messages.strSystemMemory ||
+ elem.title === PMA_messages.strSystemSwap
+ ) {
+ elem.chart['axes']['yaxis']['max'] = Math.ceil(total * 1.1 / 100) * 100;
+ elem.chart['axes']['yaxis']['tickInterval'] = Math.ceil(total * 1.1 / 5);
+ }
+ i++;
+
+ if (runtime.redrawCharts) {
+ elem.chart.replot();
+ }
+ });
+
+ oldChartData = chartData;
+
+ runtime.refreshTimeout = setTimeout(refreshChartGrid, monitorSettings.gridRefresh);
+ });
+ }
+
+ /* Function to get highest plotted point's y label, to scale the chart,
+ * TODO: make jqplot's autoscale:true work here
+ */
+ function getMaxYLabel(dataValues) {
+ var maxY = dataValues[0][1];
+ $.each(dataValues, function (k, v) {
+ maxY = (v[1] > maxY) ? v[1] : maxY;
+ });
+ return maxY;
+ }
+
+ /* Function that supplies special value transform functions for chart values */
+ function chartValueTransform(name, cur, prev) {
+ switch (name) {
+ case 'cpu-linux':
+ if (prev === null) {
+ return undefined;
+ }
+ // cur and prev are datapoint arrays, but containing
+ // only 1 element for cpu-linux
+ cur = cur[0];
+ prev = prev[0];
+
+ var diff_total = cur.busy + cur.idle - (prev.busy + prev.idle);
+ var diff_idle = cur.idle - prev.idle;
+ return 100 * (diff_total - diff_idle) / diff_total;
+
+ // Query cache efficiency (%)
+ case 'qce':
+ if (prev === null) {
+ return undefined;
+ }
+ // cur[0].value is Qcache_hits, cur[1].value is Com_select
+ var diffQHits = cur[0].value - prev[0].value;
+ // No NaN please :-)
+ if (cur[1].value - prev[1].value === 0) {
+ return 0;
+ }
+
+ return diffQHits / (cur[1].value - prev[1].value + diffQHits) * 100;
+
+ // Query cache usage (%)
+ case 'qcu':
+ if (cur[1].value === 0) {
+ return 0;
+ }
+ // cur[0].value is Qcache_free_memory, cur[1].value is query_cache_size
+ return 100 - cur[0].value / cur[1].value * 100;
+
+ }
+ return undefined;
+ }
+
+ /* Build list of nodes that need to be retrieved from server.
+ * It creates something like a stripped down version of the runtime.charts object.
+ */
+ function buildRequiredDataList() {
+ runtime.dataList = {};
+ // Store an own id, because the property name is subject of reordering,
+ // thus destroying our mapping with runtime.charts <=> runtime.dataList
+ var chartID = 0;
+ $.each(runtime.charts, function (key, chart) {
+ runtime.dataList[chartID] = [];
+ for (var i = 0, l = chart.nodes.length; i < l; i++) {
+ runtime.dataList[chartID][i] = chart.nodes[i].dataPoints;
+ }
+ runtime.charts[key].chartID = chartID;
+ chartID++;
+ });
+ }
+
+ /* Loads the log table data, generates the table and handles the filters */
+ function loadLogStatistics(opts) {
+ var tableStr = '';
+ var logRequest = null;
+
+ if (! opts.removeVariables) {
+ opts.removeVariables = false;
+ }
+ if (! opts.limitTypes) {
+ opts.limitTypes = false;
+ }
+
+ $('#emptyDialog').dialog({title: PMA_messages.strAnalysingLogsTitle});
+ $('#emptyDialog').html(PMA_messages.strAnalysingLogs +
+ ' <img class="ajaxIcon" src="' + pmaThemeImage +
+ 'ajax_clock_small.gif" alt="">');
+ var dlgBtns = {};
+
+ dlgBtns[PMA_messages.strCancelRequest] = function () {
+ if (logRequest !== null) {
+ logRequest.abort();
+ }
+
+ $(this).dialog("close");
+ };
+
+ $('#emptyDialog').dialog({
+ width: 'auto',
+ height: 'auto',
+ buttons: dlgBtns
+ });
+
+
+ logRequest = $.get('server_status_monitor.php?' + PMA_commonParams.get('common_query'),
+ { ajax_request: true,
+ log_data: 1,
+ type: opts.src,
+ time_start: Math.round(opts.start / 1000),
+ time_end: Math.round(opts.end / 1000),
+ removeVariables: opts.removeVariables,
+ limitTypes: opts.limitTypes
+ },
+ function (data) {
+ var logData;
+ var dlgBtns = {};
+ if (data.success === true) {
+ logData = data.message;
+ } else {
+ return serverResponseError();
+ }
+
+ if (logData.rows.length !== 0) {
+ runtime.logDataCols = buildLogTable(logData);
+
+ /* Show some stats in the dialog */
+ $('#emptyDialog').dialog({title: PMA_messages.strLoadingLogs});
+ $('#emptyDialog').html('<p>' + PMA_messages.strLogDataLoaded + '</p>');
+ $.each(logData.sum, function (key, value) {
+ key = key.charAt(0).toUpperCase() + key.slice(1).toLowerCase();
+ if (key == 'Total') {
+ key = '<b>' + key + '</b>';
+ }
+ $('#emptyDialog').append(key + ': ' + value + '<br/>');
+ });
+
+ /* Add filter options if more than a bunch of rows there to filter */
+ if (logData.numRows > 12) {
+ $('#logTable').prepend(
+ '<fieldset id="logDataFilter">' +
+ ' <legend>' + PMA_messages.strFiltersForLogTable + '</legend>' +
+ ' <div class="formelement">' +
+ ' <label for="filterQueryText">' + PMA_messages.strFilterByWordRegexp + '</label>' +
+ ' <input name="filterQueryText" type="text" id="filterQueryText" style="vertical-align: baseline;" />' +
+ ' </div>' +
+ ((logData.numRows > 250) ? ' <div class="formelement"><button name="startFilterQueryText" id="startFilterQueryText">' + PMA_messages.strFilter + '</button></div>' : '') +
+ ' <div class="formelement">' +
+ ' <input type="checkbox" id="noWHEREData" name="noWHEREData" value="1" /> ' +
+ ' <label for="noWHEREData"> ' + PMA_messages.strIgnoreWhereAndGroup + '</label>' +
+ ' </div' +
+ '</fieldset>'
+ );
+
+ $('#logTable #noWHEREData').change(function () {
+ filterQueries(true);
+ });
+
+ if (logData.numRows > 250) {
+ $('#logTable #startFilterQueryText').click(filterQueries);
+ } else {
+ $('#logTable #filterQueryText').keyup(filterQueries);
+ }
+
+ }
+
+ dlgBtns[PMA_messages.strJumpToTable] = function () {
+ $(this).dialog("close");
+ $(document).scrollTop($('#logTable').offset().top);
+ };
+
+ $('#emptyDialog').dialog("option", "buttons", dlgBtns);
+
+ } else {
+ $('#emptyDialog').dialog({title: PMA_messages.strNoDataFoundTitle});
+ $('#emptyDialog').html('<p>' + PMA_messages.strNoDataFound + '</p>');
+
+ dlgBtns[PMA_messages.strClose] = function () {
+ $(this).dialog("close");
+ };
+
+ $('#emptyDialog').dialog("option", "buttons", dlgBtns);
+ }
+ }
+ );
+
+ /* Handles the actions performed when the user uses any of the
+ * log table filters which are the filter by name and grouping
+ * with ignoring data in WHERE clauses
+ *
+ * @param boolean Should be true when the users enabled or disabled
+ * to group queries ignoring data in WHERE clauses
+ */
+ function filterQueries(varFilterChange) {
+ var odd_row = false, cell, textFilter;
+ var val = $('#logTable #filterQueryText').val();
+
+ if (val.length === 0) {
+ textFilter = null;
+ } else {
+ textFilter = new RegExp(val, 'i');
+ }
+
+ var rowSum = 0, totalSum = 0, i = 0, q;
+ var noVars = $('#logTable #noWHEREData').prop('checked');
+ var equalsFilter = /([^=]+)=(\d+|((\'|"|).*?[^\\])\4((\s+)|$))/gi;
+ var functionFilter = /([a-z0-9_]+)\(.+?\)/gi;
+ var filteredQueries = {}, filteredQueriesLines = {};
+ var hide = false, rowData;
+ var queryColumnName = runtime.logDataCols[runtime.logDataCols.length - 2];
+ var sumColumnName = runtime.logDataCols[runtime.logDataCols.length - 1];
+ var isSlowLog = opts.src == 'slow';
+ var columnSums = {};
+
+ // For the slow log we have to count many columns (query_time, lock_time, rows_examined, rows_sent, etc.)
+ var countRow = function (query, row) {
+ var cells = row.match(/<td>(.*?)<\/td>/gi);
+ if (!columnSums[query]) {
+ columnSums[query] = [0, 0, 0, 0];
+ }
+
+ // lock_time and query_time and displayed in timespan format
+ columnSums[query][0] += timeToSec(cells[2].replace(/(<td>|<\/td>)/gi, ''));
+ columnSums[query][1] += timeToSec(cells[3].replace(/(<td>|<\/td>)/gi, ''));
+ // rows_examind and rows_sent are just numbers
+ columnSums[query][2] += parseInt(cells[4].replace(/(<td>|<\/td>)/gi, ''), 10);
+ columnSums[query][3] += parseInt(cells[5].replace(/(<td>|<\/td>)/gi, ''), 10);
+ };
+
+ // We just assume the sql text is always in the second last column, and that the total count is right of it
+ $('#logTable table tbody tr td:nth-child(' + (runtime.logDataCols.length - 1) + ')').each(function () {
+ var $t = $(this);
+ // If query is a SELECT and user enabled or disabled to group
+ // queries ignoring data in where statements, we
+ // need to re-calculate the sums of each row
+ if (varFilterChange && $t.html().match(/^SELECT/i)) {
+ if (noVars) {
+ // Group on => Sum up identical columns, and hide all but 1
+
+ q = $t.text().replace(equalsFilter, '$1=...$6').trim();
+ q = q.replace(functionFilter, ' $1(...)');
+
+ // Js does not specify a limit on property name length,
+ // so we can abuse it as index :-)
+ if (filteredQueries[q]) {
+ filteredQueries[q] += parseInt($t.next().text(), 10);
+ totalSum += parseInt($t.next().text(), 10);
+ hide = true;
+ } else {
+ filteredQueries[q] = parseInt($t.next().text(), 10);
+ filteredQueriesLines[q] = i;
+ $t.text(q);
+ }
+ if (isSlowLog) {
+ countRow(q, $t.parent().html());
+ }
+
+ } else {
+ // Group off: Restore original columns
+
+ rowData = $t.parent().data('query');
+ // Restore SQL text
+ $t.text(rowData[queryColumnName]);
+ // Restore total count
+ $t.next().text(rowData[sumColumnName]);
+ // Restore slow log columns
+ if (isSlowLog) {
+ $t.parent().children('td:nth-child(3)').text(rowData['query_time']);
+ $t.parent().children('td:nth-child(4)').text(rowData['lock_time']);
+ $t.parent().children('td:nth-child(5)').text(rowData['rows_sent']);
+ $t.parent().children('td:nth-child(6)').text(rowData['rows_examined']);
+ }
+ }
+ }
+
+ // If not required to be hidden, do we need
+ // to hide because of a not matching text filter?
+ if (! hide && (textFilter !== null && ! textFilter.exec($t.text()))) {
+ hide = true;
+ }
+
+ // Now display or hide this column
+ if (hide) {
+ $t.parent().css('display', 'none');
+ } else {
+ totalSum += parseInt($t.next().text(), 10);
+ rowSum++;
+
+ odd_row = ! odd_row;
+ $t.parent().css('display', '');
+ if (odd_row) {
+ $t.parent().addClass('odd');
+ $t.parent().removeClass('even');
+ } else {
+ $t.parent().addClass('even');
+ $t.parent().removeClass('odd');
+ }
+ }
+
+ hide = false;
+ i++;
+ });
+
+ // We finished summarizing counts => Update count values of all grouped entries
+ if (varFilterChange) {
+ if (noVars) {
+ var numCol, row, $table = $('#logTable table tbody');
+ $.each(filteredQueriesLines, function (key, value) {
+ if (filteredQueries[key] <= 1) {
+ return;
+ }
+
+ row = $table.children('tr:nth-child(' + (value + 1) + ')');
+ numCol = row.children(':nth-child(' + (runtime.logDataCols.length) + ')');
+ numCol.text(filteredQueries[key]);
+
+ if (isSlowLog) {
+ row.children('td:nth-child(3)').text(secToTime(columnSums[key][0]));
+ row.children('td:nth-child(4)').text(secToTime(columnSums[key][1]));
+ row.children('td:nth-child(5)').text(columnSums[key][2]);
+ row.children('td:nth-child(6)').text(columnSums[key][3]);
+ }
+ });
+ }
+
+ $('#logTable table').trigger("update");
+ setTimeout(function () {
+ $('#logTable table').trigger('sorton', [[[runtime.logDataCols.length - 1, 1]]]);
+ }, 0);
+ }
+
+ // Display some stats at the bottom of the table
+ $('#logTable table tfoot tr')
+ .html('<th colspan="' + (runtime.logDataCols.length - 1) + '">' +
+ PMA_messages.strSumRows + ' ' + rowSum + '<span style="float:right">' +
+ PMA_messages.strTotal + '</span></th><th class="right">' + totalSum + '</th>');
+ }
+ }
+
+ /* Turns a timespan (12:12:12) into a number */
+ function timeToSec(timeStr) {
+ var time = timeStr.split(':');
+ return (parseInt(time[0], 10) * 3600) + (parseInt(time[1], 10) * 60) + parseInt(time[2], 10);
+ }
+
+ /* Turns a number into a timespan (100 into 00:01:40) */
+ function secToTime(timeInt) {
+ var hours = Math.floor(timeInt / 3600);
+ timeInt -= hours * 3600;
+ var minutes = Math.floor(timeInt / 60);
+ timeInt -= minutes * 60;
+
+ if (hours < 10) {
+ hours = '0' + hours;
+ }
+ if (minutes < 10) {
+ minutes = '0' + minutes;
+ }
+ if (timeInt < 10) {
+ timeInt = '0' + timeInt;
+ }
+
+ return hours + ':' + minutes + ':' + timeInt;
+ }
+
+ /* Constructs the log table out of the retrieved server data */
+ function buildLogTable(data) {
+ var rows = data.rows;
+ var cols = [];
+ var $table = $('<table class="sortable"></table>');
+ var $tBody, $tRow, $tCell;
+
+ $('#logTable').html($table);
+
+ var formatValue = function (name, value) {
+ if (name == 'user_host') {
+ return value.replace(/(\[.*?\])+/g, '');
+ }
+ return value;
+ };
+
+ for (var i = 0, l = rows.length; i < l; i++) {
+ if (i === 0) {
+ $.each(rows[0], function (key, value) {
+ cols.push(key);
+ });
+ $table.append('<thead>' +
+ '<tr><th class="nowrap">' + cols.join('</th><th class="nowrap">') + '</th></tr>' +
+ '</thead>'
+ );
+
+ $table.append($tBody = $('<tbody></tbody>'));
+ }
+
+ $tBody.append($tRow = $('<tr class="noclick"></tr>'));
+ var cl = '';
+ for (var j = 0, ll = cols.length; j < ll; j++) {
+ // Assuming the query column is the second last
+ if (j == cols.length - 2 && rows[i][cols[j]].match(/^SELECT/i)) {
+ $tRow.append($tCell = $('<td class="linkElem">' + formatValue(cols[j], rows[i][cols[j]]) + '</td>'));
+ $tCell.click(openQueryAnalyzer);
+ } else {
+ $tRow.append('<td>' + formatValue(cols[j], rows[i][cols[j]]) + '</td>');
+ }
+
+ $tRow.data('query', rows[i]);
+ }
+ }
+
+ $table.append('<tfoot>' +
+ '<tr><th colspan="' + (cols.length - 1) + '">' + PMA_messages.strSumRows +
+ ' ' + data.numRows + '<span style="float:right">' + PMA_messages.strTotal +
+ '</span></th><th class="right">' + data.sum.TOTAL + '</th></tr></tfoot>');
+
+ // Append a tooltip to the count column, if there exist one
+ if ($('#logTable th:last').html() == '#') {
+ $('#logTable th:last').append('&nbsp;' + PMA_getImage('b_docs.png', '', {'class': 'qroupedQueryInfoIcon'}));
+
+ var tooltipContent = PMA_messages.strCountColumnExplanation;
+ if (groupInserts) {
+ tooltipContent += '<p>' + PMA_messages.strMoreCountColumnExplanation + '</p>';
+ }
+
+ PMA_tooltip(
+ $('img.qroupedQueryInfoIcon'),
+ 'img',
+ tooltipContent
+ );
+ }
+
+ $('#logTable table').tablesorter({
+ sortList: [[cols.length - 1, 1]],
+ widgets: ['fast-zebra']
+ });
+
+ $('#logTable table thead th')
+ .append('<img class="icon sortableIcon" src="themes/dot.gif" alt="">');
+
+ return cols;
+ }
+
+ /* Opens the query analyzer dialog */
+ function openQueryAnalyzer() {
+ var rowData = $(this).parent().data('query');
+ var query = rowData.argument || rowData.sql_text;
+
+ if (codemirror_editor) {
+ //TODO: somehow PMA_SQLPrettyPrint messes up the query, needs be fixed
+ //query = PMA_SQLPrettyPrint(query);
+ codemirror_editor.setValue(query);
+ // Codemirror is bugged, it doesn't refresh properly sometimes.
+ // Following lines seem to fix that
+ setTimeout(function () {
+ codemirror_editor.refresh();
+ }, 50);
+ }
+ else {
+ $('#sqlquery').val(query);
+ }
+
+ var profilingChart = null;
+ var dlgBtns = {};
+
+ dlgBtns[PMA_messages.strAnalyzeQuery] = function () {
+ loadQueryAnalysis(rowData);
+ };
+ dlgBtns[PMA_messages.strClose] = function () {
+ $(this).dialog('close');
+ };
+
+ $('#queryAnalyzerDialog').dialog({
+ width: 'auto',
+ height: 'auto',
+ resizable: false,
+ buttons: dlgBtns,
+ close: function () {
+ if (profilingChart !== null) {
+ profilingChart.destroy();
+ }
+ $('#queryAnalyzerDialog div.placeHolder').html('');
+ if (codemirror_editor) {
+ codemirror_editor.setValue('');
+ } else {
+ $('#sqlquery').val('');
+ }
+ }
+ });
+ }
+
+ /* Loads and displays the analyzed query data */
+ function loadQueryAnalysis(rowData) {
+ var db = rowData.db || '';
+
+ $('#queryAnalyzerDialog div.placeHolder').html(
+ PMA_messages.strAnalyzing + ' <img class="ajaxIcon" src="' +
+ pmaThemeImage + 'ajax_clock_small.gif" alt="">');
+
+ $.post('server_status_monitor.php?' + PMA_commonParams.get('common_query'), {
+ ajax_request: true,
+ query_analyzer: true,
+ query: codemirror_editor ? codemirror_editor.getValue() : $('#sqlquery').val(),
+ database: db
+ }, function (data) {
+ var i;
+ if (data.success === true) {
+ data = data.message;
+ }
+ if (data.error) {
+ if (data.error.indexOf('1146') != -1 || data.error.indexOf('1046') != -1) {
+ data.error = PMA_messages['strServerLogError'];
+ }
+ $('#queryAnalyzerDialog div.placeHolder').html('<div class="error">' + data.error + '</div>');
+ return;
+ }
+ var totalTime = 0;
+ // Float sux, I'll use table :(
+ $('#queryAnalyzerDialog div.placeHolder')
+ .html('<table width="100%" border="0"><tr><td class="explain"></td><td class="chart"></td></tr></table>');
+
+ var explain = '<b>' + PMA_messages.strExplainOutput + '</b> ' + $('#explain_docu').html();
+ if (data.explain.length > 1) {
+ explain += ' (';
+ for (i = 0; i < data.explain.length; i++) {
+ if (i > 0) {
+ explain += ', ';
+ }
+ explain += '<a href="#showExplain-' + i + '">' + i + '</a>';
+ }
+ explain += ')';
+ }
+ explain += '<p></p>';
+ for (i = 0, l = data.explain.length; i < l; i++) {
+ explain += '<div class="explain-' + i + '"' + (i > 0 ? 'style="display:none;"' : '') + '>';
+ $.each(data.explain[i], function (key, value) {
+ value = (value === null) ? 'null' : value;
+
+ if (key == 'type' && value.toLowerCase() == 'all') {
+ value = '<span class="attention">' + value + '</span>';
+ }
+ if (key == 'Extra') {
+ value = value.replace(/(using (temporary|filesort))/gi, '<span class="attention">$1</span>');
+ }
+ explain += key + ': ' + value + '<br />';
+ });
+ explain += '</div>';
+ }
+
+ explain += '<p><b>' + PMA_messages.strAffectedRows + '</b> ' + data.affectedRows;
+
+ $('#queryAnalyzerDialog div.placeHolder td.explain').append(explain);
+
+ $('#queryAnalyzerDialog div.placeHolder a[href*="#showExplain"]').click(function () {
+ var id = $(this).attr('href').split('-')[1];
+ $(this).parent().find('div[class*="explain"]').hide();
+ $(this).parent().find('div[class*="explain-' + id + '"]').show();
+ });
+
+ if (data.profiling) {
+ var chartData = [];
+ var numberTable = '<table class="queryNums"><thead><tr><th>' + PMA_messages.strStatus + '</th><th>' + PMA_messages.strTime + '</th></tr></thead><tbody>';
+ var duration;
+ var otherTime = 0;
+
+ for (i = 0, l = data.profiling.length; i < l; i++) {
+ duration = parseFloat(data.profiling[i].duration);
+
+ totalTime += duration;
+
+ numberTable += '<tr><td>' + data.profiling[i].state + ' </td><td> ' + PMA_prettyProfilingNum(duration, 2) + '</td></tr>';
+ }
+
+ // Only put those values in the pie which are > 2%
+ for (i = 0, l = data.profiling.length; i < l; i++) {
+ duration = parseFloat(data.profiling[i].duration);
+
+ if (duration / totalTime > 0.02) {
+ chartData.push([PMA_prettyProfilingNum(duration, 2) + ' ' + data.profiling[i].state, duration]);
+ } else {
+ otherTime += duration;
+ }
+ }
+
+ if (otherTime > 0) {
+ chartData.push([PMA_prettyProfilingNum(otherTime, 2) + ' ' + PMA_messages.strOther, otherTime]);
+ }
+
+ numberTable += '<tr><td><b>' + PMA_messages.strTotalTime + '</b></td><td>' + PMA_prettyProfilingNum(totalTime, 2) + '</td></tr>';
+ numberTable += '</tbody></table>';
+
+ $('#queryAnalyzerDialog div.placeHolder td.chart').append(
+ '<b>' + PMA_messages.strProfilingResults + ' ' + $('#profiling_docu').html() + '</b> ' +
+ '(<a href="#showNums">' + PMA_messages.strTable + '</a>, <a href="#showChart">' + PMA_messages.strChart + '</a>)<br/>' +
+ numberTable + ' <div id="queryProfiling"></div>');
+
+ $('#queryAnalyzerDialog div.placeHolder a[href="#showNums"]').click(function () {
+ $('#queryAnalyzerDialog #queryProfiling').hide();
+ $('#queryAnalyzerDialog table.queryNums').show();
+ return false;
+ });
+
+ $('#queryAnalyzerDialog div.placeHolder a[href="#showChart"]').click(function () {
+ $('#queryAnalyzerDialog #queryProfiling').show();
+ $('#queryAnalyzerDialog table.queryNums').hide();
+ return false;
+ });
+
+ profilingChart = PMA_createProfilingChartJqplot(
+ 'queryProfiling',
+ chartData
+ );
+
+ //$('#queryProfiling').resizable();
+ }
+ });
+ }
+
+ /* Saves the monitor to localstorage */
+ function saveMonitor() {
+ var gridCopy = {};
+
+ $.each(runtime.charts, function (key, elem) {
+ gridCopy[key] = {};
+ gridCopy[key].nodes = elem.nodes;
+ gridCopy[key].settings = elem.settings;
+ gridCopy[key].title = elem.title;
+ gridCopy[key].series = elem.series;
+ gridCopy[key].maxYLabel = elem.maxYLabel;
+ });
+
+ if (window.localStorage) {
+ window.localStorage['monitorCharts'] = $.toJSON(gridCopy);
+ window.localStorage['monitorSettings'] = $.toJSON(monitorSettings);
+ window.localStorage['monitorVersion'] = monitorProtocolVersion;
+ }
+
+ $('a[href="#clearMonitorConfig"]').show();
+ }
+});
+
+// Run the monitor once loaded
+AJAX.registerOnload('server_status_monitor.js', function () {
+ $('a[href="#pauseCharts"]').trigger('click');
+});
+
+function serverResponseError() {
+ var btns = {};
+ btns[PMA_messages.strReloadPage] = function () {
+ window.location.reload();
+ };
+ $('#emptyDialog').dialog({title: PMA_messages.strRefreshFailed});
+ $('#emptyDialog').html(
+ PMA_getImage('s_attention.png') +
+ PMA_messages.strInvalidResponseExplanation
+ );
+ $('#emptyDialog').dialog({ buttons: btns });
+}
+
+/* Destroys all monitor related resources */
+function destroyGrid() {
+ if (runtime.charts) {
+ $.each(runtime.charts, function (key, value) {
+ try {
+ value.chart.destroy();
+ } catch (err) {}
+ });
+ }
+
+ try {
+ runtime.refreshRequest.abort();
+ } catch (err) {}
+ try {
+ clearTimeout(runtime.refreshTimeout);
+ } catch (err) {}
+ $('#chartGrid').html('');
+ runtime.charts = null;
+ runtime.chartAI = 0;
+ monitorSettings = null; //TODO:this not global variable
+}
diff --git a/js/server_status_queries.js b/js/server_status_queries.js
new file mode 100644
index 0000000000..9939105d24
--- /dev/null
+++ b/js/server_status_queries.js
@@ -0,0 +1,40 @@
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ */
+
+/**
+ * Unbind all event handlers before tearing down a page
+ */
+AJAX.registerTeardown('server_status_queries.js', function () {
+ var queryPieChart = $('#serverstatusquerieschart').data('queryPieChart');
+ if (queryPieChart) {
+ queryPieChart.destroy();
+ }
+});
+
+AJAX.registerOnload('server_status_queries.js', function () {
+ // Build query statistics chart
+ var cdata = [];
+ try {
+ $.each(jQuery.parseJSON($('#serverstatusquerieschart_data').text()), function (key, value) {
+ cdata.push([key, parseInt(value, 10)]);
+ });
+ $('#serverstatusquerieschart').data(
+ 'queryPieChart',
+ PMA_createProfilingChartJqplot(
+ 'serverstatusquerieschart',
+ cdata
+ )
+ );
+ } catch (exception) {
+ // Could not load chart, no big deal...
+ }
+
+ /*** Table sort tooltip ***/
+ PMA_tooltip(
+ $('table.sortable>thead>tr:first').find('th'),
+ 'th',
+ PMA_messages.strSortHint
+ );
+ initTableSorter('statustabs_queries');
+});
diff --git a/js/server_status_sorter.js b/js/server_status_sorter.js
new file mode 100644
index 0000000000..53330402e2
--- /dev/null
+++ b/js/server_status_sorter.js
@@ -0,0 +1,99 @@
+// TODO: tablesorter shouldn't sort already sorted columns
+function initTableSorter(tabid) {
+ var $table, opts;
+ switch (tabid) {
+ case 'statustabs_queries':
+ $table = $('#serverstatusqueriesdetails');
+ opts = {
+ sortList: [[3, 1]],
+ widgets: ['fast-zebra'],
+ headers: {
+ 1: { sorter: 'fancyNumber' },
+ 2: { sorter: 'fancyNumber' }
+ }
+ };
+ break;
+ case 'statustabs_allvars':
+ $table = $('#serverstatusvariables');
+ opts = {
+ sortList: [[0, 0]],
+ widgets: ['fast-zebra'],
+ headers: {
+ 1: { sorter: 'withinSpanNumber' }
+ }
+ };
+ break;
+ }
+ $table.tablesorter(opts);
+ $table.find('tr:first th')
+ .append('<img class="icon sortableIcon" src="themes/dot.gif" alt="">');
+}
+
+$(function () {
+ $.tablesorter.addParser({
+ id: "fancyNumber",
+ is: function (s) {
+ return (/^[0-9]?[0-9,\.]*\s?(k|M|G|T|%)?$/).test(s);
+ },
+ format: function (s) {
+ var num = jQuery.tablesorter.formatFloat(
+ s.replace(PMA_messages.strThousandsSeparator, '')
+ .replace(PMA_messages.strDecimalSeparator, '.')
+ );
+
+ var factor = 1;
+ switch (s.charAt(s.length - 1)) {
+ case '%':
+ factor = -2;
+ break;
+ // Todo: Complete this list (as well as in the regexp a few lines up)
+ case 'k':
+ factor = 3;
+ break;
+ case 'M':
+ factor = 6;
+ break;
+ case 'G':
+ factor = 9;
+ break;
+ case 'T':
+ factor = 12;
+ break;
+ }
+
+ return num * Math.pow(10, factor);
+ },
+ type: "numeric"
+ });
+
+ $.tablesorter.addParser({
+ id: "withinSpanNumber",
+ is: function (s) {
+ return (/<span class="original"/).test(s);
+ },
+ format: function (s, table, html) {
+ var res = html.innerHTML.match(/<span(\s*style="display:none;"\s*)?\s*class="original">(.*)?<\/span>/);
+ return (res && res.length >= 3) ? res[2] : 0;
+ },
+ type: "numeric"
+ });
+
+ // faster zebra widget: no row visibility check, faster css class switching, no cssChildRow check
+ $.tablesorter.addWidget({
+ id: "fast-zebra",
+ format: function (table) {
+ if (table.config.debug) {
+ var time = new Date();
+ }
+ $("tr:even", table.tBodies[0])
+ .removeClass(table.config.widgetZebra.css[0])
+ .addClass(table.config.widgetZebra.css[1]);
+ $("tr:odd", table.tBodies[0])
+ .removeClass(table.config.widgetZebra.css[1])
+ .addClass(table.config.widgetZebra.css[0]);
+ if (table.config.debug) {
+ $.tablesorter.benchmark("Applying Fast-Zebra widget", time);
+ }
+ }
+ });
+});
diff --git a/js/server_status_variables.js b/js/server_status_variables.js
new file mode 100644
index 0000000000..410d8cd06e
--- /dev/null
+++ b/js/server_status_variables.js
@@ -0,0 +1,110 @@
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ *
+ *
+ * @package PhpMyAdmin
+ */
+
+/**
+ * Unbind all event handlers before tearing down a page
+ */
+AJAX.registerTeardown('server_status_variables.js', function () {
+ $('#filterAlert').unbind('change');
+ $('#filterText').unbind('keyup');
+ $('#filterCategory').unbind('change');
+ $('#dontFormat').unbind('change');
+});
+
+AJAX.registerOnload('server_status_variables.js', function () {
+ /*** Table sort tooltip ***/
+ PMA_tooltip(
+ $('table.sortable>thead>tr:first').find('th'),
+ 'th',
+ PMA_messages.strSortHint
+ );
+ initTableSorter('statustabs_allvars');
+
+ // Filters for status variables
+ var textFilter = null;
+ var alertFilter = $('#filterAlert').prop('checked');
+ var categoryFilter = $('#filterCategory').find(':selected').val();
+ var odd_row = false;
+ var text = ''; // Holds filter text
+
+ /* 3 Filtering functions */
+ $('#filterAlert').change(function () {
+ alertFilter = this.checked;
+ filterVariables();
+ });
+
+ $('#filterCategory').change(function () {
+ categoryFilter = $(this).val();
+ filterVariables();
+ });
+
+ $('#dontFormat').change(function () {
+ // Hiding the table while changing values speeds up the process a lot
+ $('#serverstatusvariables').hide();
+ $('#serverstatusvariables td.value span.original').toggle(this.checked);
+ $('#serverstatusvariables td.value span.formatted').toggle(! this.checked);
+ $('#serverstatusvariables').show();
+ }).trigger('change');
+
+ $('#filterText').keyup(function (e) {
+ var word = $(this).val().replace(/_/g, ' ');
+ if (word.length === 0) {
+ textFilter = null;
+ } else {
+ textFilter = new RegExp("(^| )" + word, 'i');
+ }
+ text = word;
+ filterVariables();
+ }).trigger('keyup');
+
+ /* Filters the status variables by name/category/alert in the variables tab */
+ function filterVariables() {
+ var useful_links = 0;
+ var section = text;
+
+ if (categoryFilter.length > 0) {
+ section = categoryFilter;
+ }
+
+ if (section.length > 1) {
+ $('#linkSuggestions span').each(function () {
+ if ($(this).attr('class').indexOf('status_' + section) != -1) {
+ useful_links++;
+ $(this).css('display', '');
+ } else {
+ $(this).css('display', 'none');
+ }
+ });
+ }
+
+ if (useful_links > 0) {
+ $('#linkSuggestions').css('display', '');
+ } else {
+ $('#linkSuggestions').css('display', 'none');
+ }
+
+ odd_row = false;
+ $('#serverstatusvariables th.name').each(function () {
+ if ((textFilter === null || textFilter.exec($(this).text())) &&
+ (! alertFilter || $(this).next().find('span.attention').length > 0) &&
+ (categoryFilter.length === 0 || $(this).parent().hasClass('s_' + categoryFilter))
+ ) {
+ odd_row = ! odd_row;
+ $(this).parent().css('display', '');
+ if (odd_row) {
+ $(this).parent().addClass('odd');
+ $(this).parent().removeClass('even');
+ } else {
+ $(this).parent().addClass('even');
+ $(this).parent().removeClass('odd');
+ }
+ } else {
+ $(this).parent().css('display', 'none');
+ }
+ });
+ }
+});
diff --git a/js/server_user_groups.js b/js/server_user_groups.js
new file mode 100644
index 0000000000..b5eb3f5cd5
--- /dev/null
+++ b/js/server_user_groups.js
@@ -0,0 +1,42 @@
+/**
+ * Unbind all event handlers before tearing down a page
+ */
+AJAX.registerTeardown('server_user_groups.js', function () {
+ $("a.deleteUserGroup.ajax").die('click');
+});
+
+/**
+ * Bind event handlers
+ */
+AJAX.registerOnload('server_user_groups.js', function () {
+
+ // update the checkall checkbox on Edit user group page
+ $(checkboxes_sel).trigger("change");
+
+ $("a.deleteUserGroup.ajax").live('click', function (event) {
+ event.preventDefault();
+ var $link = $(this);
+ var groupName = $link.parents('tr').find('td:first').text();
+ var buttonOptions = {};
+ buttonOptions[PMA_messages.strGo] = function () {
+ $(this).dialog("close");
+ $link.removeClass('ajax').trigger('click');
+ };
+ buttonOptions[PMA_messages.strClose] = function () {
+ $(this).dialog("close");
+ };
+ $('<div/>')
+ .attr('id', 'confirmUserGroupDeleteDialog')
+ .append($.sprintf(PMA_messages.strDropUserGroupWarning, escapeHtml(groupName)))
+ .dialog({
+ width: 300,
+ minWidth: 200,
+ modal: true,
+ buttons: buttonOptions,
+ title: PMA_messages.strConfirm,
+ close: function () {
+ $(this).remove();
+ }
+ });
+ });
+});
diff --git a/js/server_variables.js b/js/server_variables.js
new file mode 100644
index 0000000000..56370b8a70
--- /dev/null
+++ b/js/server_variables.js
@@ -0,0 +1,157 @@
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+
+/**
+ * Unbind all event handlers before tearing down a page
+ */
+AJAX.registerTeardown('server_variables.js', function () {
+ $('#serverVariables .var-row').unbind('hover');
+ $('#filterText').unbind('keyup');
+ $('a.editLink').die('click');
+ $('#serverVariables').find('.var-name').find('a img').remove();
+});
+
+AJAX.registerOnload('server_variables.js', function () {
+ var $editLink = $('a.editLink');
+ var $saveLink = $('a.saveLink');
+ var $cancelLink = $('a.cancelLink');
+ var $filterField = $('#filterText');
+
+ /* Show edit link on hover */
+ $('#serverVariables').delegate('.var-row', 'hover', function (event) {
+ if (event.type === 'mouseenter') {
+ var $elm = $(this).find('.var-value');
+ // Only add edit element if the element is not being edited
+ if ($elm.hasClass('editable') && ! $elm.hasClass('edit')) {
+ $elm.prepend($editLink.clone().show());
+ }
+ } else {
+ $(this).find('a.editLink').remove();
+ }
+ }).find('.var-name').find('a').append(
+ $('#docImage').clone().show()
+ );
+
+ /* Launches the variable editor */
+ $editLink.live('click', function (event) {
+ event.preventDefault();
+ editVariable(this);
+ });
+
+ /* Event handler for variables filter */
+ $filterField.keyup(function () {
+ var textFilter = null, val = $(this).val();
+ if (val.length !== 0) {
+ textFilter = new RegExp("(^| )" + val.replace(/_/g, ' '), 'i');
+ }
+ filterVariables(textFilter);
+ });
+
+ /* Trigger filtering of the list based on incoming variable name */
+ if ($filterField.val()) {
+ $filterField.trigger('keyup').select();
+ }
+
+ /* Filters the rows by the user given regexp */
+ function filterVariables(textFilter) {
+ var mark_next = false, $row, odd_row = false;
+ $('#serverVariables .var-row').not('.var-header').each(function () {
+ $row = $(this);
+ if (mark_next || textFilter === null ||
+ textFilter.exec($row.find('.var-name').text())
+ ) {
+ // If current global value is different from session value
+ // (has class diffSession), then display that one too
+ mark_next = $row.hasClass('diffSession') && ! mark_next;
+
+ odd_row = ! odd_row;
+ $row.css('display', '');
+ if (odd_row) {
+ $row.addClass('odd').removeClass('even');
+ } else {
+ $row.addClass('even').removeClass('odd');
+ }
+ } else {
+ $row.css('display', 'none');
+ }
+ });
+ }
+
+ /* Allows the user to edit a server variable */
+ function editVariable(link) {
+ var $cell = $(link).parent();
+ var varName = $cell.parent().find('.var-name').text().replace(/ /g, '_');
+ var $mySaveLink = $saveLink.clone().show();
+ var $myCancelLink = $cancelLink.clone().show();
+ var $msgbox = PMA_ajaxShowMessage();
+
+ $cell
+ .addClass('edit') // variable is being edited
+ .find('a.editLink')
+ .remove(); // remove edit link
+
+ $mySaveLink.click(function () {
+ var $msgbox = PMA_ajaxShowMessage(PMA_messages.strProcessingRequest);
+ $.get($(this).attr('href'), {
+ ajax_request: true,
+ type: 'setval',
+ varName: varName,
+ varValue: $cell.find('input').val()
+ }, function (data) {
+ if (data.success) {
+ $cell
+ .html(data.variable)
+ .data('content', data.variable);
+ PMA_ajaxRemoveMessage($msgbox);
+ } else {
+ PMA_ajaxShowMessage(data.error, false);
+ $cell.html($cell.data('content'));
+ }
+ $cell.removeClass('edit');
+ });
+ return false;
+ });
+
+ $myCancelLink.click(function () {
+ $cell
+ .html($cell.data('content'))
+ .removeClass('edit');
+ return false;
+ });
+
+ $.get($mySaveLink.attr('href'), {
+ ajax_request: true,
+ type: 'getval',
+ varName: varName
+ }, function (data) {
+ if (data.success === true) {
+ var $editor = $('<div />', {'class': 'serverVariableEditor'})
+ .append($myCancelLink)
+ .append(' ')
+ .append($mySaveLink)
+ .append(' ')
+ .append(
+ $('<div/>').append(
+ $('<input />', {type: 'text'}).val(data.message)
+ )
+ );
+ // Save and replace content
+ $cell
+ .data('content', $cell.html())
+ .html($editor)
+ .find('input')
+ .focus()
+ .keydown(function (event) { // Keyboard shortcuts
+ if (event.keyCode === 13) { // Enter key
+ $mySaveLink.trigger('click');
+ } else if (event.keyCode === 27) { // Escape key
+ $myCancelLink.trigger('click');
+ }
+ });
+ PMA_ajaxRemoveMessage($msgbox);
+ } else {
+ $cell.removeClass('edit');
+ PMA_ajaxShowMessage(data.error);
+ }
+ });
+ }
+});
diff --git a/js/sql.js b/js/sql.js
new file mode 100644
index 0000000000..5db1d75633
--- /dev/null
+++ b/js/sql.js
@@ -0,0 +1,492 @@
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * @fileoverview functions used wherever an sql query form is used
+ *
+ * @requires jQuery
+ * @requires js/functions.js
+ *
+ */
+
+var $data_a;
+
+/**
+ * decode a string URL_encoded
+ *
+ * @param string str
+ * @return string the URL-decoded string
+ */
+function PMA_urldecode(str)
+{
+ return decodeURIComponent(str.replace(/\+/g, '%20'));
+}
+
+/**
+ * endecode a string URL_decoded
+ *
+ * @param string str
+ * @return string the URL-encoded string
+ */
+function PMA_urlencode(str)
+{
+ return encodeURIComponent(str).replace(/\%20/g, '+');
+}
+
+/**
+ * Get the field name for the current field. Required to construct the query
+ * for grid editing
+ *
+ * @param $this_field jQuery object that points to the current field's tr
+ */
+function getFieldName($this_field)
+{
+
+ var this_field_index = $this_field.index();
+ // ltr or rtl direction does not impact how the DOM was generated
+ // check if the action column in the left exist
+ var left_action_exist = !$('#table_results').find('th:first').hasClass('draggable');
+ // number of column span for checkbox and Actions
+ var left_action_skip = left_action_exist ? $('#table_results').find('th:first').attr('colspan') - 1 : 0;
+ var field_name = $('#table_results').find('thead').find('th:eq(' + (this_field_index - left_action_skip) + ') a').text();
+ // happens when just one row (headings contain no a)
+ if (field_name === '') {
+ var $heading = $('#table_results').find('thead').find('th:eq(' + (this_field_index - left_action_skip) + ')').children('span');
+ // may contain column comment enclosed in a span - detach it temporarily to read the column name
+ var $tempColComment = $heading.children().detach();
+ field_name = $heading.text();
+ // re-attach the column comment
+ $heading.append($tempColComment);
+ }
+
+ field_name = $.trim(field_name);
+
+ return field_name;
+}
+
+/**
+ * Unbind all event handlers before tearing down a page
+ */
+AJAX.registerTeardown('sql.js', function () {
+ $('a.delete_row.ajax').die('click');
+ $('#bookmarkQueryForm').die('submit');
+ $('input#bkm_label').unbind('keyup');
+ $("#sqlqueryresults").die('makegrid');
+ $("#togglequerybox").unbind('click');
+ $("#button_submit_query").die('click');
+ $("input[name=bookmark_variable]").unbind("keypress");
+ $("#sqlqueryform.ajax").die('submit');
+ $("input[name=navig].ajax").die('click');
+ $("#pageselector").die('change');
+ $("#table_results.ajax").find("a[title=Sort]").die('click');
+ $("#displayOptionsForm.ajax").die('submit');
+ $('a.browse_foreign').die('click');
+ $('th.column_heading.pointer').die('hover');
+ $('th.column_heading.marker').die('click');
+});
+
+/**
+ * @description <p>Ajax scripts for sql and browse pages</p>
+ *
+ * Actions ajaxified here:
+ * <ul>
+ * <li>Retrieve results of an SQL query</li>
+ * <li>Paginate the results table</li>
+ * <li>Sort the results table</li>
+ * <li>Change table according to display options</li>
+ * <li>Grid editing of data</li>
+ * <li>Saving a bookmark</li>
+ * </ul>
+ *
+ * @name document.ready
+ * @memberOf jQuery
+ */
+AJAX.registerOnload('sql.js', function () {
+ // Delete row from SQL results
+ $('a.delete_row.ajax').live('click', function (e) {
+ e.preventDefault();
+ var question = $.sprintf(PMA_messages.strDoYouReally, $(this).closest('td').find('div').text());
+ var $link = $(this);
+ $link.PMA_confirm(question, $link.attr('href'), function (url) {
+ $msgbox = PMA_ajaxShowMessage();
+ $.get(url, {'ajax_request': true, 'is_js_confirmed': true}, function (data) {
+ if (data.success) {
+ PMA_ajaxShowMessage(data.message);
+ $link.closest('tr').remove();
+ } else {
+ PMA_ajaxShowMessage(data.error, false);
+ }
+ });
+ });
+ });
+
+ // Ajaxification for 'Bookmark this SQL query'
+ $('#bookmarkQueryForm').live('submit', function (e) {
+ e.preventDefault();
+ PMA_ajaxShowMessage();
+ $.post($(this).attr('action'), 'ajax_request=1&' + $(this).serialize(), function (data) {
+ if (data.success) {
+ PMA_ajaxShowMessage(data.message);
+ } else {
+ PMA_ajaxShowMessage(data.error, false);
+ }
+ });
+ });
+
+ /* Hides the bookmarkoptions checkboxes when the bookmark label is empty */
+ $('input#bkm_label').keyup(function () {
+ $('input#id_bkm_all_users, input#id_bkm_replace')
+ .parent()
+ .toggle($(this).val().length > 0);
+ }).trigger('keyup');
+
+ /**
+ * Attach the {@link makegrid} function to a custom event, which will be
+ * triggered manually everytime the table of results is reloaded
+ * @memberOf jQuery
+ */
+ $("#sqlqueryresults").live('makegrid', function () {
+ PMA_makegrid($('#table_results')[0]);
+ });
+
+ /**
+ * Append the "Show/Hide query box" message to the query input form
+ *
+ * @memberOf jQuery
+ * @name appendToggleSpan
+ */
+ // do not add this link more than once
+ if (! $('#sqlqueryform').find('a').is('#togglequerybox')) {
+ $('<a id="togglequerybox"></a>')
+ .html(PMA_messages.strHideQueryBox)
+ .appendTo("#sqlqueryform")
+ // initially hidden because at this point, nothing else
+ // appears under the link
+ .hide();
+
+ // Attach the toggling of the query box visibility to a click
+ $("#togglequerybox").bind('click', function () {
+ var $link = $(this);
+ $link.siblings().slideToggle("fast");
+ if ($link.text() == PMA_messages.strHideQueryBox) {
+ $link.text(PMA_messages.strShowQueryBox);
+ // cheap trick to add a spacer between the menu tabs
+ // and "Show query box"; feel free to improve!
+ $('#togglequerybox_spacer').remove();
+ $link.before('<br id="togglequerybox_spacer" />');
+ } else {
+ $link.text(PMA_messages.strHideQueryBox);
+ }
+ // avoid default click action
+ return false;
+ });
+ }
+
+
+ /**
+ * Event handler for sqlqueryform.ajax button_submit_query
+ *
+ * @memberOf jQuery
+ */
+ $("#button_submit_query").live('click', function (event) {
+ $(".success,.error").hide();
+ //hide already existing error or success message
+ var $form = $(this).closest("form");
+ // the Go button related to query submission was clicked,
+ // instead of the one related to Bookmarks, so empty the
+ // id_bookmark selector to avoid misinterpretation in
+ // import.php about what needs to be done
+ $form.find("select[name=id_bookmark]").val("");
+ // let normal event propagation happen
+ });
+
+ /**
+ * Event handler for hitting enter on sqlqueryform bookmark_variable
+ * (the Variable textfield in Bookmarked SQL query section)
+ *
+ * @memberOf jQuery
+ */
+ $("input[name=bookmark_variable]").bind("keypress", function (event) {
+ // force the 'Enter Key' to implicitly click the #button_submit_bookmark
+ var keycode = (event.keyCode ? event.keyCode : (event.which ? event.which : event.charCode));
+ if (keycode == 13) { // keycode for enter key
+ // When you press enter in the sqlqueryform, which
+ // has 2 submit buttons, the default is to run the
+ // #button_submit_query, because of the tabindex
+ // attribute.
+ // This submits #button_submit_bookmark instead,
+ // because when you are in the Bookmarked SQL query
+ // section and hit enter, you expect it to do the
+ // same action as the Go button in that section.
+ $("#button_submit_bookmark").click();
+ return false;
+ } else {
+ return true;
+ }
+ });
+
+ /**
+ * Ajax Event handler for 'SQL Query Submit'
+ *
+ * @see PMA_ajaxShowMessage()
+ * @memberOf jQuery
+ * @name sqlqueryform_submit
+ */
+ $("#sqlqueryform.ajax").live('submit', function (event) {
+ event.preventDefault();
+
+ var $form = $(this);
+ if (codemirror_editor) {
+ $form[0].elements['sql_query'].value = codemirror_editor.getValue();
+ }
+ if (! checkSqlQuery($form[0])) {
+ return false;
+ }
+
+ // remove any div containing a previous error message
+ $('div.error').remove();
+
+ var $msgbox = PMA_ajaxShowMessage();
+ var $sqlqueryresults = $('#sqlqueryresults');
+
+ PMA_prepareForAjaxRequest($form);
+
+ $.post($form.attr('action'), $form.serialize(), function (data) {
+ if (data.success === true) {
+ // success happens if the query returns rows or not
+ //
+ // fade out previous messages, if any
+ $('div.success, div.sqlquery_message').fadeOut();
+ if ($('#result_query').length) {
+ $('#result_query').remove();
+ }
+
+ // show a message that stays on screen
+ if (typeof data.action_bookmark != 'undefined') {
+ // view only
+ if ('1' == data.action_bookmark) {
+ $('#sqlquery').text(data.sql_query);
+ // send to codemirror if possible
+ setQuery(data.sql_query);
+ }
+ // delete
+ if ('2' == data.action_bookmark) {
+ $("#id_bookmark option[value='" + data.id_bookmark + "']").remove();
+ // if there are no bookmarked queries now (only the empty option),
+ // remove the bookmark section
+ if ($('#id_bookmark option').length == 1) {
+ $('#fieldsetBookmarkOptions').hide();
+ $('#fieldsetBookmarkOptionsFooter').hide();
+ }
+ }
+ $sqlqueryresults
+ .show()
+ .html(data.message);
+ } else if (typeof data.sql_query != 'undefined') {
+ $('<div class="sqlquery_message"></div>')
+ .html(data.sql_query)
+ .insertBefore('#sqlqueryform');
+ // unnecessary div that came from data.sql_query
+ $('div.notice').remove();
+ } else {
+ $sqlqueryresults
+ .show()
+ .html(data.message);
+ }
+ PMA_highlightSQL($('#result_query'));
+
+ if (typeof data.ajax_reload != 'undefined') {
+ if (data.ajax_reload.reload) {
+ if (data.ajax_reload.table_name) {
+ PMA_commonParams.set('table', data.ajax_reload.table_name);
+ PMA_commonActions.refreshMain();
+ } else {
+ PMA_reloadNavigation();
+ }
+ }
+ } else if (typeof data.reload != 'undefined') {
+ // this happens if a USE or DROP command was typed
+ PMA_commonActions.setDb(data.db);
+ var url;
+ if (data.db) {
+ if (data.table) {
+ url = 'table_sql.php';
+ } else {
+ url = 'db_sql.php';
+ }
+ } else {
+ url = 'server_sql.php';
+ }
+ PMA_commonActions.refreshMain(url, function () {
+ if ($('#result_query').length) {
+ $('#result_query').remove();
+ }
+ if (data.sql_query) {
+ $('<div id="result_query"></div>')
+ .html(data.sql_query)
+ .prependTo('#page_content');
+ PMA_highlightSQL($('#page_content'));
+ }
+ });
+ }
+
+ $sqlqueryresults.show().trigger('makegrid');
+ $('#togglequerybox').show();
+ PMA_init_slider();
+
+ if (typeof data.action_bookmark == 'undefined') {
+ if ($('#sqlqueryform input[name="retain_query_box"]').is(':checked') !== true) {
+ if ($("#togglequerybox").siblings(":visible").length > 0) {
+ $("#togglequerybox").trigger('click');
+ }
+ }
+ }
+ } else if (data.success === false) {
+ // show an error message that stays on screen
+ $('#sqlqueryform').before(data.error);
+ $sqlqueryresults.hide();
+ }
+ PMA_ajaxRemoveMessage($msgbox);
+ }); // end $.post()
+ }); // end SQL Query submit
+
+ /**
+ * Paginate results with Page Selector dropdown
+ * @memberOf jQuery
+ * @name paginate_dropdown_change
+ */
+ $("#pageselector").live('change', function (event) {
+ var $form = $(this).parent("form");
+ $form.submit();
+ }); // end Paginate results with Page Selector
+
+ /**
+ * Ajax Event handler for the display options
+ * @memberOf jQuery
+ * @name displayOptionsForm_submit
+ */
+ $("#displayOptionsForm.ajax").live('submit', function (event) {
+ event.preventDefault();
+
+ $form = $(this);
+
+ $.post($form.attr('action'), $form.serialize() + '&ajax_request=true', function (data) {
+ $("#sqlqueryresults")
+ .html(data.message)
+ .trigger('makegrid');
+ PMA_init_slider();
+ }); // end $.post()
+ }); //end displayOptionsForm handler
+}); // end $()
+
+
+/**
+ * Starting from some th, change the class of all td under it.
+ * If isAddClass is specified, it will be used to determine whether to add or remove the class.
+ */
+function PMA_changeClassForColumn($this_th, newclass, isAddClass)
+{
+ // index 0 is the th containing the big T
+ var th_index = $this_th.index();
+ var has_big_t = !$this_th.closest('tr').children(':first').hasClass('column_heading');
+ // .eq() is zero-based
+ if (has_big_t) {
+ th_index--;
+ }
+ var $tds = $this_th.closest('table').find('tbody tr').find('td.data:eq(' + th_index + ')');
+ if (isAddClass === undefined) {
+ $tds.toggleClass(newclass);
+ } else {
+ $tds.toggleClass(newclass, isAddClass);
+ }
+}
+
+AJAX.registerOnload('sql.js', function () {
+
+ $('a.browse_foreign').live('click', function (e) {
+ e.preventDefault();
+ window.open(this.href, 'foreigners', 'width=640,height=240,scrollbars=yes,resizable=yes');
+ $anchor = $(this);
+ $anchor.addClass('browse_foreign_clicked');
+ });
+
+ /**
+ * vertical column highlighting in horizontal mode when hovering over the column header
+ */
+ $('th.column_heading.pointer').live('hover', function (e) {
+ PMA_changeClassForColumn($(this), 'hover', e.type == 'mouseenter');
+ });
+
+ /**
+ * vertical column marking in horizontal mode when clicking the column header
+ */
+ $('th.column_heading.marker').live('click', function () {
+ PMA_changeClassForColumn($(this), 'marked');
+ });
+
+ /**
+ * create resizable table
+ */
+ $("#sqlqueryresults").trigger('makegrid');
+});
+
+/*
+ * Profiling Chart
+ */
+function makeProfilingChart()
+{
+ if ($('#profilingchart').length === 0 ||
+ $('#profilingchart').html().length !== 0 ||
+ !$.jqplot || !$.jqplot.Highlighter || !$.jqplot.PieRenderer
+ ) {
+ return;
+ }
+
+ var data = [];
+ $.each(jQuery.parseJSON($('#profilingChartData').html()), function (key, value) {
+ data.push([key, parseFloat(value)]);
+ });
+
+ // Remove chart and data divs contents
+ $('#profilingchart').html('').show();
+ $('#profilingChartData').html('');
+
+ PMA_createProfilingChartJqplot('profilingchart', data);
+}
+
+/*
+ * initialize profiling data tables
+ */
+function initProfilingTables()
+{
+ if (!$.tablesorter) {
+ return;
+ }
+
+ $('#profiletable').tablesorter({
+ widgets: ['zebra'],
+ sortList: [[0, 0]],
+ textExtraction: function (node) {
+ if (node.children.length > 0) {
+ return node.children[0].innerHTML;
+ } else {
+ return node.innerHTML;
+ }
+ }
+ });
+
+ $('#profilesummarytable').tablesorter({
+ widgets: ['zebra'],
+ sortList: [[1, 1]],
+ textExtraction: function (node) {
+ if (node.children.length > 0) {
+ return node.children[0].innerHTML;
+ } else {
+ return node.innerHTML;
+ }
+ }
+ });
+}
+
+AJAX.registerOnload('sql.js', function () {
+ makeProfilingChart();
+ initProfilingTables();
+});
diff --git a/js/tbl_change.js b/js/tbl_change.js
new file mode 100644
index 0000000000..47e9c4ceb4
--- /dev/null
+++ b/js/tbl_change.js
@@ -0,0 +1,526 @@
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * @fileoverview function used in table data manipulation pages
+ *
+ * @requires jQuery
+ * @requires jQueryUI
+ * @requires js/functions.js
+ *
+ */
+
+/**
+ * Modify form controls when the "NULL" checkbox is checked
+ *
+ * @param theType string the MySQL field type
+ * @param urlField string the urlencoded field name - OBSOLETE
+ * @param md5Field string the md5 hashed field name
+ * @param multi_edit string the multi_edit row sequence number
+ *
+ * @return boolean always true
+ */
+function nullify(theType, urlField, md5Field, multi_edit)
+{
+ var rowForm = document.forms['insertForm'];
+
+ if (typeof(rowForm.elements['funcs' + multi_edit + '[' + md5Field + ']']) != 'undefined') {
+ rowForm.elements['funcs' + multi_edit + '[' + md5Field + ']'].selectedIndex = -1;
+ }
+
+ // "ENUM" field with more than 20 characters
+ if (theType == 1) {
+ rowForm.elements['fields' + multi_edit + '[' + md5Field + ']'][1].selectedIndex = -1;
+ }
+ // Other "ENUM" field
+ else if (theType == 2) {
+ var elts = rowForm.elements['fields' + multi_edit + '[' + md5Field + ']'];
+ // when there is just one option in ENUM:
+ if (elts.checked) {
+ elts.checked = false;
+ } else {
+ var elts_cnt = elts.length;
+ for (var i = 0; i < elts_cnt; i++) {
+ elts[i].checked = false;
+ } // end for
+
+ } // end if
+ }
+ // "SET" field
+ else if (theType == 3) {
+ rowForm.elements['fields' + multi_edit + '[' + md5Field + '][]'].selectedIndex = -1;
+ }
+ // Foreign key field (drop-down)
+ else if (theType == 4) {
+ rowForm.elements['fields' + multi_edit + '[' + md5Field + ']'].selectedIndex = -1;
+ }
+ // foreign key field (with browsing icon for foreign values)
+ else if (theType == 6) {
+ rowForm.elements['fields' + multi_edit + '[' + md5Field + ']'].value = '';
+ }
+ // Other field types
+ else /*if (theType == 5)*/ {
+ rowForm.elements['fields' + multi_edit + '[' + md5Field + ']'].value = '';
+ } // end if... else if... else
+
+ return true;
+} // end of the 'nullify()' function
+
+
+/**
+ * javascript DateTime format validation.
+ * its used to prevent adding default (0000-00-00 00:00:00) to database when user enter wrong values
+ * Start of validation part
+ */
+//function checks the number of days in febuary
+function daysInFebruary(year)
+{
+ return (((year % 4 === 0) && (((year % 100 !== 0)) || (year % 400 === 0))) ? 29 : 28);
+}
+//function to convert single digit to double digit
+function fractionReplace(num)
+{
+ num = parseInt(num, 10);
+ return num >= 1 && num <= 9 ? '0' + num : '00';
+}
+
+/* function to check the validity of date
+* The following patterns are accepted in this validation (accepted in mysql as well)
+* 1) 2001-12-23
+* 2) 2001-1-2
+* 3) 02-12-23
+* 4) And instead of using '-' the following punctuations can be used (+,.,*,^,@,/) All these are accepted by mysql as well. Therefore no issues
+*/
+function isDate(val, tmstmp)
+{
+ val = val.replace(/[.|*|^|+|//|@]/g, '-');
+ var arrayVal = val.split("-");
+ for (var a = 0; a < arrayVal.length; a++) {
+ if (arrayVal[a].length == 1) {
+ arrayVal[a] = fractionReplace(arrayVal[a]);
+ }
+ }
+ val = arrayVal.join("-");
+ var pos = 2;
+ var dtexp = new RegExp(/^([0-9]{4})-(((01|03|05|07|08|10|12)-((0[0-9])|([1-2][0-9])|(3[0-1])))|((02|04|06|09|11)-((0[0-9])|([1-2][0-9])|30)))$/);
+ if (val.length == 8) {
+ pos = 0;
+ }
+ if (dtexp.test(val)) {
+ var month = parseInt(val.substring(pos + 3, pos + 5), 10);
+ var day = parseInt(val.substring(pos + 6, pos + 8), 10);
+ var year = parseInt(val.substring(0, pos + 2), 10);
+ if (month == 2 && day > daysInFebruary(year)) {
+ return false;
+ }
+ if (val.substring(0, pos + 2).length == 2) {
+ year = parseInt("20" + val.substring(0, pos + 2), 10);
+ }
+ if (tmstmp === true) {
+ if (year < 1978) {
+ return false;
+ }
+ if (year > 2038 || (year > 2037 && day > 19 && month >= 1) || (year > 2037 && month > 1)) {
+ return false;
+ }
+ }
+ } else {
+ return false;
+ }
+ return true;
+}
+
+/* function to check the validity of time
+* The following patterns are accepted in this validation (accepted in mysql as well)
+* 1) 2:3:4
+* 2) 2:23:43
+* 3) 2:23:43.123456
+*/
+function isTime(val)
+{
+ var arrayVal = val.split(":");
+ for (var a = 0, l = arrayVal.length; a < l; a++) {
+ if (arrayVal[a].length == 1) {
+ arrayVal[a] = fractionReplace(arrayVal[a]);
+ }
+ }
+ val = arrayVal.join(":");
+ var tmexp = new RegExp(/^(([0-1][0-9])|(2[0-3])):((0[0-9])|([1-5][0-9])):((0[0-9])|([1-5][0-9]))(\.[0-9]{1,6}){0,1}$/);
+ return tmexp.test(val);
+}
+
+function verificationsAfterFieldChange(urlField, multi_edit, theType)
+{
+ var evt = window.event || arguments.callee.caller.arguments[0];
+ var target = evt.target || evt.srcElement;
+
+ //To generate the textbox that can take the salt
+ var new_salt_box = "<br><input type=text name=salt[multi_edit][" + multi_edit + "][" + urlField + "]" +
+ " id=salt_" + target.id + " placeholder='enter Salt'>";
+
+ //If AES_ENCRYPT is Selected then append the new textbox for salt
+ if (target.value == "AES_ENCRYPT" && !($("#salt_" + target.id).length)) {
+ $("input[name='fields[multi_edit][" + multi_edit + "][" + urlField + "]']").after(new_salt_box);
+ } else {
+ //The value of the select is no longer AES_ENCRYPT, remove the textbox for salt
+ $("#salt_" + target.id).remove();
+ }
+
+ // Unchecks the corresponding "NULL" control
+ $("input[name='fields_null[multi_edit][" + multi_edit + "][" + urlField + "]']").prop('checked', false);
+
+ // Unchecks the Ignore checkbox for the current row
+ $("input[name='insert_ignore_" + multi_edit + "']").prop('checked', false);
+ var $this_input = $("input[name='fields[multi_edit][" + multi_edit + "][" + urlField + "]']");
+
+ // Does this field come from datepicker?
+ if ($this_input.data('comes_from') == 'datepicker') {
+ // Yes, so do not validate because the final value is not yet in
+ // the field and hopefully the datepicker returns a valid date+time
+ $this_input.removeClass("invalid_value");
+ return true;
+ }
+
+ if (target.name.substring(0, 6) == "fields") {
+ // validate for date time
+ if (theType == "datetime" || theType == "time" || theType == "date" || theType == "timestamp") {
+ $this_input.removeClass("invalid_value");
+ var dt_value = $this_input.val();
+ if (theType == "date") {
+ if (! isDate(dt_value)) {
+ $this_input.addClass("invalid_value");
+ return false;
+ }
+ } else if (theType == "time") {
+ if (! isTime(dt_value)) {
+ $this_input.addClass("invalid_value");
+ return false;
+ }
+ } else if (theType == "datetime" || theType == "timestamp") {
+ var tmstmp = false;
+ if (dt_value == "CURRENT_TIMESTAMP") {
+ return true;
+ }
+ if (theType == "timestamp") {
+ tmstmp = true;
+ }
+ if (dt_value == "0000-00-00 00:00:00") {
+ return true;
+ }
+ var dv = dt_value.indexOf(" ");
+ if (dv == -1) {
+ $this_input.addClass("invalid_value");
+ return false;
+ } else {
+ if (! (isDate(dt_value.substring(0, dv), tmstmp) && isTime(dt_value.substring(dv + 1)))) {
+ $this_input.addClass("invalid_value");
+ return false;
+ }
+ }
+ }
+ }
+ //validate for integer type
+ if (theType.substring(0, 3) == "int") {
+ $this_input.removeClass("invalid_value");
+ if (isNaN($this_input.val())) {
+ $this_input.addClass("invalid_value");
+ return false;
+ }
+ }
+ }
+}
+ /* End of datetime validation*/
+
+
+/**
+ * Unbind all event handlers before tearing down a page
+ */
+AJAX.registerTeardown('tbl_change.js', function () {
+ $('span.open_gis_editor').die('click');
+ $("input[name='gis_data[save]']").die('click');
+ $('input.checkbox_null').die('click');
+ $('select[name="submit_type"]').unbind('change');
+ $("#insert_rows").die('change');
+});
+
+/**
+ * Ajax handlers for Change Table page
+ *
+ * Actions Ajaxified here:
+ * Submit Data to be inserted into the table.
+ * Restart insertion with 'N' rows.
+ */
+AJAX.registerOnload('tbl_change.js', function () {
+ $.datepicker.initialized = false;
+
+ $('span.open_gis_editor').live('click', function (event) {
+ event.preventDefault();
+
+ var $span = $(this);
+ // Current value
+ var value = $span.parent('td').children("input[type='text']").val();
+ // Field name
+ var field = $span.parents('tr').children('td:first').find("input[type='hidden']").val();
+ // Column type
+ var type = $span.parents('tr').find('span.column_type').text();
+ // Names of input field and null checkbox
+ var input_name = $span.parent('td').children("input[type='text']").attr('name');
+ //Token
+ var token = $("input[name='token']").val();
+
+ openGISEditor();
+ if (!gisEditorLoaded) {
+ loadJSAndGISEditor(value, field, type, input_name, token);
+ } else {
+ loadGISEditor(value, field, type, input_name, token);
+ }
+ });
+
+ /**
+ * Uncheck the null checkbox as geometry data is placed on the input field
+ */
+ $("input[name='gis_data[save]']").live('click', function (event) {
+ var input_name = $('form#gis_data_editor_form').find("input[name='input_name']").val();
+ var $null_checkbox = $("input[name='" + input_name + "']").parents('tr').find('.checkbox_null');
+ $null_checkbox.prop('checked', false);
+ });
+
+ /**
+ * Handles all current checkboxes for Null; this only takes care of the
+ * checkboxes on currently displayed rows as the rows generated by
+ * "Continue insertion" are handled in the "Continue insertion" code
+ *
+ */
+ $('input.checkbox_null').live('click', function (e) {
+ nullify(
+ // use hidden fields populated by tbl_change.php
+ $(this).siblings('.nullify_code').val(),
+ $(this).closest('tr').find('input:hidden').first().val(),
+ $(this).siblings('.hashed_field').val(),
+ $(this).siblings('.multi_edit').val()
+ );
+ });
+
+
+ /**
+ * Reset the auto_increment column to 0 when selecting any of the
+ * insert options in submit_type-dropdown. Only perform the reset
+ * when we are in edit-mode, and not in insert-mode(no previous value
+ * available).
+ */
+ $('select[name="submit_type"]').bind('change', function (e) {
+ var $table = $('table.insertRowTable');
+ var auto_increment_column = $table.find('input[name^="auto_increment"]').attr('name');
+ if (auto_increment_column) {
+ var prev_value_field = $table.find('input[name="' + auto_increment_column.replace('auto_increment', 'fields_prev') + '"]');
+ var value_field = $table.find('input[name="' + auto_increment_column.replace('auto_increment', 'fields') + '"]');
+ var previous_value = $(prev_value_field).val();
+ if (previous_value !== undefined) {
+ if ($(this).val() == 'insert' || $(this).val() == 'insertignore' || $(this).val() == 'showinsert') {
+ $(value_field).val(0);
+ } else {
+ $(value_field).val(previous_value);
+ }
+ }
+ }
+ });
+
+ /**
+ * Continue Insertion form
+ */
+ $("#insert_rows").live('change', function (event) {
+ event.preventDefault();
+
+ /**
+ * @var curr_rows Number of current insert rows already on page
+ */
+ var curr_rows = $("table.insertRowTable").length;
+ /**
+ * @var target_rows Number of rows the user wants
+ */
+ var target_rows = $("#insert_rows").val();
+
+ // remove all datepickers
+ $('input.datefield, input.datetimefield').each(function () {
+ $(this).datepicker('destroy');
+ });
+
+ if (curr_rows < target_rows) {
+ while (curr_rows < target_rows) {
+
+ /**
+ * @var $last_row Object referring to the last row
+ */
+ var $last_row = $("#insertForm").find(".insertRowTable:last");
+
+ // need to access this at more than one level
+ // (also needs improvement because it should be calculated
+ // just once per cloned row, not once per column)
+ var new_row_index = 0;
+
+ //Clone the insert tables
+ $last_row
+ .clone()
+ .insertBefore("#actions_panel")
+ .find('input[name*=multi_edit],select[name*=multi_edit],textarea[name*=multi_edit]')
+ .each(function () {
+
+ var $this_element = $(this);
+ /**
+ * Extract the index from the name attribute for all input/select fields and increment it
+ * name is of format funcs[multi_edit][10][<long random string of alphanum chars>]
+ */
+
+ /**
+ * @var this_name String containing name of the input/select elements
+ */
+ var this_name = $this_element.attr('name');
+ /** split {@link this_name} at [10], so we have the parts that can be concatenated later */
+ var name_parts = this_name.split(/\[\d+\]/);
+ /** extract the [10] from {@link name_parts} */
+ var old_row_index_string = this_name.match(/\[\d+\]/)[0];
+ /** extract 10 - had to split into two steps to accomodate double digits */
+ var old_row_index = parseInt(old_row_index_string.match(/\d+/)[0], 10);
+
+ /** calculate next index i.e. 11 */
+ new_row_index = old_row_index + 1;
+ /** generate the new name i.e. funcs[multi_edit][11][foobarbaz] */
+ var new_name = name_parts[0] + '[' + new_row_index + ']' + name_parts[1];
+
+ var hashed_field = name_parts[1].match(/\[(.+)\]/)[1];
+ $this_element.attr('name', new_name);
+
+ // handle input text fields and textareas
+ if ($this_element.is('.textfield') || $this_element.is('.char')) {
+ // do not remove the 'value' attribute for ENUM columns
+ if ($this_element.closest('tr').find('span.column_type').html() != 'enum') {
+ $this_element.val($this_element.closest('tr').find('span.default_value').html());
+ }
+ $this_element
+ .unbind('change')
+ // Remove onchange attribute that was placed
+ // by tbl_change.php; it refers to the wrong row index
+ .attr('onchange', null)
+ // Keep these values to be used when the element
+ // will change
+ .data('hashed_field', hashed_field)
+ .data('new_row_index', new_row_index)
+ .bind('change', function (e) {
+ var $changed_element = $(this);
+ verificationsAfterFieldChange(
+ $changed_element.data('hashed_field'),
+ $changed_element.data('new_row_index'),
+ $changed_element.closest('tr').find('span.column_type').html()
+ );
+ });
+ }
+
+ if ($this_element.is('.checkbox_null')) {
+ $this_element
+ // this event was bound earlier by jQuery but
+ // to the original row, not the cloned one, so unbind()
+ .unbind('click')
+ // Keep these values to be used when the element
+ // will be clicked
+ .data('hashed_field', hashed_field)
+ .data('new_row_index', new_row_index)
+ .bind('click', function (e) {
+ var $changed_element = $(this);
+ nullify(
+ $changed_element.siblings('.nullify_code').val(),
+ $this_element.closest('tr').find('input:hidden').first().val(),
+ $changed_element.data('hashed_field'),
+ '[multi_edit][' + $changed_element.data('new_row_index') + ']'
+ );
+ });
+ }
+ }) // end each
+ .end()
+ .find('.foreign_values_anchor')
+ .each(function () {
+ var $anchor = $(this);
+ var new_value = 'rownumber=' + new_row_index;
+ // needs improvement in case something else inside
+ // the href contains this pattern
+ var new_href = $anchor.attr('href').replace(/rownumber=\d+/, new_value);
+ $anchor.attr('href', new_href);
+ });
+
+ //Insert/Clone the ignore checkboxes
+ if (curr_rows == 1) {
+ $('<input id="insert_ignore_1" type="checkbox" name="insert_ignore_1" checked="checked" />')
+ .insertBefore("table.insertRowTable:last")
+ .after('<label for="insert_ignore_1">' + PMA_messages.strIgnore + '</label>');
+ } else {
+
+ /**
+ * @var $last_checkbox Object reference to the last checkbox in #insertForm
+ */
+ var $last_checkbox = $("#insertForm").children('input:checkbox:last');
+
+ /** name of {@link $last_checkbox} */
+ var last_checkbox_name = $last_checkbox.attr('name');
+ /** index of {@link $last_checkbox} */
+ var last_checkbox_index = parseInt(last_checkbox_name.match(/\d+/), 10);
+ /** name of new {@link $last_checkbox} */
+ var new_name = last_checkbox_name.replace(/\d+/, last_checkbox_index + 1);
+
+ $last_checkbox
+ .clone()
+ .attr({'id': new_name, 'name': new_name})
+ .prop('checked', true)
+ .add('label[for^=insert_ignore]:last')
+ .clone()
+ .attr('for', new_name)
+ .before('<br />')
+ .insertBefore("table.insertRowTable:last");
+ }
+ curr_rows++;
+ }
+ // recompute tabindex for text fields and other controls at footer;
+ // IMO it's not really important to handle the tabindex for
+ // function and Null
+ var tabindex = 0;
+ $('.textfield, .char, textarea')
+ .each(function () {
+ tabindex++;
+ $(this).attr('tabindex', tabindex);
+ // update the IDs of textfields to ensure that they are unique
+ $(this).attr('id', "field_" + tabindex + "_3");
+ });
+ $('.control_at_footer')
+ .each(function () {
+ tabindex++;
+ $(this).attr('tabindex', tabindex);
+ });
+ } else if (curr_rows > target_rows) {
+ while (curr_rows > target_rows) {
+ $("input[id^=insert_ignore]:last")
+ .nextUntil("fieldset")
+ .andSelf()
+ .remove();
+ curr_rows--;
+ }
+ }
+ });
+ // Add all the required datepickers back
+ addDateTimePicker();
+});
+
+function changeValueFieldType(elem, searchIndex)
+{
+ var fieldsValue = $("select#fieldID_" + searchIndex);
+ if (0 == fieldsValue.size()) {
+ return;
+ }
+
+ var type = $(elem).val();
+ if (
+ 'IN (...)' == type
+ || 'NOT IN (...)' == type
+ || 'BETWEEN' == type
+ || 'NOT BETWEEN' == type
+ ) {
+ $("#fieldID_" + searchIndex).attr('multiple', '');
+ } else {
+ $("#fieldID_" + searchIndex).removeAttr('multiple');
+ }
+}
diff --git a/js/tbl_chart.js b/js/tbl_chart.js
new file mode 100644
index 0000000000..3e11725aca
--- /dev/null
+++ b/js/tbl_chart.js
@@ -0,0 +1,325 @@
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+
+var chart_data = {};
+var temp_chart_title;
+
+var currentChart = null;
+var currentSettings = null;
+
+function extractDate(dateString) {
+ var matches, match;
+ var dateTimeRegExp = /[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}/;
+ var dateRegExp = /[0-9]{4}-[0-9]{2}-[0-9]{2}/;
+
+ matches = dateTimeRegExp.exec(dateString);
+ if (matches !== null && matches.length > 0) {
+ match = matches[0];
+ return new Date(match.substr(0, 4), match.substr(5, 2), match.substr(8, 2), match.substr(11, 2), match.substr(14, 2), match.substr(17, 2));
+ } else {
+ matches = dateRegExp.exec(dateString);
+ if (matches !== null && matches.length > 0) {
+ match = matches[0];
+ return new Date(match.substr(0, 4), match.substr(5, 2), match.substr(8, 2));
+ }
+ }
+ return null;
+}
+
+function PMA_queryChart(data, columnNames, settings) {
+ if ($('#querychart').length === 0) {
+ return;
+ }
+
+ var jqPlotSettings = {
+ title : {
+ text : settings.title,
+ escapeHtml: true
+ },
+ grid : {
+ drawBorder : false,
+ shadow : false,
+ background : 'rgba(0,0,0,0)'
+ },
+ legend : {
+ show : true,
+ placement : 'outsideGrid',
+ location : 'e'
+ },
+ axes : {
+ xaxis : {
+ label : settings.xaxisLabel
+ },
+ yaxis : {
+ label : settings.yaxisLabel
+ }
+ },
+ stackSeries : settings.stackSeries,
+ highlighter: {
+ show: true,
+ showTooltip: true,
+ tooltipAxes: 'xy'
+ }
+ };
+
+ // create the chart
+ var factory = new JQPlotChartFactory();
+ var chart = factory.createChart(settings.type, "querychart");
+
+ // create the data table and add columns
+ var dataTable = new DataTable();
+ if (settings.type == 'timeline') {
+ dataTable.addColumn(ColumnType.DATE, columnNames[settings.mainAxis]);
+ } else {
+ dataTable.addColumn(ColumnType.STRING, columnNames[settings.mainAxis]);
+ }
+ $.each(settings.selectedSeries, function (index, element) {
+ dataTable.addColumn(ColumnType.NUMBER, columnNames[element]);
+ });
+
+ // set data to the data table
+ var columnsToExtract = [ settings.mainAxis ];
+ $.each(settings.selectedSeries, function (index, element) {
+ columnsToExtract.push(element);
+ });
+ var values = [], newRow, row, col;
+ for (var i = 0; i < data.length; i++) {
+ row = data[i];
+ newRow = [];
+ for (var j = 0; j < columnsToExtract.length; j++) {
+ col = columnNames[columnsToExtract[j]];
+ if (j === 0) {
+ if (settings.type == 'timeline') { // first column is date type
+ newRow.push(extractDate(row[col]));
+ } else { // first column is string type
+ newRow.push(row[col]);
+ }
+ } else { // subsequent columns are of type, number
+ newRow.push(parseFloat(row[col]));
+ }
+ }
+ values.push(newRow);
+ }
+ dataTable.setData(values);
+
+ // draw the chart and return the chart object
+ chart.draw(dataTable, jqPlotSettings);
+ return chart;
+}
+
+function drawChart() {
+ currentSettings.width = $('#resizer').width() - 20;
+ currentSettings.height = $('#resizer').height() - 20;
+
+ // todo: a better way using .redraw() ?
+ if (currentChart !== null) {
+ currentChart.destroy();
+ }
+
+ var columnNames = [];
+ $('select[name="chartXAxis"] option').each(function () {
+ columnNames.push($(this).text());
+ });
+ try {
+ currentChart = PMA_queryChart(chart_data, columnNames, currentSettings);
+ } catch (err) {
+ PMA_ajaxShowMessage(err.message, false);
+ }
+}
+
+function getSelectedSeries() {
+ var val = $('select[name="chartSeries"]').val() || [];
+ var ret = [];
+ $.each(val, function (i, v) {
+ ret.push(parseInt(v, 10));
+ });
+ return ret;
+}
+
+/**
+ * Unbind all event handlers before tearing down a page
+ */
+AJAX.registerTeardown('tbl_chart.js', function () {
+ $('input[name="chartType"]').unbind('click');
+ $('input[name="barStacked"]').unbind('click');
+ $('input[name="chartTitle"]').unbind('focus').unbind('keyup').unbind('blur');
+ $('select[name="chartXAxis"]').unbind('change');
+ $('select[name="chartSeries"]').unbind('change');
+ $('input[name="xaxis_label"]').unbind('keyup');
+ $('input[name="yaxis_label"]').unbind('keyup');
+ $('#resizer').unbind('resizestop');
+});
+
+AJAX.registerOnload('tbl_chart.js', function () {
+
+ // from jQuery UI
+ $('#resizer').resizable({
+ minHeight: 240,
+ minWidth: 300
+ })
+ .width($('#div_view_options').width() - 50);
+
+ $('#resizer').bind('resizestop', function (event, ui) {
+ // make room so that the handle will still appear
+ $('#querychart').height($('#resizer').height() * 0.96);
+ $('#querychart').width($('#resizer').width() * 0.96);
+ currentChart.redraw({
+ resetAxes : true
+ });
+ });
+
+ currentSettings = {
+ type : 'line',
+ width : $('#resizer').width() - 20,
+ height : $('#resizer').height() - 20,
+ xaxisLabel : $('input[name="xaxis_label"]').val(),
+ yaxisLabel : $('input[name="yaxis_label"]').val(),
+ title : $('input[name="chartTitle"]').val(),
+ stackSeries : false,
+ mainAxis : parseInt($('select[name="chartXAxis"]').val(), 10),
+ selectedSeries : getSelectedSeries()
+ };
+
+ // handle chart type changes
+ $('input[name="chartType"]').click(function () {
+ currentSettings.type = $(this).val();
+ drawChart();
+ if ($(this).val() == 'bar' || $(this).val() == 'column' ||
+ $(this).val() == 'line' || $(this).val() == 'area' ||
+ $(this).val() == 'timeline' || $(this).val() == 'spline'
+ ) {
+ $('span.barStacked').show();
+ } else {
+ $('span.barStacked').hide();
+ }
+ });
+
+ // handle stacking for bar, column and area charts
+ $('input[name="barStacked"]').click(function () {
+ if (this.checked) {
+ $.extend(true, currentSettings, {stackSeries : true});
+ } else {
+ $.extend(true, currentSettings, {stackSeries : false});
+ }
+ drawChart();
+ });
+
+ // handle changes in chart title
+ $('input[name="chartTitle"]').focus(function () {
+ temp_chart_title = $(this).val();
+ }).keyup(function () {
+ var title = $(this).val();
+ if (title.length === 0) {
+ title = ' ';
+ }
+ currentSettings.title = $('input[name="chartTitle"]').val();
+ drawChart();
+ }).blur(function () {
+ if ($(this).val() != temp_chart_title) {
+ drawChart();
+ }
+ });
+
+ var dateTimeCols = [];
+ var vals = $('input[name="dateTimeCols"]').val().split(' ');
+ $.each(vals, function (i, v) {
+ dateTimeCols.push(parseInt(v, 10));
+ });
+
+ // handle changing the x-axis
+ $('select[name="chartXAxis"]').change(function () {
+ currentSettings.mainAxis = parseInt($(this).val(), 10);
+ if (dateTimeCols.indexOf(currentSettings.mainAxis) != -1) {
+ $('span.span_timeline').show();
+ } else {
+ $('span.span_timeline').hide();
+ if (currentSettings.type == 'timeline') {
+ $('input#radio_line').prop('checked', true);
+ currentSettings.type = 'line';
+ }
+ }
+ var xaxis_title = $(this).children('option:selected').text();
+ $('input[name="xaxis_label"]').val(xaxis_title);
+ currentSettings.xaxisLabel = xaxis_title;
+ drawChart();
+ });
+
+ // handle changing the selected data series
+ $('select[name="chartSeries"]').change(function () {
+ currentSettings.selectedSeries = getSelectedSeries();
+ var yaxis_title;
+ if (currentSettings.selectedSeries.length == 1) {
+ $('span.span_pie').show();
+ yaxis_title = $(this).children('option:selected').text();
+ } else {
+ $('span.span_pie').hide();
+ if (currentSettings.type == 'pie') {
+ $('input#radio_line').prop('checked', true);
+ currentSettings.type = 'line';
+ }
+ yaxis_title = PMA_messages.strYValues;
+ }
+ $('input[name="yaxis_label"]').val(yaxis_title);
+ currentSettings.yaxisLabel = yaxis_title;
+ drawChart();
+ });
+
+ // handle manual changes to the chart axis labels
+ $('input[name="xaxis_label"]').keyup(function () {
+ currentSettings.xaxisLabel = $(this).val();
+ drawChart();
+ });
+ $('input[name="yaxis_label"]').keyup(function () {
+ currentSettings.yaxisLabel = $(this).val();
+ drawChart();
+ });
+
+ $("#tblchartform").submit();
+});
+
+/**
+ * Ajax Event handler for 'Go' button click
+ *
+ */
+$("#tblchartform").live('submit', function (event) {
+ if (!checkFormElementInRange(this, 'session_max_rows', PMA_messages.strNotValidRowNumber, 1) ||
+ !checkFormElementInRange(this, 'pos', PMA_messages.strNotValidRowNumber, 0 - 1)
+ ) {
+ return false;
+ }
+
+ var $form = $(this);
+ if (codemirror_editor) {
+ $form[0].elements['sql_query'].value = codemirror_editor.getValue();
+ }
+ if (!checkSqlQuery($form[0])) {
+ return false;
+ }
+ // remove any div containing a previous error message
+ $('.error').remove();
+ var $msgbox = PMA_ajaxShowMessage();
+ PMA_prepareForAjaxRequest($form);
+
+ $.post($form.attr('action'), $form.serialize(), function (data) {
+ if (data.success === true) {
+ $('.success').fadeOut();
+ if (typeof data.chartData != 'undefined') {
+ chart_data = jQuery.parseJSON(data.chartData);
+ drawChart();
+ $('div#querychart').height($('div#resizer').height() * 0.96);
+ $('div#querychart').width($('div#resizer').width() * 0.96);
+ currentChart.redraw({
+ resetAxes : true
+ });
+ $('#querychart').show();
+ }
+ } else {
+ PMA_ajaxRemoveMessage($msgbox);
+ PMA_ajaxShowMessage(data.error, false);
+ chart_data = null;
+ drawChart();
+ }
+ PMA_ajaxRemoveMessage($msgbox);
+ }, "json"); // end $.post()
+
+ return false;
+}); // end
diff --git a/js/tbl_find_replace.js b/js/tbl_find_replace.js
new file mode 100644
index 0000000000..975648f1cf
--- /dev/null
+++ b/js/tbl_find_replace.js
@@ -0,0 +1,47 @@
+/**
+ * Unbind all event handlers before tearing down a page
+ */
+AJAX.registerTeardown('tbl_find_replace.js', function () {
+ $('#find_replace_form').unbind('submit');
+ $('#toggle_find').unbind('click');
+});
+
+/**
+ * Bind events
+ */
+AJAX.registerOnload('tbl_find_replace.js', function () {
+
+ $('<div id="toggle_find_div"><a id="toggle_find"></a></div>')
+ .insertAfter('#find_replace_form')
+ .hide();
+
+ $('#toggle_find')
+ .html(PMA_messages.strHideFindNReplaceCriteria)
+ .click(function () {
+ var $link = $(this);
+ $('#find_replace_form').slideToggle();
+ if ($link.text() == PMA_messages.strHideFindNReplaceCriteria) {
+ $link.text(PMA_messages.strShowFindNReplaceCriteria);
+ } else {
+ $link.text(PMA_messages.strHideFindNReplaceCriteria);
+ }
+ return false;
+ });
+
+ $('#find_replace_form').submit(function (e) {
+ e.preventDefault();
+ var findReplaceForm = $('#find_replace_form');
+ PMA_prepareForAjaxRequest(findReplaceForm);
+ var $msgbox = PMA_ajaxShowMessage();
+ $.post(findReplaceForm.attr('action'), findReplaceForm.serialize(), function (data) {
+ PMA_ajaxRemoveMessage($msgbox);
+ if (data.success === true) {
+ $('#toggle_find_div').show();
+ $('#toggle_find').click();
+ $("#sqlqueryresults").html(data.preview);
+ } else {
+ $("#sqlqueryresults").html(data.error);
+ }
+ });
+ });
+});
diff --git a/js/tbl_gis_visualization.js b/js/tbl_gis_visualization.js
new file mode 100644
index 0000000000..38516d56e6
--- /dev/null
+++ b/js/tbl_gis_visualization.js
@@ -0,0 +1,352 @@
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * @fileoverview functions used for visualizing GIS data
+ *
+ * @requires jquery
+ * @requires jquery/jquery.svg.js
+ * @requires jquery/jquery.mousewheel.js
+ * @requires jquery/jquery.event.drag-2.2.js
+ */
+
+// Constants
+var zoomFactor = 1.5;
+var defaultX = 0;
+var defaultY = 0;
+
+// Variables
+var x;
+var y;
+var scale = 1;
+
+var svg;
+
+/**
+ * Zooms and pans the visualization.
+ */
+function zoomAndPan()
+{
+ var g = svg.getElementById('groupPanel');
+
+ g.setAttribute('transform', 'translate(' + x + ', ' + y + ') scale(' + scale + ')');
+ var id;
+ var circle;
+ $('circle.vector').each(function () {
+ id = $(this).attr('id');
+ circle = svg.getElementById(id);
+ svg.change(circle, {
+ r : (3 / scale),
+ "stroke-width" : (2 / scale)
+ });
+ });
+
+ var line;
+ $('polyline.vector').each(function () {
+ id = $(this).attr('id');
+ line = svg.getElementById(id);
+ svg.change(line, {
+ "stroke-width" : (2 / scale)
+ });
+ });
+
+ var polygon;
+ $('path.vector').each(function () {
+ id = $(this).attr('id');
+ polygon = svg.getElementById(id);
+ svg.change(polygon, {
+ "stroke-width" : (0.5 / scale)
+ });
+ });
+}
+
+/**
+ * Initially loads either SVG or OSM visualization based on the choice.
+ */
+function selectVisualization() {
+ if ($('#choice').prop('checked') !== true) {
+ $('#openlayersmap').hide();
+ } else {
+ $('#placeholder').hide();
+ }
+}
+
+/**
+ * Adds necessary styles to the div that coontains the openStreetMap.
+ */
+function styleOSM() {
+ var $placeholder = $('#placeholder');
+ var cssObj = {
+ 'border' : '1px solid #aaa',
+ 'width' : $placeholder.width(),
+ 'height' : $placeholder.height(),
+ 'float' : 'right'
+ };
+ $('#openlayersmap').css(cssObj);
+}
+
+/**
+ * Loads the SVG element and make a reference to it.
+ */
+function loadSVG() {
+ var $placeholder = $('#placeholder');
+
+ $placeholder.svg({
+ onLoad: function (svg_ref) {
+ svg = svg_ref;
+ }
+ });
+
+ // Removes the second SVG element unnecessarily added due to the above command
+ $placeholder.find('svg:nth-child(2)').remove();
+}
+
+/**
+ * Adds controllers for zooming and panning.
+ */
+function addZoomPanControllers() {
+ var $placeholder = $('#placeholder');
+ if ($("#placeholder svg").length > 0) {
+ var pmaThemeImage = $('#pmaThemeImage').val();
+ // add panning arrows
+ $('<img class="button" id="left_arrow" src="' + pmaThemeImage + 'west-mini.png">').appendTo($placeholder);
+ $('<img class="button" id="right_arrow" src="' + pmaThemeImage + 'east-mini.png">').appendTo($placeholder);
+ $('<img class="button" id="up_arrow" src="' + pmaThemeImage + 'north-mini.png">').appendTo($placeholder);
+ $('<img class="button" id="down_arrow" src="' + pmaThemeImage + 'south-mini.png">').appendTo($placeholder);
+ // add zooming controls
+ $('<img class="button" id="zoom_in" src="' + pmaThemeImage + 'zoom-plus-mini.png">').appendTo($placeholder);
+ $('<img class="button" id="zoom_world" src="' + pmaThemeImage + 'zoom-world-mini.png">').appendTo($placeholder);
+ $('<img class="button" id="zoom_out" src="' + pmaThemeImage + 'zoom-minus-mini.png">').appendTo($placeholder);
+ }
+}
+
+/**
+ * Resizes the GIS visualization to fit into the space available.
+ */
+function resizeGISVisualization() {
+ var $placeholder = $('#placeholder');
+ var old_width = $placeholder.width();
+ var visWidth = $('#div_view_options').width() - 48;
+
+ // Assign new value for width
+ $placeholder.width(visWidth);
+ $('svg').attr('width', visWidth);
+
+ // Assign the offset created due to resizing to defaultX and center the svg.
+ defaultX = (visWidth - old_width) / 2;
+ x = defaultX;
+ y = 0;
+ scale = 1;
+}
+
+/**
+ * Initialize the GIS visualization.
+ */
+function initGISVisualization() {
+ // Loads either SVG or OSM visualization based on the choice
+ selectVisualization();
+ // Resizes the GIS visualization to fit into the space available
+ resizeGISVisualization();
+ // Adds necessary styles to the div that coontains the openStreetMap
+ styleOSM();
+ // Draws openStreetMap with openLayers
+ drawOpenLayers();
+ // Loads the SVG element and make a reference to it
+ loadSVG();
+ // Adds controllers for zooming and panning
+ addZoomPanControllers();
+ zoomAndPan();
+}
+
+function getRelativeCoords(e) {
+ var position = $('#placeholder').offset();
+ return {
+ x : e.pageX - position.left,
+ y : e.pageY - position.top
+ };
+}
+
+/**
+ * Ajax handlers for GIS visualization page
+ *
+ * Actions Ajaxified here:
+ *
+ * Zooming in and zooming out on mousewheel movement.
+ * Panning the visualization on dragging.
+ * Zooming in on double clicking.
+ * Zooming out on clicking the zoom out button.
+ * Panning on clicking the arrow buttons.
+ * Displaying tooltips for GIS objects.
+ */
+
+/**
+ * Unbind all event handlers before tearing down a page
+ */
+AJAX.registerTeardown('tbl_gis_visualization.js', function () {
+ $('#choice').die('click');
+ $('#placeholder').die('mousewheel');
+ $('svg').die('dragstart');
+ $('svg').die('mouseup');
+ $('svg').die('drag');
+ $('#placeholder').die('dblclick');
+ $('#zoom_in').die('click');
+ $('#zoom_world').die('click');
+ $('#zoom_out').die('click');
+ $('#left_arrow').die('click');
+ $('#right_arrow').die('click');
+ $('#up_arrow').die('click');
+ $('#down_arrow').die('click');
+ $('.vector').unbind('mousemove').unbind('mouseout');
+});
+
+AJAX.registerOnload('tbl_gis_visualization.js', function () {
+
+ // If we are in GIS visualization, initialize it
+ if ($('table.gis_table').length > 0) {
+ initGISVisualization();
+ }
+
+ $('#choice').live('click', function () {
+ if ($(this).prop('checked') === false) {
+ $('#placeholder').show();
+ $('#openlayersmap').hide();
+ } else {
+ $('#placeholder').hide();
+ $('#openlayersmap').show();
+ }
+ });
+
+ $('#placeholder').live('mousewheel', function (event, delta) {
+ var relCoords = getRelativeCoords(event);
+ if (delta > 0) {
+ //zoom in
+ scale *= zoomFactor;
+ // zooming in keeping the position under mouse pointer unmoved.
+ x = relCoords.x - (relCoords.x - x) * zoomFactor;
+ y = relCoords.y - (relCoords.y - y) * zoomFactor;
+ zoomAndPan();
+ } else {
+ //zoom out
+ scale /= zoomFactor;
+ // zooming out keeping the position under mouse pointer unmoved.
+ x = relCoords.x - (relCoords.x - x) / zoomFactor;
+ y = relCoords.y - (relCoords.y - y) / zoomFactor;
+ zoomAndPan();
+ }
+ return true;
+ });
+
+ var dragX = 0;
+ var dragY = 0;
+
+ $('svg').live('dragstart', function (event, dd) {
+ $('#placeholder').addClass('placeholderDrag');
+ dragX = Math.round(dd.offsetX);
+ dragY = Math.round(dd.offsetY);
+ });
+
+ $('svg').live('mouseup', function (event) {
+ $('#placeholder').removeClass('placeholderDrag');
+ });
+
+ $('svg').live('drag', function (event, dd) {
+ newX = Math.round(dd.offsetX);
+ x += newX - dragX;
+ dragX = newX;
+ newY = Math.round(dd.offsetY);
+ y += newY - dragY;
+ dragY = newY;
+ zoomAndPan();
+ });
+
+ $('#placeholder').live('dblclick', function (event) {
+ scale *= zoomFactor;
+ // zooming in keeping the position under mouse pointer unmoved.
+ var relCoords = getRelativeCoords(event);
+ x = relCoords.x - (relCoords.x - x) * zoomFactor;
+ y = relCoords.y - (relCoords.y - y) * zoomFactor;
+ zoomAndPan();
+ });
+
+ $('#zoom_in').live('click', function (e) {
+ e.preventDefault();
+ //zoom in
+ scale *= zoomFactor;
+
+ width = $('#placeholder svg').attr('width');
+ height = $('#placeholder svg').attr('height');
+ // zooming in keeping the center unmoved.
+ x = width / 2 - (width / 2 - x) * zoomFactor;
+ y = height / 2 - (height / 2 - y) * zoomFactor;
+ zoomAndPan();
+ });
+
+ $('#zoom_world').live('click', function (e) {
+ e.preventDefault();
+ scale = 1;
+ x = defaultX;
+ y = defaultY;
+ zoomAndPan();
+ });
+
+ $('#zoom_out').live('click', function (e) {
+ e.preventDefault();
+ //zoom out
+ scale /= zoomFactor;
+
+ width = $('#placeholder svg').attr('width');
+ height = $('#placeholder svg').attr('height');
+ // zooming out keeping the center unmoved.
+ x = width / 2 - (width / 2 - x) / zoomFactor;
+ y = height / 2 - (height / 2 - y) / zoomFactor;
+ zoomAndPan();
+ });
+
+ $('#left_arrow').live('click', function (e) {
+ e.preventDefault();
+ x += 100;
+ zoomAndPan();
+ });
+
+ $('#right_arrow').live('click', function (e) {
+ e.preventDefault();
+ x -= 100;
+ zoomAndPan();
+ });
+
+ $('#up_arrow').live('click', function (e) {
+ e.preventDefault();
+ y += 100;
+ zoomAndPan();
+ });
+
+ $('#down_arrow').live('click', function (e) {
+ e.preventDefault();
+ y -= 100;
+ zoomAndPan();
+ });
+
+ /**
+ * Detect the mousemove event and show tooltips.
+ */
+ $('.vector').bind('mousemove', function (event) {
+ var contents = $.trim(escapeHtml($(this).attr('name')));
+ $("#tooltip").remove();
+ if (contents !== '') {
+ $('<div id="tooltip">' + contents + '</div>').css({
+ position : 'absolute',
+ top : event.pageY + 10,
+ left : event.pageX + 10,
+ border : '1px solid #fdd',
+ padding : '2px',
+ 'background-color' : '#fee',
+ opacity : 0.90
+ }).appendTo("body").fadeIn(200);
+ }
+ });
+
+ /**
+ * Detect the mouseout event and hide tooltips.
+ */
+ $('.vector').bind('mouseout', function (event) {
+ $("#tooltip").remove();
+ });
+});
diff --git a/js/tbl_relation.js b/js/tbl_relation.js
new file mode 100644
index 0000000000..d6ceb1dbd5
--- /dev/null
+++ b/js/tbl_relation.js
@@ -0,0 +1,135 @@
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * for tbl_relation.php
+ *
+ */
+function show_hide_clauses($thisDropdown)
+{
+ if ($thisDropdown.val() === '') {
+ $thisDropdown.parent().nextAll('span').hide();
+ } else {
+ if ($thisDropdown.is('select[name^="destination_foreign_column"]')) {
+ $thisDropdown.parent().nextAll('span').show();
+ }
+ }
+}
+
+/**
+ * Sets dropdown options to values
+ */
+function setDropdownValues($dropdown, values) {
+ $dropdown.empty();
+ var optionsAsString = '';
+ // add an empty string to the beginning for empty selection
+ values.unshift('');
+ $.each(values, function () {
+ optionsAsString += "<option value='" + this + "'>" + this + "</option>";
+ });
+ $dropdown.append($(optionsAsString));
+}
+
+/**
+ * Retrieves and populates dropdowns to the left based on the selected value
+ *
+ * @param $dropdown the dropdown whose value got changed
+ */
+function getDropdownValues($dropdown) {
+ var foreignDb = null, foreignTable = null;
+ var $tableDd, $columnDd;
+ var foreign = '';
+ // if the changed dropdown is for foreign key constraints
+ if ($dropdown.is('select[name^="destination_foreign"]')) {
+ $tableDd = $dropdown.parent().find('select[name^="destination_foreign_table"]');
+ $columnDd = $dropdown.parent().find('select[name^="destination_foreign_column"]');
+ foreign = '_foreign';
+ } else { // internal relations
+ $tableDd = $dropdown.parent().find('select[name^="destination_table"]');
+ $columnDd = $dropdown.parent().find('select[name^="destination_column"]');
+ }
+
+ // if the changed dropdown is a database selector
+ if ($dropdown.is('select[name^="destination' + foreign + '_db"]')) {
+ foreignDb = $dropdown.val();
+ // if no database is selected empty table and column dropdowns
+ if (foreignDb === '') {
+ setDropdownValues($tableDd, []);
+ setDropdownValues($columnDd, []);
+ return;
+ }
+ } else { // if a table selector
+ foreignDb = $dropdown.parent()
+ .find('select[name^="destination' + foreign + '_db"]').val();
+ foreignTable = $dropdown.val();
+ // if no table is selected empty the column dropdown
+ if (foreignTable === '') {
+ setDropdownValues($columnDd, []);
+ return;
+ }
+ }
+ var $msgbox = PMA_ajaxShowMessage();
+ var $form = $dropdown.parents('form');
+ var url = 'tbl_relation.php?getDropdownValues=true&ajax_request=true' +
+ '&token=' + $form.find('input[name="token"]').val() +
+ '&db=' + $form.find('input[name="db"]').val() +
+ '&table=' + $form.find('input[name="table"]').val() +
+ '&foreign=' + (foreign !== '') +
+ '&foreignDb=' + encodeURIComponent(foreignDb) +
+ (foreignTable !== null ?
+ '&foreignTable=' + encodeURIComponent(foreignTable) : ''
+ );
+ var $server = $form.find('input[name="server"]');
+ if ($server.length > 0) {
+ url += '&server=' + $form.find('input[name="server"]').val();
+ }
+ $.ajax({
+ url: url,
+ datatype: 'json',
+ success: function (data) {
+ PMA_ajaxRemoveMessage($msgbox);
+ if (data.success) {
+ // if the changed dropdown is a database selector
+ if (foreignTable === null) {
+ // set values for table and column dropdowns
+ setDropdownValues($tableDd, data.tables);
+ setDropdownValues($columnDd, []);
+ } else { // if a table selector
+ // set values for the column dropdown
+ setDropdownValues($columnDd, data.columns);
+ }
+ } else {
+ PMA_ajaxShowMessage(data.error, false);
+ }
+ }
+ });
+}
+
+/**
+ * Unbind all event handlers before tearing down a page
+ */
+AJAX.registerTeardown('tbl_relation.js', function () {
+ $('select[name^="destination_foreign"]').unbind('change');
+ $('select[name^="destination_db"],' +
+ ' select[name^="destination_table"],' +
+ ' select[name^="destination_foreign_db"],' +
+ ' select[name^="destination_foreign_table"]'
+ ).unbind('change');
+});
+
+AJAX.registerOnload('tbl_relation.js', function () {
+ // initial display
+ $('select[name^="destination_foreign_column"]').each(function (index, one_dropdown) {
+ show_hide_clauses($(one_dropdown));
+ });
+ // change
+ $('select[name^="destination_foreign"]').change(function () {
+ show_hide_clauses($(this));
+ });
+
+ $('select[name^="destination_db"],' +
+ ' select[name^="destination_table"],' +
+ ' select[name^="destination_foreign_db"],' +
+ ' select[name^="destination_foreign_table"]'
+ ).change(function () {
+ getDropdownValues($(this));
+ });
+});
diff --git a/js/tbl_select.js b/js/tbl_select.js
new file mode 100644
index 0000000000..4efb86ae39
--- /dev/null
+++ b/js/tbl_select.js
@@ -0,0 +1,227 @@
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * @fileoverview JavaScript functions used on tbl_select.php
+ *
+ * @requires jQuery
+ * @requires js/functions.js
+ */
+
+/**
+ * Ajax event handlers for this page
+ *
+ * Actions ajaxified here:
+ * Table Search
+ */
+
+/**
+ * Unbind all event handlers before tearing down a page
+ */
+AJAX.registerTeardown('tbl_select.js', function () {
+ $('#togglesearchformlink').unbind('click');
+ $("#tbl_search_form.ajax").die('submit');
+ $('select.geom_func').unbind('change');
+ $('span.open_search_gis_editor').die('click');
+});
+
+AJAX.registerOnload('tbl_select.js', function () {
+ /**
+ * Prepare a div containing a link, otherwise it's incorrectly displayed
+ * after a couple of clicks
+ */
+ $('<div id="togglesearchformdiv"><a id="togglesearchformlink"></a></div>')
+ .insertAfter('#tbl_search_form')
+ // don't show it until we have results on-screen
+ .hide();
+
+ $('#togglesearchformlink')
+ .html(PMA_messages.strShowSearchCriteria)
+ .bind('click', function () {
+ var $link = $(this);
+ $('#tbl_search_form').slideToggle();
+ if ($link.text() == PMA_messages.strHideSearchCriteria) {
+ $link.text(PMA_messages.strShowSearchCriteria);
+ } else {
+ $link.text(PMA_messages.strHideSearchCriteria);
+ }
+ // avoid default click action
+ return false;
+ });
+
+ /**
+ * Ajax event handler for Table Search
+ */
+ $("#tbl_search_form.ajax").live('submit', function (event) {
+ var unaryFunctions = [
+ 'IS NULL',
+ 'IS NOT NULL',
+ "= ''",
+ "!= ''"
+ ];
+
+ // jQuery object to reuse
+ $search_form = $(this);
+ event.preventDefault();
+
+ // empty previous search results while we are waiting for new results
+ $("#sqlqueryresults").empty();
+ var $msgbox = PMA_ajaxShowMessage(PMA_messages.strSearching, false);
+
+ PMA_prepareForAjaxRequest($search_form);
+
+ var values = {};
+ $search_form.find(':input').each(function () {
+ var $input = $(this);
+ if ($input.attr('type') == 'checkbox' || $input.attr('type') == 'radio') {
+ if ($input.is(':checked')) {
+ values[this.name] = $input.val();
+ }
+ } else {
+ values[this.name] = $input.val();
+ }
+ });
+ var columnCount = $('select[name="columnsToDisplay[]"] option').length;
+ // Submit values only for the columns that have unary column operator or a search criteria
+ for (var a = 0; a < columnCount; a++) {
+ if ($.inArray(values['criteriaColumnOperators[' + a + ']'], unaryFunctions) >= 0) {
+ continue;
+ }
+
+ if (values['criteriaValues[' + a + ']'] === '' || values['criteriaValues[' + a + ']'] === null) {
+ delete values['criteriaValues[' + a + ']'];
+ delete values['criteriaColumnOperators[' + a + ']'];
+ delete values['criteriaColumnNames[' + a + ']'];
+ delete values['criteriaColumnTypes[' + a + ']'];
+ delete values['criteriaColumnCollations[' + a + ']'];
+ }
+ }
+ // If all columns are selected, use a single parameter to indicate that
+ if (values['columnsToDisplay[]'] !== null) {
+ if (values['columnsToDisplay[]'].length == columnCount) {
+ delete values['columnsToDisplay[]'];
+ values['displayAllColumns'] = true;
+ }
+ } else {
+ values['displayAllColumns'] = true;
+ }
+
+ $.post($search_form.attr('action'), values, function (data) {
+ PMA_ajaxRemoveMessage($msgbox);
+ if (data.success === true) {
+ if (typeof data.sql_query !== 'undefined') { // zero rows
+ $("#sqlqueryresults").html(data.sql_query);
+ } else { // results found
+ $("#sqlqueryresults").html(data.message);
+ $("#sqlqueryresults").trigger('makegrid');
+ }
+ $('#tbl_search_form')
+ // workaround for bug #3168569 - Issue on toggling the "Hide search criteria" in chrome.
+ .slideToggle()
+ .hide();
+ $('#togglesearchformlink')
+ // always start with the Show message
+ .text(PMA_messages.strShowSearchCriteria);
+ $('#togglesearchformdiv')
+ // now it's time to show the div containing the link
+ .show();
+ // needed for the display options slider in the results
+ PMA_init_slider();
+ } else {
+ $("#sqlqueryresults").html(data.error);
+ }
+ PMA_highlightSQL($('#sqlqueryresults'));
+ }); // end $.post()
+ });
+
+ // Following section is related to the 'function based search' for geometry data types.
+ // Initialy hide all the open_gis_editor spans
+ $('span.open_search_gis_editor').hide();
+
+ $('select.geom_func').bind('change', function () {
+ var $geomFuncSelector = $(this);
+
+ var binaryFunctions = [
+ 'Contains',
+ 'Crosses',
+ 'Disjoint',
+ 'Equals',
+ 'Intersects',
+ 'Overlaps',
+ 'Touches',
+ 'Within',
+ 'MBRContains',
+ 'MBRDisjoint',
+ 'MBREquals',
+ 'MBRIntersects',
+ 'MBROverlaps',
+ 'MBRTouches',
+ 'MBRWithin',
+ 'ST_Contains',
+ 'ST_Crosses',
+ 'ST_Disjoint',
+ 'ST_Equals',
+ 'ST_Intersects',
+ 'ST_Overlaps',
+ 'ST_Touches',
+ 'ST_Within'
+ ];
+
+ var tempArray = [
+ 'Envelope',
+ 'EndPoint',
+ 'StartPoint',
+ 'ExteriorRing',
+ 'Centroid',
+ 'PointOnSurface'
+ ];
+ var outputGeomFunctions = binaryFunctions.concat(tempArray);
+
+ // If the chosen function takes two geometry objects as parameters
+ var $operator = $geomFuncSelector.parents('tr').find('td:nth-child(5)').find('select');
+ if ($.inArray($geomFuncSelector.val(), binaryFunctions) >= 0) {
+ $operator.prop('readonly', true);
+ } else {
+ $operator.prop('readonly', false);
+ }
+
+ // if the chosen function's output is a geometry, enable GIS editor
+ var $editorSpan = $geomFuncSelector.parents('tr').find('span.open_search_gis_editor');
+ if ($.inArray($geomFuncSelector.val(), outputGeomFunctions) >= 0) {
+ $editorSpan.show();
+ } else {
+ $editorSpan.hide();
+ }
+
+ });
+
+ $('span.open_search_gis_editor').live('click', function (event) {
+ event.preventDefault();
+
+ var $span = $(this);
+ // Current value
+ var value = $span.parent('td').children("input[type='text']").val();
+ // Field name
+ var field = 'Parameter';
+ // Column type
+ var geom_func = $span.parents('tr').find('.geom_func').val();
+ var type;
+ if (geom_func == 'Envelope') {
+ type = 'polygon';
+ } else if (geom_func == 'ExteriorRing') {
+ type = 'linestring';
+ } else {
+ type = 'point';
+ }
+ // Names of input field and null checkbox
+ var input_name = $span.parent('td').children("input[type='text']").attr('name');
+ //Token
+ var token = $("input[name='token']").val();
+
+ openGISEditor();
+ if (!gisEditorLoaded) {
+ loadJSAndGISEditor(value, field, type, input_name, token);
+ } else {
+ loadGISEditor(value, field, type, input_name, token);
+ }
+ });
+
+});
diff --git a/js/tbl_structure.js b/js/tbl_structure.js
new file mode 100644
index 0000000000..b7890ed413
--- /dev/null
+++ b/js/tbl_structure.js
@@ -0,0 +1,504 @@
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * @fileoverview functions used on the table structure page
+ * @name Table Structure
+ *
+ * @requires jQuery
+ * @requires jQueryUI
+ * @required js/functions.js
+ */
+
+/**
+ * AJAX scripts for tbl_structure.php
+ *
+ * Actions ajaxified here:
+ * Drop Column
+ * Add Primary Key
+ * Drop Primary Key/Index
+ *
+ */
+
+/**
+ * This function returns the horizontal space available for the menu in pixels.
+ * To calculate this value we start we the width of the main panel, then we
+ * substract the margin of the page content, then we substract any cellspacing
+ * that the table may have (original theme only) and finally we substract the
+ * width of all columns of the table except for the last one (which is where
+ * the menu will go). What we should end up with is the distance between the
+ * start of the last column on the table and the edge of the page, again this
+ * is the space available for the menu.
+ *
+ * In the case where the table cell where the menu will be displayed is already
+ * off-screen (the table is wider than the page), a negative value will be returned,
+ * but this will be treated as a zero by the menuResizer plugin.
+ *
+ * @return int
+ */
+function PMA_tbl_structure_menu_resizer_callback() {
+ var pagewidth = $('body').width();
+ var $page = $('#page_content');
+ pagewidth -= $page.outerWidth(true) - $page.outerWidth();
+ var columnsWidth = 0;
+ var $columns = $('#tablestructure').find('tr:eq(1)').find('td,th');
+ $columns.not(':last').each(function () {
+ columnsWidth += $(this).outerWidth(true);
+ });
+ var totalCellSpacing = $('#tablestructure').width();
+ $columns.each(function () {
+ totalCellSpacing -= $(this).outerWidth(true);
+ });
+ return pagewidth - columnsWidth - totalCellSpacing - 15; // 15px extra margin
+}
+
+/**
+ * Reload fields table
+ */
+function reloadFieldForm() {
+ $.post($("#fieldsForm").attr('action'), $("#fieldsForm").serialize() + "&ajax_request=true", function (form_data) {
+ var $temp_div = $("<div id='temp_div'><div>").append(form_data.message);
+ $("#fieldsForm").replaceWith($temp_div.find("#fieldsForm"));
+ $("#addColumns").replaceWith($temp_div.find("#addColumns"));
+ $('#move_columns_dialog ul').replaceWith($temp_div.find("#move_columns_dialog ul"));
+ $("#moveColumns").removeClass("move-active");
+ /* reinitialise the more options in table */
+ $('#fieldsForm ul.table-structure-actions').menuResizer(PMA_tbl_structure_menu_resizer_callback);
+ });
+ $('#page_content').show();
+}
+
+/**
+ * Unbind all event handlers before tearing down a page
+ */
+AJAX.registerTeardown('tbl_structure.js', function () {
+ $("a.change_column_anchor.ajax").die('click');
+ $("button.change_columns_anchor.ajax, input.change_columns_anchor.ajax").die('click');
+ $("a.drop_column_anchor.ajax").die('click');
+ $("a.add_primary_key_anchor.ajax").die('click');
+ $("a.add_index_anchor.ajax").die('click');
+ $("a.add_unique_anchor.ajax").die('click');
+ $("#move_columns_anchor").die('click');
+ $(".append_fields_form.ajax").unbind('submit');
+});
+
+AJAX.registerOnload('tbl_structure.js', function () {
+
+ /**
+ *Ajax action for submitting the "Column Change" and "Add Column" form
+ */
+ $(".append_fields_form.ajax").die().live('submit', function (event) {
+ event.preventDefault();
+ /**
+ * @var the_form object referring to the export form
+ */
+ var $form = $(this);
+
+ /*
+ * First validate the form; if there is a problem, avoid submitting it
+ *
+ * checkTableEditForm() needs a pure element and not a jQuery object,
+ * this is why we pass $form[0] as a parameter (the jQuery object
+ * is actually an array of DOM elements)
+ */
+ if (checkTableEditForm($form[0], $form.find('input[name=orig_num_fields]').val())) {
+ // OK, form passed validation step
+ PMA_prepareForAjaxRequest($form);
+ //User wants to submit the form
+ $msg = PMA_ajaxShowMessage();
+ $.post($form.attr('action'), $form.serialize() + '&do_save_data=1', function (data) {
+ if ($("#sqlqueryresults").length !== 0) {
+ $("#sqlqueryresults").remove();
+ } else if ($(".error:not(.tab)").length !== 0) {
+ $(".error:not(.tab)").remove();
+ }
+ if (data.success === true) {
+ $("#page_content")
+ .empty()
+ .append(data.message)
+ .append(data.sql_query)
+ .show();
+ PMA_highlightSQL($('#page_content'));
+ $("#result_query .notice").remove();
+ reloadFieldForm();
+ $form.remove();
+ PMA_ajaxRemoveMessage($msg);
+ PMA_reloadNavigation();
+ } else {
+ PMA_ajaxShowMessage(data.error, false);
+ }
+ }); // end $.post()
+ }
+ }); // end change table button "do_save_data"
+
+ /**
+ * Attach Event Handler for 'Change Column'
+ */
+ $("a.change_column_anchor.ajax").live('click', function (event) {
+ event.preventDefault();
+ var $msg = PMA_ajaxShowMessage();
+ $('#page_content').hide();
+ $.get($(this).attr('href'), {'ajax_request': true}, function (data) {
+ PMA_ajaxRemoveMessage($msg);
+ if (data.success) {
+ $('<div id="change_column_dialog" class="margin"></div>')
+ .html(data.message)
+ .insertBefore('#page_content');
+ PMA_highlightSQL($('#page_content'));
+ PMA_showHints();
+ PMA_verifyColumnsProperties();
+ } else {
+ PMA_ajaxShowMessage(PMA_messages.strErrorProcessingRequest + " : " + data.error, false);
+ }
+ });
+ });
+
+ /**
+ * Attach Event Handler for 'Change multiple columns'
+ */
+ $("button.change_columns_anchor.ajax, input.change_columns_anchor.ajax").live('click', function (event) {
+ event.preventDefault();
+ var $msg = PMA_ajaxShowMessage();
+ $('#page_content').hide();
+ var $form = $(this).closest('form');
+ var params = $form.serialize() + "&ajax_request=true&submit_mult=change";
+ $.post($form.prop("action"), params, function (data) {
+ PMA_ajaxRemoveMessage($msg);
+ if (data.success) {
+ $('#page_content')
+ .empty()
+ .append(
+ $('<div id="change_column_dialog"></div>')
+ .html(data.message)
+ )
+ .show();
+ PMA_highlightSQL($('#page_content'));
+ PMA_showHints();
+ PMA_verifyColumnsProperties();
+ } else {
+ $('#page_content').show();
+ PMA_ajaxShowMessage(data.error);
+ }
+ });
+ });
+
+ /**
+ * Attach Event Handler for 'Drop Column'
+ */
+ $("a.drop_column_anchor.ajax").live('click', function (event) {
+ event.preventDefault();
+ /**
+ * @var curr_table_name String containing the name of the current table
+ */
+ var curr_table_name = $(this).closest('form').find('input[name=table]').val();
+ /**
+ * @var curr_row Object reference to the currently selected row (i.e. field in the table)
+ */
+ var $curr_row = $(this).parents('tr');
+ /**
+ * @var curr_column_name String containing name of the field referred to by {@link curr_row}
+ */
+ var curr_column_name = $curr_row.children('th').children('label').text();
+ /**
+ * @var $after_field_item Corresponding entry in the 'After' field.
+ */
+ var $after_field_item = $("select[name='after_field'] option[value='" + curr_column_name + "']");
+ /**
+ * @var question String containing the question to be asked for confirmation
+ */
+ var question = $.sprintf(PMA_messages.strDoYouReally, 'ALTER TABLE `' + escapeHtml(curr_table_name) + '` DROP `' + escapeHtml(curr_column_name) + '`;');
+ $(this).PMA_confirm(question, $(this).attr('href'), function (url) {
+ var $msg = PMA_ajaxShowMessage(PMA_messages.strDroppingColumn, false);
+ $.get(url, {'is_js_confirmed' : 1, 'ajax_request' : true, 'ajax_page_request' : true}, function (data) {
+ if (data.success === true) {
+ PMA_ajaxRemoveMessage($msg);
+ if ($('#result_query').length) {
+ $('#result_query').remove();
+ }
+ if (data.sql_query) {
+ $('<div id="result_query"></div>')
+ .html(data.sql_query)
+ .prependTo('#page_content');
+ PMA_highlightSQL($('#page_content'));
+ }
+ toggleRowColors($curr_row.next());
+ // Adjust the row numbers
+ for (var $row = $curr_row.next(); $row.length > 0; $row = $row.next()) {
+ var new_val = parseInt($row.find('td:nth-child(2)').text(), 10) - 1;
+ $row.find('td:nth-child(2)').text(new_val);
+ }
+ $after_field_item.remove();
+ $curr_row.hide("medium").remove();
+ //refresh table stats
+ if (data.tableStat) {
+ $('#tablestatistics').html(data.tableStat);
+ }
+ // refresh the list of indexes (comes from sql.php)
+ $('.index_info').replaceWith(data.indexes_list);
+ PMA_reloadNavigation();
+ } else {
+ PMA_ajaxShowMessage(PMA_messages.strErrorProcessingRequest + " : " + data.error, false);
+ }
+ }); // end $.get()
+ }); // end $.PMA_confirm()
+ }); //end of Drop Column Anchor action
+
+ /**
+ * Ajax Event handler for 'Add Primary Key'
+ */
+ $("a.add_primary_key_anchor.ajax").live('click', function (event) {
+ event.preventDefault();
+ /**
+ * @var curr_table_name String containing the name of the current table
+ */
+ var curr_table_name = $(this).closest('form').find('input[name=table]').val();
+ /**
+ * @var curr_column_name String containing name of the field referred to by {@link curr_row}
+ */
+ var curr_column_name = $(this).parents('tr').children('th').children('label').text();
+ /**
+ * @var question String containing the question to be asked for confirmation
+ */
+ var question = $.sprintf(PMA_messages.strDoYouReally, 'ALTER TABLE `' + escapeHtml(curr_table_name) + '` ADD PRIMARY KEY(`' + escapeHtml(curr_column_name) + '`);');
+ $(this).PMA_confirm(question, $(this).attr('href'), function (url) {
+ var $msg = PMA_ajaxShowMessage(PMA_messages.strAddingPrimaryKey, false);
+ $.get(url, {'is_js_confirmed' : 1, 'ajax_request' : true}, function (data) {
+ if (data.success === true) {
+ PMA_ajaxRemoveMessage($msg);
+ $(this).remove();
+ if (typeof data.reload != 'undefined') {
+ PMA_commonActions.refreshMain(false, function () {
+ if ($('#result_query').length) {
+ $('#result_query').remove();
+ }
+ if (data.sql_query) {
+ $('<div id="result_query"></div>')
+ .html(data.sql_query)
+ .prependTo('#page_content');
+ PMA_highlightSQL($('#page_content'));
+ }
+ });
+ PMA_reloadNavigation();
+ }
+ } else {
+ PMA_ajaxShowMessage(PMA_messages.strErrorProcessingRequest + " : " + data.error, false);
+ }
+ }); // end $.get()
+ }); // end $.PMA_confirm()
+ }); //end Add Primary Key
+
+ /**
+ * Ajax Event handler for 'Add Index'
+ */
+ $("a.add_index_anchor.ajax").live('click', function (event) {
+ event.preventDefault();
+ /**
+ * @var curr_table_name String containing the name of the current table
+ */
+ var curr_table_name = $(this).closest('form').find('input[name=table]').val();
+ /**
+ * @var curr_column_name String containing name of the field referred to by {@link curr_row}
+ */
+ var curr_column_name = $(this).parents('tr').children('th').children('label').text();
+ /**
+ * @var question String containing the question to be asked for confirmation
+ */
+ var question = $.sprintf(PMA_messages.strDoYouReally, 'ALTER TABLE `' + escapeHtml(curr_table_name) + '` ADD INDEX(`' + escapeHtml(curr_column_name) + '`);');
+ $(this).PMA_confirm(question, $(this).attr('href'), function (url) {
+ var $msg = PMA_ajaxShowMessage(PMA_messages.strAddingIndex, false);
+ $.get(url, {'is_js_confirmed' : 1, 'ajax_request' : true}, function (data) {
+ if (data.success === true) {
+ PMA_ajaxRemoveMessage($msg);
+ if ($('#result_query').length) {
+ $('#result_query').remove();
+ }
+ if (data.sql_query) {
+ $('<div id="result_query"></div>')
+ .html(data.sql_query)
+ .prependTo('#page_content');
+ PMA_highlightSQL($('#page_content'));
+ }
+ PMA_reloadNavigation();
+ } else {
+ PMA_ajaxShowMessage(PMA_messages.strErrorProcessingRequest + " : " + data.error, false);
+ }
+ }); // end $.get()
+ }); // end $.PMA_confirm()
+ }); //end Add Index
+
+ /**
+ * Ajax Event handler for 'Add Unique'
+ */
+ $("a.add_unique_anchor.ajax").live('click', function (event) {
+ event.preventDefault();
+ /**
+ * @var curr_table_name String containing the name of the current table
+ */
+ var curr_table_name = $(this).closest('form').find('input[name=table]').val();
+ /**
+ * @var curr_column_name String containing name of the field referred to by {@link curr_row}
+ */
+ var curr_column_name = $(this).parents('tr').children('th').children('label').text();
+ /**
+ * @var question String containing the question to be asked for confirmation
+ */
+ var question = $.sprintf(PMA_messages.strDoYouReally, 'ALTER TABLE `' + escapeHtml(curr_table_name) + '` ADD UNIQUE(`' + escapeHtml(curr_column_name) + '`);');
+ $(this).PMA_confirm(question, $(this).attr('href'), function (url) {
+ var $msg = PMA_ajaxShowMessage(PMA_messages.strAddingUnique, false);
+ $.get(url, {'is_js_confirmed' : 1, 'ajax_request' : true}, function (data) {
+ if (data.success === true) {
+ PMA_ajaxRemoveMessage($msg);
+ if ($('#result_query').length) {
+ $('#result_query').remove();
+ }
+ if (data.sql_query) {
+ $('<div id="result_query"></div>')
+ .html(data.sql_query)
+ .prependTo('#page_content');
+ PMA_highlightSQL($('#page_content'));
+ }
+ PMA_reloadNavigation();
+ } else {
+ PMA_ajaxShowMessage(PMA_messages.strErrorProcessingRequest + " : " + data.error, false);
+ }
+ }); // end $.get()
+ }); // end $.PMA_confirm()
+ }); //end Add Unique
+
+ /**
+ * Inline move columns
+ **/
+ $("#move_columns_anchor").live('click', function (e) {
+ e.preventDefault();
+
+ if ($(this).hasClass("move-active")) {
+ return;
+ }
+
+ /**
+ * @var button_options Object that stores the options passed to jQueryUI
+ * dialog
+ */
+ var button_options = {};
+
+ button_options[PMA_messages.strGo] = function (event) {
+ event.preventDefault();
+ var $msgbox = PMA_ajaxShowMessage();
+ var $this = $(this);
+ var $form = $this.find("form");
+ var serialized = $form.serialize();
+
+ // check if any columns were moved at all
+ if (serialized == $form.data("serialized-unmoved")) {
+ PMA_ajaxRemoveMessage($msgbox);
+ $this.dialog('close');
+ return;
+ }
+
+ $.post($form.prop("action"), serialized + "&ajax_request=true", function (data) {
+ if (data.success === false) {
+ PMA_ajaxRemoveMessage($msgbox);
+ $this
+ .clone()
+ .html(data.error)
+ .dialog({
+ title: $(this).prop("title"),
+ height: 230,
+ width: 900,
+ modal: true,
+ buttons: button_options_error
+ }); // end dialog options
+ } else {
+ $('#fieldsForm ul.table-structure-actions').menuResizer('destroy');
+ // sort the fields table
+ var $fields_table = $("table#tablestructure tbody");
+ // remove all existing rows and remember them
+ var $rows = $fields_table.find("tr").remove();
+ // loop through the correct order
+ for (var i in data.columns) {
+ var the_column = data.columns[i];
+ var $the_row = $rows
+ .find("input:checkbox[value=" + the_column + "]")
+ .closest("tr");
+ // append the row for this column to the table
+ $fields_table.append($the_row);
+ }
+ var $firstrow = $fields_table.find("tr").eq(0);
+ // Adjust the row numbers and colors
+ for (var $row = $firstrow; $row.length > 0; $row = $row.next()) {
+ $row
+ .find('td:nth-child(2)')
+ .text($row.index() + 1)
+ .end()
+ .removeClass("odd even")
+ .addClass($row.index() % 2 === 0 ? "odd" : "even");
+ }
+ PMA_ajaxShowMessage(data.message);
+ $this.dialog('close');
+ $('#fieldsForm ul.table-structure-actions').menuResizer(PMA_tbl_structure_menu_resizer_callback);
+ }
+ });
+ };
+ button_options[PMA_messages.strCancel] = function () {
+ $(this).dialog('close');
+ };
+
+ var button_options_error = {};
+ button_options_error[PMA_messages.strOK] = function () {
+ $(this).dialog('close').remove();
+ };
+
+ var columns = [];
+
+ $("#tablestructure tbody tr").each(function () {
+ var col_name = $(this).find("input:checkbox").eq(0).val();
+ var hidden_input = $("<input/>")
+ .prop({
+ name: "move_columns[]",
+ type: "hidden"
+ })
+ .val(col_name);
+ columns[columns.length] = $("<li/>")
+ .addClass("placeholderDrag")
+ .text(col_name)
+ .append(hidden_input);
+ });
+
+ var col_list = $("#move_columns_dialog ul")
+ .find("li").remove().end();
+ for (var i in columns) {
+ col_list.append(columns[i]);
+ }
+ col_list.sortable({
+ axis: 'y',
+ containment: $("#move_columns_dialog div"),
+ tolerance: 'pointer'
+ }).disableSelection();
+ var $form = $("#move_columns_dialog form");
+ $form.data("serialized-unmoved", $form.serialize());
+
+ $("#move_columns_dialog").dialog({
+ modal: true,
+ buttons: button_options,
+ beforeClose: function () {
+ $("#move_columns_anchor").removeClass("move-active");
+ }
+ });
+ });
+});
+
+/** Handler for "More" dropdown in structure table rows */
+AJAX.registerOnload('tbl_structure.js', function () {
+ if ($('#fieldsForm').hasClass('HideStructureActions')) {
+ $('#fieldsForm ul.table-structure-actions').menuResizer(PMA_tbl_structure_menu_resizer_callback);
+ }
+});
+AJAX.registerTeardown('tbl_structure.js', function () {
+ $('#fieldsForm ul.table-structure-actions').menuResizer('destroy');
+});
+$(function () {
+ $(window).resize($.throttle(function () {
+ var $list = $('#fieldsForm ul.table-structure-actions');
+ if ($list.length) {
+ $list.menuResizer('resize');
+ }
+ }));
+});
diff --git a/js/tbl_zoom_plot_jqplot.js b/js/tbl_zoom_plot_jqplot.js
new file mode 100644
index 0000000000..aba6bc2e4b
--- /dev/null
+++ b/js/tbl_zoom_plot_jqplot.js
@@ -0,0 +1,622 @@
+// TODO: change the axis
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ ** @fileoverview JavaScript functions used on tbl_select.php
+ **
+ ** @requires jQuery
+ ** @requires js/functions.js
+ **/
+
+
+/**
+ ** Display Help/Info
+ **/
+function displayHelp() {
+ PMA_ajaxShowMessage(PMA_messages.strDisplayHelp, 10000);
+}
+
+/**
+ ** Extend the array object for max function
+ ** @param array
+ **/
+Array.max = function (array) {
+ return Math.max.apply(Math, array);
+};
+
+/**
+ ** Extend the array object for min function
+ ** @param array
+ **/
+Array.min = function (array) {
+ return Math.min.apply(Math, array);
+};
+
+/**
+ ** Checks if a string contains only numeric value
+ ** @param n: String (to be checked)
+ **/
+function isNumeric(n) {
+ return !isNaN(parseFloat(n)) && isFinite(n);
+}
+
+/**
+ ** Checks if an object is empty
+ ** @param n: Object (to be checked)
+ **/
+function isEmpty(obj) {
+ var name;
+ for (name in obj) {
+ return false;
+ }
+ return true;
+}
+
+/**
+ ** Converts a date/time into timestamp
+ ** @param val String Date
+ ** @param type Sring Field type(datetime/timestamp/time/date)
+ **/
+function getTimeStamp(val, type) {
+ if (type.toString().search(/datetime/i) != -1 ||
+ type.toString().search(/timestamp/i) != -1
+ ) {
+ return $.datepicker.parseDateTime('yy-mm-dd', 'HH:mm:ss', val);
+ }
+ else if (type.toString().search(/time/i) != -1) {
+ return $.datepicker.parseDateTime('yy-mm-dd', 'HH:mm:ss', '1970-01-01 ' + val);
+ }
+ else if (type.toString().search(/date/i) != -1) {
+ return $.datepicker.parseDate('yy-mm-dd', val);
+ }
+}
+
+/**
+ ** Classifies the field type into numeric,timeseries or text
+ ** @param field: field type (as in database structure)
+ **/
+function getType(field) {
+ if (field.toString().search(/int/i) != -1 ||
+ field.toString().search(/decimal/i) != -1 ||
+ field.toString().search(/year/i) != -1
+ ) {
+ return 'numeric';
+ } else if (field.toString().search(/time/i) != -1 ||
+ field.toString().search(/date/i) != -1
+ ) {
+ return 'time';
+ } else {
+ return 'text';
+ }
+}
+/**
+ ** Converts a categorical array into numeric array
+ ** @param array categorical values array
+ **/
+function getCord(arr) {
+ var newCord = [];
+ var original = $.extend(true, [], arr);
+ arr = jQuery.unique(arr).sort();
+ $.each(original, function (index, value) {
+ newCord.push(jQuery.inArray(value, arr));
+ });
+ return [newCord, arr, original];
+}
+
+/**
+ ** Scrolls the view to the display section
+ **/
+function scrollToChart() {
+ var x = $('#dataDisplay').offset().top - 100; // 100 provides buffer in viewport
+ $('html,body').animate({scrollTop: x}, 500);
+}
+
+/**
+ * Unbind all event handlers before tearing down a page
+ */
+AJAX.registerTeardown('tbl_zoom_plot_jqplot.js', function () {
+ $('#tableid_0').unbind('change');
+ $('#tableid_1').unbind('change');
+ $('#tableid_2').unbind('change');
+ $('#tableid_3').unbind('change');
+ $('#inputFormSubmitId').unbind('click');
+ $('#togglesearchformlink').unbind('click');
+ $("#dataDisplay").find(':input').die('keydown');
+ $('button.button-reset').unbind('click');
+ $('div#resizer').unbind('resizestop');
+ $('div#querychart').unbind('jqplotDataClick');
+});
+
+AJAX.registerOnload('tbl_zoom_plot_jqplot.js', function () {
+ var cursorMode = ($("input[name='mode']:checked").val() == 'edit') ? 'crosshair' : 'pointer';
+ var currentChart = null;
+ var searchedDataKey = null;
+ var xLabel = $('#tableid_0').val();
+ var yLabel = $('#tableid_1').val();
+ // will be updated via Ajax
+ var xType = $('#types_0').val();
+ var yType = $('#types_1').val();
+ var dataLabel = $('#dataLabel').val();
+ var lastX;
+ var lastY;
+ var zoomRatio = 1;
+
+
+ // Get query result
+ var searchedData = jQuery.parseJSON($('#querydata').html());
+
+ /**
+ ** Input form submit on field change
+ **/
+
+ // first column choice corresponds to the X axis
+ $('#tableid_0').change(function () {
+ //AJAX request for field type, collation, operators, and value field
+ $.post('tbl_zoom_select.php', {
+ 'ajax_request' : true,
+ 'change_tbl_info' : true,
+ 'db' : PMA_commonParams.get('db'),
+ 'table' : PMA_commonParams.get('table'),
+ 'field' : $('#tableid_0').val(),
+ 'it' : 0,
+ 'token' : PMA_commonParams.get('token')
+ }, function (data) {
+ $('#tableFieldsId tr:eq(1) td:eq(0)').html(data.field_type);
+ $('#tableFieldsId tr:eq(1) td:eq(1)').html(data.field_collation);
+ $('#tableFieldsId tr:eq(1) td:eq(2)').html(data.field_operators);
+ $('#tableFieldsId tr:eq(1) td:eq(3)').html(data.field_value);
+ xLabel = $('#tableid_0').val();
+ $('#types_0').val(data.field_type);
+ xType = data.field_type;
+ $('#collations_0').val(data.field_collations);
+ addDateTimePicker();
+ });
+ });
+
+ // second column choice corresponds to the Y axis
+ $('#tableid_1').change(function () {
+ //AJAX request for field type, collation, operators, and value field
+ $.post('tbl_zoom_select.php', {
+ 'ajax_request' : true,
+ 'change_tbl_info' : true,
+ 'db' : PMA_commonParams.get('db'),
+ 'table' : PMA_commonParams.get('table'),
+ 'field' : $('#tableid_1').val(),
+ 'it' : 1,
+ 'token' : PMA_commonParams.get('token')
+ }, function (data) {
+ $('#tableFieldsId tr:eq(3) td:eq(0)').html(data.field_type);
+ $('#tableFieldsId tr:eq(3) td:eq(1)').html(data.field_collation);
+ $('#tableFieldsId tr:eq(3) td:eq(2)').html(data.field_operators);
+ $('#tableFieldsId tr:eq(3) td:eq(3)').html(data.field_value);
+ yLabel = $('#tableid_1').val();
+ $('#types_1').val(data.field_type);
+ yType = data.field_type;
+ $('#collations_1').val(data.field_collations);
+ addDateTimePicker();
+ });
+ });
+
+ $('#tableid_2').change(function () {
+ //AJAX request for field type, collation, operators, and value field
+ $.post('tbl_zoom_select.php', {
+ 'ajax_request' : true,
+ 'change_tbl_info' : true,
+ 'db' : PMA_commonParams.get('db'),
+ 'table' : PMA_commonParams.get('table'),
+ 'field' : $('#tableid_2').val(),
+ 'it' : 2,
+ 'token' : PMA_commonParams.get('token')
+ }, function (data) {
+ $('#tableFieldsId tr:eq(6) td:eq(0)').html(data.field_type);
+ $('#tableFieldsId tr:eq(6) td:eq(1)').html(data.field_collation);
+ $('#tableFieldsId tr:eq(6) td:eq(2)').html(data.field_operators);
+ $('#tableFieldsId tr:eq(6) td:eq(3)').html(data.field_value);
+ $('#types_2').val(data.field_type);
+ $('#collations_2').val(data.field_collations);
+ addDateTimePicker();
+ });
+ });
+
+ $('#tableid_3').change(function () {
+ //AJAX request for field type, collation, operators, and value field
+ $.post('tbl_zoom_select.php', {
+ 'ajax_request' : true,
+ 'change_tbl_info' : true,
+ 'db' : PMA_commonParams.get('db'),
+ 'table' : PMA_commonParams.get('table'),
+ 'field' : $('#tableid_3').val(),
+ 'it' : 3,
+ 'token' : PMA_commonParams.get('token')
+ }, function (data) {
+ $('#tableFieldsId tr:eq(8) td:eq(0)').html(data.field_type);
+ $('#tableFieldsId tr:eq(8) td:eq(1)').html(data.field_collation);
+ $('#tableFieldsId tr:eq(8) td:eq(2)').html(data.field_operators);
+ $('#tableFieldsId tr:eq(8) td:eq(3)').html(data.field_value);
+ $('#types_3').val(data.field_type);
+ $('#collations_3').val(data.field_collations);
+ addDateTimePicker();
+ });
+ });
+
+ /**
+ * Input form validation
+ **/
+ $('#inputFormSubmitId').click(function () {
+ if ($('#tableid_0').get(0).selectedIndex === 0 || $('#tableid_1').get(0).selectedIndex === 0) {
+ PMA_ajaxShowMessage(PMA_messages.strInputNull);
+ } else if (xLabel == yLabel) {
+ PMA_ajaxShowMessage(PMA_messages.strSameInputs);
+ }
+ });
+
+ /**
+ ** Prepare a div containing a link, otherwise it's incorrectly displayed
+ ** after a couple of clicks
+ **/
+ $('<div id="togglesearchformdiv"><a id="togglesearchformlink"></a></div>')
+ .insertAfter('#zoom_search_form')
+ // don't show it until we have results on-screen
+ .hide();
+
+ $('#togglesearchformlink')
+ .html(PMA_messages.strShowSearchCriteria)
+ .bind('click', function () {
+ var $link = $(this);
+ $('#zoom_search_form').slideToggle();
+ if ($link.text() == PMA_messages.strHideSearchCriteria) {
+ $link.text(PMA_messages.strShowSearchCriteria);
+ } else {
+ $link.text(PMA_messages.strHideSearchCriteria);
+ }
+ // avoid default click action
+ return false;
+ });
+
+ /**
+ ** Set dialog properties for the data display form
+ **/
+ var buttonOptions = {};
+ /*
+ * Handle saving of a row in the editor
+ */
+ buttonOptions[PMA_messages.strSave] = function () {
+ //Find changed values by comparing form values with selectedRow Object
+ var newValues = {};//Stores the values changed from original
+ var sqlTypes = {};
+ var it = 0;
+ var xChange = false;
+ var yChange = false;
+ var key;
+ for (key in selectedRow) {
+ var oldVal = selectedRow[key];
+ var newVal = ($('#edit_fields_null_id_' + it).prop('checked')) ? null : $('#edit_fieldID_' + it).val();
+ if (newVal instanceof Array) { // when the column is of type SET
+ newVal = $('#edit_fieldID_' + it).map(function () {
+ return $(this).val();
+ }).get().join(",");
+ }
+ if (oldVal != newVal) {
+ selectedRow[key] = newVal;
+ newValues[key] = newVal;
+ if (key == xLabel) {
+ xChange = true;
+ searchedData[searchedDataKey][xLabel] = newVal;
+ } else if (key == yLabel) {
+ yChange = true;
+ searchedData[searchedDataKey][yLabel] = newVal;
+ }
+ }
+ var $input = $('#edit_fieldID_' + it);
+ if ($input.hasClass('bit')) {
+ sqlTypes[key] = 'bit';
+ }
+ it++;
+ } //End data update
+
+ //Update the chart series and replot
+ if (xChange || yChange) {
+ //Logic similar to plot generation, replot only if xAxis changes or yAxis changes.
+ //Code includes a lot of checks so as to replot only when necessary
+ if (xChange) {
+ xCord[searchedDataKey] = selectedRow[xLabel];
+ // [searchedDataKey][0] contains the x value
+ if (xType == 'numeric') {
+ series[0][searchedDataKey][0] = selectedRow[xLabel];
+ } else if (xType == 'time') {
+ series[0][searchedDataKey][0] =
+ getTimeStamp(selectedRow[xLabel], $('#types_0').val());
+ } else {
+ // TODO: text values
+ }
+ currentChart.series[0].data = series[0];
+ // TODO: axis changing
+ currentChart.replot();
+
+ }
+ if (yChange) {
+ yCord[searchedDataKey] = selectedRow[yLabel];
+ // [searchedDataKey][1] contains the y value
+ if (yType == 'numeric') {
+ series[0][searchedDataKey][1] = selectedRow[yLabel];
+ } else if (yType == 'time') {
+ series[0][searchedDataKey][1] =
+ getTimeStamp(selectedRow[yLabel], $('#types_1').val());
+ } else {
+ // TODO: text values
+ }
+ currentChart.series[0].data = series[0];
+ // TODO: axis changing
+ currentChart.replot();
+ }
+ } //End plot update
+
+ //Generate SQL query for update
+ if (!isEmpty(newValues)) {
+ var sql_query = 'UPDATE `' + PMA_commonParams.get('table') + '` SET ';
+ for (key in newValues) {
+ sql_query += '`' + key + '`=';
+ var value = newValues[key];
+
+ // null
+ if (value === null) {
+ sql_query += 'NULL, ';
+
+ // empty
+ } else if ($.trim(value) === '') {
+ sql_query += "'', ";
+
+ // other
+ } else {
+ // type explicitly identified
+ if (sqlTypes[key] !== null) {
+ if (sqlTypes[key] == 'bit') {
+ sql_query += "b'" + value + "', ";
+ }
+ // type not explicitly identified
+ } else {
+ if (!isNumeric(value)) {
+ sql_query += "'" + value + "', ";
+ } else {
+ sql_query += value + ', ';
+ }
+ }
+ }
+ }
+ // remove two extraneous characters ', '
+ sql_query = sql_query.substring(0, sql_query.length - 2);
+ sql_query += ' WHERE ' + PMA_urldecode(searchedData[searchedDataKey].where_clause);
+
+ //Post SQL query to sql.php
+ $.post('sql.php', {
+ 'token' : PMA_commonParams.get('token'),
+ 'db' : PMA_commonParams.get('db'),
+ 'ajax_request' : true,
+ 'sql_query' : sql_query,
+ 'inline_edit' : false
+ }, function (data) {
+ if (data.success === true) {
+ $('#sqlqueryresults').html(data.sql_query);
+ $("#sqlqueryresults").trigger('appendAnchor');
+ } else {
+ PMA_ajaxShowMessage(data.error, false);
+ }
+ }); //End $.post
+ }//End database update
+ $("#dataDisplay").dialog('close');
+ };
+ buttonOptions[PMA_messages.strCancel] = function () {
+ $(this).dialog('close');
+ };
+ $("#dataDisplay").dialog({
+ autoOpen: false,
+ title: PMA_messages.strDataPointContent,
+ modal: true,
+ buttons: buttonOptions,
+ width: $('#dataDisplay').width() + 80,
+ open: function () {
+ $(this).find('input[type=checkbox]').css('margin', '0.5em');
+ }
+ });
+ /**
+ * Attach Ajax event handlers for input fields
+ * in the dialog. Used to submit the Ajax
+ * request when the ENTER key is pressed.
+ */
+ $("#dataDisplay").find(':input').live('keydown', function (e) {
+ if (e.which === 13) { // 13 is the ENTER key
+ e.preventDefault();
+ if (typeof buttonOptions[PMA_messages.strSave] === 'function') {
+ buttonOptions[PMA_messages.strSave].call();
+ }
+ }
+ });
+
+
+ /*
+ * Generate plot using jqplot
+ */
+
+ if (searchedData !== null) {
+ $('#zoom_search_form')
+ .slideToggle()
+ .hide();
+ $('#togglesearchformlink')
+ .text(PMA_messages.strShowSearchCriteria);
+ $('#togglesearchformdiv').show();
+ var selectedRow;
+ var colorCodes = ['#FF0000', '#00FFFF', '#0000FF', '#0000A0', '#FF0080', '#800080', '#FFFF00', '#00FF00', '#FF00FF'];
+ var series = [];
+ var xCord = [];
+ var yCord = [];
+ var tempX, tempY;
+ var it = 0;
+ var xMax; // xAxis extreme max
+ var xMin; // xAxis extreme min
+ var yMax; // yAxis extreme max
+ var yMin; // yAxis extreme min
+ var xVal;
+ var yVal;
+ var format;
+
+ var options = {
+ series: [
+ // for a scatter plot
+ { showLine: false }
+ ],
+ grid: {
+ drawBorder: false,
+ shadow: false,
+ background: 'rgba(0,0,0,0)'
+ },
+ axes: {
+ xaxis: {
+ label: $('#tableid_0').val(),
+ labelRenderer: $.jqplot.CanvasAxisLabelRenderer
+ },
+ yaxis: {
+ label: $('#tableid_1').val(),
+ labelRenderer: $.jqplot.CanvasAxisLabelRenderer
+ }
+ },
+ highlighter: {
+ show: true,
+ tooltipAxes: 'y',
+ yvalues: 2,
+ // hide the first y value
+ formatString: '<span class="hide">%s</span>%s'
+ },
+ cursor: {
+ show: true,
+ zoom: true,
+ showTooltip: false
+ }
+ };
+
+ // If data label is not set, do not show tooltips
+ if (dataLabel === '') {
+ options.highlighter.show = false;
+ }
+
+ // Classify types as either numeric,time,text
+ xType = getType(xType);
+ yType = getType(yType);
+
+ // could have multiple series but we'll have just one
+ series[0] = [];
+
+ if (xType == 'time') {
+ var originalXType = $('#types_0').val();
+ if (originalXType == 'date') {
+ format = '%Y-%m-%d';
+ }
+ // TODO: does not seem to work
+ //else if (originalXType == 'time') {
+ // format = '%H:%M';
+ //} else {
+ // format = '%Y-%m-%d %H:%M';
+ //}
+ $.extend(options.axes.xaxis, {
+ renderer: $.jqplot.DateAxisRenderer,
+ tickOptions: {
+ formatString: format
+ }
+ });
+ }
+ if (yType == 'time') {
+ var originalYType = $('#types_1').val();
+ if (originalYType == 'date') {
+ format = '%Y-%m-%d';
+ }
+ $.extend(options.axes.yaxis, {
+ renderer: $.jqplot.DateAxisRenderer,
+ tickOptions: {
+ formatString: format
+ }
+ });
+ }
+
+ $.each(searchedData, function (key, value) {
+ if (xType == 'numeric') {
+ xVal = parseFloat(value[xLabel]);
+ }
+ if (xType == 'time') {
+ xVal = getTimeStamp(value[xLabel], originalXType);
+ }
+ if (yType == 'numeric') {
+ yVal = parseFloat(value[yLabel]);
+ }
+ if (yType == 'time') {
+ yVal = getTimeStamp(value[yLabel], originalYType);
+ }
+ series[0].push([
+ xVal,
+ yVal,
+ // extra Y values
+ value[dataLabel], // for highlighter
+ // (may set an undefined value)
+ value.where_clause, // for click on point
+ key // key from searchedData
+ ]);
+ });
+
+ // under IE 8, the initial display is mangled; after a manual
+ // resizing, it's ok
+ // under IE 9, everything is fine
+ currentChart = $.jqplot('querychart', series, options);
+ currentChart.resetZoom();
+
+ $('button.button-reset').click(function (event) {
+ event.preventDefault();
+ currentChart.resetZoom();
+ });
+
+ $('div#resizer').resizable();
+ $('div#resizer').bind('resizestop', function (event, ui) {
+ // make room so that the handle will still appear
+ $('div#querychart').height($('div#resizer').height() * 0.96);
+ $('div#querychart').width($('div#resizer').width() * 0.96);
+ currentChart.replot({resetAxes: true});
+ });
+
+ $('div#querychart').bind('jqplotDataClick',
+ function (event, seriesIndex, pointIndex, data) {
+ searchedDataKey = data[4]; // key from searchedData (global)
+ var field_id = 0;
+ var post_params = {
+ 'ajax_request' : true,
+ 'get_data_row' : true,
+ 'db' : PMA_commonParams.get('db'),
+ 'table' : PMA_commonParams.get('table'),
+ 'where_clause' : data[3],
+ 'token' : PMA_commonParams.get('token')
+ };
+
+ $.post('tbl_zoom_select.php', post_params, function (data) {
+ // Row is contained in data.row_info,
+ // now fill the displayResultForm with row values
+ var key;
+ for (key in data.row_info) {
+ $field = $('#edit_fieldID_' + field_id);
+ $field_null = $('#edit_fields_null_id_' + field_id);
+ if (data.row_info[key] === null) {
+ $field_null.prop('checked', true);
+ $field.val('');
+ } else {
+ $field_null.prop('checked', false);
+ if ($field.attr('multiple')) { // when the column is of type SET
+ $field.val(data.row_info[key].split(','));
+ } else {
+ $field.val(data.row_info[key]);
+ }
+ }
+ field_id++;
+ }
+ selectedRow = {};
+ selectedRow = data.row_info;
+ });
+
+ $("#dataDisplay").dialog("open");
+ }
+ );
+ }
+});
diff --git a/js/tracekit/tracekit.js b/js/tracekit/tracekit.js
new file mode 100644
index 0000000000..78bb33be54
--- /dev/null
+++ b/js/tracekit/tracekit.js
@@ -0,0 +1,1114 @@
+/*
+ TraceKit - Cross brower stack traces - github.com/occ/TraceKit
+ MIT license
+*/
+
+;(function(window, undefined) {
+
+
+var TraceKit = {};
+var _oldTraceKit = window.TraceKit;
+
+// global reference to slice
+var _slice = [].slice;
+var UNKNOWN_FUNCTION = '?';
+
+
+/**
+ * _has, a better form of hasOwnProperty
+ * Example: _has(MainHostObject, property) === true/false
+ *
+ * @param {Object} host object to check property
+ * @param {string} key to check
+ */
+function _has(object, key) {
+ return Object.prototype.hasOwnProperty.call(object, key);
+}
+
+function _isUndefined(what) {
+ return typeof what === 'undefined';
+}
+
+/**
+ * TraceKit.noConflict: Export TraceKit out to another variable
+ * Example: var TK = TraceKit.noConflict()
+ */
+TraceKit.noConflict = function noConflict() {
+ window.TraceKit = _oldTraceKit;
+ return TraceKit;
+};
+
+/**
+ * TraceKit.wrap: Wrap any function in a TraceKit reporter
+ * Example: func = TraceKit.wrap(func);
+ *
+ * @param {Function} func Function to be wrapped
+ * @return {Function} The wrapped func
+ */
+TraceKit.wrap = function traceKitWrapper(func) {
+ function wrapped() {
+ try {
+ return func.apply(this, arguments);
+ } catch (e) {
+ TraceKit.report(e);
+ throw e;
+ }
+ }
+ return wrapped;
+};
+
+/**
+ * TraceKit.report: cross-browser processing of unhandled exceptions
+ *
+ * Syntax:
+ * TraceKit.report.subscribe(function(stackInfo) { ... })
+ * TraceKit.report.unsubscribe(function(stackInfo) { ... })
+ * TraceKit.report(exception)
+ * try { ...code... } catch(ex) { TraceKit.report(ex); }
+ *
+ * Supports:
+ * - Firefox: full stack trace with line numbers, plus column number
+ * on top frame; column number is not guaranteed
+ * - Opera: full stack trace with line and column numbers
+ * - Chrome: full stack trace with line and column numbers
+ * - Safari: line and column number for the top frame only; some frames
+ * may be missing, and column number is not guaranteed
+ * - IE: line and column number for the top frame only; some frames
+ * may be missing, and column number is not guaranteed
+ *
+ * In theory, TraceKit should work on all of the following versions:
+ * - IE5.5+ (only 8.0 tested)
+ * - Firefox 0.9+ (only 3.5+ tested)
+ * - Opera 7+ (only 10.50 tested; versions 9 and earlier may require
+ * Exceptions Have Stacktrace to be enabled in opera:config)
+ * - Safari 3+ (only 4+ tested)
+ * - Chrome 1+ (only 5+ tested)
+ * - Konqueror 3.5+ (untested)
+ *
+ * Requires TraceKit.computeStackTrace.
+ *
+ * Tries to catch all unhandled exceptions and report them to the
+ * subscribed handlers. Please note that TraceKit.report will rethrow the
+ * exception. This is REQUIRED in order to get a useful stack trace in IE.
+ * If the exception does not reach the top of the browser, you will only
+ * get a stack trace from the point where TraceKit.report was called.
+ *
+ * Handlers receive a stackInfo object as described in the
+ * TraceKit.computeStackTrace docs.
+ */
+TraceKit.report = (function reportModuleWrapper() {
+ var handlers = [],
+ lastException = null,
+ lastExceptionStack = null;
+
+ /**
+ * Add a crash handler.
+ * @param {Function} handler
+ */
+ function subscribe(handler) {
+ installGlobalHandler();
+ handlers.push(handler);
+ }
+
+ /**
+ * Remove a crash handler.
+ * @param {Function} handler
+ */
+ function unsubscribe(handler) {
+ for (var i = handlers.length - 1; i >= 0; --i) {
+ if (handlers[i] === handler) {
+ handlers.splice(i, 1);
+ }
+ }
+ }
+
+ /**
+ * Dispatch stack information to all handlers.
+ * @param {Object.<string, *>} stack
+ */
+ function notifyHandlers(stack, windowError) {
+ var exception = null;
+ if (windowError && !TraceKit.collectWindowErrors) {
+ return;
+ }
+ for (var i in handlers) {
+ if (_has(handlers, i)) {
+ try {
+ handlers[i].apply(null, [stack].concat(_slice.call(arguments, 2)));
+ } catch (inner) {
+ exception = inner;
+ }
+ }
+ }
+
+ if (exception) {
+ throw exception;
+ }
+ }
+
+ var _oldOnerrorHandler, _onErrorHandlerInstalled;
+
+ /**
+ * Ensures all global unhandled exceptions are recorded.
+ * Supported by Gecko and IE.
+ * @param {string} message Error message.
+ * @param {string} url URL of script that generated the exception.
+ * @param {(number|string)} lineNo The line number at which the error
+ * occurred.
+ */
+ function traceKitWindowOnError(message, url, lineNo) {
+ var stack = null;
+
+ if (lastExceptionStack) {
+ TraceKit.computeStackTrace.augmentStackTraceWithInitialElement(lastExceptionStack, url, lineNo, message);
+ stack = lastExceptionStack;
+ lastExceptionStack = null;
+ lastException = null;
+ } else {
+ var location = {
+ 'url': url,
+ 'line': lineNo
+ };
+ location.func = TraceKit.computeStackTrace.guessFunctionName(location.url, location.line);
+ location.context = TraceKit.computeStackTrace.gatherContext(location.url, location.line);
+ stack = {
+ 'mode': 'onerror',
+ 'message': message,
+ 'url': document.location.href,
+ 'stack': [location],
+ 'useragent': navigator.userAgent
+ };
+ }
+
+ notifyHandlers(stack, 'from window.onerror');
+
+ if (_oldOnerrorHandler) {
+ return _oldOnerrorHandler.apply(this, arguments);
+ }
+
+ return false;
+ }
+
+ function installGlobalHandler ()
+ {
+ if (_onErrorHandlerInstalled === true) {
+ return;
+ }
+ _oldOnerrorHandler = window.onerror;
+ window.onerror = traceKitWindowOnError;
+ _onErrorHandlerInstalled = true;
+ }
+
+ /**
+ * Reports an unhandled Error to TraceKit.
+ * @param {Error} ex
+ */
+ function report(ex) {
+ var args = _slice.call(arguments, 1);
+ if (lastExceptionStack) {
+ if (lastException === ex) {
+ return; // already caught by an inner catch block, ignore
+ } else {
+ var s = lastExceptionStack;
+ lastExceptionStack = null;
+ lastException = null;
+ notifyHandlers.apply(null, [s, null].concat(args));
+ }
+ }
+
+ var stack = TraceKit.computeStackTrace(ex);
+ lastExceptionStack = stack;
+ lastException = ex;
+
+ // If the stack trace is incomplete, wait for 2 seconds for
+ // slow slow IE to see if onerror occurs or not before reporting
+ // this exception; otherwise, we will end up with an incomplete
+ // stack trace
+ window.setTimeout(function () {
+ if (lastException === ex) {
+ lastExceptionStack = null;
+ lastException = null;
+ notifyHandlers.apply(null, [stack, null].concat(args));
+ }
+ }, (stack.incomplete ? 2000 : 0));
+
+ throw ex; // re-throw to propagate to the top level (and cause window.onerror)
+ }
+
+ report.subscribe = subscribe;
+ report.unsubscribe = unsubscribe;
+ return report;
+}());
+
+/**
+ * TraceKit.computeStackTrace: cross-browser stack traces in JavaScript
+ *
+ * Syntax:
+ * s = TraceKit.computeStackTrace.ofCaller([depth])
+ * s = TraceKit.computeStackTrace(exception) // consider using TraceKit.report instead (see below)
+ * Returns:
+ * s.name - exception name
+ * s.message - exception message
+ * s.stack[i].url - JavaScript or HTML file URL
+ * s.stack[i].func - function name, or empty for anonymous functions (if guessing did not work)
+ * s.stack[i].args - arguments passed to the function, if known
+ * s.stack[i].line - line number, if known
+ * s.stack[i].column - column number, if known
+ * s.stack[i].context - an array of source code lines; the middle element corresponds to the correct line#
+ * s.mode - 'stack', 'stacktrace', 'multiline', 'callers', 'onerror', or 'failed' -- method used to collect the stack trace
+ *
+ * Supports:
+ * - Firefox: full stack trace with line numbers and unreliable column
+ * number on top frame
+ * - Opera 10: full stack trace with line and column numbers
+ * - Opera 9-: full stack trace with line numbers
+ * - Chrome: full stack trace with line and column numbers
+ * - Safari: line and column number for the topmost stacktrace element
+ * only
+ * - IE: no line numbers whatsoever
+ *
+ * Tries to guess names of anonymous functions by looking for assignments
+ * in the source code. In IE and Safari, we have to guess source file names
+ * by searching for function bodies inside all page scripts. This will not
+ * work for scripts that are loaded cross-domain.
+ * Here be dragons: some function names may be guessed incorrectly, and
+ * duplicate functions may be mismatched.
+ *
+ * TraceKit.computeStackTrace should only be used for tracing purposes.
+ * Logging of unhandled exceptions should be done with TraceKit.report,
+ * which builds on top of TraceKit.computeStackTrace and provides better
+ * IE support by utilizing the window.onerror event to retrieve information
+ * about the top of the stack.
+ *
+ * Note: In IE and Safari, no stack trace is recorded on the Error object,
+ * so computeStackTrace instead walks its *own* chain of callers.
+ * This means that:
+ * * in Safari, some methods may be missing from the stack trace;
+ * * in IE, the topmost function in the stack trace will always be the
+ * caller of computeStackTrace.
+ *
+ * This is okay for tracing (because you are likely to be calling
+ * computeStackTrace from the function you want to be the topmost element
+ * of the stack trace anyway), but not okay for logging unhandled
+ * exceptions (because your catch block will likely be far away from the
+ * inner function that actually caused the exception).
+ *
+ * Tracing example:
+ * function trace(message) {
+ * var stackInfo = TraceKit.computeStackTrace.ofCaller();
+ * var data = message + "\n";
+ * for(var i in stackInfo.stack) {
+ * var item = stackInfo.stack[i];
+ * data += (item.func || '[anonymous]') + "() in " + item.url + ":" + (item.line || '0') + "\n";
+ * }
+ * if (window.console)
+ * console.info(data);
+ * else
+ * alert(data);
+ * }
+ */
+TraceKit.computeStackTrace = (function computeStackTraceWrapper() {
+ var debug = false,
+ sourceCache = {};
+
+ /**
+ * Attempts to retrieve source code via XMLHttpRequest, which is used
+ * to look up anonymous function names.
+ * @param {string} url URL of source code.
+ * @return {string} Source contents.
+ */
+ function loadSource(url) {
+ if (!TraceKit.remoteFetching) { //Only attempt request if remoteFetching is on.
+ return '';
+ }
+ try {
+ function getXHR() {
+ try {
+ return new window.XMLHttpRequest();
+ } catch (e) {
+ // explicitly bubble up the exception if not found
+ return new window.ActiveXObject('Microsoft.XMLHTTP');
+ }
+ }
+
+ var request = getXHR();
+ request.open('GET', url, false);
+ request.send('');
+ return request.responseText;
+ } catch (e) {
+ return '';
+ }
+ }
+
+ /**
+ * Retrieves source code from the source code cache.
+ * @param {string} url URL of source code.
+ * @return {Array.<string>} Source contents.
+ */
+ function getSource(url) {
+ if (!_has(sourceCache, url)) {
+ // URL needs to be able to fetched within the acceptable domain. Otherwise,
+ // cross-domain errors will be triggered.
+ var source = '';
+ if (url.indexOf(document.domain) !== -1) {
+ source = loadSource(url);
+ }
+ sourceCache[url] = source ? source.split('\n') : [];
+ }
+
+ return sourceCache[url];
+ }
+
+ /**
+ * Tries to use an externally loaded copy of source code to determine
+ * the name of a function by looking at the name of the variable it was
+ * assigned to, if any.
+ * @param {string} url URL of source code.
+ * @param {(string|number)} lineNo Line number in source code.
+ * @return {string} The function name, if discoverable.
+ */
+ function guessFunctionName(url, lineNo) {
+ var reFunctionArgNames = /function ([^(]*)\(([^)]*)\)/,
+ reGuessFunction = /['"]?([0-9A-Za-z$_]+)['"]?\s*[:=]\s*(function|eval|new Function)/,
+ line = '',
+ maxLines = 10,
+ source = getSource(url),
+ m;
+
+ if (!source.length) {
+ return UNKNOWN_FUNCTION;
+ }
+
+ // Walk backwards from the first line in the function until we find the line which
+ // matches the pattern above, which is the function definition
+ for (var i = 0; i < maxLines; ++i) {
+ line = source[lineNo - i] + line;
+
+ if (!_isUndefined(line)) {
+ if ((m = reGuessFunction.exec(line))) {
+ return m[1];
+ } else if ((m = reFunctionArgNames.exec(line))) {
+ return m[1];
+ }
+ }
+ }
+
+ return UNKNOWN_FUNCTION;
+ }
+
+ /**
+ * Retrieves the surrounding lines from where an exception occurred.
+ * @param {string} url URL of source code.
+ * @param {(string|number)} line Line number in source code to centre
+ * around for context.
+ * @return {?Array.<string>} Lines of source code.
+ */
+ function gatherContext(url, line) {
+ var source = getSource(url);
+
+ if (!source.length) {
+ return null;
+ }
+
+ var context = [],
+ // linesBefore & linesAfter are inclusive with the offending line.
+ // if linesOfContext is even, there will be one extra line
+ // *before* the offending line.
+ linesBefore = Math.floor(TraceKit.linesOfContext / 2),
+ // Add one extra line if linesOfContext is odd
+ linesAfter = linesBefore + (TraceKit.linesOfContext % 2),
+ start = Math.max(0, line - linesBefore - 1),
+ end = Math.min(source.length, line + linesAfter - 1);
+
+ line -= 1; // convert to 0-based index
+
+ for (var i = start; i < end; ++i) {
+ if (!_isUndefined(source[i])) {
+ context.push(source[i]);
+ }
+ }
+
+ return context.length > 0 ? context : null;
+ }
+
+ /**
+ * Escapes special characters, except for whitespace, in a string to be
+ * used inside a regular expression as a string literal.
+ * @param {string} text The string.
+ * @return {string} The escaped string literal.
+ */
+ function escapeRegExp(text) {
+ return text.replace(/[\-\[\]{}()*+?.,\\\^$|#]/g, '\\$&');
+ }
+
+ /**
+ * Escapes special characters in a string to be used inside a regular
+ * expression as a string literal. Also ensures that HTML entities will
+ * be matched the same as their literal friends.
+ * @param {string} body The string.
+ * @return {string} The escaped string.
+ */
+ function escapeCodeAsRegExpForMatchingInsideHTML(body) {
+ return escapeRegExp(body).replace('<', '(?:<|&lt;)').replace('>', '(?:>|&gt;)').replace('&', '(?:&|&amp;)').replace('"', '(?:"|&quot;)').replace(/\s+/g, '\\s+');
+ }
+
+ /**
+ * Determines where a code fragment occurs in the source code.
+ * @param {RegExp} re The function definition.
+ * @param {Array.<string>} urls A list of URLs to search.
+ * @return {?Object.<string, (string|number)>} An object containing
+ * the url, line, and column number of the defined function.
+ */
+ function findSourceInUrls(re, urls) {
+ var source, m;
+ for (var i = 0, j = urls.length; i < j; ++i) {
+ // console.log('searching', urls[i]);
+ if ((source = getSource(urls[i])).length) {
+ source = source.join('\n');
+ if ((m = re.exec(source))) {
+ // console.log('Found function in ' + urls[i]);
+
+ return {
+ 'url': urls[i],
+ 'line': source.substring(0, m.index).split('\n').length,
+ 'column': m.index - source.lastIndexOf('\n', m.index) - 1
+ };
+ }
+ }
+ }
+
+ // console.log('no match');
+
+ return null;
+ }
+
+ /**
+ * Determines at which column a code fragment occurs on a line of the
+ * source code.
+ * @param {string} fragment The code fragment.
+ * @param {string} url The URL to search.
+ * @param {(string|number)} line The line number to examine.
+ * @return {?number} The column number.
+ */
+ function findSourceInLine(fragment, url, line) {
+ var source = getSource(url),
+ re = new RegExp('\\b' + escapeRegExp(fragment) + '\\b'),
+ m;
+
+ line -= 1;
+
+ if (source && source.length > line && (m = re.exec(source[line]))) {
+ return m.index;
+ }
+
+ return null;
+ }
+
+ /**
+ * Determines where a function was defined within the source code.
+ * @param {(Function|string)} func A function reference or serialized
+ * function definition.
+ * @return {?Object.<string, (string|number)>} An object containing
+ * the url, line, and column number of the defined function.
+ */
+ function findSourceByFunctionBody(func) {
+ var urls = [window.location.href],
+ scripts = document.getElementsByTagName('script'),
+ body,
+ code = '' + func,
+ codeRE = /^function(?:\s+([\w$]+))?\s*\(([\w\s,]*)\)\s*\{\s*(\S[\s\S]*\S)\s*\}\s*$/,
+ eventRE = /^function on([\w$]+)\s*\(event\)\s*\{\s*(\S[\s\S]*\S)\s*\}\s*$/,
+ re,
+ parts,
+ result;
+
+ for (var i = 0; i < scripts.length; ++i) {
+ var script = scripts[i];
+ if (script.src) {
+ urls.push(script.src);
+ }
+ }
+
+ if (!(parts = codeRE.exec(code))) {
+ re = new RegExp(escapeRegExp(code).replace(/\s+/g, '\\s+'));
+ }
+
+ // not sure if this is really necessary, but I don’t have a test
+ // corpus large enough to confirm that and it was in the original.
+ else {
+ var name = parts[1] ? '\\s+' + parts[1] : '',
+ args = parts[2].split(',').join('\\s*,\\s*');
+
+ body = escapeRegExp(parts[3]).replace(/;$/, ';?'); // semicolon is inserted if the function ends with a comment.replace(/\s+/g, '\\s+');
+ re = new RegExp('function' + name + '\\s*\\(\\s*' + args + '\\s*\\)\\s*{\\s*' + body + '\\s*}');
+ }
+
+ // look for a normal function definition
+ if ((result = findSourceInUrls(re, urls))) {
+ return result;
+ }
+
+ // look for an old-school event handler function
+ if ((parts = eventRE.exec(code))) {
+ var event = parts[1];
+ body = escapeCodeAsRegExpForMatchingInsideHTML(parts[2]);
+
+ // look for a function defined in HTML as an onXXX handler
+ re = new RegExp('on' + event + '=[\\\'"]\\s*' + body + '\\s*[\\\'"]', 'i');
+
+ if ((result = findSourceInUrls(re, urls[0]))) {
+ return result;
+ }
+
+ // look for ???
+ re = new RegExp(body);
+
+ if ((result = findSourceInUrls(re, urls))) {
+ return result;
+ }
+ }
+
+ return null;
+ }
+
+ // Contents of Exception in various browsers.
+ //
+ // SAFARI:
+ // ex.message = Can't find variable: qq
+ // ex.line = 59
+ // ex.sourceId = 580238192
+ // ex.sourceURL = http://...
+ // ex.expressionBeginOffset = 96
+ // ex.expressionCaretOffset = 98
+ // ex.expressionEndOffset = 98
+ // ex.name = ReferenceError
+ //
+ // FIREFOX:
+ // ex.message = qq is not defined
+ // ex.fileName = http://...
+ // ex.lineNumber = 59
+ // ex.stack = ...stack trace... (see the example below)
+ // ex.name = ReferenceError
+ //
+ // CHROME:
+ // ex.message = qq is not defined
+ // ex.name = ReferenceError
+ // ex.type = not_defined
+ // ex.arguments = ['aa']
+ // ex.stack = ...stack trace...
+ //
+ // INTERNET EXPLORER:
+ // ex.message = ...
+ // ex.name = ReferenceError
+ //
+ // OPERA:
+ // ex.message = ...message... (see the example below)
+ // ex.name = ReferenceError
+ // ex.opera#sourceloc = 11 (pretty much useless, duplicates the info in ex.message)
+ // ex.stacktrace = n/a; see 'opera:config#UserPrefs|Exceptions Have Stacktrace'
+
+ /**
+ * Computes stack trace information from the stack property.
+ * Chrome and Gecko use this property.
+ * @param {Error} ex
+ * @return {?Object.<string, *>} Stack trace information.
+ */
+ function computeStackTraceFromStackProp(ex) {
+ if (!ex.stack) {
+ return null;
+ }
+
+ var chrome = /^\s*at (?:((?:\[object object\])?\S+(?: \[as \S+\])?) )?\(?((?:file|http|https):.*?):(\d+)(?::(\d+))?\)?\s*$/i,
+ gecko = /^\s*(\S*)(?:\((.*?)\))?@((?:file|http|https).*?):(\d+)(?::(\d+))?\s*$/i,
+ lines = ex.stack.split('\n'),
+ stack = [],
+ parts,
+ element,
+ reference = /^(.*) is undefined$/.exec(ex.message);
+
+ for (var i = 0, j = lines.length; i < j; ++i) {
+ if ((parts = gecko.exec(lines[i]))) {
+ element = {
+ 'url': parts[3],
+ 'func': parts[1] || UNKNOWN_FUNCTION,
+ 'args': parts[2] ? parts[2].split(',') : '',
+ 'line': +parts[4],
+ 'column': parts[5] ? +parts[5] : null
+ };
+ } else if ((parts = chrome.exec(lines[i]))) {
+ element = {
+ 'url': parts[2],
+ 'func': parts[1] || UNKNOWN_FUNCTION,
+ 'line': +parts[3],
+ 'column': parts[4] ? +parts[4] : null
+ };
+ } else {
+ continue;
+ }
+
+ if (!element.func && element.line) {
+ element.func = guessFunctionName(element.url, element.line);
+ }
+
+ if (element.line) {
+ element.context = gatherContext(element.url, element.line);
+ }
+
+ stack.push(element);
+ }
+
+ if (stack[0] && stack[0].line && !stack[0].column && reference) {
+ stack[0].column = findSourceInLine(reference[1], stack[0].url, stack[0].line);
+ }
+
+ if (!stack.length) {
+ return null;
+ }
+
+ return {
+ 'mode': 'stack',
+ 'name': ex.name,
+ 'message': ex.message,
+ 'url': document.location.href,
+ 'stack': stack,
+ 'useragent': navigator.userAgent
+ };
+ }
+
+ /**
+ * Computes stack trace information from the stacktrace property.
+ * Opera 10 uses this property.
+ * @param {Error} ex
+ * @return {?Object.<string, *>} Stack trace information.
+ */
+ function computeStackTraceFromStacktraceProp(ex) {
+ // Access and store the stacktrace property before doing ANYTHING
+ // else to it because Opera is not very good at providing it
+ // reliably in other circumstances.
+ var stacktrace = ex.stacktrace;
+
+ var testRE = / line (\d+), column (\d+) in (?:<anonymous function: ([^>]+)>|([^\)]+))\((.*)\) in (.*):\s*$/i,
+ lines = stacktrace.split('\n'),
+ stack = [],
+ parts;
+
+ for (var i = 0, j = lines.length; i < j; i += 2) {
+ if ((parts = testRE.exec(lines[i]))) {
+ var element = {
+ 'line': +parts[1],
+ 'column': +parts[2],
+ 'func': parts[3] || parts[4],
+ 'args': parts[5] ? parts[5].split(',') : [],
+ 'url': parts[6]
+ };
+
+ if (!element.func && element.line) {
+ element.func = guessFunctionName(element.url, element.line);
+ }
+ if (element.line) {
+ try {
+ element.context = gatherContext(element.url, element.line);
+ } catch (exc) {}
+ }
+
+ if (!element.context) {
+ element.context = [lines[i + 1]];
+ }
+
+ stack.push(element);
+ }
+ }
+
+ if (!stack.length) {
+ return null;
+ }
+
+ return {
+ 'mode': 'stacktrace',
+ 'name': ex.name,
+ 'message': ex.message,
+ 'url': document.location.href,
+ 'stack': stack,
+ 'useragent': navigator.userAgent
+ };
+ }
+
+ /**
+ * NOT TESTED.
+ * Computes stack trace information from an error message that includes
+ * the stack trace.
+ * Opera 9 and earlier use this method if the option to show stack
+ * traces is turned on in opera:config.
+ * @param {Error} ex
+ * @return {?Object.<string, *>} Stack information.
+ */
+ function computeStackTraceFromOperaMultiLineMessage(ex) {
+ // Opera includes a stack trace into the exception message. An example is:
+ //
+ // Statement on line 3: Undefined variable: undefinedFunc
+ // Backtrace:
+ // Line 3 of linked script file://localhost/Users/andreyvit/Projects/TraceKit/javascript-client/sample.js: In function zzz
+ // undefinedFunc(a);
+ // Line 7 of inline#1 script in file://localhost/Users/andreyvit/Projects/TraceKit/javascript-client/sample.html: In function yyy
+ // zzz(x, y, z);
+ // Line 3 of inline#1 script in file://localhost/Users/andreyvit/Projects/TraceKit/javascript-client/sample.html: In function xxx
+ // yyy(a, a, a);
+ // Line 1 of function script
+ // try { xxx('hi'); return false; } catch(ex) { TraceKit.report(ex); }
+ // ...
+
+ var lines = ex.message.split('\n');
+ if (lines.length < 4) {
+ return null;
+ }
+
+ var lineRE1 = /^\s*Line (\d+) of linked script ((?:file|http|https)\S+)(?:: in function (\S+))?\s*$/i,
+ lineRE2 = /^\s*Line (\d+) of inline#(\d+) script in ((?:file|http|https)\S+)(?:: in function (\S+))?\s*$/i,
+ lineRE3 = /^\s*Line (\d+) of function script\s*$/i,
+ stack = [],
+ scripts = document.getElementsByTagName('script'),
+ inlineScriptBlocks = [],
+ parts,
+ i,
+ len,
+ source;
+
+ for (i in scripts) {
+ if (_has(scripts, i) && !scripts[i].src) {
+ inlineScriptBlocks.push(scripts[i]);
+ }
+ }
+
+ for (i = 2, len = lines.length; i < len; i += 2) {
+ var item = null;
+ if ((parts = lineRE1.exec(lines[i]))) {
+ item = {
+ 'url': parts[2],
+ 'func': parts[3],
+ 'line': +parts[1]
+ };
+ } else if ((parts = lineRE2.exec(lines[i]))) {
+ item = {
+ 'url': parts[3],
+ 'func': parts[4]
+ };
+ var relativeLine = (+parts[1]); // relative to the start of the <SCRIPT> block
+ var script = inlineScriptBlocks[parts[2] - 1];
+ if (script) {
+ source = getSource(item.url);
+ if (source) {
+ source = source.join('\n');
+ var pos = source.indexOf(script.innerText);
+ if (pos >= 0) {
+ item.line = relativeLine + source.substring(0, pos).split('\n').length;
+ }
+ }
+ }
+ } else if ((parts = lineRE3.exec(lines[i]))) {
+ var url = window.location.href.replace(/#.*$/, ''),
+ line = parts[1];
+ var re = new RegExp(escapeCodeAsRegExpForMatchingInsideHTML(lines[i + 1]));
+ source = findSourceInUrls(re, [url]);
+ item = {
+ 'url': url,
+ 'line': source ? source.line : line,
+ 'func': ''
+ };
+ }
+
+ if (item) {
+ if (!item.func) {
+ item.func = guessFunctionName(item.url, item.line);
+ }
+ var context = gatherContext(item.url, item.line);
+ var midline = (context ? context[Math.floor(context.length / 2)] : null);
+ if (context && midline.replace(/^\s*/, '') === lines[i + 1].replace(/^\s*/, '')) {
+ item.context = context;
+ } else {
+ // if (context) alert("Context mismatch. Correct midline:\n" + lines[i+1] + "\n\nMidline:\n" + midline + "\n\nContext:\n" + context.join("\n") + "\n\nURL:\n" + item.url);
+ item.context = [lines[i + 1]];
+ }
+ stack.push(item);
+ }
+ }
+ if (!stack.length) {
+ return null; // could not parse multiline exception message as Opera stack trace
+ }
+
+ return {
+ 'mode': 'multiline',
+ 'name': ex.name,
+ 'message': lines[0],
+ 'url': document.location.href,
+ 'stack': stack,
+ 'useragent': navigator.userAgent
+ };
+ }
+
+ /**
+ * Adds information about the first frame to incomplete stack traces.
+ * Safari and IE require this to get complete data on the first frame.
+ * @param {Object.<string, *>} stackInfo Stack trace information from
+ * one of the compute* methods.
+ * @param {string} url The URL of the script that caused an error.
+ * @param {(number|string)} lineNo The line number of the script that
+ * caused an error.
+ * @param {string=} message The error generated by the browser, which
+ * hopefully contains the name of the object that caused the error.
+ * @return {boolean} Whether or not the stack information was
+ * augmented.
+ */
+ function augmentStackTraceWithInitialElement(stackInfo, url, lineNo, message) {
+ var initial = {
+ 'url': url,
+ 'line': lineNo
+ };
+
+ if (initial.url && initial.line) {
+ stackInfo.incomplete = false;
+
+ if (!initial.func) {
+ initial.func = guessFunctionName(initial.url, initial.line);
+ }
+
+ if (!initial.context) {
+ initial.context = gatherContext(initial.url, initial.line);
+ }
+
+ var reference = / '([^']+)' /.exec(message);
+ if (reference) {
+ initial.column = findSourceInLine(reference[1], initial.url, initial.line);
+ }
+
+ if (stackInfo.stack.length > 0) {
+ if (stackInfo.stack[0].url === initial.url) {
+ if (stackInfo.stack[0].line === initial.line) {
+ return false; // already in stack trace
+ } else if (!stackInfo.stack[0].line && stackInfo.stack[0].func === initial.func) {
+ stackInfo.stack[0].line = initial.line;
+ stackInfo.stack[0].context = initial.context;
+ return false;
+ }
+ }
+ }
+
+ stackInfo.stack.unshift(initial);
+ stackInfo.partial = true;
+ return true;
+ } else {
+ stackInfo.incomplete = true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Computes stack trace information by walking the arguments.caller
+ * chain at the time the exception occurred. This will cause earlier
+ * frames to be missed but is the only way to get any stack trace in
+ * Safari and IE. The top frame is restored by
+ * {@link augmentStackTraceWithInitialElement}.
+ * @param {Error} ex
+ * @return {?Object.<string, *>} Stack trace information.
+ */
+ function computeStackTraceByWalkingCallerChain(ex, depth) {
+ var functionName = /function\s+([_$a-zA-Z\xA0-\uFFFF][_$a-zA-Z0-9\xA0-\uFFFF]*)?\s*\(/i,
+ stack = [],
+ funcs = {},
+ recursion = false,
+ parts,
+ item,
+ source;
+
+ for (var curr = computeStackTraceByWalkingCallerChain.caller; curr && !recursion; curr = curr.caller) {
+ if (curr === computeStackTrace || curr === TraceKit.report) {
+ // console.log('skipping internal function');
+ continue;
+ }
+
+ item = {
+ 'url': null,
+ 'func': UNKNOWN_FUNCTION,
+ 'line': null,
+ 'column': null
+ };
+
+ if (curr.name) {
+ item.func = curr.name;
+ } else if ((parts = functionName.exec(curr.toString()))) {
+ item.func = parts[1];
+ }
+
+ if ((source = findSourceByFunctionBody(curr))) {
+ item.url = source.url;
+ item.line = source.line;
+
+ if (item.func === UNKNOWN_FUNCTION) {
+ item.func = guessFunctionName(item.url, item.line);
+ }
+
+ var reference = / '([^']+)' /.exec(ex.message || ex.description);
+ if (reference) {
+ item.column = findSourceInLine(reference[1], source.url, source.line);
+ }
+ }
+
+ if (funcs['' + curr]) {
+ recursion = true;
+ }else{
+ funcs['' + curr] = true;
+ }
+
+ stack.push(item);
+ }
+
+ if (depth) {
+ // console.log('depth is ' + depth);
+ // console.log('stack is ' + stack.length);
+ stack.splice(0, depth);
+ }
+
+ var result = {
+ 'mode': 'callers',
+ 'name': ex.name,
+ 'message': ex.message,
+ 'url': document.location.href,
+ 'stack': stack,
+ 'useragent': navigator.userAgent
+ };
+ augmentStackTraceWithInitialElement(result, ex.sourceURL || ex.fileName, ex.line || ex.lineNumber, ex.message || ex.description);
+ return result;
+ }
+
+ /**
+ * Computes a stack trace for an exception.
+ * @param {Error} ex
+ * @param {(string|number)=} depth
+ */
+ function computeStackTrace(ex, depth) {
+ var stack = null;
+ depth = (depth == null ? 0 : +depth);
+
+ try {
+ // This must be tried first because Opera 10 *destroys*
+ // its stacktrace property if you try to access the stack
+ // property first!!
+ stack = computeStackTraceFromStacktraceProp(ex);
+ if (stack) {
+ return stack;
+ }
+ } catch (e) {
+ if (debug) {
+ throw e;
+ }
+ }
+
+ try {
+ stack = computeStackTraceFromStackProp(ex);
+ if (stack) {
+ return stack;
+ }
+ } catch (e) {
+ if (debug) {
+ throw e;
+ }
+ }
+
+ try {
+ stack = computeStackTraceFromOperaMultiLineMessage(ex);
+ if (stack) {
+ return stack;
+ }
+ } catch (e) {
+ if (debug) {
+ throw e;
+ }
+ }
+
+ try {
+ stack = computeStackTraceByWalkingCallerChain(ex, depth + 1);
+ if (stack) {
+ return stack;
+ }
+ } catch (e) {
+ if (debug) {
+ throw e;
+ }
+ }
+
+ return {
+ 'mode': 'failed'
+ };
+ }
+
+ /**
+ * Logs a stacktrace starting from the previous call and working down.
+ * @param {(number|string)=} depth How many frames deep to trace.
+ * @return {Object.<string, *>} Stack trace information.
+ */
+ function computeStackTraceOfCaller(depth) {
+ depth = (depth == null ? 0 : +depth) + 1; // "+ 1" because "ofCaller" should drop one frame
+ try {
+ throw new Error();
+ } catch (ex) {
+ return computeStackTrace(ex, depth + 1);
+ }
+
+ return null;
+ }
+
+ computeStackTrace.augmentStackTraceWithInitialElement = augmentStackTraceWithInitialElement;
+ computeStackTrace.guessFunctionName = guessFunctionName;
+ computeStackTrace.gatherContext = gatherContext;
+ computeStackTrace.ofCaller = computeStackTraceOfCaller;
+
+ return computeStackTrace;
+}());
+
+/**
+ * Extends support for global error handling for asynchronous browser
+ * functions. Adopted from Closure Library's errorhandler.js
+ */
+(function extendToAsynchronousCallbacks() {
+ var _helper = function _helper(fnName) {
+ var originalFn = window[fnName];
+ window[fnName] = function traceKitAsyncExtension() {
+ // Make a copy of the arguments
+ var args = _slice.call(arguments);
+ var originalCallback = args[0];
+ if (typeof (originalCallback) === 'function') {
+ args[0] = TraceKit.wrap(originalCallback);
+ }
+ // IE < 9 doesn't support .call/.apply on setInterval/setTimeout, but it
+ // also only supports 2 argument and doesn't care what "this" is, so we
+ // can just call the original function directly.
+ if (originalFn.apply) {
+ return originalFn.apply(this, args);
+ } else {
+ return originalFn(args[0], args[1]);
+ }
+ };
+ };
+
+ _helper('setTimeout');
+ _helper('setInterval');
+}());
+
+//Default options:
+if (!TraceKit.remoteFetching) {
+ TraceKit.remoteFetching = true;
+}
+if (!TraceKit.collectWindowErrors) {
+ TraceKit.collectWindowErrors = true;
+}
+if (!TraceKit.linesOfContext || TraceKit.linesOfContext < 1) {
+ // 5 lines before, the offending line, 5 lines after
+ TraceKit.linesOfContext = 11;
+}
+
+
+
+// Export to global object
+window.TraceKit = TraceKit;
+
+}(window));
diff --git a/libraries/Advisor.class.php b/libraries/Advisor.class.php
new file mode 100644
index 0000000000..0512927549
--- /dev/null
+++ b/libraries/Advisor.class.php
@@ -0,0 +1,517 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * A simple rules engine, that parses and executes the rules in advisory_rules.txt.
+ * Adjusted to phpMyAdmin.
+ *
+ * @package PhpMyAdmin
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ * Advisor class
+ *
+ * @package PhpMyAdmin
+ */
+class Advisor
+{
+ var $variables;
+ var $parseResult;
+ var $runResult;
+
+ /**
+ * Parses and executes advisor rules
+ *
+ * @return array with run and parse results
+ */
+ function run()
+ {
+ // HowTo: A simple Advisory system in 3 easy steps.
+
+ // Step 1: Get some variables to evaluate on
+ $this->variables = array_merge(
+ $GLOBALS['dbi']->fetchResult('SHOW GLOBAL STATUS', 0, 1),
+ $GLOBALS['dbi']->fetchResult('SHOW GLOBAL VARIABLES', 0, 1)
+ );
+ if (PMA_DRIZZLE) {
+ $this->variables = array_merge(
+ $this->variables,
+ $GLOBALS['dbi']->fetchResult(
+ "SELECT concat('Com_', variable_name), variable_value "
+ . "FROM data_dictionary.GLOBAL_STATEMENTS", 0, 1
+ )
+ );
+ }
+ // Add total memory to variables as well
+ include_once 'libraries/sysinfo.lib.php';
+ $sysinfo = PMA_getSysInfo();
+ $memory = $sysinfo->memory();
+ $this->variables['system_memory'] = $memory['MemTotal'];
+
+ // Step 2: Read and parse the list of rules
+ $this->parseResult = $this->parseRulesFile();
+ // Step 3: Feed the variables to the rules and let them fire. Sets
+ // $runResult
+ $this->runRules();
+
+ return array(
+ 'parse' => array('errors' => $this->parseResult['errors']),
+ 'run' => $this->runResult
+ );
+ }
+
+ /**
+ * Stores current error in run results.
+ *
+ * @param string $description description of an error.
+ * @param Exception $exception exception raised
+ *
+ * @return void
+ */
+ function storeError($description, $exception)
+ {
+ $this->runResult['errors'][] = $description
+ . ' '
+ . sprintf(
+ __('PHP threw following error: %s'),
+ $exception->getMessage()
+ );
+ }
+
+ /**
+ * Executes advisor rules
+ *
+ * @return boolean
+ */
+ function runRules()
+ {
+ $this->runResult = array(
+ 'fired' => array(),
+ 'notfired' => array(),
+ 'unchecked'=> array(),
+ 'errors' => array()
+ );
+
+ foreach ($this->parseResult['rules'] as $rule) {
+ $this->variables['value'] = 0;
+ $precond = true;
+
+ if (isset($rule['precondition'])) {
+ try {
+ $precond = $this->ruleExprEvaluate($rule['precondition']);
+ } catch (Exception $e) {
+ $this->storeError(
+ sprintf(
+ __('Failed evaluating precondition for rule \'%s\'.'),
+ $rule['name']
+ ),
+ $e
+ );
+ continue;
+ }
+ }
+
+ if (! $precond) {
+ $this->addRule('unchecked', $rule);
+ } else {
+ try {
+ $value = $this->ruleExprEvaluate($rule['formula']);
+ } catch(Exception $e) {
+ $this->storeError(
+ sprintf(
+ __('Failed calculating value for rule \'%s\'.'),
+ $rule['name']
+ ),
+ $e
+ );
+ continue;
+ }
+
+ $this->variables['value'] = $value;
+
+ try {
+ if ($this->ruleExprEvaluate($rule['test'])) {
+ $this->addRule('fired', $rule);
+ } else {
+ $this->addRule('notfired', $rule);
+ }
+ } catch(Exception $e) {
+ $this->storeError(
+ sprintf(
+ __('Failed running test for rule \'%s\'.'),
+ $rule['name']
+ ),
+ $e
+ );
+ }
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Escapes percent string to be used in format string.
+ *
+ * @param string $str string to escape
+ *
+ * @return string
+ */
+ static function escapePercent($str)
+ {
+ return preg_replace('/%( |,|\.|$|\(|\)|<|>)/', '%%\1', $str);
+ }
+
+ /**
+ * Wrapper function for translating.
+ *
+ * @param string $str the string
+ * @param mixed $param the parameters
+ *
+ * @return string
+ */
+ function translate($str, $param = null)
+ {
+ $string = _gettext(Advisor::escapePercent($str));
+ if ( ! is_null($param)) {
+ $param = $this->ruleExprEvaluate($param);
+ }
+ return sprintf($string, $param);
+ }
+
+ /**
+ * Splits justification to text and formula.
+ *
+ * @param string $rule the rule
+ *
+ * @return array
+ */
+ static function splitJustification($rule)
+ {
+ $jst = preg_split('/\s*\|\s*/', $rule['justification'], 2);
+ if (count($jst) > 1) {
+ return array($jst[0], $jst[1]);
+ }
+ return array($rule['justification']);
+ }
+
+ /**
+ * Adds a rule to the result list
+ *
+ * @param string $type type of rule
+ * @param array $rule rule itslef
+ *
+ * @return void
+ */
+ function addRule($type, $rule)
+ {
+ switch($type) {
+ case 'notfired':
+ case 'fired':
+ $jst = Advisor::splitJustification($rule);
+ if (count($jst) > 1) {
+ try {
+ /* Translate */
+ $str = $this->translate($jst[0], $jst[1]);
+ } catch (Exception $e) {
+ $this->storeError(
+ sprintf(
+ __('Failed formatting string for rule \'%s\'.'),
+ $rule['name']
+ ),
+ $e
+ );
+ return;
+ }
+
+ $rule['justification'] = $str;
+ } else {
+ $rule['justification'] = $this->translate($rule['justification']);
+ }
+ $rule['id'] = $rule['name'];
+ $rule['name'] = $this->translate($rule['name']);
+ $rule['issue'] = $this->translate($rule['issue']);
+
+ // Replaces {server_variable} with 'server_variable'
+ // linking to server_variables.php
+ $rule['recommendation'] = preg_replace(
+ '/\{([a-z_0-9]+)\}/Ui',
+ '<a href="server_variables.php?' . PMA_URL_getCommon()
+ . '&filter=\1">\1</a>',
+ $this->translate($rule['recommendation'])
+ );
+
+ // Replaces external Links with PMA_linkURL() generated links
+ $rule['recommendation'] = preg_replace_callback(
+ '#href=("|\')(https?://[^\1]+)\1#i',
+ array($this, '_replaceLinkURL'),
+ $rule['recommendation']
+ );
+ break;
+ }
+
+ $this->runResult[$type][] = $rule;
+ }
+
+ /**
+ * Callback for wrapping links with PMA_linkURL
+ *
+ * @param array $matches List of matched elements form preg_replace_callback
+ *
+ * @return string Replacement value
+ */
+ private function _replaceLinkURL($matches)
+ {
+ return 'href="' . PMA_linkURL($matches[2]) . '"';
+ }
+
+ /**
+ * Callback for evaluating fired() condition.
+ *
+ * @param array $matches List of matched elements form preg_replace_callback
+ *
+ * @return string Replacement value
+ */
+ private function _ruleExprEvaluateFired($matches)
+ {
+ // No list of fired rules
+ if (!isset($this->runResult['fired'])) {
+ return '0';
+ }
+
+ // Did matching rule fire?
+ foreach ($this->runResult['fired'] as $rule) {
+ if ($rule['id'] == $matches[2]) {
+ return '1';
+ }
+ }
+
+ return '0';
+ }
+
+ /**
+ * Callback for evaluating variables in expression.
+ *
+ * @param array $matches List of matched elements form preg_replace_callback
+ *
+ * @return string Replacement value
+ */
+ private function _ruleExprEvaluateVariable($matches)
+ {
+ if (! isset($this->variables[$matches[1]])) {
+ return $matches[1];
+ }
+ if (is_numeric($this->variables[$matches[1]])) {
+ return $this->variables[$matches[1]];
+ } else {
+ return '\'' . addslashes($this->variables[$matches[1]]) . '\'';
+ }
+ }
+
+ /**
+ * Runs a code expression, replacing variable names with their respective
+ * values
+ *
+ * @param string $expr expression to evaluate
+ *
+ * @return string result of evaluated expression
+ *
+ * @throws Exception
+ */
+ function ruleExprEvaluate($expr)
+ {
+ // Evaluate fired() conditions
+ $expr = preg_replace_callback(
+ '/fired\s*\(\s*(\'|")(.*)\1\s*\)/Ui',
+ array($this, '_ruleExprEvaluateFired'),
+ $expr
+ );
+ // Evaluate variables
+ $expr = preg_replace_callback(
+ '/\b(\w+)\b/',
+ array($this, '_ruleExprEvaluateVariable'),
+ $expr
+ );
+ $value = 0;
+ $err = 0;
+
+ // Actually evaluate the code
+ ob_start();
+ try {
+ eval('$value = ' . $expr . ';');
+ $err = ob_get_contents();
+ } catch (Exception $e) {
+ // In normal operation, there is just output in the buffer,
+ // but when running under phpunit, error in eval raises exception
+ $err = $e->getMessage();
+ }
+ ob_end_clean();
+
+ // Error handling
+ if ($err) {
+ throw new Exception(
+ strip_tags($err)
+ . '<br />Executed code: $value = ' . htmlspecialchars($expr) . ';'
+ );
+ }
+ return $value;
+ }
+
+ /**
+ * Reads the rule file into an array, throwing errors messages on syntax
+ * errors.
+ *
+ * @return array with parsed data
+ */
+ static function parseRulesFile()
+ {
+ $file = file('libraries/advisory_rules.txt', FILE_IGNORE_NEW_LINES);
+ $errors = array();
+ $rules = array();
+ $lines = array();
+ $ruleSyntax = array(
+ 'name', 'formula', 'test', 'issue', 'recommendation', 'justification'
+ );
+ $numRules = count($ruleSyntax);
+ $numLines = count($file);
+ $ruleNo = -1;
+ $ruleLine = -1;
+
+ for ($i = 0; $i < $numLines; $i++) {
+ $line = $file[$i];
+ if ($line == "" || $line[0] == '#') {
+ continue;
+ }
+
+ // Reading new rule
+ if (substr($line, 0, 4) == 'rule') {
+ if ($ruleLine > 0) {
+ $errors[] = sprintf(
+ __(
+ 'Invalid rule declaration on line %1$s, expected line '
+ . '%2$s of previous rule.'
+ ),
+ $i + 1,
+ $ruleSyntax[$ruleLine++]
+ );
+ continue;
+ }
+ if (preg_match("/rule\s'(.*)'( \[(.*)\])?$/", $line, $match)) {
+ $ruleLine = 1;
+ $ruleNo++;
+ $rules[$ruleNo] = array('name' => $match[1]);
+ $lines[$ruleNo] = array('name' => $i + 1);
+ if (isset($match[3])) {
+ $rules[$ruleNo]['precondition'] = $match[3];
+ $lines[$ruleNo]['precondition'] = $i + 1;
+ }
+ } else {
+ $errors[] = sprintf(
+ __('Invalid rule declaration on line %s.'),
+ $i + 1
+ );
+ }
+ continue;
+ } else {
+ if ($ruleLine == -1) {
+ $errors[] = sprintf(
+ __('Unexpected characters on line %s.'),
+ $i + 1
+ );
+ }
+ }
+
+ // Reading rule lines
+ if ($ruleLine > 0) {
+ if (!isset($line[0])) {
+ continue; // Empty lines are ok
+ }
+ // Non tabbed lines are not
+ if ($line[0] != "\t") {
+ $errors[] = sprintf(
+ __(
+ 'Unexpected character on line %1$s. Expected tab, but '
+ . 'found "%2$s".'
+ ),
+ $i + 1,
+ $line[0]
+ );
+ continue;
+ }
+ $rules[$ruleNo][$ruleSyntax[$ruleLine]] = chop(substr($line, 1));
+ $lines[$ruleNo][$ruleSyntax[$ruleLine]] = $i + 1;
+ $ruleLine += 1;
+ }
+
+ // Rule complete
+ if ($ruleLine == $numRules) {
+ $ruleLine = -1;
+ }
+ }
+
+ return array('rules' => $rules, 'lines' => $lines, 'errors' => $errors);
+ }
+}
+
+/**
+ * Formats interval like 10 per hour
+ *
+ * @param integer $num number to format
+ * @param integer $precision required precision
+ *
+ * @return string formatted string
+ */
+function ADVISOR_bytime($num, $precision)
+{
+ $per = '';
+ if ($num >= 1) { // per second
+ $per = __('per second');
+ } elseif ($num * 60 >= 1) { // per minute
+ $num = $num * 60;
+ $per = __('per minute');
+ } elseif ($num * 60 * 60 >= 1 ) { // per hour
+ $num = $num * 60 * 60;
+ $per = __('per hour');
+ } else {
+ $num = $num * 60 * 60 * 24;
+ $per = __('per day');
+ }
+
+ $num = round($num, $precision);
+
+ if ($num == 0) {
+ $num = '<' . PMA_Util::pow(10, -$precision);
+ }
+
+ return "$num $per";
+}
+
+/**
+ * Wrapper for PMA_Util::timespanFormat
+ *
+ * @param int $seconds the timespan
+ *
+ * @return string the formatted value
+ */
+function ADVISOR_timespanFormat($seconds)
+{
+ return PMA_Util::timespanFormat($seconds);
+}
+
+/**
+ * Wrapper around PMA_Util::formatByteDown
+ *
+ * @param double $value the value to format
+ * @param int $limes the sensitiveness
+ * @param int $comma the number of decimals to retain
+ *
+ * @return array the formatted value and its unit
+ */
+function ADVISOR_formatByteDown($value, $limes = 6, $comma = 0)
+{
+ return PMA_Util::formatByteDown($value, $limes, $comma);
+}
+
+?>
diff --git a/libraries/Config.class.php b/libraries/Config.class.php
new file mode 100644
index 0000000000..caacaff4cd
--- /dev/null
+++ b/libraries/Config.class.php
@@ -0,0 +1,1912 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Configuration handling.
+ *
+ * @package PhpMyAdmin
+ */
+
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ * Load vendor configuration.
+ */
+require_once './libraries/vendor_config.php';
+
+/**
+ * Indication for error handler (see end of this file).
+ */
+$GLOBALS['pma_config_loading'] = false;
+
+/**
+ * Configuration class
+ *
+ * @package PhpMyAdmin
+ */
+class PMA_Config
+{
+ /**
+ * @var string default config source
+ */
+ var $default_source = './libraries/config.default.php';
+
+ /**
+ * @var array default configuration settings
+ */
+ var $default = array();
+
+ /**
+ * @var array configuration settings, without user preferences applied
+ */
+ var $base_settings = array();
+
+ /**
+ * @var array configuration settings
+ */
+ var $settings = array();
+
+ /**
+ * @var string config source
+ */
+ var $source = '';
+
+ /**
+ * @var int source modification time
+ */
+ var $source_mtime = 0;
+ var $default_source_mtime = 0;
+ var $set_mtime = 0;
+
+ /**
+ * @var boolean
+ */
+ var $error_config_file = false;
+
+ /**
+ * @var boolean
+ */
+ var $error_config_default_file = false;
+
+ /**
+ * @var boolean
+ */
+ var $error_pma_uri = false;
+
+ /**
+ * @var array
+ */
+ var $default_server = array();
+
+ /**
+ * @var boolean whether init is done or not
+ * set this to false to force some initial checks
+ * like checking for required functions
+ */
+ var $done = false;
+
+ /**
+ * constructor
+ *
+ * @param string $source source to read config from
+ */
+ function __construct($source = null)
+ {
+ $this->settings = array();
+
+ // functions need to refresh in case of config file changed goes in
+ // PMA_Config::load()
+ $this->load($source);
+
+ // other settings, independent from config file, comes in
+ $this->checkSystem();
+
+ $this->isHttps();
+
+ $this->base_settings = $this->settings;
+ }
+
+ /**
+ * sets system and application settings
+ *
+ * @return void
+ */
+ function checkSystem()
+ {
+ $this->set('PMA_VERSION', '4.1.12');
+ /**
+ * @deprecated
+ */
+ $this->set('PMA_THEME_VERSION', 2);
+ /**
+ * @deprecated
+ */
+ $this->set('PMA_THEME_GENERATION', 2);
+
+ $this->checkPhpVersion();
+ $this->checkWebServerOs();
+ $this->checkWebServer();
+ $this->checkGd2();
+ $this->checkClient();
+ $this->checkUpload();
+ $this->checkUploadSize();
+ $this->checkOutputCompression();
+ }
+
+ /**
+ * whether to use gzip output compression or not
+ *
+ * @return void
+ */
+ function checkOutputCompression()
+ {
+ // If zlib output compression is set in the php configuration file, no
+ // output buffering should be run
+ if (@ini_get('zlib.output_compression')) {
+ $this->set('OBGzip', false);
+ }
+
+ // disable output-buffering (if set to 'auto') for IE6, else enable it.
+ if (strtolower($this->get('OBGzip')) == 'auto') {
+ if ($this->get('PMA_USR_BROWSER_AGENT') == 'IE'
+ && $this->get('PMA_USR_BROWSER_VER') >= 6
+ && $this->get('PMA_USR_BROWSER_VER') < 7
+ ) {
+ $this->set('OBGzip', false);
+ } else {
+ $this->set('OBGzip', true);
+ }
+ }
+ }
+
+ /**
+ * Determines platform (OS), browser and version of the user
+ * Based on a phpBuilder article:
+ *
+ * @see http://www.phpbuilder.net/columns/tim20000821.php
+ *
+ * @return void
+ */
+ function checkClient()
+ {
+ if (PMA_getenv('HTTP_USER_AGENT')) {
+ $HTTP_USER_AGENT = PMA_getenv('HTTP_USER_AGENT');
+ } else {
+ $HTTP_USER_AGENT = '';
+ }
+
+ // 1. Platform
+ if (strstr($HTTP_USER_AGENT, 'Win')) {
+ $this->set('PMA_USR_OS', 'Win');
+ } elseif (strstr($HTTP_USER_AGENT, 'Mac')) {
+ $this->set('PMA_USR_OS', 'Mac');
+ } elseif (strstr($HTTP_USER_AGENT, 'Linux')) {
+ $this->set('PMA_USR_OS', 'Linux');
+ } elseif (strstr($HTTP_USER_AGENT, 'Unix')) {
+ $this->set('PMA_USR_OS', 'Unix');
+ } elseif (strstr($HTTP_USER_AGENT, 'OS/2')) {
+ $this->set('PMA_USR_OS', 'OS/2');
+ } else {
+ $this->set('PMA_USR_OS', 'Other');
+ }
+
+ // 2. browser and version
+ // (must check everything else before Mozilla)
+
+ $is_mozilla = preg_match(
+ '@Mozilla/([0-9].[0-9]{1,2})@',
+ $HTTP_USER_AGENT,
+ $mozilla_version
+ );
+
+ if (preg_match(
+ '@Opera(/| )([0-9].[0-9]{1,2})@',
+ $HTTP_USER_AGENT,
+ $log_version
+ )) {
+ $this->set('PMA_USR_BROWSER_VER', $log_version[2]);
+ $this->set('PMA_USR_BROWSER_AGENT', 'OPERA');
+ } elseif (preg_match(
+ '@(MS)?IE ([0-9]{1,2}.[0-9]{1,2})@',
+ $HTTP_USER_AGENT,
+ $log_version
+ )) {
+ $this->set('PMA_USR_BROWSER_VER', $log_version[2]);
+ $this->set('PMA_USR_BROWSER_AGENT', 'IE');
+ } elseif (preg_match(
+ '@Trident/(7)\.0@',
+ $HTTP_USER_AGENT,
+ $log_version
+ )) {
+ $this->set('PMA_USR_BROWSER_VER', intval($log_version[1]) + 4);
+ $this->set('PMA_USR_BROWSER_AGENT', 'IE');
+ } elseif (preg_match(
+ '@OmniWeb/([0-9].[0-9]{1,2})@',
+ $HTTP_USER_AGENT,
+ $log_version
+ )) {
+ $this->set('PMA_USR_BROWSER_VER', $log_version[1]);
+ $this->set('PMA_USR_BROWSER_AGENT', 'OMNIWEB');
+ // Konqueror 2.2.2 says Konqueror/2.2.2
+ // Konqueror 3.0.3 says Konqueror/3
+ } elseif (preg_match(
+ '@(Konqueror/)(.*)(;)@',
+ $HTTP_USER_AGENT,
+ $log_version
+ )) {
+ $this->set('PMA_USR_BROWSER_VER', $log_version[2]);
+ $this->set('PMA_USR_BROWSER_AGENT', 'KONQUEROR');
+ // must check Chrome before Safari
+ } elseif ($is_mozilla
+ && preg_match('@Chrome/([0-9.]*)@', $HTTP_USER_AGENT, $log_version)
+ ) {
+ $this->set('PMA_USR_BROWSER_VER', $log_version[1]);
+ $this->set('PMA_USR_BROWSER_AGENT', 'CHROME');
+ // newer Safari
+ } elseif ($is_mozilla
+ && preg_match('@Version/(.*) Safari@', $HTTP_USER_AGENT, $log_version)
+ ) {
+ $this->set(
+ 'PMA_USR_BROWSER_VER', $log_version[1]
+ );
+ $this->set('PMA_USR_BROWSER_AGENT', 'SAFARI');
+ // older Safari
+ } elseif ($is_mozilla
+ && preg_match('@Safari/([0-9]*)@', $HTTP_USER_AGENT, $log_version)
+ ) {
+ $this->set(
+ 'PMA_USR_BROWSER_VER', $mozilla_version[1] . '.' . $log_version[1]
+ );
+ $this->set('PMA_USR_BROWSER_AGENT', 'SAFARI');
+ // Firefox
+ } elseif (! strstr($HTTP_USER_AGENT, 'compatible')
+ && preg_match('@Firefox/([\w.]+)@', $HTTP_USER_AGENT, $log_version)
+ ) {
+ $this->set(
+ 'PMA_USR_BROWSER_VER', $log_version[1]
+ );
+ $this->set('PMA_USR_BROWSER_AGENT', 'FIREFOX');
+ } elseif (preg_match('@rv:1.9(.*)Gecko@', $HTTP_USER_AGENT)) {
+ $this->set('PMA_USR_BROWSER_VER', '1.9');
+ $this->set('PMA_USR_BROWSER_AGENT', 'GECKO');
+ } elseif ($is_mozilla) {
+ $this->set('PMA_USR_BROWSER_VER', $mozilla_version[1]);
+ $this->set('PMA_USR_BROWSER_AGENT', 'MOZILLA');
+ } else {
+ $this->set('PMA_USR_BROWSER_VER', 0);
+ $this->set('PMA_USR_BROWSER_AGENT', 'OTHER');
+ }
+ }
+
+ /**
+ * Whether GD2 is present
+ *
+ * @return void
+ */
+ function checkGd2()
+ {
+ if ($this->get('GD2Available') == 'yes') {
+ $this->set('PMA_IS_GD2', 1);
+ } elseif ($this->get('GD2Available') == 'no') {
+ $this->set('PMA_IS_GD2', 0);
+ } else {
+ if (!@function_exists('imagecreatetruecolor')) {
+ $this->set('PMA_IS_GD2', 0);
+ } else {
+ if (@function_exists('gd_info')) {
+ $gd_nfo = gd_info();
+ if (strstr($gd_nfo["GD Version"], '2.')) {
+ $this->set('PMA_IS_GD2', 1);
+ } else {
+ $this->set('PMA_IS_GD2', 0);
+ }
+ } else {
+ $this->set('PMA_IS_GD2', 0);
+ }
+ }
+ }
+ }
+
+ /**
+ * Whether the Web server php is running on is IIS
+ *
+ * @return void
+ */
+ function checkWebServer()
+ {
+ // some versions return Microsoft-IIS, some Microsoft/IIS
+ // we could use a preg_match() but it's slower
+ if (PMA_getenv('SERVER_SOFTWARE')
+ && stristr(PMA_getenv('SERVER_SOFTWARE'), 'Microsoft')
+ && stristr(PMA_getenv('SERVER_SOFTWARE'), 'IIS')
+ ) {
+ $this->set('PMA_IS_IIS', 1);
+ } else {
+ $this->set('PMA_IS_IIS', 0);
+ }
+ }
+
+ /**
+ * Whether the os php is running on is windows or not
+ *
+ * @return void
+ */
+ function checkWebServerOs()
+ {
+ // Default to Unix or Equiv
+ $this->set('PMA_IS_WINDOWS', 0);
+ // If PHP_OS is defined then continue
+ if (defined('PHP_OS')) {
+ if (stristr(PHP_OS, 'win') && !stristr(PHP_OS, 'darwin')) {
+ // Is it some version of Windows
+ $this->set('PMA_IS_WINDOWS', 1);
+ } elseif (stristr(PHP_OS, 'OS/2')) {
+ // Is it OS/2 (No file permissions like Windows)
+ $this->set('PMA_IS_WINDOWS', 1);
+ }
+ }
+ }
+
+ /**
+ * detects PHP version
+ *
+ * @return void
+ */
+ function checkPhpVersion()
+ {
+ $match = array();
+ if (! preg_match(
+ '@([0-9]{1,2}).([0-9]{1,2}).([0-9]{1,2})@',
+ phpversion(),
+ $match
+ )) {
+ preg_match(
+ '@([0-9]{1,2}).([0-9]{1,2})@',
+ phpversion(),
+ $match
+ );
+ }
+ if (isset($match) && ! empty($match[1])) {
+ if (! isset($match[2])) {
+ $match[2] = 0;
+ }
+ if (! isset($match[3])) {
+ $match[3] = 0;
+ }
+ $this->set(
+ 'PMA_PHP_INT_VERSION',
+ (int) sprintf('%d%02d%02d', $match[1], $match[2], $match[3])
+ );
+ } else {
+ $this->set('PMA_PHP_INT_VERSION', 0);
+ }
+ $this->set('PMA_PHP_STR_VERSION', phpversion());
+ }
+
+ /**
+ * detects if Git revision
+ *
+ * @return boolean
+ */
+ function isGitRevision()
+ {
+ // caching
+ if (isset($_SESSION['is_git_revision'])) {
+ if ($_SESSION['is_git_revision']) {
+ $this->set('PMA_VERSION_GIT', 1);
+ }
+ return $_SESSION['is_git_revision'];
+ }
+ // find out if there is a .git folder
+ $git_folder = '.git';
+ if (! @file_exists($git_folder)
+ || ! @file_exists($git_folder . '/config')
+ ) {
+ $_SESSION['is_git_revision'] = false;
+ return false;
+ }
+ $_SESSION['is_git_revision'] = true;
+ return true;
+ }
+
+ /**
+ * detects Git revision, if running inside repo
+ *
+ * @return void
+ */
+ function checkGitRevision()
+ {
+ // find out if there is a .git folder
+ $git_folder = '.git';
+ if (! $this->isGitRevision()) {
+ return;
+ }
+
+ if (! $ref_head = @file_get_contents($git_folder . '/HEAD')) {
+ return;
+ }
+ $branch = false;
+ // are we on any branch?
+ if (strstr($ref_head, '/')) {
+ $ref_head = substr(trim($ref_head), 5);
+ if (substr($ref_head, 0, 11) === 'refs/heads/') {
+ $branch = substr($ref_head, 11);
+ } else {
+ $branch = basename($ref_head);
+ }
+
+ $ref_file = $git_folder . '/' . $ref_head;
+ if (@file_exists($ref_file)) {
+ $hash = @file_get_contents($ref_file);
+ if (! $hash) {
+ return;
+ }
+ $hash = trim($hash);
+ } else {
+ // deal with packed refs
+ $packed_refs = @file_get_contents($git_folder . '/packed-refs');
+ if (! $packed_refs) {
+ return;
+ }
+ // split file to lines
+ $ref_lines = explode("\n", $packed_refs);
+ foreach ($ref_lines as $line) {
+ // skip comments
+ if ($line[0] == '#') {
+ continue;
+ }
+ // parse line
+ $parts = explode(' ', $line);
+ // care only about named refs
+ if (count($parts) != 2) {
+ continue;
+ }
+ // have found our ref?
+ if ($parts[1] == $ref_head) {
+ $hash = $parts[0];
+ break;
+ }
+ }
+ if (! isset($hash)) {
+ // Could not find ref
+ return;
+ }
+ }
+ } else {
+ $hash = trim($ref_head);
+ }
+
+ $commit = false;
+ if (! isset($_SESSION['PMA_VERSION_COMMITDATA_' . $hash])) {
+ $git_file_name = $git_folder . '/objects/' . substr($hash, 0, 2)
+ . '/' . substr($hash, 2);
+ if (file_exists($git_file_name) ) {
+ if (! $commit = @file_get_contents($git_file_name)) {
+ return;
+ }
+ $commit = explode("\0", gzuncompress($commit), 2);
+ $commit = explode("\n", $commit[1]);
+ $_SESSION['PMA_VERSION_COMMITDATA_' . $hash] = $commit;
+ } else {
+ $pack_names = array();
+ // work with packed data
+ $packs_file = $git_folder . '/objects/info/packs';
+ if (file_exists($packs_file)
+ && $packs = @file_get_contents($packs_file)
+ ) {
+ // File exists. Read it, parse the file to get the names of the
+ // packs. (to look for them in .git/object/pack directory later)
+ foreach (explode("\n", $packs) as $line) {
+ // skip blank lines
+ if (strlen(trim($line)) == 0) {
+ continue;
+ }
+ // skip non pack lines
+ if ($line[0] != 'P') {
+ continue;
+ }
+ // parse names
+ $pack_names[] = substr($line, 2);
+ }
+ } else {
+ // '.git/objects/info/packs' file can be missing
+ // (atlease in mysGit)
+ // File missing. May be we can look in the .git/object/pack
+ // directory for all the .pack files and use that list of
+ // files instead
+ $it = new DirectoryIterator($git_folder . '/objects/pack');
+ foreach ($it as $file_info) {
+ $file_name = $file_info->getFilename();
+ // if this is a .pack file
+ if ($file_info->isFile()
+ && substr($file_name, -5) == '.pack'
+ ) {
+ $pack_names[] = $file_name;
+ }
+ }
+ }
+ $hash = strtolower($hash);
+ foreach ($pack_names as $pack_name) {
+ $index_name = str_replace('.pack', '.idx', $pack_name);
+
+ // load index
+ $index_data = @file_get_contents(
+ $git_folder . '/objects/pack/' . $index_name
+ );
+ if (! $index_data) {
+ continue;
+ }
+ // check format
+ if (substr($index_data, 0, 4) != "\377tOc") {
+ continue;
+ }
+ // check version
+ $version = unpack('N', substr($index_data, 4, 4));
+ if ($version[1] != 2) {
+ continue;
+ }
+ // parse fanout table
+ $fanout = unpack("N*", substr($index_data, 8, 256 * 4));
+
+ // find where we should search
+ $firstbyte = intval(substr($hash, 0, 2), 16);
+ // array is indexed from 1 and we need to get
+ // previous entry for start
+ if ($firstbyte == 0) {
+ $start = 0;
+ } else {
+ $start = $fanout[$firstbyte];
+ }
+ $end = $fanout[$firstbyte + 1];
+
+ // stupid linear search for our sha
+ $position = $start;
+ $found = false;
+ $offset = 8 + (256 * 4);
+ for ($position = $start; $position < $end; $position++) {
+ $sha = strtolower(
+ bin2hex(
+ substr(
+ $index_data, $offset + ($position * 20), 20
+ )
+ )
+ );
+ if ($sha == $hash) {
+ $found = true;
+ break;
+ }
+ }
+ if (! $found) {
+ continue;
+ }
+ // read pack offset
+ $offset = 8 + (256 * 4) + (24 * $fanout[256]);
+ $pack_offset = unpack(
+ 'N', substr($index_data, $offset + ($position * 4), 4)
+ );
+ $pack_offset = $pack_offset[1];
+
+ // open pack file
+ $pack_file = fopen(
+ $git_folder . '/objects/pack/' . $pack_name, 'rb'
+ );
+ if ($pack_file === false) {
+ continue;
+ }
+ // seek to start
+ fseek($pack_file, $pack_offset);
+
+ // parse header
+ $header = ord(fread($pack_file, 1));
+ $type = ($header >> 4) & 7;
+ $hasnext = ($header & 128) >> 7;
+ $size = $header & 0xf;
+ $offset = 4;
+
+ while ($hasnext) {
+ $byte = ord(fread($pack_file, 1));
+ $size |= ($byte & 0x7f) << $offset;
+ $hasnext = ($byte & 128) >> 7;
+ $offset += 7;
+ }
+
+ // we care only about commit objects
+ if ($type != 1) {
+ continue;
+ }
+
+ // read data
+ $commit = fread($pack_file, $size);
+ $commit = gzuncompress($commit);
+ $commit = explode("\n", $commit);
+ $_SESSION['PMA_VERSION_COMMITDATA_' . $hash] = $commit;
+ fclose($pack_file);
+ }
+ }
+ } else {
+ $commit = $_SESSION['PMA_VERSION_COMMITDATA_' . $hash];
+ }
+
+ // check if commit exists in Github
+ $is_remote_commit = false;
+ if ($commit !== false
+ && isset($_SESSION['PMA_VERSION_REMOTECOMMIT_' . $hash])
+ ) {
+ $is_remote_commit = $_SESSION['PMA_VERSION_REMOTECOMMIT_' . $hash];
+ } else {
+ $link = 'https://api.github.com/repos/phpmyadmin/phpmyadmin/git/commits/'
+ . $hash;
+ $is_found = $this->checkHTTP($link, ! $commit);
+ switch($is_found) {
+ case false:
+ $is_remote_commit = false;
+ $_SESSION['PMA_VERSION_REMOTECOMMIT_' . $hash] = false;
+ break;
+ case null:
+ // no remote link for now, but don't cache this as Github is down
+ $is_remote_commit = false;
+ break;
+ default:
+ $is_remote_commit = true;
+ $_SESSION['PMA_VERSION_REMOTECOMMIT_' . $hash] = true;
+ if ($commit === false) {
+ // if no local commit data, try loading from Github
+ $commit_json = json_decode($is_found);
+ }
+ break;
+ }
+ }
+
+ $is_remote_branch = false;
+ if ($is_remote_commit && $branch !== false) {
+ // check if branch exists in Github
+ if (isset($_SESSION['PMA_VERSION_REMOTEBRANCH_' . $hash])) {
+ $is_remote_branch = $_SESSION['PMA_VERSION_REMOTEBRANCH_' . $hash];
+ } else {
+ $link = 'https://api.github.com/repos/phpmyadmin/phpmyadmin'
+ . '/git/trees/' . $branch;
+ $is_found = $this->checkHTTP($link);
+ switch($is_found) {
+ case true:
+ $is_remote_branch = true;
+ $_SESSION['PMA_VERSION_REMOTEBRANCH_' . $hash] = true;
+ break;
+ case false:
+ $is_remote_branch = false;
+ $_SESSION['PMA_VERSION_REMOTEBRANCH_' . $hash] = false;
+ break;
+ case null:
+ // no remote link for now, but don't cache this as Github is down
+ $is_remote_branch = false;
+ break;
+ }
+ }
+ }
+
+ if ($commit !== false) {
+ $author = array('name' => '', 'email' => '', 'date' => '');
+ $committer = array('name' => '', 'email' => '', 'date' => '');
+
+ do {
+ $dataline = array_shift($commit);
+ $datalinearr = explode(' ', $dataline, 2);
+ $linetype = $datalinearr[0];
+ if (in_array($linetype, array('author', 'committer'))) {
+ $user = $datalinearr[1];
+ preg_match('/([^<]+)<([^>]+)> ([0-9]+)( [^ ]+)?/', $user, $user);
+ $user2 = array(
+ 'name' => trim($user[1]),
+ 'email' => trim($user[2]),
+ 'date' => date('Y-m-d H:i:s', $user[3]));
+ if (isset($user[4])) {
+ $user2['date'] .= $user[4];
+ }
+ $$linetype = $user2;
+ }
+ } while ($dataline != '');
+ $message = trim(implode(' ', $commit));
+
+ } elseif (isset($commit_json)) {
+ $author = array(
+ 'name' => $commit_json->author->name,
+ 'email' => $commit_json->author->email,
+ 'date' => $commit_json->author->date);
+ $committer = array(
+ 'name' => $commit_json->committer->name,
+ 'email' => $commit_json->committer->email,
+ 'date' => $commit_json->committer->date);
+ $message = trim($commit_json->message);
+ } else {
+ return;
+ }
+
+ $this->set('PMA_VERSION_GIT', 1);
+ $this->set('PMA_VERSION_GIT_COMMITHASH', $hash);
+ $this->set('PMA_VERSION_GIT_BRANCH', $branch);
+ $this->set('PMA_VERSION_GIT_MESSAGE', $message);
+ $this->set('PMA_VERSION_GIT_AUTHOR', $author);
+ $this->set('PMA_VERSION_GIT_COMMITTER', $committer);
+ $this->set('PMA_VERSION_GIT_ISREMOTECOMMIT', $is_remote_commit);
+ $this->set('PMA_VERSION_GIT_ISREMOTEBRANCH', $is_remote_branch);
+ }
+
+ /**
+ * Checks if given URL is 200 or 404, optionally returns data
+ *
+ * @param mixed $link curl link
+ * @param boolean $get_body whether to retrieve body of document
+ *
+ * @return string|boolean test result or data
+ */
+ function checkHTTP($link, $get_body = false)
+ {
+ if (! function_exists('curl_init')) {
+ return null;
+ }
+ $ch = curl_init($link);
+ curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 0);
+ curl_setopt($ch, CURLOPT_HEADER, 1);
+ curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
+ curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
+ curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
+ curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5);
+ curl_setopt($ch, CURLOPT_USERAGENT, 'phpMyAdmin/' . PMA_VERSION);
+ curl_setopt($ch, CURLOPT_TIMEOUT, 5);
+ if (! defined('TESTSUITE')) {
+ session_write_close();
+ }
+ $data = @curl_exec($ch);
+ if (! defined('TESTSUITE')) {
+ ini_set('session.use_only_cookies', false);
+ ini_set('session.use_cookies', false);
+ ini_set('session.use_trans_sid', false);
+ ini_set('session.cache_limiter', null);
+ session_start();
+ }
+ if ($data === false) {
+ return null;
+ }
+ $ok = 'HTTP/1.1 200 OK';
+ $notfound = 'HTTP/1.1 404 Not Found';
+ if (substr($data, 0, strlen($ok)) === $ok) {
+ return $get_body ? substr($data, strpos($data, "\r\n\r\n") + 4) : true;
+ } elseif (substr($data, 0, strlen($notfound)) === $notfound) {
+ return false;
+ }
+ return null;
+ }
+
+ /**
+ * loads default values from default source
+ *
+ * @return boolean success
+ */
+ function loadDefaults()
+ {
+ $cfg = array();
+ if (! file_exists($this->default_source)) {
+ $this->error_config_default_file = true;
+ return false;
+ }
+ include $this->default_source;
+
+ $this->default_source_mtime = filemtime($this->default_source);
+
+ $this->default_server = $cfg['Servers'][1];
+ unset($cfg['Servers']);
+
+ $this->default = $cfg;
+ $this->settings = PMA_arrayMergeRecursive($this->settings, $cfg);
+
+ $this->error_config_default_file = false;
+
+ return true;
+ }
+
+ /**
+ * loads configuration from $source, usually the config file
+ * should be called on object creation
+ *
+ * @param string $source config file
+ *
+ * @return bool
+ */
+ function load($source = null)
+ {
+ $this->loadDefaults();
+
+ if (null !== $source) {
+ $this->setSource($source);
+ }
+
+ if (! $this->checkConfigSource()) {
+ return false;
+ }
+
+ $cfg = array();
+
+ /**
+ * Parses the configuration file, we throw away any errors or
+ * output.
+ */
+ $old_error_reporting = error_reporting(0);
+ ob_start();
+ $GLOBALS['pma_config_loading'] = true;
+ $eval_result = include $this->getSource();
+ $GLOBALS['pma_config_loading'] = false;
+ ob_end_clean();
+ error_reporting($old_error_reporting);
+
+ if ($eval_result === false) {
+ $this->error_config_file = true;
+ } else {
+ $this->error_config_file = false;
+ $this->source_mtime = filemtime($this->getSource());
+ }
+
+ /**
+ * Backward compatibility code
+ */
+ if (!empty($cfg['DefaultTabTable'])) {
+ $cfg['DefaultTabTable'] = str_replace(
+ '_properties',
+ '',
+ str_replace(
+ 'tbl_properties.php',
+ 'tbl_sql.php',
+ $cfg['DefaultTabTable']
+ )
+ );
+ }
+ if (!empty($cfg['DefaultTabDatabase'])) {
+ $cfg['DefaultTabDatabase'] = str_replace(
+ '_details',
+ '',
+ str_replace(
+ 'db_details.php',
+ 'db_sql.php',
+ $cfg['DefaultTabDatabase']
+ )
+ );
+ }
+
+ $this->settings = PMA_arrayMergeRecursive($this->settings, $cfg);
+ $this->checkPmaAbsoluteUri();
+ $this->checkFontsize();
+
+ // Handling of the collation must be done after merging of $cfg
+ // (from config.inc.php) so that $cfg['DefaultConnectionCollation']
+ // can have an effect. Note that the presence of collation
+ // information in a cookie has priority over what is defined
+ // in the default or user's config files.
+ /**
+ * @todo check validity of $_COOKIE['pma_collation_connection']
+ */
+ if (! empty($_COOKIE['pma_collation_connection'])) {
+ $this->set(
+ 'collation_connection',
+ strip_tags($_COOKIE['pma_collation_connection'])
+ );
+ } else {
+ $this->set(
+ 'collation_connection',
+ $this->get('DefaultConnectionCollation')
+ );
+ }
+ // Now, a collation information could come from REQUEST
+ // (an example of this: the collation selector in index.php)
+ // so the following handles the setting of collation_connection
+ // and later, in common.inc.php, the cookie will be set
+ // according to this.
+ $this->checkCollationConnection();
+
+ return true;
+ }
+
+ /**
+ * Loads user preferences and merges them with current config
+ * must be called after control connection has been estabilished
+ *
+ * @return boolean
+ */
+ function loadUserPreferences()
+ {
+ // index.php should load these settings, so that phpmyadmin.css.php
+ // will have everything avaiable in session cache
+ $server = isset($GLOBALS['server'])
+ ? $GLOBALS['server']
+ : (!empty($GLOBALS['cfg']['ServerDefault'])
+ ? $GLOBALS['cfg']['ServerDefault']
+ : 0);
+ $cache_key = 'server_' . $server;
+ if ($server > 0 && !defined('PMA_MINIMUM_COMMON')) {
+ $config_mtime = max($this->default_source_mtime, $this->source_mtime);
+ // cache user preferences, use database only when needed
+ if (! isset($_SESSION['cache'][$cache_key]['userprefs'])
+ || $_SESSION['cache'][$cache_key]['config_mtime'] < $config_mtime
+ ) {
+ // load required libraries
+ include_once './libraries/user_preferences.lib.php';
+ $prefs = PMA_loadUserprefs();
+ $_SESSION['cache'][$cache_key]['userprefs']
+ = PMA_applyUserprefs($prefs['config_data']);
+ $_SESSION['cache'][$cache_key]['userprefs_mtime'] = $prefs['mtime'];
+ $_SESSION['cache'][$cache_key]['userprefs_type'] = $prefs['type'];
+ $_SESSION['cache'][$cache_key]['config_mtime'] = $config_mtime;
+ }
+ } elseif ($server == 0
+ || ! isset($_SESSION['cache'][$cache_key]['userprefs'])
+ ) {
+ $this->set('user_preferences', false);
+ return;
+ }
+ $config_data = $_SESSION['cache'][$cache_key]['userprefs'];
+ // type is 'db' or 'session'
+ $this->set(
+ 'user_preferences',
+ $_SESSION['cache'][$cache_key]['userprefs_type']
+ );
+ $this->set(
+ 'user_preferences_mtime',
+ $_SESSION['cache'][$cache_key]['userprefs_mtime']
+ );
+
+ // backup some settings
+ $org_fontsize = '';
+ if (isset($this->settings['fontsize'])) {
+ $org_fontsize = $this->settings['fontsize'];
+ }
+ // load config array
+ $this->settings = PMA_arrayMergeRecursive($this->settings, $config_data);
+ $GLOBALS['cfg'] = PMA_arrayMergeRecursive($GLOBALS['cfg'], $config_data);
+ if (defined('PMA_MINIMUM_COMMON')) {
+ return;
+ }
+
+ // settings below start really working on next page load, but
+ // changes are made only in index.php so everything is set when
+ // in frames
+
+ // save theme
+ $tmanager = $_SESSION['PMA_Theme_Manager'];
+ if ($tmanager->getThemeCookie() || isset($_REQUEST['set_theme'])) {
+ if ((! isset($config_data['ThemeDefault'])
+ && $tmanager->theme->getId() != 'original')
+ || isset($config_data['ThemeDefault'])
+ && $config_data['ThemeDefault'] != $tmanager->theme->getId()
+ ) {
+ // new theme was set in common.inc.php
+ $this->setUserValue(
+ null,
+ 'ThemeDefault',
+ $tmanager->theme->getId(),
+ 'original'
+ );
+ }
+ } else {
+ // no cookie - read default from settings
+ if ($this->settings['ThemeDefault'] != $tmanager->theme->getId()
+ && $tmanager->checkTheme($this->settings['ThemeDefault'])
+ ) {
+ $tmanager->setActiveTheme($this->settings['ThemeDefault']);
+ $tmanager->setThemeCookie();
+ }
+ }
+
+ // save font size
+ if ((! isset($config_data['fontsize'])
+ && $org_fontsize != '82%')
+ || isset($config_data['fontsize'])
+ && $org_fontsize != $config_data['fontsize']
+ ) {
+ $this->setUserValue(null, 'fontsize', $org_fontsize, '82%');
+ }
+
+ // save language
+ if (isset($_COOKIE['pma_lang']) || isset($_POST['lang'])) {
+ if ((! isset($config_data['lang'])
+ && $GLOBALS['lang'] != 'en')
+ || isset($config_data['lang'])
+ && $GLOBALS['lang'] != $config_data['lang']
+ ) {
+ $this->setUserValue(null, 'lang', $GLOBALS['lang'], 'en');
+ }
+ } else {
+ // read language from settings
+ if (isset($config_data['lang']) && PMA_langSet($config_data['lang'])) {
+ $this->setCookie('pma_lang', $GLOBALS['lang']);
+ }
+ }
+
+ // save connection collation
+ if (!PMA_DRIZZLE) {
+ // just to shorten the lines
+ $collation = 'collation_connection';
+ if (isset($_COOKIE['pma_collation_connection'])
+ || isset($_POST[$collation])
+ ) {
+ if ((! isset($config_data[$collation])
+ && $GLOBALS[$collation] != 'utf8_general_ci')
+ || isset($config_data[$collation])
+ && $GLOBALS[$collation] != $config_data[$collation]
+ ) {
+ $this->setUserValue(
+ null,
+ $collation,
+ $GLOBALS[$collation],
+ 'utf8_general_ci'
+ );
+ }
+ } else {
+ // read collation from settings
+ if (isset($config_data['collation_connection'])) {
+ $GLOBALS['collation_connection']
+ = $config_data['collation_connection'];
+ $this->setCookie(
+ 'pma_collation_connection',
+ $GLOBALS['collation_connection']
+ );
+ }
+ }
+ }
+ }
+
+ /**
+ * Sets config value which is stored in user preferences (if available)
+ * or in a cookie.
+ *
+ * If user preferences are not yet initialized, option is applied to
+ * global config and added to a update queue, which is processed
+ * by {@link loadUserPreferences()}
+ *
+ * @param string $cookie_name can be null
+ * @param string $cfg_path configuration path
+ * @param mixed $new_cfg_value new value
+ * @param mixed $default_value default value
+ *
+ * @return void
+ */
+ function setUserValue($cookie_name, $cfg_path, $new_cfg_value,
+ $default_value = null
+ ) {
+ // use permanent user preferences if possible
+ $prefs_type = $this->get('user_preferences');
+ if ($prefs_type) {
+ include_once './libraries/user_preferences.lib.php';
+ if ($default_value === null) {
+ $default_value = PMA_arrayRead($cfg_path, $this->default);
+ }
+ PMA_persistOption($cfg_path, $new_cfg_value, $default_value);
+ }
+ if ($prefs_type != 'db' && $cookie_name) {
+ // fall back to cookies
+ if ($default_value === null) {
+ $default_value = PMA_arrayRead($cfg_path, $this->settings);
+ }
+ $this->setCookie($cookie_name, $new_cfg_value, $default_value);
+ }
+ PMA_arrayWrite($cfg_path, $GLOBALS['cfg'], $new_cfg_value);
+ PMA_arrayWrite($cfg_path, $this->settings, $new_cfg_value);
+ }
+
+ /**
+ * Reads value stored by {@link setUserValue()}
+ *
+ * @param string $cookie_name cookie name
+ * @param mixed $cfg_value config value
+ *
+ * @return mixed
+ */
+ function getUserValue($cookie_name, $cfg_value)
+ {
+ $cookie_exists = isset($_COOKIE) && !empty($_COOKIE[$cookie_name]);
+ $prefs_type = $this->get('user_preferences');
+ if ($prefs_type == 'db') {
+ // permanent user preferences value exists, remove cookie
+ if ($cookie_exists) {
+ $this->removeCookie($cookie_name);
+ }
+ } else if ($cookie_exists) {
+ return $_COOKIE[$cookie_name];
+ }
+ // return value from $cfg array
+ return $cfg_value;
+ }
+
+ /**
+ * set source
+ *
+ * @param string $source source
+ *
+ * @return void
+ */
+ function setSource($source)
+ {
+ $this->source = trim($source);
+ }
+
+ /**
+ * check config source
+ *
+ * @return boolean whether source is valid or not
+ */
+ function checkConfigSource()
+ {
+ if (! $this->getSource()) {
+ // no configuration file set at all
+ return false;
+ }
+
+ if (! file_exists($this->getSource())) {
+ $this->source_mtime = 0;
+ return false;
+ }
+
+ if (! is_readable($this->getSource())) {
+ // manually check if file is readable
+ // might be bug #3059806 Supporting running from CIFS/Samba shares
+
+ $contents = false;
+ $handle = @fopen($this->getSource(), 'r');
+ if ($handle !== false) {
+ $contents = @fread($handle, 1); // reading 1 byte is enough to test
+ @fclose($handle);
+ }
+ if ($contents === false) {
+ $this->source_mtime = 0;
+ PMA_fatalError(
+ sprintf(
+ function_exists('__')
+ ? __('Existing configuration file (%s) is not readable.')
+ : 'Existing configuration file (%s) is not readable.',
+ $this->getSource()
+ )
+ );
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * verifies the permissions on config file (if asked by configuration)
+ * (must be called after config.inc.php has been merged)
+ *
+ * @return void
+ */
+ function checkPermissions()
+ {
+ // Check for permissions (on platforms that support it):
+ if ($this->get('CheckConfigurationPermissions')) {
+ $perms = @fileperms($this->getSource());
+ if (!($perms === false) && ($perms & 2)) {
+ // This check is normally done after loading configuration
+ $this->checkWebServerOs();
+ if ($this->get('PMA_IS_WINDOWS') == 0) {
+ $this->source_mtime = 0;
+ PMA_fatalError(
+ __(
+ 'Wrong permissions on configuration file, '
+ . 'should not be world writable!'
+ )
+ );
+ }
+ }
+ }
+ }
+
+ /**
+ * returns specific config setting
+ *
+ * @param string $setting config setting
+ *
+ * @return mixed value
+ */
+ function get($setting)
+ {
+ if (isset($this->settings[$setting])) {
+ return $this->settings[$setting];
+ }
+ return null;
+ }
+
+ /**
+ * sets configuration variable
+ *
+ * @param string $setting configuration option
+ * @param mixed $value new value for configuration option
+ *
+ * @return void
+ */
+ function set($setting, $value)
+ {
+ if (! isset($this->settings[$setting])
+ || $this->settings[$setting] !== $value
+ ) {
+ $this->settings[$setting] = $value;
+ $this->set_mtime = time();
+ }
+ }
+
+ /**
+ * returns source for current config
+ *
+ * @return string config source
+ */
+ function getSource()
+ {
+ return $this->source;
+ }
+
+ /**
+ * returns a unique value to force a CSS reload if either the config
+ * or the theme changes
+ * must also check the pma_fontsize cookie in case there is no
+ * config file
+ *
+ * @return int Summary of unix timestamps and fontsize,
+ * to be unique on theme parameters change
+ */
+ function getThemeUniqueValue()
+ {
+ if (null !== $this->get('fontsize')) {
+ $fontsize = intval($this->get('fontsize'));
+ } elseif (isset($_COOKIE['pma_fontsize'])) {
+ $fontsize = intval($_COOKIE['pma_fontsize']);
+ } else {
+ $fontsize = 0;
+ }
+ return (
+ $fontsize +
+ $this->source_mtime +
+ $this->default_source_mtime +
+ $this->get('user_preferences_mtime') +
+ $_SESSION['PMA_Theme']->mtime_info +
+ $_SESSION['PMA_Theme']->filesize_info);
+ }
+
+ /**
+ * $cfg['PmaAbsoluteUri'] is a required directive else cookies won't be
+ * set properly and, depending on browsers, inserting or updating a
+ * record might fail
+ *
+ * @return bool
+ */
+ function checkPmaAbsoluteUri()
+ {
+ // Setup a default value to let the people and lazy sysadmins work anyway,
+ // they'll get an error if the autodetect code doesn't work
+ $pma_absolute_uri = $this->get('PmaAbsoluteUri');
+ $is_https = $this->detectHttps();
+
+ if (strlen($pma_absolute_uri) < 5) {
+ $url = array();
+
+ // If we don't have scheme, we didn't have full URL so we need to
+ // dig deeper
+ if (empty($url['scheme'])) {
+ // Scheme
+ if ($is_https) {
+ $url['scheme'] = 'https';
+ } else {
+ $url['scheme'] = 'http';
+ }
+
+ // Host and port
+ if (PMA_getenv('HTTP_HOST')) {
+ // Prepend the scheme before using parse_url() since this
+ // is not part of the RFC2616 Host request-header
+ $parsed_url = parse_url(
+ $url['scheme'] . '://' . PMA_getenv('HTTP_HOST')
+ );
+ if (!empty($parsed_url['host'])) {
+ $url = $parsed_url;
+ } else {
+ $url['host'] = PMA_getenv('HTTP_HOST');
+ }
+ } elseif (PMA_getenv('SERVER_NAME')) {
+ $url['host'] = PMA_getenv('SERVER_NAME');
+ } else {
+ $this->error_pma_uri = true;
+ return false;
+ }
+
+ // If we didn't set port yet...
+ if (empty($url['port']) && PMA_getenv('SERVER_PORT')) {
+ $url['port'] = PMA_getenv('SERVER_PORT');
+ }
+
+ // And finally the path could be already set from REQUEST_URI
+ if (empty($url['path'])) {
+ // we got a case with nginx + php-fpm where PHP_SELF
+ // was not set, so PMA_PHP_SELF was not set as well
+ if (isset($GLOBALS['PMA_PHP_SELF'])) {
+ $path = parse_url($GLOBALS['PMA_PHP_SELF']);
+ } else {
+ $path = parse_url(PMA_getenv('REQUEST_URI'));
+ }
+ $url['path'] = $path['path'];
+ }
+ }
+
+ // Make url from parts we have
+ $pma_absolute_uri = $url['scheme'] . '://';
+ // Was there user information?
+ if (!empty($url['user'])) {
+ $pma_absolute_uri .= $url['user'];
+ if (!empty($url['pass'])) {
+ $pma_absolute_uri .= ':' . $url['pass'];
+ }
+ $pma_absolute_uri .= '@';
+ }
+ // Add hostname
+ $pma_absolute_uri .= $url['host'];
+ // Add port, if it not the default one
+ if (! empty($url['port'])
+ && (($url['scheme'] == 'http' && $url['port'] != 80)
+ || ($url['scheme'] == 'https' && $url['port'] != 443))
+ ) {
+ $pma_absolute_uri .= ':' . $url['port'];
+ }
+ // And finally path, without script name, the 'a' is there not to
+ // strip our directory, when path is only /pmadir/ without filename.
+ // Backslashes returned by Windows have to be changed.
+ // Only replace backslashes by forward slashes if on Windows,
+ // as the backslash could be valid on a non-Windows system.
+ $this->checkWebServerOs();
+ if ($this->get('PMA_IS_WINDOWS') == 1) {
+ $path = str_replace("\\", "/", dirname($url['path'] . 'a'));
+ } else {
+ $path = dirname($url['path'] . 'a');
+ }
+
+ // To work correctly within javascript
+ if (defined('PMA_PATH_TO_BASEDIR') && PMA_PATH_TO_BASEDIR == '../') {
+ if ($this->get('PMA_IS_WINDOWS') == 1) {
+ $path = str_replace("\\", "/", dirname($path));
+ } else {
+ $path = dirname($path);
+ }
+ }
+
+ // PHP's dirname function would have returned a dot
+ // when $path contains no slash
+ if ($path == '.') {
+ $path = '';
+ }
+ // in vhost situations, there could be already an ending slash
+ if (substr($path, -1) != '/') {
+ $path .= '/';
+ }
+ $pma_absolute_uri .= $path;
+
+ // This is to handle the case of a reverse proxy
+ if ($this->get('ForceSSL')) {
+ $this->set('PmaAbsoluteUri', $pma_absolute_uri);
+ $pma_absolute_uri = $this->getSSLUri();
+ $this->isHttps();
+ }
+
+ // We used to display a warning if PmaAbsoluteUri wasn't set, but now
+ // the autodetect code works well enough that we don't display the
+ // warning at all. The user can still set PmaAbsoluteUri manually.
+
+ } else {
+ // The URI is specified, however users do often specify this
+ // wrongly, so we try to fix this.
+
+ // Adds a trailing slash et the end of the phpMyAdmin uri if it
+ // does not exist.
+ if (substr($pma_absolute_uri, -1) != '/') {
+ $pma_absolute_uri .= '/';
+ }
+
+ // If URI doesn't start with http:// or https://, we will add
+ // this.
+ if (substr($pma_absolute_uri, 0, 7) != 'http://'
+ && substr($pma_absolute_uri, 0, 8) != 'https://'
+ ) {
+ $pma_absolute_uri
+ = ($is_https ? 'https' : 'http')
+ . ':' . (substr($pma_absolute_uri, 0, 2) == '//' ? '' : '//')
+ . $pma_absolute_uri;
+ }
+ }
+ $this->set('PmaAbsoluteUri', $pma_absolute_uri);
+ }
+
+ /**
+ * Converts currently used PmaAbsoluteUri to SSL based variant.
+ *
+ * @return String witch adjusted URI
+ */
+ function getSSLUri()
+ {
+ // grab current URL
+ $url = $this->get('PmaAbsoluteUri');
+ // Parse current URL
+ $parsed = parse_url($url);
+ // In case parsing has failed do stupid string replacement
+ if ($parsed === false) {
+ // Replace http protocol
+ return preg_replace('@^http:@', 'https:', $url);
+ }
+
+ // Reconstruct URL using parsed parts
+ return 'https://' . $parsed['host'] . ':443' . $parsed['path'];
+ }
+
+ /**
+ * check selected collation_connection
+ *
+ * @todo check validity of $_REQUEST['collation_connection']
+ *
+ * @return void
+ */
+ function checkCollationConnection()
+ {
+ if (! empty($_REQUEST['collation_connection'])) {
+ $this->set(
+ 'collation_connection',
+ strip_tags($_REQUEST['collation_connection'])
+ );
+ }
+ }
+
+ /**
+ * checks for font size configuration, and sets font size as requested by user
+ *
+ * @return void
+ */
+ function checkFontsize()
+ {
+ $new_fontsize = '';
+
+ if (isset($_GET['set_fontsize'])) {
+ $new_fontsize = $_GET['set_fontsize'];
+ } elseif (isset($_POST['set_fontsize'])) {
+ $new_fontsize = $_POST['set_fontsize'];
+ } elseif (isset($_COOKIE['pma_fontsize'])) {
+ $new_fontsize = $_COOKIE['pma_fontsize'];
+ }
+
+ if (preg_match('/^[0-9.]+(px|em|pt|\%)$/', $new_fontsize)) {
+ $this->set('fontsize', $new_fontsize);
+ } elseif (! $this->get('fontsize')) {
+ // 80% would correspond to the default browser font size
+ // of 16, but use 82% to help read the monoface font
+ $this->set('fontsize', '82%');
+ }
+
+ $this->setCookie('pma_fontsize', $this->get('fontsize'), '82%');
+ }
+
+ /**
+ * checks if upload is enabled
+ *
+ * @return void
+ */
+ function checkUpload()
+ {
+ if (ini_get('file_uploads')) {
+ $this->set('enable_upload', true);
+ // if set "php_admin_value file_uploads Off" in httpd.conf
+ // ini_get() also returns the string "Off" in this case:
+ if ('off' == strtolower(ini_get('file_uploads'))) {
+ $this->set('enable_upload', false);
+ }
+ } else {
+ $this->set('enable_upload', false);
+ }
+ }
+
+ /**
+ * Maximum upload size as limited by PHP
+ * Used with permission from Moodle (http://moodle.org) by Martin Dougiamas
+ *
+ * this section generates $max_upload_size in bytes
+ *
+ * @return void
+ */
+ function checkUploadSize()
+ {
+ if (! $filesize = ini_get('upload_max_filesize')) {
+ $filesize = "5M";
+ }
+
+ if ($postsize = ini_get('post_max_size')) {
+ $this->set(
+ 'max_upload_size',
+ min(PMA_getRealSize($filesize), PMA_getRealSize($postsize))
+ );
+ } else {
+ $this->set('max_upload_size', PMA_getRealSize($filesize));
+ }
+ }
+
+ /**
+ * Checks if protocol is https
+ *
+ * This function checks if the https protocol is used in the PmaAbsoluteUri
+ * configuration setting, as opposed to detectHttps() which checks if the
+ * https protocol is used on the active connection.
+ *
+ * @return bool
+ */
+ public function isHttps()
+ {
+
+ if (null !== $this->get('is_https')) {
+ return $this->get('is_https');
+ }
+
+ $url = parse_url($this->get('PmaAbsoluteUri'));
+ $is_https = null;
+
+ if (isset($url['scheme']) && $url['scheme'] == 'https') {
+ $is_https = true;
+ } else {
+ $is_https = false;
+ }
+
+ $this->set('is_https', $is_https);
+
+ return $is_https;
+ }
+
+ /**
+ * Detects whether https appears to be used.
+ *
+ * This function checks if the https protocol is used in the current connection
+ * with the webserver, based on environment variables.
+ * Please note that this just detects what we see, so
+ * it completely ignores things like reverse proxies.
+ *
+ * @return bool
+ */
+ function detectHttps()
+ {
+ $is_https = false;
+
+ $url = array();
+
+ // At first we try to parse REQUEST_URI, it might contain full URL,
+ if (PMA_getenv('REQUEST_URI')) {
+ // produces E_WARNING if it cannot get parsed, e.g. '/foobar:/'
+ $url = @parse_url(PMA_getenv('REQUEST_URI'));
+ if ($url === false) {
+ $url = array();
+ }
+ }
+
+ // If we don't have scheme, we didn't have full URL so we need to
+ // dig deeper
+ if (empty($url['scheme'])) {
+ // Scheme
+ if (PMA_getenv('HTTP_SCHEME')) {
+ $url['scheme'] = PMA_getenv('HTTP_SCHEME');
+ } elseif (PMA_getenv('HTTPS')
+ && strtolower(PMA_getenv('HTTPS')) == 'on'
+ ) {
+ $url['scheme'] = 'https';
+ // A10 Networks load balancer:
+ } elseif (PMA_getenv('HTTP_HTTPS_FROM_LB')
+ && strtolower(PMA_getenv('HTTP_HTTPS_FROM_LB')) == 'on') {
+ $url['scheme'] = 'https';
+ } elseif (PMA_getenv('HTTP_X_FORWARDED_PROTO')) {
+ $url['scheme'] = strtolower(PMA_getenv('HTTP_X_FORWARDED_PROTO'));
+ } elseif (PMA_getenv('HTTP_FRONT_END_HTTPS')
+ && strtolower(PMA_getenv('HTTP_FRONT_END_HTTPS')) == 'on'
+ ) {
+ $url['scheme'] = 'https';
+ } else {
+ $url['scheme'] = 'http';
+ }
+ }
+
+ if (isset($url['scheme']) && $url['scheme'] == 'https') {
+ $is_https = true;
+ } else {
+ $is_https = false;
+ }
+
+ return $is_https;
+ }
+
+ /**
+ * detect correct cookie path
+ *
+ * @return void
+ */
+ function checkCookiePath()
+ {
+ $this->set('cookie_path', $this->getCookiePath());
+ }
+
+ /**
+ * Get cookie path
+ *
+ * @return string
+ */
+ public function getCookiePath()
+ {
+ static $cookie_path = null;
+
+ if (null !== $cookie_path && !defined('TESTSUITE')) {
+ return $cookie_path;
+ }
+
+ $parsed_url = parse_url($this->get('PmaAbsoluteUri'));
+
+ $cookie_path = $parsed_url['path'];
+
+ return $cookie_path;
+ }
+
+ /**
+ * enables backward compatibility
+ *
+ * @return void
+ */
+ function enableBc()
+ {
+ $GLOBALS['cfg'] = $this->settings;
+ $GLOBALS['default_server'] = $this->default_server;
+ unset($this->default_server);
+ $GLOBALS['collation_connection'] = $this->get('collation_connection');
+ $GLOBALS['is_upload'] = $this->get('enable_upload');
+ $GLOBALS['max_upload_size'] = $this->get('max_upload_size');
+ $GLOBALS['cookie_path'] = $this->get('cookie_path');
+ $GLOBALS['is_https'] = $this->get('is_https');
+
+ $defines = array(
+ 'PMA_VERSION',
+ 'PMA_THEME_VERSION',
+ 'PMA_THEME_GENERATION',
+ 'PMA_PHP_STR_VERSION',
+ 'PMA_PHP_INT_VERSION',
+ 'PMA_IS_WINDOWS',
+ 'PMA_IS_IIS',
+ 'PMA_IS_GD2',
+ 'PMA_USR_OS',
+ 'PMA_USR_BROWSER_VER',
+ 'PMA_USR_BROWSER_AGENT'
+ );
+
+ foreach ($defines as $define) {
+ if (! defined($define)) {
+ define($define, $this->get($define));
+ }
+ }
+ }
+
+ /**
+ * returns options for font size selection
+ *
+ * @param string $current_size current selected font size with unit
+ *
+ * @return array selectable font sizes
+ *
+ * @static
+ */
+ static protected function getFontsizeOptions($current_size = '82%')
+ {
+ $unit = preg_replace('/[0-9.]*/', '', $current_size);
+ $value = preg_replace('/[^0-9.]*/', '', $current_size);
+
+ $factors = array();
+ $options = array();
+ $options["$value"] = $value . $unit;
+
+ if ($unit === '%') {
+ $factors[] = 1;
+ $factors[] = 5;
+ $factors[] = 10;
+ } elseif ($unit === 'em') {
+ $factors[] = 0.05;
+ $factors[] = 0.2;
+ $factors[] = 1;
+ } elseif ($unit === 'pt') {
+ $factors[] = 0.5;
+ $factors[] = 2;
+ } elseif ($unit === 'px') {
+ $factors[] = 1;
+ $factors[] = 5;
+ $factors[] = 10;
+ } else {
+ //unknown font size unit
+ $factors[] = 0.05;
+ $factors[] = 0.2;
+ $factors[] = 1;
+ $factors[] = 5;
+ $factors[] = 10;
+ }
+
+ foreach ($factors as $key => $factor) {
+ $option_inc = $value + $factor;
+ $option_dec = $value - $factor;
+ while (count($options) < 21) {
+ $options["$option_inc"] = $option_inc . $unit;
+ if ($option_dec > $factors[0]) {
+ $options["$option_dec"] = $option_dec . $unit;
+ }
+ $option_inc += $factor;
+ $option_dec -= $factor;
+ if (isset($factors[$key + 1])
+ && $option_inc >= $value + $factors[$key + 1]
+ ) {
+ break;
+ }
+ }
+ }
+ ksort($options);
+ return $options;
+ }
+
+ /**
+ * returns html selectbox for font sizes
+ *
+ * @static
+ *
+ * @return string html selectbox
+ */
+ static protected function getFontsizeSelection()
+ {
+ $current_size = $GLOBALS['PMA_Config']->get('fontsize');
+ // for the case when there is no config file (this is supported)
+ if (empty($current_size)) {
+ if (isset($_COOKIE['pma_fontsize'])) {
+ $current_size = $_COOKIE['pma_fontsize'];
+ } else {
+ $current_size = '82%';
+ }
+ }
+ $options = PMA_Config::getFontsizeOptions($current_size);
+
+ $return = '<label for="select_fontsize">' . __('Font size')
+ . ':</label>' . "\n"
+ . '<select name="set_fontsize" id="select_fontsize"'
+ . ' class="autosubmit">' . "\n";
+ foreach ($options as $option) {
+ $return .= '<option value="' . $option . '"';
+ if ($option == $current_size) {
+ $return .= ' selected="selected"';
+ }
+ $return .= '>' . $option . '</option>' . "\n";
+ }
+ $return .= '</select>';
+
+ return $return;
+ }
+
+ /**
+ * return complete font size selection form
+ *
+ * @static
+ *
+ * @return string html selectbox
+ */
+ static public function getFontsizeForm()
+ {
+ return '<form name="form_fontsize_selection" id="form_fontsize_selection"'
+ . ' method="get" action="index.php" class="disableAjax">' . "\n"
+ . PMA_URL_getHiddenInputs() . "\n"
+ . PMA_Config::getFontsizeSelection() . "\n"
+ . '</form>';
+ }
+
+ /**
+ * removes cookie
+ *
+ * @param string $cookie name of cookie to remove
+ *
+ * @return boolean result of setcookie()
+ */
+ function removeCookie($cookie)
+ {
+ if (defined('TESTSUITE')) {
+ if (isset($_COOKIE[$cookie])) {
+ unset($_COOKIE[$cookie]);
+ }
+ return true;
+ }
+ return setcookie(
+ $cookie,
+ '',
+ time() - 3600,
+ $this->getCookiePath(),
+ '',
+ $this->isHttps()
+ );
+ }
+
+ /**
+ * sets cookie if value is different from current cookie value,
+ * or removes if value is equal to default
+ *
+ * @param string $cookie name of cookie to remove
+ * @param mixed $value new cookie value
+ * @param string $default default value
+ * @param int $validity validity of cookie in seconds (default is one month)
+ * @param bool $httponly whether cookie is only for HTTP (and not for scripts)
+ *
+ * @return boolean result of setcookie()
+ */
+ function setCookie($cookie, $value, $default = null, $validity = null,
+ $httponly = true
+ ) {
+ if (strlen($value) && null !== $default && $value === $default) {
+ // default value is used
+ if (isset($_COOKIE[$cookie])) {
+ // remove cookie
+ return $this->removeCookie($cookie);
+ }
+ return false;
+ }
+
+ if (! strlen($value) && isset($_COOKIE[$cookie])) {
+ // remove cookie, value is empty
+ return $this->removeCookie($cookie);
+ }
+
+ if (! isset($_COOKIE[$cookie]) || $_COOKIE[$cookie] !== $value) {
+ // set cookie with new value
+ /* Calculate cookie validity */
+ if ($validity === null) {
+ $validity = time() + 2592000;
+ } elseif ($validity == 0) {
+ $validity = 0;
+ } else {
+ $validity = time() + $validity;
+ }
+ if (defined('TESTSUITE')) {
+ $_COOKIE[$cookie] = $value;
+ return true;
+ }
+ return setcookie(
+ $cookie,
+ $value,
+ $validity,
+ $this->getCookiePath(),
+ '',
+ $this->isHttps(),
+ $httponly
+ );
+ }
+
+ // cookie has already $value as value
+ return true;
+ }
+}
+
+
+/**
+ * Error handler to catch fatal errors when loading configuration
+ * file
+ *
+ * @return void
+ */
+function PMA_Config_fatalErrorHandler()
+{
+ if (isset($GLOBALS['pma_config_loading']) && $GLOBALS['pma_config_loading']) {
+ $error = error_get_last();
+ if ($error !== null) {
+ PMA_fatalError(
+ sprintf(
+ 'Failed to load phpMyAdmin configuration (%s:%s): %s',
+ PMA_Error::relPath($error['file']),
+ $error['line'],
+ $error['message']
+ )
+ );
+ }
+ }
+}
+
+if (!defined('TESTSUITE')) {
+ register_shutdown_function('PMA_Config_fatalErrorHandler');
+}
+
+?>
diff --git a/libraries/DBQbe.class.php b/libraries/DBQbe.class.php
new file mode 100644
index 0000000000..94b8274ac8
--- /dev/null
+++ b/libraries/DBQbe.class.php
@@ -0,0 +1,1374 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Handles DB QBE search
+ *
+ * @package PhpMyAdmin
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ * Class to handle database QBE search
+ *
+ * @package PhpMyAdmin
+ */
+class PMA_DbQbe
+{
+ /**
+ * Database name
+ *
+ * @access private
+ * @var string
+ */
+ private $_db;
+ /**
+ * Table Names (selected/non-selected)
+ *
+ * @access private
+ * @var array
+ */
+ private $_criteriaTables;
+ /**
+ * Column Names
+ *
+ * @access private
+ * @var array
+ */
+ private $_columnNames;
+ /**
+ * Number of columns
+ *
+ * @access private
+ * @var integer
+ */
+ private $_criteria_column_count;
+ /**
+ * Number of Rows
+ *
+ * @access private
+ * @var integer
+ */
+ private $_criteria_row_count;
+ /**
+ * Whether to insert a new column
+ *
+ * @access private
+ * @var array
+ */
+ private $_criteriaColumnInsert;
+ /**
+ * Whether to delete a column
+ *
+ * @access private
+ * @var array
+ */
+ private $_criteriaColumnDelete;
+ /**
+ * Whether to insert a new row
+ *
+ * @access private
+ * @var array
+ */
+ private $_criteriaRowInsert;
+ /**
+ * Already set criteria values
+ *
+ * @access private
+ * @var array
+ */
+ private $_criteria;
+ /**
+ * Previously set criteria values
+ *
+ * @access private
+ * @var array
+ */
+ private $_prev_criteria;
+ /**
+ * AND/OR relation b/w criteria columns
+ *
+ * @access private
+ * @var array
+ */
+ private $_criteriaAndOrColumn;
+ /**
+ * AND/OR relation b/w criteria rows
+ *
+ * @access private
+ * @var array
+ */
+ private $_criteriaAndOrRow;
+ /**
+ * Larget width of a column
+ *
+ * @access private
+ * @var string
+ */
+ private $_realwidth;
+ /**
+ * Minimum width of a column
+ *
+ * @access private
+ * @var string
+ */
+ private $_form_column_width;
+ /**
+ * Current criteria field
+ *
+ * @access private
+ * @var array
+ */
+ private $_curField;
+ /**
+ * Current criteria Sort options
+ *
+ * @access private
+ * @var array
+ */
+ private $_curSort;
+ /**
+ * Current criteria Show options
+ *
+ * @access private
+ * @var array
+ */
+ private $_curShow;
+ /**
+ * Current criteria values
+ *
+ * @access private
+ * @var array
+ */
+ private $_curCriteria;
+ /**
+ * Current criteria AND/OR column realtions
+ *
+ * @access private
+ * @var array
+ */
+ private $_curAndOrCol;
+ /**
+ * New column count in case of add/delete
+ *
+ * @access private
+ * @var integer
+ */
+ private $_new_column_count;
+ /**
+ * New row count in case of add/delete
+ *
+ * @access private
+ * @var integer
+ */
+ private $_new_row_count;
+
+ /**
+ * Public Constructor
+ *
+ * @param string $db Database name
+ */
+ public function __construct($db)
+ {
+ $this->_db = $db;
+ // Sets criteria parameters
+ $this->_setSearchParams();
+ $this->_setCriteriaTablesAndColumns();
+ }
+
+ /**
+ * Sets search parameters
+ *
+ * @return void
+ */
+ private function _setSearchParams()
+ {
+ // sets column count
+ $criteriaColumnCount = PMA_ifSetOr(
+ $_REQUEST['criteriaColumnCount'],
+ 3,
+ 'numeric'
+ );
+ $criteriaColumnAdd = PMA_ifSetOr(
+ $_REQUEST['criteriaColumnAdd'],
+ 0,
+ 'numeric'
+ );
+ $this->_criteria_column_count = max(
+ $criteriaColumnCount + $criteriaColumnAdd,
+ 0
+ );
+
+ // sets row count
+ $rows = PMA_ifSetOr($_REQUEST['rows'], 0, 'numeric');
+ $criteriaRowAdd = PMA_ifSetOr($_REQUEST['criteriaRowAdd'], 0, 'numeric');
+ $this->_criteria_row_count = max($rows + $criteriaRowAdd, 0);
+
+ $this->_criteriaColumnInsert = PMA_ifSetOr(
+ $_REQUEST['criteriaColumnInsert'],
+ null,
+ 'array'
+ );
+ $this->_criteriaColumnDelete = PMA_ifSetOr(
+ $_REQUEST['criteriaColumnDelete'],
+ null,
+ 'array'
+ );
+
+ $this->_prev_criteria = isset($_REQUEST['prev_criteria'])
+ ? $_REQUEST['prev_criteria']
+ : array();
+ $this->_criteria = isset($_REQUEST['criteria'])
+ ? $_REQUEST['criteria']
+ : array_fill(0, $criteriaColumnCount, '');
+
+ $this->_criteriaRowInsert = isset($_REQUEST['criteriaRowInsert'])
+ ? $_REQUEST['criteriaRowInsert']
+ : array_fill(0, $criteriaColumnCount, '');
+ $this->_criteriaRowDelete = isset($_REQUEST['criteriaRowDelete'])
+ ? $_REQUEST['criteriaRowDelete']
+ : array_fill(0, $criteriaColumnCount, '');
+ $this->_criteriaAndOrRow = isset($_REQUEST['criteriaAndOrRow'])
+ ? $_REQUEST['criteriaAndOrRow']
+ : array_fill(0, $criteriaColumnCount, '');
+ $this->_criteriaAndOrColumn = isset($_REQUEST['criteriaAndOrColumn'])
+ ? $_REQUEST['criteriaAndOrColumn']
+ : array_fill(0, $criteriaColumnCount, '');
+ // sets minimum width
+ $this->_form_column_width = 12;
+ $this->_curField = array();
+ $this->_curSort = array();
+ $this->_curShow = array();
+ $this->_curCriteria = array();
+ $this->_curAndOrRow = array();
+ $this->_curAndOrCol = array();
+ }
+
+ /**
+ * Sets criteria tables and columns
+ *
+ * @return void
+ */
+ private function _setCriteriaTablesAndColumns()
+ {
+ // The tables list sent by a previously submitted form
+ if (PMA_isValid($_REQUEST['TableList'], 'array')) {
+ foreach ($_REQUEST['TableList'] as $each_table) {
+ $this->_criteriaTables[$each_table] = ' selected="selected"';
+ }
+ } // end if
+ $all_tables = $GLOBALS['dbi']->query(
+ 'SHOW TABLES FROM ' . PMA_Util::backquote($this->_db) . ';',
+ null,
+ PMA_DatabaseInterface::QUERY_STORE
+ );
+ $all_tables_count = $GLOBALS['dbi']->numRows($all_tables);
+ if (0 == $all_tables_count) {
+ PMA_Message::error(__('No tables found in database.'))->display();
+ exit;
+ }
+ // The tables list gets from MySQL
+ while (list($table) = $GLOBALS['dbi']->fetchRow($all_tables)) {
+ $columns = $GLOBALS['dbi']->getColumns($this->_db, $table);
+
+ if (empty($this->_criteriaTables[$table])
+ && ! empty($_REQUEST['TableList'])
+ ) {
+ $this->_criteriaTables[$table] = '';
+ } else {
+ $this->_criteriaTables[$table] = ' selected="selected"';
+ } // end if
+
+ // The fields list per selected tables
+ if ($this->_criteriaTables[$table] == ' selected="selected"') {
+ $each_table = PMA_Util::backquote($table);
+ $this->_columnNames[] = $each_table . '.*';
+ foreach ($columns as $each_column) {
+ $each_column = $each_table . '.'
+ . PMA_Util::backquote($each_column['Field']);
+ $this->_columnNames[] = $each_column;
+ // increase the width if necessary
+ $this->_form_column_width = max(
+ strlen($each_column),
+ $this->_form_column_width
+ );
+ } // end foreach
+ } // end if
+ } // end while
+ $GLOBALS['dbi']->freeResult($all_tables);
+
+ // sets the largest width found
+ $this->_realwidth = $this->_form_column_width . 'ex';
+ }
+ /**
+ * Provides select options list containing column names
+ *
+ * @param integer $column_number Column Number (0,1,2) or more
+ * @param string $selected Selected criteria column name
+ *
+ * @return string HTML for select options
+ */
+ private function _showColumnSelectCell($column_number, $selected = '')
+ {
+ $html_output = '';
+ $html_output .= '<td class="center">';
+ $html_output .= '<select name="criteriaColumn[' . $column_number . ']" size="1">';
+ $html_output .= '<option value="">&nbsp;</option>';
+ foreach ($this->_columnNames as $column) {
+ $html_output .= '<option value="' . htmlspecialchars($column) . '"'
+ . (($column === $selected) ? ' selected="selected"' : '') . '>'
+ . str_replace(' ', '&nbsp;', htmlspecialchars($column))
+ . '</option>';
+ }
+ $html_output .= '</select>';
+ $html_output .= '</td>';
+ return $html_output;
+ }
+
+ /**
+ * Provides select options list containing sort options (ASC/DESC)
+ *
+ * @param integer $column_number Column Number (0,1,2) or more
+ * @param string $asc_selected Selected criteria 'Ascending'
+ * @param string $desc_selected Selected criteria 'Descending'
+ *
+ * @return string HTML for select options
+ */
+ private function _getSortSelectCell($column_number, $asc_selected = '',
+ $desc_selected = ''
+ ) {
+ $html_output = '<td class="center">';
+ $html_output .= '<select style="width: ' . $this->_realwidth
+ . '" name="criteriaSort[' . $column_number . ']" size="1">';
+ $html_output .= '<option value="">&nbsp;</option>';
+ $html_output .= '<option value="ASC"' . $asc_selected . '>'
+ . __('Ascending')
+ . '</option>';
+ $html_output .= '<option value="DESC"' . $desc_selected . '>'
+ . __('Descending')
+ . '</option>';
+ $html_output .= '</select>';
+ $html_output .= '</td>';
+ return $html_output;
+ }
+
+ /**
+ * Provides search form's row containing column select options
+ *
+ * @return string HTML for search table's row
+ */
+ private function _getColumnNamesRow()
+ {
+ $html_output = '<tr class="odd noclick">';
+ $html_output .= '<th>' . __('Column:') . '</th>';
+ $new_column_count = 0;
+ for ($column_index = 0; $column_index < $this->_criteria_column_count; $column_index++) {
+ if (isset($this->_criteriaColumnInsert[$column_index])
+ && $this->_criteriaColumnInsert[$column_index] == 'on'
+ ) {
+ $html_output .= $this->_showColumnSelectCell(
+ $new_column_count
+ );
+ $new_column_count++;
+ }
+ if (! empty($this->_criteriaColumnDelete)
+ && isset($this->_criteriaColumnDelete[$column_index])
+ && $this->_criteriaColumnDelete[$column_index] == 'on'
+ ) {
+ continue;
+ }
+ $selected = '';
+ if (isset($_REQUEST['criteriaColumn'][$column_index])) {
+ $selected = $_REQUEST['criteriaColumn'][$column_index];
+ $this->_curField[$new_column_count]
+ = $_REQUEST['criteriaColumn'][$column_index];
+ }
+ $html_output .= $this->_showColumnSelectCell(
+ $new_column_count,
+ $selected
+ );
+ $new_column_count++;
+ } // end for
+ $this->_new_column_count = $new_column_count;
+ $html_output .= '</tr>';
+ return $html_output;
+ }
+
+ /**
+ * Provides search form's row containing sort(ASC/DESC) select options
+ *
+ * @return string HTML for search table's row
+ */
+ private function _getSortRow()
+ {
+ $html_output = '<tr class="even noclick">';
+ $html_output .= '<th>' . __('Sort:') . '</th>';
+ $new_column_count = 0;
+ for ($column_index = 0; $column_index < $this->_criteria_column_count; $column_index++) {
+ if (! empty($this->_criteriaColumnInsert)
+ && isset($this->_criteriaColumnInsert[$column_index])
+ && $this->_criteriaColumnInsert[$column_index] == 'on'
+ ) {
+ $html_output .= $this->_getSortSelectCell($new_column_count);
+ $new_column_count++;
+ } // end if
+
+ if (! empty($this->_criteriaColumnDelete)
+ && isset($this->_criteriaColumnDelete[$column_index])
+ && $this->_criteriaColumnDelete[$column_index] == 'on'
+ ) {
+ continue;
+ }
+ // If they have chosen all fields using the * selector,
+ // then sorting is not available, Fix for Bug #570698
+ if (isset($_REQUEST['criteriaSort'][$column_index])
+ && isset($_REQUEST['criteriaColumn'][$column_index])
+ && substr($_REQUEST['criteriaColumn'][$column_index], -2) == '.*'
+ ) {
+ $_REQUEST['criteriaSort'][$column_index] = '';
+ } //end if
+ // Set asc_selected
+ if (isset($_REQUEST['criteriaSort'][$column_index])
+ && $_REQUEST['criteriaSort'][$column_index] == 'ASC'
+ ) {
+ $this->_curSort[$new_column_count]
+ = $_REQUEST['criteriaSort'][$column_index];
+ $asc_selected = ' selected="selected"';
+ } else {
+ $asc_selected = '';
+ } // end if
+ // Set desc selected
+ if (isset($_REQUEST['criteriaSort'][$column_index])
+ && $_REQUEST['criteriaSort'][$column_index] == 'DESC'
+ ) {
+ $this->_curSort[$new_column_count]
+ = $_REQUEST['criteriaSort'][$column_index];
+ $desc_selected = ' selected="selected"';
+ } else {
+ $desc_selected = '';
+ } // end if
+ $html_output .= $this->_getSortSelectCell(
+ $new_column_count, $asc_selected, $desc_selected
+ );
+ $new_column_count++;
+ } // end for
+ $html_output .= '</tr>';
+ return $html_output;
+ }
+
+ /**
+ * Provides search form's row containing SHOW checkboxes
+ *
+ * @return string HTML for search table's row
+ */
+ private function _getShowRow()
+ {
+ $html_output = '<tr class="odd noclick">';
+ $html_output .= '<th>' . __('Show:') . '</th>';
+ $new_column_count = 0;
+ for ($column_index = 0; $column_index < $this->_criteria_column_count; $column_index++) {
+ if (! empty($this->_criteriaColumnInsert)
+ && isset($this->_criteriaColumnInsert[$column_index])
+ && $this->_criteriaColumnInsert[$column_index] == 'on'
+ ) {
+ $html_output .= '<td class="center">';
+ $html_output .= '<input type="checkbox"'
+ . ' name="criteriaShow[' . $new_column_count . ']" />';
+ $html_output .= '</td>';
+ $new_column_count++;
+ } // end if
+ if (! empty($this->_criteriaColumnDelete)
+ && isset($this->_criteriaColumnDelete[$column_index])
+ && $this->_criteriaColumnDelete[$column_index] == 'on'
+ ) {
+ continue;
+ }
+ if (isset($_REQUEST['criteriaShow'][$column_index])) {
+ $checked_options = ' checked="checked"';
+ $this->_curShow[$new_column_count]
+ = $_REQUEST['criteriaShow'][$column_index];
+ } else {
+ $checked_options = '';
+ }
+ $html_output .= '<td class="center">';
+ $html_output .= '<input type="checkbox"'
+ . ' name="criteriaShow[' . $new_column_count . ']"'
+ . $checked_options . ' />';
+ $html_output .= '</td>';
+ $new_column_count++;
+ } // end for
+ $html_output .= '</tr>';
+ return $html_output;
+ }
+
+ /**
+ * Provides search form's row containing criteria Inputboxes
+ *
+ * @return string HTML for search table's row
+ */
+ private function _getCriteriaInputboxRow()
+ {
+ $html_output = '<tr class="even noclick">';
+ $html_output .= '<th>' . __('Criteria:') . '</th>';
+ $new_column_count = 0;
+ for ($column_index = 0; $column_index < $this->_criteria_column_count; $column_index++) {
+ if (! empty($this->_criteriaColumnInsert)
+ && isset($this->_criteriaColumnInsert[$column_index])
+ && $this->_criteriaColumnInsert[$column_index] == 'on'
+ ) {
+ $html_output .= '<td class="center">';
+ $html_output .= '<input type="text"'
+ . ' name="criteria[' . $new_column_count . ']"'
+ . ' value=""'
+ . ' class="textfield"'
+ . ' style="width: ' . $this->_realwidth . '"'
+ . ' size="20" />';
+ $html_output .= '</td>';
+ $new_column_count++;
+ } // end if
+ if (! empty($this->_criteriaColumnDelete)
+ && isset($this->_criteriaColumnDelete[$column_index])
+ && $this->_criteriaColumnDelete[$column_index] == 'on'
+ ) {
+ continue;
+ }
+ if (isset($this->_criteria[$column_index])) {
+ $tmp_criteria = $this->_criteria[$column_index];
+ }
+ if ((empty($this->_prev_criteria)
+ || ! isset($this->_prev_criteria[$column_index]))
+ || $this->_prev_criteria[$column_index] != htmlspecialchars($tmp_criteria)
+ ) {
+ $this->_curCriteria[$new_column_count] = $tmp_criteria;
+ } else {
+ $this->_curCriteria[$new_column_count]
+ = $this->_prev_criteria[$column_index];
+ }
+ $html_output .= '<td class="center">';
+ $html_output .= '<input type="hidden"'
+ . ' name="prev_criteria[' . $new_column_count . ']"'
+ . ' value="'
+ . htmlspecialchars($this->_curCriteria[$new_column_count])
+ . '" />';
+ $html_output .= '<input type="text"'
+ . ' name="criteria[' . $new_column_count . ']"'
+ . ' value="' . htmlspecialchars($tmp_criteria) . '"'
+ . ' class="textfield"'
+ . ' style="width: ' . $this->_realwidth . '"'
+ . ' size="20" />';
+ $html_output .= '</td>';
+ $new_column_count++;
+ } // end for
+ $html_output .= '</tr>';
+ return $html_output;
+ }
+
+ /**
+ * Provides footer options for adding/deleting row/columns
+ *
+ * @param string $type Whether row or column
+ *
+ * @return string HTML for footer options
+ */
+ private function _getFootersOptions($type)
+ {
+ $html_output = '<div class="floatleft">';
+ $html_output .= (($type == 'row')
+ ? __('Add/Delete criteria rows') : __('Add/Delete columns'));
+ $html_output .= ':<select size="1" name="'
+ . (($type == 'row') ? 'criteriaRowAdd' : 'criteriaColumnAdd') . '">';
+ $html_output .= '<option value="-3">-3</option>';
+ $html_output .= '<option value="-2">-2</option>';
+ $html_output .= '<option value="-1">-1</option>';
+ $html_output .= '<option value="0" selected="selected">0</option>';
+ $html_output .= '<option value="1">1</option>';
+ $html_output .= '<option value="2">2</option>';
+ $html_output .= '<option value="3">3</option>';
+ $html_output .= '</select>';
+ $html_output .= '</div>';
+ return $html_output;
+ }
+
+ /**
+ * Provides search form table's footer options
+ *
+ * @return string HTML for table footer
+ */
+ private function _getTableFooters()
+ {
+ $html_output = '<fieldset class="tblFooters">';
+ $html_output .= $this->_getFootersOptions("row");
+ $html_output .= $this->_getFootersOptions("column");
+ $html_output .= '<div class="floatleft">';
+ $html_output .= '<input type="submit" name="modify"'
+ . 'value="' . __('Update Query') . '" />';
+ $html_output .= '</div>';
+ $html_output .= '</fieldset>';
+ return $html_output;
+ }
+
+ /**
+ * Provides a select list of database tables
+ *
+ * @return string HTML for table select list
+ */
+ private function _getTablesList()
+ {
+ $html_output = '<div class="floatleft">';
+ $html_output .= '<fieldset>';
+ $html_output .= '<legend>' . __('Use Tables') . '</legend>';
+ // Build the options list for each table name
+ $options = '';
+ $numTableListOptions = 0;
+ foreach ($this->_criteriaTables as $key => $val) {
+ $options .= '<option value="' . htmlspecialchars($key) . '"' . $val . '>'
+ . (str_replace(' ', '&nbsp;', htmlspecialchars($key))) . '</option>';
+ $numTableListOptions++;
+ }
+ $html_output .= '<select name="TableList[]"'
+ . ' multiple="multiple" id="listTable"'
+ . ' size="' . (($numTableListOptions > 30) ? '15' : '7') . '">';
+ $html_output .= $options;
+ $html_output .= '</select>';
+ $html_output .= '</fieldset>';
+ $html_output .= '<fieldset class="tblFooters">';
+ $html_output .= '<input type="submit" name="modify" value="'
+ . __('Update Query') . '" />';
+ $html_output .= '</fieldset>';
+ $html_output .= '</div>';
+ return $html_output;
+ }
+
+ /**
+ * Provides And/Or modification cell along with Insert/Delete options
+ * (For modifying search form's table columns)
+ *
+ * @param integer $column_number Column Number (0,1,2) or more
+ * @param array $selected Selected criteria column name
+ *
+ * @return string HTML for modification cell
+ */
+ private function _getAndOrColCell($column_number, $selected = null)
+ {
+ $html_output = '<td class="center">';
+ $html_output .= '<strong>' . __('Or:') . '</strong>';
+ $html_output .= '<input type="radio"'
+ . ' name="criteriaAndOrColumn[' . $column_number . ']"'
+ . ' value="or"' . $selected['or'] . ' />';
+ $html_output .= '&nbsp;&nbsp;<strong>' . __('And:') . '</strong>';
+ $html_output .= '<input type="radio"'
+ . ' name="criteriaAndOrColumn[' . $column_number . ']"'
+ . ' value="and"' . $selected['and'] . ' />';
+ $html_output .= '<br />' . __('Ins');
+ $html_output .= '<input type="checkbox"'
+ . ' name="criteriaColumnInsert[' . $column_number . ']" />';
+ $html_output .= '&nbsp;&nbsp;' . __('Del');
+ $html_output .= '<input type="checkbox"'
+ . ' name="criteriaColumnDelete[' . $column_number . ']" />';
+ $html_output .= '</td>';
+ return $html_output;
+ }
+
+ /**
+ * Provides search form's row containing column modifications options
+ * (For modifying search form's table columns)
+ *
+ * @return string HTML for search table's row
+ */
+ private function _getModifyColumnsRow()
+ {
+ $html_output = '<tr class="even noclick">';
+ $html_output .= '<th>' . __('Modify:') . '</th>';
+ $new_column_count = 0;
+ for (
+ $column_index = 0;
+ $column_index < $this->_criteria_column_count;
+ $column_index++
+ ) {
+ if (! empty($this->_criteriaColumnInsert)
+ && isset($this->_criteriaColumnInsert[$column_index])
+ && $this->_criteriaColumnInsert[$column_index] == 'on'
+ ) {
+ $html_output .= $this->_getAndOrColCell($new_column_count);
+ $new_column_count++;
+ } // end if
+
+ if (! empty($this->_criteriaColumnDelete)
+ && isset($this->_criteriaColumnDelete[$column_index])
+ && $this->_criteriaColumnDelete[$column_index] == 'on'
+ ) {
+ continue;
+ }
+
+ if (isset($this->_criteriaAndOrColumn[$column_index])) {
+ $this->_curAndOrCol[$new_column_count]
+ = $this->_criteriaAndOrColumn[$column_index];
+ }
+ if (isset($this->_criteriaAndOrColumn[$column_index])
+ && $this->_criteriaAndOrColumn[$column_index] == 'or'
+ ) {
+ $checked_options['or'] = ' checked="checked"';
+ $checked_options['and'] = '';
+ } else {
+ $checked_options['and'] = ' checked="checked"';
+ $checked_options['or'] = '';
+ }
+ $html_output .= $this->_getAndOrColCell(
+ $new_column_count,
+ $checked_options
+ );
+ $new_column_count++;
+ } // end for
+ $html_output .= '</tr>';
+ return $html_output;
+ }
+
+ /**
+ * Provides Insert/Delete options for criteria inputbox
+ * with AND/OR relationship modification options
+ *
+ * @param integer $row_index Number of criteria row
+ * @param string $checked_options If checked
+ *
+ * @return string HTML
+ */
+ private function _getInsDelAndOrCell($row_index, $checked_options)
+ {
+ $html_output = '<td class="' . $GLOBALS['cell_align_right'] . ' nowrap">';
+ $html_output .= '<!-- Row controls -->';
+ $html_output .= '<table class="nospacing nopadding">';
+ $html_output .= '<tr>';
+ $html_output .= '<td class="' . $GLOBALS['cell_align_right'] . ' nowrap">';
+ $html_output .= '<small>' . __('Ins:') . '</small>';
+ $html_output .= '<input type="checkbox"'
+ . ' name="criteriaRowInsert[' . $row_index . ']" />';
+ $html_output .= '</td>';
+ $html_output .= '<td class="' . $GLOBALS['cell_align_right'] . '">';
+ $html_output .= '<strong>' . __('And:') . '</strong>';
+ $html_output .= '</td>';
+ $html_output .= '<td>';
+ $html_output .= '<input type="radio"'
+ . ' name="criteriaAndOrRow[' . $row_index . ']" value="and"'
+ . $checked_options['and'] . ' />';
+ $html_output .= '</td>';
+ $html_output .= '</tr>';
+ $html_output .= '<tr>';
+ $html_output .= '<td class="' . $GLOBALS['cell_align_right'] . ' nowrap">';
+ $html_output .= '<small>' . __('Del:') . '</small>';
+ $html_output .= '<input type="checkbox"'
+ . ' name="criteriaRowDelete[' . $row_index . ']" />';
+ $html_output .= '</td>';
+ $html_output .= '<td class="' . $GLOBALS['cell_align_right'] . '">';
+ $html_output .= '<strong>' . __('Or:') . '</strong>';
+ $html_output .= '</td>';
+ $html_output .= '<td>';
+ $html_output .= '<input type="radio"'
+ . ' name="criteriaAndOrRow[' . $row_index . ']"'
+ . ' value="or"' . $checked_options['or'] . ' />';
+ $html_output .= '</td>';
+ $html_output .= '</tr>';
+ $html_output .= '</table>';
+ $html_output .= '</td>';
+ return $html_output;
+ }
+
+ /**
+ * Provides rows for criteria inputbox Insert/Delete options
+ * with AND/OR relationship modification options
+ *
+ * @param integer $new_row_index New row index if rows are added/deleted
+ * @param integer $row_index Row index
+ *
+ * @return string HTML table rows
+ */
+ private function _getInputboxRow($new_row_index, $row_index)
+ {
+ $html_output = '';
+ $new_column_count = 0;
+ for ($column_index = 0; $column_index < $this->_criteria_column_count; $column_index++) {
+ if (! empty($this->_criteriaColumnInsert)
+ && isset($this->_criteriaColumnInsert[$column_index])
+ && $this->_criteriaColumnInsert[$column_index] == 'on'
+ ) {
+ $or = 'Or' . $new_row_index . '[' . $new_column_count . ']';
+ $html_output .= '<td class="center">';
+ $html_output .= '<input type="text"'
+ . ' name="Or' . $or . '" class="textfield"'
+ . ' style="width: ' . $this->_realwidth . '" size="20" />';
+ $html_output .= '</td>';
+ $new_column_count++;
+ } // end if
+ if (! empty($this->_criteriaColumnDelete)
+ && isset($this->_criteriaColumnDelete[$column_index])
+ && $this->_criteriaColumnDelete[$column_index] == 'on'
+ ) {
+ continue;
+ }
+ $or = 'Or' . $new_row_index;
+ if (! empty($_POST[$or]) && isset($_POST[$or][$column_index])) {
+ $tmp_or = $_POST[$or][$column_index];
+ } else {
+ $tmp_or = '';
+ }
+ $html_output .= '<td class="center">';
+ $html_output .= '<input type="text"'
+ . ' name="Or' . $new_row_index . '[' . $new_column_count . ']' . '"'
+ . ' value="' . htmlspecialchars($tmp_or) . '" class="textfield"'
+ . ' style="width: ' . $this->_realwidth . '" size="20" />';
+ $html_output .= '</td>';
+ if (! empty(${$or}) && isset(${$or}[$column_index])) {
+ $GLOBALS[${'cur' . $or}][$new_column_count] = ${$or}[$column_index];
+ }
+ $new_column_count++;
+ } // end for
+ return $html_output;
+ }
+
+ /**
+ * Provides rows for criteria inputbox Insert/Delete options
+ * with AND/OR relationship modification options
+ *
+ * @return string HTML table rows
+ */
+ private function _getInsDelAndOrCriteriaRows()
+ {
+ $html_output = '';
+ $new_row_count = 0;
+ $odd_row = true;
+ for (
+ $row_index = 0;
+ $row_index <= $this->_criteria_row_count;
+ $row_index++
+ ) {
+ if (isset($this->_criteriaRowInsert[$row_index])
+ && $this->_criteriaRowInsert[$row_index] == 'on'
+ ) {
+ $checked_options['or'] = ' checked="checked"';
+ $checked_options['and'] = '';
+ $html_output .= '<tr class="' . ($odd_row ? 'odd' : 'even') . ' noclick">';
+ $html_output .= $this->_getInsDelAndOrCell(
+ $new_row_count, $checked_options
+ );
+ $html_output .= $this->_getInputboxRow(
+ $new_row_count, $row_index
+ );
+ $new_row_count++;
+ $html_output .= '</tr>';
+ $odd_row =! $odd_row;
+ } // end if
+ if (isset($this->_criteriaRowDelete[$row_index])
+ && $this->_criteriaRowDelete[$row_index] == 'on'
+ ) {
+ continue;
+ }
+ if (isset($this->_criteriaAndOrRow[$row_index])) {
+ $this->_curAndOrRow[$new_row_count]
+ = $this->_criteriaAndOrRow[$row_index];
+ }
+ if (isset($this->_criteriaAndOrRow[$row_index])
+ && $this->_criteriaAndOrRow[$row_index] == 'and'
+ ) {
+ $checked_options['and'] = ' checked="checked"';
+ $checked_options['or'] = '';
+ } else {
+ $checked_options['or'] = ' checked="checked"';
+ $checked_options['and'] = '';
+ }
+ $html_output .= '<tr class="' . ($odd_row ? 'odd' : 'even') . ' noclick">';
+ $html_output .= $this->_getInsDelAndOrCell(
+ $new_row_count, $checked_options
+ );
+ $html_output .= $this->_getInputboxRow(
+ $new_row_count, $row_index
+ );
+ $new_row_count++;
+ $html_output .= '</tr>';
+ $odd_row =! $odd_row;
+ } // end for
+ $this->_new_row_count = $new_row_count;
+ return $html_output;
+ }
+
+ /**
+ * Provides SELECT clause for building SQL query
+ *
+ * @return string Select clause
+ */
+ private function _getSelectClause()
+ {
+ $select_clause = '';
+ $select_clauses = array();
+ for ($column_index = 0; $column_index < $this->_criteria_column_count; $column_index++) {
+ if (! empty($this->_curField[$column_index])
+ && isset($this->_curShow[$column_index])
+ && $this->_curShow[$column_index] == 'on'
+ ) {
+ $select_clauses[] = $this->_curField[$column_index];
+ }
+ } // end for
+ if ($select_clauses) {
+ $select_clause = 'SELECT '
+ . htmlspecialchars(implode(", ", $select_clauses)) . "\n";
+ }
+ return $select_clause;
+ }
+
+ /**
+ * Provides WHERE clause for building SQL query
+ *
+ * @return string Where clause
+ */
+ private function _getWhereClause()
+ {
+ $where_clause = '';
+ $criteria_cnt = 0;
+ for (
+ $column_index = 0;
+ $column_index < $this->_criteria_column_count;
+ $column_index++
+ ) {
+ if (! empty($this->_curField[$column_index])
+ && ! empty($this->_curCriteria[$column_index])
+ && $column_index
+ && isset($last_where)
+ && isset($this->_curAndOrCol)
+ ) {
+ $where_clause .= ' '
+ . strtoupper($this->_curAndOrCol[$last_where]) . ' ';
+ }
+ if (! empty($this->_curField[$column_index])
+ && ! empty($this->_curCriteria[$column_index])
+ ) {
+ $where_clause .= '(' . $this->_curField[$column_index] . ' '
+ . $this->_curCriteria[$column_index] . ')';
+ $last_where = $column_index;
+ $criteria_cnt++;
+ }
+ } // end for
+ if ($criteria_cnt > 1) {
+ $where_clause = '(' . $where_clause . ')';
+ }
+ // OR rows ${'cur' . $or}[$column_index]
+ if (! isset($this->_curAndOrRow)) {
+ $this->_curAndOrRow = array();
+ }
+ for (
+ $row_index = 0;
+ $row_index <= $this->_criteria_row_count;
+ $row_index++
+ ) {
+ $criteria_cnt = 0;
+ $qry_orwhere = '';
+ $last_orwhere = '';
+ for (
+ $column_index = 0;
+ $column_index < $this->_criteria_column_count;
+ $column_index++
+ ) {
+ if (! empty($this->_curField[$column_index])
+ && ! empty($_REQUEST['Or' .$row_index][$column_index])
+ && $column_index
+ ) {
+ $qry_orwhere .= ' '
+ . strtoupper($this->_curAndOrCol[$last_orwhere]) . ' ';
+ }
+ if (! empty($this->_curField[$column_index])
+ && ! empty($_REQUEST['Or' .$row_index][$column_index])
+ ) {
+ $qry_orwhere .= '(' . $this->_curField[$column_index]
+ . ' '
+ . $_REQUEST['Or' .$row_index][$column_index]
+ . ')';
+ $last_orwhere = $column_index;
+ $criteria_cnt++;
+ }
+ } // end for
+ if ($criteria_cnt > 1) {
+ $qry_orwhere = '(' . $qry_orwhere . ')';
+ }
+ if (! empty($qry_orwhere)) {
+ $where_clause .= "\n"
+ . strtoupper(
+ isset($this->_curAndOrRow[$row_index])
+ ? $this->_curAndOrRow[$row_index] . ' '
+ : ''
+ )
+ . $qry_orwhere;
+ } // end if
+ } // end for
+
+ if (! empty($where_clause) && $where_clause != '()') {
+ $where_clause = 'WHERE ' . htmlspecialchars($where_clause) . "\n";
+ } // end if
+ return $where_clause;
+ }
+
+ /**
+ * Provides ORDER BY clause for building SQL query
+ *
+ * @return string Order By clause
+ */
+ private function _getOrderByClause()
+ {
+ $orderby_clause = '';
+ $orderby_clauses = array();
+ for (
+ $column_index = 0;
+ $column_index < $this->_criteria_column_count;
+ $column_index++
+ ) {
+ // if all columns are chosen with * selector,
+ // then sorting isn't available
+ // Fix for Bug #570698
+ if (! empty($this->_curField[$column_index])
+ && ! empty($this->_curSort[$column_index])
+ ) {
+ if (substr($this->_curField[$column_index], -2) == '.*') {
+ continue;
+ }
+ $orderby_clauses[] = $this->_curField[$column_index] . ' '
+ . $this->_curSort[$column_index];
+ }
+ } // end for
+ if ($orderby_clauses) {
+ $orderby_clause = 'ORDER BY '
+ . htmlspecialchars(implode(", ", $orderby_clauses)) . "\n";
+ }
+ return $orderby_clause;
+ }
+
+ /**
+ * Provides UNIQUE columns and INDEX columns present in criteria tables
+ *
+ * @param array $all_tables Tables involved in the search
+ * @param array $all_columns Columns involved in the search
+ * @param array $where_clause_columns Columns having criteria where clause
+ *
+ * @return array having UNIQUE and INDEX columns
+ */
+ private function _getIndexes($all_tables, $all_columns,
+ $where_clause_columns
+ ) {
+ $unique_columns = array();
+ $index_columns = array();
+
+ foreach ($all_tables as $table) {
+ $indexes = $GLOBALS['dbi']->getTableIndexes($this->_db, $table);
+ foreach ($indexes as $index) {
+ $column = $table . '.' . $index['Column_name'];
+ if (isset($all_columns[$column])) {
+ if ($index['Non_unique'] == 0) {
+ if (isset($where_clause_columns[$column])) {
+ $unique_columns[$column] = 'Y';
+ } else {
+ $unique_columns[$column] = 'N';
+ }
+ } else {
+ if (isset($where_clause_columns[$column])) {
+ $index_columns[$column] = 'Y';
+ } else {
+ $index_columns[$column] = 'N';
+ }
+ }
+ }
+ } // end while (each index of a table)
+ } // end while (each table)
+
+ return array(
+ 'unique' => $unique_columns,
+ 'index' => $index_columns
+ );
+ }
+
+ /**
+ * Provides UNIQUE columns and INDEX columns present in criteria tables
+ *
+ * @param array $all_tables Tables involved in the search
+ * @param array $all_columns Columns involved in the search
+ * @param array $where_clause_columns Columns having criteria where clause
+ *
+ * @return array having UNIQUE and INDEX columns
+ */
+ private function _getLeftJoinColumnCandidates($all_tables, $all_columns,
+ $where_clause_columns
+ ) {
+ $GLOBALS['dbi']->selectDb($this->_db);
+ $candidate_columns = array();
+
+ // Get unique columns and index columns
+ $indexes = $this->_getIndexes(
+ $all_tables, $all_columns, $where_clause_columns
+ );
+ $unique_columns = $indexes['unique'];
+ $index_columns = $indexes['index'];
+
+ // now we want to find the best.
+ if (isset($unique_columns) && count($unique_columns) > 0) {
+ $candidate_columns = $unique_columns;
+ $needsort = 1;
+ } elseif (isset($index_columns) && count($index_columns) > 0) {
+ $candidate_columns = $index_columns;
+ $needsort = 1;
+ } elseif (isset($where_clause_columns) && count($where_clause_columns) > 0) {
+ $candidate_columns = $where_clause_columns;
+ $needsort = 0;
+ } else {
+ $candidate_columns = $all_tables;
+ $needsort = 0;
+ }
+
+ // If we came up with $unique_columns (very good) or $index_columns (still
+ // good) as $candidate_columns we want to check if we have any 'Y' there
+ // (that would mean that they were also found in the whereclauses
+ // which would be great). if yes, we take only those
+ if ($needsort == 1) {
+ foreach ($candidate_columns as $column => $is_where) {
+ $table = explode('.', $column);
+ $table = $table[0];
+ if ($is_where == 'Y') {
+ $vg[$column] = $table;
+ } else {
+ $sg[$column] = $table;
+ }
+ }
+ if (isset($vg)) {
+ $candidate_columns = $vg;
+ // Candidates restricted in index+where
+ } else {
+ $candidate_columns = $sg;
+ // None of the candidates where in a where-clause
+ }
+ }
+
+ return $candidate_columns;
+ }
+
+ /**
+ * Provides the main table to form the LEFT JOIN clause
+ *
+ * @param array $all_tables Tables involved in the search
+ * @param array $all_columns Columns involved in the search
+ * @param array $where_clause_columns Columns having criteria where clause
+ * @param array $where_clause_tables Tables having criteria where clause
+ *
+ * @return string table name
+ */
+ private function _getMasterTable($all_tables, $all_columns,
+ $where_clause_columns, $where_clause_tables
+ ) {
+ $master = '';
+ if (count($where_clause_tables) == 1) {
+ // If there is exactly one column that has a decent where-clause
+ // we will just use this
+ $master = key($where_clause_tables);
+ } else {
+ // Now let's find out which of the tables has an index
+ // (When the control user is the same as the normal user
+ // because he is using one of his databases as pmadb,
+ // the last db selected is not always the one where we need to work)
+ $candidate_columns = $this->_getLeftJoinColumnCandidates(
+ $all_tables, $all_columns, $where_clause_columns
+ );
+ // If our array of candidates has more than one member we'll just
+ // find the smallest table.
+ // Of course the actual query would be faster if we check for
+ // the Criteria which gives the smallest result set in its table,
+ // but it would take too much time to check this
+ if (count($candidate_columns) > 1) {
+ // Of course we only want to check each table once
+ $checked_tables = $candidate_columns;
+ foreach ($candidate_columns as $table) {
+ if ($checked_tables[$table] != 1) {
+ $tsize[$table] = PMA_Table::countRecords(
+ $this->_db,
+ $table,
+ false
+ );
+ $checked_tables[$table] = 1;
+ }
+ $csize[$table] = $tsize[$table];
+ }
+ asort($csize);
+ reset($csize);
+ $master = key($csize); // Smallest
+ } else {
+ reset($candidate_columns);
+ $master = current($candidate_columns); // Only one single candidate
+ }
+ } // end if (exactly one where clause)
+ return $master;
+ }
+
+ /**
+ * Provides columns and tables that have valid where clause criteria
+ *
+ * @return array
+ */
+ private function _getWhereClauseTablesAndColumns()
+ {
+ $where_clause_columns = array();
+ $where_clause_tables = array();
+ // Now we need all tables that we have in the where clause
+ for (
+ $column_index = 0;
+ $column_index < count($this->_criteria);
+ $column_index++
+ ) {
+ $current_table = explode('.', $_POST['criteriaColumn'][$column_index]);
+ if (empty($current_table[0]) || empty($current_table[1])) {
+ continue;
+ } // end if
+ $table = str_replace('`', '', $current_table[0]);
+ $column = str_replace('`', '', $current_table[1]);
+ $column = $table . '.' . $column;
+ // Now we know that our array has the same numbers as $criteria
+ // we can check which of our columns has a where clause
+ if (! empty($this->_criteria[$column_index])) {
+ if (substr($this->_criteria[$column_index], 0, 1) == '='
+ || stristr($this->_criteria[$column_index], 'is')
+ ) {
+ $where_clause_columns[$column] = $column;
+ $where_clause_tables[$table] = $table;
+ }
+ } // end if
+ } // end for
+ return array(
+ 'where_clause_tables' => $where_clause_tables,
+ 'where_clause_columns' => $where_clause_columns
+ );
+ }
+
+ /**
+ * Provides FROM clause for building SQL query
+ *
+ * @param string $cfgRelation Relation Settings
+ *
+ * @return FROM clause
+ */
+ private function _getFromClause($cfgRelation)
+ {
+ $from_clause = '';
+ if (isset($_POST['criteriaColumn']) && count($_POST['criteriaColumn']) > 0) {
+ // Initialize some variables
+ $all_tables = $all_columns = array();
+
+ // We only start this if we have fields, otherwise it would be dumb
+ foreach ($_POST['criteriaColumn'] as $value) {
+ $parts = explode('.', $value);
+ if (! empty($parts[0]) && ! empty($parts[1])) {
+ $table = str_replace('`', '', $parts[0]);
+ $all_tables[$table] = $table;
+ $all_columns[] = $table . '.' . str_replace('`', '', $parts[1]);
+ }
+ } // end while
+
+ // Create LEFT JOINS out of Relations
+ if ($cfgRelation['relwork'] && count($all_tables) > 0) {
+ // Get tables and columns with valid where clauses
+ $valid_where_clauses = $this->_getWhereClauseTablesAndColumns();
+ $where_clause_tables = $valid_where_clauses['where_clause_tables'];
+ $where_clause_columns = $valid_where_clauses['where_clause_columns'];
+ // Get master table
+ $master = $this->_getMasterTable(
+ $all_tables, $all_columns,
+ $where_clause_columns, $where_clause_tables
+ );
+ $from_clause = PMA_Util::backquote($master)
+ . PMA_getRelatives($all_tables, $master);
+
+ } // end if ($cfgRelation['relwork'] && count($all_tables) > 0)
+ } // end count($_POST['criteriaColumn']) > 0
+
+ // In case relations are not defined, just generate the FROM clause
+ // from the list of tables, however we don't generate any JOIN
+ if (empty($from_clause) && isset($all_tables)) {
+ $from_clause = implode(', ', $all_tables);
+ }
+ return $from_clause;
+ }
+
+ /**
+ * Provides the generated SQL query
+ *
+ * @param string $cfgRelation Relation Settings
+ *
+ * @return string SQL query
+ */
+ private function _getSQLQuery($cfgRelation)
+ {
+ $sql_query = '';
+ // get SELECT clause
+ $sql_query .= $this->_getSelectClause();
+ // get FROM clause
+ $from_clause = $this->_getFromClause($cfgRelation);
+ if (! empty($from_clause)) {
+ $sql_query .= 'FROM ' . htmlspecialchars($from_clause) . "\n";
+ }
+ // get WHERE clause
+ $sql_query .= $this->_getWhereClause();
+ // get ORDER BY clause
+ $sql_query .= $this->_getOrderByClause();
+ return $sql_query;
+ }
+
+ /**
+ * Provides the generated QBE form
+ *
+ * @param string $cfgRelation Relation Settings
+ *
+ * @return string QBE form
+ */
+ public function getSelectionForm($cfgRelation)
+ {
+ $html_output = '<form action="db_qbe.php" method="post">';
+ $html_output .= '<fieldset>';
+ $html_output .= '<table class="data" style="width: 100%;">';
+ // Get table's <tr> elements
+ $html_output .= $this->_getColumnNamesRow();
+ $html_output .= $this->_getSortRow();
+ $html_output .= $this->_getShowRow();
+ $html_output .= $this->_getCriteriaInputboxRow();
+ $html_output .= $this->_getInsDelAndOrCriteriaRows();
+ $html_output .= $this->_getModifyColumnsRow();
+ $html_output .= '</table>';
+ $this->_new_row_count--;
+ $url_params['db'] = $this->_db;
+ $url_params['criteriaColumnCount'] = $this->_new_column_count;
+ $url_params['rows'] = $this->_new_row_count;
+ $html_output .= PMA_URL_getHiddenInputs($url_params);
+ $html_output .= '</fieldset>';
+ // get footers
+ $html_output .= $this->_getTableFooters();
+ // get tables select list
+ $html_output .= $this->_getTablesList();
+ $html_output .= '</form>';
+ $html_output .= '<form action="db_qbe.php" method="post">';
+ $html_output .= PMA_URL_getHiddenInputs(array('db' => $this->_db));
+ // get SQL query
+ $html_output .= '<div class="floatleft">';
+ $html_output .= '<fieldset>';
+ $html_output .= '<legend>'
+ . sprintf(
+ __('SQL query on database <b>%s</b>:'),
+ PMA_Util::getDbLink($this->_db)
+ );
+ $html_output .= '</legend>';
+ $text_dir = 'ltr';
+ $html_output .= '<textarea cols="80" name="sql_query" id="textSqlquery"'
+ . ' rows="' . ((count($this->_criteriaTables) > 30) ? '15' : '7') . '"'
+ . ' dir="' . $text_dir . '">';
+ $html_output .= $this->_getSQLQuery($cfgRelation);
+ $html_output .= '</textarea>';
+ $html_output .= '</fieldset>';
+ // displays form's footers
+ $html_output .= '<fieldset class="tblFooters">';
+ $html_output .= '<input type="hidden" name="submit_sql" value="1" />';
+ $html_output .= '<input type="submit" value="' . __('Submit Query') . '" />';
+ $html_output .= '</fieldset>';
+ $html_output .= '</div>';
+ $html_output .= '</form>';
+ return $html_output;
+ }
+}
+?>
diff --git a/libraries/DatabaseInterface.class.php b/libraries/DatabaseInterface.class.php
new file mode 100644
index 0000000000..f98a8c84dd
--- /dev/null
+++ b/libraries/DatabaseInterface.class.php
@@ -0,0 +1,2353 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Main interface for database interactions
+ *
+ * @package PhpMyAdmin-DBI
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ * Main interface for database interactions
+ *
+ * @package PhpMyAdmin-DBI
+ */
+class PMA_DatabaseInterface
+{
+ /**
+ * Force STORE_RESULT method, ignored by classic MySQL.
+ */
+ const QUERY_STORE = 1;
+ /**
+ * Do not read whole query.
+ */
+ const QUERY_UNBUFFERED = 2;
+ /**
+ * Get session variable.
+ */
+ const GETVAR_SESSION = 1;
+ /**
+ * Get global variable.
+ */
+ const GETVAR_GLOBAL = 2;
+
+ /**
+ * @var PMA_DBI_Extension
+ */
+ private $_extension;
+
+ /**
+ * Constructor
+ *
+ * @param PMA_DBI_Extension $ext Object to be used for database queries
+ */
+ public function __construct(PMA_DBI_Extension $ext)
+ {
+ $this->_extension = $ext;
+ }
+
+ /**
+ * Checks whether database extension is loaded
+ *
+ * @param string $extension mysql extension to check
+ *
+ * @return bool
+ */
+ public static function checkDbExtension($extension = 'mysql')
+ {
+ if ($extension == 'drizzle' && function_exists('drizzle_create')) {
+ return true;
+ } else if (function_exists($extension . '_connect')) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * runs a query
+ *
+ * @param string $query SQL query to execte
+ * @param mixed $link optional database link to use
+ * @param int $options optional query options
+ * @param bool $cache_affected_rows whether to cache affected rows
+ *
+ * @return mixed
+ */
+ public function query($query, $link = null, $options = 0,
+ $cache_affected_rows = true
+ ) {
+ $res = $this->tryQuery($query, $link, $options, $cache_affected_rows)
+ or PMA_Util::mysqlDie($this->getError($link), $query);
+ return $res;
+ }
+
+ /**
+ * Stores query data into session data for debugging purposes
+ *
+ * @param string $query Query text
+ * @param resource $link database link
+ * @param resource $result Query result
+ * @param integer $time Time to execute query
+ *
+ * @return void
+ */
+ private function _dbgQuery($query, $link, $result, $time)
+ {
+ $hash = md5($query);
+
+ if (isset($_SESSION['debug']['queries'][$hash])) {
+ $_SESSION['debug']['queries'][$hash]['count']++;
+ } else {
+ $_SESSION['debug']['queries'][$hash] = array();
+ if ($result == false) {
+ $_SESSION['debug']['queries'][$hash]['error']
+ = '<b style="color:red">' . mysqli_error($link) . '</b>';
+ }
+ $_SESSION['debug']['queries'][$hash]['count'] = 1;
+ $_SESSION['debug']['queries'][$hash]['query'] = $query;
+ $_SESSION['debug']['queries'][$hash]['time'] = $time;
+ }
+
+ $trace = array();
+ foreach (debug_backtrace() as $trace_step) {
+ $trace[]
+ = (isset($trace_step['file'])
+ ? PMA_Error::relPath($trace_step['file'])
+ : '')
+ . (isset($trace_step['line'])
+ ? '#' . $trace_step['line'] . ': '
+ : '')
+ . (isset($trace_step['class']) ? $trace_step['class'] : '')
+ . (isset($trace_step['type']) ? $trace_step['type'] : '')
+ . (isset($trace_step['function']) ? $trace_step['function'] : '')
+ . '('
+ . (isset($trace_step['params'])
+ ? implode(', ', $trace_step['params'])
+ : ''
+ )
+ . ')'
+ ;
+ }
+ $_SESSION['debug']['queries'][$hash]['trace'][] = $trace;
+ }
+
+ /**
+ * runs a query and returns the result
+ *
+ * @param string $query query to run
+ * @param resource $link mysql link resource
+ * @param integer $options query options
+ * @param bool $cache_affected_rows whether to cache affected row
+ *
+ * @return mixed
+ */
+ public function tryQuery($query, $link = null, $options = 0,
+ $cache_affected_rows = true
+ ) {
+ if (empty($link)) {
+ if (isset($GLOBALS['userlink'])) {
+ $link = $GLOBALS['userlink'];
+ } else {
+ return false;
+ }
+ }
+
+ if ($GLOBALS['cfg']['DBG']['sql']) {
+ $time = microtime(true);
+ }
+
+ $result = $this->_extension->realQuery($query, $link, $options);
+
+ if ($cache_affected_rows) {
+ $GLOBALS['cached_affected_rows'] = $this->affectedRows($link, false);
+ }
+
+ if ($GLOBALS['cfg']['DBG']['sql']) {
+ $time = microtime(true) - $time;
+ $this->_dbgQuery($query, $link, $result, $time);
+ }
+ if ($result != false && PMA_Tracker::isActive() == true ) {
+ PMA_Tracker::handleQuery($query);
+ }
+
+ return $result;
+ }
+
+ /**
+ * Run multi query statement and return results
+ *
+ * @param string $multi_query multi query statement to execute
+ * @param mysqli $link mysqli object
+ *
+ * @return mysqli_result collection | boolean(false)
+ */
+ public function tryMultiQuery($multi_query = '', $link = null)
+ {
+ if (empty($link)) {
+ if (isset($GLOBALS['userlink'])) {
+ $link = $GLOBALS['userlink'];
+ } else {
+ return false;
+ }
+ }
+
+ return $this->_extension->realMultiQuery($link, $multi_query);
+ }
+
+ /**
+ * converts charset of a mysql message, usually coming from mysql_error(),
+ * into PMA charset, usually UTF-8
+ * uses language to charset mapping from mysql/share/errmsg.txt
+ * and charset names to ISO charset from information_schema.CHARACTER_SETS
+ *
+ * @param string $message the message
+ *
+ * @return string $message
+ */
+ public function convertMessage($message)
+ {
+ // latin always last!
+ $encodings = array(
+ 'japanese' => 'EUC-JP', //'ujis',
+ 'japanese-sjis' => 'Shift-JIS', //'sjis',
+ 'korean' => 'EUC-KR', //'euckr',
+ 'russian' => 'KOI8-R', //'koi8r',
+ 'ukrainian' => 'KOI8-U', //'koi8u',
+ 'greek' => 'ISO-8859-7', //'greek',
+ 'serbian' => 'CP1250', //'cp1250',
+ 'estonian' => 'ISO-8859-13', //'latin7',
+ 'slovak' => 'ISO-8859-2', //'latin2',
+ 'czech' => 'ISO-8859-2', //'latin2',
+ 'hungarian' => 'ISO-8859-2', //'latin2',
+ 'polish' => 'ISO-8859-2', //'latin2',
+ 'romanian' => 'ISO-8859-2', //'latin2',
+ 'spanish' => 'CP1252', //'latin1',
+ 'swedish' => 'CP1252', //'latin1',
+ 'italian' => 'CP1252', //'latin1',
+ 'norwegian-ny' => 'CP1252', //'latin1',
+ 'norwegian' => 'CP1252', //'latin1',
+ 'portuguese' => 'CP1252', //'latin1',
+ 'danish' => 'CP1252', //'latin1',
+ 'dutch' => 'CP1252', //'latin1',
+ 'english' => 'CP1252', //'latin1',
+ 'french' => 'CP1252', //'latin1',
+ 'german' => 'CP1252', //'latin1',
+ );
+
+ $server_language = $this->fetchValue(
+ 'SHOW VARIABLES LIKE \'language\';',
+ 0,
+ 1
+ );
+ if ($server_language) {
+ $found = array();
+ $match = preg_match(
+ '&(?:\\\|\\/)([^\\\\\/]*)(?:\\\|\\/)$&i',
+ $server_language,
+ $found
+ );
+ if ($match) {
+ $server_language = $found[1];
+ }
+ }
+
+ if (! empty($server_language) && isset($encodings[$server_language])) {
+ $encoding = $encodings[$server_language];
+ } else {
+ /* Fallback to CP1252 if we can not detect */
+ $encoding = 'CP1252';
+ }
+
+ return PMA_convertString($encoding, 'utf-8', $message);
+ }
+
+ /**
+ * returns array with table names for given db
+ *
+ * @param string $database name of database
+ * @param mixed $link mysql link resource|object
+ *
+ * @return array tables names
+ */
+ public function getTables($database, $link = null)
+ {
+ return $this->fetchResult(
+ 'SHOW TABLES FROM ' . PMA_Util::backquote($database) . ';',
+ null,
+ 0,
+ $link,
+ self::QUERY_STORE
+ );
+ }
+
+ /**
+ * returns array of all tables in given db or dbs
+ * this function expects unquoted names:
+ * RIGHT: my_database
+ * WRONG: `my_database`
+ * WRONG: my\_database
+ * if $tbl_is_group is true, $table is used as filter for table names
+ *
+ * <code>
+ * $GLOBALS['dbi']->getTablesFull('my_database');
+ * $GLOBALS['dbi']->getTablesFull('my_database', 'my_table'));
+ * $GLOBALS['dbi']->getTablesFull('my_database', 'my_tables_', true));
+ * </code>
+ *
+ * @param string $database database
+ * @param string|bool $table table or false
+ * @param boolean $tbl_is_group $table is a table group
+ * @param mixed $link mysql link
+ * @param integer $limit_offset zero-based offset for the count
+ * @param boolean|integer $limit_count number of tables to return
+ * @param string $sort_by table attribute to sort by
+ * @param string $sort_order direction to sort (ASC or DESC)
+ * @param string $tble_type whether table or view
+ *
+ * @todo move into PMA_Table
+ *
+ * @return array list of tables in given db(s)
+ */
+ public function getTablesFull($database, $table = false,
+ $tbl_is_group = false, $link = null, $limit_offset = 0,
+ $limit_count = false, $sort_by = 'Name', $sort_order = 'ASC',
+ $tble_type = null
+ ) {
+ if (true === $limit_count) {
+ $limit_count = $GLOBALS['cfg']['MaxTableList'];
+ }
+ // prepare and check parameters
+ if (! is_array($database)) {
+ $databases = array($database);
+ } else {
+ $databases = $database;
+ }
+
+ $tables = array();
+
+ // get table information from information_schema
+ if ($table) {
+ if (true === $tbl_is_group) {
+ $sql_where_table = 'AND t.`TABLE_NAME` LIKE \''
+ . PMA_Util::escapeMysqlWildcards(
+ PMA_Util::sqlAddSlashes($table)
+ )
+ . '%\'';
+ } else {
+ $sql_where_table = 'AND t.`TABLE_NAME` = \''
+ . PMA_Util::sqlAddSlashes($table) . '\'';
+ }
+ } else {
+ $sql_where_table = '';
+ }
+
+ if ($tble_type) {
+ if ($tble_type == 'view') {
+ if (PMA_DRIZZLE) {
+ $sql_where_table .= " AND t.`TABLE_TYPE` != 'BASE'";
+ } else {
+ $sql_where_table .= " AND t.`TABLE_TYPE` != 'BASE TABLE'";
+ }
+ } else if ($tble_type == 'table') {
+ if (PMA_DRIZZLE) {
+ $sql_where_table .= " AND t.`TABLE_TYPE` = 'BASE'";
+ } else {
+ $sql_where_table .= " AND t.`TABLE_TYPE` = 'BASE TABLE'";
+ }
+ }
+ }
+
+ // for PMA bc:
+ // `SCHEMA_FIELD_NAME` AS `SHOW_TABLE_STATUS_FIELD_NAME`
+ //
+ // on non-Windows servers,
+ // added BINARY in the WHERE clause to force a case sensitive
+ // comparison (if we are looking for the db Aa we don't want
+ // to find the db aa)
+ $this_databases = array_map('PMA_Util::sqlAddSlashes', $databases);
+
+ if (PMA_DRIZZLE) {
+ $engine_info = PMA_Util::cacheGet('drizzle_engines', true);
+ $stats_join = "LEFT JOIN (SELECT 0 NUM_ROWS) AS stat ON false";
+ if (isset($engine_info['InnoDB'])
+ && $engine_info['InnoDB']['module_library'] == 'innobase'
+ ) {
+ $stats_join = "LEFT JOIN data_dictionary.INNODB_SYS_TABLESTATS"
+ . " stat ON (t.ENGINE = 'InnoDB' AND stat.NAME"
+ . " = (t.TABLE_SCHEMA || '/') || t.TABLE_NAME)";
+ }
+
+ // data_dictionary.table_cache may not contain any data
+ // for some tables, it's just a table cache
+ // auto_increment == 0 is cast to NULL because currently
+ // (2011.03.13 GA)
+ // Drizzle doesn't provide correct value
+ $sql = "
+ SELECT t.*,
+ t.TABLE_SCHEMA AS `Db`,
+ t.TABLE_NAME AS `Name`,
+ t.TABLE_TYPE AS `TABLE_TYPE`,
+ t.ENGINE AS `Engine`,
+ t.ENGINE AS `Type`,
+ t.TABLE_VERSION AS `Version`,-- VERSION
+ t.ROW_FORMAT AS `Row_format`,
+ coalesce(tc.ROWS, stat.NUM_ROWS)
+ AS `Rows`,-- TABLE_ROWS,
+ coalesce(tc.ROWS, stat.NUM_ROWS)
+ AS `TABLE_ROWS`,
+ tc.AVG_ROW_LENGTH AS `Avg_row_length`, -- AVG_ROW_LENGTH
+ tc.TABLE_SIZE AS `Data_length`, -- DATA_LENGTH
+ NULL AS `Max_data_length`, -- MAX_DATA_LENGTH
+ NULL AS `Index_length`, -- INDEX_LENGTH
+ NULL AS `Data_free`, -- DATA_FREE
+ nullif(t.AUTO_INCREMENT, 0)
+ AS `Auto_increment`,
+ t.TABLE_CREATION_TIME AS `Create_time`, -- CREATE_TIME
+ t.TABLE_UPDATE_TIME AS `Update_time`, -- UPDATE_TIME
+ NULL AS `Check_time`, -- CHECK_TIME
+ t.TABLE_COLLATION AS `Collation`,
+ NULL AS `Checksum`, -- CHECKSUM
+ NULL AS `Create_options`, -- CREATE_OPTIONS
+ t.TABLE_COMMENT AS `Comment`
+ FROM data_dictionary.TABLES t
+ LEFT JOIN data_dictionary.TABLE_CACHE tc
+ ON tc.TABLE_SCHEMA = t.TABLE_SCHEMA AND tc.TABLE_NAME
+ = t.TABLE_NAME
+ $stats_join
+ WHERE t.TABLE_SCHEMA IN ('" . implode("', '", $this_databases) . "')
+ " . $sql_where_table;
+ } else {
+ $sql = '
+ SELECT *,
+ `TABLE_SCHEMA` AS `Db`,
+ `TABLE_NAME` AS `Name`,
+ `TABLE_TYPE` AS `TABLE_TYPE`,
+ `ENGINE` AS `Engine`,
+ `ENGINE` AS `Type`,
+ `VERSION` AS `Version`,
+ `ROW_FORMAT` AS `Row_format`,
+ `TABLE_ROWS` AS `Rows`,
+ `AVG_ROW_LENGTH` AS `Avg_row_length`,
+ `DATA_LENGTH` AS `Data_length`,
+ `MAX_DATA_LENGTH` AS `Max_data_length`,
+ `INDEX_LENGTH` AS `Index_length`,
+ `DATA_FREE` AS `Data_free`,
+ `AUTO_INCREMENT` AS `Auto_increment`,
+ `CREATE_TIME` AS `Create_time`,
+ `UPDATE_TIME` AS `Update_time`,
+ `CHECK_TIME` AS `Check_time`,
+ `TABLE_COLLATION` AS `Collation`,
+ `CHECKSUM` AS `Checksum`,
+ `CREATE_OPTIONS` AS `Create_options`,
+ `TABLE_COMMENT` AS `Comment`
+ FROM `information_schema`.`TABLES` t
+ WHERE ' . (PMA_IS_WINDOWS ? '' : 'BINARY') . ' `TABLE_SCHEMA`
+ IN (\'' . implode("', '", $this_databases) . '\')
+ ' . $sql_where_table;
+ }
+
+ // Sort the tables
+ $sql .= " ORDER BY $sort_by $sort_order";
+
+ if ($limit_count) {
+ $sql .= ' LIMIT ' . $limit_count . ' OFFSET ' . $limit_offset;
+ }
+
+ $tables = $this->fetchResult(
+ $sql, array('TABLE_SCHEMA', 'TABLE_NAME'), null, $link
+ );
+ unset($sql_where_table, $sql);
+
+ if (PMA_DRIZZLE) {
+ // correct I_S and D_D names returned by D_D.TABLES -
+ // Drizzle generally uses lower case for them,
+ // but TABLES returns uppercase
+ foreach ((array)$database as $db) {
+ $db_upper = strtoupper($db);
+ if (!isset($tables[$db]) && isset($tables[$db_upper])) {
+ $tables[$db] = $tables[$db_upper];
+ unset($tables[$db_upper]);
+ }
+ }
+ }
+
+ if ($sort_by == 'Name' && $GLOBALS['cfg']['NaturalOrder']) {
+ // here, the array's first key is by schema name
+ foreach ($tables as $one_database_name => $one_database_tables) {
+ uksort($one_database_tables, 'strnatcasecmp');
+
+ if ($sort_order == 'DESC') {
+ $one_database_tables = array_reverse($one_database_tables);
+ }
+ $tables[$one_database_name] = $one_database_tables;
+ }
+ } else if ($sort_by == 'Data_length') { // Size = Data_length + Index_length
+ foreach ($tables as $one_database_name => $one_database_tables) {
+ uasort(
+ $one_database_tables,
+ function($a, $b) {
+ $aLength = $a['Data_length'] + $a['Index_length'];
+ $bLength = $b['Data_length'] + $b['Index_length'];
+ return ($aLength == $bLength)
+ ? 0
+ : ($aLength < $bLength) ? -1 : 1;
+ }
+ );
+
+ if ($sort_order == 'DESC') {
+ $one_database_tables = array_reverse($one_database_tables);
+ }
+ $tables[$one_database_name] = $one_database_tables;
+ }
+ }
+ // end (get information from table schema)
+
+ // If permissions are wrong on even one database directory,
+ // information_schema does not return any table info for any database
+ // this is why we fall back to SHOW TABLE STATUS even for MySQL >= 50002
+ if (empty($tables) && !PMA_DRIZZLE) {
+ foreach ($databases as $each_database) {
+ if ($table || (true === $tbl_is_group) || $tble_type) {
+ $sql = 'SHOW TABLE STATUS FROM '
+ . PMA_Util::backquote($each_database)
+ .' WHERE';
+ $needAnd = false;
+ if ($table || (true === $tbl_is_group)) {
+ $sql .= " `Name` LIKE '"
+ . PMA_Util::escapeMysqlWildcards(
+ PMA_Util::sqlAddSlashes($table, true)
+ )
+ . "%'";
+ $needAnd = true;
+ }
+ if ($tble_type) {
+ if ($needAnd) {
+ $sql .= " AND";
+ }
+ if ($tble_type == 'view') {
+ $sql .= " `Comment` = 'VIEW'";
+ } else if ($tble_type == 'table') {
+ $sql .= " `Comment` != 'VIEW'";
+ }
+ }
+ } else {
+ $sql = 'SHOW TABLE STATUS FROM '
+ . PMA_Util::backquote($each_database);
+ }
+
+ $useStatusCache = false;
+
+ if (extension_loaded('apc')
+ && isset($GLOBALS['cfg']['Server']['StatusCacheDatabases'])
+ && ! empty($GLOBALS['cfg']['Server']['StatusCacheLifetime'])
+ ) {
+ $statusCacheDatabases
+ = (array) $GLOBALS['cfg']['Server']['StatusCacheDatabases'];
+ if (in_array($each_database, $statusCacheDatabases)) {
+ $useStatusCache = true;
+ }
+ }
+
+ $each_tables = null;
+
+ if ($useStatusCache) {
+ $cacheKey = 'phpMyAdmin_tableStatus_'
+ . sha1($GLOBALS['cfg']['Server']['host'] . '_' . $sql);
+
+ $each_tables = apc_fetch($cacheKey);
+ }
+
+ if (! $each_tables) {
+ $each_tables = $this->fetchResult($sql, 'Name', null, $link);
+ }
+
+ if ($useStatusCache) {
+ apc_store(
+ $cacheKey, $each_tables,
+ $GLOBALS['cfg']['Server']['StatusCacheLifetime']
+ );
+ }
+
+ // Sort naturally if the config allows it and we're sorting
+ // the Name column.
+ if ($sort_by == 'Name' && $GLOBALS['cfg']['NaturalOrder']) {
+ uksort($each_tables, 'strnatcasecmp');
+
+ if ($sort_order == 'DESC') {
+ $each_tables = array_reverse($each_tables);
+ }
+ } else {
+ // Prepare to sort by creating array of the selected sort
+ // value to pass to array_multisort
+
+ // Size = Data_length + Index_length
+ if ($sort_by == 'Data_length') {
+ foreach ($each_tables as $table_name => $table_data) {
+ ${$sort_by}[$table_name] = strtolower(
+ $table_data['Data_length'] + $table_data['Index_length']
+ );
+ }
+ } else {
+ foreach ($each_tables as $table_name => $table_data) {
+ ${$sort_by}[$table_name]
+ = strtolower($table_data[$sort_by]);
+ }
+ }
+
+ if ($sort_order == 'DESC') {
+ array_multisort($$sort_by, SORT_DESC, $each_tables);
+ } else {
+ array_multisort($$sort_by, SORT_ASC, $each_tables);
+ }
+
+ // cleanup the temporary sort array
+ unset($$sort_by);
+ }
+
+ if ($limit_count) {
+ $each_tables = array_slice(
+ $each_tables, $limit_offset, $limit_count
+ );
+ }
+
+ $tables[$each_database] = $this->copyTableProperties(
+ $each_tables, $each_database
+ );
+ }
+ }
+
+ // cache table data
+ // so PMA_Table does not require to issue SHOW TABLE STATUS again
+ // Note: I don't see why we would need array_merge_recursive() here,
+ // as it creates double entries for the same table (for example a double
+ // entry for Comment when changing the storage engine in Operations)
+ // Note 2: Instead of array_merge(), simply use the + operator because
+ // array_merge() renumbers numeric keys starting with 0, therefore
+ // we would lose a db name thats consists only of numbers
+ foreach ($tables as $one_database => $its_tables) {
+ if (isset(PMA_Table::$cache[$one_database])) {
+ // the + operator does not do the intended effect
+ // when the cache for one table already exists
+ if ($table
+ && isset(PMA_Table::$cache[$one_database][$table])
+ ) {
+ unset(PMA_Table::$cache[$one_database][$table]);
+ }
+ PMA_Table::$cache[$one_database]
+ = PMA_Table::$cache[$one_database] + $tables[$one_database];
+ } else {
+ PMA_Table::$cache[$one_database] = $tables[$one_database];
+ }
+ }
+ unset($one_database, $its_tables);
+
+ if (! is_array($database)) {
+ if (isset($tables[$database])) {
+ return $tables[$database];
+ } elseif (isset($tables[strtolower($database)])) {
+ // on windows with lower_case_table_names = 1
+ // MySQL returns
+ // with SHOW DATABASES or information_schema.SCHEMATA: `Test`
+ // but information_schema.TABLES gives `test`
+ // bug #2036
+ // https://sourceforge.net/p/phpmyadmin/bugs/2036/
+ return $tables[strtolower($database)];
+ } else {
+ // one database but inexact letter case match
+ // as Drizzle is always case insensitive,
+ // we can safely return the only result
+ if (PMA_DRIZZLE && count($tables) == 1) {
+ $keys = array_keys($tables);
+ if (strlen(array_pop($keys)) == strlen($database)) {
+ return array_pop($tables);
+ }
+ }
+ return $tables;
+ }
+ } else {
+ return $tables;
+ }
+ }
+
+ /**
+ * Copies the table properties to the set of property names used by PMA.
+ *
+ * @param array $tables array of table properties
+ * @param string $database database name
+ *
+ * @return array array with added properties
+ */
+ public function copyTableProperties($tables, $database)
+ {
+ foreach ($tables as $table_name => $each_table) {
+ if (! isset($tables[$table_name]['Type'])
+ && isset($tables[$table_name]['Engine'])
+ ) {
+ // pma BC, same parts of PMA still uses 'Type'
+ $tables[$table_name]['Type']
+ =& $tables[$table_name]['Engine'];
+ } elseif (! isset($tables[$table_name]['Engine'])
+ && isset($tables[$table_name]['Type'])
+ ) {
+ // old MySQL reports Type, newer MySQL reports Engine
+ $tables[$table_name]['Engine']
+ =& $tables[$table_name]['Type'];
+ }
+
+ // MySQL forward compatibility
+ // so pma could use this array as if every server
+ // is of version >5.0
+ // todo : remove and check usage in the rest of the code,
+ // MySQL 5.0 is required by current PMA version
+ $tables[$table_name]['TABLE_SCHEMA']
+ = $database;
+ $tables[$table_name]['TABLE_NAME']
+ =& $tables[$table_name]['Name'];
+ $tables[$table_name]['ENGINE']
+ =& $tables[$table_name]['Engine'];
+ $tables[$table_name]['VERSION']
+ =& $tables[$table_name]['Version'];
+ $tables[$table_name]['ROW_FORMAT']
+ =& $tables[$table_name]['Row_format'];
+ $tables[$table_name]['TABLE_ROWS']
+ =& $tables[$table_name]['Rows'];
+ $tables[$table_name]['AVG_ROW_LENGTH']
+ =& $tables[$table_name]['Avg_row_length'];
+ $tables[$table_name]['DATA_LENGTH']
+ =& $tables[$table_name]['Data_length'];
+ $tables[$table_name]['MAX_DATA_LENGTH']
+ =& $tables[$table_name]['Max_data_length'];
+ $tables[$table_name]['INDEX_LENGTH']
+ =& $tables[$table_name]['Index_length'];
+ $tables[$table_name]['DATA_FREE']
+ =& $tables[$table_name]['Data_free'];
+ $tables[$table_name]['AUTO_INCREMENT']
+ =& $tables[$table_name]['Auto_increment'];
+ $tables[$table_name]['CREATE_TIME']
+ =& $tables[$table_name]['Create_time'];
+ $tables[$table_name]['UPDATE_TIME']
+ =& $tables[$table_name]['Update_time'];
+ $tables[$table_name]['CHECK_TIME']
+ =& $tables[$table_name]['Check_time'];
+ $tables[$table_name]['TABLE_COLLATION']
+ =& $tables[$table_name]['Collation'];
+ $tables[$table_name]['CHECKSUM']
+ =& $tables[$table_name]['Checksum'];
+ $tables[$table_name]['CREATE_OPTIONS']
+ =& $tables[$table_name]['Create_options'];
+ $tables[$table_name]['TABLE_COMMENT']
+ =& $tables[$table_name]['Comment'];
+
+ if (strtoupper($tables[$table_name]['Comment']) === 'VIEW'
+ && $tables[$table_name]['Engine'] == null
+ ) {
+ $tables[$table_name]['TABLE_TYPE'] = 'VIEW';
+ } else {
+ /**
+ * @todo difference between 'TEMPORARY' and 'BASE TABLE'
+ * but how to detect?
+ */
+ $tables[$table_name]['TABLE_TYPE'] = 'BASE TABLE';
+ }
+ }
+ return $tables;
+ }
+
+ /**
+ * Get VIEWs in a particular database
+ *
+ * @param string $db Database name to look in
+ *
+ * @return array $views Set of VIEWs inside the database
+ */
+ public function getVirtualTables($db)
+ {
+
+ $tables_full = $this->getTablesFull($db);
+ $views = array();
+
+ foreach ($tables_full as $table=>$tmp) {
+
+ if (PMA_Table::isView($db, $table)) {
+ $views[] = $table;
+ }
+
+ }
+
+ return $views;
+
+ }
+
+
+ /**
+ * returns array with databases containing extended infos about them
+ *
+ * @param string $database database
+ * @param boolean $force_stats retrieve stats also for MySQL < 5
+ * @param resource $link mysql link
+ * @param string $sort_by column to order by
+ * @param string $sort_order ASC or DESC
+ * @param integer $limit_offset starting offset for LIMIT
+ * @param bool|int $limit_count row count for LIMIT or true
+ * for $GLOBALS['cfg']['MaxDbList']
+ *
+ * @todo move into PMA_List_Database?
+ *
+ * @return array $databases
+ */
+ public function getDatabasesFull($database = null, $force_stats = false,
+ $link = null, $sort_by = 'SCHEMA_NAME', $sort_order = 'ASC',
+ $limit_offset = 0, $limit_count = false
+ ) {
+ $sort_order = strtoupper($sort_order);
+
+ if (true === $limit_count) {
+ $limit_count = $GLOBALS['cfg']['MaxDbList'];
+ }
+
+ // initialize to avoid errors when there are no databases
+ $databases = array();
+
+ $apply_limit_and_order_manual = true;
+
+ /**
+ * if $GLOBALS['cfg']['NaturalOrder'] is enabled, we cannot use LIMIT
+ * cause MySQL does not support natural ordering,
+ * we have to do it afterward
+ */
+ $limit = '';
+ if (! $GLOBALS['cfg']['NaturalOrder']) {
+ if ($limit_count) {
+ $limit = ' LIMIT ' . $limit_count . ' OFFSET ' . $limit_offset;
+ }
+
+ $apply_limit_and_order_manual = false;
+ }
+
+ // get table information from information_schema
+ if ($database) {
+ $sql_where_schema = 'WHERE `SCHEMA_NAME` LIKE \''
+ . PMA_Util::sqlAddSlashes($database) . '\'';
+ } else {
+ $sql_where_schema = '';
+ }
+
+ if (PMA_DRIZZLE) {
+ // data_dictionary.table_cache may not contain any data for some
+ // tables, it's just a table cache
+ $sql = 'SELECT
+ s.SCHEMA_NAME,
+ s.DEFAULT_COLLATION_NAME';
+ if ($force_stats) {
+ // no TABLE_CACHE data, stable results are better than
+ // constantly changing
+ $sql .= ',
+ COUNT(t.TABLE_SCHEMA) AS SCHEMA_TABLES,
+ SUM(stat.NUM_ROWS) AS SCHEMA_TABLE_ROWS';
+ }
+ $sql .= '
+ FROM data_dictionary.SCHEMAS s';
+ if ($force_stats) {
+ $engine_info = PMA_Util::cacheGet('drizzle_engines', true);
+ $stats_join = "LEFT JOIN (SELECT 0 NUM_ROWS) AS stat ON false";
+ if (isset($engine_info['InnoDB'])
+ && $engine_info['InnoDB']['module_library'] == 'innobase'
+ ) {
+ $stats_join
+ = "LEFT JOIN data_dictionary.INNODB_SYS_TABLESTATS stat"
+ . " ON (t.ENGINE = 'InnoDB' AND stat.NAME"
+ . " = (t.TABLE_SCHEMA || '/') || t.TABLE_NAME)";
+ }
+
+ $sql .= "
+ LEFT JOIN data_dictionary.TABLES t
+ ON t.TABLE_SCHEMA = s.SCHEMA_NAME
+ $stats_join";
+ }
+ $sql .= $sql_where_schema . '
+ GROUP BY s.SCHEMA_NAME
+ ORDER BY ' . PMA_Util::backquote($sort_by) . ' ' . $sort_order
+ . $limit;
+ } else {
+ $sql = 'SELECT
+ s.SCHEMA_NAME,
+ s.DEFAULT_COLLATION_NAME';
+ if ($force_stats) {
+ $sql .= ',
+ COUNT(t.TABLE_SCHEMA) AS SCHEMA_TABLES,
+ SUM(t.TABLE_ROWS) AS SCHEMA_TABLE_ROWS,
+ SUM(t.DATA_LENGTH) AS SCHEMA_DATA_LENGTH,
+ SUM(t.MAX_DATA_LENGTH) AS SCHEMA_MAX_DATA_LENGTH,
+ SUM(t.INDEX_LENGTH) AS SCHEMA_INDEX_LENGTH,
+ SUM(t.DATA_LENGTH + t.INDEX_LENGTH)
+ AS SCHEMA_LENGTH,
+ SUM(t.DATA_FREE) AS SCHEMA_DATA_FREE';
+ }
+ $sql .= '
+ FROM `information_schema`.SCHEMATA s';
+ if ($force_stats) {
+ $sql .= '
+ LEFT JOIN `information_schema`.TABLES t
+ ON BINARY t.TABLE_SCHEMA = BINARY s.SCHEMA_NAME';
+ }
+ $sql .= $sql_where_schema . '
+ GROUP BY BINARY s.SCHEMA_NAME
+ ORDER BY BINARY ' . PMA_Util::backquote($sort_by)
+ . ' ' . $sort_order
+ . $limit;
+ }
+
+ $databases = $this->fetchResult($sql, 'SCHEMA_NAME', null, $link);
+
+ $mysql_error = $this->getError($link);
+ if (! count($databases) && $GLOBALS['errno']) {
+ PMA_Util::mysqlDie($mysql_error, $sql);
+ }
+
+ // display only databases also in official database list
+ // f.e. to apply hide_db and only_db
+ $drops = array_diff(
+ array_keys($databases), (array) $GLOBALS['pma']->databases
+ );
+ if (count($drops)) {
+ foreach ($drops as $drop) {
+ unset($databases[$drop]);
+ }
+ unset($drop);
+ }
+ unset($sql_where_schema, $sql, $drops);
+
+ /**
+ * apply limit and order manually now
+ * (caused by older MySQL < 5 or $GLOBALS['cfg']['NaturalOrder'])
+ */
+ if ($apply_limit_and_order_manual) {
+ $GLOBALS['callback_sort_order'] = $sort_order;
+ $GLOBALS['callback_sort_by'] = $sort_by;
+ usort(
+ $databases,
+ array('PMA_DatabaseInterface', '_usortComparisonCallback')
+ );
+ unset($GLOBALS['callback_sort_order'], $GLOBALS['callback_sort_by']);
+
+ /**
+ * now apply limit
+ */
+ if ($limit_count) {
+ $databases = array_slice($databases, $limit_offset, $limit_count);
+ }
+ }
+
+ return $databases;
+ }
+
+ /**
+ * usort comparison callback
+ *
+ * @param string $a first argument to sort
+ * @param string $b second argument to sort
+ *
+ * @return integer a value representing whether $a should be before $b in the
+ * sorted array or not
+ *
+ * @access private
+ */
+ private static function _usortComparisonCallback($a, $b)
+ {
+ if ($GLOBALS['cfg']['NaturalOrder']) {
+ $sorter = 'strnatcasecmp';
+ } else {
+ $sorter = 'strcasecmp';
+ }
+ /* No sorting when key is not present */
+ if (! isset($a[$GLOBALS['callback_sort_by']])
+ || ! isset($b[$GLOBALS['callback_sort_by']])
+ ) {
+ return 0;
+ }
+ // produces f.e.:
+ // return -1 * strnatcasecmp($a["SCHEMA_TABLES"], $b["SCHEMA_TABLES"])
+ return ($GLOBALS['callback_sort_order'] == 'ASC' ? 1 : -1) * $sorter(
+ $a[$GLOBALS['callback_sort_by']], $b[$GLOBALS['callback_sort_by']]
+ );
+ } // end of the '_usortComparisonCallback()' method
+
+ /**
+ * returns detailed array with all columns for given table in database,
+ * or all tables/databases
+ *
+ * @param string $database name of database
+ * @param string $table name of table to retrieve columns from
+ * @param string $column name of specific column
+ * @param mixed $link mysql link resource
+ *
+ * @return array
+ */
+ public function getColumnsFull($database = null, $table = null,
+ $column = null, $link = null
+ ) {
+ $columns = array();
+
+ $sql_wheres = array();
+ $array_keys = array();
+
+ // get columns information from information_schema
+ if (null !== $database) {
+ $sql_wheres[] = '`TABLE_SCHEMA` = \''
+ . PMA_Util::sqlAddSlashes($database) . '\' ';
+ } else {
+ $array_keys[] = 'TABLE_SCHEMA';
+ }
+ if (null !== $table) {
+ $sql_wheres[] = '`TABLE_NAME` = \''
+ . PMA_Util::sqlAddSlashes($table) . '\' ';
+ } else {
+ $array_keys[] = 'TABLE_NAME';
+ }
+ if (null !== $column) {
+ $sql_wheres[] = '`COLUMN_NAME` = \''
+ . PMA_Util::sqlAddSlashes($column) . '\' ';
+ } else {
+ $array_keys[] = 'COLUMN_NAME';
+ }
+
+ // for PMA bc:
+ // `[SCHEMA_FIELD_NAME]` AS `[SHOW_FULL_COLUMNS_FIELD_NAME]`
+ if (PMA_DRIZZLE) {
+ $sql = "SELECT TABLE_SCHEMA, TABLE_NAME, COLUMN_NAME,
+ column_name AS `Field`,
+ (CASE
+ WHEN character_maximum_length > 0
+ THEN concat(lower(data_type), '(', character_maximum_length, ')')
+ WHEN numeric_precision > 0 OR numeric_scale > 0
+ THEN concat(lower(data_type), '(', numeric_precision,
+ ',', numeric_scale, ')')
+ WHEN enum_values IS NOT NULL
+ THEN concat(lower(data_type), '(', enum_values, ')')
+ ELSE lower(data_type) END)
+ AS `Type`,
+ collation_name AS `Collation`,
+ (CASE is_nullable
+ WHEN 1 THEN 'YES'
+ ELSE 'NO' END) AS `Null`,
+ (CASE
+ WHEN is_used_in_primary THEN 'PRI'
+ ELSE '' END) AS `Key`,
+ column_default AS `Default`,
+ (CASE
+ WHEN is_auto_increment THEN 'auto_increment'
+ WHEN column_default_update
+ THEN 'on update ' || column_default_update
+ ELSE '' END) AS `Extra`,
+ NULL AS `Privileges`,
+ column_comment AS `Comment`
+ FROM data_dictionary.columns";
+ } else {
+ $sql = '
+ SELECT *,
+ `COLUMN_NAME` AS `Field`,
+ `COLUMN_TYPE` AS `Type`,
+ `COLLATION_NAME` AS `Collation`,
+ `IS_NULLABLE` AS `Null`,
+ `COLUMN_KEY` AS `Key`,
+ `COLUMN_DEFAULT` AS `Default`,
+ `EXTRA` AS `Extra`,
+ `PRIVILEGES` AS `Privileges`,
+ `COLUMN_COMMENT` AS `Comment`
+ FROM `information_schema`.`COLUMNS`';
+ }
+ if (count($sql_wheres)) {
+ $sql .= "\n" . ' WHERE ' . implode(' AND ', $sql_wheres);
+ }
+
+ $columns = $this->fetchResult($sql, $array_keys, null, $link);
+ unset($sql_wheres, $sql);
+
+ $ordinal_position = 1;
+ foreach ($columns as $column_name => $each_column) {
+
+ // MySQL forward compatibility
+ // so pma could use this array as if every server is of version >5.0
+ // todo : remove and check the rest of the code for usage,
+ // MySQL 5.0 or higher is required for current PMA version
+ $columns[$column_name]['COLUMN_NAME']
+ =& $columns[$column_name]['Field'];
+ $columns[$column_name]['COLUMN_TYPE']
+ =& $columns[$column_name]['Type'];
+ $columns[$column_name]['COLLATION_NAME']
+ =& $columns[$column_name]['Collation'];
+ $columns[$column_name]['IS_NULLABLE']
+ =& $columns[$column_name]['Null'];
+ $columns[$column_name]['COLUMN_KEY']
+ =& $columns[$column_name]['Key'];
+ $columns[$column_name]['COLUMN_DEFAULT']
+ =& $columns[$column_name]['Default'];
+ $columns[$column_name]['EXTRA']
+ =& $columns[$column_name]['Extra'];
+ $columns[$column_name]['PRIVILEGES']
+ =& $columns[$column_name]['Privileges'];
+ $columns[$column_name]['COLUMN_COMMENT']
+ =& $columns[$column_name]['Comment'];
+
+ $columns[$column_name]['TABLE_CATALOG'] = null;
+ $columns[$column_name]['TABLE_SCHEMA'] = $database;
+ $columns[$column_name]['TABLE_NAME'] = $table;
+ $columns[$column_name]['ORDINAL_POSITION'] = $ordinal_position;
+ $columns[$column_name]['DATA_TYPE']
+ = substr(
+ $columns[$column_name]['COLUMN_TYPE'],
+ 0,
+ strpos($columns[$column_name]['COLUMN_TYPE'], '(')
+ );
+ /**
+ * @todo guess CHARACTER_MAXIMUM_LENGTH from COLUMN_TYPE
+ */
+ $columns[$column_name]['CHARACTER_MAXIMUM_LENGTH'] = null;
+ /**
+ * @todo guess CHARACTER_OCTET_LENGTH from CHARACTER_MAXIMUM_LENGTH
+ */
+ $columns[$column_name]['CHARACTER_OCTET_LENGTH'] = null;
+ $columns[$column_name]['NUMERIC_PRECISION'] = null;
+ $columns[$column_name]['NUMERIC_SCALE'] = null;
+ $columns[$column_name]['CHARACTER_SET_NAME']
+ = substr(
+ $columns[$column_name]['COLLATION_NAME'],
+ 0,
+ strpos($columns[$column_name]['COLLATION_NAME'], '_')
+ );
+
+ $ordinal_position++;
+ }
+
+ if (null !== $column) {
+ reset($columns);
+ $columns = current($columns);
+ }
+
+ return $columns;
+ }
+
+ /**
+ * Returns SQL query for fetching columns for a table
+ *
+ * The 'Key' column is not calculated properly, use $GLOBALS['dbi']->getColumns()
+ * to get correct values.
+ *
+ * @param string $database name of database
+ * @param string $table name of table to retrieve columns from
+ * @param string $column name of column, null to show all columns
+ * @param boolean $full whether to return full info or only column names
+ *
+ * @see getColumns()
+ *
+ * @return string
+ */
+ public function getColumnsSql($database, $table, $column = null, $full = false)
+ {
+ if (PMA_DRIZZLE) {
+ // `Key` column:
+ // * used in primary key => PRI
+ // * unique one-column => UNI
+ // * indexed, one-column or first in multi-column => MUL
+ // Promotion of UNI to PRI in case no promary index exists
+ // is done after query is executed
+ $sql = "SELECT
+ column_name AS `Field`,
+ (CASE
+ WHEN character_maximum_length > 0
+ THEN concat(lower(data_type), '(', character_maximum_length, ')')
+ WHEN numeric_precision > 0 OR numeric_scale > 0
+ THEN concat(lower(data_type), '(', numeric_precision,
+ ',', numeric_scale, ')')
+ WHEN enum_values IS NOT NULL
+ THEN concat(lower(data_type), '(', enum_values, ')')
+ ELSE lower(data_type) END)
+ AS `Type`,
+ " . ($full ? "
+ collation_name AS `Collation`," : '') . "
+ (CASE is_nullable
+ WHEN 1 THEN 'YES'
+ ELSE 'NO' END) AS `Null`,
+ (CASE
+ WHEN is_used_in_primary THEN 'PRI'
+ WHEN is_unique AND NOT is_multi THEN 'UNI'
+ WHEN is_indexed
+ AND (NOT is_multi OR is_first_in_multi) THEN 'MUL'
+ ELSE '' END) AS `Key`,
+ column_default AS `Default`,
+ (CASE
+ WHEN is_auto_increment THEN 'auto_increment'
+ WHEN column_default_update <> ''
+ THEN 'on update ' || column_default_update
+ ELSE '' END) AS `Extra`
+ " . ($full ? " ,
+ NULL AS `Privileges`,
+ column_comment AS `Comment`" : '') . "
+ FROM data_dictionary.columns
+ WHERE table_schema = '" . PMA_Util::sqlAddSlashes($database) . "'
+ AND table_name = '" . PMA_Util::sqlAddSlashes($table) . "'
+ " . (($column != null) ? "
+ AND column_name = '" . PMA_Util::sqlAddSlashes($column) . "'" : '');
+ // ORDER BY ordinal_position
+ } else {
+ $sql = 'SHOW ' . ($full ? 'FULL' : '') . ' COLUMNS FROM '
+ . PMA_Util::backquote($database) . '.' . PMA_Util::backquote($table)
+ . (($column != null) ? "LIKE '"
+ . PMA_Util::sqlAddSlashes($column, true) . "'" : '');
+ }
+ return $sql;
+ }
+
+ /**
+ * Returns descriptions of columns in given table (all or given by $column)
+ *
+ * @param string $database name of database
+ * @param string $table name of table to retrieve columns from
+ * @param string $column name of column, null to show all columns
+ * @param boolean $full whether to return full info or only column names
+ * @param mixed $link mysql link resource
+ *
+ * @return false|array array indexed by column names or,
+ * if $column is given, flat array description
+ */
+ public function getColumns($database, $table, $column = null, $full = false,
+ $link = null
+ ) {
+ $sql = $this->getColumnsSql($database, $table, $column, $full);
+ $fields = $this->fetchResult($sql, 'Field', null, $link);
+ if (! is_array($fields) || count($fields) == 0) {
+ return null;
+ }
+ if (PMA_DRIZZLE) {
+ // fix Key column, it's much simpler in PHP than in SQL
+ $has_pk = false;
+ $has_pk_candidates = false;
+ foreach ($fields as $f) {
+ if ($f['Key'] == 'PRI') {
+ $has_pk = true;
+ break;
+ } else if ($f['Null'] == 'NO'
+ && ($f['Key'] == 'MUL'
+ || $f['Key'] == 'UNI')
+ ) {
+ $has_pk_candidates = true;
+ }
+ }
+ if (! $has_pk && $has_pk_candidates) {
+ // check whether we can promote some unique index to PRI
+ $sql = "
+ SELECT i.index_name, p.column_name
+ FROM data_dictionary.indexes i
+ JOIN data_dictionary.index_parts p
+ USING (table_schema, table_name)
+ WHERE i.table_schema = '" . PMA_Util::sqlAddSlashes($database) . "'
+ AND i.table_name = '" . PMA_Util::sqlAddSlashes($table) . "'
+ AND i.is_unique
+ AND NOT i.is_nullable";
+ $fs = $this->fetchResult($sql, 'index_name', null, $link);
+ $fs = $fs ? array_shift($fs) : array();
+ foreach ($fs as $f) {
+ $fields[$f]['Key'] = 'PRI';
+ }
+ }
+ }
+
+ return ($column != null) ? array_shift($fields) : $fields;
+ }
+
+ /**
+ * Returns all column names in given table
+ *
+ * @param string $database name of database
+ * @param string $table name of table to retrieve columns from
+ * @param mixed $link mysql link resource
+ *
+ * @return null|array
+ */
+ public function getColumnNames($database, $table, $link = null)
+ {
+ $sql = $this->getColumnsSql($database, $table);
+ // We only need the 'Field' column which contains the table's column names
+ $fields = array_keys($this->fetchResult($sql, 'Field', null, $link));
+
+ if ( ! is_array($fields) || count($fields) == 0 ) {
+ return null;
+ }
+ return $fields;
+ }
+
+ /**
+ * Returns SQL for fetching information on table indexes (SHOW INDEXES)
+ *
+ * @param string $database name of database
+ * @param string $table name of the table whose indexes are to be retreived
+ * @param string $where additional conditions for WHERE
+ *
+ * @return array $indexes
+ */
+ public function getTableIndexesSql($database, $table, $where = null)
+ {
+ if (PMA_DRIZZLE) {
+ $sql = "SELECT
+ ip.table_name AS `Table`,
+ (NOT ip.is_unique) AS Non_unique,
+ ip.index_name AS Key_name,
+ ip.sequence_in_index+1 AS Seq_in_index,
+ ip.column_name AS Column_name,
+ (CASE
+ WHEN i.index_type = 'BTREE' THEN 'A'
+ ELSE NULL END) AS Collation,
+ NULL AS Cardinality,
+ compare_length AS Sub_part,
+ NULL AS Packed,
+ ip.is_nullable AS `Null`,
+ i.index_type AS Index_type,
+ NULL AS Comment,
+ i.index_comment AS Index_comment
+ FROM data_dictionary.index_parts ip
+ LEFT JOIN data_dictionary.indexes i
+ USING (table_schema, table_name, index_name)
+ WHERE table_schema = '" . PMA_Util::sqlAddSlashes($database) . "'
+ AND table_name = '" . PMA_Util::sqlAddSlashes($table) . "'
+ ";
+ if ($where) {
+ $sql = "SELECT * FROM (" . $sql . ") A WHERE (" . $where . ")";
+ }
+ } else {
+ $sql = 'SHOW INDEXES FROM ' . PMA_Util::backquote($database) . '.'
+ . PMA_Util::backquote($table);
+ if ($where) {
+ $sql .= ' WHERE (' . $where . ')';
+ }
+ }
+ return $sql;
+ }
+
+ /**
+ * Returns indexes of a table
+ *
+ * @param string $database name of database
+ * @param string $table name of the table whose indexes are to be retrieved
+ * @param mixed $link mysql link resource
+ *
+ * @return array $indexes
+ */
+ public function getTableIndexes($database, $table, $link = null)
+ {
+ $sql = $this->getTableIndexesSql($database, $table);
+ $indexes = $this->fetchResult($sql, null, null, $link);
+
+ if (! is_array($indexes) || count($indexes) < 1) {
+ return array();
+ }
+ return $indexes;
+ }
+
+ /**
+ * returns value of given mysql server variable
+ *
+ * @param string $var mysql server variable name
+ * @param int $type PMA_DatabaseInterface::GETVAR_SESSION |
+ * PMA_DatabaseInterface::GETVAR_GLOBAL
+ * @param mixed $link mysql link resource|object
+ *
+ * @return mixed value for mysql server variable
+ */
+ public function getVariable(
+ $var, $type = self::GETVAR_SESSION, $link = null
+ ) {
+ if ($link === null) {
+ if (isset($GLOBALS['userlink'])) {
+ $link = $GLOBALS['userlink'];
+ } else {
+ return false;
+ }
+ }
+
+ switch ($type) {
+ case self::GETVAR_SESSION:
+ $modifier = ' SESSION';
+ break;
+ case self::GETVAR_GLOBAL:
+ $modifier = ' GLOBAL';
+ break;
+ default:
+ $modifier = '';
+ }
+ return $this->fetchValue(
+ 'SHOW' . $modifier . ' VARIABLES LIKE \'' . $var . '\';', 0, 1, $link
+ );
+ }
+
+ /**
+ * Function called just after a connection to the MySQL database server has
+ * been established. It sets the connection collation, and determines the
+ * version of MySQL which is running.
+ *
+ * @param mixed $link mysql link resource|object
+ * @param boolean $is_controluser whether link is for control user
+ *
+ * @return void
+ */
+ public function postConnect($link, $is_controluser = false)
+ {
+ if ($is_controluser) {
+ /*
+ * FIXME: Not sure if this is right approach, but we can not
+ * define constants multiple time.
+ */
+ return;
+ }
+ if (! defined('PMA_MYSQL_INT_VERSION')) {
+ if (PMA_Util::cacheExists('PMA_MYSQL_INT_VERSION', true)) {
+ define(
+ 'PMA_MYSQL_INT_VERSION',
+ PMA_Util::cacheGet('PMA_MYSQL_INT_VERSION', true)
+ );
+ define(
+ 'PMA_MYSQL_MAJOR_VERSION',
+ PMA_Util::cacheGet('PMA_MYSQL_MAJOR_VERSION', true)
+ );
+ define(
+ 'PMA_MYSQL_STR_VERSION',
+ PMA_Util::cacheGet('PMA_MYSQL_STR_VERSION', true)
+ );
+ define(
+ 'PMA_MYSQL_VERSION_COMMENT',
+ PMA_Util::cacheGet('PMA_MYSQL_VERSION_COMMENT', true)
+ );
+ define(
+ 'PMA_DRIZZLE',
+ PMA_Util::cacheGet('PMA_DRIZZLE', true)
+ );
+ } else {
+ $version = $this->fetchSingleRow(
+ 'SELECT @@version, @@version_comment',
+ 'ASSOC',
+ $link
+ );
+
+ if ($version) {
+ $match = explode('.', $version['@@version']);
+ define('PMA_MYSQL_MAJOR_VERSION', (int)$match[0]);
+ define(
+ 'PMA_MYSQL_INT_VERSION',
+ (int) sprintf(
+ '%d%02d%02d', $match[0], $match[1], intval($match[2])
+ )
+ );
+ define('PMA_MYSQL_STR_VERSION', $version['@@version']);
+ define(
+ 'PMA_MYSQL_VERSION_COMMENT',
+ $version['@@version_comment']
+ );
+ } else {
+ define('PMA_MYSQL_INT_VERSION', 50015);
+ define('PMA_MYSQL_MAJOR_VERSION', 5);
+ define('PMA_MYSQL_STR_VERSION', '5.00.15');
+ define('PMA_MYSQL_VERSION_COMMENT', '');
+ }
+ PMA_Util::cacheSet(
+ 'PMA_MYSQL_INT_VERSION',
+ PMA_MYSQL_INT_VERSION,
+ true
+ );
+ PMA_Util::cacheSet(
+ 'PMA_MYSQL_MAJOR_VERSION',
+ PMA_MYSQL_MAJOR_VERSION,
+ true
+ );
+ PMA_Util::cacheSet(
+ 'PMA_MYSQL_STR_VERSION',
+ PMA_MYSQL_STR_VERSION,
+ true
+ );
+ PMA_Util::cacheSet(
+ 'PMA_MYSQL_VERSION_COMMENT',
+ PMA_MYSQL_VERSION_COMMENT,
+ true
+ );
+
+ /* Detect Drizzle - it does not support charsets */
+ $charset_result = $this->query(
+ "SHOW VARIABLES LIKE 'character_set_results'",
+ $link
+ );
+ if ($this->numRows($charset_result) == 0) {
+ define('PMA_DRIZZLE', true);
+ } else {
+ define('PMA_DRIZZLE', false);
+ }
+ $this->freeResult($charset_result);
+
+ PMA_Util::cacheSet(
+ 'PMA_DRIZZLE',
+ PMA_DRIZZLE,
+ true
+ );
+ }
+ }
+
+ // Skip charsets for Drizzle
+ if (!PMA_DRIZZLE) {
+ if (PMA_MYSQL_INT_VERSION > 50503) {
+ $default_charset = 'utf8mb4';
+ $default_collation = 'utf8mb4_general_ci';
+ } else {
+ $default_charset = 'utf8';
+ $default_collation = 'utf8_general_ci';
+ }
+ if (! empty($GLOBALS['collation_connection'])) {
+ $this->query(
+ "SET CHARACTER SET '$default_charset';",
+ $link,
+ self::QUERY_STORE
+ );
+ /* Automatically adjust collation to mb4 variant */
+ if ($default_charset == 'utf8mb4'
+ && strncmp('utf8_', $GLOBALS['collation_connection'], 5) == 0
+ ) {
+ $GLOBALS['collation_connection'] = 'utf8mb4_' . substr(
+ $GLOBALS['collation_connection'],
+ 5
+ );
+ }
+ $set_collation_con_query = "SET collation_connection = '"
+ . PMA_Util::sqlAddSlashes($GLOBALS['collation_connection'])
+ . "';";
+ $this->query(
+ $set_collation_con_query,
+ $link,
+ self::QUERY_STORE
+ );
+ } else {
+ $this->query(
+ "SET NAMES '$default_charset' COLLATE '$default_collation';",
+ $link,
+ self::QUERY_STORE
+ );
+ }
+ }
+
+ // Cache plugin list for Drizzle
+ if (PMA_DRIZZLE && !PMA_Util::cacheExists('drizzle_engines', true)) {
+ $sql = "SELECT p.plugin_name, m.module_library
+ FROM data_dictionary.plugins p
+ JOIN data_dictionary.modules m USING (module_name)
+ WHERE p.plugin_type = 'StorageEngine'
+ AND p.plugin_name NOT IN ('FunctionEngine', 'schema')
+ AND p.is_active = 'YES'";
+ $engines = $this->fetchResult($sql, 'plugin_name', null, $link);
+ PMA_Util::cacheSet('drizzle_engines', $engines, true);
+ }
+ }
+
+ /**
+ * returns a single value from the given result or query,
+ * if the query or the result has more than one row or field
+ * the first field of the first row is returned
+ *
+ * <code>
+ * $sql = 'SELECT `name` FROM `user` WHERE `id` = 123';
+ * $user_name = $GLOBALS['dbi']->fetchValue($sql);
+ * // produces
+ * // $user_name = 'John Doe'
+ * </code>
+ *
+ * @param string|mysql_result $result query or mysql result
+ * @param integer $row_number row to fetch the value from,
+ * starting at 0, with 0 being default
+ * @param integer|string $field field to fetch the value from,
+ * starting at 0, with 0 being default
+ * @param resource $link mysql link
+ *
+ * @return mixed value of first field in first row from result
+ * or false if not found
+ */
+ public function fetchValue($result, $row_number = 0, $field = 0, $link = null)
+ {
+ $value = false;
+
+ if (is_string($result)) {
+ $result = $this->tryQuery(
+ $result,
+ $link,
+ self::QUERY_STORE,
+ false
+ );
+ }
+
+ // return false if result is empty or false
+ // or requested row is larger than rows in result
+ if ($this->numRows($result) < ($row_number + 1)) {
+ return $value;
+ }
+
+ // if $field is an integer use non associative mysql fetch function
+ if (is_int($field)) {
+ $fetch_function = 'fetchRow';
+ } else {
+ $fetch_function = 'fetchAssoc';
+ }
+
+ // get requested row
+ for ($i = 0; $i <= $row_number; $i++) {
+ $row = $this->$fetch_function($result);
+ }
+ $this->freeResult($result);
+
+ // return requested field
+ if (isset($row[$field])) {
+ $value = $row[$field];
+ }
+ unset($row);
+
+ return $value;
+ }
+
+ /**
+ * returns only the first row from the result
+ *
+ * <code>
+ * $sql = 'SELECT * FROM `user` WHERE `id` = 123';
+ * $user = $GLOBALS['dbi']->fetchSingleRow($sql);
+ * // produces
+ * // $user = array('id' => 123, 'name' => 'John Doe')
+ * </code>
+ *
+ * @param string|mysql_result $result query or mysql result
+ * @param string $type NUM|ASSOC|BOTH
+ * returned array should either numeric
+ * associativ or booth
+ * @param resource $link mysql link
+ *
+ * @return array|boolean first row from result
+ * or false if result is empty
+ */
+ public function fetchSingleRow($result, $type = 'ASSOC', $link = null)
+ {
+ if (is_string($result)) {
+ $result = $this->tryQuery(
+ $result,
+ $link,
+ self::QUERY_STORE,
+ false
+ );
+ }
+
+ // return null if result is empty or false
+ if (! $this->numRows($result)) {
+ return false;
+ }
+
+ switch ($type) {
+ case 'NUM' :
+ $fetch_function = 'fetchRow';
+ break;
+ case 'ASSOC' :
+ $fetch_function = 'fetchAssoc';
+ break;
+ case 'BOTH' :
+ default :
+ $fetch_function = 'fetchArray';
+ break;
+ }
+
+ $row = $this->$fetch_function($result);
+ $this->freeResult($result);
+ return $row;
+ }
+
+ /**
+ * returns all rows in the resultset in one array
+ *
+ * <code>
+ * $sql = 'SELECT * FROM `user`';
+ * $users = $GLOBALS['dbi']->fetchResult($sql);
+ * // produces
+ * // $users[] = array('id' => 123, 'name' => 'John Doe')
+ *
+ * $sql = 'SELECT `id`, `name` FROM `user`';
+ * $users = $GLOBALS['dbi']->fetchResult($sql, 'id');
+ * // produces
+ * // $users['123'] = array('id' => 123, 'name' => 'John Doe')
+ *
+ * $sql = 'SELECT `id`, `name` FROM `user`';
+ * $users = $GLOBALS['dbi']->fetchResult($sql, 0);
+ * // produces
+ * // $users['123'] = array(0 => 123, 1 => 'John Doe')
+ *
+ * $sql = 'SELECT `id`, `name` FROM `user`';
+ * $users = $GLOBALS['dbi']->fetchResult($sql, 'id', 'name');
+ * // or
+ * $users = $GLOBALS['dbi']->fetchResult($sql, 0, 1);
+ * // produces
+ * // $users['123'] = 'John Doe'
+ *
+ * $sql = 'SELECT `name` FROM `user`';
+ * $users = $GLOBALS['dbi']->fetchResult($sql);
+ * // produces
+ * // $users[] = 'John Doe'
+ *
+ * $sql = 'SELECT `group`, `name` FROM `user`'
+ * $users = $GLOBALS['dbi']->fetchResult($sql, array('group', null), 'name');
+ * // produces
+ * // $users['admin'][] = 'John Doe'
+ *
+ * $sql = 'SELECT `group`, `name` FROM `user`'
+ * $users = $GLOBALS['dbi']->fetchResult($sql, array('group', 'name'), 'id');
+ * // produces
+ * // $users['admin']['John Doe'] = '123'
+ * </code>
+ *
+ * @param string|mysql_result $result query or mysql result
+ * @param string|integer $key field-name or offset
+ * used as key for array
+ * @param string|integer $value value-name or offset
+ * used as value for array
+ * @param resource $link mysql link
+ * @param mixed $options query options
+ *
+ * @return array resultrows or values indexed by $key
+ */
+ public function fetchResult($result, $key = null, $value = null,
+ $link = null, $options = 0
+ ) {
+ $resultrows = array();
+
+ if (is_string($result)) {
+ $result = $this->tryQuery($result, $link, $options, false);
+ }
+
+ // return empty array if result is empty or false
+ if (! $result) {
+ return $resultrows;
+ }
+
+ $fetch_function = 'fetchAssoc';
+
+ // no nested array if only one field is in result
+ if (null === $key && 1 === $this->numFields($result)) {
+ $value = 0;
+ $fetch_function = 'fetchRow';
+ }
+
+ // if $key is an integer use non associative mysql fetch function
+ if (is_int($key)) {
+ $fetch_function = 'fetchRow';
+ }
+
+ if (null === $key && null === $value) {
+ while ($row = $this->$fetch_function($result)) {
+ $resultrows[] = $row;
+ }
+ } elseif (null === $key) {
+ while ($row = $this->$fetch_function($result)) {
+ $resultrows[] = $row[$value];
+ }
+ } elseif (null === $value) {
+ if (is_array($key)) {
+ while ($row = $this->$fetch_function($result)) {
+ $result_target =& $resultrows;
+ foreach ($key as $key_index) {
+ if (null === $key_index) {
+ $result_target =& $result_target[];
+ continue;
+ }
+
+ if (! isset($result_target[$row[$key_index]])) {
+ $result_target[$row[$key_index]] = array();
+ }
+ $result_target =& $result_target[$row[$key_index]];
+ }
+ $result_target = $row;
+ }
+ } else {
+ while ($row = $this->$fetch_function($result)) {
+ $resultrows[$row[$key]] = $row;
+ }
+ }
+ } else {
+ if (is_array($key)) {
+ while ($row = $this->$fetch_function($result)) {
+ $result_target =& $resultrows;
+ foreach ($key as $key_index) {
+ if (null === $key_index) {
+ $result_target =& $result_target[];
+ continue;
+ }
+
+ if (! isset($result_target[$row[$key_index]])) {
+ $result_target[$row[$key_index]] = array();
+ }
+ $result_target =& $result_target[$row[$key_index]];
+ }
+ $result_target = $row[$value];
+ }
+ } else {
+ while ($row = $this->$fetch_function($result)) {
+ $resultrows[$row[$key]] = $row[$value];
+ }
+ }
+ }
+
+ $this->freeResult($result);
+ return $resultrows;
+ }
+
+ /**
+ * Get supported SQL compatibility modes
+ *
+ * @return array supported SQL compatibility modes
+ */
+ public function getCompatibilities()
+ {
+ // Drizzle doesn't support compatibility modes
+ if (PMA_DRIZZLE) {
+ return array();
+ }
+
+ $compats = array('NONE');
+ $compats[] = 'ANSI';
+ $compats[] = 'DB2';
+ $compats[] = 'MAXDB';
+ $compats[] = 'MYSQL323';
+ $compats[] = 'MYSQL40';
+ $compats[] = 'MSSQL';
+ $compats[] = 'ORACLE';
+ // removed; in MySQL 5.0.33, this produces exports that
+ // can't be read by POSTGRESQL (see our bug #1596328)
+ //$compats[] = 'POSTGRESQL';
+ $compats[] = 'TRADITIONAL';
+
+ return $compats;
+ }
+
+ /**
+ * returns warnings for last query
+ *
+ * @param resource $link mysql link resource
+ *
+ * @return array warnings
+ */
+ public function getWarnings($link = null)
+ {
+ if (empty($link)) {
+ if (isset($GLOBALS['userlink'])) {
+ $link = $GLOBALS['userlink'];
+ } else {
+ return array();
+ }
+ }
+
+ return $this->fetchResult('SHOW WARNINGS', null, null, $link);
+ }
+
+ /**
+ * returns an array of PROCEDURE or FUNCTION names for a db
+ *
+ * @param string $db db name
+ * @param string $which PROCEDURE | FUNCTION
+ * @param resource $link mysql link
+ *
+ * @return array the procedure names or function names
+ */
+ public function getProceduresOrFunctions($db, $which, $link = null)
+ {
+ if (PMA_DRIZZLE) {
+ // Drizzle doesn't support functions and procedures
+ return array();
+ }
+ $shows = $this->fetchResult(
+ 'SHOW ' . $which . ' STATUS;', null, null, $link
+ );
+ $result = array();
+ foreach ($shows as $one_show) {
+ if ($one_show['Db'] == $db && $one_show['Type'] == $which) {
+ $result[] = $one_show['Name'];
+ }
+ }
+ return($result);
+ }
+
+ /**
+ * returns the definition of a specific PROCEDURE, FUNCTION, EVENT or VIEW
+ *
+ * @param string $db db name
+ * @param string $which PROCEDURE | FUNCTION | EVENT | VIEW
+ * @param string $name the procedure|function|event|view name
+ * @param resource $link mysql link
+ *
+ * @return string the definition
+ */
+ public function getDefinition($db, $which, $name, $link = null)
+ {
+ $returned_field = array(
+ 'PROCEDURE' => 'Create Procedure',
+ 'FUNCTION' => 'Create Function',
+ 'EVENT' => 'Create Event',
+ 'VIEW' => 'Create View'
+ );
+ $query = 'SHOW CREATE ' . $which . ' '
+ . PMA_Util::backquote($db) . '.'
+ . PMA_Util::backquote($name);
+ return($this->fetchValue($query, 0, $returned_field[$which]));
+ }
+
+ /**
+ * returns details about the TRIGGERs for a specific table or database
+ *
+ * @param string $db db name
+ * @param string $table table name
+ * @param string $delimiter the delimiter to use (may be empty)
+ *
+ * @return array information about triggers (may be empty)
+ */
+ public function getTriggers($db, $table = '', $delimiter = '//')
+ {
+ if (PMA_DRIZZLE) {
+ // Drizzle doesn't support triggers
+ return array();
+ }
+
+ $result = array();
+ // Note: in http://dev.mysql.com/doc/refman/5.0/en/faqs-triggers.html
+ // their example uses WHERE TRIGGER_SCHEMA='dbname' so let's use this
+ // instead of WHERE EVENT_OBJECT_SCHEMA='dbname'
+ $query = 'SELECT TRIGGER_SCHEMA, TRIGGER_NAME, EVENT_MANIPULATION'
+ . ', EVENT_OBJECT_TABLE, ACTION_TIMING, ACTION_STATEMENT'
+ . ', EVENT_OBJECT_SCHEMA, EVENT_OBJECT_TABLE, DEFINER'
+ . ' FROM information_schema.TRIGGERS'
+ . ' WHERE TRIGGER_SCHEMA= \'' . PMA_Util::sqlAddSlashes($db) . '\'';
+
+ if (! empty($table)) {
+ $query .= " AND EVENT_OBJECT_TABLE = '"
+ . PMA_Util::sqlAddSlashes($table) . "';";
+ }
+
+ if ($triggers = $this->fetchResult($query)) {
+ foreach ($triggers as $trigger) {
+ $one_result = array();
+ $one_result['name'] = $trigger['TRIGGER_NAME'];
+ $one_result['table'] = $trigger['EVENT_OBJECT_TABLE'];
+ $one_result['action_timing'] = $trigger['ACTION_TIMING'];
+ $one_result['event_manipulation'] = $trigger['EVENT_MANIPULATION'];
+ $one_result['definition'] = $trigger['ACTION_STATEMENT'];
+ $one_result['definer'] = $trigger['DEFINER'];
+
+ // do not prepend the schema name; this way, importing the
+ // definition into another schema will work
+ $one_result['full_trigger_name'] = PMA_Util::backquote(
+ $trigger['TRIGGER_NAME']
+ );
+ $one_result['drop'] = 'DROP TRIGGER IF EXISTS '
+ . $one_result['full_trigger_name'];
+ $one_result['create'] = 'CREATE TRIGGER '
+ . $one_result['full_trigger_name'] . ' '
+ . $trigger['ACTION_TIMING']. ' '
+ . $trigger['EVENT_MANIPULATION']
+ . ' ON ' . PMA_Util::backquote($trigger['EVENT_OBJECT_TABLE'])
+ . "\n" . ' FOR EACH ROW '
+ . $trigger['ACTION_STATEMENT'] . "\n" . $delimiter . "\n";
+
+ $result[] = $one_result;
+ }
+ }
+
+ // Sort results by name
+ $name = array();
+ foreach ($result as $value) {
+ $name[] = $value['name'];
+ }
+ array_multisort($name, SORT_ASC, $result);
+
+ return($result);
+ }
+
+ /**
+ * Formats database error message in a friendly way.
+ * This is needed because some errors messages cannot
+ * be obtained by mysql_error().
+ *
+ * @param int $error_number Error code
+ * @param string $error_message Error message as returned by server
+ *
+ * @return string HML text with error details
+ */
+ public function formatError($error_number, $error_message)
+ {
+ if (! empty($error_message)) {
+ $error_message = $this->convertMessage($error_message);
+ }
+
+ $error_message = htmlspecialchars($error_message);
+
+ $error = '#' . ((string) $error_number);
+
+ if ($error_number == 2002) {
+ $error .= ' - ' . $error_message;
+ $error .= '<br />';
+ $error .= __(
+ 'The server is not responding (or the local server\'s socket'
+ . ' is not correctly configured).'
+ );
+ } elseif ($error_number == 2003) {
+ $error .= ' - ' . $error_message;
+ $error .= '<br />' . __('The server is not responding.');
+ } elseif ($error_number == 1005) {
+ if (strpos($error_message, 'errno: 13') !== false) {
+ $error .= ' - ' . $error_message;
+ $error .= '<br />'
+ . __('Please check privileges of directory containing database.');
+ } else {
+ /* InnoDB contraints, see
+ * http://dev.mysql.com/doc/refman/5.0/en/
+ * innodb-foreign-key-constraints.html
+ */
+ $error .= ' - ' . $error_message .
+ ' (<a href="server_engines.php' .
+ PMA_URL_getCommon(
+ array('engine' => 'InnoDB', 'page' => 'Status')
+ ) . '">' . __('Details…') . '</a>)';
+ }
+ } else {
+ $error .= ' - ' . $error_message;
+ }
+
+ return $error;
+ }
+
+ /**
+ * returns true (int > 0) if current user is superuser
+ * otherwise 0
+ *
+ * @return bool Whether use is a superuser
+ */
+ public function isSuperuser()
+ {
+ if (PMA_Util::cacheExists('is_superuser', true)) {
+ return PMA_Util::cacheGet('is_superuser', true);
+ }
+
+ // when connection failed we don't have a $userlink
+ if (isset($GLOBALS['userlink'])) {
+ if (PMA_DRIZZLE) {
+ // Drizzle has no authorization by default, so when no plugin is
+ // enabled everyone is a superuser
+ // Known authorization libraries: regex_policy, simple_user_policy
+ // Plugins limit object visibility (dbs, tables, processes), we can
+ // safely assume we always deal with superuser
+ $result = true;
+ } else {
+ // check access to mysql.user table
+ $result = (bool) $GLOBALS['dbi']->tryQuery(
+ 'SELECT COUNT(*) FROM mysql.user',
+ $GLOBALS['userlink'],
+ self::QUERY_STORE
+ );
+ }
+ PMA_Util::cacheSet('is_superuser', $result, true);
+ } else {
+ PMA_Util::cacheSet('is_superuser', false, true);
+ }
+
+ return PMA_Util::cacheGet('is_superuser', true);
+ }
+
+ /**
+ * Checks whether given schema is a system schema: information_schema
+ * (MySQL and Drizzle) or data_dictionary (Drizzle)
+ *
+ * @param string $schema_name Name of schema (database) to test
+ * @param bool $test_for_mysql_schema Whether 'mysql' schema should
+ * be treated the same as IS and DD
+ *
+ * @return bool
+ */
+ public function isSystemSchema($schema_name, $test_for_mysql_schema = false)
+ {
+ return strtolower($schema_name) == 'information_schema'
+ || (!PMA_DRIZZLE && strtolower($schema_name) == 'performance_schema')
+ || (PMA_DRIZZLE && strtolower($schema_name) == 'data_dictionary')
+ || ($test_for_mysql_schema && !PMA_DRIZZLE && $schema_name == 'mysql');
+ }
+
+ /**
+ * connects to the database server
+ *
+ * @param string $user user name
+ * @param string $password user password
+ * @param bool $is_controluser whether this is a control user connection
+ * @param array $server host/port/socket/persistent
+ * @param bool $auxiliary_connection (when true, don't go back to login if
+ * connection fails)
+ *
+ * @return mixed false on error or a connection object on success
+ */
+ public function connect(
+ $user, $password, $is_controluser = false, $server = null,
+ $auxiliary_connection = false
+ ) {
+ return $this->_extension->connect(
+ $user, $password, $is_controluser, $server, $auxiliary_connection
+ );
+ }
+
+ /**
+ * selects given database
+ *
+ * @param string $dbname database name to select
+ * @param object $link connection object
+ *
+ * @return boolean
+ */
+ public function selectDb($dbname, $link = null)
+ {
+ return $this->_extension->selectDb($dbname, $link);
+ }
+
+ /**
+ * returns array of rows with associative and numeric keys from $result
+ *
+ * @param object $result result set identifier
+ *
+ * @return array
+ */
+ public function fetchArray($result)
+ {
+ return $this->_extension->fetchArray($result);
+ }
+
+ /**
+ * returns array of rows with associative keys from $result
+ *
+ * @param object $result result set identifier
+ *
+ * @return array
+ */
+ public function fetchAssoc($result)
+ {
+ return $this->_extension->fetchAssoc($result);
+ }
+
+ /**
+ * returns array of rows with numeric keys from $result
+ *
+ * @param object $result result set identifier
+ *
+ * @return array
+ */
+ public function fetchRow($result)
+ {
+ return $this->_extension->fetchRow($result);
+ }
+
+ /**
+ * Adjusts the result pointer to an arbitrary row in the result
+ *
+ * @param object $result database result
+ * @param integer $offset offset to seek
+ *
+ * @return bool true on success, false on failure
+ */
+ public function dataSeek($result, $offset)
+ {
+ return $this->_extension->dataSeek($result, $offset);
+ }
+
+ /**
+ * Frees memory associated with the result
+ *
+ * @param object $result database result
+ *
+ * @return void
+ */
+ public function freeResult($result)
+ {
+ $this->_extension->freeResult($result);
+ }
+
+ /**
+ * Check if there are any more query results from a multi query
+ *
+ * @param object $link the connection object
+ *
+ * @return bool true or false
+ */
+ public function moreResults($link = null)
+ {
+ return $this->_extension->moreResults($link = null);
+ }
+
+ /**
+ * Prepare next result from multi_query
+ *
+ * @param object $link the connection object
+ *
+ * @return bool true or false
+ */
+ public function nextResult($link = null)
+ {
+ return $this->_extension->nextResult($link = null);
+ }
+
+ /**
+ * Store the result returned from multi query
+ *
+ * @return mixed false when empty results / result set when not empty
+ */
+ public function storeResult()
+ {
+ return $this->_extension->storeResult();
+ }
+
+ /**
+ * Returns a string representing the type of connection used
+ *
+ * @param object $link mysql link
+ *
+ * @return string type of connection used
+ */
+ public function getHostInfo($link = null)
+ {
+ return $this->_extension->getHostInfo($link);
+ }
+
+ /**
+ * Returns the version of the MySQL protocol used
+ *
+ * @param object $link mysql link
+ *
+ * @return integer version of the MySQL protocol used
+ */
+ public function getProtoInfo($link = null)
+ {
+ return $this->_extension->getProtoInfo($link);
+ }
+
+ /**
+ * returns a string that represents the client library version
+ *
+ * @return string MySQL client library version
+ */
+ public function getClientInfo()
+ {
+ return $this->_extension->getClientInfo();
+ }
+
+ /**
+ * returns last error message or false if no errors occurred
+ *
+ * @param object $link connection link
+ *
+ * @return string|bool $error or false
+ */
+ public function getError($link = null)
+ {
+ return $this->_extension->getError($link);
+ }
+
+ /**
+ * returns the number of rows returned by last query
+ *
+ * @param object $result result set identifier
+ *
+ * @return string|int
+ */
+ public function numRows($result)
+ {
+ return $this->_extension->numRows($result);
+ }
+
+ /**
+ * returns last inserted auto_increment id for given $link
+ * or $GLOBALS['userlink']
+ *
+ * @param object $link the connection object
+ *
+ * @return string|int
+ */
+ public function insertId($link = null)
+ {
+ return $this->_extension->insertId($link);
+ }
+
+ /**
+ * returns the number of rows affected by last query
+ *
+ * @param object $link the connection object
+ * @param bool $get_from_cache whether to retrieve from cache
+ *
+ * @return string|int
+ */
+ public function affectedRows($link = null, $get_from_cache = true)
+ {
+ return $this->_extension->affectedRows($link, $get_from_cache);
+ }
+
+ /**
+ * returns metainfo for fields in $result
+ *
+ * @param object $result result set identifier
+ *
+ * @return array meta info for fields in $result
+ */
+ public function getFieldsMeta($result)
+ {
+ return $this->_extension->getFieldsMeta($result);
+ }
+
+ /**
+ * return number of fields in given $result
+ *
+ * @param object $result result set identifier
+ *
+ * @return int field count
+ */
+ public function numFields($result)
+ {
+ return $this->_extension->numFields($result);
+ }
+
+ /**
+ * returns the length of the given field $i in $result
+ *
+ * @param object $result result set identifier
+ * @param int $i field
+ *
+ * @return int length of field
+ */
+ public function fieldLen($result, $i)
+ {
+ return $this->_extension->fieldLen($result, $i);
+ }
+
+ /**
+ * returns name of $i. field in $result
+ *
+ * @param object $result result set identifier
+ * @param int $i field
+ *
+ * @return string name of $i. field in $result
+ */
+ public function fieldName($result, $i)
+ {
+ return $this->_extension->fieldName($result, $i);
+ }
+
+ /**
+ * returns concatenated string of human readable field flags
+ *
+ * @param object $result result set identifier
+ * @param int $i field
+ *
+ * @return string field flags
+ */
+ public function fieldFlags($result, $i)
+ {
+ return $this->_extension->fieldFlags($result, $i);
+ }
+}
+?>
diff --git a/libraries/DbSearch.class.php b/libraries/DbSearch.class.php
new file mode 100644
index 0000000000..faf7a42db0
--- /dev/null
+++ b/libraries/DbSearch.class.php
@@ -0,0 +1,500 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Handles Database Search
+ *
+ * @package PhpMyAdmin
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ * Class to handle database search
+ *
+ * @package PhpMyAdmin
+ */
+class PMA_DbSearch
+{
+ /**
+ * Database name
+ *
+ * @access private
+ * @var string
+ */
+ private $_db;
+ /**
+ * Table Names
+ *
+ * @access private
+ * @var array
+ */
+ private $_tables_names_only;
+ /**
+ * Type of search
+ *
+ * @access private
+ * @var array
+ */
+ private $_searchTypes;
+ /**
+ * Already set search type
+ *
+ * @access private
+ * @var integer
+ */
+ private $_criteriaSearchType;
+ /**
+ * Already set search type's description
+ *
+ * @access private
+ * @var string
+ */
+ private $_searchTypeDescription;
+ /**
+ * Search string/regexp
+ *
+ * @access private
+ * @var string
+ */
+ private $_criteriaSearchString;
+ /**
+ * Criteria Tables to search in
+ *
+ * @access private
+ * @var array
+ */
+ private $_criteriaTables;
+ /**
+ * Restrict the search to this column
+ *
+ * @access private
+ * @var string
+ */
+ private $_criteriaColumnName;
+
+ /**
+ * Public Constructor
+ *
+ * @param string $db Database name
+ */
+ public function __construct($db)
+ {
+ $this->_db = $db;
+ // Sets criteria parameters
+ $this->_setSearchParams();
+ }
+
+ /**
+ * Sets search parameters
+ *
+ * @return void
+ */
+ private function _setSearchParams()
+ {
+ $this->_tables_names_only = $GLOBALS['dbi']->getTables($this->_db);
+
+ $this->_searchTypes = array(
+ '1' => __('at least one of the words'),
+ '2' => __('all words'),
+ '3' => __('the exact phrase'),
+ '4' => __('as regular expression'),
+ );
+
+ if (empty($_REQUEST['criteriaSearchType'])
+ || ! is_string($_REQUEST['criteriaSearchType'])
+ || ! array_key_exists(
+ $_REQUEST['criteriaSearchType'],
+ $this->_searchTypes
+ )
+ ) {
+ $this->_criteriaSearchType = 1;
+ unset($_REQUEST['submit_search']);
+ } else {
+ $this->_criteriaSearchType = (int) $_REQUEST['criteriaSearchType'];
+ $this->_searchTypeDescription
+ = $this->_searchTypes[$_REQUEST['criteriaSearchType']];
+ }
+
+ if (empty($_REQUEST['criteriaSearchString'])
+ || ! is_string($_REQUEST['criteriaSearchString'])
+ ) {
+ $this->_criteriaSearchString = '';
+ unset($_REQUEST['submit_search']);
+ } else {
+ $this->_criteriaSearchString = $_REQUEST['criteriaSearchString'];
+ }
+
+ $this->_criteriaTables = array();
+ if (empty($_REQUEST['criteriaTables'])
+ || ! is_array($_REQUEST['criteriaTables'])
+ ) {
+ unset($_REQUEST['submit_search']);
+ } else {
+ $this->_criteriaTables = array_intersect(
+ $_REQUEST['criteriaTables'], $this->_tables_names_only
+ );
+ }
+
+ if (empty($_REQUEST['criteriaColumnName'])
+ || ! is_string($_REQUEST['criteriaColumnName'])
+ ) {
+ unset($this->_criteriaColumnName);
+ } else {
+ $this->_criteriaColumnName = PMA_Util::sqlAddSlashes(
+ $_REQUEST['criteriaColumnName'], true
+ );
+ }
+ }
+
+ /**
+ * Builds the SQL search query
+ *
+ * @param string $table The table name
+ *
+ * @return array 3 SQL querys (for count, display and delete results)
+ *
+ * @todo can we make use of fulltextsearch IN BOOLEAN MODE for this?
+ * PMA_backquote
+ * DatabaseInterface::freeResult
+ * DatabaseInterface::fetchAssoc
+ * $GLOBALS['db']
+ * explode
+ * count
+ * strlen
+ */
+ private function _getSearchSqls($table)
+ {
+ // Statement types
+ $sqlstr_select = 'SELECT';
+ $sqlstr_delete = 'DELETE';
+ // Table to use
+ $sqlstr_from = ' FROM '
+ . PMA_Util::backquote($GLOBALS['db']) . '.'
+ . PMA_Util::backquote($table);
+ // Gets where clause for the query
+ $where_clause = $this->_getWhereClause($table);
+ // Builds complete queries
+ $sql['select_columns'] = $sqlstr_select . ' * ' . $sqlstr_from
+ . $where_clause;
+ // here, I think we need to still use the COUNT clause, even for
+ // VIEWs, anyway we have a WHERE clause that should limit results
+ $sql['select_count'] = $sqlstr_select . ' COUNT(*) AS `count`'
+ . $sqlstr_from . $where_clause;
+ $sql['delete'] = $sqlstr_delete . $sqlstr_from . $where_clause;
+
+ return $sql;
+ }
+
+ /**
+ * Provides where clause for bulding SQL query
+ *
+ * @param string $table The table name
+ *
+ * @return string The generated where clause
+ */
+ private function _getWhereClause($table)
+ {
+ $where_clause = '';
+ // Columns to select
+ $allColumns = $GLOBALS['dbi']->getColumns($GLOBALS['db'], $table);
+ $likeClauses = array();
+ // Based on search type, decide like/regex & '%'/''
+ $like_or_regex = (($this->_criteriaSearchType == 4) ? 'REGEXP' : 'LIKE');
+ $automatic_wildcard = (($this->_criteriaSearchType < 3) ? '%' : '');
+ // For "as regular expression" (search option 4), LIKE won't be used
+ // Usage example: If user is seaching for a literal $ in a regexp search,
+ // he should enter \$ as the value.
+ $this->_criteriaSearchString = PMA_Util::sqlAddSlashes(
+ $this->_criteriaSearchString,
+ ($this->_criteriaSearchType == 4 ? false : true)
+ );
+ // Extract search words or pattern
+ $search_words = (($this->_criteriaSearchType > 2)
+ ? array($this->_criteriaSearchString)
+ : explode(' ', $this->_criteriaSearchString));
+
+ foreach ($search_words as $search_word) {
+ // Eliminates empty values
+ if (strlen($search_word) === 0) {
+ continue;
+ }
+ $likeClausesPerColumn = array();
+ // for each column in the table
+ foreach ($allColumns as $column) {
+ if (! isset($this->_criteriaColumnName)
+ || strlen($this->_criteriaColumnName) == 0
+ || $column['Field'] == $this->_criteriaColumnName
+ ) {
+ // Drizzle has no CONVERT and all text columns are UTF-8
+ $column = ((PMA_DRIZZLE)
+ ? PMA_Util::backquote($column['Field'])
+ : 'CONVERT(' . PMA_Util::backquote($column['Field'])
+ . ' USING utf8)');
+ $likeClausesPerColumn[] = $column . ' ' . $like_or_regex . ' '
+ . "'"
+ . $automatic_wildcard . $search_word . $automatic_wildcard
+ . "'";
+ }
+ } // end for
+ if (count($likeClausesPerColumn) > 0) {
+ $likeClauses[] = implode(' OR ', $likeClausesPerColumn);
+ }
+ } // end for
+ // Use 'OR' if 'at least one word' is to be searched, else use 'AND'
+ $implode_str = ($this->_criteriaSearchType == 1 ? ' OR ' : ' AND ');
+ if ( empty($likeClauses)) {
+ // this could happen when the "inside column" does not exist
+ // in any selected tables
+ $where_clause = ' WHERE FALSE';
+ } else {
+ $where_clause = ' WHERE ('
+ . implode(') ' . $implode_str . ' (', $likeClauses)
+ . ')';
+ }
+ return $where_clause;
+ }
+
+ /**
+ * Displays database search results
+ *
+ * @return string HTML for search results
+ */
+ public function getSearchResults()
+ {
+ $html_output = '';
+ // Displays search string
+ $html_output .= '<br />'
+ . '<table class="data">'
+ . '<caption class="tblHeaders">'
+ . sprintf(
+ __('Search results for "<i>%s</i>" %s:'),
+ htmlspecialchars($this->_criteriaSearchString),
+ $this->_searchTypeDescription
+ )
+ . '</caption>';
+
+ $num_search_result_total = 0;
+ $odd_row = true;
+ // For each table selected as search criteria
+ foreach ($this->_criteriaTables as $each_table) {
+ // Gets the SQL statements
+ $newsearchsqls = $this->_getSearchSqls($each_table);
+ // Executes the "COUNT" statement
+ $res_cnt = $GLOBALS['dbi']->fetchValue($newsearchsqls['select_count']);
+ $num_search_result_total += $res_cnt;
+ // Gets the result row's HTML for a table
+ $html_output .= $this->_getResultsRow(
+ $each_table, $newsearchsqls, $odd_row, $res_cnt
+ );
+ $odd_row = ! $odd_row;
+ } // end for
+ $html_output .= '</table>';
+ // Displays total number of matches
+ if (count($this->_criteriaTables) > 1) {
+ $html_output .= '<p>';
+ $html_output .= sprintf(
+ _ngettext(
+ '<b>Total:</b> <i>%s</i> match',
+ '<b>Total:</b> <i>%s</i> matches',
+ $num_search_result_total
+ ),
+ $num_search_result_total
+ );
+ $html_output .= '</p>';
+ }
+ return $html_output;
+ }
+
+ /**
+ * Provides search results row with browse/delete links.
+ * (for a table)
+ *
+ * @param string $each_table One of the tables on which search was performed
+ * @param array $newsearchsqls Contains SQL queries
+ * @param bool $odd_row For displaying contrasting table rows
+ * @param integer $res_cnt Number of results found
+ *
+ * @return string HTML row
+ */
+ private function _getResultsRow($each_table, $newsearchsqls, $odd_row, $res_cnt)
+ {
+ $this_url_params = array(
+ 'db' => $GLOBALS['db'],
+ 'table' => $each_table,
+ 'goto' => 'db_sql.php',
+ 'pos' => 0,
+ 'is_js_confirmed' => 0,
+ );
+ // Start forming search results row
+ $html_output = '<tr class="noclick ' . ($odd_row ? 'odd' : 'even') . '">';
+ // Displays results count for a table
+ $html_output .= '<td>';
+ $html_output .= sprintf(
+ _ngettext(
+ '%1$s match in <strong>%2$s</strong>',
+ '%1$s matches in <strong>%2$s</strong>', $res_cnt
+ ),
+ $res_cnt, htmlspecialchars($each_table)
+ );
+ $html_output .= '</td>';
+ // Displays browse/delete link if result count > 0
+ if ($res_cnt > 0) {
+ $this_url_params['sql_query'] = $newsearchsqls['select_columns'];
+ $browse_result_path = 'sql.php' . PMA_URL_getCommon($this_url_params);
+ $html_output .= '<td><a name="browse_search" class="ajax" href="'
+ . $browse_result_path . '" onclick="loadResult(\''
+ . $browse_result_path . '\',\'' . $each_table . '\',\''
+ . PMA_URL_getCommon($GLOBALS['db'], $each_table) . '\''
+ . ');return false;" >'
+ . __('Browse') . '</a></td>';
+ $this_url_params['sql_query'] = $newsearchsqls['delete'];
+ $delete_result_path = 'sql.php' . PMA_URL_getCommon($this_url_params);
+ $html_output .= '<td><a name="delete_search" class="ajax" href="'
+ . $delete_result_path . '" onclick="deleteResult(\''
+ . $delete_result_path . '\' , \''
+ . sprintf(
+ __('Delete the matches for the %s table?'),
+ htmlspecialchars($each_table)
+ )
+ . '\');return false;">'
+ . __('Delete') . '</a></td>';
+ } else {
+ $html_output .= '<td>&nbsp;</td>'
+ .'<td>&nbsp;</td>';
+ }// end if else
+ $html_output .= '</tr>';
+ return $html_output;
+ }
+
+ /**
+ * Provides the main search form's html
+ *
+ * @param array $url_params URL parameters
+ *
+ * @return string HTML for selection form
+ */
+ public function getSelectionForm($url_params)
+ {
+ $html_output = '<a id="db_search"></a>';
+ $html_output .= '<form id="db_search_form"'
+ . ' class="ajax"'
+ . ' method="post" action="db_search.php" name="db_search">';
+ $html_output .= PMA_URL_getHiddenInputs($GLOBALS['db']);
+ $html_output .= '<fieldset>';
+ // set legend caption
+ $html_output .= '<legend>' . __('Search in database') . '</legend>';
+ $html_output .= '<table class="formlayout">';
+ // inputbox for search phrase
+ $html_output .= '<tr>';
+ $html_output .= '<td>' . __('Words or values to search for (wildcard: "%"):')
+ . '</td>';
+ $html_output .= '<td><input type="text"'
+ . ' name="criteriaSearchString" size="60"'
+ . ' value="' . htmlspecialchars($this->_criteriaSearchString) . '" />';
+ $html_output .= '</td>';
+ $html_output .= '</tr>';
+ // choices for types of search
+ $html_output .= '<tr>';
+ $html_output .= '<td class="right vtop">' . __('Find:') . '</td>';
+ $html_output .= '<td>';
+ $choices = array(
+ '1' => __('at least one of the words')
+ . PMA_Util::showHint(
+ __('Words are separated by a space character (" ").')
+ ),
+ '2' => __('all words')
+ . PMA_Util::showHint(
+ __('Words are separated by a space character (" ").')
+ ),
+ '3' => __('the exact phrase'),
+ '4' => __('as regular expression') . ' '
+ . PMA_Util::showMySQLDocu('Regexp')
+ );
+ // 4th parameter set to true to add line breaks
+ // 5th parameter set to false to avoid htmlspecialchars() escaping
+ // in the label since we have some HTML in some labels
+ $html_output .= PMA_Util::getRadioFields(
+ 'criteriaSearchType', $choices, $this->_criteriaSearchType, true, false
+ );
+ $html_output .= '</td></tr>';
+ // displays table names as select options
+ $html_output .= '<tr>';
+ $html_output .= '<td class="right vtop">' . __('Inside tables:') . '</td>';
+ $html_output .= '<td rowspan="2">';
+ $html_output .= '<select name="criteriaTables[]" size="6"'
+ . ' multiple="multiple">';
+ foreach ($this->_tables_names_only as $each_table) {
+ if (in_array($each_table, $this->_criteriaTables)) {
+ $is_selected = ' selected="selected"';
+ } else {
+ $is_selected = '';
+ }
+ $html_output .= '<option value="' . htmlspecialchars($each_table) . '"'
+ . $is_selected . '>'
+ . str_replace(' ', '&nbsp;', htmlspecialchars($each_table))
+ . '</option>';
+ } // end for
+ $html_output .= '</select>';
+ $html_output .= '</td></tr>';
+ // Displays 'select all' and 'unselect all' links
+ $alter_select = '<a href="#" '
+ . 'onclick="setSelectOptions(\'db_search\','
+ . ' \'criteriaTables[]\', true); return false;">'
+ . __('Select All') . '</a> &nbsp;/&nbsp;';
+ $alter_select .= '<a href="#" '
+ . 'onclick="setSelectOptions(\'db_search\','
+ . ' \'criteriaTables[]\', false); return false;">'
+ . __('Unselect All') . '</a>';
+ $html_output .= '<tr><td class="right vbottom">'
+ . $alter_select . '</td></tr>';
+ // Inputbox for column name entry
+ $html_output .= '<tr>';
+ $html_output .= '<td class="right">' . __('Inside column:') . '</td>';
+ $html_output .= '<td><input type="text" name="criteriaColumnName" size="60"'
+ . 'value="'
+ . (! empty($this->_criteriaColumnName)
+ ? htmlspecialchars($this->_criteriaColumnName)
+ : '')
+ . '" /></td>';
+ $html_output .= '</tr>';
+ $html_output .= '</table>';
+ $html_output .= '</fieldset>';
+ $html_output .= '<fieldset class="tblFooters">';
+ $html_output .= '<input type="submit" name="submit_search" value="'
+ . __('Go') . '" id="buttonGo" />';
+ $html_output .= '</fieldset>';
+ $html_output .= '</form>';
+ $html_output .= '<div id="togglesearchformdiv">'
+ . '<a id="togglesearchformlink"></a></div>';
+
+ return $html_output;
+ }
+
+ /**
+ * Provides div tags for browsing search results and sql query form.
+ *
+ * @return string div tags
+ */
+ public function _getResultDivs()
+ {
+ $html_output = '<!-- These two table-image and table-link elements display'
+ . ' the table name in browse search results -->';
+ $html_output .= '<div id="table-info">';
+ $html_output .= '<a class="item" id="table-link" ></a>';
+ $html_output .= '</div>';
+ // div for browsing results
+ $html_output .= '<div id="browse-results">';
+ $html_output .= '<!-- this browse-results div is used to load the browse'
+ . ' and delete results in the db search -->';
+ $html_output .= '</div>';
+ $html_output .= '<br class="clearfloat" />';
+ $html_output .= '<div id="sqlqueryform">';
+ $html_output .= '<!-- this sqlqueryform div is used to load the delete'
+ . ' form in the db search -->';
+ $html_output .= '</div>';
+ $html_output .= '<!-- toggle query box link-->';
+ $html_output .= '<a id="togglequerybox"></a>';
+ return $html_output;
+ }
+}
diff --git a/libraries/DisplayResults.class.php b/libraries/DisplayResults.class.php
new file mode 100644
index 0000000000..9d197f5843
--- /dev/null
+++ b/libraries/DisplayResults.class.php
@@ -0,0 +1,6038 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Hold the PMA_DisplayResults class
+ *
+ * @package PhpMyAdmin
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ * Handle all the functionalities related to displaying results
+ * of sql queries, stored procedure, browsing sql processes or
+ * displaying binary log.
+ *
+ * @package PhpMyAdmin
+ */
+class PMA_DisplayResults
+{
+
+ // Define constants
+ const NO_EDIT_OR_DELETE = 'nn';
+ const UPDATE_ROW = 'ur';
+ const DELETE_ROW = 'dr';
+ const KILL_PROCESS = 'kp';
+
+ const POSITION_LEFT = 'left';
+ const POSITION_RIGHT = 'right';
+ const POSITION_BOTH = 'both';
+ const POSITION_NONE = 'none';
+
+ const PLACE_TOP_DIRECTION_DROPDOWN = 'top_direction_dropdown';
+ const PLACE_BOTTOM_DIRECTION_DROPDOWN = 'bottom_direction_dropdown';
+
+ const DISP_DIR_HORIZONTAL = 'horizontal';
+ const DISP_DIR_HORIZONTAL_FLIPPED = 'horizontalflipped';
+ const DISP_DIR_VERTICAL = 'vertical';
+
+ const DISPLAY_FULL_TEXT = 'F';
+ const DISPLAY_PARTIAL_TEXT = 'P';
+
+ const HEADER_FLIP_TYPE_AUTO = 'auto';
+ const HEADER_FLIP_TYPE_CSS = 'css';
+ const HEADER_FLIP_TYPE_FAKE = 'fake';
+
+ const DATE_FIELD = 'date';
+ const DATETIME_FIELD = 'datetime';
+ const TIMESTAMP_FIELD = 'timestamp';
+ const TIME_FIELD = 'time';
+ const STRING_FIELD = 'string';
+ const GEOMETRY_FIELD = 'geometry';
+ const BLOB_FIELD = 'BLOB';
+ const BINARY_FIELD = 'BINARY';
+
+ const RELATIONAL_KEY = 'K';
+ const RELATIONAL_DISPLAY_COLUMN = 'D';
+
+ const GEOMETRY_DISP_GEOM = 'GEOM';
+ const GEOMETRY_DISP_WKT = 'WKT';
+ const GEOMETRY_DISP_WKB = 'WKB';
+
+ const SMART_SORT_ORDER = 'SMART';
+ const ASCENDING_SORT_DIR = 'ASC';
+ const DESCENDING_SORT_DIR = 'DESC';
+
+ const TABLE_TYPE_INNO_DB = 'InnoDB';
+ const ALL_ROWS = 'all';
+ const QUERY_TYPE_SELECT = 'SELECT';
+
+ const ROUTINE_PROCEDURE = 'procedure';
+ const ROUTINE_FUNCTION = 'function';
+
+
+ // Declare global fields
+
+ /** array with properties of the class */
+ private $_property_array = array(
+
+ /** string Database name */
+ 'db' => null,
+
+ /** string Table name */
+ 'table' => null,
+
+ /** string the URL to go back in case of errors */
+ 'goto' => null,
+
+ /** string the SQL query */
+ 'sql_query' => null,
+
+ /**
+ * integer the total number of rows returned by the SQL query without any
+ * appended "LIMIT" clause programmatically
+ */
+ 'unlim_num_rows' => null,
+
+ /** array meta information about fields */
+ 'fields_meta' => null,
+
+ /** boolean */
+ 'is_count' => null,
+
+ /** integer */
+ 'is_export' => null,
+
+ /** boolean */
+ 'is_func' => null,
+
+ /** integer */
+ 'is_analyse' => null,
+
+ /** integer the total number of rows returned by the SQL query */
+ 'num_rows' => null,
+
+ /** integer the total number of fields returned by the SQL query */
+ 'fields_cnt' => null,
+
+ /** double time taken for execute the SQL query */
+ 'querytime' => null,
+
+ /** string path for theme images directory */
+ 'pma_theme_image' => null,
+
+ /** string */
+ 'text_dir' => null,
+
+ /** boolean */
+ 'is_maint' => null,
+
+ /** boolean */
+ 'is_explain' => null,
+
+ /** boolean */
+ 'is_show' => null,
+
+ /** array table definitions */
+ 'showtable' => null,
+
+ /** string */
+ 'printview' => null,
+
+ /** string URL query */
+ 'url_query' => null,
+
+ /** array column names to highlight */
+ 'highlight_columns' => null,
+
+ /** array informations used with vertical display mode */
+ 'vertical_display' => null,
+
+ /** array mime types information of fields */
+ 'mime_map' => null,
+
+ /** boolean */
+ 'editable' => null
+ );
+
+ /**
+ * This variable contains the column transformation information
+ * for some of the system databases.
+ * One element of this array represent all relevant columns in all tables in
+ * one specific database
+ */
+ public $transformation_info;
+
+
+ /**
+ * Get any property of this class
+ *
+ * @param string $property name of the property
+ *
+ * @return mixed|void if property exist, value of the relevant property
+ */
+ public function __get($property)
+ {
+ if (array_key_exists($property, $this->_property_array)) {
+ return $this->_property_array[$property];
+ }
+ }
+
+
+ /**
+ * Set values for any property of this class
+ *
+ * @param string $property name of the property
+ * @param mixed $value value to set
+ *
+ * @return void
+ */
+ public function __set($property, $value)
+ {
+ if (array_key_exists($property, $this->_property_array)) {
+ $this->_property_array[$property] = $value;
+ }
+ }
+
+
+ /**
+ * Constructor for PMA_DisplayResults class
+ *
+ * @param string $db the database name
+ * @param string $table the table name
+ * @param string $goto the URL to go back in case of errors
+ * @param string $sql_query the SQL query
+ *
+ * @access public
+ */
+ public function __construct($db, $table, $goto, $sql_query)
+ {
+ $this->_setDefaultTransformations();
+
+ $this->__set('db', $db);
+ $this->__set('table', $table);
+ $this->__set('goto', $goto);
+ $this->__set('sql_query', $sql_query);
+ }
+
+ /**
+ * Sets default transformations for some columns
+ *
+ * @return void
+ */
+ private function _setDefaultTransformations()
+ {
+ $sql_highlighting_data = array(
+ 'libraries/plugins/transformations/Text_Plain_Formatted.class.php',
+ 'Text_Plain_Formatted',
+ 'Text_Plain'
+ );
+ $this->transformation_info = array(
+ 'information_schema' => array(
+ 'events' => array(
+ 'event_definition' => $sql_highlighting_data
+ ),
+ 'processlist' => array(
+ 'info' => $sql_highlighting_data
+ ),
+ 'routines' => array(
+ 'routine_definition' => $sql_highlighting_data
+ ),
+ 'triggers' => array(
+ 'action_statement' => $sql_highlighting_data
+ ),
+ 'views' => array(
+ 'view_definition' => $sql_highlighting_data
+ )
+ )
+ );
+
+ $cfgRelation = PMA_getRelationsParam();
+ if ($cfgRelation['db']) {
+ $this->transformation_info[$cfgRelation['db']] = array();
+ $relDb = &$this->transformation_info[$cfgRelation['db']];
+ if (! empty($cfgRelation['history'])) {
+ $relDb[$cfgRelation['history']] = array(
+ 'sqlquery' => $sql_highlighting_data
+ );
+ }
+ if (! empty($cfgRelation['bookmark'])) {
+ $relDb[$cfgRelation['bookmark']] = array(
+ 'query' => $sql_highlighting_data
+ );
+ }
+ if (! empty($cfgRelation['tracking'])) {
+ $relDb[$cfgRelation['tracking']] = array(
+ 'schema_sql' => $sql_highlighting_data,
+ 'data_sql' => $sql_highlighting_data
+ );
+ }
+ }
+ }
+
+ /**
+ * Set properties which were not initialized at the constructor
+ *
+ * @param integer $unlim_num_rows the total number of rows returned by
+ * the SQL query without any appended
+ * "LIMIT" clause programmatically
+ * @param array $fields_meta meta information about fields
+ * @param boolean $is_count statement is SELECT COUNT
+ * @param integer $is_export statement contains INTO OUTFILE
+ * @param boolean $is_func statement contains a function like SUM()
+ * @param integer $is_analyse statement contains PROCEDURE ANALYSE
+ * @param integer $num_rows total no. of rows returned by SQL query
+ * @param integer $fields_cnt total no.of fields returned by SQL query
+ * @param double $querytime time taken for execute the SQL query
+ * @param string $pmaThemeImage path for theme images directory
+ * @param string $text_dir text direction
+ * @param boolean $is_maint statement contains a maintenance command
+ * @param boolean $is_explain statement contains EXPLAIN
+ * @param boolean $is_show statement contains SHOW
+ * @param array $showtable table definitions
+ * @param string $printview print view was requested
+ * @param string $url_query URL query
+ * @param boolean $editable whether the results set is editable
+ *
+ * @return void
+ *
+ * @see sql.php
+ */
+ public function setProperties(
+ $unlim_num_rows, $fields_meta, $is_count, $is_export, $is_func,
+ $is_analyse, $num_rows, $fields_cnt, $querytime, $pmaThemeImage, $text_dir,
+ $is_maint, $is_explain, $is_show, $showtable, $printview, $url_query,
+ $editable
+ ) {
+
+ $this->__set('unlim_num_rows', $unlim_num_rows);
+ $this->__set('fields_meta', $fields_meta);
+ $this->__set('is_count', $is_count);
+ $this->__set('is_export', $is_export);
+ $this->__set('is_func', $is_func);
+ $this->__set('is_analyse', $is_analyse);
+ $this->__set('num_rows', $num_rows);
+ $this->__set('fields_cnt', $fields_cnt);
+ $this->__set('querytime', $querytime);
+ $this->__set('pma_theme_image', $pmaThemeImage);
+ $this->__set('text_dir', $text_dir);
+ $this->__set('is_maint', $is_maint);
+ $this->__set('is_explain', $is_explain);
+ $this->__set('is_show', $is_show);
+ $this->__set('showtable', $showtable);
+ $this->__set('printview', $printview);
+ $this->__set('url_query', $url_query);
+ $this->__set('editable', $editable);
+
+ } // end of the 'setProperties()' function
+
+
+ /**
+ * Defines the display mode to use for the results of a SQL query
+ *
+ * It uses a synthetic string that contains all the required informations.
+ * In this string:
+ * - the first two characters stand for the action to do while
+ * clicking on the "edit" link (e.g. 'ur' for update a row, 'nn' for no
+ * edit link...);
+ * - the next two characters stand for the action to do while
+ * clicking on the "delete" link (e.g. 'kp' for kill a process, 'nn' for
+ * no delete link...);
+ * - the next characters are boolean values (1/0) and respectively stand
+ * for sorting links, navigation bar, "insert a new row" link, the
+ * bookmark feature, the expand/collapse text/blob fields button and
+ * the "display printable view" option.
+ * Of course '0'/'1' means the feature won't/will be enabled.
+ *
+ * @param string &$the_disp_mode the synthetic value for display_mode (see a few
+ * lines above for explanations)
+ * @param integer &$the_total the total number of rows returned by the SQL
+ * query without any programmatically appended
+ * LIMIT clause
+ * (just a copy of $unlim_num_rows if it exists,
+ * elsecomputed inside this function)
+ *
+ * @return array an array with explicit indexes for all the display
+ * elements
+ *
+ * @access private
+ *
+ * @see getTable()
+ */
+ private function _setDisplayMode(&$the_disp_mode, &$the_total)
+ {
+
+ // Following variables are needed for use in isset/empty or
+ // use with array indexes or safe use in foreach
+ $db = $this->__get('db');
+ $table = $this->__get('table');
+ $unlim_num_rows = $this->__get('unlim_num_rows');
+ $fields_meta = $this->__get('fields_meta');
+ $printview = $this->__get('printview');
+
+ // 1. Initializes the $do_display array
+ $do_display = array();
+ $do_display['edit_lnk'] = $the_disp_mode[0] . $the_disp_mode[1];
+ $do_display['del_lnk'] = $the_disp_mode[2] . $the_disp_mode[3];
+ $do_display['sort_lnk'] = (string) $the_disp_mode[4];
+ $do_display['nav_bar'] = (string) $the_disp_mode[5];
+ $do_display['ins_row'] = (string) $the_disp_mode[6];
+ $do_display['bkm_form'] = (string) $the_disp_mode[7];
+ $do_display['text_btn'] = (string) $the_disp_mode[8];
+ $do_display['pview_lnk'] = (string) $the_disp_mode[9];
+
+ // 2. Display mode is not "false for all elements" -> updates the
+ // display mode
+ if ($the_disp_mode != 'nnnn000000') {
+
+ if (isset($printview) && ($printview == '1')) {
+ // 2.0 Print view -> set all elements to false!
+ $do_display['edit_lnk'] = self::NO_EDIT_OR_DELETE; // no edit link
+ $do_display['del_lnk'] = self::NO_EDIT_OR_DELETE; // no delete link
+ $do_display['sort_lnk'] = (string) '0';
+ $do_display['nav_bar'] = (string) '0';
+ $do_display['ins_row'] = (string) '0';
+ $do_display['bkm_form'] = (string) '0';
+ $do_display['text_btn'] = (string) '0';
+ $do_display['pview_lnk'] = (string) '0';
+
+ } elseif ($this->__get('is_count') || $this->__get('is_analyse')
+ || $this->__get('is_maint') || $this->__get('is_explain')
+ ) {
+ // 2.1 Statement is a "SELECT COUNT", a
+ // "CHECK/ANALYZE/REPAIR/OPTIMIZE", an "EXPLAIN" one or
+ // contains a "PROC ANALYSE" part
+ $do_display['edit_lnk'] = self::NO_EDIT_OR_DELETE; // no edit link
+ $do_display['del_lnk'] = self::NO_EDIT_OR_DELETE; // no delete link
+ $do_display['sort_lnk'] = (string) '0';
+ $do_display['nav_bar'] = (string) '0';
+ $do_display['ins_row'] = (string) '0';
+ $do_display['bkm_form'] = (string) '1';
+
+ if ($this->__get('is_maint')) {
+ $do_display['text_btn'] = (string) '1';
+ } else {
+ $do_display['text_btn'] = (string) '0';
+ }
+ $do_display['pview_lnk'] = (string) '1';
+
+ } elseif ($this->__get('is_show')) {
+ // 2.2 Statement is a "SHOW..."
+ /**
+ * 2.2.1
+ * @todo defines edit/delete links depending on show statement
+ */
+ preg_match(
+ '@^SHOW[[:space:]]+(VARIABLES|(FULL[[:space:]]+)?'
+ . 'PROCESSLIST|STATUS|TABLE|GRANTS|CREATE|LOGS|DATABASES|FIELDS'
+ . ')@i',
+ $this->__get('sql_query'), $which
+ );
+ if (isset($which[1])
+ && (strpos(' ' . strtoupper($which[1]), 'PROCESSLIST') > 0)
+ ) {
+ // no edit link
+ $do_display['edit_lnk'] = self::NO_EDIT_OR_DELETE;
+ // "kill process" type edit link
+ $do_display['del_lnk'] = self::KILL_PROCESS;
+ } else {
+ // Default case -> no links
+ // no edit link
+ $do_display['edit_lnk'] = self::NO_EDIT_OR_DELETE;
+ // no delete link
+ $do_display['del_lnk'] = self::NO_EDIT_OR_DELETE;
+ }
+ // 2.2.2 Other settings
+ $do_display['sort_lnk'] = (string) '0';
+ $do_display['nav_bar'] = (string) '0';
+ $do_display['ins_row'] = (string) '0';
+ $do_display['bkm_form'] = (string) '1';
+ $do_display['text_btn'] = (string) '1';
+ $do_display['pview_lnk'] = (string) '1';
+
+ } else {
+ // 2.3 Other statements (ie "SELECT" ones) -> updates
+ // $do_display['edit_lnk'], $do_display['del_lnk'] and
+ // $do_display['text_btn'] (keeps other default values)
+ $prev_table = $fields_meta[0]->table;
+ $do_display['text_btn'] = (string) '1';
+
+ for ($i = 0; $i < $this->__get('fields_cnt'); $i++) {
+
+ $is_link = ($do_display['edit_lnk'] != self::NO_EDIT_OR_DELETE)
+ || ($do_display['del_lnk'] != self::NO_EDIT_OR_DELETE)
+ || ($do_display['sort_lnk'] != '0')
+ || ($do_display['ins_row'] != '0');
+
+ // 2.3.2 Displays edit/delete/sort/insert links?
+ if ($is_link
+ && (($fields_meta[$i]->table == '')
+ || ($fields_meta[$i]->table != $prev_table))
+ ) {
+ // don't display links
+ $do_display['edit_lnk'] = self::NO_EDIT_OR_DELETE;
+ $do_display['del_lnk'] = self::NO_EDIT_OR_DELETE;
+ /**
+ * @todo May be problematic with same field names
+ * in two joined table.
+ */
+ // $do_display['sort_lnk'] = (string) '0';
+ $do_display['ins_row'] = (string) '0';
+ if ($do_display['text_btn'] == '1') {
+ break;
+ }
+ } // end if (2.3.2)
+
+ // 2.3.3 Always display print view link
+ $do_display['pview_lnk'] = (string) '1';
+ $prev_table = $fields_meta[$i]->table;
+
+ } // end for
+ } // end if..elseif...else (2.1 -> 2.3)
+ } // end if (2)
+
+ // 3. Gets the total number of rows if it is unknown
+ if (isset($unlim_num_rows) && $unlim_num_rows != '') {
+ $the_total = $unlim_num_rows;
+ } elseif ((($do_display['nav_bar'] == '1')
+ || ($do_display['sort_lnk'] == '1'))
+ && (strlen($db) && !empty($table))
+ ) {
+ $the_total = PMA_Table::countRecords($db, $table);
+ }
+
+ // 4. If navigation bar or sorting fields names URLs should be
+ // displayed but there is only one row, change these settings to
+ // false
+ if ($do_display['nav_bar'] == '1' || $do_display['sort_lnk'] == '1') {
+
+ // - Do not display sort links if less than 2 rows.
+ // - For a VIEW we (probably) did not count the number of rows
+ // so don't test this number here, it would remove the possibility
+ // of sorting VIEW results.
+ if (isset($unlim_num_rows)
+ && ($unlim_num_rows < 2)
+ && ! PMA_Table::isView($db, $table)
+ ) {
+ // force display of navbar for vertical/horizontal display-choice.
+ // $do_display['nav_bar'] = (string) '0';
+ $do_display['sort_lnk'] = (string) '0';
+ }
+ } // end if (3)
+
+ // 5. Updates the synthetic var
+ $the_disp_mode = join('', $do_display);
+
+ return $do_display;
+
+ } // end of the 'setDisplayMode()' function
+
+
+ /**
+ * Return true if we are executing a query in the form of
+ * "SELECT * FROM <a table> ..."
+ *
+ * @param array $analyzed_sql the analyzed query
+ *
+ * @return boolean
+ *
+ * @access private
+ *
+ * @see _getTableHeaders(), _getColumnParams()
+ */
+ private function _isSelect($analyzed_sql)
+ {
+ if (!isset($analyzed_sql[0]['select_expr'])) {
+ $analyzed_sql[0]['select_expr'] = 0;
+ }
+
+ return ! ($this->__get('is_count') || $this->__get('is_export')
+ || $this->__get('is_func') || $this->__get('is_analyse'))
+ && (count($analyzed_sql[0]['select_expr']) == 0)
+ && isset($analyzed_sql[0]['queryflags']['select_from'])
+ && (count($analyzed_sql[0]['table_ref']) == 1);
+ }
+
+
+ /**
+ * Get a navigation button
+ *
+ * @param string $caption iconic caption for button
+ * @param string $title text for button
+ * @param integer $pos position for next query
+ * @param string $html_sql_query query ready for display
+ * @param string $onsubmit optional onsubmit clause
+ * @param string $input_for_real_end optional hidden field for special treatment
+ * @param string $onclick optional onclick clause
+ *
+ * @return string html content
+ *
+ * @access private
+ *
+ * @see _getMoveBackwardButtonsForTableNavigation(),
+ * _getMoveForwardButtonsForTableNavigation()
+ */
+ private function _getTableNavigationButton(
+ $caption, $title, $pos, $html_sql_query, $onsubmit = '',
+ $input_for_real_end = '', $onclick = ''
+ ) {
+
+ $caption_output = '';
+ if (PMA_Util::showIcons('TableNavigationLinksMode')) {
+ $caption_output .= $caption;
+ }
+
+ if (PMA_Util::showText('TableNavigationLinksMode')) {
+ $caption_output .= '&nbsp;' . $title;
+ }
+ $title_output = ' title="' . $title . '"';
+
+ return '<td>'
+ . '<form action="sql.php" method="post" ' . $onsubmit . '>'
+ . PMA_URL_getHiddenInputs(
+ $this->__get('db'), $this->__get('table')
+ )
+ . '<input type="hidden" name="sql_query" value="'
+ . $html_sql_query . '" />'
+ . '<input type="hidden" name="pos" value="' . $pos . '" />'
+ . '<input type="hidden" name="goto" value="' . $this->__get('goto')
+ . '" />'
+ . $input_for_real_end
+ . '<input type="submit" name="navig"'
+ . ' class="ajax" '
+ . 'value="' . $caption_output . '" ' . $title_output . $onclick . ' />'
+ . '</form>'
+ . '</td>';
+
+ } // end function _getTableNavigationButton()
+
+
+ /**
+ * Get a navigation bar to browse among the results of a SQL query
+ *
+ * @param integer $pos_next the offset for the "next" page
+ * @param integer $pos_prev the offset for the "previous" page
+ * @param string $id_for_direction_dropdown the id for the direction dropdown
+ * @param boolean $is_innodb whether its InnoDB or not
+ *
+ * @return string html content
+ *
+ * @access private
+ *
+ * @see _getTable()
+ */
+ private function _getTableNavigation(
+ $pos_next, $pos_prev, $id_for_direction_dropdown, $is_innodb
+ ) {
+
+ $table_navigation_html = '';
+ $showtable = $this->__get('showtable'); // To use in isset
+
+ // here, using htmlentities() would cause problems if the query
+ // contains accented characters
+ $html_sql_query = htmlspecialchars($this->__get('sql_query'));
+
+ /**
+ * @todo move this to a central place
+ * @todo for other future table types
+ */
+ $is_innodb = (isset($showtable['Type'])
+ && $showtable['Type'] == self::TABLE_TYPE_INNO_DB);
+
+ // Navigation bar
+ $table_navigation_html .= '<table class="navigation nospacing nopadding">'
+ . '<tr>'
+ . '<td class="navigation_separator"></td>';
+
+ // Move to the beginning or to the previous page
+ if ($_SESSION['tmpval']['pos']
+ && ($_SESSION['tmpval']['max_rows'] != self::ALL_ROWS)
+ ) {
+
+ $table_navigation_html
+ .= $this->_getMoveBackwardButtonsForTableNavigation(
+ $html_sql_query, $pos_prev
+ );
+
+ } // end move back
+
+ $nbTotalPage = 1;
+ //page redirection
+ // (unless we are showing all records)
+ if ($_SESSION['tmpval']['max_rows'] != self::ALL_ROWS) { //if1
+
+ $pageNow = @floor(
+ $_SESSION['tmpval']['pos']
+ / $_SESSION['tmpval']['max_rows']
+ ) + 1;
+
+ $nbTotalPage = @ceil(
+ $this->__get('unlim_num_rows')
+ / $_SESSION['tmpval']['max_rows']
+ );
+
+ if ($nbTotalPage > 1) { //if2
+
+ $table_navigation_html .= '<td>';
+ $_url_params = array(
+ 'db' => $this->__get('db'),
+ 'table' => $this->__get('table'),
+ 'sql_query' => $this->__get('sql_query'),
+ 'goto' => $this->__get('goto'),
+ );
+
+ //<form> to keep the form alignment of button < and <<
+ // and also to know what to execute when the selector changes
+ $table_navigation_html .= '<form action="sql.php'
+ . PMA_URL_getCommon($_url_params)
+ . '" method="post">';
+
+ $table_navigation_html .= PMA_Util::pageselector(
+ 'pos',
+ $_SESSION['tmpval']['max_rows'],
+ $pageNow, $nbTotalPage, 200, 5, 5, 20, 10
+ );
+
+ $table_navigation_html .= '</form>'
+ . '</td>';
+ } //_if2
+ } //_if1
+
+ // Display the "Show all" button if allowed
+ if (($this->__get('num_rows') < $this->__get('unlim_num_rows'))
+ && ($GLOBALS['cfg']['ShowAll']
+ || ($this->__get('unlim_num_rows') <= 500))
+ ) {
+
+ $table_navigation_html .= $this->_getShowAllButtonForTableNavigation(
+ $html_sql_query
+ );
+
+ } // end show all
+
+ // Move to the next page or to the last one
+ $endpos = $_SESSION['tmpval']['pos']
+ + $_SESSION['tmpval']['max_rows'];
+
+ if (($endpos < $this->__get('unlim_num_rows'))
+ && ($this->__get('num_rows') >= $_SESSION['tmpval']['max_rows'])
+ && ($_SESSION['tmpval']['max_rows'] != self::ALL_ROWS)
+ ) {
+
+ $table_navigation_html
+ .= $this->_getMoveForwardButtonsForTableNavigation(
+ $html_sql_query, $pos_next, $is_innodb
+ );
+
+ } // end move toward
+
+ // show separator if pagination happen
+ if ($nbTotalPage > 1) {
+ $table_navigation_html
+ .= '<td><div class="navigation_separator">|</div></td>';
+ }
+
+ $table_navigation_html .= '<td>'
+ . '<div class="save_edited hide">'
+ . '<input type="submit" value="' . __('Save edited data') . '" />'
+ . '<div class="navigation_separator">|</div>'
+ . '</div>'
+ . '</td>'
+ . '<td>'
+ . '<div class="restore_column hide">'
+ . '<input type="submit" value="' . __('Restore column order') . '" />'
+ . '<div class="navigation_separator">|</div>'
+ . '</div>'
+ . '</td>';
+
+ // if displaying a VIEW, $unlim_num_rows could be zero because
+ // of $cfg['MaxExactCountViews']; in this case, avoid passing
+ // the 5th parameter to checkFormElementInRange()
+ // (this means we can't validate the upper limit
+ $table_navigation_html .= '<td class="navigation_goto">';
+
+ $table_navigation_html .= '<form action="sql.php" method="post" '
+ . 'onsubmit="return '
+ . '(checkFormElementInRange('
+ . 'this, '
+ . '\'session_max_rows\', '
+ . '\''
+ . str_replace('\'', '\\\'', __('%d is not valid row number.'))
+ . '\', '
+ . '1)'
+ . ' &amp;&amp; '
+ . 'checkFormElementInRange('
+ . 'this, '
+ . '\'pos\', '
+ . '\''
+ . str_replace('\'', '\\\'', __('%d is not valid row number.'))
+ . '\', '
+ . '0'
+ . (($this->__get('unlim_num_rows') > 0)
+ ? ', ' . ($this->__get('unlim_num_rows') - 1)
+ : ''
+ )
+ . ')'
+ . ')'
+ .'">';
+
+ $table_navigation_html .= PMA_URL_getHiddenInputs(
+ $this->__get('db'), $this->__get('table')
+ );
+
+ $table_navigation_html .= $this->_getAdditionalFieldsForTableNavigation(
+ $html_sql_query, $id_for_direction_dropdown
+ );
+
+ $table_navigation_html .= '</form>'
+ . '</td>'
+ . '<td class="navigation_separator"></td>'
+ . '</tr>'
+ . '</table>';
+
+ return $table_navigation_html;
+
+ } // end of the '_getTableNavigation()' function
+
+
+ /**
+ * Prepare move backward buttons - previous and first
+ *
+ * @param string $html_sql_query the sql encoded by html special characters
+ * @param integer $pos_prev the offset for the "previous" page
+ *
+ * @return string html content
+ *
+ * @access private
+ *
+ * @see _getTableNavigation()
+ */
+ private function _getMoveBackwardButtonsForTableNavigation(
+ $html_sql_query, $pos_prev
+ ) {
+ return $this->_getTableNavigationButton(
+ '&lt;&lt;', _pgettext('First page', 'Begin'), 0, $html_sql_query
+ )
+ . $this->_getTableNavigationButton(
+ '&lt;', _pgettext('Previous page', 'Previous'), $pos_prev,
+ $html_sql_query
+ );
+ } // end of the '_getMoveBackwardButtonsForTableNavigation()' function
+
+
+ /**
+ * Prepare Show All button for table navigation
+ *
+ * @param string $html_sql_query the sql encoded by html special characters
+ *
+ * @return string html content
+ *
+ * @access private
+ *
+ * @see _getTableNavigation()
+ */
+ private function _getShowAllButtonForTableNavigation($html_sql_query)
+ {
+ return "\n"
+ . '<td>'
+ . '<form action="sql.php" method="post">'
+ . PMA_URL_getHiddenInputs(
+ $this->__get('db'), $this->__get('table')
+ )
+ . '<input type="hidden" name="sql_query" value="'
+ . $html_sql_query . '" />'
+ . '<input type="hidden" name="pos" value="0" />'
+ . '<input type="hidden" name="session_max_rows" value="all" />'
+ . '<input type="hidden" name="goto" value="' . $this->__get('goto')
+ . '" />'
+ . '<input type="submit" name="navig" value="' . __('Show all') . '" />'
+ . '</form>'
+ . '</td>';
+ } // end of the '_getShowAllButtonForTableNavigation()' function
+
+
+ /**
+ * Prepare move forward buttons - next and last
+ *
+ * @param string $html_sql_query the sql encoded by htmlspecialchars()
+ * @param integer $pos_next the offset for the "next" page
+ * @param boolean $is_innodb whether it's InnoDB or not
+ *
+ * @return string $buttons_html html content
+ *
+ * @access private
+ *
+ * @see _getTableNavigation()
+ */
+ private function _getMoveForwardButtonsForTableNavigation(
+ $html_sql_query, $pos_next, $is_innodb
+ ) {
+
+ // display the Next button
+ $buttons_html = $this->_getTableNavigationButton(
+ '&gt;',
+ _pgettext('Next page', 'Next'),
+ $pos_next,
+ $html_sql_query
+ );
+
+ // prepare some options for the End button
+ if ($is_innodb
+ && $this->__get('unlim_num_rows') > $GLOBALS['cfg']['MaxExactCount']
+ ) {
+ $input_for_real_end = '<input id="real_end_input" type="hidden" '
+ . 'name="find_real_end" value="1" />';
+ // no backquote around this message
+ $onclick = '';
+ } else {
+ $input_for_real_end = $onclick = '';
+ }
+
+ $maxRows = $_SESSION['tmpval']['max_rows'];
+ $onsubmit = 'onsubmit="return '
+ . ($_SESSION['tmpval']['pos']
+ + $maxRows
+ < $this->__get('unlim_num_rows')
+ && $this->__get('num_rows') >= $maxRows)
+ ? 'true'
+ : 'false' . '"';
+
+ // display the End button
+ $buttons_html .= $this->_getTableNavigationButton(
+ '&gt;&gt;',
+ _pgettext('Last page', 'End'),
+ @((ceil(
+ $this->__get('unlim_num_rows')
+ / $_SESSION['tmpval']['max_rows']
+ )- 1) * $maxRows),
+ $html_sql_query, $onsubmit, $input_for_real_end, $onclick
+ );
+
+ return $buttons_html;
+
+ } // end of the '_getMoveForwardButtonsForTableNavigation()' function
+
+
+ /**
+ * Prepare fields for table navigation
+ * Number of rows
+ *
+ * @param string $html_sql_query the sql encoded by htmlspecialchars()
+ * @param string $id_for_direction_dropdown the id for the direction dropdown
+ *
+ * @return string $additional_fields_html html content
+ *
+ * @access private
+ *
+ * @see _getTableNavigation()
+ */
+ private function _getAdditionalFieldsForTableNavigation(
+ $html_sql_query, $id_for_direction_dropdown
+ ) {
+
+ $additional_fields_html = '';
+
+ $additional_fields_html .= '<input type="hidden" name="sql_query" '
+ . 'value="' . $html_sql_query . '" />'
+ . '<input type="hidden" name="goto" value="' . $this->__get('goto')
+ . '" />'
+ . '<input type="hidden" name="pos" size="3" value="'
+ // Do not change the position when changing the number of rows
+ . $_SESSION['tmpval']['pos'] . '" />';
+
+ $numberOfRowsChoices = array(
+ '25' => 25,
+ '50' => 50,
+ '100' => 100,
+ '250' => 250,
+ '500' => 500
+ );
+ $additional_fields_html .= __('Number of rows:') . ' ';
+ $additional_fields_html .= PMA_Util::getDropdown(
+ 'session_max_rows', $numberOfRowsChoices,
+ $_SESSION['tmpval']['max_rows'], '', 'autosubmit'
+ );
+
+ if ($GLOBALS['cfg']['ShowDisplayDirection']) {
+ // Display mode (horizontal/vertical)
+ $additional_fields_html .= __('Mode:') . ' ' . "\n";
+ $choices = array(
+ 'horizontal' => __('horizontal'),
+ 'horizontalflipped' => __('horizontal (rotated headers)'),
+ 'vertical' => __('vertical')
+ );
+
+ $additional_fields_html .= PMA_Util::getDropdown(
+ 'disp_direction', $choices,
+ $_SESSION['tmpval']['disp_direction'],
+ $id_for_direction_dropdown, 'autosubmit'
+ );
+ unset($choices);
+ }
+
+ return $additional_fields_html;
+
+ } // end of the '_getAdditionalFieldsForTableNavigation()' function
+
+
+ /**
+ * Get the headers of the results table
+ *
+ * @param array &$is_display which elements to display
+ * @param array|string $analyzed_sql the analyzed query
+ * @param string $sort_expression sort expression
+ * @param string $sort_expression_nodirection sort expression
+ * without direction
+ * @param string $sort_direction sort direction
+ * @param boolean $is_limited_display with limited operations
+ * or not
+ *
+ * @return string html content
+ *
+ * @access private
+ *
+ * @see getTable()
+ */
+ private function _getTableHeaders(
+ &$is_display, $analyzed_sql = '',
+ $sort_expression = '', $sort_expression_nodirection = '',
+ $sort_direction = '', $is_limited_display = false
+ ) {
+
+ $table_headers_html = '';
+ // Following variable are needed for use in isset/empty or
+ // use with array indexes/safe use in foreach
+ $fields_meta = $this->__get('fields_meta');
+ $highlight_columns = $this->__get('highlight_columns');
+ $printview = $this->__get('printview');
+ $vertical_display = $this->__get('vertical_display');
+
+ // required to generate sort links that will remember whether the
+ // "Show all" button has been clicked
+ $sql_md5 = md5($this->__get('sql_query'));
+ $session_max_rows = $is_limited_display
+ ? 0
+ : $_SESSION['tmpval']['query'][$sql_md5]['max_rows'];
+
+ $direction = isset($_SESSION['tmpval']['disp_direction'])
+ ? $_SESSION['tmpval']['disp_direction']
+ : '';
+
+ if ($analyzed_sql == '') {
+ $analyzed_sql = array();
+ }
+
+ $directionCondition = ($direction == self::DISP_DIR_HORIZONTAL)
+ || ($direction == self::DISP_DIR_HORIZONTAL_FLIPPED);
+
+ // can the result be sorted?
+ if ($is_display['sort_lnk'] == '1') {
+
+ list($unsorted_sql_query, $drop_down_html)
+ = $this->_getUnsortedSqlAndSortByKeyDropDown(
+ $analyzed_sql, $sort_expression
+ );
+
+ $table_headers_html .= $drop_down_html;
+
+ }
+
+ // Output data needed for grid editing
+ $table_headers_html .= '<input id="save_cells_at_once" type="hidden" value="'
+ . $GLOBALS['cfg']['SaveCellsAtOnce'] . '" />'
+ . '<div class="common_hidden_inputs">'
+ . PMA_URL_getHiddenInputs(
+ $this->__get('db'), $this->__get('table')
+ )
+ . '</div>';
+
+ // Output data needed for column reordering and show/hide column
+ if ($this->_isSelect($analyzed_sql)) {
+ $table_headers_html .= $this->_getDataForResettingColumnOrder();
+ }
+
+ $vertical_display['emptypre'] = 0;
+ $vertical_display['emptyafter'] = 0;
+ $vertical_display['textbtn'] = '';
+ $full_or_partial_text_link = null;
+
+ $this->__set('vertical_display', $vertical_display);
+
+ // Display options (if we are not in print view)
+ if (! (isset($printview) && ($printview == '1')) && ! $is_limited_display) {
+
+ $table_headers_html .= $this->_getOptionsBlock();
+
+ // prepare full/partial text button or link
+ $full_or_partial_text_link = $this->_getFullOrPartialTextButtonOrLink();
+ }
+
+ // Start of form for multi-rows edit/delete/export
+ $table_headers_html .= $this->_getFormForMultiRowOperations(
+ $is_display['del_lnk']
+ );
+
+ // 1. Set $colspan or $rowspan and generate html with full/partial
+ // text button or link
+ list($colspan, $rowspan, $button_html)
+ = $this->_getFieldVisibilityParams(
+ $directionCondition, $is_display, $full_or_partial_text_link
+ );
+
+ $table_headers_html .= $button_html;
+
+ // 2. Displays the fields' name
+ // 2.0 If sorting links should be used, checks if the query is a "JOIN"
+ // statement (see 2.1.3)
+
+ // 2.0.1 Prepare Display column comments if enabled
+ // ($GLOBALS['cfg']['ShowBrowseComments']).
+ // Do not show comments, if using horizontalflipped mode,
+ // because of space usage
+ $comments_map = $this->_getTableCommentsArray($direction, $analyzed_sql);
+
+ if ($GLOBALS['cfgRelation']['commwork']
+ && $GLOBALS['cfgRelation']['mimework']
+ && $GLOBALS['cfg']['BrowseMIME']
+ && ! $_SESSION['tmpval']['hide_transformation']
+ ) {
+ include_once './libraries/transformations.lib.php';
+ $this->__set(
+ 'mime_map',
+ PMA_getMIME($this->__get('db'), $this->__get('table'))
+ );
+ }
+
+ // See if we have to highlight any header fields of a WHERE query.
+ // Uses SQL-Parser results.
+ $this->_setHighlightedColumnGlobalField($analyzed_sql);
+
+ list($col_order, $col_visib) = $this->_getColumnParams($analyzed_sql);
+
+ for ($j = 0; $j < $this->__get('fields_cnt'); $j++) {
+
+ // assign $i with appropriate column order
+ $i = $col_order ? $col_order[$j] : $j;
+
+ // See if this column should get highlight because it's used in the
+ // where-query.
+ $condition_field = (isset($highlight_columns[$fields_meta[$i]->name])
+ || isset(
+ $highlight_columns[PMA_Util::backquote($fields_meta[$i]->name)])
+ )
+ ? true
+ : false;
+
+ // 2.0 Prepare comment-HTML-wrappers for each row, if defined/enabled.
+ $comments = $this->_getCommentForRow($comments_map, $fields_meta[$i]);
+
+ $vertical_display = $this->__get('vertical_display');
+
+ if (($is_display['sort_lnk'] == '1') && ! $is_limited_display) {
+
+ list($order_link, $sorted_header_html)
+ = $this->_getOrderLinkAndSortedHeaderHtml(
+ $fields_meta[$i], $sort_expression,
+ $sort_expression_nodirection, $i, $unsorted_sql_query,
+ $session_max_rows, $direction, $comments,
+ $sort_direction, $directionCondition, $col_visib,
+ $col_visib[$j], $condition_field
+ );
+
+ $table_headers_html .= $sorted_header_html;
+
+ $vertical_display['desc'][] = ' <th '
+ . 'class="draggable'
+ . ($condition_field ? ' condition' : '')
+ . '" data-column="' . htmlspecialchars($fields_meta[$i]->name)
+ . '">' . "\n" . $order_link . $comments . ' </th>' . "\n";
+ } else {
+ // 2.2 Results can't be sorted
+
+ if ($directionCondition) {
+ $table_headers_html
+ .= $this->_getDraggableClassForNonSortableColumns(
+ $col_visib, $col_visib[$j], $condition_field,
+ $direction, $fields_meta[$i], $comments
+ );
+ }
+
+ $vertical_display['desc'][] = ' <th '
+ . 'class="draggable'
+ . ($condition_field ? ' condition"' : '')
+ . '" data-column="' . htmlspecialchars($fields_meta[$i]->name)
+ . '">' . "\n" . ' '
+ . htmlspecialchars($fields_meta[$i]->name)
+ . "\n" . $comments . ' </th>';
+ } // end else (2.2)
+
+ $this->__set('vertical_display', $vertical_display);
+
+ } // end for
+
+ // Display column at rightside - checkboxes or empty column
+ if (! $printview) {
+ $table_headers_html .= $this->_getColumnAtRightSide(
+ $is_display, $directionCondition, $full_or_partial_text_link,
+ $colspan, $rowspan
+ );
+ }
+
+ if ($directionCondition) {
+ $table_headers_html .= '</tr>'
+ . '</thead>';
+ }
+
+ return $table_headers_html;
+
+ } // end of the '_getTableHeaders()' function
+
+
+ /**
+ * Prepare unsorted sql query and sort by key drop down
+ *
+ * @param array $analyzed_sql the analyzed query
+ * @param string $sort_expression sort expression
+ *
+ * @return array two element array - $unsorted_sql_query, $drop_down_html
+ *
+ * @access private
+ *
+ * @see _getTableHeaders()
+ */
+ private function _getUnsortedSqlAndSortByKeyDropDown(
+ $analyzed_sql, $sort_expression
+ ) {
+
+ $drop_down_html = '';
+
+ // Just as fallback
+ $unsorted_sql_query = $this->__get('sql_query');
+ if (isset($analyzed_sql[0]['unsorted_query'])) {
+ $unsorted_sql_query = $analyzed_sql[0]['unsorted_query'];
+ }
+ // Handles the case of multiple clicks on a column's header
+ // which would add many spaces before "ORDER BY" in the
+ // generated query.
+ $unsorted_sql_query = trim($unsorted_sql_query);
+
+ // sorting by indexes, only if it makes sense (only one table ref)
+ if (isset($analyzed_sql)
+ && isset($analyzed_sql[0])
+ && isset($analyzed_sql[0]['querytype'])
+ && ($analyzed_sql[0]['querytype'] == self::QUERY_TYPE_SELECT)
+ && isset($analyzed_sql[0]['table_ref'])
+ && (count($analyzed_sql[0]['table_ref']) == 1)
+ ) {
+ // grab indexes data:
+ $indexes = PMA_Index::getFromTable(
+ $this->__get('table'),
+ $this->__get('db')
+ );
+
+ // do we have any index?
+ if ($indexes) {
+ $drop_down_html = $this->_getSortByKeyDropDown(
+ $indexes, $sort_expression,
+ $unsorted_sql_query
+ );
+ }
+ }
+
+ return array($unsorted_sql_query, $drop_down_html);
+
+ } // end of the '_getUnsortedSqlAndSortByKeyDropDown()' function
+
+
+ /**
+ * Prepare sort by key dropdown - html code segment
+ *
+ * @param array $indexes the indexes of the table for sort criteria
+ * @param string $sort_expression the sort expression
+ * @param string $unsorted_sql_query the unsorted sql query
+ *
+ * @return string $drop_down_html html content
+ *
+ * @access private
+ *
+ * @see _getTableHeaders()
+ */
+ private function _getSortByKeyDropDown(
+ $indexes, $sort_expression, $unsorted_sql_query
+ ) {
+
+ $drop_down_html = '';
+
+ $drop_down_html .= '<form action="sql.php" method="post">' . "\n"
+ . PMA_URL_getHiddenInputs(
+ $this->__get('db'), $this->__get('table')
+ )
+ . __('Sort by key')
+ . ': <select name="sql_query" class="autosubmit">' . "\n";
+
+ $used_index = false;
+ $local_order = (isset($sort_expression) ? $sort_expression : '');
+
+ foreach ($indexes as $index) {
+
+ $asc_sort = '`'
+ . implode('` ASC, `', array_keys($index->getColumns()))
+ . '` ASC';
+
+ $desc_sort = '`'
+ . implode('` DESC, `', array_keys($index->getColumns()))
+ . '` DESC';
+
+ $used_index = $used_index
+ || ($local_order == $asc_sort)
+ || ($local_order == $desc_sort);
+
+ if (preg_match(
+ '@(.*)([[:space:]](LIMIT (.*)|PROCEDURE (.*)|'
+ . 'FOR UPDATE|LOCK IN SHARE MODE))@is',
+ $unsorted_sql_query, $my_reg
+ )) {
+ $unsorted_sql_query_first_part = $my_reg[1];
+ $unsorted_sql_query_second_part = $my_reg[2];
+ } else {
+ $unsorted_sql_query_first_part = $unsorted_sql_query;
+ $unsorted_sql_query_second_part = '';
+ }
+
+ $drop_down_html .= '<option value="'
+ . htmlspecialchars(
+ $unsorted_sql_query_first_part . "\n"
+ . ' ORDER BY ' . $asc_sort
+ . $unsorted_sql_query_second_part
+ )
+ . '"' . ($local_order == $asc_sort
+ ? ' selected="selected"'
+ : '')
+ . '>' . htmlspecialchars($index->getName()) . ' ('
+ . __('Ascending') . ')</option>';
+
+ $drop_down_html .= '<option value="'
+ . htmlspecialchars(
+ $unsorted_sql_query_first_part . "\n"
+ . ' ORDER BY ' . $desc_sort
+ . $unsorted_sql_query_second_part
+ )
+ . '"' . ($local_order == $desc_sort
+ ? ' selected="selected"'
+ : '')
+ . '>' . htmlspecialchars($index->getName()) . ' ('
+ . __('Descending') . ')</option>';
+ }
+
+ $drop_down_html .= '<option value="' . htmlspecialchars($unsorted_sql_query)
+ . '"' . ($used_index ? '' : ' selected="selected"') . '>' . __('None')
+ . '</option>'
+ . '</select>' . "\n"
+ . '</form>' . "\n";
+
+ return $drop_down_html;
+
+ } // end of the '_getSortByKeyDropDown()' function
+
+
+ /**
+ * Set column span, row span and prepare html with full/partial
+ * text button or link
+ *
+ * @param boolean $directionCondition display direction horizontal or
+ * horizontalflipped
+ * @param array &$is_display which elements to display
+ * @param string $full_or_partial_text_link full/partial link or text button
+ *
+ * @return array 3 element array - $colspan, $rowspan, $button_html
+ *
+ * @access private
+ *
+ * @see _getTableHeaders()
+ */
+ private function _getFieldVisibilityParams(
+ $directionCondition, &$is_display, $full_or_partial_text_link
+ ) {
+
+ $button_html = '';
+ $colspan = $rowspan = null;
+ $vertical_display = $this->__get('vertical_display');
+
+ // 1. Displays the full/partial text button (part 1)...
+ if ($directionCondition) {
+
+ $button_html .= '<thead><tr>' . "\n";
+
+ $colspan = (($is_display['edit_lnk'] != self::NO_EDIT_OR_DELETE)
+ && ($is_display['del_lnk'] != self::NO_EDIT_OR_DELETE))
+ ? ' colspan="4"'
+ : '';
+
+ } else {
+ $rowspan = (($is_display['edit_lnk'] != self::NO_EDIT_OR_DELETE)
+ && ($is_display['del_lnk'] != self::NO_EDIT_OR_DELETE))
+ ? ' rowspan="4"'
+ : '';
+ }
+
+ // ... before the result table
+ if ((($is_display['edit_lnk'] == self::NO_EDIT_OR_DELETE)
+ && ($is_display['del_lnk'] == self::NO_EDIT_OR_DELETE))
+ && ($is_display['text_btn'] == '1')
+ ) {
+
+ $vertical_display['emptypre']
+ = (($is_display['edit_lnk'] != self::NO_EDIT_OR_DELETE)
+ && ($is_display['del_lnk'] != self::NO_EDIT_OR_DELETE)) ? 4 : 0;
+
+ if ($directionCondition) {
+
+ $button_html .= '<th colspan="' . $this->__get('fields_cnt') . '">'
+ . '</th>'
+ . '</tr>'
+ . '<tr>';
+
+ // end horizontal/horizontalflipped mode
+ } else {
+
+ $span = $this->__get('num_rows') + 1 + floor(
+ $this->__get('num_rows')
+ / $_SESSION['tmpval']['repeat_cells']
+ );
+ $button_html .= '<tr><th colspan="' . $span . '"></th></tr>';
+
+ } // end vertical mode
+
+ } elseif ((($GLOBALS['cfg']['RowActionLinks'] == self::POSITION_LEFT)
+ || ($GLOBALS['cfg']['RowActionLinks'] == self::POSITION_BOTH))
+ && ($is_display['text_btn'] == '1')
+ ) {
+ // ... at the left column of the result table header if possible
+ // and required
+
+ $vertical_display['emptypre']
+ = (($is_display['edit_lnk'] != self::NO_EDIT_OR_DELETE)
+ && ($is_display['del_lnk'] != self::NO_EDIT_OR_DELETE)) ? 4 : 0;
+
+ if ($directionCondition) {
+
+ $button_html .= '<th ' . $colspan . '>'
+ . $full_or_partial_text_link . '</th>';
+ // end horizontal/horizontalflipped mode
+
+ } else {
+
+ $vertical_display['textbtn']
+ = ' <th ' . $rowspan . ' class="vmiddle">' . "\n"
+ . ' ' . "\n"
+ . ' </th>' . "\n";
+ } // end vertical mode
+
+ } elseif ((($GLOBALS['cfg']['RowActionLinks'] == self::POSITION_LEFT)
+ || ($GLOBALS['cfg']['RowActionLinks'] == self::POSITION_BOTH))
+ && (($is_display['edit_lnk'] != self::NO_EDIT_OR_DELETE)
+ || ($is_display['del_lnk'] != self::NO_EDIT_OR_DELETE))
+ ) {
+ // ... elseif no button, displays empty(ies) col(s) if required
+
+ $vertical_display['emptypre']
+ = (($is_display['edit_lnk'] != self::NO_EDIT_OR_DELETE)
+ && ($is_display['del_lnk'] != self::NO_EDIT_OR_DELETE)) ? 4 : 0;
+
+ if ($directionCondition) {
+
+ $button_html .= '<td ' . $colspan . '></td>';
+
+ // end horizontal/horizontalfipped mode
+ } else {
+ $vertical_display['textbtn'] = ' <td' . $rowspan .
+ '></td>' . "\n";
+ } // end vertical mode
+
+ } elseif (($GLOBALS['cfg']['RowActionLinks'] == self::POSITION_NONE)
+ && ($directionCondition)
+ ) {
+ // ... elseif display an empty column if the actions links are
+ // disabled to match the rest of the table
+ $button_html .= '<th></th>';
+ }
+
+ $this->__set('vertical_display', $vertical_display);
+
+ return array($colspan, $rowspan, $button_html);
+
+ } // end of the '_getFieldVisibilityParams()' function
+
+
+ /**
+ * Get table comments as array
+ *
+ * @param boolean $direction display direction, horizontal
+ * or horizontalflipped
+ * @param array $analyzed_sql the analyzed query
+ *
+ * @return array $comments_map table comments when condition true
+ * null when condition falls
+ *
+ * @access private
+ *
+ * @see _getTableHeaders()
+ */
+ private function _getTableCommentsArray($direction, $analyzed_sql)
+ {
+
+ $comments_map = null;
+
+ if ($GLOBALS['cfg']['ShowBrowseComments']
+ && ($direction != self::DISP_DIR_HORIZONTAL_FLIPPED)
+ ) {
+ $comments_map = array();
+ if (isset($analyzed_sql[0])
+ && is_array($analyzed_sql[0])
+ && isset($analyzed_sql[0]['table_ref'])
+ ) {
+ foreach ($analyzed_sql[0]['table_ref'] as $tbl) {
+ $tb = $tbl['table_true_name'];
+ $comments_map[$tb] = PMA_getComments($this->__get('db'), $tb);
+ unset($tb);
+ }
+ }
+ }
+
+ return $comments_map;
+
+ } // end of the '_getTableCommentsArray()' function
+
+
+ /**
+ * Set global array for store highlighted header fields
+ *
+ * @param array $analyzed_sql the analyzed query
+ *
+ * @return void
+ *
+ * @access private
+ *
+ * @see _getTableHeaders()
+ */
+ private function _setHighlightedColumnGlobalField($analyzed_sql)
+ {
+
+ $highlight_columns = array();
+ if (isset($analyzed_sql) && isset($analyzed_sql[0])
+ && isset($analyzed_sql[0]['where_clause_identifiers'])
+ && is_array($analyzed_sql[0]['where_clause_identifiers'])
+ ) {
+ foreach ($analyzed_sql[0]['where_clause_identifiers'] as $wci) {
+ $highlight_columns[$wci] = 'true';
+ }
+ }
+
+ $this->__set('highlight_columns', $highlight_columns);
+
+ } // end of the '_setHighlightedColumnGlobalField()' function
+
+
+ /**
+ * Prepare data for column restoring and show/hide
+ *
+ * @return string $data_html html content
+ *
+ * @access private
+ *
+ * @see _getTableHeaders()
+ */
+ private function _getDataForResettingColumnOrder()
+ {
+
+ $data_html = '';
+
+ // generate the column order, if it is set
+ $pmatable = new PMA_Table($this->__get('table'), $this->__get('db'));
+ $col_order = $pmatable->getUiProp(PMA_Table::PROP_COLUMN_ORDER);
+
+ if ($col_order) {
+ $data_html .= '<input id="col_order" type="hidden" value="'
+ . implode(',', $col_order) . '" />';
+ }
+
+ $col_visib = $pmatable->getUiProp(PMA_Table::PROP_COLUMN_VISIB);
+
+ if ($col_visib) {
+ $data_html .= '<input id="col_visib" type="hidden" value="'
+ . implode(',', $col_visib) . '" />';
+ }
+
+ // generate table create time
+ if (! PMA_Table::isView($this->__get('db'), $this->__get('table'))) {
+ $data_html .= '<input id="table_create_time" type="hidden" value="'
+ . PMA_Table::sGetStatusInfo(
+ $this->__get('db'), $this->__get('table'), 'Create_time'
+ ) . '" />';
+ }
+
+ return $data_html;
+
+ } // end of the '_getDataForResettingColumnOrder()' function
+
+
+ /**
+ * Prepare option fields block
+ *
+ * @return string $options_html html content
+ *
+ * @access private
+ *
+ * @see _getTableHeaders()
+ */
+ private function _getOptionsBlock()
+ {
+
+ $options_html = '';
+
+ $options_html .= '<form method="post" action="sql.php" '
+ . 'name="displayOptionsForm" '
+ . 'id="displayOptionsForm"';
+
+ $options_html .= ' class="ajax" ';
+
+ $options_html .= '>';
+ $url_params = array(
+ 'db' => $this->__get('db'),
+ 'table' => $this->__get('table'),
+ 'sql_query' => $this->__get('sql_query'),
+ 'goto' => $this->__get('goto'),
+ 'display_options_form' => 1
+ );
+
+ $options_html .= PMA_URL_getHiddenInputs($url_params)
+ . '<br />'
+ . PMA_Util::getDivForSliderEffect(
+ 'displayoptions', __('Options')
+ )
+ . '<fieldset>';
+
+ $options_html .= '<div class="formelement">';
+ $choices = array(
+ 'P' => __('Partial texts'),
+ 'F' => __('Full texts')
+ );
+
+ // pftext means "partial or full texts" (done to reduce line lengths)
+ $options_html .= PMA_Util::getRadioFields(
+ 'pftext', $choices,
+ $_SESSION['tmpval']['pftext']
+ )
+ . '</div>';
+
+ if ($GLOBALS['cfgRelation']['relwork']
+ && $GLOBALS['cfgRelation']['displaywork']
+ ) {
+ $options_html .= '<div class="formelement">';
+ $choices = array(
+ 'K' => __('Relational key'),
+ 'D' => __('Relational display column')
+ );
+
+ $options_html .= PMA_Util::getRadioFields(
+ 'relational_display', $choices,
+ $_SESSION['tmpval']['relational_display']
+ )
+ . '</div>';
+ }
+
+ $options_html .= '<div class="formelement">'
+ . PMA_Util::getCheckbox(
+ 'display_binary', __('Show binary contents'),
+ ! empty($_SESSION['tmpval']['display_binary']), false
+ )
+ . '<br />'
+ . PMA_Util::getCheckbox(
+ 'display_blob', __('Show BLOB contents'),
+ ! empty($_SESSION['tmpval']['display_blob']), false
+ )
+ . '<br />'
+ . PMA_Util::getCheckbox(
+ 'display_binary_as_hex', __('Show binary contents as HEX'),
+ ! empty($_SESSION['tmpval']['display_binary_as_hex']), false
+ )
+ . '</div>';
+
+ // I would have preferred to name this "display_transformation".
+ // This is the only way I found to be able to keep this setting sticky
+ // per SQL query, and at the same time have a default that displays
+ // the transformations.
+ $options_html .= '<div class="formelement">'
+ . PMA_Util::getCheckbox(
+ 'hide_transformation', __('Hide browser transformation'),
+ ! empty($_SESSION['tmpval']['hide_transformation']), false
+ )
+ . '</div>';
+
+ if (! PMA_DRIZZLE) {
+ $options_html .= '<div class="formelement">';
+ $choices = array(
+ 'GEOM' => __('Geometry'),
+ 'WKT' => __('Well Known Text'),
+ 'WKB' => __('Well Known Binary')
+ );
+
+ $options_html .= PMA_Util::getRadioFields(
+ 'geoOption', $choices,
+ $_SESSION['tmpval']['geoOption']
+ )
+ . '</div>';
+ }
+
+ $options_html .= '<div class="clearfloat"></div>'
+ . '</fieldset>';
+
+ $options_html .= '<fieldset class="tblFooters">'
+ . '<input type="submit" value="' . __('Go') . '" />'
+ . '</fieldset>'
+ . '</div>'
+ . '</form>';
+
+ return $options_html;
+
+ } // end of the '_getOptionsBlock()' function
+
+
+ /**
+ * Get full/partial text button or link
+ *
+ * @return string html content
+ *
+ * @access private
+ *
+ * @see _getTableHeaders()
+ */
+ private function _getFullOrPartialTextButtonOrLink()
+ {
+
+ $url_params_full_text = array(
+ 'db' => $this->__get('db'),
+ 'table' => $this->__get('table'),
+ 'sql_query' => $this->__get('sql_query'),
+ 'goto' => $this->__get('goto'),
+ 'full_text_button' => 1
+ );
+
+ if ($_SESSION['tmpval']['pftext'] == self::DISPLAY_FULL_TEXT) {
+ // currently in fulltext mode so show the opposite link
+ $tmp_image_file = $this->__get('pma_theme_image') . 's_partialtext.png';
+ $tmp_txt = __('Partial texts');
+ $url_params_full_text['pftext'] = self::DISPLAY_PARTIAL_TEXT;
+ } else {
+ $tmp_image_file = $this->__get('pma_theme_image') . 's_fulltext.png';
+ $tmp_txt = __('Full texts');
+ $url_params_full_text['pftext'] = self::DISPLAY_FULL_TEXT;
+ }
+
+ $tmp_image = '<img class="fulltext" src="' . $tmp_image_file . '" alt="'
+ . $tmp_txt . '" title="' . $tmp_txt . '" />';
+ $tmp_url = 'sql.php' . PMA_URL_getCommon($url_params_full_text);
+
+ return PMA_Util::linkOrButton(
+ $tmp_url, $tmp_image, array(), false
+ );
+
+ } // end of the '_getFullOrPartialTextButtonOrLink()' function
+
+
+ /**
+ * Prepare html form for multi row operations
+ *
+ * @param string $del_lnk the delete link of current row
+ *
+ * @return string $form_html html content
+ *
+ * @access private
+ *
+ * @see _getTableHeaders()
+ */
+ private function _getFormForMultiRowOperations($del_lnk)
+ {
+
+ $form_html = '';
+
+ if (($del_lnk == self::DELETE_ROW) || ($del_lnk == self::KILL_PROCESS)) {
+
+ $form_html .= '<form method="post" action="tbl_row_action.php" '
+ . 'name="resultsForm" id="resultsForm"';
+
+ $form_html .= ' class="ajax" ';
+
+ $form_html .= '>'
+ . PMA_URL_getHiddenInputs(
+ $this->__get('db'), $this->__get('table'), 1
+ )
+ . '<input type="hidden" name="goto" value="sql.php" />';
+ }
+
+ $form_html .= '<table id="table_results" class="data';
+ $form_html .= ' ajax';
+ $form_html .= '">';
+
+ return $form_html;
+
+ } // end of the '_getFormForMultiRowOperations()' function
+
+
+ /**
+ * Get comment for row
+ *
+ * @param array $comments_map comments array
+ * @param array $fields_meta set of field properties
+ *
+ * @return string $comment html content
+ *
+ * @access private
+ *
+ * @see _getTableHeaders()
+ */
+ private function _getCommentForRow($comments_map, $fields_meta)
+ {
+ $comments = '';
+ if (isset($comments_map)
+ && isset($comments_map[$fields_meta->table])
+ && isset($comments_map[$fields_meta->table][$fields_meta->name])
+ ) {
+ $comments = '<span class="tblcomment">'
+ . htmlspecialchars(
+ $comments_map[$fields_meta->table][$fields_meta->name]
+ )
+ . '</span>';
+ }
+ return $comments;
+ } // end of the '_getCommentForRow()' function
+
+
+ /**
+ * Prepare parameters and html for sorted table header fields
+ *
+ * @param array $fields_meta set of field properties
+ * @param string $sort_expression sort expression
+ * @param string $sort_expression_nodirection sort expression without direction
+ * @param integer $column_index the index of the column
+ * @param string $unsorted_sql_query the unsorted sql query
+ * @param integer $session_max_rows maximum rows resulted by sql
+ * @param string $direction the display direction
+ * @param string $comments comment for row
+ * @param string $sort_direction sort direction
+ * @param boolean $directionCondition display direction horizontal
+ * or horizontalflipped
+ * @param boolean $col_visib column is visible(false)
+ * array column isn't visible(string array)
+ * @param string $col_visib_j element of $col_visib array
+ * @param boolean $condition_field whether the column is a part of
+ * the where clause
+ *
+ * @return array 2 element array - $order_link, $sorted_header_html
+ *
+ * @access private
+ *
+ * @see _getTableHeaders()
+ */
+ private function _getOrderLinkAndSortedHeaderHtml(
+ $fields_meta, $sort_expression, $sort_expression_nodirection,
+ $column_index, $unsorted_sql_query, $session_max_rows, $direction,
+ $comments, $sort_direction, $directionCondition, $col_visib,
+ $col_visib_j, $condition_field
+ ) {
+
+ $sorted_header_html = '';
+
+ // Checks if the table name is required; it's the case
+ // for a query with a "JOIN" statement and if the column
+ // isn't aliased, or in queries like
+ // SELECT `1`.`master_field` , `2`.`master_field`
+ // FROM `PMA_relation` AS `1` , `PMA_relation` AS `2`
+
+ $sort_tbl = (isset($fields_meta->table)
+ && strlen($fields_meta->table))
+ ? PMA_Util::backquote(
+ $fields_meta->table
+ ) . '.'
+ : '';
+
+ // Checks if the current column is used to sort the
+ // results
+ // the orgname member does not exist for all MySQL versions
+ // but if found, it's the one on which to sort
+ $name_to_use_in_sort = $fields_meta->name;
+ $is_orgname = false;
+ if (isset($fields_meta->orgname)
+ && strlen($fields_meta->orgname)
+ ) {
+ $name_to_use_in_sort = $fields_meta->orgname;
+ $is_orgname = true;
+ }
+
+ // $name_to_use_in_sort might contain a space due to
+ // formatting of function expressions like "COUNT(name )"
+ // so we remove the space in this situation
+ $name_to_use_in_sort = str_replace(' )', ')', $name_to_use_in_sort);
+
+ $is_in_sort = $this->_isInSorted(
+ $sort_expression, $sort_expression_nodirection,
+ $sort_tbl, $name_to_use_in_sort
+ );
+
+ // Check the field name for a bracket.
+ // If it contains one, it's probably a function column
+ // like 'COUNT(`field`)'
+ // It still might be a column name of a view. See bug #3383711
+ // Check is_orgname.
+ if ((strpos($name_to_use_in_sort, '(') !== false) && ! $is_orgname) {
+ $sort_order = "\n" . 'ORDER BY ' . $name_to_use_in_sort . ' ';
+ } else {
+ $sort_order = "\n" . 'ORDER BY ' . $sort_tbl
+ . PMA_Util::backquote(
+ $name_to_use_in_sort
+ ) . ' ';
+ }
+ unset($name_to_use_in_sort);
+ unset($is_orgname);
+
+ // Do define the sorting URL
+
+ list($sort_order, $order_img) = $this->_getSortingUrlParams(
+ $is_in_sort, $sort_direction, $fields_meta,
+ $sort_order, $column_index
+ );
+
+ if (preg_match(
+ '@(.*)([[:space:]](LIMIT (.*)|PROCEDURE (.*)|FOR UPDATE|'
+ . 'LOCK IN SHARE MODE))@is',
+ $unsorted_sql_query, $regs3
+ )) {
+ $sorted_sql_query = $regs3[1] . $sort_order . $regs3[2];
+ } else {
+ $sorted_sql_query = $unsorted_sql_query . $sort_order;
+ }
+
+ $_url_params = array(
+ 'db' => $this->__get('db'),
+ 'table' => $this->__get('table'),
+ 'sql_query' => $sorted_sql_query,
+ 'session_max_rows' => $session_max_rows
+ );
+ $order_url = 'sql.php' . PMA_URL_getCommon($_url_params);
+
+ // Displays the sorting URL
+ // enable sort order swapping for image
+ $order_link = $this->_getSortOrderLink(
+ $order_img, $column_index, $direction,
+ $fields_meta, $order_url
+ );
+
+ if ($directionCondition) {
+ $sorted_header_html .= $this->_getDraggableClassForSortableColumns(
+ $col_visib, $col_visib_j, $condition_field, $direction,
+ $fields_meta, $order_link, $comments
+ );
+ }
+
+ return array($order_link, $sorted_header_html);
+
+ } // end of the '_getOrderLinkAndSortedHeaderHtml()' function
+
+
+ /**
+ * Check whether the column is sorted
+ *
+ * @param string $sort_expression sort expression
+ * @param string $sort_expression_nodirection sort expression without direction
+ * @param string $sort_tbl the table name
+ * @param string $name_to_use_in_sort the sorting column name
+ *
+ * @return boolean $is_in_sort the column sorted or not
+ *
+ * @access private
+ *
+ * @see _getTableHeaders()
+ */
+ private function _isInSorted(
+ $sort_expression, $sort_expression_nodirection, $sort_tbl,
+ $name_to_use_in_sort
+ ) {
+
+ if (empty($sort_expression)) {
+ $is_in_sort = false;
+ } else {
+ // Field name may be preceded by a space, or any number
+ // of characters followed by a dot (tablename.fieldname)
+ // so do a direct comparison for the sort expression;
+ // this avoids problems with queries like
+ // "SELECT id, count(id)..." and clicking to sort
+ // on id or on count(id).
+ // Another query to test this:
+ // SELECT p.*, FROM_UNIXTIME(p.temps) FROM mytable AS p
+ // (and try clicking on each column's header twice)
+ if (! empty($sort_tbl)
+ && strpos($sort_expression_nodirection, $sort_tbl) === false
+ && strpos($sort_expression_nodirection, '(') === false
+ ) {
+ $new_sort_expression_nodirection = $sort_tbl
+ . $sort_expression_nodirection;
+ } else {
+ $new_sort_expression_nodirection = $sort_expression_nodirection;
+ }
+
+ //Back quotes are removed in next comparison, so remove them from value
+ //to compare.
+ $name_to_use_in_sort = str_replace('`', '', $name_to_use_in_sort);
+
+ $is_in_sort = false;
+ $sort_name = str_replace('`', '', $sort_tbl) . $name_to_use_in_sort;
+
+ if ($sort_name == str_replace('`', '', $new_sort_expression_nodirection)
+ || $sort_name == str_replace('`', '', $sort_expression_nodirection)
+ ) {
+ $is_in_sort = true;
+ }
+ }
+
+ return $is_in_sort;
+
+ } // end of the '_isInSorted()' function
+
+
+ /**
+ * Get sort url paramaeters - sort order and order image
+ *
+ * @param boolean $is_in_sort the column sorted or not
+ * @param string $sort_direction the sort direction
+ * @param array $fields_meta set of field properties
+ * @param string $sort_order the sorting order
+ * @param integer $column_index the index of the column
+ *
+ * @return array 2 element array - $sort_order, $order_img
+ *
+ * @access private
+ *
+ * @see _getTableHeaders()
+ */
+ private function _getSortingUrlParams(
+ $is_in_sort, $sort_direction, $fields_meta, $sort_order, $column_index
+ ) {
+
+ if (! $is_in_sort) {
+
+ // patch #455484 ("Smart" order)
+ $GLOBALS['cfg']['Order'] = strtoupper($GLOBALS['cfg']['Order']);
+
+ if ($GLOBALS['cfg']['Order'] === self::SMART_SORT_ORDER) {
+ $sort_order .= (preg_match(
+ '@time|date@i',
+ $fields_meta->type
+ )) ? self::DESCENDING_SORT_DIR : self::ASCENDING_SORT_DIR;
+ } else {
+ $sort_order .= $GLOBALS['cfg']['Order'];
+ }
+ $order_img = '';
+
+ } elseif ($sort_direction == self::DESCENDING_SORT_DIR) {
+
+ $sort_order .= ' ASC';
+ $order_img = ' ' . PMA_Util::getImage(
+ 's_desc.png', __('Descending'),
+ array('class' => "soimg$column_index", 'title' => '')
+ );
+
+ $order_img .= ' ' . PMA_Util::getImage(
+ 's_asc.png', __('Ascending'),
+ array('class' => "soimg$column_index hide", 'title' => '')
+ );
+
+ } else {
+
+ $sort_order .= ' DESC';
+ $order_img = ' ' . PMA_Util::getImage(
+ 's_asc.png', __('Ascending'),
+ array('class' => "soimg$column_index", 'title' => '')
+ );
+
+ $order_img .= ' ' . PMA_Util::getImage(
+ 's_desc.png', __('Descending'),
+ array('class' => "soimg$column_index hide", 'title' => '')
+ );
+ }
+
+ return array($sort_order, $order_img);
+
+ } // end of the '_getSortingUrlParams()' function
+
+
+ /**
+ * Get sort order link
+ *
+ * @param string $order_img the sort order image
+ * @param integer $col_index the index of the column
+ * @param string $direction the display direction
+ * @param array $fields_meta set of field properties
+ * @param string $order_url the url for sort
+ *
+ * @return string the sort order link
+ *
+ * @access private
+ *
+ * @see _getTableHeaders()
+ */
+ private function _getSortOrderLink(
+ $order_img, $col_index, $direction, $fields_meta, $order_url
+ ) {
+
+ $order_link_params = array();
+
+ if (isset($order_img) && ($order_img != '')) {
+ if (strstr($order_img, 'asc')) {
+ $order_link_params['onmouseover'] = "$('.soimg$col_index').toggle()";
+ $order_link_params['onmouseout'] = "$('.soimg$col_index').toggle()";
+ } elseif (strstr($order_img, 'desc')) {
+ $order_link_params['onmouseover'] = "$('.soimg$col_index').toggle()";
+ $order_link_params['onmouseout'] = "$('.soimg$col_index').toggle()";
+ }
+ }
+
+ if ($GLOBALS['cfg']['HeaderFlipType'] == self::HEADER_FLIP_TYPE_AUTO) {
+
+ $GLOBALS['cfg']['HeaderFlipType']
+ = (PMA_USR_BROWSER_AGENT == 'IE')
+ ? self::HEADER_FLIP_TYPE_CSS
+ : self::HEADER_FLIP_TYPE_FAKE;
+ }
+
+ if ($direction == self::DISP_DIR_HORIZONTAL_FLIPPED
+ && $GLOBALS['cfg']['HeaderFlipType'] == self::HEADER_FLIP_TYPE_CSS
+ ) {
+ $order_link_params['style'] = 'direction: ltr; writing-mode: tb-rl;';
+ }
+
+ $order_link_content = (($direction == self::DISP_DIR_HORIZONTAL_FLIPPED)
+ && ($GLOBALS['cfg']['HeaderFlipType'] == self::HEADER_FLIP_TYPE_FAKE))
+ ? PMA_Util::flipstring(
+ htmlspecialchars($fields_meta->name),
+ "<br />\n"
+ )
+ : htmlspecialchars($fields_meta->name);
+
+ return PMA_Util::linkOrButton(
+ $order_url, $order_link_content . $order_img,
+ $order_link_params, false, true
+ );
+
+ } // end of the '_getSortOrderLink()' function
+
+
+ /**
+ * Prepare columns to draggable effect for sortable columns
+ *
+ * @param boolean $col_visib the column is visible (false)
+ * array the column is not visible (string array)
+ * @param string $col_visib_j element of $col_visib array
+ * @param boolean $condition_field whether to add CSS class condition
+ * @param string $direction the display direction
+ * @param array $fields_meta set of field properties
+ * @param string $order_link the order link
+ * @param string $comments the comment for the column
+ *
+ * @return string $draggable_html html content
+ *
+ * @access private
+ *
+ * @see _getTableHeaders()
+ */
+ private function _getDraggableClassForSortableColumns(
+ $col_visib, $col_visib_j, $condition_field, $direction, $fields_meta,
+ $order_link, $comments
+ ) {
+
+ $draggable_html = '<th';
+ $th_class = array();
+ $th_class[] = 'draggable';
+
+ if ($col_visib && !$col_visib_j) {
+ $th_class[] = 'hide';
+ }
+
+ if ($condition_field) {
+ $th_class[] = 'condition';
+ }
+
+ $th_class[] = 'column_heading';
+ if ($GLOBALS['cfg']['BrowsePointerEnable'] == true) {
+ $th_class[] = 'pointer';
+ }
+
+ if ($GLOBALS['cfg']['BrowseMarkerEnable'] == true) {
+ $th_class[] = 'marker';
+ }
+
+ $draggable_html .= ' class="' . implode(' ', $th_class);
+
+ if ($direction == self::DISP_DIR_HORIZONTAL_FLIPPED) {
+ $draggable_html .= ' vbottom';
+ }
+
+ $draggable_html .= '" data-column="' . htmlspecialchars($fields_meta->name)
+ . '">' . $order_link . $comments . '</th>';
+
+ return $draggable_html;
+
+ } // end of the '_getDraggableClassForSortableColumns()' function
+
+
+ /**
+ * Prepare columns to draggable effect for non sortable columns
+ *
+ * @param boolean $col_visib the column is visible (false)
+ * array the column is not visible (string array)
+ * @param string $col_visib_j element of $col_visib array
+ * @param boolean $condition_field whether to add CSS class condition
+ * @param string $direction the display direction
+ * @param array $fields_meta set of field properties
+ * @param string $comments the comment for the column
+ *
+ * @return string $draggable_html html content
+ *
+ * @access private
+ *
+ * @see _getTableHeaders()
+ */
+ private function _getDraggableClassForNonSortableColumns(
+ $col_visib, $col_visib_j, $condition_field,
+ $direction, $fields_meta, $comments
+ ) {
+
+ $draggable_html = '<th';
+ $th_class = array();
+ $th_class[] = 'draggable';
+
+ if ($col_visib && !$col_visib_j) {
+ $th_class[] = 'hide';
+ }
+
+ if ($condition_field) {
+ $th_class[] = 'condition';
+ }
+
+ $draggable_html .= ' class="' . implode(' ', $th_class);
+ if ($direction == self::DISP_DIR_HORIZONTAL_FLIPPED) {
+ $draggable_html .= ' vbottom';
+ }
+
+ $draggable_html .= '"';
+ if (($direction == self::DISP_DIR_HORIZONTAL_FLIPPED)
+ && ($GLOBALS['cfg']['HeaderFlipType'] == self::HEADER_FLIP_TYPE_CSS)
+ ) {
+ $draggable_html .= ' style="direction: ltr; writing-mode: tb-rl;"';
+ }
+
+ $draggable_html .= ' data-column="'
+ . htmlspecialchars($fields_meta->name) . '">';
+
+ if (($direction == self::DISP_DIR_HORIZONTAL_FLIPPED)
+ && ($GLOBALS['cfg']['HeaderFlipType'] == self::HEADER_FLIP_TYPE_FAKE)
+ ) {
+
+ $draggable_html .= PMA_Util::flipstring(
+ htmlspecialchars($fields_meta->name), '<br />'
+ );
+
+ } else {
+ $draggable_html .= htmlspecialchars($fields_meta->name);
+ }
+
+ $draggable_html .= "\n" . $comments . '</th>';
+
+ return $draggable_html;
+
+ } // end of the '_getDraggableClassForNonSortableColumns()' function
+
+
+ /**
+ * Prepare column to show at right side - check boxes or empty column
+ *
+ * @param array &$is_display which elements to display
+ * @param boolean $directionCondition display direction horizontal
+ * or horizontalflipped
+ * @param string $full_or_partial_text_link full/partial link or text button
+ * @param string $colspan column span of table header
+ * @param string $rowspan row span of table header
+ *
+ * @return string html content
+ *
+ * @access private
+ *
+ * @see _getTableHeaders()
+ */
+ private function _getColumnAtRightSide(
+ &$is_display, $directionCondition, $full_or_partial_text_link,
+ $colspan, $rowspan
+ ) {
+
+ $right_column_html = '';
+ $vertical_display = $this->__get('vertical_display');
+
+ // Displays the needed checkboxes at the right
+ // column of the result table header if possible and required...
+ if ((($GLOBALS['cfg']['RowActionLinks'] == self::POSITION_RIGHT)
+ || ($GLOBALS['cfg']['RowActionLinks'] == self::POSITION_BOTH))
+ && (($is_display['edit_lnk'] != self::NO_EDIT_OR_DELETE)
+ || ($is_display['del_lnk'] != self::NO_EDIT_OR_DELETE))
+ && ($is_display['text_btn'] == '1')
+ ) {
+
+ $vertical_display['emptyafter']
+ = (($is_display['edit_lnk'] != self::NO_EDIT_OR_DELETE)
+ && ($is_display['del_lnk'] != self::NO_EDIT_OR_DELETE)) ? 4 : 1;
+
+ if ($directionCondition) {
+ $right_column_html .= "\n"
+ . '<th ' . $colspan . '>' . $full_or_partial_text_link
+ . '</th>';
+
+ // end horizontal/horizontalflipped mode
+ } else {
+ $vertical_display['textbtn'] = ' <th ' . $rowspan
+ . ' class="vmiddle">' . "\n"
+ . ' ' . "\n"
+ . ' </th>' . "\n";
+ } // end vertical mode
+ } elseif ((($GLOBALS['cfg']['RowActionLinks'] == self::POSITION_LEFT)
+ || ($GLOBALS['cfg']['RowActionLinks'] == self::POSITION_BOTH))
+ && (($is_display['edit_lnk'] == self::NO_EDIT_OR_DELETE)
+ && ($is_display['del_lnk'] == self::NO_EDIT_OR_DELETE))
+ && (! isset($GLOBALS['is_header_sent']) || ! $GLOBALS['is_header_sent'])
+ ) {
+ // ... elseif no button, displays empty columns if required
+ // (unless coming from Browse mode print view)
+
+ $vertical_display['emptyafter']
+ = (($is_display['edit_lnk'] != self::NO_EDIT_OR_DELETE)
+ && ($is_display['del_lnk'] != self::NO_EDIT_OR_DELETE)) ? 4 : 1;
+
+ if ($directionCondition) {
+ $right_column_html .= "\n"
+ . '<td ' . $colspan . '></td>';
+
+ // end horizontal/horizontalflipped mode
+ } else {
+ $vertical_display['textbtn'] = ' <td' . $rowspan
+ . '></td>' . "\n";
+ } // end vertical mode
+ }
+
+ $this->__set('vertical_display', $vertical_display);
+
+ return $right_column_html;
+
+ } // end of the '_getColumnAtRightSide()' function
+
+
+ /**
+ * Prepares the display for a value
+ *
+ * @param string $class class of table cell
+ * @param bool $condition_field whether to add CSS class condition
+ * @param string $value value to display
+ *
+ * @return string the td
+ *
+ * @access private
+ *
+ * @see _getDataCellForBlobColumns(), _getDataCellForGeometryColumns(),
+ * _getDataCellForNonNumericAndNonBlobColumns()
+ */
+ private function _buildValueDisplay($class, $condition_field, $value)
+ {
+ return '<td class="left ' . $class . ($condition_field ? ' condition' : '')
+ . '">' . $value . '</td>';
+ } // end of the '_buildValueDisplay()' function
+
+
+ /**
+ * Prepares the display for a null value
+ *
+ * @param string $class class of table cell
+ * @param bool $condition_field whether to add CSS class condition
+ * @param object $meta the meta-information about this field
+ * @param string $align cell allignment
+ *
+ * @return string the td
+ *
+ * @access private
+ *
+ * @see _getDataCellForNumericColumns(), _getDataCellForBlobColumns(),
+ * _getDataCellForGeometryColumns(),
+ * _getDataCellForNonNumericAndNonBlobColumns()
+ */
+ private function _buildNullDisplay($class, $condition_field, $meta, $align = '')
+ {
+ // the null class is needed for grid editing
+ return '<td ' . $align . ' class="'
+ . $this->_addClass(
+ $class, $condition_field, $meta, ''
+ )
+ . ' null"><i>NULL</i></td>';
+ } // end of the '_buildNullDisplay()' function
+
+
+ /**
+ * Prepares the display for an empty value
+ *
+ * @param string $class class of table cell
+ * @param bool $condition_field whether to add CSS class condition
+ * @param object $meta the meta-information about this field
+ * @param string $align cell allignment
+ *
+ * @return string the td
+ *
+ * @access private
+ *
+ * @see _getDataCellForNumericColumns(), _getDataCellForBlobColumns(),
+ * _getDataCellForGeometryColumns(),
+ * _getDataCellForNonNumericAndNonBlobColumns()
+ */
+ private function _buildEmptyDisplay($class, $condition_field, $meta, $align = '')
+ {
+ return '<td ' . $align . ' class="'
+ . $this->_addClass(
+ $class, $condition_field, $meta, ' nowrap'
+ )
+ . '"></td>';
+ } // end of the '_buildEmptyDisplay()' function
+
+
+ /**
+ * Adds the relevant classes.
+ *
+ * @param string $class class of table cell
+ * @param bool $condition_field whether to add CSS class condition
+ * @param object $meta the meta-information about the field
+ * @param string $nowrap avoid wrapping
+ * @param bool $is_field_truncated is field truncated (display ...)
+ * @param string $transformation_plugin transformation plugin.
+ * Can also be the default function:
+ * PMA_mimeDefaultFunction
+ * @param string $default_function default transformation function
+ *
+ * @return string the list of classes
+ *
+ * @access private
+ *
+ * @see _buildNullDisplay(), _getRowData()
+ */
+ private function _addClass(
+ $class, $condition_field, $meta, $nowrap, $is_field_truncated = false,
+ $transformation_plugin = '', $default_function = ''
+ ) {
+
+ // Define classes to be added to this data field based on the type of data
+ $enum_class = '';
+ if (strpos($meta->flags, 'enum') !== false) {
+ $enum_class = ' enum';
+ }
+
+ $set_class = '';
+ if (strpos($meta->flags, 'set') !== false) {
+ $set_class = ' set';
+ }
+
+ $bit_class = '';
+ if (strpos($meta->type, 'bit') !== false) {
+ $bit_class = ' bit';
+ }
+
+ $mime_type_class = '';
+ if (isset($meta->mimetype)) {
+ $mime_type_class = ' ' . preg_replace('/\//', '_', $meta->mimetype);
+ }
+
+ return $class . ($condition_field ? ' condition' : '') . $nowrap
+ . ' ' . ($is_field_truncated ? ' truncated' : '')
+ . ($transformation_plugin != $default_function ? ' transformed' : '')
+ . $enum_class . $set_class . $bit_class . $mime_type_class;
+
+ } // end of the '_addClass()' function
+
+
+ /**
+ * Prepare the body of the results table
+ *
+ * @param integer &$dt_result the link id associated to the query
+ * which results have to be displayed
+ * @param array &$is_display which elements to display
+ * @param array $map the list of relations
+ * @param array $analyzed_sql the analyzed query
+ * @param boolean $is_limited_display with limited operations or not
+ *
+ * @return string $table_body_html html content
+ *
+ * @global array $row current row data
+ *
+ * @access private
+ *
+ * @see getTable()
+ */
+ private function _getTableBody(
+ &$dt_result, &$is_display, $map, $analyzed_sql, $is_limited_display = false
+ ) {
+
+ global $row; // mostly because of browser transformations,
+ // to make the row-data accessible in a plugin
+
+ $table_body_html = '';
+
+ // query without conditions to shorten URLs when needed, 200 is just
+ // guess, it should depend on remaining URL length
+ $url_sql_query = $this->_getUrlSqlQuery($analyzed_sql);
+
+ $vertical_display = $this->__get('vertical_display');
+
+ if (! is_array($map)) {
+ $map = array();
+ }
+
+ $row_no = 0;
+ $vertical_display['edit'] = array();
+ $vertical_display['copy'] = array();
+ $vertical_display['delete'] = array();
+ $vertical_display['data'] = array();
+ $vertical_display['row_delete'] = array();
+ $this->__set('vertical_display', $vertical_display);
+
+ // name of the class added to all grid editable elements;
+ // if we don't have all the columns of a unique key in the result set,
+ // do not permit grid editing
+ if ($is_limited_display || ! $this->__get('editable')) {
+ $grid_edit_class = '';
+ } else {
+ switch ($GLOBALS['cfg']['GridEditing']) {
+ case 'double-click':
+ // trying to reduce generated HTML by using shorter
+ // classes like click1 and click2
+ $grid_edit_class = 'grid_edit click2';
+ break;
+ case 'click':
+ $grid_edit_class = 'grid_edit click1';
+ break;
+ case 'disabled':
+ $grid_edit_class = '';
+ break;
+ }
+ }
+
+ // prepare to get the column order, if available
+ list($col_order, $col_visib) = $this->_getColumnParams($analyzed_sql);
+
+ // Correction University of Virginia 19991216 in the while below
+ // Previous code assumed that all tables have keys, specifically that
+ // the phpMyAdmin GUI should support row delete/edit only for such
+ // tables.
+ // Although always using keys is arguably the prescribed way of
+ // defining a relational table, it is not required. This will in
+ // particular be violated by the novice.
+ // We want to encourage phpMyAdmin usage by such novices. So the code
+ // below has been changed to conditionally work as before when the
+ // table being displayed has one or more keys; but to display
+ // delete/edit options correctly for tables without keys.
+
+ $odd_row = true;
+ $directionCondition
+ = ($_SESSION['tmpval']['disp_direction']
+ == self::DISP_DIR_HORIZONTAL)
+ || ($_SESSION['tmpval']['disp_direction']
+ == self::DISP_DIR_HORIZONTAL_FLIPPED);
+
+ while ($row = $GLOBALS['dbi']->fetchRow($dt_result)) {
+
+ // "vertical display" mode stuff
+ $table_body_html .= $this->_getVerticalDisplaySupportSegments(
+ $vertical_display, $row_no, $directionCondition
+ );
+
+ $alternating_color_class = ($odd_row ? 'odd' : 'even');
+ $odd_row = ! $odd_row;
+
+ if ($directionCondition) {
+ // pointer code part
+ $table_body_html .= '<tr class="' . $alternating_color_class . '">';
+ }
+
+ // 1. Prepares the row
+ // 1.1 Results from a "SELECT" statement -> builds the
+ // WHERE clause to use in links (a unique key if possible)
+ /**
+ * @todo $where_clause could be empty, for example a table
+ * with only one field and it's a BLOB; in this case,
+ * avoid to display the delete and edit links
+ */
+ list($where_clause, $clause_is_unique, $condition_array)
+ = PMA_Util::getUniqueCondition(
+ $dt_result,
+ $this->__get('fields_cnt'),
+ $this->__get('fields_meta'),
+ $row
+ );
+ $where_clause_html = urlencode($where_clause);
+
+ // In print view these variable needs toinitialized
+ $del_url = $del_query = $del_str = $edit_anchor_class
+ = $edit_str = $js_conf = $copy_url = $copy_str = $edit_url = null;
+
+ // 1.2 Defines the URLs for the modify/delete link(s)
+
+ if (($is_display['edit_lnk'] != self::NO_EDIT_OR_DELETE)
+ || ($is_display['del_lnk'] != self::NO_EDIT_OR_DELETE)
+ ) {
+ // We need to copy the value
+ // or else the == 'both' check will always return true
+
+ if ($GLOBALS['cfg']['ActionLinksMode'] === self::POSITION_BOTH) {
+ $iconic_spacer = '<div class="nowrap">';
+ } else {
+ $iconic_spacer = '';
+ }
+
+ // 1.2.1 Modify link(s) - update row case
+ if ($is_display['edit_lnk'] == self::UPDATE_ROW) {
+
+ list($edit_url, $copy_url, $edit_str, $copy_str,
+ $edit_anchor_class)
+ = $this->_getModifiedLinks(
+ $where_clause,
+ $clause_is_unique, $url_sql_query
+ );
+
+ } // end if (1.2.1)
+
+ // 1.2.2 Delete/Kill link(s)
+ if (($is_display['del_lnk'] == self::DELETE_ROW)
+ || ($is_display['del_lnk'] == self::KILL_PROCESS)
+ ) {
+
+ list($del_query, $del_url, $del_str, $js_conf)
+ = $this->_getDeleteAndKillLinks(
+ $where_clause, $clause_is_unique,
+ $url_sql_query, $is_display['del_lnk'],
+ $row
+ );
+
+ } // end if (1.2.2)
+
+ // 1.3 Displays the links at left if required
+ if ((($GLOBALS['cfg']['RowActionLinks'] == self::POSITION_LEFT)
+ || ($GLOBALS['cfg']['RowActionLinks'] == self::POSITION_BOTH))
+ && $directionCondition
+ ) {
+
+ $table_body_html .= $this->_getPlacedLinks(
+ self::POSITION_LEFT, $del_url, $is_display, $row_no,
+ $where_clause, $where_clause_html, $condition_array,
+ $del_query, 'l', $edit_url, $copy_url, $edit_anchor_class,
+ $edit_str, $copy_str, $del_str, $js_conf
+ );
+
+ } elseif (($GLOBALS['cfg']['RowActionLinks'] == self::POSITION_NONE)
+ && $directionCondition
+ ) {
+
+ $table_body_html .= $this->_getPlacedLinks(
+ self::POSITION_NONE, $del_url, $is_display, $row_no,
+ $where_clause, $where_clause_html, $condition_array,
+ $del_query, 'l', $edit_url, $copy_url, $edit_anchor_class,
+ $edit_str, $copy_str, $del_str, $js_conf
+ );
+
+ } // end if (1.3)
+ } // end if (1)
+
+ // 2. Displays the rows' values
+ $table_body_html .= $this->_getRowValues(
+ $dt_result, $row, $row_no, $col_order, $map,
+ $grid_edit_class, $col_visib, $where_clause,
+ $url_sql_query, $analyzed_sql, $directionCondition
+ );
+
+ // 3. Displays the modify/delete links on the right if required
+ if ((($GLOBALS['cfg']['RowActionLinks'] == self::POSITION_RIGHT)
+ || ($GLOBALS['cfg']['RowActionLinks'] == self::POSITION_BOTH))
+ && $directionCondition
+ ) {
+
+ $table_body_html .= $this->_getPlacedLinks(
+ self::POSITION_RIGHT, $del_url, $is_display, $row_no,
+ $where_clause, $where_clause_html, $condition_array,
+ $del_query, 'r', $edit_url, $copy_url, $edit_anchor_class,
+ $edit_str, $copy_str, $del_str, $js_conf
+ );
+
+ } // end if (3)
+
+ if ($directionCondition) {
+ $table_body_html .= '</tr>';
+ } // end if
+
+ // 4. Gather links of del_urls and edit_urls in an array for later
+ // output
+ $this->_gatherLinksForLaterOutputs(
+ $row_no, $is_display, $where_clause, $where_clause_html, $js_conf,
+ $del_url, $del_query, $del_str, $edit_anchor_class, $edit_url,
+ $edit_str, $copy_url, $copy_str, $alternating_color_class,
+ $condition_array
+ );
+
+ $table_body_html .= $directionCondition ? "\n" : '';
+ $row_no++;
+
+ } // end while
+
+ return $table_body_html;
+
+ } // end of the '_getTableBody()' function
+
+
+ /**
+ * Get the values for one data row
+ *
+ * @param integer &$dt_result the link id associated to the query
+ * which results have to be displayed
+ * @param array $row current row data
+ * @param integer $row_no the index of current row
+ * @param array $col_order the column order
+ * false when a property not found
+ * @param array $map the list of relations
+ * @param string $grid_edit_class the class for all editable columns
+ * @param boolean $col_visib column is visible(false)
+ * array column isn't visible(string array)
+ * @param string $where_clause where clause
+ * @param string $url_sql_query the analyzed sql query
+ * @param array $analyzed_sql the analyzed query
+ * @param boolean $directionCondition the directional condition
+ *
+ * @return string $row_values_html html content
+ *
+ * @access private
+ *
+ * @see _getTableBody()
+ */
+ private function _getRowValues(
+ &$dt_result, $row, $row_no, $col_order, $map,
+ $grid_edit_class, $col_visib, $where_clause,
+ $url_sql_query, $analyzed_sql, $directionCondition
+ ) {
+
+ $row_values_html = '';
+
+ // Following variable are needed for use in isset/empty or
+ // use with array indexes/safe use in foreach
+ $sql_query = $this->__get('sql_query');
+ $fields_meta = $this->__get('fields_meta');
+ $highlight_columns = $this->__get('highlight_columns');
+ $mime_map = $this->__get('mime_map');
+
+ $row_info = $this->_getRowInfoForSpecialLinks($row, $col_order);
+
+ for ($currentColumn = 0;
+ $currentColumn < $this->__get('fields_cnt');
+ ++$currentColumn) {
+
+ // assign $i with appropriate column order
+ $i = $col_order ? $col_order[$currentColumn] : $currentColumn;
+
+ $meta = $fields_meta[$i];
+ $not_null_class = $meta->not_null ? 'not_null' : '';
+ $relation_class = isset($map[$meta->name]) ? 'relation' : '';
+ $hide_class = ($col_visib && ! $col_visib[$currentColumn]
+ // hide per <td> only if the display dir is not vertical
+ && ($_SESSION['tmpval']['disp_direction']
+ != self::DISP_DIR_VERTICAL))
+ ? 'hide'
+ : '';
+
+ // handle datetime-related class, for grid editing
+ $field_type_class
+ = $this->_getClassForDateTimeRelatedFields($meta->type);
+
+ $is_field_truncated = false;
+ // combine all the classes applicable to this column's value
+ $class = $this->_getClassesForColumn(
+ $grid_edit_class, $not_null_class, $relation_class,
+ $hide_class, $field_type_class, $row_no
+ );
+
+ // See if this column should get highlight because it's used in the
+ // where-query.
+ $condition_field = (isset($highlight_columns)
+ && (isset($highlight_columns[$meta->name])
+ || isset($highlight_columns[PMA_Util::backquote($meta->name)])))
+ ? true
+ : false;
+
+ // Wrap MIME-transformations. [MIME]
+ $default_function = '_mimeDefaultFunction'; // default_function
+ $transformation_plugin = $default_function;
+ $transform_options = array();
+
+ if ($GLOBALS['cfgRelation']['mimework']
+ && $GLOBALS['cfg']['BrowseMIME']
+ ) {
+
+ if (isset($mime_map[$meta->name]['mimetype'])
+ && isset($mime_map[$meta->name]['transformation'])
+ && !empty($mime_map[$meta->name]['transformation'])
+ ) {
+
+ $file = $mime_map[$meta->name]['transformation'];
+ $include_file = 'libraries/plugins/transformations/' . $file;
+
+ if (file_exists($include_file)) {
+
+ include_once $include_file;
+ $class_name = str_replace('.class.php', '', $file);
+ // todo add $plugin_manager
+ $plugin_manager = null;
+ $transformation_plugin = new $class_name(
+ $plugin_manager
+ );
+
+ $transform_options = PMA_Transformation_getOptions(
+ isset($mime_map[$meta->name]
+ ['transformation_options']
+ )
+ ? $mime_map[$meta->name]
+ ['transformation_options']
+ : ''
+ );
+
+ $meta->mimetype = str_replace(
+ '_', '/',
+ $mime_map[$meta->name]['mimetype']
+ );
+
+ } // end if file_exists
+ } // end if transformation is set
+ } // end if mime/transformation works.
+
+ $_url_params = array(
+ 'db' => $this->__get('db'),
+ 'table' => $this->__get('table'),
+ 'where_clause' => $where_clause,
+ 'transform_key' => $meta->name,
+ );
+
+ if (! empty($sql_query)) {
+ $_url_params['sql_query'] = $url_sql_query;
+ }
+
+ $transform_options['wrapper_link']
+ = PMA_URL_getCommon($_url_params);
+
+ $vertical_display = $this->__get('vertical_display');
+
+ // Check whether the field needs to display with syntax highlighting
+
+ if ($this->_isNeedToSyntaxHighlight($meta->name)
+ && (trim($row[$i]) != '')
+ ) {
+ $row[$i] = PMA_Util::formatSql($row[$i]);
+ include_once $this->transformation_info[strtolower($this->__get('db'))][strtolower($this->__get('table'))][strtolower($meta->name)][0];
+ $transformation_plugin = new $this->transformation_info
+ [strtolower($this->__get('db'))]
+ [strtolower($this->__get('table'))]
+ [strtolower($meta->name)][1](null);
+
+ $transform_options = PMA_Transformation_getOptions(
+ isset($mime_map[$meta->name]['transformation_options'])
+ ? $mime_map[$meta->name]['transformation_options']
+ : ''
+ );
+
+ $meta->mimetype = str_replace(
+ '_', '/',
+ $this->transformation_info[strtolower($this->__get('db'))][strtolower($this->__get('table'))][strtolower($meta->name)][2]
+ );
+
+ }
+
+ // Check for the predefined fields need to show as link in schemas
+ include_once 'libraries/special_schema_links.lib.php';
+
+ if (isset($GLOBALS['special_schema_links'])
+ && ($this->_isFieldNeedToLink(strtolower($meta->name)))
+ ) {
+
+ $linking_url = $this->_getSpecialLinkUrl(
+ $row[$i], $row_info, strtolower($meta->name)
+ );
+ include_once
+ "libraries/plugins/transformations/Text_Plain_Link.class.php";
+ $transformation_plugin = new Text_Plain_Link(null);
+
+ $transform_options = array(
+ 0 => $linking_url,
+ 2 => true
+ );
+
+ $meta->mimetype = str_replace(
+ '_', '/',
+ 'Text/Plain'
+ );
+
+ }
+
+ if ($meta->numeric == 1) {
+ // n u m e r i c
+
+ $vertical_display['data'][$row_no][$i]
+ = $this->_getDataCellForNumericColumns(
+ $row[$i], $class, $condition_field, $meta, $map,
+ $is_field_truncated, $analyzed_sql,
+ $transformation_plugin, $default_function,
+ $transform_options
+ );
+
+ } elseif (stristr($meta->type, self::BLOB_FIELD)) {
+ // b l o b
+
+ // PMA_mysql_fetch_fields returns BLOB in place of
+ // TEXT fields type so we have to ensure it's really a BLOB
+ $field_flags = $GLOBALS['dbi']->fieldFlags($dt_result, $i);
+
+ $vertical_display['data'][$row_no][$i]
+ = $this->_getDataCellForBlobColumns(
+ $row[$i], $class, $meta, $_url_params, $field_flags,
+ $transformation_plugin, $default_function,
+ $transform_options, $condition_field, $is_field_truncated
+ );
+
+ } elseif ($meta->type == self::GEOMETRY_FIELD) {
+ // g e o m e t r y
+
+ // Remove 'grid_edit' from $class as we do not allow to
+ // inline-edit geometry data.
+ $class = str_replace('grid_edit', '', $class);
+
+ $vertical_display['data'][$row_no][$i]
+ = $this->_getDataCellForGeometryColumns(
+ $row[$i], $class, $meta, $map, $_url_params,
+ $condition_field, $transformation_plugin,
+ $default_function, $transform_options,
+ $is_field_truncated, $analyzed_sql
+ );
+
+ } else {
+ // n o t n u m e r i c a n d n o t B L O B
+
+ $vertical_display['data'][$row_no][$i]
+ = $this->_getDataCellForNonNumericAndNonBlobColumns(
+ $row[$i], $class, $meta, $map, $_url_params,
+ $condition_field, $transformation_plugin,
+ $default_function, $transform_options,
+ $is_field_truncated, $analyzed_sql, $dt_result, $i
+ );
+
+ }
+
+ // output stored cell
+ if ($directionCondition) {
+ $row_values_html
+ .= $vertical_display['data'][$row_no][$i];
+ }
+
+ if (isset($vertical_display['rowdata'][$i][$row_no])) {
+ $vertical_display['rowdata'][$i][$row_no]
+ .= $vertical_display['data'][$row_no][$i];
+ } else {
+ $vertical_display['rowdata'][$i][$row_no]
+ = $vertical_display['data'][$row_no][$i];
+ }
+
+ $this->__set('vertical_display', $vertical_display);
+
+ } // end for
+
+ return $row_values_html;
+
+ } // end of the '_getRowValues()' function
+
+
+ /**
+ * Gather delete/edit url links for further outputs
+ *
+ * @param integer $row_no the index of current row
+ * @param array $is_display which elements to display
+ * @param string $where_clause where clause
+ * @param string $where_clause_html the html encoded where clause
+ * @param string $js_conf text for the JS confirmation
+ * @param string $del_url the url for delete row
+ * @param string $del_query the query for delete row
+ * @param string $del_str the label for delete row
+ * @param string $edit_anchor_class the class for html element for edit
+ * @param string $edit_url the url for edit row
+ * @param string $edit_str the label for edit row
+ * @param string $copy_url the url for copy row
+ * @param string $copy_str the label for copy row
+ * @param string $alternating_color_class class for display two colors in rows
+ * @param array $condition_array array of keys
+ * (primary,unique,condition)
+ *
+ * @return void
+ *
+ * @access private
+ *
+ * @see _getTableBody()
+ */
+ private function _gatherLinksForLaterOutputs(
+ $row_no, $is_display, $where_clause, $where_clause_html, $js_conf,
+ $del_url, $del_query, $del_str, $edit_anchor_class, $edit_url, $edit_str,
+ $copy_url, $copy_str, $alternating_color_class, $condition_array
+ ) {
+
+ $vertical_display = $this->__get('vertical_display');
+
+ if (! isset($vertical_display['edit'][$row_no])) {
+ $vertical_display['edit'][$row_no] = '';
+ $vertical_display['copy'][$row_no] = '';
+ $vertical_display['delete'][$row_no] = '';
+ $vertical_display['row_delete'][$row_no] = '';
+ }
+
+ $vertical_class = ' row_' . $row_no;
+ if ($GLOBALS['cfg']['BrowsePointerEnable'] == true) {
+ $vertical_class .= ' vpointer';
+ }
+
+ if ($GLOBALS['cfg']['BrowseMarkerEnable'] == true) {
+ $vertical_class .= ' vmarker';
+ }
+
+ if (!empty($del_url)
+ && ($is_display['del_lnk'] != self::KILL_PROCESS)
+ ) {
+
+ $vertical_display['row_delete'][$row_no]
+ .= $this->_getCheckboxForMultiRowSubmissions(
+ $del_url, $is_display, $row_no, $where_clause_html,
+ $condition_array, $del_query, '[%_PMA_CHECKBOX_DIR_%]',
+ $alternating_color_class . $vertical_class
+ );
+
+ } else {
+ unset($vertical_display['row_delete'][$row_no]);
+ }
+
+ if (isset($edit_url)) {
+
+ $vertical_display['edit'][$row_no] .= $this->_getEditLink(
+ $edit_url,
+ $alternating_color_class . ' ' . $edit_anchor_class
+ . $vertical_class, $edit_str,
+ $where_clause,
+ $where_clause_html
+ );
+
+ } else {
+ unset($vertical_display['edit'][$row_no]);
+ }
+
+ if (isset($copy_url)) {
+
+ $vertical_display['copy'][$row_no] .= $this->_getCopyLink(
+ $copy_url, $copy_str, $where_clause, $where_clause_html,
+ $alternating_color_class . $vertical_class
+ );
+
+ } else {
+ unset($vertical_display['copy'][$row_no]);
+ }
+
+ if (isset($del_url)) {
+
+ if (! isset($js_conf)) {
+ $js_conf = '';
+ }
+
+ $vertical_display['delete'][$row_no]
+ .= $this->_getDeleteLink(
+ $del_url, $del_str, $js_conf,
+ $alternating_color_class . $vertical_class
+ );
+
+ } else {
+ unset($vertical_display['delete'][$row_no]);
+ }
+
+ $this->__set('vertical_display', $vertical_display);
+
+ } // end of the '_gatherLinksForLaterOutputs()' function
+
+
+ /**
+ * Check whether any field is marked as need to syntax highlight
+ *
+ * @param string $field field to check
+ *
+ * @return boolean
+ */
+ private function _isNeedToSyntaxHighlight($field)
+ {
+ if (! empty($this->transformation_info[strtolower($this->__get('db'))][strtolower($this->__get('table'))][strtolower($field)])) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Check whether the field needs to be link
+ *
+ * @param string $field field to check
+ *
+ * @return boolean
+ */
+ private function _isFieldNeedToLink($field)
+ {
+ if (! empty($GLOBALS['special_schema_links'][strtolower($this->__get('db'))][strtolower($this->__get('table'))][$field])) {
+ return true;
+ }
+ return false;
+ }
+
+
+ /**
+ * Get link for display special schema links
+ *
+ * @param string $column_value column value
+ * @param array $row_info information about row
+ * @param string $field_name column name
+ *
+ * @return string generated link
+ */
+ private function _getSpecialLinkUrl($column_value, $row_info, $field_name)
+ {
+
+ $linking_url_params = array();
+ $link_relations = $GLOBALS['special_schema_links']
+ [strtolower($this->__get('db'))]
+ [strtolower($this->__get('table'))]
+ [$field_name];
+
+ if (! is_array($link_relations['link_param'])) {
+ $linking_url_params[$link_relations['link_param']] = $column_value;
+ } else {
+ // Consider only the case of creating link for column field
+ // sql query need to be pass as url param
+ $sql = 'SELECT `'.$column_value.'` FROM `'
+ . $row_info[$link_relations['link_param'][1]] .'`.`'
+ . $row_info[$link_relations['link_param'][2]] .'`';
+ $linking_url_params[$link_relations['link_param'][0]] = $sql;
+ }
+
+
+ if (! empty($link_relations['link_dependancy_params'])) {
+
+ foreach ($link_relations['link_dependancy_params'] as $new_param) {
+
+ // If param_info is an array, set the key and value
+ // from that array
+ if (is_array($new_param['param_info'])) {
+ $linking_url_params[$new_param['param_info'][0]]
+ = $new_param['param_info'][1];
+ } else {
+
+ $linking_url_params[$new_param['param_info']]
+ = $row_info[strtolower($new_param['column_name'])];
+
+ // Special case 1 - when executing routines, according
+ // to the type of the routine, url param changes
+ if (!empty($row_info['routine_type'])) {
+ $lowerRoutineType = strtolower($row_info['routine_type']);
+ if ($lowerRoutineType == self::ROUTINE_PROCEDURE
+ || $lowerRoutineType == self::ROUTINE_FUNCTION
+ ) {
+ $linking_url_params['edit_item'] = 1;
+ }
+ }
+ }
+
+ }
+
+ }
+
+ return $link_relations['default_page']
+ . PMA_URL_getCommon($linking_url_params);
+
+ }
+
+
+ /**
+ * Prepare row information for display special links
+ *
+ * @param array $row current row data
+ * @param array $col_order the column order
+ *
+ * @return array $row_info associative array with column nama -> value
+ */
+ private function _getRowInfoForSpecialLinks($row, $col_order)
+ {
+
+ $row_info = array();
+ $fields_meta = $this->__get('fields_meta');
+
+ for ($n = 0; $n < $this->__get('fields_cnt'); ++$n) {
+ $m = $col_order ? $col_order[$n] : $n;
+ $row_info[strtolower($fields_meta[$m]->name)] = $row[$m];
+ }
+
+ return $row_info;
+
+ }
+
+
+ /**
+ * Get url sql query without conditions to shorten URLs
+ *
+ * @param array $analyzed_sql analyzed query
+ *
+ * @return string $url_sql analyzed sql query
+ *
+ * @access private
+ *
+ * @see _getTableBody()
+ */
+ private function _getUrlSqlQuery($analyzed_sql)
+ {
+
+ if (isset($analyzed_sql)
+ && isset($analyzed_sql[0])
+ && isset($analyzed_sql[0]['querytype'])
+ && ($analyzed_sql[0]['querytype'] == self::QUERY_TYPE_SELECT)
+ && (strlen($this->__get('sql_query')) > 200)
+ ) {
+
+ $url_sql_query = 'SELECT ';
+ if (isset($analyzed_sql[0]['queryflags']['distinct'])) {
+ $url_sql_query .= ' DISTINCT ';
+ }
+
+ $url_sql_query .= $analyzed_sql[0]['select_expr_clause'];
+ if (!empty($analyzed_sql[0]['from_clause'])) {
+ $url_sql_query .= ' FROM ' . $analyzed_sql[0]['from_clause'];
+ }
+
+ return $url_sql_query;
+ }
+
+ return $this->__get('sql_query');
+
+ } // end of the '_getUrlSqlQuery()' function
+
+
+ /**
+ * Get column order and column visibility
+ *
+ * @param array $analyzed_sql the analyzed query
+ *
+ * @return array 2 element array - $col_order, $col_visib
+ *
+ * @access private
+ *
+ * @see _getTableBody()
+ */
+ private function _getColumnParams($analyzed_sql)
+ {
+ if ($this->_isSelect($analyzed_sql)) {
+ $pmatable = new PMA_Table($this->__get('table'), $this->__get('db'));
+ $col_order = $pmatable->getUiProp(PMA_Table::PROP_COLUMN_ORDER);
+ $col_visib = $pmatable->getUiProp(PMA_Table::PROP_COLUMN_VISIB);
+ } else {
+ $col_order = false;
+ $col_visib = false;
+ }
+
+ return array($col_order, $col_visib);
+ } // end of the '_getColumnParams()' function
+
+
+ /**
+ * Prepare vertical display mode necessay HTML stuff
+ *
+ * @param array $vertical_display informations used with vertical
+ * display mode
+ * @param integer $row_no the index of current row
+ * @param boolean $directionCondition the directional condition
+ *
+ * @return string $vertical_disp_html html content
+ *
+ * @access private
+ *
+ * @see _getTableBody()
+ */
+ private function _getVerticalDisplaySupportSegments(
+ $vertical_display, $row_no, $directionCondition
+ ) {
+
+ $support_html = '';
+
+ if ((($row_no != 0) && ($_SESSION['tmpval']['repeat_cells'] != 0))
+ && !($row_no % $_SESSION['tmpval']['repeat_cells'])
+ && $directionCondition
+ ) {
+
+ $support_html .= '<tr>' . "\n";
+
+ if ($vertical_display['emptypre'] > 0) {
+
+ $support_html .= ' <th colspan="'
+ . $vertical_display['emptypre'] . '">'
+ . "\n".' &nbsp;</th>' . "\n";
+
+ } else if ($GLOBALS['cfg']['RowActionLinks'] == self::POSITION_NONE) {
+ $support_html .= ' <th></th>' . "\n";
+ }
+
+ foreach ($vertical_display['desc'] as $val) {
+ $support_html .= $val;
+ }
+
+ if ($vertical_display['emptyafter'] > 0) {
+ $support_html
+ .= ' <th colspan="' . $vertical_display['emptyafter']
+ . '">'
+ . "\n" . ' &nbsp;</th>' . "\n";
+ }
+ $support_html .= '</tr>' . "\n";
+ } // end if
+
+ return $support_html;
+
+ } // end of the '_getVerticalDisplaySupportSegments()' function
+
+
+ /**
+ * Get modified links
+ *
+ * @param string $where_clause the where clause of the sql
+ * @param boolean $clause_is_unique the unique condition of clause
+ * @param string $url_sql_query the analyzed sql query
+ *
+ * @return array 5 element array - $edit_url, $copy_url,
+ * $edit_str, $copy_str, $edit_anchor_class
+ *
+ * @access private
+ *
+ * @see _getTableBody()
+ */
+ private function _getModifiedLinks(
+ $where_clause, $clause_is_unique, $url_sql_query
+ ) {
+
+ $_url_params = array(
+ 'db' => $this->__get('db'),
+ 'table' => $this->__get('table'),
+ 'where_clause' => $where_clause,
+ 'clause_is_unique' => $clause_is_unique,
+ 'sql_query' => $url_sql_query,
+ 'goto' => 'sql.php',
+ );
+
+ $edit_url = 'tbl_change.php'
+ . PMA_URL_getCommon(
+ $_url_params + array('default_action' => 'update')
+ );
+
+ $copy_url = 'tbl_change.php'
+ . PMA_URL_getCommon(
+ $_url_params + array('default_action' => 'insert')
+ );
+
+ $edit_str = PMA_Util::getIcon(
+ 'b_edit.png', __('Edit')
+ );
+ $copy_str = PMA_Util::getIcon(
+ 'b_insrow.png', __('Copy')
+ );
+
+ // Class definitions required for grid editing jQuery scripts
+ $edit_anchor_class = "edit_row_anchor";
+ if ( $clause_is_unique == 0) {
+ $edit_anchor_class .= ' nonunique';
+ }
+
+ return array($edit_url, $copy_url, $edit_str, $copy_str, $edit_anchor_class);
+
+ } // end of the '_getModifiedLinks()' function
+
+
+ /**
+ * Get delete and kill links
+ *
+ * @param string $where_clause the where clause of the sql
+ * @param boolean $clause_is_unique the unique condition of clause
+ * @param string $url_sql_query the analyzed sql query
+ * @param string $del_lnk the delete link of current row
+ * @param array $row the current row
+ *
+ * @return array 4 element array - $del_query,
+ * $del_url, $del_str, $js_conf
+ *
+ * @access private
+ *
+ * @see _getTableBody()
+ */
+ private function _getDeleteAndKillLinks(
+ $where_clause, $clause_is_unique, $url_sql_query, $del_lnk, $row
+ ) {
+
+ $goto = $this->__get('goto');
+
+ if ($del_lnk == self::DELETE_ROW) { // delete row case
+
+ $_url_params = array(
+ 'db' => $this->__get('db'),
+ 'table' => $this->__get('table'),
+ 'sql_query' => $url_sql_query,
+ 'message_to_show' => __('The row has been deleted'),
+ 'goto' => (empty($goto) ? 'tbl_sql.php' : $goto),
+ );
+
+ $lnk_goto = 'sql.php' . PMA_URL_getCommon($_url_params, 'text');
+
+ $del_query = 'DELETE FROM '
+ . PMA_Util::backquote($this->__get('db')) . '.'
+ . PMA_Util::backquote($this->__get('table'))
+ . ' WHERE ' . $where_clause .
+ ($clause_is_unique ? '' : ' LIMIT 1');
+
+ $_url_params = array(
+ 'db' => $this->__get('db'),
+ 'table' => $this->__get('table'),
+ 'sql_query' => $del_query,
+ 'message_to_show' => __('The row has been deleted'),
+ 'goto' => $lnk_goto,
+ );
+ $del_url = 'sql.php' . PMA_URL_getCommon($_url_params);
+
+ $js_conf = 'DELETE FROM ' . PMA_jsFormat($this->__get('db')) . '.'
+ . PMA_jsFormat($this->__get('table'))
+ . ' WHERE ' . PMA_jsFormat($where_clause, false)
+ . ($clause_is_unique ? '' : ' LIMIT 1');
+
+ $del_str = PMA_Util::getIcon(
+ 'b_drop.png', __('Delete')
+ );
+
+ } elseif ($del_lnk == self::KILL_PROCESS) { // kill process case
+
+ $_url_params = array(
+ 'db' => $this->__get('db'),
+ 'table' => $this->__get('table'),
+ 'sql_query' => $url_sql_query,
+ 'goto' => 'index.php',
+ );
+
+ $lnk_goto = 'sql.php'
+ . PMA_URL_getCommon(
+ $_url_params, 'text'
+ );
+
+ $_url_params = array(
+ 'db' => 'mysql',
+ 'sql_query' => 'KILL ' . $row[0],
+ 'goto' => $lnk_goto,
+ );
+
+ $del_url = 'sql.php' . PMA_URL_getCommon($_url_params);
+ $del_query = 'KILL ' . $row[0];
+ $js_conf = 'KILL ' . $row[0];
+ $del_str = PMA_Util::getIcon(
+ 'b_drop.png', __('Kill')
+ );
+ }
+
+ return array($del_query, $del_url, $del_str, $js_conf);
+
+ } // end of the '_getDeleteAndKillLinks()' function
+
+
+ /**
+ * Prepare placed links
+ *
+ * @param string $dir the direction of links should place
+ * @param string $del_url the url for delete row
+ * @param array $is_display which elements to display
+ * @param integer $row_no the index of current row
+ * @param string $where_clause the where clause of the sql
+ * @param string $where_clause_html the html encoded where clause
+ * @param array $condition_array array of keys (primary, unique, condition)
+ * @param string $del_query the query for delete row
+ * @param string $dir_letter the letter denoted the direction
+ * @param string $edit_url the url for edit row
+ * @param string $copy_url the url for copy row
+ * @param string $edit_anchor_class the class for html element for edit
+ * @param string $edit_str the label for edit row
+ * @param string $copy_str the label for copy row
+ * @param string $del_str the label for delete row
+ * @param string $js_conf text for the JS confirmation
+ *
+ * @return string html content
+ *
+ * @access private
+ *
+ * @see _getTableBody()
+ */
+ private function _getPlacedLinks(
+ $dir, $del_url, $is_display, $row_no, $where_clause, $where_clause_html,
+ $condition_array, $del_query, $dir_letter, $edit_url, $copy_url,
+ $edit_anchor_class, $edit_str, $copy_str, $del_str, $js_conf
+ ) {
+
+ if (! isset($js_conf)) {
+ $js_conf = '';
+ }
+
+ return $this->_getCheckboxAndLinks(
+ $dir, $del_url, $is_display,
+ $row_no, $where_clause, $where_clause_html, $condition_array,
+ $del_query, 'l', $edit_url, $copy_url, $edit_anchor_class,
+ $edit_str, $copy_str, $del_str, $js_conf
+ );
+
+ } // end of the '_getPlacedLinks()' function
+
+
+ /**
+ * Get the combined classes for a column
+ *
+ * @param string $grid_edit_class the class for all editable columns
+ * @param string $not_null_class the class for not null columns
+ * @param string $relation_class the class for relations in a column
+ * @param string $hide_class the class for visibility of a column
+ * @param string $field_type_class the class related to type of the field
+ * @param integer $row_no the row index
+ *
+ * @return string $class the combined classes
+ *
+ * @access private
+ *
+ * @see _getTableBody()
+ */
+ private function _getClassesForColumn(
+ $grid_edit_class, $not_null_class, $relation_class,
+ $hide_class, $field_type_class, $row_no
+ ) {
+
+ $printview = $this->__get('printview');
+
+ $class = 'data ' . $grid_edit_class . ' ' . $not_null_class . ' '
+ . $relation_class . ' ' . $hide_class . ' ' . $field_type_class;
+
+ $disp_direction = $_SESSION['tmpval']['disp_direction'];
+ if (($disp_direction == self::DISP_DIR_VERTICAL)
+ && (! isset($printview) || ($printview != '1'))
+ ) {
+ // the row number corresponds to a data row, not HTML table row
+ $class .= ' row_' . $row_no;
+ if ($GLOBALS['cfg']['BrowsePointerEnable'] == true) {
+ $class .= ' vpointer';
+ }
+
+ if ($GLOBALS['cfg']['BrowseMarkerEnable'] == true) {
+ $class .= ' vmarker';
+ }
+ }
+
+ return $class;
+
+ } // end of the '_getClassesForColumn()' function
+
+
+ /**
+ * Get class for datetime related fields
+ *
+ * @param string $type the type of the column field
+ *
+ * @return string $field_type_class the class for the column
+ *
+ * @access private
+ *
+ * @see _getTableBody()
+ */
+ private function _getClassForDateTimeRelatedFields($type)
+ {
+ if ((substr($type, 0, 9) == self::TIMESTAMP_FIELD)
+ || ($type == self::DATETIME_FIELD)
+ ) {
+ $field_type_class = 'datetimefield';
+ } else if ($type == self::DATE_FIELD) {
+ $field_type_class = 'datefield';
+ } else {
+ $field_type_class = '';
+ }
+ return $field_type_class;
+ } // end of the '_getClassForDateTimeRelatedFields()' function
+
+
+ /**
+ * Prepare data cell for numeric type fields
+ *
+ * @param string $column the relevant column in data row
+ * @param string $class the html class for column
+ * @param boolean $condition_field the column should highlighted
+ * or not
+ * @param object $meta the meta-information about this
+ * field
+ * @param array $map the list of relations
+ * @param boolean $is_field_truncated the condition for blob data
+ * replacements
+ * @param array $analyzed_sql the analyzed query
+ * @param string $transformation_plugin the name of transformation plugin
+ * @param string $default_function the default transformation function
+ * @param string $transform_options the transformation parameters
+ *
+ * @return string $cell the prepared cell, html content
+ *
+ * @access private
+ *
+ * @see _getTableBody()
+ */
+ private function _getDataCellForNumericColumns(
+ $column, $class, $condition_field, $meta, $map, $is_field_truncated,
+ $analyzed_sql, $transformation_plugin, $default_function,
+ $transform_options
+ ) {
+
+ if (! isset($column) || is_null($column)) {
+
+ $cell = $this->_buildNullDisplay(
+ 'right '.$class, $condition_field, $meta, ''
+ );
+
+ } elseif ($column != '') {
+
+ $nowrap = ' nowrap';
+ $where_comparison = ' = ' . $column;
+
+ $cell = $this->_getRowData(
+ 'right '.$class, $condition_field,
+ $analyzed_sql, $meta, $map, $column,
+ $transformation_plugin, $default_function, $nowrap,
+ $where_comparison, $transform_options,
+ $is_field_truncated
+ );
+ } else {
+
+ $cell = $this->_buildEmptyDisplay(
+ 'right '.$class, $condition_field, $meta, ''
+ );
+ }
+
+ return $cell;
+
+ } // end of the '_getDataCellForNumericColumns()' function
+
+
+ /**
+ * Get data cell for blob type fields
+ *
+ * @param string $column the relevant column in data row
+ * @param string $class the html class for column
+ * @param object $meta the meta-information about this
+ * field
+ * @param array $_url_params the parameters for generate url
+ * @param string $field_flags field flags for column(blob,
+ * primary etc)
+ * @param string $transformation_plugin the name of transformation function
+ * @param string $default_function the default transformation function
+ * @param string $transform_options the transformation parameters
+ * @param boolean $condition_field the column should highlighted
+ * or not
+ * @param boolean $is_field_truncated the condition for blob data
+ * replacements
+ *
+ * @return string $cell the prepared cell, html content
+ *
+ * @access private
+ *
+ * @see _getTableBody()
+ */
+ private function _getDataCellForBlobColumns(
+ $column, $class, $meta, $_url_params, $field_flags, $transformation_plugin,
+ $default_function, $transform_options, $condition_field, $is_field_truncated
+ ) {
+
+ if (stristr($field_flags, self::BINARY_FIELD)) {
+
+ // remove 'grid_edit' from $class as we can't edit binary data.
+ $class = str_replace('grid_edit', '', $class);
+
+ if (! isset($column) || is_null($column)) {
+
+ $cell = $this->_buildNullDisplay($class, $condition_field, $meta);
+
+ } else {
+
+ $blobtext = $this->_handleNonPrintableContents(
+ self::BLOB_FIELD, (isset($column) ? $column : ''),
+ $transformation_plugin, $transform_options,
+ $default_function, $meta, $_url_params
+ );
+
+ $cell = $this->_buildValueDisplay(
+ $class, $condition_field, $blobtext
+ );
+ unset($blobtext);
+ }
+ } else {
+ // not binary:
+
+ if (! isset($column) || is_null($column)) {
+
+ $cell = $this->_buildNullDisplay($class, $condition_field, $meta);
+
+ } elseif ($column != '') {
+
+ // if a transform function for blob is set, none of these
+ // replacements will be made
+ $limitChars = $GLOBALS['cfg']['LimitChars'];
+ if (($GLOBALS['PMA_String']->strlen($column) > $limitChars)
+ && ($_SESSION['tmpval']['pftext'] == self::DISPLAY_PARTIAL_TEXT)
+ && ! $this->_isNeedToSyntaxHighlight(strtolower($meta->name))
+ ) {
+ $column = $GLOBALS['PMA_String']->substr(
+ $column, 0, $GLOBALS['cfg']['LimitChars']
+ ) . '...';
+ $is_field_truncated = true;
+ }
+
+ // displays all space characters, 4 space
+ // characters for tabulations and <cr>/<lf>
+ $column = ($default_function != $transformation_plugin)
+ ? $transformation_plugin->applyTransformation(
+ $column,
+ $transform_options,
+ $meta
+ )
+ : $this->$default_function($column, array(), $meta);
+
+ if ($is_field_truncated) {
+ $class .= ' truncated';
+ }
+
+ $cell = $this->_buildValueDisplay($class, $condition_field, $column);
+
+ } else {
+ $cell = $this->_buildEmptyDisplay($class, $condition_field, $meta);
+ }
+ }
+
+ return $cell;
+
+ } // end of the '_getDataCellForBlobColumns()' function
+
+
+ /**
+ * Get data cell for geometry type fields
+ *
+ * @param string $column the relevant column in data row
+ * @param string $class the html class for column
+ * @param object $meta the meta-information about this field
+ * @param array $map the list of relations
+ * @param array $_url_params the parameters for generate url
+ * @param boolean $condition_field the column should highlighted or not
+ * @param string $transformation_plugin the name of transformation function
+ * @param string $default_function the default transformation function
+ * @param string $transform_options the transformation parameters
+ * @param boolean $is_field_truncated the condition for blob data replacements
+ * @param array $analyzed_sql the analyzed query
+ *
+ * @return string $cell the prepared data cell, html content
+ *
+ * @access private
+ *
+ * @see _getTableBody()
+ */
+ private function _getDataCellForGeometryColumns(
+ $column, $class, $meta, $map, $_url_params, $condition_field,
+ $transformation_plugin, $default_function, $transform_options,
+ $is_field_truncated, $analyzed_sql
+ ) {
+
+ $pftext = $_SESSION['tmpval']['pftext'];
+ $limitChars = $GLOBALS['cfg']['LimitChars'];
+
+ if (! isset($column) || is_null($column)) {
+
+ $cell = $this->_buildNullDisplay($class, $condition_field, $meta);
+
+ } elseif ($column != '') {
+
+ // Display as [GEOMETRY - (size)]
+ if ($_SESSION['tmpval']['geoOption'] == self::GEOMETRY_DISP_GEOM) {
+
+ $geometry_text = $this->_handleNonPrintableContents(
+ strtoupper(self::GEOMETRY_FIELD),
+ (isset($column) ? $column : ''), $transformation_plugin,
+ $transform_options, $default_function, $meta
+ );
+
+ $cell = $this->_buildValueDisplay(
+ $class, $condition_field, $geometry_text
+ );
+
+ } elseif ($_SESSION['tmpval']['geoOption'] == self::GEOMETRY_DISP_WKT) {
+ // Prepare in Well Known Text(WKT) format.
+
+ $where_comparison = ' = ' . $column;
+
+ // Convert to WKT format
+ $wktval = PMA_Util::asWKT($column);
+
+ if (($GLOBALS['PMA_String']->strlen($wktval) > $limitChars)
+ && ($pftext == self::DISPLAY_PARTIAL_TEXT)
+ ) {
+ $wktval = $GLOBALS['PMA_String']->substr(
+ $wktval, 0, $limitChars
+ ) . '...';
+ $is_field_truncated = true;
+ }
+
+ $cell = $this->_getRowData(
+ $class, $condition_field, $analyzed_sql, $meta, $map,
+ $wktval, $transformation_plugin, $default_function, '',
+ $where_comparison, $transform_options,
+ $is_field_truncated
+ );
+
+ } else {
+ // Prepare in Well Known Binary (WKB) format.
+
+ if ($_SESSION['tmpval']['display_binary']) {
+
+ $where_comparison = ' = ' . $column;
+
+ $wkbval = $this->_displayBinaryAsPrintable($column, 'binary', 8);
+
+ if (($GLOBALS['PMA_String']->strlen($wkbval) > $limitChars)
+ && ($pftext == self::DISPLAY_PARTIAL_TEXT)
+ ) {
+ $wkbval = $GLOBALS['PMA_String']->substr(
+ $wkbval, 0, $GLOBALS['cfg']['LimitChars']
+ ) . '...';
+ $is_field_truncated = true;
+ }
+
+ $cell = $this->_getRowData(
+ $class, $condition_field,
+ $analyzed_sql, $meta, $map, $wkbval,
+ $transformation_plugin, $default_function, '',
+ $where_comparison, $transform_options,
+ $is_field_truncated
+ );
+
+ } else {
+ $wkbval = $this->_handleNonPrintableContents(
+ self::BINARY_FIELD, $column, $transformation_plugin,
+ $transform_options, $default_function, $meta,
+ $_url_params
+ );
+
+ $cell = $this->_buildValueDisplay(
+ $class, $condition_field, $wkbval
+ );
+ }
+ }
+ } else {
+ $cell = $this->_buildEmptyDisplay($class, $condition_field, $meta);
+ }
+
+ return $cell;
+
+ } // end of the '_getDataCellForGeometryColumns()' function
+
+
+ /**
+ * Get data cell for non numeric and non blob type fields
+ *
+ * @param string $column the relevant column in data row
+ * @param string $class the html class for column
+ * @param object $meta the meta-information about the field
+ * @param array $map the list of relations
+ * @param array $_url_params the parameters for generate url
+ * @param boolean $condition_field the column should highlighted
+ * or not
+ * @param string $transformation_plugin the name of transformation function
+ * @param string $default_function the default transformation function
+ * @param string $transform_options the transformation parameters
+ * @param boolean $is_field_truncated the condition for blob data
+ * replacements
+ * @param array $analyzed_sql the analyzed query
+ * @param integer &$dt_result the link id associated to the query
+ * which results have to be displayed
+ * @param integer $col_index the column index
+ *
+ * @return string $cell the prepared data cell, html content
+ *
+ * @access private
+ *
+ * @see _getTableBody()
+ */
+ private function _getDataCellForNonNumericAndNonBlobColumns(
+ $column, $class, $meta, $map, $_url_params, $condition_field,
+ $transformation_plugin, $default_function, $transform_options,
+ $is_field_truncated, $analyzed_sql, &$dt_result, $col_index
+ ) {
+
+ $limitChars = $GLOBALS['cfg']['LimitChars'];
+ $is_analyse = $this->__get('is_analyse');
+ $field_flags = $GLOBALS['dbi']->fieldFlags($dt_result, $col_index);
+ if (stristr($field_flags, self::BINARY_FIELD)
+ && ($GLOBALS['cfg']['ProtectBinary'] == 'all'
+ || $GLOBALS['cfg']['ProtectBinary'] == 'noblob')
+ ) {
+ $class = str_replace('grid_edit', '', $class);
+ }
+
+ if (! isset($column) || is_null($column)) {
+
+ $cell = $this->_buildNullDisplay($class, $condition_field, $meta);
+
+ } elseif ($column != '') {
+
+ // Cut all fields to $GLOBALS['cfg']['LimitChars']
+ // (unless it's a link-type transformation)
+ if ($GLOBALS['PMA_String']->strlen($column) > $limitChars
+ && ($_SESSION['tmpval']['pftext'] == self::DISPLAY_PARTIAL_TEXT)
+ && ! (gettype($transformation_plugin) == "object"
+ && strpos($transformation_plugin->getName(), 'Link') !== false)
+ ) {
+ $column = $GLOBALS['PMA_String']->substr(
+ $column, 0, $GLOBALS['cfg']['LimitChars']
+ ) . '...';
+ $is_field_truncated = true;
+ }
+
+ $formatted = false;
+ if (isset($meta->_type) && $meta->_type === MYSQLI_TYPE_BIT) {
+
+ $column = PMA_Util::printableBitValue(
+ $column, $meta->length
+ );
+
+ // some results of PROCEDURE ANALYSE() are reported as
+ // being BINARY but they are quite readable,
+ // so don't treat them as BINARY
+ } elseif (stristr($field_flags, self::BINARY_FIELD)
+ && ($meta->type == self::STRING_FIELD)
+ && !(isset($is_analyse) && $is_analyse)
+ ) {
+
+ if ($_SESSION['tmpval']['display_binary']) {
+
+ // user asked to see the real contents of BINARY
+ // fields
+ $column = $this->_displayBinaryAsPrintable($column, 'binary');
+
+ } else {
+ // we show the BINARY message and field's size
+ // (or maybe use a transformation)
+ $column = $this->_handleNonPrintableContents(
+ self::BINARY_FIELD, $column, $transformation_plugin,
+ $transform_options, $default_function,
+ $meta, $_url_params
+ );
+ $formatted = true;
+ }
+ } elseif (((substr($meta->type, 0, 9) == self::TIMESTAMP_FIELD)
+ || ($meta->type == self::DATETIME_FIELD)
+ || ($meta->type == self::TIME_FIELD)
+ || ($meta->type == self::TIME_FIELD)) && (strpos ($column,"." ) === TRUE)
+ ) {
+ $column = PMA_Util::addMicroseconds($column);
+ }
+
+ if ($formatted) {
+
+ $cell = $this->_buildValueDisplay(
+ $class, $condition_field, $column
+ );
+
+ } else {
+
+ // transform functions may enable no-wrapping:
+ $function_nowrap = 'applyTransformationNoWrap';
+
+ $bool_nowrap = (($default_function != $transformation_plugin)
+ && function_exists($transformation_plugin->$function_nowrap()))
+ ? $transformation_plugin->$function_nowrap($transform_options)
+ : false;
+
+ // do not wrap if date field type
+ $nowrap = (preg_match('@DATE|TIME@i', $meta->type)
+ || $bool_nowrap) ? ' nowrap' : '';
+
+ $where_comparison = ' = \''
+ . PMA_Util::sqlAddSlashes($column)
+ . '\'';
+
+ $cell = $this->_getRowData(
+ $class, $condition_field,
+ $analyzed_sql, $meta, $map, $column,
+ $transformation_plugin, $default_function, $nowrap,
+ $where_comparison, $transform_options,
+ $is_field_truncated
+ );
+ }
+
+ } else {
+ $cell = $this->_buildEmptyDisplay($class, $condition_field, $meta);
+ }
+
+ return $cell;
+
+ } // end of the '_getDataCellForNonNumericAndNonBlobColumns()' function
+
+
+ /**
+ * Get the resulted table with the vertical direction mode.
+ *
+ * @param array $analyzed_sql the analyzed query
+ * @param array $is_display display mode
+ *
+ * @return string html content
+ *
+ * @access private
+ *
+ * @see _getTable()
+ */
+ private function _getVerticalTable($analyzed_sql, $is_display)
+ {
+
+ $vertical_table_html = '';
+ $vertical_display = $this->__get('vertical_display');
+
+ // Prepares "multi row delete" link at top if required
+ if (($GLOBALS['cfg']['RowActionLinks'] != self::POSITION_RIGHT)
+ && is_array($vertical_display['row_delete'])
+ && ((count($vertical_display['row_delete']) > 0)
+ || !empty($vertical_display['textbtn']))
+ ) {
+
+ $vertical_table_html .= '<tr>' . "\n";
+ if ($GLOBALS['cfg']['RowActionLinks'] == self::POSITION_NONE) {
+ // if we are not showing the RowActionLinks, then we need to show
+ // the Multi-Row-Action checkboxes
+ $vertical_table_html .= '<th></th>' . "\n";
+ }
+
+ $vertical_table_html .= $vertical_display['textbtn']
+ . $this->_getCheckBoxesForMultipleRowOperations('_left', $is_display)
+ . '</tr>' . "\n";
+ } // end if
+
+ // Prepares "edit" link at top if required
+ if ((($GLOBALS['cfg']['RowActionLinks'] == self::POSITION_LEFT)
+ || ($GLOBALS['cfg']['RowActionLinks'] == self::POSITION_BOTH))
+ && is_array($vertical_display['edit'])
+ && ((count($vertical_display['edit']) > 0)
+ || !empty($vertical_display['textbtn']))
+ ) {
+ $vertical_table_html .= $this->_getOperationLinksForVerticleTable(
+ 'edit'
+ );
+ } // end if
+
+ // Prepares "copy" link at top if required
+ if ((($GLOBALS['cfg']['RowActionLinks'] == self::POSITION_LEFT)
+ || ($GLOBALS['cfg']['RowActionLinks'] == self::POSITION_BOTH))
+ && is_array($vertical_display['copy'])
+ && ((count($vertical_display['copy']) > 0)
+ || !empty($vertical_display['textbtn']))
+ ) {
+ $vertical_table_html .= $this->_getOperationLinksForVerticleTable(
+ 'copy'
+ );
+ } // end if
+
+ // Prepares "delete" link at top if required
+ if ((($GLOBALS['cfg']['RowActionLinks'] == self::POSITION_LEFT)
+ || ($GLOBALS['cfg']['RowActionLinks'] == self::POSITION_BOTH))
+ && is_array($vertical_display['delete'])
+ && ((count($vertical_display['delete']) > 0)
+ || !empty($vertical_display['textbtn']))
+ ) {
+ $vertical_table_html .= $this->_getOperationLinksForVerticleTable(
+ 'delete'
+ );
+ } // end if
+
+ list($col_order, $col_visib) = $this->_getColumnParams($analyzed_sql);
+
+ // Prepares data
+ foreach ($vertical_display['desc'] as $j => $val) {
+
+ // assign appropriate key with current column order
+ $key = $col_order ? $col_order[$j] : $j;
+
+ $vertical_table_html .= '<tr'
+ . (($col_visib && !$col_visib[$j]) ? ' class="hide"' : '')
+ . '>' . "\n"
+ . $val;
+
+ $cell_displayed = 0;
+ foreach ($vertical_display['rowdata'][$key] as $subval) {
+
+ if (($cell_displayed != 0)
+ && ($_SESSION['tmpval']['repeat_cells'] != 0)
+ && ! ($cell_displayed % $_SESSION['tmpval']['repeat_cells'])
+ ) {
+ $vertical_table_html .= $val;
+ }
+
+ $vertical_table_html .= $subval;
+ $cell_displayed++;
+
+ } // end while
+
+ $vertical_table_html .= '</tr>' . "\n";
+ } // end while
+
+ // Prepares "multi row delete" link at bottom if required
+ if ((($GLOBALS['cfg']['RowActionLinks'] == self::POSITION_RIGHT)
+ || ($GLOBALS['cfg']['RowActionLinks'] == self::POSITION_BOTH))
+ && is_array($vertical_display['row_delete'])
+ && ((count($vertical_display['row_delete']) > 0)
+ || !empty($vertical_display['textbtn']))
+ ) {
+
+ $vertical_table_html .= '<tr>' . "\n"
+ . $vertical_display['textbtn']
+ . $this->_getCheckBoxesForMultipleRowOperations(
+ '_right', $is_display
+ )
+ . '</tr>' . "\n";
+ } // end if
+
+ // Prepares "edit" link at bottom if required
+ if ((($GLOBALS['cfg']['RowActionLinks'] == self::POSITION_RIGHT)
+ || ($GLOBALS['cfg']['RowActionLinks'] == self::POSITION_BOTH))
+ && is_array($vertical_display['edit'])
+ && ((count($vertical_display['edit']) > 0)
+ || !empty($vertical_display['textbtn']))
+ ) {
+ $vertical_table_html .= $this->_getOperationLinksForVerticleTable(
+ 'edit'
+ );
+ } // end if
+
+ // Prepares "copy" link at bottom if required
+ if ((($GLOBALS['cfg']['RowActionLinks'] == self::POSITION_RIGHT)
+ || ($GLOBALS['cfg']['RowActionLinks'] == self::POSITION_BOTH))
+ && is_array($vertical_display['copy'])
+ && ((count($vertical_display['copy']) > 0)
+ || !empty($vertical_display['textbtn']))
+ ) {
+ $vertical_table_html .= $this->_getOperationLinksForVerticleTable(
+ 'copy'
+ );
+ } // end if
+
+ // Prepares "delete" link at bottom if required
+ if ((($GLOBALS['cfg']['RowActionLinks'] == self::POSITION_RIGHT)
+ || ($GLOBALS['cfg']['RowActionLinks'] == self::POSITION_BOTH))
+ && is_array($vertical_display['delete'])
+ && ((count($vertical_display['delete']) > 0)
+ || !empty($vertical_display['textbtn']))
+ ) {
+ $vertical_table_html .= $this->_getOperationLinksForVerticleTable(
+ 'delete'
+ );
+ }
+
+ return $vertical_table_html;
+
+ } // end of the '_getVerticalTable' function
+
+
+ /**
+ * Prepare edit, copy and delete links for verticle table
+ *
+ * @param string $operation edit/copy/delete
+ *
+ * @return string $links_html html content
+ *
+ * @access private
+ *
+ * @see _getVerticalTable()
+ */
+ private function _getOperationLinksForVerticleTable($operation)
+ {
+
+ $link_html = '<tr>' . "\n";
+ $vertical_display = $this->__get('vertical_display');
+
+ if (! is_array($vertical_display['row_delete'])) {
+
+ if (($operation == 'edit') || ($operation == 'copy')) {
+ $link_html .= $vertical_display['textbtn'];
+
+ } elseif ($operation == 'delete') {
+
+ if (! is_array($vertical_display['edit'])) {
+ $link_html .= $vertical_display['textbtn'];
+ }
+ }
+ }
+
+ foreach ($vertical_display[$operation] as $val) {
+ $link_html .= $val;
+ } // end while
+
+ $link_html .= '</tr>' . "\n";
+
+ return $link_html;
+
+ } // end of the '_getOperationLinksForVerticleTable' function
+
+
+ /**
+ * Get checkboxes for multiple row data operations
+ *
+ * @param string $dir _left / _right
+ * @param array $is_display display mode
+ *
+ * @return String $checkBoxes_html html content
+ *
+ * @access private
+ *
+ * @see _getVerticalTable()
+ */
+ private function _getCheckBoxesForMultipleRowOperations($dir, $is_display)
+ {
+
+ $checkBoxes_html = '';
+ $cell_displayed = 0;
+ $vertical_display = $this->__get('vertical_display');
+
+ foreach ($vertical_display['row_delete'] as $val) {
+
+ if (($cell_displayed != 0)
+ && ($_SESSION['tmpval']['repeat_cells'] != 0)
+ && !($cell_displayed % $_SESSION['tmpval']['repeat_cells'])
+ ) {
+
+ $checkBoxes_html .= '<th'
+ . (($is_display['edit_lnk'] != self::NO_EDIT_OR_DELETE)
+ && ($is_display['del_lnk'] != self::NO_EDIT_OR_DELETE))
+ ? ' rowspan="4"'
+ : ''
+ . '></th>' . "\n";
+
+ }
+
+ $checkBoxes_html .= str_replace('[%_PMA_CHECKBOX_DIR_%]', $dir, $val);
+ $cell_displayed++;
+ } // end while
+
+ return $checkBoxes_html;
+
+ } // end of the '_getCheckBoxesForMultipleRowOperations' function
+
+
+ /**
+ * Checks the posted options for viewing query resutls
+ * and sets appropriate values in the session.
+ *
+ * @todo make maximum remembered queries configurable
+ * @todo move/split into SQL class!?
+ * @todo currently this is called twice unnecessary
+ * @todo ignore LIMIT and ORDER in query!?
+ *
+ * @return void
+ *
+ * @access public
+ *
+ * @see sql.php file
+ */
+ public function setConfigParamsForDisplayTable()
+ {
+
+ $sql_md5 = md5($this->__get('sql_query'));
+ $query = array();
+ if (isset($_SESSION['tmpval']['query'][$sql_md5])) {
+ $query = $_SESSION['tmpval']['query'][$sql_md5];
+ }
+
+ $query['sql'] = $this->__get('sql_query');
+
+ $valid_disp_dir = PMA_isValid(
+ $_REQUEST['disp_direction'],
+ array(self::DISP_DIR_HORIZONTAL, self::DISP_DIR_VERTICAL,
+ self::DISP_DIR_HORIZONTAL_FLIPPED
+ )
+ );
+
+ if ($valid_disp_dir) {
+ $query['disp_direction'] = $_REQUEST['disp_direction'];
+ unset($_REQUEST['disp_direction']);
+ } elseif (empty($query['disp_direction'])) {
+ $query['disp_direction'] = $GLOBALS['cfg']['DefaultDisplay'];
+ }
+
+ if (empty($query['repeat_cells'])) {
+ $query['repeat_cells'] = $GLOBALS['cfg']['RepeatCells'];
+ }
+
+ // as this is a form value, the type is always string so we cannot
+ // use PMA_isValid($_REQUEST['session_max_rows'], 'integer')
+ if (PMA_isValid($_REQUEST['session_max_rows'], 'numeric')) {
+ $query['max_rows'] = (int)$_REQUEST['session_max_rows'];
+ unset($_REQUEST['session_max_rows']);
+ } elseif ($_REQUEST['session_max_rows'] == self::ALL_ROWS) {
+ $query['max_rows'] = self::ALL_ROWS;
+ unset($_REQUEST['session_max_rows']);
+ } elseif (empty($query['max_rows'])) {
+ $query['max_rows'] = $GLOBALS['cfg']['MaxRows'];
+ }
+
+ if (PMA_isValid($_REQUEST['pos'], 'numeric')) {
+ $query['pos'] = $_REQUEST['pos'];
+ unset($_REQUEST['pos']);
+ } elseif (empty($query['pos'])) {
+ $query['pos'] = 0;
+ }
+
+ if (PMA_isValid(
+ $_REQUEST['pftext'],
+ array(
+ self::DISPLAY_PARTIAL_TEXT, self::DISPLAY_FULL_TEXT
+ )
+ )
+ ) {
+ $query['pftext'] = $_REQUEST['pftext'];
+ unset($_REQUEST['pftext']);
+ } elseif (empty($query['pftext'])) {
+ $query['pftext'] = self::DISPLAY_PARTIAL_TEXT;
+ }
+
+ if (PMA_isValid(
+ $_REQUEST['relational_display'],
+ array(
+ self::RELATIONAL_KEY, self::RELATIONAL_DISPLAY_COLUMN
+ )
+ )
+ ) {
+ $query['relational_display'] = $_REQUEST['relational_display'];
+ unset($_REQUEST['relational_display']);
+ } elseif (empty($query['relational_display'])) {
+ $query['relational_display'] = self::RELATIONAL_KEY;
+ }
+
+ if (PMA_isValid(
+ $_REQUEST['geoOption'],
+ array(
+ self::GEOMETRY_DISP_WKT, self::GEOMETRY_DISP_WKB,
+ self::GEOMETRY_DISP_GEOM
+ )
+ )
+ ) {
+ $query['geoOption'] = $_REQUEST['geoOption'];
+ unset($_REQUEST['geoOption']);
+ } elseif (empty($query['geoOption'])) {
+ $query['geoOption'] = self::GEOMETRY_DISP_GEOM;
+ }
+
+ if (isset($_REQUEST['display_binary'])) {
+ $query['display_binary'] = true;
+ unset($_REQUEST['display_binary']);
+ } elseif (isset($_REQUEST['display_options_form'])) {
+ // we know that the checkbox was unchecked
+ unset($query['display_binary']);
+ } elseif (isset($_REQUEST['full_text_button'])) {
+ // do nothing to keep the value that is there in the session
+ } else {
+ // selected by default because some operations like OPTIMIZE TABLE
+ // and all queries involving functions return "binary" contents,
+ // according to low-level field flags
+ $query['display_binary'] = true;
+ }
+
+ if (isset($_REQUEST['display_binary_as_hex'])) {
+ $query['display_binary_as_hex'] = true;
+ unset($_REQUEST['display_binary_as_hex']);
+ } elseif (isset($_REQUEST['display_options_form'])) {
+ // we know that the checkbox was unchecked
+ unset($query['display_binary_as_hex']);
+ } elseif (isset($_REQUEST['full_text_button'])) {
+ // do nothing to keep the value that is there in the session
+ } else {
+ // display_binary_as_hex config option
+ if (isset($GLOBALS['cfg']['DisplayBinaryAsHex'])
+ && ($GLOBALS['cfg']['DisplayBinaryAsHex'] === true)
+ ) {
+ $query['display_binary_as_hex'] = true;
+ }
+ }
+
+ if (isset($_REQUEST['display_blob'])) {
+ $query['display_blob'] = true;
+ unset($_REQUEST['display_blob']);
+ } elseif (isset($_REQUEST['display_options_form'])) {
+ // we know that the checkbox was unchecked
+ unset($query['display_blob']);
+ }
+
+ if (isset($_REQUEST['hide_transformation'])) {
+ $query['hide_transformation'] = true;
+ unset($_REQUEST['hide_transformation']);
+ } elseif (isset($_REQUEST['display_options_form'])) {
+ // we know that the checkbox was unchecked
+ unset($query['hide_transformation']);
+ }
+
+ // move current query to the last position, to be removed last
+ // so only least executed query will be removed if maximum remembered
+ // queries limit is reached
+ unset($_SESSION['tmpval']['query'][$sql_md5]);
+ $_SESSION['tmpval']['query'][$sql_md5] = $query;
+
+ // do not exceed a maximum number of queries to remember
+ if (count($_SESSION['tmpval']['query']) > 10) {
+ array_shift($_SESSION['tmpval']['query']);
+ //echo 'deleting one element ...';
+ }
+
+ // populate query configuration
+ $_SESSION['tmpval']['pftext']
+ = $query['pftext'];
+ $_SESSION['tmpval']['relational_display']
+ = $query['relational_display'];
+ $_SESSION['tmpval']['geoOption']
+ = $query['geoOption'];
+ $_SESSION['tmpval']['display_binary'] = isset(
+ $query['display_binary']
+ );
+ $_SESSION['tmpval']['display_binary_as_hex'] = isset(
+ $query['display_binary_as_hex']
+ );
+ $_SESSION['tmpval']['display_blob'] = isset(
+ $query['display_blob']
+ );
+ $_SESSION['tmpval']['hide_transformation'] = isset(
+ $query['hide_transformation']
+ );
+ $_SESSION['tmpval']['pos']
+ = $query['pos'];
+ $_SESSION['tmpval']['max_rows']
+ = $query['max_rows'];
+ $_SESSION['tmpval']['repeat_cells']
+ = $query['repeat_cells'];
+ $_SESSION['tmpval']['disp_direction']
+ = $query['disp_direction'];
+
+ }
+
+
+ /**
+ * Prepare a table of results returned by a SQL query.
+ * This function is called by the "sql.php" script.
+ *
+ * @param integer &$dt_result the link id associated to the query
+ * which results have to be displayed
+ * @param array &$the_disp_mode the display mode
+ * @param array $analyzed_sql the analyzed query
+ * @param boolean $is_limited_display With limited operations or not
+ *
+ * @return string $table_html Generated HTML content for resulted table
+ *
+ * @access public
+ *
+ * @see sql.php file
+ */
+ public function getTable(
+ &$dt_result, &$the_disp_mode, $analyzed_sql,
+ $is_limited_display = false
+ ) {
+
+ $table_html = '';
+ // Following variable are needed for use in isset/empty or
+ // use with array indexes/safe use in foreach
+ $fields_meta = $this->__get('fields_meta');
+ $showtable = $this->__get('showtable');
+ $printview = $this->__get('printview');
+
+ // why was this called here? (already called from sql.php)
+ //$this->setConfigParamsForDisplayTable();
+
+ /**
+ * @todo move this to a central place
+ * @todo for other future table types
+ */
+ $is_innodb = (isset($showtable['Type'])
+ && $showtable['Type'] == self::TABLE_TYPE_INNO_DB);
+
+ if ($is_innodb
+ && ! isset($analyzed_sql[0]['queryflags']['union'])
+ && ! isset($analyzed_sql[0]['table_ref'][1]['table_name'])
+ && (empty($analyzed_sql[0]['where_clause'])
+ || ($analyzed_sql[0]['where_clause'] == '1 '))
+ ) {
+ // "j u s t b r o w s i n g"
+ $pre_count = '~';
+ $after_count = PMA_Util::showHint(
+ PMA_sanitize(
+ __('May be approximate. See [doc@faq3-11]FAQ 3.11[/doc]')
+ )
+ );
+ } else {
+ $pre_count = '';
+ $after_count = '';
+ }
+
+ // 1. ----- Prepares the work -----
+
+ // 1.1 Gets the informations about which functionalities should be
+ // displayed
+ $total = '';
+ $is_display = $this->_setDisplayMode($the_disp_mode, $total);
+
+ // 1.2 Defines offsets for the next and previous pages
+ if ($is_display['nav_bar'] == '1') {
+ list($pos_next, $pos_prev) = $this->_getOffsets();
+ } // end if
+ if (!isset($analyzed_sql[0]['order_by_clause'])) {
+ $analyzed_sql[0]['order_by_clause'] = "";
+ }
+
+ // 1.3 Find the sort expression
+ // we need $sort_expression and $sort_expression_nodirection
+ // even if there are many table references
+ list($sort_expression, $sort_expression_nodirection, $sort_direction)
+ = $this->_getSortParams($analyzed_sql[0]['order_by_clause']);
+
+
+ // 1.4 Prepares display of first and last value of the sorted column
+
+ $sorted_column_message = $this->_getSortedColumnMessage(
+ $dt_result, $sort_expression_nodirection
+ );
+
+
+ // 2. ----- Prepare to display the top of the page -----
+
+ // 2.1 Prepares a messages with position informations
+ if (($is_display['nav_bar'] == '1') && isset($pos_next)) {
+
+ $message = $this->_setMessageInformation(
+ $sorted_column_message, $analyzed_sql[0]['limit_clause'],
+ $total, $pos_next, $pre_count, $after_count
+ );
+
+ $table_html .= PMA_Util::getMessage(
+ $message, $this->__get('sql_query'), 'success'
+ );
+
+ } elseif (! isset($printview) || ($printview != '1')) {
+
+ $table_html .= PMA_Util::getMessage(
+ __('Your SQL query has been executed successfully'),
+ $this->__get('sql_query'), 'success'
+ );
+ }
+
+ // 2.3 Prepare the navigation bars
+ if (! strlen($this->__get('table'))) {
+
+ if (isset($analyzed_sql[0]['query_type'])
+ && ($analyzed_sql[0]['query_type'] == self::QUERY_TYPE_SELECT)
+ ) {
+ // table does not always contain a real table name,
+ // for example in MySQL 5.0.x, the query SHOW STATUS
+ // returns STATUS as a table name
+ $this->__set('table', $fields_meta[0]->table);
+ } else {
+ $this->__set('table', '');
+ }
+
+ }
+
+ if (($is_display['nav_bar'] == '1')
+ && empty($analyzed_sql[0]['limit_clause'])
+ ) {
+
+ $table_html .= $this->_getPlacedTableNavigations(
+ $pos_next, $pos_prev, self::PLACE_TOP_DIRECTION_DROPDOWN,
+ "\n", $is_innodb
+ );
+
+ } elseif (! isset($printview) || ($printview != '1')) {
+ $table_html .= "\n" . '<br /><br />' . "\n";
+ }
+
+ // 2b ----- Get field references from Database -----
+ // (see the 'relation' configuration variable)
+
+ // initialize map
+ $map = array();
+
+ // find tables
+ $target=array();
+ if (isset($analyzed_sql[0]['table_ref'])
+ && is_array($analyzed_sql[0]['table_ref'])
+ ) {
+
+ foreach ($analyzed_sql[0]['table_ref']
+ as $table_ref_position => $table_ref) {
+ $target[] = $analyzed_sql[0]['table_ref']
+ [$table_ref_position]['table_true_name'];
+ }
+
+ }
+
+ if (strlen($this->__get('table'))) {
+ // This method set the values for $map array
+ $this->_setParamForLinkForeignKeyRelatedTables($map);
+ } // end if
+ // end 2b
+
+ // 3. ----- Prepare the results table -----
+ $table_html .= $this->_getTableHeaders(
+ $is_display, $analyzed_sql, $sort_expression,
+ $sort_expression_nodirection, $sort_direction, $is_limited_display
+ )
+ . '<tbody>' . "\n";
+
+ $table_html .= $this->_getTableBody(
+ $dt_result, $is_display, $map, $analyzed_sql, $is_limited_display
+ );
+
+ // vertical output case
+ if ($_SESSION['tmpval']['disp_direction'] == self::DISP_DIR_VERTICAL) {
+ $table_html .= $this->_getVerticalTable($analyzed_sql, $is_display);
+ } // end if
+
+ $this->__set('vertical_display', null);
+
+ $table_html .= '</tbody>' . "\n"
+ . '</table>';
+
+ // 4. ----- Prepares the link for multi-fields edit and delete
+
+ if ($is_display['del_lnk'] == self::DELETE_ROW
+ && $is_display['del_lnk'] != self::KILL_PROCESS
+ ) {
+
+ $table_html .= $this->_getMultiRowOperationLinks(
+ $dt_result, $analyzed_sql, $is_display['del_lnk']
+ );
+
+ }
+
+ // 5. ----- Get the navigation bar at the bottom if required -----
+ if (($is_display['nav_bar'] == '1')
+ && empty($analyzed_sql[0]['limit_clause'])
+ ) {
+ $table_html .= $this->_getPlacedTableNavigations(
+ $pos_next, $pos_prev, self::PLACE_BOTTOM_DIRECTION_DROPDOWN,
+ '<br />' . "\n", $is_innodb
+ );
+ } elseif (! isset($printview) || ($printview != '1')) {
+ $table_html .= "\n" . '<br /><br />' . "\n";
+ }
+
+
+ // 6. ----- Prepare "Query results operations"
+ if ((! isset($printview) || ($printview != '1')) && ! $is_limited_display) {
+ $table_html .= $this->_getResultsOperations(
+ $the_disp_mode, $analyzed_sql
+ );
+ }
+
+ return $table_html;
+
+ } // end of the 'getTable()' function
+
+
+ /**
+ * Get offsets for next page and previous page
+ *
+ * @return array array with two elements - $pos_next, $pos_prev
+ *
+ * @access private
+ *
+ * @see getTable()
+ */
+ private function _getOffsets()
+ {
+
+ if ($_SESSION['tmpval']['max_rows'] == self::ALL_ROWS) {
+ $pos_next = 0;
+ $pos_prev = 0;
+ } else {
+
+ $pos_next = $_SESSION['tmpval']['pos']
+ + $_SESSION['tmpval']['max_rows'];
+
+ $pos_prev = $_SESSION['tmpval']['pos']
+ - $_SESSION['tmpval']['max_rows'];
+
+ if ($pos_prev < 0) {
+ $pos_prev = 0;
+ }
+ }
+
+ return array($pos_next, $pos_prev);
+
+ } // end of the '_getOffsets()' function
+
+
+ /**
+ * Get sort parameters
+ *
+ * @param string $order_by_clause the order by clause of the sql query
+ *
+ * @return array 3 element array: $sort_expression,
+ * $sort_expression_nodirection, $sort_direction
+ *
+ * @access private
+ *
+ * @see getTable()
+ */
+ private function _getSortParams($order_by_clause)
+ {
+
+ if (! empty($order_by_clause)) {
+
+ $sort_expression = trim(
+ str_replace(' ', ' ', $order_by_clause)
+ );
+ /**
+ * Get rid of ASC|DESC
+ */
+ preg_match(
+ '@(.*)([[:space:]]*(ASC|DESC))@si', $sort_expression, $matches
+ );
+
+ $sort_expression_nodirection = isset($matches[1])
+ ? trim($matches[1])
+ : $sort_expression;
+
+ $sort_direction = isset($matches[2]) ? trim($matches[2]) : '';
+ unset($matches);
+
+ } else {
+ $sort_expression = $sort_expression_nodirection = $sort_direction = '';
+ }
+
+ return array($sort_expression, $sort_expression_nodirection,
+ $sort_direction
+ );
+
+ } // end of the '_getSortParams()' function
+
+
+ /**
+ * Prepare sorted column message
+ *
+ * @param integer &$dt_result the link id associated to the
+ * query which results have to
+ * be displayed
+ * @param string $sort_expression_nodirection sort expression without direction
+ *
+ * @return string html content
+ * null if not found sorted column
+ *
+ * @access private
+ *
+ * @see getTable()
+ */
+ private function _getSortedColumnMessage(
+ &$dt_result, $sort_expression_nodirection
+ ) {
+
+ $fields_meta = $this->__get('fields_meta'); // To use array indexes
+
+ if (! empty($sort_expression_nodirection)) {
+
+ if (strpos($sort_expression_nodirection, '.') === false) {
+ $sort_table = $this->__get('table');
+ $sort_column = $sort_expression_nodirection;
+ } else {
+ list($sort_table, $sort_column)
+ = explode('.', $sort_expression_nodirection);
+ }
+
+ $sort_table = PMA_Util::unQuote($sort_table);
+ $sort_column = PMA_Util::unQuote($sort_column);
+
+ // find the sorted column index in row result
+ // (this might be a multi-table query)
+ $sorted_column_index = false;
+
+ foreach ($fields_meta as $key => $meta) {
+ if (($meta->table == $sort_table) && ($meta->name == $sort_column)) {
+ $sorted_column_index = $key;
+ break;
+ }
+ }
+
+ if ($sorted_column_index !== false) {
+
+ // fetch first row of the result set
+ $row = $GLOBALS['dbi']->fetchRow($dt_result);
+
+ // initializing default arguments
+ $default_function = '_mimeDefaultFunction';
+ $transformation_plugin = $default_function;
+ $transform_options = array();
+
+ // check for non printable sorted row data
+ $meta = $fields_meta[$sorted_column_index];
+
+ if (stristr($meta->type, self::BLOB_FIELD)
+ || ($meta->type == self::GEOMETRY_FIELD)
+ ) {
+
+ $column_for_first_row = $this->_handleNonPrintableContents(
+ $meta->type, $row[$sorted_column_index],
+ $transformation_plugin, $transform_options,
+ $default_function, $meta, null
+ );
+
+ } else {
+ $column_for_first_row = $row[$sorted_column_index];
+ }
+
+ $column_for_first_row = strtoupper(
+ substr($column_for_first_row, 0, $GLOBALS['cfg']['LimitChars'])
+ );
+
+ // fetch last row of the result set
+ $GLOBALS['dbi']->dataSeek($dt_result, $this->__get('num_rows') - 1);
+ $row = $GLOBALS['dbi']->fetchRow($dt_result);
+
+ // check for non printable sorted row data
+ $meta = $fields_meta[$sorted_column_index];
+ if (stristr($meta->type, self::BLOB_FIELD)
+ || ($meta->type == self::GEOMETRY_FIELD)
+ ) {
+
+ $column_for_last_row = $this->_handleNonPrintableContents(
+ $meta->type, $row[$sorted_column_index],
+ $transformation_plugin, $transform_options,
+ $default_function, $meta, null
+ );
+
+ } else {
+ $column_for_last_row = $row[$sorted_column_index];
+ }
+
+ $column_for_last_row = strtoupper(
+ substr($column_for_last_row, 0, $GLOBALS['cfg']['LimitChars'])
+ );
+
+ // reset to first row for the loop in _getTableBody()
+ $GLOBALS['dbi']->dataSeek($dt_result, 0);
+
+ // we could also use here $sort_expression_nodirection
+ return ' [' . htmlspecialchars($sort_column)
+ . ': <strong>' . htmlspecialchars($column_for_first_row) . ' - '
+ . htmlspecialchars($column_for_last_row) . '</strong>]';
+ }
+ }
+
+ return null;
+
+ } // end of the '_getSortedColumnMessage()' function
+
+
+ /**
+ * Set the content need to be show in message
+ *
+ * @param string $sorted_column_message the message for sorted column
+ * @param string $limit_clause the limit clause of analyzed query
+ * @param integer $total the total number of rows returned by
+ * the SQL query without any
+ * programmatically appended LIMIT clause
+ * @param integer $pos_next the offset for next page
+ * @param string $pre_count the string renders before row count
+ * @param string $after_count the string renders after row count
+ *
+ * @return PMA_Message $message an object of PMA_Message
+ *
+ * @access private
+ *
+ * @see getTable()
+ */
+ private function _setMessageInformation(
+ $sorted_column_message, $limit_clause, $total,
+ $pos_next, $pre_count, $after_count
+ ) {
+
+ $unlim_num_rows = $this->__get('unlim_num_rows'); // To use in isset()
+
+ if (isset($unlim_num_rows) && ($unlim_num_rows != $total)) {
+ $selectstring = ', ' . $unlim_num_rows . ' ' . __('in query');
+ } else {
+ $selectstring = '';
+ }
+
+ if (! empty($limit_clause)) {
+
+ $limit_data
+ = PMA_Util::analyzeLimitClause($limit_clause);
+ $first_shown_rec = $limit_data['start'];
+
+ if ($limit_data['length'] < $total) {
+ $last_shown_rec = $limit_data['start'] + $limit_data['length'] - 1;
+ } else {
+ $last_shown_rec = $limit_data['start'] + $total - 1;
+ }
+
+ } elseif (($_SESSION['tmpval']['max_rows'] == self::ALL_ROWS)
+ || ($pos_next > $total)
+ ) {
+
+ $first_shown_rec = $_SESSION['tmpval']['pos'];
+ $last_shown_rec = $total - 1;
+
+ } else {
+
+ $first_shown_rec = $_SESSION['tmpval']['pos'];
+ $last_shown_rec = $pos_next - 1;
+
+ }
+
+ if (PMA_Table::isView($this->__get('db'), $this->__get('table'))
+ && ($total == $GLOBALS['cfg']['MaxExactCountViews'])
+ ) {
+
+ $message = PMA_Message::notice(
+ __(
+ 'This view has at least this number of rows. '
+ . 'Please refer to %sdocumentation%s.'
+ )
+ );
+
+ $message->addParam('[doc@cfg_MaxExactCount]');
+ $message->addParam('[/doc]');
+ $message_view_warning = PMA_Util::showHint($message);
+
+ } else {
+ $message_view_warning = false;
+ }
+
+ $message = PMA_Message::success(__('Showing rows %1s - %2s'));
+ $message->addParam($first_shown_rec);
+
+ if ($message_view_warning) {
+ $message->addParam('... ' . $message_view_warning, false);
+ } else {
+ $message->addParam($last_shown_rec);
+ }
+
+ $message->addMessage('(');
+
+ if (!$message_view_warning) {
+ $message_total = PMA_Message::notice($pre_count . __('%d total'));
+ $message_total->addParam($total);
+
+ if (!empty($after_count)) {
+ $message_total->addMessage($after_count);
+ }
+ $message->addMessage($message_total, '');
+
+ $message->addMessage($selectstring, '');
+ $message->addMessage(', ', '');
+ }
+
+ $message_qt = PMA_Message::notice(__('Query took %01.4f sec') . ')');
+ $message_qt->addParam($this->__get('querytime'));
+
+ $message->addMessage($message_qt, '');
+ if (! is_null($sorted_column_message)) {
+ $message->addMessage($sorted_column_message, '');
+ }
+
+ return $message;
+
+ } // end of the '_setMessageInformation()' function
+
+
+ /**
+ * Set the value of $map array for linking foreign key related tables
+ *
+ * @param array &$map the list of relations
+ *
+ * @return void
+ *
+ * @access private
+ *
+ * @see getTable()
+ */
+ private function _setParamForLinkForeignKeyRelatedTables(&$map)
+ {
+
+ // To be able to later display a link to the related table,
+ // we verify both types of relations: either those that are
+ // native foreign keys or those defined in the phpMyAdmin
+ // configuration storage. If no PMA storage, we won't be able
+ // to use the "column to display" notion (for example show
+ // the name related to a numeric id).
+ $exist_rel = PMA_getForeigners(
+ $this->__get('db'), $this->__get('table'), '', self::POSITION_BOTH
+ );
+
+ if ($exist_rel) {
+
+ foreach ($exist_rel as $master_field => $rel) {
+
+ $display_field = PMA_getDisplayField(
+ $rel['foreign_db'], $rel['foreign_table']
+ );
+
+ $map[$master_field] = array(
+ $rel['foreign_table'],
+ $rel['foreign_field'],
+ $display_field,
+ $rel['foreign_db']
+ );
+ } // end while
+ } // end if
+
+ } // end of the '_setParamForLinkForeignKeyRelatedTables()' function
+
+
+ /**
+ * Prepare multi field edit/delete links
+ *
+ * @param integer &$dt_result the link id associated to the query
+ * which results have to be displayed
+ * @param array $analyzed_sql the analyzed query
+ * @param string $del_link the display element - 'del_link'
+ *
+ * @return string $links_html html content
+ *
+ * @access private
+ *
+ * @see getTable()
+ */
+ private function _getMultiRowOperationLinks(
+ &$dt_result, $analyzed_sql, $del_link
+ ) {
+
+ $links_html = '';
+ $url_query = $this->__get('url_query');
+ $delete_text = ($del_link == self::DELETE_ROW) ? __('Delete') : __('Kill');
+
+ if ($_SESSION['tmpval']['disp_direction'] != self::DISP_DIR_VERTICAL) {
+
+ $links_html .= '<img class="selectallarrow" width="38" height="22"'
+ . ' src="' . $this->__get('pma_theme_image') . 'arrow_'
+ . $this->__get('text_dir') . '.png' . '"'
+ . ' alt="' . __('With selected:') . '" />';
+ }
+
+ $links_html .= '<input type="checkbox" id="resultsForm_checkall" '
+ . 'class="checkall_box" title="' . __('Check All') . '" /> '
+ . '<label for="resultsForm_checkall">' . __('Check All') . '</label> '
+ . '<i style="margin-left: 2em">' . __('With selected:') . '</i>' . "\n";
+
+ $links_html .= PMA_Util::getButtonOrImage(
+ 'submit_mult', 'mult_submit', 'submit_mult_change',
+ __('Change'), 'b_edit.png', 'edit'
+ );
+
+ $links_html .= PMA_Util::getButtonOrImage(
+ 'submit_mult', 'mult_submit', 'submit_mult_delete',
+ $delete_text, 'b_drop.png', 'delete'
+ );
+
+ if (isset($analyzed_sql[0])
+ && $analyzed_sql[0]['querytype'] == self::QUERY_TYPE_SELECT
+ ) {
+ $links_html .= PMA_Util::getButtonOrImage(
+ 'submit_mult', 'mult_submit', 'submit_mult_export',
+ __('Export'), 'b_tblexport.png', 'export'
+ );
+ }
+
+ $links_html .= "\n";
+
+ $links_html .= '<input type="hidden" name="sql_query"'
+ .' value="' . htmlspecialchars($this->__get('sql_query')) . '" />'
+ . "\n";
+
+ if (! empty($url_query)) {
+ $links_html .= '<input type="hidden" name="url_query"'
+ .' value="' . $url_query . '" />' . "\n";
+ }
+
+ // fetch last row of the result set
+ $GLOBALS['dbi']->dataSeek($dt_result, $this->__get('num_rows') - 1);
+ $row = $GLOBALS['dbi']->fetchRow($dt_result);
+
+ // $clause_is_unique is needed by getTable() to generate the proper param
+ // in the multi-edit and multi-delete form
+ list($where_clause, $clause_is_unique, $condition_array)
+ = PMA_Util::getUniqueCondition(
+ $dt_result,
+ $this->__get('fields_cnt'),
+ $this->__get('fields_meta'),
+ $row
+ );
+
+ // reset to first row for the loop in _getTableBody()
+ $GLOBALS['dbi']->dataSeek($dt_result, 0);
+
+ $links_html .= '<input type="hidden" name="clause_is_unique"'
+ .' value="' . $clause_is_unique . '" />' . "\n";
+
+ $links_html .= '</form>' . "\n";
+
+ return $links_html;
+
+ } // end of the '_getMultiRowOperationLinks()' function
+
+
+ /**
+ * Prepare table navigation bar at the top or bottom
+ *
+ * @param integer $pos_next the offset for the "next" page
+ * @param integer $pos_prev the offset for the "previous" page
+ * @param string $place the place to show navigation
+ * @param string $empty_line empty line depend on the $place
+ * @param boolean $is_innodb whether its InnoDB or not
+ *
+ * @return string html content of navigation bar
+ *
+ * @access private
+ *
+ * @see _getTable()
+ */
+ private function _getPlacedTableNavigations(
+ $pos_next, $pos_prev, $place, $empty_line, $is_innodb
+ ) {
+
+ $navigation_html = '';
+
+ if ($place == self::PLACE_BOTTOM_DIRECTION_DROPDOWN) {
+ $navigation_html .= '<br />' . "\n";
+ }
+
+ $navigation_html .= $this->_getTableNavigation(
+ $pos_next, $pos_prev, 'top_direction_dropdown', $is_innodb
+ );
+
+ if ($place == self::PLACE_TOP_DIRECTION_DROPDOWN) {
+ $navigation_html .= "\n";
+ }
+
+ return $navigation_html;
+
+ } // end of the '_getPlacedTableNavigations()' function
+
+ /**
+ * Generates HTML to display the Create view in span tag
+ *
+ * @param array $analyzed_sql the analyzed Query
+ * @param string $url_query String with URL Parameters
+ *
+ * @return string
+ *
+ * @access private
+ *
+ * @see _getResultsOperations()
+ */
+ private function _getLinkForCreateView($analyzed_sql, $url_query)
+ {
+ $results_operations_html = '';
+ if (!PMA_DRIZZLE && !isset($analyzed_sql[0]['queryflags']['procedure'])) {
+
+ $ajax_class = ' ajax';
+
+ $results_operations_html .= '<span>'
+ . PMA_Util::linkOrButton(
+ 'view_create.php' . $url_query,
+ PMA_Util::getIcon(
+ 'b_views.png', __('Create view'), true
+ ),
+ array('class' => 'create_view' . $ajax_class), true, true, ''
+ )
+ . '</span>' . "\n";
+ }
+ return $results_operations_html;
+
+ }
+
+ /**
+ * Calls the _getResultsOperations with $only_view as true
+ *
+ * @param array $analyzed_sql the analyzed Query
+ *
+ * @return string
+ *
+ * @access public
+ *
+ */
+ public function getCreateViewQueryResultOp($analyzed_sql)
+ {
+
+ $results_operations_html = '';
+ $fake_display_mode = array();
+ //calling to _getResultOperations with a fake display mode
+ //and setting only_view parameter to be true to generate just view
+ $results_operations_html .= $this->_getResultsOperations(
+ $fake_display_mode,
+ $analyzed_sql,
+ true
+ );
+ return $results_operations_html;
+ }
+
+ /**
+ * Get operations that are available on results.
+ *
+ * @param array $the_disp_mode the display mode
+ * @param array $analyzed_sql the analyzed query
+ * @param boolean $only_view Whether to show only view
+ *
+ * @return string $results_operations_html html content
+ *
+ * @access private
+ *
+ * @see getTable()
+ */
+ private function _getResultsOperations(
+ $the_disp_mode, $analyzed_sql, $only_view = false
+ ) {
+ global $printview;
+
+ $results_operations_html = '';
+ $fields_meta = $this->__get('fields_meta'); // To safe use in foreach
+ $header_shown = false;
+ $header = '<fieldset><legend>' . __('Query results operations')
+ . '</legend>';
+
+ $_url_params = array(
+ 'db' => $this->__get('db'),
+ 'table' => $this->__get('table'),
+ 'printview' => '1',
+ 'sql_query' => $this->__get('sql_query'),
+ );
+ $url_query = PMA_URL_getCommon($_url_params);
+
+ if (!$header_shown) {
+ $results_operations_html .= $header;
+ $header_shown = true;
+ }
+ // if empty result set was produced we need to
+ // show only view and not other options
+ if ($only_view == true) {
+ $results_operations_html .= $this->_getLinkForCreateView(
+ $analyzed_sql, $url_query
+ );
+
+ if ($header_shown) {
+ $results_operations_html .= '</fieldset><br />';
+ }
+ return $results_operations_html;
+ }
+
+ if (($the_disp_mode[6] == '1') || ($the_disp_mode[9] == '1')) {
+ // Displays "printable view" link if required
+ if ($the_disp_mode[9] == '1') {
+
+ $results_operations_html
+ .= PMA_Util::linkOrButton(
+ 'sql.php' . $url_query,
+ PMA_Util::getIcon(
+ 'b_print.png', __('Print view'), true
+ ),
+ array('target' => 'print_view'),
+ true,
+ true,
+ 'print_view'
+ )
+ . "\n";
+
+ if ($_SESSION['tmpval']['pftext']) {
+
+ $_url_params['pftext'] = self::DISPLAY_FULL_TEXT;
+
+ $results_operations_html
+ .= PMA_Util::linkOrButton(
+ 'sql.php' . PMA_URL_getCommon($_url_params),
+ PMA_Util::getIcon(
+ 'b_print.png',
+ __('Print view (with full texts)'), true
+ ),
+ array('target' => 'print_view'),
+ true,
+ true,
+ 'print_view'
+ )
+ . "\n";
+ unset($_url_params['pftext']);
+ }
+ } // end displays "printable view"
+ }
+
+ // Export link
+ // (the url_query has extra parameters that won't be used to export)
+ // (the single_table parameter is used in display_export.inc.php
+ // to hide the SQL and the structure export dialogs)
+ // If the parser found a PROCEDURE clause
+ // (most probably PROCEDURE ANALYSE()) it makes no sense to
+ // display the Export link).
+ if (isset($analyzed_sql[0])
+ && ($analyzed_sql[0]['querytype'] == self::QUERY_TYPE_SELECT)
+ && ! isset($printview)
+ && ! isset($analyzed_sql[0]['queryflags']['procedure'])
+ ) {
+
+ if (isset($analyzed_sql[0]['table_ref'][0]['table_true_name'])
+ && ! isset($analyzed_sql[0]['table_ref'][1]['table_true_name'])
+ ) {
+ $_url_params['single_table'] = 'true';
+ }
+
+ if (! $header_shown) {
+ $results_operations_html .= $header;
+ $header_shown = true;
+ }
+
+ $_url_params['unlim_num_rows'] = $this->__get('unlim_num_rows');
+
+ /**
+ * At this point we don't know the table name; this can happen
+ * for example with a query like
+ * SELECT bike_code FROM (SELECT bike_code FROM bikes) tmp
+ * As a workaround we set in the table parameter the name of the
+ * first table of this database, so that tbl_export.php and
+ * the script it calls do not fail
+ */
+ if (empty($_url_params['table']) && ! empty($_url_params['db'])) {
+ $_url_params['table'] = $GLOBALS['dbi']->fetchValue("SHOW TABLES");
+ /* No result (probably no database selected) */
+ if ($_url_params['table'] === false) {
+ unset($_url_params['table']);
+ }
+ }
+
+ $results_operations_html .= PMA_Util::linkOrButton(
+ 'tbl_export.php' . PMA_URL_getCommon($_url_params),
+ PMA_Util::getIcon(
+ 'b_tblexport.png', __('Export'), true
+ ),
+ '',
+ true,
+ true,
+ ''
+ )
+ . "\n";
+
+ // prepare chart
+ $results_operations_html .= PMA_Util::linkOrButton(
+ 'tbl_chart.php' . PMA_URL_getCommon($_url_params),
+ PMA_Util::getIcon(
+ 'b_chart.png', __('Display chart'), true
+ ),
+ '',
+ true,
+ true,
+ ''
+ )
+ . "\n";
+
+ // prepare GIS chart
+ $geometry_found = false;
+ // If atleast one geometry field is found
+ foreach ($fields_meta as $meta) {
+ if ($meta->type == self::GEOMETRY_FIELD) {
+ $geometry_found = true;
+ break;
+ }
+ }
+
+ if ($geometry_found) {
+ $results_operations_html
+ .= PMA_Util::linkOrButton(
+ 'tbl_gis_visualization.php'
+ . PMA_URL_getCommon($_url_params),
+ PMA_Util::getIcon(
+ 'b_globe.gif', __('Visualize GIS data'), true
+ ),
+ '',
+ true,
+ true,
+ ''
+ )
+ . "\n";
+ }
+ }
+
+ // CREATE VIEW
+ /**
+ *
+ * @todo detect privileges to create a view
+ * (but see 2006-01-19 note in display_create_table.lib.php,
+ * I think we cannot detect db-specific privileges reliably)
+ * Note: we don't display a Create view link if we found a PROCEDURE clause
+ */
+ if (!$header_shown) {
+ $results_operations_html .= $header;
+ $header_shown = true;
+ }
+
+ $results_operations_html .= $this->_getLinkForCreateView(
+ $analyzed_sql, $url_query
+ );
+
+ if ($header_shown) {
+ $results_operations_html .= '</fieldset><br />';
+ }
+
+ return $results_operations_html;
+
+ } // end of the '_getResultsOperations()' function
+
+
+ /**
+ * Verifies what to do with non-printable contents (binary or BLOB)
+ * in Browse mode.
+ *
+ * @param string $category BLOB|BINARY|GEOMETRY
+ * @param string $content the binary content
+ * @param string $transformation_plugin transformation plugin.
+ * Can also be the default function:
+ * PMA_mimeDefaultFunction
+ * @param string $transform_options transformation parameters
+ * @param string $default_function default transformation function
+ * @param object $meta the meta-information about the field
+ * @param array $url_params parameters that should go to the
+ * download link
+ *
+ * @return mixed string or float
+ *
+ * @access private
+ *
+ * @see _getDataCellForBlobColumns(),
+ * _getDataCellForGeometryColumns(),
+ * _getDataCellForNonNumericAndNonBlobColumns(),
+ * _getSortedColumnMessage()
+ */
+ private function _handleNonPrintableContents(
+ $category, $content, $transformation_plugin, $transform_options,
+ $default_function, $meta, $url_params = array()
+ ) {
+
+ $result = '[' . $category;
+
+ if (isset($content)) {
+
+ $size = strlen($content);
+ $display_size = PMA_Util::formatByteDown($size, 3, 1);
+ $result .= ' - '. $display_size[0] . ' ' . $display_size[1];
+
+ } else {
+
+ $result .= ' - NULL';
+ $size = 0;
+
+ }
+
+ $result .= ']';
+
+ // if we want to use a text transformation on a BLOB column
+ if (gettype($transformation_plugin) == "object"
+ && (strpos($transformation_plugin->getMIMESubtype(), 'Octetstream')
+ || strpos($transformation_plugin->getMIMEtype(), 'Text') !== false)
+ ) {
+ $result = $content;
+ }
+
+ if ($size > 0) {
+
+ if ($default_function != $transformation_plugin) {
+ $result = $transformation_plugin->applyTransformation(
+ $result,
+ $transform_options,
+ $meta
+ );
+ } else {
+
+ $result = $this->$default_function($result, array(), $meta);
+ if (stristr($meta->type, self::BLOB_FIELD)
+ && $_SESSION['tmpval']['display_blob']
+ ) {
+ // in this case, restart from the original $content
+ $result = $this->_displayBinaryAsPrintable($content, 'blob');
+ }
+
+ /* Create link to download */
+ if (count($url_params) > 0) {
+ $result = '<a href="tbl_get_field.php'
+ . PMA_URL_getCommon($url_params)
+ . '" class="disableAjax">'
+ . $result . '</a>';
+ }
+ }
+ }
+
+ return($result);
+
+ } // end of the '_handleNonPrintableContents()' function
+
+
+ /**
+ * Prepares the displayable content of a data cell in Browse mode,
+ * taking into account foreign key description field and transformations
+ *
+ * @param string $class css classes for the td element
+ * @param bool $condition_field whether the column is a part of the
+ * where clause
+ * @param string $analyzed_sql the analyzed query
+ * @param object $meta the meta-information about the field
+ * @param array $map the list of relations
+ * @param string $data data
+ * @param string $transformation_plugin transformation plugin.
+ * Can also be the default function:
+ * PMA_mimeDefaultFunction
+ * @param string $default_function default function
+ * @param string $nowrap 'nowrap' if the content should not
+ * be wrapped
+ * @param string $where_comparison data for the where clause
+ * @param array $transform_options array of options for transformation
+ * @param bool $is_field_truncated whether the field is truncated
+ *
+ * @return string formatted data
+ *
+ * @access private
+ *
+ * @see _getDataCellForNumericColumns(), _getDataCellForGeometryColumns(),
+ * _getDataCellForNonNumericAndNonBlobColumns(),
+ *
+ */
+ private function _getRowData(
+ $class, $condition_field, $analyzed_sql, $meta, $map, $data,
+ $transformation_plugin, $default_function, $nowrap, $where_comparison,
+ $transform_options, $is_field_truncated
+ ) {
+
+ $relational_display = $_SESSION['tmpval']['relational_display'];
+ $printview = $this->__get('printview');
+ $result = '<td data-decimals="'.$meta->decimals.'" data-type="'.$meta->type.'" class="'
+ . $this->_addClass(
+ $class, $condition_field, $meta, $nowrap,
+ $is_field_truncated, $transformation_plugin, $default_function
+ )
+ . '">';
+
+ if (isset($analyzed_sql[0]['select_expr'])
+ && is_array($analyzed_sql[0]['select_expr'])
+ ) {
+
+ foreach ($analyzed_sql[0]['select_expr']
+ as $select_expr_position => $select_expr
+ ) {
+
+ $alias = $analyzed_sql[0]['select_expr']
+ [$select_expr_position]['alias'];
+
+ if (isset($alias) && strlen($alias)) {
+ $true_column = $analyzed_sql[0]['select_expr']
+ [$select_expr_position]['column'];
+
+ if ($alias == $meta->name) {
+ // this change in the parameter does not matter
+ // outside of the function
+ $meta->name = $true_column;
+ } // end if
+
+ } // end if
+ } // end foreach
+ } // end if
+
+ if (isset($map[$meta->name])) {
+
+ // Field to display from the foreign table?
+ if (isset($map[$meta->name][2]) && strlen($map[$meta->name][2])) {
+
+ $dispsql = 'SELECT '
+ . PMA_Util::backquote($map[$meta->name][2])
+ . ' FROM '
+ . PMA_Util::backquote($map[$meta->name][3])
+ . '.'
+ . PMA_Util::backquote($map[$meta->name][0])
+ . ' WHERE '
+ . PMA_Util::backquote($map[$meta->name][1])
+ . $where_comparison;
+
+ $dispresult = $GLOBALS['dbi']->tryQuery(
+ $dispsql,
+ null,
+ PMA_DatabaseInterface::QUERY_STORE
+ );
+
+ if ($dispresult && $GLOBALS['dbi']->numRows($dispresult) > 0) {
+ list($dispval) = $GLOBALS['dbi']->fetchRow($dispresult, 0);
+ } else {
+ $dispval = __('Link not found');
+ }
+
+ @$GLOBALS['dbi']->freeResult($dispresult);
+
+ } else {
+ $dispval = '';
+ } // end if... else...
+
+ if (isset($printview) && ($printview == '1')) {
+
+ $result .= ($transformation_plugin != $default_function
+ ? $transformation_plugin->applyTransformation(
+ $data,
+ $transform_options,
+ $meta
+ )
+ : $this->$default_function($data)
+ )
+ . ' <code>[-&gt;' . $dispval . ']</code>';
+
+ } else {
+
+ if ($relational_display == self::RELATIONAL_KEY) {
+
+ // user chose "relational key" in the display options, so
+ // the title contains the display field
+ $title = (! empty($dispval))
+ ? ' title="' . htmlspecialchars($dispval) . '"'
+ : '';
+
+ } else {
+ $title = ' title="' . htmlspecialchars($data) . '"';
+ }
+
+ $_url_params = array(
+ 'db' => $map[$meta->name][3],
+ 'table' => $map[$meta->name][0],
+ 'pos' => '0',
+ 'sql_query' => 'SELECT * FROM '
+ . PMA_Util::backquote(
+ $map[$meta->name][3]
+ ) . '.'
+ . PMA_Util::backquote(
+ $map[$meta->name][0]
+ )
+ . ' WHERE '
+ . PMA_Util::backquote(
+ $map[$meta->name][1]
+ )
+ . $where_comparison,
+ );
+
+ $result .= '<a class="ajax" href="sql.php'
+ . PMA_URL_getCommon($_url_params)
+ . '"' . $title . '>';
+
+ if ($transformation_plugin != $default_function) {
+ // always apply a transformation on the real data,
+ // not on the display field
+ $result .= $transformation_plugin->applyTransformation(
+ $data,
+ $transform_options,
+ $meta
+ );
+ } else {
+
+ if ($relational_display == self::RELATIONAL_DISPLAY_COLUMN) {
+ // user chose "relational display field" in the
+ // display options, so show display field in the cell
+ $result .= $this->$default_function($dispval);
+ } else {
+ // otherwise display data in the cell
+ $result .= $this->$default_function($data);
+ }
+
+ }
+ $result .= '</a>';
+ }
+
+ } else {
+ $result .= ($transformation_plugin != $default_function
+ ? $transformation_plugin->applyTransformation(
+ $data,
+ $transform_options,
+ $meta
+ )
+ : $this->$default_function($data)
+ );
+ }
+
+ // create hidden field if results from structure table
+ if (isset($_GET['browse_distinct']) && ($_GET['browse_distinct'] == 1)) {
+
+ $where_comparison = " = '" . $data . "'";
+
+ $_url_params_for_show_data_row = array(
+ 'db' => $this->__get('db'),
+ 'table' => $meta->orgtable,
+ 'pos' => '0',
+ 'sql_query' => 'SELECT * FROM '
+ . PMA_Util::backquote($this->__get('db'))
+ . '.' . PMA_Util::backquote($meta->orgtable)
+ . ' WHERE '
+ . PMA_Util::backquote($meta->orgname)
+ . $where_comparison,
+ );
+
+ $result .= '<input type="hidden" class="data_browse_link" value="'
+ . PMA_URL_getCommon($_url_params_for_show_data_row). '" />';
+
+ }
+
+ $result .= '</td>' . "\n";
+
+ return $result;
+
+ } // end of the '_getRowData()' function
+
+
+ /**
+ * Prepares a checkbox for multi-row submits
+ *
+ * @param string $del_url delete url
+ * @param array $is_display array with explicit indexes for all
+ * the display elements
+ * @param string $row_no the row number
+ * @param string $where_clause_html url encoded where clause
+ * @param array $condition_array array of conditions in the where clause
+ * @param string $del_query delete query
+ * @param string $id_suffix suffix for the id
+ * @param string $class css classes for the td element
+ *
+ * @return string the generated HTML
+ *
+ * @access private
+ *
+ * @see _getTableBody(), _getCheckboxAndLinks()
+ */
+ private function _getCheckboxForMultiRowSubmissions(
+ $del_url, $is_display, $row_no, $where_clause_html, $condition_array,
+ $del_query, $id_suffix, $class
+ ) {
+
+ $ret = '';
+
+ if (! empty($del_url) && $is_display['del_lnk'] != self::KILL_PROCESS) {
+
+ $ret .= '<td ';
+ if (! empty($class)) {
+ $ret .= 'class="' . $class . '"';
+ }
+
+ $ret .= ' class="center">'
+ . '<input type="checkbox" id="id_rows_to_delete'
+ . $row_no . $id_suffix
+ . '" name="rows_to_delete[' . $row_no . ']"'
+ . ' class="multi_checkbox checkall"'
+ . ' value="' . $where_clause_html . '" '
+ . ' />'
+ . '<input type="hidden" class="condition_array" value="'
+ . htmlspecialchars(json_encode($condition_array)) . '" />'
+ . ' </td>';
+ }
+
+ return $ret;
+
+ } // end of the '_getCheckboxForMultiRowSubmissions()' function
+
+
+ /**
+ * Prepares an Edit link
+ *
+ * @param string $edit_url edit url
+ * @param string $class css classes for td element
+ * @param string $edit_str text for the edit link
+ * @param string $where_clause where clause
+ * @param string $where_clause_html url encoded where clause
+ *
+ * @return string the generated HTML
+ *
+ * @access private
+ *
+ * @see _getTableBody(), _getCheckboxAndLinks()
+ */
+ private function _getEditLink(
+ $edit_url, $class, $edit_str, $where_clause, $where_clause_html
+ ) {
+
+ $ret = '';
+ if (! empty($edit_url)) {
+
+ $ret .= '<td class="' . $class . ' center" ' . ' ><span class="nowrap">'
+ . PMA_Util::linkOrButton(
+ $edit_url, $edit_str, array(), false
+ );
+ /*
+ * Where clause for selecting this row uniquely is provided as
+ * a hidden input. Used by jQuery scripts for handling grid editing
+ */
+ if (! empty($where_clause)) {
+ $ret .= '<input type="hidden" class="where_clause" value ="'
+ . $where_clause_html . '" />';
+ }
+ $ret .= '</span></td>';
+ }
+
+ return $ret;
+
+ } // end of the '_getEditLink()' function
+
+
+ /**
+ * Prepares an Copy link
+ *
+ * @param string $copy_url copy url
+ * @param string $copy_str text for the copy link
+ * @param string $where_clause where clause
+ * @param string $where_clause_html url encoded where clause
+ * @param string $class css classes for the td element
+ *
+ * @return string the generated HTML
+ *
+ * @access private
+ *
+ * @see _getTableBody(), _getCheckboxAndLinks()
+ */
+ private function _getCopyLink(
+ $copy_url, $copy_str, $where_clause, $where_clause_html, $class
+ ) {
+
+ $ret = '';
+ if (! empty($copy_url)) {
+
+ $ret .= '<td class="';
+ if (! empty($class)) {
+ $ret .= $class . ' ';
+ }
+
+ $ret .= 'center" ' . ' ><span class="nowrap">'
+ . PMA_Util::linkOrButton(
+ $copy_url, $copy_str, array(), false
+ );
+
+ /*
+ * Where clause for selecting this row uniquely is provided as
+ * a hidden input. Used by jQuery scripts for handling grid editing
+ */
+ if (! empty($where_clause)) {
+ $ret .= '<input type="hidden" class="where_clause" value="'
+ . $where_clause_html . '" />';
+ }
+ $ret .= '</span></td>';
+ }
+
+ return $ret;
+
+ } // end of the '_getCopyLink()' function
+
+
+ /**
+ * Prepares a Delete link
+ *
+ * @param string $del_url delete url
+ * @param string $del_str text for the delete link
+ * @param string $js_conf text for the JS confirmation
+ * @param string $class css classes for the td element
+ *
+ * @return string the generated HTML
+ *
+ * @access private
+ *
+ * @see _getTableBody(), _getCheckboxAndLinks()
+ */
+ private function _getDeleteLink($del_url, $del_str, $js_conf, $class)
+ {
+
+ $ret = '';
+ if (! empty($del_url)) {
+
+ $ret .= '<td class="';
+ if (! empty($class)) {
+ $ret .= $class . ' ';
+ }
+ $ajax = PMA_Response::getInstance()->isAjax() ? ' ajax' : '';
+ $ret .= 'center" ' . ' >'
+ . PMA_Util::linkOrButton(
+ $del_url, $del_str, array('class' => 'delete_row' . $ajax), false
+ )
+ . '<div class="hide">' . $js_conf . '</div>'
+ . '</td>';
+ }
+
+ return $ret;
+
+ } // end of the '_getDeleteLink()' function
+
+
+ /**
+ * Prepare checkbox and links at some position (left or right)
+ * (only called for horizontal mode)
+ *
+ * @param string $position the position of the checkbox and links
+ * @param string $del_url delete url
+ * @param array $is_display array with explicit indexes for all the
+ * display elements
+ * @param string $row_no row number
+ * @param string $where_clause where clause
+ * @param string $where_clause_html url encoded where clause
+ * @param array $condition_array array of conditions in the where clause
+ * @param string $del_query delete query
+ * @param string $id_suffix suffix for the id
+ * @param string $edit_url edit url
+ * @param string $copy_url copy url
+ * @param string $class css classes for the td elements
+ * @param string $edit_str text for the edit link
+ * @param string $copy_str text for the copy link
+ * @param string $del_str text for the delete link
+ * @param string $js_conf text for the JS confirmation
+ *
+ * @return string the generated HTML
+ *
+ * @access private
+ *
+ * @see _getPlacedLinks()
+ */
+ private function _getCheckboxAndLinks(
+ $position, $del_url, $is_display, $row_no, $where_clause,
+ $where_clause_html, $condition_array, $del_query, $id_suffix,
+ $edit_url, $copy_url, $class, $edit_str, $copy_str, $del_str, $js_conf
+ ) {
+
+ $ret = '';
+
+ if ($position == self::POSITION_LEFT) {
+
+ $ret .= $this->_getCheckboxForMultiRowSubmissions(
+ $del_url, $is_display, $row_no, $where_clause_html, $condition_array,
+ $del_query, $id_suffix = '_left', ''
+ );
+
+ $ret .= $this->_getEditLink(
+ $edit_url, $class, $edit_str, $where_clause, $where_clause_html
+ );
+
+ $ret .= $this->_getCopyLink(
+ $copy_url, $copy_str, $where_clause, $where_clause_html, ''
+ );
+
+ $ret .= $this->_getDeleteLink($del_url, $del_str, $js_conf, '');
+
+ } elseif ($position == self::POSITION_RIGHT) {
+
+ $ret .= $this->_getDeleteLink($del_url, $del_str, $js_conf, '');
+
+ $ret .= $this->_getCopyLink(
+ $copy_url, $copy_str, $where_clause, $where_clause_html, ''
+ );
+
+ $ret .= $this->_getEditLink(
+ $edit_url, $class, $edit_str, $where_clause, $where_clause_html
+ );
+
+ $ret .= $this->_getCheckboxForMultiRowSubmissions(
+ $del_url, $is_display, $row_no, $where_clause_html, $condition_array,
+ $del_query, $id_suffix = '_right', ''
+ );
+
+ } else { // $position == self::POSITION_NONE
+
+ $ret .= $this->_getCheckboxForMultiRowSubmissions(
+ $del_url, $is_display, $row_no, $where_clause_html, $condition_array,
+ $del_query, $id_suffix = '_left', ''
+ );
+ }
+
+ return $ret;
+
+ } // end of the '_getCheckboxAndLinks()' function
+
+
+ /**
+ * Replace some html-unfriendly stuff
+ *
+ * @param string $buffer String to process
+ *
+ * @return String Escaped and cleaned up text suitable for html.
+ *
+ * @access private
+ *
+ * @see _getDataCellForBlobField(), _getRowData(),
+ * _handleNonPrintableContents()
+ */
+ private function _mimeDefaultFunction($buffer)
+ {
+ $buffer = htmlspecialchars($buffer);
+ $buffer = str_replace(
+ "\011",
+ ' &nbsp;&nbsp;&nbsp;',
+ str_replace(' ', ' &nbsp;', $buffer)
+ );
+ $buffer = preg_replace("@((\015\012)|(\015)|(\012))@", '<br />', $buffer);
+
+ return $buffer;
+ }
+
+ /**
+ * Display binary columns as hex string if requested
+ * otherwise escape the contents using the best possible way
+ *
+ * @param string $content String to parse
+ * @param string $binary_or_blob binary' or 'blob'
+ * @param int $hexlength optional, get substring
+ *
+ * @return String Displayable version of the binary string
+ *
+ * @access private
+ *
+ * @see _getDataCellForGeometryColumns
+ * _getDataCellForNonNumericAndNonBlobColumns
+ * _handleNonPrintableContents
+ */
+ private function _displayBinaryAsPrintable(
+ $content, $binary_or_blob, $hexlength = null
+ ) {
+ if ($binary_or_blob === 'binary'
+ && $_SESSION['tmpval']['display_binary_as_hex']
+ ) {
+ $content = bin2hex($content);
+ if ($hexlength !== null) {
+ $content = $GLOBALS['PMA_String']->substr($content, $hexlength);
+ }
+ } elseif (PMA_Util::containsNonPrintableAscii($content)) {
+ if (PMA_PHP_INT_VERSION < 50400) {
+ $content = htmlspecialchars(
+ PMA_Util::replaceBinaryContents(
+ $content
+ )
+ );
+ } else {
+ // The ENT_SUBSTITUTE option is available for PHP >= 5.4.0
+ $content = htmlspecialchars(
+ PMA_Util::replaceBinaryContents(
+ $content
+ ),
+ ENT_SUBSTITUTE
+ );
+ }
+ }
+ return $content;
+ }
+}
+
+?>
diff --git a/libraries/Error.class.php b/libraries/Error.class.php
new file mode 100644
index 0000000000..ab8aae1c84
--- /dev/null
+++ b/libraries/Error.class.php
@@ -0,0 +1,415 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Holds class PMA_Error
+ *
+ * @package PhpMyAdmin
+ */
+
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ * base class
+ */
+require_once './libraries/Message.class.php';
+
+/**
+ * a single error
+ *
+ * @package PhpMyAdmin
+ */
+class PMA_Error extends PMA_Message
+{
+ /**
+ * Error types
+ *
+ * @var array
+ */
+ static public $errortype = array (
+ E_ERROR => 'Error',
+ E_WARNING => 'Warning',
+ E_PARSE => 'Parsing Error',
+ E_NOTICE => 'Notice',
+ E_CORE_ERROR => 'Core Error',
+ E_CORE_WARNING => 'Core Warning',
+ E_COMPILE_ERROR => 'Compile Error',
+ E_COMPILE_WARNING => 'Compile Warning',
+ E_USER_ERROR => 'User Error',
+ E_USER_WARNING => 'User Warning',
+ E_USER_NOTICE => 'User Notice',
+ E_STRICT => 'Runtime Notice',
+ E_DEPRECATED => 'Deprecation Notice',
+ E_RECOVERABLE_ERROR => 'Catchable Fatal Error',
+ );
+
+ /**
+ * Error levels
+ *
+ * @var array
+ */
+ static public $errorlevel = array (
+ E_ERROR => 'error',
+ E_WARNING => 'error',
+ E_PARSE => 'error',
+ E_NOTICE => 'notice',
+ E_CORE_ERROR => 'error',
+ E_CORE_WARNING => 'error',
+ E_COMPILE_ERROR => 'error',
+ E_COMPILE_WARNING => 'error',
+ E_USER_ERROR => 'error',
+ E_USER_WARNING => 'error',
+ E_USER_NOTICE => 'notice',
+ E_STRICT => 'notice',
+ E_DEPRECATED => 'notice',
+ E_RECOVERABLE_ERROR => 'error',
+ );
+
+ /**
+ * The file in which the error occurred
+ *
+ * @var string
+ */
+ protected $file = '';
+
+ /**
+ * The line in which the error occurred
+ *
+ * @var integer
+ */
+ protected $line = 0;
+
+ /**
+ * Holds the backtrace for this error
+ *
+ * @var array
+ */
+ protected $backtrace = array();
+
+ /**
+ * Unique id
+ *
+ * @var string
+ */
+ protected $hash = null;
+
+ /**
+ * Constructor
+ *
+ * @param integer $errno error number
+ * @param string $errstr error message
+ * @param string $errfile file
+ * @param integer $errline line
+ */
+ public function __construct($errno, $errstr, $errfile, $errline)
+ {
+ $this->setNumber($errno);
+ $this->setMessage($errstr, false);
+ $this->setFile($errfile);
+ $this->setLine($errline);
+
+ $backtrace = debug_backtrace();
+ // remove last three calls:
+ // debug_backtrace(), handleError() and addError()
+ $backtrace = array_slice($backtrace, 3);
+
+ $this->setBacktrace($backtrace);
+ }
+
+ /**
+ * sets PMA_Error::$_backtrace
+ *
+ * @param array $backtrace backtrace
+ *
+ * @return void
+ */
+ public function setBacktrace($backtrace)
+ {
+ $this->backtrace = $backtrace;
+ }
+
+ /**
+ * sets PMA_Error::$_line
+ *
+ * @param integer $line the line
+ *
+ * @return void
+ */
+ public function setLine($line)
+ {
+ $this->line = $line;
+ }
+
+ /**
+ * sets PMA_Error::$_file
+ *
+ * @param string $file the file
+ *
+ * @return void
+ */
+ public function setFile($file)
+ {
+ $this->file = PMA_Error::relPath($file);
+ }
+
+
+ /**
+ * returns unique PMA_Error::$hash, if not exists it will be created
+ *
+ * @return string PMA_Error::$hash
+ */
+ public function getHash()
+ {
+ try {
+ $backtrace = serialize($this->getBacktrace());
+ } catch(Exception $e) {
+ $backtrace = '';
+ }
+ if ($this->hash === null) {
+ $this->hash = md5(
+ $this->getNumber() .
+ $this->getMessage() .
+ $this->getFile() .
+ $this->getLine() .
+ $backtrace
+ );
+ }
+
+ return $this->hash;
+ }
+
+ /**
+ * returns PMA_Error::$_backtrace
+ *
+ * @return array PMA_Error::$_backtrace
+ */
+ public function getBacktrace()
+ {
+ return $this->backtrace;
+ }
+
+ /**
+ * returns PMA_Error::$file
+ *
+ * @return string PMA_Error::$file
+ */
+ public function getFile()
+ {
+ return $this->file;
+ }
+
+ /**
+ * returns PMA_Error::$line
+ *
+ * @return integer PMA_Error::$line
+ */
+ public function getLine()
+ {
+ return $this->line;
+ }
+
+ /**
+ * returns type of error
+ *
+ * @return string type of error
+ */
+ public function getType()
+ {
+ return PMA_Error::$errortype[$this->getNumber()];
+ }
+
+ /**
+ * returns level of error
+ *
+ * @return string level of error
+ */
+ public function getLevel()
+ {
+ return PMA_Error::$errorlevel[$this->getNumber()];
+ }
+
+ /**
+ * returns title prepared for HTML Title-Tag
+ *
+ * @return string HTML escaped and truncated title
+ */
+ public function getHtmlTitle()
+ {
+ return htmlspecialchars(substr($this->getTitle(), 0, 100));
+ }
+
+ /**
+ * returns title for error
+ *
+ * @return string
+ */
+ public function getTitle()
+ {
+ return $this->getType() . ': ' . $this->getMessage();
+ }
+
+ /**
+ * Get HTML backtrace
+ *
+ * @return string
+ */
+ public function getBacktraceDisplay()
+ {
+ $retval = '';
+
+ foreach ($this->getBacktrace() as $step) {
+ if (isset($step['file']) && isset($step['line'])) {
+ $retval .= PMA_Error::relPath($step['file'])
+ . '#' . $step['line'] . ': ';
+ }
+ if (isset($step['class'])) {
+ $retval .= $step['class'] . $step['type'];
+ }
+ $retval .= $step['function'] . '(';
+ if (isset($step['args']) && (count($step['args']) > 1)) {
+ $retval .= "<br />\n";
+ foreach ($step['args'] as $arg) {
+ $retval .= "\t";
+ $retval .= $this->getArg($arg, $step['function']);
+ $retval .= ',' . "<br />\n";
+ }
+ } elseif (isset($step['args']) && (count($step['args']) > 0)) {
+ foreach ($step['args'] as $arg) {
+ $retval .= $this->getArg($arg, $step['function']);
+ }
+ }
+ $retval .= ')' . "<br />\n";
+ }
+
+ return $retval;
+ }
+
+ /**
+ * Get a single function argument
+ *
+ * if $function is one of include/require
+ * the $arg is converted to a relative path
+ *
+ * @param string $arg argument to process
+ * @param string $function function name
+ *
+ * @return string
+ */
+ protected function getArg($arg, $function)
+ {
+ $retval = '';
+ $include_functions = array(
+ 'include',
+ 'include_once',
+ 'require',
+ 'require_once',
+ );
+ $connect_functions = array(
+ 'mysql_connect',
+ 'mysql_pconnect',
+ 'mysqli_connect',
+ 'mysqli_real_connect',
+ 'connect',
+ '_realConnect'
+ );
+
+ if (in_array($function, $include_functions)) {
+ $retval .= PMA_Error::relPath($arg);
+ } elseif (in_array($function, $connect_functions)
+ && getType($arg) === 'string'
+ ) {
+ $retval .= getType($arg) . ' ********';
+ } elseif (is_scalar($arg)) {
+ $retval .= getType($arg) . ' '
+ . htmlspecialchars(var_export($arg, true));
+ } else {
+ $retval .= getType($arg);
+ }
+
+ return $retval;
+ }
+
+ /**
+ * Gets the error as string of HTML
+ *
+ * @return string
+ */
+ public function getDisplay()
+ {
+ $this->isDisplayed(true);
+ $retval = '<div class="' . $this->getLevel() . '">';
+ if (! $this->isUserError()) {
+ $retval .= '<strong>' . $this->getType() . '</strong>';
+ $retval .= ' in ' . $this->getFile() . '#' . $this->getLine();
+ $retval .= "<br />\n";
+ }
+ $retval .= $this->getMessage();
+ if (! $this->isUserError()) {
+ $retval .= "<br />\n";
+ $retval .= "<br />\n";
+ $retval .= "<strong>Backtrace</strong><br />\n";
+ $retval .= "<br />\n";
+ $retval .= $this->getBacktraceDisplay();
+ }
+ $retval .= '</div>';
+
+ return $retval;
+ }
+
+ /**
+ * whether this error is a user error
+ *
+ * @return boolean
+ */
+ public function isUserError()
+ {
+ return $this->getNumber() & (E_USER_WARNING | E_USER_ERROR | E_USER_NOTICE);
+ }
+
+ /**
+ * return short relative path to phpMyAdmin basedir
+ *
+ * prevent path disclosure in error message,
+ * and make users feel safe to submit error reports
+ *
+ * @param string $dest path to be shorten
+ *
+ * @return string shortened path
+ * @static
+ */
+ static function relPath($dest)
+ {
+ $dest = realpath($dest);
+
+ if (substr(PHP_OS, 0, 3) == 'WIN') {
+ $separator = '\\';
+ } else {
+ $separator = '/';
+ }
+
+ $Ahere = explode(
+ $separator,
+ realpath(__DIR__ . $separator . '..')
+ );
+ $Adest = explode($separator, $dest);
+
+ $result = '.';
+ // && count ($Adest)>0 && count($Ahere)>0 )
+ while (implode($separator, $Adest) != implode($separator, $Ahere)) {
+ if (count($Ahere) > count($Adest)) {
+ array_pop($Ahere);
+ $result .= $separator . '..';
+ } else {
+ array_pop($Adest);
+ }
+ }
+ $path = $result . str_replace(implode($separator, $Adest), '', $dest);
+ return str_replace(
+ $separator . $separator,
+ $separator,
+ $path
+ );
+ }
+}
+?>
diff --git a/libraries/Error_Handler.class.php b/libraries/Error_Handler.class.php
new file mode 100644
index 0000000000..ebb6fa103a
--- /dev/null
+++ b/libraries/Error_Handler.class.php
@@ -0,0 +1,413 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Holds class PMA_Error_Handler
+ *
+ * @package PhpMyAdmin
+ */
+
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ *
+ */
+require_once './libraries/Error.class.php';
+
+/**
+ * handling errors
+ *
+ * @package PhpMyAdmin
+ */
+class PMA_Error_Handler
+{
+ /**
+ * holds errors to be displayed or reported later ...
+ *
+ * @var array of PMA_Error
+ */
+ protected $errors = array();
+
+ /**
+ * Constructor - set PHP error handler
+ *
+ */
+ public function __construct()
+ {
+ /**
+ * Do not set ourselves as error handler in case of testsuite.
+ *
+ * This behavior is not tested there and breaks other tests as they
+ * rely on PHPUnit doing it's own error handling which we break here.
+ */
+ if (!defined('TESTSUITE')) {
+ set_error_handler(array($this, 'handleError'));
+ }
+ }
+
+ /**
+ * Destructor
+ *
+ * stores errors in session
+ *
+ */
+ public function __destruct()
+ {
+ if (isset($_SESSION)) {
+ if (! isset($_SESSION['errors'])) {
+ $_SESSION['errors'] = array();
+ }
+
+ // remember only not displayed errors
+ foreach ($this->errors as $key => $error) {
+ /**
+ * We don't want to store all errors here as it would
+ * explode user session.
+ */
+ if (count($_SESSION['errors']) >= 10) {
+ $error = new PMA_Error(
+ 0,
+ __('Too many error messages, some are not displayed.'),
+ __FILE__,
+ __LINE__
+ );
+ $_SESSION['errors'][$error->getHash()] = $error;
+ break;
+ } else if (($error instanceof PMA_Error)
+ && ! $error->isDisplayed()
+ ) {
+ $_SESSION['errors'][$key] = $error;
+ }
+ }
+ }
+ }
+
+ /**
+ * returns array with all errors
+ *
+ * @return array PMA_Error_Handler::$_errors
+ */
+ protected function getErrors()
+ {
+ $this->checkSavedErrors();
+ return $this->errors;
+ }
+
+ /**
+ * Error handler - called when errors are triggered/occurred
+ *
+ * This calls the addError() function, escaping the error string
+ *
+ * @param integer $errno error number
+ * @param string $errstr error string
+ * @param string $errfile error file
+ * @param integer $errline error line
+ *
+ * @return void
+ */
+ public function handleError($errno, $errstr, $errfile, $errline)
+ {
+ $this->addError($errstr, $errno, $errfile, $errline, true);
+ }
+
+ /**
+ * Add an error; can also be called directly (with or without escaping)
+ *
+ * The following error types cannot be handled with a user defined function:
+ * E_ERROR, E_PARSE, E_CORE_ERROR, E_CORE_WARNING, E_COMPILE_ERROR,
+ * E_COMPILE_WARNING,
+ * and most of E_STRICT raised in the file where set_error_handler() is called.
+ *
+ * Do not use the context parameter as we want to avoid storing the
+ * complete $GLOBALS inside $_SESSION['errors']
+ *
+ * @param string $errstr error string
+ * @param integer $errno error number
+ * @param string $errfile error file
+ * @param integer $errline error line
+ * @param boolean $escape whether to escape the error string
+ *
+ * @return void
+ */
+ public function addError($errstr, $errno, $errfile, $errline, $escape = true)
+ {
+ if ($escape) {
+ $errstr = htmlspecialchars($errstr);
+ }
+ // create error object
+ $error = new PMA_Error(
+ $errno,
+ $errstr,
+ $errfile,
+ $errline
+ );
+
+ // do not repeat errors
+ $this->errors[$error->getHash()] = $error;
+
+ switch ($error->getNumber()) {
+ case E_USER_NOTICE:
+ case E_USER_WARNING:
+ case E_STRICT:
+ case E_DEPRECATED:
+ case E_NOTICE:
+ case E_WARNING:
+ case E_CORE_WARNING:
+ case E_COMPILE_WARNING:
+ case E_USER_ERROR:
+ case E_RECOVERABLE_ERROR:
+ // just collect the error
+ // display is called from outside
+ break;
+ case E_ERROR:
+ case E_PARSE:
+ case E_CORE_ERROR:
+ case E_COMPILE_ERROR:
+ default:
+ // FATAL error, dislay it and exit
+ $this->dispFatalError($error);
+ exit;
+ break;
+ }
+ }
+
+
+ /**
+ * log error to configured log facility
+ *
+ * @param PMA_Error $error the error
+ *
+ * @return bool
+ *
+ * @todo finish!
+ */
+ protected function logError($error)
+ {
+ return error_log($error->getMessage());
+ }
+
+ /**
+ * trigger a custom error
+ *
+ * @param string $errorInfo error message
+ * @param integer $errorNumber error number
+ * @param string $file file name
+ * @param integer $line line number
+ *
+ * @return void
+ */
+ public function triggerError($errorInfo, $errorNumber = null,
+ $file = null, $line = null
+ ) {
+ // we could also extract file and line from backtrace
+ // and call handleError() directly
+ trigger_error($errorInfo, $errorNumber);
+ }
+
+ /**
+ * display fatal error and exit
+ *
+ * @param PMA_Error $error the error
+ *
+ * @return void
+ */
+ protected function dispFatalError($error)
+ {
+ if (! headers_sent()) {
+ $this->dispPageStart($error);
+ }
+ $error->display();
+ $this->dispPageEnd();
+ exit;
+ }
+
+ /**
+ * Displays user errors not displayed
+ *
+ * @return void
+ */
+ public function dispUserErrors()
+ {
+ echo $this->getDispUserErrors();
+ }
+
+ /**
+ * Renders user errors not displayed
+ *
+ * @return string
+ */
+ public function getDispUserErrors()
+ {
+ $retval = '';
+ foreach ($this->getErrors() as $error) {
+ if ($error->isUserError() && ! $error->isDisplayed()) {
+ $retval .= $error->getDisplay();
+ }
+ }
+ return $retval;
+ }
+
+ /**
+ * display HTML header
+ *
+ * @param PMA_error $error the error
+ *
+ * @return void
+ */
+ protected function dispPageStart($error = null)
+ {
+ PMA_Response::getInstance()->disable();
+ echo '<html><head><title>';
+ if ($error) {
+ echo $error->getTitle();
+ } else {
+ echo 'phpMyAdmin error reporting page';
+ }
+ echo '</title></head>';
+ }
+
+ /**
+ * display HTML footer
+ *
+ * @return void
+ */
+ protected function dispPageEnd()
+ {
+ echo '</body></html>';
+ }
+
+ /**
+ * renders errors not displayed
+ *
+ * @return string
+ */
+ public function getDispErrors()
+ {
+ $retval = '';
+ if ($GLOBALS['cfg']['Error_Handler']['display']) {
+ foreach ($this->getErrors() as $error) {
+ if ($error instanceof PMA_Error) {
+ if (! $error->isDisplayed()) {
+ $retval .= $error->getDisplay();
+ }
+ } else {
+ ob_start();
+ var_dump($error);
+ $retval .= ob_get_contents();
+ ob_end_clean();
+ }
+ }
+ } else {
+ $retval .= $this->getDispUserErrors();
+ }
+ return $retval;
+ }
+
+ /**
+ * displays errors not displayed
+ *
+ * @return void
+ */
+ public function dispErrors()
+ {
+ echo $this->getDispErrors();
+ }
+
+ /**
+ * look in session for saved errors
+ *
+ * @return void
+ */
+ protected function checkSavedErrors()
+ {
+ if (isset($_SESSION['errors'])) {
+
+ // restore saved errors
+ foreach ($_SESSION['errors'] as $hash => $error) {
+ if ($error instanceof PMA_Error && ! isset($this->errors[$hash])) {
+ $this->errors[$hash] = $error;
+ }
+ }
+ //$this->errors = array_merge($_SESSION['errors'], $this->errors);
+
+ // delete stored errors
+ $_SESSION['errors'] = array();
+ unset($_SESSION['errors']);
+ }
+ }
+
+ /**
+ * return count of errors
+ *
+ * @return integer number of errors occoured
+ */
+ public function countErrors()
+ {
+ return count($this->getErrors());
+ }
+
+ /**
+ * return count of user errors
+ *
+ * @return integer number of user errors occoured
+ */
+ public function countUserErrors()
+ {
+ $count = 0;
+ if ($this->countErrors()) {
+ foreach ($this->getErrors() as $error) {
+ if ($error->isUserError()) {
+ $count++;
+ }
+ }
+ }
+
+ return $count;
+ }
+
+ /**
+ * whether use errors occurred or not
+ *
+ * @return boolean
+ */
+ public function hasUserErrors()
+ {
+ return (bool) $this->countUserErrors();
+ }
+
+ /**
+ * whether errors occurred or not
+ *
+ * @return boolean
+ */
+ public function hasErrors()
+ {
+ return (bool) $this->countErrors();
+ }
+
+ /**
+ * number of errors to be displayed
+ *
+ * @return integer number of errors to be displayed
+ */
+ public function countDisplayErrors()
+ {
+ if ($GLOBALS['cfg']['Error_Handler']['display']) {
+ return $this->countErrors();
+ } else {
+ return $this->countUserErrors();
+ }
+ }
+
+ /**
+ * whether there are errors to display or not
+ *
+ * @return boolean
+ */
+ public function hasDisplayErrors()
+ {
+ return (bool) $this->countDisplayErrors();
+ }
+}
+?>
diff --git a/libraries/File.class.php b/libraries/File.class.php
new file mode 100644
index 0000000000..68fa6712bc
--- /dev/null
+++ b/libraries/File.class.php
@@ -0,0 +1,865 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * file upload functions
+ *
+ * @package PhpMyAdmin
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ * File wrapper class
+ *
+ * @todo when uploading a file into a blob field, should we also consider using
+ * chunks like in import? UPDATE `table` SET `field` = `field` + [chunk]
+ *
+ * @package PhpMyAdmin
+ */
+class PMA_File
+{
+ /**
+ * @var string the temporary file name
+ * @access protected
+ */
+ var $_name = null;
+
+ /**
+ * @var string the content
+ * @access protected
+ */
+ var $_content = null;
+
+ /**
+ * @var string the error message
+ * @access protected
+ */
+ var $_error_message = '';
+
+ /**
+ * @var bool whether the file is temporary or not
+ * @access protected
+ */
+ var $_is_temp = false;
+
+ /**
+ * @var string type of compression
+ * @access protected
+ */
+ var $_compression = null;
+
+ /**
+ * @var integer
+ */
+ var $_offset = 0;
+
+ /**
+ * @var integer size of chunk to read with every step
+ */
+ var $_chunk_size = 32768;
+
+ /**
+ * @var resource file handle
+ */
+ var $_handle = null;
+
+ /**
+ * @var boolean whether to decompress content before returning
+ */
+ var $_decompress = false;
+
+ /**
+ * @var string charset of file
+ */
+ var $_charset = null;
+
+ /**
+ * constructor
+ *
+ * @param boolean|string $name file name or false
+ *
+ * @access public
+ */
+ public function __construct($name = false)
+ {
+ if ($name) {
+ $this->setName($name);
+ }
+ }
+
+ /**
+ * destructor
+ *
+ * @see PMA_File::cleanUp()
+ * @access public
+ */
+ public function __destruct()
+ {
+ $this->cleanUp();
+ }
+
+ /**
+ * deletes file if it is temporary, usually from a moved upload file
+ *
+ * @access public
+ * @return boolean success
+ */
+ public function cleanUp()
+ {
+ if ($this->isTemp()) {
+ return $this->delete();
+ }
+
+ return true;
+ }
+
+ /**
+ * deletes the file
+ *
+ * @access public
+ * @return boolean success
+ */
+ public function delete()
+ {
+ return unlink($this->getName());
+ }
+
+ /**
+ * checks or sets the temp flag for this file
+ * file objects with temp flags are deleted with object destruction
+ *
+ * @param boolean $is_temp sets the temp flag
+ *
+ * @return boolean PMA_File::$_is_temp
+ * @access public
+ */
+ public function isTemp($is_temp = null)
+ {
+ if (null !== $is_temp) {
+ $this->_is_temp = (bool) $is_temp;
+ }
+
+ return $this->_is_temp;
+ }
+
+ /**
+ * accessor
+ *
+ * @param string $name file name
+ *
+ * @return void
+ * @access public
+ */
+ public function setName($name)
+ {
+ $this->_name = trim($name);
+ }
+
+ /**
+ * Gets file content
+ *
+ * @param boolean $as_binary whether to return content as binary
+ * @param integer $offset starting offset
+ * @param integer $length length
+ *
+ * @return mixed the binary file content as a string,
+ * or false if no content
+ *
+ * @access public
+ */
+ public function getContent($as_binary = true, $offset = 0, $length = null)
+ {
+ if (null === $this->_content) {
+ if ($this->isUploaded() && ! $this->checkUploadedFile()) {
+ return false;
+ }
+
+ if (! $this->isReadable()) {
+ return false;
+ }
+
+ if (function_exists('file_get_contents')) {
+ $this->_content = file_get_contents($this->getName());
+ } elseif ($size = filesize($this->getName())) {
+ $this->_content = fread(fopen($this->getName(), 'rb'), $size);
+ }
+ }
+
+ if (! empty($this->_content) && $as_binary) {
+ return '0x' . bin2hex($this->_content);
+ }
+
+ if (null !== $length) {
+ return substr($this->_content, $offset, $length);
+ } elseif ($offset > 0) {
+ return substr($this->_content, $offset);
+ }
+
+ return $this->_content;
+ }
+
+ /**
+ * Whether file is uploaded.
+ *
+ * @access public
+ *
+ * @return bool
+ */
+ public function isUploaded()
+ {
+ return is_uploaded_file($this->getName());
+ }
+
+ /**
+ * accessor
+ *
+ * @access public
+ * @return string PMA_File::$_name
+ */
+ public function getName()
+ {
+ return $this->_name;
+ }
+
+ /**
+ * Initializes object from uploaded file.
+ *
+ * @param string $name name of file uploaded
+ *
+ * @return boolean success
+ * @access public
+ */
+ public function setUploadedFile($name)
+ {
+ $this->setName($name);
+
+ if (! $this->isUploaded()) {
+ $this->setName(null);
+ $this->_error_message = __('File was not an uploaded file.');
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Loads uploaded file from table change request.
+ *
+ * @param string $key the md5 hash of the column name
+ * @param string $rownumber number of row to process
+ *
+ * @return boolean success
+ * @access public
+ */
+ public function setUploadedFromTblChangeRequest($key, $rownumber)
+ {
+ if (! isset($_FILES['fields_upload'])
+ || empty($_FILES['fields_upload']['name']['multi_edit'][$rownumber][$key])
+ ) {
+ return false;
+ }
+ $file = PMA_File::fetchUploadedFromTblChangeRequestMultiple(
+ $_FILES['fields_upload'],
+ $rownumber,
+ $key
+ );
+
+ // check for file upload errors
+ switch ($file['error']) {
+ // we do not use the PHP constants here cause not all constants
+ // are defined in all versions of PHP - but the correct constants names
+ // are given as comment
+ case 0: //UPLOAD_ERR_OK:
+ return $this->setUploadedFile($file['tmp_name']);
+ break;
+ case 4: //UPLOAD_ERR_NO_FILE:
+ break;
+ case 1: //UPLOAD_ERR_INI_SIZE:
+ $this->_error_message = __('The uploaded file exceeds the upload_max_filesize directive in php.ini.');
+ break;
+ case 2: //UPLOAD_ERR_FORM_SIZE:
+ $this->_error_message = __('The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form.');
+ break;
+ case 3: //UPLOAD_ERR_PARTIAL:
+ $this->_error_message = __('The uploaded file was only partially uploaded.');
+ break;
+ case 6: //UPLOAD_ERR_NO_TMP_DIR:
+ $this->_error_message = __('Missing a temporary folder.');
+ break;
+ case 7: //UPLOAD_ERR_CANT_WRITE:
+ $this->_error_message = __('Failed to write file to disk.');
+ break;
+ case 8: //UPLOAD_ERR_EXTENSION:
+ $this->_error_message = __('File upload stopped by extension.');
+ break;
+ default:
+ $this->_error_message = __('Unknown error in file upload.');
+ } // end switch
+
+ return false;
+ }
+
+ /**
+ * strips some dimension from the multi-dimensional array from $_FILES
+ *
+ * <code>
+ * $file['name']['multi_edit'][$rownumber][$key] = [value]
+ * $file['type']['multi_edit'][$rownumber][$key] = [value]
+ * $file['size']['multi_edit'][$rownumber][$key] = [value]
+ * $file['tmp_name']['multi_edit'][$rownumber][$key] = [value]
+ * $file['error']['multi_edit'][$rownumber][$key] = [value]
+ *
+ * // becomes:
+ *
+ * $file['name'] = [value]
+ * $file['type'] = [value]
+ * $file['size'] = [value]
+ * $file['tmp_name'] = [value]
+ * $file['error'] = [value]
+ * </code>
+ *
+ * @param array $file the array
+ * @param string $rownumber number of row to process
+ * @param string $key key to process
+ *
+ * @return array
+ * @access public
+ * @static
+ */
+ public function fetchUploadedFromTblChangeRequestMultiple(
+ $file, $rownumber, $key
+ ) {
+ $new_file = array(
+ 'name' => $file['name']['multi_edit'][$rownumber][$key],
+ 'type' => $file['type']['multi_edit'][$rownumber][$key],
+ 'size' => $file['size']['multi_edit'][$rownumber][$key],
+ 'tmp_name' => $file['tmp_name']['multi_edit'][$rownumber][$key],
+ 'error' => $file['error']['multi_edit'][$rownumber][$key],
+ );
+
+ return $new_file;
+ }
+
+ /**
+ * sets the name if the file to the one selected in the tbl_change form
+ *
+ * @param string $key the md5 hash of the column name
+ * @param string $rownumber number of row to process
+ *
+ * @return boolean success
+ * @access public
+ */
+ public function setSelectedFromTblChangeRequest($key, $rownumber = null)
+ {
+ if (! empty($_REQUEST['fields_uploadlocal']['multi_edit'][$rownumber][$key])
+ && is_string($_REQUEST['fields_uploadlocal']['multi_edit'][$rownumber][$key])
+ ) {
+ // ... whether with multiple rows ...
+ return $this->setLocalSelectedFile(
+ $_REQUEST['fields_uploadlocal']['multi_edit'][$rownumber][$key]
+ );
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Returns possible error message.
+ *
+ * @access public
+ * @return string error message
+ */
+ public function getError()
+ {
+ return $this->_error_message;
+ }
+
+ /**
+ * Checks whether there was any error.
+ *
+ * @access public
+ * @return boolean whether an error occurred or not
+ */
+ public function isError()
+ {
+ return ! empty($this->_error_message);
+ }
+
+ /**
+ * checks the superglobals provided if the tbl_change form is submitted
+ * and uses the submitted/selected file
+ *
+ * @param string $key the md5 hash of the column name
+ * @param string $rownumber number of row to process
+ *
+ * @return boolean success
+ * @access public
+ */
+ public function checkTblChangeForm($key, $rownumber)
+ {
+ if ($this->setUploadedFromTblChangeRequest($key, $rownumber)) {
+ // well done ...
+ $this->_error_message = '';
+ return true;
+ } elseif ($this->setSelectedFromTblChangeRequest($key, $rownumber)) {
+ // well done ...
+ $this->_error_message = '';
+ return true;
+ }
+ // all failed, whether just no file uploaded/selected or an error
+
+ return false;
+ }
+
+ /**
+ * Sets named file to be read from UploadDir.
+ *
+ * @param string $name file name
+ *
+ * @return boolean success
+ * @access public
+ */
+ public function setLocalSelectedFile($name)
+ {
+ if (empty($GLOBALS['cfg']['UploadDir'])) {
+ return false;
+ }
+
+ $this->setName(
+ PMA_Util::userDir($GLOBALS['cfg']['UploadDir']) . PMA_securePath($name)
+ );
+ if (! $this->isReadable()) {
+ $this->_error_message = __('File could not be read');
+ $this->setName(null);
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Checks whether file can be read.
+ *
+ * @access public
+ * @return boolean whether the file is readable or not
+ */
+ public function isReadable()
+ {
+ // suppress warnings from being displayed, but not from being logged
+ // any file access outside of open_basedir will issue a warning
+ ob_start();
+ $is_readable = is_readable($this->getName());
+ ob_end_clean();
+ return $is_readable;
+ }
+
+ /**
+ * If we are on a server with open_basedir, we must move the file
+ * before opening it. The FAQ 1.11 explains how to create the "./tmp"
+ * directory - if needed
+ *
+ * @todo move check of $cfg['TempDir'] into PMA_Config?
+ * @access public
+ * @return boolean whether uploaded fiel is fine or not
+ */
+ public function checkUploadedFile()
+ {
+ if ($this->isReadable()) {
+ return true;
+ }
+
+ if (empty($GLOBALS['cfg']['TempDir'])
+ || ! is_writable($GLOBALS['cfg']['TempDir'])
+ ) {
+ // cannot create directory or access, point user to FAQ 1.11
+ $this->_error_message = __('Error moving the uploaded file, see [doc@faq1-11]FAQ 1.11[/doc]');
+ return false;
+ }
+
+ $new_file_to_upload = tempnam(
+ realpath($GLOBALS['cfg']['TempDir']),
+ basename($this->getName())
+ );
+
+ // suppress warnings from being displayed, but not from being logged
+ // any file access outside of open_basedir will issue a warning
+ ob_start();
+ $move_uploaded_file_result = move_uploaded_file(
+ $this->getName(),
+ $new_file_to_upload
+ );
+ ob_end_clean();
+ if (! $move_uploaded_file_result) {
+ $this->_error_message = __('Error while moving uploaded file.');
+ return false;
+ }
+
+ $this->setName($new_file_to_upload);
+ $this->isTemp(true);
+
+ if (! $this->isReadable()) {
+ $this->_error_message = __('Cannot read (moved) upload file.');
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Detects what compression filse uses
+ *
+ * @todo move file read part into readChunk() or getChunk()
+ * @todo add support for compression plugins
+ * @access protected
+ * @return string MIME type of compression, none for none
+ */
+ protected function detectCompression()
+ {
+ // suppress warnings from being displayed, but not from being logged
+ // f.e. any file access outside of open_basedir will issue a warning
+ ob_start();
+ $file = fopen($this->getName(), 'rb');
+ ob_end_clean();
+
+ if (! $file) {
+ $this->_error_message = __('File could not be read');
+ return false;
+ }
+
+ /**
+ * @todo
+ * get registered plugins for file compression
+
+ foreach (PMA_getPlugins($type = 'compression') as $plugin) {
+ if ($plugin['classname']::canHandle($this->getName())) {
+ $this->setCompressionPlugin($plugin);
+ break;
+ }
+ }
+ */
+
+ $test = fread($file, 4);
+ $len = strlen($test);
+ fclose($file);
+
+ if ($len >= 2 && $test[0] == chr(31) && $test[1] == chr(139)) {
+ $this->_compression = 'application/gzip';
+ } elseif ($len >= 3 && substr($test, 0, 3) == 'BZh') {
+ $this->_compression = 'application/bzip2';
+ } elseif ($len >= 4 && $test == "PK\003\004") {
+ $this->_compression = 'application/zip';
+ } else {
+ $this->_compression = 'none';
+ }
+
+ return $this->_compression;
+ }
+
+ /**
+ * Sets whether the content should be decompressed before returned
+ *
+ * @param boolean $decompress whether to decompres
+ *
+ * @return void
+ */
+ public function setDecompressContent($decompress)
+ {
+ $this->_decompress = (bool) $decompress;
+ }
+
+ /**
+ * Returns the file handle
+ *
+ * @return resource file handle
+ */
+ public function getHandle()
+ {
+ if (null === $this->_handle) {
+ $this->open();
+ }
+ return $this->_handle;
+ }
+
+ /**
+ * Sets the file handle
+ *
+ * @param object $handle file handle
+ *
+ * @return void
+ */
+ public function setHandle($handle)
+ {
+ $this->_handle = $handle;
+ }
+
+
+ /**
+ * Sets error message for unsupported compression.
+ *
+ * @return void
+ */
+ public function errorUnsupported()
+ {
+ $this->_error_message = sprintf(
+ __('You attempted to load file with unsupported compression (%s). Either support for it is not implemented or disabled by your configuration.'),
+ $this->getCompression()
+ );
+ }
+
+ /**
+ * Attempts to open the file.
+ *
+ * @return bool
+ */
+ public function open()
+ {
+ if (! $this->_decompress) {
+ $this->_handle = @fopen($this->getName(), 'r');
+ }
+
+ switch ($this->getCompression()) {
+ case false:
+ return false;
+ case 'application/bzip2':
+ if ($GLOBALS['cfg']['BZipDump'] && @function_exists('bzopen')) {
+ $this->_handle = @bzopen($this->getName(), 'r');
+ } else {
+ $this->errorUnsupported();
+ return false;
+ }
+ break;
+ case 'application/gzip':
+ if ($GLOBALS['cfg']['GZipDump'] && @function_exists('gzopen')) {
+ $this->_handle = @gzopen($this->getName(), 'r');
+ } else {
+ $this->errorUnsupported();
+ return false;
+ }
+ break;
+ case 'application/zip':
+ if ($GLOBALS['cfg']['ZipDump'] && @function_exists('zip_open')) {
+ include_once './libraries/zip_extension.lib.php';
+ $result = PMA_getZipContents($this->getName());
+ if (! empty($result['error'])) {
+ $this->_error_message = PMA_Message::rawError($result['error']);
+ return false;
+ } else {
+ $this->content_uncompressed = $result['data'];
+ }
+ unset($result);
+ } else {
+ $this->errorUnsupported();
+ return false;
+ }
+ break;
+ case 'none':
+ $this->_handle = @fopen($this->getName(), 'r');
+ break;
+ default:
+ $this->errorUnsupported();
+ return false;
+ break;
+ }
+
+ return true;
+ }
+
+ /**
+ * Returns the character set of the file
+ *
+ * @return string character set of the file
+ */
+ public function getCharset()
+ {
+ return $this->_charset;
+ }
+
+ /**
+ * Sets the character set of the file
+ *
+ * @param string $charset character set of the file
+ *
+ * @return void
+ */
+ public function setCharset($charset)
+ {
+ $this->_charset = $charset;
+ }
+
+ /**
+ * Returns compression used by file.
+ *
+ * @return string MIME type of compression, none for none
+ * @access public
+ */
+ public function getCompression()
+ {
+ if (null === $this->_compression) {
+ return $this->detectCompression();
+ }
+
+ return $this->_compression;
+ }
+
+ /**
+ * advances the file pointer in the file handle by $length bytes/chars
+ *
+ * @param integer $length numbers of chars/bytes to skip
+ *
+ * @return boolean
+ * @todo this function is unused
+ */
+ public function advanceFilePointer($length)
+ {
+ while ($length > 0) {
+ $this->getNextChunk($length);
+ $length -= $this->getChunkSize();
+ }
+ }
+
+ /**
+ * http://bugs.php.net/bug.php?id=29532
+ * bzip reads a maximum of 8192 bytes on windows systems
+ *
+ * @param int $max_size maximum size of the next chunk to be returned
+ *
+ * @return bool|string
+ * @todo this function is unused
+ */
+ public function getNextChunk($max_size = null)
+ {
+ if (null !== $max_size) {
+ $size = min($max_size, $this->getChunkSize());
+ } else {
+ $size = $this->getChunkSize();
+ }
+
+ // $result = $this->handler->getNextChunk($size);
+ $result = '';
+ switch ($this->getCompression()) {
+ case 'application/bzip2':
+ $result = '';
+ while (strlen($result) < $size - 8192 && ! feof($this->getHandle())) {
+ $result .= bzread($this->getHandle(), $size);
+ }
+ break;
+ case 'application/gzip':
+ $result = gzread($this->getHandle(), $size);
+ break;
+ case 'application/zip':
+ /*
+ * if getNextChunk() is used some day,
+ * replace this code by code similar to the one
+ * in open()
+ *
+ include_once './libraries/unzip.lib.php';
+ $import_handle = new SimpleUnzip();
+ $import_handle->ReadFile($this->getName());
+ if ($import_handle->Count() == 0) {
+ $this->_error_message = __('No files found inside ZIP archive!');
+ return false;
+ } elseif ($import_handle->GetError(0) != 0) {
+ $this->_error_message = __('Error in ZIP archive:')
+ . ' ' . $import_handle->GetErrorMsg(0);
+ return false;
+ } else {
+ $result = $import_handle->GetData(0);
+ }
+ */
+ break;
+ case 'none':
+ $result = fread($this->getHandle(), $size);
+ break;
+ default:
+ return false;
+ }
+
+ if ($GLOBALS['charset_conversion']) {
+ $result = PMA_convertString($this->getCharset(), 'utf-8', $result);
+ } else {
+ /**
+ * Skip possible byte order marks (I do not think we need more
+ * charsets, but feel free to add more, you can use wikipedia for
+ * reference: <http://en.wikipedia.org/wiki/Byte_Order_Mark>)
+ *
+ * @todo BOM could be used for charset autodetection
+ */
+ if ($this->getOffset() === 0) {
+ // UTF-8
+ if (strncmp($result, "\xEF\xBB\xBF", 3) == 0) {
+ $result = substr($result, 3);
+ // UTF-16 BE, LE
+ } elseif (strncmp($result, "\xFE\xFF", 2) == 0
+ || strncmp($result, "\xFF\xFE", 2) == 0
+ ) {
+ $result = substr($result, 2);
+ }
+ }
+ }
+
+ $this->_offset += $size;
+ if (0 === $result) {
+ return true;
+ }
+ return $result;
+ }
+
+ /**
+ * Returns the offset
+ *
+ * @return integer the offset
+ */
+ public function getOffset()
+ {
+ return $this->_offset;
+ }
+
+ /**
+ * Returns the chunk size
+ *
+ * @return integer the chunk size
+ */
+ public function getChunkSize()
+ {
+ return $this->_chunk_size;
+ }
+
+ /**
+ * Sets the chunk size
+ *
+ * @param integer $chunk_size the chunk size
+ *
+ * @return void
+ */
+ public function setChunkSize($chunk_size)
+ {
+ $this->_chunk_size = (int) $chunk_size;
+ }
+
+ /**
+ * Returns the length of the content in the file
+ *
+ * @return integer the length of the file content
+ */
+ public function getContentLength()
+ {
+ return strlen($this->_content);
+ }
+
+ /**
+ * Returns whether the end of the file has been reached
+ *
+ * @return boolean whether the end of the file has been reached
+ */
+ public function eof()
+ {
+ if ($this->getHandle()) {
+ return feof($this->getHandle());
+ } else {
+ return ($this->getOffset() >= $this->getContentLength());
+ }
+
+ }
+}
+?>
diff --git a/libraries/Footer.class.php b/libraries/Footer.class.php
new file mode 100644
index 0000000000..d89c7af975
--- /dev/null
+++ b/libraries/Footer.class.php
@@ -0,0 +1,336 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Used to render the footer of PMA's pages
+ *
+ * @package PhpMyAdmin
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+require_once 'libraries/Scripts.class.php';
+
+/**
+ * Class used to output the footer
+ *
+ * @package PhpMyAdmin
+ */
+class PMA_Footer
+{
+ /**
+ * PMA_Scripts instance
+ *
+ * @access private
+ * @var object
+ */
+ private $_scripts;
+ /**
+ * Whether we are servicing an ajax request.
+ * We can't simply use $GLOBALS['is_ajax_request']
+ * here since it may have not been initialised yet.
+ *
+ * @access private
+ * @var bool
+ */
+ private $_isAjax;
+ /**
+ * Whether to only close the BODY and HTML tags
+ * or also include scripts, errors and links
+ *
+ * @access private
+ * @var bool
+ */
+ private $_isMinimal;
+ /**
+ * Whether to display anything
+ *
+ * @access private
+ * @var bool
+ */
+ private $_isEnabled;
+
+ /**
+ * Creates a new class instance
+ */
+ public function __construct()
+ {
+ $this->_isEnabled = true;
+ $this->_scripts = new PMA_Scripts();
+ $this->_isMinimal = false;
+ }
+
+ /**
+ * Returns the message for demo server to error messages
+ *
+ * @return string
+ */
+ private function _getDemoMessage()
+ {
+ $message = '<a href="/">' . __('phpMyAdmin Demo Server') . '</a>: ';
+ if (file_exists('./revision-info.php')) {
+ include './revision-info.php';
+ $message .= sprintf(
+ __('Currently running Git revision %1$s from the %2$s branch.'),
+ '<a target="_blank" href="' . $repobase . $fullrevision . '">'
+ . $revision .'</a>',
+ '<a target="_blank" href="' . $repobranchbase . $branch . '">'
+ . $branch . '</a>'
+ );
+ } else {
+ $message .= __('Git information missing!');
+ }
+
+ return PMA_Message::notice($message)->getDisplay();
+ }
+
+ /**
+ * Renders the debug messages
+ *
+ * @return string
+ */
+ private function _getDebugMessage()
+ {
+ $retval = '';
+ if (! empty($_SESSION['debug'])) {
+ $sum_time = 0;
+ $sum_exec = 0;
+ foreach ($_SESSION['debug']['queries'] as $query) {
+ $sum_time += $query['count'] * $query['time'];
+ $sum_exec += $query['count'];
+ }
+
+ $retval .= '<div id="session_debug">';
+ $retval .= count($_SESSION['debug']['queries']) . ' queries executed ';
+ $retval .= $sum_exec . ' times in ' . $sum_time . ' seconds';
+ $retval .= '<pre>';
+
+ ob_start();
+ print_r($_SESSION['debug']);
+ $retval .= ob_get_contents();
+ ob_end_clean();
+
+ $retval .= '</pre>';
+ $retval .= '</div>';
+ $_SESSION['debug'] = array();
+ }
+ return $retval;
+ }
+
+ /**
+ * Returns the url of the current page
+ *
+ * @param mixed $encoding See PMA_URL_getCommon()
+ *
+ * @return string
+ */
+ public function getSelfUrl($encoding = null)
+ {
+ $db = ! empty($GLOBALS['db']) ? $GLOBALS['db'] : '';
+ $table = ! empty($GLOBALS['table']) ? $GLOBALS['table'] : '';
+ $target = ! empty($_REQUEST['target']) ? $_REQUEST['target'] : '';
+ $params = array(
+ 'db' => $db,
+ 'table' => $table,
+ 'server' => $GLOBALS['server'],
+ 'target' => $target
+ );
+ // needed for server privileges tabs
+ if (isset($_REQUEST['viewing_mode'])
+ && in_array($_REQUEST['viewing_mode'], array('server', 'db', 'table'))
+ ) {
+ $params['viewing_mode'] = $_REQUEST['viewing_mode'];
+ }
+ if (isset($_REQUEST['checkprivsdb'])
+ //TODO: coming from server_privileges.php, here $db is not set, uncomment below line when that is fixed
+ //&& $_REQUEST['checkprivsdb'] == $db
+ ) {
+ $params['checkprivsdb'] = $_REQUEST['checkprivsdb'];
+ }
+ if (isset($_REQUEST['checkprivstable'])
+ //TODO: coming from server_privileges.php, here $table is not set, uncomment below line when that is fixed
+ //&& $_REQUEST['checkprivstable'] == $table
+ ) {
+ $params['checkprivstable'] = $_REQUEST['checkprivstable'];
+ }
+ if (isset($_REQUEST['single_table'])
+ && in_array($_REQUEST['single_table'], array(true, false))
+ ) {
+ $params['single_table'] = $_REQUEST['single_table'];
+ }
+ return basename(PMA_getenv('SCRIPT_NAME')) . PMA_URL_getCommon(
+ $params,
+ $encoding
+ );
+ }
+
+ /**
+ * Renders the link to open a new page
+ *
+ * @param string $url The url of the page
+ *
+ * @return string
+ */
+ private function _getSelfLink($url)
+ {
+ $retval = '';
+ $retval .= '<div id="selflink" class="print_ignore">';
+ $retval .= '<a href="' . $url . '"'
+ . ' title="' . __('Open new phpMyAdmin window') . '" target="_blank">';
+ if (PMA_Util::showIcons('TabsMode')) {
+ $retval .= PMA_Util::getImage(
+ 'window-new.png',
+ __('Open new phpMyAdmin window')
+ );
+ } else {
+ $retval .= __('Open new phpMyAdmin window');
+ }
+ $retval .= '</a>';
+ $retval .= '</div>';
+ return $retval;
+ }
+
+ /**
+ * Renders the link to open a new page
+ *
+ * @return string
+ */
+ public function getErrorMessages()
+ {
+ $retval = '';
+ if ($GLOBALS['error_handler']->hasDisplayErrors()) {
+ $retval .= '<div class="clearfloat" id="pma_errors">';
+ $retval .= $GLOBALS['error_handler']->getDispErrors();
+ $retval .= '</div>';
+ }
+ return $retval;
+ }
+
+ /**
+ * Saves query in history
+ *
+ * @return void
+ */
+ private function _setHistory()
+ {
+ if (! PMA_isValid($_REQUEST['no_history'])
+ && empty($GLOBALS['error_message'])
+ && ! empty($GLOBALS['sql_query'])
+ ) {
+ PMA_setHistory(
+ PMA_ifSetOr($GLOBALS['db'], ''),
+ PMA_ifSetOr($GLOBALS['table'], ''),
+ $GLOBALS['cfg']['Server']['user'],
+ $GLOBALS['sql_query']
+ );
+ }
+ }
+
+ /**
+ * Disables the rendering of the footer
+ *
+ * @return void
+ */
+ public function disable()
+ {
+ $this->_isEnabled = false;
+ }
+
+ /**
+ * Set the ajax flag to indicate whether
+ * we are sevicing an ajax request
+ *
+ * @param bool $isAjax Whether we are sevicing an ajax request
+ *
+ * @return void
+ */
+ public function setAjax($isAjax)
+ {
+ $this->_isAjax = ($isAjax == true);
+ }
+
+ /**
+ * Turn on minimal display mode
+ *
+ * @return void
+ */
+ public function setMinimal()
+ {
+ $this->_isMinimal = true;
+ }
+
+ /**
+ * Returns the PMA_Scripts object
+ *
+ * @return PMA_Scripts object
+ */
+ public function getScripts()
+ {
+ return $this->_scripts;
+ }
+
+ /**
+ * Renders the footer
+ *
+ * @return string
+ */
+ public function getDisplay()
+ {
+ $retval = '';
+ $this->_setHistory();
+ if ($this->_isEnabled) {
+ if (! $this->_isAjax) {
+ $retval .= "</div>";
+ }
+ if (! $this->_isAjax && ! $this->_isMinimal) {
+ if (PMA_getenv('SCRIPT_NAME')
+ && empty($_POST)
+ && empty($GLOBALS['checked_special'])
+ && ! $this->_isAjax
+ ) {
+ $url = $this->getSelfUrl('unencoded');
+ $header = PMA_Response::getInstance()->getHeader();
+ $scripts = $header->getScripts()->getFiles();
+ $menuHash = $header->getMenu()->getHash();
+ // prime the client-side cache
+ $this->_scripts->addCode(
+ sprintf(
+ 'AJAX.cache.primer = {'
+ . ' url: "%s",'
+ . ' scripts: %s,'
+ . ' menuHash: "%s"'
+ . '};',
+ PMA_escapeJsString($url),
+ json_encode($scripts),
+ PMA_escapeJsString($menuHash)
+ )
+ );
+ $url = $this->getSelfUrl();
+ $retval .= $this->_getSelfLink($url);
+ }
+ $retval .= $this->_getDebugMessage();
+ $retval .= $this->getErrorMessages();
+ $retval .= $this->_scripts->getDisplay();
+ if ($GLOBALS['cfg']['DBG']['demo']) {
+ $retval .= '<div id="pma_demo">';
+ $retval .= $this->_getDemoMessage();
+ $retval .= '</div>';
+ }
+ // Include possible custom footers
+ if (file_exists(CUSTOM_FOOTER_FILE)) {
+ $retval .= '<div id="pma_footer">';
+ ob_start();
+ include CUSTOM_FOOTER_FILE;
+ $retval .= ob_get_contents();
+ ob_end_clean();
+ $retval .= '</div>';
+ }
+ }
+ if (! $this->_isAjax) {
+ $retval .= "</body></html>";
+ }
+ }
+
+ return $retval;
+ }
+}
diff --git a/libraries/Header.class.php b/libraries/Header.class.php
new file mode 100644
index 0000000000..8b7d9624e9
--- /dev/null
+++ b/libraries/Header.class.php
@@ -0,0 +1,720 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Used to render the header of PMA's pages
+ *
+ * @package PhpMyAdmin
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+require_once 'libraries/Scripts.class.php';
+require_once 'libraries/RecentTable.class.php';
+require_once 'libraries/Menu.class.php';
+require_once 'libraries/navigation/Navigation.class.php';
+require_once 'libraries/url_generating.lib.php';
+
+
+/**
+ * Class used to output the HTTP and HTML headers
+ *
+ * @package PhpMyAdmin
+ */
+class PMA_Header
+{
+ /**
+ * PMA_Scripts instance
+ *
+ * @access private
+ * @var object
+ */
+ private $_scripts;
+ /**
+ * PMA_Menu instance
+ *
+ * @access private
+ * @var object
+ */
+ private $_menu;
+ /**
+ * Whether to offer the option of importing user settings
+ *
+ * @access private
+ * @var bool
+ */
+ private $_userprefsOfferImport;
+ /**
+ * The page title
+ *
+ * @access private
+ * @var string
+ */
+ private $_title;
+ /**
+ * The value for the id attribute for the body tag
+ *
+ * @access private
+ * @var string
+ */
+ private $_bodyId;
+ /**
+ * Whether to show the top menu
+ *
+ * @access private
+ * @var bool
+ */
+ private $_menuEnabled;
+ /**
+ * Whether to show the warnings
+ *
+ * @access private
+ * @var bool
+ */
+ private $_warningsEnabled;
+ /**
+ * Whether the page is in 'print view' mode
+ *
+ * @access private
+ * @var bool
+ */
+ private $_isPrintView;
+ /**
+ * Whether we are servicing an ajax request.
+ * We can't simply use $GLOBALS['is_ajax_request']
+ * here since it may have not been initialised yet.
+ *
+ * @access private
+ * @var bool
+ */
+ private $_isAjax;
+ /**
+ * Whether to display anything
+ *
+ * @access private
+ * @var bool
+ */
+ private $_isEnabled;
+ /**
+ * Whether the HTTP headers (and possibly some HTML)
+ * have already been sent to the browser
+ *
+ * @access private
+ * @var bool
+ */
+ private $_headerIsSent;
+
+ /**
+ * Creates a new class instance
+ */
+ public function __construct()
+ {
+ $this->_isEnabled = true;
+ $this->_isAjax = false;
+ $this->_bodyId = '';
+ $this->_title = '';
+ $db = ! empty($GLOBALS['db']) ? $GLOBALS['db'] : '';
+ $table = ! empty($GLOBALS['table']) ? $GLOBALS['table'] : '';
+ $this->_menu = new PMA_Menu(
+ $GLOBALS['server'],
+ $db,
+ $table
+ );
+ $this->_menuEnabled = true;
+ $this->_warningsEnabled = true;
+ $this->_isPrintView = false;
+ $this->_scripts = new PMA_Scripts();
+ $this->_addDefaultScripts();
+ $this->_headerIsSent = false;
+ // if database storage for user preferences is transient,
+ // offer to load exported settings from localStorage
+ // (detection will be done in JavaScript)
+ $this->_userprefsOfferImport = false;
+ if ($GLOBALS['PMA_Config']->get('user_preferences') == 'session'
+ && ! isset($_SESSION['userprefs_autoload'])
+ ) {
+ $this->_userprefsOfferImport = true;
+ }
+ }
+
+ /**
+ * Loads common scripts
+ *
+ * @return void
+ */
+ private function _addDefaultScripts()
+ {
+ $this->_scripts->addFile('jquery/jquery-1.8.3.min.js');
+ $this->_scripts->addFile('ajax.js');
+ $this->_scripts->addFile('keyhandler.js');
+ $this->_scripts->addFile('jquery/jquery-ui-1.9.2.custom.min.js');
+ $this->_scripts->addFile('jquery/jquery.sprintf.js');
+ $this->_scripts->addFile('jquery/jquery.cookie.js');
+ $this->_scripts->addFile('jquery/jquery.mousewheel.js');
+ $this->_scripts->addFile('jquery/jquery.event.drag-2.2.js');
+ $this->_scripts->addFile('jquery/jquery-ui-timepicker-addon.js');
+ $this->_scripts->addFile('jquery/jquery.ba-hashchange-1.3.js');
+ $this->_scripts->addFile('jquery/jquery.debounce-1.0.5.js');
+ $this->_scripts->addFile('jquery/jquery.menuResizer-1.0.js');
+
+ // Cross-framing protection
+ if ($GLOBALS['cfg']['AllowThirdPartyFraming'] === false) {
+ $this->_scripts->addFile('cross_framing_protection.js');
+ }
+
+ $this->_scripts->addFile('rte.js');
+ if ($GLOBALS['cfg']['SendErrorReports'] !== 'never') {
+ $this->_scripts->addFile('tracekit/tracekit.js');
+ $this->_scripts->addFile('error_report.js');
+ }
+
+ // Here would not be a good place to add CodeMirror because
+ // the user preferences have not been merged at this point
+
+ // Localised strings
+ $params = array('lang' => $GLOBALS['lang']);
+ if (isset($GLOBALS['db'])) {
+ $params['db'] = $GLOBALS['db'];
+ }
+ $this->_scripts->addFile('messages.php' . PMA_URL_getCommon($params));
+ // Append the theme id to this url to invalidate
+ // the cache on a theme change. Though this might be
+ // unavailable for fatal errors.
+ if (isset($_SESSION['PMA_Theme'])) {
+ $theme_id = urlencode($_SESSION['PMA_Theme']->getId());
+ } else {
+ $theme_id = 'default';
+ }
+ $this->_scripts->addFile(
+ 'get_image.js.php?theme=' . $theme_id
+ );
+ $this->_scripts->addFile('doclinks.js');
+ $this->_scripts->addFile('functions.js');
+ $this->_scripts->addFile('navigation.js');
+ $this->_scripts->addFile('indexes.js');
+ $this->_scripts->addFile('common.js');
+ $this->_scripts->addCode($this->getJsParamsCode());
+ }
+
+ /**
+ * Returns, as an array, a list of parameters
+ * used on the client side
+ *
+ * @return array
+ */
+ public function getJsParams()
+ {
+ $db = ! empty($GLOBALS['db']) ? $GLOBALS['db'] : '';
+ $table = ! empty($GLOBALS['table']) ? $GLOBALS['table'] : '';
+ return array(
+ 'common_query' => PMA_URL_getCommon('', '', '&'),
+ 'opendb_url' => $GLOBALS['cfg']['DefaultTabDatabase'],
+ 'safari_browser' => PMA_USR_BROWSER_AGENT == 'SAFARI' ? 1 : 0,
+ 'querywindow_height' => $GLOBALS['cfg']['QueryWindowHeight'],
+ 'querywindow_width' => $GLOBALS['cfg']['QueryWindowWidth'],
+ 'collation_connection' => $GLOBALS['collation_connection'],
+ 'lang' => $GLOBALS['lang'],
+ 'server' => $GLOBALS['server'],
+ 'table' => $table,
+ 'db' => $db,
+ 'token' => $_SESSION[' PMA_token '],
+ 'text_dir' => $GLOBALS['text_dir'],
+ 'pma_absolute_uri' => $GLOBALS['cfg']['PmaAbsoluteUri'],
+ 'pma_text_default_tab' => PMA_Util::getTitleForTarget(
+ $GLOBALS['cfg']['DefaultTabTable']
+ ),
+ 'pma_text_left_default_tab' => PMA_Util::getTitleForTarget(
+ $GLOBALS['cfg']['NavigationTreeDefaultTabTable']
+ ),
+ 'confirm' => $GLOBALS['cfg']['Confirm']
+ );
+ }
+
+ /**
+ * Returns, as a string, a list of parameters
+ * used on the client side
+ *
+ * @return string
+ */
+ public function getJsParamsCode()
+ {
+ $params = $this->getJsParams();
+ foreach ($params as $key => $value) {
+ $params[$key] = $key . ':"' . PMA_escapeJsString($value) . '"';
+ }
+ return 'PMA_commonParams.setAll({' . implode(',', $params) . '});';
+ }
+
+ /**
+ * Disables the rendering of the header
+ *
+ * @return void
+ */
+ public function disable()
+ {
+ $this->_isEnabled = false;
+ }
+
+ /**
+ * Set the ajax flag to indicate whether
+ * we are sevicing an ajax request
+ *
+ * @param bool $isAjax Whether we are sevicing an ajax request
+ *
+ * @return void
+ */
+ public function setAjax($isAjax)
+ {
+ $this->_isAjax = ($isAjax == true);
+ }
+
+ /**
+ * Returns the PMA_Scripts object
+ *
+ * @return PMA_Scripts object
+ */
+ public function getScripts()
+ {
+ return $this->_scripts;
+ }
+
+ /**
+ * Returns the PMA_Menu object
+ *
+ * @return PMA_Menu object
+ */
+ public function getMenu()
+ {
+ return $this->_menu;
+ }
+
+ /**
+ * Setter for the ID attribute in the BODY tag
+ *
+ * @param string $id Value for the ID attribute
+ *
+ * @return void
+ */
+ public function setBodyId($id)
+ {
+ $this->_bodyId = htmlspecialchars($id);
+ }
+
+ /**
+ * Setter for the title of the page
+ *
+ * @param string $title New title
+ *
+ * @return void
+ */
+ public function setTitle($title)
+ {
+ $this->_title = htmlspecialchars($title);
+ }
+
+ /**
+ * Disables the display of the top menu
+ *
+ * @return void
+ */
+ public function disableMenu()
+ {
+ $this->_menuEnabled = false;
+ }
+
+ /**
+ * Disables the display of the top menu
+ *
+ * @return void
+ */
+ public function disableWarnings()
+ {
+ $this->_warningsEnabled = false;
+ }
+
+ /**
+ * Turns on 'print view' mode
+ *
+ * @return void
+ */
+ public function enablePrintView()
+ {
+ $this->disableMenu();
+ $this->setTitle(__('Print view') . ' - phpMyAdmin ' . PMA_VERSION);
+ $this->_isPrintView = true;
+ }
+
+ /**
+ * Generates the header
+ *
+ * @return string The header
+ */
+ public function getDisplay()
+ {
+ $retval = '';
+ if (! $this->_headerIsSent) {
+ if (! $this->_isAjax && $this->_isEnabled) {
+ $this->sendHttpHeaders();
+ $retval .= $this->_getHtmlStart();
+ $retval .= $this->_getMetaTags();
+ $retval .= $this->_getLinkTags();
+ $retval .= $this->getTitleTag();
+
+ // The user preferences have been merged at this point
+ // so we can conditionally add CodeMirror
+ if ($GLOBALS['cfg']['CodemirrorEnable']) {
+ $this->_scripts->addFile('codemirror/lib/codemirror.js');
+ $this->_scripts->addFile('codemirror/mode/sql/sql.js');
+ $this->_scripts->addFile('codemirror/addon/runmode/runmode.js');
+ }
+ if ($this->_userprefsOfferImport) {
+ $this->_scripts->addFile('config.js');
+ }
+ $retval .= $this->_scripts->getDisplay();
+ $retval .= $this->_getBodyStart();
+ if ($this->_menuEnabled && $GLOBALS['server'] > 0) {
+ $nav = new PMA_Navigation();
+ $retval .= $nav->getDisplay();
+ }
+ // Include possible custom headers
+ if (file_exists(CUSTOM_HEADER_FILE)) {
+ $retval .= '<div id="pma_header">';
+ ob_start();
+ include CUSTOM_HEADER_FILE;
+ $retval .= ob_get_contents();
+ ob_end_clean();
+ $retval .= '</div>';
+ }
+ // offer to load user preferences from localStorage
+ if ($this->_userprefsOfferImport) {
+ include_once './libraries/user_preferences.lib.php';
+ $retval .= PMA_userprefsAutoloadGetHeader();
+ }
+ // pass configuration for hint tooltip display
+ // (to be used by PMA_tooltip() in js/functions.js)
+ if (! $GLOBALS['cfg']['ShowHint']) {
+ $retval .= '<span id="no_hint" class="hide"></span>';
+ }
+ $retval .= $this->_getWarnings();
+ if ($this->_menuEnabled && $GLOBALS['server'] > 0) {
+ $retval .= $this->_menu->getDisplay();
+ $retval .= sprintf(
+ '<a id="goto_pagetop" href="#" title="%s">%s</a>',
+ __('Click on the bar to scroll to top of page'),
+ PMA_Util::getImage('s_top.png')
+ );
+ }
+ $retval .= '<div id="page_content">';
+ $retval .= $this->getMessage();
+ }
+ if ($this->_isEnabled && empty($_REQUEST['recent_table'])) {
+ $retval .= $this->_addRecentTable(
+ $GLOBALS['db'],
+ $GLOBALS['table']
+ );
+ }
+ }
+ return $retval;
+ }
+
+ /**
+ * Returns the message to be displayed at the top of
+ * the page, including the executed SQL query, if any.
+ *
+ * @return string
+ */
+ public function getMessage()
+ {
+ $retval = '';
+ $message = '';
+ if (! empty($GLOBALS['message'])) {
+ $message = $GLOBALS['message'];
+ unset($GLOBALS['message']);
+ } else if (! empty($_REQUEST['message'])) {
+ $message = $_REQUEST['message'];
+ }
+ if (! empty($message)) {
+ if (isset($GLOBALS['buffer_message'])) {
+ $buffer_message = $GLOBALS['buffer_message'];
+ }
+ $retval .= PMA_Util::getMessage($message);
+ if (isset($buffer_message)) {
+ $GLOBALS['buffer_message'] = $buffer_message;
+ }
+ }
+ return $retval;
+ }
+
+ /**
+ * Sends out the HTTP headers
+ *
+ * @return void
+ */
+ public function sendHttpHeaders()
+ {
+ $https = $GLOBALS['PMA_Config']->isHttps();
+ $mapTilesUrls = ' *.tile.openstreetmap.org *.tile.opencyclemap.org';
+
+ /**
+ * Sends http headers
+ */
+ $GLOBALS['now'] = gmdate('D, d M Y H:i:s') . ' GMT';
+ if (! defined('TESTSUITE')) {
+ $use_captcha = (
+ !empty($GLOBALS['cfg']['CaptchaLoginPrivateKey'])
+ && !empty($GLOBALS['cfg']['CaptchaLoginPublicKey'])
+ );
+ /* Prevent against ClickJacking by disabling framing */
+ if (! $GLOBALS['cfg']['AllowThirdPartyFraming']) {
+ header(
+ 'X-Frame-Options: DENY'
+ );
+ }
+ header(
+ "Content-Security-Policy: default-src 'self' "
+ . ($use_captcha ? 'https://www.google.com ' : ' ')
+ . $GLOBALS['cfg']['CSPAllow'] . ';'
+ . "script-src 'self' 'unsafe-inline' 'unsafe-eval' "
+ . ($use_captcha ? 'https://www.google.com ' : ' ')
+ . ";"
+ . "style-src 'self' 'unsafe-inline' "
+ . ($use_captcha ? 'https://www.google.com ' : ' ')
+ . $GLOBALS['cfg']['CSPAllow']
+ . ";"
+ . "img-src 'self' data: "
+ . $GLOBALS['cfg']['CSPAllow']
+ . ($https ? "" : $mapTilesUrls)
+ // for reCAPTCHA
+ . ($use_captcha ? ' https://www.google.com' : ' ')
+ . ";"
+ );
+ header(
+ "X-Content-Security-Policy: default-src 'self' "
+ . ($use_captcha ? 'https://www.google.com ' : ' ')
+ . $GLOBALS['cfg']['CSPAllow'] . ';'
+ . "options inline-script eval-script;"
+ . "img-src 'self' data: "
+ . $GLOBALS['cfg']['CSPAllow']
+ . ($https ? "" : $mapTilesUrls)
+ // for reCAPTCHA
+ . ($use_captcha ? ' https://www.google.com' : ' ')
+ . ";"
+ );
+ if (PMA_USR_BROWSER_AGENT == 'SAFARI'
+ && PMA_USR_BROWSER_VER < '6.0.0'
+ ) {
+ header(
+ "X-WebKit-CSP: allow 'self' "
+ . ($use_captcha ? 'https://www.google.com ' : ' ')
+ . $GLOBALS['cfg']['CSPAllow'] . ';'
+ . "options inline-script eval-script;"
+ . "img-src 'self' data: "
+ . $GLOBALS['cfg']['CSPAllow']
+ . ($https ? "" : $mapTilesUrls)
+ // for reCAPTCHA
+ . ($use_captcha ? ' https://www.google.com' : ' ')
+ . ";"
+ );
+ } else {
+ header(
+ "X-WebKit-CSP: default-src 'self' "
+ . ($use_captcha ? 'https://www.google.com ' : ' ')
+ . $GLOBALS['cfg']['CSPAllow'] . ';'
+ . "script-src 'self' "
+ . ($use_captcha ? 'https://www.google.com ' : ' ')
+ . $GLOBALS['cfg']['CSPAllow']
+ . " 'unsafe-inline' 'unsafe-eval';"
+ . "style-src 'self' 'unsafe-inline' "
+ . ($use_captcha ? 'https://www.google.com ' : ' ')
+ . ';'
+ . "img-src 'self' data: "
+ . $GLOBALS['cfg']['CSPAllow']
+ . ($https ? "" : $mapTilesUrls)
+ // for reCAPTCHA
+ . ($use_captcha ? ' https://www.google.com' : ' ')
+ . ";"
+ );
+ }
+ }
+ PMA_noCacheHeader();
+ if (! defined('IS_TRANSFORMATION_WRAPPER') && ! defined('TESTSUITE')) {
+ // Define the charset to be used
+ header('Content-Type: text/html; charset=utf-8');
+ }
+ $this->_headerIsSent = true;
+ }
+
+ /**
+ * Returns the DOCTYPE and the start HTML tag
+ *
+ * @return string DOCTYPE and HTML tags
+ */
+ private function _getHtmlStart()
+ {
+ $lang = $GLOBALS['available_languages'][$GLOBALS['lang']][1];
+ $dir = $GLOBALS['text_dir'];
+
+ $retval = "<!DOCTYPE HTML>";
+ $retval .= "<html lang='$lang' dir='$dir' class='";
+ $retval .= strtolower(PMA_USR_BROWSER_AGENT) . " ";
+ $retval .= strtolower(PMA_USR_BROWSER_AGENT)
+ . intval(PMA_USR_BROWSER_VER) . "'>";
+
+ return $retval;
+ }
+
+ /**
+ * Returns the META tags
+ *
+ * @return string the META tags
+ */
+ private function _getMetaTags()
+ {
+ $retval = '<meta charset="utf-8" />';
+ $retval .= '<meta name="robots" content="noindex,nofollow" />';
+ $retval .= '<meta http-equiv="X-UA-Compatible" content="IE=Edge">';
+ if (! $GLOBALS['cfg']['AllowThirdPartyFraming']) {
+ $retval .= '<style>html{display: none;}</style>';
+ }
+ return $retval;
+ }
+
+ /**
+ * Returns the LINK tags for the favicon and the stylesheets
+ *
+ * @return string the LINK tags
+ */
+ private function _getLinkTags()
+ {
+ $retval = '<link rel="icon" href="favicon.ico" '
+ . 'type="image/x-icon" />'
+ . '<link rel="shortcut icon" href="favicon.ico" '
+ . 'type="image/x-icon" />';
+ // stylesheets
+ $basedir = defined('PMA_PATH_TO_BASEDIR') ? PMA_PATH_TO_BASEDIR : '';
+ $common_url = PMA_URL_getCommon(array('server' => $GLOBALS['server']));
+ $theme_id = $GLOBALS['PMA_Config']->getThemeUniqueValue();
+ $theme_path = $GLOBALS['pmaThemePath'];
+
+ if ($this->_isPrintView) {
+ $retval .= '<link rel="stylesheet" type="text/css" href="'
+ . $basedir . 'print.css" />';
+ } else {
+ $retval .= '<link rel="stylesheet" type="text/css" href="'
+ . $basedir . 'phpmyadmin.css.php'
+ . $common_url . '&amp;nocache='
+ . $theme_id . $GLOBALS['text_dir'] . '" />';
+ $retval .= '<link rel="stylesheet" type="text/css" href="'
+ . $theme_path . '/jquery/jquery-ui-1.9.2.custom.css" />';
+ }
+
+ return $retval;
+ }
+
+ /**
+ * Returns the TITLE tag
+ *
+ * @return string the TITLE tag
+ */
+ public function getTitleTag()
+ {
+ $retval = "<title>";
+ $retval .= $this->_getPageTitle();
+ $retval .= "</title>";
+ return $retval;
+ }
+
+ /**
+ * If the page is missing the title, this function
+ * will set it to something reasonable
+ *
+ * @return string
+ */
+ private function _getPageTitle()
+ {
+ if (empty($this->_title)) {
+ if ($GLOBALS['server'] > 0) {
+ if (! empty($GLOBALS['table'])) {
+ $temp_title = $GLOBALS['cfg']['TitleTable'];
+ } else if (! empty($GLOBALS['db'])) {
+ $temp_title = $GLOBALS['cfg']['TitleDatabase'];
+ } elseif (! empty($GLOBALS['cfg']['Server']['host'])) {
+ $temp_title = $GLOBALS['cfg']['TitleServer'];
+ } else {
+ $temp_title = $GLOBALS['cfg']['TitleDefault'];
+ }
+ $this->_title = htmlspecialchars(
+ PMA_Util::expandUserString($temp_title)
+ );
+ } else {
+ $this->_title = 'phpMyAdmin';
+ }
+ }
+ return $this->_title;
+ }
+
+ /**
+ * Returns the close tag to the HEAD
+ * and the start tag for the BODY
+ *
+ * @return string HEAD and BODY tags
+ */
+ private function _getBodyStart()
+ {
+ $retval = "</head><body";
+ if (! empty($this->_bodyId)) {
+ $retval .= " id='" . $this->_bodyId . "'";
+ }
+ $retval .= ">";
+ return $retval;
+ }
+
+ /**
+ * Returns some warnings to be displayed at the top of the page
+ *
+ * @return string The warnings
+ */
+ private function _getWarnings()
+ {
+ $retval = '';
+ if ($this->_warningsEnabled) {
+ $retval .= "<noscript>";
+ $retval .= PMA_message::error(
+ __("Javascript must be enabled past this point")
+ )->getDisplay();
+ $retval .= "</noscript>";
+ }
+ return $retval;
+ }
+
+ /**
+ * Add recently used table and reload the navigation.
+ *
+ * @param string $db Database name where the table is located.
+ * @param string $table The table name
+ *
+ * @return string
+ */
+ private function _addRecentTable($db, $table)
+ {
+ $retval = '';
+ if ($this->_menuEnabled
+ && strlen($table)
+ && $GLOBALS['cfg']['NumRecentTables'] > 0
+ ) {
+ $tmp_result = PMA_RecentTable::getInstance()->add($db, $table);
+ if ($tmp_result === true) {
+ $params = array('ajax_request' => true, 'recent_table' => true);
+ $url = 'index.php' . PMA_URL_getCommon($params);
+ $retval = '<a class="hide" id="update_recent_tables"';
+ $retval .= ' href="' . $url . '"></a>';
+ } else {
+ $error = $tmp_result;
+ $retval = $error->getDisplay();
+ }
+ }
+ return $retval;
+ }
+}
+
+?>
diff --git a/libraries/Index.class.php b/libraries/Index.class.php
new file mode 100644
index 0000000000..e6e975557c
--- /dev/null
+++ b/libraries/Index.class.php
@@ -0,0 +1,924 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * holds the database index class
+ *
+ * @package PhpMyAdmin
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ * Index manipulation class
+ *
+ * @package PhpMyAdmin
+ * @since phpMyAdmin 3.0.0
+ */
+class PMA_Index
+{
+ /**
+ * Class-wide storage container for indexes (caching, singleton)
+ *
+ * @var array
+ */
+ private static $_registry = array();
+
+ /**
+ * @var string The name of the schema
+ */
+ private $_schema = '';
+
+ /**
+ * @var string The name of the table
+ */
+ private $_table = '';
+
+ /**
+ * @var string The name of the index
+ */
+ private $_name = '';
+
+ /**
+ * Columns in index
+ *
+ * @var array
+ */
+ private $_columns = array();
+
+ /**
+ * The index method used (BTREE, SPATIAL, FULLTEXT, HASH, RTREE).
+ *
+ * @var string
+ */
+ private $_type = '';
+
+ /**
+ * The index choice (PRIMARY, UNIQUE, INDEX, SPATIAL, FULLTEXT)
+ *
+ * @var string
+ */
+ private $_choice = '';
+
+ /**
+ * Various remarks.
+ *
+ * @var string
+ */
+ private $_remarks = '';
+
+ /**
+ * Any comment provided for the index with a COMMENT attribute when the
+ * index was created.
+ *
+ * @var string
+ */
+ private $_comment = '';
+
+ /**
+ * @var integer 0 if the index cannot contain duplicates, 1 if it can.
+ */
+ private $_non_unique = 0;
+
+ /**
+ * Indicates how the key is packed. NULL if it is not.
+ *
+ * @var string
+ */
+ private $_packed = null;
+
+ /**
+ * Constructor
+ *
+ * @param array $params parameters
+ */
+ public function __construct($params = array())
+ {
+ $this->set($params);
+ }
+
+ /**
+ * Creates(if not already created) and returns the corresponding Index object
+ *
+ * @param string $schema database name
+ * @param string $table table name
+ * @param string $index_name index name
+ *
+ * @return PMA_Index corresponding Index object
+ */
+ static public function singleton($schema, $table, $index_name = '')
+ {
+ PMA_Index::_loadIndexes($table, $schema);
+ if (! isset(PMA_Index::$_registry[$schema][$table][$index_name])) {
+ $index = new PMA_Index;
+ if (strlen($index_name)) {
+ $index->setName($index_name);
+ PMA_Index::$_registry[$schema][$table][$index->getName()] = $index;
+ }
+ return $index;
+ } else {
+ return PMA_Index::$_registry[$schema][$table][$index_name];
+ }
+ }
+
+ /**
+ * returns an array with all indexes from the given table
+ *
+ * @param string $table table
+ * @param string $schema schema
+ *
+ * @return array array of indexes
+ */
+ static public function getFromTable($table, $schema)
+ {
+ PMA_Index::_loadIndexes($table, $schema);
+
+ if (isset(PMA_Index::$_registry[$schema][$table])) {
+ return PMA_Index::$_registry[$schema][$table];
+ } else {
+ return array();
+ }
+ }
+
+ /**
+ * return primary if set, false otherwise
+ *
+ * @param string $table table
+ * @param string $schema schema
+ *
+ * @return mixed primary index or false if no one exists
+ */
+ static public function getPrimary($table, $schema)
+ {
+ PMA_Index::_loadIndexes($table, $schema);
+
+ if (isset(PMA_Index::$_registry[$schema][$table]['PRIMARY'])) {
+ return PMA_Index::$_registry[$schema][$table]['PRIMARY'];
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Load index data for table
+ *
+ * @param string $table table
+ * @param string $schema schema
+ *
+ * @return boolean whether loading was successful
+ */
+ static private function _loadIndexes($table, $schema)
+ {
+ if (isset(PMA_Index::$_registry[$schema][$table])) {
+ return true;
+ }
+
+ $_raw_indexes = $GLOBALS['dbi']->getTableIndexes($schema, $table);
+ foreach ($_raw_indexes as $_each_index) {
+ $_each_index['Schema'] = $schema;
+ $keyName = $_each_index['Key_name'];
+ if (! isset(PMA_Index::$_registry[$schema][$table][$keyName])) {
+ $key = new PMA_Index($_each_index);
+ PMA_Index::$_registry[$schema][$table][$keyName] = $key;
+ } else {
+ $key = PMA_Index::$_registry[$schema][$table][$keyName];
+ }
+
+ $key->addColumn($_each_index);
+ }
+
+ return true;
+ }
+
+ /**
+ * Add column to index
+ *
+ * @param array $params column params
+ *
+ * @return void
+ */
+ public function addColumn($params)
+ {
+ if (strlen($params['Column_name'])) {
+ $this->_columns[$params['Column_name']] = new PMA_Index_Column($params);
+ }
+ }
+
+ /**
+ * Adds a list of columns to the index
+ *
+ * @param array $columns array containing details about the columns
+ *
+ * @return void
+ */
+ public function addColumns($columns)
+ {
+ $_columns = array();
+
+ if (isset($columns['names'])) {
+ // coming from form
+ // $columns[names][]
+ // $columns[sub_parts][]
+ foreach ($columns['names'] as $key => $name) {
+ $sub_part = isset($columns['sub_parts'][$key])
+ ? $columns['sub_parts'][$key] : '';
+ $_columns[] = array(
+ 'Column_name' => $name,
+ 'Sub_part' => $sub_part,
+ );
+ }
+ } else {
+ // coming from SHOW INDEXES
+ // $columns[][name]
+ // $columns[][sub_part]
+ // ...
+ $_columns = $columns;
+ }
+
+ foreach ($_columns as $column) {
+ $this->addColumn($column);
+ }
+ }
+
+ /**
+ * Returns true if $column indexed in this index
+ *
+ * @param string $column the column
+ *
+ * @return boolean true if $column indexed in this index
+ */
+ public function hasColumn($column)
+ {
+ return isset($this->_columns[$column]);
+ }
+
+ /**
+ * Sets index details
+ *
+ * @param array $params index details
+ *
+ * @return void
+ */
+ public function set($params)
+ {
+ if (isset($params['columns'])) {
+ $this->addColumns($params['columns']);
+ }
+ if (isset($params['Schema'])) {
+ $this->_schema = $params['Schema'];
+ }
+ if (isset($params['Table'])) {
+ $this->_table = $params['Table'];
+ }
+ if (isset($params['Key_name'])) {
+ $this->_name = $params['Key_name'];
+ }
+ if (isset($params['Index_type'])) {
+ $this->_type = $params['Index_type'];
+ }
+ if (isset($params['Comment'])) {
+ $this->_remarks = $params['Comment'];
+ }
+ if (isset($params['Index_comment'])) {
+ $this->_comment = $params['Index_comment'];
+ }
+ if (isset($params['Non_unique'])) {
+ $this->_non_unique = $params['Non_unique'];
+ }
+ if (isset($params['Packed'])) {
+ $this->_packed = $params['Packed'];
+ }
+ if ('PRIMARY' == $this->_name) {
+ $this->_choice = 'PRIMARY';
+ } elseif ('FULLTEXT' == $this->_type) {
+ $this->_choice = 'FULLTEXT';
+ } elseif ('SPATIAL' == $this->_type) {
+ $this->_choice = 'SPATIAL';
+ } elseif ('0' == $this->_non_unique) {
+ $this->_choice = 'UNIQUE';
+ } else {
+ $this->_choice = 'INDEX';
+ }
+ }
+
+ /**
+ * Returns the number of columns of the index
+ *
+ * @return integer the number of the columns
+ */
+ public function getColumnCount()
+ {
+ return count($this->_columns);
+ }
+
+ /**
+ * Returns the index comment
+ *
+ * @return string index comment
+ */
+ public function getComment()
+ {
+ return $this->_comment;
+ }
+
+ /**
+ * Returns index remarks
+ *
+ * @return string index remarks
+ */
+ public function getRemarks()
+ {
+ return $this->_remarks;
+ }
+
+ /**
+ * Returns concatenated remarks and comment
+ *
+ * @return string concatenated remarks and comment
+ */
+ public function getComments()
+ {
+ $comments = $this->getRemarks();
+ if (strlen($comments)) {
+ $comments .= "\n";
+ }
+ $comments .= $this->getComment();
+
+ return $comments;
+ }
+
+ /**
+ * Returns index type ((BTREE, SPATIAL, FULLTEXT, HASH, RTREE)
+ *
+ * @return string index type
+ */
+ public function getType()
+ {
+ return $this->_type;
+ }
+
+ /**
+ * Returns index choice (PRIMARY, UNIQUE, INDEX, SPATIAL, FULLTEXT)
+ *
+ * @return string index choice
+ */
+ public function getChoice()
+ {
+ return $this->_choice;
+ }
+
+ /**
+ * Return a list of all index choices
+ *
+ * @return array index choices
+ */
+ static public function getIndexChoices()
+ {
+ return array(
+ 'PRIMARY',
+ 'INDEX',
+ 'UNIQUE',
+ 'SPATIAL',
+ 'FULLTEXT',
+ );
+ }
+
+ /**
+ * Returns HTML for the index choice selector
+ *
+ * @return string HTML for the index choice selector
+ */
+ public function generateIndexSelector()
+ {
+ $html_options = '';
+
+ foreach (PMA_Index::getIndexChoices() as $each_index_choice) {
+ if ($each_index_choice === 'PRIMARY'
+ && $this->_choice !== 'PRIMARY'
+ && PMA_Index::getPrimary($this->_table, $this->_schema)
+ ) {
+ // skip PRIMARY if there is already one in the table
+ continue;
+ }
+ $html_options .= '<option value="' . $each_index_choice . '"'
+ . (($this->_choice == $each_index_choice)
+ ? ' selected="selected"'
+ : '')
+ . '>'. $each_index_choice . '</option>' . "\n";
+ }
+
+ return $html_options;
+ }
+
+ /**
+ * Returns how the index is packed
+ *
+ * @return string how the index is packed
+ */
+ public function getPacked()
+ {
+ return $this->_packed;
+ }
+
+ /**
+ * Returns 'No'/false if the index is not packed,
+ * how the index is packed if packed
+ *
+ * @param boolean $as_text whether to output should be in text
+ *
+ * @return mixed how index is paked
+ */
+ public function isPacked($as_text = false)
+ {
+ if ($as_text) {
+ $r = array(
+ '0' => __('No'),
+ '1' => __('Yes'),
+ );
+ } else {
+ $r = array(
+ '0' => false,
+ '1' => true,
+ );
+ }
+
+ if (null === $this->_packed) {
+ return $r[0];
+ }
+
+ return $this->_packed;
+ }
+
+ /**
+ * Returns integer 0 if the index cannot contain duplicates, 1 if it can
+ *
+ * @return integer 0 if the index cannot contain duplicates, 1 if it can
+ */
+ public function getNonUnique()
+ {
+ return $this->_non_unique;
+ }
+
+ /**
+ * Returns whether the index is a 'Unique' index
+ *
+ * @param boolean $as_text whether to output should be in text
+ *
+ * @return mixed whether the index is a 'Unique' index
+ */
+ public function isUnique($as_text = false)
+ {
+ if ($as_text) {
+ $r = array(
+ '0' => __('Yes'),
+ '1' => __('No'),
+ );
+ } else {
+ $r = array(
+ '0' => true,
+ '1' => false,
+ );
+ }
+
+ return $r[$this->_non_unique];
+ }
+
+ /**
+ * Returns the name of the index
+ *
+ * @return string the name of the index
+ */
+ public function getName()
+ {
+ return $this->_name;
+ }
+
+ /**
+ * Sets the name of the index
+ *
+ * @param string $name index name
+ *
+ * @return void
+ */
+ public function setName($name)
+ {
+ $this->_name = (string) $name;
+ }
+
+ /**
+ * Returns the columns of the index
+ *
+ * @return array the columns of the index
+ */
+ public function getColumns()
+ {
+ return $this->_columns;
+ }
+
+ /**
+ * Show index data
+ *
+ * @param string $table The table name
+ * @param string $schema The schema name
+ * @param boolean $print_mode Whether the output is for the print mode
+ *
+ * @return array Index collection array
+ *
+ * @access public
+ */
+ static public function getView($table, $schema, $print_mode = false)
+ {
+ $indexes = PMA_Index::getFromTable($table, $schema);
+
+ $no_indexes_class = count($indexes) > 0 ? ' hide' : '';
+ $no_indexes = "<div class='no_indexes_defined$no_indexes_class'>";
+ $no_indexes .= PMA_Message::notice(__('No index defined!'))->getDisplay();
+ $no_indexes .= '</div>';
+
+ if (! $print_mode) {
+ $r = '<fieldset class="index_info">';
+ $r .= '<legend id="index_header">' . __('Indexes');
+ $r .= PMA_Util::showMySQLDocu('optimizing-database-structure');
+
+ $r .= '</legend>';
+ $r .= $no_indexes;
+ if (count($indexes) < 1) {
+ $r .= '</fieldset>';
+ return $r;
+ }
+ $r .= PMA_Index::findDuplicates($table, $schema);
+ } else {
+ $r = '<h3>' . __('Indexes') . '</h3>';
+ $r .= $no_indexes;
+ if (count($indexes) < 1) {
+ return $r;
+ }
+ }
+ $r .= '<table id="table_index">';
+ $r .= '<thead>';
+ $r .= '<tr>';
+ if (! $print_mode) {
+ $r .= '<th colspan="2">' . __('Action') . '</th>';
+ }
+ $r .= '<th>' . __('Keyname') . '</th>';
+ $r .= '<th>' . __('Type') . '</th>';
+ $r .= '<th>' . __('Unique') . '</th>';
+ $r .= '<th>' . __('Packed') . '</th>';
+ $r .= '<th>' . __('Column') . '</th>';
+ $r .= '<th>' . __('Cardinality') . '</th>';
+ $r .= '<th>' . __('Collation') . '</th>';
+ $r .= '<th>' . __('Null') . '</th>';
+ if (PMA_MYSQL_INT_VERSION > 50500) {
+ $r .= '<th>' . __('Comment') . '</th>';
+ }
+ $r .= '</tr>';
+ $r .= '</thead>';
+ $r .= '<tbody>';
+
+ $odd_row = true;
+ foreach ($indexes as $index) {
+ $row_span = ' rowspan="' . $index->getColumnCount() . '" ';
+
+ $r .= '<tr class="noclick ' . ($odd_row ? 'odd' : 'even') . '">';
+
+ if (! $print_mode) {
+ $this_params = $GLOBALS['url_params'];
+ $this_params['index'] = $index->getName();
+ $r .= '<td class="edit_index';
+ $r .= ' ajax';
+ $r .= '" ' . $row_span . '>'
+ . ' <a class="';
+ $r .= 'ajax';
+ $r .= '" href="tbl_indexes.php' . PMA_URL_getCommon($this_params)
+ . '">' . PMA_Util::getIcon('b_edit.png', __('Edit')) . '</a>'
+ . '</td>' . "\n";
+ $this_params = $GLOBALS['url_params'];
+ if ($index->getName() == 'PRIMARY') {
+ $this_params['sql_query'] = 'ALTER TABLE '
+ . PMA_Util::backquote($table)
+ . ' DROP PRIMARY KEY;';
+ $this_params['message_to_show']
+ = __('The primary key has been dropped');
+ $js_msg = PMA_jsFormat(
+ 'ALTER TABLE ' . $table . ' DROP PRIMARY KEY'
+ );
+ } else {
+ $this_params['sql_query'] = 'ALTER TABLE '
+ . PMA_Util::backquote($table) . ' DROP INDEX '
+ . PMA_Util::backquote($index->getName()) . ';';
+ $this_params['message_to_show'] = sprintf(
+ __('Index %s has been dropped.'), $index->getName()
+ );
+
+ $js_msg = PMA_jsFormat(
+ 'ALTER TABLE ' . $table . ' DROP INDEX '
+ . $index->getName() . ';'
+ );
+
+ }
+
+ $r .= '<td ' . $row_span . '>';
+ $r .= '<input type="hidden" class="drop_primary_key_index_msg"'
+ . ' value="' . $js_msg . '" />';
+ $r .= ' <a class="drop_primary_key_index_anchor';
+ $r .= ' ajax';
+ $r .= '" href="sql.php' . PMA_URL_getCommon($this_params)
+ . '" >'
+ . PMA_Util::getIcon('b_drop.png', __('Drop')) . '</a>'
+ . '</td>' . "\n";
+ }
+
+ if (! $print_mode) {
+ $r .= '<th ' . $row_span . '>'
+ . htmlspecialchars($index->getName())
+ . '</th>';
+ } else {
+ $r .= '<td ' . $row_span . '>'
+ . htmlspecialchars($index->getName())
+ . '</td>';
+ }
+ $r .= '<td ' . $row_span . '>'
+ . htmlspecialchars($index->getType())
+ . '</td>';
+ $r .= '<td ' . $row_span . '>' . $index->isUnique(true) . '</td>';
+ $r .= '<td ' . $row_span . '>' . $index->isPacked(true) . '</td>';
+
+ foreach ($index->getColumns() as $column) {
+ if ($column->getSeqInIndex() > 1) {
+ $r .= '<tr class="noclick ' . ($odd_row ? 'odd' : 'even') . '">';
+ }
+ $r .= '<td>' . htmlspecialchars($column->getName());
+ if ($column->getSubPart()) {
+ $r .= ' (' . $column->getSubPart() . ')';
+ }
+ $r .= '</td>';
+ $r .= '<td>'
+ . htmlspecialchars($column->getCardinality())
+ . '</td>';
+ $r .= '<td>'
+ . htmlspecialchars($column->getCollation())
+ . '</td>';
+ $r .= '<td>'
+ . htmlspecialchars($column->getNull(true))
+ . '</td>';
+
+ if (PMA_MYSQL_INT_VERSION > 50500
+ && $column->getSeqInIndex() == 1
+ ) {
+ $r .= '<td ' . $row_span . '>'
+ . htmlspecialchars($index->getComments()) . '</td>';
+ }
+ $r .= '</tr>';
+ } // end foreach $index['Sequences']
+
+ $odd_row = ! $odd_row;
+ } // end while
+ $r .= '</tbody>';
+ $r .= '</table>';
+ if (! $print_mode) {
+ $r .= '</fieldset>';
+ }
+
+ return $r;
+ }
+
+ /**
+ * Gets the properties in an array for comparison purposes
+ *
+ * @return array an array containing the properties of the index
+ */
+ public function getCompareData()
+ {
+ $data = array(
+ // 'Non_unique' => $this->_non_unique,
+ 'Packed' => $this->_packed,
+ 'Index_type' => $this->_type,
+ );
+
+ foreach ($this->_columns as $column) {
+ $data['columns'][] = $column->getCompareData();
+ }
+
+ return $data;
+ }
+
+ /**
+ * Function to check over array of indexes and look for common problems
+ *
+ * @param string $table table name
+ * @param string $schema schema name
+ *
+ * @return string Output HTML
+ * @access public
+ */
+ static public function findDuplicates($table, $schema)
+ {
+ $indexes = PMA_Index::getFromTable($table, $schema);
+
+ $output = '';
+
+ // count($indexes) < 2:
+ // there is no need to check if there less than two indexes
+ if (count($indexes) < 2) {
+ return $output;
+ }
+
+ // remove last index from stack and ...
+ while ($while_index = array_pop($indexes)) {
+ // ... compare with every remaining index in stack
+ foreach ($indexes as $each_index) {
+ if ($each_index->getCompareData() !== $while_index->getCompareData()) {
+ continue;
+ }
+
+ // did not find any difference
+ // so it makes no sense to have this two equal indexes
+
+ $message = PMA_Message::notice(
+ __('The indexes %1$s and %2$s seem to be equal and one of them could possibly be removed.')
+ );
+ $message->addParam($each_index->getName());
+ $message->addParam($while_index->getName());
+ $output .= $message->getDisplay();
+
+ // there is no need to check any further indexes if we have already
+ // found that this one has a duplicate
+ continue 2;
+ }
+ }
+ return $output;
+ }
+}
+
+/**
+ * Index column wrapper
+ *
+ * @package PhpMyAdmin
+ */
+class PMA_Index_Column
+{
+ /**
+ * @var string The column name
+ */
+ private $_name = '';
+
+ /**
+ * @var integer The column sequence number in the index, starting with 1.
+ */
+ private $_seq_in_index = 1;
+
+ /**
+ * @var string How the column is sorted in the index. “A†(Ascending) or
+ * NULL (Not sorted)
+ */
+ private $_collation = null;
+
+ /**
+ * The number of indexed characters if the column is only partly indexed,
+ * NULL if the entire column is indexed.
+ *
+ * @var integer
+ */
+ private $_sub_part = null;
+
+ /**
+ * Contains YES if the column may contain NULL.
+ * If not, the column contains NO.
+ *
+ * @var string
+ */
+ private $_null = '';
+
+ /**
+ * An estimate of the number of unique values in the index. This is updated
+ * by running ANALYZE TABLE or myisamchk -a. Cardinality is counted based on
+ * statistics stored as integers, so the value is not necessarily exact even
+ * for small tables. The higher the cardinality, the greater the chance that
+ * MySQL uses the index when doing joins.
+ *
+ * @var integer
+ */
+ private $_cardinality = null;
+
+ /**
+ * Constructor
+ *
+ * @param array $params an array containing the parameters of the index column
+ */
+ public function __construct($params = array())
+ {
+ $this->set($params);
+ }
+
+ /**
+ * Sets parameters of the index column
+ *
+ * @param array $params an array containing the parameters of the index column
+ *
+ * @return void
+ */
+ public function set($params)
+ {
+ if (isset($params['Column_name'])) {
+ $this->_name = $params['Column_name'];
+ }
+ if (isset($params['Seq_in_index'])) {
+ $this->_seq_in_index = $params['Seq_in_index'];
+ }
+ if (isset($params['Collation'])) {
+ $this->_collation = $params['Collation'];
+ }
+ if (isset($params['Cardinality'])) {
+ $this->_cardinality = $params['Cardinality'];
+ }
+ if (isset($params['Sub_part'])) {
+ $this->_sub_part = $params['Sub_part'];
+ }
+ if (isset($params['Null'])) {
+ $this->_null = $params['Null'];
+ }
+ }
+
+ /**
+ * Returns the column name
+ *
+ * @return string column name
+ */
+ public function getName()
+ {
+ return $this->_name;
+ }
+
+ /**
+ * Return the column collation
+ *
+ * @return string column collation
+ */
+ public function getCollation()
+ {
+ return $this->_collation;
+ }
+
+ /**
+ * Returns the cardinality of the column
+ *
+ * @return int cardinality of the column
+ */
+ public function getCardinality()
+ {
+ return $this->_cardinality;
+ }
+
+ /**
+ * Returns whether the column is nullable
+ *
+ * @param boolean $as_text whether to returned the string representation
+ *
+ * @return mixed nullability of the column. True/false or Yes/No depending
+ * on the value of the $as_text parameter
+ */
+ public function getNull($as_text = false)
+ {
+ return $as_text
+ ? (!$this->_null || $this->_null == 'NO' ? __('No') : __('Yes'))
+ : $this->_null;
+ }
+
+ /**
+ * Returns the sequence number of the column in the index
+ *
+ * @return int sequence number of the column in the index
+ */
+ public function getSeqInIndex()
+ {
+ return $this->_seq_in_index;
+ }
+
+ /**
+ * Returns the number of indexed characters if the column is only
+ * partly indexed
+ *
+ * @return int the number of indexed characters
+ */
+ public function getSubPart()
+ {
+ return $this->_sub_part;
+ }
+
+ /**
+ * Gets the properties in an array for comparison purposes
+ *
+ * @return array an array containing the properties of the index column
+ */
+ public function getCompareData()
+ {
+ return array(
+ 'Column_name' => $this->_name,
+ 'Seq_in_index' => $this->_seq_in_index,
+ 'Collation' => $this->_collation,
+ 'Sub_part' => $this->_sub_part,
+ 'Null' => $this->_null,
+ );
+ }
+}
+?>
diff --git a/libraries/List.class.php b/libraries/List.class.php
new file mode 100644
index 0000000000..eb40b86098
--- /dev/null
+++ b/libraries/List.class.php
@@ -0,0 +1,132 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * hold the PMA_List base class
+ *
+ * @package PhpMyAdmin
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ * Generic list class
+ *
+ * @todo add caching
+ * @abstract
+ * @package PhpMyAdmin
+ * @since phpMyAdmin 2.9.10
+ */
+abstract class PMA_List extends ArrayObject
+{
+ /**
+ * @var mixed empty item
+ */
+ protected $item_empty = '';
+
+ /**
+ * PMA_List constructor
+ *
+ * @param array $array The input parameter accepts an array or an Object.
+ * @param int $flags Flags to control the behaviour of the ArrayObject object.
+ * @param string $iterator_class Specify the class that will be used for iteration of the ArrayObject object. ArrayIterator is the default class used.
+ */
+ public function __construct(
+ $array = array(), $flags = 0, $iterator_class = "ArrayIterator"
+ ) {
+ parent::__construct($array, $flags, $iterator_class);
+ }
+
+ /**
+ * returns item only if there is only one in the list
+ *
+ * @return PMA_List single item
+ */
+ public function getSingleItem()
+ {
+ if (count($this) === 1) {
+ return reset($this);
+ }
+
+ return $this->getEmpty();
+ }
+
+ /**
+ * defines what is an empty item (0, '', false or null)
+ *
+ * @return mixed an empty item
+ */
+ public function getEmpty()
+ {
+ return $this->item_empty;
+ }
+
+ /**
+ * checks if the given db names exists in the current list, if there is
+ * missing at least one item it returns false otherwise true
+ *
+ * @return boolean true if all items exists, otheriwse false
+ */
+ public function exists()
+ {
+ $this_elements = $this->getArrayCopy();
+ foreach (func_get_args() as $result) {
+ if (! in_array($result, $this_elements)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * returns HTML <option>-tags to be used inside <select></select>
+ *
+ * @param mixed $selected the selected db or true for
+ * selecting current db
+ * @param boolean $include_information_schema whether include information schema
+ *
+ * @return string HTML option tags
+ */
+ public function getHtmlOptions(
+ $selected = '', $include_information_schema = true
+ ) {
+ if (true === $selected) {
+ $selected = $this->getDefault();
+ }
+
+ $options = '';
+ foreach ($this as $each_item) {
+ if (false === $include_information_schema
+ && $GLOBALS['dbi']->isSystemSchema($each_item)
+ ) {
+ continue;
+ }
+ $options .= '<option value="' . htmlspecialchars($each_item) . '"';
+ if ($selected === $each_item) {
+ $options .= ' selected="selected"';
+ }
+ $options .= '>' . htmlspecialchars($each_item) . '</option>' . "\n";
+ }
+
+ return $options;
+ }
+
+ /**
+ * returns default item
+ *
+ * @return string default item
+ */
+ public function getDefault()
+ {
+ return $this->getEmpty();
+ }
+
+ /**
+ * builds up the list
+ *
+ * @return void
+ */
+ abstract public function build();
+}
+?>
diff --git a/libraries/List_Database.class.php b/libraries/List_Database.class.php
new file mode 100644
index 0000000000..fcb5481783
--- /dev/null
+++ b/libraries/List_Database.class.php
@@ -0,0 +1,218 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * holds the PMA_List_Database class
+ *
+ * @package PhpMyAdmin
+ */
+
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ * the list base class
+ */
+require_once './libraries/List.class.php';
+
+/**
+ * handles database lists
+ *
+ * <code>
+ * $PMA_List_Database = new PMA_List_Database($userlink, $controllink);
+ * </code>
+ *
+ * @todo this object should be attached to the PMA_Server object
+ * @todo ? make use of INFORMATION_SCHEMA
+ * @todo ? support --skip-showdatabases and user has only global rights
+ *
+ * @package PhpMyAdmin
+ * @since phpMyAdmin 2.9.10
+ */
+class PMA_List_Database extends PMA_List
+{
+ /**
+ * @var mixed database link resource|object to be used
+ * @access protected
+ */
+ protected $db_link = null;
+
+ /**
+ * @var mixed user database link resource|object
+ * @access protected
+ */
+ protected $db_link_user = null;
+
+ /**
+ * @var mixed controluser database link resource|object
+ * @access protected
+ */
+ protected $db_link_control = null;
+
+ /**
+ * @var boolean whether we can retrieve the list of databases
+ * @access protected
+ */
+ protected $can_retrieve_databases = true;
+
+ /**
+ * Constructor
+ *
+ * @param mixed $db_link_user user database link resource|object
+ * @param mixed $db_link_control control database link resource|object
+ */
+ public function __construct($db_link_user = null, $db_link_control = null)
+ {
+ $this->db_link = $db_link_user;
+ $this->db_link_user = $db_link_user;
+ $this->db_link_control = $db_link_control;
+
+ parent::__construct();
+ $this->build();
+ }
+
+ /**
+ * checks if the configuration wants to hide some databases
+ *
+ * @return void
+ */
+ protected function checkHideDatabase()
+ {
+ if (empty($GLOBALS['cfg']['Server']['hide_db'])) {
+ return;
+ }
+
+ foreach ($this->getArrayCopy() as $key => $db) {
+ if (preg_match('/' . $GLOBALS['cfg']['Server']['hide_db'] . '/', $db)) {
+ $this->offsetUnset($key);
+ }
+ }
+ }
+
+ /**
+ * retrieves database list from server
+ *
+ * @param string $like_db_name usually a db_name containing wildcards
+ *
+ * @return array
+ */
+ protected function retrieve($like_db_name = null)
+ {
+ if (! $this->can_retrieve_databases) {
+ return array();
+ }
+
+ $command = "SELECT `SCHEMA_NAME` FROM `INFORMATION_SCHEMA`.`SCHEMATA`"
+ . " WHERE TRUE";
+
+ if (null !== $like_db_name) {
+ $command .= " AND `SCHEMA_NAME` LIKE '" . $like_db_name . "'";
+ }
+
+ $database_list = $GLOBALS['dbi']->fetchResult(
+ $command, null, null, $this->db_link
+ );
+ $GLOBALS['dbi']->getError();
+
+ if ($GLOBALS['errno'] !== 0) {
+ // failed to get database list, try the control user
+ // (hopefully there is one and he has the necessary rights)
+ $this->db_link = $this->db_link_control;
+ $database_list = $GLOBALS['dbi']->fetchResult(
+ $command, null, null, $this->db_link
+ );
+
+ $GLOBALS['dbi']->getError();
+
+ if ($GLOBALS['errno'] !== 0) {
+ // failed! we will display a warning that phpMyAdmin could not
+ // safely retrieve database list, the admin has to setup a
+ // control user
+ $GLOBALS['error_showdatabases'] = true;
+ $this->can_retrieve_databases = false;
+ }
+ }
+
+ if ($GLOBALS['cfg']['NaturalOrder']) {
+ natsort($database_list);
+ } else {
+ // need to sort anyway, otherwise information_schema
+ // goes at the top
+ sort($database_list);
+ }
+
+ return $database_list;
+ }
+
+ /**
+ * builds up the list
+ *
+ * @return void
+ */
+ public function build()
+ {
+ if (! $this->checkOnlyDatabase()) {
+ $items = $this->retrieve();
+ $this->exchangeArray($items);
+ }
+
+ $this->checkHideDatabase();
+ }
+
+ /**
+ * checks the only_db configuration
+ *
+ * @return boolean false if there is no only_db, otherwise true
+ */
+ protected function checkOnlyDatabase()
+ {
+ if (is_string($GLOBALS['cfg']['Server']['only_db'])
+ && strlen($GLOBALS['cfg']['Server']['only_db'])
+ ) {
+ $GLOBALS['cfg']['Server']['only_db'] = array(
+ $GLOBALS['cfg']['Server']['only_db']
+ );
+ }
+
+ if (! is_array($GLOBALS['cfg']['Server']['only_db'])) {
+ return false;
+ }
+
+ $items = array();
+
+ foreach ($GLOBALS['cfg']['Server']['only_db'] as $each_only_db) {
+
+ // check if the db name contains wildcard,
+ // thus containing not escaped _ or %
+ if (! preg_match('/(^|[^\\\\])(_|%)/', $each_only_db)) {
+ // ... not contains wildcard
+ $items[] = PMA_Util::unescapeMysqlWildcards($each_only_db);
+ continue;
+ }
+
+ if ($this->can_retrieve_databases) {
+ $items = array_merge($items, $this->retrieve($each_only_db));
+ continue;
+ }
+ }
+
+ $this->exchangeArray($items);
+
+ return true;
+ }
+
+ /**
+ * returns default item
+ *
+ * @return string default item
+ */
+ public function getDefault()
+ {
+ if (strlen($GLOBALS['db'])) {
+ return $GLOBALS['db'];
+ }
+
+ return $this->getEmpty();
+ }
+}
+?>
diff --git a/libraries/Menu.class.php b/libraries/Menu.class.php
new file mode 100644
index 0000000000..6f07b59417
--- /dev/null
+++ b/libraries/Menu.class.php
@@ -0,0 +1,588 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Generates and renders the top menu
+ *
+ * @package PhpMyAdmin
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ * Class for generating the top menu
+ *
+ * @package PhpMyAdmin
+ */
+class PMA_Menu
+{
+ /**
+ * Server id
+ *
+ * @access private
+ * @var string
+ */
+ private $_server;
+ /**
+ * Database name
+ *
+ * @access private
+ * @var string
+ */
+ private $_db;
+ /**
+ * Table name
+ *
+ * @access private
+ * @var string
+ */
+ private $_table;
+
+ /**
+ * Creates a new instance of PMA_Menu
+ *
+ * @param int $server Server id
+ * @param string $db Database name
+ * @param string $table Table name
+ */
+ public function __construct($server, $db, $table)
+ {
+ $this->_server = $server;
+ $this->_db = $db;
+ $this->_table = $table;
+ }
+
+ /**
+ * Prints the menu and the breadcrumbs
+ *
+ * @return void
+ */
+ public function display()
+ {
+ echo $this->getDisplay();
+ }
+
+ /**
+ * Returns the menu and the breadcrumbs as a string
+ *
+ * @return string
+ */
+ public function getDisplay()
+ {
+ $retval = $this->_getBreadcrumbs();
+ $retval .= $this->_getMenu();
+ return $retval;
+ }
+
+ /**
+ * Returns hash for the menu and the breadcrumbs
+ *
+ * @return string
+ */
+ public function getHash()
+ {
+ return substr(
+ md5($this->_getMenu() . $this->_getBreadcrumbs()),
+ 0,
+ 8
+ );
+ }
+
+ /**
+ * Returns the menu as HTML
+ *
+ * @return string HTML formatted menubar
+ */
+ private function _getMenu()
+ {
+ $tabs = array();
+ $url_params = array('db' => $this->_db);
+ $level = '';
+
+ if (strlen($this->_table)) {
+ $tabs = $this->_getTableTabs();
+ $url_params['table'] = $this->_table;
+ $level = 'table';
+ } else if (strlen($this->_db)) {
+ $tabs = $this->_getDbTabs();
+ $level = 'db';
+ } else {
+ $tabs = $this->_getServerTabs();
+ $level = 'server';
+ }
+
+ $allowedTabs = $this->_getAllowedTabs($level);
+ foreach ($tabs as $key => $value) {
+ if (! array_key_exists($key, $allowedTabs)) {
+ unset($tabs[$key]);
+ }
+ }
+ return PMA_Util::getHtmlTabs($tabs, $url_params, 'topmenu', true);
+ }
+
+ /**
+ * Returns a list of allowed tabs for the current user for the given level
+ *
+ * @param string $level 'server', 'db' or 'table' level
+ *
+ * @return array list of allowed tabs
+ */
+ private function _getAllowedTabs($level)
+ {
+ $allowedTabs = PMA_Util::getMenuTabList($level);
+ $cfgRelation = PMA_getRelationsParam();
+ if ($cfgRelation['menuswork']) {
+ $groupTable = PMA_Util::backquote($GLOBALS['cfg']['Server']['pmadb'])
+ . "."
+ . PMA_Util::backquote($GLOBALS['cfg']['Server']['usergroups']);
+ $userTable = PMA_Util::backquote($GLOBALS['cfg']['Server']['pmadb'])
+ . "." . PMA_Util::backquote($GLOBALS['cfg']['Server']['users']);
+
+ $sql_query = "SELECT `tab` FROM " . $groupTable
+ . " WHERE `allowed` = 'N' AND `usergroup` = (SELECT usergroup FROM "
+ . $userTable . " WHERE `username` = '"
+ . PMA_Util::sqlAddSlashes($GLOBALS['cfg']['Server']['user']) . "')";
+
+ $result = PMA_queryAsControlUser($sql_query, false);
+ if ($result) {
+ while ($row = $GLOBALS['dbi']->fetchAssoc($result)) {
+ $tabName = substr($row['tab'], strpos($row['tab'], '_') + 1);
+ unset($allowedTabs[$tabName]);
+ }
+ }
+ }
+ return $allowedTabs;
+ }
+
+ /**
+ * Returns the breadcrumbs as HTML
+ *
+ * @return string HTML formatted breadcrumbs
+ */
+ private function _getBreadcrumbs()
+ {
+ $retval = '';
+ $tbl_is_view = PMA_Table::isView($this->_db, $this->_table);
+ $server_info = ! empty($GLOBALS['cfg']['Server']['verbose'])
+ ? $GLOBALS['cfg']['Server']['verbose']
+ : $GLOBALS['cfg']['Server']['host'];
+ $server_info .= empty($GLOBALS['cfg']['Server']['port'])
+ ? ''
+ : ':' . $GLOBALS['cfg']['Server']['port'];
+
+ $separator = "<span class='separator item'>&nbsp;»</span>";
+ $item = '<a href="%1$s?%2$s" class="item">';
+
+
+ if (PMA_Util::showText('TabsMode')) {
+ $item .= '%4$s: ';
+ }
+ $item .= '%3$s</a>';
+ $retval .= "<div id='floating_menubar'></div>";
+ $retval .= "<div id='serverinfo'>";
+ if (PMA_Util::showIcons('TabsMode')) {
+ $retval .= PMA_Util::getImage(
+ 's_host.png',
+ '',
+ array('class' => 'item')
+ );
+ }
+ $retval .= sprintf(
+ $item,
+ $GLOBALS['cfg']['DefaultTabServer'],
+ PMA_URL_getCommon(),
+ htmlspecialchars($server_info),
+ __('Server')
+ );
+
+ if (strlen($this->_db)) {
+ $retval .= $separator;
+ if (PMA_Util::showIcons('TabsMode')) {
+ $retval .= PMA_Util::getImage(
+ 's_db.png',
+ '',
+ array('class' => 'item')
+ );
+ }
+ $retval .= sprintf(
+ $item,
+ $GLOBALS['cfg']['DefaultTabDatabase'],
+ PMA_URL_getCommon($this->_db),
+ htmlspecialchars($this->_db),
+ __('Database')
+ );
+ // if the table is being dropped, $_REQUEST['purge'] is set to '1'
+ // so do not display the table name in upper div
+ if (strlen($this->_table)
+ && ! (isset($_REQUEST['purge']) && $_REQUEST['purge'] == '1')
+ ) {
+ include './libraries/tbl_info.inc.php';
+
+ $retval .= $separator;
+ if (PMA_Util::showIcons('TabsMode')) {
+ $icon = $tbl_is_view ? 'b_views.png' : 's_tbl.png';
+ $retval .= PMA_Util::getImage(
+ $icon,
+ '',
+ array('class' => 'item')
+ );
+ }
+ $retval .= sprintf(
+ $item,
+ $GLOBALS['cfg']['DefaultTabTable'],
+ PMA_URL_getCommon($this->_db, $this->_table),
+ str_replace(' ', '&nbsp;', htmlspecialchars($this->_table)),
+ $tbl_is_view ? __('View') : __('Table')
+ );
+
+ /**
+ * Displays table comment
+ */
+ if (! empty($show_comment)
+ && ! isset($GLOBALS['avoid_show_comment'])
+ ) {
+ if (strstr($show_comment, '; InnoDB free')) {
+ $show_comment = preg_replace(
+ '@; InnoDB free:.*?$@',
+ '',
+ $show_comment
+ );
+ }
+ $retval .= '<span class="table_comment"';
+ $retval .= ' id="span_table_comment">&quot;';
+ $retval .= htmlspecialchars($show_comment);
+ $retval .= '&quot;</span>';
+ } // end if
+ } else {
+ // no table selected, display database comment if present
+ $cfgRelation = PMA_getRelationsParam();
+
+ // Get additional information about tables for tooltip is done
+ // in libraries/db_info.inc.php only once
+ if ($cfgRelation['commwork']) {
+ $comment = PMA_getDbComment($this->_db);
+ /**
+ * Displays table comment
+ */
+ if (! empty($comment)) {
+ $retval .= '<span class="table_comment"'
+ . ' id="span_table_comment">&quot;'
+ . htmlspecialchars($comment)
+ . '&quot;</span>';
+ } // end if
+ }
+ }
+ }
+ $retval .= '<div class="clearfloat"></div>';
+ $retval .= '</div>';
+ return $retval;
+ }
+
+ /**
+ * Returns the table tabs as an array
+ *
+ * @return array Data for generating table tabs
+ */
+ private function _getTableTabs()
+ {
+ $db_is_information_schema = $GLOBALS['dbi']->isSystemSchema($this->_db);
+ $tbl_is_view = PMA_Table::isView($this->_db, $this->_table);
+ $is_superuser = $GLOBALS['dbi']->isSuperuser();
+
+ $tabs = array();
+
+ $tabs['browse']['icon'] = 'b_browse.png';
+ $tabs['browse']['text'] = __('Browse');
+ $tabs['browse']['link'] = 'sql.php';
+ $tabs['browse']['args']['pos'] = 0;
+
+ $tabs['structure']['icon'] = 'b_props.png';
+ $tabs['structure']['link'] = 'tbl_structure.php';
+ $tabs['structure']['text'] = __('Structure');
+
+ $tabs['sql']['icon'] = 'b_sql.png';
+ $tabs['sql']['link'] = 'tbl_sql.php';
+ $tabs['sql']['text'] = __('SQL');
+
+ $tabs['search']['icon'] = 'b_search.png';
+ $tabs['search']['text'] = __('Search');
+ $tabs['search']['link'] = 'tbl_select.php';
+ $tabs['search']['active'] = in_array(
+ basename($GLOBALS['PMA_PHP_SELF']),
+ array('tbl_select.php', 'tbl_zoom_select.php', 'tbl_find_replace.php')
+ );
+
+ if (! $db_is_information_schema) {
+ $tabs['insert']['icon'] = 'b_insrow.png';
+ $tabs['insert']['link'] = 'tbl_change.php';
+ $tabs['insert']['text'] = __('Insert');
+ }
+
+ $tabs['export']['icon'] = 'b_tblexport.png';
+ $tabs['export']['link'] = 'tbl_export.php';
+ $tabs['export']['args']['single_table'] = 'true';
+ $tabs['export']['text'] = __('Export');
+
+ /**
+ * Don't display "Import" for views and information_schema
+ */
+ if (! $tbl_is_view && ! $db_is_information_schema) {
+ $tabs['import']['icon'] = 'b_tblimport.png';
+ $tabs['import']['link'] = 'tbl_import.php';
+ $tabs['import']['text'] = __('Import');
+ }
+ if ($is_superuser && ! PMA_DRIZZLE && ! $db_is_information_schema) {
+ $tabs['privileges']['link'] = 'server_privileges.php';
+ $tabs['privileges']['args']['checkprivsdb'] = $this->_db;
+ $tabs['privileges']['args']['checkprivstable'] = $this->_table;
+ // stay on table view
+ $tabs['privileges']['args']['viewing_mode'] = 'table';
+ $tabs['privileges']['text'] = __('Privileges');
+ $tabs['privileges']['icon'] = 's_rights.png';
+ }
+ /**
+ * Don't display "Operations" for views and information_schema
+ */
+ if (! $tbl_is_view && ! $db_is_information_schema) {
+ $tabs['operation']['icon'] = 'b_tblops.png';
+ $tabs['operation']['link'] = 'tbl_operations.php';
+ $tabs['operation']['text'] = __('Operations');
+ }
+ if (PMA_Tracker::isActive()) {
+ $tabs['tracking']['icon'] = 'eye.png';
+ $tabs['tracking']['text'] = __('Tracking');
+ $tabs['tracking']['link'] = 'tbl_tracking.php';
+ }
+ if (! $db_is_information_schema
+ && ! PMA_DRIZZLE
+ && PMA_Util::currentUserHasPrivilege(
+ 'TRIGGER',
+ $this->_db,
+ $this->_table
+ )
+ && ! $tbl_is_view
+ ) {
+ $tabs['triggers']['link'] = 'tbl_triggers.php';
+ $tabs['triggers']['text'] = __('Triggers');
+ $tabs['triggers']['icon'] = 'b_triggers.png';
+ }
+
+ /**
+ * Views support a limited number of operations
+ */
+ if ($tbl_is_view && ! $db_is_information_schema) {
+ $tabs['operation']['icon'] = 'b_tblops.png';
+ $tabs['operation']['link'] = 'view_operations.php';
+ $tabs['operation']['text'] = __('Operations');
+ }
+
+ return $tabs;
+ }
+
+ /**
+ * Returns the db tabs as an array
+ *
+ * @return array Data for generating db tabs
+ */
+ private function _getDbTabs()
+ {
+ $db_is_information_schema = $GLOBALS['dbi']->isSystemSchema($this->_db);
+ $num_tables = count($GLOBALS['dbi']->getTables($this->_db));
+ $is_superuser = $GLOBALS['dbi']->isSuperuser();
+
+ /**
+ * Gets the relation settings
+ */
+ $cfgRelation = PMA_getRelationsParam();
+
+ $tabs = array();
+
+ $tabs['structure']['link'] = 'db_structure.php';
+ $tabs['structure']['text'] = __('Structure');
+ $tabs['structure']['icon'] = 'b_props.png';
+
+ $tabs['sql']['link'] = 'db_sql.php';
+ $tabs['sql']['text'] = __('SQL');
+ $tabs['sql']['icon'] = 'b_sql.png';
+
+ $tabs['search']['text'] = __('Search');
+ $tabs['search']['icon'] = 'b_search.png';
+ $tabs['search']['link'] = 'db_search.php';
+ if ($num_tables == 0) {
+ $tabs['search']['warning'] = __('Database seems to be empty!');
+ }
+
+ $tabs['qbe']['text'] = __('Query');
+ $tabs['qbe']['icon'] = 's_db.png';
+ $tabs['qbe']['link'] = 'db_qbe.php';
+ if ($num_tables == 0) {
+ $tabs['qbe']['warning'] = __('Database seems to be empty!');
+ }
+
+ $tabs['export']['text'] = __('Export');
+ $tabs['export']['icon'] = 'b_export.png';
+ $tabs['export']['link'] = 'db_export.php';
+ if ($num_tables == 0) {
+ $tabs['export']['warning'] = __('Database seems to be empty!');
+ }
+
+ if (! $db_is_information_schema) {
+ $tabs['import']['link'] = 'db_import.php';
+ $tabs['import']['text'] = __('Import');
+ $tabs['import']['icon'] = 'b_import.png';
+
+ $tabs['operation']['link'] = 'db_operations.php';
+ $tabs['operation']['text'] = __('Operations');
+ $tabs['operation']['icon'] = 'b_tblops.png';
+
+ if ($is_superuser && ! PMA_DRIZZLE) {
+ $tabs['privileges']['link'] = 'server_privileges.php';
+ $tabs['privileges']['args']['checkprivsdb'] = $this->_db;
+ // stay on database view
+ $tabs['privileges']['args']['viewing_mode'] = 'db';
+ $tabs['privileges']['text'] = __('Privileges');
+ $tabs['privileges']['icon'] = 's_rights.png';
+ }
+ if (! PMA_DRIZZLE) {
+ $tabs['routines']['link'] = 'db_routines.php';
+ $tabs['routines']['text'] = __('Routines');
+ $tabs['routines']['icon'] = 'b_routines.png';
+ }
+ if (PMA_MYSQL_INT_VERSION >= 50106
+ && ! PMA_DRIZZLE
+ && PMA_Util::currentUserHasPrivilege('EVENT', $this->_db)
+ ) {
+ $tabs['events']['link'] = 'db_events.php';
+ $tabs['events']['text'] = __('Events');
+ $tabs['events']['icon'] = 'b_events.png';
+ }
+ if (! PMA_DRIZZLE
+ && PMA_Util::currentUserHasPrivilege('TRIGGER', $this->_db)
+ ) {
+ $tabs['triggers']['link'] = 'db_triggers.php';
+ $tabs['triggers']['text'] = __('Triggers');
+ $tabs['triggers']['icon'] = 'b_triggers.png';
+ }
+ }
+
+ if (PMA_Tracker::isActive()) {
+ $tabs['tracking']['text'] = __('Tracking');
+ $tabs['tracking']['icon'] = 'eye.png';
+ $tabs['tracking']['link'] = 'db_tracking.php';
+ }
+
+ if (! $db_is_information_schema && $cfgRelation['designerwork']) {
+ $tabs['designer']['text'] = __('Designer');
+ $tabs['designer']['icon'] = 'b_relations.png';
+ $tabs['designer']['link'] = 'pmd_general.php';
+ }
+
+ return $tabs;
+ }
+
+ /**
+ * Returns the server tabs as an array
+ *
+ * @return array Data for generating server tabs
+ */
+ private function _getServerTabs()
+ {
+ $is_superuser = isset($GLOBALS['dbi']) && $GLOBALS['dbi']->isSuperuser();
+ $binary_logs = null;
+ $notDrizzle = ! defined('PMA_DRIZZLE')
+ || (defined('PMA_DRIZZLE') && ! PMA_DRIZZLE);
+ if (isset($GLOBALS['dbi']) && $notDrizzle) {
+ $binary_logs = $GLOBALS['dbi']->fetchResult(
+ 'SHOW MASTER LOGS',
+ 'Log_name',
+ null,
+ null,
+ PMA_DatabaseInterface::QUERY_STORE
+ );
+ }
+
+ $tabs = array();
+
+ $tabs['databases']['icon'] = 's_db.png';
+ $tabs['databases']['link'] = 'server_databases.php';
+ $tabs['databases']['text'] = __('Databases');
+
+ $tabs['sql']['icon'] = 'b_sql.png';
+ $tabs['sql']['link'] = 'server_sql.php';
+ $tabs['sql']['text'] = __('SQL');
+
+ $tabs['status']['icon'] = 's_status.png';
+ $tabs['status']['link'] = 'server_status.php';
+ $tabs['status']['text'] = __('Status');
+ $tabs['status']['active'] = in_array(
+ basename($GLOBALS['PMA_PHP_SELF']),
+ array(
+ 'server_status.php',
+ 'server_status_advisor.php',
+ 'server_status_monitor.php',
+ 'server_status_queries.php',
+ 'server_status_variables.php'
+ )
+ );
+
+ if ($is_superuser && ! PMA_DRIZZLE) {
+ $tabs['rights']['icon'] = 's_rights.png';
+ $tabs['rights']['link'] = 'server_privileges.php';
+ $tabs['rights']['text'] = __('Users');
+ $tabs['rights']['active'] = in_array(
+ basename($GLOBALS['PMA_PHP_SELF']),
+ array('server_privileges.php', 'server_user_groups.php')
+ );
+ $tabs['rights']['args']['viewing_mode'] = 'server';
+ }
+
+ $tabs['export']['icon'] = 'b_export.png';
+ $tabs['export']['link'] = 'server_export.php';
+ $tabs['export']['text'] = __('Export');
+
+ $tabs['import']['icon'] = 'b_import.png';
+ $tabs['import']['link'] = 'server_import.php';
+ $tabs['import']['text'] = __('Import');
+
+ $tabs['settings']['icon'] = 'b_tblops.png';
+ $tabs['settings']['link'] = 'prefs_manage.php';
+ $tabs['settings']['text'] = __('Settings');
+ $tabs['settings']['active'] = in_array(
+ basename($GLOBALS['PMA_PHP_SELF']),
+ array('prefs_forms.php', 'prefs_manage.php')
+ );
+
+ if (! empty($binary_logs)) {
+ $tabs['binlog']['icon'] = 's_tbl.png';
+ $tabs['binlog']['link'] = 'server_binlog.php';
+ $tabs['binlog']['text'] = __('Binary log');
+ }
+
+ if ($is_superuser && ! PMA_DRIZZLE) {
+ $tabs['replication']['icon'] = 's_replication.png';
+ $tabs['replication']['link'] = 'server_replication.php';
+ $tabs['replication']['text'] = __('Replication');
+ }
+
+ $tabs['vars']['icon'] = 's_vars.png';
+ $tabs['vars']['link'] = 'server_variables.php';
+ $tabs['vars']['text'] = __('Variables');
+
+ $tabs['charset']['icon'] = 's_asci.png';
+ $tabs['charset']['link'] = 'server_collations.php';
+ $tabs['charset']['text'] = __('Charsets');
+
+ if (defined('PMA_DRIZZLE') && PMA_DRIZZLE) {
+ $tabs['plugins']['icon'] = 'b_engine.png';
+ $tabs['plugins']['link'] = 'server_plugins.php';
+ $tabs['plugins']['text'] = __('Plugins');
+ } else {
+ $tabs['engine']['icon'] = 'b_engine.png';
+ $tabs['engine']['link'] = 'server_engines.php';
+ $tabs['engine']['text'] = __('Engines');
+ }
+ return $tabs;
+ }
+}
+
+?>
diff --git a/libraries/Message.class.php b/libraries/Message.class.php
new file mode 100644
index 0000000000..ddec29a299
--- /dev/null
+++ b/libraries/Message.class.php
@@ -0,0 +1,749 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Holds class PMA_Message
+ *
+ * @package PhpMyAdmin
+ */
+
+/**
+ * a single message
+ *
+ * simple usage examples:
+ * <code>
+ * // display simple error message 'Error'
+ * PMA_Message::error()->display();
+ *
+ * // get simple success message 'Success'
+ * $message = PMA_Message::success();
+ *
+ * // get special notice
+ * $message = PMA_Message::notice(__('This is a localized notice'));
+ * </code>
+ *
+ * more advanced usage example:
+ * <code>
+ * // create a localized success message
+ * $message = PMA_Message::success('strSomeLocaleMessage');
+ *
+ * // create another message, a hint, with a localized string which expects
+ * // two parameters: $strSomeTooltip = 'Read the %smanual%s'
+ * $hint = PMA_Message::notice('strSomeTooltip');
+ * // replace placeholders with the following params
+ * $hint->addParam('[doc@cfg_Example]');
+ * $hint->addParam('[/doc]');
+ * // add this hint as a tooltip
+ * $hint = showHint($hint);
+ *
+ * // add the retrieved tooltip reference to the original message
+ * $message->addMessage($hint);
+ *
+ * // create another message ...
+ * $more = PMA_Message::notice('strSomeMoreLocale');
+ * $more->addString('strSomeEvenMoreLocale', '<br />');
+ * $more->addParam('parameter for strSomeMoreLocale');
+ * $more->addParam('more parameter for strSomeMoreLocale');
+ *
+ * // and add it also to the original message
+ * $message->addMessage($more);
+ * // finally add another raw message
+ * $message->addMessage('some final words', ' - ');
+ *
+ * // display() will now print all messages in the same order as they are added
+ * $message->display();
+ * // strSomeLocaleMessage <sup>1</sup> strSomeMoreLocale<br />
+ * // strSomeEvenMoreLocale - some final words
+ * </code>
+ *
+ * @package PhpMyAdmin
+ */
+class PMA_Message
+{
+ const SUCCESS = 1; // 0001
+ const NOTICE = 2; // 0010
+ const ERROR = 8; // 1000
+
+ const SANITIZE_NONE = 0; // 0000 0000
+ const SANITIZE_STRING = 16; // 0001 0000
+ const SANITIZE_PARAMS = 32; // 0010 0000
+ const SANITIZE_BOOTH = 48; // 0011 0000
+
+ /**
+ * message levels
+ *
+ * @var array
+ */
+ static public $level = array (
+ PMA_Message::SUCCESS => 'success',
+ PMA_Message::NOTICE => 'notice',
+ PMA_Message::ERROR => 'error',
+ );
+
+ /**
+ * The message number
+ *
+ * @access protected
+ * @var integer
+ */
+ protected $number = PMA_Message::NOTICE;
+
+ /**
+ * The locale string identifier
+ *
+ * @access protected
+ * @var string
+ */
+ protected $string = '';
+
+ /**
+ * The formatted message
+ *
+ * @access protected
+ * @var string
+ */
+ protected $message = '';
+
+ /**
+ * Whether the message was already displayed
+ *
+ * @access protected
+ * @var boolean
+ */
+ protected $isDisplayed = false;
+
+ /**
+ * Unique id
+ *
+ * @access protected
+ * @var string
+ */
+ protected $hash = null;
+
+ /**
+ * holds parameters
+ *
+ * @access protected
+ * @var array
+ */
+ protected $params = array();
+
+ /**
+ * holds additional messages
+ *
+ * @access protected
+ * @var array
+ */
+ protected $addedMessages = array();
+
+ /**
+ * Constructor
+ *
+ * @param string $string The message to be displayed
+ * @param integer $number A numeric representation of the type of message
+ * @param array $params An array of parameters to use in the message
+ * @param integer $sanitize A flag to indicate what to sanitize, see
+ * constant definitions above
+ */
+ public function __construct($string = '', $number = PMA_Message::NOTICE,
+ $params = array(), $sanitize = PMA_Message::SANITIZE_NONE
+ ) {
+ $this->setString($string, $sanitize & PMA_Message::SANITIZE_STRING);
+ $this->setNumber($number);
+ $this->setParams($params, $sanitize & PMA_Message::SANITIZE_PARAMS);
+ }
+
+ /**
+ * magic method: return string representation for this object
+ *
+ * @return string
+ */
+ public function __toString()
+ {
+ return $this->getMessage();
+ }
+
+ /**
+ * get PMA_Message of type success
+ *
+ * shorthand for getting a simple success message
+ *
+ * @param string $string A localized string
+ * e.g. __('Your SQL query has been
+ * executed successfully')
+ *
+ * @return PMA_Message
+ * @static
+ */
+ static public function success($string = '')
+ {
+ if (empty($string)) {
+ $string = __('Your SQL query has been executed successfully');
+ }
+
+ return new PMA_Message($string, PMA_Message::SUCCESS);
+ }
+
+ /**
+ * get PMA_Message of type error
+ *
+ * shorthand for getting a simple error message
+ *
+ * @param string $string A localized string e.g. __('Error')
+ *
+ * @return PMA_Message
+ * @static
+ */
+ static public function error($string = '')
+ {
+ if (empty($string)) {
+ $string = __('Error');
+ }
+
+ return new PMA_Message($string, PMA_Message::ERROR);
+ }
+
+ /**
+ * get PMA_Message of type notice
+ *
+ * shorthand for getting a simple notice message
+ *
+ * @param string $string A localized string
+ * e.g. __('The additional features for working with
+ * linked tables have been deactivated. To find out
+ * why click %shere%s.')
+ *
+ * @return PMA_Message
+ * @static
+ */
+ static public function notice($string)
+ {
+ return new PMA_Message($string, PMA_Message::NOTICE);
+ }
+
+ /**
+ * get PMA_Message with customized content
+ *
+ * shorthand for getting a customized message
+ *
+ * @param string $message A localized string
+ * @param integer $type A numeric representation of the type of message
+ *
+ * @return PMA_Message
+ * @static
+ */
+ static public function raw($message, $type = PMA_Message::NOTICE)
+ {
+ $r = new PMA_Message('', $type);
+ $r->setMessage($message);
+ return $r;
+ }
+
+ /**
+ * get PMA_Message for number of affected rows
+ *
+ * shorthand for getting a customized message
+ *
+ * @param integer $rows Number of rows
+ *
+ * @return PMA_Message
+ * @static
+ */
+ static public function getMessageForAffectedRows($rows)
+ {
+ $message = PMA_Message::success(
+ _ngettext('%1$d row affected.', '%1$d rows affected.', $rows)
+ );
+ $message->addParam($rows);
+ return $message;
+ }
+
+ /**
+ * get PMA_Message for number of deleted rows
+ *
+ * shorthand for getting a customized message
+ *
+ * @param integer $rows Number of rows
+ *
+ * @return PMA_Message
+ * @static
+ */
+ static public function getMessageForDeletedRows($rows)
+ {
+ $message = PMA_Message::success(
+ _ngettext('%1$d row deleted.', '%1$d rows deleted.', $rows)
+ );
+ $message->addParam($rows);
+ return $message;
+ }
+
+ /**
+ * get PMA_Message for number of inserted rows
+ *
+ * shorthand for getting a customized message
+ *
+ * @param integer $rows Number of rows
+ *
+ * @return PMA_Message
+ * @static
+ */
+ static public function getMessageForInsertedRows($rows)
+ {
+ $message = PMA_Message::success(
+ _ngettext('%1$d row inserted.', '%1$d rows inserted.', $rows)
+ );
+ $message->addParam($rows);
+ return $message;
+ }
+
+ /**
+ * get PMA_Message of type error with custom content
+ *
+ * shorthand for getting a customized error message
+ *
+ * @param string $message A localized string
+ *
+ * @return PMA_Message
+ * @static
+ */
+ static public function rawError($message)
+ {
+ return PMA_Message::raw($message, PMA_Message::ERROR);
+ }
+
+ /**
+ * get PMA_Message of type notice with custom content
+ *
+ * shorthand for getting a customized notice message
+ *
+ * @param string $message A localized string
+ *
+ * @return PMA_Message
+ * @static
+ */
+ static public function rawNotice($message)
+ {
+ return PMA_Message::raw($message, PMA_Message::NOTICE);
+ }
+
+ /**
+ * get PMA_Message of type success with custom content
+ *
+ * shorthand for getting a customized success message
+ *
+ * @param string $message A localized string
+ *
+ * @return PMA_Message
+ * @static
+ */
+ static public function rawSuccess($message)
+ {
+ return PMA_Message::raw($message, PMA_Message::SUCCESS);
+ }
+
+ /**
+ * returns whether this message is a success message or not
+ * and optionaly makes this message a success message
+ *
+ * @param boolean $set Whether to make this message of SUCCESS type
+ *
+ * @return boolean whether this is a success message or not
+ */
+ public function isSuccess($set = false)
+ {
+ if ($set) {
+ $this->setNumber(PMA_Message::SUCCESS);
+ }
+
+ return $this->getNumber() === PMA_Message::SUCCESS;
+ }
+
+ /**
+ * returns whether this message is a notice message or not
+ * and optionally makes this message a notice message
+ *
+ * @param boolean $set Whether to make this message of NOTICE type
+ *
+ * @return boolean whether this is a notice message or not
+ */
+ public function isNotice($set = false)
+ {
+ if ($set) {
+ $this->setNumber(PMA_Message::NOTICE);
+ }
+
+ return $this->getNumber() === PMA_Message::NOTICE;
+ }
+
+ /**
+ * returns whether this message is an error message or not
+ * and optionally makes this message an error message
+ *
+ * @param boolean $set Whether to make this message of ERROR type
+ *
+ * @return boolean Whether this is an error message or not
+ */
+ public function isError($set = false)
+ {
+ if ($set) {
+ $this->setNumber(PMA_Message::ERROR);
+ }
+
+ return $this->getNumber() === PMA_Message::ERROR;
+ }
+
+ /**
+ * set raw message (overrides string)
+ *
+ * @param string $message A localized string
+ * @param boolean $sanitize Whether to sanitize $message or not
+ *
+ * @return void
+ */
+ public function setMessage($message, $sanitize = false)
+ {
+ if ($sanitize) {
+ $message = PMA_Message::sanitize($message);
+ }
+ $this->message = $message;
+ }
+
+ /**
+ * set string (does not take effect if raw message is set)
+ *
+ * @param string $string string to set
+ * @param boolean $sanitize whether to sanitize $string or not
+ *
+ * @return void
+ */
+ public function setString($string, $sanitize = true)
+ {
+ if ($sanitize) {
+ $string = PMA_Message::sanitize($string);
+ }
+ $this->string = $string;
+ }
+
+ /**
+ * set message type number
+ *
+ * @param integer $number message type number to set
+ *
+ * @return void
+ */
+ public function setNumber($number)
+ {
+ $this->number = $number;
+ }
+
+ /**
+ * add parameter, usually in conjunction with strings
+ *
+ * usage
+ * <code>
+ * $message->addParam('strLocale', false);
+ * $message->addParam('[em]some string[/em]');
+ * $message->addParam('<img src="img" />', false);
+ * </code>
+ *
+ * @param mixed $param parameter to add
+ * @param boolean $raw whether parameter should be passed as is
+ * without html escaping
+ *
+ * @return void
+ */
+ public function addParam($param, $raw = true)
+ {
+ if ($param instanceof PMA_Message) {
+ $this->params[] = $param;
+ } elseif ($raw) {
+ $this->params[] = htmlspecialchars($param);
+ } else {
+ $this->params[] = PMA_Message::notice($param);
+ }
+ }
+
+ /**
+ * add another string to be concatenated on displaying
+ *
+ * @param string $string to be added
+ * @param string $separator to use between this and previous string/message
+ *
+ * @return void
+ */
+ public function addString($string, $separator = ' ')
+ {
+ $this->addedMessages[] = $separator;
+ $this->addedMessages[] = PMA_Message::notice($string);
+ }
+
+ /**
+ * add a bunch of messages at once
+ *
+ * @param array $messages to be added
+ * @param string $separator to use between this and previous string/message
+ *
+ * @return void
+ */
+ public function addMessages($messages, $separator = ' ')
+ {
+ foreach ($messages as $message) {
+ $this->addMessage($message, $separator);
+ }
+ }
+
+ /**
+ * add another raw message to be concatenated on displaying
+ *
+ * @param mixed $message to be added
+ * @param string $separator to use between this and previous string/message
+ *
+ * @return void
+ */
+ public function addMessage($message, $separator = ' ')
+ {
+ if (strlen($separator)) {
+ $this->addedMessages[] = $separator;
+ }
+
+ if ($message instanceof PMA_Message) {
+ $this->addedMessages[] = $message;
+ } else {
+ $this->addedMessages[] = PMA_Message::rawNotice($message);
+ }
+ }
+
+ /**
+ * set all params at once, usually used in conjunction with string
+ *
+ * @param array $params parameters to set
+ * @param boolean $sanitize whether to sanitize params
+ *
+ * @return void
+ */
+ public function setParams($params, $sanitize = false)
+ {
+ if ($sanitize) {
+ $params = PMA_Message::sanitize($params);
+ }
+ $this->params = $params;
+ }
+
+ /**
+ * return all parameters
+ *
+ * @return array
+ */
+ public function getParams()
+ {
+ return $this->params;
+ }
+
+ /**
+ * return all added messages
+ *
+ * @return array
+ */
+ public function getAddedMessages()
+ {
+ return $this->addedMessages;
+ }
+
+ /**
+ * Sanitizes $message
+ *
+ * @param mixed $message the message(s)
+ *
+ * @return mixed the sanitized message(s)
+ * @access public
+ * @static
+ */
+ static public function sanitize($message)
+ {
+ if (is_array($message)) {
+ foreach ($message as $key => $val) {
+ $message[$key] = PMA_Message::sanitize($val);
+ }
+
+ return $message;
+ }
+
+ return htmlspecialchars($message);
+ }
+
+ /**
+ * decode $message, taking into account our special codes
+ * for formatting
+ *
+ * @param string $message the message
+ *
+ * @return string the decoded message
+ * @access public
+ * @static
+ */
+ static public function decodeBB($message)
+ {
+ return PMA_sanitize($message, false, true);
+ }
+
+ /**
+ * wrapper for sprintf()
+ *
+ * @return string formatted
+ */
+ static public function format()
+ {
+ $params = func_get_args();
+ if (isset($params[1]) && is_array($params[1])) {
+ array_unshift($params[1], $params[0]);
+ $params = $params[1];
+ }
+
+ return call_user_func_array('sprintf', $params);
+ }
+
+ /**
+ * returns unique PMA_Message::$hash, if not exists it will be created
+ *
+ * @return string PMA_Message::$hash
+ */
+ public function getHash()
+ {
+ if (null === $this->hash) {
+ $this->hash = md5(
+ $this->getNumber() .
+ $this->string .
+ $this->message
+ );
+ }
+
+ return $this->hash;
+ }
+
+ /**
+ * returns compiled message
+ *
+ * @return string complete message
+ */
+ public function getMessage()
+ {
+ $message = $this->message;
+
+ if (0 === strlen($message)) {
+ $string = $this->getString();
+ if (isset($GLOBALS[$string])) {
+ $message = $GLOBALS[$string];
+ } elseif (0 === strlen($string)) {
+ $message = '';
+ } else {
+ $message = $string;
+ }
+ }
+
+ if ($this->isDisplayed()) {
+ $message = $this->getMessageWithIcon($message);
+ }
+ if (count($this->getParams()) > 0) {
+ $message = PMA_Message::format($message, $this->getParams());
+ }
+
+ $message = PMA_Message::decodeBB($message);
+
+ foreach ($this->getAddedMessages() as $add_message) {
+ $message .= $add_message;
+ }
+
+ return $message;
+ }
+
+ /**
+ * returns PMA_Message::$string
+ *
+ * @return string PMA_Message::$string
+ */
+ public function getString()
+ {
+ return $this->string;
+ }
+
+ /**
+ * returns PMA_Message::$number
+ *
+ * @return integer PMA_Message::$number
+ */
+ public function getNumber()
+ {
+ return $this->number;
+ }
+
+ /**
+ * returns level of message
+ *
+ * @return string level of message
+ */
+ public function getLevel()
+ {
+ return PMA_Message::$level[$this->getNumber()];
+ }
+
+ /**
+ * Displays the message in HTML
+ *
+ * @return void
+ */
+ public function display()
+ {
+ echo $this->getDisplay();
+ $this->isDisplayed(true);
+ }
+
+ /**
+ * returns HTML code for displaying this message
+ *
+ * @return string whole message box
+ */
+ public function getDisplay()
+ {
+ $this->isDisplayed(true);
+ return '<div class="' . $this->getLevel() . '">'
+ . $this->getMessage() . '</div>';
+ }
+
+ /**
+ * sets and returns whether the message was displayed or not
+ *
+ * @param boolean $isDisplayed whether to set displayed flag
+ *
+ * @return boolean PMA_Message::$isDisplayed
+ */
+ public function isDisplayed($isDisplayed = false)
+ {
+ if ($isDisplayed) {
+ $this->isDisplayed = true;
+ }
+
+ return $this->isDisplayed;
+ }
+
+ /**
+ * Returns the message with corresponding image icon
+ *
+ * @param string $message the message(s)
+ *
+ * @return string message with icon
+ */
+ public function getMessageWithIcon($message)
+ {
+ $image = '';
+ if ('error' == $this->getLevel()) {
+ $image = 's_error.png';
+ } elseif ('success' == $this->getLevel()) {
+ $image = 's_success.png';
+ } else {
+ $image = 's_notice.png';
+ }
+ $message = PMA_Message::notice(PMA_Util::getImage($image)) . " " . $message;
+ return $message;
+
+ }
+}
+?>
diff --git a/libraries/OutputBuffering.class.php b/libraries/OutputBuffering.class.php
new file mode 100644
index 0000000000..ff1f0b7ad5
--- /dev/null
+++ b/libraries/OutputBuffering.class.php
@@ -0,0 +1,142 @@
+<?php /* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Output buffering wrapper
+ *
+ * @package PhpMyAdmin
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ * Output buffering wrapper class
+ *
+ * @package PhpMyAdmin
+ */
+class PMA_OutputBuffering
+{
+ private static $_instance;
+ private $_mode;
+ private $_content;
+ private $_on;
+
+ /**
+ * Initializes class
+ */
+ private function __construct()
+ {
+ $this->_mode = $this->_getMode();
+ $this->_on = false;
+ }
+
+ /**
+ * This function could be used eventually to support more modes.
+ *
+ * @return integer the output buffer mode
+ */
+ private function _getMode()
+ {
+ $mode = 0;
+ if ($GLOBALS['cfg']['OBGzip'] && function_exists('ob_start')) {
+ if (ini_get('output_handler') == 'ob_gzhandler') {
+ // If a user sets the output_handler in php.ini to ob_gzhandler, then
+ // any right frame file in phpMyAdmin will not be handled properly by
+ // the browser. My fix was to check the ini file within the
+ // PMA_outBufferModeGet() function.
+ $mode = 0;
+ } elseif (function_exists('ob_get_level') && ob_get_level() > 0) {
+ // If output buffering is enabled in php.ini it's not possible to
+ // add the ob_gzhandler without a warning message from php 4.3.0.
+ // Being better safe than sorry, check for any existing output
+ // handler instead of just checking the 'output_buffering' setting.
+ $mode = 0;
+ } else {
+ $mode = 1;
+ }
+ }
+ // Zero (0) is no mode or in other words output buffering is OFF.
+ // Follow 2^0, 2^1, 2^2, 2^3 type values for the modes.
+ // Usefull if we ever decide to combine modes. Then a bitmask field of
+ // the sum of all modes will be the natural choice.
+ return $mode;
+ }
+
+ /**
+ * Returns the singleton PMA_OutputBuffering object
+ *
+ * @return PMA_OutputBuffering object
+ */
+ public static function getInstance()
+ {
+ if (empty(self::$_instance)) {
+ self::$_instance = new PMA_OutputBuffering();
+ }
+ return self::$_instance;
+ }
+
+ /**
+ * This function will need to run at the top of all pages if output
+ * output buffering is turned on. It also needs to be passed $mode from
+ * the PMA_outBufferModeGet() function or it will be useless.
+ *
+ * @return void
+ */
+ public function start()
+ {
+ if (! $this->_on) {
+ if ($this->_mode) {
+ ob_start('ob_gzhandler');
+ }
+ ob_start();
+ if (! defined('TESTSUITE')) {
+ header('X-ob_mode: ' . $this->_mode);
+ }
+ register_shutdown_function('PMA_OutputBuffering::stop');
+ $this->_on = true;
+ }
+ }
+
+ /**
+ * This function will need to run at the bottom of all pages if output
+ * buffering is turned on. It also needs to be passed $mode from the
+ * PMA_outBufferModeGet() function or it will be useless.
+ *
+ * @return void
+ */
+ public static function stop()
+ {
+ $buffer = PMA_OutputBuffering::getInstance();
+ if ($buffer->_on) {
+ $buffer->_on = false;
+ $buffer->_content = ob_get_contents();
+ ob_end_clean();
+ }
+ PMA_Response::response();
+ }
+
+ /**
+ * Gets buffer content
+ *
+ * @return string buffer content
+ */
+ public function getContents()
+ {
+ return $this->_content;
+ }
+
+ /**
+ * Flushes output buffer
+ *
+ * @return void
+ */
+ public function flush()
+ {
+ if (ob_get_status() && $this->_mode) {
+ ob_flush();
+ } else {
+ flush();
+ }
+ }
+}
+
+?>
diff --git a/libraries/PDF.class.php b/libraries/PDF.class.php
new file mode 100644
index 0000000000..026a87f707
--- /dev/null
+++ b/libraries/PDF.class.php
@@ -0,0 +1,145 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * TCPDF wrapper class.
+ *
+ * @package PhpMyAdmin
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+require_once TCPDF_INC;
+
+/**
+ * PDF font to use.
+ */
+define('PMA_PDF_FONT', 'DejaVuSans');
+
+/**
+ * PDF export base class providing basic configuration.
+ *
+ * @package PhpMyAdmin
+ */
+class PMA_PDF extends TCPDF
+{
+ var $footerset;
+ var $Alias = array();
+
+ /**
+ * Constructs PDF and configures standard parameters.
+ *
+ * @param string $orientation page orientation
+ * @param string $unit unit
+ * @param mixed $format the format used for pages
+ * @param boolean $unicode true means that the input text is unicode
+ * @param string $encoding charset encoding; default is UTF-8.
+ * @param boolean $diskcache if true reduce the RAM memory usage by caching
+ * temporary data on filesystem (slower).
+ * @param boolean $pdfa If TRUE set the document to PDF/A mode.
+ *
+ * @access public
+ */
+ public function __construct($orientation = 'P', $unit = 'mm', $format = 'A4',
+ $unicode = true, $encoding = 'UTF-8', $diskcache = false, $pdfa=false
+ ) {
+ parent::__construct(
+ $orientation, $unit, $format, $unicode,
+ $encoding, $diskcache, $pdfa
+ );
+ $this->SetAuthor('phpMyAdmin ' . PMA_VERSION);
+ $this->AddFont('DejaVuSans', '', 'dejavusans.php');
+ $this->AddFont('DejaVuSans', 'B', 'dejavusansb.php');
+ $this->SetFont(PMA_PDF_FONT, '', 14);
+ $this->setFooterFont(array(PMA_PDF_FONT, '', 14));
+ }
+
+ /**
+ * This function must be named "Footer" to work with the TCPDF library
+ *
+ * @return void
+ */
+ function Footer()
+ {
+ // Check if footer for this page already exists
+ if (!isset($this->footerset[$this->page])) {
+ $this->SetY(-15);
+ $this->SetFont(PMA_PDF_FONT, '', 14);
+ $this->Cell(
+ 0, 6,
+ __('Page number:') . ' '
+ . $this->getAliasNumPage() . '/' . $this->getAliasNbPages(),
+ 'T', 0, 'C'
+ );
+ $this->Cell(0, 6, PMA_Util::localisedDate(), 0, 1, 'R');
+ $this->SetY(20);
+
+ // set footerset
+ $this->footerset[$this->page] = 1;
+ }
+ }
+
+ /**
+ * Function to set alias which will be expanded on page rendering.
+ *
+ * @param string $name name of the alias
+ * @param string $value value of the alias
+ *
+ * @return void
+ */
+ function SetAlias($name, $value)
+ {
+ $name = TCPDF_FONTS::UTF8ToUTF16BE(
+ $name, false, true, $this->CurrentFont
+ );
+ $this->Alias[$name] = TCPDF_FONTS::UTF8ToUTF16BE(
+ $value, false, true, $this->CurrentFont
+ );
+ }
+
+ /**
+ * Improved with alias expading.
+ *
+ * @return void
+ */
+ function _putpages()
+ {
+ if (count($this->Alias) > 0) {
+ $nb = count($this->pages);
+ for ($n = 1;$n <= $nb;$n++) {
+ $this->pages[$n] = strtr($this->pages[$n], $this->Alias);
+ }
+ }
+ parent::_putpages();
+ }
+
+ /**
+ * Displays an error message
+ *
+ * @param string $error_message the error mesage
+ *
+ * @return void
+ */
+ function Error($error_message = '')
+ {
+ PMA_Message::error(
+ __('Error while creating PDF:') . ' ' . $error_message
+ )->display();
+ exit;
+ }
+
+ /**
+ * Sends file as a download to user.
+ *
+ * @param string $filename file name
+ *
+ * @return void
+ */
+ function Download($filename)
+ {
+ $pdfData = $this->getPDFData();
+ PMA_Response::getInstance()->disable();
+ PMA_downloadHeader($filename, 'application/pdf', strlen($pdfData));
+ echo $pdfData;
+ }
+}
diff --git a/libraries/PMA.php b/libraries/PMA.php
new file mode 100644
index 0000000000..021917d374
--- /dev/null
+++ b/libraries/PMA.php
@@ -0,0 +1,110 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * phpMyAdmin main Controller
+ *
+ * @package PhpMyAdmin
+ *
+ */
+
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ * Database listing.
+ */
+require_once './libraries/List_Database.class.php';
+
+/**
+ * phpMyAdmin main Controller
+ *
+ * @package PhpMyAdmin
+ */
+class PMA
+{
+ /**
+ * Holds database list
+ *
+ * @var PMA_List_Database
+ */
+ protected $databases = null;
+
+ /**
+ * DBMS user link
+ *
+ * @var resource
+ */
+ protected $userlink = null;
+
+ /**
+ * DBMS control link
+ *
+ * @var resource
+ */
+ protected $controllink = null;
+
+ /**
+ * magic access to protected/inaccessible members/properties
+ *
+ * @param string $param parameter name
+ *
+ * @return mixed
+ * @see http://php.net/language.oop5.overloading
+ */
+ public function __get($param)
+ {
+ switch ($param) {
+ case 'databases' :
+ return $this->getDatabaseList();
+ break;
+ case 'userlink' :
+ return $this->userlink;
+ break;
+ case 'controllink' :
+ return $this->controllink;
+ break;
+ }
+
+ return null;
+ }
+
+ /**
+ * magic access to protected/inaccessible members/properties
+ *
+ * @param string $param parameter name
+ * @param mixed $value value to set
+ *
+ * @return void
+ * @see http://php.net/language.oop5.overloading
+ */
+ public function __set($param, $value)
+ {
+ switch ($param) {
+ case 'userlink' :
+ $this->userlink = $value;
+ break;
+ case 'controllink' :
+ $this->controllink = $value;
+ break;
+ }
+ }
+
+ /**
+ * Accessor to PMA::$databases
+ *
+ * @return PMA_List_Databases
+ */
+ public function getDatabaseList()
+ {
+ if (null === $this->databases) {
+ $this->databases = new PMA_List_Database(
+ $this->userlink,
+ $this->controllink
+ );
+ }
+
+ return $this->databases;
+ }
+}
+?>
diff --git a/libraries/Partition.class.php b/libraries/Partition.class.php
new file mode 100644
index 0000000000..512588588c
--- /dev/null
+++ b/libraries/Partition.class.php
@@ -0,0 +1,79 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Library for extracting information about the partitions
+ *
+ * @package PhpMyAdmin
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ * base Partition Class
+ *
+ * @package PhpMyAdmin
+ */
+class PMA_Partition
+{
+ /**
+ * returns array of partition names for a specific db/table
+ *
+ * @param string $db database name
+ * @param string $table table name
+ *
+ * @access public
+ * @return array of partition names
+ */
+ static public function getPartitionNames($db, $table)
+ {
+ if (PMA_Partition::havePartitioning()) {
+ return $GLOBALS['dbi']->fetchResult(
+ "SELECT `PARTITION_NAME` FROM `information_schema`.`PARTITIONS`"
+ . " WHERE `TABLE_SCHEMA` = '" . $db
+ . "' AND `TABLE_NAME` = '" . $table . "'"
+ );
+ } else {
+ return array();
+ }
+ }
+
+ /**
+ * checks if MySQL server supports partitioning
+ *
+ * @static
+ * @staticvar boolean $have_partitioning
+ * @staticvar boolean $already_checked
+ * @access public
+ * @return boolean
+ */
+ static public function havePartitioning()
+ {
+ static $have_partitioning = false;
+ static $already_checked = false;
+
+ if (! $already_checked) {
+ if (PMA_MYSQL_INT_VERSION >= 50100) {
+ if (PMA_MYSQL_INT_VERSION < 50600) {
+ if ($GLOBALS['dbi']->fetchValue(
+ "SHOW VARIABLES LIKE 'have_partitioning';"
+ )) {
+ $have_partitioning = true;
+ }
+ } else {
+ // see http://dev.mysql.com/doc/refman/5.6/en/partitioning.html
+ $plugins = $GLOBALS['dbi']->fetchResult("SHOW PLUGINS");
+ foreach ($plugins as $value) {
+ if ($value['Name'] == 'partition') {
+ $have_partitioning = true;
+ break;
+ }
+ }
+ }
+ $already_checked = true;
+ }
+ }
+ return $have_partitioning;
+ }
+}
+?>
diff --git a/libraries/RecentTable.class.php b/libraries/RecentTable.class.php
new file mode 100644
index 0000000000..65b798fd98
--- /dev/null
+++ b/libraries/RecentTable.class.php
@@ -0,0 +1,220 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Recent table list handling
+ *
+ * @package PhpMyAdmin
+ */
+
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+require_once './libraries/Message.class.php';
+
+/**
+ * Handles the recently used tables.
+ *
+ * @TODO Change the release version in table pma_recent
+ * (#recent in documentation)
+ *
+ * @package PhpMyAdmin
+ */
+class PMA_RecentTable
+{
+ /**
+ * Defines the internal PMA table which contains recent tables.
+ *
+ * @access private
+ * @var string
+ */
+ private $_pmaTable;
+
+ /**
+ * Reference to session variable containing recently used tables.
+ *
+ * @access public
+ * @var array
+ */
+ public $tables;
+
+ /**
+ * PMA_RecentTable instance.
+ *
+ * @var PMA_RecentTable
+ */
+ private static $_instance;
+
+ /**
+ * Creates a new instance of PMA_RecentTable
+ */
+ public function __construct()
+ {
+ if (strlen($GLOBALS['cfg']['Server']['pmadb'])
+ && strlen($GLOBALS['cfg']['Server']['recent'])
+ ) {
+ $this->_pmaTable
+ = PMA_Util::backquote($GLOBALS['cfg']['Server']['pmadb']) . "."
+ . PMA_Util::backquote($GLOBALS['cfg']['Server']['recent']);
+ }
+ $server_id = $GLOBALS['server'];
+ if (! isset($_SESSION['tmpval']['recent_tables'][$server_id])) {
+ $_SESSION['tmpval']['recent_tables'][$server_id]
+ = isset($this->_pmaTable) ? $this->getFromDb() : array();
+ }
+ $this->tables =& $_SESSION['tmpval']['recent_tables'][$server_id];
+ }
+
+ /**
+ * Returns class instance.
+ *
+ * @return PMA_RecentTable
+ */
+ public static function getInstance()
+ {
+ if (is_null(self::$_instance)) {
+ self::$_instance = new PMA_RecentTable();
+ }
+ return self::$_instance;
+ }
+
+ /**
+ * Returns recently used tables from phpMyAdmin database.
+ *
+ * @return array
+ */
+ public function getFromDb()
+ {
+ // Read from phpMyAdmin database, if recent tables is not in session
+ $sql_query
+ = " SELECT `tables` FROM " . $this->_pmaTable .
+ " WHERE `username` = '" . $GLOBALS['cfg']['Server']['user'] . "'";
+
+ $return = array();
+ $result = PMA_queryAsControlUser($sql_query, false);
+ if ($result) {
+ $row = $GLOBALS['dbi']->fetchArray($result);
+ if (isset($row[0])) {
+ $return = json_decode($row[0], true);
+ }
+ }
+ return $return;
+ }
+
+ /**
+ * Save recent tables into phpMyAdmin database.
+ *
+ * @return true|PMA_Message
+ */
+ public function saveToDb()
+ {
+ $username = $GLOBALS['cfg']['Server']['user'];
+ $sql_query
+ = " REPLACE INTO " . $this->_pmaTable . " (`username`, `tables`)" .
+ " VALUES ('" . $username . "', '"
+ . PMA_Util::sqlAddSlashes(
+ json_encode($this->tables)
+ ) . "')";
+
+ $success = $GLOBALS['dbi']->tryQuery($sql_query, $GLOBALS['controllink']);
+
+ if (! $success) {
+ $message = PMA_Message::error(__('Could not save recent table'));
+ $message->addMessage('<br /><br />');
+ $message->addMessage(
+ PMA_Message::rawError(
+ $GLOBALS['dbi']->getError($GLOBALS['controllink'])
+ )
+ );
+ return $message;
+ }
+ return true;
+ }
+
+ /**
+ * Trim recent table according to the NumRecentTables configuration.
+ *
+ * @return boolean True if trimming occurred
+ */
+ public function trim()
+ {
+ $max = max($GLOBALS['cfg']['NumRecentTables'], 0);
+ $trimming_occurred = count($this->tables) > $max;
+ while (count($this->tables) > $max) {
+ array_pop($this->tables);
+ }
+ return $trimming_occurred;
+ }
+
+ /**
+ * Return options for HTML select.
+ *
+ * @return string
+ */
+ public function getHtmlSelectOption()
+ {
+ // trim and save, in case where the configuration is changed
+ if ($this->trim() && isset($this->_pmaTable)) {
+ $this->saveToDb();
+ }
+
+ $html = '<option value="">(' . __('Recent tables') . ') ...</option>';
+ if (count($this->tables)) {
+ foreach ($this->tables as $table) {
+ $html .= '<option value="'
+ . htmlspecialchars(json_encode($table)) . '">'
+ . htmlspecialchars(
+ '`' . $table['db'] . '`.`' . $table['table'] . '`'
+ )
+ . '</option>';
+ }
+ } else {
+ $html .= '<option value="">'
+ . __('There are no recent tables')
+ . '</option>';
+ }
+ return $html;
+ }
+
+ /**
+ * Return HTML select.
+ *
+ * @return string
+ */
+ public function getHtmlSelect()
+ {
+ $html = '<select name="selected_recent_table" id="recentTable">';
+ $html .= $this->getHtmlSelectOption();
+ $html .= '</select>';
+
+ return $html;
+ }
+
+ /**
+ * Add recently used tables.
+ *
+ * @param string $db database name where the table is located
+ * @param string $table table name
+ *
+ * @return true|PMA_Message True if success, PMA_Message if not
+ */
+ public function add($db, $table)
+ {
+ $table_arr = array();
+ $table_arr['db'] = $db;
+ $table_arr['table'] = $table;
+
+ // add only if this is new table
+ if (! isset($this->tables[0]) || $this->tables[0] != $table_arr) {
+ array_unshift($this->tables, $table_arr);
+ $this->tables = array_merge(array_unique($this->tables, SORT_REGULAR));
+ $this->trim();
+ if (isset($this->_pmaTable)) {
+ return $this->saveToDb();
+ }
+ }
+ return true;
+ }
+
+}
+?>
diff --git a/libraries/Response.class.php b/libraries/Response.class.php
new file mode 100644
index 0000000000..10858d2dee
--- /dev/null
+++ b/libraries/Response.class.php
@@ -0,0 +1,380 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Manages the rendering of pages in PMA
+ *
+ * @package PhpMyAdmin
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+require_once 'libraries/OutputBuffering.class.php';
+require_once 'libraries/Header.class.php';
+require_once 'libraries/Footer.class.php';
+
+/**
+ * Singleton class used to manage the rendering of pages in PMA
+ *
+ * @package PhpMyAdmin
+ */
+class PMA_Response
+{
+ /**
+ * PMA_Response instance
+ *
+ * @access private
+ * @static
+ * @var object
+ */
+ private static $_instance;
+ /**
+ * PMA_Header instance
+ *
+ * @access private
+ * @var object
+ */
+ private $_header;
+ /**
+ * HTML data to be used in the response
+ *
+ * @access private
+ * @var string
+ */
+ private $_HTML;
+ /**
+ * An array of JSON key-value pairs
+ * to be sent back for ajax requests
+ *
+ * @access private
+ * @var array
+ */
+ private $_JSON;
+ /**
+ * PMA_Footer instance
+ *
+ * @access private
+ * @var object
+ */
+ private $_footer;
+ /**
+ * Whether we are servicing an ajax request.
+ * We can't simply use $GLOBALS['is_ajax_request']
+ * here since it may have not been initialised yet.
+ *
+ * @access private
+ * @var bool
+ */
+ private $_isAjax;
+ /**
+ * Whether we are servicing an ajax request for a page
+ * that was fired using the generic page handler in JS.
+ *
+ * @access private
+ * @var bool
+ */
+ private $_isAjaxPage;
+ /**
+ * Whether there were any errors druing the processing of the request
+ * Only used for ajax responses
+ *
+ * @access private
+ * @var bool
+ */
+ private $_isSuccess;
+ /**
+ * Workaround for PHP bug
+ *
+ * @access private
+ * @var bool
+ */
+ private $_CWD;
+
+ /**
+ * Creates a new class instance
+ */
+ private function __construct()
+ {
+ if (! defined('TESTSUITE')) {
+ $buffer = PMA_OutputBuffering::getInstance();
+ $buffer->start();
+ }
+ $this->_header = new PMA_Header();
+ $this->_HTML = '';
+ $this->_JSON = array();
+ $this->_footer = new PMA_Footer();
+
+ $this->_isSuccess = true;
+ $this->_isAjax = false;
+ $this->_isAjaxPage = false;
+ if (isset($_REQUEST['ajax_request']) && $_REQUEST['ajax_request'] == true) {
+ $this->_isAjax = true;
+ }
+ if (isset($_REQUEST['ajax_page_request'])
+ && $_REQUEST['ajax_page_request'] == true
+ ) {
+ $this->_isAjaxPage = true;
+ }
+ $this->_header->setAjax($this->_isAjax);
+ $this->_footer->setAjax($this->_isAjax);
+ $this->_CWD = getcwd();
+ }
+
+ /**
+ * Returns the singleton PMA_Response object
+ *
+ * @return PMA_Response object
+ */
+ public static function getInstance()
+ {
+ if (empty(self::$_instance)) {
+ self::$_instance = new PMA_Response();
+ }
+ return self::$_instance;
+ }
+
+ /**
+ * Set the status of an ajax response,
+ * whether it is a success or an error
+ *
+ * @param bool $state Whether the request was successfully processed
+ *
+ * @return void
+ */
+ public function isSuccess($state)
+ {
+ $this->_isSuccess = ($state == true);
+ }
+
+ /**
+ * Returns true or false depending on whether
+ * we are servicing an ajax request
+ *
+ * @return bool
+ */
+ public function isAjax()
+ {
+ return $this->_isAjax;
+ }
+
+ /**
+ * Returns the path to the current working directory
+ * Necessary to work around a PHP bug where the CWD is
+ * reset after the initial script exits
+ *
+ * @return string
+ */
+ public function getCWD()
+ {
+ return $this->_CWD;
+ }
+
+ /**
+ * Disables the rendering of the header
+ * and the footer in responses
+ *
+ * @return void
+ */
+ public function disable()
+ {
+ $this->_header->disable();
+ $this->_footer->disable();
+ }
+
+ /**
+ * Returns a PMA_Header object
+ *
+ * @return PMA_Header
+ */
+ public function getHeader()
+ {
+ return $this->_header;
+ }
+
+ /**
+ * Returns a PMA_Footer object
+ *
+ * @return PMA_Footer
+ */
+ public function getFooter()
+ {
+ return $this->_footer;
+ }
+
+ /**
+ * Add HTML code to the response
+ *
+ * @param string $content A string to be appended to
+ * the current output buffer
+ *
+ * @return void
+ */
+ public function addHTML($content)
+ {
+ if (is_array($content)) {
+ foreach ($content as $msg) {
+ $this->addHTML($msg);
+ }
+ } elseif ($content instanceof PMA_Message) {
+ $this->_HTML .= $content->getDisplay();
+ } else {
+ $this->_HTML .= $content;
+ }
+ }
+
+ /**
+ * Add JSON code to the response
+ *
+ * @param mixed $json Either a key (string) or an
+ * array or key-value pairs
+ * @param mixed $value Null, if passing an array in $json otherwise
+ * it's a string value to the key
+ *
+ * @return void
+ */
+ public function addJSON($json, $value = null)
+ {
+ if (is_array($json)) {
+ foreach ($json as $key => $value) {
+ $this->addJSON($key, $value);
+ }
+ } else {
+ if ($value instanceof PMA_Message) {
+ $this->_JSON[$json] = $value->getDisplay();
+ } else {
+ $this->_JSON[$json] = $value;
+ }
+ }
+
+ }
+
+ /**
+ * Renders the HTML response text
+ *
+ * @return string
+ */
+ private function _getDisplay()
+ {
+ // The header may contain nothing at all,
+ // if it's content was already rendered
+ // and, in this case, the header will be
+ // in the content part of the request
+ $retval = $this->_header->getDisplay();
+ $retval .= $this->_HTML;
+ $retval .= $this->_footer->getDisplay();
+ return $retval;
+ }
+
+ /**
+ * Sends an HTML response to the browser
+ *
+ * @return void
+ */
+ private function _htmlResponse()
+ {
+ echo $this->_getDisplay();
+ }
+
+ /**
+ * Sends a JSON response to the browser
+ *
+ * @return void
+ */
+ private function _ajaxResponse()
+ {
+ if (! isset($this->_JSON['message'])) {
+ $this->_JSON['message'] = $this->_getDisplay();
+ } else if ($this->_JSON['message'] instanceof PMA_Message) {
+ $this->_JSON['message'] = $this->_JSON['message']->getDisplay();
+ }
+
+ if ($this->_isSuccess) {
+ $this->_JSON['success'] = true;
+ } else {
+ $this->_JSON['success'] = false;
+ $this->_JSON['error'] = $this->_JSON['message'];
+ unset($this->_JSON['message']);
+ }
+
+ if ($this->_isAjaxPage && $this->_isSuccess) {
+ $this->addJSON('_title', $this->getHeader()->getTitleTag());
+
+ $menuHash = $this->getHeader()->getMenu()->getHash();
+ $this->addJSON('_menuHash', $menuHash);
+ $hashes = array();
+ if (isset($_REQUEST['menuHashes'])) {
+ $hashes = explode('-', $_REQUEST['menuHashes']);
+ }
+ if (! in_array($menuHash, $hashes)) {
+ $this->addJSON('_menu', $this->getHeader()->getMenu()->getDisplay());
+ }
+
+ $this->addJSON('_scripts', $this->getHeader()->getScripts()->getFiles());
+ $this->addJSON('_selflink', $this->getFooter()->getSelfUrl('unencoded'));
+ $this->addJSON('_displayMessage', $this->getHeader()->getMessage());
+ $errors = $this->_footer->getErrorMessages();
+ if (strlen($errors)) {
+ $this->addJSON('_errors', $errors);
+ }
+ if (empty($GLOBALS['error_message'])) {
+ // set current db, table and sql query in the querywindow
+ $query = '';
+ $maxChars = $GLOBALS['cfg']['MaxCharactersInDisplayedSQL'];
+ if (isset($GLOBALS['sql_query'])
+ && strlen($GLOBALS['sql_query']) < $maxChars
+ ) {
+ $query = PMA_escapeJsString($GLOBALS['sql_query']);
+ }
+ $this->addJSON(
+ '_reloadQuerywindow',
+ array(
+ 'db' => PMA_ifSetOr($GLOBALS['db'], ''),
+ 'table' => PMA_ifSetOr($GLOBALS['table'], ''),
+ 'sql_query' => $query
+ )
+ );
+ if (! empty($GLOBALS['focus_querywindow'])) {
+ $this->addJSON('_focusQuerywindow', $query);
+ }
+ if (! empty($GLOBALS['reload'])) {
+ $this->addJSON('_reloadNavigation', 1);
+ }
+ $this->addJSON('_params', $this->getHeader()->getJsParams());
+ }
+ }
+
+ // Set the Content-Type header to JSON so that jQuery parses the
+ // response correctly.
+ if (! defined('TESTSUITE')) {
+ header('Cache-Control: no-cache');
+ header('Content-Type: application/json');
+ }
+
+ echo json_encode($this->_JSON);
+ }
+
+ /**
+ * Sends an HTML response to the browser
+ *
+ * @static
+ * @return void
+ */
+ public static function response()
+ {
+ $response = PMA_Response::getInstance();
+ chdir($response->getCWD());
+ $buffer = PMA_OutputBuffering::getInstance();
+ if (empty($response->_HTML)) {
+ $response->_HTML = $buffer->getContents();
+ }
+ if ($response->isAjax()) {
+ $response->_ajaxResponse();
+ } else {
+ $response->_htmlResponse();
+ }
+ $buffer->flush();
+ exit;
+ }
+}
+
+?>
diff --git a/libraries/Scripts.class.php b/libraries/Scripts.class.php
new file mode 100644
index 0000000000..1ceb370632
--- /dev/null
+++ b/libraries/Scripts.class.php
@@ -0,0 +1,248 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * JavaScript management
+ *
+ * @package PhpMyAdmin
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ * Collects information about which JavaScript
+ * files and objects are necessary to render
+ * the page and generates the relevant code.
+ *
+ * @package PhpMyAdmin
+ */
+class PMA_Scripts
+{
+ /**
+ * An array of SCRIPT tags
+ *
+ * @access private
+ * @var array of strings
+ */
+ private $_files;
+ /**
+ * An array of discrete javascript code snippets
+ *
+ * @access private
+ * @var array of strings
+ */
+ private $_code;
+ /**
+ * An array of event names to bind and javascript code
+ * snippets to fire for the corresponding events
+ *
+ * @access private
+ * @var array
+ */
+ private $_events;
+
+ /**
+ * Returns HTML code to include javascript file.
+ *
+ * @param array $files The list of js file to include
+ *
+ * @return string HTML code for javascript inclusion.
+ */
+ private function _includeFiles($files)
+ {
+ $dynamic_scripts = "";
+ $params = array();
+ foreach ($files as $value) {
+ if (strpos($value['filename'], "?") === false) {
+ $include = true;
+ if ($value['conditional_ie'] !== false
+ && PMA_USR_BROWSER_AGENT === 'IE'
+ ) {
+ if ($value['conditional_ie'] === true) {
+ $include = true;
+ } else if ($value['conditional_ie'] == PMA_USR_BROWSER_VER) {
+ $include = true;
+ } else {
+ $include = false;
+ }
+ }
+ if ($include) {
+ $scripts[] = "scripts[]=" . $value['filename'];
+ }
+ } else {
+ $dynamic_scripts .= "<script type='text/javascript' src='js/"
+ . $value['filename'] . "'></script>";
+ }
+ }
+ $static_scripts = sprintf(
+ '<script type="text/javascript" '
+ . 'src="js/get_scripts.js.php%s&%s"></script>',
+ PMA_URL_getCommon(array(), 'none'), implode("&", $scripts)
+ );
+ return $static_scripts . $dynamic_scripts;
+ }
+
+ /**
+ * Generates new PMA_Scripts objects
+ *
+ * @return PMA_Scripts object
+ */
+ public function __construct()
+ {
+ $this->_files = array();
+ $this->_code = '';
+ $this->_events = array();
+
+ }
+
+ /**
+ * Adds a new file to the list of scripts
+ *
+ * @param string $filename The name of the file to include
+ * @param bool $conditional_ie Whether to wrap the script tag in
+ * conditional comments for IE
+ *
+ * @return void
+ */
+ public function addFile($filename, $conditional_ie = false)
+ {
+ $hash = md5($filename);
+ if (empty($this->_files[$hash])) {
+ $has_onload = $this->_eventBlacklist($filename);
+ $this->_files[$hash] = array(
+ 'has_onload' => $has_onload,
+ 'filename' => $filename,
+ 'conditional_ie' => $conditional_ie
+ );
+ }
+ }
+
+ /**
+ * Determines whether to fire up an onload event for a file
+ *
+ * @param string $filename The name of the file to be checked
+ * against the blacklist
+ *
+ * @return int 1 to fire up the event, 0 not to
+ */
+ private function _eventBlacklist($filename)
+ {
+ if ( strpos($filename, 'jquery') !== false
+ || strpos($filename, 'codemirror') !== false
+ || strpos($filename, 'messages.php') !== false
+ || strpos($filename, 'ajax.js') !== false
+ || strpos($filename, 'navigation.js') !== false
+ || strpos($filename, 'get_image.js.php') !== false
+ || strpos($filename, 'cross_framing_protection.js') !== false
+ ) {
+ return 0;
+ } else {
+ return 1;
+ }
+ }
+
+ /**
+ * Adds a new code snippet to the code to be executed
+ *
+ * @param string $code The JS code to be added
+ *
+ * @return void
+ */
+ public function addCode($code)
+ {
+ $this->_code .= "$code\n";
+ }
+
+ /**
+ * Adds a new event to the list of events
+ *
+ * @param string $event The name of the event to register
+ * @param string $function The code to execute when the event fires
+ * E.g: 'function () { doSomething(); }'
+ * or 'doSomething'
+ *
+ * @return void
+ */
+ public function addEvent($event, $function)
+ {
+ $this->_events[] = array(
+ 'event' => $event,
+ 'function' => $function
+ );
+ }
+
+ /**
+ * Returns a list with filenames and a flag to indicate
+ * whether to register onload events for this file
+ *
+ * @return array
+ */
+ public function getFiles()
+ {
+ $retval = array();
+ foreach ($this->_files as $file) {
+ if (strpos($file['filename'], "?") === false) {
+ if (! $file['conditional_ie'] || PMA_USR_BROWSER_AGENT == 'IE') {
+ $retval[] = array(
+ 'name' => $file['filename'],
+ 'fire' => $file['has_onload']
+ );
+ }
+ }
+ }
+ return $retval;
+ }
+
+ /**
+ * Renders all the JavaScript file inclusions, code and events
+ *
+ * @return string
+ */
+ public function getDisplay()
+ {
+ $retval = '';
+
+ if (count($this->_files) > 0) {
+ $retval .= $this->_includeFiles(
+ $this->_files
+ );
+ }
+
+ $code = 'AJAX.scriptHandler';
+ foreach ($this->_files as $file) {
+ $code .= sprintf(
+ '.add("%s",%d)',
+ PMA_escapeJsString($file['filename']),
+ $file['has_onload'] ? 1 : 0
+ );
+ }
+ $code .= ';';
+ $this->addCode($code);
+
+ $code = '$(function() {';
+ foreach ($this->_files as $file) {
+ if ($file['has_onload']) {
+ $code .= 'AJAX.fireOnload("';
+ $code .= PMA_escapeJsString($file['filename']);
+ $code .= '");';
+ }
+ }
+ $code .= '});';
+ $this->addCode($code);
+
+ $retval .= '<script type="text/javascript">';
+ $retval .= "// <![CDATA[\n";
+ $retval .= $this->_code;
+ foreach ($this->_events as $js_event) {
+ $retval .= sprintf(
+ "$(window).bind('%s', %s);\n",
+ $js_event['event'],
+ $js_event['function']
+ );
+ }
+ $retval .= '// ]]>';
+ $retval .= '</script>';
+
+ return $retval;
+ }
+}
diff --git a/libraries/ServerStatusData.class.php b/libraries/ServerStatusData.class.php
new file mode 100644
index 0000000000..028eada50c
--- /dev/null
+++ b/libraries/ServerStatusData.class.php
@@ -0,0 +1,407 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * PMA_ServerStatusData class
+ * Used by server_status_*.php pages
+ *
+ * @package PhpMyAdmin
+ */
+
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ * This class provides data about the server status
+ *
+ * All properties of the class are read-only
+ *
+ * TODO: Use lazy initialisation for some of the properties
+ * since not all of the server_status_*.php pages need
+ * all the data that this class provides.
+ *
+ * @package PhpMyAdmin
+ */
+class PMA_ServerStatusData
+{
+ public $status;
+ public $sections;
+ public $variables;
+ public $used_queries;
+ public $allocationMap;
+ public $links;
+ public $db_isLocal;
+ public $section;
+ public $categoryUsed;
+ public $selfUrl;
+
+ /**
+ * An empty setter makes the above properties read-only
+ *
+ * @param string $a key
+ * @param mixed $b value
+ *
+ * @return void
+ */
+ public function __set($a, $b)
+ {
+ // Discard everything
+ }
+
+ /**
+ * Constructor
+ */
+ public function __construct()
+ {
+ $this->selfUrl = basename($GLOBALS['PMA_PHP_SELF']);
+ /**
+ * get status from server
+ */
+ $server_status = $GLOBALS['dbi']->fetchResult('SHOW GLOBAL STATUS', 0, 1);
+ if (PMA_DRIZZLE) {
+ // Drizzle doesn't put query statistics into variables, add it
+ $sql = "SELECT concat('Com_', variable_name), variable_value "
+ . "FROM data_dictionary.GLOBAL_STATEMENTS";
+ $statements = $GLOBALS['dbi']->fetchResult($sql, 0, 1);
+ $server_status = array_merge($server_status, $statements);
+ }
+
+ /**
+ * for some calculations we require also some server settings
+ */
+ $server_variables = $GLOBALS['dbi']->fetchResult(
+ 'SHOW GLOBAL VARIABLES', 0, 1
+ );
+
+ /**
+ * cleanup of some deprecated values
+ */
+ $server_status = self::cleanDeprecated($server_status);
+
+ /**
+ * calculate some values
+ */
+ // Key_buffer_fraction
+ if (isset($server_status['Key_blocks_unused'])
+ && isset($server_variables['key_cache_block_size'])
+ && isset($server_variables['key_buffer_size'])
+ ) {
+ $server_status['Key_buffer_fraction_%']
+ = 100
+ - $server_status['Key_blocks_unused']
+ * $server_variables['key_cache_block_size']
+ / $server_variables['key_buffer_size']
+ * 100;
+ } elseif (isset($server_status['Key_blocks_used'])
+ && isset($server_variables['key_buffer_size'])
+ ) {
+ $server_status['Key_buffer_fraction_%']
+ = $server_status['Key_blocks_used']
+ * 1024
+ / $server_variables['key_buffer_size'];
+ }
+
+ // Ratio for key read/write
+ if (isset($server_status['Key_writes'])
+ && isset($server_status['Key_write_requests'])
+ && $server_status['Key_write_requests'] > 0
+ ) {
+ $key_writes = $server_status['Key_writes'];
+ $key_write_requests = $server_status['Key_write_requests'];
+ $server_status['Key_write_ratio_%']
+ = 100 * $key_writes / $key_write_requests;
+ }
+
+ if (isset($server_status['Key_reads'])
+ && isset($server_status['Key_read_requests'])
+ && $server_status['Key_read_requests'] > 0
+ ) {
+ $key_reads = $server_status['Key_reads'];
+ $key_read_requests = $server_status['Key_read_requests'];
+ $server_status['Key_read_ratio_%']
+ = 100 * $key_reads / $key_read_requests;
+ }
+
+ // Threads_cache_hitrate
+ if (isset($server_status['Threads_created'])
+ && isset($server_status['Connections'])
+ && $server_status['Connections'] > 0
+ ) {
+
+ $server_status['Threads_cache_hitrate_%']
+ = 100 - $server_status['Threads_created']
+ / $server_status['Connections'] * 100;
+ }
+
+ /**
+ * split variables in sections
+ */
+ $allocations = array(
+ // variable name => section
+ // variable names match when they begin with the given string
+
+ 'Com_' => 'com',
+ 'Innodb_' => 'innodb',
+ 'Ndb_' => 'ndb',
+ 'Handler_' => 'handler',
+ 'Qcache_' => 'qcache',
+ 'Threads_' => 'threads',
+ 'Slow_launch_threads' => 'threads',
+
+ 'Binlog_cache_' => 'binlog_cache',
+ 'Created_tmp_' => 'created_tmp',
+ 'Key_' => 'key',
+
+ 'Delayed_' => 'delayed',
+ 'Not_flushed_delayed_rows' => 'delayed',
+
+ 'Flush_commands' => 'query',
+ 'Last_query_cost' => 'query',
+ 'Slow_queries' => 'query',
+ 'Queries' => 'query',
+ 'Prepared_stmt_count' => 'query',
+
+ 'Select_' => 'select',
+ 'Sort_' => 'sort',
+
+ 'Open_tables' => 'table',
+ 'Opened_tables' => 'table',
+ 'Open_table_definitions' => 'table',
+ 'Opened_table_definitions' => 'table',
+ 'Table_locks_' => 'table',
+
+ 'Rpl_status' => 'repl',
+ 'Slave_' => 'repl',
+
+ 'Tc_' => 'tc',
+
+ 'Ssl_' => 'ssl',
+
+ 'Open_files' => 'files',
+ 'Open_streams' => 'files',
+ 'Opened_files' => 'files',
+ );
+
+ $sections = array(
+ // section => section name (description)
+ 'com' => 'Com',
+ 'query' => __('SQL query'),
+ 'innodb' => 'InnoDB',
+ 'ndb' => 'NDB',
+ 'handler' => __('Handler'),
+ 'qcache' => __('Query cache'),
+ 'threads' => __('Threads'),
+ 'binlog_cache' => __('Binary log'),
+ 'created_tmp' => __('Temporary data'),
+ 'delayed' => __('Delayed inserts'),
+ 'key' => __('Key cache'),
+ 'select' => __('Joins'),
+ 'repl' => __('Replication'),
+ 'sort' => __('Sorting'),
+ 'table' => __('Tables'),
+ 'tc' => __('Transaction coordinator'),
+ 'files' => __('Files'),
+ 'ssl' => 'SSL',
+ 'other' => __('Other')
+ );
+
+ /**
+ * define some needfull links/commands
+ */
+ // variable or section name => (name => url)
+ $links = array();
+
+ $links['table'][__('Flush (close) all tables')] = $this->selfUrl
+ . PMA_URL_getCommon(
+ array(
+ 'flush' => 'TABLES'
+ )
+ );
+ $links['table'][__('Show open tables')]
+ = 'sql.php' . PMA_URL_getCommon(
+ array(
+ 'sql_query' => 'SHOW OPEN TABLES',
+ 'goto' => $this->selfUrl,
+ )
+ );
+
+ if ($GLOBALS['server_master_status']) {
+ $links['repl'][__('Show slave hosts')]
+ = 'sql.php' . PMA_URL_getCommon(
+ array(
+ 'sql_query' => 'SHOW SLAVE HOSTS',
+ 'goto' => $this->selfUrl,
+ )
+ );
+ $links['repl'][__('Show master status')] = '#replication_master';
+ }
+ if ($GLOBALS['server_slave_status']) {
+ $links['repl'][__('Show slave status')] = '#replication_slave';
+ }
+
+ $links['repl']['doc'] = 'replication';
+
+ $links['qcache'][__('Flush query cache')]
+ = $this->selfUrl
+ . PMA_URL_getCommon(
+ array(
+ 'flush' => 'QUERY CACHE'
+ )
+ );
+ $links['qcache']['doc'] = 'query_cache';
+
+ $links['threads']['doc'] = 'mysql_threads';
+
+ $links['key']['doc'] = 'myisam_key_cache';
+
+ $links['binlog_cache']['doc'] = 'binary_log';
+
+ $links['Slow_queries']['doc'] = 'slow_query_log';
+
+ $links['innodb'][__('Variables')]
+ = 'server_engines.php?engine=InnoDB&amp;' . PMA_URL_getCommon();
+ $links['innodb'][__('InnoDB Status')]
+ = 'server_engines.php'
+ . PMA_URL_getCommon(
+ array(
+ 'engine' => 'InnoDB',
+ 'page' => 'Status'
+ )
+ );
+ $links['innodb']['doc'] = 'innodb';
+
+
+ // Variable to contain all com_ variables (query statistics)
+ $used_queries = array();
+
+ // Variable to map variable names to their respective section name
+ // (used for js category filtering)
+ $allocationMap = array();
+
+ // Variable to mark used sections
+ $categoryUsed = array();
+
+ // sort vars into arrays
+ foreach ($server_status as $name => $value) {
+ $section_found = false;
+ foreach ($allocations as $filter => $section) {
+ if (strpos($name, $filter) !== false) {
+ $allocationMap[$name] = $section;
+ $categoryUsed[$section] = true;
+ $section_found = true;
+ if ($section == 'com' && $value > 0) {
+ $used_queries[$name] = $value;
+ }
+ break; // Only exits inner loop
+ }
+ }
+ if (!$section_found) {
+ $allocationMap[$name] = 'other';
+ $categoryUsed['other'] = true;
+ }
+ }
+
+ if (PMA_DRIZZLE) {
+ $used_queries = $GLOBALS['dbi']->fetchResult(
+ 'SELECT * FROM data_dictionary.global_statements',
+ 0,
+ 1
+ );
+ unset($used_queries['admin_commands']);
+ } else {
+ // admin commands are not queries (e.g. they include COM_PING,
+ // which is excluded from $server_status['Questions'])
+ unset($used_queries['Com_admin_commands']);
+ }
+
+ // Set all class properties
+ $this->db_isLocal = false;
+ if (strtolower($GLOBALS['cfg']['Server']['host']) === 'localhost'
+ || $GLOBALS['cfg']['Server']['host'] === '127.0.0.1'
+ || $GLOBALS['cfg']['Server']['host'] === '::1'
+ ) {
+ $this->db_isLocal = true;
+ }
+ $this->status = $server_status;
+ $this->sections = $sections;
+ $this->variables = $server_variables;
+ $this->used_queries = $used_queries;
+ $this->allocationMap = $allocationMap;
+ $this->links = $links;
+ $this->categoryUsed = $categoryUsed;
+ }
+
+ /**
+ * cleanup of some deprecated values
+ *
+ * @param array $server_status status array to process
+ *
+ * @return array
+ */
+ public static function cleanDeprecated($server_status)
+ {
+ $deprecated = array(
+ 'Com_prepare_sql' => 'Com_stmt_prepare',
+ 'Com_execute_sql' => 'Com_stmt_execute',
+ 'Com_dealloc_sql' => 'Com_stmt_close',
+ );
+ foreach ($deprecated as $old => $new) {
+ if (isset($server_status[$old]) && isset($server_status[$new])) {
+ unset($server_status[$old]);
+ }
+ }
+ return $server_status;
+ }
+
+ /**
+ * cleanup of some deprecated values
+ *
+ * @return array
+ */
+ public function getMenuHtml()
+ {
+ $url_params = PMA_URL_getCommon();
+ $items = array(
+ array(
+ 'name' => __('Server'),
+ 'url' => 'server_status.php'
+ ),
+ array(
+ 'name' => __('Query statistics'),
+ 'url' => 'server_status_queries.php'
+ ),
+ array(
+ 'name' => __('All status variables'),
+ 'url' => 'server_status_variables.php'
+ ),
+ array(
+ 'name' => __('Monitor'),
+ 'url' => 'server_status_monitor.php'
+ ),
+ array(
+ 'name' => __('Advisor'),
+ 'url' => 'server_status_advisor.php'
+ )
+ );
+
+ $retval = '<ul id="topmenu2">';
+ foreach ($items as $item) {
+ $class = '';
+ if ($item['url'] === $this->selfUrl) {
+ $class = ' class="tabactive"';
+ }
+ $retval .= '<li>';
+ $retval .= '<a' . $class;
+ $retval .= ' href="' . $item['url'] . '?' . $url_params . '">';
+ $retval .= $item['name'];
+ $retval .= '</a>';
+ $retval .= '</li>';
+ }
+ $retval .= '</ul>';
+ $retval .= '<div class="clearfloat"></div>';
+
+ return $retval;
+ }
+}
+
+?>
diff --git a/libraries/StorageEngine.class.php b/libraries/StorageEngine.class.php
new file mode 100644
index 0000000000..19269d0a3c
--- /dev/null
+++ b/libraries/StorageEngine.class.php
@@ -0,0 +1,434 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Library for extracting information about the available storage engines
+ *
+ * @package PhpMyAdmin
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ * defines
+ */
+define('PMA_ENGINE_SUPPORT_NO', 0);
+define('PMA_ENGINE_SUPPORT_DISABLED', 1);
+define('PMA_ENGINE_SUPPORT_YES', 2);
+define('PMA_ENGINE_SUPPORT_DEFAULT', 3);
+
+define('PMA_ENGINE_DETAILS_TYPE_PLAINTEXT', 0);
+define('PMA_ENGINE_DETAILS_TYPE_SIZE', 1);
+define('PMA_ENGINE_DETAILS_TYPE_NUMERIC', 2); //Has no effect yet...
+define('PMA_ENGINE_DETAILS_TYPE_BOOLEAN', 3); // 'ON' or 'OFF'
+
+/**
+ * Base Storage Engine Class
+ *
+ * @package PhpMyAdmin
+ */
+class PMA_StorageEngine
+{
+ /**
+ * @var string engine name
+ */
+ var $engine = 'dummy';
+
+ /**
+ * @var string engine title/description
+ */
+ var $title = 'PMA Dummy Engine Class';
+
+ /**
+ * @var string engine lang description
+ */
+ var $comment
+ = 'If you read this text inside phpMyAdmin, something went wrong...';
+
+ /**
+ * @var integer engine supported by current server
+ */
+ var $support = PMA_ENGINE_SUPPORT_NO;
+
+ /**
+ * returns array of storage engines
+ *
+ * @static
+ * @staticvar array $storage_engines storage engines
+ * @access public
+ * @return array of storage engines
+ */
+ static public function getStorageEngines()
+ {
+ static $storage_engines = null;
+
+ if (null == $storage_engines) {
+ if (PMA_DRIZZLE) {
+ $sql = "SELECT
+ p.plugin_name AS Engine,
+ (CASE
+ WHEN p.plugin_name = @@storage_engine THEN 'DEFAULT'
+ WHEN p.is_active THEN 'YES'
+ ELSE 'DISABLED' END) AS Support,
+ m.module_description AS Comment
+ FROM data_dictionary.plugins p
+ JOIN data_dictionary.modules m USING (module_name)
+ WHERE p.plugin_type = 'StorageEngine'
+ AND p.plugin_name NOT IN ('FunctionEngine', 'schema')";
+ $storage_engines = $GLOBALS['dbi']->fetchResult($sql, 'Engine');
+ } else {
+ $storage_engines
+ = $GLOBALS['dbi']->fetchResult('SHOW STORAGE ENGINES', 'Engine');
+ }
+ }
+
+ return $storage_engines;
+ }
+
+ /**
+ * returns HTML code for storage engine select box
+ *
+ * @param string $name The name of the select form element
+ * @param string $id The ID of the form field
+ * @param string $selected The selected engine
+ * @param boolean $offerUnavailableEngines Should unavailable storage
+ * engines be offered?
+ *
+ * @static
+ * @return string html selectbox
+ */
+ static public function getHtmlSelect(
+ $name = 'engine', $id = null,
+ $selected = null, $offerUnavailableEngines = false
+ ) {
+ $selected = strtolower($selected);
+ $output = '<select name="' . $name . '"'
+ . (empty($id) ? '' : ' id="' . $id . '"') . '>' . "\n";
+
+ foreach (PMA_StorageEngine::getStorageEngines() as $key => $details) {
+ // Don't show PERFORMANCE_SCHEMA engine (MySQL 5.5)
+ // Don't show MyISAM for Drizzle (allowed only for temporary tables)
+ if (! $offerUnavailableEngines
+ && ($details['Support'] == 'NO'
+ || $details['Support'] == 'DISABLED'
+ || $details['Engine'] == 'PERFORMANCE_SCHEMA')
+ || (PMA_DRIZZLE && $details['Engine'] == 'MyISAM')
+ ) {
+ continue;
+ }
+
+ $output .= ' <option value="' . htmlspecialchars($key). '"'
+ . (empty($details['Comment'])
+ ? '' : ' title="' . htmlspecialchars($details['Comment']) . '"')
+ . (strtolower($key) == $selected
+ || (empty($selected) && $details['Support'] == 'DEFAULT')
+ ? ' selected="selected"' : '')
+ . '>' . "\n"
+ . ' ' . htmlspecialchars($details['Engine']) . "\n"
+ . ' </option>' . "\n";
+ }
+ $output .= '</select>' . "\n";
+ return $output;
+ }
+
+ /**
+ * public static final PMA_StorageEngine getEngine()
+ *
+ * Loads the corresponding engine plugin, if available.
+ *
+ * @param string $engine The engine ID
+ *
+ * @return PMA_StorageEngine The engine plugin
+ */
+ static public function getEngine($engine)
+ {
+ $engine = str_replace('/', '', str_replace('.', '', $engine));
+ $filename = './libraries/engines/' . strtolower($engine) . '.lib.php';
+ if (file_exists($filename) && include_once $filename) {
+ $class_name = 'PMA_StorageEngine_' . $engine;
+ $engine_object = new $class_name($engine);
+ } else {
+ $engine_object = new PMA_StorageEngine($engine);
+ }
+ return $engine_object;
+ }
+
+ /**
+ * return true if given engine name is supported/valid, otherwise false
+ *
+ * @param string $engine name of engine
+ *
+ * @static
+ * @return boolean whether $engine is valid or not
+ */
+ static public function isValid($engine)
+ {
+ if ($engine == "PBMS") {
+ return true;
+ }
+ $storage_engines = PMA_StorageEngine::getStorageEngines();
+ return isset($storage_engines[$engine]);
+ }
+
+ /**
+ * returns as HTML table of the engine's server variables
+ *
+ * @return string The table that was generated based on the retrieved
+ * information
+ */
+ function getHtmlVariables()
+ {
+ $odd_row = false;
+ $ret = '';
+
+ foreach ($this->getVariablesStatus() as $details) {
+ $ret .= '<tr class="' . ($odd_row ? 'odd' : 'even') . '">' . "\n"
+ . ' <td>' . "\n";
+ if (! empty($details['desc'])) {
+ $ret .= ' '
+ . PMA_Util::showHint($details['desc'])
+ . "\n";
+ }
+ $ret .= ' </td>' . "\n"
+ . ' <th>' . htmlspecialchars($details['title']) . '</th>'
+ . "\n"
+ . ' <td class="value">';
+ switch ($details['type']) {
+ case PMA_ENGINE_DETAILS_TYPE_SIZE:
+ $parsed_size = $this->resolveTypeSize($details['value']);
+ $ret .= $parsed_size[0] . '&nbsp;' . $parsed_size[1];
+ unset($parsed_size);
+ break;
+ case PMA_ENGINE_DETAILS_TYPE_NUMERIC:
+ $ret .= PMA_Util::formatNumber($details['value']) . ' ';
+ break;
+ default:
+ $ret .= htmlspecialchars($details['value']) . ' ';
+ }
+ $ret .= '</td>' . "\n"
+ . '</tr>' . "\n";
+ $odd_row = ! $odd_row;
+ }
+
+ if (! $ret) {
+ $ret = '<p>' . "\n"
+ . ' '
+ . __('There is no detailed status information available for this storage engine.')
+ . "\n"
+ . '</p>' . "\n";
+ } else {
+ $ret = '<table class="data">' . "\n" . $ret . '</table>' . "\n";
+ }
+
+ return $ret;
+ }
+
+ /**
+ * Returns the engine specific handling for
+ * PMA_ENGINE_DETAILS_TYPE_SIZE type variables.
+ *
+ * This function should be overridden when
+ * PMA_ENGINE_DETAILS_TYPE_SIZE type needs to be
+ * handled differently for a particular engine.
+ *
+ * @param integer $value Value to format
+ *
+ * @return string the formatted value and its unit
+ */
+ function resolveTypeSize($value)
+ {
+ return PMA_Util::formatByteDown($value);
+ }
+
+ /**
+ * returns array with detailed info about engine specific server variables
+ *
+ * @return array with detailed info about specific engine server variables
+ */
+ function getVariablesStatus()
+ {
+ $variables = $this->getVariables();
+ $like = $this->getVariablesLikePattern();
+
+ if ($like) {
+ $like = " LIKE '" . $like . "' ";
+ } else {
+ $like = '';
+ }
+
+ $mysql_vars = array();
+
+ $sql_query = 'SHOW GLOBAL VARIABLES ' . $like . ';';
+ $res = $GLOBALS['dbi']->query($sql_query);
+ while ($row = $GLOBALS['dbi']->fetchAssoc($res)) {
+ if (isset($variables[$row['Variable_name']])) {
+ $mysql_vars[$row['Variable_name']] = $variables[$row['Variable_name']];
+ } elseif (! $like
+ && strpos(strtolower($row['Variable_name']), strtolower($this->engine)) !== 0
+ ) {
+ continue;
+ }
+ $mysql_vars[$row['Variable_name']]['value'] = $row['Value'];
+
+ if (empty($mysql_vars[$row['Variable_name']]['title'])) {
+ $mysql_vars[$row['Variable_name']]['title'] = $row['Variable_name'];
+ }
+
+ if (! isset($mysql_vars[$row['Variable_name']]['type'])) {
+ $mysql_vars[$row['Variable_name']]['type']
+ = PMA_ENGINE_DETAILS_TYPE_PLAINTEXT;
+ }
+ }
+ $GLOBALS['dbi']->freeResult($res);
+
+ return $mysql_vars;
+ }
+
+ /**
+ * Constructor
+ *
+ * @param string $engine The engine ID
+ */
+ function __construct($engine)
+ {
+ $storage_engines = PMA_StorageEngine::getStorageEngines();
+ if (! empty($storage_engines[$engine])) {
+ $this->engine = $engine;
+ $this->title = $storage_engines[$engine]['Engine'];
+ $this->comment
+ = (isset($storage_engines[$engine]['Comment'])
+ ? $storage_engines[$engine]['Comment']
+ : '');
+ switch ($storage_engines[$engine]['Support']) {
+ case 'DEFAULT':
+ $this->support = PMA_ENGINE_SUPPORT_DEFAULT;
+ break;
+ case 'YES':
+ $this->support = PMA_ENGINE_SUPPORT_YES;
+ break;
+ case 'DISABLED':
+ $this->support = PMA_ENGINE_SUPPORT_DISABLED;
+ break;
+ case 'NO':
+ default:
+ $this->support = PMA_ENGINE_SUPPORT_NO;
+ }
+ }
+ }
+
+ /**
+ * public String getTitle()
+ *
+ * Reveals the engine's title
+ *
+ * @return string The title
+ */
+ function getTitle()
+ {
+ return $this->title;
+ }
+
+ /**
+ * public String getComment()
+ *
+ * Fetches the server's comment about this engine
+ *
+ * @return string The comment
+ */
+ function getComment()
+ {
+ return $this->comment;
+ }
+
+ /**
+ * public String getSupportInformationMessage()
+ *
+ * @return string The localized message.
+ */
+ function getSupportInformationMessage()
+ {
+ switch ($this->support) {
+ case PMA_ENGINE_SUPPORT_DEFAULT:
+ $message = __('%s is the default storage engine on this MySQL server.');
+ break;
+ case PMA_ENGINE_SUPPORT_YES:
+ $message = __('%s is available on this MySQL server.');
+ break;
+ case PMA_ENGINE_SUPPORT_DISABLED:
+ $message = __('%s has been disabled for this MySQL server.');
+ break;
+ case PMA_ENGINE_SUPPORT_NO:
+ default:
+ $message = __('This MySQL server does not support the %s storage engine.');
+ }
+ return sprintf($message, htmlspecialchars($this->title));
+ }
+
+ /**
+ * public string[][] getVariables()
+ *
+ * Generates a list of MySQL variables that provide information about this
+ * engine. This function should be overridden when extending this class
+ * for a particular engine.
+ *
+ * @abstract
+ * @return Array The list of variables.
+ */
+ function getVariables()
+ {
+ return array();
+ }
+
+ /**
+ * returns string with filename for the MySQL helppage
+ * about this storage engine
+ *
+ * @return string mysql helppage filename
+ */
+ function getMysqlHelpPage()
+ {
+ return $this->engine . '-storage-engine';
+ }
+
+ /**
+ * public string getVariablesLikePattern()
+ *
+ * @abstract
+ * @return string SQL query LIKE pattern
+ */
+ function getVariablesLikePattern()
+ {
+ return false;
+ }
+
+ /**
+ * public String[] getInfoPages()
+ *
+ * Returns a list of available information pages with labels
+ *
+ * @abstract
+ * @return array The list
+ */
+ function getInfoPages()
+ {
+ return array();
+ }
+
+ /**
+ * public String getPage()
+ *
+ * Generates the requested information page
+ *
+ * @param string $id The page ID
+ *
+ * @abstract
+ * @return string The page
+ * boolean or false on error.
+ */
+ function getPage($id)
+ {
+ return false;
+ }
+}
+
+?>
diff --git a/libraries/String.class.php b/libraries/String.class.php
new file mode 100644
index 0000000000..a5fbe8a0ff
--- /dev/null
+++ b/libraries/String.class.php
@@ -0,0 +1,261 @@
+<?php
+/**
+ * Specialized string class for phpMyAdmin.
+ * The SQL Parser code relies heavily on these functions.
+ *
+ * @package PhpMyAdmin-String
+ */
+
+/**
+ * Specialized string class for phpMyAdmin.
+ * The SQL Parser code relies heavily on these functions.
+ *
+ * @package PhpMyAdmin-String
+ */
+class PMA_String
+{
+ /**
+ * @var PMA_StringType
+ */
+ private $_type;
+ /**
+ * @var PMA_StringByte
+ */
+ private $_byte;
+
+ /**
+ * Constructor
+ */
+ public function __construct()
+ {
+ if (@function_exists('mb_strlen')) {
+ mb_internal_encoding('utf-8');
+ include_once 'libraries/StringMB.class.php';
+ $this->_byte = new PMA_StringMB();
+ } else {
+ include_once 'libraries/StringNative.class.php';
+ $this->_byte = new PMA_StringNative();
+ }
+
+ if (@extension_loaded('ctype')) {
+ include_once 'libraries/StringCType.class.php';
+ $this->_type = new PMA_StringCType();
+ } else {
+ include_once 'libraries/StringNativeType.class.php';
+ $this->_type = new PMA_StringNativeType();
+ }
+ }
+
+ /**
+ * Checks if a given character position in the string is escaped or not
+ *
+ * @param string $string string to check for
+ * @param integer $pos the character to check for
+ * @param integer $start starting position in the string
+ *
+ * @return boolean whether the character is escaped or not
+ */
+ public function charIsEscaped($string, $pos, $start = 0)
+ {
+ $pos = max(intval($pos), 0);
+ $start = max(intval($start), 0);
+ $len = $this->strlen($string);
+ // Base case:
+ // Check for string length or invalid input or special case of input
+ // (pos == $start)
+ if ($pos <= $start || $len <= max($pos, $start)) {
+ return false;
+ }
+
+ $pos--;
+ $escaped = false;
+ while ($pos >= $start && $this->substr($string, $pos, 1) == '\\') {
+ $escaped = !$escaped;
+ $pos--;
+ } // end while
+
+ return $escaped;
+ }
+
+ /**
+ * Checks if a character is an SQL identifier
+ *
+ * @param string $c character to check for
+ * @param boolean $dot_is_valid whether the dot character is valid or not
+ *
+ * @return boolean whether the character is an SQL identifier or not
+ */
+ public function isSqlIdentifier($c, $dot_is_valid = false)
+ {
+ return ($this->isAlnum($c)
+ || ($ord_c = $this->ord($c)) && $ord_c >= 192 && $ord_c != 215 &&
+ $ord_c != 249
+ || $c == '_'
+ || $c == '$'
+ || ($dot_is_valid != false && $c == '.'));
+ }
+
+ /**
+ * Returns length of string depending on current charset.
+ *
+ * @param string $string string to count
+ *
+ * @return int string length
+ */
+
+ public function strlen($string)
+ {
+ return $this->_byte->strlen($string);
+ }
+
+ /**
+ * Returns substring from string, works depending on current charset.
+ *
+ * @param string $string string to count
+ * @param int $start start of substring
+ * @param int $length length of substring
+ *
+ * @return string the sub string
+ */
+ public function substr($string, $start, $length = 2147483647)
+ {
+ return $this->_byte->substr($string, $start, $length);
+ }
+
+ /**
+ * Returns postion of $needle in $haystack or false if not found
+ *
+ * @param string $haystack the string being checked
+ * @param string $needle the string to find in haystack
+ * @param int $offset the search offset
+ *
+ * @return integer position of $needle in $haystack or false
+ */
+ public function strpos($haystack, $needle, $offset = 0)
+ {
+ return $this->_byte->strpos($haystack, $needle, $offset);
+ }
+
+ /**
+ * Make a string lowercase
+ *
+ * @param string $string the string being lowercased
+ *
+ * @return string the lower case string
+ */
+ public function strtolower($string)
+ {
+ return $this->_byte->strtolower($string);
+ }
+
+ /**
+ * Get the ordinal value of a string
+ *
+ * @param string $string the string for which ord is required
+ *
+ * @return string the ord value
+ */
+ public function ord($string)
+ {
+ return $this->_byte->ord($string);
+ }
+
+ /**
+ * Checks if a character is an alphanumeric one
+ *
+ * @param string $c character to check for
+ *
+ * @return boolean whether the character is an alphanumeric one or not
+ */
+ public function isAlnum($c)
+ {
+ return $this->_type->isAlnum($c);
+ }
+
+ /**
+ * Checks if a character is an alphabetic one
+ *
+ * @param string $c character to check for
+ *
+ * @return boolean whether the character is an alphabetic one or not
+ */
+ public function isAlpha($c)
+ {
+ return $this->_type->isAlpha($c);
+ }
+
+ /**
+ * Checks if a character is a digit
+ *
+ * @param string $c character to check for
+ *
+ * @return boolean whether the character is a digit or not
+ */
+ public function isDigit($c)
+ {
+ return $this->_type->isDigit($c);
+ }
+
+ /**
+ * Checks if a character is an upper alphabetic one
+ *
+ * @param string $c character to check for
+ *
+ * @return boolean whether the character is an upper alphabetic one or not
+ */
+ public function isUpper($c)
+ {
+ return $this->_type->isUpper($c);
+ }
+
+ /**
+ * Checks if a character is a lower alphabetic one
+ *
+ * @param string $c character to check for
+ *
+ * @return boolean whether the character is a lower alphabetic one or not
+ */
+ public function isLower($c)
+ {
+ return $this->_type->isLower($c);
+ }
+
+ /**
+ * Checks if a character is a space one
+ *
+ * @param string $c character to check for
+ *
+ * @return boolean whether the character is a space one or not
+ */
+ public function isSpace($c)
+ {
+ return $this->_type->isSpace($c);
+ }
+
+ /**
+ * Checks if a character is an hexadecimal digit
+ *
+ * @param string $c character to check for
+ *
+ * @return boolean whether the character is an hexadecimal digit or not
+ */
+ public function isHexDigit($c)
+ {
+ return $this->_type->isHexDigit($c);
+ }
+
+ /**
+ * Checks if a number is in a range
+ *
+ * @param integer $num number to check for
+ * @param integer $lower lower bound
+ * @param integer $upper upper bound
+ *
+ * @return boolean whether the number is in the range or not
+ */
+ public function numberInRangeInclusive($num, $lower, $upper)
+ {
+ return $this->_type->numberInRangeInclusive($num, $lower, $upper);
+ }
+}
+?> \ No newline at end of file
diff --git a/libraries/StringAbstractType.class.php b/libraries/StringAbstractType.class.php
new file mode 100644
index 0000000000..6424844a2c
--- /dev/null
+++ b/libraries/StringAbstractType.class.php
@@ -0,0 +1,35 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Common functions for classes based on PMA_StringByte interface.
+ *
+ * @package PhpMyAdmin-String
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+require_once 'libraries/StringType.int.php';
+
+/**
+ * Implements PMA_StringByte interface using native PHP functions.
+ *
+ * @package PhpMyAdmin-String
+ */
+abstract class PMA_StringAbstractType implements PMA_StringType
+{
+ /**
+ * Checks if a number is in a range
+ *
+ * @param integer $num number to check for
+ * @param integer $lower lower bound
+ * @param integer $upper upper bound
+ *
+ * @return boolean whether the number is in the range or not
+ */
+ public function numberInRangeInclusive($num, $lower, $upper)
+ {
+ return ($num >= $lower && $num <= $upper);
+ }
+}
+?> \ No newline at end of file
diff --git a/libraries/StringByte.int.php b/libraries/StringByte.int.php
new file mode 100644
index 0000000000..99e15a37f7
--- /dev/null
+++ b/libraries/StringByte.int.php
@@ -0,0 +1,60 @@
+<?php
+/**
+ * Defines a set of specialized string functions.
+ *
+ * @package PhpMyAdmin-String
+ * @todo May be move this into file of its own
+ */
+interface PMA_StringByte
+{
+ /**
+ * Returns length of string depending on current charset.
+ *
+ * @param string $string string to count
+ *
+ * @return int string length
+ */
+
+ public function strlen($string);
+
+ /**
+ * Returns substring from string, works depending on current charset.
+ *
+ * @param string $string string to count
+ * @param int $start start of substring
+ * @param int $length length of substring
+ *
+ * @return string the sub string
+ */
+ public function substr($string, $start, $length = 2147483647);
+
+ /**
+ * Returns postion of $needle in $haystack or false if not found
+ *
+ * @param string $haystack the string being checked
+ * @param string $needle the string to find in haystack
+ * @param int $offset the search offset
+ *
+ * @return integer position of $needle in $haystack or false
+ */
+ public function strpos($haystack, $needle, $offset = 0);
+
+ /**
+ * Make a string lowercase
+ *
+ * @param string $string the string being lowercased
+ *
+ * @return string the lower case string
+ */
+ public function strtolower($string);
+
+ /**
+ * Get the ordinal value of a string
+ *
+ * @param string $string the string for which ord is required
+ *
+ * @return string the ord value
+ */
+ public function ord($string);
+}
+?> \ No newline at end of file
diff --git a/libraries/StringCType.class.php b/libraries/StringCType.class.php
new file mode 100644
index 0000000000..5972f12487
--- /dev/null
+++ b/libraries/StringCType.class.php
@@ -0,0 +1,110 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Implements PMA_StringType interface using the "ctype" extension.
+ * Methods of the "ctype" extension are faster compared to PHP versions of them.
+ *
+ * @package PhpMyAdmin-String
+ * @subpackage CType
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+require_once 'libraries/StringAbstractType.class.php';
+
+/**
+ * Implements PMA_StringType interface using the "ctype" extension.
+ * Methods of the "ctype" extension are faster compared to PHP versions of them.
+ *
+ * @package PhpMyAdmin-String
+ * @subpackage CType
+ */
+class PMA_StringCType extends PMA_StringAbstractType
+{
+ /**
+ * Checks if a character is an alphanumeric one
+ *
+ * @param string $c character to check for
+ *
+ * @return boolean whether the character is an alphanumeric one or not
+ */
+ public function isAlnum($c)
+ {
+ return ctype_alnum($c);
+ } // end of the "isAlnum()" function
+
+ /**
+ * Checks if a character is an alphabetic one
+ *
+ * @param string $c character to check for
+ *
+ * @return boolean whether the character is an alphabetic one or not
+ */
+ public function isAlpha($c)
+ {
+ return ctype_alpha($c);
+ } // end of the "isAlpha()" function
+
+ /**
+ * Checks if a character is a digit
+ *
+ * @param string $c character to check for
+ *
+ * @return boolean whether the character is a digit or not
+ */
+ public function isDigit($c)
+ {
+ return ctype_digit($c);
+ } // end of the "isDigit()" function
+
+ /**
+ * Checks if a character is an upper alphabetic one
+ *
+ * @param string $c character to check for
+ *
+ * @return boolean whether the character is an upper alphabetic one or not
+ */
+ public function isUpper($c)
+ {
+ return ctype_upper($c);
+ } // end of the "isUpper()" function
+
+
+ /**
+ * Checks if a character is a lower alphabetic one
+ *
+ * @param string $c character to check for
+ *
+ * @return boolean whether the character is a lower alphabetic one or not
+ */
+ public function isLower($c)
+ {
+ return ctype_lower($c);
+ } // end of the "PisLower()" function
+
+ /**
+ * Checks if a character is a space one
+ *
+ * @param string $c character to check for
+ *
+ * @return boolean whether the character is a space one or not
+ */
+ public function isSpace($c)
+ {
+ return ctype_space($c);
+ } // end of the "isSpace()" function
+
+ /**
+ * Checks if a character is an hexadecimal digit
+ *
+ * @param string $c character to check for
+ *
+ * @return boolean whether the character is an hexadecimal digit or not
+ */
+ public function isHexDigit($c)
+ {
+ return ctype_xdigit($c);
+ } // end of the "isHexDigit()" function
+}
+?>
diff --git a/libraries/StringMB.class.php b/libraries/StringMB.class.php
new file mode 100644
index 0000000000..fb4cdc99db
--- /dev/null
+++ b/libraries/StringMB.class.php
@@ -0,0 +1,92 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Implements PMA_StringByte interface using the "mbstring" extension.
+ *
+ * @package PhpMyAdmin-String
+ * @subpackage MB
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+require_once 'libraries/StringByte.int.php';
+
+/**
+ * Implements PMA_StringByte interface using the "mbstring" extension.
+ *
+ * @package PhpMyAdmin-String
+ * @subpackage MB
+ */
+class PMA_StringMB implements PMA_StringByte
+{
+ /**
+ * Returns length of string depending on current charset.
+ *
+ * @param string $string string to count
+ *
+ * @return int string length
+ */
+
+ public function strlen($string)
+ {
+ return mb_strlen($string);
+ }
+
+ /**
+ * Returns substring from string, works depending on current charset.
+ *
+ * @param string $string string to count
+ * @param int $start start of substring
+ * @param int $length length of substring
+ *
+ * @return string the sub string
+ */
+ public function substr($string, $start, $length = 2147483647)
+ {
+ return mb_substr($string, $start, $length);
+ }
+
+ /**
+ * Returns postion of $needle in $haystack or false if not found
+ *
+ * @param string $haystack the string being checked
+ * @param string $needle the string to find in haystack
+ * @param int $offset the search offset
+ *
+ * @return integer position of $needle in $haystack or false
+ */
+ public function strpos($haystack, $needle, $offset = 0)
+ {
+ return mb_strpos($haystack, $needle, $offset);
+ }
+
+ /**
+ * Make a string lowercase
+ *
+ * @param string $string the string being lowercased
+ *
+ * @return string the lower case string
+ */
+ public function strtolower($string)
+ {
+ return mb_strtolower($string);
+ }
+
+ /**
+ * Get the ordinal value of a multibyte string
+ * (Adapted from http://www.php.net/manual/en/function.ord.php#72463)
+ *
+ * @param string $string the string for which ord is required
+ *
+ * @return string the ord value
+ */
+ public function ord($string)
+ {
+ $str = mb_convert_encoding($string, "UCS-4BE", "UTF-8");
+ $substr = mb_substr($str, 0, 1, "UCS-4BE");
+ $val = unpack("N", $substr);
+ return $val[1];
+ }
+}
+?>
diff --git a/libraries/StringNative.class.php b/libraries/StringNative.class.php
new file mode 100644
index 0000000000..b333610a90
--- /dev/null
+++ b/libraries/StringNative.class.php
@@ -0,0 +1,87 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Implements PMA_StringByte interface using native PHP functions.
+ *
+ * @package PhpMyAdmin-String
+ * @subpackage Native
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+require_once 'libraries/StringByte.int.php';
+
+/**
+ * Implements PMA_StringByte interface using native PHP functions.
+ *
+ * @package PhpMyAdmin-String
+ * @subpackage Native
+ */
+class PMA_StringNative implements PMA_StringByte
+{
+ /**
+ * Returns length of string depending on current charset.
+ *
+ * @param string $string string to count
+ *
+ * @return int string length
+ */
+ public function strlen($string)
+ {
+ return strlen($string);
+ }
+
+ /**
+ * Returns substring from string, works depending on current charset.
+ *
+ * @param string $string string to count
+ * @param int $start start of substring
+ * @param int $length length of substring
+ *
+ * @return string the sub string
+ */
+ public function substr($string, $start, $length = 2147483647)
+ {
+ return substr($string, $start, $length);
+ }
+
+ /**
+ * Returns postion of $needle in $haystack or false if not found
+ *
+ * @param string $haystack the string being checked
+ * @param string $needle the string to find in haystack
+ * @param int $offset the search offset
+ *
+ * @return integer position of $needle in $haystack or false
+ */
+ public function strpos($haystack, $needle, $offset = 0)
+ {
+ return strpos($haystack, $needle, $offset);
+ }
+
+ /**
+ * Make a string lowercase
+ *
+ * @param string $string the string being lowercased
+ *
+ * @return string the lower case string
+ */
+ public function strtolower($string)
+ {
+ return strtolower($string);
+ }
+
+ /**
+ * Get the ordinal value of a string
+ *
+ * @param string $string the string for which ord is required
+ *
+ * @return string the ord value
+ */
+ public function ord($string)
+ {
+ return ord($string);
+ }
+};
+?>
diff --git a/libraries/StringNativeType.class.php b/libraries/StringNativeType.class.php
new file mode 100644
index 0000000000..04b7679ee5
--- /dev/null
+++ b/libraries/StringNativeType.class.php
@@ -0,0 +1,138 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Implements PMA_StringByte interface using native PHP functions.
+ *
+ * @package PhpMyAdmin-String
+ * @subpackage Native
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+require_once 'libraries/StringAbstractType.class.php';
+
+/**
+ * Implements PMA_StringByte interface using native PHP functions.
+ *
+ * @package PhpMyAdmin-String
+ * @subpackage Native
+ * @todo May be join this class with PMA_StringNative class
+ */
+class PMA_StringNativeType extends PMA_StringAbstractType
+{
+ /**
+ * Checks if a character is an alphanumeric one
+ *
+ * @param string $c character to check for
+ *
+ * @return boolean whether the character is an alphanumeric one or not
+ */
+ public function isAlnum($c)
+ {
+ return ($this->isAlpha($c) || $this->isDigit($c));
+ } // end of the "isAlnum()" function
+
+ /**
+ * Checks if a character is an alphabetic one
+ *
+ * @param string $c character to check for
+ *
+ * @return boolean whether the character is an alphabetic one or not
+ */
+ public function isAlpha($c)
+ {
+ return ($this->isUpper($c) || $this->isLower($c));
+ } // end of the "isAlpha()" function
+
+ /**
+ * Checks if a character is a digit
+ *
+ * @param string $c character to check for
+ *
+ * @return boolean whether the character is a digit or not
+ */
+ public function isDigit($c)
+ {
+ $ord_zero = 48; //ord('0');
+ $ord_nine = 57; //ord('9');
+ $ord_c = ord($c);
+
+ return $this->numberInRangeInclusive($ord_c, $ord_zero, $ord_nine);
+ } // end of the "isDigit()" function
+
+ /**
+ * Checks if a character is an upper alphabetic one
+ *
+ * @param string $c character to check for
+ *
+ * @return boolean whether the character is an upper alphabetic one or not
+ */
+ public function isUpper($c)
+ {
+ $ord_zero = 65; //ord('A');
+ $ord_nine = 90; //ord('Z');
+ $ord_c = ord($c);
+
+ return $this->numberInRangeInclusive($ord_c, $ord_zero, $ord_nine);
+ } // end of the "isUpper()" function
+
+ /**
+ * Checks if a character is a lower alphabetic one
+ *
+ * @param string $c character to check for
+ *
+ * @return boolean whether the character is a lower alphabetic one or not
+ */
+ public function isLower($c)
+ {
+ $ord_zero = 97; //ord('a');
+ $ord_nine = 122; //ord('z');
+ $ord_c = ord($c);
+
+ return $this->numberInRangeInclusive($ord_c, $ord_zero, $ord_nine);
+ } // end of the "isLower()" function
+
+ /**
+ * Checks if a character is a space one
+ *
+ * @param string $c character to check for
+ *
+ * @return boolean whether the character is a space one or not
+ */
+ public function isSpace($c)
+ {
+ $ord_space = 32; //ord(' ')
+ $ord_tab = 9; //ord('\t')
+ $ord_CR = 13; //ord('\n')
+ $ord_NOBR = 160; //ord('U+00A0);
+ $ord_c = ord($c);
+
+ return ($ord_c == $ord_space
+ || $ord_c == $ord_NOBR
+ || $this->numberInRangeInclusive($ord_c, $ord_tab, $ord_CR));
+ } // end of the "isSpace()" function
+
+ /**
+ * Checks if a character is an hexadecimal digit
+ *
+ * @param string $c character to check for
+ *
+ * @return boolean whether the character is an hexadecimal digit or not
+ */
+ public function isHexDigit($c)
+ {
+ $ord_Aupper = 65; //ord('A');
+ $ord_Fupper = 70; //ord('F');
+ $ord_Alower = 97; //ord('a');
+ $ord_Flower = 102; //ord('f');
+ $ord_zero = 48; //ord('0');
+ $ord_nine = 57; //ord('9');
+ $ord_c = ord($c);
+
+ return ($this->numberInRangeInclusive($ord_c, $ord_zero, $ord_nine)
+ || $this->numberInRangeInclusive($ord_c, $ord_Aupper, $ord_Fupper)
+ || $this->numberInRangeInclusive($ord_c, $ord_Alower, $ord_Flower));
+ } // end of the "isHexDigit()" function
+}
+?>
diff --git a/libraries/StringType.int.php b/libraries/StringType.int.php
new file mode 100644
index 0000000000..dfcb7de8ec
--- /dev/null
+++ b/libraries/StringType.int.php
@@ -0,0 +1,85 @@
+<?php
+/**
+ * Defines a set of specialized string functions.
+ *
+ * @package PhpMyAdmin-String
+ * @todo May be move this into file of its own
+ */
+interface PMA_StringType
+{
+ /**
+ * Checks if a character is an alphanumeric one
+ *
+ * @param string $c character to check for
+ *
+ * @return boolean whether the character is an alphanumeric one or not
+ */
+ public function isAlnum($c);
+
+ /**
+ * Checks if a character is an alphabetic one
+ *
+ * @param string $c character to check for
+ *
+ * @return boolean whether the character is an alphabetic one or not
+ */
+ public function isAlpha($c);
+
+ /**
+ * Checks if a character is a digit
+ *
+ * @param string $c character to check for
+ *
+ * @return boolean whether the character is a digit or not
+ */
+ public function isDigit($c);
+
+ /**
+ * Checks if a character is an upper alphabetic one
+ *
+ * @param string $c character to check for
+ *
+ * @return boolean whether the character is an upper alphabetic one or not
+ */
+ public function isUpper($c);
+
+
+ /**
+ * Checks if a character is a lower alphabetic one
+ *
+ * @param string $c character to check for
+ *
+ * @return boolean whether the character is a lower alphabetic one or not
+ */
+ public function isLower($c);
+
+ /**
+ * Checks if a character is a space one
+ *
+ * @param string $c character to check for
+ *
+ * @return boolean whether the character is a space one or not
+ */
+ public function isSpace($c);
+
+ /**
+ * Checks if a character is an hexadecimal digit
+ *
+ * @param string $c character to check for
+ *
+ * @return boolean whether the character is an hexadecimal digit or not
+ */
+ public function isHexDigit($c);
+
+ /**
+ * Checks if a number is in a range
+ *
+ * @param integer $num number to check for
+ * @param integer $lower lower bound
+ * @param integer $upper upper bound
+ *
+ * @return boolean whether the number is in the range or not
+ */
+ public function numberInRangeInclusive($num, $lower, $upper);
+}
+?> \ No newline at end of file
diff --git a/libraries/Table.class.php b/libraries/Table.class.php
new file mode 100644
index 0000000000..f7dc1ae306
--- /dev/null
+++ b/libraries/Table.class.php
@@ -0,0 +1,1713 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Holds the PMA_Table class
+ *
+ * @package PhpMyAdmin
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ * Handles everything related to tables
+ *
+ * @todo make use of PMA_Message and PMA_Error
+ * @package PhpMyAdmin
+ */
+class PMA_Table
+{
+ /**
+ * UI preferences properties
+ */
+ const PROP_SORTED_COLUMN = 'sorted_col';
+ const PROP_COLUMN_ORDER = 'col_order';
+ const PROP_COLUMN_VISIB = 'col_visib';
+
+ static $cache = array();
+
+ /**
+ * @var string table name
+ */
+ var $name = '';
+
+ /**
+ * @var string database name
+ */
+ var $db_name = '';
+
+ /**
+ * @var string engine (innodb, myisam, bdb, ...)
+ */
+ var $engine = '';
+
+ /**
+ * @var string type (view, base table, system view)
+ */
+ var $type = '';
+
+ /**
+ * @var array settings
+ */
+ var $settings = array();
+
+ /**
+ * @var array UI preferences
+ */
+ var $uiprefs;
+
+ /**
+ * @var array errors occurred
+ */
+ var $errors = array();
+
+ /**
+ * @var array messages
+ */
+ var $messages = array();
+
+ /**
+ * Constructor
+ *
+ * @param string $table_name table name
+ * @param string $db_name database name
+ */
+ function __construct($table_name, $db_name)
+ {
+ $this->setName($table_name);
+ $this->setDbName($db_name);
+ }
+
+ /**
+ * returns table name
+ *
+ * @see PMA_Table::getName()
+ * @return string table name
+ */
+ function __toString()
+ {
+ return $this->getName();
+ }
+
+ /**
+ * return the last error
+ *
+ * @return string the last error
+ */
+ function getLastError()
+ {
+ return end($this->errors);
+ }
+
+ /**
+ * return the last message
+ *
+ * @return string the last message
+ */
+ function getLastMessage()
+ {
+ return end($this->messages);
+ }
+
+ /**
+ * sets table name
+ *
+ * @param string $table_name new table name
+ *
+ * @return void
+ */
+ function setName($table_name)
+ {
+ $this->name = $table_name;
+ }
+
+ /**
+ * returns table name
+ *
+ * @param boolean $backquoted whether to quote name with backticks ``
+ *
+ * @return string table name
+ */
+ function getName($backquoted = false)
+ {
+ if ($backquoted) {
+ return PMA_Util::backquote($this->name);
+ }
+ return $this->name;
+ }
+
+ /**
+ * sets database name for this table
+ *
+ * @param string $db_name database name
+ *
+ * @return void
+ */
+ function setDbName($db_name)
+ {
+ $this->db_name = $db_name;
+ }
+
+ /**
+ * returns database name for this table
+ *
+ * @param boolean $backquoted whether to quote name with backticks ``
+ *
+ * @return string database name for this table
+ */
+ function getDbName($backquoted = false)
+ {
+ if ($backquoted) {
+ return PMA_Util::backquote($this->db_name);
+ }
+ return $this->db_name;
+ }
+
+ /**
+ * returns full name for table, including database name
+ *
+ * @param boolean $backquoted whether to quote name with backticks ``
+ *
+ * @return string
+ */
+ function getFullName($backquoted = false)
+ {
+ return $this->getDbName($backquoted) . '.'
+ . $this->getName($backquoted);
+ }
+
+ /**
+ * returns whether the table is actually a view
+ *
+ * @param string $db database
+ * @param string $table table
+ *
+ * @return boolean whether the given is a view
+ */
+ static public function isView($db = null, $table = null)
+ {
+ if (empty($db) || empty($table)) {
+ return false;
+ }
+
+ // use cached data or load information with SHOW command
+ if (isset(PMA_Table::$cache[$db][$table])
+ ) {
+ $type = PMA_Table::sGetStatusInfo($db, $table, 'TABLE_TYPE');
+ return $type == 'VIEW';
+ }
+
+ // query information_schema
+ $result = $GLOBALS['dbi']->fetchResult(
+ "SELECT TABLE_NAME
+ FROM information_schema.VIEWS
+ WHERE TABLE_SCHEMA = '" . PMA_Util::sqlAddSlashes($db) . "'
+ AND TABLE_NAME = '" . PMA_Util::sqlAddSlashes($table) . "'"
+ );
+ return $result ? true : false;
+ }
+
+ /**
+ * Returns whether the table is actually an updatable view
+ *
+ * @param string $db database
+ * @param string $table table
+ *
+ * @return boolean whether the given is an updatable view
+ */
+ static public function isUpdatableView($db = null, $table = null)
+ {
+ if (empty($db) || empty($table)) {
+ return false;
+ }
+
+ $result = $GLOBALS['dbi']->fetchResult(
+ "SELECT TABLE_NAME
+ FROM information_schema.VIEWS
+ WHERE TABLE_SCHEMA = '" . PMA_Util::sqlAddSlashes($db) . "'
+ AND TABLE_NAME = '" . PMA_Util::sqlAddSlashes($table) . "'
+ AND IS_UPDATABLE = 'YES'"
+ );
+ return $result ? true : false;
+ }
+
+ /**
+ * Returns the analysis of 'SHOW CREATE TABLE' query for the table.
+ * In case of a view, the values are taken from the information_schema.
+ *
+ * @param string $db database
+ * @param string $table table
+ *
+ * @return array analysis of 'SHOW CREATE TABLE' query for the table
+ */
+ static public function analyzeStructure($db = null, $table = null)
+ {
+ if (empty($db) || empty($table)) {
+ return false;
+ }
+
+ $analyzed_sql = array();
+ if (self::isView($db, $table)) {
+ // For a view, 'SHOW CREATE TABLE' returns the definition,
+ // but the structure of the view. So, we try to mock
+ // the result of analyzing 'SHOW CREATE TABLE' query.
+ $analyzed_sql[0] = array();
+ $analyzed_sql[0]['create_table_fields'] = array();
+
+ $results = $GLOBALS['dbi']->fetchResult(
+ "SELECT COLUMN_NAME, DATA_TYPE
+ FROM information_schema.COLUMNS
+ WHERE TABLE_SCHEMA = '" . PMA_Util::sqlAddSlashes($db) . "'
+ AND TABLE_NAME = '" . PMA_Util::sqlAddSlashes($table) . "'"
+ );
+ foreach ($results as $result) {
+ $analyzed_sql[0]['create_table_fields'][$result['COLUMN_NAME']]
+ = array('type' => strtoupper($result['DATA_TYPE']));
+ }
+ } else {
+ $show_create_table = $GLOBALS['dbi']->fetchValue(
+ 'SHOW CREATE TABLE '
+ . PMA_Util::backquote($db)
+ . '.' . PMA_Util::backquote($table),
+ 0,
+ 1
+ );
+ $analyzed_sql = PMA_SQP_analyze(PMA_SQP_parse($show_create_table));
+ }
+ return $analyzed_sql;
+ }
+
+ /**
+ * sets given $value for given $param
+ *
+ * @param string $param name
+ * @param mixed $value value
+ *
+ * @return void
+ */
+ function set($param, $value)
+ {
+ $this->settings[$param] = $value;
+ }
+
+ /**
+ * returns value for given setting/param
+ *
+ * @param string $param name for value to return
+ *
+ * @return mixed value for $param
+ */
+ function get($param)
+ {
+ if (isset($this->settings[$param])) {
+ return $this->settings[$param];
+ }
+
+ return null;
+ }
+
+ /**
+ * Checks if this is a merge table
+ *
+ * If the ENGINE of the table is MERGE or MRG_MYISAM (alias),
+ * this is a merge table.
+ *
+ * @param string $db the database name
+ * @param string $table the table name
+ *
+ * @return boolean true if it is a merge table
+ */
+ static public function isMerge($db = null, $table = null)
+ {
+ $engine = null;
+ // if called static, with parameters
+ if (! empty($db) && ! empty($table)) {
+ $engine = PMA_Table::sGetStatusInfo(
+ $db, $table, 'ENGINE', null, true
+ );
+ }
+
+ // did we get engine?
+ if (empty($engine)) {
+ return false;
+ }
+
+ // any of known merge engines?
+ return in_array(strtoupper($engine), array('MERGE', 'MRG_MYISAM'));
+ }
+
+ /**
+ * Returns tooltip for the table
+ * Format : <table_comment> (<number_of_rows>)
+ *
+ * @param string $db database name
+ * @param string $table table name
+ *
+ * @return string tooltip fot the table
+ */
+ static public function sGetToolTip($db, $table)
+ {
+ return PMA_Table::sGetStatusInfo($db, $table, 'Comment')
+ . ' (' . PMA_Table::countRecords($db, $table)
+ . ' ' . __('Rows') . ')';
+ }
+
+ /**
+ * Returns full table status info, or specific if $info provided
+ * this info is collected from information_schema
+ *
+ * @param string $db database name
+ * @param string $table table name
+ * @param string $info specific information to be fetched
+ * @param boolean $force_read read new rather than serving from cache
+ * @param boolean $disable_error if true, disables error message
+ *
+ * @todo DatabaseInterface::getTablesFull needs to be merged
+ * somehow into this class or at least better documented
+ *
+ * @return mixed
+ */
+ static public function sGetStatusInfo($db, $table, $info = null,
+ $force_read = false, $disable_error = false
+ ) {
+ if (! empty($_SESSION['is_multi_query'])) {
+ $disable_error = true;
+ }
+
+ if (! isset(PMA_Table::$cache[$db][$table])
+ || $force_read
+ // sometimes there is only one entry (ExactRows) so
+ // we have to get the table's details
+ || count(PMA_Table::$cache[$db][$table]) == 1
+ ) {
+ $GLOBALS['dbi']->getTablesFull($db, $table);
+ }
+
+ if (! isset(PMA_Table::$cache[$db][$table])) {
+ // happens when we enter the table creation dialog
+ // or when we really did not get any status info, for example
+ // when $table == 'TABLE_NAMES' after the user tried SHOW TABLES
+ return '';
+ }
+
+ if (null === $info) {
+ return PMA_Table::$cache[$db][$table];
+ }
+
+ // array_key_exists allows for null values
+ if (!array_key_exists($info, PMA_Table::$cache[$db][$table])) {
+ if (! $disable_error) {
+ trigger_error(
+ __('unknown table status: ') . $info,
+ E_USER_WARNING
+ );
+ }
+ return false;
+ }
+
+ return PMA_Table::$cache[$db][$table][$info];
+ }
+
+ /**
+ * generates column specification for ALTER or CREATE TABLE syntax
+ *
+ * @param string $name name
+ * @param string $type type ('INT', 'VARCHAR', 'BIT', ...)
+ * @param string $index index
+ * @param string $length length ('2', '5,2', '', ...)
+ * @param string $attribute attribute
+ * @param string $collation collation
+ * @param bool|string $null with 'NULL' or 'NOT NULL'
+ * @param string $default_type whether default is CURRENT_TIMESTAMP,
+ * NULL, NONE, USER_DEFINED
+ * @param string $default_value default value for USER_DEFINED
+ * default type
+ * @param string $extra 'AUTO_INCREMENT'
+ * @param string $comment field comment
+ * @param array &$field_primary list of fields for PRIMARY KEY
+ * @param string $move_to new position for column
+ *
+ * @todo move into class PMA_Column
+ * @todo on the interface, some js to clear the default value when the
+ * default current_timestamp is checked
+ *
+ * @return string field specification
+ */
+ static function generateFieldSpec($name, $type, $index, $length = '',
+ $attribute = '', $collation = '', $null = false,
+ $default_type = 'USER_DEFINED', $default_value = '', $extra = '',
+ $comment = '', &$field_primary = null, $move_to = ''
+ ) {
+ $is_timestamp = strpos(strtoupper($type), 'TIMESTAMP') !== false;
+
+ $query = PMA_Util::backquote($name) . ' ' . $type;
+
+ // allow the possibility of a length for TIME, DATETIME and TIMESTAMP
+ // (will work on MySQL >= 5.6.4)
+ if ($length != ''
+ && ! preg_match(
+ '@^(DATE|TINYBLOB|TINYTEXT|BLOB|TEXT|'
+ . 'MEDIUMBLOB|MEDIUMTEXT|LONGBLOB|LONGTEXT|SERIAL|BOOLEAN|DOUBLE|FLOAT|UUID)$@i',
+ $type
+ )
+ ) {
+ $query .= '(' . $length . ')';
+ }
+
+ if ($attribute != '') {
+ $query .= ' ' . $attribute;
+ }
+
+ $matches = preg_match(
+ '@^(TINYTEXT|TEXT|MEDIUMTEXT|LONGTEXT|VARCHAR|CHAR|ENUM|SET)$@i',
+ $type
+ );
+ if (! empty($collation) && $collation != 'NULL' && $matches) {
+ $query .= PMA_generateCharsetQueryPart($collation);
+ }
+
+ if ($null !== false) {
+ if ($null == 'NULL') {
+ $query .= ' NULL';
+ } else {
+ $query .= ' NOT NULL';
+ }
+ }
+
+ switch ($default_type) {
+ case 'USER_DEFINED' :
+ if ($is_timestamp && $default_value === '0') {
+ // a TIMESTAMP does not accept DEFAULT '0'
+ // but DEFAULT 0 works
+ $query .= ' DEFAULT 0';
+ } elseif ($type == 'BIT') {
+ $query .= ' DEFAULT b\''
+ . preg_replace('/[^01]/', '0', $default_value)
+ . '\'';
+ } elseif ($type == 'BOOLEAN') {
+ if (preg_match('/^1|T|TRUE|YES$/i', $default_value)) {
+ $query .= ' DEFAULT TRUE';
+ } elseif (preg_match('/^0|F|FALSE|NO$/i', $default_value)) {
+ $query .= ' DEFAULT FALSE';
+ } else {
+ // Invalid BOOLEAN value
+ $query .= ' DEFAULT \''
+ . PMA_Util::sqlAddSlashes($default_value) . '\'';
+ }
+ } else {
+ $query .= ' DEFAULT \''
+ . PMA_Util::sqlAddSlashes($default_value) . '\'';
+ }
+ break;
+ case 'NULL' :
+ //If user uncheck null checkbox and not change default value null,
+ //default value will be ignored.
+ if ($null !== false && $null != 'NULL') {
+ break;
+ }
+ case 'CURRENT_TIMESTAMP' :
+ $query .= ' DEFAULT ' . $default_type;
+ break;
+ case 'NONE' :
+ default :
+ break;
+ }
+
+ if (!empty($extra)) {
+ $query .= ' ' . $extra;
+ // Force an auto_increment field to be part of the primary key
+ // even if user did not tick the PK box;
+ if ($extra == 'AUTO_INCREMENT') {
+ $primary_cnt = count($field_primary);
+ if (1 == $primary_cnt) {
+ for ($j = 0; $j < $primary_cnt; $j++) {
+ if ($field_primary[$j] == $index) {
+ break;
+ }
+ }
+ if (isset($field_primary[$j]) && $field_primary[$j] == $index) {
+ $query .= ' PRIMARY KEY';
+ unset($field_primary[$j]);
+ }
+ } else {
+ // but the PK could contain other columns so do not append
+ // a PRIMARY KEY clause, just add a member to $field_primary
+ $found_in_pk = false;
+ for ($j = 0; $j < $primary_cnt; $j++) {
+ if ($field_primary[$j] == $index) {
+ $found_in_pk = true;
+ break;
+ }
+ } // end for
+ if (! $found_in_pk) {
+ $field_primary[] = $index;
+ }
+ }
+ } // end if (auto_increment)
+ }
+ if (!empty($comment)) {
+ $query .= " COMMENT '" . PMA_Util::sqlAddSlashes($comment) . "'";
+ }
+
+ // move column
+ if ($move_to == '-first') { // dash can't appear as part of column name
+ $query .= ' FIRST';
+ } elseif ($move_to != '') {
+ $query .= ' AFTER ' . PMA_Util::backquote($move_to);
+ }
+ return $query;
+ } // end function
+
+ /**
+ * Counts and returns (or displays) the number of records in a table
+ *
+ * @param string $db the current database name
+ * @param string $table the current table name
+ * @param bool $force_exact whether to force an exact count
+ * @param bool $is_view whether the table is a view
+ *
+ * @return mixed the number of records if "retain" param is true,
+ * otherwise true
+ */
+ static public function countRecords($db, $table, $force_exact = false,
+ $is_view = null
+ ) {
+ if (isset(PMA_Table::$cache[$db][$table]['ExactRows'])) {
+ $row_count = PMA_Table::$cache[$db][$table]['ExactRows'];
+ } else {
+ $row_count = false;
+
+ if (null === $is_view) {
+ $is_view = PMA_Table::isView($db, $table);
+ }
+
+ if (! $force_exact) {
+ if (! isset(PMA_Table::$cache[$db][$table]['Rows']) && ! $is_view) {
+ $tmp_tables = $GLOBALS['dbi']->getTablesFull($db, $table);
+ if (isset($tmp_tables[$table])) {
+ PMA_Table::$cache[$db][$table] = $tmp_tables[$table];
+ }
+ }
+ if (isset(PMA_Table::$cache[$db][$table]['Rows'])) {
+ $row_count = PMA_Table::$cache[$db][$table]['Rows'];
+ } else {
+ $row_count = false;
+ }
+ }
+
+ // for a VIEW, $row_count is always false at this point
+ if (false === $row_count
+ || $row_count < $GLOBALS['cfg']['MaxExactCount']
+ ) {
+ // Make an exception for views in I_S and D_D schema in
+ // Drizzle, as these map to in-memory data and should execute
+ // fast enough
+ if (! $is_view
+ || (PMA_DRIZZLE && $GLOBALS['dbi']->isSystemSchema($db))
+ ) {
+ $row_count = $GLOBALS['dbi']->fetchValue(
+ 'SELECT COUNT(*) FROM ' . PMA_Util::backquote($db) . '.'
+ . PMA_Util::backquote($table)
+ );
+ } else {
+ // For complex views, even trying to get a partial record
+ // count could bring down a server, so we offer an
+ // alternative: setting MaxExactCountViews to 0 will bypass
+ // completely the record counting for views
+
+ if ($GLOBALS['cfg']['MaxExactCountViews'] == 0) {
+ $row_count = 0;
+ } else {
+ // Counting all rows of a VIEW could be too long,
+ // so use a LIMIT clause.
+ // Use try_query because it can fail (when a VIEW is
+ // based on a table that no longer exists)
+ $result = $GLOBALS['dbi']->tryQuery(
+ 'SELECT 1 FROM ' . PMA_Util::backquote($db) . '.'
+ . PMA_Util::backquote($table) . ' LIMIT '
+ . $GLOBALS['cfg']['MaxExactCountViews'],
+ null,
+ PMA_DatabaseInterface::QUERY_STORE
+ );
+ if (!$GLOBALS['dbi']->getError()) {
+ $row_count = $GLOBALS['dbi']->numRows($result);
+ $GLOBALS['dbi']->freeResult($result);
+ }
+ }
+ }
+ if ($row_count) {
+ PMA_Table::$cache[$db][$table]['ExactRows'] = $row_count;
+ }
+ }
+ }
+
+ return $row_count;
+ } // end of the 'PMA_Table::countRecords()' function
+
+ /**
+ * Generates column specification for ALTER syntax
+ *
+ * @param string $oldcol old column name
+ * @param string $newcol new column name
+ * @param string $type type ('INT', 'VARCHAR', 'BIT', ...)
+ * @param string $length length ('2', '5,2', '', ...)
+ * @param string $attribute attribute
+ * @param string $collation collation
+ * @param bool|string $null with 'NULL' or 'NOT NULL'
+ * @param string $default_type whether default is CURRENT_TIMESTAMP,
+ * NULL, NONE, USER_DEFINED
+ * @param string $default_value default value for USER_DEFINED default
+ * type
+ * @param string $extra 'AUTO_INCREMENT'
+ * @param string $comment field comment
+ * @param array &$field_primary list of fields for PRIMARY KEY
+ * @param string $index index
+ * @param string $move_to new position for column
+ *
+ * @see PMA_Table::generateFieldSpec()
+ *
+ * @return string field specification
+ */
+ static public function generateAlter($oldcol, $newcol, $type, $length,
+ $attribute, $collation, $null, $default_type, $default_value,
+ $extra, $comment, &$field_primary, $index, $move_to
+ ) {
+ return PMA_Util::backquote($oldcol) . ' '
+ . PMA_Table::generateFieldSpec(
+ $newcol, $type, $index, $length, $attribute,
+ $collation, $null, $default_type, $default_value, $extra,
+ $comment, $field_primary, $move_to
+ );
+ } // end function
+
+ /**
+ * Inserts existing entries in a PMA_* table by reading a value from an old
+ * entry
+ *
+ * @param string $work The array index, which Relation feature to
+ * check ('relwork', 'commwork', ...)
+ * @param string $pma_table The array index, which PMA-table to update
+ * ('bookmark', 'relation', ...)
+ * @param array $get_fields Which fields will be SELECT'ed from the old entry
+ * @param array $where_fields Which fields will be used for the WHERE query
+ * (array('FIELDNAME' => 'FIELDVALUE'))
+ * @param array $new_fields Which fields will be used as new VALUES.
+ * These are the important keys which differ
+ * from the old entry
+ * (array('FIELDNAME' => 'NEW FIELDVALUE'))
+ *
+ * @global relation variable
+ *
+ * @return int|true
+ */
+ static public function duplicateInfo($work, $pma_table, $get_fields,
+ $where_fields, $new_fields
+ ) {
+ $last_id = -1;
+
+ if (isset($GLOBALS['cfgRelation']) && $GLOBALS['cfgRelation'][$work]) {
+ $select_parts = array();
+ $row_fields = array();
+ foreach ($get_fields as $get_field) {
+ $select_parts[] = PMA_Util::backquote($get_field);
+ $row_fields[$get_field] = 'cc';
+ }
+
+ $where_parts = array();
+ foreach ($where_fields as $_where => $_value) {
+ $where_parts[] = PMA_Util::backquote($_where) . ' = \''
+ . PMA_Util::sqlAddSlashes($_value) . '\'';
+ }
+
+ $new_parts = array();
+ $new_value_parts = array();
+ foreach ($new_fields as $_where => $_value) {
+ $new_parts[] = PMA_Util::backquote($_where);
+ $new_value_parts[] = PMA_Util::sqlAddSlashes($_value);
+ }
+
+ $table_copy_query = '
+ SELECT ' . implode(', ', $select_parts) . '
+ FROM ' . PMA_Util::backquote($GLOBALS['cfgRelation']['db']) . '.'
+ . PMA_Util::backquote($GLOBALS['cfgRelation'][$pma_table]) . '
+ WHERE ' . implode(' AND ', $where_parts);
+
+ // must use PMA_DatabaseInterface::QUERY_STORE here, since we execute
+ // another query inside the loop
+ $table_copy_rs = PMA_queryAsControlUser(
+ $table_copy_query, true, PMA_DatabaseInterface::QUERY_STORE
+ );
+
+ while ($table_copy_row = @$GLOBALS['dbi']->fetchAssoc($table_copy_rs)) {
+ $value_parts = array();
+ foreach ($table_copy_row as $_key => $_val) {
+ if (isset($row_fields[$_key]) && $row_fields[$_key] == 'cc') {
+ $value_parts[] = PMA_Util::sqlAddSlashes($_val);
+ }
+ }
+
+ $new_table_query = 'INSERT IGNORE INTO '
+ . PMA_Util::backquote($GLOBALS['cfgRelation']['db'])
+ . '.'
+ . PMA_Util::backquote($GLOBALS['cfgRelation'][$pma_table])
+ . ' (' . implode(', ', $select_parts)
+ . ', ' . implode(', ', $new_parts)
+ . ') VALUES (\''
+ . implode('\', \'', $value_parts) . '\', \''
+ . implode('\', \'', $new_value_parts) . '\')';
+
+ PMA_queryAsControlUser($new_table_query);
+ $last_id = $GLOBALS['dbi']->insertId();
+ } // end while
+
+ $GLOBALS['dbi']->freeResult($table_copy_rs);
+
+ return $last_id;
+ }
+
+ return true;
+ } // end of 'PMA_Table::duplicateInfo()' function
+
+ /**
+ * Copies or renames table
+ *
+ * @param string $source_db source database
+ * @param string $source_table source table
+ * @param string $target_db target database
+ * @param string $target_table target table
+ * @param string $what what to be moved or copied (data, dataonly)
+ * @param bool $move whether to move
+ * @param string $mode mode
+ *
+ * @return bool true if success, false otherwise
+ */
+ static public function moveCopy($source_db, $source_table, $target_db,
+ $target_table, $what, $move, $mode
+ ) {
+ global $err_url;
+
+ /* Try moving table directly */
+ if ($move && $what == 'data') {
+ $tbl = new PMA_Table($source_table, $source_db);
+ $result = $tbl->rename($target_table, $target_db);
+ if ($result) {
+ $GLOBALS['message'] = $tbl->getLastMessage();
+ return true;
+ }
+ }
+
+ // set export settings we need
+ $GLOBALS['sql_backquotes'] = 1;
+ $GLOBALS['asfile'] = 1;
+
+ // Ensure the target is valid
+ if (! $GLOBALS['pma']->databases->exists($source_db, $target_db)) {
+ if (! $GLOBALS['pma']->databases->exists($source_db)) {
+ $GLOBALS['message'] = PMA_Message::rawError(
+ sprintf(
+ __('Source database `%s` was not found!'),
+ htmlspecialchars($source_db)
+ )
+ );
+ }
+ if (! $GLOBALS['pma']->databases->exists($target_db)) {
+ $GLOBALS['message'] = PMA_Message::rawError(
+ sprintf(
+ __('Target database `%s` was not found!'),
+ htmlspecialchars($target_db)
+ )
+ );
+ }
+ return false;
+ }
+
+ $source = PMA_Util::backquote($source_db)
+ . '.' . PMA_Util::backquote($source_table);
+ if (! isset($target_db) || ! strlen($target_db)) {
+ $target_db = $source_db;
+ }
+
+ // Doing a select_db could avoid some problems with replicated databases,
+ // when moving table from replicated one to not replicated one
+ $GLOBALS['dbi']->selectDb($target_db);
+
+ $target = PMA_Util::backquote($target_db)
+ . '.' . PMA_Util::backquote($target_table);
+
+ // do not create the table if dataonly
+ if ($what != 'dataonly') {
+ include_once "libraries/plugin_interface.lib.php";
+ // get Export SQL instance
+ $export_sql_plugin = PMA_getPlugin(
+ "export",
+ "sql",
+ 'libraries/plugins/export/',
+ array(
+ 'export_type' => 'table',
+ 'single_table' => false,
+ )
+ );
+
+ $no_constraints_comments = true;
+ $GLOBALS['sql_constraints_query'] = '';
+ // set the value of global sql_auto_increment variable
+ if (isset($_POST['sql_auto_increment'])) {
+ $GLOBALS['sql_auto_increment'] = $_POST['sql_auto_increment'];
+ }
+
+ $sql_structure = $export_sql_plugin->getTableDef(
+ $source_db, $source_table, "\n", $err_url, false, false
+ );
+ unset($no_constraints_comments);
+ $parsed_sql = PMA_SQP_parse($sql_structure);
+ $analyzed_sql = PMA_SQP_analyze($parsed_sql);
+ $i = 0;
+ if (empty($analyzed_sql[0]['create_table_fields'])) {
+ // this is not a CREATE TABLE, so find the first VIEW
+ $target_for_view = PMA_Util::backquote($target_db);
+ while (true) {
+ if ($parsed_sql[$i]['type'] == 'alpha_reservedWord'
+ && $parsed_sql[$i]['data'] == 'VIEW'
+ ) {
+ break;
+ }
+ $i++;
+ }
+ }
+ unset($analyzed_sql);
+ if (PMA_DRIZZLE) {
+ $table_delimiter = 'quote_backtick';
+ } else {
+ $server_sql_mode = $GLOBALS['dbi']->fetchValue(
+ "SHOW VARIABLES LIKE 'sql_mode'",
+ 0,
+ 1
+ );
+ // ANSI_QUOTES might be a subset of sql_mode, for example
+ // REAL_AS_FLOAT,PIPES_AS_CONCAT,ANSI_QUOTES,IGNORE_SPACE,ANSI
+ if (false !== strpos($server_sql_mode, 'ANSI_QUOTES')) {
+ $table_delimiter = 'quote_double';
+ } else {
+ $table_delimiter = 'quote_backtick';
+ }
+ unset($server_sql_mode);
+ }
+
+ /* Find table name in query and replace it */
+ while ($parsed_sql[$i]['type'] != $table_delimiter) {
+ $i++;
+ }
+
+ /* no need to backquote() */
+ if (isset($target_for_view)) {
+ // this a view definition; we just found the first db name
+ // that follows DEFINER VIEW
+ // so change it for the new db name
+ $parsed_sql[$i]['data'] = $target_for_view;
+ // then we have to find all references to the source db
+ // and change them to the target db, ensuring we stay into
+ // the $parsed_sql limits
+ $last = $parsed_sql['len'] - 1;
+ $backquoted_source_db = PMA_Util::backquote($source_db);
+ for (++$i; $i <= $last; $i++) {
+ if ($parsed_sql[$i]['type'] == $table_delimiter
+ && $parsed_sql[$i]['data'] == $backquoted_source_db
+ && $parsed_sql[$i - 1]['type'] != 'punct_qualifier'
+ ) {
+ $parsed_sql[$i]['data'] = $target_for_view;
+ }
+ }
+ unset($last,$backquoted_source_db);
+ } else {
+ $parsed_sql[$i]['data'] = $target;
+ }
+
+ /* Generate query back */
+ $sql_structure = PMA_SQP_format($parsed_sql, 'query_only');
+ // If table exists, and 'add drop table' is selected: Drop it!
+ $drop_query = '';
+ if (isset($_REQUEST['drop_if_exists'])
+ && $_REQUEST['drop_if_exists'] == 'true'
+ ) {
+ if (PMA_Table::isView($target_db, $target_table)) {
+ $drop_query = 'DROP VIEW';
+ } else {
+ $drop_query = 'DROP TABLE';
+ }
+ $drop_query .= ' IF EXISTS '
+ . PMA_Util::backquote($target_db) . '.'
+ . PMA_Util::backquote($target_table);
+ $GLOBALS['dbi']->query($drop_query);
+
+ $GLOBALS['sql_query'] .= "\n" . $drop_query . ';';
+
+ // If an existing table gets deleted, maintain any
+ // entries for the PMA_* tables
+ $maintain_relations = true;
+ }
+
+ @$GLOBALS['dbi']->query($sql_structure);
+ $GLOBALS['sql_query'] .= "\n" . $sql_structure . ';';
+
+ if (($move || isset($GLOBALS['add_constraints']))
+ && !empty($GLOBALS['sql_constraints_query'])
+ ) {
+ $parsed_sql = PMA_SQP_parse($GLOBALS['sql_constraints_query']);
+ $i = 0;
+
+ // find the first $table_delimiter, it must be the source
+ // table name
+ while ($parsed_sql[$i]['type'] != $table_delimiter) {
+ $i++;
+ // maybe someday we should guard against going over limit
+ //if ($i == $parsed_sql['len']) {
+ // break;
+ //}
+ }
+
+ // replace it by the target table name, no need
+ // to backquote()
+ $parsed_sql[$i]['data'] = $target;
+
+ // now we must remove all $table_delimiter that follow a
+ // CONSTRAINT keyword, because a constraint name must be
+ // unique in a db
+
+ $cnt = $parsed_sql['len'] - 1;
+
+ for ($j = $i; $j < $cnt; $j++) {
+ if ($parsed_sql[$j]['type'] == 'alpha_reservedWord'
+ && strtoupper($parsed_sql[$j]['data']) == 'CONSTRAINT'
+ ) {
+ if ($parsed_sql[$j+1]['type'] == $table_delimiter) {
+ $parsed_sql[$j+1]['data'] = '';
+ }
+ }
+ }
+
+ // Generate query back
+ $GLOBALS['sql_constraints_query'] = PMA_SQP_format(
+ $parsed_sql, 'query_only'
+ );
+ if ($mode == 'one_table') {
+ $GLOBALS['dbi']->query($GLOBALS['sql_constraints_query']);
+ }
+ $GLOBALS['sql_query'] .= "\n" . $GLOBALS['sql_constraints_query'];
+ if ($mode == 'one_table') {
+ unset($GLOBALS['sql_constraints_query']);
+ }
+ }
+ } else {
+ $GLOBALS['sql_query'] = '';
+ }
+
+ // Copy the data unless this is a VIEW
+ if (($what == 'data' || $what == 'dataonly')
+ && ! PMA_Table::isView($target_db, $target_table)
+ ) {
+ $sql_set_mode = "SET SQL_MODE='NO_AUTO_VALUE_ON_ZERO'";
+ $GLOBALS['dbi']->query($sql_set_mode);
+ $GLOBALS['sql_query'] .= "\n\n" . $sql_set_mode . ';';
+
+ $sql_insert_data = 'INSERT INTO ' . $target
+ . ' SELECT * FROM ' . $source;
+ $GLOBALS['dbi']->query($sql_insert_data);
+ $GLOBALS['sql_query'] .= "\n\n" . $sql_insert_data . ';';
+ }
+
+ $GLOBALS['cfgRelation'] = PMA_getRelationsParam();
+
+ // Drops old table if the user has requested to move it
+ if ($move) {
+
+ // This could avoid some problems with replicated databases, when
+ // moving table from replicated one to not replicated one
+ $GLOBALS['dbi']->selectDb($source_db);
+
+ if (PMA_Table::isView($source_db, $source_table)) {
+ $sql_drop_query = 'DROP VIEW';
+ } else {
+ $sql_drop_query = 'DROP TABLE';
+ }
+ $sql_drop_query .= ' ' . $source;
+ $GLOBALS['dbi']->query($sql_drop_query);
+
+ // Renable table in configuration storage
+ PMA_REL_renameTable(
+ $source_db, $target_db,
+ $source_table, $target_table
+ );
+
+ $GLOBALS['sql_query'] .= "\n\n" . $sql_drop_query . ';';
+ // end if ($move)
+ } else {
+ // we are copying
+ // Create new entries as duplicates from old PMA DBs
+ if ($what != 'dataonly' && ! isset($maintain_relations)) {
+ if ($GLOBALS['cfgRelation']['commwork']) {
+ // Get all comments and MIME-Types for current table
+ $comments_copy_rs = PMA_queryAsControlUser(
+ 'SELECT column_name, comment'
+ . ($GLOBALS['cfgRelation']['mimework']
+ ? ', mimetype, transformation, transformation_options'
+ : '')
+ . ' FROM '
+ . PMA_Util::backquote($GLOBALS['cfgRelation']['db'])
+ . '.'
+ . PMA_Util::backquote($GLOBALS['cfgRelation']['column_info'])
+ . ' WHERE '
+ . ' db_name = \''
+ . PMA_Util::sqlAddSlashes($source_db) . '\''
+ . ' AND '
+ . ' table_name = \''
+ . PMA_Util::sqlAddSlashes($source_table) . '\''
+ );
+
+ // Write every comment as new copied entry. [MIME]
+ while ($comments_copy_row = $GLOBALS['dbi']->fetchAssoc($comments_copy_rs)) {
+ $new_comment_query = 'REPLACE INTO ' . PMA_Util::backquote($GLOBALS['cfgRelation']['db']) . '.' . PMA_Util::backquote($GLOBALS['cfgRelation']['column_info'])
+ . ' (db_name, table_name, column_name, comment' . ($GLOBALS['cfgRelation']['mimework'] ? ', mimetype, transformation, transformation_options' : '') . ') '
+ . ' VALUES('
+ . '\'' . PMA_Util::sqlAddSlashes($target_db) . '\','
+ . '\'' . PMA_Util::sqlAddSlashes($target_table) . '\','
+ . '\'' . PMA_Util::sqlAddSlashes($comments_copy_row['column_name']) . '\''
+ . ($GLOBALS['cfgRelation']['mimework'] ? ',\'' . PMA_Util::sqlAddSlashes($comments_copy_row['comment']) . '\','
+ . '\'' . PMA_Util::sqlAddSlashes($comments_copy_row['mimetype']) . '\','
+ . '\'' . PMA_Util::sqlAddSlashes($comments_copy_row['transformation']) . '\','
+ . '\'' . PMA_Util::sqlAddSlashes($comments_copy_row['transformation_options']) . '\'' : '')
+ . ')';
+ PMA_queryAsControlUser($new_comment_query);
+ } // end while
+ $GLOBALS['dbi']->freeResult($comments_copy_rs);
+ unset($comments_copy_rs);
+ }
+
+ // duplicating the bookmarks must not be done here, but
+ // just once per db
+
+ $get_fields = array('display_field');
+ $where_fields = array(
+ 'db_name' => $source_db,
+ 'table_name' => $source_table
+ );
+ $new_fields = array(
+ 'db_name' => $target_db,
+ 'table_name' => $target_table
+ );
+ PMA_Table::duplicateInfo(
+ 'displaywork',
+ 'table_info',
+ $get_fields,
+ $where_fields,
+ $new_fields
+ );
+
+
+ /**
+ * @todo revise this code when we support cross-db relations
+ */
+ $get_fields = array(
+ 'master_field',
+ 'foreign_table',
+ 'foreign_field'
+ );
+ $where_fields = array(
+ 'master_db' => $source_db,
+ 'master_table' => $source_table
+ );
+ $new_fields = array(
+ 'master_db' => $target_db,
+ 'foreign_db' => $target_db,
+ 'master_table' => $target_table
+ );
+ PMA_Table::duplicateInfo(
+ 'relwork',
+ 'relation',
+ $get_fields,
+ $where_fields,
+ $new_fields
+ );
+
+
+ $get_fields = array(
+ 'foreign_field',
+ 'master_table',
+ 'master_field'
+ );
+ $where_fields = array(
+ 'foreign_db' => $source_db,
+ 'foreign_table' => $source_table
+ );
+ $new_fields = array(
+ 'master_db' => $target_db,
+ 'foreign_db' => $target_db,
+ 'foreign_table' => $target_table
+ );
+ PMA_Table::duplicateInfo(
+ 'relwork',
+ 'relation',
+ $get_fields,
+ $where_fields,
+ $new_fields
+ );
+
+
+ $get_fields = array('x', 'y', 'v', 'h');
+ $where_fields = array(
+ 'db_name' => $source_db,
+ 'table_name' => $source_table
+ );
+ $new_fields = array(
+ 'db_name' => $target_db,
+ 'table_name' => $target_table
+ );
+ PMA_Table::duplicateInfo(
+ 'designerwork',
+ 'designer_coords',
+ $get_fields,
+ $where_fields,
+ $new_fields
+ );
+
+ /**
+ * @todo Can't get duplicating PDFs the right way. The
+ * page numbers always get screwed up independently from
+ * duplication because the numbers do not seem to be stored on a
+ * per-database basis. Would the author of pdf support please
+ * have a look at it?
+ *
+ $get_fields = array('page_descr');
+ $where_fields = array('db_name' => $source_db);
+ $new_fields = array('db_name' => $target_db);
+ $last_id = PMA_Table::duplicateInfo(
+ 'pdfwork',
+ 'pdf_pages',
+ $get_fields,
+ $where_fields,
+ $new_fields
+ );
+
+ if (isset($last_id) && $last_id >= 0) {
+ $get_fields = array('x', 'y');
+ $where_fields = array(
+ 'db_name' => $source_db,
+ 'table_name' => $source_table
+ );
+ $new_fields = array(
+ 'db_name' => $target_db,
+ 'table_name' => $target_table,
+ 'pdf_page_number' => $last_id
+ );
+ PMA_Table::duplicateInfo(
+ 'pdfwork',
+ 'table_coords',
+ $get_fields,
+ $where_fields,
+ $new_fields
+ );
+ }
+ */
+ }
+ }
+ return true;
+ }
+
+ /**
+ * checks if given name is a valid table name,
+ * currently if not empty, trailing spaces, '.', '/' and '\'
+ *
+ * @param string $table_name name to check
+ *
+ * @todo add check for valid chars in filename on current system/os
+ * @see http://dev.mysql.com/doc/refman/5.0/en/legal-names.html
+ *
+ * @return boolean whether the string is valid or not
+ */
+ static function isValidName($table_name)
+ {
+ if ($table_name !== trim($table_name)) {
+ // trailing spaces
+ return false;
+ }
+
+ if (! strlen($table_name)) {
+ // zero length
+ return false;
+ }
+
+ if (preg_match('/[.\/\\\\]+/i', $table_name)) {
+ // illegal char . / \
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * renames table
+ *
+ * @param string $new_name new table name
+ * @param string $new_db new database name
+ *
+ * @return bool success
+ */
+ function rename($new_name, $new_db = null)
+ {
+ if (null !== $new_db && $new_db !== $this->getDbName()) {
+ // Ensure the target is valid
+ if (! $GLOBALS['pma']->databases->exists($new_db)) {
+ $this->errors[] = __('Invalid database:') . ' ' . $new_db;
+ return false;
+ }
+ } else {
+ $new_db = $this->getDbName();
+ }
+
+ $new_table = new PMA_Table($new_name, $new_db);
+
+ if ($this->getFullName() === $new_table->getFullName()) {
+ return true;
+ }
+
+ if (! PMA_Table::isValidName($new_name)) {
+ $this->errors[] = __('Invalid table name:') . ' '
+ . $new_table->getFullName();
+ return false;
+ }
+
+ // If the table is moved to a different database drop its triggers first
+ $triggers = $GLOBALS['dbi']->getTriggers(
+ $this->getDbName(), $this->getName(), ''
+ );
+ $handle_triggers = $this->getDbName() != $new_db && $triggers;
+ if ($handle_triggers) {
+ foreach ($triggers as $trigger) {
+ $sql = 'DROP TRIGGER IF EXISTS '
+ . PMA_Util::backquote($this->getDbName())
+ . '.' . PMA_Util::backquote($trigger['name']) . ';';
+ $GLOBALS['dbi']->query($sql);
+ }
+ }
+
+ /*
+ * tested also for a view, in MySQL 5.0.92, 5.1.55 and 5.5.13
+ */
+ $GLOBALS['sql_query'] = '
+ RENAME TABLE ' . $this->getFullName(true) . '
+ TO ' . $new_table->getFullName(true) . ';';
+ // I don't think a specific error message for views is necessary
+ if (! $GLOBALS['dbi']->query($GLOBALS['sql_query'])) {
+ // Restore triggers in the old database
+ if ($handle_triggers) {
+ $GLOBALS['dbi']->selectDb($this->getDbName());
+ foreach ($triggers as $trigger) {
+ $GLOBALS['dbi']->query($trigger['create']);
+ }
+ }
+ $this->errors[] = sprintf(
+ __('Error renaming table %1$s to %2$s'),
+ $this->getFullName(),
+ $new_table->getFullName()
+ );
+ return false;
+ }
+
+ $old_name = $this->getName();
+ $old_db = $this->getDbName();
+ $this->setName($new_name);
+ $this->setDbName($new_db);
+
+ // Renable table in configuration storage
+ PMA_REL_renameTable(
+ $old_db, $new_db,
+ $old_name, $new_name
+ );
+
+ $this->messages[] = sprintf(
+ __('Table %1$s has been renamed to %2$s.'),
+ htmlspecialchars($old_name),
+ htmlspecialchars($new_name)
+ );
+ return true;
+ }
+
+ /**
+ * Get all unique columns
+ *
+ * returns an array with all columns with unqiue content, in fact these are
+ * all columns being single indexed in PRIMARY or UNIQUE
+ *
+ * e.g.
+ * - PRIMARY(id) // id
+ * - UNIQUE(name) // name
+ * - PRIMARY(fk_id1, fk_id2) // NONE
+ * - UNIQUE(x,y) // NONE
+ *
+ * @param bool $backquoted whether to quote name with backticks ``
+ * @param bool $fullName whether to include full name of the table as a prefix
+ *
+ * @return array
+ */
+ public function getUniqueColumns($backquoted = true, $fullName = true)
+ {
+ $sql = $GLOBALS['dbi']->getTableIndexesSql(
+ $this->getDbName(),
+ $this->getName(),
+ 'Non_unique = 0'
+ );
+ $uniques = $GLOBALS['dbi']->fetchResult(
+ $sql,
+ array('Key_name', null),
+ 'Column_name'
+ );
+
+ $return = array();
+ foreach ($uniques as $index) {
+ if (count($index) > 1) {
+ continue;
+ }
+ $return[] = ($fullName ? $this->getFullName($backquoted) . '.' : '')
+ . ($backquoted ? PMA_Util::backquote($index[0]) : $index[0]);
+ }
+
+ return $return;
+ }
+
+ /**
+ * Get all indexed columns
+ *
+ * returns an array with all columns make use of an index, in fact only
+ * first columns in an index
+ *
+ * e.g. index(col1, col2) would only return col1
+ *
+ * @param bool $backquoted whether to quote name with backticks ``
+ *
+ * @return array
+ */
+ public function getIndexedColumns($backquoted = true)
+ {
+ $sql = $GLOBALS['dbi']->getTableIndexesSql(
+ $this->getDbName(),
+ $this->getName(),
+ 'Seq_in_index = 1'
+ );
+ $indexed = $GLOBALS['dbi']->fetchResult($sql, 'Column_name', 'Column_name');
+
+ $return = array();
+ foreach ($indexed as $column) {
+ $return[] = $this->getFullName($backquoted) . '.'
+ . ($backquoted ? PMA_Util::backquote($column) : $column);
+ }
+
+ return $return;
+ }
+
+ /**
+ * Get all columns
+ *
+ * returns an array with all columns
+ *
+ * @param bool $backquoted whether to quote name with backticks ``
+ *
+ * @return array
+ */
+ public function getColumns($backquoted = true)
+ {
+ $sql = 'SHOW COLUMNS FROM ' . $this->getFullName(true);
+ $indexed = $GLOBALS['dbi']->fetchResult($sql, 'Field', 'Field');
+
+ $return = array();
+ foreach ($indexed as $column) {
+ $return[] = $this->getFullName($backquoted) . '.'
+ . ($backquoted ? PMA_Util::backquote($column) : $column);
+ }
+
+ return $return;
+ }
+
+ /**
+ * Return UI preferences for this table from phpMyAdmin database.
+ *
+ * @return array
+ */
+ protected function getUiPrefsFromDb()
+ {
+ $pma_table = PMA_Util::backquote($GLOBALS['cfg']['Server']['pmadb']) ."."
+ . PMA_Util::backquote($GLOBALS['cfg']['Server']['table_uiprefs']);
+
+ // Read from phpMyAdmin database
+ $sql_query = " SELECT `prefs` FROM " . $pma_table
+ . " WHERE `username` = '" . $GLOBALS['cfg']['Server']['user'] . "'"
+ . " AND `db_name` = '" . PMA_Util::sqlAddSlashes($this->db_name) . "'"
+ . " AND `table_name` = '" . PMA_Util::sqlAddSlashes($this->name) . "'";
+
+ $row = $GLOBALS['dbi']->fetchArray(PMA_queryAsControlUser($sql_query));
+ if (isset($row[0])) {
+ return json_decode($row[0], true);
+ } else {
+ return array();
+ }
+ }
+
+ /**
+ * Save this table's UI preferences into phpMyAdmin database.
+ *
+ * @return true|PMA_Message
+ */
+ protected function saveUiPrefsToDb()
+ {
+ $pma_table = PMA_Util::backquote($GLOBALS['cfg']['Server']['pmadb']) . "."
+ . PMA_Util::backquote($GLOBALS['cfg']['Server']['table_uiprefs']);
+
+ $username = $GLOBALS['cfg']['Server']['user'];
+ $sql_query = " REPLACE INTO " . $pma_table
+ . " VALUES ('" . $username . "', '" . PMA_Util::sqlAddSlashes($this->db_name)
+ . "', '" . PMA_Util::sqlAddSlashes($this->name) . "', '"
+ . PMA_Util::sqlAddSlashes(json_encode($this->uiprefs)) . "', NULL)";
+
+ $success = $GLOBALS['dbi']->tryQuery($sql_query, $GLOBALS['controllink']);
+
+ if (!$success) {
+ $message = PMA_Message::error(
+ __('Could not save table UI preferences')
+ );
+ $message->addMessage('<br /><br />');
+ $message->addMessage(
+ PMA_Message::rawError(
+ $GLOBALS['dbi']->getError($GLOBALS['controllink'])
+ )
+ );
+ return $message;
+ }
+
+ // Remove some old rows in table_uiprefs if it exceeds the configured
+ // maximum rows
+ $sql_query = 'SELECT COUNT(*) FROM ' . $pma_table;
+ $rows_count = $GLOBALS['dbi']->fetchValue($sql_query);
+ $max_rows = $GLOBALS['cfg']['Server']['MaxTableUiprefs'];
+ if ($rows_count > $max_rows) {
+ $num_rows_to_delete = $rows_count - $max_rows;
+ $sql_query
+ = ' DELETE FROM ' . $pma_table .
+ ' ORDER BY last_update ASC' .
+ ' LIMIT ' . $num_rows_to_delete;
+ $success = $GLOBALS['dbi']->tryQuery(
+ $sql_query, $GLOBALS['controllink']
+ );
+
+ if (!$success) {
+ $message = PMA_Message::error(
+ sprintf(
+ __('Failed to cleanup table UI preferences (see $cfg[\'Servers\'][$i][\'MaxTableUiprefs\'] %s)'),
+ PMA_Util::showDocu('config', 'cfg_Servers_MaxTableUiprefs')
+ )
+ );
+ $message->addMessage('<br /><br />');
+ $message->addMessage(
+ PMA_Message::rawError(
+ $GLOBALS['dbi']->getError($GLOBALS['controllink'])
+ )
+ );
+ print_r($message);
+ return $message;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Loads the UI preferences for this table.
+ * If pmadb and table_uiprefs is set, it will load the UI preferences from
+ * phpMyAdmin database.
+ *
+ * @return void
+ */
+ protected function loadUiPrefs()
+ {
+ $server_id = $GLOBALS['server'];
+ // set session variable if it's still undefined
+ if (! isset($_SESSION['tmpval']['table_uiprefs'][$server_id][$this->db_name][$this->name])) {
+ // check whether we can get from pmadb
+ $_SESSION['tmpval']['table_uiprefs'][$server_id][$this->db_name][$this->name]
+ = (strlen($GLOBALS['cfg']['Server']['pmadb'])
+ && strlen($GLOBALS['cfg']['Server']['table_uiprefs']))
+ ? $this->getUiPrefsFromDb()
+ : array();
+ }
+ $this->uiprefs =& $_SESSION['tmpval']['table_uiprefs'][$server_id]
+ [$this->db_name][$this->name];
+ }
+
+ /**
+ * Get a property from UI preferences.
+ * Return false if the property is not found.
+ * Available property:
+ * - PROP_SORTED_COLUMN
+ * - PROP_COLUMN_ORDER
+ * - PROP_COLUMN_VISIB
+ *
+ * @param string $property property
+ *
+ * @return mixed
+ */
+ public function getUiProp($property)
+ {
+ if (! isset($this->uiprefs)) {
+ $this->loadUiPrefs();
+ }
+ // do checking based on property
+ if ($property == self::PROP_SORTED_COLUMN) {
+ if (isset($this->uiprefs[$property])) {
+ // check if the column name exists in this table
+ $tmp = explode(' ', $this->uiprefs[$property]);
+ $colname = $tmp[0];
+ //remove backquoting from colname
+ $colname = str_replace('`', '', $colname);
+ //get the available column name without backquoting
+ $avail_columns = $this->getColumns(false);
+ foreach ($avail_columns as $each_col) {
+ // check if $each_col ends with $colname
+ if (substr_compare(
+ $each_col,
+ $colname,
+ strlen($each_col) - strlen($colname)
+ ) === 0) {
+ return $this->uiprefs[$property];
+ }
+ }
+ // remove the property, since it is not exist anymore in database
+ $this->removeUiProp(self::PROP_SORTED_COLUMN);
+ return false;
+ } else {
+ return false;
+ }
+ } elseif ($property == self::PROP_COLUMN_ORDER
+ || $property == self::PROP_COLUMN_VISIB
+ ) {
+ if (! PMA_Table::isView($this->db_name, $this->name)
+ && isset($this->uiprefs[$property])
+ ) {
+ // check if the table has not been modified
+ if (self::sGetStatusInfo(
+ $this->db_name,
+ $this->name, 'Create_time'
+ ) == $this->uiprefs['CREATE_TIME']) {
+ return $this->uiprefs[$property];
+ } else {
+ // remove the property, since the table has been modified
+ $this->removeUiProp(self::PROP_COLUMN_ORDER);
+ return false;
+ }
+ } else {
+ return false;
+ }
+ }
+ // default behaviour for other property:
+ return isset($this->uiprefs[$property]) ? $this->uiprefs[$property] : false;
+ }
+
+ /**
+ * Set a property from UI preferences.
+ * If pmadb and table_uiprefs is set, it will save the UI preferences to
+ * phpMyAdmin database.
+ * Available property:
+ * - PROP_SORTED_COLUMN
+ * - PROP_COLUMN_ORDER
+ * - PROP_COLUMN_VISIB
+ *
+ * @param string $property Property
+ * @param mixed $value Value for the property
+ * @param string $table_create_time Needed for PROP_COLUMN_ORDER
+ * and PROP_COLUMN_VISIB
+ *
+ * @return boolean|PMA_Message
+ */
+ public function setUiProp($property, $value, $table_create_time = null)
+ {
+ if (! isset($this->uiprefs)) {
+ $this->loadUiPrefs();
+ }
+ // we want to save the create time if the property is PROP_COLUMN_ORDER
+ if (! PMA_Table::isView($this->db_name, $this->name)
+ && ($property == self::PROP_COLUMN_ORDER
+ || $property == self::PROP_COLUMN_VISIB)
+ ) {
+ $curr_create_time = self::sGetStatusInfo(
+ $this->db_name,
+ $this->name,
+ 'CREATE_TIME'
+ );
+ if (isset($table_create_time)
+ && $table_create_time == $curr_create_time
+ ) {
+ $this->uiprefs['CREATE_TIME'] = $curr_create_time;
+ } else {
+ // there is no $table_create_time, or
+ // supplied $table_create_time is older than current create time,
+ // so don't save
+ return PMA_Message::error(
+ sprintf(
+ __('Cannot save UI property "%s". The changes made will not be persistent after you refresh this page. Please check if the table structure has been changed.'),
+ $property
+ )
+ );
+ }
+ }
+ // save the value
+ $this->uiprefs[$property] = $value;
+ // check if pmadb is set
+ if (strlen($GLOBALS['cfg']['Server']['pmadb'])
+ && strlen($GLOBALS['cfg']['Server']['table_uiprefs'])
+ ) {
+ return $this->saveUiprefsToDb();
+ }
+ return true;
+ }
+
+ /**
+ * Remove a property from UI preferences.
+ *
+ * @param string $property the property
+ *
+ * @return true|PMA_Message
+ */
+ public function removeUiProp($property)
+ {
+ if (! isset($this->uiprefs)) {
+ $this->loadUiPrefs();
+ }
+ if (isset($this->uiprefs[$property])) {
+ unset($this->uiprefs[$property]);
+ // check if pmadb is set
+ if (strlen($GLOBALS['cfg']['Server']['pmadb'])
+ && strlen($GLOBALS['cfg']['Server']['table_uiprefs'])
+ ) {
+ return $this->saveUiprefsToDb();
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Get all column names which are MySQL reserved words
+ *
+ * @return array
+ * @access public
+ */
+ public function getReservedColumnNames()
+ {
+ $columns = $this->getColumns(false);
+ $return = array();
+ foreach ($columns as $column) {
+ $temp = explode('.', $column);
+ $column_name = $temp[2];
+ if (PMA_SQP_isKeyWord($column_name)) {
+ $return[] = $column_name;
+ }
+ }
+ return $return;
+ }
+}
+?>
diff --git a/libraries/TableSearch.class.php b/libraries/TableSearch.class.php
new file mode 100644
index 0000000000..ec273ed123
--- /dev/null
+++ b/libraries/TableSearch.class.php
@@ -0,0 +1,1469 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Handles Table search and Zoom search
+ *
+ * @package PhpMyAdmin
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ * Class to handle normal-search
+ * and zoom-search in a table
+ *
+ * @package PhpMyAdmin
+ */
+class PMA_TableSearch
+{
+ /**
+ * Database name
+ *
+ * @access private
+ * @var string
+ */
+ private $_db;
+ /**
+ * Table name
+ *
+ * @access private
+ * @var string
+ */
+ private $_table;
+ /**
+ * Normal search or Zoom search
+ *
+ * @access private
+ * @var string
+ */
+ private $_searchType;
+ /**
+ * Names of columns
+ *
+ * @access private
+ * @var array
+ */
+ private $_columnNames;
+ /**
+ * Types of columns
+ *
+ * @access private
+ * @var array
+ */
+ private $_columnTypes;
+ /**
+ * Collations of columns
+ *
+ * @access private
+ * @var array
+ */
+ private $_columnCollations;
+ /**
+ * Null Flags of columns
+ *
+ * @access private
+ * @var array
+ */
+ private $_columnNullFlags;
+ /**
+ * Whether a geometry column is present
+ *
+ * @access private
+ * @var boolean
+ */
+ private $_geomColumnFlag;
+ /**
+ * Foreign Keys
+ *
+ * @access private
+ * @var array
+ */
+ private $_foreigners;
+
+
+ /**
+ * Public Constructor
+ *
+ * @param string $db Database name
+ * @param string $table Table name
+ * @param string $searchType Whether normal or zoom search
+ */
+ public function __construct($db, $table, $searchType)
+ {
+ $this->_db = $db;
+ $this->_table = $table;
+ $this->_searchType = $searchType;
+ $this->_columnNames = array();
+ $this->_columnNullFlags = array();
+ $this->_columnTypes = array();
+ $this->_columnCollations = array();
+ $this->_geomColumnFlag = false;
+ $this->_foreigners = array();
+ // Loads table's information
+ $this->_loadTableInfo();
+ }
+
+ /**
+ * Returns Column names array
+ *
+ * @return array column names
+ */
+ public function getColumnNames()
+ {
+ return $this->_columnNames;
+ }
+
+ /**
+ * Gets all the columns of a table along with their types, collations
+ * and whether null or not.
+ *
+ * @return void
+ */
+ private function _loadTableInfo()
+ {
+ // Gets the list and number of columns
+ $columns = $GLOBALS['dbi']->getColumns(
+ $this->_db, $this->_table, null, true
+ );
+ // Get details about the geometry fucntions
+ $geom_types = PMA_Util::getGISDatatypes();
+
+ foreach ($columns as $row) {
+ // set column name
+ $this->_columnNames[] = $row['Field'];
+
+ $type = $row['Type'];
+ // check whether table contains geometric columns
+ if (in_array($type, $geom_types)) {
+ $this->_geomColumnFlag = true;
+ }
+ // reformat mysql query output
+ if (strncasecmp($type, 'set', 3) == 0
+ || strncasecmp($type, 'enum', 4) == 0
+ ) {
+ $type = str_replace(',', ', ', $type);
+ } else {
+ // strip the "BINARY" attribute, except if we find "BINARY(" because
+ // this would be a BINARY or VARBINARY column type
+ if (! preg_match('@BINARY[\(]@i', $type)) {
+ $type = preg_replace('@BINARY@i', '', $type);
+ }
+ $type = preg_replace('@ZEROFILL@i', '', $type);
+ $type = preg_replace('@UNSIGNED@i', '', $type);
+ $type = strtolower($type);
+ }
+ if (empty($type)) {
+ $type = '&nbsp;';
+ }
+ $this->_columnTypes[] = $type;
+ $this->_columnNullFlags[] = $row['Null'];
+ $this->_columnCollations[]
+ = ! empty($row['Collation']) && $row['Collation'] != 'NULL'
+ ? $row['Collation']
+ : '';
+ } // end for
+
+ // Retrieve foreign keys
+ $this->_foreigners = PMA_getForeigners($this->_db, $this->_table);
+ }
+
+ /**
+ * Sets the table header for displaying a table in query-by-example format.
+ *
+ * @return string HTML content, the tags and content for table header
+ */
+ private function _getTableHeader()
+ {
+ // Display the Function column only if there is at least one geometry column
+ $func = '';
+ if ($this->_geomColumnFlag) {
+ $func = '<th>' . __('Function') . '</th>';
+ }
+
+ return '<thead>
+ <tr>' . $func . '<th>' . __('Column') . '</th>
+ <th>' . __('Type') . '</th>
+ <th>' . __('Collation') . '</th>
+ <th>' . __('Operator') . '</th>
+ <th>' . __('Value') . '</th>
+ </tr>
+ </thead>';
+ }
+
+ /**
+ * Returns an array with necessary configurations to create
+ * sub-tabs in the table_select page.
+ *
+ * @return array Array containing configuration (icon, text, link, id, args)
+ * of sub-tabs
+ */
+ private function _getSubTabs()
+ {
+ $subtabs = array();
+ $subtabs['search']['icon'] = 'b_search.png';
+ $subtabs['search']['text'] = __('Table Search');
+ $subtabs['search']['link'] = 'tbl_select.php';
+ $subtabs['search']['id'] = 'tbl_search_id';
+ $subtabs['search']['args']['pos'] = 0;
+
+ $subtabs['zoom']['icon'] = 'b_props.png';
+ $subtabs['zoom']['link'] = 'tbl_zoom_select.php';
+ $subtabs['zoom']['text'] = __('Zoom Search');
+ $subtabs['zoom']['id'] = 'zoom_search_id';
+
+ $subtabs['replace']['icon'] = 'b_find_replace.png';
+ $subtabs['replace']['link'] = 'tbl_find_replace.php';
+ $subtabs['replace']['text'] = __('Find and Replace');
+ $subtabs['replace']['id'] = 'find_replace_id';
+
+ return $subtabs;
+ }
+
+ /**
+ * Provides html elements for search criteria inputbox
+ * in case the column's type is geometrical
+ *
+ * @param int $column_index Column's index
+ * @param bool $in_fbs Whether we are in 'function based search'
+ *
+ * @return string HTML elements.
+ */
+ private function _getGeometricalInputBox($column_index, $in_fbs)
+ {
+ $html_output = '<input type="text" name="criteriaValues['
+ . $column_index . ']"'
+ . ' size="40" class="textfield" id="field_' . $column_index . '" />';
+
+ if ($in_fbs) {
+ $edit_url = 'gis_data_editor.php?' . PMA_URL_getCommon();
+ $edit_str = PMA_Util::getIcon('b_edit.png', __('Edit/Insert'));
+ $html_output .= '<span class="open_search_gis_editor">';
+ $html_output .= PMA_Util::linkOrButton(
+ $edit_url, $edit_str, array(), false, false, '_blank'
+ );
+ $html_output .= '</span>';
+ }
+ return $html_output;
+ }
+
+ /**
+ * Provides html elements for search criteria inputbox
+ * in case the column is a Foreign Key
+ *
+ * @param array $foreignData Foreign keys data
+ * @param string $column_name Column name
+ * @param int $column_index Column index
+ * @param array $titles Selected title
+ * @param int $foreignMaxLimit Max limit of displaying foreign elements
+ * @param array $criteriaValues Array of search criteria inputs
+ * @param string $column_id Column's inputbox's id
+ * @param bool $in_zoom_search_edit Whether we are in zoom search edit
+ *
+ * @return string HTML elements.
+ */
+ private function _getForeignKeyInputBox($foreignData, $column_name,
+ $column_index, $titles, $foreignMaxLimit, $criteriaValues, $column_id,
+ $in_zoom_search_edit = false
+ ) {
+ $html_output = '';
+ if (is_array($foreignData['disp_row'])) {
+ $html_output .= '<select name="criteriaValues[' . $column_index . ']"'
+ . ' id="' . $column_id . $column_index .'">';
+ $html_output .= PMA_foreignDropdown(
+ $foreignData['disp_row'], $foreignData['foreign_field'],
+ $foreignData['foreign_display'], '', $foreignMaxLimit
+ );
+ $html_output .= '</select>';
+
+ } elseif ($foreignData['foreign_link'] == true) {
+ $html_output .= '<input type="text" id="' . $column_id
+ . $column_index . '"'
+ . ' name="criteriaValues[' . $column_index . ']" id="field_'
+ . md5($column_name) . '[' . $column_index .']" class="textfield"'
+ . (isset($criteriaValues[$column_index])
+ && is_string($criteriaValues[$column_index])
+ ? (' value="' . $criteriaValues[$column_index] . '"')
+ : '')
+ . ' />';
+
+ $html_output .= <<<EOT
+<a target="_blank" onclick="window.open(this.href, 'foreigners', 'width=640,height=240,scrollbars=yes'); return false" href="browse_foreigners.php?
+EOT;
+ $html_output .= '' . PMA_URL_getCommon($this->_db, $this->_table)
+ . '&amp;field=' . urlencode($column_name) . '&amp;fieldkey='
+ . $column_index . '&amp;fromsearch=1"';
+ if ($in_zoom_search_edit) {
+ $html_output .= ' class="browse_foreign"';
+ }
+ $html_output .= '>' . str_replace("'", "\'", $titles['Browse']) . '</a>';
+ }
+ return $html_output;
+ }
+
+ /**
+ * Provides html elements for search criteria inputbox
+ * in case the column is of ENUM or SET type
+ *
+ * @param int $column_index Column index
+ * @param array $criteriaValues Array of search criteria inputs
+ * @param string $column_type Column type
+ * @param string $column_id Column's inputbox's id
+ * @param bool $in_zoom_search_edit Whether we are in zoom search edit
+ *
+ * @return string HTML elements.
+ */
+ private function _getEnumSetInputBox($column_index, $criteriaValues,
+ $column_type, $column_id, $in_zoom_search_edit = false
+ ) {
+ $html_output = '';
+ $value = explode(
+ ', ',
+ str_replace("'", '', substr($column_type, 5, -1))
+ );
+ $cnt_value = count($value);
+
+ /*
+ * Enum in edit mode --> dropdown
+ * Enum in search mode --> multiselect
+ * Set in edit mode --> multiselect
+ * Set in search mode --> input (skipped here, so the 'else'
+ * section would handle it)
+ */
+ if ((strncasecmp($column_type, 'enum', 4) && ! $in_zoom_search_edit)
+ || (strncasecmp($column_type, 'set', 3) && $in_zoom_search_edit)
+ ) {
+ $html_output .= '<select name="criteriaValues[' . ($column_index)
+ . ']" id="' . $column_id . $column_index .'">';
+ } else {
+ $html_output .= '<select name="criteriaValues[' . $column_index . ']"'
+ . ' id="' . $column_id . $column_index . '" multiple="multiple"'
+ . ' size="' . min(3, $cnt_value) . '">';
+ }
+
+ //Add select options
+ for ($j = 0; $j < $cnt_value; $j++) {
+ if (isset($criteriaValues[$column_index])
+ && is_array($criteriaValues[$column_index])
+ && in_array($value[$j], $criteriaValues[$column_index])
+ ) {
+ $html_output .= '<option value="' . $value[$j] . '" Selected>'
+ . $value[$j] . '</option>';
+ } else {
+ $html_output .= '<option value="' . $value[$j] . '">'
+ . $value[$j] . '</option>';
+ }
+ } // end for
+ $html_output .= '</select>';
+ return $html_output;
+ }
+
+ /**
+ * Creates the HTML content for:
+ * 1) Browsing foreign data for a column.
+ * 2) Creating elements for search criteria input on columns.
+ *
+ * @param array $foreignData Foreign keys data
+ * @param string $column_name Column name
+ * @param string $column_type Column type
+ * @param int $column_index Column index
+ * @param array $titles Selected title
+ * @param int $foreignMaxLimit Max limit of displaying foreign elements
+ * @param array $criteriaValues Array of search criteria inputs
+ * @param bool $in_fbs Whether we are in 'function based search'
+ * @param bool $in_zoom_search_edit Whether we are in zoom search edit
+ *
+ * @return string HTML content for viewing foreign data and elements
+ * for search criteria input.
+ */
+ private function _getInputbox($foreignData, $column_name, $column_type,
+ $column_index, $titles, $foreignMaxLimit, $criteriaValues, $in_fbs = false,
+ $in_zoom_search_edit = false
+ ) {
+ $str = '';
+ $column_type = (string)$column_type;
+ $column_id = ($in_zoom_search_edit) ? 'edit_fieldID_' : 'fieldID_';
+
+ // Get inputbox based on different column types
+ // (Foreign key, geometrical, enum)
+ if ($this->_foreigners && isset($this->_foreigners[$column_name])) {
+ $str .= $this->_getForeignKeyInputBox(
+ $foreignData, $column_name, $column_index, $titles,
+ $foreignMaxLimit, $criteriaValues, $column_id
+ );
+
+ } elseif (in_array($column_type, PMA_Util::getGISDatatypes())) {
+ $str .= $this->_getGeometricalInputBox($column_index, $in_fbs);
+
+ } elseif (strncasecmp($column_type, 'enum', 4) == 0
+ || (strncasecmp($column_type, 'set', 3) == 0 && $in_zoom_search_edit)
+ ) {
+ $str .= $this->_getEnumSetInputBox(
+ $column_index, $criteriaValues, $column_type, $column_id,
+ $in_zoom_search_edit = false
+ );
+
+ } else {
+ // other cases
+ $the_class = 'textfield';
+
+ if ($column_type == 'date') {
+ $the_class .= ' datefield';
+ } elseif ($column_type == 'datetime'
+ || substr($column_type, 0, 9) == 'timestamp'
+ ) {
+ $the_class .= ' datetimefield';
+ } elseif (substr($column_type, 0, 3) == 'bit') {
+ $the_class .= ' bit';
+ }
+
+ $str .= '<input type="text" name="criteriaValues[' . $column_index . ']"'
+ .' size="40" class="' . $the_class . '" id="'
+ . $column_id . $column_index . '"'
+ . (isset($criteriaValues[$column_index])
+ && is_string($criteriaValues[$column_index])
+ ? (' value="' . $criteriaValues[$column_index] . '"')
+ : '')
+ . ' />';
+ }
+ return $str;
+ }
+
+ /**
+ * Return the where clause in case column's type is ENUM.
+ *
+ * @param mixed $criteriaValues Search criteria input
+ * @param string $func_type Search function/operator
+ *
+ * @return string part of where clause.
+ */
+ private function _getEnumWhereClause($criteriaValues, $func_type)
+ {
+ if (! is_array($criteriaValues)) {
+ $criteriaValues = explode(',', $criteriaValues);
+ }
+ $enum_selected_count = count($criteriaValues);
+ if ($func_type == '=' && $enum_selected_count > 1) {
+ $func_type = 'IN';
+ $parens_open = '(';
+ $parens_close = ')';
+
+ } elseif ($func_type == '!=' && $enum_selected_count > 1) {
+ $func_type = 'NOT IN';
+ $parens_open = '(';
+ $parens_close = ')';
+
+ } else {
+ $parens_open = '';
+ $parens_close = '';
+ }
+ $enum_where = '\''
+ . PMA_Util::sqlAddSlashes($criteriaValues[0]) . '\'';
+ for ($e = 1; $e < $enum_selected_count; $e++) {
+ $enum_where .= ', \''
+ . PMA_Util::sqlAddSlashes($criteriaValues[$e]) . '\'';
+ }
+
+ return ' ' . $func_type . ' ' . $parens_open
+ . $enum_where . $parens_close;
+ }
+
+ /**
+ * Return the where clause for a geometrical column.
+ *
+ * @param mixed $criteriaValues Search criteria input
+ * @param string $names Name of the column on which search is submitted
+ * @param string $func_type Search function/operator
+ * @param string $types Type of the field
+ * @param bool $geom_func Whether geometry functions should be applied
+ *
+ * @return string part of where clause.
+ */
+ private function _getGeomWhereClause($criteriaValues, $names,
+ $func_type, $types, $geom_func = null
+ ) {
+ $geom_unary_functions = array(
+ 'IsEmpty' => 1,
+ 'IsSimple' => 1,
+ 'IsRing' => 1,
+ 'IsClosed' => 1,
+ );
+ $where = '';
+
+ // Get details about the geometry functions
+ $geom_funcs = PMA_Util::getGISFunctions($types, true, false);
+ // New output type is the output type of the function being applied
+ $types = $geom_funcs[$geom_func]['type'];
+
+ // If the function takes a single parameter
+ if ($geom_funcs[$geom_func]['params'] == 1) {
+ $backquoted_name = $geom_func . '(' . PMA_Util::backquote($names) . ')';
+ } else {
+ // If the function takes two parameters
+ // create gis data from the criteria input
+ $gis_data = PMA_Util::createGISData($criteriaValues);
+ $where = $geom_func . '(' . PMA_Util::backquote($names)
+ . ',' . $gis_data . ')';
+ return $where;
+ }
+
+ // If the where clause is something like 'IsEmpty(`spatial_col_name`)'
+ if (isset($geom_unary_functions[$geom_func])
+ && trim($criteriaValues) == ''
+ ) {
+ $where = $backquoted_name;
+
+ } elseif (in_array($types, PMA_Util::getGISDatatypes())
+ && ! empty($criteriaValues)
+ ) {
+ // create gis data from the criteria input
+ $gis_data = PMA_Util::createGISData($criteriaValues);
+ $where = $backquoted_name . ' ' . $func_type . ' ' . $gis_data;
+ }
+ return $where;
+ }
+
+ /**
+ * Return the where clause for query generation based on the inputs provided.
+ *
+ * @param mixed $criteriaValues Search criteria input
+ * @param string $names Name of the column on which search is submitted
+ * @param string $types Type of the field
+ * @param string $collations Field collation
+ * @param string $func_type Search function/operator
+ * @param bool $unaryFlag Whether operator unary or not
+ * @param bool $geom_func Whether geometry functions should be applied
+ *
+ * @return string generated where clause.
+ */
+ private function _getWhereClause($criteriaValues, $names, $types, $collations,
+ $func_type, $unaryFlag, $geom_func = null
+ ) {
+ // If geometry function is set
+ if ($geom_func != null && trim($geom_func) != '') {
+ return $this->_getGeomWhereClause(
+ $criteriaValues, $names, $func_type, $types, $geom_func
+ );
+ }
+
+ $backquoted_name = PMA_Util::backquote($names);
+ $where = '';
+ if ($unaryFlag) {
+ $criteriaValues = '';
+ $where = $backquoted_name . ' ' . $func_type;
+
+ } elseif (strncasecmp($types, 'enum', 4) == 0 && ! empty($criteriaValues)) {
+ $where = $backquoted_name;
+ $where .= $this->_getEnumWhereClause($criteriaValues, $func_type);
+
+ } elseif ($criteriaValues != '') {
+ // For these types we quote the value. Even if it's another type
+ // (like INT), for a LIKE we always quote the value. MySQL converts
+ // strings to numbers and numbers to strings as necessary
+ // during the comparison
+ if (preg_match('@char|binary|blob|text|set|date|time|year@i', $types)
+ || strpos(' ' . $func_type, 'LIKE')
+ ) {
+ $quot = '\'';
+ } else {
+ $quot = '';
+ }
+
+ // LIKE %...%
+ if ($func_type == 'LIKE %...%') {
+ $func_type = 'LIKE';
+ $criteriaValues = '%' . $criteriaValues . '%';
+ }
+ if ($func_type == 'REGEXP ^...$') {
+ $func_type = 'REGEXP';
+ $criteriaValues = '^' . $criteriaValues . '$';
+ }
+
+ if ('IN (...)' == $func_type
+ || 'NOT IN (...)' == $func_type
+ || 'BETWEEN' == $func_type
+ || 'NOT BETWEEN' == $func_type
+ ) {
+ $func_type = str_replace(' (...)', '', $func_type);
+
+ //Don't explode if this is already an array
+ //(Case for (NOT) IN/BETWEEN.)
+ if (is_array($criteriaValues)) {
+ $values = $criteriaValues;
+ } else {
+ $values = explode(',', $criteriaValues);
+ }
+ // quote values one by one
+ $emptyKey = false;
+ foreach ($values as $key => &$value) {
+ if ('' === $value) {
+ $emptyKey = $key;
+ $value = 'NULL';
+ continue;
+ }
+ $value = $quot . PMA_Util::sqlAddSlashes(trim($value))
+ . $quot;
+ }
+
+ if ('BETWEEN' == $func_type || 'NOT BETWEEN' == $func_type) {
+ $where = $backquoted_name . ' ' . $func_type . ' '
+ . (isset($values[0]) ? $values[0] : '')
+ . ' AND ' . (isset($values[1]) ? $values[1] : '');
+ } else { //[NOT] IN
+ if (false !== $emptyKey) {
+ unset($values[$emptyKey]);
+ }
+ $wheres = array();
+ if (!empty($values)) {
+ $wheres[] = $backquoted_name . ' ' . $func_type
+ . ' (' . implode(',', $values) . ')';
+ }
+ if (false !== $emptyKey) {
+ $wheres[] = $backquoted_name . ' IS NULL';
+ }
+ $where = implode(' OR ', $wheres);
+ if (1 < count($wheres)) {
+ $where = '(' . $where . ')';
+ }
+ }
+ } else {
+ if ($func_type == 'LIKE %...%' || $func_type == 'LIKE') {
+ $where = $backquoted_name . ' ' . $func_type . ' ' . $quot
+ . PMA_Util::sqlAddSlashes($criteriaValues, true) . $quot;
+ } else {
+ $where = $backquoted_name . ' ' . $func_type . ' ' . $quot
+ . PMA_Util::sqlAddSlashes($criteriaValues) . $quot;
+ }
+ }
+ } // end if
+
+ return $where;
+ }
+
+ /**
+ * Builds the sql search query from the post parameters
+ *
+ * @return string the generated SQL query
+ */
+ public function buildSqlQuery()
+ {
+ $sql_query = 'SELECT ';
+
+ // If only distinct values are needed
+ $is_distinct = (isset($_POST['distinct'])) ? 'true' : 'false';
+ if ($is_distinct == 'true') {
+ $sql_query .= 'DISTINCT ';
+ }
+
+ // if all column names were selected to display, we do a 'SELECT *'
+ // (more efficient and this helps prevent a problem in IE
+ // if one of the rows is edited and we come back to the Select results)
+ if (isset($_POST['zoom_submit']) || ! empty($_POST['displayAllColumns'])) {
+ $sql_query .= '* ';
+ } else {
+ $sql_query .= implode(
+ ', ',
+ PMA_Util::backquote($_POST['columnsToDisplay'])
+ );
+ } // end if
+
+ $sql_query .= ' FROM '
+ . PMA_Util::backquote($_POST['table']);
+ $whereClause = $this->_generateWhereClause();
+ $sql_query .= $whereClause;
+
+ // if the search results are to be ordered
+ if (isset($_POST['orderByColumn']) && $_POST['orderByColumn'] != '--nil--') {
+ $sql_query .= ' ORDER BY '
+ . PMA_Util::backquote($_POST['orderByColumn'])
+ . ' ' . $_POST['order'];
+ } // end if
+ return $sql_query;
+ }
+
+ /**
+ * Generates the where clause for the SQL search query to be executed
+ *
+ * @return string the generated where clause
+ */
+ private function _generateWhereClause()
+ {
+ if (isset($_POST['customWhereClause'])
+ && trim($_POST['customWhereClause']) != ''
+ ) {
+ return ' WHERE ' . $_POST['customWhereClause'];
+ }
+
+ // If there are no search criteria set or no unary criteria operators,
+ // return
+ if (! isset($_POST['criteriaValues'])
+ && ! isset($_POST['criteriaColumnOperators'])
+ ) {
+ return '';
+ }
+
+ // else continue to form the where clause from column criteria values
+ $fullWhereClause = $charsets = array();
+ reset($_POST['criteriaColumnOperators']);
+ while (list($column_index, $operator) = each(
+ $_POST['criteriaColumnOperators']
+ )) {
+ list($charsets[$column_index]) = explode(
+ '_', $_POST['criteriaColumnCollations'][$column_index]
+ );
+ $unaryFlag = $GLOBALS['PMA_Types']->isUnaryOperator($operator);
+ $tmp_geom_func = isset($geom_func[$column_index])
+ ? $geom_func[$column_index] : null;
+
+ $whereClause = $this->_getWhereClause(
+ $_POST['criteriaValues'][$column_index],
+ $_POST['criteriaColumnNames'][$column_index],
+ $_POST['criteriaColumnTypes'][$column_index],
+ $_POST['criteriaColumnCollations'][$column_index],
+ $operator,
+ $unaryFlag,
+ $tmp_geom_func
+ );
+
+ if ($whereClause) {
+ $fullWhereClause[] = $whereClause;
+ }
+ } // end while
+
+ if ($fullWhereClause) {
+ return ' WHERE ' . implode(' AND ', $fullWhereClause);
+ }
+ return '';
+ }
+
+ /**
+ * Generates HTML for a geometrical function column to be displayed in table
+ * search selection form
+ *
+ * @param integer $column_index index of current column in $columnTypes array
+ *
+ * @return string the generated HTML
+ */
+ private function _getGeomFuncHtml($column_index)
+ {
+ $html_output = '';
+ // return if geometrical column is not present
+ if (! $this->_geomColumnFlag) {
+ return $html_output;
+ }
+
+ /**
+ * Displays 'Function' column if it is present
+ */
+ $html_output .= '<td>';
+ $geom_types = PMA_Util::getGISDatatypes();
+ // if a geometry column is present
+ if (in_array($this->_columnTypes[$column_index], $geom_types)) {
+ $html_output .= '<select class="geom_func" name="geom_func['
+ . $column_index . ']">';
+ // get the relevant list of GIS functions
+ $funcs = PMA_Util::getGISFunctions(
+ $this->_columnTypes[$column_index], true, true
+ );
+ /**
+ * For each function in the list of functions,
+ * add an option to select list
+ */
+ foreach ($funcs as $func_name => $func) {
+ $name = isset($func['display']) ? $func['display'] : $func_name;
+ $html_output .= '<option value="' . htmlspecialchars($name) . '">'
+ . htmlspecialchars($name) . '</option>';
+ }
+ $html_output .= '</select>';
+ } else {
+ $html_output .= '&nbsp;';
+ }
+ $html_output .= '</td>';
+ return $html_output;
+ }
+
+ /**
+ * Generates formatted HTML for extra search options in table search form
+ *
+ * @return string the generated HTML
+ */
+ private function _getOptions()
+ {
+ $html_output = '';
+ $html_output .= PMA_Util::getDivForSliderEffect(
+ 'searchoptions', __('Options')
+ );
+
+ /**
+ * Displays columns select list for selecting distinct columns in the search
+ */
+ $html_output .= '<fieldset id="fieldset_select_fields">'
+ . '<legend>' . __('Select columns (at least one):') . '</legend>'
+ . '<select name="columnsToDisplay[]"'
+ . ' size="' . min(count($this->_columnNames), 10) . '"'
+ . ' multiple="multiple">';
+ // Displays the list of the fields
+ foreach ($this->_columnNames as $each_field) {
+ $html_output .= ' '
+ . '<option value="' . htmlspecialchars($each_field) . '"'
+ . ' selected="selected">' . htmlspecialchars($each_field)
+ . '</option>' . "\n";
+ } // end for
+ $html_output .= '</select>'
+ . '<input type="checkbox" name="distinct" value="DISTINCT"'
+ . ' id="oDistinct" />'
+ . '<label for="oDistinct">DISTINCT</label></fieldset>';
+
+ /**
+ * Displays input box for custom 'Where' clause to be used in the search
+ */
+ $html_output .= '<fieldset id="fieldset_search_conditions">'
+ . '<legend>' . '<em>' . __('Or') . '</em> '
+ . __('Add search conditions (body of the "where" clause):') . '</legend>';
+ $html_output .= PMA_Util::showMySQLDocu('Functions');
+ $html_output .= '<input type="text" name="customWhereClause"'
+ . ' class="textfield" size="64" />';
+ $html_output .= '</fieldset>';
+
+ /**
+ * Displays option of changing default number of rows displayed per page
+ */
+ $html_output .= '<fieldset id="fieldset_limit_rows">'
+ . '<legend>' . __('Number of rows per page') . '</legend>'
+ . '<input type="number" size="4" name="session_max_rows" required '
+ . 'min="1" '
+ . 'value="' . $GLOBALS['cfg']['MaxRows'] . '" class="textfield" />'
+ . '</fieldset>';
+
+ /**
+ * Displays option for ordering search results
+ * by a column value (Asc or Desc)
+ */
+ $html_output .= '<fieldset id="fieldset_display_order">'
+ . '<legend>' . __('Display order:') . '</legend>'
+ . '<select name="orderByColumn"><option value="--nil--"></option>';
+ foreach ($this->_columnNames as $each_field) {
+ $html_output .= ' '
+ . '<option value="' . htmlspecialchars($each_field) . '">'
+ . htmlspecialchars($each_field) . '</option>' . "\n";
+ } // end for
+ $html_output .= '</select>';
+ $choices = array(
+ 'ASC' => __('Ascending'),
+ 'DESC' => __('Descending')
+ );
+ $html_output .= PMA_Util::getRadioFields(
+ 'order', $choices, 'ASC', false, true, "formelement"
+ );
+ unset($choices);
+
+ $html_output .= '</fieldset><br style="clear: both;"/></div>';
+ return $html_output;
+ }
+
+ /**
+ * Other search criteria like data label
+ * (for tbl_zoom_select.php)
+ *
+ * @param array $dataLabel Label for points in zoom plot
+ *
+ * @return string the generated html
+ */
+ private function _getOptionsZoom($dataLabel)
+ {
+ $html_output = '';
+ $html_output .= '<table class="data">';
+ //Select options for datalabel
+ $html_output .= '<tr>';
+ $html_output .= '<td><label for="dataLabel">'
+ . __("Use this column to label each point") . '</label></td>';
+ $html_output .= '<td><select name="dataLabel" id="dataLabel" >'
+ . '<option value = "">' . __('None') . '</option>';
+ for ($j = 0; $j < count($this->_columnNames); $j++) {
+ if (isset($dataLabel)
+ && $dataLabel == htmlspecialchars($this->_columnNames[$j])
+ ) {
+ $html_output .= '<option value="'
+ . htmlspecialchars($this->_columnNames[$j])
+ . '" selected="selected">'
+ . htmlspecialchars($this->_columnNames[$j])
+ . '</option>';
+ } else {
+ $html_output .= '<option value="'
+ . htmlspecialchars($this->_columnNames[$j]) . '" >'
+ . htmlspecialchars($this->_columnNames[$j]) . '</option>';
+ }
+ }
+ $html_output .= '</select></td>';
+ $html_output .= '</tr>';
+ //Inputbox for changing default maximum rows to plot
+ $html_output .= '<tr>';
+ $html_output .= '<td><label for="maxRowPlotLimit">'
+ . __("Maximum rows to plot") . '</label></td>';
+ $html_output .= '<td>';
+ $html_output .= '<input type="number" name="maxPlotLimit"'
+ . ' id="maxRowPlotLimit" required'
+ . ' value="' . ((! empty($_POST['maxPlotLimit']))
+ ? htmlspecialchars($_POST['maxPlotLimit'])
+ : $GLOBALS['cfg']['maxRowPlotLimit'])
+ . '" />';
+ $html_output .= '</td></tr>';
+ $html_output .= '</table>';
+ return $html_output;
+ }
+
+ /**
+ * Provides a column's type, collation, operators list, and crietria value
+ * to display in table search form
+ *
+ * @param integer $search_index Row number in table search form
+ * @param integer $column_index Column index in ColumnNames array
+ *
+ * @return array Array contaning column's properties
+ */
+ public function getColumnProperties($search_index, $column_index)
+ {
+ $selected_operator = (isset($_POST['criteriaColumnOperators'])
+ ? $_POST['criteriaColumnOperators'][$search_index] : '');
+ $entered_value = (isset($_POST['criteriaValues'])
+ ? $_POST['criteriaValues'] : '');
+ $titles['Browse'] = PMA_Util::getIcon(
+ 'b_browse.png', __('Browse foreign values')
+ );
+ //Gets column's type and collation
+ $type = $this->_columnTypes[$column_index];
+ $collation = $this->_columnCollations[$column_index];
+ //Gets column's comparison operators depending on column type
+ $func = '<select name="criteriaColumnOperators['
+ . $search_index . ']" onchange="changeValueFieldType(this, '
+ . $search_index . ')">';
+ $func .= $GLOBALS['PMA_Types']->getTypeOperatorsHtml(
+ preg_replace('@\(.*@s', '', $this->_columnTypes[$column_index]),
+ $this->_columnNullFlags[$column_index], $selected_operator
+ );
+ $func .= '</select>';
+ //Gets link to browse foreign data(if any) and criteria inputbox
+ $foreignData = PMA_getForeignData(
+ $this->_foreigners, $this->_columnNames[$column_index], false, '', ''
+ );
+ $value = $this->_getInputbox(
+ $foreignData, $this->_columnNames[$column_index], $type, $search_index,
+ $titles, $GLOBALS['cfg']['ForeignKeyMaxLimit'], $entered_value
+ );
+ return array(
+ 'type' => $type,
+ 'collation' => $collation,
+ 'func' => $func,
+ 'value' => $value
+ );
+ }
+
+ /**
+ * Provides the search form's table row in case of Normal Search
+ * (for tbl_select.php)
+ *
+ * @return string the generated table row
+ */
+ private function _getRowsNormal()
+ {
+ $odd_row = true;
+ $html_output = '';
+ // for every column present in table
+ for (
+ $column_index = 0;
+ $column_index < count($this->_columnNames);
+ $column_index++
+ ) {
+ $html_output .= '<tr class="noclick '
+ . ($odd_row ? 'odd' : 'even')
+ . '">';
+ $odd_row = !$odd_row;
+ //If 'Function' column is present
+ $html_output .= $this->_getGeomFuncHtml($column_index);
+ //Displays column's name, type, collation and value
+ $html_output .= '<th>'
+ . htmlspecialchars($this->_columnNames[$column_index]) . '</th>';
+ $properties = $this->getColumnProperties($column_index, $column_index);
+ $html_output .= '<td>' . $properties['type'] . '</td>';
+ $html_output .= '<td>' . $properties['collation'] . '</td>';
+ $html_output .= '<td>' . $properties['func'] . '</td>';
+ $html_output .= '<td>' . $properties['value'] . '</td>';
+ $html_output .= '</tr>';
+ //Displays hidden fields
+ $html_output .= '<tr><td>';
+ $html_output .= '<input type="hidden"'
+ . ' name="criteriaColumnNames[' . $column_index . ']"'
+ . ' value="' . htmlspecialchars($this->_columnNames[$column_index])
+ . '" />';
+ $html_output .= '<input type="hidden"'
+ . ' name="criteriaColumnTypes[' . $column_index . ']"'
+ . ' value="' . $this->_columnTypes[$column_index] . '" />';
+ $html_output .= '<input type="hidden"'
+ . ' name="criteriaColumnCollations[' . $column_index . ']"'
+ . ' value="' . $this->_columnCollations[$column_index] . '" />';
+ $html_output .= '</td></tr>';
+ } // end for
+
+ return $html_output;
+ }
+
+ /**
+ * Provides the search form's table row in case of Zoom Search
+ * (for tbl_zoom_select.php)
+ *
+ * @return string the generated table row
+ */
+ private function _getRowsZoom()
+ {
+ $odd_row = true;
+ $html_output = '';
+ /**
+ * Get already set search criteria (if any)
+ */
+
+ //Displays column rows for search criteria input
+ for ($i = 0; $i < 4; $i++) {
+ //After X-Axis and Y-Axis column rows, display additional criteria
+ // option
+ if ($i == 2) {
+ $html_output .= '<tr><td>';
+ $html_output .= __("Additional search criteria");
+ $html_output .= '</td></tr>';
+ }
+ $html_output .= '<tr class="noclick '
+ . ($odd_row ? 'odd' : 'even')
+ . '">';
+ $odd_row = ! $odd_row;
+ //Select options for column names
+ $html_output .= '<th><select name="criteriaColumnNames[]" id="'
+ . 'tableid_' . $i . '" >';
+ $html_output .= '<option value="' . 'pma_null' . '">' . __('None')
+ . '</option>';
+ for ($j = 0 ; $j < count($this->_columnNames); $j++) {
+ if (isset($_POST['criteriaColumnNames'][$i])
+ && $_POST['criteriaColumnNames'][$i] == htmlspecialchars($this->_columnNames[$j])
+ ) {
+ $html_output .= '<option value="'
+ . htmlspecialchars($this->_columnNames[$j])
+ . '" selected="selected">'
+ . htmlspecialchars($this->_columnNames[$j])
+ . '</option>';
+ } else {
+ $html_output .= '<option value="'
+ . htmlspecialchars($this->_columnNames[$j]) . '">'
+ . htmlspecialchars($this->_columnNames[$j]) . '</option>';
+ }
+ }
+ $html_output .= '</select></th>';
+ if (isset($_POST['criteriaColumnNames'])
+ && $_POST['criteriaColumnNames'][$i] != 'pma_null'
+ ) {
+ $key = array_search(
+ $_POST['criteriaColumnNames'][$i],
+ $this->_columnNames
+ );
+ $properties = $this->getColumnProperties($i, $key);
+ $type[$i] = $properties['type'];
+ $collation[$i] = $properties['collation'];
+ $func[$i] = $properties['func'];
+ $value[$i] = $properties['value'];
+ }
+ //Column type
+ $html_output .= '<td>' . (isset($type[$i]) ? $type[$i] : '') . '</td>';
+ //Column Collation
+ $html_output .= '<td>' . (isset($collation[$i]) ? $collation[$i] : '')
+ . '</td>';
+ //Select options for column operators
+ $html_output .= '<td>' . (isset($func[$i]) ? $func[$i] : '') . '</td>';
+ //Inputbox for search criteria value
+ $html_output .= '<td>' . (isset($value[$i]) ? $value[$i] : '') . '</td>';
+ $html_output .= '</tr>';
+ //Displays hidden fields
+ $html_output .= '<tr><td>';
+ $html_output
+ .= '<input type="hidden" name="criteriaColumnTypes[' . $i . ']"'
+ . ' id="types_' . $i . '" ';
+ if (isset($_POST['criteriaColumnTypes'][$i])) {
+ $html_output .= 'value="' . $_POST['criteriaColumnTypes'][$i] . '" ';
+ }
+ $html_output .= '/>';
+ $html_output .= '<input type="hidden" name="criteriaColumnCollations['
+ . $i . ']" id="collations_' . $i . '" />';
+ $html_output .= '</td></tr>';
+ }//end for
+ return $html_output;
+ }
+
+ /**
+ * Generates HTML for displaying fields table in search form
+ *
+ * @return string the generated HTML
+ */
+ private function _getFieldsTableHtml()
+ {
+ $html_output = '';
+ $html_output .= '<table class="data"'
+ . ($this->_searchType == 'zoom' ? ' id="tableFieldsId"' : '') . '>';
+ $html_output .= $this->_getTableHeader();
+ $html_output .= '<tbody>';
+
+ if ($this->_searchType == 'zoom') {
+ $html_output .= $this->_getRowsZoom();
+ } else {
+ $html_output .= $this->_getRowsNormal();
+ }
+
+ $html_output .= '</tbody></table>';
+ return $html_output;
+ }
+
+ /**
+ * Provides the form tag for table search form
+ * (normal search or zoom search)
+ *
+ * @param string $goto Goto URL
+ *
+ * @return string the HTML for form tag
+ */
+ private function _getFormTag($goto)
+ {
+ $html_output = '';
+ $scriptName = '';
+ $formId = '';
+ switch ($this->_searchType) {
+ case 'normal' :
+ $scriptName = 'tbl_select.php';
+ $formId = 'tbl_search_form';
+ break;
+ case 'zoom' :
+ $scriptName = 'tbl_zoom_select.php';
+ $formId = 'zoom_search_form';
+ break;
+ case 'replace' :
+ $scriptName = 'tbl_find_replace.php';
+ $formId = 'find_replace_form';
+ break;
+ }
+
+ $html_output .= '<form method="post" action="' . $scriptName . '" '
+ . 'name="insertForm" id="' . $formId . '" '
+ . 'class="ajax"' . '>';
+
+ $html_output .= PMA_URL_getHiddenInputs($this->_db, $this->_table);
+ $html_output .= '<input type="hidden" name="goto" value="' . $goto . '" />';
+ $html_output .= '<input type="hidden" name="back" value="' . $scriptName
+ . '" />';
+
+ return $html_output;
+ }
+
+ /**
+ * Returns the HTML for secondary levels tabs of the table search page
+ *
+ * @return string HTML for secondary levels tabs
+ */
+ public function getSecondaryTabs()
+ {
+ $url_params = array();
+ $url_params['db'] = $this->_db;
+ $url_params['table'] = $this->_table;
+
+ $html_output = '<ul id="topmenu2">';
+ foreach ($this->_getSubTabs() as $tab) {
+ $html_output .= PMA_Util::getHtmlTab($tab, $url_params);
+ }
+ $html_output .= '</ul>';
+ $html_output .= '<div class="clearfloat"></div>';
+ return $html_output;
+ }
+
+ /**
+ * Generates the table search form under table search tab
+ *
+ * @param string $goto Goto URL
+ * @param string $dataLabel Label for points in zoom plot
+ *
+ * @return string the generated HTML for table search form
+ */
+ public function getSelectionForm($goto, $dataLabel = null)
+ {
+ $html_output = $this->_getFormTag($goto);
+
+ if ($this->_searchType == 'zoom') {
+ $html_output .= '<fieldset id="fieldset_zoom_search">';
+ $html_output .= '<fieldset id="inputSection">';
+ $html_output .= '<legend>'
+ . __('Do a "query by example" (wildcard: "%") for two different columns')
+ . '</legend>';
+ $html_output .= $this->_getFieldsTableHtml();
+ $html_output .= $this->_getOptionsZoom($dataLabel);
+ $html_output .= '</fieldset>';
+ $html_output .= '</fieldset>';
+ } else if ($this->_searchType == 'normal') {
+ $html_output .= '<fieldset id="fieldset_table_search">';
+ $html_output .= '<fieldset id="fieldset_table_qbe">';
+ $html_output .= '<legend>'
+ . __('Do a "query by example" (wildcard: "%")')
+ . '</legend>';
+ $html_output .= $this->_getFieldsTableHtml();
+ $html_output .= '<div id="gis_editor"></div>';
+ $html_output .= '<div id="popup_background"></div>';
+ $html_output .= '</fieldset>';
+ $html_output .= $this->_getOptions();
+ $html_output .= '</fieldset>';
+ } else if ($this->_searchType == 'replace') {
+ $html_output .= '<fieldset id="fieldset_find_replace">';
+ $html_output .= '<fieldset id="fieldset_find">';
+ $html_output .= '<legend>' . __('Find and Replace') . '</legend>';
+ $html_output .= $this->_getSearchAndReplaceHTML();
+ $html_output .= '</fieldset>';
+ $html_output .= '</fieldset>';
+ }
+
+ /**
+ * Displays selection form's footer elements
+ */
+ $html_output .= '<fieldset class="tblFooters">';
+ $html_output .= '<input type="submit" name="'
+ . ($this->_searchType == 'zoom' ? 'zoom_submit' : 'submit')
+ . ($this->_searchType == 'zoom' ? '" id="inputFormSubmitId"' : '" ')
+ . 'value="' . __('Go') . '" />';
+ $html_output .= '</fieldset></form>';
+ $html_output .= '<div id="sqlqueryresults"></div>';
+ return $html_output;
+ }
+
+ /**
+ * Provides form for displaying point data and also the scatter plot
+ * (for tbl_zoom_select.php)
+ *
+ * @param string $goto Goto URL
+ * @param array $data Array containing SQL query data
+ *
+ * @return string form's html
+ */
+ public function getZoomResultsForm($goto, $data)
+ {
+ $html_output = '';
+ $titles['Browse'] = PMA_Util::getIcon(
+ 'b_browse.png',
+ __('Browse foreign values')
+ );
+ $html_output .= '<form method="post" action="tbl_zoom_select.php"'
+ . ' name="displayResultForm" id="zoom_display_form"'
+ . ' class="ajax"' . '>';
+ $html_output .= PMA_URL_getHiddenInputs($this->_db, $this->_table);
+ $html_output .= '<input type="hidden" name="goto" value="' . $goto . '" />';
+ $html_output
+ .= '<input type="hidden" name="back" value="tbl_zoom_select.php" />';
+
+ $html_output .= '<fieldset id="displaySection">';
+ $html_output .= '<legend>' . __('Browse/Edit the points') . '</legend>';
+
+ //JSON encode the data(query result)
+ $html_output .= '<center>';
+ if (isset($_POST['zoom_submit']) && ! empty($data)) {
+ $html_output .= '<div id="resizer">';
+ $html_output .= '<center><a href="#" onclick="displayHelp();">'
+ . __('How to use') . '</a></center>';
+ $html_output .= '<div id="querydata" style="display:none">'
+ . json_encode($data) . '</div>';
+ $html_output .= '<div id="querychart"></div>';
+ $html_output .= '<button class="button-reset">'
+ . __('Reset zoom') . '</button>';
+ $html_output .= '</div>';
+ }
+ $html_output .= '</center>';
+
+ //Displays rows in point edit form
+ $html_output .= '<div id="dataDisplay" style="display:none">';
+ $html_output .= '<table><thead>';
+ $html_output .= '<tr>';
+ $html_output .= '<th>' . __('Column') . '</th>'
+ . '<th>' . __('Null') . '</th>'
+ . '<th>' . __('Value') . '</th>';
+ $html_output .= '</tr>';
+ $html_output .= '</thead>';
+
+ $html_output .= '<tbody>';
+ $odd_row = true;
+ for (
+ $column_index = 0;
+ $column_index < count($this->_columnNames);
+ $column_index++
+ ) {
+ $fieldpopup = $this->_columnNames[$column_index];
+ $foreignData = PMA_getForeignData(
+ $this->_foreigners,
+ $fieldpopup,
+ false,
+ '',
+ ''
+ );
+ $html_output
+ .= '<tr class="noclick ' . ($odd_row ? 'odd' : 'even') . '">';
+ $odd_row = ! $odd_row;
+ //Display column Names
+ $html_output
+ .= '<th>' . htmlspecialchars($this->_columnNames[$column_index])
+ . '</th>';
+ //Null checkbox if column can be null
+ $html_output .= '<th>'
+ . (($this->_columnNullFlags[$column_index] == 'YES')
+ ? '<input type="checkbox" class="checkbox_null"'
+ . ' name="criteriaColumnNullFlags[' . $column_index . ']"'
+ . ' id="edit_fields_null_id_' . $column_index . '" />'
+ : '');
+ $html_output .= '</th>';
+ //Column's Input box
+ $html_output .= '<th>';
+ $html_output .= $this->_getInputbox(
+ $foreignData, $fieldpopup, $this->_columnTypes[$column_index],
+ $column_index, $titles, $GLOBALS['cfg']['ForeignKeyMaxLimit'],
+ '', false, true
+ );
+ $html_output .= '</th></tr>';
+ }
+ $html_output .= '</tbody></table>';
+ $html_output .= '</div>';
+ $html_output .= '<input type="hidden" id="queryID" name="sql_query" />';
+ $html_output .= '</form>';
+ return $html_output;
+ }
+
+ /**
+ * Displays the 'Find and Replace' form
+ *
+ * @return string HTML for 'Find and Replace' form
+ */
+ function _getSearchAndReplaceHTML()
+ {
+ $htmlOutput = __('Find:')
+ . '<input type="text" value="" name="find" required />';
+ $htmlOutput .= __('Replace with:')
+ . '<input type="text" value="" name="replaceWith" required />';
+
+ $htmlOutput .= __('Column:') . '<select name="columnIndex">';
+ for ($i = 0; $i < count($this->_columnNames); $i++) {
+ $type = preg_replace('@\(.*@s', '', $this->_columnTypes[$i]);
+ if ($GLOBALS['PMA_Types']->getTypeClass($type) == 'CHAR') {
+ $column = $this->_columnNames[$i];
+ $htmlOutput .= '<option value="' . $i . '">'
+ . htmlspecialchars($column) . '</option>';
+ }
+ }
+ $htmlOutput .= '</select>';
+ return $htmlOutput;
+ }
+
+ /**
+ * Returns HTML for prviewing strings found and their replacements
+ *
+ * @param int $columnIndex index of the column
+ * @param string $find string to find in the column
+ * @param string $replaceWith string to replace with
+ * @param string $charSet character set of the connection
+ *
+ * @return string HTML for prviewing strings found and their replacements
+ */
+ function getReplacePreview($columnIndex, $find, $replaceWith, $charSet)
+ {
+ $column = $this->_columnNames[$columnIndex];
+ $sql_query = "SELECT "
+ . PMA_Util::backquote($column) . ","
+ . " REPLACE("
+ . PMA_Util::backquote($column) . ", '" . $find . "', '" . $replaceWith
+ . "'),"
+ . " COUNT(*)"
+ . " FROM " . PMA_Util::backquote($this->_db)
+ . "." . PMA_Util::backquote($this->_table)
+ . " WHERE " . PMA_Util::backquote($column)
+ . " LIKE '%" . $find . "%' COLLATE " . $charSet . "_bin"; // here we
+ // change the collation of the 2nd operand to a case sensitive
+ // binary collation to make sure that the comparison is case sensitive
+ $sql_query .= " GROUP BY " . PMA_Util::backquote($column)
+ . " ORDER BY " . PMA_Util::backquote($column) . " ASC";
+
+ $rs = $GLOBALS['dbi']->query(
+ $sql_query, null, PMA_DatabaseInterface::QUERY_STORE
+ );
+
+ $htmlOutput = '<form method="post" action="tbl_find_replace.php"'
+ . ' name="previewForm" id="previewForm" class="ajax">';
+ $htmlOutput .= PMA_URL_getHiddenInputs($this->_db, $this->_table);
+ $htmlOutput .= '<input type="hidden" name="replace" value="true" />';
+ $htmlOutput .= '<input type="hidden" name="columnIndex" value="'
+ . $columnIndex . '" />';
+ $htmlOutput .= '<input type="hidden" name="findString"'
+ . ' value="' . $find . '" />';
+ $htmlOutput .= '<input type="hidden" name="replaceWith"'
+ . ' value="' . $replaceWith . '" />';
+
+ $htmlOutput .= '<fieldset id="fieldset_find_replace_preview">';
+ $htmlOutput .= '<legend>' . __('Find and replace - preview') . '</legend>';
+
+ $htmlOutput .= '<table id="previewTable">'
+ . '<thead><tr>'
+ . '<th>' . __('Count') . '</th>'
+ . '<th>' . __('Original string') . '</th>'
+ . '<th>' . __('Replaced string') . '</th>'
+ . '</tr></thead>';
+
+ $htmlOutput .= '<tbody>';
+ $odd = true;
+ while ($row = $GLOBALS['dbi']->fetchRow($rs)) {
+ $val = $row[0];
+ $replaced = $row[1];
+ $count = $row[2];
+
+ $htmlOutput .= '<tr class="' . ($odd ? 'odd' : 'even') . '">';
+ $htmlOutput .= '<td class="right">' . htmlspecialchars($count) . '</td>';
+ $htmlOutput .= '<td>' . htmlspecialchars($val) . '</td>';
+ $htmlOutput .= '<td>' . htmlspecialchars($replaced) . '</td>';
+ $htmlOutput .= '</tr>';
+
+ $odd = ! $odd;
+ }
+ $htmlOutput .= '</tbody>';
+ $htmlOutput .= '</table>';
+ $htmlOutput .= '</fieldset>';
+
+ $htmlOutput .= '<fieldset class="tblFooters">';
+ $htmlOutput .= '<input type="submit" name="replace"'
+ . ' value="' . __('Replace') . '" />';
+ $htmlOutput .= '</fieldset>';
+
+ $htmlOutput .= '</form>';
+ return $htmlOutput;
+ }
+
+ /**
+ * Replaces a given string in a column with a give replacement
+ *
+ * @param int $columnIndex index of the column
+ * @param string $find string to find in the column
+ * @param string $replaceWith string to replace with
+ * @param string $charSet character set of the connection
+ *
+ * @return void
+ */
+ function replace($columnIndex, $find, $replaceWith, $charSet)
+ {
+ $column = $this->_columnNames[$columnIndex];
+ $sql_query = "UPDATE " . PMA_Util::backquote($this->_db)
+ . "." . PMA_Util::backquote($this->_table)
+ . " SET " . PMA_Util::backquote($column) . " ="
+ . " REPLACE("
+ . PMA_Util::backquote($column) . ", '" . $find . "', '" . $replaceWith
+ . "')"
+ . " WHERE " . PMA_Util::backquote($column)
+ . " LIKE '%" . $find . "%' COLLATE " . $charSet . "_bin"; // here we
+ // change the collation of the 2nd operand to a case sensitive
+ // binary collation to make sure that the comparison is case sensitive
+ $GLOBALS['dbi']->query(
+ $sql_query, null, PMA_DatabaseInterface::QUERY_STORE
+ );
+ $GLOBALS['sql_query'] = $sql_query;
+ }
+}
+?>
diff --git a/libraries/Theme.class.php b/libraries/Theme.class.php
new file mode 100644
index 0000000000..a6c8a5f6e7
--- /dev/null
+++ b/libraries/Theme.class.php
@@ -0,0 +1,485 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * hold PMA_Theme class
+ *
+ * @package PhpMyAdmin
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ * handles theme
+ *
+ * @todo add the possibility to make a theme depend on another theme
+ * and by default on original
+ * @todo make all components optional - get missing components from 'parent' theme
+ *
+ * @package PhpMyAdmin
+ */
+class PMA_Theme
+{
+ /**
+ * @var string theme version
+ * @access protected
+ */
+ var $version = '0.0.0.0';
+
+ /**
+ * @var string theme name
+ * @access protected
+ */
+ var $name = '';
+
+ /**
+ * @var string theme id
+ * @access protected
+ */
+ var $id = '';
+
+ /**
+ * @var string theme path
+ * @access protected
+ */
+ var $path = '';
+
+ /**
+ * @var string image path
+ * @access protected
+ */
+ var $img_path = '';
+
+ /**
+ * @var integer last modification time for info file
+ * @access protected
+ */
+ var $mtime_info = 0;
+
+ /**
+ * needed because sometimes, the mtime for different themes
+ * is identical
+ * @var integer filesize for info file
+ * @access protected
+ */
+ var $filesize_info = 0;
+
+ /**
+ * @var array List of css files to load
+ * @access private
+ */
+ private $_cssFiles = array(
+ 'common',
+ 'enum_editor',
+ 'gis',
+ 'navigation',
+ 'pmd',
+ 'rte',
+ 'codemirror',
+ 'jqplot',
+ 'resizable-menu'
+ );
+
+ /**
+ * Loads theme information
+ *
+ * @return boolean whether loading them info was successful or not
+ * @access public
+ */
+ function loadInfo()
+ {
+ if (! file_exists($this->getPath() . '/info.inc.php')) {
+ return false;
+ }
+
+ if ($this->mtime_info === filemtime($this->getPath() . '/info.inc.php')) {
+ return true;
+ }
+
+ @include $this->getPath() . '/info.inc.php';
+
+ // was it set correctly?
+ if (! isset($theme_name)) {
+ return false;
+ }
+
+ $this->mtime_info = filemtime($this->getPath() . '/info.inc.php');
+ $this->filesize_info = filesize($this->getPath() . '/info.inc.php');
+
+ if (isset($theme_full_version)) {
+ $this->setVersion($theme_full_version);
+ } elseif (isset($theme_generation, $theme_version)) {
+ $this->setVersion($theme_generation . '.' . $theme_version);
+ }
+ $this->setName($theme_name);
+
+ return true;
+ }
+
+ /**
+ * returns theme object loaded from given folder
+ * or false if theme is invalid
+ *
+ * @param string $folder path to theme
+ *
+ * @return PMA_Theme|false
+ * @static
+ * @access public
+ */
+ static public function load($folder)
+ {
+ $theme = new PMA_Theme();
+
+ $theme->setPath($folder);
+
+ if (! $theme->loadInfo()) {
+ return false;
+ }
+
+ $theme->checkImgPath();
+
+ return $theme;
+ }
+
+ /**
+ * checks image path for existance - if not found use img from fallback theme
+ *
+ * @access public
+ * @return bool
+ */
+ public function checkImgPath()
+ {
+ // try current theme first
+ if (is_dir($this->getPath() . '/img/')) {
+ $this->setImgPath($this->getPath() . '/img/');
+ return true;
+ }
+
+ // try fallback theme
+ $fallback = $GLOBALS['cfg']['ThemePath'] . '/'
+ . PMA_Theme_Manager::FALLBACK_THEME
+ . '/img/';
+ if (is_dir($fallback)) {
+ $this->setImgPath($fallback);
+ return true;
+ }
+
+ // we failed
+ trigger_error(
+ sprintf(
+ __('No valid image path for theme %s found!'),
+ $this->getName()
+ ),
+ E_USER_ERROR
+ );
+ return false;
+ }
+
+ /**
+ * returns path to theme
+ *
+ * @access public
+ * @return string path to theme
+ */
+ public function getPath()
+ {
+ return $this->path;
+ }
+
+ /**
+ * returns layout file
+ *
+ * @access public
+ * @return string layout file
+ */
+ public function getLayoutFile()
+ {
+ return $this->getPath() . '/layout.inc.php';
+ }
+
+ /**
+ * set path to theme
+ *
+ * @param string $path path to theme
+ *
+ * @return void
+ * @access public
+ */
+ public function setPath($path)
+ {
+ $this->path = trim($path);
+ }
+
+ /**
+ * sets version
+ *
+ * @param string $version version to set
+ *
+ * @return void
+ * @access public
+ */
+ public function setVersion($version)
+ {
+ $this->version = trim($version);
+ }
+
+ /**
+ * returns version
+ *
+ * @return string version
+ * @access public
+ */
+ public function getVersion()
+ {
+ return $this->version;
+ }
+
+ /**
+ * checks theme version agaisnt $version
+ * returns true if theme version is equal or higher to $version
+ *
+ * @param string $version version to compare to
+ *
+ * @return boolean true if theme version is equal or higher to $version
+ * @access public
+ */
+ public function checkVersion($version)
+ {
+ return version_compare($this->getVersion(), $version, 'lt');
+ }
+
+ /**
+ * sets name
+ *
+ * @param string $name name to set
+ *
+ * @return void
+ * @access public
+ */
+ public function setName($name)
+ {
+ $this->name = trim($name);
+ }
+
+ /**
+ * returns name
+ *
+ * @access public
+ * @return string name
+ */
+ public function getName()
+ {
+ return $this->name;
+ }
+
+ /**
+ * sets id
+ *
+ * @param string $id new id
+ *
+ * @return void
+ * @access public
+ */
+ public function setId($id)
+ {
+ $this->id = trim($id);
+ }
+
+ /**
+ * returns id
+ *
+ * @return string id
+ * @access public
+ */
+ public function getId()
+ {
+ return $this->id;
+ }
+
+ /**
+ * Sets path to images for the theme
+ *
+ * @param string $path path to images for this theme
+ *
+ * @return void
+ * @access public
+ */
+ public function setImgPath($path)
+ {
+ $this->img_path = $path;
+ }
+
+ /**
+ * Returns the path to image for the theme.
+ * If filename is given, it possibly fallbacks to fallback
+ * theme for it if image does not exist.
+ *
+ * @param string $file file name for image
+ *
+ * @access public
+ * @return string image path for this theme
+ */
+ public function getImgPath($file = null)
+ {
+ if (is_null($file)) {
+ return $this->img_path;
+ } else {
+ if (is_readable($this->img_path . $file)) {
+ return $this->img_path . $file;
+ } else {
+ return $GLOBALS['cfg']['ThemePath'] . '/'
+ . PMA_Theme_Manager::FALLBACK_THEME . '/img/' . $file;
+ }
+ }
+ }
+
+ /**
+ * load css (send to stdout, normally the browser)
+ *
+ * @return bool
+ * @access public
+ */
+ public function loadCss()
+ {
+ $success = true;
+
+ if ($GLOBALS['text_dir'] === 'ltr') {
+ $right = 'right';
+ $left = 'left';
+ } else {
+ $right = 'left';
+ $left = 'right';
+ }
+
+ foreach ($this->_cssFiles as $file) {
+ $path = $this->getPath() . "/css/$file.css.php";
+ $fallback = "./themes/"
+ . PMA_Theme_Manager::FALLBACK_THEME . "/css/$file.css.php";
+
+ if (is_readable($path)) {
+ echo "\n/* FILE: $file.css.php */\n";
+ include $path;
+ } else if (is_readable($fallback)) {
+ echo "\n/* FILE: $file.css.php */\n";
+ include $fallback;
+ } else {
+ $success = false;
+ }
+ }
+
+ include './themes/sprites.css.php';
+
+ return $success;
+ }
+
+ /**
+ * Renders the preview for this theme
+ *
+ * @return string
+ * @access public
+ */
+ public function getPrintPreview()
+ {
+ $url_params = array('set_theme' => $this->getId());
+ $url = 'index.php'. PMA_URL_getCommon($url_params);
+
+ $retval = '<div class="theme_preview">';
+ $retval .= '<h2>';
+ $retval .= htmlspecialchars($this->getName());
+ $retval .= ' (' . htmlspecialchars($this->getVersion()) . ') ';
+ $retval .= '</h2>';
+ $retval .= '<p>';
+ $retval .= '<a class="take_theme" ';
+ $retval .= 'name="' . htmlspecialchars($this->getId()) . '" ';
+ $retval .= 'href="' . $url . '">';
+ if (@file_exists($this->getPath() . '/screen.png')) {
+ // if screen exists then output
+ $retval .= '<img src="' . $this->getPath() . '/screen.png" border="1"';
+ $retval .= ' alt="' . htmlspecialchars($this->getName()) . '"';
+ $retval .= ' title="' . htmlspecialchars($this->getName()) . '" />';
+ $retval .= '<br />';
+ } else {
+ $retval .= __('No preview available.');
+ }
+ $retval .= '[ <strong>' . __('take it') . '</strong> ]';
+ $retval .= '</a>';
+ $retval .= '</p>';
+ $retval .= '</div>';
+ return $retval;
+ }
+
+ /**
+ * Remove filter for IE.
+ *
+ * @return string CSS code.
+ */
+ function getCssIEClearFilter()
+ {
+ return PMA_USR_BROWSER_AGENT == 'IE'
+ && PMA_USR_BROWSER_VER >= 6
+ && PMA_USR_BROWSER_VER <= 8
+ ? 'filter: none'
+ : '';
+ }
+
+ /**
+ * Gets currently configured font size.
+ *
+ * @return String with font size.
+ */
+ function getFontSize()
+ {
+ $fs = $GLOBALS['PMA_Config']->get('fontsize');
+ if (!is_null($fs)) {
+ return $fs;
+ }
+ if (isset($_COOKIE['pma_fontsize'])) {
+ return $_COOKIE['pma_fontsize'];
+ }
+ return '82%';
+ }
+
+ /**
+ * Generates code for CSS gradient using various browser extensions.
+ *
+ * @param string $start_color Color of gradient start, hex value without #
+ * @param string $end_color Color of gradient end, hex value without #
+ *
+ * @return string CSS code.
+ */
+ function getCssGradient($start_color, $end_color)
+ {
+ $result = array();
+ // Opera 9.5+, IE 9
+ $result[] = 'background-image: url(./themes/svg_gradient.php?from='
+ . $start_color . '&to=' . $end_color . ');';
+ $result[] = 'background-size: 100% 100%;';
+ // Safari 4-5, Chrome 1-9
+ $result[] = 'background: '
+ . '-webkit-gradient(linear, left top, left bottom, from(#'
+ . $start_color . '), to(#' . $end_color . '));';
+ // Safari 5.1, Chrome 10+
+ $result[] = 'background: -webkit-linear-gradient(top, #'
+ . $start_color . ', #' . $end_color . ');';
+ // Firefox 3.6+
+ $result[] = 'background: -moz-linear-gradient(top, #'
+ . $start_color . ', #' . $end_color . ');';
+ // IE 10
+ $result[] = 'background: -ms-linear-gradient(top, #'
+ . $start_color . ', #' . $end_color . ');';
+ // Opera 11.10
+ $result[] = 'background: -o-linear-gradient(top, #'
+ . $start_color . ', #' . $end_color . ');';
+ // IE 6-8
+ if (PMA_USR_BROWSER_AGENT == 'IE'
+ && PMA_USR_BROWSER_VER >= 6
+ && PMA_USR_BROWSER_VER <= 8
+ ) {
+ $result[] = 'filter: '
+ . 'progid:DXImageTransform.Microsoft.gradient(startColorstr="#'
+ . $start_color . '", endColorstr="#' . $end_color . '");';
+ }
+ return implode("\n", $result);
+ }
+}
+?>
diff --git a/libraries/Theme_Manager.class.php b/libraries/Theme_Manager.class.php
new file mode 100644
index 0000000000..d12689ea05
--- /dev/null
+++ b/libraries/Theme_Manager.class.php
@@ -0,0 +1,461 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * phpMyAdmin theme manager
+ *
+ * @package PhpMyAdmin
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ * phpMyAdmin theme manager
+ *
+ * @package PhpMyAdmin
+ */
+class PMA_Theme_Manager
+{
+ /**
+ * @var string path to theme folder
+ * @access protected
+ */
+ private $_themes_path;
+
+ /**
+ * @var array available themes
+ */
+ var $themes = array();
+
+ /**
+ * @var string cookie name
+ */
+ var $cookie_name = 'pma_theme';
+
+ /**
+ * @var boolean
+ */
+ var $per_server = false;
+
+ /**
+ * @var string name of active theme
+ */
+ var $active_theme = '';
+
+ /**
+ * @var PMA_Theme PMA_Theme active theme
+ */
+ var $theme = null;
+
+ /**
+ * @var string
+ */
+ var $theme_default;
+
+ /**
+ * @const string The name of the fallback theme
+ */
+ const FALLBACK_THEME = 'pmahomme';
+
+ /**
+ * Constructor for Theme Manager class
+ *
+ * @access public
+ */
+ public function __construct()
+ {
+ $this->init();
+ }
+
+ /**
+ * sets path to folder containing the themes
+ *
+ * @param string $path path to themes folder
+ *
+ * @access public
+ * @return boolean success
+ */
+ public function setThemesPath($path)
+ {
+ if (! $this->_checkThemeFolder($path)) {
+ return false;
+ }
+
+ $this->_themes_path = trim($path);
+ return true;
+ }
+
+ /**
+ * Returns path to folder containing themes
+ *
+ * @access public
+ * @return string theme path
+ */
+ public function getThemesPath()
+ {
+ return $this->_themes_path;
+ }
+
+ /**
+ * sets if there are different themes per server
+ *
+ * @param boolean $per_server Whether to enable per server flag
+ *
+ * @access public
+ * @return void
+ */
+ public function setThemePerServer($per_server)
+ {
+ $this->per_server = (bool) $per_server;
+ }
+
+ /**
+ * Initialise the class
+ *
+ * @access public
+ * @return boolean|void
+ */
+ public function init()
+ {
+ $this->themes = array();
+ $this->theme_default = self::FALLBACK_THEME;
+ $this->active_theme = '';
+
+ if (! $this->setThemesPath($GLOBALS['cfg']['ThemePath'])) {
+ return false;
+ }
+
+ $this->setThemePerServer($GLOBALS['cfg']['ThemePerServer']);
+
+ $this->loadThemes();
+
+ $this->theme = new PMA_Theme;
+
+
+ if (! $this->checkTheme($GLOBALS['cfg']['ThemeDefault'])) {
+ trigger_error(
+ sprintf(
+ __('Default theme %s not found!'),
+ htmlspecialchars($GLOBALS['cfg']['ThemeDefault'])
+ ),
+ E_USER_ERROR
+ );
+ $GLOBALS['cfg']['ThemeDefault'] = false;
+ }
+
+ $this->theme_default = $GLOBALS['cfg']['ThemeDefault'];
+
+ // check if user have a theme cookie
+ if (! $this->getThemeCookie()
+ || ! $this->setActiveTheme($this->getThemeCookie())
+ ) {
+ if ($GLOBALS['cfg']['ThemeDefault']) {
+ // otherwise use default theme
+ $this->setActiveTheme($this->theme_default);
+ } else {
+ // or fallback theme
+ $this->setActiveTheme(self::FALLBACK_THEME);
+ }
+ }
+ }
+
+ /**
+ * Checks configuration
+ *
+ * @access public
+ * @return void
+ */
+ public function checkConfig()
+ {
+ if ($this->_themes_path != trim($GLOBALS['cfg']['ThemePath'])
+ || $this->theme_default != $GLOBALS['cfg']['ThemeDefault']
+ ) {
+ $this->init();
+ } else {
+ // at least the theme path needs to be checked every time for new
+ // themes, as there is no other way at the moment to keep track of
+ // new or removed themes
+ $this->loadThemes();
+ }
+ }
+
+ /**
+ * Sets active theme
+ *
+ * @param string $theme theme name
+ *
+ * @access public
+ * @return bool true on success
+ */
+ public function setActiveTheme($theme = null)
+ {
+ if (! $this->checkTheme($theme)) {
+ trigger_error(
+ sprintf(
+ __('Theme %s not found!'),
+ htmlspecialchars($theme)
+ ),
+ E_USER_ERROR
+ );
+ return false;
+ }
+
+ $this->active_theme = $theme;
+ $this->theme = $this->themes[$theme];
+
+ // need to set later
+ //$this->setThemeCookie();
+
+ return true;
+ }
+
+ /**
+ * Returns name for storing theme
+ *
+ * @return string cookie name
+ * @access public
+ */
+ public function getThemeCookieName()
+ {
+ // Allow different theme per server
+ if (isset($GLOBALS['server']) && $this->per_server) {
+ return $this->cookie_name . '-' . $GLOBALS['server'];
+ } else {
+ return $this->cookie_name;
+ }
+ }
+
+ /**
+ * returns name of theme stored in the cookie
+ *
+ * @return string theme name from cookie
+ * @access public
+ */
+ public function getThemeCookie()
+ {
+ if (isset($_COOKIE[$this->getThemeCookieName()])) {
+ return $_COOKIE[$this->getThemeCookieName()];
+ }
+
+ return false;
+ }
+
+ /**
+ * save theme in cookie
+ *
+ * @return bool true
+ * @access public
+ */
+ public function setThemeCookie()
+ {
+ $GLOBALS['PMA_Config']->setCookie(
+ $this->getThemeCookieName(),
+ $this->theme->id,
+ $this->theme_default
+ );
+ // force a change of a dummy session variable to avoid problems
+ // with the caching of phpmyadmin.css.php
+ $GLOBALS['PMA_Config']->set('theme-update', $this->theme->id);
+ return true;
+ }
+
+ /**
+ * Checks whether folder is valid for storing themes
+ *
+ * @param string $folder Folder name to test
+ *
+ * @return boolean
+ * @access private
+ */
+ private function _checkThemeFolder($folder)
+ {
+ if (! is_dir($folder)) {
+ trigger_error(
+ sprintf(
+ __('Theme path not found for theme %s!'),
+ htmlspecialchars($folder)
+ ),
+ E_USER_ERROR
+ );
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * read all themes
+ *
+ * @return bool true
+ * @access public
+ */
+ public function loadThemes()
+ {
+ $this->themes = array();
+
+ if ($handleThemes = opendir($this->getThemesPath())) {
+ // check for themes directory
+ while (false !== ($PMA_Theme = readdir($handleThemes))) {
+ // Skip non dirs, . and ..
+ if ($PMA_Theme == '.'
+ || $PMA_Theme == '..'
+ || ! is_dir($this->getThemesPath() . '/' . $PMA_Theme)
+ ) {
+ continue;
+ }
+ if (array_key_exists($PMA_Theme, $this->themes)) {
+ continue;
+ }
+ $new_theme = PMA_Theme::load(
+ $this->getThemesPath() . '/' . $PMA_Theme
+ );
+ if ($new_theme) {
+ $new_theme->setId($PMA_Theme);
+ $this->themes[$PMA_Theme] = $new_theme;
+ }
+ } // end get themes
+ closedir($handleThemes);
+ } else {
+ trigger_error(
+ 'phpMyAdmin-ERROR: cannot open themes folder: '
+ . $this->getThemesPath(),
+ E_USER_WARNING
+ );
+ return false;
+ } // end check for themes directory
+
+ ksort($this->themes);
+ return true;
+ }
+
+ /**
+ * checks if given theme name is a known theme
+ *
+ * @param string $theme name fo theme to check for
+ *
+ * @return bool
+ * @access public
+ */
+ public function checkTheme($theme)
+ {
+ if (! array_key_exists($theme, $this->themes)) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * returns HTML selectbox, with or without form enclosed
+ *
+ * @param boolean $form whether enclosed by from tags or not
+ *
+ * @return string
+ * @access public
+ */
+ public function getHtmlSelectBox($form = true)
+ {
+ $select_box = '';
+
+ if ($form) {
+ $select_box .= '<form name="setTheme" method="get"';
+ $select_box .= ' action="index.php" class="disableAjax">';
+ $select_box .= PMA_URL_getHiddenInputs();
+ }
+
+ $theme_preview_path= './themes.php';
+ $theme_preview_href = '<a href="'
+ . $theme_preview_path . '" target="themes" class="themeselect">';
+ $select_box .= $theme_preview_href . __('Theme:') . '</a>' . "\n";
+
+ $select_box .= '<select name="set_theme" lang="en" dir="ltr"'
+ . ' class="autosubmit">';
+ foreach ($this->themes as $each_theme_id => $each_theme) {
+ $select_box .= '<option value="' . $each_theme_id . '"';
+ if ($this->active_theme === $each_theme_id) {
+ $select_box .= ' selected="selected"';
+ }
+ $select_box .= '>' . htmlspecialchars($each_theme->getName())
+ . '</option>';
+ }
+ $select_box .= '</select>';
+
+ if ($form) {
+ $select_box .= '</form>';
+ }
+
+ return $select_box;
+ }
+
+ /**
+ * enables backward compatibility
+ *
+ * @return void
+ * @access public
+ */
+ public function makeBc()
+ {
+ $GLOBALS['theme'] = $this->theme->getId();
+ $GLOBALS['pmaThemePath'] = $this->theme->getPath();
+ $GLOBALS['pmaThemeImage'] = $this->theme->getImgPath();
+
+ /**
+ * load layout file if exists
+ */
+ if (file_exists($this->theme->getLayoutFile())) {
+ include $this->theme->getLayoutFile();
+ }
+ }
+
+ /**
+ * Renders the previews for all themes
+ *
+ * @return string
+ * @access public
+ */
+ public function getPrintPreviews()
+ {
+ $retval = '';
+ foreach ($this->themes as $each_theme) {
+ $retval .= $each_theme->getPrintPreview();
+ } // end 'open themes'
+ return $retval;
+ }
+
+ /**
+ * returns PMA_Theme object for fall back theme
+ *
+ * @return PMA_Theme fall back theme
+ * @access public
+ */
+ public function getFallBackTheme()
+ {
+ if (isset($this->themes[self::FALLBACK_THEME])) {
+ return $this->themes[self::FALLBACK_THEME];
+ }
+
+ return false;
+ }
+
+ /**
+ * prints css data
+ *
+ * @return bool
+ * @access public
+ */
+ public function printCss()
+ {
+ if ($this->theme->loadCss()) {
+ return true;
+ }
+
+ // if loading css for this theme failed, try default theme css
+ $fallback_theme = $this->getFallBackTheme();
+ if ($fallback_theme && $fallback_theme->loadCss()) {
+ return true;
+ }
+
+ return false;
+ }
+}
+?>
diff --git a/libraries/Tracker.class.php b/libraries/Tracker.class.php
new file mode 100644
index 0000000000..d4dab00ee2
--- /dev/null
+++ b/libraries/Tracker.class.php
@@ -0,0 +1,1087 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Tracking changes on databases, tables and views
+ *
+ * @package PhpMyAdmin
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ * This class tracks changes on databases, tables and views.
+ *
+ * @package PhpMyAdmin
+ *
+ * @todo use stristr instead of strstr
+ */
+class PMA_Tracker
+{
+ /**
+ * Whether tracking is ready.
+ */
+ static protected $enabled = false;
+
+ /**
+ * Defines the internal PMA table which contains tracking data.
+ *
+ * @access protected
+ * @var string
+ */
+ static protected $pma_table;
+
+ /**
+ * Defines the usage of DROP TABLE statment in SQL dumps.
+ *
+ * @access protected
+ * @var boolean
+ */
+ static protected $add_drop_table;
+
+ /**
+ * Defines the usage of DROP VIEW statment in SQL dumps.
+ *
+ * @access protected
+ * @var boolean
+ */
+ static protected $add_drop_view;
+
+ /**
+ * Defines the usage of DROP DATABASE statment in SQL dumps.
+ *
+ * @access protected
+ * @var boolean
+ */
+ static protected $add_drop_database;
+
+ /**
+ * Defines auto-creation of tracking versions.
+ *
+ * @var boolean
+ */
+ static protected $version_auto_create;
+
+ /**
+ * Defines the default set of tracked statements.
+ *
+ * @var string
+ */
+ static protected $default_tracking_set;
+
+ /**
+ * Flags copied from `tracking` column definition in `pma_tracking` table.
+ * Used for column type conversion in Drizzle.
+ *
+ * @var array
+ */
+ static private $_tracking_set_flags = array(
+ 'UPDATE','REPLACE','INSERT','DELETE','TRUNCATE','CREATE DATABASE',
+ 'ALTER DATABASE','DROP DATABASE','CREATE TABLE','ALTER TABLE',
+ 'RENAME TABLE','DROP TABLE','CREATE INDEX','DROP INDEX',
+ 'CREATE VIEW','ALTER VIEW','DROP VIEW'
+ );
+
+
+ /**
+ * Initializes settings.
+ *
+ * @static
+ *
+ * @return void
+ */
+ static protected function init()
+ {
+ self::$pma_table = PMA_Util::backquote($GLOBALS['cfg']['Server']['pmadb'])
+ . '.' . PMA_Util::backquote($GLOBALS['cfg']['Server']['tracking']);
+
+ self::$add_drop_table
+ = $GLOBALS['cfg']['Server']['tracking_add_drop_table'];
+
+ self::$add_drop_view
+ = $GLOBALS['cfg']['Server']['tracking_add_drop_view'];
+
+ self::$add_drop_database
+ = $GLOBALS['cfg']['Server']['tracking_add_drop_database'];
+
+ self::$default_tracking_set
+ = $GLOBALS['cfg']['Server']['tracking_default_statements'];
+
+ self::$version_auto_create
+ = $GLOBALS['cfg']['Server']['tracking_version_auto_create'];
+ }
+
+ /**
+ * Actually enables tracking. This needs to be done after all
+ * underlaying code is initialized.
+ *
+ * @static
+ *
+ * @return void
+ */
+ static public function enable()
+ {
+ self::$enabled = true;
+ }
+
+ /**
+ * Gets the on/off value of the Tracker module, starts initialization.
+ *
+ * @static
+ *
+ * @return boolean (true=on|false=off)
+ */
+ static public function isActive()
+ {
+ if (! self::$enabled) {
+ return false;
+ }
+ /* We need to avoid attempt to track any queries
+ * from PMA_getRelationsParam
+ */
+ self::$enabled = false;
+ $cfgRelation = PMA_getRelationsParam();
+ /* Restore original state */
+ self::$enabled = true;
+ if (! $cfgRelation['trackingwork']) {
+ return false;
+ }
+ self::init();
+
+ if (isset(self::$pma_table)) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Parses the name of a table from a SQL statement substring.
+ *
+ * @param string $string part of SQL statement
+ *
+ * @static
+ *
+ * @return string the name of table
+ */
+ static protected function getTableName($string)
+ {
+ if (strstr($string, '.')) {
+ $temp = explode('.', $string);
+ $tablename = $temp[1];
+ } else {
+ $tablename = $string;
+ }
+
+ $str = explode("\n", $tablename);
+ $tablename = $str[0];
+
+ $tablename = str_replace(';', '', $tablename);
+ $tablename = str_replace('`', '', $tablename);
+ $tablename = trim($tablename);
+
+ return $tablename;
+ }
+
+
+ /**
+ * Gets the tracking status of a table, is it active or deactive ?
+ *
+ * @param string $dbname name of database
+ * @param string $tablename name of table
+ *
+ * @static
+ *
+ * @return boolean true or false
+ */
+ static public function isTracked($dbname, $tablename)
+ {
+ if (! self::$enabled) {
+ return false;
+ }
+ /* We need to avoid attempt to track any queries
+ * from PMA_getRelationsParam
+ */
+ self::$enabled = false;
+ $cfgRelation = PMA_getRelationsParam();
+ /* Restore original state */
+ self::$enabled = true;
+ if (! $cfgRelation['trackingwork']) {
+ return false;
+ }
+
+ $sql_query = " SELECT tracking_active FROM " . self::$pma_table .
+ " WHERE db_name = '" . PMA_Util::sqlAddSlashes($dbname) . "' " .
+ " AND table_name = '" . PMA_Util::sqlAddSlashes($tablename) . "' " .
+ " ORDER BY version DESC";
+
+ $row = $GLOBALS['dbi']->fetchArray(PMA_queryAsControlUser($sql_query));
+
+ if (isset($row['tracking_active']) && $row['tracking_active'] == 1) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Returns the comment line for the log.
+ *
+ * @return string Comment, contains date and username
+ */
+ static public function getLogComment()
+ {
+ $date = date('Y-m-d H:i:s');
+
+ return "# log " . $date . " " . $GLOBALS['cfg']['Server']['user'] . "\n";
+ }
+
+ /**
+ * Creates tracking version of a table / view
+ * (in other words: create a job to track future changes on the table).
+ *
+ * @param string $dbname name of database
+ * @param string $tablename name of table
+ * @param string $version version
+ * @param string $tracking_set set of tracking statements
+ * @param bool $is_view if table is a view
+ *
+ * @static
+ *
+ * @return int result of version insertion
+ */
+ static public function createVersion($dbname, $tablename, $version,
+ $tracking_set = '', $is_view = false
+ ) {
+ global $sql_backquotes, $export_type;
+
+ if ($tracking_set == '') {
+ $tracking_set = self::$default_tracking_set;
+ }
+
+ // get Export SQL instance
+ include_once "libraries/plugin_interface.lib.php";
+ $export_sql_plugin = PMA_getPlugin(
+ "export",
+ "sql",
+ 'libraries/plugins/export/',
+ array(
+ 'export_type' => $export_type,
+ 'single_table' => false,
+ )
+ );
+
+ $sql_backquotes = true;
+
+ $date = date('Y-m-d H:i:s');
+
+ // Get data definition snapshot of table
+
+ $columns = $GLOBALS['dbi']->getColumns($dbname, $tablename, null, true);
+ // int indices to reduce size
+ $columns = array_values($columns);
+ // remove Privileges to reduce size
+ for ($i = 0; $i < count($columns); $i++) {
+ unset($columns[$i]['Privileges']);
+ }
+
+ $indexes = $GLOBALS['dbi']->getTableIndexes($dbname, $tablename);
+
+ $snapshot = array('COLUMNS' => $columns, 'INDEXES' => $indexes);
+ $snapshot = serialize($snapshot);
+
+ // Get DROP TABLE / DROP VIEW and CREATE TABLE SQL statements
+ $sql_backquotes = true;
+
+ $create_sql = "";
+
+ if (self::$add_drop_table == true && $is_view == false) {
+ $create_sql .= self::getLogComment()
+ . 'DROP TABLE IF EXISTS ' . PMA_Util::backquote($tablename) . ";\n";
+
+ }
+
+ if (self::$add_drop_view == true && $is_view == true) {
+ $create_sql .= self::getLogComment()
+ . 'DROP VIEW IF EXISTS ' . PMA_Util::backquote($tablename) . ";\n";
+ }
+
+ $create_sql .= self::getLogComment() .
+ $export_sql_plugin->getTableDef($dbname, $tablename, "\n", "");
+
+ // Save version
+
+ $sql_query = "/*NOTRACK*/\n" .
+ "INSERT INTO" . self::$pma_table . " (" .
+ "db_name, " .
+ "table_name, " .
+ "version, " .
+ "date_created, " .
+ "date_updated, " .
+ "schema_snapshot, " .
+ "schema_sql, " .
+ "data_sql, " .
+ "tracking " .
+ ") " .
+ "values (
+ '" . PMA_Util::sqlAddSlashes($dbname) . "',
+ '" . PMA_Util::sqlAddSlashes($tablename) . "',
+ '" . PMA_Util::sqlAddSlashes($version) . "',
+ '" . PMA_Util::sqlAddSlashes($date) . "',
+ '" . PMA_Util::sqlAddSlashes($date) . "',
+ '" . PMA_Util::sqlAddSlashes($snapshot) . "',
+ '" . PMA_Util::sqlAddSlashes($create_sql) . "',
+ '" . PMA_Util::sqlAddSlashes("\n") . "',
+ '" . PMA_Util::sqlAddSlashes(self::_transformTrackingSet($tracking_set))
+ . "' )";
+
+ $result = PMA_queryAsControlUser($sql_query);
+
+ if ($result) {
+ // Deactivate previous version
+ self::deactivateTracking($dbname, $tablename, ($version - 1));
+ }
+
+ return $result;
+ }
+
+
+ /**
+ * Removes all tracking data for a table
+ *
+ * @param string $dbname name of database
+ * @param string $tablename name of table
+ *
+ * @static
+ *
+ * @return int result of version insertion
+ */
+ static public function deleteTracking($dbname, $tablename)
+ {
+ $sql_query = "/*NOTRACK*/\n"
+ . "DELETE FROM " . self::$pma_table
+ . " WHERE `db_name` = '"
+ . PMA_Util::sqlAddSlashes($dbname) . "'"
+ . " AND `table_name` = '"
+ . PMA_Util::sqlAddSlashes($tablename) . "'";
+ $result = PMA_queryAsControlUser($sql_query);
+
+ return $result;
+ }
+
+ /**
+ * Creates tracking version of a database
+ * (in other words: create a job to track future changes on the database).
+ *
+ * @param string $dbname name of database
+ * @param string $version version
+ * @param string $query query
+ * @param string $tracking_set set of tracking statements
+ *
+ * @static
+ *
+ * @return int result of version insertion
+ */
+ static public function createDatabaseVersion($dbname, $version, $query,
+ $tracking_set = 'CREATE DATABASE,ALTER DATABASE,DROP DATABASE'
+ ) {
+ $date = date('Y-m-d H:i:s');
+
+ if ($tracking_set == '') {
+ $tracking_set = self::$default_tracking_set;
+ }
+
+ $create_sql = "";
+
+ if (self::$add_drop_database == true) {
+ $create_sql .= self::getLogComment()
+ . 'DROP DATABASE IF EXISTS ' . PMA_Util::backquote($dbname) . ";\n";
+ }
+
+ $create_sql .= self::getLogComment() . $query;
+
+ // Save version
+ $sql_query = "/*NOTRACK*/\n" .
+ "INSERT INTO" . self::$pma_table . " (" .
+ "db_name, " .
+ "table_name, " .
+ "version, " .
+ "date_created, " .
+ "date_updated, " .
+ "schema_snapshot, " .
+ "schema_sql, " .
+ "data_sql, " .
+ "tracking " .
+ ") " .
+ "values (
+ '" . PMA_Util::sqlAddSlashes($dbname) . "',
+ '" . PMA_Util::sqlAddSlashes('') . "',
+ '" . PMA_Util::sqlAddSlashes($version) . "',
+ '" . PMA_Util::sqlAddSlashes($date) . "',
+ '" . PMA_Util::sqlAddSlashes($date) . "',
+ '" . PMA_Util::sqlAddSlashes('') . "',
+ '" . PMA_Util::sqlAddSlashes($create_sql) . "',
+ '" . PMA_Util::sqlAddSlashes("\n") . "',
+ '" . PMA_Util::sqlAddSlashes(self::_transformTrackingSet($tracking_set))
+ . "' )";
+
+ $result = PMA_queryAsControlUser($sql_query);
+
+ return $result;
+ }
+
+
+
+ /**
+ * Changes tracking of a table.
+ *
+ * @param string $dbname name of database
+ * @param string $tablename name of table
+ * @param string $version version
+ * @param integer $new_state the new state of tracking
+ *
+ * @static
+ *
+ * @return int result of SQL query
+ */
+ static private function _changeTracking($dbname, $tablename,
+ $version, $new_state
+ ) {
+
+ $sql_query = " UPDATE " . self::$pma_table .
+ " SET `tracking_active` = '" . $new_state . "' " .
+ " WHERE `db_name` = '" . PMA_Util::sqlAddSlashes($dbname) . "' " .
+ " AND `table_name` = '" . PMA_Util::sqlAddSlashes($tablename) . "' " .
+ " AND `version` = '" . PMA_Util::sqlAddSlashes($version) . "' ";
+
+ $result = PMA_queryAsControlUser($sql_query);
+
+ return $result;
+ }
+
+ /**
+ * Changes tracking data of a table.
+ *
+ * @param string $dbname name of database
+ * @param string $tablename name of table
+ * @param string $version version
+ * @param string $type type of data(DDL || DML)
+ * @param string|array $new_data the new tracking data
+ *
+ * @static
+ *
+ * @return bool result of change
+ */
+ static public function changeTrackingData($dbname, $tablename,
+ $version, $type, $new_data
+ ) {
+ if ($type == 'DDL') {
+ $save_to = 'schema_sql';
+ } elseif ($type == 'DML') {
+ $save_to = 'data_sql';
+ } else {
+ return false;
+ }
+ $date = date('Y-m-d H:i:s');
+
+ $new_data_processed = '';
+ if (is_array($new_data)) {
+ foreach ($new_data as $data) {
+ $new_data_processed .= '# log ' . $date . ' ' . $data['username']
+ . PMA_Util::sqlAddSlashes($data['statement']) . "\n";
+ }
+ } else {
+ $new_data_processed = $new_data;
+ }
+
+ $sql_query = " UPDATE " . self::$pma_table .
+ " SET `" . $save_to . "` = '" . $new_data_processed . "' " .
+ " WHERE `db_name` = '" . PMA_Util::sqlAddSlashes($dbname) . "' " .
+ " AND `table_name` = '" . PMA_Util::sqlAddSlashes($tablename) . "' " .
+ " AND `version` = '" . PMA_Util::sqlAddSlashes($version) . "' ";
+
+ $result = PMA_queryAsControlUser($sql_query);
+
+ return $result;
+ }
+
+ /**
+ * Activates tracking of a table.
+ *
+ * @param string $dbname name of database
+ * @param string $tablename name of table
+ * @param string $version version
+ *
+ * @static
+ *
+ * @return int result of SQL query
+ */
+ static public function activateTracking($dbname, $tablename, $version)
+ {
+ return self::_changeTracking($dbname, $tablename, $version, 1);
+ }
+
+
+ /**
+ * Deactivates tracking of a table.
+ *
+ * @param string $dbname name of database
+ * @param string $tablename name of table
+ * @param string $version version
+ *
+ * @static
+ *
+ * @return int result of SQL query
+ */
+ static public function deactivateTracking($dbname, $tablename, $version)
+ {
+ return self::_changeTracking($dbname, $tablename, $version, 0);
+ }
+
+
+ /**
+ * Gets the newest version of a tracking job
+ * (in other words: gets the HEAD version).
+ *
+ * @param string $dbname name of database
+ * @param string $tablename name of table
+ * @param string $statement tracked statement
+ *
+ * @static
+ *
+ * @return int (-1 if no version exists | > 0 if a version exists)
+ */
+ static public function getVersion($dbname, $tablename, $statement = null)
+ {
+ $sql_query = " SELECT MAX(version) FROM " . self::$pma_table .
+ " WHERE `db_name` = '" . PMA_Util::sqlAddSlashes($dbname) . "' " .
+ " AND `table_name` = '" . PMA_Util::sqlAddSlashes($tablename) . "' ";
+
+ if ($statement != "") {
+ if (PMA_DRIZZLE) {
+ $sql_query .= ' AND tracking & '
+ . self::_transformTrackingSet($statement) . ' <> 0';
+ } else {
+ $sql_query .= " AND FIND_IN_SET('"
+ . $statement . "',tracking) > 0" ;
+ }
+ }
+ $row = $GLOBALS['dbi']->fetchArray(PMA_queryAsControlUser($sql_query));
+ return isset($row[0])
+ ? $row[0]
+ : -1;
+ }
+
+
+ /**
+ * Gets the record of a tracking job.
+ *
+ * @param string $dbname name of database
+ * @param string $tablename name of table
+ * @param string $version version number
+ *
+ * @static
+ *
+ * @return mixed record DDM log, DDL log, structure snapshot, tracked
+ * statements.
+ */
+ static public function getTrackedData($dbname, $tablename, $version)
+ {
+ if (! isset(self::$pma_table)) {
+ self::init();
+ }
+ $sql_query = " SELECT * FROM " . self::$pma_table .
+ " WHERE `db_name` = '" . PMA_Util::sqlAddSlashes($dbname) . "' ";
+ if (! empty($tablename)) {
+ $sql_query .= " AND `table_name` = '"
+ . PMA_Util::sqlAddSlashes($tablename) ."' ";
+ }
+ $sql_query .= " AND `version` = '" . PMA_Util::sqlAddSlashes($version)
+ . "' " . " ORDER BY `version` DESC LIMIT 1";
+
+ $mixed = $GLOBALS['dbi']->fetchAssoc(PMA_queryAsControlUser($sql_query));
+
+ // Parse log
+ $log_schema_entries = explode('# log ', $mixed['schema_sql']);
+ $log_data_entries = explode('# log ', $mixed['data_sql']);
+
+ $ddl_date_from = $date = date('Y-m-d H:i:s');
+
+ $ddlog = array();
+ $i = 0;
+
+ // Iterate tracked data definition statements
+ // For each log entry we want to get date, username and statement
+ foreach ($log_schema_entries as $log_entry) {
+ if (trim($log_entry) != '') {
+ $date = substr($log_entry, 0, 19);
+ $username = substr($log_entry, 20, strpos($log_entry, "\n") - 20);
+ if ($i == 0) {
+ $ddl_date_from = $date;
+ }
+ $statement = rtrim(strstr($log_entry, "\n"));
+
+ $ddlog[] = array( 'date' => $date,
+ 'username'=> $username,
+ 'statement' => $statement );
+ $i++;
+ }
+ }
+
+ $date_from = $ddl_date_from;
+ $ddl_date_to = $date;
+
+ $dml_date_from = $date_from;
+
+ $dmlog = array();
+ $i = 0;
+
+ // Iterate tracked data manipulation statements
+ // For each log entry we want to get date, username and statement
+ foreach ($log_data_entries as $log_entry) {
+ if (trim($log_entry) != '') {
+ $date = substr($log_entry, 0, 19);
+ $username = substr($log_entry, 20, strpos($log_entry, "\n") - 20);
+ if ($i == 0) {
+ $dml_date_from = $date;
+ }
+ $statement = rtrim(strstr($log_entry, "\n"));
+
+ $dmlog[] = array( 'date' => $date,
+ 'username' => $username,
+ 'statement' => $statement );
+ $i++;
+ }
+ }
+
+ $dml_date_to = $date;
+
+ // Define begin and end of date range for both logs
+ if (strtotime($ddl_date_from) <= strtotime($dml_date_from)) {
+ $data['date_from'] = $ddl_date_from;
+ } else {
+ $data['date_from'] = $dml_date_from;
+ }
+ if (strtotime($ddl_date_to) >= strtotime($dml_date_to)) {
+ $data['date_to'] = $ddl_date_to;
+ } else {
+ $data['date_to'] = $dml_date_to;
+ }
+ $data['ddlog'] = $ddlog;
+ $data['dmlog'] = $dmlog;
+ $data['tracking'] = self::_transformTrackingSet($mixed['tracking']);
+ $data['schema_snapshot'] = $mixed['schema_snapshot'];
+
+ return $data;
+ }
+
+
+ /**
+ * Parses a query. Gets
+ * - statement identifier (UPDATE, ALTER TABLE, ...)
+ * - type of statement, is it part of DDL or DML ?
+ * - tablename
+ *
+ * @param string $query query
+ *
+ * @static
+ * @todo: using PMA SQL Parser when possible
+ * @todo: support multi-table/view drops
+ *
+ * @return mixed Array containing identifier, type and tablename.
+ *
+ */
+ static public function parseQuery($query)
+ {
+
+ // Usage of PMA_SQP does not work here
+ //
+ // require_once("libraries/sqlparser.lib.php");
+ // $parsed_sql = PMA_SQP_parse($query);
+ // $sql_info = PMA_SQP_analyze($parsed_sql);
+
+ $query = str_replace("\n", " ", $query);
+ $query = str_replace("\r", " ", $query);
+
+ $query = trim($query);
+ $query = trim($query, ' -');
+
+ $tokens = explode(" ", $query);
+ foreach ($tokens as $key => $value) {
+ $tokens[$key] = strtoupper($value);
+ }
+
+ // Parse USE statement, need it for SQL dump imports
+ if (substr($query, 0, 4) == 'USE ') {
+ $prefix = explode('USE ', $query);
+ $GLOBALS['db'] = self::getTableName($prefix[1]);
+ }
+
+ /*
+ * DDL statements
+ */
+
+ $result['type'] = 'DDL';
+
+ // Parse CREATE VIEW statement
+ if (in_array('CREATE', $tokens) == true
+ && in_array('VIEW', $tokens) == true
+ && in_array('AS', $tokens) == true
+ ) {
+ $result['identifier'] = 'CREATE VIEW';
+
+ $index = array_search('VIEW', $tokens);
+
+ $result['tablename'] = strtolower(
+ self::getTableName($tokens[$index + 1])
+ );
+ }
+
+ // Parse ALTER VIEW statement
+ if (in_array('ALTER', $tokens) == true
+ && in_array('VIEW', $tokens) == true
+ && in_array('AS', $tokens) == true
+ && ! isset($result['identifier'])
+ ) {
+ $result['identifier'] = 'ALTER VIEW';
+
+ $index = array_search('VIEW', $tokens);
+
+ $result['tablename'] = strtolower(
+ self::getTableName($tokens[$index + 1])
+ );
+ }
+
+ // Parse DROP VIEW statement
+ if (! isset($result['identifier'])
+ && substr($query, 0, 10) == 'DROP VIEW '
+ ) {
+ $result['identifier'] = 'DROP VIEW';
+
+ $prefix = explode('DROP VIEW ', $query);
+ $str = strstr($prefix[1], 'IF EXISTS');
+
+ if ($str == false ) {
+ $str = $prefix[1];
+ }
+ $result['tablename'] = self::getTableName($str);
+ }
+
+ // Parse CREATE DATABASE statement
+ if (! isset($result['identifier'])
+ && substr($query, 0, 15) == 'CREATE DATABASE'
+ ) {
+ $result['identifier'] = 'CREATE DATABASE';
+ $str = str_replace('CREATE DATABASE', '', $query);
+ $str = str_replace('IF NOT EXISTS', '', $str);
+
+ $prefix = explode('DEFAULT ', $str);
+
+ $result['tablename'] = '';
+ $GLOBALS['db'] = self::getTableName($prefix[0]);
+ }
+
+ // Parse ALTER DATABASE statement
+ if (! isset($result['identifier'])
+ && substr($query, 0, 14) == 'ALTER DATABASE'
+ ) {
+ $result['identifier'] = 'ALTER DATABASE';
+ $result['tablename'] = '';
+ }
+
+ // Parse DROP DATABASE statement
+ if (! isset($result['identifier'])
+ && substr($query, 0, 13) == 'DROP DATABASE'
+ ) {
+ $result['identifier'] = 'DROP DATABASE';
+ $str = str_replace('DROP DATABASE', '', $query);
+ $str = str_replace('IF EXISTS', '', $str);
+ $GLOBALS['db'] = self::getTableName($str);
+ $result['tablename'] = '';
+ }
+
+ // Parse CREATE TABLE statement
+ if (! isset($result['identifier'])
+ && substr($query, 0, 12) == 'CREATE TABLE'
+ ) {
+ $result['identifier'] = 'CREATE TABLE';
+ $query = str_replace('IF NOT EXISTS', '', $query);
+ $prefix = explode('CREATE TABLE ', $query);
+ $suffix = explode('(', $prefix[1]);
+ $result['tablename'] = self::getTableName($suffix[0]);
+ }
+
+ // Parse ALTER TABLE statement
+ if (! isset($result['identifier'])
+ && substr($query, 0, 12) == 'ALTER TABLE '
+ ) {
+ $result['identifier'] = 'ALTER TABLE';
+
+ $prefix = explode('ALTER TABLE ', $query);
+ $suffix = explode(' ', $prefix[1]);
+ $result['tablename'] = self::getTableName($suffix[0]);
+ }
+
+ // Parse DROP TABLE statement
+ if (! isset($result['identifier'])
+ && substr($query, 0, 11) == 'DROP TABLE '
+ ) {
+ $result['identifier'] = 'DROP TABLE';
+
+ $prefix = explode('DROP TABLE ', $query);
+ $str = strstr($prefix[1], 'IF EXISTS');
+
+ if ($str == false ) {
+ $str = $prefix[1];
+ }
+ $result['tablename'] = self::getTableName($str);
+ }
+
+ // Parse CREATE INDEX statement
+ if (! isset($result['identifier'])
+ && (substr($query, 0, 12) == 'CREATE INDEX'
+ || substr($query, 0, 19) == 'CREATE UNIQUE INDEX'
+ || substr($query, 0, 20) == 'CREATE SPATIAL INDEX')
+ ) {
+ $result['identifier'] = 'CREATE INDEX';
+ $prefix = explode('ON ', $query);
+ $suffix = explode('(', $prefix[1]);
+ $result['tablename'] = self::getTableName($suffix[0]);
+ }
+
+ // Parse DROP INDEX statement
+ if (! isset($result['identifier'])
+ && substr($query, 0, 10) == 'DROP INDEX'
+ ) {
+ $result['identifier'] = 'DROP INDEX';
+ $prefix = explode('ON ', $query);
+ $result['tablename'] = self::getTableName($prefix[1]);
+ }
+
+ // Parse RENAME TABLE statement
+ if (! isset($result['identifier'])
+ && substr($query, 0, 13) == 'RENAME TABLE '
+ ) {
+ $result['identifier'] = 'RENAME TABLE';
+ $prefix = explode('RENAME TABLE ', $query);
+ $names = explode(' TO ', $prefix[1]);
+ $result['tablename'] = self::getTableName($names[0]);
+ $result["tablename_after_rename"] = self::getTableName($names[1]);
+ }
+
+ /*
+ * DML statements
+ */
+
+ if (! isset($result['identifier'])) {
+ $result["type"] = 'DML';
+ }
+ // Parse UPDATE statement
+ if (! isset($result['identifier'])
+ && substr($query, 0, 6) == 'UPDATE'
+ ) {
+ $result['identifier'] = 'UPDATE';
+ $prefix = explode('UPDATE ', $query);
+ $suffix = explode(' ', $prefix[1]);
+ $result['tablename'] = self::getTableName($suffix[0]);
+ }
+
+ // Parse INSERT INTO statement
+ if (! isset($result['identifier'])
+ && substr($query, 0, 11) == 'INSERT INTO'
+ ) {
+ $result['identifier'] = 'INSERT';
+ $prefix = explode('INSERT INTO', $query);
+ $suffix = explode('(', $prefix[1]);
+ $result['tablename'] = self::getTableName($suffix[0]);
+ }
+
+ // Parse DELETE statement
+ if (! isset($result['identifier'])
+ && substr($query, 0, 6) == 'DELETE'
+ ) {
+ $result['identifier'] = 'DELETE';
+ $prefix = explode('FROM ', $query);
+ $suffix = explode(' ', $prefix[1]);
+ $result['tablename'] = self::getTableName($suffix[0]);
+ }
+
+ // Parse TRUNCATE statement
+ if (! isset($result['identifier'])
+ && substr($query, 0, 8) == 'TRUNCATE'
+ ) {
+ $result['identifier'] = 'TRUNCATE';
+ $prefix = explode('TRUNCATE', $query);
+ $result['tablename'] = self::getTableName($prefix[1]);
+ }
+
+ return $result;
+ }
+
+
+ /**
+ * Analyzes a given SQL statement and saves tracking data.
+ *
+ * @param string $query a SQL query
+ *
+ * @static
+ *
+ * @return void
+ */
+ static public function handleQuery($query)
+ {
+ // If query is marked as untouchable, leave
+ if (strstr($query, "/*NOTRACK*/")) {
+ return;
+ }
+
+ if (! (substr($query, -1) == ';')) {
+ $query = $query . ";\n";
+ }
+ // Get some information about query
+ $result = self::parseQuery($query);
+
+ // Get database name
+ $dbname = trim(isset($GLOBALS['db']) ? $GLOBALS['db'] : '', '`');
+ // $dbname can be empty, for example when coming from Synchronize
+ // and this is a query for the remote server
+ if (empty($dbname)) {
+ return;
+ }
+ // Remove null bytes (preg_replace() is vulnerable in some
+ // PHP versions)
+ $dbname = str_replace("\0", "", $dbname);
+
+ // If we found a valid statement
+ if (isset($result['identifier'])) {
+ $version = self::getVersion(
+ $dbname, $result['tablename'], $result['identifier']
+ );
+
+ // If version not exists and auto-creation is enabled
+ if (self::$version_auto_create == true
+ && self::isTracked($dbname, $result['tablename']) == false
+ && $version == -1
+ ) {
+ // Create the version
+
+ switch ($result['identifier']) {
+ case 'CREATE TABLE':
+ self::createVersion($dbname, $result['tablename'], '1');
+ break;
+ case 'CREATE VIEW':
+ self::createVersion(
+ $dbname, $result['tablename'], '1', '', true
+ );
+ break;
+ case 'CREATE DATABASE':
+ self::createDatabaseVersion($dbname, '1', $query);
+ break;
+ } // end switch
+ }
+
+ // If version exists
+ if (self::isTracked($dbname, $result['tablename']) && $version != -1) {
+ if ($result['type'] == 'DDL') {
+ $save_to = 'schema_sql';
+ } elseif ($result['type'] == 'DML') {
+ $save_to = 'data_sql';
+ } else {
+ $save_to = '';
+ }
+ $date = date('Y-m-d H:i:s');
+
+ // Cut off `dbname`. from query
+ $query = preg_replace('/`' . $dbname . '`\s?\./', '', $query);
+
+ // Add log information
+ $query = self::getLogComment() . $query ;
+
+ // Mark it as untouchable
+ $sql_query = " /*NOTRACK*/\n"
+ . " UPDATE " . self::$pma_table
+ . " SET " . PMA_Util::backquote($save_to)
+ . " = CONCAT( " . PMA_Util::backquote($save_to) . ",'\n"
+ . PMA_Util::sqlAddSlashes($query) . "') ,"
+ . " `date_updated` = '" . $date . "' ";
+
+ // If table was renamed we have to change
+ // the tablename attribute in pma_tracking too
+ if ($result['identifier'] == 'RENAME TABLE') {
+ $sql_query .= ', `table_name` = \''
+ . PMA_Util::sqlAddSlashes($result['tablename_after_rename'])
+ . '\' ';
+ }
+
+ // Save the tracking information only for
+ // 1. the database
+ // 2. the table / view
+ // 3. the statements
+ // we want to track
+ $sql_query .=
+ " WHERE FIND_IN_SET('" . $result['identifier'] . "',tracking) > 0" .
+ " AND `db_name` = '" . PMA_Util::sqlAddSlashes($dbname) . "' " .
+ " AND `table_name` = '"
+ . PMA_Util::sqlAddSlashes($result['tablename']) . "' " .
+ " AND `version` = '" . PMA_Util::sqlAddSlashes($version) . "' ";
+
+ $result = PMA_queryAsControlUser($sql_query);
+ }
+ }
+ }
+
+ /**
+ * Transforms tracking set for Drizzle, which has no SET type
+ *
+ * Converts int<>string for Drizzle, does nothing for MySQL
+ *
+ * @param int|string $tracking_set Set to convert
+ *
+ * @return int|string
+ */
+ static private function _transformTrackingSet($tracking_set)
+ {
+ if (!PMA_DRIZZLE) {
+ return $tracking_set;
+ }
+
+ // init conversion array (key 3 doesn't exist in calculated array)
+ if (isset(self::$_tracking_set_flags[3])) {
+ // initialize flags
+ $set = self::$_tracking_set_flags;
+ $array = array();
+ for ($i = 0; $i < count($set); $i++) {
+ $flag = 1 << $i;
+ $array[$flag] = $set[$i];
+ $array[$set[$i]] = $flag;
+ }
+ self::$_tracking_set_flags = $array;
+ }
+
+ if (is_numeric($tracking_set)) {
+ // int > string conversion
+ $aflags = array();
+ // count/2 - conversion table has both int > string
+ // and string > int values
+ for ($i = 0; $i < count(self::$_tracking_set_flags)/2; $i++) {
+ $flag = 1 << $i;
+ if ($tracking_set & $flag) {
+ $aflags[] = self::$_tracking_set_flags[$flag];
+ }
+ }
+ $flags = implode(',', $aflags);
+ } else {
+ // string > int conversion
+ $flags = 0;
+ foreach (explode(',', $tracking_set) as $strflag) {
+ if ($strflag == '') {
+ continue;
+ }
+ $flags |= self::$_tracking_set_flags[$strflag];
+ }
+ }
+
+ return $flags;
+ }
+}
+?>
diff --git a/libraries/Types.class.php b/libraries/Types.class.php
new file mode 100644
index 0000000000..455c27dc90
--- /dev/null
+++ b/libraries/Types.class.php
@@ -0,0 +1,986 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * SQL data types definition
+ *
+ * @package PhpMyAdmin
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ * Generic class holding type definitions.
+ *
+ * @package PhpMyAdmin
+ */
+class PMA_Types
+{
+ /**
+ * Returns list of unary operators.
+ *
+ * @return array
+ */
+ public function getUnaryOperators()
+ {
+ return array(
+ 'IS NULL',
+ 'IS NOT NULL',
+ "= ''",
+ "!= ''",
+ );
+ }
+
+ /**
+ * Check whether operator is unary.
+ *
+ * @param string $op operator name
+ *
+ * @return boolean
+ */
+ public function isUnaryOperator($op)
+ {
+ return in_array($op, $this->getUnaryOperators());
+ }
+
+ /**
+ * Returns list of operators checking for NULL.
+ *
+ * @return array
+ */
+ public function getNullOperators()
+ {
+ return array(
+ 'IS NULL',
+ 'IS NOT NULL',
+ );
+ }
+
+ /**
+ * ENUM search operators
+ *
+ * @return array
+ */
+ public function getEnumOperators()
+ {
+ return array(
+ '=',
+ '!=',
+ );
+ }
+
+ /**
+ * TEXT search operators
+ *
+ * @return array
+ */
+ public function getTextOperators()
+ {
+ return array(
+ 'LIKE',
+ 'LIKE %...%',
+ 'NOT LIKE',
+ '=',
+ '!=',
+ 'REGEXP',
+ 'REGEXP ^...$',
+ 'NOT REGEXP',
+ "= ''",
+ "!= ''",
+ 'IN (...)',
+ 'NOT IN (...)',
+ 'BETWEEN',
+ 'NOT BETWEEN',
+ );
+ }
+
+ /**
+ * Number search operators
+ *
+ * @return array
+ */
+ public function getNumberOperators()
+ {
+ return array(
+ '=',
+ '>',
+ '>=',
+ '<',
+ '<=',
+ '!=',
+ 'LIKE',
+ 'LIKE %...%',
+ 'NOT LIKE',
+ 'IN (...)',
+ 'NOT IN (...)',
+ 'BETWEEN',
+ 'NOT BETWEEN',
+ );
+ }
+
+ /**
+ * Returns operators for given type
+ *
+ * @param string $type Type of field
+ * @param boolean $null Whether field can be NULL
+ *
+ * @return array
+ */
+ public function getTypeOperators($type, $null)
+ {
+ $ret = array();
+ $class = $this->getTypeClass($type);
+
+ if (strncasecmp($type, 'enum', 4) == 0) {
+ $ret = array_merge($ret, $this->getEnumOperators());
+ } elseif ($class == 'CHAR') {
+ $ret = array_merge($ret, $this->getTextOperators());
+ } else {
+ $ret = array_merge($ret, $this->getNumberOperators());
+ }
+
+ if ($null) {
+ $ret = array_merge($ret, $this->getNullOperators());
+ }
+
+ return $ret;
+ }
+
+ /**
+ * Returns operators for given type as html options
+ *
+ * @param string $type Type of field
+ * @param boolean $null Whether field can be NULL
+ * @param string $selectedOperator Option to be selected
+ *
+ * @return string Generated Html
+ */
+ public function getTypeOperatorsHtml($type, $null, $selectedOperator = null)
+ {
+ $html = '';
+
+ foreach ($this->getTypeOperators($type, $null) as $fc) {
+ if (isset($selectedOperator) && $selectedOperator == $fc) {
+ $selected = ' selected="selected"';
+ } else {
+ $selected = '';
+ }
+ $html .= '<option value="' . htmlspecialchars($fc) . '"'
+ . $selected . '>'
+ . htmlspecialchars($fc) . '</option>';
+ }
+
+ return $html;
+ }
+
+ /**
+ * Returns the data type description.
+ *
+ * @param string $type The data type to get a description.
+ *
+ * @return string
+ *
+ */
+ public function getTypeDescription($type)
+ {
+ return '';
+ }
+
+ /**
+ * Returns class of a type, used for functions available for type
+ * or default values.
+ *
+ * @param string $type The data type to get a class.
+ *
+ * @return string
+ *
+ */
+ public function getTypeClass($type)
+ {
+ return '';
+ }
+
+ /**
+ * Returns array of functions available for a class.
+ *
+ * @param string $class The class to get function list.
+ *
+ * @return array
+ *
+ */
+ public function getFunctionsClass($class)
+ {
+ return array();
+ }
+
+ /**
+ * Returns array of functions available for a type.
+ *
+ * @param string $type The data type to get function list.
+ *
+ * @return array
+ *
+ */
+ public function getFunctions($type)
+ {
+ $class = $this->getTypeClass($type);
+ return $this->getFunctionsClass($class);
+ }
+
+ /**
+ * Returns array of all functions available.
+ *
+ * @return array
+ *
+ */
+ public function getAllFunctions()
+ {
+ $ret = array_merge(
+ $this->getFunctionsClass('CHAR'),
+ $this->getFunctionsClass('NUMBER'),
+ $this->getFunctionsClass('DATE'),
+ $this->getFunctionsClass('UUID')
+ );
+ sort($ret);
+ return $ret;
+ }
+
+ /**
+ * Returns array of all attributes available.
+ *
+ * @return array
+ *
+ */
+ public function getAttributes()
+ {
+ return array();
+ }
+
+ /**
+ * Returns array of all column types available.
+ *
+ * @return array
+ *
+ */
+ public function getColumns()
+ {
+ // most used types
+ return array(
+ 'INT',
+ 'VARCHAR',
+ 'TEXT',
+ 'DATE',
+ );
+ }
+}
+
+/**
+ * Class holding type definitions for MySQL.
+ *
+ * @package PhpMyAdmin
+ */
+class PMA_Types_MySQL extends PMA_Types
+{
+ /**
+ * Returns the data type description.
+ *
+ * @param string $type The data type to get a description.
+ *
+ * @return string
+ *
+ */
+ public function getTypeDescription($type)
+ {
+ $type = strtoupper($type);
+ switch ($type) {
+ case 'TINYINT':
+ return __('A 1-byte integer, signed range is -128 to 127, unsigned range is 0 to 255');
+ case 'SMALLINT':
+ return __('A 2-byte integer, signed range is -32,768 to 32,767, unsigned range is 0 to 65,535');
+ case 'MEDIUMINT':
+ return __('A 3-byte integer, signed range is -8,388,608 to 8,388,607, unsigned range is 0 to 16,777,215');
+ case 'INT':
+ return __('A 4-byte integer, signed range is -2,147,483,648 to 2,147,483,647, unsigned range is 0 to 4,294,967,295');
+ case 'BIGINT':
+ return __('An 8-byte integer, signed range is -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807, unsigned range is 0 to 18,446,744,073,709,551,615');
+ case 'DECIMAL':
+ return __('A fixed-point number (M, D) - the maximum number of digits (M) is 65 (default 10), the maximum number of decimals (D) is 30 (default 0)');
+ case 'FLOAT':
+ return __('A small floating-point number, allowable values are -3.402823466E+38 to -1.175494351E-38, 0, and 1.175494351E-38 to 3.402823466E+38');
+ case 'DOUBLE':
+ return __('A double-precision floating-point number, allowable values are -1.7976931348623157E+308 to -2.2250738585072014E-308, 0, and 2.2250738585072014E-308 to 1.7976931348623157E+308');
+ case 'REAL':
+ return __('Synonym for DOUBLE (exception: in REAL_AS_FLOAT SQL mode it is a synonym for FLOAT)');
+ case 'BIT':
+ return __('A bit-field type (M), storing M of bits per value (default is 1, maximum is 64)');
+ case 'BOOLEAN':
+ return __('A synonym for TINYINT(1), a value of zero is considered false, nonzero values are considered true');
+ case 'SERIAL':
+ return __('An alias for BIGINT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE');
+ case 'DATE':
+ return sprintf(__('A date, supported range is %1$s to %2$s'), '1000-01-01', '9999-12-31');
+ case 'DATETIME':
+ return sprintf(__('A date and time combination, supported range is %1$s to %2$s'), '1000-01-01 00:00:00', '9999-12-31 23:59:59');
+ case 'TIMESTAMP':
+ return __('A timestamp, range is 1970-01-01 00:00:01 UTC to 2038-01-09 03:14:07 UTC, stored as the number of seconds since the epoch (1970-01-01 00:00:00 UTC)');
+ case 'TIME':
+ return sprintf(__('A time, range is %1$s to %2$s'), '-838:59:59', '838:59:59');
+ case 'YEAR':
+ return __("A year in four-digit (4, default) or two-digit (2) format, the allowable values are 70 (1970) to 69 (2069) or 1901 to 2155 and 0000");
+ case 'CHAR':
+ return __('A fixed-length (0-255, default 1) string that is always right-padded with spaces to the specified length when stored');
+ case 'VARCHAR':
+ return sprintf(__('A variable-length (%s) string, the effective maximum length is subject to the maximum row size'), '0-65,535');
+ case 'TINYTEXT':
+ return __('A TEXT column with a maximum length of 255 (2^8 - 1) characters, stored with a one-byte prefix indicating the length of the value in bytes');
+ case 'TEXT':
+ return __('A TEXT column with a maximum length of 65,535 (2^16 - 1) characters, stored with a two-byte prefix indicating the length of the value in bytes');
+ case 'MEDIUMTEXT':
+ return __('A TEXT column with a maximum length of 16,777,215 (2^24 - 1) characters, stored with a three-byte prefix indicating the length of the value in bytes');
+ case 'LONGTEXT':
+ return __('A TEXT column with a maximum length of 4,294,967,295 or 4GiB (2^32 - 1) characters, stored with a four-byte prefix indicating the length of the value in bytes');
+ case 'BINARY':
+ return __('Similar to the CHAR type, but stores binary byte strings rather than non-binary character strings');
+ case 'VARBINARY':
+ return __('Similar to the VARCHAR type, but stores binary byte strings rather than non-binary character strings');
+ case 'TINYBLOB':
+ return __('A BLOB column with a maximum length of 255 (2^8 - 1) bytes, stored with a one-byte prefix indicating the length of the value');
+ case 'MEDIUMBLOB':
+ return __('A BLOB column with a maximum length of 16,777,215 (2^24 - 1) bytes, stored with a three-byte prefix indicating the length of the value');
+ case 'BLOB':
+ return __('A BLOB column with a maximum length of 65,535 (2^16 - 1) bytes, stored with a two-byte prefix indicating the length of the value');
+ case 'LONGBLOB':
+ return __('A BLOB column with a maximum length of 4,294,967,295 or 4GiB (2^32 - 1) bytes, stored with a four-byte prefix indicating the length of the value');
+ case 'ENUM':
+ return __("An enumeration, chosen from the list of up to 65,535 values or the special '' error value");
+ case 'SET':
+ return __("A single value chosen from a set of up to 64 members");
+ case 'GEOMETRY':
+ return __('A type that can store a geometry of any type');
+ case 'POINT':
+ return __('A point in 2-dimensional space');
+ case 'LINESTRING':
+ return __('A curve with linear interpolation between points');
+ case 'POLYGON':
+ return __('A polygon');
+ case 'MULTIPOINT':
+ return __('A collection of points');
+ case 'MULTILINESTRING':
+ return __('A collection of curves with linear interpolation between points');
+ case 'MULTIPOLYGON':
+ return __('A collection of polygons');
+ case 'GEOMETRYCOLLECTION':
+ return __('A collection of geometry objects of any type');
+ }
+ return '';
+ }
+
+ /**
+ * Returns class of a type, used for functions available for type
+ * or default values.
+ *
+ * @param string $type The data type to get a class.
+ *
+ * @return string
+ *
+ */
+ public function getTypeClass($type)
+ {
+ $type = strtoupper($type);
+ switch ($type) {
+ case 'TINYINT':
+ case 'SMALLINT':
+ case 'MEDIUMINT':
+ case 'INT':
+ case 'BIGINT':
+ case 'DECIMAL':
+ case 'FLOAT':
+ case 'DOUBLE':
+ case 'REAL':
+ case 'BIT':
+ case 'BOOLEAN':
+ case 'SERIAL':
+ return 'NUMBER';
+
+ case 'DATE':
+ case 'DATETIME':
+ case 'TIMESTAMP':
+ case 'TIME':
+ case 'YEAR':
+ return 'DATE';
+
+ case 'CHAR':
+ case 'VARCHAR':
+ case 'TINYTEXT':
+ case 'TEXT':
+ case 'MEDIUMTEXT':
+ case 'LONGTEXT':
+ case 'BINARY':
+ case 'VARBINARY':
+ case 'TINYBLOB':
+ case 'MEDIUMBLOB':
+ case 'BLOB':
+ case 'LONGBLOB':
+ case 'ENUM':
+ case 'SET':
+ return 'CHAR';
+
+ case 'GEOMETRY':
+ case 'POINT':
+ case 'LINESTRING':
+ case 'POLYGON':
+ case 'MULTIPOINT':
+ case 'MULTILINESTRING':
+ case 'MULTIPOLYGON':
+ case 'GEOMETRYCOLLECTION':
+ return 'SPATIAL';
+ }
+
+ return '';
+ }
+
+ /**
+ * Returns array of functions available for a class.
+ *
+ * @param string $class The class to get function list.
+ *
+ * @return array
+ *
+ */
+ public function getFunctionsClass($class)
+ {
+ switch ($class) {
+ case 'CHAR':
+ return array(
+ 'AES_ENCRYPT',
+ 'BIN',
+ 'CHAR',
+ 'COMPRESS',
+ 'CURRENT_USER',
+ 'DATABASE',
+ 'DAYNAME',
+ 'DES_DECRYPT',
+ 'DES_ENCRYPT',
+ 'ENCRYPT',
+ 'HEX',
+ 'INET_NTOA',
+ 'LOAD_FILE',
+ 'LOWER',
+ 'LTRIM',
+ 'MD5',
+ 'MONTHNAME',
+ 'OLD_PASSWORD',
+ 'PASSWORD',
+ 'QUOTE',
+ 'REVERSE',
+ 'RTRIM',
+ 'SHA1',
+ 'SOUNDEX',
+ 'SPACE',
+ 'TRIM',
+ 'UNCOMPRESS',
+ 'UNHEX',
+ 'UPPER',
+ 'USER',
+ 'UUID',
+ 'VERSION',
+ );
+
+ case 'DATE':
+ return array(
+ 'CURRENT_DATE',
+ 'CURRENT_TIME',
+ 'DATE',
+ 'FROM_DAYS',
+ 'FROM_UNIXTIME',
+ 'LAST_DAY',
+ 'NOW',
+ 'SEC_TO_TIME',
+ 'SYSDATE',
+ 'TIME',
+ 'TIMESTAMP',
+ 'UTC_DATE',
+ 'UTC_TIME',
+ 'UTC_TIMESTAMP',
+ 'YEAR',
+ );
+
+ case 'NUMBER':
+ $ret = array(
+ 'ABS',
+ 'ACOS',
+ 'ASCII',
+ 'ASIN',
+ 'ATAN',
+ 'BIT_LENGTH',
+ 'BIT_COUNT',
+ 'CEILING',
+ 'CHAR_LENGTH',
+ 'CONNECTION_ID',
+ 'COS',
+ 'COT',
+ 'CRC32',
+ 'DAYOFMONTH',
+ 'DAYOFWEEK',
+ 'DAYOFYEAR',
+ 'DEGREES',
+ 'EXP',
+ 'FLOOR',
+ 'HOUR',
+ 'INET_ATON',
+ 'LENGTH',
+ 'LN',
+ 'LOG',
+ 'LOG2',
+ 'LOG10',
+ 'MICROSECOND',
+ 'MINUTE',
+ 'MONTH',
+ 'OCT',
+ 'ORD',
+ 'PI',
+ 'QUARTER',
+ 'RADIANS',
+ 'RAND',
+ 'ROUND',
+ 'SECOND',
+ 'SIGN',
+ 'SIN',
+ 'SQRT',
+ 'TAN',
+ 'TO_DAYS',
+ 'TO_SECONDS',
+ 'TIME_TO_SEC',
+ 'UNCOMPRESSED_LENGTH',
+ 'UNIX_TIMESTAMP',
+ 'UUID_SHORT',
+ 'WEEK',
+ 'WEEKDAY',
+ 'WEEKOFYEAR',
+ 'YEARWEEK',
+ );
+ // remove functions that are unavailable on current server
+ if (PMA_MYSQL_INT_VERSION < 50500) {
+ $ret = array_diff($ret, array('TO_SECONDS'));
+ }
+ if (PMA_MYSQL_INT_VERSION < 50120) {
+ $ret = array_diff($ret, array('UUID_SHORT'));
+ }
+ return $ret;
+
+ case 'SPATIAL':
+ return array(
+ 'GeomFromText',
+ 'GeomFromWKB',
+
+ 'GeomCollFromText',
+ 'LineFromText',
+ 'MLineFromText',
+ 'PointFromText',
+ 'MPointFromText',
+ 'PolyFromText',
+ 'MPolyFromText',
+
+ 'GeomCollFromWKB',
+ 'LineFromWKB',
+ 'MLineFromWKB',
+ 'PointFromWKB',
+ 'MPointFromWKB',
+ 'PolyFromWKB',
+ 'MPolyFromWKB',
+ );
+ }
+ return array();
+ }
+
+ /**
+ * Returns array of all attributes available.
+ *
+ * @return array
+ *
+ */
+ public function getAttributes()
+ {
+ return array(
+ '',
+ 'BINARY',
+ 'UNSIGNED',
+ 'UNSIGNED ZEROFILL',
+ 'on update CURRENT_TIMESTAMP',
+ );
+ }
+
+ /**
+ * Returns array of all column types available.
+ *
+ * VARCHAR, TINYINT, TEXT and DATE are listed first, based on
+ * estimated popularity.
+ *
+ * @return array
+ *
+ */
+ public function getColumns()
+ {
+ $ret = parent::getColumns();
+ // numeric
+ $ret[_pgettext('numeric types', 'Numeric')] = array(
+ 'TINYINT',
+ 'SMALLINT',
+ 'MEDIUMINT',
+ 'INT',
+ 'BIGINT',
+ '-',
+ 'DECIMAL',
+ 'FLOAT',
+ 'DOUBLE',
+ 'REAL',
+ '-',
+ 'BIT',
+ 'BOOLEAN',
+ 'SERIAL',
+ );
+
+
+ // Date/Time
+ $ret[_pgettext('date and time types', 'Date and time')] = array(
+ 'DATE',
+ 'DATETIME',
+ 'TIMESTAMP',
+ 'TIME',
+ 'YEAR',
+ );
+
+ // Text
+ $ret[_pgettext('string types', 'String')] = array(
+ 'CHAR',
+ 'VARCHAR',
+ '-',
+ 'TINYTEXT',
+ 'TEXT',
+ 'MEDIUMTEXT',
+ 'LONGTEXT',
+ '-',
+ 'BINARY',
+ 'VARBINARY',
+ '-',
+ 'TINYBLOB',
+ 'MEDIUMBLOB',
+ 'BLOB',
+ 'LONGBLOB',
+ '-',
+ 'ENUM',
+ 'SET',
+ );
+
+ $ret[_pgettext('spatial types', 'Spatial')] = array(
+ 'GEOMETRY',
+ 'POINT',
+ 'LINESTRING',
+ 'POLYGON',
+ 'MULTIPOINT',
+ 'MULTILINESTRING',
+ 'MULTIPOLYGON',
+ 'GEOMETRYCOLLECTION',
+ );
+
+ return $ret;
+ }
+}
+
+/**
+ * Class holding type definitions for Drizzle.
+ *
+ * @package PhpMyAdmin
+ */
+class PMA_Types_Drizzle extends PMA_Types
+{
+ /**
+ * Returns the data type description.
+ *
+ * @param string $type The data type to get a description.
+ *
+ * @return string
+ *
+ */
+ public function getTypeDescription($type)
+ {
+ $type = strtoupper($type);
+ switch ($type) {
+ case 'INTEGER':
+ return __('A 4-byte integer, range is -2,147,483,648 to 2,147,483,647');
+ case 'BIGINT':
+ return __('An 8-byte integer, range is -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807');
+ case 'DECIMAL':
+ return __('A fixed-point number (M, D) - the maximum number of digits (M) is 65 (default 10), the maximum number of decimals (D) is 30 (default 0)');
+ case 'DOUBLE':
+ return __("A system's default double-precision floating-point number");
+ case 'BOOLEAN':
+ return __('True or false');
+ case 'SERIAL':
+ return __('An alias for BIGINT NOT NULL AUTO_INCREMENT UNIQUE');
+ case 'UUID':
+ return __('Stores a Universally Unique Identifier (UUID)');
+ case 'DATE':
+ return sprintf(__('A date, supported range is %1$s to %2$s'), '0001-01-01', '9999-12-31');
+ case 'DATETIME':
+ return sprintf(__('A date and time combination, supported range is %1$s to %2$s'), '0001-01-01 00:00:0', '9999-12-31 23:59:59');
+ case 'TIMESTAMP':
+ return __("A timestamp, range is '0001-01-01 00:00:00' UTC to '9999-12-31 23:59:59' UTC; TIMESTAMP(6) can store microseconds");
+ case 'TIME':
+ return sprintf(__('A time, range is %1$s to %2$s'), '00:00:00', '23:59:59');
+ case 'VARCHAR':
+ return sprintf(__('A variable-length (%s) string, the effective maximum length is subject to the maximum row size'), '0-16,383');
+ case 'TEXT':
+ return __('A TEXT column with a maximum length of 65,535 (2^16 - 1) characters, stored with a two-byte prefix indicating the length of the value in bytes');
+ case 'VARBINARY':
+ return __('A variable-length (0-65,535) string, uses binary collation for all comparisons');
+ case 'BLOB':
+ return __('A BLOB column with a maximum length of 65,535 (2^16 - 1) bytes, stored with a two-byte prefix indicating the length of the value');
+ case 'ENUM':
+ return __("An enumeration, chosen from the list of defined values");
+ }
+ return '';
+ }
+
+ /**
+ * Returns class of a type, used for functions available for type
+ * or default values.
+ *
+ * @param string $type The data type to get a class.
+ *
+ * @return string
+ *
+ */
+ public function getTypeClass($type)
+ {
+ $type = strtoupper($type);
+ switch ($type) {
+ case 'INTEGER':
+ case 'BIGINT':
+ case 'DECIMAL':
+ case 'DOUBLE':
+ case 'BOOLEAN':
+ case 'SERIAL':
+ return 'NUMBER';
+
+ case 'DATE':
+ case 'DATETIME':
+ case 'TIMESTAMP':
+ case 'TIME':
+ return 'DATE';
+
+ case 'VARCHAR':
+ case 'TEXT':
+ case 'VARBINARY':
+ case 'BLOB':
+ case 'ENUM':
+ return 'CHAR';
+
+ case 'UUID':
+ return 'UUID';
+ }
+ return '';
+ }
+
+ /**
+ * Returns array of functions available for a class.
+ *
+ * @param string $class The class to get function list.
+ *
+ * @return array
+ *
+ */
+ public function getFunctionsClass($class)
+ {
+ switch ($class) {
+ case 'CHAR':
+ $ret = array(
+ 'BIN',
+ 'CHAR',
+ 'COMPRESS',
+ 'CURRENT_USER',
+ 'DATABASE',
+ 'DAYNAME',
+ 'HEX',
+ 'LOAD_FILE',
+ 'LOWER',
+ 'LTRIM',
+ 'MD5',
+ 'MONTHNAME',
+ 'QUOTE',
+ 'REVERSE',
+ 'RTRIM',
+ 'SCHEMA',
+ 'SPACE',
+ 'TRIM',
+ 'UNCOMPRESS',
+ 'UNHEX',
+ 'UPPER',
+ 'USER',
+ 'UUID',
+ 'VERSION',
+ );
+
+ // check for some functions known to be in modules
+ $functions = array(
+ 'MYSQL_PASSWORD',
+ 'ROT13',
+ );
+
+ // add new functions
+ $sql = "SELECT upper(plugin_name) f
+ FROM data_dictionary.plugins
+ WHERE plugin_name IN ('" . implode("','", $functions) . "')
+ AND plugin_type = 'Function'
+ AND is_active";
+ $drizzle_functions = $GLOBALS['dbi']->fetchResult($sql, 'f', 'f');
+ if (count($drizzle_functions) > 0) {
+ $ret = array_merge($ret, $drizzle_functions);
+ sort($ret);
+ }
+
+ return $ret;
+
+ case 'UUID':
+ return array(
+ 'UUID',
+ );
+
+ case 'DATE':
+ return array(
+ 'CURRENT_DATE',
+ 'CURRENT_TIME',
+ 'DATE',
+ 'FROM_DAYS',
+ 'FROM_UNIXTIME',
+ 'LAST_DAY',
+ 'NOW',
+ 'SYSDATE',
+ //'TIME', // https://bugs.launchpad.net/drizzle/+bug/804571
+ 'TIMESTAMP',
+ 'UTC_DATE',
+ 'UTC_TIME',
+ 'UTC_TIMESTAMP',
+ 'YEAR',
+ );
+
+ case 'NUMBER':
+ return array(
+ 'ABS',
+ 'ACOS',
+ 'ASCII',
+ 'ASIN',
+ 'ATAN',
+ 'BIT_COUNT',
+ 'CEILING',
+ 'CHAR_LENGTH',
+ 'CONNECTION_ID',
+ 'COS',
+ 'COT',
+ 'CRC32',
+ 'DAYOFMONTH',
+ 'DAYOFWEEK',
+ 'DAYOFYEAR',
+ 'DEGREES',
+ 'EXP',
+ 'FLOOR',
+ 'HOUR',
+ 'LENGTH',
+ 'LN',
+ 'LOG',
+ 'LOG2',
+ 'LOG10',
+ 'MICROSECOND',
+ 'MINUTE',
+ 'MONTH',
+ 'OCT',
+ 'ORD',
+ 'PI',
+ 'QUARTER',
+ 'RADIANS',
+ 'RAND',
+ 'ROUND',
+ 'SECOND',
+ 'SIGN',
+ 'SIN',
+ 'SQRT',
+ 'TAN',
+ 'TO_DAYS',
+ 'TIME_TO_SEC',
+ 'UNCOMPRESSED_LENGTH',
+ 'UNIX_TIMESTAMP',
+ //'WEEK', // same as TIME
+ 'WEEKDAY',
+ 'WEEKOFYEAR',
+ 'YEARWEEK',
+ );
+ }
+ return array();
+ }
+
+ /**
+ * Returns array of all attributes available.
+ *
+ * @return array
+ *
+ */
+ public function getAttributes()
+ {
+ return array(
+ '',
+ 'on update CURRENT_TIMESTAMP',
+ );
+ }
+
+ /**
+ * Returns array of all column types available.
+ *
+ * @return array
+ *
+ */
+ public function getColumns()
+ {
+ $types_num = array(
+ 'INTEGER',
+ 'BIGINT',
+ '-',
+ 'DECIMAL',
+ 'DOUBLE',
+ '-',
+ 'BOOLEAN',
+ 'SERIAL',
+ 'UUID',
+ );
+ $types_date = array(
+ 'DATE',
+ 'DATETIME',
+ 'TIMESTAMP',
+ 'TIME',
+ );
+ $types_string = array(
+ 'VARCHAR',
+ 'TEXT',
+ '-',
+ 'VARBINARY',
+ 'BLOB',
+ '-',
+ 'ENUM',
+ );
+ if (PMA_MYSQL_INT_VERSION >= 20120130) {
+ $types_string[] = '-';
+ $types_string[] = 'IPV6';
+ }
+
+ $ret = parent::getColumns();
+ // numeric
+ $ret[_pgettext('numeric types', 'Numeric')] = $types_num;
+
+ // Date/Time
+ $ret[_pgettext('date and time types', 'Date and time')] = $types_date;
+
+ // Text
+ $ret[_pgettext('string types', 'String')] = $types_string;
+
+ return $ret;
+ }
+}
diff --git a/libraries/Util.class.php b/libraries/Util.class.php
new file mode 100644
index 0000000000..8a8fddce57
--- /dev/null
+++ b/libraries/Util.class.php
@@ -0,0 +1,4366 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Hold the PMA_Util class
+ *
+ * @package PhpMyAdmin
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ * Misc functions used all over the scripts.
+ *
+ * @package PhpMyAdmin
+ */
+class PMA_Util
+{
+
+ /**
+ * Detects which function to use for pow.
+ *
+ * @return string Function name.
+ */
+ public static function detectPow()
+ {
+ if (function_exists('bcpow')) {
+ // BCMath Arbitrary Precision Mathematics Function
+ return 'bcpow';
+ } elseif (function_exists('gmp_pow')) {
+ // GMP Function
+ return 'gmp_pow';
+ } else {
+ // PHP function
+ return 'pow';
+ }
+ }
+
+ /**
+ * Exponential expression / raise number into power
+ *
+ * @param string $base base to raise
+ * @param string $exp exponent to use
+ * @param mixed $use_function pow function to use, or false for auto-detect
+ *
+ * @return mixed string or float
+ */
+ public static function pow($base, $exp, $use_function = false)
+ {
+ static $pow_function = null;
+
+ if ($pow_function == null) {
+ $pow_function = self::detectPow();
+ }
+
+ if (! $use_function) {
+ if ($exp < 0) {
+ $use_function = 'pow';
+ } else {
+ $use_function = $pow_function;
+ }
+ }
+
+ if (($exp < 0) && ($use_function != 'pow')) {
+ return false;
+ }
+
+ switch ($use_function) {
+ case 'bcpow' :
+ // bcscale() needed for testing pow() with base values < 1
+ bcscale(10);
+ $pow = bcpow($base, $exp);
+ break;
+ case 'gmp_pow' :
+ $pow = gmp_strval(gmp_pow($base, $exp));
+ break;
+ case 'pow' :
+ $base = (float) $base;
+ $exp = (int) $exp;
+ $pow = pow($base, $exp);
+ break;
+ default:
+ $pow = $use_function($base, $exp);
+ }
+
+ return $pow;
+ }
+
+ /**
+ * Checks whether configuration value tells to show icons.
+ *
+ * @param string $value Configuration option name
+ *
+ * @return boolean Whether to show icons.
+ */
+ public static function showIcons($value)
+ {
+ return in_array($GLOBALS['cfg'][$value], array('icons', 'both'));
+ }
+
+ /**
+ * Checks whether configuration value tells to show text.
+ *
+ * @param string $value Configuration option name
+ *
+ * @return boolean Whether to show text.
+ */
+ public static function showText($value)
+ {
+ return in_array($GLOBALS['cfg'][$value], array('text', 'both'));
+ }
+
+ /**
+ * Returns an HTML IMG tag for a particular icon from a theme,
+ * which may be an actual file or an icon from a sprite.
+ * This function takes into account the ActionLinksMode
+ * configuration setting and wraps the image tag in a span tag.
+ *
+ * @param string $icon name of icon file
+ * @param string $alternate alternate text
+ * @param boolean $force_text whether to force alternate text to be displayed
+ * @param boolean $menu_icon whether this icon is for the menu bar or not
+ * @param string $control_param which directive controls the display
+ *
+ * @return string an html snippet
+ */
+ public static function getIcon(
+ $icon, $alternate = '', $force_text = false,
+ $menu_icon = false, $control_param = 'ActionLinksMode'
+ ) {
+ $include_icon = $include_text = false;
+ if (self::showIcons($control_param)) {
+ $include_icon = true;
+ }
+ if ($force_text
+ || self::showText($control_param)
+ ) {
+ $include_text = true;
+ }
+ // Sometimes use a span (we rely on this in js/sql.js). But for menu bar
+ // we don't need a span
+ $button = $menu_icon ? '' : '<span class="nowrap">';
+ if ($include_icon) {
+ $button .= self::getImage($icon, $alternate);
+ }
+ if ($include_icon && $include_text) {
+ $button .= ' ';
+ }
+ if ($include_text) {
+ $button .= $alternate;
+ }
+ $button .= $menu_icon ? '' : '</span>';
+
+ return $button;
+ }
+
+ /**
+ * Returns an HTML IMG tag for a particular image from a theme,
+ * which may be an actual file or an icon from a sprite
+ *
+ * @param string $image The name of the file to get
+ * @param string $alternate Used to set 'alt' and 'title' attributes
+ * of the image
+ * @param array $attributes An associative array of other attributes
+ *
+ * @return string an html IMG tag
+ */
+ public static function getImage($image, $alternate = '', $attributes = array())
+ {
+ static $sprites; // cached list of available sprites (if any)
+ if (defined('TESTSUITE')) {
+ // prevent caching in testsuite
+ unset($sprites);
+ }
+
+ $url = '';
+ $is_sprite = false;
+ $alternate = htmlspecialchars($alternate);
+
+ // If it's the first time this function is called
+ if (! isset($sprites)) {
+ // Try to load the list of sprites
+ $sprite_file = $_SESSION['PMA_Theme']->getPath() . '/sprites.lib.php';
+ if (is_readable($sprite_file)) {
+ include_once $sprite_file;
+ $sprites = PMA_sprites();
+ } else {
+ // No sprites are available for this theme
+ $sprites = array();
+ }
+ }
+
+ // Check if we have the requested image as a sprite
+ // and set $url accordingly
+ $class = str_replace(array('.gif','.png'), '', $image);
+ if (array_key_exists($class, $sprites)) {
+ $is_sprite = true;
+ $url = (defined('PMA_TEST_THEME') ? '../' : '') . 'themes/dot.gif';
+ } else {
+ $url = $GLOBALS['pmaThemeImage'] . $image;
+ }
+
+ // set class attribute
+ if ($is_sprite) {
+ if (isset($attributes['class'])) {
+ $attributes['class'] = "icon ic_$class " . $attributes['class'];
+ } else {
+ $attributes['class'] = "icon ic_$class";
+ }
+ }
+
+ // set all other attributes
+ $attr_str = '';
+ foreach ($attributes as $key => $value) {
+ if (! in_array($key, array('alt', 'title'))) {
+ $attr_str .= " $key=\"$value\"";
+ }
+ }
+
+ // override the alt attribute
+ if (isset($attributes['alt'])) {
+ $alt = $attributes['alt'];
+ } else {
+ $alt = $alternate;
+ }
+
+ // override the title attribute
+ if (isset($attributes['title'])) {
+ $title = $attributes['title'];
+ } else {
+ $title = $alternate;
+ }
+
+ // generate the IMG tag
+ $template = '<img src="%s" title="%s" alt="%s"%s />';
+ $retval = sprintf($template, $url, $title, $alt, $attr_str);
+
+ return $retval;
+ }
+
+ /**
+ * Returns the formatted maximum size for an upload
+ *
+ * @param integer $max_upload_size the size
+ *
+ * @return string the message
+ *
+ * @access public
+ */
+ public static function getFormattedMaximumUploadSize($max_upload_size)
+ {
+ // I have to reduce the second parameter (sensitiveness) from 6 to 4
+ // to avoid weird results like 512 kKib
+ list($max_size, $max_unit) = self::formatByteDown($max_upload_size, 4);
+ return '(' . sprintf(__('Max: %s%s'), $max_size, $max_unit) . ')';
+ }
+
+ /**
+ * Generates a hidden field which should indicate to the browser
+ * the maximum size for upload
+ *
+ * @param integer $max_size the size
+ *
+ * @return string the INPUT field
+ *
+ * @access public
+ */
+ public static function generateHiddenMaxFileSize($max_size)
+ {
+ return '<input type="hidden" name="MAX_FILE_SIZE" value="'
+ . $max_size . '" />';
+ }
+
+ /**
+ * Add slashes before "'" and "\" characters so a value containing them can
+ * be used in a sql comparison.
+ *
+ * @param string $a_string the string to slash
+ * @param bool $is_like whether the string will be used in a 'LIKE' clause
+ * (it then requires two more escaped sequences) or not
+ * @param bool $crlf whether to treat cr/lfs as escape-worthy entities
+ * (converts \n to \\n, \r to \\r)
+ * @param bool $php_code whether this function is used as part of the
+ * "Create PHP code" dialog
+ *
+ * @return string the slashed string
+ *
+ * @access public
+ */
+ public static function sqlAddSlashes(
+ $a_string = '', $is_like = false, $crlf = false, $php_code = false
+ ) {
+ if ($is_like) {
+ $a_string = str_replace('\\', '\\\\\\\\', $a_string);
+ } else {
+ $a_string = str_replace('\\', '\\\\', $a_string);
+ }
+
+ if ($crlf) {
+ $a_string = strtr(
+ $a_string,
+ array("\n" => '\n', "\r" => '\r', "\t" => '\t')
+ );
+ }
+
+ if ($php_code) {
+ $a_string = str_replace('\'', '\\\'', $a_string);
+ } else {
+ $a_string = str_replace('\'', '\'\'', $a_string);
+ }
+
+ return $a_string;
+ } // end of the 'sqlAddSlashes()' function
+
+ /**
+ * Add slashes before "_" and "%" characters for using them in MySQL
+ * database, table and field names.
+ * Note: This function does not escape backslashes!
+ *
+ * @param string $name the string to escape
+ *
+ * @return string the escaped string
+ *
+ * @access public
+ */
+ public static function escapeMysqlWildcards($name)
+ {
+ return strtr($name, array('_' => '\\_', '%' => '\\%'));
+ } // end of the 'escapeMysqlWildcards()' function
+
+ /**
+ * removes slashes before "_" and "%" characters
+ * Note: This function does not unescape backslashes!
+ *
+ * @param string $name the string to escape
+ *
+ * @return string the escaped string
+ *
+ * @access public
+ */
+ public static function unescapeMysqlWildcards($name)
+ {
+ return strtr($name, array('\\_' => '_', '\\%' => '%'));
+ } // end of the 'unescapeMysqlWildcards()' function
+
+ /**
+ * removes quotes (',",`) from a quoted string
+ *
+ * checks if the string is quoted and removes this quotes
+ *
+ * @param string $quoted_string string to remove quotes from
+ * @param string $quote type of quote to remove
+ *
+ * @return string unqoted string
+ */
+ public static function unQuote($quoted_string, $quote = null)
+ {
+ $quotes = array();
+
+ if ($quote === null) {
+ $quotes[] = '`';
+ $quotes[] = '"';
+ $quotes[] = "'";
+ } else {
+ $quotes[] = $quote;
+ }
+
+ foreach ($quotes as $quote) {
+ if (substr($quoted_string, 0, 1) === $quote
+ && substr($quoted_string, -1, 1) === $quote
+ ) {
+ $unquoted_string = substr($quoted_string, 1, -1);
+ // replace escaped quotes
+ $unquoted_string = str_replace(
+ $quote . $quote,
+ $quote,
+ $unquoted_string
+ );
+ return $unquoted_string;
+ }
+ }
+
+ return $quoted_string;
+ }
+
+ /**
+ * format sql strings
+ *
+ * @param string $sql_query raw SQL string
+ * @param boolean $truncate truncate the query if it is too long
+ *
+ * @return string the formatted sql
+ *
+ * @global array $cfg the configuration array
+ *
+ * @access public
+ * @todo move into PMA_Sql
+ */
+ public static function formatSql($sql_query, $truncate = false)
+ {
+ global $cfg;
+ if ($truncate
+ && strlen($sql_query) > $cfg['MaxCharactersInDisplayedSQL']
+ ) {
+ $sql_query = $GLOBALS['PMA_String']->substr(
+ $sql_query,
+ 0,
+ $cfg['MaxCharactersInDisplayedSQL']
+ ) . '[...]';
+ }
+ return '<code class="sql"><pre>' . "\n"
+ . htmlspecialchars($sql_query) . "\n"
+ . '</pre></code>';
+ } // end of the "formatSql()" function
+
+ /**
+ * Displays a link to the documentation as an icon
+ *
+ * @param string $link documentation link
+ * @param string $target optional link target
+ *
+ * @return string the html link
+ *
+ * @access public
+ */
+ public static function showDocLink($link, $target = 'documentation')
+ {
+ return '<a href="' . $link . '" target="' . $target . '">'
+ . self::getImage('b_help.png', __('Documentation'))
+ . '</a>';
+ } // end of the 'showDocLink()' function
+
+ /**
+ * Get a URL link to the official MySQL documentation
+ *
+ * @param string $link contains name of page/anchor that is being linked
+ * @param string $anchor anchor to page part
+ *
+ * @return string the URL link
+ *
+ * @access public
+ */
+ public static function getMySQLDocuURL($link, $anchor = '')
+ {
+ global $cfg;
+
+ // Fixup for newly used names:
+ $link = str_replace('_', '-', strtolower($link));
+
+ if (empty($link)) {
+ $link = 'index';
+ }
+ $mysql = '5.5';
+ $lang = 'en';
+ if (defined('PMA_MYSQL_INT_VERSION')) {
+ if (PMA_MYSQL_INT_VERSION >= 50600) {
+ $mysql = '5.6';
+ } else if (PMA_MYSQL_INT_VERSION >= 50500) {
+ $mysql = '5.5';
+ } else if (PMA_MYSQL_INT_VERSION >= 50100) {
+ $mysql = '5.1';
+ } else {
+ $mysql = '5.0';
+ }
+ }
+ $url = 'http://dev.mysql.com/doc/refman/'
+ . $mysql . '/' . $lang . '/' . $link . '.html';
+ if (! empty($anchor)) {
+ $url .= '#' . $anchor;
+ }
+
+ return PMA_linkURL($url);
+ }
+
+ /**
+ * Displays a link to the official MySQL documentation
+ *
+ * @param string $link contains name of page/anchor that is being linked
+ * @param bool $big_icon whether to use big icon (like in left frame)
+ * @param string $anchor anchor to page part
+ * @param bool $just_open whether only the opening <a> tag should be returned
+ *
+ * @return string the html link
+ *
+ * @access public
+ */
+ public static function showMySQLDocu(
+ $link, $big_icon = false, $anchor = '', $just_open = false
+ ) {
+ $url = self::getMySQLDocuURL($link, $anchor);
+ $open_link = '<a href="' . $url . '" target="mysql_doc">';
+ if ($just_open) {
+ return $open_link;
+ } elseif ($big_icon) {
+ return $open_link
+ . self::getImage('b_sqlhelp.png', __('Documentation')) . '</a>';
+ } else {
+ return self::showDocLink($url, 'mysql_doc');
+ }
+ } // end of the 'showMySQLDocu()' function
+
+ /**
+ * Returns link to documentation.
+ *
+ * @param string $page Page in documentation
+ * @param string $anchor Optional anchor in page
+ *
+ * @return string URL
+ */
+ public static function getDocuLink($page, $anchor = '')
+ {
+ /* Construct base URL */
+ $url = $page . '.html';
+ if (!empty($anchor)) {
+ $url .= '#' . $anchor;
+ }
+
+ /* Check if we have built local documentation */
+ if (defined('TESTSUITE')) {
+ /* Provide consistent URL for testsuite */
+ return PMA_linkURL('http://docs.phpmyadmin.net/en/latest/' . $url);
+ } else if (file_exists('doc/html/index.html')) {
+ if (defined('PMA_SETUP')) {
+ return '../doc/html/' . $url;
+ } else {
+ return './doc/html/' . $url;
+ }
+ } else {
+ /* TODO: Should link to correct branch for released versions */
+ return PMA_linkURL('http://docs.phpmyadmin.net/en/latest/' . $url);
+ }
+ }
+
+ /**
+ * Displays a link to the phpMyAdmin documentation
+ *
+ * @param string $page Page in documentation
+ * @param string $anchor Optional anchor in page
+ *
+ * @return string the html link
+ *
+ * @access public
+ */
+ public static function showDocu($page, $anchor = '')
+ {
+ return self::showDocLink(self::getDocuLink($page, $anchor));
+ } // end of the 'showDocu()' function
+
+ /**
+ * Displays a link to the PHP documentation
+ *
+ * @param string $target anchor in documentation
+ *
+ * @return string the html link
+ *
+ * @access public
+ */
+ public static function showPHPDocu($target)
+ {
+ $url = PMA_getPHPDocLink($target);
+
+ return self::showDocLink($url);
+ } // end of the 'showPHPDocu()' function
+
+ /**
+ * Returns HTML code for a tooltip
+ *
+ * @param string $message the message for the tooltip
+ *
+ * @return string
+ *
+ * @access public
+ */
+ public static function showHint($message)
+ {
+ if ($GLOBALS['cfg']['ShowHint']) {
+ $classClause = ' class="pma_hint"';
+ } else {
+ $classClause = '';
+ }
+ return '<span' . $classClause . '>'
+ . self::getImage('b_help.png')
+ . '<span class="hide">' . $message . '</span>'
+ . '</span>';
+ }
+
+ /**
+ * Displays a MySQL error message in the main panel when $exit is true.
+ * Returns the error message otherwise.
+ *
+ * @param string $error_message the error message
+ * @param string $the_query the sql query that failed
+ * @param bool $is_modify_link whether to show a "modify" link or not
+ * @param string $back_url the "back" link url (full path is not required)
+ * @param bool $exit EXIT the page?
+ *
+ * @return mixed
+ *
+ * @global string $table the curent table
+ * @global string $db the current db
+ *
+ * @access public
+ */
+ public static function mysqlDie(
+ $error_message = '', $the_query = '',
+ $is_modify_link = true, $back_url = '', $exit = true
+ ) {
+ global $table, $db;
+
+ $error_msg = '';
+
+ if (! $error_message) {
+ $error_message = $GLOBALS['dbi']->getError();
+ }
+ if (! $the_query && ! empty($GLOBALS['sql_query'])) {
+ $the_query = $GLOBALS['sql_query'];
+ }
+
+ // --- Added to solve bug #641765
+ if (! function_exists('PMA_SQP_isError') || PMA_SQP_isError()) {
+ $formatted_sql = htmlspecialchars($the_query);
+ } elseif (empty($the_query) || (trim($the_query) == '')) {
+ $formatted_sql = '';
+ } else {
+ $formatted_sql = self::formatSql($the_query, true);
+ }
+ // ---
+ $error_msg .= "\n" . '<!-- PMA-SQL-ERROR -->' . "\n";
+ $error_msg .= ' <div class="error"><h1>' . __('Error')
+ . '</h1>' . "\n";
+ // if the config password is wrong, or the MySQL server does not
+ // respond, do not show the query that would reveal the
+ // username/password
+ if (! empty($the_query) && ! strstr($the_query, 'connect')) {
+ // --- Added to solve bug #641765
+ if (function_exists('PMA_SQP_isError') && PMA_SQP_isError()) {
+ $error_msg .= PMA_SQP_getErrorString() . "\n";
+ $error_msg .= '<br />' . "\n";
+ }
+ // ---
+ // modified to show the help on sql errors
+ $error_msg .= '<p><strong>' . __('SQL query:') . '</strong>' . "\n";
+ if (strstr(strtolower($formatted_sql), 'select')) {
+ // please show me help to the error on select
+ $error_msg .= self::showMySQLDocu('SELECT');
+ }
+ if ($is_modify_link) {
+ $_url_params = array(
+ 'sql_query' => $the_query,
+ 'show_query' => 1,
+ );
+ if (strlen($table)) {
+ $_url_params['db'] = $db;
+ $_url_params['table'] = $table;
+ $doedit_goto = '<a href="tbl_sql.php'
+ . PMA_URL_getCommon($_url_params) . '">';
+ } elseif (strlen($db)) {
+ $_url_params['db'] = $db;
+ $doedit_goto = '<a href="db_sql.php'
+ . PMA_URL_getCommon($_url_params) . '">';
+ } else {
+ $doedit_goto = '<a href="server_sql.php'
+ . PMA_URL_getCommon($_url_params) . '">';
+ }
+
+ $error_msg .= $doedit_goto
+ . self::getIcon('b_edit.png', __('Edit'))
+ . '</a>';
+ } // end if
+ $error_msg .= ' </p>' . "\n"
+ .'<p>' . "\n"
+ . $formatted_sql . "\n"
+ . '</p>' . "\n";
+ } // end if
+
+ if (! empty($error_message)) {
+ $error_message = preg_replace(
+ "@((\015\012)|(\015)|(\012)){3,}@",
+ "\n\n",
+ $error_message
+ );
+ }
+ // modified to show the help on error-returns
+ // (now error-messages-server)
+ $error_msg .= '<p>' . "\n"
+ . ' <strong>' . __('MySQL said: ') . '</strong>'
+ . self::showMySQLDocu('Error-messages-server')
+ . "\n"
+ . '</p>' . "\n";
+
+ // The error message will be displayed within a CODE segment.
+ // To preserve original formatting, but allow wordwrapping,
+ // we do a couple of replacements
+
+ // Replace all non-single blanks with their HTML-counterpart
+ $error_message = str_replace(' ', '&nbsp;&nbsp;', $error_message);
+ // Replace TAB-characters with their HTML-counterpart
+ $error_message = str_replace(
+ "\t", '&nbsp;&nbsp;&nbsp;&nbsp;', $error_message
+ );
+ // Replace linebreaks
+ $error_message = nl2br($error_message);
+
+ $error_msg .= '<code>' . "\n"
+ . $error_message . "\n"
+ . '</code><br />' . "\n";
+ $error_msg .= '</div>';
+
+ $_SESSION['Import_message']['message'] = $error_msg;
+
+ if ($exit) {
+ /**
+ * If in an Ajax request
+ * - avoid displaying a Back link
+ * - use PMA_Response() to transmit the message and exit
+ */
+ if (isset($GLOBALS['is_ajax_request'])
+ && $GLOBALS['is_ajax_request'] == true
+ ) {
+ $response = PMA_Response::getInstance();
+ $response->isSuccess(false);
+ $response->addJSON('message', $error_msg);
+ exit;
+ }
+ if (! empty($back_url)) {
+ if (strstr($back_url, '?')) {
+ $back_url .= '&amp;no_history=true';
+ } else {
+ $back_url .= '?no_history=true';
+ }
+
+ $_SESSION['Import_message']['go_back_url'] = $back_url;
+
+ $error_msg .= '<fieldset class="tblFooters">'
+ . '[ <a href="' . $back_url . '">' . __('Back') . '</a> ]'
+ . '</fieldset>' . "\n\n";
+ }
+ echo $error_msg;
+ exit;
+ } else {
+ return $error_msg;
+ }
+ } // end of the 'mysqlDie()' function
+
+ /**
+ * returns array with tables of given db with extended information and grouped
+ *
+ * @param string $db name of db
+ * @param string $tables name of tables
+ * @param integer $limit_offset list offset
+ * @param int|bool $limit_count max tables to return
+ *
+ * @return array (recursive) grouped table list
+ */
+ public static function getTableList(
+ $db, $tables = null, $limit_offset = 0, $limit_count = false
+ ) {
+ $sep = $GLOBALS['cfg']['NavigationTreeTableSeparator'];
+
+ if ($tables === null) {
+ $tables = $GLOBALS['dbi']->getTablesFull(
+ $db, false, false, null, $limit_offset, $limit_count
+ );
+ if ($GLOBALS['cfg']['NaturalOrder']) {
+ uksort($tables, 'strnatcasecmp');
+ }
+ }
+
+ if (count($tables) < 1) {
+ return $tables;
+ }
+
+ $default = array(
+ 'Name' => '',
+ 'Rows' => 0,
+ 'Comment' => '',
+ 'disp_name' => '',
+ );
+
+ $table_groups = array();
+
+ foreach ($tables as $table_name => $table) {
+ // check for correct row count
+ if ($table['Rows'] === null) {
+ // Do not check exact row count here,
+ // if row count is invalid possibly the table is defect
+ // and this would break left frame;
+ // but we can check row count if this is a view or the
+ // information_schema database
+ // since PMA_Table::countRecords() returns a limited row count
+ // in this case.
+
+ // set this because PMA_Table::countRecords() can use it
+ $tbl_is_view = $table['TABLE_TYPE'] == 'VIEW';
+
+ if ($tbl_is_view || $GLOBALS['dbi']->isSystemSchema($db)) {
+ $table['Rows'] = PMA_Table::countRecords(
+ $db,
+ $table['Name'],
+ false,
+ true
+ );
+ }
+ }
+
+ // in $group we save the reference to the place in $table_groups
+ // where to store the table info
+ if ($GLOBALS['cfg']['NavigationTreeEnableGrouping']
+ && $sep && strstr($table_name, $sep)
+ ) {
+ $parts = explode($sep, $table_name);
+
+ $group =& $table_groups;
+ $i = 0;
+ $group_name_full = '';
+ $parts_cnt = count($parts) - 1;
+
+ while (($i < $parts_cnt)
+ && ($i < $GLOBALS['cfg']['NavigationTreeTableLevel'])
+ ) {
+ $group_name = $parts[$i] . $sep;
+ $group_name_full .= $group_name;
+
+ if (! isset($group[$group_name])) {
+ $group[$group_name] = array();
+ $group[$group_name]['is' . $sep . 'group'] = true;
+ $group[$group_name]['tab' . $sep . 'count'] = 1;
+ $group[$group_name]['tab' . $sep . 'group']
+ = $group_name_full;
+
+ } elseif (! isset($group[$group_name]['is' . $sep . 'group'])) {
+ $table = $group[$group_name];
+ $group[$group_name] = array();
+ $group[$group_name][$group_name] = $table;
+ unset($table);
+ $group[$group_name]['is' . $sep . 'group'] = true;
+ $group[$group_name]['tab' . $sep . 'count'] = 1;
+ $group[$group_name]['tab' . $sep . 'group']
+ = $group_name_full;
+
+ } else {
+ $group[$group_name]['tab' . $sep . 'count']++;
+ }
+
+ $group =& $group[$group_name];
+ $i++;
+ }
+
+ } else {
+ if (! isset($table_groups[$table_name])) {
+ $table_groups[$table_name] = array();
+ }
+ $group =& $table_groups;
+ }
+
+ $table['disp_name'] = $table['Name'];
+ $group[$table_name] = array_merge($default, $table);
+ }
+
+ return $table_groups;
+ }
+
+ /* ----------------------- Set of misc functions ----------------------- */
+
+ /**
+ * Adds backquotes on both sides of a database, table or field name.
+ * and escapes backquotes inside the name with another backquote
+ *
+ * example:
+ * <code>
+ * echo backquote('owner`s db'); // `owner``s db`
+ *
+ * </code>
+ *
+ * @param mixed $a_name the database, table or field name to "backquote"
+ * or array of it
+ * @param boolean $do_it a flag to bypass this function (used by dump
+ * functions)
+ *
+ * @return mixed the "backquoted" database, table or field name
+ *
+ * @access public
+ */
+ public static function backquote($a_name, $do_it = true)
+ {
+ if (is_array($a_name)) {
+ foreach ($a_name as &$data) {
+ $data = self::backquote($data, $do_it);
+ }
+ return $a_name;
+ }
+
+ if (! $do_it) {
+ global $PMA_SQPdata_forbidden_word;
+ if (! in_array(strtoupper($a_name), $PMA_SQPdata_forbidden_word)) {
+ return $a_name;
+ }
+ }
+
+ // '0' is also empty for php :-(
+ if (strlen($a_name) && $a_name !== '*') {
+ return '`' . str_replace('`', '``', $a_name) . '`';
+ } else {
+ return $a_name;
+ }
+ } // end of the 'backquote()' function
+
+ /**
+ * Adds quotes on both sides of a database, table or field name.
+ * in compatibility mode
+ *
+ * example:
+ * <code>
+ * echo backquote('owner`s db'); // `owner``s db`
+ *
+ * </code>
+ *
+ * @param mixed $a_name the database, table or field name to
+ * "backquote" or array of it
+ * @param string $compatibility string compatibility mode (used by dump
+ * functions)
+ * @param boolean $do_it a flag to bypass this function (used by dump
+ * functions)
+ *
+ * @return mixed the "backquoted" database, table or field name
+ *
+ * @access public
+ */
+ public static function backquoteCompat(
+ $a_name, $compatibility = 'MSSQL', $do_it = true
+ ) {
+ if (is_array($a_name)) {
+ foreach ($a_name as &$data) {
+ $data = self::backquoteCompat($data, $compatibility, $do_it);
+ }
+ return $a_name;
+ }
+
+ if (! $do_it) {
+ global $PMA_SQPdata_forbidden_word;
+ if (! in_array(strtoupper($a_name), $PMA_SQPdata_forbidden_word)) {
+ return $a_name;
+ }
+ }
+
+ // @todo add more compatibility cases (ORACLE for example)
+ switch ($compatibility) {
+ case 'MSSQL':
+ $quote = '"';
+ break;
+ default:
+ (isset($GLOBALS['sql_backquotes'])) ? $quote = "`" : $quote = '';
+ break;
+ }
+
+ // '0' is also empty for php :-(
+ if (strlen($a_name) && $a_name !== '*') {
+ return $quote . $a_name . $quote;
+ } else {
+ return $a_name;
+ }
+ } // end of the 'backquoteCompat()' function
+
+ /**
+ * Defines the <CR><LF> value depending on the user OS.
+ *
+ * @return string the <CR><LF> value to use
+ *
+ * @access public
+ */
+ public static function whichCrlf()
+ {
+ // The 'PMA_USR_OS' constant is defined in "libraries/Config.class.php"
+ // Win case
+ if (PMA_USR_OS == 'Win') {
+ $the_crlf = "\r\n";
+ } else {
+ // Others
+ $the_crlf = "\n";
+ }
+
+ return $the_crlf;
+ } // end of the 'whichCrlf()' function
+
+ /**
+ * Prepare the message and the query
+ * usually the message is the result of the query executed
+ *
+ * @param string $message the message to display
+ * @param string $sql_query the query to display
+ * @param string $type the type (level) of the message
+ * @param boolean $is_view is this a message after a VIEW operation?
+ *
+ * @return string
+ *
+ * @access public
+ */
+ public static function getMessage(
+ $message, $sql_query = null, $type = 'notice', $is_view = false
+ ) {
+ global $cfg;
+ $retval = '';
+
+ if (null === $sql_query) {
+ if (! empty($GLOBALS['display_query'])) {
+ $sql_query = $GLOBALS['display_query'];
+ } elseif (! empty($GLOBALS['unparsed_sql'])) {
+ $sql_query = $GLOBALS['unparsed_sql'];
+ } elseif (! empty($GLOBALS['sql_query'])) {
+ $sql_query = $GLOBALS['sql_query'];
+ } else {
+ $sql_query = '';
+ }
+ }
+
+ if (isset($GLOBALS['using_bookmark_message'])) {
+ $retval .= $GLOBALS['using_bookmark_message']->getDisplay();
+ unset($GLOBALS['using_bookmark_message']);
+ }
+
+ // In an Ajax request, $GLOBALS['cell_align_left'] may not be defined. Hence,
+ // check for it's presence before using it
+ $retval .= '<div id="result_query"'
+ . ( isset($GLOBALS['cell_align_left'])
+ ? ' style="text-align: ' . $GLOBALS['cell_align_left'] . '"'
+ : '' )
+ . '>' . "\n";
+
+ if ($message instanceof PMA_Message) {
+ if (isset($GLOBALS['special_message'])) {
+ $message->addMessage($GLOBALS['special_message']);
+ unset($GLOBALS['special_message']);
+ }
+ $retval .= $message->getDisplay();
+ } else {
+ $retval .= '<div class="' . $type . '">';
+ $retval .= PMA_sanitize($message);
+ if (isset($GLOBALS['special_message'])) {
+ $retval .= PMA_sanitize($GLOBALS['special_message']);
+ unset($GLOBALS['special_message']);
+ }
+ $retval .= '</div>';
+ }
+
+ if ($cfg['ShowSQL'] == true && ! empty($sql_query)) {
+ // Html format the query to be displayed
+ // If we want to show some sql code it is easiest to create it here
+ /* SQL-Parser-Analyzer */
+
+ if (! empty($GLOBALS['show_as_php'])) {
+ $new_line = '\\n"<br />' . "\n"
+ . '&nbsp;&nbsp;&nbsp;&nbsp;. "';
+ $query_base = htmlspecialchars(addslashes($sql_query));
+ $query_base = preg_replace(
+ '/((\015\012)|(\015)|(\012))/', $new_line, $query_base
+ );
+ } else {
+ $query_base = $sql_query;
+ }
+
+ $query_too_big = false;
+
+ if (strlen($query_base) > $cfg['MaxCharactersInDisplayedSQL']) {
+ // when the query is large (for example an INSERT of binary
+ // data), the parser chokes; so avoid parsing the query
+ $query_too_big = true;
+ $shortened_query_base = nl2br(
+ htmlspecialchars(
+ substr($sql_query, 0, $cfg['MaxCharactersInDisplayedSQL'])
+ . '[...]'
+ )
+ );
+ } elseif (! empty($GLOBALS['parsed_sql'])
+ && $query_base == $GLOBALS['parsed_sql']['raw']
+ ) {
+ // (here, use "! empty" because when deleting a bookmark,
+ // $GLOBALS['parsed_sql'] is set but empty
+ $parsed_sql = $GLOBALS['parsed_sql'];
+ } else {
+ // Parse SQL if needed
+ $parsed_sql = PMA_SQP_parse($query_base);
+ }
+
+ // Analyze it
+ if (isset($parsed_sql) && ! PMA_SQP_isError()) {
+ $analyzed_display_query = PMA_SQP_analyze($parsed_sql);
+
+ // Same as below (append LIMIT), append the remembered ORDER BY
+ if ($GLOBALS['cfg']['RememberSorting']
+ && isset($analyzed_display_query[0]['queryflags']['select_from'])
+ && isset($GLOBALS['sql_order_to_append'])
+ ) {
+ $query_base = $analyzed_display_query[0]['section_before_limit']
+ . "\n" . $GLOBALS['sql_order_to_append']
+ . $analyzed_display_query[0]['limit_clause'] . ' '
+ . $analyzed_display_query[0]['section_after_limit'];
+
+ // Need to reparse query
+ $parsed_sql = PMA_SQP_parse($query_base);
+ // update the $analyzed_display_query
+ $analyzed_display_query[0]['section_before_limit']
+ .= $GLOBALS['sql_order_to_append'];
+ $analyzed_display_query[0]['order_by_clause']
+ = $GLOBALS['sorted_col'];
+ }
+
+ // Here we append the LIMIT added for navigation, to
+ // enable its display. Adding it higher in the code
+ // to $sql_query would create a problem when
+ // using the Refresh or Edit links.
+
+ // Only append it on SELECTs.
+
+ /**
+ * @todo what would be the best to do when someone hits Refresh:
+ * use the current LIMITs ?
+ */
+
+ if (isset($analyzed_display_query[0]['queryflags']['select_from'])
+ && ! empty($GLOBALS['sql_limit_to_append'])
+ ) {
+ $query_base = $analyzed_display_query[0]['section_before_limit']
+ . "\n" . $GLOBALS['sql_limit_to_append']
+ . $analyzed_display_query[0]['section_after_limit'];
+ // Need to reparse query
+ $parsed_sql = PMA_SQP_parse($query_base);
+ }
+ }
+
+ if (! empty($GLOBALS['show_as_php'])) {
+ $query_base = '$sql = "' . $query_base;
+ } elseif (! empty($GLOBALS['validatequery'])) {
+ try {
+ $query_base = PMA_validateSQL($query_base);
+ } catch (Exception $e) {
+ $retval .= PMA_Message::error(
+ __('Failed to connect to SQL validator!')
+ )->getDisplay();
+ }
+ } elseif (isset($query_base)) {
+ $query_base = self::formatSql($query_base);
+ }
+
+ // Prepares links that may be displayed to edit/explain the query
+ // (don't go to default pages, we must go to the page
+ // where the query box is available)
+
+ // Basic url query part
+ $url_params = array();
+ if (! isset($GLOBALS['db'])) {
+ $GLOBALS['db'] = '';
+ }
+ if (strlen($GLOBALS['db'])) {
+ $url_params['db'] = $GLOBALS['db'];
+ if (strlen($GLOBALS['table'])) {
+ $url_params['table'] = $GLOBALS['table'];
+ $edit_link = 'tbl_sql.php';
+ } else {
+ $edit_link = 'db_sql.php';
+ }
+ } else {
+ $edit_link = 'server_sql.php';
+ }
+
+ // Want to have the query explained
+ // but only explain a SELECT (that has not been explained)
+ /* SQL-Parser-Analyzer */
+ $explain_link = '';
+ $is_select = preg_match('@^SELECT[[:space:]]+@i', $sql_query);
+ if (! empty($cfg['SQLQuery']['Explain']) && ! $query_too_big) {
+ $explain_params = $url_params;
+ // Detect if we are validating as well
+ // To preserve the validate uRL data
+ if (! empty($GLOBALS['validatequery'])) {
+ $explain_params['validatequery'] = 1;
+ }
+ if ($is_select) {
+ $explain_params['sql_query'] = 'EXPLAIN ' . $sql_query;
+ $_message = __('Explain SQL');
+ } elseif (
+ preg_match(
+ '@^EXPLAIN[[:space:]]+SELECT[[:space:]]+@i', $sql_query
+ )
+ ) {
+ $explain_params['sql_query'] = substr($sql_query, 8);
+ $_message = __('Skip Explain SQL');
+ }
+ if (isset($explain_params['sql_query'])) {
+ $explain_link = 'import.php'
+ . PMA_URL_getCommon($explain_params);
+ $explain_link = ' ['
+ . self::linkOrButton($explain_link, $_message) . ']';
+ }
+ } //show explain
+
+ $url_params['sql_query'] = $sql_query;
+ $url_params['show_query'] = 1;
+
+ // even if the query is big and was truncated, offer the chance
+ // to edit it (unless it's enormous, see linkOrButton() )
+ if (! empty($cfg['SQLQuery']['Edit'])) {
+ if ($cfg['EditInWindow'] == true) {
+ $onclick = 'PMA_querywindow.focus(\''
+ . PMA_jsFormat($sql_query, false) . '\'); return false;';
+ } else {
+ $onclick = '';
+ }
+
+ $edit_link .= PMA_URL_getCommon($url_params) . '#querybox';
+ $edit_link = ' ['
+ . self::linkOrButton(
+ $edit_link, __('Edit'),
+ array('onclick' => $onclick, 'class' => 'disableAjax')
+ )
+ . ']';
+ } else {
+ $edit_link = '';
+ }
+
+ // Also we would like to get the SQL formed in some nice
+ // php-code
+ if (! empty($cfg['SQLQuery']['ShowAsPHP']) && ! $query_too_big) {
+ $php_params = $url_params;
+
+ if (! empty($GLOBALS['show_as_php'])) {
+ $_message = __('Without PHP Code');
+ } else {
+ $php_params['show_as_php'] = 1;
+ $_message = __('Create PHP Code');
+ }
+
+ $php_link = 'import.php' . PMA_URL_getCommon($php_params);
+ $php_link = ' [' . self::linkOrButton($php_link, $_message) . ']';
+
+ if (isset($GLOBALS['show_as_php'])) {
+
+ $runquery_link = 'import.php'
+ . PMA_URL_getCommon($url_params);
+
+ $php_link .= ' ['
+ . self::linkOrButton($runquery_link, __('Submit Query'))
+ . ']';
+ }
+ } else {
+ $php_link = '';
+ } //show as php
+
+ // Refresh query
+ if (! empty($cfg['SQLQuery']['Refresh'])
+ && ! isset($GLOBALS['show_as_php']) // 'Submit query' does the same
+ && preg_match('@^(SELECT|SHOW)[[:space:]]+@i', $sql_query)
+ ) {
+ $refresh_link = 'import.php' . PMA_URL_getCommon($url_params);
+ $refresh_link = ' ['
+ . self::linkOrButton($refresh_link, __('Refresh')) . ']';
+ } else {
+ $refresh_link = '';
+ } //refresh
+
+ if (! empty($cfg['SQLValidator']['use'])
+ && ! empty($cfg['SQLQuery']['Validate'])
+ ) {
+ $validate_params = $url_params;
+ if (! empty($GLOBALS['validatequery'])) {
+ $validate_message = __('Skip Validate SQL');
+ } else {
+ $validate_params['validatequery'] = 1;
+ $validate_message = __('Validate SQL');
+ }
+
+ $validate_link = 'import.php'
+ . PMA_URL_getCommon($validate_params);
+ $validate_link = ' ['
+ . self::linkOrButton($validate_link, $validate_message) . ']';
+ } else {
+ $validate_link = '';
+ } //validator
+
+ if (! empty($GLOBALS['validatequery'])) {
+ $retval .= '<div class="sqlvalidate">';
+ } else {
+ $retval .= '<div class="sqlOuter">';
+ }
+ if ($query_too_big) {
+ $retval .= $shortened_query_base;
+ } else {
+ $retval .= $query_base;
+ }
+
+ //Clean up the end of the PHP
+ if (! empty($GLOBALS['show_as_php'])) {
+ $retval .= '";';
+ }
+ $retval .= '</div>';
+
+ $retval .= '<div class="tools">';
+ $retval .= '<form action="sql.php" method="post">';
+ $retval .= PMA_URL_getHiddenInputs(
+ $GLOBALS['db'], $GLOBALS['table']
+ );
+ $retval .= '<input type="hidden" name="sql_query" value="'
+ . htmlspecialchars($sql_query) . '" />';
+
+ // avoid displaying a Profiling checkbox that could
+ // be checked, which would reexecute an INSERT, for example
+ if (! empty($refresh_link) && self::profilingSupported()) {
+ $retval .= '<input type="hidden" name="profiling_form" value="1" />';
+ $retval .= self::getCheckbox(
+ 'profiling', __('Profiling'), isset($_SESSION['profiling']), true
+ );
+ }
+ $retval .= '</form>';
+
+ /**
+ * TODO: Should we have $cfg['SQLQuery']['InlineEdit']?
+ */
+ if (! empty($cfg['SQLQuery']['Edit']) && ! $query_too_big) {
+ $inline_edit_link = ' ['
+ . self::linkOrButton(
+ '#',
+ _pgettext('Inline edit query', 'Inline'),
+ array('class' => 'inline_edit_sql')
+ )
+ . ']';
+ } else {
+ $inline_edit_link = '';
+ }
+ $retval .= $inline_edit_link . $edit_link . $explain_link . $php_link
+ . $refresh_link . $validate_link;
+ $retval .= '</div>';
+ }
+
+ $retval .= '</div>';
+ if ($GLOBALS['is_ajax_request'] === false) {
+ $retval .= '<br class="clearfloat" />';
+ }
+
+ return $retval;
+ } // end of the 'getMessage()' function
+
+ /**
+ * Verifies if current MySQL server supports profiling
+ *
+ * @access public
+ *
+ * @return boolean whether profiling is supported
+ */
+ public static function profilingSupported()
+ {
+ if (!self::cacheExists('profiling_supported', true)) {
+ // 5.0.37 has profiling but for example, 5.1.20 does not
+ // (avoid a trip to the server for MySQL before 5.0.37)
+ // and do not set a constant as we might be switching servers
+ if (defined('PMA_MYSQL_INT_VERSION')
+ && (PMA_MYSQL_INT_VERSION >= 50037)
+ && $GLOBALS['dbi']->fetchValue("SHOW VARIABLES LIKE 'profiling'")
+ ) {
+ self::cacheSet('profiling_supported', true, true);
+ } else {
+ self::cacheSet('profiling_supported', false, true);
+ }
+ }
+
+ return self::cacheGet('profiling_supported', true);
+ }
+
+ /**
+ * Formats $value to byte view
+ *
+ * @param double $value the value to format
+ * @param int $limes the sensitiveness
+ * @param int $comma the number of decimals to retain
+ *
+ * @return array the formatted value and its unit
+ *
+ * @access public
+ */
+ public static function formatByteDown($value, $limes = 6, $comma = 0)
+ {
+ if ($value === null) {
+ return null;
+ }
+
+ $byteUnits = array(
+ /* l10n: shortcuts for Byte */
+ __('B'),
+ /* l10n: shortcuts for Kilobyte */
+ __('KiB'),
+ /* l10n: shortcuts for Megabyte */
+ __('MiB'),
+ /* l10n: shortcuts for Gigabyte */
+ __('GiB'),
+ /* l10n: shortcuts for Terabyte */
+ __('TiB'),
+ /* l10n: shortcuts for Petabyte */
+ __('PiB'),
+ /* l10n: shortcuts for Exabyte */
+ __('EiB')
+ );
+
+ $dh = self::pow(10, $comma);
+ $li = self::pow(10, $limes);
+ $unit = $byteUnits[0];
+
+ for ($d = 6, $ex = 15; $d >= 1; $d--, $ex-=3) {
+ if (isset($byteUnits[$d]) && ($value >= $li * self::pow(10, $ex))) {
+ // use 1024.0 to avoid integer overflow on 64-bit machines
+ $value = round($value / (self::pow(1024, $d) / $dh)) /$dh;
+ $unit = $byteUnits[$d];
+ break 1;
+ } // end if
+ } // end for
+
+ if ($unit != $byteUnits[0]) {
+ // if the unit is not bytes (as represented in current language)
+ // reformat with max length of 5
+ // 4th parameter=true means do not reformat if value < 1
+ $return_value = self::formatNumber($value, 5, $comma, true);
+ } else {
+ // do not reformat, just handle the locale
+ $return_value = self::formatNumber($value, 0);
+ }
+
+ return array(trim($return_value), $unit);
+ } // end of the 'formatByteDown' function
+
+ /**
+ * Changes thousands and decimal separators to locale specific values.
+ *
+ * @param string $value the value
+ *
+ * @return string
+ */
+ public static function localizeNumber($value)
+ {
+ return str_replace(
+ array(',', '.'),
+ array(
+ /* l10n: Thousands separator */
+ __(','),
+ /* l10n: Decimal separator */
+ __('.'),
+ ),
+ $value
+ );
+ }
+
+ /**
+ * Formats $value to the given length and appends SI prefixes
+ * with a $length of 0 no truncation occurs, number is only formated
+ * to the current locale
+ *
+ * examples:
+ * <code>
+ * echo formatNumber(123456789, 6); // 123,457 k
+ * echo formatNumber(-123456789, 4, 2); // -123.46 M
+ * echo formatNumber(-0.003, 6); // -3 m
+ * echo formatNumber(0.003, 3, 3); // 0.003
+ * echo formatNumber(0.00003, 3, 2); // 0.03 m
+ * echo formatNumber(0, 6); // 0
+ * </code>
+ *
+ * @param double $value the value to format
+ * @param integer $digits_left number of digits left of the comma
+ * @param integer $digits_right number of digits right of the comma
+ * @param boolean $only_down do not reformat numbers below 1
+ * @param boolean $noTrailingZero removes trailing zeros right of the comma
+ * (default: true)
+ *
+ * @return string the formatted value and its unit
+ *
+ * @access public
+ */
+ public static function formatNumber(
+ $value, $digits_left = 3, $digits_right = 0,
+ $only_down = false, $noTrailingZero = true
+ ) {
+ if ($value == 0) {
+ return '0';
+ }
+
+ $originalValue = $value;
+ //number_format is not multibyte safe, str_replace is safe
+ if ($digits_left === 0) {
+ $value = number_format($value, $digits_right);
+ if (($originalValue != 0) && (floatval($value) == 0)) {
+ $value = ' <' . (1 / self::pow(10, $digits_right));
+ }
+ return self::localizeNumber($value);
+ }
+
+ // this units needs no translation, ISO
+ $units = array(
+ -8 => 'y',
+ -7 => 'z',
+ -6 => 'a',
+ -5 => 'f',
+ -4 => 'p',
+ -3 => 'n',
+ -2 => '&micro;',
+ -1 => 'm',
+ 0 => ' ',
+ 1 => 'k',
+ 2 => 'M',
+ 3 => 'G',
+ 4 => 'T',
+ 5 => 'P',
+ 6 => 'E',
+ 7 => 'Z',
+ 8 => 'Y'
+ );
+
+ // check for negative value to retain sign
+ if ($value < 0) {
+ $sign = '-';
+ $value = abs($value);
+ } else {
+ $sign = '';
+ }
+
+ $dh = self::pow(10, $digits_right);
+
+ /*
+ * This gives us the right SI prefix already,
+ * but $digits_left parameter not incorporated
+ */
+ $d = floor(log10($value) / 3);
+ /*
+ * Lowering the SI prefix by 1 gives us an additional 3 zeros
+ * So if we have 3,6,9,12.. free digits ($digits_left - $cur_digits)
+ * to use, then lower the SI prefix
+ */
+ $cur_digits = floor(log10($value / self::pow(1000, $d, 'pow'))+1);
+ if ($digits_left > $cur_digits) {
+ $d -= floor(($digits_left - $cur_digits)/3);
+ }
+
+ if ($d < 0 && $only_down) {
+ $d = 0;
+ }
+
+ $value = round($value / (self::pow(1000, $d, 'pow') / $dh)) /$dh;
+ $unit = $units[$d];
+
+ // If we dont want any zeros after the comma just add the thousand separator
+ if ($noTrailingZero) {
+ $value = self::localizeNumber(
+ preg_replace('/(?<=\d)(?=(\d{3})+(?!\d))/', ',', $value)
+ );
+ } else {
+ //number_format is not multibyte safe, str_replace is safe
+ $value = self::localizeNumber(number_format($value, $digits_right));
+ }
+
+ if ($originalValue != 0 && floatval($value) == 0) {
+ return ' <' . (1 / self::pow(10, $digits_right)) . ' ' . $unit;
+ }
+
+ return $sign . $value . ' ' . $unit;
+ } // end of the 'formatNumber' function
+
+ /**
+ * Returns the number of bytes when a formatted size is given
+ *
+ * @param string $formatted_size the size expression (for example 8MB)
+ *
+ * @return integer The numerical part of the expression (for example 8)
+ */
+ public static function extractValueFromFormattedSize($formatted_size)
+ {
+ $return_value = -1;
+
+ if (preg_match('/^[0-9]+GB$/', $formatted_size)) {
+ $return_value = substr($formatted_size, 0, -2) * self::pow(1024, 3);
+ } elseif (preg_match('/^[0-9]+MB$/', $formatted_size)) {
+ $return_value = substr($formatted_size, 0, -2) * self::pow(1024, 2);
+ } elseif (preg_match('/^[0-9]+K$/', $formatted_size)) {
+ $return_value = substr($formatted_size, 0, -1) * self::pow(1024, 1);
+ }
+ return $return_value;
+ }// end of the 'extractValueFromFormattedSize' function
+
+ /**
+ * Writes localised date
+ *
+ * @param string $timestamp the current timestamp
+ * @param string $format format
+ *
+ * @return string the formatted date
+ *
+ * @access public
+ */
+ public static function localisedDate($timestamp = -1, $format = '')
+ {
+ $month = array(
+ /* l10n: Short month name */
+ __('Jan'),
+ /* l10n: Short month name */
+ __('Feb'),
+ /* l10n: Short month name */
+ __('Mar'),
+ /* l10n: Short month name */
+ __('Apr'),
+ /* l10n: Short month name */
+ _pgettext('Short month name', 'May'),
+ /* l10n: Short month name */
+ __('Jun'),
+ /* l10n: Short month name */
+ __('Jul'),
+ /* l10n: Short month name */
+ __('Aug'),
+ /* l10n: Short month name */
+ __('Sep'),
+ /* l10n: Short month name */
+ __('Oct'),
+ /* l10n: Short month name */
+ __('Nov'),
+ /* l10n: Short month name */
+ __('Dec'));
+ $day_of_week = array(
+ /* l10n: Short week day name */
+ _pgettext('Short week day name', 'Sun'),
+ /* l10n: Short week day name */
+ __('Mon'),
+ /* l10n: Short week day name */
+ __('Tue'),
+ /* l10n: Short week day name */
+ __('Wed'),
+ /* l10n: Short week day name */
+ __('Thu'),
+ /* l10n: Short week day name */
+ __('Fri'),
+ /* l10n: Short week day name */
+ __('Sat'));
+
+ if ($format == '') {
+ /* l10n: See http://www.php.net/manual/en/function.strftime.php */
+ $format = __('%B %d, %Y at %I:%M %p');
+ }
+
+ if ($timestamp == -1) {
+ $timestamp = time();
+ }
+
+ $date = preg_replace(
+ '@%[aA]@',
+ $day_of_week[(int)strftime('%w', $timestamp)],
+ $format
+ );
+ $date = preg_replace(
+ '@%[bB]@',
+ $month[(int)strftime('%m', $timestamp)-1],
+ $date
+ );
+
+ return strftime($date, $timestamp);
+ } // end of the 'localisedDate()' function
+
+ /**
+ * returns a tab for tabbed navigation.
+ * If the variables $link and $args ar left empty, an inactive tab is created
+ *
+ * @param array $tab array with all options
+ * @param array $url_params tab specific URL parameters
+ *
+ * @return string html code for one tab, a link if valid otherwise a span
+ *
+ * @access public
+ */
+ public static function getHtmlTab($tab, $url_params = array())
+ {
+ // default values
+ $defaults = array(
+ 'text' => '',
+ 'class' => '',
+ 'active' => null,
+ 'link' => '',
+ 'sep' => '?',
+ 'attr' => '',
+ 'args' => '',
+ 'warning' => '',
+ 'fragment' => '',
+ 'id' => '',
+ );
+
+ $tab = array_merge($defaults, $tab);
+
+ // determine additionnal style-class
+ if (empty($tab['class'])) {
+ if (! empty($tab['active'])
+ || PMA_isValid($GLOBALS['active_page'], 'identical', $tab['link'])
+ ) {
+ $tab['class'] = 'active';
+ } elseif (is_null($tab['active']) && empty($GLOBALS['active_page'])
+ && (basename($GLOBALS['PMA_PHP_SELF']) == $tab['link'])
+ ) {
+ $tab['class'] = 'active';
+ }
+ }
+
+ // If there are any tab specific URL parameters, merge those with
+ // the general URL parameters
+ if (! empty($tab['url_params']) && is_array($tab['url_params'])) {
+ $url_params = array_merge($url_params, $tab['url_params']);
+ }
+
+ // build the link
+ if (! empty($tab['link'])) {
+ $tab['link'] = htmlentities($tab['link']);
+ $tab['link'] = $tab['link'] . PMA_URL_getCommon($url_params);
+ if (! empty($tab['args'])) {
+ foreach ($tab['args'] as $param => $value) {
+ $tab['link'] .= PMA_URL_getArgSeparator('html')
+ . urlencode($param) . '=' . urlencode($value);
+ }
+ }
+ }
+
+ if (! empty($tab['fragment'])) {
+ $tab['link'] .= $tab['fragment'];
+ }
+
+ // display icon
+ if (isset($tab['icon'])) {
+ // avoid generating an alt tag, because it only illustrates
+ // the text that follows and if browser does not display
+ // images, the text is duplicated
+ $tab['text'] = self::getIcon(
+ $tab['icon'],
+ $tab['text'],
+ false,
+ true,
+ 'TabsMode'
+ );
+
+ } elseif (empty($tab['text'])) {
+ // check to not display an empty link-text
+ $tab['text'] = '?';
+ trigger_error(
+ 'empty linktext in function ' . __FUNCTION__ . '()',
+ E_USER_NOTICE
+ );
+ }
+
+ //Set the id for the tab, if set in the params
+ $id_string = ( empty($tab['id']) ? '' : ' id="'.$tab['id'].'" ' );
+ $out = '<li' . ($tab['class'] == 'active' ? ' class="active"' : '') . '>';
+
+ if (! empty($tab['link'])) {
+ $out .= '<a class="tab' . htmlentities($tab['class']) . '"'
+ .$id_string
+ .' href="' . $tab['link'] . '" ' . $tab['attr'] . '>'
+ . $tab['text'] . '</a>';
+ } else {
+ $out .= '<span class="tab' . htmlentities($tab['class']) . '"'
+ . $id_string. '>' . $tab['text'] . '</span>';
+ }
+
+ $out .= '</li>';
+ return $out;
+ } // end of the 'getHtmlTab()' function
+
+ /**
+ * returns html-code for a tab navigation
+ *
+ * @param array $tabs one element per tab
+ * @param string $url_params additional URL parameters
+ * @param string $menu_id HTML id attribute for the menu container
+ * @param bool $resizable whether to add a "resizable" class
+ *
+ * @return string html-code for tab-navigation
+ */
+ public static function getHtmlTabs($tabs, $url_params, $menu_id,
+ $resizable = false
+ ) {
+ $class = '';
+ if ($resizable) {
+ $class = ' class="resizable-menu"';
+ }
+
+ $tab_navigation = '<div id="' . htmlentities($menu_id)
+ . 'container" class="menucontainer">'
+ .'<ul id="' . htmlentities($menu_id) . '" ' . $class . '>';
+
+ foreach ($tabs as $tab) {
+ $tab_navigation .= self::getHtmlTab($tab, $url_params);
+ }
+
+ $tab_navigation .=
+ '</ul>' . "\n"
+ .'<div class="clearfloat"></div>'
+ .'</div>' . "\n";
+
+ return $tab_navigation;
+ }
+
+ /**
+ * Displays a link, or a button if the link's URL is too large, to
+ * accommodate some browsers' limitations
+ *
+ * @param string $url the URL
+ * @param string $message the link message
+ * @param mixed $tag_params string: js confirmation
+ * array: additional tag params (f.e. style="")
+ * @param boolean $new_form we set this to false when we are already in
+ * a form, to avoid generating nested forms
+ * @param boolean $strip_img whether to strip the image
+ * @param string $target target
+ *
+ * @return string the results to be echoed or saved in an array
+ */
+ public static function linkOrButton(
+ $url, $message, $tag_params = array(),
+ $new_form = true, $strip_img = false, $target = ''
+ ) {
+ $url_length = strlen($url);
+ // with this we should be able to catch case of image upload
+ // into a (MEDIUM) BLOB; not worth generating even a form for these
+ if ($url_length > $GLOBALS['cfg']['LinkLengthLimit'] * 100) {
+ return '';
+ }
+
+ if (! is_array($tag_params)) {
+ $tmp = $tag_params;
+ $tag_params = array();
+ if (! empty($tmp)) {
+ $tag_params['onclick'] = 'return confirmLink(this, \''
+ . PMA_escapeJsString($tmp) . '\')';
+ }
+ unset($tmp);
+ }
+ if (! empty($target)) {
+ $tag_params['target'] = htmlentities($target);
+ }
+
+ $tag_params_strings = array();
+ foreach ($tag_params as $par_name => $par_value) {
+ // htmlspecialchars() only on non javascript
+ $par_value = substr($par_name, 0, 2) == 'on'
+ ? $par_value
+ : htmlspecialchars($par_value);
+ $tag_params_strings[] = $par_name . '="' . $par_value . '"';
+ }
+
+ $displayed_message = '';
+ // Add text if not already added
+ if (stristr($message, '<img')
+ && (! $strip_img || ($GLOBALS['cfg']['ActionLinksMode'] == 'icons'))
+ && (strip_tags($message) == $message)
+ ) {
+ $displayed_message = '<span>'
+ . htmlspecialchars(
+ preg_replace('/^.*\salt="([^"]*)".*$/si', '\1', $message)
+ )
+ . '</span>';
+ }
+
+ // Suhosin: Check that each query parameter is not above maximum
+ $in_suhosin_limits = true;
+ if ($url_length <= $GLOBALS['cfg']['LinkLengthLimit']) {
+ $suhosin_get_MaxValueLength = ini_get('suhosin.get.max_value_length');
+ if ($suhosin_get_MaxValueLength) {
+ $query_parts = self::splitURLQuery($url);
+ foreach ($query_parts as $query_pair) {
+ list($eachvar, $eachval) = explode('=', $query_pair);
+ if (strlen($eachval) > $suhosin_get_MaxValueLength) {
+ $in_suhosin_limits = false;
+ break;
+ }
+ }
+ }
+ }
+
+ if (($url_length <= $GLOBALS['cfg']['LinkLengthLimit'])
+ && $in_suhosin_limits
+ ) {
+ // no whitespace within an <a> else Safari will make it part of the link
+ $ret = "\n" . '<a href="' . $url . '" '
+ . implode(' ', $tag_params_strings) . '>'
+ . $message . $displayed_message . '</a>' . "\n";
+ } else {
+ // no spaces (linebreaks) at all
+ // or after the hidden fields
+ // IE will display them all
+
+ // add class=link to submit button
+ if (empty($tag_params['class'])) {
+ $tag_params['class'] = 'link';
+ }
+
+ if (! isset($query_parts)) {
+ $query_parts = self::splitURLQuery($url);
+ }
+ $url_parts = parse_url($url);
+
+ if ($new_form) {
+ $ret = '<form action="' . $url_parts['path'] . '" class="link"'
+ . ' method="post"' . $target . ' style="display: inline;">';
+ $subname_open = '';
+ $subname_close = '';
+ $submit_link = '#';
+ } else {
+ $query_parts[] = 'redirect=' . $url_parts['path'];
+ if (empty($GLOBALS['subform_counter'])) {
+ $GLOBALS['subform_counter'] = 0;
+ }
+ $GLOBALS['subform_counter']++;
+ $ret = '';
+ $subname_open = 'subform[' . $GLOBALS['subform_counter'] . '][';
+ $subname_close = ']';
+ $submit_link = '#usesubform[' . $GLOBALS['subform_counter']
+ . ']=1';
+ }
+
+ foreach ($query_parts as $query_pair) {
+ list($eachvar, $eachval) = explode('=', $query_pair);
+ $ret .= '<input type="hidden" name="' . $subname_open . $eachvar
+ . $subname_close . '" value="'
+ . htmlspecialchars(urldecode($eachval)) . '" />';
+ } // end while
+
+ $ret .= "\n" . '<a href="' . $submit_link . '" class="formLinkSubmit" '
+ . implode(' ', $tag_params_strings) . '>'
+ . $message . ' ' . $displayed_message . '</a>' . "\n";
+
+ if ($new_form) {
+ $ret .= '</form>';
+ }
+ } // end if... else...
+
+ return $ret;
+ } // end of the 'linkOrButton()' function
+
+ /**
+ * Splits a URL string by parameter
+ *
+ * @param string $url the URL
+ *
+ * @return array the parameter/value pairs, for example [0] db=sakila
+ */
+ public static function splitURLQuery($url)
+ {
+ // decode encoded url separators
+ $separator = PMA_URL_getArgSeparator();
+ // on most places separator is still hard coded ...
+ if ($separator !== '&') {
+ // ... so always replace & with $separator
+ $url = str_replace(htmlentities('&'), $separator, $url);
+ $url = str_replace('&', $separator, $url);
+ }
+
+ $url = str_replace(htmlentities($separator), $separator, $url);
+ // end decode
+
+ $url_parts = parse_url($url);
+
+ return explode($separator, $url_parts['query']);
+ }
+
+ /**
+ * Returns a given timespan value in a readable format.
+ *
+ * @param int $seconds the timespan
+ *
+ * @return string the formatted value
+ */
+ public static function timespanFormat($seconds)
+ {
+ $days = floor($seconds / 86400);
+ if ($days > 0) {
+ $seconds -= $days * 86400;
+ }
+
+ $hours = floor($seconds / 3600);
+ if ($days > 0 || $hours > 0) {
+ $seconds -= $hours * 3600;
+ }
+
+ $minutes = floor($seconds / 60);
+ if ($days > 0 || $hours > 0 || $minutes > 0) {
+ $seconds -= $minutes * 60;
+ }
+
+ return sprintf(
+ __('%s days, %s hours, %s minutes and %s seconds'),
+ (string)$days, (string)$hours, (string)$minutes, (string)$seconds
+ );
+ }
+
+ /**
+ * Takes a string and outputs each character on a line for itself. Used
+ * mainly for horizontalflipped display mode.
+ * Takes care of special html-characters.
+ * Fulfills https://sourceforge.net/p/phpmyadmin/feature-requests/164/
+ *
+ * @param string $string The string
+ * @param string $Separator The Separator (defaults to "<br />\n")
+ *
+ * @access public
+ * @todo add a multibyte safe function $GLOBALS['PMA_String']->split()
+ *
+ * @return string The flipped string
+ */
+ public static function flipstring($string, $Separator = "<br />\n")
+ {
+ $format_string = '';
+ $charbuff = false;
+
+ for ($i = 0, $str_len = strlen($string); $i < $str_len; $i++) {
+ $char = $string{$i};
+ $append = false;
+
+ if ($char == '&') {
+ $format_string .= $charbuff;
+ $charbuff = $char;
+ } elseif ($char == ';' && ! empty($charbuff)) {
+ $format_string .= $charbuff . $char;
+ $charbuff = false;
+ $append = true;
+ } elseif (! empty($charbuff)) {
+ $charbuff .= $char;
+ } else {
+ $format_string .= $char;
+ $append = true;
+ }
+
+ // do not add separator after the last character
+ if ($append && ($i != $str_len - 1)) {
+ $format_string .= $Separator;
+ }
+ }
+
+ return $format_string;
+ }
+
+ /**
+ * Function added to avoid path disclosures.
+ * Called by each script that needs parameters, it displays
+ * an error message and, by default, stops the execution.
+ *
+ * Not sure we could use a strMissingParameter message here,
+ * would have to check if the error message file is always available
+ *
+ * @param array $params The names of the parameters needed by the calling script
+ * @param bool $request Whether to include this list in checking for
+ * special params
+ *
+ * @return void
+ *
+ * @global boolean $checked_special flag whether any special variable was required
+ *
+ * @access public
+ */
+ public static function checkParameters($params, $request = true)
+ {
+ global $checked_special;
+
+ if (! isset($checked_special)) {
+ $checked_special = false;
+ }
+
+ $reported_script_name = basename($GLOBALS['PMA_PHP_SELF']);
+ $found_error = false;
+ $error_message = '';
+
+ foreach ($params as $param) {
+ if ($request && ($param != 'db') && ($param != 'table')) {
+ $checked_special = true;
+ }
+
+ if (! isset($GLOBALS[$param])) {
+ $error_message .= $reported_script_name
+ . ': ' . __('Missing parameter:') . ' '
+ . $param
+ . self::showDocu('faq', 'faqmissingparameters')
+ . '<br />';
+ $found_error = true;
+ }
+ }
+ if ($found_error) {
+ PMA_fatalError($error_message, null, false);
+ }
+ } // end function
+
+ /**
+ * Function to generate unique condition for specified row.
+ *
+ * @param resource $handle current query result
+ * @param integer $fields_cnt number of fields
+ * @param array $fields_meta meta information about fields
+ * @param array $row current row
+ * @param boolean $force_unique generate condition only on pk or unique
+ *
+ * @access public
+ *
+ * @return array the calculated condition and whether condition is unique
+ */
+ public static function getUniqueCondition(
+ $handle, $fields_cnt, $fields_meta, $row, $force_unique = false
+ ) {
+ $primary_key = '';
+ $unique_key = '';
+ $nonprimary_condition = '';
+ $preferred_condition = '';
+ $primary_key_array = array();
+ $unique_key_array = array();
+ $nonprimary_condition_array = array();
+ $condition_array = array();
+
+ for ($i = 0; $i < $fields_cnt; ++$i) {
+
+ $condition = '';
+ $con_key = '';
+ $con_val = '';
+ $field_flags = $GLOBALS['dbi']->fieldFlags($handle, $i);
+ $meta = $fields_meta[$i];
+
+ // do not use a column alias in a condition
+ if (! isset($meta->orgname) || ! strlen($meta->orgname)) {
+ $meta->orgname = $meta->name;
+
+ if (isset($GLOBALS['analyzed_sql'][0]['select_expr'])
+ && is_array($GLOBALS['analyzed_sql'][0]['select_expr'])
+ ) {
+ foreach (
+ $GLOBALS['analyzed_sql'][0]['select_expr'] as $select_expr
+ ) {
+ // need (string) === (string)
+ // '' !== 0 but '' == 0
+ if ((string)$select_expr['alias'] === (string)$meta->name) {
+ $meta->orgname = $select_expr['column'];
+ break;
+ } // end if
+ } // end foreach
+ }
+ }
+
+ // Do not use a table alias in a condition.
+ // Test case is:
+ // select * from galerie x WHERE
+ //(select count(*) from galerie y where y.datum=x.datum)>1
+ //
+ // But orgtable is present only with mysqli extension so the
+ // fix is only for mysqli.
+ // Also, do not use the original table name if we are dealing with
+ // a view because this view might be updatable.
+ // (The isView() verification should not be costly in most cases
+ // because there is some caching in the function).
+ if (isset($meta->orgtable)
+ && ($meta->table != $meta->orgtable)
+ && ! PMA_Table::isView($GLOBALS['db'], $meta->table)
+ ) {
+ $meta->table = $meta->orgtable;
+ }
+
+ // to fix the bug where float fields (primary or not)
+ // can't be matched because of the imprecision of
+ // floating comparison, use CONCAT
+ // (also, the syntax "CONCAT(field) IS NULL"
+ // that we need on the next "if" will work)
+ if ($meta->type == 'real') {
+ $con_key = 'CONCAT(' . self::backquote($meta->table) . '.'
+ . self::backquote($meta->orgname) . ')';
+ } else {
+ $con_key = self::backquote($meta->table) . '.'
+ . self::backquote($meta->orgname);
+ } // end if... else...
+ $condition = ' ' . $con_key . ' ';
+
+ if (! isset($row[$i]) || is_null($row[$i])) {
+ $con_val = 'IS NULL';
+ } else {
+ // timestamp is numeric on some MySQL 4.1
+ // for real we use CONCAT above and it should compare to string
+ if ($meta->numeric
+ && ($meta->type != 'timestamp')
+ && ($meta->type != 'real')
+ ) {
+ $con_val = '= ' . $row[$i];
+ } elseif ((($meta->type == 'blob') || ($meta->type == 'string'))
+ && stristr($field_flags, 'BINARY')
+ && ! empty($row[$i])
+ ) {
+ // hexify only if this is a true not empty BLOB or a BINARY
+
+ // do not waste memory building a too big condition
+ if (strlen($row[$i]) < 1000) {
+ // use a CAST if possible, to avoid problems
+ // if the field contains wildcard characters % or _
+ $con_val = '= CAST(0x' . bin2hex($row[$i]) . ' AS BINARY)';
+ } else if ($fields_cnt == 1) {
+ // when this blob is the only field present
+ // try settling with length comparison
+ $condition = ' CHAR_LENGTH(' . $con_key . ') ';
+ $con_val = ' = ' . strlen($row[$i]);
+ } else {
+ // this blob won't be part of the final condition
+ $con_val = null;
+ }
+ } elseif (in_array($meta->type, self::getGISDatatypes())
+ && ! empty($row[$i])
+ ) {
+ // do not build a too big condition
+ if (strlen($row[$i]) < 5000) {
+ $condition .= '=0x' . bin2hex($row[$i]) . ' AND';
+ } else {
+ $condition = '';
+ }
+ } elseif ($meta->type == 'bit') {
+ $con_val = "= b'"
+ . self::printableBitValue($row[$i], $meta->length) . "'";
+ } else {
+ $con_val = '= \''
+ . self::sqlAddSlashes($row[$i], false, true) . '\'';
+ }
+ }
+
+ if ($con_val != null) {
+
+ $condition .= $con_val . ' AND';
+
+ if ($meta->primary_key > 0) {
+ $primary_key .= $condition;
+ $primary_key_array[$con_key] = $con_val;
+ } elseif ($meta->unique_key > 0) {
+ $unique_key .= $condition;
+ $unique_key_array[$con_key] = $con_val;
+ }
+
+ $nonprimary_condition .= $condition;
+ $nonprimary_condition_array[$con_key] = $con_val;
+ }
+ } // end for
+
+ // Correction University of Virginia 19991216:
+ // prefer primary or unique keys for condition,
+ // but use conjunction of all values if no primary key
+ $clause_is_unique = true;
+
+ if ($primary_key) {
+ $preferred_condition = $primary_key;
+ $condition_array = $primary_key_array;
+
+ } elseif ($unique_key) {
+ $preferred_condition = $unique_key;
+ $condition_array = $unique_key_array;
+
+ } elseif (! $force_unique) {
+ $preferred_condition = $nonprimary_condition;
+ $condition_array = $nonprimary_condition_array;
+ $clause_is_unique = false;
+ }
+
+ $where_clause = trim(preg_replace('|\s?AND$|', '', $preferred_condition));
+ return(array($where_clause, $clause_is_unique, $condition_array));
+ } // end function
+
+ /**
+ * Generate a button or image tag
+ *
+ * @param string $button_name name of button element
+ * @param string $button_class class of button or image element
+ * @param string $image_name name of image element
+ * @param string $text text to display
+ * @param string $image image to display
+ * @param string $value value
+ *
+ * @return string html content
+ *
+ * @access public
+ */
+ public static function getButtonOrImage(
+ $button_name, $button_class, $image_name, $text, $image, $value = ''
+ ) {
+ if ($value == '') {
+ $value = $text;
+ }
+
+ if ($GLOBALS['cfg']['ActionLinksMode'] == 'text') {
+ return ' <input type="submit" name="' . $button_name . '"'
+ .' value="' . htmlspecialchars($value) . '"'
+ .' title="' . htmlspecialchars($text) . '" />' . "\n";
+ }
+
+ /* Opera has trouble with <input type="image"> */
+ /* IE (before version 9) has trouble with <button> */
+ if (PMA_USR_BROWSER_AGENT == 'IE' && PMA_USR_BROWSER_VER < 9) {
+ return '<input type="image" name="' . $image_name
+ . '" class="' . $button_class
+ . '" value="' . htmlspecialchars($value)
+ . '" title="' . htmlspecialchars($text)
+ . '" src="' . $GLOBALS['pmaThemeImage']. $image . '" />'
+ . ($GLOBALS['cfg']['ActionLinksMode'] == 'both'
+ ? '&nbsp;' . htmlspecialchars($text)
+ : '') . "\n";
+ } else {
+ return '<button class="' . $button_class . '" type="submit"'
+ .' name="' . $button_name . '" value="' . htmlspecialchars($value)
+ . '" title="' . htmlspecialchars($text) . '">' . "\n"
+ . self::getIcon($image, $text)
+ .'</button>' . "\n";
+ }
+ } // end function
+
+ /**
+ * Generate a pagination selector for browsing resultsets
+ *
+ * @param string $name The name for the request parameter
+ * @param int $rows Number of rows in the pagination set
+ * @param int $pageNow current page number
+ * @param int $nbTotalPage number of total pages
+ * @param int $showAll If the number of pages is lower than this
+ * variable, no pages will be omitted in pagination
+ * @param int $sliceStart How many rows at the beginning should always
+ * be shown?
+ * @param int $sliceEnd How many rows at the end should always be shown?
+ * @param int $percent Percentage of calculation page offsets to hop to a
+ * next page
+ * @param int $range Near the current page, how many pages should
+ * be considered "nearby" and displayed as well?
+ * @param string $prompt The prompt to display (sometimes empty)
+ *
+ * @return string
+ *
+ * @access public
+ */
+ public static function pageselector(
+ $name, $rows, $pageNow = 1, $nbTotalPage = 1, $showAll = 200,
+ $sliceStart = 5,
+ $sliceEnd = 5, $percent = 20, $range = 10, $prompt = ''
+ ) {
+ $increment = floor($nbTotalPage / $percent);
+ $pageNowMinusRange = ($pageNow - $range);
+ $pageNowPlusRange = ($pageNow + $range);
+
+ $gotopage = $prompt . ' <select class="pageselector ';
+ $gotopage .= ' ajax';
+
+ $gotopage .= '" name="' . $name . '" >';
+ if ($nbTotalPage < $showAll) {
+ $pages = range(1, $nbTotalPage);
+ } else {
+ $pages = array();
+
+ // Always show first X pages
+ for ($i = 1; $i <= $sliceStart; $i++) {
+ $pages[] = $i;
+ }
+
+ // Always show last X pages
+ for ($i = $nbTotalPage - $sliceEnd; $i <= $nbTotalPage; $i++) {
+ $pages[] = $i;
+ }
+
+ // Based on the number of results we add the specified
+ // $percent percentage to each page number,
+ // so that we have a representing page number every now and then to
+ // immediately jump to specific pages.
+ // As soon as we get near our currently chosen page ($pageNow -
+ // $range), every page number will be shown.
+ $i = $sliceStart;
+ $x = $nbTotalPage - $sliceEnd;
+ $met_boundary = false;
+
+ while ($i <= $x) {
+ if ($i >= $pageNowMinusRange && $i <= $pageNowPlusRange) {
+ // If our pageselector comes near the current page, we use 1
+ // counter increments
+ $i++;
+ $met_boundary = true;
+ } else {
+ // We add the percentage increment to our current page to
+ // hop to the next one in range
+ $i += $increment;
+
+ // Make sure that we do not cross our boundaries.
+ if ($i > $pageNowMinusRange && ! $met_boundary) {
+ $i = $pageNowMinusRange;
+ }
+ }
+
+ if ($i > 0 && $i <= $x) {
+ $pages[] = $i;
+ }
+ }
+
+ /*
+ Add page numbers with "geometrically increasing" distances.
+
+ This helps me a lot when navigating through giant tables.
+
+ Test case: table with 2.28 million sets, 76190 pages. Page of interest
+ is between 72376 and 76190.
+ Selecting page 72376.
+ Now, old version enumerated only +/- 10 pages around 72376 and the
+ percentage increment produced steps of about 3000.
+
+ The following code adds page numbers +/- 2,4,8,16,32,64,128,256 etc.
+ around the current page.
+ */
+ $i = $pageNow;
+ $dist = 1;
+ while ($i < $x) {
+ $dist = 2 * $dist;
+ $i = $pageNow + $dist;
+ if ($i > 0 && $i <= $x) {
+ $pages[] = $i;
+ }
+ }
+
+ $i = $pageNow;
+ $dist = 1;
+ while ($i >0) {
+ $dist = 2 * $dist;
+ $i = $pageNow - $dist;
+ if ($i > 0 && $i <= $x) {
+ $pages[] = $i;
+ }
+ }
+
+ // Since because of ellipsing of the current page some numbers may be
+ // double, we unify our array:
+ sort($pages);
+ $pages = array_unique($pages);
+ }
+
+ foreach ($pages as $i) {
+ if ($i == $pageNow) {
+ $selected = 'selected="selected" style="font-weight: bold"';
+ } else {
+ $selected = '';
+ }
+ $gotopage .= ' <option ' . $selected
+ . ' value="' . (($i - 1) * $rows) . '">' . $i . '</option>' . "\n";
+ }
+
+ $gotopage .= ' </select>';
+
+ return $gotopage;
+ } // end function
+
+ /**
+ * Prepare navigation for a list
+ *
+ * @param int $count number of elements in the list
+ * @param int $pos current position in the list
+ * @param array $_url_params url parameters
+ * @param string $script script name for form target
+ * @param string $frame target frame
+ * @param int $max_count maximum number of elements to display from the list
+ * @param string $name the name for the request parameter
+ * @param array $classes additional classes for the container
+ *
+ * @return string $list_navigator_html the html content
+ *
+ * @access public
+ *
+ * @todo use $pos from $_url_params
+ */
+ public static function getListNavigator(
+ $count, $pos, $_url_params, $script, $frame, $max_count, $name = 'pos',
+ $classes = array()
+ ) {
+
+ $class = $frame == 'frame_navigation' ? ' class="ajax"' : '';
+
+ $list_navigator_html = '';
+
+ if ($max_count < $count) {
+
+ $classes[] = 'pageselector';
+ $list_navigator_html .= '<div class="' . implode(' ', $classes) . '">';
+
+ if ($frame != 'frame_navigation') {
+ $list_navigator_html .= __('Page number:');
+ }
+
+ // Move to the beginning or to the previous page
+ if ($pos > 0) {
+ if (self::showIcons('TableNavigationLinksMode')) {
+ $caption1 = '&lt;&lt;';
+ $caption2 = ' &lt; ';
+ $title1 = ' title="' . _pgettext('First page', 'Begin') . '"';
+ $title2 = ' title="'
+ . _pgettext('Previous page', 'Previous') . '"';
+ } else {
+ $caption1 = _pgettext('First page', 'Begin') . ' &lt;&lt;';
+ $caption2 = _pgettext('Previous page', 'Previous') . ' &lt;';
+ $title1 = '';
+ $title2 = '';
+ } // end if... else...
+
+ $_url_params[$name] = 0;
+ $list_navigator_html .= '<a' . $class . $title1 . ' href="' . $script
+ . PMA_URL_getCommon($_url_params) . '">' . $caption1
+ . '</a>';
+
+ $_url_params[$name] = $pos - $max_count;
+ $list_navigator_html .= '<a' . $class . $title2 . ' href="' . $script
+ . PMA_URL_getCommon($_url_params) . '">' . $caption2
+ . '</a>';
+ }
+
+ $list_navigator_html .= '<form action="' . basename($script).
+ '" method="post">';
+
+ $list_navigator_html .= PMA_URL_getHiddenInputs($_url_params);
+ $list_navigator_html .= self::pageselector(
+ $name,
+ $max_count,
+ floor(($pos + 1) / $max_count) + 1,
+ ceil($count / $max_count)
+ );
+ $list_navigator_html .= '</form>';
+
+ if ($pos + $max_count < $count) {
+ if ( self::showIcons('TableNavigationLinksMode')) {
+ $caption3 = ' &gt; ';
+ $caption4 = '&gt;&gt;';
+ $title3 = ' title="' . _pgettext('Next page', 'Next') . '"';
+ $title4 = ' title="' . _pgettext('Last page', 'End') . '"';
+ } else {
+ $caption3 = '&gt; ' . _pgettext('Next page', 'Next');
+ $caption4 = '&gt;&gt; ' . _pgettext('Last page', 'End');
+ $title3 = '';
+ $title4 = '';
+ } // end if... else...
+
+ $_url_params[$name] = $pos + $max_count;
+ $list_navigator_html .= '<a' . $class . $title3 . ' href="' . $script
+ . PMA_URL_getCommon($_url_params) . '" >' . $caption3
+ . '</a>';
+
+ $_url_params[$name] = floor($count / $max_count) * $max_count;
+ if ($_url_params[$name] == $count) {
+ $_url_params[$name] = $count - $max_count;
+ }
+
+ $list_navigator_html .= '<a' . $class . $title4 . ' href="' . $script
+ . PMA_URL_getCommon($_url_params) . '" >' . $caption4
+ . '</a>';
+ }
+ $list_navigator_html .= '</div>' . "\n";
+ }
+
+ return $list_navigator_html;
+ }
+
+ /**
+ * replaces %u in given path with current user name
+ *
+ * example:
+ * <code>
+ * $user_dir = userDir('/var/pma_tmp/%u/'); // '/var/pma_tmp/root/'
+ *
+ * </code>
+ *
+ * @param string $dir with wildcard for user
+ *
+ * @return string per user directory
+ */
+ public static function userDir($dir)
+ {
+ // add trailing slash
+ if (substr($dir, -1) != '/') {
+ $dir .= '/';
+ }
+
+ return str_replace('%u', $GLOBALS['cfg']['Server']['user'], $dir);
+ }
+
+ /**
+ * returns html code for db link to default db page
+ *
+ * @param string $database database
+ *
+ * @return string html link to default db page
+ */
+ public static function getDbLink($database = null)
+ {
+ if (! strlen($database)) {
+ if (! strlen($GLOBALS['db'])) {
+ return '';
+ }
+ $database = $GLOBALS['db'];
+ } else {
+ $database = self::unescapeMysqlWildcards($database);
+ }
+
+ return '<a href="' . $GLOBALS['cfg']['DefaultTabDatabase'] . '?'
+ . PMA_URL_getCommon($database) . '" title="'
+ . sprintf(
+ __('Jump to database &quot;%s&quot;.'),
+ htmlspecialchars($database)
+ )
+ . '">' . htmlspecialchars($database) . '</a>';
+ }
+
+ /**
+ * Prepare a lightbulb hint explaining a known external bug
+ * that affects a functionality
+ *
+ * @param string $functionality localized message explaining the func.
+ * @param string $component 'mysql' (eventually, 'php')
+ * @param string $minimum_version of this component
+ * @param string $bugref bug reference for this component
+ *
+ * @return String
+ */
+ public static function getExternalBug(
+ $functionality, $component, $minimum_version, $bugref
+ ) {
+ $ext_but_html = '';
+ if (($component == 'mysql') && (PMA_MYSQL_INT_VERSION < $minimum_version)) {
+ $ext_but_html .= self::showHint(
+ sprintf(
+ __('The %s functionality is affected by a known bug, see %s'),
+ $functionality,
+ PMA_linkURL('http://bugs.mysql.com/') . $bugref
+ )
+ );
+ }
+ return $ext_but_html;
+ }
+
+ /**
+ * Returns a HTML checkbox
+ *
+ * @param string $html_field_name the checkbox HTML field
+ * @param string $label label for checkbox
+ * @param boolean $checked is it initially checked?
+ * @param boolean $onclick should it submit the form on click?
+ *
+ * @return string HTML for the checkbox
+ */
+ public static function getCheckbox($html_field_name, $label, $checked, $onclick)
+ {
+ return '<input type="checkbox" name="' . $html_field_name . '" id="'
+ . $html_field_name . '"' . ($checked ? ' checked="checked"' : '')
+ . ($onclick ? ' class="autosubmit"' : '') . ' /><label for="'
+ . $html_field_name . '">' . $label . '</label>';
+ }
+
+ /**
+ * Generates a set of radio HTML fields
+ *
+ * @param string $html_field_name the radio HTML field
+ * @param array $choices the choices values and labels
+ * @param string $checked_choice the choice to check by default
+ * @param boolean $line_break whether to add HTML line break after a choice
+ * @param boolean $escape_label whether to use htmlspecialchars() on label
+ * @param string $class enclose each choice with a div of this class
+ *
+ * @return string set of html radio fiels
+ */
+ public static function getRadioFields(
+ $html_field_name, $choices, $checked_choice = '',
+ $line_break = true, $escape_label = true, $class = ''
+ ) {
+ $radio_html = '';
+
+ foreach ($choices as $choice_value => $choice_label) {
+
+ if (! empty($class)) {
+ $radio_html .= '<div class="' . $class . '">';
+ }
+
+ $html_field_id = $html_field_name . '_' . $choice_value;
+ $radio_html .= '<input type="radio" name="' . $html_field_name . '" id="'
+ . $html_field_id . '" value="'
+ . htmlspecialchars($choice_value) . '"';
+
+ if ($choice_value == $checked_choice) {
+ $radio_html .= ' checked="checked"';
+ }
+
+ $radio_html .= ' />' . "\n"
+ . '<label for="' . $html_field_id . '">'
+ . ($escape_label
+ ? htmlspecialchars($choice_label)
+ : $choice_label)
+ . '</label>';
+
+ if ($line_break) {
+ $radio_html .= '<br />';
+ }
+
+ if (! empty($class)) {
+ $radio_html .= '</div>';
+ }
+ $radio_html .= "\n";
+ }
+
+ return $radio_html;
+ }
+
+ /**
+ * Generates and returns an HTML dropdown
+ *
+ * @param string $select_name name for the select element
+ * @param array $choices choices values
+ * @param string $active_choice the choice to select by default
+ * @param string $id id of the select element; can be different in
+ * case the dropdown is present more than once
+ * on the page
+ * @param string $class class for the select element
+ *
+ * @return string html content
+ *
+ * @todo support titles
+ */
+ public static function getDropdown(
+ $select_name, $choices, $active_choice, $id, $class = ''
+ ) {
+ $result = '<select'
+ . ' name="' . htmlspecialchars($select_name) . '"'
+ . ' id="' . htmlspecialchars($id) . '"'
+ . (! empty($class) ? ' class="' . htmlspecialchars($class) . '"' : '')
+ . '>';
+
+ foreach ($choices as $one_choice_value => $one_choice_label) {
+ $result .= '<option value="' . htmlspecialchars($one_choice_value) . '"';
+
+ if ($one_choice_value == $active_choice) {
+ $result .= ' selected="selected"';
+ }
+ $result .= '>' . htmlspecialchars($one_choice_label) . '</option>';
+ }
+ $result .= '</select>';
+
+ return $result;
+ }
+
+ /**
+ * Generates a slider effect (jQjuery)
+ * Takes care of generating the initial <div> and the link
+ * controlling the slider; you have to generate the </div> yourself
+ * after the sliding section.
+ *
+ * @param string $id the id of the <div> on which to apply the effect
+ * @param string $message the message to show as a link
+ *
+ * @return string html div element
+ *
+ */
+ public static function getDivForSliderEffect($id, $message)
+ {
+ if ($GLOBALS['cfg']['InitialSlidersState'] == 'disabled') {
+ return '<div id="' . $id . '">';
+ }
+ /**
+ * Bad hack on the next line. document.write() conflicts with jQuery,
+ * hence, opening the <div> with PHP itself instead of JavaScript.
+ *
+ * @todo find a better solution that uses $.append(), the recommended
+ * method maybe by using an additional param, the id of the div to
+ * append to
+ */
+
+ return '<div id="' . $id . '"'
+ . (($GLOBALS['cfg']['InitialSlidersState'] == 'closed')
+ ? ' style="display: none; overflow:auto;"'
+ : '')
+ . ' class="pma_auto_slider" title="' . htmlspecialchars($message) . '">';
+ }
+
+ /**
+ * Creates an AJAX sliding toggle button
+ * (or and equivalent form when AJAX is disabled)
+ *
+ * @param string $action The URL for the request to be executed
+ * @param string $select_name The name for the dropdown box
+ * @param array $options An array of options (see rte_footer.lib.php)
+ * @param string $callback A JS snippet to execute when the request is
+ * successfully processed
+ *
+ * @return string HTML code for the toggle button
+ */
+ public static function toggleButton($action, $select_name, $options, $callback)
+ {
+ // Do the logic first
+ $link = "$action&amp;" . urlencode($select_name) . "=";
+ $link_on = $link . urlencode($options[1]['value']);
+ $link_off = $link . urlencode($options[0]['value']);
+
+ if ($options[1]['selected'] == true) {
+ $state = 'on';
+ } else if ($options[0]['selected'] == true) {
+ $state = 'off';
+ } else {
+ $state = 'on';
+ }
+
+ // Generate output
+ return "<!-- TOGGLE START -->\n"
+ . "<div class='wrapper toggleAjax hide'>\n"
+ . " <div class='toggleButton'>\n"
+ . " <div title='" . __('Click to toggle')
+ . "' class='container $state'>\n"
+ . " <img src='" . htmlspecialchars($GLOBALS['pmaThemeImage'])
+ . "toggle-" . htmlspecialchars($GLOBALS['text_dir']) . ".png'\n"
+ . " alt='' />\n"
+ . " <table class='nospacing nopadding'>\n"
+ . " <tbody>\n"
+ . " <tr>\n"
+ . " <td class='toggleOn'>\n"
+ . " <span class='hide'>$link_on</span>\n"
+ . " <div>"
+ . str_replace(' ', '&nbsp;', htmlspecialchars($options[1]['label']))
+ . "\n" . " </div>\n"
+ . " </td>\n"
+ . " <td><div>&nbsp;</div></td>\n"
+ . " <td class='toggleOff'>\n"
+ . " <span class='hide'>$link_off</span>\n"
+ . " <div>"
+ . str_replace(' ', '&nbsp;', htmlspecialchars($options[0]['label']))
+ . "\n" . " </div>\n"
+ . " </tr>\n"
+ . " </tbody>\n"
+ . " </table>\n"
+ . " <span class='hide callback'>"
+ . htmlspecialchars($callback) . "</span>\n"
+ . " <span class='hide text_direction'>"
+ . htmlspecialchars($GLOBALS['text_dir']) . "</span>\n"
+ . " </div>\n"
+ . " </div>\n"
+ . "</div>\n"
+ . "<!-- TOGGLE END -->";
+
+ } // end toggleButton()
+
+ /**
+ * Clears cache content which needs to be refreshed on user change.
+ *
+ * @return void
+ */
+ public static function clearUserCache()
+ {
+ self::cacheUnset('is_superuser', true);
+ }
+
+ /**
+ * Verifies if something is cached in the session
+ *
+ * @param string $var variable name
+ * @param int|true $server server
+ *
+ * @return boolean
+ */
+ public static function cacheExists($var, $server = 0)
+ {
+ if ($server === true) {
+ $server = $GLOBALS['server'];
+ }
+ return isset($_SESSION['cache']['server_' . $server][$var]);
+ }
+
+ /**
+ * Gets cached information from the session
+ *
+ * @param string $var varibale name
+ * @param int|true $server server
+ *
+ * @return mixed
+ */
+ public static function cacheGet($var, $server = 0)
+ {
+ if ($server === true) {
+ $server = $GLOBALS['server'];
+ }
+ if (isset($_SESSION['cache']['server_' . $server][$var])) {
+ return $_SESSION['cache']['server_' . $server][$var];
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Caches information in the session
+ *
+ * @param string $var variable name
+ * @param mixed $val value
+ * @param int|true $server server
+ *
+ * @return mixed
+ */
+ public static function cacheSet($var, $val = null, $server = 0)
+ {
+ if ($server === true) {
+ $server = $GLOBALS['server'];
+ }
+ $_SESSION['cache']['server_' . $server][$var] = $val;
+ }
+
+ /**
+ * Removes cached information from the session
+ *
+ * @param string $var variable name
+ * @param int|true $server server
+ *
+ * @return void
+ */
+ public static function cacheUnset($var, $server = 0)
+ {
+ if ($server === true) {
+ $server = $GLOBALS['server'];
+ }
+ unset($_SESSION['cache']['server_' . $server][$var]);
+ }
+
+ /**
+ * Converts a bit value to printable format;
+ * in MySQL a BIT field can be from 1 to 64 bits so we need this
+ * function because in PHP, decbin() supports only 32 bits
+ * on 32-bit servers
+ *
+ * @param number $value coming from a BIT field
+ * @param integer $length length
+ *
+ * @return string the printable value
+ */
+ public static function printableBitValue($value, $length)
+ {
+ // if running on a 64-bit server or the length is safe for decbin()
+ if (PHP_INT_SIZE == 8 || $length < 33) {
+ $printable = decbin($value);
+ } else {
+ // FIXME: does not work for the leftmost bit of a 64-bit value
+ $i = 0;
+ $printable = '';
+ while ($value >= pow(2, $i)) {
+ $i++;
+ }
+ if ($i != 0) {
+ $i = $i - 1;
+ }
+
+ while ($i >= 0) {
+ if ($value - pow(2, $i) < 0) {
+ $printable = '0' . $printable;
+ } else {
+ $printable = '1' . $printable;
+ $value = $value - pow(2, $i);
+ }
+ $i--;
+ }
+ $printable = strrev($printable);
+ }
+ $printable = str_pad($printable, $length, '0', STR_PAD_LEFT);
+ return $printable;
+ }
+
+ /**
+ * Verifies whether the value contains a non-printable character
+ *
+ * @param string $value value
+ *
+ * @return boolean
+ */
+ public static function containsNonPrintableAscii($value)
+ {
+ return preg_match('@[^[:print:]]@', $value);
+ }
+
+ /**
+ * Converts a BIT type default value
+ * for example, b'010' becomes 010
+ *
+ * @param string $bit_default_value value
+ *
+ * @return string the converted value
+ */
+ public static function convertBitDefaultValue($bit_default_value)
+ {
+ return strtr($bit_default_value, array("b" => "", "'" => ""));
+ }
+
+ /**
+ * Extracts the various parts from a column spec
+ *
+ * @param string $columnspec Column specification
+ *
+ * @return array associative array containing type, spec_in_brackets
+ * and possibly enum_set_values (another array)
+ */
+ public static function extractColumnSpec($columnspec)
+ {
+ $first_bracket_pos = strpos($columnspec, '(');
+ if ($first_bracket_pos) {
+ $spec_in_brackets = chop(
+ substr(
+ $columnspec,
+ $first_bracket_pos + 1,
+ (strrpos($columnspec, ')') - $first_bracket_pos - 1)
+ )
+ );
+ // convert to lowercase just to be sure
+ $type = strtolower(chop(substr($columnspec, 0, $first_bracket_pos)));
+ } else {
+ $type = strtolower($columnspec);
+ $spec_in_brackets = '';
+ }
+
+ if ('enum' == $type || 'set' == $type) {
+ // Define our working vars
+ $enum_set_values = self::parseEnumSetValues($columnspec, false);
+ $printtype = $type
+ . '(' . str_replace("','", "', '", $spec_in_brackets) . ')';
+ $binary = false;
+ $unsigned = false;
+ $zerofill = false;
+ } else {
+ $enum_set_values = array();
+
+ /* Create printable type name */
+ $printtype = strtolower($columnspec);
+
+ // Strip the "BINARY" attribute, except if we find "BINARY(" because
+ // this would be a BINARY or VARBINARY column type;
+ // by the way, a BLOB should not show the BINARY attribute
+ // because this is not accepted in MySQL syntax.
+ if (preg_match('@binary@', $printtype)
+ && ! preg_match('@binary[\(]@', $printtype)
+ ) {
+ $printtype = preg_replace('@binary@', '', $printtype);
+ $binary = true;
+ } else {
+ $binary = false;
+ }
+
+ $printtype = preg_replace(
+ '@zerofill@', '', $printtype, -1, $zerofill_cnt
+ );
+ $zerofill = ($zerofill_cnt > 0);
+ $printtype = preg_replace(
+ '@unsigned@', '', $printtype, -1, $unsigned_cnt
+ );
+ $unsigned = ($unsigned_cnt > 0);
+ $printtype = trim($printtype);
+ }
+
+ $attribute = ' ';
+ if ($binary) {
+ $attribute = 'BINARY';
+ }
+ if ($unsigned) {
+ $attribute = 'UNSIGNED';
+ }
+ if ($zerofill) {
+ $attribute = 'UNSIGNED ZEROFILL';
+ }
+
+ $can_contain_collation = false;
+ if (! $binary
+ && preg_match(
+ "@^(char|varchar|text|tinytext|mediumtext|longtext|set|enum)@", $type
+ )
+ ) {
+ $can_contain_collation = true;
+ }
+
+ // for the case ENUM('&#8211;','&ldquo;')
+ $displayed_type = htmlspecialchars($printtype);
+ if (strlen($printtype) > $GLOBALS['cfg']['LimitChars']) {
+ $displayed_type = '<abbr title="' . $printtype . '">';
+ $displayed_type .= substr($printtype, 0, $GLOBALS['cfg']['LimitChars']);
+ $displayed_type .= '</abbr>';
+ }
+
+ return array(
+ 'type' => $type,
+ 'spec_in_brackets' => $spec_in_brackets,
+ 'enum_set_values' => $enum_set_values,
+ 'print_type' => $printtype,
+ 'binary' => $binary,
+ 'unsigned' => $unsigned,
+ 'zerofill' => $zerofill,
+ 'attribute' => $attribute,
+ 'can_contain_collation' => $can_contain_collation,
+ 'displayed_type' => $displayed_type
+ );
+ }
+
+ /**
+ * Verifies if this table's engine supports foreign keys
+ *
+ * @param string $engine engine
+ *
+ * @return boolean
+ */
+ public static function isForeignKeySupported($engine)
+ {
+ $engine = strtoupper($engine);
+ if (($engine == 'INNODB') || ($engine == 'PBXT')) {
+ return true;
+ } elseif ($engine == 'NDBCLUSTER' || $engine == 'NDB') {
+ $ndbver = $GLOBALS['dbi']->fetchValue(
+ "SHOW VARIABLES LIKE 'ndb_version_string'"
+ );
+ return ($ndbver >= 7.3);
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Replaces some characters by a displayable equivalent
+ *
+ * @param string $content content
+ *
+ * @return string the content with characters replaced
+ */
+ public static function replaceBinaryContents($content)
+ {
+ $result = str_replace("\x00", '\0', $content);
+ $result = str_replace("\x08", '\b', $result);
+ $result = str_replace("\x0a", '\n', $result);
+ $result = str_replace("\x0d", '\r', $result);
+ $result = str_replace("\x1a", '\Z', $result);
+ return $result;
+ }
+
+ /**
+ * Converts GIS data to Well Known Text format
+ *
+ * @param binary $data GIS data
+ * @param bool $includeSRID Add SRID to the WKT
+ *
+ * @return string GIS data in Well Know Text format
+ */
+ public static function asWKT($data, $includeSRID = false)
+ {
+ // Convert to WKT format
+ $hex = bin2hex($data);
+ $wktsql = "SELECT ASTEXT(x'" . $hex . "')";
+ if ($includeSRID) {
+ $wktsql .= ", SRID(x'" . $hex . "')";
+ }
+
+ $wktresult = $GLOBALS['dbi']->tryQuery(
+ $wktsql, null, PMA_DatabaseInterface::QUERY_STORE
+ );
+ $wktarr = $GLOBALS['dbi']->fetchRow($wktresult, 0);
+ $wktval = $wktarr[0];
+
+ if ($includeSRID) {
+ $srid = $wktarr[1];
+ $wktval = "'" . $wktval . "'," . $srid;
+ }
+ @$GLOBALS['dbi']->freeResult($wktresult);
+
+ return $wktval;
+ }
+
+ /**
+ * If the string starts with a \r\n pair (0x0d0a) add an extra \n
+ *
+ * @param string $string string
+ *
+ * @return string with the chars replaced
+ */
+ public static function duplicateFirstNewline($string)
+ {
+ $first_occurence = strpos($string, "\r\n");
+ if ($first_occurence === 0) {
+ $string = "\n" . $string;
+ }
+ return $string;
+ }
+
+ /**
+ * Get the action word corresponding to a script name
+ * in order to display it as a title in navigation panel
+ *
+ * @param string $target a valid value for $cfg['NavigationTreeDefaultTabTable'],
+ * $cfg['DefaultTabTable'] or $cfg['DefaultTabDatabase']
+ *
+ * @return array
+ */
+ public static function getTitleForTarget($target)
+ {
+ $mapping = array(
+ // Values for $cfg['DefaultTabTable']
+ 'tbl_structure.php' => __('Structure'),
+ 'tbl_sql.php' => __('SQL'),
+ 'tbl_select.php' =>__('Search'),
+ 'tbl_change.php' =>__('Insert'),
+ 'sql.php' => __('Browse'),
+
+ // Values for $cfg['DefaultTabDatabase']
+ 'db_structure.php' => __('Structure'),
+ 'db_sql.php' => __('SQL'),
+ 'db_search.php' => __('Search'),
+ 'db_operations.php' => __('Operations'),
+ );
+ return $mapping[$target];
+ }
+
+ /**
+ * Formats user string, expanding @VARIABLES@, accepting strftime format
+ * string.
+ *
+ * @param string $string Text where to do expansion.
+ * @param array|string $escape Function to call for escaping variable values.
+ * Can also be an array of:
+ * - the escape method name
+ * - the class that contains the method
+ * - location of the class (for inclusion)
+ * @param array $updates Array with overrides for default parameters
+ * (obtained from GLOBALS).
+ *
+ * @return string
+ */
+ public static function expandUserString(
+ $string, $escape = null, $updates = array()
+ ) {
+ /* Content */
+ $vars['http_host'] = PMA_getenv('HTTP_HOST');
+ $vars['server_name'] = $GLOBALS['cfg']['Server']['host'];
+ $vars['server_verbose'] = $GLOBALS['cfg']['Server']['verbose'];
+
+ if (empty($GLOBALS['cfg']['Server']['verbose'])) {
+ $vars['server_verbose_or_name'] = $GLOBALS['cfg']['Server']['host'];
+ } else {
+ $vars['server_verbose_or_name'] = $GLOBALS['cfg']['Server']['verbose'];
+ }
+
+ $vars['database'] = $GLOBALS['db'];
+ $vars['table'] = $GLOBALS['table'];
+ $vars['phpmyadmin_version'] = 'phpMyAdmin ' . PMA_VERSION;
+
+ /* Update forced variables */
+ foreach ($updates as $key => $val) {
+ $vars[$key] = $val;
+ }
+
+ /* Replacement mapping */
+ /*
+ * The __VAR__ ones are for backward compatibility, because user
+ * might still have it in cookies.
+ */
+ $replace = array(
+ '@HTTP_HOST@' => $vars['http_host'],
+ '@SERVER@' => $vars['server_name'],
+ '__SERVER__' => $vars['server_name'],
+ '@VERBOSE@' => $vars['server_verbose'],
+ '@VSERVER@' => $vars['server_verbose_or_name'],
+ '@DATABASE@' => $vars['database'],
+ '__DB__' => $vars['database'],
+ '@TABLE@' => $vars['table'],
+ '__TABLE__' => $vars['table'],
+ '@PHPMYADMIN@' => $vars['phpmyadmin_version'],
+ );
+
+ /* Optional escaping */
+ if (! is_null($escape)) {
+ if (is_array($escape)) {
+ include_once $escape[2];
+ $escape_class = new $escape[1];
+ $escape_method = $escape[0];
+ }
+ foreach ($replace as $key => $val) {
+ if (is_array($escape)) {
+ $replace[$key] = $escape_class->$escape_method($val);
+ } else {
+ $replace[$key] = ($escape == 'backquote')
+ ? self::$escape($val)
+ : $escape($val);
+ }
+ }
+ }
+
+ /* Backward compatibility in 3.5.x */
+ if (strpos($string, '@FIELDS@') !== false) {
+ $string = strtr($string, array('@FIELDS@' => '@COLUMNS@'));
+ }
+
+ /* Fetch columns list if required */
+ if (strpos($string, '@COLUMNS@') !== false) {
+ $columns_list = $GLOBALS['dbi']->getColumns(
+ $GLOBALS['db'], $GLOBALS['table']
+ );
+
+ // sometimes the table no longer exists at this point
+ if (! is_null($columns_list)) {
+ $column_names = array();
+ foreach ($columns_list as $column) {
+ if (! is_null($escape)) {
+ $column_names[] = self::$escape($column['Field']);
+ } else {
+ $column_names[] = $column['Field'];
+ }
+ }
+ $replace['@COLUMNS@'] = implode(',', $column_names);
+ } else {
+ $replace['@COLUMNS@'] = '*';
+ }
+ }
+
+ /* Do the replacement */
+ return strtr(strftime($string), $replace);
+ }
+
+ /**
+ * Prepare the form used to browse anywhere on the local server for a file to
+ * import
+ *
+ * @param string $max_upload_size maximum upload size
+ *
+ * @return String
+ */
+ public static function getBrowseUploadFileBlock($max_upload_size)
+ {
+ $block_html = '';
+
+ if ($GLOBALS['is_upload'] && ! empty($GLOBALS['cfg']['UploadDir'])) {
+ $block_html .= '<label for="radio_import_file">';
+ } else {
+ $block_html .= '<label for="input_import_file">';
+ }
+
+ $block_html .= __("Browse your computer:") . '</label>'
+ . '<div id="upload_form_status" style="display: none;"></div>'
+ . '<div id="upload_form_status_info" style="display: none;"></div>'
+ . '<input type="file" name="import_file" id="input_import_file" />'
+ . self::getFormattedMaximumUploadSize($max_upload_size) . "\n"
+ // some browsers should respect this :)
+ . self::generateHiddenMaxFileSize($max_upload_size) . "\n";
+
+ return $block_html;
+ }
+
+ /**
+ * Prepare the form used to select a file to import from the server upload
+ * directory
+ *
+ * @param array $import_list array of import plugins
+ * @param string $uploaddir upload directory
+ *
+ * @return String
+ */
+ public static function getSelectUploadFileBlock($import_list, $uploaddir)
+ {
+ $block_html = '';
+ $block_html .= '<label for="radio_local_import_file">'
+ . sprintf(
+ __("Select from the web server upload directory <b>%s</b>:"),
+ htmlspecialchars(self::userDir($uploaddir))
+ )
+ . '</label>';
+
+ $extensions = '';
+ foreach ($import_list as $import_plugin) {
+ if (! empty($extensions)) {
+ $extensions .= '|';
+ }
+ $extensions .= $import_plugin->getProperties()->getExtension();
+ }
+
+ $matcher = '@\.(' . $extensions . ')(\.('
+ . PMA_supportedDecompressions() . '))?$@';
+
+ $active = (isset($GLOBALS['timeout_passed']) && $GLOBALS['timeout_passed']
+ && isset($local_import_file))
+ ? $local_import_file
+ : '';
+
+ $files = PMA_getFileSelectOptions(
+ self::userDir($uploaddir),
+ $matcher,
+ $active
+ );
+
+ if ($files === false) {
+ PMA_Message::error(
+ __('The directory you set for upload work cannot be reached.')
+ )->display();
+ } elseif (! empty($files)) {
+ $block_html .= "\n"
+ . ' <select style="margin: 5px" size="1" '
+ . 'name="local_import_file" '
+ . 'id="select_local_import_file">' . "\n"
+ . ' <option value="">&nbsp;</option>' . "\n"
+ . $files
+ . ' </select>' . "\n";
+ } elseif (empty ($files)) {
+ $block_html .= '<i>' . __('There are no files to upload') . '</i>';
+ }
+
+ return $block_html;
+
+ }
+
+ /**
+ * Build titles and icons for action links
+ *
+ * @return array the action titles
+ */
+ public static function buildActionTitles()
+ {
+ $titles = array();
+
+ $titles['Browse'] = self::getIcon('b_browse.png', __('Browse'));
+ $titles['NoBrowse'] = self::getIcon('bd_browse.png', __('Browse'));
+ $titles['Search'] = self::getIcon('b_select.png', __('Search'));
+ $titles['NoSearch'] = self::getIcon('bd_select.png', __('Search'));
+ $titles['Insert'] = self::getIcon('b_insrow.png', __('Insert'));
+ $titles['NoInsert'] = self::getIcon('bd_insrow.png', __('Insert'));
+ $titles['Structure'] = self::getIcon('b_props.png', __('Structure'));
+ $titles['Drop'] = self::getIcon('b_drop.png', __('Drop'));
+ $titles['NoDrop'] = self::getIcon('bd_drop.png', __('Drop'));
+ $titles['Empty'] = self::getIcon('b_empty.png', __('Empty'));
+ $titles['NoEmpty'] = self::getIcon('bd_empty.png', __('Empty'));
+ $titles['Edit'] = self::getIcon('b_edit.png', __('Edit'));
+ $titles['NoEdit'] = self::getIcon('bd_edit.png', __('Edit'));
+ $titles['Export'] = self::getIcon('b_export.png', __('Export'));
+ $titles['NoExport'] = self::getIcon('bd_export.png', __('Export'));
+ $titles['Execute'] = self::getIcon('b_nextpage.png', __('Execute'));
+ $titles['NoExecute'] = self::getIcon('bd_nextpage.png', __('Execute'));
+
+ return $titles;
+ }
+
+ /**
+ * This function processes the datatypes supported by the DB,
+ * as specified in PMA_Types->getColumns() and either returns an array
+ * (useful for quickly checking if a datatype is supported)
+ * or an HTML snippet that creates a drop-down list.
+ *
+ * @param bool $html Whether to generate an html snippet or an array
+ * @param string $selected The value to mark as selected in HTML mode
+ *
+ * @return mixed An HTML snippet or an array of datatypes.
+ *
+ */
+ public static function getSupportedDatatypes($html = false, $selected = '')
+ {
+ if ($html) {
+
+ // NOTE: the SELECT tag in not included in this snippet.
+ $retval = '';
+
+ foreach ($GLOBALS['PMA_Types']->getColumns() as $key => $value) {
+ if (is_array($value)) {
+ $retval .= "<optgroup label='" . htmlspecialchars($key) . "'>";
+ foreach ($value as $subvalue) {
+ if ($subvalue == $selected) {
+ $retval .= sprintf(
+ '<option selected="selected" title="%s">%s</option>',
+ $GLOBALS['PMA_Types']->getTypeDescription($subvalue),
+ $subvalue
+ );
+ } else if ($subvalue === '-') {
+ $retval .= '<option disabled="disabled">';
+ $retval .= $subvalue;
+ $retval .= '</option>';
+ } else {
+ $retval .= sprintf(
+ '<option title="%s">%s</option>',
+ $GLOBALS['PMA_Types']->getTypeDescription($subvalue),
+ $subvalue
+ );
+ }
+ }
+ $retval .= '</optgroup>';
+ } else {
+ if ($selected == $value) {
+ $retval .= sprintf(
+ '<option selected="selected" title="%s">%s</option>',
+ $GLOBALS['PMA_Types']->getTypeDescription($value),
+ $value
+ );
+ } else {
+ $retval .= sprintf(
+ '<option title="%s">%s</option>',
+ $GLOBALS['PMA_Types']->getTypeDescription($value),
+ $value
+ );
+ }
+ }
+ }
+ } else {
+ $retval = array();
+ foreach ($GLOBALS['PMA_Types']->getColumns() as $value) {
+ if (is_array($value)) {
+ foreach ($value as $subvalue) {
+ if ($subvalue !== '-') {
+ $retval[] = $subvalue;
+ }
+ }
+ } else {
+ if ($value !== '-') {
+ $retval[] = $value;
+ }
+ }
+ }
+ }
+
+ return $retval;
+ } // end getSupportedDatatypes()
+
+ /**
+ * Returns a list of datatypes that are not (yet) handled by PMA.
+ * Used by: tbl_change.php and libraries/db_routines.inc.php
+ *
+ * @return array list of datatypes
+ */
+ public static function unsupportedDatatypes()
+ {
+ $no_support_types = array();
+ return $no_support_types;
+ }
+
+ /**
+ * Return GIS data types
+ *
+ * @param bool $upper_case whether to return values in upper case
+ *
+ * @return array GIS data types
+ */
+ public static function getGISDatatypes($upper_case = false)
+ {
+ $gis_data_types = array(
+ 'geometry',
+ 'point',
+ 'linestring',
+ 'polygon',
+ 'multipoint',
+ 'multilinestring',
+ 'multipolygon',
+ 'geometrycollection'
+ );
+ if ($upper_case) {
+ for ($i = 0; $i < count($gis_data_types); $i++) {
+ $gis_data_types[$i] = strtoupper($gis_data_types[$i]);
+ }
+ }
+ return $gis_data_types;
+ }
+
+ /**
+ * Generates GIS data based on the string passed.
+ *
+ * @param string $gis_string GIS string
+ *
+ * @return string GIS data enclosed in 'GeomFromText' function
+ */
+ public static function createGISData($gis_string)
+ {
+ $gis_string = trim($gis_string);
+ $geom_types = '(POINT|MULTIPOINT|LINESTRING|MULTILINESTRING|'
+ . 'POLYGON|MULTIPOLYGON|GEOMETRYCOLLECTION)';
+ if (preg_match("/^'" . $geom_types . "\(.*\)',[0-9]*$/i", $gis_string)) {
+ return 'GeomFromText(' . $gis_string . ')';
+ } elseif (preg_match("/^" . $geom_types . "\(.*\)$/i", $gis_string)) {
+ return "GeomFromText('" . $gis_string . "')";
+ } else {
+ return $gis_string;
+ }
+ }
+
+ /**
+ * Returns the names and details of the functions
+ * that can be applied on geometry data typess.
+ *
+ * @param string $geom_type if provided the output is limited to the functions
+ * that are applicable to the provided geometry type.
+ * @param bool $binary if set to false functions that take two geometries
+ * as arguments will not be included.
+ * @param bool $display if set to true separators will be added to the
+ * output array.
+ *
+ * @return array names and details of the functions that can be applied on
+ * geometry data typess.
+ */
+ public static function getGISFunctions(
+ $geom_type = null, $binary = true, $display = false
+ ) {
+ $funcs = array();
+ if ($display) {
+ $funcs[] = array('display' => ' ');
+ }
+
+ // Unary functions common to all geomety types
+ $funcs['Dimension'] = array('params' => 1, 'type' => 'int');
+ $funcs['Envelope'] = array('params' => 1, 'type' => 'Polygon');
+ $funcs['GeometryType'] = array('params' => 1, 'type' => 'text');
+ $funcs['SRID'] = array('params' => 1, 'type' => 'int');
+ $funcs['IsEmpty'] = array('params' => 1, 'type' => 'int');
+ $funcs['IsSimple'] = array('params' => 1, 'type' => 'int');
+
+ $geom_type = trim(strtolower($geom_type));
+ if ($display && $geom_type != 'geometry' && $geom_type != 'multipoint') {
+ $funcs[] = array('display' => '--------');
+ }
+
+ // Unary functions that are specific to each geomety type
+ if ($geom_type == 'point') {
+ $funcs['X'] = array('params' => 1, 'type' => 'float');
+ $funcs['Y'] = array('params' => 1, 'type' => 'float');
+
+ } elseif ($geom_type == 'multipoint') {
+ // no fucntions here
+ } elseif ($geom_type == 'linestring') {
+ $funcs['EndPoint'] = array('params' => 1, 'type' => 'point');
+ $funcs['GLength'] = array('params' => 1, 'type' => 'float');
+ $funcs['NumPoints'] = array('params' => 1, 'type' => 'int');
+ $funcs['StartPoint'] = array('params' => 1, 'type' => 'point');
+ $funcs['IsRing'] = array('params' => 1, 'type' => 'int');
+
+ } elseif ($geom_type == 'multilinestring') {
+ $funcs['GLength'] = array('params' => 1, 'type' => 'float');
+ $funcs['IsClosed'] = array('params' => 1, 'type' => 'int');
+
+ } elseif ($geom_type == 'polygon') {
+ $funcs['Area'] = array('params' => 1, 'type' => 'float');
+ $funcs['ExteriorRing'] = array('params' => 1, 'type' => 'linestring');
+ $funcs['NumInteriorRings'] = array('params' => 1, 'type' => 'int');
+
+ } elseif ($geom_type == 'multipolygon') {
+ $funcs['Area'] = array('params' => 1, 'type' => 'float');
+ $funcs['Centroid'] = array('params' => 1, 'type' => 'point');
+ // Not yet implemented in MySQL
+ //$funcs['PointOnSurface'] = array('params' => 1, 'type' => 'point');
+
+ } elseif ($geom_type == 'geometrycollection') {
+ $funcs['NumGeometries'] = array('params' => 1, 'type' => 'int');
+ }
+
+ // If we are asked for binary functions as well
+ if ($binary) {
+ // section separator
+ if ($display) {
+ $funcs[] = array('display' => '--------');
+ }
+
+ if (PMA_MYSQL_INT_VERSION < 50601) {
+ $funcs['Crosses'] = array('params' => 2, 'type' => 'int');
+ $funcs['Contains'] = array('params' => 2, 'type' => 'int');
+ $funcs['Disjoint'] = array('params' => 2, 'type' => 'int');
+ $funcs['Equals'] = array('params' => 2, 'type' => 'int');
+ $funcs['Intersects'] = array('params' => 2, 'type' => 'int');
+ $funcs['Overlaps'] = array('params' => 2, 'type' => 'int');
+ $funcs['Touches'] = array('params' => 2, 'type' => 'int');
+ $funcs['Within'] = array('params' => 2, 'type' => 'int');
+ } else {
+ // If MySQl version is greaeter than or equal 5.6.1,
+ // use the ST_ prefix.
+ $funcs['ST_Crosses'] = array('params' => 2, 'type' => 'int');
+ $funcs['ST_Contains'] = array('params' => 2, 'type' => 'int');
+ $funcs['ST_Disjoint'] = array('params' => 2, 'type' => 'int');
+ $funcs['ST_Equals'] = array('params' => 2, 'type' => 'int');
+ $funcs['ST_Intersects'] = array('params' => 2, 'type' => 'int');
+ $funcs['ST_Overlaps'] = array('params' => 2, 'type' => 'int');
+ $funcs['ST_Touches'] = array('params' => 2, 'type' => 'int');
+ $funcs['ST_Within'] = array('params' => 2, 'type' => 'int');
+ }
+
+ if ($display) {
+ $funcs[] = array('display' => '--------');
+ }
+ // Minimum bounding rectangle functions
+ $funcs['MBRContains'] = array('params' => 2, 'type' => 'int');
+ $funcs['MBRDisjoint'] = array('params' => 2, 'type' => 'int');
+ $funcs['MBREquals'] = array('params' => 2, 'type' => 'int');
+ $funcs['MBRIntersects'] = array('params' => 2, 'type' => 'int');
+ $funcs['MBROverlaps'] = array('params' => 2, 'type' => 'int');
+ $funcs['MBRTouches'] = array('params' => 2, 'type' => 'int');
+ $funcs['MBRWithin'] = array('params' => 2, 'type' => 'int');
+ }
+ return $funcs;
+ }
+
+ /**
+ * Returns default function for a particular column.
+ *
+ * @param array $field Data about the column for which
+ * to generate the dropdown
+ * @param bool $insert_mode Whether the operation is 'insert'
+ *
+ * @global array $cfg PMA configuration
+ * @global array $analyzed_sql Analyzed SQL query
+ * @global mixed $data data of currently edited row
+ * (used to detect whether to choose defaults)
+ *
+ * @return string An HTML snippet of a dropdown list with function
+ * names appropriate for the requested column.
+ */
+ public static function getDefaultFunctionForField($field, $insert_mode)
+ {
+ /*
+ * @todo Except for $cfg, no longer use globals but pass as parameters
+ * from higher levels
+ */
+ global $cfg, $analyzed_sql, $data;
+
+ $default_function = '';
+
+ // Can we get field class based values?
+ $current_class = $GLOBALS['PMA_Types']->getTypeClass($field['True_Type']);
+ if (! empty($current_class)) {
+ if (isset($cfg['DefaultFunctions']['FUNC_' . $current_class])) {
+ $default_function
+ = $cfg['DefaultFunctions']['FUNC_' . $current_class];
+ }
+ }
+
+ $analyzed_sql_field_array = $analyzed_sql[0]['create_table_fields']
+ [$field['Field']];
+ // what function defined as default?
+ // for the first timestamp we don't set the default function
+ // if there is a default value for the timestamp
+ // (not including CURRENT_TIMESTAMP)
+ // and the column does not have the
+ // ON UPDATE DEFAULT TIMESTAMP attribute.
+ if (($field['True_Type'] == 'timestamp')
+ && $field['first_timestamp']
+ && empty($field['Default'])
+ && empty($data)
+ && ! isset($analyzed_sql_field_array['on_update_current_timestamp'])
+ && ($analyzed_sql_field_array['default_value'] != 'NULL')
+ ) {
+ $default_function = $cfg['DefaultFunctions']['first_timestamp'];
+ }
+
+ // For primary keys of type char(36) or varchar(36) UUID if the default
+ // function
+ // Only applies to insert mode, as it would silently trash data on updates.
+ if ($insert_mode
+ && $field['Key'] == 'PRI'
+ && ($field['Type'] == 'char(36)' || $field['Type'] == 'varchar(36)')
+ ) {
+ $default_function = $cfg['DefaultFunctions']['FUNC_UUID'];
+ }
+
+ // this is set only when appropriate and is always true
+ if (isset($field['display_binary_as_hex'])) {
+ $default_function = 'UNHEX';
+ }
+
+ return $default_function;
+ }
+
+ /**
+ * Creates a dropdown box with MySQL functions for a particular column.
+ *
+ * @param array $field Data about the column for which
+ * to generate the dropdown
+ * @param bool $insert_mode Whether the operation is 'insert'
+ *
+ * @return string An HTML snippet of a dropdown list with function
+ * names appropriate for the requested column.
+ */
+ public static function getFunctionsForField($field, $insert_mode)
+ {
+ $default_function = self::getDefaultFunctionForField($field, $insert_mode);
+ $dropdown_built = array();
+
+ // Create the output
+ $retval = '<option></option>' . "\n";
+ // loop on the dropdown array and print all available options for that
+ // field.
+ $functions = $GLOBALS['PMA_Types']->getFunctions($field['True_Type']);
+ foreach ($functions as $function) {
+ $retval .= '<option';
+ if ($default_function === $function) {
+ $retval .= ' selected="selected"';
+ }
+ $retval .= '>' . $function . '</option>' . "\n";
+ $dropdown_built[$function] = true;
+ }
+
+ // Create separator before all functions list
+ if (count($functions) > 0) {
+ $retval .= '<option value="" disabled="disabled">--------</option>'
+ . "\n";
+ }
+
+ // For compatibility's sake, do not let out all other functions. Instead
+ // print a separator (blank) and then show ALL functions which weren't
+ // shown yet.
+ $functions = $GLOBALS['PMA_Types']->getAllFunctions();
+ foreach ($functions as $function) {
+ // Skip already included functions
+ if (isset($dropdown_built[$function])) {
+ continue;
+ }
+ $retval .= '<option';
+ if ($default_function === $function) {
+ $retval .= ' selected="selected"';
+ }
+ $retval .= '>' . $function . '</option>' . "\n";
+ } // end for
+
+ return $retval;
+ } // end getFunctionsForField()
+
+ /**
+ * Checks if the current user has a specific privilege and returns true if the
+ * user indeed has that privilege or false if (s)he doesn't. This function must
+ * only be used for features that are available since MySQL 5, because it
+ * relies on the INFORMATION_SCHEMA database to be present.
+ *
+ * Example: currentUserHasPrivilege('CREATE ROUTINE', 'mydb');
+ * // Checks if the currently logged in user has the global
+ * // 'CREATE ROUTINE' privilege or, if not, checks if the
+ * // user has this privilege on database 'mydb'.
+ *
+ * @param string $priv The privilege to check
+ * @param mixed $db null, to only check global privileges
+ * string, db name where to also check for privileges
+ * @param mixed $tbl null, to only check global/db privileges
+ * string, table name where to also check for privileges
+ *
+ * @return bool
+ */
+ public static function currentUserHasPrivilege($priv, $db = null, $tbl = null)
+ {
+ // Get the username for the current user in the format
+ // required to use in the information schema database.
+ $user = $GLOBALS['dbi']->fetchValue("SELECT CURRENT_USER();");
+ if ($user === false) {
+ return false;
+ }
+
+ $user = explode('@', $user);
+ $username = "''";
+ $username .= str_replace("'", "''", $user[0]);
+ $username .= "''@''";
+ $username .= str_replace("'", "''", $user[1]);
+ $username .= "''";
+
+ // Prepare the query
+ $query = "SELECT `PRIVILEGE_TYPE` FROM `INFORMATION_SCHEMA`.`%s` "
+ . "WHERE GRANTEE='%s' AND PRIVILEGE_TYPE='%s'";
+
+ // Check global privileges first.
+ $user_privileges = $GLOBALS['dbi']->fetchValue(
+ sprintf(
+ $query,
+ 'USER_PRIVILEGES',
+ $username,
+ $priv
+ )
+ );
+ if ($user_privileges) {
+ return true;
+ }
+ // If a database name was provided and user does not have the
+ // required global privilege, try database-wise permissions.
+ if ($db !== null) {
+ // need to escape wildcards in db and table names, see bug #3566
+ // (wildcard characters appear as being quoted with a backslash
+ // when querying TABLE_SCHEMA.SCHEMA_PRIVILEGES)
+ $db = str_replace(array('%', '_'), array('\%', '\_'), $db);
+ /*
+ * This is to take into account a wildcard db privilege
+ * so we replace % by .* and _ by . to be able to compare
+ * with REGEXP.
+ *
+ * Also, we need to double the inner % to please sprintf().
+ */
+ $query .= " AND '%s' REGEXP"
+ . " REPLACE(REPLACE(TABLE_SCHEMA, '_', '.'), '%%', '.*')";
+ $schema_privileges = $GLOBALS['dbi']->fetchValue(
+ sprintf(
+ $query,
+ 'SCHEMA_PRIVILEGES',
+ $username,
+ $priv,
+ self::sqlAddSlashes($db)
+ )
+ );
+ if ($schema_privileges) {
+ return true;
+ }
+ } else {
+ // There was no database name provided and the user
+ // does not have the correct global privilege.
+ return false;
+ }
+ // If a table name was also provided and we still didn't
+ // find any valid privileges, try table-wise privileges.
+ if ($tbl !== null) {
+ // need to escape wildcards in db and table names, see bug #3518484
+ $tbl = str_replace(array('%', '_'), array('\%', '\_'), $tbl);
+ $query .= " AND TABLE_NAME='%s'";
+ $table_privileges = $GLOBALS['dbi']->fetchValue(
+ sprintf(
+ $query,
+ 'TABLE_PRIVILEGES',
+ $username,
+ $priv,
+ self::sqlAddSlashes($db),
+ self::sqlAddSlashes($tbl)
+ )
+ );
+ if ($table_privileges) {
+ return true;
+ }
+ }
+ // If we reached this point, the user does not
+ // have even valid table-wise privileges.
+ return false;
+ }
+
+ /**
+ * Returns server type for current connection
+ *
+ * Known types are: Drizzle, MariaDB and MySQL (default)
+ *
+ * @return string
+ */
+ public static function getServerType()
+ {
+ $server_type = 'MySQL';
+ if (PMA_DRIZZLE) {
+ $server_type = 'Drizzle';
+ } else if (stripos(PMA_MYSQL_STR_VERSION, 'mariadb') !== false) {
+ $server_type = 'MariaDB';
+ } else if (stripos(PMA_MYSQL_VERSION_COMMENT, 'percona') !== false) {
+ $server_type = 'Percona Server';
+ }
+ return $server_type;
+ }
+
+ /**
+ * Analyzes the limit clause and return the start and length attributes of it.
+ *
+ * @param string $limit_clause limit clause
+ *
+ * @return array|void Start and length attributes of the limit clause
+ */
+ public static function analyzeLimitClause($limit_clause)
+ {
+ $start_and_length = explode(',', str_ireplace('LIMIT', '', $limit_clause));
+ $size = count($start_and_length);
+ if ($size == 1) {
+ return array(
+ 'start' => '0',
+ 'length' => trim($start_and_length[0])
+ );
+ } elseif ($size == 2) {
+ return array(
+ 'start' => trim($start_and_length[0]),
+ 'length' => trim($start_and_length[1])
+ );
+ }
+ }
+
+ /**
+ * Prepare HTML code for display button.
+ *
+ * @return String
+ */
+ public static function getButton()
+ {
+ return '<p class="print_ignore">'
+ . '<input type="button" class="button" id="print" value="'
+ . __('Print') . '" />'
+ . '</p>';
+ }
+
+ /**
+ * Parses ENUM/SET values
+ *
+ * @param string $definition The definition of the column
+ * for which to parse the values
+ * @param bool $escapeHtml Whether to escape html entitites
+ *
+ * @return array
+ */
+ public static function parseEnumSetValues($definition, $escapeHtml = true)
+ {
+ $values_string = htmlentities($definition, ENT_COMPAT, "UTF-8");
+ // There is a JS port of the below parser in functions.js
+ // If you are fixing something here,
+ // you need to also update the JS port.
+ $values = array();
+ $in_string = false;
+ $buffer = '';
+
+ for ($i=0; $i<strlen($values_string); $i++) {
+
+ $curr = $values_string[$i];
+ $next = ($i == strlen($values_string)-1) ? '' : $values_string[$i+1];
+
+ if (! $in_string && $curr == "'") {
+ $in_string = true;
+ } else if (($in_string && $curr == "\\") && $next == "\\") {
+ $buffer .= "&#92;";
+ $i++;
+ } else if (($in_string && $next == "'")
+ && ($curr == "'" || $curr == "\\")
+ ) {
+ $buffer .= "&#39;";
+ $i++;
+ } else if ($in_string && $curr == "'") {
+ $in_string = false;
+ $values[] = $buffer;
+ $buffer = '';
+ } else if ($in_string) {
+ $buffer .= $curr;
+ }
+
+ }
+
+ if (strlen($buffer) > 0) {
+ // The leftovers in the buffer are the last value (if any)
+ $values[] = $buffer;
+ }
+
+ if (! $escapeHtml) {
+ foreach ($values as $key => $value) {
+ $values[$key] = html_entity_decode($value, ENT_QUOTES, 'UTF-8');
+ }
+ }
+
+ return $values;
+ }
+
+ /**
+ * fills given tooltip arrays
+ *
+ * @param array &$tooltip_truename tooltip data
+ * @param array &$tooltip_aliasname tooltip data
+ * @param array $table tabledata
+ *
+ * @return void
+ */
+ public static function fillTooltip(
+ &$tooltip_truename, &$tooltip_aliasname, $table
+ ) {
+ if (strstr($table['Comment'], '; InnoDB free') === false) {
+ if (!strstr($table['Comment'], 'InnoDB free') === false) {
+ // here we have just InnoDB generated part
+ $table['Comment'] = '';
+ }
+ } else {
+ // remove InnoDB comment from end, just the minimal part
+ // (*? is non greedy)
+ $table['Comment'] = preg_replace(
+ '@; InnoDB free:.*?$@', '', $table['Comment']
+ );
+ }
+ // views have VIEW as comment so it's not a real comment put by a user
+ if ('VIEW' == $table['Comment']) {
+ $table['Comment'] = '';
+ }
+ if (empty($table['Comment'])) {
+ $table['Comment'] = $table['Name'];
+ } else {
+ // todo: why?
+ $table['Comment'] .= ' ';
+ }
+
+ $tooltip_truename[$table['Name']] = $table['Name'];
+ $tooltip_aliasname[$table['Name']] = $table['Comment'];
+
+ if (isset($table['Create_time']) && !empty($table['Create_time'])) {
+ $tooltip_aliasname[$table['Name']] .= ', ' . __('Creation')
+ . ': '
+ . PMA_Util::localisedDate(strtotime($table['Create_time']));
+ }
+
+ if (! empty($table['Update_time'])) {
+ $tooltip_aliasname[$table['Name']] .= ', ' . __('Last update')
+ . ': '
+ . PMA_Util::localisedDate(strtotime($table['Update_time']));
+ }
+
+ if (! empty($table['Check_time'])) {
+ $tooltip_aliasname[$table['Name']] .= ', ' . __('Last check')
+ . ': '
+ . PMA_Util::localisedDate(strtotime($table['Check_time']));
+ }
+ }
+
+ /**
+ * Get regular expression which occur first inside the given sql query.
+ *
+ * @param Array $regex_array Comparing regular expressions.
+ * @param String $query SQL query to be checked.
+ *
+ * @return String Matching regular expression.
+ */
+ public static function getFirstOccurringRegularExpression($regex_array, $query)
+ {
+ $minimum_first_occurence_index = null;
+ $regex = null;
+
+ foreach ($regex_array as $test_regex) {
+ if (preg_match($test_regex, $query, $matches, PREG_OFFSET_CAPTURE)) {
+ if (is_null($minimum_first_occurence_index)
+ || ($matches[0][1] < $minimum_first_occurence_index)
+ ) {
+ $regex = $test_regex;
+ $minimum_first_occurence_index = $matches[0][1];
+ }
+ }
+ }
+ return $regex;
+ }
+
+ /**
+ * Return the list of tabs for the menu with corresponding names
+ *
+ * @param string $level 'server', 'db' or 'table' level
+ *
+ * @return array list of tabs for the menu
+ */
+ public static function getMenuTabList($level = null)
+ {
+ $tabList = array(
+ 'server' => array(
+ 'databases' => __('Databases'),
+ 'sql' => __('SQL'),
+ 'status' => __('Status'),
+ 'rights' => __('Users'),
+ 'export' => __('Export'),
+ 'import' => __('Import'),
+ 'settings' => __('Settings'),
+ 'binlog' => __('Binary log'),
+ 'replication' => __('Replication'),
+ 'vars' => __('Variables'),
+ 'charset' => __('Charsets'),
+ 'plugins' => __('Plugins'),
+ 'engine' => __('Engines')
+ ),
+ 'db' => array(
+ 'structure' => __('Structure'),
+ 'sql' => __('SQL'),
+ 'search' => __('Search'),
+ 'qbe' => __('Query'),
+ 'export' => __('Export'),
+ 'import' => __('Import'),
+ 'operation' => __('Operations'),
+ 'privileges' => __('Privileges'),
+ 'routines' => __('Routines'),
+ 'events' => __('Events'),
+ 'triggers' => __('Triggers'),
+ 'tracking' => __('Tracking'),
+ 'designer' => __('Designer')
+ ),
+ 'table' => array(
+ 'browse' => __('Browse'),
+ 'structure' => __('Structure'),
+ 'sql' => __('SQL'),
+ 'search' => __('Search'),
+ 'insert' => __('Insert'),
+ 'export' => __('Export'),
+ 'import' => __('Import'),
+ 'privileges' => __('Privileges'),
+ 'operation' => __('Operations'),
+ 'tracking' => __('Tracking'),
+ 'triggers' => __('Triggers'),
+ )
+ );
+
+ if ($level == null) {
+ return $tabList;
+ } else if (array_key_exists($level, $tabList)) {
+ return $tabList[$level];
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Returns information with latest version from phpmyadmin.net
+ *
+ * @return String JSON decoded object with the data
+ */
+ public static function getLatestVersion()
+ {
+ global $cfg;
+
+ // wait 3s at most for server response, it's enough to get information
+ // from a working server
+ $connection_timeout = 3;
+
+ $response = '{}';
+ // Get response text from phpmyadmin.net or from the session
+ // Update cache every 6 hours
+ if (isset($_SESSION['cache']['version_check'])
+ && time() < $_SESSION['cache']['version_check']['timestamp'] + 3600 * 6
+ ) {
+ $save = false;
+ $response = $_SESSION['cache']['version_check']['response'];
+ } else {
+ $save = true;
+ $file = 'http://www.phpmyadmin.net/home_page/version.json';
+ if (ini_get('allow_url_fopen')) {
+ $context = array(
+ 'http' => array(
+ 'request_fulluri' => true,
+ 'timeout' => $connection_timeout,
+ )
+ );
+ if (strlen($cfg['ProxyUrl'])) {
+ $context['http']['proxy'] = $cfg['ProxyUrl'];
+ if (strlen($cfg['ProxyUser'])) {
+ $auth = base64_encode(
+ $cfg['ProxyUser'] . ':'
+ . $cfg['ProxyPass']
+ );
+ $context['http']['header']
+ = 'Proxy-Authorization: Basic ' . $auth;
+ }
+ }
+ if (! defined('TESTSUITE')) {
+ session_write_close();
+ }
+ $response = file_get_contents(
+ $file,
+ false,
+ stream_context_create($context)
+ );
+ } else if (function_exists('curl_init')) {
+ $curl_handle = curl_init($file);
+ if (strlen($cfg['ProxyUrl'])) {
+ curl_setopt(
+ $curl_handle,
+ CURLOPT_PROXY,
+ $cfg['ProxyUrl']
+ );
+ if (strlen($cfg['ProxyUser'])) {
+ curl_setopt(
+ $curl_handle,
+ CURLOPT_PROXYUSERPWD,
+ $cfg['ProxyUser']
+ . ':' . $cfg['ProxyPass']
+ );
+ }
+ }
+ curl_setopt(
+ $curl_handle,
+ CURLOPT_HEADER,
+ false
+ );
+ curl_setopt(
+ $curl_handle,
+ CURLOPT_RETURNTRANSFER,
+ true
+ );
+ curl_setopt(
+ $curl_handle,
+ CURLOPT_TIMEOUT,
+ $connection_timeout
+ );
+ if (! defined('TESTSUITE')) {
+ session_write_close();
+ }
+ $response = curl_exec($curl_handle);
+ }
+ }
+
+ $data = json_decode($response);
+ if (is_object($data)
+ && strlen($data->version)
+ && strlen($data->date)
+ && $save
+ ) {
+ if (! isset($_SESSION) && ! defined('TESTSUITE')) {
+ ini_set('session.use_only_cookies', false);
+ ini_set('session.use_cookies', false);
+ ini_set('session.use_trans_sid', false);
+ ini_set('session.cache_limiter', null);
+ session_start();
+ }
+ $_SESSION['cache']['version_check'] = array(
+ 'response' => $response,
+ 'timestamp' => time()
+ );
+ }
+ return $data;
+ }
+
+ /**
+ * Calculates numerical equivalent of phpMyAdmin version string
+ *
+ * @param string $version version
+ *
+ * @return mixed false on failure, integer on success
+ */
+ public static function versionToInt($version)
+ {
+ $parts = explode('-', $version);
+ if (count($parts) > 1) {
+ $suffix = $parts[1];
+ } else {
+ $suffix = '';
+ }
+ $parts = explode('.', $parts[0]);
+
+ $result = 0;
+
+ if (count($parts) >= 1 && is_numeric($parts[0])) {
+ $result += 1000000 * $parts[0];
+ }
+
+ if (count($parts) >= 2 && is_numeric($parts[1])) {
+ $result += 10000 * $parts[1];
+ }
+
+ if (count($parts) >= 3 && is_numeric($parts[2])) {
+ $result += 100 * $parts[2];
+ }
+
+ if (count($parts) >= 4 && is_numeric($parts[3])) {
+ $result += 1 * $parts[3];
+ }
+
+ if (!empty($suffix)) {
+ $matches = array();
+ if (preg_match('/^(\D+)(\d+)$/', $suffix, $matches)) {
+ $suffix = $matches[1];
+ $result += intval($matches[2]);
+ }
+ switch ($suffix) {
+ case 'pl':
+ $result += 60;
+ break;
+ case 'rc':
+ $result += 30;
+ break;
+ case 'beta':
+ $result += 20;
+ break;
+ case 'alpha':
+ $result += 10;
+ break;
+ case 'dev':
+ $result += 0;
+ break;
+ }
+ } else {
+ $result += 50; // for final
+ }
+
+ return $result;
+ }
+
+ /**
+ * Add fractional seconds to time, datetime and timestamp strings.
+ * If the string contsins fractional seconds,
+ * pads it with 0s upto 6 decimal places.
+ *
+ * @param string $value time, datetime or timestamp strings
+ *
+ * @return string time, datetime or timestamp strings with fractional seconds
+ */
+ public static function addMicroseconds($value)
+ {
+ if (empty($value) || $value == 'CURRENT_TIMESTAMP') {
+ return $value;
+ } elseif (strpos($value, '.')) {
+ $value .= '000000';
+ return substr($value, 0, strpos($value, '.') + 7);
+ } else {
+ return $value . '.000000';
+ }
+ }
+}
+
+?> \ No newline at end of file
diff --git a/libraries/advisory_rules.txt b/libraries/advisory_rules.txt
new file mode 100644
index 0000000000..ad0ca775b9
--- /dev/null
+++ b/libraries/advisory_rules.txt
@@ -0,0 +1,470 @@
+# phpMyAdmin Advisory rules file
+#
+# Use only UNIX style newlines
+#
+# This file is being parsed by Advisor.class.php, which should handle syntax
+# errors correctly. However, PHP Warnings and the like are being consumed by
+# the phpMyAdmin error handler, so those won't show up E.g.: Justification line
+# is empty because you used an unescape percent sign, sprintf() returns an
+# empty string and no warning/error is shown
+#
+# Rule Syntax:
+# 'rule' identifier[the name of the rule] eexpr [an optional precondition]
+# expr [variable or value calculation used for the test]
+# expr [test, if evaluted to 'true' it fires the rule. Use 'value' to insert the calculated value (without quotes)]
+# string [the issue (what is the problem?)]
+# string [the recommendation (how do i fix it?)]
+# formatted-string '|' comma-seperated-expr [the justification (result of the calculated value / why did this rule fire?)]
+
+# comma-seperated-expr: expr(,expr)*
+# eexpr: [expr] - expr enclosed in []
+# expr: a php code literal with extras:
+# - variable names are replaced with their respective values
+# - fired('name of rule') is replaced with true/false when given rule has
+# been fired. Note however that this is a very simple rules engine.
+# Rules are only checked in sequential order as they are written down
+# here. If given rule has not been checked yet, fired() will always
+# evaluate to false
+# - 'value' is replaced with the calculated value. If it is a string, it
+# will be put within single quotes
+# - other than that you may use any php function, initialized variable or
+# constant
+#
+# identifier: A string enclosed in single quotes
+# string: A quoteless string, may contain HTML. Variable names enclosed in
+# curly braces are replaced with links to directly edit this variable.
+# e.g. {tmp_table_size}
+# formatted-string: You may use classic php sprintf() string formatting here,
+# the arguments must be appended after a trailing pipe (|) as
+# mentioned in above syntax percent signs (%) are
+# automatically escaped (%%) in the following cases: When
+# followed by a space, dot or comma and at the end of the
+# line)
+#
+# Comments start with #
+#
+
+# Queries
+
+rule 'Uptime below one day'
+ Uptime
+ value < 86400
+ Uptime is less than 1 day, performance tuning may not be accurate.
+ To have more accurate averages it is recommended to let the server run for longer than a day before running this analyzer
+ The uptime is only %s | ADVISOR_timespanFormat(Uptime)
+
+rule 'Questions below 1,000'
+ Questions
+ value < 1000
+ Fewer than 1,000 questions have been run against this server. The recommendations may not be accurate.
+ Let the server run for a longer time until it has executed a greater amount of queries.
+ Current amount of Questions: %s | Questions
+
+rule 'Percentage of slow queries' [Questions > 0 && !PMA_DRIZZLE]
+ Slow_queries / Questions * 100
+ value >= 5
+ There is a lot of slow queries compared to the overall amount of Queries.
+ You might want to increase {long_query_time} or optimize the queries listed in the slow query log
+ The slow query rate should be below 5%, your value is %s%. | round(value,2)
+
+rule 'Slow query rate' [Questions > 0]
+ (Slow_queries / Questions * 100) / Uptime
+ value * 60 * 60 > 1
+ There is a high percentage of slow queries compared to the server uptime.
+ You might want to increase {long_query_time} or optimize the queries listed in the slow query log
+ You have a slow query rate of %s per hour, you should have less than 1% per hour. | ADVISOR_bytime(value,2)
+
+rule 'Long query time' [!PMA_DRIZZLE]
+ long_query_time
+ value >= 10
+ {long_query_time} is set to 10 seconds or more, thus only slow queries that take above 10 seconds are logged.
+ It is suggested to set {long_query_time} to a lower value, depending on your environment. Usually a value of 1-5 seconds is suggested.
+ long_query_time is currently set to %ds. | value
+
+rule 'Slow query logging' [!PMA_DRIZZLE && PMA_MYSQL_INT_VERSION < 50600]
+ log_slow_queries
+ value == 'OFF'
+ The slow query log is disabled.
+ Enable slow query logging by setting {log_slow_queries} to 'ON'. This will help troubleshooting badly performing queries.
+ log_slow_queries is set to 'OFF'
+
+rule 'Slow query logging' [!PMA_DRIZZLE && PMA_MYSQL_INT_VERSION >= 50600]
+ slow_query_log
+ value == 'OFF'
+ The slow query log is disabled.
+ Enable slow query logging by setting {slow_query_log} to 'ON'. This will help troubleshooting badly performing queries.
+ slow_query_log is set to 'OFF'
+
+#
+# versions
+rule 'Release Series' [!PMA_DRIZZLE]
+ version
+ substr(value,0,2) <= '5.' && substr(value,2,1) < 1
+ The MySQL server version less than 5.1.
+ You should upgrade, as MySQL 5.1 has improved performance, and MySQL 5.5 even more so.
+ Current version: %s | value
+
+rule 'Minor Version' [! fired('Release Series')]
+ version
+ substr(value,0,2) <= '5.' && substr(value,2,1) <= 1 && substr(value,4,2) < 30
+ Version less than 5.1.30 (the first GA release of 5.1).
+ You should upgrade, as recent versions of MySQL 5.1 have improved performance and MySQL 5.5 even more so.
+ Current version: %s | value
+
+rule 'Minor Version' [! fired('Release Series')]
+ version
+ substr(value,0,1) == 5 && substr(value,2,1) == 5 && substr(value,4,2) < 8
+ Version less than 5.5.8 (the first GA release of 5.5).
+ You should upgrade, to a stable version of MySQL 5.5
+ Current version: %s | value
+
+rule 'Distribution'
+ version_comment
+ preg_match('/source/i',value)
+ Version is compiled from source, not a MySQL official binary.
+ If you did not compile from source, you may be using a package modified by a distribution. The MySQL manual only is accurate for official MySQL binaries, not any package distributions (such as RedHat, Debian/Ubuntu etc).
+ 'source' found in version_comment
+
+rule 'Distribution'
+ version_comment
+ preg_match('/percona/i',value)
+ The MySQL manual only is accurate for official MySQL binaries.
+ Percona documentation is at http://www.percona.com/docs/wiki/
+ 'percona' found in version_comment
+
+rule 'Distribution'
+ version
+ PMA_DRIZZLE
+ The MySQL manual only is accurate for official MySQL binaries.
+ Drizzle documentation is at http://docs.drizzle.org/
+ Version string (%s) matches Drizzle versioning scheme | value
+
+rule 'MySQL Architecture'
+ system_memory
+ value > 3072*1024 && !preg_match('/64/',version_compile_machine) && !preg_match('/64/',version_compile_os)
+ MySQL is not compiled as a 64-bit package.
+ Your memory capacity is above 3 GiB (assuming the Server is on localhost), so MySQL might not be able to access all of your memory. You might want to consider installing the 64-bit version of MySQL.
+ Available memory on this host: %s | implode(' ',ADVISOR_formatByteDown(value*1024, 2, 2))
+
+#
+# Query cache
+
+# Lame: 'ON' == 0 is true, so you need to compare 'ON' == '0'
+rule 'Query cache disabled' [!PMA_DRIZZLE]
+ query_cache_size
+ value == 0 || query_cache_type == 'OFF' || query_cache_type == '0'
+ The query cache is not enabled.
+ The query cache is known to greatly improve performance if configured correctly. Enable it by setting {query_cache_size} to a 2 digit MiB value and setting {query_cache_type} to 'ON'. <b>Note:</b> If you are using memcached, ignore this recommendation.
+ query_cache_size is set to 0 or query_cache_type is set to 'OFF'
+
+rule 'Query caching method' [!fired('Query cache disabled')]
+ Questions / Uptime
+ value > 100
+ Suboptimal caching method.
+ You are using the MySQL Query cache with a fairly high traffic database. It might be worth considering to use <a href="http://dev.mysql.com/doc/refman/5.5/en/ha-memcached.html">memcached</a> instead of the MySQL Query cache, especially if you have multiple slaves.
+ The query cache is enabled and the server receives %d queries per second. This rule fires if there is more than 100 queries per second. | round(value,1)
+
+rule 'Query cache efficiency (%)' [!PMA_DRIZZLE && Com_select + Qcache_hits > 0 && !fired('Query cache disabled')]
+ Qcache_hits / (Com_select + Qcache_hits) * 100
+ value < 20
+ Query cache not running efficiently, it has a low hit rate.
+ Consider increasing {query_cache_limit}.
+ The current query cache hit rate of %s% is below 20% | round(value,1)
+
+rule 'Query Cache usage' [!fired('Query cache disabled') && !PMA_DRIZZLE]
+ 100 - Qcache_free_memory / query_cache_size * 100
+ value < 80
+ Less than 80% of the query cache is being utilized.
+ This might be caused by {query_cache_limit} being too low. Flushing the query cache might help as well.
+ The current ratio of free query cache memory to total query cache size is %s%. It should be above 80% | round(value,1)
+
+rule 'Query cache fragmentation' [!fired('Query cache disabled') && !PMA_DRIZZLE]
+ Qcache_free_blocks / (Qcache_total_blocks / 2) * 100
+ value > 20
+ The query cache is considerably fragmented.
+ Severe fragmentation is likely to (further) increase Qcache_lowmem_prunes. This might be caused by many Query cache low memory prunes due to {query_cache_size} being too small. For a immediate but short lived fix you can flush the query cache (might lock the query cache for a long time). Carefully adjusting {query_cache_min_res_unit} to a lower value might help too, e.g. you can set it to the average size of your queries in the cache using this formula: (query_cache_size - qcache_free_memory) / qcache_queries_in_cache
+ The cache is currently fragmented by %s% , with 100% fragmentation meaning that the query cache is an alternating pattern of free and used blocks. This value should be below 20%. | round(value,1)
+
+rule 'Query cache low memory prunes' [!PMA_DRIZZLE && Qcache_inserts > 0 && !fired('Query cache disabled')]
+ Qcache_lowmem_prunes / Qcache_inserts * 100
+ value > 0.1
+ Cached queries are removed due to low query cache memory from the query cache.
+ You might want to increase {query_cache_size}, however keep in mind that the overhead of maintaining the cache is likely to increase with its size, so do this in small increments and monitor the results.
+ The ratio of removed queries to inserted queries is %s%. The lower this value is, the better (This rules firing limit: 0.1%) | round(value,1)
+
+rule 'Query cache max size' [!fired('Query cache disabled')]
+ query_cache_size
+ value > 1024 * 1024 * 128
+ The query cache size is above 128 MiB. Big query caches may cause significant overhead that is required to maintain the cache.
+ Depending on your environment, it might be performance increasing to reduce this value.
+ Current query cache size: %s | implode(' ',ADVISOR_formatByteDown(value, 2, 2))
+
+rule 'Query cache min result size' [!fired('Query cache disabled')]
+ query_cache_limit
+ value == 1024*1024
+ The max size of the result set in the query cache is the default of 1 MiB.
+ Changing {query_cache_limit} (usually by increasing) may increase efficiency. This variable determines the maximum size a query result may have to be inserted into the query cache. If there are many query results above 1 MiB that are well cacheable (many reads, little writes) then increasing {query_cache_limit} will increase efficiency. Whereas in the case of many query results being above 1 MiB that are not very well cacheable (often invalidated due to table updates) increasing {query_cache_limit} might reduce efficiency.
+ query_cache_limit is set to 1 MiB
+
+#
+# Sorts
+rule 'Percentage of sorts that cause temporary tables' [Sort_scan + Sort_range > 0]
+ Sort_merge_passes / (Sort_scan + Sort_range) * 100
+ value > 10
+ Too many sorts are causing temporary tables.
+ Consider increasing {sort_buffer_size} and/or {read_rnd_buffer_size}, depending on your system memory limits
+ %s% of all sorts cause temporary tables, this value should be lower than 10%. | round(value,1)
+
+rule 'Rate of sorts that cause temporary tables'
+ Sort_merge_passes / Uptime
+ value * 60 * 60 > 1
+ Too many sorts are causing temporary tables.
+ Consider increasing {sort_buffer_size} and/or {read_rnd_buffer_size}, depending on your system memory limits
+ Temporary tables average: %s, this value should be less than 1 per hour. | ADVISOR_bytime(value,2)
+
+rule 'Sort rows'
+ Sort_rows / Uptime
+ value * 60 >= 1
+ There are lots of rows being sorted.
+ While there is nothing wrong with a high amount of row sorting, you might want to make sure that the queries which require a lot of sorting use indexed columns in the ORDER BY clause, as this will result in much faster sorting
+ Sorted rows average: %s | ADVISOR_bytime(value,2)
+
+# Joins, scans
+rule 'Rate of joins without indexes'
+ (Select_range_check + Select_scan + Select_full_join) / Uptime
+ value * 60 * 60 > 1
+ There are too many joins without indexes.
+ This means that joins are doing full table scans. Adding indexes for the columns being used in the join conditions will greatly speed up table joins
+ Table joins average: %s, this value should be less than 1 per hour | ADVISOR_bytime(value,2)
+
+rule 'Rate of reading first index entry'
+ Handler_read_first / Uptime
+ value * 60 * 60 > 1
+ The rate of reading the first index entry is high.
+ This usually indicates frequent full index scans. Full index scans are faster than table scans but require lots of CPU cycles in big tables, if those tables that have or had high volumes of UPDATEs and DELETEs, running 'OPTIMIZE TABLE' might reduce the amount of and/or speed up full index scans. Other than that full index scans can only be reduced by rewriting queries.
+ Index scans average: %s, this value should be less than 1 per hour | ADVISOR_bytime(value,2)
+
+rule 'Rate of reading fixed position'
+ Handler_read_rnd / Uptime
+ value * 60 * 60 > 1
+ The rate of reading data from a fixed position is high.
+ This indicates that many queries need to sort results and/or do a full table scan, including join queries that do not use indexes. Add indexes where applicable.
+ Rate of reading fixed position average: %s, this value should be less than 1 per hour | ADVISOR_bytime(value,2)
+
+rule 'Rate of reading next table row'
+ Handler_read_rnd_next / Uptime
+ value * 60 * 60 > 1
+ The rate of reading the next table row is high.
+ This indicates that many queries are doing full table scans. Add indexes where applicable.
+ Rate of reading next table row: %s, this value should be less than 1 per hour | ADVISOR_bytime(value,2)
+
+# temp tables
+rule 'tmp_table_size vs. max_heap_table_size'
+ tmp_table_size - max_heap_table_size
+ value !=0
+ {tmp_table_size} and {max_heap_table_size} are not the same.
+ If you have deliberately changed one of either: The server uses the lower value of either to determine the maximum size of in-memory tables. So if you wish to increase the in-memory table limit you will have to increase the other value as well.
+ Current values are tmp_table_size: %s, max_heap_table_size: %s | implode(' ',ADVISOR_formatByteDown(tmp_table_size, 2, 2)), implode(' ',ADVISOR_formatByteDown(max_heap_table_size, 2, 2))
+
+rule 'Percentage of temp tables on disk' [Created_tmp_tables + Created_tmp_disk_tables > 0]
+ Created_tmp_disk_tables / (Created_tmp_tables + Created_tmp_disk_tables) * 100
+ value > 25
+ Many temporary tables are being written to disk instead of being kept in memory.
+ Increasing {max_heap_table_size} and {tmp_table_size} might help. However some temporary tables are always being written to disk, independent of the value of these variables. To eliminate these you will have to rewrite your queries to avoid those conditions (Within a temporary table: Presence of a BLOB or TEXT column or presence of a column bigger than 512 bytes) as mentioned in the beginning of an <a href="http://www.facebook.com/note.php?note_id=10150111255065841&comments">Article by the Pythian Group</a>
+ %s% of all temporary tables are being written to disk, this value should be below 25% | round(value,1)
+
+rule 'Temp disk rate' [!fired('Percentage of temp tables on disk')]
+ Created_tmp_disk_tables / Uptime
+ value * 60 * 60 > 1
+ Many temporary tables are being written to disk instead of being kept in memory.
+ Increasing {max_heap_table_size} and {tmp_table_size} might help. However some temporary tables are always being written to disk, independent of the value of these variables. To eliminate these you will have to rewrite your queries to avoid those conditions (Within a temporary table: Presence of a BLOB or TEXT column or presence of a column bigger than 512 bytes) as mentioned in the <a href="http://dev.mysql.com/doc/refman/5.5/en/internal-temporary-tables.html">MySQL Documentation</a>
+ Rate of temporary tables being written to disk: %s, this value should be less than 1 per hour | ADVISOR_bytime(value,2)
+
+# I couldn't find any source on the internet that suggests a direct relation between high counts of temporary tables and any of these variables.
+# Several independent Blog entries suggest (http://ronaldbradford.com/blog/more-on-understanding-sort_buffer_size-2010-05-10/ and http://www.xaprb.com/blog/2010/05/09/how-to-tune-mysqls-sort_buffer_size/)
+# that sort_buffer_size should be left as it is. And increasing read_buffer_size is only suggested when there are a lot of
+# table scans (http://dev.mysql.com/doc/refman/5.5/en/server-system-variables.html#sysvar_read_buffer_size and other sources) though
+# setting it too high is bad too (http://www.mysqlperformanceblog.com/2007/09/17/mysql-what-read_buffer_size-value-is-optimal/).
+#rule 'Temp table rate'
+# Created_tmp_tables / Uptime
+# value * 60 * 60 > 1
+# Many intermediate temporary tables are being created.
+# This may be caused by queries under certain conditions as mentioned in the <a href="http://dev.mysql.com/doc/refman/5.5/en/internal-temporary-tables.html">MySQL Documentation</a>. Consider increasing {sort_buffer_size} (sorting), {read_rnd_buffer_size} (random read buffer, ie, post-sort), {read_buffer_size} (sequential scan).
+
+#
+# MyISAM index cache
+rule 'MyISAM key buffer size' [!PMA_DRIZZLE]
+ key_buffer_size
+ value == 0
+ Key buffer is not initialized. No MyISAM indexes will be cached.
+ Set {key_buffer_size} depending on the size of your MyISAM indexes. 64M is a good start.
+ key_buffer_size is 0
+
+rule 'Max % MyISAM key buffer ever used' [!PMA_DRIZZLE && key_buffer_size > 0]
+ Key_blocks_used * key_cache_block_size / key_buffer_size * 100
+ value < 95
+ MyISAM key buffer (index cache) % used is low.
+ You may need to decrease the size of {key_buffer_size}, re-examine your tables to see if indexes have been removed, or examine queries and expectations about what indexes are being used.
+ max % MyISAM key buffer ever used: %s%, this value should be above 95% | round(value,1)
+
+# Don't fire if above rule fired - we don't need the same advice twice
+rule 'Percentage of MyISAM key buffer used' [!PMA_DRIZZLE && key_buffer_size > 0 && !fired('Max % MyISAM key buffer ever used')]
+ ( 1 - Key_blocks_unused * key_cache_block_size / key_buffer_size) * 100
+ value < 95
+ MyISAM key buffer (index cache) % used is low.
+ You may need to decrease the size of {key_buffer_size}, re-examine your tables to see if indexes have been removed, or examine queries and expectations about what indexes are being used.
+ % MyISAM key buffer used: %s%, this value should be above 95% | round(value,1)
+
+rule 'Percentage of index reads from memory' [!PMA_DRIZZLE && Key_read_requests > 0]
+ 100 - (Key_reads / Key_read_requests * 100)
+ value < 95
+ The % of indexes that use the MyISAM key buffer is low.
+ You may need to increase {key_buffer_size}.
+ Index reads from memory: %s%, this value should be above 95% | round(value,1)
+
+#
+# other caches
+rule 'Rate of table open' [!PMA_DRIZZLE]
+ Opened_tables / Uptime
+ value*60*60 > 10
+ The rate of opening tables is high.
+ Opening tables requires disk I/O which is costly. Increasing {table_open_cache} might avoid this.
+ Opened table rate: %s, this value should be less than 10 per hour | ADVISOR_bytime(value,2)
+
+rule 'Percentage of used open files limit' [!PMA_DRIZZLE]
+ Open_files / open_files_limit * 100
+ value > 85
+ The number of open files is approaching the max number of open files. You may get a "Too many open files" error.
+ Consider increasing {open_files_limit}, and check the error log when restarting after changing {open_files_limit}.
+ The number of opened files is at %s% of the limit. It should be below 85% | round(value,1)
+
+rule 'Rate of open files' [!PMA_DRIZZLE]
+ Open_files / Uptime
+ value * 60 * 60 > 5
+ The rate of opening files is high.
+ Consider increasing {open_files_limit}, and check the error log when restarting after changing {open_files_limit}.
+ Opened files rate: %s, this value should be less than 5 per hour | ADVISOR_bytime(value,2)
+
+rule 'Immediate table locks %' [Table_locks_waited + Table_locks_immediate > 0]
+ Table_locks_immediate / (Table_locks_waited + Table_locks_immediate) * 100
+ value < 95
+ Too many table locks were not granted immediately.
+ Optimize queries and/or use InnoDB to reduce lock wait.
+ Immediate table locks: %s%, this value should be above 95% | round(value,1)
+
+rule 'Table lock wait rate'
+ Table_locks_waited / Uptime
+ value * 60 * 60 > 1
+ Too many table locks were not granted immediately.
+ Optimize queries and/or use InnoDB to reduce lock wait.
+ Table lock wait rate: %s, this value should be less than 1 per hour | ADVISOR_bytime(value,2)
+
+rule 'Thread cache' [!PMA_DRIZZLE]
+ thread_cache_size
+ value < 1
+ Thread cache is disabled, resulting in more overhead from new connections to MySQL.
+ Enable the thread cache by setting {thread_cache_size} > 0.
+ The thread cache is set to 0
+
+rule 'Thread cache hit rate %' [!PMA_DRIZZLE && thread_cache_size > 0]
+ 100 - Threads_created / Connections
+ value < 80
+ Thread cache is not efficient.
+ Increase {thread_cache_size}.
+ Thread cache hitrate: %s%, this value should be above 80% | round(value,1)
+
+rule 'Threads that are slow to launch' [!PMA_DRIZZLE && slow_launch_time > 0]
+ Slow_launch_threads
+ value > 0
+ There are too many threads that are slow to launch.
+ This generally happens in case of general system overload as it is pretty simple operations. You might want to monitor your system load carefully.
+ %s thread(s) took longer than %s seconds to start, it should be 0 | value, slow_launch_time
+
+rule 'Slow launch time' [!PMA_DRIZZLE]
+ slow_launch_time
+ value > 2
+ Slow_launch_threads is above 2s
+ Set {slow_launch_time} to 1s or 2s to correctly count threads that are slow to launch
+ slow_launch_time is set to %s | value
+
+#
+#Connections
+rule 'Percentage of used connections' [!PMA_DRIZZLE]
+ Max_used_connections / max_connections * 100
+ value > 80
+ The maximum amount of used connections is getting close to the value of {max_connections}.
+ Increase {max_connections}, or decrease {wait_timeout} so that connections that do not close database handlers properly get killed sooner. Make sure the code closes database handlers properly.
+ Max_used_connections is at %s% of max_connections, it should be below 80% | round(value,1)
+
+rule 'Percentage of aborted connections'
+ Aborted_connects / Connections * 100
+ value > 1
+ Too many connections are aborted.
+ Connections are usually aborted when they cannot be authorized. <a href="http://www.mysqlperformanceblog.com/2008/08/23/how-to-track-down-the-source-of-aborted_connects/">This article</a> might help you track down the source.
+ %s% of all connections are aborted. This value should be below 1% | round(value,1)
+
+rule 'Rate of aborted connections'
+ Aborted_connects / Uptime
+ value * 60 * 60 > 1
+ Too many connections are aborted.
+ Connections are usually aborted when they cannot be authorized. <a href="http://www.mysqlperformanceblog.com/2008/08/23/how-to-track-down-the-source-of-aborted_connects/">This article</a> might help you track down the source.
+ Aborted connections rate is at %s, this value should be less than 1 per hour | ADVISOR_bytime(value,2)
+
+rule 'Percentage of aborted clients'
+ Aborted_clients / Connections * 100
+ value > 2
+ Too many clients are aborted.
+ Clients are usually aborted when they did not close their connection to MySQL properly. This can be due to network issues or code not closing a database handler properly. Check your network and code.
+ %s% of all clients are aborted. This value should be below 2% | round(value,1)
+
+rule 'Rate of aborted clients'
+ Aborted_clients / Uptime
+ value * 60 * 60 > 1
+ Too many clients are aborted.
+ Clients are usually aborted when they did not close their connection to MySQL properly. This can be due to network issues or code not closing a database handler properly. Check your network and code.
+ Aborted client rate is at %s, this value should be less than 1 per hour | ADVISOR_bytime(value,2)
+
+#
+# InnoDB
+rule 'Is InnoDB disabled?' [!PMA_DRIZZLE && PMA_MYSQL_INT_VERSION < 50600]
+ have_innodb
+ value != "YES"
+ You do not have InnoDB enabled.
+ InnoDB is usually the better choice for table engines.
+ have_innodb is set to 'value'
+
+rule 'InnoDB log size' [innodb_buffer_pool_size > 0]
+ innodb_log_file_size / innodb_buffer_pool_size * 100
+ value < 20 && innodb_log_file_size / (1024 * 1024) < 256
+ The InnoDB log file size is not an appropriate size, in relation to the InnoDB buffer pool.
+ Especially on a system with a lot of writes to InnoDB tables you should set {innodb_log_file_size} to 25% of {innodb_buffer_pool_size}. However the bigger this value, the longer the recovery time will be when database crashes, so this value should not be set much higher than 256 MiB. Please note however that you cannot simply change the value of this variable. You need to shutdown the server, remove the InnoDB log files, set the new value in my.cnf, start the server, then check the error logs if everything went fine. See also <a href="http://mysqldatabaseadministration.blogspot.com/2007/01/increase-innodblogfilesize-proper-way.html">this blog entry</a>
+ Your InnoDB log size is at %s% in relation to the InnoDB buffer pool size, it should not be below 20% | round(value,1)
+
+rule 'Max InnoDB log size' [innodb_buffer_pool_size > 0 && innodb_log_file_size / innodb_buffer_pool_size * 100 < 30]
+ innodb_log_file_size / (1024 * 1024)
+ value > 256
+ The InnoDB log file size is inadequately large.
+ It is usually sufficient to set {innodb_log_file_size} to 25% of the size of {innodb_buffer_pool_size}. A very big {innodb_log_file_size} slows down the recovery time after a database crash considerably. See also <a href="http://www.mysqlperformanceblog.com/2006/07/03/choosing-proper-innodb_log_file_size/">this Article</a>. You need to shutdown the server, remove the InnoDB log files, set the new value in my.cnf, start the server, then check the error logs if everything went fine. See also <a href="http://mysqldatabaseadministration.blogspot.com/2007/01/increase-innodblogfilesize-proper-way.html">this blog entry</a>
+ Your absolute InnoDB log size is %s MiB | round(value,1)
+
+rule 'InnoDB buffer pool size' [system_memory > 0]
+ innodb_buffer_pool_size / system_memory * 100
+ value < 60
+ Your InnoDB buffer pool is fairly small.
+ The InnoDB buffer pool has a profound impact on performance for InnoDB tables. Assign all your remaining memory to this buffer. For database servers that use solely InnoDB as storage engine and have no other services (e.g. a web server) running, you may set this as high as 80% of your available memory. If that is not the case, you need to carefully assess the memory consumption of your other services and non-InnoDB-Tables and set this variable accordingly. If it is set too high, your system will start swapping, which decreases performance significantly. See also <a href="http://www.mysqlperformanceblog.com/2007/11/03/choosing-innodb_buffer_pool_size/">this article</a>
+ You are currently using %s% of your memory for the InnoDB buffer pool. This rule fires if you are assigning less than 60%, however this might be perfectly adequate for your system if you don't have much InnoDB tables or other services running on the same machine. | value
+
+#
+# other
+rule 'MyISAM concurrent inserts' [!PMA_DRIZZLE]
+ concurrent_insert
+ value === 0 || value === 'NEVER'
+ Enable {concurrent_insert} by setting it to 1
+ Setting {concurrent_insert} to 1 reduces contention between readers and writers for a given table. See also <a href="http://dev.mysql.com/doc/refman/5.5/en/concurrent-inserts.html">MySQL Documentation</a>
+ concurrent_insert is set to 0
+
+# INSERT DELAYED USAGE
+#Delayed_errors 0
+#Delayed_insert_threads 0
+#Delayed_writes 0
+#Not_flushed_delayed_rows
diff --git a/libraries/bfShapeFiles/ShapeFile.lib.php b/libraries/bfShapeFiles/ShapeFile.lib.php
new file mode 100644
index 0000000000..bf726a328c
--- /dev/null
+++ b/libraries/bfShapeFiles/ShapeFile.lib.php
@@ -0,0 +1,682 @@
+<?php
+/**
+ * BytesFall ShapeFiles library
+ *
+ * The library implements the 2D variants of the ShapeFile format as defined in
+ * http://www.esri.com/library/whitepapers/pdfs/shapefile.pdf.
+ * The library currently supports reading and editing of ShapeFiles and the
+ * Associated information (DBF file).
+ *
+ * @package bfShapeFiles
+ * @version 0.0.2
+ * @link http://bfshapefiles.sourceforge.net/
+ * @license http://www.gnu.org/licenses/gpl-2.0.html GPLv2-or-later
+ *
+ * Copyright 2006-2007 Ovidio <ovidio AT users.sourceforge.net>
+ *
+ * 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.
+ *
+ * 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, you can download one from
+ * http://www.gnu.org/copyleft/gpl.html.
+ *
+ */
+ function loadData($type, $data) {
+ if (!$data) return $data;
+ $tmp = unpack($type, $data);
+ return current($tmp);
+ }
+
+ function swap($binValue) {
+ $result = $binValue{strlen($binValue) - 1};
+ for($i = strlen($binValue) - 2; $i >= 0 ; $i--) {
+ $result .= $binValue{$i};
+ }
+
+ return $result;
+ }
+
+ function packDouble($value, $mode = 'LE') {
+ $value = (double)$value;
+ $bin = pack("d", $value);
+
+ //We test if the conversion of an integer (1) is done as LE or BE by default
+ switch (pack ('L', 1)) {
+ case pack ('V', 1): //Little Endian
+ $result = ($mode == 'LE') ? $bin : swap($bin);
+ break;
+ case pack ('N', 1): //Big Endian
+ $result = ($mode == 'BE') ? $bin : swap($bin);
+ break;
+ default: //Some other thing, we just return false
+ $result = FALSE;
+ }
+
+ return $result;
+ }
+/**
+ * ShapeFile class
+ *
+ * @package bfShapeFiles
+ */
+ class ShapeFile {
+ var $FileName;
+
+ var $SHPFile;
+ var $SHXFile;
+ var $DBFFile;
+
+ var $DBFHeader;
+
+ var $lastError = "";
+
+ var $boundingBox = array("xmin" => 0.0, "ymin" => 0.0, "xmax" => 0.0, "ymax" => 0.0);
+ var $fileLength = 0;
+ var $shapeType = 0;
+
+ var $records;
+
+ function ShapeFile($shapeType, $boundingBox = array("xmin" => 0.0, "ymin" => 0.0, "xmax" => 0.0, "ymax" => 0.0), $FileName = NULL) {
+ $this->shapeType = $shapeType;
+ $this->boundingBox = $boundingBox;
+ $this->FileName = $FileName;
+ $this->fileLength = 50;
+ }
+
+ function loadFromFile($FileName) {
+ $this->FileName = $FileName;
+
+ if (($this->_openSHPFile()) && ($this->_openDBFFile())) {
+ $this->_loadHeaders();
+ $this->_loadRecords();
+ $this->_closeSHPFile();
+ $this->_closeDBFFile();
+ } else {
+ return false;
+ }
+ }
+
+ function saveToFile($FileName = NULL) {
+ if ($FileName != NULL) $this->FileName = $FileName;
+
+ if (($this->_openSHPFile(TRUE)) && ($this->_openSHXFile(TRUE)) && ($this->_openDBFFile(TRUE))) {
+ $this->_saveHeaders();
+ $this->_saveRecords();
+ $this->_closeSHPFile();
+ $this->_closeSHXFile();
+ $this->_closeDBFFile();
+ } else {
+ return false;
+ }
+ }
+
+ function addRecord($record) {
+ if ((isset($this->DBFHeader)) && (is_array($this->DBFHeader))) {
+ $record->updateDBFInfo($this->DBFHeader);
+ }
+
+ $this->fileLength += ($record->getContentLength() + 4);
+ $this->records[] = $record;
+ $this->records[count($this->records) - 1]->recordNumber = count($this->records);
+
+ return (count($this->records) - 1);
+ }
+
+ function deleteRecord($index) {
+ if (isset($this->records[$index])) {
+ $this->fileLength -= ($this->records[$index]->getContentLength() + 4);
+ for ($i = $index; $i < (count($this->records) - 1); $i++) {
+ $this->records[$i] = $this->records[$i + 1];
+ }
+ unset($this->records[count($this->records) - 1]);
+ $this->_deleteRecordFromDBF($index);
+ }
+ }
+
+ function getDBFHeader() {
+ return $this->DBFHeader;
+ }
+
+ function setDBFHeader($header) {
+ $this->DBFHeader = $header;
+
+ for ($i = 0; $i < count($this->records); $i++) {
+ $this->records[$i]->updateDBFInfo($header);
+ }
+ }
+
+ function getIndexFromDBFData($field, $value) {
+ $result = -1;
+ for ($i = 0; $i < (count($this->records) - 1); $i++) {
+ if (isset($this->records[$i]->DBFData[$field]) && (strtoupper($this->records[$i]->DBFData[$field]) == strtoupper($value))) {
+ $result = $i;
+ }
+ }
+
+ return $result;
+ }
+
+ function _loadDBFHeader() {
+ $DBFFile = fopen(str_replace('.*', '.dbf', $this->FileName), 'r');
+
+ $result = array();
+ $buff32 = array();
+ $i = 1;
+ $inHeader = true;
+
+ while ($inHeader) {
+ if (!feof($DBFFile)) {
+ $buff32 = fread($DBFFile, 32);
+ if ($i > 1) {
+ if (substr($buff32, 0, 1) == chr(13)) {
+ $inHeader = false;
+ } else {
+ $pos = strpos(substr($buff32, 0, 10), chr(0));
+ $pos = ($pos == 0 ? 10 : $pos);
+
+ $fieldName = substr($buff32, 0, $pos);
+ $fieldType = substr($buff32, 11, 1);
+ $fieldLen = ord(substr($buff32, 16, 1));
+ $fieldDec = ord(substr($buff32, 17, 1));
+
+ array_push($result, array($fieldName, $fieldType, $fieldLen, $fieldDec));
+ }
+ }
+ $i++;
+ } else {
+ $inHeader = false;
+ }
+ }
+
+ fclose($DBFFile);
+ return($result);
+ }
+
+ function _deleteRecordFromDBF($index) {
+ if (@dbase_delete_record($this->DBFFile, $index)) {
+ @dbase_pack($this->DBFFile);
+ }
+ }
+
+ function _loadHeaders() {
+ fseek($this->SHPFile, 24, SEEK_SET);
+ $this->fileLength = loadData("N", fread($this->SHPFile, 4));
+
+ fseek($this->SHPFile, 32, SEEK_SET);
+ $this->shapeType = loadData("V", fread($this->SHPFile, 4));
+
+ $this->boundingBox = array();
+ $this->boundingBox["xmin"] = loadData("d", fread($this->SHPFile, 8));
+ $this->boundingBox["ymin"] = loadData("d", fread($this->SHPFile, 8));
+ $this->boundingBox["xmax"] = loadData("d", fread($this->SHPFile, 8));
+ $this->boundingBox["ymax"] = loadData("d", fread($this->SHPFile, 8));
+
+ $this->DBFHeader = $this->_loadDBFHeader();
+ }
+
+ function _saveHeaders() {
+ fwrite($this->SHPFile, pack("NNNNNN", 9994, 0, 0, 0, 0, 0));
+ fwrite($this->SHPFile, pack("N", $this->fileLength));
+ fwrite($this->SHPFile, pack("V", 1000));
+ fwrite($this->SHPFile, pack("V", $this->shapeType));
+ fwrite($this->SHPFile, packDouble($this->boundingBox['xmin']));
+ fwrite($this->SHPFile, packDouble($this->boundingBox['ymin']));
+ fwrite($this->SHPFile, packDouble($this->boundingBox['xmax']));
+ fwrite($this->SHPFile, packDouble($this->boundingBox['ymax']));
+ fwrite($this->SHPFile, pack("dddd", 0, 0, 0, 0));
+
+ fwrite($this->SHXFile, pack("NNNNNN", 9994, 0, 0, 0, 0, 0));
+ fwrite($this->SHXFile, pack("N", 50 + 4*count($this->records)));
+ fwrite($this->SHXFile, pack("V", 1000));
+ fwrite($this->SHXFile, pack("V", $this->shapeType));
+ fwrite($this->SHXFile, packDouble($this->boundingBox['xmin']));
+ fwrite($this->SHXFile, packDouble($this->boundingBox['ymin']));
+ fwrite($this->SHXFile, packDouble($this->boundingBox['xmax']));
+ fwrite($this->SHXFile, packDouble($this->boundingBox['ymax']));
+ fwrite($this->SHXFile, pack("dddd", 0, 0, 0, 0));
+ }
+
+ function _loadRecords() {
+ fseek($this->SHPFile, 100);
+ while (!feof($this->SHPFile)) {
+ $bByte = ftell($this->SHPFile);
+ $record = new ShapeRecord(-1);
+ $record->loadFromFile($this->SHPFile, $this->DBFFile);
+ $eByte = ftell($this->SHPFile);
+ if (($eByte <= $bByte) || ($record->lastError != "")) {
+ return false;
+ }
+
+ $this->records[] = $record;
+ }
+ }
+
+ function _saveRecords() {
+ if (file_exists(str_replace('.*', '.dbf', $this->FileName))) {
+ @unlink(str_replace('.*', '.dbf', $this->FileName));
+ }
+ if (!($this->DBFFile = @dbase_create(str_replace('.*', '.dbf', $this->FileName), $this->DBFHeader))) {
+ return $this->setError(sprintf("It wasn't possible to create the DBase file '%s'", str_replace('.*', '.dbf', $this->FileName)));
+ }
+
+ $offset = 50;
+ if (is_array($this->records) && (count($this->records) > 0)) {
+ reset($this->records);
+ while (list($index, $record) = each($this->records)) {
+ //Save the record to the .shp file
+ $record->saveToFile($this->SHPFile, $this->DBFFile, $index + 1);
+
+ //Save the record to the .shx file
+ fwrite($this->SHXFile, pack("N", $offset));
+ fwrite($this->SHXFile, pack("N", $record->getContentLength()));
+ $offset += (4 + $record->getContentLength());
+ }
+ }
+ @dbase_pack($this->DBFFile);
+ }
+
+ function _openSHPFile($toWrite = false) {
+ $this->SHPFile = @fopen(str_replace('.*', '.shp', $this->FileName), ($toWrite ? "wb+" : "rb"));
+ if (!$this->SHPFile) {
+ return $this->setError(sprintf("It wasn't possible to open the Shape file '%s'", str_replace('.*', '.shp', $this->FileName)));
+ }
+
+ return TRUE;
+ }
+
+ function _closeSHPFile() {
+ if ($this->SHPFile) {
+ fclose($this->SHPFile);
+ $this->SHPFile = NULL;
+ }
+ }
+
+ function _openSHXFile($toWrite = false) {
+ $this->SHXFile = @fopen(str_replace('.*', '.shx', $this->FileName), ($toWrite ? "wb+" : "rb"));
+ if (!$this->SHXFile) {
+ return $this->setError(sprintf("It wasn't possible to open the Index file '%s'", str_replace('.*', '.shx', $this->FileName)));
+ }
+
+ return TRUE;
+ }
+
+ function _closeSHXFile() {
+ if ($this->SHXFile) {
+ fclose($this->SHXFile);
+ $this->SHXFile = NULL;
+ }
+ }
+
+ function _openDBFFile($toWrite = false) {
+ $checkFunction = $toWrite ? "is_writable" : "is_readable";
+ if (($toWrite) && (!file_exists(str_replace('.*', '.dbf', $this->FileName)))) {
+ if (!@dbase_create(str_replace('.*', '.dbf', $this->FileName), $this->DBFHeader)) {
+ return $this->setError(sprintf("It wasn't possible to create the DBase file '%s'", str_replace('.*', '.dbf', $this->FileName)));
+ }
+ }
+ if ($checkFunction(str_replace('.*', '.dbf', $this->FileName))) {
+ $this->DBFFile = dbase_open(str_replace('.*', '.dbf', $this->FileName), ($toWrite ? 2 : 0));
+ if (!$this->DBFFile) {
+ return $this->setError(sprintf("It wasn't possible to open the DBase file '%s'", str_replace('.*', '.dbf', $this->FileName)));
+ }
+ } else {
+ return $this->setError(sprintf("It wasn't possible to find the DBase file '%s'", str_replace('.*', '.dbf', $this->FileName)));
+ }
+ return TRUE;
+ }
+
+ function _closeDBFFile() {
+ if ($this->DBFFile) {
+ dbase_close($this->DBFFile);
+ $this->DBFFile = NULL;
+ }
+ }
+
+ function setError($error) {
+ $this->lastError = $error;
+ return false;
+ }
+ }
+
+ class ShapeRecord {
+ var $SHPFile = NULL;
+ var $DBFFile = NULL;
+
+ var $recordNumber = NULL;
+ var $shapeType = NULL;
+
+ var $lastError = "";
+
+ var $SHPData = array();
+ var $DBFData = array();
+
+ function ShapeRecord($shapeType) {
+ $this->shapeType = $shapeType;
+ }
+
+ function loadFromFile(&$SHPFile, &$DBFFile) {
+ $this->SHPFile = $SHPFile;
+ $this->DBFFile = $DBFFile;
+ $this->_loadHeaders();
+
+ switch ($this->shapeType) {
+ case 0:
+ $this->_loadNullRecord();
+ break;
+ case 1:
+ $this->_loadPointRecord();
+ break;
+ case 3:
+ $this->_loadPolyLineRecord();
+ break;
+ case 5:
+ $this->_loadPolygonRecord();
+ break;
+ case 8:
+ $this->_loadMultiPointRecord();
+ break;
+ default:
+ $this->setError(sprintf("The Shape Type '%s' is not supported.", $this->shapeType));
+ break;
+ }
+ $this->_loadDBFData();
+ }
+
+ function saveToFile(&$SHPFile, &$DBFFile, $recordNumber) {
+ $this->SHPFile = $SHPFile;
+ $this->DBFFile = $DBFFile;
+ $this->recordNumber = $recordNumber;
+ $this->_saveHeaders();
+
+ switch ($this->shapeType) {
+ case 0:
+ $this->_saveNullRecord();
+ break;
+ case 1:
+ $this->_savePointRecord();
+ break;
+ case 3:
+ $this->_savePolyLineRecord();
+ break;
+ case 5:
+ $this->_savePolygonRecord();
+ break;
+ case 8:
+ $this->_saveMultiPointRecord();
+ break;
+ default:
+ $this->setError(sprintf("The Shape Type '%s' is not supported.", $this->shapeType));
+ break;
+ }
+ $this->_saveDBFData();
+ }
+
+ function updateDBFInfo($header) {
+ $tmp = $this->DBFData;
+ unset($this->DBFData);
+ $this->DBFData = array();
+ reset($header);
+ while (list($key, $value) = each($header)) {
+ $this->DBFData[$value[0]] = (isset($tmp[$value[0]])) ? $tmp[$value[0]] : "";
+ }
+ }
+
+ function _loadHeaders() {
+ $this->recordNumber = loadData("N", fread($this->SHPFile, 4));
+ $tmp = loadData("N", fread($this->SHPFile, 4)); //We read the length of the record
+ $this->shapeType = loadData("V", fread($this->SHPFile, 4));
+ }
+
+ function _saveHeaders() {
+ fwrite($this->SHPFile, pack("N", $this->recordNumber));
+ fwrite($this->SHPFile, pack("N", $this->getContentLength()));
+ fwrite($this->SHPFile, pack("V", $this->shapeType));
+ }
+
+ function _loadPoint() {
+ $data = array();
+
+ $data["x"] = loadData("d", fread($this->SHPFile, 8));
+ $data["y"] = loadData("d", fread($this->SHPFile, 8));
+
+ return $data;
+ }
+
+ function _savePoint($data) {
+ fwrite($this->SHPFile, packDouble($data["x"]));
+ fwrite($this->SHPFile, packDouble($data["y"]));
+ }
+
+ function _loadNullRecord() {
+ $this->SHPData = array();
+ }
+
+ function _saveNullRecord() {
+ //Don't save anything
+ }
+
+ function _loadPointRecord() {
+ $this->SHPData = $this->_loadPoint();
+ }
+
+ function _savePointRecord() {
+ $this->_savePoint($this->SHPData);
+ }
+
+ function _loadMultiPointRecord() {
+ $this->SHPData = array();
+ $this->SHPData["xmin"] = loadData("d", fread($this->SHPFile, 8));
+ $this->SHPData["ymin"] = loadData("d", fread($this->SHPFile, 8));
+ $this->SHPData["xmax"] = loadData("d", fread($this->SHPFile, 8));
+ $this->SHPData["ymax"] = loadData("d", fread($this->SHPFile, 8));
+
+ $this->SHPData["numpoints"] = loadData("V", fread($this->SHPFile, 4));
+
+ for ($i = 0; $i <= $this->SHPData["numpoints"]; $i++) {
+ $this->SHPData["points"][] = $this->_loadPoint();
+ }
+ }
+
+ function _saveMultiPointRecord() {
+ fwrite($this->SHPFile, pack("dddd", $this->SHPData["xmin"], $this->SHPData["ymin"], $this->SHPData["xmax"], $this->SHPData["ymax"]));
+
+ fwrite($this->SHPFile, pack("V", $this->SHPData["numpoints"]));
+
+ for ($i = 0; $i <= $this->SHPData["numpoints"]; $i++) {
+ $this->_savePoint($this->SHPData["points"][$i]);
+ }
+ }
+
+ function _loadPolyLineRecord() {
+ $this->SHPData = array();
+ $this->SHPData["xmin"] = loadData("d", fread($this->SHPFile, 8));
+ $this->SHPData["ymin"] = loadData("d", fread($this->SHPFile, 8));
+ $this->SHPData["xmax"] = loadData("d", fread($this->SHPFile, 8));
+ $this->SHPData["ymax"] = loadData("d", fread($this->SHPFile, 8));
+
+ $this->SHPData["numparts"] = loadData("V", fread($this->SHPFile, 4));
+ $this->SHPData["numpoints"] = loadData("V", fread($this->SHPFile, 4));
+
+ for ($i = 0; $i < $this->SHPData["numparts"]; $i++) {
+ $this->SHPData["parts"][$i] = loadData("V", fread($this->SHPFile, 4));
+ }
+
+ $firstIndex = ftell($this->SHPFile);
+ $readPoints = 0;
+ reset($this->SHPData["parts"]);
+ while (list($partIndex, $partData) = each($this->SHPData["parts"])) {
+ if (!isset($this->SHPData["parts"][$partIndex]["points"]) || !is_array($this->SHPData["parts"][$partIndex]["points"])) {
+ $this->SHPData["parts"][$partIndex] = array();
+ $this->SHPData["parts"][$partIndex]["points"] = array();
+ }
+ while (!in_array($readPoints, $this->SHPData["parts"]) && ($readPoints < ($this->SHPData["numpoints"])) && !feof($this->SHPFile)) {
+ $this->SHPData["parts"][$partIndex]["points"][] = $this->_loadPoint();
+ $readPoints++;
+ }
+ }
+
+ fseek($this->SHPFile, $firstIndex + ($readPoints*16));
+ }
+
+ function _savePolyLineRecord() {
+ fwrite($this->SHPFile, pack("dddd", $this->SHPData["xmin"], $this->SHPData["ymin"], $this->SHPData["xmax"], $this->SHPData["ymax"]));
+
+ fwrite($this->SHPFile, pack("VV", $this->SHPData["numparts"], $this->SHPData["numpoints"]));
+
+ for ($i = 0; $i < $this->SHPData["numparts"]; $i++) {
+ fwrite($this->SHPFile, pack("V", count($this->SHPData["parts"][$i])));
+ }
+
+ reset($this->SHPData["parts"]);
+ foreach ($this->SHPData["parts"] as $partData){
+ reset($partData["points"]);
+ while (list($pointIndex, $pointData) = each($partData["points"])) {
+ $this->_savePoint($pointData);
+ }
+ }
+ }
+
+ function _loadPolygonRecord() {
+ $this->_loadPolyLineRecord();
+ }
+
+ function _savePolygonRecord() {
+ $this->_savePolyLineRecord();
+ }
+
+ function addPoint($point, $partIndex = 0) {
+ switch ($this->shapeType) {
+ case 0:
+ //Don't add anything
+ break;
+ case 1:
+ //Substitutes the value of the current point
+ $this->SHPData = $point;
+ break;
+ case 3:
+ case 5:
+ //Adds a new point to the selected part
+ if (!isset($this->SHPData["xmin"]) || ($this->SHPData["xmin"] > $point["x"])) $this->SHPData["xmin"] = $point["x"];
+ if (!isset($this->SHPData["ymin"]) || ($this->SHPData["ymin"] > $point["y"])) $this->SHPData["ymin"] = $point["y"];
+ if (!isset($this->SHPData["xmax"]) || ($this->SHPData["xmax"] < $point["x"])) $this->SHPData["xmax"] = $point["x"];
+ if (!isset($this->SHPData["ymax"]) || ($this->SHPData["ymax"] < $point["y"])) $this->SHPData["ymax"] = $point["y"];
+
+ $this->SHPData["parts"][$partIndex]["points"][] = $point;
+
+ $this->SHPData["numparts"] = count($this->SHPData["parts"]);
+ $this->SHPData["numpoints"]++;
+ break;
+ case 8:
+ //Adds a new point
+ if (!isset($this->SHPData["xmin"]) || ($this->SHPData["xmin"] > $point["x"])) $this->SHPData["xmin"] = $point["x"];
+ if (!isset($this->SHPData["ymin"]) || ($this->SHPData["ymin"] > $point["y"])) $this->SHPData["ymin"] = $point["y"];
+ if (!isset($this->SHPData["xmax"]) || ($this->SHPData["xmax"] < $point["x"])) $this->SHPData["xmax"] = $point["x"];
+ if (!isset($this->SHPData["ymax"]) || ($this->SHPData["ymax"] < $point["y"])) $this->SHPData["ymax"] = $point["y"];
+
+ $this->SHPData["points"][] = $point;
+ $this->SHPData["numpoints"]++;
+ break;
+ default:
+ $this->setError(sprintf("The Shape Type '%s' is not supported.", $this->shapeType));
+ break;
+ }
+ }
+
+ function deletePoint($pointIndex = 0, $partIndex = 0) {
+ switch ($this->shapeType) {
+ case 0:
+ //Don't delete anything
+ break;
+ case 1:
+ //Sets the value of the point to zero
+ $this->SHPData["x"] = 0.0;
+ $this->SHPData["y"] = 0.0;
+ break;
+ case 3:
+ case 5:
+ //Deletes the point from the selected part, if exists
+ if (isset($this->SHPData["parts"][$partIndex]) && isset($this->SHPData["parts"][$partIndex]["points"][$pointIndex])) {
+ for ($i = $pointIndex; $i < (count($this->SHPData["parts"][$partIndex]["points"]) - 1); $i++) {
+ $this->SHPData["parts"][$partIndex]["points"][$i] = $this->SHPData["parts"][$partIndex]["points"][$i + 1];
+ }
+ unset($this->SHPData["parts"][$partIndex]["points"][count($this->SHPData["parts"][$partIndex]["points"]) - 1]);
+
+ $this->SHPData["numparts"] = count($this->SHPData["parts"]);
+ $this->SHPData["numpoints"]--;
+ }
+ break;
+ case 8:
+ //Deletes the point, if exists
+ if (isset($this->SHPData["points"][$pointIndex])) {
+ for ($i = $pointIndex; $i < (count($this->SHPData["points"]) - 1); $i++) {
+ $this->SHPData["points"][$i] = $this->SHPData["points"][$i + 1];
+ }
+ unset($this->SHPData["points"][count($this->SHPData["points"]) - 1]);
+
+ $this->SHPData["numpoints"]--;
+ }
+ break;
+ default:
+ $this->setError(sprintf("The Shape Type '%s' is not supported.", $this->shapeType));
+ break;
+ }
+ }
+
+ function getContentLength() {
+ switch ($this->shapeType) {
+ case 0:
+ $result = 0;
+ break;
+ case 1:
+ $result = 10;
+ break;
+ case 3:
+ case 5:
+ $result = 22 + 2*count($this->SHPData["parts"]);
+ for ($i = 0; $i < count($this->SHPData["parts"]); $i++) {
+ $result += 8*count($this->SHPData["parts"][$i]["points"]);
+ }
+ break;
+ case 8:
+ $result = 20 + 8*count($this->SHPData["points"]);
+ break;
+ default:
+ $result = false;
+ $this->setError(sprintf("The Shape Type '%s' is not supported.", $this->shapeType));
+ break;
+ }
+ return $result;
+ }
+
+ function _loadDBFData() {
+ $this->DBFData = @dbase_get_record_with_names($this->DBFFile, $this->recordNumber);
+ unset($this->DBFData["deleted"]);
+ }
+
+ function _saveDBFData() {
+ unset($this->DBFData["deleted"]);
+ if ($this->recordNumber <= dbase_numrecords($this->DBFFile)) {
+ if (!dbase_replace_record($this->DBFFile, array_values($this->DBFData), $this->recordNumber)) {
+ $this->setError("I wasn't possible to update the information in the DBF file.");
+ }
+ } else {
+ if (!dbase_add_record($this->DBFFile, array_values($this->DBFData))) {
+ $this->setError("I wasn't possible to add the information to the DBF file.");
+ }
+ }
+ }
+
+ function setError($error) {
+ $this->lastError = $error;
+ return false;
+ }
+ }
+
+?>
diff --git a/libraries/bookmark.lib.php b/libraries/bookmark.lib.php
new file mode 100644
index 0000000000..10adc2e107
--- /dev/null
+++ b/libraries/bookmark.lib.php
@@ -0,0 +1,216 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Set of functions used with the bookmark feature
+ *
+ * @package PhpMyAdmin
+ */
+
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ * Defines the bookmark parameters for the current user
+ *
+ * @return array the bookmark parameters for the current user
+ * @access public
+ */
+function PMA_Bookmark_getParams()
+{
+ static $cfgBookmark = null;
+
+ if (null !== $cfgBookmark) {
+ return $cfgBookmark;
+ }
+
+ $cfgRelation = PMA_getRelationsParam();
+
+ if ($cfgRelation['bookmarkwork']) {
+ $cfgBookmark = array(
+ 'user' => $GLOBALS['cfg']['Server']['user'],
+ 'db' => $GLOBALS['cfg']['Server']['pmadb'],
+ 'table' => $GLOBALS['cfg']['Server']['bookmarktable'],
+ );
+ } else {
+ $cfgBookmark = false;
+ }
+
+ return $cfgBookmark;
+} // end of the 'PMA_Bookmark_getParams()' function
+
+
+/**
+ * Gets the list of bookmarks defined for the current database
+ *
+ * @param string $db the current database name
+ *
+ * @return array the bookmarks list (key as index, label as value)
+ *
+ * @access public
+ *
+ * @global resource $controllink the controluser db connection handle
+ */
+function PMA_Bookmark_getList($db)
+{
+ global $controllink;
+
+ $cfgBookmark = PMA_Bookmark_getParams();
+
+ if (empty($cfgBookmark)) {
+ return array();
+ }
+
+ $query = 'SELECT label, id FROM '. PMA_Util::backquote($cfgBookmark['db'])
+ . '.' . PMA_Util::backquote($cfgBookmark['table'])
+ . ' WHERE dbase = \'' . PMA_Util::sqlAddSlashes($db) . '\''
+ . ' AND user = \'' . PMA_Util::sqlAddSlashes($cfgBookmark['user']) . '\''
+ . ' ORDER BY label';
+ $per_user = $GLOBALS['dbi']->fetchResult(
+ $query, 'id', 'label', $controllink, PMA_DatabaseInterface::QUERY_STORE
+ );
+
+ $query = 'SELECT label, id FROM '. PMA_Util::backquote($cfgBookmark['db'])
+ . '.' . PMA_Util::backquote($cfgBookmark['table'])
+ . ' WHERE dbase = \'' . PMA_Util::sqlAddSlashes($db) . '\''
+ . ' AND user = \'\''
+ . ' ORDER BY label';
+ $global = $GLOBALS['dbi']->fetchResult(
+ $query, 'id', 'label', $controllink, PMA_DatabaseInterface::QUERY_STORE
+ );
+
+ foreach ($global as $key => $val) {
+ $global[$key] = $val . ' (' . __('shared') . ')';
+ }
+
+ $ret = $global + $per_user;
+
+ asort($ret);
+
+ return $ret;
+} // end of the 'PMA_Bookmark_getList()' function
+
+
+/**
+ * Gets the sql command from a bookmark
+ *
+ * @param string $db the current database name
+ * @param mixed $id the id of the bookmark to get
+ * @param string $id_field which field to look up the $id
+ * @param boolean $action_bookmark_all true: get all bookmarks regardless
+ * of the owning user
+ * @param boolean $exact_user_match whether to ignore bookmarks with no user
+ *
+ * @return string the sql query
+ *
+ * @access public
+ *
+ * @global resource $controllink the controluser db connection handle
+ *
+ */
+function PMA_Bookmark_get($db, $id, $id_field = 'id', $action_bookmark_all = false,
+ $exact_user_match = false
+) {
+ global $controllink;
+
+ $cfgBookmark = PMA_Bookmark_getParams();
+
+ if (empty($cfgBookmark)) {
+ return '';
+ }
+
+ $query = 'SELECT query FROM ' . PMA_Util::backquote($cfgBookmark['db'])
+ . '.' . PMA_Util::backquote($cfgBookmark['table'])
+ . ' WHERE dbase = \'' . PMA_Util::sqlAddSlashes($db) . '\'';
+
+ if (! $action_bookmark_all) {
+ $query .= ' AND (user = \''
+ . PMA_Util::sqlAddSlashes($cfgBookmark['user']) . '\'';
+ if (! $exact_user_match) {
+ $query .= ' OR user = \'\'';
+ }
+ $query .= ')';
+ }
+
+ $query .= ' AND ' . PMA_Util::backquote($id_field) . ' = ' . $id;
+
+ return $GLOBALS['dbi']->fetchValue($query, 0, 0, $controllink);
+} // end of the 'PMA_Bookmark_get()' function
+
+/**
+ * Adds a bookmark
+ *
+ * @param array $bkm_fields the properties of the bookmark to add; here,
+ * $bkm_fields['bkm_sql_query'] is urlencoded
+ * @param boolean $all_users whether to make the bookmark available for all users
+ *
+ * @return boolean whether the INSERT succeeds or not
+ *
+ * @access public
+ *
+ * @global resource $controllink the controluser db connection handle
+ */
+function PMA_Bookmark_save($bkm_fields, $all_users = false)
+{
+ global $controllink;
+
+ $cfgBookmark = PMA_Bookmark_getParams();
+
+ if (empty($cfgBookmark)) {
+ return false;
+ }
+
+ $query = 'INSERT INTO ' . PMA_Util::backquote($cfgBookmark['db'])
+ . '.' . PMA_Util::backquote($cfgBookmark['table'])
+ . ' (id, dbase, user, query, label)'
+ . ' VALUES (NULL, \''
+ . PMA_Util::sqlAddSlashes($bkm_fields['bkm_database']) . '\', '
+ . '\''
+ . ($all_users ? '' : PMA_Util::sqlAddSlashes($bkm_fields['bkm_user']))
+ . '\', '
+ . '\''
+ . PMA_Util::sqlAddSlashes(urldecode($bkm_fields['bkm_sql_query']))
+ . '\', '
+ . '\'' . PMA_Util::sqlAddSlashes($bkm_fields['bkm_label']) . '\')';
+ return $GLOBALS['dbi']->query($query, $controllink);
+} // end of the 'PMA_Bookmark_save()' function
+
+
+/**
+ * Deletes a bookmark
+ *
+ * @param string $db the current database name
+ * @param integer $id the id of the bookmark to get
+ *
+ * @return bool true if successful
+ *
+ * @access public
+ *
+ * @global resource $controllink the controluser db connection handle
+ */
+function PMA_Bookmark_delete($db, $id)
+{
+ global $controllink;
+
+ $cfgBookmark = PMA_Bookmark_getParams();
+
+ if (empty($cfgBookmark)) {
+ return false;
+ }
+
+ $query = 'DELETE FROM ' . PMA_Util::backquote($cfgBookmark['db'])
+ . '.' . PMA_Util::backquote($cfgBookmark['table'])
+ . ' WHERE (user = \'' . PMA_Util::sqlAddSlashes($cfgBookmark['user']) . '\''
+ . ' OR user = \'\')'
+ . ' AND id = ' . $id;
+ return $GLOBALS['dbi']->tryQuery($query, $controllink);
+} // end of the 'PMA_Bookmark_delete()' function
+
+
+/**
+ * Bookmark Support
+ */
+if (!defined('TESTSUITE')) {
+ $GLOBALS['cfg']['Bookmark'] = PMA_Bookmark_getParams();
+}
+?>
diff --git a/libraries/browse_foreigners.lib.php b/libraries/browse_foreigners.lib.php
new file mode 100644
index 0000000000..e5515fc4bc
--- /dev/null
+++ b/libraries/browse_foreigners.lib.php
@@ -0,0 +1,415 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Contains functions used by browse_foreigners.php
+ *
+ * @package PhpMyAdmin
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ * Function to get html for relational field selection
+ *
+ * @param string $db current database
+ * @param string $table current table
+ * @param string $field field
+ * @param array $foreignData foreign column data
+ * @param string $fieldkey field key
+ * @param array $data data
+ *
+ * @return string
+ */
+function PMA_getHtmlForRelationalFieldSelection($db, $table, $field, $foreignData,
+ $fieldkey, $data
+) {
+ $gotopage = PMA_getHtmlForGotoPage($foreignData);
+ $showall = PMA_getHtmlForShowAll($foreignData);
+
+ $output = '<form action="browse_foreigners.php" method="post">'
+ . '<fieldset>'
+ . PMA_URL_getHiddenInputs($db, $table)
+ . '<input type="hidden" name="field" value="' . htmlspecialchars($field)
+ . '" />'
+ . '<input type="hidden" name="fieldkey" value="'
+ . (isset($fieldkey) ? htmlspecialchars($fieldkey) : '') . '" />';
+
+ if (isset($_REQUEST['rownumber'])) {
+ $output .= '<input type="hidden" name="rownumber" value="'
+ . htmlspecialchars($_REQUEST['rownumber']) . '" />';
+ }
+ $output .= '<span class="formelement">'
+ . '<label for="input_foreign_filter">' . __('Search:') . '</label>'
+ . '<input type="text" name="foreign_filter" '
+ . 'id="input_foreign_filter" value="'
+ . (isset($_REQUEST['foreign_filter'])
+ ? htmlspecialchars($_REQUEST['foreign_filter'])
+ : '')
+ . '" />'
+ . '<input type="submit" name="submit_foreign_filter" value="'
+ . __('Go') . '" />'
+ . '</span>'
+ . '<span class="formelement">' . $gotopage . '</span>'
+ . '<span class="formelement">' . $showall . '</span>'
+ . '</fieldset>'
+ . '</form>';
+
+ $output .= '<table width="100%">';
+
+ if (is_array($foreignData['disp_row'])) {
+ $header = '<tr>
+ <th>' . __('Keyname') . '</th>
+ <th>' . __('Description') . '</th>
+ <td width="20%"></td>
+ <th>' . __('Description') . '</th>
+ <th>' . __('Keyname') . '</th>
+ </tr>';
+
+ $output .= '<thead>' . $header . '</thead>' . "\n"
+ . '<tfoot>' . $header . '</tfoot>' . "\n"
+ . '<tbody>' . "\n";
+
+ $values = array();
+ $keys = array();
+ foreach ($foreignData['disp_row'] as $relrow) {
+ if ($foreignData['foreign_display'] != false) {
+ $values[] = $relrow[$foreignData['foreign_display']];
+ } else {
+ $values[] = '';
+ }
+
+ $keys[] = $relrow[$foreignData['foreign_field']];
+ }
+
+ asort($keys);
+
+ $hcount = 0;
+ $odd_row = true;
+ $val_ordered_current_row = 0;
+ $val_ordered_current_equals_data = false;
+ $key_ordered_current_equals_data = false;
+ foreach ($keys as $key_ordered_current_row => $value) {
+ $hcount++;
+
+ if ($GLOBALS['cfg']['RepeatCells'] > 0
+ && $hcount > $GLOBALS['cfg']['RepeatCells']
+ ) {
+ $output .= $header;
+ $hcount = 0;
+ $odd_row = true;
+ }
+
+ $key_ordered_current_key = $keys[$key_ordered_current_row];
+ $key_ordered_current_val = $values[$key_ordered_current_row];
+
+ $val_ordered_current_key = $keys[$val_ordered_current_row];
+ $val_ordered_current_val = $values[$val_ordered_current_row];
+
+ $val_ordered_current_row++;
+
+ $pmaString = $GLOBALS['PMA_String'];
+ $limitChars = $GLOBALS['cfg']['LimitChars'];
+ if ($pmaString->strlen($val_ordered_current_val) <= $limitChars) {
+ $val_ordered_current_val = htmlspecialchars(
+ $val_ordered_current_val
+ );
+ $val_ordered_current_val_title = '';
+ } else {
+ $val_ordered_current_val_title = htmlspecialchars(
+ $val_ordered_current_val
+ );
+ $val_ordered_current_val = htmlspecialchars(
+ $pmaString->substr(
+ $val_ordered_current_val, 0, $limitChars
+ )
+ . '...'
+ );
+ }
+ if ($pmaString->strlen($key_ordered_current_val) <= $limitChars) {
+ $key_ordered_current_val = htmlspecialchars(
+ $key_ordered_current_val
+ );
+ $key_ordered_current_val_title = '';
+ } else {
+ $key_ordered_current_val_title = htmlspecialchars(
+ $key_ordered_current_val
+ );
+ $key_ordered_current_val = htmlspecialchars(
+ $pmaString->substr(
+ $key_ordered_current_val, 0, $limitChars
+ ) . '...'
+ );
+ }
+
+ if (! empty($data)) {
+ $val_ordered_current_equals_data
+ = $val_ordered_current_key == $data;
+ $key_ordered_current_equals_data
+ = $key_ordered_current_key == $data;
+ }
+
+ $output .= '<tr class="noclick ' . ($odd_row ? 'odd' : 'even') . '">';
+ $odd_row = ! $odd_row;
+
+ $output .= PMA_getHtmlForColumnElement(
+ 'class="nowrap"', $key_ordered_current_equals_data,
+ $key_ordered_current_key, $key_ordered_current_val,
+ $key_ordered_current_val_title, $field
+ );
+
+ $output .= PMA_getHtmlForColumnElement(
+ '', $key_ordered_current_equals_data, $key_ordered_current_key,
+ $key_ordered_current_val, $key_ordered_current_val_title, $field
+ );
+
+ $output .= '<td width="20%">'
+ . '<img src="' . $GLOBALS['pmaThemeImage'] . 'spacer.png" alt=""'
+ . ' width="1" height="1" /></td>';
+
+ $output .= PMA_getHtmlForColumnElement(
+ '', $val_ordered_current_equals_data, $key_ordered_current_key,
+ $val_ordered_current_val, $val_ordered_current_val_title, $field
+ );
+
+ $output .= PMA_getHtmlForColumnElement(
+ 'class="nowrap"', $val_ordered_current_equals_data,
+ $val_ordered_current_key, $val_ordered_current_val,
+ $val_ordered_current_val_title, $field
+ );
+ $output .= '</tr>';
+ } // end while
+ }
+ $output .= '</tbody>'
+ . '</table>';
+
+ return $output;
+}
+
+/**
+ * Function to get html for each column element
+ *
+ * @param string $cssClass class="nowrap" or ''
+ * @param bool $currentEqualsData whether current equals data
+ * @param string $currentKey current key
+ * @param string $currentVal current value
+ * @param string $currentTitle current title
+ * @param string $field field
+ *
+ * @return string
+ */
+function PMA_getHtmlForColumnElement($cssClass, $currentEqualsData, $currentKey,
+ $currentVal, $currentTitle, $field
+) {
+ $output = '<td ' . $cssClass . '>'
+ . ($currentEqualsData ? '<strong>' : '')
+ . '<a href="#" title="' . __('Use this value')
+ . ($currentTitle != ''
+ ? ': ' . $currentTitle
+ : '')
+ . '" onclick="formupdate(\'' . md5($field) . '\', \''
+ . PMA_jsFormat($currentKey, false)
+ . '\'); return false;">';
+ if ($cssClass !== '') {
+ $output .= htmlspecialchars($currentKey);
+ } else {
+ $output .= $currentVal;
+ }
+
+ $output .= '</a>' . ($currentEqualsData ? '</strong>' : '') . '</td>';
+
+ return $output;
+}
+
+/**
+ * Function to get javascript code to handle display selection for relational
+ * field values
+ *
+ * @return string
+ */
+function PMA_getJsScriptToHandleSelectRelationalFields()
+{
+ $element_name = PMA_getElementName();
+ $fieldkey = PMA_getFieldKey();
+ $error = PMA_getJsError();
+ $code = <<<EOC
+self.focus();
+function formupdate(fieldmd5, key) {
+ var \$inline = window.opener.jQuery('.browse_foreign_clicked');
+ if (\$inline.length != 0) {
+ \$inline.removeClass('browse_foreign_clicked')
+ // for grid editing,
+ // puts new value in the previous element which is
+ // a span with class curr_value, and trigger .change()
+ .prev('.curr_value').text(key).change();
+ // for zoom-search editing, puts new value in the previous
+ // element which is an input field
+ \$inline.prev('input[type=text]').val(key);
+ self.close();
+ return false;
+ }
+
+ if (opener && opener.document && opener.document.insertForm) {
+ var field = 'fields';
+ var field_null = 'fields_null';
+
+ $element_name
+
+ var element_name_alt = field + '[$fieldkey]';
+
+ if (opener.document.insertForm.elements[element_name]) {
+ // Edit/Insert form
+ opener.document.insertForm.elements[element_name].value = key;
+ if (opener.document.insertForm.elements[null_name]) {
+ opener.document.insertForm.elements[null_name].checked = false;
+ }
+ self.close();
+ return false;
+ } else if (opener.document.insertForm.elements[element_name_alt]) {
+ // Search form
+ opener.document.insertForm.elements[element_name_alt].value = key;
+ self.close();
+ return false;
+ }
+ }
+
+ alert('$error');
+}
+EOC;
+
+ return $code;
+}
+
+/**
+ * Function to get formatted error message for javascript
+ *
+ * @return string
+ */
+function PMA_getJsError()
+{
+ return PMA_jsFormat(
+ __(
+ 'The target browser window could not be updated. '
+ . 'Maybe you have closed the parent window, or '
+ . 'your browser\'s security settings are '
+ . 'configured to block cross-window updates.'
+ )
+ );
+}
+
+/**
+ * Function to get the field key
+ *
+ * @return string
+ */
+function PMA_getFieldKey()
+{
+ if (! isset($_REQUEST['fieldkey']) || ! is_numeric($_REQUEST['fieldkey'])) {
+ $fieldkey = 0;
+ } else {
+ $fieldkey = $_REQUEST['fieldkey'];
+ }
+
+ return $fieldkey;
+}
+
+/**
+ * Function to get the element name
+ *
+ * @return string
+ */
+function PMA_getElementName()
+{
+ // When coming from Table/Zoom search
+ if (isset($_REQUEST['fromsearch'])) {
+ // In table or zoom search, input fields are named "criteriaValues"
+ $element_name = " var field = 'criteriaValues';\n";
+ } else {
+ // In insert/edit, input fields are named "fields"
+ $element_name = " var field = 'fields';\n";
+ }
+
+ if (isset($_REQUEST['rownumber'])) {
+ $element_name .= " var element_name = field + '[multi_edit]["
+ . htmlspecialchars($_REQUEST['rownumber']) . "][' + fieldmd5 + ']';\n"
+ . " var null_name = field_null + '[multi_edit]["
+ . htmlspecialchars($_REQUEST['rownumber']) . "][' + fieldmd5 + ']';\n";
+ } else {
+ $element_name .= "var element_name = field + '[]'";
+ }
+
+ return $element_name;
+}
+
+/**
+ * Function to get html for show all case
+ *
+ * @param array $foreignData foreign data
+ *
+ * @return string
+ */
+function PMA_getHtmlForShowAll($foreignData)
+{
+ $showall = '';
+ if (is_array($foreignData['disp_row'])) {
+ if ($GLOBALS['cfg']['ShowAll']
+ && ($foreignData['the_total'] > $GLOBALS['cfg']['MaxRows'])
+ ) {
+ $showall = '<input type="submit" name="foreign_navig" value="'
+ . __('Show all') . '" />';
+ }
+ }
+
+ return $showall;
+}
+
+/**
+ * Function to get html for the goto page option
+ *
+ * @param array $foreignData foreign data
+ *
+ * @return string
+ */
+function PMA_getHtmlForGotoPage($foreignData)
+{
+ $gotopage = '';
+ isset($_REQUEST['pos']) ? $pos = $_REQUEST['pos'] : $pos = 0;
+ if (is_array($foreignData['disp_row'])) {
+ $session_max_rows = $GLOBALS['cfg']['MaxRows'];
+ $pageNow = @floor($pos / $session_max_rows) + 1;
+ $nbTotalPage = @ceil($foreignData['the_total'] / $session_max_rows);
+
+ if ($foreignData['the_total'] > $GLOBALS['cfg']['MaxRows']) {
+ $gotopage = PMA_Util::pageselector(
+ 'pos',
+ $session_max_rows,
+ $pageNow,
+ $nbTotalPage,
+ 200,
+ 5,
+ 5,
+ 20,
+ 10,
+ __('Page number:')
+ );
+ }
+ }
+ return $gotopage;
+}
+
+/**
+ * Function to get foreign limit
+ *
+ * @param string $foreign_navig foreign navigation
+ *
+ * @return string
+ */
+function PMA_getForeignLimit($foreign_navig)
+{
+ if (isset($foreign_navig) && $foreign_navig == __('Show all')) {
+ return null;
+ }
+ isset($_REQUEST['pos']) ? $pos = $_REQUEST['pos'] : $pos = 0;
+ return 'LIMIT ' . $pos . ', ' . $GLOBALS['cfg']['MaxRows'] . ' ';
+}
+?>
diff --git a/libraries/build_html_for_db.lib.php b/libraries/build_html_for_db.lib.php
new file mode 100644
index 0000000000..e998272372
--- /dev/null
+++ b/libraries/build_html_for_db.lib.php
@@ -0,0 +1,182 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+
+/**
+ * HTML geneartor for database listing
+ *
+ * @package PhpMyAdmin
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ * Prepares the $column_order array
+ *
+ * @return array
+ */
+function PMA_getColumnOrder()
+{
+ $column_order['DEFAULT_COLLATION_NAME'] = array(
+ 'disp_name' => __('Collation'),
+ 'description_function' => 'PMA_getCollationDescr',
+ 'format' => 'string',
+ 'footer' => PMA_getServerCollation(),
+ );
+ $column_order['SCHEMA_TABLES'] = array(
+ 'disp_name' => __('Tables'),
+ 'format' => 'number',
+ 'footer' => 0,
+ );
+ $column_order['SCHEMA_TABLE_ROWS'] = array(
+ 'disp_name' => __('Rows'),
+ 'format' => 'number',
+ 'footer' => 0,
+ );
+ $column_order['SCHEMA_DATA_LENGTH'] = array(
+ 'disp_name' => __('Data'),
+ 'format' => 'byte',
+ 'footer' => 0,
+ );
+ $column_order['SCHEMA_INDEX_LENGTH'] = array(
+ 'disp_name' => __('Indexes'),
+ 'format' => 'byte',
+ 'footer' => 0,
+ );
+ $column_order['SCHEMA_LENGTH'] = array(
+ 'disp_name' => __('Total'),
+ 'format' => 'byte',
+ 'footer' => 0,
+ );
+ $column_order['SCHEMA_DATA_FREE'] = array(
+ 'disp_name' => __('Overhead'),
+ 'format' => 'byte',
+ 'footer' => 0,
+ );
+
+ return $column_order;
+}
+
+/**
+ * Builds the HTML td elements for one database to display in the list
+ * of databases from server_databases.php (which can be modified by
+ * db_create.php)
+ *
+ * @param array $current current database
+ * @param boolean $is_superuser user status
+ * @param string $url_query url query
+ * @param array $column_order column order
+ * @param array $replication_types replication types
+ * @param array $replication_info replication info
+ *
+ * @return array $column_order, $out
+ */
+function PMA_buildHtmlForDb(
+ $current, $is_superuser, $url_query,
+ $column_order, $replication_types, $replication_info
+) {
+ $out = '';
+ if ($is_superuser || $GLOBALS['cfg']['AllowUserDropDatabase']) {
+ $out .= '<td class="tool">';
+ $out .= '<input type="checkbox" name="selected_dbs[]" class="checkall" '
+ . 'title="' . htmlspecialchars($current['SCHEMA_NAME']) . '" '
+ . 'value="' . htmlspecialchars($current['SCHEMA_NAME']) . '"';
+
+ if ($GLOBALS['dbi']->isSystemSchema($current['SCHEMA_NAME'], true)) {
+ $out .= ' disabled="disabled"';
+ }
+ $out .= ' /></td>';
+ }
+ $out .= '<td class="name">'
+ . '<a href="' . $GLOBALS['cfg']['DefaultTabDatabase']
+ . '?' . $url_query . '&amp;db='
+ . urlencode($current['SCHEMA_NAME']) . '" title="'
+ . sprintf(
+ __('Jump to database'),
+ htmlspecialchars($current['SCHEMA_NAME'])
+ )
+ . '">'
+ . ' ' . htmlspecialchars($current['SCHEMA_NAME'])
+ . '</a>'
+ . '</td>';
+
+ foreach ($column_order as $stat_name => $stat) {
+ if (array_key_exists($stat_name, $current)) {
+ if (is_numeric($stat['footer'])) {
+ $column_order[$stat_name]['footer'] += $current[$stat_name];
+ }
+ if ($stat['format'] === 'byte') {
+ list($value, $unit) = PMA_Util::formatByteDown(
+ $current[$stat_name], 3, 1
+ );
+ } elseif ($stat['format'] === 'number') {
+ $value = PMA_Util::formatNumber(
+ $current[$stat_name], 0
+ );
+ } else {
+ $value = htmlentities($current[$stat_name], 0);
+ }
+ $out .= '<td class="value">';
+ if (isset($stat['description_function'])) {
+ $out .= '<dfn title="'
+ . $stat['description_function']($current[$stat_name]) . '">';
+ }
+ $out .= $value;
+ if (isset($stat['description_function'])) {
+ $out .= '</dfn>';
+ }
+ $out .= '</td>';
+ if ($stat['format'] === 'byte') {
+ $out .= '<td class="unit">' . $unit . '</td>';
+ }
+ }
+ }
+ foreach ($replication_types as $type) {
+ if ($replication_info[$type]['status']) {
+ $out .= '<td class="tool" style="text-align: center;">';
+
+ $key = array_search(
+ $current["SCHEMA_NAME"],
+ $replication_info[$type]['Ignore_DB']
+ );
+ if (strlen($key) > 0) {
+ $out .= PMA_Util::getIcon('s_cancel.png', __('Not replicated'));
+ } else {
+ $key = array_search(
+ $current["SCHEMA_NAME"], $replication_info[$type]['Do_DB']
+ );
+
+ if (strlen($key) > 0
+ || ($replication_info[$type]['Do_DB'][0] == ""
+ && count($replication_info[$type]['Do_DB']) == 1)
+ ) {
+ // if ($key != null) did not work for index "0"
+ $out .= PMA_Util::getIcon('s_success.png', __('Replicated'));
+ }
+ }
+
+ $out .= '</td>';
+ }
+ }
+
+ if ($is_superuser && !PMA_DRIZZLE) {
+ $out .= '<td class="tool">'
+ . '<a onclick="'
+ . 'PMA_commonActions.setDb(\''
+ . PMA_jsFormat($current['SCHEMA_NAME']) . '\');'
+ . '" href="server_privileges.php?' . $url_query
+ . '&amp;db=' . urlencode($current['SCHEMA_NAME'])
+ . '&amp;checkprivsdb=' . urlencode($current['SCHEMA_NAME'])
+ . '" title="'
+ . sprintf(
+ __('Check privileges for database &quot;%s&quot;.'),
+ htmlspecialchars($current['SCHEMA_NAME'])
+ )
+ . '">'
+ . ' '
+ . PMA_Util::getIcon('s_rights.png', __('Check Privileges'))
+ . '</a></td>';
+ }
+ return array($column_order, $out);
+}
+?>
diff --git a/libraries/charset_conversion.lib.php b/libraries/charset_conversion.lib.php
new file mode 100644
index 0000000000..7359dc61e1
--- /dev/null
+++ b/libraries/charset_conversion.lib.php
@@ -0,0 +1,114 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Charset conversion functions.
+ *
+ * @package PhpMyAdmin
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+define('PMA_CHARSET_NONE', 0);
+define('PMA_CHARSET_ICONV', 1);
+define('PMA_CHARSET_RECODE', 2);
+define('PMA_CHARSET_ICONV_AIX', 3);
+define('PMA_CHARSET_MB', 4);
+
+if (! isset($GLOBALS['cfg']['RecodingEngine'])) {
+ $GLOBALS['cfg']['RecodingEngine'] = '';
+}
+// Finally detect which function we will use:
+if ($GLOBALS['cfg']['RecodingEngine'] == 'iconv') {
+ if (@function_exists('iconv')) {
+ if ((@stristr(PHP_OS, 'AIX'))
+ && (@strcasecmp(ICONV_IMPL, 'unknown') == 0)
+ && (@strcasecmp(ICONV_VERSION, 'unknown') == 0)
+ ) {
+ $PMA_recoding_engine = PMA_CHARSET_ICONV_AIX;
+ } else {
+ $PMA_recoding_engine = PMA_CHARSET_ICONV;
+ }
+ } else {
+ $PMA_recoding_engine = PMA_CHARSET_NONE;
+ PMA_warnMissingExtension('iconv');
+ }
+} elseif ($GLOBALS['cfg']['RecodingEngine'] == 'recode') {
+ if (@function_exists('recode_string')) {
+ $PMA_recoding_engine = PMA_CHARSET_RECODE;
+ } else {
+ $PMA_recoding_engine = PMA_CHARSET_NONE;
+ PMA_warnMissingExtension('recode');
+ }
+} elseif ($GLOBALS['cfg']['RecodingEngine'] == 'mb') {
+ if (@function_exists('mb_convert_encoding')) {
+ $PMA_recoding_engine = PMA_CHARSET_MB;
+ } else {
+ $PMA_recoding_engine = PMA_CHARSET_NONE;
+ PMA_warnMissingExtension('mbstring');
+ }
+} elseif ($GLOBALS['cfg']['RecodingEngine'] == 'auto') {
+ if (@function_exists('iconv')) {
+ if ((@stristr(PHP_OS, 'AIX'))
+ && (@strcasecmp(ICONV_IMPL, 'unknown') == 0)
+ && (@strcasecmp(ICONV_VERSION, 'unknown') == 0)
+ ) {
+ $PMA_recoding_engine = PMA_CHARSET_ICONV_AIX;
+ } else {
+ $PMA_recoding_engine = PMA_CHARSET_ICONV;
+ }
+ } elseif (@function_exists('recode_string')) {
+ $PMA_recoding_engine = PMA_CHARSET_RECODE;
+ } elseif (@function_exists('mb_convert_encoding')) {
+ $PMA_recoding_engine = PMA_CHARSET_MB;
+ } else {
+ $PMA_recoding_engine = PMA_CHARSET_NONE;
+ }
+} else {
+ $PMA_recoding_engine = PMA_CHARSET_NONE;
+}
+
+/* Load AIX iconv wrapper if needed */
+if ($PMA_recoding_engine == PMA_CHARSET_ICONV_AIX) {
+ include_once './libraries/iconv_wrapper.lib.php';
+}
+
+/**
+ * Converts encoding of text according to parameters with detected
+ * conversion function.
+ *
+ * @param string $src_charset source charset
+ * @param string $dest_charset target charset
+ * @param string $what what to convert
+ *
+ * @return string converted text
+ *
+ * @access public
+ *
+ */
+function PMA_convertString($src_charset, $dest_charset, $what)
+{
+ if ($src_charset == $dest_charset) {
+ return $what;
+ }
+ switch ($GLOBALS['PMA_recoding_engine']) {
+ case PMA_CHARSET_RECODE:
+ return recode_string($src_charset . '..' . $dest_charset, $what);
+ case PMA_CHARSET_ICONV:
+ return iconv(
+ $src_charset, $dest_charset . $GLOBALS['cfg']['IconvExtraParams'], $what
+ );
+ case PMA_CHARSET_ICONV_AIX:
+ return PMA_convertAIXIconv(
+ $src_charset, $dest_charset . $GLOBALS['cfg']['IconvExtraParams'], $what
+ );
+ case PMA_CHARSET_MB:
+ return mb_convert_encoding(
+ $what, $dest_charset, $src_charset
+ );
+ default:
+ return $what;
+ }
+} // end of the "PMA_convertString()" function
+
+?>
diff --git a/libraries/check_user_privileges.lib.php b/libraries/check_user_privileges.lib.php
new file mode 100644
index 0000000000..22c6e3bf07
--- /dev/null
+++ b/libraries/check_user_privileges.lib.php
@@ -0,0 +1,178 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Get user's global privileges and some db-specific privileges
+ *
+ * @package PhpMyAdmin
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ *
+ */
+$GLOBALS['is_superuser'] = $GLOBALS['dbi']->isSuperuser();
+
+/**
+ * sets privilege information extracted from SHOW GRANTS result
+ *
+ * Detection for some CREATE privilege.
+ *
+ * Since MySQL 4.1.2, we can easily detect current user's grants using $userlink
+ * (no control user needed) and we don't have to try any other method for
+ * detection
+ *
+ * @todo fix to get really all privileges, not only explicitly defined for this user
+ * from MySQL manual: (http://dev.mysql.com/doc/refman/5.0/en/show-grants.html)
+ * SHOW GRANTS displays only the privileges granted explicitly to the named
+ * account. Other privileges might be available to the account, but they are not
+ * displayed. For example, if an anonymous account exists, the named account
+ * might be able to use its privileges, but SHOW GRANTS will not display them.
+ *
+ * @return void
+ */
+function PMA_analyseShowGrant()
+{
+ if (PMA_Util::cacheExists('is_create_db_priv', true)) {
+ $GLOBALS['is_create_db_priv'] = PMA_Util::cacheGet(
+ 'is_create_db_priv', true
+ );
+ $GLOBALS['is_process_priv'] = PMA_Util::cacheGet(
+ 'is_process_priv', true
+ );
+ $GLOBALS['is_reload_priv'] = PMA_Util::cacheGet(
+ 'is_reload_priv', true
+ );
+ $GLOBALS['db_to_create'] = PMA_Util::cacheGet(
+ 'db_to_create', true
+ );
+ $GLOBALS['dbs_where_create_table_allowed'] = PMA_Util::cacheGet(
+ 'dbs_where_create_table_allowed', true
+ );
+ return;
+ }
+
+ // defaults
+ $GLOBALS['is_create_db_priv'] = false;
+ $GLOBALS['is_process_priv'] = true;
+ $GLOBALS['is_reload_priv'] = false;
+ $GLOBALS['db_to_create'] = '';
+ $GLOBALS['dbs_where_create_table_allowed'] = array();
+
+ $rs_usr = $GLOBALS['dbi']->tryQuery('SHOW GRANTS');
+
+ if (! $rs_usr) {
+ return;
+ }
+
+ $re0 = '(^|(\\\\\\\\)+|[^\\\\])'; // non-escaped wildcards
+ $re1 = '(^|[^\\\\])(\\\)+'; // escaped wildcards
+
+ while ($row = $GLOBALS['dbi']->fetchRow($rs_usr)) {
+ // extract db from GRANT ... ON *.* or GRANT ... ON db.*
+ $db_name_offset = strpos($row[0], ' ON ') + 4;
+ $show_grants_dbname = substr(
+ $row[0], $db_name_offset,
+ strpos($row[0], '.', $db_name_offset) - $db_name_offset
+ );
+ $show_grants_dbname
+ = PMA_Util::unQuote($show_grants_dbname, '`');
+
+ $show_grants_str = substr($row[0], 6, (strpos($row[0], ' ON ') - 6));
+ if ($show_grants_str == 'RELOAD') {
+ $GLOBALS['is_reload_priv'] = true;
+ }
+
+ /**
+ * @todo if we find CREATE VIEW but not CREATE, do not offer
+ * the create database dialog box
+ */
+ if ($show_grants_str == 'ALL'
+ || $show_grants_str == 'ALL PRIVILEGES'
+ || $show_grants_str == 'CREATE'
+ || strpos($show_grants_str, 'CREATE,') !== false
+ ) {
+ if ($show_grants_dbname == '*') {
+ // a global CREATE privilege
+ $GLOBALS['is_create_db_priv'] = true;
+ $GLOBALS['is_reload_priv'] = true;
+ $GLOBALS['db_to_create'] = '';
+ $GLOBALS['dbs_where_create_table_allowed'][] = '*';
+ // @todo we should not break here, cause GRANT ALL *.*
+ // could be revoked by a later rule like GRANT SELECT ON db.*
+ break;
+ } else {
+ // this array may contain wildcards
+ $GLOBALS['dbs_where_create_table_allowed'][] = $show_grants_dbname;
+
+ $dbname_to_test = PMA_Util::backquote($show_grants_dbname);
+
+ if ($GLOBALS['is_create_db_priv']) {
+ // no need for any more tests if we already know this
+ continue;
+ }
+
+ // does this db exist?
+ if ((preg_match('/' . $re0 . '%|_/', $show_grants_dbname)
+ && ! preg_match('/\\\\%|\\\\_/', $show_grants_dbname))
+ || (! $GLOBALS['dbi']->tryQuery(
+ 'USE ' . preg_replace(
+ '/' . $re1 . '(%|_)/', '\\1\\3', $dbname_to_test
+ )
+ )
+ && substr($GLOBALS['dbi']->getError(), 1, 4) != 1044)
+ ) {
+ /**
+ * Do not handle the underscore wildcard
+ * (this case must be rare anyway)
+ */
+ $GLOBALS['db_to_create'] = preg_replace(
+ '/' . $re0 . '%/', '\\1',
+ $show_grants_dbname
+ );
+ $GLOBALS['db_to_create'] = preg_replace(
+ '/' . $re1 . '(%|_)/', '\\1\\3',
+ $GLOBALS['db_to_create']
+ );
+ $GLOBALS['is_create_db_priv'] = true;
+
+ /**
+ * @todo collect $GLOBALS['db_to_create'] into an array,
+ * to display a drop-down in the "Create database" dialog
+ */
+ // we don't break, we want all possible databases
+ //break;
+ } // end if
+ } // end elseif
+ } // end if
+ } // end while
+
+ $GLOBALS['dbi']->freeResult($rs_usr);
+
+ // must also cacheUnset() them in
+ // libraries/plugins/auth/AuthenticationCookie.class.php
+ PMA_Util::cacheSet('is_create_db_priv', $GLOBALS['is_create_db_priv'], true);
+ PMA_Util::cacheSet('is_process_priv', $GLOBALS['is_process_priv'], true);
+ PMA_Util::cacheSet('is_reload_priv', $GLOBALS['is_reload_priv'], true);
+ PMA_Util::cacheSet('db_to_create', $GLOBALS['db_to_create'], true);
+ PMA_Util::cacheSet(
+ 'dbs_where_create_table_allowed',
+ $GLOBALS['dbs_where_create_table_allowed'],
+ true
+ );
+} // end function
+
+if (!PMA_DRIZZLE) {
+ PMA_analyseShowGrant();
+} else {
+ // todo: for simple_user_policy only database with user's login can be created
+ // (unless logged in as root)
+ $GLOBALS['is_create_db_priv'] = $GLOBALS['is_superuser'];
+ $GLOBALS['is_process_priv'] = false;
+ $GLOBALS['is_reload_priv'] = false;
+ $GLOBALS['db_to_create'] = '';
+ $GLOBALS['dbs_where_create_table_allowed'] = array('*');
+}
+
+?>
diff --git a/libraries/cleanup.lib.php b/libraries/cleanup.lib.php
new file mode 100644
index 0000000000..2874f5a0e2
--- /dev/null
+++ b/libraries/cleanup.lib.php
@@ -0,0 +1,50 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Functions for cleanup of user input.
+ *
+ * @package PhpMyAdmin
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ * Removes all variables from request except whitelisted ones.
+ *
+ * @param string &$whitelist list of variables to allow
+ *
+ * @return void
+ * @access public
+ */
+function PMA_removeRequestVars(&$whitelist)
+{
+ // do not check only $_REQUEST because it could have been overwritten
+ // and use type casting because the variables could have become
+ // strings
+ $keys = array_keys(
+ array_merge((array)$_REQUEST, (array)$_GET, (array)$_POST, (array)$_COOKIE)
+ );
+
+ foreach ($keys as $key) {
+ if (! in_array($key, $whitelist)) {
+ unset($_REQUEST[$key], $_GET[$key], $_POST[$key], $GLOBALS[$key]);
+ } else {
+ // allowed stuff could be compromised so escape it
+ // we require it to be a string
+ if (isset($_REQUEST[$key]) && ! is_string($_REQUEST[$key])) {
+ unset($_REQUEST[$key]);
+ }
+ if (isset($_POST[$key]) && ! is_string($_POST[$key])) {
+ unset($_POST[$key]);
+ }
+ if (isset($_COOKIE[$key]) && ! is_string($_COOKIE[$key])) {
+ unset($_COOKIE[$key]);
+ }
+ if (isset($_GET[$key]) && ! is_string($_GET[$key])) {
+ unset($_GET[$key]);
+ }
+ }
+ }
+}
+?>
diff --git a/libraries/common.inc.php b/libraries/common.inc.php
new file mode 100644
index 0000000000..3e6fe729b0
--- /dev/null
+++ b/libraries/common.inc.php
@@ -0,0 +1,1150 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Misc stuff and REQUIRED by ALL the scripts.
+ * MUST be included by every script
+ *
+ * Among other things, it contains the advanced authentication work.
+ *
+ * Order of sections for common.inc.php:
+ *
+ * the authentication libraries must be before the connection to db
+ *
+ * ... so the required order is:
+ *
+ * LABEL_variables_init
+ * - initialize some variables always needed
+ * LABEL_parsing_config_file
+ * - parsing of the configuration file
+ * LABEL_loading_language_file
+ * - loading language file
+ * LABEL_setup_servers
+ * - check and setup configured servers
+ * LABEL_theme_setup
+ * - setting up themes
+ *
+ * - load of MySQL extension (if necessary)
+ * - loading of an authentication library
+ * - db connection
+ * - authentication work
+ *
+ * @package PhpMyAdmin
+ */
+
+/**
+ * block attempts to directly run this script
+ */
+if (getcwd() == dirname(__FILE__)) {
+ die('Attack stopped');
+}
+
+/**
+ * Minimum PHP version; can't call PMA_fatalError() which uses a
+ * PHP 5 function, so cannot easily localize this message.
+ */
+if (version_compare(PHP_VERSION, '5.3.0', 'lt')) {
+ die('PHP 5.3+ is required');
+}
+
+/**
+ * for verification in all procedural scripts under libraries
+ */
+define('PHPMYADMIN', true);
+
+/**
+ * the error handler
+ */
+require './libraries/Error_Handler.class.php';
+
+/**
+ * initialize the error handler
+ */
+$GLOBALS['error_handler'] = new PMA_Error_Handler();
+$cfg['Error_Handler']['display'] = true;
+
+/**
+ * This setting was removed in PHP 5.4. But at this point PMA_PHP_INT_VERSION
+ * is not yet defined so we use another way to find out the PHP version.
+ */
+if (version_compare(phpversion(), '5.4', 'lt')) {
+ /**
+ * Avoid problems with magic_quotes_runtime
+ */
+ @ini_set('magic_quotes_runtime', false);
+}
+
+/**
+ * core functions
+ */
+require './libraries/core.lib.php';
+
+/**
+ * Input sanitizing
+ */
+require './libraries/sanitizing.lib.php';
+
+/**
+ * Warning about mbstring.
+ */
+if (! function_exists('mb_detect_encoding')) {
+ PMA_warnMissingExtension('mbstring', $fatal = true);
+}
+
+/**
+ * the PMA_Theme class
+ */
+require './libraries/Theme.class.php';
+
+/**
+ * the PMA_Theme_Manager class
+ */
+require './libraries/Theme_Manager.class.php';
+
+/**
+ * the PMA_Config class
+ */
+require './libraries/Config.class.php';
+
+/**
+ * the relation lib, tracker needs it
+ */
+require './libraries/relation.lib.php';
+
+/**
+ * the PMA_Tracker class
+ */
+require './libraries/Tracker.class.php';
+
+/**
+ * the PMA_Table class
+ */
+require './libraries/Table.class.php';
+
+/**
+ * the PMA_Types class
+ */
+require './libraries/Types.class.php';
+
+if (! defined('PMA_MINIMUM_COMMON')) {
+ /**
+ * common functions
+ */
+ include_once './libraries/Util.class.php';
+
+ /**
+ * JavaScript escaping.
+ */
+ include_once './libraries/js_escape.lib.php';
+
+ /**
+ * Include URL/hidden inputs generating.
+ */
+ include_once './libraries/url_generating.lib.php';
+
+ /**
+ * Used to generate the page
+ */
+ include_once 'libraries/Response.class.php';
+}
+
+/******************************************************************************/
+/* start procedural code label_start_procedural */
+
+/**
+ * PATH_INFO could be compromised if set, so remove it from PHP_SELF
+ * and provide a clean PHP_SELF here
+ */
+$PMA_PHP_SELF = PMA_getenv('PHP_SELF');
+$_PATH_INFO = PMA_getenv('PATH_INFO');
+if (! empty($_PATH_INFO) && ! empty($PMA_PHP_SELF)) {
+ $path_info_pos = strrpos($PMA_PHP_SELF, $_PATH_INFO);
+ if ($path_info_pos + strlen($_PATH_INFO) === strlen($PMA_PHP_SELF)) {
+ $PMA_PHP_SELF = substr($PMA_PHP_SELF, 0, $path_info_pos);
+ }
+}
+$PMA_PHP_SELF = htmlspecialchars($PMA_PHP_SELF);
+
+
+/**
+ * just to be sure there was no import (registering) before here
+ * we empty the global space (but avoid unsetting $variables_list
+ * and $key in the foreach (), we still need them!)
+ */
+$variables_whitelist = array (
+ 'GLOBALS',
+ '_SERVER',
+ '_GET',
+ '_POST',
+ '_REQUEST',
+ '_FILES',
+ '_ENV',
+ '_COOKIE',
+ '_SESSION',
+ 'error_handler',
+ 'PMA_PHP_SELF',
+ 'variables_whitelist',
+ 'key'
+);
+
+foreach (get_defined_vars() as $key => $value) {
+ if (! in_array($key, $variables_whitelist)) {
+ unset($$key);
+ }
+}
+unset($key, $value, $variables_whitelist);
+
+
+/**
+ * Subforms - some functions need to be called by form, cause of the limited URL
+ * length, but if this functions inside another form you cannot just open a new
+ * form - so phpMyAdmin uses 'arrays' inside this form
+ *
+ * <code>
+ * <form ...>
+ * ... main form elments ...
+ * <input type="hidden" name="subform[action1][id]" value="1" />
+ * ... other subform data ...
+ * <input type="submit" name="usesubform[action1]" value="do action1" />
+ * ... other subforms ...
+ * <input type="hidden" name="subform[actionX][id]" value="X" />
+ * ... other subform data ...
+ * <input type="submit" name="usesubform[actionX]" value="do actionX" />
+ * ... main form elments ...
+ * <input type="submit" name="main_action" value="submit form" />
+ * </form>
+ * </code>
+ *
+ * so we now check if a subform is submitted
+ */
+$__redirect = null;
+if (isset($_POST['usesubform'])) {
+ // if a subform is present and should be used
+ // the rest of the form is deprecated
+ $subform_id = key($_POST['usesubform']);
+ $subform = $_POST['subform'][$subform_id];
+ $_POST = $subform;
+ $_REQUEST = $subform;
+ /**
+ * some subforms need another page than the main form, so we will just
+ * include this page at the end of this script - we use $__redirect to
+ * track this
+ */
+ if (isset($_POST['redirect'])
+ && $_POST['redirect'] != basename($PMA_PHP_SELF)
+ ) {
+ $__redirect = $_POST['redirect'];
+ unset($_POST['redirect']);
+ }
+ unset($subform_id, $subform);
+} else {
+ // Note: here we overwrite $_REQUEST so that it does not contain cookies,
+ // because another application for the same domain could have set
+ // a cookie (with a compatible path) that overrides a variable
+ // we expect from GET or POST.
+ // We'll refer to cookies explicitly with the $_COOKIE syntax.
+ $_REQUEST = array_merge($_GET, $_POST);
+}
+// end check if a subform is submitted
+
+/**
+ * This setting was removed in PHP 5.4. But at this point PMA_PHP_INT_VERSION
+ * is not yet defined so we use another way to find out the PHP version.
+ */
+if (version_compare(phpversion(), '5.4', 'lt')) {
+ // remove quotes added by PHP
+ if (function_exists('get_magic_quotes_gpc') && get_magic_quotes_gpc()) {
+ PMA_arrayWalkRecursive($_GET, 'stripslashes', true);
+ PMA_arrayWalkRecursive($_POST, 'stripslashes', true);
+ PMA_arrayWalkRecursive($_COOKIE, 'stripslashes', true);
+ PMA_arrayWalkRecursive($_REQUEST, 'stripslashes', true);
+ }
+}
+
+/**
+ * check timezone setting
+ * this could produce an E_STRICT - but only once,
+ * if not done here it will produce E_STRICT on every date/time function
+ * (starting with PHP 5.3, this code can produce E_WARNING rather than
+ * E_STRICT)
+ *
+ */
+date_default_timezone_set(@date_default_timezone_get());
+
+/******************************************************************************/
+/* parsing configuration file LABEL_parsing_config_file */
+
+/**
+ * We really need this one!
+ */
+if (! function_exists('preg_replace')) {
+ PMA_warnMissingExtension('pcre', true);
+}
+
+/**
+ * JSON is required in several places.
+ */
+if (! function_exists('json_encode')) {
+ PMA_warnMissingExtension('json', true);
+}
+
+/**
+ * @global PMA_Config $GLOBALS['PMA_Config']
+ * force reading of config file, because we removed sensitive values
+ * in the previous iteration
+ */
+$GLOBALS['PMA_Config'] = new PMA_Config(CONFIG_FILE);
+
+if (!defined('PMA_MINIMUM_COMMON')) {
+ $GLOBALS['PMA_Config']->checkPmaAbsoluteUri();
+}
+
+/**
+ * BC - enable backward compatibility
+ * exports all configuration settings into $GLOBALS ($GLOBALS['cfg'])
+ */
+$GLOBALS['PMA_Config']->enableBc();
+
+/**
+ * clean cookies on upgrade
+ * when changing something related to PMA cookies, increment the cookie version
+ */
+$pma_cookie_version = 4;
+if (isset($_COOKIE)
+ && (isset($_COOKIE['pmaCookieVer'])
+ && $_COOKIE['pmaCookieVer'] < $pma_cookie_version)
+) {
+ // delete all cookies
+ foreach ($_COOKIE as $cookie_name => $tmp) {
+ $GLOBALS['PMA_Config']->removeCookie($cookie_name);
+ }
+ $_COOKIE = array();
+ $GLOBALS['PMA_Config']->setCookie('pmaCookieVer', $pma_cookie_version);
+}
+
+
+/**
+ * check HTTPS connection
+ */
+if ($GLOBALS['PMA_Config']->get('ForceSSL')
+ && ! $GLOBALS['PMA_Config']->get('is_https')
+) {
+ // grab SSL URL
+ $url = $GLOBALS['PMA_Config']->getSSLUri();
+ // Actually redirect
+ PMA_sendHeaderLocation($url . PMA_URL_getCommon($_GET, 'text'));
+ // delete the current session, otherwise we get problems (see bug #2397877)
+ $GLOBALS['PMA_Config']->removeCookie($GLOBALS['session_name']);
+ exit;
+}
+
+
+/**
+ * include session handling after the globals, to prevent overwriting
+ */
+require './libraries/session.inc.php';
+
+/**
+ * init some variables LABEL_variables_init
+ */
+
+/**
+ * holds parameters to be passed to next page
+ * @global array $GLOBALS['url_params']
+ */
+$GLOBALS['url_params'] = array();
+
+/**
+ * the whitelist for $GLOBALS['goto']
+ * @global array $goto_whitelist
+ */
+$goto_whitelist = array(
+ //'browse_foreigners.php',
+ //'changelog.php',
+ //'chk_rel.php',
+ 'db_create.php',
+ 'db_datadict.php',
+ 'db_sql.php',
+ 'db_events.php',
+ 'db_export.php',
+ 'db_importdocsql.php',
+ 'db_qbe.php',
+ 'db_structure.php',
+ 'db_import.php',
+ 'db_operations.php',
+ 'db_printview.php',
+ 'db_search.php',
+ 'db_routines.php',
+ 'export.php',
+ 'import.php',
+ //'index.php',
+ //'navigation.php',
+ //'license.php',
+ 'index.php',
+ 'pdf_pages.php',
+ 'pdf_schema.php',
+ //'phpinfo.php',
+ 'querywindow.php',
+ 'server_binlog.php',
+ 'server_collations.php',
+ 'server_databases.php',
+ 'server_engines.php',
+ 'server_export.php',
+ 'server_import.php',
+ 'server_privileges.php',
+ 'server_sql.php',
+ 'server_status.php',
+ 'server_status_advisor.php',
+ 'server_status_monitor.php',
+ 'server_status_queries.php',
+ 'server_status_variables.php',
+ 'server_variables.php',
+ 'sql.php',
+ 'tbl_addfield.php',
+ 'tbl_change.php',
+ 'tbl_create.php',
+ 'tbl_import.php',
+ 'tbl_indexes.php',
+ 'tbl_move_copy.php',
+ 'tbl_printview.php',
+ 'tbl_sql.php',
+ 'tbl_export.php',
+ 'tbl_operations.php',
+ 'tbl_structure.php',
+ 'tbl_relation.php',
+ 'tbl_replace.php',
+ 'tbl_row_action.php',
+ 'tbl_select.php',
+ 'tbl_zoom_select.php',
+ //'themes.php',
+ 'transformation_overview.php',
+ 'transformation_wrapper.php',
+ 'user_password.php',
+);
+
+/**
+ * check $__redirect against whitelist
+ */
+if (! PMA_checkPageValidity($__redirect, $goto_whitelist)) {
+ $__redirect = null;
+}
+
+/**
+ * holds page that should be displayed
+ * @global string $GLOBALS['goto']
+ */
+$GLOBALS['goto'] = '';
+// Security fix: disallow accessing serious server files via "?goto="
+if (PMA_checkPageValidity($_REQUEST['goto'], $goto_whitelist)) {
+ $GLOBALS['goto'] = $_REQUEST['goto'];
+ $GLOBALS['url_params']['goto'] = $_REQUEST['goto'];
+} else {
+ unset($_REQUEST['goto'], $_GET['goto'], $_POST['goto'], $_COOKIE['goto']);
+}
+
+/**
+ * returning page
+ * @global string $GLOBALS['back']
+ */
+if (PMA_checkPageValidity($_REQUEST['back'], $goto_whitelist)) {
+ $GLOBALS['back'] = $_REQUEST['back'];
+} else {
+ unset($_REQUEST['back'], $_GET['back'], $_POST['back'], $_COOKIE['back']);
+}
+
+/**
+ * Check whether user supplied token is valid, if not remove any possibly
+ * dangerous stuff from request.
+ *
+ * remember that some objects in the session with session_start and __wakeup()
+ * could access this variables before we reach this point
+ * f.e. PMA_Config: fontsize
+ *
+ * @todo variables should be handled by their respective owners (objects)
+ * f.e. lang, server, collation_connection in PMA_Config
+ */
+$token_mismatch = true;
+if (PMA_isValid($_REQUEST['token'])) {
+ $token_mismatch = ($_SESSION[' PMA_token '] != $_REQUEST['token']);
+}
+
+if ($token_mismatch) {
+ /**
+ * List of parameters which are allowed from unsafe source
+ */
+ $allow_list = array(
+ /* needed for direct access, see FAQ 1.34
+ * also, server needed for cookie login screen (multi-server)
+ */
+ 'server', 'db', 'table', 'target', 'lang',
+ /* Session ID */
+ 'phpMyAdmin',
+ /* Cookie preferences */
+ 'pma_lang', 'pma_collation_connection',
+ /* Possible login form */
+ 'pma_servername', 'pma_username', 'pma_password',
+ 'recaptcha_challenge_field', 'recaptcha_response_field',
+ /* Needed to send the correct reply */
+ 'ajax_request',
+ /* Permit to log out even if there is a token mismatch */
+ 'old_usr'
+ );
+ /**
+ * Allow changing themes in test/theme.php
+ */
+ if (defined('PMA_TEST_THEME')) {
+ $allow_list[] = 'set_theme';
+ }
+ /**
+ * Require cleanup functions
+ */
+ include './libraries/cleanup.lib.php';
+ /**
+ * Do actual cleanup
+ */
+ PMA_removeRequestVars($allow_list);
+
+}
+
+
+/**
+ * current selected database
+ * @global string $GLOBALS['db']
+ */
+$GLOBALS['db'] = '';
+if (PMA_isValid($_REQUEST['db'])) {
+ // can we strip tags from this?
+ // only \ and / is not allowed in db names for MySQL
+ $GLOBALS['db'] = $_REQUEST['db'];
+ $GLOBALS['url_params']['db'] = $GLOBALS['db'];
+}
+
+/**
+ * current selected table
+ * @global string $GLOBALS['table']
+ */
+$GLOBALS['table'] = '';
+if (PMA_isValid($_REQUEST['table'])) {
+ // can we strip tags from this?
+ // only \ and / is not allowed in table names for MySQL
+ $GLOBALS['table'] = $_REQUEST['table'];
+ $GLOBALS['url_params']['table'] = $GLOBALS['table'];
+}
+
+/**
+ * Store currently selected recent table.
+ * Affect $GLOBALS['db'] and $GLOBALS['table']
+ */
+if (PMA_isValid($_REQUEST['selected_recent_table'])) {
+ $recent_table = json_decode($_REQUEST['selected_recent_table'], true);
+ $GLOBALS['db'] = $recent_table['db'];
+ $GLOBALS['url_params']['db'] = $GLOBALS['db'];
+ $GLOBALS['table'] = $recent_table['table'];
+ $GLOBALS['url_params']['table'] = $GLOBALS['table'];
+}
+
+/**
+ * SQL query to be executed
+ * @global string $GLOBALS['sql_query']
+ */
+$GLOBALS['sql_query'] = '';
+if (PMA_isValid($_REQUEST['sql_query'])) {
+ $GLOBALS['sql_query'] = $_REQUEST['sql_query'];
+}
+
+//$_REQUEST['set_theme'] // checked later in this file LABEL_theme_setup
+//$_REQUEST['server']; // checked later in this file
+//$_REQUEST['lang']; // checked by LABEL_loading_language_file
+
+/******************************************************************************/
+/* loading language file LABEL_loading_language_file */
+
+/**
+ * lang detection is done here
+ */
+require './libraries/select_lang.lib.php';
+
+// Defines the cell alignment values depending on text direction
+if ($GLOBALS['text_dir'] == 'ltr') {
+ $GLOBALS['cell_align_left'] = 'left';
+ $GLOBALS['cell_align_right'] = 'right';
+} else {
+ $GLOBALS['cell_align_left'] = 'right';
+ $GLOBALS['cell_align_right'] = 'left';
+}
+
+/**
+ * check for errors occurred while loading configuration
+ * this check is done here after loading language files to present errors in locale
+ */
+$GLOBALS['PMA_Config']->checkPermissions();
+
+if ($GLOBALS['PMA_Config']->error_config_file) {
+ $error = '[strong]' . __('Failed to read configuration file') . '[/strong]'
+ . '[br][br]'
+ . __('This usually means there is a syntax error in it, please check any errors shown below.')
+ . '[br][br]'
+ . '[conferr]';
+ trigger_error($error, E_USER_ERROR);
+}
+if ($GLOBALS['PMA_Config']->error_config_default_file) {
+ $error = sprintf(
+ __('Could not load default configuration from: %1$s'),
+ $GLOBALS['PMA_Config']->default_source
+ );
+ trigger_error($error, E_USER_ERROR);
+}
+if ($GLOBALS['PMA_Config']->error_pma_uri) {
+ trigger_error(
+ __('The [code]$cfg[\'PmaAbsoluteUri\'][/code] directive MUST be set in your configuration file!'),
+ E_USER_ERROR
+ );
+}
+
+
+/******************************************************************************/
+/* setup servers LABEL_setup_servers */
+
+/**
+ * current server
+ * @global integer $GLOBALS['server']
+ */
+$GLOBALS['server'] = 0;
+
+/**
+ * Servers array fixups.
+ * $default_server comes from PMA_Config::enableBc()
+ * @todo merge into PMA_Config
+ */
+// Do we have some server?
+if (! isset($cfg['Servers']) || count($cfg['Servers']) == 0) {
+ // No server => create one with defaults
+ $cfg['Servers'] = array(1 => $default_server);
+} else {
+ // We have server(s) => apply default configuration
+ $new_servers = array();
+
+ foreach ($cfg['Servers'] as $server_index => $each_server) {
+
+ // Detect wrong configuration
+ if (!is_int($server_index) || $server_index < 1) {
+ trigger_error(
+ sprintf(__('Invalid server index: %s'), $server_index),
+ E_USER_ERROR
+ );
+ }
+
+ $each_server = array_merge($default_server, $each_server);
+
+ // Don't use servers with no hostname
+ if ($each_server['connect_type'] == 'tcp' && empty($each_server['host'])) {
+ trigger_error(
+ sprintf(
+ __('Invalid hostname for server %1$s. Please review your configuration.'),
+ $server_index
+ ),
+ E_USER_ERROR
+ );
+ }
+
+ // Final solution to bug #582890
+ // If we are using a socket connection
+ // and there is nothing in the verbose server name
+ // or the host field, then generate a name for the server
+ // in the form of "Server 2", localized of course!
+ if ($each_server['connect_type'] == 'socket'
+ && empty($each_server['host'])
+ && empty($each_server['verbose'])
+ ) {
+ $each_server['verbose'] = sprintf(__('Server %d'), $server_index);
+ }
+
+ $new_servers[$server_index] = $each_server;
+ }
+ $cfg['Servers'] = $new_servers;
+ unset($new_servers, $server_index, $each_server);
+}
+
+// Cleanup
+unset($default_server);
+
+
+/******************************************************************************/
+/* setup themes LABEL_theme_setup */
+
+/**
+ * @global PMA_Theme_Manager $_SESSION['PMA_Theme_Manager']
+ */
+if (! isset($_SESSION['PMA_Theme_Manager'])) {
+ $_SESSION['PMA_Theme_Manager'] = new PMA_Theme_Manager;
+} else {
+ /**
+ * @todo move all __wakeup() functionality into session.inc.php
+ */
+ $_SESSION['PMA_Theme_Manager']->checkConfig();
+}
+
+// for the theme per server feature
+if (isset($_REQUEST['server']) && ! isset($_REQUEST['set_theme'])) {
+ $GLOBALS['server'] = $_REQUEST['server'];
+ $tmp = $_SESSION['PMA_Theme_Manager']->getThemeCookie();
+ if (empty($tmp)) {
+ $tmp = $_SESSION['PMA_Theme_Manager']->theme_default;
+ }
+ $_SESSION['PMA_Theme_Manager']->setActiveTheme($tmp);
+ unset($tmp);
+}
+/**
+ * @todo move into PMA_Theme_Manager::__wakeup()
+ */
+if (isset($_REQUEST['set_theme'])) {
+ // if user selected a theme
+ $_SESSION['PMA_Theme_Manager']->setActiveTheme($_REQUEST['set_theme']);
+}
+
+/**
+ * the theme object
+ * @global PMA_Theme $_SESSION['PMA_Theme']
+ */
+$_SESSION['PMA_Theme'] = $_SESSION['PMA_Theme_Manager']->theme;
+
+// BC
+/**
+ * the active theme
+ * @global string $GLOBALS['theme']
+ */
+$GLOBALS['theme'] = $_SESSION['PMA_Theme']->getName();
+/**
+ * the theme path
+ * @global string $GLOBALS['pmaThemePath']
+ */
+$GLOBALS['pmaThemePath'] = $_SESSION['PMA_Theme']->getPath();
+/**
+ * the theme image path
+ * @global string $GLOBALS['pmaThemeImage']
+ */
+$GLOBALS['pmaThemeImage'] = $_SESSION['PMA_Theme']->getImgPath();
+
+/**
+ * load layout file if exists
+ */
+if (@file_exists($_SESSION['PMA_Theme']->getLayoutFile())) {
+ include $_SESSION['PMA_Theme']->getLayoutFile();
+}
+
+if (! defined('PMA_MINIMUM_COMMON')) {
+ /**
+ * Character set conversion.
+ */
+ include_once './libraries/charset_conversion.lib.php';
+
+ /**
+ * String handling
+ */
+ include_once './libraries/string.inc.php';
+
+ /**
+ * Lookup server by name
+ * (see FAQ 4.8)
+ */
+ if (! empty($_REQUEST['server'])
+ && is_string($_REQUEST['server'])
+ && ! is_numeric($_REQUEST['server'])
+ ) {
+ foreach ($cfg['Servers'] as $i => $server) {
+ if ($server['host'] == $_REQUEST['server']
+ || $server['verbose'] == $_REQUEST['server']
+ || $PMA_String->strtolower($server['verbose']) == $PMA_String->strtolower($_REQUEST['server'])
+ || md5($PMA_String->strtolower($server['verbose'])) == $PMA_String->strtolower($_REQUEST['server'])
+ ) {
+ $_REQUEST['server'] = $i;
+ break;
+ }
+ }
+ if (is_string($_REQUEST['server'])) {
+ unset($_REQUEST['server']);
+ }
+ unset($i);
+ }
+
+ /**
+ * If no server is selected, make sure that $cfg['Server'] is empty (so
+ * that nothing will work), and skip server authentication.
+ * We do NOT exit here, but continue on without logging into any server.
+ * This way, the welcome page will still come up (with no server info) and
+ * present a choice of servers in the case that there are multiple servers
+ * and '$cfg['ServerDefault'] = 0' is set.
+ */
+
+ if (isset($_REQUEST['server'])
+ && (is_string($_REQUEST['server']) || is_numeric($_REQUEST['server']))
+ && ! empty($_REQUEST['server'])
+ && ! empty($cfg['Servers'][$_REQUEST['server']])
+ ) {
+ $GLOBALS['server'] = $_REQUEST['server'];
+ $cfg['Server'] = $cfg['Servers'][$GLOBALS['server']];
+ } else {
+ if (!empty($cfg['Servers'][$cfg['ServerDefault']])) {
+ $GLOBALS['server'] = $cfg['ServerDefault'];
+ $cfg['Server'] = $cfg['Servers'][$GLOBALS['server']];
+ } else {
+ $GLOBALS['server'] = 0;
+ $cfg['Server'] = array();
+ }
+ }
+ $GLOBALS['url_params']['server'] = $GLOBALS['server'];
+
+ /**
+ * Kanji encoding convert feature appended by Y.Kawada (2002/2/20)
+ */
+ if (function_exists('mb_convert_encoding')
+ && $lang == 'ja'
+ ) {
+ include_once './libraries/kanji-encoding.lib.php';
+ } // end if
+
+ /**
+ * save some settings in cookies
+ * @todo should be done in PMA_Config
+ */
+ $GLOBALS['PMA_Config']->setCookie('pma_lang', $GLOBALS['lang']);
+ if (isset($GLOBALS['collation_connection'])) {
+ $GLOBALS['PMA_Config']->setCookie(
+ 'pma_collation_connection',
+ $GLOBALS['collation_connection']
+ );
+ }
+
+ $_SESSION['PMA_Theme_Manager']->setThemeCookie();
+
+ if (! empty($cfg['Server'])) {
+
+ /**
+ * Loads the proper database interface for this server
+ */
+ include_once './libraries/database_interface.inc.php';
+
+ include_once './libraries/logging.lib.php';
+
+ // get LoginCookieValidity from preferences cache
+ // no generic solution for loading preferences from cache as some settings
+ // need to be kept for processing in PMA_Config::loadUserPreferences()
+ $cache_key = 'server_' . $GLOBALS['server'];
+ if (isset($_SESSION['cache'][$cache_key]['userprefs']['LoginCookieValidity'])) {
+ $value = $_SESSION['cache'][$cache_key]['userprefs']['LoginCookieValidity'];
+ $GLOBALS['PMA_Config']->set('LoginCookieValidity', $value);
+ $GLOBALS['cfg']['LoginCookieValidity'] = $value;
+ unset($value);
+ }
+ unset($cache_key);
+
+ // Gets the authentication library that fits the $cfg['Server'] settings
+ // and run authentication
+
+ // to allow HTTP or http
+ $cfg['Server']['auth_type'] = strtolower($cfg['Server']['auth_type']);
+
+ /**
+ * the required auth type plugin
+ */
+ $auth_class = "Authentication" . ucfirst($cfg['Server']['auth_type']);
+ if (! file_exists(
+ './libraries/plugins/auth/'
+ . $auth_class . '.class.php'
+ )) {
+ PMA_fatalError(
+ __('Invalid authentication method set in configuration:')
+ . ' ' . $cfg['Server']['auth_type']
+ );
+ }
+ include_once './libraries/plugins/auth/' . $auth_class . '.class.php';
+ // todo: add plugin manager
+ $plugin_manager = null;
+ $auth_plugin = new $auth_class($plugin_manager);
+
+ if (! $auth_plugin->authCheck()) {
+ /* Force generating of new session on login */
+ PMA_secureSession();
+ $auth_plugin->auth();
+ } else {
+ $auth_plugin->authSetUser();
+ }
+
+ // Check IP-based Allow/Deny rules as soon as possible to reject the
+ // user
+ // Based on mod_access in Apache:
+ // http://cvs.apache.org/viewcvs.cgi/httpd-2.0/modules/aaa/mod_access.c?rev=1.37&content-type=text/vnd.viewcvs-markup
+ // Look at: "static int check_dir_access(request_rec *r)"
+ if (isset($cfg['Server']['AllowDeny'])
+ && isset($cfg['Server']['AllowDeny']['order'])
+ ) {
+
+ /**
+ * ip based access library
+ */
+ include_once './libraries/ip_allow_deny.lib.php';
+
+ $allowDeny_forbidden = false; // default
+ if ($cfg['Server']['AllowDeny']['order'] == 'allow,deny') {
+ $allowDeny_forbidden = true;
+ if (PMA_allowDeny('allow')) {
+ $allowDeny_forbidden = false;
+ }
+ if (PMA_allowDeny('deny')) {
+ $allowDeny_forbidden = true;
+ }
+ } elseif ($cfg['Server']['AllowDeny']['order'] == 'deny,allow') {
+ if (PMA_allowDeny('deny')) {
+ $allowDeny_forbidden = true;
+ }
+ if (PMA_allowDeny('allow')) {
+ $allowDeny_forbidden = false;
+ }
+ } elseif ($cfg['Server']['AllowDeny']['order'] == 'explicit') {
+ if (PMA_allowDeny('allow') && ! PMA_allowDeny('deny')) {
+ $allowDeny_forbidden = false;
+ } else {
+ $allowDeny_forbidden = true;
+ }
+ } // end if ... elseif ... elseif
+
+ // Ejects the user if banished
+ if ($allowDeny_forbidden) {
+ PMA_logUser($cfg['Server']['user'], 'allow-denied');
+ $auth_plugin->authFails();
+ }
+ } // end if
+
+ // is root allowed?
+ if (! $cfg['Server']['AllowRoot'] && $cfg['Server']['user'] == 'root') {
+ $allowDeny_forbidden = true;
+ PMA_logUser($cfg['Server']['user'], 'root-denied');
+ $auth_plugin->authFails();
+ }
+
+ // is a login without password allowed?
+ if (! $cfg['Server']['AllowNoPassword']
+ && $cfg['Server']['password'] == ''
+ ) {
+ $login_without_password_is_forbidden = true;
+ PMA_logUser($cfg['Server']['user'], 'empty-denied');
+ $auth_plugin->authFails();
+ }
+
+ // if using TCP socket is not needed
+ if (strtolower($cfg['Server']['connect_type']) == 'tcp') {
+ $cfg['Server']['socket'] = '';
+ }
+
+ // Try to connect MySQL with the control user profile (will be used to
+ // get the privileges list for the current user but the true user link
+ // must be open after this one so it would be default one for all the
+ // scripts)
+ $controllink = false;
+ if ($cfg['Server']['controluser'] != '') {
+ if (! empty($cfg['Server']['controlhost'])
+ || ! empty($cfg['Server']['controlport'])
+ ) {
+ $server_details = array();
+ if (! empty($cfg['Server']['controlhost'])) {
+ $server_details['host'] = $cfg['Server']['controlhost'];
+ } else {
+ $server_details['host'] = $cfg['Server']['host'];
+ }
+ if (! empty($cfg['Server']['controlport'])) {
+ $server_details['port'] = $cfg['Server']['controlport'];
+ } elseif ($server_details['host'] == $cfg['Server']['host']) {
+ // Evaluates to true when controlhost == host
+ // or controlhost is not defined (hence it defaults to host)
+ // In such case we can use the value of port.
+ $server_details['port'] = $cfg['Server']['port'];
+ }
+ // otherwise we leave the $server_details['port'] unset,
+ // allowing it to take default mysql port
+
+ $controllink = $GLOBALS['dbi']->connect(
+ $cfg['Server']['controluser'],
+ $cfg['Server']['controlpass'],
+ true,
+ $server_details
+ );
+ } else {
+ $controllink = $GLOBALS['dbi']->connect(
+ $cfg['Server']['controluser'],
+ $cfg['Server']['controlpass'],
+ true
+ );
+ }
+ }
+
+ // Connects to the server (validates user's login)
+ $userlink = $GLOBALS['dbi']->connect(
+ $cfg['Server']['user'], $cfg['Server']['password'], false
+ );
+
+ if (! $controllink) {
+ $controllink = $userlink;
+ }
+
+ /* Log success */
+ PMA_logUser($cfg['Server']['user']);
+
+ /**
+ * with phpMyAdmin 3 we support MySQL >=5
+ * but only production releases:
+ * - > 5.0.15
+ */
+ if (PMA_MYSQL_INT_VERSION < 50015) {
+ PMA_fatalError(
+ __('You should upgrade to %s %s or later.'),
+ array('MySQL', '5.0.15')
+ );
+ }
+
+ /**
+ * Type handling object.
+ */
+ if (PMA_DRIZZLE) {
+ $GLOBALS['PMA_Types'] = new PMA_Types_Drizzle();
+ } else {
+ $GLOBALS['PMA_Types'] = new PMA_Types_MySQL();
+ }
+
+ if (PMA_DRIZZLE) {
+ // SHOW OPEN TABLES is not supported by Drizzle
+ $cfg['SkipLockedTables'] = false;
+ }
+
+ /**
+ * SQL Parser code
+ */
+ include_once './libraries/sqlparser.lib.php';
+
+ /**
+ * SQL Validator interface code
+ */
+ include_once './libraries/sqlvalidator.lib.php';
+
+ /**
+ * the PMA_List_Database class
+ */
+ include_once './libraries/PMA.php';
+ $pma = new PMA;
+ $pma->userlink = $userlink;
+ $pma->controllink = $controllink;
+
+ /**
+ * some resetting has to be done when switching servers
+ */
+ if (isset($_SESSION['tmpval']['previous_server'])
+ && $_SESSION['tmpval']['previous_server'] != $GLOBALS['server']
+ ) {
+ unset($_SESSION['tmpval']['navi_limit_offset']);
+ }
+ $_SESSION['tmpval']['previous_server'] = $GLOBALS['server'];
+
+ } // end server connecting
+
+ /**
+ * check if profiling was requested and remember it
+ * (note: when $cfg['ServerDefault'] = 0, constant is not defined)
+ */
+ if (isset($_REQUEST['profiling'])
+ && PMA_Util::profilingSupported()
+ ) {
+ $_SESSION['profiling'] = true;
+ } elseif (isset($_REQUEST['profiling_form'])) {
+ // the checkbox was unchecked
+ unset($_SESSION['profiling']);
+ }
+ /**
+ * Inclusion of profiling scripts is needed on various
+ * pages like sql, tbl_sql, db_sql, tbl_select
+ */
+ if (! defined('PMA_BYPASS_GET_INSTANCE')) {
+ $response = PMA_Response::getInstance();
+ }
+ if (isset($_SESSION['profiling'])) {
+ $header = $response->getHeader();
+ $scripts = $header->getScripts();
+ $scripts->addFile('jqplot/jquery.jqplot.js');
+ $scripts->addFile('jqplot/plugins/jqplot.pieRenderer.js');
+ $scripts->addFile('jqplot/plugins/jqplot.highlighter.js');
+ $scripts->addFile('canvg/canvg.js');
+ $scripts->addFile('jquery/jquery.tablesorter.js');
+ }
+
+ /*
+ * There is no point in even attempting to process
+ * an ajax request if there is a token mismatch
+ */
+ if (isset($response) && $response->isAjax() && $token_mismatch) {
+ $response->isSuccess(false);
+ $response->addJSON(
+ 'message',
+ PMA_Message::error(__('Error: Token mismatch'))
+ );
+ exit;
+ }
+} // end if !defined('PMA_MINIMUM_COMMON')
+
+// load user preferences
+$GLOBALS['PMA_Config']->loadUserPreferences();
+
+// remove sensitive values from session
+$GLOBALS['PMA_Config']->set('blowfish_secret', '');
+$GLOBALS['PMA_Config']->set('Servers', '');
+$GLOBALS['PMA_Config']->set('default_server', '');
+
+/* Tell tracker that it can actually work */
+PMA_Tracker::enable();
+
+/**
+ * @global boolean $GLOBALS['is_ajax_request']
+ * @todo should this be moved to the variables init section above?
+ *
+ * Check if the current request is an AJAX request, and set is_ajax_request
+ * accordingly. Suppress headers, footers and unnecessary output if set to
+ * true
+ */
+if (isset($_REQUEST['ajax_request']) && $_REQUEST['ajax_request'] == true) {
+ $GLOBALS['is_ajax_request'] = true;
+} else {
+ $GLOBALS['is_ajax_request'] = false;
+}
+
+if (isset($_REQUEST['GLOBALS']) || isset($_FILES['GLOBALS'])) {
+ PMA_fatalError(__("GLOBALS overwrite attempt"));
+}
+
+/**
+ * protect against possible exploits - there is no need to have so much variables
+ */
+if (count($_REQUEST) > 1000) {
+ PMA_fatalError(__('possible exploit'));
+}
+
+/**
+ * Check for numeric keys
+ * (if register_globals is on, numeric key can be found in $GLOBALS)
+ */
+foreach ($GLOBALS as $key => $dummy) {
+ if (is_numeric($key)) {
+ PMA_fatalError(__('numeric key detected'));
+ }
+}
+unset($dummy);
+
+// here, the function does not exist with this configuration:
+// $cfg['ServerDefault'] = 0;
+$GLOBALS['is_superuser']
+ = isset($GLOBALS['dbi']) && $GLOBALS['dbi']->isSuperuser();
+
+if (!empty($__redirect) && in_array($__redirect, $goto_whitelist)) {
+ /**
+ * include subform target page
+ */
+ include $__redirect;
+ exit();
+}
+
+?>
diff --git a/libraries/config.default.php b/libraries/config.default.php
new file mode 100644
index 0000000000..c6290c3ea9
--- /dev/null
+++ b/libraries/config.default.php
@@ -0,0 +1,2856 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * <code>
+ * N N OOO !! DDDD OOO N N OOO TTTTT EEEE DDDD I TTTTT !!
+ * NN N O O !! D D O O NN N O O T E D D I T !!
+ * N N N O O !! D D O O N N N O O T EEEE D D I T !!
+ * N NN O O D D O O N NN O O T E D D I T
+ * N N OOO !! DDDD OOO N N OOO T EEEE DDDD I T !!
+ * </code>
+ *
+ * DO NOT EDIT THIS FILE, EDIT config.inc.php INSTEAD !!!
+ *
+ * phpMyAdmin default configuration, you can copy values from here to your
+ * config.inc.php
+ *
+ * All directives are explained in the documentation
+ *
+ * @package PhpMyAdmin
+ */
+
+/**
+ * Your phpMyAdmin URL.
+ *
+ * Complete the variable below with the full URL ie
+ * http://www.your_web.net/path_to_your_phpMyAdmin_directory/
+ *
+ * It must contain characters that are valid for a URL, and the path is
+ * case sensitive on some Web servers, for example Unix-based servers.
+ *
+ * In most cases you can leave this variable empty, as the correct value
+ * will be detected automatically. However, we recommend that you do
+ * test to see that the auto-detection code works in your system. A good
+ * test is to browse a table, then edit a row and save it. There will be
+ * an error message if phpMyAdmin cannot auto-detect the correct value.
+ *
+ * @global string $cfg['PmaAbsoluteUri']
+ */
+$cfg['PmaAbsoluteUri'] = '';
+
+/**
+ * Disable the default warning that is displayed on the DB Details Structure page if
+ * any of the required Tables for the configuration storage could not be found
+ *
+ * @global boolean $cfg['PmaNoRelation_DisableWarning']
+ */
+$cfg['PmaNoRelation_DisableWarning'] = false;
+
+/**
+ * Disable the default warning that is displayed if Suhosin is detected
+ *
+ * @global boolean $cfg['SuhosinDisableWarning']
+ */
+$cfg['SuhosinDisableWarning'] = false;
+
+/**
+ * Disable the default warning that is displayed if mcrypt is missing for
+ * cookie authentication.
+ *
+ * @global boolean $cfg['McryptDisableWarning']
+ */
+$cfg['McryptDisableWarning'] = false;
+
+/**
+ * Disable the default warning that is displayed if a diffrence between
+ * the MySQL library and server is detected.
+ *
+ * @global boolean $cfg['['ServerLibraryDifference_DisableWarning']']
+ */
+$cfg['ServerLibraryDifference_DisableWarning'] = false;
+
+/**
+ * Disable the default warning about MySQL reserved words in column names
+ *
+ * @global boolean $cfg['ReservedWordDisableWarning']
+ */
+$cfg['ReservedWordDisableWarning'] = false;
+
+/**
+ * Show warning about incomplete translations on certain threshold.
+ *
+ * @global boolean $cfg['TranslationWarningThreshold']
+ */
+$cfg['TranslationWarningThreshold'] = 80;
+
+/**
+ * Allows phpMyAdmin to be included from a other document in a frame;
+ * setting this to true is a potential security hole
+ *
+ * @global boolean $cfg['AllowThirdPartyFraming']
+ */
+$cfg['AllowThirdPartyFraming'] = false;
+
+/**
+ * The 'cookie' auth_type uses blowfish algorithm to encrypt the password. If
+ * at least one server configuration uses 'cookie' auth_type, enter here a
+ * pass phrase that will be used by blowfish. The maximum length seems to be 46
+ * characters.
+ *
+ * @global string $cfg['blowfish_secret']
+ */
+$cfg['blowfish_secret'] = '';
+
+
+/*******************************************************************************
+ * Server(s) configuration
+ *
+ * The $cfg['Servers'] array starts with $cfg['Servers'][1]. Do not use
+ * $cfg['Servers'][0]. You can disable a server configuration entry by setting host
+ * to ''. If you want more than one server, just copy following section
+ * (including $i incrementation) several times. There is no need to define
+ * full server array, just define values you need to change.
+ *
+ * @global array $cfg['Servers']
+ */
+$cfg['Servers'] = array();
+
+$i = 1;
+
+/**
+ * MySQL hostname or IP address
+ *
+ * @global string $cfg['Servers'][$i]['host']
+ */
+$cfg['Servers'][$i]['host'] = 'localhost';
+
+/**
+ * MySQL port - leave blank for default port
+ *
+ * @global string $cfg['Servers'][$i]['port']
+ */
+$cfg['Servers'][$i]['port'] = '';
+
+/**
+ * Path to the socket - leave blank for default socket
+ *
+ * @global string $cfg['Servers'][$i]['socket']
+ */
+$cfg['Servers'][$i]['socket'] = '';
+
+/**
+ * Use SSL for connecting to MySQL server?
+ *
+ * @global boolean $cfg['Servers'][$i]['ssl']
+ */
+$cfg['Servers'][$i]['ssl'] = false;
+
+/**
+ * Path to the key file when using SSL for connecting to the MySQL server
+ *
+ * @global string $cfg['Servers'][$i]['ssl_key']
+ */
+$cfg['Servers'][$i]['ssl_key'] = null;
+
+/**
+ * Path to the cert file when using SSL for connecting to the MySQL server
+ *
+ * @global string $cfg['Servers'][$i]['ssl_cert']
+ */
+$cfg['Servers'][$i]['ssl_cert'] = null;
+
+/**
+ * Path to the CA file when using SSL for connecting to the MySQL server
+ *
+ * @global string $cfg['Servers'][$i]['ssl_ca']
+ */
+$cfg['Servers'][$i]['ssl_ca'] = null;
+
+/**
+ * Directory containing trusted SSL CA certificates in PEM format
+ *
+ * @global string $cfg['Servers'][$i]['ssl_ca_path']
+ */
+$cfg['Servers'][$i]['ssl_ca_path'] = null;
+
+/**
+ * List of allowable ciphers for SSL connections to the MySQL server
+ *
+ * @global string $cfg['Servers'][$i]['ssl_ciphers']
+ */
+$cfg['Servers'][$i]['ssl_ciphers'] = null;
+
+/**
+ * How to connect to MySQL server ('tcp' or 'socket')
+ *
+ * @global string $cfg['Servers'][$i]['connect_type']
+ */
+$cfg['Servers'][$i]['connect_type'] = 'tcp';
+
+/**
+ * The PHP MySQL extension to use ('mysql' or 'mysqli')
+ *
+ * @global string $cfg['Servers'][$i]['extension']
+ */
+$cfg['Servers'][$i]['extension'] = 'mysqli';
+
+/**
+ * Use compressed protocol for the MySQL connection
+ *
+ * @global boolean $cfg['Servers'][$i]['compress']
+ */
+$cfg['Servers'][$i]['compress'] = false;
+
+/**
+ * MySQL control host. This permits to use a host different than the
+ * main host, for the phpMyAdmin configuration storage. If left empty,
+ * $cfg['Servers'][$i]['host'] is used instead.
+ *
+ * @global string $cfg['Servers'][$i]['controlhost']
+ */
+$cfg['Servers'][$i]['controlhost'] = '';
+
+/**
+ * MySQL control port. This permits to use a port different than the
+ * main port, for the phpMyAdmin configuration storage. If left empty,
+ * $cfg['Servers'][$i]['port'] is used instead.
+ *
+ * @global string $cfg['Servers'][$i]['controlport']
+ */
+$cfg['Servers'][$i]['controlport'] = '';
+
+/**
+ * MySQL control user settings (this user must have read-only
+ * access to the "mysql/user" and "mysql/db" tables). The controluser is also
+ * used for all relational features (pmadb)
+ *
+ * @global string $cfg['Servers'][$i]['controluser']
+ */
+$cfg['Servers'][$i]['controluser'] = '';
+
+/**
+ * MySQL control user settings (this user must have read-only
+ * access to the "mysql/user" and "mysql/db" tables). The controluser is also
+ * used for all relational features (pmadb)
+ *
+ * @global string $cfg['Servers'][$i]['controlpass']
+ */
+$cfg['Servers'][$i]['controlpass'] = '';
+
+/**
+ * Authentication method (valid choices: config, http, signon or cookie)
+ *
+ * @global string $cfg['Servers'][$i]['auth_type']
+ */
+$cfg['Servers'][$i]['auth_type'] = 'cookie';
+
+/**
+ * HTTP Basic Auth Realm name to display (only used with 'HTTP' auth_type)
+ *
+ * @global string $cfg['Servers'][$i]['auth_http_realm']
+ */
+$cfg['Servers'][$i]['auth_http_realm'] = '';
+
+/**
+ * File containing Swekey ids and login names (see /contrib);
+ * leave empty to deactivate Swekey hardware authentication
+ *
+ * @global string $cfg['Servers'][$i]['auth_swekey_config']
+ */
+$cfg['Servers'][$i]['auth_swekey_config'] = '';
+
+/**
+ * MySQL user
+ *
+ * @global string $cfg['Servers'][$i]['user']
+ */
+$cfg['Servers'][$i]['user'] = 'root';
+
+/**
+ * MySQL password (only needed with 'config' auth_type)
+ *
+ * @global string $cfg['Servers'][$i]['password']
+ */
+$cfg['Servers'][$i]['password'] = '';
+
+/**
+ * Session to use for 'signon' authentication method
+ *
+ * @global string $cfg['Servers'][$i]['SignonSession']
+ */
+$cfg['Servers'][$i]['SignonSession'] = '';
+
+/**
+ * PHP script to use for 'signon' authentication method
+ *
+ * @global string $cfg['Servers'][$i]['SignonScript']
+ */
+$cfg['Servers'][$i]['SignonScript'] = '';
+
+/**
+ * URL where to redirect user to login for 'signon' authentication method
+ *
+ * @global string $cfg['Servers'][$i]['SignonURL']
+ */
+$cfg['Servers'][$i]['SignonURL'] = '';
+
+/**
+ * URL where to redirect user after logout
+ *
+ * @global string $cfg['Servers'][$i]['LogoutURL']
+ */
+$cfg['Servers'][$i]['LogoutURL'] = '';
+
+/**
+ * Whether to try to connect without password
+ *
+ * @global boolean $cfg['Servers'][$i]['nopassword']
+ */
+$cfg['Servers'][$i]['nopassword'] = false;
+
+/**
+ * If set to a db-name, only this db is displayed in navigation panel
+ * It may also be an array of db-names
+ *
+ * @global string $cfg['Servers'][$i]['only_db']
+ */
+$cfg['Servers'][$i]['only_db'] = '';
+
+/**
+ * Database name to be hidden from listings
+ *
+ * @global string $cfg['Servers'][$i]['hide_db']
+ */
+$cfg['Servers'][$i]['hide_db'] = '';
+
+/**
+ * Verbose name for this host - leave blank to show the hostname
+ * (for HTTP authentication, all non-US-ASCII characters will be stripped)
+ *
+ * @global string $cfg['Servers'][$i]['verbose']
+ */
+$cfg['Servers'][$i]['verbose'] = '';
+
+/**
+ * Database used for Relation, Bookmark and PDF Features
+ * (see examples/create_tables.sql)
+ * - leave blank for no support
+ * SUGGESTED: 'phpmyadmin'
+ *
+ * @global string $cfg['Servers'][$i]['pmadb']
+ */
+$cfg['Servers'][$i]['pmadb'] = '';
+
+/**
+ * Bookmark table
+ * - leave blank for no bookmark support
+ * SUGGESTED: 'pma__bookmark'
+ *
+ * @global string $cfg['Servers'][$i]['bookmarktable']
+ */
+$cfg['Servers'][$i]['bookmarktable'] = '';
+
+/**
+ * table to describe the relation between links (see doc)
+ * - leave blank for no relation-links support
+ * SUGGESTED: 'pma__relation'
+ *
+ * @global string $cfg['Servers'][$i]['relation']
+ */
+$cfg['Servers'][$i]['relation'] = '';
+
+/**
+ * table to describe the display fields
+ * - leave blank for no display fields support
+ * SUGGESTED: 'pma__table_info'
+ *
+ * @global string $cfg['Servers'][$i]['table_info']
+ */
+$cfg['Servers'][$i]['table_info'] = '';
+
+/**
+ * table to describe the tables position for the PDF schema
+ * - leave blank for no PDF schema support
+ * SUGGESTED: 'pma__table_coords'
+ *
+ * @global string $cfg['Servers'][$i]['table_coords']
+ */
+$cfg['Servers'][$i]['table_coords'] = '';
+
+/**
+ * table to describe pages of relationpdf
+ * - leave blank if you don't want to use this
+ * SUGGESTED: 'pma__pdf_pages'
+ *
+ * @global string $cfg['Servers'][$i]['pdf_pages']
+ */
+$cfg['Servers'][$i]['pdf_pages'] = '';
+
+/**
+ * table to store column information
+ * - leave blank for no column comments/mime types
+ * SUGGESTED: 'pma__column_info'
+ *
+ * @global string $cfg['Servers'][$i]['column_info']
+ */
+$cfg['Servers'][$i]['column_info'] = '';
+
+/**
+ * table to store SQL history
+ * - leave blank for no SQL query history
+ * SUGGESTED: 'pma__history'
+ *
+ * @global string $cfg['Servers'][$i]['history']
+ */
+$cfg['Servers'][$i]['history'] = '';
+
+/**
+ * table to store the coordinates for Designer
+ * - leave blank for no Designer feature
+ * SUGGESTED: 'pma__designer_coords'
+ *
+ * @global string $cfg['Servers'][$i]['designer_coords']
+ */
+$cfg['Servers'][$i]['designer_coords'] = '';
+
+/**
+ * table to store recently used tables
+ * - leave blank for no "persistent" recently used tables
+ * SUGGESTED: 'pma__recent'
+ */
+$cfg['Servers'][$i]['recent'] = '';
+
+/**
+ * table to store UI preferences for tables
+ * - leave blank for no "persistent" UI preferences
+ * SUGGESTED: 'pma__table_uiprefs'
+ */
+$cfg['Servers'][$i]['table_uiprefs'] = '';
+
+/**
+ * table to store SQL tracking
+ * - leave blank for no SQL tracking
+ * SUGGESTED: 'pma__tracking'
+ *
+ * @global string $cfg['Servers'][$i]['tracking']
+ */
+$cfg['Servers'][$i]['tracking'] = '';
+
+/**
+ * table to store user preferences
+ * - leave blank to disable server storage
+ * SUGGESTED: 'pma__userconfig'
+ *
+ * @global string $cfg['Servers'][$i]['userconfig']
+ */
+$cfg['Servers'][$i]['userconfig'] = '';
+
+/**
+ * table to store users and their assignment to user groups
+ * - leave blank to disable configurable menus feature
+ * SUGGESTED: 'pma__users'
+ *
+ * @global string $cfg['Servers'][$i]['users']
+ */
+$cfg['Servers'][$i]['users'] = '';
+
+/**
+ * table to store allowed menu items for each user group
+ * - leave blank to disable configurable menus feature
+ * SUGGESTED: 'pma__usergroups'
+ *
+ * @global string $cfg['Servers'][$i]['usergroups']
+ */
+$cfg['Servers'][$i]['usergroups'] = '';
+
+/**
+ * table to store information about item hidden from navigation triee
+ * - leave blank to disable hide/show navigation items feature
+ * SUGGESTED: 'pma__navigationhiding'
+ *
+ * @global string $cfg['Servers'][$i]['navigationhiding']
+ */
+$cfg['Servers'][$i]['navigationhiding'] = '';
+
+/**
+ * Maximum number of records saved in $cfg['Servers'][$i]['table_uiprefs'] table.
+ *
+ * In case where tables in databases is modified (e.g. dropped or renamed),
+ * table_uiprefs may contains invalid data (referring to tables which are not
+ * exist anymore).
+ * This configuration make sure that we only keep N (N = MaxTableUiprefs)
+ * newest record in table_uiprefs and automatically delete older records.
+ *
+ * @global integer $cfg['Servers'][$i]['userconfig'] = '';
+ */
+$cfg['Servers'][$i]['MaxTableUiprefs'] = 100;
+
+/**
+ * whether to allow root login
+ *
+ * @global boolean $cfg['Servers'][$i]['AllowRoot']
+ */
+$cfg['Servers'][$i]['AllowRoot'] = true;
+
+/**
+ * whether to allow login of any user without a password
+ *
+ * @global boolean $cfg['Servers'][$i]['AllowNoPassword']
+ */
+$cfg['Servers'][$i]['AllowNoPassword'] = false;
+
+/**
+ * Host authentication order, leave blank to not use
+ *
+ * @global string $cfg['Servers'][$i]['AllowDeny']['order']
+ */
+$cfg['Servers'][$i]['AllowDeny']['order'] = '';
+
+/**
+ * Host authentication rules, leave blank for defaults
+ *
+ * @global array $cfg['Servers'][$i]['AllowDeny']['rules']
+ */
+$cfg['Servers'][$i]['AllowDeny']['rules'] = array();
+
+/**
+ * Whether the tracking mechanism creates
+ * versions for tables and views automatically.
+ *
+ * @global bool $cfg['Servers'][$i]['tracking_version_auto_create']
+ */
+
+$cfg['Servers'][$i]['tracking_version_auto_create'] = false;
+
+/**
+ * Defines the list of statements
+ * the auto-creation uses for new versions.
+ *
+ * @global string $cfg['Servers'][$i]['tracking_default_statements']
+ */
+
+$cfg['Servers'][$i]['tracking_default_statements']
+ = 'CREATE TABLE,ALTER TABLE,DROP TABLE,RENAME TABLE,CREATE INDEX,' .
+ 'DROP INDEX,INSERT,UPDATE,DELETE,TRUNCATE,REPLACE,CREATE VIEW,' .
+ 'ALTER VIEW,DROP VIEW,CREATE DATABASE,ALTER DATABASE,DROP DATABASE';
+
+/**
+ * Whether a DROP VIEW IF EXISTS statement will be added
+ * as first line to the log when creating a view.
+ *
+ * @global bool $cfg['Servers'][$i]['tracking_add_drop_view']
+ */
+
+$cfg['Servers'][$i]['tracking_add_drop_view'] = true;
+
+/**
+ * Whether a DROP TABLE IF EXISTS statement will be added
+ * as first line to the log when creating a table.
+ *
+ * @global bool $cfg['Servers'][$i]['tracking_add_drop_table']
+ */
+
+$cfg['Servers'][$i]['tracking_add_drop_table'] = true;
+
+/**
+ * Whether a DROP DATABASE IF EXISTS statement will be added
+ * as first line to the log when creating a database.
+ *
+ * @global bool $cfg['Servers'][$i]['tracking_add_drop_database']
+ */
+
+$cfg['Servers'][$i]['tracking_add_drop_database'] = true;
+
+/**
+ * Enables caching of TABLE STATUS outputs for specific databases on this server
+ * (in some cases TABLE STATUS can be very slow, so you may want to cache it).
+ * APC is used (if the PHP extension is available, if not, this setting is ignored
+ * silently). You have to provide StatusCacheLifetime.
+ *
+ * @global array $cfg['Servers'][$i]['StatusCacheDatabases']
+ */
+$cfg['Servers'][$i]['StatusCacheDatabases'] = array();
+
+/**
+ * Lifetime in seconds of the TABLE STATUS cache if StatusCacheDatabases is used
+ *
+ * @global integer $cfg['Servers'][$i]['StatusCacheLifetime']
+ */
+$cfg['Servers'][$i]['StatusCacheLifetime'] = 0;
+
+/**
+ * Default server (0 = no default server)
+ *
+ * If you have more than one server configured, you can set $cfg['ServerDefault']
+ * to any one of them to auto-connect to that server when phpMyAdmin is started,
+ * or set it to 0 to be given a list of servers without logging in
+ * If you have only one server configured, $cfg['ServerDefault'] *MUST* be
+ * set to that server.
+ *
+ * @global integer $cfg['ServerDefault']
+ */
+$cfg['ServerDefault'] = 1;
+
+/*
+ * Other core phpMyAdmin settings
+ */
+
+/**
+ * whether version check is active
+ *
+ * @global boolean $cfg['VersionCheck']
+ */
+if (defined('VERSION_CHECK_DEFAULT')) {
+ $cfg['VersionCheck'] = VERSION_CHECK_DEFAULT;
+} else {
+ $cfg['VersionCheck'] = true;
+}
+
+/**
+ * The url of the proxy to be used when retrieving the information about
+ * the latest version of phpMyAdmin or error reporting. You need this if
+ * the server where phpMyAdmin is installed does not have direct access to
+ * the internet.
+ * The format is: "hostname:portnumber"
+ *
+ * @global string $cfg['ProxyUrl']
+ */
+$cfg['ProxyUrl'] = "";
+
+/**
+ * The username for authenticating with the proxy. By default, no
+ * authentication is performed. If a username is supplied, Basic
+ * Authentication will be performed. No other types of authentication
+ * are currently supported.
+ *
+ * @global string $cfg['ProxyUser']
+ */
+$cfg['ProxyUser'] = "";
+
+/**
+ * The password for authenticating with the proxy.
+ *
+ * @global string $cfg['ProxyPass']
+ */
+$cfg['ProxyPass'] = "";
+
+/**
+ * maximum number of db's displayed in database list
+ *
+ * @global integer $cfg['MaxDbList']
+ */
+$cfg['MaxDbList'] = 100;
+
+/**
+ * maximum number of tables displayed in table list
+ *
+ * @global integer $cfg['MaxTableList']
+ */
+$cfg['MaxTableList'] = 250;
+
+/**
+ * whether to show hint or not
+ *
+ * @global boolean $cfg['ShowHint']
+ */
+$cfg['ShowHint'] = true;
+
+/**
+ * maximum number of characters when a SQL query is displayed
+ *
+ * @global integer $cfg['MaxCharactersInDisplayedSQL']
+ */
+$cfg['MaxCharactersInDisplayedSQL'] = 1000;
+
+/**
+ * use GZIP output buffering if possible (true|false|'auto')
+ *
+ * @global string $cfg['OBGzip']
+ */
+$cfg['OBGzip'] = 'auto';
+
+/**
+ * use persistent connections to MySQL database
+ *
+ * @global boolean $cfg['PersistentConnections']
+ */
+$cfg['PersistentConnections'] = false;
+
+/**
+ * whether to force using HTTPS
+ *
+ * @global boolean $cfg['ForceSSL']
+ */
+$cfg['ForceSSL'] = false;
+
+/**
+ * maximum execution time in seconds (0 for no limit)
+ *
+ * @global integer $cfg['ExecTimeLimit']
+ */
+$cfg['ExecTimeLimit'] = 300;
+
+/**
+ * Path for storing session data (session_save_path PHP parameter).
+ *
+ * @global integer $cfg['SessionSavePath']
+ */
+$cfg['SessionSavePath'] = '';
+
+/**
+ * maximum allocated bytes ('-1' for no limit)
+ * this is a string because '16M' is a valid value; we must put here
+ * a string as the default value so that /setup accepts strings
+ *
+ * @global string $cfg['MemoryLimit']
+ */
+$cfg['MemoryLimit'] = '-1';
+
+/**
+ * mark used tables, make possible to show locked tables (since MySQL 3.23.30)
+ * Is ignored for Drizzle.
+ *
+ * @global boolean $cfg['SkipLockedTables']
+ */
+$cfg['SkipLockedTables'] = false;
+
+/**
+ * show SQL queries as run
+ *
+ * @global boolean $cfg['ShowSQL']
+ */
+$cfg['ShowSQL'] = true;
+
+/**
+ * retain SQL input on Ajax execute
+ *
+ * @global boolean $cfg['RetainQueryEditor']
+ */
+$cfg['RetainQueryBox'] = false;
+
+/**
+ * use CodeMirror syntax highlighting for editing SQL
+ *
+ * @global boolean $cfg['CodemirrorEnable']
+ */
+$cfg['CodemirrorEnable'] = true;
+
+/**
+ * show a 'Drop database' link to normal users
+ *
+ * @global boolean $cfg['AllowUserDropDatabase']
+ */
+$cfg['AllowUserDropDatabase'] = false;
+
+/**
+ * confirm some commands that can result in loss of data
+ *
+ * @global boolean $cfg['Confirm']
+ */
+$cfg['Confirm'] = true;
+
+/**
+ * recall previous login in cookie authentication mode or not
+ *
+ * @global boolean $cfg['LoginCookieRecall']
+ */
+$cfg['LoginCookieRecall'] = true;
+
+/**
+ * validity of cookie login (in seconds; 1440 matches php.ini's
+ * session.gc_maxlifetime)
+ *
+ * @global integer $cfg['LoginCookieValidity']
+ */
+$cfg['LoginCookieValidity'] = 1440;
+
+/**
+ * how long login cookie should be stored (in seconds)
+ *
+ * @global integer $cfg['LoginCookieStore']
+ */
+$cfg['LoginCookieStore'] = 0;
+
+/**
+ * whether to delete all login cookies on logout
+ *
+ * @global boolean $cfg['LoginCookieDeleteAll']
+ */
+$cfg['LoginCookieDeleteAll'] = true;
+
+/**
+ * whether to enable the "database search" feature or not
+ *
+ * @global boolean $cfg['UseDbSearch']
+ */
+$cfg['UseDbSearch'] = true;
+
+/**
+ * if set to true, PMA continues computing multiple-statement queries
+ * even if one of the queries failed
+ *
+ * @global boolean $cfg['IgnoreMultiSubmitErrors']
+ */
+$cfg['IgnoreMultiSubmitErrors'] = false;
+
+/**
+ * allow login to any user entered server in cookie based authentication
+ *
+ * @global boolean $cfg['AllowArbitraryServer']
+ */
+$cfg['AllowArbitraryServer'] = false;
+
+/**
+ * if reCaptcha is enabled it needs public key to connect with the service
+ *
+ * @global string $cfg['CaptchaLoginPublicKey']
+ */
+$cfg['CaptchaLoginPublicKey'] = '';
+
+/**
+ * if reCaptcha is enabled it needs private key to connect with the service
+ *
+ * @global string $cfg['CaptchaLoginPrivateKey']
+ */
+$cfg['CaptchaLoginPrivateKey'] = '';
+
+/*******************************************************************************
+ * Error handler configuration
+ *
+ * this configures phpMyAdmin's own error handler, it is used to avoid information
+ * disclosure, gather errors for logging, reporting and displaying
+ *
+ * @global array $cfg['Error_Handler']
+ */
+$cfg['Error_Handler'] = array();
+
+/**
+ * whether to display errors or not
+ *
+ * this does not affect errors of type E_USER_*
+ *
+ * @global boolean $cfg['Error_Handler']['display']
+ */
+$cfg['Error_Handler']['display'] = false;
+
+
+/*******************************************************************************
+ * Navigation panel setup
+ */
+
+/**
+ * maximum number of items displayed in navigation panel
+ *
+ * @global integer $cfg['MaxDbList']
+ */
+$cfg['MaxNavigationItems'] = 250;
+
+/**
+ * turn the select-based light menu into a tree
+ *
+ * @global boolean $cfg['NavigationTreeEnableGrouping']
+ */
+$cfg['NavigationTreeEnableGrouping'] = true;
+
+/**
+ * the separator to sub-tree the select-based light menu tree
+ *
+ * @global string $cfg['NavigationTreeDbSeparator']
+ */
+$cfg['NavigationTreeDbSeparator'] = '_';
+
+/**
+ * Which string will be used to generate table prefixes
+ * to split/nest tables into multiple categories
+ *
+ * @global string $cfg['NavigationTreeTableSeparator']
+ */
+$cfg['NavigationTreeTableSeparator'] = '__';
+
+/**
+ * How many sublevels should be displayed when splitting up tables
+ * by the above Separator
+ *
+ * @global integer $cfg['NavigationTreeTableLevel']
+ */
+$cfg['NavigationTreeTableLevel'] = 1;
+
+/**
+ * display logo at top of navigation panel
+ *
+ * @global boolean $cfg['NavigationDisplayLogo']
+ */
+$cfg['NavigationDisplayLogo'] = true;
+
+/**
+ * where should logo link point to (can also contain an external URL)
+ *
+ * @global string $cfg['NavigationLogoLink']
+ */
+$cfg['NavigationLogoLink'] = 'index.php';
+
+/**
+ * whether to open the linked page in the main window ('main') or
+ * in a new window ('new')
+ *
+ * @global string $cfg['NavigationLogoLinkWindow']
+ */
+$cfg['NavigationLogoLinkWindow'] = 'main';
+
+/**
+ * number of recently used tables displayed in the navigation panel
+ *
+ * @global integer $cfg['NumRecentTables']
+ */
+$cfg['NumRecentTables'] = 10;
+
+/**
+ * display a JavaScript table filter in the navigation panel
+ * when more then x tables are present
+ *
+ * @global boolean $cfg['NavigationTreeDisplayItemFilterMinimum']
+ */
+$cfg['NavigationTreeDisplayItemFilterMinimum'] = 30;
+
+/**
+ * display server choice at top of navigation panel
+ *
+ * @global boolean $cfg['NavigationDisplayServers']
+ */
+$cfg['NavigationDisplayServers'] = true;
+
+/**
+ * server choice as links
+ *
+ * @global boolean $cfg['DisplayServersList']
+ */
+$cfg['DisplayServersList'] = false;
+
+/**
+ * display a JavaScript database filter in the navigation panel
+ * when more then x databases are present
+ *
+ * @global boolean $cfg['NavigationTreeDisplayDbFilterMinimum']
+ */
+$cfg['NavigationTreeDisplayDbFilterMinimum'] = 30;
+
+/**
+ * target of the navigation panel quick access icon
+ *
+ * Possible values:
+ * 'tbl_structure.php' = fields list
+ * 'tbl_sql.php' = SQL form
+ * 'tbl_select.php' = search page
+ * 'tbl_change.php' = insert row page
+ * 'sql.php' = browse page
+ *
+ * @global string $cfg['NavigationTreeDefaultTabTable']
+ */
+$cfg['NavigationTreeDefaultTabTable'] = 'tbl_structure.php';
+
+
+/*******************************************************************************
+ * In the main panel, at startup...
+ */
+
+/**
+ * allow to display statistics and space usage in the pages about database
+ * details and table properties
+ *
+ * @global boolean $cfg['ShowStats']
+ */
+$cfg['ShowStats'] = true;
+
+/**
+ * show PHP info link
+ *
+ * @global boolean $cfg['ShowPhpInfo']
+ */
+$cfg['ShowPhpInfo'] = false;
+
+/**
+ * show MySQL server and web server information
+ *
+ * @global boolean $cfg['ShowServerInfo']
+ */
+$cfg['ShowServerInfo'] = true;
+
+/**
+ * show change password link
+ *
+ * @global boolean $cfg['ShowChgPassword']
+ */
+$cfg['ShowChgPassword'] = true;
+
+/**
+ * show create database form
+ *
+ * @global boolean $cfg['ShowCreateDb']
+ */
+$cfg['ShowCreateDb'] = true;
+
+
+/*******************************************************************************
+ * Database structure
+ */
+
+/**
+ * show creation timestamp column in database structure (true|false)?
+ *
+ * @global boolean $cfg['ShowDbStructureCreation']
+ */
+$cfg['ShowDbStructureCreation'] = false;
+
+/**
+ * show last update timestamp column in database structure (true|false)?
+ *
+ * @global boolean $cfg['ShowDbStructureLastUpdate']
+ */
+$cfg['ShowDbStructureLastUpdate'] = false;
+
+/**
+ * show last check timestamp column in database structure (true|false)?
+ *
+ * @global boolean $cfg['ShowDbStructureLastCheck']
+ */
+$cfg['ShowDbStructureLastCheck'] = false;
+
+/**
+ * allow hide action columns to drop down menu in database structure (true|false)?
+ *
+ * @global boolean $cfg['HideStructureActions']
+ */
+$cfg['HideStructureActions'] = true;
+
+
+/*******************************************************************************
+ * In browse mode...
+ */
+
+/**
+ * Use icons instead of text for the navigation bar buttons (table browse)
+ * ('text'|'icons'|'both')
+ *
+ * @global string $cfg['TableNavigationLinksMode']
+ */
+$cfg['TableNavigationLinksMode'] = 'icons';
+
+/**
+ * Defines whether a user should be displayed a "show all (records)"
+ * button in browse mode or not.
+ *
+ * @global boolean $cfg['ShowAll']
+ */
+$cfg['ShowAll'] = false;
+
+/**
+ * Number of rows displayed when browsing a result set. If the result
+ * set contains more rows, "Previous" and "Next".
+ * Possible values: 25,50,100,250,500
+ *
+ * @global integer $cfg['MaxRows']
+ */
+$cfg['MaxRows'] = 25;
+
+/**
+ * default for 'ORDER BY' clause (valid values are 'ASC', 'DESC' or 'SMART' -ie
+ * descending order for fields of type TIME, DATE, DATETIME & TIMESTAMP,
+ * ascending order else-)
+ *
+ * @global string $cfg['Order']
+ */
+$cfg['Order'] = 'SMART';
+
+/**
+ * default for 'Show binary contents as HEX'
+ *
+ * @global string $cfg['DisplayBinaryAsHex']
+ */
+$cfg['DisplayBinaryAsHex'] = true;
+
+/**
+ * grid editing: save edited cell(s) in browse-mode at once
+ *
+ * @global boolean $cfg['SaveCellsAtOnce']
+ */
+
+$cfg['SaveCellsAtOnce'] = false;
+
+/**
+ * grid editing: which action triggers it, or completely disable the feature
+ *
+ * Possible values:
+ * 'click'
+ * 'double-click'
+ * 'disabled'
+ *
+ * @global string $cfg['GridEditing']
+ */
+$cfg['GridEditing'] ='double-click';
+
+
+/*******************************************************************************
+ * In edit mode...
+ */
+
+/**
+ * disallow editing of binary fields
+ * valid values are:
+ * false allow editing
+ * 'blob' allow editing except for BLOB fields
+ * 'noblob' disallow editing except for BLOB fields
+ * 'all' disallow editing
+ *
+ * @global string $cfg['ProtectBinary']
+ */
+$cfg['ProtectBinary'] = 'blob';
+
+/**
+ * Display the function fields in edit/insert mode
+ *
+ * @global boolean $cfg['ShowFunctionFields']
+ */
+$cfg['ShowFunctionFields'] = true;
+
+/**
+ * Display the type fields in edit/insert mode
+ *
+ * @global boolean $cfg['ShowFieldTypesInDataEditView']
+ */
+$cfg['ShowFieldTypesInDataEditView'] = true;
+
+/**
+ * Which editor should be used for CHAR/VARCHAR fields:
+ * input - allows limiting of input length
+ * textarea - allows newlines in fields
+ *
+ * @global string $cfg['CharEditing']
+ */
+$cfg['CharEditing'] = 'input';
+
+/**
+ * The minimum size for character input fields
+ *
+ * @global integer $cfg['MinSizeForInputField']
+ */
+$cfg['MinSizeForInputField'] = 4;
+
+/**
+ * The maximum size for character input fields
+ *
+ * @global integer $cfg['MinSizeForInputField']
+ */
+$cfg['MaxSizeForInputField'] = 60;
+
+/**
+ * How many rows can be inserted at one time
+ *
+ * @global integer $cfg['InsertRows']
+ */
+$cfg['InsertRows'] = 2;
+
+/**
+ * Sort order for items in a foreign-key drop-down list.
+ * 'content' is the referenced data, 'id' is the key value.
+ *
+ * @global array $cfg['ForeignKeyDropdownOrder']
+ */
+$cfg['ForeignKeyDropdownOrder'] = array('content-id', 'id-content');
+
+/**
+ * A drop-down list will be used if fewer items are present
+ *
+ * @global integer $cfg['ForeignKeyMaxLimit']
+ */
+$cfg['ForeignKeyMaxLimit'] = 100;
+
+
+/*******************************************************************************
+ * For the export features...
+ */
+
+/**
+ * Allow for the use of zip compression (requires zip support to be enabled)
+ *
+ * @global boolean $cfg['ZipDump']
+ */
+$cfg['ZipDump'] = true;
+
+/**
+ * Allow for the use of gzip compression (requires zlib)
+ *
+ * @global boolean $cfg['GZipDump']
+ */
+$cfg['GZipDump'] = true;
+
+/**
+ * Allow for the use of bzip2 decompression (requires bz2 extension)
+ *
+ * @global boolean $cfg['BZipDump']
+ */
+$cfg['BZipDump'] = true;
+
+/**
+ * Will compress gzip exports on the fly without the need for much memory.
+ * If you encounter problems with created gzip files disable this feature.
+ *
+ * @global boolean $cfg['CompressOnFly']
+ */
+$cfg['CompressOnFly'] = true;
+
+
+/*******************************************************************************
+ * Tabs display settings
+ */
+
+/**
+ * How to display the menu tabs ('icons'|'text'|'both')
+ *
+ * @global boolean $cfg['TabsMode']
+ */
+$cfg['TabsMode'] = 'both';
+
+/**
+ * How to display various action links ('icons'|'text'|'both')
+ *
+ * @global boolean $cfg['ActionLinksMode']
+ */
+$cfg['ActionLinksMode'] = 'both';
+
+/**
+ * How many columns should be used for table display of a database?
+ * (a value larger than 1 results in some information being hidden)
+ *
+ * @global integer $cfg['PropertiesNumColumns']
+ */
+$cfg['PropertiesNumColumns'] = 1;
+
+/**
+ * Possible values:
+ * 'index.php' = the welcome page
+ * (recommended for multiuser setups)
+ * 'server_databases.php' = list of databases
+ * 'server_status.php' = runtime information
+ * 'server_variables.php' = MySQL server variables
+ * 'server_privileges.php' = user management
+ *
+ * @global string $cfg['DefaultTabServer']
+ */
+$cfg['DefaultTabServer'] = 'index.php';
+
+/**
+ * Possible values:
+ * 'db_structure.php' = tables list
+ * 'db_sql.php' = SQL form
+ * 'db_search.php' = search query
+ * 'db_operations.php' = operations on database
+ *
+ * @global string $cfg['DefaultTabDatabase']
+ */
+$cfg['DefaultTabDatabase'] = 'db_structure.php';
+
+/**
+ * Possible values:
+ * 'tbl_structure.php' = fields list
+ * 'tbl_sql.php' = SQL form
+ * 'tbl_select.php' = search page
+ * 'tbl_change.php' = insert row page
+ * 'sql.php' = browse page
+ *
+ * @global string $cfg['DefaultTabTable']
+ */
+$cfg['DefaultTabTable'] = 'sql.php';
+
+/*******************************************************************************
+ * Export defaults
+ */
+$cfg['Export'] = array();
+
+/**
+ * codegen/csv/excel/htmlexcel/htmlword/latex/ods/odt/pdf/sql/texytext/xls/xml/yaml
+ *
+ * @global string $cfg['Export']['format']
+ */
+$cfg['Export']['format'] = 'sql';
+
+/**
+ * quick/custom/custom-no-form
+ *
+ * @global string $cfg['Export']['format']
+ */
+$cfg['Export']['method'] = 'quick';
+
+/**
+ * none/zip/gzip
+ *
+ * @global string $cfg['Export']['compression']
+ */
+$cfg['Export']['compression'] = 'none';
+
+/**
+ *
+ *
+ * @global boolean $cfg['Export']['asfile']
+ */
+$cfg['Export']['asfile'] = true;
+
+/**
+ *
+ *
+ * @global string $cfg['Export']['charset']
+ */
+$cfg['Export']['charset'] = '';
+
+/**
+ *
+ *
+ * @global boolean $cfg['Export']['onserver']
+ */
+$cfg['Export']['onserver'] = false;
+
+/**
+ *
+ *
+ * @global boolean $cfg['Export']['onserver_overwrite']
+ */
+$cfg['Export']['onserver_overwrite'] = false;
+
+/**
+ *
+ *
+ * @global boolean $cfg['Export']['quick_export_onserver']
+ */
+$cfg['Export']['quick_export_onserver'] = false;
+
+/**
+ *
+ *
+ * @global boolean $cfg['Export']['quick_export_onserver_overwrite']
+ */
+$cfg['Export']['quick_export_onserver_overwrite'] = false;
+
+/**
+ *
+ *
+ * @global boolean $cfg['Export']['remember_file_template']
+ */
+$cfg['Export']['remember_file_template'] = true;
+
+/**
+ *
+ *
+ * @global string $cfg['Export']['file_template_table']
+ */
+$cfg['Export']['file_template_table'] = '@TABLE@';
+
+/**
+ *
+ *
+ * @global string $cfg['Export']['file_template_database']
+ */
+$cfg['Export']['file_template_database'] = '@DATABASE@';
+
+/**
+ *
+ *
+ * @global string $cfg['Export']['file_template_server']
+ */
+$cfg['Export']['file_template_server'] = '@SERVER@';
+
+/**
+ *
+ *
+ * @global string $cfg['Export']['codegen_structure_or_data']
+ */
+$cfg['Export']['codegen_structure_or_data'] = 'data';
+
+/**
+ *
+ *
+ * @global $cfg['Export']['codegen_format']
+ */
+$cfg['Export']['codegen_format'] = 0;
+
+/**
+ *
+ *
+ * @global boolean $cfg['Export']['ods_columns']
+ */
+$cfg['Export']['ods_columns'] = false;
+
+/**
+ *
+ *
+ * @global string $cfg['Export']['ods_null']
+ */
+$cfg['Export']['ods_null'] = 'NULL';
+
+/**
+ *
+ *
+ * @global string $cfg['Export']['odt_structure_or_data']
+ */
+$cfg['Export']['odt_structure_or_data'] = 'structure_and_data';
+
+/**
+ *
+ *
+ * @global boolean $cfg['Export']['odt_columns']
+ */
+$cfg['Export']['odt_columns'] = true;
+
+/**
+ *
+ *
+ * @global boolean $cfg['Export']['odt_relation']
+ */
+$cfg['Export']['odt_relation'] = true;
+
+/**
+ *
+ *
+ * @global boolean $cfg['Export']['odt_comments']
+ */
+$cfg['Export']['odt_comments'] = true;
+
+/**
+ *
+ *
+ * @global boolean $cfg['Export']['odt_mime']
+ */
+$cfg['Export']['odt_mime'] = true;
+
+/**
+ *
+ *
+ * @global string $cfg['Export']['odt_null']
+ */
+$cfg['Export']['odt_null'] = 'NULL';
+
+/**
+ *
+ *
+ * @global boolean $cfg['Export']['htmlword_structure_or_data']
+ */
+$cfg['Export']['htmlword_structure_or_data'] = 'structure_and_data';
+
+/**
+ *
+ *
+ * @global boolean $cfg['Export']['htmlword_columns']
+ */
+$cfg['Export']['htmlword_columns'] = false;
+
+/**
+ *
+ *
+ * @global string $cfg['Export']['htmlword_null']
+ */
+$cfg['Export']['htmlword_null'] = 'NULL';
+
+/**
+ *
+ *
+ * @global string $cfg['Export']['texytext_structure_or_data']
+ */
+$cfg['Export']['texytext_structure_or_data'] = 'structure_and_data';
+
+/**
+ *
+ *
+ * @global boolean $cfg['Export']['texytext_columns']
+ */
+$cfg['Export']['texytext_columns'] = false;
+
+/**
+ *
+ *
+ * @global string $cfg['Export']['texytext_null']
+ */
+$cfg['Export']['texytext_null'] = 'NULL';
+
+/**
+ *
+ *
+ * @global boolean $cfg['Export']['xls_columns']
+ */
+$cfg['Export']['xls_columns'] = false;
+
+/**
+ *
+ *
+ * @global string $cfg['Export']['xls_structure_or_data']
+ */
+$cfg['Export']['xls_structure_or_data'] = 'data';
+
+/**
+ *
+ *
+ * @global string $cfg['Export']['xls_null']
+ */
+$cfg['Export']['xls_null'] = 'NULL';
+
+/**
+ *
+ *
+ * @global boolean $cfg['Export']['xlsx_columns']
+ */
+$cfg['Export']['xlsx_columns'] = false;
+
+/**
+ *
+ *
+ * @global string $cfg['Export']['xlsx_structure_or_data']
+ */
+$cfg['Export']['xlsx_structure_or_data'] = 'data';
+
+/**
+ *
+ *
+ * @global string $cfg['Export']['xlsx_null']
+ */
+$cfg['Export']['xlsx_null'] = 'NULL';
+
+/**
+ *
+ *
+ * @global boolean $cfg['Export']['csv_columns']
+ */
+$cfg['Export']['csv_columns'] = false;
+
+/**
+ *
+ *
+ * @global string $cfg['Export']['csv_structure_or_data']
+ */
+$cfg['Export']['csv_structure_or_data'] = 'data';
+
+/**
+ *
+ *
+ * @global string $cfg['Export']['csv_null']
+ */
+$cfg['Export']['csv_null'] = 'NULL';
+
+/**
+ *
+ *
+ * @global string $cfg['Export']['csv_separator']
+ */
+$cfg['Export']['csv_separator'] = ',';
+
+/**
+ *
+ *
+ * @global string $cfg['Export']['csv_enclosed']
+ */
+$cfg['Export']['csv_enclosed'] = '"';
+
+/**
+ *
+ *
+ * @global string $cfg['Export']['csv_escaped']
+ */
+$cfg['Export']['csv_escaped'] = '"';
+
+/**
+ *
+ *
+ * @global string $cfg['Export']['csv_terminated']
+ */
+$cfg['Export']['csv_terminated'] = 'AUTO';
+
+/**
+ *
+ *
+ * @global string $cfg['Export']['csv_removeCRLF']
+ */
+$cfg['Export']['csv_removeCRLF'] = false;
+
+/**
+ *
+ *
+ * @global boolean $cfg['Export']['excel_columns']
+ */
+$cfg['Export']['excel_columns'] = false;
+
+/**
+ *
+ *
+ * @global string $cfg['Export']['excel_null']
+ */
+$cfg['Export']['excel_null'] = 'NULL';
+
+/**
+ * win/mac
+ *
+ * @global string $cfg['Export']['excel_edition']
+ */
+$cfg['Export']['excel_edition'] = 'win';
+
+/**
+ *
+ *
+ * @global string $cfg['Export']['excel_removeCRLF']
+ */
+$cfg['Export']['excel_removeCRLF'] = false;
+
+/**
+ *
+ *
+ * @global string $cfg['Export']['excel_structure_or_data']
+ */
+$cfg['Export']['excel_structure_or_data'] = 'data';
+
+/**
+ *
+ *
+ * @global string $cfg['Export']['latex_structure_or_data']
+ */
+$cfg['Export']['latex_structure_or_data'] = 'structure_and_data';
+
+/**
+ *
+ *
+ * @global boolean $cfg['Export']['latex_columns']
+ */
+$cfg['Export']['latex_columns'] = true;
+
+/**
+ *
+ *
+ * @global boolean $cfg['Export']['latex_relation']
+ */
+$cfg['Export']['latex_relation'] = true;
+
+/**
+ *
+ *
+ * @global boolean $cfg['Export']['latex_comments']
+ */
+$cfg['Export']['latex_comments'] = true;
+
+/**
+ *
+ *
+ * @global boolean $cfg['Export']['latex_mime']
+ */
+$cfg['Export']['latex_mime'] = true;
+
+/**
+ *
+ *
+ * @global string $cfg['Export']['latex_null']
+ */
+$cfg['Export']['latex_null'] = '\textit{NULL}';
+
+/**
+ *
+ *
+ * @global boolean $cfg['Export']['latex_caption']
+ */
+$cfg['Export']['latex_caption'] = true;
+
+/**
+ *
+ *
+ * @global string $cfg['Export']['latex_structure_caption']
+ */
+$cfg['Export']['latex_structure_caption'] = 'strLatexStructure';
+
+/**
+ *
+ *
+ * @global string $cfg['Export']['latex_structure_continued_caption']
+ */
+$cfg['Export']['latex_structure_continued_caption']
+ = 'strLatexStructure strLatexContinued';
+
+/**
+ *
+ *
+ * @global string $cfg['Export']['latex_data_caption']
+ */
+$cfg['Export']['latex_data_caption'] = 'strLatexContent';
+
+/**
+ *
+ *
+ * @global string $cfg['Export']['latex_data_continued_caption']
+ */
+$cfg['Export']['latex_data_continued_caption'] = 'strLatexContent strLatexContinued';
+
+/**
+ *
+ *
+ * @global string $cfg['Export']['latex_data_label']
+ */
+$cfg['Export']['latex_data_label'] = 'tab:@TABLE@-data';
+
+/**
+ *
+ *
+ * @global string $cfg['Export']['latex_structure_label']
+ */
+$cfg['Export']['latex_structure_label'] = 'tab:@TABLE@-structure';
+
+/**
+ *
+ *
+ * @global string $cfg['Export']['mediawiki_structure_or_data']
+ */
+$cfg['Export']['mediawiki_structure_or_data'] = 'data';
+
+/**
+ *
+ *
+ * @global boolean $cfg['Export']['mediawiki_caption']
+ */
+
+$cfg['Export']['mediawiki_caption'] = true;
+/**
+ *
+ *
+ * @global boolean $cfg['Export']['mediawiki_headers']
+ */
+$cfg['Export']['mediawiki_headers'] = true;
+
+/**
+ *
+ *
+ * @global string $cfg['Export']['ods_structure_or_data']
+ */
+$cfg['Export']['ods_structure_or_data'] = 'data';
+
+/**
+ *
+ *
+ * @global string $cfg['Export']['pdf_structure_or_data']
+ */
+$cfg['Export']['pdf_structure_or_data'] = 'data';
+
+/**
+ *
+ *
+ * @global string $cfg['Export']['phparray_structure_or_data']
+ */
+$cfg['Export']['phparray_structure_or_data'] = 'data';
+
+/**
+ *
+ *
+ * @global string $cfg['Export']['json_structure_or_data']
+ */
+$cfg['Export']['json_structure_or_data'] = 'data';
+
+/**
+ *
+ *
+ * @global string $cfg['Export']['sql_structure_or_data']
+ */
+$cfg['Export']['sql_structure_or_data'] = 'structure_and_data';
+
+/**
+ *
+ *
+ * @global string $cfg['Export']['sql_compatibility']
+ */
+$cfg['Export']['sql_compatibility'] = 'NONE';
+
+/**
+ * Whether to include comments in SQL export.
+ *
+ * @global string $cfg['Export']['sql_include_comments']
+ */
+$cfg['Export']['sql_include_comments'] = true;
+
+/**
+ *
+ *
+ * @global boolean $cfg['Export']['sql_disable_fk']
+ */
+$cfg['Export']['sql_disable_fk'] = false;
+/**
+ *
+ *
+ * @global boolean $cfg['Export']['sql_views_as_tables']
+ */
+$cfg['Export']['sql_views_as_tables'] = false;
+
+/**
+ *
+ *
+ * @global boolean $cfg['Export']['sql_use_transaction']
+ */
+$cfg['Export']['sql_use_transaction'] = false;
+
+/**
+ *
+ *
+ * @global boolean $cfg['Export']['sql_create_database']
+ */
+$cfg['Export']['sql_create_database'] = false;
+
+/**
+ *
+ *
+ * @global boolean $cfg['Export']['sql_drop_database']
+ */
+$cfg['Export']['sql_drop_database'] = false;
+
+/**
+ *
+ *
+ * @global boolean $cfg['Export']['sql_drop_table']
+ */
+$cfg['Export']['sql_drop_table'] = false;
+
+/**
+ *
+ *
+ * true by default for correct behavior when dealing with exporting
+ * of VIEWs and the stand-in table
+ * @global boolean $cfg['Export']['sql_if_not_exists']
+ */
+$cfg['Export']['sql_if_not_exists'] = true;
+
+/**
+ *
+ *
+ * @global boolean $cfg['Export']['sql_procedure_function']
+ */
+$cfg['Export']['sql_procedure_function'] = true;
+
+/**
+ *
+ *
+ * @global boolean $cfg['Export']['sql_auto_increment']
+ */
+$cfg['Export']['sql_auto_increment'] = true;
+
+/**
+ *
+ *
+ * @global boolean $cfg['Export']['sql_backquotes']
+ */
+$cfg['Export']['sql_backquotes'] = true;
+
+/**
+ *
+ *
+ * @global boolean $cfg['Export']['sql_dates']
+ */
+$cfg['Export']['sql_dates'] = false;
+
+/**
+ *
+ *
+ * @global boolean $cfg['Export']['sql_relation']
+ */
+$cfg['Export']['sql_relation'] = false;
+
+/**
+ *
+ *
+ * @global boolean $cfg['Export']['sql_truncate']
+ */
+$cfg['Export']['sql_truncate'] = false;
+
+/**
+ *
+ *
+ * @global boolean $cfg['Export']['sql_delayed']
+ */
+$cfg['Export']['sql_delayed'] = false;
+
+/**
+ *
+ *
+ * @global boolean $cfg['Export']['sql_ignore']
+ */
+$cfg['Export']['sql_ignore'] = false;
+
+/**
+ * Export time in UTC.
+ *
+ * @global boolean $cfg['Export']['sql_utc_time']
+ */
+$cfg['Export']['sql_utc_time'] = true;
+
+/**
+ *
+ *
+ * @global boolean $cfg['Export']['sql_hex_for_blob']
+ */
+$cfg['Export']['sql_hex_for_blob'] = true;
+
+/**
+ * insert/update/replace
+ *
+ * @global string $cfg['Export']['sql_type']
+ */
+$cfg['Export']['sql_type'] = 'INSERT';
+
+/**
+ *
+ *
+ * @global integer $cfg['Export']['sql_max_query_size']
+ */
+$cfg['Export']['sql_max_query_size'] = 50000;
+
+/**
+ *
+ *
+ * @global boolean $cfg['Export']['sql_mime']
+ */
+$cfg['Export']['sql_mime'] = false;
+
+/**
+ * \n is replaced by new line
+ *
+ * @global string $cfg['Export']['sql_header_comment']
+ */
+$cfg['Export']['sql_header_comment'] = '';
+
+/**
+ *
+ *
+ * @global boolean $cfg['Export']['sql_create_table_statements']
+ */
+$cfg['Export']['sql_create_table_statements'] = true;
+
+/**
+ * Whether to use complete inserts, extended inserts, both, or neither
+ *
+ * @global string $cfg['Export']['sql_insert_syntax']
+ */
+$cfg['Export']['sql_insert_syntax'] = 'both';
+
+/**
+ *
+ *
+ * @global string $cfg['Export']['pdf_report_title']
+ */
+$cfg['Export']['pdf_report_title'] = '';
+
+/**
+ *
+ *
+ *@global string $cfg['Export']['xml_structure_or_data']
+ */
+$cfg['Export']['xml_structure_or_data'] = 'data';
+
+/**
+ * Export schema for each structure
+ *
+ * @global string $cfg['Export']['xml_export_struc']
+ */
+$cfg['Export']['xml_export_struc'] = true;
+
+/**
+ * Export functions
+ *
+ * @global string $cfg['Export']['xml_export_functions']
+ */
+$cfg['Export']['xml_export_functions'] = true;
+
+/**
+ * Export procedures
+ *
+ * @global string $cfg['Export']['xml_export_procedures']
+ */
+$cfg['Export']['xml_export_procedures'] = true;
+
+/**
+ * Export schema for each table
+ *
+ * @global string $cfg['Export']['xml_export_tables']
+ */
+$cfg['Export']['xml_export_tables'] = true;
+
+/**
+ * Export triggers
+ *
+ * @global string $cfg['Export']['xml_export_triggers']
+ */
+$cfg['Export']['xml_export_triggers'] = true;
+
+/**
+ * Export views
+ *
+ * @global string $cfg['Export']['xml_export_views']
+ */
+$cfg['Export']['xml_export_views'] = true;
+
+/**
+ * Export contents data
+ *
+ * @global string $cfg['Export']['xml_export_contents']
+ */
+$cfg['Export']['xml_export_contents'] = true;
+
+/**
+ *
+ *
+ * @global string $cfg['Export']['yaml_structure_or_data']
+ */
+$cfg['Export']['yaml_structure_or_data'] = 'data';
+
+/*******************************************************************************
+ * Import defaults
+ */
+$cfg['Import'] = array();
+
+/**
+ *
+ *
+ * @global string $cfg['Import']['format']
+ */
+$cfg['Import']['format'] = 'sql';
+
+/**
+ * Default charset for import.
+ *
+ * @global string $cfg['Import']['charset']
+ */
+$cfg['Import']['charset'] = '';
+
+/**
+ *
+ *
+ * @global boolean $cfg['Import']['allow_interrupt']
+ */
+$cfg['Import']['allow_interrupt'] = true;
+
+/**
+ *
+ *
+ * @global integer $cfg['Import']['skip_queries']
+ */
+$cfg['Import']['skip_queries'] = 0;
+
+/**
+ *
+ *
+ * @global string $cfg['Import']['sql_compatibility']
+ */
+$cfg['Import']['sql_compatibility'] = 'NONE';
+
+/**
+ *
+ *
+ * @global string $cfg['Import']['sql_no_auto_value_on_zero']
+ */
+$cfg['Import']['sql_no_auto_value_on_zero'] = true;
+
+/**
+ *
+ *
+ * @global boolean $cfg['Import']['csv_replace']
+ */
+$cfg['Import']['csv_replace'] = false;
+
+/**
+ *
+ *
+ * @global boolean $cfg['Import']['csv_ignore']
+ */
+$cfg['Import']['csv_ignore'] = false;
+
+/**
+ *
+ *
+ * @global string $cfg['Import']['csv_terminated']
+ */
+$cfg['Import']['csv_terminated'] = ',';
+
+/**
+ *
+ *
+ * @global string $cfg['Import']['csv_enclosed']
+ */
+$cfg['Import']['csv_enclosed'] = '"';
+
+/**
+ *
+ *
+ * @global string $cfg['Import']['csv_escaped']
+ */
+$cfg['Import']['csv_escaped'] = '"';
+
+/**
+ *
+ *
+ * @global string $cfg['Import']['csv_new_line']
+ */
+$cfg['Import']['csv_new_line'] = 'auto';
+
+/**
+ *
+ *
+ * @global string $cfg['Import']['csv_columns']
+ */
+$cfg['Import']['csv_columns'] = '';
+
+/**
+ *
+ *
+ * @global string $cfg['Import']['csv_col_names']
+ */
+$cfg['Import']['csv_col_names'] = false;
+
+/**
+ *
+ *
+ * @global boolean $cfg['Import']['ldi_replace']
+ */
+$cfg['Import']['ldi_replace'] = false;
+
+/**
+ *
+ *
+ * @global boolean $cfg['Import']['ldi_ignore']
+ */
+$cfg['Import']['ldi_ignore'] = false;
+
+/**
+ *
+ *
+ * @global string $cfg['Import']['ldi_terminated']
+ */
+$cfg['Import']['ldi_terminated'] = ';';
+
+/**
+ *
+ *
+ * @global string $cfg['Import']['ldi_enclosed']
+ */
+$cfg['Import']['ldi_enclosed'] = '"';
+
+/**
+ *
+ *
+ * @global string $cfg['Import']['ldi_escaped']
+ */
+$cfg['Import']['ldi_escaped'] = '\\';
+
+/**
+ *
+ *
+ * @global string $cfg['Import']['ldi_new_line']
+ */
+$cfg['Import']['ldi_new_line'] = 'auto';
+
+/**
+ *
+ *
+ * @global string $cfg['Import']['ldi_columns']
+ */
+$cfg['Import']['ldi_columns'] = '';
+
+/**
+ * 'auto' for auto-detection, true or false for forcing
+ *
+ * @global string $cfg['Import']['ldi_local_option']
+ */
+$cfg['Import']['ldi_local_option'] = 'auto';
+
+/**
+ *
+ *
+ * @global string $cfg['Import']['ods_col_names']
+ */
+$cfg['Import']['ods_col_names'] = false;
+
+/**
+ *
+ *
+ * @global string $cfg['Import']['ods_empty_rows']
+ */
+$cfg['Import']['ods_empty_rows'] = true;
+
+/**
+ *
+ *
+ * @global string $cfg['Import']['ods_recognize_percentages']
+ */
+$cfg['Import']['ods_recognize_percentages'] = true;
+
+/**
+ *
+ *
+ * @global string $cfg['Import']['ods_recognize_currency']
+ */
+$cfg['Import']['ods_recognize_currency'] = true;
+
+/**
+ *
+ *
+ * @global string $cfg['Import']['xml_col_names']
+ */
+$cfg['Import']['xls_col_names'] = false;
+
+/**
+ *
+ *
+ * @global string $cfg['Import']['xml_empty_rows']
+ */
+$cfg['Import']['xls_empty_rows'] = true;
+
+/**
+ *
+ *
+ * @global string $cfg['Import']['xlsx_col_names']
+ */
+$cfg['Import']['xlsx_col_names'] = false;
+
+/*******************************************************************************
+ * PDF options
+ */
+
+/**
+ *
+ *
+ * @global array $cfg['PDFPageSizes']
+ */
+$cfg['PDFPageSizes'] = array('A3', 'A4', 'A5', 'letter', 'legal');
+
+/**
+ *
+ *
+ * @global string $cfg['PDFDefaultPageSize']
+ */
+$cfg['PDFDefaultPageSize'] = 'A4';
+
+
+/*******************************************************************************
+ * Language and character set conversion settings
+ */
+
+/**
+ * Default language to use, if not browser-defined or user-defined
+ *
+ * @global string $cfg['DefaultLang']
+ */
+$cfg['DefaultLang'] = 'en';
+
+/**
+ * Default connection collation
+ *
+ * @global string $cfg['DefaultConnectionCollation']
+ */
+$cfg['DefaultConnectionCollation'] = 'utf8_general_ci';
+
+/**
+ * Force: always use this language
+ * $cfg['Lang'] = 'en';
+ *
+ * Regular expression to limit listed languages, e.g. '^(cs|en)' for Czech and
+ * English only
+ *
+ * @global string $cfg['FilterLanguages']
+ */
+$cfg['FilterLanguages'] = '';
+
+/**
+ * You can select here which functions will be used for character set conversion.
+ * Possible values are:
+ * auto - automatically use available one (first is tested iconv, then
+ * recode)
+ * iconv - use iconv or libiconv functions
+ * recode - use recode_string function
+ * mb - use mbstring extension
+ * none - disable encoding conversion
+ *
+ * @global string $cfg['RecodingEngine']
+ */
+$cfg['RecodingEngine'] = 'auto';
+
+/**
+ * Specify some parameters for iconv used in character set conversion. See iconv
+ * documentation for details:
+ * http://www.gnu.org/software/libiconv/documentation/libiconv/iconv_open.3.html
+ *
+ * @global string $cfg['IconvExtraParams']
+ */
+$cfg['IconvExtraParams'] = '//TRANSLIT';
+
+/**
+ * Available character sets for MySQL conversion. currently contains all which could
+ * be found in lang/* files and few more.
+ * Character sets will be shown in same order as here listed, so if you frequently
+ * use some of these move them to the top.
+ *
+ * @global array $cfg['AvailableCharsets']
+ */
+$cfg['AvailableCharsets'] = array(
+ 'iso-8859-1',
+ 'iso-8859-2',
+ 'iso-8859-3',
+ 'iso-8859-4',
+ 'iso-8859-5',
+ 'iso-8859-6',
+ 'iso-8859-7',
+ 'iso-8859-8',
+ 'iso-8859-9',
+ 'iso-8859-10',
+ 'iso-8859-11',
+ 'iso-8859-12',
+ 'iso-8859-13',
+ 'iso-8859-14',
+ 'iso-8859-15',
+ 'windows-1250',
+ 'windows-1251',
+ 'windows-1252',
+ 'windows-1256',
+ 'windows-1257',
+ 'koi8-r',
+ 'big5',
+ 'gb2312',
+ 'utf-16',
+ 'utf-8',
+ 'utf-7',
+ 'x-user-defined',
+ 'euc-jp',
+ 'ks_c_5601-1987',
+ 'tis-620',
+ 'SHIFT_JIS'
+);
+
+
+/*******************************************************************************
+ * Customization & design
+ *
+ * The graphical settings are now located in themes/theme-name/layout.inc.php
+ */
+
+/**
+ * enable the left panel pointer
+ * see also LeftPointerColor
+ * in layout.inc.php
+ *
+ * @global boolean $cfg['NavigationTreePointerEnable']
+ */
+$cfg['NavigationTreePointerEnable'] = true;
+
+/**
+ * enable the browse pointer
+ * see also BrowsePointerColor
+ * in layout.inc.php
+ *
+ * @global boolean $cfg['BrowsePointerEnable']
+ */
+$cfg['BrowsePointerEnable'] = true;
+
+/**
+ * enable the browse marker
+ * see also BrowseMarkerColor
+ * in layout.inc.php
+ *
+ * @global boolean $cfg['BrowseMarkerEnable']
+ */
+$cfg['BrowseMarkerEnable'] = true;
+
+/**
+ * textarea size (columns) in edit mode
+ * (this value will be emphasized (*2) for SQL
+ * query textareas and (*1.25) for query window)
+ *
+ * @global integer $cfg['TextareaCols']
+ */
+$cfg['TextareaCols'] = 40;
+
+/**
+ * textarea size (rows) in edit mode
+ *
+ * @global integer $cfg['TextareaRows']
+ */
+$cfg['TextareaRows'] = 15;
+
+/**
+ * double size of textarea size for LONGTEXT columns
+ *
+ * @global boolean $cfg['LongtextDoubleTextarea']
+ */
+$cfg['LongtextDoubleTextarea'] = true;
+
+/**
+ * auto-select when clicking in the textarea of the query-box
+ *
+ * @global boolean $cfg['TextareaAutoSelect']
+ */
+$cfg['TextareaAutoSelect'] = false;
+
+/**
+ * textarea size (columns) for CHAR/VARCHAR
+ *
+ * @global integer $cfg['CharTextareaCols']
+ */
+$cfg['CharTextareaCols'] = 40;
+
+/**
+ * textarea size (rows) for CHAR/VARCHAR
+ *
+ * @global integer $cfg['CharTextareaRows']
+ */
+$cfg['CharTextareaRows'] = 2;
+
+/**
+ * Max field data length in browse mode for all non-numeric fields
+ *
+ * @global integer $cfg['LimitChars']
+ */
+$cfg['LimitChars'] = 50;
+
+/**
+ * Where to show the edit/copy/delete links in browse mode
+ * Possible values are 'left', 'right', 'both' and 'none';
+ * which will be interpreted as 'top', 'bottom', 'both' and 'none'
+ * respectively for vertical display mode
+ *
+ * @global string $cfg['RowActionLinks']
+ */
+$cfg['RowActionLinks'] = 'left';
+
+/**
+ * default display direction (horizontal|vertical|horizontalflipped)
+ *
+ * @global string $cfg['DefaultDisplay']
+ */
+$cfg['DefaultDisplay'] = 'horizontal';
+
+/**
+ * remember the last way a table sorted
+ *
+ * @global string $cfg['RememberSorting']
+ */
+$cfg['RememberSorting'] = true;
+
+/**
+ * table-header rotation via faking or CSS? (css|fake|auto)
+ * NOTE: CSS only works in IE browsers!
+ *
+ * @global string $cfg['HeaderFlipType']
+ */
+$cfg['HeaderFlipType'] = 'auto';
+
+/**
+ * shows stored relation-comments in 'browse' mode.
+ *
+ * @global boolean $cfg['ShowBrowseComments']
+ */
+$cfg['ShowBrowseComments'] = true;
+
+/**
+ * shows stored relation-comments in 'table property' mode.
+ *
+ * @global boolean $cfg['ShowPropertyComments']
+ */
+$cfg['ShowPropertyComments']= true;
+
+/**
+ * shows table display direction.
+ */
+$cfg['ShowDisplayDirection'] = false;
+
+/**
+ * repeat header names every X cells? (0 = deactivate)
+ *
+ * @global integer $cfg['RepeatCells']
+ */
+$cfg['RepeatCells'] = 100;
+
+/**
+ * Set to true if Edit link should open the query to edit in the query window
+ * and to false if we should edit in the right panel
+ *
+ * @global boolean $cfg['EditInWindow']
+ */
+$cfg['EditInWindow'] = true;
+
+/**
+ * Width of Query window
+ *
+ * @global integer $cfg['QueryWindowWidth']
+ */
+$cfg['QueryWindowWidth'] = 550;
+
+/**
+ * Height of Query window
+ *
+ * @global integer $cfg['QueryWindowHeight']
+ */
+$cfg['QueryWindowHeight'] = 310;
+
+/**
+ * Set to true if you want DB-based query history.If false, this utilizes
+ * JS-routines to display query history (lost by window close)
+ *
+ * @global boolean $cfg['QueryHistoryDB']
+ */
+$cfg['QueryHistoryDB'] = false;
+
+/**
+ * which tab to display in the querywindow on startup
+ * (sql|files|history|full)
+ *
+ * @global string $cfg['QueryWindowDefTab']
+ */
+$cfg['QueryWindowDefTab'] = 'sql';
+
+/**
+ * When using DB-based query history, how many entries should be kept?
+ *
+ * @global integer $cfg['QueryHistoryMax']
+ */
+$cfg['QueryHistoryMax'] = 25;
+
+/**
+ * Use MIME-Types (stored in column comments table) for
+ *
+ * @global boolean $cfg['BrowseMIME']
+ */
+$cfg['BrowseMIME'] = true;
+
+/**
+ * When approximate count < this, PMA will get exact count for table rows.
+ *
+ * @global integer $cfg['MaxExactCount']
+ */
+$cfg['MaxExactCount'] = 0;
+
+/**
+ * Zero means that no row count is done for views; see the doc
+ *
+ * @global integer $cfg['MaxExactCountViews']
+ */
+$cfg['MaxExactCountViews'] = 0;
+
+/**
+ * Sort table and database in natural order
+ *
+ * @global boolean $cfg['NaturalOrder']
+ */
+$cfg['NaturalOrder'] = true;
+
+/**
+ * Initial state for sliders
+ * (open | closed | disabled)
+ *
+ * @global string $cfg['InitialSlidersState']
+ */
+$cfg['InitialSlidersState'] = 'closed';
+
+/**
+ * User preferences: disallow these settings
+ * For possible setting names look in libraries/config/user_preferences.forms.php
+ *
+ * @global array $cfg['UserprefsDisallow']
+ */
+$cfg['UserprefsDisallow'] = array();
+
+/**
+ * User preferences: enable the Developer tab
+ */
+$cfg['UserprefsDeveloperTab'] = false;
+
+/*******************************************************************************
+ * Window title settings
+ */
+
+/**
+ * title of browser window when a table is selected
+ *
+ * @global string $cfg['TitleTable']
+ */
+$cfg['TitleTable'] = '@HTTP_HOST@ / @VSERVER@ / @DATABASE@ / @TABLE@ | @PHPMYADMIN@';
+
+/**
+ * title of browser window when a database is selected
+ *
+ * @global string $cfg['TitleDatabase']
+ */
+$cfg['TitleDatabase'] = '@HTTP_HOST@ / @VSERVER@ / @DATABASE@ | @PHPMYADMIN@';
+
+/**
+ * title of browser window when a server is selected
+ *
+ * @global string $cfg['TitleServer']
+ */
+$cfg['TitleServer'] = '@HTTP_HOST@ / @VSERVER@ | @PHPMYADMIN@';
+
+/**
+ * title of browser window when nothing is selected
+ * @global string $cfg['TitleDefault']
+ */
+$cfg['TitleDefault'] = '@HTTP_HOST@ | @PHPMYADMIN@';
+
+
+/*******************************************************************************
+ * theme manager
+ */
+
+/**
+ * using themes manager please set up here the path to 'themes' else leave empty
+ *
+ * @global string $cfg['ThemePath']
+ */
+$cfg['ThemePath'] = './themes';
+
+/**
+ * if you want to use selectable themes and if ThemesPath not empty
+ * set it to true, else set it to false (default is false);
+ *
+ * @global boolean $cfg['ThemeManager']
+ */
+$cfg['ThemeManager'] = true;
+
+/**
+ * set up default theme, if ThemePath not empty you can set up here an valid
+ * path to themes or 'original' for the original pma-theme
+ *
+ * @global string $cfg['ThemeDefault']
+ */
+$cfg['ThemeDefault'] = 'pmahomme';
+
+/**
+ * allow different theme for each configured server
+ *
+ * @global boolean $cfg['ThemePerServer']
+ */
+$cfg['ThemePerServer'] = false;
+
+
+/*******************************************************************************
+ *
+ */
+
+/**
+ * Default query for table
+ *
+ * @global string $cfg['DefaultQueryTable']
+ */
+$cfg['DefaultQueryTable'] = 'SELECT * FROM @TABLE@ WHERE 1';
+
+/**
+ * Default query for database
+ *
+ * @global string $cfg['DefaultQueryDatabase']
+ */
+$cfg['DefaultQueryDatabase'] = '';
+
+
+/*******************************************************************************
+ * SQL Query box settings
+ * These are the links display in all of the SQL Query boxes
+ *
+ * @global array $cfg['SQLQuery']
+ */
+$cfg['SQLQuery'] = array();
+
+/**
+ * Edit link to change a query
+ *
+ * @global boolean $cfg['SQLQuery']['Edit']
+ */
+$cfg['SQLQuery']['Edit'] = true;
+
+/**
+ * EXPLAIN on SELECT queries
+ *
+ * @global boolean $cfg['SQLQuery']['Explain']
+ */
+$cfg['SQLQuery']['Explain'] = true;
+
+/**
+ * Wrap a query in PHP
+ *
+ * @global boolean $cfg['SQLQuery']['ShowAsPHP']
+ */
+$cfg['SQLQuery']['ShowAsPHP'] = true;
+
+/**
+ * Validate a query (see $cfg['SQLValidator'] as well)
+ *
+ * @global boolean $cfg['SQLQuery']['Validate']
+ */
+$cfg['SQLQuery']['Validate'] = false;
+
+/**
+ * Refresh the results page
+ *
+ * @global boolean $cfg['SQLQuery']['Refresh']
+ */
+$cfg['SQLQuery']['Refresh'] = true;
+
+
+/*******************************************************************************
+ * Web server upload/save/import directories
+ */
+
+/**
+ * Directory for uploaded files that can be executed by phpMyAdmin.
+ * For example './upload'. Leave empty for no upload directory support.
+ * Use %u for username inclusion.
+ *
+ * @global string $cfg['UploadDir']
+ */
+$cfg['UploadDir'] = '';
+
+/**
+ * Directory where phpMyAdmin can save exported data on server.
+ * For example './save'. Leave empty for no save directory support.
+ * Use %u for username inclusion.
+ *
+ * @global string $cfg['SaveDir']
+ */
+$cfg['SaveDir'] = '';
+
+/**
+ * Directory where phpMyAdmin can save temporary files.
+ *
+ * @global string $cfg['TempDir']
+ */
+$cfg['TempDir'] = '';
+
+
+/**
+ * Misc. settings
+ */
+
+/**
+ * Is GD >= 2 available? Set to yes/no/auto. 'auto' does auto-detection,
+ * which is the only safe way to determine GD version.
+ *
+ * @global string $cfg['GD2Available']
+ */
+$cfg['GD2Available'] = 'auto';
+
+/**
+ * Lists proxy IP and HTTP header combinations which are trusted for IP allow/deny
+ *
+ * @global array $cfg['TrustedProxies']
+ */
+$cfg['TrustedProxies'] = array();
+
+/**
+ * We normally check the permissions on the configuration file to ensure
+ * it's not world writable. However, phpMyAdmin could be installed on
+ * a NTFS filesystem mounted on a non-Windows server, in which case the
+ * permissions seems wrong but in fact cannot be detected. In this case
+ * a sysadmin would set the following to false.
+ */
+$cfg['CheckConfigurationPermissions'] = true;
+
+/**
+ * Limit for length of URL in links. When length would be above this limit, it
+ * is replaced by form with button.
+ * This is required as some web servers (IIS) have problems with long URLs.
+ * The recommended limit is 2000
+ * (see http://www.boutell.com/newfaq/misc/urllength.html) but we put
+ * 1000 to accommodate Suhosin, see bug #3358750.
+ */
+$cfg['LinkLengthLimit'] = 1000;
+
+/**
+ * Additional string to allow in CSP headers.
+ */
+ $cfg['CSPAllow'] = '';
+
+/**
+ * Disable the table maintenance mass operations, like optimizing or
+ * repairing the selected tables of a database. An accidental execution
+ * of such a maintenance task can enormously slow down a bigger database.
+ */
+$cfg['DisableMultiTableMaintenance'] = false;
+
+/**
+ * Whether or not to query the user before sending the error report to
+ * the phpMyAdmin team when a JavaScript error occurs
+ *
+ * Available options
+ * (ask | always | never)
+ *
+ * @global string $cfg['SendErrorReports']
+ */
+$cfg['SendErrorReports'] = 'ask';
+
+/*******************************************************************************
+ * If you wish to use the SQL Validator service, you should be aware of the
+ * following:
+ * All SQL statements are stored anonymously for statistical purposes.
+ * Mimer SQL Validator, Copyright 2002 Upright Database Technology.
+ * All rights reserved.
+ *
+ * @global array $cfg['SQLValidator']
+ */
+$cfg['SQLValidator'] = array();
+
+/**
+ * Make the SQL Validator available
+ *
+ * @global boolean $cfg['SQLValidator']['use']
+ */
+$cfg['SQLValidator']['use'] = false;
+
+/**
+ * If you have a custom username, specify it here (defaults to anonymous)
+ *
+ * @global string $cfg['SQLValidator']['username']
+ */
+$cfg['SQLValidator']['username'] = '';
+
+/**
+ * Password for username
+ *
+ * @global string $cfg['SQLValidator']['password']
+ */
+$cfg['SQLValidator']['password'] = '';
+
+
+/*******************************************************************************
+ * Developers ONLY!
+ *
+ * @global array $cfg['DBG']
+ */
+$cfg['DBG'] = array();
+
+/**
+ * Output executed queries and their execution times
+ *
+ * @global boolean $cfg['DBG']['sql']
+ */
+$cfg['DBG']['sql'] = false;
+
+/**
+ * Enable to let server present itself as demo server.
+ *
+ * @global boolean $cfg['DBG']['demo']
+ */
+$cfg['DBG']['demo'] = false;
+
+
+/*******************************************************************************
+ * MySQL settings
+ */
+
+/**
+ * Default functions for above defined groups
+ *
+ * @global array $cfg['DefaultFunctions']
+ */
+$cfg['DefaultFunctions'] = array(
+ 'FUNC_CHAR' => '',
+ 'FUNC_DATE' => '',
+ 'FUNC_NUMBER' => '',
+ 'FUNC_SPATIAL' => 'GeomFromText',
+ 'FUNC_UUID' => 'UUID',
+ 'first_timestamp' => 'NOW',
+);
+
+/**
+ * Max rows retreived for zoom search
+ */
+$cfg['maxRowPlotLimit'] = 500;
+
+?>
diff --git a/libraries/config.values.php b/libraries/config.values.php
new file mode 100644
index 0000000000..33d6361d8f
--- /dev/null
+++ b/libraries/config.values.php
@@ -0,0 +1,263 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Database with allowed values for configuration stored in the $cfg array,
+ * used by setup script and user preferences to generate forms.
+ *
+ * @package PhpMyAdmin
+ */
+
+if (!defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ * Value meaning:
+ * o array - select field, array contains allowed values
+ * o string - type override
+ *
+ * Use normal array, paths won't be expanded
+ */
+$cfg_db = array();
+
+$cfg_db['Servers'] = array(
+ 1 => array(
+ 'port' => 'integer',
+ 'connect_type' => array('tcp', 'socket'),
+ 'extension' => array('mysql', 'mysqli'),
+ 'auth_type' => array('config', 'http', 'signon', 'cookie'),
+ 'AllowDeny' => array(
+ 'order' => array('', 'deny,allow', 'allow,deny', 'explicit')
+ ),
+ 'only_db' => 'array'
+ )
+);
+$cfg_db['RecodingEngine'] = array('auto', 'iconv', 'recode', 'mb', 'none');
+$cfg_db['OBGzip'] = array('auto', true, false);
+$cfg_db['MemoryLimit'] = 'short_string';
+$cfg_db['NavigationLogoLinkWindow'] = array('main', 'new');
+$cfg_db['NavigationTreeDefaultTabTable'] = array(
+ 'tbl_structure.php', // fields list
+ 'tbl_sql.php', // SQL form
+ 'tbl_select.php', // search page
+ 'tbl_change.php', // insert row page
+ 'sql.php' // browse page
+);
+$cfg_db['NavigationTreeDbSeparator'] = 'short_string';
+$cfg_db['NavigationTreeTableSeparator'] = 'short_string';
+$cfg_db['TableNavigationLinksMode'] = array(
+ 'icons' => __('Icons'),
+ 'text' => __('Text'),
+ 'both' => __('Both')
+);
+$cfg_db['MaxRows'] = array(25, 50, 100, 250, 500);
+$cfg_db['Order'] = array('ASC', 'DESC', 'SMART');
+$cfg_db['RowActionLinks'] = array(
+ 'none' => __('Nowhere'),
+ 'left' => __('Left'),
+ 'right' => __('Right'),
+ 'both' => __('Both')
+);
+$cfg_db['ProtectBinary'] = array(false, 'blob', 'noblob', 'all');
+$cfg_db['DefaultDisplay'] = array('horizontal', 'vertical', 'horizontalflipped');
+$cfg_db['CharEditing'] = array('input', 'textarea');
+$cfg_db['TabsMode'] = array(
+ 'icons' => __('Icons'),
+ 'text' => __('Text'),
+ 'both' => __('Both')
+);
+$cfg_db['PDFDefaultPageSize'] = array(
+ 'A3' => 'A3',
+ 'A4' => 'A4',
+ 'A5' => 'A5',
+ 'letter' => 'letter',
+ 'legal' => 'legal'
+);
+$cfg_db['ActionLinksMode'] = array(
+ 'icons' => __('Icons'),
+ 'text' => __('Text'),
+ 'both' => __('Both')
+);
+$cfg_db['GridEditing'] = array(
+ 'click' => __('Click'),
+ 'double-click' => __('Double click'),
+ 'disabled' => __('Disabled'),
+);
+$cfg_db['DefaultTabServer'] = array(
+ 'index.php', // the welcome page (recommended for multiuser setups)
+ 'server_databases.php', // list of databases
+ 'server_status.php', // runtime information
+ 'server_variables.php', // MySQL server variables
+ 'server_privileges.php' // user management
+);
+$cfg_db['DefaultTabDatabase'] = array(
+ 'db_structure.php', // tables list
+ 'db_sql.php', // SQL form
+ 'db_search.php', // search query
+ 'db_operations.php' // operations on database
+);
+$cfg_db['DefaultTabTable'] = array(
+ 'tbl_structure.php', // fields list
+ 'tbl_sql.php', // SQL form
+ 'tbl_select.php', // search page
+ 'tbl_change.php', // insert row page
+ 'sql.php' // browse page
+);
+$cfg_db['QueryWindowDefTab'] = array(
+ 'sql', // SQL
+ 'files', // Import files
+ 'history', // SQL history
+ 'full' // All (SQL and SQL history)
+);
+$cfg_db['InitialSlidersState'] = array(
+ 'open' => __('Open'),
+ 'closed' => __('Closed'),
+ 'disabled' => __('Disabled')
+);
+$cfg_db['SendErrorReports'] = array(
+ 'ask' => __('Ask before sending error reports'),
+ 'always' => __('Always send error reports'),
+ 'never' => __('Never send error reports')
+);
+$cfg_db['Import']['format'] = array(
+ 'csv', // CSV
+ 'docsql', // DocSQL
+ 'ldi', // CSV using LOAD DATA
+ 'sql' // SQL
+);
+$cfg_db['Import']['charset'] = array_merge(
+ array(''),
+ $GLOBALS['cfg']['AvailableCharsets']
+);
+$cfg_db['Import']['sql_compatibility']
+ = $cfg_db['Export']['sql_compatibility'] = array(
+ 'NONE', 'ANSI', 'DB2', 'MAXDB', 'MYSQL323',
+ 'MYSQL40', 'MSSQL', 'ORACLE',
+ // removed; in MySQL 5.0.33, this produces exports that
+ // can't be read by POSTGRESQL (see our bug #1596328)
+ //'POSTGRESQL',
+ 'TRADITIONAL'
+);
+$cfg_db['Import']['csv_terminated'] = 'short_string';
+$cfg_db['Import']['csv_enclosed'] = 'short_string';
+$cfg_db['Import']['csv_escaped'] = 'short_string';
+$cfg_db['Import']['ldi_terminated'] = 'short_string';
+$cfg_db['Import']['ldi_enclosed'] = 'short_string';
+$cfg_db['Import']['ldi_escaped'] = 'short_string';
+$cfg_db['Import']['ldi_local_option'] = array('auto', true, false);
+$cfg_db['Export']['_sod_select'] = array(
+ 'structure' => __('structure'),
+ 'data' => __('data'),
+ 'structure_and_data' => __('structure and data')
+);
+$cfg_db['Export']['method'] = array(
+ 'quick' => __('Quick - display only the minimal options to configure'),
+ 'custom' => __('Custom - display all possible options to configure'),
+ 'custom-no-form' => __('Custom - like above, but without the quick/custom choice')
+);
+$cfg_db['Export']['format'] = array(
+ 'codegen', 'csv', 'excel', 'htmlexcel','htmlword', 'latex', 'ods',
+ 'odt', 'pdf', 'sql', 'texytext', 'xls', 'xml', 'yaml'
+);
+$cfg_db['Export']['compression'] = array('none', 'zip', 'gzip');
+$cfg_db['Export']['charset'] = array_merge(
+ array(''),
+ $GLOBALS['cfg']['AvailableCharsets']
+);
+$cfg_db['Export']['codegen_format'] = array(
+ '#', 'NHibernate C# DO', 'NHibernate XML'
+);
+$cfg_db['Export']['csv_separator'] = 'short_string';
+$cfg_db['Export']['csv_terminated'] = 'short_string';
+$cfg_db['Export']['csv_enclosed'] = 'short_string';
+$cfg_db['Export']['csv_escaped'] = 'short_string';
+$cfg_db['Export']['csv_null'] = 'short_string';
+$cfg_db['Export']['excel_null'] = 'short_string';
+$cfg_db['Export']['excel_edition'] = array(
+ 'win' => 'Windows',
+ 'mac_excel2003' => 'Excel 2003 / Macintosh',
+ 'mac_excel2008' => 'Excel 2008 / Macintosh'
+);
+$cfg_db['Export']['sql_structure_or_data'] = $cfg_db['Export']['_sod_select'];
+$cfg_db['Export']['sql_type'] = array('INSERT', 'UPDATE', 'REPLACE');
+$cfg_db['Export']['sql_insert_syntax'] = array(
+ 'complete' => __('complete inserts'),
+ 'extended' => __('extended inserts'),
+ 'both' => __('both of the above'),
+ 'none' => __('neither of the above')
+);
+$cfg_db['Export']['xls_null'] = 'short_string';
+$cfg_db['Export']['xlsx_null'] = 'short_string';
+$cfg_db['Export']['htmlword_structure_or_data'] = $cfg_db['Export']['_sod_select'];
+$cfg_db['Export']['htmlword_null'] = 'short_string';
+$cfg_db['Export']['ods_null'] = 'short_string';
+$cfg_db['Export']['odt_null'] = 'short_string';
+$cfg_db['Export']['odt_structure_or_data'] = $cfg_db['Export']['_sod_select'];
+$cfg_db['Export']['texytext_structure_or_data'] = $cfg_db['Export']['_sod_select'];
+$cfg_db['Export']['texytext_null'] = 'short_string';
+
+/**
+ * Default values overrides
+ * Use only full paths
+ */
+$cfg_db['_overrides'] = array();
+$cfg_db['_overrides']['Servers/1/extension'] = extension_loaded('mysqli')
+ ? 'mysqli' : 'mysql';
+
+/**
+ * Basic validator assignments (functions from libraries/config/Validator.class.php
+ * and 'validators' object in js/config.js)
+ * Use only full paths and form ids
+ */
+$cfg_db['_validators'] = array(
+ 'CharTextareaCols' => 'validatePositiveNumber',
+ 'CharTextareaRows' => 'validatePositiveNumber',
+ 'ExecTimeLimit' => 'validateNonNegativeNumber',
+ 'Export/sql_max_query_size' => 'validatePositiveNumber',
+ 'ForeignKeyMaxLimit' => 'validatePositiveNumber',
+ 'Import/csv_enclosed' => array(array('validateByRegex', '/^.?$/')),
+ 'Import/csv_escaped' => array(array('validateByRegex', '/^.$/')),
+ 'Import/csv_terminated' => array(array('validateByRegex', '/^.$/')),
+ 'Import/ldi_enclosed' => array(array('validateByRegex', '/^.?$/')),
+ 'Import/ldi_escaped' => array(array('validateByRegex', '/^.$/')),
+ 'Import/ldi_terminated' => array(array('validateByRegex', '/^.$/')),
+ 'Import/skip_queries' => 'validateNonNegativeNumber',
+ 'InsertRows' => 'validatePositiveNumber',
+ 'NumRecentTables' => 'validateNonNegativeNumber',
+ 'LimitChars' => 'validatePositiveNumber',
+ 'LoginCookieValidity' => 'validatePositiveNumber',
+ 'LoginCookieStore' => 'validateNonNegativeNumber',
+ 'MaxDbList' => 'validatePositiveNumber',
+ 'MaxNavigationItems' => 'validatePositiveNumber',
+ 'MaxCharactersInDisplayedSQL' => 'validatePositiveNumber',
+ 'MaxRows' => 'validatePositiveNumber',
+ 'MaxTableList' => 'validatePositiveNumber',
+ 'MemoryLimit' => array(array('validateByRegex', '/^(-1|(\d+(?:[kmg])?))$/i')),
+ 'NavigationTreeTableLevel' => 'validatePositiveNumber',
+ 'QueryHistoryMax' => 'validatePositiveNumber',
+ 'QueryWindowWidth' => 'validatePositiveNumber',
+ 'QueryWindowHeight' => 'validatePositiveNumber',
+ 'RepeatCells' => 'validateNonNegativeNumber',
+ 'Server' => 'validateServer',
+ 'Server_pmadb' => 'validatePMAStorage',
+ 'Servers/1/port' => 'validatePortNumber',
+ 'Servers/1/hide_db' => 'validateRegex',
+ 'TextareaCols' => 'validatePositiveNumber',
+ 'TextareaRows' => 'validatePositiveNumber',
+ 'TrustedProxies' => 'validateTrustedProxies');
+
+/**
+ * Additional validators used for user preferences
+ */
+$cfg_db['_userValidators'] = array(
+ 'MaxDbList' => array(
+ array('validateUpperBound', 'value:MaxDbList')
+ ),
+ 'MaxTableList' => array(
+ array('validateUpperBound', 'value:MaxTableList')
+ ),
+ 'QueryHistoryMax' => array(
+ array('validateUpperBound', 'value:QueryHistoryMax')
+ )
+);
+?>
diff --git a/libraries/config/ConfigFile.class.php b/libraries/config/ConfigFile.class.php
new file mode 100644
index 0000000000..7f2c22a7d2
--- /dev/null
+++ b/libraries/config/ConfigFile.class.php
@@ -0,0 +1,538 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Config file management
+ *
+ * @package PhpMyAdmin
+ */
+
+/**
+ * Config file management class.
+ * Stores its data in $_SESSION
+ *
+ * @package PhpMyAdmin
+ */
+class ConfigFile
+{
+ /**
+ * Stores default PMA config from config.default.php
+ * @var array
+ */
+ private $_defaultCfg;
+
+ /**
+ * Stores allowed values for non-standard fields
+ * @var array
+ */
+ private $_cfgDb;
+
+ /**
+ * Stores original PMA config, not modified by user preferences
+ * @var PMA_Config
+ */
+ private $_baseCfg;
+
+ /**
+ * Whether we are currently working in PMA Setup context
+ * @var bool
+ */
+ private $_isInSetup;
+
+ /**
+ * Keys which will be always written to config file
+ * @var array
+ */
+ private $_persistKeys = array();
+
+ /**
+ * Changes keys while updating config in {@link updateWithGlobalConfig()}
+ * or reading by {@link getConfig()} or {@link getConfigArray()}
+ * @var array
+ */
+ private $_cfgUpdateReadMapping = array();
+
+ /**
+ * Key filter for {@link set()}
+ * @var array|null
+ */
+ private $_setFilter;
+
+ /**
+ * Instance id (key in $_SESSION array, separate for each server -
+ * ConfigFile{server id})
+ * @var string
+ */
+ private $_id;
+
+ /**
+ * Result for {@link _flattenArray()}
+ * @var array
+ */
+ private $_flattenArrayResult;
+
+ /**
+ * Constructor
+ *
+ * @param array $base_config base configuration read from
+ * {@link PMA_Config::$base_config},
+ * use only when not in PMA Setup
+ */
+ public function __construct(array $base_config = null)
+ {
+ // load default config values
+ $cfg = &$this->_defaultCfg;
+ include './libraries/config.default.php';
+ $cfg['fontsize'] = '82%';
+
+ // load additional config information
+ $cfg_db = &$this->_cfgDb;
+ include './libraries/config.values.php';
+
+ // apply default values overrides
+ if (count($cfg_db['_overrides'])) {
+ foreach ($cfg_db['_overrides'] as $path => $value) {
+ PMA_arrayWrite($path, $cfg, $value);
+ }
+ }
+
+ $this->_baseCfg = $base_config;
+ $this->_isInSetup = is_null($base_config);
+ $this->_id = 'ConfigFile' . $GLOBALS['server'];
+ if (!isset($_SESSION[$this->_id])) {
+ $_SESSION[$this->_id] = array();
+ }
+ }
+
+ /**
+ * Sets names of config options which will be placed in config file even if
+ * they are set to their default values (use only full paths)
+ *
+ * @param array $keys the names of the config options
+ *
+ * @return void
+ */
+ public function setPersistKeys(array $keys)
+ {
+ // checking key presence is much faster than searching so move values
+ // to keys
+ $this->_persistKeys = array_flip($keys);
+ }
+
+ /**
+ * Returns flipped array set by {@link setPersistKeys()}
+ *
+ * @return array
+ */
+ public function getPersistKeysMap()
+ {
+ return $this->_persistKeys;
+ }
+
+ /**
+ * By default ConfigFile allows setting of all configuration keys, use
+ * this method to set up a filter on {@link set()} method
+ *
+ * @param array|null $keys array of allowed keys or null to remove filter
+ *
+ * @return void
+ */
+ public function setAllowedKeys($keys)
+ {
+ if ($keys === null) {
+ $this->_setFilter = null;
+ return;
+ }
+ // checking key presence is much faster than searching so move values
+ // to keys
+ $this->_setFilter = array_flip($keys);
+ }
+
+ /**
+ * Sets path mapping for updating config in
+ * {@link updateWithGlobalConfig()} or reading
+ * by {@link getConfig()} or {@link getConfigArray()}
+ *
+ * @param array $mapping Contains the mapping of "Server/config options"
+ * to "Server/1/config options"
+ *
+ * @return void
+ */
+ public function setCfgUpdateReadMapping(array $mapping)
+ {
+ $this->_cfgUpdateReadMapping = $mapping;
+ }
+
+ /**
+ * Resets configuration data
+ *
+ * @return void
+ */
+ public function resetConfigData()
+ {
+ $_SESSION[$this->_id] = array();
+ }
+
+ /**
+ * Sets configuration data (overrides old data)
+ *
+ * @param array $cfg Configuration options
+ *
+ * @return void
+ */
+ public function setConfigData(array $cfg)
+ {
+ $_SESSION[$this->_id] = $cfg;
+ }
+
+ /**
+ * Sets config value
+ *
+ * @param string $path
+ * @param mixed $value
+ * @param string $canonical_path
+ *
+ * @return void
+ */
+ public function set($path, $value, $canonical_path = null)
+ {
+ if ($canonical_path === null) {
+ $canonical_path = $this->getCanonicalPath($path);
+ }
+ // apply key whitelist
+ if ($this->_setFilter !== null
+ && ! isset($this->_setFilter[$canonical_path])
+ ) {
+ return;
+ }
+ // if the path isn't protected it may be removed
+ if (!isset($this->_persistKeys[$canonical_path])) {
+ $default_value = $this->getDefault($canonical_path);
+ $remove_path = $value === $default_value;
+ if ($this->_isInSetup) {
+ // remove if it has a default value or is empty
+ $remove_path = $remove_path
+ || (empty($value) && empty($default_value));
+ } else {
+ // get original config values not overwritten by user
+ // preferences to allow for overwriting options set in
+ // config.inc.php with default values
+ $instance_default_value = PMA_arrayRead(
+ $canonical_path,
+ $this->_baseCfg
+ );
+ // remove if it has a default value and base config (config.inc.php)
+ // uses default value
+ $remove_path = $remove_path
+ && ($instance_default_value === $default_value);
+ }
+ if ($remove_path) {
+ PMA_arrayRemove($path, $_SESSION[$this->_id]);
+ return;
+ }
+ }
+ PMA_arrayWrite($path, $_SESSION[$this->_id], $value);
+ }
+
+ /**
+ * Flattens multidimensional array, changes indices to paths
+ * (eg. 'key/subkey').
+ * Used as array_walk() callback.
+ *
+ * @param mixed $value
+ * @param mixed $key
+ * @param mixed $prefix
+ *
+ * @return void
+ */
+ private function _flattenArray($value, $key, $prefix)
+ {
+ // no recursion for numeric arrays
+ if (is_array($value) && !isset($value[0])) {
+ $prefix .= $key . '/';
+ array_walk($value, array($this, '_flattenArray'), $prefix);
+ } else {
+ $this->_flattenArrayResult[$prefix . $key] = $value;
+ }
+ }
+
+ /**
+ * Returns default config in a flattened array
+ *
+ * @return array
+ */
+ public function getFlatDefaultConfig()
+ {
+ $this->_flattenArrayResult = array();
+ array_walk($this->_defaultCfg, array($this, '_flattenArray'), '');
+ $flat_cfg = $this->_flattenArrayResult;
+ $this->_flattenArrayResult = null;
+ return $flat_cfg;
+ }
+
+ /**
+ * Updates config with values read from given array
+ * (config will contain differences to defaults from config.defaults.php).
+ *
+ * @param array $cfg
+ *
+ * @return void
+ */
+ public function updateWithGlobalConfig(array $cfg)
+ {
+ // load config array and flatten it
+ $this->_flattenArrayResult = array();
+ array_walk($cfg, array($this, '_flattenArray'), '');
+ $flat_cfg = $this->_flattenArrayResult;
+ $this->_flattenArrayResult = null;
+
+ // save values map for translating a few user preferences paths,
+ // should be complemented by code reading from generated config
+ // to perform inverse mapping
+ foreach ($flat_cfg as $path => $value) {
+ if (isset($this->_cfgUpdateReadMapping[$path])) {
+ $path = $this->_cfgUpdateReadMapping[$path];
+ }
+ $this->set($path, $value, $path);
+ }
+ }
+
+ /**
+ * Returns config value or $default if it's not set
+ *
+ * @param string $path Path of config file
+ * @param mixed $default Default values
+ *
+ * @return mixed
+ */
+ public function get($path, $default = null)
+ {
+ return PMA_arrayRead($path, $_SESSION[$this->_id], $default);
+ }
+
+ /**
+ * Returns default config value or $default it it's not set ie. it doesn't
+ * exist in config.default.php ($cfg) and config.values.php
+ * ($_cfg_db['_overrides'])
+ *
+ * @param string $canonical_path
+ * @param mixed $default
+ *
+ * @return mixed
+ */
+ public function getDefault($canonical_path, $default = null)
+ {
+ return PMA_arrayRead($canonical_path, $this->_defaultCfg, $default);
+ }
+
+ /**
+ * Returns config value, if it's not set uses the default one; returns
+ * $default if the path isn't set and doesn't contain a default value
+ *
+ * @param string $path
+ * @param mixed $default
+ *
+ * @return mixed
+ */
+ public function getValue($path, $default = null)
+ {
+ $v = PMA_arrayRead($path, $_SESSION[$this->_id], null);
+ if ($v !== null) {
+ return $v;
+ }
+ $path = $this->getCanonicalPath($path);
+ return $this->getDefault($path, $default);
+ }
+
+ /**
+ * Returns canonical path
+ *
+ * @param string $path
+ *
+ * @return string
+ */
+ public function getCanonicalPath($path)
+ {
+ return preg_replace('#^Servers/([\d]+)/#', 'Servers/1/', $path);
+ }
+
+ /**
+ * Returns config database entry for $path ($cfg_db in config_info.php)
+ *
+ * @param string $path path of the variable in config db
+ * @param mixed $default default value
+ *
+ * @return mixed
+ */
+ public function getDbEntry($path, $default = null)
+ {
+ return PMA_arrayRead($path, $this->_cfgDb, $default);
+ }
+
+ /**
+ * Returns server count
+ *
+ * @return int
+ */
+ public function getServerCount()
+ {
+ return isset($_SESSION[$this->_id]['Servers'])
+ ? count($_SESSION[$this->_id]['Servers'])
+ : 0;
+ }
+
+ /**
+ * Returns server list
+ *
+ * @return array|null
+ */
+ public function getServers()
+ {
+ return isset($_SESSION[$this->_id]['Servers'])
+ ? $_SESSION[$this->_id]['Servers']
+ : null;
+ }
+
+ /**
+ * Returns DSN of given server
+ *
+ * @param integer $server server index
+ *
+ * @return string
+ */
+ function getServerDSN($server)
+ {
+ if (!isset($_SESSION[$this->_id]['Servers'][$server])) {
+ return '';
+ }
+
+ $path = 'Servers/' . $server;
+ $dsn = $this->getValue("$path/extension") . '://';
+ if ($this->getValue("$path/auth_type") == 'config') {
+ $dsn .= $this->getValue("$path/user");
+ if (! $this->getValue("$path/nopassword")) {
+ $dsn .= ':***';
+ }
+ $dsn .= '@';
+ }
+ if ($this->getValue("$path/connect_type") == 'tcp') {
+ $dsn .= $this->getValue("$path/host");
+ $port = $this->getValue("$path/port");
+ if ($port) {
+ $dsn .= ':' . $port;
+ }
+ } else {
+ $dsn .= $this->getValue("$path/socket");
+ }
+ return $dsn;
+ }
+
+ /**
+ * Returns server name
+ *
+ * @param int $id server index
+ *
+ * @return string
+ */
+ public function getServerName($id)
+ {
+ if (!isset($_SESSION[$this->_id]['Servers'][$id])) {
+ return '';
+ }
+ $verbose = $this->get("Servers/$id/verbose");
+ if (!empty($verbose)) {
+ return $verbose;
+ }
+ $host = $this->get("Servers/$id/host");
+ return empty($host) ? 'localhost' : $host;
+ }
+
+ /**
+ * Removes server
+ *
+ * @param int $server server index
+ *
+ * @return void
+ */
+ public function removeServer($server)
+ {
+ if (!isset($_SESSION[$this->_id]['Servers'][$server])) {
+ return;
+ }
+ $last_server = $this->getServerCount();
+
+ for ($i = $server; $i < $last_server; $i++) {
+ $_SESSION[$this->_id]['Servers'][$i]
+ = $_SESSION[$this->_id]['Servers'][$i + 1];
+ }
+ unset($_SESSION[$this->_id]['Servers'][$last_server]);
+
+ if (isset($_SESSION[$this->_id]['ServerDefault'])
+ && $_SESSION[$this->_id]['ServerDefault'] == $last_server
+ ) {
+ unset($_SESSION[$this->_id]['ServerDefault']);
+ }
+ }
+
+ /**
+ * Returns config file path, relative to phpMyAdmin's root path
+ *
+ * @return string
+ */
+ public function getFilePath()
+ {
+ // Load paths
+ if (!defined('SETUP_CONFIG_FILE')) {
+ include_once './libraries/vendor_config.php';
+ }
+
+ return SETUP_CONFIG_FILE;
+ }
+
+ /**
+ * Returns configuration array (full, multidimensional format)
+ *
+ * @return array
+ */
+ public function getConfig()
+ {
+ $c = $_SESSION[$this->_id];
+ foreach ($this->_cfgUpdateReadMapping as $map_to => $map_from) {
+ if (PMA_arrayKeyExists($map_to, $c)) {
+ PMA_arrayWrite($map_to, $c, PMA_arrayRead($map_from, $c));
+ PMA_arrayRemove($map_from, $c);
+ }
+ }
+ return $c;
+ }
+
+ /**
+ * Returns configuration array (flat format)
+ *
+ * @return array
+ */
+ public function getConfigArray()
+ {
+ $this->_flattenArrayResult = array();
+ array_walk($_SESSION[$this->_id], array($this, '_flattenArray'), '');
+ $c = $this->_flattenArrayResult;
+ $this->_flattenArrayResult = null;
+
+ $persistKeys = array_diff(
+ array_keys($this->_persistKeys),
+ array_keys($c)
+ );
+ foreach ($persistKeys as $k) {
+ $c[$k] = $this->getDefault($this->getCanonicalPath($k));
+ }
+
+ foreach ($this->_cfgUpdateReadMapping as $map_to => $map_from) {
+ if (!isset($c[$map_from])) {
+ continue;
+ }
+ $c[$map_to] = $c[$map_from];
+ unset($c[$map_from]);
+ }
+ return $c;
+ }
+}
+?>
diff --git a/libraries/config/Form.class.php b/libraries/config/Form.class.php
new file mode 100644
index 0000000000..b8b55bcc8a
--- /dev/null
+++ b/libraries/config/Form.class.php
@@ -0,0 +1,219 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Form handling code.
+ *
+ * @package PhpMyAdmin
+ */
+
+/**
+ * Base class for forms, loads default configuration options, checks allowed
+ * values etc.
+ *
+ * @package PhpMyAdmin
+ */
+class Form
+{
+ /**
+ * Form name
+ * @var string
+ */
+ public $name;
+
+ /**
+ * Arbitrary index, doesn't affect class' behavior
+ * @var int
+ */
+ public $index;
+
+ /**
+ * Form fields (paths), filled by {@link readFormPaths()}, indexed by field name
+ * @var array
+ */
+ public $fields;
+
+ /**
+ * Stores default values for some fields (eg. pmadb tables)
+ * @var array
+ */
+ public $default;
+
+ /**
+ * Caches field types, indexed by field names
+ * @var array
+ */
+ private $_fieldsTypes;
+
+ /**
+ * ConfigFile instance
+ * @var ConfigFile
+ */
+ private $_configFile;
+
+ /**
+ * Constructor, reads default config values
+ *
+ * @param string $form_name Form name
+ * @param array $form Form data
+ * @param ConfigFile $cf Config file instance
+ * @param int $index arbitrary index, stored in Form::$index
+ */
+ public function __construct(
+ $form_name, array $form, ConfigFile $cf, $index = null
+ ) {
+ $this->index = $index;
+ $this->_configFile = $cf;
+ $this->loadForm($form_name, $form);
+ }
+
+ /**
+ * Returns type of given option
+ *
+ * @param string $option_name path or field name
+ *
+ * @return string|null one of: boolean, integer, double, string, select, array
+ */
+ public function getOptionType($option_name)
+ {
+ $key = ltrim(substr($option_name, strrpos($option_name, '/')), '/');
+ return isset($this->_fieldsTypes[$key])
+ ? $this->_fieldsTypes[$key]
+ : null;
+ }
+
+ /**
+ * Returns allowed values for select fields
+ *
+ * @param string $option_path
+ *
+ * @return array
+ */
+ public function getOptionValueList($option_path)
+ {
+ $value = $this->_configFile->getDbEntry($option_path);
+ if ($value === null) {
+ trigger_error("$option_path - select options not defined", E_USER_ERROR);
+ return array();
+ }
+ if (!is_array($value)) {
+ trigger_error("$option_path - not a static value list", E_USER_ERROR);
+ return array();
+ }
+ // convert array('#', 'a', 'b') to array('a', 'b')
+ if (isset($value[0]) && $value[0] === '#') {
+ // remove first element ('#')
+ array_shift($value);
+ } else {
+ // convert value list array('a', 'b') to array('a' => 'a', 'b' => 'b')
+ $has_string_keys = false;
+ $keys = array();
+ for ($i = 0; $i < count($value); $i++) {
+ if (!isset($value[$i])) {
+ $has_string_keys = true;
+ break;
+ }
+ $keys[] = is_bool($value[$i]) ? (int)$value[$i] : $value[$i];
+ }
+ if (! $has_string_keys) {
+ $value = array_combine($keys, $value);
+ }
+ }
+
+ // $value has keys and value names, return it
+ return $value;
+ }
+
+ /**
+ * array_walk callback function, reads path of form fields from
+ * array (see file comment in setup.forms.php or user_preferences.forms.inc)
+ *
+ * @param mixed $value
+ * @param mixed $key
+ * @param mixed $prefix
+ *
+ * @return void
+ */
+ private function _readFormPathsCallback($value, $key, $prefix)
+ {
+ static $group_counter = 0;
+
+ if (is_array($value)) {
+ $prefix .= $key . '/';
+ array_walk($value, array($this, '_readFormPathsCallback'), $prefix);
+ } else {
+ if (!is_int($key)) {
+ $this->default[$prefix . $key] = $value;
+ $value = $key;
+ }
+ // add unique id to group ends
+ if ($value == ':group:end') {
+ $value .= ':' . $group_counter++;
+ }
+ $this->fields[] = $prefix . $value;
+ }
+ }
+
+ /**
+ * Reads form paths to {@link $fields}
+ *
+ * @param array $form
+ *
+ * @return void
+ */
+ protected function readFormPaths($form)
+ {
+ // flatten form fields' paths and save them to $fields
+ $this->fields = array();
+ array_walk($form, array($this, '_readFormPathsCallback'), '');
+
+ // $this->fields is an array of the form: [0..n] => 'field path'
+ // change numeric indexes to contain field names (last part of the path)
+ $paths = $this->fields;
+ $this->fields = array();
+ foreach ($paths as $path) {
+ $key = ltrim(substr($path, strrpos($path, '/')), '/');
+ $this->fields[$key] = $path;
+ }
+ // now $this->fields is an array of the form: 'field name' => 'field path'
+ }
+
+ /**
+ * Reads fields' types to $this->_fieldsTypes
+ *
+ * @return void
+ */
+ protected function readTypes()
+ {
+ $cf = $this->_configFile;
+ foreach ($this->fields as $name => $path) {
+ if (strpos($name, ':group:') === 0) {
+ $this->_fieldsTypes[$name] = 'group';
+ continue;
+ }
+ $v = $cf->getDbEntry($path);
+ if ($v !== null) {
+ $type = is_array($v) ? 'select' : $v;
+ } else {
+ $type = gettype($cf->getDefault($path));
+ }
+ $this->_fieldsTypes[$name] = $type;
+ }
+ }
+
+ /**
+ * Reads form settings and prepares class to work with given subset of
+ * config file
+ *
+ * @param string $form_name
+ * @param array $form
+ *
+ * @return void
+ */
+ public function loadForm($form_name, $form)
+ {
+ $this->name = $form_name;
+ $this->readFormPaths($form);
+ $this->readTypes();
+ }
+}
+?>
diff --git a/libraries/config/FormDisplay.class.php b/libraries/config/FormDisplay.class.php
new file mode 100644
index 0000000000..74e326a2f5
--- /dev/null
+++ b/libraries/config/FormDisplay.class.php
@@ -0,0 +1,830 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Form management class, displays and processes forms
+ *
+ * Explanation of used terms:
+ * o work_path - original field path, eg. Servers/4/verbose
+ * o system_path - work_path modified so that it points to the first server,
+ * eg. Servers/1/verbose
+ * o translated_path - work_path modified for HTML field name, a path with
+ * slashes changed to hyphens, eg. Servers-4-verbose
+ *
+ * @package PhpMyAdmin
+ */
+
+/**
+ * Core libraries.
+ */
+require_once './libraries/config/FormDisplay.tpl.php';
+require_once './libraries/config/Validator.class.php';
+require_once './libraries/js_escape.lib.php';
+
+/**
+ * Form management class, displays and processes forms
+ *
+ * @package PhpMyAdmin
+ */
+class FormDisplay
+{
+ /**
+ * ConfigFile instance
+ * @var ConfigFile
+ */
+ private $_configFile;
+
+ /**
+ * Form list
+ * @var Form[]
+ */
+ private $_forms = array();
+
+ /**
+ * Stores validation errors, indexed by paths
+ * [ Form_name ] is an array of form errors
+ * [path] is a string storing error associated with single field
+ * @var array
+ */
+ private $_errors = array();
+
+ /**
+ * Paths changed so that they can be used as HTML ids, indexed by paths
+ * @var array
+ */
+ private $_translatedPaths = array();
+
+ /**
+ * Server paths change indexes so we define maps from current server
+ * path to the first one, indexed by work path
+ * @var array
+ */
+ private $_systemPaths = array();
+
+ /**
+ * Language strings which will be sent to PMA_messages JS variable
+ * Will be looked up in $GLOBALS: str{value} or strSetup{value}
+ * @var array
+ */
+ private $_jsLangStrings = array();
+
+ /**
+ * Tells whether forms have been validated
+ * @var bool
+ */
+ private $_isValidated = true;
+
+ /**
+ * Dictionary with user preferences keys
+ * @var array
+ */
+ private $_userprefsKeys;
+
+ /**
+ * Dictionary with disallowed user preferences keys
+ * @var array
+ */
+ private $_userprefsDisallow;
+
+ /**
+ * Constructor
+ *
+ * @param ConfigFile $cf Config file instance
+ */
+ public function __construct(ConfigFile $cf)
+ {
+ $this->_jsLangStrings = array(
+ 'error_nan_p' => __('Not a positive number'),
+ 'error_nan_nneg' => __('Not a non-negative number'),
+ 'error_incorrect_port' => __('Not a valid port number'),
+ 'error_invalid_value' => __('Incorrect value'),
+ 'error_value_lte' => __('Value must be equal or lower than %s'));
+ $this->_configFile = $cf;
+ // initialize validators
+ PMA_Validator::getValidators($this->_configFile);
+ }
+
+ /**
+ * Returns {@link ConfigFile} associated with this instance
+ *
+ * @return ConfigFile
+ */
+ public function getConfigFile()
+ {
+ return $this->_configFile;
+ }
+
+ /**
+ * Registers form in form manager
+ *
+ * @param string $form_name Form name
+ * @param array $form Form data
+ * @param int $server_id 0 if new server, validation; >= 1 if editing a server
+ *
+ * @return void
+ */
+ public function registerForm($form_name, array $form, $server_id = null)
+ {
+ $this->_forms[$form_name] = new Form(
+ $form_name, $form, $this->_configFile, $server_id
+ );
+ $this->_isValidated = false;
+ foreach ($this->_forms[$form_name]->fields as $path) {
+ $work_path = $server_id === null
+ ? $path
+ : str_replace('Servers/1/', "Servers/$server_id/", $path);
+ $this->_systemPaths[$work_path] = $path;
+ $this->_translatedPaths[$work_path] = str_replace('/', '-', $work_path);
+ }
+ }
+
+ /**
+ * Processes forms, returns true on successful save
+ *
+ * @param bool $allow_partial_save allows for partial form saving
+ * on failed validation
+ * @param bool $check_form_submit whether check for $_POST['submit_save']
+ *
+ * @return boolean whether processing was successful
+ */
+ public function process($allow_partial_save = true, $check_form_submit = true)
+ {
+ if ($check_form_submit && !isset($_POST['submit_save'])) {
+ return false;
+ }
+
+ // save forms
+ if (count($this->_forms) > 0) {
+ return $this->save(array_keys($this->_forms), $allow_partial_save);
+ }
+ return false;
+ }
+
+ /**
+ * Runs validation for all registered forms
+ *
+ * @return void
+ */
+ private function _validate()
+ {
+ if ($this->_isValidated) {
+ return;
+ }
+
+ $paths = array();
+ $values = array();
+ foreach ($this->_forms as $form) {
+ /* @var $form Form */
+ $paths[] = $form->name;
+ // collect values and paths
+ foreach ($form->fields as $path) {
+ $work_path = array_search($path, $this->_systemPaths);
+ $values[$path] = $this->_configFile->getValue($work_path);
+ $paths[] = $path;
+ }
+ }
+
+ // run validation
+ $errors = PMA_Validator::validate(
+ $this->_configFile, $paths, $values, false
+ );
+
+ // change error keys from canonical paths to work paths
+ if (is_array($errors) && count($errors) > 0) {
+ $this->_errors = array();
+ foreach ($errors as $path => $error_list) {
+ $work_path = array_search($path, $this->_systemPaths);
+ // field error
+ if (! $work_path) {
+ // form error, fix path
+ $work_path = $path;
+ }
+ $this->_errors[$work_path] = $error_list;
+ }
+ }
+ $this->_isValidated = true;
+ }
+
+ /**
+ * Outputs HTML for forms
+ *
+ * @param bool $tabbed_form if true, use a form with tabs
+ * @param bool $show_restore_default whether show "restore default" button
+ * besides the input field
+ *
+ * @return void
+ */
+ public function display($tabbed_form = false, $show_restore_default = false)
+ {
+ static $js_lang_sent = false;
+
+ $js = array();
+ $js_default = array();
+ $tabbed_form = $tabbed_form && (count($this->_forms) > 1);
+ $validators = PMA_Validator::getValidators($this->_configFile);
+
+ PMA_displayFormTop();
+
+ if ($tabbed_form) {
+ $tabs = array();
+ foreach ($this->_forms as $form) {
+ $tabs[$form->name] = PMA_lang("Form_$form->name");
+ }
+ PMA_displayTabsTop($tabs);
+ }
+
+ // validate only when we aren't displaying a "new server" form
+ $is_new_server = false;
+ foreach ($this->_forms as $form) {
+ /* @var $form Form */
+ if ($form->index === 0) {
+ $is_new_server = true;
+ break;
+ }
+ }
+ if (! $is_new_server) {
+ $this->_validate();
+ }
+
+ // user preferences
+ $this->_loadUserprefsInfo();
+
+ // display forms
+ foreach ($this->_forms as $form) {
+ /* @var $form Form */
+ $form_desc = isset($GLOBALS["strConfigForm_{$form->name}_desc"])
+ ? PMA_lang("Form_{$form->name}_desc")
+ : '';
+ $form_errors = isset($this->_errors[$form->name])
+ ? $this->_errors[$form->name] : null;
+ PMA_displayFieldsetTop(
+ PMA_lang("Form_$form->name"),
+ $form_desc,
+ $form_errors,
+ array('id' => $form->name)
+ );
+
+ foreach ($form->fields as $field => $path) {
+ $work_path = array_search($path, $this->_systemPaths);
+ $translated_path = $this->_translatedPaths[$work_path];
+ // always true/false for user preferences display
+ // otherwise null
+ $userprefs_allow = isset($this->_userprefsKeys[$path])
+ ? !isset($this->_userprefsDisallow[$path])
+ : null;
+ // display input
+ $this->_displayFieldInput(
+ $form,
+ $field,
+ $path,
+ $work_path,
+ $translated_path,
+ $show_restore_default,
+ $userprefs_allow,
+ $js_default
+ );
+ // register JS validators for this field
+ if (isset($validators[$path])) {
+ PMA_addJsValidate($translated_path, $validators[$path], $js);
+ }
+ }
+ PMA_displayFieldsetBottom();
+ }
+
+ if ($tabbed_form) {
+ PMA_displayTabsBottom();
+ }
+ PMA_displayFormBottom();
+
+ // if not already done, send strings used for valdiation to JavaScript
+ if (! $js_lang_sent) {
+ $js_lang_sent = true;
+ $js_lang = array();
+ foreach ($this->_jsLangStrings as $strName => $strValue) {
+ $js_lang[] = "'$strName': '" . PMA_jsFormat($strValue, false) . '\'';
+ }
+ $js[] = "$.extend(PMA_messages, {\n\t"
+ . implode(",\n\t", $js_lang) . '})';
+ }
+
+ $js[] = "$.extend(defaultValues, {\n\t"
+ . implode(",\n\t", $js_default) . '})';
+ PMA_displayJavascript($js);
+ }
+
+ /**
+ * Prepares data for input field display and outputs HTML code
+ *
+ * @param Form $form Form object
+ * @param string $field field name as it appears in $form
+ * @param string $system_path field path, eg. Servers/1/verbose
+ * @param string $work_path work path, eg. Servers/4/verbose
+ * @param string $translated_path work path changed so that it can be
+ * used as XHTML id
+ * @param bool $show_restore_default whether show "restore default" button
+ * besides the input field
+ * @param mixed $userprefs_allow whether user preferences are enabled
+ * for this field (null - no support,
+ * true/false - enabled/disabled)
+ * @param array &$js_default array which stores JavaScript code
+ * to be displayed
+ *
+ * @return void
+ */
+ private function _displayFieldInput(
+ Form $form, $field, $system_path, $work_path,
+ $translated_path, $show_restore_default, $userprefs_allow, array &$js_default
+ ) {
+ $name = PMA_langName($system_path);
+ $description = PMA_langName($system_path, 'desc', '');
+
+ $value = $this->_configFile->get($work_path);
+ $value_default = $this->_configFile->getDefault($system_path);
+ $value_is_default = false;
+ if ($value === null || $value === $value_default) {
+ $value = $value_default;
+ $value_is_default = true;
+ }
+
+ $opts = array(
+ 'doc' => $this->getDocLink($system_path),
+ 'show_restore_default' => $show_restore_default,
+ 'userprefs_allow' => $userprefs_allow,
+ 'userprefs_comment' => PMA_langName($system_path, 'cmt', ''));
+ if (isset($form->default[$system_path])) {
+ $opts['setvalue'] = $form->default[$system_path];
+ }
+
+ if (isset($this->_errors[$work_path])) {
+ $opts['errors'] = $this->_errors[$work_path];
+ }
+ switch ($form->getOptionType($field)) {
+ case 'string':
+ $type = 'text';
+ break;
+ case 'short_string':
+ $type = 'short_text';
+ break;
+ case 'double':
+ case 'integer':
+ $type = 'number_text';
+ break;
+ case 'boolean':
+ $type = 'checkbox';
+ break;
+ case 'select':
+ $type = 'select';
+ $opts['values'] = $form->getOptionValueList($form->fields[$field]);
+ break;
+ case 'array':
+ $type = 'list';
+ $value = (array) $value;
+ $value_default = (array) $value_default;
+ break;
+ case 'group':
+ // :group:end is changed to :group:end:{unique id} in Form class
+ if (substr($field, 7, 4) != 'end:') {
+ PMA_displayGroupHeader(substr($field, 7));
+ } else {
+ PMA_displayGroupFooter();
+ }
+ return;
+ case 'NULL':
+ trigger_error("Field $system_path has no type", E_USER_WARNING);
+ return;
+ }
+
+ // detect password fields
+ if ($type === 'text'
+ && substr($translated_path, -9) === '-password'
+ ) {
+ $type = 'password';
+ }
+
+ // TrustedProxies requires changes before displaying
+ if ($system_path == 'TrustedProxies') {
+ foreach ($value as $ip => &$v) {
+ if (!preg_match('/^-\d+$/', $ip)) {
+ $v = $ip . ': ' . $v;
+ }
+ }
+ }
+ $this->_setComments($system_path, $opts);
+
+ // send default value to form's JS
+ $js_line = '\'' . $translated_path . '\': ';
+ switch ($type) {
+ case 'text':
+ case 'short_text':
+ case 'number_text':
+ case 'password':
+ $js_line .= '\'' . PMA_escapeJsString($value_default) . '\'';
+ break;
+ case 'checkbox':
+ $js_line .= $value_default ? 'true' : 'false';
+ break;
+ case 'select':
+ $value_default_js = is_bool($value_default)
+ ? (int) $value_default
+ : $value_default;
+ $js_line .= '[\'' . PMA_escapeJsString($value_default_js) . '\']';
+ break;
+ case 'list':
+ $js_line .= '\'' . PMA_escapeJsString(implode("\n", $value_default))
+ . '\'';
+ break;
+ }
+ $js_default[] = $js_line;
+
+ PMA_displayInput(
+ $translated_path, $name, $type, $value,
+ $description, $value_is_default, $opts
+ );
+ }
+
+ /**
+ * Displays errors
+ *
+ * @return void
+ */
+ public function displayErrors()
+ {
+ $this->_validate();
+ if (count($this->_errors) == 0) {
+ return;
+ }
+
+ foreach ($this->_errors as $system_path => $error_list) {
+ if (isset($this->_systemPaths[$system_path])) {
+ $path = $this->_systemPaths[$system_path];
+ $name = PMA_langName($path);
+ } else {
+ $name = $GLOBALS["strConfigForm_$system_path"];
+ }
+ PMA_displayErrors($name, $error_list);
+ }
+ }
+
+ /**
+ * Reverts erroneous fields to their default values
+ *
+ * @return void
+ */
+ public function fixErrors()
+ {
+ $this->_validate();
+ if (count($this->_errors) == 0) {
+ return;
+ }
+
+ $cf = $this->_configFile;
+ foreach (array_keys($this->_errors) as $work_path) {
+ if (!isset($this->_systemPaths[$work_path])) {
+ continue;
+ }
+ $canonical_path = $this->_systemPaths[$work_path];
+ $cf->set($work_path, $cf->getDefault($canonical_path));
+ }
+ }
+
+ /**
+ * Validates select field and casts $value to correct type
+ *
+ * @param string &$value Current value
+ * @param array $allowed List of allowed values
+ *
+ * @return bool
+ */
+ private function _validateSelect(&$value, array $allowed)
+ {
+ $value_cmp = is_bool($value)
+ ? (int) $value
+ : $value;
+ foreach ($allowed as $vk => $v) {
+ // equality comparison only if both values are numeric or not numeric
+ // (allows to skip 0 == 'string' equalling to true)
+ // or identity (for string-string)
+ if (($vk == $value && !(is_numeric($value_cmp) xor is_numeric($vk)))
+ || $vk === $value
+ ) {
+ // keep boolean value as boolean
+ if (!is_bool($value)) {
+ settype($value, gettype($vk));
+ }
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Validates and saves form data to session
+ *
+ * @param array|string $forms array of form names
+ * @param bool $allow_partial_save allows for partial form saving on
+ * failed validation
+ *
+ * @return boolean true on success (no errors and all saved)
+ */
+ public function save($forms, $allow_partial_save = true)
+ {
+ $result = true;
+ $forms = (array) $forms;
+
+ $values = array();
+ $to_save = array();
+ $is_setup_script = defined('PMA_SETUP');
+ if ($is_setup_script) {
+ $this->_loadUserprefsInfo();
+ }
+
+ $this->_errors = array();
+ foreach ($forms as $form_name) {
+ /* @var $form Form */
+ if (isset($this->_forms[$form_name])) {
+ $form = $this->_forms[$form_name];
+ } else {
+ continue;
+ }
+ // get current server id
+ $change_index = $form->index === 0
+ ? $this->_configFile->getServerCount() + 1
+ : false;
+ // grab POST values
+ foreach ($form->fields as $field => $system_path) {
+ $work_path = array_search($system_path, $this->_systemPaths);
+ $key = $this->_translatedPaths[$work_path];
+ $type = $form->getOptionType($field);
+
+ // skip groups
+ if ($type == 'group') {
+ continue;
+ }
+
+ // ensure the value is set
+ if (!isset($_POST[$key])) {
+ // checkboxes aren't set by browsers if they're off
+ if ($type == 'boolean') {
+ $_POST[$key] = false;
+ } else {
+ $this->_errors[$form->name][] = sprintf(
+ __('Missing data for %s'),
+ '<i>' . PMA_langName($system_path) . '</i>'
+ );
+ $result = false;
+ continue;
+ }
+ }
+
+ // user preferences allow/disallow
+ if ($is_setup_script
+ && isset($this->_userprefsKeys[$system_path])
+ ) {
+ if (isset($this->_userprefsDisallow[$system_path])
+ && isset($_POST[$key . '-userprefs-allow'])
+ ) {
+ unset($this->_userprefsDisallow[$system_path]);
+ } else if (!isset($_POST[$key . '-userprefs-allow'])) {
+ $this->_userprefsDisallow[$system_path] = true;
+ }
+ }
+
+ // cast variables to correct type
+ switch ($type) {
+ case 'double':
+ settype($_POST[$key], 'float');
+ break;
+ case 'boolean':
+ case 'integer':
+ if ($_POST[$key] !== '') {
+ settype($_POST[$key], $type);
+ }
+ break;
+ case 'select':
+ $successfully_validated = $this->_validateSelect(
+ $_POST[$key],
+ $form->getOptionValueList($system_path)
+ );
+ if (! $successfully_validated) {
+ $this->_errors[$work_path][] = __('Incorrect value');
+ $result = false;
+ continue;
+ }
+ break;
+ case 'string':
+ case 'short_string':
+ $_POST[$key] = trim($_POST[$key]);
+ break;
+ case 'array':
+ // eliminate empty values and ensure we have an array
+ $post_values = is_array($_POST[$key])
+ ? $_POST[$key]
+ : explode("\n", $_POST[$key]);
+ $_POST[$key] = array();
+ foreach ($post_values as $v) {
+ $v = trim($v);
+ if ($v !== '') {
+ $_POST[$key][] = $v;
+ }
+ }
+ break;
+ }
+
+ // now we have value with proper type
+ $values[$system_path] = $_POST[$key];
+ if ($change_index !== false) {
+ $work_path = str_replace(
+ "Servers/$form->index/",
+ "Servers/$change_index/", $work_path
+ );
+ }
+ $to_save[$work_path] = $system_path;
+ }
+ }
+
+ // save forms
+ if ($allow_partial_save || empty($this->_errors)) {
+ foreach ($to_save as $work_path => $path) {
+ // TrustedProxies requires changes before saving
+ if ($path == 'TrustedProxies') {
+ $proxies = array();
+ $i = 0;
+ foreach ($values[$path] as $value) {
+ $matches = array();
+ $match = preg_match(
+ "/^(.+):(?:[ ]?)(\\w+)$/", $value, $matches
+ );
+ if ($match) {
+ // correct 'IP: HTTP header' pair
+ $ip = trim($matches[1]);
+ $proxies[$ip] = trim($matches[2]);
+ } else {
+ // save also incorrect values
+ $proxies["-$i"] = $value;
+ $i++;
+ }
+ }
+ $values[$path] = $proxies;
+ }
+ $this->_configFile->set($work_path, $values[$path], $path);
+ }
+ if ($is_setup_script) {
+ $this->_configFile->set(
+ 'UserprefsDisallow',
+ array_keys($this->_userprefsDisallow)
+ );
+ }
+ }
+
+ // don't look for non-critical errors
+ $this->_validate();
+
+ return $result;
+ }
+
+ /**
+ * Tells whether form validation failed
+ *
+ * @return boolean
+ */
+ public function hasErrors()
+ {
+ return count($this->_errors) > 0;
+ }
+
+
+ /**
+ * Returns link to documentation
+ *
+ * @param string $path Path to documentation
+ *
+ * @return string
+ */
+ public function getDocLink($path)
+ {
+ $test = substr($path, 0, 6);
+ if ($test == 'Import' || $test == 'Export') {
+ return '';
+ }
+ return PMA_Util::getDocuLink(
+ 'config',
+ 'cfg_' . $this->_getOptName($path)
+ );
+ }
+
+ /**
+ * Changes path so it can be used in URLs
+ *
+ * @param string $path Path
+ *
+ * @return string
+ */
+ private function _getOptName($path)
+ {
+ return str_replace(array('Servers/1/', '/'), array('Servers/', '_'), $path);
+ }
+
+ /**
+ * Fills out {@link userprefs_keys} and {@link userprefs_disallow}
+ *
+ * @return void
+ */
+ private function _loadUserprefsInfo()
+ {
+ if ($this->_userprefsKeys === null) {
+ $this->_userprefsKeys = array_flip(PMA_readUserprefsFieldNames());
+ // read real config for user preferences display
+ $userprefs_disallow = defined('PMA_SETUP')
+ ? $this->_configFile->get('UserprefsDisallow', array())
+ : $GLOBALS['cfg']['UserprefsDisallow'];
+ $this->_userprefsDisallow = array_flip($userprefs_disallow);
+ }
+ }
+
+ /**
+ * Sets field comments and warnings based on current environment
+ *
+ * @param string $system_path Path to settings
+ * @param array &$opts Chosen options
+ *
+ * @return void
+ */
+ private function _setComments($system_path, array &$opts)
+ {
+ // RecodingEngine - mark unavailable types
+ if ($system_path == 'RecodingEngine') {
+ $comment = '';
+ if (!function_exists('iconv')) {
+ $opts['values']['iconv'] .= ' (' . __('unavailable') . ')';
+ $comment = sprintf(
+ __('"%s" requires %s extension'), 'iconv', 'iconv'
+ );
+ }
+ if (!function_exists('recode_string')) {
+ $opts['values']['recode'] .= ' (' . __('unavailable') . ')';
+ $comment .= ($comment ? ", " : '') . sprintf(
+ __('"%s" requires %s extension'),
+ 'recode', 'recode'
+ );
+ }
+ if (!function_exists('mb_convert_encoding')) {
+ $opts['values']['mb'] .= ' (' . __('unavailable') . ')';
+ $comment .= ($comment ? ", " : '') . sprintf(
+ __('"%s" requires %s extension'),
+ 'mb', 'mbstring'
+ );
+ }
+ $opts['comment'] = $comment;
+ $opts['comment_warning'] = true;
+ }
+ // ZipDump, GZipDump, BZipDump - check function availability
+ if ($system_path == 'ZipDump'
+ || $system_path == 'GZipDump'
+ || $system_path == 'BZipDump'
+ ) {
+ $comment = '';
+ $funcs = array(
+ 'ZipDump' => array('zip_open', 'gzcompress'),
+ 'GZipDump' => array('gzopen', 'gzencode'),
+ 'BZipDump' => array('bzopen', 'bzcompress'));
+ if (!function_exists($funcs[$system_path][0])) {
+ $comment = sprintf(
+ __('Compressed import will not work due to missing function %s.'),
+ $funcs[$system_path][0]
+ );
+ }
+ if (!function_exists($funcs[$system_path][1])) {
+ $comment .= ($comment ? '; ' : '') . sprintf(
+ __('Compressed export will not work due to missing function %s.'),
+ $funcs[$system_path][1]
+ );
+ }
+ $opts['comment'] = $comment;
+ $opts['comment_warning'] = true;
+ }
+ if ($system_path == 'SQLQuery/Validate'
+ && ! $GLOBALS['cfg']['SQLValidator']['use']
+ ) {
+ $opts['comment'] = __('SQL Validator is disabled');
+ $opts['comment_warning'] = true;
+ }
+ if ($system_path == 'SQLValidator/use') {
+ if (!class_exists('SOAPClient')) {
+ @include_once 'SOAP/Client.php';
+ if (!class_exists('SOAP_Client')) {
+ $opts['comment'] = __('SOAP extension not found');
+ $opts['comment_warning'] = true;
+ }
+ }
+ }
+ if (!defined('PMA_SETUP')) {
+ if (($system_path == 'MaxDbList' || $system_path == 'MaxTableList'
+ || $system_path == 'QueryHistoryMax')
+ ) {
+ $opts['comment'] = sprintf(
+ __('maximum %s'), $GLOBALS['cfg'][$system_path]
+ );
+ }
+ }
+ }
+}
+?>
diff --git a/libraries/config/FormDisplay.tpl.php b/libraries/config/FormDisplay.tpl.php
new file mode 100644
index 0000000000..07d82bf415
--- /dev/null
+++ b/libraries/config/FormDisplay.tpl.php
@@ -0,0 +1,489 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Form templates
+ *
+ * @package PhpMyAdmin
+ */
+
+/**
+ * Displays top part of the form
+ *
+ * @param string $action default: $_SERVER['REQUEST_URI']
+ * @param string $method 'post' or 'get'
+ * @param array $hidden_fields array of form hidden fields (key: field name)
+ *
+ * @return void
+ */
+function PMA_displayFormTop($action = null, $method = 'post', $hidden_fields = null)
+{
+ static $has_check_page_refresh = false;
+
+ if ($action === null) {
+ $action = $_SERVER['REQUEST_URI'];
+ }
+ if ($method != 'post') {
+ $method = 'get';
+ }
+ echo '<form method="' . $method . '" action="'
+ . htmlspecialchars($action) . '" class="config-form disableAjax">';
+ echo '<input type="hidden" name="tab_hash" value="" />';
+ // we do validation on page refresh when browser remembers field values,
+ // add a field with known value which will be used for checks
+ if (! $has_check_page_refresh) {
+ $has_check_page_refresh = true;
+ echo '<input type="hidden" name="check_page_refresh" '
+ . ' id="check_page_refresh" value="" />' . "\n";
+ }
+ echo PMA_URL_getHiddenInputs('', '', 0, 'server') . "\n";
+ echo PMA_getHiddenFields((array)$hidden_fields);
+}
+
+/**
+ * Displays form tabs which are given by an array indexed by fieldset id
+ * ({@link PMA_displayFieldsetTop}), with values being tab titles.
+ *
+ * @param array $tabs tab names
+ *
+ * @return void
+ */
+function PMA_displayTabsTop($tabs)
+{
+ echo '<ul class="tabs">';
+ foreach ($tabs as $tab_id => $tab_name) {
+ echo '<li><a href="#' . $tab_id . '">'
+ . htmlspecialchars($tab_name) . '</a></li>';
+ }
+ echo '</ul>';
+ echo '<br clear="right" />';
+ echo '<div class="tabs_contents">';
+}
+
+
+/**
+ * Displays top part of a fieldset
+ *
+ * @param string $title title of fieldset
+ * @param string $description description shown on top of fieldset
+ * @param array $errors error messages to display
+ * @param array $attributes optional extra attributes of fieldset
+ *
+ * @return void
+ */
+function PMA_displayFieldsetTop($title = '', $description = '', $errors = null,
+ $attributes = array()
+) {
+ global $_FormDisplayGroup;
+
+ $_FormDisplayGroup = 0;
+
+ $attributes = array_merge(array('class' => 'optbox'), $attributes);
+ foreach ($attributes as $k => &$attr) {
+ $attr = $k . '="' . htmlspecialchars($attr) . '"';
+ }
+
+ echo '<fieldset ' . implode(' ', $attributes) . '>';
+ echo '<legend>' . $title . '</legend>';
+ if (!empty($description)) {
+ echo '<p>' . $description . '</p>';
+ }
+ // this must match with displayErrors() in scripts.js
+ if (is_array($errors) && count($errors) > 0) {
+ echo '<dl class="errors">';
+ foreach ($errors as $error) {
+ echo '<dd>' . $error . '</dd>';
+ }
+ echo '</dl>';
+ }
+ echo '<table width="100%" cellspacing="0">';
+}
+
+/**
+ * Displays input field
+ *
+ * $opts keys:
+ * o doc - (string) documentation link
+ * o errors - error array
+ * o setvalue - (string) shows button allowing to set poredefined value
+ * o show_restore_default - (boolean) whether show "restore default" button
+ * o userprefs_allow - whether user preferences are enabled for this field
+ * (null - no support, true/false - enabled/disabled)
+ * o userprefs_comment - (string) field comment
+ * o values - key - value paris for <select> fields
+ * o values_escaped - (boolean) tells whether values array is already escaped
+ * (defaults to false)
+ * o values_disabled - (array)list of disabled values (keys from values)
+ * o comment - (string) tooltip comment
+ * o comment_warning - (bool) whether this comments warns about something
+ *
+ * @param string $path config option path
+ * @param string $name config option name
+ * @param string $type type of config option
+ * @param mixed $value current value
+ * @param string $description verbose description
+ * @param bool $value_is_default whether value is default
+ * @param array $opts see above description
+ *
+ * @return void
+ */
+function PMA_displayInput($path, $name, $type, $value, $description = '',
+ $value_is_default = true, $opts = null
+) {
+ global $_FormDisplayGroup;
+ static $icons; // An array of IMG tags used further below in the function
+
+ if (defined('TESTSUITE')) {
+ $icons = null;
+ }
+
+ $is_setup_script = defined('PMA_SETUP');
+ if ($icons === null) { // if the static variables have not been initialised
+ $icons = array();
+ // Icon definitions:
+ // The same indexes will be used in the $icons array.
+ // The first element contains the filename and the second
+ // element is used for the "alt" and "title" attributes.
+ $icon_init = array(
+ 'edit' => array('b_edit.png', ''),
+ 'help' => array('b_help.png', __('Documentation')),
+ 'reload' => array('s_reload.png', ''),
+ 'tblops' => array('b_tblops.png', '')
+ );
+ if ($is_setup_script) {
+ // When called from the setup script, we don't have access to the
+ // sprite-aware getImage() function because the PMA_theme class
+ // has not been loaded, so we generate the img tags manually.
+ foreach ($icon_init as $k => $v) {
+ $title = '';
+ if (! empty($v[1])) {
+ $title = ' title="' . $v[1] . '"';
+ }
+ $icons[$k] = sprintf(
+ '<img alt="%s" src="%s"%s />',
+ $v[1],
+ ".{$GLOBALS['cfg']['ThemePath']}/original/img/{$v[0]}",
+ $title
+ );
+ }
+ } else {
+ // In this case we just use getImage() because it's available
+ foreach ($icon_init as $k => $v) {
+ $icons[$k] = PMA_Util::getImage(
+ $v[0], $v[1]
+ );
+ }
+ }
+ }
+ $has_errors = isset($opts['errors']) && !empty($opts['errors']);
+ $option_is_disabled = ! $is_setup_script && isset($opts['userprefs_allow'])
+ && ! $opts['userprefs_allow'];
+ $name_id = 'name="' . htmlspecialchars($path) . '" id="'
+ . htmlspecialchars($path) . '"';
+ $field_class = $type == 'checkbox' ? 'checkbox' : '';
+ if (! $value_is_default) {
+ $field_class .= ($field_class == '' ? '' : ' ')
+ . ($has_errors ? 'custom field-error' : 'custom');
+ }
+ $field_class = $field_class ? ' class="' . $field_class . '"' : '';
+ $tr_class = $_FormDisplayGroup > 0
+ ? 'group-field group-field-' . $_FormDisplayGroup
+ : '';
+ if (isset($opts['setvalue']) && $opts['setvalue'] == ':group') {
+ unset($opts['setvalue']);
+ $_FormDisplayGroup++;
+ $tr_class = 'group-header-field group-header-' . $_FormDisplayGroup;
+ }
+ if ($option_is_disabled) {
+ $tr_class .= ($tr_class ? ' ' : '') . 'disabled-field';
+ }
+ $tr_class = $tr_class ? ' class="' . $tr_class . '"' : '';
+
+ echo '<tr' . $tr_class . '>';
+ echo '<th>';
+ echo '<label for="' . htmlspecialchars($path) . '">' . $name . '</label>';
+
+ if (! empty($opts['doc'])) {
+ echo '<span class="doc">';
+ echo '<a href="' . $opts['doc']
+ . '" target="documentation">' . $icons['help'] . '</a>';
+ echo "\n";
+ echo '</span>';
+ }
+
+ if ($option_is_disabled) {
+ echo '<span class="disabled-notice" title="';
+ echo __(
+ 'This setting is disabled, it will not be applied to your configuration.'
+ );
+ echo '">' . __('Disabled') . "</span>";
+ }
+
+ if (!empty($description)) {
+ echo '<small>' . $description . '</small>';
+ }
+
+ echo '</th>';
+ echo '<td>';
+
+ switch ($type) {
+ case 'text':
+ echo '<input type="text" size="60" ' . $name_id . $field_class
+ . ' value="' . htmlspecialchars($value) . '" />';
+ break;
+ case 'password':
+ echo '<input type="password" size="60" ' . $name_id . $field_class
+ . ' value="' . htmlspecialchars($value) . '" />';
+ break;
+ case 'short_text':
+ echo '<input type="text" size="25" ' . $name_id . $field_class
+ . ' value="' . htmlspecialchars($value) . '" />';
+ break;
+ case 'number_text':
+ echo '<input type="number" size="15" ' . $name_id . $field_class
+ . ' value="' . htmlspecialchars($value) . '" />';
+ break;
+ case 'checkbox':
+ echo '<span' . $field_class . '><input type="checkbox" ' . $name_id
+ . ($value ? ' checked="checked"' : '') . ' /></span>';
+ break;
+ case 'select':
+ echo '<select ' . $name_id . $field_class . '>';
+ $escape = !(isset($opts['values_escaped']) && $opts['values_escaped']);
+ $values_disabled = isset($opts['values_disabled'])
+ ? array_flip($opts['values_disabled']) : array();
+ foreach ($opts['values'] as $opt_value_key => $opt_value) {
+ // set names for boolean values
+ if (is_bool($opt_value)) {
+ $opt_value = strtolower($opt_value ? __('Yes') : __('No'));
+ }
+ // escape if necessary
+ if ($escape) {
+ $display = htmlspecialchars($opt_value);
+ $display_value = htmlspecialchars($opt_value_key);
+ } else {
+ $display = $opt_value;
+ $display_value = $opt_value_key;
+ }
+ // compare with selected value
+ // boolean values are cast to integers when used as array keys
+ $selected = is_bool($value)
+ ? (int) $value === $opt_value_key
+ : $opt_value_key === $value;
+ echo '<option value="' . $display_value . '"';
+ if ($selected) {
+ echo ' selected="selected"';
+ }
+ if (isset($values_disabled[$opt_value_key])) {
+ echo ' disabled="disabled"';
+ }
+ echo '>' . $display . '</option>';
+ }
+ echo '</select>';
+ break;
+ case 'list':
+ echo '<textarea cols="40" rows="5" ' . $name_id . $field_class . '>'
+ . htmlspecialchars(implode("\n", $value))
+ . '</textarea>';
+ break;
+ }
+ if (isset($opts['comment']) && $opts['comment']) {
+ $class = 'field-comment-mark';
+ if (isset($opts['comment_warning']) && $opts['comment_warning']) {
+ $class .= ' field-comment-warning';
+ }
+ echo '<span class="' . $class . '" title="'
+ . htmlspecialchars($opts['comment']) . '">i</span>';
+ }
+ if ($is_setup_script
+ && isset($opts['userprefs_comment'])
+ && $opts['userprefs_comment']
+ ) {
+ echo '<a class="userprefs-comment" title="'
+ . htmlspecialchars($opts['userprefs_comment']) . '">'
+ . $icons['tblops'] . '</a>';
+ }
+ if (isset($opts['setvalue']) && $opts['setvalue']) {
+ echo '<a class="set-value" href="#'
+ . htmlspecialchars("$path={$opts['setvalue']}") . '" title="'
+ . sprintf(__('Set value: %s'), htmlspecialchars($opts['setvalue']))
+ . '" style="display:none">' . $icons['edit'] . '</a>';
+ }
+ if (isset($opts['show_restore_default']) && $opts['show_restore_default']) {
+ echo '<a class="restore-default" href="#' . $path . '" title="'
+ . __('Restore default value') . '" style="display:none">'
+ . $icons['reload'] . '</a>';
+ }
+ // this must match with displayErrors() in scripts/config.js
+ if ($has_errors) {
+ echo "\n <dl class=\"inline_errors\">";
+ foreach ($opts['errors'] as $error) {
+ echo '<dd>' . htmlspecialchars($error) . '</dd>';
+ }
+ echo '</dl>';
+ }
+ echo '</td>';
+ if ($is_setup_script && isset($opts['userprefs_allow'])) {
+ echo '<td class="userprefs-allow" title="' .
+ __('Allow users to customize this value') . '">';
+ echo '<input type="checkbox" name="' . $path . '-userprefs-allow" ';
+ if ($opts['userprefs_allow']) {
+ echo 'checked="checked"';
+ };
+ echo '/>';
+ echo '</td>';
+ } else if ($is_setup_script) {
+ echo '<td>&nbsp;</td>';
+ }
+ echo '</tr>';
+}
+
+/**
+ * Display group header
+ *
+ * @param string $header_text Text of header
+ *
+ * @return void
+ */
+function PMA_displayGroupHeader($header_text)
+{
+ global $_FormDisplayGroup;
+
+ $_FormDisplayGroup++;
+ if (! $header_text) {
+ return;
+ }
+ $colspan = defined('PMA_SETUP')
+ ? 3
+ : 2;
+ echo '<tr class="group-header group-header-' . $_FormDisplayGroup . '">';
+ echo '<th colspan="' . $colspan . '">';
+ echo $header_text;
+ echo '</th>';
+ echo '</tr>';
+}
+
+/**
+ * Display group footer
+ *
+ * @return void
+ */
+function PMA_displayGroupFooter()
+{
+ global $_FormDisplayGroup;
+
+ $_FormDisplayGroup--;
+}
+
+/**
+ * Displays bottom part of a fieldset
+ *
+ * @return void
+ */
+function PMA_displayFieldsetBottom()
+{
+ $colspan = 2;
+ if (defined('PMA_SETUP')) {
+ $colspan++;
+ }
+ echo '<tr>';
+ echo '<td colspan="' . $colspan . '" class="lastrow">';
+ echo '<input type="submit" name="submit_save" value="'
+ . __('Apply') . '" class="green" />';
+ echo '<input type="button" name="submit_reset" value="'
+ . __('Reset') . '" />';
+ echo '</td>';
+ echo '</tr>';
+ echo '</table>';
+ echo '</fieldset>';
+}
+
+/**
+ * Displays simple bottom part of a fieldset (without submit buttons)
+ *
+ * @return void
+ */
+function PMA_displayFieldsetBottomSimple()
+{
+ echo '</table>';
+ echo '</fieldset>';
+}
+
+/**
+ * Closes form tabs
+ *
+ * @return void
+ */
+function PMA_displayTabsBottom()
+{
+ echo "</div>\n";
+}
+
+/**
+ * Displays bottom part of the form
+ *
+ * @return void
+ */
+function PMA_displayFormBottom()
+{
+ echo "</form>\n";
+}
+
+/**
+ * Appends JS validation code to $js_array
+ *
+ * @param string $field_id ID of field to validate
+ * @param string|array $validators validators callback
+ * @param array &$js_array will be updated with javascript code
+ *
+ * @return void
+ */
+function PMA_addJsValidate($field_id, $validators, &$js_array)
+{
+ foreach ((array)$validators as $validator) {
+ $validator = (array)$validator;
+ $v_name = array_shift($validator);
+ $v_name = "PMA_" . $v_name;
+ $v_args = array();
+ foreach ($validator as $arg) {
+ $v_args[] = PMA_escapeJsString($arg);
+ }
+ $v_args = $v_args ? ", ['" . implode("', '", $v_args) . "']" : '';
+ $js_array[] = "validateField('$field_id', '$v_name', true$v_args)";
+ }
+}
+
+/**
+ * Displays JavaScript code
+ *
+ * @param array $js_array lines of javascript code
+ *
+ * @return void
+ */
+function PMA_displayJavascript($js_array)
+{
+ if (empty($js_array)) {
+ return;
+ }
+ echo '<script type="text/javascript">' . "\n";
+ echo implode(";\n", $js_array) . ";\n";
+ echo '</script>' . "\n";
+}
+
+/**
+ * Displays error list
+ *
+ * @param string $name name of item with errors
+ * @param array $error_list list of errors to show
+ *
+ * @return void
+ */
+function PMA_displayErrors($name, $error_list)
+{
+ echo '<dl>';
+ echo '<dt>' . htmlspecialchars($name) . '</dt>';
+ foreach ($error_list as $error) {
+ echo '<dd>' . htmlspecialchars($error) . '</dd>';
+ }
+ echo '</dl>';
+}
+?>
diff --git a/libraries/config/Validator.class.php b/libraries/config/Validator.class.php
new file mode 100644
index 0000000000..e6a958880f
--- /dev/null
+++ b/libraries/config/Validator.class.php
@@ -0,0 +1,598 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Form validation for configuration editor
+ *
+ * @package PhpMyAdmin
+ */
+
+/**
+ * Validation class for various validation functions
+ *
+ * Validation function takes two argument: id for which it is called
+ * and array of fields' values (usually values for entire formset, as defined
+ * in forms.inc.php).
+ * The function must always return an array with an error (or error array)
+ * assigned to a form element (formset name or field path). Even if there are
+ * no errors, key must be set with an empty value.
+ *
+ * Valdiation functions are assigned in $cfg_db['_validators'] (config.values.php).
+ *
+ * @package PhpMyAdmin
+ */
+class PMA_Validator
+{
+ /**
+ * Returns validator list
+ *
+ * @param ConfigFile $cf Config file instance
+ *
+ * @return array
+ */
+ public static function getValidators(ConfigFile $cf)
+ {
+ static $validators = null;
+
+ if ($validators === null) {
+ $validators = $cf->getDbEntry('_validators', array());
+ if (!defined('PMA_SETUP')) {
+ // not in setup script: load additional validators for user
+ // preferences we need original config values not overwritten
+ // by user preferences, creating a new PMA_Config instance is a
+ // better idea than hacking into its code
+ $uvs = $cf->getDbEntry('_userValidators', array());
+ foreach ($uvs as $field => $uv_list) {
+ $uv_list = (array)$uv_list;
+ foreach ($uv_list as &$uv) {
+ if (!is_array($uv)) {
+ continue;
+ }
+ for ($i = 1; $i < count($uv); $i++) {
+ if (substr($uv[$i], 0, 6) == 'value:') {
+ $uv[$i] = PMA_arrayRead(
+ substr($uv[$i], 6),
+ $GLOBALS['PMA_Config']->base_settings
+ );
+ }
+ }
+ }
+ $validators[$field] = isset($validators[$field])
+ ? array_merge((array)$validators[$field], $uv_list)
+ : $uv_list;
+ }
+ }
+ }
+ return $validators;
+ }
+
+ /**
+ * Runs validation $validator_id on values $values and returns error list.
+ *
+ * Return values:
+ * o array, keys - field path or formset id, values - array of errors
+ * when $isPostSource is true values is an empty array to allow for error list
+ * cleanup in HTML documen
+ * o false - when no validators match name(s) given by $validator_id
+ *
+ * @param ConfigFile $cf Config file instance
+ * @param string|array $validator_id ID of validator(s) to run
+ * @param array &$values Values to validate
+ * @param bool $isPostSource tells whether $values are directly from
+ * POST request
+ *
+ * @return bool|array
+ */
+ public static function validate(
+ ConfigFile $cf, $validator_id, &$values, $isPostSource
+ ) {
+ // find validators
+ $validator_id = (array) $validator_id;
+ $validators = static::getValidators($cf);
+ $vids = array();
+ foreach ($validator_id as &$vid) {
+ $vid = $cf->getCanonicalPath($vid);
+ if (isset($validators[$vid])) {
+ $vids[] = $vid;
+ }
+ }
+ if (empty($vids)) {
+ return false;
+ }
+
+ // create argument list with canonical paths and remember path mapping
+ $arguments = array();
+ $key_map = array();
+ foreach ($values as $k => $v) {
+ $k2 = $isPostSource ? str_replace('-', '/', $k) : $k;
+ $k2 = strpos($k2, '/') ? $cf->getCanonicalPath($k2) : $k2;
+ $key_map[$k2] = $k;
+ $arguments[$k2] = $v;
+ }
+
+ // validate
+ $result = array();
+ foreach ($vids as $vid) {
+ // call appropriate validation functions
+ foreach ((array)$validators[$vid] as $validator) {
+ $vdef = (array) $validator;
+ $vname = array_shift($vdef);
+ $vname = "PMA_Validator::" . $vname;
+ $args = array_merge(array($vid, &$arguments), $vdef);
+ $r = call_user_func_array($vname, $args);
+
+ // merge results
+ if (is_array($r)) {
+ foreach ($r as $key => $error_list) {
+ // skip empty values if $isPostSource is false
+ if (! $isPostSource && empty($error_list)) {
+ continue;
+ }
+ if (! isset($result[$key])) {
+ $result[$key] = array();
+ }
+ $result[$key] = array_merge(
+ $result[$key], (array)$error_list
+ );
+ }
+ }
+ }
+ }
+
+ // restore original paths
+ $new_result = array();
+ foreach ($result as $k => $v) {
+ $k2 = isset($key_map[$k]) ? $key_map[$k] : $k;
+ $new_result[$k2] = $v;
+ }
+ return empty($new_result) ? true : $new_result;
+ }
+
+ /**
+ * Empty error handler, used to temporarily restore PHP internal error handler
+ *
+ * @return bool
+ */
+ public static function nullErrorHandler()
+ {
+ return false;
+ }
+
+ /**
+ * Ensures that $php_errormsg variable will be registered in case of an error
+ * and enables output buffering (when $start = true).
+ * Called with $start = false disables output buffering end restores
+ * html_errors and track_errors.
+ *
+ * @param boolean $start Whether to start buffering
+ *
+ * @return void
+ */
+ public static function testPHPErrorMsg($start = true)
+ {
+ static $old_html_errors, $old_track_errors, $old_error_reporting;
+ static $old_display_errors;
+ if ($start) {
+ $old_html_errors = ini_get('html_errors');
+ $old_track_errors = ini_get('track_errors');
+ $old_display_errors = ini_get('display_errors');
+ $old_error_reporting = error_reporting(E_ALL);
+ ini_set('html_errors', false);
+ ini_set('track_errors', true);
+ ini_set('display_errors', true);
+ set_error_handler(array("PMA_Validator", "nullErrorHandler"));
+ ob_start();
+ } else {
+ ob_end_clean();
+ restore_error_handler();
+ error_reporting($old_error_reporting);
+ ini_set('html_errors', $old_html_errors);
+ ini_set('track_errors', $old_track_errors);
+ ini_set('display_errors', $old_display_errors);
+ }
+ }
+
+ /**
+ * Test database connection
+ *
+ * @param string $extension 'drizzle', 'mysql' or 'mysqli'
+ * @param string $connect_type 'tcp' or 'socket'
+ * @param string $host host name
+ * @param string $port tcp port to use
+ * @param string $socket socket to use
+ * @param string $user username to use
+ * @param string $pass password to use
+ * @param string $error_key key to use in return array
+ *
+ * @return bool|array
+ */
+ public static function testDBConnection(
+ $extension,
+ $connect_type,
+ $host,
+ $port,
+ $socket,
+ $user,
+ $pass = null,
+ $error_key = 'Server'
+ ) {
+ // static::testPHPErrorMsg();
+ $socket = empty($socket) || $connect_type == 'tcp' ? null : $socket;
+ $port = empty($port) || $connect_type == 'socket' ? null : ':' . $port;
+ $error = null;
+ if ($extension == 'drizzle') {
+ while (1) {
+ $drizzle = @drizzle_create();
+ if (! $drizzle) {
+ $error = __('Could not initialize Drizzle connection library');
+ break;
+ }
+ $conn = $socket
+ ? @drizzle_con_add_uds($socket, $user, $pass, null, 0)
+ : @drizzle_con_add_tcp(
+ $drizzle, $host, $port, $user, $pass, null, 0
+ );
+ if (! $conn) {
+ $error = __('Could not connect to Drizzle server');
+ drizzle_free($drizzle);
+ break;
+ }
+ // connection object is set up but we have to send some query
+ // to actually connect
+ $res = @drizzle_query($conn, 'SELECT 1');
+ if (! $res) {
+ $error = __('Could not connect to Drizzle server');
+ } else {
+ drizzle_result_free($res);
+ }
+ drizzle_con_free($conn);
+ drizzle_free($drizzle);
+ break;
+ }
+ } else if ($extension == 'mysql') {
+ $conn = @mysql_connect($host . $socket . $port, $user, $pass);
+ if (! $conn) {
+ $error = __('Could not connect to MySQL server');
+ } else {
+ mysql_close($conn);
+ }
+ } else {
+ $conn = @mysqli_connect($host, $user, $pass, null, $port, $socket);
+ if (! $conn) {
+ $error = __('Could not connect to MySQL server');
+ } else {
+ mysqli_close($conn);
+ }
+ }
+ // static::testPHPErrorMsg(false);
+ if (isset($php_errormsg)) {
+ $error .= " - $php_errormsg";
+ }
+ return is_null($error) ? true : array($error_key => $error);
+ }
+
+ /**
+ * Validate server config
+ *
+ * @param string $path path to config, not used
+ * @param array $values config values
+ *
+ * @return array
+ */
+ public static function validateServer($path, $values)
+ {
+ $result = array(
+ 'Server' => '',
+ 'Servers/1/user' => '',
+ 'Servers/1/SignonSession' => '',
+ 'Servers/1/SignonURL' => ''
+ );
+ $error = false;
+ if ($values['Servers/1/auth_type'] == 'config'
+ && empty($values['Servers/1/user'])
+ ) {
+ $result['Servers/1/user']
+ = __('Empty username while using config authentication method');
+ $error = true;
+ }
+ if ($values['Servers/1/auth_type'] == 'signon'
+ && empty($values['Servers/1/SignonSession'])
+ ) {
+ $result['Servers/1/SignonSession'] = __(
+ 'Empty signon session name '
+ . 'while using signon authentication method'
+ );
+ $error = true;
+ }
+ if ($values['Servers/1/auth_type'] == 'signon'
+ && empty($values['Servers/1/SignonURL'])
+ ) {
+ $result['Servers/1/SignonURL']
+ = __('Empty signon URL while using signon authentication method');
+ $error = true;
+ }
+
+ if (! $error && $values['Servers/1/auth_type'] == 'config') {
+ $password = $values['Servers/1/nopassword'] ? null
+ : $values['Servers/1/password'];
+ $test = static::testDBConnection(
+ $values['Servers/1/extension'],
+ $values['Servers/1/connect_type'],
+ $values['Servers/1/host'],
+ $values['Servers/1/port'],
+ $values['Servers/1/socket'],
+ $values['Servers/1/user'],
+ $password,
+ 'Server'
+ );
+ if ($test !== true) {
+ $result = array_merge($result, $test);
+ }
+ }
+ return $result;
+ }
+
+ /**
+ * Validate pmadb config
+ *
+ * @param string $path path to config, not used
+ * @param array $values config values
+ *
+ * @return array
+ */
+ public static function validatePMAStorage($path, $values)
+ {
+ $result = array(
+ 'Server_pmadb' => '',
+ 'Servers/1/controluser' => '',
+ 'Servers/1/controlpass' => ''
+ );
+ $error = false;
+
+ if ($values['Servers/1/pmadb'] == '') {
+ return $result;
+ }
+
+ $result = array();
+ if ($values['Servers/1/controluser'] == '') {
+ $result['Servers/1/controluser']
+ = __('Empty phpMyAdmin control user while using pmadb');
+ $error = true;
+ }
+ if ($values['Servers/1/controlpass'] == '') {
+ $result['Servers/1/controlpass']
+ = __('Empty phpMyAdmin control user password while using pmadb');
+ $error = true;
+ }
+ if (! $error) {
+ $test = static::testDBConnection(
+ $values['Servers/1/extension'], $values['Servers/1/connect_type'],
+ $values['Servers/1/host'], $values['Servers/1/port'],
+ $values['Servers/1/socket'], $values['Servers/1/controluser'],
+ $values['Servers/1/controlpass'], 'Server_pmadb'
+ );
+ if ($test !== true) {
+ $result = array_merge($result, $test);
+ }
+ }
+ return $result;
+ }
+
+
+ /**
+ * Validates regular expression
+ *
+ * @param string $path path to config
+ * @param array $values config values
+ *
+ * @return array
+ */
+ public static function validateRegex($path, $values)
+ {
+ $result = array($path => '');
+
+ if ($values[$path] == '') {
+ return $result;
+ }
+
+ static::testPHPErrorMsg();
+
+ $matches = array();
+ // in libraries/List_Database.class.php _checkHideDatabase(),
+ // a '/' is used as the delimiter for hide_db
+ preg_match('/' . $values[$path] . '/', '', $matches);
+
+ static::testPHPErrorMsg(false);
+
+ if (isset($php_errormsg)) {
+ $error = preg_replace('/^preg_match\(\): /', '', $php_errormsg);
+ return array($path => $error);
+ }
+
+ return $result;
+ }
+
+ /**
+ * Validates TrustedProxies field
+ *
+ * @param string $path path to config
+ * @param array $values config values
+ *
+ * @return array
+ */
+ public static function validateTrustedProxies($path, $values)
+ {
+ $result = array($path => array());
+
+ if (empty($values[$path])) {
+ return $result;
+ }
+
+ if (is_array($values[$path])) {
+ // value already processed by FormDisplay::save
+ $lines = array();
+ foreach ($values[$path] as $ip => $v) {
+ $lines[] = preg_match('/^-\d+$/', $ip)
+ ? $v
+ : $ip . ': ' . $v;
+ }
+ } else {
+ // AJAX validation
+ $lines = explode("\n", $values[$path]);
+ }
+ foreach ($lines as $line) {
+ $line = trim($line);
+ $matches = array();
+ // we catch anything that may (or may not) be an IP
+ if (!preg_match("/^(.+):(?:[ ]?)\\w+$/", $line, $matches)) {
+ $result[$path][] = __('Incorrect value:') . ' '
+ . htmlspecialchars($line);
+ continue;
+ }
+ // now let's check whether we really have an IP address
+ if (filter_var($matches[1], FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) === false
+ && filter_var($matches[1], FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) === false
+ ) {
+ $ip = htmlspecialchars(trim($matches[1]));
+ $result[$path][] = sprintf(__('Incorrect IP address: %s'), $ip);
+ continue;
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * Tests integer value
+ *
+ * @param string $path path to config
+ * @param array $values config values
+ * @param bool $allow_neg allow negative values
+ * @param bool $allow_zero allow zero
+ * @param int $max_value max allowed value
+ * @param string $error_string error message key:
+ * $GLOBALS["strConfig$error_lang_key"]
+ *
+ * @return string empty string if test is successful
+ */
+ public static function validateNumber(
+ $path,
+ $values,
+ $allow_neg,
+ $allow_zero,
+ $max_value,
+ $error_string
+ ) {
+ if ($values[$path] === '') {
+ return '';
+ }
+
+ if (intval($values[$path]) != $values[$path]
+ || (! $allow_neg && $values[$path] < 0)
+ || (! $allow_zero && $values[$path] == 0)
+ || $values[$path] > $max_value
+ ) {
+ return $error_string;
+ }
+
+ return '';
+ }
+
+ /**
+ * Validates port number
+ *
+ * @param string $path path to config
+ * @param array $values config values
+ *
+ * @return array
+ */
+ public static function validatePortNumber($path, $values)
+ {
+ return array(
+ $path => static::validateNumber(
+ $path,
+ $values,
+ false,
+ false,
+ 65535,
+ __('Not a valid port number')
+ )
+ );
+ }
+
+ /**
+ * Validates positive number
+ *
+ * @param string $path path to config
+ * @param array $values config values
+ *
+ * @return array
+ */
+ public static function validatePositiveNumber($path, $values)
+ {
+ return array(
+ $path => static::validateNumber(
+ $path,
+ $values,
+ false,
+ false,
+ PHP_INT_MAX,
+ __('Not a positive number')
+ )
+ );
+ }
+
+ /**
+ * Validates non-negative number
+ *
+ * @param string $path path to config
+ * @param array $values config values
+ *
+ * @return array
+ */
+ public static function validateNonNegativeNumber($path, $values)
+ {
+ return array(
+ $path => static::validateNumber(
+ $path,
+ $values,
+ false,
+ true,
+ PHP_INT_MAX,
+ __('Not a non-negative number')
+ )
+ );
+ }
+
+ /**
+ * Validates value according to given regular expression
+ * Pattern and modifiers must be a valid for PCRE <b>and</b> JavaScript RegExp
+ *
+ * @param string $path path to config
+ * @param array $values config values
+ * @param string $regex regullar expression to match
+ *
+ * @return array
+ */
+ public static function validateByRegex($path, $values, $regex)
+ {
+ $result = preg_match($regex, $values[$path]);
+ return array($path => ($result ? '' : __('Incorrect value')));
+ }
+
+ /**
+ * Validates upper bound for numeric inputs
+ *
+ * @param string $path path to config
+ * @param array $values config values
+ * @param int $max_value maximal allowed value
+ *
+ * @return array
+ */
+ public static function validateUpperBound($path, $values, $max_value)
+ {
+ $result = $values[$path] <= $max_value;
+ return array($path => ($result ? ''
+ : sprintf(__('Value must be equal or lower than %s'), $max_value)));
+ }
+}
+?>
diff --git a/libraries/config/config_functions.lib.php b/libraries/config/config_functions.lib.php
new file mode 100644
index 0000000000..ad1053ea95
--- /dev/null
+++ b/libraries/config/config_functions.lib.php
@@ -0,0 +1,55 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Common config manipulation functions
+ *
+ * @package PhpMyAdmin
+ */
+
+/**
+ * Returns sanitized language string, taking into account our special codes
+ * for formatting. Takes variable number of arguments.
+ * Based on PMA_sanitize from sanitize.lib.php.
+ *
+ * @param string $lang_key key in $GLOBALS WITHOUT 'strSetup' prefix
+ * @param mixed $args arguments for sprintf
+ *
+ * @return string
+ */
+function PMA_lang($lang_key, $args = null)
+{
+ $message = isset($GLOBALS["strConfig$lang_key"])
+ ? $GLOBALS["strConfig$lang_key"] : $lang_key;
+
+ $message = PMA_sanitize($message);
+
+ if (func_num_args() == 1) {
+ return $message;
+ } else {
+ $args = func_get_args();
+ array_shift($args);
+ return vsprintf($message, $args);
+ }
+}
+
+/**
+ * Returns translated field name/description or comment
+ *
+ * @param string $canonical_path path to handle
+ * @param string $type 'name', 'desc' or 'cmt'
+ * @param mixed $default default value
+ *
+ * @return string
+ */
+function PMA_langName($canonical_path, $type = 'name', $default = 'key')
+{
+ $lang_key = str_replace(
+ array('Servers/1/', '/'),
+ array('Servers/', '_'),
+ $canonical_path
+ ) . '_' . $type;
+ return isset($GLOBALS["strConfig$lang_key"])
+ ? ($type == 'desc' ? PMA_lang($lang_key) : $GLOBALS["strConfig$lang_key"])
+ : ($default == 'key' ? $lang_key : $default);
+}
+?>
diff --git a/libraries/config/messages.inc.php b/libraries/config/messages.inc.php
new file mode 100644
index 0000000000..34382552a6
--- /dev/null
+++ b/libraries/config/messages.inc.php
@@ -0,0 +1,548 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Messages for phpMyAdmin.
+ *
+ * This file defines variables in a special format suited for the
+ * configuration subsystem, with $strConfig as a prefix, _desc or _name
+ * as a suffix, and the directive name in between.
+ *
+ * @package PhpMyAdmin
+ */
+
+if (!function_exists('__')) {
+ PMA_fatalError('Bad invocation!');
+}
+
+$strConfigAllowArbitraryServer_desc = __('If enabled user can enter any MySQL server in login form for cookie auth');
+$strConfigAllowArbitraryServer_name = __('Allow login to any MySQL server');
+$strConfigAllowThirdPartyFraming_desc = __('Enabling this allows a page located on a different domain to call phpMyAdmin inside a frame, and is a potential [strong]security hole[/strong] allowing cross-frame scripting attacks');
+$strConfigAllowThirdPartyFraming_name = __('Allow third party framing');
+$strConfigAllowUserDropDatabase_name = __('Show &quot;Drop database&quot; link to normal users');
+$strConfigblowfish_secret_desc = __('Secret passphrase used for encrypting cookies in [kbd]cookie[/kbd] authentication');
+$strConfigblowfish_secret_name = __('Blowfish secret');
+$strConfigBrowseMarkerEnable_desc = __('Highlight selected rows');
+$strConfigBrowseMarkerEnable_name = __('Row marker');
+$strConfigBrowsePointerEnable_desc = __('Highlight row pointed by the mouse cursor');
+$strConfigBrowsePointerEnable_name = __('Highlight pointer');
+$strConfigBZipDump_desc = __('Enable [a@http://en.wikipedia.org/wiki/Bzip2]bzip2[/a] compression for import operations');
+$strConfigBZipDump_name = __('Bzip2');
+$strConfigCharEditing_desc = __('Defines which type of editing controls should be used for CHAR and VARCHAR columns; [kbd]input[/kbd] - allows limiting of input length, [kbd]textarea[/kbd] - allows newlines in columns');
+$strConfigCharEditing_name = __('CHAR columns editing');
+$strConfigCodemirrorEnable_desc = __('Use user-friendly editor for editing SQL queries ([a@http://codemirror.net/]CodeMirror[/a]) with syntax highlighting and line numbers');
+$strConfigCodemirrorEnable_name = __('Enable CodeMirror');
+$strConfigMinSizeForInputField_desc = __('Defines the minimum size for input fields generated for CHAR and VARCHAR columns');
+$strConfigMinSizeForInputField_name = __('Minimum size for input field');
+$strConfigMaxSizeForInputField_desc = __('Defines the maximum size for input fields generated for CHAR and VARCHAR columns');
+$strConfigMaxSizeForInputField_name = __('Maximum size for input field');
+$strConfigCharTextareaCols_desc = __('Number of columns for CHAR/VARCHAR textareas');
+$strConfigCharTextareaCols_name = __('CHAR textarea columns');
+$strConfigCharTextareaRows_desc = __('Number of rows for CHAR/VARCHAR textareas');
+$strConfigCharTextareaRows_name = __('CHAR textarea rows');
+$strConfigCheckConfigurationPermissions_name = __('Check config file permissions');
+$strConfigCompressOnFly_desc = __('Compress gzip exports on the fly without the need for much memory; if you encounter problems with created gzip files disable this feature');
+$strConfigCompressOnFly_name = __('Compress on the fly');
+$strConfigConfigurationFile = __('Configuration file');
+$strConfigConfirm_desc = __('Whether a warning (&quot;Are your really sure…&quot;) should be displayed when you\'re about to lose data');
+$strConfigConfirm_name = __('Confirm DROP queries');
+$strConfigDBG_sql_name = __('Debug SQL');
+$strConfigDefaultDisplay_name = __('Default display direction');
+$strConfigDefaultTabDatabase_desc = __('Tab that is displayed when entering a database');
+$strConfigDefaultTabDatabase_name = __('Default database tab');
+$strConfigDefaultTabServer_desc = __('Tab that is displayed when entering a server');
+$strConfigDefaultTabServer_name = __('Default server tab');
+$strConfigDefaultTabTable_desc = __('Tab that is displayed when entering a table');
+$strConfigDefaultTabTable_name = __('Default table tab');
+$strConfigHideStructureActions_desc = __('Whether the table structure actions should be hidden');
+$strConfigHideStructureActions_name = __('Hide table structure actions');
+$strConfigDisplayBinaryAsHex_desc = __('Show binary contents as HEX by default');
+$strConfigDisplayBinaryAsHex_name = __('Show binary contents as HEX');
+$strConfigDisplayServersList_desc = __('Show server listing as a list instead of a drop down');
+$strConfigDisplayServersList_name = __('Display servers as a list');
+$strConfigDisableMultiTableMaintenance_desc = __('Disable the table maintenance mass operations, like optimizing or repairing the selected tables of a database.');
+$strConfigDisableMultiTableMaintenance_name = __('Disable multi table maintenance');
+$strConfigEditInWindow_desc = __('Edit SQL queries in popup window');
+$strConfigEditInWindow_name = __('Edit in window');
+$strConfigError_Handler_display_name = __('Display errors');
+$strConfigExecTimeLimit_desc = __('Set the number of seconds a script is allowed to run ([kbd]0[/kbd] for no limit)');
+$strConfigExecTimeLimit_name = __('Maximum execution time');
+$strConfigExport_asfile_name = __('Save as file');
+$strConfigExport_charset_name = __('Character set of the file');
+$strConfigExport_codegen_format_name = __('Format');
+$strConfigExport_compression_name = __('Compression');
+$strConfigExport_csv_columns_name = __('Put columns names in the first row');
+$strConfigExport_csv_enclosed_name = __('Columns enclosed with');
+$strConfigExport_csv_escaped_name = __('Columns escaped with');
+$strConfigExport_csv_null_name = __('Replace NULL with');
+$strConfigExport_csv_removeCRLF_name = __('Remove CRLF characters within columns');
+$strConfigExport_csv_separator_name = __('Columns terminated with');
+$strConfigExport_csv_terminated_name = __('Lines terminated with');
+$strConfigExport_excel_columns_name = __('Put columns names in the first row');
+$strConfigExport_excel_edition_name = __('Excel edition');
+$strConfigExport_excel_null_name = __('Replace NULL with');
+$strConfigExport_excel_removeCRLF_name = __('Remove CRLF characters within columns');
+$strConfigExport_file_template_database_name = __('Database name template');
+$strConfigExport_file_template_server_name = __('Server name template');
+$strConfigExport_file_template_table_name = __('Table name template');
+$strConfigExport_format_name = __('Format');
+$strConfigExport_htmlword_columns_name = __('Put columns names in the first row');
+$strConfigExport_htmlword_null_name = __('Replace NULL with');
+$strConfigExport_htmlword_structure_or_data_name = __('Dump table');
+$strConfigExport_latex_caption_name = __('Include table caption');
+$strConfigExport_latex_columns_name = __('Put columns names in the first row');
+$strConfigExport_latex_comments_name = __('Comments');
+$strConfigExport_latex_data_caption_name = __('Table caption');
+$strConfigExport_latex_data_continued_caption_name = __('Continued table caption');
+$strConfigExport_latex_data_label_name = __('Label key');
+$strConfigExport_latex_mime_name = __('MIME type');
+$strConfigExport_latex_null_name = __('Replace NULL with');
+$strConfigExport_latex_relation_name = __('Relations');
+$strConfigExport_latex_structure_caption_name = __('Table caption');
+$strConfigExport_latex_structure_continued_caption_name = __('Continued table caption');
+$strConfigExport_latex_structure_label_name = __('Label key');
+$strConfigExport_latex_structure_or_data_name = __('Dump table');
+$strConfigExport_method_name = __('Export method');
+$strConfigExport_ods_columns_name = __('Put columns names in the first row');
+$strConfigExport_ods_null_name = __('Replace NULL with');
+$strConfigExport_odt_columns_name = __('Put columns names in the first row');
+$strConfigExport_odt_comments_name = __('Comments');
+$strConfigExport_odt_mime_name = __('MIME type');
+$strConfigExport_odt_null_name = __('Replace NULL with');
+$strConfigExport_odt_relation_name = __('Relations');
+$strConfigExport_odt_structure_or_data_name = __('Dump table');
+$strConfigExport_onserver_name = __('Save on server');
+$strConfigExport_onserver_overwrite_name = __('Overwrite existing file(s)');
+$strConfigExport_quick_export_onserver_name = __('Save on server');
+$strConfigExport_quick_export_onserver_overwrite_name = __('Overwrite existing file(s)');
+$strConfigExport_remember_file_template_name = __('Remember file name template');
+$strConfigExport_sql_auto_increment_name = __('Add AUTO_INCREMENT value');
+$strConfigExport_sql_backquotes_name = __('Enclose table and column names with backquotes');
+$strConfigExport_sql_compatibility_name = __('SQL compatibility mode');
+$strConfigExport_sql_create_table_statements_name = __('<code>CREATE TABLE</code> options:');
+$strConfigExport_sql_dates_name = __('Creation/Update/Check dates');
+$strConfigExport_sql_delayed_name = __('Use delayed inserts');
+$strConfigExport_sql_disable_fk_name = __('Disable foreign key checks');
+$strConfigExport_sql_views_as_tables_name = __('Export views as tables');
+$strConfigExport_sql_drop_database_name = sprintf(__('Add %s'), 'DROP DATABASE');
+$strConfigExport_sql_drop_table_name = sprintf(__('Add %s'), 'DROP TABLE / VIEW / PROCEDURE / FUNCTION / EVENT');
+$strConfigExport_sql_hex_for_blob_name = __('Use hexadecimal for BLOB');
+$strConfigExport_sql_if_not_exists_name = sprintf(__('Add %s'), 'IF NOT EXISTS');
+$strConfigExport_sql_ignore_name = __('Use ignore inserts');
+$strConfigExport_sql_include_comments_name = __('Comments');
+$strConfigExport_sql_insert_syntax_name = __('Syntax to use when inserting data');
+$strConfigExport_sql_max_query_size_name = __('Maximal length of created query');
+$strConfigExport_sql_mime_name = __('MIME type');
+$strConfigExport_sql_procedure_function_name = sprintf(__('Add %s'), 'CREATE PROCEDURE / FUNCTION / EVENT');
+$strConfigExport_sql_relation_name = __('Relations');
+$strConfigExport_sql_structure_or_data_name = __('Dump table');
+$strConfigExport_sql_type_name = __('Export type');
+$strConfigExport_sql_use_transaction_name = __('Enclose export in a transaction');
+$strConfigExport_sql_utc_time_name = __('Export time in UTC');
+$strConfigExport_texytext_columns_name = __('Put columns names in the first row');
+$strConfigExport_texytext_null_name = __('Replace NULL with');
+$strConfigExport_texytext_structure_or_data_name = __('Dump table');
+$strConfigExport_xls_columns_name = __('Put columns names in the first row');
+$strConfigExport_xls_null_name = __('Replace NULL with');
+$strConfigExport_xlsx_columns_name = __('Put columns names in the first row');
+$strConfigExport_xlsx_null_name = __('Replace NULL with');
+$strConfigForceSSL_desc = __('Force secured connection while using phpMyAdmin');
+$strConfigForceSSL_name = __('Force SSL connection');
+$strConfigForeignKeyDropdownOrder_desc = __('Sort order for items in a foreign-key dropdown box; [kbd]content[/kbd] is the referenced data, [kbd]id[/kbd] is the key value');
+$strConfigForeignKeyDropdownOrder_name = __('Foreign key dropdown order');
+$strConfigForeignKeyMaxLimit_desc = __('A dropdown will be used if fewer items are present');
+$strConfigForeignKeyMaxLimit_name = __('Foreign key limit');
+$strConfigForm_Browse = __('Browse mode');
+$strConfigForm_Browse_desc = __('Customize browse mode');
+$strConfigForm_CodeGen = 'CodeGen';
+$strConfigForm_CodeGen_desc = __('Customize default options');
+$strConfigForm_Csv = __('CSV');
+$strConfigForm_Csv_desc = __('Customize default options');
+$strConfigForm_Developer = __('Developer');
+$strConfigForm_Developer_desc = __('Settings for phpMyAdmin developers');
+$strConfigForm_Edit = __('Edit mode');
+$strConfigForm_Edit_desc = __('Customize edit mode');
+$strConfigForm_Export = __('Export');
+$strConfigForm_Export_defaults = __('Export defaults');
+$strConfigForm_Export_defaults_desc = __('Customize default export options');
+$strConfigForm_Features = __('Features');
+$strConfigForm_General = __('General');
+$strConfigForm_General_desc = __('Set some commonly used options');
+$strConfigForm_Import = __('Import');
+$strConfigForm_Import_defaults = __('Import defaults');
+$strConfigForm_Import_defaults_desc = __('Customize default common import options');
+$strConfigForm_Import_export = __('Import / export');
+$strConfigForm_Import_export_desc = __('Set import and export directories and compression options');
+$strConfigForm_Latex = __('LaTeX');
+$strConfigForm_Latex_desc = __('Customize default options');
+$strConfigForm_Navi_databases = __('Databases');
+$strConfigForm_Navi_databases_desc = __('Databases display options');
+$strConfigForm_Navi_panel = __('Navigation panel');
+$strConfigForm_Navi_panel_desc = __('Customize appearance of the navigation panel');
+$strConfigForm_Navi_servers = __('Servers');
+$strConfigForm_Navi_servers_desc = __('Servers display options');
+$strConfigForm_Navi_tables = __('Tables');
+$strConfigForm_Navi_tables_desc = __('Tables display options');
+$strConfigForm_Main_panel = __('Main panel');
+$strConfigForm_Microsoft_Office = __('Microsoft Office');
+$strConfigForm_Microsoft_Office_desc = __('Customize default options');
+$strConfigForm_Open_Document = 'OpenDocument';
+$strConfigForm_Open_Document_desc = __('Customize default options');
+$strConfigForm_Other_core_settings = __('Other core settings');
+$strConfigForm_Other_core_settings_desc = __('Settings that didn\'t fit anywhere else');
+$strConfigForm_Page_titles = __('Page titles');
+$strConfigForm_Page_titles_desc = __('Specify browser\'s title bar text. Refer to [doc@cfg_TitleTable]documentation[/doc] for magic strings that can be used to get special values.');
+$strConfigForm_Query_window = __('Query window');
+$strConfigForm_Query_window_desc = __('Customize query window options');
+$strConfigForm_Security = __('Security');
+$strConfigForm_Security_desc = __('Please note that phpMyAdmin is just a user interface and its features do not limit MySQL');
+$strConfigForm_Server = __('Basic settings');
+$strConfigForm_Server_auth = __('Authentication');
+$strConfigForm_Server_auth_desc = __('Authentication settings');
+$strConfigForm_Server_config = __('Server configuration');
+$strConfigForm_Server_config_desc = __('Advanced server configuration, do not change these options unless you know what they are for');
+$strConfigForm_Server_desc = __('Enter server connection parameters');
+$strConfigForm_Server_pmadb = __('Configuration storage');
+$strConfigForm_Server_pmadb_desc = __('Configure phpMyAdmin configuration storage to gain access to additional features, see [doc@linked-tables]phpMyAdmin configuration storage[/doc] in documentation');
+$strConfigForm_Server_tracking = __('Changes tracking');
+$strConfigForm_Server_tracking_desc = __('Tracking of changes made in database. Requires the phpMyAdmin configuration storage.');
+$strConfigFormset_Export = __('Customize export options');
+$strConfigFormset_Features = __('Features');
+$strConfigFormset_Import = __('Customize import defaults');
+$strConfigFormset_Navi_panel = __('Customize navigation panel');
+$strConfigFormset_Main_panel = __('Customize main panel');
+$strConfigFormset_Sql_queries = __('SQL queries');
+$strConfigForm_Sql = __('SQL');
+$strConfigForm_Sql_box = __('SQL Query box');
+$strConfigForm_Sql_box_desc = __('Customize links shown in SQL Query boxes');
+$strConfigForm_Sql_desc = __('Customize default options');
+$strConfigForm_Sql_queries = __('SQL queries');
+$strConfigForm_Sql_queries_desc = __('SQL queries settings');
+$strConfigForm_Sql_validator = __('SQL Validator');
+$strConfigForm_Sql_validator_desc = __('If you wish to use the SQL Validator service, you should be aware that [strong]all SQL statements are stored anonymously for statistical purposes[/strong].[br][em][a@http://sqlvalidator.mimer.com/]Mimer SQL Validator[/a], Copyright 2002 Upright Database Technology. All rights reserved.[/em]');
+$strConfigForm_Startup = __('Startup');
+$strConfigForm_Startup_desc = __('Customize startup page');
+$strConfigForm_DbStructure = __('Database structure');
+$strConfigForm_DbStructure_desc = __('Choose which details to show in the database structure (list of tables)');
+$strConfigForm_TableStructure = __('Table structure');
+$strConfigForm_TableStructure_desc = __('Settings for the table structure (list of columns)');
+$strConfigForm_Tabs = __('Tabs');
+$strConfigForm_Tabs_desc = __('Choose how you want tabs to work');
+$strConfigForm_DisplayRelationalSchema = __('Display relational schema');
+$strConfigForm_DisplayRelationalSchema_desc = '';
+$strConfigPDFDefaultPageSize_name = __('Paper size');
+$strConfigPDFDefaultPageSize_desc = '';
+$strConfigForm_Text_fields = __('Text fields');
+$strConfigForm_Text_fields_desc = __('Customize text input fields');
+$strConfigForm_Texy = __('Texy! text');
+$strConfigForm_Texy_desc = __('Customize default options');
+$strConfigForm_Warnings = __('Warnings');
+$strConfigForm_Warnings_desc = __('Disable some of the warnings shown by phpMyAdmin');
+$strConfigGZipDump_desc = __('Enable [a@http://en.wikipedia.org/wiki/Gzip]gzip[/a] compression for import and export operations');
+$strConfigGZipDump_name = __('GZip');
+$strConfigIconvExtraParams_name = __('Extra parameters for iconv');
+$strConfigIgnoreMultiSubmitErrors_desc = __('If enabled, phpMyAdmin continues computing multiple-statement queries even if one of the queries failed');
+$strConfigIgnoreMultiSubmitErrors_name = __('Ignore multiple statement errors');
+$strConfigImport_allow_interrupt_desc = __('Allow interrupt of import in case script detects it is close to time limit. This might be a good way to import large files, however it can break transactions.');
+$strConfigImport_allow_interrupt_name = __('Partial import: allow interrupt');
+$strConfigImport_charset_name = __('Character set of the file');
+$strConfigImport_csv_col_names_name = __('Lines terminated with');
+$strConfigImport_csv_enclosed_name = __('Columns enclosed with');
+$strConfigImport_csv_escaped_name = __('Columns escaped with');
+$strConfigImport_csv_ignore_name = __('Do not abort on INSERT error');
+$strConfigImport_csv_replace_name = __('Replace table data with file');
+$strConfigImport_csv_terminated_name = __('Columns terminated with');
+$strConfigImport_format_desc = __('Default format; be aware that this list depends on location (database, table) and only SQL is always available');
+$strConfigImport_format_name = __('Format of imported file');
+$strConfigImport_ldi_enclosed_name = __('Columns enclosed with');
+$strConfigImport_ldi_escaped_name = __('Columns escaped with');
+$strConfigImport_ldi_ignore_name = __('Do not abort on INSERT error');
+$strConfigImport_ldi_local_option_name = __('Use LOCAL keyword');
+$strConfigImport_ldi_replace_name = __('Replace table data with file');
+$strConfigImport_ldi_terminated_name = __('Columns terminated with');
+$strConfigImport_ods_col_names_name = __('Column names in first row');
+$strConfigImport_ods_empty_rows_name = __('Do not import empty rows');
+$strConfigImport_ods_recognize_currency_name = __('Import currencies ($5.00 to 5.00)');
+$strConfigImport_ods_recognize_percentages_name = __('Import percentages as proper decimals (12.00% to .12)');
+$strConfigImport_skip_queries_desc = __('Number of queries to skip from start');
+$strConfigImport_skip_queries_name = __('Partial import: skip queries');
+$strConfigImport_sql_compatibility_name = __('SQL compatibility mode');
+$strConfigImport_sql_no_auto_value_on_zero_name = __('Do not use AUTO_INCREMENT for zero values');
+$strConfigImport_xls_col_names_name = __('Column names in first row');
+$strConfigImport_xlsx_col_names_name = __('Column names in first row');
+$strConfigInitialSlidersState_name = __('Initial state for sliders');
+$strConfigInsertRows_desc = __('How many rows can be inserted at one time');
+$strConfigInsertRows_name = __('Number of inserted rows');
+$strConfigLimitChars_desc = __('Maximum number of characters shown in any non-numeric column on browse view');
+$strConfigLimitChars_name = __('Limit column characters');
+$strConfigLoginCookieDeleteAll_desc = __('If TRUE, logout deletes cookies for all servers; when set to FALSE, logout only occurs for the current server. Setting this to FALSE makes it easy to forget to log out from other servers when connected to multiple servers.');
+$strConfigLoginCookieDeleteAll_name = __('Delete all cookies on logout');
+$strConfigLoginCookieRecall_desc = __('Define whether the previous login should be recalled or not in cookie authentication mode');
+$strConfigLoginCookieRecall_name = __('Recall user name');
+$strConfigLoginCookieStore_desc = __('Defines how long (in seconds) a login cookie should be stored in browser. The default of 0 means that it will be kept for the existing session only, and will be deleted as soon as you close the browser window. This is recommended for non-trusted environments.');
+$strConfigLoginCookieStore_name = __('Login cookie store');
+$strConfigLoginCookieValidity_desc = __('Define how long (in seconds) a login cookie is valid');
+$strConfigLoginCookieValidity_name = __('Login cookie validity');
+$strConfigLongtextDoubleTextarea_desc = __('Double size of textarea for LONGTEXT columns');
+$strConfigLongtextDoubleTextarea_name = __('Bigger textarea for LONGTEXT');
+$strConfigMaxCharactersInDisplayedSQL_desc = __('Maximum number of characters used when a SQL query is displayed');
+$strConfigMaxCharactersInDisplayedSQL_name = __('Maximum displayed SQL length');
+$strConfigMaxDbList_cmt = __('Users cannot set a higher value');
+$strConfigMaxDbList_desc = __('Maximum number of databases displayed in database list');
+$strConfigMaxDbList_name = __('Maximum databases');
+$strConfigMaxNavigationItems_desc = __('The number of items that can be displayed on each page of the navigation tree');
+$strConfigMaxNavigationItems_name = __('Maximum items in branch');
+$strConfigMaxRows_desc = __('Number of rows displayed when browsing a result set. If the result set contains more rows, &quot;Previous&quot; and &quot;Next&quot; links will be shown.');
+$strConfigMaxRows_name = __('Maximum number of rows to display');
+$strConfigMaxTableList_cmt = __('Users cannot set a higher value');
+$strConfigMaxTableList_desc = __('Maximum number of tables displayed in table list');
+$strConfigMaxTableList_name = __('Maximum tables');
+$strConfigMcryptDisableWarning_desc = __('Disable the default warning that is displayed if mcrypt is missing for cookie authentication');
+$strConfigMcryptDisableWarning_name = __('mcrypt warning');
+$strConfigMemoryLimit_desc = __('The number of bytes a script is allowed to allocate, eg. [kbd]32M[/kbd] ([kbd]0[/kbd] for no limit)');
+$strConfigMemoryLimit_name = __('Memory limit');
+$strConfigNavigationDisplayLogo_desc = __('Show logo in navigation panel');
+$strConfigNavigationDisplayLogo_name = __('Display logo');
+$strConfigNavigationLogoLink_desc = __('URL where logo in the navigation panel will point to');
+$strConfigNavigationLogoLink_name = __('Logo link URL');
+$strConfigNavigationLogoLinkWindow_desc = __('Open the linked page in the main window ([kbd]main[/kbd]) or in a new one ([kbd]new[/kbd])');
+$strConfigNavigationLogoLinkWindow_name = __('Logo link target');
+$strConfigNavigationDisplayServers_desc = __('Display server choice at the top of the navigation panel');
+$strConfigNavigationDisplayServers_name = __('Display servers selection');
+$strConfigNavigationTreeDefaultTabTable_name = __('Target for quick access icon');
+$strConfigNavigationTreeDisplayItemFilterMinimum_desc = __('Defines the minimum number of items (tables, views, routines and events) to display a filter box.');
+$strConfigNavigationTreeDisplayItemFilterMinimum_name = __('Minimum number of items to display the filter box');
+$strConfigNavigationTreeDisplayDbFilterMinimum_name = __('Minimum number of databases to display the database filter box');
+$strConfigNavigationTreeEnableGrouping_desc = __('Group items in the navigation tree (determined by the separator defined below)');
+$strConfigNavigationTreeEnableGrouping_name = __('Group items in the tree');
+$strConfigNavigationTreeDbSeparator_desc = __('String that separates databases into different tree levels');
+$strConfigNavigationTreeDbSeparator_name = __('Database tree separator');
+$strConfigNavigationTreeTableSeparator_desc = __('String that separates tables into different tree levels');
+$strConfigNavigationTreeTableSeparator_name = __('Table tree separator');
+$strConfigNavigationTreeTableLevel_name = __('Maximum table tree depth');
+$strConfigNavigationTreePointerEnable_desc = __('Highlight server under the mouse cursor');
+$strConfigNavigationTreePointerEnable_name = __('Enable highlighting');
+$strConfigNumRecentTables_desc = __('Maximum number of recently used tables; set 0 to disable');
+$strConfigNumRecentTables_name = __('Recently used tables');
+$strConfigRowActionLinks_desc = __('These are Edit, Copy and Delete links');
+$strConfigRowActionLinks_name = __('Where to show the table row links');
+$strConfigNaturalOrder_desc = __('Use natural order for sorting table and database names');
+$strConfigNaturalOrder_name = __('Natural order');
+$strConfigTableNavigationLinksMode_desc = __('Use only icons, only text or both');
+$strConfigTableNavigationLinksMode_name = __('Table navigation bar');
+$strConfigOBGzip_desc = __('use GZip output buffering for increased speed in HTTP transfers');
+$strConfigOBGzip_name = __('GZip output buffering');
+$strConfigOrder_desc = __('[kbd]SMART[/kbd] - i.e. descending order for columns of type TIME, DATE, DATETIME and TIMESTAMP, ascending order otherwise');
+$strConfigOrder_name = __('Default sorting order');
+$strConfigPersistentConnections_desc = __('Use persistent connections to MySQL databases');
+$strConfigPersistentConnections_name = __('Persistent connections');
+$strConfigPmaNoRelation_DisableWarning_desc = __('Disable the default warning that is displayed on the database details Structure page if any of the required tables for the phpMyAdmin configuration storage could not be found');
+$strConfigPmaNoRelation_DisableWarning_name = __('Missing phpMyAdmin configuration storage tables');
+$strConfigServerLibraryDifference_DisableWarning_desc = __('Disable the default warning that is displayed if a difference between the MySQL library and server is detected');
+$strConfigServerLibraryDifference_DisableWarning_name = __('Server/library difference warning');
+$strConfigReservedWordDisableWarning_desc = __('Disable the default warning that is displayed on the Structure page if column names in a table are reserved MySQL words');
+$strConfigReservedWordDisableWarning_name = __('MySQL reserved word warning');
+$strConfigTabsMode_desc = __('Use only icons, only text or both');
+$strConfigTabsMode_name = __('How to display the menu tabs');
+$strConfigActionLinksMode_desc = __('Use only icons, only text or both');
+$strConfigActionLinksMode_name = __('How to display various action links');
+$strConfigProtectBinary_desc = __('Disallow BLOB and BINARY columns from editing');
+$strConfigProtectBinary_name = __('Protect binary columns');
+$strConfigQueryHistoryDB_desc = __('Enable if you want DB-based query history (requires phpMyAdmin configuration storage). If disabled, this utilizes JS-routines to display query history (lost by window close).');
+$strConfigQueryHistoryDB_name = __('Permanent query history');
+$strConfigQueryHistoryMax_cmt = __('Users cannot set a higher value');
+$strConfigQueryHistoryMax_desc = __('How many queries are kept in history');
+$strConfigQueryHistoryMax_name = __('Query history length');
+$strConfigQueryWindowDefTab_desc = __('Tab displayed when opening a new query window');
+$strConfigQueryWindowDefTab_name = __('Default query window tab');
+$strConfigQueryWindowHeight_desc = __('Query window height (in pixels)');
+$strConfigQueryWindowHeight_name = __('Query window height');
+$strConfigQueryWindowWidth_desc = __('Query window width (in pixels)');
+$strConfigQueryWindowWidth_name = __('Query window width');
+$strConfigRecodingEngine_desc = __('Select which functions will be used for character set conversion');
+$strConfigRecodingEngine_name = __('Recoding engine');
+$strConfigRememberSorting_desc = __('When browsing tables, the sorting of each table is remembered');
+$strConfigRememberSorting_name = __('Remember table\'s sorting');
+$strConfigRepeatCells_desc = __('Repeat the headers every X cells, [kbd]0[/kbd] deactivates this feature');
+$strConfigRepeatCells_name = __('Repeat headers');
+$strConfigRestoreDefaultValue = __('Restore default value');
+$strConfigGridEditing_name = __('Grid editing: trigger action');
+$strConfigSaveCellsAtOnce_name = __('Grid editing: save all edited cells at once');
+$strConfigSaveDir_desc = __('Directory where exports can be saved on server');
+$strConfigSaveDir_name = __('Save directory');
+$strConfigServers_AllowDeny_order_desc = __('Leave blank if not used');
+$strConfigServers_AllowDeny_order_name = __('Host authorization order');
+$strConfigServers_AllowDeny_rules_desc = __('Leave blank for defaults');
+$strConfigServers_AllowDeny_rules_name = __('Host authorization rules');
+$strConfigServers_AllowNoPassword_name = __('Allow logins without a password');
+$strConfigServers_AllowRoot_name = __('Allow root login');
+$strConfigServers_auth_http_realm_desc = __('HTTP Basic Auth Realm name to display when doing HTTP Auth');
+$strConfigServers_auth_http_realm_name = __('HTTP Realm');
+$strConfigServers_auth_swekey_config_desc = __('The path for the config file for [a@http://swekey.com]SweKey hardware authentication[/a] (not located in your document root; suggested: /etc/swekey.conf)');
+$strConfigServers_auth_swekey_config_name = __('SweKey config file');
+$strConfigServers_auth_type_desc = __('Authentication method to use');
+$strConfigServers_auth_type_name = __('Authentication type');
+$strConfigServers_bookmarktable_desc = __('Leave blank for no [a@http://wiki.phpmyadmin.net/pma/bookmark]bookmark[/a] support, suggested: [kbd]pma__bookmark[/kbd]');
+$strConfigServers_bookmarktable_name = __('Bookmark table');
+$strConfigServers_column_info_desc = __('Leave blank for no column comments/mime types, suggested: [kbd]pma__column_info[/kbd]');
+$strConfigServers_column_info_name = __('Column information table');
+$strConfigServers_compress_desc = __('Compress connection to MySQL server');
+$strConfigServers_compress_name = __('Compress connection');
+$strConfigServers_connect_type_desc = __('How to connect to server, keep [kbd]tcp[/kbd] if unsure');
+$strConfigServers_connect_type_name = __('Connection type');
+$strConfigServers_controlpass_name = __('Control user password');
+$strConfigServers_controluser_desc = __('A special MySQL user configured with limited permissions, more information available on [a@http://wiki.phpmyadmin.net/pma/controluser]wiki[/a]');
+$strConfigServers_controluser_name = __('Control user');
+$strConfigServers_controlhost_desc = __('An alternate host to hold the configuration storage; leave blank to use the already defined host');
+$strConfigServers_controlhost_name = __('Control host');
+$strConfigServers_controlport_desc = __('An alternate port to connect to the host that holds the configuration storage; leave blank to use the default port, or the already defined port, if the controlhost equals host');
+$strConfigServers_controlport_name = __('Control port');
+$strConfigServers_designer_coords_desc = __('Leave blank for no Designer support, suggested: [kbd]pma__designer_coords[/kbd]');
+$strConfigServers_designer_coords_name = __('Designer table');
+$strConfigServers_extension_desc = __('What PHP extension to use; you should use mysqli if supported');
+$strConfigServers_extension_name = __('PHP extension to use');
+$strConfigServers_hide_db_desc = __('Hide databases matching regular expression (PCRE)');
+$strConfigServers_hide_db_name = __('Hide databases');
+$strConfigServers_history_desc = __('Leave blank for no SQL query history support, suggested: [kbd]pma__history[/kbd]');
+$strConfigServers_history_name = __('SQL query history table');
+$strConfigServers_host_desc = __('Hostname where MySQL server is running');
+$strConfigServers_host_name = __('Server hostname');
+$strConfigServers_LogoutURL_name = __('Logout URL');
+$strConfigServers_MaxTableUiprefs_desc = __('Limits number of table preferences which are stored in database, the oldest records are automatically removed');
+$strConfigServers_MaxTableUiprefs_name = __('Maximal number of table preferences to store');
+$strConfigServers_nopassword_desc = __('Try to connect without password');
+$strConfigServers_nopassword_name = __('Connect without password');
+$strConfigServers_only_db_desc = __('You can use MySQL wildcard characters (% and _), escape them if you want to use their literal instances, i.e. use [kbd]\'my\_db\'[/kbd] and not [kbd]\'my_db\'[/kbd].');
+$strConfigServers_only_db_name = __('Show only listed databases');
+$strConfigServers_password_desc = __('Leave empty if not using config auth');
+$strConfigServers_password_name = __('Password for config auth');
+$strConfigServers_pdf_pages_desc = __('Leave blank for no PDF schema support, suggested: [kbd]pma__pdf_pages[/kbd]');
+$strConfigServers_pdf_pages_name = __('PDF schema: pages table');
+$strConfigServers_pmadb_desc = __('Database used for relations, bookmarks, and PDF features. See [a@http://wiki.phpmyadmin.net/pma/pmadb]pmadb[/a] for complete information. Leave blank for no support. Suggested: [kbd]phpmyadmin[/kbd]');
+$strConfigServers_pmadb_name = __('Database name');
+$strConfigServers_port_desc = __('Port on which MySQL server is listening, leave empty for default');
+$strConfigServers_port_name = __('Server port');
+$strConfigServers_recent_desc = __('Leave blank for no "persistent" recently used tables across sessions, suggested: [kbd]pma__recent[/kbd]');
+$strConfigServers_recent_name = __('Recently used table');
+$strConfigServers_relation_desc = __('Leave blank for no [a@http://wiki.phpmyadmin.net/pma/relation]relation-links[/a] support, suggested: [kbd]pma__relation[/kbd]');
+$strConfigServers_relation_name = __('Relation table');
+$strConfigServers_SignonSession_desc = __('See [a@http://wiki.phpmyadmin.net/pma/auth_types#signon]authentication types[/a] for an example');
+$strConfigServers_SignonSession_name = __('Signon session name');
+$strConfigServers_SignonURL_name = __('Signon URL');
+$strConfigServers_socket_desc = __('Socket on which MySQL server is listening, leave empty for default');
+$strConfigServers_socket_name = __('Server socket');
+$strConfigServers_ssl_desc = __('Enable SSL for connection to MySQL server');
+$strConfigServers_ssl_name = __('Use SSL');
+$strConfigServers_table_coords_desc = __('Leave blank for no PDF schema support, suggested: [kbd]pma__table_coords[/kbd]');
+$strConfigServers_table_coords_name = __('PDF schema: table coordinates');
+$strConfigServers_table_info_desc = __('Table to describe the display columns, leave blank for no support; suggested: [kbd]pma__table_info[/kbd]');
+$strConfigServers_table_info_name = __('Display columns table');
+$strConfigServers_table_uiprefs_desc = __('Leave blank for no "persistent" tables\' UI preferences across sessions, suggested: [kbd]pma__table_uiprefs[/kbd]');
+$strConfigServers_table_uiprefs_name = __('UI preferences table');
+$strConfigServers_tracking_add_drop_database_desc = __('Whether a DROP DATABASE IF EXISTS statement will be added as first line to the log when creating a database.');
+$strConfigServers_tracking_add_drop_database_name = __('Add DROP DATABASE');
+$strConfigServers_tracking_add_drop_table_desc = __('Whether a DROP TABLE IF EXISTS statement will be added as first line to the log when creating a table.');
+$strConfigServers_tracking_add_drop_table_name = __('Add DROP TABLE');
+$strConfigServers_tracking_add_drop_view_desc = __('Whether a DROP VIEW IF EXISTS statement will be added as first line to the log when creating a view.');
+$strConfigServers_tracking_add_drop_view_name = __('Add DROP VIEW');
+$strConfigServers_tracking_default_statements_desc = __('Defines the list of statements the auto-creation uses for new versions.');
+$strConfigServers_tracking_default_statements_name = __('Statements to track');
+$strConfigServers_tracking_desc = __('Leave blank for no SQL query tracking support, suggested: [kbd]pma__tracking[/kbd]');
+$strConfigServers_tracking_name = __('SQL query tracking table');
+$strConfigServers_tracking_version_auto_create_desc = __('Whether the tracking mechanism creates versions for tables and views automatically.');
+$strConfigServers_tracking_version_auto_create_name = __('Automatically create versions');
+$strConfigServers_userconfig_desc = __('Leave blank for no user preferences storage in database, suggested: [kbd]pma__userconfig[/kbd]');
+$strConfigServers_userconfig_name = __('User preferences storage table');
+$strConfigServers_users_desc = __('Leave blank to disable configurable menus feature, suggested: [kbd]pma__users[/kbd]');
+$strConfigServers_users_name = __('Users table');
+$strConfigServers_usergroups_desc = __('Leave blank to disable configurable menus feature, suggested: [kbd]pma__usergroups[/kbd]');
+$strConfigServers_usergroups_name = __('User groups table');
+$strConfigServers_navigationhiding_desc = __('Leave blank to disable the feature to hide and show navigation items, suggested: [kbd]pma__navigationhiding[/kbd]');
+$strConfigServers_navigationhiding_name = __('Hidden navigation items table');
+$strConfigServers_user_desc = __('Leave empty if not using config auth');
+$strConfigServers_user_name = __('User for config auth');
+$strConfigServers_verbose_desc = __('A user-friendly description of this server. Leave blank to display the hostname instead.');
+$strConfigServers_verbose_name = __('Verbose name of this server');
+$strConfigShowAll_desc = __('Whether a user should be displayed a &quot;show all (rows)&quot; button');
+$strConfigShowAll_name = __('Allow to display all the rows');
+$strConfigShowChgPassword_desc = __('Please note that enabling this has no effect with [kbd]config[/kbd] authentication mode because the password is hard coded in the configuration file; this does not limit the ability to execute the same command directly');
+$strConfigShowChgPassword_name = __('Show password change form');
+$strConfigShowCreateDb_name = __('Show create database form');
+$strConfigShowDbStructureCreation_desc = __('Show or hide a column displaying the Creation timestamp for all tables');
+$strConfigShowDbStructureCreation_name = __('Show Creation timestamp');
+$strConfigShowDbStructureLastUpdate_desc = __('Show or hide a column displaying the Last update timestamp for all tables');
+$strConfigShowDbStructureLastUpdate_name = __('Show Last update timestamp');
+$strConfigShowDbStructureLastCheck_desc = __('Show or hide a column displaying the Last check timestamp for all tables');
+$strConfigShowDbStructureLastCheck_name = __('Show Last check timestamp');
+$strConfigShowDisplayDirection_desc = __('Defines whether or not type display direction option is shown when browsing a table');
+$strConfigShowDisplayDirection_name = __('Show display direction');
+$strConfigShowFieldTypesInDataEditView_desc = __('Defines whether or not type fields should be initially displayed in edit/insert mode');
+$strConfigShowFieldTypesInDataEditView_name = __('Show field types');
+$strConfigShowFunctionFields_desc = __('Display the function fields in edit/insert mode');
+$strConfigShowFunctionFields_name = __('Show function fields');
+$strConfigShowHint_desc = __('Whether to show hint or not');
+$strConfigShowHint_name = __('Show hint');
+$strConfigShowPhpInfo_desc = __('Shows link to [a@http://php.net/manual/function.phpinfo.php]phpinfo()[/a] output');
+$strConfigShowPhpInfo_name = __('Show phpinfo() link');
+$strConfigShowServerInfo_name = __('Show detailed MySQL server information');
+$strConfigShowSQL_desc = __('Defines whether SQL queries generated by phpMyAdmin should be displayed');
+$strConfigShowSQL_name = __('Show SQL queries');
+$strConfigRetainQueryBox_desc = __('Defines whether the query box should stay on-screen after its submission');
+$strConfigRetainQueryBox_name = __('Retain query box');
+$strConfigShowStats_desc = __('Allow to display database and table statistics (eg. space usage)');
+$strConfigShowStats_name = __('Show statistics');
+$strConfigSkipLockedTables_desc = __('Mark used tables and make it possible to show databases with locked tables');
+$strConfigSkipLockedTables_name = __('Skip locked tables');
+$strConfigSQLQuery_Edit_name = __('Edit');
+$strConfigSQLQuery_Explain_name = __('Explain SQL');
+$strConfigSQLQuery_Refresh_name = __('Refresh');
+$strConfigSQLQuery_ShowAsPHP_name = __('Create PHP Code');
+$strConfigSQLQuery_Validate_desc = __('Requires SQL Validator to be enabled');
+$strConfigSQLQuery_Validate_name = __('Validate SQL');
+$strConfigSQLValidator_password_name = __('Password');
+$strConfigSQLValidator_use_desc = __('[strong]Warning:[/strong] requires PHP SOAP extension or PEAR SOAP to be installed');
+$strConfigSQLValidator_use_name = __('Enable SQL Validator');
+$strConfigSQLValidator_username_desc = __('If you have a custom username, specify it here (defaults to [kbd]anonymous[/kbd])');
+$strConfigSQLValidator_username_name = __('Username');
+$strConfigSuhosinDisableWarning_desc = __('A warning is displayed on the main page if Suhosin is detected');
+$strConfigSuhosinDisableWarning_name = __('Suhosin warning');
+$strConfigTextareaCols_desc = __('Textarea size (columns) in edit mode, this value will be emphasized for SQL query textareas (*2) and for query window (*1.25)');
+$strConfigTextareaCols_name = __('Textarea columns');
+$strConfigTextareaRows_desc = __('Textarea size (rows) in edit mode, this value will be emphasized for SQL query textareas (*2) and for query window (*1.25)');
+$strConfigTextareaRows_name = __('Textarea rows');
+$strConfigTitleDatabase_desc = __('Title of browser window when a database is selected');
+$strConfigTitleDatabase_name = __('Database');
+$strConfigTitleDefault_desc = __('Title of browser window when nothing is selected');
+$strConfigTitleDefault_name = __('Default title');
+$strConfigTitleServer_desc = __('Title of browser window when a server is selected');
+$strConfigTitleServer_name = __('Server');
+$strConfigTitleTable_desc = __('Title of browser window when a table is selected');
+$strConfigTitleTable_name = __('Table');
+$strConfigTrustedProxies_desc = __('Input proxies as [kbd]IP: trusted HTTP header[/kbd]. The following example specifies that phpMyAdmin should trust a HTTP_X_FORWARDED_FOR (X-Forwarded-For) header coming from the proxy 1.2.3.4:[br][kbd]1.2.3.4: HTTP_X_FORWARDED_FOR[/kbd]');
+$strConfigTrustedProxies_name = __('List of trusted proxies for IP allow/deny');
+$strConfigUploadDir_desc = __('Directory on server where you can upload files for import');
+$strConfigUploadDir_name = __('Upload directory');
+$strConfigUseDbSearch_desc = __('Allow for searching inside the entire database');
+$strConfigUseDbSearch_name = __('Use database search');
+$strConfigUserprefsDeveloperTab_desc = __('When disabled, users cannot set any of the options below, regardless of the checkbox on the right');
+$strConfigUserprefsDeveloperTab_name = __('Enable the Developer tab in settings');
+$strConfigVersionCheckLink = __('Check for latest version');
+$strConfigVersionCheck_desc = __('Enables check for latest version on main phpMyAdmin page');
+$strConfigVersionCheck_name = __('Version check');
+$strConfigProxyUrl_desc = __('The url of the proxy to be used when retrieving the information about the latest version of phpMyAdmin or when submitting error reports. You need this if the server where phpMyAdmin is installed does not have direct access to the internet. The format is: "hostname:portnumber"');
+$strConfigProxyUrl_name = __('Proxy url');
+$strConfigProxyUser_desc = __('The username for authenticating with the proxy. By default, no authentication is performed. If a username is supplied, Basic Authentication will be performed. No other types of authentication are currently supported.');
+$strConfigProxyUser_name = __('Proxy username');
+$strConfigProxyPass_desc = __('The password for authenticating with the proxy');
+$strConfigProxyPass_name = __('Proxy password');
+
+$strConfigZipDump_desc = __('Enable [a@http://en.wikipedia.org/wiki/ZIP_(file_format)]ZIP[/a] compression for import and export operations');
+$strConfigZipDump_name = __('ZIP');
+$strConfigCaptchaLoginPublicKey_desc = __('Enter your public key for your domain reCaptcha service');
+$strConfigCaptchaLoginPublicKey_name = __('Public key for reCaptcha');
+$strConfigCaptchaLoginPrivateKey_desc = __('Enter your private key for your domain reCaptcha service');
+$strConfigCaptchaLoginPrivateKey_name = __('Private key for reCaptcha');
+
+$strConfigSendErrorReports_desc = __('Choose the default action when sending error reports');
+$strConfigSendErrorReports_name = __('Send error reports');
+
+?>
diff --git a/libraries/config/setup.forms.php b/libraries/config/setup.forms.php
new file mode 100644
index 0000000000..257f6e1f72
--- /dev/null
+++ b/libraries/config/setup.forms.php
@@ -0,0 +1,378 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * List of avaible forms, each form is described as an array of fields to display.
+ * Fields MUST have their counterparts in the $cfg array.
+ *
+ * There are two possible notations:
+ * $forms['Form group']['Form name'] = array('Servers' => array(1 => array('host')));
+ * can be written as
+ * $forms['Form group']['Form name'] = array('Servers/1/host');
+ *
+ * You can assign default values set by special button ("set value: ..."), eg.:
+ * 'Servers/1/pmadb' => 'phpmyadmin'
+ *
+ * To group options, use:
+ * ':group:' . __('group name') // just define a group
+ * or
+ * 'option' => ':group' // group starting from this option
+ * End group blocks with:
+ * ':group:end'
+ *
+ * @package PhpMyAdmin-Setup
+ */
+
+$forms = array();
+$forms['_config.php'] = array(
+ 'DefaultLang',
+ 'ServerDefault');
+$forms['Servers']['Server'] = array('Servers' => array(1 => array(
+ 'verbose',
+ 'host',
+ 'port',
+ 'socket',
+ 'ssl',
+ 'connect_type',
+ 'extension',
+ 'compress',
+ 'nopassword')));
+$forms['Servers']['Server_auth'] = array('Servers' => array(1 => array(
+ 'auth_type',
+ ':group:' . __('Config authentication'),
+ 'user',
+ 'password',
+ ':group:end',
+ ':group:' . __('Cookie authentication'),
+ 'auth_swekey_config' => './swekey.conf',
+ ':group:end',
+ ':group:' . __('HTTP authentication'),
+ 'auth_http_realm',
+ ':group:end',
+ ':group:' . __('Signon authentication'),
+ 'SignonSession',
+ 'SignonURL',
+ 'LogoutURL')));
+$forms['Servers']['Server_config'] = array('Servers' => array(1 => array(
+ 'only_db',
+ 'hide_db',
+ 'AllowRoot',
+ 'AllowNoPassword',
+ 'AllowDeny/order',
+ 'AllowDeny/rules')));
+$forms['Servers']['Server_pmadb'] = array('Servers' => array(1 => array(
+ 'pmadb' => 'phpmyadmin',
+ 'controlhost',
+ 'controlport',
+ 'controluser',
+ 'controlpass',
+ 'bookmarktable' => 'pma__bookmark',
+ 'relation' => 'pma__relation',
+ 'userconfig' => 'pma__userconfig',
+ 'users' => 'pma__users',
+ 'usergroups' => 'pma__usergroups',
+ 'navigationhiding' => 'pma__navigationhiding',
+ 'table_info' => 'pma__table_info',
+ 'column_info' => 'pma__column_info',
+ 'history' => 'pma__history',
+ 'recent' => 'pma__recent',
+ 'table_uiprefs' => 'pma__table_uiprefs',
+ 'tracking' => 'pma__tracking',
+ 'table_coords' => 'pma__table_coords',
+ 'pdf_pages' => 'pma__pdf_pages',
+ 'designer_coords' => 'pma__designer_coords',
+ 'MaxTableUiprefs' => 100)));
+$forms['Servers']['Server_tracking'] = array('Servers' => array(1 => array(
+ 'tracking_version_auto_create',
+ 'tracking_default_statements',
+ 'tracking_add_drop_view',
+ 'tracking_add_drop_table',
+ 'tracking_add_drop_database',
+)));
+$forms['Features']['Import_export'] = array(
+ 'UploadDir',
+ 'SaveDir',
+ 'RecodingEngine' => ':group',
+ 'IconvExtraParams',
+ ':group:end',
+ 'ZipDump',
+ 'GZipDump',
+ 'BZipDump',
+ 'CompressOnFly');
+$forms['Features']['Security'] = array(
+ 'blowfish_secret',
+ 'ForceSSL',
+ 'CheckConfigurationPermissions',
+ 'TrustedProxies',
+ 'AllowUserDropDatabase',
+ 'AllowArbitraryServer',
+ 'LoginCookieRecall',
+ 'LoginCookieValidity',
+ 'LoginCookieStore',
+ 'LoginCookieDeleteAll',
+ 'CaptchaLoginPublicKey',
+ 'CaptchaLoginPrivateKey');
+$forms['Features']['Page_titles'] = array(
+ 'TitleDefault',
+ 'TitleTable',
+ 'TitleDatabase',
+ 'TitleServer');
+$forms['Features']['Warnings'] = array(
+ 'ServerLibraryDifference_DisableWarning',
+ 'PmaNoRelation_DisableWarning',
+ 'SuhosinDisableWarning',
+ 'McryptDisableWarning');
+$forms['Features']['Developer'] = array(
+ 'UserprefsDeveloperTab',
+ 'Error_Handler/display',
+ 'DBG/sql');
+$forms['Features']['Other_core_settings'] = array(
+ 'NaturalOrder',
+ 'InitialSlidersState',
+ 'MaxDbList',
+ 'MaxTableList',
+ 'NumRecentTables',
+ 'ShowHint',
+ 'OBGzip',
+ 'PersistentConnections',
+ 'ExecTimeLimit',
+ 'MemoryLimit',
+ 'SkipLockedTables',
+ 'DisableMultiTableMaintenance',
+ 'UseDbSearch',
+ 'VersionCheck',
+ 'SendErrorReports',
+ 'ProxyUrl',
+ 'ProxyUser',
+ 'ProxyPass',
+ 'AllowThirdPartyFraming',
+);
+$forms['Sql_queries']['Sql_queries'] = array(
+ 'ShowSQL',
+ 'Confirm',
+ 'QueryHistoryDB',
+ 'QueryHistoryMax',
+ 'IgnoreMultiSubmitErrors',
+ 'MaxCharactersInDisplayedSQL',
+ 'EditInWindow',
+ //'QueryWindowWidth', // overridden in theme
+ //'QueryWindowHeight',
+ 'QueryWindowDefTab',
+ 'RetainQueryBox',
+ 'CodemirrorEnable');
+$forms['Sql_queries']['Sql_box'] = array('SQLQuery' => array(
+ 'Edit',
+ 'Explain',
+ 'ShowAsPHP',
+ 'Validate',
+ 'Refresh'));
+$forms['Sql_queries']['Sql_validator'] = array('SQLValidator' => array(
+ 'use',
+ 'username',
+ 'password'));
+$forms['Navi_panel']['Navi_panel'] = array(
+ 'NavigationDisplayLogo',
+ 'NavigationLogoLink',
+ 'NavigationLogoLinkWindow',
+ 'NavigationTreePointerEnable',
+ 'MaxNavigationItems',
+ 'NavigationTreeEnableGrouping',
+ 'NavigationTreeDisplayItemFilterMinimum');
+$forms['Navi_panel']['Navi_servers'] = array(
+ 'NavigationDisplayServers',
+ 'DisplayServersList');
+$forms['Navi_panel']['Navi_databases'] = array(
+ 'NavigationTreeDbSeparator');
+$forms['Navi_panel']['Navi_tables'] = array(
+ 'NavigationTreeDefaultTabTable',
+ 'NavigationTreeTableSeparator',
+ 'NavigationTreeTableLevel',
+);
+$forms['Main_panel']['Startup'] = array(
+ 'ShowCreateDb',
+ 'ShowStats',
+ 'ShowServerInfo',
+ 'ShowPhpInfo',
+ 'ShowChgPassword');
+$forms['Main_panel']['DbStructure'] = array(
+ 'ShowDbStructureCreation',
+ 'ShowDbStructureLastUpdate',
+ 'ShowDbStructureLastCheck');
+$forms['Main_panel']['TableStructure'] = array(
+ 'HideStructureActions');
+$forms['Main_panel']['Browse'] = array(
+ 'TableNavigationLinksMode',
+ 'ShowAll',
+ 'MaxRows',
+ 'Order',
+ 'BrowsePointerEnable',
+ 'BrowseMarkerEnable',
+ 'GridEditing',
+ 'SaveCellsAtOnce',
+ 'ShowDisplayDirection',
+ 'RepeatCells',
+ 'LimitChars',
+ 'RowActionLinks',
+ 'DefaultDisplay',
+ 'RememberSorting');
+$forms['Main_panel']['Edit'] = array(
+ 'ProtectBinary',
+ 'ShowFunctionFields',
+ 'ShowFieldTypesInDataEditView',
+ 'CharEditing',
+ 'MinSizeForInputField',
+ 'MaxSizeForInputField',
+ 'CharTextareaCols',
+ 'CharTextareaRows',
+ 'TextareaCols',
+ 'TextareaRows',
+ 'LongtextDoubleTextarea',
+ 'InsertRows',
+ 'ForeignKeyDropdownOrder',
+ 'ForeignKeyMaxLimit');
+$forms['Main_panel']['Tabs'] = array(
+ 'TabsMode',
+ 'ActionLinksMode',
+ 'DefaultTabServer',
+ 'DefaultTabDatabase',
+ 'DefaultTabTable',
+ 'QueryWindowDefTab');
+$forms['Import']['Import_defaults'] = array('Import' => array(
+ 'format',
+ 'charset',
+ 'allow_interrupt',
+ 'skip_queries'));
+$forms['Import']['Sql'] = array('Import' => array(
+ 'sql_compatibility',
+ 'sql_no_auto_value_on_zero'));
+$forms['Import']['Csv'] = array('Import' => array(
+ ':group:' . __('CSV'),
+ 'csv_replace',
+ 'csv_ignore',
+ 'csv_terminated',
+ 'csv_enclosed',
+ 'csv_escaped',
+ 'csv_col_names',
+ ':group:end',
+ ':group:' . __('CSV using LOAD DATA'),
+ 'ldi_replace',
+ 'ldi_ignore',
+ 'ldi_terminated',
+ 'ldi_enclosed',
+ 'ldi_escaped',
+ 'ldi_local_option',
+ ':group:end'));
+$forms['Import']['Open_Document'] = array('Import' => array(
+ ':group:' . __('OpenDocument Spreadsheet'),
+ 'ods_col_names',
+ 'ods_empty_rows',
+ 'ods_recognize_percentages',
+ 'ods_recognize_currency'));
+$forms['Export']['Export_defaults'] = array('Export' => array(
+ 'method',
+ ':group:' . __('Quick'),
+ 'quick_export_onserver',
+ 'quick_export_onserver_overwrite',
+ ':group:end',
+ ':group:' . __('Custom'),
+ 'format',
+ 'compression',
+ 'charset',
+ 'asfile' => ':group',
+ 'onserver',
+ 'onserver_overwrite',
+ ':group:end',
+ 'remember_file_template',
+ 'file_template_table',
+ 'file_template_database',
+ 'file_template_server'));
+$forms['Export']['Sql'] = array('Export' => array(
+ 'sql_include_comments' => ':group',
+ 'sql_dates',
+ 'sql_relation',
+ 'sql_mime',
+ ':group:end',
+ 'sql_use_transaction',
+ 'sql_disable_fk',
+ 'sql_views_as_tables',
+ 'sql_compatibility',
+ ':group:' . __('Database export options'),
+ 'sql_drop_database',
+ 'sql_structure_or_data',
+ ':group:end',
+ ':group:' . __('Structure'),
+ 'sql_drop_table',
+ 'sql_procedure_function',
+ 'sql_create_table_statements' => ':group',
+ 'sql_if_not_exists',
+ 'sql_auto_increment',
+ ':group:end',
+ 'sql_backquotes',
+ ':group:end',
+ ':group:' . __('Data'),
+ 'sql_delayed',
+ 'sql_ignore',
+ 'sql_type',
+ 'sql_insert_syntax',
+ 'sql_max_query_size',
+ 'sql_hex_for_blob',
+ 'sql_utc_time'));
+$forms['Export']['CodeGen'] = array('Export' => array(
+ 'codegen_format'));
+$forms['Export']['Csv'] = array('Export' => array(
+ ':group:' . __('CSV'),
+ 'csv_separator',
+ 'csv_enclosed',
+ 'csv_escaped',
+ 'csv_terminated',
+ 'csv_null',
+ 'csv_removeCRLF',
+ 'csv_columns',
+ ':group:end',
+ ':group:' . __('CSV for MS Excel'),
+ 'excel_null',
+ 'excel_removeCRLF',
+ 'excel_columns',
+ 'excel_edition'));
+$forms['Export']['Latex'] = array('Export' => array(
+ 'latex_caption',
+ 'latex_structure_or_data',
+ ':group:' . __('Structure'),
+ 'latex_structure_caption',
+ 'latex_structure_continued_caption',
+ 'latex_structure_label',
+ 'latex_relation',
+ 'latex_comments',
+ 'latex_mime',
+ ':group:end',
+ ':group:' . __('Data'),
+ 'latex_columns',
+ 'latex_data_caption',
+ 'latex_data_continued_caption',
+ 'latex_data_label',
+ 'latex_null'));
+$forms['Export']['Microsoft_Office'] = array('Export' => array(
+ ':group:' . __('Microsoft Word 2000'),
+ 'htmlword_structure_or_data',
+ 'htmlword_null',
+ 'htmlword_columns'));
+$forms['Export']['Open_Document'] = array('Export' => array(
+ ':group:' . __('OpenDocument Spreadsheet'),
+ 'ods_columns',
+ 'ods_null',
+ ':group:end',
+ ':group:' . __('OpenDocument Text'),
+ 'odt_structure_or_data',
+ ':group:' . __('Structure'),
+ 'odt_relation',
+ 'odt_comments',
+ 'odt_mime',
+ ':group:end',
+ ':group:' . __('Data'),
+ 'odt_columns',
+ 'odt_null'));
+$forms['Export']['Texy'] = array('Export' => array(
+ 'texytext_structure_or_data',
+ ':group:' . __('Data'),
+ 'texytext_null',
+ 'texytext_columns'));
+?>
diff --git a/libraries/config/user_preferences.forms.php b/libraries/config/user_preferences.forms.php
new file mode 100644
index 0000000000..e97bd34df9
--- /dev/null
+++ b/libraries/config/user_preferences.forms.php
@@ -0,0 +1,276 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * List of avaible forms, each form is described as an array of fields to display.
+ * Fields MUST have their counterparts in the $cfg array.
+ *
+ * To define form field, use the notatnion below:
+ * $forms['Form group']['Form name'] = array('Option/path');
+ *
+ * You can assign default values set by special button ("set value: ..."), eg.:
+ * 'Servers/1/pmadb' => 'phpmyadmin'
+ *
+ * To group options, use:
+ * ':group:' . __('group name') // just define a group
+ * or
+ * 'option' => ':group' // group starting from this option
+ * End group blocks with:
+ * ':group:end'
+ *
+ * @package PhpMyAdmin
+ */
+
+$forms = array();
+$forms['Features']['General'] = array(
+ 'VersionCheck',
+ 'NaturalOrder',
+ 'InitialSlidersState',
+ 'LoginCookieValidity',
+ 'Servers/1/only_db', // saves to Server/only_db
+ 'Servers/1/hide_db', // saves to Server/hide_db
+ 'SkipLockedTables',
+ 'DisableMultiTableMaintenance',
+ 'MaxDbList',
+ 'MaxTableList',
+ 'NumRecentTables',
+ 'ShowHint',
+ 'SendErrorReports');
+$forms['Features']['Text_fields'] = array(
+ 'CharEditing',
+ 'MinSizeForInputField',
+ 'MaxSizeForInputField',
+ 'CharTextareaCols',
+ 'CharTextareaRows',
+ 'TextareaCols',
+ 'TextareaRows',
+ 'LongtextDoubleTextarea');
+$forms['Features']['Page_titles'] = array(
+ 'TitleDefault',
+ 'TitleTable',
+ 'TitleDatabase',
+ 'TitleServer');
+$forms['Features']['Warnings'] = array(
+ 'ServerLibraryDifference_DisableWarning',
+ 'PmaNoRelation_DisableWarning',
+ 'SuhosinDisableWarning',
+ 'McryptDisableWarning',
+ 'ReservedWordDisableWarning');
+// settings from this form are treated specially,
+// see prefs_forms.php and user_preferences.lib.php
+$forms['Features']['Developer'] = array(
+ 'Error_Handler/display',
+ 'DBG/sql');
+$forms['Sql_queries']['Sql_queries'] = array(
+ 'ShowSQL',
+ 'Confirm',
+ 'QueryHistoryMax',
+ 'IgnoreMultiSubmitErrors',
+ 'MaxCharactersInDisplayedSQL',
+ 'EditInWindow',
+ //'QueryWindowWidth', // overridden in theme
+ //'QueryWindowHeight',
+ 'QueryWindowDefTab',
+ 'RetainQueryBox',
+ 'CodemirrorEnable');
+$forms['Sql_queries']['Sql_box'] = array(
+ 'SQLQuery/Edit',
+ 'SQLQuery/Explain',
+ 'SQLQuery/ShowAsPHP',
+ 'SQLQuery/Validate',
+ 'SQLQuery/Refresh');
+$forms['Navi_panel']['Navi_panel'] = array(
+ 'NavigationDisplayLogo',
+ 'NavigationLogoLink',
+ 'NavigationLogoLinkWindow',
+ 'NavigationTreePointerEnable',
+ 'MaxNavigationItems',
+ 'NavigationTreeEnableGrouping',
+ 'NavigationTreeDisplayItemFilterMinimum');
+$forms['Navi_panel']['Navi_databases'] = array(
+ 'NavigationTreeDisplayDbFilterMinimum',
+ 'NavigationTreeDbSeparator');
+$forms['Navi_panel']['Navi_tables'] = array(
+ 'NavigationTreeDefaultTabTable',
+ 'NavigationTreeTableSeparator',
+ 'NavigationTreeTableLevel',
+);
+$forms['Main_panel']['Startup'] = array(
+ 'ShowCreateDb',
+ 'ShowStats',
+ 'ShowServerInfo');
+$forms['Main_panel']['DbStructure'] = array(
+ 'ShowDbStructureCreation',
+ 'ShowDbStructureLastUpdate',
+ 'ShowDbStructureLastCheck');
+$forms['Main_panel']['TableStructure'] = array(
+ 'HideStructureActions');
+$forms['Main_panel']['Browse'] = array(
+ 'TableNavigationLinksMode',
+ 'ActionLinksMode',
+ 'ShowAll',
+ 'MaxRows',
+ 'Order',
+ 'DisplayBinaryAsHex',
+ 'BrowsePointerEnable',
+ 'BrowseMarkerEnable',
+ 'GridEditing',
+ 'SaveCellsAtOnce',
+ 'ShowDisplayDirection',
+ 'RepeatCells',
+ 'LimitChars',
+ 'RowActionLinks',
+ 'DefaultDisplay',
+ 'RememberSorting');
+$forms['Main_panel']['Edit'] = array(
+ 'ProtectBinary',
+ 'ShowFunctionFields',
+ 'ShowFieldTypesInDataEditView',
+ 'InsertRows',
+ 'ForeignKeyDropdownOrder',
+ 'ForeignKeyMaxLimit');
+$forms['Main_panel']['Tabs'] = array(
+ 'TabsMode',
+ 'DefaultTabServer',
+ 'DefaultTabDatabase',
+ 'DefaultTabTable');
+$forms['Main_panel']['DisplayRelationalSchema'] = array(
+ 'PDFDefaultPageSize');
+
+$forms['Import']['Import_defaults'] = array(
+ 'Import/format',
+ 'Import/charset',
+ 'Import/allow_interrupt',
+ 'Import/skip_queries');
+$forms['Import']['Sql'] = array(
+ 'Import/sql_compatibility',
+ 'Import/sql_no_auto_value_on_zero');
+$forms['Import']['Csv'] = array(
+ ':group:' . __('CSV'),
+ 'Import/csv_replace',
+ 'Import/csv_ignore',
+ 'Import/csv_terminated',
+ 'Import/csv_enclosed',
+ 'Import/csv_escaped',
+ 'Import/csv_col_names',
+ ':group:end',
+ ':group:' . __('CSV using LOAD DATA'),
+ 'Import/ldi_replace',
+ 'Import/ldi_ignore',
+ 'Import/ldi_terminated',
+ 'Import/ldi_enclosed',
+ 'Import/ldi_escaped',
+ 'Import/ldi_local_option');
+$forms['Import']['Open_Document'] = array(
+ ':group:' . __('OpenDocument Spreadsheet'),
+ 'Import/ods_col_names',
+ 'Import/ods_empty_rows',
+ 'Import/ods_recognize_percentages',
+ 'Import/ods_recognize_currency');
+$forms['Export']['Export_defaults'] = array(
+ 'Export/method',
+ ':group:' . __('Quick'),
+ 'Export/quick_export_onserver',
+ 'Export/quick_export_onserver_overwrite',
+ ':group:end',
+ ':group:' . __('Custom'),
+ 'Export/format',
+ 'Export/compression',
+ 'Export/charset',
+ 'Export/asfile' => ':group',
+ 'Export/onserver',
+ 'Export/onserver_overwrite',
+ ':group:end',
+ 'Export/file_template_table',
+ 'Export/file_template_database',
+ 'Export/file_template_server');
+$forms['Export']['Sql'] = array(
+ 'Export/sql_include_comments' => ':group',
+ 'Export/sql_dates',
+ 'Export/sql_relation',
+ 'Export/sql_mime',
+ ':group:end',
+ 'Export/sql_use_transaction',
+ 'Export/sql_disable_fk',
+ 'Export/sql_views_as_tables',
+ 'Export/sql_compatibility',
+ ':group:' . __('Database export options'),
+ 'Export/sql_drop_database',
+ 'Export/sql_structure_or_data',
+ ':group:end',
+ ':group:' . __('Structure'),
+ 'Export/sql_drop_table',
+ 'Export/sql_procedure_function',
+ 'Export/sql_create_table_statements' => ':group',
+ 'Export/sql_if_not_exists',
+ 'Export/sql_auto_increment',
+ ':group:end',
+ 'Export/sql_backquotes',
+ ':group:end',
+ ':group:' . __('Data'),
+ 'Export/sql_delayed',
+ 'Export/sql_ignore',
+ 'Export/sql_type',
+ 'Export/sql_insert_syntax',
+ 'Export/sql_max_query_size',
+ 'Export/sql_hex_for_blob',
+ 'Export/sql_utc_time');
+$forms['Export']['CodeGen'] = array(
+ 'Export/codegen_format');
+$forms['Export']['Csv'] = array(
+ ':group:' . __('CSV'),
+ 'Export/csv_separator',
+ 'Export/csv_enclosed',
+ 'Export/csv_escaped',
+ 'Export/csv_terminated',
+ 'Export/csv_null',
+ 'Export/csv_removeCRLF',
+ 'Export/csv_columns',
+ ':group:end',
+ ':group:' . __('CSV for MS Excel'),
+ 'Export/excel_null',
+ 'Export/excel_removeCRLF',
+ 'Export/excel_columns',
+ 'Export/excel_edition');
+$forms['Export']['Latex'] = array(
+ 'Export/latex_caption',
+ 'Export/latex_structure_or_data',
+ ':group:' . __('Structure'),
+ 'Export/latex_structure_caption',
+ 'Export/latex_structure_continued_caption',
+ 'Export/latex_structure_label',
+ 'Export/latex_relation',
+ 'Export/latex_comments',
+ 'Export/latex_mime',
+ ':group:end',
+ ':group:' . __('Data'),
+ 'Export/latex_columns',
+ 'Export/latex_data_caption',
+ 'Export/latex_data_continued_caption',
+ 'Export/latex_data_label',
+ 'Export/latex_null');
+$forms['Export']['Microsoft_Office'] = array(
+ ':group:' . __('Microsoft Word 2000'),
+ 'Export/htmlword_structure_or_data',
+ 'Export/htmlword_null',
+ 'Export/htmlword_columns');
+$forms['Export']['Open_Document'] = array(
+ ':group:' . __('OpenDocument Spreadsheet'),
+ 'Export/ods_columns',
+ 'Export/ods_null',
+ ':group:end',
+ ':group:' . __('OpenDocument Text'),
+ 'Export/odt_structure_or_data',
+ ':group:' . __('Structure'),
+ 'Export/odt_relation',
+ 'Export/odt_comments',
+ 'Export/odt_mime',
+ ':group:end',
+ ':group:' . __('Data'),
+ 'Export/odt_columns',
+ 'Export/odt_null');
+$forms['Export']['Texy'] = array(
+ 'Export/texytext_structure_or_data',
+ ':group:' . __('Data'),
+ 'Export/texytext_null',
+ 'Export/texytext_columns');
+?>
diff --git a/libraries/core.lib.php b/libraries/core.lib.php
new file mode 100644
index 0000000000..30f33c3cfa
--- /dev/null
+++ b/libraries/core.lib.php
@@ -0,0 +1,827 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Core functions used all over the scripts.
+ * This script is distinct from libraries/common.inc.php because this
+ * script is called from /test.
+ *
+ * @package PhpMyAdmin
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ * checks given $var and returns it if valid, or $default of not valid
+ * given $var is also checked for type being 'similar' as $default
+ * or against any other type if $type is provided
+ *
+ * <code>
+ * // $_REQUEST['db'] not set
+ * echo PMA_ifSetOr($_REQUEST['db'], ''); // ''
+ * // $_REQUEST['sql_query'] not set
+ * echo PMA_ifSetOr($_REQUEST['sql_query']); // null
+ * // $cfg['ForceSSL'] not set
+ * echo PMA_ifSetOr($cfg['ForceSSL'], false, 'boolean'); // false
+ * echo PMA_ifSetOr($cfg['ForceSSL']); // null
+ * // $cfg['ForceSSL'] set to 1
+ * echo PMA_ifSetOr($cfg['ForceSSL'], false, 'boolean'); // false
+ * echo PMA_ifSetOr($cfg['ForceSSL'], false, 'similar'); // 1
+ * echo PMA_ifSetOr($cfg['ForceSSL'], false); // 1
+ * // $cfg['ForceSSL'] set to true
+ * echo PMA_ifSetOr($cfg['ForceSSL'], false, 'boolean'); // true
+ * </code>
+ *
+ * @param mixed &$var param to check
+ * @param mixed $default default value
+ * @param mixed $type var type or array of values to check against $var
+ *
+ * @return mixed $var or $default
+ *
+ * @see PMA_isValid()
+ */
+function PMA_ifSetOr(&$var, $default = null, $type = 'similar')
+{
+ if (! PMA_isValid($var, $type, $default)) {
+ return $default;
+ }
+
+ return $var;
+}
+
+/**
+ * checks given $var against $type or $compare
+ *
+ * $type can be:
+ * - false : no type checking
+ * - 'scalar' : whether type of $var is integer, float, string or boolean
+ * - 'numeric' : whether type of $var is any number representation
+ * - 'length' : whether type of $var is scalar with a string length > 0
+ * - 'similar' : whether type of $var is similar to type of $compare
+ * - 'equal' : whether type of $var is identical to type of $compare
+ * - 'identical' : whether $var is identical to $compare, not only the type!
+ * - or any other valid PHP variable type
+ *
+ * <code>
+ * // $_REQUEST['doit'] = true;
+ * PMA_isValid($_REQUEST['doit'], 'identical', 'true'); // false
+ * // $_REQUEST['doit'] = 'true';
+ * PMA_isValid($_REQUEST['doit'], 'identical', 'true'); // true
+ * </code>
+ *
+ * NOTE: call-by-reference is used to not get NOTICE on undefined vars,
+ * but the var is not altered inside this function, also after checking a var
+ * this var exists nut is not set, example:
+ * <code>
+ * // $var is not set
+ * isset($var); // false
+ * functionCallByReference($var); // false
+ * isset($var); // true
+ * functionCallByReference($var); // true
+ * </code>
+ *
+ * to avoid this we set this var to null if not isset
+ *
+ * @param mixed &$var variable to check
+ * @param mixed $type var type or array of valid values to check against $var
+ * @param mixed $compare var to compare with $var
+ *
+ * @return boolean whether valid or not
+ *
+ * @todo add some more var types like hex, bin, ...?
+ * @see http://php.net/gettype
+ */
+function PMA_isValid(&$var, $type = 'length', $compare = null)
+{
+ if (! isset($var)) {
+ // var is not even set
+ return false;
+ }
+
+ if ($type === false) {
+ // no vartype requested
+ return true;
+ }
+
+ if (is_array($type)) {
+ return in_array($var, $type);
+ }
+
+ // allow some aliaes of var types
+ $type = strtolower($type);
+ switch ($type) {
+ case 'identic' :
+ $type = 'identical';
+ break;
+ case 'len' :
+ $type = 'length';
+ break;
+ case 'bool' :
+ $type = 'boolean';
+ break;
+ case 'float' :
+ $type = 'double';
+ break;
+ case 'int' :
+ $type = 'integer';
+ break;
+ case 'null' :
+ $type = 'NULL';
+ break;
+ }
+
+ if ($type === 'identical') {
+ return $var === $compare;
+ }
+
+ // whether we should check against given $compare
+ if ($type === 'similar') {
+ switch (gettype($compare)) {
+ case 'string':
+ case 'boolean':
+ $type = 'scalar';
+ break;
+ case 'integer':
+ case 'double':
+ $type = 'numeric';
+ break;
+ default:
+ $type = gettype($compare);
+ }
+ } elseif ($type === 'equal') {
+ $type = gettype($compare);
+ }
+
+ // do the check
+ if ($type === 'length' || $type === 'scalar') {
+ $is_scalar = is_scalar($var);
+ if ($is_scalar && $type === 'length') {
+ return (bool) strlen($var);
+ }
+ return $is_scalar;
+ }
+
+ if ($type === 'numeric') {
+ return is_numeric($var);
+ }
+
+ if (gettype($var) === $type) {
+ return true;
+ }
+
+ return false;
+}
+
+/**
+ * Removes insecure parts in a path; used before include() or
+ * require() when a part of the path comes from an insecure source
+ * like a cookie or form.
+ *
+ * @param string $path The path to check
+ *
+ * @return string The secured path
+ *
+ * @access public
+ */
+function PMA_securePath($path)
+{
+ // change .. to .
+ $path = preg_replace('@\.\.*@', '.', $path);
+
+ return $path;
+} // end function
+
+/**
+ * displays the given error message on phpMyAdmin error page in foreign language,
+ * ends script execution and closes session
+ *
+ * loads language file if not loaded already
+ *
+ * @param string $error_message the error message or named error message
+ * @param string|array $message_args arguments applied to $error_message
+ * @param boolean $delete_session whether to delete session cookie
+ *
+ * @return void
+ */
+function PMA_fatalError(
+ $error_message, $message_args = null, $delete_session = true
+) {
+ /* Use format string if applicable */
+ if (is_string($message_args)) {
+ $error_message = sprintf($error_message, $message_args);
+ } elseif (is_array($message_args)) {
+ $error_message = vsprintf($error_message, $message_args);
+ }
+
+ if ($GLOBALS['is_ajax_request']) {
+ $response = PMA_Response::getInstance();
+ $response->isSuccess(false);
+ $response->addJSON('message', PMA_Message::error($error_message));
+ } else {
+ $error_message = strtr($error_message, array('<br />' => '[br]'));
+
+ /* Load gettext for fatal errors */
+ if (!function_exists('__')) {
+ include_once './libraries/php-gettext/gettext.inc';
+ }
+
+ // these variables are used in the included file libraries/error.inc.php
+ $error_header = __('Error');
+ $lang = $GLOBALS['available_languages'][$GLOBALS['lang']][1];
+ $dir = $GLOBALS['text_dir'];
+
+ // on fatal errors it cannot hurt to always delete the current session
+ if ($delete_session
+ && isset($GLOBALS['session_name'])
+ && isset($_COOKIE[$GLOBALS['session_name']])
+ ) {
+ $GLOBALS['PMA_Config']->removeCookie($GLOBALS['session_name']);
+ }
+
+ // Displays the error message
+ include './libraries/error.inc.php';
+ }
+ if (! defined('TESTSUITE')) {
+ exit;
+ }
+}
+
+/**
+ * Returns a link to the PHP documentation
+ *
+ * @param string $target anchor in documentation
+ *
+ * @return string the URL
+ *
+ * @access public
+ */
+function PMA_getPHPDocLink($target)
+{
+ /* List of PHP documentation translations */
+ $php_doc_languages = array(
+ 'pt_BR', 'zh', 'fr', 'de', 'it', 'ja', 'pl', 'ro', 'ru', 'fa', 'es', 'tr'
+ );
+
+ $lang = 'en';
+ if (in_array($GLOBALS['lang'], $php_doc_languages)) {
+ $lang = $GLOBALS['lang'];
+ }
+
+ return PMA_linkURL('http://php.net/manual/' . $lang . '/' . $target);
+}
+
+/**
+ * Warn or fail on missing extension.
+ *
+ * @param string $extension Extension name
+ * @param bool $fatal Whether the error is fatal.
+ * @param string $extra Extra string to append to messsage.
+ *
+ * @return void
+ */
+function PMA_warnMissingExtension($extension, $fatal = false, $extra = '')
+{
+ /* Gettext does not have to be loaded yet here */
+ if (function_exists('__')) {
+ $message = __(
+ 'The %s extension is missing. Please check your PHP configuration.'
+ );
+ } else {
+ $message
+ = 'The %s extension is missing. Please check your PHP configuration.';
+ }
+ $doclink = PMA_getPHPDocLink('book.' . $extension . '.php');
+ $message = sprintf(
+ $message,
+ '[a@' . $doclink . '@Documentation][em]' . $extension . '[/em][/a]'
+ );
+ if ($extra != '') {
+ $message .= ' ' . $extra;
+ }
+ if ($fatal) {
+ PMA_fatalError($message);
+ } else {
+ $GLOBALS['error_handler']->addError(
+ $message,
+ E_USER_WARNING,
+ '',
+ '',
+ false
+ );
+ }
+}
+
+/**
+ * returns count of tables in given db
+ *
+ * @param string $db database to count tables for
+ *
+ * @return integer count of tables in $db
+ */
+function PMA_getTableCount($db)
+{
+ $tables = $GLOBALS['dbi']->tryQuery(
+ 'SHOW TABLES FROM ' . PMA_Util::backquote($db) . ';',
+ null, PMA_DatabaseInterface::QUERY_STORE
+ );
+ if ($tables) {
+ $num_tables = $GLOBALS['dbi']->numRows($tables);
+ $GLOBALS['dbi']->freeResult($tables);
+ } else {
+ $num_tables = 0;
+ }
+
+ return $num_tables;
+}
+
+/**
+ * Converts numbers like 10M into bytes
+ * Used with permission from Moodle (http://moodle.org) by Martin Dougiamas
+ * (renamed with PMA prefix to avoid double definition when embedded
+ * in Moodle)
+ *
+ * @param string $size size
+ *
+ * @return integer $size
+ */
+function PMA_getRealSize($size = 0)
+{
+ if (! $size) {
+ return 0;
+ }
+
+ $scan['gb'] = 1073741824; //1024 * 1024 * 1024;
+ $scan['g'] = 1073741824; //1024 * 1024 * 1024;
+ $scan['mb'] = 1048576;
+ $scan['m'] = 1048576;
+ $scan['kb'] = 1024;
+ $scan['k'] = 1024;
+ $scan['b'] = 1;
+
+ foreach ($scan as $unit => $factor) {
+ if (strlen($size) > strlen($unit)
+ && strtolower(substr($size, strlen($size) - strlen($unit))) == $unit
+ ) {
+ return substr($size, 0, strlen($size) - strlen($unit)) * $factor;
+ }
+ }
+
+ return $size;
+} // end function PMA_getRealSize()
+
+/**
+ * merges array recursive like array_merge_recursive() but keyed-values are
+ * always overwritten.
+ *
+ * array PMA_arrayMergeRecursive(array $array1[, array $array2[, array ...]])
+ *
+ * @return array merged array
+ *
+ * @see http://php.net/array_merge
+ * @see http://php.net/array_merge_recursive
+ */
+function PMA_arrayMergeRecursive()
+{
+ switch(func_num_args()) {
+ case 0 :
+ return false;
+ break;
+ case 1 :
+ // when does that happen?
+ return func_get_arg(0);
+ break;
+ case 2 :
+ $args = func_get_args();
+ if (! is_array($args[0]) || ! is_array($args[1])) {
+ return $args[1];
+ }
+ foreach ($args[1] as $key2 => $value2) {
+ if (isset($args[0][$key2]) && !is_int($key2)) {
+ $args[0][$key2] = PMA_arrayMergeRecursive(
+ $args[0][$key2], $value2
+ );
+ } else {
+ // we erase the parent array, otherwise we cannot override
+ // a directive that contains array elements, like this:
+ // (in config.default.php)
+ // $cfg['ForeignKeyDropdownOrder']= array('id-content','content-id');
+ // (in config.inc.php)
+ // $cfg['ForeignKeyDropdownOrder']= array('content-id');
+ if (is_int($key2) && $key2 == 0) {
+ unset($args[0]);
+ }
+ $args[0][$key2] = $value2;
+ }
+ }
+ return $args[0];
+ break;
+ default :
+ $args = func_get_args();
+ $args[1] = PMA_arrayMergeRecursive($args[0], $args[1]);
+ array_shift($args);
+ return call_user_func_array('PMA_arrayMergeRecursive', $args);
+ break;
+ }
+}
+
+/**
+ * calls $function for every element in $array recursively
+ *
+ * this function is protected against deep recursion attack CVE-2006-1549,
+ * 1000 seems to be more than enough
+ *
+ * @param array &$array array to walk
+ * @param string $function function to call for every array element
+ * @param bool $apply_to_keys_also whether to call the function for the keys also
+ *
+ * @return void
+ *
+ * @see http://www.php-security.org/MOPB/MOPB-02-2007.html
+ * @see http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2006-1549
+ */
+function PMA_arrayWalkRecursive(&$array, $function, $apply_to_keys_also = false)
+{
+ static $recursive_counter = 0;
+ if (++$recursive_counter > 1000) {
+ PMA_fatalError(__('possible deep recursion attack'));
+ }
+ foreach ($array as $key => $value) {
+ if (is_array($value)) {
+ PMA_arrayWalkRecursive($array[$key], $function, $apply_to_keys_also);
+ } else {
+ $array[$key] = $function($value);
+ }
+
+ if ($apply_to_keys_also && is_string($key)) {
+ $new_key = $function($key);
+ if ($new_key != $key) {
+ $array[$new_key] = $array[$key];
+ unset($array[$key]);
+ }
+ }
+ }
+ $recursive_counter--;
+}
+
+/**
+ * boolean phpMyAdmin.PMA_checkPageValidity(string &$page, array $whitelist)
+ *
+ * checks given $page against given $whitelist and returns true if valid
+ * it optionally ignores query parameters in $page (script.php?ignored)
+ *
+ * @param string &$page page to check
+ * @param array $whitelist whitelist to check page against
+ *
+ * @return boolean whether $page is valid or not (in $whitelist or not)
+ */
+function PMA_checkPageValidity(&$page, $whitelist)
+{
+ if (! isset($page) || !is_string($page)) {
+ return false;
+ }
+
+ if (in_array($page, $whitelist)) {
+ return true;
+ } elseif (in_array(substr($page, 0, strpos($page . '?', '?')), $whitelist)) {
+ return true;
+ } else {
+ $_page = urldecode($page);
+ if (in_array(substr($_page, 0, strpos($_page . '?', '?')), $whitelist)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+/**
+ * tries to find the value for the given environment variable name
+ *
+ * searches in $_SERVER, $_ENV then tries getenv() and apache_getenv()
+ * in this order
+ *
+ * @param string $var_name variable name
+ *
+ * @return string value of $var or empty string
+ */
+function PMA_getenv($var_name)
+{
+ if (isset($_SERVER[$var_name])) {
+ return $_SERVER[$var_name];
+ } elseif (isset($_ENV[$var_name])) {
+ return $_ENV[$var_name];
+ } elseif (getenv($var_name)) {
+ return getenv($var_name);
+ } elseif (function_exists('apache_getenv')
+ && apache_getenv($var_name, true)
+ ) {
+ return apache_getenv($var_name, true);
+ }
+
+ return '';
+}
+
+/**
+ * Send HTTP header, taking IIS limits into account (600 seems ok)
+ *
+ * @param string $uri the header to send
+ * @param bool $use_refresh whether to use Refresh: header when running on IIS
+ *
+ * @return boolean always true
+ */
+function PMA_sendHeaderLocation($uri, $use_refresh = false)
+{
+ if (PMA_IS_IIS && strlen($uri) > 600) {
+ include_once './libraries/js_escape.lib.php';
+ PMA_Response::getInstance()->disable();
+
+ echo '<html><head><title>- - -</title>' . "\n";
+ echo '<meta http-equiv="expires" content="0">' . "\n";
+ echo '<meta http-equiv="Pragma" content="no-cache">' . "\n";
+ echo '<meta http-equiv="Cache-Control" content="no-cache">' . "\n";
+ echo '<meta http-equiv="Refresh" content="0;url='
+ . htmlspecialchars($uri) . '">' . "\n";
+ echo '<script type="text/javascript">' . "\n";
+ echo '//<![CDATA[' . "\n";
+ echo 'setTimeout("window.location = unescape(\'"'
+ . PMA_escapeJsString($uri) . '"\')", 2000);' . "\n";
+ echo '//]]>' . "\n";
+ echo '</script>' . "\n";
+ echo '</head>' . "\n";
+ echo '<body>' . "\n";
+ echo '<script type="text/javascript">' . "\n";
+ echo '//<![CDATA[' . "\n";
+ echo 'document.write(\'<p><a href="' . htmlspecialchars($uri) . '">'
+ . __('Go') . '</a></p>\');' . "\n";
+ echo '//]]>' . "\n";
+ echo '</script></body></html>' . "\n";
+
+ } else {
+ if (SID) {
+ if (strpos($uri, '?') === false) {
+ header('Location: ' . $uri . '?' . SID);
+ } else {
+ $separator = PMA_URL_getArgSeparator();
+ header('Location: ' . $uri . $separator . SID);
+ }
+ } else {
+ session_write_close();
+ if (headers_sent()) {
+ if (function_exists('debug_print_backtrace')) {
+ echo '<pre>';
+ debug_print_backtrace();
+ echo '</pre>';
+ }
+ trigger_error(
+ 'PMA_sendHeaderLocation called when headers are already sent!',
+ E_USER_ERROR
+ );
+ }
+ // bug #1523784: IE6 does not like 'Refresh: 0', it
+ // results in a blank page
+ // but we need it when coming from the cookie login panel)
+ if (PMA_IS_IIS && $use_refresh) {
+ header('Refresh: 0; ' . $uri);
+ } else {
+ header('Location: ' . $uri);
+ }
+ }
+ }
+}
+
+/**
+ * Outputs headers to prevent caching in browser (and on the way).
+ *
+ * @return void
+ */
+function PMA_noCacheHeader()
+{
+ if (defined('TESTSUITE')) {
+ return;
+ }
+ // rfc2616 - Section 14.21
+ header('Expires: ' . date(DATE_RFC1123));
+ // HTTP/1.1
+ header(
+ 'Cache-Control: no-store, no-cache, must-revalidate,'
+ . ' pre-check=0, post-check=0, max-age=0'
+ );
+ if (PMA_USR_BROWSER_AGENT == 'IE') {
+ /* On SSL IE sometimes fails with:
+ *
+ * Internet Explorer was not able to open this Internet site. The
+ * requested site is either unavailable or cannot be found. Please
+ * try again later.
+ *
+ * Adding Pragma: public fixes this.
+ */
+ header('Pragma: public');
+ } else {
+ header('Pragma: no-cache'); // HTTP/1.0
+ // test case: exporting a database into a .gz file with Safari
+ // would produce files not having the current time
+ // (added this header for Safari but should not harm other browsers)
+ header('Last-Modified: ' . date(DATE_RFC1123));
+ }
+}
+
+
+/**
+ * Sends header indicating file download.
+ *
+ * @param string $filename Filename to include in headers if empty,
+ * none Content-Disposition header will be sent.
+ * @param string $mimetype MIME type to include in headers.
+ * @param int $length Length of content (optional)
+ * @param bool $no_cache Whether to include no-caching headers.
+ *
+ * @return void
+ */
+function PMA_downloadHeader($filename, $mimetype, $length = 0, $no_cache = true)
+{
+ if ($no_cache) {
+ PMA_noCacheHeader();
+ }
+ /* Replace all possibly dangerous chars in filename */
+ $filename = str_replace(array(';', '"', "\n", "\r"), '-', $filename);
+ if (!empty($filename)) {
+ header('Content-Description: File Transfer');
+ header('Content-Disposition: attachment; filename="' . $filename . '"');
+ }
+ header('Content-Type: ' . $mimetype);
+ // inform the server that compression has been done,
+ // to avoid a double compression (for example with Apache + mod_deflate)
+ if (strpos($mimetype, 'gzip') !== false) {
+ header('Content-Encoding: gzip');
+ }
+ header('Content-Transfer-Encoding: binary');
+ if ($length > 0) {
+ header('Content-Length: ' . $length);
+ }
+}
+
+/**
+ * Checks whether element given by $path exists in $array.
+ * $path is a string describing position of an element in an associative array,
+ * eg. Servers/1/host refers to $array[Servers][1][host]
+ *
+ * @param string $path path in the arry
+ * @param array $array the array
+ *
+ * @return mixed array element or $default
+ */
+function PMA_arrayKeyExists($path, $array)
+{
+ $keys = explode('/', $path);
+ $value =& $array;
+ foreach ($keys as $key) {
+ if (! isset($value[$key])) {
+ return false;
+ }
+ $value =& $value[$key];
+ }
+ return true;
+}
+
+/**
+ * Returns value of an element in $array given by $path.
+ * $path is a string describing position of an element in an associative array,
+ * eg. Servers/1/host refers to $array[Servers][1][host]
+ *
+ * @param string $path path in the arry
+ * @param array $array the array
+ * @param mixed $default default value
+ *
+ * @return mixed array element or $default
+ */
+function PMA_arrayRead($path, $array, $default = null)
+{
+ $keys = explode('/', $path);
+ $value =& $array;
+ foreach ($keys as $key) {
+ if (! isset($value[$key])) {
+ return $default;
+ }
+ $value =& $value[$key];
+ }
+ return $value;
+}
+
+/**
+ * Stores value in an array
+ *
+ * @param string $path path in the array
+ * @param array &$array the array
+ * @param mixed $value value to store
+ *
+ * @return void
+ */
+function PMA_arrayWrite($path, &$array, $value)
+{
+ $keys = explode('/', $path);
+ $last_key = array_pop($keys);
+ $a =& $array;
+ foreach ($keys as $key) {
+ if (! isset($a[$key])) {
+ $a[$key] = array();
+ }
+ $a =& $a[$key];
+ }
+ $a[$last_key] = $value;
+}
+
+/**
+ * Removes value from an array
+ *
+ * @param string $path path in the array
+ * @param array &$array the array
+ *
+ * @return void
+ */
+function PMA_arrayRemove($path, &$array)
+{
+ $keys = explode('/', $path);
+ $keys_last = array_pop($keys);
+ $path = array();
+ $depth = 0;
+
+ $path[0] =& $array;
+ $found = true;
+ // go as deep as required or possible
+ foreach ($keys as $key) {
+ if (! isset($path[$depth][$key])) {
+ $found = false;
+ break;
+ }
+ $depth++;
+ $path[$depth] =& $path[$depth-1][$key];
+ }
+ // if element found, remove it
+ if ($found) {
+ unset($path[$depth][$keys_last]);
+ $depth--;
+ }
+
+ // remove empty nested arrays
+ for (; $depth >= 0; $depth--) {
+ if (! isset($path[$depth+1]) || count($path[$depth+1]) == 0) {
+ unset($path[$depth][$keys[$depth]]);
+ } else {
+ break;
+ }
+ }
+}
+
+/**
+ * Returns link to (possibly) external site using defined redirector.
+ *
+ * @param string $url URL where to go.
+ *
+ * @return string URL for a link.
+ */
+function PMA_linkURL($url)
+{
+ if (!preg_match('#^https?://#', $url) || defined('PMA_SETUP')) {
+ return $url;
+ } else {
+ if (!function_exists('PMA_URL_getCommon')) {
+ include_once './libraries/url_generating.lib.php';
+ }
+ $params = array();
+ $params['url'] = $url;
+ return './url.php' . PMA_URL_getCommon($params);
+ }
+}
+
+/**
+ * Adds JS code snippets to be displayed by the PMA_Response class.
+ * Adds a newline to each snippet.
+ *
+ * @param string $str Js code to be added (e.g. "token=1234;")
+ *
+ * @return void
+ */
+function PMA_addJSCode($str)
+{
+ $response = PMA_Response::getInstance();
+ $header = $response->getHeader();
+ $scripts = $header->getScripts();
+ $scripts->addCode($str);
+}
+
+/**
+ * Adds JS code snippet for variable assignment
+ * to be displayed by the PMA_Response class.
+ *
+ * @param string $key Name of value to set
+ * @param mixed $value Value to set, can be either string or array of strings
+ * @param bool $escape Whether to escape value or keep it as it is
+ * (for inclusion of js code)
+ *
+ * @return void
+ */
+function PMA_addJSVar($key, $value, $escape = true)
+{
+ PMA_addJSCode(PMA_getJsValue($key, $value, $escape));
+}
+
+?>
diff --git a/libraries/create_addfield.lib.php b/libraries/create_addfield.lib.php
new file mode 100644
index 0000000000..1323b7ae81
--- /dev/null
+++ b/libraries/create_addfield.lib.php
@@ -0,0 +1,325 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * set of functions for tbl_create.php and tbl_addfield.php
+ *
+ * @package PhpMyAdmin
+ */
+
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ * Transforms the radio button field_key into 4 arrays
+ *
+ * @return array An array of arrays which represents column keys for each index type
+ */
+function PMA_getIndexedColumns()
+{
+ $field_cnt = count($_REQUEST['field_name']);
+ $field_primary = array();
+ $field_index = array();
+ $field_unique = array();
+ $field_fulltext = array();
+ for ($i = 0; $i < $field_cnt; ++$i) {
+ if (isset($_REQUEST['field_key'][$i])
+ && strlen($_REQUEST['field_name'][$i])
+ ) {
+ if ($_REQUEST['field_key'][$i] == 'primary_' . $i) {
+ $field_primary[] = $i;
+ }
+ if ($_REQUEST['field_key'][$i] == 'index_' . $i) {
+ $field_index[] = $i;
+ }
+ if ($_REQUEST['field_key'][$i] == 'unique_' . $i) {
+ $field_unique[] = $i;
+ }
+ if ($_REQUEST['field_key'][$i] == 'fulltext_' . $i) {
+ $field_fulltext[] = $i;
+ }
+ } // end if
+ } // end for
+
+ return array(
+ $field_cnt, $field_primary, $field_index, $field_unique,
+ $field_fulltext
+ );
+}
+
+/**
+ * Initiate the column creation statement according to the table creation or
+ * add columns to a existing table
+ *
+ * @param int $field_cnt number of columns
+ * @param int &$field_primary primary index field
+ * @param boolean $is_create_tbl true if requirement is to get the statement
+ * for table creation
+ *
+ * @return array $definitions An array of initial sql statements
+ * according to the request
+ */
+function PMA_buildColumnCreationStatement(
+ $field_cnt, &$field_primary, $is_create_tbl = true
+) {
+ $definitions = array();
+ for ($i = 0; $i < $field_cnt; ++$i) {
+ // '0' is also empty for php :-(
+ if (empty($_REQUEST['field_name'][$i])
+ && $_REQUEST['field_name'][$i] != '0'
+ ) {
+ continue;
+ }
+
+ $definition = PMA_getStatementPrefix($is_create_tbl) .
+ PMA_Table::generateFieldSpec(
+ $_REQUEST['field_name'][$i],
+ $_REQUEST['field_type'][$i],
+ $i,
+ $_REQUEST['field_length'][$i],
+ $_REQUEST['field_attribute'][$i],
+ isset($_REQUEST['field_collation'][$i])
+ ? $_REQUEST['field_collation'][$i]
+ : '',
+ isset($_REQUEST['field_null'][$i])
+ ? $_REQUEST['field_null'][$i]
+ : 'NOT NULL',
+ $_REQUEST['field_default_type'][$i],
+ $_REQUEST['field_default_value'][$i],
+ isset($_REQUEST['field_extra'][$i])
+ ? $_REQUEST['field_extra'][$i]
+ : false,
+ isset($_REQUEST['field_comments'][$i])
+ ? $_REQUEST['field_comments'][$i]
+ : '',
+ $field_primary
+ );
+
+
+ $definition .= PMA_setColumnCreationStatementSuffix($i, $is_create_tbl);
+ $definitions[] = $definition;
+ } // end for
+
+ return $definitions;
+}
+
+/**
+ * Set column creation suffix according to requested position of the new column
+ *
+ * @param int $current_field_num current column number
+ * @param boolean $is_create_tbl true if requirement is to get the statement
+ * for table creation
+ *
+ * @return string $sql_suffix suffix
+ */
+function PMA_setColumnCreationStatementSuffix($current_field_num,
+ $is_create_tbl = true
+) {
+ // no suffix is needed if request is a table creation
+ $sql_suffix = " ";
+ if (! $is_create_tbl) {
+ if ($_REQUEST['field_where'] != 'last') {
+ // Only the first field can be added somewhere other than at the end
+ if ($current_field_num == 0) {
+ if ($_REQUEST['field_where'] == 'first') {
+ $sql_suffix .= ' FIRST';
+ } else {
+ $sql_suffix .= ' AFTER '
+ . PMA_Util::backquote($_REQUEST['after_field']);
+ }
+ } else {
+ $sql_suffix .= ' AFTER '
+ . PMA_Util::backquote(
+ $_REQUEST['field_name'][$current_field_num - 1]
+ );
+ }
+ }
+ }
+ return $sql_suffix;
+}
+
+/**
+ * Create relevant index statements
+ *
+ * @param array $indexed_fields an array of index columns
+ * @param string $index_type index type that which represents
+ * the index type of $indexed_fields
+ * @param boolean $is_create_tbl true if requirement is to get the statement
+ * for table creation
+ *
+ * @return array an array of sql statements for indexes
+ */
+function PMA_buildIndexStatements($indexed_fields, $index_type,
+ $is_create_tbl = true
+) {
+ $statement = array();
+ if (count($indexed_fields)) {
+ $fields = array();
+ foreach ($indexed_fields as $field_nr) {
+ $fields[] = PMA_Util::backquote($_REQUEST['field_name'][$field_nr]);
+ }
+ $statement[] = PMA_getStatementPrefix($is_create_tbl)
+ .' '.$index_type.' (' . implode(', ', $fields) . ') ';
+ unset($fields);
+ }
+
+ return $statement;
+}
+
+/**
+ * Statement prefix for the PMA_buildColumnCreationStatement()
+ *
+ * @param boolean $is_create_tbl true if requirement is to get the statement
+ * for table creation
+ *
+ * @return string $sql_prefix prefix
+ */
+function PMA_getStatementPrefix($is_create_tbl = true)
+{
+ $sql_prefix = " ";
+ if (! $is_create_tbl) {
+ $sql_prefix = ' ADD ';
+ }
+ return $sql_prefix;
+}
+
+/**
+ * Returns sql statement according to the column and index specifications as
+ * requested
+ *
+ * @param boolean $is_create_tbl true if requirement is to get the statement
+ * for table creation
+ *
+ * @return string sql statement
+ */
+function PMA_getColumnCreationStatements($is_create_tbl = true)
+{
+ $definitions = array();
+ $sql_statement = "";
+ list($field_cnt, $field_primary, $field_index,
+ $field_unique, $field_fulltext
+ ) = PMA_getIndexedColumns();
+ $definitions = PMA_buildColumnCreationStatement(
+ $field_cnt, $field_primary, $is_create_tbl
+ );
+
+ // Builds the primary keys statements
+ $primary_key_statements = PMA_buildIndexStatements(
+ $field_primary, " PRIMARY KEY ", $is_create_tbl
+ );
+ $definitions = array_merge($definitions, $primary_key_statements);
+
+ // Builds the indexes statements
+ $index_statements = PMA_buildIndexStatements(
+ $field_index, " INDEX ", $is_create_tbl
+ );
+ $definitions = array_merge($definitions, $index_statements);
+
+ // Builds the uniques statements
+ $unique_statements = PMA_buildIndexStatements(
+ $field_unique, " UNIQUE ", $is_create_tbl
+ );
+ $definitions = array_merge($definitions, $unique_statements);
+
+ // Builds the fulltext statements
+ $fulltext_statements = PMA_buildIndexStatements(
+ $field_fulltext, " FULLTEXT ", $is_create_tbl
+ );
+ $definitions = array_merge($definitions, $fulltext_statements);
+
+ if (count($definitions)) {
+ $sql_statement = implode(', ', $definitions);
+ }
+ $sql_statement = preg_replace('@, $@', '', $sql_statement);
+
+ return $sql_statement;
+
+}
+
+/**
+ * Function to get table creation sql query
+ *
+ * @param string $db database name
+ * @param string $table table name
+ *
+ * @return string
+ */
+function PMA_getTableCreationQuery($db, $table)
+{
+ // get column addition statements
+ $sql_statement = PMA_getColumnCreationStatements(true);
+
+ // Builds the 'create table' statement
+ $sql_query = 'CREATE TABLE ' . PMA_Util::backquote($db) . '.'
+ . PMA_Util::backquote($table) . ' (' . $sql_statement . ')';
+
+ // Adds table type, character set, comments and partition definition
+ if (!empty($_REQUEST['tbl_storage_engine'])
+ && ($_REQUEST['tbl_storage_engine'] != 'Default')
+ ) {
+ $sql_query .= ' ENGINE = ' . $_REQUEST['tbl_storage_engine'];
+ }
+ if (!empty($_REQUEST['tbl_collation'])) {
+ $sql_query .= PMA_generateCharsetQueryPart($_REQUEST['tbl_collation']);
+ }
+ if (!empty($_REQUEST['comment'])) {
+ $sql_query .= ' COMMENT = \''
+ . PMA_Util::sqlAddSlashes($_REQUEST['comment']) . '\'';
+ }
+ if (!empty($_REQUEST['partition_definition'])) {
+ $sql_query .= ' ' . PMA_Util::sqlAddSlashes(
+ $_REQUEST['partition_definition']
+ );
+ }
+ $sql_query .= ';';
+
+ return $sql_query;
+}
+
+/**
+ * Function to get the number of fields for the table creation form
+ *
+ * @return int
+ */
+function PMA_getNumberOfFieldsFromRequest()
+{
+ if (isset($_REQUEST['submit_num_fields'])) {
+ $num_fields = $_REQUEST['orig_num_fields'] + $_REQUEST['added_fields'];
+ } elseif (isset($_REQUEST['num_fields'])
+ && intval($_REQUEST['num_fields']) > 0
+ ) {
+ $num_fields = (int) $_REQUEST['num_fields'];
+ } else {
+ $num_fields = 4;
+ }
+
+ return $num_fields;
+}
+
+/**
+ * Function to execute the column creation statement
+ *
+ * @param string $db current database
+ * @param string $table current table
+ * @param string $err_url error page url
+ *
+ * @return array
+ */
+function PMA_tryColumnCreationQuery($db, $table, $err_url)
+{
+ // get column addition statements
+ $sql_statement = PMA_getColumnCreationStatements(false);
+
+ // To allow replication, we first select the db to use and then run queries
+ // on this db.
+ $GLOBALS['dbi']->selectDb($db)
+ or PMA_Util::mysqlDie(
+ $GLOBALS['dbi']->getError(),
+ 'USE ' . PMA_Util::backquote($db), '',
+ $err_url
+ );
+ $sql_query = 'ALTER TABLE ' .
+ PMA_Util::backquote($table) . ' ' . $sql_statement . ';';
+ return array($GLOBALS['dbi']->tryQuery($sql_query) , $sql_query);
+}
+?>
diff --git a/libraries/data_dictionary_relations.lib.php b/libraries/data_dictionary_relations.lib.php
new file mode 100644
index 0000000000..99a0526da3
--- /dev/null
+++ b/libraries/data_dictionary_relations.lib.php
@@ -0,0 +1,167 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Definition of internal relations for data dictionary tables.
+ *
+ * @package PhpMyAdmin
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ *
+ */
+$GLOBALS['data_dictionary_relations'] = array(
+ 'CHARACTER_SETS' => array(
+ 'DEFAULT_COLLATE_NAME' => array(
+ 'foreign_db' => 'data_dictionary',
+ 'foreign_table' => 'COLLATIONS',
+ 'foreign_field' => 'COLLATION_NAME'
+ )
+ ),
+ 'COLLATIONS' => array(
+ 'CHARACTER_SET_NAME' => array(
+ 'foreign_db' => 'data_dictionary',
+ 'foreign_table' => 'CHARACTER_SETS',
+ 'foreign_field' => 'CHARACTER_SET_NAME'
+ )
+ ),
+ 'COLUMNS' => array(
+ 'TABLE_SCHEMA' => array(
+ 'foreign_db' => 'data_dictionary',
+ 'foreign_table' => 'SCHEMAS',
+ 'foreign_field' => 'SCHEMA_NAME'
+ ),
+ 'COLLATION_NAME' => array(
+ 'foreign_db' => 'data_dictionary',
+ 'foreign_table' => 'COLLATIONS',
+ 'foreign_field' => 'COLLATION_NAME'
+ )
+ ),
+ 'INDEXES' => array(
+ 'TABLE_SCHEMA' => array(
+ 'foreign_db' => 'data_dictionary',
+ 'foreign_table' => 'SCHEMAS',
+ 'foreign_field' => 'SCHEMA_NAME'
+ )
+ ),
+ 'INDEX_PARTS' => array(
+ 'TABLE_SCHEMA' => array(
+ 'foreign_db' => 'data_dictionary',
+ 'foreign_table' => 'SCHEMAS',
+ 'foreign_field' => 'SCHEMA_NAME'
+ )
+ ),
+ 'INNODB_LOCKS' => array(
+ 'LOCK_TRX_ID' => array(
+ 'foreign_db' => 'data_dictionary',
+ 'foreign_table' => 'INNODB_TRX',
+ 'foreign_field' => 'TRX_ID'
+ )
+ ),
+ 'INNODB_LOCK_WAITS' => array(
+ 'REQUESTING_TRX_ID' => array(
+ 'foreign_db' => 'data_dictionary',
+ 'foreign_table' => 'INNODB_TRX',
+ 'foreign_field' => 'TRX_ID'
+ ),
+ 'REQUESTED_LOCK_ID' => array(
+ 'foreign_db' => 'data_dictionary',
+ 'foreign_table' => 'INNODB_LOCKS',
+ 'foreign_field' => 'LOCK_ID'
+ ),
+ 'BLOCKING_TRX_ID' => array(
+ 'foreign_db' => 'data_dictionary',
+ 'foreign_table' => 'INNODB_TRX',
+ 'foreign_field' => 'TRX_ID'
+ ),
+ 'BLOCKING_LOCK_ID' => array(
+ 'foreign_db' => 'data_dictionary',
+ 'foreign_table' => 'INNODB_LOCKS',
+ 'foreign_field' => 'LOCK_ID'
+ )
+ ),
+ 'INNODB_SYS_COLUMNS' => array(
+ 'TABLE_ID' => array(
+ 'foreign_db' => 'data_dictionary',
+ 'foreign_table' => 'INNODB_SYS_TABLES',
+ 'foreign_field' => 'TABLE_ID'
+ )
+ ),
+ 'INNODB_SYS_FIELDS' => array(
+ 'INDEX_ID' => array(
+ 'foreign_db' => 'data_dictionary',
+ 'foreign_table' => 'INNODB_SYS_INDEXES',
+ 'foreign_field' => 'INDEX_ID'
+ )
+ ),
+ 'INNODB_SYS_INDEXES' => array(
+ 'TABLE_ID' => array(
+ 'foreign_db' => 'data_dictionary',
+ 'foreign_table' => 'INNODB_SYS_TABLES',
+ 'foreign_field' => 'TABLE_ID'
+ )
+ ),
+ 'INNODB_SYS_TABLESTATS' => array(
+ 'TABLE_ID' => array(
+ 'foreign_db' => 'data_dictionary',
+ 'foreign_table' => 'INNODB_SYS_TABLES',
+ 'foreign_field' => 'TABLE_ID'
+ )
+ ),
+ 'PLUGINS' => array(
+ 'MODULE_NAME' => array(
+ 'foreign_db' => 'data_dictionary',
+ 'foreign_table' => 'MODULES',
+ 'foreign_field' => 'MODULE_NAME'
+ )
+ ),
+ 'SCHEMAS' => array(
+ 'DEFAULT_COLLATION_NAME' => array(
+ 'foreign_db' => 'data_dictionary',
+ 'foreign_table' => 'COLLATIONS',
+ 'foreign_field' => 'COLLATION_NAME'
+ )
+ ),
+ 'TABLES' => array(
+ 'TABLE_SCHEMA' => array(
+ 'foreign_db' => 'data_dictionary',
+ 'foreign_table' => 'SCHEMAS',
+ 'foreign_field' => 'SCHEMA_NAME'
+ ),
+ 'TABLE_COLLATION' => array(
+ 'foreign_db' => 'data_dictionary',
+ 'foreign_table' => 'COLLATIONS',
+ 'foreign_field' => 'COLLATION_NAME'
+ )
+ ),
+ 'TABLE_CACHE' => array(
+ 'TABLE_SCHEMA' => array(
+ 'foreign_db' => 'data_dictionary',
+ 'foreign_table' => 'SCHEMAS',
+ 'foreign_field' => 'SCHEMA_NAME'
+ )
+ ),
+ 'TABLE_CONSTRAINTS' => array(
+ 'CONSTRAINT_SCHEMA' => array(
+ 'foreign_db' => 'data_dictionary',
+ 'foreign_table' => 'SCHEMAS',
+ 'foreign_field' => 'SCHEMA_NAME'
+ ),
+ 'TABLE_SCHEMA' => array(
+ 'foreign_db' => 'data_dictionary',
+ 'foreign_table' => 'SCHEMAS',
+ 'foreign_field' => 'SCHEMA_NAME'
+ )
+ ),
+ 'TABLE_DEFINITION_CACHE' => array(
+ 'TABLE_SCHEMA' => array(
+ 'foreign_db' => 'data_dictionary',
+ 'foreign_table' => 'SCHEMAS',
+ 'foreign_field' => 'SCHEMA_NAME'
+ )
+ )
+);
+
+?>
diff --git a/libraries/database_interface.inc.php b/libraries/database_interface.inc.php
new file mode 100644
index 0000000000..2ad1706a25
--- /dev/null
+++ b/libraries/database_interface.inc.php
@@ -0,0 +1,87 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Creates the database interface required for database interctions
+ * and add it to GLOBALS.
+ *
+ * @package PhpMyAdmin-DBI
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+require_once './libraries/DatabaseInterface.class.php';
+
+$extension = null;
+if (defined('TESTSUITE')) {
+ /**
+ * For testsuite we use dummy driver which can fake some queries.
+ */
+ include_once './libraries/dbi/DBIDummy.class.php';
+ $extension = new PMA_DBI_Dummy();
+} else {
+
+ /**
+ * check for requested extension
+ */
+ $extensionName = $GLOBALS['cfg']['Server']['extension'];
+ if (! PMA_DatabaseInterface::checkDbExtension($extensionName)) {
+
+ // if it fails try alternative extension ...
+ // and display an error ...
+ $docurl = PMA_Util::getDocuLink('faq', 'faqmysql');
+ $doclink = sprintf(
+ __('See %sour documentation%s for more information.'),
+ '[a@' . $docurl . '@documentation]',
+ '[/a]'
+ );
+
+ /**
+ * @todo add different messages for alternative extension
+ * and complete fail (no alternative extension too)
+ */
+ PMA_warnMissingExtension(
+ $extensionName,
+ false,
+ $doclink
+ );
+
+ if ($extensionName === 'mysql') {
+ $alternativ_extension = 'mysqli';
+ } else {
+ $alternativ_extension = 'mysql';
+ }
+
+ if (! PMA_DatabaseInterface::checkDbExtension($alternativ_extension)) {
+ // if alternative fails too ...
+ PMA_warnMissingExtension(
+ $extensionName,
+ true,
+ $doclink
+ );
+ }
+
+ $GLOBALS['cfg']['Server']['extension'] = $alternativ_extension;
+ unset($alternativ_extension);
+ }
+
+ /**
+ * Including The DBI Plugin
+ */
+ switch($GLOBALS['cfg']['Server']['extension']) {
+ case 'mysql' :
+ include_once './libraries/dbi/DBIMysql.class.php';
+ $extension = new PMA_DBI_Mysql();
+ break;
+ case 'mysqli' :
+ include_once './libraries/dbi/DBIMysqli.class.php';
+ $extension = new PMA_DBI_Mysqli();
+ break;
+ case 'drizzle' :
+ include_once './libraries/dbi/DBIDrizzle.class.php';
+ $extension = new PMA_DBI_Drizzle();
+ break;
+ }
+}
+$GLOBALS['dbi'] = new PMA_DatabaseInterface($extension);
+?>
diff --git a/libraries/db_common.inc.php b/libraries/db_common.inc.php
new file mode 100644
index 0000000000..a16a700890
--- /dev/null
+++ b/libraries/db_common.inc.php
@@ -0,0 +1,100 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Common includes for the database level views
+ *
+ * @package PhpMyAdmin
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ * Gets some core libraries
+ */
+require_once './libraries/bookmark.lib.php';
+
+PMA_Util::checkParameters(array('db'));
+
+$is_show_stats = $cfg['ShowStats'];
+
+$db_is_information_schema = $GLOBALS['dbi']->isSystemSchema($db);
+if ($db_is_information_schema) {
+ $is_show_stats = false;
+}
+
+/**
+ * Defines the urls to return to in case of error in a sql statement
+ */
+$err_url_0 = 'index.php?' . PMA_URL_getCommon();
+$err_url = $cfg['DefaultTabDatabase'] . '?' . PMA_URL_getCommon($db);
+
+
+/**
+ * Ensures the database exists (else move to the "parent" script) and displays
+ * headers
+ */
+if (! isset($is_db) || ! $is_db) {
+ if (strlen($db)) {
+ $is_db = $GLOBALS['dbi']->selectDb($db);
+ // This "Command out of sync" 2014 error may happen, for example
+ // after calling a MySQL procedure; at this point we can't select
+ // the db but it's not necessarily wrong
+ if ($GLOBALS['dbi']->getError() && $GLOBALS['errno'] == 2014) {
+ $is_db = true;
+ unset($GLOBALS['errno']);
+ }
+ }
+ // Not a valid db name -> back to the welcome page
+ $uri = $cfg['PmaAbsoluteUri'] . 'index.php?'
+ . PMA_URL_getCommon('', '', '&')
+ . (isset($message) ? '&message=' . urlencode($message) : '') . '&reload=1';
+ if (! strlen($db) || ! $is_db) {
+ $response = PMA_Response::getInstance();
+ if ($response->isAjax()) {
+ $response->isSuccess(false);
+ $response->addJSON(
+ 'message',
+ PMA_Message::error(__('No databases selected.'))
+ );
+ } else {
+ PMA_sendHeaderLocation($uri);
+ }
+ exit;
+ }
+} // end if (ensures db exists)
+
+/**
+ * Changes database charset if requested by the user
+ */
+if (isset($_REQUEST['submitcollation'])
+ && isset($_REQUEST['db_collation'])
+ && ! empty($_REQUEST['db_collation'])
+) {
+ list($db_charset) = explode('_', $_REQUEST['db_collation']);
+ $sql_query = 'ALTER DATABASE '
+ . PMA_Util::backquote($db)
+ . ' DEFAULT' . PMA_generateCharsetQueryPart($_REQUEST['db_collation']);
+ $result = $GLOBALS['dbi']->query($sql_query);
+ $message = PMA_Message::success();
+ unset($db_charset);
+
+ /**
+ * If we are in an Ajax request, let us stop the execution here. Necessary for
+ * db charset change action on db_operations.php. If this causes a bug on
+ * other pages, we might have to move this to a different location.
+ */
+ if ( $GLOBALS['is_ajax_request'] == true) {
+ $response = PMA_Response::getInstance();
+ $response->isSuccess($message->isSuccess());
+ $response->addJSON('message', $message);
+ exit;
+ }
+}
+
+/**
+ * Set parameters for links
+ */
+$url_query = PMA_URL_getCommon($db);
+
+?>
diff --git a/libraries/db_info.inc.php b/libraries/db_info.inc.php
new file mode 100644
index 0000000000..90d3931a85
--- /dev/null
+++ b/libraries/db_info.inc.php
@@ -0,0 +1,248 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Gets the list of the table in the current db and informations about these
+ * tables if possible
+ *
+ * fills tooltip arrays and provides $tables, $num_tables, $is_show_stats
+ * and $db_is_information_schema
+ *
+ * speedup view on locked tables
+ *
+ * @package PhpMyAdmin
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ * limits for table list
+ */
+if (! isset($_SESSION['tmpval']['table_limit_offset'])
+ || $_SESSION['tmpval']['table_limit_offset_db'] != $db
+) {
+ $_SESSION['tmpval']['table_limit_offset'] = 0;
+ $_SESSION['tmpval']['table_limit_offset_db'] = $db;
+}
+if (isset($_REQUEST['pos'])) {
+ $_SESSION['tmpval']['table_limit_offset'] = (int) $_REQUEST['pos'];
+}
+$pos = $_SESSION['tmpval']['table_limit_offset'];
+
+PMA_Util::checkParameters(array('db'));
+
+/**
+ * @global bool whether to display extended stats
+ */
+$is_show_stats = $cfg['ShowStats'];
+
+/**
+ * @global bool whether selected db is information_schema
+ */
+$db_is_information_schema = false;
+
+if ($GLOBALS['dbi']->isSystemSchema($db)) {
+ $is_show_stats = false;
+ $db_is_information_schema = true;
+}
+
+/**
+ * @global array information about tables in db
+ */
+$tables = array();
+
+$tooltip_truename = array();
+$tooltip_aliasname = array();
+
+// Special speedup for newer MySQL Versions (in 4.0 format changed)
+if (true === $cfg['SkipLockedTables'] && ! PMA_DRIZZLE) {
+ $db_info_result = $GLOBALS['dbi']->query(
+ 'SHOW OPEN TABLES FROM ' . PMA_Util::backquote($db) . ';'
+ );
+
+ // Blending out tables in use
+ if ($db_info_result && $GLOBALS['dbi']->numRows($db_info_result) > 0) {
+ while ($tmp = $GLOBALS['dbi']->fetchAssoc($db_info_result)) {
+ // if in use, memorize table name
+ if ($tmp['In_use'] > 0) {
+ $sot_cache[$tmp['Table']] = true;
+ }
+ }
+ $GLOBALS['dbi']->freeResult($db_info_result);
+
+ if (isset($sot_cache)) {
+ $db_info_result = false;
+
+ $tblGroupSql = "";
+ $whereAdded = false;
+ if (PMA_isValid($_REQUEST['tbl_group'])) {
+ $group = PMA_Util::escapeMysqlWildcards($_REQUEST['tbl_group']);
+ $groupWithSeperator = PMA_Util::escapeMysqlWildcards(
+ $_REQUEST['tbl_group']
+ . $GLOBALS['cfg']['NavigationTreeTableSeparator']
+ );
+ $tblGroupSql .= " WHERE ("
+ . PMA_Util::backquote('Tables_in_' . $db)
+ . " LIKE '" . $groupWithSeperator . "%'"
+ . " OR "
+ . PMA_Util::backquote('Tables_in_' . $db)
+ . " LIKE '" . $group . "')";
+ $whereAdded = true;
+ }
+ if (PMA_isValid($_REQUEST['tbl_type'], array('table', 'view'))) {
+ $tblGroupSql .= $whereAdded ? " AND" : " WHERE";
+ if ($_REQUEST['tbl_type'] == 'view') {
+ $tblGroupSql .= " `Table_type` != 'BASE TABLE'";
+ } else {
+ $tblGroupSql .= " `Table_type` = 'BASE TABLE'";
+ }
+ }
+ $db_info_result = $GLOBALS['dbi']->query(
+ 'SHOW FULL TABLES FROM ' . PMA_Util::backquote($db) . $tblGroupSql,
+ null, PMA_DatabaseInterface::QUERY_STORE
+ );
+ unset($tblGroupSql, $whereAdded);
+
+ if ($db_info_result && $GLOBALS['dbi']->numRows($db_info_result) > 0) {
+ while ($tmp = $GLOBALS['dbi']->fetchRow($db_info_result)) {
+ if (! isset($sot_cache[$tmp[0]])) {
+ $sts_result = $GLOBALS['dbi']->query(
+ "SHOW TABLE STATUS FROM " . PMA_Util::backquote($db)
+ . " LIKE '" . PMA_Util::sqlAddSlashes($tmp[0], true)
+ . "';"
+ );
+ $sts_tmp = $GLOBALS['dbi']->fetchAssoc($sts_result);
+ $GLOBALS['dbi']->freeResult($sts_result);
+ unset($sts_result);
+
+ $tableArray = $GLOBALS['dbi']->copyTableProperties(
+ array($sts_tmp), $db
+ );
+ $tables[$sts_tmp['Name']] = $tableArray[0];
+ } else { // table in use
+ $tables[$tmp[0]] = array(
+ 'TABLE_NAME' => $tmp[0],
+ 'ENGINE' => '',
+ 'TABLE_TYPE' => '',
+ 'TABLE_ROWS' => 0,
+ );
+ }
+ }
+ if ($GLOBALS['cfg']['NaturalOrder']) {
+ uksort($tables, 'strnatcasecmp');
+ }
+
+ $sot_ready = true;
+ } elseif ($db_info_result) {
+ $GLOBALS['dbi']->freeResult($db_info_result);
+ }
+ unset($sot_cache);
+ }
+ unset($tmp);
+ } elseif ($db_info_result) {
+ $GLOBALS['dbi']->freeResult($db_info_result);
+ }
+}
+
+if (! isset($sot_ready)) {
+
+ // Set some sorting defaults
+ $sort = 'Name';
+ $sort_order = 'ASC';
+
+ if (isset($_REQUEST['sort'])) {
+ $sortable_name_mappings = array(
+ 'table' => 'Name',
+ 'records' => 'Rows',
+ 'type' => 'Engine',
+ 'collation' => 'Collation',
+ 'size' => 'Data_length',
+ 'overhead' => 'Data_free',
+ 'creation' => 'Create_time',
+ 'last_update' => 'Update_time',
+ 'last_check' => 'Check_time'
+ );
+
+ // Make sure the sort type is implemented
+ if (isset($sortable_name_mappings[$_REQUEST['sort']])) {
+ $sort = $sortable_name_mappings[$_REQUEST['sort']];
+ if ($_REQUEST['sort_order'] == 'DESC') {
+ $sort_order = 'DESC';
+ }
+ }
+ }
+
+ $tbl_group = false;
+ $groupWithSeperator = false;
+ $tbl_type = null;
+ $limit_offset = 0;
+ $limit_count = false;
+ $groupTable = array();
+
+ if (! empty($_REQUEST['tbl_group']) || ! empty($_REQUEST['tbl_type'])) {
+ if (! empty($_REQUEST['tbl_type'])) {
+ // only tables for selected type
+ $tbl_type = $_REQUEST['tbl_type'];
+ }
+ if (! empty($_REQUEST['tbl_group'])) {
+ // only tables for selected group
+ $tbl_group = $_REQUEST['tbl_group'];
+ // include the table with the exact name of the group if such exists
+ $groupTable = $GLOBALS['dbi']->getTablesFull(
+ $db, $tbl_group, false, null, $limit_offset,
+ $limit_count, $sort, $sort_order, $tbl_type
+ );
+ $groupWithSeperator = $tbl_group
+ . $GLOBALS['cfg']['NavigationTreeTableSeparator'];
+ }
+ } else {
+ // all tables in db
+ // - get the total number of tables
+ // (needed for proper working of the MaxTableList feature)
+ $tables = $GLOBALS['dbi']->getTables($db);
+ $total_num_tables = count($tables);
+ if (isset($sub_part) && $sub_part == '_export') {
+ // (don't fetch only a subset if we are coming from db_export.php,
+ // because I think it's too risky to display only a subset of the
+ // table names when exporting a db)
+ /**
+ *
+ * @todo Page selector for table names?
+ */
+ } else {
+ // fetch the details for a possible limited subset
+ $limit_offset = $pos;
+ $limit_count = true;
+ }
+ }
+ $tables = array_merge(
+ $groupTable,
+ $GLOBALS['dbi']->getTablesFull(
+ $db, $groupWithSeperator, ($groupWithSeperator != false), null,
+ $limit_offset, $limit_count, $sort, $sort_order, $tbl_type
+ )
+ );
+}
+
+/**
+ * @global int count of tables in db
+ */
+$num_tables = count($tables);
+// (needed for proper working of the MaxTableList feature)
+if (! isset($total_num_tables)) {
+ $total_num_tables = $num_tables;
+}
+
+/**
+ * cleanup
+ */
+unset($each_table, $db_info_result);
+
+/**
+ * If coming from a Show MySQL link on the home page,
+ * put something in $sub_part
+ */
+if (empty($sub_part)) {
+ $sub_part = '_structure';
+}
+?>
diff --git a/libraries/db_table_exists.lib.php b/libraries/db_table_exists.lib.php
new file mode 100644
index 0000000000..706629e488
--- /dev/null
+++ b/libraries/db_table_exists.lib.php
@@ -0,0 +1,104 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Ensure the database and the table exist (else move to the "parent" script)
+ * and display headers
+ *
+ * @package PhpMyAdmin
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+if (empty($is_db)) {
+ if (strlen($db)) {
+ $is_db = @$GLOBALS['dbi']->selectDb($db);
+ } else {
+ $is_db = false;
+ }
+
+ if (! $is_db) {
+ // not a valid db name -> back to the welcome page
+ if (! defined('IS_TRANSFORMATION_WRAPPER')) {
+ $response = PMA_Response::getInstance();
+ if ($response->isAjax()) {
+ $response->isSuccess(false);
+ $response->addJSON(
+ 'message',
+ PMA_Message::error(__('No databases selected.'))
+ );
+ } else {
+ $url_params = array('reload' => 1);
+ if (isset($message)) {
+ $url_params['message'] = $message;
+ }
+ if (! empty($sql_query)) {
+ $url_params['sql_query'] = $sql_query;
+ }
+ if (isset($show_as_php)) {
+ $url_params['show_as_php'] = $show_as_php;
+ }
+ PMA_sendHeaderLocation(
+ $cfg['PmaAbsoluteUri'] . 'index.php'
+ . PMA_URL_getCommon($url_params, '&')
+ );
+ }
+ exit;
+ }
+ }
+} // end if (ensures db exists)
+
+if (empty($is_table)
+ && !defined('PMA_SUBMIT_MULT')
+ && ! defined('TABLE_MAY_BE_ABSENT')
+) {
+ // Not a valid table name -> back to the db_sql.php
+
+ if (strlen($table)) {
+ $is_table = isset(PMA_Table::$cache[$db][$table]);
+
+ if (! $is_table) {
+ $_result = $GLOBALS['dbi']->tryQuery(
+ 'SHOW TABLES LIKE \'' . PMA_Util::sqlAddSlashes($table, true)
+ . '\';',
+ null, PMA_DatabaseInterface::QUERY_STORE
+ );
+ $is_table = @$GLOBALS['dbi']->numRows($_result);
+ $GLOBALS['dbi']->freeResult($_result);
+ }
+ } else {
+ $is_table = false;
+ }
+
+ if (! $is_table) {
+ if (! defined('IS_TRANSFORMATION_WRAPPER')) {
+ if (strlen($table)) {
+ // SHOW TABLES doesn't show temporary tables, so try select
+ // (as it can happen just in case temporary table, it should be
+ // fast):
+
+ /**
+ * @todo should this check really
+ * only happen if IS_TRANSFORMATION_WRAPPER?
+ */
+ $_result = $GLOBALS['dbi']->tryQuery(
+ 'SELECT COUNT(*) FROM ' . PMA_Util::backquote($table) . ';',
+ null,
+ PMA_DatabaseInterface::QUERY_STORE
+ );
+ $is_table = ($_result && @$GLOBALS['dbi']->numRows($_result));
+ $GLOBALS['dbi']->freeResult($_result);
+ }
+
+ if (! $is_table) {
+ include './db_sql.php';
+ exit;
+ }
+ }
+
+ if (! $is_table) {
+ exit;
+ }
+ }
+} // end if (ensures table exists)
+?>
diff --git a/libraries/dbi/DBIDrizzle.class.php b/libraries/dbi/DBIDrizzle.class.php
new file mode 100644
index 0000000000..82293d9da3
--- /dev/null
+++ b/libraries/dbi/DBIDrizzle.class.php
@@ -0,0 +1,712 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Interface to the Drizzle extension
+ *
+ * WARNING - EXPERIMENTAL, never use in production,
+ * drizzle module segfaults often and when you least expect it to
+ *
+ * TODO: This file and drizzle-wrappers.lib.php should be devoid
+ * of any segault related hacks.
+ * TODO: Crashing versions of drizzle module and/or libdrizzle
+ * should be blacklisted
+ *
+ * @package PhpMyAdmin-DBI
+ * @subpackage Drizzle
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+require_once './libraries/logging.lib.php';
+require_once './libraries/dbi/drizzle-wrappers.lib.php';
+require_once './libraries/dbi/DBIExtension.int.php';
+
+/**
+ * MySQL client API
+ */
+if (! defined('PMA_MYSQL_CLIENT_API')) {
+ define('PMA_MYSQL_CLIENT_API', (int)drizzle_version());
+}
+
+/**
+ * Interface to the Drizzle extension
+ *
+ * @package PhpMyAdmin-DBI
+ * @subpackage Drizzle
+ */
+class PMA_DBI_Drizzle implements PMA_DBI_Extension
+{
+ /**
+ * Helper function for connecting to the database server
+ *
+ * @param PMA_Drizzle $drizzle connection handle
+ * @param string $host Drizzle host
+ * @param integer $port Drizzle port
+ * @param string $uds server socket
+ * @param string $user username
+ * @param string $password password
+ * @param string $db database name
+ * @param integer $options connection options
+ *
+ * @return PMA_DrizzleCon
+ */
+ private function _realConnect($drizzle, $host, $port, $uds, $user, $password,
+ $db = null, $options = DRIZZLE_CON_NONE
+ ) {
+ if ($uds) {
+ $con = $drizzle->addUds($uds, $user, $password, $db, $options);
+ } else {
+ $con = $drizzle->addTcp($host, $port, $user, $password, $db, $options);
+ }
+
+ return $con;
+ }
+
+ /**
+ * connects to the database server
+ *
+ * @param string $user drizzle user name
+ * @param string $password drizzle user password
+ * @param bool $is_controluser whether this is a control user connection
+ * @param array $server host/port/socket/persistent
+ * @param bool $auxiliary_connection (when true, don't go back to login if
+ * connection fails)
+ *
+ * @return mixed false on error or a mysqli object on success
+ */
+ public function connect($user, $password, $is_controluser = false,
+ $server = null, $auxiliary_connection = false
+ ) {
+ global $cfg;
+
+ if ($server) {
+ $server_port = (empty($server['port']))
+ ? false
+ : (int)$server['port'];
+ $server_socket = (empty($server['socket']))
+ ? ''
+ : $server['socket'];
+ $server['host'] = (empty($server['host']))
+ ? 'localhost'
+ : $server['host'];
+ } else {
+ $server_port = (empty($cfg['Server']['port']))
+ ? false
+ : (int) $cfg['Server']['port'];
+ $server_socket = (empty($cfg['Server']['socket']))
+ ? null
+ : $cfg['Server']['socket'];
+ }
+
+ if (strtolower($GLOBALS['cfg']['Server']['connect_type']) == 'tcp') {
+ $GLOBALS['cfg']['Server']['socket'] = '';
+ }
+
+ $drizzle = new PMA_Drizzle();
+
+ $client_flags = 0;
+
+ /* Optionally compress connection */
+ if ($GLOBALS['cfg']['Server']['compress']) {
+ $client_flags |= DRIZZLE_CAPABILITIES_COMPRESS;
+ }
+
+ /* Optionally enable SSL */
+ if ($GLOBALS['cfg']['Server']['ssl']) {
+ $client_flags |= DRIZZLE_CAPABILITIES_SSL;
+ }
+
+ if (! $server) {
+ $link = @$this->_realConnect(
+ $drizzle, $cfg['Server']['host'],
+ $server_port, $server_socket, $user,
+ $password, false, $client_flags
+ );
+ // Retry with empty password if we're allowed to
+ if ($link == false && isset($cfg['Server']['nopassword'])
+ && $cfg['Server']['nopassword'] && ! $is_controluser
+ ) {
+ $link = @$this->_realConnect(
+ $drizzle, $cfg['Server']['host'], $server_port, $server_socket,
+ $user, null, false, $client_flags
+ );
+ }
+ } else {
+ $link = @$this->_realConnect(
+ $drizzle, $server['host'], $server_port, $server_socket,
+ $user, $password
+ );
+ }
+
+ if ($link == false) {
+ if ($is_controluser) {
+ trigger_error(
+ __(
+ 'Connection for controluser as defined'
+ . ' in your configuration failed.'
+ ),
+ E_USER_WARNING
+ );
+ return false;
+ }
+ // we could be calling $GLOBALS['dbi']->connect() to connect to another
+ // server, for example in the Synchronize feature, so do not
+ // go back to main login if it fails
+ if (! $auxiliary_connection) {
+ PMA_logUser($user, 'drizzle-denied');
+ global $auth_plugin;
+ $auth_plugin->authFails();
+ } else {
+ return false;
+ }
+ } else {
+ $GLOBALS['dbi']->postConnect($link, $is_controluser);
+ }
+
+ return $link;
+ }
+
+ /**
+ * selects given database
+ *
+ * @param string $dbname database name to select
+ * @param PMA_DrizzleCom $link connection object
+ *
+ * @return bool
+ */
+ public function selectDb($dbname, $link = null)
+ {
+ if (empty($link)) {
+ if (isset($GLOBALS['userlink'])) {
+ $link = $GLOBALS['userlink'];
+ } else {
+ return false;
+ }
+ }
+ return $link->selectDb($dbname);
+ }
+
+ /**
+ * runs a query and returns the result
+ *
+ * @param string $query query to execute
+ * @param PMA_DrizzleCon $link connection object
+ * @param int $options query options
+ *
+ * @return PMA_DrizzleResult
+ */
+ public function realQuery($query, $link, $options)
+ {
+ $buffer_mode = $options & PMA_DatabaseInterface::QUERY_UNBUFFERED
+ ? PMA_Drizzle::BUFFER_ROW
+ : PMA_Drizzle::BUFFER_RESULT;
+ $res = $link->query($query, $buffer_mode);
+ return $res;
+ }
+
+ /**
+ * Run the multi query and output the results
+ *
+ * @param object $link connection object
+ * @param string $query multi query statement to execute
+ *
+ * @return result collection | boolean(false)
+ */
+ public function realMultiQuery($link, $query)
+ {
+ return false;
+ }
+
+ /**
+ * returns array of rows with associative and numeric keys from $result
+ *
+ * @param PMA_DrizzleResult $result Drizzle result object
+ *
+ * @return array
+ */
+ public function fetchArray($result)
+ {
+ return $result->fetchRow(PMA_Drizzle::FETCH_BOTH);
+ }
+
+ /**
+ * returns array of rows with associative keys from $result
+ *
+ * @param PMA_DrizzleResult $result Drizzle result object
+ *
+ * @return array
+ */
+ public function fetchAssoc($result)
+ {
+ return $result->fetchRow(PMA_Drizzle::FETCH_ASSOC);
+ }
+
+ /**
+ * returns array of rows with numeric keys from $result
+ *
+ * @param PMA_DrizzleResult $result Drizzle result object
+ *
+ * @return array
+ */
+ public function fetchRow($result)
+ {
+ return $result->fetchRow(PMA_Drizzle::FETCH_NUM);
+ }
+
+ /**
+ * Adjusts the result pointer to an arbitrary row in the result
+ *
+ * @param PMA_DrizzleResult $result Drizzle result object
+ * @param int $offset offset to seek
+ *
+ * @return boolean true on success, false on failure
+ */
+ public function dataSeek($result, $offset)
+ {
+ return $result->seek($offset);
+ }
+
+ /**
+ * Frees memory associated with the result
+ *
+ * @param PMA_DrizzleResult $result database result
+ *
+ * @return void
+ */
+ public function freeResult($result)
+ {
+ if ($result instanceof PMA_DrizzleResult) {
+ $result->free();
+ }
+ }
+
+ /**
+ * Check if there are any more query results from a multi query
+ *
+ * @param object $link the connection object
+ *
+ * @return bool false
+ */
+ public function moreResults($link = null)
+ {
+ // N.B.: PHP's 'mysql' extension does not support
+ // multi_queries so this function will always
+ // return false. Use the 'mysqli' extension, if
+ // you need support for multi_queries.
+ return false;
+ }
+
+ /**
+ * Prepare next result from multi_query
+ *
+ * @param object $link the connection object
+ *
+ * @return bool false
+ */
+ public function nextResult($link = null)
+ {
+ // N.B.: PHP's 'mysql' extension does not support
+ // multi_queries so this function will always
+ // return false. Use the 'mysqli' extension, if
+ // you need support for multi_queries.
+ return false;
+ }
+
+ /**
+ * Returns a string representing the type of connection used
+ *
+ * @param PMA_DrizzleCon $link connection object
+ *
+ * @return string type of connection used
+ */
+ public function getHostInfo($link = null)
+ {
+ if (null === $link) {
+ if (isset($GLOBALS['userlink'])) {
+ $link = $GLOBALS['userlink'];
+ } else {
+ return false;
+ }
+ }
+
+ $str = $link->port()
+ ? $link->host() . ':' . $link->port() . ' via TCP/IP'
+ : 'Localhost via UNIX socket';
+ return $str;
+ }
+
+ /**
+ * Returns the version of the Drizzle protocol used
+ *
+ * @param PMA_DrizzleCon $link connection object
+ *
+ * @return int version of the Drizzle protocol used
+ */
+ public function getProtoInfo($link = null)
+ {
+ if (null === $link) {
+ if (isset($GLOBALS['userlink'])) {
+ $link = $GLOBALS['userlink'];
+ } else {
+ return false;
+ }
+ }
+
+ return $link->protocolVersion();
+ }
+
+ /**
+ * returns a string that represents the client library version
+ *
+ * @return string Drizzle client library version
+ */
+ public function getClientInfo()
+ {
+ return 'libdrizzle (Drizzle ' . drizzle_version() . ')';
+ }
+
+ /**
+ * returns last error message or false if no errors occurred
+ *
+ * @param PMA_DrizzleCon $link connection object
+ *
+ * @return string|bool $error or false
+ */
+ public function getError($link = null)
+ {
+ $GLOBALS['errno'] = 0;
+
+ /* Treat false same as null because of controllink */
+ if ($link === false) {
+ $link = null;
+ }
+
+ if (null === $link && isset($GLOBALS['userlink'])) {
+ $link =& $GLOBALS['userlink'];
+ // Do not stop now. We still can get the error code
+ // with mysqli_connect_errno()
+ // } else {
+ // return false;
+ }
+
+ if (null !== $link) {
+ $error_number = drizzle_con_errno($link->getConnectionObject());
+ $error_message = drizzle_con_error($link->getConnectionObject());
+ } else {
+ $error_number = drizzle_errno();
+ $error_message = drizzle_error();
+ }
+ if (0 == $error_number) {
+ return false;
+ }
+
+ // keep the error number for further check after
+ // the call to getError()
+ $GLOBALS['errno'] = $error_number;
+
+ return $GLOBALS['dbi']->formatError($error_number, $error_message);
+ }
+
+ /**
+ * returns the number of rows returned by last query
+ *
+ * @param PMA_DrizzleResult $result Drizzle result object
+ *
+ * @return string|int
+ */
+ public function numRows($result)
+ {
+ // see the note for $GLOBALS['dbi']->tryQuery();
+ if (!is_bool($result)) {
+ return @$result->numRows();
+ } else {
+ return 0;
+ }
+ }
+
+ /**
+ * returns last inserted auto_increment id for given $link
+ * or $GLOBALS['userlink']
+ *
+ * @param PMA_DrizzleCon $link connection object
+ *
+ * @return string|int
+ */
+ public function insertId($link = null)
+ {
+ if (empty($link)) {
+ if (isset($GLOBALS['userlink'])) {
+ $link = $GLOBALS['userlink'];
+ } else {
+ return false;
+ }
+ }
+
+ // copied from mysql and mysqli
+
+ // When no controluser is defined, using mysqli_insert_id($link)
+ // does not always return the last insert id due to a mixup with
+ // the tracking mechanism, but this works:
+ return $GLOBALS['dbi']->fetchValue('SELECT LAST_INSERT_ID();', 0, 0, $link);
+ // Curiously, this problem does not happen with the mysql extension but
+ // there is another problem with BIGINT primary keys so insertId()
+ // in the mysql extension also uses this logic.
+ }
+
+ /**
+ * returns the number of rows affected by last query
+ *
+ * @param PMA_DrizzleResult $link connection object
+ * @param bool $get_from_cache whether to retrieve from cache
+ *
+ * @return string|int
+ */
+ public function affectedRows($link = null, $get_from_cache = true)
+ {
+ if (empty($link)) {
+ if (isset($GLOBALS['userlink'])) {
+ $link = $GLOBALS['userlink'];
+ } else {
+ return false;
+ }
+ }
+ if ($get_from_cache) {
+ return $GLOBALS['cached_affected_rows'];
+ } else {
+ return $link->affectedRows();
+ }
+ }
+
+ /**
+ * returns metainfo for fields in $result
+ *
+ * @param PMA_DrizzleResult $result Drizzle result object
+ *
+ * @return array meta info for fields in $result
+ */
+ public function getFieldsMeta($result)
+ {
+ // Build an associative array for a type look up
+ $typeAr = array();
+ /*$typeAr[DRIZZLE_COLUMN_TYPE_DECIMAL] = 'real';
+ $typeAr[DRIZZLE_COLUMN_TYPE_NEWDECIMAL] = 'real';
+ $typeAr[DRIZZLE_COLUMN_TYPE_BIT] = 'int';
+ $typeAr[DRIZZLE_COLUMN_TYPE_TINY] = 'int';
+ $typeAr[DRIZZLE_COLUMN_TYPE_SHORT] = 'int';
+ $typeAr[DRIZZLE_COLUMN_TYPE_LONG] = 'int';
+ $typeAr[DRIZZLE_COLUMN_TYPE_FLOAT] = 'real';
+ $typeAr[DRIZZLE_COLUMN_TYPE_DOUBLE] = 'real';
+ $typeAr[DRIZZLE_COLUMN_TYPE_NULL] = 'null';
+ $typeAr[DRIZZLE_COLUMN_TYPE_TIMESTAMP] = 'timestamp';
+ $typeAr[DRIZZLE_COLUMN_TYPE_LONGLONG] = 'int';
+ $typeAr[DRIZZLE_COLUMN_TYPE_INT24] = 'int';
+ $typeAr[DRIZZLE_COLUMN_TYPE_DATE] = 'date';
+ $typeAr[DRIZZLE_COLUMN_TYPE_TIME] = 'date';
+ $typeAr[DRIZZLE_COLUMN_TYPE_DATETIME] = 'datetime';
+ $typeAr[DRIZZLE_COLUMN_TYPE_YEAR] = 'year';
+ $typeAr[DRIZZLE_COLUMN_TYPE_NEWDATE] = 'date';
+ $typeAr[DRIZZLE_COLUMN_TYPE_ENUM] = 'unknown';
+ $typeAr[DRIZZLE_COLUMN_TYPE_SET] = 'unknown';
+ $typeAr[DRIZZLE_COLUMN_TYPE_VIRTUAL] = 'unknown';
+ $typeAr[DRIZZLE_COLUMN_TYPE_TINY_BLOB] = 'blob';
+ $typeAr[DRIZZLE_COLUMN_TYPE_MEDIUM_BLOB] = 'blob';
+ $typeAr[DRIZZLE_COLUMN_TYPE_LONG_BLOB] = 'blob';
+ $typeAr[DRIZZLE_COLUMN_TYPE_BLOB] = 'blob';
+ $typeAr[DRIZZLE_COLUMN_TYPE_VAR_STRING] = 'string';
+ $typeAr[DRIZZLE_COLUMN_TYPE_VARCHAR] = 'string';
+ $typeAr[DRIZZLE_COLUMN_TYPE_STRING] = 'string';
+ $typeAr[DRIZZLE_COLUMN_TYPE_GEOMETRY] = 'geometry';*/
+
+ $typeAr[DRIZZLE_COLUMN_TYPE_DRIZZLE_BLOB] = 'blob';
+ $typeAr[DRIZZLE_COLUMN_TYPE_DRIZZLE_DATE] = 'date';
+ $typeAr[DRIZZLE_COLUMN_TYPE_DRIZZLE_DATETIME] = 'datetime';
+ $typeAr[DRIZZLE_COLUMN_TYPE_DRIZZLE_DOUBLE] = 'real';
+ $typeAr[DRIZZLE_COLUMN_TYPE_DRIZZLE_ENUM] = 'unknown';
+ $typeAr[DRIZZLE_COLUMN_TYPE_DRIZZLE_LONG] = 'int';
+ $typeAr[DRIZZLE_COLUMN_TYPE_DRIZZLE_LONGLONG] = 'int';
+ $typeAr[DRIZZLE_COLUMN_TYPE_DRIZZLE_MAX] = 'unknown';
+ $typeAr[DRIZZLE_COLUMN_TYPE_DRIZZLE_NULL] = 'null';
+ $typeAr[DRIZZLE_COLUMN_TYPE_DRIZZLE_TIMESTAMP] = 'timestamp';
+ $typeAr[DRIZZLE_COLUMN_TYPE_DRIZZLE_TINY] = 'int';
+ $typeAr[DRIZZLE_COLUMN_TYPE_DRIZZLE_VARCHAR] = 'string';
+
+ // array of DrizzleColumn
+ $columns = $result->getColumns();
+ // columns in a standarized format
+ $std_columns = array();
+
+ foreach ($columns as $k => $column) {
+ $c = new stdClass();
+ $c->name = $column->name();
+ $c->orgname = $column->origName();
+ $c->table = $column->table();
+ $c->orgtable = $column->origTable();
+ $c->def = $column->defaultValue();
+ $c->db = $column->db();
+ $c->catalog = $column->catalog();
+ // $column->maxSize() returns always 0 while size() seems
+ // to return a correct value (drizzle extension v.0.5, API v.7)
+ $c->max_length = $column->size();
+ $c->decimals = $column->decimals();
+ $c->charsetnr = $column->charset();
+ $c->type = $typeAr[$column->typeDrizzle()];
+ $c->_type = $column->type();
+ $c->flags = $this->fieldFlags($result, $k);
+ $c->_flags = $column->flags();
+
+ $c->multiple_key = (int) (bool) (
+ $c->_flags & DRIZZLE_COLUMN_FLAGS_MULTIPLE_KEY
+ );
+ $c->primary_key = (int) (bool) (
+ $c->_flags & DRIZZLE_COLUMN_FLAGS_PRI_KEY
+ );
+ $c->unique_key = (int) (bool) (
+ $c->_flags & DRIZZLE_COLUMN_FLAGS_UNIQUE_KEY
+ );
+ $c->not_null = (int) (bool) (
+ $c->_flags & DRIZZLE_COLUMN_FLAGS_NOT_NULL
+ );
+ $c->unsigned = (int) (bool) (
+ $c->_flags & DRIZZLE_COLUMN_FLAGS_UNSIGNED
+ );
+ $c->zerofill = (int) (bool) (
+ $c->_flags & DRIZZLE_COLUMN_FLAGS_ZEROFILL
+ );
+ $c->numeric = (int) (bool) (
+ $c->_flags & DRIZZLE_COLUMN_FLAGS_NUM
+ );
+ $c->blob = (int) (bool) (
+ $c->_flags & DRIZZLE_COLUMN_FLAGS_BLOB
+ );
+
+ $std_columns[] = $c;
+ }
+
+ return $std_columns;
+ }
+
+ /**
+ * return number of fields in given $result
+ *
+ * @param PMA_DrizzleResult $result Drizzle result object
+ *
+ * @return int field count
+ */
+ public function numFields($result)
+ {
+ return $result->numColumns();
+ }
+
+ /**
+ * returns the length of the given field $i in $result
+ *
+ * @param PMA_DrizzleResult $result Drizzle result object
+ * @param int $i field
+ *
+ * @return int length of field
+ */
+ public function fieldLen($result, $i)
+ {
+ $colums = $result->getColumns();
+ return $colums[$i]->size();
+ }
+
+ /**
+ * returns name of $i. field in $result
+ *
+ * @param PMA_DrizzleResult $result Drizzle result object
+ * @param int $i field
+ *
+ * @return string name of $i. field in $result
+ */
+ public function fieldName($result, $i)
+ {
+ $colums = $result->getColumns();
+ return $colums[$i]->name();
+ }
+
+ /**
+ * returns concatenated string of human readable field flags
+ *
+ * @param PMA_DrizzleResult $result Drizzle result object
+ * @param int $i field
+ *
+ * @return string field flags
+ */
+ public function fieldFlags($result, $i)
+ {
+ $columns = $result->getColumns();
+ $f = $columns[$i];
+ $type = $f->typeDrizzle();
+ $charsetnr = $f->charset();
+ $f = $f->flags();
+ $flags = '';
+ if ($f & DRIZZLE_COLUMN_FLAGS_UNIQUE_KEY) {
+ $flags .= 'unique ';
+ }
+ if ($f & DRIZZLE_COLUMN_FLAGS_NUM) {
+ $flags .= 'num ';
+ }
+ if ($f & DRIZZLE_COLUMN_FLAGS_PART_KEY) {
+ $flags .= 'part_key ';
+ }
+ if ($f & DRIZZLE_COLUMN_FLAGS_SET) {
+ $flags .= 'set ';
+ }
+ if ($f & DRIZZLE_COLUMN_FLAGS_TIMESTAMP) {
+ $flags .= 'timestamp ';
+ }
+ if ($f & DRIZZLE_COLUMN_FLAGS_AUTO_INCREMENT) {
+ $flags .= 'auto_increment ';
+ }
+ if ($f & DRIZZLE_COLUMN_FLAGS_ENUM) {
+ $flags .= 'enum ';
+ }
+ // See http://dev.mysql.com/doc/refman/6.0/en/c-api-datatypes.html:
+ // to determine if a string is binary, we should not use MYSQLI_BINARY_FLAG
+ // but instead the charsetnr member of the MYSQL_FIELD
+ // structure. Watch out: some types like DATE returns 63 in charsetnr
+ // so we have to check also the type.
+ // Unfortunately there is no equivalent in the mysql extension.
+ if (($type == DRIZZLE_COLUMN_TYPE_DRIZZLE_BLOB
+ || $type == DRIZZLE_COLUMN_TYPE_DRIZZLE_VARCHAR)
+ && 63 == $charsetnr
+ ) {
+ $flags .= 'binary ';
+ }
+ if ($f & DRIZZLE_COLUMN_FLAGS_ZEROFILL) {
+ $flags .= 'zerofill ';
+ }
+ if ($f & DRIZZLE_COLUMN_FLAGS_UNSIGNED) {
+ $flags .= 'unsigned ';
+ }
+ if ($f & DRIZZLE_COLUMN_FLAGS_BLOB) {
+ $flags .= 'blob ';
+ }
+ if ($f & DRIZZLE_COLUMN_FLAGS_MULTIPLE_KEY) {
+ $flags .= 'multiple_key ';
+ }
+ if ($f & DRIZZLE_COLUMN_FLAGS_UNIQUE_KEY) {
+ $flags .= 'unique_key ';
+ }
+ if ($f & DRIZZLE_COLUMN_FLAGS_PRI_KEY) {
+ $flags .= 'primary_key ';
+ }
+ if ($f & DRIZZLE_COLUMN_FLAGS_NOT_NULL) {
+ $flags .= 'not_null ';
+ }
+ return trim($flags);
+ }
+
+ /**
+ * Store the result returned from multi query
+ *
+ * @return false
+ */
+ public function storeResult()
+ {
+ return false;
+ }
+}
+?>
diff --git a/libraries/dbi/DBIDummy.class.php b/libraries/dbi/DBIDummy.class.php
new file mode 100644
index 0000000000..c9d2eed70c
--- /dev/null
+++ b/libraries/dbi/DBIDummy.class.php
@@ -0,0 +1,869 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Fake database driver for testing purposes
+ *
+ * It has hardcoded results for given queries what makes easy to use it
+ * in testsuite. Feel free to include other queries which your test will
+ * need.
+ *
+ * @package PhpMyAdmin-DBI
+ * @subpackage Dummy
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+require_once './libraries/dbi/DBIExtension.int.php';
+
+/**
+ * Array of queries this "driver" supports
+ */
+$GLOBALS['dummy_queries'] = array(
+ array('query' => 'SELECT 1', 'result' => array(array('1'))),
+ array(
+ 'query' => 'SELECT CURRENT_USER();',
+ 'result' => array(array('pma_test@localhost')),
+ ),
+ array(
+ 'query' => 'SELECT COUNT(*) FROM mysql.user',
+ 'result' => false,
+ ),
+ array(
+ 'query' => 'SHOW MASTER LOGS',
+ 'result' => false,
+ ),
+ array(
+ 'query' => 'SHOW STORAGE ENGINES',
+ 'result' => array(
+ array(
+ 'Engine' => 'dummy',
+ 'Support' => 'YES',
+ 'Comment' => 'dummy comment'
+ ),
+ array(
+ 'Engine' => 'dummy2',
+ 'Support' => 'NO',
+ 'Comment' => 'dummy2 comment'
+ ),
+ array(
+ 'Engine' => 'FEDERATED',
+ 'Support' => 'NO',
+ 'Comment' => 'Federated MySQL storage engine'
+ ),
+ )
+ ),
+ array(
+ 'query' => 'SHOW STATUS WHERE Variable_name'
+ . ' LIKE \'Innodb\\_buffer\\_pool\\_%\''
+ . ' OR Variable_name = \'Innodb_page_size\';',
+ 'result' => array(
+ array('Innodb_buffer_pool_pages_data', 0),
+ array('Innodb_buffer_pool_pages_dirty', 0),
+ array('Innodb_buffer_pool_pages_flushed', 0),
+ array('Innodb_buffer_pool_pages_free', 0),
+ array('Innodb_buffer_pool_pages_misc', 0),
+ array('Innodb_buffer_pool_pages_total', 4096),
+ array('Innodb_buffer_pool_read_ahead_rnd', 0),
+ array('Innodb_buffer_pool_read_ahead', 0),
+ array('Innodb_buffer_pool_read_ahead_evicted', 0),
+ array('Innodb_buffer_pool_read_requests', 64),
+ array('Innodb_buffer_pool_reads', 32),
+ array('Innodb_buffer_pool_wait_free', 0),
+ array('Innodb_buffer_pool_write_requests', 64),
+ array('Innodb_page_size', 16384),
+ )
+ ),
+ array(
+ 'query' => 'SHOW INNODB STATUS;',
+ 'result' => false,
+ ),
+ array(
+ 'query' => 'SELECT @@innodb_version;',
+ 'result' => array(
+ array('1.1.8'),
+ )
+ ),
+ array(
+ 'query' => 'SHOW GLOBAL VARIABLES LIKE \'innodb_file_per_table\';',
+ 'result' => array(
+ array('innodb_file_per_table', 'OFF'),
+ )
+ ),
+ array(
+ 'query' => 'SHOW GLOBAL VARIABLES LIKE \'innodb_file_format\';',
+ 'result' => array(
+ array('innodb_file_format', 'Antelope'),
+ )
+ ),
+ array(
+ 'query' => 'SHOW VARIABLES LIKE \'collation_server\'',
+ 'result' => array(
+ array('collation_server', 'utf8_general_ci'),
+ )
+ ),
+ array(
+ 'query' => 'SHOW VARIABLES LIKE \'language\';',
+ 'result' => array(),
+ ),
+ array(
+ 'query' => 'SHOW TABLES FROM `pma_test`;',
+ 'result' => array(
+ array('table1'),
+ array('table2'),
+ )
+ ),
+ array(
+ 'query' => 'SHOW TABLES FROM `pmadb`',
+ 'result' => array(
+ array('column_info'),
+ )
+ ),
+ array(
+ 'query' => 'SHOW COLUMNS FROM `pma_test`.`table1`',
+ 'columns' => array(
+ 'Field', 'Type', 'Null', 'Key', 'Default', 'Extra'
+ ),
+ 'result' => array(
+ array('i', 'int(11)', 'NO', 'PRI', 'NULL', 'auto_increment'),
+ array('o', 'int(11)', 'NO', 'MUL', 'NULL', ''),
+ )
+ ),
+ array(
+ 'query' => 'SHOW INDEXES FROM `pma_test`.`table1` WHERE (Non_unique = 0)',
+ 'result' => array(),
+ ),
+ array(
+ 'query' => 'SHOW COLUMNS FROM `pma_test`.`table2`',
+ 'columns' => array(
+ 'Field', 'Type', 'Null', 'Key', 'Default', 'Extra'
+ ),
+ 'result' => array(
+ array('i', 'int(11)', 'NO', 'PRI', 'NULL', 'auto_increment'),
+ array('o', 'int(11)', 'NO', 'MUL', 'NULL', ''),
+ )
+ ),
+ array(
+ 'query' => 'SHOW INDEXES FROM `pma_test`.`table1`',
+ 'result' => array(),
+ ),
+ array(
+ 'query' => 'SHOW INDEXES FROM `pma_test`.`table2`',
+ 'result' => array(),
+ ),
+ array(
+ 'query' => 'SHOW COLUMNS FROM `pma`.`table1`',
+ 'columns' => array(
+ 'Field', 'Type', 'Null', 'Key', 'Default', 'Extra',
+ 'Privileges', 'Comment'
+ ),
+ 'result' => array(
+ array(
+ 'i', 'int(11)', 'NO', 'PRI', 'NULL', 'auto_increment',
+ 'select,insert,update,references', ''
+ ),
+ array(
+ 'o', 'varchar(100)', 'NO', 'MUL', 'NULL', '',
+ 'select,insert,update,references', ''
+ ),
+ )
+ ),
+ array(
+ 'query' => 'SELECT * FROM information_schema.CHARACTER_SETS',
+ 'columns' => array(
+ 'CHARACTER_SET_NAME', 'DEFAULT_COLLATE_NAME', 'DESCRIPTION', 'MAXLEN'
+ ),
+ 'result' => array(
+ array('utf8', 'utf8_general_ci', 'UTF-8 Unicode', 3),
+ )
+ ),
+ array(
+ 'query' => 'SELECT * FROM information_schema.COLLATIONS',
+ 'columns' => array(
+ 'COLLATION_NAME', 'CHARACTER_SET_NAME', 'ID', 'IS_DEFAULT',
+ 'IS_COMPILED', 'SORTLEN'
+ ),
+ 'result' => array(
+ array('utf8_general_ci', 'utf8', 33, 'Yes', 'Yes', 1),
+ array('utf8_bin', 'utf8', 83, '', 'Yes', 1),
+ )
+ ),
+ array(
+ 'query' => 'SELECT `TABLE_NAME` FROM `INFORMATION_SCHEMA`.`TABLES`'
+ . ' WHERE `TABLE_SCHEMA`=\'pma_test\' AND `TABLE_TYPE`=\'BASE TABLE\'',
+ 'result' => array(),
+ ),
+ array(
+ 'query' => 'SELECT upper(plugin_name) f FROM data_dictionary.plugins'
+ . ' WHERE plugin_name IN (\'MYSQL_PASSWORD\',\'ROT13\')'
+ . ' AND plugin_type = \'Function\' AND is_active',
+ 'columns' => array('f'),
+ 'result' => array(array('ROT13')),
+ ),
+ array(
+ 'query' => 'SELECT `column_name`, `mimetype`, `transformation`,'
+ . ' `transformation_options` FROM `pmadb`.`column_info`'
+ . ' WHERE `db_name` = \'pma_test\' AND `table_name` = \'table1\''
+ . ' AND ( `mimetype` != \'\' OR `transformation` != \'\''
+ . ' OR `transformation_options` != \'\')',
+ 'columns' => array(
+ 'column_name', 'mimetype', 'transformation', 'transformation_options'
+ ),
+ 'result' => array(
+ array('o', 'text/plain', 'sql'),
+ )
+ ),
+ array(
+ 'query' => 'SELECT TABLE_NAME FROM information_schema.VIEWS'
+ . ' WHERE TABLE_SCHEMA = \'pma_test\' AND TABLE_NAME = \'table1\'',
+ 'result' => array(),
+ ),
+ array(
+ 'query' => 'SELECT *, `TABLE_SCHEMA` AS `Db`, `TABLE_NAME` AS `Name`,'
+ . ' `TABLE_TYPE` AS `TABLE_TYPE`, `ENGINE` AS `Engine`,'
+ . ' `ENGINE` AS `Type`, `VERSION` AS `Version`,'
+ . ' `ROW_FORMAT` AS `Row_format`, `TABLE_ROWS` AS `Rows`,'
+ . ' `AVG_ROW_LENGTH` AS `Avg_row_length`,'
+ . ' `DATA_LENGTH` AS `Data_length`,'
+ . ' `MAX_DATA_LENGTH` AS `Max_data_length`,'
+ . ' `INDEX_LENGTH` AS `Index_length`, `DATA_FREE` AS `Data_free`,'
+ . ' `AUTO_INCREMENT` AS `Auto_increment`,'
+ . ' `CREATE_TIME` AS `Create_time`, `UPDATE_TIME` AS `Update_time`,'
+ . ' `CHECK_TIME` AS `Check_time`, `TABLE_COLLATION` AS `Collation`,'
+ . ' `CHECKSUM` AS `Checksum`, `CREATE_OPTIONS` AS `Create_options`,'
+ . ' `TABLE_COMMENT` AS `Comment`'
+ . ' FROM `information_schema`.`TABLES` t'
+ . ' WHERE BINARY `TABLE_SCHEMA` IN (\'pma_test\')'
+ . ' AND t.`TABLE_NAME` = \'table1\' ORDER BY Name ASC',
+ 'columns' => array(
+ 'TABLE_CATALOG', 'TABLE_SCHEMA', 'TABLE_NAME', 'TABLE_TYPE', 'ENGINE',
+ 'VERSION', 'ROW_FORMAT', 'TABLE_ROWS', 'AVG_ROW_LENGTH', 'DATA_LENGTH',
+ 'MAX_DATA_LENGTH', 'INDEX_LENGTH', 'DATA_FREE', 'AUTO_INCREMENT',
+ 'CREATE_TIME', 'UPDATE_TIME', 'CHECK_TIME', 'TABLE_COLLATION',
+ 'CHECKSUM', 'CREATE_OPTIONS', 'TABLE_COMMENT', 'Db', 'Name',
+ 'TABLE_TYPE', 'Engine', 'Type', 'Version', 'Row_format', 'Rows',
+ 'Avg_row_length', 'Data_length', 'Max_data_length', 'Index_length',
+ 'Data_free', 'Auto_increment', 'Create_time', 'Update_time',
+ 'Check_time', 'Collation', 'Checksum', 'Create_options', 'Comment'
+ ),
+ 'result' => array(
+ array(
+ 'def', 'smash', 'issues_issue', 'BASE TABLE', 'InnoDB', '10',
+ 'Compact', '9136', '862', '7880704', '0', '1032192', '420478976',
+ '155862', '2012-08-29 13:28:28', 'NULL', 'NULL', 'utf8_general_ci',
+ 'NULL', '', '', 'smash', 'issues_issue', 'BASE TABLE', 'InnoDB',
+ 'InnoDB', '10', 'Compact', '9136', '862', '7880704', '0', '1032192',
+ '420478976', '155862', '2012-08-29 13:28:28', 'NULL', 'NULL',
+ 'utf8_general_ci', 'NULL'
+ ),
+ ),
+ ),
+ array(
+ 'query' => 'SELECT *, `TABLE_SCHEMA` AS `Db`, `TABLE_NAME` AS `Name`,'
+ . ' `TABLE_TYPE` AS `TABLE_TYPE`, `ENGINE` AS `Engine`,'
+ . ' `ENGINE` AS `Type`, `VERSION` AS `Version`,'
+ . ' `ROW_FORMAT` AS `Row_format`, `TABLE_ROWS` AS `Rows`,'
+ . ' `AVG_ROW_LENGTH` AS `Avg_row_length`,'
+ . ' `DATA_LENGTH` AS `Data_length`,'
+ . ' `MAX_DATA_LENGTH` AS `Max_data_length`,'
+ . ' `INDEX_LENGTH` AS `Index_length`, `DATA_FREE` AS `Data_free`,'
+ . ' `AUTO_INCREMENT` AS `Auto_increment`,'
+ . ' `CREATE_TIME` AS `Create_time`, `UPDATE_TIME` AS `Update_time`,'
+ . ' `CHECK_TIME` AS `Check_time`, `TABLE_COLLATION` AS `Collation`,'
+ . ' `CHECKSUM` AS `Checksum`, `CREATE_OPTIONS` AS `Create_options`,'
+ . ' `TABLE_COMMENT` AS `Comment`'
+ . ' FROM `information_schema`.`TABLES` t'
+ . ' WHERE `TABLE_SCHEMA` IN (\'pma_test\')'
+ . ' AND t.`TABLE_NAME` = \'table1\' ORDER BY Name ASC',
+ 'columns' => array('TABLE_CATALOG', 'TABLE_SCHEMA', 'TABLE_NAME',
+ 'TABLE_TYPE', 'ENGINE', 'VERSION', 'ROW_FORMAT', 'TABLE_ROWS',
+ 'AVG_ROW_LENGTH', 'DATA_LENGTH', 'MAX_DATA_LENGTH',
+ 'INDEX_LENGTH', 'DATA_FREE', 'AUTO_INCREMENT', 'CREATE_TIME',
+ 'UPDATE_TIME', 'CHECK_TIME', 'TABLE_COLLATION', 'CHECKSUM',
+ 'CREATE_OPTIONS', 'TABLE_COMMENT', 'Db', 'Name', 'TABLE_TYPE',
+ 'Engine', 'Type', 'Version', 'Row_format', 'Rows',
+ 'Avg_row_length', 'Data_length', 'Max_data_length',
+ 'Index_length', 'Data_free', 'Auto_increment', 'Create_time',
+ 'Update_time', 'Check_time', 'Collation', 'Checksum',
+ 'Create_options', 'Comment'),
+ 'result' => array(
+ array('def', 'smash', 'issues_issue', 'BASE TABLE', 'InnoDB', '10',
+ 'Compact', '9136', '862', '7880704', '0', '1032192',
+ '420478976', '155862', '2012-08-29 13:28:28', 'NULL', 'NULL',
+ 'utf8_general_ci', 'NULL', '', '', 'smash', 'issues_issue',
+ 'BASE TABLE', 'InnoDB', 'InnoDB', '10', 'Compact', '9136',
+ '862', '7880704', '0', '1032192', '420478976', '155862',
+ '2012-08-29 13:28:28', 'NULL', 'NULL', 'utf8_general_ci',
+ 'NULL'),
+ ),
+ ),
+ array(
+ 'query' => 'SELECT COUNT(*) FROM `pma_test`.`table1`',
+ 'result' => array(array(0)),
+ ),
+ array(
+ 'query' => 'SELECT `PRIVILEGE_TYPE` FROM `INFORMATION_SCHEMA`.'
+ . '`USER_PRIVILEGES`'
+ . ' WHERE GRANTEE=\'\'\'pma_test\'\'@\'\'localhost\'\'\''
+ . ' AND PRIVILEGE_TYPE=\'TRIGGER\'',
+ 'result' => array(),
+ ),
+ array(
+ 'query' => 'SELECT `PRIVILEGE_TYPE` FROM `INFORMATION_SCHEMA`.'
+ . '`SCHEMA_PRIVILEGES`'
+ . ' WHERE GRANTEE=\'\'\'pma_test\'\'@\'\'localhost\'\'\''
+ . ' AND PRIVILEGE_TYPE=\'TRIGGER\' AND \'pma\\\\_test\''
+ . ' REGEXP REPLACE(REPLACE(TABLE_SCHEMA, \'_\', \'.\'), \'%\', \'.*\')',
+ 'result' => array(),
+ ),
+ array(
+ 'query' => 'SELECT `PRIVILEGE_TYPE` FROM `INFORMATION_SCHEMA`.'
+ . '`TABLE_PRIVILEGES`'
+ . ' WHERE GRANTEE=\'\'\'pma_test\'\'@\'\'localhost\'\'\''
+ . ' AND PRIVILEGE_TYPE=\'TRIGGER\' AND \'pma\\\\_test\''
+ . ' REGEXP REPLACE(REPLACE(TABLE_SCHEMA, \'_\', \'.\'), \'%\', \'.*\')'
+ . ' AND TABLE_NAME=\'table1\'',
+ 'result' => array(),
+ ),
+ array(
+ 'query' => 'SELECT `PRIVILEGE_TYPE` FROM `INFORMATION_SCHEMA`.'
+ . '`USER_PRIVILEGES`'
+ . ' WHERE GRANTEE=\'\'\'pma_test\'\'@\'\'localhost\'\'\''
+ . ' AND PRIVILEGE_TYPE=\'EVENT\'',
+ 'result' => array(),
+ ),
+ array(
+ 'query' => 'SELECT `PRIVILEGE_TYPE` FROM `INFORMATION_SCHEMA`.'
+ . '`SCHEMA_PRIVILEGES`'
+ . ' WHERE GRANTEE=\'\'\'pma_test\'\'@\'\'localhost\'\'\''
+ . ' AND PRIVILEGE_TYPE=\'EVENT\' AND \'pma\\\\_test\''
+ . ' REGEXP REPLACE(REPLACE(TABLE_SCHEMA, \'_\', \'.\'), \'%\', \'.*\')',
+ 'result' => array(),
+ ),
+ array(
+ 'query' => 'SELECT `PRIVILEGE_TYPE` FROM `INFORMATION_SCHEMA`.'
+ . '`TABLE_PRIVILEGES`'
+ . ' WHERE GRANTEE=\'\'\'pma_test\'\'@\'\'localhost\'\'\''
+ . ' AND PRIVILEGE_TYPE=\'EVENT\''
+ . ' AND TABLE_SCHEMA=\'pma\\\\_test\' AND TABLE_NAME=\'table1\'',
+ 'result' => array(),
+ ),
+ array(
+ 'query' => 'RENAME TABLE `pma_test`.`table1` TO `pma_test`.`table3`;',
+ 'result' => array(),
+ ),
+ array(
+ 'query' => 'SELECT TRIGGER_SCHEMA, TRIGGER_NAME, EVENT_MANIPULATION,'
+ . ' EVENT_OBJECT_TABLE, ACTION_TIMING, ACTION_STATEMENT, '
+ . 'EVENT_OBJECT_SCHEMA, EVENT_OBJECT_TABLE, DEFINER'
+ . ' FROM information_schema.TRIGGERS'
+ . ' WHERE TRIGGER_SCHEMA= \'pma_test\''
+ . ' AND EVENT_OBJECT_TABLE = \'table1\';',
+ 'result' => array(),
+ ),
+ array(
+ 'query' => 'SHOW TABLES FROM `pma`;',
+ 'result' => array(),
+ ),
+ array(
+ 'query' => "SELECT `PRIVILEGE_TYPE` FROM `INFORMATION_SCHEMA`."
+ . "`SCHEMA_PRIVILEGES` WHERE GRANTEE='''pma_test''@''localhost'''"
+ . " AND PRIVILEGE_TYPE='EVENT' AND TABLE_SCHEMA='pma'",
+ 'result' => array(),
+ ),
+ array(
+ 'query' => "SELECT `PRIVILEGE_TYPE` FROM `INFORMATION_SCHEMA`."
+ . "`SCHEMA_PRIVILEGES` WHERE GRANTEE='''pma_test''@''localhost'''"
+ . " AND PRIVILEGE_TYPE='TRIGGER' AND TABLE_SCHEMA='pma'",
+ 'result' => array(),
+ ),
+ array(
+ 'query' => 'SELECT DEFAULT_COLLATION_NAME FROM information_schema.SCHEMATA'
+ . ' WHERE SCHEMA_NAME = \'pma_test\' LIMIT 1',
+ 'columns' => array('DEFAULT_COLLATION_NAME'),
+ 'result' => array(
+ array('utf8_general_ci')
+ )
+ ),
+ array(
+ 'query' => 'SELECT DEFAULT_COLLATION_NAME FROM data_dictionary.SCHEMAS'
+ . ' WHERE SCHEMA_NAME = \'pma_test\' LIMIT 1',
+ 'columns' => array('DEFAULT_COLLATION_NAME'),
+ 'result' => array(
+ array('utf8_general_ci_pma_drizzle')
+ )
+ ),
+ array(
+ 'query' => 'SHOW VARIABLES LIKE \'collation_database\'',
+ 'columns' => array('variable_name', 'variable_value'),
+ 'result' => array(
+ array('foo', 'bar'),
+ )
+ ),
+ array(
+ 'query' => "SHOW TABLES FROM `phpmyadmin`",
+ 'result' => array(),
+ ),
+ array(
+ 'query' => "SELECT tracking_active FROM pma_table_tracking" .
+ " WHERE db_name = 'pma_test_db'" .
+ " AND table_name = 'pma_test_table'" .
+ " ORDER BY version DESC",
+ 'columns' => array('tracking_active'),
+ 'result' => array(
+ array(1)
+ )
+ ),
+ array(
+ 'query' => "SELECT tracking_active FROM pma_table_tracking" .
+ " WHERE db_name = 'pma_test_db'" .
+ " AND table_name = 'pma_test_table2'" .
+ " ORDER BY version DESC",
+ 'result' => array()
+ ),
+ array(
+ 'query' => "SHOW SLAVE STATUS",
+ 'result' => array(
+ array(
+ 'Slave_IO_State' => 'running',
+ 'Master_Host' => 'locahost',
+ 'Master_User' => 'Master_User',
+ 'Master_Port' => '1002',
+ 'Connect_Retry' => 'Connect_Retry',
+ 'Master_Log_File' => 'Master_Log_File',
+ 'Read_Master_Log_Pos' => 'Read_Master_Log_Pos',
+ 'Relay_Log_File' => 'Relay_Log_File',
+ 'Relay_Log_Pos' => 'Relay_Log_Pos',
+ 'Relay_Master_Log_File' => 'Relay_Master_Log_File',
+ 'Slave_IO_Running' => 'NO',
+ 'Slave_SQL_Running' => 'NO',
+ 'Replicate_Do_DB' => 'Replicate_Do_DB',
+ 'Replicate_Ignore_DB' => 'Replicate_Ignore_DB',
+ 'Replicate_Do_Table' => 'Replicate_Do_Table',
+ 'Replicate_Ignore_Table' => 'Replicate_Ignore_Table',
+ 'Replicate_Wild_Do_Table' => 'Replicate_Wild_Do_Table',
+ 'Replicate_Wild_Ignore_Table' => 'Replicate_Wild_Ignore_Table',
+ 'Last_Errno' => 'Last_Errno',
+ 'Last_Error' => 'Last_Error',
+ 'Skip_Counter' => 'Skip_Counter',
+ 'Exec_Master_Log_Pos' => 'Exec_Master_Log_Pos',
+ 'Relay_Log_Space' => 'Relay_Log_Space',
+ 'Until_Condition' => 'Until_Condition',
+ 'Until_Log_File' => 'Until_Log_File',
+ 'Until_Log_Pos' => 'Until_Log_Pos',
+ 'Master_SSL_Allowed' => 'Master_SSL_Allowed',
+ 'Master_SSL_CA_File' => 'Master_SSL_CA_File',
+ 'Master_SSL_CA_Path' => 'Master_SSL_CA_Path',
+ 'Master_SSL_Cert' => 'Master_SSL_Cert',
+ 'Master_SSL_Cipher' => 'Master_SSL_Cipher',
+ 'Master_SSL_Key' => 'Master_SSL_Key',
+ 'Seconds_Behind_Master' => 'Seconds_Behind_Master',
+ )
+ )
+ ),
+ array(
+ 'query' => "SHOW MASTER STATUS",
+ 'result' => array(
+ array(
+ "File" => "master-bin.000030",
+ "Position" => "107",
+ "Binlog_Do_DB" => "Binlog_Do_DB",
+ "Binlog_Ignore_DB" => "Binlog_Ignore_DB",
+ )
+ )
+ ),
+ array(
+ 'query' => "SHOW GRANTS",
+ 'result' => array()
+ )
+);
+/**
+ * Current database.
+ */
+$GLOBALS['dummy_db'] = '';
+
+/* Some basic setup for dummy driver */
+$GLOBALS['userlink'] = 1;
+$GLOBALS['controllink'] = 2;
+$GLOBALS['cfg']['DBG']['sql'] = false;
+if (! defined('PMA_DRIZZLE')) {
+ define('PMA_DRIZZLE', 0);
+}
+
+/**
+ * Fake database driver for testing purposes
+ *
+ * It has hardcoded results for given queries what makes easy to use it
+ * in testsuite. Feel free to include other queries which your test will
+ * need.
+ *
+ * @package PhpMyAdmin-DBI
+ * @subpackage Dummy
+ */
+class PMA_DBI_Dummy implements PMA_DBI_Extension
+{
+ /**
+ * connects to the database server
+ *
+ * @param string $user mysql user name
+ * @param string $password mysql user password
+ * @param bool $is_controluser whether this is a control user connection
+ * @param array $server host/port/socket/persistent
+ * @param bool $auxiliary_connection (when true, don't go back to login if
+ * connection fails)
+ *
+ * @return mixed false on error or a mysqli object on success
+ */
+ public function connect(
+ $user, $password, $is_controluser = false, $server = null,
+ $auxiliary_connection = false
+ ) {
+ return true;
+ }
+
+ /**
+ * selects given database
+ *
+ * @param string $dbname name of db to select
+ * @param resource $link mysql link resource
+ *
+ * @return bool
+ */
+ public function selectDb($dbname, $link = null)
+ {
+ $GLOBALS['dummy_db'] = $dbname;
+ return true;
+ }
+
+ /**
+ * runs a query and returns the result
+ *
+ * @param string $query query to run
+ * @param resource $link mysql link resource
+ * @param int $options query options
+ *
+ * @return mixed
+ */
+ public function realQuery($query, $link = null, $options = 0)
+ {
+ $query = trim(preg_replace('/ */', ' ', str_replace("\n", ' ', $query)));
+ for ($i = 0; $i < count($GLOBALS['dummy_queries']); $i++) {
+ if ($GLOBALS['dummy_queries'][$i]['query'] == $query) {
+ $GLOBALS['dummy_queries'][$i]['pos'] = 0;
+ if (is_array($GLOBALS['dummy_queries'][$i]['result'])) {
+ return $i;
+ } else {
+ return false;
+ }
+ }
+ }
+ echo "Not supported query: $query\n";
+ return false;
+ }
+
+ /**
+ * Run the multi query and output the results
+ *
+ * @param object $link connection object
+ * @param string $query multi query statement to execute
+ *
+ * @return result collection | boolean(false)
+ */
+ public function realMultiQuery($link, $query)
+ {
+ return false;
+ }
+
+ /**
+ * returns result data from $result
+ *
+ * @param resource $result result MySQL result
+ *
+ * @return array
+ */
+ public function fetchAny($result)
+ {
+ $query_data = $GLOBALS['dummy_queries'][$result];
+ if ($query_data['pos'] >= count($query_data['result'])) {
+ return false;
+ }
+ $ret = $query_data['result'][$query_data['pos']];
+ $GLOBALS['dummy_queries'][$result]['pos'] += 1;
+ return $ret;
+ }
+
+ /**
+ * returns array of rows with associative and numeric keys from $result
+ *
+ * @param resource $result result MySQL result
+ *
+ * @return array
+ */
+ public function fetchArray($result)
+ {
+ $data = $this->fetchAny($result);
+ if (is_array($data)
+ && isset($GLOBALS['dummy_queries'][$result]['columns'])
+ ) {
+ foreach ($data as $key => $val) {
+ $data[$GLOBALS['dummy_queries'][$result]['columns'][$key]] = $val;
+ }
+ return $data;
+ }
+ return $data;
+ }
+
+ /**
+ * returns array of rows with associative keys from $result
+ *
+ * @param resource $result MySQL result
+ *
+ * @return array
+ */
+ public function fetchAssoc($result)
+ {
+ $data = $this->fetchAny($result);
+ if (is_array($data)
+ && isset($GLOBALS['dummy_queries'][$result]['columns'])
+ ) {
+ $ret = array();
+ foreach ($data as $key => $val) {
+ $ret[$GLOBALS['dummy_queries'][$result]['columns'][$key]] = $val;
+ }
+ return $ret;
+ }
+ return $data;
+ }
+
+ /**
+ * returns array of rows with numeric keys from $result
+ *
+ * @param resource $result MySQL result
+ *
+ * @return array
+ */
+ public function fetchRow($result)
+ {
+ $data = $this->fetchAny($result);
+ return $data;
+ }
+
+ /**
+ * Adjusts the result pointer to an arbitrary row in the result
+ *
+ * @param resource $result database result
+ * @param integer $offset offset to seek
+ *
+ * @return bool true on success, false on failure
+ */
+ public function dataSeek($result, $offset)
+ {
+ if ($offset > count($GLOBALS['dummy_queries'][$result]['result'])) {
+ return false;
+ }
+ $GLOBALS['dummy_queries'][$result]['pos'] = $offset;
+ return true;
+ }
+
+ /**
+ * Frees memory associated with the result
+ *
+ * @param resource $result database result
+ *
+ * @return void
+ */
+ public function freeResult($result)
+ {
+ return;
+ }
+
+ /**
+ * Check if there are any more query results from a multi query
+ *
+ * @param object $link the connection object
+ *
+ * @return bool false
+ */
+ public function moreResults($link = null)
+ {
+ return false;
+ }
+
+ /**
+ * Prepare next result from multi_query
+ *
+ * @param object $link the connection object
+ *
+ * @return boolean false
+ */
+ public function nextResult($link = null)
+ {
+ return false;
+ }
+
+ /**
+ * Store the result returned from multi query
+ *
+ * @return mixed false when empty results / result set when not empty
+ */
+ public function storeResult()
+ {
+ return false;
+ }
+
+ /**
+ * Returns a string representing the type of connection used
+ *
+ * @param object $link mysql link
+ *
+ * @return string type of connection used
+ */
+ public function getHostInfo($link = null)
+ {
+ return '';
+ }
+
+ /**
+ * Returns the version of the MySQL protocol used
+ *
+ * @param object $link mysql link
+ *
+ * @return integer version of the MySQL protocol used
+ */
+ public function getProtoInfo($link = null)
+ {
+ return -1;
+ }
+
+ /**
+ * returns a string that represents the client library version
+ *
+ * @return string MySQL client library version
+ */
+ public function getClientInfo()
+ {
+ return '';
+ }
+
+ /**
+ * returns last error message or false if no errors occurred
+ *
+ * @param object $link connection link
+ *
+ * @return string|bool $error or false
+ */
+ public function getError($link = null)
+ {
+ return false;
+ }
+
+ /**
+ * returns the number of rows returned by last query
+ *
+ * @param resource $result MySQL result
+ *
+ * @return string|int
+ */
+ public function numRows($result)
+ {
+ if (!is_bool($result)) {
+ return count($GLOBALS['dummy_queries'][$result]['result']);
+ } else {
+ return 0;
+ }
+ }
+
+ /**
+ * returns last inserted auto_increment id for given $link
+ * or $GLOBALS['userlink']
+ *
+ * @param object $link the connection object
+ *
+ * @return string|int
+ */
+ public function insertId($link = null)
+ {
+ return -1;
+ }
+
+ /**
+ * returns the number of rows affected by last query
+ *
+ * @param resource $link the mysql object
+ * @param bool $get_from_cache whether to retrieve from cache
+ *
+ * @return string|int
+ */
+ public function affectedRows($link = null, $get_from_cache = true)
+ {
+ return 0;
+ }
+
+ /**
+ * returns metainfo for fields in $result
+ *
+ * @param object $result result set identifier
+ *
+ * @return array meta info for fields in $result
+ */
+ public function getFieldsMeta($result)
+ {
+ return array();
+ }
+
+ /**
+ * return number of fields in given $result
+ *
+ * @param resource $result MySQL result
+ *
+ * @return int field count
+ */
+ public function numFields($result)
+ {
+ if (isset($GLOBALS['dummy_queries'][$result]['columns'])) {
+ return count($GLOBALS['dummy_queries'][$result]['columns']);
+ } else {
+ return 0;
+ }
+ }
+
+ /**
+ * returns the length of the given field $i in $result
+ *
+ * @param object $result result set identifier
+ * @param int $i field
+ *
+ * @return int length of field
+ */
+ public function fieldLen($result, $i)
+ {
+ return -1;
+ }
+
+ /**
+ * returns name of $i. field in $result
+ *
+ * @param object $result result set identifier
+ * @param int $i field
+ *
+ * @return string name of $i. field in $result
+ */
+ public function fieldName($result, $i)
+ {
+ return '';
+ }
+
+ /**
+ * returns concatenated string of human readable field flags
+ *
+ * @param object $result result set identifier
+ * @param int $i field
+ *
+ * @return string field flags
+ */
+ public function fieldFlags($result, $i)
+ {
+ return '';
+ }
+}
+?>
diff --git a/libraries/dbi/DBIExtension.int.php b/libraries/dbi/DBIExtension.int.php
new file mode 100644
index 0000000000..0e6ec3fea6
--- /dev/null
+++ b/libraries/dbi/DBIExtension.int.php
@@ -0,0 +1,248 @@
+<?php
+/**
+ * Contract for every database extension supported by phpMyAdmin
+ *
+ * @package PhpMyAdmin-DBI
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ * Contract for every database extension supported by phpMyAdmin
+ *
+ * @package PhpMyAdmin-DBI
+ */
+interface PMA_DBI_Extension
+{
+ /**
+ * connects to the database server
+ *
+ * @param string $user user name
+ * @param string $password user password
+ * @param bool $is_controluser whether this is a control user connection
+ * @param array $server host/port/socket/persistent
+ * @param bool $auxiliary_connection (when true, don't go back to login if
+ * connection fails)
+ *
+ * @return mixed false on error or a connection object on success
+ */
+ public function connect(
+ $user, $password, $is_controluser = false, $server = null,
+ $auxiliary_connection = false
+ );
+
+ /**
+ * selects given database
+ *
+ * @param string $dbname database name to select
+ * @param object $link connection object
+ *
+ * @return boolean
+ */
+ public function selectDb($dbname, $link = null);
+
+ /**
+ * runs a query and returns the result
+ *
+ * @param string $query query to execute
+ * @param object $link connection object
+ * @param int $options query options
+ *
+ * @return object|bool result
+ */
+ public function realQuery($query, $link, $options);
+
+ /**
+ * Run the multi query and output the results
+ *
+ * @param object $link connection object
+ * @param string $query multi query statement to execute
+ *
+ * @return result collection | boolean(false)
+ */
+ public function realMultiQuery($link, $query);
+
+ /**
+ * returns array of rows with associative and numeric keys from $result
+ *
+ * @param object $result result set identifier
+ *
+ * @return array
+ */
+ public function fetchArray($result);
+
+ /**
+ * returns array of rows with associative keys from $result
+ *
+ * @param object $result result set identifier
+ *
+ * @return array
+ */
+ public function fetchAssoc($result);
+
+ /**
+ * returns array of rows with numeric keys from $result
+ *
+ * @param object $result result set identifier
+ *
+ * @return array
+ */
+ public function fetchRow($result);
+
+ /**
+ * Adjusts the result pointer to an arbitrary row in the result
+ *
+ * @param object $result database result
+ * @param integer $offset offset to seek
+ *
+ * @return bool true on success, false on failure
+ */
+ public function dataSeek($result, $offset);
+
+ /**
+ * Frees memory associated with the result
+ *
+ * @param object $result database result
+ *
+ * @return void
+ */
+ public function freeResult($result);
+
+ /**
+ * Check if there are any more query results from a multi query
+ *
+ * @param object $link the connection object
+ *
+ * @return bool true or false
+ */
+ public function moreResults($link = null);
+
+ /**
+ * Prepare next result from multi_query
+ *
+ * @param object $link the connection object
+ *
+ * @return bool true or false
+ */
+ public function nextResult($link = null);
+
+ /**
+ * Store the result returned from multi query
+ *
+ * @return mixed false when empty results / result set when not empty
+ */
+ public function storeResult();
+
+ /**
+ * Returns a string representing the type of connection used
+ *
+ * @param object $link mysql link
+ *
+ * @return string type of connection used
+ */
+ public function getHostInfo($link = null);
+
+ /**
+ * Returns the version of the MySQL protocol used
+ *
+ * @param object $link mysql link
+ *
+ * @return integer version of the MySQL protocol used
+ */
+ public function getProtoInfo($link = null);
+
+ /**
+ * returns a string that represents the client library version
+ *
+ * @return string MySQL client library version
+ */
+ public function getClientInfo();
+
+ /**
+ * returns last error message or false if no errors occurred
+ *
+ * @param object $link connection link
+ *
+ * @return string|bool $error or false
+ */
+ public function getError($link = null);
+
+ /**
+ * returns the number of rows returned by last query
+ *
+ * @param object $result result set identifier
+ *
+ * @return string|int
+ */
+ public function numRows($result);
+
+ /**
+ * returns last inserted auto_increment id for given $link
+ * or $GLOBALS['userlink']
+ *
+ * @param object $link the connection object
+ *
+ * @return string|int
+ */
+ public function insertId($link = null);
+
+ /**
+ * returns the number of rows affected by last query
+ *
+ * @param object $link the connection object
+ * @param bool $get_from_cache whether to retrieve from cache
+ *
+ * @return string|int
+ */
+ public function affectedRows($link = null, $get_from_cache = true);
+
+ /**
+ * returns metainfo for fields in $result
+ *
+ * @param object $result result set identifier
+ *
+ * @return array meta info for fields in $result
+ */
+ public function getFieldsMeta($result);
+
+ /**
+ * return number of fields in given $result
+ *
+ * @param object $result result set identifier
+ *
+ * @return int field count
+ */
+ public function numFields($result);
+
+ /**
+ * returns the length of the given field $i in $result
+ *
+ * @param object $result result set identifier
+ * @param int $i field
+ *
+ * @return int length of field
+ */
+ public function fieldLen($result, $i);
+
+ /**
+ * returns name of $i. field in $result
+ *
+ * @param object $result result set identifier
+ * @param int $i field
+ *
+ * @return string name of $i. field in $result
+ */
+ public function fieldName($result, $i);
+
+ /**
+ * returns concatenated string of human readable field flags
+ *
+ * @param object $result result set identifier
+ * @param int $i field
+ *
+ * @return string field flags
+ */
+ public function fieldFlags($result, $i);
+}
+?> \ No newline at end of file
diff --git a/libraries/dbi/DBIMysql.class.php b/libraries/dbi/DBIMysql.class.php
new file mode 100644
index 0000000000..d67c64b90f
--- /dev/null
+++ b/libraries/dbi/DBIMysql.class.php
@@ -0,0 +1,570 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Interface to the classic MySQL extension
+ *
+ * @package PhpMyAdmin-DBI
+ * @subpackage MySQL
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+require_once './libraries/logging.lib.php';
+require_once './libraries/dbi/DBIExtension.int.php';
+
+/**
+ * MySQL client API
+ */
+if (! defined('PMA_MYSQL_CLIENT_API')) {
+ $client_api = explode('.', mysql_get_client_info());
+ define(
+ 'PMA_MYSQL_CLIENT_API',
+ (int)sprintf(
+ '%d%02d%02d',
+ $client_api[0], $client_api[1], intval($client_api[2])
+ )
+ );
+ unset($client_api);
+}
+
+/**
+ * Interface to the classic MySQL extension
+ *
+ * @package PhpMyAdmin-DBI
+ * @subpackage MySQL
+ */
+class PMA_DBI_Mysql implements PMA_DBI_Extension
+{
+ /**
+ * Helper function for connecting to the database server
+ *
+ * @param array $server host/port/socket
+ * @param string $user mysql user name
+ * @param string $password mysql user password
+ * @param int $client_flags client flags of connection
+ * @param bool $persistent whether to use peristent connection
+ *
+ * @return mixed false on error or a mysql connection resource on success
+ */
+ private function _realConnect($server, $user, $password, $client_flags,
+ $persistent = false
+ ) {
+ global $cfg;
+
+ if (empty($client_flags)) {
+ if ($cfg['PersistentConnections'] || $persistent) {
+ $link = @mysql_pconnect($server, $user, $password);
+ } else {
+ $link = @mysql_connect($server, $user, $password);
+ }
+ } else {
+ if ($cfg['PersistentConnections'] || $persistent) {
+ $link = @mysql_pconnect($server, $user, $password, $client_flags);
+ } else {
+ $link = @mysql_connect(
+ $server, $user, $password, false, $client_flags
+ );
+ }
+ }
+
+ return $link;
+ }
+
+ /**
+ * Run the multi query and output the results
+ *
+ * @param mysqli $link mysqli object
+ * @param string $query multi query statement to execute
+ *
+ * @return boolean false always false since mysql extention not support
+ * for multi query executions
+ */
+ public function realMultiQuery($link, $query)
+ {
+ // N.B.: PHP's 'mysql' extension does not support
+ // multi_queries so this function will always
+ // return false. Use the 'mysqli' extension, if
+ // you need support for multi_queries.
+ return false;
+ }
+
+ /**
+ * connects to the database server
+ *
+ * @param string $user mysql user name
+ * @param string $password mysql user password
+ * @param bool $is_controluser whether this is a control user connection
+ * @param array $server host/port/socket/persistent
+ * @param bool $auxiliary_connection (when true, don't go back to login if
+ * connection fails)
+ *
+ * @return mixed false on error or a mysqli object on success
+ */
+ public function connect(
+ $user, $password, $is_controluser = false, $server = null,
+ $auxiliary_connection = false
+ ) {
+ global $cfg;
+
+ if ($server) {
+ $server_port = (empty($server['port']))
+ ? ''
+ : ':' . (int)$server['port'];
+ $server_socket = (empty($server['socket']))
+ ? ''
+ : ':' . $server['socket'];
+ } else {
+ $server_port = (empty($cfg['Server']['port']))
+ ? ''
+ : ':' . (int)$cfg['Server']['port'];
+ $server_socket = (empty($cfg['Server']['socket']))
+ ? ''
+ : ':' . $cfg['Server']['socket'];
+ }
+
+ $client_flags = 0;
+
+ // always use CLIENT_LOCAL_FILES as defined in mysql_com.h
+ // for the case where the client library was not compiled
+ // with --enable-local-infile
+ $client_flags |= 128;
+
+ /* Optionally compress connection */
+ if (defined('MYSQL_CLIENT_COMPRESS') && $cfg['Server']['compress']) {
+ $client_flags |= MYSQL_CLIENT_COMPRESS;
+ }
+
+ /* Optionally enable SSL */
+ if (defined('MYSQL_CLIENT_SSL') && $cfg['Server']['ssl']) {
+ $client_flags |= MYSQL_CLIENT_SSL;
+ }
+
+ if (! $server) {
+ $link = $this->_realConnect(
+ $cfg['Server']['host'] . $server_port . $server_socket,
+ $user, $password, empty($client_flags) ? null : $client_flags
+ );
+
+ // Retry with empty password if we're allowed to
+ if (empty($link) && $cfg['Server']['nopassword'] && ! $is_controluser) {
+ $link = $this->_realConnect(
+ $cfg['Server']['host'] . $server_port . $server_socket,
+ $user, '', empty($client_flags) ? null : $client_flags
+ );
+ }
+ } else {
+ if (!isset($server['host'])) {
+ $link = $this->_realConnect($server_socket, $user, $password, null);
+ } else {
+ $link = $this->_realConnect(
+ $server['host'] . $server_port . $server_socket,
+ $user, $password, null
+ );
+ }
+ }
+ if (empty($link)) {
+ if ($is_controluser) {
+ trigger_error(
+ __(
+ 'Connection for controluser as defined'
+ . ' in your configuration failed.'
+ ),
+ E_USER_WARNING
+ );
+ return false;
+ }
+ // we could be calling $GLOBALS['dbi']->connect() to connect to another
+ // server, for example in the Synchronize feature, so do not
+ // go back to main login if it fails
+ if (! $auxiliary_connection) {
+ PMA_logUser($user, 'mysql-denied');
+ global $auth_plugin;
+ $auth_plugin->authFails();
+ } else {
+ return false;
+ }
+ } // end if
+ if (! $server) {
+ $GLOBALS['dbi']->postConnect($link, $is_controluser);
+ }
+ return $link;
+ }
+
+ /**
+ * selects given database
+ *
+ * @param string $dbname name of db to select
+ * @param resource $link mysql link resource
+ *
+ * @return bool
+ */
+ public function selectDb($dbname, $link = null)
+ {
+ if (empty($link)) {
+ if (isset($GLOBALS['userlink'])) {
+ $link = $GLOBALS['userlink'];
+ } else {
+ return false;
+ }
+ }
+ return mysql_select_db($dbname, $link);
+ }
+
+ /**
+ * runs a query and returns the result
+ *
+ * @param string $query query to run
+ * @param resource $link mysql link resource
+ * @param int $options query options
+ *
+ * @return mixed
+ */
+ public function realQuery($query, $link, $options)
+ {
+ if ($options == ($options | PMA_DatabaseInterface::QUERY_STORE)) {
+ return mysql_query($query, $link);
+ } elseif ($options == ($options | PMA_DatabaseInterface::QUERY_UNBUFFERED)) {
+ return mysql_unbuffered_query($query, $link);
+ } else {
+ return mysql_query($query, $link);
+ }
+ }
+
+ /**
+ * returns array of rows with associative and numeric keys from $result
+ *
+ * @param resource $result result MySQL result
+ *
+ * @return array
+ */
+ public function fetchArray($result)
+ {
+ return mysql_fetch_array($result, MYSQL_BOTH);
+ }
+
+ /**
+ * returns array of rows with associative keys from $result
+ *
+ * @param resource $result MySQL result
+ *
+ * @return array
+ */
+ public function fetchAssoc($result)
+ {
+ return mysql_fetch_array($result, MYSQL_ASSOC);
+ }
+
+ /**
+ * returns array of rows with numeric keys from $result
+ *
+ * @param resource $result MySQL result
+ *
+ * @return array
+ */
+ public function fetchRow($result)
+ {
+ return mysql_fetch_array($result, MYSQL_NUM);
+ }
+
+ /**
+ * Adjusts the result pointer to an arbitrary row in the result
+ *
+ * @param resource $result database result
+ * @param integer $offset offset to seek
+ *
+ * @return bool true on success, false on failure
+ */
+ public function dataSeek($result, $offset)
+ {
+ return mysql_data_seek($result, $offset);
+ }
+
+ /**
+ * Frees memory associated with the result
+ *
+ * @param resource $result database result
+ *
+ * @return void
+ */
+ public function freeResult($result)
+ {
+ if (is_resource($result) && get_resource_type($result) === 'mysql result') {
+ mysql_free_result($result);
+ }
+ }
+
+ /**
+ * Check if there are any more query results from a multi query
+ *
+ * @param object $link the connection object
+ *
+ * @return bool false
+ */
+ public function moreResults($link = null)
+ {
+ // N.B.: PHP's 'mysql' extension does not support
+ // multi_queries so this function will always
+ // return false. Use the 'mysqli' extension, if
+ // you need support for multi_queries.
+ return false;
+ }
+
+ /**
+ * Prepare next result from multi_query
+ *
+ * @param object $link the connection object
+ *
+ * @return boolean false
+ */
+ public function nextResult($link = null)
+ {
+ // N.B.: PHP's 'mysql' extension does not support
+ // multi_queries so this function will always
+ // return false. Use the 'mysqli' extension, if
+ // you need support for multi_queries.
+ return false;
+ }
+
+ /**
+ * Returns a string representing the type of connection used
+ *
+ * @param resource $link mysql link
+ *
+ * @return string type of connection used
+ */
+ public function getHostInfo($link = null)
+ {
+ if (null === $link) {
+ if (isset($GLOBALS['userlink'])) {
+ $link = $GLOBALS['userlink'];
+ } else {
+ return false;
+ }
+ }
+ return mysql_get_host_info($link);
+ }
+
+ /**
+ * Returns the version of the MySQL protocol used
+ *
+ * @param resource $link mysql link
+ *
+ * @return int version of the MySQL protocol used
+ */
+ public function getProtoInfo($link = null)
+ {
+ if (null === $link) {
+ if (isset($GLOBALS['userlink'])) {
+ $link = $GLOBALS['userlink'];
+ } else {
+ return false;
+ }
+ }
+ return mysql_get_proto_info($link);
+ }
+
+ /**
+ * returns a string that represents the client library version
+ *
+ * @return string MySQL client library version
+ */
+ public function getClientInfo()
+ {
+ return mysql_get_client_info();
+ }
+
+ /**
+ * returns last error message or false if no errors occurred
+ *
+ * @param resource $link mysql link
+ *
+ * @return string|bool $error or false
+ */
+ public function getError($link = null)
+ {
+ $GLOBALS['errno'] = 0;
+
+ /* Treat false same as null because of controllink */
+ if ($link === false) {
+ $link = null;
+ }
+
+ if (null === $link && isset($GLOBALS['userlink'])) {
+ $link =& $GLOBALS['userlink'];
+
+ // Do not stop now. On the initial connection, we don't have a $link,
+ // we don't have a $GLOBALS['userlink'], but we can catch the error code
+ // } else {
+ // return false;
+ }
+
+ if (null !== $link && false !== $link) {
+ $error_number = mysql_errno($link);
+ $error_message = mysql_error($link);
+ } else {
+ $error_number = mysql_errno();
+ $error_message = mysql_error();
+ }
+ if (0 == $error_number) {
+ return false;
+ }
+
+ // keep the error number for further check after
+ // the call to getError()
+ $GLOBALS['errno'] = $error_number;
+
+ return $GLOBALS['dbi']->formatError($error_number, $error_message);
+ }
+
+ /**
+ * returns the number of rows returned by last query
+ *
+ * @param resource $result MySQL result
+ *
+ * @return string|int
+ */
+ public function numRows($result)
+ {
+ if (!is_bool($result)) {
+ return mysql_num_rows($result);
+ } else {
+ return 0;
+ }
+ }
+
+ /**
+ * returns last inserted auto_increment id for given $link
+ * or $GLOBALS['userlink']
+ *
+ * @param resource $link the mysql object
+ *
+ * @return string|int
+ */
+ public function insertId($link = null)
+ {
+ if (empty($link)) {
+ if (isset($GLOBALS['userlink'])) {
+ $link = $GLOBALS['userlink'];
+ } else {
+ return false;
+ }
+ }
+ // If the primary key is BIGINT we get an incorrect result
+ // (sometimes negative, sometimes positive)
+ // and in the present function we don't know if the PK is BIGINT
+ // so better play safe and use LAST_INSERT_ID()
+ //
+ return $GLOBALS['dbi']->fetchValue('SELECT LAST_INSERT_ID();', 0, 0, $link);
+ }
+
+ /**
+ * returns the number of rows affected by last query
+ *
+ * @param resource $link the mysql object
+ * @param bool $get_from_cache whether to retrieve from cache
+ *
+ * @return string|int
+ */
+ public function affectedRows($link = null, $get_from_cache = true)
+ {
+ if (empty($link)) {
+ if (isset($GLOBALS['userlink'])) {
+ $link = $GLOBALS['userlink'];
+ } else {
+ return false;
+ }
+ }
+
+ if ($get_from_cache) {
+ return $GLOBALS['cached_affected_rows'];
+ } else {
+ return mysql_affected_rows($link);
+ }
+ }
+
+ /**
+ * returns metainfo for fields in $result
+ *
+ * @param resource $result MySQL result
+ *
+ * @return array meta info for fields in $result
+ *
+ * @todo add missing keys like in mysqli_query (decimals)
+ */
+ public function getFieldsMeta($result)
+ {
+ $fields = array();
+ $num_fields = mysql_num_fields($result);
+ for ($i = 0; $i < $num_fields; $i++) {
+ $field = mysql_fetch_field($result, $i);
+ $field->flags = mysql_field_flags($result, $i);
+ $field->orgtable = mysql_field_table($result, $i);
+ $field->orgname = mysql_field_name($result, $i);
+ $fields[] = $field;
+ }
+ return $fields;
+ }
+
+ /**
+ * return number of fields in given $result
+ *
+ * @param resource $result MySQL result
+ *
+ * @return int field count
+ */
+ public function numFields($result)
+ {
+ return mysql_num_fields($result);
+ }
+
+ /**
+ * returns the length of the given field $i in $result
+ *
+ * @param resource $result MySQL result
+ * @param int $i field
+ *
+ * @return int length of field
+ */
+ public function fieldLen($result, $i)
+ {
+ return mysql_field_len($result, $i);
+ }
+
+ /**
+ * returns name of $i. field in $result
+ *
+ * @param resource $result MySQL result
+ * @param int $i field
+ *
+ * @return string name of $i. field in $result
+ */
+ public function fieldName($result, $i)
+ {
+ return mysql_field_name($result, $i);
+ }
+
+ /**
+ * returns concatenated string of human readable field flags
+ *
+ * @param resource $result MySQL result
+ * @param int $i field
+ *
+ * @return string field flags
+ */
+ public function fieldFlags($result, $i)
+ {
+ return mysql_field_flags($result, $i);
+ }
+
+ /**
+ * Store the result returned from multi query
+ *
+ * @return false
+ */
+ public function storeResult()
+ {
+ return false;
+ }
+}
+?> \ No newline at end of file
diff --git a/libraries/dbi/DBIMysqli.class.php b/libraries/dbi/DBIMysqli.class.php
new file mode 100644
index 0000000000..31a389cb74
--- /dev/null
+++ b/libraries/dbi/DBIMysqli.class.php
@@ -0,0 +1,764 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Interface to the improved MySQL extension (MySQLi)
+ *
+ * @package PhpMyAdmin-DBI
+ * @subpackage MySQLi
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+require_once './libraries/logging.lib.php';
+require_once './libraries/dbi/DBIExtension.int.php';
+
+/**
+ * MySQL client API
+ */
+if (!defined('PMA_MYSQL_CLIENT_API')) {
+ $client_api = explode('.', mysqli_get_client_info());
+ define(
+ 'PMA_MYSQL_CLIENT_API',
+ (int)sprintf(
+ '%d%02d%02d',
+ $client_api[0], $client_api[1], intval($client_api[2])
+ )
+ );
+ unset($client_api);
+}
+
+/**
+ * some PHP versions are reporting extra messages like "No index used in query"
+ */
+
+mysqli_report(MYSQLI_REPORT_OFF);
+
+/**
+ * some older mysql client libs are missing these constants ...
+ */
+if (! defined('MYSQLI_BINARY_FLAG')) {
+ define('MYSQLI_BINARY_FLAG', 128);
+}
+
+/**
+ * @see http://bugs.php.net/36007
+ */
+if (! defined('MYSQLI_TYPE_NEWDECIMAL')) {
+ define('MYSQLI_TYPE_NEWDECIMAL', 246);
+}
+if (! defined('MYSQLI_TYPE_BIT')) {
+ define('MYSQLI_TYPE_BIT', 16);
+}
+
+// for Drizzle
+if (! defined('MYSQLI_TYPE_VARCHAR')) {
+ define('MYSQLI_TYPE_VARCHAR', 15);
+}
+
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Interface to the improved MySQL extension (MySQLi)
+ *
+ * @package PhpMyAdmin-DBI
+ * @subpackage MySQLi
+ */
+class PMA_DBI_Mysqli implements PMA_DBI_Extension
+{
+ /**
+ * Helper function for connecting to the database server
+ *
+ * @param mysqli $link connection link
+ * @param string $host mysql hostname
+ * @param string $user mysql user name
+ * @param string $password mysql user password
+ * @param string $dbname database name
+ * @param int $server_port server port
+ * @param string $server_socket server socket
+ * @param int $client_flags client flags of connection
+ * @param bool $persistent whether to use peristent connection
+ *
+ * @return bool
+ */
+ private function _realConnect(
+ $link, $host, $user, $password, $dbname, $server_port,
+ $server_socket, $client_flags = null, $persistent = false
+ ) {
+ global $cfg;
+
+ // mysqli persistent connections
+ if ($cfg['PersistentConnections'] || $persistent) {
+ $host = 'p:' . $host;
+ }
+
+ if ($client_flags === null) {
+ return @mysqli_real_connect(
+ $link,
+ $host,
+ $user,
+ $password,
+ $dbname,
+ $server_port,
+ $server_socket
+ );
+ } else {
+ return @mysqli_real_connect(
+ $link,
+ $host,
+ $user,
+ $password,
+ $dbname,
+ $server_port,
+ $server_socket,
+ $client_flags
+ );
+ }
+ }
+
+ /**
+ * connects to the database server
+ *
+ * @param string $user mysql user name
+ * @param string $password mysql user password
+ * @param bool $is_controluser whether this is a control user connection
+ * @param array $server host/port/socket/persistent
+ * @param bool $auxiliary_connection (when true, don't go back to login if
+ * connection fails)
+ *
+ * @return mixed false on error or a mysqli object on success
+ */
+ public function connect(
+ $user, $password, $is_controluser = false, $server = null,
+ $auxiliary_connection = false
+ ) {
+ global $cfg;
+
+ if ($server) {
+ $server_port = (empty($server['port']))
+ ? false
+ : (int)$server['port'];
+ $server_socket = (empty($server['socket']))
+ ? ''
+ : $server['socket'];
+ $server['host'] = (empty($server['host']))
+ ? 'localhost'
+ : $server['host'];
+ } else {
+ $server_port = (empty($cfg['Server']['port']))
+ ? false
+ : (int) $cfg['Server']['port'];
+ $server_socket = (empty($cfg['Server']['socket']))
+ ? null
+ : $cfg['Server']['socket'];
+ }
+
+ // NULL enables connection to the default socket
+
+ $link = mysqli_init();
+
+ mysqli_options($link, MYSQLI_OPT_LOCAL_INFILE, true);
+
+ $client_flags = 0;
+
+ /* Optionally compress connection */
+ if ($cfg['Server']['compress'] && defined('MYSQLI_CLIENT_COMPRESS')) {
+ $client_flags |= MYSQLI_CLIENT_COMPRESS;
+ }
+
+ /* Optionally enable SSL */
+ if ($cfg['Server']['ssl'] && defined('MYSQLI_CLIENT_SSL')) {
+ mysqli_ssl_set(
+ $link,
+ $cfg['Server']['ssl_key'],
+ $cfg['Server']['ssl_cert'],
+ $cfg['Server']['ssl_ca'],
+ $cfg['Server']['ssl_ca_path'],
+ $cfg['Server']['ssl_ciphers']
+ );
+ $client_flags |= MYSQLI_CLIENT_SSL;
+ }
+
+ if (! $server) {
+ $return_value = @$this->_realConnect(
+ $link,
+ $cfg['Server']['host'],
+ $user,
+ $password,
+ false,
+ $server_port,
+ $server_socket,
+ $client_flags
+ );
+ // Retry with empty password if we're allowed to
+ if ($return_value == false
+ && isset($cfg['Server']['nopassword'])
+ && $cfg['Server']['nopassword']
+ && ! $is_controluser
+ ) {
+ $return_value = @$this->_realConnect(
+ $link,
+ $cfg['Server']['host'],
+ $user,
+ '',
+ false,
+ $server_port,
+ $server_socket,
+ $client_flags
+ );
+ }
+ } else {
+ $return_value = @$this->_realConnect(
+ $link,
+ $server['host'],
+ $user,
+ $password,
+ false,
+ $server_port,
+ $server_socket
+ );
+ }
+
+ if ($return_value == false) {
+ if ($is_controluser) {
+ trigger_error(
+ __('Connection for controluser as defined in your configuration failed.'),
+ E_USER_WARNING
+ );
+ return false;
+ }
+ // we could be calling $GLOBALS['dbi']->connect() to connect to another
+ // server, for example in the Synchronize feature, so do not
+ // go back to main login if it fails
+ if (! $auxiliary_connection) {
+ PMA_logUser($user, 'mysql-denied');
+ global $auth_plugin;
+ $auth_plugin->authFails();
+ } else {
+ return false;
+ }
+ } else {
+ $GLOBALS['dbi']->postConnect($link, $is_controluser);
+ }
+
+ return $link;
+ }
+
+ /**
+ * selects given database
+ *
+ * @param string $dbname database name to select
+ * @param mysqli $link the mysqli object
+ *
+ * @return boolean
+ */
+ public function selectDb($dbname, $link = null)
+ {
+ if (empty($link)) {
+ if (isset($GLOBALS['userlink'])) {
+ $link = $GLOBALS['userlink'];
+ } else {
+ return false;
+ }
+ }
+ return mysqli_select_db($link, $dbname);
+ }
+
+ /**
+ * runs a query and returns the result
+ *
+ * @param string $query query to execute
+ * @param mysqli $link mysqli object
+ * @param int $options query options
+ *
+ * @return mysqli_result|bool
+ */
+ public function realQuery($query, $link, $options)
+ {
+ if ($options == ($options | PMA_DatabaseInterface::QUERY_STORE)) {
+ $method = MYSQLI_STORE_RESULT;
+ } elseif ($options == ($options | PMA_DatabaseInterface::QUERY_UNBUFFERED)) {
+ $method = MYSQLI_USE_RESULT;
+ } else {
+ $method = 0;
+ }
+
+ return mysqli_query($link, $query, $method);
+ }
+
+ /**
+ * Run the multi query and output the results
+ *
+ * @param mysqli $link mysqli object
+ * @param string $query multi query statement to execute
+ *
+ * @return mysqli_result collection | boolean(false)
+ */
+ public function realMultiQuery($link, $query)
+ {
+ return mysqli_multi_query($link, $query);
+ }
+
+ /**
+ * returns array of rows with associative and numeric keys from $result
+ *
+ * @param mysqli_result $result result set identifier
+ *
+ * @return array
+ */
+ public function fetchArray($result)
+ {
+ return mysqli_fetch_array($result, MYSQLI_BOTH);
+ }
+
+ /**
+ * returns array of rows with associative keys from $result
+ *
+ * @param mysqli_result $result result set identifier
+ *
+ * @return array
+ */
+ public function fetchAssoc($result)
+ {
+ return mysqli_fetch_array($result, MYSQLI_ASSOC);
+ }
+
+ /**
+ * returns array of rows with numeric keys from $result
+ *
+ * @param mysqli_result $result result set identifier
+ *
+ * @return array
+ */
+ public function fetchRow($result)
+ {
+ return mysqli_fetch_array($result, MYSQLI_NUM);
+ }
+
+ /**
+ * Adjusts the result pointer to an arbitrary row in the result
+ *
+ * @param resource $result database result
+ * @param integer $offset offset to seek
+ *
+ * @return bool true on success, false on failure
+ */
+ public function dataSeek($result, $offset)
+ {
+ return mysqli_data_seek($result, $offset);
+ }
+
+ /**
+ * Frees memory associated with the result
+ *
+ * @param mysqli_result $result database result
+ *
+ * @return void
+ */
+ public function freeResult($result)
+ {
+ if ($result instanceof mysqli_result) {
+ mysqli_free_result($result);
+ }
+ }
+
+ /**
+ * Check if there are any more query results from a multi query
+ *
+ * @param mysqli $link the mysqli object
+ *
+ * @return bool true or false
+ */
+ public function moreResults($link = null)
+ {
+ if (empty($link)) {
+ if (isset($GLOBALS['userlink'])) {
+ $link = $GLOBALS['userlink'];
+ } else {
+ return false;
+ }
+ }
+ return mysqli_more_results($link);
+ }
+
+ /**
+ * Prepare next result from multi_query
+ *
+ * @param mysqli $link the mysqli object
+ *
+ * @return bool true or false
+ */
+ public function nextResult($link = null)
+ {
+ if (empty($link)) {
+ if (isset($GLOBALS['userlink'])) {
+ $link = $GLOBALS['userlink'];
+ } else {
+ return false;
+ }
+ }
+ return mysqli_next_result($link);
+ }
+
+ /**
+ * Store the result returned from multi query
+ *
+ * @return mixed false when empty results / result set when not empty
+ */
+ public function storeResult()
+ {
+ if (isset($GLOBALS['userlink'])) {
+ $link = $GLOBALS['userlink'];
+ } else {
+ return false;
+ }
+ return mysqli_store_result($link);
+ }
+
+ /**
+ * Returns a string representing the type of connection used
+ *
+ * @param resource $link mysql link
+ *
+ * @return string type of connection used
+ */
+ public function getHostInfo($link = null)
+ {
+ if (null === $link) {
+ if (isset($GLOBALS['userlink'])) {
+ $link = $GLOBALS['userlink'];
+ } else {
+ return false;
+ }
+ }
+ return mysqli_get_host_info($link);
+ }
+
+ /**
+ * Returns the version of the MySQL protocol used
+ *
+ * @param resource $link mysql link
+ *
+ * @return integer version of the MySQL protocol used
+ */
+ public function getProtoInfo($link = null)
+ {
+ if (null === $link) {
+ if (isset($GLOBALS['userlink'])) {
+ $link = $GLOBALS['userlink'];
+ } else {
+ return false;
+ }
+ }
+ return mysqli_get_proto_info($link);
+ }
+
+ /**
+ * returns a string that represents the client library version
+ *
+ * @return string MySQL client library version
+ */
+ public function getClientInfo()
+ {
+ return mysqli_get_client_info();
+ }
+
+ /**
+ * returns last error message or false if no errors occurred
+ *
+ * @param resource $link mysql link
+ *
+ * @return string|bool $error or false
+ */
+ public function getError($link = null)
+ {
+ $GLOBALS['errno'] = 0;
+
+ /* Treat false same as null because of controllink */
+ if ($link === false) {
+ $link = null;
+ }
+
+ if (null === $link && isset($GLOBALS['userlink'])) {
+ $link =& $GLOBALS['userlink'];
+ // Do not stop now. We still can get the error code
+ // with mysqli_connect_errno()
+ }
+
+ if (null !== $link) {
+ $error_number = mysqli_errno($link);
+ $error_message = mysqli_error($link);
+ } else {
+ $error_number = mysqli_connect_errno();
+ $error_message = mysqli_connect_error();
+ }
+ if (0 == $error_number) {
+ return false;
+ }
+
+ // keep the error number for further check after
+ // the call to getError()
+ $GLOBALS['errno'] = $error_number;
+
+ return $GLOBALS['dbi']->formatError($error_number, $error_message);
+ }
+
+ /**
+ * returns the number of rows returned by last query
+ *
+ * @param mysqli_result $result result set identifier
+ *
+ * @return string|int
+ */
+ public function numRows($result)
+ {
+ // see the note for tryQuery();
+ if (!is_bool($result)) {
+ return @mysqli_num_rows($result);
+ } else {
+ return 0;
+ }
+ }
+
+ /**
+ * returns last inserted auto_increment id for given $link
+ * or $GLOBALS['userlink']
+ *
+ * @param mysqli $link the mysqli object
+ *
+ * @return string|int
+ */
+ public function insertId($link = null)
+ {
+ if (empty($link)) {
+ if (isset($GLOBALS['userlink'])) {
+ $link = $GLOBALS['userlink'];
+ } else {
+ return false;
+ }
+ }
+ // When no controluser is defined, using mysqli_insert_id($link)
+ // does not always return the last insert id due to a mixup with
+ // the tracking mechanism, but this works:
+ return $GLOBALS['dbi']->fetchValue('SELECT LAST_INSERT_ID();', 0, 0, $link);
+ // Curiously, this problem does not happen with the mysql extension but
+ // there is another problem with BIGINT primary keys so insertId()
+ // in the mysql extension also uses this logic.
+ }
+
+ /**
+ * returns the number of rows affected by last query
+ *
+ * @param mysqli $link the mysqli object
+ * @param bool $get_from_cache whether to retrieve from cache
+ *
+ * @return string|int
+ */
+ public function affectedRows($link = null, $get_from_cache = true)
+ {
+ if (empty($link)) {
+ if (isset($GLOBALS['userlink'])) {
+ $link = $GLOBALS['userlink'];
+ } else {
+ return false;
+ }
+ }
+ if ($get_from_cache) {
+ return $GLOBALS['cached_affected_rows'];
+ } else {
+ return mysqli_affected_rows($link);
+ }
+ }
+
+ /**
+ * returns metainfo for fields in $result
+ *
+ * @param mysqli_result $result result set identifier
+ *
+ * @return array meta info for fields in $result
+ */
+ public function getFieldsMeta($result)
+ {
+ // Build an associative array for a type look up
+ $typeAr = array();
+ $typeAr[MYSQLI_TYPE_DECIMAL] = 'real';
+ $typeAr[MYSQLI_TYPE_NEWDECIMAL] = 'real';
+ $typeAr[MYSQLI_TYPE_BIT] = 'int';
+ $typeAr[MYSQLI_TYPE_TINY] = 'int';
+ $typeAr[MYSQLI_TYPE_SHORT] = 'int';
+ $typeAr[MYSQLI_TYPE_LONG] = 'int';
+ $typeAr[MYSQLI_TYPE_FLOAT] = 'real';
+ $typeAr[MYSQLI_TYPE_DOUBLE] = 'real';
+ $typeAr[MYSQLI_TYPE_NULL] = 'null';
+ $typeAr[MYSQLI_TYPE_TIMESTAMP] = 'timestamp';
+ $typeAr[MYSQLI_TYPE_LONGLONG] = 'int';
+ $typeAr[MYSQLI_TYPE_INT24] = 'int';
+ $typeAr[MYSQLI_TYPE_DATE] = 'date';
+ $typeAr[MYSQLI_TYPE_TIME] = 'time';
+ $typeAr[MYSQLI_TYPE_DATETIME] = 'datetime';
+ $typeAr[MYSQLI_TYPE_YEAR] = 'year';
+ $typeAr[MYSQLI_TYPE_NEWDATE] = 'date';
+ $typeAr[MYSQLI_TYPE_ENUM] = 'unknown';
+ $typeAr[MYSQLI_TYPE_SET] = 'unknown';
+ $typeAr[MYSQLI_TYPE_TINY_BLOB] = 'blob';
+ $typeAr[MYSQLI_TYPE_MEDIUM_BLOB] = 'blob';
+ $typeAr[MYSQLI_TYPE_LONG_BLOB] = 'blob';
+ $typeAr[MYSQLI_TYPE_BLOB] = 'blob';
+ $typeAr[MYSQLI_TYPE_VAR_STRING] = 'string';
+ $typeAr[MYSQLI_TYPE_STRING] = 'string';
+ $typeAr[MYSQLI_TYPE_VARCHAR] = 'string'; // for Drizzle
+ // MySQL returns MYSQLI_TYPE_STRING for CHAR
+ // and MYSQLI_TYPE_CHAR === MYSQLI_TYPE_TINY
+ // so this would override TINYINT and mark all TINYINT as string
+ // https://sourceforge.net/p/phpmyadmin/bugs/2205/
+ //$typeAr[MYSQLI_TYPE_CHAR] = 'string';
+ $typeAr[MYSQLI_TYPE_GEOMETRY] = 'geometry';
+ $typeAr[MYSQLI_TYPE_BIT] = 'bit';
+
+ $fields = mysqli_fetch_fields($result);
+
+ // this happens sometimes (seen under MySQL 4.0.25)
+ if (!is_array($fields)) {
+ return false;
+ }
+
+ foreach ($fields as $k => $field) {
+ $fields[$k]->_type = $field->type;
+ $fields[$k]->type = $typeAr[$field->type];
+ $fields[$k]->_flags = $field->flags;
+ $fields[$k]->flags = $this->fieldFlags($result, $k);
+
+ // Enhance the field objects for mysql-extension compatibilty
+ //$flags = explode(' ', $fields[$k]->flags);
+ //array_unshift($flags, 'dummy');
+ $fields[$k]->multiple_key
+ = (int) (bool) ($fields[$k]->_flags & MYSQLI_MULTIPLE_KEY_FLAG);
+ $fields[$k]->primary_key
+ = (int) (bool) ($fields[$k]->_flags & MYSQLI_PRI_KEY_FLAG);
+ $fields[$k]->unique_key
+ = (int) (bool) ($fields[$k]->_flags & MYSQLI_UNIQUE_KEY_FLAG);
+ $fields[$k]->not_null
+ = (int) (bool) ($fields[$k]->_flags & MYSQLI_NOT_NULL_FLAG);
+ $fields[$k]->unsigned
+ = (int) (bool) ($fields[$k]->_flags & MYSQLI_UNSIGNED_FLAG);
+ $fields[$k]->zerofill
+ = (int) (bool) ($fields[$k]->_flags & MYSQLI_ZEROFILL_FLAG);
+ $fields[$k]->numeric
+ = (int) (bool) ($fields[$k]->_flags & MYSQLI_NUM_FLAG);
+ $fields[$k]->blob
+ = (int) (bool) ($fields[$k]->_flags & MYSQLI_BLOB_FLAG);
+ }
+ return $fields;
+ }
+
+ /**
+ * return number of fields in given $result
+ *
+ * @param mysqli_result $result result set identifier
+ *
+ * @return int field count
+ */
+ public function numFields($result)
+ {
+ return mysqli_num_fields($result);
+ }
+
+ /**
+ * returns the length of the given field $i in $result
+ *
+ * @param mysqli_result $result result set identifier
+ * @param int $i field
+ *
+ * @return int length of field
+ */
+ public function fieldLen($result, $i)
+ {
+ return mysqli_fetch_field_direct($result, $i)->length;
+ }
+
+ /**
+ * returns name of $i. field in $result
+ *
+ * @param mysqli_result $result result set identifier
+ * @param int $i field
+ *
+ * @return string name of $i. field in $result
+ */
+ public function fieldName($result, $i)
+ {
+ return mysqli_fetch_field_direct($result, $i)->name;
+ }
+
+ /**
+ * returns concatenated string of human readable field flags
+ *
+ * @param mysqli_result $result result set identifier
+ * @param int $i field
+ *
+ * @return string field flags
+ */
+ public function fieldFlags($result, $i)
+ {
+ $f = mysqli_fetch_field_direct($result, $i);
+ $type = $f->type;
+ $charsetnr = $f->charsetnr;
+ $f = $f->flags;
+ $flags = '';
+ if ($f & MYSQLI_UNIQUE_KEY_FLAG) {
+ $flags .= 'unique ';
+ }
+ if ($f & MYSQLI_NUM_FLAG) {
+ $flags .= 'num ';
+ }
+ if ($f & MYSQLI_PART_KEY_FLAG) {
+ $flags .= 'part_key ';
+ }
+ if ($f & MYSQLI_SET_FLAG) {
+ $flags .= 'set ';
+ }
+ if ($f & MYSQLI_TIMESTAMP_FLAG) {
+ $flags .= 'timestamp ';
+ }
+ if ($f & MYSQLI_AUTO_INCREMENT_FLAG) {
+ $flags .= 'auto_increment ';
+ }
+ if ($f & MYSQLI_ENUM_FLAG) {
+ $flags .= 'enum ';
+ }
+ // See http://dev.mysql.com/doc/refman/6.0/en/c-api-datatypes.html:
+ // to determine if a string is binary, we should not use MYSQLI_BINARY_FLAG
+ // but instead the charsetnr member of the MYSQL_FIELD
+ // structure. Watch out: some types like DATE returns 63 in charsetnr
+ // so we have to check also the type.
+ // Unfortunately there is no equivalent in the mysql extension.
+ if (($type == MYSQLI_TYPE_TINY_BLOB || $type == MYSQLI_TYPE_BLOB
+ || $type == MYSQLI_TYPE_MEDIUM_BLOB || $type == MYSQLI_TYPE_LONG_BLOB
+ || $type == MYSQLI_TYPE_VAR_STRING || $type == MYSQLI_TYPE_STRING)
+ && 63 == $charsetnr
+ ) {
+ $flags .= 'binary ';
+ }
+ if ($f & MYSQLI_ZEROFILL_FLAG) {
+ $flags .= 'zerofill ';
+ }
+ if ($f & MYSQLI_UNSIGNED_FLAG) {
+ $flags .= 'unsigned ';
+ }
+ if ($f & MYSQLI_BLOB_FLAG) {
+ $flags .= 'blob ';
+ }
+ if ($f & MYSQLI_MULTIPLE_KEY_FLAG) {
+ $flags .= 'multiple_key ';
+ }
+ if ($f & MYSQLI_UNIQUE_KEY_FLAG) {
+ $flags .= 'unique_key ';
+ }
+ if ($f & MYSQLI_PRI_KEY_FLAG) {
+ $flags .= 'primary_key ';
+ }
+ if ($f & MYSQLI_NOT_NULL_FLAG) {
+ $flags .= 'not_null ';
+ }
+ return trim($flags);
+ }
+}
+?>
diff --git a/libraries/dbi/drizzle-wrappers.lib.php b/libraries/dbi/drizzle-wrappers.lib.php
new file mode 100644
index 0000000000..a712e7d35c
--- /dev/null
+++ b/libraries/dbi/drizzle-wrappers.lib.php
@@ -0,0 +1,435 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Wrappers for Drizzle extension classes
+ *
+ * Drizzle extension exposes libdrizzle functions and requires user to have it in
+ * mind while using them.
+ * This wrapper is not complete and hides a lot of original functionality,
+ * but allows for easy usage of the drizzle PHP extension.
+ *
+ * @package PhpMyAdmin-DBI
+ * @subpackage Drizzle
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ * Workaround for crashing module
+ *
+ * @return void
+ *
+ * @todo drizzle module segfaults while freeing resources, often.
+ * This allows at least for some development
+ */
+function PMA_drizzleShutdownFlush()
+{
+ flush();
+}
+register_shutdown_function('PMA_drizzleShutdownFlush');
+
+/**
+ * Wrapper for Drizzle class
+ *
+ * @package PhpMyAdmin-DBI
+ * @subpackage Drizzle
+ */
+class PMA_Drizzle extends Drizzle
+{
+ /**
+ * Fetch mode: result rows contain column names
+ */
+ const FETCH_ASSOC = 1;
+ /**
+ * Fetch mode: result rows contain only numeric indices
+ */
+ const FETCH_NUM = 2;
+ /**
+ * Fetch mode: result rows have both column names and numeric indices
+ */
+ const FETCH_BOTH = 3;
+
+ /**
+ * Result buffering: entire result set is buffered upon execution
+ */
+ const BUFFER_RESULT = 1;
+ /**
+ * Result buffering: buffering occurs only on row level
+ */
+ const BUFFER_ROW = 2;
+
+ /**
+ * Constructor
+ */
+ public function __construct()
+ {
+ parent::__construct();
+ }
+
+ /**
+ * Creates a new database conection using TCP
+ *
+ * @param string $host Drizzle host
+ * @param integer $port Drizzle port
+ * @param string $user username
+ * @param string $password password
+ * @param string $db database name
+ * @param integer $options connection options
+ *
+ * @return PMA_DrizzleCon
+ */
+ public function addTcp($host, $port, $user, $password, $db, $options)
+ {
+ $dcon = parent::addTcp($host, $port, $user, $password, $db, $options);
+ return $dcon instanceof DrizzleCon
+ ? new PMA_DrizzleCon($dcon)
+ : $dcon;
+ }
+
+ /**
+ * Creates a new connection using unix domain socket
+ *
+ * @param string $uds socket
+ * @param string $user username
+ * @param string $password password
+ * @param string $db database name
+ * @param integer $options connection options
+ *
+ * @return PMA_DrizzleCon
+ */
+ public function addUds($uds, $user, $password, $db, $options)
+ {
+ $dcon = parent::addUds($uds, $user, $password, $db, $options);
+ return $dcon instanceof DrizzleCon
+ ? new PMA_DrizzleCon($dcon)
+ : $dcon;
+ }
+}
+
+/**
+ * Wrapper around DrizzleCon class
+ *
+ * Its main task is to wrap results with PMA_DrizzleResult class
+ *
+ * @package PhpMyAdmin-DBI
+ * @subpackage Drizzle
+ */
+class PMA_DrizzleCon
+{
+ /**
+ * Instance of DrizzleCon class
+ * @var DrizzleCon
+ */
+ private $_dcon;
+
+ /**
+ * Result of the most recent query
+ * @var PMA_DrizzleResult
+ */
+ private $_lastResult;
+
+ /**
+ * Constructor
+ *
+ * @param DrizzleCon $dcon connection handle
+ */
+ public function __construct(DrizzleCon $dcon)
+ {
+ $this->_dcon = $dcon;
+ }
+
+ /**
+ * Executes given query. Opens database connection if not already done.
+ *
+ * @param string $query query to execute
+ * @param int $bufferMode PMA_Drizzle::BUFFER_RESULT,PMA_Drizzle::BUFFER_ROW
+ * @param int $fetchMode PMA_Drizzle::FETCH_ASSOC, PMA_Drizzle::FETCH_NUM
+ * or PMA_Drizzle::FETCH_BOTH
+ *
+ * @return PMA_DrizzleResult
+ */
+ public function query($query, $bufferMode = PMA_Drizzle::BUFFER_RESULT,
+ $fetchMode = PMA_Drizzle::FETCH_ASSOC
+ ) {
+ $result = $this->_dcon->query($query);
+ if ($result instanceof DrizzleResult) {
+ $this->_lastResult = new PMA_DrizzleResult(
+ $result, $bufferMode, $fetchMode
+ );
+ return $this->_lastResult;
+ }
+ return $result;
+ }
+
+ /**
+ * Returns the number of rows affected by last query
+ *
+ * @return int|false
+ */
+ public function affectedRows()
+ {
+ return $this->_lastResult
+ ? $this->_lastResult->affectedRows()
+ : false;
+ }
+
+ /**
+ * Pass calls of undefined methods to DrizzleCon object
+ *
+ * @param string $method method name
+ * @param mixed $args method parameters
+ *
+ * @return mixed
+ */
+ public function __call($method, $args)
+ {
+ return call_user_func_array(array($this->_dcon, $method), $args);
+ }
+
+ /**
+ * Returns original Drizzle connection object
+ *
+ * @return DrizzleCon
+ */
+ public function getConnectionObject()
+ {
+ return $this->_dcon;
+ }
+}
+
+/**
+ * Wrapper around DrizzleResult.
+ *
+ * Allows for reading result rows as an associative array and hides complexity
+ * behind buffering.
+ *
+ * @package PhpMyAdmin-DBI
+ * @subpackage Drizzle
+ */
+class PMA_DrizzleResult
+{
+ /**
+ * Instamce of DrizzleResult class
+ * @var DrizzleResult
+ */
+ private $_dresult;
+ /**
+ * Fetch mode
+ * @var int
+ */
+ private $_fetchMode;
+ /**
+ * Buffering mode
+ * @var int
+ */
+ private $_bufferMode;
+
+ /**
+ * Cached column data
+ * @var DrizzleColumn[]
+ */
+ private $_columns = null;
+ /**
+ * Cached column names
+ * @var string[]
+ */
+ private $_columnNames = null;
+
+ /**
+ * Constructor
+ *
+ * @param DrizzleResult $dresult result handler
+ * @param int $bufferMode buffering mode
+ * @param int $fetchMode fetching mode
+ */
+ public function __construct(DrizzleResult $dresult, $bufferMode, $fetchMode)
+ {
+ $this->_dresult = $dresult;
+ $this->_bufferMode = $bufferMode;
+ $this->_fetchMode = $fetchMode;
+
+ if ($this->_bufferMode == PMA_Drizzle::BUFFER_RESULT) {
+ $this->_dresult->buffer();
+ }
+ }
+
+ /**
+ * Sets fetch mode
+ *
+ * @param int $fetchMode fetch mode
+ *
+ * @return void
+ */
+ public function setFetchMode($fetchMode)
+ {
+ $this->_fetchMode = $fetchMode;
+ }
+
+ /**
+ * Reads information about columns contained in current result
+ * set into {@see $_columns} and {@see $_columnNames} arrays
+ *
+ * @return void
+ */
+ private function _readColumns()
+ {
+ $this->_columns = array();
+ $this->_columnNames = array();
+ if ($this->_bufferMode == PMA_Drizzle::BUFFER_RESULT) {
+ while (($column = $this->_dresult->columnNext()) !== null) {
+ $this->_columns[] = $column;
+ $this->_columnNames[] = $column->name();
+ }
+ } else {
+ while (($column = $this->_dresult->columnRead()) !== null) {
+ $this->_columns[] = $column;
+ $this->_columnNames[] = $column->name();
+ }
+ }
+ }
+
+ /**
+ * Returns columns in current result
+ *
+ * @return DrizzleColumn[]
+ */
+ public function getColumns()
+ {
+ if (! $this->_columns) {
+ $this->_readColumns();
+ }
+ return $this->_columns;
+ }
+
+ /**
+ * Returns number if columns in result
+ *
+ * @return int
+ */
+ public function numColumns()
+ {
+ return $this->_dresult->columnCount();
+ }
+
+ /**
+ * Transforms result row to conform to current fetch mode
+ *
+ * @param mixed &$row row to process
+ * @param int $fetchMode fetch mode
+ *
+ * @return void
+ */
+ private function _transformResultRow(&$row, $fetchMode)
+ {
+ if (! $row) {
+ return;
+ }
+
+ switch ($fetchMode) {
+ case PMA_Drizzle::FETCH_ASSOC:
+ $row = array_combine($this->_columnNames, $row);
+ break;
+ case PMA_Drizzle::FETCH_BOTH:
+ $length = count($row);
+ for ($i = 0; $i < $length; $i++) {
+ $row[$this->_columnNames[$i]] = $row[$i];
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ /**
+ * Fetches next for from this result set
+ *
+ * @param int $fetchMode fetch mode to use, if not given the default one is used
+ *
+ * @return array|null
+ */
+ public function fetchRow($fetchMode = null)
+ {
+ // read column names on first fetch, only buffered results
+ // allow for reading it later
+ if (! $this->_columns) {
+ $this->_readColumns();
+ }
+ if ($fetchMode === null) {
+ $fetchMode = $this->_fetchMode;
+ }
+ $row = null;
+ switch ($this->_bufferMode) {
+ case PMA_Drizzle::BUFFER_RESULT:
+ $row = $this->_dresult->rowNext();
+ break;
+ case PMA_Drizzle::BUFFER_ROW:
+ $row = $this->_dresult->rowBuffer();
+ break;
+ }
+ $this->_transformResultRow($row, $fetchMode);
+ return $row;
+ }
+
+ /**
+ * Adjusts the result pointer to an arbitrary row in buffered result
+ *
+ * @param integer $row_index where to seek
+ *
+ * @return bool
+ */
+ public function seek($row_index)
+ {
+ if ($this->_bufferMode != PMA_Drizzle::BUFFER_RESULT) {
+ trigger_error(
+ __("Can't seek in an unbuffered result set"), E_USER_WARNING
+ );
+ return false;
+ }
+ // rowSeek always returns NULL (drizzle extension v.0.5, API v.7)
+ if ($row_index >= 0 && $row_index < $this->_dresult->rowCount()) {
+ $this->_dresult->rowSeek($row_index);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Returns the number of rows in buffered result set
+ *
+ * @return int|false
+ */
+ public function numRows()
+ {
+ if ($this->_bufferMode != PMA_Drizzle::BUFFER_RESULT) {
+ trigger_error(
+ __("Can't count rows in an unbuffered result set"), E_USER_WARNING
+ );
+ return false;
+ }
+ return $this->_dresult->rowCount();
+ }
+
+ /**
+ * Returns the number of rows affected by query
+ *
+ * @return int|false
+ */
+ public function affectedRows()
+ {
+ return $this->_dresult->affectedRows();
+ }
+
+ /**
+ * Frees resources taken by this result
+ *
+ * @return void
+ */
+ public function free()
+ {
+ unset($this->_columns);
+ unset($this->_columnNames);
+ drizzle_result_free($this->_dresult);
+ unset($this->_dresult);
+ }
+}
diff --git a/libraries/display_change_password.lib.php b/libraries/display_change_password.lib.php
new file mode 100644
index 0000000000..3d068d4c2a
--- /dev/null
+++ b/libraries/display_change_password.lib.php
@@ -0,0 +1,98 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Displays form for password change
+ *
+ * @package PhpMyAdmin
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ * Get HTML for the Change password dialog
+ *
+ * @param string $username username
+ * @param string $hostname hostname
+ *
+ * @return string html snippet
+ */
+function PMA_getHtmlForChangePassword($username, $hostname)
+{
+ /**
+ * autocomplete feature of IE kills the "onchange" event handler and it
+ * must be replaced by the "onpropertychange" one in this case
+ */
+ $chg_evt_handler = (PMA_USR_BROWSER_AGENT == 'IE'
+ && PMA_USR_BROWSER_VER >= 5
+ && PMA_USR_BROWSER_VER < 7)
+ ? 'onpropertychange'
+ : 'onchange';
+
+ $html = '<form method="post" id="change_password_form" '
+ . 'action="' . $GLOBALS['PMA_PHP_SELF'] . '" '
+ . 'name="chgPassword" '
+ . 'class="ajax" >';
+
+ $html .= PMA_URL_getHiddenInputs();
+
+ if (strpos($GLOBALS['PMA_PHP_SELF'], 'server_privileges') !== false) {
+ $html .= '<input type="hidden" name="username" '
+ . 'value="' . htmlspecialchars($username) . '" />'
+ . '<input type="hidden" name="hostname" '
+ . 'value="' . htmlspecialchars($hostname) . '" />';
+ }
+ $html .= '<fieldset id="fieldset_change_password">'
+ . '<legend>' . __('Change password') . '</legend>'
+ . '<table class="data noclick">'
+ . '<tr class="odd">'
+ . '<td colspan="2">'
+ . '<input type="radio" name="nopass" value="1" id="nopass_1" '
+ . 'onclick="pma_pw.value = \'\'; pma_pw2.value = \'\'; '
+ . 'this.checked = true" />'
+ . '<label for="nopass_1">' . __('No Password') . '</label>'
+ . '</td>'
+ . '</tr>'
+ . '<tr class="even vmiddle">'
+ . '<td>'
+ . '<input type="radio" name="nopass" value="0" id="nopass_0" '
+ . 'onclick="document.getElementById(\'text_pma_pw\').focus();" '
+ . 'checked="checked " />'
+ . '<label for="nopass_0">' . __('Password:') . '&nbsp;</label>'
+ . '</td>'
+ . '<td>'
+ . '<input type="password" name="pma_pw" id="text_pma_pw" size="10" '
+ . 'class="textfield"'
+ . $chg_evt_handler . '="nopass[1].checked = true" />'
+ . '&nbsp;&nbsp;' . __('Re-type:') . '&nbsp;'
+ . '<input type="password" name="pma_pw2" id="text_pma_pw2" size="10" '
+ . 'class="textfield"'
+ . $chg_evt_handler . '="nopass[1].checked = true" />'
+ . '</td>'
+ . '</tr>'
+ . '<tr class="vmiddle">'
+ . '<td>' . __('Password Hashing:')
+ . '</td>'
+ . '<td>'
+ . '<input type="radio" name="pw_hash" id="radio_pw_hash_new" '
+ . 'value="new" checked="checked" />'
+ . '<label for="radio_pw_hash_new">MySQL&nbsp;4.1+</label>'
+ . '</td>'
+ . '</tr>'
+ . '<tr id="tr_element_before_generate_password">'
+ . '<td>&nbsp;</td>'
+ . '<td>'
+ . '<input type="radio" name="pw_hash" id="radio_pw_hash_old" '
+ . 'value="old" />'
+ . '<label for="radio_pw_hash_old">' . __('MySQL 4.0 compatible')
+ . '</label>'
+ . '</td>'
+ . '</tr>'
+ . '</table>'
+ . '</fieldset>'
+ . '<fieldset id="fieldset_change_password_footer" class="tblFooters">'
+ . '<input type="submit" name="change_pw" value="' . __('Go') . '" />'
+ . '</fieldset>'
+ . '</form>';
+ return $html;
+}
diff --git a/libraries/display_create_database.lib.php b/libraries/display_create_database.lib.php
new file mode 100644
index 0000000000..425d13c15c
--- /dev/null
+++ b/libraries/display_create_database.lib.php
@@ -0,0 +1,63 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Displays form for creating database (if user has privileges for that)
+ *
+ * @package PhpMyAdmin
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ *
+ */
+require_once './libraries/check_user_privileges.lib.php';
+
+if ($is_create_db_priv) {
+ // The user is allowed to create a db
+ $html .= '<form method="post" action="db_create.php"'
+ . ' id="create_database_form" class="ajax"><strong>';
+ $html .= '<label for="text_create_db">'
+ . PMA_Util::getImage('b_newdb.png')
+ . " " . __('Create database')
+ . '</label>&nbsp;'
+ . PMA_Util::showMySQLDocu('CREATE_DATABASE');
+ $html .= '</strong><br />';
+ $html .= PMA_URL_getHiddenInputs('', '', 5);
+ $html .= '<input type="hidden" name="reload" value="1" />';
+ $html .= '<input type="text" name="new_db" value="' . $db_to_create
+ . '" maxlength="64" class="textfield" id="text_create_db" '
+ . 'required placeholder="'.__('Database name').'"/>';
+
+ include_once './libraries/mysql_charsets.inc.php';
+ $html .= PMA_generateCharsetDropdownBox(
+ PMA_CSDROPDOWN_COLLATION,
+ 'db_collation',
+ null,
+ null,
+ true,
+ 5
+ );
+
+ if (! empty($dbstats)) {
+ $html .= '<input type="hidden" name="dbstats" value="1" />';
+ }
+
+ $html .= '<input type="submit" value="' . __('Create') .'" id="buttonGo" />';
+ $html .= '</form>';
+} else {
+ $html .= '<!-- db creation no privileges message -->';
+ $html .= '<strong>' . __('Create database:') . '&nbsp;'
+ . PMA_Util::showMySQLDocu('CREATE_DATABASE')
+ . '</strong><br />';
+
+ $html .= '<span class="noPrivileges">'
+ . PMA_Util::getImage(
+ 's_error2.png',
+ '',
+ array('hspace' => 2, 'border' => 0, 'align' => 'middle')
+ )
+ . '' . __('No Privileges') .'</span>';
+} // end create db form or message
+?>
diff --git a/libraries/display_create_table.lib.php b/libraries/display_create_table.lib.php
new file mode 100644
index 0000000000..124ae3f80a
--- /dev/null
+++ b/libraries/display_create_table.lib.php
@@ -0,0 +1,81 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Displays form for creating a table (if user has privileges for that)
+ *
+ * for MySQL >= 4.1.0, we should be able to detect if user has a CREATE
+ * privilege by looking at SHOW GRANTS output;
+ * for < 4.1.0, it could be more difficult because the logic tries to
+ * detect the current host and it might be expressed in many ways; also
+ * on a shared server, the user might be unable to define a controluser
+ * that has the proper rights to the "mysql" db;
+ * so we give up and assume that user has the right to create a table
+ *
+ * Note: in this case we could even skip the following "foreach" logic
+ *
+ * Addendum, 2006-01-19: ok, I give up. We got some reports about servers
+ * where the hostname field in mysql.user is not the same as the one
+ * in mysql.db for a user. In this case, SHOW GRANTS does not return
+ * the db-specific privileges. And probably, those users are on a shared
+ * server, so can't set up a control user with rights to the "mysql" db.
+ * We cannot reliably detect the db-specific privileges, so no more
+ * warnings about the lack of privileges for CREATE TABLE. Tested
+ * on MySQL 5.0.18.
+ *
+ * @package PhpMyAdmin
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ *
+ */
+require_once './libraries/check_user_privileges.lib.php';
+
+$is_create_table_priv = true;
+
+/**
+ * Returns the html for create table.
+ *
+ * @param string $db database name
+ *
+ * @return string
+ */
+function PMA_getHtmlForCreateTable($db)
+{
+ $html = '<form id="create_table_form_minimal" method="post" '
+ . 'action="tbl_create.php">';
+ $html .= '<fieldset>';
+ $html .= '<legend>';
+
+ if (PMA_Util::showIcons('ActionLinksMode')) {
+ $html .= PMA_Util::getImage('b_newtbl.png');
+ }
+ $html .= __('Create table');
+
+ $html .= ' </legend>';
+ $html .= PMA_URL_getHiddenInputs($db);
+ $html .= '<div class="formelement">';
+ $html .= __('Name') . ":";
+ $html .= ' <input type="text" name="table" maxlength="64" '
+ . 'size="30" required />';
+ $html .= ' </div>';
+ $html .= ' <div class="formelement">';
+ $html .= __('Number of columns') . ":";
+ $html .= ' <input type="number" min="1" name="num_fields" size="2" />';
+ $html .= ' </div>';
+ $html .= ' <div class="clearfloat"></div>';
+ $html .= '</fieldset>';
+ $html .= '<fieldset class="tblFooters">';
+ $html .= ' <input type="submit" value="' . __('Go') . '" />';
+ $html .= '</fieldset>';
+ $html .= '</form>';
+
+ return $html;
+}
+
+if (!defined('TESTSUITE')) {
+ echo PMA_getHtmlForCreateTable($db);
+}
+?>
diff --git a/libraries/display_export.inc.php b/libraries/display_export.inc.php
new file mode 100644
index 0000000000..39b07d38d2
--- /dev/null
+++ b/libraries/display_export.inc.php
@@ -0,0 +1,72 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Displays export tab.
+ *
+ * @package PhpMyAdmin
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+// Get relations & co. status
+$cfgRelation = PMA_getRelationsParam();
+
+if (isset($_REQUEST['single_table'])) {
+ $GLOBALS['single_table'] = $_REQUEST['single_table'];
+}
+
+require_once './libraries/file_listing.lib.php';
+require_once './libraries/plugin_interface.lib.php';
+require_once './libraries/display_export.lib.php';
+
+/* Scan for plugins */
+$export_list = PMA_getPlugins(
+ "export",
+ 'libraries/plugins/export/',
+ array(
+ 'export_type' => $export_type,
+ 'single_table' => isset($single_table)
+ )
+);
+
+/* Fail if we didn't find any plugin */
+if (empty($export_list)) {
+ PMA_Message::error(
+ __('Could not load export plugins, please check your installation!')
+ )->display();
+ exit;
+}
+
+$html = '<form method="post" action="export.php" '
+ . ' name="dump" class="disableAjax">';
+
+//output Hidden Inputs
+$single_table_str = isset($single_table)? $single_table : '';
+$sql_query_str = isset($sql_query)? $sql_query : '';
+$html .= PMA_getHtmlForHiddenInput(
+ $export_type,
+ $db,
+ $table,
+ $single_table_str,
+ $sql_query_str
+);
+
+//output Export Options
+$num_tables_str = isset($num_tables)? $num_tables : '';
+$unlim_num_rows_str = isset($unlim_num_rows)? $unlim_num_rows : '';
+$multi_values_str = isset($multi_values)? $multi_values : '';
+$html .= PMA_getHtmlForExportOptions(
+ $export_type,
+ $db,
+ $table,
+ $multi_values_str,
+ $num_tables_str,
+ $export_list,
+ $unlim_num_rows_str
+);
+
+$html .= '</form>';
+
+$response = PMA_Response::getInstance();
+$response->addHTML($html);
diff --git a/libraries/display_export.lib.php b/libraries/display_export.lib.php
new file mode 100644
index 0000000000..eca617997c
--- /dev/null
+++ b/libraries/display_export.lib.php
@@ -0,0 +1,697 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+
+/**
+ * functions for displaying server, database and table export
+ *
+ * @usedby display_export.inc.php
+ *
+ * @package PhpMyAdmin
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ * Outputs appropriate checked statement for checkbox.
+ *
+ * @param string $str option name
+ *
+ * @return string|void
+ */
+function PMA_exportCheckboxCheck($str)
+{
+ if (isset($GLOBALS['cfg']['Export'][$str]) && $GLOBALS['cfg']['Export'][$str]) {
+ return ' checked="checked"';
+ }
+}
+
+/**
+ * Prints Html For Export Selection Options
+ *
+ * @param String $tmp_select Tmp selected method of export
+ *
+ * @return string
+ */
+function PMA_getHtmlForExportSelectOptions($tmp_select = '')
+{
+ $multi_values = '<div style="text-align: left">';
+ $multi_values .= '<a href="#"';
+ $multi_values .= ' onclick="setSelectOptions'
+ . '(\'dump\', \'db_select[]\', true); return false;">';
+ $multi_values .= __('Select All');
+ $multi_values .= '</a>';
+ $multi_values .= ' / ';
+ $multi_values .= '<a href="#"';
+ $multi_values .= ' onclick="setSelectOptions'
+ . '(\'dump\', \'db_select[]\', false); return false;">';
+ $multi_values .= __('Unselect All') . '</a><br />';
+
+ $multi_values .= '<select name="db_select[]" '
+ . 'id="db_select" size="10" multiple="multiple">';
+ $multi_values .= "\n";
+
+ // Check if the selected databases are defined in $_GET
+ // (from clicking Back button on export.php)
+ if (isset($_GET['db_select'])) {
+ $_GET['db_select'] = urldecode($_GET['db_select']);
+ $_GET['db_select'] = explode(",", $_GET['db_select']);
+ }
+
+ foreach ($GLOBALS['pma']->databases as $current_db) {
+ if ($current_db == 'information_schema'
+ || $current_db == 'performance_schema'
+ || $current_db == 'mysql'
+ ) {
+ continue;
+ }
+ if (isset($_GET['db_select'])) {
+ if (in_array($current_db, $_GET['db_select'])) {
+ $is_selected = ' selected="selected"';
+ } else {
+ $is_selected = '';
+ }
+ } elseif (!empty($tmp_select)) {
+ if (strpos(' ' . $tmp_select, '|' . $current_db . '|')) {
+ $is_selected = ' selected="selected"';
+ } else {
+ $is_selected = '';
+ }
+ } else {
+ $is_selected = ' selected="selected"';
+ }
+ $current_db = htmlspecialchars($current_db);
+ $multi_values .= ' <option value="' . $current_db . '"'
+ . $is_selected . '>' . $current_db . '</option>' . "\n";
+ } // end while
+ $multi_values .= "\n";
+ $multi_values .= '</select></div>';
+
+ return $multi_values;
+}
+
+/**
+ * Prints Html For Export Hidden Input
+ *
+ * @param String $export_type Selected Export Type
+ * @param String $db Selected DB
+ * @param String $table Selected Table
+ * @param String $single_table Single Table
+ * @param String $sql_query Sql Query
+ *
+ * @return string
+ */
+function PMA_getHtmlForHiddenInput(
+ $export_type, $db, $table, $single_table, $sql_query
+) {
+ global $cfg;
+ $html = "";
+ if ($export_type == 'server') {
+ $html .= PMA_URL_getHiddenInputs('', '', 1);
+ } elseif ($export_type == 'database') {
+ $html .= PMA_URL_getHiddenInputs($db, '', 1);
+ } else {
+ $html .= PMA_URL_getHiddenInputs($db, $table, 1);
+ }
+
+ // just to keep this value for possible next display of this form after saving
+ // on server
+ if (!empty($single_table)) {
+ $html .= '<input type="hidden" name="single_table" value="TRUE" />'
+ . "\n";
+ }
+
+ $html .= '<input type="hidden" name="export_type" value="'
+ . $export_type . '" />';
+ $html .= "\n";
+
+ // If the export method was not set, the default is quick
+ if (isset($_GET['export_method'])) {
+ $cfg['Export']['method'] = $_GET['export_method'];
+ } elseif (! isset($cfg['Export']['method'])) {
+ $cfg['Export']['method'] = 'quick';
+ }
+ // The export method (quick, custom or custom-no-form)
+ $html .= '<input type="hidden" name="export_method" value="'
+ . htmlspecialchars($cfg['Export']['method']) . '" />';
+
+
+ if (isset($_GET['sql_query'])) {
+ $html .= '<input type="hidden" name="sql_query" value="'
+ . htmlspecialchars($_GET['sql_query']) . '" />' . "\n";
+ } elseif (! empty($sql_query)) {
+ $html .= '<input type="hidden" name="sql_query" value="'
+ . htmlspecialchars($sql_query) . '" />' . "\n";
+ }
+
+ return $html;
+}
+
+/**
+ * Prints Html For Export Options Header
+ *
+ * @param String $export_type Selected Export Type
+ * @param String $db Selected DB
+ * @param String $table Selected Table
+ *
+ * @return string
+ */
+function PMA_getHtmlForExportOptionHeader($export_type, $db, $table)
+{
+ $html = '<div class="exportoptions" id="header">';
+ $html .= '<h2>';
+ $html .= PMA_Util::getImage('b_export.png', __('Export'));
+ if ($export_type == 'server') {
+ $html .= __('Exporting databases from the current server');
+ } elseif ($export_type == 'database') {
+ $html .= sprintf(
+ __('Exporting tables from "%s" database'),
+ htmlspecialchars($db)
+ );
+ } else {
+ $html .= sprintf(
+ __('Exporting rows from "%s" table'),
+ htmlspecialchars($table)
+ );
+ }
+ $html .= '</h2>';
+ $html .= '</div>';
+
+ return $html;
+}
+
+/**
+ * Prints Html For Export Options Method
+ *
+ * @return string
+ */
+function PMA_getHtmlForExportOptionsMethod()
+{
+ global $cfg;
+ if (isset($_GET['quick_or_custom'])) {
+ $export_method = $_GET['quick_or_custom'];
+ } else {
+ $export_method = $cfg['Export']['method'];
+ }
+
+ $html = '<div class="exportoptions" id="quick_or_custom">';
+ $html .= '<h3>' . __('Export Method:') . '</h3>';
+ $html .= '<ul>';
+ $html .= '<li>';
+ $html .= '<input type="radio" name="quick_or_custom" value="quick" '
+ . ' id="radio_quick_export"';
+ if ($export_method == 'quick' || $export_method == 'quick_no_form') {
+ $html .= ' checked="checked"';
+ }
+ $html .= ' />';
+ $html .= '<label for ="radio_quick_export">';
+ $html .= __('Quick - display only the minimal options');
+ $html .= '</label>';
+ $html .= '</li>';
+
+ $html .= '<li>';
+ $html .= '<input type="radio" name="quick_or_custom" value="custom" '
+ . ' id="radio_custom_export"';
+ if ($export_method == 'custom' || $export_method == 'custom_no_form') {
+ $html .= ' checked="checked"';
+ }
+ $html .= ' />';
+ $html .= '<label for="radio_custom_export">';
+ $html .= __('Custom - display all possible options');
+ $html .= '</label>';
+ $html .= '</li>';
+
+ $html .= '</ul>';
+ $html .= '</div>';
+
+ return $html;
+}
+
+/**
+ * Prints Html For Export Options Selection
+ *
+ * @param String $export_type Selected Export Type
+ * @param String $multi_values Export Options
+ *
+ * @return string
+ */
+function PMA_getHtmlForExportOptionsSelection($export_type, $multi_values)
+{
+ $html = '<div class="exportoptions" id="databases_and_tables">';
+ if ($export_type == 'server') {
+ $html .= '<h3>' . __('Database(s):') . '</h3>';
+ } else if ($export_type == 'database') {
+ $html .= '<h3>' . __('Table(s):') . '</h3>';
+ }
+ if (! empty($multi_values)) {
+ $html .= $multi_values;
+ }
+ $html .= '</div>';
+
+ return $html;
+}
+
+/**
+ * Prints Html For Export Options Format
+ *
+ * @param String $export_list Export List
+ *
+ * @return string
+ */
+function PMA_getHtmlForExportOptionsFormat($export_list)
+{
+ $html = '<div class="exportoptions" id="format">';
+ $html .= '<h3>' . __('Format:') . '</h3>';
+ $html .= PMA_pluginGetChoice('Export', 'what', $export_list, 'format');
+ $html .= '</div>';
+
+ $html .= '<div class="exportoptions" id="format_specific_opts">';
+ $html .= '<h3>' . __('Format-specific options:') . '</h3>';
+ $html .= '<p class="no_js_msg" id="scroll_to_options_msg">';
+ $html .= __(
+ 'Scroll down to fill in the options for the selected format '
+ . 'and ignore the options for other formats.'
+ );
+ $html .= '</p>';
+ $html .= PMA_pluginGetOptions('Export', $export_list);
+ $html .= '</div>';
+
+ if (function_exists('PMA_Kanji_encodingForm')) {
+ // Encoding setting form appended by Y.Kawada
+ // Japanese encoding setting
+ $html .= '<div class="exportoptions" id="kanji_encoding">';
+ $html .= '<h3>' . __('Encoding Conversion:') . '</h3>';
+ $html .= PMA_Kanji_encodingForm();
+ $html .= '</div>';
+ }
+
+ $html .= '<div class="exportoptions" id="submit">';
+
+ $html .= PMA_Util::getExternalBug(
+ __('SQL compatibility mode'), 'mysql', '50027', '14515'
+ );
+ global $cfg;
+ $html .= '<input type="submit" value="' . __('Go') . '" id="buttonGo" onclick="check_time_out('.$cfg['ExecTimeLimit'].')"/>';
+ $html .= '</div>';
+
+ return $html;
+}
+/**
+ * Prints Html For Export Options Rows
+ *
+ * @param String $db Selected DB
+ * @param String $table Selected Table
+ * @param String $unlim_num_rows Num of Rows
+ *
+ * @return string
+ */
+function PMA_getHtmlForExportOptionsRows($db, $table, $unlim_num_rows)
+{
+ $html = '<div class="exportoptions" id="rows">';
+ $html .= '<h3>' . __('Rows:') . '</h3>';
+ $html .= '<ul>';
+ $html .= '<li>';
+ $html .= '<input type="radio" name="allrows" value="0" id="radio_allrows_0"';
+ if (isset($_GET['allrows']) && $_GET['allrows'] == 0) {
+ $html .= ' checked="checked"';
+ }
+ $html .= '/>';
+ $html .= '<label for ="radio_allrows_0">' . __('Dump some row(s)') . '</label>';
+ $html .= '<ul>';
+ $html .= '<li>';
+ $html .= '<label for="limit_to">' . __('Number of rows:') . '</label>';
+ $html .= '<input type="text" id="limit_to" name="limit_to" size="5" value="';
+ if (isset($_GET['limit_to'])) {
+ $html .= htmlspecialchars($_GET['limit_to']);
+ } elseif (!empty($unlim_num_rows)) {
+ $html .= $unlim_num_rows;
+ } else {
+ $html .= PMA_Table::countRecords($db, $table);
+ }
+ $html .= '" onfocus="this.select()" />';
+ $html .= '</li>';
+ $html .= '<li>';
+ $html .= '<label for="limit_from">' . __('Row to begin at:') . '</label>';
+ $html .= '<input type="text" id="limit_from" name="limit_from" value="';
+ if (isset($_GET['limit_from'])) {
+ $html .= htmlspecialchars($_GET['limit_from']);
+ } else {
+ $html .= '0';
+ }
+ $html .= '" size="5" onfocus="this.select()" />';
+ $html .= '</li>';
+ $html .= '</ul>';
+ $html .= '</li>';
+ $html .= '<li>';
+ $html .= '<input type="radio" name="allrows" value="1" id="radio_allrows_1"';
+ if (! isset($_GET['allrows']) || $_GET['allrows'] == 1) {
+ $html .= ' checked="checked"';
+ }
+ $html .= '/>';
+ $html .= ' <label for="radio_allrows_1">' . __('Dump all rows') . '</label>';
+ $html .= '</li>';
+ $html .= '</ul>';
+ $html .= '</div>';
+ return $html;
+}
+
+/**
+ * Prints Html For Export Options Quick Export
+ *
+ * @return string
+ */
+function PMA_getHtmlForExportOptionsQuickExport()
+{
+ global $cfg;
+ $html = '<div class="exportoptions" id="output_quick_export">';
+ $html .= '<h3>' . __('Output:') . '</h3>';
+ $html .= '<ul>';
+ $html .= '<li>';
+ $html .= '<input type="checkbox" name="quick_export_onserver" value="saveit" ';
+ $html .= 'id="checkbox_quick_dump_onserver" ';
+ $html .= PMA_exportCheckboxCheck('quick_export_onserver');
+ $html .= '/>';
+ $html .= '<label for="checkbox_quick_dump_onserver">';
+ $html .= sprintf(
+ __('Save on server in the directory <b>%s</b>'),
+ htmlspecialchars(PMA_Util::userDir($cfg['SaveDir']))
+ );
+ $html .= '</label>';
+ $html .= '</li>';
+ $html .= '<li>';
+ $html .= '<input type="checkbox" name="quick_export_onserverover" ';
+ $html .= 'value="saveitover" id="checkbox_quick_dump_onserverover" ';
+ $html .= PMA_exportCheckboxCheck('quick_export_onserver_overwrite');
+ $html .= '/>';
+ $html .= '<label for="checkbox_quick_dump_onserverover">';
+ $html .= __('Overwrite existing file(s)');
+ $html .= '</label>';
+ $html .= '</li>';
+ $html .= '</ul>';
+ $html .= '</div>';
+
+ return $html;
+}
+
+/**
+ * Prints Html For Export Options Save Dir
+ *
+ * @return string
+ */
+function PMA_getHtmlForExportOptionsOutputSaveDir()
+{
+ global $cfg;
+ $html = '<li>';
+ $html .= '<input type="checkbox" name="onserver" value="saveit" ';
+ $html .= 'id="checkbox_dump_onserver" ';
+ $html .= PMA_exportCheckboxCheck('onserver');
+ $html .= '/>';
+ $html .= '<label for="checkbox_dump_onserver">';
+ $html .= sprintf(
+ __('Save on server in the directory <b>%s</b>'),
+ htmlspecialchars(PMA_Util::userDir($cfg['SaveDir']))
+ );
+ $html .= '</label>';
+ $html .= '</li>';
+ $html .= '<li>';
+ $html .= '<input type="checkbox" name="onserverover" value="saveitover"';
+ $html .= ' id="checkbox_dump_onserverover" ';
+ $html .= PMA_exportCheckboxCheck('onserver_overwrite');
+ $html .= '/>';
+ $html .= '<label for="checkbox_dump_onserverover">';
+ $html .= __('Overwrite existing file(s)');
+ $html .= '</label>';
+ $html .= '</li>';
+
+ return $html;
+}
+
+
+/**
+ * Prints Html For Export Options
+ *
+ * @param String $export_type Selected Export Type
+ *
+ * @return string
+ */
+function PMA_getHtmlForExportOptionsOutputFormat($export_type)
+{
+ $html = '<li>';
+ $html .= '<label for="filename_template" class="desc">';
+ $html .= __('File name template:');
+ $trans = new PMA_Message;
+ $trans->addMessage(__('@SERVER@ will become the server name'));
+ if ($export_type == 'database' || $export_type == 'table') {
+ $trans->addMessage(__(', @DATABASE@ will become the database name'));
+ if ($export_type == 'table') {
+ $trans->addMessage(__(', @TABLE@ will become the table name'));
+ }
+ }
+
+ $msg = new PMA_Message(
+ __(
+ 'This value is interpreted using %1$sstrftime%2$s, '
+ . 'so you can use time formatting strings. '
+ . 'Additionally the following transformations will happen: %3$s. '
+ . 'Other text will be kept as is. See the %4$sFAQ%5$s for details.'
+ )
+ );
+ $msg->addParam(
+ '<a href="' . PMA_linkURL(PMA_getPHPDocLink('function.strftime.php'))
+ . '" target="documentation" title="' . __('Documentation') . '">',
+ false
+ );
+ $msg->addParam('</a>', false);
+ $msg->addParam($trans);
+ $doc_url = PMA_Util::getDocuLink('faq', 'faq6-27');
+ $msg->addParam(
+ '<a href="'. $doc_url . '" target="documentation">',
+ false
+ );
+ $msg->addParam('</a>', false);
+
+ $html .= PMA_Util::showHint($msg);
+ $html .= '</label>';
+ $html .= '<input type="text" name="filename_template" id="filename_template" ';
+ $html .= ' value="';
+ if (isset($_GET['filename_template'])) {
+ $html .= htmlspecialchars($_GET['filename_template']);
+ } else {
+ if ($export_type == 'database') {
+ $html .= htmlspecialchars(
+ $GLOBALS['PMA_Config']->getUserValue(
+ 'pma_db_filename_template',
+ $GLOBALS['cfg']['Export']['file_template_database']
+ )
+ );
+ } elseif ($export_type == 'table') {
+ $html .= htmlspecialchars(
+ $GLOBALS['PMA_Config']->getUserValue(
+ 'pma_table_filename_template',
+ $GLOBALS['cfg']['Export']['file_template_table']
+ )
+ );
+ } else {
+ $html .= htmlspecialchars(
+ $GLOBALS['PMA_Config']->getUserValue(
+ 'pma_server_filename_template',
+ $GLOBALS['cfg']['Export']['file_template_server']
+ )
+ );
+ }
+ }
+ $html .= '"';
+ $html .= '/>';
+ $html .= '<input type="checkbox" name="remember_template" ';
+ $html .= 'id="checkbox_remember_template" ';
+ $html .= PMA_exportCheckboxCheck('remember_file_template');
+ $html .= '/>';
+ $html .= '<label for="checkbox_remember_template">';
+ $html .= __('use this for future exports');
+ $html .= '</label>';
+ $html .= '</li>';
+ return $html;
+}
+
+/**
+ * Prints Html For Export Options Charset
+ *
+ * @return string
+ */
+function PMA_getHtmlForExportOptionsOutputCharset()
+{
+ global $cfg;
+ $html = ' <li><label for="select_charset_of_file" class="desc">'
+ . __('Character set of the file:') . '</label>' . "\n";
+ reset($cfg['AvailableCharsets']);
+ $html .= '<select id="select_charset_of_file" name="charset_of_file" size="1">';
+ foreach ($cfg['AvailableCharsets'] as $temp_charset) {
+ $html .= '<option value="' . $temp_charset . '"';
+ if (isset($_GET['charset_of_file'])
+ && ($_GET['charset_of_file'] != $temp_charset)
+ ) {
+ $html .= '';
+ } elseif ((empty($cfg['Export']['charset']) && $temp_charset == 'utf-8')
+ || $temp_charset == $cfg['Export']['charset']
+ ) {
+ $html .= ' selected="selected"';
+ }
+ $html .= '>' . $temp_charset . '</option>';
+ } // end foreach
+ $html .= '</select></li>';
+
+ return $html;
+}
+
+/**
+ * Prints Html For Export Options Compression
+ *
+ * @return string
+ */
+function PMA_getHtmlForExportOptionsOutputCompression()
+{
+ global $cfg;
+ if (isset($_GET['compression'])) {
+ $selected_compression = $_GET['compression'];
+ } elseif (isset($cfg['Export']['compression'])) {
+ $selected_compression = $cfg['Export']['compression'];
+ } else {
+ $selected_compression = "none";
+ }
+
+ $html = "";
+ // zip and gzip encode features
+ $is_zip = ($cfg['ZipDump'] && @function_exists('gzcompress'));
+ $is_gzip = ($cfg['GZipDump'] && @function_exists('gzencode'));
+ if ($is_zip || $is_gzip) {
+ $html .= '<li>';
+ $html .= '<label for="compression" class="desc">'
+ . __('Compression:') . '</label>';
+ $html .= '<select id="compression" name="compression">';
+ $html .= '<option value="none">' . __('None') . '</option>';
+ if ($is_zip) {
+ $html .= '<option value="zip" ';
+ if ($selected_compression == "zip") {
+ $html .= 'selected="selected"';
+ }
+ $html .= '>' . __('zipped') . '</option>';
+ }
+ if ($is_gzip) {
+ $html .= '<option value="gzip" ';
+ if ($selected_compression == "gzip") {
+ $html .= 'selected="selected"';
+ }
+ $html .= '>' . __('gzipped') . '</option>';
+ }
+ $html .= '</select>';
+ $html .= '</li>';
+ } else {
+ $html .= '<input type="hidden" name="compression" value="'
+ . htmlspecialchars($selected_compression) . '" />';
+ }
+
+ return $html;
+}
+
+/**
+ * Prints Html For Export Options Radio
+ *
+ * @return string
+ */
+function PMA_getHtmlForExportOptionsOutputRadio()
+{
+ $html = '<li>';
+ $html .= '<input type="radio" id="radio_view_as_text" '
+ . ' name="output_format" value="astext" ';
+ if (isset($_GET['repopulate']) || $GLOBALS['cfg']['Export']['asfile'] == false) {
+ $html .= 'checked="checked"';
+ }
+ $html .= '/>';
+ $html .= '<label for="radio_view_as_text">'
+ . __('View output as text') . '</label></li>';
+ return $html;
+}
+
+/**
+ * Prints Html For Export Options
+ *
+ * @param String $export_type Selected Export Type
+ *
+ * @return string
+ */
+function PMA_getHtmlForExportOptionsOutput($export_type)
+{
+ global $cfg;
+ $html = '<div class="exportoptions" id="output">';
+ $html .= '<h3>' . __('Output:') . '</h3>';
+ $html .= '<ul id="ul_output">';
+ $html .= '<li>';
+ $html .= '<input type="radio" name="output_format" value="sendit" ';
+ $html .= 'id="radio_dump_asfile" ';
+ if (!isset($_GET['repopulate'])) {
+ $html .= PMA_exportCheckboxCheck('asfile');
+ }
+ $html .= '/>';
+ $html .= '<label for="radio_dump_asfile">'
+ . __('Save output to a file') . '</label>';
+ $html .= '<ul id="ul_save_asfile">';
+ if (isset($cfg['SaveDir']) && !empty($cfg['SaveDir'])) {
+ $html .= PMA_getHtmlForExportOptionsOutputSaveDir();
+ }
+
+ $html .= PMA_getHtmlForExportOptionsOutputFormat($export_type);
+
+ // charset of file
+ if ($GLOBALS['PMA_recoding_engine'] != PMA_CHARSET_NONE) {
+ $html .= PMA_getHtmlForExportOptionsOutputCharset();
+ } // end if
+
+ $html .= PMA_getHtmlForExportOptionsOutputCompression();
+
+ $html .= '</ul>';
+ $html .= '</li>';
+
+ $html .= PMA_getHtmlForExportOptionsOutputRadio();
+
+ $html .= '</ul>';
+ $html .= '</div>';
+
+ return $html;
+}
+
+/**
+ * Prints Html For Export Options
+ *
+ * @param String $export_type Selected Export Type
+ * @param String $db Selected DB
+ * @param String $table Selected Table
+ * @param String $multi_values Export selection
+ * @param String $num_tables number of tables
+ * @param String $export_list Export List
+ * @param String $unlim_num_rows Number of Rows
+ *
+ * @return string
+ */
+function PMA_getHtmlForExportOptions(
+ $export_type, $db, $table, $multi_values,
+ $num_tables, $export_list, $unlim_num_rows
+) {
+ global $cfg;
+ $html = PMA_getHtmlForExportOptionHeader($export_type, $db, $table);
+ $html .= PMA_getHtmlForExportOptionsMethod();
+ $html .= PMA_getHtmlForExportOptionsSelection($export_type, $multi_values);
+
+ if (strlen($table) && empty($num_tables) && ! PMA_Table::isMerge($db, $table)) {
+ $html .= PMA_getHtmlForExportOptionsRows($db, $table, $unlim_num_rows);
+ }
+
+ if (isset($cfg['SaveDir']) && !empty($cfg['SaveDir'])) {
+ $html .= PMA_getHtmlForExportOptionsQuickExport();
+ }
+
+ $html .= PMA_getHtmlForExportOptionsOutput($export_type);
+
+ $html .= PMA_getHtmlForExportOptionsFormat($export_list);
+ return $html;
+}
+?>
diff --git a/libraries/display_git_revision.lib.php b/libraries/display_git_revision.lib.php
new file mode 100644
index 0000000000..d794832eeb
--- /dev/null
+++ b/libraries/display_git_revision.lib.php
@@ -0,0 +1,83 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Displays form for password change
+ *
+ * @package PhpMyAdmin
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+* Prints details about the current Git commit revision
+*
+* @return void
+*/
+function PMA_printGitRevision()
+{
+ if (! $GLOBALS['PMA_Config']->get('PMA_VERSION_GIT')) {
+ $response = PMA_Response::getInstance();
+ $response->isSuccess(false);
+ return;
+ }
+
+ // load revision data from repo
+ $GLOBALS['PMA_Config']->checkGitRevision();
+
+ // if using a remote commit fast-forwarded, link to Github
+ $commit_hash = substr(
+ $GLOBALS['PMA_Config']->get('PMA_VERSION_GIT_COMMITHASH'),
+ 0,
+ 7
+ );
+ $commit_hash = '<strong title="'
+ . htmlspecialchars($GLOBALS['PMA_Config']->get('PMA_VERSION_GIT_MESSAGE'))
+ . '">' . $commit_hash . '</strong>';
+ if ($GLOBALS['PMA_Config']->get('PMA_VERSION_GIT_ISREMOTECOMMIT')) {
+ $commit_hash = '<a href="'
+ . PMA_linkURL(
+ 'https://github.com/phpmyadmin/phpmyadmin/commit/'
+ . $GLOBALS['PMA_Config']->get('PMA_VERSION_GIT_COMMITHASH')
+ )
+ . '" target="_blank">' . $commit_hash . '</a>';
+ }
+
+ $branch = $GLOBALS['PMA_Config']->get('PMA_VERSION_GIT_BRANCH');
+ if ($GLOBALS['PMA_Config']->get('PMA_VERSION_GIT_ISREMOTEBRANCH')) {
+ $branch = '<a href="'
+ . PMA_linkURL(
+ 'https://github.com/phpmyadmin/phpmyadmin/tree/'
+ . $GLOBALS['PMA_Config']->get('PMA_VERSION_GIT_BRANCH')
+ )
+ . '" target="_blank">' . $branch . '</a>';
+ }
+ if ($branch !== false) {
+ $branch = sprintf(__('%1$s from %2$s branch'), $commit_hash, $branch);
+ } else {
+ $branch = $commit_hash . ' (' . __('no branch') . ')';
+ }
+
+ $committer = $GLOBALS['PMA_Config']->get('PMA_VERSION_GIT_COMMITTER');
+ $author = $GLOBALS['PMA_Config']->get('PMA_VERSION_GIT_AUTHOR');
+ PMA_printListItem(
+ __('Git revision:') . ' '
+ . $branch . ',<br /> '
+ . sprintf(
+ __('committed on %1$s by %2$s'),
+ PMA_Util::localisedDate(strtotime($committer['date'])),
+ '<a href="' . PMA_linkURL('mailto:' . $committer['email']) . '">'
+ . htmlspecialchars($committer['name']) . '</a>'
+ )
+ . ($author != $committer
+ ? ', <br />'
+ . sprintf(
+ __('authored on %1$s by %2$s'),
+ PMA_Util::localisedDate(strtotime($author['date'])),
+ '<a href="' . PMA_linkURL('mailto:' . $author['email']) . '">'
+ . htmlspecialchars($author['name']) . '</a>'
+ )
+ : ''),
+ 'li_pma_version_git', null, null, null
+ );
+}
diff --git a/libraries/display_import.inc.php b/libraries/display_import.inc.php
new file mode 100644
index 0000000000..8431f2c1c3
--- /dev/null
+++ b/libraries/display_import.inc.php
@@ -0,0 +1,54 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+
+/**
+ * include file for display import : server, database, table
+ *
+ * @package PhpMyAdmin
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ *
+ */
+require_once './libraries/file_listing.lib.php';
+require_once './libraries/plugin_interface.lib.php';
+require_once './libraries/display_import.lib.php';
+require_once './libraries/display_import_ajax.lib.php';
+
+/* Scan for plugins */
+$import_list = PMA_getPlugins(
+ "import",
+ 'libraries/plugins/import/',
+ $import_type
+);
+
+/* Fail if we didn't find any plugin */
+if (empty($import_list)) {
+ PMA_Message::error(
+ __(
+ 'Could not load import plugins, please check your installation!'
+ )
+ )->display();
+ exit;
+}
+
+$timeout_passed_str = isset($timeout_passed)? $timeout_passed : null;
+$offset_str = isset($offset)? $offset : null;
+$html = PMA_getHtmlForImport(
+ $upload_id,
+ $import_type,
+ $db,
+ $table,
+ $max_upload_size,
+ $import_list,
+ $timeout_passed_str,
+ $offset_str
+);
+
+$response = PMA_Response::getInstance();
+$response->addHTML($html);
+
+?>
diff --git a/libraries/display_import.lib.php b/libraries/display_import.lib.php
new file mode 100644
index 0000000000..493330b40a
--- /dev/null
+++ b/libraries/display_import.lib.php
@@ -0,0 +1,586 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+
+/**
+ * functions for displaying import for: server, database and table
+ *
+ * @usedby display_import.inc.php
+ *
+ * @package PhpMyAdmin
+ */
+
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ * Prints Html For Display Import Hidden Input
+ *
+ * @param String $import_type Import type: server, database, table
+ * @param String $db Selected DB
+ * @param String $table Selected Table
+ *
+ * @return string
+ */
+function PMA_getHtmlForHiddenInputs($import_type, $db, $table)
+{
+ $html = '';
+ if ($import_type == 'server') {
+ $html .= PMA_URL_getHiddenInputs('', '', 1);
+ } elseif ($import_type == 'database') {
+ $html .= PMA_URL_getHiddenInputs($db, '', 1);
+ } else {
+ $html .= PMA_URL_getHiddenInputs($db, $table, 1);
+ }
+ $html .= ' <input type="hidden" name="import_type" value="'
+ . $import_type . '" />'."\n";
+
+ return $html;
+}
+
+/**
+ * Prints Html For Import Javascript
+ *
+ * @param int $upload_id The selected upload id
+ *
+ * @return string
+ */
+function PMA_getHtmlForImportJS($upload_id)
+{
+ global $SESSION_KEY;
+ $html = '';
+ $html .= '<script type="text/javascript">';
+ $html .= ' //<![CDATA[';
+ //with "\n", so that the following lines won't be commented out by //<![CDATA[
+ $html .= "\n";
+ $html .= ' $( function() {';
+ // add event when user click on "Go" button
+ $html .= ' $("#buttonGo").bind("click", function() {';
+ // hide form
+ $html .= ' $("#upload_form_form").css("display", "none");';
+ // show progress bar
+ $html .= ' $("#upload_form_status").css("display", "inline");';
+ $html .= ' $("#upload_form_status_info").css("display", "inline");';
+
+ if ($_SESSION[$SESSION_KEY]["handler"] != "UploadNoplugin") {
+
+ $html .= PMA_getHtmlForImportWithPlugin($upload_id);
+
+ } else { // no plugin available
+ $image_tag = '<img src="' . $GLOBALS['pmaThemeImage']
+ . 'ajax_clock_small.gif" width="16" height="16" alt="ajax clock" /> '
+ . PMA_jsFormat(
+ __(
+ 'Please be patient, the file is being uploaded. '
+ . 'Details about the upload are not available.'
+ ),
+ false
+ ) . PMA_Util::showDocu('faq', 'faq2-9');
+ $html .= " $('#upload_form_status_info').html('" . $image_tag . "');";
+ $html .= ' $("#upload_form_status").css("display", "none");';
+ } // else
+
+ // onclick
+ $html .= ' });';
+ // domready
+ $html .= ' });';
+ $html .= ' //]]>';
+ //with "\n", so that the following lines won't be commented out by //]]>
+ $html .= "\n";
+ $html .= '</script>';
+
+ return $html;
+}
+
+/**
+ * Prints Html For Display Export options
+ *
+ * @param String $import_type Import type: server, database, table
+ * @param String $db Selected DB
+ * @param String $table Selected Table
+ *
+ * @return string
+ */
+function PMA_getHtmlForExportOptions($import_type, $db, $table)
+{
+ $html = ' <div class="exportoptions" id="header">';
+ $html .= ' <h2>';
+ $html .= PMA_Util::getImage('b_import.png', __('Import'));
+
+ if ($import_type == 'server') {
+ $html .= __('Importing into the current server');
+ } elseif ($import_type == 'database') {
+ $import_str = sprintf(
+ __('Importing into the database "%s"'),
+ htmlspecialchars($db)
+ );
+ $html .= $import_str;
+ } else {
+ $import_str = sprintf(
+ __('Importing into the table "%s"'),
+ htmlspecialchars($table)
+ );
+ $html .= $import_str;
+ }
+ $html .= ' </h2>';
+ $html .= ' </div>';
+
+ return $html;
+}
+
+/**
+ * Prints Html For Display Import options : Compressions
+ *
+ * @return string
+ */
+function PMA_getHtmlForImportCompressions()
+{
+ global $cfg;
+ $html = '';
+ // zip, gzip and bzip2 encode features
+ $compressions = array();
+
+ if ($cfg['GZipDump'] && @function_exists('gzopen')) {
+ $compressions[] = 'gzip';
+ }
+ if ($cfg['BZipDump'] && @function_exists('bzopen')) {
+ $compressions[] = 'bzip2';
+ }
+ if ($cfg['ZipDump'] && @function_exists('zip_open')) {
+ $compressions[] = 'zip';
+ }
+ // We don't have show anything about compression, when no supported
+ if ($compressions != array()) {
+ $html .= '<div class="formelementrow" id="compression_info">';
+ $compress_str = sprintf(
+ __('File may be compressed (%s) or uncompressed.'),
+ implode(", ", $compressions)
+ );
+ $html .= $compress_str;
+ $html .= '<br />';
+ $html .= __(
+ 'A compressed file\'s name must end in <b>.[format].[compression]</b>. '
+ . 'Example: <b>.sql.zip</b>'
+ );
+ $html .= '</div>';
+ }
+
+ return $html;
+}
+
+/**
+ * Prints Html For Display Import charset
+ *
+ * @return string
+ */
+function PMA_getHtmlForImportCharset()
+{
+ global $cfg;
+ $html = ' <div class="formelementrow" id="charaset_of_file">';
+ // charset of file
+ if ($GLOBALS['PMA_recoding_engine'] != PMA_CHARSET_NONE) {
+ $html .= '<label for="charset_of_file">' . __('Character set of the file:')
+ . '</label>';
+ reset($cfg['AvailableCharsets']);
+ $html .= '<select id="charset_of_file" name="charset_of_file" size="1">';
+ foreach ($cfg['AvailableCharsets'] as $temp_charset) {
+ $html .= '<option value="' . htmlentities($temp_charset) . '"';
+ if ((empty($cfg['Import']['charset']) && $temp_charset == 'utf-8')
+ || $temp_charset == $cfg['Import']['charset']
+ ) {
+ $html .= ' selected="selected"';
+ }
+ $html .= '>' . htmlentities($temp_charset) . '</option>';
+ }
+ $html .= ' </select><br />';
+ } else {
+ $html .= '<label for="charset_of_file">' . __('Character set of the file:')
+ . '</label>' . "\n";
+ $html .= PMA_generateCharsetDropdownBox(
+ PMA_CSDROPDOWN_CHARSET,
+ 'charset_of_file',
+ 'charset_of_file',
+ 'utf8',
+ false
+ );
+ } // end if (recoding)
+
+ $html .= ' </div>';
+
+ return $html;
+}
+
+/**
+ * Prints Html For Display Import options : file property
+ *
+ * @param int $max_upload_size Max upload size
+ * @param Array $import_list import list
+ *
+ * @return string
+ */
+function PMA_getHtmlForImportOptionsFile($max_upload_size, $import_list)
+{
+ global $cfg;
+ $html = ' <div class="importoptions">';
+ $html .= ' <h3>' . __('File to Import:') . '</h3>';
+ $html .= PMA_getHtmlForImportCompressions();
+ $html .= ' <div class="formelementrow" id="upload_form">';
+
+ if ($GLOBALS['is_upload'] && !empty($cfg['UploadDir'])) {
+ $html .= ' <ul>';
+ $html .= ' <li>';
+ $html .= ' <input type="radio" name="file_location" '
+ . 'id="radio_import_file" />';
+ $html .= PMA_Util::getBrowseUploadFileBlock($max_upload_size);
+ $html .= ' </li>';
+ $html .= ' <li>';
+ $html .= ' <input type="radio" name="file_location" '
+ . 'id="radio_local_import_file" />';
+ $html .= PMA_Util::getSelectUploadFileBlock($import_list, $cfg['UploadDir']);
+ $html .= ' </li>';
+ $html .= ' </ul>';
+
+ } elseif ($GLOBALS['is_upload']) {
+ $uid = uniqid('');
+ $html .= PMA_Util::getBrowseUploadFileBlock($max_upload_size);
+ } elseif (!$GLOBALS['is_upload']) {
+ $html .= PMA_Message::notice(
+ __('File uploads are not allowed on this server.')
+ )->getDisplay();
+ } elseif (!empty($cfg['UploadDir'])) {
+ $html .= PMA_Util::getSelectUploadFileBlock($import_list, $cfg['UploadDir']);
+ } // end if (web-server upload directory)
+
+ $html .= ' </div>';
+ $html .= PMA_getHtmlForImportCharset();
+ $html .= ' </div>';
+
+ return $html;
+}
+
+/**
+ * Prints Html For Display Import options : Partial Import
+ *
+ * @param String $timeout_passed timeout passed
+ * @param String $offset timeout offset
+ *
+ * @return string
+ */
+function PMA_getHtmlForImportOptionsPartialImport($timeout_passed, $offset)
+{
+ $html = ' <div class="importoptions">';
+ $html .= ' <h3>' . __('Partial Import:') . '</h3>';
+
+ if (isset($timeout_passed) && $timeout_passed) {
+ $html .= '<div class="formelementrow">' . "\n";
+ $html .= '<input type="hidden" name="skip" value="' . $offset . '" />';
+ $html .= sprintf(
+ __(
+ 'Previous import timed out, after resubmitting '
+ . 'will continue from position %d.'
+ ),
+ $offset
+ );
+ $html .= '</div>' . "\n";
+ }
+
+ $html .= ' <div class="formelementrow">';
+ $html .= ' <input type="checkbox" name="allow_interrupt" value="yes"';
+ $html .= ' id="checkbox_allow_interrupt" '
+ . PMA_pluginCheckboxCheck('Import', 'allow_interrupt') . '/>';
+ $html .= ' <label for="checkbox_allow_interrupt">'
+ . __(
+ 'Allow the interruption of an import in case the script detects '
+ . 'it is close to the PHP timeout limit. <i>(This might be a good way'
+ . ' to import large files, however it can break transactions.)</i>'
+ ) . '</label><br />';
+ $html .= ' </div>';
+
+ if (! (isset($timeout_passed) && $timeout_passed)) {
+ $html .= ' <div class="formelementrow">';
+ $html .= ' <label for="text_skip_queries">'
+ . __(
+ 'Skip this number of queries (for SQL) or lines (for other '
+ . 'formats), starting from the first one:'
+ )
+ . '</label>';
+ $html .= ' <input type="number" name="skip_queries" value="'
+ . PMA_pluginGetDefault('Import', 'skip_queries')
+ . '" id="text_skip_queries" min="0" />';
+ $html .= ' </div>';
+
+ } else {
+ // If timeout has passed,
+ // do not show the Skip dialog to avoid the risk of someone
+ // entering a value here that would interfere with "skip"
+ $html .= ' <input type="hidden" name="skip_queries" value="'
+ . PMA_pluginGetDefault('Import', 'skip_queries')
+ . '" id="text_skip_queries" />';
+ }
+
+ $html .= ' </div>';
+
+ return $html;
+}
+
+/**
+ * Prints Html For Display Import options : Format
+ *
+ * @param Array $import_list import list
+ *
+ * @return string
+ */
+function PMA_getHtmlForImportOptionsFormat($import_list)
+{
+ $html = ' <div class="importoptions">';
+ $html .= ' <h3>' . __('Format:') . '</h3>';
+ $html .= PMA_pluginGetChoice('Import', 'format', $import_list);
+ $html .= ' <div id="import_notification"></div>';
+ $html .= ' </div>';
+
+ $html .= ' <div class="importoptions" id="format_specific_opts">';
+ $html .= ' <h3>' . __('Format-Specific Options:') . '</h3>';
+ $html .= ' <p class="no_js_msg" id="scroll_to_options_msg">'
+ . 'Scroll down to fill in the options for the selected format '
+ . 'and ignore the options for other formats.</p>';
+ $html .= PMA_pluginGetOptions('Import', $import_list);
+ $html .= ' </div>';
+ $html .= ' <div class="clearfloat"></div>';
+
+ // Encoding setting form appended by Y.Kawada
+ if (function_exists('PMA_Kanji_encodingForm')) {
+ $html .= ' <div class="importoptions" id="kanji_encoding">';
+ $html .= ' <h3>' . __('Encoding Conversion:') . '</h3>';
+ $html .= PMA_Kanji_encodingForm();
+ $html .= ' </div>';
+
+ }
+ $html .= "\n";
+
+ return $html;
+}
+
+/**
+ * Prints Html For Display Import options : submit
+ *
+ * @return string
+ */
+function PMA_getHtmlForImportOptionsSubmit()
+{
+ $html = ' <div class="importoptions" id="submit">';
+ $html .= ' <input type="submit" value="' . __('Go') . '" id="buttonGo" />';
+ $html .= ' </div>';
+
+ return $html;
+}
+
+/**
+ * Prints Html For Display Import
+ *
+ * @param int $upload_id The selected upload id
+ * @param String $import_type Import type: server, database, table
+ * @param String $db Selected DB
+ * @param String $table Selected Table
+ * @param int $max_upload_size Max upload size
+ * @param Array $import_list Import list
+ * @param String $timeout_passed Timeout passed
+ * @param String $offset Timeout offset
+ *
+ * @return string
+ */
+function PMA_getHtmlForImport(
+ $upload_id, $import_type, $db, $table,
+ $max_upload_size, $import_list, $timeout_passed, $offset
+) {
+ global $SESSION_KEY;
+ $html = '';
+ $html .= '<iframe id="import_upload_iframe" name="import_upload_iframe" '
+ . 'width="1" height="1" style="display: none;"></iframe>';
+ $html .= '<div id="import_form_status" style="display: none;"></div>';
+ $html .= '<div id="importmain">';
+ $html .= ' <img src="' . $GLOBALS['pmaThemeImage'] . 'ajax_clock_small.gif" '
+ . 'width="16" height="16" alt="ajax clock" style="display: none;" />';
+
+ $html .= PMA_getHtmlForImportJS($upload_id);
+
+ $html .= ' <form action="import.php" method="post" '
+ . 'enctype="multipart/form-data"';
+ $html .= ' name="import"';
+ if ($_SESSION[$SESSION_KEY]["handler"] != "UploadNoplugin") {
+ $html .= ' target="import_upload_iframe"';
+ }
+ $html .= ' class="ajax"';
+ $html .= '>';
+ $html .= ' <input type="hidden" name="';
+ $html .= $_SESSION[$SESSION_KEY]['handler']::getIdKey();
+ $html .= '" value="' . $upload_id . '" />';
+
+ $html .= PMA_getHtmlForHiddenInputs($import_type, $db, $table);
+
+ $html .= PMA_getHtmlForExportOptions($import_type, $db, $table);
+
+ $html .= PMA_getHtmlForImportOptionsFile($max_upload_size, $import_list);
+
+ $html .= PMA_getHtmlForImportOptionsPartialImport($timeout_passed, $offset);
+
+ $html .= PMA_getHtmlForImportOptionsFormat($import_list);
+
+ $html .= PMA_getHtmlForImportOptionsSubmit();
+
+ $html .= '</form>';
+ $html .= '</div>';
+
+ return $html;
+}
+
+/**
+ * Prints javascript for upload with plugin, upload process bar
+ *
+ * @param int $upload_id The selected upload id
+ *
+ * @return string
+ */
+function PMA_getHtmlForImportWithPlugin($upload_id)
+{
+ //some variable for javasript
+ $ajax_url = "import_status.php?id=" . $upload_id . "&"
+ . PMA_URL_getCommon(array('import_status'=>1), '&');
+ $promot_str = PMA_jsFormat(
+ __(
+ 'The file being uploaded is probably larger than '
+ . 'the maximum allowed size or this is a known bug in webkit '
+ . 'based (Safari, Google Chrome, Arora etc.) browsers.'
+ ),
+ false
+ );
+ $statustext_str = PMA_escapeJsString(__('%s of %s'));
+ $upload_str = PMA_jsFormat(__('Uploading your import file…'), false);
+ $second_str = PMA_jsFormat(__('%s/sec.'), false);
+ $remaining_min = PMA_jsFormat(__('About %MIN min. %SEC sec. remaining.'), false);
+ $remaining_second = PMA_jsFormat(__('About %SEC sec. remaining.'), false);
+ $processed_str = PMA_jsFormat(
+ __('The file is being processed, please be patient.'),
+ false
+ );
+ $import_url = PMA_URL_getCommon(array('import_status'=>1), '&');
+
+ //start output
+ $html = 'var finished = false; ';
+ $html .= 'var percent = 0.0; ';
+ $html .= 'var total = 0; ';
+ $html .= 'var complete = 0; ';
+ $html .= 'var original_title = '
+ . 'parent && parent.document ? parent.document.title : false; ';
+ $html .= 'var import_start; ';
+
+ $html .= 'var perform_upload = function () { ';
+ $html .= 'new $.getJSON( ';
+ $html .= ' "' . $ajax_url . '", ';
+ $html .= ' {}, ';
+ $html .= ' function(response) { ';
+ $html .= ' finished = response.finished; ';
+ $html .= ' percent = response.percent; ';
+ $html .= ' total = response.total; ';
+ $html .= ' complete = response.complete; ';
+
+ $html .= ' if (total==0 && complete==0 && percent==0) { ';
+ $img_tag = '<img src="'. $GLOBALS['pmaThemeImage'] . 'ajax_clock_small.gif"';
+ $html .= ' $("#upload_form_status_info").html(\''
+ . $img_tag . ' width="16" height="16" alt="ajax clock" /> '
+ . $promot_str . '\'); ';
+ $html .= ' $("#upload_form_status").css("display", "none"); ';
+ $html .= ' } else { ';
+ $html .= ' var now = new Date(); ';
+ $html .= ' now = Date.UTC( ';
+ $html .= ' now.getFullYear(), now.getMonth(), now.getDate(), ';
+ $html .= ' now.getHours(), now.getMinutes(), now.getSeconds()) ';
+ $html .= ' + now.getMilliseconds() - 1000; ';
+ $html .= ' var statustext = $.sprintf("' . $statustext_str . '", ';
+ $html .= ' formatBytes(complete, 1, PMA_messages.strDecimalSeparator), ';
+ $html .= ' formatBytes(total, 1, PMA_messages.strDecimalSeparator) ';
+ $html .= ' ); ';
+
+ $html .= ' if ($("#importmain").is(":visible")) { ';
+ // show progress UI
+ $html .= ' $("#importmain").hide(); ';
+ $html .= ' $("#import_form_status") ';
+ $html .= ' .html(\'<div class="upload_progress">'
+ . '<div class="upload_progress_bar_outer"><div class="percentage">'
+ . '</div><div id="status" class="upload_progress_bar_inner">'
+ . '<div class="percentage"></div></div></div><div>'
+ . '<img src="'. $GLOBALS['pmaThemeImage']
+ . 'ajax_clock_small.gif" width="16" height="16" alt="ajax clock" /> '
+ . $upload_str . '</div><div id="statustext"></div></div>\') ';
+ $html .= ' .show(); ';
+ $html .= ' import_start = now; ';
+ $html .= ' } ';
+ $html .= ' else if (percent > 9 || complete > 2000000) { ';
+ // calculate estimated time
+ $html .= ' var used_time = now - import_start; ';
+ $html .= ' var seconds = '
+ . 'parseInt(((total - complete) / complete) * used_time / 1000); ';
+ $html .= ' var speed = $.sprintf("' . $second_str . '"';
+ $html .= ' , formatBytes(complete / used_time * 1000, 1,'
+ . ' PMA_messages.strDecimalSeparator)); ';
+
+ $html .= ' var minutes = parseInt(seconds / 60); ';
+ $html .= ' seconds %= 60; ';
+ $html .= ' var estimated_time; ';
+ $html .= ' if (minutes > 0) { ';
+ $html .= ' estimated_time = "' . $remaining_min . '"';
+ $html .= ' .replace("%MIN", minutes).replace("%SEC", seconds); ';
+ $html .= ' } ';
+ $html .= ' else { ';
+ $html .= ' estimated_time = "' . $remaining_second . '"';
+ $html .= ' .replace("%SEC", seconds); ';
+ $html .= ' } ';
+
+ $html .= ' statustext += "<br />" + speed + "<br /><br />" '
+ . '+ estimated_time; ';
+ $html .= ' } ';
+
+ $html .= ' var percent_str = Math.round(percent) + "%"; ';
+ $html .= ' $("#status").animate({width: percent_str}, 150); ';
+ $html .= ' $(".percentage").text(percent_str); ';
+
+ // show percent in window title
+ $html .= ' if (original_title !== false) { ';
+ $html .= ' parent.document.title = percent_str + " - " + original_title; ';
+ $html .= ' } ';
+ $html .= ' else { ';
+ $html .= ' document.title = percent_str + " - " + original_title; ';
+ $html .= ' } ';
+ $html .= ' $("#statustext").html(statustext); ';
+ $html .= ' } ';
+
+ $html .= ' if (finished == true) { ';
+ $html .= ' if (original_title !== false) { ';
+ $html .= ' parent.document.title = original_title; ';
+ $html .= ' } ';
+ $html .= ' else { ';
+ $html .= ' document.title = original_title; ';
+ $html .= ' } ';
+ $html .= ' $("#importmain").hide(); ';
+ // loads the message, either success or mysql error
+ $html .= ' $("#import_form_status") ';
+ $html .= ' .html(\'<img src="' . $GLOBALS['pmaThemeImage']
+ . 'ajax_clock_small.gif" width="16" height="16" alt="ajax clock" /> '
+ . $processed_str . '\')';
+ $html .= ' .show(); ';
+ $html .= ' $("#import_form_status").load("import_status.php?'
+ . 'message=true&' . $import_url . '"); ';
+ $html .= ' PMA_reloadNavigation(); ';
+
+ // if finished
+ $html .= ' } ';
+ $html .= ' else { ';
+ $html .= ' setTimeout(perform_upload, 1000); ';
+ $html .= ' } ';
+ $html .= '}); ';
+ $html .= '}; ';
+ $html .= 'setTimeout(perform_upload, 1000); ';
+
+ return $html;
+}
+
+?>
diff --git a/libraries/display_import_ajax.lib.php b/libraries/display_import_ajax.lib.php
new file mode 100644
index 0000000000..275db02a7d
--- /dev/null
+++ b/libraries/display_import_ajax.lib.php
@@ -0,0 +1,129 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+* Handles plugins that show the upload progress
+*
+* @package PhpMyAdmin
+*/
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ * constant for differenciating array in $_SESSION variable
+ */
+$SESSION_KEY = '__upload_status';
+
+/**
+ * sets default plugin for handling the import process
+ */
+$_SESSION[$SESSION_KEY]["handler"] = "";
+
+/**
+ * unique ID for each upload
+ */
+$upload_id = uniqid("");
+
+/**
+ * list of available plugins
+ *
+ * Each plugin has own checkfunction in display_import_ajax.lib.php
+ * and own file with functions in upload_#KEY#.php
+ */
+$plugins = array(
+ // PHP 5.4 session-based upload progress is problematic, see bug 3964
+ //"session",
+ "progress",
+ "apc",
+ "noplugin"
+);
+
+// select available plugin
+foreach ($plugins as $plugin) {
+ $check = "PMA_Import_" . $plugin . "Check";
+
+ if ($check()) {
+ $upload_class = "Upload" . ucwords($plugin);
+ $_SESSION[$SESSION_KEY]["handler"] = $upload_class;
+ include_once "plugins/import/upload/" . $upload_class . ".class.php";
+ break;
+ }
+}
+
+/**
+ * Checks if APC bar extension is available and configured correctly.
+ *
+ * @return boolean true if APC extension is available and if rfc1867 is enabled,
+ * false if it is not
+ */
+function PMA_Import_apcCheck()
+{
+ if (! extension_loaded('apc')
+ || ! function_exists('apc_fetch')
+ || ! function_exists('getallheaders')
+ ) {
+ return false;
+ }
+ return (ini_get('apc.enabled') && ini_get('apc.rfc1867'));
+}
+
+/**
+ * Checks if UploadProgress bar extension is available.
+ *
+ * @return boolean true if UploadProgress extension is available,
+ * false if it is not
+ */
+function PMA_Import_progressCheck()
+{
+ if (! function_exists("uploadprogress_get_info")
+ || ! function_exists('getallheaders')
+ ) {
+ return false;
+ }
+ return true;
+}
+
+/**
+ * Checks if PHP 5.4 session upload-progress feature is available.
+ *
+ * @return boolean true if PHP 5.4 session upload-progress is available,
+ * false if it is not
+ */
+function PMA_Import_sessionCheck()
+{
+ if (PMA_PHP_INT_VERSION < 50400
+ || ! ini_get('session.upload_progress.enabled')
+ ) {
+ return false;
+ }
+ return true;
+}
+
+/**
+ * Default plugin for handling import.
+ * If no other plugin is available, noplugin is used.
+ *
+ * @return boolean true
+ */
+function PMA_Import_nopluginCheck()
+{
+ return true;
+}
+
+/**
+ * The function outputs json encoded status of uploaded.
+ * It uses PMA_getUploadStatus, which is defined in plugin's file.
+ *
+ * @param string $id ID of transfer, usually $upload_id
+ * from display_import_ajax.lib.php
+ *
+ * @return void
+ */
+function PMA_importAjaxStatus($id)
+{
+ header('Content-type: application/json');
+ echo json_encode(
+ $_SESSION[$GLOBALS['SESSION_KEY']]['handler']::getUploadStatus($id)
+ );
+}
+?>
diff --git a/libraries/display_select_lang.lib.php b/libraries/display_select_lang.lib.php
new file mode 100644
index 0000000000..0ff4c86cc4
--- /dev/null
+++ b/libraries/display_select_lang.lib.php
@@ -0,0 +1,98 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Code for displaying language selection
+ *
+ * @package PhpMyAdmin
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ * Compares the names of two languages.
+ * Used by uasort in PMA_getLanguageSelectorHtml()
+ *
+ * @param array $a The first language being compared
+ * @param array $b The second language being compared
+ *
+ * @return int the sorted array
+ */
+function PMA_languageCmp($a, $b)
+{
+ return strcmp($a[1], $b[1]);
+}
+
+/**
+ * Returns HTML code for the language selector
+ *
+ * @param boolean $use_fieldset whether to use fieldset for selection
+ * @param boolean $show_doc whether to show documentation links
+ *
+ * @return string
+ *
+ * @access public
+ */
+function PMA_getLanguageSelectorHtml($use_fieldset = false, $show_doc = true)
+{
+ global $lang;
+
+ $retval = '';
+
+ // Display language selection only if there
+ // is more than one language to choose from
+ if (count($GLOBALS['available_languages']) > 1) {
+ $retval .= '<form method="get" action="index.php" class="disableAjax">';
+
+ $_form_params = array(
+ 'db' => $GLOBALS['db'],
+ 'table' => $GLOBALS['table'],
+ );
+ $retval .= PMA_URL_getHiddenInputs($_form_params);
+
+ // For non-English, display "Language" with emphasis because it's
+ // not a proper word in the current language; we show it to help
+ // people recognize the dialog
+ $language_title = __('Language')
+ . (__('Language') != 'Language' ? ' - <em>Language</em>' : '');
+ if ($show_doc) {
+ $language_title .= PMA_Util::showDocu('faq', 'faq7-2');
+ }
+ if ($use_fieldset) {
+ $retval .= '<fieldset><legend lang="en" dir="ltr">'
+ . $language_title . '</legend>';
+ } else {
+ $retval .= '<bdo lang="en" dir="ltr"><label for="sel-lang">'
+ . $language_title . ': </label></bdo>';
+ }
+
+ $retval .= '<select name="lang" class="autosubmit" lang="en"'
+ . ' dir="ltr" id="sel-lang">';
+
+ uasort($GLOBALS['available_languages'], 'PMA_languageCmp');
+ foreach ($GLOBALS['available_languages'] as $id => $tmplang) {
+ $lang_name = PMA_languageName($tmplang);
+
+ //Is current one active?
+ if ($lang == $id) {
+ $selected = ' selected="selected"';
+ } else {
+ $selected = '';
+ }
+ $retval .= '<option value="' . $id . '"' . $selected . '>';
+ $retval .= $lang_name;
+ $retval .= '</option>';
+ }
+
+ $retval .= '</select>';
+
+ if ($use_fieldset) {
+ $retval .= '</fieldset>';
+ }
+
+ $retval .= '</form>';
+ }
+ return $retval;
+}
+
+?>
diff --git a/libraries/display_structure.inc.php b/libraries/display_structure.inc.php
new file mode 100644
index 0000000000..dade7f4938
--- /dev/null
+++ b/libraries/display_structure.inc.php
@@ -0,0 +1,267 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Displays the table structure ('show table' works correct since 3.23.03)
+ *
+ * @package PhpMyAdmin
+ */
+
+require_once 'libraries/common.inc.php';
+require_once 'libraries/mysql_charsets.inc.php';
+require_once 'libraries/structure.lib.php';
+require_once 'libraries/index.lib.php';
+require_once 'libraries/tbl_info.inc.php';
+
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/* TABLE INFORMATION */
+// table header
+
+
+$HideStructureActions = '';
+if (PMA_Util::showText('ActionLinksMode')
+ && $GLOBALS['cfg']['HideStructureActions'] === true
+) {
+ $HideStructureActions .= ' HideStructureActions';
+}
+
+$html_form = '<form method="post" action="tbl_structure.php" name="fieldsForm" '
+. 'id="fieldsForm" class="ajax' . $HideStructureActions . '">';
+
+$response->addHTML($html_form);
+$response->addHTML(PMA_URL_getHiddenInputs($db, $table));
+
+$tabletype = '<input type="hidden" name="table_type" value=';
+if ($db_is_information_schema) {
+ $tabletype .= '"information_schema" />';
+} else if ($tbl_is_view) {
+ $tabletype .= '"view" />';
+} else {
+ $tabletype .= '"table" />';
+}
+$response->addHTML($tabletype);
+
+$tablestructure = '<table id="tablestructure" class="data">';
+$response->addHTML($tablestructure);
+
+
+$response->addHTML(
+ PMA_getHtmlForTableStructureHeader(
+ $db_is_information_schema,
+ $tbl_is_view
+ )
+);
+
+$response->addHTML('<tbody>');
+
+// table body
+
+// prepare comments
+$comments_map = array();
+$mime_map = array();
+
+if ($GLOBALS['cfg']['ShowPropertyComments']) {
+ include_once 'libraries/transformations.lib.php';
+ $comments_map = PMA_getComments($db, $table);
+ if ($cfgRelation['mimework'] && $cfg['BrowseMIME']) {
+ $mime_map = PMA_getMIME($db, $table, true);
+ }
+}
+
+$rownum = 0;
+$columns_list = array();
+$save_row = array();
+$odd_row = true;
+foreach ($fields as $row) {
+ $save_row[] = $row;
+ $rownum++;
+ $columns_list[] = $row['Field'];
+
+ $type = $row['Type'];
+ $extracted_columnspec = PMA_Util::extractColumnSpec($row['Type']);
+
+ if ('set' == $extracted_columnspec['type']
+ || 'enum' == $extracted_columnspec['type']
+ ) {
+ $type_nowrap = '';
+ } else {
+ $type_nowrap = ' class="nowrap"';
+ }
+ $type = $extracted_columnspec['print_type'];
+ if (empty($type)) {
+ $type = ' ';
+ }
+
+ $field_charset = '';
+ if ($extracted_columnspec['can_contain_collation']
+ && ! empty($row['Collation'])
+ ) {
+ $field_charset = $row['Collation'];
+ }
+
+ // Display basic mimetype [MIME]
+ if ($cfgRelation['commwork']
+ && $cfgRelation['mimework']
+ && $cfg['BrowseMIME']
+ && isset($mime_map[$row['Field']]['mimetype'])
+ ) {
+ $type_mime = '<br />MIME: '
+ . str_replace('_', '/', $mime_map[$row['Field']]['mimetype']);
+ } else {
+ $type_mime = '';
+ }
+
+ $attribute = $extracted_columnspec['attribute'];
+
+ // prepare a common variable to reuse below; however,
+ // in case of a VIEW, $analyzed_sql[0]['create_table_fields'] is empty
+ if (isset($analyzed_sql[0]['create_table_fields'][$row['Field']])) {
+ $tempField = $analyzed_sql[0]['create_table_fields'][$row['Field']];
+ } else {
+ $tempField = array();
+ }
+
+ // MySQL 4.1.2+ TIMESTAMP options
+ // (if on_update_current_timestamp is set, then it's TRUE)
+ if (isset($tempField['on_update_current_timestamp'])) {
+ $attribute = 'on update CURRENT_TIMESTAMP';
+ }
+
+ // here, we have a TIMESTAMP that SHOW FULL COLUMNS reports as having the
+ // NULL attribute, but SHOW CREATE TABLE says the contrary. Believe
+ // the latter.
+ if (! empty($tempField['type'])
+ && $tempField['type'] == 'TIMESTAMP'
+ && $tempField['timestamp_not_null']
+ ) {
+ $row['Null'] = '';
+ }
+
+
+ if (! isset($row['Default'])) {
+ if ($row['Null'] == 'YES') {
+ $row['Default'] = '<i>NULL</i>';
+ }
+ } else {
+ $row['Default'] = htmlspecialchars($row['Default']);
+ }
+
+ $field_encoded = urlencode($row['Field']);
+ $field_name = htmlspecialchars($row['Field']);
+ $displayed_field_name = $field_name;
+
+ // underline commented fields and display a hover-title (CSS only)
+
+ if (isset($comments_map[$row['Field']])) {
+ $displayed_field_name = '<span class="commented_column" title="'
+ . htmlspecialchars($comments_map[$row['Field']]) . '">'
+ . $field_name . '</span>';
+ }
+
+ if ($primary && $primary->hasColumn($field_name)) {
+ $displayed_field_name = '<u>' . $field_name . '</u>';
+ }
+ $response->addHTML(
+ '<tr class="' . ($odd_row ? 'odd': 'even') . '">'
+ );
+ $odd_row = !$odd_row;
+
+ $response->addHTML(
+ PMA_getHtmlTableStructureRow(
+ $row, $rownum, $displayed_field_name,
+ $type_nowrap, $extracted_columnspec, $type_mime,
+ $field_charset, $attribute, $tbl_is_view,
+ $db_is_information_schema, $url_query, $field_encoded, $titles, $table
+ )
+ );
+
+ if (! $tbl_is_view && ! $db_is_information_schema) {
+ $response->addHTML(
+ PMA_getHtmlForActionsInTableStructure(
+ $type, $tbl_storage_engine, $primary,
+ $field_name, $url_query, $titles, $row, $rownum,
+ $hidden_titles, $columns_with_unique_index
+ )
+ );
+ } // end if (! $tbl_is_view && ! $db_is_information_schema)
+
+ $response->addHTML('</tr>');
+
+ unset($field_charset);
+} // end foreach
+
+$response->addHTML('</tbody></table>');
+
+$response->addHTML(
+ PMA_getHtmlForCheckAllTableColumn(
+ $pmaThemeImage, $text_dir, $tbl_is_view,
+ $db_is_information_schema, $tbl_storage_engine
+ )
+);
+
+$response->addHTML(
+ '</form><hr />'
+);
+$response->addHTML(
+ PMA_getHtmlDivForMoveColumnsDialog()
+);
+
+/**
+ * Work on the table
+ */
+
+if ($tbl_is_view) {
+ $response->addHTML(PMA_getHtmlForEditView($url_params));
+}
+$response->addHTML(
+ PMA_getHtmlForOptionalActionLinks(
+ $url_query, $tbl_is_view, $db_is_information_schema,
+ $tbl_storage_engine, $cfgRelation
+ )
+);
+
+if (! $tbl_is_view && ! $db_is_information_schema) {
+ $response->addHTML('<br />');
+ $response->addHTML(PMA_getHtmlForAddColumn($columns_list));
+}
+
+/**
+ * Displays indexes
+ */
+
+if (! $tbl_is_view
+ && ! $db_is_information_schema
+ && 'ARCHIVE' != $tbl_storage_engine
+) {
+ //return the list of index
+ $response->addJSON(
+ 'indexes_list',
+ PMA_Index::getView($GLOBALS['table'], $GLOBALS['db'])
+ );
+ $response->addHTML(PMA_getHtmlForDisplayIndexes());
+}
+
+/**
+ * Displays Space usage and row statistics
+ */
+// BEGIN - Calc Table Space
+// Get valid statistics whatever is the table type
+if ($cfg['ShowStats']) {
+ //get table stats in HTML format
+ $tablestats = PMA_getHtmlForDisplayTableStats(
+ $showtable, $table_info_num_rows, $tbl_is_view,
+ $db_is_information_schema, $tbl_storage_engine,
+ $url_query, $tbl_collation
+ );
+ //returning the response in JSON format to be used by Ajax
+ $response->addJSON('tableStat', $tablestats);
+ $response->addHTML($tablestats);
+}
+// END - Calc Table Space
+
+$response->addHTML(
+ '<div class="clearfloat"></div>'
+);
+?>
diff --git a/libraries/engines/bdb.lib.php b/libraries/engines/bdb.lib.php
new file mode 100644
index 0000000000..d5203cf7ea
--- /dev/null
+++ b/libraries/engines/bdb.lib.php
@@ -0,0 +1,84 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * @package PhpMyAdmin-Engines
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ *
+ * @package PhpMyAdmin-Engines
+ */
+class PMA_StorageEngine_Bdb extends PMA_StorageEngine
+{
+ /**
+ * Returns array with variable names related to this storage engine
+ *
+ * @return array variable names
+ */
+ function getVariables()
+ {
+ return array(
+ 'version_bdb' => array(
+ 'title' => __('Version information'),
+ ),
+ 'bdb_cache_size' => array(
+ 'type' => PMA_ENGINE_DETAILS_TYPE_SIZE,
+ ),
+ 'bdb_home' => array(
+ ),
+ 'bdb_log_buffer_size' => array(
+ 'type' => PMA_ENGINE_DETAILS_TYPE_SIZE,
+ ),
+ 'bdb_logdir' => array(
+ ),
+ 'bdb_max_lock' => array(
+ 'type' => PMA_ENGINE_DETAILS_TYPE_NUMERIC,
+ ),
+ 'bdb_shared_data' => array(
+ ),
+ 'bdb_tmpdir' => array(
+ ),
+ 'bdb_data_direct' => array(
+ ),
+ 'bdb_lock_detect' => array(
+ ),
+ 'bdb_log_direct' => array(
+ ),
+ 'bdb_no_recover' => array(
+ ),
+ 'bdb_no_sync' => array(
+ ),
+ 'skip_sync_bdb_logs' => array(
+ ),
+ 'sync_bdb_logs' => array(
+ ),
+ );
+ }
+
+ /**
+ * Returns the pattern to be used in the query for SQL variables
+ * related to this storage engine
+ *
+ * @return string LIKE pattern
+ */
+ function getVariablesLikePattern()
+ {
+ return '%bdb%';
+ }
+
+ /**
+ * returns string with filename for the MySQL helppage
+ * about this storage engine
+ *
+ * @return string mysql helppage filename
+ */
+ function getMysqlHelpPage()
+ {
+ return 'bdb';
+ }
+}
+
+?>
diff --git a/libraries/engines/berkeleydb.lib.php b/libraries/engines/berkeleydb.lib.php
new file mode 100644
index 0000000000..d9558860fc
--- /dev/null
+++ b/libraries/engines/berkeleydb.lib.php
@@ -0,0 +1,24 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * @package PhpMyAdmin-Engines
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ * Load BDB class.
+ */
+require_once './libraries/engines/bdb.lib.php';
+
+/**
+ * This is same as BDB
+ *
+ * @package PhpMyAdmin-Engines
+ */
+class PMA_StorageEngine_berkeleydb extends PMA_StorageEngine_Bdb
+{
+}
+
+?>
diff --git a/libraries/engines/binlog.lib.php b/libraries/engines/binlog.lib.php
new file mode 100644
index 0000000000..ea81738967
--- /dev/null
+++ b/libraries/engines/binlog.lib.php
@@ -0,0 +1,28 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * @package PhpMyAdmin-Engines
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ *
+ * @package PhpMyAdmin-Engines
+ */
+class PMA_StorageEngine_binlog extends PMA_StorageEngine
+{
+ /**
+ * returns string with filename for the MySQL helppage
+ * about this storage engine
+ *
+ * @return string mysql helppage filename
+ */
+ function getMysqlHelpPage()
+ {
+ return 'binary-log';
+ }
+}
+
+?>
diff --git a/libraries/engines/innobase.lib.php b/libraries/engines/innobase.lib.php
new file mode 100644
index 0000000000..88dbecde3e
--- /dev/null
+++ b/libraries/engines/innobase.lib.php
@@ -0,0 +1,25 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * The Innobase storage engine
+ *
+ * @package PhpMyAdmin-Engines
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ *
+ */
+require_once './libraries/engines/innodb.lib.php';
+
+/**
+ * The Innobase storage engine
+ *
+ * @package PhpMyAdmin-Engines
+ */
+class PMA_StorageEngine_innobase extends PMA_StorageEngine_innodb
+{
+}
+?>
diff --git a/libraries/engines/innodb.lib.php b/libraries/engines/innodb.lib.php
new file mode 100644
index 0000000000..c7b639b25a
--- /dev/null
+++ b/libraries/engines/innodb.lib.php
@@ -0,0 +1,411 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * The InnoDB storage engine
+ *
+ * @package PhpMyAdmin-Engines
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ * The InnoDB storage engine
+ *
+ * @package PhpMyAdmin-Engines
+ */
+class PMA_StorageEngine_innodb extends PMA_StorageEngine
+{
+ /**
+ * Returns array with variable names related to InnoDB storage engine
+ *
+ * @return array variable names
+ */
+ function getVariables()
+ {
+ return array(
+ 'innodb_data_home_dir' => array(
+ 'title' => __('Data home directory'),
+ 'desc' => __('The common part of the directory path for all InnoDB data files.'),
+ ),
+ 'innodb_data_file_path' => array(
+ 'title' => __('Data files'),
+ ),
+ 'innodb_autoextend_increment' => array(
+ 'title' => __('Autoextend increment'),
+ 'desc' => __('The increment size for extending the size of an autoextending tablespace when it becomes full.'),
+ 'type' => PMA_ENGINE_DETAILS_TYPE_NUMERIC,
+ ),
+ 'innodb_buffer_pool_size' => array(
+ 'title' => __('Buffer pool size'),
+ 'desc' => __('The size of the memory buffer InnoDB uses to cache data and indexes of its tables.'),
+ 'type' => PMA_ENGINE_DETAILS_TYPE_SIZE,
+ ),
+ 'innodb_additional_mem_pool_size' => array(
+ 'title' => 'innodb_additional_mem_pool_size',
+ 'type' => PMA_ENGINE_DETAILS_TYPE_SIZE,
+ ),
+ 'innodb_buffer_pool_awe_mem_mb' => array(
+ 'type' => PMA_ENGINE_DETAILS_TYPE_SIZE,
+ ),
+ 'innodb_checksums' => array(
+ ),
+ 'innodb_commit_concurrency' => array(
+ ),
+ 'innodb_concurrency_tickets' => array(
+ 'type' => PMA_ENGINE_DETAILS_TYPE_NUMERIC,
+ ),
+ 'innodb_doublewrite' => array(
+ ),
+ 'innodb_fast_shutdown' => array(
+ ),
+ 'innodb_file_io_threads' => array(
+ 'type' => PMA_ENGINE_DETAILS_TYPE_NUMERIC,
+ ),
+ 'innodb_file_per_table' => array(
+ ),
+ 'innodb_flush_log_at_trx_commit' => array(
+ ),
+ 'innodb_flush_method' => array(
+ ),
+ 'innodb_force_recovery' => array(
+ ),
+ 'innodb_lock_wait_timeout' => array(
+ 'type' => PMA_ENGINE_DETAILS_TYPE_NUMERIC,
+ ),
+ 'innodb_locks_unsafe_for_binlog' => array(
+ ),
+ 'innodb_log_arch_dir' => array(
+ ),
+ 'innodb_log_archive' => array(
+ ),
+ 'innodb_log_buffer_size' => array(
+ 'type' => PMA_ENGINE_DETAILS_TYPE_SIZE,
+ ),
+ 'innodb_log_file_size' => array(
+ 'type' => PMA_ENGINE_DETAILS_TYPE_SIZE,
+ ),
+ 'innodb_log_files_in_group' => array(
+ 'type' => PMA_ENGINE_DETAILS_TYPE_NUMERIC,
+ ),
+ 'innodb_log_group_home_dir' => array(
+ ),
+ 'innodb_max_dirty_pages_pct' => array(
+ 'type' => PMA_ENGINE_DETAILS_TYPE_NUMERIC,
+ ),
+ 'innodb_max_purge_lag' => array(
+ ),
+ 'innodb_mirrored_log_groups' => array(
+ 'type' => PMA_ENGINE_DETAILS_TYPE_NUMERIC,
+ ),
+ 'innodb_open_files' => array(
+ 'type' => PMA_ENGINE_DETAILS_TYPE_NUMERIC,
+ ),
+ 'innodb_support_xa' => array(
+ ),
+ 'innodb_sync_spin_loops' => array(
+ 'type' => PMA_ENGINE_DETAILS_TYPE_NUMERIC,
+ ),
+ 'innodb_table_locks' => array(
+ 'type' => PMA_ENGINE_DETAILS_TYPE_BOOLEAN,
+ ),
+ 'innodb_thread_concurrency' => array(
+ 'type' => PMA_ENGINE_DETAILS_TYPE_NUMERIC,
+ ),
+ 'innodb_thread_sleep_delay' => array(
+ 'type' => PMA_ENGINE_DETAILS_TYPE_NUMERIC,
+ ),
+ );
+ }
+
+ /**
+ * Returns the pattern to be used in the query for SQL variables
+ * related to InnoDb storage engine
+ *
+ * @return string SQL query LIKE pattern
+ */
+ function getVariablesLikePattern()
+ {
+ return 'innodb\\_%';
+ }
+
+ /**
+ * Get information pages
+ *
+ * @return array detail pages
+ */
+ function getInfoPages()
+ {
+ if ($this->support < PMA_ENGINE_SUPPORT_YES) {
+ return array();
+ }
+ $pages = array();
+ $pages['Bufferpool'] = __('Buffer Pool');
+ $pages['Status'] = __('InnoDB Status');
+ return $pages;
+ }
+
+ /**
+ * returns html tables with stats over inno db buffer pool
+ *
+ * @return string html table with stats
+ */
+ function getPageBufferpool()
+ {
+ // The following query is only possible because we know
+ // that we are on MySQL 5 here (checked above)!
+ // side note: I love MySQL 5 for this. :-)
+ $sql = '
+ SHOW STATUS
+ WHERE Variable_name LIKE \'Innodb\\_buffer\\_pool\\_%\'
+ OR Variable_name = \'Innodb_page_size\';';
+ $status = $GLOBALS['dbi']->fetchResult($sql, 0, 1);
+
+ $output = '<table class="data" id="table_innodb_bufferpool_usage">' . "\n"
+ . ' <caption class="tblHeaders">' . "\n"
+ . ' ' . __('Buffer Pool Usage') . "\n"
+ . ' </caption>' . "\n"
+ . ' <tfoot>' . "\n"
+ . ' <tr>' . "\n"
+ . ' <th colspan="2">' . "\n"
+ . ' ' . __('Total') . "\n"
+ . ' : '
+ . PMA_Util::formatNumber(
+ $status['Innodb_buffer_pool_pages_total'], 0
+ )
+ . '&nbsp;' . __('pages')
+ . ' / '
+ . join(
+ '&nbsp;',
+ PMA_Util::formatByteDown(
+ $status['Innodb_buffer_pool_pages_total']
+ * $status['Innodb_page_size']
+ )
+ ) . "\n"
+ . ' </th>' . "\n"
+ . ' </tr>' . "\n"
+ . ' </tfoot>' . "\n"
+ . ' <tbody>' . "\n"
+ . ' <tr class="odd">' . "\n"
+ . ' <th>' . __('Free pages') . '</th>' . "\n"
+ . ' <td class="value">'
+ . PMA_Util::formatNumber(
+ $status['Innodb_buffer_pool_pages_free'], 0
+ )
+ . '</td>' . "\n"
+ . ' </tr>' . "\n"
+ . ' <tr class="even">' . "\n"
+ . ' <th>' . __('Dirty pages') . '</th>' . "\n"
+ . ' <td class="value">'
+ . PMA_Util::formatNumber(
+ $status['Innodb_buffer_pool_pages_dirty'], 0
+ )
+ . '</td>' . "\n"
+ . ' </tr>' . "\n"
+ . ' <tr class="odd">' . "\n"
+ . ' <th>' . __('Pages containing data') . '</th>' . "\n"
+ . ' <td class="value">'
+ . PMA_Util::formatNumber(
+ $status['Innodb_buffer_pool_pages_data'], 0
+ ) . "\n"
+ . '</td>' . "\n"
+ . ' </tr>' . "\n"
+ . ' <tr class="even">' . "\n"
+ . ' <th>' . __('Pages to be flushed') . '</th>' . "\n"
+ . ' <td class="value">'
+ . PMA_Util::formatNumber(
+ $status['Innodb_buffer_pool_pages_flushed'], 0
+ ) . "\n"
+ . '</td>' . "\n"
+ . ' </tr>' . "\n"
+ . ' <tr class="odd">' . "\n"
+ . ' <th>' . __('Busy pages') . '</th>' . "\n"
+ . ' <td class="value">'
+ . PMA_Util::formatNumber(
+ $status['Innodb_buffer_pool_pages_misc'], 0
+ ) . "\n"
+ . '</td>' . "\n"
+ . ' </tr>';
+
+ // not present at least since MySQL 5.1.40
+ if (isset($status['Innodb_buffer_pool_pages_latched'])) {
+ $output .= ' <tr class="even">'
+ . ' <th>' . __('Latched pages') . '</th>'
+ . ' <td class="value">'
+ . PMA_Util::formatNumber(
+ $status['Innodb_buffer_pool_pages_latched'], 0
+ )
+ . '</td>'
+ . ' </tr>';
+ }
+
+ $output .= ' </tbody>' . "\n"
+ . '</table>' . "\n\n"
+ . '<table class="data" id="table_innodb_bufferpool_activity">' . "\n"
+ . ' <caption class="tblHeaders">' . "\n"
+ . ' ' . __('Buffer Pool Activity') . "\n"
+ . ' </caption>' . "\n"
+ . ' <tbody>' . "\n"
+ . ' <tr class="odd">' . "\n"
+ . ' <th>' . __('Read requests') . '</th>' . "\n"
+ . ' <td class="value">'
+ . PMA_Util::formatNumber(
+ $status['Innodb_buffer_pool_read_requests'], 0
+ ) . "\n"
+ . '</td>' . "\n"
+ . ' </tr>' . "\n"
+ . ' <tr class="even">' . "\n"
+ . ' <th>' . __('Write requests') . '</th>' . "\n"
+ . ' <td class="value">'
+ . PMA_Util::formatNumber(
+ $status['Innodb_buffer_pool_write_requests'], 0
+ ) . "\n"
+ . '</td>' . "\n"
+ . ' </tr>' . "\n"
+ . ' <tr class="odd">' . "\n"
+ . ' <th>' . __('Read misses') . '</th>' . "\n"
+ . ' <td class="value">'
+ . PMA_Util::formatNumber(
+ $status['Innodb_buffer_pool_reads'], 0
+ ) . "\n"
+ . '</td>' . "\n"
+ . ' </tr>' . "\n"
+ . ' <tr class="even">' . "\n"
+ . ' <th>' . __('Write waits') . '</th>' . "\n"
+ . ' <td class="value">'
+ . PMA_Util::formatNumber(
+ $status['Innodb_buffer_pool_wait_free'], 0
+ ) . "\n"
+ . '</td>' . "\n"
+ . ' </tr>' . "\n"
+ . ' <tr class="odd">' . "\n"
+ . ' <th>' . __('Read misses in %') . '</th>' . "\n"
+ . ' <td class="value">'
+ . ($status['Innodb_buffer_pool_read_requests'] == 0
+ ? '---'
+ : htmlspecialchars(
+ PMA_Util::formatNumber(
+ $status['Innodb_buffer_pool_reads'] * 100
+ / $status['Innodb_buffer_pool_read_requests'],
+ 3,
+ 2
+ )
+ ) . ' %') . "\n"
+ . '</td>' . "\n"
+ . ' </tr>' . "\n"
+ . ' <tr class="even">' . "\n"
+ . ' <th>' . __('Write waits in %') . '</th>' . "\n"
+ . ' <td class="value">'
+ . ($status['Innodb_buffer_pool_write_requests'] == 0
+ ? '---'
+ : htmlspecialchars(
+ PMA_Util::formatNumber(
+ $status['Innodb_buffer_pool_wait_free'] * 100
+ / $status['Innodb_buffer_pool_write_requests'],
+ 3,
+ 2
+ )
+ ) . ' %') . "\n"
+ . '</td>' . "\n"
+ . ' </tr>' . "\n"
+ . ' </tbody>' . "\n"
+ . '</table>' . "\n";
+ return $output;
+ }
+
+ /**
+ * returns InnoDB status
+ *
+ * @return string result of SHOW INNODB STATUS inside pre tags
+ */
+ function getPageStatus()
+ {
+ return '<pre id="pre_innodb_status">' . "\n"
+ . htmlspecialchars(
+ $GLOBALS['dbi']->fetchValue('SHOW INNODB STATUS;', 0, 'Status')
+ ) . "\n"
+ . '</pre>' . "\n";
+ }
+
+ /**
+ * Returns content for page $id
+ *
+ * @param string $id page id
+ *
+ * @return string html output
+ */
+ function getPage($id)
+ {
+ if (! array_key_exists($id, $this->getInfoPages())) {
+ return false;
+ }
+
+ $id = 'getPage' . $id;
+
+ return $this->$id();
+ }
+
+ /**
+ * returns string with filename for the MySQL helppage
+ * about this storage engine
+ *
+ * @return string mysql helppage filename
+ */
+ function getMysqlHelpPage()
+ {
+ return 'innodb-storage-engine';
+ }
+
+ /**
+ * Gets the InnoDB plugin version number
+ *
+ * http://www.innodb.com/products/innodb_plugin
+ * (do not confuse this with phpMyAdmin's storage engine plugins!)
+ *
+ * @return string the version number, or empty if not running as a plugin
+ */
+ function getInnodbPluginVersion()
+ {
+ return $GLOBALS['dbi']->fetchValue('SELECT @@innodb_version;');
+ }
+
+ /**
+ * Gets the InnoDB file format
+ *
+ * (works only for the InnoDB plugin)
+ * http://www.innodb.com/products/innodb_plugin
+ * (do not confuse this with phpMyAdmin's storage engine plugins!)
+ *
+ * @return string the InnoDB file format
+ */
+ function getInnodbFileFormat()
+ {
+ return $GLOBALS['dbi']->fetchValue(
+ "SHOW GLOBAL VARIABLES LIKE 'innodb_file_format';", 0, 1
+ );
+ }
+
+ /**
+ * Verifies if this server supports the innodb_file_per_table feature
+ *
+ * (works only for the InnoDB plugin)
+ * http://www.innodb.com/products/innodb_plugin
+ * (do not confuse this with phpMyAdmin's storage engine plugins!)
+ *
+ * @return boolean whether this feature is supported or not
+ */
+ function supportsFilePerTable()
+ {
+ $innodb_file_per_table = $GLOBALS['dbi']->fetchValue(
+ "SHOW GLOBAL VARIABLES LIKE 'innodb_file_per_table';", 0, 1
+ );
+ if ($innodb_file_per_table == 'ON') {
+ return true;
+ } else {
+ return false;
+ }
+
+ }
+}
+
+?>
diff --git a/libraries/engines/memory.lib.php b/libraries/engines/memory.lib.php
new file mode 100644
index 0000000000..8e9fe5fc88
--- /dev/null
+++ b/libraries/engines/memory.lib.php
@@ -0,0 +1,34 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * The MEMORY (HEAP) storage engine
+ *
+ * @package PhpMyAdmin-Engines
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ * The MEMORY (HEAP) storage engine
+ *
+ * @package PhpMyAdmin-Engines
+ */
+class PMA_StorageEngine_memory extends PMA_StorageEngine
+{
+ /**
+ * Returns array with variable names dedicated to MEMORY storage engine
+ *
+ * @return array variable names
+ */
+ function getVariables()
+ {
+ return array(
+ 'max_heap_table_size' => array(
+ 'type' => PMA_ENGINE_DETAILS_TYPE_SIZE,
+ ),
+ );
+ }
+}
+
+?>
diff --git a/libraries/engines/merge.lib.php b/libraries/engines/merge.lib.php
new file mode 100644
index 0000000000..4bccfc79ca
--- /dev/null
+++ b/libraries/engines/merge.lib.php
@@ -0,0 +1,21 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * The MERGE storage engine
+ *
+ * @package PhpMyAdmin-Engines
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ * The MERGE storage engine
+ *
+ * @package PhpMyAdmin-Engines
+ */
+class PMA_StorageEngine_merge extends PMA_StorageEngine
+{
+}
+
+?>
diff --git a/libraries/engines/mrg_myisam.lib.php b/libraries/engines/mrg_myisam.lib.php
new file mode 100644
index 0000000000..d3425365c2
--- /dev/null
+++ b/libraries/engines/mrg_myisam.lib.php
@@ -0,0 +1,33 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * @package PhpMyAdmin-Engines
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ *
+ */
+require_once './libraries/engines/merge.lib.php';
+
+/**
+ *
+ * @package PhpMyAdmin-Engines
+ */
+class PMA_StorageEngine_mrg_myisam extends PMA_StorageEngine_merge
+{
+ /**
+ * returns string with filename for the MySQL helppage
+ * about this storage engine
+ *
+ * @return string mysql helppage filename
+ */
+ function getMysqlHelpPage()
+ {
+ return 'merge-storage-engine';
+ }
+}
+
+?>
diff --git a/libraries/engines/myisam.lib.php b/libraries/engines/myisam.lib.php
new file mode 100644
index 0000000000..5882824bc4
--- /dev/null
+++ b/libraries/engines/myisam.lib.php
@@ -0,0 +1,69 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * The MyISAM storage engine
+ *
+ * @package PhpMyAdmin-Engines
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ * The MyISAM storage engine
+ *
+ * @package PhpMyAdmin-Engines
+ */
+class PMA_StorageEngine_myisam extends PMA_StorageEngine
+{
+ /**
+ * Returns array with variable names dedicated to MyISAM storage engine
+ *
+ * @return array variable names
+ */
+ function getVariables()
+ {
+ return array(
+ 'myisam_data_pointer_size' => array(
+ 'title' => __('Data pointer size'),
+ 'desc' => __('The default pointer size in bytes, to be used by CREATE TABLE for MyISAM tables when no MAX_ROWS option is specified.'),
+ 'type' => PMA_ENGINE_DETAILS_TYPE_SIZE,
+ ),
+ 'myisam_recover_options' => array(
+ 'title' => __('Automatic recovery mode'),
+ 'desc' => __('The mode for automatic recovery of crashed MyISAM tables, as set via the --myisam-recover server startup option.'),
+ ),
+ 'myisam_max_sort_file_size' => array(
+ 'title' => __('Maximum size for temporary sort files'),
+ 'desc' => __('The maximum size of the temporary file MySQL is allowed to use while re-creating a MyISAM index (during REPAIR TABLE, ALTER TABLE, or LOAD DATA INFILE).'),
+ 'type' => PMA_ENGINE_DETAILS_TYPE_SIZE,
+ ),
+ 'myisam_max_extra_sort_file_size' => array(
+ 'title' => __('Maximum size for temporary files on index creation'),
+ 'desc' => __('If the temporary file used for fast MyISAM index creation would be larger than using the key cache by the amount specified here, prefer the key cache method.'),
+ 'type' => PMA_ENGINE_DETAILS_TYPE_SIZE,
+ ),
+ 'myisam_repair_threads' => array(
+ 'title' => __('Repair threads'),
+ 'desc' => __('If this value is greater than 1, MyISAM table indexes are created in parallel (each index in its own thread) during the repair by sorting process.'),
+ 'type' => PMA_ENGINE_DETAILS_TYPE_NUMERIC,
+ ),
+ 'myisam_sort_buffer_size' => array(
+ 'title' => __('Sort buffer size'),
+ 'desc' => __('The buffer that is allocated when sorting MyISAM indexes during a REPAIR TABLE or when creating indexes with CREATE INDEX or ALTER TABLE.'),
+ 'type' => PMA_ENGINE_DETAILS_TYPE_SIZE,
+ ),
+ 'myisam_stats_method' => array(
+ ),
+ 'delay_key_write' => array(
+ ),
+ 'bulk_insert_buffer_size' => array(
+ 'type' => PMA_ENGINE_DETAILS_TYPE_SIZE,
+ ),
+ 'skip_external_locking' => array(
+ ),
+ );
+ }
+}
+
+?>
diff --git a/libraries/engines/ndbcluster.lib.php b/libraries/engines/ndbcluster.lib.php
new file mode 100644
index 0000000000..e57846b601
--- /dev/null
+++ b/libraries/engines/ndbcluster.lib.php
@@ -0,0 +1,55 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * The NDBCLUSTER storage engine
+ *
+ * @package PhpMyAdmin-Engines
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ * The NDBCLUSTER storage engine
+ *
+ * @package PhpMyAdmin-Engines
+ */
+class PMA_StorageEngine_ndbcluster extends PMA_StorageEngine
+{
+ /**
+ * Returns array with variable names realted to NDBCLUSTER storage engine
+ *
+ * @return array variable names
+ */
+ function getVariables()
+ {
+ return array(
+ 'ndb_connectstring' => array(
+ ),
+ );
+ }
+
+ /**
+ * Returns the pattern to be used in the query for SQL variables
+ * related to NDBCLUSTER storage engine
+ *
+ * @return string SQL query LIKE pattern
+ */
+ function getVariablesLikePattern()
+ {
+ return 'ndb\\_%';
+ }
+
+ /**
+ * Returns string with filename for the MySQL help page
+ * about this storage engine
+ *
+ * @return string mysql helppage filename
+ */
+ function getMysqlHelpPage()
+ {
+ return 'ndbcluster';
+ }
+}
+
+?>
diff --git a/libraries/engines/pbxt.lib.php b/libraries/engines/pbxt.lib.php
new file mode 100644
index 0000000000..4b7bcf92db
--- /dev/null
+++ b/libraries/engines/pbxt.lib.php
@@ -0,0 +1,167 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * The PBXT storage engine
+ *
+ * @package PhpMyAdmin-Engines
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ * The PBXT storage engine
+ *
+ * @package PhpMyAdmin-Engines
+ */
+class PMA_StorageEngine_pbxt extends PMA_StorageEngine
+{
+ /**
+ * Returns array with variable names dedicated to PBXT storage engine
+ *
+ * @return array variable names
+ */
+ function getVariables()
+ {
+ return array(
+ 'pbxt_index_cache_size' => array(
+ 'title' => __('Index cache size'),
+ 'desc' => __('This is the amount of memory allocated to the index cache. Default value is 32MB. The memory allocated here is used only for caching index pages.'),
+ 'type' => PMA_ENGINE_DETAILS_TYPE_SIZE
+ ),
+ 'pbxt_record_cache_size' => array(
+ 'title' => __('Record cache size'),
+ 'desc' => __('This is the amount of memory allocated to the record cache used to cache table data. The default value is 32MB. This memory is used to cache changes to the handle data (.xtd) and row pointer (.xtr) files.'),
+ 'type' => PMA_ENGINE_DETAILS_TYPE_SIZE
+ ),
+ 'pbxt_log_cache_size' => array(
+ 'title' => __('Log cache size'),
+ 'desc' => __('The amount of memory allocated to the transaction log cache used to cache on transaction log data. The default is 16MB.'),
+ 'type' => PMA_ENGINE_DETAILS_TYPE_SIZE
+ ),
+ 'pbxt_log_file_threshold' => array(
+ 'title' => __('Log file threshold'),
+ 'desc' => __('The size of a transaction log before rollover, and a new log is created. The default value is 16MB.'),
+ 'type' => PMA_ENGINE_DETAILS_TYPE_SIZE
+ ),
+ 'pbxt_transaction_buffer_size' => array(
+ 'title' => __('Transaction buffer size'),
+ 'desc' => __('The size of the global transaction log buffer (the engine allocates 2 buffers of this size). The default is 1MB.'),
+ 'type' => PMA_ENGINE_DETAILS_TYPE_SIZE
+ ),
+ 'pbxt_checkpoint_frequency' => array(
+ 'title' => __('Checkpoint frequency'),
+ 'desc' => __('The amount of data written to the transaction log before a checkpoint is performed. The default value is 24MB.'),
+ 'type' => PMA_ENGINE_DETAILS_TYPE_SIZE
+ ),
+ 'pbxt_data_log_threshold' => array(
+ 'title' => __('Data log threshold'),
+ 'desc' => __('The maximum size of a data log file. The default value is 64MB. PBXT can create a maximum of 32000 data logs, which are used by all tables. So the value of this variable can be increased to increase the total amount of data that can be stored in the database.'),
+ 'type' => PMA_ENGINE_DETAILS_TYPE_SIZE
+ ),
+ 'pbxt_garbage_threshold' => array(
+ 'title' => __('Garbage threshold'),
+ 'desc' => __('The percentage of garbage in a data log file before it is compacted. This is a value between 1 and 99. The default is 50.'),
+ 'type' => PMA_ENGINE_DETAILS_TYPE_NUMERIC
+ ),
+ 'pbxt_log_buffer_size' => array(
+ 'title' => __('Log buffer size'),
+ 'desc' => __('The size of the buffer used when writing a data log. The default is 256MB. The engine allocates one buffer per thread, but only if the thread is required to write a data log.'),
+ 'type' => PMA_ENGINE_DETAILS_TYPE_SIZE
+ ),
+ 'pbxt_data_file_grow_size' => array(
+ 'title' => __('Data file grow size'),
+ 'desc' => __('The grow size of the handle data (.xtd) files.'),
+ 'type' => PMA_ENGINE_DETAILS_TYPE_SIZE
+ ),
+ 'pbxt_row_file_grow_size' => array(
+ 'title' => __('Row file grow size'),
+ 'desc' => __('The grow size of the row pointer (.xtr) files.'),
+ 'type' => PMA_ENGINE_DETAILS_TYPE_SIZE
+ ),
+ 'pbxt_log_file_count' => array(
+ 'title' => __('Log file count'),
+ 'desc' => __('This is the number of transaction log files (pbxt/system/xlog*.xt) the system will maintain. If the number of logs exceeds this value then old logs will be deleted, otherwise they are renamed and given the next highest number.'),
+ 'type' => PMA_ENGINE_DETAILS_TYPE_NUMERIC
+ ),
+ );
+ }
+
+ /**
+ * returns the pbxt engine specific handling for
+ * PMA_ENGINE_DETAILS_TYPE_SIZE variables.
+ *
+ * @param string $formatted_size the size expression (for example 8MB)
+ *
+ * @return string the formatted value and its unit
+ */
+ function resolveTypeSize($formatted_size)
+ {
+ if (preg_match('/^[0-9]+[a-zA-Z]+$/', $formatted_size)) {
+ $value = PMA_Util::extractValueFromFormattedSize($formatted_size);
+ } else {
+ $value = $formatted_size;
+ }
+ return PMA_Util::formatByteDown($value);
+ }
+
+ //--------------------
+ /**
+ * Get information about pages
+ *
+ * @return array Information about pages
+ */
+ function getInfoPages()
+ {
+ $pages = array();
+ $pages['Documentation'] = __('Documentation');
+ return $pages;
+ }
+
+ //--------------------
+ /**
+ * Get content of a page
+ *
+ * @param string $id Id of searched page
+ *
+ * @return string
+ */
+ function getPage($id)
+ {
+ if (! array_key_exists($id, $this->getInfoPages())) {
+ return false;
+ }
+
+ $id = 'getPage' . $id;
+
+ return $this->$id();
+ }
+
+ /**
+ * Get content of documentation page
+ *
+ * @return string
+ */
+ function getPageDocumentation()
+ {
+ $output = '<p>' . sprintf(
+ __(
+ 'Documentation and further information about PBXT'
+ . ' can be found on the %sPrimeBase XT Home Page%s.'
+ ),
+ '<a href="' . PMA_linkURL('http://www.primebase.com/xt/')
+ . '" target="_blank">', '</a>'
+ )
+ . '</p>' . "\n"
+ . '<h3>' . __('Related Links') . '</h3>' . "\n"
+ . '<ul>' . "\n"
+ . '<li><a href="' . PMA_linkURL('http://pbxt.blogspot.com/')
+ . '" target="_blank">' . __('The PrimeBase XT Blog by Paul McCullagh')
+ . '</a></li>' . "\n"
+ . '</ul>' . "\n";
+
+ return $output;
+ }
+}
+
+?>
diff --git a/libraries/engines/performance_schema.lib.php b/libraries/engines/performance_schema.lib.php
new file mode 100644
index 0000000000..f7664b6558
--- /dev/null
+++ b/libraries/engines/performance_schema.lib.php
@@ -0,0 +1,28 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * @package PhpMyAdmin-Engines
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ *
+ * @package PhpMyAdmin-Engines
+ */
+class PMA_StorageEngine_performance_schema extends PMA_StorageEngine
+{
+ /**
+ * returns string with filename for the MySQL helppage
+ * about this storage engine
+ *
+ * @return string mysql helppage filename
+ */
+ function getMysqlHelpPage()
+ {
+ return 'performance-schema';
+ }
+}
+
+?>
diff --git a/libraries/error.inc.php b/libraries/error.inc.php
new file mode 100644
index 0000000000..c48a7f1422
--- /dev/null
+++ b/libraries/error.inc.php
@@ -0,0 +1,59 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * phpMyAdmin fatal error display page
+ *
+ * @package PhpMyAdmin
+ */
+
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+if (! defined('TESTSUITE')) {
+ header('Content-Type: text/html; charset=utf-8');
+}
+?>
+<!DOCTYPE HTML>
+<html lang="<?php echo $lang; ?>" dir="<?php echo $dir; ?>">
+<head>
+ <link rel="icon" href="favicon.ico" type="image/x-icon" />
+ <link rel="shortcut icon" href="favicon.ico" type="image/x-icon" />
+ <title>phpMyAdmin</title>
+ <meta charset="utf-8" />
+ <style type="text/css">
+ <!--
+ html {
+ padding: 0;
+ margin: 0;
+ }
+ body {
+ font-family: sans-serif;
+ font-size: small;
+ color: #000000;
+ background-color: #F5F5F5;
+ margin: 1em;
+ }
+ h1 {
+ margin: 0;
+ padding: 0.3em;
+ font-size: 1.4em;
+ font-weight: bold;
+ color: #ffffff;
+ background-color: #ff0000;
+ }
+ p {
+ margin: 0;
+ padding: 0.5em;
+ border: 0.1em solid red;
+ background-color: #ffeeee;
+ }
+ //-->
+ </style>
+</head>
+<body>
+<h1>phpMyAdmin - <?php echo $error_header; ?></h1>
+<p><?php echo PMA_sanitize($error_message); ?></p>
+</body>
+</html>
+
diff --git a/libraries/error_report.lib.php b/libraries/error_report.lib.php
new file mode 100644
index 0000000000..3b2316dea3
--- /dev/null
+++ b/libraries/error_report.lib.php
@@ -0,0 +1,358 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Error reporting functions used to generate and submit error reports
+ *
+ * @package PhpMyAdmin
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ * The generated file that contains the linenumbers for the js files
+ * If you change any of the js files you can run the scripts/line-counts.sh
+ */
+if (is_readable('js/line_counts.php')) {
+ include_once 'js/line_counts.php';
+}
+
+/**
+ * the url where to submit reports to
+ */
+define('SUBMISSION_URL', "http://reports.phpmyadmin.net/incidents/create");
+
+/**
+ * returns the error report data collected from the current configuration or
+ * from the request parameters sent by the error reporting js code.
+ *
+ * @param boolean $pretty_print whether to prettify the report
+ *
+ * @return Array/String the report
+ */
+function PMA_getReportData($pretty_print = true)
+{
+ if (empty($_REQUEST['exception'])) {
+ return '';
+ }
+ $exception = $_REQUEST['exception'];
+ $exception["stack"] = PMA_translateStacktrace($exception["stack"]);
+ List($uri, $script_name) = PMA_sanitizeUrl($exception["url"]);
+ $exception["uri"] = $uri;
+ unset($exception["url"]);
+ $report = array(
+ "exception" => $exception,
+ "script_name" => $script_name,
+ "pma_version" => PMA_VERSION,
+ "browser_name" => PMA_USR_BROWSER_AGENT,
+ "browser_version" => PMA_USR_BROWSER_VER,
+ "user_os" => PMA_USR_OS,
+ "server_software" => $_SERVER['SERVER_SOFTWARE'],
+ "user_agent_string" => $_SERVER['HTTP_USER_AGENT'],
+ "locale" => $_COOKIE['pma_lang'],
+ "configuration_storage" =>
+ empty($GLOBALS['cfg']['Servers'][1]['pmadb']) ? "disabled" :
+ "enabled",
+ "php_version" => phpversion(),
+ "microhistory" => $_REQUEST['microhistory'],
+ );
+
+ if (! empty($_REQUEST['description'])) {
+ $report['steps'] = $_REQUEST['description'];
+ }
+
+ if ($pretty_print) {
+ /* JSON_PRETTY_PRINT available since PHP 5.4 */
+ if (defined('JSON_PRETTY_PRINT')) {
+ return json_encode($report, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
+ } else {
+ return PMA_prettyPrint($report);
+ }
+ } else {
+ return $report;
+ }
+}
+
+/**
+ * Sanitize a url to remove the identifiable host name and extract the
+ * current scriptname from the url fragment
+ *
+ * It returns two things in an array. The first is the uri without the
+ * hostname and identifying query params. The second is the name of the
+ * php script in the url
+ *
+ * @param String $url the url to sanitize
+ *
+ * @return Array the uri and script name
+ */
+function PMA_sanitizeUrl($url)
+{
+ $components = parse_url($url);
+ if (isset($components["fragment"])
+ && preg_match("<PMAURL-\d+:>", $components["fragment"], $matches)
+ ) {
+ $uri = str_replace($matches[0], "", $components["fragment"]);
+ $url = "http://dummy_host/" . $uri;
+ $components = parse_url($url);
+ }
+
+ // get script name
+ preg_match("<([a-zA-Z\-_\d]*\.php)$>", $components["path"], $matches);
+ $script_name = $matches[1];
+
+ // remove deployment specific details to make uri more generic
+ parse_str($components["query"], $query_array);
+ unset($query_array["db"]);
+ unset($query_array["table"]);
+ unset($query_array["token"]);
+ unset($query_array["server"]);
+ $query = http_build_query($query_array);
+
+ $uri = $script_name . "?" . $query;
+ return array($uri, $script_name);
+}
+
+/**
+ * Sends report data to the error reporting server
+ *
+ * @param Array $report the report info to be sent
+ *
+ * @return String the reply of the server
+ */
+function PMA_sendErrorReport($report)
+{
+ $data_string = json_encode($report);
+ if (ini_get('allow_url_fopen')) {
+ $context = array("http" =>
+ array(
+ 'method' => 'POST',
+ 'content' => $data_string,
+ 'header' => "Content-Type: multipart/form-data\r\n",
+ )
+ );
+ if (strlen($GLOBALS['cfg']['ProxyUrl'])) {
+ $context['http'] = array(
+ 'proxy' => $GLOBALS['cfg']['ProxyUrl'],
+ 'request_fulluri' => true
+ );
+ if (strlen($GLOBALS['cfg']['ProxyUser'])) {
+ $auth = base64_encode(
+ $GLOBALS['cfg']['ProxyUser'] . ':' . $GLOBALS['cfg']['ProxyPass']
+ );
+ $context['http']['header'] .= 'Proxy-Authorization: Basic '
+ . $auth . "\r\n";
+ }
+ }
+ $response = file_get_contents(
+ SUBMISSION_URL,
+ false,
+ stream_context_create($context)
+ );
+ } else if (function_exists('curl_init')) {
+ $curl_handle = curl_init(SUBMISSION_URL);
+ if (strlen($GLOBALS['cfg']['ProxyUrl'])) {
+ curl_setopt($curl_handle, CURLOPT_PROXY, $GLOBALS['cfg']['ProxyUrl']);
+ if (strlen($GLOBALS['cfg']['ProxyUser'])) {
+ curl_setopt(
+ $curl_handle,
+ CURLOPT_PROXYUSERPWD,
+ $GLOBALS['cfg']['ProxyUser'] . ':' . $GLOBALS['cfg']['ProxyPass']
+ );
+ }
+ }
+ curl_setopt($curl_handle, CURLOPT_CUSTOMREQUEST, "POST");
+ curl_setopt($curl_handle, CURLOPT_HTTPHEADER, array('Expect:'));
+ curl_setopt($curl_handle, CURLOPT_POSTFIELDS, $data_string);
+ curl_setopt($curl_handle, CURLOPT_RETURNTRANSFER, 1);
+ $response = curl_exec($curl_handle);
+ curl_close($curl_handle);
+ }
+ return $response;
+}
+
+/**
+ * Returns number of lines in given javascript file.
+ *
+ * @param string $filename javascript filename
+ *
+ * @return Number of lines
+ */
+function PMA_countLines($filename)
+{
+ global $LINE_COUNT;
+ if (!defined('LINE_COUNTS')) {
+ $linecount = 0;
+ $handle = fopen('./js/' . $filename, 'r');
+ while (!feof($handle)) {
+ $line = fgets($handle);
+ if ($line === false) {
+ break;
+ }
+ $linecount++;
+ }
+ fclose($handle);
+ return $linecount;
+ } else {
+ return $LINE_COUNT[$filename];
+ }
+}
+
+/**
+ * returns the translated linenumber and the file name from the cumulative line
+ * number and an array of files
+ *
+ * uses the $LINE_COUNT global array of file names and line numbers
+ *
+ * @param Array $filenames list of files in order of concatenation
+ * @param Integer $cumulative_number the cumulative line number in the
+ * concatenated files
+ *
+ * @return Array the filename and linenumber
+ * Returns two variables in an array:
+ * - A String $filename the filename where the requested cumulative number
+ * exists
+ * - Integer $linenumber the translated line number in the returned file
+ */
+function PMA_getLineNumber($filenames, $cumulative_number)
+{
+ $cumulative_sum = 0;
+ foreach ($filenames as $filename) {
+ $filecount = PMA_countLines($filename);
+ if ($cumulative_number <= $cumulative_sum + $filecount + 2) {
+ $linenumber = $cumulative_number - $cumulative_sum;
+ break;
+ }
+ $cumulative_sum += $filecount + 2;
+ }
+ return array($filename, $linenumber);
+}
+
+/**
+ * translates the cumulative line numbers in the stactrace as well as sanitize
+ * urls and trim long lines in the context
+ *
+ * @param Array $stack the stacktrace
+ *
+ * @return Array $stack the modified stacktrace
+ */
+function PMA_translateStacktrace($stack)
+{
+ foreach ($stack as &$level) {
+ foreach ($level["context"] as &$line) {
+ if (strlen($line) > 80) {
+ $line = substr($line, 0, 75) . "//...";
+ }
+ }
+ if (preg_match("<js/get_scripts.js.php\?(.*)>", $level["url"], $matches)) {
+ parse_str($matches[1], $vars);
+ List($file_name, $line_number) = PMA_getLineNumber(
+ $vars["scripts"], $level["line"]
+ );
+ $level["filename"] = $file_name;
+ $level["line"] = $line_number;
+ } else {
+ unset($level["context"]);
+ List($uri, $script_name) = PMA_sanitizeUrl($level["url"]);
+ $level["uri"] = $uri;
+ $level["scriptname"] = $script_name;
+ }
+ unset($level["url"]);
+ }
+ unset($level);
+ return $stack;
+}
+
+/**
+ * generates the error report form to collect user description and preview the
+ * report before being sent
+ *
+ * @return String the form
+ */
+function PMA_getErrorReportForm()
+{
+ $html = "";
+ $html .= '<form action="error_report.php" method="post" name="report_frm"'
+ .' id="report_frm" class="ajax">'
+ .'<fieldset style="padding-top:0px">';
+
+ $html .= '<p>' . __(
+ 'phpMyAdmin has encountered an error. We have collected data about'
+ .' this error as well as information about relevant configuration'
+ .' settings to send to the phpMyAdmin team to help us in'
+ .' debugging the problem.'
+ ) .'</p>';
+
+ $html .= '<div class="label"><label><p>'
+ . __('You may examine the data in the error report:')
+ .'</p></label></div>'
+ .'<pre class="report-data">'
+ .PMA_getReportData()
+ .'</pre>';
+
+ $html .= '<div class="label"><label><p>'
+ . __('Please explain the steps that lead to the error:')
+ .'</p></label></div>'
+ .'<textarea class="report-description" name="description"'
+ .'id="report_description"></textarea>';
+
+ $html .= '<input type="checkbox" name="always_send"'
+ .' id="always_send_checkbox"/>'
+ .'<label for="always_send_checkbox">'
+ . __('Automatically send report next time')
+ .'</label>';
+
+ $html .= '</fieldset>';
+
+ $html .= PMA_URL_getHiddenInputs();
+
+ $reportData = PMA_getReportData(false);
+ if (! empty($reportData)) {
+ $html .= PMA_getHiddenFields($reportData);
+ }
+
+ $html .= '</form>';
+
+ return $html;
+}
+
+/**
+ * generates the error report form to collect user description and preview the
+ * report before being sent
+ *
+ * @return String the form
+ */
+function PMA_hasLatestLineCounts()
+{
+ $line_counts_time = filemtime("js/line_counts.php");
+ $js_time = filemtime("js");
+ return $line_counts_time >= $js_time;
+}
+
+/**
+ * pretty print a variable for the user
+ *
+ * @param mixed $object the variable to pretty print
+ * @param String $namespace the namespace to use for printing values
+ *
+ * @return String the human readable form of the variable
+ */
+function PMA_prettyPrint($object, $namespace="")
+{
+ if (! is_array($object)) {
+ if (empty($namespace)) {
+ return "$object\n";
+ } else {
+ return "$namespace: \"$object\"\n";
+ }
+ }
+ $output = "";
+ foreach ($object as $key => $value) {
+ if ($namespace == "") {
+ $new_namespace = "$key";
+ } else {
+ $new_namespace = $namespace . "[$key]";
+ }
+ $output .= PMA_prettyPrint($value, $new_namespace);
+ }
+ return $output;
+}
diff --git a/libraries/file_listing.lib.php b/libraries/file_listing.lib.php
new file mode 100644
index 0000000000..2c457ef1c5
--- /dev/null
+++ b/libraries/file_listing.lib.php
@@ -0,0 +1,99 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Functions for listing directories
+ *
+ * @package PhpMyAdmin
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ * Returns array of filtered file names
+ *
+ * @param string $dir directory to list
+ * @param string $expression regular expression to match files
+ *
+ * @return array sorted file list on success, false on failure
+ */
+function PMA_getDirContent($dir, $expression = '')
+{
+ if (file_exists($dir) && $handle = @opendir($dir)) {
+ $result = array();
+ if (substr($dir, -1) != '/') {
+ $dir .= '/';
+ }
+ while ($file = @readdir($handle)) {
+ if (is_file($dir . $file)
+ && ($expression == '' || preg_match($expression, $file))
+ ) {
+ $result[] = $file;
+ }
+ }
+ @closedir($handle);
+ asort($result);
+ return $result;
+ } else {
+ return false;
+ }
+}
+
+/**
+ * Returns options of filtered file names
+ *
+ * @param string $dir directory to list
+ * @param string $extensions regullar expression to match files
+ * @param string $active currently active choice
+ *
+ * @return array sorted file list on success, false on failure
+ */
+function PMA_getFileSelectOptions($dir, $extensions = '', $active = '')
+{
+ $list = PMA_getDirContent($dir, $extensions);
+ if ($list === false) {
+ return false;
+ }
+ $result = '';
+ foreach ($list as $val) {
+ $result .= '<option value="'. htmlspecialchars($val) . '"';
+ if ($val == $active) {
+ $result .= ' selected="selected"';
+ }
+ $result .= '>' . htmlspecialchars($val) . '</option>' . "\n";
+ }
+ return $result;
+}
+
+/**
+ * Get currently supported decompressions.
+ *
+ * @return string separated list of extensions usable in PMA_getDirContent
+ */
+function PMA_supportedDecompressions()
+{
+ global $cfg;
+
+ $compressions = '';
+
+ if ($cfg['GZipDump'] && @function_exists('gzopen')) {
+ if (!empty($compressions)) {
+ $compressions .= '|';
+ }
+ $compressions .= 'gz';
+ }
+ if ($cfg['BZipDump'] && @function_exists('bzopen')) {
+ if (!empty($compressions)) {
+ $compressions .= '|';
+ }
+ $compressions .= 'bz2';
+ }
+ if ($cfg['ZipDump'] && @function_exists('gzinflate')) {
+ if (!empty($compressions)) {
+ $compressions .= '|';
+ }
+ $compressions .= 'zip';
+ }
+
+ return $compressions;
+}
diff --git a/libraries/gis/pma_gis_factory.php b/libraries/gis/pma_gis_factory.php
new file mode 100644
index 0000000000..a8f8b3777f
--- /dev/null
+++ b/libraries/gis/pma_gis_factory.php
@@ -0,0 +1,63 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Contains the factory class that handles the creation of geometric objects
+ *
+ * @package PhpMyAdmin-GIS
+ */
+
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ * Factory class that handles the creation of geometric objects.
+ *
+ * @package PhpMyAdmin-GIS
+ */
+class PMA_GIS_Factory
+{
+ /**
+ * Returns the singleton instance of geometric class of the given type.
+ *
+ * @param string $type type of the geometric object
+ *
+ * @return PMA_GIS_Geometry the singleton instance of geometric class
+ * of the given type
+ *
+ * @access public
+ * @static
+ */
+ public static function factory($type)
+ {
+ include_once './libraries/gis/pma_gis_geometry.php';
+
+ $type_lower = strtolower($type);
+ if (! file_exists('./libraries/gis/pma_gis_' . $type_lower . '.php')) {
+ return false;
+ }
+ if (include_once './libraries/gis/pma_gis_' . $type_lower . '.php') {
+ switch(strtoupper($type)) {
+ case 'MULTIPOLYGON' :
+ return PMA_GIS_Multipolygon::singleton();
+ case 'POLYGON' :
+ return PMA_GIS_Polygon::singleton();
+ case 'MULTIPOINT' :
+ return PMA_GIS_Multipoint::singleton();
+ case 'POINT' :
+ return PMA_GIS_Point::singleton();
+ case 'MULTILINESTRING' :
+ return PMA_GIS_Multilinestring::singleton();
+ case 'LINESTRING' :
+ return PMA_GIS_Linestring::singleton();
+ case 'GEOMETRYCOLLECTION' :
+ return PMA_GIS_Geometrycollection::singleton();
+ default :
+ return false;
+ }
+ } else {
+ return false;
+ }
+ }
+}
+?>
diff --git a/libraries/gis/pma_gis_geometry.php b/libraries/gis/pma_gis_geometry.php
new file mode 100644
index 0000000000..b457db53cd
--- /dev/null
+++ b/libraries/gis/pma_gis_geometry.php
@@ -0,0 +1,361 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Base class for all GIS data type classes
+ *
+ * @package PhpMyAdmin-GIS
+ */
+
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ * Base class for all GIS data type classes.
+ *
+ * @package PhpMyAdmin-GIS
+ */
+abstract class PMA_GIS_Geometry
+{
+ /**
+ * Prepares and returns the code related to a row in the GIS dataset as SVG.
+ *
+ * @param string $spatial GIS data object
+ * @param string $label label for the GIS data object
+ * @param string $color color for the GIS data object
+ * @param array $scale_data data related to scaling
+ *
+ * @return string the code related to a row in the GIS dataset
+ * @access public
+ */
+ public abstract function prepareRowAsSvg($spatial, $label, $color, $scale_data);
+
+ /**
+ * Adds to the PNG image object, the data related to a row in the GIS dataset.
+ *
+ * @param string $spatial GIS data object
+ * @param string $label label for the GIS data object
+ * @param string $color color for the GIS data object
+ * @param array $scale_data array containing data related to scaling
+ * @param object $image image object
+ *
+ * @return object the modified image object
+ * @access public
+ */
+ public abstract function prepareRowAsPng($spatial, $label, $color,
+ $scale_data, $image
+ );
+
+ /**
+ * Adds to the TCPDF instance, the data related to a row in the GIS dataset.
+ *
+ * @param string $spatial GIS data object
+ * @param string $label label for the GIS data object
+ * @param string $color color for the GIS data object
+ * @param array $scale_data array containing data related to scaling
+ * @param TCPDF $pdf TCPDF instance
+ *
+ * @return TCPDF the modified TCPDF instance
+ * @access public
+ */
+ public abstract function prepareRowAsPdf($spatial, $label, $color,
+ $scale_data, $pdf
+ );
+
+ /**
+ * Prepares the JavaScript related to a row in the GIS dataset
+ * to visualize it with OpenLayers.
+ *
+ * @param string $spatial GIS data object
+ * @param int $srid spatial reference ID
+ * @param string $label label for the GIS data object
+ * @param string $color color for the GIS data object
+ * @param array $scale_data array containing data related to scaling
+ *
+ * @return string the JavaScript related to a row in the GIS dataset
+ * @access public
+ */
+ public abstract function prepareRowAsOl($spatial, $srid, $label,
+ $color, $scale_data
+ );
+
+ /**
+ * Scales each row.
+ *
+ * @param string $spatial spatial data of a row
+ *
+ * @return array array containing the min, max values for x and y cordinates
+ * @access public
+ */
+ public abstract function scaleRow($spatial);
+
+ /**
+ * Generates the WKT with the set of parameters passed by the GIS editor.
+ *
+ * @param array $gis_data GIS data
+ * @param int $index index into the parameter object
+ * @param string $empty value for empty points
+ *
+ * @return string WKT with the set of parameters passed by the GIS editor
+ * @access public
+ */
+ public abstract function generateWkt($gis_data, $index, $empty = '');
+
+ /**
+ * Returns OpenLayers.Bounds object that correspond to the bounds of GIS data.
+ *
+ * @param string $srid spatial reference ID
+ * @param array $scale_data data related to scaling
+ *
+ * @return string OpenLayers.Bounds object that
+ * correspond to the bounds of GIS data
+ * @access protected
+ */
+ protected function getBoundsForOl($srid, $scale_data)
+ {
+ return 'bound = new OpenLayers.Bounds(); '
+ . 'bound.extend(new OpenLayers.LonLat('
+ . $scale_data['minX'] . ', ' . $scale_data['minY']
+ . ').transform(new OpenLayers.Projection("EPSG:'
+ . $srid . '"), map.getProjectionObject())); '
+ . 'bound.extend(new OpenLayers.LonLat('
+ . $scale_data['maxX'] . ', ' . $scale_data['maxY']
+ . ').transform(new OpenLayers.Projection("EPSG:'
+ . $srid . '"), map.getProjectionObject()));';
+ }
+
+ /**
+ * Updates the min, max values with the given point set.
+ *
+ * @param string $point_set point set
+ * @param array $min_max existing min, max values
+ *
+ * @return array the updated min, max values
+ * @access protected
+ */
+ protected function setMinMax($point_set, $min_max)
+ {
+ // Seperate each point
+ $points = explode(",", $point_set);
+
+ foreach ($points as $point) {
+ // Extract cordinates of the point
+ $cordinates = explode(" ", $point);
+
+ $x = (float) $cordinates[0];
+ if (! isset($min_max['maxX']) || $x > $min_max['maxX']) {
+ $min_max['maxX'] = $x;
+ }
+ if (! isset($min_max['minX']) || $x < $min_max['minX']) {
+ $min_max['minX'] = $x;
+ }
+ $y = (float) $cordinates[1];
+ if (! isset($min_max['maxY']) || $y > $min_max['maxY']) {
+ $min_max['maxY'] = $y;
+ }
+ if (! isset($min_max['minY']) || $y < $min_max['minY']) {
+ $min_max['minY'] = $y;
+ }
+ }
+ return $min_max;
+ }
+
+ /**
+ * Generates parameters for the GIS data editor from the value of the GIS column.
+ * This method performs common work.
+ * More specific work is performed by each of the geom classes.
+ *
+ * @param string $value value of the GIS column
+ *
+ * @return array parameters for the GIS editor from the value of the GIS column
+ * @access protected
+ */
+ protected function generateParams($value)
+ {
+ $geom_types = '(POINT|MULTIPOINT|LINESTRING|MULTILINESTRING'
+ . '|POLYGON|MULTIPOLYGON|GEOMETRYCOLLECTION)';
+ $srid = 0;
+ $wkt = '';
+ if (preg_match("/^'" . $geom_types . "\(.*\)',[0-9]*$/i", $value)) {
+ $last_comma = strripos($value, ",");
+ $srid = trim(substr($value, $last_comma + 1));
+ $wkt = trim(substr($value, 1, $last_comma - 2));
+ } elseif (preg_match("/^" . $geom_types . "\(.*\)$/i", $value)) {
+ $wkt = $value;
+ }
+ return array('srid' => $srid, 'wkt' => $wkt);
+ }
+
+ /**
+ * Extracts points, scales and returns them as an array.
+ *
+ * @param string $point_set string of comma sperated points
+ * @param array $scale_data data related to scaling
+ * @param boolean $linear if true, as a 1D array, else as a 2D array
+ *
+ * @return array scaled points
+ * @access protected
+ */
+ protected function extractPoints($point_set, $scale_data, $linear = false)
+ {
+ $points_arr = array();
+
+ // Seperate each point
+ $points = explode(",", $point_set);
+
+ foreach ($points as $point) {
+ // Extract cordinates of the point
+ $cordinates = explode(" ", $point);
+
+ if (isset($cordinates[0]) && trim($cordinates[0]) != ''
+ && isset($cordinates[1]) && trim($cordinates[1]) != ''
+ ) {
+ if ($scale_data != null) {
+ $x = ($cordinates[0] - $scale_data['x']) * $scale_data['scale'];
+ $y = $scale_data['height']
+ - ($cordinates[1] - $scale_data['y']) * $scale_data['scale'];
+ } else {
+ $x = trim($cordinates[0]);
+ $y = trim($cordinates[1]);
+ }
+ } else {
+ $x = '';
+ $y = '';
+ }
+
+
+ if (! $linear) {
+ $points_arr[] = array($x, $y);
+ } else {
+ $points_arr[] = $x;
+ $points_arr[] = $y;
+ }
+ }
+
+ return $points_arr;
+ }
+
+ /**
+ * Generates JavaScript for adding an array of polygons to OpenLayers.
+ *
+ * @param array $polygons x and y coordinates for each polygon
+ * @param string $srid spatial reference id
+ *
+ * @return string JavaScript for adding an array of polygons to OpenLayers
+ * @access protected
+ */
+ protected function getPolygonArrayForOpenLayers($polygons, $srid)
+ {
+ $ol_array = 'new Array(';
+ foreach ($polygons as $polygon) {
+ $rings = explode("),(", $polygon);
+ $ol_array .= $this->getPolygonForOpenLayers($rings, $srid) . ', ';
+ }
+ $ol_array = substr($ol_array, 0, strlen($ol_array) - 2);
+ $ol_array .= ')';
+
+ return $ol_array;
+ }
+
+ /**
+ * Generates JavaScript for adding points for OpenLayers polygon.
+ *
+ * @param array $polygon x and y coordinates for each line
+ * @param string $srid spatial reference id
+ *
+ * @return string JavaScript for adding points for OpenLayers polygon
+ * @access protected
+ */
+ protected function getPolygonForOpenLayers($polygon, $srid)
+ {
+ return 'new OpenLayers.Geometry.Polygon('
+ . $this->getLineArrayForOpenLayers($polygon, $srid, false)
+ . ')';
+ }
+
+ /**
+ * Generates JavaScript for adding an array of LineString
+ * or LineRing to OpenLayers.
+ *
+ * @param array $lines x and y coordinates for each line
+ * @param string $srid spatial reference id
+ * @param bool $is_line_string whether it's an array of LineString
+ *
+ * @return string JavaScript for adding an array of LineString
+ * or LineRing to OpenLayers
+ * @access protected
+ */
+ protected function getLineArrayForOpenLayers($lines, $srid,
+ $is_line_string = true
+ ) {
+ $ol_array = 'new Array(';
+ foreach ($lines as $line) {
+ $points_arr = $this->extractPoints($line, null);
+ $ol_array .= $this->getLineForOpenLayers(
+ $points_arr, $srid, $is_line_string
+ );
+ $ol_array .= ', ';
+ }
+ $ol_array = substr($ol_array, 0, strlen($ol_array) - 2);
+ $ol_array .= ')';
+
+ return $ol_array;
+ }
+
+ /**
+ * Generates JavaScript for adding a LineString or LineRing to OpenLayers.
+ *
+ * @param array $points_arr x and y coordinates for each point
+ * @param string $srid spatial reference id
+ * @param bool $is_line_string whether it's a LineString
+ *
+ * @return string JavaScript for adding a LineString or LineRing to OpenLayers
+ * @access protected
+ */
+ protected function getLineForOpenLayers($points_arr, $srid,
+ $is_line_string = true
+ ) {
+ return 'new OpenLayers.Geometry.'
+ . ($is_line_string ? 'LineString' : 'LinearRing') . '('
+ . $this->getPointsArrayForOpenLayers($points_arr, $srid)
+ . ')';
+ }
+
+ /**
+ * Generates JavaScript for adding an array of points to OpenLayers.
+ *
+ * @param array $points_arr x and y coordinates for each point
+ * @param string $srid spatial reference id
+ *
+ * @return string JavaScript for adding an array of points to OpenLayers
+ * @access protected
+ */
+ protected function getPointsArrayForOpenLayers($points_arr, $srid)
+ {
+ $ol_array = 'new Array(';
+ foreach ($points_arr as $point) {
+ $ol_array .= $this->getPointForOpenLayers($point, $srid) . ', ';
+ }
+ $ol_array = substr($ol_array, 0, strlen($ol_array) - 2);
+ $ol_array .= ')';
+
+ return $ol_array;
+ }
+
+ /**
+ * Generates JavaScript for adding a point to OpenLayers.
+ *
+ * @param array $point array containing the x and y coordinates of the point
+ * @param string $srid spatial reference id
+ *
+ * @return string JavaScript for adding points to OpenLayers
+ * @access protected
+ */
+ protected function getPointForOpenLayers($point, $srid)
+ {
+ return '(new OpenLayers.Geometry.Point(' . $point[0] . ',' . $point[1] . '))'
+ . '.transform(new OpenLayers.Projection("EPSG:'
+ . $srid . '"), map.getProjectionObject())';
+ }
+}
+?>
diff --git a/libraries/gis/pma_gis_geometrycollection.php b/libraries/gis/pma_gis_geometrycollection.php
new file mode 100644
index 0000000000..09b04ed404
--- /dev/null
+++ b/libraries/gis/pma_gis_geometrycollection.php
@@ -0,0 +1,336 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Handles actions related to GIS GEOMETRYCOLLECTION objects
+ *
+ * @package PhpMyAdmin-GIS
+ */
+
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ * Handles actions related to GIS GEOMETRYCOLLECTION objects
+ *
+ * @package PhpMyAdmin-GIS
+ */
+class PMA_GIS_Geometrycollection extends PMA_GIS_Geometry
+{
+ // Hold the singleton instance of the class
+ private static $_instance;
+
+ /**
+ * A private constructor; prevents direct creation of object.
+ *
+ * @access private
+ */
+ private function __construct()
+ {
+ }
+
+ /**
+ * Returns the singleton.
+ *
+ * @return PMA_GIS_Geometrycollection the singleton
+ * @access public
+ */
+ public static function singleton()
+ {
+ if (!isset(self::$_instance)) {
+ $class = __CLASS__;
+ self::$_instance = new $class;
+ }
+
+ return self::$_instance;
+ }
+
+ /**
+ * Scales each row.
+ *
+ * @param string $spatial spatial data of a row
+ *
+ * @return array array containing the min, max values for x and y cordinates
+ * @access public
+ */
+ public function scaleRow($spatial)
+ {
+ $min_max = array();
+
+ // Trim to remove leading 'GEOMETRYCOLLECTION(' and trailing ')'
+ $goem_col = substr($spatial, 19, (strlen($spatial) - 20));
+
+ // Split the geometry collection object to get its constituents.
+ $sub_parts = $this->_explodeGeomCol($goem_col);
+
+ foreach ($sub_parts as $sub_part) {
+ $type_pos = stripos($sub_part, '(');
+ $type = substr($sub_part, 0, $type_pos);
+
+ $gis_obj = PMA_GIS_Factory::factory($type);
+ if (! $gis_obj) {
+ continue;
+ }
+ $scale_data = $gis_obj->scaleRow($sub_part);
+
+ // Upadate minimum/maximum values for x and y cordinates.
+ $c_maxX = (float) $scale_data['maxX'];
+ if (! isset($min_max['maxX']) || $c_maxX > $min_max['maxX']) {
+ $min_max['maxX'] = $c_maxX;
+ }
+
+ $c_minX = (float) $scale_data['minX'];
+ if (! isset($min_max['minX']) || $c_minX < $min_max['minX']) {
+ $min_max['minX'] = $c_minX;
+ }
+
+ $c_maxY = (float) $scale_data['maxY'];
+ if (! isset($min_max['maxY']) || $c_maxY > $min_max['maxY']) {
+ $min_max['maxY'] = $c_maxY;
+ }
+
+ $c_minY = (float) $scale_data['minY'];
+ if (! isset($min_max['minY']) || $c_minY < $min_max['minY']) {
+ $min_max['minY'] = $c_minY;
+ }
+ }
+ return $min_max;
+ }
+
+ /**
+ * Adds to the PNG image object, the data related to a row in the GIS dataset.
+ *
+ * @param string $spatial GIS GEOMETRYCOLLECTION object
+ * @param string $label label for the GIS GEOMETRYCOLLECTION object
+ * @param string $color color for the GIS GEOMETRYCOLLECTION object
+ * @param array $scale_data array containing data related to scaling
+ * @param object $image image object
+ *
+ * @return resource the modified image object
+ * @access public
+ */
+ public function prepareRowAsPng($spatial, $label, $color, $scale_data, $image)
+ {
+ // Trim to remove leading 'GEOMETRYCOLLECTION(' and trailing ')'
+ $goem_col = substr($spatial, 19, (strlen($spatial) - 20));
+ // Split the geometry collection object to get its constituents.
+ $sub_parts = $this->_explodeGeomCol($goem_col);
+
+ foreach ($sub_parts as $sub_part) {
+ $type_pos = stripos($sub_part, '(');
+ $type = substr($sub_part, 0, $type_pos);
+
+ $gis_obj = PMA_GIS_Factory::factory($type);
+ if (! $gis_obj) {
+ continue;
+ }
+ $image = $gis_obj->prepareRowAsPng(
+ $sub_part, $label, $color, $scale_data, $image
+ );
+ }
+ return $image;
+ }
+
+ /**
+ * Adds to the TCPDF instance, the data related to a row in the GIS dataset.
+ *
+ * @param string $spatial GIS GEOMETRYCOLLECTION object
+ * @param string $label label for the GIS GEOMETRYCOLLECTION object
+ * @param string $color color for the GIS GEOMETRYCOLLECTION object
+ * @param array $scale_data array containing data related to scaling
+ * @param TCPDF $pdf TCPDF instance
+ *
+ * @return TCPDF the modified TCPDF instance
+ * @access pubilc
+ */
+ public function prepareRowAsPdf($spatial, $label, $color, $scale_data, $pdf)
+ {
+ // Trim to remove leading 'GEOMETRYCOLLECTION(' and trailing ')'
+ $goem_col = substr($spatial, 19, (strlen($spatial) - 20));
+ // Split the geometry collection object to get its constituents.
+ $sub_parts = $this->_explodeGeomCol($goem_col);
+
+ foreach ($sub_parts as $sub_part) {
+ $type_pos = stripos($sub_part, '(');
+ $type = substr($sub_part, 0, $type_pos);
+
+ $gis_obj = PMA_GIS_Factory::factory($type);
+ if (! $gis_obj) {
+ continue;
+ }
+ $pdf = $gis_obj->prepareRowAsPdf(
+ $sub_part, $label, $color, $scale_data, $pdf
+ );
+ }
+ return $pdf;
+ }
+
+ /**
+ * Prepares and returns the code related to a row in the GIS dataset as SVG.
+ *
+ * @param string $spatial GIS GEOMETRYCOLLECTION object
+ * @param string $label label for the GIS GEOMETRYCOLLECTION object
+ * @param string $color color for the GIS GEOMETRYCOLLECTION object
+ * @param array $scale_data array containing data related to scaling
+ *
+ * @return string the code related to a row in the GIS dataset
+ * @access public
+ */
+ public function prepareRowAsSvg($spatial, $label, $color, $scale_data)
+ {
+ $row = '';
+
+ // Trim to remove leading 'GEOMETRYCOLLECTION(' and trailing ')'
+ $goem_col = substr($spatial, 19, (strlen($spatial) - 20));
+ // Split the geometry collection object to get its constituents.
+ $sub_parts = $this->_explodeGeomCol($goem_col);
+
+ foreach ($sub_parts as $sub_part) {
+ $type_pos = stripos($sub_part, '(');
+ $type = substr($sub_part, 0, $type_pos);
+
+ $gis_obj = PMA_GIS_Factory::factory($type);
+ if (! $gis_obj) {
+ continue;
+ }
+ $row .= $gis_obj->prepareRowAsSvg(
+ $sub_part, $label, $color, $scale_data
+ );
+ }
+ return $row;
+ }
+
+ /**
+ * Prepares JavaScript related to a row in the GIS dataset
+ * to visualize it with OpenLayers.
+ *
+ * @param string $spatial GIS GEOMETRYCOLLECTION object
+ * @param int $srid spatial reference ID
+ * @param string $label label for the GIS GEOMETRYCOLLECTION object
+ * @param string $color color for the GIS GEOMETRYCOLLECTION object
+ * @param array $scale_data array containing data related to scaling
+ *
+ * @return string JavaScript related to a row in the GIS dataset
+ * @access public
+ */
+ public function prepareRowAsOl($spatial, $srid, $label, $color, $scale_data)
+ {
+ $row = '';
+
+ // Trim to remove leading 'GEOMETRYCOLLECTION(' and trailing ')'
+ $goem_col = substr($spatial, 19, (strlen($spatial) - 20));
+ // Split the geometry collection object to get its constituents.
+ $sub_parts = $this->_explodeGeomCol($goem_col);
+
+ foreach ($sub_parts as $sub_part) {
+ $type_pos = stripos($sub_part, '(');
+ $type = substr($sub_part, 0, $type_pos);
+
+ $gis_obj = PMA_GIS_Factory::factory($type);
+ if (! $gis_obj) {
+ continue;
+ }
+ $row .= $gis_obj->prepareRowAsOl(
+ $sub_part, $srid, $label, $color, $scale_data
+ );
+ }
+ return $row;
+ }
+
+ /**
+ * Splits the GEOMETRYCOLLECTION object and get its constituents.
+ *
+ * @param string $goem_col geometry collection string
+ *
+ * @return array the constituents of the geometry collection object
+ * @access private
+ */
+ private function _explodeGeomCol($goem_col)
+ {
+ $sub_parts = array();
+ $br_count = 0;
+ $start = 0;
+ $count = 0;
+ foreach (str_split($goem_col) as $char) {
+ if ($char == '(') {
+ $br_count++;
+ } elseif ($char == ')') {
+ $br_count--;
+ if ($br_count == 0) {
+ $sub_parts[] = substr($goem_col, $start, ($count + 1 - $start));
+ $start = $count + 2;
+ }
+ }
+ $count++;
+ }
+ return $sub_parts;
+ }
+
+ /**
+ * Generates the WKT with the set of parameters passed by the GIS editor.
+ *
+ * @param array $gis_data GIS data
+ * @param int $index index into the parameter object
+ * @param string $empty value for empty points
+ *
+ * @return string WKT with the set of parameters passed by the GIS editor
+ * @access public
+ */
+ public function generateWkt($gis_data, $index, $empty = '')
+ {
+ $geom_count = (isset($gis_data['GEOMETRYCOLLECTION']['geom_count']))
+ ? $gis_data['GEOMETRYCOLLECTION']['geom_count'] : 1;
+ $wkt = 'GEOMETRYCOLLECTION(';
+ for ($i = 0; $i < $geom_count; $i++) {
+ if (isset($gis_data[$i]['gis_type'])) {
+ $type = $gis_data[$i]['gis_type'];
+ $gis_obj = PMA_GIS_Factory::factory($type);
+ if (! $gis_obj) {
+ continue;
+ }
+ $wkt .= $gis_obj->generateWkt($gis_data, $i, $empty) . ',';
+ }
+ }
+ if (isset($gis_data[0]['gis_type'])) {
+ $wkt = substr($wkt, 0, strlen($wkt) - 1);
+ }
+ $wkt .= ')';
+ return $wkt;
+ }
+
+ /**
+ * Generates parameters for the GIS data editor from the value of the GIS column.
+ *
+ * @param string $value of the GIS column
+ *
+ * @return array parameters for the GIS editor from the value of the GIS column
+ * @access public
+ */
+ public function generateParams($value)
+ {
+ $params = array();
+ $data = PMA_GIS_Geometry::generateParams($value);
+ $params['srid'] = $data['srid'];
+ $wkt = $data['wkt'];
+
+ // Trim to remove leading 'GEOMETRYCOLLECTION(' and trailing ')'
+ $goem_col = substr($wkt, 19, (strlen($wkt) - 20));
+ // Split the geometry collection object to get its constituents.
+ $sub_parts = $this->_explodeGeomCol($goem_col);
+ $params['GEOMETRYCOLLECTION']['geom_count'] = count($sub_parts);
+
+ $i = 0;
+ foreach ($sub_parts as $sub_part) {
+ $type_pos = stripos($sub_part, '(');
+ $type = substr($sub_part, 0, $type_pos);
+ $gis_obj = PMA_GIS_Factory::factory($type);
+ if (! $gis_obj) {
+ continue;
+ }
+ $params = array_merge($params, $gis_obj->generateParams($sub_part, $i));
+ $i++;
+ }
+ return $params;
+ }
+}
+?>
diff --git a/libraries/gis/pma_gis_linestring.php b/libraries/gis/pma_gis_linestring.php
new file mode 100644
index 0000000000..306e88bca4
--- /dev/null
+++ b/libraries/gis/pma_gis_linestring.php
@@ -0,0 +1,298 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Handles actions related to GIS LINESTRING objects
+ *
+ * @package PhpMyAdmin-GIS
+ */
+
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ * Handles actions related to GIS LINESTRING objects
+ *
+ * @package PhpMyAdmin-GIS
+ */
+class PMA_GIS_Linestring extends PMA_GIS_Geometry
+{
+ // Hold the singleton instance of the class
+ private static $_instance;
+
+ /**
+ * A private constructor; prevents direct creation of object.
+ *
+ * @access private
+ */
+ private function __construct()
+ {
+ }
+
+ /**
+ * Returns the singleton.
+ *
+ * @return PMA_GIS_Linestring the singleton
+ * @access public
+ */
+ public static function singleton()
+ {
+ if (!isset(self::$_instance)) {
+ $class = __CLASS__;
+ self::$_instance = new $class;
+ }
+
+ return self::$_instance;
+ }
+
+ /**
+ * Scales each row.
+ *
+ * @param string $spatial spatial data of a row
+ *
+ * @return array an array containing the min, max values for x and y cordinates
+ * @access public
+ */
+ public function scaleRow($spatial)
+ {
+ // Trim to remove leading 'LINESTRING(' and trailing ')'
+ $linesrting = substr($spatial, 11, (strlen($spatial) - 12));
+ return $this->setMinMax($linesrting, array());
+ }
+
+ /**
+ * Adds to the PNG image object, the data related to a row in the GIS dataset.
+ *
+ * @param string $spatial GIS LINESTRING object
+ * @param string $label Label for the GIS LINESTRING object
+ * @param string $line_color Color for the GIS LINESTRING object
+ * @param array $scale_data Array containing data related to scaling
+ * @param object $image Image object
+ *
+ * @return resource the modified image object
+ * @access public
+ */
+ public function prepareRowAsPng($spatial, $label, $line_color,
+ $scale_data, $image
+ ) {
+ // allocate colors
+ $black = imagecolorallocate($image, 0, 0, 0);
+ $red = hexdec(substr($line_color, 1, 2));
+ $green = hexdec(substr($line_color, 3, 2));
+ $blue = hexdec(substr($line_color, 4, 2));
+ $color = imagecolorallocate($image, $red, $green, $blue);
+
+ // Trim to remove leading 'LINESTRING(' and trailing ')'
+ $linesrting = substr($spatial, 11, (strlen($spatial) - 12));
+ $points_arr = $this->extractPoints($linesrting, $scale_data);
+
+ foreach ($points_arr as $point) {
+ if (! isset($temp_point)) {
+ $temp_point = $point;
+ } else {
+ // draw line section
+ imageline(
+ $image, $temp_point[0], $temp_point[1],
+ $point[0], $point[1], $color
+ );
+ $temp_point = $point;
+ }
+ }
+ // print label if applicable
+ if (isset($label) && trim($label) != '') {
+ imagestring(
+ $image, 1, $points_arr[1][0],
+ $points_arr[1][1], trim($label), $black
+ );
+ }
+ return $image;
+ }
+
+ /**
+ * Adds to the TCPDF instance, the data related to a row in the GIS dataset.
+ *
+ * @param string $spatial GIS LINESTRING object
+ * @param string $label Label for the GIS LINESTRING object
+ * @param string $line_color Color for the GIS LINESTRING object
+ * @param array $scale_data Array containing data related to scaling
+ * @param TCPDF $pdf TCPDF instance
+ *
+ * @return TCPDF the modified TCPDF instance
+ * @access public
+ */
+ public function prepareRowAsPdf($spatial, $label, $line_color, $scale_data, $pdf)
+ {
+ // allocate colors
+ $red = hexdec(substr($line_color, 1, 2));
+ $green = hexdec(substr($line_color, 3, 2));
+ $blue = hexdec(substr($line_color, 4, 2));
+ $line = array('width' => 1.5, 'color' => array($red, $green, $blue));
+
+ // Trim to remove leading 'LINESTRING(' and trailing ')'
+ $linesrting = substr($spatial, 11, (strlen($spatial) - 12));
+ $points_arr = $this->extractPoints($linesrting, $scale_data);
+
+ foreach ($points_arr as $point) {
+ if (! isset($temp_point)) {
+ $temp_point = $point;
+ } else {
+ // draw line section
+ $pdf->Line(
+ $temp_point[0], $temp_point[1],
+ $point[0], $point[1], $line
+ );
+ $temp_point = $point;
+ }
+ }
+ // print label
+ if (isset($label) && trim($label) != '') {
+ $pdf->SetXY($points_arr[1][0], $points_arr[1][1]);
+ $pdf->SetFontSize(5);
+ $pdf->Cell(0, 0, trim($label));
+ }
+ return $pdf;
+ }
+
+ /**
+ * Prepares and returns the code related to a row in the GIS dataset as SVG.
+ *
+ * @param string $spatial GIS LINESTRING object
+ * @param string $label Label for the GIS LINESTRING object
+ * @param string $line_color Color for the GIS LINESTRING object
+ * @param array $scale_data Array containing data related to scaling
+ *
+ * @return string the code related to a row in the GIS dataset
+ * @access public
+ */
+ public function prepareRowAsSvg($spatial, $label, $line_color, $scale_data)
+ {
+ $line_options = array(
+ 'name' => $label,
+ 'id' => $label . rand(),
+ 'class' => 'linestring vector',
+ 'fill' => 'none',
+ 'stroke' => $line_color,
+ 'stroke-width'=> 2,
+ );
+
+ // Trim to remove leading 'LINESTRING(' and trailing ')'
+ $linesrting = substr($spatial, 11, (strlen($spatial) - 12));
+ $points_arr = $this->extractPoints($linesrting, $scale_data);
+
+ $row = '<polyline points="';
+ foreach ($points_arr as $point) {
+ $row .= $point[0] . ',' . $point[1] . ' ';
+ }
+ $row .= '"';
+ foreach ($line_options as $option => $val) {
+ $row .= ' ' . $option . '="' . trim($val) . '"';
+ }
+ $row .= '/>';
+
+ return $row;
+ }
+
+ /**
+ * Prepares JavaScript related to a row in the GIS dataset
+ * to visualize it with OpenLayers.
+ *
+ * @param string $spatial GIS LINESTRING object
+ * @param int $srid Spatial reference ID
+ * @param string $label Label for the GIS LINESTRING object
+ * @param string $line_color Color for the GIS LINESTRING object
+ * @param array $scale_data Array containing data related to scaling
+ *
+ * @return string JavaScript related to a row in the GIS dataset
+ * @access public
+ */
+ public function prepareRowAsOl($spatial, $srid, $label, $line_color, $scale_data)
+ {
+ $style_options = array(
+ 'strokeColor' => $line_color,
+ 'strokeWidth' => 2,
+ 'label' => $label,
+ 'fontSize' => 10,
+ );
+ if ($srid == 0) {
+ $srid = 4326;
+ }
+ $result = $this->getBoundsForOl($srid, $scale_data);
+
+ // Trim to remove leading 'LINESTRING(' and trailing ')'
+ $linesrting = substr($spatial, 11, (strlen($spatial) - 12));
+ $points_arr = $this->extractPoints($linesrting, null);
+
+ $result .= 'vectorLayer.addFeatures(new OpenLayers.Feature.Vector('
+ . $this->getLineForOpenLayers($points_arr, $srid)
+ . ', null, ' . json_encode($style_options) . '));';
+ return $result;
+ }
+
+ /**
+ * Generate the WKT with the set of parameters passed by the GIS editor.
+ *
+ * @param array $gis_data GIS data
+ * @param int $index Index into the parameter object
+ * @param string $empty Value for empty points
+ *
+ * @return string WKT with the set of parameters passed by the GIS editor
+ * @access public
+ */
+ public function generateWkt($gis_data, $index, $empty = '')
+ {
+ $no_of_points = isset($gis_data[$index]['LINESTRING']['no_of_points'])
+ ? $gis_data[$index]['LINESTRING']['no_of_points'] : 2;
+ if ($no_of_points < 2) {
+ $no_of_points = 2;
+ }
+ $wkt = 'LINESTRING(';
+ for ($i = 0; $i < $no_of_points; $i++) {
+ $wkt .= ((isset($gis_data[$index]['LINESTRING'][$i]['x'])
+ && trim($gis_data[$index]['LINESTRING'][$i]['x']) != '')
+ ? $gis_data[$index]['LINESTRING'][$i]['x'] : $empty)
+ . ' ' . ((isset($gis_data[$index]['LINESTRING'][$i]['y'])
+ && trim($gis_data[$index]['LINESTRING'][$i]['y']) != '')
+ ? $gis_data[$index]['LINESTRING'][$i]['y'] : $empty) .',';
+ }
+ $wkt = substr($wkt, 0, strlen($wkt) - 1);
+ $wkt .= ')';
+ return $wkt;
+ }
+
+ /**
+ * Generate parameters for the GIS data editor from the value of the GIS column.
+ *
+ * @param string $value of the GIS column
+ * @param int $index of the geometry
+ *
+ * @return array params for the GIS data editor from the value of the GIS column
+ * @access public
+ */
+ public function generateParams($value, $index = -1)
+ {
+ if ($index == -1) {
+ $index = 0;
+ $params = array();
+ $data = PMA_GIS_Geometry::generateParams($value);
+ $params['srid'] = $data['srid'];
+ $wkt = $data['wkt'];
+ } else {
+ $params[$index]['gis_type'] = 'LINESTRING';
+ $wkt = $value;
+ }
+
+ // Trim to remove leading 'LINESTRING(' and trailing ')'
+ $linestring = substr($wkt, 11, (strlen($wkt) - 12));
+ $points_arr = $this->extractPoints($linestring, null);
+
+ $no_of_points = count($points_arr);
+ $params[$index]['LINESTRING']['no_of_points'] = $no_of_points;
+ for ($i = 0; $i < $no_of_points; $i++) {
+ $params[$index]['LINESTRING'][$i]['x'] = $points_arr[$i][0];
+ $params[$index]['LINESTRING'][$i]['y'] = $points_arr[$i][1];
+ }
+
+ return $params;
+ }
+}
+?>
diff --git a/libraries/gis/pma_gis_multilinestring.php b/libraries/gis/pma_gis_multilinestring.php
new file mode 100644
index 0000000000..f6e85639c2
--- /dev/null
+++ b/libraries/gis/pma_gis_multilinestring.php
@@ -0,0 +1,370 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Handles actions related to GIS MULTILINESTRING objects
+ *
+ * @package PhpMyAdmin-GIS
+ */
+
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ * Handles actions related to GIS MULTILINESTRING objects
+ *
+ * @package PhpMyAdmin-GIS
+ */
+class PMA_GIS_Multilinestring extends PMA_GIS_Geometry
+{
+ // Hold the singleton instance of the class
+ private static $_instance;
+
+ /**
+ * A private constructor; prevents direct creation of object.
+ *
+ * @access private
+ */
+ private function __construct()
+ {
+ }
+
+ /**
+ * Returns the singleton.
+ *
+ * @return PMA_GIS_Multilinestring the singleton
+ * @access public
+ */
+ public static function singleton()
+ {
+ if (!isset(self::$_instance)) {
+ $class = __CLASS__;
+ self::$_instance = new $class;
+ }
+
+ return self::$_instance;
+ }
+
+ /**
+ * Scales each row.
+ *
+ * @param string $spatial spatial data of a row
+ *
+ * @return array an array containing the min, max values for x and y cordinates
+ * @access public
+ */
+ public function scaleRow($spatial)
+ {
+ $min_max = array();
+
+ // Trim to remove leading 'MULTILINESTRING((' and trailing '))'
+ $multilinestirng = substr($spatial, 17, (strlen($spatial) - 19));
+ // Seperate each linestring
+ $linestirngs = explode("),(", $multilinestirng);
+
+ foreach ($linestirngs as $linestring) {
+ $min_max = $this->setMinMax($linestring, $min_max);
+ }
+
+ return $min_max;
+ }
+
+ /**
+ * Adds to the PNG image object, the data related to a row in the GIS dataset.
+ *
+ * @param string $spatial GIS MULTILINESTRING object
+ * @param string $label Label for the GIS MULTILINESTRING object
+ * @param string $line_color Color for the GIS MULTILINESTRING object
+ * @param array $scale_data Array containing data related to scaling
+ * @param object $image Image object
+ *
+ * @return object the modified image object
+ * @access public
+ */
+ public function prepareRowAsPng($spatial, $label, $line_color,
+ $scale_data, $image
+ ) {
+ // allocate colors
+ $black = imagecolorallocate($image, 0, 0, 0);
+ $red = hexdec(substr($line_color, 1, 2));
+ $green = hexdec(substr($line_color, 3, 2));
+ $blue = hexdec(substr($line_color, 4, 2));
+ $color = imagecolorallocate($image, $red, $green, $blue);
+
+ // Trim to remove leading 'MULTILINESTRING((' and trailing '))'
+ $multilinestirng = substr($spatial, 17, (strlen($spatial) - 19));
+ // Seperate each linestring
+ $linestirngs = explode("),(", $multilinestirng);
+
+ $first_line = true;
+ foreach ($linestirngs as $linestring) {
+ $points_arr = $this->extractPoints($linestring, $scale_data);
+ foreach ($points_arr as $point) {
+ if (! isset($temp_point)) {
+ $temp_point = $point;
+ } else {
+ // draw line section
+ imageline(
+ $image, $temp_point[0], $temp_point[1],
+ $point[0], $point[1], $color
+ );
+ $temp_point = $point;
+ }
+ }
+ unset($temp_point);
+ // print label if applicable
+ if (isset($label) && trim($label) != '' && $first_line) {
+ imagestring(
+ $image, 1, $points_arr[1][0],
+ $points_arr[1][1], trim($label), $black
+ );
+ }
+ $first_line = false;
+ }
+ return $image;
+ }
+
+ /**
+ * Adds to the TCPDF instance, the data related to a row in the GIS dataset.
+ *
+ * @param string $spatial GIS MULTILINESTRING object
+ * @param string $label Label for the GIS MULTILINESTRING object
+ * @param string $line_color Color for the GIS MULTILINESTRING object
+ * @param array $scale_data Array containing data related to scaling
+ * @param TCPDF $pdf TCPDF instance
+ *
+ * @return TCPDF the modified TCPDF instance
+ * @access public
+ */
+ public function prepareRowAsPdf($spatial, $label, $line_color, $scale_data, $pdf)
+ {
+ // allocate colors
+ $red = hexdec(substr($line_color, 1, 2));
+ $green = hexdec(substr($line_color, 3, 2));
+ $blue = hexdec(substr($line_color, 4, 2));
+ $line = array('width' => 1.5, 'color' => array($red, $green, $blue));
+
+ // Trim to remove leading 'MULTILINESTRING((' and trailing '))'
+ $multilinestirng = substr($spatial, 17, (strlen($spatial) - 19));
+ // Seperate each linestring
+ $linestirngs = explode("),(", $multilinestirng);
+
+ $first_line = true;
+ foreach ($linestirngs as $linestring) {
+ $points_arr = $this->extractPoints($linestring, $scale_data);
+ foreach ($points_arr as $point) {
+ if (! isset($temp_point)) {
+ $temp_point = $point;
+ } else {
+ // draw line section
+ $pdf->Line(
+ $temp_point[0], $temp_point[1], $point[0], $point[1], $line
+ );
+ $temp_point = $point;
+ }
+ }
+ unset($temp_point);
+ // print label
+ if (isset($label) && trim($label) != '' && $first_line) {
+ $pdf->SetXY($points_arr[1][0], $points_arr[1][1]);
+ $pdf->SetFontSize(5);
+ $pdf->Cell(0, 0, trim($label));
+ }
+ $first_line = false;
+ }
+ return $pdf;
+ }
+
+ /**
+ * Prepares and returns the code related to a row in the GIS dataset as SVG.
+ *
+ * @param string $spatial GIS MULTILINESTRING object
+ * @param string $label Label for the GIS MULTILINESTRING object
+ * @param string $line_color Color for the GIS MULTILINESTRING object
+ * @param array $scale_data Array containing data related to scaling
+ *
+ * @return string the code related to a row in the GIS dataset
+ * @access public
+ */
+ public function prepareRowAsSvg($spatial, $label, $line_color, $scale_data)
+ {
+ $line_options = array(
+ 'name' => $label,
+ 'class' => 'linestring vector',
+ 'fill' => 'none',
+ 'stroke' => $line_color,
+ 'stroke-width'=> 2,
+ );
+
+ // Trim to remove leading 'MULTILINESTRING((' and trailing '))'
+ $multilinestirng = substr($spatial, 17, (strlen($spatial) - 19));
+ // Seperate each linestring
+ $linestirngs = explode("),(", $multilinestirng);
+
+ $row = '';
+ foreach ($linestirngs as $linestring) {
+ $points_arr = $this->extractPoints($linestring, $scale_data);
+
+ $row .= '<polyline points="';
+ foreach ($points_arr as $point) {
+ $row .= $point[0] . ',' . $point[1] . ' ';
+ }
+ $row .= '"';
+ $line_options['id'] = $label . rand();
+ foreach ($line_options as $option => $val) {
+ $row .= ' ' . $option . '="' . trim($val) . '"';
+ }
+ $row .= '/>';
+ }
+
+ return $row;
+ }
+
+ /**
+ * Prepares JavaScript related to a row in the GIS dataset
+ * to visualize it with OpenLayers.
+ *
+ * @param string $spatial GIS MULTILINESTRING object
+ * @param int $srid Spatial reference ID
+ * @param string $label Label for the GIS MULTILINESTRING object
+ * @param string $line_color Color for the GIS MULTILINESTRING object
+ * @param array $scale_data Array containing data related to scaling
+ *
+ * @return string JavaScript related to a row in the GIS dataset
+ * @access public
+ */
+ public function prepareRowAsOl($spatial, $srid, $label, $line_color, $scale_data)
+ {
+ $style_options = array(
+ 'strokeColor' => $line_color,
+ 'strokeWidth' => 2,
+ 'label' => $label,
+ 'fontSize' => 10,
+ );
+ if ($srid == 0) {
+ $srid = 4326;
+ }
+ $row = $this->getBoundsForOl($srid, $scale_data);
+
+ // Trim to remove leading 'MULTILINESTRING((' and trailing '))'
+ $multilinestirng = substr($spatial, 17, (strlen($spatial) - 19));
+ // Seperate each linestring
+ $linestirngs = explode("),(", $multilinestirng);
+
+ $row .= 'vectorLayer.addFeatures(new OpenLayers.Feature.Vector('
+ . 'new OpenLayers.Geometry.MultiLineString('
+ . $this->getLineArrayForOpenLayers($linestirngs, $srid)
+ . '), null, ' . json_encode($style_options) . '));';
+ return $row;
+ }
+
+ /**
+ * Generate the WKT with the set of parameters passed by the GIS editor.
+ *
+ * @param array $gis_data GIS data
+ * @param int $index Index into the parameter object
+ * @param string $empty Value for empty points
+ *
+ * @return string WKT with the set of parameters passed by the GIS editor
+ * @access public
+ */
+ public function generateWkt($gis_data, $index, $empty = '')
+ {
+ $data_row = $gis_data[$index]['MULTILINESTRING'];
+
+ $no_of_lines = isset($data_row['no_of_lines'])
+ ? $data_row['no_of_lines'] : 1;
+ if ($no_of_lines < 1) {
+ $no_of_lines = 1;
+ }
+ $wkt = 'MULTILINESTRING(';
+ for ($i = 0; $i < $no_of_lines; $i++) {
+ $no_of_points = isset($data_row[$i]['no_of_points'])
+ ? $data_row[$i]['no_of_points'] : 2;
+ if ($no_of_points < 2) {
+ $no_of_points = 2;
+ }
+ $wkt .= '(';
+ for ($j = 0; $j < $no_of_points; $j++) {
+ $wkt .= ((isset($data_row[$i][$j]['x'])
+ && trim($data_row[$i][$j]['x']) != '')
+ ? $data_row[$i][$j]['x'] : $empty)
+ . ' ' . ((isset($data_row[$i][$j]['y'])
+ && trim($data_row[$i][$j]['y']) != '')
+ ? $data_row[$i][$j]['y'] : $empty) . ',';
+ }
+ $wkt = substr($wkt, 0, strlen($wkt) - 1);
+ $wkt .= '),';
+ }
+ $wkt = substr($wkt, 0, strlen($wkt) - 1);
+ $wkt .= ')';
+ return $wkt;
+ }
+
+ /**
+ * Generate the WKT for the data from ESRI shape files.
+ *
+ * @param array $row_data GIS data
+ *
+ * @return string the WKT for the data from ESRI shape files
+ * @access public
+ */
+ public function getShape($row_data)
+ {
+ $wkt = 'MULTILINESTRING(';
+ for ($i = 0; $i < $row_data['numparts']; $i++) {
+ $wkt .= '(';
+ foreach ($row_data['parts'][$i]['points'] as $point) {
+ $wkt .= $point['x'] . ' ' . $point['y'] . ',';
+ }
+ $wkt = substr($wkt, 0, strlen($wkt) - 1);
+ $wkt .= '),';
+ }
+ $wkt = substr($wkt, 0, strlen($wkt) - 1);
+ $wkt .= ')';
+ return $wkt;
+ }
+
+ /**
+ * Generate parameters for the GIS data editor from the value of the GIS column.
+ *
+ * @param string $value Value of the GIS column
+ * @param int $index Index of the geometry
+ *
+ * @return array params for the GIS data editor from the value of the GIS column
+ * @access public
+ */
+ public function generateParams($value, $index = -1)
+ {
+ if ($index == -1) {
+ $index = 0;
+ $params = array();
+ $data = PMA_GIS_Geometry::generateParams($value);
+ $params['srid'] = $data['srid'];
+ $wkt = $data['wkt'];
+ } else {
+ $params[$index]['gis_type'] = 'MULTILINESTRING';
+ $wkt = $value;
+ }
+
+ // Trim to remove leading 'MULTILINESTRING((' and trailing '))'
+ $multilinestirng = substr($wkt, 17, (strlen($wkt) - 19));
+ // Seperate each linestring
+ $linestirngs = explode("),(", $multilinestirng);
+ $params[$index]['MULTILINESTRING']['no_of_lines'] = count($linestirngs);
+
+ $j = 0;
+ foreach ($linestirngs as $linestring) {
+ $points_arr = $this->extractPoints($linestring, null);
+ $no_of_points = count($points_arr);
+ $params[$index]['MULTILINESTRING'][$j]['no_of_points'] = $no_of_points;
+ for ($i = 0; $i < $no_of_points; $i++) {
+ $params[$index]['MULTILINESTRING'][$j][$i]['x'] = $points_arr[$i][0];
+ $params[$index]['MULTILINESTRING'][$j][$i]['y'] = $points_arr[$i][1];
+ }
+ $j++;
+ }
+ return $params;
+ }
+}
+?>
diff --git a/libraries/gis/pma_gis_multipoint.php b/libraries/gis/pma_gis_multipoint.php
new file mode 100644
index 0000000000..6ef57ddec7
--- /dev/null
+++ b/libraries/gis/pma_gis_multipoint.php
@@ -0,0 +1,343 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Handles actions related to GIS MULTIPOINT objects
+ *
+ * @package PhpMyAdmin-GIS
+ */
+
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ * Handles actions related to GIS MULTIPOINT objects
+ *
+ * @package PhpMyAdmin-GIS
+ */
+class PMA_GIS_Multipoint extends PMA_GIS_Geometry
+{
+ // Hold the singleton instance of the class
+ private static $_instance;
+
+ /**
+ * A private constructor; prevents direct creation of object.
+ *
+ * @access private
+ */
+ private function __construct()
+ {
+ }
+
+ /**
+ * Returns the singleton.
+ *
+ * @return PMA_GIS_Multipoint the singleton
+ * @access public
+ */
+ public static function singleton()
+ {
+ if (!isset(self::$_instance)) {
+ $class = __CLASS__;
+ self::$_instance = new $class;
+ }
+
+ return self::$_instance;
+ }
+
+ /**
+ * Scales each row.
+ *
+ * @param string $spatial spatial data of a row
+ *
+ * @return array an array containing the min, max values for x and y cordinates
+ * @access public
+ */
+ public function scaleRow($spatial)
+ {
+ // Trim to remove leading 'MULTIPOINT(' and trailing ')'
+ $multipoint = substr($spatial, 11, (strlen($spatial) - 12));
+ return $this->setMinMax($multipoint, array());
+ }
+
+ /**
+ * Adds to the PNG image object, the data related to a row in the GIS dataset.
+ *
+ * @param string $spatial GIS MULTIPOINT object
+ * @param string $label Label for the GIS MULTIPOINT object
+ * @param string $point_color Color for the GIS MULTIPOINT object
+ * @param array $scale_data Array containing data related to scaling
+ * @param object $image Image object
+ *
+ * @return object the modified image object
+ * @access public
+ */
+ public function prepareRowAsPng($spatial, $label, $point_color,
+ $scale_data, $image
+ ) {
+ // allocate colors
+ $black = imagecolorallocate($image, 0, 0, 0);
+ $red = hexdec(substr($point_color, 1, 2));
+ $green = hexdec(substr($point_color, 3, 2));
+ $blue = hexdec(substr($point_color, 4, 2));
+ $color = imagecolorallocate($image, $red, $green, $blue);
+
+ // Trim to remove leading 'MULTIPOINT(' and trailing ')'
+ $multipoint = substr($spatial, 11, (strlen($spatial) - 12));
+ $points_arr = $this->extractPoints($multipoint, $scale_data);
+
+ foreach ($points_arr as $point) {
+ // draw a small circle to mark the point
+ if ($point[0] != '' && $point[1] != '') {
+ imagearc($image, $point[0], $point[1], 7, 7, 0, 360, $color);
+ }
+ }
+ // print label for each point
+ if ((isset($label) && trim($label) != '')
+ && ($points_arr[0][0] != '' && $points_arr[0][1] != '')
+ ) {
+ imagestring(
+ $image, 1, $points_arr[0][0], $points_arr[0][1], trim($label), $black
+ );
+ }
+ return $image;
+ }
+
+ /**
+ * Adds to the TCPDF instance, the data related to a row in the GIS dataset.
+ *
+ * @param string $spatial GIS MULTIPOINT object
+ * @param string $label Label for the GIS MULTIPOINT object
+ * @param string $point_color Color for the GIS MULTIPOINT object
+ * @param array $scale_data Array containing data related to scaling
+ * @param TCPDF $pdf TCPDF instance
+ *
+ * @return TCPDF the modified TCPDF instance
+ * @access public
+ */
+ public function prepareRowAsPdf($spatial, $label, $point_color,
+ $scale_data, $pdf
+ ) {
+ // allocate colors
+ $red = hexdec(substr($point_color, 1, 2));
+ $green = hexdec(substr($point_color, 3, 2));
+ $blue = hexdec(substr($point_color, 4, 2));
+ $line = array('width' => 1.25, 'color' => array($red, $green, $blue));
+
+ // Trim to remove leading 'MULTIPOINT(' and trailing ')'
+ $multipoint = substr($spatial, 11, (strlen($spatial) - 12));
+ $points_arr = $this->extractPoints($multipoint, $scale_data);
+
+ foreach ($points_arr as $point) {
+ // draw a small circle to mark the point
+ if ($point[0] != '' && $point[1] != '') {
+ $pdf->Circle($point[0], $point[1], 2, 0, 360, 'D', $line);
+ }
+ }
+ // print label for each point
+ if ((isset($label) && trim($label) != '')
+ && ($points_arr[0][0] != '' && $points_arr[0][1] != '')
+ ) {
+ $pdf->SetXY($points_arr[0][0], $points_arr[0][1]);
+ $pdf->SetFontSize(5);
+ $pdf->Cell(0, 0, trim($label));
+ }
+ return $pdf;
+ }
+
+ /**
+ * Prepares and returns the code related to a row in the GIS dataset as SVG.
+ *
+ * @param string $spatial GIS MULTIPOINT object
+ * @param string $label Label for the GIS MULTIPOINT object
+ * @param string $point_color Color for the GIS MULTIPOINT object
+ * @param array $scale_data Array containing data related to scaling
+ *
+ * @return string the code related to a row in the GIS dataset
+ * @access public
+ */
+ public function prepareRowAsSvg($spatial, $label, $point_color, $scale_data)
+ {
+ $point_options = array(
+ 'name' => $label,
+ 'class' => 'multipoint vector',
+ 'fill' => 'white',
+ 'stroke' => $point_color,
+ 'stroke-width'=> 2,
+ );
+
+ // Trim to remove leading 'MULTIPOINT(' and trailing ')'
+ $multipoint = substr($spatial, 11, (strlen($spatial) - 12));
+ $points_arr = $this->extractPoints($multipoint, $scale_data);
+
+ $row = '';
+ foreach ($points_arr as $point) {
+ if ($point[0] != '' && $point[1] != '') {
+ $row .= '<circle cx="' . $point[0] . '" cy="'
+ . $point[1] . '" r="3"';
+ $point_options['id'] = $label . rand();
+ foreach ($point_options as $option => $val) {
+ $row .= ' ' . $option . '="' . trim($val) . '"';
+ }
+ $row .= '/>';
+ }
+ }
+
+ return $row;
+ }
+
+ /**
+ * Prepares JavaScript related to a row in the GIS dataset
+ * to visualize it with OpenLayers.
+ *
+ * @param string $spatial GIS MULTIPOINT object
+ * @param int $srid Spatial reference ID
+ * @param string $label Label for the GIS MULTIPOINT object
+ * @param string $point_color Color for the GIS MULTIPOINT object
+ * @param array $scale_data Array containing data related to scaling
+ *
+ * @return string JavaScript related to a row in the GIS dataset
+ * @access public
+ */
+ public function prepareRowAsOl($spatial, $srid, $label,
+ $point_color, $scale_data
+ ) {
+ $style_options = array(
+ 'pointRadius' => 3,
+ 'fillColor' => '#ffffff',
+ 'strokeColor' => $point_color,
+ 'strokeWidth' => 2,
+ 'label' => $label,
+ 'labelYOffset' => -8,
+ 'fontSize' => 10,
+ );
+ if ($srid == 0) {
+ $srid = 4326;
+ }
+ $result = $this->getBoundsForOl($srid, $scale_data);
+
+ // Trim to remove leading 'MULTIPOINT(' and trailing ')'
+ $multipoint = substr($spatial, 11, (strlen($spatial) - 12));
+ $points_arr = $this->extractPoints($multipoint, null);
+
+ $result .= 'vectorLayer.addFeatures(new OpenLayers.Feature.Vector('
+ . 'new OpenLayers.Geometry.MultiPoint('
+ . $this->getPointsArrayForOpenLayers($points_arr, $srid)
+ . '), null, ' . json_encode($style_options) . '));';
+ return $result;
+ }
+
+ /**
+ * Generate the WKT with the set of parameters passed by the GIS editor.
+ *
+ * @param array $gis_data GIS data
+ * @param int $index Index into the parameter object
+ * @param string $empty Multipoint does not adhere to this
+ *
+ * @return string WKT with the set of parameters passed by the GIS editor
+ * @access public
+ */
+ public function generateWkt($gis_data, $index, $empty = '')
+ {
+ $no_of_points = isset($gis_data[$index]['MULTIPOINT']['no_of_points'])
+ ? $gis_data[$index]['MULTIPOINT']['no_of_points'] : 1;
+ if ($no_of_points < 1) {
+ $no_of_points = 1;
+ }
+ $wkt = 'MULTIPOINT(';
+ for ($i = 0; $i < $no_of_points; $i++) {
+ $wkt .= ((isset($gis_data[$index]['MULTIPOINT'][$i]['x'])
+ && trim($gis_data[$index]['MULTIPOINT'][$i]['x']) != '')
+ ? $gis_data[$index]['MULTIPOINT'][$i]['x'] : '')
+ . ' ' . ((isset($gis_data[$index]['MULTIPOINT'][$i]['y'])
+ && trim($gis_data[$index]['MULTIPOINT'][$i]['y']) != '')
+ ? $gis_data[$index]['MULTIPOINT'][$i]['y'] : '') . ',';
+ }
+ $wkt = substr($wkt, 0, strlen($wkt) - 1);
+ $wkt .= ')';
+ return $wkt;
+ }
+
+ /**
+ * Generate the WKT for the data from ESRI shape files.
+ *
+ * @param array $row_data GIS data
+ *
+ * @return string the WKT for the data from ESRI shape files
+ * @access public
+ */
+ public function getShape($row_data)
+ {
+ $wkt = 'MULTIPOINT(';
+ for ($i = 0; $i < $row_data['numpoints']; $i++) {
+ $wkt .= $row_data['points'][$i]['x'] . ' '
+ . $row_data['points'][$i]['y'] . ',';
+ }
+ $wkt = substr($wkt, 0, strlen($wkt) - 1);
+ $wkt .= ')';
+ return $wkt;
+ }
+
+ /**
+ * Generate parameters for the GIS data editor from the value of the GIS column.
+ *
+ * @param string $value Value of the GIS column
+ * @param integer $index Index of the geometry
+ *
+ * @return array params for the GIS data editor from the value of the GIS column
+ * @access public
+ */
+ public function generateParams($value, $index = -1)
+ {
+ if ($index == -1) {
+ $index = 0;
+ $params = array();
+ $data = PMA_GIS_Geometry::generateParams($value);
+ $params['srid'] = $data['srid'];
+ $wkt = $data['wkt'];
+ } else {
+ $params[$index]['gis_type'] = 'MULTIPOINT';
+ $wkt = $value;
+ }
+
+ // Trim to remove leading 'MULTIPOINT(' and trailing ')'
+ $points = substr($wkt, 11, (strlen($wkt) - 12));
+ $points_arr = $this->extractPoints($points, null);
+
+ $no_of_points = count($points_arr);
+ $params[$index]['MULTIPOINT']['no_of_points'] = $no_of_points;
+ for ($i = 0; $i < $no_of_points; $i++) {
+ $params[$index]['MULTIPOINT'][$i]['x'] = $points_arr[$i][0];
+ $params[$index]['MULTIPOINT'][$i]['y'] = $points_arr[$i][1];
+ }
+
+ return $params;
+ }
+
+ /**
+ * Overidden to make sure that only the points having valid values
+ * for x and y coordinates are added.
+ *
+ * @param array $points_arr x and y coordinates for each point
+ * @param string $srid spatial reference id
+ *
+ * @return string JavaScript for adding an array of points to OpenLayers
+ * @access protected
+ */
+ protected function getPointsArrayForOpenLayers($points_arr, $srid)
+ {
+ $ol_array = 'new Array(';
+ foreach ($points_arr as $point) {
+ if ($point[0] != '' && $point[1] != '') {
+ $ol_array .= $this->getPointForOpenLayers($point, $srid) . ', ';
+ }
+ }
+ if (substr($ol_array, strlen($ol_array) - 2) == ', ') {
+ $ol_array = substr($ol_array, 0, strlen($ol_array) - 2);
+ }
+ $ol_array .= ')';
+
+ return $ol_array;
+ }
+}
+?>
diff --git a/libraries/gis/pma_gis_multipolygon.php b/libraries/gis/pma_gis_multipolygon.php
new file mode 100644
index 0000000000..7afbff386e
--- /dev/null
+++ b/libraries/gis/pma_gis_multipolygon.php
@@ -0,0 +1,527 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Handles actions related to GIS MULTIPOLYGON objects
+ *
+ * @package PhpMyAdmin-GIS
+ */
+
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ * Handles actions related to GIS MULTIPOLYGON objects
+ *
+ * @package PhpMyAdmin-GIS
+ */
+class PMA_GIS_Multipolygon extends PMA_GIS_Geometry
+{
+ // Hold the singleton instance of the class
+ private static $_instance;
+
+ /**
+ * A private constructor; prevents direct creation of object.
+ *
+ * @access private
+ */
+ private function __construct()
+ {
+ }
+
+ /**
+ * Returns the singleton.
+ *
+ * @return PMA_GIS_Multipolygon the singleton
+ * @access public
+ */
+ public static function singleton()
+ {
+ if (!isset(self::$_instance)) {
+ $class = __CLASS__;
+ self::$_instance = new $class;
+ }
+
+ return self::$_instance;
+ }
+
+ /**
+ * Scales each row.
+ *
+ * @param string $spatial spatial data of a row
+ *
+ * @return array an array containing the min, max values for x and y cordinates
+ * @access public
+ */
+ public function scaleRow($spatial)
+ {
+ $min_max = array();
+
+ // Trim to remove leading 'MULTIPOLYGON(((' and trailing ')))'
+ $multipolygon = substr($spatial, 15, (strlen($spatial) - 18));
+ // Seperate each polygon
+ $polygons = explode(")),((", $multipolygon);
+
+ foreach ($polygons as $polygon) {
+ // If the polygon doesn't have an inner ring, use polygon itself
+ if (strpos($polygon, "),(") === false) {
+ $ring = $polygon;
+ } else {
+ // Seperate outer ring and use it to determin min-max
+ $parts = explode("),(", $polygon);
+ $ring = $parts[0];
+ }
+ $min_max = $this->setMinMax($ring, $min_max);
+ }
+
+ return $min_max;
+ }
+
+ /**
+ * Adds to the PNG image object, the data related to a row in the GIS dataset.
+ *
+ * @param string $spatial GIS MULTIPOLYGON object
+ * @param string $label Label for the GIS MULTIPOLYGON object
+ * @param string $fill_color Color for the GIS MULTIPOLYGON object
+ * @param array $scale_data Array containing data related to scaling
+ * @param object $image Image object
+ *
+ * @return object the modified image object
+ * @access public
+ */
+ public function prepareRowAsPng($spatial, $label, $fill_color,
+ $scale_data, $image
+ ) {
+ // allocate colors
+ $black = imagecolorallocate($image, 0, 0, 0);
+ $red = hexdec(substr($fill_color, 1, 2));
+ $green = hexdec(substr($fill_color, 3, 2));
+ $blue = hexdec(substr($fill_color, 4, 2));
+ $color = imagecolorallocate($image, $red, $green, $blue);
+
+ // Trim to remove leading 'MULTIPOLYGON(((' and trailing ')))'
+ $multipolygon = substr($spatial, 15, (strlen($spatial) - 18));
+ // Seperate each polygon
+ $polygons = explode(")),((", $multipolygon);
+
+ $first_poly = true;
+ foreach ($polygons as $polygon) {
+ // If the polygon doesnt have an inner polygon
+ if (strpos($polygon, "),(") === false) {
+ $points_arr = $this->extractPoints($polygon, $scale_data, true);
+ } else {
+ // Seperate outer and inner polygons
+ $parts = explode("),(", $polygon);
+ $outer = $parts[0];
+ $inner = array_slice($parts, 1);
+
+ $points_arr = $this->extractPoints($outer, $scale_data, true);
+
+ foreach ($inner as $inner_poly) {
+ $points_arr = array_merge(
+ $points_arr,
+ $this->extractPoints($inner_poly, $scale_data, true)
+ );
+ }
+ }
+ // draw polygon
+ imagefilledpolygon($image, $points_arr, sizeof($points_arr) / 2, $color);
+ // mark label point if applicable
+ if (isset($label) && trim($label) != '' && $first_poly) {
+ $label_point = array($points_arr[2], $points_arr[3]);
+ }
+ $first_poly = false;
+ }
+ // print label if applicable
+ if (isset($label_point)) {
+ imagestring(
+ $image, 1, $points_arr[2], $points_arr[3], trim($label), $black
+ );
+ }
+ return $image;
+ }
+
+ /**
+ * Adds to the TCPDF instance, the data related to a row in the GIS dataset.
+ *
+ * @param string $spatial GIS MULTIPOLYGON object
+ * @param string $label Label for the GIS MULTIPOLYGON object
+ * @param string $fill_color Color for the GIS MULTIPOLYGON object
+ * @param array $scale_data Array containing data related to scaling
+ * @param TCPDF $pdf TCPDF instance
+ *
+ * @return TCPDF the modified TCPDF instance
+ * @access public
+ */
+ public function prepareRowAsPdf($spatial, $label, $fill_color, $scale_data, $pdf)
+ {
+ // allocate colors
+ $red = hexdec(substr($fill_color, 1, 2));
+ $green = hexdec(substr($fill_color, 3, 2));
+ $blue = hexdec(substr($fill_color, 4, 2));
+ $color = array($red, $green, $blue);
+
+ // Trim to remove leading 'MULTIPOLYGON(((' and trailing ')))'
+ $multipolygon = substr($spatial, 15, (strlen($spatial) - 18));
+ // Seperate each polygon
+ $polygons = explode(")),((", $multipolygon);
+
+ $first_poly = true;
+ foreach ($polygons as $polygon) {
+ // If the polygon doesnt have an inner polygon
+ if (strpos($polygon, "),(") === false) {
+ $points_arr = $this->extractPoints($polygon, $scale_data, true);
+ } else {
+ // Seperate outer and inner polygons
+ $parts = explode("),(", $polygon);
+ $outer = $parts[0];
+ $inner = array_slice($parts, 1);
+
+ $points_arr = $this->extractPoints($outer, $scale_data, true);
+
+ foreach ($inner as $inner_poly) {
+ $points_arr = array_merge(
+ $points_arr,
+ $this->extractPoints($inner_poly, $scale_data, true)
+ );
+ }
+ }
+ // draw polygon
+ $pdf->Polygon($points_arr, 'F*', array(), $color, true);
+ // mark label point if applicable
+ if (isset($label) && trim($label) != '' && $first_poly) {
+ $label_point = array($points_arr[2], $points_arr[3]);
+ }
+ $first_poly = false;
+ }
+
+ // print label if applicable
+ if (isset($label_point)) {
+ $pdf->SetXY($label_point[0], $label_point[1]);
+ $pdf->SetFontSize(5);
+ $pdf->Cell(0, 0, trim($label));
+ }
+ return $pdf;
+ }
+
+ /**
+ * Prepares and returns the code related to a row in the GIS dataset as SVG.
+ *
+ * @param string $spatial GIS MULTIPOLYGON object
+ * @param string $label Label for the GIS MULTIPOLYGON object
+ * @param string $fill_color Color for the GIS MULTIPOLYGON object
+ * @param array $scale_data Array containing data related to scaling
+ *
+ * @return string the code related to a row in the GIS dataset
+ * @access public
+ */
+ public function prepareRowAsSvg($spatial, $label, $fill_color, $scale_data)
+ {
+ $polygon_options = array(
+ 'name' => $label,
+ 'class' => 'multipolygon vector',
+ 'stroke' => 'black',
+ 'stroke-width'=> 0.5,
+ 'fill' => $fill_color,
+ 'fill-rule' => 'evenodd',
+ 'fill-opacity'=> 0.8,
+ );
+
+ $row = '';
+
+ // Trim to remove leading 'MULTIPOLYGON(((' and trailing ')))'
+ $multipolygon = substr($spatial, 15, (strlen($spatial) - 18));
+ // Seperate each polygon
+ $polygons = explode(")),((", $multipolygon);
+
+ foreach ($polygons as $polygon) {
+ $row .= '<path d="';
+
+ // If the polygon doesnt have an inner polygon
+ if (strpos($polygon, "),(") === false) {
+ $row .= $this->_drawPath($polygon, $scale_data);
+ } else {
+ // Seperate outer and inner polygons
+ $parts = explode("),(", $polygon);
+ $outer = $parts[0];
+ $inner = array_slice($parts, 1);
+
+ $row .= $this->_drawPath($outer, $scale_data);
+
+ foreach ($inner as $inner_poly) {
+ $row .= $this->_drawPath($inner_poly, $scale_data);
+ }
+ }
+ $polygon_options['id'] = $label . rand();
+ $row .= '"';
+ foreach ($polygon_options as $option => $val) {
+ $row .= ' ' . $option . '="' . trim($val) . '"';
+ }
+ $row .= '/>';
+ }
+
+ return $row;
+ }
+
+ /**
+ * Prepares JavaScript related to a row in the GIS dataset
+ * to visualize it with OpenLayers.
+ *
+ * @param string $spatial GIS MULTIPOLYGON object
+ * @param int $srid Spatial reference ID
+ * @param string $label Label for the GIS MULTIPOLYGON object
+ * @param string $fill_color Color for the GIS MULTIPOLYGON object
+ * @param array $scale_data Array containing data related to scaling
+ *
+ * @return string JavaScript related to a row in the GIS dataset
+ * @access public
+ */
+ public function prepareRowAsOl($spatial, $srid, $label, $fill_color, $scale_data)
+ {
+ $style_options = array(
+ 'strokeColor' => '#000000',
+ 'strokeWidth' => 0.5,
+ 'fillColor' => $fill_color,
+ 'fillOpacity' => 0.8,
+ 'label' => $label,
+ 'fontSize' => 10,
+ );
+ if ($srid == 0) {
+ $srid = 4326;
+ }
+ $row = $this->getBoundsForOl($srid, $scale_data);
+
+ // Trim to remove leading 'MULTIPOLYGON(((' and trailing ')))'
+ $multipolygon = substr($spatial, 15, (strlen($spatial) - 18));
+ // Seperate each polygon
+ $polygons = explode(")),((", $multipolygon);
+
+ $row .= 'vectorLayer.addFeatures(new OpenLayers.Feature.Vector('
+ . 'new OpenLayers.Geometry.MultiPolygon('
+ . $this->getPolygonArrayForOpenLayers($polygons, $srid)
+ . '), null, ' . json_encode($style_options) . '));';
+ return $row;
+ }
+
+ /**
+ * Draws a ring of the polygon using SVG path element.
+ *
+ * @param string $polygon The ring
+ * @param array $scale_data Array containing data related to scaling
+ *
+ * @return string the code to draw the ring
+ * @access private
+ */
+ private function _drawPath($polygon, $scale_data)
+ {
+ $points_arr = $this->extractPoints($polygon, $scale_data);
+
+ $row = ' M ' . $points_arr[0][0] . ', ' . $points_arr[0][1];
+ $other_points = array_slice($points_arr, 1, count($points_arr) - 2);
+ foreach ($other_points as $point) {
+ $row .= ' L ' . $point[0] . ', ' . $point[1];
+ }
+ $row .= ' Z ';
+
+ return $row;
+ }
+
+ /**
+ * Generate the WKT with the set of parameters passed by the GIS editor.
+ *
+ * @param array $gis_data GIS data
+ * @param int $index Index into the parameter object
+ * @param string $empty Value for empty points
+ *
+ * @return string WKT with the set of parameters passed by the GIS editor
+ * @access public
+ */
+ public function generateWkt($gis_data, $index, $empty = '')
+ {
+ $data_row = $gis_data[$index]['MULTIPOLYGON'];
+
+ $no_of_polygons = isset($data_row['no_of_polygons'])
+ ? $data_row['no_of_polygons'] : 1;
+ if ($no_of_polygons < 1) {
+ $no_of_polygons = 1;
+ }
+
+ $wkt = 'MULTIPOLYGON(';
+ for ($k = 0; $k < $no_of_polygons; $k++) {
+ $no_of_lines = isset($data_row[$k]['no_of_lines'])
+ ? $data_row[$k]['no_of_lines'] : 1;
+ if ($no_of_lines < 1) {
+ $no_of_lines = 1;
+ }
+ $wkt .= '(';
+ for ($i = 0; $i < $no_of_lines; $i++) {
+ $no_of_points = isset($data_row[$k][$i]['no_of_points'])
+ ? $data_row[$k][$i]['no_of_points'] : 4;
+ if ($no_of_points < 4) {
+ $no_of_points = 4;
+ }
+ $wkt .= '(';
+ for ($j = 0; $j < $no_of_points; $j++) {
+ $wkt .= ((isset($data_row[$k][$i][$j]['x'])
+ && trim($data_row[$k][$i][$j]['x']) != '')
+ ? $data_row[$k][$i][$j]['x'] : $empty)
+ . ' ' . ((isset($data_row[$k][$i][$j]['y'])
+ && trim($data_row[$k][$i][$j]['y']) != '')
+ ? $data_row[$k][$i][$j]['y'] : $empty) .',';
+ }
+ $wkt = substr($wkt, 0, strlen($wkt) - 1);
+ $wkt .= '),';
+ }
+ $wkt = substr($wkt, 0, strlen($wkt) - 1);
+ $wkt .= '),';
+ }
+ $wkt = substr($wkt, 0, strlen($wkt) - 1);
+ $wkt .= ')';
+ return $wkt;
+ }
+
+ /**
+ * Generate the WKT for the data from ESRI shape files.
+ *
+ * @param array $row_data GIS data
+ *
+ * @return string the WKT for the data from ESRI shape files
+ * @access public
+ */
+ public function getShape($row_data)
+ {
+ // Determines whether each line ring is an inner ring or an outer ring.
+ // If it's an inner ring get a point on the surface which can be used to
+ // correctly classify inner rings to their respective outer rings.
+ include_once './libraries/gis/pma_gis_polygon.php';
+ foreach ($row_data['parts'] as $i => $ring) {
+ $row_data['parts'][$i]['isOuter']
+ = PMA_GIS_Polygon::isOuterRing($ring['points']);
+ }
+
+ // Find points on surface for inner rings
+ foreach ($row_data['parts'] as $i => $ring) {
+ if (! $ring['isOuter']) {
+ $row_data['parts'][$i]['pointOnSurface']
+ = PMA_GIS_Polygon::getPointOnSurface($ring['points']);
+ }
+ }
+
+ // Classify inner rings to their respective outer rings.
+ foreach ($row_data['parts'] as $j => $ring1) {
+ if (! $ring1['isOuter']) {
+ foreach ($row_data['parts'] as $k => $ring2) {
+ if ($ring2['isOuter']) {
+ // If the pointOnSurface of the inner ring
+ // is also inside the outer ring
+ if (PMA_GIS_Polygon::isPointInsidePolygon(
+ $ring1['pointOnSurface'], $ring2['points']
+ )) {
+ if (! isset($ring2['inner'])) {
+ $row_data['parts'][$k]['inner'] = array();
+ }
+ $row_data['parts'][$k]['inner'][] = $j;
+ }
+ }
+ }
+ }
+ }
+
+ $wkt = 'MULTIPOLYGON(';
+ // for each polygon
+ foreach ($row_data['parts'] as $ring) {
+ if ($ring['isOuter']) {
+ $wkt .= '('; // start of polygon
+
+ $wkt .= '('; // start of outer ring
+ foreach ($ring['points'] as $point) {
+ $wkt .= $point['x'] . ' ' . $point['y'] . ',';
+ }
+ $wkt = substr($wkt, 0, strlen($wkt) - 1);
+ $wkt .= ')'; // end of outer ring
+
+ // inner rings if any
+ if (isset($ring['inner'])) {
+ foreach ($ring['inner'] as $j) {
+ $wkt .= ',('; // start of inner ring
+ foreach ($row_data['parts'][$j]['points'] as $innerPoint) {
+ $wkt .= $innerPoint['x'] . ' ' . $innerPoint['y'] . ',';
+ }
+ $wkt = substr($wkt, 0, strlen($wkt) - 1);
+ $wkt .= ')'; // end of inner ring
+ }
+ }
+
+ $wkt .= '),'; // end of polygon
+ }
+ }
+ $wkt = substr($wkt, 0, strlen($wkt) - 1);
+
+ $wkt .= ')'; // end of multipolygon
+ return $wkt;
+ }
+
+ /**
+ * Generate parameters for the GIS data editor from the value of the GIS column.
+ *
+ * @param string $value Value of the GIS column
+ * @param int $index Index of the geometry
+ *
+ * @return array params for the GIS data editor from the value of the GIS column
+ * @access public
+ */
+ public function generateParams($value, $index = -1)
+ {
+ if ($index == -1) {
+ $index = 0;
+ $params = array();
+ $data = PMA_GIS_Geometry::generateParams($value);
+ $params['srid'] = $data['srid'];
+ $wkt = $data['wkt'];
+ } else {
+ $params[$index]['gis_type'] = 'MULTIPOLYGON';
+ $wkt = $value;
+ }
+
+ // Trim to remove leading 'MULTIPOLYGON(((' and trailing ')))'
+ $multipolygon = substr($wkt, 15, (strlen($wkt) - 18));
+ // Seperate each polygon
+ $polygons = explode(")),((", $multipolygon);
+
+ $param_row =& $params[$index]['MULTIPOLYGON'];
+ $param_row['no_of_polygons'] = count($polygons);
+
+ $k = 0;
+ foreach ($polygons as $polygon) {
+ // If the polygon doesnt have an inner polygon
+ if (strpos($polygon, "),(") === false) {
+ $param_row[$k]['no_of_lines'] = 1;
+ $points_arr = $this->extractPoints($polygon, null);
+ $no_of_points = count($points_arr);
+ $param_row[$k][0]['no_of_points'] = $no_of_points;
+ for ($i = 0; $i < $no_of_points; $i++) {
+ $param_row[$k][0][$i]['x'] = $points_arr[$i][0];
+ $param_row[$k][0][$i]['y'] = $points_arr[$i][1];
+ }
+ } else {
+ // Seperate outer and inner polygons
+ $parts = explode("),(", $polygon);
+ $param_row[$k]['no_of_lines'] = count($parts);
+ $j = 0;
+ foreach ($parts as $ring) {
+ $points_arr = $this->extractPoints($ring, null);
+ $no_of_points = count($points_arr);
+ $param_row[$k][$j]['no_of_points'] = $no_of_points;
+ for ($i = 0; $i < $no_of_points; $i++) {
+ $param_row[$k][$j][$i]['x'] = $points_arr[$i][0];
+ $param_row[$k][$j][$i]['y'] = $points_arr[$i][1];
+ }
+ $j++;
+ }
+ }
+ $k++;
+ }
+ return $params;
+ }
+}
+?>
diff --git a/libraries/gis/pma_gis_point.php b/libraries/gis/pma_gis_point.php
new file mode 100644
index 0000000000..e434e0c26c
--- /dev/null
+++ b/libraries/gis/pma_gis_point.php
@@ -0,0 +1,294 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Handles actions related to GIS POINT objects
+ *
+ * @package PhpMyAdmin-GIS
+ */
+
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ * Handles actions related to GIS POINT objects
+ *
+ * @package PhpMyAdmin-GIS
+ */
+class PMA_GIS_Point extends PMA_GIS_Geometry
+{
+ // Hold the singleton instance of the class
+ private static $_instance;
+
+ /**
+ * A private constructor; prevents direct creation of object.
+ *
+ * @access private
+ */
+ private function __construct()
+ {
+ }
+
+ /**
+ * Returns the singleton.
+ *
+ * @return PMA_GIS_Point the singleton
+ * @access public
+ */
+ public static function singleton()
+ {
+ if (!isset(self::$_instance)) {
+ $class = __CLASS__;
+ self::$_instance = new $class;
+ }
+
+ return self::$_instance;
+ }
+
+ /**
+ * Scales each row.
+ *
+ * @param string $spatial spatial data of a row
+ *
+ * @return array an array containing the min, max values for x and y cordinates
+ * @access public
+ */
+ public function scaleRow($spatial)
+ {
+ // Trim to remove leading 'POINT(' and trailing ')'
+ $point = substr($spatial, 6, (strlen($spatial) - 7));
+ return $this->setMinMax($point, array());
+ }
+
+ /**
+ * Adds to the PNG image object, the data related to a row in the GIS dataset.
+ *
+ * @param string $spatial GIS POINT object
+ * @param string $label Label for the GIS POINT object
+ * @param string $point_color Color for the GIS POINT object
+ * @param array $scale_data Array containing data related to scaling
+ * @param object $image Image object
+ *
+ * @return object the modified image object
+ * @access public
+ */
+ public function prepareRowAsPng($spatial, $label, $point_color,
+ $scale_data, $image
+ ) {
+ // allocate colors
+ $black = imagecolorallocate($image, 0, 0, 0);
+ $red = hexdec(substr($point_color, 1, 2));
+ $green = hexdec(substr($point_color, 3, 2));
+ $blue = hexdec(substr($point_color, 4, 2));
+ $color = imagecolorallocate($image, $red, $green, $blue);
+
+ // Trim to remove leading 'POINT(' and trailing ')'
+ $point = substr($spatial, 6, (strlen($spatial) - 7));
+ $points_arr = $this->extractPoints($point, $scale_data);
+
+ // draw a small circle to mark the point
+ if ($points_arr[0][0] != '' && $points_arr[0][1] != '') {
+ imagearc(
+ $image, $points_arr[0][0], $points_arr[0][1], 7, 7, 0, 360, $color
+ );
+ // print label if applicable
+ if (isset($label) && trim($label) != '') {
+ imagestring(
+ $image, 1, $points_arr[0][0],
+ $points_arr[0][1], trim($label), $black
+ );
+ }
+ }
+ return $image;
+ }
+
+ /**
+ * Adds to the TCPDF instance, the data related to a row in the GIS dataset.
+ *
+ * @param string $spatial GIS POINT object
+ * @param string $label Label for the GIS POINT object
+ * @param string $point_color Color for the GIS POINT object
+ * @param array $scale_data Array containing data related to scaling
+ * @param TCPDF $pdf TCPDF instance
+ *
+ * @return TCPDF the modified TCPDF instance
+ * @access public
+ */
+ public function prepareRowAsPdf($spatial, $label, $point_color,
+ $scale_data, $pdf
+ ) {
+ // allocate colors
+ $red = hexdec(substr($point_color, 1, 2));
+ $green = hexdec(substr($point_color, 3, 2));
+ $blue = hexdec(substr($point_color, 4, 2));
+ $line = array('width' => 1.25, 'color' => array($red, $green, $blue));
+
+ // Trim to remove leading 'POINT(' and trailing ')'
+ $point = substr($spatial, 6, (strlen($spatial) - 7));
+ $points_arr = $this->extractPoints($point, $scale_data);
+
+ // draw a small circle to mark the point
+ if ($points_arr[0][0] != '' && $points_arr[0][1] != '') {
+ $pdf->Circle(
+ $points_arr[0][0], $points_arr[0][1], 2, 0, 360, 'D', $line
+ );
+ // print label if applicable
+ if (isset($label) && trim($label) != '') {
+ $pdf->SetXY($points_arr[0][0], $points_arr[0][1]);
+ $pdf->SetFontSize(5);
+ $pdf->Cell(0, 0, trim($label));
+ }
+ }
+ return $pdf;
+ }
+
+ /**
+ * Prepares and returns the code related to a row in the GIS dataset as SVG.
+ *
+ * @param string $spatial GIS POINT object
+ * @param string $label Label for the GIS POINT object
+ * @param string $point_color Color for the GIS POINT object
+ * @param array $scale_data Array containing data related to scaling
+ *
+ * @return string the code related to a row in the GIS dataset
+ * @access public
+ */
+ public function prepareRowAsSvg($spatial, $label, $point_color, $scale_data)
+ {
+ $point_options = array(
+ 'name' => $label,
+ 'id' => $label . rand(),
+ 'class' => 'point vector',
+ 'fill' => 'white',
+ 'stroke' => $point_color,
+ 'stroke-width'=> 2,
+ );
+
+ // Trim to remove leading 'POINT(' and trailing ')'
+ $point = substr($spatial, 6, (strlen($spatial) - 7));
+ $points_arr = $this->extractPoints($point, $scale_data);
+
+ $row = '';
+ if ($points_arr[0][0] != '' && $points_arr[0][1] != '') {
+ $row .= '<circle cx="' . $points_arr[0][0]
+ . '" cy="' . $points_arr[0][1] . '" r="3"';
+ foreach ($point_options as $option => $val) {
+ $row .= ' ' . $option . '="' . trim($val) . '"';
+ }
+ $row .= '/>';
+ }
+
+ return $row;
+ }
+
+ /**
+ * Prepares JavaScript related to a row in the GIS dataset
+ * to visualize it with OpenLayers.
+ *
+ * @param string $spatial GIS POINT object
+ * @param int $srid Spatial reference ID
+ * @param string $label Label for the GIS POINT object
+ * @param string $point_color Color for the GIS POINT object
+ * @param array $scale_data Array containing data related to scaling
+ *
+ * @return string JavaScript related to a row in the GIS dataset
+ * @access public
+ */
+ public function prepareRowAsOl($spatial, $srid, $label,
+ $point_color, $scale_data
+ ) {
+ $style_options = array(
+ 'pointRadius' => 3,
+ 'fillColor' => '#ffffff',
+ 'strokeColor' => $point_color,
+ 'strokeWidth' => 2,
+ 'label' => $label,
+ 'labelYOffset' => -8,
+ 'fontSize' => 10,
+ );
+ if ($srid == 0) {
+ $srid = 4326;
+ }
+ $result = $this->getBoundsForOl($srid, $scale_data);
+
+ // Trim to remove leading 'POINT(' and trailing ')'
+ $point = substr($spatial, 6, (strlen($spatial) - 7));
+ $points_arr = $this->extractPoints($point, null);
+
+ if ($points_arr[0][0] != '' && $points_arr[0][1] != '') {
+ $result .= 'vectorLayer.addFeatures(new OpenLayers.Feature.Vector('
+ . $this->getPointForOpenLayers($points_arr[0], $srid). ', null, '
+ . json_encode($style_options) . '));';
+ }
+ return $result;
+ }
+
+ /**
+ * Generate the WKT with the set of parameters passed by the GIS editor.
+ *
+ * @param array $gis_data GIS data
+ * @param int $index Index into the parameter object
+ * @param string $empty Point deos not adhere to this parameter
+ *
+ * @return string WKT with the set of parameters passed by the GIS editor
+ * @access public
+ */
+ public function generateWkt($gis_data, $index, $empty = '')
+ {
+ return 'POINT('
+ . ((isset($gis_data[$index]['POINT']['x'])
+ && trim($gis_data[$index]['POINT']['x']) != '')
+ ? $gis_data[$index]['POINT']['x'] : '')
+ . ' '
+ . ((isset($gis_data[$index]['POINT']['y'])
+ && trim($gis_data[$index]['POINT']['y']) != '')
+ ? $gis_data[$index]['POINT']['y'] : '') . ')';
+ }
+
+ /**
+ * Generate the WKT for the data from ESRI shape files.
+ *
+ * @param array $row_data GIS data
+ *
+ * @return string the WKT for the data from ESRI shape files
+ * @access public
+ */
+ public function getShape($row_data)
+ {
+ return 'POINT(' . (isset($row_data['x']) ? $row_data['x'] : '')
+ . ' ' . (isset($row_data['y']) ? $row_data['y'] : '') . ')';
+ }
+
+ /**
+ * Generate parameters for the GIS data editor from the value of the GIS column.
+ *
+ * @param string $value of the GIS column
+ * @param int $index of the geometry
+ *
+ * @return array params for the GIS data editor from the value of the GIS column
+ * @access public
+ */
+ public function generateParams($value, $index = -1)
+ {
+ if ($index == -1) {
+ $index = 0;
+ $params = array();
+ $data = PMA_GIS_Geometry::generateParams($value);
+ $params['srid'] = $data['srid'];
+ $wkt = $data['wkt'];
+ } else {
+ $params[$index]['gis_type'] = 'POINT';
+ $wkt = $value;
+ }
+
+ // Trim to remove leading 'POINT(' and trailing ')'
+ $point = substr($wkt, 6, (strlen($wkt) - 7));
+ $points_arr = $this->extractPoints($point, null);
+
+ $params[$index]['POINT']['x'] = $points_arr[0][0];
+ $params[$index]['POINT']['y'] = $points_arr[0][1];
+
+ return $params;
+ }
+}
+?>
diff --git a/libraries/gis/pma_gis_polygon.php b/libraries/gis/pma_gis_polygon.php
new file mode 100644
index 0000000000..215625e13e
--- /dev/null
+++ b/libraries/gis/pma_gis_polygon.php
@@ -0,0 +1,549 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Handles actions related to GIS POLYGON objects
+ *
+ * @package PhpMyAdmin-GIS
+ */
+
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ * Handles actions related to GIS POLYGON objects
+ *
+ * @package PhpMyAdmin-GIS
+ */
+class PMA_GIS_Polygon extends PMA_GIS_Geometry
+{
+ // Hold the singleton instance of the class
+ private static $_instance;
+
+ /**
+ * A private constructor; prevents direct creation of object.
+ *
+ * @access private
+ */
+ private function __construct()
+ {
+ }
+
+ /**
+ * Returns the singleton.
+ *
+ * @return PMA_GIS_Polygon the singleton
+ * @access public
+ */
+ public static function singleton()
+ {
+ if (!isset(self::$_instance)) {
+ $class = __CLASS__;
+ self::$_instance = new $class;
+ }
+
+ return self::$_instance;
+ }
+
+ /**
+ * Scales each row.
+ *
+ * @param string $spatial spatial data of a row
+ *
+ * @return array an array containing the min, max values for x and y cordinates
+ * @access public
+ */
+ public function scaleRow($spatial)
+ {
+ // Trim to remove leading 'POLYGON((' and trailing '))'
+ $polygon = substr($spatial, 9, (strlen($spatial) - 11));
+
+ // If the polygon doesn't have an inner ring, use polygon itself
+ if (strpos($polygon, "),(") === false) {
+ $ring = $polygon;
+ } else {
+ // Seperate outer ring and use it to determin min-max
+ $parts = explode("),(", $polygon);
+ $ring = $parts[0];
+ }
+ return $this->setMinMax($ring, array());
+ }
+
+ /**
+ * Adds to the PNG image object, the data related to a row in the GIS dataset.
+ *
+ * @param string $spatial GIS POLYGON object
+ * @param string $label Label for the GIS POLYGON object
+ * @param string $fill_color Color for the GIS POLYGON object
+ * @param array $scale_data Array containing data related to scaling
+ * @param object $image Image object
+ *
+ * @return object the modified image object
+ * @access public
+ */
+ public function prepareRowAsPng($spatial, $label, $fill_color,
+ $scale_data, $image
+ ) {
+ // allocate colors
+ $black = imagecolorallocate($image, 0, 0, 0);
+ $red = hexdec(substr($fill_color, 1, 2));
+ $green = hexdec(substr($fill_color, 3, 2));
+ $blue = hexdec(substr($fill_color, 4, 2));
+ $color = imagecolorallocate($image, $red, $green, $blue);
+
+ // Trim to remove leading 'POLYGON((' and trailing '))'
+ $polygon = substr($spatial, 9, (strlen($spatial) - 11));
+
+ // If the polygon doesnt have an inner polygon
+ if (strpos($polygon, "),(") === false) {
+ $points_arr = $this->extractPoints($polygon, $scale_data, true);
+ } else {
+ // Seperate outer and inner polygons
+ $parts = explode("),(", $polygon);
+ $outer = $parts[0];
+ $inner = array_slice($parts, 1);
+
+ $points_arr = $this->extractPoints($outer, $scale_data, true);
+
+ foreach ($inner as $inner_poly) {
+ $points_arr = array_merge(
+ $points_arr, $this->extractPoints($inner_poly, $scale_data, true)
+ );
+ }
+ }
+
+ // draw polygon
+ imagefilledpolygon($image, $points_arr, sizeof($points_arr) / 2, $color);
+ // print label if applicable
+ if (isset($label) && trim($label) != '') {
+ imagestring(
+ $image, 1, $points_arr[2], $points_arr[3], trim($label), $black
+ );
+ }
+ return $image;
+ }
+
+ /**
+ * Adds to the TCPDF instance, the data related to a row in the GIS dataset.
+ *
+ * @param string $spatial GIS POLYGON object
+ * @param string $label Label for the GIS POLYGON object
+ * @param string $fill_color Color for the GIS POLYGON object
+ * @param array $scale_data Array containing data related to scaling
+ * @param TCPDF $pdf TCPDF instance
+ *
+ * @return TCPDF the modified TCPDF instance
+ * @access public
+ */
+ public function prepareRowAsPdf($spatial, $label, $fill_color, $scale_data, $pdf)
+ {
+ // allocate colors
+ $red = hexdec(substr($fill_color, 1, 2));
+ $green = hexdec(substr($fill_color, 3, 2));
+ $blue = hexdec(substr($fill_color, 4, 2));
+ $color = array($red, $green, $blue);
+
+ // Trim to remove leading 'POLYGON((' and trailing '))'
+ $polygon = substr($spatial, 9, (strlen($spatial) - 11));
+
+ // If the polygon doesnt have an inner polygon
+ if (strpos($polygon, "),(") === false) {
+ $points_arr = $this->extractPoints($polygon, $scale_data, true);
+ } else {
+ // Seperate outer and inner polygons
+ $parts = explode("),(", $polygon);
+ $outer = $parts[0];
+ $inner = array_slice($parts, 1);
+
+ $points_arr = $this->extractPoints($outer, $scale_data, true);
+
+ foreach ($inner as $inner_poly) {
+ $points_arr = array_merge(
+ $points_arr, $this->extractPoints($inner_poly, $scale_data, true)
+ );
+ }
+ }
+
+ // draw polygon
+ $pdf->Polygon($points_arr, 'F*', array(), $color, true);
+ // print label if applicable
+ if (isset($label) && trim($label) != '') {
+ $pdf->SetXY($points_arr[2], $points_arr[3]);
+ $pdf->SetFontSize(5);
+ $pdf->Cell(0, 0, trim($label));
+ }
+ return $pdf;
+ }
+
+ /**
+ * Prepares and returns the code related to a row in the GIS dataset as SVG.
+ *
+ * @param string $spatial GIS POLYGON object
+ * @param string $label Label for the GIS POLYGON object
+ * @param string $fill_color Color for the GIS POLYGON object
+ * @param array $scale_data Array containing data related to scaling
+ *
+ * @return string the code related to a row in the GIS dataset
+ * @access public
+ */
+ public function prepareRowAsSvg($spatial, $label, $fill_color, $scale_data)
+ {
+ $polygon_options = array(
+ 'name' => $label,
+ 'id' => $label . rand(),
+ 'class' => 'polygon vector',
+ 'stroke' => 'black',
+ 'stroke-width'=> 0.5,
+ 'fill' => $fill_color,
+ 'fill-rule' => 'evenodd',
+ 'fill-opacity'=> 0.8,
+ );
+
+ // Trim to remove leading 'POLYGON((' and trailing '))'
+ $polygon = substr($spatial, 9, (strlen($spatial) - 11));
+
+ $row = '<path d="';
+
+ // If the polygon doesnt have an inner polygon
+ if (strpos($polygon, "),(") === false) {
+ $row .= $this->_drawPath($polygon, $scale_data);
+ } else {
+ // Seperate outer and inner polygons
+ $parts = explode("),(", $polygon);
+ $outer = $parts[0];
+ $inner = array_slice($parts, 1);
+
+ $row .= $this->_drawPath($outer, $scale_data);
+
+ foreach ($inner as $inner_poly) {
+ $row .= $this->_drawPath($inner_poly, $scale_data);
+ }
+ }
+
+ $row .= '"';
+ foreach ($polygon_options as $option => $val) {
+ $row .= ' ' . $option . '="' . trim($val) . '"';
+ }
+ $row .= '/>';
+ return $row;
+ }
+
+ /**
+ * Prepares JavaScript related to a row in the GIS dataset
+ * to visualize it with OpenLayers.
+ *
+ * @param string $spatial GIS POLYGON object
+ * @param int $srid Spatial reference ID
+ * @param string $label Label for the GIS POLYGON object
+ * @param string $fill_color Color for the GIS POLYGON object
+ * @param array $scale_data Array containing data related to scaling
+ *
+ * @return string JavaScript related to a row in the GIS dataset
+ * @access public
+ */
+ public function prepareRowAsOl($spatial, $srid, $label, $fill_color, $scale_data)
+ {
+ $style_options = array(
+ 'strokeColor' => '#000000',
+ 'strokeWidth' => 0.5,
+ 'fillColor' => $fill_color,
+ 'fillOpacity' => 0.8,
+ 'label' => $label,
+ 'fontSize' => 10,
+ );
+ if ($srid == 0) {
+ $srid = 4326;
+ }
+ $row = $this->getBoundsForOl($srid, $scale_data);
+
+ // Trim to remove leading 'POLYGON((' and trailing '))'
+ $polygon = substr($spatial, 9, (strlen($spatial) - 11));
+
+ // Seperate outer and inner polygons
+ $parts = explode("),(", $polygon);
+ $row .= 'vectorLayer.addFeatures(new OpenLayers.Feature.Vector('
+ . $this->getPolygonForOpenLayers($parts, $srid)
+ . ', null, ' . json_encode($style_options) . '));';
+ return $row;
+ }
+
+ /**
+ * Draws a ring of the polygon using SVG path element.
+ *
+ * @param string $polygon The ring
+ * @param array $scale_data Array containing data related to scaling
+ *
+ * @return string the code to draw the ring
+ * @access private
+ */
+ private function _drawPath($polygon, $scale_data)
+ {
+ $points_arr = $this->extractPoints($polygon, $scale_data);
+
+ $row = ' M ' . $points_arr[0][0] . ', ' . $points_arr[0][1];
+ $other_points = array_slice($points_arr, 1, count($points_arr) - 2);
+ foreach ($other_points as $point) {
+ $row .= ' L ' . $point[0] . ', ' . $point[1];
+ }
+ $row .= ' Z ';
+
+ return $row;
+ }
+
+ /**
+ * Generate the WKT with the set of parameters passed by the GIS editor.
+ *
+ * @param array $gis_data GIS data
+ * @param int $index Index into the parameter object
+ * @param string $empty Value for empty points
+ *
+ * @return string WKT with the set of parameters passed by the GIS editor
+ * @access public
+ */
+ public function generateWkt($gis_data, $index, $empty = '')
+ {
+ $no_of_lines = isset($gis_data[$index]['POLYGON']['no_of_lines'])
+ ? $gis_data[$index]['POLYGON']['no_of_lines'] : 1;
+ if ($no_of_lines < 1) {
+ $no_of_lines = 1;
+ }
+ $wkt = 'POLYGON(';
+ for ($i = 0; $i < $no_of_lines; $i++) {
+ $no_of_points = isset($gis_data[$index]['POLYGON'][$i]['no_of_points'])
+ ? $gis_data[$index]['POLYGON'][$i]['no_of_points'] : 4;
+ if ($no_of_points < 4) {
+ $no_of_points = 4;
+ }
+ $wkt .= '(';
+ for ($j = 0; $j < $no_of_points; $j++) {
+ $wkt .= ((isset($gis_data[$index]['POLYGON'][$i][$j]['x'])
+ && trim($gis_data[$index]['POLYGON'][$i][$j]['x']) != '')
+ ? $gis_data[$index]['POLYGON'][$i][$j]['x'] : $empty)
+ . ' ' . ((isset($gis_data[$index]['POLYGON'][$i][$j]['y'])
+ && trim($gis_data[$index]['POLYGON'][$i][$j]['y']) != '')
+ ? $gis_data[$index]['POLYGON'][$i][$j]['y'] : $empty) .',';
+ }
+ $wkt = substr($wkt, 0, strlen($wkt) - 1);
+ $wkt .= '),';
+ }
+ $wkt = substr($wkt, 0, strlen($wkt) - 1);
+ $wkt .= ')';
+ return $wkt;
+ }
+
+ /**
+ * Calculates the area of a closed simple polygon.
+ *
+ * @param array $ring array of points forming the ring
+ *
+ * @return float the area of a closed simple polygon
+ * @access public
+ * @static
+ */
+ public static function area($ring)
+ {
+
+ $no_of_points = count($ring);
+
+ // If the last point is same as the first point ignore it
+ $last = count($ring) - 1;
+ if (($ring[0]['x'] == $ring[$last]['x'])
+ && ($ring[0]['y'] == $ring[$last]['y'])
+ ) {
+ $no_of_points--;
+ }
+
+ // _n-1
+ // A = _1_ \ (X(i) * Y(i+1)) - (Y(i) * X(i+1))
+ // 2 /__
+ // i=0
+ $area = 0;
+ for ($i = 0; $i < $no_of_points; $i++) {
+ $j = ($i + 1) % $no_of_points;
+ $area += $ring[$i]['x'] * $ring[$j]['y'];
+ $area -= $ring[$i]['y'] * $ring[$j]['x'];
+ }
+ $area /= 2.0;
+
+ return $area;
+ }
+
+ /**
+ * Determines whether a set of points represents an outer ring.
+ * If points are in clockwise orientation then, they form an outer ring.
+ *
+ * @param array $ring array of points forming the ring
+ *
+ * @return bool whether a set of points represents an outer ring
+ * @access public
+ * @static
+ */
+ public static function isOuterRing($ring)
+ {
+ // If area is negative then it's in clockwise orientation,
+ // i.e. it's an outer ring
+ if (PMA_GIS_Polygon::area($ring) < 0) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Determines whether a given point is inside a given polygon.
+ *
+ * @param array $point x, y coordinates of the point
+ * @param array $polygon array of points forming the ring
+ *
+ * @return bool whether a given point is inside a given polygon
+ * @access public
+ * @static
+ */
+ public static function isPointInsidePolygon($point, $polygon)
+ {
+ // If first point is repeated at the end remove it
+ $last = count($polygon) - 1;
+ if (($polygon[0]['x'] == $polygon[$last]['x'])
+ && ($polygon[0]['y'] == $polygon[$last]['y'])
+ ) {
+ $polygon = array_slice($polygon, 0, $last);
+ }
+
+ $no_of_points = count($polygon);
+ $counter = 0;
+
+ // Use ray casting algorithm
+ $p1 = $polygon[0];
+ for ($i = 1; $i <= $no_of_points; $i++) {
+ $p2 = $polygon[$i % $no_of_points];
+ if ($point['y'] > min(array($p1['y'], $p2['y']))) {
+ if ($point['y'] <= max(array($p1['y'], $p2['y']))) {
+ if ($point['x'] <= max(array($p1['x'], $p2['x']))) {
+ if ($p1['y'] != $p2['y']) {
+ $xinters = ($point['y'] - $p1['y'])
+ * ($p2['x'] - $p1['x'])
+ / ($p2['y'] - $p1['y']) + $p1['x'];
+ if ($p1['x'] == $p2['x'] || $point['x'] <= $xinters) {
+ $counter++;
+ }
+ }
+ }
+ }
+ }
+ $p1 = $p2;
+ }
+
+ if ($counter % 2 == 0) {
+ return false;
+ } else {
+ return true;
+ }
+ }
+
+ /**
+ * Returns a point that is guaranteed to be on the surface of the ring.
+ * (for simple closed rings)
+ *
+ * @param array $ring array of points forming the ring
+ *
+ * @return array|void a point on the surface of the ring
+ * @access public
+ * @static
+ */
+ public static function getPointOnSurface($ring)
+ {
+ // Find two consecutive distinct points.
+ for ($i = 0; $i < count($ring) - 1; $i++) {
+ if ($ring[$i]['y'] != $ring[$i + 1]['y']) {
+ $x0 = $ring[$i]['x'];
+ $x1 = $ring[$i + 1]['x'];
+ $y0 = $ring[$i]['y'];
+ $y1 = $ring[$i + 1]['y'];
+ break;
+ }
+ }
+
+ if (! isset($x0)) {
+ return false;
+ }
+
+ // Find the mid point
+ $x2 = ($x0 + $x1) / 2;
+ $y2 = ($y0 + $y1) / 2;
+
+ // Always keep $epsilon < 1 to go with the reduction logic down here
+ $epsilon = 0.1;
+ $denominator = sqrt(
+ PMA_Util::pow(($y1 - $y0), 2)
+ + PMA_Util::pow(($x0 - $x1), 2)
+ );
+ $pointA = array(); $pointB = array();
+
+ while (true) {
+ // Get the points on either sides of the line
+ // with a distance of epsilon to the mid point
+ $pointA['x'] = $x2 + ($epsilon * ($y1 - $y0)) / $denominator;
+ $pointA['y'] = $y2 + ($pointA['x'] - $x2) * ($x0 - $x1) / ($y1 - $y0);
+
+ $pointB['x'] = $x2 + ($epsilon * ($y1 - $y0)) / (0 - $denominator);
+ $pointB['y'] = $y2 + ($pointB['x'] - $x2) * ($x0 - $x1) / ($y1 - $y0);
+
+ // One of the points should be inside the polygon,
+ // unless epcilon chosen is too large
+ if (PMA_GIS_Polygon::isPointInsidePolygon($pointA, $ring)) {
+ return $pointA;
+ } elseif (PMA_GIS_Polygon::isPointInsidePolygon($pointB, $ring)) {
+ return $pointB;
+ } else {
+ //If both are outside the polygon reduce the epsilon and
+ //recalculate the points(reduce exponentially for faster convergance)
+ $epsilon = PMA_Util::pow($epsilon, 2);
+ if ($epsilon == 0) {
+ return false;
+ }
+ }
+
+ }
+ }
+
+ /** Generate parameters for the GIS data editor from the value of the GIS column.
+ *
+ * @param string $value Value of the GIS column
+ * @param int $index Index of the geometry
+ *
+ * @return array params for the GIS data editor from the value of the GIS column
+ * @access public
+ */
+ public function generateParams($value, $index = -1)
+ {
+ if ($index == -1) {
+ $index = 0;
+ $params = array();
+ $data = PMA_GIS_Geometry::generateParams($value);
+ $params['srid'] = $data['srid'];
+ $wkt = $data['wkt'];
+ } else {
+ $params[$index]['gis_type'] = 'POLYGON';
+ $wkt = $value;
+ }
+
+ // Trim to remove leading 'POLYGON((' and trailing '))'
+ $polygon = substr($wkt, 9, (strlen($wkt) - 11));
+ // Seperate each linestring
+ $linerings = explode("),(", $polygon);
+ $params[$index]['POLYGON']['no_of_lines'] = count($linerings);
+
+ $j = 0;
+ foreach ($linerings as $linering) {
+ $points_arr = $this->extractPoints($linering, null);
+ $no_of_points = count($points_arr);
+ $params[$index]['POLYGON'][$j]['no_of_points'] = $no_of_points;
+ for ($i = 0; $i < $no_of_points; $i++) {
+ $params[$index]['POLYGON'][$j][$i]['x'] = $points_arr[$i][0];
+ $params[$index]['POLYGON'][$j][$i]['y'] = $points_arr[$i][1];
+ }
+ $j++;
+ }
+ return $params;
+ }
+}
+?>
diff --git a/libraries/gis/pma_gis_visualization.php b/libraries/gis/pma_gis_visualization.php
new file mode 100644
index 0000000000..85f0e289bf
--- /dev/null
+++ b/libraries/gis/pma_gis_visualization.php
@@ -0,0 +1,503 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Handles visualization of GIS data
+ *
+ * @package PhpMyAdmin-GIS
+ */
+
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ * Handles visualization of GIS data
+ *
+ * @package PhpMyAdmin-GIS
+ */
+class PMA_GIS_Visualization
+{
+ /**
+ * @var array Raw data for the visualization
+ */
+ private $_data;
+
+ /**
+ * @var array Set of default settigs values are here.
+ */
+ private $_settings = array(
+
+ // Array of colors to be used for GIS visualizations.
+ 'colors' => array(
+ '#B02EE0',
+ '#E0642E',
+ '#E0D62E',
+ '#2E97E0',
+ '#BCE02E',
+ '#E02E75',
+ '#5CE02E',
+ '#E0B02E',
+ '#0022E0',
+ '#726CB1',
+ '#481A36',
+ '#BAC658',
+ '#127224',
+ '#825119',
+ '#238C74',
+ '#4C489B',
+ '#87C9BF',
+ ),
+
+ // The width of the GIS visualization.
+ 'width' => 600,
+
+ // The height of the GIS visualization.
+ 'height' => 450,
+ );
+
+ /**
+ * @var array Options that the user has specified.
+ */
+ private $_userSpecifiedSettings = null;
+
+ /**
+ * Returns the settings array
+ *
+ * @return array the settings array
+ * @access public
+ */
+ public function getSettings()
+ {
+ return $this->_settings;
+ }
+
+ /**
+ * Constructor. Stores user specified options.
+ *
+ * @param array $data Data for the visualization
+ * @param array $options Users specified options
+ *
+ * @access public
+ */
+ public function __construct($data, $options)
+ {
+ $this->_userSpecifiedSettings = $options;
+ $this->_data = $data;
+ }
+
+ /**
+ * All the variable initialization, options handling has to be done here.
+ *
+ * @return void
+ * @access protected
+ */
+ protected function init()
+ {
+ $this->_handleOptions();
+ }
+
+ /**
+ * A function which handles passed parameters. Useful if desired
+ * chart needs to be a little bit different from the default one.
+ *
+ * @return void
+ * @access private
+ */
+ private function _handleOptions()
+ {
+ if (! is_null($this->_userSpecifiedSettings)) {
+ $this->_settings = array_merge(
+ $this->_settings,
+ $this->_userSpecifiedSettings
+ );
+ }
+ }
+
+ /**
+ * Sanitizes the file name.
+ *
+ * @param string $file_name file name
+ * @param string $ext extension of the file
+ *
+ * @return string the sanitized file name
+ * @access private
+ */
+ private function _sanitizeName($file_name, $ext)
+ {
+ $file_name = PMA_sanitizeFilename($file_name);
+
+ // Check if the user already added extension;
+ // get the substring where the extension would be if it was included
+ $extension_start_pos = strlen($file_name) - strlen($ext) - 1;
+ $user_extension = substr(
+ $file_name, $extension_start_pos, strlen($file_name)
+ );
+ $required_extension = "." . $ext;
+ if (strtolower($user_extension) != $required_extension) {
+ $file_name .= $required_extension;
+ }
+ return $file_name;
+ }
+
+ /**
+ * Handles common tasks of writing the visualization to file for various formats.
+ *
+ * @param string $file_name file name
+ * @param string $type mime type
+ * @param string $ext extension of the file
+ *
+ * @return void
+ * @access private
+ */
+ private function _toFile($file_name, $type, $ext)
+ {
+ $file_name = $this->_sanitizeName($file_name, $ext);
+ PMA_downloadHeader($file_name, $type);
+ }
+
+ /**
+ * Generate the visualization in SVG format.
+ *
+ * @return string the generated image resource
+ * @access private
+ */
+ private function _svg()
+ {
+ $this->init();
+
+ $output = '<?xml version="1.0" encoding="UTF-8" standalone="no"?>' . "\n";
+ $output .= '<svg version="1.1" xmlns:svg="http://www.w3.org/2000/svg"'
+ . ' xmlns="http://www.w3.org/2000/svg"'
+ . ' width="' . $this->_settings['width'] . '"'
+ . ' height="' . $this->_settings['height'] . '">';
+ $output .= '<g id="groupPanel">';
+
+ $scale_data = $this->_scaleDataSet($this->_data);
+ $output .= $this->_prepareDataSet($this->_data, $scale_data, 'svg', '');
+
+ $output .= '</g>';
+ $output .= '</svg>';
+
+ return $output;
+ }
+
+ /**
+ * Get the visualization as a SVG.
+ *
+ * @return string the visualization as a SVG
+ * @access public
+ */
+ public function asSVG()
+ {
+ $output = $this->_svg();
+ return $output;
+ }
+
+ /**
+ * Saves as a SVG image to a file.
+ *
+ * @param string $file_name File name
+ *
+ * @return void
+ * @access public
+ */
+ public function toFileAsSvg($file_name)
+ {
+ $img = $this->_svg();
+ $this->_toFile($file_name, 'image/svg+xml', 'svg');
+ echo($img);
+ }
+
+ /**
+ * Generate the visualization in PNG format.
+ *
+ * @return resource the generated image resource
+ * @access private
+ */
+ private function _png()
+ {
+ $this->init();
+
+ // create image
+ $image = imagecreatetruecolor(
+ $this->_settings['width'],
+ $this->_settings['height']
+ );
+
+ // fill the background
+ $bg = imagecolorallocate($image, 229, 229, 229);
+ imagefilledrectangle(
+ $image, 0, 0, $this->_settings['width'] - 1,
+ $this->_settings['height'] - 1, $bg
+ );
+
+ $scale_data = $this->_scaleDataSet($this->_data);
+ $image = $this->_prepareDataSet($this->_data, $scale_data, 'png', $image);
+
+ return $image;
+ }
+
+ /**
+ * Get the visualization as a PNG.
+ *
+ * @return string the visualization as a PNG
+ * @access public
+ */
+ public function asPng()
+ {
+ $img = $this->_png();
+
+ // render and save it to variable
+ ob_start();
+ imagepng($img, null, 9, PNG_ALL_FILTERS);
+ imagedestroy($img);
+ $output = ob_get_contents();
+ ob_end_clean();
+
+ // base64 encode
+ $encoded = base64_encode($output);
+ return '<img src="data:image/png;base64,'. $encoded .'" />';
+ }
+
+ /**
+ * Saves as a PNG image to a file.
+ *
+ * @param string $file_name File name
+ *
+ * @return void
+ * @access public
+ */
+ public function toFileAsPng($file_name)
+ {
+ $img = $this->_png();
+ $this->_toFile($file_name, 'image/png', 'png');
+ imagepng($img, null, 9, PNG_ALL_FILTERS);
+ imagedestroy($img);
+ }
+
+ /**
+ * Get the code for visualization with OpenLayers.
+ *
+ * @return string the code for visualization with OpenLayers
+ * @access public
+ */
+ public function asOl()
+ {
+ $this->init();
+ $scale_data = $this->_scaleDataSet($this->_data);
+ $output
+ = 'var options = {'
+ . 'projection: new OpenLayers.Projection("EPSG:900913"),'
+ . 'displayProjection: new OpenLayers.Projection("EPSG:4326"),'
+ . 'units: "m",'
+ . 'numZoomLevels: 18,'
+ . 'maxResolution: 156543.0339,'
+ . 'maxExtent: new OpenLayers.Bounds('
+ . '-20037508, -20037508, 20037508, 20037508),'
+ . 'restrictedExtent: new OpenLayers.Bounds('
+ . '-20037508, -20037508, 20037508, 20037508)'
+ . '};'
+ . 'var map = new OpenLayers.Map("openlayersmap", options);'
+ . 'var layerNone = new OpenLayers.Layer.Boxes('
+ . '"None", {isBaseLayer: true});'
+ . 'var layerMapnik = new OpenLayers.Layer.OSM.Mapnik("Mapnik");'
+ . 'var layerCycleMap = new OpenLayers.Layer.OSM.CycleMap("CycleMap");'
+ . 'map.addLayers([layerMapnik,layerCycleMap,layerNone]);'
+ . 'var vectorLayer = new OpenLayers.Layer.Vector("Data");'
+ . 'var bound;';
+ $output .= $this->_prepareDataSet($this->_data, $scale_data, 'ol', '');
+ $output .=
+ 'map.addLayer(vectorLayer);'
+ . 'map.zoomToExtent(bound);'
+ . 'if (map.getZoom() < 2) {'
+ . 'map.zoomTo(2);'
+ . '}'
+ . 'map.addControl(new OpenLayers.Control.LayerSwitcher());'
+ . 'map.addControl(new OpenLayers.Control.MousePosition());';
+ return $output;
+ }
+
+ /**
+ * Saves as a PDF to a file.
+ *
+ * @param string $file_name File name
+ *
+ * @return void
+ * @access public
+ */
+ public function toFileAsPdf($file_name)
+ {
+ $this->init();
+
+ include_once './libraries/tcpdf/tcpdf.php';
+
+ // create pdf
+ $pdf = new TCPDF(
+ '', 'pt', $GLOBALS['cfg']['PDFDefaultPageSize'], true, 'UTF-8', false
+ );
+
+ // disable header and footer
+ $pdf->setPrintHeader(false);
+ $pdf->setPrintFooter(false);
+
+ //set auto page breaks
+ $pdf->SetAutoPageBreak(false);
+
+ // add a page
+ $pdf->AddPage();
+
+ $scale_data = $this->_scaleDataSet($this->_data);
+ $pdf = $this->_prepareDataSet($this->_data, $scale_data, 'pdf', $pdf);
+
+ // sanitize file name
+ $file_name = $this->_sanitizeName($file_name, 'pdf');
+ $pdf->Output($file_name, 'D');
+ }
+
+ /**
+ * Calculates the scale, horizontal and vertical offset that should be used.
+ *
+ * @param array $data Row data
+ *
+ * @return array an array containing the scale, x and y offsets
+ * @access private
+ */
+ private function _scaleDataSet($data)
+ {
+ $min_max = array();
+ $border = 15;
+ // effective width and height of the plot
+ $plot_width = $this->_settings['width'] - 2 * $border;
+ $plot_height = $this->_settings['height'] - 2 * $border;
+
+ foreach ($data as $row) {
+
+ // Figure out the data type
+ $ref_data = $row[$this->_settings['spatialColumn']];
+ $type_pos = stripos($ref_data, '(');
+ $type = substr($ref_data, 0, $type_pos);
+
+ $gis_obj = PMA_GIS_Factory::factory($type);
+ if (! $gis_obj) {
+ continue;
+ }
+ $scale_data = $gis_obj->scaleRow(
+ $row[$this->_settings['spatialColumn']]
+ );
+
+ // Upadate minimum/maximum values for x and y cordinates.
+ $c_maxX = (float) $scale_data['maxX'];
+ if (! isset($min_max['maxX']) || $c_maxX > $min_max['maxX']) {
+ $min_max['maxX'] = $c_maxX;
+ }
+
+ $c_minX = (float) $scale_data['minX'];
+ if (! isset($min_max['minX']) || $c_minX < $min_max['minX']) {
+ $min_max['minX'] = $c_minX;
+ }
+
+ $c_maxY = (float) $scale_data['maxY'];
+ if (! isset($min_max['maxY']) || $c_maxY > $min_max['maxY']) {
+ $min_max['maxY'] = $c_maxY;
+ }
+
+ $c_minY = (float) $scale_data['minY'];
+ if (! isset($min_max['minY']) || $c_minY < $min_max['minY']) {
+ $min_max['minY'] = $c_minY;
+ }
+ }
+
+ // scale the visualization
+ $x_ratio = ($min_max['maxX'] - $min_max['minX']) / $plot_width;
+ $y_ratio = ($min_max['maxY'] - $min_max['minY']) / $plot_height;
+ $ratio = ($x_ratio > $y_ratio) ? $x_ratio : $y_ratio;
+
+ $scale = ($ratio != 0) ? (1 / $ratio) : 1;
+
+ if ($x_ratio < $y_ratio) {
+ // center horizontally
+ $x = ($min_max['maxX'] + $min_max['minX'] - $plot_width / $scale) / 2;
+ // fit vertically
+ $y = $min_max['minY'] - ($border / $scale);
+ } else {
+ // fit horizontally
+ $x = $min_max['minX'] - ($border / $scale);
+ // center vertically
+ $y =($min_max['maxY'] + $min_max['minY'] - $plot_height / $scale) / 2;
+ }
+
+ return array(
+ 'scale' => $scale,
+ 'x' => $x,
+ 'y' => $y,
+ 'minX' => $min_max['minX'],
+ 'maxX' => $min_max['maxX'],
+ 'minY' => $min_max['minY'],
+ 'maxY' => $min_max['maxY'],
+ 'height' => $this->_settings['height'],
+ );
+ }
+
+ /**
+ * Prepares and return the dataset as needed by the visualization.
+ *
+ * @param array $data Raw data
+ * @param array $scale_data Data related to scaling
+ * @param string $format Format of the visulaization
+ * @param object $results Image object in the case of png
+ * TCPDF object in the case of pdf
+ *
+ * @return mixed the formatted array of data
+ * @access private
+ */
+ private function _prepareDataSet($data, $scale_data, $format, $results)
+ {
+ $color_number = 0;
+
+ // loop through the rows
+ foreach ($data as $row) {
+ $index = $color_number % sizeof($this->_settings['colors']);
+
+ // Figure out the data type
+ $ref_data = $row[$this->_settings['spatialColumn']];
+ $type_pos = stripos($ref_data, '(');
+ $type = substr($ref_data, 0, $type_pos);
+
+ $gis_obj = PMA_GIS_Factory::factory($type);
+ if (! $gis_obj) {
+ continue;
+ }
+ $label = '';
+ if (isset($this->_settings['labelColumn'])
+ && isset($row[$this->_settings['labelColumn']])
+ ) {
+ $label = $row[$this->_settings['labelColumn']];
+ }
+
+ if ($format == 'svg') {
+ $results .= $gis_obj->prepareRowAsSvg(
+ $row[$this->_settings['spatialColumn']], $label,
+ $this->_settings['colors'][$index], $scale_data
+ );
+ } elseif ($format == 'png') {
+ $results = $gis_obj->prepareRowAsPng(
+ $row[$this->_settings['spatialColumn']], $label,
+ $this->_settings['colors'][$index], $scale_data, $results
+ );
+ } elseif ($format == 'pdf') {
+ $results = $gis_obj->prepareRowAsPdf(
+ $row[$this->_settings['spatialColumn']], $label,
+ $this->_settings['colors'][$index], $scale_data, $results
+ );
+ } elseif ($format == 'ol') {
+ $results .= $gis_obj->prepareRowAsOl(
+ $row[$this->_settings['spatialColumn']], $row['srid'],
+ $label, $this->_settings['colors'][$index], $scale_data
+ );
+ }
+ $color_number++;
+ }
+ return $results;
+ }
+}
+?>
diff --git a/libraries/iconv_wrapper.lib.php b/libraries/iconv_wrapper.lib.php
new file mode 100644
index 0000000000..bc822e6fee
--- /dev/null
+++ b/libraries/iconv_wrapper.lib.php
@@ -0,0 +1,120 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Iconv wrapper for AIX
+ *
+ * @package PhpMyAdmin
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ * GNU iconv code set to IBM AIX libiconv code set table
+ * Keys of this table should be in lowercase,
+ * and searches should be performed using lowercase!
+ */
+$gnu_iconv_to_aix_iconv_codepage_map = array (
+ // "iso-8859-[1-9]" --> "ISO8859-[1-9]" according to
+ // http://publibn.boulder.ibm.com/doc_link/en_US/
+ // a_doc_lib/libs/basetrf2/setlocale.htm
+ 'iso-8859-1' => 'ISO8859-1',
+ 'iso-8859-2' => 'ISO8859-2',
+ 'iso-8859-3' => 'ISO8859-3',
+ 'iso-8859-4' => 'ISO8859-4',
+ 'iso-8859-5' => 'ISO8859-5',
+ 'iso-8859-6' => 'ISO8859-6',
+ 'iso-8859-7' => 'ISO8859-7',
+ 'iso-8859-8' => 'ISO8859-8',
+ 'iso-8859-9' => 'ISO8859-9',
+
+ // "big5" --> "IBM-eucTW" according to
+ // http://kadesh.cepba.upc.es/mancpp/classref/ref/ITranscoder_DSC.htm
+ 'big5' => 'IBM-eucTW',
+
+ // Other mappings corresponding to the phpMyAdmin dropdown box when using the
+ // charset conversion feature
+ 'euc-jp' => 'IBM-eucJP',
+ 'koi8-r' => 'IBM-eucKR',
+ 'ks_c_5601-1987' => 'KSC5601.1987-0',
+ 'tis-620' => 'TIS-620',
+ 'utf-8' => 'UTF-8'
+);
+
+/**
+ * Wrapper around IBM AIX iconv(), whose character set naming differs
+ * from the GNU version of iconv().
+ *
+ * @param string $in_charset input character set
+ * @param string $out_charset output character set
+ * @param string $str the string to convert
+ *
+ * @return mixed converted string or false on failure
+ *
+ * @access public
+ *
+ */
+function PMA_convertAIXIconv($in_charset, $out_charset, $str)
+{
+ list($in_charset, $out_charset) = PMA_convertAIXMapCharsets(
+ $in_charset, $out_charset
+ );
+ // Call iconv() with the possibly modified parameters
+ return iconv($in_charset, $out_charset, $str);
+} // end of the "PMA_convertAIXIconv()" function
+
+/**
+ * Maps input and output character set names to corresponding AIX ones
+ *
+ * @param string $in_charset input character set
+ * @param string $out_charset output character set
+ *
+ * @return array array of mapped input and output character set names
+ */
+function PMA_convertAIXMapCharsets($in_charset, $out_charset)
+{
+ global $gnu_iconv_to_aix_iconv_codepage_map;
+
+ // Check for transliteration argument at the end of output character set name
+ $translit_search = strpos(strtolower($out_charset), '//translit');
+ $using_translit = (!($translit_search === false));
+
+ // Extract "plain" output character set name
+ // (without any transliteration argument)
+ $out_charset_plain = ($using_translit
+ ? substr($out_charset, 0, $translit_search)
+ : $out_charset);
+
+ // Transform name of input character set (if found)
+ $in_charset_exisits = array_key_exists(
+ strtolower($in_charset),
+ $gnu_iconv_to_aix_iconv_codepage_map
+ );
+ if ($in_charset_exisits) {
+ $in_charset = $gnu_iconv_to_aix_iconv_codepage_map[strtolower($in_charset)];
+ }
+
+ // Transform name of "plain" output character set (if found)
+ $out_charset_plain_exists = array_key_exists(
+ strtolower($out_charset_plain),
+ $gnu_iconv_to_aix_iconv_codepage_map
+ );
+ if ($out_charset_plain_exists) {
+ $out_charset_plain = $gnu_iconv_to_aix_iconv_codepage_map[
+ strtolower($out_charset_plain)];
+ }
+
+ // Add transliteration argument again (exactly as specified by user) if used
+ // Build the output character set name that we will use
+ $out_charset = ($using_translit
+ ? $out_charset_plain . substr($out_charset, $translit_search)
+ : $out_charset_plain);
+
+ // NOTE: Transliteration not supported; we will use the "plain"
+ // output character set name
+ $out_charset = $out_charset_plain;
+
+ return array($in_charset, $out_charset);
+}
+
+?>
diff --git a/libraries/import.lib.php b/libraries/import.lib.php
new file mode 100644
index 0000000000..adf2233317
--- /dev/null
+++ b/libraries/import.lib.php
@@ -0,0 +1,1282 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Library that provides common import functions that are used by import plugins
+ *
+ * @package PhpMyAdmin-Import
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ * We need to know something about user
+ */
+require_once './libraries/check_user_privileges.lib.php';
+
+/**
+ * We do this check, DROP DATABASE does not need to be confirmed elsewhere
+ */
+define('PMA_CHK_DROP', 1);
+
+/**
+ * Checks whether timeout is getting close
+ *
+ * @return boolean true if timeout is close
+ * @access public
+ */
+function PMA_checkTimeout()
+{
+ global $timestamp, $maximum_time, $timeout_passed;
+ if ($maximum_time == 0) {
+ return false;
+ } elseif ($timeout_passed) {
+ return true;
+ /* 5 in next row might be too much */
+ } elseif ((time() - $timestamp) > ($maximum_time - 5)) {
+ $timeout_passed = true;
+ return true;
+ } else {
+ return false;
+ }
+}
+
+/**
+ * Detects what compression filse uses
+ *
+ * @param string $filepath filename to check
+ *
+ * @return string MIME type of compression, none for none
+ * @access public
+ */
+function PMA_detectCompression($filepath)
+{
+ $file = @fopen($filepath, 'rb');
+ if (! $file) {
+ return false;
+ }
+ $test = fread($file, 4);
+ $len = strlen($test);
+ fclose($file);
+ if ($len >= 2 && $test[0] == chr(31) && $test[1] == chr(139)) {
+ return 'application/gzip';
+ }
+ if ($len >= 3 && substr($test, 0, 3) == 'BZh') {
+ return 'application/bzip2';
+ }
+ if ($len >= 4 && $test == "PK\003\004") {
+ return 'application/zip';
+ }
+ return 'none';
+}
+
+/**
+ * Runs query inside import buffer. This is needed to allow displaying
+ * of last SELECT, SHOW or HANDLER results and similar nice stuff.
+ *
+ * @param string $sql query to run
+ * @param string $full query to display, this might be commented
+ * @param bool $controluser whether to use control user for queries
+ * @param array &$sql_data SQL parse data storage
+ *
+ * @return void
+ * @access public
+ */
+function PMA_importRunQuery($sql = '', $full = '', $controluser = false,
+ &$sql_data = array()
+) {
+ global $import_run_buffer, $go_sql, $complete_query, $display_query,
+ $sql_query, $my_die, $error, $reload,
+ $last_query_with_results, $result, $msg,
+ $skip_queries, $executed_queries, $max_sql_len, $read_multiply,
+ $cfg, $sql_query_disabled, $db, $run_query, $is_superuser;
+ $read_multiply = 1;
+ if (isset($import_run_buffer)) {
+ // Should we skip something?
+ if ($skip_queries > 0) {
+ $skip_queries--;
+ } else {
+ if (! empty($import_run_buffer['sql'])
+ && trim($import_run_buffer['sql']) != ''
+ ) {
+
+ // USE query changes the database, son need to track
+ // while running multiple queries
+ $is_use_query
+ = (stripos($import_run_buffer['sql'], "use ") !== false)
+ ? true
+ : false;
+
+ $max_sql_len = max($max_sql_len, strlen($import_run_buffer['sql']));
+ if (! $sql_query_disabled) {
+ $sql_query .= $import_run_buffer['full'];
+ }
+ if (! $cfg['AllowUserDropDatabase']
+ && ! $is_superuser
+ && preg_match('@^[[:space:]]*DROP[[:space:]]+(IF EXISTS[[:space:]]+)?DATABASE @i', $import_run_buffer['sql'])
+ ) {
+ $GLOBALS['message'] = PMA_Message::error(__('"DROP DATABASE" statements are disabled.'));
+ $error = true;
+ } else {
+
+ $executed_queries++;
+
+ if ($run_query
+ && $GLOBALS['finished']
+ && empty($sql)
+ && ! $error
+ && ((! empty($import_run_buffer['sql'])
+ && preg_match('/^[\s]*(SELECT|SHOW|HANDLER)/i', $import_run_buffer['sql']))
+ || ($executed_queries == 1))
+ ) {
+ $go_sql = true;
+ if (! $sql_query_disabled) {
+ $complete_query = $sql_query;
+ $display_query = $sql_query;
+ } else {
+ $complete_query = '';
+ $display_query = '';
+ }
+ $sql_query = $import_run_buffer['sql'];
+ $sql_data['valid_sql'][] = $import_run_buffer['sql'];
+ if (! isset($sql_data['valid_queries'])) {
+ $sql_data['valid_queries'] = 0;
+ }
+ $sql_data['valid_queries']++;
+
+ // If a 'USE <db>' SQL-clause was found,
+ // set our current $db to the new one
+ list($db, $reload) = PMA_lookForUse(
+ $import_run_buffer['sql'],
+ $db,
+ $reload
+ );
+ } elseif ($run_query) {
+
+ if ($controluser) {
+ $result = PMA_queryAsControlUser(
+ $import_run_buffer['sql']
+ );
+ } else {
+ $result = $GLOBALS['dbi']->tryQuery($import_run_buffer['sql']);
+ }
+
+ $msg = '# ';
+ if ($result === false) { // execution failed
+ if (! isset($my_die)) {
+ $my_die = array();
+ }
+ $my_die[] = array(
+ 'sql' => $import_run_buffer['full'],
+ 'error' => $GLOBALS['dbi']->getError()
+ );
+
+ $msg .= __('Error');
+
+ if (! $cfg['IgnoreMultiSubmitErrors']) {
+ $error = true;
+ return;
+ }
+ } else {
+ $a_num_rows = (int)@$GLOBALS['dbi']->numRows($result);
+ $a_aff_rows = (int)@$GLOBALS['dbi']->affectedRows();
+ if ($a_num_rows > 0) {
+ $msg .= __('Rows'). ': ' . $a_num_rows;
+ $last_query_with_results = $import_run_buffer['sql'];
+ } elseif ($a_aff_rows > 0) {
+ $message = PMA_Message::getMessageForAffectedRows($a_aff_rows);
+ $msg .= $message->getMessage();
+ } else {
+ $msg .= __('MySQL returned an empty result set (i.e. zero rows).');
+ }
+
+ if (($a_num_rows > 0) || $is_use_query) {
+ $sql_data['valid_sql'][] = $import_run_buffer['sql'];
+ if (! isset($sql_data['valid_queries'])) {
+ $sql_data['valid_queries'] = 0;
+ }
+ $sql_data['valid_queries']++;
+ }
+
+ }
+ if (! $sql_query_disabled) {
+ $sql_query .= $msg . "\n";
+ }
+
+ // If a 'USE <db>' SQL-clause was found and the query
+ // succeeded, set our current $db to the new one
+ if ($result != false) {
+ list($db, $reload) = PMA_lookForUse(
+ $import_run_buffer['sql'],
+ $db,
+ $reload
+ );
+ }
+
+ if ($result != false
+ && preg_match('@^[\s]*(DROP|CREATE)[\s]+(IF EXISTS[[:space:]]+)?(TABLE|DATABASE)[[:space:]]+(.+)@im', $import_run_buffer['sql'])
+ ) {
+ $reload = true;
+ }
+ } // end run query
+ } // end if not DROP DATABASE
+ // end non empty query
+ } elseif (! empty($import_run_buffer['full'])) {
+ if ($go_sql) {
+ $complete_query .= $import_run_buffer['full'];
+ $display_query .= $import_run_buffer['full'];
+ } else {
+ if (! $sql_query_disabled) {
+ $sql_query .= $import_run_buffer['full'];
+ }
+ }
+ }
+ // check length of query unless we decided to pass it to sql.php
+ // (if $run_query is false, we are just displaying so show
+ // the complete query in the textarea)
+ if (! $go_sql && $run_query) {
+ if (! empty($sql_query)) {
+ if (strlen($sql_query) > 50000
+ || $executed_queries > 50
+ || $max_sql_len > 1000
+ ) {
+ $sql_query = '';
+ $sql_query_disabled = true;
+ }
+ }
+ }
+ } // end do query (no skip)
+ } // end buffer exists
+
+ // Do we have something to push into buffer?
+ if (! empty($sql) || ! empty($full)) {
+ $import_run_buffer = array('sql' => $sql, 'full' => $full);
+ } else {
+ unset($GLOBALS['import_run_buffer']);
+ }
+}
+
+/**
+ * Looks for the presence of USE to possibly change current db
+ *
+ * @param string $buffer buffer to examine
+ * @param string $db current db
+ * @param bool $reload reload
+ *
+ * @return array (current or new db, whether to reload)
+ * @access public
+ */
+function PMA_lookForUse($buffer, $db, $reload)
+{
+ if (preg_match('@^[\s]*USE[[:space:]]+([\S]+)@i', $buffer, $match)) {
+ $db = trim($match[1]);
+ $db = trim($db, ';'); // for example, USE abc;
+
+ // $db must not contain the escape characters generated by backquote()
+ // ( used in PMA_buildSQL() as: backquote($db_name), and then called
+ // in PMA_importRunQuery() which in turn calls PMA_lookForUse() )
+ $db = PMA_Util::unQuote($db);
+
+ $reload = true;
+ }
+ return(array($db, $reload));
+}
+
+
+/**
+ * Returns next part of imported file/buffer
+ *
+ * @param int $size size of buffer to read
+ * (this is maximal size function will return)
+ *
+ * @return string part of file/buffer
+ * @access public
+ */
+function PMA_importGetNextChunk($size = 32768)
+{
+ global $compression, $import_handle, $charset_conversion, $charset_of_file,
+ $read_multiply;
+
+ // Add some progression while reading large amount of data
+ if ($read_multiply <= 8) {
+ $size *= $read_multiply;
+ } else {
+ $size *= 8;
+ }
+ $read_multiply++;
+
+ // We can not read too much
+ if ($size > $GLOBALS['read_limit']) {
+ $size = $GLOBALS['read_limit'];
+ }
+
+ if (PMA_checkTimeout()) {
+ return false;
+ }
+ if ($GLOBALS['finished']) {
+ return true;
+ }
+
+ if ($GLOBALS['import_file'] == 'none') {
+ // Well this is not yet supported and tested,
+ // but should return content of textarea
+ if (strlen($GLOBALS['import_text']) < $size) {
+ $GLOBALS['finished'] = true;
+ return $GLOBALS['import_text'];
+ } else {
+ $r = substr($GLOBALS['import_text'], 0, $size);
+ $GLOBALS['offset'] += $size;
+ $GLOBALS['import_text'] = substr($GLOBALS['import_text'], $size);
+ return $r;
+ }
+ }
+
+ switch ($compression) {
+ case 'application/bzip2':
+ $result = bzread($import_handle, $size);
+ $GLOBALS['finished'] = feof($import_handle);
+ break;
+ case 'application/gzip':
+ $result = gzread($import_handle, $size);
+ $GLOBALS['finished'] = feof($import_handle);
+ break;
+ case 'application/zip':
+ $result = substr($GLOBALS['import_text'], 0, $size);
+ $GLOBALS['import_text'] = substr($GLOBALS['import_text'], $size);
+ $GLOBALS['finished'] = empty($GLOBALS['import_text']);
+ break;
+ case 'none':
+ $result = fread($import_handle, $size);
+ $GLOBALS['finished'] = feof($import_handle);
+ break;
+ }
+ $GLOBALS['offset'] += $size;
+
+ if ($charset_conversion) {
+ return PMA_convertString($charset_of_file, 'utf-8', $result);
+ } else {
+ /**
+ * Skip possible byte order marks (I do not think we need more
+ * charsets, but feel free to add more, you can use wikipedia for
+ * reference: <http://en.wikipedia.org/wiki/Byte_Order_Mark>)
+ *
+ * @todo BOM could be used for charset autodetection
+ */
+ if ($GLOBALS['offset'] == $size) {
+ // UTF-8
+ if (strncmp($result, "\xEF\xBB\xBF", 3) == 0) {
+ $result = substr($result, 3);
+ // UTF-16 BE, LE
+ } elseif (strncmp($result, "\xFE\xFF", 2) == 0
+ || strncmp($result, "\xFF\xFE", 2) == 0
+ ) {
+ $result = substr($result, 2);
+ }
+ }
+ return $result;
+ }
+}
+
+/**
+ * Returns the "Excel" column name (i.e. 1 = "A", 26 = "Z", 27 = "AA", etc.)
+ *
+ * This functions uses recursion to build the Excel column name.
+ *
+ * The column number (1-26) is converted to the responding
+ * ASCII character (A-Z) and returned.
+ *
+ * If the column number is bigger than 26 (= num of letters in alfabet),
+ * an extra character needs to be added. To find this extra character,
+ * the number is divided by 26 and this value is passed to another instance
+ * of the same function (hence recursion). In that new instance the number is
+ * evaluated again, and if it is still bigger than 26, it is divided again
+ * and passed to another instance of the same function. This continues until
+ * the number is smaller than 26. Then the last called function returns
+ * the corresponding ASCII character to the function that called it.
+ * Each time a called function ends an extra character is added to the column name.
+ * When the first function is reached, the last character is addded and the complete
+ * column name is returned.
+ *
+ * @param int $num the column number
+ *
+ * @return string The column's "Excel" name
+ * @access public
+ */
+function PMA_getColumnAlphaName($num)
+{
+ $A = 65; // ASCII value for capital "A"
+ $col_name = "";
+
+ if ($num > 26) {
+ $div = (int)($num / 26);
+ $remain = (int)($num % 26);
+
+ // subtract 1 of divided value in case the modulus is 0,
+ // this is necessary because A-Z has no 'zero'
+ if ($remain == 0) {
+ $div--;
+ }
+
+ // recursive function call
+ $col_name = PMA_getColumnAlphaName($div);
+ // use modulus as new column number
+ $num = $remain;
+ }
+
+ if ($num == 0) {
+ // use 'Z' if column number is 0,
+ // this is necessary because A-Z has no 'zero'
+ $col_name .= chr(($A + 26) - 1);
+ } else {
+ // convert column number to ASCII character
+ $col_name .= chr(($A + $num) - 1);
+ }
+
+ return $col_name;
+}
+
+/**
+ * Returns the column number based on the Excel name.
+ * So "A" = 1, "Z" = 26, "AA" = 27, etc.
+ *
+ * Basicly this is a base26 (A-Z) to base10 (0-9) conversion.
+ * It iterates through all characters in the column name and
+ * calculates the corresponding value, based on character value
+ * (A = 1, ..., Z = 26) and position in the string.
+ *
+ * @param string $name column name(i.e. "A", or "BC", etc.)
+ *
+ * @return int The column number
+ * @access public
+ */
+function PMA_getColumnNumberFromName($name)
+{
+ if (! empty($name)) {
+ $name = strtoupper($name);
+ $num_chars = strlen($name);
+ $column_number = 0;
+ for ($i = 0; $i < $num_chars; ++$i) {
+ // read string from back to front
+ $char_pos = ($num_chars - 1) - $i;
+
+ // convert capital character to ASCII value
+ // and subtract 64 to get corresponding decimal value
+ // ASCII value of "A" is 65, "B" is 66, etc.
+ // Decimal equivalent of "A" is 1, "B" is 2, etc.
+ $number = (ord($name[$char_pos]) - 64);
+
+ // base26 to base10 conversion : multiply each number
+ // with corresponding value of the position, in this case
+ // $i=0 : 1; $i=1 : 26; $i=2 : 676; ...
+ $column_number += $number * PMA_Util::pow(26, $i);
+ }
+ return $column_number;
+ } else {
+ return 0;
+ }
+}
+
+/**
+ * Constants definitions
+ */
+
+/* MySQL type defs */
+define("NONE", 0);
+define("VARCHAR", 1);
+define("INT", 2);
+define("DECIMAL", 3);
+define("BIGINT", 4);
+define("GEOMETRY", 5);
+
+/* Decimal size defs */
+define("M", 0);
+define("D", 1);
+define("FULL", 2);
+
+/* Table array defs */
+define("TBL_NAME", 0);
+define("COL_NAMES", 1);
+define("ROWS", 2);
+
+/* Analysis array defs */
+define("TYPES", 0);
+define("SIZES", 1);
+define("FORMATTEDSQL", 2);
+
+/**
+ * Obtains the precision (total # of digits) from a size of type decimal
+ *
+ * @param string $last_cumulative_size
+ *
+ * @return int Precision of the given decimal size notation
+ * @access public
+ */
+function PMA_getM($last_cumulative_size)
+{
+ return (int)substr($last_cumulative_size, 0, strpos($last_cumulative_size, ","));
+}
+
+/**
+ * Obtains the scale (# of digits to the right of the decimal point)
+ * from a size of type decimal
+ *
+ * @param string $last_cumulative_size
+ *
+ * @return int Scale of the given decimal size notation
+ * @access public
+ */
+function PMA_getD($last_cumulative_size)
+{
+ return (int) substr(
+ $last_cumulative_size,
+ (strpos($last_cumulative_size, ",") + 1),
+ (strlen($last_cumulative_size) - strpos($last_cumulative_size, ","))
+ );
+}
+
+/**
+ * Obtains the decimal size of a given cell
+ *
+ * @param string $cell cell content
+ *
+ * @return array Contains the precision, scale, and full size
+ * representation of the given decimal cell
+ * @access public
+ */
+function PMA_getDecimalSize($cell)
+{
+ $curr_size = strlen((string)$cell);
+ $decPos = strpos($cell, ".");
+ $decPrecision = ($curr_size - 1) - $decPos;
+
+ $m = $curr_size - 1;
+ $d = $decPrecision;
+
+ return array($m, $d, ($m . "," . $d));
+}
+
+/**
+ * Obtains the size of the given cell
+ *
+ * @param string $last_cumulative_size Last cumulative column size
+ * @param int $last_cumulative_type Last cumulative column type
+ * (NONE or VARCHAR or DECIMAL or INT or BIGINT)
+ * @param int $curr_type Type of the current cell
+ * (NONE or VARCHAR or DECIMAL or INT or BIGINT)
+ * @param string $cell The current cell
+ *
+ * @return string Size of the given cell in the type-appropriate format
+ * @access public
+ *
+ * @todo Handle the error cases more elegantly
+ */
+function PMA_detectSize($last_cumulative_size, $last_cumulative_type,
+ $curr_type, $cell
+) {
+ $curr_size = strlen((string)$cell);
+
+ /**
+ * If the cell is NULL, don't treat it as a varchar
+ */
+ if (! strcmp('NULL', $cell)) {
+ return $last_cumulative_size;
+ } elseif ($curr_type == VARCHAR) {
+ /**
+ * What to do if the current cell is of type VARCHAR
+ */
+ /**
+ * The last cumulative type was VARCHAR
+ */
+ if ($last_cumulative_type == VARCHAR) {
+ if ($curr_size >= $last_cumulative_size) {
+ return $curr_size;
+ } else {
+ return $last_cumulative_size;
+ }
+ } elseif ($last_cumulative_type == DECIMAL) {
+ /**
+ * The last cumulative type was DECIMAL
+ */
+ $oldM = PMA_getM($last_cumulative_size);
+
+ if ($curr_size >= $oldM) {
+ return $curr_size;
+ } else {
+ return $oldM;
+ }
+ } elseif ($last_cumulative_type == BIGINT || $last_cumulative_type == INT) {
+ /**
+ * The last cumulative type was BIGINT or INT
+ */
+ if ($curr_size >= $last_cumulative_size) {
+ return $curr_size;
+ } else {
+ return $last_cumulative_size;
+ }
+ } elseif (! isset($last_cumulative_type) || $last_cumulative_type == NONE) {
+ /**
+ * This is the first row to be analyzed
+ */
+ return $curr_size;
+ } else {
+ /**
+ * An error has DEFINITELY occurred
+ */
+ /**
+ * TODO: Handle this MUCH more elegantly
+ */
+
+ return -1;
+ }
+ } elseif ($curr_type == DECIMAL) {
+ /**
+ * What to do if the current cell is of type DECIMAL
+ */
+ /**
+ * The last cumulative type was VARCHAR
+ */
+ if ($last_cumulative_type == VARCHAR) {
+ /* Convert $last_cumulative_size from varchar to decimal format */
+ $size = PMA_getDecimalSize($cell);
+
+ if ($size[M] >= $last_cumulative_size) {
+ return $size[M];
+ } else {
+ return $last_cumulative_size;
+ }
+ } elseif ($last_cumulative_type == DECIMAL) {
+ /**
+ * The last cumulative type was DECIMAL
+ */
+ $size = PMA_getDecimalSize($cell);
+
+ $oldM = PMA_getM($last_cumulative_size);
+ $oldD = PMA_getD($last_cumulative_size);
+
+ /* New val if M or D is greater than current largest */
+ if ($size[M] > $oldM || $size[D] > $oldD) {
+ /* Take the largest of both types */
+ return (string) ((($size[M] > $oldM) ? $size[M] : $oldM)
+ . "," . (($size[D] > $oldD) ? $size[D] : $oldD));
+ } else {
+ return $last_cumulative_size;
+ }
+ } elseif ($last_cumulative_type == BIGINT || $last_cumulative_type == INT) {
+ /**
+ * The last cumulative type was BIGINT or INT
+ */
+ /* Convert $last_cumulative_size from int to decimal format */
+ $size = PMA_getDecimalSize($cell);
+
+ if ($size[M] >= $last_cumulative_size) {
+ return $size[FULL];
+ } else {
+ return ($last_cumulative_size.",".$size[D]);
+ }
+ } elseif (! isset($last_cumulative_type) || $last_cumulative_type == NONE) {
+ /**
+ * This is the first row to be analyzed
+ */
+ /* First row of the column */
+ $size = PMA_getDecimalSize($cell);
+
+ return $size[FULL];
+ } else {
+ /**
+ * An error has DEFINITELY occurred
+ */
+ /**
+ * TODO: Handle this MUCH more elegantly
+ */
+
+ return -1;
+ }
+ } elseif ($curr_type == BIGINT || $curr_type == INT) {
+ /**
+ * What to do if the current cell is of type BIGINT or INT
+ */
+ /**
+ * The last cumulative type was VARCHAR
+ */
+ if ($last_cumulative_type == VARCHAR) {
+ if ($curr_size >= $last_cumulative_size) {
+ return $curr_size;
+ } else {
+ return $last_cumulative_size;
+ }
+ } elseif ($last_cumulative_type == DECIMAL) {
+ /**
+ * The last cumulative type was DECIMAL
+ */
+ $oldM = PMA_getM($last_cumulative_size);
+ $oldD = PMA_getD($last_cumulative_size);
+ $oldInt = $oldM - $oldD;
+ $newInt = strlen((string)$cell);
+
+ /* See which has the larger integer length */
+ if ($oldInt >= $newInt) {
+ /* Use old decimal size */
+ return $last_cumulative_size;
+ } else {
+ /* Use $newInt + $oldD as new M */
+ return (($newInt + $oldD) . "," . $oldD);
+ }
+ } elseif ($last_cumulative_type == BIGINT || $last_cumulative_type == INT) {
+ /**
+ * The last cumulative type was BIGINT or INT
+ */
+ if ($curr_size >= $last_cumulative_size) {
+ return $curr_size;
+ } else {
+ return $last_cumulative_size;
+ }
+ } elseif (! isset($last_cumulative_type) || $last_cumulative_type == NONE) {
+ /**
+ * This is the first row to be analyzed
+ */
+ return $curr_size;
+ } else {
+ /**
+ * An error has DEFINITELY occurred
+ */
+ /**
+ * TODO: Handle this MUCH more elegantly
+ */
+
+ return -1;
+ }
+ } else {
+ /**
+ * An error has DEFINITELY occurred
+ */
+ /**
+ * TODO: Handle this MUCH more elegantly
+ */
+
+ return -1;
+ }
+}
+
+/**
+ * Determines what MySQL type a cell is
+ *
+ * @param int $last_cumulative_type Last cumulative column type
+ * (VARCHAR or INT or BIGINT or DECIMAL or NONE)
+ * @param string $cell String representation of the cell for which
+ * a best-fit type is to be determined
+ *
+ * @return int The MySQL type representation
+ * (VARCHAR or INT or BIGINT or DECIMAL or NONE)
+ * @access public
+ */
+function PMA_detectType($last_cumulative_type, $cell)
+{
+ /**
+ * If numeric, determine if decimal, int or bigint
+ * Else, we call it varchar for simplicity
+ */
+
+ if (! strcmp('NULL', $cell)) {
+ if ($last_cumulative_type === null || $last_cumulative_type == NONE) {
+ return NONE;
+ } else {
+ return $last_cumulative_type;
+ }
+ } elseif (is_numeric($cell)) {
+ if ($cell == (string)(float)$cell
+ && strpos($cell, ".") !== false
+ && substr_count($cell, ".") == 1
+ ) {
+ return DECIMAL;
+ } else {
+ if (abs($cell) > 2147483647) {
+ return BIGINT;
+ } else {
+ return INT;
+ }
+ }
+ } else {
+ return VARCHAR;
+ }
+}
+
+/**
+ * Determines if the column types are int, decimal, or string
+ *
+ * @param array &$table array(string $table_name, array $col_names, array $rows)
+ *
+ * @return array array(array $types, array $sizes)
+ * @access public
+ *
+ * @link http://wiki.phpmyadmin.net/pma/Import
+ *
+ * @todo Handle the error case more elegantly
+ */
+function PMA_analyzeTable(&$table)
+{
+ /* Get number of rows in table */
+ $numRows = count($table[ROWS]);
+ /* Get number of columns */
+ $numCols = count($table[COL_NAMES]);
+ /* Current type for each column */
+ $types = array();
+ $sizes = array();
+
+ /* Initialize $sizes to all 0's */
+ for ($i = 0; $i < $numCols; ++$i) {
+ $sizes[$i] = 0;
+ }
+
+ /* Initialize $types to NONE */
+ for ($i = 0; $i < $numCols; ++$i) {
+ $types[$i] = NONE;
+ }
+
+ /* Temp vars */
+ $curr_type = NONE;
+
+ /* If the passed array is not of the correct form, do not process it */
+ if (is_array($table)
+ && ! is_array($table[TBL_NAME])
+ && is_array($table[COL_NAMES])
+ && is_array($table[ROWS])
+ ) {
+ /* Analyze each column */
+ for ($i = 0; $i < $numCols; ++$i) {
+ /* Analyze the column in each row */
+ for ($j = 0; $j < $numRows; ++$j) {
+ /* Determine type of the current cell */
+ $curr_type = PMA_detectType($types[$i], $table[ROWS][$j][$i]);
+ /* Determine size of the current cell */
+ $sizes[$i] = PMA_detectSize(
+ $sizes[$i],
+ $types[$i],
+ $curr_type,
+ $table[ROWS][$j][$i]
+ );
+
+ /**
+ * If a type for this column has already been declared,
+ * only alter it if it was a number and a varchar was found
+ */
+ if ($curr_type != NONE) {
+ if ($curr_type == VARCHAR) {
+ $types[$i] = VARCHAR;
+ } else if ($curr_type == DECIMAL) {
+ if ($types[$i] != VARCHAR) {
+ $types[$i] = DECIMAL;
+ }
+ } else if ($curr_type == BIGINT) {
+ if ($types[$i] != VARCHAR && $types[$i] != DECIMAL) {
+ $types[$i] = BIGINT;
+ }
+ } else if ($curr_type == INT) {
+ if ($types[$i] != VARCHAR
+ && $types[$i] != DECIMAL
+ && $types[$i] != BIGINT
+ ) {
+ $types[$i] = INT;
+ }
+ }
+ }
+ }
+ }
+
+ /* Check to ensure that all types are valid */
+ $len = count($types);
+ for ($n = 0; $n < $len; ++$n) {
+ if (! strcmp(NONE, $types[$n])) {
+ $types[$n] = VARCHAR;
+ $sizes[$n] = '10';
+ }
+ }
+
+ return array($types, $sizes);
+ } else {
+ /**
+ * TODO: Handle this better
+ */
+
+ return false;
+ }
+}
+
+/* Needed to quell the beast that is PMA_Message */
+$import_notice = null;
+
+/**
+ * Builds and executes SQL statements to create the database and tables
+ * as necessary, as well as insert all the data.
+ *
+ * @param string $db_name Name of the database
+ * @param array &$tables Array of tables for the specified database
+ * @param array &$analyses Analyses of the tables
+ * @param array &$additional_sql Additional SQL statements to be executed
+ * @param array $options Associative array of options
+ *
+ * @return void
+ * @access public
+ *
+ * @link http://wiki.phpmyadmin.net/pma/Import
+ */
+function PMA_buildSQL($db_name, &$tables, &$analyses = null,
+ &$additional_sql = null, $options = null
+) {
+ /* Take care of the options */
+ if (isset($options['db_collation'])&& ! is_null($options['db_collation'])) {
+ $collation = $options['db_collation'];
+ } else {
+ $collation = "utf8_general_ci";
+ }
+
+ if (isset($options['db_charset']) && ! is_null($options['db_charset'])) {
+ $charset = $options['db_charset'];
+ } else {
+ $charset = "utf8";
+ }
+
+ if (isset($options['create_db'])) {
+ $create_db = $options['create_db'];
+ } else {
+ $create_db = true;
+ }
+
+ /* Create SQL code to handle the database */
+ $sql = array();
+
+ if ($create_db) {
+ if (PMA_DRIZZLE) {
+ $sql[] = "CREATE DATABASE IF NOT EXISTS " . PMA_Util::backquote($db_name)
+ . " COLLATE " . $collation;
+ } else {
+ $sql[] = "CREATE DATABASE IF NOT EXISTS " . PMA_Util::backquote($db_name)
+ . " DEFAULT CHARACTER SET " . $charset . " COLLATE " . $collation;
+ }
+ }
+
+ /**
+ * The calling plug-in should include this statement,
+ * if necessary, in the $additional_sql parameter
+ *
+ * $sql[] = "USE " . backquote($db_name);
+ */
+
+ /* Execute the SQL statements create above */
+ $sql_len = count($sql);
+ for ($i = 0; $i < $sql_len; ++$i) {
+ PMA_importRunQuery($sql[$i], $sql[$i]);
+ }
+
+ /* No longer needed */
+ unset($sql);
+
+ /* Run the $additional_sql statements supplied by the caller plug-in */
+ if ($additional_sql != null) {
+ /* Clean the SQL first */
+ $additional_sql_len = count($additional_sql);
+
+ /**
+ * Only match tables for now, because CREATE IF NOT EXISTS
+ * syntax is lacking or nonexisting for views, triggers,
+ * functions, and procedures.
+ *
+ * See: http://bugs.mysql.com/bug.php?id=15287
+ *
+ * To the best of my knowledge this is still an issue.
+ *
+ * $pattern = 'CREATE (TABLE|VIEW|TRIGGER|FUNCTION|PROCEDURE)';
+ */
+ $pattern = '/CREATE [^`]*(TABLE)/';
+ $replacement = 'CREATE \\1 IF NOT EXISTS';
+
+ /* Change CREATE statements to CREATE IF NOT EXISTS to support
+ * inserting into existing structures
+ */
+ for ($i = 0; $i < $additional_sql_len; ++$i) {
+ $additional_sql[$i] = preg_replace(
+ $pattern,
+ $replacement,
+ $additional_sql[$i]
+ );
+ /* Execute the resulting statements */
+ PMA_importRunQuery($additional_sql[$i], $additional_sql[$i]);
+ }
+ }
+
+ if ($analyses != null) {
+ $type_array = array(
+ NONE => "NULL",
+ VARCHAR => "varchar",
+ INT => "int",
+ DECIMAL => "decimal",
+ BIGINT => "bigint",
+ GEOMETRY => 'geometry'
+ );
+
+ /* TODO: Do more checking here to make sure they really are matched */
+ if (count($tables) != count($analyses)) {
+ exit();
+ }
+
+ /* Create SQL code to create the tables */
+ $tempSQLStr = "";
+ $num_tables = count($tables);
+ for ($i = 0; $i < $num_tables; ++$i) {
+ $num_cols = count($tables[$i][COL_NAMES]);
+ $tempSQLStr = "CREATE TABLE IF NOT EXISTS "
+ . PMA_Util::backquote($db_name)
+ . '.' . PMA_Util::backquote($tables[$i][TBL_NAME]) . " (";
+ for ($j = 0; $j < $num_cols; ++$j) {
+ $size = $analyses[$i][SIZES][$j];
+ if ((int)$size == 0) {
+ $size = 10;
+ }
+
+ $tempSQLStr .= PMA_Util::backquote($tables[$i][COL_NAMES][$j]) . " "
+ . $type_array[$analyses[$i][TYPES][$j]];
+ if ($analyses[$i][TYPES][$j] != GEOMETRY) {
+ $tempSQLStr .= "(" . $size . ")";
+ }
+
+ if ($j != (count($tables[$i][COL_NAMES]) - 1)) {
+ $tempSQLStr .= ", ";
+ }
+ }
+ $tempSQLStr .= ")"
+ . (PMA_DRIZZLE ? "" : " DEFAULT CHARACTER SET " . $charset)
+ . " COLLATE " . $collation . ";";
+
+ /**
+ * Each SQL statement is executed immediately
+ * after it is formed so that we don't have
+ * to store them in a (possibly large) buffer
+ */
+ PMA_importRunQuery($tempSQLStr, $tempSQLStr);
+ }
+ }
+
+ /**
+ * Create the SQL statements to insert all the data
+ *
+ * Only one insert query is formed for each table
+ */
+ $tempSQLStr = "";
+ $col_count = 0;
+ $num_tables = count($tables);
+ for ($i = 0; $i < $num_tables; ++$i) {
+ $num_cols = count($tables[$i][COL_NAMES]);
+ $num_rows = count($tables[$i][ROWS]);
+
+ $tempSQLStr = "INSERT INTO " . PMA_Util::backquote($db_name) . '.'
+ . PMA_Util::backquote($tables[$i][TBL_NAME]) . " (";
+
+ for ($m = 0; $m < $num_cols; ++$m) {
+ $tempSQLStr .= PMA_Util::backquote($tables[$i][COL_NAMES][$m]);
+
+ if ($m != ($num_cols - 1)) {
+ $tempSQLStr .= ", ";
+ }
+ }
+
+ $tempSQLStr .= ") VALUES ";
+
+ for ($j = 0; $j < $num_rows; ++$j) {
+ $tempSQLStr .= "(";
+
+ for ($k = 0; $k < $num_cols; ++$k) {
+ // If fully formatted SQL, no need to enclose
+ // with aphostrophes, add shalshes etc.
+ if ($analyses != null
+ && isset($analyses[$i][FORMATTEDSQL][$col_count])
+ && $analyses[$i][FORMATTEDSQL][$col_count] == true
+ ) {
+ $tempSQLStr .= (string) $tables[$i][ROWS][$j][$k];
+ } else {
+ if ($analyses != null) {
+ $is_varchar = ($analyses[$i][TYPES][$col_count] === VARCHAR);
+ } else {
+ $is_varchar = ! is_numeric($tables[$i][ROWS][$j][$k]);
+ }
+
+ /* Don't put quotes around NULL fields */
+ if (! strcmp($tables[$i][ROWS][$j][$k], 'NULL')) {
+ $is_varchar = false;
+ }
+
+ $tempSQLStr .= (($is_varchar) ? "'" : "");
+ $tempSQLStr .= PMA_Util::sqlAddSlashes(
+ (string) $tables[$i][ROWS][$j][$k]
+ );
+ $tempSQLStr .= (($is_varchar) ? "'" : "");
+ }
+
+ if ($k != ($num_cols - 1)) {
+ $tempSQLStr .= ", ";
+ }
+
+ if ($col_count == ($num_cols - 1)) {
+ $col_count = 0;
+ } else {
+ $col_count++;
+ }
+
+ /* Delete the cell after we are done with it */
+ unset($tables[$i][ROWS][$j][$k]);
+ }
+
+ $tempSQLStr .= ")";
+
+ if ($j != ($num_rows - 1)) {
+ $tempSQLStr .= ",\n ";
+ }
+
+ $col_count = 0;
+ /* Delete the row after we are done with it */
+ unset($tables[$i][ROWS][$j]);
+ }
+
+ $tempSQLStr .= ";";
+
+ /**
+ * Each SQL statement is executed immediately
+ * after it is formed so that we don't have
+ * to store them in a (possibly large) buffer
+ */
+ PMA_importRunQuery($tempSQLStr, $tempSQLStr);
+ }
+
+ /* No longer needed */
+ unset($tempSQLStr);
+
+ /**
+ * A work in progress
+ */
+
+ /* Add the viewable structures from $additional_sql
+ * to $tables so they are also displayed
+ */
+ $view_pattern = '@VIEW `[^`]+`\.`([^`]+)@';
+ $table_pattern = '@CREATE TABLE IF NOT EXISTS `([^`]+)`@';
+ /* Check a third pattern to make sure its not a "USE `db_name`;" statement */
+
+ $regs = array();
+
+ $inTables = false;
+
+ $additional_sql_len = count($additional_sql);
+ for ($i = 0; $i < $additional_sql_len; ++$i) {
+ preg_match($view_pattern, $additional_sql[$i], $regs);
+
+ if (count($regs) == 0) {
+ preg_match($table_pattern, $additional_sql[$i], $regs);
+ }
+
+ if (count($regs)) {
+ for ($n = 0; $n < $num_tables; ++$n) {
+ if (! strcmp($regs[1], $tables[$n][TBL_NAME])) {
+ $inTables = true;
+ break;
+ }
+ }
+
+ if (! $inTables) {
+ $tables[] = array(TBL_NAME => $regs[1]);
+ }
+ }
+
+ /* Reset the array */
+ $regs = array();
+ $inTables = false;
+ }
+
+ $params = array('db' => (string)$db_name);
+ $db_url = 'db_structure.php' . PMA_URL_getCommon($params);
+ $db_ops_url = 'db_operations.php' . PMA_URL_getCommon($params);
+
+ $message = '<br /><br />';
+ $message .= '<strong>' . __('The following structures have either been created or altered. Here you can:') . '</strong><br />';
+ $message .= '<ul><li>' . __("View a structure's contents by clicking on its name") . '</li>';
+ $message .= '<li>' . __('Change any of its settings by clicking the corresponding "Options" link') . '</li>';
+ $message .= '<li>' . __('Edit structure by following the "Structure" link') . '</li>';
+ $message .= sprintf(
+ '<br /><li><a href="%s" title="%s">%s</a> (<a href="%s" title="%s">'
+ . __('Options') . '</a>)</li>',
+ $db_url,
+ sprintf(
+ __('Go to database: %s'),
+ htmlspecialchars(PMA_Util::backquote($db_name))
+ ),
+ htmlspecialchars($db_name),
+ $db_ops_url,
+ sprintf(
+ __('Edit settings for %s'),
+ htmlspecialchars(PMA_Util::backquote($db_name))
+ )
+ );
+
+ $message .= '<ul>';
+
+ unset($params);
+
+ $num_tables = count($tables);
+ for ($i = 0; $i < $num_tables; ++$i) {
+ $params = array(
+ 'db' => (string) $db_name,
+ 'table' => (string) $tables[$i][TBL_NAME]
+ );
+ $tbl_url = 'sql.php' . PMA_URL_getCommon($params);
+ $tbl_struct_url = 'tbl_structure.php' . PMA_URL_getCommon($params);
+ $tbl_ops_url = 'tbl_operations.php' . PMA_URL_getCommon($params);
+
+ unset($params);
+
+ if (! PMA_Table::isView($db_name, $tables[$i][TBL_NAME])) {
+ $message .= sprintf(
+ '<li><a href="%s" title="%s">%s</a> (<a href="%s" title="%s">' . __('Structure') . '</a>) (<a href="%s" title="%s">' . __('Options') . '</a>)</li>',
+ $tbl_url,
+ sprintf(
+ __('Go to table: %s'),
+ htmlspecialchars(
+ PMA_Util::backquote($tables[$i][TBL_NAME])
+ )
+ ),
+ htmlspecialchars($tables[$i][TBL_NAME]),
+ $tbl_struct_url,
+ sprintf(
+ __('Structure of %s'),
+ htmlspecialchars(
+ PMA_Util::backquote($tables[$i][TBL_NAME])
+ )
+ ),
+ $tbl_ops_url,
+ sprintf(
+ __('Edit settings for %s'),
+ htmlspecialchars(
+ PMA_Util::backquote($tables[$i][TBL_NAME])
+ )
+ )
+ );
+ } else {
+ $message .= sprintf(
+ '<li><a href="%s" title="%s">%s</a></li>',
+ $tbl_url,
+ sprintf(
+ __('Go to view: %s'),
+ htmlspecialchars(
+ PMA_Util::backquote($tables[$i][TBL_NAME])
+ )
+ ),
+ htmlspecialchars($tables[$i][TBL_NAME])
+ );
+ }
+ }
+
+ $message .= '</ul></ul>';
+
+ global $import_notice;
+ $import_notice = $message;
+
+ unset($tables);
+}
+
+?>
diff --git a/libraries/index.lib.php b/libraries/index.lib.php
new file mode 100644
index 0000000000..f5df69ebf4
--- /dev/null
+++ b/libraries/index.lib.php
@@ -0,0 +1,49 @@
+<?php
+
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * set of functions for structure section in pma
+ *
+ * @package PhpMyAdmin
+ */
+if (!defined('PHPMYADMIN')) {
+ exit;
+}
+
+require_once 'libraries/Index.class.php';
+
+/**
+ * Get HTML for display indexes
+ *
+ * @return string $html_output
+ */
+function PMA_getHtmlForDisplayIndexes()
+{
+ $html_output = '<div id="index_div" class="ajax" >';
+
+ $html_output .= PMA_Util::getDivForSliderEffect(
+ 'indexes', __('Indexes')
+ );
+ $html_output .= PMA_Index::getView($GLOBALS['table'], $GLOBALS['db']);
+ $html_output .= '<fieldset class="tblFooters" style="text-align: left;">'
+ . '<form action="tbl_indexes.php" method="post">';
+ $html_output .= PMA_URL_getHiddenInputs(
+ $GLOBALS['db'], $GLOBALS['table']
+ );
+ $html_output .= sprintf(
+ __('Create an index on &nbsp;%s&nbsp;columns'),
+ '<input type="number" size="2" name="added_fields" value="1" '
+ . 'min="1" required />'
+ );
+ $html_output .= '<input type="hidden" name="create_index" value="1" />'
+ . '<input class="add_index ajax"'
+ . ' type="submit" value="' . __('Go') . '" />';
+
+ $html_output .= '</form>'
+ . '</fieldset>'
+ . '</div>'
+ . '</div>';
+
+ return $html_output;
+}
+
diff --git a/libraries/information_schema_relations.lib.php b/libraries/information_schema_relations.lib.php
new file mode 100644
index 0000000000..d1532073ef
--- /dev/null
+++ b/libraries/information_schema_relations.lib.php
@@ -0,0 +1,332 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Internal relations for information schema.
+ *
+ * @package PhpMyAdmin
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ *
+ */
+$GLOBALS['information_schema_relations'] = array(
+ 'CHARACTER_SETS' => array(
+ 'DEFAULT_COLLATE_NAME' => array(
+ 'foreign_db' => 'information_schema',
+ 'foreign_table' => 'COLLATIONS',
+ 'foreign_field' => 'COLLATION_NAME'
+ ),
+ 'CHARACTER_SET_NAME' => array(
+ 'foreign_db' => 'information_schema',
+ 'foreign_table' => 'CHARACTER_SETS',
+ 'foreign_field' => 'CHARACTER_SET_NAME'
+ )
+ ),
+ 'COLLATIONS' => array(
+ 'CHARACTER_SET_NAME' => array(
+ 'foreign_db' => 'information_schema',
+ 'foreign_table' => 'CHARACTER_SETS',
+ 'foreign_field' => 'CHARACTER_SET_NAME'
+ )
+ ),
+ 'COLLATION_CHARACTER_SET_APPLICABILITY' => array(
+ 'CHARACTER_SET_NAME' => array(
+ 'foreign_db' => 'information_schema',
+ 'foreign_table' => 'CHARACTER_SETS',
+ 'foreign_field' => 'CHARACTER_SET_NAME'
+ ),
+ 'COLLATION_NAME' => array(
+ 'foreign_db' => 'information_schema',
+ 'foreign_table' => 'COLLATIONS',
+ 'foreign_field' => 'COLLATION_NAME'
+ )
+ ),
+ 'COLUMNS' => array(
+ 'TABLE_SCHEMA' => array(
+ 'foreign_db' => 'information_schema',
+ 'foreign_table' => 'SCHEMATA',
+ 'foreign_field' => 'SCHEMA_NAME'
+ ),
+ 'CHARACTER_SET_NAME' => array(
+ 'foreign_db' => 'information_schema',
+ 'foreign_table' => 'CHARACTER_SETS',
+ 'foreign_field' => 'CHARACTER_SET_NAME'
+ ),
+ 'COLLATION_NAME' => array(
+ 'foreign_db' => 'information_schema',
+ 'foreign_table' => 'COLLATIONS',
+ 'foreign_field' => 'COLLATION_NAME'
+ )
+ ),
+ 'COLUMN_PRIVILEGES' => array(
+ 'TABLE_SCHEMA' => array(
+ 'foreign_db' => 'information_schema',
+ 'foreign_table' => 'SCHEMATA',
+ 'foreign_field' => 'SCHEMA_NAME'
+ )
+ ),
+ 'EVENTS' => array(
+ 'EVENT_SCHEMA' => array(
+ 'foreign_db' => 'information_schema',
+ 'foreign_table' => 'SCHEMATA',
+ 'foreign_field' => 'SCHEMA_NAME'
+ ),
+ 'CHARACTER_SET_CLIENT' => array(
+ 'foreign_db' => 'information_schema',
+ 'foreign_table' => 'CHARACTER_SETS',
+ 'foreign_field' => 'CHARACTER_SET_NAME'
+ ),
+ 'COLLATION_CONNECTION' => array(
+ 'foreign_db' => 'information_schema',
+ 'foreign_table' => 'COLLATIONS',
+ 'foreign_field' => 'COLLATION_NAME'
+ ),
+ 'DATABASE_COLLATION' => array(
+ 'foreign_db' => 'information_schema',
+ 'foreign_table' => 'COLLATIONS',
+ 'foreign_field' => 'COLLATION_NAME'
+ )
+ ),
+ 'FILES' => array(
+ 'TABLESPACE_NAME' => array(
+ 'foreign_db' => 'information_schema',
+ 'foreign_table' => 'TABLESPACES',
+ 'foreign_field' => 'TABLESPACE_NAME'
+ ),
+ 'TABLE_SCHEMA' => array(
+ 'foreign_db' => 'information_schema',
+ 'foreign_table' => 'SCHEMATA',
+ 'foreign_field' => 'SCHEMA_NAME'
+ ),
+ 'COLLATION_CONNECTION' => array(
+ 'foreign_db' => 'information_schema',
+ 'foreign_table' => 'COLLATIONS',
+ 'foreign_field' => 'COLLATION_NAME'
+ ),
+ 'ENGINE' => array(
+ 'foreign_db' => 'information_schema',
+ 'foreign_table' => 'ENGINES',
+ 'foreign_field' => 'ENGINE'
+ )
+ ),
+ 'KEY_COLUMN_USAGE' => array(
+ 'CONSTRAINT_SCHEMA' => array(
+ 'foreign_db' => 'information_schema',
+ 'foreign_table' => 'SCHEMATA',
+ 'foreign_field' => 'SCHEMA_NAME'
+ ),
+ 'TABLE_SCHEMA' => array(
+ 'foreign_db' => 'information_schema',
+ 'foreign_table' => 'SCHEMATA',
+ 'foreign_field' => 'SCHEMA_NAME'
+ ),
+ 'REFERENCED_TABLE_SCHEMA' => array(
+ 'foreign_db' => 'information_schema',
+ 'foreign_table' => 'SCHEMATA',
+ 'foreign_field' => 'SCHEMA_NAME'
+ )
+ ),
+ 'PARAMETERS' => array(
+ 'SPECIFIC_SCHEMA' => array(
+ 'foreign_db' => 'information_schema',
+ 'foreign_table' => 'SCHEMATA',
+ 'foreign_field' => 'SCHEMA_NAME'
+ ),
+ 'CHARACTER_SET_NAME' => array(
+ 'foreign_db' => 'information_schema',
+ 'foreign_table' => 'CHARACTER_SETS',
+ 'foreign_field' => 'CHARACTER_SET_NAME'
+ ),
+ 'COLLATION_NAME' => array(
+ 'foreign_db' => 'information_schema',
+ 'foreign_table' => 'COLLATIONS',
+ 'foreign_field' => 'COLLATION_NAME'
+ )
+ ),
+ 'PARTITIONS' => array(
+ 'TABLE_SCHEMA' => array(
+ 'foreign_db' => 'information_schema',
+ 'foreign_table' => 'SCHEMATA',
+ 'foreign_field' => 'SCHEMA_NAME'
+ ),
+ 'TABLESPACE_NAME' => array(
+ 'foreign_db' => 'information_schema',
+ 'foreign_table' => 'TABLESPACES',
+ 'foreign_field' => 'TABLESPACE_NAME'
+ )
+ ),
+ 'PROCESSLIST' => array(
+ 'DB' => array(
+ 'foreign_db' => 'information_schema',
+ 'foreign_table' => 'SCHEMATA',
+ 'foreign_field' => 'SCHEMA_NAME'
+ )
+ ),
+ 'REFERENTIAL_CONSTRAINTS' => array(
+ 'CONSTRAINT_SCHEMA' => array(
+ 'foreign_db' => 'information_schema',
+ 'foreign_table' => 'SCHEMATA',
+ 'foreign_field' => 'SCHEMA_NAME'
+ ),
+ 'UNIQUE_CONSTRAINT_SCHEMA' => array(
+ 'foreign_db' => 'information_schema',
+ 'foreign_table' => 'SCHEMATA',
+ 'foreign_field' => 'SCHEMA_NAME'
+ )
+ ),
+ 'ROUTINES' => array(
+ 'ROUTINE_SCHEMA' => array(
+ 'foreign_db' => 'information_schema',
+ 'foreign_table' => 'SCHEMATA',
+ 'foreign_field' => 'SCHEMA_NAME'
+ ),
+ 'CHARACTER_SET_NAME' => array(
+ 'foreign_db' => 'information_schema',
+ 'foreign_table' => 'CHARACTER_SETS',
+ 'foreign_field' => 'CHARACTER_SET_NAME'
+ ),
+ 'COLLATION_NAME' => array(
+ 'foreign_db' => 'information_schema',
+ 'foreign_table' => 'COLLATIONS',
+ 'foreign_field' => 'COLLATION_NAME'
+ ),
+ 'CHARACTER_SET_CLIENT' => array(
+ 'foreign_db' => 'information_schema',
+ 'foreign_table' => 'CHARACTER_SETS',
+ 'foreign_field' => 'CHARACTER_SET_NAME'
+ ),
+ 'COLLATION_CONNECTION' => array(
+ 'foreign_db' => 'information_schema',
+ 'foreign_table' => 'COLLATIONS',
+ 'foreign_field' => 'COLLATION_NAME'
+ ),
+ 'DATABASE_COLLATION' => array(
+ 'foreign_db' => 'information_schema',
+ 'foreign_table' => 'COLLATIONS',
+ 'foreign_field' => 'COLLATION_NAME'
+ )
+ ),
+ 'SCHEMATA' => array(
+ 'DEFAULT_CHARACTER_SET_NAME' => array(
+ 'foreign_db' => 'information_schema',
+ 'foreign_table' => 'CHARACTER_SETS',
+ 'foreign_field' => 'CHARACTER_SET_NAME'
+ ),
+ 'DEFAULT_COLLATION_NAME' => array(
+ 'foreign_db' => 'information_schema',
+ 'foreign_table' => 'COLLATIONS',
+ 'foreign_field' => 'COLLATION_NAME'
+ )
+ ),
+ 'SCHEMA_PRIVILEGES' => array(
+ 'TABLE_SCHEMA' => array(
+ 'foreign_db' => 'information_schema',
+ 'foreign_table' => 'SCHEMATA',
+ 'foreign_field' => 'SCHEMA_NAME'
+ )
+ ),
+ 'STATISTICS' => array(
+ 'TABLE_SCHEMA' => array(
+ 'foreign_db' => 'information_schema',
+ 'foreign_table' => 'SCHEMATA',
+ 'foreign_field' => 'SCHEMA_NAME'
+ ),
+ 'INDEX_SCHEMA' => array(
+ 'foreign_db' => 'information_schema',
+ 'foreign_table' => 'SCHEMATA',
+ 'foreign_field' => 'SCHEMA_NAME'
+ )
+ ),
+ 'TABLES' => array(
+ 'TABLE_SCHEMA' => array(
+ 'foreign_db' => 'information_schema',
+ 'foreign_table' => 'SCHEMATA',
+ 'foreign_field' => 'SCHEMA_NAME'
+ ),
+ 'TABLE_COLLATION' => array(
+ 'foreign_db' => 'information_schema',
+ 'foreign_table' => 'COLLATIONS',
+ 'foreign_field' => 'COLLATION_NAME'
+ ),
+ 'ENGINE' => array(
+ 'foreign_db' => 'information_schema',
+ 'foreign_table' => 'ENGINES',
+ 'foreign_field' => 'ENGINE'
+ ),
+ ),
+ 'TABLESAPCES' => array(
+ 'ENGINE' => array(
+ 'foreign_db' => 'information_schema',
+ 'foreign_table' => 'ENGINES',
+ 'foreign_field' => 'ENGINE'
+ )
+ ),
+ 'TABLE_CONSTRAINTS' => array(
+ 'CONSTRAINT_SCHEMA' => array(
+ 'foreign_db' => 'information_schema',
+ 'foreign_table' => 'SCHEMATA',
+ 'foreign_field' => 'SCHEMA_NAME'
+ ),
+ 'TABLE_SCHEMA' => array(
+ 'foreign_db' => 'information_schema',
+ 'foreign_table' => 'SCHEMATA',
+ 'foreign_field' => 'SCHEMA_NAME'
+ )
+ ),
+ 'TABLE_PRIVILEGES' => array(
+ 'TABLE_SCHEMA' => array(
+ 'foreign_db' => 'information_schema',
+ 'foreign_table' => 'SCHEMATA',
+ 'foreign_field' => 'SCHEMA_NAME'
+ )
+ ),
+ 'TRIGGERS' => array(
+ 'TRIGGER_SCHEMA' => array(
+ 'foreign_db' => 'information_schema',
+ 'foreign_table' => 'SCHEMATA',
+ 'foreign_field' => 'SCHEMA_NAME'
+ ),
+ 'EVENT_OBJECT_SCHEMA' => array(
+ 'foreign_db' => 'information_schema',
+ 'foreign_table' => 'SCHEMATA',
+ 'foreign_field' => 'SCHEMA_NAME'
+ ),
+ 'CHARACTER_SET_CLIENT' => array(
+ 'foreign_db' => 'information_schema',
+ 'foreign_table' => 'CHARACTER_SETS',
+ 'foreign_field' => 'CHARACTER_SET_NAME'
+ ),
+ 'COLLATION_CONNECTION' => array(
+ 'foreign_db' => 'information_schema',
+ 'foreign_table' => 'COLLATIONS',
+ 'foreign_field' => 'COLLATION_NAME'
+ ),
+ 'DATABASE_COLLATION' => array(
+ 'foreign_db' => 'information_schema',
+ 'foreign_table' => 'COLLATIONS',
+ 'foreign_field' => 'COLLATION_NAME'
+ )
+ ),
+ 'VIEWS' => array(
+ 'TABLE_SCHEMA' => array(
+ 'foreign_db' => 'information_schema',
+ 'foreign_table' => 'SCHEMATA',
+ 'foreign_field' => 'SCHEMA_NAME'
+ ),
+ 'CHARACTER_SET_CLIENT' => array(
+ 'foreign_db' => 'information_schema',
+ 'foreign_table' => 'CHARACTER_SETS',
+ 'foreign_field' => 'CHARACTER_SET_NAME'
+ ),
+ 'COLLATION_CONNECTION' => array(
+ 'foreign_db' => 'information_schema',
+ 'foreign_table' => 'COLLATIONS',
+ 'foreign_field' => 'COLLATION_NAME'
+ )
+ )
+);
+
+?>
diff --git a/libraries/insert_edit.lib.php b/libraries/insert_edit.lib.php
new file mode 100644
index 0000000000..54bcdfd72e
--- /dev/null
+++ b/libraries/insert_edit.lib.php
@@ -0,0 +1,2920 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * set of functions with the insert/edit features in pma
+ *
+ * @package PhpMyAdmin
+ */
+
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ * Retrieve form parameters for insert/edit form
+ *
+ * @param string $db name of the database
+ * @param string $table name of the table
+ * @param array $where_clauses where clauses
+ * @param array $where_clause_array array of where clauses
+ * @param string $err_url error url
+ *
+ * @return array $_form_params array of insert/edit form parameters
+ */
+function PMA_getFormParametersForInsertForm($db, $table, $where_clauses,
+ $where_clause_array, $err_url
+) {
+ $_form_params = array(
+ 'db' => $db,
+ 'table' => $table,
+ 'goto' => $GLOBALS['goto'],
+ 'err_url' => $err_url,
+ 'sql_query' => $_REQUEST['sql_query'],
+ );
+ if (isset($where_clauses)) {
+ foreach ($where_clause_array as $key_id => $where_clause) {
+ $_form_params['where_clause[' . $key_id . ']'] = trim($where_clause);
+ }
+ }
+ if (isset($_REQUEST['clause_is_unique'])) {
+ $_form_params['clause_is_unique'] = $_REQUEST['clause_is_unique'];
+ }
+ return $_form_params;
+}
+
+/**
+ * Creates array of where clauses
+ *
+ * @param array $where_clause where clause
+ *
+ * @return array|void whereClauseArray array of where clauses
+ */
+function PMA_getWhereClauseArray($where_clause)
+{
+ if (isset ($where_clause)) {
+ if (is_array($where_clause)) {
+ return $where_clause;
+ } else {
+ return array(0 => $where_clause);
+ }
+ }
+}
+
+/**
+ * Analysing where clauses array
+ *
+ * @param array $where_clause_array array of where clauses
+ * @param string $table name of the table
+ * @param string $db name of the database
+ *
+ * @return array $where_clauses, $result, $rows
+ */
+function PMA_analyzeWhereClauses(
+ $where_clause_array, $table, $db
+) {
+ $rows = array();
+ $result = array();
+ $where_clauses = array();
+ $found_unique_key = false;
+ foreach ($where_clause_array as $key_id => $where_clause) {
+
+ $local_query = 'SELECT * FROM '
+ . PMA_Util::backquote($db) . '.'
+ . PMA_Util::backquote($table)
+ . ' WHERE ' . $where_clause . ';';
+ $result[$key_id] = $GLOBALS['dbi']->query(
+ $local_query, null, PMA_DatabaseInterface::QUERY_STORE
+ );
+ $rows[$key_id] = $GLOBALS['dbi']->fetchAssoc($result[$key_id]);
+
+ $where_clauses[$key_id] = str_replace('\\', '\\\\', $where_clause);
+ $has_unique_condition = PMA_showEmptyResultMessageOrSetUniqueCondition(
+ $rows, $key_id, $where_clause_array, $local_query, $result
+ );
+ if ($has_unique_condition) {
+ $found_unique_key = true;
+ }
+ }
+ return array($where_clauses, $result, $rows, $found_unique_key);
+}
+
+/**
+ * Show message for empty result or set the unique_condition
+ *
+ * @param array $rows MySQL returned rows
+ * @param string $key_id ID in current key
+ * @param array $where_clause_array array of where clauses
+ * @param string $local_query query performed
+ * @param array $result MySQL result handle
+ *
+ * @return boolean $has_unique_condition
+ */
+function PMA_showEmptyResultMessageOrSetUniqueCondition($rows, $key_id,
+ $where_clause_array, $local_query, $result
+) {
+ $has_unique_condition = false;
+
+ // No row returned
+ if (! $rows[$key_id]) {
+ unset($rows[$key_id], $where_clause_array[$key_id]);
+ PMA_Response::getInstance()->addHtml(
+ PMA_Util::getMessage(
+ __('MySQL returned an empty result set (i.e. zero rows).'),
+ $local_query
+ )
+ );
+ /**
+ * @todo not sure what should be done at this point, but we must not
+ * exit if we want the message to be displayed
+ */
+ } else {// end if (no row returned)
+ $meta = $GLOBALS['dbi']->getFieldsMeta($result[$key_id]);
+
+ list($unique_condition, $tmp_clause_is_unique)
+ = PMA_Util::getUniqueCondition(
+ $result[$key_id], count($meta), $meta, $rows[$key_id], true
+ );
+
+ if (! empty($unique_condition)) {
+ $has_unique_condition = true;
+ }
+ unset($unique_condition, $tmp_clause_is_unique);
+ }
+ return $has_unique_condition;
+}
+
+/**
+ * No primary key given, just load first row
+ *
+ * @param string $table name of the table
+ * @param string $db name of the database
+ *
+ * @return array containing $result and $rows arrays
+ */
+function PMA_loadFirstRow($table, $db)
+{
+ $result = $GLOBALS['dbi']->query(
+ 'SELECT * FROM ' . PMA_Util::backquote($db)
+ . '.' . PMA_Util::backquote($table) . ' LIMIT 1;',
+ null,
+ PMA_DatabaseInterface::QUERY_STORE
+ );
+ $rows = array_fill(0, $GLOBALS['cfg']['InsertRows'], false);
+ return array($result, $rows);
+}
+
+/**
+ * Add some url parameters
+ *
+ * @param array $url_params containing $db and $table as url parameters
+ * @param array $where_clause_array where clauses array
+ * @param string $where_clause where clause
+ *
+ * @return array Add some url parameters to $url_params array and return it
+ */
+function PMA_urlParamsInEditMode($url_params, $where_clause_array, $where_clause)
+{
+ if (isset($where_clause)) {
+ foreach ($where_clause_array as $where_clause) {
+ $url_params['where_clause'] = trim($where_clause);
+ }
+ }
+ if (! empty($_REQUEST['sql_query'])) {
+ $url_params['sql_query'] = $_REQUEST['sql_query'];
+ }
+ return $url_params;
+}
+
+/**
+ * Show function fields in data edit view in pma
+ *
+ * @param array $url_params containing url parameters
+ * @param boolean $showFuncFields whether to show function field
+ *
+ * @return string an html snippet
+ */
+function PMA_showFunctionFieldsInEditMode($url_params, $showFuncFields)
+{
+ $params = array();
+ if (! $showFuncFields) {
+ $params['ShowFunctionFields'] = 1;
+ } else {
+ $params['ShowFunctionFields'] = 0;
+ }
+ $params['ShowFieldTypesInDataEditView']
+ = $GLOBALS['cfg']['ShowFieldTypesInDataEditView'];
+ $params['goto'] = 'sql.php';
+ $this_url_params = array_merge($url_params, $params);
+ if (! $showFuncFields) {
+ return ' : <a href="tbl_change.php'
+ . PMA_URL_getCommon($this_url_params) . '">'
+ . __('Function')
+ . '</a>' . "\n";
+ }
+ return '<th><a href="tbl_change.php'
+ . PMA_URL_getCommon($this_url_params)
+ . '" title="' . __('Hide') . '">'
+ . __('Function')
+ . '</a></th>' . "\n";
+}
+
+/**
+ * Show field types in data edit view in pma
+ *
+ * @param array $url_params containing url parameters
+ * @param boolean $showColumnType whether to show column type
+ *
+ * @return string an html snippet
+ */
+function PMA_showColumnTypesInDataEditView($url_params, $showColumnType)
+{
+ $params = array();
+ if (! $showColumnType) {
+ $params['ShowFieldTypesInDataEditView'] = 1;
+ } else {
+ $params['ShowFieldTypesInDataEditView'] = 0;
+ }
+ $params['ShowFunctionFields'] = $GLOBALS['cfg']['ShowFunctionFields'];
+ $params['goto'] = 'sql.php';
+ $this_other_url_params = array_merge($url_params, $params);
+ if (! $showColumnType) {
+ return ' : <a href="tbl_change.php'
+ . PMA_URL_getCommon($this_other_url_params) . '">'
+ . __('Type') . '</a>' . "\n";
+ }
+ return '<th><a href="tbl_change.php'
+ . PMA_URL_getCommon($this_other_url_params)
+ . '" title="' . __('Hide') . '">' . __('Type') . '</a></th>' . "\n";
+
+}
+
+/**
+ * Retrieve the default for datetime data type
+ *
+ * @param array $column containing column type, Default and null
+ *
+ * @return void
+ */
+function PMA_getDefaultForDatetime($column)
+{
+ // d a t e t i m e
+ //
+ // Current date should not be set as default if the field is NULL
+ // for the current row, but do not put here the current datetime
+ // if there is a default value (the real default value will be set
+ // in the Default value logic below)
+
+ // Note: (tested in MySQL 4.0.16): when lang is some UTF-8,
+ // $column['Default'] is not set if it contains NULL:
+ // Array ([Field] => d [Type] => datetime [Null] => YES [Key] =>
+ // [Extra] => [True_Type] => datetime)
+ // but, look what we get if we switch to iso: (Default is NULL)
+ // Array ([Field] => d [Type] => datetime [Null] => YES [Key] =>
+ // [Default] => [Extra] => [True_Type] => datetime)
+ // so I force a NULL into it (I don't think it's possible
+ // to have an empty default value for DATETIME)
+ // then, the "if" after this one will work
+ if ($column['Type'] == 'datetime'
+ && ! isset($column['Default'])
+ && isset($column['Null'])
+ && $column['Null'] == 'YES'
+ ) {
+ $column['Default'] = null;
+ }
+}
+
+ /**
+ * Analyze the table column array
+ *
+ * @param array $column description of column in given table
+ * @param array $comments_map comments for every column that has a comment
+ * @param boolean $timestamp_seen whether a timestamp has been seen
+ *
+ * @return array description of column in given table
+ */
+function PMA_analyzeTableColumnsArray($column, $comments_map, $timestamp_seen)
+{
+ $column['Field_html'] = htmlspecialchars($column['Field']);
+ $column['Field_md5'] = md5($column['Field']);
+ // True_Type contains only the type (stops at first bracket)
+ $column['True_Type'] = preg_replace('@\(.*@s', '', $column['Type']);
+ PMA_getDefaultForDatetime($column);
+ $column['len'] = preg_match('@float|double@', $column['Type']) ? 100 : -1;
+ $column['Field_title'] = PMA_getColumnTitle($column, $comments_map);
+ $column['is_binary'] = PMA_isColumnBinary($column);
+ $column['is_blob'] = PMA_isColumnBlob($column);
+ $column['is_char'] = PMA_isColumnChar($column);
+ list($column['pma_type'], $column['wrap'], $column['first_timestamp'])
+ = PMA_getEnumSetAndTimestampColumns($column, $timestamp_seen);
+
+ return $column;
+}
+
+ /**
+ * Retrieve the column title
+ *
+ * @param array $column description of column in given table
+ * @param array $comments_map comments for every column that has a comment
+ *
+ * @return string column title
+ */
+function PMA_getColumnTitle($column, $comments_map)
+{
+ if (isset($comments_map[$column['Field']])) {
+ return '<span style="border-bottom: 1px dashed black;" title="'
+ . htmlspecialchars($comments_map[$column['Field']]) . '">'
+ . $column['Field_html'] . '</span>';
+ } else {
+ return $column['Field_html'];
+ }
+}
+
+ /**
+ * check whether the column is a bainary
+ *
+ * @param array $column description of column in given table
+ *
+ * @return boolean If check to ensure types such as "enum('one','two','binary',..)"
+ * or "enum('one','two','varbinary',..)" are not categorized as
+ * binary.
+ */
+function PMA_isColumnBinary($column)
+{
+ // The type column.
+ // Fix for bug #3152931 'ENUM and SET cannot have "Binary" option'
+ if (stripos($column['Type'], 'binary') === 0
+ || stripos($column['Type'], 'varbinary') === 0
+ ) {
+ return stristr($column['Type'], 'binary');
+ } else {
+ return false;
+ }
+
+}
+
+ /**
+ * check whether the column is a blob
+ *
+ * @param array $column description of column in given table
+ *
+ * @return boolean If check to ensure types such as "enum('one','two','blob',..)"
+ * or "enum('one','two','tinyblob',..)" etc. are not categorized
+ * as blob.
+ */
+function PMA_isColumnBlob($column)
+{
+ if (stripos($column['Type'], 'blob') === 0
+ || stripos($column['Type'], 'tinyblob') === 0
+ || stripos($column['Type'], 'mediumblob') === 0
+ || stripos($column['Type'], 'longblob') === 0
+ ) {
+ return stristr($column['Type'], 'blob');
+ } else {
+ return false;
+ }
+}
+
+/**
+ * check is table column char
+ *
+ * @param array $column description of column in given table
+ *
+ * @return boolean If check to ensure types such as "enum('one','two','char',..)" or
+ * "enum('one','two','varchar',..)" are not categorized as char.
+ */
+function PMA_isColumnChar($column)
+{
+ if (stripos($column['Type'], 'char') === 0
+ || stripos($column['Type'], 'varchar') === 0
+ ) {
+ return stristr($column['Type'], 'char');
+ } else {
+ return false;
+ }
+}
+/**
+ * Retrieve set, enum, timestamp table columns
+ *
+ * @param array $column description of column in given table
+ * @param boolean $timestamp_seen whether a timestamp has been seen
+ *
+ * @return array $column['pma_type'], $column['wrap'], $column['first_timestamp']
+ */
+function PMA_getEnumSetAndTimestampColumns($column, $timestamp_seen)
+{
+ $column['first_timestamp'] = false;
+ switch ($column['True_Type']) {
+ case 'set':
+ $column['pma_type'] = 'set';
+ $column['wrap'] = '';
+ break;
+ case 'enum':
+ $column['pma_type'] = 'enum';
+ $column['wrap'] = '';
+ break;
+ case 'timestamp':
+ if (! $timestamp_seen) { // can only occur once per table
+ $timestamp_seen = true;
+ $column['first_timestamp'] = true;
+ }
+ $column['pma_type'] = $column['Type'];
+ $column['wrap'] = ' nowrap';
+ break;
+
+ default:
+ $column['pma_type'] = $column['Type'];
+ $column['wrap'] = ' nowrap';
+ break;
+ }
+ return array($column['pma_type'], $column['wrap'], $column['first_timestamp']);
+}
+
+/**
+ * The function column
+ * We don't want binary data to be destroyed
+ * Note: from the MySQL manual: "BINARY doesn't affect how the column is
+ * stored or retrieved" so it does not mean that the contents is binary
+ *
+ * @param array $column description of column in given table
+ * @param boolean $is_upload upload or no
+ * @param string $column_name_appendix the name atttibute
+ * @param string $unnullify_trigger validation string
+ * @param array $no_support_types list of datatypes that are not (yet)
+ * handled by PMA
+ * @param integer $tabindex_for_function +3000
+ * @param integer $tabindex tab index
+ * @param integer $idindex id index
+ * @param boolean $insert_mode insert mode or edit mode
+ *
+ * @return string an html sippet
+ */
+function PMA_getFunctionColumn($column, $is_upload, $column_name_appendix,
+ $unnullify_trigger, $no_support_types, $tabindex_for_function,
+ $tabindex, $idindex, $insert_mode
+) {
+ $html_output = '';
+ if (($GLOBALS['cfg']['ProtectBinary'] && $column['is_blob'] && ! $is_upload)
+ || ($GLOBALS['cfg']['ProtectBinary'] === 'all' && $column['is_binary'])
+ || ($GLOBALS['cfg']['ProtectBinary'] === 'noblob' && ! $column['is_blob'])
+ ) {
+ $html_output .= '<td class="center">' . __('Binary') . '</td>' . "\n";
+ } elseif (strstr($column['True_Type'], 'enum')
+ || strstr($column['True_Type'], 'set')
+ || in_array($column['pma_type'], $no_support_types)
+ ) {
+ $html_output .= '<td class="center">--</td>' . "\n";
+ } else {
+ $html_output .= '<td>' . "\n";
+
+ $html_output .= '<select name="funcs' . $column_name_appendix . '"'
+ . ' ' . $unnullify_trigger
+ . ' tabindex="' . ($tabindex + $tabindex_for_function) . '"'
+ . ' id="field_' . $idindex . '_1">';
+ $html_output .= PMA_Util::getFunctionsForField($column, $insert_mode) . "\n";
+
+ $html_output .= '</select>' . "\n";
+ $html_output .= '</td>' . "\n";
+ }
+ return $html_output;
+}
+
+/**
+ * The null column
+ *
+ * @param array $column description of column in given table
+ * @param string $column_name_appendix the name atttibute
+ * @param array $real_null_value is column value null or not null
+ * @param integer $tabindex tab index
+ * @param integer $tabindex_for_null +6000
+ * @param integer $idindex id index
+ * @param array $vkey [multi_edit]['row_id']
+ * @param array $foreigners keys into foreign fields
+ * @param array $foreignData data about the foreign keys
+ *
+ * @return string an html snippet
+ */
+function PMA_getNullColumn($column, $column_name_appendix, $real_null_value,
+ $tabindex, $tabindex_for_null, $idindex, $vkey, $foreigners, $foreignData
+) {
+ if ($column['Null'] != 'YES') {
+ return "<td></td>\n";
+ }
+ $html_output = '';
+ $html_output .= '<td>' . "\n";
+ $html_output .= '<input type="hidden" name="fields_null_prev'
+ . $column_name_appendix . '"';
+ if ($real_null_value && !$column['first_timestamp']) {
+ $html_output .= ' value="on"';
+ }
+ $html_output .= ' />' . "\n";
+
+ $html_output .= '<input type="checkbox" class="checkbox_null" tabindex="'
+ . ($tabindex + $tabindex_for_null) . '"'
+ . ' name="fields_null' . $column_name_appendix . '"';
+ if ($real_null_value) {
+ $html_output .= ' checked="checked"';
+ }
+ $html_output .= ' id="field_' . ($idindex) . '_2" />';
+
+ // nullify_code is needed by the js nullify() function
+ $nullify_code = PMA_getNullifyCodeForNullColumn(
+ $column, $foreigners, $foreignData
+ );
+ // to be able to generate calls to nullify() in jQuery
+ $html_output .= '<input type="hidden" class="nullify_code" name="nullify_code'
+ . $column_name_appendix . '" value="' . $nullify_code . '" />';
+ $html_output .= '<input type="hidden" class="hashed_field" name="hashed_field'
+ . $column_name_appendix . '" value="' . $column['Field_md5'] . '" />';
+ $html_output .= '<input type="hidden" class="multi_edit" name="multi_edit'
+ . $column_name_appendix . '" value="' . PMA_escapeJsString($vkey) . '" />';
+ $html_output .= '</td>' . "\n";
+
+ return $html_output;
+}
+
+/**
+ * Retrieve the nullify code for the null column
+ *
+ * @param array $column description of column in given table
+ * @param array $foreigners keys into foreign fields
+ * @param array $foreignData data about the foreign keys
+ *
+ * @return integer $nullify_code
+ */
+function PMA_getNullifyCodeForNullColumn($column, $foreigners, $foreignData)
+{
+ if (strstr($column['True_Type'], 'enum')) {
+ if (strlen($column['Type']) > 20) {
+ $nullify_code = '1';
+ } else {
+ $nullify_code = '2';
+ }
+ } elseif (strstr($column['True_Type'], 'set')) {
+ $nullify_code = '3';
+ } elseif ($foreigners
+ && isset($foreigners[$column['Field']])
+ && $foreignData['foreign_link'] == false
+ ) {
+ // foreign key in a drop-down
+ $nullify_code = '4';
+ } elseif ($foreigners
+ && isset($foreigners[$column['Field']])
+ && $foreignData['foreign_link'] == true
+ ) {
+ // foreign key with a browsing icon
+ $nullify_code = '6';
+ } else {
+ $nullify_code = '5';
+ }
+ return $nullify_code;
+}
+
+/**
+ * Get the HTML elements for value column in insert form
+ * (here, "column" is used in the sense of HTML column in HTML table)
+ *
+ * @param array $column description of column in given table
+ * @param string $backup_field hidden input field
+ * @param string $column_name_appendix the name atttibute
+ * @param string $unnullify_trigger validation string
+ * @param integer $tabindex tab index
+ * @param integer $tabindex_for_value offset for the values tabindex
+ * @param integer $idindex id index
+ * @param array $data description of the column field
+ * @param array $special_chars special characters
+ * @param array $foreignData data about the foreign keys
+ * @param boolean $odd_row whether row is odd
+ * @param array $paramTableDbArray array containing $table and $db
+ * @param array $rownumber the row number
+ * @param array $titles An HTML IMG tag for a particular icon from
+ * a theme, which may be an actual file or
+ * an icon from a sprite
+ * @param array $text_dir text direction
+ * @param string $special_chars_encoded replaced char if the string starts
+ * with a \r\n pair (0x0d0a) add an extra \n
+ * @param string $vkey [multi_edit]['row_id']
+ * @param boolean $is_upload is upload or not
+ * @param integer $biggest_max_file_size 0 intger
+ * @param string $default_char_editing default char editing mode which is stroe
+ * in the config.inc.php script
+ * @param array $no_support_types list of datatypes that are not (yet)
+ * handled by PMA
+ * @param array $gis_data_types list of GIS data types
+ * @param array $extracted_columnspec associative array containing type,
+ * spec_in_brackets and possibly
+ * enum_set_values (another array)
+ *
+ * @return string an html snippet
+ */
+function PMA_getValueColumn($column, $backup_field, $column_name_appendix,
+ $unnullify_trigger, $tabindex, $tabindex_for_value, $idindex, $data,
+ $special_chars, $foreignData, $odd_row, $paramTableDbArray, $rownumber,
+ $titles, $text_dir, $special_chars_encoded, $vkey,
+ $is_upload, $biggest_max_file_size,
+ $default_char_editing, $no_support_types, $gis_data_types, $extracted_columnspec
+) {
+ $html_output = '';
+
+ if ($foreignData['foreign_link'] == true) {
+ $html_output .= PMA_getForeignLink(
+ $column, $backup_field, $column_name_appendix,
+ $unnullify_trigger, $tabindex, $tabindex_for_value, $idindex, $data,
+ $paramTableDbArray, $rownumber, $titles
+ );
+
+ } elseif (is_array($foreignData['disp_row'])) {
+ $html_output .= PMA_dispRowForeignData(
+ $backup_field, $column_name_appendix,
+ $unnullify_trigger, $tabindex, $tabindex_for_value,
+ $idindex, $data, $foreignData
+ );
+
+ } elseif ($GLOBALS['cfg']['LongtextDoubleTextarea']
+ && strstr($column['pma_type'], 'longtext')
+ ) {
+ $html_output = '&nbsp;</td>';
+ $html_output .= '</tr>';
+ $html_output .= '<tr class="' . ($odd_row ? 'odd' : 'even') . '">'
+ . '<td colspan="5" class="right">';
+ $html_output .= PMA_getTextarea(
+ $column, $backup_field, $column_name_appendix, $unnullify_trigger,
+ $tabindex, $tabindex_for_value, $idindex, $text_dir,
+ $special_chars_encoded
+ );
+
+ } elseif (strstr($column['pma_type'], 'text')) {
+
+ $html_output .= PMA_getTextarea(
+ $column, $backup_field, $column_name_appendix, $unnullify_trigger,
+ $tabindex, $tabindex_for_value, $idindex, $text_dir,
+ $special_chars_encoded
+ );
+ $html_output .= "\n";
+ if (strlen($special_chars) > 32000) {
+ $html_output .= "</td>\n";
+ $html_output .= '<td>' . __(
+ 'Because of its length,<br /> this column might not be editable'
+ );
+ }
+
+ } elseif ($column['pma_type'] == 'enum') {
+ $html_output .= PMA_getPmaTypeEnum(
+ $column, $backup_field, $column_name_appendix, $extracted_columnspec,
+ $unnullify_trigger, $tabindex, $tabindex_for_value, $idindex, $data
+ );
+
+ } elseif ($column['pma_type'] == 'set') {
+ $html_output .= PMA_getPmaTypeSet(
+ $column, $extracted_columnspec, $backup_field,
+ $column_name_appendix, $unnullify_trigger, $tabindex,
+ $tabindex_for_value, $idindex, $data
+ );
+
+ } elseif ($column['is_binary'] || $column['is_blob']) {
+ $html_output .= PMA_getBinaryAndBlobColumn(
+ $column, $data, $special_chars, $biggest_max_file_size,
+ $backup_field, $column_name_appendix, $unnullify_trigger, $tabindex,
+ $tabindex_for_value, $idindex, $text_dir, $special_chars_encoded,
+ $vkey, $is_upload
+ );
+
+ } elseif (! in_array($column['pma_type'], $no_support_types)) {
+ $html_output .= PMA_getValueColumnForOtherDatatypes(
+ $column, $default_char_editing, $backup_field,
+ $column_name_appendix, $unnullify_trigger, $tabindex, $special_chars,
+ $tabindex_for_value, $idindex, $text_dir, $special_chars_encoded,
+ $data, $extracted_columnspec
+ );
+ }
+
+ if (in_array($column['pma_type'], $gis_data_types)) {
+ $html_output .= PMA_getHTMLforGisDataTypes();
+ }
+
+ return $html_output;
+}
+
+/**
+ * Get HTML for foreign link in insert form
+ *
+ * @param array $column description of column in given table
+ * @param string $backup_field hidden input field
+ * @param string $column_name_appendix the name atttibute
+ * @param string $unnullify_trigger validation string
+ * @param integer $tabindex tab index
+ * @param integer $tabindex_for_value offset for the values tabindex
+ * @param integer $idindex id index
+ * @param array $data data to edit
+ * @param array $paramTableDbArray array containing $table and $db
+ * @param array $rownumber the row number
+ * @param array $titles An HTML IMG tag for a particular icon from
+ * a theme, which may be an actual file or
+ * an icon from a sprite
+ *
+ * @return string an html snippet
+ */
+function PMA_getForeignLink($column, $backup_field, $column_name_appendix,
+ $unnullify_trigger, $tabindex, $tabindex_for_value, $idindex, $data,
+ $paramTableDbArray, $rownumber, $titles
+) {
+ list($table, $db) = $paramTableDbArray;
+ $html_output = '';
+ $html_output .= $backup_field . "\n";
+
+ $html_output .= '<input type="hidden" name="fields_type'
+ . $column_name_appendix . '" value="foreign" />';
+
+ $html_output .= '<input type="text" name="fields' . $column_name_appendix . '" '
+ . 'class="textfield" '
+ . $unnullify_trigger . ' '
+ . 'tabindex="' . ($tabindex + $tabindex_for_value) . '" '
+ . 'id="field_' . ($idindex) . '_3" '
+ . 'value="' . htmlspecialchars($data) . '" />';
+
+ $html_output .= '<a class="foreign_values_anchor" target="_blank" '
+ . 'onclick="window.open(this.href,\'foreigners\', \'width=640,height=240,'
+ . 'scrollbars=yes,resizable=yes\'); return false;" '
+ . 'href="browse_foreigners.php'
+ . PMA_URL_getCommon(
+ array(
+ 'db' => $db,
+ 'table' => $table,
+ 'field' => $column['Field'],
+ 'rownumber' => $rownumber,
+ 'data' => $data
+ )
+ ) . '">'
+ . str_replace("'", "\'", $titles['Browse']) . '</a>';
+ return $html_output;
+}
+
+/**
+ * Get HTML to display foreign data
+ *
+ * @param string $backup_field hidden input field
+ * @param string $column_name_appendix the name atttibute
+ * @param string $unnullify_trigger validation string
+ * @param integer $tabindex tab index
+ * @param integer $tabindex_for_value offset for the values tabindex
+ * @param integer $idindex id index
+ * @param array $data data to edit
+ * @param array $foreignData data about the foreign keys
+ *
+ * @return string an html snippet
+ */
+function PMA_dispRowForeignData($backup_field, $column_name_appendix,
+ $unnullify_trigger, $tabindex, $tabindex_for_value, $idindex, $data,
+ $foreignData
+) {
+ $html_output = '';
+ $html_output .= $backup_field . "\n";
+ $html_output .= '<input type="hidden"'
+ . ' name="fields_type' . $column_name_appendix . '"'
+ . ' value="foreign" />';
+
+ $html_output .= '<select name="fields' . $column_name_appendix . '"'
+ . ' ' . $unnullify_trigger
+ . ' class="textfield"'
+ . ' tabindex="' . ($tabindex + $tabindex_for_value). '"'
+ . ' id="field_' . $idindex . '_3">';
+ $html_output .= PMA_foreignDropdown(
+ $foreignData['disp_row'], $foreignData['foreign_field'],
+ $foreignData['foreign_display'], $data,
+ $GLOBALS['cfg']['ForeignKeyMaxLimit']
+ );
+ $html_output .= '</select>';
+
+ return $html_output;
+}
+
+/**
+ * Get HTML textarea for insert form
+ *
+ * @param array $column column information
+ * @param string $backup_field hidden input field
+ * @param string $column_name_appendix the name atttibute
+ * @param string $unnullify_trigger validation string
+ * @param integer $tabindex tab index
+ * @param integer $tabindex_for_value offset for the values tabindex
+ * @param integer $idindex id index
+ * @param array $text_dir text direction
+ * @param array $special_chars_encoded replaced char if the string starts
+ * with a \r\n pair (0x0d0a) add an extra \n
+ *
+ * @return string an html snippet
+ */
+function PMA_getTextarea($column, $backup_field, $column_name_appendix,
+ $unnullify_trigger,
+ $tabindex, $tabindex_for_value, $idindex, $text_dir, $special_chars_encoded
+) {
+ $the_class = '';
+ $textAreaRows = $GLOBALS['cfg']['TextareaRows'];
+ $textareaCols = $GLOBALS['cfg']['TextareaCols'];
+
+ if ($column['is_char']) {
+ /**
+ * @todo clarify the meaning of the "textfield" class and explain
+ * why character columns have the "char" class instead
+ */
+ $the_class = 'char';
+ $textAreaRows = $GLOBALS['cfg']['CharTextareaRows'];
+ $textareaCols = $GLOBALS['cfg']['CharTextareaCols'];
+ } elseif ($GLOBALS['cfg']['LongtextDoubleTextarea']
+ && strstr($column['pma_type'], 'longtext')
+ ) {
+ $textAreaRows = $GLOBALS['cfg']['TextareaRows'] * 2;
+ $textareaCols = $GLOBALS['cfg']['TextareaCols'] * 2;
+ }
+ $html_output = $backup_field . "\n"
+ . '<textarea name="fields' . $column_name_appendix . '"'
+ . ' class="' . $the_class . '"'
+ . ' rows="' . $textAreaRows . '"'
+ . ' cols="' . $textareaCols . '"'
+ . ' dir="' . $text_dir . '"'
+ . ' id="field_' . ($idindex) . '_3"'
+ . ' ' . $unnullify_trigger
+ . ' tabindex="' . ($tabindex + $tabindex_for_value) . '">'
+ . $special_chars_encoded
+ . '</textarea>';
+
+ return $html_output;
+}
+
+/**
+ * Get HTML for enum type
+ *
+ * @param type $column description of column in given table
+ * @param type $backup_field hidden input field
+ * @param type $column_name_appendix the name atttibute
+ * @param type $extracted_columnspec associative array containing type,
+ * spec_in_brackets and possibly
+ * enum_set_values (another array)
+ * @param type $unnullify_trigger validation string
+ * @param type $tabindex tab index
+ * @param type $tabindex_for_value offset for the values tabindex
+ * @param type $idindex id index
+ * @param type $data data to edit
+ *
+ * @return string an html snippet
+ */
+function PMA_getPmaTypeEnum($column, $backup_field, $column_name_appendix,
+ $extracted_columnspec, $unnullify_trigger, $tabindex, $tabindex_for_value,
+ $idindex, $data
+) {
+ $html_output = '';
+ if (! isset($column['values'])) {
+ $column['values'] = PMA_getColumnEnumValues(
+ $column, $extracted_columnspec
+ );
+ }
+ $column_enum_values = $column['values'];
+ $html_output .= '<input type="hidden" name="fields_type'
+ . $column_name_appendix. '" value="enum" />';
+ $html_output .= '<input type="hidden" name="fields'
+ . $column_name_appendix . '" value="" />';
+ $html_output .= "\n" . ' ' . $backup_field . "\n";
+ if (strlen($column['Type']) > 20) {
+ $html_output .= PMA_getDropDownDependingOnLength(
+ $column, $column_name_appendix, $unnullify_trigger,
+ $tabindex, $tabindex_for_value, $idindex, $data, $column_enum_values
+ );
+ } else {
+ $html_output .= PMA_getRadioButtonDependingOnLength(
+ $column_name_appendix, $unnullify_trigger,
+ $tabindex, $column, $tabindex_for_value,
+ $idindex, $data, $column_enum_values
+ );
+ }
+ return $html_output;
+}
+
+/**
+ * Get column values
+ *
+ * @param array $column description of column in given table
+ * @param array $extracted_columnspec associative array containing type,
+ * spec_in_brackets and possibly enum_set_values
+ * (another array)
+ *
+ * @return array column values as an associative array
+ */
+function PMA_getColumnEnumValues($column, $extracted_columnspec)
+{
+ $column['values'] = array();
+ foreach ($extracted_columnspec['enum_set_values'] as $val) {
+ $column['values'][] = array(
+ 'plain' => $val,
+ 'html' => htmlspecialchars($val),
+ );
+ }
+ return $column['values'];
+}
+
+/**
+ * Get HTML drop down for more than 20 string length
+ *
+ * @param array $column description of column in given table
+ * @param string $column_name_appendix the name atttibute
+ * @param string $unnullify_trigger validation string
+ * @param integer $tabindex tab index
+ * @param integer $tabindex_for_value offset for the values tabindex
+ * @param integer $idindex id index
+ * @param array $data data to edit
+ * @param array $column_enum_values $column['values']
+ *
+ * @return string an html snippet
+ */
+function PMA_getDropDownDependingOnLength(
+ $column, $column_name_appendix, $unnullify_trigger,
+ $tabindex, $tabindex_for_value, $idindex, $data, $column_enum_values
+) {
+ $html_output = '<select name="fields' . $column_name_appendix . '"'
+ . ' ' . $unnullify_trigger
+ . ' class="textfield"'
+ . ' tabindex="' . ($tabindex + $tabindex_for_value) . '"'
+ . ' id="field_' . ($idindex) . '_3">';
+ $html_output .= '<option value="">&nbsp;</option>' . "\n";
+
+ foreach ($column_enum_values as $enum_value) {
+ $html_output .= '<option value="' . $enum_value['html'] . '"';
+ if ($data == $enum_value['plain']
+ || ($data == ''
+ && (! isset($_REQUEST['where_clause']) || $column['Null'] != 'YES')
+ && isset($column['Default'])
+ && $enum_value['plain'] == $column['Default'])
+ ) {
+ $html_output .= ' selected="selected"';
+ }
+ $html_output .= '>' . $enum_value['html'] . '</option>' . "\n";
+ }
+ $html_output .= '</select>';
+ return $html_output;
+}
+
+/**
+ * Get HTML radio button for less than 20 string length
+ *
+ * @param string $column_name_appendix the name atttibute
+ * @param string $unnullify_trigger validation string
+ * @param integer $tabindex tab index
+ * @param array $column description of column in given table
+ * @param integer $tabindex_for_value offset for the values tabindex
+ * @param integer $idindex id index
+ * @param array $data data to edit
+ * @param array $column_enum_values $column['values']
+ *
+ * @return string an html snippet
+ */
+function PMA_getRadioButtonDependingOnLength(
+ $column_name_appendix, $unnullify_trigger,
+ $tabindex, $column, $tabindex_for_value, $idindex, $data, $column_enum_values
+) {
+ $j = 0;
+ $html_output = '';
+ foreach ($column_enum_values as $enum_value) {
+ $html_output .= ' '
+ . '<input type="radio" name="fields' . $column_name_appendix . '"'
+ . ' class="textfield"'
+ . ' value="' . $enum_value['html'] . '"'
+ . ' id="field_' . ($idindex) . '_3_' . $j . '"'
+ . ' ' . $unnullify_trigger;
+ if ($data == $enum_value['plain']
+ || ($data == ''
+ && (! isset($_REQUEST['where_clause']) || $column['Null'] != 'YES')
+ && isset($column['Default'])
+ && $enum_value['plain'] == $column['Default'])
+ ) {
+ $html_output .= ' checked="checked"';
+ }
+ $html_output .= ' tabindex="' . ($tabindex + $tabindex_for_value) . '" />';
+ $html_output .= '<label for="field_' . $idindex . '_3_' . $j . '">'
+ . $enum_value['html'] . '</label>' . "\n";
+ $j++;
+ }
+ return $html_output;
+}
+
+/**
+ * Get the HTML for 'set' pma type
+ *
+ * @param array $column description of column in given table
+ * @param array $extracted_columnspec associative array containing type,
+ * spec_in_brackets and possibly
+ * enum_set_values (another array)
+ * @param string $backup_field hidden input field
+ * @param string $column_name_appendix the name atttibute
+ * @param string $unnullify_trigger validation string
+ * @param integer $tabindex tab index
+ * @param integer $tabindex_for_value offset for the values tabindex
+ * @param integer $idindex id index
+ * @param array $data description of the column field
+ *
+ * @return string an html snippet
+ */
+function PMA_getPmaTypeSet(
+ $column, $extracted_columnspec, $backup_field,
+ $column_name_appendix, $unnullify_trigger, $tabindex,
+ $tabindex_for_value, $idindex, $data
+) {
+ list($column_set_values, $select_size) = PMA_getColumnSetValueAndSelectSize(
+ $column, $extracted_columnspec
+ );
+ $vset = array_flip(explode(',', $data));
+ $html_output = $backup_field . "\n";
+ $html_output .= '<input type="hidden" name="fields_type'
+ . $column_name_appendix . '" value="set" />';
+ $html_output .= '<select name="fields' . $column_name_appendix . '[]' . '"'
+ . ' class="textfield"'
+ . ' size="' . $select_size . '"'
+ . ' multiple="multiple"'
+ . ' ' . $unnullify_trigger
+ . ' tabindex="' . ($tabindex + $tabindex_for_value) . '"'
+ . ' id="field_' . ($idindex) . '_3">';
+ foreach ($column_set_values as $column_set_value) {
+ $html_output .= '<option value="' . $column_set_value['html'] . '"';
+ if (isset($vset[$column_set_value['plain']])) {
+ $html_output .= ' selected="selected"';
+ }
+ $html_output .= '>' . $column_set_value['html'] . '</option>' . "\n";
+ }
+ $html_output .= '</select>';
+ return $html_output;
+}
+
+/**
+ * Retrieve column 'set' value and select size
+ *
+ * @param array $column description of column in given table
+ * @param array $extracted_columnspec associative array containing type,
+ * spec_in_brackets and possibly enum_set_values
+ * (another array)
+ *
+ * @return array $column['values'], $column['select_size']
+ */
+function PMA_getColumnSetValueAndSelectSize($column, $extracted_columnspec)
+{
+ if (! isset($column['values'])) {
+ $column['values'] = array();
+ foreach ($extracted_columnspec['enum_set_values'] as $val) {
+ $column['values'][] = array(
+ 'plain' => $val,
+ 'html' => htmlspecialchars($val),
+ );
+ }
+ $column['select_size'] = min(4, count($column['values']));
+ }
+ return array($column['values'], $column['select_size']);
+}
+
+/**
+ * Get HTML for binary and blob column
+ *
+ * @param array $column description of column in given table
+ * @param array $data data to edit
+ * @param array $special_chars special characters
+ * @param integer $biggest_max_file_size biggest max file size for uploading
+ * @param string $backup_field hidden input field
+ * @param string $column_name_appendix the name atttibute
+ * @param string $unnullify_trigger validation string
+ * @param integer $tabindex tab index
+ * @param integer $tabindex_for_value offset for the values tabindex
+ * @param integer $idindex id index
+ * @param string $text_dir text direction
+ * @param string $special_chars_encoded replaced char if the string starts
+ * with a \r\n pair (0x0d0a) add an extra \n
+ * @param string $vkey [multi_edit]['row_id']
+ * @param boolean $is_upload is upload or not
+ *
+ * @return string an html snippet
+ */
+function PMA_getBinaryAndBlobColumn(
+ $column, $data, $special_chars, $biggest_max_file_size,
+ $backup_field, $column_name_appendix, $unnullify_trigger, $tabindex,
+ $tabindex_for_value, $idindex, $text_dir, $special_chars_encoded,
+ $vkey, $is_upload
+) {
+ $html_output = '';
+ if (($GLOBALS['cfg']['ProtectBinary'] && $column['is_blob'])
+ || ($GLOBALS['cfg']['ProtectBinary'] == 'all' && $column['is_binary'])
+ || ($GLOBALS['cfg']['ProtectBinary'] == 'noblob' && !$column['is_blob'])
+ ) {
+ $html_output .= __('Binary - do not edit');
+ if (isset($data)) {
+ $data_size = PMA_Util::formatByteDown(
+ strlen(stripslashes($data)), 3, 1
+ );
+ $html_output .= ' ('. $data_size[0] . ' ' . $data_size[1] . ')';
+ unset($data_size);
+ }
+ $html_output .= '<input type="hidden" name="fields_type'
+ . $column_name_appendix . '" value="protected" />'
+ . '<input type="hidden" name="fields'
+ . $column_name_appendix . '" value="" />';
+ } elseif ($column['is_blob']
+ || ($column['len'] > $GLOBALS['cfg']['LimitChars'])
+ ) {
+ $html_output .= "\n" . PMA_getTextarea(
+ $column, $backup_field, $column_name_appendix, $unnullify_trigger,
+ $tabindex, $tabindex_for_value, $idindex, $text_dir,
+ $special_chars_encoded
+ );
+ } else {
+ // field size should be at least 4 and max $GLOBALS['cfg']['LimitChars']
+ $fieldsize = min(max($column['len'], 4), $GLOBALS['cfg']['LimitChars']);
+ $html_output .= "\n" . $backup_field . "\n" . PMA_getHTMLinput(
+ $column, $column_name_appendix, $special_chars, $fieldsize,
+ $unnullify_trigger, $tabindex, $tabindex_for_value, $idindex
+ );
+ }
+
+ if ($is_upload && $column['is_blob']) {
+ $html_output .= '<br />'
+ . '<input type="file"'
+ . ' name="fields_upload' . $vkey . '[' . $column['Field_md5'] . ']"'
+ . ' class="textfield" id="field_' . $idindex . '_3" size="10"'
+ . ' ' . $unnullify_trigger . '/>&nbsp;';
+ list($html_out, $biggest_max_file_size) = PMA_getMaxUploadSize(
+ $column, $biggest_max_file_size
+ );
+ $html_output .= $html_out;
+ }
+
+ if (!empty($GLOBALS['cfg']['UploadDir'])) {
+ $html_output .= PMA_getSelectOptionForUpload($vkey, $column);
+ }
+
+ return $html_output;
+}
+
+/**
+ * Get HTML input type
+ *
+ * @param array $column description of column in given table
+ * @param string $column_name_appendix the name attribute
+ * @param array $special_chars special characters
+ * @param integer $fieldsize html field size
+ * @param string $unnullify_trigger validation string
+ * @param integer $tabindex tab index
+ * @param integer $tabindex_for_value offset for the values tabindex
+ * @param integer $idindex id index
+ *
+ * @return string an html snippet
+ */
+function PMA_getHTMLinput($column, $column_name_appendix, $special_chars,
+ $fieldsize, $unnullify_trigger, $tabindex, $tabindex_for_value, $idindex
+) {
+ static $min_max_data = array(
+ 'unsigned' => array(
+ 'tinyint' => array('0', '255'),
+ 'smallint' => array('0', '65535'),
+ 'mediumint' => array('0', '16777215'),
+ 'int' => array('0', '4294967295'),
+ 'bigint' => array('0', '18446744073709551615')
+ ),
+ 'signed' => array(
+ 'tinyint' => array('-128', '127'),
+ 'smallint' => array('-32768', '32767'),
+ 'mediumint' => array('-8388608', '8388607'),
+ 'int' => array('-2147483648', '2147483647'),
+ 'bigint' => array('-9223372036854775808', '9223372036854775807')
+ )
+ );
+
+ $input_type = 'text';
+ // do not use the 'date' or 'time' types here; they have no effect on some
+ // browsers and create side effects (see bug #4218)
+
+ $the_class = 'textfield';
+ // verify True_Type which does not contain the parentheses and length
+ if ($column['True_Type'] === 'date') {
+ $the_class .= ' datefield';
+ } elseif ($column['True_Type'] === 'datetime'
+ || $column['True_Type'] === 'timestamp'
+ ) {
+ $the_class .= ' datetimefield';
+ }
+ $input_min_max = false;
+ if (in_array(
+ $column['True_Type'],
+ array('tinyint', 'smallint', 'mediumint', 'int', 'bigint')
+ )) {
+ $input_type = 'number';
+ $is_unsigned = substr($column['pma_type'], -9) === ' unsigned';
+ $min_max_values
+ = $min_max_data[$is_unsigned ? 'unsigned' : 'signed']
+ [$column['True_Type']];
+ $input_min_max = 'min="' . $min_max_values[0] . '" '
+ . 'max="' . $min_max_values[1] . '" ';
+ }
+ return '<input type="' . $input_type . '"'
+ . ' name="fields'. $column_name_appendix . '"'
+ . ' value="' . $special_chars . '" size="' . $fieldsize . '"'
+ . ($input_min_max !== false ? ' ' . $input_min_max : '')
+ . ($input_type === 'time' ? ' step="1"' : '')
+ . ' class="' . $the_class . '" ' . $unnullify_trigger
+ . ' tabindex="' . ($tabindex + $tabindex_for_value). '"'
+ . ' id="field_' . ($idindex) . '_3" />';
+}
+
+/**
+ * Get HTML select option for upload
+ *
+ * @param string $vkey [multi_edit]['row_id']
+ * @param array $column description of column in given table
+ *
+ * @return string|void an html snippet
+ */
+function PMA_getSelectOptionForUpload($vkey, $column)
+{
+ $files = PMA_getFileSelectOptions(
+ PMA_Util::userDir($GLOBALS['cfg']['UploadDir'])
+ );
+
+ if ($files === false) {
+ return '<font color="red">' . __('Error') . '</font><br />' . "\n"
+ . __('The directory you set for upload work cannot be reached.') . "\n";
+ } elseif (!empty($files)) {
+ return "<br />\n"
+ . '<i>' . __('Or') . '</i>' . ' '
+ . __('web server upload directory:') . '<br />' . "\n"
+ . '<select size="1" name="fields_uploadlocal'
+ . $vkey . '[' . $column['Field_md5'] . ']">' . "\n"
+ . '<option value="" selected="selected"></option>' . "\n"
+ . $files
+ . '</select>' . "\n";
+ }
+}
+
+/**
+ * Retrieve the maximum upload file size
+ *
+ * @param array $column description of column in given table
+ * @param integer $biggest_max_file_size biggest max file size for uploading
+ *
+ * @return array an html snippet and $biggest_max_file_size
+ */
+function PMA_getMaxUploadSize($column, $biggest_max_file_size)
+{
+ // find maximum upload size, based on field type
+ /**
+ * @todo with functions this is not so easy, as you can basically
+ * process any data with function like MD5
+ */
+ global $max_upload_size;
+ $max_field_sizes = array(
+ 'tinyblob' => '256',
+ 'blob' => '65536',
+ 'mediumblob' => '16777216',
+ 'longblob' => '4294967296' // yeah, really
+ );
+
+ $this_field_max_size = $max_upload_size; // from PHP max
+ if ($this_field_max_size > $max_field_sizes[$column['pma_type']]) {
+ $this_field_max_size = $max_field_sizes[$column['pma_type']];
+ }
+ $html_output
+ = PMA_Util::getFormattedMaximumUploadSize(
+ $this_field_max_size
+ ) . "\n";
+ // do not generate here the MAX_FILE_SIZE, because we should
+ // put only one in the form to accommodate the biggest field
+ if ($this_field_max_size > $biggest_max_file_size) {
+ $biggest_max_file_size = $this_field_max_size;
+ }
+ return array($html_output, $biggest_max_file_size);
+}
+
+/**
+ * Get HTML for the Value column of other datatypes
+ * (here, "column" is used in the sense of HTML column in HTML table)
+ *
+ * @param array $column description of column in given table
+ * @param string $default_char_editing default char editing mode which is stroe
+ * in the config.inc.php script
+ * @param string $backup_field hidden input field
+ * @param string $column_name_appendix the name atttibute
+ * @param string $unnullify_trigger validation string
+ * @param integer $tabindex tab index
+ * @param array $special_chars special characters
+ * @param integer $tabindex_for_value offset for the values tabindex
+ * @param integer $idindex id index
+ * @param string $text_dir text direction
+ * @param array $special_chars_encoded replaced char if the string starts
+ * with a \r\n pair (0x0d0a) add an extra \n
+ * @param strign $data data to edit
+ * @param array $extracted_columnspec associative array containing type,
+ * spec_in_brackets and possibly
+ * enum_set_values (another array)
+ *
+ * @return string an html snippet
+ */
+function PMA_getValueColumnForOtherDatatypes($column, $default_char_editing,
+ $backup_field,
+ $column_name_appendix, $unnullify_trigger, $tabindex, $special_chars,
+ $tabindex_for_value, $idindex, $text_dir, $special_chars_encoded, $data,
+ $extracted_columnspec
+) {
+ $fieldsize = PMA_getColumnSize($column, $extracted_columnspec);
+ $html_output = $backup_field . "\n";
+ if ($column['is_char']
+ && ($GLOBALS['cfg']['CharEditing'] == 'textarea'
+ || strpos($data, "\n") !== false)
+ ) {
+ $html_output .= "\n";
+ $GLOBALS['cfg']['CharEditing'] = $default_char_editing;
+ $html_output .= PMA_getTextarea(
+ $column, $backup_field, $column_name_appendix, $unnullify_trigger,
+ $tabindex, $tabindex_for_value, $idindex, $text_dir,
+ $special_chars_encoded
+ );
+ } else {
+ $html_output .= PMA_getHTMLinput(
+ $column, $column_name_appendix, $special_chars,
+ $fieldsize, $unnullify_trigger, $tabindex, $tabindex_for_value, $idindex
+ );
+
+ if ($column['Extra'] == 'auto_increment') {
+ $html_output .= '<input type="hidden" name="auto_increment'
+ . $column_name_appendix . '" value="1" />';
+ }
+ if (substr($column['pma_type'], 0, 9) == 'timestamp') {
+ $html_output .= '<input type="hidden" name="fields_type'
+ . $column_name_appendix . '" value="timestamp" />';
+ }
+ if (substr($column['pma_type'], 0, 8) == 'datetime') {
+ $html_output .= '<input type="hidden" name="fields_type'
+ . $column_name_appendix . '" value="datetime" />';
+ }
+ if ($column['True_Type'] == 'bit') {
+ $html_output .= '<input type="hidden" name="fields_type'
+ . $column_name_appendix . '" value="bit" />';
+ }
+ if ($column['pma_type'] == 'date'
+ || $column['pma_type'] == 'datetime'
+ || substr($column['pma_type'], 0, 9) == 'timestamp'
+ ) {
+ // the _3 suffix points to the date field
+ // the _2 suffix points to the corresponding NULL checkbox
+ // in dateFormat, 'yy' means the year with 4 digits
+ }
+ }
+ return $html_output;
+}
+
+/**
+ * Get the field size
+ *
+ * @param array $column description of column in given table
+ * @param array $extracted_columnspec associative array containing type,
+ * spec_in_brackets and possibly enum_set_values
+ * (another array)
+ *
+ * @return integer field size
+ */
+function PMA_getColumnSize($column, $extracted_columnspec)
+{
+ if ($column['is_char']) {
+ $fieldsize = $extracted_columnspec['spec_in_brackets'];
+ if ($fieldsize > $GLOBALS['cfg']['MaxSizeForInputField']) {
+ /**
+ * This case happens for CHAR or VARCHAR columns which have
+ * a size larger than the maximum size for input field.
+ */
+ $GLOBALS['cfg']['CharEditing'] = 'textarea';
+ }
+ } else {
+ /**
+ * This case happens for example for INT or DATE columns;
+ * in these situations, the value returned in $column['len']
+ * seems appropriate.
+ */
+ $fieldsize = $column['len'];
+ }
+ return min(
+ max($fieldsize, $GLOBALS['cfg']['MinSizeForInputField']),
+ $GLOBALS['cfg']['MaxSizeForInputField']
+ );
+}
+
+/**
+ * Get HTML for gis data types
+ *
+ * @return string an html snippet
+ */
+function PMA_getHTMLforGisDataTypes()
+{
+ $edit_str = PMA_Util::getIcon('b_edit.png', __('Edit/Insert'));
+ return '<span class="open_gis_editor">'
+ . PMA_Util::linkOrButton(
+ '#', $edit_str, array(), false, false, '_blank'
+ )
+ . '</span>';
+}
+
+/**
+ * get html for continue insertion form
+ *
+ * @param string $table name of the table
+ * @param string $db name of the database
+ * @param array $where_clause_array array of where clauses
+ * @param string $err_url error url
+ *
+ * @return string an html snippet
+ */
+function PMA_getContinueInsertionForm($table, $db, $where_clause_array, $err_url)
+{
+ $html_output = '<form id="continueForm" method="post"'
+ . ' action="tbl_replace.php" name="continueForm">'
+ . PMA_URL_getHiddenInputs($db, $table)
+ . '<input type="hidden" name="goto"'
+ . ' value="' . htmlspecialchars($GLOBALS['goto']) . '" />'
+ . '<input type="hidden" name="err_url"'
+ . ' value="' . htmlspecialchars($err_url) . '" />'
+ . '<input type="hidden" name="sql_query"'
+ . ' value="' . htmlspecialchars($_REQUEST['sql_query']) . '" />';
+
+ if (isset($_REQUEST['where_clause'])) {
+ foreach ($where_clause_array as $key_id => $where_clause) {
+
+ $html_output .= '<input type="hidden"'
+ . ' name="where_clause[' . $key_id . ']"'
+ . ' value="' . htmlspecialchars(trim($where_clause)) . '" />'. "\n";
+ }
+ }
+ $tmp = '<select name="insert_rows" id="insert_rows">' . "\n";
+ $option_values = array(1, 2, 5, 10, 15, 20, 30, 40);
+
+ foreach ($option_values as $value) {
+ $tmp .= '<option value="' . $value . '"';
+ if ($value == $GLOBALS['cfg']['InsertRows']) {
+ $tmp .= ' selected="selected"';
+ }
+ $tmp .= '>' . $value . '</option>' . "\n";
+ }
+
+ $tmp .= '</select>' . "\n";
+ $html_output .= "\n" . sprintf(__('Continue insertion with %s rows'), $tmp);
+ unset($tmp);
+ $html_output .= '</form>' . "\n";
+ return $html_output;
+}
+
+/**
+ * Get action panel
+ *
+ * @param array $where_clause where clause
+ * @param string $after_insert insert mode, e.g. new_insert, same_insert
+ * @param integer $tabindex tab index
+ * @param integer $tabindex_for_value offset for the values tabindex
+ * @param boolean $found_unique_key boolean variable for unique key
+ *
+ * @return string an html snippet
+ */
+function PMA_getActionsPanel($where_clause, $after_insert, $tabindex,
+ $tabindex_for_value, $found_unique_key
+) {
+ $html_output = '<fieldset id="actions_panel">'
+ . '<table cellpadding="5" cellspacing="0">'
+ . '<tr>'
+ . '<td class="nowrap vmiddle">'
+ . PMA_getSubmitTypeDropDown($where_clause, $tabindex, $tabindex_for_value)
+ . "\n";
+
+ $html_output .= '</td>'
+ . '<td class="vmiddle">'
+ . '&nbsp;&nbsp;&nbsp;<strong>'
+ . __('and then') . '</strong>&nbsp;&nbsp;&nbsp;'
+ . '</td>'
+ . '<td class="nowrap vmiddle">'
+ . PMA_getAfterInsertDropDown(
+ $where_clause, $after_insert, $found_unique_key
+ )
+ . '</td>'
+ . '</tr>';
+ $html_output .='<tr>'
+ . PMA_getSumbitAndResetButtonForActionsPanel($tabindex, $tabindex_for_value)
+ . '</tr>'
+ . '</table>'
+ . '</fieldset>';
+ return $html_output;
+}
+
+/**
+ * Get a HTML drop down for submit types
+ *
+ * @param array $where_clause where clause
+ * @param integer $tabindex tab index
+ * @param integer $tabindex_for_value offset for the values tabindex
+ *
+ * @return string an html snippet
+ */
+function PMA_getSubmitTypeDropDown($where_clause, $tabindex, $tabindex_for_value)
+{
+ $html_output = '<select name="submit_type" class="control_at_footer" tabindex="'
+ . ($tabindex + $tabindex_for_value + 1) . '">';
+ if (isset($where_clause)) {
+ $html_output .= '<option value="save">' . __('Save') . '</option>';
+ }
+ $html_output .= '<option value="insert">'
+ . __('Insert as new row')
+ . '</option>'
+ . '<option value="insertignore">'
+ . __('Insert as new row and ignore errors')
+ . '</option>'
+ . '<option value="showinsert">'
+ . __('Show insert query')
+ . '</option>'
+ . '</select>';
+ return $html_output;
+}
+
+/**
+ * Get HTML drop down for after insert
+ *
+ * @param array $where_clause where clause
+ * @param string $after_insert insert mode, e.g. new_insert, same_insert
+ * @param boolean $found_unique_key boolean variable for unique key
+ *
+ * @return string an html snippet
+ */
+function PMA_getAfterInsertDropDown($where_clause, $after_insert, $found_unique_key)
+{
+ $html_output = '<select name="after_insert" class="control_at_footer">'
+ . '<option value="back" '
+ . ($after_insert == 'back' ? 'selected="selected"' : '') . '>'
+ . __('Go back to previous page') . '</option>'
+ . '<option value="new_insert" '
+ . ($after_insert == 'new_insert' ? 'selected="selected"' : '') . '>'
+ . __('Insert another new row') . '</option>';
+
+ if (isset($where_clause)) {
+ $html_output .= '<option value="same_insert" '
+ . ($after_insert == 'same_insert' ? 'selected="selected"' : '') . '>'
+ . __('Go back to this page') . '</option>';
+
+ // If we have just numeric primary key, we can also edit next
+ // in 2.8.2, we were looking for `field_name` = numeric_value
+ //if (preg_match('@^[\s]*`[^`]*` = [0-9]+@', $where_clause)) {
+ // in 2.9.0, we are looking for `table_name`.`field_name` = numeric_value
+ $is_numeric = false;
+ if (! is_array($where_clause)) {
+ $where_clause = array($where_clause);
+ }
+ for ($i = 0; $i < count($where_clause); $i++) {
+ $is_numeric = preg_match(
+ '@^[\s]*`[^`]*`[\.]`[^`]*` = [0-9]+@',
+ $where_clause[$i]
+ );
+ if ($is_numeric == true) {
+ break;
+ }
+ }
+ if ($found_unique_key && $is_numeric) {
+ $html_output .= '<option value="edit_next" '
+ . ($after_insert == 'edit_next' ? 'selected="selected"' : '') . '>'
+ . __('Edit next row') . '</option>';
+
+ }
+ }
+ $html_output .= '</select>';
+ return $html_output;
+
+}
+
+/**
+ * get Submit button and Reset button for action panel
+ *
+ * @param integer $tabindex tab index
+ * @param integer $tabindex_for_value offset for the values tabindex
+ *
+ * @return string an html snippet
+ */
+function PMA_getSumbitAndResetButtonForActionsPanel($tabindex, $tabindex_for_value)
+{
+ return '<td>'
+ . PMA_Util::showHint(
+ __(
+ 'Use TAB key to move from value to value,'
+ . ' or CTRL+arrows to move anywhere'
+ )
+ )
+ . '</td>'
+ . '<td colspan="3" class="right vmiddle">'
+ . '<input type="submit" class="control_at_footer" value="' . __('Go') . '"'
+ . ' tabindex="' . ($tabindex + $tabindex_for_value + 6) . '" id="buttonYes" />'
+ . '<input type="reset" class="control_at_footer" value="' . __('Reset') . '"'
+ . ' tabindex="' . ($tabindex + $tabindex_for_value + 7) . '" />'
+ . '</td>';
+}
+
+/**
+ * Get table head and table foot for insert row table
+ *
+ * @param array $url_params url parameters
+ *
+ * @return string an html snippet
+ */
+function PMA_getHeadAndFootOfInsertRowTable($url_params)
+{
+ $html_output = '<table class="insertRowTable">'
+ . '<thead>'
+ . '<tr>'
+ . '<th>' . __('Column') . '</th>';
+
+ if ($GLOBALS['cfg']['ShowFieldTypesInDataEditView']) {
+ $html_output .= PMA_showColumnTypesInDataEditView($url_params, true);
+ }
+ if ($GLOBALS['cfg']['ShowFunctionFields']) {
+ $html_output .= PMA_showFunctionFieldsInEditMode($url_params, true);
+ }
+
+ $html_output .= '<th>'. __('Null') . '</th>'
+ . '<th>' . __('Value') . '</th>'
+ . '</tr>'
+ . '</thead>'
+ . ' <tfoot>'
+ . '<tr>'
+ . '<th colspan="5" class="tblFooters right">'
+ . '<input type="submit" value="' . __('Go') . '" />'
+ . '</th>'
+ . '</tr>'
+ . '</tfoot>';
+ return $html_output;
+}
+
+/**
+ * Prepares the field value and retrieve special chars, backup field and data array
+ *
+ * @param array $current_row a row of the table
+ * @param array $column description of column in given table
+ * @param array $extracted_columnspec associative array containing type,
+ * spec_in_brackets and possibly
+ * enum_set_values (another array)
+ * @param boolean $real_null_value whether column value null or not null
+ * @param array $gis_data_types list of GIS data types
+ * @param string $column_name_appendix string to append to column name in input
+ *
+ * @return array $real_null_value, $data, $special_chars, $backup_field,
+ * $special_chars_encoded
+ */
+function PMA_getSpecialCharsAndBackupFieldForExistingRow(
+ $current_row, $column, $extracted_columnspec,
+ $real_null_value, $gis_data_types, $column_name_appendix
+) {
+ $special_chars_encoded = '';
+ $data = null;
+ // (we are editing)
+ if (is_null($current_row[$column['Field']])) {
+ $real_null_value = true;
+ $current_row[$column['Field']] = '';
+ $special_chars = '';
+ $data = $current_row[$column['Field']];
+ } elseif ($column['True_Type'] == 'bit') {
+ $special_chars = PMA_Util::printableBitValue(
+ $current_row[$column['Field']], $extracted_columnspec['spec_in_brackets']
+ );
+ } elseif ((substr($column['True_Type'], 0, 9) == 'timestamp'
+ || $column['True_Type'] == 'datetime'
+ || $column['True_Type'] == 'time')
+ && (strpos ($current_row[$column['Field']],"." ) === TRUE)
+ ) {
+ $current_row[$column['Field']] = PMA_Util::addMicroseconds(
+ $current_row[$column['Field']]
+ );
+ $special_chars = htmlspecialchars($current_row[$column['Field']]);
+ } elseif (in_array($column['True_Type'], $gis_data_types)) {
+ // Convert gis data to Well Know Text format
+ $current_row[$column['Field']] = PMA_Util::asWKT(
+ $current_row[$column['Field']], true
+ );
+ $special_chars = htmlspecialchars($current_row[$column['Field']]);
+ } else {
+ // special binary "characters"
+ if ($column['is_binary']
+ || ($column['is_blob'] && ! $GLOBALS['cfg']['ProtectBinary'])
+ ) {
+ if ($_SESSION['tmpval']['display_binary_as_hex']
+ && $GLOBALS['cfg']['ShowFunctionFields']
+ ) {
+ $current_row[$column['Field']] = bin2hex(
+ $current_row[$column['Field']]
+ );
+ $column['display_binary_as_hex'] = true;
+ } else {
+ $current_row[$column['Field']]
+ = PMA_Util::replaceBinaryContents(
+ $current_row[$column['Field']]
+ );
+ }
+ } // end if
+ $special_chars = htmlspecialchars($current_row[$column['Field']]);
+
+ //We need to duplicate the first \n or otherwise we will lose
+ //the first newline entered in a VARCHAR or TEXT column
+ $special_chars_encoded
+ = PMA_Util::duplicateFirstNewline($special_chars);
+
+ $data = $current_row[$column['Field']];
+ } // end if... else...
+
+ //when copying row, it is useful to empty auto-increment column
+ // to prevent duplicate key error
+ if (isset($_REQUEST['default_action'])
+ && $_REQUEST['default_action'] === 'insert'
+ ) {
+ if ($column['Key'] === 'PRI'
+ && strpos($column['Extra'], 'auto_increment') !== false
+ ) {
+ $data = $special_chars_encoded = $special_chars = null;
+ }
+ }
+ // If a timestamp field value is not included in an update
+ // statement MySQL auto-update it to the current timestamp;
+ // however, things have changed since MySQL 4.1, so
+ // it's better to set a fields_prev in this situation
+ $backup_field = '<input type="hidden" name="fields_prev'
+ . $column_name_appendix . '" value="'
+ . htmlspecialchars($current_row[$column['Field']]) . '" />';
+
+ return array(
+ $real_null_value,
+ $special_chars_encoded,
+ $special_chars,
+ $data,
+ $backup_field
+ );
+}
+
+/**
+ * display default values
+ *
+ * @param type $column description of column in given table
+ * @param boolean $real_null_value whether column value null or not null
+ *
+ * @return array $real_null_value, $data, $special_chars,
+ * $backup_field, $special_chars_encoded
+ */
+function PMA_getSpecialCharsAndBackupFieldForInsertingMode(
+ $column, $real_null_value
+) {
+ if (! isset($column['Default'])) {
+ $column['Default'] = '';
+ $real_null_value = true;
+ $data = '';
+ } else {
+ $data = $column['Default'];
+ }
+
+ if ($column['True_Type'] == 'bit') {
+ $special_chars = PMA_Util::convertBitDefaultValue($column['Default']);
+ } elseif ((substr($column['True_Type'], 0, 9) == 'timestamp'
+ || $column['True_Type'] == 'datetime'
+ || $column['True_Type'] == 'time')
+ && strpos($column['Default'], '.') === true
+ ) {
+ $special_chars = PMA_Util::addMicroseconds($column['Default']);
+ } else {
+ $special_chars = htmlspecialchars($column['Default']);
+ }
+ $backup_field = '';
+ $special_chars_encoded = PMA_Util::duplicateFirstNewline($special_chars);
+ // this will select the UNHEX function while inserting
+ if (($column['is_binary']
+ || ($column['is_blob'] && ! $GLOBALS['cfg']['ProtectBinary']))
+ && (isset($_SESSION['tmpval']['display_binary_as_hex'])
+ && $_SESSION['tmpval']['display_binary_as_hex'])
+ && $GLOBALS['cfg']['ShowFunctionFields']
+ ) {
+ $column['display_binary_as_hex'] = true;
+ }
+ return array(
+ $real_null_value, $data, $special_chars,
+ $backup_field, $special_chars_encoded
+ );
+}
+
+/**
+ * Prepares the update/insert of a row
+ *
+ * @return array $loop_array, $using_key, $is_insert, $is_insertignore
+ */
+function PMA_getParamsForUpdateOrInsert()
+{
+ if (isset($_REQUEST['where_clause'])) {
+ // we were editing something => use the WHERE clause
+ $loop_array = is_array($_REQUEST['where_clause'])
+ ? $_REQUEST['where_clause']
+ : array($_REQUEST['where_clause']);
+ $using_key = true;
+ $is_insert = $_REQUEST['submit_type'] == 'insert'
+ || $_REQUEST['submit_type'] == 'showinsert'
+ || $_REQUEST['submit_type'] == 'insertignore';
+ } else {
+ // new row => use indexes
+ $loop_array = array();
+ foreach ($_REQUEST['fields']['multi_edit'] as $key => $dummy) {
+ $loop_array[] = $key;
+ }
+ $using_key = false;
+ $is_insert = true;
+ }
+ $is_insertignore = $_REQUEST['submit_type'] == 'insertignore';
+ return array($loop_array, $using_key, $is_insert, $is_insertignore);
+}
+
+/**
+ * Check wether insert row mode and if so include tbl_changen script and set
+ * global variables.
+ *
+ * @return void
+ */
+function PMA_isInsertRow()
+{
+ if (isset($_REQUEST['insert_rows'])
+ && is_numeric($_REQUEST['insert_rows'])
+ && $_REQUEST['insert_rows'] != $GLOBALS['cfg']['InsertRows']
+ ) {
+ $GLOBALS['cfg']['InsertRows'] = $_REQUEST['insert_rows'];
+ $response = PMA_Response::getInstance();
+ $header = $response->getHeader();
+ $scripts = $header->getScripts();
+ $scripts->addFile('tbl_change.js');
+ if (!defined('TESTSUITE')) {
+ include 'tbl_change.php';
+ exit;
+ }
+ }
+}
+
+/**
+ * set $_SESSION for edit_next
+ *
+ * @param string $one_where_clause one where clause from where clauses array
+ *
+ * @return void
+ */
+function PMA_setSessionForEditNext($one_where_clause)
+{
+ $local_query = 'SELECT * FROM ' . PMA_Util::backquote($GLOBALS['db'])
+ . '.' . PMA_Util::backquote($GLOBALS['table']) . ' WHERE '
+ . str_replace('` =', '` >', $one_where_clause) . ' LIMIT 1;';
+
+ $res = $GLOBALS['dbi']->query($local_query);
+ $row = $GLOBALS['dbi']->fetchRow($res);
+ $meta = $GLOBALS['dbi']->getFieldsMeta($res);
+ // must find a unique condition based on unique key,
+ // not a combination of all fields
+ list($unique_condition, $clause_is_unique)
+ = PMA_Util::getUniqueCondition(
+ $res, count($meta), $meta, $row, true
+ );
+ if (! empty($unique_condition)) {
+ $_SESSION['edit_next'] = $unique_condition;
+ }
+ unset($unique_condition, $clause_is_unique);
+}
+
+/**
+ * set $goto_include variable for different cases and retrieve like,
+ * if $GLOBALS['goto'] empty, if $goto_include previously not defined
+ * and new_insert, same_insert, edit_next
+ *
+ * @param string $goto_include store some script for include, otherwise it is
+ * boolean false
+ *
+ * @return string $goto_include
+ */
+function PMA_getGotoInclude($goto_include)
+{
+ $valid_options = array('new_insert', 'same_insert', 'edit_next');
+ if (isset($_REQUEST['after_insert'])
+ && in_array($_REQUEST['after_insert'], $valid_options)
+ ) {
+ $goto_include = 'tbl_change.php';
+ } elseif (! empty($GLOBALS['goto'])) {
+ if (! preg_match('@^[a-z_]+\.php$@', $GLOBALS['goto'])) {
+ // this should NOT happen
+ //$GLOBALS['goto'] = false;
+ $goto_include = false;
+ } else {
+ $goto_include = $GLOBALS['goto'];
+ }
+ if ($GLOBALS['goto'] == 'db_sql.php' && strlen($GLOBALS['table'])) {
+ $GLOBALS['table'] = '';
+ }
+ }
+ if (! $goto_include) {
+ if (! strlen($GLOBALS['table'])) {
+ $goto_include = 'db_sql.php';
+ } else {
+ $goto_include = 'tbl_sql.php';
+ }
+ }
+ return $goto_include;
+}
+
+/**
+ * Defines the url to return in case of failure of the query
+ *
+ * @param array $url_params url parameters
+ *
+ * @return string error url for query failure
+ */
+function PMA_getErrorUrl($url_params)
+{
+ if (isset($_REQUEST['err_url'])) {
+ return $_REQUEST['err_url'];
+ } else {
+ return 'tbl_change.php' . PMA_URL_getCommon($url_params);
+ }
+}
+
+/**
+ * Builds the sql query
+ *
+ * @param boolean $is_insertignore $_REQUEST['submit_type'] == 'insertignore'
+ * @param array $query_fields column names array
+ * @param array $value_sets array of query values
+ *
+ * @return string a query
+ */
+function PMA_buildSqlQuery($is_insertignore, $query_fields, $value_sets)
+{
+ if ($is_insertignore) {
+ $insert_command = 'INSERT IGNORE ';
+ } else {
+ $insert_command = 'INSERT ';
+ }
+ $query[] = $insert_command . 'INTO '
+ . PMA_Util::backquote($GLOBALS['db']) . '.'
+ . PMA_Util::backquote($GLOBALS['table'])
+ . ' (' . implode(', ', $query_fields) . ') VALUES ('
+ . implode('), (', $value_sets) . ')';
+ unset($insert_command, $query_fields);
+ return $query;
+}
+
+/**
+ * Executes the sql query and get the result, then move back to the calling page
+ *
+ * @param array $url_params url parameters array
+ * @param array $query built query from PMA_buildSqlQuery()
+ *
+ * @return array $url_params, $total_affected_rows, $last_messages
+ * $warning_messages, $error_messages, $return_to_sql_query
+ */
+function PMA_executeSqlQuery($url_params, $query)
+{
+ $return_to_sql_query = '';
+ if (! empty($GLOBALS['sql_query'])) {
+ $url_params['sql_query'] = $GLOBALS['sql_query'];
+ $return_to_sql_query = $GLOBALS['sql_query'];
+ }
+ $GLOBALS['sql_query'] = implode('; ', $query) . ';';
+ // to ensure that the query is displayed in case of
+ // "insert as new row" and then "insert another new row"
+ $GLOBALS['display_query'] = $GLOBALS['sql_query'];
+
+ $total_affected_rows = 0;
+ $last_messages = array();
+ $warning_messages = array();
+ $error_messages = array();
+
+ foreach ($query as $single_query) {
+ if ($_REQUEST['submit_type'] == 'showinsert') {
+ $last_messages[] = PMA_Message::notice(__('Showing SQL query'));
+ continue;
+ }
+ if ($GLOBALS['cfg']['IgnoreMultiSubmitErrors']) {
+ $result = $GLOBALS['dbi']->tryQuery($single_query);
+ } else {
+ $result = $GLOBALS['dbi']->query($single_query);
+ }
+ if (! $result) {
+ $error_messages[] = PMA_Message::sanitize($GLOBALS['dbi']->getError());
+ } else {
+ // The next line contains a real assignment, it's not a typo
+ if ($tmp = @$GLOBALS['dbi']->affectedRows()) {
+ $total_affected_rows += $tmp;
+ }
+ unset($tmp);
+
+ $insert_id = $GLOBALS['dbi']->insertId();
+ if ($insert_id != 0) {
+ // insert_id is id of FIRST record inserted in one insert, so if we
+ // inserted multiple rows, we had to increment this
+
+ if ($total_affected_rows > 0) {
+ $insert_id = $insert_id + $total_affected_rows - 1;
+ }
+ $last_message = PMA_Message::notice(__('Inserted row id: %1$d'));
+ $last_message->addParam($insert_id);
+ $last_messages[] = $last_message;
+ }
+ $GLOBALS['dbi']->freeResult($result);
+ }
+ $warning_messages = PMA_getWarningMessages();
+ }
+ return array(
+ $url_params,
+ $total_affected_rows,
+ $last_messages,
+ $warning_messages,
+ $error_messages,
+ $return_to_sql_query
+ );
+}
+
+/**
+ * get the warning messages array
+ *
+ * @return array $warning_essages
+ */
+function PMA_getWarningMessages()
+{
+ $warning_essages = array();
+ foreach ($GLOBALS['dbi']->getWarnings() as $warning) {
+ $warning_essages[] = PMA_Message::sanitize(
+ $warning['Level'] . ': #' . $warning['Code'] . ' ' . $warning['Message']
+ );
+ }
+ return $warning_essages;
+}
+
+/**
+ * Column to display from the foreign table?
+ *
+ * @param string $where_comparison string that contain relation field value
+ * @param string $relation_field_value relation field value
+ * @param array $map all Relations to foreign tables for a given
+ * table or optionally a given column in a table
+ * @param string $relation_field relation field
+ *
+ * @return string $dispval display value from the foreign table
+ */
+function PMA_getDisplayValueForForeignTableColumn($where_comparison,
+ $relation_field_value, $map, $relation_field
+) {
+ $display_field = PMA_getDisplayField(
+ $map[$relation_field]['foreign_db'],
+ $map[$relation_field]['foreign_table']
+ );
+ // Field to display from the foreign table?
+ if (isset($display_field) && strlen($display_field)) {
+ $dispsql = 'SELECT ' . PMA_Util::backquote($display_field)
+ . ' FROM ' . PMA_Util::backquote($map[$relation_field]['foreign_db'])
+ . '.' . PMA_Util::backquote($map[$relation_field]['foreign_table'])
+ . ' WHERE ' . PMA_Util::backquote($map[$relation_field]['foreign_field'])
+ . $where_comparison;
+ $dispresult = $GLOBALS['dbi']->tryQuery(
+ $dispsql, null, PMA_DatabaseInterface::QUERY_STORE
+ );
+ if ($dispresult && $GLOBALS['dbi']->numRows($dispresult) > 0) {
+ list($dispval) = $GLOBALS['dbi']->fetchRow($dispresult, 0);
+ }
+ @$GLOBALS['dbi']->freeResult($dispresult);
+ return $dispval;
+ }
+ return '';
+}
+
+/**
+ * Display option in the cell according to user choises
+ *
+ * @param array $map all Relations to foreign tables for a given
+ * table or optionally a given column in a table
+ * @param string $relation_field relation field
+ * @param string $where_comparison string that contain relation field value
+ * @param string $dispval display value from the foreign table
+ * @param string $relation_field_value relation field value
+ *
+ * @return string $output HTML <a> tag
+ */
+function PMA_getLinkForRelationalDisplayField($map, $relation_field,
+ $where_comparison, $dispval, $relation_field_value
+) {
+ if ('K' == $_SESSION['tmpval']['relational_display']) {
+ // user chose "relational key" in the display options, so
+ // the title contains the display field
+ $title = (! empty($dispval))
+ ? ' title="' . htmlspecialchars($dispval) . '"'
+ : '';
+ } else {
+ $title = ' title="' . htmlspecialchars($relation_field_value) . '"';
+ }
+ $_url_params = array(
+ 'db' => $map[$relation_field]['foreign_db'],
+ 'table' => $map[$relation_field]['foreign_table'],
+ 'pos' => '0',
+ 'sql_query' => 'SELECT * FROM '
+ . PMA_Util::backquote($map[$relation_field]['foreign_db'])
+ . '.' . PMA_Util::backquote($map[$relation_field]['foreign_table'])
+ . ' WHERE ' . PMA_Util::backquote($map[$relation_field]['foreign_field'])
+ . $where_comparison
+ );
+ $output = '<a href="sql.php'
+ . PMA_URL_getCommon($_url_params) . '"' . $title . '>';
+
+ if ('D' == $_SESSION['tmpval']['relational_display']) {
+ // user chose "relational display field" in the
+ // display options, so show display field in the cell
+ $output .= (!empty($dispval)) ? htmlspecialchars($dispval) : '';
+ } else {
+ // otherwise display data in the cell
+ $output .= htmlspecialchars($relation_field_value);
+ }
+ $output .= '</a>';
+ return $output;
+}
+
+/**
+ * Transform edited values
+ *
+ * @param string $db db name
+ * @param string $table table name
+ * @param array $transformation mimetypes for all columns of a table
+ * [field_name][field_key]
+ * @param array $edited_values transform columns list and new values
+ * @param string $file file containing the transformation plugin
+ * @param string $column_name column name
+ * @param array $extra_data extra data array
+ *
+ * @return array $extra_data
+ */
+function PMA_transformEditedValues($db, $table,
+ $transformation, $edited_values, $file, $column_name, $extra_data
+) {
+ foreach ($edited_values as $cell_index => $curr_cell_edited_values) {
+ if (isset($curr_cell_edited_values[$column_name])) {
+ $column_data = $curr_cell_edited_values[$column_name];
+
+ $_url_params = array(
+ 'db' => $db,
+ 'table' => $table,
+ 'where_clause' => $_REQUEST['where_clause'],
+ 'transform_key' => $column_name
+ );
+
+ $include_file = 'libraries/plugins/transformations/' . $file;
+ if (file_exists($include_file)) {
+ include_once $include_file;
+
+ $transform_options = PMA_Transformation_getOptions(
+ isset($transformation['transformation_options'])
+ ? $transformation['transformation_options']
+ : ''
+ );
+ $transform_options['wrapper_link']
+ = PMA_URL_getCommon($_url_params);
+ $class_name = str_replace('.class.php', '', $file);
+ $plugin_manager = null;
+ $transformation_plugin = new $class_name(
+ $plugin_manager
+ );
+ }
+
+ $extra_data['transformations'][$cell_index]
+ = $transformation_plugin->applyTransformation(
+ $column_data,
+ $transform_options,
+ ''
+ );
+ }
+ } // end of loop for each transformation cell
+ return $extra_data;
+}
+
+/**
+ * Get current value in multi edit mode
+ *
+ * @param array $multi_edit_colummns multiple edit column array
+ * @param array $multi_edit_columns_name multiple edit columns name array
+ * @param array $multi_edit_funcs multiple edit functions array
+ * @param array $multi_edit_salt multiple edit array with encryption salt
+ * @param array $gis_from_text_functions array that contains gis from text functions
+ * @param string $current_value current value in the column
+ * @param array $gis_from_wkb_functions initialy $val is $multi_edit_colummns[$key]
+ * @param array $func_optional_param array('RAND','UNIX_TIMESTAMP')
+ * @param array $func_no_param array of set of string
+ * @param string $key an md5 of the column name
+ *
+ * @return array $cur_value
+ */
+function PMA_getCurrentValueAsAnArrayForMultipleEdit($multi_edit_colummns,
+ $multi_edit_columns_name, $multi_edit_funcs, $multi_edit_salt,
+ $gis_from_text_functions, $current_value, $gis_from_wkb_functions,
+ $func_optional_param, $func_no_param, $key
+) {
+ if (empty($multi_edit_funcs[$key])) {
+ return $current_value;
+ } elseif ('UUID' === $multi_edit_funcs[$key]) {
+ /* This way user will know what UUID new row has */
+ $uuid = $GLOBALS['dbi']->fetchValue('SELECT UUID()');
+ return "'" . $uuid . "'";
+ } elseif ((in_array($multi_edit_funcs[$key], $gis_from_text_functions)
+ && substr($current_value, 0, 3) == "'''")
+ || in_array($multi_edit_funcs[$key], $gis_from_wkb_functions)
+ ) {
+ // Remove enclosing apostrophes
+ $current_value = substr($current_value, 1, strlen($current_value) - 2);
+ // Remove escaping apostrophes
+ $current_value = str_replace("''", "'", $current_value);
+ return $multi_edit_funcs[$key] . '(' . $current_value . ')';
+ } elseif (! in_array($multi_edit_funcs[$key], $func_no_param)
+ || ($current_value != "''"
+ && in_array($multi_edit_funcs[$key], $func_optional_param))
+ ) {
+ if (isset($multi_edit_salt[$key])
+ && ($multi_edit_funcs[$key] == "AES_ENCRYPT")
+ ) {
+ return $multi_edit_funcs[$key] . '(' . $current_value . ",'"
+ . PMA_Util::sqlAddSlashes($multi_edit_salt[$key]) . "')";
+ } else {
+ return $multi_edit_funcs[$key] . '(' . $current_value . ')';
+ }
+ } else {
+ return $multi_edit_funcs[$key] . '()';
+ }
+}
+
+/**
+ * Get query values array and query fields array for insert and update in multi edit
+ *
+ * @param array $multi_edit_columns_name multiple edit columns name array
+ * @param array $multi_edit_columns_null multiple edit columns null array
+ * @param string $current_value current value in the column in loop
+ * @param array $multi_edit_columns_prev multiple edit previous columns array
+ * @param array $multi_edit_funcs multiple edit functions array
+ * @param boolean $is_insert boolean value whether insert or not
+ * @param array $query_values SET part of the sql query
+ * @param array $query_fields array of query fields
+ * @param string $current_value_as_an_array current value in the column
+ * as an array
+ * @param array $value_sets array of valu sets
+ * @param string $key an md5 of the column name
+ * @param array $multi_edit_columns_null_prev array of multiple edit columns
+ * null previous
+ *
+ * @return array ($query_values, $query_fields)
+ */
+function PMA_getQueryValuesForInsertAndUpdateInMultipleEdit($multi_edit_columns_name,
+ $multi_edit_columns_null, $current_value, $multi_edit_columns_prev,
+ $multi_edit_funcs,$is_insert, $query_values, $query_fields,
+ $current_value_as_an_array, $value_sets, $key, $multi_edit_columns_null_prev
+) {
+ // i n s e r t
+ if ($is_insert) {
+ // no need to add column into the valuelist
+ if (strlen($current_value_as_an_array)) {
+ $query_values[] = $current_value_as_an_array;
+ // first inserted row so prepare the list of fields
+ if (empty($value_sets)) {
+ $query_fields[] = PMA_Util::backquote(
+ $multi_edit_columns_name[$key]
+ );
+ }
+ }
+
+ } elseif (! empty($multi_edit_columns_null_prev[$key])
+ && ! isset($multi_edit_columns_null[$key])
+ ) {
+ // u p d a t e
+
+ // field had the null checkbox before the update
+ // field no longer has the null checkbox
+ $query_values[]
+ = PMA_Util::backquote($multi_edit_columns_name[$key])
+ . ' = ' . $current_value_as_an_array;
+ } elseif (empty($multi_edit_funcs[$key])
+ && isset($multi_edit_columns_prev[$key])
+ && ("'" . PMA_Util::sqlAddSlashes($multi_edit_columns_prev[$key]) . "'"
+ == $current_value)
+ ) {
+ // No change for this column and no MySQL function is used -> next column
+ } elseif (! empty($current_value)) {
+ // avoid setting a field to NULL when it's already NULL
+ // (field had the null checkbox before the update
+ // field still has the null checkbox)
+ if (empty($multi_edit_columns_null_prev[$key])
+ || empty($multi_edit_columns_null[$key])
+ ) {
+ $query_values[]
+ = PMA_Util::backquote($multi_edit_columns_name[$key])
+ . ' = ' . $current_value_as_an_array;
+ }
+ }
+ return array($query_values, $query_fields);
+}
+
+/**
+ * Get the current column value in the form for different data types
+ *
+ * @param string $possibly_uploaded_val uploaded file content
+ * @param string $key an md5 of the column name
+ * @param array $multi_edit_columns_type array of multi edit column types
+ * @param string $current_value current column value in the form
+ * @param array $multi_edit_auto_increment multi edit auto increment
+ * @param string $rownumber index of where clause array
+ * @param array $multi_edit_columns_name multi edit column names array
+ * @param array $multi_edit_columns_null multi edit columns null array
+ * @param array $multi_edit_columns_null_prev multi edit columns previous null
+ * @param boolean $is_insert whether insert or not
+ * @param boolean $using_key whether editing or new row
+ * @param array $where_clause where clauses
+ * @param string $table table name
+ *
+ * @return string $current_value current column value in the form
+ */
+function PMA_getCurrentValueForDifferentTypes($possibly_uploaded_val, $key,
+ $multi_edit_columns_type, $current_value, $multi_edit_auto_increment,
+ $rownumber, $multi_edit_columns_name, $multi_edit_columns_null,
+ $multi_edit_columns_null_prev, $is_insert, $using_key, $where_clause, $table
+) {
+ // Fetch the current values of a row to use in case we have a protected field
+ if ($is_insert
+ && $using_key && isset($multi_edit_columns_type)
+ && is_array($multi_edit_columns_type) && isset($where_clause)
+ ) {
+ $protected_row = $GLOBALS['dbi']->fetchSingleRow(
+ 'SELECT * FROM ' . PMA_Util::backquote($table)
+ . ' WHERE ' . $where_clause . ';'
+ );
+ }
+
+ if (false !== $possibly_uploaded_val) {
+ $current_value = $possibly_uploaded_val;
+ } else {
+ // c o l u m n v a l u e i n t h e f o r m
+ if (isset($multi_edit_columns_type[$key])) {
+ $type = $multi_edit_columns_type[$key];
+ } else {
+ $type = '';
+ }
+
+ if ($type != 'protected' && $type != 'set' && 0 === strlen($current_value)) {
+ // best way to avoid problems in strict mode
+ // (works also in non-strict mode)
+ if (isset($multi_edit_auto_increment)
+ && isset($multi_edit_auto_increment[$key])
+ ) {
+ $current_value = 'NULL';
+ } else {
+ $current_value = "''";
+ }
+ } elseif ($type == 'set') {
+ if (! empty($_REQUEST['fields']['multi_edit'][$rownumber][$key])) {
+ $current_value = implode(
+ ',', $_REQUEST['fields']['multi_edit'][$rownumber][$key]
+ );
+ $current_value = "'" . PMA_Util::sqlAddSlashes($current_value) . "'";
+ } else {
+ $current_value = "''";
+ }
+ } elseif ($type == 'protected') {
+ // here we are in protected mode (asked in the config)
+ // so tbl_change has put this special value in the
+ // coulmns array, so we do not change the column value
+ // but we can still handle column upload
+
+ // when in UPDATE mode, do not alter field's contents. When in INSERT
+ // mode, insert empty field because no values were submitted.
+ // If protected blobs where set, insert original fields content.
+ if (! empty($protected_row[$multi_edit_columns_name[$key]])) {
+ $current_value = '0x'
+ . bin2hex($protected_row[$multi_edit_columns_name[$key]]);
+ } else {
+ $current_value = '';
+ }
+ } elseif ($type == 'bit') {
+ $current_value = preg_replace('/[^01]/', '0', $current_value);
+ $current_value = "b'" . PMA_Util::sqlAddSlashes($current_value) . "'";
+ } elseif (! ($type == 'datetime' || $type == 'timestamp')
+ || $current_value != 'CURRENT_TIMESTAMP'
+ ) {
+ $current_value = "'" . PMA_Util::sqlAddSlashes($current_value) . "'";
+ }
+
+ // Was the Null checkbox checked for this field?
+ // (if there is a value, we ignore the Null checkbox: this could
+ // be possible if Javascript is disabled in the browser)
+ if (! empty($multi_edit_columns_null[$key])
+ && ($current_value == "''" || $current_value == '')
+ ) {
+ $current_value = 'NULL';
+ }
+
+ // The Null checkbox was unchecked for this field
+ if (empty($current_value)
+ && ! empty($multi_edit_columns_null_prev[$key])
+ && ! isset($multi_edit_columns_null[$key])
+ ) {
+ $current_value = "''";
+ }
+ } // end else (column value in the form)
+ return $current_value;
+}
+
+
+/**
+ * Check whether inline edited value can be truncated or not,
+ * and add additional parameters for extra_data array if needed
+ *
+ * @param string $db Database name
+ * @param string $table Table name
+ * @param string $column_name Column name
+ * @param array &$extra_data Extra data for ajax response
+ *
+ * @return void
+ */
+function PMA_verifyWhetherValueCanBeTruncatedAndAppendExtraData(
+ $db, $table, $column_name, &$extra_data
+) {
+
+ $extra_data['isNeedToRecheck'] = true;
+
+ $sql_for_real_value = 'SELECT '. PMA_Util::backquote($table) . '.'
+ . PMA_Util::backquote($column_name)
+ . ' FROM ' . PMA_Util::backquote($db) . '.'
+ . PMA_Util::backquote($table)
+ . ' WHERE ' . $_REQUEST['where_clause'][0];
+
+ $result = $GLOBALS['dbi']->tryQuery($sql_for_real_value);
+ $fields_meta = $GLOBALS['dbi']->getFieldsMeta($result);
+ $meta = $fields_meta[0];
+ $new_value = $GLOBALS['dbi']->fetchValue($result);
+ if ($new_value !== false) {
+ if ((substr($meta->type, 0, 9) == 'timestamp')
+ || ($meta->type == 'datetime')
+ || ($meta->type == 'time')
+ ) {
+ $new_value = PMA_Util::addMicroseconds($new_value);
+ }
+ $extra_data['truncatableFieldValue'] = $new_value;
+ } else {
+ $extra_data['isNeedToRecheck'] = false;
+ }
+
+}
+
+/**
+ * Function to get the columns of a table
+ *
+ * @param string $db current db
+ * @param string $table current table
+ *
+ * @return array
+ */
+function PMA_getTableColumns($db, $table)
+{
+ $GLOBALS['dbi']->selectDb($db);
+ return array_values($GLOBALS['dbi']->getColumns($db, $table));
+
+}
+
+/**
+ * Function to determine Insert/Edit rows
+ *
+ * @param string $where_clause where clause
+ * @param string $db current database
+ * @param string $table current table
+ *
+ * @return mixed
+ */
+function PMA_determineInsertOrEdit($where_clause, $db, $table)
+{
+ if (isset($_REQUEST['where_clause'])) {
+ $where_clause = $_REQUEST['where_clause'];
+ }
+ if (isset($_SESSION['edit_next'])) {
+ $where_clause = $_SESSION['edit_next'];
+ unset($_SESSION['edit_next']);
+ $after_insert = 'edit_next';
+ }
+ if (isset($_REQUEST['ShowFunctionFields'])) {
+ $GLOBALS['cfg']['ShowFunctionFields'] = $_REQUEST['ShowFunctionFields'];
+ }
+ if (isset($_REQUEST['ShowFieldTypesInDataEditView'])) {
+ $GLOBALS['cfg']['ShowFieldTypesInDataEditView']
+ = $_REQUEST['ShowFieldTypesInDataEditView'];
+ }
+ if (isset($_REQUEST['after_insert'])) {
+ $after_insert = $_REQUEST['after_insert'];
+ }
+
+ if (isset($where_clause)) {
+ // we are editing
+ $insert_mode = false;
+ $where_clause_array = PMA_getWhereClauseArray($where_clause);
+ list($where_clauses, $result, $rows, $found_unique_key)
+ = PMA_analyzeWhereClauses(
+ $where_clause_array, $table, $db
+ );
+ } else {
+ // we are inserting
+ $insert_mode = true;
+ $where_clause = null;
+ list($result, $rows) = PMA_loadFirstRow($table, $db);
+ $where_clauses = null;
+ $where_clause_array = null;
+ $found_unique_key = false;
+ }
+
+ // Copying a row - fetched data will be inserted as a new row,
+ // therefore the where clause is needless.
+ if (isset($_REQUEST['default_action'])
+ && $_REQUEST['default_action'] === 'insert'
+ ) {
+ $where_clause = $where_clauses = null;
+ }
+
+ return array(
+ $insert_mode, $where_clause, $where_clause_array, $where_clauses,
+ $result, $rows, $found_unique_key,
+ isset($after_insert) ? $after_insert : null
+ );
+}
+
+/**
+ * Function to get comments for the table columns
+ *
+ * @param string $db current database
+ * @param string $table current table
+ *
+ * @return array $comments_map comments for columns
+ */
+function PMA_getCommentsMap($db, $table)
+{
+ /**
+ * get table information
+ * @todo should be done by a Table object
+ */
+ include 'libraries/tbl_info.inc.php';
+
+ /**
+ * Get comments for table fields/columns
+ */
+ $comments_map = array();
+
+ if ($GLOBALS['cfg']['ShowPropertyComments']) {
+ $comments_map = PMA_getComments($db, $table);
+ }
+
+ return $comments_map;
+}
+
+/**
+ * Function to get URL parameters
+ *
+ * @param string $db current database
+ * @param string $table current table
+ *
+ * @return array $url_params url parameters
+ */
+function PMA_getUrlParameters($db, $table)
+{
+ /**
+ * @todo check if we could replace by "db_|tbl_" - please clarify!?
+ */
+ $url_params = array(
+ 'db' => $db,
+ 'sql_query' => $_REQUEST['sql_query']
+ );
+
+ if (preg_match('@^tbl_@', $GLOBALS['goto'])) {
+ $url_params['table'] = $table;
+ }
+
+ return $url_params;
+}
+
+/**
+ * Function to get html for the gis editor div
+ *
+ * @return string
+ */
+function PMA_getHtmlForGisEditor()
+{
+ return '<div id="gis_editor"></div>'
+ . '<div id="popup_background"></div>'
+ . '<br />';
+}
+
+/**
+ * Function to get html for the ignore option in insert mode
+ *
+ * @param int $row_id row id
+ *
+ * @return string
+ */
+function PMA_getHtmlForIgnoreOption($row_id)
+{
+ return '<input type="checkbox" checked="checked"'
+ . ' name="insert_ignore_' . $row_id . '"'
+ . ' id="insert_ignore_' . $row_id . '" />'
+ .'<label for="insert_ignore_' . $row_id . '">'
+ . __('Ignore')
+ . '</label><br />' . "\n";
+}
+
+/**
+ * Function to get html for the function option
+ *
+ * @param bool $odd_row whether odd row or not
+ * @param array $column column
+ * @param string $column_name_appendix column name appendix
+ *
+ * @return String
+ */
+function PMA_getHtmlForFunctionOption($odd_row, $column, $column_name_appendix)
+{
+ $longDoubleTextArea = $GLOBALS['cfg']['LongtextDoubleTextarea'];
+ return '<tr class="noclick ' . ($odd_row ? 'odd' : 'even' ) . '">'
+ . '<td '
+ . ($longDoubleTextArea && strstr($column['True_Type'], 'longtext')
+ ? 'rowspan="2"'
+ : ''
+ )
+ . 'class="center">'
+ . $column['Field_title']
+ . '<input type="hidden" name="fields_name' . $column_name_appendix
+ . '" value="' . $column['Field_html'] . '"/>'
+ . '</td>';
+
+}
+
+/**
+ * Function to get html for the column type
+ *
+ * @param array $column column
+ *
+ * @return string
+ */
+function PMA_getHtmlForInsertEditColumnType($column)
+{
+ return '<td class="center' . $column['wrap'] . '">'
+ . '<span class="column_type">' . $column['pma_type'] . '</span>'
+ . '</td>';
+
+}
+
+/**
+ * Function to get html for the insert edit form header
+ *
+ * @param bool $has_blob_field whether has blob field
+ * @param bool $is_upload whether is upload
+ *
+ * @return string
+ */
+function PMA_getHtmlForInsertEditFormHeader($has_blob_field, $is_upload)
+{
+ $html_output ='<form id="insertForm" ';
+ if ($has_blob_field && $is_upload) {
+ $html_output .='class="disableAjax" ';
+ }
+ $html_output .='method="post" action="tbl_replace.php" name="insertForm" ';
+ if ($is_upload) {
+ $html_output .= ' enctype="multipart/form-data"';
+ }
+ $html_output .= '>';
+
+ return $html_output;
+}
+
+/**
+ * Function to get html for each insert/edit column
+ *
+ * @param array $table_columns table columns
+ * @param int $i row counter
+ * @param array $column column
+ * @param array $comments_map comments map
+ * @param bool $timestamp_seen whether timestamp seen
+ * @param array $current_result current result
+ * @param string $chg_evt_handler javascript change event handler
+ * @param string $jsvkey javascript validation key
+ * @param string $vkey validation key
+ * @param bool $insert_mode whether insert mode
+ * @param array $current_row current row
+ * @param bool $odd_row whether odd row
+ * @param int &$o_rows row offset
+ * @param int &$tabindex tab index
+ * @param int $columns_cnt columns count
+ * @param bool $is_upload whether upload
+ * @param int $tabindex_for_function tab index offset for function
+ * @param array $foreigners foreigners
+ * @param int $tabindex_for_null tab index offset for null
+ * @param int $tabindex_for_value tab index offset for value
+ * @param string $table table
+ * @param string $db database
+ * @param int $row_id row id
+ * @param array $titles titles
+ * @param int $biggest_max_file_size biggest max file size
+ * @param string $default_char_editing default char editing mode which is stroe
+ * in the config.inc.php script
+ * @param string $text_dir text direction
+ *
+ * @return string
+ */
+function PMA_getHtmlForInsertEditFormColumn($table_columns, $i, $column,
+ $comments_map, $timestamp_seen, $current_result, $chg_evt_handler,
+ $jsvkey, $vkey, $insert_mode, $current_row, $odd_row, &$o_rows,
+ &$tabindex, $columns_cnt, $is_upload, $tabindex_for_function,
+ $foreigners, $tabindex_for_null, $tabindex_for_value,
+ $table, $db, $row_id, $titles, $biggest_max_file_size,
+ $default_char_editing, $text_dir
+) {
+ if (! isset($table_columns[$i]['processed'])) {
+ $column = $table_columns[$i];
+ $column = PMA_analyzeTableColumnsArray(
+ $column, $comments_map, $timestamp_seen
+ );
+ }
+
+ $extracted_columnspec
+ = PMA_Util::extractColumnSpec($column['Type']);
+
+ if (-1 === $column['len']) {
+ $column['len'] = $GLOBALS['dbi']->fieldLen($current_result, $i);
+ // length is unknown for geometry fields,
+ // make enough space to edit very simple WKTs
+ if (-1 === $column['len']) {
+ $column['len'] = 30;
+ }
+ }
+ //Call validation when the form submitted...
+ $unnullify_trigger = $chg_evt_handler
+ . "=\"return verificationsAfterFieldChange('"
+ . PMA_escapeJsString($column['Field_md5']) . "', '"
+ . PMA_escapeJsString($jsvkey) . "','".$column['pma_type'] . "')\"";
+
+ // Use an MD5 as an array index to avoid having special characters
+ // in the name atttibute (see bug #1746964 )
+ $column_name_appendix = $vkey . '[' . $column['Field_md5'] . ']';
+
+ if ($column['Type'] == 'datetime'
+ && ! isset($column['Default'])
+ && ! is_null($column['Default'])
+ && ($insert_mode || ! isset($current_row[$column['Field']]))
+ ) {
+ // INSERT case or
+ // UPDATE case with an NULL value
+ $current_row[$column['Field']] = date('Y-m-d H:i:s', time());
+ }
+
+ $html_output = PMA_getHtmlForFunctionOption(
+ $odd_row, $column, $column_name_appendix
+ );
+
+ if ($GLOBALS['cfg']['ShowFieldTypesInDataEditView']) {
+ $html_output .= PMA_getHtmlForInsertEditColumnType($column);
+ } //End if
+
+ // Get a list of GIS data types.
+ $gis_data_types = PMA_Util::getGISDatatypes();
+
+ // Prepares the field value
+ $real_null_value = false;
+ $special_chars_encoded = '';
+ if (isset($current_row)) {
+ // (we are editing)
+ list(
+ $real_null_value, $special_chars_encoded, $special_chars,
+ $data, $backup_field
+ )
+ = PMA_getSpecialCharsAndBackupFieldForExistingRow(
+ $current_row, $column, $extracted_columnspec,
+ $real_null_value, $gis_data_types, $column_name_appendix
+ );
+ } else {
+ // (we are inserting)
+ // display default values
+ list($real_null_value, $data, $special_chars, $backup_field,
+ $special_chars_encoded
+ )
+ = PMA_getSpecialCharsAndBackupFieldForInsertingMode(
+ $column, $real_null_value
+ );
+ }
+
+ $idindex = ($o_rows * $columns_cnt) + $i + 1;
+ $tabindex = $idindex;
+
+ // Get a list of data types that are not yet supported.
+ $no_support_types = PMA_Util::unsupportedDatatypes();
+
+ // The function column
+ // -------------------
+ if ($GLOBALS['cfg']['ShowFunctionFields']) {
+ $html_output .= PMA_getFunctionColumn(
+ $column, $is_upload, $column_name_appendix,
+ $unnullify_trigger, $no_support_types, $tabindex_for_function,
+ $tabindex, $idindex, $insert_mode
+ );
+ }
+
+ // The null column
+ // ---------------
+ $foreignData = PMA_getForeignData(
+ $foreigners, $column['Field'], false, '', ''
+ );
+ $html_output .= PMA_getNullColumn(
+ $column, $column_name_appendix, $real_null_value,
+ $tabindex, $tabindex_for_null, $idindex, $vkey, $foreigners,
+ $foreignData
+ );
+
+ // The value column (depends on type)
+ // ----------------
+ // See bug #1667887 for the reason why we don't use the maxlength
+ // HTML attribute
+
+ //add data attributes "no of decimals" and "data type"
+ $no_decimals=0;
+ $type = current(explode("(", $column['pma_type']));
+ if(preg_match('/\(([^()]+)\)/', $column['pma_type'], $match)){
+ $match[0] = trim($match[0], '()');
+ $no_decimals=$match[0];
+ }
+ $html_output .= '<td' . ' data-type="' . $type . '"' . ' data-decimals="' . $no_decimals . '">' . "\n";
+ // Will be used by js/tbl_change.js to set the default value
+ // for the "Continue insertion" feature
+ $html_output .= '<span class="default_value hide">'
+ . $special_chars . '</span>';
+
+ $html_output .= PMA_getValueColumn(
+ $column, $backup_field, $column_name_appendix, $unnullify_trigger,
+ $tabindex, $tabindex_for_value, $idindex, $data, $special_chars,
+ $foreignData, $odd_row, array($table, $db), $row_id, $titles,
+ $text_dir, $special_chars_encoded, $vkey, $is_upload,
+ $biggest_max_file_size, $default_char_editing,
+ $no_support_types, $gis_data_types, $extracted_columnspec
+ );
+
+ $html_output .= '</td>'
+ . '</tr>';
+
+ return $html_output;
+}
+
+/**
+ * Function to get html for each insert/edit row
+ *
+ * @param array $url_params url parameters
+ * @param array $table_columns table columns
+ * @param array $column column
+ * @param array $comments_map comments map
+ * @param bool $timestamp_seen whether timestamp seen
+ * @param array $current_result current result
+ * @param string $chg_evt_handler javascript change event handler
+ * @param string $jsvkey javascript validation key
+ * @param string $vkey validation key
+ * @param bool $insert_mode whether insert mode
+ * @param array $current_row current row
+ * @param int &$o_rows row offset
+ * @param int &$tabindex tab index
+ * @param int $columns_cnt columns count
+ * @param bool $is_upload whether upload
+ * @param int $tabindex_for_function tab index offset for function
+ * @param array $foreigners foreigners
+ * @param int $tabindex_for_null tab index offset for null
+ * @param int $tabindex_for_value tab index offset for value
+ * @param string $table table
+ * @param string $db database
+ * @param int $row_id row id
+ * @param array $titles titles
+ * @param int $biggest_max_file_size biggest max file size
+ * @param string $text_dir text direction
+ *
+ * @return string
+ */
+function PMA_getHtmlForInsertEditRow($url_params, $table_columns,
+ $column, $comments_map, $timestamp_seen, $current_result, $chg_evt_handler,
+ $jsvkey, $vkey, $insert_mode, $current_row, &$o_rows, &$tabindex, $columns_cnt,
+ $is_upload, $tabindex_for_function, $foreigners, $tabindex_for_null,
+ $tabindex_for_value, $table, $db, $row_id, $titles,
+ $biggest_max_file_size, $text_dir
+) {
+ $html_output = PMA_getHeadAndFootOfInsertRowTable($url_params)
+ . '<tbody>';
+
+ //store the default value for CharEditing
+ $default_char_editing = $GLOBALS['cfg']['CharEditing'];
+
+ $odd_row = true;
+ for ($i = 0; $i < $columns_cnt; $i++) {
+ $html_output .= PMA_getHtmlForInsertEditFormColumn(
+ $table_columns, $i, $column, $comments_map, $timestamp_seen,
+ $current_result, $chg_evt_handler, $jsvkey, $vkey, $insert_mode,
+ $current_row, $odd_row, $o_rows, $tabindex, $columns_cnt, $is_upload,
+ $tabindex_for_function, $foreigners, $tabindex_for_null,
+ $tabindex_for_value, $table, $db, $row_id, $titles,
+ $biggest_max_file_size, $default_char_editing, $text_dir
+ );
+ $odd_row = !$odd_row;
+ } // end for
+ $o_rows++;
+ $html_output .= ' </tbody>'
+ . '</table><br />';
+
+ return $html_output;
+}
+?>
diff --git a/libraries/ip_allow_deny.lib.php b/libraries/ip_allow_deny.lib.php
new file mode 100644
index 0000000000..86a88ed6e2
--- /dev/null
+++ b/libraries/ip_allow_deny.lib.php
@@ -0,0 +1,334 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * This library is used with the server IP allow/deny host authentication
+ * feature
+ *
+ * @package PhpMyAdmin
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ * Gets the "true" IP address of the current user
+ *
+ * @return string the ip of the user
+ *
+ * @access private
+ */
+function PMA_getIp()
+{
+ /* Get the address of user */
+ if (!empty($_SERVER['REMOTE_ADDR'])) {
+ $direct_ip = $_SERVER['REMOTE_ADDR'];
+ } else {
+ /* We do not know remote IP */
+ return false;
+ }
+
+ /* Do we trust this IP as a proxy? If yes we will use it's header. */
+ if (isset($GLOBALS['cfg']['TrustedProxies'][$direct_ip])) {
+ $trusted_header_value
+ = PMA_getenv($GLOBALS['cfg']['TrustedProxies'][$direct_ip]);
+ $matches = array();
+ // the $ checks that the header contains only one IP address,
+ // ?: makes sure the () don't capture
+ $is_ip = preg_match(
+ '|^(?:[0-9]{1,3}\.){3,3}[0-9]{1,3}$|',
+ $trusted_header_value, $matches
+ );
+ if ($is_ip && (count($matches) == 1)) {
+ // True IP behind a proxy
+ return $matches[0];
+ }
+ }
+
+ /* Return true IP */
+ return $direct_ip;
+} // end of the 'PMA_getIp()' function
+
+
+/**
+ * Matches for IPv4 or IPv6 addresses
+ *
+ * @param string $testRange string of IP range to match
+ * @param string $ipToTest string of IP to test against range
+ *
+ * @return boolean whether the IP mask matches
+ *
+ * @access public
+ */
+function PMA_ipMaskTest($testRange, $ipToTest)
+{
+ $result = true;
+
+ if (strpos($testRange, ':') > -1 || strpos($ipToTest, ':') > -1) {
+ // assume IPv6
+ $result = PMA_ipv6MaskTest($testRange, $ipToTest);
+ } else {
+ $result = PMA_ipv4MaskTest($testRange, $ipToTest);
+ }
+
+ return $result;
+} // end of the "PMA_ipMaskTest()" function
+
+
+/**
+ * Based on IP Pattern Matcher
+ * Originally by J.Adams <jna@retina.net>
+ * Found on <http://www.php.net/manual/en/function.ip2long.php>
+ * Modified for phpMyAdmin
+ *
+ * Matches:
+ * xxx.xxx.xxx.xxx (exact)
+ * xxx.xxx.xxx.[yyy-zzz] (range)
+ * xxx.xxx.xxx.xxx/nn (CIDR)
+ *
+ * Does not match:
+ * xxx.xxx.xxx.xx[yyy-zzz] (range, partial octets not supported)
+ *
+ * @param string $testRange string of IP range to match
+ * @param string $ipToTest string of IP to test against range
+ *
+ * @return boolean whether the IP mask matches
+ *
+ * @access public
+ */
+function PMA_ipv4MaskTest($testRange, $ipToTest)
+{
+ $result = true;
+ $match = preg_match(
+ '|([0-9]+)\.([0-9]+)\.([0-9]+)\.([0-9]+)/([0-9]+)|',
+ $testRange,
+ $regs
+ );
+ if ($match) {
+ // performs a mask match
+ $ipl = ip2long($ipToTest);
+ $rangel = ip2long(
+ $regs[1] . '.' . $regs[2] . '.' . $regs[3] . '.' . $regs[4]
+ );
+
+ $maskl = 0;
+
+ for ($i = 0; $i < 31; $i++) {
+ if ($i < $regs[5] - 1) {
+ $maskl = $maskl + PMA_Util::pow(2, (30 - $i));
+ } // end if
+ } // end for
+
+ if (($maskl & $rangel) == ($maskl & $ipl)) {
+ return true;
+ } else {
+ return false;
+ }
+ } else {
+ // range based
+ $maskocts = explode('.', $testRange);
+ $ipocts = explode('.', $ipToTest);
+
+ // perform a range match
+ for ($i = 0; $i < 4; $i++) {
+ if (preg_match('|\[([0-9]+)\-([0-9]+)\]|', $maskocts[$i], $regs)) {
+ if (($ipocts[$i] > $regs[2]) || ($ipocts[$i] < $regs[1])) {
+ $result = false;
+ } // end if
+ } else {
+ if ($maskocts[$i] <> $ipocts[$i]) {
+ $result = false;
+ } // end if
+ } // end if/else
+ } //end for
+ } //end if/else
+
+ return $result;
+} // end of the "PMA_ipv4MaskTest()" function
+
+
+/**
+ * IPv6 matcher
+ * CIDR section taken from http://stackoverflow.com/a/10086404
+ * Modified for phpMyAdmin
+ *
+ * Matches:
+ * xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx
+ * (exact)
+ * xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:[yyyy-zzzz]
+ * (range, only at end of IP - no subnets)
+ * xxxx:xxxx:xxxx:xxxx/nn
+ * (CIDR)
+ *
+ * Does not match:
+ * xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xx[yyy-zzz]
+ * (range, partial octets not supported)
+ *
+ * @param string $test_range string of IP range to match
+ * @param string $ip_to_test string of IP to test against range
+ *
+ * @return boolean whether the IP mask matches
+ *
+ * @access public
+ */
+function PMA_ipv6MaskTest($test_range, $ip_to_test)
+{
+ $result = true;
+
+ // convert to lowercase for easier comparison
+ $test_range = strtolower($test_range);
+ $ip_to_test = strtolower($ip_to_test);
+
+ $is_cidr = strpos($test_range, '/') > -1;
+ $is_range = strpos($test_range, '[') > -1;
+ $is_single = ! $is_cidr && ! $is_range;
+
+ $ip_hex = bin2hex(inet_pton($ip_to_test));
+
+ if ($is_single) {
+ $range_hex = bin2hex(inet_pton($test_range));
+ $result = $ip_hex === $range_hex;
+ } elseif ($is_range) {
+ // what range do we operate on?
+ $range_match = array();
+ $match = preg_match(
+ '/\[([0-9a-f]+)\-([0-9a-f]+)\]/', $test_range, $range_match
+ );
+ if ($match) {
+ $range_start = $range_match[1];
+ $range_end = $range_match[2];
+
+ // get the first and last allowed IPs
+ $first_ip = str_replace($range_match[0], $range_start, $test_range);
+ $first_hex = bin2hex(inet_pton($first_ip));
+ $last_ip = str_replace($range_match[0], $range_end, $test_range);
+ $last_hex = bin2hex(inet_pton($last_ip));
+
+ // check if the IP to test is within the range
+ $result = ($ip_hex >= $first_hex && $ip_hex <= $last_hex);
+ }
+ } elseif ($is_cidr) {
+ // Split in address and prefix length
+ list($first_ip, $subnet) = explode('/', $test_range);
+
+ // Parse the address into a binary string
+ $first_bin = inet_pton($first_ip);
+ $first_hex = bin2hex($first_bin);
+
+ // Overwriting first address string to make sure notation is optimal
+ $first_ip = inet_ntop($first_bin);
+
+ $flexbits = 128 - $subnet;
+
+ // Build the hexadecimal string of the last address
+ $last_hex = $first_hex;
+
+ $pos = 31;
+ while ($flexbits > 0) {
+ // Get the character at this position
+ $orig = substr($last_hex, $pos, 1);
+
+ // Convert it to an integer
+ $origval = hexdec($orig);
+
+ // OR it with (2^flexbits)-1, with flexbits limited to 4 at a time
+ $newval = $origval | (pow(2, min(4, $flexbits)) - 1);
+
+ // Convert it back to a hexadecimal character
+ $new = dechex($newval);
+
+ // And put that character back in the string
+ $last_hex = substr_replace($last_hex, $new, $pos, 1);
+
+ // We processed one nibble, move to previous position
+ $flexbits -= 4;
+ $pos -= 1;
+ }
+
+ // check if the IP to test is within the range
+ $result = ($ip_hex >= $first_hex && $ip_hex <= $last_hex);
+ }
+
+ return $result;
+} // end of the "PMA_ipv6MaskTest()" function
+
+
+/**
+ * Runs through IP Allow/Deny rules the use of it below for more information
+ *
+ * @param string $type 'allow' | 'deny' type of rule to match
+ *
+ * @return bool Matched a rule ?
+ *
+ * @access public
+ *
+ * @see PMA_getIp()
+ */
+function PMA_allowDeny($type)
+{
+ global $cfg;
+
+ // Grabs true IP of the user and returns if it can't be found
+ $remote_ip = PMA_getIp();
+ if (empty($remote_ip)) {
+ return false;
+ }
+
+ // copy username
+ $username = $cfg['Server']['user'];
+
+ // copy rule database
+ $rules = $cfg['Server']['AllowDeny']['rules'];
+
+ // lookup table for some name shortcuts
+ $shortcuts = array(
+ 'all' => '0.0.0.0/0',
+ 'localhost' => '127.0.0.1/8'
+ );
+
+ // Provide some useful shortcuts if server gives us address:
+ if (PMA_getenv('SERVER_ADDR')) {
+ $shortcuts['localnetA'] = PMA_getenv('SERVER_ADDR') . '/8';
+ $shortcuts['localnetB'] = PMA_getenv('SERVER_ADDR') . '/16';
+ $shortcuts['localnetC'] = PMA_getenv('SERVER_ADDR') . '/24';
+ }
+
+ foreach ($rules as $rule) {
+ // extract rule data
+ $rule_data = explode(' ', $rule);
+
+ // check for rule type
+ if ($rule_data[0] != $type) {
+ continue;
+ }
+
+ // check for username
+ if (($rule_data[1] != '%') //wildcarded first
+ && ($rule_data[1] != $username)
+ ) {
+ continue;
+ }
+
+ // check if the config file has the full string with an extra
+ // 'from' in it and if it does, just discard it
+ if ($rule_data[2] == 'from') {
+ $rule_data[2] = $rule_data[3];
+ }
+
+ // Handle shortcuts with above array
+ if (isset($shortcuts[$rule_data[2]])) {
+ $rule_data[2] = $shortcuts[$rule_data[2]];
+ }
+
+ // Add code for host lookups here
+ // Excluded for the moment
+
+ // Do the actual matching now
+ if (PMA_ipMaskTest($rule_data[2], $remote_ip)) {
+ return true;
+ }
+ } // end while
+
+ return false;
+} // end of the "PMA_AllowDeny()" function
+
+?>
diff --git a/libraries/js_escape.lib.php b/libraries/js_escape.lib.php
new file mode 100644
index 0000000000..42d7fed520
--- /dev/null
+++ b/libraries/js_escape.lib.php
@@ -0,0 +1,133 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Javascript escaping functions.
+ *
+ * @package PhpMyAdmin
+ *
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ * Format a string so it can be a string inside JavaScript code inside an
+ * eventhandler (onclick, onchange, on..., ).
+ * This function is used to displays a javascript confirmation box for
+ * "DROP/DELETE/ALTER" queries.
+ *
+ * @param string $a_string the string to format
+ * @param boolean $add_backquotes whether to add backquotes to the string or not
+ *
+ * @return string the formatted string
+ *
+ * @access public
+ */
+function PMA_jsFormat($a_string = '', $add_backquotes = true)
+{
+ if (is_string($a_string)) {
+ $a_string = htmlspecialchars($a_string);
+ $a_string = PMA_escapeJsString($a_string);
+ // Needed for inline javascript to prevent some browsers
+ // treating it as a anchor
+ $a_string = str_replace('#', '\\#', $a_string);
+ }
+
+ return (($add_backquotes) ? PMA_Util::backquote($a_string) : $a_string);
+} // end of the 'PMA_jsFormat()' function
+
+/**
+ * escapes a string to be inserted as string a JavaScript block
+ * enclosed by <![CDATA[ ... ]]>
+ * this requires only to escape ' with \' and end of script block
+ *
+ * We also remove NUL byte as some browsers (namely MSIE) ignore it and
+ * inserting it anywhere inside </script would allow to bypass this check.
+ *
+ * @param string $string the string to be escaped
+ *
+ * @return string the escaped string
+ */
+function PMA_escapeJsString($string)
+{
+ return preg_replace(
+ '@</script@i', '</\' + \'script',
+ strtr(
+ $string,
+ array(
+ "\000" => '',
+ '\\' => '\\\\',
+ '\'' => '\\\'',
+ '"' => '\"',
+ "\n" => '\n',
+ "\r" => '\r'
+ )
+ )
+ );
+}
+
+/**
+ * Formats a value for javascript code.
+ *
+ * @param string $value String to be formatted.
+ *
+ * @return string formatted value.
+ */
+function PMA_formatJsVal($value)
+{
+ if (is_bool($value)) {
+ if ($value) {
+ return 'true';
+ } else {
+ return 'false';
+ }
+ } elseif (is_int($value)) {
+ return (int)$value;
+ } else {
+ return '"' . PMA_escapeJsString($value) . '"';
+ }
+}
+
+/**
+ * Formats an javascript assignment with proper escaping of a value
+ * and support for assigning array of strings.
+ *
+ * @param string $key Name of value to set
+ * @param mixed $value Value to set, can be either string or array of strings
+ * @param bool $escape Whether to escape value or keep it as it is
+ * (for inclusion of js code)
+ *
+ * @return string Javascript code.
+ */
+function PMA_getJsValue($key, $value, $escape = true)
+{
+ $result = $key . ' = ';
+ if (!$escape) {
+ $result .= $value;
+ } elseif (is_array($value)) {
+ $result .= '[';
+ foreach ($value as $val) {
+ $result .= PMA_formatJsVal($val) . ",";
+ }
+ $result .= "];\n";
+ } else {
+ $result .= PMA_formatJsVal($value) . ";\n";
+ }
+ return $result;
+}
+
+/**
+ * Prints an javascript assignment with proper escaping of a value
+ * and support for assigning array of strings.
+ *
+ * @param string $key Name of value to set
+ * @param mixed $value Value to set, can be either string or array of strings
+ *
+ * @return void
+ */
+function PMA_printJsValue($key, $value)
+{
+ echo PMA_getJsValue($key, $value);
+}
+
+?>
diff --git a/libraries/kanji-encoding.lib.php b/libraries/kanji-encoding.lib.php
new file mode 100644
index 0000000000..0495112b79
--- /dev/null
+++ b/libraries/kanji-encoding.lib.php
@@ -0,0 +1,168 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Set of functions for kanji-encoding convert (available only with japanese
+ * language)
+ *
+ * PHP4 configure requirements:
+ * --enable-mbstring --enable-mbstr-enc-trans --enable-mbregex
+ *
+ * 2002/2/22 - by Yukihiro Kawada <kawada@den.fujifilm.co.jp>
+ *
+ * @package PhpMyAdmin
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ * Gets the php internal encoding codes and sets the available encoding
+ * codes list
+ * 2002/1/4 by Y.Kawada
+ *
+ * @global string $kanji_encoding_list the available encoding codes list
+ *
+ * @return boolean always true
+ */
+function PMA_Kanji_checkEncoding()
+{
+ global $kanji_encoding_list;
+
+ $internal_enc = mb_internal_encoding();
+ if ($internal_enc == 'EUC-JP') {
+ $kanji_encoding_list = 'ASCII,EUC-JP,SJIS,JIS';
+ } else {
+ $kanji_encoding_list = 'ASCII,SJIS,EUC-JP,JIS';
+ }
+
+ return true;
+} // end of the 'PMA_Kanji_checkEncoding' function
+
+
+/**
+ * Reverses SJIS & EUC-JP position in the encoding codes list
+ * 2002/1/4 by Y.Kawada
+ *
+ * @global string $kanji_encoding_list the available encoding codes list
+ *
+ * @return boolean always true
+ */
+function PMA_Kanji_changeOrder()
+{
+ global $kanji_encoding_list;
+
+ $parts = explode(',', $kanji_encoding_list);
+ if ($parts[1] == 'EUC-JP') {
+ $kanji_encoding_list = 'ASCII,SJIS,EUC-JP,JIS';
+ } else {
+ $kanji_encoding_list = 'ASCII,EUC-JP,SJIS,JIS';
+ }
+
+ return true;
+} // end of the 'PMA_Kanji_changeOrder' function
+
+
+/**
+ * Kanji string encoding convert
+ * 2002/1/4 by Y.Kawada
+ *
+ * @param string $str the string to convert
+ * @param string $enc the destination encoding code
+ * @param string $kana set 'kana' convert to JIS-X208-kana
+ *
+ * @global string $kanji_encoding_list the available encoding codes list
+ *
+ * @return string the converted string
+ */
+function PMA_Kanji_strConv($str, $enc, $kana)
+{
+ global $kanji_encoding_list;
+
+ if ($enc == '' && $kana == '') {
+ return $str;
+ }
+ $string_encoding = mb_detect_encoding($str, $kanji_encoding_list);
+
+ if ($kana == 'kana') {
+ $dist = mb_convert_kana($str, 'KV', $string_encoding);
+ $str = $dist;
+ }
+ if ($string_encoding != $enc && $enc != '') {
+ $dist = mb_convert_encoding($str, $enc, $string_encoding);
+ } else {
+ $dist = $str;
+ }
+ return $dist;
+} // end of the 'PMA_Kanji_strConv' function
+
+
+/**
+ * Kanji file encoding convert
+ * 2002/1/4 by Y.Kawada
+ *
+ * @param string $file the name of the file to convert
+ * @param string $enc the destination encoding code
+ * @param string $kana set 'kana' convert to JIS-X208-kana
+ *
+ * @return string the name of the converted file
+ */
+function PMA_Kanji_fileConv($file, $enc, $kana)
+{
+ if ($enc == '' && $kana == '') {
+ return $file;
+ }
+
+ $tmpfname = tempnam('', $enc);
+ $fpd = fopen($tmpfname, 'wb');
+ $fps = fopen($file, 'r');
+ PMA_Kanji_changeOrder();
+ while (!feof($fps)) {
+ $line = fgets($fps, 4096);
+ $dist = PMA_Kanji_strConv($line, $enc, $kana);
+ fputs($fpd, $dist);
+ } // end while
+ PMA_Kanji_changeOrder();
+ fclose($fps);
+ fclose($fpd);
+ unlink($file);
+
+ return $tmpfname;
+} // end of the 'PMA_Kanji_fileConv' function
+
+
+/**
+ * Defines radio form fields to switch between encoding modes
+ * 2002/1/4 by Y.Kawada
+ *
+ * @return string xhtml code for the radio controls
+ */
+function PMA_Kanji_encodingForm()
+{
+ return "\n"
+ . '<ul>' . "\n" . '<li>'
+ . '<input type="radio" name="knjenc" value="" checked="checked" '
+ . 'id="kj-none" />'
+ . '<label for="kj-none">'
+ /* l10n: This is currently used only in Japanese locales */
+ . _pgettext('None encoding conversion', 'None')
+ . "</label>\n"
+ . '<input type="radio" name="knjenc" value="EUC-JP" id="kj-euc" />'
+ . '<label for="kj-euc">EUC</label>' . "\n"
+ . '<input type="radio" name="knjenc" value="SJIS" id="kj-sjis" />'
+ . '<label for="kj-sjis">SJIS</label>' . "\n"
+ . '</li>' . "\n" . '<li>'
+ . '<input type="checkbox" name="xkana" value="kana" id="kj-kana" />'
+ . "\n"
+ . '<label for="kj-kana">'
+ /* l10n: This is currently used only in Japanese locales */
+ . __('Convert to Kana')
+ . '</label><br />'
+ . "\n"
+ . '</li>' . "\n" . '</ul>'
+ ;
+} // end of the 'PMA_Kanji_encodingForm' function
+
+
+PMA_Kanji_checkEncoding();
+
+?>
diff --git a/libraries/language_stats.inc.php b/libraries/language_stats.inc.php
new file mode 100644
index 0000000000..c6c246808c
--- /dev/null
+++ b/libraries/language_stats.inc.php
@@ -0,0 +1,80 @@
+<?php
+/* Automatically generated file, do not edit! */
+/* Generated by scripts/remove-incomplete-mo */
+
+$GLOBALS["language_stats"] = array (
+ 'af' => 12,
+ 'ar' => 45,
+ 'az' => 15,
+ 'be@latin' => 31,
+ 'be' => 31,
+ 'bg' => 71,
+ 'bn' => 98,
+ 'br' => 24,
+ 'bs' => 16,
+ 'ca' => 93,
+ 'ckb' => 19,
+ 'cs' => 100,
+ 'cy' => 23,
+ 'da' => 99,
+ 'de' => 100,
+ 'el' => 100,
+ 'en_GB' => 99,
+ 'es' => 100,
+ 'et' => 100,
+ 'eu' => 20,
+ 'fa' => 34,
+ 'fi' => 70,
+ 'fr' => 100,
+ 'gl' => 96,
+ 'he' => 22,
+ 'hi' => 52,
+ 'hr' => 38,
+ 'hu' => 98,
+ 'hy' => 3,
+ 'ia' => 41,
+ 'id' => 80,
+ 'it' => 89,
+ 'ja' => 89,
+ 'ka' => 19,
+ 'kk' => 12,
+ 'kn' => 0,
+ 'ko' => 77,
+ 'ky' => 2,
+ 'lt' => 61,
+ 'lv' => 25,
+ 'mk' => 21,
+ 'ml' => 1,
+ 'mn' => 26,
+ 'ms' => 13,
+ 'nb' => 66,
+ 'nl' => 100,
+ 'pa' => 0,
+ 'pl' => 92,
+ 'pt_BR' => 100,
+ 'pt' => 61,
+ 'ro' => 59,
+ 'ru' => 95,
+ 'si' => 78,
+ 'sk' => 83,
+ 'sl' => 100,
+ 'sq' => 29,
+ 'sr@latin' => 62,
+ 'sr' => 29,
+ 'sv' => 99,
+ 'ta' => 15,
+ 'te' => 12,
+ 'th' => 42,
+ 'tk' => 0,
+ 'tr' => 100,
+ 'tt' => 21,
+ 'ug' => 13,
+ 'uk' => 94,
+ 'ur' => 27,
+ 'uz@latin' => 44,
+ 'uz' => 45,
+ 'vls' => 0,
+ 'zh_CN' => 95,
+ 'zh_TW' => 100,
+);
+?>
diff --git a/libraries/logging.lib.php b/libraries/logging.lib.php
new file mode 100644
index 0000000000..975fc5a0c3
--- /dev/null
+++ b/libraries/logging.lib.php
@@ -0,0 +1,30 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Logging functionality for webserver.
+ *
+ * This includes web server specific code to log some information.
+ *
+ * @package PhpMyAdmin
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ * Logs user information to webserver logs.
+ *
+ * @param string $user user name
+ * @param string $status status message
+ *
+ * @return void
+ */
+function PMA_logUser($user, $status = 'ok')
+{
+ if (function_exists('apache_note')) {
+ apache_note('userID', $user);
+ apache_note('userStatus', $status);
+ }
+}
+
+?>
diff --git a/libraries/mime.lib.php b/libraries/mime.lib.php
new file mode 100644
index 0000000000..399b36d859
--- /dev/null
+++ b/libraries/mime.lib.php
@@ -0,0 +1,34 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * MIME detection code.
+ *
+ * @package PhpMyAdmin
+ * @todo Maybe we could try to use fileinfo module if loaded
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ * Tries to detect MIME type of content.
+ *
+ * @param string &$test First few bytes of content to use for detection
+ *
+ * @return string
+ */
+function PMA_detectMIME(&$test)
+{
+ $len = strlen($test);
+ if ($len >= 2 && $test[0] == chr(0xff) && $test[1] == chr(0xd8)) {
+ return 'image/jpeg';
+ }
+ if ($len >= 3 && substr($test, 0, 3) == 'GIF') {
+ return 'image/gif';
+ }
+ if ($len >= 4 && substr($test, 0, 4) == "\x89PNG") {
+ return 'image/png';
+ }
+ return 'application/octet-stream';
+}
+?>
diff --git a/libraries/mult_submits.inc.php b/libraries/mult_submits.inc.php
new file mode 100644
index 0000000000..b52087feee
--- /dev/null
+++ b/libraries/mult_submits.inc.php
@@ -0,0 +1,254 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Helper for multi submit forms
+ *
+ * @package PhpMyAdmin
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+require_once 'libraries/transformations.lib.php';
+require_once 'libraries/bookmark.lib.php';
+require_once 'libraries/sql.lib.php';
+require_once 'libraries/mult_submits.lib.php';
+
+$request_params = array(
+ 'clause_is_unique',
+ 'from_prefix',
+ 'goto',
+ 'mult_btn',
+ 'original_sql_query',
+ 'query_type',
+ 'reload',
+ 'rows_to_delete',
+ 'selected',
+ 'selected_fld',
+ 'selected_recent_table',
+ 'sql_query',
+ 'submit_mult',
+ 'table_type',
+ 'to_prefix',
+ 'url_query'
+);
+
+foreach ($request_params as $one_request_param) {
+ if (isset($_REQUEST[$one_request_param])) {
+ $GLOBALS[$one_request_param] = $_REQUEST[$one_request_param];
+ }
+}
+
+/**
+ * Prepares the work and runs some other scripts if required
+ */
+if (! empty($submit_mult)
+ && $submit_mult != __('With selected:')
+ && (! empty($selected_db)
+ || ! empty($_POST['selected_tbl'])
+ || ! empty($selected_fld)
+ || ! empty($rows_to_delete))
+) {
+ define('PMA_SUBMIT_MULT', 1);
+ if (isset($selected_db) && !empty($selected_db)) {
+ // coming from server database view - do something with
+ // selected databases
+ $selected = $selected_db;
+ $what = 'drop_db';
+ } elseif (! empty($_POST['selected_tbl'])) {
+ // coming from database structure view - do something with
+ // selected tables
+ if ($submit_mult == 'print') {
+ include './tbl_printview.php';
+ } else {
+ $selected = $_POST['selected_tbl'];
+ switch ($submit_mult) {
+ case 'add_prefix_tbl':
+ case 'replace_prefix_tbl':
+ case 'copy_tbl_change_prefix':
+ case 'drop_db':
+ case 'drop_tbl':
+ case 'empty_tbl':
+ $what = $submit_mult;
+ break;
+ case 'check_tbl':
+ case 'optimize_tbl':
+ case 'repair_tbl':
+ case 'analyze_tbl':
+ $query_type = $submit_mult;
+ unset($submit_mult);
+ $mult_btn = __('Yes');
+ break;
+ case 'export':
+ unset($submit_mult);
+ include 'db_export.php';
+ exit;
+ break;
+ } // end switch
+ }
+ } elseif (isset($selected_fld) && !empty($selected_fld)) {
+ // coming from table structure view - do something with
+ // selected columns
+ $selected = $selected_fld;
+ list($what_ret, $query_type_ret, $is_unset_submit_mult, $mult_btn_ret)
+ = PMA_getDataForSubmitMult(
+ $submit_mult, $db, $table,
+ $selected, $action
+ );
+ //update the existing variables
+ if (isset($what_ret)) {
+ $what = $what_ret;
+ }
+ if (isset($query_type_ret)) {
+ $query_type = $query_type_ret;
+ }
+ if ($is_unset_submit_mult) {
+ unset($submit_mult);
+ }
+ if (isset($mult_btn_ret)) {
+ $mult_btn = $mult_btn_ret;
+ }
+ } else {
+ // coming from browsing - do something with selected rows
+ $what = 'row_delete';
+ $selected = $rows_to_delete;
+ }
+} // end if
+
+$views = $GLOBALS['dbi']->getVirtualTables($db);
+
+/**
+ * Displays the confirmation form if required
+ */
+if (!empty($submit_mult) && !empty($what)) {
+ unset($message);
+
+ if (strlen($table)) {
+ include './libraries/tbl_common.inc.php';
+ $url_query .= '&amp;goto=tbl_sql.php&amp;back=tbl_sql.php';
+ include './libraries/tbl_info.inc.php';
+ } elseif (strlen($db)) {
+ include './libraries/db_common.inc.php';
+ include './libraries/db_info.inc.php';
+ } else {
+ include_once './libraries/server_common.inc.php';
+ }
+
+ // Builds the query
+ list($full_query, $reload, $full_query_views)
+ = PMA_getQueryFromSelected(
+ $what, $db, $table, $selected, $action, $views
+ );
+
+ // Displays the confirmation form
+ $_url_params = PMA_getUrlParams(
+ $what, $reload, $action, $db, $table, $selected, $views,
+ isset($original_sql_query)? $original_sql_query : null,
+ isset($original_url_query)? $original_url_query : null
+ );
+
+ if ($what == 'replace_prefix_tbl' || $what == 'copy_tbl_change_prefix') {
+ echo PMA_getHtmlForReplacePrefixTable($what, $action, $_url_params);
+ } elseif ($what == 'add_prefix_tbl') {
+ echo PMA_getHtmlForAddPrefixTable($action, $_url_params);
+ } else {
+ echo PMA_getHtmlForOtherActions($what, $action, $_url_params, $full_query);
+ }
+ exit;
+
+} elseif (! empty($mult_btn) && $mult_btn == __('Yes')) {
+ /**
+ * Executes the query - dropping rows, columns/fields, tables or dbs
+ */
+ if ($query_type == 'drop_db'
+ || $query_type == 'drop_tbl'
+ || $query_type == 'drop_fld'
+ ) {
+ include_once './libraries/relation_cleanup.lib.php';
+ }
+
+ if ($query_type == 'primary_fld') {
+ // Gets table primary key
+ $GLOBALS['dbi']->selectDb($db);
+ $result = $GLOBALS['dbi']->query(
+ 'SHOW KEYS FROM ' . PMA_Util::backquote($table) . ';'
+ );
+ $primary = '';
+ while ($row = $GLOBALS['dbi']->fetchAssoc($result)) {
+ // Backups the list of primary keys
+ if ($row['Key_name'] == 'PRIMARY') {
+ $primary .= $row['Column_name'] . ', ';
+ }
+ } // end while
+ $GLOBALS['dbi']->freeResult($result);
+ }
+
+ list(
+ $result, $rebuild_database_list, $reload_ret,
+ $run_parts, $use_sql, $sql_query, $sql_query_views
+ ) = PMA_getQueryStrFromSelected(
+ $query_type, $selected, $db, $table, $views,
+ isset($primary) ? $primary : null,
+ isset($from_prefix) ? $from_prefix : null,
+ isset($to_prefix) ? $to_prefix : null
+ );
+ //update the existed variable
+ if (isset($reload_ret)) {
+ $reload = $reload_ret;
+ }
+
+ if ($query_type == 'drop_tbl') {
+ $default_fk_check_value = $GLOBALS['dbi']->fetchValue(
+ 'SHOW VARIABLES LIKE \'foreign_key_checks\';', 0, 1
+ ) == 'ON';
+ if (!empty($sql_query)) {
+ $sql_query .= ';';
+ } elseif (!empty($sql_query_views)) {
+ $sql_query = $sql_query_views . ';';
+ unset($sql_query_views);
+ }
+ }
+
+ if ($use_sql) {
+ /**
+ * Parse and analyze the query
+ */
+ include_once 'libraries/parse_analyze.inc.php';
+
+ PMA_executeQueryAndSendQueryResponse(
+ $analyzed_sql_results, false, $db, $table, null, null, null,
+ false, null, null, null, null, $goto, $pmaThemeImage, null, null,
+ $query_type, $sql_query, $selected, null
+ );
+ } elseif (!$run_parts) {
+ $GLOBALS['dbi']->selectDb($db);
+ // for disabling foreign key checks while dropping tables
+ if (! isset($_REQUEST['fk_check']) && $query_type == 'drop_tbl') {
+ $GLOBALS['dbi']->query('SET FOREIGN_KEY_CHECKS = 0;');
+ }
+ $result = $GLOBALS['dbi']->tryQuery($sql_query);
+ if (! isset($_REQUEST['fk_check'])
+ && $query_type == 'drop_tbl'
+ && $default_fk_check_value
+ ) {
+ $GLOBALS['dbi']->query('SET FOREIGN_KEY_CHECKS = 1;');
+ }
+ if ($result && !empty($sql_query_views)) {
+ $sql_query .= ' ' . $sql_query_views . ';';
+ $result = $GLOBALS['dbi']->tryQuery($sql_query_views);
+ unset($sql_query_views);
+ }
+
+ if (! $result) {
+ $message = PMA_Message::error($GLOBALS['dbi']->getError());
+ }
+ }
+ if ($rebuild_database_list) {
+ // avoid a problem with the database list navigator
+ // when dropping a db from server_databases
+ $GLOBALS['pma']->databases->build();
+ }
+} else {
+ $message = PMA_Message::success(__('No change'));
+}
+?>
diff --git a/libraries/mult_submits.lib.php b/libraries/mult_submits.lib.php
new file mode 100644
index 0000000000..1247513a2a
--- /dev/null
+++ b/libraries/mult_submits.lib.php
@@ -0,0 +1,627 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * functions for multi submit forms
+ *
+ * @usedby mult_submits.inc.php
+ *
+ * @package PhpMyAdmin
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ * Gets url params
+ *
+ * @param string $what mult submit type
+ * @param bool $reload is reload
+ * @param string $action action type
+ * @param string $db database name
+ * @param string $table table name
+ * @param array $selected selected rows(table,db)
+ * @param array $views table views
+ * @param string $original_sql_query original sql query
+ * @param string $original_url_query original url query
+ *
+ * @return array
+ */
+function PMA_getUrlParams(
+ $what, $reload, $action, $db, $table, $selected, $views,
+ $original_sql_query, $original_url_query
+) {
+ $_url_params = array(
+ 'query_type' => $what,
+ 'reload' => (! empty($reload) ? 1 : 0),
+ );
+ if (strpos(' ' . $action, 'db_') == 1) {
+ $_url_params['db']= $db;
+ } elseif (strpos(' ' . $action, 'tbl_') == 1 || $what == 'row_delete') {
+ $_url_params['db']= $db;
+ $_url_params['table']= $table;
+ }
+ foreach ($selected as $idx => $sval) {
+ if ($what == 'row_delete') {
+ $_url_params['selected'][] = 'DELETE FROM '
+ . PMA_Util::backquote($db) . '.' . PMA_Util::backquote($table)
+ . ' WHERE ' . urldecode($sval) . ' LIMIT 1;';
+ } else {
+ $_url_params['selected'][] = $sval;
+ }
+ }
+ if ($what == 'drop_tbl' && !empty($views)) {
+ foreach ($views as $current) {
+ $_url_params['views'][] = $current;
+ }
+ }
+ if ($what == 'row_delete') {
+ $_url_params['original_sql_query'] = $original_sql_query;
+ if (! empty($original_url_query)) {
+ $_url_params['original_url_query'] = $original_url_query;
+ }
+ }
+
+ return $_url_params;
+}
+
+/**
+ * Gets query results from
+ *
+ * @param string $query_type query type
+ * @param array $selected selected tables
+ * @param string $db db name
+ * @param string $table table name
+ * @param string $views table views
+ * @param string $primary table primary
+ * @param string $from_prefix from prefix original
+ * @param string $to_prefix to prefix original
+ *
+ * @return array
+ */
+function PMA_getQueryStrFromSelected(
+ $query_type, $selected, $db, $table, $views, $primary,
+ $from_prefix, $to_prefix
+) {
+ $rebuild_database_list = false;
+ $reload = null;
+ $a_query = null;
+ $sql_query = '';
+ $sql_query_views = null;
+ // whether to run query after each pass
+ $run_parts = false;
+ // whether to execute the query at the end (to display results)
+ $use_sql = false;
+ $result = null;
+
+ if ($query_type == 'drop_tbl') {
+ $sql_query_views = '';
+ }
+
+ $selected_cnt = count($selected);
+ $deletes = false;
+
+ for ($i = 0; $i < $selected_cnt; $i++) {
+ switch ($query_type) {
+ case 'row_delete':
+ $deletes = true;
+ $a_query = $selected[$i];
+ $run_parts = true;
+ break;
+
+ case 'drop_db':
+ PMA_relationsCleanupDatabase($selected[$i]);
+ $a_query = 'DROP DATABASE '
+ . PMA_Util::backquote($selected[$i]);
+ $reload = 1;
+ $run_parts = true;
+ $rebuild_database_list = true;
+ break;
+
+ case 'drop_tbl':
+ PMA_relationsCleanupTable($db, $selected[$i]);
+ $current = $selected[$i];
+ if (!empty($views) && in_array($current, $views)) {
+ $sql_query_views .= (empty($sql_query_views) ? 'DROP VIEW ' : ', ')
+ . PMA_Util::backquote($current);
+ } else {
+ $sql_query .= (empty($sql_query) ? 'DROP TABLE ' : ', ')
+ . PMA_Util::backquote($current);
+ }
+ $reload = 1;
+ break;
+
+ case 'check_tbl':
+ $sql_query .= (empty($sql_query) ? 'CHECK TABLE ' : ', ')
+ . PMA_Util::backquote($selected[$i]);
+ $use_sql = true;
+ break;
+
+ case 'optimize_tbl':
+ $sql_query .= (empty($sql_query) ? 'OPTIMIZE TABLE ' : ', ')
+ . PMA_Util::backquote($selected[$i]);
+ $use_sql = true;
+ break;
+
+ case 'analyze_tbl':
+ $sql_query .= (empty($sql_query) ? 'ANALYZE TABLE ' : ', ')
+ . PMA_Util::backquote($selected[$i]);
+ $use_sql = true;
+ break;
+
+ case 'repair_tbl':
+ $sql_query .= (empty($sql_query) ? 'REPAIR TABLE ' : ', ')
+ . PMA_Util::backquote($selected[$i]);
+ $use_sql = true;
+ break;
+
+ case 'empty_tbl':
+ $deletes = true;
+ $a_query = 'TRUNCATE ';
+ $a_query .= PMA_Util::backquote($selected[$i]);
+ $run_parts = true;
+ break;
+
+ case 'drop_fld':
+ PMA_relationsCleanupColumn($db, $table, $selected[$i]);
+ $sql_query .= (empty($sql_query)
+ ? 'ALTER TABLE ' . PMA_Util::backquote($table)
+ : ',')
+ . ' DROP ' . PMA_Util::backquote($selected[$i])
+ . (($i == $selected_cnt-1) ? ';' : '');
+ break;
+
+ case 'primary_fld':
+ $sql_query .= (empty($sql_query)
+ ? 'ALTER TABLE ' . PMA_Util::backquote($table) . (empty($primary)
+ ? ''
+ : ' DROP PRIMARY KEY,') . ' ADD PRIMARY KEY( '
+ : ', ')
+ . PMA_Util::backquote($selected[$i])
+ . (($i == $selected_cnt-1) ? ');' : '');
+ break;
+
+ case 'index_fld':
+ $sql_query .= (empty($sql_query)
+ ? 'ALTER TABLE ' . PMA_Util::backquote($table) . ' ADD INDEX( '
+ : ', ')
+ . PMA_Util::backquote($selected[$i])
+ . (($i == $selected_cnt-1) ? ');' : '');
+ break;
+
+ case 'unique_fld':
+ $sql_query .= (empty($sql_query)
+ ? 'ALTER TABLE ' . PMA_Util::backquote($table) . ' ADD UNIQUE( '
+ : ', ')
+ . PMA_Util::backquote($selected[$i])
+ . (($i == $selected_cnt-1) ? ');' : '');
+ break;
+
+ case 'spatial_fld':
+ $sql_query .= (empty($sql_query)
+ ? 'ALTER TABLE ' . PMA_Util::backquote($table) . ' ADD SPATIAL( '
+ : ', ')
+ . PMA_Util::backquote($selected[$i])
+ . (($i == $selected_cnt-1) ? ');' : '');
+ break;
+
+ case 'fulltext_fld':
+ $sql_query .= (empty($sql_query)
+ ? 'ALTER TABLE ' . PMA_Util::backquote($table) . ' ADD FULLTEXT( '
+ : ', ')
+ . PMA_Util::backquote($selected[$i])
+ . (($i == $selected_cnt-1) ? ');' : '');
+ break;
+
+ case 'add_prefix_tbl':
+ $newtablename = $_POST['add_prefix'] . $selected[$i];
+ // ADD PREFIX TO TABLE NAME
+ $a_query = 'ALTER TABLE '
+ . PMA_Util::backquote($selected[$i])
+ . ' RENAME '
+ . PMA_Util::backquote($newtablename);
+ $run_parts = true;
+ break;
+
+ case 'replace_prefix_tbl':
+ $current = $selected[$i];
+ if (substr($current, 0, strlen($from_prefix)) == $from_prefix) {
+ $newtablename = $to_prefix . substr($current, strlen($from_prefix));
+ } else {
+ $newtablename = $current;
+ }
+ // CHANGE PREFIX PATTERN
+ $a_query = 'ALTER TABLE '
+ . PMA_Util::backquote($selected[$i])
+ . ' RENAME '
+ . PMA_Util::backquote($newtablename);
+ $run_parts = true;
+ break;
+
+ case 'copy_tbl_change_prefix':
+ $current = $selected[$i];
+ if (substr($current, 0, strlen($from_prefix)) == $from_prefix) {
+ $newtablename = $to_prefix . substr($current, strlen($from_prefix));
+ } else {
+ $newtablename = $current;
+ }
+ $newtablename = $to_prefix . substr($current, strlen($from_prefix));
+ // COPY TABLE AND CHANGE PREFIX PATTERN
+ $a_query = 'CREATE TABLE '
+ . PMA_Util::backquote($newtablename)
+ . ' SELECT * FROM '
+ . PMA_Util::backquote($selected[$i]);
+ $run_parts = true;
+ break;
+
+ } // end switch
+
+ // All "DROP TABLE", "DROP FIELD", "OPTIMIZE TABLE" and "REPAIR TABLE"
+ // statements will be run at once below
+ if ($run_parts) {
+ $sql_query .= $a_query . ';' . "\n";
+ if ($query_type != 'drop_db') {
+ $GLOBALS['dbi']->selectDb($db);
+ }
+ $result = $GLOBALS['dbi']->query($a_query);
+
+ if ($query_type == 'drop_db') {
+ PMA_clearTransformations($selected[$i]);
+ } elseif ($query_type == 'drop_tbl') {
+ PMA_clearTransformations($db, $selected[$i]);
+ } else if ($query_type == 'drop_fld') {
+ PMA_clearTransformations($db, $table, $selected[$i]);
+ }
+ } // end if
+ } // end for
+
+ if ($deletes) {
+ $_REQUEST['pos'] = PMA_calculatePosForLastPage($db, $table, $_REQUEST['pos']);
+ }
+
+ return array(
+ $result, $rebuild_database_list, $reload,
+ $run_parts, $use_sql, $sql_query, $sql_query_views
+ );
+}
+
+/**
+ * Gets table primary key
+ *
+ * @param string $db name of db
+ * @param string $table name of table
+ *
+ * @return string
+ */
+function PMA_getKeyForTablePrimary($db, $table)
+{
+ $GLOBALS['dbi']->selectDb($db);
+ $result = $GLOBALS['dbi']->query(
+ 'SHOW KEYS FROM ' . PMA_Util::backquote($table) . ';'
+ );
+ $primary = '';
+ while ($row = $GLOBALS['dbi']->fetchAssoc($result)) {
+ // Backups the list of primary keys
+ if ($row['Key_name'] == 'PRIMARY') {
+ $primary .= $row['Column_name'] . ', ';
+ }
+ } // end while
+ $GLOBALS['dbi']->freeResult($result);
+
+ return $primary;
+}
+
+/**
+ * Gets HTML for replace_prefix_tbl or copy_tbl_change_prefix
+ *
+ * @param string $what mult_submit type
+ * @param string $action action type
+ * @param array $_url_params URL params
+ *
+ * @return string
+ */
+function PMA_getHtmlForReplacePrefixTable($what, $action, $_url_params)
+{
+ $html = '<form action="' . $action . '" method="post">';
+ $html .= PMA_URL_getHiddenInputs($_url_params);
+ $html .= '<fieldset class = "input">';
+ $html .= '<legend>';
+ if ($what == 'replace_prefix_tbl') {
+ $html .= __('Replace table prefix:');
+ } else {
+ $html .= __('Copy table with prefix:');
+ }
+ $html .= '</legend>';
+ $html .= '<table>';
+ $html .= '<tr>';
+ $html .= '<td>' . __('From') . '</td>';
+ $html .= '<td>';
+ $html .= '<input type="text" name="from_prefix" id="initialPrefix" />';
+ $html .= '</td>';
+ $html .= '</tr>';
+ $html .= '<tr>';
+ $html .= '<td>' . __('To') . '</td>';
+ $html .= '<td>';
+ $html .= '<input type="text" name="to_prefix" id="newPrefix" />';
+ $html .= '</td>';
+ $html .= '</tr>';
+ $html .= '</table>';
+ $html .= '</fieldset>';
+ $html .= '<fieldset class="tblFooters">';
+ $html .= '<input type="hidden" name="mult_btn" value="' . __('Yes') . '" />';
+ $html .= '<input type="submit" value="' . __('Submit') . '" id="buttonYes" />';
+ $html .= '</fieldset>';
+ $html .= '</form>';
+
+ return $html;
+}
+
+/**
+ * Gets HTML for add_prefix_tbl
+ *
+ * @param string $action action type
+ * @param array $_url_params URL params
+ *
+ * @return string
+ */
+function PMA_getHtmlForAddPrefixTable($action, $_url_params)
+{
+ $html = '<form action="' . $action . '" method="post">';
+ $html .= PMA_URL_getHiddenInputs($_url_params);
+ $html .= '<fieldset class = "input">';
+ $html .= '<legend>' . __('Add table prefix:') . '</legend>';
+ $html .= '<table>';
+ $html .= '<tr>';
+ $html .= '<td>' . __('Add prefix') . '</td>';
+ $html .= '<td>';
+ $html .= '<input type="text" name="add_prefix" id="txtPrefix" />';
+ $html .= '</td>';
+ $html .= '</tr>';
+ $html .= '<tr>';
+ $html .= '</table>';
+ $html .= '</fieldset>';
+ $html .= '<fieldset class="tblFooters">';
+ $html .= '<input type="hidden" name="mult_btn" value="' . __('Yes') . '" />';
+ $html .= '<input type="submit" value="' . __('Submit') . '" id="buttonYes" />';
+ $html .= '</fieldset>';
+ $html .= '</form>';
+
+ return $html;
+}
+
+/**
+ * Gets HTML for other mult_submits actions
+ *
+ * @param string $what mult_submit type
+ * @param string $action action type
+ * @param array $_url_params URL params
+ * @param array $full_query full sql query string
+ *
+ * @return string
+ */
+function PMA_getHtmlForOtherActions($what, $action, $_url_params, $full_query)
+{
+ $html = '<fieldset class="confirmation">';
+ $html .= '<legend>';
+ if ($what == 'drop_db') {
+ $html .= __('You are about to DESTROY a complete database!') . ' ';
+ }
+ $html .= __('Do you really want to execute the following query?');
+ $html .= '</legend>';
+ $html .= '<code>' . $full_query . '</code>';
+ $html .= '</fieldset>';
+ $html .= '<fieldset class="tblFooters">';
+ $html .= '<form action="' . $action . '" method="post">';
+ $html .= PMA_URL_getHiddenInputs($_url_params);
+ // Display option to disable foreign key checks while dropping tables
+ if ($what == 'drop_tbl') {
+ $html .= '<div id="foreignkeychk">';
+ $html .= '<span class="fkc_switch">';
+ $html .= __('Foreign key check:');
+ $html .= '</span>';
+ $html .= '<span class="checkbox">';
+ $html .= '<input type="checkbox" name="fk_check" value="1" '
+ . 'id="fkc_checkbox"';
+ $default_fk_check_value = $GLOBALS['dbi']->fetchValue(
+ 'SHOW VARIABLES LIKE \'foreign_key_checks\';', 0, 1
+ ) == 'ON';
+ if ($default_fk_check_value) {
+ $html .= ' checked="checked"';
+ }
+ $html .= '/></span>';
+ $html .= '<span id="fkc_status" class="fkc_switch">';
+ $html .= ($default_fk_check_value) ? __('(Enabled)') : __('(Disabled)');
+ $html .= '</span>';
+ $html .= '</div>';
+ }
+ $html .= '<input type="hidden" name="mult_btn" value="' . __('Yes') . '" />';
+ $html .= '<input type="submit" value="' . __('Yes') . '" id="buttonYes" />';
+ $html .= '</form>';
+
+ $html .= '<form action="' . $action . '" method="post">';
+ $html .= PMA_URL_getHiddenInputs($_url_params);
+ $html .= '<input type="hidden" name="mult_btn" value="' . __('No') . '" />';
+ $html .= '<input type="submit" value="' . __('No') . '" id="buttonNo" />';
+ $html .= '</form>';
+ $html .= '</fieldset>';
+
+ return $html;
+}
+
+/**
+ * Get List of information for Submit Mult
+ *
+ * @param string $submit_mult mult_submit type
+ * @param string $db dtabase name
+ * @param array $table table name
+ * @param array $selected the selected columns
+ * @param array $action action type
+ *
+ * @return array()
+ */
+function PMA_getDataForSubmitMult($submit_mult, $db, $table, $selected, $action)
+{
+ $what = null;
+ $query_type = null;
+ $is_unset_submit_mult = false;
+ $mult_btn = null;
+
+ switch ($submit_mult) {
+ case 'drop':
+ $what = 'drop_fld';
+ break;
+ case 'primary':
+ // Gets table primary key
+ $primary = PMA_getKeyForTablePrimary($db, $table);
+ if (empty($primary)) {
+ // no primary key, so we can safely create new
+ $is_unset_submit_mult = true;
+ $query_type = 'primary_fld';
+ $mult_btn = __('Yes');
+ } else {
+ // primary key exists, so lets as user
+ $what = 'primary_fld';
+ }
+ break;
+ case 'index':
+ $is_unset_submit_mult = true;
+ $query_type = 'index_fld';
+ $mult_btn = __('Yes');
+ break;
+ case 'unique':
+ $is_unset_submit_mult = true;
+ $query_type = 'unique_fld';
+ $mult_btn = __('Yes');
+ break;
+ case 'spatial':
+ $is_unset_submit_mult = true;
+ $query_type = 'spatial_fld';
+ $mult_btn = __('Yes');
+ break;
+ case 'ftext':
+ $is_unset_submit_mult = true;
+ $query_type = 'fulltext_fld';
+ $mult_btn = __('Yes');
+ break;
+ case 'change':
+ PMA_displayHtmlForColumnChange($db, $table, $selected, $action);
+ // execution stops here but PMA_Response correctly finishes
+ // the rendering
+ exit;
+ case 'browse':
+ // this should already be handled by tbl_structure.php
+ }
+
+ return array($what, $query_type, $is_unset_submit_mult, $mult_btn);
+}
+
+/**
+ * Get query string from Selected
+ *
+ * @param string $what mult_submit type
+ * @param string $db dtabase name
+ * @param array $table table name
+ * @param array $selected the selected columns
+ * @param array $action action type
+ * @param array $views table views
+ *
+ * @return array()
+ */
+function PMA_getQueryFromSelected($what, $db, $table, $selected, $action, $views)
+{
+ $reload = null;
+ $full_query_views = null;
+ $full_query = '';
+
+ if ($what == 'drop_tbl') {
+ $full_query_views = '';
+ }
+
+ $selected_cnt = count($selected);
+ $i = 0;
+ foreach ($selected as $idx => $sval) {
+ switch ($what) {
+ case 'row_delete':
+ $full_query .= 'DELETE FROM ' . PMA_Util::backquote($db)
+ . '.' . PMA_Util::backquote($table)
+ // Do not append a "LIMIT 1" clause here
+ // (it's not binlog friendly).
+ // We don't need the clause because the calling panel permits
+ // this feature only when there is a unique index.
+ . ' WHERE ' . urldecode($sval)
+ . ';<br />';
+ break;
+ case 'drop_db':
+ $full_query .= 'DROP DATABASE '
+ . PMA_Util::backquote(htmlspecialchars($sval))
+ . ';<br />';
+ $reload = 1;
+ break;
+
+ case 'drop_tbl':
+ $current = $sval;
+ if (!empty($views) && in_array($current, $views)) {
+ $full_query_views .= (empty($full_query_views) ? 'DROP VIEW ' : ', ')
+ . PMA_Util::backquote(htmlspecialchars($current));
+ } else {
+ $full_query .= (empty($full_query) ? 'DROP TABLE ' : ', ')
+ . PMA_Util::backquote(htmlspecialchars($current));
+ }
+ break;
+
+ case 'empty_tbl':
+ $full_query .= 'TRUNCATE ';
+ $full_query .= PMA_Util::backquote(htmlspecialchars($sval))
+ . ';<br />';
+ break;
+
+ case 'primary_fld':
+ if ($full_query == '') {
+ $full_query .= 'ALTER TABLE '
+ . PMA_Util::backquote(htmlspecialchars($table))
+ . '<br />&nbsp;&nbsp;DROP PRIMARY KEY,'
+ . '<br />&nbsp;&nbsp; ADD PRIMARY KEY('
+ . '<br />&nbsp;&nbsp;&nbsp;&nbsp; '
+ . PMA_Util::backquote(htmlspecialchars($sval))
+ . ',';
+ } else {
+ $full_query .= '<br />&nbsp;&nbsp;&nbsp;&nbsp; '
+ . PMA_Util::backquote(htmlspecialchars($sval))
+ . ',';
+ }
+ if ($i == $selected_cnt-1) {
+ $full_query = preg_replace('@,$@', ');<br />', $full_query);
+ }
+ break;
+
+ case 'drop_fld':
+ if ($full_query == '') {
+ $full_query .= 'ALTER TABLE '
+ . PMA_Util::backquote(htmlspecialchars($table));
+ }
+ $full_query .= '<br />&nbsp;&nbsp;DROP '
+ . PMA_Util::backquote(htmlspecialchars($sval))
+ . ',';
+ if ($i == $selected_cnt - 1) {
+ $full_query = preg_replace('@,$@', ';<br />', $full_query);
+ }
+ break;
+ } // end switch
+ $i++;
+ }
+
+ if ($what == 'drop_tbl') {
+ if (!empty($full_query)) {
+ $full_query .= ';<br />' . "\n";
+ }
+ if (!empty($full_query_views)) {
+ $full_query .= $full_query_views . ';<br />' . "\n";
+ }
+ unset($full_query_views);
+ }
+
+ $full_query_views = isset($full_query_views)? $full_query_views : null;
+
+ return array($full_query, $reload, $full_query_views);
+}
+
+?>
diff --git a/libraries/mysql_charsets.inc.php b/libraries/mysql_charsets.inc.php
new file mode 100644
index 0000000000..ea3aa2989b
--- /dev/null
+++ b/libraries/mysql_charsets.inc.php
@@ -0,0 +1,143 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * MySQL charsets listings
+ *
+ * @package PhpMyAdmin
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ *
+ */
+
+if (! PMA_Util::cacheExists('mysql_charsets', true)) {
+ global $mysql_charsets, $mysql_charsets_descriptions,
+ $mysql_charsets_available, $mysql_collations, $mysql_collations_available,
+ $mysql_default_collations, $mysql_collations_flat;
+ $sql = PMA_DRIZZLE
+ ? 'SELECT * FROM data_dictionary.CHARACTER_SETS'
+ : 'SELECT * FROM information_schema.CHARACTER_SETS';
+ $res = $GLOBALS['dbi']->query($sql);
+
+ $mysql_charsets = array();
+ while ($row = $GLOBALS['dbi']->fetchAssoc($res)) {
+ $mysql_charsets[] = $row['CHARACTER_SET_NAME'];
+ // never used
+ //$mysql_charsets_maxlen[$row['Charset']] = $row['Maxlen'];
+ $mysql_charsets_descriptions[$row['CHARACTER_SET_NAME']]
+ = $row['DESCRIPTION'];
+ }
+ $GLOBALS['dbi']->freeResult($res);
+
+ sort($mysql_charsets, SORT_STRING);
+
+ $mysql_collations = array_flip($mysql_charsets);
+ $mysql_default_collations = $mysql_collations_flat
+ = $mysql_charsets_available = $mysql_collations_available = array();
+
+ $sql = PMA_DRIZZLE
+ ? 'SELECT * FROM data_dictionary.COLLATIONS'
+ : 'SELECT * FROM information_schema.COLLATIONS';
+ $res = $GLOBALS['dbi']->query($sql);
+ while ($row = $GLOBALS['dbi']->fetchAssoc($res)) {
+ if (! is_array($mysql_collations[$row['CHARACTER_SET_NAME']])) {
+ $mysql_collations[$row['CHARACTER_SET_NAME']]
+ = array($row['COLLATION_NAME']);
+ } else {
+ $mysql_collations[$row['CHARACTER_SET_NAME']][] = $row['COLLATION_NAME'];
+ }
+ $mysql_collations_flat[] = $row['COLLATION_NAME'];
+ if ($row['IS_DEFAULT'] == 'Yes' || $row['IS_DEFAULT'] == '1') {
+ $mysql_default_collations[$row['CHARACTER_SET_NAME']]
+ = $row['COLLATION_NAME'];
+ }
+ //$mysql_collations_available[$row['Collation']]
+ // = ! isset($row['Compiled']) || $row['Compiled'] == 'Yes';
+ $mysql_collations_available[$row['COLLATION_NAME']] = true;
+ $mysql_charsets_available[$row['CHARACTER_SET_NAME']]
+ = !empty($mysql_charsets_available[$row['CHARACTER_SET_NAME']])
+ || !empty($mysql_collations_available[$row['COLLATION_NAME']]);
+ }
+ $GLOBALS['dbi']->freeResult($res);
+ unset($res, $row);
+
+ if (PMA_DRIZZLE
+ && isset($mysql_collations['utf8_general_ci'])
+ && isset($mysql_collations['utf8'])
+ ) {
+ $mysql_collations['utf8'] = $mysql_collations['utf8_general_ci'];
+ $mysql_default_collations['utf8']
+ = $mysql_default_collations['utf8_general_ci'];
+ $mysql_charsets_available['utf8']
+ = $mysql_charsets_available['utf8_general_ci'];
+ unset(
+ $mysql_collations['utf8_general_ci'],
+ $mysql_default_collations['utf8_general_ci'],
+ $mysql_charsets_available['utf8_general_ci']
+ );
+ }
+
+ sort($mysql_collations_flat, SORT_STRING);
+ foreach ($mysql_collations as $key => $value) {
+ sort($mysql_collations[$key], SORT_STRING);
+ reset($mysql_collations[$key]);
+ }
+ unset($key, $value);
+
+ PMA_Util::cacheSet(
+ 'mysql_charsets', $GLOBALS['mysql_charsets'], true
+ );
+ PMA_Util::cacheSet(
+ 'mysql_charsets_descriptions', $GLOBALS['mysql_charsets_descriptions'], true
+ );
+ PMA_Util::cacheSet(
+ 'mysql_charsets_available', $GLOBALS['mysql_charsets_available'], true
+ );
+ PMA_Util::cacheSet(
+ 'mysql_collations', $GLOBALS['mysql_collations'], true
+ );
+ PMA_Util::cacheSet(
+ 'mysql_default_collations', $GLOBALS['mysql_default_collations'], true
+ );
+ PMA_Util::cacheSet(
+ 'mysql_collations_flat', $GLOBALS['mysql_collations_flat'], true
+ );
+ PMA_Util::cacheSet(
+ 'mysql_collations_available', $GLOBALS['mysql_collations_available'], true
+ );
+} else {
+ $GLOBALS['mysql_charsets'] = PMA_Util::cacheGet(
+ 'mysql_charsets', true
+ );
+ $GLOBALS['mysql_charsets_descriptions'] = PMA_Util::cacheGet(
+ 'mysql_charsets_descriptions', true
+ );
+ $GLOBALS['mysql_charsets_available'] = PMA_Util::cacheGet(
+ 'mysql_charsets_available', true
+ );
+ $GLOBALS['mysql_collations'] = PMA_Util::cacheGet(
+ 'mysql_collations', true
+ );
+ $GLOBALS['mysql_default_collations'] = PMA_Util::cacheGet(
+ 'mysql_default_collations', true
+ );
+ $GLOBALS['mysql_collations_flat'] = PMA_Util::cacheGet(
+ 'mysql_collations_flat', true
+ );
+ $GLOBALS['mysql_collations_available'] = PMA_Util::cacheGet(
+ 'mysql_collations_available', true
+ );
+}
+
+define('PMA_CSDROPDOWN_COLLATION', 0);
+define('PMA_CSDROPDOWN_CHARSET', 1);
+
+/**
+ * shared functions for mysql charsets
+ */
+require_once './libraries/mysql_charsets.lib.php';
+
+?>
diff --git a/libraries/mysql_charsets.lib.php b/libraries/mysql_charsets.lib.php
new file mode 100644
index 0000000000..3a88ec0661
--- /dev/null
+++ b/libraries/mysql_charsets.lib.php
@@ -0,0 +1,354 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Shared code for mysql charsets
+ *
+ * @package PhpMyAdmin
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+function PMA_generateCharsetDropdownBox($type = PMA_CSDROPDOWN_COLLATION,
+ $name = null, $id = null, $default = null, $label = true, $indent = 0,
+ $submitOnChange = false, $displayUnavailable = false
+) {
+ global $mysql_charsets, $mysql_charsets_descriptions,
+ $mysql_charsets_available, $mysql_collations, $mysql_collations_available;
+
+ if (empty($name)) {
+ if ($type == PMA_CSDROPDOWN_COLLATION) {
+ $name = 'collation';
+ } else {
+ $name = 'character_set';
+ }
+ }
+
+ $return_str = '<select lang="en" dir="ltr" name="'
+ . htmlspecialchars($name) . '"'
+ . (empty($id) ? '' : ' id="' . htmlspecialchars($id) . '"')
+ . ($submitOnChange ? ' class="autosubmit"' : '') . '>' . "\n";
+ if ($label) {
+ $return_str .= '<option value="">'
+ . ($type == PMA_CSDROPDOWN_COLLATION ? __('Collation') : __('Charset'))
+ . '</option>' . "\n";
+ }
+ $return_str .= '<option value=""></option>' . "\n";
+ foreach ($mysql_charsets as $current_charset) {
+ if (!$mysql_charsets_available[$current_charset]) {
+ continue;
+ }
+ $current_cs_descr
+ = empty($mysql_charsets_descriptions[$current_charset])
+ ? $current_charset
+ : $mysql_charsets_descriptions[$current_charset];
+
+ if ($type == PMA_CSDROPDOWN_COLLATION) {
+ $return_str .= '<optgroup label="' . $current_charset
+ . '" title="' . $current_cs_descr . '">' . "\n";
+ foreach ($mysql_collations[$current_charset] as $current_collation) {
+ if (!$mysql_collations_available[$current_collation]) {
+ continue;
+ }
+ $return_str .= '<option value="' . $current_collation
+ . '" title="' . PMA_getCollationDescr($current_collation) . '"'
+ . ($default == $current_collation ? ' selected="selected"' : '')
+ . '>'
+ . $current_collation . '</option>' . "\n";
+ }
+ $return_str .= '</optgroup>' . "\n";
+ } else {
+ $return_str .= '<option value="' . $current_charset
+ . '" title="' . $current_cs_descr . '"'
+ . ($default == $current_charset ? ' selected="selected"' : '') . '>'
+ . $current_charset . '</option>' . "\n";
+ }
+ }
+ $return_str .= '</select>' . "\n";
+
+ return $return_str;
+}
+
+/**
+ * Generate the charset query part
+ *
+ * @param string $collation Collation
+ *
+ * @return string
+ */
+function PMA_generateCharsetQueryPart($collation)
+{
+ if (!PMA_DRIZZLE) {
+ list($charset) = explode('_', $collation);
+ return ' CHARACTER SET ' . $charset
+ . ($charset == $collation ? '' : ' COLLATE ' . $collation);
+ } else {
+ return ' COLLATE ' . $collation;
+ }
+}
+
+/**
+ * returns collation of given db
+ *
+ * @param string $db name of db
+ *
+ * @return string collation of $db
+ */
+function PMA_getDbCollation($db)
+{
+ if ($GLOBALS['dbi']->isSystemSchema($db)) {
+ // We don't have to check the collation of the virtual
+ // information_schema database: We know it!
+ return 'utf8_general_ci';
+ }
+
+ $sql = PMA_DRIZZLE
+ ? 'SELECT DEFAULT_COLLATION_NAME FROM data_dictionary.SCHEMAS'
+ . ' WHERE SCHEMA_NAME = \'' . PMA_Util::sqlAddSlashes($db)
+ . '\' LIMIT 1'
+ : 'SELECT DEFAULT_COLLATION_NAME FROM information_schema.SCHEMATA'
+ . ' WHERE SCHEMA_NAME = \'' . PMA_Util::sqlAddSlashes($db)
+ . '\' LIMIT 1';
+ return $GLOBALS['dbi']->fetchValue($sql);
+}
+
+/**
+ * returns default server collation from show variables
+ *
+ * @return string $server_collation
+ */
+function PMA_getServerCollation()
+{
+ return $GLOBALS['dbi']->fetchValue(
+ 'SHOW VARIABLES LIKE \'collation_server\'', 0, 1
+ );
+}
+
+/**
+ * returns description for given collation
+ *
+ * @param string $collation MySQL collation string
+ *
+ * @return string collation description
+ */
+function PMA_getCollationDescr($collation)
+{
+ if ($collation == 'binary') {
+ return __('Binary');
+ }
+ $parts = explode('_', $collation);
+ if (count($parts) == 1) {
+ $parts[1] = 'general';
+ } elseif ($parts[1] == 'ci' || $parts[1] == 'cs') {
+ $parts[2] = $parts[1];
+ $parts[1] = 'general';
+ }
+ $descr = '';
+ switch ($parts[1]) {
+ case 'bulgarian':
+ $descr = __('Bulgarian');
+ break;
+ case 'chinese':
+ if ($parts[0] == 'gb2312' || $parts[0] == 'gbk') {
+ $descr = __('Simplified Chinese');
+ } elseif ($parts[0] == 'big5') {
+ $descr = __('Traditional Chinese');
+ }
+ break;
+ case 'ci':
+ $descr = __('case-insensitive');
+ break;
+ case 'cs':
+ $descr = __('case-sensitive');
+ break;
+ case 'croatian':
+ $descr = __('Croatian');
+ break;
+ case 'czech':
+ $descr = __('Czech');
+ break;
+ case 'danish':
+ $descr = __('Danish');
+ break;
+ case 'english':
+ $descr = __('English');
+ break;
+ case 'esperanto':
+ $descr = __('Esperanto');
+ break;
+ case 'estonian':
+ $descr = __('Estonian');
+ break;
+ case 'german1':
+ $descr = __('German') . ' (' . __('dictionary') . ')';
+ break;
+ case 'german2':
+ $descr = __('German') . ' (' . __('phone book') . ')';
+ break;
+ case 'hungarian':
+ $descr = __('Hungarian');
+ break;
+ case 'icelandic':
+ $descr = __('Icelandic');
+ break;
+ case 'japanese':
+ $descr = __('Japanese');
+ break;
+ case 'latvian':
+ $descr = __('Latvian');
+ break;
+ case 'lithuanian':
+ $descr = __('Lithuanian');
+ break;
+ case 'korean':
+ $descr = __('Korean');
+ break;
+ case 'persian':
+ $descr = __('Persian');
+ break;
+ case 'polish':
+ $descr = __('Polish');
+ break;
+ case 'roman':
+ $descr = __('West European');
+ break;
+ case 'romanian':
+ $descr = __('Romanian');
+ break;
+ case 'slovak':
+ $descr = __('Slovak');
+ break;
+ case 'slovenian':
+ $descr = __('Slovenian');
+ break;
+ case 'spanish':
+ $descr = __('Spanish');
+ break;
+ case 'spanish2':
+ $descr = __('Traditional Spanish');
+ break;
+ case 'swedish':
+ $descr = __('Swedish');
+ break;
+ case 'thai':
+ $descr = __('Thai');
+ break;
+ case 'turkish':
+ $descr = __('Turkish');
+ break;
+ case 'ukrainian':
+ $descr = __('Ukrainian');
+ break;
+ case 'unicode':
+ $descr = __('Unicode') . ' (' . __('multilingual') . ')';
+ break;
+ case 'bin':
+ $is_bin = true;
+ case 'general':
+ switch ($parts[0]) {
+ // Unicode charsets
+ case 'ucs2':
+ case 'utf8':
+ case 'utf8mb4':
+ $descr = __('Unicode') . ' (' . __('multilingual') . ')';
+ break;
+ // West European charsets
+ case 'ascii':
+ case 'cp850':
+ case 'dec8':
+ case 'hp8':
+ case 'latin1':
+ case 'macroman':
+ $descr = __('West European') . ' (' . __('multilingual') . ')';
+ break;
+ // Central European charsets
+ case 'cp1250':
+ case 'cp852':
+ case 'latin2':
+ case 'macce':
+ $descr = __('Central European') . ' (' . __('multilingual') . ')';
+ break;
+ // Russian charsets
+ case 'cp866':
+ case 'koi8r':
+ $descr = __('Russian');
+ break;
+ // Simplified Chinese charsets
+ case 'gb2312':
+ case 'gbk':
+ $descr = __('Simplified Chinese');
+ break;
+ // Japanese charsets
+ case 'sjis':
+ case 'ujis':
+ case 'cp932':
+ case 'eucjpms':
+ $descr = __('Japanese');
+ break;
+ // Baltic charsets
+ case 'cp1257':
+ case 'latin7':
+ $descr = __('Baltic') . ' (' . __('multilingual') . ')';
+ break;
+ // Other
+ case 'armscii8':
+ case 'armscii':
+ $descr = __('Armenian');
+ break;
+ case 'big5':
+ $descr = __('Traditional Chinese');
+ break;
+ case 'cp1251':
+ $descr = __('Cyrillic') . ' (' . __('multilingual') . ')';
+ break;
+ case 'cp1256':
+ $descr = __('Arabic');
+ break;
+ case 'euckr':
+ $descr = __('Korean');
+ break;
+ case 'hebrew':
+ $descr = __('Hebrew');
+ break;
+ case 'geostd8':
+ $descr = __('Georgian');
+ break;
+ case 'greek':
+ $descr = __('Greek');
+ break;
+ case 'keybcs2':
+ $descr = __('Czech-Slovak');
+ break;
+ case 'koi8u':
+ $descr = __('Ukrainian');
+ break;
+ case 'latin5':
+ $descr = __('Turkish');
+ break;
+ case 'swe7':
+ $descr = __('Swedish');
+ break;
+ case 'tis620':
+ $descr = __('Thai');
+ break;
+ default:
+ $descr = __('unknown');
+ break;
+ }
+ if (!empty($is_bin)) {
+ $descr .= ', ' . __('Binary');
+ }
+ break;
+ default: $descr = __('unknown');
+ }
+ if (!empty($parts[2])) {
+ if ($parts[2] == 'ci') {
+ $descr .= ', ' . __('case-insensitive');
+ } elseif ($parts[2] == 'cs') {
+ $descr .= ', ' . __('case-sensitive');
+ }
+ }
+
+ return $descr;
+}
+?>
diff --git a/libraries/navigation/Navigation.class.php b/libraries/navigation/Navigation.class.php
new file mode 100644
index 0000000000..b7827f6b1c
--- /dev/null
+++ b/libraries/navigation/Navigation.class.php
@@ -0,0 +1,211 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * This class is responsible for instanciating
+ * the various components of the navigation panel
+ *
+ * @package PhpMyAdmin-navigation
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+require_once 'libraries/navigation/NodeFactory.class.php';
+require_once 'libraries/navigation/NavigationHeader.class.php';
+require_once 'libraries/navigation/NavigationTree.class.php';
+
+/**
+ * The navigation panel - displays server, db and table selection tree
+ *
+ * @package PhpMyAdmin-Navigation
+ */
+class PMA_Navigation
+{
+ /**
+ * Initialises the class
+ */
+ public function __construct()
+ {
+ if (empty($GLOBALS['token'])) {
+ $GLOBALS['token'] = $_SESSION[' PMA_token '];
+ }
+ }
+
+ /**
+ * Renders the navigation tree, or part of it
+ *
+ * @return string The navigation tree
+ */
+ public function getDisplay()
+ {
+ /* Init */
+ $retval = '';
+ if (! PMA_Response::getInstance()->isAjax()) {
+ $header = new PMA_NavigationHeader();
+ $retval = $header->getDisplay();
+ }
+ $tree = new PMA_NavigationTree();
+ if (! PMA_Response::getInstance()->isAjax()
+ || ! empty($_REQUEST['full'])
+ || ! empty($_REQUEST['reload'])
+ ) {
+ $treeRender = $tree->renderState();
+ } else {
+ $treeRender = $tree->renderPath();
+ }
+
+ if (! $treeRender) {
+ $retval .= PMA_Message::error(
+ __('An error has occurred while loading the navigation tree')
+ )->getDisplay();
+ } else {
+ $retval .= $treeRender;
+ }
+
+ if (! PMA_Response::getInstance()->isAjax()) {
+ // closes the tags that were opened by the navigation header
+ $retval .= '</div>';
+ $retval .= '</div>';
+ $retval .= '</div>';
+ }
+
+ return $retval;
+ }
+
+ /**
+ * Add an item of navigation tree to the hidden items list in PMA database.
+ *
+ * @param string $itemName name of the navigation tree item
+ * @param string $itemType type of the navigation tree item
+ * @param string $dbName database name
+ * @param string $tableName table name if applicable
+ *
+ * @return void
+ */
+ public function hideNavigationItem(
+ $itemName, $itemType, $dbName, $tableName = null
+ ) {
+ $navTable = PMA_Util::backquote($GLOBALS['cfgRelation']['db'])
+ . "." . PMA_Util::backquote($GLOBALS['cfgRelation']['navigationhiding']);
+ $sqlQuery = "INSERT INTO " . $navTable
+ . "(`username`, `item_name`, `item_type`, `db_name`, `table_name`)"
+ . " VALUES ("
+ . "'" . PMA_Util::sqlAddSlashes($GLOBALS['cfg']['Server']['user']) . "',"
+ . "'" . PMA_Util::sqlAddSlashes($itemName) . "',"
+ . "'" . PMA_Util::sqlAddSlashes($itemType) . "',"
+ . "'" . PMA_Util::sqlAddSlashes($dbName) . "',"
+ . "'" . (! empty($tableName)? PMA_Util::sqlAddSlashes($tableName) : "" )
+ . "')";
+ PMA_queryAsControlUser($sqlQuery, false);
+ }
+
+ /**
+ * Remove a hidden item of navigation tree from the
+ * list of hidden items in PMA database.
+ *
+ * @param string $itemName name of the navigation tree item
+ * @param string $itemType type of the navigation tree item
+ * @param string $dbName database name
+ * @param string $tableName table name if applicable
+ *
+ * @return void
+ */
+ public function unhideNavigationItem(
+ $itemName, $itemType, $dbName, $tableName = null
+ ) {
+ $navTable = PMA_Util::backquote($GLOBALS['cfgRelation']['db'])
+ . "." . PMA_Util::backquote($GLOBALS['cfgRelation']['navigationhiding']);
+ $sqlQuery = "DELETE FROM " . $navTable
+ . " WHERE"
+ . " `username`='"
+ . PMA_Util::sqlAddSlashes($GLOBALS['cfg']['Server']['user']) . "'"
+ . " AND `item_name`='" . PMA_Util::sqlAddSlashes($itemName) . "'"
+ . " AND `item_type`='" . PMA_Util::sqlAddSlashes($itemType) . "'"
+ . " AND `db_name`='" . PMA_Util::sqlAddSlashes($dbName) . "'"
+ . (! empty($tableName)
+ ? " AND `table_name`='" . PMA_Util::sqlAddSlashes($tableName) . "'"
+ : ""
+ );
+ PMA_queryAsControlUser($sqlQuery, false);
+ }
+
+ /**
+ * Returns HTML for the dialog to show hidden nativation items.
+ *
+ * @param string $dbName database name
+ * @param string $itemType type of the items to include
+ * @param string $tableName table name
+ *
+ * @return string HTML for the dialog to show hidden nativation items
+ */
+ public function getItemUnhideDialog($dbName, $itemType = null, $tableName = null)
+ {
+ $html = '<form method="post" action="navigation.php" class="ajax">';
+ $html .= '<fieldset>';
+ $html .= PMA_URL_getHiddenInputs($dbName, $tableName);
+
+ $navTable = PMA_Util::backquote($GLOBALS['cfgRelation']['db'])
+ . "." . PMA_Util::backquote($GLOBALS['cfgRelation']['navigationhiding']);
+ $sqlQuery = "SELECT `item_name`, `item_type` FROM " . $navTable
+ . " WHERE `username`='"
+ . PMA_Util::sqlAddSlashes($GLOBALS['cfg']['Server']['user']) . "'"
+ . " AND `db_name`='" . PMA_Util::sqlAddSlashes($dbName) . "'"
+ . " AND `table_name`='"
+ . (! empty($tableName) ? PMA_Util::sqlAddSlashes($tableName) : '') . "'";
+ $result = PMA_queryAsControlUser($sqlQuery, false);
+
+ $hidden = array();
+ if ($result) {
+ while ($row = $GLOBALS['dbi']->fetchArray($result)) {
+ $type = $row['item_type'];
+ if (! isset($hidden[$type])) {
+ $hidden[$type] = array();
+ }
+ $hidden[$type][] = $row['item_name'];
+ }
+ }
+ $GLOBALS['dbi']->freeResult($result);
+
+ $typeMap = array(
+ 'event' => __('Events:'),
+ 'function' => __('Functions:'),
+ 'procedure' => __('Procedures:'),
+ 'table' => __('Tables:'),
+ 'view' => __('Views:'),
+ );
+ if (empty($tableName)) {
+ $first = true;
+ foreach ($typeMap as $t => $lable) {
+ if ((empty($itemType) || $itemType == $t)
+ && isset($hidden[$t])
+ ) {
+ $html .= (! $first ? '<br/>' : '')
+ . '<strong>' . $lable . '</strong>';
+ $html .= '<table width="100%"><tbody>';
+ $odd = true;
+ foreach ($hidden[$t] as $hiddenItem) {
+ $html .= '<tr class="' . ($odd ? 'odd' : 'even') . '">';
+ $html .= '<td>' . htmlspecialchars($hiddenItem) . '</td>';
+ $html .= '<td style="width:80px"><a href="navigation.php?'
+ . PMA_URL_getCommon()
+ . '&unhideNavItem=true'
+ . '&itemType=' . $t
+ . '&itemName=' . urldecode($hiddenItem)
+ . '&dbName=' . urldecode($dbName) . '"'
+ . ' class="unhideNavItem ajax">'
+ . PMA_Util::getIcon('lightbulb.png', __('Show'))
+ . '</a></td>';
+ $odd = ! $odd;
+ }
+ $html .= '</tbody></table>';
+ $first = false;
+ }
+ }
+ }
+
+ $html .= '</fieldset>';
+ $html .= '</form>';
+ return $html;
+ }
+}
+?>
diff --git a/libraries/navigation/NavigationHeader.class.php b/libraries/navigation/NavigationHeader.class.php
new file mode 100644
index 0000000000..ea6e2f2d5c
--- /dev/null
+++ b/libraries/navigation/NavigationHeader.class.php
@@ -0,0 +1,310 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Header for the navigation panel
+ *
+ * @package PhpMyAdmin-Navigation
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ * This class renders the logo, links, server selection and recent tables,
+ * which are then displayed at the top of the naviagtion panel
+ *
+ * @package PhpMyAdmin-Navigation
+ */
+class PMA_NavigationHeader
+{
+ /**
+ * Renders the navigation
+ *
+ * @return String HTML
+ */
+ public function getDisplay()
+ {
+ if (empty($GLOBALS['url_query'])) {
+ $GLOBALS['url_query'] = PMA_URL_getCommon();
+ }
+ $link_url = PMA_URL_getCommon(
+ array(
+ 'ajax_request' => true
+ )
+ );
+ $class = ' class="list_container';
+ if ($GLOBALS['cfg']['NavigationTreePointerEnable']) {
+ $class .= ' highlight';
+ }
+ $class .= '"';
+ $buffer = '<div id="pma_navigation">';
+ $buffer .= '<div id="pma_navigation_resizer"></div>';
+ $buffer .= '<div id="pma_navigation_collapser"></div>';
+ $buffer .= '<div id="pma_navigation_content">';
+ $buffer .= '<div id="pma_navigation_header">';
+ $buffer .= sprintf(
+ '<a class="hide navigation_url" href="navigation.php%s"></a>',
+ $link_url
+ );
+ $buffer .= $this->_logo();
+ $buffer .= $this->_links();
+ $buffer .= $this->_serverChoice();
+ $buffer .= $this->_recent();
+ $buffer .= PMA_Util::getImage(
+ 'ajax_clock_small.gif',
+ __('Loading…'),
+ array('style' => 'visibility: hidden; display:none', 'class' => 'throbber')
+ );
+ $buffer .= '</div>'; // pma_navigation_header
+ $buffer .= '<div id="pma_navigation_tree"' . $class . '>';
+ return $buffer;
+ }
+
+ /**
+ * Create the code for displaying the phpMyAdmin
+ * logo based on configuration settings
+ *
+ * @return string HTML code for the logo
+ */
+ private function _logo()
+ {
+ $retval = '<!-- LOGO START -->';
+ // display Logo, depending on $GLOBALS['cfg']['NavigationDisplayLogo']
+ if ($GLOBALS['cfg']['NavigationDisplayLogo']) {
+ $logo = 'phpMyAdmin';
+ if (@file_exists($GLOBALS['pmaThemeImage'] . 'logo_left.png')) {
+ $logo = '<img src="' . $GLOBALS['pmaThemeImage'] . 'logo_left.png" '
+ . 'alt="' . $logo . '" id="imgpmalogo" />';
+ } elseif (@file_exists($GLOBALS['pmaThemeImage'] . 'pma_logo2.png')) {
+ $logo = '<img src="' . $GLOBALS['pmaThemeImage'] . 'pma_logo2.png" '
+ . 'alt="' . $logo . '" id="imgpmalogo" />';
+ }
+ $retval .= '<div id="pmalogo">';
+ if ($GLOBALS['cfg']['NavigationLogoLink']) {
+ $logo_link = trim(
+ htmlspecialchars($GLOBALS['cfg']['NavigationLogoLink'])
+ );
+ // prevent XSS, see PMASA-2013-9
+ // if link has protocol, allow only http and https
+ if (preg_match('/^[a-z]+:/i', $logo_link)
+ && ! preg_match('/^https?:/i', $logo_link)
+ ) {
+ $logo_link = 'index.php';
+ }
+ $retval .= ' <a href="' . $logo_link;
+ switch ($GLOBALS['cfg']['NavigationLogoLinkWindow']) {
+ case 'new':
+ $retval .= '" target="_blank"';
+ break;
+ case 'main':
+ // do not add our parameters for an external link
+ if (substr(
+ strtolower($GLOBALS['cfg']['NavigationLogoLink']), 0, 4
+ ) !== '://') {
+ $retval .= '?' . $GLOBALS['url_query'] . '"';
+ } else {
+ $retval .= '" target="_blank"';
+ }
+ }
+ $retval .= '>';
+ $retval .= $logo;
+ $retval .= '</a>';
+ } else {
+ $retval .= $logo;
+ }
+ $retval .= '</div>';
+ }
+ $retval .= '<!-- LOGO END -->';
+ return $retval;
+ }
+
+ /**
+ * Renders a single link for the top of the navigation panel
+ *
+ * @param string $link The url for the link
+ * @param bool $showText Whether to show the text or to
+ * only use it for title attributes
+ * @param string $text The text to display and use for title attributes
+ * @param bool $showIcon Whether to show the icon
+ * @param string $icon The filename of the icon to show
+ * @param string $linkId Value to use for the ID attribute
+ * @param boolean $disableAjax Whether to disable ajax page loading for this link
+ * @param string $linkTarget The name of the target frame for the link
+ *
+ * @return string HTML code for one link
+ */
+ private function _getLink(
+ $link,
+ $showText,
+ $text,
+ $showIcon,
+ $icon,
+ $linkId = '',
+ $disableAjax = false,
+ $linkTarget = ''
+ ) {
+ $retval = '<a href="' . $link . '"';
+ if (! empty($linkId)) {
+ $retval .= ' id="' . $linkId . '"';
+ }
+ if (! empty($linkTarget)) {
+ $retval .= ' target="' . $linkTarget . '"';
+ }
+ if ($disableAjax) {
+ $retval .= ' class="disableAjax"';
+ }
+ $retval .= ' title="' . $text . '">';
+ if ($showIcon) {
+ $retval .= PMA_Util::getImage(
+ $icon,
+ $text
+ );
+ }
+ if ($showText) {
+ $retval .= $text;
+ }
+ $retval .= '</a>';
+ if ($showText) {
+ $retval .= '<br />';
+ }
+ return $retval;
+ }
+
+ /**
+ * Creates the code for displaying the links
+ * at the top of the navigation panel
+ *
+ * @return string HTML code for the links
+ */
+ private function _links()
+ {
+ // always iconic
+ $showIcon = true;
+ $showText = false;
+
+ $retval = '<!-- LINKS START -->';
+ $retval .= '<div id="leftframelinks">';
+ $retval .= $this->_getLink(
+ 'index.php?' . PMA_URL_getCommon(),
+ $showText,
+ __('Home'),
+ $showIcon,
+ 'b_home.png'
+ );
+ // if we have chosen server
+ if ($GLOBALS['server'] != 0) {
+ // Logout for advanced authentication
+ if ($GLOBALS['cfg']['Server']['auth_type'] != 'config') {
+ $link = 'index.php?' . $GLOBALS['url_query'];
+ $link .= '&amp;old_usr=' . urlencode($GLOBALS['PHP_AUTH_USER']);
+ $retval .= $this->_getLink(
+ $link,
+ $showText,
+ __('Log out'),
+ $showIcon,
+ 's_loggoff.png',
+ '',
+ true
+ );
+ }
+ $link = 'querywindow.php?';
+ $link .= PMA_URL_getCommon($GLOBALS['db'], $GLOBALS['table']);
+ $link .= '&amp;no_js=true';
+ $retval .= $this->_getLink(
+ $link,
+ $showText,
+ __('Query window'),
+ $showIcon,
+ 'b_selboard.png',
+ 'pma_open_querywindow',
+ true
+ );
+ }
+ $retval .= $this->_getLink(
+ PMA_Util::getDocuLink('index'),
+ $showText,
+ __('phpMyAdmin documentation'),
+ $showIcon,
+ 'b_docs.png',
+ '',
+ false,
+ 'documentation'
+ );
+ if ($showIcon) {
+ $retval .= PMA_Util::showMySQLDocu('', true);
+ }
+ if ($showText) {
+ // PMA_showMySQLDocu always spits out an icon,
+ // we just replace it with some perl regexp.
+ $link = preg_replace(
+ '/<img[^>]+>/i',
+ __('Documentation'),
+ PMA_Util::showMySQLDocu('', true)
+ );
+ $retval .= $link;
+ $retval .= '<br />';
+ }
+ $retval .= $this->_getLink(
+ '#',
+ $showText,
+ __('Reload navigation panel'),
+ $showIcon,
+ 's_reload.png',
+ 'pma_navigation_reload'
+ );
+ $retval .= '</div>';
+ $retval .= '<!-- LINKS ENDS -->';
+ return $retval;
+ }
+
+ /**
+ * Displays the MySQL servers choice form
+ *
+ * @return string HTML code for the MySQL servers choice
+ */
+ private function _serverChoice()
+ {
+ $retval = '';
+ if ($GLOBALS['cfg']['NavigationDisplayServers']
+ && count($GLOBALS['cfg']['Servers']) > 1
+ ) {
+ include_once './libraries/select_server.lib.php';
+ $retval .= '<!-- SERVER CHOICE START -->';
+ $retval .= '<div id="serverChoice">';
+ $retval .= PMA_selectServer(true, true);
+ $retval .= '</div>';
+ $retval .= '<!-- SERVER CHOICE END -->';
+ }
+ return $retval;
+ }
+
+ /**
+ * Displays a drop-down choice of most recently used tables
+ *
+ * @return string HTML code for the Recent tables
+ */
+ private function _recent()
+ {
+ $retval = '';
+ // display recently used tables
+ if ($GLOBALS['cfg']['NumRecentTables'] > 0) {
+ $retval .= '<!-- RECENT START -->';
+ $retval .= '<div id="recentTableList">';
+ $retval .= '<form method="post" ';
+ $retval .= 'action="' . $GLOBALS['cfg']['DefaultTabTable'] . '">';
+ $retval .= PMA_URL_getHiddenInputs(
+ array(
+ 'db' => '',
+ 'table' => '',
+ 'server' => $GLOBALS['server']
+ )
+ );
+ $retval .= PMA_RecentTable::getInstance()->getHtmlSelect();
+ $retval .= '</form>';
+ $retval .= '</div>';
+ $retval .= '<!-- RECENT END -->';
+ }
+ return $retval;
+ }
+}
+?>
diff --git a/libraries/navigation/NavigationTree.class.php b/libraries/navigation/NavigationTree.class.php
new file mode 100644
index 0000000000..2d8e0ae996
--- /dev/null
+++ b/libraries/navigation/NavigationTree.class.php
@@ -0,0 +1,1167 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Functionality for the navigation tree
+ *
+ * @package PhpMyAdmin-Navigation
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ * Displays a collapsible of database objects in the navigation frame
+ *
+ * @package PhpMyAdmin-Navigation
+ */
+class PMA_NavigationTree
+{
+ /**
+ * @var Node Reference to the root node of the tree
+ */
+ private $_tree;
+
+ /**
+ * @var array The actual paths to all expanded nodes in the tree
+ * This does not include nodes created after the grouping
+ * of nodes has been performed
+ */
+ private $_aPath = array();
+
+ /**
+ * @var array The virtual paths to all expanded nodes in the tree
+ * This includes nodes created after the grouping of
+ * nodes has been performed
+ */
+ private $_vPath = array();
+
+ /**
+ * @var int Position in the list of databases,
+ * used for pagination
+ */
+ private $_pos;
+
+ /**
+ * @var int The names of the type of items that are being paginated on
+ * the second level of the navigation tree. These may be
+ * tables, views, functions, procedures or events.
+ */
+ private $_pos2_name = array();
+
+ /**
+ * @var int The positions of nodes in the lists of tables, views,
+ * routines or events used for pagination
+ */
+ private $_pos2_value = array();
+
+ /**
+ * @var int The names of the type of items that are being paginated
+ * on the second level of the navigation tree.
+ * These may be columns or indexes
+ */
+ private $_pos3_name = array();
+
+ /**
+ * @var int The positions of nodes in the lists of columns or indexes
+ * used for pagination
+ */
+ private $_pos3_value = array();
+
+ /**
+ * @var string The search clause to use in SQL queries for
+ * fetching databases
+ * Used by the asynchronous fast filter
+ */
+ private $_searchClause = '';
+
+ /**
+ * @var string The search clause to use in SQL queries for
+ * fetching nodes
+ * Used by the asynchronous fast filter
+ */
+ private $_searchClause2 = '';
+
+ /**
+ * Initialises the class
+ */
+ public function __construct()
+ {
+ // Save the position at which we are in the database list
+ if (isset($_REQUEST['pos'])) {
+ $this->_pos = (int) $_REQUEST['pos'];
+ }
+ if (! isset($this->_pos)) {
+ $this->_pos = $this->_getNavigationDbPos();
+ }
+ // Get the active node
+ if (isset($_REQUEST['aPath'])) {
+ $this->_aPath[0] = $this->_parsePath($_REQUEST['aPath']);
+ $this->_pos2_name[0] = $_REQUEST['pos2_name'];
+ $this->_pos2_value[0] = $_REQUEST['pos2_value'];
+ if (isset($_REQUEST['pos3_name'])) {
+ $this->_pos3_name[0] = $_REQUEST['pos3_name'];
+ $this->_pos3_value[0] = $_REQUEST['pos3_value'];
+ }
+ } else if (isset($_REQUEST['n0_aPath'])) {
+ $count = 0;
+ while (isset($_REQUEST['n' . $count . '_aPath'])) {
+ $this->_aPath[$count] = $this->_parsePath(
+ $_REQUEST['n' . $count . '_aPath']
+ );
+ $index = 'n' . $count . '_pos2_';
+ $this->_pos2_name[$count] = $_REQUEST[$index . 'name'];
+ $this->_pos2_value[$count] = $_REQUEST[$index . 'value'];
+ $index = 'n' . $count . '_pos3_';
+ if (isset($_REQUEST[$index])) {
+ $this->_pos3_name[$count] = $_REQUEST[$index . 'name'];
+ $this->_pos3_value[$count] = $_REQUEST[$index . 'value'];
+ }
+ $count++;
+ }
+ }
+ if (isset($_REQUEST['vPath'])) {
+ $this->_vPath[0] = $this->_parsePath($_REQUEST['vPath']);
+ } else if (isset($_REQUEST['n0_vPath'])) {
+ $count = 0;
+ while (isset($_REQUEST['n' . $count . '_vPath'])) {
+ $this->_vPath[$count] = $this->_parsePath(
+ $_REQUEST['n' . $count . '_vPath']
+ );
+ $count++;
+ }
+ }
+ if (isset($_REQUEST['searchClause'])) {
+ $this->_searchClause = $_REQUEST['searchClause'];
+ }
+ if (isset($_REQUEST['searchClause2'])) {
+ $this->_searchClause2 = $_REQUEST['searchClause2'];
+ }
+ // Initialise the tree by creating a root node
+ $node = PMA_NodeFactory::getInstance('Node_Database_Container', 'root');
+ $this->_tree = $node;
+ if ($GLOBALS['cfg']['NavigationTreeEnableGrouping']) {
+ $this->_tree->separator = $GLOBALS['cfg']['NavigationTreeDbSeparator'];
+ $this->_tree->separator_depth = 10000;
+ }
+ }
+
+ /**
+ * Returns the database position for the page selector
+ *
+ * @return int
+ */
+ private function _getNavigationDbPos()
+ {
+ $retval = 0;
+ if (! empty($GLOBALS['db'])) {
+ $query = "SELECT (COUNT(`SCHEMA_NAME`) DIV %d) * %d ";
+ $query .= "FROM `INFORMATION_SCHEMA`.`SCHEMATA` ";
+ $query .= "WHERE `SCHEMA_NAME` < '%s' ";
+ $query .= "ORDER BY `SCHEMA_NAME` ASC";
+ $retval = $GLOBALS['dbi']->fetchValue(
+ sprintf(
+ $query,
+ (int)$GLOBALS['cfg']['MaxNavigationItems'],
+ (int)$GLOBALS['cfg']['MaxNavigationItems'],
+ PMA_Util::sqlAddSlashes($GLOBALS['db'])
+ )
+ );
+ }
+ return $retval;
+ }
+
+ /**
+ * Converts an encoded path to a node in string format to an array
+ *
+ * @param string $string The path to parse
+ *
+ * @return array
+ */
+ private function _parsePath($string)
+ {
+ $path = explode('.', $string);
+ foreach ($path as $key => $value) {
+ $path[$key] = base64_decode($value);
+ }
+ return $path;
+ }
+
+ /**
+ * Generates the tree structure so that it can be rendered later
+ *
+ * @return Node|false The active node or false in case of failure
+ */
+ private function _buildPath()
+ {
+ $retval = $this->_tree;
+
+ // Add all databases unconditionally
+ $data = $this->_tree->getData(
+ 'databases',
+ $this->_pos,
+ $this->_searchClause
+ );
+ foreach ($data as $db) {
+ $node = PMA_NodeFactory::getInstance('Node_Database', $db);
+ $this->_tree->addChild($node);
+ }
+
+ // Whether build other parts of the tree depends
+ // on whether we have any paths in $this->_aPath
+ foreach ($this->_aPath as $key => $path) {
+ $retval = $this->_buildPathPart(
+ $path,
+ $this->_pos2_name[$key],
+ $this->_pos2_value[$key],
+ isset($this->_pos3_name[$key]) ? $this->_pos3_name[$key] : '',
+ isset($this->_pos3_value[$key]) ? $this->_pos3_value[$key] : ''
+ );
+ }
+ return $retval;
+ }
+
+ /**
+ * Builds a branch of the tree
+ *
+ * @param array $path A paths pointing to the branch
+ * of the tree that needs to be built
+ * @param string $type2 The type of item being paginated on
+ * the second level of the tree
+ * @param int $pos2 The position for the pagination of
+ * the branch at the second level of the tree
+ * @param string $type3 The type of item being paginated on
+ * the third level of the tree
+ * @param int $pos3 The position for the pagination of
+ * the branch at the third level of the tree
+ *
+ * @return Node|false The active node or false in case of failure
+ */
+ private function _buildPathPart($path, $type2, $pos2, $type3, $pos3)
+ {
+ $retval = true;
+ if (count($path) > 1) {
+ array_shift($path); // remove 'root'
+ $db = $this->_tree->getChild($path[0]);
+ $retval = $db;
+
+ if ($db === false) {
+ return false;
+ }
+
+ $containers = $this->_addDbContainers($db, $type2, $pos2);
+
+ array_shift($path); // remove db
+
+ if ((count($path) > 0
+ && array_key_exists($path[0], $containers))
+ || count($containers) == 1
+ ) {
+ if (count($containers) == 1) {
+ $container = array_shift($containers);
+ } else {
+ $container = $db->getChild($path[0], true);
+ if ($container === false) {
+ return false;
+ }
+ }
+ $retval = $container;
+
+ if (count($container->children) <= 1) {
+ $dbData = $db->getData(
+ $container->real_name,
+ $pos2,
+ $this->_searchClause2
+ );
+ foreach ($dbData as $item) {
+ switch ($container->real_name) {
+ case 'events':
+ $node = PMA_NodeFactory::getInstance(
+ 'Node_Event',
+ $item
+ );
+ break;
+ case 'functions':
+ $node = PMA_NodeFactory::getInstance(
+ 'Node_Function',
+ $item
+ );
+ break;
+ case 'procedures':
+ $node = PMA_NodeFactory::getInstance(
+ 'Node_Procedure',
+ $item
+ );
+ break;
+ case 'tables':
+ $node = PMA_NodeFactory::getInstance(
+ 'Node_Table',
+ $item
+ );
+ break;
+ case 'views':
+ $node = PMA_NodeFactory::getInstance(
+ 'Node_View',
+ $item
+ );
+ break;
+ default:
+ break;
+ }
+ if (isset($node)) {
+ if ($type2 == $container->real_name) {
+ $node->pos2 = $pos2;
+ }
+ $container->addChild($node);
+ }
+ }
+ }
+ if (count($path) > 1 && $path[0] != 'tables') {
+ $retval = false;
+ } else {
+ array_shift($path); // remove container
+ if (count($path) > 0) {
+ $table = $container->getChild($path[0], true);
+ if ($table === false) {
+ return false;
+ }
+ $retval = $table;
+ $containers = $this->_addTableContainers(
+ $table,
+ $pos2,
+ $type3,
+ $pos3
+ );
+ array_shift($path); // remove table
+ if (count($path) > 0
+ && array_key_exists($path[0], $containers)
+ ) {
+ $container = $table->getChild($path[0], true);
+ $retval = $container;
+ $tableData = $table->getData(
+ $container->real_name,
+ $pos3
+ );
+ foreach ($tableData as $item) {
+ switch ($container->real_name) {
+ case 'indexes':
+ $node = PMA_NodeFactory::getInstance(
+ 'Node_Index',
+ $item
+ );
+ break;
+ case 'columns':
+ $node = PMA_NodeFactory::getInstance(
+ 'Node_Column',
+ $item
+ );
+ break;
+ case 'triggers':
+ $node = PMA_NodeFactory::getInstance(
+ 'Node_Trigger',
+ $item
+ );
+ break;
+ default:
+ break;
+ }
+ if (isset($node)) {
+ $node->pos2 = $container->parent->pos2;
+ if ($type3 == $container->real_name) {
+ $node->pos3 = $pos3;
+ }
+ $container->addChild($node);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ return $retval;
+ }
+
+ /**
+ * Adds containers to a node that is a table
+ *
+ * References to existing children are returned
+ * if this function is called twice on the same node
+ *
+ * @param Node $table The table node, new containers will be
+ * attached to this node
+ * @param int $pos2 The position for the pagination of
+ * the branch at the second level of the tree
+ * @param string $type3 The type of item being paginated on
+ * the third level of the tree
+ * @param int $pos3 The position for the pagination of
+ * the branch at the third level of the tree
+ *
+ * @return array An array of new nodes
+ */
+ private function _addTableContainers($table, $pos2, $type3, $pos3)
+ {
+ $retval = array();
+ if ($table->hasChildren(true) == 0) {
+ if ($table->getPresence('columns')) {
+ $retval['columns'] = PMA_NodeFactory::getInstance(
+ 'Node_Column_Container'
+ );
+ }
+ if ($table->getPresence('indexes')) {
+ $retval['indexes'] = PMA_NodeFactory::getInstance(
+ 'Node_Index_Container'
+ );
+ }
+ if ($table->getPresence('triggers')) {
+ $retval['triggers'] = PMA_NodeFactory::getInstance(
+ 'Node_Trigger_Container'
+ );
+ }
+ // Add all new Nodes to the tree
+ foreach ($retval as $node) {
+ $node->pos2 = $pos2;
+ if ($type3 == $node->real_name) {
+ $node->pos3 = $pos3;
+ }
+ $table->addChild($node);
+ }
+ } else {
+ foreach ($table->children as $node) {
+ if ($type3 == $node->real_name) {
+ $node->pos3 = $pos3;
+ }
+ $retval[$node->real_name] = $node;
+ }
+ }
+ return $retval;
+ }
+
+ /**
+ * Adds containers to a node that is a database
+ *
+ * References to existing children are returned
+ * if this function is called twice on the same node
+ *
+ * @param Node $db The database node, new containers will be
+ * attached to this node
+ * @param string $type The type of item being paginated on
+ * the second level of the tree
+ * @param int $pos2 The position for the pagination of
+ * the branch at the second level of the tree
+ *
+ * @return array An array of new nodes
+ */
+ private function _addDbContainers($db, $type, $pos2)
+ {
+ $retval = array();
+ if ($db->hasChildren(true) == 0) {
+ if ($db->getPresence('tables')) {
+ $retval['tables'] = PMA_NodeFactory::getInstance(
+ 'Node_Table_Container'
+ );
+ }
+ if ($db->getPresence('views')) {
+ $retval['views'] = PMA_NodeFactory::getInstance(
+ 'Node_View_Container'
+ );
+ }
+ if ($db->getPresence('functions')) {
+ $retval['functions'] = PMA_NodeFactory::getInstance(
+ 'Node_Function_Container'
+ );
+ }
+ if ($db->getPresence('procedures')) {
+ $retval['procedures'] = PMA_NodeFactory::getInstance(
+ 'Node_Procedure_Container'
+ );
+ }
+ if ($db->getPresence('events')) {
+ $retval['events'] = PMA_NodeFactory::getInstance(
+ 'Node_Event_Container'
+ );
+ }
+ // Add all new Nodes to the tree
+ foreach ($retval as $node) {
+ if ($type == $node->real_name) {
+ $node->pos2 = $pos2;
+ }
+ $db->addChild($node);
+ }
+ } else {
+ foreach ($db->children as $node) {
+ if ($type == $node->real_name) {
+ $node->pos2 = $pos2;
+ }
+ $retval[$node->real_name] = $node;
+ }
+ }
+ return $retval;
+ }
+
+ /**
+ * Recursively groups tree nodes given a separator
+ *
+ * @param mixed $node The node to group or null
+ * to group the whole tree. If
+ * passed as an argument, $node
+ * must be of type CONTAINER
+ *
+ * @return void
+ */
+ public function groupTree($node = null)
+ {
+ if (! isset($node)) {
+ $node = $this->_tree;
+ }
+ $this->groupNode($node);
+ foreach ($node->children as $child) {
+ $this->groupTree($child);
+ }
+ }
+
+ /**
+ * Recursively groups tree nodes given a sperarator
+ *
+ * @param Node $node The node to group
+ *
+ * @return void
+ */
+ public function groupNode($node)
+ {
+ if ($node->type == Node::CONTAINER) {
+ $separators = array();
+ if (is_array($node->separator)) {
+ $separators = $node->separator;
+ } else if (strlen($node->separator)) {
+ $separators[] = $node->separator;
+ }
+ $prefixes = array();
+ if ($node->separator_depth > 0) {
+ foreach ($node->children as $child) {
+ $prefix_pos = false;
+ foreach ($separators as $separator) {
+ $sep_pos = strpos($child->name, $separator);
+ if ($sep_pos != false
+ && $sep_pos != strlen($child->name)
+ && $sep_pos != 0
+ && ($prefix_pos == false || $sep_pos < $prefix_pos)
+ ) {
+ $prefix_pos = $sep_pos;
+ }
+ }
+ if ($prefix_pos !== false) {
+ $prefix = substr($child->name, 0, $prefix_pos);
+ if (! isset($prefixes[$prefix])) {
+ $prefixes[$prefix] = 1;
+ } else {
+ $prefixes[$prefix]++;
+ }
+ }
+ }
+ }
+ foreach ($prefixes as $key => $value) {
+ if ($value == 1) {
+ unset($prefixes[$key]);
+ }
+ }
+ if (count($prefixes)) {
+ $groups = array();
+ foreach ($prefixes as $key => $value) {
+ $groups[$key] = new Node(
+ $key,
+ Node::CONTAINER,
+ true
+ );
+ $groups[$key]->separator = $node->separator;
+ $groups[$key]->separator_depth = $node->separator_depth - 1;
+ $groups[$key]->icon = '';
+ if (PMA_Util::showIcons('TableNavigationLinksMode')) {
+ $groups[$key]->icon = PMA_Util::getImage(
+ 'b_group.png'
+ );
+ }
+ $groups[$key]->pos2 = $node->pos2;
+ $groups[$key]->pos3 = $node->pos3;
+ if ($node instanceof Node_Table_Container
+ || $node instanceof Node_View_Container
+ ) {
+ $tblGroup = '&amp;tbl_group=' . urlencode($key);
+ $groups[$key]->links = array(
+ 'text' => $node->links['text'] . $tblGroup,
+ 'icon' => $node->links['icon'] . $tblGroup
+ );
+ }
+ $node->addChild($groups[$key]);
+ foreach ($separators as $separator) {
+ // FIXME: this could be more efficient
+ foreach ($node->children as $child) {
+ $name_substring = substr(
+ $child->name, 0, strlen($key) + strlen($separator)
+ );
+ if (($name_substring == $key . $separator
+ || $child->name == $key)
+ && $child->type == Node::OBJECT
+ ) {
+ $class = get_class($child);
+ $new_child = PMA_NodeFactory::getInstance(
+ $class,
+ substr(
+ $child->name,
+ strlen($key) + strlen($separator)
+ )
+ );
+ $new_child->real_name = $child->real_name;
+ $new_child->icon = $child->icon;
+ $new_child->links = $child->links;
+ $new_child->pos2 = $child->pos2;
+ $new_child->pos3 = $child->pos3;
+ $groups[$key]->addChild($new_child);
+ foreach ($child->children as $elm) {
+ $new_child->addChild($elm);
+ }
+ $node->removeChild($child->name);
+ }
+ }
+ }
+ }
+ foreach ($prefixes as $key => $value) {
+ $this->groupNode($groups[$key]);
+ $groups[$key]->classes = "navGroup";
+ }
+ }
+ }
+ }
+
+ /**
+ * Renders a state of the tree, used in light mode when
+ * either JavaScript and/or Ajax are disabled
+ *
+ * @return string HTML code for the navigation tree
+ */
+ public function renderState()
+ {
+ $this->_buildPath();
+ $retval = $this->_fastFilterHtml($this->_tree);
+ $retval .= $this->_getPageSelector($this->_tree);
+ $this->groupTree();
+ $retval .= "<div id='pma_navigation_tree_content'><ul>";
+ $children = $this->_tree->children;
+ usort($children, array('PMA_NavigationTree', 'sortNode'));
+ $this->_setVisibility();
+ for ($i=0; $i<count($children); $i++) {
+ if ($i == 0) {
+ $retval .= $this->_renderNode($children[0], true, 'first');
+ } else if ($i + 1 != count($children)) {
+ $retval .= $this->_renderNode($children[$i], true);
+ } else {
+ $retval .= $this->_renderNode($children[$i], true, 'last');
+ }
+ }
+ $retval .= "</ul></div>";
+ return $retval;
+ }
+
+ /**
+ * Renders a part of the tree, used for Ajax
+ * requests in light mode
+ *
+ * @return string HTML code for the navigation tree
+ */
+ public function renderPath()
+ {
+ $node = $this->_buildPath();
+ if ($node === false) {
+ $retval = false;
+ } else {
+ $this->groupTree();
+ $retval = "<div class='list_container' style='display: none;'>";
+ $retval .= "<ul>";
+ $retval .= $this->_fastFilterHtml($node);
+ $retval .= $this->_getPageSelector($node);
+ $children = $node->children;
+ usort($children, array('PMA_NavigationTree', 'sortNode'));
+ for ($i=0; $i<count($children); $i++) {
+ if ($i + 1 != count($children)) {
+ $retval .= $this->_renderNode($children[$i], true);
+ } else {
+ $retval .= $this->_renderNode($children[$i], true, 'last');
+ }
+ }
+ $retval .= "</ul>";
+ $retval .= "</div>";
+ }
+
+ if (! empty($this->_searchClause) || ! empty($this->_searchClause2)) {
+ $results = 0;
+ if (! empty($this->_searchClause2)) {
+ if (is_object($node->realParent())) {
+ $results = $node->realParent()->getPresence(
+ $node->real_name,
+ $this->_searchClause2
+ );
+ }
+ } else {
+ $results = $this->_tree->getPresence(
+ 'databases',
+ $this->_searchClause
+ );
+ }
+
+ $clientResults = 0;
+ if (! empty($_REQUEST['results'])) {
+ $clientResults = (int)$_REQUEST['results'];
+ }
+ $otherResults = $results - $clientResults;
+ if ($otherResults < 1) {
+ $otherResults = '';
+ } else {
+ $otherResults = sprintf(
+ _ngettext(
+ '%s other result found',
+ '%s other results found',
+ $otherResults
+ ),
+ $otherResults
+ );
+ }
+ PMA_Response::getInstance()->addJSON(
+ 'results',
+ $otherResults
+ );
+ }
+ return $retval;
+ }
+
+ /**
+ * Renders the parameters that are required on the client
+ * side to know which page(s) we will be requesting data from
+ *
+ * @param Node $node The node to create the pagination parameters for
+ *
+ * @return string
+ */
+ private function _getPaginationParamsHtml($node)
+ {
+ $retval = '';
+ $paths = $node->getPaths();
+ if (isset($paths['aPath_clean'][2])) {
+ $retval .= "<span class='hide pos2_name'>";
+ $retval .= $paths['aPath_clean'][2];
+ $retval .= "</span>";
+ $retval .= "<span class='hide pos2_value'>";
+ $retval .= $node->pos2;
+ $retval .= "</span>";
+ }
+ if (isset($paths['aPath_clean'][4])) {
+ $retval .= "<span class='hide pos3_name'>";
+ $retval .= $paths['aPath_clean'][4];
+ $retval .= "</span>";
+ $retval .= "<span class='hide pos3_value'>";
+ $retval .= $node->pos3;
+ $retval .= "</span>";
+ }
+ return $retval;
+ }
+
+ /**
+ * Renders a single node or a branch of the tree
+ *
+ * @param Node $node The node to render
+ * @param int|bool $recursive Bool: Whether to render a single node or a branch
+ * Int: How many levels deep to render
+ * @param string $class An additional class for the list item
+ *
+ * @return string HTML code for the tree node or branch
+ */
+ private function _renderNode($node, $recursive = -1, $class = '')
+ {
+ $retval = '';
+ $paths = $node->getPaths();
+ if ($node->hasSiblings()
+ || isset($_REQUEST['results'])
+ || $node->realParent() === false
+ ) {
+ if ( $node->type == Node::CONTAINER
+ && count($node->children) == 0
+ && $GLOBALS['is_ajax_request'] != true
+ ) {
+ return '';
+ }
+ $liClass = '';
+ if ($class || $node->classes) {
+ $liClass = " class='" . trim($class . ' ' . $node->classes) . "'";
+ }
+ $retval .= "<li$liClass>";
+ $sterile = array(
+ 'events',
+ 'triggers',
+ 'functions',
+ 'procedures',
+ 'views',
+ 'columns',
+ 'indexes'
+ );
+ $parentName = '';
+ $parents = $node->parents(false, true);
+ if (count($parents)) {
+ $parentName = $parents[0]->real_name;
+ }
+ if ($node->is_group
+ || (! in_array($parentName, $sterile) && ! $node->isNew)
+ || (in_array($node->real_name, $sterile)) //if node name itself is in sterile, then allow
+ ) {
+ $loaded = '';
+ if ($node->is_group) {
+ $loaded = ' loaded';
+ }
+ $container = '';
+ if ($node->type == Node::CONTAINER) {
+ $container = ' container';
+ }
+ $retval .= "<div class='block'>";
+ $iClass = '';
+ if ($class == 'first') {
+ $iClass = " class='first'";
+ }
+ $retval .= "<i$iClass></i>";
+ if (strpos($class, 'last') === false) {
+ $retval .= "<b></b>";
+ }
+ $icon = PMA_Util::getImage('b_plus.png', __('Expand/Collapse'));
+ $match = 1;
+ foreach ($this->_aPath as $path) {
+ $match = 1;
+ foreach ($paths['aPath_clean'] as $key => $part) {
+ if (! isset($path[$key]) || $part != $path[$key]) {
+ $match = 0;
+ break;
+ }
+ }
+ if ($match) {
+ $loaded = ' loaded';
+ if (! $node->is_group) {
+ $icon = PMA_Util::getImage(
+ 'b_minus.png'
+ );
+ }
+ break;
+ }
+ }
+
+ foreach ($this->_vPath as $path) {
+ $match = 1;
+ foreach ($paths['vPath_clean'] as $key => $part) {
+ if ((! isset($path[$key]) || $part != $path[$key])) {
+ $match = 0;
+ break;
+ }
+ }
+ if ($match) {
+ $loaded = ' loaded';
+ $icon = PMA_Util::getImage('b_minus.png');
+ break;
+ }
+ }
+
+ $retval .= "<a class='expander$loaded$container'";
+ $retval .= " href='#'>";
+ $retval .= "<span class='hide aPath'>";
+ $retval .= $paths['aPath'];
+ $retval .= "</span>";
+ $retval .= "<span class='hide vPath'>";
+ $retval .= $paths['vPath'];
+ $retval .= "</span>";
+ $retval .= "<span class='hide pos'>";
+ $retval .= $this->_pos;
+ $retval .= "</span>";
+ $retval .= $this->_getPaginationParamsHtml($node);
+ $retval .= $icon;
+
+ $retval .= "</a>";
+ $retval .= "</div>";
+ } else {
+ $retval .= "<div class='block'>";
+ $iClass = '';
+ if ($class == 'first') {
+ $iClass = " class='first'";
+ }
+ $retval .= "<i$iClass></i>";
+ $retval .= $this->_getPaginationParamsHtml($node);
+ $retval .= "</div>";
+ }
+
+ $linkClass = '';
+ $haveAjax = array(
+ 'functions',
+ 'procedures',
+ 'events',
+ 'triggers',
+ 'indexes'
+ );
+ $parent = $node->parents(false, true);
+ $isNewView = $parent[0]->real_name == 'views' && $node->isNew == true;
+ if ($parent[0]->type == Node::CONTAINER
+ && (in_array($parent[0]->real_name, $haveAjax) || $isNewView)
+ ) {
+ $linkClass = ' class="ajax"';
+ }
+
+ if ($node->type == Node::CONTAINER) {
+ $retval .= "<i>";
+ }
+ if (PMA_Util::showIcons('TableNavigationLinksMode')) {
+ $retval .= "<div class='block'>";
+ if (isset($node->links['icon'])) {
+ $args = array();
+ foreach ($node->parents(true) as $parent) {
+ $args[] = urlencode($parent->real_name);
+ }
+ $link = vsprintf($node->links['icon'], $args);
+ $retval .= "<a$linkClass href='$link'>{$node->icon}</a>";
+ } else {
+ $retval .= "<u>{$node->icon}</u>";
+ }
+ $retval .= "</div>";
+ }
+ if (isset($node->links['text'])) {
+ $args = array();
+ foreach ($node->parents(true) as $parent) {
+ $args[] = urlencode($parent->real_name);
+ }
+ $link = vsprintf($node->links['text'], $args);
+ if ($node->type == Node::CONTAINER) {
+ $retval .= "<a href='$link'>";
+ $retval .= htmlspecialchars($node->name);
+ $retval .= "</a>";
+ } else {
+ $retval .= "<a$linkClass href='$link'>";
+ $retval .= htmlspecialchars($node->real_name);
+ $retval .= "</a>";
+ }
+ } else {
+ $retval .= "{$node->name}";
+ }
+ if ($node->type == Node::CONTAINER) {
+ $retval .= "</i>";
+ }
+ $retval .= $node->getHtmlForControlButtons();
+ $wrap = true;
+ } else {
+ $node->visible = true;
+ $wrap = false;
+ $retval .= $this->_getPaginationParamsHtml($node);
+ }
+
+ if ($recursive) {
+ $hide = '';
+ if ($node->visible == false) {
+ $hide = " style='display: none;'";
+ }
+ $children = $node->children;
+ usort($children, array('PMA_NavigationTree', 'sortNode'));
+ $buffer = '';
+ for ($i=0; $i<count($children); $i++) {
+ if ($i + 1 != count($children)) {
+ $buffer .= $this->_renderNode(
+ $children[$i],
+ true,
+ $children[$i]->classes
+ );
+ } else {
+ $buffer .= $this->_renderNode(
+ $children[$i],
+ true,
+ $children[$i]->classes . ' last'
+ );
+ }
+ }
+ if (! empty($buffer)) {
+ if ($wrap) {
+ $retval .= "<div$hide class='list_container'><ul>";
+ }
+ $retval .= $this->_fastFilterHtml($node);
+ $retval .= $this->_getPageSelector($node);
+ $retval .= $buffer;
+ if ($wrap) {
+ $retval .= "</ul></div>";
+ }
+ }
+ }
+ if ($node->hasSiblings() || isset($_REQUEST['results'])) {
+ $retval .= "</li>";
+ }
+ return $retval;
+ }
+
+ /**
+ * Makes some nodes visible based on the which node is active
+ *
+ * @return void
+ */
+ private function _setVisibility()
+ {
+ foreach ($this->_vPath as $path) {
+ $node = $this->_tree;
+ foreach ($path as $value) {
+ $child = $node->getChild($value);
+ if ($child !== false) {
+ $child->visible = true;
+ $node = $child;
+ }
+ }
+ }
+ }
+
+ /**
+ * Generates the HTML code for displaying the fast filter for tables
+ *
+ * @param Node $node The node for which to generate the fast filter html
+ *
+ * @return string LI element used for the fast filter
+ */
+ private function _fastFilterHtml($node)
+ {
+ $retval = '';
+ $filter_min = (int)$GLOBALS['cfg']['NavigationTreeDisplayDbFilterMinimum'];
+ if ($node === $this->_tree
+ && $this->_tree->getPresence() >= $filter_min
+ ) {
+ $url_params = array(
+ 'pos' => 0
+ );
+ $retval .= '<ul>';
+ $retval .= '<li class="fast_filter db_fast_filter">';
+ $retval .= '<form class="ajax fast_filter">';
+ $retval .= PMA_getHiddenFields($url_params);
+ $retval .= '<input class="searchClause" name="searchClause"';
+ $retval .= ' accesskey="q"';
+ // allow html5 placeholder attribute
+ $placeholder_key = 'value';
+ if (PMA_USR_BROWSER_AGENT !== 'IE'
+ || PMA_USR_BROWSER_VER > 9
+ ) {
+ $placeholder_key = 'placeholder';
+ }
+ $retval .= " $placeholder_key='"
+ . __('Filter databases by name or regex');
+ $retval .= "' />";
+ $retval .= '<span title="' . __('Clear fast filter') . '">X</span>';
+ $retval .= "</form>";
+ $retval .= "</li>";
+ $retval .= "</ul>";
+ } else if (($node->type == Node::CONTAINER
+ && ( $node->real_name == 'tables'
+ || $node->real_name == 'views'
+ || $node->real_name == 'functions'
+ || $node->real_name == 'procedures'
+ || $node->real_name == 'events'))
+ && method_exists($node->realParent(), 'getPresence')
+ && $node->realParent()->getPresence($node->real_name) >= $filter_min
+ ) {
+ $paths = $node->getPaths();
+ $url_params = array(
+ 'pos' => $this->_pos,
+ 'aPath' => $paths['aPath'],
+ 'vPath' => $paths['vPath'],
+ 'pos2_name' => $node->real_name,
+ 'pos2_value' => 0
+ );
+ $retval .= "<li class='fast_filter'>";
+ $retval .= "<form class='ajax fast_filter'>";
+ $retval .= PMA_getHiddenFields($url_params);
+ $retval .= "<input class='searchClause' name='searchClause2'";
+ // allow html5 placeholder attribute
+ $placeholder_key = 'value';
+ if (PMA_USR_BROWSER_AGENT !== 'IE'
+ || PMA_USR_BROWSER_VER > 9
+ ) {
+ $placeholder_key = 'placeholder';
+ }
+ $retval .= " $placeholder_key='"
+ . __('Filter by name or regex') . "' />";
+ $retval .= "<span title='" . __('Clear fast filter') . "'>X</span>";
+ $retval .= "</form>";
+ $retval .= "</li>";
+ }
+ return $retval;
+ }
+
+ /**
+ * Generates the HTML code for displaying the list pagination
+ *
+ * @param Node $node The node for whose children the page
+ * selector will be created
+ *
+ * @return string
+ */
+ private function _getPageSelector($node)
+ {
+ $retval = '';
+ if ($node === $this->_tree) {
+ $retval .= PMA_Util::getListNavigator(
+ $this->_tree->getPresence('databases', $this->_searchClause),
+ $this->_pos,
+ array('server' => $GLOBALS['server']),
+ 'navigation.php',
+ 'frame_navigation',
+ $GLOBALS['cfg']['MaxNavigationItems'],
+ 'pos',
+ array('dbselector')
+ );
+ } else if ($node->type == Node::CONTAINER && ! $node->is_group) {
+ $paths = $node->getPaths();
+
+ $level = isset($paths['aPath_clean'][4]) ? 3 : 2;
+ $_url_params = array(
+ 'aPath' => $paths['aPath'],
+ 'vPath' => $paths['vPath'],
+ 'pos' => $this->_pos,
+ 'server' => $GLOBALS['server'],
+ 'pos2_name' => $paths['aPath_clean'][2]
+ );
+ if ($level == 3) {
+ $pos = $node->pos3;
+ $_url_params['pos2_value'] = $node->pos2;
+ $_url_params['pos3_name'] = $paths['aPath_clean'][4];
+ } else {
+ $pos = $node->pos2;
+ }
+ $num = $node->realParent()->getPresence(
+ $node->real_name,
+ $this->_searchClause2
+ );
+ $retval .= PMA_Util::getListNavigator(
+ $num,
+ $pos,
+ $_url_params,
+ 'navigation.php',
+ 'frame_navigation',
+ $GLOBALS['cfg']['MaxNavigationItems'],
+ 'pos' . $level . '_value'
+ );
+ }
+ return $retval;
+ }
+
+ /**
+ * Called by usort() for sorting the nodes in a container
+ *
+ * @param Node $a The first element used in the comparison
+ * @param Node $b The second element used in the comparison
+ *
+ * @return int See strnatcmp() and strcmp()
+ */
+ static public function sortNode($a, $b)
+ {
+ if ($a->isNew) {
+ return -1;
+ } else if ($b->isNew) {
+ return 1;
+ }
+ if ($GLOBALS['cfg']['NaturalOrder']) {
+ return strnatcasecmp($a->name, $b->name);
+ } else {
+ return strcasecmp($a->name, $b->name);
+ }
+ }
+}
+?>
diff --git a/libraries/navigation/NodeFactory.class.php b/libraries/navigation/NodeFactory.class.php
new file mode 100644
index 0000000000..f561fb87f5
--- /dev/null
+++ b/libraries/navigation/NodeFactory.class.php
@@ -0,0 +1,97 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * This class is responsible for creating Node objects
+ *
+ * @package PhpMyAdmin-navigation
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+require_once 'libraries/navigation/Nodes/Node.class.php';
+
+/**
+ * Node factory - instanciates Node objects or objects derived from the Node class
+ *
+ * @package PhpMyAdmin-Navigation
+ */
+class PMA_NodeFactory
+{
+ /**
+ * @var string $_path A template for generating paths to files
+ * that contain various Node classes
+ * @access private
+ */
+ private static $_path = 'libraries/navigation/Nodes/%s.class.php';
+ /**
+ * Sanitizes the name of a Node class
+ *
+ * @param string $class The class name to be sanitized
+ *
+ * @return string
+ */
+ private static function _sanitizeClass($class)
+ {
+ if ($class !== 'Node' && ! preg_match('@^Node_\w+(_\w+)?$@', $class)) {
+ $class = 'Node';
+ trigger_error(
+ sprintf(
+ /* l10n: The word "Node" must not be translated here */
+ __('Invalid class name "%1$s", using default of "Node"'),
+ $class
+ ),
+ E_USER_ERROR
+ );
+ }
+ return self::_checkFile($class);
+ }
+ /**
+ * Checks if a file exists for a given class name
+ * Will return the default class name back if the
+ * file for some subclass is not available
+ *
+ * @param string $class The class name to check
+ *
+ * @return string
+ */
+ private static function _checkFile($class)
+ {
+ $path = sprintf(self::$_path, $class);
+ if (! is_readable($path)) {
+ $class = 'Node';
+ trigger_error(
+ sprintf(
+ __('Could not include class "%1$s", file "%2$s" not found'),
+ $class,
+ 'Nodes/' . $class . '.class.php'
+ ),
+ E_USER_ERROR
+ );
+ }
+ return $class;
+ }
+ /**
+ * Instanciates a Node object
+ *
+ * @param string $class The name of the class to instanciate
+ * @param string $name An identifier for the new node
+ * @param int $type Type of node, may be one of CONTAINER or OBJECT
+ * @param bool $is_group Whether this object has been created
+ * while grouping nodes
+ *
+ * @return mixed
+ */
+ public static function getInstance(
+ $class = 'Node',
+ $name = 'default',
+ $type = Node::OBJECT,
+ $is_group = false
+ ) {
+ $class = self::_sanitizeClass($class);
+ include_once sprintf(self::$_path, $class);
+ return new $class($name, $type, $is_group);
+ }
+}
+
+?>
diff --git a/libraries/navigation/Nodes/Node.class.php b/libraries/navigation/Nodes/Node.class.php
new file mode 100644
index 0000000000..583f28b796
--- /dev/null
+++ b/libraries/navigation/Nodes/Node.class.php
@@ -0,0 +1,441 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Functionality for the navigation tree in the left frame
+ *
+ * @package PhpMyAdmin-Navigation
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ * The Node is the building block for the collapsible navigation tree
+ *
+ * @package PhpMyAdmin-Navigation
+ */
+class Node
+{
+ /**
+ * @var int Defines a possible node type
+ */
+ const CONTAINER = 0;
+
+ /**
+ * @var int Defines a possible node type
+ */
+ const OBJECT = 1;
+
+ /**
+ * @var string A non-unique identifier for the node
+ * This may be trimmed when grouping nodes
+ */
+ public $name = "";
+
+ /**
+ * @var string A non-unique identifier for the node
+ * This will never change after being assigned
+ */
+ public $real_name = "";
+
+ /**
+ * @var int May be one of CONTAINER or OBJECT
+ */
+ public $type = Node::OBJECT;
+
+ /**
+ * @var bool Whether this object has been created while grouping nodes
+ * Only relevant if the node is of type CONTAINER
+ */
+ public $is_group;
+
+ /**
+ * @var bool Whether to add a "display: none;" CSS
+ * rule to the node when rendering it
+ */
+ public $visible = false;
+
+ /**
+ * @var Node A reference to the parent object of
+ * this node, NULL for the root node.
+ */
+ public $parent;
+
+ /**
+ * @var array An array of Node objects that are
+ * direct children of this node
+ */
+ public $children = array();
+
+ /**
+ * @var Mixed A string used to group nodes, or an array of strings
+ * Only relevant if the node is of type CONTAINER
+ */
+ public $separator = '';
+
+ /**
+ * @var int How many time to recursively apply the grouping function
+ * Only relevant if the node is of type CONTAINER
+ */
+ public $separator_depth = 1;
+
+ /**
+ * @var string An IMG tag, used when rendering the node
+ */
+ public $icon;
+
+ /**
+ * @var Array An array of A tags, used when rendering the node
+ * The indexes in the array may be 'icon' and 'text'
+ */
+ public $links;
+
+ /**
+ * @var string Extra CSS classes for the node
+ */
+ public $classes = '';
+
+ /**
+ * @var string Whether this node is a link for creating new objects
+ */
+ public $isNew = false;
+
+ /**
+ * @var int The position for the pagination of
+ * the branch at the second level of the tree
+ */
+ public $pos2 = 0;
+
+ /**
+ * @var int The position for the pagination of
+ * the branch at the third level of the tree
+ */
+ public $pos3 = 0;
+
+ /**
+ * Initialises the class by setting the mandatory variables
+ *
+ * @param string $name An identifier for the new node
+ * @param int $type Type of node, may be one of CONTAINER or OBJECT
+ * @param bool $is_group Whether this object has been created
+ * while grouping nodes
+ *
+ * @return Node
+ */
+ public function __construct($name, $type = Node::OBJECT, $is_group = false)
+ {
+ if (! empty($name)) {
+ $this->name = $name;
+ $this->real_name = $name;
+ }
+ if ($type === Node::CONTAINER) {
+ $this->type = Node::CONTAINER;
+ }
+ $this->is_group = (bool)$is_group;
+ }
+
+ /**
+ * Adds a child node to this node
+ *
+ * @param Node $child A child node
+ *
+ * @return void
+ */
+ public function addChild($child)
+ {
+ $this->children[] = $child;
+ $child->parent = $this;
+ }
+
+ /**
+ * Returns a child node given it's name
+ *
+ * @param string $name The name of requested child
+ * @param bool $real_name Whether to use the "real_name"
+ * instead of "name" in comparisons
+ *
+ * @return false|Node The requested child node or false,
+ * if the requested node cannot be found
+ */
+ public function getChild($name, $real_name = false)
+ {
+ if ($real_name) {
+ foreach ($this->children as $child) {
+ if ($child->real_name == $name) {
+ return $child;
+ }
+ }
+ } else {
+ foreach ($this->children as $child) {
+ if ($child->name == $name) {
+ return $child;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Removes a child node from this node
+ *
+ * @param string $name The name of child to be removed
+ *
+ * @return void
+ */
+ public function removeChild($name)
+ {
+ foreach ($this->children as $key => $child) {
+ if ($child->name == $name) {
+ unset($this->children[$key]);
+ break;
+ }
+ }
+ }
+
+ /**
+ * Retreives the parents for a node
+ *
+ * @param bool $self Whether to include the Node itself in the results
+ * @param bool $containers Whether to include nodes of type CONTAINER
+ * @param bool $groups Whether to include nodes which have $group == true
+ *
+ * @return array An array of parent Nodes
+ */
+ public function parents($self = false, $containers = false, $groups = false)
+ {
+ $parents = array();
+ if ($self
+ && ($this->type != Node::CONTAINER || $containers)
+ && ($this->is_group != true || $groups)
+ ) {
+ $parents[] = $this;
+ $self = false;
+ }
+ $parent = $this->parent;
+ while (isset($parent)) {
+ if ( ($parent->type != Node::CONTAINER || $containers)
+ && ($parent->is_group != true || $groups)
+ ) {
+ $parents[] = $parent;
+ }
+ $parent = $parent->parent;
+ }
+ return $parents;
+ }
+
+ /**
+ * Returns the actual parent of a node. If used twice on an index or columns
+ * node, it will return the table and database nodes. The names of the returned
+ * nodes can be used in SQL queries, etc...
+ *
+ * @return Node
+ */
+ public function realParent()
+ {
+ $retval = $this->parents();
+ if (count($retval) > 0) {
+ return $retval[0];
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * This function checks if the node has children nodes associated with it
+ *
+ * @param bool $count_empty_containers Whether to count empty child
+ * containers as valid children
+ *
+ * @return bool Whether the node has child nodes
+ */
+ public function hasChildren($count_empty_containers = true)
+ {
+ $retval = false;
+ if ($count_empty_containers) {
+ if (count($this->children)) {
+ $retval = true;
+ }
+ } else {
+ foreach ($this->children as $child) {
+ if ($child->type == Node::OBJECT || $child->hasChildren(false)) {
+ $retval = true;
+ break;
+ }
+ }
+ }
+ return $retval;
+ }
+
+ /**
+ * Returns true the node has some siblings (other nodes on the same tree level,
+ * in the same branch), false otherwise. The only exception is for nodes on
+ * the third level of the tree (columns and indexes), for which the function
+ * always returns true. This is because we want to render the containers
+ * for these nodes
+ *
+ * @return bool
+ */
+ public function hasSiblings()
+ {
+ $retval = false;
+ $paths = $this->getPaths();
+ if (count($paths['aPath_clean']) > 3) {
+ $retval = true;
+ } else {
+ foreach ($this->parent->children as $child) {
+ if ($child !== $this
+ && ($child->type == Node::OBJECT || $child->hasChildren(false))
+ ) {
+ $retval = true;
+ break;
+ }
+ }
+ }
+ return $retval;
+ }
+
+ /**
+ * Returns the number of child nodes that a node has associated with it
+ *
+ * @return int The number of children nodes
+ */
+ public function numChildren()
+ {
+ $retval = 0;
+ foreach ($this->children as $child) {
+ if ($child->type == Node::OBJECT) {
+ $retval++;
+ } else {
+ $retval += $child->numChildren();
+ }
+ }
+ return $retval;
+ }
+
+ /**
+ * Returns the actual path and the virtual paths for a node
+ * both as clean arrays and base64 encoded strings
+ *
+ * @return array
+ */
+ public function getPaths()
+ {
+ $aPath = array();
+ $aPath_clean = array();
+ foreach ($this->parents(true, true, false) as $parent) {
+ $aPath[] = base64_encode($parent->real_name);
+ $aPath_clean[] = $parent->real_name;
+ }
+ $aPath = implode('.', array_reverse($aPath));
+ $aPath_clean = array_reverse($aPath_clean);
+
+ $vPath = array();
+ $vPath_clean = array();
+ foreach ($this->parents(true, true, true) as $parent) {
+ $vPath[] = base64_encode($parent->name);
+ $vPath_clean[] = $parent->name;
+ }
+ $vPath = implode('.', array_reverse($vPath));
+ $vPath_clean = array_reverse($vPath_clean);
+
+ return array(
+ 'aPath' => $aPath,
+ 'aPath_clean' => $aPath_clean,
+ 'vPath' => $vPath,
+ 'vPath_clean' => $vPath_clean
+ );
+ }
+
+ /**
+ * Returns the names of children of type $type present inside this container
+ * This method is overridden by the Node_Database and Node_Table classes
+ *
+ * @param string $type The type of item we are looking for
+ * ('tables', 'views', etc)
+ * @param int $pos The offset of the list within the results
+ * @param string $searchClause A string used to filter the results of the query
+ *
+ * @return array
+ */
+ public function getData($type, $pos, $searchClause = '')
+ {
+ $query = "SELECT `SCHEMA_NAME` ";
+ $query .= "FROM `INFORMATION_SCHEMA`.`SCHEMATA` ";
+ $query .= $this->_getWhereClause($searchClause);
+ $query .= "ORDER BY `SCHEMA_NAME` ASC ";
+ $query .= "LIMIT $pos, {$GLOBALS['cfg']['MaxNavigationItems']}";
+ return $GLOBALS['dbi']->fetchResult($query);
+
+ }
+
+ /**
+ * Returns the number of children of type $type present inside this container
+ * This method is overridden by the Node_Database and Node_Table classes
+ *
+ * @param string $type The type of item we are looking for
+ * ('tables', 'views', etc)
+ * @param string $searchClause A string used to filter the results of the query
+ *
+ * @return int
+ */
+ public function getPresence($type = '', $searchClause = '')
+ {
+ $query = "SELECT COUNT(*) ";
+ $query .= "FROM `INFORMATION_SCHEMA`.`SCHEMATA` ";
+ $query .= $this->_getWhereClause($searchClause);
+ $retval = (int)$GLOBALS['dbi']->fetchValue($query);
+ return $retval;
+ }
+
+ /**
+ * Returns the WHERE clause depending on the $searchClause parameter
+ * and the hide_db directive
+ *
+ * @param string $searchClause A string used to filter the results of the query
+ *
+ * @return string
+ */
+ private function _getWhereClause($searchClause = '')
+ {
+ $whereClause = "WHERE TRUE ";
+ if (! empty($searchClause)) {
+ $whereClause .= "AND `SCHEMA_NAME` LIKE '%";
+ $whereClause .= PMA_Util::sqlAddSlashes(
+ $searchClause, true
+ );
+ $whereClause .= "%' ";
+ }
+
+ if (! empty($GLOBALS['cfg']['Server']['hide_db'])) {
+ $whereClause .= "AND `SCHEMA_NAME` NOT REGEXP '"
+ . $GLOBALS['cfg']['Server']['hide_db'] . "' ";
+ }
+
+ if (! empty($GLOBALS['cfg']['Server']['only_db'])) {
+ if (is_string($GLOBALS['cfg']['Server']['only_db'])) {
+ $GLOBALS['cfg']['Server']['only_db'] = array(
+ $GLOBALS['cfg']['Server']['only_db']
+ );
+ }
+ $whereClause .= "AND (";
+ $subClauses = array();
+ foreach ($GLOBALS['cfg']['Server']['only_db'] as $each_only_db) {
+ $subClauses[] = " `SCHEMA_NAME` LIKE '"
+ . $each_only_db . "' ";
+ }
+ $whereClause .= implode("OR", $subClauses) . ")";
+ }
+ return $whereClause;
+ }
+
+ /**
+ * Returns HTML for control buttons displayed infront of a node
+ *
+ * @return String HTML for control buttons
+ */
+ public function getHtmlForControlButtons()
+ {
+ return '';
+ }
+}
+?>
diff --git a/libraries/navigation/Nodes/Node_Column.class.php b/libraries/navigation/Nodes/Node_Column.class.php
new file mode 100644
index 0000000000..2458aeee3a
--- /dev/null
+++ b/libraries/navigation/Nodes/Node_Column.class.php
@@ -0,0 +1,46 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Functionality for the navigation tree
+ *
+ * @package PhpMyAdmin-Navigation
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ * Represents a columns node in the navigation tree
+ *
+ * @package PhpMyAdmin-Navigation
+ */
+class Node_Column extends Node
+{
+ /**
+ * Initialises the class
+ *
+ * @param string $name An identifier for the new node
+ * @param int $type Type of node, may be one of CONTAINER or OBJECT
+ * @param bool $is_group Whether this object has been created
+ * while grouping nodes
+ *
+ * @return Node_Column
+ */
+ public function __construct($name, $type = Node::OBJECT, $is_group = false)
+ {
+ parent::__construct($name, $type, $is_group);
+ $this->icon = PMA_Util::getImage('pause.png', __('Column'));
+ $this->links = array(
+ 'text' => 'tbl_structure.php?server=' . $GLOBALS['server']
+ . '&amp;db=%3$s&amp;table=%2$s&amp;field=%1$s'
+ . '&amp;change_column=1'
+ . '&amp;token=' . $GLOBALS['token'],
+ 'icon' => 'tbl_structure.php?server=' . $GLOBALS['server']
+ . '&amp;db=%3$s&amp;table=%2$s&amp;field=%1$s'
+ . '&amp;change_column=1'
+ . '&amp;token=' . $GLOBALS['token']
+ );
+ }
+}
+
+?>
diff --git a/libraries/navigation/Nodes/Node_Column_Container.class.php b/libraries/navigation/Nodes/Node_Column_Container.class.php
new file mode 100644
index 0000000000..3bb1462135
--- /dev/null
+++ b/libraries/navigation/Nodes/Node_Column_Container.class.php
@@ -0,0 +1,57 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Functionality for the navigation tree
+ *
+ * @package PhpMyAdmin-Navigation
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ * Represents a container for column nodes in the navigation tree
+ *
+ * @package PhpMyAdmin-Navigation
+ */
+class Node_Column_Container extends Node
+{
+ /**
+ * Initialises the class
+ *
+ * @return Node_Column_Container
+ */
+ public function __construct()
+ {
+ parent::__construct(__('Columns'), Node::CONTAINER);
+ $this->icon = PMA_Util::getImage('pause.png', __('Columns'));
+ $this->links = array(
+ 'text' => 'tbl_structure.php?server=' . $GLOBALS['server']
+ . '&amp;db=%2$s&amp;table=%1$s'
+ . '&amp;token=' . $GLOBALS['token'],
+ 'icon' => 'tbl_structure.php?server=' . $GLOBALS['server']
+ . '&amp;db=%2$s&amp;table=%1$s'
+ . '&amp;token=' . $GLOBALS['token'],
+ );
+ $this->real_name = 'columns';
+
+ $new_label = _pgettext('Create new column', 'New');
+ $new = PMA_NodeFactory::getInstance('Node', $new_label);
+ $new->isNew = true;
+ $new->icon = PMA_Util::getImage('b_column_add.png', $new_label);
+ $new->links = array(
+ 'text' => 'tbl_addfield.php?server=' . $GLOBALS['server']
+ . '&amp;db=%3$s&amp;table=%2$s'
+ . '&amp;field_where=last&after_field='
+ . '&amp;token=' . $GLOBALS['token'],
+ 'icon' => 'tbl_addfield.php?server=' . $GLOBALS['server']
+ . '&amp;db=%3$s&amp;table=%2$s'
+ . '&amp;field_where=last&after_field='
+ . '&amp;token=' . $GLOBALS['token'],
+ );
+ $new->classes = 'new_column italics';
+ $this->addChild($new);
+ }
+}
+
+?>
diff --git a/libraries/navigation/Nodes/Node_Database.class.php b/libraries/navigation/Nodes/Node_Database.class.php
new file mode 100644
index 0000000000..7a445ab693
--- /dev/null
+++ b/libraries/navigation/Nodes/Node_Database.class.php
@@ -0,0 +1,327 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Functionality for the navigation tree
+ *
+ * @package PhpMyAdmin-Navigation
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ * Represents a database node in the navigation tree
+ *
+ * @package PhpMyAdmin-Navigation
+ */
+class Node_Database extends Node
+{
+ /**
+ * Initialises the class
+ *
+ * @param string $name An identifier for the new node
+ * @param int $type Type of node, may be one of CONTAINER or OBJECT
+ * @param bool $is_group Whether this object has been created
+ * while grouping nodes
+ *
+ * @return Node_Database
+ */
+ public function __construct($name, $type = Node::OBJECT, $is_group = false)
+ {
+ parent::__construct($name, $type, $is_group);
+ $this->icon = PMA_Util::getImage(
+ 's_db.png',
+ __('Database operations')
+ );
+ $this->links = array(
+ 'text' => $GLOBALS['cfg']['DefaultTabDatabase']
+ . '?server=' . $GLOBALS['server']
+ . '&amp;db=%1$s&amp;token=' . $GLOBALS['token'],
+ 'icon' => 'db_operations.php?server=' . $GLOBALS['server']
+ . '&amp;db=%1$s&amp;token=' . $GLOBALS['token']
+ );
+ $this->classes = 'database';
+ }
+
+ /**
+ * Returns the number of children of type $type present inside this container
+ * This method is overridden by the Node_Database and Node_Table classes
+ *
+ * @param string $type The type of item we are looking for
+ * ('tables', 'views', etc)
+ * @param string $searchClause A string used to filter the results of the query
+ *
+ * @return int
+ */
+ public function getPresence($type = '', $searchClause = '')
+ {
+ $retval = 0;
+ $db = $this->real_name;
+ switch ($type) {
+ case 'tables':
+ $db = PMA_Util::sqlAddSlashes($db);
+ $query = "SELECT COUNT(*) ";
+ $query .= "FROM `INFORMATION_SCHEMA`.`TABLES` ";
+ $query .= "WHERE `TABLE_SCHEMA`='$db' ";
+ if (PMA_DRIZZLE) {
+ $query .= "AND `TABLE_TYPE`='BASE' ";
+ } else {
+ $query .= "AND `TABLE_TYPE`='BASE TABLE' ";
+ }
+ if (! empty($searchClause)) {
+ $query .= "AND `TABLE_NAME` LIKE '%";
+ $query .= PMA_Util::sqlAddSlashes(
+ $searchClause, true
+ );
+ $query .= "%'";
+ }
+ $retval = (int)$GLOBALS['dbi']->fetchValue($query);
+ break;
+ case 'views':
+ $db = PMA_Util::sqlAddSlashes($db);
+ $query = "SELECT COUNT(*) ";
+ $query .= "FROM `INFORMATION_SCHEMA`.`TABLES` ";
+ $query .= "WHERE `TABLE_SCHEMA`='$db' ";
+ if (PMA_DRIZZLE) {
+ $query .= "AND `TABLE_TYPE`!='BASE' ";
+ } else {
+ $query .= "AND `TABLE_TYPE`!='BASE TABLE' ";
+ }
+ if (! empty($searchClause)) {
+ $query .= "AND `TABLE_NAME` LIKE '%";
+ $query .= PMA_Util::sqlAddSlashes(
+ $searchClause, true
+ );
+ $query .= "%'";
+ }
+ $retval = (int)$GLOBALS['dbi']->fetchValue($query);
+ break;
+ case 'procedures':
+ $db = PMA_Util::sqlAddSlashes($db);
+ $query = "SELECT COUNT(*) ";
+ $query .= "FROM `INFORMATION_SCHEMA`.`ROUTINES` ";
+ $query .= "WHERE `ROUTINE_SCHEMA`='$db'";
+ $query .= "AND `ROUTINE_TYPE`='PROCEDURE' ";
+ if (! empty($searchClause)) {
+ $query .= "AND `ROUTINE_NAME` LIKE '%";
+ $query .= PMA_Util::sqlAddSlashes(
+ $searchClause, true
+ );
+ $query .= "%'";
+ }
+ $retval = (int)$GLOBALS['dbi']->fetchValue($query);
+ break;
+ case 'functions':
+ $db = PMA_Util::sqlAddSlashes($db);
+ $query = "SELECT COUNT(*) ";
+ $query .= "FROM `INFORMATION_SCHEMA`.`ROUTINES` ";
+ $query .= "WHERE `ROUTINE_SCHEMA`='$db' ";
+ $query .= "AND `ROUTINE_TYPE`='FUNCTION' ";
+ if (! empty($searchClause)) {
+ $query .= "AND `ROUTINE_NAME` LIKE '%";
+ $query .= PMA_Util::sqlAddSlashes(
+ $searchClause, true
+ );
+ $query .= "%'";
+ }
+ $retval = (int)$GLOBALS['dbi']->fetchValue($query);
+ break;
+ case 'events':
+ $db = PMA_Util::sqlAddSlashes($db);
+ $query = "SELECT COUNT(*) ";
+ $query .= "FROM `INFORMATION_SCHEMA`.`EVENTS` ";
+ $query .= "WHERE `EVENT_SCHEMA`='$db' ";
+ if (! empty($searchClause)) {
+ $query .= "AND `EVENT_NAME` LIKE '%";
+ $query .= PMA_Util::sqlAddSlashes(
+ $searchClause, true
+ );
+ $query .= "%'";
+ }
+ $retval = (int)$GLOBALS['dbi']->fetchValue($query);
+ break;
+ default:
+ break;
+ }
+ return $retval;
+ }
+
+ /**
+ * Returns the names of children of type $type present inside this container
+ * This method is overridden by the Node_Database and Node_Table classes
+ *
+ * @param string $type The type of item we are looking for
+ * ('tables', 'views', etc)
+ * @param int $pos The offset of the list within the results
+ * @param string $searchClause A string used to filter the results of the query
+ *
+ * @return array
+ */
+ public function getData($type, $pos, $searchClause = '')
+ {
+ $maxItems = $GLOBALS['cfg']['MaxNavigationItems'];
+ $retval = array();
+ $db = $this->real_name;
+ switch ($type) {
+ case 'tables':
+ $escdDb = PMA_Util::sqlAddSlashes($db);
+ $query = "SELECT `TABLE_NAME` AS `name` ";
+ $query .= "FROM `INFORMATION_SCHEMA`.`TABLES` ";
+ $query .= "WHERE `TABLE_SCHEMA`='$escdDb' ";
+ if (PMA_DRIZZLE) {
+ $query .= "AND `TABLE_TYPE`='BASE' ";
+ } else {
+ $query .= "AND `TABLE_TYPE`='BASE TABLE' ";
+ }
+ if (! empty($searchClause)) {
+ $query .= "AND `TABLE_NAME` LIKE '%";
+ $query .= PMA_Util::sqlAddSlashes(
+ $searchClause, true
+ );
+ $query .= "%'";
+ }
+ $query .= "ORDER BY `TABLE_NAME` ASC ";
+ $query .= "LIMIT " . intval($pos) . ", $maxItems";
+ $retval = $GLOBALS['dbi']->fetchResult($query);
+ break;
+ case 'views':
+ $escdDb = PMA_Util::sqlAddSlashes($db);
+ $query = "SELECT `TABLE_NAME` AS `name` ";
+ $query .= "FROM `INFORMATION_SCHEMA`.`TABLES` ";
+ $query .= "WHERE `TABLE_SCHEMA`='$escdDb' ";
+ if (PMA_DRIZZLE) {
+ $query .= "AND `TABLE_TYPE`!='BASE' ";
+ } else {
+ $query .= "AND `TABLE_TYPE`!='BASE TABLE' ";
+ }
+ if (! empty($searchClause)) {
+ $query .= "AND `TABLE_NAME` LIKE '%";
+ $query .= PMA_Util::sqlAddSlashes(
+ $searchClause, true
+ );
+ $query .= "%'";
+ }
+ $query .= "ORDER BY `TABLE_NAME` ASC ";
+ $query .= "LIMIT " . intval($pos) . ", $maxItems";
+ $retval = $GLOBALS['dbi']->fetchResult($query);
+ break;
+ case 'procedures':
+ $escdDb = PMA_Util::sqlAddSlashes($db);
+ $query = "SELECT `ROUTINE_NAME` AS `name` ";
+ $query .= "FROM `INFORMATION_SCHEMA`.`ROUTINES` ";
+ $query .= "WHERE `ROUTINE_SCHEMA`='$escdDb'";
+ $query .= "AND `ROUTINE_TYPE`='PROCEDURE' ";
+ if (! empty($searchClause)) {
+ $query .= "AND `ROUTINE_NAME` LIKE '%";
+ $query .= PMA_Util::sqlAddSlashes(
+ $searchClause, true
+ );
+ $query .= "%'";
+ }
+ $query .= "ORDER BY `ROUTINE_NAME` ASC ";
+ $query .= "LIMIT " . intval($pos) . ", $maxItems";
+ $retval = $GLOBALS['dbi']->fetchResult($query);
+ break;
+ case 'functions':
+ $escdDb = PMA_Util::sqlAddSlashes($db);
+ $query = "SELECT `ROUTINE_NAME` AS `name` ";
+ $query .= "FROM `INFORMATION_SCHEMA`.`ROUTINES` ";
+ $query .= "WHERE `ROUTINE_SCHEMA`='$escdDb' ";
+ $query .= "AND `ROUTINE_TYPE`='FUNCTION' ";
+ if (! empty($searchClause)) {
+ $query .= "AND `ROUTINE_NAME` LIKE '%";
+ $query .= PMA_Util::sqlAddSlashes(
+ $searchClause, true
+ );
+ $query .= "%'";
+ }
+ $query .= "ORDER BY `ROUTINE_NAME` ASC ";
+ $query .= "LIMIT " . intval($pos) . ", $maxItems";
+ $retval = $GLOBALS['dbi']->fetchResult($query);
+ break;
+ case 'events':
+ $escdDb = PMA_Util::sqlAddSlashes($db);
+ $query = "SELECT `EVENT_NAME` AS `name` ";
+ $query .= "FROM `INFORMATION_SCHEMA`.`EVENTS` ";
+ $query .= "WHERE `EVENT_SCHEMA`='$escdDb' ";
+ if (! empty($searchClause)) {
+ $query .= "AND `EVENT_NAME` LIKE '%";
+ $query .= PMA_Util::sqlAddSlashes(
+ $searchClause, true
+ );
+ $query .= "%'";
+ }
+ $query .= "ORDER BY `EVENT_NAME` ASC ";
+ $query .= "LIMIT " . intval($pos) . ", $maxItems";
+ $retval = $GLOBALS['dbi']->fetchResult($query);
+ break;
+ default:
+ break;
+ }
+
+ // Remove hidden items so that they are not displayed in navigation tree
+ $cfgRelation = PMA_getRelationsParam();
+ if ($cfgRelation['navwork']) {
+ $navTable = PMA_Util::backquote($cfgRelation['db'])
+ . "." . PMA_Util::backquote($cfgRelation['navigationhiding']);
+ $sqlQuery = "SELECT `item_name` FROM " . $navTable
+ . " WHERE `username`='" . $cfgRelation['user'] . "'"
+ . " AND `item_type`='" . substr($type, 0, -1) . "'"
+ . " AND `db_name`='" . PMA_Util::sqlAddSlashes($db) . "'";
+ $result = PMA_queryAsControlUser($sqlQuery, false);
+ if ($result) {
+ $hiddenItems = array();
+ while ($row = $GLOBALS['dbi']->fetchArray($result)) {
+ $hiddenItems[] = $row[0];
+ }
+ foreach ($retval as $key => $item) {
+ if (in_array($item, $hiddenItems)) {
+ unset($retval[$key]);
+ }
+ }
+ }
+ $GLOBALS['dbi']->freeResult($result);
+ }
+
+ return $retval;
+ }
+
+ /**
+ * Returns HTML for show hidden button displayed infront of database node
+ *
+ * @return String HTML for show hidden button
+ */
+ public function getHtmlForControlButtons()
+ {
+ $ret = '';
+ $db = $this->real_name;
+
+ $cfgRelation = PMA_getRelationsParam();
+ if ($cfgRelation['navwork']) {
+ $navTable = PMA_Util::backquote($cfgRelation['db'])
+ . "." . PMA_Util::backquote($cfgRelation['navigationhiding']);
+ $sqlQuery = "SELECT COUNT(*) FROM " . $navTable
+ . " WHERE `username`='"
+ . PMA_Util::sqlAddSlashes($GLOBALS['cfg']['Server']['user']) ."'"
+ . " AND `db_name`='" . PMA_Util::sqlAddSlashes($db) . "'";
+ $count = $GLOBALS['dbi']->fetchValue(
+ $sqlQuery, 0, 0, $GLOBALS['controllink']
+ );
+ if ($count > 0) {
+ $ret = '<span class="dbItemControls">'
+ . '<a href="navigation.php?'
+ . PMA_URL_getCommon()
+ . '&showUnhideDialog=true'
+ . '&dbName=' . urldecode($db) . '"'
+ . ' class="showUnhide ajax">'
+ . PMA_Util::getImage(
+ 'lightbulb.png', __('Show hidden items')
+ )
+ . '</a></span>';
+ }
+ }
+ return $ret;
+ }
+}
+
+?>
diff --git a/libraries/navigation/Nodes/Node_DatabaseChild.class.php b/libraries/navigation/Nodes/Node_DatabaseChild.class.php
new file mode 100644
index 0000000000..36e8a37700
--- /dev/null
+++ b/libraries/navigation/Nodes/Node_DatabaseChild.class.php
@@ -0,0 +1,52 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Functionality for the navigation tree
+ *
+ * @package PhpMyAdmin-Navigation
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ * Represents a node that is a concrete child of a database node
+ *
+ * @package PhpMyAdmin-Navigation
+ */
+abstract class Node_DatabaseChild extends Node
+{
+ /**
+ * Returns HTML for hide button displayed infront of the database child node
+ *
+ * @return String HTML for hide button
+ */
+ public function getHtmlForControlButtons()
+ {
+ $ret = '';
+ $cfgRelation = PMA_getRelationsParam();
+ if ($cfgRelation['navwork']) {
+ $db = $this->realParent()->real_name;
+ $item = $this->real_name;
+ $ret = '<span class="navItemControls">'
+ . '<a href="navigation.php?'
+ . PMA_URL_getCommon()
+ . '&hideNavItem=true'
+ . '&itemType=' . urldecode($this->getItemType())
+ . '&itemName=' . urldecode($item)
+ . '&dbName=' . urldecode($db) . '"'
+ . ' class="hideNavItem ajax">'
+ . PMA_Util::getImage('lightbulb_off', __('Hide'))
+ . '</a></span>';
+ }
+ return $ret;
+ }
+
+ /**
+ * Returns the type of the item reprsented by the node.
+ *
+ * @return string type of the item
+ */
+ protected abstract function getItemType();
+}
+?>
diff --git a/libraries/navigation/Nodes/Node_Database_Container.class.php b/libraries/navigation/Nodes/Node_Database_Container.class.php
new file mode 100644
index 0000000000..c76da069d1
--- /dev/null
+++ b/libraries/navigation/Nodes/Node_Database_Container.class.php
@@ -0,0 +1,49 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Functionality for the navigation tree
+ *
+ * @package PhpMyAdmin-Navigation
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+require_once './libraries/check_user_privileges.lib.php';
+
+/**
+ * Represents a container for database nodes in the navigation tree
+ *
+ * @package PhpMyAdmin-Navigation
+ */
+class Node_Database_Container extends Node
+{
+ /**
+ * Initialises the class
+ *
+ * @param string $name An identifier for the new node
+ *
+ * @return Node_Database_Container
+ */
+ public function __construct($name)
+ {
+ parent::__construct($name, Node::CONTAINER);
+
+ if ($GLOBALS['is_create_db_priv']) {
+ $new = PMA_NodeFactory::getInstance(
+ 'Node', _pgettext('Create new database', 'New')
+ );
+ $new->isNew = true;
+ $new->icon = PMA_Util::getImage('b_newdb.png', '');
+ $new->links = array(
+ 'text' => 'server_databases.php?server=' . $GLOBALS['server']
+ . '&amp;token=' . $GLOBALS['token'],
+ 'icon' => 'server_databases.php?server=' . $GLOBALS['server']
+ . '&amp;token=' . $GLOBALS['token'],
+ );
+ $new->classes = 'new_database italics';
+ $this->addChild($new);
+ }
+ }
+}
+?>
diff --git a/libraries/navigation/Nodes/Node_Event.class.php b/libraries/navigation/Nodes/Node_Event.class.php
new file mode 100644
index 0000000000..7df1f8882b
--- /dev/null
+++ b/libraries/navigation/Nodes/Node_Event.class.php
@@ -0,0 +1,57 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Functionality for the navigation tree
+ *
+ * @package PhpMyAdmin-Navigation
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+require_once 'libraries/navigation/Nodes/Node_DatabaseChild.class.php';
+
+/**
+ * Represents a event node in the navigation tree
+ *
+ * @package PhpMyAdmin-Navigation
+ */
+class Node_Event extends Node_DatabaseChild
+{
+ /**
+ * Initialises the class
+ *
+ * @param string $name An identifier for the new node
+ * @param int $type Type of node, may be one of CONTAINER or OBJECT
+ * @param bool $is_group Whether this object has been created
+ * while grouping nodes
+ *
+ * @return Node_Event
+ */
+ public function __construct($name, $type = Node::OBJECT, $is_group = false)
+ {
+ parent::__construct($name, $type, $is_group);
+ $this->icon = PMA_Util::getImage('b_events.png');
+ $this->links = array(
+ 'text' => 'db_events.php?server=' . $GLOBALS['server']
+ . '&amp;db=%2$s&amp;item_name=%1$s&amp;edit_item=1'
+ . '&amp;token=' . $GLOBALS['token'],
+ 'icon' => 'db_events.php?server=' . $GLOBALS['server']
+ . '&amp;db=%2$s&amp;item_name=%1$s&amp;export_item=1'
+ . '&amp;token=' . $GLOBALS['token']
+ );
+ $this->classes = 'event';
+ }
+
+ /**
+ * Returns the type of the item represented by the node.
+ *
+ * @return string type of the item
+ */
+ protected function getItemType()
+ {
+ return 'event';
+ }
+}
+
+?>
diff --git a/libraries/navigation/Nodes/Node_Event_Container.class.php b/libraries/navigation/Nodes/Node_Event_Container.class.php
new file mode 100644
index 0000000000..cc66db8bb1
--- /dev/null
+++ b/libraries/navigation/Nodes/Node_Event_Container.class.php
@@ -0,0 +1,54 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Functionality for the navigation tree
+ *
+ * @package PhpMyAdmin-Navigation
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ * Represents a container for events nodes in the navigation tree
+ *
+ * @package PhpMyAdmin-Navigation
+ */
+class Node_Event_Container extends Node
+{
+ /**
+ * Initialises the class
+ *
+ * @return Node_Event_Container
+ */
+ public function __construct()
+ {
+ parent::__construct(__('Events'), Node::CONTAINER);
+ $this->icon = PMA_Util::getImage('b_events.png', '');
+ $this->links = array(
+ 'text' => 'db_events.php?server=' . $GLOBALS['server']
+ . '&amp;db=%1$s&amp;token=' . $GLOBALS['token'],
+ 'icon' => 'db_events.php?server=' . $GLOBALS['server']
+ . '&amp;db=%1$s&amp;token=' . $GLOBALS['token'],
+ );
+ $this->real_name = 'events';
+
+ $new = PMA_NodeFactory::getInstance(
+ 'Node', _pgettext('Create new event', 'New')
+ );
+ $new->isNew = true;
+ $new->icon = PMA_Util::getImage('b_event_add.png', '');
+ $new->links = array(
+ 'text' => 'db_events.php?server=' . $GLOBALS['server']
+ . '&amp;db=%2$s&amp;token=' . $GLOBALS['token']
+ . '&add_item=1',
+ 'icon' => 'db_events.php?server=' . $GLOBALS['server']
+ . '&amp;db=%2$s&amp;token=' . $GLOBALS['token']
+ . '&add_item=1',
+ );
+ $new->classes = 'new_event italics';
+ $this->addChild($new);
+ }
+}
+
+?>
diff --git a/libraries/navigation/Nodes/Node_Function.class.php b/libraries/navigation/Nodes/Node_Function.class.php
new file mode 100644
index 0000000000..c068e49c16
--- /dev/null
+++ b/libraries/navigation/Nodes/Node_Function.class.php
@@ -0,0 +1,57 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Functionality for the navigation tree
+ *
+ * @package PhpMyAdmin-Navigation
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+require_once 'libraries/navigation/Nodes/Node_DatabaseChild.class.php';
+
+/**
+ * Represents a function node in the navigation tree
+ *
+ * @package PhpMyAdmin-Navigation
+ */
+class Node_Function extends Node_DatabaseChild
+{
+ /**
+ * Initialises the class
+ *
+ * @param string $name An identifier for the new node
+ * @param int $type Type of node, may be one of CONTAINER or OBJECT
+ * @param bool $is_group Whether this object has been created
+ * while grouping nodes
+ *
+ * @return Node_Function
+ */
+ public function __construct($name, $type = Node::OBJECT, $is_group = false)
+ {
+ parent::__construct($name, $type, $is_group);
+ $this->icon = PMA_Util::getImage('b_routines.png', __('Function'));
+ $this->links = array(
+ 'text' => 'db_routines.php?server=' . $GLOBALS['server']
+ . '&amp;db=%2$s&amp;item_name=%1$s&amp;item_type=FUNCTION'
+ . '&amp;edit_item=1&amp;token=' . $GLOBALS['token'],
+ 'icon' => 'db_routines.php?server=' . $GLOBALS['server']
+ . '&amp;db=%2$s&amp;item_name=%1$s&amp;item_type=FUNCTION'
+ . '&amp;export_item=1&amp;token=' . $GLOBALS['token']
+ );
+ $this->classes = 'function';
+ }
+
+ /**
+ * Returns the type of the item represented by the node.
+ *
+ * @return string type of the item
+ */
+ protected function getItemType()
+ {
+ return 'function';
+ }
+}
+
+?>
diff --git a/libraries/navigation/Nodes/Node_Function_Container.class.php b/libraries/navigation/Nodes/Node_Function_Container.class.php
new file mode 100644
index 0000000000..6a571ff1f1
--- /dev/null
+++ b/libraries/navigation/Nodes/Node_Function_Container.class.php
@@ -0,0 +1,53 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Functionality for the navigation tree
+ *
+ * @package PhpMyAdmin-Navigation
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ * Represents a container for functions nodes in the navigation tree
+ *
+ * @package PhpMyAdmin-Navigation
+ */
+class Node_Function_Container extends Node
+{
+ /**
+ * Initialises the class
+ */
+ public function __construct()
+ {
+ parent::__construct(__('Functions'), Node::CONTAINER);
+ $this->icon = PMA_Util::getImage('b_routines.png', __('Functions'));
+ $this->links = array(
+ 'text' => 'db_routines.php?server=' . $GLOBALS['server']
+ . '&amp;db=%1$s&amp;token=' . $GLOBALS['token']
+ . '&amp;type=FUNCTION',
+ 'icon' => 'db_routines.php?server=' . $GLOBALS['server']
+ . '&amp;db=%1$s&amp;token=' . $GLOBALS['token']
+ . '&amp;type=FUNCTION',
+ );
+ $this->real_name = 'functions';
+
+ $new_label = _pgettext('Create new function', 'New');
+ $new = PMA_NodeFactory::getInstance('Node', $new_label);
+ $new->isNew = true;
+ $new->icon = PMA_Util::getImage('b_routine_add.png', $new_label);
+ $new->links = array(
+ 'text' => 'db_routines.php?server=' . $GLOBALS['server']
+ . '&amp;db=%2$s&amp;token=' . $GLOBALS['token']
+ . '&add_item=1&amp;item_type=FUNCTION',
+ 'icon' => 'db_routines.php?server=' . $GLOBALS['server']
+ . '&amp;db=%2$s&amp;token=' . $GLOBALS['token']
+ . '&add_item=1&amp;item_type=FUNCTION',
+ );
+ $new->classes = 'new_function italics';
+ $this->addChild($new);
+ }
+}
+
+?>
diff --git a/libraries/navigation/Nodes/Node_Index.class.php b/libraries/navigation/Nodes/Node_Index.class.php
new file mode 100644
index 0000000000..15ddcb4354
--- /dev/null
+++ b/libraries/navigation/Nodes/Node_Index.class.php
@@ -0,0 +1,45 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Functionality for the navigation tree
+ *
+ * @package PhpMyAdmin-Navigation
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ * Represents a index node in the navigation tree
+ *
+ * @package PhpMyAdmin-Navigation
+ */
+class Node_Index extends Node
+{
+ /**
+ * Initialises the class
+ *
+ * @param string $name An identifier for the new node
+ * @param int $type Type of node, may be one of CONTAINER or OBJECT
+ * @param bool $is_group Whether this object has been created
+ * while grouping nodes
+ *
+ * @return Node_Index
+ */
+ public function __construct($name, $type = Node::OBJECT, $is_group = false)
+ {
+ parent::__construct($name, $type, $is_group);
+ $this->icon = PMA_Util::getImage('b_index.png', __('Index'));
+ $this->links = array(
+ 'text' => 'tbl_indexes.php?server=' . $GLOBALS['server']
+ . '&amp;db=%3$s&amp;table=%2$s&amp;index=%1$s'
+ . '&amp;token=' . $GLOBALS['token'],
+ 'icon' => 'tbl_indexes.php?server=' . $GLOBALS['server']
+ . '&amp;db=%3$s&amp;table=%2$s&amp;index=%1$s'
+ . '&amp;token=' . $GLOBALS['token']
+ );
+ $this->classes = 'index';
+ }
+}
+
+?>
diff --git a/libraries/navigation/Nodes/Node_Index_Container.class.php b/libraries/navigation/Nodes/Node_Index_Container.class.php
new file mode 100644
index 0000000000..89035150cf
--- /dev/null
+++ b/libraries/navigation/Nodes/Node_Index_Container.class.php
@@ -0,0 +1,55 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Functionality for the navigation tree
+ *
+ * @package PhpMyAdmin-Navigation
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ * Represents a container for index nodes in the navigation tree
+ *
+ * @package PhpMyAdmin-Navigation
+ */
+class Node_Index_Container extends Node
+{
+ /**
+ * Initialises the class
+ *
+ * @return Node_Index_Container
+ */
+ public function __construct()
+ {
+ parent::__construct(__('Indexes'), Node::CONTAINER);
+ $this->icon = PMA_Util::getImage('b_index.png', __('Indexes'));
+ $this->links = array(
+ 'text' => 'tbl_structure.php?server=' . $GLOBALS['server']
+ . '&amp;db=%2$s&amp;table=%1$s'
+ . '&amp;token=' . $GLOBALS['token'],
+ 'icon' => 'tbl_structure.php?server=' . $GLOBALS['server']
+ . '&amp;db=%2$s&amp;table=%1$s'
+ . '&amp;token=' . $GLOBALS['token'],
+ );
+ $this->real_name = 'indexes';
+
+ $new_label = _pgettext('Create new index', 'New');
+ $new = PMA_NodeFactory::getInstance('Node', $new_label);
+ $new->isNew = true;
+ $new->icon = PMA_Util::getImage('b_index_add.png', $new_label);
+ $new->links = array(
+ 'text' => 'tbl_indexes.php?server=' . $GLOBALS['server']
+ . '&amp;create_index=1&amp;added_fields=2'
+ . '&amp;db=%3$s&amp;table=%2$s&amp;token=' . $GLOBALS['token'],
+ 'icon' => 'tbl_indexes.php?server=' . $GLOBALS['server']
+ . '&amp;create_index=1&amp;added_fields=2'
+ . '&amp;db=%3$s&amp;table=%2$s&amp;token=' . $GLOBALS['token'],
+ );
+ $new->classes = 'new_index italics';
+ $this->addChild($new);
+ }
+}
+
+?>
diff --git a/libraries/navigation/Nodes/Node_Procedure.class.php b/libraries/navigation/Nodes/Node_Procedure.class.php
new file mode 100644
index 0000000000..686e2d7bd5
--- /dev/null
+++ b/libraries/navigation/Nodes/Node_Procedure.class.php
@@ -0,0 +1,57 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Functionality for the navigation tree
+ *
+ * @package PhpMyAdmin-Navigation
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+require_once 'libraries/navigation/Nodes/Node_DatabaseChild.class.php';
+
+/**
+ * Represents a procedure node in the navigation tree
+ *
+ * @package PhpMyAdmin-Navigation
+ */
+class Node_Procedure extends Node_DatabaseChild
+{
+ /**
+ * Initialises the class
+ *
+ * @param string $name An identifier for the new node
+ * @param int $type Type of node, may be one of CONTAINER or OBJECT
+ * @param bool $is_group Whether this object has been created
+ * while grouping nodes
+ *
+ * @return Node_Procedure
+ */
+ public function __construct($name, $type = Node::OBJECT, $is_group = false)
+ {
+ parent::__construct($name, $type, $is_group);
+ $this->icon = PMA_Util::getImage('b_routines.png', __('Procedure'));
+ $this->links = array(
+ 'text' => 'db_routines.php?server=' . $GLOBALS['server']
+ . '&amp;db=%2$s&amp;item_name=%1$s&amp;item_type=PROCEDURE'
+ . '&amp;edit_item=1&amp;token=' . $GLOBALS['token'],
+ 'icon' => 'db_routines.php?server=' . $GLOBALS['server']
+ . '&amp;db=%2$s&amp;item_name=%1$s&amp;item_type=PROCEDURE'
+ . '&amp;export_item=1&amp;token=' . $GLOBALS['token']
+ );
+ $this->classes = 'procedure';
+ }
+
+ /**
+ * Returns the type of the item represented by the node.
+ *
+ * @return string type of the item
+ */
+ protected function getItemType()
+ {
+ return 'procedure';
+ }
+}
+
+?>
diff --git a/libraries/navigation/Nodes/Node_Procedure_Container.class.php b/libraries/navigation/Nodes/Node_Procedure_Container.class.php
new file mode 100644
index 0000000000..0e4fe083a9
--- /dev/null
+++ b/libraries/navigation/Nodes/Node_Procedure_Container.class.php
@@ -0,0 +1,55 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Functionality for the navigation tree
+ *
+ * @package PhpMyAdmin-Navigation
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ * Represents a container for procedure nodes in the navigation tree
+ *
+ * @package PhpMyAdmin-Navigation
+ */
+class Node_Procedure_Container extends Node
+{
+ /**
+ * Initialises the class
+ *
+ * @return Node_Procedure_Container
+ */
+ public function __construct()
+ {
+ parent::__construct(__('Procedures'), Node::CONTAINER);
+ $this->icon = PMA_Util::getImage('b_routines.png', __('Procedures'));
+ $this->links = array(
+ 'text' => 'db_routines.php?server=' . $GLOBALS['server']
+ . '&amp;db=%1$s&amp;token=' . $GLOBALS['token']
+ . '&amp;type=PROCEDURE',
+ 'icon' => 'db_routines.php?server=' . $GLOBALS['server']
+ . '&amp;db=%1$s&amp;token=' . $GLOBALS['token']
+ . '&amp;type=PROCEDURE',
+ );
+ $this->real_name = 'procedures';
+
+ $new_label = _pgettext('Create new procedure', 'New');
+ $new = PMA_NodeFactory::getInstance('Node', $new_label);
+ $new->isNew = true;
+ $new->icon = PMA_Util::getImage('b_routine_add.png', $new_label);
+ $new->links = array(
+ 'text' => 'db_routines.php?server=' . $GLOBALS['server']
+ . '&amp;db=%2$s&amp;token=' . $GLOBALS['token']
+ . '&add_item=1',
+ 'icon' => 'db_routines.php?server=' . $GLOBALS['server']
+ . '&amp;db=%2$s&amp;token=' . $GLOBALS['token']
+ . '&add_item=1',
+ );
+ $new->classes = 'new_procedure italics';
+ $this->addChild($new);
+ }
+}
+
+?>
diff --git a/libraries/navigation/Nodes/Node_Table.class.php b/libraries/navigation/Nodes/Node_Table.class.php
new file mode 100644
index 0000000000..8a398a560e
--- /dev/null
+++ b/libraries/navigation/Nodes/Node_Table.class.php
@@ -0,0 +1,186 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Functionality for the navigation tree
+ *
+ * @package PhpMyAdmin-Navigation
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+require_once 'libraries/navigation/Nodes/Node_DatabaseChild.class.php';
+
+/**
+ * Represents a columns node in the navigation tree
+ *
+ * @package PhpMyAdmin-Navigation
+ */
+class Node_Table extends Node_DatabaseChild
+{
+ /**
+ * Initialises the class
+ *
+ * @param string $name An identifier for the new node
+ * @param int $type Type of node, may be one of CONTAINER or OBJECT
+ * @param bool $is_group Whether this object has been created
+ * while grouping nodes
+ *
+ * @return Node_Table
+ */
+ public function __construct($name, $type = Node::OBJECT, $is_group = false)
+ {
+ parent::__construct($name, $type, $is_group);
+ switch($GLOBALS['cfg']['NavigationTreeDefaultTabTable']) {
+ case 'tbl_structure.php':
+ $this->icon = PMA_Util::getImage('b_props.png', __('Structure'));
+ break;
+ case 'tbl_select.php':
+ $this->icon = PMA_Util::getImage('b_search.png', __('Search'));
+ break;
+ case 'tbl_change.php':
+ $this->icon = PMA_Util::getImage('b_insrow.png', __('Insert'));
+ break;
+ case 'tbl_sql.php':
+ $this->icon = PMA_Util::getImage('b_sql.png', __('SQL'));
+ break;
+ case 'sql.php':
+ $this->icon = PMA_Util::getImage('b_browse.png', __('Browse'));
+ break;
+ }
+ $this->links = array(
+ 'text' => $GLOBALS['cfg']['DefaultTabTable']
+ . '?server=' . $GLOBALS['server']
+ . '&amp;db=%2$s&amp;table=%1$s'
+ . '&amp;pos=0&amp;token=' . $GLOBALS['token'],
+ 'icon' => $GLOBALS['cfg']['NavigationTreeDefaultTabTable']
+ . '?server=' . $GLOBALS['server']
+ . '&amp;db=%2$s&amp;table=%1$s&amp;token=' . $GLOBALS['token']
+ );
+ $this->classes = 'table';
+ }
+
+ /**
+ * Returns the number of children of type $type present inside this container
+ * This method is overridden by the Node_Database and Node_Table classes
+ *
+ * @param string $type The type of item we are looking for
+ * ('columns' or 'indexes')
+ * @param string $searchClause A string used to filter the results of the query
+ *
+ * @return int
+ */
+ public function getPresence($type = '', $searchClause = '')
+ {
+ $retval = 0;
+ $db = $this->realParent()->real_name;
+ $table = $this->real_name;
+ switch ($type) {
+ case 'columns':
+ $db = PMA_Util::sqlAddSlashes($db);
+ $table = PMA_Util::sqlAddSlashes($table);
+ $query = "SELECT COUNT(*) ";
+ $query .= "FROM `INFORMATION_SCHEMA`.`COLUMNS` ";
+ $query .= "WHERE `TABLE_NAME`='$table' ";
+ $query .= "AND `TABLE_SCHEMA`='$db'";
+ $retval = (int)$GLOBALS['dbi']->fetchValue($query);
+ break;
+ case 'indexes':
+ $db = PMA_Util::backquote($db);
+ $table = PMA_Util::backquote($table);
+ $query = "SHOW INDEXES FROM $table FROM $db";
+ $retval = (int)$GLOBALS['dbi']->numRows(
+ $GLOBALS['dbi']->tryQuery($query)
+ );
+ break;
+ case 'triggers':
+ $db = PMA_Util::sqlAddSlashes($db);
+ $table = PMA_Util::sqlAddSlashes($table);
+ $query = "SELECT COUNT(*) ";
+ $query .= "FROM `INFORMATION_SCHEMA`.`TRIGGERS` ";
+ $query .= "WHERE `EVENT_OBJECT_SCHEMA`='$db' ";
+ $query .= "AND `EVENT_OBJECT_TABLE`='$table'";
+ $retval = (int)$GLOBALS['dbi']->fetchValue($query);
+ break;
+ default:
+ break;
+ }
+ return $retval;
+ }
+
+ /**
+ * Returns the names of children of type $type present inside this container
+ * This method is overridden by the Node_Database and Node_Table classes
+ *
+ * @param string $type The type of item we are looking for
+ * ('tables', 'views', etc)
+ * @param int $pos The offset of the list within the results
+ * @param string $searchClause A string used to filter the results of the query
+ *
+ * @return array
+ */
+ public function getData($type, $pos, $searchClause = '')
+ {
+ $maxItems = $GLOBALS['cfg']['MaxNavigationItems'];
+ $retval = array();
+ $db = $this->realParent()->real_name;
+ $table = $this->real_name;
+ switch ($type) {
+ case 'columns':
+ $db = PMA_Util::sqlAddSlashes($db);
+ $table = PMA_Util::sqlAddSlashes($table);
+ $query = "SELECT `COLUMN_NAME` AS `name` ";
+ $query .= "FROM `INFORMATION_SCHEMA`.`COLUMNS` ";
+ $query .= "WHERE `TABLE_NAME`='$table' ";
+ $query .= "AND `TABLE_SCHEMA`='$db' ";
+ $query .= "ORDER BY `COLUMN_NAME` ASC ";
+ $query .= "LIMIT " . intval($pos) . ", $maxItems";
+ $retval = $GLOBALS['dbi']->fetchResult($query);
+ break;
+ case 'indexes':
+ $db = PMA_Util::backquote($db);
+ $table = PMA_Util::backquote($table);
+ $query = "SHOW INDEXES FROM $table FROM $db";
+ $handle = $GLOBALS['dbi']->tryQuery($query);
+ if ($handle !== false) {
+ $count = 0;
+ while ($arr = $GLOBALS['dbi']->fetchArray($handle)) {
+ if (! in_array($arr['Key_name'], $retval)) {
+ if ($pos <= 0 && $count < $maxItems) {
+ $retval[] = $arr['Key_name'];
+ $count++;
+ }
+ $pos--;
+ }
+ }
+ }
+ break;
+ case 'triggers':
+ $db = PMA_Util::sqlAddSlashes($db);
+ $table = PMA_Util::sqlAddSlashes($table);
+ $query = "SELECT `TRIGGER_NAME` AS `name` ";
+ $query .= "FROM `INFORMATION_SCHEMA`.`TRIGGERS` ";
+ $query .= "WHERE `EVENT_OBJECT_SCHEMA`='$db' ";
+ $query .= "AND `EVENT_OBJECT_TABLE`='$table' ";
+ $query .= "ORDER BY `TRIGGER_NAME` ASC ";
+ $query .= "LIMIT " . intval($pos) . ", $maxItems";
+ $retval = $GLOBALS['dbi']->fetchResult($query);
+ break;
+ default:
+ break;
+ }
+ return $retval;
+ }
+
+ /**
+ * Returns the type of the item represented by the node.
+ *
+ * @return string type of the item
+ */
+ protected function getItemType()
+ {
+ return 'table';
+ }
+}
+
+?>
diff --git a/libraries/navigation/Nodes/Node_Table_Container.class.php b/libraries/navigation/Nodes/Node_Table_Container.class.php
new file mode 100644
index 0000000000..4c4ef20e09
--- /dev/null
+++ b/libraries/navigation/Nodes/Node_Table_Container.class.php
@@ -0,0 +1,60 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Functionality for the navigation tree
+ *
+ * @package PhpMyAdmin-Navigation
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ * Represents a container for table nodes in the navigation tree
+ *
+ * @package PhpMyAdmin-Navigation
+ */
+class Node_Table_Container extends Node
+{
+ /**
+ * Initialises the class
+ *
+ * @return Node_Table_Container
+ */
+ public function __construct()
+ {
+ parent::__construct(__('Tables'), Node::CONTAINER);
+ $this->icon = PMA_Util::getImage('b_browse.png', __('Tables'));
+ $this->links = array(
+ 'text' => 'db_structure.php?server=' . $GLOBALS['server']
+ . '&amp;db=%1$s&amp;tbl_type=table'
+ . '&amp;token=' . $GLOBALS['token'],
+ 'icon' => 'db_structure.php?server=' . $GLOBALS['server']
+ . '&amp;db=%1$s&amp;tbl_type=table'
+ . '&amp;token=' . $GLOBALS['token'],
+ );
+ if ($GLOBALS['cfg']['NavigationTreeEnableGrouping']) {
+ $this->separator = $GLOBALS['cfg']['NavigationTreeTableSeparator'];
+ $this->separator_depth = (int)(
+ $GLOBALS['cfg']['NavigationTreeTableLevel']
+ );
+ }
+ $this->real_name = 'tables';
+ $this->classes = 'tableContainer subContainer';
+
+ $new_label = _pgettext('Create new table', 'New');
+ $new = PMA_NodeFactory::getInstance('Node', $new_label);
+ $new->isNew = true;
+ $new->icon = PMA_Util::getImage('b_table_add.png', $new_label);
+ $new->links = array(
+ 'text' => 'tbl_create.php?server=' . $GLOBALS['server']
+ . '&amp;db=%2$s&amp;token=' . $GLOBALS['token'],
+ 'icon' => 'tbl_create.php?server=' . $GLOBALS['server']
+ . '&amp;db=%2$s&amp;token=' . $GLOBALS['token'],
+ );
+ $new->classes = 'new_table italics';
+ $this->addChild($new);
+ }
+}
+
+?>
diff --git a/libraries/navigation/Nodes/Node_Trigger.class.php b/libraries/navigation/Nodes/Node_Trigger.class.php
new file mode 100644
index 0000000000..bc51106cae
--- /dev/null
+++ b/libraries/navigation/Nodes/Node_Trigger.class.php
@@ -0,0 +1,45 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Functionality for the navigation tree
+ *
+ * @package PhpMyAdmin-Navigation
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ * Represents a trigger node in the navigation tree
+ *
+ * @package PhpMyAdmin-Navigation
+ */
+class Node_Trigger extends Node
+{
+ /**
+ * Initialises the class
+ *
+ * @param string $name An identifier for the new node
+ * @param int $type Type of node, may be one of CONTAINER or OBJECT
+ * @param bool $is_group Whether this object has been created
+ * while grouping nodes
+ *
+ * @return Node_Trigger
+ */
+ public function __construct($name, $type = Node::OBJECT, $is_group = false)
+ {
+ parent::__construct($name, $type, $is_group);
+ $this->icon = PMA_Util::getImage('b_triggers.png');
+ $this->links = array(
+ 'text' => 'db_triggers.php?server=' . $GLOBALS['server']
+ . '&amp;db=%3$s&amp;item_name=%1$s&amp;edit_item=1'
+ . '&amp;token=' . $GLOBALS['token'],
+ 'icon' => 'db_triggers.php?server=' . $GLOBALS['server']
+ . '&amp;db=%3$s&amp;item_name=%1$s&amp;export_item=1'
+ . '&amp;token=' . $GLOBALS['token']
+ );
+ $this->classes = 'trigger';
+ }
+}
+
+?>
diff --git a/libraries/navigation/Nodes/Node_Trigger_Container.class.php b/libraries/navigation/Nodes/Node_Trigger_Container.class.php
new file mode 100644
index 0000000000..1d47d31f19
--- /dev/null
+++ b/libraries/navigation/Nodes/Node_Trigger_Container.class.php
@@ -0,0 +1,55 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Functionality for the navigation tree
+ *
+ * @package PhpMyAdmin-Navigation
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ * Represents a container for trigger nodes in the navigation tree
+ *
+ * @package PhpMyAdmin-Navigation
+ */
+class Node_Trigger_Container extends Node
+{
+ /**
+ * Initialises the class
+ *
+ * @return Node_Trigger_Container
+ */
+ public function __construct()
+ {
+ parent::__construct(__('Triggers'), Node::CONTAINER);
+ $this->icon = PMA_Util::getImage('b_triggers.png');
+ $this->links = array(
+ 'text' => 'db_triggers.php?server=' . $GLOBALS['server']
+ . '&amp;db=%2$s&amp;table=%1$s&amp;token=' . $GLOBALS['token'],
+ 'icon' => 'db_triggers.php?server=' . $GLOBALS['server']
+ . '&amp;db=%2$s&amp;table=%1$s&amp;token=' . $GLOBALS['token']
+ );
+ $this->real_name = 'triggers';
+
+ $new = PMA_NodeFactory::getInstance(
+ 'Node', _pgettext('Create new trigger', 'New')
+ );
+ $new->isNew = true;
+ $new->icon = PMA_Util::getImage('b_trigger_add.png', '');
+ $new->links = array(
+ 'text' => 'db_triggers.php?server=' . $GLOBALS['server']
+ . '&amp;db=%3$s&amp;token=' . $GLOBALS['token']
+ . '&amp;add_item=1',
+ 'icon' => 'db_triggers.php?server=' . $GLOBALS['server']
+ . '&amp;db=%3$s&amp;token=' . $GLOBALS['token']
+ . '&amp;add_item=1',
+ );
+ $new->classes = 'new_trigger italics';
+ $this->addChild($new);
+ }
+
+}
+
+?>
diff --git a/libraries/navigation/Nodes/Node_View.class.php b/libraries/navigation/Nodes/Node_View.class.php
new file mode 100644
index 0000000000..cdb4f33089
--- /dev/null
+++ b/libraries/navigation/Nodes/Node_View.class.php
@@ -0,0 +1,57 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Functionality for the navigation tree
+ *
+ * @package PhpMyAdmin-Navigation
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+require_once 'libraries/navigation/Nodes/Node_DatabaseChild.class.php';
+
+/**
+ * Represents a view node in the navigation tree
+ *
+ * @package PhpMyAdmin-Navigation
+ */
+class Node_View extends Node_DatabaseChild
+{
+ /**
+ * Initialises the class
+ *
+ * @param string $name An identifier for the new node
+ * @param int $type Type of node, may be one of CONTAINER or OBJECT
+ * @param bool $is_group Whether this object has been created
+ * while grouping nodes
+ *
+ * @return Node_View
+ */
+ public function __construct($name, $type = Node::OBJECT, $is_group = false)
+ {
+ parent::__construct($name, $type, $is_group);
+ $this->icon = PMA_Util::getImage('b_props.png', __('View'));
+ $this->links = array(
+ 'text' => 'sql.php?server=' . $GLOBALS['server']
+ . '&amp;db=%2$s&amp;table=%1$s&amp;pos=0'
+ . '&amp;token=' . $GLOBALS['token'],
+ 'icon' => 'tbl_structure.php?server=' . $GLOBALS['server']
+ . '&amp;db=%2$s&amp;table=%1$s'
+ . '&amp;token=' . $GLOBALS['token']
+ );
+ $this->classes = 'view';
+ }
+
+ /**
+ * Returns the type of the item represented by the node.
+ *
+ * @return string type of the item
+ */
+ protected function getItemType()
+ {
+ return 'view';
+ }
+}
+
+?>
diff --git a/libraries/navigation/Nodes/Node_View_Container.class.php b/libraries/navigation/Nodes/Node_View_Container.class.php
new file mode 100644
index 0000000000..775c5500c3
--- /dev/null
+++ b/libraries/navigation/Nodes/Node_View_Container.class.php
@@ -0,0 +1,60 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Functionality for the navigation tree
+ *
+ * @package PhpMyAdmin-Navigation
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ * Represents a container for view nodes in the navigation tree
+ *
+ * @package PhpMyAdmin-Navigation
+ */
+class Node_View_Container extends Node
+{
+ /**
+ * Initialises the class
+ *
+ * @return Node_View_Container
+ */
+ public function __construct()
+ {
+ parent::__construct(__('Views'), Node::CONTAINER);
+ $this->icon = PMA_Util::getImage('b_views.png', __('Views'));
+ $this->links = array(
+ 'text' => 'db_structure.php?server=' . $GLOBALS['server']
+ . '&amp;db=%1$s&amp;tbl_type=view'
+ . '&amp;token=' . $GLOBALS['token'],
+ 'icon' => 'db_structure.php?server=' . $GLOBALS['server']
+ . '&amp;db=%1$s&amp;tbl_type=view'
+ . '&amp;token=' . $GLOBALS['token'],
+ );
+ if ($GLOBALS['cfg']['NavigationTreeEnableGrouping']) {
+ $this->separator = $GLOBALS['cfg']['NavigationTreeTableSeparator'];
+ $this->separator_depth = (int)(
+ $GLOBALS['cfg']['NavigationTreeTableLevel']
+ );
+ }
+ $this->classes = 'viewContainer subContainer';
+ $this->real_name = 'views';
+
+ $new_label = _pgettext('Create new view', 'New');
+ $new = PMA_NodeFactory::getInstance('Node', $new_label);
+ $new->isNew = true;
+ $new->icon = PMA_Util::getImage('b_view_add.png', $new_label);
+ $new->links = array(
+ 'text' => 'view_create.php?server=' . $GLOBALS['server']
+ . '&amp;db=%2$s&amp;token=' . $GLOBALS['token'],
+ 'icon' => 'view_create.php?server=' . $GLOBALS['server']
+ . '&amp;db=%2$s&amp;token=' . $GLOBALS['token'],
+ );
+ $new->classes = 'new_view italics';
+ $this->addChild($new);
+ }
+}
+
+?>
diff --git a/libraries/opendocument.lib.php b/libraries/opendocument.lib.php
new file mode 100644
index 0000000000..08ff2b16b7
--- /dev/null
+++ b/libraries/opendocument.lib.php
@@ -0,0 +1,171 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Simple interface for creating OASIS OpenDocument files.
+ *
+ * @package PhpMyAdmin
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ * Load ZIP handler.
+ */
+require_once './libraries/zip.lib.php';
+
+$GLOBALS['OpenDocumentNS']
+ = 'xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" '
+ . 'xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0" '
+ . 'xmlns:table="urn:oasis:names:tc:opendocument:xmlns:table:1.0" '
+ . 'xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0" '
+ . 'xmlns:number="urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0" '
+ . 'xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0" ';
+
+/**
+ * Minimalistic creator of OASIS OpenDocument
+ *
+ * @param string $mime desired MIME type
+ * @param string $data document content
+ *
+ * @return string OASIS OpenDocument data
+ *
+ * @access public
+ */
+function PMA_createOpenDocument($mime, $data)
+{
+ $zipfile = new ZipFile();
+ $zipfile -> addFile($mime, 'mimetype');
+ $zipfile -> addFile($data, 'content.xml');
+ $zipfile -> addFile(
+ '<?xml version="1.0" encoding="UTF-8"?'. '>'
+ . '<office:document-meta '
+ . 'xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" '
+ . 'xmlns:meta="urn:oasis:names:tc:opendocument:xmlns:meta:1.0" '
+ . 'office:version="1.0">'
+ . '<office:meta>'
+ . '<meta:generator>phpMyAdmin ' . PMA_VERSION . '</meta:generator>'
+ . '<meta:initial-creator>phpMyAdmin ' . PMA_VERSION
+ . '</meta:initial-creator>'
+ . '<meta:creation-date>' . strftime('%Y-%m-%dT%H:%M:%S')
+ . '</meta:creation-date>'
+ . '</office:meta>'
+ . '</office:document-meta>',
+ 'meta.xml'
+ );
+ $zipfile -> addFile(
+ '<?xml version="1.0" encoding="UTF-8"?' . '>'
+ . '<office:document-styles '. $GLOBALS['OpenDocumentNS']
+ . 'office:version="1.0">'
+ . '<office:font-face-decls>'
+ . '<style:font-face style:name="Arial Unicode MS"'
+ . ' svg:font-family="\'Arial Unicode MS\'" style:font-pitch="variable"/>'
+ . '<style:font-face style:name="DejaVu Sans1"'
+ . ' svg:font-family="\'DejaVu Sans\'" style:font-pitch="variable"/>'
+ . '<style:font-face style:name="HG Mincho Light J"'
+ . ' svg:font-family="\'HG Mincho Light J\'" style:font-pitch="variable"/>'
+ . '<style:font-face style:name="DejaVu Serif"'
+ . ' svg:font-family="\'DejaVu Serif\'" style:font-family-generic="roman"'
+ . ' style:font-pitch="variable"/>'
+ . '<style:font-face style:name="Thorndale"'
+ . ' svg:font-family="Thorndale" style:font-family-generic="roman"'
+ . ' style:font-pitch="variable"/>'
+ . '<style:font-face style:name="DejaVu Sans"'
+ . ' svg:font-family="\'DejaVu Sans\'" style:font-family-generic="swiss"'
+ . ' style:font-pitch="variable"/>'
+ . '</office:font-face-decls>'
+ . '<office:styles>'
+ . '<style:default-style style:family="paragraph">'
+ . '<style:paragraph-properties fo:hyphenation-ladder-count="no-limit"'
+ . ' style:text-autospace="ideograph-alpha" style:punctuation-wrap="hanging"'
+ . ' style:line-break="strict" style:tab-stop-distance="0.4925in"'
+ . ' style:writing-mode="page"/>'
+ . '<style:text-properties style:use-window-font-color="true"'
+ . ' style:font-name="DejaVu Serif" fo:font-size="12pt" fo:language="en"'
+ . ' fo:country="US" style:font-name-asian="DejaVu Sans1"'
+ . ' style:font-size-asian="12pt" style:language-asian="none"'
+ . ' style:country-asian="none" style:font-name-complex="DejaVu Sans1"'
+ . ' style:font-size-complex="12pt" style:language-complex="none"'
+ . ' style:country-complex="none" fo:hyphenate="false"'
+ . ' fo:hyphenation-remain-char-count="2"'
+ . ' fo:hyphenation-push-char-count="2"/>'
+ . '</style:default-style>'
+ . '<style:style style:name="Standard" style:family="paragraph"'
+ . ' style:class="text"/>'
+ . '<style:style style:name="Text_body" style:display-name="Text body"'
+ . ' style:family="paragraph" style:parent-style-name="Standard"'
+ . ' style:class="text">'
+ . '<style:paragraph-properties fo:margin-top="0in"'
+ . ' fo:margin-bottom="0.0835in"/>'
+ . '</style:style>'
+ . '<style:style style:name="Heading" style:family="paragraph"'
+ . ' style:parent-style-name="Standard" style:next-style-name="Text_body"'
+ . ' style:class="text">'
+ . '<style:paragraph-properties fo:margin-top="0.1665in"'
+ . ' fo:margin-bottom="0.0835in" fo:keep-with-next="always"/>'
+ . '<style:text-properties style:font-name="DejaVu Sans" fo:font-size="14pt"'
+ . ' style:font-name-asian="DejaVu Sans1" style:font-size-asian="14pt"'
+ . ' style:font-name-complex="DejaVu Sans1" style:font-size-complex="14pt"/>'
+ . '</style:style>'
+ . '<style:style style:name="Heading_1" style:display-name="Heading 1"'
+ . ' style:family="paragraph" style:parent-style-name="Heading"'
+ . ' style:next-style-name="Text_body" style:class="text"'
+ . ' style:default-outline-level="1">'
+ . '<style:text-properties style:font-name="Thorndale" fo:font-size="24pt"'
+ . ' fo:font-weight="bold" style:font-name-asian="HG Mincho Light J"'
+ . ' style:font-size-asian="24pt" style:font-weight-asian="bold"'
+ . ' style:font-name-complex="Arial Unicode MS"'
+ . ' style:font-size-complex="24pt" style:font-weight-complex="bold"/>'
+ . '</style:style>'
+ . '<style:style style:name="Heading_2" style:display-name="Heading 2"'
+ . ' style:family="paragraph" style:parent-style-name="Heading"'
+ . ' style:next-style-name="Text_body" style:class="text"'
+ . ' style:default-outline-level="2">'
+ . '<style:text-properties style:font-name="DejaVu Serif"'
+ . ' fo:font-size="18pt" fo:font-weight="bold"'
+ . ' style:font-name-asian="DejaVu Sans1" style:font-size-asian="18pt"'
+ . ' style:font-weight-asian="bold" style:font-name-complex="DejaVu Sans1"'
+ . ' style:font-size-complex="18pt" style:font-weight-complex="bold"/>'
+ . '</style:style>'
+ . '</office:styles>'
+ . '<office:automatic-styles>'
+ . '<style:page-layout style:name="pm1">'
+ . '<style:page-layout-properties fo:page-width="8.2673in"'
+ . ' fo:page-height="11.6925in" style:num-format="1"'
+ . ' style:print-orientation="portrait" fo:margin-top="1in"'
+ . ' fo:margin-bottom="1in" fo:margin-left="1.25in"'
+ . ' fo:margin-right="1.25in" style:writing-mode="lr-tb"'
+ . ' style:footnote-max-height="0in">'
+ . '<style:footnote-sep style:width="0.0071in"'
+ . ' style:distance-before-sep="0.0398in"'
+ . ' style:distance-after-sep="0.0398in" style:adjustment="left"'
+ . ' style:rel-width="25%" style:color="#000000"/>'
+ . '</style:page-layout-properties>'
+ . '<style:header-style/>'
+ . '<style:footer-style/>'
+ . '</style:page-layout>'
+ . '</office:automatic-styles>'
+ . '<office:master-styles>'
+ . '<style:master-page style:name="Standard" style:page-layout-name="pm1"/>'
+ . '</office:master-styles>'
+ . '</office:document-styles>',
+ 'styles.xml'
+ );
+ $zipfile -> addFile(
+ '<?xml version="1.0" encoding="UTF-8"?' . '>'
+ . '<manifest:manifest'
+ . ' xmlns:manifest="urn:oasis:names:tc:opendocument:xmlns:manifest:1.0">'
+ . '<manifest:file-entry manifest:media-type="' . $mime
+ . '" manifest:full-path="/"/>'
+ . '<manifest:file-entry manifest:media-type="text/xml"'
+ . ' manifest:full-path="content.xml"/>'
+ . '<manifest:file-entry manifest:media-type="text/xml"'
+ . ' manifest:full-path="meta.xml"/>'
+ . '<manifest:file-entry manifest:media-type="text/xml"'
+ . ' manifest:full-path="styles.xml"/>'
+ . '</manifest:manifest>',
+ 'META-INF/manifest.xml'
+ );
+ return $zipfile -> file();
+}
+?>
diff --git a/libraries/operations.lib.php b/libraries/operations.lib.php
new file mode 100644
index 0000000000..a47ae51b18
--- /dev/null
+++ b/libraries/operations.lib.php
@@ -0,0 +1,1619 @@
+<?php
+
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * set of functions with the operations section in pma
+ *
+ * @package PhpMyAdmin
+ */
+
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ * Get HTML output for database comment
+ *
+ * @param string $db database name
+ *
+ * @return string $html_output
+ */
+function PMA_getHtmlForDatabaseComment($db)
+{
+ $html_output = '<div class="operations_half_width">'
+ . '<form method="post" action="db_operations.php">'
+ . PMA_URL_getHiddenInputs($db)
+ . '<fieldset>'
+ . '<legend>';
+ if (PMA_Util::showIcons('ActionLinksMode')) {
+ $html_output .= '<img class="icon ic_b_comment" '
+ . 'src="themes/dot.gif" alt="" />';
+ }
+ $html_output .= __('Database comment: ');
+ $html_output .= '</legend>';
+ $html_output .= '<input type="text" name="comment" '
+ . 'class="textfield" size="30"'
+ . 'value="' . htmlspecialchars(PMA_getDBComment($db)) . '" />'
+ . '</fieldset>';
+ $html_output .= '<fieldset class="tblFooters">'
+ . '<input type="submit" value="' . __('Go') . '" />'
+ . '</fieldset>'
+ . '</form>'
+ . '</div>';
+
+ return $html_output;
+}
+
+/**
+ * Get HTML output for rename database
+ *
+ * @param string $db database name
+ *
+ * @return string $html_output
+ */
+function PMA_getHtmlForRenameDatabase($db)
+{
+ $html_output = '<div class="operations_half_width">'
+ . '<form id="rename_db_form" '
+ . 'class="ajax" '
+ . 'method="post" action="db_operations.php" '
+ . 'onsubmit="return emptyFormElements(this, \'newname\')">';
+ if (isset($_REQUEST['db_collation'])) {
+ $html_output .= '<input type="hidden" name="db_collation" '
+ . 'value="' . $_REQUEST['db_collation']
+ .'" />' . "\n";
+ }
+ $html_output .= '<input type="hidden" name="what" value="data" />'
+ . '<input type="hidden" name="db_rename" value="true" />'
+ . PMA_URL_getHiddenInputs($db)
+ . '<fieldset>'
+ . '<legend>';
+
+ if (PMA_Util::showIcons('ActionLinksMode')) {
+ $html_output .= PMA_Util::getImage('b_edit.png');
+ }
+ $html_output .= __('Rename database to:')
+ . '</legend>';
+
+ $html_output .= '<input id="new_db_name" type="text" name="newname" '
+ . 'size="30" class="textfield" value="" />'
+ . '</fieldset>'
+ . '<fieldset class="tblFooters">'
+ . '<input id="rename_db_input" type="submit" value="' . __('Go') . '" />'
+ . '</fieldset>'
+ . '</form>'
+ . '</div>';
+
+ return $html_output;
+}
+
+/**
+ * Get HTML for database drop link
+ *
+ * @param string $db database name
+ *
+ * @return string $html_output
+ */
+function PMA_getHtmlForDropDatabaseLink($db)
+{
+ $this_sql_query = 'DROP DATABASE ' . PMA_Util::backquote($db);
+ $this_url_params = array(
+ 'sql_query' => $this_sql_query,
+ 'back' => 'db_operations.php',
+ 'goto' => 'index.php',
+ 'reload' => '1',
+ 'purge' => '1',
+ 'message_to_show' => sprintf(
+ __('Database %s has been dropped.'),
+ htmlspecialchars(PMA_Util::backquote($db))
+ ),
+ 'db' => null,
+ );
+
+ $html_output = '<div class="operations_half_width">'
+ . '<fieldset class="caution">';
+ $html_output .= '<legend>';
+ if (PMA_Util::showIcons('ActionLinksMode')) {
+ $html_output .= PMA_Util::getImage('b_deltbl.png');
+ }
+ $html_output .= __('Remove database')
+ . '</legend>';
+ $html_output .= '<ul>';
+ $html_output .= PMA_getDeleteDataOrTablelink(
+ $this_url_params,
+ 'DROP_DATABASE',
+ __('Drop the database (DROP)'),
+ 'drop_db_anchor'
+ );
+ $html_output .= '</ul></fieldset>'
+ . '</div>';
+
+ return $html_output;
+}
+
+/**
+ * Get HTML snippet for copy database
+ *
+ * @param string $db database name
+ *
+ * @return string $html_output
+ */
+function PMA_getHtmlForCopyDatabase($db)
+{
+ $drop_clause = 'DROP TABLE / DROP VIEW';
+ $choices = array(
+ 'structure' => __('Structure only'),
+ 'data' => __('Structure and data'),
+ 'dataonly' => __('Data only')
+ );
+
+ if (isset($_COOKIE)
+ && isset($_COOKIE['pma_switch_to_new'])
+ && $_COOKIE['pma_switch_to_new'] == 'true'
+ ) {
+ $pma_switch_to_new = 'true';
+ }
+
+ $html_output = '<div class="operations_half_width clearfloat">';
+ $html_output .= '<form id="copy_db_form" '
+ . 'class="ajax" '
+ . 'method="post" action="db_operations.php"'
+ . 'onsubmit="return emptyFormElements(this, \'newname\')">';
+
+ if (isset($_REQUEST['db_collation'])) {
+ $html_output .= '<input type="hidden" name="db_collation" '
+ . 'value="' . $_REQUEST['db_collation'] .'" />' . "\n";
+ }
+ $html_output .= '<input type="hidden" name="db_copy" value="true" />' . "\n"
+ . PMA_URL_getHiddenInputs($db);
+ $html_output .= '<fieldset>'
+ . '<legend>';
+
+ if (PMA_Util::showIcons('ActionLinksMode')) {
+ $html_output .= PMA_Util::getImage('b_edit.png');
+ }
+ $html_output .= __('Copy database to:')
+ . '</legend>'
+ . '<input type="text" name="newname" size="30" '
+ . 'class="textfield" value="" /><br />'
+ . PMA_Util::getRadioFields(
+ 'what', $choices, 'data', true
+ );
+ $html_output .= '<input type="checkbox" name="create_database_before_copying" '
+ . 'value="1" id="checkbox_create_database_before_copying"'
+ . 'checked="checked" />';
+ $html_output .= '<label for="checkbox_create_database_before_copying">'
+ . __('CREATE DATABASE before copying') . '</label><br />';
+ $html_output .= '<input type="checkbox" name="drop_if_exists" value="true"'
+ . 'id="checkbox_drop" />';
+ $html_output .= '<label for="checkbox_drop">'
+ . sprintf(__('Add %s'), $drop_clause)
+ . '</label><br />';
+ $html_output .= '<input type="checkbox" name="sql_auto_increment" value="1" '
+ . 'checked="checked" id="checkbox_auto_increment" />';
+ $html_output .= '<label for="checkbox_auto_increment">'
+ . __('Add AUTO_INCREMENT value') . '</label><br />';
+ $html_output .= '<input type="checkbox" name="add_constraints" value="1"'
+ . 'id="checkbox_constraints" />';
+ $html_output .= '<label for="checkbox_constraints">'
+ . __('Add constraints') . '</label><br />';
+ $html_output .= '<input type="checkbox" name="switch_to_new" value="true"'
+ . 'id="checkbox_switch"'
+ . ((isset($pma_switch_to_new) && $pma_switch_to_new == 'true')
+ ? ' checked="checked"'
+ : '')
+ . '/>';
+ $html_output .= '<label for="checkbox_switch">'
+ . __('Switch to copied database') . '</label>'
+ . '</fieldset>';
+ $html_output .= '<fieldset class="tblFooters">'
+ . '<input type="submit" name="submit_copy" value="' . __('Go') . '" />'
+ . '</fieldset>'
+ . '</form>'
+ . '</div>';
+
+ return $html_output;
+}
+
+/**
+ * Get HTML snippet for change database charset
+ *
+ * @param string $db database name
+ * @param string $table tabel name
+ *
+ * @return string $html_output
+ */
+function PMA_getHtmlForChangeDatabaseCharset($db, $table)
+{
+ $html_output = '<div class="operations_half_width">'
+ . '<form id="change_db_charset_form" ';
+ $html_output .= 'class="ajax" ';
+ $html_output .= 'method="post" action="db_operations.php">';
+
+ $html_output .= PMA_URL_getHiddenInputs($db, $table);
+
+ $html_output .= '<fieldset>' . "\n"
+ . ' <legend>';
+ if (PMA_Util::showIcons('ActionLinksMode')) {
+ $html_output .= PMA_Util::getImage('s_asci.png');
+ }
+ $html_output .= '<label for="select_db_collation">' . __('Collation')
+ . ':</label>' . "\n"
+ . '</legend>' . "\n"
+ . PMA_generateCharsetDropdownBox(
+ PMA_CSDROPDOWN_COLLATION,
+ 'db_collation',
+ 'select_db_collation',
+ isset($_REQUEST['db_collation']) ? $_REQUEST['db_collation'] : '',
+ false,
+ 3
+ )
+ . '</fieldset>'
+ . '<fieldset class="tblFooters">'
+ . '<input type="submit" name="submitcollation"'
+ . ' value="' . __('Go') . '" />' . "\n"
+ . '</fieldset>' . "\n"
+ . '</form></div>' . "\n";
+
+ return $html_output;
+}
+
+/**
+ * Get HTML snippet for export relational schema view
+ *
+ * @param string $url_query Query string for link
+ *
+ * @return string $html_output
+ */
+function PMA_getHtmlForExportRelationalSchemaView($url_query)
+{
+ $html_output = '<div class="operations_full_width">'
+ . '<fieldset><a href="schema_edit.php?' . $url_query . '">';
+ if (PMA_Util::showIcons('ActionLinksMode')) {
+ $html_output .= PMA_Util::getImage(
+ 'b_edit.png'
+ );
+ }
+ $html_output .= __('Edit or export relational schema')
+ . '</a></fieldset>'
+ . '</div>';
+
+ return $html_output;
+}
+
+/**
+ * Run the Procedure definitions and function definitions
+ *
+ * to avoid selecting alternatively the current and new db
+ * we would need to modify the CREATE definitions to qualify
+ * the db name
+ *
+ * @param string $db database name
+ *
+ * @return void
+ */
+function PMA_runProcedureAndFunctionDefinitions($db)
+{
+ $procedure_names = $GLOBALS['dbi']->getProceduresOrFunctions($db, 'PROCEDURE');
+ if ($procedure_names) {
+ foreach ($procedure_names as $procedure_name) {
+ $GLOBALS['dbi']->selectDb($db);
+ $tmp_query = $GLOBALS['dbi']->getDefinition(
+ $db, 'PROCEDURE', $procedure_name
+ );
+ // collect for later display
+ $GLOBALS['sql_query'] .= "\n" . $tmp_query;
+ $GLOBALS['dbi']->selectDb($_REQUEST['newname']);
+ $GLOBALS['dbi']->query($tmp_query);
+ }
+ }
+
+ $function_names = $GLOBALS['dbi']->getProceduresOrFunctions($db, 'FUNCTION');
+ if ($function_names) {
+ foreach ($function_names as $function_name) {
+ $GLOBALS['dbi']->selectDb($db);
+ $tmp_query = $GLOBALS['dbi']->getDefinition(
+ $db, 'FUNCTION', $function_name
+ );
+ // collect for later display
+ $GLOBALS['sql_query'] .= "\n" . $tmp_query;
+ $GLOBALS['dbi']->selectDb($_REQUEST['newname']);
+ $GLOBALS['dbi']->query($tmp_query);
+ }
+ }
+}
+
+/**
+ * Get sql query and create database before copy
+ *
+ * @return string $sql_query
+ */
+function PMA_getSqlQueryAndCreateDbBeforeCopy()
+{
+ // lower_case_table_names=1 `DB` becomes `db`
+ if (! PMA_DRIZZLE) {
+ $lower_case_table_names = $GLOBALS['dbi']->fetchValue(
+ 'SHOW VARIABLES LIKE "lower_case_table_names"', 0, 1
+ );
+ if ($lower_case_table_names === '1') {
+ $_REQUEST['newname'] = $GLOBALS['PMA_String']->strtolower(
+ $_REQUEST['newname']
+ );
+ }
+ }
+
+ $local_query = 'CREATE DATABASE IF NOT EXISTS '
+ . PMA_Util::backquote($_REQUEST['newname']);
+ if (isset($_REQUEST['db_collation'])) {
+ $local_query .= ' DEFAULT'
+ . PMA_generateCharsetQueryPart($_REQUEST['db_collation']);
+ }
+ $local_query .= ';';
+ $sql_query = $local_query;
+ // save the original db name because Tracker.class.php which
+ // may be called under $GLOBALS['dbi']->query() changes $GLOBALS['db']
+ // for some statements, one of which being CREATE DATABASE
+ $original_db = $GLOBALS['db'];
+ $GLOBALS['dbi']->query($local_query);
+ $GLOBALS['db'] = $original_db;
+
+ // Set the SQL mode to NO_AUTO_VALUE_ON_ZERO to prevent MySQL from creating
+ // export statements it cannot import
+ $sql_set_mode = "SET SQL_MODE='NO_AUTO_VALUE_ON_ZERO'";
+ $GLOBALS['dbi']->query($sql_set_mode);
+
+ // rebuild the database list because PMA_Table::moveCopy
+ // checks in this list if the target db exists
+ $GLOBALS['pma']->databases->build();
+
+ return $sql_query;
+}
+
+/**
+ * remove all foreign key constraints and return
+ * sql constraints query for full database
+ *
+ * @param array $tables_full array of all tables in given db or dbs
+ * @param object $export_sql_plugin export plugin instance
+ * @param boolean $move whether database name is empty or not
+ * @param string $db database name
+ *
+ * @return string sql constraints query for full databases
+ */
+function PMA_getSqlConstraintsQueryForFullDb(
+ $tables_full, $export_sql_plugin, $move, $db
+) {
+ global $sql_constraints, $sql_drop_foreign_keys;
+ $sql_constraints_query_full_db = array();
+ foreach ($tables_full as $each_table => $tmp) {
+ /* Following globals are set in getTableDef */
+ $sql_constraints = '';
+ $sql_drop_foreign_keys = '';
+ $export_sql_plugin->getTableDef(
+ $db, $each_table, "\n", '', false, false
+ );
+ if ($move && ! empty($sql_drop_foreign_keys)) {
+ $GLOBALS['dbi']->query($sql_drop_foreign_keys);
+ }
+ // keep the constraint we just dropped
+ if (! empty($sql_constraints)) {
+ $sql_constraints_query_full_db[] = $sql_constraints;
+ }
+ }
+ return $sql_constraints_query_full_db;
+}
+
+/**
+ * Get views as an array and create SQL view stand-in
+ *
+ * @param array $tables_full array of all tables in given db or dbs
+ * @param object $export_sql_plugin export plugin instance
+ * @param string $db database name
+ *
+ * @return array $views
+ */
+function PMA_getViewsAndCreateSqlViewStandIn(
+ $tables_full, $export_sql_plugin, $db
+) {
+ $views = array();
+ foreach ($tables_full as $each_table => $tmp) {
+ // to be able to rename a db containing views,
+ // first all the views are collected and a stand-in is created
+ // the real views are created after the tables
+ if (PMA_Table::isView($db, $each_table)) {
+ $views[] = $each_table;
+ // Create stand-in definition to resolve view dependencies
+ $sql_view_standin = $export_sql_plugin->getTableDefStandIn(
+ $db, $each_table, "\n"
+ );
+ $GLOBALS['dbi']->selectDb($_REQUEST['newname']);
+ $GLOBALS['dbi']->query($sql_view_standin);
+ $GLOBALS['sql_query'] .= "\n" . $sql_view_standin;
+ }
+ }
+ return $views;
+}
+
+/**
+ * Get sql query for copy/rename table and boolean for whether copy/rename or not
+ *
+ * @param array $tables_full array of all tables in given db or dbs
+ * @param string $sql_query sql query for all operations
+ * @param boolean $move whether database name is empty or not
+ * @param string $db database name
+ *
+ * @return array ($sql_query, $error)
+ */
+function PMA_getSqlQueryForCopyTable($tables_full, $sql_query, $move, $db)
+{
+ $error = false;
+ foreach ($tables_full as $each_table => $tmp) {
+ // skip the views; we have creted stand-in definitions
+ if (PMA_Table::isView($db, $each_table)) {
+ continue;
+ }
+ $back = $sql_query;
+ $sql_query = '';
+
+ // value of $what for this table only
+ $this_what = $_REQUEST['what'];
+
+ // do not copy the data from a Merge table
+ // note: on the calling FORM, 'data' means 'structure and data'
+ if (PMA_Table::isMerge($db, $each_table)) {
+ if ($this_what == 'data') {
+ $this_what = 'structure';
+ }
+ if ($this_what == 'dataonly') {
+ $this_what = 'nocopy';
+ }
+ }
+
+ if ($this_what != 'nocopy') {
+ // keep the triggers from the original db+table
+ // (third param is empty because delimiters are only intended
+ // for importing via the mysql client or our Import feature)
+ $triggers = $GLOBALS['dbi']->getTriggers($db, $each_table, '');
+
+ if (! PMA_Table::moveCopy(
+ $db, $each_table, $_REQUEST['newname'], $each_table,
+ (isset($this_what) ? $this_what : 'data'),
+ $move, 'db_copy'
+ )) {
+ $error = true;
+ // $sql_query is filled by PMA_Table::moveCopy()
+ $sql_query = $back . $sql_query;
+ break;
+ }
+ // apply the triggers to the destination db+table
+ if ($triggers) {
+ $GLOBALS['dbi']->selectDb($_REQUEST['newname']);
+ foreach ($triggers as $trigger) {
+ $GLOBALS['dbi']->query($trigger['create']);
+ $GLOBALS['sql_query'] .= "\n" . $trigger['create'] . ';';
+ }
+ }
+
+ // this does not apply to a rename operation
+ if (isset($_REQUEST['add_constraints'])
+ && ! empty($GLOBALS['sql_constraints_query'])
+ ) {
+ $GLOBALS['sql_constraints_query_full_db'][]
+ = $GLOBALS['sql_constraints_query'];
+ unset($GLOBALS['sql_constraints_query']);
+ }
+ }
+ // $sql_query is filled by PMA_Table::moveCopy()
+ $sql_query = $back . $sql_query;
+ }
+ return array($sql_query, $error);
+}
+
+/**
+ * Run the EVENT definition for selected database
+ *
+ * to avoid selecting alternatively the current and new db
+ * we would need to modify the CREATE definitions to qualify
+ * the db name
+ *
+ * @param string $db database name
+ *
+ * @return void
+ */
+function PMA_runEventDefinitionsForDb($db)
+{
+ $event_names = $GLOBALS['dbi']->fetchResult(
+ 'SELECT EVENT_NAME FROM information_schema.EVENTS WHERE EVENT_SCHEMA= \''
+ . PMA_Util::sqlAddSlashes($db, true) . '\';'
+ );
+ if ($event_names) {
+ foreach ($event_names as $event_name) {
+ $GLOBALS['dbi']->selectDb($db);
+ $tmp_query = $GLOBALS['dbi']->getDefinition($db, 'EVENT', $event_name);
+ // collect for later display
+ $GLOBALS['sql_query'] .= "\n" . $tmp_query;
+ $GLOBALS['dbi']->selectDb($_REQUEST['newname']);
+ $GLOBALS['dbi']->query($tmp_query);
+ }
+ }
+}
+
+/**
+ * Handle the views, return the boolean value whether table rename/copy or not
+ *
+ * @param array $views views as an array
+ * @param boolean $move whether database name is empty or not
+ * @param string $db database name
+ *
+ * @return boolean $_error whether table rename/copy or not
+ */
+function PMA_handleTheViews($views, $move, $db)
+{
+ $_error = false;
+ // temporarily force to add DROP IF EXIST to CREATE VIEW query,
+ // to remove stand-in VIEW that was created earlier
+ // ( $_REQUEST['drop_if_exists'] is used in moveCopy() )
+ if (isset($_REQUEST['drop_if_exists'])) {
+ $temp_drop_if_exists = $_REQUEST['drop_if_exists'];
+ }
+ $_REQUEST['drop_if_exists'] = 'true';
+
+ foreach ($views as $view) {
+ $copying_succeeded = PMA_Table::moveCopy(
+ $db, $view, $_REQUEST['newname'], $view, 'structure', $move, 'db_copy'
+ );
+ if (! $copying_succeeded) {
+ $_error = true;
+ break;
+ }
+ }
+ unset($_REQUEST['drop_if_exists']);
+ if (isset($temp_drop_if_exists)) {
+ // restore previous value
+ $_REQUEST['drop_if_exists'] = $temp_drop_if_exists;
+ }
+ return $_error;
+}
+
+/**
+ * Create all accumulated constraaints
+ *
+ * @return void
+ */
+function PMA_createAllAccumulatedConstraints()
+{
+ $GLOBALS['dbi']->selectDb($_REQUEST['newname']);
+ foreach ($GLOBALS['sql_constraints_query_full_db'] as $one_query) {
+ $GLOBALS['dbi']->query($one_query);
+ // and prepare to display them
+ $GLOBALS['sql_query'] .= "\n" . $one_query;
+ }
+ unset($GLOBALS['sql_constraints_query_full_db']);
+}
+
+/**
+ * Duplicate the bookmarks for the db (done once for each db)
+ *
+ * @param boolean $_error whether table rename/copy or not
+ * @param string $db database name
+ *
+ * @return void
+ */
+function PMA_duplicateBookmarks($_error, $db)
+{
+ if (! $_error && $db != $_REQUEST['newname']) {
+ $get_fields = array('user', 'label', 'query');
+ $where_fields = array('dbase' => $db);
+ $new_fields = array('dbase' => $_REQUEST['newname']);
+ PMA_Table::duplicateInfo(
+ 'bookmarkwork', 'bookmark', $get_fields,
+ $where_fields, $new_fields
+ );
+ }
+}
+
+/**
+ * Get the HTML snippet for order the table
+ *
+ * @param array $columns columns array
+ *
+ * @return string $html_out
+ */
+function PMA_getHtmlForOrderTheTable($columns)
+{
+ $html_output = '<div class="operations_half_width">';
+ $html_output .= '<form method="post" id="alterTableOrderby" '
+ . 'action="tbl_operations.php">';
+ $html_output .= PMA_URL_getHiddenInputs(
+ $GLOBALS['db'], $GLOBALS['table']
+ );
+ $html_output .= '<fieldset id="fieldset_table_order">'
+ . '<legend>' . __('Alter table order by') . '</legend>'
+ . '<select name="order_field">';
+
+ foreach ($columns as $fieldname) {
+ $html_output .= '<option '
+ . 'value="' . htmlspecialchars($fieldname['Field']) . '">'
+ . htmlspecialchars($fieldname['Field']) . '</option>' . "\n";
+ }
+ $html_output .= '</select> ' . __('(singly)') . ' '
+ . '<br />'
+ . '<input id="order_order_asc" name="order_order"'
+ . ' type="radio" value="asc" checked="checked" />'
+ . '<label for="order_order_asc">' . __('Ascending') . '</label>'
+ . '<input id="order_order_desc" name="order_order"'
+ . ' type="radio" value="desc" />'
+ . '<label for="order_order_desc">' . __('Descending') . '</label>'
+ . '</fieldset>'
+ . '<fieldset class="tblFooters">'
+ . '<input type="hidden" name="submitorderby" value="1" />'
+ . '<input type="submit" value="' . __('Go') . '" />'
+ . '</fieldset>'
+ . '</form>'
+ . '</div>';
+
+ return $html_output;
+}
+
+/**
+ * Get the HTML snippet for move table
+ *
+ * @return string $html_output
+ */
+function PMA_getHtmlForMoveTable()
+{
+ $html_output = '<div class="operations_half_width">';
+ $html_output .= '<form method="post" action="tbl_operations.php"'
+ . ' id="moveTableForm" class="ajax"'
+ . ' onsubmit="return emptyFormElements(this, \'new_name\')">'
+ . PMA_URL_getHiddenInputs($GLOBALS['db'], $GLOBALS['table']);
+
+ $html_output .= '<input type="hidden" name="reload" value="1" />'
+ . '<input type="hidden" name="what" value="data" />'
+ . '<fieldset id="fieldset_table_rename">';
+
+ $html_output .= '<legend>' . __('Move table to (database<b>.</b>table):')
+ . '</legend>';
+
+ if (count($GLOBALS['pma']->databases) > $GLOBALS['cfg']['MaxDbList']) {
+ $html_output .= '<input type="text" maxlength="100" size="30" '
+ . 'name="target_db" value="' . htmlspecialchars($GLOBALS['db'])
+ . '"/>';
+ } else {
+ $html_output .= '<select class="halfWidth" name="target_db">'
+ . $GLOBALS['pma']->databases->getHtmlOptions(true, false)
+ . '</select>';
+ }
+ $html_output .= '&nbsp;<strong>.</strong>&nbsp;';
+ $html_output .= '<input class="halfWidth" type="text" size="20" name="new_name"'
+ . ' onfocus="this.select()" required '
+ . 'value="' . htmlspecialchars($GLOBALS['table']) . '" /><br />';
+
+ // starting with MySQL 5.0.24, SHOW CREATE TABLE includes the AUTO_INCREMENT
+ // next value but users can decide if they want it or not for the operation
+
+ $html_output .= '<input type="checkbox" name="sql_auto_increment" '
+ . 'value="1" id="checkbox_auto_increment_mv" checked="checked" />'
+ . '<label for="checkbox_auto_increment_mv">'
+ . __('Add AUTO_INCREMENT value')
+ . '</label><br />'
+ . '</fieldset>';
+
+ $html_output .= '<fieldset class="tblFooters">'
+ . '<input type="submit" name="submit_move" value="' . __('Go') . '" />'
+ . '</fieldset>'
+ . '</form>'
+ . '</div>';
+
+ return $html_output;
+}
+
+/**
+ * Get the HTML div for Table option
+ *
+ * @param string $comment Comment
+ * @param array $tbl_collation table collation
+ * @param string $tbl_storage_engine table storage engine
+ * @param boolean $is_myisam_or_aria whether MYISAM | ARIA or not
+ * @param boolean $is_isam whether ISAM or not
+ * @param array $pack_keys pack keys
+ * @param string $auto_increment value of auto increment
+ * @param string $delay_key_write delay key write
+ * @param string $transactional value of transactional
+ * @param string $page_checksum value of page checksum
+ * @param boolean $is_innodb whether INNODB or not
+ * @param boolean $is_pbxt whether PBXT or not
+ * @param boolean $is_aria whether ARIA or not
+ * @param string $checksum the checksum
+ *
+ * @return string $html_output
+ */
+function PMA_getTableOptionDiv($comment, $tbl_collation, $tbl_storage_engine,
+ $is_myisam_or_aria, $is_isam, $pack_keys, $auto_increment, $delay_key_write,
+ $transactional, $page_checksum, $is_innodb, $is_pbxt, $is_aria, $checksum
+) {
+ $html_output = '<div class="operations_half_width clearfloat">';
+ $html_output .= '<form method="post" action="tbl_operations.php"';
+ $html_output .= ' id="tableOptionsForm" class="ajax">';
+ $html_output .= PMA_URL_getHiddenInputs(
+ $GLOBALS['db'], $GLOBALS['table']
+ );
+ $html_output .= '<input type="hidden" name="reload" value="1" />';
+
+ $html_output .= PMA_getTableOptionFieldset(
+ $comment, $tbl_collation,
+ $tbl_storage_engine, $is_myisam_or_aria, $is_isam, $pack_keys,
+ $delay_key_write, $auto_increment, $transactional, $page_checksum,
+ $is_innodb, $is_pbxt, $is_aria, $checksum
+ );
+
+ $html_output .= '<fieldset class="tblFooters">'
+ . '<input type="hidden" name="submitoptions" value="1" />'
+ . '<input type="submit" value="' . __('Go') . '" />'
+ . '</fieldset>'
+ . '</form>'
+ . '</div>';
+
+ return $html_output;
+}
+
+/**
+ * Get HTML fieldset for Table option, it contains HTML table for options
+ *
+ * @param string $comment Comment
+ * @param array $tbl_collation table collation
+ * @param string $tbl_storage_engine table storage engine
+ * @param boolean $is_myisam_or_aria whether MYISAM | ARIA or not
+ * @param boolean $is_isam whether ISAM or not
+ * @param array $pack_keys pack keys
+ * @param string $delay_key_write delay key write
+ * @param string $auto_increment value of auto increment
+ * @param string $transactional value of transactional
+ * @param string $page_checksum value of page checksum
+ * @param boolean $is_innodb whether INNODB or not
+ * @param boolean $is_pbxt whether PBXT or not
+ * @param boolean $is_aria whether ARIA or not
+ * @param string $checksum the checksum
+ *
+ * @return string $html_output
+ */
+function PMA_getTableOptionFieldset($comment, $tbl_collation,
+ $tbl_storage_engine, $is_myisam_or_aria, $is_isam, $pack_keys,
+ $delay_key_write, $auto_increment, $transactional,
+ $page_checksum, $is_innodb, $is_pbxt, $is_aria, $checksum
+) {
+ $html_output = '<fieldset>'
+ . '<legend>' . __('Table options') . '</legend>';
+
+ $html_output .= '<table>';
+ //Change table name
+ $html_output .= '<tr><td>' . __('Rename table to') . '</td>'
+ . '<td>'
+ . '<input type="text" size="20" name="new_name" onfocus="this.select()"'
+ . 'value="' . htmlspecialchars($GLOBALS['table']) . '" required />'
+ . '</td>'
+ . '</tr>';
+
+ //Table comments
+ $html_output .= '<tr><td>' . __('Table comments') . '</td>'
+ . '<td><input type="text" name="comment" maxlength="60" size="30"'
+ . 'value="' . htmlspecialchars($comment) . '" onfocus="this.select()" />'
+ . '<input type="hidden" name="prev_comment" value="'
+ . htmlspecialchars($comment) . '" />'
+ . '</td>'
+ . '</tr>';
+
+ //Storage engine
+ $html_output .= '<tr><td>' . __('Storage Engine')
+ . PMA_Util::showMySQLDocu('Storage_engines')
+ . '</td>'
+ . '<td>'
+ . PMA_StorageEngine::getHtmlSelect(
+ 'new_tbl_storage_engine', null, $tbl_storage_engine
+ )
+ . '</td>'
+ . '</tr>';
+
+ //Table character set
+ $html_output .= '<tr><td>' . __('Collation') . '</td>'
+ . '<td>'
+ . PMA_generateCharsetDropdownBox(
+ PMA_CSDROPDOWN_COLLATION,
+ 'tbl_collation', null, $tbl_collation, false, 3
+ )
+ . '</td>'
+ . '</tr>';
+
+ if ($is_myisam_or_aria || $is_isam) {
+ $html_output .= '<tr>'
+ . '<td><label for="new_pack_keys">PACK_KEYS</label></td>'
+ . '<td><select name="new_pack_keys" id="new_pack_keys">';
+
+ $html_output .= '<option value="DEFAULT"';
+ if ($pack_keys == 'DEFAULT') {
+ $html_output .= 'selected="selected"';
+ }
+ $html_output .= '>DEFAULT</option>
+ <option value="0"';
+ if ($pack_keys == '0') {
+ $html_output .= 'selected="selected"';
+ }
+ $html_output .= '>0</option>
+ <option value="1" ';
+ if ($pack_keys == '1') {
+ $html_output .= 'selected="selected"';
+ }
+ $html_output .= '>1</option>'
+ . '</select>'
+ . '</td>'
+ . '</tr>';
+ } // end if (MYISAM|ISAM)
+
+ if ($is_myisam_or_aria) {
+ $html_output .= PMA_getHtmlForTableRow(
+ 'new_checksum',
+ 'CHECKSUM',
+ $checksum
+ );
+
+ $html_output .= PMA_getHtmlForTableRow(
+ 'new_delay_key_write',
+ 'DELAY_KEY_WRITE',
+ $delay_key_write
+ );
+ } // end if (MYISAM)
+
+ if ($is_aria) {
+ $html_output .= PMA_getHtmlForTableRow(
+ 'new_transactional',
+ 'TRANSACTIONAL',
+ $transactional
+ );
+
+ $html_output .= PMA_getHtmlForTableRow(
+ 'new_page_checksum',
+ 'PAGE_CHECKSUM',
+ $page_checksum
+ );
+ } // end if (ARIA)
+
+ if (strlen($auto_increment) > 0
+ && ($is_myisam_or_aria || $is_innodb || $is_pbxt)
+ ) {
+ $html_output .= '<tr><td>'
+ . '<label for="auto_increment_opt">AUTO_INCREMENT</label></td>'
+ . '<td><input type="number" name="new_auto_increment" '
+ . 'id="auto_increment_opt"'
+ . 'value="' . $auto_increment . '" /></td>'
+ . '</tr> ';
+ } // end if (MYISAM|INNODB)
+
+ $possible_row_formats = PMA_getPossibleRowFormat();
+
+ // for MYISAM there is also COMPRESSED but it can be set only by the
+ // myisampack utility, so don't offer here the choice because if we
+ // try it inside an ALTER TABLE, MySQL (at least in 5.1.23-maria)
+ // does not return a warning
+ // (if the table was compressed, it can be seen on the Structure page)
+
+ if (isset($possible_row_formats[$tbl_storage_engine])) {
+ $current_row_format = strtoupper($GLOBALS['showtable']['Row_format']);
+ $html_output .= '<tr><td>'
+ . '<label for="new_row_format">ROW_FORMAT</label></td>'
+ . '<td>';
+ $html_output .= PMA_Util::getDropdown(
+ 'new_row_format', $possible_row_formats[$tbl_storage_engine],
+ $current_row_format, 'new_row_format'
+ );
+ $html_output .= '</td></tr>';
+ }
+ $html_output .= '</table>'
+ . '</fieldset>';
+
+ return $html_output;
+}
+
+/**
+ * Get the common HTML table row (tr) for new_checksum, new_delay_key_write,
+ * new_transactional and new_page_checksum
+ *
+ * @param string $attribute class, name and id attribute
+ * @param string $label label value
+ * @param string $val checksum, delay_key_write, transactional, page_checksum
+ *
+ * @return string $html_output
+ */
+function PMA_getHtmlForTableRow($attribute, $label, $val)
+{
+ return '<tr>'
+ . '<td><label for="' . $attribute . '">' . $label . '</label></td>'
+ . '<td><input type="checkbox" name="'. $attribute .'"'
+ . ' id="' . $attribute .'"'
+ . ' value="1"'
+ . ((!empty($val) && $val == 1) ? ' checked="checked"' : '') . '/></td>'
+ . '</tr>';
+}
+
+/**
+ * Get array of possible row formats
+ *
+ * @return array $possible_row_formats
+ */
+function PMA_getPossibleRowFormat()
+{
+ // the outer array is for engines, the inner array contains the dropdown
+ // option values as keys then the dropdown option labels
+
+ $possible_row_formats = array(
+ 'ARIA' => array(
+ 'FIXED' => 'FIXED',
+ 'DYNAMIC' => 'DYNAMIC',
+ 'PAGE' => 'PAGE'
+ ),
+ 'MARIA' => array(
+ 'FIXED' => 'FIXED',
+ 'DYNAMIC' => 'DYNAMIC',
+ 'PAGE' => 'PAGE'
+ ),
+ 'MYISAM' => array(
+ 'FIXED' => 'FIXED',
+ 'DYNAMIC' => 'DYNAMIC'
+ ),
+ 'PBXT' => array(
+ 'FIXED' => 'FIXED',
+ 'DYNAMIC' => 'DYNAMIC'
+ ),
+ 'INNODB' => array(
+ 'COMPACT' => 'COMPACT',
+ 'REDUNDANT' => 'REDUNDANT'
+ )
+ );
+
+ $innodb_engine_plugin = PMA_StorageEngine::getEngine('innodb');
+ $innodb_plugin_version = $innodb_engine_plugin->getInnodbPluginVersion();
+ if (!empty($innodb_plugin_version)) {
+ $innodb_file_format = $innodb_engine_plugin->getInnodbFileFormat();
+ } else {
+ $innodb_file_format = '';
+ }
+ if ('Barracuda' == $innodb_file_format
+ && $innodb_engine_plugin->supportsFilePerTable()
+ ) {
+ $possible_row_formats['INNODB']['DYNAMIC'] = 'DYNAMIC';
+ $possible_row_formats['INNODB']['COMPRESSED'] = 'COMPRESSED';
+ }
+
+ return $possible_row_formats;
+}
+
+/**
+ * Get HTML div for copy table
+ *
+ * @return string $html_output
+ */
+function PMA_getHtmlForCopytable()
+{
+ $html_output = '<div class="operations_half_width">';
+ $html_output .= '<form method="post" action="tbl_operations.php" '
+ . 'name="copyTable" '
+ . 'id="copyTable" '
+ . ' class="ajax" '
+ . 'onsubmit="return emptyFormElements(this, \'new_name\')">'
+ . PMA_URL_getHiddenInputs($GLOBALS['db'], $GLOBALS['table'])
+ . '<input type="hidden" name="reload" value="1" />';
+
+ $html_output .= '<fieldset>';
+ $html_output .= '<legend>'
+ . __('Copy table to (database<b>.</b>table):') . '</legend>';
+
+ if (count($GLOBALS['pma']->databases) > $GLOBALS['cfg']['MaxDbList']) {
+ $html_output .= '<input class="halfWidth" type="text" maxlength="100" '
+ . 'size="30" name="target_db" '
+ . 'value="'. htmlspecialchars($GLOBALS['db']) . '"/>';
+ } else {
+ $html_output .= '<select class="halfWidth" name="target_db">'
+ . $GLOBALS['pma']->databases->getHtmlOptions(true, false)
+ . '</select>';
+ }
+ $html_output .= '&nbsp;<strong>.</strong>&nbsp;';
+ $html_output .= '<input class="halfWidth" type="text" required '
+ . 'size="20" name="new_name" onfocus="this.select()" '
+ . 'value="'. htmlspecialchars($GLOBALS['table']) . '"/><br />';
+
+ $choices = array(
+ 'structure' => __('Structure only'),
+ 'data' => __('Structure and data'),
+ 'dataonly' => __('Data only'));
+
+ $html_output .= PMA_Util::getRadioFields(
+ 'what', $choices, 'data', true
+ );
+
+ $html_output .= '<input type="checkbox" name="drop_if_exists" '
+ . 'value="true" id="checkbox_drop" />'
+ . '<label for="checkbox_drop">'
+ . sprintf(__('Add %s'), 'DROP TABLE') . '</label><br />'
+ . '<input type="checkbox" name="sql_auto_increment" '
+ . 'value="1" id="checkbox_auto_increment_cp" />'
+ . '<label for="checkbox_auto_increment_cp">'
+ . __('Add AUTO_INCREMENT value') . '</label><br />';
+
+ // display "Add constraints" choice only if there are
+ // foreign keys
+ if (PMA_getForeigners($GLOBALS['db'], $GLOBALS['table'], '', 'foreign')) {
+ $html_output .= '<input type="checkbox" name="add_constraints" '
+ . 'value="1" id="checkbox_constraints" />';
+ $html_output .= '<label for="checkbox_constraints">'
+ .__('Add constraints') . '</label><br />';
+ } // endif
+
+ if (isset($_COOKIE['pma_switch_to_new'])
+ && $_COOKIE['pma_switch_to_new'] == 'true'
+ ) {
+ $pma_switch_to_new = 'true';
+ }
+
+ $html_output .= '<input type="checkbox" name="switch_to_new" value="true"'
+ . 'id="checkbox_switch"'
+ . ((isset($pma_switch_to_new) && $pma_switch_to_new == 'true')
+ ? ' checked="checked"'
+ : '' . '/>');
+ $html_output .= '<label for="checkbox_switch">'
+ . __('Switch to copied table') . '</label>'
+ . '</fieldset>';
+
+ $html_output .= '<fieldset class="tblFooters">'
+ . '<input type="submit" name="submit_copy" value="' .__('Go') . '" />'
+ . '</fieldset>'
+ . '</form>'
+ . '</div>';
+
+ return $html_output;
+}
+
+/**
+ * Get HTML snippet for table maintence
+ *
+ * @param boolean $is_myisam_or_aria whether MYISAM | ARIA or not
+ * @param boolean $is_innodb whether innodb or not
+ * @param boolean $is_berkeleydb whether berkeleydb or not
+ * @param array $url_params array of URL parameters
+ *
+ * @return string $html_output
+ */
+function PMA_getHtmlForTableMaintenance(
+ $is_myisam_or_aria, $is_innodb, $is_berkeleydb, $url_params
+) {
+ $html_output = '<div class="operations_half_width">';
+ $html_output .= '<fieldset>'
+ . '<legend>' . __('Table maintenance') . '</legend>';
+ $html_output .= '<ul id="tbl_maintenance">';
+
+ // Note: BERKELEY (BDB) is no longer supported, starting with MySQL 5.1
+ $html_output .= PMA_getListofMaintainActionLink(
+ $is_myisam_or_aria, $is_innodb, $url_params, $is_berkeleydb
+ );
+
+ $html_output .= '</ul>'
+ . '</fieldset>'
+ . '</div>';
+
+ return $html_output;
+}
+
+/**
+ * Get HTML 'li' having a link of maintain action
+ *
+ * @param boolean $is_myisam_or_aria whether MYISAM | ARIA or not
+ * @param boolean $is_innodb whether innodb or not
+ * @param array $url_params array of URL parameters
+ * @param boolean $is_berkeleydb whether berkeleydb or not
+ *
+ * @return string $html_output
+ */
+function PMA_getListofMaintainActionLink($is_myisam_or_aria,
+ $is_innodb, $url_params, $is_berkeleydb
+) {
+ $html_output = '';
+
+ if ($is_myisam_or_aria || $is_innodb || $is_berkeleydb) {
+ if ($is_myisam_or_aria || $is_innodb) {
+ $params = array(
+ 'sql_query' => 'CHECK TABLE '
+ . PMA_Util::backquote($GLOBALS['table']),
+ 'table_maintenance' => 'Go',
+ );
+ $html_output .= PMA_getMaintainActionlink(
+ __('Check table'),
+ $params,
+ $url_params,
+ 'CHECK_TABLE'
+ );
+ }
+ if ($is_innodb) {
+ $params = array(
+ 'sql_query' => 'ALTER TABLE '
+ . PMA_Util::backquote($GLOBALS['table'])
+ . ' ENGINE = InnoDB;'
+ );
+ $html_output .= PMA_getMaintainActionlink(
+ __('Defragment table'),
+ $params,
+ $url_params,
+ 'InnoDB_File_Defragmenting',
+ 'Table_types'
+ );
+ }
+ if ($is_innodb || $is_myisam_or_aria || $is_berkeleydb) {
+ $params = array(
+ 'sql_query' => 'ANALYZE TABLE '
+ . PMA_Util::backquote($GLOBALS['table']),
+ 'table_maintenance' => 'Go',
+ );
+ $html_output .= PMA_getMaintainActionlink(
+ __('Analyze table'),
+ $params,
+ $url_params,
+ 'ANALYZE_TABLE'
+ );
+ }
+ if ($is_myisam_or_aria && !PMA_DRIZZLE) {
+ $params = array(
+ 'sql_query' => 'REPAIR TABLE '
+ . PMA_Util::backquote($GLOBALS['table']),
+ 'table_maintenance' => 'Go',
+ );
+ $html_output .= PMA_getMaintainActionlink(
+ __('Repair table'),
+ $params,
+ $url_params,
+ 'REPAIR_TABLE'
+ );
+ }
+ if (($is_myisam_or_aria || $is_innodb || $is_berkeleydb)
+ && !PMA_DRIZZLE
+ ) {
+ $params = array(
+ 'sql_query' => 'OPTIMIZE TABLE '
+ . PMA_Util::backquote($GLOBALS['table']),
+ 'table_maintenance' => 'Go',
+ );
+ $html_output .= PMA_getMaintainActionlink(
+ __('Optimize table'),
+ $params,
+ $url_params,
+ 'OPTIMIZE_TABLE'
+ );
+ }
+ } // end MYISAM or BERKELEYDB case
+
+ $params = array(
+ 'sql_query' => 'FLUSH TABLE '
+ . PMA_Util::backquote($GLOBALS['table']),
+ 'message_to_show' => sprintf(
+ __('Table %s has been flushed'),
+ htmlspecialchars($GLOBALS['table'])
+ ),
+ 'reload' => 1,
+ );
+
+ $html_output .= PMA_getMaintainActionlink(
+ __('Flush the table (FLUSH)'),
+ $params,
+ $url_params,
+ 'FLUSH'
+ );
+
+ return $html_output;
+}
+
+/**
+ * Get maintain action HTML link
+ *
+ * @param string $action action name
+ * @param array $params url parameters array
+ * @param array $url_params additional url parameters
+ * @param string $link contains name of page/anchor that is being linked
+ *
+ * @return string $html_output
+ */
+function PMA_getMaintainActionlink($action, $params, $url_params, $link)
+{
+ return '<li>'
+ . '<a class="maintain_action ajax" '
+ . 'href="sql.php'
+ . PMA_URL_getCommon(array_merge($url_params, $params)) .'">'
+ . $action
+ . '</a>'
+ . PMA_Util::showMySQLDocu($link)
+ . '</li>';
+}
+
+/**
+ * Get HTML for Delete data or table (truncate table, drop table)
+ *
+ * @param array $truncate_table_url_params url parameter array for truncate table
+ * @param array $drop_table_url_params url parameter array for drop table
+ *
+ * @return string $html_output
+ */
+function PMA_getHtmlForDeleteDataOrTable(
+ $truncate_table_url_params,
+ $drop_table_url_params
+) {
+ $html_output = '<div class="operations_half_width">'
+ . '<fieldset class="caution">'
+ . '<legend>' . __('Delete data or table') . '</legend>';
+
+ $html_output .= '<ul>';
+
+ if (! empty($truncate_table_url_params)) {
+ $html_output .= PMA_getDeleteDataOrTablelink(
+ $truncate_table_url_params,
+ 'TRUNCATE_TABLE',
+ __('Empty the table (TRUNCATE)'),
+ 'truncate_tbl_anchor'
+ );
+ }
+ if (!empty ($drop_table_url_params)) {
+ $html_output .= PMA_getDeleteDataOrTablelink(
+ $drop_table_url_params,
+ 'DROP_TABLE',
+ __('Delete the table (DROP)'),
+ 'drop_tbl_anchor'
+ );
+ }
+ $html_output .= '</ul></fieldset></div>';
+
+ return $html_output;
+}
+
+/**
+ * Get the HTML link for Truncate table, Drop table and Drop db
+ *
+ * @param array $url_params url parameter array for delete data or table
+ * @param string $syntax TRUNCATE_TABLE or DROP_TABLE or DROP_DATABASE
+ * @param string $link link to be shown
+ * @param string $id id of the link
+ *
+ * @return String html output
+ */
+function PMA_getDeleteDataOrTablelink($url_params, $syntax, $link, $id)
+{
+ return '<li><a '
+ . 'href="sql.php' . PMA_URL_getCommon($url_params) . '"'
+ . ' id="' . $id . '" class="ajax">'
+ . $link . '</a>'
+ . PMA_Util::showMySQLDocu($syntax)
+ . '</li>';
+}
+
+/**
+ * Get HTML snippet for partition maintenance
+ *
+ * @param array $partition_names array of partition names for a specific db/table
+ * @param array $url_params url parameters
+ *
+ * @return string $html_output
+ */
+function PMA_getHtmlForPartitionMaintenance($partition_names, $url_params)
+{
+ $choices = array(
+ 'ANALYZE' => __('Analyze'),
+ 'CHECK' => __('Check'),
+ 'OPTIMIZE' => __('Optimize'),
+ 'REBUILD' => __('Rebuild'),
+ 'REPAIR' => __('Repair')
+ );
+
+ $html_output = '<div class="operations_half_width">'
+ . '<form method="post" action="tbl_operations.php">'
+ . PMA_URL_getHiddenInputs($GLOBALS['db'], $GLOBALS['table'])
+ . '<fieldset>'
+ . '<legend>' . __('Partition maintenance') . '</legend>';
+
+ $html_select = '<select name="partition_name">' . "\n";
+ foreach ($partition_names as $one_partition) {
+ $one_partition = htmlspecialchars($one_partition);
+ $html_select .= '<option value="' . $one_partition . '">'
+ . $one_partition . '</option>' . "\n";
+ }
+ $html_select .= '</select>' . "\n";
+ $html_output .= sprintf(__('Partition %s'), $html_select);
+
+ $html_output .= PMA_Util::getRadioFields(
+ 'partition_operation', $choices, '', false
+ );
+ $html_output .= PMA_Util::showMySQLDocu('partitioning_maintenance');
+ $this_url_params = array_merge(
+ $url_params,
+ array(
+ 'sql_query' => 'ALTER TABLE '
+ . PMA_Util::backquote($GLOBALS['table'])
+ . ' REMOVE PARTITIONING;'
+ )
+ );
+ $html_output .= '<br /><a href="sql.php'
+ . PMA_URL_getCommon($this_url_params) . '">'
+ . __('Remove partitioning') . '</a>';
+
+ $html_output .= '</fieldset>'
+ . '<fieldset class="tblFooters">'
+ . '<input type="hidden" name="submit_partition" value="1">'
+ . '<input type="submit" value="' . __('Go') . '" />'
+ . '</fieldset>'
+ . '</form>'
+ . '</div>';
+
+ return $html_output;
+}
+
+/**
+ * Get the HTML for Referential Integrity check
+ *
+ * @param array $foreign all Relations to foreign tables for a given table
+ * or optionally a given column in a table
+ * @param array $url_params array of url parameters
+ *
+ * @return string $html_output
+ */
+function PMA_getHtmlForReferentialIntegrityCheck($foreign, $url_params)
+{
+ $html_output = '<div class="operations_half_width">'
+ . '<fieldset>'
+ . '<legend>' . __('Check referential integrity:') . '</legend>';
+
+ $html_output .= '<ul>';
+
+ foreach ($foreign as $master => $arr) {
+ $join_query = 'SELECT '
+ . PMA_Util::backquote($GLOBALS['table']) . '.*'
+ . ' FROM ' . PMA_Util::backquote($GLOBALS['table'])
+ . ' LEFT JOIN '
+ . PMA_Util::backquote($arr['foreign_db'])
+ . '.'
+ . PMA_Util::backquote($arr['foreign_table']);
+ if ($arr['foreign_table'] == $GLOBALS['table']) {
+ $foreign_table = $GLOBALS['table'] . '1';
+ $join_query .= ' AS ' . PMA_Util::backquote($foreign_table);
+ } else {
+ $foreign_table = $arr['foreign_table'];
+ }
+ $join_query .= ' ON '
+ . PMA_Util::backquote($GLOBALS['table']) . '.'
+ . PMA_Util::backquote($master)
+ . ' = '
+ . PMA_Util::backquote($arr['foreign_db'])
+ . '.'
+ . PMA_Util::backquote($foreign_table) . '.'
+ . PMA_Util::backquote($arr['foreign_field'])
+ . ' WHERE '
+ . PMA_Util::backquote($arr['foreign_db'])
+ . '.'
+ . PMA_Util::backquote($foreign_table) . '.'
+ . PMA_Util::backquote($arr['foreign_field'])
+ . ' IS NULL AND '
+ . PMA_Util::backquote($GLOBALS['table']) . '.'
+ . PMA_Util::backquote($master)
+ . ' IS NOT NULL';
+ $this_url_params = array_merge(
+ $url_params,
+ array('sql_query' => $join_query)
+ );
+
+ $html_output .= '<li>'
+ . '<a href="sql.php'
+ . PMA_URL_getCommon($this_url_params)
+ . '">'
+ . $master . '&nbsp;->&nbsp;' . $arr['foreign_db'] . '.'
+ . $arr['foreign_table'] . '.' . $arr['foreign_field']
+ . '</a></li>' . "\n";
+ } // foreach $foreign
+ $html_output .= '</ul></fieldset></div>';
+
+ return $html_output;
+}
+
+/**
+ * Reorder table based on request params
+ *
+ * @return array SQL query and result
+ */
+function PMA_getQueryAndResultForReorderingTable()
+{
+ $sql_query = 'ALTER TABLE '
+ . PMA_Util::backquote($GLOBALS['table'])
+ . ' ORDER BY '
+ . PMA_Util::backquote(urldecode($_REQUEST['order_field']));
+ if (isset($_REQUEST['order_order'])
+ && $_REQUEST['order_order'] === 'desc'
+ ) {
+ $sql_query .= ' DESC';
+ }
+ $sql_query .= ';';
+ $result = $GLOBALS['dbi']->query($sql_query);
+
+ return array($sql_query, $result);
+}
+
+/**
+ * Get table alters array
+ *
+ * @param boolean $is_myisam_or_aria whether MYISAM | ARIA or not
+ * @param boolean $is_isam whether ISAM or not
+ * @param string $pack_keys pack keys
+ * @param string $checksum value of checksum
+ * @param boolean $is_aria whether ARIA or not
+ * @param string $page_checksum value of page checksum
+ * @param string $delay_key_write delay key write
+ * @param boolean $is_innodb whether INNODB or not
+ * @param boolean $is_pbxt whether PBXT or not
+ * @param string $row_format row format
+ * @param string $new_tbl_storage_engine table storage engine
+ * @param string $transactional value of transactional
+ * @param string $tbl_collation collation of the table
+ *
+ * @return array $table_alters
+ */
+function PMA_getTableAltersArray($is_myisam_or_aria, $is_isam, $pack_keys,
+ $checksum, $is_aria, $page_checksum, $delay_key_write, $is_innodb,
+ $is_pbxt, $row_format, $new_tbl_storage_engine, $transactional, $tbl_collation
+) {
+ global $auto_increment;
+
+ $table_alters = array();
+
+ if (isset($_REQUEST['comment'])
+ && urldecode($_REQUEST['prev_comment']) !== $_REQUEST['comment']
+ ) {
+ $table_alters[] = 'COMMENT = \''
+ . PMA_Util::sqlAddSlashes($_REQUEST['comment']) . '\'';
+ }
+ if (! empty($new_tbl_storage_engine)
+ && strtolower($new_tbl_storage_engine) !== strtolower($GLOBALS['tbl_storage_engine'])
+ ) {
+ $table_alters[] = 'ENGINE = ' . $new_tbl_storage_engine;
+ }
+ if (! empty($_REQUEST['tbl_collation'])
+ && $_REQUEST['tbl_collation'] !== $tbl_collation
+ ) {
+ $table_alters[] = 'DEFAULT '
+ . PMA_generateCharsetQueryPart($_REQUEST['tbl_collation']);
+ }
+
+ if (($is_myisam_or_aria || $is_isam)
+ && isset($_REQUEST['new_pack_keys'])
+ && $_REQUEST['new_pack_keys'] != (string)$pack_keys
+ ) {
+ $table_alters[] = 'pack_keys = ' . $_REQUEST['new_pack_keys'];
+ }
+
+ $_REQUEST['new_checksum'] = empty($_REQUEST['new_checksum']) ? '0' : '1';
+ if ($is_myisam_or_aria
+ && $_REQUEST['new_checksum'] !== $checksum
+ ) {
+ $table_alters[] = 'checksum = ' . $_REQUEST['new_checksum'];
+ }
+
+ $_REQUEST['new_transactional']
+ = empty($_REQUEST['new_transactional']) ? '0' : '1';
+ if ($is_aria
+ && $_REQUEST['new_transactional'] !== $transactional
+ ) {
+ $table_alters[] = 'TRANSACTIONAL = ' . $_REQUEST['new_transactional'];
+ }
+
+ $_REQUEST['new_page_checksum']
+ = empty($_REQUEST['new_page_checksum']) ? '0' : '1';
+ if ($is_aria
+ && $_REQUEST['new_page_checksum'] !== $page_checksum
+ ) {
+ $table_alters[] = 'PAGE_CHECKSUM = ' . $_REQUEST['new_page_checksum'];
+ }
+
+ $_REQUEST['new_delay_key_write']
+ = empty($_REQUEST['new_delay_key_write']) ? '0' : '1';
+ if ($is_myisam_or_aria
+ && $_REQUEST['new_delay_key_write'] !== $delay_key_write
+ ) {
+ $table_alters[] = 'delay_key_write = ' . $_REQUEST['new_delay_key_write'];
+ }
+
+ if (($is_myisam_or_aria || $is_innodb || $is_pbxt)
+ && ! empty($_REQUEST['new_auto_increment'])
+ && (! isset($auto_increment)
+ || $_REQUEST['new_auto_increment'] !== $auto_increment)
+ ) {
+ $table_alters[] = 'auto_increment = '
+ . PMA_Util::sqlAddSlashes($_REQUEST['new_auto_increment']);
+ }
+
+ if (($is_myisam_or_aria || $is_innodb || $is_pbxt)
+ && ! empty($_REQUEST['new_row_format'])
+ && (!strlen($row_format)
+ || strtolower($_REQUEST['new_row_format']) !== strtolower($row_format))
+ ) {
+ $table_alters[] = 'ROW_FORMAT = '
+ . PMA_Util::sqlAddSlashes($_REQUEST['new_row_format']);
+ }
+
+ return $table_alters;
+}
+
+/**
+ * set initial value of the set of variables, based on the current table engine
+ *
+ * @param string $tbl_storage_engine table storage engine
+ *
+ * @return array ($is_myisam_or_aria, $is_innodb, $is_isam,
+ * $is_berkeleydb, $is_aria, $is_pbxt)
+ */
+function PMA_setGlobalVariablesForEngine($tbl_storage_engine)
+{
+ $is_myisam_or_aria = $is_isam = $is_innodb = $is_berkeleydb
+ = $is_aria = $is_pbxt = false;
+ $upper_tbl_storage_engine = strtoupper($tbl_storage_engine);
+
+ //Options that apply to MYISAM usually apply to ARIA
+ $is_myisam_or_aria = ($upper_tbl_storage_engine == 'MYISAM'
+ || $upper_tbl_storage_engine == 'ARIA'
+ || $upper_tbl_storage_engine == 'MARIA'
+ );
+ $is_aria = ($upper_tbl_storage_engine == 'ARIA');
+
+ $is_isam = ($upper_tbl_storage_engine == 'ISAM');
+ $is_innodb = ($upper_tbl_storage_engine == 'INNODB');
+ $is_berkeleydb = ($upper_tbl_storage_engine == 'BERKELEYDB');
+ $is_pbxt = ($upper_tbl_storage_engine == 'PBXT');
+
+ return array(
+ $is_myisam_or_aria, $is_innodb, $is_isam,
+ $is_berkeleydb, $is_aria, $is_pbxt
+ );
+}
+
+/**
+ * Get warning messages array
+ *
+ * @return array $warning_messages
+ */
+function PMA_getWarningMessagesArray()
+{
+ $warning_messages = array();
+ foreach ($GLOBALS['dbi']->getWarnings() as $warning) {
+ // In MariaDB 5.1.44, when altering a table from Maria to MyISAM
+ // and if TRANSACTIONAL was set, the system reports an error;
+ // I discussed with a Maria developer and he agrees that this
+ // should not be reported with a Level of Error, so here
+ // I just ignore it. But there are other 1478 messages
+ // that it's better to show.
+ if (! ($_REQUEST['new_tbl_storage_engine'] == 'MyISAM'
+ && $warning['Code'] == '1478'
+ && $warning['Level'] == 'Error')
+ ) {
+ $warning_messages[] = $warning['Level'] . ': #' . $warning['Code']
+ . ' ' . $warning['Message'];
+ }
+ }
+ return $warning_messages;
+}
+
+/**
+ * Get SQL query and result after ran this SQL query for a partition operation
+ * has been requested by the user
+ *
+ * @return array $sql_query, $result
+ */
+function PMA_getQueryAndResultForPartition()
+{
+ $sql_query = 'ALTER TABLE '
+ . PMA_Util::backquote($GLOBALS['table']) . ' '
+ . $_REQUEST['partition_operation']
+ . ' PARTITION '
+ . $_REQUEST['partition_name'] . ';';
+ $result = $GLOBALS['dbi']->query($sql_query);
+
+ return array($sql_query, $result);
+}
+
+?>
diff --git a/libraries/parse_analyze.inc.php b/libraries/parse_analyze.inc.php
new file mode 100644
index 0000000000..eb7704efcb
--- /dev/null
+++ b/libraries/parse_analyze.inc.php
@@ -0,0 +1,144 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Parse and analyse a SQL query
+ *
+ * @package PhpMyAdmin
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ *
+ */
+$GLOBALS['unparsed_sql'] = $sql_query;
+$parsed_sql = PMA_SQP_parse($sql_query);
+$analyzed_sql = PMA_SQP_analyze($parsed_sql);
+
+// for bug 780516: now that we use case insensitive preg_match
+// or flags from the analyser, do not put back the reformatted query
+// into $sql_query, to make this kind of query work without
+// capitalizing keywords:
+//
+// CREATE TABLE SG_Persons (
+// id int(10) unsigned NOT NULL auto_increment,
+// first varchar(64) NOT NULL default '',
+// PRIMARY KEY (`id`)
+// )
+
+// Fills some variables from the analysed SQL
+// A table has to be created, renamed, dropped:
+// the navigation panel should be reloaded
+$reload = isset($analyzed_sql[0]['queryflags']['reload']);
+
+// check for drop database
+$drop_database = isset($analyzed_sql[0]['queryflags']['drop_database']);
+
+// for the presence of EXPLAIN
+$is_explain = isset($analyzed_sql[0]['queryflags']['is_explain']);
+
+// for the presence of DELETE
+$is_delete = isset($analyzed_sql[0]['queryflags']['is_delete']);
+
+// for the presence of UPDATE, DELETE or INSERT|LOAD DATA|REPLACE
+$is_affected = isset($analyzed_sql[0]['queryflags']['is_affected']);
+
+// for the presence of REPLACE
+$is_replace = isset($analyzed_sql[0]['queryflags']['is_replace']);
+
+// for the presence of INSERT
+$is_insert = isset($analyzed_sql[0]['queryflags']['is_insert']);
+
+// for the presence of CHECK|ANALYZE|REPAIR|OPTIMIZE TABLE
+$is_maint = isset($analyzed_sql[0]['queryflags']['is_maint']);
+
+// for the presence of SHOW
+$is_show = isset($analyzed_sql[0]['queryflags']['is_show']);
+
+// for the presence of PROCEDURE ANALYSE
+$is_analyse = isset($analyzed_sql[0]['queryflags']['is_analyse']);
+
+// for the presence of INTO OUTFILE
+$is_export = isset($analyzed_sql[0]['queryflags']['is_export']);
+
+// for the presence of GROUP BY|HAVING|SELECT DISTINCT
+$is_group = isset($analyzed_sql[0]['queryflags']['is_group']);
+
+// for the presence of SUM|AVG|STD|STDDEV|MIN|MAX|BIT_OR|BIT_AND
+$is_func = isset($analyzed_sql[0]['queryflags']['is_func']);
+
+// for the presence of SELECT COUNT
+$is_count = isset($analyzed_sql[0]['queryflags']['is_count']);
+
+// check for a real SELECT ... FROM
+$is_select = isset($analyzed_sql[0]['queryflags']['select_from']);
+
+// the query contains a subquery
+$is_subquery = isset($analyzed_sql[0]['queryflags']['is_subquery']);
+
+// check for CALL
+// Since multiple query execution is anyway handled,
+// ignore the WHERE clause of the first sql statement
+// which might contain a phrase like 'call '
+if (isset($analyzed_sql[0]['queryflags']['is_procedure'])
+ && empty($analyzed_sql[0]['where_clause'])
+) {
+ $is_procedure = true;
+} else {
+ $is_procedure = false;
+}
+
+// aggregates all the results into one array
+$analyzed_sql_results = array(
+ "parsed_sql" => $parsed_sql,
+ "analyzed_sql" => $analyzed_sql,
+ "reload" => $reload,
+ "drop_database" => $drop_database,
+ "is_explain" => $is_explain,
+ "is_delete" => $is_delete,
+ "is_affected" => $is_affected,
+ "is_replace" => $is_replace,
+ "is_insert" => $is_insert,
+ "is_maint" => $is_maint,
+ "is_show" => $is_show,
+ "is_analyse" => $is_analyse,
+ "is_export" => $is_export,
+ "is_group" => $is_group,
+ "is_func" => $is_func,
+ "is_count" => $is_count,
+ "is_select" => $is_select,
+ "is_procedure" => $is_procedure,
+ "is_subquery" => $is_subquery
+);
+
+
+// If the query is a Select, extract the db and table names and modify
+// $db and $table, to have correct page headers, links and left frame.
+// db and table name may be enclosed with backquotes, db is optionnal,
+// query may contain aliases.
+
+/**
+ * @todo if there are more than one table name in the Select:
+ * - do not extract the first table name
+ * - do not show a table name in the page header
+ * - do not display the sub-pages links)
+ */
+if ($is_select) {
+ $prev_db = $db;
+ if (isset($analyzed_sql[0]['table_ref'][0]['table_true_name'])) {
+ $table = $analyzed_sql[0]['table_ref'][0]['table_true_name'];
+ }
+ if (isset($analyzed_sql[0]['table_ref'][0]['db'])
+ && strlen($analyzed_sql[0]['table_ref'][0]['db'])
+ ) {
+ $db = $analyzed_sql[0]['table_ref'][0]['db'];
+ } else {
+ $db = $prev_db;
+ }
+ // Don't change reload, if we already decided to reload in import
+ if (empty($reload) && empty($GLOBALS['is_ajax_request'])) {
+ $reload = ($db == $prev_db) ? 0 : 1;
+ }
+}
+?>
diff --git a/libraries/php-gettext/gettext.inc b/libraries/php-gettext/gettext.inc
new file mode 100644
index 0000000000..00b966692c
--- /dev/null
+++ b/libraries/php-gettext/gettext.inc
@@ -0,0 +1,536 @@
+<?php
+/*
+ Copyright (c) 2005 Steven Armstrong <sa at c-area dot ch>
+ Copyright (c) 2009 Danilo Segan <danilo@kvota.net>
+
+ Drop in replacement for native gettext.
+
+ This file is part of PHP-gettext.
+
+ PHP-gettext 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.
+
+ PHP-gettext 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 PHP-gettext; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+*/
+/*
+LC_CTYPE 0
+LC_NUMERIC 1
+LC_TIME 2
+LC_COLLATE 3
+LC_MONETARY 4
+LC_MESSAGES 5
+LC_ALL 6
+*/
+
+// LC_MESSAGES is not available if php-gettext is not loaded
+// while the other constants are already available from session extension.
+if (!defined('LC_MESSAGES')) {
+ define('LC_MESSAGES', 5);
+}
+
+require('streams.php');
+require('gettext.php');
+
+
+// Variables
+
+global $text_domains, $default_domain, $LC_CATEGORIES, $EMULATEGETTEXT, $CURRENTLOCALE;
+$text_domains = array();
+$default_domain = 'messages';
+$LC_CATEGORIES = array('LC_CTYPE', 'LC_NUMERIC', 'LC_TIME', 'LC_COLLATE', 'LC_MONETARY', 'LC_MESSAGES', 'LC_ALL');
+$EMULATEGETTEXT = 0;
+$CURRENTLOCALE = '';
+
+/* Class to hold a single domain included in $text_domains. */
+class domain {
+ var $l10n;
+ var $path;
+ var $codeset;
+}
+
+// Utility functions
+
+/**
+ * Return a list of locales to try for any POSIX-style locale specification.
+ */
+function get_list_of_locales($locale) {
+ /* Figure out all possible locale names and start with the most
+ * specific ones. I.e. for sr_CS.UTF-8@latin, look through all of
+ * sr_CS.UTF-8@latin, sr_CS@latin, sr@latin, sr_CS.UTF-8, sr_CS, sr.
+ */
+ $locale_names = array();
+ $lang = NULL;
+ $country = NULL;
+ $charset = NULL;
+ $modifier = NULL;
+ if ($locale) {
+ if (preg_match("/^(?P<lang>[a-z]{2,3})" // language code
+ ."(?:_(?P<country>[A-Z]{2}))?" // country code
+ ."(?:\.(?P<charset>[-A-Za-z0-9_]+))?" // charset
+ ."(?:@(?P<modifier>[-A-Za-z0-9_]+))?$/", // @ modifier
+ $locale, $matches)) {
+
+ if (isset($matches["lang"])) $lang = $matches["lang"];
+ if (isset($matches["country"])) $country = $matches["country"];
+ if (isset($matches["charset"])) $charset = $matches["charset"];
+ if (isset($matches["modifier"])) $modifier = $matches["modifier"];
+
+ if ($modifier) {
+ if ($country) {
+ if ($charset)
+ array_push($locale_names, "${lang}_$country.$charset@$modifier");
+ array_push($locale_names, "${lang}_$country@$modifier");
+ } elseif ($charset)
+ array_push($locale_names, "${lang}.$charset@$modifier");
+ array_push($locale_names, "$lang@$modifier");
+ }
+ if ($country) {
+ if ($charset)
+ array_push($locale_names, "${lang}_$country.$charset");
+ array_push($locale_names, "${lang}_$country");
+ } elseif ($charset)
+ array_push($locale_names, "${lang}.$charset");
+ array_push($locale_names, $lang);
+ }
+
+ // If the locale name doesn't match POSIX style, just include it as-is.
+ if (!in_array($locale, $locale_names))
+ array_push($locale_names, $locale);
+ }
+ return $locale_names;
+}
+
+/**
+ * Utility function to get a StreamReader for the given text domain.
+ */
+function _get_reader($domain=null, $category=5, $enable_cache=true) {
+ global $text_domains, $default_domain, $LC_CATEGORIES;
+ if (!isset($domain)) $domain = $default_domain;
+ if (!isset($text_domains[$domain]->l10n)) {
+ // get the current locale
+ $locale = _setlocale(LC_MESSAGES, 0);
+ $bound_path = isset($text_domains[$domain]->path) ?
+ $text_domains[$domain]->path : './';
+ $subpath = $LC_CATEGORIES[$category] ."/$domain.mo";
+
+ $locale_names = get_list_of_locales($locale);
+ $input = null;
+ foreach ($locale_names as $locale) {
+ $full_path = $bound_path . $locale . "/" . $subpath;
+ if (file_exists($full_path)) {
+ $input = new FileReader($full_path);
+ break;
+ }
+ }
+
+ if (!array_key_exists($domain, $text_domains)) {
+ // Initialize an empty domain object.
+ $text_domains[$domain] = new domain();
+ }
+ $text_domains[$domain]->l10n = new gettext_reader($input,
+ $enable_cache);
+ }
+ return $text_domains[$domain]->l10n;
+}
+
+/**
+ * Returns whether we are using our emulated gettext API or PHP built-in one.
+ */
+function locale_emulation() {
+ global $EMULATEGETTEXT;
+ return $EMULATEGETTEXT;
+}
+
+/**
+ * Checks if the current locale is supported on this system.
+ */
+function _check_locale_and_function($function=false) {
+ global $EMULATEGETTEXT;
+ if ($function and !function_exists($function))
+ return false;
+ return !$EMULATEGETTEXT;
+}
+
+/**
+ * Get the codeset for the given domain.
+ */
+function _get_codeset($domain=null) {
+ global $text_domains, $default_domain, $LC_CATEGORIES;
+ if (!isset($domain)) $domain = $default_domain;
+ return (isset($text_domains[$domain]->codeset))? $text_domains[$domain]->codeset : ini_get('mbstring.internal_encoding');
+}
+
+/**
+ * Convert the given string to the encoding set by bind_textdomain_codeset.
+ */
+function _encode($text) {
+ $source_encoding = mb_detect_encoding($text);
+ $target_encoding = _get_codeset();
+ if ($source_encoding != $target_encoding) {
+ return mb_convert_encoding($text, $target_encoding, $source_encoding);
+ }
+ else {
+ return $text;
+ }
+}
+
+
+// Custom implementation of the standard gettext related functions
+
+/**
+ * Returns passed in $locale, or environment variable $LANG if $locale == ''.
+ */
+function _get_default_locale($locale) {
+ if ($locale == '') // emulate variable support
+ return getenv('LANG');
+ else
+ return $locale;
+}
+
+/**
+ * Sets a requested locale, if needed emulates it.
+ */
+function _setlocale($category, $locale) {
+ global $CURRENTLOCALE, $EMULATEGETTEXT;
+ if ($locale === 0) { // use === to differentiate between string "0"
+ if ($CURRENTLOCALE != '')
+ return $CURRENTLOCALE;
+ else
+ // obey LANG variable, maybe extend to support all of LC_* vars
+ // even if we tried to read locale without setting it first
+ return _setlocale($category, $CURRENTLOCALE);
+ } else {
+ if (function_exists('setlocale')) {
+ $ret = setlocale($category, $locale);
+ if (($locale == '' and !$ret) or // failed setting it by env
+ ($locale != '' and $ret != $locale)) { // failed setting it
+ // Failed setting it according to environment.
+ $CURRENTLOCALE = _get_default_locale($locale);
+ $EMULATEGETTEXT = 1;
+ } else {
+ $CURRENTLOCALE = $ret;
+ $EMULATEGETTEXT = 0;
+ }
+ } else {
+ // No function setlocale(), emulate it all.
+ $CURRENTLOCALE = _get_default_locale($locale);
+ $EMULATEGETTEXT = 1;
+ }
+ // Allow locale to be changed on the go for one translation domain.
+ global $text_domains, $default_domain;
+ if (array_key_exists($default_domain, $text_domains)) {
+ unset($text_domains[$default_domain]->l10n);
+ }
+ return $CURRENTLOCALE;
+ }
+}
+
+/**
+ * Sets the path for a domain.
+ */
+function _bindtextdomain($domain, $path) {
+ global $text_domains;
+ // ensure $path ends with a slash ('/' should work for both, but lets still play nice)
+ if (substr(php_uname(), 0, 7) == "Windows") {
+ if ($path[strlen($path)-1] != '\\' and $path[strlen($path)-1] != '/')
+ $path .= '\\';
+ } else {
+ if ($path[strlen($path)-1] != '/')
+ $path .= '/';
+ }
+ if (!array_key_exists($domain, $text_domains)) {
+ // Initialize an empty domain object.
+ $text_domains[$domain] = new domain();
+ }
+ $text_domains[$domain]->path = $path;
+}
+
+/**
+ * Specify the character encoding in which the messages from the DOMAIN message catalog will be returned.
+ */
+function _bind_textdomain_codeset($domain, $codeset) {
+ global $text_domains;
+ $text_domains[$domain]->codeset = $codeset;
+}
+
+/**
+ * Sets the default domain.
+ */
+function _textdomain($domain) {
+ global $default_domain;
+ $default_domain = $domain;
+}
+
+/**
+ * Lookup a message in the current domain.
+ */
+function _gettext($msgid) {
+ $l10n = _get_reader();
+ return _encode($l10n->translate($msgid));
+}
+
+/**
+ * Alias for gettext.
+ */
+function __($msgid) {
+ return _gettext($msgid);
+}
+
+/**
+ * Plural version of gettext.
+ */
+function _ngettext($singular, $plural, $number) {
+ $l10n = _get_reader();
+ return _encode($l10n->ngettext($singular, $plural, $number));
+}
+
+/**
+ * Override the current domain.
+ */
+function _dgettext($domain, $msgid) {
+ $l10n = _get_reader($domain);
+ return _encode($l10n->translate($msgid));
+}
+
+/**
+ * Plural version of dgettext.
+ */
+function _dngettext($domain, $singular, $plural, $number) {
+ $l10n = _get_reader($domain);
+ return _encode($l10n->ngettext($singular, $plural, $number));
+}
+
+/**
+ * Overrides the domain and category for a single lookup.
+ */
+function _dcgettext($domain, $msgid, $category) {
+ $l10n = _get_reader($domain, $category);
+ return _encode($l10n->translate($msgid));
+}
+/**
+ * Plural version of dcgettext.
+ */
+function _dcngettext($domain, $singular, $plural, $number, $category) {
+ $l10n = _get_reader($domain, $category);
+ return _encode($l10n->ngettext($singular, $plural, $number));
+}
+
+/**
+ * Context version of gettext.
+ */
+function _pgettext($context, $msgid) {
+ $l10n = _get_reader();
+ return _encode($l10n->pgettext($context, $msgid));
+}
+
+/**
+ * Override the current domain in a context gettext call.
+ */
+function _dpgettext($domain, $context, $msgid) {
+ $l10n = _get_reader($domain);
+ return _encode($l10n->pgettext($context, $msgid));
+}
+
+/**
+ * Overrides the domain and category for a single context-based lookup.
+ */
+function _dcpgettext($domain, $context, $msgid, $category) {
+ $l10n = _get_reader($domain, $category);
+ return _encode($l10n->pgettext($context, $msgid));
+}
+
+/**
+ * Context version of ngettext.
+ */
+function _npgettext($context, $singular, $plural) {
+ $l10n = _get_reader();
+ return _encode($l10n->npgettext($context, $singular, $plural));
+}
+
+/**
+ * Override the current domain in a context ngettext call.
+ */
+function _dnpgettext($domain, $context, $singular, $plural) {
+ $l10n = _get_reader($domain);
+ return _encode($l10n->npgettext($context, $singular, $plural));
+}
+
+/**
+ * Overrides the domain and category for a plural context-based lookup.
+ */
+function _dcnpgettext($domain, $context, $singular, $plural, $category) {
+ $l10n = _get_reader($domain, $category);
+ return _encode($l10n->npgettext($context, $singular, $plural));
+}
+
+
+
+// Wrappers to use if the standard gettext functions are available,
+// but the current locale is not supported by the system.
+// Use the standard impl if the current locale is supported, use the
+// custom impl otherwise.
+
+function T_setlocale($category, $locale) {
+ return _setlocale($category, $locale);
+}
+
+function T_bindtextdomain($domain, $path) {
+ if (_check_locale_and_function()) return bindtextdomain($domain, $path);
+ else return _bindtextdomain($domain, $path);
+}
+function T_bind_textdomain_codeset($domain, $codeset) {
+ // bind_textdomain_codeset is available only in PHP 4.2.0+
+ if (_check_locale_and_function('bind_textdomain_codeset'))
+ return bind_textdomain_codeset($domain, $codeset);
+ else return _bind_textdomain_codeset($domain, $codeset);
+}
+function T_textdomain($domain) {
+ if (_check_locale_and_function()) return textdomain($domain);
+ else return _textdomain($domain);
+}
+function T_gettext($msgid) {
+ if (_check_locale_and_function()) return gettext($msgid);
+ else return _gettext($msgid);
+}
+function T_($msgid) {
+ if (_check_locale_and_function()) return _($msgid);
+ return __($msgid);
+}
+function T_ngettext($singular, $plural, $number) {
+ if (_check_locale_and_function())
+ return ngettext($singular, $plural, $number);
+ else return _ngettext($singular, $plural, $number);
+}
+function T_dgettext($domain, $msgid) {
+ if (_check_locale_and_function()) return dgettext($domain, $msgid);
+ else return _dgettext($domain, $msgid);
+}
+function T_dngettext($domain, $singular, $plural, $number) {
+ if (_check_locale_and_function())
+ return dngettext($domain, $singular, $plural, $number);
+ else return _dngettext($domain, $singular, $plural, $number);
+}
+function T_dcgettext($domain, $msgid, $category) {
+ if (_check_locale_and_function())
+ return dcgettext($domain, $msgid, $category);
+ else return _dcgettext($domain, $msgid, $category);
+}
+function T_dcngettext($domain, $singular, $plural, $number, $category) {
+ if (_check_locale_and_function())
+ return dcngettext($domain, $singular, $plural, $number, $category);
+ else return _dcngettext($domain, $singular, $plural, $number, $category);
+}
+
+function T_pgettext($context, $msgid) {
+ if (_check_locale_and_function('pgettext'))
+ return pgettext($context, $msgid);
+ else
+ return _pgettext($context, $msgid);
+}
+
+function T_dpgettext($domain, $context, $msgid) {
+ if (_check_locale_and_function('dpgettext'))
+ return dpgettext($domain, $context, $msgid);
+ else
+ return _dpgettext($domain, $context, $msgid);
+}
+
+function T_dcpgettext($domain, $context, $msgid, $category) {
+ if (_check_locale_and_function('dcpgettext'))
+ return dcpgettext($domain, $context, $msgid, $category);
+ else
+ return _dcpgettext($domain, $context, $msgid, $category);
+}
+
+function T_npgettext($context, $singular, $plural, $number) {
+ if (_check_locale_and_function('npgettext'))
+ return npgettext($context, $singular, $plural, $number);
+ else
+ return _npgettext($context, $singular, $plural, $number);
+}
+
+function T_dnpgettext($domain, $context, $singular, $plural, $number) {
+ if (_check_locale_and_function('dnpgettext'))
+ return dnpgettext($domain, $context, $singular, $plural, $number);
+ else
+ return _dnpgettext($domain, $context, $singular, $plural, $number);
+}
+
+function T_dcnpgettext($domain, $context, $singular, $plural,
+ $number, $category) {
+ if (_check_locale_and_function('dcnpgettext'))
+ return dcnpgettext($domain, $context, $singular,
+ $plural, $number, $category);
+ else
+ return _dcnpgettext($domain, $context, $singular,
+ $plural, $number, $category);
+}
+
+
+
+// Wrappers used as a drop in replacement for the standard gettext functions
+
+if (!function_exists('gettext')) {
+ function bindtextdomain($domain, $path) {
+ return _bindtextdomain($domain, $path);
+ }
+ function bind_textdomain_codeset($domain, $codeset) {
+ return _bind_textdomain_codeset($domain, $codeset);
+ }
+ function textdomain($domain) {
+ return _textdomain($domain);
+ }
+ function gettext($msgid) {
+ return _gettext($msgid);
+ }
+ function _($msgid) {
+ return __($msgid);
+ }
+ function ngettext($singular, $plural, $number) {
+ return _ngettext($singular, $plural, $number);
+ }
+ function dgettext($domain, $msgid) {
+ return _dgettext($domain, $msgid);
+ }
+ function dngettext($domain, $singular, $plural, $number) {
+ return _dngettext($domain, $singular, $plural, $number);
+ }
+ function dcgettext($domain, $msgid, $category) {
+ return _dcgettext($domain, $msgid, $category);
+ }
+ function dcngettext($domain, $singular, $plural, $number, $category) {
+ return _dcngettext($domain, $singular, $plural, $number, $category);
+ }
+ function pgettext($context, $msgid) {
+ return _pgettext($context, $msgid);
+ }
+ function npgettext($context, $singular, $plural, $number) {
+ return _npgettext($context, $singular, $plural, $number);
+ }
+ function dpgettext($domain, $context, $msgid) {
+ return _dpgettext($domain, $context, $msgid);
+ }
+ function dnpgettext($domain, $context, $singular, $plural, $number) {
+ return _dnpgettext($domain, $context, $singular, $plural, $number);
+ }
+ function dcpgettext($domain, $context, $msgid, $category) {
+ return _dcpgettext($domain, $context, $msgid, $category);
+ }
+ function dcnpgettext($domain, $context, $singular, $plural,
+ $number, $category) {
+ return _dcnpgettext($domain, $context, $singular, $plural,
+ $number, $category);
+ }
+}
+
+?>
diff --git a/libraries/php-gettext/gettext.php b/libraries/php-gettext/gettext.php
new file mode 100644
index 0000000000..5064047cbd
--- /dev/null
+++ b/libraries/php-gettext/gettext.php
@@ -0,0 +1,432 @@
+<?php
+/*
+ Copyright (c) 2003, 2009 Danilo Segan <danilo@kvota.net>.
+ Copyright (c) 2005 Nico Kaiser <nico@siriux.net>
+
+ This file is part of PHP-gettext.
+
+ PHP-gettext 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.
+
+ PHP-gettext 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 PHP-gettext; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+*/
+
+/**
+ * Provides a simple gettext replacement that works independently from
+ * the system's gettext abilities.
+ * It can read MO files and use them for translating strings.
+ * The files are passed to gettext_reader as a Stream (see streams.php)
+ *
+ * This version has the ability to cache all strings and translations to
+ * speed up the string lookup.
+ * While the cache is enabled by default, it can be switched off with the
+ * second parameter in the constructor (e.g. whenusing very large MO files
+ * that you don't want to keep in memory)
+ */
+class gettext_reader {
+ //public:
+ var $error = 0; // public variable that holds error code (0 if no error)
+
+ //private:
+ var $BYTEORDER = 0; // 0: low endian, 1: big endian
+ var $STREAM = NULL;
+ var $short_circuit = false;
+ var $enable_cache = false;
+ var $originals = NULL; // offset of original table
+ var $translations = NULL; // offset of translation table
+ var $pluralheader = NULL; // cache header field for plural forms
+ var $total = 0; // total string count
+ var $table_originals = NULL; // table for original strings (offsets)
+ var $table_translations = NULL; // table for translated strings (offsets)
+ var $cache_translations = NULL; // original -> translation mapping
+
+
+ /* Methods */
+
+
+ /**
+ * Reads a 32bit Integer from the Stream
+ *
+ * @access private
+ * @return Integer from the Stream
+ */
+ function readint() {
+ if ($this->BYTEORDER == 0) {
+ // low endian
+ $input=unpack('V', $this->STREAM->read(4));
+ return array_shift($input);
+ } else {
+ // big endian
+ $input=unpack('N', $this->STREAM->read(4));
+ return array_shift($input);
+ }
+ }
+
+ function read($bytes) {
+ return $this->STREAM->read($bytes);
+ }
+
+ /**
+ * Reads an array of Integers from the Stream
+ *
+ * @param int count How many elements should be read
+ * @return Array of Integers
+ */
+ function readintarray($count) {
+ if ($this->BYTEORDER == 0) {
+ // low endian
+ return unpack('V'.$count, $this->STREAM->read(4 * $count));
+ } else {
+ // big endian
+ return unpack('N'.$count, $this->STREAM->read(4 * $count));
+ }
+ }
+
+ /**
+ * Constructor
+ *
+ * @param object Reader the StreamReader object
+ * @param boolean enable_cache Enable or disable caching of strings (default on)
+ */
+ function gettext_reader($Reader, $enable_cache = true) {
+ // If there isn't a StreamReader, turn on short circuit mode.
+ if (! $Reader || isset($Reader->error) ) {
+ $this->short_circuit = true;
+ return;
+ }
+
+ // Caching can be turned off
+ $this->enable_cache = $enable_cache;
+
+ $MAGIC1 = "\x95\x04\x12\xde";
+ $MAGIC2 = "\xde\x12\x04\x95";
+
+ $this->STREAM = $Reader;
+ $magic = $this->read(4);
+ if ($magic == $MAGIC1) {
+ $this->BYTEORDER = 1;
+ } elseif ($magic == $MAGIC2) {
+ $this->BYTEORDER = 0;
+ } else {
+ $this->error = 1; // not MO file
+ return false;
+ }
+
+ // FIXME: Do we care about revision? We should.
+ $revision = $this->readint();
+
+ $this->total = $this->readint();
+ $this->originals = $this->readint();
+ $this->translations = $this->readint();
+ }
+
+ /**
+ * Loads the translation tables from the MO file into the cache
+ * If caching is enabled, also loads all strings into a cache
+ * to speed up translation lookups
+ *
+ * @access private
+ */
+ function load_tables() {
+ if (is_array($this->cache_translations) &&
+ is_array($this->table_originals) &&
+ is_array($this->table_translations))
+ return;
+
+ /* get original and translations tables */
+ if (!is_array($this->table_originals)) {
+ $this->STREAM->seekto($this->originals);
+ $this->table_originals = $this->readintarray($this->total * 2);
+ }
+ if (!is_array($this->table_translations)) {
+ $this->STREAM->seekto($this->translations);
+ $this->table_translations = $this->readintarray($this->total * 2);
+ }
+
+ if ($this->enable_cache) {
+ $this->cache_translations = array ();
+ /* read all strings in the cache */
+ for ($i = 0; $i < $this->total; $i++) {
+ $this->STREAM->seekto($this->table_originals[$i * 2 + 2]);
+ $original = $this->STREAM->read($this->table_originals[$i * 2 + 1]);
+ $this->STREAM->seekto($this->table_translations[$i * 2 + 2]);
+ $translation = $this->STREAM->read($this->table_translations[$i * 2 + 1]);
+ $this->cache_translations[$original] = $translation;
+ }
+ }
+ }
+
+ /**
+ * Returns a string from the "originals" table
+ *
+ * @access private
+ * @param int num Offset number of original string
+ * @return string Requested string if found, otherwise ''
+ */
+ function get_original_string($num) {
+ $length = $this->table_originals[$num * 2 + 1];
+ $offset = $this->table_originals[$num * 2 + 2];
+ if (! $length)
+ return '';
+ $this->STREAM->seekto($offset);
+ $data = $this->STREAM->read($length);
+ return (string)$data;
+ }
+
+ /**
+ * Returns a string from the "translations" table
+ *
+ * @access private
+ * @param int num Offset number of original string
+ * @return string Requested string if found, otherwise ''
+ */
+ function get_translation_string($num) {
+ $length = $this->table_translations[$num * 2 + 1];
+ $offset = $this->table_translations[$num * 2 + 2];
+ if (! $length)
+ return '';
+ $this->STREAM->seekto($offset);
+ $data = $this->STREAM->read($length);
+ return (string)$data;
+ }
+
+ /**
+ * Binary search for string
+ *
+ * @access private
+ * @param string string
+ * @param int start (internally used in recursive function)
+ * @param int end (internally used in recursive function)
+ * @return int string number (offset in originals table)
+ */
+ function find_string($string, $start = -1, $end = -1) {
+ if (($start == -1) or ($end == -1)) {
+ // find_string is called with only one parameter, set start end end
+ $start = 0;
+ $end = $this->total;
+ }
+ if (abs($start - $end) <= 1) {
+ // We're done, now we either found the string, or it doesn't exist
+ $txt = $this->get_original_string($start);
+ if ($string == $txt)
+ return $start;
+ else
+ return -1;
+ } else if ($start > $end) {
+ // start > end -> turn around and start over
+ return $this->find_string($string, $end, $start);
+ } else {
+ // Divide table in two parts
+ $half = (int)(($start + $end) / 2);
+ $cmp = strcmp($string, $this->get_original_string($half));
+ if ($cmp == 0)
+ // string is exactly in the middle => return it
+ return $half;
+ else if ($cmp < 0)
+ // The string is in the upper half
+ return $this->find_string($string, $start, $half);
+ else
+ // The string is in the lower half
+ return $this->find_string($string, $half, $end);
+ }
+ }
+
+ /**
+ * Translates a string
+ *
+ * @access public
+ * @param string string to be translated
+ * @return string translated string (or original, if not found)
+ */
+ function translate($string) {
+ if ($this->short_circuit)
+ return $string;
+ $this->load_tables();
+
+ if ($this->enable_cache) {
+ // Caching enabled, get translated string from cache
+ if (array_key_exists($string, $this->cache_translations))
+ return $this->cache_translations[$string];
+ else
+ return $string;
+ } else {
+ // Caching not enabled, try to find string
+ $num = $this->find_string($string);
+ if ($num == -1)
+ return $string;
+ else
+ return $this->get_translation_string($num);
+ }
+ }
+
+ /**
+ * Sanitize plural form expression for use in PHP eval call.
+ *
+ * @access private
+ * @return string sanitized plural form expression
+ */
+ function sanitize_plural_expression($expr) {
+ // Get rid of disallowed characters.
+ $expr = preg_replace('@[^a-zA-Z0-9_:;\(\)\?\|\&=!<>+*/\%-]@', '', $expr);
+
+ // Add parenthesis for tertiary '?' operator.
+ $expr .= ';';
+ $res = '';
+ $p = 0;
+ for ($i = 0; $i < strlen($expr); $i++) {
+ $ch = $expr[$i];
+ switch ($ch) {
+ case '?':
+ $res .= ' ? (';
+ $p++;
+ break;
+ case ':':
+ $res .= ') : (';
+ break;
+ case ';':
+ $res .= str_repeat( ')', $p) . ';';
+ $p = 0;
+ break;
+ default:
+ $res .= $ch;
+ }
+ }
+ return $res;
+ }
+
+ /**
+ * Parse full PO header and extract only plural forms line.
+ *
+ * @access private
+ * @return string verbatim plural form header field
+ */
+ function extract_plural_forms_header_from_po_header($header) {
+ if (preg_match("/(^|\n)plural-forms: ([^\n]*)\n/i", $header, $regs))
+ $expr = $regs[2];
+ else
+ $expr = "nplurals=2; plural=n == 1 ? 0 : 1;";
+ return $expr;
+ }
+
+ /**
+ * Get possible plural forms from MO header
+ *
+ * @access private
+ * @return string plural form header
+ */
+ function get_plural_forms() {
+ // lets assume message number 0 is header
+ // this is true, right?
+ $this->load_tables();
+
+ // cache header field for plural forms
+ if (! is_string($this->pluralheader)) {
+ if ($this->enable_cache) {
+ $header = $this->cache_translations[""];
+ } else {
+ $header = $this->get_translation_string(0);
+ }
+ $expr = $this->extract_plural_forms_header_from_po_header($header);
+ $this->pluralheader = $this->sanitize_plural_expression($expr);
+ }
+ return $this->pluralheader;
+ }
+
+ /**
+ * Detects which plural form to take
+ *
+ * @access private
+ * @param n count
+ * @return int array index of the right plural form
+ */
+ function select_string($n) {
+ $string = $this->get_plural_forms();
+ $string = str_replace('nplurals',"\$total",$string);
+ $string = str_replace("n",$n,$string);
+ $string = str_replace('plural',"\$plural",$string);
+
+ $total = 0;
+ $plural = 0;
+
+ eval("$string");
+ if ($plural >= $total) $plural = $total - 1;
+ return $plural;
+ }
+
+ /**
+ * Plural version of gettext
+ *
+ * @access public
+ * @param string single
+ * @param string plural
+ * @param string number
+ * @return translated plural form
+ */
+ function ngettext($single, $plural, $number) {
+ if ($this->short_circuit) {
+ if ($number != 1)
+ return $plural;
+ else
+ return $single;
+ }
+
+ // find out the appropriate form
+ $select = $this->select_string($number);
+
+ // this should contains all strings separated by NULLs
+ $key = $single . chr(0) . $plural;
+
+
+ if ($this->enable_cache) {
+ if (! array_key_exists($key, $this->cache_translations)) {
+ return ($number != 1) ? $plural : $single;
+ } else {
+ $result = $this->cache_translations[$key];
+ $list = explode(chr(0), $result);
+ return $list[$select];
+ }
+ } else {
+ $num = $this->find_string($key);
+ if ($num == -1) {
+ return ($number != 1) ? $plural : $single;
+ } else {
+ $result = $this->get_translation_string($num);
+ $list = explode(chr(0), $result);
+ return $list[$select];
+ }
+ }
+ }
+
+ function pgettext($context, $msgid) {
+ $key = $context . chr(4) . $msgid;
+ $ret = $this->translate($key);
+ if (strpos($ret, "\004") !== FALSE) {
+ return $msgid;
+ } else {
+ return $ret;
+ }
+ }
+
+ function npgettext($context, $singular, $plural, $number) {
+ $key = $context . chr(4) . $singular;
+ $ret = $this->ngettext($key, $plural, $number);
+ if (strpos($ret, "\004") !== FALSE) {
+ return $singular;
+ } else {
+ return $ret;
+ }
+
+ }
+}
+
+?>
diff --git a/libraries/php-gettext/streams.php b/libraries/php-gettext/streams.php
new file mode 100644
index 0000000000..3cdc1584e1
--- /dev/null
+++ b/libraries/php-gettext/streams.php
@@ -0,0 +1,167 @@
+<?php
+/*
+ Copyright (c) 2003, 2005, 2006, 2009 Danilo Segan <danilo@kvota.net>.
+
+ This file is part of PHP-gettext.
+
+ PHP-gettext 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.
+
+ PHP-gettext 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 PHP-gettext; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+*/
+
+
+ // Simple class to wrap file streams, string streams, etc.
+ // seek is essential, and it should be byte stream
+class StreamReader {
+ // should return a string [FIXME: perhaps return array of bytes?]
+ function read($bytes) {
+ return false;
+ }
+
+ // should return new position
+ function seekto($position) {
+ return false;
+ }
+
+ // returns current position
+ function currentpos() {
+ return false;
+ }
+
+ // returns length of entire stream (limit for seekto()s)
+ function length() {
+ return false;
+ }
+};
+
+class StringReader {
+ var $_pos;
+ var $_str;
+
+ function StringReader($str='') {
+ $this->_str = $str;
+ $this->_pos = 0;
+ }
+
+ function read($bytes) {
+ $data = substr($this->_str, $this->_pos, $bytes);
+ $this->_pos += $bytes;
+ if (strlen($this->_str)<$this->_pos)
+ $this->_pos = strlen($this->_str);
+
+ return $data;
+ }
+
+ function seekto($pos) {
+ $this->_pos = $pos;
+ if (strlen($this->_str)<$this->_pos)
+ $this->_pos = strlen($this->_str);
+ return $this->_pos;
+ }
+
+ function currentpos() {
+ return $this->_pos;
+ }
+
+ function length() {
+ return strlen($this->_str);
+ }
+
+};
+
+
+class FileReader {
+ var $_pos;
+ var $_fd;
+ var $_length;
+
+ function FileReader($filename) {
+ if (file_exists($filename)) {
+
+ $this->_length=filesize($filename);
+ $this->_pos = 0;
+ $this->_fd = fopen($filename,'rb');
+ if (!$this->_fd) {
+ $this->error = 3; // Cannot read file, probably permissions
+ return false;
+ }
+ } else {
+ $this->error = 2; // File doesn't exist
+ return false;
+ }
+ }
+
+ function read($bytes) {
+ if ($bytes) {
+ fseek($this->_fd, $this->_pos);
+
+ // PHP 5.1.1 does not read more than 8192 bytes in one fread()
+ // the discussions at PHP Bugs suggest it's the intended behaviour
+ $data = '';
+ while ($bytes > 0) {
+ $chunk = fread($this->_fd, $bytes);
+ $data .= $chunk;
+ $bytes -= strlen($chunk);
+ }
+ $this->_pos = ftell($this->_fd);
+
+ return $data;
+ } else return '';
+ }
+
+ function seekto($pos) {
+ fseek($this->_fd, $pos);
+ $this->_pos = ftell($this->_fd);
+ return $this->_pos;
+ }
+
+ function currentpos() {
+ return $this->_pos;
+ }
+
+ function length() {
+ return $this->_length;
+ }
+
+ function close() {
+ fclose($this->_fd);
+ }
+
+};
+
+// Preloads entire file in memory first, then creates a StringReader
+// over it (it assumes knowledge of StringReader internals)
+class CachedFileReader extends StringReader {
+ function CachedFileReader($filename) {
+ if (file_exists($filename)) {
+
+ $length=filesize($filename);
+ $fd = fopen($filename,'rb');
+
+ if (!$fd) {
+ $this->error = 3; // Cannot read file, probably permissions
+ return false;
+ }
+ $this->_str = fread($fd, $length);
+ fclose($fd);
+
+ } else {
+ $this->error = 2; // File doesn't exist
+ return false;
+ }
+ }
+};
+
+
+?>
diff --git a/libraries/phpseclib/Crypt/AES.php b/libraries/phpseclib/Crypt/AES.php
new file mode 100644
index 0000000000..58c8b52c9f
--- /dev/null
+++ b/libraries/phpseclib/Crypt/AES.php
@@ -0,0 +1,612 @@
+<?php
+/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
+
+/**
+ * Pure-PHP implementation of AES.
+ *
+ * Uses mcrypt, if available, and an internal implementation, otherwise.
+ *
+ * PHP versions 4 and 5
+ *
+ * If {@link Crypt_AES::setKeyLength() setKeyLength()} isn't called, it'll be calculated from
+ * {@link Crypt_AES::setKey() setKey()}. ie. if the key is 128-bits, the key length will be 128-bits. If it's 136-bits
+ * it'll be null-padded to 160-bits and 160 bits will be the key length until {@link Crypt_Rijndael::setKey() setKey()}
+ * is called, again, at which point, it'll be recalculated.
+ *
+ * Since Crypt_AES extends Crypt_Rijndael, some functions are available to be called that, in the context of AES, don't
+ * make a whole lot of sense. {@link Crypt_AES::setBlockLength() setBlockLength()}, for instance. Calling that function,
+ * however possible, won't do anything (AES has a fixed block length whereas Rijndael has a variable one).
+ *
+ * Here's a short example of how to use this library:
+ * <code>
+ * <?php
+ * include('Crypt/AES.php');
+ *
+ * $aes = new Crypt_AES();
+ *
+ * $aes->setKey('abcdefghijklmnop');
+ *
+ * $size = 10 * 1024;
+ * $plaintext = '';
+ * for ($i = 0; $i < $size; $i++) {
+ * $plaintext.= 'a';
+ * }
+ *
+ * echo $aes->decrypt($aes->encrypt($plaintext));
+ * ?>
+ * </code>
+ *
+ * LICENSE: 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.
+ *
+ * @category Crypt
+ * @package Crypt_AES
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright MMVIII Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @version $Id: AES.php,v 1.7 2010/02/09 06:10:25 terrafrost Exp $
+ * @link http://phpseclib.sourceforge.net
+ */
+
+/**
+ * Include Crypt_Rijndael
+ */
+if (!class_exists('Crypt_Rijndael')) {
+ require_once 'Rijndael.php';
+}
+
+/**#@+
+ * @access public
+ * @see Crypt_AES::encrypt()
+ * @see Crypt_AES::decrypt()
+ */
+/**
+ * Encrypt / decrypt using the Counter mode.
+ *
+ * Set to -1 since that's what Crypt/Random.php uses to index the CTR mode.
+ *
+ * @link http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation#Counter_.28CTR.29
+ */
+define('CRYPT_AES_MODE_CTR', -1);
+/**
+ * Encrypt / decrypt using the Electronic Code Book mode.
+ *
+ * @link http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation#Electronic_codebook_.28ECB.29
+ */
+define('CRYPT_AES_MODE_ECB', 1);
+/**
+ * Encrypt / decrypt using the Code Book Chaining mode.
+ *
+ * @link http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation#Cipher-block_chaining_.28CBC.29
+ */
+define('CRYPT_AES_MODE_CBC', 2);
+/**
+ * Encrypt / decrypt using the Cipher Feedback mode.
+ *
+ * @link http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation#Cipher_feedback_.28CFB.29
+ */
+define('CRYPT_AES_MODE_CFB', 3);
+/**
+ * Encrypt / decrypt using the Cipher Feedback mode.
+ *
+ * @link http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation#Output_feedback_.28OFB.29
+ */
+define('CRYPT_AES_MODE_OFB', 4);
+/**#@-*/
+
+/**#@+
+ * @access private
+ * @see Crypt_AES::Crypt_AES()
+ */
+/**
+ * Toggles the internal implementation
+ */
+define('CRYPT_AES_MODE_INTERNAL', 1);
+/**
+ * Toggles the mcrypt implementation
+ */
+define('CRYPT_AES_MODE_MCRYPT', 2);
+/**#@-*/
+
+/**
+ * Pure-PHP implementation of AES.
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @version 0.1.0
+ * @access public
+ * @package Crypt_AES
+ */
+class Crypt_AES extends Crypt_Rijndael {
+ /**
+ * mcrypt resource for encryption
+ *
+ * The mcrypt resource can be recreated every time something needs to be created or it can be created just once.
+ * Since mcrypt operates in continuous mode, by default, it'll need to be recreated when in non-continuous mode.
+ *
+ * @see Crypt_AES::encrypt()
+ * @var String
+ * @access private
+ */
+ var $enmcrypt;
+
+ /**
+ * mcrypt resource for decryption
+ *
+ * The mcrypt resource can be recreated every time something needs to be created or it can be created just once.
+ * Since mcrypt operates in continuous mode, by default, it'll need to be recreated when in non-continuous mode.
+ *
+ * @see Crypt_AES::decrypt()
+ * @var String
+ * @access private
+ */
+ var $demcrypt;
+
+ /**
+ * mcrypt resource for CFB mode
+ *
+ * @see Crypt_AES::encrypt()
+ * @see Crypt_AES::decrypt()
+ * @var String
+ * @access private
+ */
+ var $ecb;
+
+ /**
+ * Default Constructor.
+ *
+ * Determines whether or not the mcrypt extension should be used. $mode should only, at present, be
+ * CRYPT_AES_MODE_ECB or CRYPT_AES_MODE_CBC. If not explictly set, CRYPT_AES_MODE_CBC will be used.
+ *
+ * @param optional Integer $mode
+ * @return Crypt_AES
+ * @access public
+ */
+ function Crypt_AES($mode = CRYPT_AES_MODE_CBC)
+ {
+ if ( !defined('CRYPT_AES_MODE') ) {
+ switch (true) {
+ case extension_loaded('mcrypt') && in_array('rijndael-128', mcrypt_list_algorithms()):
+ define('CRYPT_AES_MODE', CRYPT_AES_MODE_MCRYPT);
+ break;
+ default:
+ define('CRYPT_AES_MODE', CRYPT_AES_MODE_INTERNAL);
+ }
+ }
+
+ switch ( CRYPT_AES_MODE ) {
+ case CRYPT_AES_MODE_MCRYPT:
+ switch ($mode) {
+ case CRYPT_AES_MODE_ECB:
+ $this->paddable = true;
+ $this->mode = MCRYPT_MODE_ECB;
+ break;
+ case CRYPT_AES_MODE_CTR:
+ // ctr doesn't have a constant associated with it even though it appears to be fairly widely
+ // supported. in lieu of knowing just how widely supported it is, i've, for now, opted not to
+ // include a compatibility layer. the layer has been implemented but, for now, is commented out.
+ $this->mode = 'ctr';
+ //$this->mode = in_array('ctr', mcrypt_list_modes()) ? 'ctr' : CRYPT_AES_MODE_CTR;
+ break;
+ case CRYPT_AES_MODE_CFB:
+ $this->mode = 'ncfb';
+ break;
+ case CRYPT_AES_MODE_OFB:
+ $this->mode = MCRYPT_MODE_NOFB;
+ break;
+ case CRYPT_AES_MODE_CBC:
+ default:
+ $this->paddable = true;
+ $this->mode = MCRYPT_MODE_CBC;
+ }
+
+ $this->debuffer = $this->enbuffer = '';
+
+ break;
+ default:
+ switch ($mode) {
+ case CRYPT_AES_MODE_ECB:
+ $this->paddable = true;
+ $this->mode = CRYPT_RIJNDAEL_MODE_ECB;
+ break;
+ case CRYPT_AES_MODE_CTR:
+ $this->mode = CRYPT_RIJNDAEL_MODE_CTR;
+ break;
+ case CRYPT_AES_MODE_CFB:
+ $this->mode = CRYPT_RIJNDAEL_MODE_CFB;
+ break;
+ case CRYPT_AES_MODE_OFB:
+ $this->mode = CRYPT_RIJNDAEL_MODE_OFB;
+ break;
+ case CRYPT_AES_MODE_CBC:
+ default:
+ $this->paddable = true;
+ $this->mode = CRYPT_RIJNDAEL_MODE_CBC;
+ }
+ }
+
+ if (CRYPT_AES_MODE == CRYPT_AES_MODE_INTERNAL) {
+ parent::Crypt_Rijndael($this->mode);
+ }
+ }
+
+ /**
+ * Dummy function
+ *
+ * Since Crypt_AES extends Crypt_Rijndael, this function is, technically, available, but it doesn't do anything.
+ *
+ * @access public
+ * @param Integer $length
+ */
+ function setBlockLength($length)
+ {
+ return;
+ }
+
+
+ /**
+ * Sets the initialization vector. (optional)
+ *
+ * SetIV is not required when CRYPT_RIJNDAEL_MODE_ECB is being used. If not explictly set, it'll be assumed
+ * to be all zero's.
+ *
+ * @access public
+ * @param String $iv
+ */
+ function setIV($iv)
+ {
+ parent::setIV($iv);
+ if ( CRYPT_AES_MODE == CRYPT_AES_MODE_MCRYPT ) {
+ $this->changed = true;
+ }
+ }
+
+ /**
+ * Encrypts a message.
+ *
+ * $plaintext will be padded with up to 16 additional bytes. Other AES implementations may or may not pad in the
+ * same manner. Other common approaches to padding and the reasons why it's necessary are discussed in the following
+ * URL:
+ *
+ * {@link http://www.di-mgt.com.au/cryptopad.html http://www.di-mgt.com.au/cryptopad.html}
+ *
+ * An alternative to padding is to, separately, send the length of the file. This is what SSH, in fact, does.
+ * strlen($plaintext) will still need to be a multiple of 16, however, arbitrary values can be added to make it that
+ * length.
+ *
+ * @see Crypt_AES::decrypt()
+ * @access public
+ * @param String $plaintext
+ */
+ function encrypt($plaintext)
+ {
+ if ( CRYPT_AES_MODE == CRYPT_AES_MODE_MCRYPT ) {
+ $changed = $this->changed;
+ $this->_mcryptSetup();
+ /*
+ if ($this->mode == CRYPT_AES_MODE_CTR) {
+ $iv = $this->encryptIV;
+ $xor = mcrypt_generic($this->enmcrypt, $this->_generate_xor(strlen($plaintext), $iv));
+ $ciphertext = $plaintext ^ $xor;
+ if ($this->continuousBuffer) {
+ $this->encryptIV = $iv;
+ }
+ return $ciphertext;
+ }
+ */
+ // re: http://phpseclib.sourceforge.net/cfb-demo.phps
+ // using mcrypt's default handing of CFB the above would output two different things. using phpseclib's
+ // rewritten CFB implementation the above outputs the same thing twice.
+ if ($this->mode == 'ncfb') {
+ if ($changed) {
+ $this->ecb = mcrypt_module_open(MCRYPT_RIJNDAEL_128, '', MCRYPT_MODE_ECB, '');
+ mcrypt_generic_init($this->ecb, $this->key, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0");
+ }
+
+ if (strlen($this->enbuffer)) {
+ $ciphertext = $plaintext ^ substr($this->encryptIV, strlen($this->enbuffer));
+ $this->enbuffer.= $ciphertext;
+ if (strlen($this->enbuffer) == 16) {
+ $this->encryptIV = $this->enbuffer;
+ $this->enbuffer = '';
+ mcrypt_generic_init($this->enmcrypt, $this->key, $this->encryptIV);
+ }
+ $plaintext = substr($plaintext, strlen($ciphertext));
+ } else {
+ $ciphertext = '';
+ }
+
+ $last_pos = strlen($plaintext) & 0xFFFFFFF0;
+ $ciphertext.= $last_pos ? mcrypt_generic($this->enmcrypt, substr($plaintext, 0, $last_pos)) : '';
+
+ if (strlen($plaintext) & 0xF) {
+ if (strlen($ciphertext)) {
+ $this->encryptIV = substr($ciphertext, -16);
+ }
+ $this->encryptIV = mcrypt_generic($this->ecb, $this->encryptIV);
+ $this->enbuffer = substr($plaintext, $last_pos) ^ $this->encryptIV;
+ $ciphertext.= $this->enbuffer;
+ }
+
+ return $ciphertext;
+ }
+
+ if ($this->paddable) {
+ $plaintext = $this->_pad($plaintext);
+ }
+
+ $ciphertext = mcrypt_generic($this->enmcrypt, $plaintext);
+
+ if (!$this->continuousBuffer) {
+ mcrypt_generic_init($this->enmcrypt, $this->key, $this->iv);
+ }
+
+ return $ciphertext;
+ }
+
+ return parent::encrypt($plaintext);
+ }
+
+ /**
+ * Decrypts a message.
+ *
+ * If strlen($ciphertext) is not a multiple of 16, null bytes will be added to the end of the string until it is.
+ *
+ * @see Crypt_AES::encrypt()
+ * @access public
+ * @param String $ciphertext
+ */
+ function decrypt($ciphertext)
+ {
+ if ( CRYPT_AES_MODE == CRYPT_AES_MODE_MCRYPT ) {
+ $changed = $this->changed;
+ $this->_mcryptSetup();
+ /*
+ if ($this->mode == CRYPT_AES_MODE_CTR) {
+ $iv = $this->decryptIV;
+ $xor = mcrypt_generic($this->enmcrypt, $this->_generate_xor(strlen($ciphertext), $iv));
+ $plaintext = $ciphertext ^ $xor;
+ if ($this->continuousBuffer) {
+ $this->decryptIV = $iv;
+ }
+ return $plaintext;
+ }
+ */
+ if ($this->mode == 'ncfb') {
+ if ($changed) {
+ $this->ecb = mcrypt_module_open(MCRYPT_RIJNDAEL_128, '', MCRYPT_MODE_ECB, '');
+ mcrypt_generic_init($this->ecb, $this->key, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0");
+ }
+
+ if (strlen($this->debuffer)) {
+ $plaintext = $ciphertext ^ substr($this->decryptIV, strlen($this->debuffer));
+
+ $this->debuffer.= substr($ciphertext, 0, strlen($plaintext));
+ if (strlen($this->debuffer) == 16) {
+ $this->decryptIV = $this->debuffer;
+ $this->debuffer = '';
+ mcrypt_generic_init($this->demcrypt, $this->key, $this->decryptIV);
+ }
+ $ciphertext = substr($ciphertext, strlen($plaintext));
+ } else {
+ $plaintext = '';
+ }
+
+ $last_pos = strlen($ciphertext) & 0xFFFFFFF0;
+ $plaintext.= $last_pos ? mdecrypt_generic($this->demcrypt, substr($ciphertext, 0, $last_pos)) : '';
+
+ if (strlen($ciphertext) & 0xF) {
+ if (strlen($plaintext)) {
+ $this->decryptIV = substr($ciphertext, $last_pos - 16, 16);
+ }
+ $this->decryptIV = mcrypt_generic($this->ecb, $this->decryptIV);
+ $this->debuffer = substr($ciphertext, $last_pos);
+ $plaintext.= $this->debuffer ^ $this->decryptIV;
+ }
+
+ return $plaintext;
+ }
+
+ if ($this->paddable) {
+ // we pad with chr(0) since that's what mcrypt_generic does. to quote from http://php.net/function.mcrypt-generic :
+ // "The data is padded with "\0" to make sure the length of the data is n * blocksize."
+ $ciphertext = str_pad($ciphertext, (strlen($ciphertext) + 15) & 0xFFFFFFF0, chr(0));
+ }
+
+ $plaintext = mdecrypt_generic($this->demcrypt, $ciphertext);
+
+ if (!$this->continuousBuffer) {
+ mcrypt_generic_init($this->demcrypt, $this->key, $this->iv);
+ }
+
+ return $this->paddable ? $this->_unpad($plaintext) : $plaintext;
+ }
+
+ return parent::decrypt($ciphertext);
+ }
+
+ /**
+ * Setup mcrypt
+ *
+ * Validates all the variables.
+ *
+ * @access private
+ */
+ function _mcryptSetup()
+ {
+ if (!$this->changed) {
+ return;
+ }
+
+ if (!$this->explicit_key_length) {
+ // this just copied from Crypt_Rijndael::_setup()
+ $length = strlen($this->key) >> 2;
+ if ($length > 8) {
+ $length = 8;
+ } else if ($length < 4) {
+ $length = 4;
+ }
+ $this->Nk = $length;
+ $this->key_size = $length << 2;
+ }
+
+ switch ($this->Nk) {
+ case 4: // 128
+ $this->key_size = 16;
+ break;
+ case 5: // 160
+ case 6: // 192
+ $this->key_size = 24;
+ break;
+ case 7: // 224
+ case 8: // 256
+ $this->key_size = 32;
+ }
+
+ $this->key = str_pad(substr($this->key, 0, $this->key_size), $this->key_size, chr(0));
+ $this->encryptIV = $this->decryptIV = $this->iv = str_pad(substr($this->iv, 0, 16), 16, chr(0));
+
+ if (!isset($this->enmcrypt)) {
+ $mode = $this->mode;
+ //$mode = $this->mode == CRYPT_AES_MODE_CTR ? MCRYPT_MODE_ECB : $this->mode;
+
+ $this->demcrypt = mcrypt_module_open(MCRYPT_RIJNDAEL_128, '', $mode, '');
+ $this->enmcrypt = mcrypt_module_open(MCRYPT_RIJNDAEL_128, '', $mode, '');
+ } // else should mcrypt_generic_deinit be called?
+
+ mcrypt_generic_init($this->demcrypt, $this->key, $this->iv);
+ mcrypt_generic_init($this->enmcrypt, $this->key, $this->iv);
+
+ $this->changed = false;
+ }
+
+ /**
+ * Encrypts a block
+ *
+ * Optimized over Crypt_Rijndael's implementation by means of loop unrolling.
+ *
+ * @see Crypt_Rijndael::_encryptBlock()
+ * @access private
+ * @param String $in
+ * @return String
+ */
+ function _encryptBlock($in)
+ {
+ $state = unpack('N*word', $in);
+
+ $Nr = $this->Nr;
+ $w = $this->w;
+ $t0 = $this->t0;
+ $t1 = $this->t1;
+ $t2 = $this->t2;
+ $t3 = $this->t3;
+
+ // addRoundKey and reindex $state
+ $state = array(
+ $state['word1'] ^ $w[0][0],
+ $state['word2'] ^ $w[0][1],
+ $state['word3'] ^ $w[0][2],
+ $state['word4'] ^ $w[0][3]
+ );
+
+ // shiftRows + subWord + mixColumns + addRoundKey
+ // we could loop unroll this and use if statements to do more rounds as necessary, but, in my tests, that yields
+ // only a marginal improvement. since that also, imho, hinders the readability of the code, i've opted not to do it.
+ for ($round = 1; $round < $this->Nr; $round++) {
+ $state = array(
+ $t0[$state[0] & 0xFF000000] ^ $t1[$state[1] & 0x00FF0000] ^ $t2[$state[2] & 0x0000FF00] ^ $t3[$state[3] & 0x000000FF] ^ $w[$round][0],
+ $t0[$state[1] & 0xFF000000] ^ $t1[$state[2] & 0x00FF0000] ^ $t2[$state[3] & 0x0000FF00] ^ $t3[$state[0] & 0x000000FF] ^ $w[$round][1],
+ $t0[$state[2] & 0xFF000000] ^ $t1[$state[3] & 0x00FF0000] ^ $t2[$state[0] & 0x0000FF00] ^ $t3[$state[1] & 0x000000FF] ^ $w[$round][2],
+ $t0[$state[3] & 0xFF000000] ^ $t1[$state[0] & 0x00FF0000] ^ $t2[$state[1] & 0x0000FF00] ^ $t3[$state[2] & 0x000000FF] ^ $w[$round][3]
+ );
+
+ }
+
+ // subWord
+ $state = array(
+ $this->_subWord($state[0]),
+ $this->_subWord($state[1]),
+ $this->_subWord($state[2]),
+ $this->_subWord($state[3])
+ );
+
+ // shiftRows + addRoundKey
+ $state = array(
+ ($state[0] & 0xFF000000) ^ ($state[1] & 0x00FF0000) ^ ($state[2] & 0x0000FF00) ^ ($state[3] & 0x000000FF) ^ $this->w[$this->Nr][0],
+ ($state[1] & 0xFF000000) ^ ($state[2] & 0x00FF0000) ^ ($state[3] & 0x0000FF00) ^ ($state[0] & 0x000000FF) ^ $this->w[$this->Nr][1],
+ ($state[2] & 0xFF000000) ^ ($state[3] & 0x00FF0000) ^ ($state[0] & 0x0000FF00) ^ ($state[1] & 0x000000FF) ^ $this->w[$this->Nr][2],
+ ($state[3] & 0xFF000000) ^ ($state[0] & 0x00FF0000) ^ ($state[1] & 0x0000FF00) ^ ($state[2] & 0x000000FF) ^ $this->w[$this->Nr][3]
+ );
+
+ return pack('N*', $state[0], $state[1], $state[2], $state[3]);
+ }
+
+ /**
+ * Decrypts a block
+ *
+ * Optimized over Crypt_Rijndael's implementation by means of loop unrolling.
+ *
+ * @see Crypt_Rijndael::_decryptBlock()
+ * @access private
+ * @param String $in
+ * @return String
+ */
+ function _decryptBlock($in)
+ {
+ $state = unpack('N*word', $in);
+
+ $Nr = $this->Nr;
+ $dw = $this->dw;
+ $dt0 = $this->dt0;
+ $dt1 = $this->dt1;
+ $dt2 = $this->dt2;
+ $dt3 = $this->dt3;
+
+ // addRoundKey and reindex $state
+ $state = array(
+ $state['word1'] ^ $dw[$this->Nr][0],
+ $state['word2'] ^ $dw[$this->Nr][1],
+ $state['word3'] ^ $dw[$this->Nr][2],
+ $state['word4'] ^ $dw[$this->Nr][3]
+ );
+
+
+ // invShiftRows + invSubBytes + invMixColumns + addRoundKey
+ for ($round = $this->Nr - 1; $round > 0; $round--) {
+ $state = array(
+ $dt0[$state[0] & 0xFF000000] ^ $dt1[$state[3] & 0x00FF0000] ^ $dt2[$state[2] & 0x0000FF00] ^ $dt3[$state[1] & 0x000000FF] ^ $dw[$round][0],
+ $dt0[$state[1] & 0xFF000000] ^ $dt1[$state[0] & 0x00FF0000] ^ $dt2[$state[3] & 0x0000FF00] ^ $dt3[$state[2] & 0x000000FF] ^ $dw[$round][1],
+ $dt0[$state[2] & 0xFF000000] ^ $dt1[$state[1] & 0x00FF0000] ^ $dt2[$state[0] & 0x0000FF00] ^ $dt3[$state[3] & 0x000000FF] ^ $dw[$round][2],
+ $dt0[$state[3] & 0xFF000000] ^ $dt1[$state[2] & 0x00FF0000] ^ $dt2[$state[1] & 0x0000FF00] ^ $dt3[$state[0] & 0x000000FF] ^ $dw[$round][3]
+ );
+ }
+
+ // invShiftRows + invSubWord + addRoundKey
+ $state = array(
+ $this->_invSubWord(($state[0] & 0xFF000000) ^ ($state[3] & 0x00FF0000) ^ ($state[2] & 0x0000FF00) ^ ($state[1] & 0x000000FF)) ^ $dw[0][0],
+ $this->_invSubWord(($state[1] & 0xFF000000) ^ ($state[0] & 0x00FF0000) ^ ($state[3] & 0x0000FF00) ^ ($state[2] & 0x000000FF)) ^ $dw[0][1],
+ $this->_invSubWord(($state[2] & 0xFF000000) ^ ($state[1] & 0x00FF0000) ^ ($state[0] & 0x0000FF00) ^ ($state[3] & 0x000000FF)) ^ $dw[0][2],
+ $this->_invSubWord(($state[3] & 0xFF000000) ^ ($state[2] & 0x00FF0000) ^ ($state[1] & 0x0000FF00) ^ ($state[0] & 0x000000FF)) ^ $dw[0][3]
+ );
+
+ return pack('N*', $state[0], $state[1], $state[2], $state[3]);
+ }
+}
+
+// vim: ts=4:sw=4:et:
+// vim6: fdl=1:
+?>
diff --git a/libraries/phpseclib/Crypt/Rijndael.php b/libraries/phpseclib/Crypt/Rijndael.php
new file mode 100644
index 0000000000..b5aef38a93
--- /dev/null
+++ b/libraries/phpseclib/Crypt/Rijndael.php
@@ -0,0 +1,1479 @@
+<?php
+/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
+
+/**
+ * Pure-PHP implementation of Rijndael.
+ *
+ * Does not use mcrypt, even when available, for reasons that are explained below.
+ *
+ * PHP versions 4 and 5
+ *
+ * If {@link Crypt_Rijndael::setBlockLength() setBlockLength()} isn't called, it'll be assumed to be 128 bits. If
+ * {@link Crypt_Rijndael::setKeyLength() setKeyLength()} isn't called, it'll be calculated from
+ * {@link Crypt_Rijndael::setKey() setKey()}. ie. if the key is 128-bits, the key length will be 128-bits. If it's
+ * 136-bits it'll be null-padded to 160-bits and 160 bits will be the key length until
+ * {@link Crypt_Rijndael::setKey() setKey()} is called, again, at which point, it'll be recalculated.
+ *
+ * Not all Rijndael implementations may support 160-bits or 224-bits as the block length / key length. mcrypt, for example,
+ * does not. AES, itself, only supports block lengths of 128 and key lengths of 128, 192, and 256.
+ * {@link http://csrc.nist.gov/archive/aes/rijndael/Rijndael-ammended.pdf#page=10 Rijndael-ammended.pdf#page=10} defines the
+ * algorithm for block lengths of 192 and 256 but not for block lengths / key lengths of 160 and 224. Indeed, 160 and 224
+ * are first defined as valid key / block lengths in
+ * {@link http://csrc.nist.gov/archive/aes/rijndael/Rijndael-ammended.pdf#page=44 Rijndael-ammended.pdf#page=44}:
+ * Extensions: Other block and Cipher Key lengths.
+ *
+ * {@internal The variable names are the same as those in
+ * {@link http://www.csrc.nist.gov/publications/fips/fips197/fips-197.pdf#page=10 fips-197.pdf#page=10}.}}
+ *
+ * Here's a short example of how to use this library:
+ * <code>
+ * <?php
+ * include('Crypt/Rijndael.php');
+ *
+ * $rijndael = new Crypt_Rijndael();
+ *
+ * $rijndael->setKey('abcdefghijklmnop');
+ *
+ * $size = 10 * 1024;
+ * $plaintext = '';
+ * for ($i = 0; $i < $size; $i++) {
+ * $plaintext.= 'a';
+ * }
+ *
+ * echo $rijndael->decrypt($rijndael->encrypt($plaintext));
+ * ?>
+ * </code>
+ *
+ * LICENSE: 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.
+ *
+ * @category Crypt
+ * @package Crypt_Rijndael
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright MMVIII Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @version $Id: Rijndael.php,v 1.12 2010/02/09 06:10:26 terrafrost Exp $
+ * @link http://phpseclib.sourceforge.net
+ */
+
+/**#@+
+ * @access public
+ * @see Crypt_Rijndael::encrypt()
+ * @see Crypt_Rijndael::decrypt()
+ */
+/**
+ * Encrypt / decrypt using the Counter mode.
+ *
+ * Set to -1 since that's what Crypt/Random.php uses to index the CTR mode.
+ *
+ * @link http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation#Counter_.28CTR.29
+ */
+define('CRYPT_RIJNDAEL_MODE_CTR', -1);
+/**
+ * Encrypt / decrypt using the Electronic Code Book mode.
+ *
+ * @link http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation#Electronic_codebook_.28ECB.29
+ */
+define('CRYPT_RIJNDAEL_MODE_ECB', 1);
+/**
+ * Encrypt / decrypt using the Code Book Chaining mode.
+ *
+ * @link http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation#Cipher-block_chaining_.28CBC.29
+ */
+define('CRYPT_RIJNDAEL_MODE_CBC', 2);
+/**
+ * Encrypt / decrypt using the Cipher Feedback mode.
+ *
+ * @link http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation#Cipher_feedback_.28CFB.29
+ */
+define('CRYPT_RIJNDAEL_MODE_CFB', 3);
+/**
+ * Encrypt / decrypt using the Cipher Feedback mode.
+ *
+ * @link http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation#Output_feedback_.28OFB.29
+ */
+define('CRYPT_RIJNDAEL_MODE_OFB', 4);
+/**#@-*/
+
+/**#@+
+ * @access private
+ * @see Crypt_Rijndael::Crypt_Rijndael()
+ */
+/**
+ * Toggles the internal implementation
+ */
+define('CRYPT_RIJNDAEL_MODE_INTERNAL', 1);
+/**
+ * Toggles the mcrypt implementation
+ */
+define('CRYPT_RIJNDAEL_MODE_MCRYPT', 2);
+/**#@-*/
+
+/**
+ * Pure-PHP implementation of Rijndael.
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @version 0.1.0
+ * @access public
+ * @package Crypt_Rijndael
+ */
+class Crypt_Rijndael {
+ /**
+ * The Encryption Mode
+ *
+ * @see Crypt_Rijndael::Crypt_Rijndael()
+ * @var Integer
+ * @access private
+ */
+ var $mode;
+
+ /**
+ * The Key
+ *
+ * @see Crypt_Rijndael::setKey()
+ * @var String
+ * @access private
+ */
+ var $key = "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0";
+
+ /**
+ * The Initialization Vector
+ *
+ * @see Crypt_Rijndael::setIV()
+ * @var String
+ * @access private
+ */
+ var $iv = '';
+
+ /**
+ * A "sliding" Initialization Vector
+ *
+ * @see Crypt_Rijndael::enableContinuousBuffer()
+ * @var String
+ * @access private
+ */
+ var $encryptIV = '';
+
+ /**
+ * A "sliding" Initialization Vector
+ *
+ * @see Crypt_Rijndael::enableContinuousBuffer()
+ * @var String
+ * @access private
+ */
+ var $decryptIV = '';
+
+ /**
+ * Continuous Buffer status
+ *
+ * @see Crypt_Rijndael::enableContinuousBuffer()
+ * @var Boolean
+ * @access private
+ */
+ var $continuousBuffer = false;
+
+ /**
+ * Padding status
+ *
+ * @see Crypt_Rijndael::enablePadding()
+ * @var Boolean
+ * @access private
+ */
+ var $padding = true;
+
+ /**
+ * Does the key schedule need to be (re)calculated?
+ *
+ * @see setKey()
+ * @see setBlockLength()
+ * @see setKeyLength()
+ * @var Boolean
+ * @access private
+ */
+ var $changed = true;
+
+ /**
+ * Has the key length explicitly been set or should it be derived from the key, itself?
+ *
+ * @see setKeyLength()
+ * @var Boolean
+ * @access private
+ */
+ var $explicit_key_length = false;
+
+ /**
+ * The Key Schedule
+ *
+ * @see _setup()
+ * @var Array
+ * @access private
+ */
+ var $w;
+
+ /**
+ * The Inverse Key Schedule
+ *
+ * @see _setup()
+ * @var Array
+ * @access private
+ */
+ var $dw;
+
+ /**
+ * The Block Length
+ *
+ * @see setBlockLength()
+ * @var Integer
+ * @access private
+ * @internal The max value is 32, the min value is 16. All valid values are multiples of 4. Exists in conjunction with
+ * $Nb because we need this value and not $Nb to pad strings appropriately.
+ */
+ var $block_size = 16;
+
+ /**
+ * The Block Length divided by 32
+ *
+ * @see setBlockLength()
+ * @var Integer
+ * @access private
+ * @internal The max value is 256 / 32 = 8, the min value is 128 / 32 = 4. Exists in conjunction with $block_size
+ * because the encryption / decryption / key schedule creation requires this number and not $block_size. We could
+ * derive this from $block_size or vice versa, but that'd mean we'd have to do multiple shift operations, so in lieu
+ * of that, we'll just precompute it once.
+ *
+ */
+ var $Nb = 4;
+
+ /**
+ * The Key Length
+ *
+ * @see setKeyLength()
+ * @var Integer
+ * @access private
+ * @internal The max value is 256 / 8 = 32, the min value is 128 / 8 = 16. Exists in conjunction with $key_size
+ * because the encryption / decryption / key schedule creation requires this number and not $key_size. We could
+ * derive this from $key_size or vice versa, but that'd mean we'd have to do multiple shift operations, so in lieu
+ * of that, we'll just precompute it once.
+ */
+ var $key_size = 16;
+
+ /**
+ * The Key Length divided by 32
+ *
+ * @see setKeyLength()
+ * @var Integer
+ * @access private
+ * @internal The max value is 256 / 32 = 8, the min value is 128 / 32 = 4
+ */
+ var $Nk = 4;
+
+ /**
+ * The Number of Rounds
+ *
+ * @var Integer
+ * @access private
+ * @internal The max value is 14, the min value is 10.
+ */
+ var $Nr;
+
+ /**
+ * Shift offsets
+ *
+ * @var Array
+ * @access private
+ */
+ var $c;
+
+ /**
+ * Precomputed mixColumns table
+ *
+ * @see Crypt_Rijndael()
+ * @var Array
+ * @access private
+ */
+ var $t0;
+
+ /**
+ * Precomputed mixColumns table
+ *
+ * @see Crypt_Rijndael()
+ * @var Array
+ * @access private
+ */
+ var $t1;
+
+ /**
+ * Precomputed mixColumns table
+ *
+ * @see Crypt_Rijndael()
+ * @var Array
+ * @access private
+ */
+ var $t2;
+
+ /**
+ * Precomputed mixColumns table
+ *
+ * @see Crypt_Rijndael()
+ * @var Array
+ * @access private
+ */
+ var $t3;
+
+ /**
+ * Precomputed invMixColumns table
+ *
+ * @see Crypt_Rijndael()
+ * @var Array
+ * @access private
+ */
+ var $dt0;
+
+ /**
+ * Precomputed invMixColumns table
+ *
+ * @see Crypt_Rijndael()
+ * @var Array
+ * @access private
+ */
+ var $dt1;
+
+ /**
+ * Precomputed invMixColumns table
+ *
+ * @see Crypt_Rijndael()
+ * @var Array
+ * @access private
+ */
+ var $dt2;
+
+ /**
+ * Precomputed invMixColumns table
+ *
+ * @see Crypt_Rijndael()
+ * @var Array
+ * @access private
+ */
+ var $dt3;
+
+ /**
+ * Is the mode one that is paddable?
+ *
+ * @see Crypt_Rijndael::Crypt_Rijndael()
+ * @var Boolean
+ * @access private
+ */
+ var $paddable = false;
+
+ /**
+ * Encryption buffer for CTR, OFB and CFB modes
+ *
+ * @see Crypt_Rijndael::encrypt()
+ * @var String
+ * @access private
+ */
+ var $enbuffer = array('encrypted' => '', 'xor' => '');
+
+ /**
+ * Decryption buffer for CTR, OFB and CFB modes
+ *
+ * @see Crypt_Rijndael::decrypt()
+ * @var String
+ * @access private
+ */
+ var $debuffer = array('ciphertext' => '');
+
+ /**
+ * Default Constructor.
+ *
+ * Determines whether or not the mcrypt extension should be used. $mode should only, at present, be
+ * CRYPT_RIJNDAEL_MODE_ECB or CRYPT_RIJNDAEL_MODE_CBC. If not explictly set, CRYPT_RIJNDAEL_MODE_CBC will be used.
+ *
+ * @param optional Integer $mode
+ * @return Crypt_Rijndael
+ * @access public
+ */
+ function Crypt_Rijndael($mode = CRYPT_RIJNDAEL_MODE_CBC)
+ {
+ switch ($mode) {
+ case CRYPT_RIJNDAEL_MODE_ECB:
+ case CRYPT_RIJNDAEL_MODE_CBC:
+ $this->paddable = true;
+ $this->mode = $mode;
+ break;
+ case CRYPT_RIJNDAEL_MODE_CTR:
+ case CRYPT_RIJNDAEL_MODE_CFB:
+ case CRYPT_RIJNDAEL_MODE_OFB:
+ $this->mode = $mode;
+ break;
+ default:
+ $this->paddable = true;
+ $this->mode = CRYPT_RIJNDAEL_MODE_CBC;
+ }
+
+ $t3 = &$this->t3;
+ $t2 = &$this->t2;
+ $t1 = &$this->t1;
+ $t0 = &$this->t0;
+
+ $dt3 = &$this->dt3;
+ $dt2 = &$this->dt2;
+ $dt1 = &$this->dt1;
+ $dt0 = &$this->dt0;
+
+ // according to <http://csrc.nist.gov/archive/aes/rijndael/Rijndael-ammended.pdf#page=19> (section 5.2.1),
+ // precomputed tables can be used in the mixColumns phase. in that example, they're assigned t0...t3, so
+ // those are the names we'll use.
+ $t3 = array(
+ 0x6363A5C6, 0x7C7C84F8, 0x777799EE, 0x7B7B8DF6, 0xF2F20DFF, 0x6B6BBDD6, 0x6F6FB1DE, 0xC5C55491,
+ 0x30305060, 0x01010302, 0x6767A9CE, 0x2B2B7D56, 0xFEFE19E7, 0xD7D762B5, 0xABABE64D, 0x76769AEC,
+ 0xCACA458F, 0x82829D1F, 0xC9C94089, 0x7D7D87FA, 0xFAFA15EF, 0x5959EBB2, 0x4747C98E, 0xF0F00BFB,
+ 0xADADEC41, 0xD4D467B3, 0xA2A2FD5F, 0xAFAFEA45, 0x9C9CBF23, 0xA4A4F753, 0x727296E4, 0xC0C05B9B,
+ 0xB7B7C275, 0xFDFD1CE1, 0x9393AE3D, 0x26266A4C, 0x36365A6C, 0x3F3F417E, 0xF7F702F5, 0xCCCC4F83,
+ 0x34345C68, 0xA5A5F451, 0xE5E534D1, 0xF1F108F9, 0x717193E2, 0xD8D873AB, 0x31315362, 0x15153F2A,
+ 0x04040C08, 0xC7C75295, 0x23236546, 0xC3C35E9D, 0x18182830, 0x9696A137, 0x05050F0A, 0x9A9AB52F,
+ 0x0707090E, 0x12123624, 0x80809B1B, 0xE2E23DDF, 0xEBEB26CD, 0x2727694E, 0xB2B2CD7F, 0x75759FEA,
+ 0x09091B12, 0x83839E1D, 0x2C2C7458, 0x1A1A2E34, 0x1B1B2D36, 0x6E6EB2DC, 0x5A5AEEB4, 0xA0A0FB5B,
+ 0x5252F6A4, 0x3B3B4D76, 0xD6D661B7, 0xB3B3CE7D, 0x29297B52, 0xE3E33EDD, 0x2F2F715E, 0x84849713,
+ 0x5353F5A6, 0xD1D168B9, 0x00000000, 0xEDED2CC1, 0x20206040, 0xFCFC1FE3, 0xB1B1C879, 0x5B5BEDB6,
+ 0x6A6ABED4, 0xCBCB468D, 0xBEBED967, 0x39394B72, 0x4A4ADE94, 0x4C4CD498, 0x5858E8B0, 0xCFCF4A85,
+ 0xD0D06BBB, 0xEFEF2AC5, 0xAAAAE54F, 0xFBFB16ED, 0x4343C586, 0x4D4DD79A, 0x33335566, 0x85859411,
+ 0x4545CF8A, 0xF9F910E9, 0x02020604, 0x7F7F81FE, 0x5050F0A0, 0x3C3C4478, 0x9F9FBA25, 0xA8A8E34B,
+ 0x5151F3A2, 0xA3A3FE5D, 0x4040C080, 0x8F8F8A05, 0x9292AD3F, 0x9D9DBC21, 0x38384870, 0xF5F504F1,
+ 0xBCBCDF63, 0xB6B6C177, 0xDADA75AF, 0x21216342, 0x10103020, 0xFFFF1AE5, 0xF3F30EFD, 0xD2D26DBF,
+ 0xCDCD4C81, 0x0C0C1418, 0x13133526, 0xECEC2FC3, 0x5F5FE1BE, 0x9797A235, 0x4444CC88, 0x1717392E,
+ 0xC4C45793, 0xA7A7F255, 0x7E7E82FC, 0x3D3D477A, 0x6464ACC8, 0x5D5DE7BA, 0x19192B32, 0x737395E6,
+ 0x6060A0C0, 0x81819819, 0x4F4FD19E, 0xDCDC7FA3, 0x22226644, 0x2A2A7E54, 0x9090AB3B, 0x8888830B,
+ 0x4646CA8C, 0xEEEE29C7, 0xB8B8D36B, 0x14143C28, 0xDEDE79A7, 0x5E5EE2BC, 0x0B0B1D16, 0xDBDB76AD,
+ 0xE0E03BDB, 0x32325664, 0x3A3A4E74, 0x0A0A1E14, 0x4949DB92, 0x06060A0C, 0x24246C48, 0x5C5CE4B8,
+ 0xC2C25D9F, 0xD3D36EBD, 0xACACEF43, 0x6262A6C4, 0x9191A839, 0x9595A431, 0xE4E437D3, 0x79798BF2,
+ 0xE7E732D5, 0xC8C8438B, 0x3737596E, 0x6D6DB7DA, 0x8D8D8C01, 0xD5D564B1, 0x4E4ED29C, 0xA9A9E049,
+ 0x6C6CB4D8, 0x5656FAAC, 0xF4F407F3, 0xEAEA25CF, 0x6565AFCA, 0x7A7A8EF4, 0xAEAEE947, 0x08081810,
+ 0xBABAD56F, 0x787888F0, 0x25256F4A, 0x2E2E725C, 0x1C1C2438, 0xA6A6F157, 0xB4B4C773, 0xC6C65197,
+ 0xE8E823CB, 0xDDDD7CA1, 0x74749CE8, 0x1F1F213E, 0x4B4BDD96, 0xBDBDDC61, 0x8B8B860D, 0x8A8A850F,
+ 0x707090E0, 0x3E3E427C, 0xB5B5C471, 0x6666AACC, 0x4848D890, 0x03030506, 0xF6F601F7, 0x0E0E121C,
+ 0x6161A3C2, 0x35355F6A, 0x5757F9AE, 0xB9B9D069, 0x86869117, 0xC1C15899, 0x1D1D273A, 0x9E9EB927,
+ 0xE1E138D9, 0xF8F813EB, 0x9898B32B, 0x11113322, 0x6969BBD2, 0xD9D970A9, 0x8E8E8907, 0x9494A733,
+ 0x9B9BB62D, 0x1E1E223C, 0x87879215, 0xE9E920C9, 0xCECE4987, 0x5555FFAA, 0x28287850, 0xDFDF7AA5,
+ 0x8C8C8F03, 0xA1A1F859, 0x89898009, 0x0D0D171A, 0xBFBFDA65, 0xE6E631D7, 0x4242C684, 0x6868B8D0,
+ 0x4141C382, 0x9999B029, 0x2D2D775A, 0x0F0F111E, 0xB0B0CB7B, 0x5454FCA8, 0xBBBBD66D, 0x16163A2C
+ );
+
+ $dt3 = array(
+ 0xF4A75051, 0x4165537E, 0x17A4C31A, 0x275E963A, 0xAB6BCB3B, 0x9D45F11F, 0xFA58ABAC, 0xE303934B,
+ 0x30FA5520, 0x766DF6AD, 0xCC769188, 0x024C25F5, 0xE5D7FC4F, 0x2ACBD7C5, 0x35448026, 0x62A38FB5,
+ 0xB15A49DE, 0xBA1B6725, 0xEA0E9845, 0xFEC0E15D, 0x2F7502C3, 0x4CF01281, 0x4697A38D, 0xD3F9C66B,
+ 0x8F5FE703, 0x929C9515, 0x6D7AEBBF, 0x5259DA95, 0xBE832DD4, 0x7421D358, 0xE0692949, 0xC9C8448E,
+ 0xC2896A75, 0x8E7978F4, 0x583E6B99, 0xB971DD27, 0xE14FB6BE, 0x88AD17F0, 0x20AC66C9, 0xCE3AB47D,
+ 0xDF4A1863, 0x1A3182E5, 0x51336097, 0x537F4562, 0x6477E0B1, 0x6BAE84BB, 0x81A01CFE, 0x082B94F9,
+ 0x48685870, 0x45FD198F, 0xDE6C8794, 0x7BF8B752, 0x73D323AB, 0x4B02E272, 0x1F8F57E3, 0x55AB2A66,
+ 0xEB2807B2, 0xB5C2032F, 0xC57B9A86, 0x3708A5D3, 0x2887F230, 0xBFA5B223, 0x036ABA02, 0x16825CED,
+ 0xCF1C2B8A, 0x79B492A7, 0x07F2F0F3, 0x69E2A14E, 0xDAF4CD65, 0x05BED506, 0x34621FD1, 0xA6FE8AC4,
+ 0x2E539D34, 0xF355A0A2, 0x8AE13205, 0xF6EB75A4, 0x83EC390B, 0x60EFAA40, 0x719F065E, 0x6E1051BD,
+ 0x218AF93E, 0xDD063D96, 0x3E05AEDD, 0xE6BD464D, 0x548DB591, 0xC45D0571, 0x06D46F04, 0x5015FF60,
+ 0x98FB2419, 0xBDE997D6, 0x4043CC89, 0xD99E7767, 0xE842BDB0, 0x898B8807, 0x195B38E7, 0xC8EEDB79,
+ 0x7C0A47A1, 0x420FE97C, 0x841EC9F8, 0x00000000, 0x80868309, 0x2BED4832, 0x1170AC1E, 0x5A724E6C,
+ 0x0EFFFBFD, 0x8538560F, 0xAED51E3D, 0x2D392736, 0x0FD9640A, 0x5CA62168, 0x5B54D19B, 0x362E3A24,
+ 0x0A67B10C, 0x57E70F93, 0xEE96D2B4, 0x9B919E1B, 0xC0C54F80, 0xDC20A261, 0x774B695A, 0x121A161C,
+ 0x93BA0AE2, 0xA02AE5C0, 0x22E0433C, 0x1B171D12, 0x090D0B0E, 0x8BC7ADF2, 0xB6A8B92D, 0x1EA9C814,
+ 0xF1198557, 0x75074CAF, 0x99DDBBEE, 0x7F60FDA3, 0x01269FF7, 0x72F5BC5C, 0x663BC544, 0xFB7E345B,
+ 0x4329768B, 0x23C6DCCB, 0xEDFC68B6, 0xE4F163B8, 0x31DCCAD7, 0x63851042, 0x97224013, 0xC6112084,
+ 0x4A247D85, 0xBB3DF8D2, 0xF93211AE, 0x29A16DC7, 0x9E2F4B1D, 0xB230F3DC, 0x8652EC0D, 0xC1E3D077,
+ 0xB3166C2B, 0x70B999A9, 0x9448FA11, 0xE9642247, 0xFC8CC4A8, 0xF03F1AA0, 0x7D2CD856, 0x3390EF22,
+ 0x494EC787, 0x38D1C1D9, 0xCAA2FE8C, 0xD40B3698, 0xF581CFA6, 0x7ADE28A5, 0xB78E26DA, 0xADBFA43F,
+ 0x3A9DE42C, 0x78920D50, 0x5FCC9B6A, 0x7E466254, 0x8D13C2F6, 0xD8B8E890, 0x39F75E2E, 0xC3AFF582,
+ 0x5D80BE9F, 0xD0937C69, 0xD52DA96F, 0x2512B3CF, 0xAC993BC8, 0x187DA710, 0x9C636EE8, 0x3BBB7BDB,
+ 0x267809CD, 0x5918F46E, 0x9AB701EC, 0x4F9AA883, 0x956E65E6, 0xFFE67EAA, 0xBCCF0821, 0x15E8E6EF,
+ 0xE79BD9BA, 0x6F36CE4A, 0x9F09D4EA, 0xB07CD629, 0xA4B2AF31, 0x3F23312A, 0xA59430C6, 0xA266C035,
+ 0x4EBC3774, 0x82CAA6FC, 0x90D0B0E0, 0xA7D81533, 0x04984AF1, 0xECDAF741, 0xCD500E7F, 0x91F62F17,
+ 0x4DD68D76, 0xEFB04D43, 0xAA4D54CC, 0x9604DFE4, 0xD1B5E39E, 0x6A881B4C, 0x2C1FB8C1, 0x65517F46,
+ 0x5EEA049D, 0x8C355D01, 0x877473FA, 0x0B412EFB, 0x671D5AB3, 0xDBD25292, 0x105633E9, 0xD647136D,
+ 0xD7618C9A, 0xA10C7A37, 0xF8148E59, 0x133C89EB, 0xA927EECE, 0x61C935B7, 0x1CE5EDE1, 0x47B13C7A,
+ 0xD2DF599C, 0xF2733F55, 0x14CE7918, 0xC737BF73, 0xF7CDEA53, 0xFDAA5B5F, 0x3D6F14DF, 0x44DB8678,
+ 0xAFF381CA, 0x68C43EB9, 0x24342C38, 0xA3405FC2, 0x1DC37216, 0xE2250CBC, 0x3C498B28, 0x0D9541FF,
+ 0xA8017139, 0x0CB3DE08, 0xB4E49CD8, 0x56C19064, 0xCB84617B, 0x32B670D5, 0x6C5C7448, 0xB85742D0
+ );
+
+ for ($i = 0; $i < 256; $i++) {
+ $t2[$i << 8] = (($t3[$i] << 8) & 0xFFFFFF00) | (($t3[$i] >> 24) & 0x000000FF);
+ $t1[$i << 16] = (($t3[$i] << 16) & 0xFFFF0000) | (($t3[$i] >> 16) & 0x0000FFFF);
+ $t0[$i << 24] = (($t3[$i] << 24) & 0xFF000000) | (($t3[$i] >> 8) & 0x00FFFFFF);
+
+ $dt2[$i << 8] = (($this->dt3[$i] << 8) & 0xFFFFFF00) | (($dt3[$i] >> 24) & 0x000000FF);
+ $dt1[$i << 16] = (($this->dt3[$i] << 16) & 0xFFFF0000) | (($dt3[$i] >> 16) & 0x0000FFFF);
+ $dt0[$i << 24] = (($this->dt3[$i] << 24) & 0xFF000000) | (($dt3[$i] >> 8) & 0x00FFFFFF);
+ }
+ }
+
+ /**
+ * Sets the key.
+ *
+ * Keys can be of any length. Rijndael, itself, requires the use of a key that's between 128-bits and 256-bits long and
+ * whose length is a multiple of 32. If the key is less than 256-bits and the key length isn't set, we round the length
+ * up to the closest valid key length, padding $key with null bytes. If the key is more than 256-bits, we trim the
+ * excess bits.
+ *
+ * If the key is not explicitly set, it'll be assumed to be all null bytes.
+ *
+ * @access public
+ * @param String $key
+ */
+ function setKey($key)
+ {
+ $this->key = $key;
+ $this->changed = true;
+ }
+
+ /**
+ * Sets the initialization vector. (optional)
+ *
+ * SetIV is not required when CRYPT_RIJNDAEL_MODE_ECB is being used. If not explictly set, it'll be assumed
+ * to be all zero's.
+ *
+ * @access public
+ * @param String $iv
+ */
+ function setIV($iv)
+ {
+ $this->encryptIV = $this->decryptIV = $this->iv = str_pad(substr($iv, 0, $this->block_size), $this->block_size, chr(0));
+ }
+
+ /**
+ * Sets the key length
+ *
+ * Valid key lengths are 128, 160, 192, 224, and 256. If the length is less than 128, it will be rounded up to
+ * 128. If the length is greater then 128 and invalid, it will be rounded down to the closest valid amount.
+ *
+ * @access public
+ * @param Integer $length
+ */
+ function setKeyLength($length)
+ {
+ $length >>= 5;
+ if ($length > 8) {
+ $length = 8;
+ } else if ($length < 4) {
+ $length = 4;
+ }
+ $this->Nk = $length;
+ $this->key_size = $length << 2;
+
+ $this->explicit_key_length = true;
+ $this->changed = true;
+ }
+
+ /**
+ * Sets the password.
+ *
+ * Depending on what $method is set to, setPassword()'s (optional) parameters are as follows:
+ * {@link http://en.wikipedia.org/wiki/PBKDF2 pbkdf2}:
+ * $hash, $salt, $count
+ * Set $dkLen by calling setKeyLength()
+ *
+ * @param String $password
+ * @param optional String $method
+ * @access public
+ */
+ function setPassword($password, $method = 'pbkdf2')
+ {
+ $key = '';
+
+ switch ($method) {
+ default: // 'pbkdf2'
+ list(, , $hash, $salt, $count) = func_get_args();
+ if (!isset($hash)) {
+ $hash = 'sha1';
+ }
+ // WPA and WPA use the SSID as the salt
+ if (!isset($salt)) {
+ $salt = 'phpseclib/salt';
+ }
+ // RFC2898#section-4.2 uses 1,000 iterations by default
+ // WPA and WPA2 use 4,096.
+ if (!isset($count)) {
+ $count = 1000;
+ }
+
+ if (!class_exists('Crypt_Hash')) {
+ require_once('Crypt/Hash.php');
+ }
+
+ $i = 1;
+ while (strlen($key) < $this->key_size) { // $dkLen == $this->key_size
+ //$dk.= $this->_pbkdf($password, $salt, $count, $i++);
+ $hmac = new Crypt_Hash();
+ $hmac->setHash($hash);
+ $hmac->setKey($password);
+ $f = $u = $hmac->hash($salt . pack('N', $i++));
+ for ($j = 2; $j <= $count; $j++) {
+ $u = $hmac->hash($u);
+ $f^= $u;
+ }
+ $key.= $f;
+ }
+ }
+
+ $this->setKey(substr($key, 0, $this->key_size));
+ }
+
+ /**
+ * Sets the block length
+ *
+ * Valid block lengths are 128, 160, 192, 224, and 256. If the length is less than 128, it will be rounded up to
+ * 128. If the length is greater then 128 and invalid, it will be rounded down to the closest valid amount.
+ *
+ * @access public
+ * @param Integer $length
+ */
+ function setBlockLength($length)
+ {
+ $length >>= 5;
+ if ($length > 8) {
+ $length = 8;
+ } else if ($length < 4) {
+ $length = 4;
+ }
+ $this->Nb = $length;
+ $this->block_size = $length << 2;
+ $this->changed = true;
+ }
+
+ /**
+ * Generate CTR XOR encryption key
+ *
+ * Encrypt the output of this and XOR it against the ciphertext / plaintext to get the
+ * plaintext / ciphertext in CTR mode.
+ *
+ * @see Crypt_Rijndael::decrypt()
+ * @see Crypt_Rijndael::encrypt()
+ * @access public
+ * @param Integer $length
+ * @param String $iv
+ */
+ function _generate_xor($length, &$iv)
+ {
+ $xor = '';
+ $block_size = $this->block_size;
+ $num_blocks = floor(($length + ($block_size - 1)) / $block_size);
+ for ($i = 0; $i < $num_blocks; $i++) {
+ $xor.= $iv;
+ for ($j = 4; $j <= $block_size; $j+=4) {
+ $temp = substr($iv, -$j, 4);
+ switch ($temp) {
+ case "\xFF\xFF\xFF\xFF":
+ $iv = substr_replace($iv, "\x00\x00\x00\x00", -$j, 4);
+ break;
+ case "\x7F\xFF\xFF\xFF":
+ $iv = substr_replace($iv, "\x80\x00\x00\x00", -$j, 4);
+ break 2;
+ default:
+ extract(unpack('Ncount', $temp));
+ $iv = substr_replace($iv, pack('N', $count + 1), -$j, 4);
+ break 2;
+ }
+ }
+ }
+
+ return $xor;
+ }
+
+ /**
+ * Encrypts a message.
+ *
+ * $plaintext will be padded with additional bytes such that it's length is a multiple of the block size. Other Rjindael
+ * implementations may or may not pad in the same manner. Other common approaches to padding and the reasons why it's
+ * necessary are discussed in the following
+ * URL:
+ *
+ * {@link http://www.di-mgt.com.au/cryptopad.html http://www.di-mgt.com.au/cryptopad.html}
+ *
+ * An alternative to padding is to, separately, send the length of the file. This is what SSH, in fact, does.
+ * strlen($plaintext) will still need to be a multiple of 8, however, arbitrary values can be added to make it that
+ * length.
+ *
+ * @see Crypt_Rijndael::decrypt()
+ * @access public
+ * @param String $plaintext
+ */
+ function encrypt($plaintext)
+ {
+ $this->_setup();
+ if ($this->paddable) {
+ $plaintext = $this->_pad($plaintext);
+ }
+
+ $block_size = $this->block_size;
+ $buffer = &$this->enbuffer;
+ $continuousBuffer = $this->continuousBuffer;
+ $ciphertext = '';
+ switch ($this->mode) {
+ case CRYPT_RIJNDAEL_MODE_ECB:
+ for ($i = 0; $i < strlen($plaintext); $i+=$block_size) {
+ $ciphertext.= $this->_encryptBlock(substr($plaintext, $i, $block_size));
+ }
+ break;
+ case CRYPT_RIJNDAEL_MODE_CBC:
+ $xor = $this->encryptIV;
+ for ($i = 0; $i < strlen($plaintext); $i+=$block_size) {
+ $block = substr($plaintext, $i, $block_size);
+ $block = $this->_encryptBlock($block ^ $xor);
+ $xor = $block;
+ $ciphertext.= $block;
+ }
+ if ($this->continuousBuffer) {
+ $this->encryptIV = $xor;
+ }
+ break;
+ case CRYPT_RIJNDAEL_MODE_CTR:
+ $xor = $this->encryptIV;
+ if (!empty($buffer['encrypted'])) {
+ for ($i = 0; $i < strlen($plaintext); $i+=$block_size) {
+ $block = substr($plaintext, $i, $block_size);
+ $buffer['encrypted'].= $this->_encryptBlock($this->_generate_xor($block_size, $xor));
+ $key = $this->_string_shift($buffer['encrypted'], $block_size);
+ $ciphertext.= $block ^ $key;
+ }
+ } else {
+ for ($i = 0; $i < strlen($plaintext); $i+=$block_size) {
+ $block = substr($plaintext, $i, $block_size);
+ $key = $this->_encryptBlock($this->_generate_xor($block_size, $xor));
+ $ciphertext.= $block ^ $key;
+ }
+ }
+ if ($this->continuousBuffer) {
+ $this->encryptIV = $xor;
+ if ($start = strlen($plaintext) % $block_size) {
+ $buffer['encrypted'] = substr($key, $start) . $buffer['encrypted'];
+ }
+ }
+ break;
+ case CRYPT_RIJNDAEL_MODE_CFB:
+ if (!empty($buffer['xor'])) {
+ $ciphertext = $plaintext ^ $buffer['xor'];
+ $iv = $buffer['encrypted'] . $ciphertext;
+ $start = strlen($ciphertext);
+ $buffer['encrypted'].= $ciphertext;
+ $buffer['xor'] = substr($buffer['xor'], strlen($ciphertext));
+ } else {
+ $ciphertext = '';
+ $iv = $this->encryptIV;
+ $start = 0;
+ }
+
+ for ($i = $start; $i < strlen($plaintext); $i+=$block_size) {
+ $block = substr($plaintext, $i, $block_size);
+ $xor = $this->_encryptBlock($iv);
+ $iv = $block ^ $xor;
+ if ($continuousBuffer && strlen($iv) != $block_size) {
+ $buffer = array(
+ 'encrypted' => $iv,
+ 'xor' => substr($xor, strlen($iv))
+ );
+ }
+ $ciphertext.= $iv;
+ }
+
+ if ($this->continuousBuffer) {
+ $this->encryptIV = $iv;
+ }
+ break;
+ case CRYPT_RIJNDAEL_MODE_OFB:
+ $xor = $this->encryptIV;
+ if (strlen($buffer)) {
+ for ($i = 0; $i < strlen($plaintext); $i+=$block_size) {
+ $xor = $this->_encryptBlock($xor);
+ $buffer.= $xor;
+ $key = $this->_string_shift($buffer, $block_size);
+ $ciphertext.= substr($plaintext, $i, $block_size) ^ $key;
+ }
+ } else {
+ for ($i = 0; $i < strlen($plaintext); $i+=$block_size) {
+ $xor = $this->_encryptBlock($xor);
+ $ciphertext.= substr($plaintext, $i, $block_size) ^ $xor;
+ }
+ $key = $xor;
+ }
+ if ($this->continuousBuffer) {
+ $this->encryptIV = $xor;
+ if ($start = strlen($plaintext) % $block_size) {
+ $buffer = substr($key, $start) . $buffer;
+ }
+ }
+ }
+
+ return $ciphertext;
+ }
+
+ /**
+ * Decrypts a message.
+ *
+ * If strlen($ciphertext) is not a multiple of the block size, null bytes will be added to the end of the string until
+ * it is.
+ *
+ * @see Crypt_Rijndael::encrypt()
+ * @access public
+ * @param String $ciphertext
+ */
+ function decrypt($ciphertext)
+ {
+ $this->_setup();
+
+ if ($this->paddable) {
+ // we pad with chr(0) since that's what mcrypt_generic does. to quote from http://php.net/function.mcrypt-generic :
+ // "The data is padded with "\0" to make sure the length of the data is n * blocksize."
+ $ciphertext = str_pad($ciphertext, strlen($ciphertext) + ($this->block_size - strlen($ciphertext) % $this->block_size) % $this->block_size, chr(0));
+ }
+
+ $block_size = $this->block_size;
+ $buffer = &$this->debuffer;
+ $continuousBuffer = $this->continuousBuffer;
+ $plaintext = '';
+ switch ($this->mode) {
+ case CRYPT_RIJNDAEL_MODE_ECB:
+ for ($i = 0; $i < strlen($ciphertext); $i+=$block_size) {
+ $plaintext.= $this->_decryptBlock(substr($ciphertext, $i, $block_size));
+ }
+ break;
+ case CRYPT_RIJNDAEL_MODE_CBC:
+ $xor = $this->decryptIV;
+ for ($i = 0; $i < strlen($ciphertext); $i+=$block_size) {
+ $block = substr($ciphertext, $i, $block_size);
+ $plaintext.= $this->_decryptBlock($block) ^ $xor;
+ $xor = $block;
+ }
+ if ($this->continuousBuffer) {
+ $this->decryptIV = $xor;
+ }
+ break;
+ case CRYPT_RIJNDAEL_MODE_CTR:
+ $xor = $this->decryptIV;
+ if (!empty($buffer['ciphertext'])) {
+ for ($i = 0; $i < strlen($ciphertext); $i+=$block_size) {
+ $block = substr($ciphertext, $i, $block_size);
+ $buffer['ciphertext'].= $this->_encryptBlock($this->_generate_xor($block_size, $xor));
+ $key = $this->_string_shift($buffer['ciphertext'], $block_size);
+ $plaintext.= $block ^ $key;
+ }
+ } else {
+ for ($i = 0; $i < strlen($ciphertext); $i+=$block_size) {
+ $block = substr($ciphertext, $i, $block_size);
+ $key = $this->_encryptBlock($this->_generate_xor($block_size, $xor));
+ $plaintext.= $block ^ $key;
+ }
+ }
+ if ($this->continuousBuffer) {
+ $this->decryptIV = $xor;
+ if ($start = strlen($ciphertext) % $block_size) {
+ $buffer['ciphertext'] = substr($key, $start) . $buffer['encrypted'];
+ }
+ }
+ break;
+ case CRYPT_RIJNDAEL_MODE_CFB:
+ if (!empty($buffer['ciphertext'])) {
+ $plaintext = $ciphertext ^ substr($this->decryptIV, strlen($buffer['ciphertext']));
+ $buffer['ciphertext'].= substr($ciphertext, 0, strlen($plaintext));
+ if (strlen($buffer['ciphertext']) == $block_size) {
+ $xor = $this->_encryptBlock($buffer['ciphertext']);
+ $buffer['ciphertext'] = '';
+ }
+ $start = strlen($plaintext);
+ $block = $this->decryptIV;
+ } else {
+ $plaintext = '';
+ $xor = $this->_encryptBlock($this->decryptIV);
+ $start = 0;
+ }
+
+ for ($i = $start; $i < strlen($ciphertext); $i+=$block_size) {
+ $block = substr($ciphertext, $i, $block_size);
+ $plaintext.= $block ^ $xor;
+ if ($continuousBuffer && strlen($block) != $block_size) {
+ $buffer['ciphertext'].= $block;
+ $block = $xor;
+ } else if (strlen($block) == $block_size) {
+ $xor = $this->_encryptBlock($block);
+ }
+ }
+ if ($this->continuousBuffer) {
+ $this->decryptIV = $block;
+ }
+ break;
+ case CRYPT_RIJNDAEL_MODE_OFB:
+ $xor = $this->decryptIV;
+ if (strlen($buffer)) {
+ for ($i = 0; $i < strlen($ciphertext); $i+=$block_size) {
+ $xor = $this->_encryptBlock($xor);
+ $buffer.= $xor;
+ $key = $this->_string_shift($buffer, $block_size);
+ $plaintext.= substr($ciphertext, $i, $block_size) ^ $key;
+ }
+ } else {
+ for ($i = 0; $i < strlen($ciphertext); $i+=$block_size) {
+ $xor = $this->_encryptBlock($xor);
+ $plaintext.= substr($ciphertext, $i, $block_size) ^ $xor;
+ }
+ $key = $xor;
+ }
+ if ($this->continuousBuffer) {
+ $this->decryptIV = $xor;
+ if ($start = strlen($ciphertext) % $block_size) {
+ $buffer = substr($key, $start) . $buffer;
+ }
+ }
+ }
+
+ return $this->paddable ? $this->_unpad($plaintext) : $plaintext;
+ }
+
+ /**
+ * Encrypts a block
+ *
+ * @access private
+ * @param String $in
+ * @return String
+ */
+ function _encryptBlock($in)
+ {
+ $state = array();
+ $words = unpack('N*word', $in);
+
+ $w = $this->w;
+ $t0 = $this->t0;
+ $t1 = $this->t1;
+ $t2 = $this->t2;
+ $t3 = $this->t3;
+ $Nb = $this->Nb;
+ $Nr = $this->Nr;
+ $c = $this->c;
+
+ // addRoundKey
+ $i = 0;
+ foreach ($words as $word) {
+ $state[] = $word ^ $w[0][$i++];
+ }
+
+ // fips-197.pdf#page=19, "Figure 5. Pseudo Code for the Cipher", states that this loop has four components -
+ // subBytes, shiftRows, mixColumns, and addRoundKey. fips-197.pdf#page=30, "Implementation Suggestions Regarding
+ // Various Platforms" suggests that performs enhanced implementations are described in Rijndael-ammended.pdf.
+ // Rijndael-ammended.pdf#page=20, "Implementation aspects / 32-bit processor", discusses such an optimization.
+ // Unfortunately, the description given there is not quite correct. Per aes.spec.v316.pdf#page=19 [1],
+ // equation (7.4.7) is supposed to use addition instead of subtraction, so we'll do that here, as well.
+
+ // [1] http://fp.gladman.plus.com/cryptography_technology/rijndael/aes.spec.v316.pdf
+ $temp = array();
+ for ($round = 1; $round < $Nr; $round++) {
+ $i = 0; // $c[0] == 0
+ $j = $c[1];
+ $k = $c[2];
+ $l = $c[3];
+
+ while ($i < $this->Nb) {
+ $temp[$i] = $t0[$state[$i] & 0xFF000000] ^
+ $t1[$state[$j] & 0x00FF0000] ^
+ $t2[$state[$k] & 0x0000FF00] ^
+ $t3[$state[$l] & 0x000000FF] ^
+ $w[$round][$i];
+ $i++;
+ $j = ($j + 1) % $Nb;
+ $k = ($k + 1) % $Nb;
+ $l = ($l + 1) % $Nb;
+ }
+
+ for ($i = 0; $i < $Nb; $i++) {
+ $state[$i] = $temp[$i];
+ }
+ }
+
+ // subWord
+ for ($i = 0; $i < $Nb; $i++) {
+ $state[$i] = $this->_subWord($state[$i]);
+ }
+
+ // shiftRows + addRoundKey
+ $i = 0; // $c[0] == 0
+ $j = $c[1];
+ $k = $c[2];
+ $l = $c[3];
+ while ($i < $this->Nb) {
+ $temp[$i] = ($state[$i] & 0xFF000000) ^
+ ($state[$j] & 0x00FF0000) ^
+ ($state[$k] & 0x0000FF00) ^
+ ($state[$l] & 0x000000FF) ^
+ $w[$Nr][$i];
+ $i++;
+ $j = ($j + 1) % $Nb;
+ $k = ($k + 1) % $Nb;
+ $l = ($l + 1) % $Nb;
+ }
+ $state = $temp;
+
+ array_unshift($state, 'N*');
+
+ return call_user_func_array('pack', $state);
+ }
+
+ /**
+ * Decrypts a block
+ *
+ * @access private
+ * @param String $in
+ * @return String
+ */
+ function _decryptBlock($in)
+ {
+ $state = array();
+ $words = unpack('N*word', $in);
+
+ $num_states = count($state);
+ $dw = $this->dw;
+ $dt0 = $this->dt0;
+ $dt1 = $this->dt1;
+ $dt2 = $this->dt2;
+ $dt3 = $this->dt3;
+ $Nb = $this->Nb;
+ $Nr = $this->Nr;
+ $c = $this->c;
+
+ // addRoundKey
+ $i = 0;
+ foreach ($words as $word) {
+ $state[] = $word ^ $dw[$Nr][$i++];
+ }
+
+ $temp = array();
+ for ($round = $Nr - 1; $round > 0; $round--) {
+ $i = 0; // $c[0] == 0
+ $j = $Nb - $c[1];
+ $k = $Nb - $c[2];
+ $l = $Nb - $c[3];
+
+ while ($i < $Nb) {
+ $temp[$i] = $dt0[$state[$i] & 0xFF000000] ^
+ $dt1[$state[$j] & 0x00FF0000] ^
+ $dt2[$state[$k] & 0x0000FF00] ^
+ $dt3[$state[$l] & 0x000000FF] ^
+ $dw[$round][$i];
+ $i++;
+ $j = ($j + 1) % $Nb;
+ $k = ($k + 1) % $Nb;
+ $l = ($l + 1) % $Nb;
+ }
+
+ for ($i = 0; $i < $Nb; $i++) {
+ $state[$i] = $temp[$i];
+ }
+ }
+
+ // invShiftRows + invSubWord + addRoundKey
+ $i = 0; // $c[0] == 0
+ $j = $Nb - $c[1];
+ $k = $Nb - $c[2];
+ $l = $Nb - $c[3];
+
+ while ($i < $Nb) {
+ $temp[$i] = $dw[0][$i] ^
+ $this->_invSubWord(($state[$i] & 0xFF000000) |
+ ($state[$j] & 0x00FF0000) |
+ ($state[$k] & 0x0000FF00) |
+ ($state[$l] & 0x000000FF));
+ $i++;
+ $j = ($j + 1) % $Nb;
+ $k = ($k + 1) % $Nb;
+ $l = ($l + 1) % $Nb;
+ }
+
+ $state = $temp;
+
+ array_unshift($state, 'N*');
+
+ return call_user_func_array('pack', $state);
+ }
+
+ /**
+ * Setup Rijndael
+ *
+ * Validates all the variables and calculates $Nr - the number of rounds that need to be performed - and $w - the key
+ * key schedule.
+ *
+ * @access private
+ */
+ function _setup()
+ {
+ // Each number in $rcon is equal to the previous number multiplied by two in Rijndael's finite field.
+ // See http://en.wikipedia.org/wiki/Finite_field_arithmetic#Multiplicative_inverse
+ static $rcon = array(0,
+ 0x01000000, 0x02000000, 0x04000000, 0x08000000, 0x10000000,
+ 0x20000000, 0x40000000, 0x80000000, 0x1B000000, 0x36000000,
+ 0x6C000000, 0xD8000000, 0xAB000000, 0x4D000000, 0x9A000000,
+ 0x2F000000, 0x5E000000, 0xBC000000, 0x63000000, 0xC6000000,
+ 0x97000000, 0x35000000, 0x6A000000, 0xD4000000, 0xB3000000,
+ 0x7D000000, 0xFA000000, 0xEF000000, 0xC5000000, 0x91000000
+ );
+
+ if (!$this->changed) {
+ return;
+ }
+
+ if (!$this->explicit_key_length) {
+ // we do >> 2, here, and not >> 5, as we do above, since strlen($this->key) tells us the number of bytes - not bits
+ $length = strlen($this->key) >> 2;
+ if ($length > 8) {
+ $length = 8;
+ } else if ($length < 4) {
+ $length = 4;
+ }
+ $this->Nk = $length;
+ $this->key_size = $length << 2;
+ }
+
+ $this->key = str_pad(substr($this->key, 0, $this->key_size), $this->key_size, chr(0));
+ $this->encryptIV = $this->decryptIV = $this->iv = str_pad(substr($this->iv, 0, $this->block_size), $this->block_size, chr(0));
+
+ // see Rijndael-ammended.pdf#page=44
+ $this->Nr = max($this->Nk, $this->Nb) + 6;
+
+ // shift offsets for Nb = 5, 7 are defined in Rijndael-ammended.pdf#page=44,
+ // "Table 8: Shift offsets in Shiftrow for the alternative block lengths"
+ // shift offsets for Nb = 4, 6, 8 are defined in Rijndael-ammended.pdf#page=14,
+ // "Table 2: Shift offsets for different block lengths"
+ switch ($this->Nb) {
+ case 4:
+ case 5:
+ case 6:
+ $this->c = array(0, 1, 2, 3);
+ break;
+ case 7:
+ $this->c = array(0, 1, 2, 4);
+ break;
+ case 8:
+ $this->c = array(0, 1, 3, 4);
+ }
+
+ $key = $this->key;
+
+ $w = array_values(unpack('N*words', $key));
+
+ $length = $this->Nb * ($this->Nr + 1);
+ for ($i = $this->Nk; $i < $length; $i++) {
+ $temp = $w[$i - 1];
+ if ($i % $this->Nk == 0) {
+ // according to <http://php.net/language.types.integer>, "the size of an integer is platform-dependent".
+ // on a 32-bit machine, it's 32-bits, and on a 64-bit machine, it's 64-bits. on a 32-bit machine,
+ // 0xFFFFFFFF << 8 == 0xFFFFFF00, but on a 64-bit machine, it equals 0xFFFFFFFF00. as such, doing 'and'
+ // with 0xFFFFFFFF (or 0xFFFFFF00) on a 32-bit machine is unnecessary, but on a 64-bit machine, it is.
+ $temp = (($temp << 8) & 0xFFFFFF00) | (($temp >> 24) & 0x000000FF); // rotWord
+ $temp = $this->_subWord($temp) ^ $rcon[$i / $this->Nk];
+ } else if ($this->Nk > 6 && $i % $this->Nk == 4) {
+ $temp = $this->_subWord($temp);
+ }
+ $w[$i] = $w[$i - $this->Nk] ^ $temp;
+ }
+
+ // convert the key schedule from a vector of $Nb * ($Nr + 1) length to a matrix with $Nr + 1 rows and $Nb columns
+ // and generate the inverse key schedule. more specifically,
+ // according to <http://csrc.nist.gov/archive/aes/rijndael/Rijndael-ammended.pdf#page=23> (section 5.3.3),
+ // "The key expansion for the Inverse Cipher is defined as follows:
+ // 1. Apply the Key Expansion.
+ // 2. Apply InvMixColumn to all Round Keys except the first and the last one."
+ // also, see fips-197.pdf#page=27, "5.3.5 Equivalent Inverse Cipher"
+ $temp = array();
+ for ($i = $row = $col = 0; $i < $length; $i++, $col++) {
+ if ($col == $this->Nb) {
+ if ($row == 0) {
+ $this->dw[0] = $this->w[0];
+ } else {
+ // subWord + invMixColumn + invSubWord = invMixColumn
+ $j = 0;
+ while ($j < $this->Nb) {
+ $dw = $this->_subWord($this->w[$row][$j]);
+ $temp[$j] = $this->dt0[$dw & 0xFF000000] ^
+ $this->dt1[$dw & 0x00FF0000] ^
+ $this->dt2[$dw & 0x0000FF00] ^
+ $this->dt3[$dw & 0x000000FF];
+ $j++;
+ }
+ $this->dw[$row] = $temp;
+ }
+
+ $col = 0;
+ $row++;
+ }
+ $this->w[$row][$col] = $w[$i];
+ }
+
+ $this->dw[$row] = $this->w[$row];
+
+ $this->changed = false;
+ }
+
+ /**
+ * Performs S-Box substitutions
+ *
+ * @access private
+ */
+ function _subWord($word)
+ {
+ static $sbox0, $sbox1, $sbox2, $sbox3;
+
+ if (empty($sbox0)) {
+ $sbox0 = array(
+ 0x63, 0x7C, 0x77, 0x7B, 0xF2, 0x6B, 0x6F, 0xC5, 0x30, 0x01, 0x67, 0x2B, 0xFE, 0xD7, 0xAB, 0x76,
+ 0xCA, 0x82, 0xC9, 0x7D, 0xFA, 0x59, 0x47, 0xF0, 0xAD, 0xD4, 0xA2, 0xAF, 0x9C, 0xA4, 0x72, 0xC0,
+ 0xB7, 0xFD, 0x93, 0x26, 0x36, 0x3F, 0xF7, 0xCC, 0x34, 0xA5, 0xE5, 0xF1, 0x71, 0xD8, 0x31, 0x15,
+ 0x04, 0xC7, 0x23, 0xC3, 0x18, 0x96, 0x05, 0x9A, 0x07, 0x12, 0x80, 0xE2, 0xEB, 0x27, 0xB2, 0x75,
+ 0x09, 0x83, 0x2C, 0x1A, 0x1B, 0x6E, 0x5A, 0xA0, 0x52, 0x3B, 0xD6, 0xB3, 0x29, 0xE3, 0x2F, 0x84,
+ 0x53, 0xD1, 0x00, 0xED, 0x20, 0xFC, 0xB1, 0x5B, 0x6A, 0xCB, 0xBE, 0x39, 0x4A, 0x4C, 0x58, 0xCF,
+ 0xD0, 0xEF, 0xAA, 0xFB, 0x43, 0x4D, 0x33, 0x85, 0x45, 0xF9, 0x02, 0x7F, 0x50, 0x3C, 0x9F, 0xA8,
+ 0x51, 0xA3, 0x40, 0x8F, 0x92, 0x9D, 0x38, 0xF5, 0xBC, 0xB6, 0xDA, 0x21, 0x10, 0xFF, 0xF3, 0xD2,
+ 0xCD, 0x0C, 0x13, 0xEC, 0x5F, 0x97, 0x44, 0x17, 0xC4, 0xA7, 0x7E, 0x3D, 0x64, 0x5D, 0x19, 0x73,
+ 0x60, 0x81, 0x4F, 0xDC, 0x22, 0x2A, 0x90, 0x88, 0x46, 0xEE, 0xB8, 0x14, 0xDE, 0x5E, 0x0B, 0xDB,
+ 0xE0, 0x32, 0x3A, 0x0A, 0x49, 0x06, 0x24, 0x5C, 0xC2, 0xD3, 0xAC, 0x62, 0x91, 0x95, 0xE4, 0x79,
+ 0xE7, 0xC8, 0x37, 0x6D, 0x8D, 0xD5, 0x4E, 0xA9, 0x6C, 0x56, 0xF4, 0xEA, 0x65, 0x7A, 0xAE, 0x08,
+ 0xBA, 0x78, 0x25, 0x2E, 0x1C, 0xA6, 0xB4, 0xC6, 0xE8, 0xDD, 0x74, 0x1F, 0x4B, 0xBD, 0x8B, 0x8A,
+ 0x70, 0x3E, 0xB5, 0x66, 0x48, 0x03, 0xF6, 0x0E, 0x61, 0x35, 0x57, 0xB9, 0x86, 0xC1, 0x1D, 0x9E,
+ 0xE1, 0xF8, 0x98, 0x11, 0x69, 0xD9, 0x8E, 0x94, 0x9B, 0x1E, 0x87, 0xE9, 0xCE, 0x55, 0x28, 0xDF,
+ 0x8C, 0xA1, 0x89, 0x0D, 0xBF, 0xE6, 0x42, 0x68, 0x41, 0x99, 0x2D, 0x0F, 0xB0, 0x54, 0xBB, 0x16
+ );
+
+ $sbox1 = array();
+ $sbox2 = array();
+ $sbox3 = array();
+
+ for ($i = 0; $i < 256; $i++) {
+ $sbox1[$i << 8] = $sbox0[$i] << 8;
+ $sbox2[$i << 16] = $sbox0[$i] << 16;
+ $sbox3[$i << 24] = $sbox0[$i] << 24;
+ }
+ }
+
+ return $sbox0[$word & 0x000000FF] |
+ $sbox1[$word & 0x0000FF00] |
+ $sbox2[$word & 0x00FF0000] |
+ $sbox3[$word & 0xFF000000];
+ }
+
+ /**
+ * Performs inverse S-Box substitutions
+ *
+ * @access private
+ */
+ function _invSubWord($word)
+ {
+ static $sbox0, $sbox1, $sbox2, $sbox3;
+
+ if (empty($sbox0)) {
+ $sbox0 = array(
+ 0x52, 0x09, 0x6A, 0xD5, 0x30, 0x36, 0xA5, 0x38, 0xBF, 0x40, 0xA3, 0x9E, 0x81, 0xF3, 0xD7, 0xFB,
+ 0x7C, 0xE3, 0x39, 0x82, 0x9B, 0x2F, 0xFF, 0x87, 0x34, 0x8E, 0x43, 0x44, 0xC4, 0xDE, 0xE9, 0xCB,
+ 0x54, 0x7B, 0x94, 0x32, 0xA6, 0xC2, 0x23, 0x3D, 0xEE, 0x4C, 0x95, 0x0B, 0x42, 0xFA, 0xC3, 0x4E,
+ 0x08, 0x2E, 0xA1, 0x66, 0x28, 0xD9, 0x24, 0xB2, 0x76, 0x5B, 0xA2, 0x49, 0x6D, 0x8B, 0xD1, 0x25,
+ 0x72, 0xF8, 0xF6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xD4, 0xA4, 0x5C, 0xCC, 0x5D, 0x65, 0xB6, 0x92,
+ 0x6C, 0x70, 0x48, 0x50, 0xFD, 0xED, 0xB9, 0xDA, 0x5E, 0x15, 0x46, 0x57, 0xA7, 0x8D, 0x9D, 0x84,
+ 0x90, 0xD8, 0xAB, 0x00, 0x8C, 0xBC, 0xD3, 0x0A, 0xF7, 0xE4, 0x58, 0x05, 0xB8, 0xB3, 0x45, 0x06,
+ 0xD0, 0x2C, 0x1E, 0x8F, 0xCA, 0x3F, 0x0F, 0x02, 0xC1, 0xAF, 0xBD, 0x03, 0x01, 0x13, 0x8A, 0x6B,
+ 0x3A, 0x91, 0x11, 0x41, 0x4F, 0x67, 0xDC, 0xEA, 0x97, 0xF2, 0xCF, 0xCE, 0xF0, 0xB4, 0xE6, 0x73,
+ 0x96, 0xAC, 0x74, 0x22, 0xE7, 0xAD, 0x35, 0x85, 0xE2, 0xF9, 0x37, 0xE8, 0x1C, 0x75, 0xDF, 0x6E,
+ 0x47, 0xF1, 0x1A, 0x71, 0x1D, 0x29, 0xC5, 0x89, 0x6F, 0xB7, 0x62, 0x0E, 0xAA, 0x18, 0xBE, 0x1B,
+ 0xFC, 0x56, 0x3E, 0x4B, 0xC6, 0xD2, 0x79, 0x20, 0x9A, 0xDB, 0xC0, 0xFE, 0x78, 0xCD, 0x5A, 0xF4,
+ 0x1F, 0xDD, 0xA8, 0x33, 0x88, 0x07, 0xC7, 0x31, 0xB1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xEC, 0x5F,
+ 0x60, 0x51, 0x7F, 0xA9, 0x19, 0xB5, 0x4A, 0x0D, 0x2D, 0xE5, 0x7A, 0x9F, 0x93, 0xC9, 0x9C, 0xEF,
+ 0xA0, 0xE0, 0x3B, 0x4D, 0xAE, 0x2A, 0xF5, 0xB0, 0xC8, 0xEB, 0xBB, 0x3C, 0x83, 0x53, 0x99, 0x61,
+ 0x17, 0x2B, 0x04, 0x7E, 0xBA, 0x77, 0xD6, 0x26, 0xE1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0C, 0x7D
+ );
+
+ $sbox1 = array();
+ $sbox2 = array();
+ $sbox3 = array();
+
+ for ($i = 0; $i < 256; $i++) {
+ $sbox1[$i << 8] = $sbox0[$i] << 8;
+ $sbox2[$i << 16] = $sbox0[$i] << 16;
+ $sbox3[$i << 24] = $sbox0[$i] << 24;
+ }
+ }
+
+ return $sbox0[$word & 0x000000FF] |
+ $sbox1[$word & 0x0000FF00] |
+ $sbox2[$word & 0x00FF0000] |
+ $sbox3[$word & 0xFF000000];
+ }
+
+ /**
+ * Pad "packets".
+ *
+ * Rijndael works by encrypting between sixteen and thirty-two bytes at a time, provided that number is also a multiple
+ * of four. If you ever need to encrypt or decrypt something that isn't of the proper length, it becomes necessary to
+ * pad the input so that it is of the proper length.
+ *
+ * Padding is enabled by default. Sometimes, however, it is undesirable to pad strings. Such is the case in SSH,
+ * where "packets" are padded with random bytes before being encrypted. Unpad these packets and you risk stripping
+ * away characters that shouldn't be stripped away. (SSH knows how many bytes are added because the length is
+ * transmitted separately)
+ *
+ * @see Crypt_Rijndael::disablePadding()
+ * @access public
+ */
+ function enablePadding()
+ {
+ $this->padding = true;
+ }
+
+ /**
+ * Do not pad packets.
+ *
+ * @see Crypt_Rijndael::enablePadding()
+ * @access public
+ */
+ function disablePadding()
+ {
+ $this->padding = false;
+ }
+
+ /**
+ * Pads a string
+ *
+ * Pads a string using the RSA PKCS padding standards so that its length is a multiple of the blocksize.
+ * $block_size - (strlen($text) % $block_size) bytes are added, each of which is equal to
+ * chr($block_size - (strlen($text) % $block_size)
+ *
+ * If padding is disabled and $text is not a multiple of the blocksize, the string will be padded regardless
+ * and padding will, hence forth, be enabled.
+ *
+ * @see Crypt_Rijndael::_unpad()
+ * @access private
+ */
+ function _pad($text)
+ {
+ $length = strlen($text);
+
+ if (!$this->padding) {
+ if ($length % $this->block_size == 0) {
+ return $text;
+ } else {
+ user_error("The plaintext's length ($length) is not a multiple of the block size ({$this->block_size})", E_USER_NOTICE);
+ $this->padding = true;
+ }
+ }
+
+ $pad = $this->block_size - ($length % $this->block_size);
+
+ return str_pad($text, $length + $pad, chr($pad));
+ }
+
+ /**
+ * Unpads a string.
+ *
+ * If padding is enabled and the reported padding length is invalid the encryption key will be assumed to be wrong
+ * and false will be returned.
+ *
+ * @see Crypt_Rijndael::_pad()
+ * @access private
+ */
+ function _unpad($text)
+ {
+ if (!$this->padding) {
+ return $text;
+ }
+
+ $length = ord($text[strlen($text) - 1]);
+
+ if (!$length || $length > $this->block_size) {
+ return false;
+ }
+
+ return substr($text, 0, -$length);
+ }
+
+ /**
+ * Treat consecutive "packets" as if they are a continuous buffer.
+ *
+ * Say you have a 32-byte plaintext $plaintext. Using the default behavior, the two following code snippets
+ * will yield different outputs:
+ *
+ * <code>
+ * echo $rijndael->encrypt(substr($plaintext, 0, 16));
+ * echo $rijndael->encrypt(substr($plaintext, 16, 16));
+ * </code>
+ * <code>
+ * echo $rijndael->encrypt($plaintext);
+ * </code>
+ *
+ * The solution is to enable the continuous buffer. Although this will resolve the above discrepancy, it creates
+ * another, as demonstrated with the following:
+ *
+ * <code>
+ * $rijndael->encrypt(substr($plaintext, 0, 16));
+ * echo $rijndael->decrypt($des->encrypt(substr($plaintext, 16, 16)));
+ * </code>
+ * <code>
+ * echo $rijndael->decrypt($des->encrypt(substr($plaintext, 16, 16)));
+ * </code>
+ *
+ * With the continuous buffer disabled, these would yield the same output. With it enabled, they yield different
+ * outputs. The reason is due to the fact that the initialization vector's change after every encryption /
+ * decryption round when the continuous buffer is enabled. When it's disabled, they remain constant.
+ *
+ * Put another way, when the continuous buffer is enabled, the state of the Crypt_Rijndael() object changes after each
+ * encryption / decryption round, whereas otherwise, it'd remain constant. For this reason, it's recommended that
+ * continuous buffers not be used. They do offer better security and are, in fact, sometimes required (SSH uses them),
+ * however, they are also less intuitive and more likely to cause you problems.
+ *
+ * @see Crypt_Rijndael::disableContinuousBuffer()
+ * @access public
+ */
+ function enableContinuousBuffer()
+ {
+ $this->continuousBuffer = true;
+ }
+
+ /**
+ * Treat consecutive packets as if they are a discontinuous buffer.
+ *
+ * The default behavior.
+ *
+ * @see Crypt_Rijndael::enableContinuousBuffer()
+ * @access public
+ */
+ function disableContinuousBuffer()
+ {
+ $this->continuousBuffer = false;
+ $this->encryptIV = $this->iv;
+ $this->decryptIV = $this->iv;
+ }
+
+ /**
+ * String Shift
+ *
+ * Inspired by array_shift
+ *
+ * @param String $string
+ * @param optional Integer $index
+ * @return String
+ * @access private
+ */
+ function _string_shift(&$string, $index = 1)
+ {
+ $substr = substr($string, 0, $index);
+ $string = substr($string, $index);
+ return $substr;
+ }
+}
+
+// vim: ts=4:sw=4:et:
+// vim6: fdl=1:
+?>
diff --git a/libraries/plugin_interface.lib.php b/libraries/plugin_interface.lib.php
new file mode 100644
index 0000000000..4166702dc1
--- /dev/null
+++ b/libraries/plugin_interface.lib.php
@@ -0,0 +1,507 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Generic plugin interface.
+ *
+ * @package PhpMyAdmin
+ */
+
+/**
+ * Includes and instantiates the specified plugin type for a certain format
+ *
+ * @param string $plugin_type the type of the plugin (import, export, etc)
+ * @param string $plugin_format the format of the plugin (sql, xml, et )
+ * @param string $plugins_dir directrory with plugins
+ * @param mixed $plugin_param parameter to plugin by which they can
+ * decide whether they can work
+ *
+ * @return object|null new plugin instance
+ */
+function PMA_getPlugin(
+ $plugin_type,
+ $plugin_format,
+ $plugins_dir,
+ $plugin_param = false
+) {
+ $GLOBALS['plugin_param'] = $plugin_param;
+ $class_name = strtoupper($plugin_type[0])
+ . strtolower(substr($plugin_type, 1))
+ . strtoupper($plugin_format[0])
+ . strtolower(substr($plugin_format, 1));
+ $file = $class_name . ".class.php";
+ if (is_file($plugins_dir . $file)) {
+ include_once $plugins_dir . $file;
+ return new $class_name;
+ }
+
+ return null;
+}
+
+/**
+ * Reads all plugin information from directory $plugins_dir
+ *
+ * @param string $plugin_type the type of the plugin (import, export, etc)
+ * @param string $plugins_dir directrory with plugins
+ * @param mixed $plugin_param parameter to plugin by which they can
+ * decide whether they can work
+ *
+ * @return array list of plugin instances
+ */
+function PMA_getPlugins($plugin_type, $plugins_dir, $plugin_param)
+{
+ $GLOBALS['plugin_param'] = $plugin_param;
+ /* Scan for plugins */
+ $plugin_list = array();
+ if ($handle = @opendir($plugins_dir)) {
+ while ($file = @readdir($handle)) {
+ // In some situations, Mac OS creates a new file for each file
+ // (for example ._csv.php) so the following regexp
+ // matches a file which does not start with a dot but ends
+ // with ".php"
+ $class_type = mb_strtoupper($plugin_type[0], 'UTF-8')
+ . mb_strtolower(substr($plugin_type, 1), 'UTF-8');
+ if (is_file($plugins_dir . $file)
+ && preg_match(
+ '@^' . $class_type . '(.+)\.class\.php$@i',
+ $file,
+ $matches
+ )
+ ) {
+ $GLOBALS['skip_import'] = false;
+ include_once $plugins_dir . $file;
+ if (! $GLOBALS['skip_import']) {
+ $class_name = $class_type . $matches[1];
+ $plugin_list [] = new $class_name;
+ }
+ }
+ }
+ }
+ ksort($plugin_list);
+ return $plugin_list;
+}
+
+/**
+ * Returns locale string for $name or $name if no locale is found
+ *
+ * @param string $name for local string
+ *
+ * @return string locale string for $name
+ */
+function PMA_getString($name)
+{
+ return isset($GLOBALS[$name]) ? $GLOBALS[$name] : $name;
+}
+
+/**
+ * Returns html input tag option 'checked' if plugin $opt
+ * should be set by config or request
+ *
+ * @param string $section name of config section in
+ * $GLOBALS['cfg'][$section] for plugin
+ * @param string $opt name of option
+ *
+ * @return string hmtl input tag option 'checked'
+ */
+function PMA_pluginCheckboxCheck($section, $opt)
+{
+ // If the form is being repopulated using $_GET data, that is priority
+ if (isset($_GET[$opt])
+ || ! isset($_GET['repopulate'])
+ && ((isset($GLOBALS['timeout_passed'])
+ && $GLOBALS['timeout_passed']
+ && isset($_REQUEST[$opt]))
+ || (isset($GLOBALS['cfg'][$section][$opt])
+ && $GLOBALS['cfg'][$section][$opt]))
+ ) {
+ return ' checked="checked"';
+ }
+ return '';
+}
+
+/**
+ * Returns default value for option $opt
+ *
+ * @param string $section name of config section in
+ * $GLOBALS['cfg'][$section] for plugin
+ * @param string $opt name of option
+ *
+ * @return string default value for option $opt
+ */
+function PMA_pluginGetDefault($section, $opt)
+{
+ if (isset($_GET[$opt])) {
+ // If the form is being repopulated using $_GET data, that is priority
+ return htmlspecialchars($_GET[$opt]);
+ } elseif (isset($GLOBALS['timeout_passed'])
+ && $GLOBALS['timeout_passed']
+ && isset($_REQUEST[$opt])
+ ) {
+ return htmlspecialchars($_REQUEST[$opt]);
+ } elseif (isset($GLOBALS['cfg'][$section][$opt])) {
+ $matches = array();
+ /* Possibly replace localised texts */
+ if (preg_match_all(
+ '/(str[A-Z][A-Za-z0-9]*)/',
+ $GLOBALS['cfg'][$section][$opt],
+ $matches
+ )) {
+ $val = $GLOBALS['cfg'][$section][$opt];
+ foreach ($matches[0] as $match) {
+ if (isset($GLOBALS[$match])) {
+ $val = str_replace($match, $GLOBALS[$match], $val);
+ }
+ }
+ return htmlspecialchars($val);
+ } else {
+ return htmlspecialchars($GLOBALS['cfg'][$section][$opt]);
+ }
+ }
+ return '';
+}
+
+/**
+ * Returns html select form element for plugin choice
+ * and hidden fields denoting whether each plugin must be exported as a file
+ *
+ * @param string $section name of config section in
+ * $GLOBALS['cfg'][$section] for plugin
+ * @param string $name name of select element
+ * @param array &$list array with plugin instances
+ * @param string $cfgname name of config value, if none same as $name
+ *
+ * @return string html select tag
+ */
+function PMA_pluginGetChoice($section, $name, &$list, $cfgname = null)
+{
+ if (! isset($cfgname)) {
+ $cfgname = $name;
+ }
+ $ret = '<select id="plugins" name="' . $name . '">';
+ $default = PMA_pluginGetDefault($section, $cfgname);
+ foreach ($list as $plugin) {
+ $plugin_name = strtolower(substr(get_class($plugin), strlen($section)));
+ $ret .= '<option';
+ // If the form is being repopulated using $_GET data, that is priority
+ if (isset($_GET[$name])
+ && $plugin_name == $_GET[$name]
+ || ! isset($_GET[$name])
+ && $plugin_name == $default
+ ) {
+ $ret .= ' selected="selected"';
+ }
+
+ $properties = $plugin->getProperties();
+ $text = null;
+ if ($properties != null) {
+ $text = $properties->getText();
+ }
+ $ret .= ' value="' . $plugin_name . '">'
+ . PMA_getString($text)
+ . '</option>' . "\n";
+ }
+ $ret .= '</select>' . "\n";
+
+ // Whether each plugin has to be saved as a file
+ foreach ($list as $plugin) {
+ $plugin_name = strtolower(substr(get_class($plugin), strlen($section)));
+ $ret .= '<input type="hidden" id="force_file_' . $plugin_name
+ . '" value="';
+ $properties = $plugin->getProperties();
+ if ( ! strcmp($section, 'Import')
+ || ($properties != null && $properties->getForceFile() != null)
+ ) {
+ $ret .= 'true';
+ } else {
+ $ret .= 'false';
+ }
+ $ret .= '" />'. "\n";
+ }
+
+ return $ret;
+}
+
+/**
+ * Returns single option in a list element
+ *
+ * @param string $section name of config section in
+ * $GLOBALS['cfg'][$section] for plugin
+ * @param string $plugin_name unique plugin name
+ * @param array &$propertyGroup options property main group instance
+ * @param boolean $is_subgroup if this group is a subgroup
+ *
+ * @return string table row with option
+ */
+function PMA_pluginGetOneOption(
+ $section,
+ $plugin_name,
+ &$propertyGroup,
+ $is_subgroup = false
+) {
+ $ret = "\n";
+
+ if (! $is_subgroup) {
+ // for subgroup headers
+ if (strpos(get_class($propertyGroup), "PropertyItem")) {
+ $properties = array($propertyGroup);
+ } else {
+ // for main groups
+ $ret .= '<div class="export_sub_options" id="' . $plugin_name . '_'
+ . $propertyGroup->getName() . '">';
+
+ if (method_exists($propertyGroup, 'getText')) {
+ $text = $propertyGroup->getText();
+ }
+
+ if ($text != null) {
+ $ret .= '<h4>' . PMA_getString($text) . '</h4>';
+ }
+ $ret .= '<ul>';
+ }
+ }
+
+ if (! isset($properties)) {
+ $not_subgroup_header = true;
+ if (method_exists($propertyGroup, 'getProperties')) {
+ $properties = $propertyGroup->getProperties();
+ }
+ }
+
+ if (isset($properties)) {
+ foreach ($properties as $propertyItem) {
+ $property_class = get_class($propertyItem);
+ // if the property is a subgroup, we deal with it recursively
+ if (strpos($property_class, "Subgroup")) {
+ // for subgroups
+ // each subgroup can have a header, which may also be a form element
+ $subgroup_header = $propertyItem->getSubgroupHeader();
+ if (isset($subgroup_header)) {
+ $ret .= PMA_pluginGetOneOption(
+ $section,
+ $plugin_name,
+ $subgroup_header
+ );
+ }
+
+ $ret .= '<li class="subgroup"><ul';
+ if (isset($subgroup_header)) {
+ $ret .= ' id="ul_' . $subgroup_header->getName() . '">';
+ } else {
+ $ret .= '>';
+ }
+
+ $ret .= PMA_pluginGetOneOption(
+ $section,
+ $plugin_name,
+ $propertyItem,
+ true
+ );
+ } else {
+ // single property item
+ switch ($property_class) {
+ case "BoolPropertyItem":
+ $ret .= '<li>' . "\n";
+ $ret .= '<input type="checkbox" name="' . $plugin_name . '_'
+ . $propertyItem->getName() . '"'
+ . ' value="something" id="checkbox_' . $plugin_name . '_'
+ . $propertyItem->getName() . '"'
+ . ' '
+ . PMA_pluginCheckboxCheck(
+ $section,
+ $plugin_name . '_' . $propertyItem->getName()
+ );
+
+ if ($propertyItem->getForce() != null) {
+ // Same code is also few lines lower, update both if needed
+ $ret .= ' onclick="if (!this.checked &amp;&amp; '
+ . '(!document.getElementById(\'checkbox_' . $plugin_name
+ . '_' . $propertyItem->getForce() . '\') '
+ . '|| !document.getElementById(\'checkbox_'
+ . $plugin_name . '_' . $propertyItem->getForce()
+ . '\').checked)) '
+ . 'return false; else return true;"';
+ }
+ $ret .= ' />';
+ $ret .= '<label for="checkbox_' . $plugin_name . '_'
+ . $propertyItem->getName() . '">'
+ . PMA_getString($propertyItem->getText()) . '</label>';
+ break;
+ case "DocPropertyItem":
+ echo "DocPropertyItem";
+ break;
+ case "HiddenPropertyItem":
+ $ret .= '<li><input type="hidden" name="' . $plugin_name . '_'
+ . $propertyItem->getName() . '"'
+ . ' value="' . PMA_pluginGetDefault(
+ $section,
+ $plugin_name . '_' . $propertyItem->getName()
+ )
+ . '"' . ' /></li>';
+ break;
+ case "MessageOnlyPropertyItem":
+ $ret .= '<li>' . "\n";
+ $ret .= '<p>' . PMA_getString($propertyItem->getText()) . '</p>';
+ break;
+ case "RadioPropertyItem":
+ $default = PMA_pluginGetDefault(
+ $section,
+ $plugin_name . '_' . $propertyItem->getName()
+ );
+ foreach ($propertyItem->getValues() as $key => $val) {
+ $ret .= '<li><input type="radio" name="' . $plugin_name
+ . '_' . $propertyItem->getName() . '" value="' . $key
+ . '" id="radio_' . $plugin_name . '_'
+ . $propertyItem->getName() . '_' . $key . '"';
+ if ($key == $default) {
+ $ret .= ' checked="checked"';
+ }
+ $ret .= ' />' . '<label for="radio_' . $plugin_name . '_'
+ . $propertyItem->getName() . '_' . $key . '">'
+ . PMA_getString($val) . '</label></li>';
+ }
+ break;
+ case "SelectPropertyItem":
+ $ret .= '<li>' . "\n";
+ $ret .= '<label for="select_' . $plugin_name . '_'
+ . $propertyItem->getName() . '" class="desc">'
+ . PMA_getString($propertyItem->getText()) . '</label>';
+ $ret .= '<select name="' . $plugin_name . '_'
+ . $propertyItem->getName() . '"'
+ . ' id="select_' . $plugin_name . '_'
+ . $propertyItem->getName() . '">';
+ $default = PMA_pluginGetDefault(
+ $section,
+ $plugin_name . '_' . $propertyItem->getName()
+ );
+ foreach ($propertyItem->getValues() as $key => $val) {
+ $ret .= '<option value="' . $key . '"';
+ if ($key == $default) {
+ $ret .= ' selected="selected"';
+ }
+ $ret .= '>' . PMA_getString($val) . '</option>';
+ }
+ $ret .= '</select>';
+ break;
+ case "TextPropertyItem":
+ case "NumberPropertyItem":
+ $ret .= '<li>' . "\n";
+ $ret .= '<label for="text_' . $plugin_name . '_'
+ . $propertyItem->getName() . '" class="desc">'
+ . PMA_getString($propertyItem->getText()) . '</label>';
+ $ret .= '<input type="text" name="' . $plugin_name . '_'
+ . $propertyItem->getName() . '"'
+ . ' value="' . PMA_pluginGetDefault(
+ $section,
+ $plugin_name . '_' . $propertyItem->getName()
+ ) . '"'
+ . ' id="text_' . $plugin_name . '_'
+ . $propertyItem->getName() . '"'
+ . ($propertyItem->getSize() != null
+ ? ' size="' . $propertyItem->getSize() . '"'
+ : '')
+ . ($propertyItem->getLen() != null
+ ? ' maxlength="' . $propertyItem->getLen() . '"'
+ : '')
+ . ' />';
+ break;
+ default:;
+ }
+ }
+ }
+ }
+
+ if ($is_subgroup) {
+ // end subgroup
+ $ret .= '</ul></li>';
+ } else {
+ // end main group
+ if (! empty($not_subgroup_header)) {
+ $ret .= '</ul></div>';
+ }
+ }
+
+ if (method_exists($propertyGroup, "getDoc")) {
+ $doc = $propertyGroup->getDoc();
+ if ($doc != null) {
+ if (count($doc) == 3) {
+ $ret .= PMA_Util::showMySQLDocu(
+ $doc[1],
+ false,
+ $doc[2]
+ );
+ } elseif (count($doc) == 1) {
+ $ret .= PMA_Util::showDocu('faq', $doc[0]);
+ } else {
+ $ret .= PMA_Util::showMySQLDocu(
+ $doc[1]
+ );
+ }
+ }
+ }
+
+ // Close the list element after $doc link is displayed
+ if (isset($property_class)) {
+ if ($property_class == 'BoolPropertyItem'
+ || $property_class == 'MessageOnlyPropertyItem'
+ || $property_class == 'SelectPropertyItem'
+ || $property_class == 'TextPropertyItem'
+ ) {
+ $ret .= '</li>';
+ }
+ }
+ $ret .= "\n";
+ return $ret;
+}
+
+/**
+ * Returns html div with editable options for plugin
+ *
+ * @param string $section name of config section in $GLOBALS['cfg'][$section]
+ * @param array &$list array with plugin instances
+ *
+ * @return string html fieldset with plugin options
+ */
+function PMA_pluginGetOptions($section, &$list)
+{
+ $ret = '';
+ // Options for plugins that support them
+ foreach ($list as $plugin) {
+ $properties = $plugin->getProperties();
+ if ($properties != null) {
+ $text = $properties->getText();
+ $options = $properties->getOptions();
+ }
+
+ $plugin_name = strtolower(substr(get_class($plugin), strlen($section)));
+ $ret .= '<div id="' . $plugin_name
+ . '_options" class="format_specific_options">';
+ $ret .= '<h3>' . PMA_getString($text) . '</h3>';
+
+ $no_options = true;
+ if ($options != null && count($options) > 0) {
+ foreach ($options->getProperties()
+ as $propertyMainGroup
+ ) {
+ // check for hidden properties
+ $no_options = true;
+ foreach ($propertyMainGroup->getProperties() as $propertyItem) {
+ if (strcmp("HiddenPropertyItem", get_class($propertyItem))) {
+ $no_options = false;
+ break;
+ }
+ }
+
+ $ret .= PMA_pluginGetOneOption(
+ $section,
+ $plugin_name,
+ $propertyMainGroup
+ );
+ }
+ }
+
+ if ($no_options) {
+ $ret .= '<p>' . __('This format has no options') . '</p>';
+ }
+ $ret .= '</div>';
+ }
+ return $ret;
+}
diff --git a/libraries/plugins/AuthenticationPlugin.class.php b/libraries/plugins/AuthenticationPlugin.class.php
new file mode 100644
index 0000000000..3ddf55ee20
--- /dev/null
+++ b/libraries/plugins/AuthenticationPlugin.class.php
@@ -0,0 +1,51 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Abstract class for the authentication plugins
+ *
+ * @package PhpMyAdmin
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/* This class extends the PluginObserver class */
+require_once 'PluginObserver.class.php';
+
+/**
+ * Provides a common interface that will have to be implemented by all of the
+ * authentication plugins.
+ *
+ * @package PhpMyAdmin
+ */
+abstract class AuthenticationPlugin extends PluginObserver
+{
+ /**
+ * Displays authentication form
+ *
+ * @return boolean
+ */
+ abstract public function auth();
+
+ /**
+ * Gets advanced authentication settings
+ *
+ * @return boolean
+ */
+ abstract public function authCheck();
+
+ /**
+ * Set the user and password after last checkings if required
+ *
+ * @return boolean
+ */
+ abstract public function authSetUser();
+
+ /**
+ * User is not allowed to login to MySQL -> authentication failed
+ *
+ * @return boolean
+ */
+ abstract public function authFails();
+}
+?> \ No newline at end of file
diff --git a/libraries/plugins/ExportPlugin.class.php b/libraries/plugins/ExportPlugin.class.php
new file mode 100644
index 0000000000..ecfde15f98
--- /dev/null
+++ b/libraries/plugins/ExportPlugin.class.php
@@ -0,0 +1,206 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Abstract class for the export plugins
+ *
+ * @package PhpMyAdmin
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/* This class extends the PluginObserver class */
+require_once 'PluginObserver.class.php';
+
+/**
+ * Provides a common interface that will have to be implemented by all of the
+ * export plugins. Some of the plugins will also implement other public
+ * methods, but those are not declared here, because they are not implemented
+ * by all export plugins.
+ *
+ * @package PhpMyAdmin
+ */
+abstract class ExportPlugin extends PluginObserver
+{
+ /**
+ * ExportPluginProperties object containing
+ * the specific export plugin type properties
+ *
+ * @var ExportPluginProperties
+ */
+ protected $properties;
+
+ /**
+ * Common methods, must be overwritten by all export plugins
+ */
+
+
+ /**
+ * Outputs export header
+ *
+ * @return bool Whether it succeeded
+ */
+ abstract public function exportHeader ();
+
+ /**
+ * Outputs export footer
+ *
+ * @return bool Whether it succeeded
+ */
+ abstract public function exportFooter ();
+
+ /**
+ * Outputs database header
+ *
+ * @param string $db Database name
+ *
+ * @return bool Whether it succeeded
+ */
+ abstract public function exportDBHeader ($db);
+
+ /**
+ * Outputs database footer
+ *
+ * @param string $db Database name
+ *
+ * @return bool Whether it succeeded
+ */
+ abstract public function exportDBFooter ($db);
+
+ /**
+ * Outputs CREATE DATABASE statement
+ *
+ * @param string $db Database name
+ *
+ * @return bool Whether it succeeded
+ */
+ abstract public function exportDBCreate($db);
+
+ /**
+ * Outputs the content of a table
+ *
+ * @param string $db database name
+ * @param string $table table name
+ * @param string $crlf the end of line sequence
+ * @param string $error_url the url to go back in case of error
+ * @param string $sql_query SQL query for obtaining data
+ *
+ * @return bool Whether it succeeded
+ */
+ abstract public function exportData ($db, $table, $crlf, $error_url, $sql_query);
+
+
+ /**
+ * The following methods are used in export.php or in db_operations.php,
+ * but they are not implemented by all export plugins
+ */
+
+
+ /**
+ * Exports routines (procedures and functions)
+ *
+ * @param string $db Database
+ *
+ * @return bool Whether it succeeded
+ */
+ public function exportRoutines($db)
+ {
+ ;
+ }
+
+ /**
+ * Outputs table's structure
+ *
+ * @param string $db database name
+ * @param string $table table name
+ * @param string $crlf the end of line sequence
+ * @param string $error_url the url to go back in case of error
+ * @param string $export_mode 'create_table','triggers','create_view',
+ * 'stand_in'
+ * @param string $export_type 'server', 'database', 'table'
+ * @param bool $relation whether to include relation comments
+ * @param bool $comments whether to include the pmadb-style column comments
+ * as comments in the structure; this is deprecated
+ * but the parameter is left here because export.php
+ * calls exportStructure() also for other export
+ * types which use this parameter
+ * @param bool $mime whether to include mime comments
+ * @param bool $dates whether to include creation/update/check dates
+ *
+ * @return bool Whether it succeeded
+ */
+ public function exportStructure(
+ $db,
+ $table,
+ $crlf,
+ $error_url,
+ $export_mode,
+ $export_type,
+ $relation = false,
+ $comments = false,
+ $mime = false,
+ $dates = false
+ ) {
+ ;
+ }
+
+ /**
+ * Returns a stand-in CREATE definition to resolve view dependencies
+ *
+ * @param string $db the database name
+ * @param string $view the view name
+ * @param string $crlf the end of line sequence
+ *
+ * @return string resulting definition
+ */
+ public function getTableDefStandIn($db, $view, $crlf)
+ {
+ ;
+ }
+
+ /**
+ * Outputs triggers
+ *
+ * @param string $db database name
+ * @param string $table table name
+ *
+ * @return string Formatted triggers list
+ */
+ protected function getTriggers($db, $table)
+ {
+ ;
+ }
+
+ /**
+ * Initialize the specific variables for each export plugin
+ *
+ * @return void
+ */
+ protected function initSpecificVariables()
+ {
+ ;
+ }
+
+
+ /* ~~~~~~~~~~~~~~~~~~~~ Getters and Setters ~~~~~~~~~~~~~~~~~~~~ */
+
+
+ /**
+ * Gets the export specific format plugin properties
+ *
+ * @return array
+ */
+ public function getProperties()
+ {
+ return $this->properties;
+ }
+
+ /**
+ * Sets the export plugins properties and is implemented by each export
+ * plugin
+ *
+ * @return void
+ */
+ abstract protected function setProperties();
+}
+?> \ No newline at end of file
diff --git a/libraries/plugins/ImportPlugin.class.php b/libraries/plugins/ImportPlugin.class.php
new file mode 100644
index 0000000000..4ef4deb460
--- /dev/null
+++ b/libraries/plugins/ImportPlugin.class.php
@@ -0,0 +1,59 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Abstract class for the import plugins
+ *
+ * @package PhpMyAdmin
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/* This class extends the PluginObserver class */
+require_once 'PluginObserver.class.php';
+
+/**
+ * Provides a common interface that will have to be implemented by all of the
+ * import plugins.
+ *
+ * @package PhpMyAdmin
+ */
+abstract class ImportPlugin extends PluginObserver
+{
+ /**
+ * ImportPluginProperties object containing the import plugin properties
+ *
+ * @var ImportPluginProperties
+ */
+ protected $properties;
+
+ /**
+ * Handles the whole import logic
+ *
+ * @return void
+ */
+ abstract public function doImport();
+
+
+ /* ~~~~~~~~~~~~~~~~~~~~ Getters and Setters ~~~~~~~~~~~~~~~~~~~~ */
+
+
+ /**
+ * Gets the import specific format plugin properties
+ *
+ * @return array
+ */
+ public function getProperties()
+ {
+ return $this->properties;
+ }
+
+ /**
+ * Sets the export plugins properties and is implemented by each import
+ * plugin
+ *
+ * @return void
+ */
+ abstract protected function setProperties();
+}
+?> \ No newline at end of file
diff --git a/libraries/plugins/PluginManager.class.php b/libraries/plugins/PluginManager.class.php
new file mode 100644
index 0000000000..3485981944
--- /dev/null
+++ b/libraries/plugins/PluginManager.class.php
@@ -0,0 +1,132 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * The PluginManager class is used alongside PluginObserver to implement
+ * the Observer Design Pattern.
+ *
+ * @package PhpMyAdmin
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ * This class implements the SplSubject interface
+ *
+ * @todo implement all methods
+ * @package PhpMyAdmin
+ * @link http://php.net/manual/en/class.splsubject.php
+ *
+ */
+class PluginManager implements SplSubject
+{
+ /**
+ * Contains a list with all the plugins that attach to it
+ *
+ * @var SplObjectStorage
+ */
+ private $_storage;
+
+ /**
+ * Contains information about the current plugin state
+ *
+ * @var string
+ */
+ private $_status;
+
+ /**
+ * Constructor
+ * Initializes $_storage with an empty SplObjectStorage
+ */
+ public function __construct()
+ {
+ $this->_storage = new SplObjectStorage();
+ }
+
+ /**
+ * Attaches an SplObserver so that it can be notified of updates
+ *
+ * @param SplObserver $observer The SplObserver to attach
+ *
+ * @return void
+ */
+ function attach (SplObserver $observer )
+ {
+ $this->_storage->attach($observer);
+ }
+
+ /**
+ * Detaches an observer from the subject to no longer notify it of updates
+ *
+ * @param SplObserver $observer The SplObserver to detach
+ *
+ * @return void
+ */
+ function detach (SplObserver $observer)
+ {
+ $this->_storage->detach($observer);
+ }
+
+ /**
+ * It is called after setStatus() was run by a certain plugin, and has
+ * the role of sending a notification to all of the plugins in $_storage,
+ * by calling the update() method for each of them.
+ *
+ * @todo implement
+ * @return void
+ */
+ function notify ()
+ {
+ }
+
+ /* ~~~~~~~~~~~~~~~~~~~~ Getters and Setters ~~~~~~~~~~~~~~~~~~~~ */
+
+ /**
+ * Gets the list with all the plugins that attach to it
+ *
+ * @return SplObjectStorage
+ */
+ public function getStorage()
+ {
+ return $this->_storage;
+ }
+
+ /**
+ * Setter for $_storage
+ *
+ * @param SplObjectStorage $_storage the list with all the plugins that
+ * attach to it
+ *
+ * @return void
+ */
+ public function setStorage($_storage)
+ {
+ $this->_storage = $_storage;
+ }
+
+ /**
+ * Gets the information about the current plugin state
+ * It is called by all the plugins in $_storage in their update() method
+ *
+ * @return string
+ */
+ public function getStatus()
+ {
+ return $this->_status;
+ }
+
+ /**
+ * Setter for $_status
+ * If a plugin changes its status, this has to be remembered in order to
+ * notify the rest of the plugins that they should update
+ *
+ * @param string $_status contains information about the current plugin state
+ *
+ * @return void
+ */
+ public function setStatus($_status)
+ {
+ $this->_status = $_status;
+ }
+}
+?> \ No newline at end of file
diff --git a/libraries/plugins/PluginObserver.class.php b/libraries/plugins/PluginObserver.class.php
new file mode 100644
index 0000000000..82a2fdc341
--- /dev/null
+++ b/libraries/plugins/PluginObserver.class.php
@@ -0,0 +1,91 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * The PluginObserver class is used alongside PluginManager to implement
+ * the Observer Design Pattern.
+ *
+ * @package PhpMyAdmin
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/* Each PluginObserver instance contains a PluginManager instance */
+require_once 'PluginManager.class.php';
+
+/**
+ * This class implements the SplObserver interface
+ *
+ * @package PhpMyAdmin
+ * @link http://php.net/manual/en/class.splobserver.php
+ */
+abstract class PluginObserver implements SplObserver
+{
+ /**
+ * PluginManager instance that contains a list with all the observer
+ * plugins that attach to it
+ *
+ * @var PluginManager
+ */
+ private $_pluginManager;
+
+ /**
+ * Constructor
+ *
+ * @param PluginManager $pluginManager The Plugin Manager instance
+ */
+ public function __construct($pluginManager)
+ {
+ $this->_pluginManager = $pluginManager;
+ }
+
+ /**
+ * This method is called when any PluginManager to which the observer
+ * is attached calls PluginManager::notify()
+ *
+ * TODO Declare this function abstract, removing its body,
+ * as soon as we drop support for PHP 5.3.x.
+ * See bug #3625
+ *
+ * @param SplSubject $subject The PluginManager notifying the observer
+ * of an update.
+ *
+ * @return void
+ *
+ * @throws Exception
+ */
+ public function update (SplSubject $subject)
+ {
+ throw new Exception(
+ 'PluginObserver::update must be overridden in child classes.'
+ );
+ }
+
+ /* ~~~~~~~~~~~~~~~~~~~~ Getters and Setters ~~~~~~~~~~~~~~~~~~~~ */
+
+
+ /**
+ * Gets the PluginManager instance that contains the list with all the
+ * plugins that attached to it
+ *
+ * @return PluginManager
+ */
+ public function getPluginManager()
+ {
+ return $this->_pluginManager;
+ }
+
+ /**
+ * Setter for $_pluginManager
+ *
+ * @param PluginManager $_pluginManager the private instance that it will
+ * attach to
+ *
+ * @return void
+ */
+ public function setPluginManager($_pluginManager)
+ {
+ $this->_pluginManager = $_pluginManager;
+ }
+}
+?>
diff --git a/libraries/plugins/TransformationsInterface.int.php b/libraries/plugins/TransformationsInterface.int.php
new file mode 100644
index 0000000000..1bcb8af450
--- /dev/null
+++ b/libraries/plugins/TransformationsInterface.int.php
@@ -0,0 +1,49 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Interface for the transformations plugins
+ *
+ * @package PhpMyAdmin
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ * Provides a common interface that will have to be implemented by all of the
+ * transformations plugins.
+ *
+ * @package PhpMyAdmin
+ */
+interface TransformationsInterface
+{
+ /**
+ * Gets the transformation description
+ *
+ * @return string
+ */
+ public static function getInfo();
+
+ /**
+ * Gets the specific MIME type
+ *
+ * @return string
+ */
+ public static function getMIMEType();
+
+ /**
+ * Gets the specific MIME subtype
+ *
+ * @return string
+ */
+ public static function getMIMESubtype();
+
+ /**
+ * Gets the transformation name of the specific plugin
+ *
+ * @return string
+ */
+ public static function getName();
+}
+
+?>
diff --git a/libraries/plugins/TransformationsPlugin.class.php b/libraries/plugins/TransformationsPlugin.class.php
new file mode 100644
index 0000000000..02ed816bec
--- /dev/null
+++ b/libraries/plugins/TransformationsPlugin.class.php
@@ -0,0 +1,51 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Abstract class for the transformations plugins
+ *
+ * @package PhpMyAdmin
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/* It extends the PluginObserver abstract class */
+require_once 'PluginObserver.class.php';
+/* It also implements the transformations interface */
+require_once 'TransformationsInterface.int.php';
+
+/**
+ * Extends PluginObserver and provides a common interface that will have to
+ * be implemented by all of the transformations plugins.
+ *
+ * @package PhpMyAdmin
+ */
+abstract class TransformationsPlugin extends PluginObserver
+ implements TransformationsInterface
+{
+ /**
+ * Does the actual work of each specific transformations plugin.
+ *
+ * @param array $options transformation options
+ *
+ * @return void
+ */
+ public function applyTransformationNoWrap($options = array())
+ {
+ ;
+ }
+
+ /**
+ * Does the actual work of each specific transformations plugin.
+ *
+ * @param string $buffer text to be transformed
+ * @param array $options transformation options
+ * @param string $meta meta information
+ *
+ * @return void
+ */
+ abstract public function applyTransformation(
+ $buffer, $options = array(), $meta = ''
+ );
+}
+?>
diff --git a/libraries/plugins/UploadInterface.int.php b/libraries/plugins/UploadInterface.int.php
new file mode 100644
index 0000000000..ab660d610d
--- /dev/null
+++ b/libraries/plugins/UploadInterface.int.php
@@ -0,0 +1,36 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Interface for the import->upload plugins
+ *
+ * @package PhpMyAdmin
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ * Provides a common interface that will have to implemented by all of the
+ * import->upload plugins.
+ *
+ * @package PhpMyAdmin
+ */
+interface UploadInterface
+{
+ /**
+ * Gets the specific upload ID Key
+ *
+ * @return string ID Key
+ */
+ public static function getIdKey();
+
+ /**
+ * Returns upload status.
+ *
+ * @param string $id upload id
+ *
+ * @return array|null
+ */
+ public static function getUploadStatus($id);
+}
+?> \ No newline at end of file
diff --git a/libraries/plugins/auth/AuthenticationConfig.class.php b/libraries/plugins/auth/AuthenticationConfig.class.php
new file mode 100644
index 0000000000..6f7e4e45b9
--- /dev/null
+++ b/libraries/plugins/auth/AuthenticationConfig.class.php
@@ -0,0 +1,174 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Config Authentication plugin for phpMyAdmin
+ *
+ * @package PhpMyAdmin-Authentication
+ * @subpackage Config
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/* Get the authentication interface */
+require_once 'libraries/plugins/AuthenticationPlugin.class.php';
+
+/**
+ * Handles the config authentication method
+ *
+ * @package PhpMyAdmin-Authentication
+ */
+class AuthenticationConfig extends AuthenticationPlugin
+{
+ /**
+ * Displays authentication form
+ *
+ * @return boolean always true
+ */
+ public function auth()
+ {
+ return true;
+ }
+
+ /**
+ * Gets advanced authentication settings
+ *
+ * @return boolean always true
+ */
+ public function authCheck()
+ {
+ return true;
+ }
+
+ /**
+ * Set the user and password after last checkings if required
+ *
+ * @return boolean always true
+ */
+ public function authSetUser()
+ {
+ return true;
+ }
+
+ /**
+ * User is not allowed to login to MySQL -> authentication failed
+ *
+ * @global string the MySQL error message PHP returns
+ * @global string the connection type (persistent or not)
+ * @global string the MySQL server port to use
+ * @global string the MySQL socket port to use
+ * @global array the current server settings
+ * @global string the font face to use in case of failure
+ * @global string the default font size to use in case of failure
+ * @global string the big font size to use in case of failure
+ * @global boolean tell the "PMA_mysqlDie()" function headers have been
+ * sent
+ *
+ * @return boolean always true (no return indeed)
+ */
+ public function authFails()
+ {
+ $conn_error = $GLOBALS['dbi']->getError();
+ if (! $conn_error) {
+ $conn_error = __('Cannot connect: invalid settings.');
+ }
+
+ /* HTML header */
+ $response = PMA_Response::getInstance();
+ $response->getFooter()->setMinimal();
+ $header = $response->getHeader();
+ $header->setBodyId('loginform');
+ $header->setTitle(__('Access denied'));
+ $header->disableMenu();
+ echo '<br /><br />
+ <center>
+ <h1>';
+ echo sprintf(__('Welcome to %s'), ' phpMyAdmin ');
+ echo '</h1>
+ </center>
+ <br />
+ <table cellpadding="0" cellspacing="3" style="margin: 0 auto" width="80%">
+ <tr>
+ <td>';
+ if (isset($GLOBALS['allowDeny_forbidden'])
+ && $GLOBALS['allowDeny_forbidden']
+ ) {
+ trigger_error(__('Access denied'), E_USER_NOTICE);
+ } else {
+ // Check whether user has configured something
+ if ($GLOBALS['PMA_Config']->source_mtime == 0) {
+ echo '<p>' . sprintf(
+ __(
+ 'You probably did not create a configuration file.'
+ . ' You might want to use the %1$ssetup script%2$s to'
+ . ' create one.'
+ ),
+ '<a href="setup/">',
+ '</a>'
+ ) . '</p>' . "\n";
+ } elseif (! isset($GLOBALS['errno'])
+ || (isset($GLOBALS['errno']) && $GLOBALS['errno'] != 2002)
+ && $GLOBALS['errno'] != 2003
+ ) {
+ // if we display the "Server not responding" error, do not confuse
+ // users by telling them they have a settings problem
+ // (note: it's true that they could have a badly typed host name,
+ // but anyway the current message tells that the server
+ // rejected the connection, which is not really what happened)
+ // 2002 is the error given by mysqli
+ // 2003 is the error given by mysql
+ trigger_error(
+ __(
+ 'phpMyAdmin tried to connect to the MySQL server, and the'
+ . ' server rejected the connection. You should check the'
+ . ' host, username and password in your configuration and'
+ . ' make sure that they correspond to the information given'
+ . ' by the administrator of the MySQL server.'
+ ), E_USER_WARNING
+ );
+ }
+ echo PMA_Util::mysqlDie(
+ $conn_error, '', true, '', false
+ );
+ }
+ $GLOBALS['error_handler']->dispUserErrors();
+ echo '</td>
+ </tr>
+ <tr>
+ <td>' . "\n";
+ echo '<a href="'
+ . $GLOBALS['cfg']['DefaultTabServer']
+ . PMA_URL_getCommon(array()) . '" class="button disableAjax">'
+ . __('Retry to connect')
+ . '</a>' . "\n";
+ echo '</td>
+ </tr>' . "\n";
+ if (count($GLOBALS['cfg']['Servers']) > 1) {
+ // offer a chance to login to other servers if the current one failed
+ include_once './libraries/select_server.lib.php';
+ echo '<tr>' . "\n";
+ echo ' <td>' . "\n";
+ echo PMA_selectServer(true, true);
+ echo ' </td>' . "\n";
+ echo '</tr>' . "\n";
+ }
+ echo '</table>' . "\n";
+ if (!defined('TESTSUITE')) {
+ exit;
+ }
+ return true;
+ }
+
+ /**
+ * This method is called when any PluginManager to which the observer
+ * is attached calls PluginManager::notify()
+ *
+ * @param SplSubject $subject The PluginManager notifying the observer
+ * of an update.
+ *
+ * @return void
+ */
+ public function update (SplSubject $subject)
+ {
+ }
+}
diff --git a/libraries/plugins/auth/AuthenticationCookie.class.php b/libraries/plugins/auth/AuthenticationCookie.class.php
new file mode 100644
index 0000000000..6d326c3da8
--- /dev/null
+++ b/libraries/plugins/auth/AuthenticationCookie.class.php
@@ -0,0 +1,807 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Cookie Authentication plugin for phpMyAdmin
+ *
+ * @package PhpMyAdmin-Authentication
+ * @subpackage Cookie
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/* Get the authentication interface */
+require_once 'libraries/plugins/AuthenticationPlugin.class.php';
+
+/**
+ * Remember where to redirect the user
+ * in case of an expired session.
+ */
+if (! empty($_REQUEST['target'])) {
+ $GLOBALS['target'] = $_REQUEST['target'];
+} else if (PMA_getenv('SCRIPT_NAME')) {
+ $GLOBALS['target'] = basename(PMA_getenv('SCRIPT_NAME'));
+}
+
+/**
+ * Swekey authentication functions.
+ */
+require './libraries/plugins/auth/swekey/swekey.auth.lib.php';
+
+/**
+ * Initialization
+ * Store the initialization vector because it will be needed for
+ * further decryption. I don't think necessary to have one iv
+ * per server so I don't put the server number in the cookie name.
+ */
+if (function_exists('mcrypt_encrypt')) {
+ if (empty($_COOKIE['pma_mcrypt_iv'])
+ || ! ($iv = base64_decode($_COOKIE['pma_mcrypt_iv'], true))
+ ) {
+ srand((double) microtime() * 1000000);
+ $td = mcrypt_module_open(MCRYPT_BLOWFISH, '', MCRYPT_MODE_CBC, '');
+ if ($td === false) {
+ PMA_fatalError(__('Failed to use Blowfish from mcrypt!'));
+ }
+ $iv = mcrypt_create_iv(mcrypt_enc_get_iv_size($td), MCRYPT_RAND);
+ $GLOBALS['PMA_Config']->setCookie(
+ 'pma_mcrypt_iv',
+ base64_encode($iv)
+ );
+ }
+}
+
+/**
+ * Handles the cookie authentication method
+ *
+ * @package PhpMyAdmin-Authentication
+ */
+class AuthenticationCookie extends AuthenticationPlugin
+{
+ /**
+ * Displays authentication form
+ *
+ * this function MUST exit/quit the application
+ *
+ * @global string $conn_error the last connection error
+ *
+ * @return boolean|void
+ */
+ public function auth()
+ {
+ global $conn_error;
+
+ $response = PMA_Response::getInstance();
+ if ($response->isAjax()) {
+ $response->isSuccess(false);
+
+ $login_link = '<br /><br />[ ' .
+ sprintf(
+ '<a href="%s" class="ajax login-link">%s</a>',
+ $GLOBALS['cfg']['PmaAbsoluteUri'],
+ __('Log in')
+ )
+ . ' ]';
+
+ if (! empty($conn_error)) {
+
+ $conn_error .= $login_link;
+
+ $response->addJSON(
+ 'message',
+ PMA_Message::error(
+ $conn_error
+ )
+ );
+ } else {
+ $response->addJSON(
+ 'message',
+ PMA_Message::error(
+ __('Your session has expired. Please log in again.') .
+ $login_link
+ )
+ );
+ }
+ if (defined('TESTSUITE')) {
+ return true;
+ } else {
+ exit;
+ }
+ }
+
+ /* Perform logout to custom URL */
+ if (! empty($_REQUEST['old_usr'])
+ && ! empty($GLOBALS['cfg']['Server']['LogoutURL'])
+ ) {
+ PMA_sendHeaderLocation($GLOBALS['cfg']['Server']['LogoutURL']);
+ if (defined('TESTSUITE')) {
+ return true;
+ } else {
+ exit;
+ }
+ }
+
+ // No recall if blowfish secret is not configured as it would produce
+ // garbage
+ if ($GLOBALS['cfg']['LoginCookieRecall']
+ && ! empty($GLOBALS['cfg']['blowfish_secret'])
+ ) {
+ $default_user = $GLOBALS['PHP_AUTH_USER'];
+ $default_server = $GLOBALS['pma_auth_server'];
+ $autocomplete = '';
+ } else {
+ $default_user = '';
+ $default_server = '';
+ // skip the IE autocomplete feature.
+ $autocomplete = ' autocomplete="off"';
+ }
+
+ $response->getFooter()->setMinimal();
+ $header = $response->getHeader();
+ $header->setBodyId('loginform');
+ $header->setTitle('phpMyAdmin');
+ $header->disableMenu();
+ $header->disableWarnings();
+
+ if (file_exists(CUSTOM_HEADER_FILE)) {
+ include CUSTOM_HEADER_FILE;
+ }
+ echo '
+ <div class="container">
+ <a href="';
+ echo PMA_linkURL('http://www.phpmyadmin.net/');
+ echo '" target="_blank" class="logo">';
+ $logo_image = $GLOBALS['pmaThemeImage'] . 'logo_right.png';
+ if (@file_exists($logo_image)) {
+ echo '<img src="' . $logo_image
+ . '" id="imLogo" name="imLogo" alt="phpMyAdmin" border="0" />';
+ } else {
+ echo '<img name="imLogo" id="imLogo" src="'
+ . $GLOBALS['pmaThemeImage'] . 'pma_logo.png' . '" '
+ . 'border="0" width="88" height="31" alt="phpMyAdmin" />';
+ }
+ echo '</a>
+ <h1>';
+ echo sprintf(
+ __('Welcome to %s'),
+ '<bdo dir="ltr" lang="en">phpMyAdmin</bdo>'
+ );
+ echo "</h1>";
+
+ // Show error message
+ if (! empty($conn_error)) {
+ PMA_Message::rawError($conn_error)->display();
+ }
+
+ echo "<noscript>\n";
+ PMA_message::error(
+ __("Javascript must be enabled past this point")
+ )->display();
+ echo "</noscript>\n";
+
+ echo "<div class='hide js-show'>";
+ // Displays the languages form
+ if (empty($GLOBALS['cfg']['Lang'])) {
+ include_once './libraries/display_select_lang.lib.php';
+ // use fieldset, don't show doc link
+ echo PMA_getLanguageSelectorHtml(true, false);
+ }
+ echo '</div>
+ <br />
+ <!-- Login form -->
+ <form method="post" action="index.php" name="login_form"' . $autocomplete .
+ ' class="disableAjax login hide js-show">
+ <fieldset>
+ <legend>';
+ echo __('Log in');
+ echo PMA_Util::showDocu('index');
+ echo '</legend>';
+ if ($GLOBALS['cfg']['AllowArbitraryServer']) {
+ echo '
+ <div class="item">
+ <label for="input_servername" title="';
+ echo __(
+ 'You can enter hostname/IP address and port separated by space.'
+ );
+ echo '">';
+ echo __('Server:');
+ echo '</label>
+ <input type="text" name="pma_servername" id="input_servername"';
+ echo ' value="';
+ echo htmlspecialchars($default_server);
+ echo '" size="24" class="textfield" title="';
+ echo __(
+ 'You can enter hostname/IP address and port separated by space.'
+ ); echo '" />
+ </div>';
+ }
+ echo '<div class="item">
+ <label for="input_username">' . __('Username:') . '</label>
+ <input type="text" name="pma_username" id="input_username" '
+ . 'value="' . htmlspecialchars($default_user) . '" size="24"'
+ . ' class="textfield"/>
+ </div>
+ <div class="item">
+ <label for="input_password">' . __('Password:') . '</label>
+ <input type="password" name="pma_password" id="input_password"'
+ . ' value="" size="24" class="textfield" />
+ </div>';
+ if (count($GLOBALS['cfg']['Servers']) > 1) {
+ echo '<div class="item">
+ <label for="select_server">' . __('Server Choice:') .'</label>
+ <select name="server" id="select_server"';
+ if ($GLOBALS['cfg']['AllowArbitraryServer']) {
+ echo ' onchange="document.forms[\'login_form\'].'
+ . 'elements[\'pma_servername\'].value = \'\'" ';
+ }
+ echo '>';
+
+ include_once './libraries/select_server.lib.php';
+ echo PMA_selectServer(false, false);
+
+ echo '</select></div>';
+ } else {
+ echo ' <input type="hidden" name="server" value="'
+ . $GLOBALS['server'] . '" />';
+ } // end if (server choice)
+
+ // We already have one correct captcha.
+ $skip = false;
+ if ( isset($_SESSION['last_valid_captcha'])
+ && $_SESSION['last_valid_captcha']
+ ) {
+ $skip = true;
+ }
+
+ // Add captcha input field if reCaptcha is enabled
+ if ( !empty($GLOBALS['cfg']['CaptchaLoginPrivateKey'])
+ && !empty($GLOBALS['cfg']['CaptchaLoginPublicKey'])
+ && !$skip
+ ) {
+ // If enabled show captcha to the user on the login screen.
+ echo '<script type="text/javascript"
+ src="https://www.google.com/recaptcha/api/challenge?'
+ . 'k=' . $GLOBALS['cfg']['CaptchaLoginPublicKey'] . '&amp;'
+ . 'hl=' . $GLOBALS['lang'] . '">
+ </script>
+ <noscript>
+ <iframe src="https://www.google.com/recaptcha/api/noscript?k='
+ . $GLOBALS['cfg']['CaptchaLoginPublicKey'] . '"
+ height="300" width="500" frameborder="0"></iframe><br>
+ <textarea name="recaptcha_challenge_field" rows="3" cols="40">
+ </textarea>
+ <input type="hidden" name="recaptcha_response_field"
+ value="manual_challenge">
+ </noscript>
+ <script type="text/javascript">
+ $("#recaptcha_reload_btn").addClass("disableAjax");
+ $("#recaptcha_switch_audio_btn").addClass("disableAjax");
+ $("#recaptcha_switch_img_btn").addClass("disableAjax");
+ $("#recaptcha_whatsthis_btn").addClass("disableAjax");
+ $("#recaptcha_audio_play_again").live("mouseover", function() {
+ $(this).addClass("disableAjax");
+ });
+ </script>';
+ }
+
+ echo '</fieldset>
+ <fieldset class="tblFooters">
+ <input value="' . __('Go') . '" type="submit" id="input_go" />';
+ $_form_params = array();
+ if (! empty($GLOBALS['target'])) {
+ $_form_params['target'] = $GLOBALS['target'];
+ }
+ if (! empty($GLOBALS['db'])) {
+ $_form_params['db'] = $GLOBALS['db'];
+ }
+ if (! empty($GLOBALS['table'])) {
+ $_form_params['table'] = $GLOBALS['table'];
+ }
+ // do not generate a "server" hidden field as we want the "server"
+ // drop-down to have priority
+ echo PMA_URL_getHiddenInputs($_form_params, '', 0, 'server');
+ echo '</fieldset>
+ </form>';
+
+ // BEGIN Swekey Integration
+ Swekey_login('input_username', 'input_go');
+ // END Swekey Integration
+
+ if ($GLOBALS['error_handler']->hasDisplayErrors()) {
+ echo '<div>';
+ $GLOBALS['error_handler']->dispErrors();
+ echo '</div>';
+ }
+ echo '</div>';
+ if (file_exists(CUSTOM_FOOTER_FILE)) {
+ include CUSTOM_FOOTER_FILE;
+ }
+ if (! defined('TESTSUITE')) {
+ exit;
+ } else {
+ return true;
+ }
+ }
+
+ /**
+ * Gets advanced authentication settings
+ *
+ * this function DOES NOT check authentication - it just checks/provides
+ * authentication credentials required to connect to the MySQL server
+ * usually with $GLOBALS['dbi']->connect()
+ *
+ * it returns false if something is missing - which usually leads to
+ * auth() which displays login form
+ *
+ * it returns true if all seems ok which usually leads to auth_set_user()
+ *
+ * it directly switches to authFails() if user inactivity timout is reached
+ *
+ * @todo AllowArbitraryServer on does not imply that the user wants an
+ * arbitrary server, or? so we should also check if this is filled
+ * and not only if allowed
+ *
+ * @return boolean whether we get authentication settings or not
+ */
+ public function authCheck()
+ {
+ global $conn_error;
+
+ // Initialization
+ /**
+ * @global $GLOBALS['pma_auth_server'] the user provided server to
+ * connect to
+ */
+ $GLOBALS['pma_auth_server'] = '';
+
+ $GLOBALS['PHP_AUTH_USER'] = $GLOBALS['PHP_AUTH_PW'] = '';
+ $GLOBALS['from_cookie'] = false;
+
+ // BEGIN Swekey Integration
+ if (! Swekey_Auth_check()) {
+ return false;
+ }
+ // END Swekey Integration
+
+ if (defined('PMA_CLEAR_COOKIES')) {
+ foreach ($GLOBALS['cfg']['Servers'] as $key => $val) {
+ $GLOBALS['PMA_Config']->removeCookie('pmaPass-' . $key);
+ $GLOBALS['PMA_Config']->removeCookie('pmaServer-' . $key);
+ $GLOBALS['PMA_Config']->removeCookie('pmaUser-' . $key);
+ }
+ return false;
+ }
+
+ // We already have one correct captcha.
+ $skip = false;
+ if ( isset($_SESSION['last_valid_captcha'])
+ && $_SESSION['last_valid_captcha']
+ ) {
+ $skip = true;
+ }
+
+ // Verify Captcha if it is required.
+ if ( !empty($GLOBALS['cfg']['CaptchaLoginPrivateKey'])
+ && !empty($GLOBALS['cfg']['CaptchaLoginPublicKey'])
+ && !$skip
+ ) {
+ if ( !empty($_POST["recaptcha_challenge_field"])
+ && !empty($_POST["recaptcha_response_field"])
+ ) {
+ include_once 'libraries/plugins/auth/recaptchalib.php';
+
+ // Use private key to verify captcha status.
+ $resp = recaptcha_check_answer(
+ $GLOBALS['cfg']['CaptchaLoginPrivateKey'],
+ $_SERVER["REMOTE_ADDR"],
+ $_POST["recaptcha_challenge_field"],
+ $_POST["recaptcha_response_field"]
+ );
+
+ // Check if the captcha entered is valid, if not stop the login.
+ if ( !$resp->is_valid ) {
+ $conn_error = __('Entered captcha is wrong, try again!');
+ $_SESSION['last_valid_captcha'] = false;
+ return false;
+ } else {
+ $_SESSION['last_valid_captcha'] = true;
+ }
+ } elseif (! empty($_POST["recaptcha_challenge_field"])
+ && empty($_POST["recaptcha_response_field"])
+ ) {
+ $conn_error = __('Please enter correct captcha!');
+ return false;
+ } else {
+ if (! isset($_SESSION['last_valid_captcha'])
+ || ! $_SESSION['last_valid_captcha']
+ ) {
+ return false;
+ }
+ }
+ }
+
+ if (! empty($_REQUEST['old_usr'])) {
+ // The user wants to be logged out
+ // -> delete his choices that were stored in session
+
+ // according to the PHP manual we should do this before the destroy:
+ //$_SESSION = array();
+
+ if (! defined('TESTSUITE')) {
+ session_destroy();
+ // $_SESSION array is not immediately emptied
+ $_SESSION['last_valid_captcha'] = false;
+ }
+ // -> delete password cookie(s)
+ if ($GLOBALS['cfg']['LoginCookieDeleteAll']) {
+ foreach ($GLOBALS['cfg']['Servers'] as $key => $val) {
+ $GLOBALS['PMA_Config']->removeCookie('pmaPass-' . $key);
+ if (isset($_COOKIE['pmaPass-' . $key])) {
+ unset($_COOKIE['pmaPass-' . $key]);
+ }
+ }
+ } else {
+ $GLOBALS['PMA_Config']->removeCookie(
+ 'pmaPass-' . $GLOBALS['server']
+ );
+ if (isset($_COOKIE['pmaPass-' . $GLOBALS['server']])) {
+ unset($_COOKIE['pmaPass-' . $GLOBALS['server']]);
+ }
+ }
+ }
+
+ if (! empty($_REQUEST['pma_username'])) {
+ // The user just logged in
+ $GLOBALS['PHP_AUTH_USER'] = $_REQUEST['pma_username'];
+ $GLOBALS['PHP_AUTH_PW'] = empty($_REQUEST['pma_password'])
+ ? ''
+ : $_REQUEST['pma_password'];
+ if ($GLOBALS['cfg']['AllowArbitraryServer']
+ && isset($_REQUEST['pma_servername'])
+ ) {
+ $GLOBALS['pma_auth_server'] = $_REQUEST['pma_servername'];
+ }
+ return true;
+ }
+
+ // At the end, try to set the $GLOBALS['PHP_AUTH_USER']
+ // and $GLOBALS['PHP_AUTH_PW'] variables from cookies
+
+ // servername
+ if ($GLOBALS['cfg']['AllowArbitraryServer']
+ && ! empty($_COOKIE['pmaServer-' . $GLOBALS['server']])
+ ) {
+ $GLOBALS['pma_auth_server']
+ = $_COOKIE['pmaServer-' . $GLOBALS['server']];
+ }
+
+ // username
+ if (empty($_COOKIE['pmaUser-' . $GLOBALS['server']])) {
+ return false;
+ }
+
+ $GLOBALS['PHP_AUTH_USER'] = $this->blowfishDecrypt(
+ $_COOKIE['pmaUser-' . $GLOBALS['server']],
+ $this->_getBlowfishSecret()
+ );
+
+ // user was never logged in since session start
+ if (empty($_SESSION['last_access_time'])) {
+ return false;
+ }
+
+ // User inactive too long
+ $last_access_time = time() - $GLOBALS['cfg']['LoginCookieValidity'];
+ if ($_SESSION['last_access_time'] < $last_access_time
+ ) {
+ PMA_Util::cacheUnset('is_create_db_priv', true);
+ PMA_Util::cacheUnset('is_process_priv', true);
+ PMA_Util::cacheUnset('is_reload_priv', true);
+ PMA_Util::cacheUnset('db_to_create', true);
+ PMA_Util::cacheUnset('dbs_where_create_table_allowed', true);
+ $GLOBALS['no_activity'] = true;
+ $this->authFails();
+ if (! defined('TESTSUITE')) {
+ exit;
+ } else {
+ return false;
+ }
+ }
+
+ // password
+ if (empty($_COOKIE['pmaPass-' . $GLOBALS['server']])) {
+ return false;
+ }
+
+ $GLOBALS['PHP_AUTH_PW'] = $this->blowfishDecrypt(
+ $_COOKIE['pmaPass-' . $GLOBALS['server']],
+ $this->_getBlowfishSecret()
+ );
+
+ if ($GLOBALS['PHP_AUTH_PW'] == "\xff(blank)") {
+ $GLOBALS['PHP_AUTH_PW'] = '';
+ }
+
+ $GLOBALS['from_cookie'] = true;
+
+ return true;
+ }
+
+ /**
+ * Set the user and password after last checkings if required
+ *
+ * @return boolean always true
+ */
+ public function authSetUser()
+ {
+ global $cfg;
+
+ // Ensures valid authentication mode, 'only_db', bookmark database and
+ // table names and relation table name are used
+ if ($cfg['Server']['user'] != $GLOBALS['PHP_AUTH_USER']) {
+ foreach ($cfg['Servers'] as $idx => $current) {
+ if ($current['host'] == $cfg['Server']['host']
+ && $current['port'] == $cfg['Server']['port']
+ && $current['socket'] == $cfg['Server']['socket']
+ && $current['ssl'] == $cfg['Server']['ssl']
+ && $current['connect_type'] == $cfg['Server']['connect_type']
+ && $current['user'] == $GLOBALS['PHP_AUTH_USER']
+ ) {
+ $GLOBALS['server'] = $idx;
+ $cfg['Server'] = $current;
+ break;
+ }
+ } // end foreach
+ } // end if
+
+ if ($GLOBALS['cfg']['AllowArbitraryServer']
+ && ! empty($GLOBALS['pma_auth_server'])
+ ) {
+ /* Allow to specify 'host port' */
+ $parts = explode(' ', $GLOBALS['pma_auth_server']);
+ if (count($parts) == 2) {
+ $tmp_host = $parts[0];
+ $tmp_port = $parts[1];
+ } else {
+ $tmp_host = $GLOBALS['pma_auth_server'];
+ $tmp_port = '';
+ }
+ if ($cfg['Server']['host'] != $GLOBALS['pma_auth_server']) {
+ $cfg['Server']['host'] = $tmp_host;
+ if (! empty($tmp_port)) {
+ $cfg['Server']['port'] = $tmp_port;
+ }
+ }
+ unset($tmp_host, $tmp_port, $parts);
+ }
+ $cfg['Server']['user'] = $GLOBALS['PHP_AUTH_USER'];
+ $cfg['Server']['password'] = $GLOBALS['PHP_AUTH_PW'];
+
+ // Avoid showing the password in phpinfo()'s output
+ unset($GLOBALS['PHP_AUTH_PW']);
+ unset($_SERVER['PHP_AUTH_PW']);
+
+ $_SESSION['last_access_time'] = time();
+
+ // Name and password cookies need to be refreshed each time
+ // Duration = one month for username
+ $GLOBALS['PMA_Config']->setCookie(
+ 'pmaUser-' . $GLOBALS['server'],
+ $this->blowfishEncrypt(
+ $cfg['Server']['user'],
+ $this->_getBlowfishSecret()
+ )
+ );
+
+ // Duration = as configured
+ $GLOBALS['PMA_Config']->setCookie(
+ 'pmaPass-' . $GLOBALS['server'],
+ $this->blowfishEncrypt(
+ ! empty($cfg['Server']['password'])
+ ? $cfg['Server']['password'] : "\xff(blank)",
+ $this->_getBlowfishSecret()
+ ),
+ null,
+ $GLOBALS['cfg']['LoginCookieStore']
+ );
+
+ // Set server cookies if required (once per session) and, in this case,
+ // force reload to ensure the client accepts cookies
+ if (! $GLOBALS['from_cookie']) {
+ if ($GLOBALS['cfg']['AllowArbitraryServer']) {
+ if (! empty($GLOBALS['pma_auth_server'])) {
+ // Duration = one month for servername
+ $GLOBALS['PMA_Config']->setCookie(
+ 'pmaServer-' . $GLOBALS['server'],
+ $cfg['Server']['host']
+ );
+ } else {
+ // Delete servername cookie
+ $GLOBALS['PMA_Config']->removeCookie(
+ 'pmaServer-' . $GLOBALS['server']
+ );
+ }
+ }
+
+ // URL where to go:
+ $redirect_url = $cfg['PmaAbsoluteUri'] . 'index.php';
+
+ // any parameters to pass?
+ $url_params = array();
+ if (strlen($GLOBALS['db'])) {
+ $url_params['db'] = $GLOBALS['db'];
+ }
+ if (strlen($GLOBALS['table'])) {
+ $url_params['table'] = $GLOBALS['table'];
+ }
+ // any target to pass?
+ if (! empty($GLOBALS['target'])
+ && $GLOBALS['target'] != 'index.php'
+ ) {
+ $url_params['target'] = $GLOBALS['target'];
+ }
+
+ /**
+ * Clear user cache.
+ */
+ PMA_Util::clearUserCache();
+
+ PMA_Response::getInstance()->disable();
+
+ PMA_sendHeaderLocation(
+ $redirect_url . PMA_URL_getCommon($url_params, '&'),
+ true
+ );
+ if (! defined('TESTSUITE')) {
+ exit;
+ } else {
+ return false;
+ }
+ } // end if
+
+ return true;
+
+ }
+
+ /**
+ * User is not allowed to login to MySQL -> authentication failed
+ *
+ * prepares error message and switches to auth() which display the error
+ * and the login form
+ *
+ * this function MUST exit/quit the application,
+ * currently doen by call to auth()
+ *
+ * @return void
+ */
+ public function authFails()
+ {
+ global $conn_error;
+
+ // Deletes password cookie and displays the login form
+ $GLOBALS['PMA_Config']->removeCookie('pmaPass-' . $GLOBALS['server']);
+
+ if (! empty($GLOBALS['login_without_password_is_forbidden'])) {
+ $conn_error = __(
+ 'Login without a password is forbidden by configuration'
+ . ' (see AllowNoPassword)'
+ );
+ } elseif (! empty($GLOBALS['allowDeny_forbidden'])) {
+ $conn_error = __('Access denied');
+ } elseif (! empty($GLOBALS['no_activity'])) {
+ $conn_error = sprintf(
+ __('No activity within %s seconds; please log in again.'),
+ $GLOBALS['cfg']['LoginCookieValidity']
+ );
+ } elseif ($GLOBALS['dbi']->getError()) {
+ $conn_error = '#' . $GLOBALS['errno'] . ' '
+ . __('Cannot log in to the MySQL server');
+ } else {
+ $conn_error = __('Cannot log in to the MySQL server');
+ }
+
+ // needed for PHP-CGI (not need for FastCGI or mod-php)
+ header('Cache-Control: no-store, no-cache, must-revalidate');
+ header('Pragma: no-cache');
+
+ $this->auth();
+ }
+
+ /**
+ * Returns blowfish secret or generates one if needed.
+ *
+ * @return string
+ */
+ private function _getBlowfishSecret()
+ {
+ if (empty($GLOBALS['cfg']['blowfish_secret'])) {
+ if (empty($_SESSION['auto_blowfish_secret'])) {
+ // this returns 23 characters
+ $_SESSION['auto_blowfish_secret'] = uniqid('', true);
+ }
+ return $_SESSION['auto_blowfish_secret'];
+ } else {
+ // apply md5() to work around too long secrets (returns 32 characters)
+ return md5($GLOBALS['cfg']['blowfish_secret']);
+ }
+ }
+
+ /**
+ * Encryption using blowfish algorithm (mcrypt)
+ * or phpseclib's AES if mcrypt not available
+ *
+ * @param string $data original data
+ * @param string $secret the secret
+ *
+ * @return string the encrypted result
+ */
+ public function blowfishEncrypt($data, $secret)
+ {
+ global $iv;
+ if (! function_exists('mcrypt_encrypt')) {
+ /**
+ * This library uses mcrypt when available, so
+ * we could always call it instead of having an
+ * if/then/else logic, however the include_once
+ * call is costly
+ */
+ include_once "./libraries/phpseclib/Crypt/AES.php";
+ $cipher = new Crypt_AES(CRYPT_AES_MODE_ECB);
+ $cipher->setKey($secret);
+ return base64_encode($cipher->encrypt($data));
+ } else {
+ return base64_encode(
+ mcrypt_encrypt(
+ MCRYPT_BLOWFISH,
+ $secret,
+ $data,
+ MCRYPT_MODE_CBC,
+ $iv
+ )
+ );
+ }
+ }
+
+ /**
+ * Decryption using blowfish algorithm (mcrypt)
+ * or phpseclib's AES if mcrypt not available
+ *
+ * @param string $encdata encrypted data
+ * @param string $secret the secret
+ *
+ * @return string original data
+ */
+ public function blowfishDecrypt($encdata, $secret)
+ {
+ global $iv;
+ if (! function_exists('mcrypt_encrypt')) {
+ include_once "./libraries/phpseclib/Crypt/AES.php";
+ $cipher = new Crypt_AES(CRYPT_AES_MODE_ECB);
+ $cipher->setKey($secret);
+ return $cipher->decrypt(base64_decode($encdata));
+ } else {
+ $data = base64_decode($encdata);
+ $decrypted = mcrypt_decrypt(
+ MCRYPT_BLOWFISH,
+ $secret,
+ $data,
+ MCRYPT_MODE_CBC,
+ $iv
+ );
+ return trim($decrypted);
+ }
+ }
+
+ /**
+ * This method is called when any PluginManager to which the observer
+ * is attached calls PluginManager::notify()
+ *
+ * @param SplSubject $subject The PluginManager notifying the observer
+ * of an update.
+ *
+ * @return void
+ */
+ public function update (SplSubject $subject)
+ {
+ }
+}
diff --git a/libraries/plugins/auth/AuthenticationHttp.class.php b/libraries/plugins/auth/AuthenticationHttp.class.php
new file mode 100644
index 0000000000..75273b11aa
--- /dev/null
+++ b/libraries/plugins/auth/AuthenticationHttp.class.php
@@ -0,0 +1,261 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * HTTP Authentication plugin for phpMyAdmin.
+ * NOTE: Requires PHP loaded as a Apache module.
+ *
+ * @package PhpMyAdmin-Authentication
+ * @subpackage HTTP
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/* Get the authentication interface */
+require_once 'libraries/plugins/AuthenticationPlugin.class.php';
+
+/**
+ * Handles the HTTP authentication methods
+ *
+ * @package PhpMyAdmin-Authentication
+ */
+class AuthenticationHttp extends AuthenticationPlugin
+{
+ /**
+ * Displays authentication form
+ *
+ * @global string the font face to use in case of failure
+ * @global string the default font size to use in case of failure
+ * @global string the big font size to use in case of failure
+ *
+ * @return boolean always true (no return indeed)
+ */
+ public function auth()
+ {
+ /* Perform logout to custom URL */
+ if (! empty($_REQUEST['old_usr'])
+ && ! empty($GLOBALS['cfg']['Server']['LogoutURL'])
+ ) {
+ PMA_sendHeaderLocation($GLOBALS['cfg']['Server']['LogoutURL']);
+ if (! defined('TESTSUITE')) {
+ exit;
+ } else {
+ return false;
+ }
+ }
+
+ if (empty($GLOBALS['cfg']['Server']['auth_http_realm'])) {
+ if (empty($GLOBALS['cfg']['Server']['verbose'])) {
+ $server_message = $GLOBALS['cfg']['Server']['host'];
+ } else {
+ $server_message = $GLOBALS['cfg']['Server']['verbose'];
+ }
+ $realm_message = 'phpMyAdmin ' . $server_message;
+ } else {
+ $realm_message = $GLOBALS['cfg']['Server']['auth_http_realm'];
+ }
+ // remove non US-ASCII to respect RFC2616
+ $realm_message = preg_replace('/[^\x20-\x7e]/i', '', $realm_message);
+ header('WWW-Authenticate: Basic realm="' . $realm_message . '"');
+ header('HTTP/1.0 401 Unauthorized');
+ if (php_sapi_name() !== 'cgi-fcgi') {
+ header('status: 401 Unauthorized');
+ }
+
+ /* HTML header */
+ $response = PMA_Response::getInstance();
+ $response->getFooter()->setMinimal();
+ $header = $response->getHeader();
+ $header->setTitle(__('Access denied'));
+ $header->disableMenu();
+ $header->setBodyId('loginform');
+
+ $response->addHTML('<h1>');
+ $response->addHTML(sprintf(__('Welcome to %s'), ' phpMyAdmin'));
+ $response->addHTML('</h1>');
+ $response->addHTML('<h3>');
+ $response->addHTML(
+ PMA_Message::error(
+ __('Wrong username/password. Access denied.')
+ )
+ );
+ $response->addHTML('</h3>');
+
+ if (file_exists(CUSTOM_FOOTER_FILE)) {
+ include CUSTOM_FOOTER_FILE;
+ }
+
+ if (! defined('TESTSUITE')) {
+ exit;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Gets advanced authentication settings
+ *
+ * @global string $PHP_AUTH_USER the username if register_globals is on
+ * @global string $PHP_AUTH_PW the password if register_globals is on
+ * @global array the array of server variables if
+ * register_globals is off
+ * @global array the array of environment variables if
+ * register_globals is off
+ * @global string the username for the ? server
+ * @global string the password for the ? server
+ * @global string the username for the WebSite Professional
+ * server
+ * @global string the password for the WebSite Professional
+ * server
+ * @global string the username of the user who logs out
+ *
+ * @return boolean whether we get authentication settings or not
+ */
+ public function authCheck()
+ {
+ global $PHP_AUTH_USER, $PHP_AUTH_PW;
+
+ // Grabs the $PHP_AUTH_USER variable whatever are the values of the
+ // 'register_globals' and the 'variables_order' directives
+ if (empty($PHP_AUTH_USER)) {
+ if (PMA_getenv('PHP_AUTH_USER')) {
+ $PHP_AUTH_USER = PMA_getenv('PHP_AUTH_USER');
+ } elseif (PMA_getenv('REMOTE_USER')) {
+ // CGI, might be encoded, see below
+ $PHP_AUTH_USER = PMA_getenv('REMOTE_USER');
+ } elseif (PMA_getenv('REDIRECT_REMOTE_USER')) {
+ // CGI, might be encoded, see below
+ $PHP_AUTH_USER = PMA_getenv('REDIRECT_REMOTE_USER');
+ } elseif (PMA_getenv('AUTH_USER')) {
+ // WebSite Professional
+ $PHP_AUTH_USER = PMA_getenv('AUTH_USER');
+ } elseif (PMA_getenv('HTTP_AUTHORIZATION')
+ && false === strpos(PMA_getenv('HTTP_AUTHORIZATION'), '<')
+ ) {
+ // IIS, might be encoded, see below; also prevent XSS
+ $PHP_AUTH_USER = PMA_getenv('HTTP_AUTHORIZATION');
+ } elseif (PMA_getenv('Authorization')) {
+ // FastCGI, might be encoded, see below
+ $PHP_AUTH_USER = PMA_getenv('Authorization');
+ }
+ }
+ // Grabs the $PHP_AUTH_PW variable whatever are the values of the
+ // 'register_globals' and the 'variables_order' directives
+ if (empty($PHP_AUTH_PW)) {
+ if (PMA_getenv('PHP_AUTH_PW')) {
+ $PHP_AUTH_PW = PMA_getenv('PHP_AUTH_PW');
+ } elseif (PMA_getenv('REMOTE_PASSWORD')) {
+ // Apache/CGI
+ $PHP_AUTH_PW = PMA_getenv('REMOTE_PASSWORD');
+ } elseif (PMA_getenv('AUTH_PASSWORD')) {
+ // WebSite Professional
+ $PHP_AUTH_PW = PMA_getenv('AUTH_PASSWORD');
+ }
+ }
+
+ // Decode possibly encoded information (used by IIS/CGI/FastCGI)
+ // (do not use explode() because a user might have a colon in his password
+ if (strcmp(substr($PHP_AUTH_USER, 0, 6), 'Basic ') == 0) {
+ $usr_pass = base64_decode(substr($PHP_AUTH_USER, 6));
+ if (! empty($usr_pass)) {
+ $colon = strpos($usr_pass, ':');
+ if ($colon) {
+ $PHP_AUTH_USER = substr($usr_pass, 0, $colon);
+ $PHP_AUTH_PW = substr($usr_pass, $colon + 1);
+ }
+ unset($colon);
+ }
+ unset($usr_pass);
+ }
+
+ // User logged out -> ensure the new username is not the same
+ $old_usr = isset($_REQUEST['old_usr']) ? $_REQUEST['old_usr'] : '';
+ if (! empty($old_usr)
+ && (isset($PHP_AUTH_USER) && $old_usr == $PHP_AUTH_USER)
+ ) {
+ $PHP_AUTH_USER = '';
+ // -> delete user's choices that were stored in session
+ if (! defined('TESTSUITE')) {
+ session_destroy();
+ }
+ }
+
+ // Returns whether we get authentication settings or not
+ if (empty($PHP_AUTH_USER)) {
+ return false;
+ } else {
+ return true;
+ }
+ }
+
+ /**
+ * Set the user and password after last checkings if required
+ *
+ * @global array $cfg the valid servers settings
+ * @global integer $server the id of the current server
+ * @global array the current server settings
+ * @global string $PHP_AUTH_USER the current username
+ * @global string $PHP_AUTH_PW the current password
+ *
+ * @return boolean always true
+ */
+ public function authSetUser()
+ {
+ global $cfg, $server;
+ global $PHP_AUTH_USER, $PHP_AUTH_PW;
+
+ // Ensures valid authentication mode, 'only_db', bookmark database and
+ // table names and relation table name are used
+ if ($cfg['Server']['user'] != $PHP_AUTH_USER) {
+ $servers_cnt = count($cfg['Servers']);
+ for ($i = 1; $i <= $servers_cnt; $i++) {
+ if (isset($cfg['Servers'][$i])
+ && ($cfg['Servers'][$i]['host'] == $cfg['Server']['host']
+ && $cfg['Servers'][$i]['user'] == $PHP_AUTH_USER)
+ ) {
+ $server = $i;
+ $cfg['Server'] = $cfg['Servers'][$i];
+ break;
+ }
+ } // end for
+ } // end if
+
+ $cfg['Server']['user'] = $PHP_AUTH_USER;
+ $cfg['Server']['password'] = $PHP_AUTH_PW;
+
+ // Avoid showing the password in phpinfo()'s output
+ unset($GLOBALS['PHP_AUTH_PW']);
+ unset($_SERVER['PHP_AUTH_PW']);
+
+ return true;
+ }
+
+ /**
+ * User is not allowed to login to MySQL -> authentication failed
+ *
+ * @return boolean always true (no return indeed)
+ */
+ public function authFails()
+ {
+ $error = $GLOBALS['dbi']->getError();
+ if ($error && $GLOBALS['errno'] != 1045) {
+ PMA_fatalError($error);
+ } else {
+ $this->auth();
+ return true;
+ }
+ }
+
+ /**
+ * This method is called when any PluginManager to which the observer
+ * is attached calls PluginManager::notify()
+ *
+ * @param SplSubject $subject The PluginManager notifying the observer
+ * of an update.
+ *
+ * @return void
+ */
+ public function update (SplSubject $subject)
+ {
+ }
+}
diff --git a/libraries/plugins/auth/AuthenticationSignon.class.php b/libraries/plugins/auth/AuthenticationSignon.class.php
new file mode 100644
index 0000000000..4586af92b0
--- /dev/null
+++ b/libraries/plugins/auth/AuthenticationSignon.class.php
@@ -0,0 +1,299 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * SignOn Authentication plugin for phpMyAdmin
+ *
+ * @package PhpMyAdmin-Authentication
+ * @subpackage SignOn
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/* Get the authentication interface */
+require_once 'libraries/plugins/AuthenticationPlugin.class.php';
+
+/**
+ * Handles the SignOn authentication method
+ *
+ * @package PhpMyAdmin-Authentication
+ */
+class AuthenticationSignon extends AuthenticationPlugin
+{
+ /**
+ * Displays authentication form
+ *
+ * @global string the font face to use in case of failure
+ * @global string the default font size to use in case of failure
+ * @global string the big font size to use in case of failure
+ *
+ * @return boolean always true (no return indeed)
+ */
+ public function auth()
+ {
+ unset($_SESSION['LAST_SIGNON_URL']);
+ if (empty($GLOBALS['cfg']['Server']['SignonURL'])) {
+ PMA_fatalError('You must set SignonURL!');
+ } elseif (! empty($_REQUEST['old_usr'])
+ && ! empty($GLOBALS['cfg']['Server']['LogoutURL'])
+ ) {
+ /* Perform logout to custom URL */
+ PMA_sendHeaderLocation($GLOBALS['cfg']['Server']['LogoutURL']);
+ } else {
+ PMA_sendHeaderLocation($GLOBALS['cfg']['Server']['SignonURL']);
+ }
+
+ if (! defined('TESTSUITE')) {
+ exit();
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Gets advanced authentication settings
+ *
+ * @global string $PHP_AUTH_USER the username if register_globals is on
+ * @global string $PHP_AUTH_PW the password if register_globals is on
+ * @global array the array of server variables if
+ * register_globals is off
+ * @global array the array of environment variables if
+ * register_globals is off
+ * @global string the username for the ? server
+ * @global string the password for the ? server
+ * @global string the username for the WebSite Professional server
+ * @global string the password for the WebSite Professional server
+ * @global string the username of the user who logs out
+ *
+ * @return boolean whether we get authentication settings or not
+ */
+ public function authCheck()
+ {
+ global $PHP_AUTH_USER, $PHP_AUTH_PW;
+
+ /* Check if we're using same sigon server */
+ $signon_url = $GLOBALS['cfg']['Server']['SignonURL'];
+ if (isset($_SESSION['LAST_SIGNON_URL'])
+ && $_SESSION['LAST_SIGNON_URL'] != $signon_url
+ ) {
+ return false;
+ }
+
+ /* Script name */
+ $script_name = $GLOBALS['cfg']['Server']['SignonScript'];
+
+ /* Session name */
+ $session_name = $GLOBALS['cfg']['Server']['SignonSession'];
+
+ /* Login URL */
+ $signon_url = $GLOBALS['cfg']['Server']['SignonURL'];
+
+ /* Current host */
+ $single_signon_host = $GLOBALS['cfg']['Server']['host'];
+
+ /* Current port */
+ $single_signon_port = $GLOBALS['cfg']['Server']['port'];
+
+ /* No configuration updates */
+ $single_signon_cfgupdate = array();
+
+ /* Are we requested to do logout? */
+ $do_logout = !empty($_REQUEST['old_usr']);
+
+ /* Handle script based auth */
+ if (!empty($script_name)) {
+ if (! file_exists($script_name)) {
+ PMA_fatalError(
+ __('Can not find signon authentication script:')
+ . ' '. $script_name
+ );
+ }
+ include $script_name;
+
+ list ($PHP_AUTH_USER, $PHP_AUTH_PW)
+ = get_login_credentials($GLOBALS['cfg']['Server']['user']);
+
+ } elseif (isset($_COOKIE[$session_name])) { /* Does session exist? */
+ /* End current session */
+ $old_session = session_name();
+ $old_id = session_id();
+ if (! defined('TESTSUITE')) {
+ session_write_close();
+ }
+
+ /* Load single signon session */
+ session_name($session_name);
+ session_id($_COOKIE[$session_name]);
+ if (! defined('TESTSUITE')) {
+ session_start();
+ }
+
+ /* Clear error message */
+ unset($_SESSION['PMA_single_signon_error_message']);
+
+ /* Grab credentials if they exist */
+ if (isset($_SESSION['PMA_single_signon_user'])) {
+ if ($do_logout) {
+ $PHP_AUTH_USER = '';
+ } else {
+ $PHP_AUTH_USER = $_SESSION['PMA_single_signon_user'];
+ }
+ }
+ if (isset($_SESSION['PMA_single_signon_password'])) {
+ if ($do_logout) {
+ $PHP_AUTH_PW = '';
+ } else {
+ $PHP_AUTH_PW = $_SESSION['PMA_single_signon_password'];
+ }
+ }
+ if (isset($_SESSION['PMA_single_signon_host'])) {
+ $single_signon_host = $_SESSION['PMA_single_signon_host'];
+ }
+
+ if (isset($_SESSION['PMA_single_signon_port'])) {
+ $single_signon_port = $_SESSION['PMA_single_signon_port'];
+ }
+
+ if (isset($_SESSION['PMA_single_signon_cfgupdate'])) {
+ $single_signon_cfgupdate = $_SESSION['PMA_single_signon_cfgupdate'];
+ }
+
+
+ /* Also get token as it is needed to access subpages */
+ if (isset($_SESSION['PMA_single_signon_token'])) {
+ /* No need to care about token on logout */
+ $pma_token = $_SESSION['PMA_single_signon_token'];
+ }
+
+ /* End single signon session */
+ if (! defined('TESTSUITE')) {
+ session_write_close();
+ }
+
+ /* Restart phpMyAdmin session */
+ session_name($old_session);
+ if (!empty($old_id)) {
+ session_id($old_id);
+ }
+ if (! defined('TESTSUITE')) {
+ session_start();
+ }
+
+ /* Set the single signon host */
+ $GLOBALS['cfg']['Server']['host'] = $single_signon_host;
+
+ /* Set the single signon port */
+ $GLOBALS['cfg']['Server']['port'] = $single_signon_port;
+
+ /* Configuration update */
+ $GLOBALS['cfg']['Server'] = array_merge(
+ $GLOBALS['cfg']['Server'],
+ $single_signon_cfgupdate
+ );
+
+ /* Restore our token */
+ if (!empty($pma_token)) {
+ $_SESSION[' PMA_token '] = $pma_token;
+ }
+
+ /**
+ * Clear user cache.
+ */
+ PMA_Util::clearUserCache();
+ }
+
+ // Returns whether we get authentication settings or not
+ if (empty($PHP_AUTH_USER)) {
+ unset($_SESSION['LAST_SIGNON_URL']);
+ return false;
+ } else {
+ $_SESSION['LAST_SIGNON_URL'] = $GLOBALS['cfg']['Server']['SignonURL'];
+ return true;
+ }
+ }
+
+ /**
+ * Set the user and password after last checkings if required
+ *
+ * @global array $cfg the valid servers settings
+ * @global integer the id of the current server
+ * @global array the current server settings
+ * @global string $PHP_AUTH_USER the current username
+ * @global string $PHP_AUTH_PW the current password
+ *
+ * @return boolean always true
+ */
+ public function authSetUser()
+ {
+ global $cfg;
+ global $PHP_AUTH_USER, $PHP_AUTH_PW;
+
+ $cfg['Server']['user'] = $PHP_AUTH_USER;
+ $cfg['Server']['password'] = $PHP_AUTH_PW;
+
+ return true;
+ }
+
+ /**
+ * User is not allowed to login to MySQL -> authentication failed
+ *
+ * @return boolean always true (no return indeed)
+ */
+ public function authFails()
+ {
+ /* Session name */
+ $session_name = $GLOBALS['cfg']['Server']['SignonSession'];
+
+ /* Does session exist? */
+ if (isset($_COOKIE[$session_name])) {
+ /* End current session */
+ if (! defined('TESTSUITE')) {
+ session_write_close();
+ }
+
+ /* Load single signon session */
+ session_name($session_name);
+ session_id($_COOKIE[$session_name]);
+ if (! defined('TESTSUITE')) {
+ session_start();
+ }
+
+ /* Set error message */
+ if (! empty($GLOBALS['login_without_password_is_forbidden'])) {
+ $_SESSION['PMA_single_signon_error_message'] = __(
+ 'Login without a password is forbidden by configuration '
+ . '(see AllowNoPassword)'
+ );
+ } elseif (! empty($GLOBALS['allowDeny_forbidden'])) {
+ $_SESSION['PMA_single_signon_error_message'] = __('Access denied');
+ } elseif (! empty($GLOBALS['no_activity'])) {
+ $_SESSION['PMA_single_signon_error_message'] = sprintf(
+ __('No activity within %s seconds; please log in again.'),
+ $GLOBALS['cfg']['LoginCookieValidity']
+ );
+ } elseif ($GLOBALS['dbi']->getError()) {
+ $_SESSION['PMA_single_signon_error_message'] = PMA_sanitize(
+ $GLOBALS['dbi']->getError()
+ );
+ } else {
+ $_SESSION['PMA_single_signon_error_message'] = __(
+ 'Cannot log in to the MySQL server'
+ );
+ }
+ }
+ $this->auth();
+ }
+
+ /**
+ * This method is called when any PluginManager to which the observer
+ * is attached calls PluginManager::notify()
+ *
+ * @param SplSubject $subject The PluginManager notifying the observer
+ * of an update.
+ *
+ * @return void
+ */
+ public function update (SplSubject $subject)
+ {
+ }
+}
diff --git a/libraries/plugins/auth/recaptchalib.php b/libraries/plugins/auth/recaptchalib.php
new file mode 100644
index 0000000000..32c4f4d758
--- /dev/null
+++ b/libraries/plugins/auth/recaptchalib.php
@@ -0,0 +1,277 @@
+<?php
+/*
+ * This is a PHP library that handles calling reCAPTCHA.
+ * - Documentation and latest version
+ * http://recaptcha.net/plugins/php/
+ * - Get a reCAPTCHA API Key
+ * https://www.google.com/recaptcha/admin/create
+ * - Discussion group
+ * http://groups.google.com/group/recaptcha
+ *
+ * Copyright (c) 2007 reCAPTCHA -- http://recaptcha.net
+ * AUTHORS:
+ * Mike Crawford
+ * Ben Maurer
+ *
+ * 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.
+ */
+
+/**
+ * The reCAPTCHA server URL's
+ */
+define("RECAPTCHA_API_SERVER", "http://www.google.com/recaptcha/api");
+define("RECAPTCHA_API_SECURE_SERVER", "https://www.google.com/recaptcha/api");
+define("RECAPTCHA_VERIFY_SERVER", "www.google.com");
+
+/**
+ * Encodes the given data into a query string format
+ * @param $data - array of string elements to be encoded
+ * @return string - encoded request
+ */
+function _recaptcha_qsencode ($data) {
+ $req = "";
+ foreach ( $data as $key => $value )
+ $req .= $key . '=' . urlencode( stripslashes($value) ) . '&';
+
+ // Cut the last '&'
+ $req=substr($req,0,strlen($req)-1);
+ return $req;
+}
+
+
+
+/**
+ * Submits an HTTP POST to a reCAPTCHA server
+ * @param string $host
+ * @param string $path
+ * @param array $data
+ * @param int port
+ * @return array response
+ */
+function _recaptcha_http_post($host, $path, $data, $port = 80) {
+
+ $req = _recaptcha_qsencode ($data);
+
+ $http_request = "POST $path HTTP/1.0\r\n";
+ $http_request .= "Host: $host\r\n";
+ $http_request .= "Content-Type: application/x-www-form-urlencoded;\r\n";
+ $http_request .= "Content-Length: " . strlen($req) . "\r\n";
+ $http_request .= "User-Agent: reCAPTCHA/PHP\r\n";
+ $http_request .= "\r\n";
+ $http_request .= $req;
+
+ $response = '';
+ if( false == ( $fs = @fsockopen($host, $port, $errno, $errstr, 10) ) ) {
+ die ('Could not open socket');
+ }
+
+ fwrite($fs, $http_request);
+
+ while ( !feof($fs) )
+ $response .= fgets($fs, 1160); // One TCP-IP packet
+ fclose($fs);
+ $response = explode("\r\n\r\n", $response, 2);
+
+ return $response;
+}
+
+
+
+/**
+ * Gets the challenge HTML (javascript and non-javascript version).
+ * This is called from the browser, and the resulting reCAPTCHA HTML widget
+ * is embedded within the HTML form it was called from.
+ * @param string $pubkey A public key for reCAPTCHA
+ * @param string $error The error given by reCAPTCHA (optional, default is null)
+ * @param boolean $use_ssl Should the request be made over ssl? (optional, default is false)
+
+ * @return string - The HTML to be embedded in the user's form.
+ */
+function recaptcha_get_html ($pubkey, $error = null, $use_ssl = false)
+{
+ if ($pubkey == null || $pubkey == '') {
+ die ("To use reCAPTCHA you must get an API key from <a href='https://www.google.com/recaptcha/admin/create'>https://www.google.com/recaptcha/admin/create</a>");
+ }
+
+ if ($use_ssl) {
+ $server = RECAPTCHA_API_SECURE_SERVER;
+ } else {
+ $server = RECAPTCHA_API_SERVER;
+ }
+
+ $errorpart = "";
+ if ($error) {
+ $errorpart = "&amp;error=" . $error;
+ }
+ return '<script type="text/javascript" src="'. $server . '/challenge?k=' . $pubkey . $errorpart . '"></script>
+
+ <noscript>
+ <iframe src="'. $server . '/noscript?k=' . $pubkey . $errorpart . '" height="300" width="500" frameborder="0"></iframe><br/>
+ <textarea name="recaptcha_challenge_field" rows="3" cols="40"></textarea>
+ <input type="hidden" name="recaptcha_response_field" value="manual_challenge"/>
+ </noscript>';
+}
+
+
+
+
+/**
+ * A ReCaptchaResponse is returned from recaptcha_check_answer()
+ */
+class ReCaptchaResponse {
+ var $is_valid;
+ var $error;
+}
+
+
+/**
+ * Calls an HTTP POST function to verify if the user's guess was correct
+ * @param string $privkey
+ * @param string $remoteip
+ * @param string $challenge
+ * @param string $response
+ * @param array $extra_params an array of extra variables to post to the server
+ * @return ReCaptchaResponse
+ */
+function recaptcha_check_answer ($privkey, $remoteip, $challenge, $response, $extra_params = array())
+{
+ if ($privkey == null || $privkey == '') {
+ die ("To use reCAPTCHA you must get an API key from <a href='https://www.google.com/recaptcha/admin/create'>https://www.google.com/recaptcha/admin/create</a>");
+ }
+
+ if ($remoteip == null || $remoteip == '') {
+ die ("For security reasons, you must pass the remote ip to reCAPTCHA");
+ }
+
+
+
+ //discard spam submissions
+ if ($challenge == null || strlen($challenge) == 0 || $response == null || strlen($response) == 0) {
+ $recaptcha_response = new ReCaptchaResponse();
+ $recaptcha_response->is_valid = false;
+ $recaptcha_response->error = 'incorrect-captcha-sol';
+ return $recaptcha_response;
+ }
+
+ $response = _recaptcha_http_post (RECAPTCHA_VERIFY_SERVER, "/recaptcha/api/verify",
+ array (
+ 'privatekey' => $privkey,
+ 'remoteip' => $remoteip,
+ 'challenge' => $challenge,
+ 'response' => $response
+ ) + $extra_params
+ );
+
+ $answers = explode ("\n", $response [1]);
+ $recaptcha_response = new ReCaptchaResponse();
+
+ if (trim ($answers [0]) == 'true') {
+ $recaptcha_response->is_valid = true;
+ }
+ else {
+ $recaptcha_response->is_valid = false;
+ $recaptcha_response->error = $answers [1];
+ }
+ return $recaptcha_response;
+
+}
+
+/**
+ * gets a URL where the user can sign up for reCAPTCHA. If your application
+ * has a configuration page where you enter a key, you should provide a link
+ * using this function.
+ * @param string $domain The domain where the page is hosted
+ * @param string $appname The name of your application
+ */
+function recaptcha_get_signup_url ($domain = null, $appname = null) {
+ return "https://www.google.com/recaptcha/admin/create?" . _recaptcha_qsencode (array ('domains' => $domain, 'app' => $appname));
+}
+
+function _recaptcha_aes_pad($val) {
+ $block_size = 16;
+ $numpad = $block_size - (strlen ($val) % $block_size);
+ return str_pad($val, strlen ($val) + $numpad, chr($numpad));
+}
+
+/* Mailhide related code */
+
+function _recaptcha_aes_encrypt($val,$ky) {
+ if (! function_exists ("mcrypt_encrypt")) {
+ die ("To use reCAPTCHA Mailhide, you need to have the mcrypt php module installed.");
+ }
+ $mode=MCRYPT_MODE_CBC;
+ $enc=MCRYPT_RIJNDAEL_128;
+ $val=_recaptcha_aes_pad($val);
+ return mcrypt_encrypt($enc, $ky, $val, $mode, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0");
+}
+
+
+function _recaptcha_mailhide_urlbase64 ($x) {
+ return strtr(base64_encode ($x), '+/', '-_');
+}
+
+/* gets the reCAPTCHA Mailhide url for a given email, public key and private key */
+function recaptcha_mailhide_url($pubkey, $privkey, $email) {
+ if ($pubkey == '' || $pubkey == null || $privkey == "" || $privkey == null) {
+ die ("To use reCAPTCHA Mailhide, you have to sign up for a public and private key, " .
+ "you can do so at <a href='http://www.google.com/recaptcha/mailhide/apikey'>http://www.google.com/recaptcha/mailhide/apikey</a>");
+ }
+
+
+ $ky = pack('H*', $privkey);
+ $cryptmail = _recaptcha_aes_encrypt ($email, $ky);
+
+ return "http://www.google.com/recaptcha/mailhide/d?k=" . $pubkey . "&c=" . _recaptcha_mailhide_urlbase64 ($cryptmail);
+}
+
+/**
+ * gets the parts of the email to expose to the user.
+ * eg, given johndoe@example,com return ["john", "example.com"].
+ * the email is then displayed as john...@example.com
+ */
+function _recaptcha_mailhide_email_parts ($email) {
+ $arr = preg_split("/@/", $email );
+
+ if (strlen ($arr[0]) <= 4) {
+ $arr[0] = substr ($arr[0], 0, 1);
+ } else if (strlen ($arr[0]) <= 6) {
+ $arr[0] = substr ($arr[0], 0, 3);
+ } else {
+ $arr[0] = substr ($arr[0], 0, 4);
+ }
+ return $arr;
+}
+
+/**
+ * Gets html to display an email address given a public an private key.
+ * to get a key, go to:
+ *
+ * http://www.google.com/recaptcha/mailhide/apikey
+ */
+function recaptcha_mailhide_html($pubkey, $privkey, $email) {
+ $emailparts = _recaptcha_mailhide_email_parts ($email);
+ $url = recaptcha_mailhide_url ($pubkey, $privkey, $email);
+
+ return htmlentities($emailparts[0]) . "<a href='" . htmlentities ($url) .
+ "' onclick=\"window.open('" . htmlentities ($url) . "', '', 'toolbar=0,scrollbars=0,location=0,statusbar=0,menubar=0,resizable=0,width=500,height=300'); return false;\" title=\"Reveal this e-mail address\">...</a>@" . htmlentities ($emailparts [1]);
+
+}
+
+
+?>
diff --git a/libraries/plugins/auth/swekey/authentication.inc.php b/libraries/plugins/auth/swekey/authentication.inc.php
new file mode 100644
index 0000000000..1977f883e1
--- /dev/null
+++ b/libraries/plugins/auth/swekey/authentication.inc.php
@@ -0,0 +1,172 @@
+<?php
+/**
+ * @package Swekey
+ */
+?>
+
+<script>
+
+ var g_SwekeyPlugin = null;
+
+ // -------------------------------------------------------------------
+ // Create the swekey plugin if it does not exists
+ function Swekey_Plugin()
+ {
+ try
+ {
+ if (g_SwekeyPlugin != null)
+ return g_SwekeyPlugin;
+
+ if (window.ActiveXObject)
+ {
+ g_SwekeyPlugin = document.getElementById("swekey_activex");
+ if (g_SwekeyPlugin == null)
+ {
+ // we must create the activex that way instead of new ActiveXObject("FbAuthAx.FbAuthCtl");
+ // ortherwise SetClientSite is not called and we can not get the url
+ var div = document.createElement('div');
+ div.innerHTML='<object id="swekey_activex" style="display:none" CLASSID="CLSID:8E02E3F9-57AA-4EE1-AA68-A42DD7B0FADE"></object>';
+
+ // Never append to the body because it may still loading and it breaks IE
+ document.body.insertBefore(div, document.body.firstChild);
+ g_SwekeyPlugin = document.getElementById("swekey_activex");
+ }
+ return g_SwekeyPlugin;
+ }
+
+ g_SwekeyPlugin = document.getElementById("swekey_plugin");
+ if (g_SwekeyPlugin != null)
+ return g_SwekeyPlugin;
+
+ for (i = 0; i < navigator.plugins.length; i ++)
+ {
+ try
+ {
+ if (navigator.plugins[i] == null)
+ {
+ navigator.plugins.refresh();
+ }
+ else if (navigator.plugins[i][0] != null && navigator.plugins[i][0].type == "application/fbauth-plugin")
+ {
+ var x = document.createElement('embed');
+ x.setAttribute('type', 'application/fbauth-plugin');
+ x.setAttribute('id', 'swekey_plugin');
+ x.setAttribute('width', '0');
+ x.setAttribute('height', '0');
+ x.style.dislay='none';
+
+ //document.body.appendChild(x);
+ document.body.insertBefore(x, document.body.firstChild);
+ g_SwekeyPlugin = document.getElementById("swekey_plugin");
+ return g_SwekeyPlugin;
+ }
+ }
+ catch (e)
+ {
+ navigator.plugins.refresh();
+ //alert ('Failed to create plugin: ' + e);
+ }
+ }
+ }
+ catch (e)
+ {
+ //alert("Swekey_Plugin " + e);
+ g_SwekeyPlugin = null;
+ }
+ return null;
+ }
+
+ // -------------------------------------------------------------------
+ // Returns true if the swekey plugin is installed
+ function Swekey_Installed()
+ {
+ return (Swekey_Plugin() != null);
+ }
+
+ // -------------------------------------------------------------------
+ // List the id of the Swekey connected to the PC
+ // Returns a string containing comma separated Swekey Ids
+ // A Swekey is a 32 char hexadecimal value.
+ function Swekey_ListKeyIds()
+ {
+ try
+ {
+ return Swekey_Plugin().list();
+ }
+ catch (e)
+ {
+// alert("Swekey_ListKeyIds " + e);
+ }
+ return "";
+ }
+
+ // -------------------------------------------------------------------
+ // Ask the Connected Swekey to generate an OTP
+ // id: The id of the connected Swekey (returne by Swekey_ListKeyIds())
+ // rt: A random token
+ // return: The calculated OTP encoded in a 64 chars hexadecimal value.
+ function Swekey_GetOtp(id, rt)
+ {
+ try
+ {
+ return Swekey_Plugin().getotp(id, rt);
+ }
+ catch (e)
+ {
+// alert("Swekey_GetOtp " + e);
+ }
+ return "";
+ }
+
+ // -------------------------------------------------------------------
+ // Ask the Connected Swekey to generate a OTP linked to the current https host
+ // id: The id of the connected Swekey (returne by Swekey_ListKeyIds())
+ // rt: A random token
+ // return: The calculated OTP encoded in a 64 chars hexadecimal value.
+ // or "" if the current url does not start with https
+ function Swekey_GetLinkedOtp(id, rt)
+ {
+ try
+ {
+ return Swekey_Plugin().getlinkedotp(id, rt);
+ }
+ catch (e)
+ {
+// alert("Swekey_GetSOtp " + e);
+ }
+ return "";
+ }
+
+ // -------------------------------------------------------------------
+ // Calls Swekey_GetOtp or Swekey_GetLinkedOtp depending if we are in
+ // an https page or not.
+ // id: The id of the connected Swekey (returne by Swekey_ListKeyIds())
+ // rt: A random token
+ // return: The calculated OTP encoded in a 64 chars hexadecimal value.
+ function Swekey_GetSmartOtp(id, rt)
+ {
+ var res = Swekey_GetLinkedOtp(id, rt);
+ if (res == "")
+ res = Swekey_GetOtp(id, rt);
+
+ return res;
+ }
+
+ // -------------------------------------------------------------------
+ // Set a unplug handler (url) to the specified connected feebee
+ // id: The id of the connected Swekey (returne by Swekey_ListKeyIds())
+ // key: The key that index that url, (aplhanumeric values only)
+ // url: The url that will be launched ("" deletes the url)
+ function Swekey_SetUnplugUrl(id, key, url)
+ {
+ try
+ {
+ return Swekey_Plugin().setunplugurl(id, key, url);
+ }
+ catch (e)
+ {
+// alert("Swekey_SetUnplugUrl " + e);
+ }
+ }
+
+</script>
diff --git a/libraries/plugins/auth/swekey/musbe-ca.crt b/libraries/plugins/auth/swekey/musbe-ca.crt
new file mode 100644
index 0000000000..2a31ad18f9
--- /dev/null
+++ b/libraries/plugins/auth/swekey/musbe-ca.crt
@@ -0,0 +1,25 @@
+-----BEGIN CERTIFICATE-----
+MIIEKjCCAxKgAwIBAgIJAMjw7QcLWCd6MA0GCSqGSIb3DQEBBQUAMGsxCzAJBgNV
+BAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRQwEgYDVQQKEwtNdXNiZSwgSW5j
+LjESMBAGA1UEAxMJbXVzYmUuY29tMR0wGwYJKoZIhvcNAQkBFg5pbmZvQG11c2Jl
+LmNvbTAeFw0wODA5MDQxNDE2MTNaFw0zNzEyMjExNDE2MTNaMGsxCzAJBgNVBAYT
+AlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRQwEgYDVQQKEwtNdXNiZSwgSW5jLjES
+MBAGA1UEAxMJbXVzYmUuY29tMR0wGwYJKoZIhvcNAQkBFg5pbmZvQG11c2JlLmNv
+bTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOBhOljxVzQfK4gted2I
+d3BemcjW4abAUOzn3KYWXpPO5xIfVeXNDGkDbyH+X+7fo94sX25/ewuKNFDSOcvo
+tXHq7uQenTHB35r+a+LY81KceUHgW90a3XsqPAkwAjyYcgo3zmM2DtLvw+5Yod8T
+wAHk9m3qavnQ1uk99jBTwL7RZ9jIZHh9pFCL93uJc2obtd8O96Iycbn2q0w/AWbb
++eUVWIHzvLtfPvROeL3lJzr/Uz5LjKapxJ3qyqASflfHpnj9pU8l6g2TQ6Hg5KT5
+tLFkRe7uGhOfRtOQ/+NjaWrEuNCFnpyN4Q5Fv+5qA1Ip1IpH0200sWbAf/k2u0Qp
+Sx0CAwEAAaOB0DCBzTAdBgNVHQ4EFgQUczJrQ7hCvtsnzcqiDIZ/GSn/CiwwgZ0G
+A1UdIwSBlTCBkoAUczJrQ7hCvtsnzcqiDIZ/GSn/Ciyhb6RtMGsxCzAJBgNVBAYT
+AlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRQwEgYDVQQKEwtNdXNiZSwgSW5jLjES
+MBAGA1UEAxMJbXVzYmUuY29tMR0wGwYJKoZIhvcNAQkBFg5pbmZvQG11c2JlLmNv
+bYIJAMjw7QcLWCd6MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAGxk
+8xzIljeBDQWWVRr0NEALVSv3i09V4jAKkyEOfmZ8lKMKJi0atwbtjrXTzLnNYj+Q
+pyUbyY/8ItWvV7pnVxMiF9qcer7e9X4vw358GZuMVE/da1nWxz+CwzTm5oO30RzA
+antM9bISFFr9lJq69bDWOnCUi1IG8DSL3TxtlABso7S4vqiZ+sB33l6k1K4a/Njb
+QkU9UejKhKkVVZTsOrumfnOJ4MCmPfX8Y/AY2o670y5HnzpxerIYziCVzApPVrW7
+sKH0tuVGturMfQOKgstYe4/m9glBTeTLMkjD+6MJC2ONBD7GAiOO95gNl5M1fzJQ
+FEe5CJ7DCYl0GdmLXXw=
+-----END CERTIFICATE-----
diff --git a/libraries/plugins/auth/swekey/swekey.auth.lib.php b/libraries/plugins/auth/swekey/swekey.auth.lib.php
new file mode 100644
index 0000000000..9c36efcb03
--- /dev/null
+++ b/libraries/plugins/auth/swekey/swekey.auth.lib.php
@@ -0,0 +1,302 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * @package Swekey
+ */
+
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ * Checks Swekey authentication.
+ */
+function Swekey_Auth_check()
+{
+ global $cfg;
+ $confFile = $cfg['Server']['auth_swekey_config'];
+
+ if (! isset($_SESSION['SWEKEY'])) {
+ $_SESSION['SWEKEY'] = array();
+ }
+
+ $_SESSION['SWEKEY']['ENABLED'] = (! empty($confFile) && file_exists($confFile));
+
+ // Load the swekey.conf file the first time
+ if ($_SESSION['SWEKEY']['ENABLED']
+ && empty($_SESSION['SWEKEY']['CONF_LOADED'])
+ ) {
+ $_SESSION['SWEKEY']['CONF_LOADED'] = true;
+ $_SESSION['SWEKEY']['VALID_SWEKEYS'] = array();
+ $valid_swekeys = explode("\n", @file_get_contents($confFile));
+ foreach ($valid_swekeys as $line) {
+ if (preg_match("/^[0-9A-F]{32}:.+$/", $line) != false) {
+ $items = explode(":", $line);
+ if (count($items) == 2) {
+ $_SESSION['SWEKEY']['VALID_SWEKEYS'][$items[0]] = trim($items[1]);
+ }
+ } elseif (preg_match("/^[A-Z_]+=.*$/", $line) != false) {
+ $items = explode("=", $line);
+ $_SESSION['SWEKEY']['CONF_'.trim($items[0])] = trim($items[1]);
+ }
+ }
+
+ // Set default values for settings
+ if (! isset($_SESSION['SWEKEY']['CONF_SERVER_CHECK'])) {
+ $_SESSION['SWEKEY']['CONF_SERVER_CHECK'] = "";
+ }
+ if (! isset($_SESSION['SWEKEY']['CONF_SERVER_RNDTOKEN'])) {
+ $_SESSION['SWEKEY']['CONF_SERVER_RNDTOKEN'] = "";
+ }
+ if (! isset($_SESSION['SWEKEY']['CONF_SERVER_STATUS'])) {
+ $_SESSION['SWEKEY']['CONF_SERVER_STATUS'] = "";
+ }
+ if (! isset($_SESSION['SWEKEY']['CONF_CA_FILE'])) {
+ $_SESSION['SWEKEY']['CONF_CA_FILE'] = "";
+ }
+ if (! isset($_SESSION['SWEKEY']['CONF_ENABLE_TOKEN_CACHE'])) {
+ $_SESSION['SWEKEY']['CONF_ENABLE_TOKEN_CACHE'] = true;
+ }
+ if (! isset($_SESSION['SWEKEY']['CONF_DEBUG'])) {
+ $_SESSION['SWEKEY']['CONF_DEBUG'] = false;
+ }
+ }
+
+ // check if a web key has been authenticated
+ if ($_SESSION['SWEKEY']['ENABLED']) {
+ if (empty($_SESSION['SWEKEY']['AUTHENTICATED_SWEKEY'])) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+
+/**
+ * Handle Swekey authentication error.
+ */
+function Swekey_Auth_error()
+{
+ if (! isset($_SESSION['SWEKEY'])) {
+ return null;
+ }
+
+ if (! $_SESSION['SWEKEY']['ENABLED']) {
+ return null;
+ }
+
+ include_once './libraries/plugins/auth/swekey/authentication.inc.php';
+
+ ?>
+ <script>
+ function Swekey_GetValidKey()
+ {
+ var valids = "<?php
+ foreach ($_SESSION['SWEKEY']['VALID_SWEKEYS'] as $key => $value) {
+ echo $key.',';
+ }
+ ?>";
+ var connected_keys = Swekey_ListKeyIds().split(",");
+ for (i in connected_keys) {
+ if (connected_keys[i] != null && connected_keys[i].length == 32) {
+ if (valids.indexOf(connected_keys[i]) >= 0) {
+ return connected_keys[i];
+ }
+ }
+ }
+
+
+ if (connected_keys.length > 0) {
+ if (connected_keys[0].length == 32) {
+ return "unknown_key_" + connected_keys[0];
+ }
+ }
+
+ return "none";
+ }
+
+ var key = Swekey_GetValidKey();
+
+ function timedCheck()
+ {
+ if (key != Swekey_GetValidKey()) {
+ window.location.search = "?swekey_reset";
+ } else {
+ setTimeout("timedCheck()",1000);
+ }
+ }
+
+ setTimeout("timedCheck()",1000);
+ </script>
+ <?php
+
+ if (! empty($_SESSION['SWEKEY']['AUTHENTICATED_SWEKEY'])) {
+ return null;
+ }
+
+ if (count($_SESSION['SWEKEY']['VALID_SWEKEYS']) == 0) {
+ return sprintf(
+ __('File %s does not contain any key id'),
+ $GLOBALS['cfg']['Server']['auth_swekey_config']
+ );
+ }
+
+ include_once "libraries/plugins/auth/swekey/swekey.php";
+
+ Swekey_SetCheckServer($_SESSION['SWEKEY']['CONF_SERVER_CHECK']);
+ Swekey_SetRndTokenServer($_SESSION['SWEKEY']['CONF_SERVER_RNDTOKEN']);
+ Swekey_SetStatusServer($_SESSION['SWEKEY']['CONF_SERVER_STATUS']);
+ Swekey_EnableTokenCache($_SESSION['SWEKEY']['CONF_ENABLE_TOKEN_CACHE']);
+
+ $caFile = $_SESSION['SWEKEY']['CONF_CA_FILE'];
+ if (empty($caFile)) {
+ $caFile = __FILE__;
+ $pos = strrpos($caFile, '/');
+ if ($pos === false) {
+ $pos = strrpos($caFile, '\\'); // windows
+ }
+ $caFile = substr($caFile, 0, $pos + 1).'musbe-ca.crt';
+ // echo "\n<!-- $caFile -->\n";
+ // if (file_exists($caFile))
+ // echo "<!-- exists -->\n";
+ }
+
+ if (file_exists($caFile)) {
+ Swekey_SetCAFile($caFile);
+ } elseif (! empty($caFile)
+ && (substr($_SESSION['SWEKEY']['CONF_SERVER_CHECK'], 0, 8) == "https://")) {
+ return "Internal Error: CA File $caFile not found";
+ }
+
+ $result = null;
+ $swekey_id = $_GET['swekey_id'];
+ $swekey_otp = $_GET['swekey_otp'];
+
+ if (isset($swekey_id)) {
+ unset($_SESSION['SWEKEY']['AUTHENTICATED_SWEKEY']);
+ if (! isset($_SESSION['SWEKEY']['RND_TOKEN'])) {
+ unset($swekey_id);
+ } else {
+ if (strlen($swekey_id) == 32) {
+ $res = Swekey_CheckOtp($swekey_id, $_SESSION['SWEKEY']['RND_TOKEN'], $swekey_otp);
+ unset($_SESSION['SWEKEY']['RND_TOKEN']);
+ if (! $res) {
+ $result = __('Hardware authentication failed!') . ' (' . Swekey_GetLastError() . ')';
+ } else {
+ $_SESSION['SWEKEY']['AUTHENTICATED_SWEKEY'] = $swekey_id;
+ $_SESSION['SWEKEY']['FORCE_USER'] = $_SESSION['SWEKEY']['VALID_SWEKEYS'][$swekey_id];
+ return null;
+ }
+ } else {
+ $result = __('No valid authentication key plugged');
+ if ($_SESSION['SWEKEY']['CONF_DEBUG']) {
+ $result .= "<br/>" . htmlspecialchars($swekey_id);
+ }
+ unset($_SESSION['SWEKEY']['CONF_LOADED']); // reload the conf file
+ }
+ }
+ } else {
+ unset($_SESSION['SWEKEY']);
+ }
+
+ $_SESSION['SWEKEY']['RND_TOKEN'] = Swekey_GetFastRndToken();
+ if (strlen($_SESSION['SWEKEY']['RND_TOKEN']) != 64) {
+ $result = __('Hardware authentication failed!') . ' (' . Swekey_GetLastError() . ')';
+ unset($_SESSION['SWEKEY']['CONF_LOADED']); // reload the conf file
+ }
+
+ if (! isset($swekey_id)) {
+ ?>
+ <script>
+ if (key.length != 32) {
+ window.location.search="?swekey_id=" + key + "&token=<?php echo $_SESSION[' PMA_token ']; ?>";
+ } else {
+ var url = "" + window.location;
+ if (url.indexOf("?") > 0) {
+ url = url.substr(0, url.indexOf("?"));
+ }
+ Swekey_SetUnplugUrl(key, "pma_login", url + "?session_to_unset=<?php echo session_id();?>&token=<?php echo $_SESSION[' PMA_token ']; ?>");
+ var otp = Swekey_GetOtp(key, <?php echo '"'.$_SESSION['SWEKEY']['RND_TOKEN'].'"';?>);
+ window.location.search="?swekey_id=" + key + "&swekey_otp=" + otp + "&token=<?php echo $_SESSION[' PMA_token ']; ?>";
+ }
+ </script>
+ <?php
+ return __('Authenticating…');
+ }
+
+ return $result;
+}
+
+
+/**
+ * Perform login using Swekey.
+ */
+function Swekey_login($input_name, $input_go)
+{
+ $swekeyErr = Swekey_Auth_error();
+ if ($swekeyErr != null) {
+ PMA_Message::error($swekeyErr)->display();
+ if ($GLOBALS['error_handler']->hasDisplayErrors()) {
+ echo '<div>';
+ $GLOBALS['error_handler']->dispErrors();
+ echo '</div>';
+ }
+ }
+
+ if (isset($_SESSION['SWEKEY']) && $_SESSION['SWEKEY']['ENABLED']) {
+ echo '<script type="text/javascript">';
+ if (empty($_SESSION['SWEKEY']['FORCE_USER'])) {
+ echo 'var user = null;';
+ } else {
+ echo 'var user = "'.$_SESSION['SWEKEY']['FORCE_USER'].'";';
+ }
+
+ ?>
+ function open_swekey_site()
+ {
+ window.open("<?php echo PMA_linkURL('http://phpmyadmin.net/auth_key'); ?>");
+ }
+
+ var input_username = document.getElementById("<?php echo $input_name; ?>");
+ var input_go = document.getElementById("<?php echo $input_go; ?>");
+ var swekey_status = document.createElement('img');
+ swekey_status.setAttribute('onclick', 'open_swekey_site()');
+ swekey_status.setAttribute('style', 'width:8px; height:16px; border:0px; vspace:0px; hspace:0px; frameborder:no');
+ if (user == null) {
+ swekey_status.setAttribute('src', 'http://artwork.swekey.com/unplugged-8x16.png');
+ //swekey_status.setAttribute('title', 'No swekey plugged');
+ input_go.disabled = true;
+ } else {
+ swekey_status.setAttribute('src', 'http://artwork.swekey.com/plugged-8x16.png');
+ //swekey_status.setAttribute('title', 'swekey plugged');
+ input_username.value = user;
+ }
+ input_username.readOnly = true;
+
+ if (input_username.nextSibling == null) {
+ input_username.parentNode.appendChild(swekey_status);
+ } else {
+ input_username.parentNode.insertBefore(swekey_status, input_username.nextSibling);
+ }
+
+ <?php
+ echo '</script>';
+ }
+}
+
+if (!empty($_GET['session_to_unset'])) {
+ session_write_close();
+ session_id($_GET['session_to_unset']);
+ session_start();
+ $_SESSION = array();
+ session_write_close();
+ session_destroy();
+ exit;
+}
+
+if (isset($_GET['swekey_reset'])) {
+ unset($_SESSION['SWEKEY']);
+}
+
+?>
diff --git a/libraries/plugins/auth/swekey/swekey.php b/libraries/plugins/auth/swekey/swekey.php
new file mode 100644
index 0000000000..d495d45b4f
--- /dev/null
+++ b/libraries/plugins/auth/swekey/swekey.php
@@ -0,0 +1,522 @@
+<?php
+/**
+ * Library that provides common functions that are used to help integrating Swekey Authentication in a PHP web site
+ * Version 1.0
+ *
+ * History:
+ * 1.2 Use curl (widely installed) to query the server
+ * Fixed a possible tempfile race attack
+ * Random token cache can now be disabled
+ * 1.1 Added Swekey_HttpGet function that support faulty servers
+ * Support for custom servers
+ * 1.0 First release
+ *
+ * @package Swekey
+ */
+
+
+/**
+ * Errors codes
+ */
+define("SWEKEY_ERR_INVALID_DEV_STATUS", 901); // The satus of the device is not SWEKEY_STATUS_OK
+define("SWEKEY_ERR_INTERNAL", 902); // Should never occurd
+define("SWEKEY_ERR_OUTDATED_RND_TOKEN", 910); // You random token is too old
+define("SWEKEY_ERR_INVALID_OTP", 911); // The otp was not correct
+
+/**
+ * Those errors are considered as an attack and your site will be blacklisted during one minute
+ * if you receive one of those errors
+ */
+define("SWEKEY_ERR_BADLY_ENCODED_REQUEST", 920);
+define("SWEKEY_ERR_INVALID_RND_TOKEN", 921);
+define("SWEKEY_ERR_DEV_NOT_FOUND", 922);
+
+/**
+ * Default values for configuration.
+ */
+define('SWEKEY_DEFAULT_CHECK_SERVER', 'https://auth-check.musbe.net');
+define('SWEKEY_DEFAULT_RND_SERVER', 'https://auth-rnd-gen.musbe.net');
+define('SWEKEY_DEFAULT_STATUS_SERVER', 'https://auth-status.musbe.net');
+
+/**
+ * The last error of an operation is alway put in this global var
+ */
+
+global $gSwekeyLastError;
+$gSwekeyLastError = 0;
+
+global $gSwekeyLastResult;
+$gSwekeyLastResult = "<not set>";
+
+/**
+ * Servers addresses
+ * Use the Swekey_SetXxxServer($server) functions to set them
+ */
+
+global $gSwekeyCheckServer;
+if (! isset($gSwekeyCheckServer)) {
+ $gSwekeyCheckServer = SWEKEY_DEFAULT_CHECK_SERVER;
+}
+
+global $gSwekeyRndTokenServer;
+if (! isset($gSwekeyRndTokenServer)) {
+ $gSwekeyRndTokenServer = SWEKEY_DEFAULT_RND_SERVER;
+}
+
+global $gSwekeyStatusServer;
+if (! isset($gSwekeyStatusServer)) {
+ $gSwekeyStatusServer = SWEKEY_DEFAULT_STATUS_SERVER;
+}
+
+global $gSwekeyCA;
+
+global $gSwekeyTokenCacheEnabled;
+if (! isset($gSwekeyTokenCacheEnabled)) {
+ $gSwekeyTokenCacheEnabled = true;
+}
+
+/**
+ * Change the address of the Check server.
+ * If $server is empty the default value 'http://auth-check.musbe.net' will be used
+ *
+ * @param server The protocol and hostname to use
+ *
+ * @access public
+ */
+function Swekey_SetCheckServer($server)
+{
+ global $gSwekeyCheckServer;
+ if (empty($server)) {
+ $gSwekeyCheckServer = SWEKEY_DEFAULT_CHECK_SERVER;
+ } else {
+ $gSwekeyCheckServer = $server;
+ }
+}
+
+/**
+ * Change the address of the Random Token Generator server.
+ * If $server is empty the default value 'http://auth-rnd-gen.musbe.net' will be used
+ *
+ * @param server The protocol and hostname to use
+ *
+ * @access public
+ */
+function Swekey_SetRndTokenServer($server)
+{
+ global $gSwekeyRndTokenServer;
+ if (empty($server)) {
+ $gSwekeyRndTokenServer = SWEKEY_DEFAULT_RND_SERVER;
+ } else {
+ $gSwekeyRndTokenServer = $server;
+ }
+}
+
+/**
+ * Change the address of the Satus server.
+ * If $server is empty the default value 'http://auth-status.musbe.net' will be used
+ *
+ * @param server The protocol and hostname to use
+ *
+ * @access public
+ */
+function Swekey_SetStatusServer($server)
+{
+ global $gSwekeyStatusServer;
+ if (empty($server)) {
+ $gSwekeyStatusServer = SWEKEY_DEFAULT_STATUS_SERVER;
+ } else {
+ $gSwekeyStatusServer = $server;
+ }
+}
+
+/**
+ * Change the certificat file in case of the the severs use https instead of http
+ *
+ * @param cafile The path of the crt file to use
+ *
+ * @access public
+ */
+function Swekey_SetCAFile($cafile)
+{
+ global $gSwekeyCA;
+ $gSwekeyCA = $cafile;
+}
+
+/**
+ * Enable or disable the random token caching
+ * Because everybody has full access to the cache file, it can be a DOS vulnerability
+ * So disable it if you are running in a non secure enviromnement
+ *
+ * @param $enable
+ *
+ * @access public
+ */
+function Swekey_EnableTokenCache($enable)
+{
+ global $gSwekeyTokenCacheEnabled;
+ $gSwekeyTokenCacheEnabled = ! empty($enable);
+}
+
+
+/**
+ * Return the last error.
+ *
+ * @return The Last Error
+ * @access public
+ */
+function Swekey_GetLastError()
+{
+ global $gSwekeyLastError;
+ return $gSwekeyLastError;
+}
+
+/**
+ * Return the last result.
+ *
+ * @return The Last Error
+ * @access public
+ */
+function Swekey_GetLastResult()
+{
+ global $gSwekeyLastResult;
+ return $gSwekeyLastResult;
+}
+
+/**
+ * Send a synchronous request to the server.
+ * This function manages timeout then will not block if one of the server is down
+ *
+ * @param url The url to get
+ * @param response_code The response code
+ *
+ * @return The body of the response or "" in case of error
+ * @access private
+ */
+function Swekey_HttpGet($url, &$response_code)
+{
+ global $gSwekeyLastError;
+ $gSwekeyLastError = 0;
+ global $gSwekeyLastResult;
+ $gSwekeyLastResult = "<not set>";
+
+ // use curl if available
+ if (function_exists('curl_init')) {
+ $sess = curl_init($url);
+ if (substr($url, 0, 8) == "https://") {
+ global $gSwekeyCA;
+
+ if (! empty($gSwekeyCA)) {
+ if (file_exists($gSwekeyCA)) {
+ if (! curl_setopt($sess, CURLOPT_CAINFO, $gSwekeyCA)) {
+ error_log("SWEKEY_ERROR:Could not set CA file : ".curl_error($sess));
+ } else {
+ $caFileOk = true;
+ }
+ } else {
+ error_log("SWEKEY_ERROR:Could not find CA file $gSwekeyCA getting $url");
+ }
+ }
+
+ curl_setopt($sess, CURLOPT_SSL_VERIFYHOST, '2');
+ curl_setopt($sess, CURLOPT_SSL_VERIFYPEER, '2');
+ curl_setopt($sess, CURLOPT_CONNECTTIMEOUT, '20');
+ curl_setopt($sess, CURLOPT_TIMEOUT, '20');
+ } else {
+ curl_setopt($sess, CURLOPT_CONNECTTIMEOUT, '3');
+ curl_setopt($sess, CURLOPT_TIMEOUT, '5');
+ }
+
+ curl_setopt($sess, CURLOPT_RETURNTRANSFER, '1');
+ $res=curl_exec($sess);
+ $response_code = curl_getinfo($sess, CURLINFO_HTTP_CODE);
+ $curlerr = curl_error($sess);
+ curl_close($sess);
+
+ if ($response_code == 200) {
+ $gSwekeyLastResult = $res;
+ return $res;
+ }
+
+ if (! empty($response_code)) {
+ $gSwekeyLastError = $response_code;
+ error_log("SWEKEY_ERROR:Error $gSwekeyLastError ($curlerr) getting $url");
+ return "";
+ }
+
+ $response_code = 408; // Request Timeout
+ $gSwekeyLastError = $response_code;
+ error_log("SWEKEY_ERROR:Error $curlerr getting $url");
+ return "";
+ }
+
+ // use pecl_http if available
+ if (class_exists('HttpRequest')) {
+ // retry if one of the server is down
+ for ($num=1; $num <= 3; $num++ ) {
+ $r = new HttpRequest($url);
+ $options = array('timeout' => '3');
+
+ if (substr($url, 0, 6) == "https:") {
+ $sslOptions = array();
+ $sslOptions['verifypeer'] = true;
+ $sslOptions['verifyhost'] = true;
+
+ $capath = __FILE__;
+ $name = strrchr($capath, '/');
+ // windows
+ if (empty($name)) {
+ $name = strrchr($capath, '\\');
+ }
+ $capath = substr($capath, 0, strlen($capath) - strlen($name) + 1).'musbe-ca.crt';
+
+ if (! empty($gSwekeyCA)) {
+ $sslOptions['cainfo'] = $gSwekeyCA;
+ }
+
+ $options['ssl'] = $sslOptions;
+ }
+
+ $r->setOptions($options);
+
+ // try
+ {
+ $reply = $r->send();
+ $res = $reply->getBody();
+ $info = $r->getResponseInfo();
+ $response_code = $info['response_code'];
+ if ($response_code != 200) {
+ $gSwekeyLastError = $response_code;
+ error_log("SWEKEY_ERROR:Error ".$gSwekeyLastError." getting ".$url);
+ return "";
+ }
+
+
+ $gSwekeyLastResult = $res;
+ return $res;
+ }
+ // catch (HttpException $e)
+ // {
+ // error_log("SWEKEY_WARNING:HttpException ".$e." getting ".$url);
+ // }
+ }
+
+ $response_code = 408; // Request Timeout
+ $gSwekeyLastError = $response_code;
+ error_log("SWEKEY_ERROR:Error ".$gSwekeyLastError." getting ".$url);
+ return "";
+ }
+
+ global $http_response_header;
+ $res = @file_get_contents($url);
+ $response_code = substr($http_response_header[0], 9, 3); //HTTP/1.0
+ if ($response_code == 200) {
+ $gSwekeyLastResult = $res;
+ return $res;
+ }
+
+ $gSwekeyLastError = $response_code;
+ error_log("SWEKEY_ERROR:Error ".$response_code." getting ".$url);
+ return "";
+}
+
+/**
+ * Get a Random Token from a Token Server
+ * The RT is a 64 vhars hexadecimal value
+ * You should better use Swekey_GetFastRndToken() for performance
+ * @access public
+ */
+function Swekey_GetRndToken()
+{
+ global $gSwekeyRndTokenServer;
+ return Swekey_HttpGet($gSwekeyRndTokenServer.'/FULL-RND-TOKEN', $response_code);
+}
+
+/**
+ * Get a Half Random Token from a Token Server
+ * The RT is a 64 vhars hexadecimal value
+ * Use this value if you want to make your own Swekey_GetFastRndToken()
+ * @access public
+ */
+function Swekey_GetHalfRndToken()
+{
+ global $gSwekeyRndTokenServer;
+ return Swekey_HttpGet($gSwekeyRndTokenServer.'/HALF-RND-TOKEN', $response_code);
+}
+
+/**
+ * Get a Half Random Token
+ * The RT is a 64 vhars hexadecimal value
+ * This function get a new random token and reuse it.
+ * Token are refetched from the server only once every 30 seconds.
+ * You should always use this function to get half random token.
+ * @access public
+ */
+function Swekey_GetFastHalfRndToken()
+{
+ global $gSwekeyTokenCacheEnabled;
+
+ $res = "";
+ $cachefile = "";
+
+ // We check if we have a valid RT is the session
+ if (isset($_SESSION['rnd-token-date'])) {
+ if (time() - $_SESSION['rnd-token-date'] < 30) {
+ $res = $_SESSION['rnd-token'];
+ }
+ }
+
+ // If not we try to get it from a temp file (PHP >= 5.2.1 only)
+ if (strlen($res) != 32 && $gSwekeyTokenCacheEnabled) {
+ if (function_exists('sys_get_temp_dir')) {
+ $tempdir = sys_get_temp_dir();
+ $cachefile = $tempdir."/swekey-rnd-token-".get_current_user();
+ $modif = filemtime($cachefile);
+ if ($modif != false) {
+ if (time() - $modif < 30) {
+ $res = @file_get_contents($cachefile);
+ if (strlen($res) != 32) {
+ $res = "";
+ } else {
+ $_SESSION['rnd-token'] = $res;
+ $_SESSION['rnd-token-date'] = $modif;
+ }
+ }
+ }
+ }
+ }
+
+ // If we don't have a valid RT here we have to get it from the server
+ if (strlen($res) != 32) {
+ $res = substr(Swekey_GetHalfRndToken(), 0, 32);
+ $_SESSION['rnd-token'] = $res;
+ $_SESSION['rnd-token-date'] = time();
+ if (! empty($cachefile)) {
+ // we unlink the file so no possible tempfile race attack
+ unlink($cachefile);
+ $file = fopen($cachefile, "x");
+ if ($file != false) {
+ @fwrite($file, $res);
+ @fclose($file);
+ }
+ }
+ }
+
+ return $res."00000000000000000000000000000000";
+}
+
+/**
+ * Get a Random Token
+ * The RT is a 64 vhars hexadecimal value
+ * This function generates a unique random token for each call but call the
+ * server only once every 30 seconds.
+ * You should always use this function to get random token.
+ * @access public
+ */
+function Swekey_GetFastRndToken()
+{
+ $res = Swekey_GetFastHalfRndToken();
+ if (strlen($res) == 64) {
+ return substr($res, 0, 32).strtoupper(md5("Musbe Authentication Key" . mt_rand() . date(DATE_ATOM)));
+ }
+ return "";
+}
+
+
+/**
+ * Checks that an OTP generated by a Swekey is valid
+ *
+ * @param id The id of the swekey
+ * @param rt The random token used to generate the otp
+ * @param otp The otp generated by the swekey
+ *
+ * @return true or false
+ * @access public
+ */
+function Swekey_CheckOtp($id, $rt, $otp)
+{
+ global $gSwekeyCheckServer;
+ $res = Swekey_HttpGet($gSwekeyCheckServer.'/CHECK-OTP/'.$id.'/'.$rt.'/'.$otp, $response_code);
+ return $response_code == 200 && $res == "OK";
+}
+
+/**
+ * Values that are associated with a key.
+ * The following values can be returned by the Swekey_GetStatus() function
+ */
+define("SWEKEY_STATUS_OK", 0);
+define("SWEKEY_STATUS_NOT_FOUND", 1); // The key does not exist in the db
+define("SWEKEY_STATUS_INACTIVE", 2); // The key has never been activated
+define("SWEKEY_STATUS_LOST", 3); // The user has lost his key
+define("SWEKEY_STATUS_STOLEN", 4); // The key was stolen
+define("SWEKEY_STATUS_FEE_DUE", 5); // The annual fee was not paid
+define("SWEKEY_STATUS_OBSOLETE", 6); // The hardware is no longer supported
+define("SWEKEY_STATUS_UNKOWN", 201); // We could not connect to the authentication server
+
+/**
+ * Values that are associated with a key.
+ * The Javascript Api can also return the following values
+ */
+define("SWEKEY_STATUS_REPLACED", 100); // This key has been replaced by a backup key
+define("SWEKEY_STATUS_BACKUP_KEY", 101); // This key is a backup key that is not activated yet
+define("SWEKEY_STATUS_NOTPLUGGED", 200); // This key is not plugged in the computer
+
+
+/**
+ * Return the text corresponding to the integer status of a key
+ *
+ * @param status The status
+ *
+ * @return The text corresponding to the status
+ * @access public
+ */
+function Swekey_GetStatusStr($status)
+{
+ switch($status)
+ {
+ case SWEKEY_STATUS_OK :
+ return 'OK';
+ case SWEKEY_STATUS_NOT_FOUND :
+ return 'Key does not exist in the db';
+ case SWEKEY_STATUS_INACTIVE :
+ return 'Key not activated';
+ case SWEKEY_STATUS_LOST :
+ return 'Key was lost';
+ case SWEKEY_STATUS_STOLEN :
+ return 'Key was stolen';
+ case SWEKEY_STATUS_FEE_DUE :
+ return 'The annual fee was not paid';
+ case SWEKEY_STATUS_OBSOLETE :
+ return 'Key no longer supported';
+ case SWEKEY_STATUS_REPLACED :
+ return 'This key has been replaced by a backup key';
+ case SWEKEY_STATUS_BACKUP_KEY :
+ return 'This key is a backup key that is not activated yet';
+ case SWEKEY_STATUS_NOTPLUGGED :
+ return 'This key is not plugged in the computer';
+ case SWEKEY_STATUS_UNKOWN :
+ return 'Unknow Status, could not connect to the authentication server';
+ }
+ return 'unknown status '.$status;
+}
+
+/**
+ * If your web site requires a key to login you should check that the key
+ * is still valid (has not been lost or stolen) before requiring it.
+ * A key can be authenticated only if its status is SWEKEY_STATUS_OK
+ *
+ * @param id The id of the swekey
+ *
+ * @return The status of the swekey
+ * @access public
+ */
+function Swekey_GetStatus($id)
+{
+ global $gSwekeyStatusServer;
+ $res = Swekey_HttpGet($gSwekeyStatusServer.'/GET-STATUS/'.$id, $response_code);
+ if ($response_code == 200) {
+ return intval($res);
+ }
+ return SWEKEY_STATUS_UNKOWN;
+}
+
+?>
diff --git a/libraries/plugins/export/ExportCodegen.class.php b/libraries/plugins/export/ExportCodegen.class.php
new file mode 100644
index 0000000000..4495e7ee4d
--- /dev/null
+++ b/libraries/plugins/export/ExportCodegen.class.php
@@ -0,0 +1,423 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Set of functions used to build NHibernate dumps of tables
+ *
+ * @package PhpMyAdmin-Export
+ * @subpackage CodeGen
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/* Get the export interface */
+require_once 'libraries/plugins/ExportPlugin.class.php';
+/* Get the table property class */
+require_once 'libraries/plugins/export/TableProperty.class.php';
+
+/**
+ * Handles the export for the CodeGen class
+ *
+ * @package PhpMyAdmin-Export
+ * @subpackage CodeGen
+ */
+class ExportCodegen extends ExportPlugin
+{
+ /**
+ * CodeGen Formats
+ *
+ * @var array
+ */
+ private $_cgFormats;
+
+ /**
+ * CodeGen Handlers
+ *
+ * @var array
+ */
+ private $_cgHandlers;
+
+ /**
+ * Constructor
+ */
+ public function __construct()
+ {
+ // initialize the specific export CodeGen variables
+ $this->initSpecificVariables();
+ $this->setProperties();
+ }
+
+ /**
+ * Initialize the local variables that are used for export CodeGen
+ *
+ * @return void
+ */
+ protected function initSpecificVariables()
+ {
+ $this->_setCgFormats(
+ array(
+ "NHibernate C# DO",
+ "NHibernate XML"
+ )
+ );
+
+ $this->_setCgHandlers(
+ array(
+ "_handleNHibernateCSBody",
+ "_handleNHibernateXMLBody"
+ )
+ );
+ }
+
+ /**
+ * Sets the export CodeGen properties
+ *
+ * @return void
+ */
+ protected function setProperties()
+ {
+ $props = 'libraries/properties/';
+ include_once "$props/plugins/ExportPluginProperties.class.php";
+ include_once "$props/options/groups/OptionsPropertyRootGroup.class.php";
+ include_once "$props/options/groups/OptionsPropertyMainGroup.class.php";
+ include_once "$props/options/items/HiddenPropertyItem.class.php";
+ include_once "$props/options/items/SelectPropertyItem.class.php";
+
+ $exportPluginProperties = new ExportPluginProperties();
+ $exportPluginProperties->setText('CodeGen');
+ $exportPluginProperties->setExtension('cs');
+ $exportPluginProperties->setMimeType('text/cs');
+ $exportPluginProperties->setOptionsText(__('Options'));
+
+ // create the root group that will be the options field for
+ // $exportPluginProperties
+ // this will be shown as "Format specific options"
+ $exportSpecificOptions = new OptionsPropertyRootGroup();
+ $exportSpecificOptions->setName("Format Specific Options");
+
+ // general options main group
+ $generalOptions = new OptionsPropertyMainGroup();
+ $generalOptions->setName("general_opts");
+ // create primary items and add them to the group
+ $leaf = new HiddenPropertyItem();
+ $leaf->setName("structure_or_data");
+ $generalOptions->addProperty($leaf);
+ $leaf = new SelectPropertyItem();
+ $leaf->setName("format");
+ $leaf->setText(__('Format:'));
+ $leaf->setValues($this->_getCgFormats());
+ $generalOptions->addProperty($leaf);
+ // add the main group to the root group
+ $exportSpecificOptions->addProperty($generalOptions);
+
+ // set the options for the export plugin property item
+ $exportPluginProperties->setOptions($exportSpecificOptions);
+ $this->properties = $exportPluginProperties;
+ }
+
+ /**
+ * This method is called when any PluginManager to which the observer
+ * is attached calls PluginManager::notify()
+ *
+ * @param SplSubject $subject The PluginManager notifying the observer
+ * of an update.
+ *
+ * @return void
+ */
+ public function update (SplSubject $subject)
+ {
+ }
+
+ /**
+ * Outputs export header
+ *
+ * @return bool Whether it succeeded
+ */
+ public function exportHeader ()
+ {
+ return true;
+ }
+
+ /**
+ * Outputs export footer
+ *
+ * @return bool Whether it succeeded
+ */
+ public function exportFooter ()
+ {
+ return true;
+ }
+
+ /**
+ * Outputs database header
+ *
+ * @param string $db Database name
+ *
+ * @return bool Whether it succeeded
+ */
+ public function exportDBHeader ($db)
+ {
+ return true;
+ }
+
+ /**
+ * Outputs database footer
+ *
+ * @param string $db Database name
+ *
+ * @return bool Whether it succeeded
+ */
+ public function exportDBFooter ($db)
+ {
+ return true;
+ }
+
+ /**
+ * Outputs CREATE DATABASE statement
+ *
+ * @param string $db Database name
+ *
+ * @return bool Whether it succeeded
+ */
+ public function exportDBCreate($db)
+ {
+ return true;
+ }
+
+ /**
+ * Outputs the content of a table in NHibernate format
+ *
+ * @param string $db database name
+ * @param string $table table name
+ * @param string $crlf the end of line sequence
+ * @param string $error_url the url to go back in case of error
+ * @param string $sql_query SQL query for obtaining data
+ *
+ * @return bool Whether it succeeded
+ */
+ public function exportData($db, $table, $crlf, $error_url, $sql_query)
+ {
+ $CG_FORMATS = $this->_getCgFormats();
+ $CG_HANDLERS = $this->_getCgHandlers();
+
+ $format = $GLOBALS['codegen_format'];
+ if (isset($CG_FORMATS[$format])) {
+ return PMA_exportOutputHandler(
+ $this->$CG_HANDLERS[$format]($db, $table, $crlf)
+ );
+ }
+ return PMA_exportOutputHandler(sprintf("%s is not supported.", $format));
+ }
+
+ /**
+ * Used to make identifiers (from table or database names)
+ *
+ * @param string $str name to be converted
+ * @param bool $ucfirst whether to make the first character uppercase
+ *
+ * @return string identifier
+ */
+ public static function cgMakeIdentifier($str, $ucfirst = true)
+ {
+ // remove unsafe characters
+ $str = preg_replace('/[^\p{L}\p{Nl}_]/u', '', $str);
+ // make sure first character is a letter or _
+ if (! preg_match('/^\pL/u', $str)) {
+ $str = '_' . $str;
+ }
+ if ($ucfirst) {
+ $str = ucfirst($str);
+ }
+ return $str;
+ }
+
+ /**
+ * C# Handler
+ *
+ * @param string $db database name
+ * @param string $table table name
+ * @param string $crlf line separator
+ *
+ * @return string containing C# code lines, separated by "\n"
+ */
+ private function _handleNHibernateCSBody($db, $table, $crlf)
+ {
+ $lines = array();
+
+ $result = $GLOBALS['dbi']->query(
+ sprintf(
+ 'DESC %s.%s', PMA_Util::backquote($db),
+ PMA_Util::backquote($table)
+ )
+ );
+ if ($result) {
+ $tableProperties = array();
+ while ($row = $GLOBALS['dbi']->fetchRow($result)) {
+ $tableProperties[] = new TableProperty($row);
+ }
+ $GLOBALS['dbi']->freeResult($result);
+ $lines[] = 'using System;';
+ $lines[] = 'using System.Collections;';
+ $lines[] = 'using System.Collections.Generic;';
+ $lines[] = 'using System.Text;';
+ $lines[] = 'namespace ' . ExportCodegen::cgMakeIdentifier($db);
+ $lines[] = '{';
+ $lines[] = ' #region ' . ExportCodegen::cgMakeIdentifier($table);
+ $lines[] = ' public class ' . ExportCodegen::cgMakeIdentifier($table);
+ $lines[] = ' {';
+ $lines[] = ' #region Member Variables';
+ foreach ($tableProperties as $tableProperty) {
+ $lines[] = $tableProperty->formatCs(
+ ' protected #dotNetPrimitiveType# _#name#;'
+ );
+ }
+ $lines[] = ' #endregion';
+ $lines[] = ' #region Constructors';
+ $lines[] = ' public '
+ . ExportCodegen::cgMakeIdentifier($table) . '() { }';
+ $temp = array();
+ foreach ($tableProperties as $tableProperty) {
+ if (! $tableProperty->isPK()) {
+ $temp[] = $tableProperty->formatCs(
+ '#dotNetPrimitiveType# #name#'
+ );
+ }
+ }
+ $lines[] = ' public '
+ . ExportCodegen::cgMakeIdentifier($table)
+ . '('
+ . implode(', ', $temp)
+ . ')';
+ $lines[] = ' {';
+ foreach ($tableProperties as $tableProperty) {
+ if (! $tableProperty->isPK()) {
+ $lines[] = $tableProperty->formatCs(
+ ' this._#name#=#name#;'
+ );
+ }
+ }
+ $lines[] = ' }';
+ $lines[] = ' #endregion';
+ $lines[] = ' #region Public Properties';
+ foreach ($tableProperties as $tableProperty) {
+ $lines[] = $tableProperty->formatCs(
+ ' public virtual #dotNetPrimitiveType# #ucfirstName#'
+ . "\n"
+ . ' {' . "\n"
+ . ' get {return _#name#;}' . "\n"
+ . ' set {_#name#=value;}' . "\n"
+ . ' }'
+ );
+ }
+ $lines[] = ' #endregion';
+ $lines[] = ' }';
+ $lines[] = ' #endregion';
+ $lines[] = '}';
+ }
+ return implode("\n", $lines);
+ }
+
+ /**
+ * XML Handler
+ *
+ * @param string $db database name
+ * @param string $table table name
+ * @param string $crlf line separator
+ *
+ * @return string containing XML code lines, separated by "\n"
+ */
+ private function _handleNHibernateXMLBody($db, $table, $crlf)
+ {
+ $lines = array();
+ $lines[] = '<?xml version="1.0" encoding="utf-8" ?' . '>';
+ $lines[] = '<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" '
+ . 'namespace="' . ExportCodegen::cgMakeIdentifier($db) . '" '
+ . 'assembly="' . ExportCodegen::cgMakeIdentifier($db) . '">';
+ $lines[] = ' <class '
+ . 'name="' . ExportCodegen::cgMakeIdentifier($table) . '" '
+ . 'table="' . ExportCodegen::cgMakeIdentifier($table) . '">';
+ $result = $GLOBALS['dbi']->query(
+ sprintf(
+ "DESC %s.%s", PMA_Util::backquote($db),
+ PMA_Util::backquote($table)
+ )
+ );
+ if ($result) {
+ while ($row = $GLOBALS['dbi']->fetchRow($result)) {
+ $tableProperty = new TableProperty($row);
+ if ($tableProperty->isPK()) {
+ $lines[] = $tableProperty->formatXml(
+ ' <id name="#ucfirstName#" type="#dotNetObjectType#"'
+ . ' unsaved-value="0">' . "\n"
+ . ' <column name="#name#" sql-type="#type#"'
+ . ' not-null="#notNull#" unique="#unique#"'
+ . ' index="PRIMARY"/>' . "\n"
+ . ' <generator class="native" />' . "\n"
+ . ' </id>'
+ );
+ } else {
+ $lines[] = $tableProperty->formatXml(
+ ' <property name="#ucfirstName#"'
+ . ' type="#dotNetObjectType#">' . "\n"
+ . ' <column name="#name#" sql-type="#type#"'
+ . ' not-null="#notNull#" #indexName#/>' . "\n"
+ . ' </property>'
+ );
+ }
+ }
+ $GLOBALS['dbi']->freeResult($result);
+ }
+ $lines[] = ' </class>';
+ $lines[] = '</hibernate-mapping>';
+ return implode("\n", $lines);
+ }
+
+
+ /* ~~~~~~~~~~~~~~~~~~~~ Getters and Setters ~~~~~~~~~~~~~~~~~~~~ */
+
+
+ /**
+ * Getter for CodeGen formats
+ *
+ * @return array
+ */
+ private function _getCgFormats()
+ {
+ return $this->_cgFormats;
+ }
+
+ /**
+ * Setter for CodeGen formats
+ *
+ * @param array $CG_FORMATS contains CodeGen Formats
+ *
+ * @return void
+ */
+ private function _setCgFormats($CG_FORMATS)
+ {
+ $this->_cgFormats = $CG_FORMATS;
+ }
+
+ /**
+ * Getter for CodeGen handlers
+ *
+ * @return array
+ */
+ private function _getCgHandlers()
+ {
+ return $this->_cgHandlers;
+ }
+
+ /**
+ * Setter for CodeGen handlers
+ *
+ * @param array $CG_HANDLERS contains CodeGen handler methods
+ *
+ * @return void
+ */
+ private function _setCgHandlers($CG_HANDLERS)
+ {
+ $this->_cgHandlers = $CG_HANDLERS;
+ }
+}
+?> \ No newline at end of file
diff --git a/libraries/plugins/export/ExportCsv.class.php b/libraries/plugins/export/ExportCsv.class.php
new file mode 100644
index 0000000000..22cbd63121
--- /dev/null
+++ b/libraries/plugins/export/ExportCsv.class.php
@@ -0,0 +1,321 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * CSV export code
+ *
+ * @package PhpMyAdmin-Export
+ * @subpackage CSV
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/* Get the export interface */
+require_once 'libraries/plugins/ExportPlugin.class.php';
+
+/**
+ * Handles the export for the CSV format
+ *
+ * @package PhpMyAdmin-Export
+ * @subpackage CSV
+ */
+class ExportCsv extends ExportPlugin
+{
+ /**
+ * Constructor
+ */
+ public function __construct()
+ {
+ $this->setProperties();
+ }
+
+ /**
+ * Sets the export CSV properties
+ *
+ * @return void
+ */
+ protected function setProperties()
+ {
+ $props = 'libraries/properties/';
+ include_once "$props/plugins/ExportPluginProperties.class.php";
+ include_once "$props/options/groups/OptionsPropertyRootGroup.class.php";
+ include_once "$props/options/groups/OptionsPropertyMainGroup.class.php";
+ include_once "$props/options/items/TextPropertyItem.class.php";
+ include_once "$props/options/items/BoolPropertyItem.class.php";
+ include_once "$props/options/items/HiddenPropertyItem.class.php";
+
+ $exportPluginProperties = new ExportPluginProperties();
+ $exportPluginProperties->setText('CSV');
+ $exportPluginProperties->setExtension('csv');
+ $exportPluginProperties->setMimeType('text/comma-separated-values');
+ $exportPluginProperties->setOptionsText(__('Options'));
+
+ // create the root group that will be the options field for
+ // $exportPluginProperties
+ // this will be shown as "Format specific options"
+ $exportSpecificOptions = new OptionsPropertyRootGroup();
+ $exportSpecificOptions->setName("Format Specific Options");
+
+ // general options main group
+ $generalOptions = new OptionsPropertyMainGroup();
+ $generalOptions->setName("general_opts");
+ // create leaf items and add them to the group
+ $leaf = new TextPropertyItem();
+ $leaf->setName("separator");
+ $leaf->setText(__('Columns separated with:'));
+ $generalOptions->addProperty($leaf);
+ $leaf = new TextPropertyItem();
+ $leaf->setName("enclosed");
+ $leaf->setText(__('Columns enclosed with:'));
+ $generalOptions->addProperty($leaf);
+ $leaf = new TextPropertyItem();
+ $leaf->setName("escaped");
+ $leaf->setText(__('Columns escaped with:'));
+ $generalOptions->addProperty($leaf);
+ $leaf = new TextPropertyItem();
+ $leaf->setName("terminated");
+ $leaf->setText(__('Lines terminated with:'));
+ $generalOptions->addProperty($leaf);
+ $leaf = new TextPropertyItem();
+ $leaf->setName('null');
+ $leaf->setText(__('Replace NULL with:'));
+ $generalOptions->addProperty($leaf);
+ $leaf = new BoolPropertyItem();
+ $leaf->setName('removeCRLF');
+ $leaf->setText(
+ __('Remove carriage return/line feed characters within columns')
+ );
+ $generalOptions->addProperty($leaf);
+ $leaf = new BoolPropertyItem();
+ $leaf->setName('columns');
+ $leaf->setText(__('Put columns names in the first row'));
+ $generalOptions->addProperty($leaf);
+ $leaf = new HiddenPropertyItem();
+ $leaf->setName('structure_or_data');
+ $generalOptions->addProperty($leaf);
+ // add the main group to the root group
+ $exportSpecificOptions->addProperty($generalOptions);
+
+ // set the options for the export plugin property item
+ $exportPluginProperties->setOptions($exportSpecificOptions);
+ $this->properties = $exportPluginProperties;
+ }
+
+ /**
+ * This method is called when any PluginManager to which the observer
+ * is attached calls PluginManager::notify()
+ *
+ * @param SplSubject $subject The PluginManager notifying the observer
+ * of an update.
+ *
+ * @return void
+ */
+ public function update (SplSubject $subject)
+ {
+ }
+
+ /**
+ * Outputs export header
+ *
+ * @return bool Whether it succeeded
+ */
+ public function exportHeader ()
+ {
+ global $what, $csv_terminated, $csv_separator, $csv_enclosed, $csv_escaped;
+
+ // Here we just prepare some values for export
+ if ($what == 'excel') {
+ $csv_terminated = "\015\012";
+ switch($GLOBALS['excel_edition']) {
+ case 'win':
+ // as tested on Windows with Excel 2002 and Excel 2007
+ $csv_separator = ';';
+ break;
+ case 'mac_excel2003':
+ $csv_separator = ';';
+ break;
+ case 'mac_excel2008':
+ $csv_separator = ',';
+ break;
+ }
+ $csv_enclosed = '"';
+ $csv_escaped = '"';
+ if (isset($GLOBALS['excel_columns'])) {
+ $GLOBALS['csv_columns'] = 'yes';
+ }
+ } else {
+ if (empty($csv_terminated) || strtolower($csv_terminated) == 'auto') {
+ $csv_terminated = $GLOBALS['crlf'];
+ } else {
+ $csv_terminated = str_replace('\\r', "\015", $csv_terminated);
+ $csv_terminated = str_replace('\\n', "\012", $csv_terminated);
+ $csv_terminated = str_replace('\\t', "\011", $csv_terminated);
+ } // end if
+ $csv_separator = str_replace('\\t', "\011", $csv_separator);
+ }
+
+ return true;
+ }
+
+ /**
+ * Outputs export footer
+ *
+ * @return bool Whether it succeeded
+ */
+ public function exportFooter ()
+ {
+ return true;
+ }
+
+ /**
+ * Outputs database header
+ *
+ * @param string $db Database name
+ *
+ * @return bool Whether it succeeded
+ */
+ public function exportDBHeader ($db)
+ {
+ return true;
+ }
+
+ /**
+ * Outputs database footer
+ *
+ * @param string $db Database name
+ *
+ * @return bool Whether it succeeded
+ */
+ public function exportDBFooter ($db)
+ {
+ return true;
+ }
+
+ /**
+ * Outputs CREATE DATABASE statement
+ *
+ * @param string $db Database name
+ *
+ * @return bool Whether it succeeded
+ */
+ public function exportDBCreate($db)
+ {
+ return true;
+ }
+
+ /**
+ * Outputs the content of a table in CSV format
+ *
+ * @param string $db database name
+ * @param string $table table name
+ * @param string $crlf the end of line sequence
+ * @param string $error_url the url to go back in case of error
+ * @param string $sql_query SQL query for obtaining data
+ *
+ * @return bool Whether it succeeded
+ */
+ public function exportData($db, $table, $crlf, $error_url, $sql_query)
+ {
+ global $what, $csv_terminated, $csv_separator, $csv_enclosed, $csv_escaped;
+
+ // Gets the data from the database
+ $result = $GLOBALS['dbi']->query(
+ $sql_query, null, PMA_DatabaseInterface::QUERY_UNBUFFERED
+ );
+ $fields_cnt = $GLOBALS['dbi']->numFields($result);
+
+ // If required, get fields name at the first line
+ if (isset($GLOBALS['csv_columns'])) {
+ $schema_insert = '';
+ for ($i = 0; $i < $fields_cnt; $i++) {
+ if ($csv_enclosed == '') {
+ $schema_insert .= stripslashes(
+ $GLOBALS['dbi']->fieldName($result, $i)
+ );
+ } else {
+ $schema_insert .= $csv_enclosed
+ . str_replace(
+ $csv_enclosed,
+ $csv_escaped . $csv_enclosed,
+ stripslashes($GLOBALS['dbi']->fieldName($result, $i))
+ )
+ . $csv_enclosed;
+ }
+ $schema_insert .= $csv_separator;
+ } // end for
+ $schema_insert = trim(substr($schema_insert, 0, -1));
+ if (! PMA_exportOutputHandler($schema_insert . $csv_terminated)) {
+ return false;
+ }
+ } // end if
+
+ // Format the data
+ while ($row = $GLOBALS['dbi']->fetchRow($result)) {
+ $schema_insert = '';
+ for ($j = 0; $j < $fields_cnt; $j++) {
+ if (! isset($row[$j]) || is_null($row[$j])) {
+ $schema_insert .= $GLOBALS[$what . '_null'];
+ } elseif ($row[$j] == '0' || $row[$j] != '') {
+ // always enclose fields
+ if ($what == 'excel') {
+ $row[$j] = preg_replace("/\015(\012)?/", "\012", $row[$j]);
+ }
+ // remove CRLF characters within field
+ if (isset($GLOBALS[$what . '_removeCRLF'])
+ && $GLOBALS[$what . '_removeCRLF']
+ ) {
+ $row[$j] = str_replace(
+ "\n",
+ "",
+ str_replace(
+ "\r",
+ "",
+ $row[$j]
+ )
+ );
+ }
+ if ($csv_enclosed == '') {
+ $schema_insert .= $row[$j];
+ } else {
+ // also double the escape string if found in the data
+ if ($csv_escaped != $csv_enclosed) {
+ $schema_insert .= $csv_enclosed
+ . str_replace(
+ $csv_enclosed,
+ $csv_escaped . $csv_enclosed,
+ str_replace(
+ $csv_escaped,
+ $csv_escaped . $csv_escaped,
+ $row[$j]
+ )
+ )
+ . $csv_enclosed;
+ } else {
+ // avoid a problem when escape string equals enclose
+ $schema_insert .= $csv_enclosed
+ . str_replace(
+ $csv_enclosed,
+ $csv_escaped . $csv_enclosed,
+ $row[$j]
+ )
+ . $csv_enclosed;
+ }
+ }
+ } else {
+ $schema_insert .= '';
+ }
+ if ($j < $fields_cnt-1) {
+ $schema_insert .= $csv_separator;
+ }
+ } // end for
+
+ if (! PMA_exportOutputHandler($schema_insert . $csv_terminated)) {
+ return false;
+ }
+ } // end while
+ $GLOBALS['dbi']->freeResult($result);
+
+ return true;
+ }
+}
+?>
diff --git a/libraries/plugins/export/ExportExcel.class.php b/libraries/plugins/export/ExportExcel.class.php
new file mode 100644
index 0000000000..1bb1060d25
--- /dev/null
+++ b/libraries/plugins/export/ExportExcel.class.php
@@ -0,0 +1,105 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Class for exporting CSV dumps of tables for excel
+ *
+ * @package PhpMyAdmin-Export
+ * @subpackage CSV-Excel
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/* Extend the export CSV class */
+require_once 'libraries/plugins/export/ExportCsv.class.php';
+
+/**
+ * Handles the export for the CSV-Excel format
+ *
+ * @package PhpMyAdmin-Export
+ * @subpackage CSV-Excel
+ */
+class ExportExcel extends ExportCsv
+{
+ /**
+ * Sets the export CSV for Excel properties
+ *
+ * @return void
+ */
+ protected function setProperties()
+ {
+ $props = 'libraries/properties/';
+ include_once "$props/plugins/ExportPluginProperties.class.php";
+ include_once "$props/options/groups/OptionsPropertyRootGroup.class.php";
+ include_once "$props/options/groups/OptionsPropertyMainGroup.class.php";
+ include_once "$props/options/items/TextPropertyItem.class.php";
+ include_once "$props/options/items/BoolPropertyItem.class.php";
+ include_once "$props/options/items/SelectPropertyItem.class.php";
+ include_once "$props/options/items/HiddenPropertyItem.class.php";
+
+ $exportPluginProperties = new ExportPluginProperties();
+ $exportPluginProperties->setText('CSV for MS Excel');
+ $exportPluginProperties->setExtension('csv');
+ $exportPluginProperties->setMimeType('text/comma-separated-values');
+ $exportPluginProperties->setOptionsText(__('Options'));
+
+ // create the root group that will be the options field for
+ // $exportPluginProperties
+ // this will be shown as "Format specific options"
+ $exportSpecificOptions = new OptionsPropertyRootGroup();
+ $exportSpecificOptions->setName("Format Specific Options");
+
+ // general options main group
+ $generalOptions = new OptionsPropertyMainGroup();
+ $generalOptions->setName("general_opts");
+ // create primary items and add them to the group
+ $leaf = new TextPropertyItem();
+ $leaf->setName('null');
+ $leaf->setText(__('Replace NULL with:'));
+ $generalOptions->addProperty($leaf);
+ $leaf = new BoolPropertyItem();
+ $leaf->setName('removeCRLF');
+ $leaf->setText(
+ __('Remove carriage return/line feed characters within columns')
+ );
+ $generalOptions->addProperty($leaf);
+ $leaf = new BoolPropertyItem();
+ $leaf->setName('columns');
+ $leaf->setText(__('Put columns names in the first row'));
+ $generalOptions->addProperty($leaf);
+ $leaf = new SelectPropertyItem();
+ $leaf->setName('edition');
+ $leaf->setValues(
+ array(
+ 'win' => 'Windows',
+ 'mac_excel2003' => 'Excel 2003 / Macintosh',
+ 'mac_excel2008' => 'Excel 2008 / Macintosh'
+ )
+ );
+ $leaf->setText(__('Excel edition:'));
+ $generalOptions->addProperty($leaf);
+ $leaf = new HiddenPropertyItem();
+ $leaf->setName('structure_or_data');
+ $generalOptions->addProperty($leaf);
+ // add the main group to the root group
+ $exportSpecificOptions->addProperty($generalOptions);
+
+ // set the options for the export plugin property item
+ $exportPluginProperties->setOptions($exportSpecificOptions);
+ $this->properties = $exportPluginProperties;
+ }
+
+ /**
+ * This method is called when any PluginManager to which the observer
+ * is attached calls PluginManager::notify()
+ *
+ * @param SplSubject $subject The PluginManager notifying the observer
+ * of an update.
+ *
+ * @return void
+ */
+ public function update (SplSubject $subject)
+ {
+ }
+}
+?> \ No newline at end of file
diff --git a/libraries/plugins/export/ExportHtmlword.class.php b/libraries/plugins/export/ExportHtmlword.class.php
new file mode 100644
index 0000000000..edd24aa372
--- /dev/null
+++ b/libraries/plugins/export/ExportHtmlword.class.php
@@ -0,0 +1,647 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * HTML-Word export code
+ *
+ * @package PhpMyAdmin-Export
+ * @subpackage HTML-Word
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/* Get the export interface */
+require_once 'libraries/plugins/ExportPlugin.class.php';
+
+/**
+ * Handles the export for the HTML-Word format
+ *
+ * @package PhpMyAdmin-Export
+ * @subpackage HTML-Word
+ */
+class ExportHtmlword extends ExportPlugin
+{
+ /**
+ * Constructor
+ */
+ public function __construct()
+ {
+ $this->setProperties();
+ }
+
+ /**
+ * Sets the export HTML-Word properties
+ *
+ * @return void
+ */
+ protected function setProperties()
+ {
+ $props = 'libraries/properties/';
+ include_once "$props/plugins/ExportPluginProperties.class.php";
+ include_once "$props/options/groups/OptionsPropertyRootGroup.class.php";
+ include_once "$props/options/groups/OptionsPropertyMainGroup.class.php";
+ include_once "$props/options/items/RadioPropertyItem.class.php";
+ include_once "$props/options/items/TextPropertyItem.class.php";
+ include_once "$props/options/items/BoolPropertyItem.class.php";
+
+ $exportPluginProperties = new ExportPluginProperties();
+ $exportPluginProperties->setText('Microsoft Word 2000');
+ $exportPluginProperties->setExtension('doc');
+ $exportPluginProperties->setMimeType('application/vnd.ms-word');
+ $exportPluginProperties->setForceFile(true);
+ $exportPluginProperties->setOptionsText(__('Options'));
+
+ // create the root group that will be the options field for
+ // $exportPluginProperties
+ // this will be shown as "Format specific options"
+ $exportSpecificOptions = new OptionsPropertyRootGroup();
+ $exportSpecificOptions->setName("Format Specific Options");
+
+ // what to dump (structure/data/both)
+ $dumpWhat = new OptionsPropertyMainGroup();
+ $dumpWhat->setName("dump_what");
+ $dumpWhat->setText(__('Dump table'));
+ // create primary items and add them to the group
+ $leaf = new RadioPropertyItem();
+ $leaf->setName("structure_or_data");
+ $leaf->setValues(
+ array(
+ 'structure' => __('structure'),
+ 'data' => __('data'),
+ 'structure_and_data' => __('structure and data')
+ )
+ );
+ $dumpWhat->addProperty($leaf);
+ // add the main group to the root group
+ $exportSpecificOptions->addProperty($dumpWhat);
+
+ // data options main group
+ $dataOptions = new OptionsPropertyMainGroup();
+ $dataOptions->setName("dump_what");
+ $dataOptions->setText(__('Data dump options'));
+ $dataOptions->setForce('structure');
+ // create primary items and add them to the group
+ $leaf = new TextPropertyItem();
+ $leaf->setName("null");
+ $leaf->setText(__('Replace NULL with:'));
+ $dataOptions->addProperty($leaf);
+ $leaf = new BoolPropertyItem();
+ $leaf->setName("columns");
+ $leaf->setText(__('Put columns names in the first row'));
+ $dataOptions->addProperty($leaf);
+ // add the main group to the root group
+ $exportSpecificOptions->addProperty($dataOptions);
+
+ // set the options for the export plugin property item
+ $exportPluginProperties->setOptions($exportSpecificOptions);
+ $this->properties = $exportPluginProperties;
+ }
+
+ /**
+ * This method is called when any PluginManager to which the observer
+ * is attached calls PluginManager::notify()
+ *
+ * @param SplSubject $subject The PluginManager notifying the observer
+ * of an update.
+ *
+ * @return void
+ */
+ public function update (SplSubject $subject)
+ {
+ }
+
+ /**
+ * Outputs export header
+ *
+ * @return bool Whether it succeeded
+ */
+ public function exportHeader ()
+ {
+ global $charset_of_file;
+
+ return PMA_exportOutputHandler(
+ '<html xmlns:o="urn:schemas-microsoft-com:office:office"
+ xmlns:x="urn:schemas-microsoft-com:office:word"
+ xmlns="http://www.w3.org/TR/REC-html40">
+
+ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"'
+ . ' "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+ <html>
+ <head>
+ <meta http-equiv="Content-type" content="text/html;charset='
+ . (isset($charset_of_file) ? $charset_of_file : 'utf-8') . '" />
+ </head>
+ <body>'
+ );
+ }
+
+ /**
+ * Outputs export footer
+ *
+ * @return bool Whether it succeeded
+ */
+ public function exportFooter ()
+ {
+ return PMA_exportOutputHandler('</body></html>');
+ }
+
+ /**
+ * Outputs database header
+ *
+ * @param string $db Database name
+ *
+ * @return bool Whether it succeeded
+ */
+ public function exportDBHeader ($db)
+ {
+ return PMA_exportOutputHandler(
+ '<h1>' . __('Database') . ' ' . htmlspecialchars($db) . '</h1>'
+ );
+ }
+
+ /**
+ * Outputs database footer
+ *
+ * @param string $db Database name
+ *
+ * @return bool Whether it succeeded
+ */
+ public function exportDBFooter ($db)
+ {
+ return true;
+ }
+
+ /**
+ * Outputs CREATE DATABASE statement
+ *
+ * @param string $db Database name
+ *
+ * @return bool Whether it succeeded
+ */
+ public function exportDBCreate($db)
+ {
+ return true;
+ }
+
+ /**
+ * Outputs the content of a table in HTML-Word format
+ *
+ * @param string $db database name
+ * @param string $table table name
+ * @param string $crlf the end of line sequence
+ * @param string $error_url the url to go back in case of error
+ * @param string $sql_query SQL query for obtaining data
+ *
+ * @return bool Whether it succeeded
+ */
+ public function exportData($db, $table, $crlf, $error_url, $sql_query)
+ {
+ global $what;
+
+ if (! PMA_exportOutputHandler(
+ '<h2>'
+ . __('Dumping data for table') . ' ' . htmlspecialchars($table)
+ . '</h2>'
+ )) {
+ return false;
+ }
+ if (! PMA_exportOutputHandler(
+ '<table class="width100" cellspacing="1">'
+ )) {
+ return false;
+ }
+
+ // Gets the data from the database
+ $result = $GLOBALS['dbi']->query(
+ $sql_query, null, PMA_DatabaseInterface::QUERY_UNBUFFERED
+ );
+ $fields_cnt = $GLOBALS['dbi']->numFields($result);
+
+ // If required, get fields name at the first line
+ if (isset($GLOBALS['htmlword_columns'])) {
+ $schema_insert = '<tr class="print-category">';
+ for ($i = 0; $i < $fields_cnt; $i++) {
+ $schema_insert .= '<td class="print"><strong>'
+ . htmlspecialchars(
+ stripslashes($GLOBALS['dbi']->fieldName($result, $i))
+ )
+ . '</strong></td>';
+ } // end for
+ $schema_insert .= '</tr>';
+ if (! PMA_exportOutputHandler($schema_insert)) {
+ return false;
+ }
+ } // end if
+
+ // Format the data
+ while ($row = $GLOBALS['dbi']->fetchRow($result)) {
+ $schema_insert = '<tr class="print-category">';
+ for ($j = 0; $j < $fields_cnt; $j++) {
+ if (! isset($row[$j]) || is_null($row[$j])) {
+ $value = $GLOBALS[$what . '_null'];
+ } elseif ($row[$j] == '0' || $row[$j] != '') {
+ $value = $row[$j];
+ } else {
+ $value = '';
+ }
+ $schema_insert .= '<td class="print">'
+ . htmlspecialchars($value)
+ . '</td>';
+ } // end for
+ $schema_insert .= '</tr>';
+ if (! PMA_exportOutputHandler($schema_insert)) {
+ return false;
+ }
+ } // end while
+ $GLOBALS['dbi']->freeResult($result);
+ if (! PMA_exportOutputHandler('</table>')) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Returns a stand-in CREATE definition to resolve view dependencies
+ *
+ * @param string $db the database name
+ * @param string $view the view name
+ * @param string $crlf the end of line sequence
+ *
+ * @return string resulting definition
+ */
+ public function getTableDefStandIn($db, $view, $crlf)
+ {
+ $schema_insert = '<table class="width100" cellspacing="1">'
+ . '<tr class="print-category">'
+ . '<th class="print">'
+ . __('Column')
+ . '</th>'
+ . '<td class="print"><strong>'
+ . __('Type')
+ . '</strong></td>'
+ . '<td class="print"><strong>'
+ . __('Null')
+ . '</strong></td>'
+ . '<td class="print"><strong>'
+ . __('Default')
+ . '</strong></td>'
+ . '</tr>';
+
+ /**
+ * Get the unique keys in the view
+ */
+ $unique_keys = array();
+ $keys = $GLOBALS['dbi']->getTableIndexes($db, $view);
+ foreach ($keys as $key) {
+ if ($key['Non_unique'] == 0) {
+ $unique_keys[] = $key['Column_name'];
+ }
+ }
+
+ $columns = $GLOBALS['dbi']->getColumns($db, $view);
+ foreach ($columns as $column) {
+ $schema_insert .= $this->formatOneColumnDefinition(
+ $column,
+ $unique_keys
+ );
+ $schema_insert .= '</tr>';
+ }
+
+ $schema_insert .= '</table>';
+ return $schema_insert;
+ }
+
+ /**
+ * Returns $table's CREATE definition
+ *
+ * @param string $db the database name
+ * @param string $table the table name
+ * @param string $crlf the end of line sequence
+ * @param string $error_url the url to go back in case of error
+ * @param bool $do_relation whether to include relation comments
+ * @param bool $do_comments whether to include the pmadb-style column
+ * comments as comments in the structure;
+ * this is deprecated but the parameter is
+ * left here because export.php calls
+ * PMA_exportStructure() also for other
+ * export types which use this parameter
+ * @param bool $do_mime whether to include mime comments
+ * @param bool $show_dates whether to include creation/update/check dates
+ * @param bool $add_semicolon whether to add semicolon and end-of-line
+ * at the end
+ * @param bool $view whether we're handling a view
+ *
+ * @return string resulting schema
+ */
+ public function getTableDef(
+ $db,
+ $table,
+ $crlf,
+ $error_url,
+ $do_relation,
+ $do_comments,
+ $do_mime,
+ $show_dates = false,
+ $add_semicolon = true,
+ $view = false
+ ) {
+ // set $cfgRelation here, because there is a chance that it's modified
+ // since the class initialization
+ global $cfgRelation;
+
+ $schema_insert = '';
+
+ /**
+ * Gets fields properties
+ */
+ $GLOBALS['dbi']->selectDb($db);
+
+ // Check if we can use Relations
+ if ($do_relation && ! empty($cfgRelation['relation'])) {
+ // Find which tables are related with the current one and write it in
+ // an array
+ $res_rel = PMA_getForeigners($db, $table);
+
+ if ($res_rel && count($res_rel) > 0) {
+ $have_rel = true;
+ } else {
+ $have_rel = false;
+ }
+ } else {
+ $have_rel = false;
+ } // end if
+
+ /**
+ * Displays the table structure
+ */
+ $schema_insert .= '<table class="width100" cellspacing="1">';
+
+ $columns_cnt = 4;
+ if ($do_relation && $have_rel) {
+ $columns_cnt++;
+ }
+ if ($do_comments && $cfgRelation['commwork']) {
+ $columns_cnt++;
+ }
+ if ($do_mime && $cfgRelation['mimework']) {
+ $columns_cnt++;
+ }
+
+ $schema_insert .= '<tr class="print-category">';
+ $schema_insert .= '<th class="print">'
+ . __('Column')
+ . '</th>';
+ $schema_insert .= '<td class="print"><strong>'
+ . __('Type')
+ . '</strong></td>';
+ $schema_insert .= '<td class="print"><strong>'
+ . __('Null')
+ . '</strong></td>';
+ $schema_insert .= '<td class="print"><strong>'
+ . __('Default')
+ . '</strong></td>';
+ if ($do_relation && $have_rel) {
+ $schema_insert .= '<td class="print"><strong>'
+ . __('Links to')
+ . '</strong></td>';
+ }
+ if ($do_comments) {
+ $schema_insert .= '<td class="print"><strong>'
+ . __('Comments')
+ . '</strong></td>';
+ $comments = PMA_getComments($db, $table);
+ }
+ if ($do_mime && $cfgRelation['mimework']) {
+ $schema_insert .= '<td class="print"><strong>'
+ . htmlspecialchars('MIME')
+ . '</strong></td>';
+ $mime_map = PMA_getMIME($db, $table, true);
+ }
+ $schema_insert .= '</tr>';
+
+ $columns = $GLOBALS['dbi']->getColumns($db, $table);
+ /**
+ * Get the unique keys in the table
+ */
+ $unique_keys = array();
+ $keys = $GLOBALS['dbi']->getTableIndexes($db, $table);
+ foreach ($keys as $key) {
+ if ($key['Non_unique'] == 0) {
+ $unique_keys[] = $key['Column_name'];
+ }
+ }
+ foreach ($columns as $column) {
+ $schema_insert .= $this->formatOneColumnDefinition(
+ $column,
+ $unique_keys
+ );
+ $field_name = $column['Field'];
+
+ if ($do_relation && $have_rel) {
+ $schema_insert .= '<td class="print">'
+ . (isset($res_rel[$field_name])
+ ? htmlspecialchars(
+ $res_rel[$field_name]['foreign_table']
+ . ' (' . $res_rel[$field_name]['foreign_field']
+ . ')'
+ )
+ : '') . '</td>';
+ }
+ if ($do_comments && $cfgRelation['commwork']) {
+ $schema_insert .= '<td class="print">'
+ . (isset($comments[$field_name])
+ ? htmlspecialchars($comments[$field_name])
+ : '') . '</td>';
+ }
+ if ($do_mime && $cfgRelation['mimework']) {
+ $schema_insert .= '<td class="print">'
+ . (isset($mime_map[$field_name]) ?
+ htmlspecialchars(
+ str_replace('_', '/', $mime_map[$field_name]['mimetype'])
+ )
+ : '') . '</td>';
+ }
+
+ $schema_insert .= '</tr>';
+ } // end foreach
+
+ $schema_insert .= '</table>';
+ return $schema_insert;
+ }
+
+ /**
+ * Outputs triggers
+ *
+ * @param string $db database name
+ * @param string $table table name
+ *
+ * @return string Formatted triggers list
+ */
+ protected function getTriggers($db, $table)
+ {
+ $dump = '<table class="width100" cellspacing="1">';
+ $dump .= '<tr class="print-category">';
+ $dump .= '<th class="print">' . __('Name') . '</th>';
+ $dump .= '<td class="print"><strong>' . __('Time') . '</strong></td>';
+ $dump .= '<td class="print"><strong>' . __('Event') . '</strong></td>';
+ $dump .= '<td class="print"><strong>' . __('Definition') . '</strong></td>';
+ $dump .= '</tr>';
+
+ $triggers = $GLOBALS['dbi']->getTriggers($db, $table);
+
+ foreach ($triggers as $trigger) {
+ $dump .= '<tr class="print-category">';
+ $dump .= '<td class="print">'
+ . htmlspecialchars($trigger['name'])
+ . '</td>'
+ . '<td class="print">'
+ . htmlspecialchars($trigger['action_timing'])
+ . '</td>'
+ . '<td class="print">'
+ . htmlspecialchars($trigger['event_manipulation'])
+ . '</td>'
+ . '<td class="print">'
+ . htmlspecialchars($trigger['definition'])
+ . '</td>'
+ . '</tr>';
+ }
+
+ $dump .= '</table>';
+ return $dump;
+ }
+
+ /**
+ * Outputs table's structure
+ *
+ * @param string $db database name
+ * @param string $table table name
+ * @param string $crlf the end of line sequence
+ * @param string $error_url the url to go back in case of error
+ * @param string $export_mode 'create_table', 'triggers', 'create_view',
+ * 'stand_in'
+ * @param string $export_type 'server', 'database', 'table'
+ * @param bool $do_relation whether to include relation comments
+ * @param bool $do_comments whether to include the pmadb-style column
+ * comments as comments in the structure;
+ * this is deprecated but the parameter is
+ * left here because export.php calls
+ * PMA_exportStructure() also for other
+ * export types which use this parameter
+ * @param bool $do_mime whether to include mime comments
+ * @param bool $dates whether to include creation/update/check dates
+ *
+ * @return bool Whether it succeeded
+ */
+ public function exportStructure(
+ $db,
+ $table,
+ $crlf,
+ $error_url,
+ $export_mode,
+ $export_type,
+ $do_relation = false,
+ $do_comments = false,
+ $do_mime = false,
+ $dates = false
+ ) {
+ $dump = '';
+
+ switch($export_mode) {
+ case 'create_table':
+ $dump .= '<h2>'
+ . __('Table structure for table') . ' ' . htmlspecialchars($table)
+ . '</h2>';
+ $dump .= $this->getTableDef(
+ $db, $table, $crlf, $error_url, $do_relation, $do_comments, $do_mime,
+ $dates
+ );
+ break;
+ case 'triggers':
+ $dump = '';
+ $triggers = $GLOBALS['dbi']->getTriggers($db, $table);
+ if ($triggers) {
+ $dump .= '<h2>'
+ . __('Triggers') . ' ' . htmlspecialchars($table)
+ . '</h2>';
+ $dump .= $this->getTriggers($db, $table);
+ }
+ break;
+ case 'create_view':
+ $dump .= '<h2>'
+ . __('Structure for view') . ' ' . htmlspecialchars($table)
+ . '</h2>';
+ $dump .= $this->getTableDef(
+ $db, $table, $crlf, $error_url, $do_relation, $do_comments, $do_mime,
+ $dates, true, true
+ );
+ break;
+ case 'stand_in':
+ $dump .= '<h2>'
+ . __('Stand-in structure for view') . ' ' . htmlspecialchars($table)
+ . '</h2>';
+ // export a stand-in definition to resolve view dependencies
+ $dump .= $this->getTableDefStandIn($db, $table, $crlf);
+ } // end switch
+
+ return PMA_exportOutputHandler($dump);
+ }
+
+ /**
+ * Formats the definition for one column
+ *
+ * @param array $column info about this column
+ * @param array $unique_keys unique keys of the table
+ *
+ * @return string Formatted column definition
+ */
+ protected function formatOneColumnDefinition(
+ $column, $unique_keys
+ ) {
+ $definition = '<tr class="print-category">';
+
+ $extracted_columnspec
+ = PMA_Util::extractColumnSpec($column['Type']);
+
+ $type = htmlspecialchars($extracted_columnspec['print_type']);
+ if (empty($type)) {
+ $type = '&nbsp;';
+ }
+
+ if (! isset($column['Default'])) {
+ if ($column['Null'] != 'NO') {
+ $column['Default'] = 'NULL';
+ }
+ }
+
+ $fmt_pre = '';
+ $fmt_post = '';
+ if (in_array($column['Field'], $unique_keys)) {
+ $fmt_pre = '<strong>' . $fmt_pre;
+ $fmt_post = $fmt_post . '</strong>';
+ }
+ if ($column['Key'] == 'PRI') {
+ $fmt_pre = '<em>' . $fmt_pre;
+ $fmt_post = $fmt_post . '</em>';
+ }
+ $definition .= '<td class="print">' . $fmt_pre
+ . htmlspecialchars($column['Field']) . $fmt_post . '</td>';
+ $definition .= '<td class="print">' . htmlspecialchars($type)
+ . '</td>';
+ $definition .= '<td class="print">'
+ . (($column['Null'] == '' || $column['Null'] == 'NO')
+ ? __('No')
+ : __('Yes'))
+ . '</td>';
+ $definition .= '<td class="print">'
+ . htmlspecialchars(
+ isset($column['Default'])
+ ? $column['Default']
+ : ''
+ )
+ . '</td>';
+
+ return $definition;
+ }
+}
+?>
diff --git a/libraries/plugins/export/ExportJson.class.php b/libraries/plugins/export/ExportJson.class.php
new file mode 100644
index 0000000000..59bc4772cf
--- /dev/null
+++ b/libraries/plugins/export/ExportJson.class.php
@@ -0,0 +1,210 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Set of methods used to build dumps of tables as JSON
+ *
+ * @package PhpMyAdmin-Export
+ * @subpackage JSON
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/* Get the export interface */
+require_once 'libraries/plugins/ExportPlugin.class.php';
+
+/**
+ * Handles the export for the JSON format
+ *
+ * @package PhpMyAdmin-Export
+ * @subpackage JSON
+ */
+class ExportJson extends ExportPlugin
+{
+ /**
+ * Constructor
+ */
+ public function __construct()
+ {
+ $this->setProperties();
+ }
+
+ /**
+ * Sets the export JSON properties
+ *
+ * @return void
+ */
+ protected function setProperties()
+ {
+ $props = 'libraries/properties/';
+ include_once "$props/plugins/ExportPluginProperties.class.php";
+ include_once "$props/options/groups/OptionsPropertyRootGroup.class.php";
+ include_once "$props/options/groups/OptionsPropertyMainGroup.class.php";
+ include_once "$props/options/items/HiddenPropertyItem.class.php";
+
+ $exportPluginProperties = new ExportPluginProperties();
+ $exportPluginProperties->setText('JSON');
+ $exportPluginProperties->setExtension('json');
+ $exportPluginProperties->setMimeType('text/plain');
+ $exportPluginProperties->setOptionsText(__('Options'));
+
+ // create the root group that will be the options field for
+ // $exportPluginProperties
+ // this will be shown as "Format specific options"
+ $exportSpecificOptions = new OptionsPropertyRootGroup();
+ $exportSpecificOptions->setName("Format Specific Options");
+
+ // general options main group
+ $generalOptions = new OptionsPropertyMainGroup();
+ $generalOptions->setName("general_opts");
+ // create primary items and add them to the group
+ $leaf = new HiddenPropertyItem();
+ $leaf->setName("structure_or_data");
+ $generalOptions->addProperty($leaf);
+ // add the main group to the root group
+ $exportSpecificOptions->addProperty($generalOptions);
+
+ // set the options for the export plugin property item
+ $exportPluginProperties->setOptions($exportSpecificOptions);
+ $this->properties = $exportPluginProperties;
+ }
+
+ /**
+ * This method is called when any PluginManager to which the observer
+ * is attached calls PluginManager::notify()
+ *
+ * @param SplSubject $subject The PluginManager notifying the observer
+ * of an update.
+ *
+ * @return void
+ */
+ public function update (SplSubject $subject)
+ {
+ }
+
+ /**
+ * Outputs export header
+ *
+ * @return bool Whether it succeeded
+ */
+ public function exportHeader ()
+ {
+ PMA_exportOutputHandler(
+ '/**' . $GLOBALS['crlf']
+ . ' Export to JSON plugin for PHPMyAdmin' . $GLOBALS['crlf']
+ . ' @version 0.1' . $GLOBALS['crlf']
+ . ' */' . $GLOBALS['crlf'] . $GLOBALS['crlf']
+ );
+ return true;
+ }
+
+ /**
+ * Outputs export footer
+ *
+ * @return bool Whether it succeeded
+ */
+ public function exportFooter ()
+ {
+ return true;
+ }
+
+ /**
+ * Outputs database header
+ *
+ * @param string $db Database name
+ *
+ * @return bool Whether it succeeded
+ */
+ public function exportDBHeader ($db)
+ {
+ PMA_exportOutputHandler('// Database \'' . $db . '\'' . $GLOBALS['crlf']);
+ return true;
+ }
+
+ /**
+ * Outputs database footer
+ *
+ * @param string $db Database name
+ *
+ * @return bool Whether it succeeded
+ */
+ public function exportDBFooter ($db)
+ {
+ return true;
+ }
+
+ /**
+ * Outputs CREATE DATABASE statement
+ *
+ * @param string $db Database name
+ *
+ * @return bool Whether it succeeded
+ */
+ public function exportDBCreate($db)
+ {
+ return true;
+ }
+
+ /**
+ * Outputs the content of a table in JSON format
+ *
+ * @param string $db database name
+ * @param string $table table name
+ * @param string $crlf the end of line sequence
+ * @param string $error_url the url to go back in case of error
+ * @param string $sql_query SQL query for obtaining data
+ *
+ * @return bool Whether it succeeded
+ */
+ public function exportData($db, $table, $crlf, $error_url, $sql_query)
+ {
+ $result = $GLOBALS['dbi']->query(
+ $sql_query, null, PMA_DatabaseInterface::QUERY_UNBUFFERED
+ );
+ $columns_cnt = $GLOBALS['dbi']->numFields($result);
+
+ for ($i = 0; $i < $columns_cnt; $i++) {
+ $columns[$i] = stripslashes($GLOBALS['dbi']->fieldName($result, $i));
+ }
+ unset($i);
+
+ $buffer = '';
+ $record_cnt = 0;
+ while ($record = $GLOBALS['dbi']->fetchRow($result)) {
+
+ $record_cnt++;
+
+ // Output table name as comment if this is the first record of the table
+ if ($record_cnt == 1) {
+ $buffer = '// ' . $db . '.' . $table . $crlf . $crlf;
+ $buffer .= '[';
+ } else {
+ $buffer = ', ';
+ }
+
+ if (! PMA_exportOutputHandler($buffer)) {
+ return false;
+ }
+
+ $data = array();
+
+ for ($i = 0; $i < $columns_cnt; $i++) {
+ $data[$columns[$i]] = $record[$i];
+ }
+
+ if (! PMA_exportOutputHandler(json_encode($data))) {
+ return false;
+ }
+ }
+
+ if ($record_cnt) {
+ if (! PMA_exportOutputHandler(']')) {
+ return false;
+ }
+ }
+
+ $GLOBALS['dbi']->freeResult($result);
+ return true;
+ }
+}
+?>
diff --git a/libraries/plugins/export/ExportLatex.class.php b/libraries/plugins/export/ExportLatex.class.php
new file mode 100644
index 0000000000..dbd55df495
--- /dev/null
+++ b/libraries/plugins/export/ExportLatex.class.php
@@ -0,0 +1,657 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Set of methods used to build dumps of tables as Latex
+ *
+ * @package PhpMyAdmin-Export
+ * @subpackage Latex
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/* Get the export interface */
+require_once 'libraries/plugins/ExportPlugin.class.php';
+
+/**
+ * Handles the export for the Latex format
+ *
+ * @package PhpMyAdmin-Export
+ * @subpackage Latex
+ */
+class ExportLatex extends ExportPlugin
+{
+ /**
+ * Constructor
+ */
+ public function __construct()
+ {
+ // initialize the specific export sql variables
+ $this->initSpecificVariables();
+
+ $this->setProperties();
+ }
+
+ /**
+ * Initialize the local variables that are used for export Latex
+ *
+ * @return void
+ */
+ protected function initSpecificVariables()
+ {
+ /* Messages used in default captions */
+ $GLOBALS['strLatexContent'] = __('Content of table @TABLE@');
+ $GLOBALS['strLatexContinued'] = __('(continued)');
+ $GLOBALS['strLatexStructure'] = __('Structure of table @TABLE@');
+ }
+
+ /**
+ * Sets the export Latex properties
+ *
+ * @return void
+ */
+ protected function setProperties()
+ {
+ global $plugin_param;
+ $hide_structure = false;
+ if ($plugin_param['export_type'] == 'table'
+ && ! $plugin_param['single_table']
+ ) {
+ $hide_structure = true;
+ }
+
+ $props = 'libraries/properties/';
+ include_once "$props/plugins/ExportPluginProperties.class.php";
+ include_once "$props/options/groups/OptionsPropertyRootGroup.class.php";
+ include_once "$props/options/groups/OptionsPropertyMainGroup.class.php";
+ include_once "$props/options/items/BoolPropertyItem.class.php";
+ include_once "$props/options/items/RadioPropertyItem.class.php";
+ include_once "$props/options/items/TextPropertyItem.class.php";
+
+ $exportPluginProperties = new ExportPluginProperties();
+ $exportPluginProperties->setText('LaTeX');
+ $exportPluginProperties->setExtension('tex');
+ $exportPluginProperties->setMimeType('application/x-tex');
+ $exportPluginProperties->setOptionsText(__('Options'));
+
+ // create the root group that will be the options field for
+ // $exportPluginProperties
+ // this will be shown as "Format specific options"
+ $exportSpecificOptions = new OptionsPropertyRootGroup();
+ $exportSpecificOptions->setName("Format Specific Options");
+
+ // general options main group
+ $generalOptions = new OptionsPropertyMainGroup();
+ $generalOptions->setName("general_opts");
+ // create primary items and add them to the group
+ $leaf = new BoolPropertyItem();
+ $leaf->setName("caption");
+ $leaf->setText(__('Include table caption'));
+ $generalOptions->addProperty($leaf);
+ // add the main group to the root group
+ $exportSpecificOptions->addProperty($generalOptions);
+
+ // what to dump (structure/data/both) main group
+ $dumpWhat = new OptionsPropertyMainGroup();
+ $dumpWhat->setName("dump_what");
+ $dumpWhat->setText(__('Dump table'));
+ // create primary items and add them to the group
+ $leaf = new RadioPropertyItem();
+ $leaf->setName("structure_or_data");
+ $leaf->setValues(
+ array(
+ 'structure' => __('structure'),
+ 'data' => __('data'),
+ 'structure_and_data' => __('structure and data')
+ )
+ );
+ $dumpWhat->addProperty($leaf);
+ // add the main group to the root group
+ $exportSpecificOptions->addProperty($dumpWhat);
+
+ // structure options main group
+ if (! $hide_structure) {
+ $structureOptions = new OptionsPropertyMainGroup();
+ $structureOptions->setName("structure");
+ $structureOptions->setText(__('Object creation options'));
+ $structureOptions->setForce('data');
+ // create primary items and add them to the group
+ $leaf = new TextPropertyItem();
+ $leaf->setName("structure_caption");
+ $leaf->setText(__('Table caption:'));
+ $leaf->setDoc('faq6-27');
+ $structureOptions->addProperty($leaf);
+ $leaf = new TextPropertyItem();
+ $leaf->setName("structure_continued_caption");
+ $leaf->setText(__('Table caption (continued):'));
+ $leaf->setDoc('faq6-27');
+ $structureOptions->addProperty($leaf);
+ $leaf = new TextPropertyItem();
+ $leaf->setName("structure_label");
+ $leaf->setText(__('Label key:'));
+ $leaf->setDoc('faq6-27');
+ $structureOptions->addProperty($leaf);
+ if (! empty($GLOBALS['cfgRelation']['relation'])) {
+ $leaf = new BoolPropertyItem();
+ $leaf->setName("relation");
+ $leaf->setText(__('Display foreign key relationships'));
+ $structureOptions->addProperty($leaf);
+ }
+ $leaf = new BoolPropertyItem();
+ $leaf->setName("comments");
+ $leaf->setText(__('Display comments'));
+ $structureOptions->addProperty($leaf);
+ if (! empty($GLOBALS['cfgRelation']['mimework'])) {
+ $leaf = new BoolPropertyItem();
+ $leaf->setName("mime");
+ $leaf->setText(__('Display MIME types'));
+ $structureOptions->addProperty($leaf);
+ }
+ // add the main group to the root group
+ $exportSpecificOptions->addProperty($structureOptions);
+ }
+
+ // data options main group
+ $dataOptions = new OptionsPropertyMainGroup();
+ $dataOptions->setName("data");
+ $dataOptions->setText(__('Data dump options'));
+ $dataOptions->setForce('structure');
+ // create primary items and add them to the group
+ $leaf = new BoolPropertyItem();
+ $leaf->setName("columns");
+ $leaf->setText(__('Put columns names in the first row:'));
+ $dataOptions->addProperty($leaf);
+ $leaf = new TextPropertyItem();
+ $leaf->setName("data_caption");
+ $leaf->setText(__('Table caption:'));
+ $leaf->setDoc('faq6-27');
+ $dataOptions->addProperty($leaf);
+ $leaf = new TextPropertyItem();
+ $leaf->setName("data_continued_caption");
+ $leaf->setText(__('Table caption (continued):'));
+ $leaf->setDoc('faq6-27');
+ $dataOptions->addProperty($leaf);
+ $leaf = new TextPropertyItem();
+ $leaf->setName("data_label");
+ $leaf->setText(__('Label key:'));
+ $leaf->setDoc('faq6-27');
+ $dataOptions->addProperty($leaf);
+ $leaf = new TextPropertyItem();
+ $leaf->setName('null');
+ $leaf->setText(__('Replace NULL with:'));
+ $dataOptions->addProperty($leaf);
+ // add the main group to the root group
+ $exportSpecificOptions->addProperty($dataOptions);
+
+ // set the options for the export plugin property item
+ $exportPluginProperties->setOptions($exportSpecificOptions);
+ $this->properties = $exportPluginProperties;
+ }
+
+ /**
+ * This method is called when any PluginManager to which the observer
+ * is attached calls PluginManager::notify()
+ *
+ * @param SplSubject $subject The PluginManager notifying the observer
+ * of an update.
+ *
+ * @return void
+ */
+ public function update (SplSubject $subject)
+ {
+ }
+
+ /**
+ * Outputs export header
+ *
+ * @return bool Whether it succeeded
+ */
+ public function exportHeader ()
+ {
+ global $crlf;
+ global $cfg;
+
+ $head = '% phpMyAdmin LaTeX Dump' . $crlf
+ . '% version ' . PMA_VERSION . $crlf
+ . '% http://www.phpmyadmin.net' . $crlf
+ . '%' . $crlf
+ . '% ' . __('Host:') . ' ' . $cfg['Server']['host'];
+ if (! empty($cfg['Server']['port'])) {
+ $head .= ':' . $cfg['Server']['port'];
+ }
+ $head .= $crlf
+ . '% ' . __('Generation Time:') . ' '
+ . PMA_Util::localisedDate() . $crlf
+ . '% ' . __('Server version:') . ' ' . PMA_MYSQL_STR_VERSION . $crlf
+ . '% ' . __('PHP Version:') . ' ' . phpversion() . $crlf;
+ return PMA_exportOutputHandler($head);
+ }
+
+ /**
+ * Outputs export footer
+ *
+ * @return bool Whether it succeeded
+ */
+ public function exportFooter ()
+ {
+ return true;
+ }
+
+ /**
+ * Outputs database header
+ *
+ * @param string $db Database name
+ *
+ * @return bool Whether it succeeded
+ */
+ public function exportDBHeader ($db)
+ {
+ global $crlf;
+ $head = '% ' . $crlf
+ . '% ' . __('Database:') . ' ' . '\'' . $db . '\'' . $crlf
+ . '% ' . $crlf;
+ return PMA_exportOutputHandler($head);
+ }
+
+ /**
+ * Outputs database footer
+ *
+ * @param string $db Database name
+ *
+ * @return bool Whether it succeeded
+ */
+ public function exportDBFooter ($db)
+ {
+ return true;
+ }
+
+ /**
+ * Outputs CREATE DATABASE statement
+ *
+ * @param string $db Database name
+ *
+ * @return bool Whether it succeeded
+ */
+ public function exportDBCreate($db)
+ {
+ return true;
+ }
+
+ /**
+ * Outputs the content of a table in JSON format
+ *
+ * @param string $db database name
+ * @param string $table table name
+ * @param string $crlf the end of line sequence
+ * @param string $error_url the url to go back in case of error
+ * @param string $sql_query SQL query for obtaining data
+ *
+ * @return bool Whether it succeeded
+ */
+ public function exportData($db, $table, $crlf, $error_url, $sql_query)
+ {
+ $result = $GLOBALS['dbi']->tryQuery(
+ $sql_query, null, PMA_DatabaseInterface::QUERY_UNBUFFERED
+ );
+
+ $columns_cnt = $GLOBALS['dbi']->numFields($result);
+ for ($i = 0; $i < $columns_cnt; $i++) {
+ $columns[$i] = $GLOBALS['dbi']->fieldName($result, $i);
+ }
+ unset($i);
+
+ $buffer = $crlf . '%' . $crlf . '% ' . __('Data:') . ' ' . $table
+ . $crlf . '%' . $crlf . ' \\begin{longtable}{|';
+
+ for ($index = 0; $index < $columns_cnt; $index++) {
+ $buffer .= 'l|';
+ }
+ $buffer .= '} ' . $crlf ;
+
+ $buffer .= ' \\hline \\endhead \\hline \\endfoot \\hline ' . $crlf;
+ if (isset($GLOBALS['latex_caption'])) {
+ $buffer .= ' \\caption{'
+ . PMA_Util::expandUserString(
+ $GLOBALS['latex_data_caption'],
+ array(
+ 'texEscape',
+ get_class($this),
+ 'libraries/plugins/export/' . get_class($this) . ".class.php"
+ ),
+ array('table' => $table, 'database' => $db)
+ )
+ . '} \\label{'
+ . PMA_Util::expandUserString(
+ $GLOBALS['latex_data_label'],
+ null,
+ array('table' => $table, 'database' => $db)
+ )
+ . '} \\\\';
+ }
+ if (! PMA_exportOutputHandler($buffer)) {
+ return false;
+ }
+
+ // show column names
+ if (isset($GLOBALS['latex_columns'])) {
+ $buffer = '\\hline ';
+ for ($i = 0; $i < $columns_cnt; $i++) {
+ $buffer .= '\\multicolumn{1}{|c|}{\\textbf{'
+ . self::texEscape(stripslashes($columns[$i])) . '}} & ';
+ }
+
+ $buffer = substr($buffer, 0, -2) . '\\\\ \\hline \hline ';
+ if (! PMA_exportOutputHandler($buffer . ' \\endfirsthead ' . $crlf)) {
+ return false;
+ }
+ if (isset($GLOBALS['latex_caption'])) {
+ if (! PMA_exportOutputHandler(
+ '\\caption{'
+ . PMA_Util::expandUserString(
+ $GLOBALS['latex_data_continued_caption'],
+ array(
+ 'texEscape',
+ get_class($this),
+ 'libraries/plugins/export/'
+ . get_class($this) . ".class.php"
+ ),
+ array('table' => $table, 'database' => $db)
+ )
+ . '} \\\\ '
+ )) {
+ return false;
+ }
+ }
+ if (! PMA_exportOutputHandler($buffer . '\\endhead \\endfoot' . $crlf)) {
+ return false;
+ }
+ } else {
+ if (! PMA_exportOutputHandler('\\\\ \hline')) {
+ return false;
+ }
+ }
+
+ // print the whole table
+ while ($record = $GLOBALS['dbi']->fetchAssoc($result)) {
+ $buffer = '';
+ // print each row
+ for ($i = 0; $i < $columns_cnt; $i++) {
+ if ((! function_exists('is_null')
+ || ! is_null($record[$columns[$i]]))
+ && isset($record[$columns[$i]])
+ ) {
+ $column_value = self::texEscape(
+ stripslashes($record[$columns[$i]])
+ );
+ } else {
+ $column_value = $GLOBALS['latex_null'];
+ }
+
+ // last column ... no need for & character
+ if ($i == ($columns_cnt - 1)) {
+ $buffer .= $column_value;
+ } else {
+ $buffer .= $column_value . " & ";
+ }
+ }
+ $buffer .= ' \\\\ \\hline ' . $crlf;
+ if (! PMA_exportOutputHandler($buffer)) {
+ return false;
+ }
+ }
+
+ $buffer = ' \\end{longtable}' . $crlf;
+ if (! PMA_exportOutputHandler($buffer)) {
+ return false;
+ }
+
+ $GLOBALS['dbi']->freeResult($result);
+ return true;
+ } // end getTableLaTeX
+
+ /**
+ * Outputs table's structure
+ *
+ * @param string $db database name
+ * @param string $table table name
+ * @param string $crlf the end of line sequence
+ * @param string $error_url the url to go back in case of error
+ * @param string $export_mode 'create_table', 'triggers', 'create_view',
+ * 'stand_in'
+ * @param string $export_type 'server', 'database', 'table'
+ * @param bool $do_relation whether to include relation comments
+ * @param bool $do_comments whether to include the pmadb-style column
+ * comments as comments in the structure;
+ * this is deprecated but the parameter is
+ * left here because export.php calls
+ * exportStructure() also for other
+ * export types which use this parameter
+ * @param bool $do_mime whether to include mime comments
+ * @param bool $dates whether to include creation/update/check dates
+ *
+ * @return bool Whether it succeeded
+ */
+ public function exportStructure(
+ $db,
+ $table,
+ $crlf,
+ $error_url,
+ $export_mode,
+ $export_type,
+ $do_relation = false,
+ $do_comments = false,
+ $do_mime = false,
+ $dates = false
+ ) {
+ global $cfgRelation;
+
+ /* We do not export triggers */
+ if ($export_mode == 'triggers') {
+ return true;
+ }
+
+ /**
+ * Get the unique keys in the table
+ */
+ $unique_keys = array();
+ $keys = $GLOBALS['dbi']->getTableIndexes($db, $table);
+ foreach ($keys as $key) {
+ if ($key['Non_unique'] == 0) {
+ $unique_keys[] = $key['Column_name'];
+ }
+ }
+
+ /**
+ * Gets fields properties
+ */
+ $GLOBALS['dbi']->selectDb($db);
+
+ // Check if we can use Relations
+ if ($do_relation && ! empty($cfgRelation['relation'])) {
+ // Find which tables are related with the current one and write it in
+ // an array
+ $res_rel = PMA_getForeigners($db, $table);
+
+ if ($res_rel && count($res_rel) > 0) {
+ $have_rel = true;
+ } else {
+ $have_rel = false;
+ }
+ } else {
+ $have_rel = false;
+ } // end if
+
+ /**
+ * Displays the table structure
+ */
+ $buffer = $crlf . '%' . $crlf . '% ' . __('Structure:') . ' ' . $table
+ . $crlf . '%' . $crlf . ' \\begin{longtable}{';
+ if (! PMA_exportOutputHandler($buffer)) {
+ return false;
+ }
+
+ $columns_cnt = 4;
+ $alignment = '|l|c|c|c|';
+ if ($do_relation && $have_rel) {
+ $columns_cnt++;
+ $alignment .= 'l|';
+ }
+ if ($do_comments) {
+ $columns_cnt++;
+ $alignment .= 'l|';
+ }
+ if ($do_mime && $cfgRelation['mimework']) {
+ $columns_cnt++;
+ $alignment .='l|';
+ }
+ $buffer = $alignment . '} ' . $crlf ;
+
+ $header = ' \\hline ';
+ $header .= '\\multicolumn{1}{|c|}{\\textbf{' . __('Column')
+ . '}} & \\multicolumn{1}{|c|}{\\textbf{' . __('Type')
+ . '}} & \\multicolumn{1}{|c|}{\\textbf{' . __('Null')
+ . '}} & \\multicolumn{1}{|c|}{\\textbf{' . __('Default') . '}}';
+ if ($do_relation && $have_rel) {
+ $header .= ' & \\multicolumn{1}{|c|}{\\textbf{' . __('Links to') . '}}';
+ }
+ if ($do_comments) {
+ $header .= ' & \\multicolumn{1}{|c|}{\\textbf{' . __('Comments') . '}}';
+ $comments = PMA_getComments($db, $table);
+ }
+ if ($do_mime && $cfgRelation['mimework']) {
+ $header .= ' & \\multicolumn{1}{|c|}{\\textbf{MIME}}';
+ $mime_map = PMA_getMIME($db, $table, true);
+ }
+
+ // Table caption for first page and label
+ if (isset($GLOBALS['latex_caption'])) {
+ $buffer .= ' \\caption{'
+ . PMA_Util::expandUserString(
+ $GLOBALS['latex_structure_caption'],
+ array(
+ 'texEscape',
+ get_class($this),
+ 'libraries/plugins/export/' . get_class($this) . ".class.php"
+ ),
+ array('table' => $table, 'database' => $db)
+ )
+ . '} \\label{'
+ . PMA_Util::expandUserString(
+ $GLOBALS['latex_structure_label'],
+ null,
+ array('table' => $table, 'database' => $db)
+ )
+ . '} \\\\' . $crlf;
+ }
+ $buffer .= $header . ' \\\\ \\hline \\hline' . $crlf
+ . '\\endfirsthead' . $crlf;
+ // Table caption on next pages
+ if (isset($GLOBALS['latex_caption'])) {
+ $buffer .= ' \\caption{'
+ . PMA_Util::expandUserString(
+ $GLOBALS['latex_structure_continued_caption'],
+ array(
+ 'texEscape',
+ get_class($this),
+ 'libraries/plugins/export/' . get_class($this) . ".class.php"
+ ),
+ array('table' => $table, 'database' => $db)
+ )
+ . '} \\\\ ' . $crlf;
+ }
+ $buffer .= $header . ' \\\\ \\hline \\hline \\endhead \\endfoot ' . $crlf;
+
+ if (! PMA_exportOutputHandler($buffer)) {
+ return false;
+ }
+
+ $fields = $GLOBALS['dbi']->getColumns($db, $table);
+ foreach ($fields as $row) {
+ $extracted_columnspec
+ = PMA_Util::extractColumnSpec(
+ $row['Type']
+ );
+ $type = $extracted_columnspec['print_type'];
+ if (empty($type)) {
+ $type = ' ';
+ }
+
+ if (! isset($row['Default'])) {
+ if ($row['Null'] != 'NO') {
+ $row['Default'] = 'NULL';
+ }
+ }
+
+ $field_name = $row['Field'];
+
+ $local_buffer = $field_name . "\000" . $type . "\000"
+ . (($row['Null'] == '' || $row['Null'] == 'NO')
+ ? __('No') : __('Yes'))
+ . "\000" . (isset($row['Default']) ? $row['Default'] : '');
+
+ if ($do_relation && $have_rel) {
+ $local_buffer .= "\000";
+ if (isset($res_rel[$field_name])) {
+ $local_buffer .= $res_rel[$field_name]['foreign_table'] . ' ('
+ . $res_rel[$field_name]['foreign_field'] . ')';
+ }
+ }
+ if ($do_comments && $cfgRelation['commwork']) {
+ $local_buffer .= "\000";
+ if (isset($comments[$field_name])) {
+ $local_buffer .= $comments[$field_name];
+ }
+ }
+ if ($do_mime && $cfgRelation['mimework']) {
+ $local_buffer .= "\000";
+ if (isset($mime_map[$field_name])) {
+ $local_buffer .= str_replace(
+ '_',
+ '/',
+ $mime_map[$field_name]['mimetype']
+ );
+ }
+ }
+ $local_buffer = self::texEscape($local_buffer);
+ if ($row['Key']=='PRI') {
+ $pos=strpos($local_buffer, "\000");
+ $local_buffer = '\\textit{'
+ . substr($local_buffer, 0, $pos)
+ . '}' . substr($local_buffer, $pos);
+ }
+ if (in_array($field_name, $unique_keys)) {
+ $pos=strpos($local_buffer, "\000");
+ $local_buffer = '\\textbf{'
+ . substr($local_buffer, 0, $pos)
+ . '}' . substr($local_buffer, $pos);
+ }
+ $buffer = str_replace("\000", ' & ', $local_buffer);
+ $buffer .= ' \\\\ \\hline ' . $crlf;
+
+ if (! PMA_exportOutputHandler($buffer)) {
+ return false;
+ }
+ } // end while
+
+ $buffer = ' \\end{longtable}' . $crlf;
+ return PMA_exportOutputHandler($buffer);
+ } // end of the 'exportStructure' method
+
+ /**
+ * Escapes some special characters for use in TeX/LaTeX
+ *
+ * @param string $string the string to convert
+ *
+ * @return string the converted string with escape codes
+ */
+ public static function texEscape($string)
+ {
+ $escape = array('$', '%', '{', '}', '&', '#', '_', '^');
+ $cnt_escape = count($escape);
+ for ($k = 0; $k < $cnt_escape; $k++) {
+ $string = str_replace($escape[$k], '\\' . $escape[$k], $string);
+ }
+ return $string;
+ }
+}
+?>
diff --git a/libraries/plugins/export/ExportMediawiki.class.php b/libraries/plugins/export/ExportMediawiki.class.php
new file mode 100644
index 0000000000..7038144b95
--- /dev/null
+++ b/libraries/plugins/export/ExportMediawiki.class.php
@@ -0,0 +1,366 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Set of functions used to build MediaWiki dumps of tables
+ *
+ * @package PhpMyAdmin-Export
+ * @subpackage MediaWiki
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/* Get the export interface */
+require_once 'libraries/plugins/ExportPlugin.class.php';
+
+/**
+ * Handles the export for the MediaWiki class
+ *
+ * @package PhpMyAdmin-Export
+ * @subpackage MediaWiki
+ */
+class ExportMediawiki extends ExportPlugin
+{
+ /**
+ * Constructor
+ */
+ public function __construct()
+ {
+ $this->setProperties();
+ }
+
+ /**
+ * Sets the export MediaWiki properties
+ *
+ * @return void
+ */
+ protected function setProperties()
+ {
+ $props = 'libraries/properties/';
+ include_once "$props/plugins/ExportPluginProperties.class.php";
+ include_once "$props/options/groups/OptionsPropertyRootGroup.class.php";
+ include_once "$props/options/groups/OptionsPropertyMainGroup.class.php";
+ include_once "$props/options/groups/OptionsPropertySubgroup.class.php";
+ include_once "$props/options/items/MessageOnlyPropertyItem.class.php";
+ include_once "$props/options/items/RadioPropertyItem.class.php";
+ include_once "$props/options/items/BoolPropertyItem.class.php";
+
+ $exportPluginProperties = new ExportPluginProperties();
+ $exportPluginProperties->setText('MediaWiki Table');
+ $exportPluginProperties->setExtension('mediawiki');
+ $exportPluginProperties->setMimeType('text/plain');
+ $exportPluginProperties->setOptionsText(__('Options'));
+
+ // create the root group that will be the options field for
+ // $exportPluginProperties
+ // this will be shown as "Format specific options"
+ $exportSpecificOptions = new OptionsPropertyRootGroup();
+ $exportSpecificOptions->setName("Format Specific Options");
+
+ // general options main group
+ $generalOptions = new OptionsPropertyMainGroup();
+ $generalOptions->setName("general_opts");
+ $generalOptions->setText(__('Dump table'));
+
+ // what to dump (structure/data/both)
+ $subgroup = new OptionsPropertySubgroup();
+ $subgroup->setName("dump_table");
+ $subgroup->setText("Dump table");
+ $leaf = new RadioPropertyItem();
+ $leaf->setName('structure_or_data');
+ $leaf->setValues(
+ array(
+ 'structure' => __('structure'),
+ 'data' => __('data'),
+ 'structure_and_data' => __('structure and data')
+ )
+ );
+ $subgroup->setSubgroupHeader($leaf);
+ $generalOptions->addProperty($subgroup);
+
+ // export table name
+ $leaf = new BoolPropertyItem();
+ $leaf->setName("caption");
+ $leaf->setText(__('Export table names'));
+ $generalOptions->addProperty($leaf);
+
+ // export table headers
+ $leaf = new BoolPropertyItem();
+ $leaf->setName("headers");
+ $leaf->setText(__('Export table headers'));
+ $generalOptions->addProperty($leaf);
+ //add the main group to the root group
+ $exportSpecificOptions->addProperty($generalOptions);
+
+ // set the options for the export plugin property item
+ $exportPluginProperties->setOptions($exportSpecificOptions);
+ $this->properties = $exportPluginProperties;
+ }
+
+ /**
+ * This method is called when any PluginManager to which the observer
+ * is attached calls PluginManager::notify()
+ *
+ * @param SplSubject $subject The PluginManager notifying the observer
+ * of an update.
+ *
+ * @return void
+ */
+ public function update (SplSubject $subject)
+ {
+ }
+
+ /**
+ * Outputs export header
+ *
+ * @return bool Whether it succeeded
+ */
+ public function exportHeader ()
+ {
+ return true;
+ }
+
+ /**
+ * Outputs export footer
+ *
+ * @return bool Whether it succeeded
+ */
+ public function exportFooter ()
+ {
+ return true;
+ }
+
+ /**
+ * Outputs database header
+ *
+ * @param string $db Database name
+ *
+ * @return bool Whether it succeeded
+ */
+ public function exportDBHeader ($db)
+ {
+ return true;
+ }
+
+ /**
+ * Outputs database footer
+ *
+ * @param string $db Database name
+ *
+ * @return bool Whether it succeeded
+ */
+ public function exportDBFooter ($db)
+ {
+ return true;
+ }
+
+ /**
+ * Outputs CREATE DATABASE statement
+ *
+ * @param string $db Database name
+ *
+ * @return bool Whether it succeeded
+ */
+ public function exportDBCreate($db)
+ {
+ return true;
+ }
+
+ /**
+ * Outputs table's structure
+ *
+ * @param string $db database name
+ * @param string $table table name
+ * @param string $crlf the end of line sequence
+ * @param string $error_url the url to go back in case of error
+ * @param string $export_mode 'create_table','triggers','create_view',
+ * 'stand_in'
+ * @param string $export_type 'server', 'database', 'table'
+ * @param bool $do_relation whether to include relation comments
+ * @param bool $do_comments whether to include the pmadb-style column
+ * comments as comments in the structure; this is
+ * deprecated but the parameter is left here
+ * because export.php calls exportStructure()
+ * also for other export types which use this
+ * parameter
+ * @param bool $do_mime whether to include mime comments
+ * @param bool $dates whether to include creation/update/check dates
+ *
+ * @return bool Whether it succeeded
+ */
+ public function exportStructure(
+ $db,
+ $table,
+ $crlf,
+ $error_url,
+ $export_mode,
+ $export_type,
+ $do_relation = false,
+ $do_comments = false,
+ $do_mime = false,
+ $dates = false
+ ) {
+ switch($export_mode) {
+ case 'create_table':
+ $columns = $GLOBALS['dbi']->getColumns($db, $table);
+ $columns = array_values($columns);
+ $row_cnt = count($columns);
+
+ // Print structure comment
+ $output = $this->_exportComment(
+ "Table structure for "
+ . PMA_Util::backquote($table)
+ );
+
+ // Begin the table construction
+ $output .= "{| class=\"wikitable\" style=\"text-align:center;\""
+ . $this->_exportCRLF();
+
+ // Add the table name
+ if ($GLOBALS['mediawiki_caption']) {
+ $output .= "|+'''" . $table . "'''" . $this->_exportCRLF();
+ }
+
+ // Add the table headers
+ if ($GLOBALS['mediawiki_headers']) {
+ $output .= "|- style=\"background:#ffdead;\"" . $this->_exportCRLF();
+ $output .= "! style=\"background:#ffffff\" | "
+ . $this->_exportCRLF();
+ for ($i = 0; $i < $row_cnt; ++$i) {
+ $output .= " | " . $columns[$i]['Field']. $this->_exportCRLF();
+ }
+ }
+
+ // Add the table structure
+ $output .= "|-" . $this->_exportCRLF();
+ $output .= "! Type" . $this->_exportCRLF();
+ for ($i = 0; $i < $row_cnt; ++$i) {
+ $output .= " | " . $columns[$i]['Type'] . $this->_exportCRLF();
+ }
+
+ $output .= "|-" . $this->_exportCRLF();
+ $output .= "! Null" . $this->_exportCRLF();
+ for ($i = 0; $i < $row_cnt; ++$i) {
+ $output .= " | " . $columns[$i]['Null'] . $this->_exportCRLF();
+ }
+
+ $output .= "|-" . $this->_exportCRLF();
+ $output .= "! Default" . $this->_exportCRLF();
+ for ($i = 0; $i < $row_cnt; ++$i) {
+ $output .= " | " . $columns[$i]['Default'] . $this->_exportCRLF();
+ }
+
+ $output .= "|-" . $this->_exportCRLF();
+ $output .= "! Extra" . $this->_exportCRLF();
+ for ($i = 0; $i < $row_cnt; ++$i) {
+ $output .= " | " . $columns[$i]['Extra'] . $this->_exportCRLF();
+ }
+
+ $output .= "|}" . str_repeat($this->_exportCRLF(), 2);
+ break;
+ } // end switch
+
+ return PMA_exportOutputHandler($output);
+ }
+
+ /**
+ * Outputs the content of a table in MediaWiki format
+ *
+ * @param string $db database name
+ * @param string $table table name
+ * @param string $crlf the end of line sequence
+ * @param string $error_url the url to go back in case of error
+ * @param string $sql_query SQL query for obtaining data
+ *
+ * @return bool Whether it succeeded
+ */
+ public function exportData(
+ $db,
+ $table,
+ $crlf,
+ $error_url,
+ $sql_query
+ ) {
+ // Print data comment
+ $output = $this->_exportComment(
+ "Table data for ". PMA_Util::backquote($table)
+ );
+
+ // Begin the table construction
+ // Use the "wikitable" class for style
+ // Use the "sortable" class for allowing tables to be sorted by column
+ $output .= "{| class=\"wikitable sortable\" style=\"text-align:center;\""
+ . $this->_exportCRLF();
+
+ // Add the table name
+ if ($GLOBALS['mediawiki_caption']) {
+ $output .= "|+'''" . $table . "'''" . $this->_exportCRLF();
+ }
+
+ // Add the table headers
+ if ($GLOBALS['mediawiki_headers']) {
+ // Get column names
+ $column_names = $GLOBALS['dbi']->getColumnNames($db, $table);
+
+ // Add column names as table headers
+ if ( ! is_null($column_names) ) {
+ // Use '|-' for separating rows
+ $output .= "|-" . $this->_exportCRLF();
+
+ // Use '!' for separating table headers
+ foreach ($column_names as $column) {
+ $output .= " ! " . $column . "" . $this->_exportCRLF();
+ }
+ }
+ }
+
+ // Get the table data from the database
+ $result = $GLOBALS['dbi']->query(
+ $sql_query, null, PMA_DatabaseInterface::QUERY_UNBUFFERED
+ );
+ $fields_cnt = $GLOBALS['dbi']->numFields($result);
+
+ while ($row = $GLOBALS['dbi']->fetchRow($result)) {
+ $output .= "|-" . $this->_exportCRLF();
+
+ // Use '|' for separating table columns
+ for ($i = 0; $i < $fields_cnt; ++ $i) {
+ $output .= " | " . $row[$i] . "" . $this->_exportCRLF();
+ }
+ }
+
+ // End table construction
+ $output .= "|}" . str_repeat($this->_exportCRLF(), 2);
+ return PMA_exportOutputHandler($output);
+ }
+
+ /**
+ * Outputs comments containing info about the exported tables
+ *
+ * @param string $text Text of comment
+ *
+ * @return string The formatted comment
+ */
+ private function _exportComment($text = '')
+ {
+ // see http://www.mediawiki.org/wiki/Help:Formatting
+ $comment = $this->_exportCRLF();
+ $comment .= '<!--' . $this->_exportCRLF();
+ $comment .= $text . $this->_exportCRLF();
+ $comment .= '-->' . str_repeat($this->_exportCRLF(), 2);
+
+ return $comment;
+ }
+
+ /**
+ * Outputs CRLF
+ *
+ * @return string CRLF
+ */
+ private function _exportCRLF()
+ {
+ // The CRLF expected by the mediawiki format is "\n"
+ return "\n";
+ }
+}
+?>
diff --git a/libraries/plugins/export/ExportOds.class.php b/libraries/plugins/export/ExportOds.class.php
new file mode 100644
index 0000000000..f3d40d2680
--- /dev/null
+++ b/libraries/plugins/export/ExportOds.class.php
@@ -0,0 +1,336 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Set of functions used to build OpenDocument Spreadsheet dumps of tables
+ *
+ * @package PhpMyAdmin-Export
+ * @subpackage ODS
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/* Get the export interface */
+require_once 'libraries/plugins/ExportPlugin.class.php';
+
+$GLOBALS['ods_buffer'] = '';
+require_once 'libraries/opendocument.lib.php';
+
+/**
+ * Handles the export for the ODS class
+ *
+ * @package PhpMyAdmin-Export
+ * @subpackage ODS
+ */
+class ExportOds extends ExportPlugin
+{
+ /**
+ * Constructor
+ */
+ public function __construct()
+ {
+ $this->setProperties();
+ }
+
+ /**
+ * Sets the export ODS properties
+ *
+ * @return void
+ */
+ protected function setProperties()
+ {
+ $props = 'libraries/properties/';
+ include_once "$props/plugins/ExportPluginProperties.class.php";
+ include_once "$props/options/groups/OptionsPropertyRootGroup.class.php";
+ include_once "$props/options/groups/OptionsPropertyMainGroup.class.php";
+ include_once "$props/options/items/TextPropertyItem.class.php";
+ include_once "$props/options/items/BoolPropertyItem.class.php";
+ include_once "$props/options/items/HiddenPropertyItem.class.php";
+
+ $exportPluginProperties = new ExportPluginProperties();
+ $exportPluginProperties->setText('OpenDocument Spreadsheet');
+ $exportPluginProperties->setExtension('ods');
+ $exportPluginProperties->setMimeType(
+ 'application/vnd.oasis.opendocument.spreadsheet'
+ );
+ $exportPluginProperties->setForceFile(true);
+ $exportPluginProperties->setOptionsText(__('Options'));
+
+ // create the root group that will be the options field for
+ // $exportPluginProperties
+ // this will be shown as "Format specific options"
+ $exportSpecificOptions = new OptionsPropertyRootGroup();
+ $exportSpecificOptions->setName("Format Specific Options");
+
+ // general options main group
+ $generalOptions = new OptionsPropertyMainGroup();
+ $generalOptions->setName("general_opts");
+ // create primary items and add them to the group
+ $leaf = new TextPropertyItem();
+ $leaf->setName("null");
+ $leaf->setText(__('Replace NULL with:'));
+ $generalOptions->addProperty($leaf);
+ $leaf = new BoolPropertyItem();
+ $leaf->setName("columns");
+ $leaf->setText(__('Put columns names in the first row'));
+ $generalOptions->addProperty($leaf);
+ $leaf = new HiddenPropertyItem();
+ $leaf->setName("structure_or_data");
+ $generalOptions->addProperty($leaf);
+ // add the main group to the root group
+ $exportSpecificOptions->addProperty($generalOptions);
+
+ // set the options for the export plugin property item
+ $exportPluginProperties->setOptions($exportSpecificOptions);
+ $this->properties = $exportPluginProperties;
+ }
+
+ /**
+ * This method is called when any PluginManager to which the observer
+ * is attached calls PluginManager::notify()
+ *
+ * @param SplSubject $subject The PluginManager notifying the observer
+ * of an update.
+ *
+ * @return void
+ */
+ public function update (SplSubject $subject)
+ {
+ }
+
+ /**
+ * Outputs export header
+ *
+ * @return bool Whether it succeeded
+ */
+ public function exportHeader ()
+ {
+ $GLOBALS['ods_buffer'] .= '<?xml version="1.0" encoding="utf-8"?' . '>'
+ . '<office:document-content '
+ . $GLOBALS['OpenDocumentNS'] . 'office:version="1.0">'
+ . '<office:automatic-styles>'
+ . '<number:date-style style:name="N37"'
+ .' number:automatic-order="true">'
+ . '<number:month number:style="long"/>'
+ . '<number:text>/</number:text>'
+ . '<number:day number:style="long"/>'
+ . '<number:text>/</number:text>'
+ . '<number:year/>'
+ . '</number:date-style>'
+ . '<number:time-style style:name="N43">'
+ . '<number:hours number:style="long"/>'
+ . '<number:text>:</number:text>'
+ . '<number:minutes number:style="long"/>'
+ . '<number:text>:</number:text>'
+ . '<number:seconds number:style="long"/>'
+ . '<number:text> </number:text>'
+ . '<number:am-pm/>'
+ . '</number:time-style>'
+ . '<number:date-style style:name="N50"'
+ . ' number:automatic-order="true"'
+ . ' number:format-source="language">'
+ . '<number:month/>'
+ . '<number:text>/</number:text>'
+ . '<number:day/>'
+ . '<number:text>/</number:text>'
+ . '<number:year/>'
+ . '<number:text> </number:text>'
+ . '<number:hours number:style="long"/>'
+ . '<number:text>:</number:text>'
+ . '<number:minutes number:style="long"/>'
+ . '<number:text> </number:text>'
+ . '<number:am-pm/>'
+ . '</number:date-style>'
+ . '<style:style style:name="DateCell" style:family="table-cell"'
+ . ' style:parent-style-name="Default" style:data-style-name="N37"/>'
+ . '<style:style style:name="TimeCell" style:family="table-cell"'
+ . ' style:parent-style-name="Default" style:data-style-name="N43"/>'
+ . '<style:style style:name="DateTimeCell" style:family="table-cell"'
+ . ' style:parent-style-name="Default" style:data-style-name="N50"/>'
+ . '</office:automatic-styles>'
+ . '<office:body>'
+ . '<office:spreadsheet>';
+ return true;
+ }
+
+ /**
+ * Outputs export footer
+ *
+ * @return bool Whether it succeeded
+ */
+ public function exportFooter ()
+ {
+ $GLOBALS['ods_buffer'] .= '</office:spreadsheet>'
+ . '</office:body>'
+ . '</office:document-content>';
+ if (! PMA_exportOutputHandler(
+ PMA_createOpenDocument(
+ 'application/vnd.oasis.opendocument.spreadsheet',
+ $GLOBALS['ods_buffer']
+ )
+ )) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Outputs database header
+ *
+ * @param string $db Database name
+ *
+ * @return bool Whether it succeeded
+ */
+ public function exportDBHeader ($db)
+ {
+ return true;
+ }
+
+ /**
+ * Outputs database footer
+ *
+ * @param string $db Database name
+ *
+ * @return bool Whether it succeeded
+ */
+ public function exportDBFooter ($db)
+ {
+ return true;
+ }
+
+ /**
+ * Outputs CREATE DATABASE statement
+ *
+ * @param string $db Database name
+ *
+ * @return bool Whether it succeeded
+ */
+ public function exportDBCreate($db)
+ {
+ return true;
+ }
+
+ /**
+ * Outputs the content of a table in NHibernate format
+ *
+ * @param string $db database name
+ * @param string $table table name
+ * @param string $crlf the end of line sequence
+ * @param string $error_url the url to go back in case of error
+ * @param string $sql_query SQL query for obtaining data
+ *
+ * @return bool Whether it succeeded
+ */
+ public function exportData($db, $table, $crlf, $error_url, $sql_query)
+ {
+ global $what;
+
+ // Gets the data from the database
+ $result = $GLOBALS['dbi']->query(
+ $sql_query, null, PMA_DatabaseInterface::QUERY_UNBUFFERED
+ );
+ $fields_cnt = $GLOBALS['dbi']->numFields($result);
+ $fields_meta = $GLOBALS['dbi']->getFieldsMeta($result);
+ $field_flags = array();
+ for ($j = 0; $j < $fields_cnt; $j++) {
+ $field_flags[$j] = $GLOBALS['dbi']->fieldFlags($result, $j);
+ }
+
+ $GLOBALS['ods_buffer'] .=
+ '<table:table table:name="' . htmlspecialchars($table) . '">';
+
+ // If required, get fields name at the first line
+ if (isset($GLOBALS[$what . '_columns'])) {
+ $GLOBALS['ods_buffer'] .= '<table:table-row>';
+ for ($i = 0; $i < $fields_cnt; $i++) {
+ $GLOBALS['ods_buffer'] .=
+ '<table:table-cell office:value-type="string">'
+ . '<text:p>'
+ . htmlspecialchars(
+ stripslashes($GLOBALS['dbi']->fieldName($result, $i))
+ )
+ . '</text:p>'
+ . '</table:table-cell>';
+ } // end for
+ $GLOBALS['ods_buffer'] .= '</table:table-row>';
+ } // end if
+
+ // Format the data
+ while ($row = $GLOBALS['dbi']->fetchRow($result)) {
+ $GLOBALS['ods_buffer'] .= '<table:table-row>';
+ for ($j = 0; $j < $fields_cnt; $j++) {
+ if (! isset($row[$j]) || is_null($row[$j])) {
+ $GLOBALS['ods_buffer'] .=
+ '<table:table-cell office:value-type="string">'
+ . '<text:p>'
+ . htmlspecialchars($GLOBALS[$what . '_null'])
+ . '</text:p>'
+ . '</table:table-cell>';
+ } elseif (stristr($field_flags[$j], 'BINARY')
+ && $fields_meta[$j]->blob
+ ) {
+ // ignore BLOB
+ $GLOBALS['ods_buffer'] .=
+ '<table:table-cell office:value-type="string">'
+ . '<text:p></text:p>'
+ . '</table:table-cell>';
+ } elseif ($fields_meta[$j]->type == "date") {
+ $GLOBALS['ods_buffer'] .=
+ '<table:table-cell office:value-type="date"'
+ . ' office:date-value="'
+ . date("Y-m-d", strtotime($row[$j]))
+ . '" table:style-name="DateCell">'
+ . '<text:p>'
+ . htmlspecialchars($row[$j])
+ . '</text:p>'
+ . '</table:table-cell>';
+ } elseif ($fields_meta[$j]->type == "time") {
+ $GLOBALS['ods_buffer'] .=
+ '<table:table-cell office:value-type="time"'
+ . ' office:time-value="'
+ . date("\P\TH\Hi\Ms\S", strtotime($row[$j]))
+ . '" table:style-name="TimeCell">'
+ . '<text:p>'
+ . htmlspecialchars($row[$j])
+ . '</text:p>'
+ . '</table:table-cell>';
+ } elseif ($fields_meta[$j]->type == "datetime") {
+ $GLOBALS['ods_buffer'] .=
+ '<table:table-cell office:value-type="date"'
+ . ' office:date-value="'
+ . date("Y-m-d\TH:i:s", strtotime($row[$j]))
+ . '" table:style-name="DateTimeCell">'
+ . '<text:p>'
+ . htmlspecialchars($row[$j])
+ . '</text:p>'
+ . '</table:table-cell>';
+ } elseif (($fields_meta[$j]->numeric
+ && $fields_meta[$j]->type != 'timestamp'
+ && ! $fields_meta[$j]->blob) || $fields_meta[$j]->type == 'real'
+ ) {
+ $GLOBALS['ods_buffer'] .=
+ '<table:table-cell office:value-type="float"'
+ . ' office:value="' . $row[$j] . '" >'
+ . '<text:p>'
+ . htmlspecialchars($row[$j])
+ . '</text:p>'
+ . '</table:table-cell>';
+ } else {
+ $GLOBALS['ods_buffer'] .=
+ '<table:table-cell office:value-type="string">'
+ . '<text:p>'
+ . htmlspecialchars($row[$j])
+ . '</text:p>'
+ . '</table:table-cell>';
+ }
+ } // end for
+ $GLOBALS['ods_buffer'] .= '</table:table-row>';
+ } // end while
+ $GLOBALS['dbi']->freeResult($result);
+
+ $GLOBALS['ods_buffer'] .= '</table:table>';
+
+ return true;
+ }
+}
+?>
diff --git a/libraries/plugins/export/ExportOdt.class.php b/libraries/plugins/export/ExportOdt.class.php
new file mode 100644
index 0000000000..5df6f65279
--- /dev/null
+++ b/libraries/plugins/export/ExportOdt.class.php
@@ -0,0 +1,734 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Set of functions used to build OpenDocument Text dumps of tables
+ *
+ * @package PhpMyAdmin-Export
+ * @subpackage ODT
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/* Get the export interface */
+require_once 'libraries/plugins/ExportPlugin.class.php';
+
+$GLOBALS['odt_buffer'] = '';
+require_once 'libraries/opendocument.lib.php';
+
+/**
+ * Handles the export for the ODT class
+ *
+ * @package PhpMyAdmin-Export
+ * @subpackage ODT
+ */
+class ExportOdt extends ExportPlugin
+{
+ /**
+ * Constructor
+ */
+ public function __construct()
+ {
+ $this->setProperties();
+ }
+
+ /**
+ * Sets the export ODT properties
+ *
+ * @return void
+ */
+ protected function setProperties()
+ {
+ global $plugin_param;
+ $hide_structure = false;
+ if ($plugin_param['export_type'] == 'table'
+ && ! $plugin_param['single_table']
+ ) {
+ $hide_structure = true;
+ }
+
+ $props = 'libraries/properties/';
+ include_once "$props/plugins/ExportPluginProperties.class.php";
+ include_once "$props/options/groups/OptionsPropertyRootGroup.class.php";
+ include_once "$props/options/groups/OptionsPropertyMainGroup.class.php";
+ include_once "$props/options/items/TextPropertyItem.class.php";
+ include_once "$props/options/items/BoolPropertyItem.class.php";
+ include_once "$props/options/items/HiddenPropertyItem.class.php";
+ include_once "$props/options/items/RadioPropertyItem.class.php";
+
+ $exportPluginProperties = new ExportPluginProperties();
+ $exportPluginProperties->setText('OpenDocument Text');
+ $exportPluginProperties->setExtension('odt');
+ $exportPluginProperties->setMimeType(
+ 'application/vnd.oasis.opendocument.text'
+ );
+ $exportPluginProperties->setForceFile(true);
+ $exportPluginProperties->setOptionsText(__('Options'));
+
+ // create the root group that will be the options field for
+ // $exportPluginProperties
+ // this will be shown as "Format specific options"
+ $exportSpecificOptions = new OptionsPropertyRootGroup();
+ $exportSpecificOptions->setName("Format Specific Options");
+
+ // what to dump (structure/data/both) main group
+ $dumpWhat = new OptionsPropertyMainGroup();
+ $dumpWhat->setName("general_opts");
+ $dumpWhat->setText(__('Dump table'));
+ // create primary items and add them to the group
+ $leaf = new RadioPropertyItem();
+ $leaf->setName("structure_or_data");
+ $leaf->setValues(
+ array(
+ 'structure' => __('structure'),
+ 'data' => __('data'),
+ 'structure_and_data' => __('structure and data')
+ )
+ );
+ $dumpWhat->addProperty($leaf);
+ // add the main group to the root group
+ $exportSpecificOptions->addProperty($dumpWhat);
+
+
+ // structure options main group
+ if (! $hide_structure) {
+ $structureOptions = new OptionsPropertyMainGroup();
+ $structureOptions->setName("structure");
+ $structureOptions->setText(__('Object creation options'));
+ $structureOptions->setForce('data');
+ // create primary items and add them to the group
+ if (! empty($GLOBALS['cfgRelation']['relation'])) {
+ $leaf = new BoolPropertyItem();
+ $leaf->setName("relation");
+ $leaf->setText(__('Display foreign key relationships'));
+ $structureOptions->addProperty($leaf);
+ }
+ $leaf = new BoolPropertyItem();
+ $leaf->setName("comments");
+ $leaf->setText(__('Display comments'));
+ $structureOptions->addProperty($leaf);
+ if (! empty($GLOBALS['cfgRelation']['mimework'])) {
+ $leaf = new BoolPropertyItem();
+ $leaf->setName("mime");
+ $leaf->setText(__('Display MIME types'));
+ $structureOptions->addProperty($leaf);
+ }
+ // add the main group to the root group
+ $exportSpecificOptions->addProperty($structureOptions);
+ }
+
+ // data options main group
+ $dataOptions = new OptionsPropertyMainGroup();
+ $dataOptions->setName("data");
+ $dataOptions->setText(__('Data dump options'));
+ $dataOptions->setForce('structure');
+ // create primary items and add them to the group
+ $leaf = new BoolPropertyItem();
+ $leaf->setName("columns");
+ $leaf->setText(__('Put columns names in the first row'));
+ $dataOptions->addProperty($leaf);
+ $leaf = new TextPropertyItem();
+ $leaf->setName('null');
+ $leaf->setText(__('Replace NULL with:'));
+ $dataOptions->addProperty($leaf);
+ // add the main group to the root group
+ $exportSpecificOptions->addProperty($dataOptions);
+
+ // set the options for the export plugin property item
+ $exportPluginProperties->setOptions($exportSpecificOptions);
+ $this->properties = $exportPluginProperties;
+ }
+
+ /**
+ * This method is called when any PluginManager to which the observer
+ * is attached calls PluginManager::notify()
+ *
+ * @param SplSubject $subject The PluginManager notifying the observer
+ * of an update.
+ *
+ * @return void
+ */
+ public function update (SplSubject $subject)
+ {
+ }
+
+ /**
+ * Outputs export header
+ *
+ * @return bool Whether it succeeded
+ */
+ public function exportHeader ()
+ {
+ $GLOBALS['odt_buffer'] .= '<?xml version="1.0" encoding="utf-8"?' . '>'
+ . '<office:document-content '
+ . $GLOBALS['OpenDocumentNS'] . 'office:version="1.0">'
+ . '<office:body>'
+ . '<office:text>';
+ return true;
+ }
+
+ /**
+ * Outputs export footer
+ *
+ * @return bool Whether it succeeded
+ */
+ public function exportFooter ()
+ {
+ $GLOBALS['odt_buffer'] .= '</office:text>'
+ . '</office:body>'
+ . '</office:document-content>';
+ if (! PMA_exportOutputHandler(
+ PMA_createOpenDocument(
+ 'application/vnd.oasis.opendocument.text',
+ $GLOBALS['odt_buffer']
+ )
+ )) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Outputs database header
+ *
+ * @param string $db Database name
+ *
+ * @return bool Whether it succeeded
+ */
+ public function exportDBHeader ($db)
+ {
+ $GLOBALS['odt_buffer'] .=
+ '<text:h text:outline-level="1" text:style-name="Heading_1"'
+ . ' text:is-list-header="true">'
+ . __('Database') . ' ' . htmlspecialchars($db)
+ . '</text:h>';
+ return true;
+ }
+
+ /**
+ * Outputs database footer
+ *
+ * @param string $db Database name
+ *
+ * @return bool Whether it succeeded
+ */
+ public function exportDBFooter ($db)
+ {
+ return true;
+ }
+
+ /**
+ * Outputs CREATE DATABASE statement
+ *
+ * @param string $db Database name
+ *
+ * @return bool Whether it succeeded
+ */
+ public function exportDBCreate($db)
+ {
+ return true;
+ }
+ /**
+ * Outputs the content of a table in NHibernate format
+ *
+ * @param string $db database name
+ * @param string $table table name
+ * @param string $crlf the end of line sequence
+ * @param string $error_url the url to go back in case of error
+ * @param string $sql_query SQL query for obtaining data
+ *
+ * @return bool Whether it succeeded
+ */
+ public function exportData($db, $table, $crlf, $error_url, $sql_query)
+ {
+ global $what;
+
+ // Gets the data from the database
+ $result = $GLOBALS['dbi']->query(
+ $sql_query, null, PMA_DatabaseInterface::QUERY_UNBUFFERED
+ );
+ $fields_cnt = $GLOBALS['dbi']->numFields($result);
+ $fields_meta = $GLOBALS['dbi']->getFieldsMeta($result);
+ $field_flags = array();
+ for ($j = 0; $j < $fields_cnt; $j++) {
+ $field_flags[$j] = $GLOBALS['dbi']->fieldFlags($result, $j);
+ }
+
+ $GLOBALS['odt_buffer'] .=
+ '<text:h text:outline-level="2" text:style-name="Heading_2"'
+ . ' text:is-list-header="true">'
+ . __('Dumping data for table') . ' ' . htmlspecialchars($table)
+ . '</text:h>'
+ . '<table:table'
+ . ' table:name="' . htmlspecialchars($table) . '_structure">'
+ . '<table:table-column'
+ . ' table:number-columns-repeated="' . $fields_cnt . '"/>';
+
+ // If required, get fields name at the first line
+ if (isset($GLOBALS[$what . '_columns'])) {
+ $GLOBALS['odt_buffer'] .= '<table:table-row>';
+ for ($i = 0; $i < $fields_cnt; $i++) {
+ $GLOBALS['odt_buffer'] .=
+ '<table:table-cell office:value-type="string">'
+ . '<text:p>'
+ . htmlspecialchars(
+ stripslashes($GLOBALS['dbi']->fieldName($result, $i))
+ )
+ . '</text:p>'
+ . '</table:table-cell>';
+ } // end for
+ $GLOBALS['odt_buffer'] .= '</table:table-row>';
+ } // end if
+
+ // Format the data
+ while ($row = $GLOBALS['dbi']->fetchRow($result)) {
+ $GLOBALS['odt_buffer'] .= '<table:table-row>';
+ for ($j = 0; $j < $fields_cnt; $j++) {
+ if (! isset($row[$j]) || is_null($row[$j])) {
+ $GLOBALS['odt_buffer'] .=
+ '<table:table-cell office:value-type="string">'
+ . '<text:p>'
+ . htmlspecialchars($GLOBALS[$what . '_null'])
+ . '</text:p>'
+ . '</table:table-cell>';
+ } elseif (stristr($field_flags[$j], 'BINARY')
+ && $fields_meta[$j]->blob
+ ) {
+ // ignore BLOB
+ $GLOBALS['odt_buffer'] .=
+ '<table:table-cell office:value-type="string">'
+ . '<text:p></text:p>'
+ . '</table:table-cell>';
+ } elseif ($fields_meta[$j]->numeric
+ && $fields_meta[$j]->type != 'timestamp'
+ && ! $fields_meta[$j]->blob
+ ) {
+ $GLOBALS['odt_buffer'] .=
+ '<table:table-cell office:value-type="float"'
+ . ' office:value="' . $row[$j] . '" >'
+ . '<text:p>'
+ . htmlspecialchars($row[$j])
+ . '</text:p>'
+ . '</table:table-cell>';
+ } else {
+ $GLOBALS['odt_buffer'] .=
+ '<table:table-cell office:value-type="string">'
+ . '<text:p>'
+ . htmlspecialchars($row[$j])
+ . '</text:p>'
+ . '</table:table-cell>';
+ }
+ } // end for
+ $GLOBALS['odt_buffer'] .= '</table:table-row>';
+ } // end while
+ $GLOBALS['dbi']->freeResult($result);
+
+ $GLOBALS['odt_buffer'] .= '</table:table>';
+
+ return true;
+ }
+
+ /**
+ * Returns a stand-in CREATE definition to resolve view dependencies
+ *
+ * @param string $db the database name
+ * @param string $view the view name
+ * @param string $crlf the end of line sequence
+ *
+ * @return bool true
+ */
+ public function getTableDefStandIn($db, $view, $crlf)
+ {
+ /**
+ * Gets fields properties
+ */
+ $GLOBALS['dbi']->selectDb($db);
+
+ /**
+ * Displays the table structure
+ */
+ $GLOBALS['odt_buffer'] .=
+ '<table:table table:name="'
+ . htmlspecialchars($view) . '_data">';
+ $columns_cnt = 4;
+ $GLOBALS['odt_buffer'] .=
+ '<table:table-column'
+ . ' table:number-columns-repeated="' . $columns_cnt . '"/>';
+ /* Header */
+ $GLOBALS['odt_buffer'] .= '<table:table-row>'
+ . '<table:table-cell office:value-type="string">'
+ . '<text:p>' . __('Column') . '</text:p>'
+ . '</table:table-cell>'
+ . '<table:table-cell office:value-type="string">'
+ . '<text:p>' . __('Type') . '</text:p>'
+ . '</table:table-cell>'
+ . '<table:table-cell office:value-type="string">'
+ . '<text:p>' . __('Null') . '</text:p>'
+ . '</table:table-cell>'
+ . '<table:table-cell office:value-type="string">'
+ . '<text:p>' . __('Default') . '</text:p>'
+ . '</table:table-cell>'
+ . '</table:table-row>';
+
+ $columns = $GLOBALS['dbi']->getColumns($db, $view);
+ foreach ($columns as $column) {
+ $GLOBALS['odt_buffer'] .= $this->formatOneColumnDefinition($column);
+ $GLOBALS['odt_buffer'] .= '</table:table-row>';
+ } // end foreach
+
+ $GLOBALS['odt_buffer'] .= '</table:table>';
+ return true;
+ }
+
+ /**
+ * Returns $table's CREATE definition
+ *
+ * @param string $db the database name
+ * @param string $table the table name
+ * @param string $crlf the end of line sequence
+ * @param string $error_url the url to go back in case of error
+ * @param bool $do_relation whether to include relation comments
+ * @param bool $do_comments whether to include the pmadb-style column
+ * comments as comments in the structure;
+ * this is deprecated but the parameter is
+ * left here because export.php calls
+ * PMA_exportStructure() also for other
+ * @param bool $do_mime whether to include mime comments
+ * @param bool $show_dates whether to include creation/update/check dates
+ * @param bool $add_semicolon whether to add semicolon and end-of-line at
+ * the end
+ * @param bool $view whether we're handling a view
+ *
+ * @return bool true
+ */
+ public function getTableDef(
+ $db,
+ $table,
+ $crlf,
+ $error_url,
+ $do_relation,
+ $do_comments,
+ $do_mime,
+ $show_dates = false,
+ $add_semicolon = true,
+ $view = false
+ ) {
+ global $cfgRelation;
+
+ /**
+ * Gets fields properties
+ */
+ $GLOBALS['dbi']->selectDb($db);
+
+ // Check if we can use Relations
+ if ($do_relation && ! empty($cfgRelation['relation'])) {
+ // Find which tables are related with the current one and write it in
+ // an array
+ $res_rel = PMA_getForeigners($db, $table);
+
+ if ($res_rel && count($res_rel) > 0) {
+ $have_rel = true;
+ } else {
+ $have_rel = false;
+ }
+ } else {
+ $have_rel = false;
+ } // end if
+
+ /**
+ * Displays the table structure
+ */
+ $GLOBALS['odt_buffer'] .= '<table:table table:name="'
+ . htmlspecialchars($table) . '_structure">';
+ $columns_cnt = 4;
+ if ($do_relation && $have_rel) {
+ $columns_cnt++;
+ }
+ if ($do_comments) {
+ $columns_cnt++;
+ }
+ if ($do_mime && $cfgRelation['mimework']) {
+ $columns_cnt++;
+ }
+ $GLOBALS['odt_buffer'] .= '<table:table-column'
+ . ' table:number-columns-repeated="' . $columns_cnt . '"/>';
+ /* Header */
+ $GLOBALS['odt_buffer'] .= '<table:table-row>'
+ . '<table:table-cell office:value-type="string">'
+ . '<text:p>' . __('Column') . '</text:p>'
+ . '</table:table-cell>'
+ . '<table:table-cell office:value-type="string">'
+ . '<text:p>' . __('Type') . '</text:p>'
+ . '</table:table-cell>'
+ . '<table:table-cell office:value-type="string">'
+ . '<text:p>' . __('Null') . '</text:p>'
+ . '</table:table-cell>'
+ . '<table:table-cell office:value-type="string">'
+ . '<text:p>' . __('Default') . '</text:p>'
+ . '</table:table-cell>';
+ if ($do_relation && $have_rel) {
+ $GLOBALS['odt_buffer'] .= '<table:table-cell office:value-type="string">'
+ . '<text:p>' . __('Links to') . '</text:p>'
+ . '</table:table-cell>';
+ }
+ if ($do_comments) {
+ $GLOBALS['odt_buffer'] .= '<table:table-cell office:value-type="string">'
+ . '<text:p>' . __('Comments') . '</text:p>'
+ . '</table:table-cell>';
+ $comments = PMA_getComments($db, $table);
+ }
+ if ($do_mime && $cfgRelation['mimework']) {
+ $GLOBALS['odt_buffer'] .= '<table:table-cell office:value-type="string">'
+ . '<text:p>' . __('MIME type') . '</text:p>'
+ . '</table:table-cell>';
+ $mime_map = PMA_getMIME($db, $table, true);
+ }
+ $GLOBALS['odt_buffer'] .= '</table:table-row>';
+
+ $columns = $GLOBALS['dbi']->getColumns($db, $table);
+ foreach ($columns as $column) {
+ $field_name = $column['Field'];
+ $GLOBALS['odt_buffer'] .= $this->formatOneColumnDefinition($column);
+
+ if ($do_relation && $have_rel) {
+ if (isset($res_rel[$field_name])) {
+ $GLOBALS['odt_buffer'] .=
+ '<table:table-cell office:value-type="string">'
+ . '<text:p>'
+ . htmlspecialchars(
+ $res_rel[$field_name]['foreign_table']
+ . ' (' . $res_rel[$field_name]['foreign_field'] . ')'
+ )
+ . '</text:p>'
+ . '</table:table-cell>';
+ }
+ }
+ if ($do_comments) {
+ if (isset($comments[$field_name])) {
+ $GLOBALS['odt_buffer'] .=
+ '<table:table-cell office:value-type="string">'
+ . '<text:p>'
+ . htmlspecialchars($comments[$field_name])
+ . '</text:p>'
+ . '</table:table-cell>';
+ } else {
+ $GLOBALS['odt_buffer'] .=
+ '<table:table-cell office:value-type="string">'
+ . '<text:p></text:p>'
+ . '</table:table-cell>';
+ }
+ }
+ if ($do_mime && $cfgRelation['mimework']) {
+ if (isset($mime_map[$field_name])) {
+ $GLOBALS['odt_buffer'] .=
+ '<table:table-cell office:value-type="string">'
+ . '<text:p>'
+ . htmlspecialchars(
+ str_replace('_', '/', $mime_map[$field_name]['mimetype'])
+ )
+ . '</text:p>'
+ . '</table:table-cell>';
+ } else {
+ $GLOBALS['odt_buffer'] .=
+ '<table:table-cell office:value-type="string">'
+ . '<text:p></text:p>'
+ . '</table:table-cell>';
+ }
+ }
+ $GLOBALS['odt_buffer'] .= '</table:table-row>';
+ } // end foreach
+
+ $GLOBALS['odt_buffer'] .= '</table:table>';
+ return true;
+ } // end of the '$this->getTableDef()' function
+
+ /**
+ * Outputs triggers
+ *
+ * @param string $db database name
+ * @param string $table table name
+ *
+ * @return bool true
+ */
+ protected function getTriggers($db, $table)
+ {
+ $GLOBALS['odt_buffer'] .= '<table:table'
+ . ' table:name="' . htmlspecialchars($table) . '_triggers">'
+ . '<table:table-column'
+ . ' table:number-columns-repeated="4"/>'
+ . '<table:table-row>'
+ . '<table:table-cell office:value-type="string">'
+ . '<text:p>' . __('Name') . '</text:p>'
+ . '</table:table-cell>'
+ . '<table:table-cell office:value-type="string">'
+ . '<text:p>' . __('Time') . '</text:p>'
+ . '</table:table-cell>'
+ . '<table:table-cell office:value-type="string">'
+ . '<text:p>' . __('Event') . '</text:p>'
+ . '</table:table-cell>'
+ . '<table:table-cell office:value-type="string">'
+ . '<text:p>' . __('Definition') . '</text:p>'
+ . '</table:table-cell>'
+ . '</table:table-row>';
+
+ $triggers = $GLOBALS['dbi']->getTriggers($db, $table);
+
+ foreach ($triggers as $trigger) {
+ $GLOBALS['odt_buffer'] .= '<table:table-row>';
+ $GLOBALS['odt_buffer'] .= '<table:table-cell office:value-type="string">'
+ . '<text:p>'
+ . htmlspecialchars($trigger['name'])
+ . '</text:p>'
+ . '</table:table-cell>';
+ $GLOBALS['odt_buffer'] .= '<table:table-cell office:value-type="string">'
+ . '<text:p>'
+ . htmlspecialchars($trigger['action_timing'])
+ . '</text:p>'
+ . '</table:table-cell>';
+ $GLOBALS['odt_buffer'] .= '<table:table-cell office:value-type="string">'
+ . '<text:p>'
+ . htmlspecialchars($trigger['event_manipulation'])
+ . '</text:p>'
+ . '</table:table-cell>';
+ $GLOBALS['odt_buffer'] .= '<table:table-cell office:value-type="string">'
+ . '<text:p>'
+ . htmlspecialchars($trigger['definition'])
+ . '</text:p>'
+ . '</table:table-cell>';
+ $GLOBALS['odt_buffer'] .= '</table:table-row>';
+ }
+
+ $GLOBALS['odt_buffer'] .= '</table:table>';
+ return true;
+ }
+
+ /**
+ * Outputs table's structure
+ *
+ * @param string $db database name
+ * @param string $table table name
+ * @param string $crlf the end of line sequence
+ * @param string $error_url the url to go back in case of error
+ * @param string $export_mode 'create_table', 'triggers', 'create_view',
+ * 'stand_in'
+ * @param string $export_type 'server', 'database', 'table'
+ * @param bool $do_relation whether to include relation comments
+ * @param bool $do_comments whether to include the pmadb-style column
+ * comments as comments in the structure;
+ * this is deprecated but the parameter is
+ * left here because export.php calls
+ * PMA_exportStructure() also for other
+ * @param bool $do_mime whether to include mime comments
+ * @param bool $dates whether to include creation/update/check dates
+ *
+ * @return bool Whether it succeeded
+ */
+ public function exportStructure(
+ $db,
+ $table,
+ $crlf,
+ $error_url,
+ $export_mode,
+ $export_type,
+ $do_relation = false,
+ $do_comments = false,
+ $do_mime = false,
+ $dates = false
+ ) {
+ switch($export_mode) {
+ case 'create_table':
+ $GLOBALS['odt_buffer'] .=
+ '<text:h text:outline-level="2" text:style-name="Heading_2"'
+ . ' text:is-list-header="true">'
+ . __('Table structure for table') . ' ' .
+ htmlspecialchars($table)
+ . '</text:h>';
+ $this->getTableDef(
+ $db, $table, $crlf, $error_url, $do_relation, $do_comments,
+ $do_mime, $dates
+ );
+ break;
+ case 'triggers':
+ $triggers = $GLOBALS['dbi']->getTriggers($db, $table);
+ if ($triggers) {
+ $GLOBALS['odt_buffer'] .=
+ '<text:h text:outline-level="2" text:style-name="Heading_2"'
+ . ' text:is-list-header="true">'
+ . __('Triggers') . ' '
+ . htmlspecialchars($table)
+ . '</text:h>';
+ $this->getTriggers($db, $table);
+ }
+ break;
+ case 'create_view':
+ $GLOBALS['odt_buffer'] .=
+ '<text:h text:outline-level="2" text:style-name="Heading_2"'
+ . ' text:is-list-header="true">'
+ . __('Structure for view') . ' '
+ . htmlspecialchars($table)
+ . '</text:h>';
+ $this->getTableDef(
+ $db, $table, $crlf, $error_url, $do_relation, $do_comments,
+ $do_mime, $dates, true, true
+ );
+ break;
+ case 'stand_in':
+ $GLOBALS['odt_buffer'] .=
+ '<text:h text:outline-level="2" text:style-name="Heading_2"'
+ . ' text:is-list-header="true">'
+ . __('Stand-in structure for view') . ' '
+ . htmlspecialchars($table)
+ . '</text:h>';
+ // export a stand-in definition to resolve view dependencies
+ $this->getTableDefStandIn($db, $table, $crlf);
+ } // end switch
+
+ return true;
+ } // end of the '$this->exportStructure' function
+
+ /**
+ * Formats the definition for one column
+ *
+ * @param array $column info about this column
+ *
+ * @return string Formatted column definition
+ */
+ protected function formatOneColumnDefinition($column)
+ {
+ $field_name = $column['Field'];
+ $definition = '<table:table-row>';
+ $definition .= '<table:table-cell office:value-type="string">'
+ . '<text:p>' . htmlspecialchars($field_name) . '</text:p>'
+ . '</table:table-cell>';
+
+ $extracted_columnspec
+ = PMA_Util::extractColumnSpec($column['Type']);
+ $type = htmlspecialchars($extracted_columnspec['print_type']);
+ if (empty($type)) {
+ $type = '&nbsp;';
+ }
+
+ $definition .= '<table:table-cell office:value-type="string">'
+ . '<text:p>' . htmlspecialchars($type) . '</text:p>'
+ . '</table:table-cell>';
+ if (! isset($column['Default'])) {
+ if ($column['Null'] != 'NO') {
+ $column['Default'] = 'NULL';
+ } else {
+ $column['Default'] = '';
+ }
+ }
+ $definition .= '<table:table-cell office:value-type="string">'
+ . '<text:p>'
+ . (($column['Null'] == '' || $column['Null'] == 'NO')
+ ? __('No')
+ : __('Yes'))
+ . '</text:p>'
+ . '</table:table-cell>';
+ $definition .= '<table:table-cell office:value-type="string">'
+ . '<text:p>' . htmlspecialchars($column['Default']) . '</text:p>'
+ . '</table:table-cell>';
+ return $definition;
+ }
+}
+?>
diff --git a/libraries/plugins/export/ExportPdf.class.php b/libraries/plugins/export/ExportPdf.class.php
new file mode 100644
index 0000000000..876f0700bb
--- /dev/null
+++ b/libraries/plugins/export/ExportPdf.class.php
@@ -0,0 +1,276 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Produce a PDF report (export) from a query
+ *
+ * @package PhpMyAdmin-Export
+ * @subpackage PDF
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ * Skip the plugin if TCPDF is not available.
+ */
+if (! file_exists(TCPDF_INC)) {
+ $GLOBALS['skip_import'] = true;
+ return;
+}
+
+/* Get the export interface */
+require_once 'libraries/plugins/ExportPlugin.class.php';
+/* Get the PMA_ExportPdf class */
+require_once 'libraries/plugins/export/PMA_ExportPdf.class.php';
+
+/**
+ * Handles the export for the PDF class
+ *
+ * @package PhpMyAdmin-Export
+ * @subpackage PDF
+ */
+class ExportPdf extends ExportPlugin
+{
+ /**
+ * PMA_ExportPdf instance
+ *
+ * @var PMA_ExportPdf
+ */
+ private $_pdf;
+
+ /**
+ * PDF Report Title
+ *
+ * @var string
+ */
+ private $_pdfReportTitle;
+
+ /**
+ * Constructor
+ */
+ public function __construct()
+ {
+ // initialize the specific export PDF variables
+ $this->initSpecificVariables();
+
+ $this->setProperties();
+ }
+
+ /**
+ * Initialize the local variables that are used for export PDF
+ *
+ * @return void
+ */
+ protected function initSpecificVariables()
+ {
+ $this->_setPdfReportTitle("");
+ $this->_setPdf(new PMA_ExportPdf('L', 'pt', 'A3'));
+ }
+
+ /**
+ * Sets the export PDF properties
+ *
+ * @return void
+ */
+ protected function setProperties()
+ {
+ $props = 'libraries/properties/';
+ include_once "$props/plugins/ExportPluginProperties.class.php";
+ include_once "$props/options/groups/OptionsPropertyRootGroup.class.php";
+ include_once "$props/options/groups/OptionsPropertyMainGroup.class.php";
+ include_once "$props/options/items/MessageOnlyPropertyItem.class.php";
+ include_once "$props/options/items/TextPropertyItem.class.php";
+ include_once "$props/options/items/HiddenPropertyItem.class.php";
+
+ $exportPluginProperties = new ExportPluginProperties();
+ $exportPluginProperties->setText('PDF');
+ $exportPluginProperties->setExtension('pdf');
+ $exportPluginProperties->setMimeType('application/pdf');
+ $exportPluginProperties->setForceFile(true);
+ $exportPluginProperties->setOptionsText(__('Options'));
+
+ // create the root group that will be the options field for
+ // $exportPluginProperties
+ // this will be shown as "Format specific options"
+ $exportSpecificOptions = new OptionsPropertyRootGroup();
+ $exportSpecificOptions->setName("Format Specific Options");
+
+ // general options main group
+ $generalOptions = new OptionsPropertyMainGroup();
+ $generalOptions->setName("general_opts");
+ // create primary items and add them to the group
+ $leaf = new MessageOnlyPropertyItem();
+ $leaf->setName("explanation");
+ $leaf->setText(
+ __('(Generates a report containing the data of a single table)')
+ );
+ $generalOptions->addProperty($leaf);
+ $leaf = new TextPropertyItem();
+ $leaf->setName("report_title");
+ $leaf->setText(__('Report title:'));
+ $generalOptions->addProperty($leaf);
+ $leaf = new HiddenPropertyItem();
+ $leaf->setName("structure_or_data");
+ $generalOptions->addProperty($leaf);
+ // add the main group to the root group
+ $exportSpecificOptions->addProperty($generalOptions);
+
+ // set the options for the export plugin property item
+ $exportPluginProperties->setOptions($exportSpecificOptions);
+ $this->properties = $exportPluginProperties;
+ }
+
+ /**
+ * This method is called when any PluginManager to which the observer
+ * is attached calls PluginManager::notify()
+ *
+ * @param SplSubject $subject The PluginManager notifying the observer
+ * of an update.
+ *
+ * @return void
+ */
+ public function update (SplSubject $subject)
+ {
+ }
+
+ /**
+ * Outputs export header
+ *
+ * @return bool Whether it succeeded
+ */
+ public function exportHeader ()
+ {
+ $pdf_report_title = $this->_getPdfReportTitle();
+ $pdf = $this->_getPdf();
+ $pdf->Open();
+
+ $attr = array('titleFontSize' => 18, 'titleText' => $pdf_report_title);
+ $pdf->setAttributes($attr);
+ $pdf->setTopMargin(30);
+
+ return true;
+ }
+
+ /**
+ * Outputs export footer
+ *
+ * @return bool Whether it succeeded
+ */
+ public function exportFooter ()
+ {
+ $pdf = $this->_getPdf();
+
+ // instead of $pdf->Output():
+ if (! PMA_exportOutputHandler($pdf->getPDFData())) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Outputs database header
+ *
+ * @param string $db Database name
+ *
+ * @return bool Whether it succeeded
+ */
+ public function exportDBHeader ($db)
+ {
+ return true;
+ }
+
+ /**
+ * Outputs database footer
+ *
+ * @param string $db Database name
+ *
+ * @return bool Whether it succeeded
+ */
+ public function exportDBFooter ($db)
+ {
+ return true;
+ }
+
+ /**
+ * Outputs CREATE DATABASE statement
+ *
+ * @param string $db Database name
+ *
+ * @return bool Whether it succeeded
+ */
+ public function exportDBCreate($db)
+ {
+ return true;
+ }
+ /**
+ * Outputs the content of a table in NHibernate format
+ *
+ * @param string $db database name
+ * @param string $table table name
+ * @param string $crlf the end of line sequence
+ * @param string $error_url the url to go back in case of error
+ * @param string $sql_query SQL query for obtaining data
+ *
+ * @return bool Whether it succeeded
+ */
+ public function exportData($db, $table, $crlf, $error_url, $sql_query)
+ {
+ $pdf = $this->_getPdf();
+
+ $attr = array('currentDb' => $db, 'currentTable' => $table);
+ $pdf->setAttributes($attr);
+ $pdf->mysqlReport($sql_query);
+
+ return true;
+ } // end of the 'PMA_exportData()' function
+
+
+ /* ~~~~~~~~~~~~~~~~~~~~ Getters and Setters ~~~~~~~~~~~~~~~~~~~~ */
+
+
+ /**
+ * Gets the PMA_ExportPdf instance
+ *
+ * @return PMA_ExportPdf
+ */
+ private function _getPdf()
+ {
+ return $this->_pdf;
+ }
+
+ /**
+ * Instantiates the PMA_ExportPdf class
+ *
+ * @param string $pdf PMA_ExportPdf instance
+ *
+ * @return void
+ */
+ private function _setPdf($pdf)
+ {
+ $this->_pdf = $pdf;
+ }
+
+ /**
+ * Gets the PDF report title
+ *
+ * @return string
+ */
+ private function _getPdfReportTitle()
+ {
+ return $this->_pdfReportTitle;
+ }
+
+ /**
+ * Sets the PDF report title
+ *
+ * @param string $pdfReportTitle PDF report title
+ *
+ * @return void
+ */
+ private function _setPdfReportTitle($pdfReportTitle)
+ {
+ $this->_pdfReportTitle = $pdfReportTitle;
+ }
+}
+?>
diff --git a/libraries/plugins/export/ExportPhparray.class.php b/libraries/plugins/export/ExportPhparray.class.php
new file mode 100644
index 0000000000..05d6fd24ef
--- /dev/null
+++ b/libraries/plugins/export/ExportPhparray.class.php
@@ -0,0 +1,229 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Set of functions used to build dumps of tables as PHP Arrays
+ *
+ * @package PhpMyAdmin-Export
+ * @subpackage PHP
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/* Get the export interface */
+require_once 'libraries/plugins/ExportPlugin.class.php';
+
+/**
+ * Handles the export for the PHP Array class
+ *
+ * @package PhpMyAdmin-Export
+ * @subpackage PHP
+ */
+class ExportPhparray extends ExportPlugin
+{
+ /**
+ * Constructor
+ */
+ public function __construct()
+ {
+ $this->setProperties();
+ }
+
+ /**
+ * Sets the export PHP Array properties
+ *
+ * @return void
+ */
+ protected function setProperties()
+ {
+ $props = 'libraries/properties/';
+ include_once "$props/plugins/ExportPluginProperties.class.php";
+ include_once "$props/options/groups/OptionsPropertyRootGroup.class.php";
+ include_once "$props/options/groups/OptionsPropertyMainGroup.class.php";
+ include_once "$props/options/items/HiddenPropertyItem.class.php";
+
+ $exportPluginProperties = new ExportPluginProperties();
+ $exportPluginProperties->setText('PHP array');
+ $exportPluginProperties->setExtension('php');
+ $exportPluginProperties->setMimeType('text/plain');
+ $exportPluginProperties->setOptionsText(__('Options'));
+
+ // create the root group that will be the options field for
+ // $exportPluginProperties
+ // this will be shown as "Format specific options"
+ $exportSpecificOptions = new OptionsPropertyRootGroup();
+ $exportSpecificOptions->setName("Format Specific Options");
+
+ // general options main group
+ $generalOptions = new OptionsPropertyMainGroup();
+ $generalOptions->setName("general_opts");
+ // create primary items and add them to the group
+ $leaf = new HiddenPropertyItem();
+ $leaf->setName("structure_or_data");
+ $generalOptions->addProperty($leaf);
+ // add the main group to the root group
+ $exportSpecificOptions->addProperty($generalOptions);
+
+ // set the options for the export plugin property item
+ $exportPluginProperties->setOptions($exportSpecificOptions);
+ $this->properties = $exportPluginProperties;
+ }
+
+ /**
+ * This method is called when any PluginManager to which the observer
+ * is attached calls PluginManager::notify()
+ *
+ * @param SplSubject $subject The PluginManager notifying the observer
+ * of an update.
+ *
+ * @return void
+ */
+ public function update (SplSubject $subject)
+ {
+ }
+
+ /**
+ * Outputs export header
+ *
+ * @return bool Whether it succeeded
+ */
+ public function exportHeader ()
+ {
+ PMA_exportOutputHandler(
+ '<?php' . $GLOBALS['crlf']
+ . '/**' . $GLOBALS['crlf']
+ . ' * Export to PHP Array plugin for PHPMyAdmin' . $GLOBALS['crlf']
+ . ' * @version 0.2b' . $GLOBALS['crlf']
+ . ' */' . $GLOBALS['crlf'] . $GLOBALS['crlf']
+ );
+ return true;
+ }
+
+ /**
+ * Outputs export footer
+ *
+ * @return bool Whether it succeeded
+ */
+ public function exportFooter ()
+ {
+ return true;
+ }
+
+ /**
+ * Outputs database header
+ *
+ * @param string $db Database name
+ *
+ * @return bool Whether it succeeded
+ */
+ public function exportDBHeader ($db)
+ {
+ PMA_exportOutputHandler(
+ '//' . $GLOBALS['crlf']
+ . '// Database ' . PMA_Util::backquote($db)
+ . $GLOBALS['crlf'] . '//' . $GLOBALS['crlf']
+ );
+ return true;
+ }
+
+ /**
+ * Outputs database footer
+ *
+ * @param string $db Database name
+ *
+ * @return bool Whether it succeeded
+ */
+ public function exportDBFooter ($db)
+ {
+ return true;
+ }
+
+ /**
+ * Outputs CREATE DATABASE statement
+ *
+ * @param string $db Database name
+ *
+ * @return bool Whether it succeeded
+ */
+ public function exportDBCreate($db)
+ {
+ return true;
+ }
+
+ /**
+ * Outputs the content of a table in PHP array format
+ *
+ * @param string $db database name
+ * @param string $table table name
+ * @param string $crlf the end of line sequence
+ * @param string $error_url the url to go back in case of error
+ * @param string $sql_query SQL query for obtaining data
+ *
+ * @return bool Whether it succeeded
+ */
+ public function exportData($db, $table, $crlf, $error_url, $sql_query)
+ {
+ $result = $GLOBALS['dbi']->query(
+ $sql_query, null, PMA_DatabaseInterface::QUERY_UNBUFFERED
+ );
+
+ $columns_cnt = $GLOBALS['dbi']->numFields($result);
+ for ($i = 0; $i < $columns_cnt; $i++) {
+ $columns[$i] = stripslashes($GLOBALS['dbi']->fieldName($result, $i));
+ }
+ unset($i);
+
+ // fix variable names (based on
+ // http://www.php.net/manual/language.variables.basics.php)
+ if (! preg_match(
+ '/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$/',
+ $table
+ )) {
+ // fix invalid characters in variable names by replacing them with
+ // underscores
+ $tablefixed = preg_replace('/[^a-zA-Z0-9_\x7f-\xff]/', '_', $table);
+
+ // variable name must not start with a number or dash...
+ if (preg_match('/^[a-zA-Z_\x7f-\xff]/', $tablefixed) == false) {
+ $tablefixed = '_' . $tablefixed;
+ }
+ } else {
+ $tablefixed = $table;
+ }
+
+ $buffer = '';
+ $record_cnt = 0;
+ // Output table name as comment
+ $buffer .= $crlf . '// '
+ . PMA_Util::backquote($db) . '.'
+ . PMA_Util::backquote($table) . $crlf;
+ $buffer .= '$' . $tablefixed . ' = array(';
+
+ while ($record = $GLOBALS['dbi']->fetchRow($result)) {
+ $record_cnt++;
+
+ if ($record_cnt == 1) {
+ $buffer .= $crlf . ' array(';
+ } else {
+ $buffer .= ',' . $crlf . ' array(';
+ }
+
+ for ($i = 0; $i < $columns_cnt; $i++) {
+ $buffer .= var_export($columns[$i], true)
+ . " => " . var_export($record[$i], true)
+ . (($i + 1 >= $columns_cnt) ? '' : ',');
+ }
+
+ $buffer .= ')';
+ }
+
+ $buffer .= $crlf . ');' . $crlf;
+ if (! PMA_exportOutputHandler($buffer)) {
+ return false;
+ }
+
+ $GLOBALS['dbi']->freeResult($result);
+ return true;
+ }
+}
+?>
diff --git a/libraries/plugins/export/ExportSql.class.php b/libraries/plugins/export/ExportSql.class.php
new file mode 100644
index 0000000000..11519435a9
--- /dev/null
+++ b/libraries/plugins/export/ExportSql.class.php
@@ -0,0 +1,2017 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Set of functions used to build SQL dumps of tables
+ *
+ * @package PhpMyAdmin-Export
+ * @subpackage SQL
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/* Get the export interface */
+require_once 'libraries/plugins/ExportPlugin.class.php';
+
+/**
+ * Handles the export for the SQL class
+ *
+ * @package PhpMyAdmin-Export
+ * @subpackage SQL
+ */
+class ExportSql extends ExportPlugin
+{
+ /**
+ * Constructor
+ */
+ public function __construct()
+ {
+ $this->setProperties();
+
+ // Avoids undefined variables, use NULL so isset() returns false
+ if (! isset($GLOBALS['sql_backquotes'])) {
+ $GLOBALS['sql_backquotes'] = null;
+ }
+ }
+
+ /**
+ * Sets the export SQL properties
+ *
+ * @return void
+ */
+ protected function setProperties()
+ {
+ global $plugin_param;
+
+ $hide_sql = false;
+ $hide_structure = false;
+ if ($plugin_param['export_type'] == 'table'
+ && ! $plugin_param['single_table']
+ ) {
+ $hide_structure = true;
+ $hide_sql = true;
+ }
+
+ if (! $hide_sql) {
+ $props = 'libraries/properties/';
+ include_once "$props/plugins/ExportPluginProperties.class.php";
+ include_once "$props/options/groups/OptionsPropertyRootGroup.class.php";
+ include_once "$props/options/groups/OptionsPropertyMainGroup.class.php";
+ include_once "$props/options/groups/OptionsPropertySubgroup.class.php";
+ include_once "$props/options/items/BoolPropertyItem.class.php";
+ include_once "$props/options/items/MessageOnlyPropertyItem.class.php";
+ include_once "$props/options/items/RadioPropertyItem.class.php";
+ include_once "$props/options/items/SelectPropertyItem.class.php";
+ include_once "$props/options/items/TextPropertyItem.class.php";
+ include_once "$props/options/items/NumberPropertyItem.class.php";
+
+ $exportPluginProperties = new ExportPluginProperties();
+ $exportPluginProperties->setText('SQL');
+ $exportPluginProperties->setExtension('sql');
+ $exportPluginProperties->setMimeType('text/x-sql');
+ $exportPluginProperties->setOptionsText(__('Options'));
+
+ // create the root group that will be the options field for
+ // $exportPluginProperties
+ // this will be shown as "Format specific options"
+ $exportSpecificOptions = new OptionsPropertyRootGroup();
+ $exportSpecificOptions->setName("Format Specific Options");
+
+ // general options main group
+ $generalOptions = new OptionsPropertyMainGroup();
+ $generalOptions->setName("general_opts");
+
+ // comments
+ $subgroup = new OptionsPropertySubgroup();
+ $subgroup->setName("include_comments");
+ $leaf = new BoolPropertyItem();
+ $leaf->setName('include_comments');
+ $leaf->setText(
+ __(
+ 'Display comments <i>(includes info such as export'
+ . ' timestamp, PHP version, and server version)</i>'
+ )
+ );
+ $subgroup->setSubgroupHeader($leaf);
+
+ $leaf = new TextPropertyItem();
+ $leaf->setName('header_comment');
+ $leaf->setText(
+ __('Additional custom header comment (\n splits lines):')
+ );
+ $subgroup->addProperty($leaf);
+ $leaf = new BoolPropertyItem();
+ $leaf->setName('dates');
+ $leaf->setText(
+ __(
+ 'Include a timestamp of when databases were created, last'
+ . ' updated, and last checked'
+ )
+ );
+ $subgroup->addProperty($leaf);
+ if (! empty($GLOBALS['cfgRelation']['relation'])) {
+ $leaf = new BoolPropertyItem();
+ $leaf->setName('relation');
+ $leaf->setText(__('Display foreign key relationships'));
+ $subgroup->addProperty($leaf);
+ }
+ if (! empty($GLOBALS['cfgRelation']['mimework'])) {
+ $leaf = new BoolPropertyItem();
+ $leaf->setName('mime');
+ $leaf->setText(__('Display MIME types'));
+ $subgroup->addProperty($leaf);
+ }
+ $generalOptions->addProperty($subgroup);
+
+ // enclose in a transaction
+ $leaf = new BoolPropertyItem();
+ $leaf->setName("use_transaction");
+ $leaf->setText(__('Enclose export in a transaction'));
+ $leaf->setDoc(
+ array(
+ 'programs',
+ 'mysqldump',
+ 'option_mysqldump_single-transaction'
+ )
+ );
+ $generalOptions->addProperty($leaf);
+
+ // disable foreign key checks
+ $leaf = new BoolPropertyItem();
+ $leaf->setName("disable_fk");
+ $leaf->setText(__('Disable foreign key checks'));
+ $leaf->setDoc(
+ array(
+ 'manual_MySQL_Database_Administration',
+ 'server-system-variables',
+ 'sysvar_foreign_key_checks'
+ )
+ );
+ $generalOptions->addProperty($leaf);
+
+ // export views as tables
+ $leaf = new BoolPropertyItem();
+ $leaf->setName("views_as_tables");
+ $leaf->setText(__('Export views as tables'));
+ $generalOptions->addProperty($leaf);
+
+ // compatibility maximization
+ $compats = $GLOBALS['dbi']->getCompatibilities();
+ if (count($compats) > 0) {
+ $values = array();
+ foreach ($compats as $val) {
+ $values[$val] = $val;
+ }
+
+ $leaf = new SelectPropertyItem();
+ $leaf->setName("compatibility");
+ $leaf->setText(
+ __(
+ 'Database system or older MySQL server to maximize output'
+ . ' compatibility with:'
+ )
+ );
+ $leaf->setValues($values);
+ $leaf->setDoc(
+ array(
+ 'manual_MySQL_Database_Administration',
+ 'Server_SQL_mode'
+ )
+ );
+ $generalOptions->addProperty($leaf);
+
+ unset($values);
+ }
+
+ // server export options
+ if ($plugin_param['export_type'] == 'server') {
+ $leaf = new BoolPropertyItem();
+ $leaf->setName("drop_database");
+ $leaf->setText(
+ sprintf(__('Add %s statement'), '<code>DROP DATABASE</code>')
+ );
+ $generalOptions->addProperty($leaf);
+ }
+
+ // what to dump (structure/data/both)
+ $subgroup = new OptionsPropertySubgroup();
+ $subgroup->setName("dump_table");
+ $subgroup->setText("Dump table");
+ $leaf = new RadioPropertyItem();
+ $leaf->setName('structure_or_data');
+ $leaf->setValues(
+ array(
+ 'structure' => __('structure'),
+ 'data' => __('data'),
+ 'structure_and_data' => __('structure and data')
+ )
+ );
+ $subgroup->setSubgroupHeader($leaf);
+ $generalOptions->addProperty($subgroup);
+
+ // add the main group to the root group
+ $exportSpecificOptions->addProperty($generalOptions);
+
+
+ // structure options main group
+ if (! $hide_structure) {
+ $structureOptions = new OptionsPropertyMainGroup();
+ $structureOptions->setName("structure");
+ $structureOptions->setText(__('Object creation options'));
+ $structureOptions->setForce('data');
+
+ // begin SQL Statements
+ $subgroup = new OptionsPropertySubgroup();
+ $leaf = new MessageOnlyPropertyItem();
+ $leaf->setName('add_statements');
+ $leaf->setText(__('Add statements:'));
+ $subgroup->setSubgroupHeader($leaf);
+
+ if ($plugin_param['export_type'] != 'table') {
+ $leaf = new BoolPropertyItem();
+ $leaf->setName('create_database');
+ $create_clause = '<code>CREATE DATABASE / USE</code>';
+ $leaf->setText(sprintf(__('Add %s statement'), $create_clause));
+ $subgroup->addProperty($leaf);
+ }
+
+ if ($plugin_param['export_type'] == 'table') {
+ if (PMA_Table::isView($GLOBALS['db'], $GLOBALS['table'])) {
+ $drop_clause = '<code>DROP VIEW</code>';
+ } else {
+ $drop_clause = '<code>DROP TABLE</code>';
+ }
+ } else {
+ if (PMA_DRIZZLE) {
+ $drop_clause = '<code>DROP TABLE</code>';
+ } else {
+ $drop_clause = '<code>DROP TABLE / VIEW / PROCEDURE'
+ . ' / FUNCTION</code>';
+ if (PMA_MYSQL_INT_VERSION > 50100) {
+ $drop_clause .= '<code> / EVENT</code>';
+ }
+ }
+ }
+ $leaf = new BoolPropertyItem();
+ $leaf->setName('drop_table');
+ $leaf->setText(sprintf(__('Add %s statement'), $drop_clause));
+ $subgroup->addProperty($leaf);
+ // Drizzle doesn't support procedures and functions
+ if (! PMA_DRIZZLE) {
+ $leaf = new BoolPropertyItem();
+ $leaf->setName('procedure_function');
+ $leaf->setText(
+ sprintf(
+ __('Add %s statement'),
+ '<code>CREATE PROCEDURE / FUNCTION'
+ . (PMA_MYSQL_INT_VERSION > 50100
+ ? ' / EVENT</code>' : '</code>')
+ )
+ );
+ $subgroup->addProperty($leaf);
+ }
+
+ // begin CREATE TABLE statements
+ $subgroup_create_table = new OptionsPropertySubgroup();
+ $leaf = new BoolPropertyItem();
+ $leaf->setName('create_table_statements');
+ $leaf->setText(__('<code>CREATE TABLE</code> options:'));
+ $subgroup_create_table->setSubgroupHeader($leaf);
+ $leaf = new BoolPropertyItem();
+ $leaf->setName('if_not_exists');
+ $leaf->setText('<code>IF NOT EXISTS</code>');
+ $subgroup_create_table->addProperty($leaf);
+ $leaf = new BoolPropertyItem();
+ $leaf->setName('auto_increment');
+ $leaf->setText('<code>AUTO_INCREMENT</code>');
+ $subgroup_create_table->addProperty($leaf);
+ $subgroup->addProperty($subgroup_create_table);
+ $structureOptions->addProperty($subgroup);
+
+ $leaf = new BoolPropertyItem();
+ $leaf->setName("backquotes");
+ $leaf->setText(
+ __(
+ 'Enclose table and column names with backquotes '
+ . '<i>(Protects column and table names formed with'
+ . ' special characters or keywords)</i>'
+ )
+ );
+
+ $structureOptions->addProperty($leaf);
+
+ // add the main group to the root group
+ $exportSpecificOptions->addProperty($structureOptions);
+ }
+
+
+ // begin Data options
+ $dataOptions = new OptionsPropertyMainGroup();
+ $dataOptions->setName("data");
+ $dataOptions->setText(__('Data creation options'));
+ $dataOptions->setForce('structure');
+ $leaf = new BoolPropertyItem();
+ $leaf->setName("truncate");
+ $leaf->setText(__('Truncate table before insert'));
+ $dataOptions->addProperty($leaf);
+
+ // begin SQL Statements
+ $subgroup = new OptionsPropertySubgroup();
+ $leaf = new MessageOnlyPropertyItem();
+ $leaf->setText(__('Instead of <code>INSERT</code> statements, use:'));
+ $subgroup->setSubgroupHeader($leaf);
+ // Not supported in Drizzle
+ if (! PMA_DRIZZLE) {
+ $leaf = new BoolPropertyItem();
+ $leaf->setName("delayed");
+ $leaf->setText(__('<code>INSERT DELAYED</code> statements'));
+ $leaf->setDoc(
+ array(
+ 'manual_MySQL_Database_Administration',
+ 'insert_delayed'
+ )
+ );
+ $subgroup->addProperty($leaf);
+ }
+ $leaf = new BoolPropertyItem();
+ $leaf->setName("ignore");
+ $leaf->setText(__('<code>INSERT IGNORE</code> statements'));
+ $leaf->setDoc(
+ array(
+ 'manual_MySQL_Database_Administration',
+ 'insert'
+ )
+ );
+ $subgroup->addProperty($leaf);
+ $dataOptions->addProperty($subgroup);
+
+ // Function to use when dumping dat
+ $leaf = new SelectPropertyItem();
+ $leaf->setName("type");
+ $leaf->setText(__('Function to use when dumping data:'));
+ $leaf->setValues(
+ array(
+ 'INSERT' => 'INSERT',
+ 'UPDATE' => 'UPDATE',
+ 'REPLACE' => 'REPLACE'
+ )
+ );
+ $dataOptions->addProperty($leaf);
+
+ /* Syntax to use when inserting data */
+ $subgroup = new OptionsPropertySubgroup();
+ $leaf = new MessageOnlyPropertyItem();
+ $leaf->setText(__('Syntax to use when inserting data:'));
+ $subgroup->setSubgroupHeader($leaf);
+ $leaf = new RadioPropertyItem();
+ $leaf->setName("insert_syntax");
+ $leaf->setText(__('<code>INSERT IGNORE</code> statements'));
+ $leaf->setValues(
+ array(
+ 'complete' => __(
+ 'include column names in every <code>INSERT</code> statement'
+ . ' <br /> &nbsp; &nbsp; &nbsp; Example: <code>INSERT INTO'
+ . ' tbl_name (col_A,col_B,col_C) VALUES (1,2,3)</code>'
+ ),
+ 'extended' => __(
+ 'insert multiple rows in every <code>INSERT</code> statement'
+ . '<br /> &nbsp; &nbsp; &nbsp; Example: <code>INSERT INTO'
+ . ' tbl_name VALUES (1,2,3), (4,5,6), (7,8,9)</code>'
+ ),
+ 'both' => __(
+ 'both of the above<br /> &nbsp; &nbsp; &nbsp; Example:'
+ . ' <code>INSERT INTO tbl_name (col_A,col_B) VALUES (1,2,3),'
+ . ' (4,5,6), (7,8,9)</code>'
+ ),
+ 'none' => __(
+ 'neither of the above<br /> &nbsp; &nbsp; &nbsp; Example:'
+ . ' <code>INSERT INTO tbl_name VALUES (1,2,3)</code>'
+ )
+ )
+ );
+ $subgroup->addProperty($leaf);
+ $dataOptions->addProperty($subgroup);
+
+ // Max length of query
+ $leaf = new NumberPropertyItem();
+ $leaf->setName("max_query_size");
+ $leaf->setText(__('Maximal length of created query'));
+ $dataOptions->addProperty($leaf);
+
+ // Dump binary columns in hexadecimal
+ $leaf = new BoolPropertyItem();
+ $leaf->setName("hex_for_blob");
+ $leaf->setText(
+ __(
+ 'Dump binary columns in hexadecimal notation'
+ . ' <i>(for example, "abc" becomes 0x616263)</i>'
+ )
+ );
+ $dataOptions->addProperty($leaf);
+
+ // Drizzle works only with UTC timezone
+ if (! PMA_DRIZZLE) {
+ // Dump time in UTC
+ $leaf = new BoolPropertyItem();
+ $leaf->setName("utc_time");
+ $leaf->setText(
+ __(
+ 'Dump TIMESTAMP columns in UTC <i>(enables TIMESTAMP columns'
+ . ' to be dumped and reloaded between servers in different'
+ . ' time zones)</i>'
+ )
+ );
+ $dataOptions->addProperty($leaf);
+ }
+
+ // add the main group to the root group
+ $exportSpecificOptions->addProperty($dataOptions);
+
+ // set the options for the export plugin property item
+ $exportPluginProperties->setOptions($exportSpecificOptions);
+ $this->properties = $exportPluginProperties;
+ }
+ }
+
+ /**
+ * This method is called when any PluginManager to which the observer
+ * is attached calls PluginManager::notify()
+ *
+ * @param SplSubject $subject The PluginManager notifying the observer
+ * of an update.
+ *
+ * @return void
+ */
+ public function update (SplSubject $subject)
+ {
+ }
+
+ /**
+ * Exports routines (procedures and functions)
+ *
+ * @param string $db Database
+ *
+ * @return bool Whether it succeeded
+ */
+ public function exportRoutines($db)
+ {
+ global $crlf;
+
+ $text = '';
+ $delimiter = '$$';
+
+ $procedure_names = $GLOBALS['dbi']
+ ->getProceduresOrFunctions($db, 'PROCEDURE');
+ $function_names = $GLOBALS['dbi']->getProceduresOrFunctions($db, 'FUNCTION');
+
+ if ($procedure_names || $function_names) {
+ $text .= $crlf
+ . 'DELIMITER ' . $delimiter . $crlf;
+ }
+
+ if ($procedure_names) {
+ $text .=
+ $this->_exportComment()
+ . $this->_exportComment(__('Procedures'))
+ . $this->_exportComment();
+
+ foreach ($procedure_names as $procedure_name) {
+ if (! empty($GLOBALS['sql_drop_table'])) {
+ $text .= 'DROP PROCEDURE IF EXISTS '
+ . PMA_Util::backquote($procedure_name)
+ . $delimiter . $crlf;
+ }
+ $text .= $GLOBALS['dbi']
+ ->getDefinition($db, 'PROCEDURE', $procedure_name)
+ . $delimiter . $crlf . $crlf;
+ }
+ }
+
+ if ($function_names) {
+ $text .=
+ $this->_exportComment()
+ . $this->_exportComment(__('Functions'))
+ . $this->_exportComment();
+
+ foreach ($function_names as $function_name) {
+ if (! empty($GLOBALS['sql_drop_table'])) {
+ $text .= 'DROP FUNCTION IF EXISTS '
+ . PMA_Util::backquote($function_name)
+ . $delimiter . $crlf;
+ }
+ $text .= $GLOBALS['dbi']
+ ->getDefinition($db, 'FUNCTION', $function_name)
+ . $delimiter . $crlf . $crlf;
+ }
+ }
+
+ if ($procedure_names || $function_names) {
+ $text .= 'DELIMITER ;' . $crlf;
+ }
+
+ if (! empty($text)) {
+ return PMA_exportOutputHandler($text);
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Possibly outputs comment
+ *
+ * @param string $text Text of comment
+ *
+ * @return string The formatted comment
+ */
+ private function _exportComment($text = '')
+ {
+ if (isset($GLOBALS['sql_include_comments'])
+ && $GLOBALS['sql_include_comments']
+ ) {
+ // see http://dev.mysql.com/doc/refman/5.0/en/ansi-diff-comments.html
+ return '--' . (empty($text) ? '' : ' ') . $text . $GLOBALS['crlf'];
+ } else {
+ return '';
+ }
+ }
+
+ /**
+ * Possibly outputs CRLF
+ *
+ * @return string $crlf or nothing
+ */
+ private function _possibleCRLF()
+ {
+ if (isset($GLOBALS['sql_include_comments'])
+ && $GLOBALS['sql_include_comments']
+ ) {
+ return $GLOBALS['crlf'];
+ } else {
+ return '';
+ }
+ }
+
+ /**
+ * Outputs export footer
+ *
+ * @return bool Whether it succeeded
+ */
+ public function exportFooter()
+ {
+ global $crlf, $mysql_charset_map;
+
+ $foot = '';
+
+ if (isset($GLOBALS['sql_disable_fk'])) {
+ $foot .= 'SET FOREIGN_KEY_CHECKS=1;' . $crlf;
+ }
+
+ if (isset($GLOBALS['sql_use_transaction'])) {
+ $foot .= 'COMMIT;' . $crlf;
+ }
+
+ // restore connection settings
+ $charset_of_file = isset($GLOBALS['charset_of_file'])
+ ? $GLOBALS['charset_of_file'] : '';
+ if (! empty($GLOBALS['asfile'])
+ && isset($mysql_charset_map[$charset_of_file])
+ && ! PMA_DRIZZLE
+ ) {
+ $foot .= $crlf
+ . '/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;'
+ . $crlf
+ . '/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;'
+ . $crlf
+ . '/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;'
+ . $crlf;
+ }
+
+ /* Restore timezone */
+ if (isset($GLOBALS['sql_utc_time']) && $GLOBALS['sql_utc_time']) {
+ $GLOBALS['dbi']->query('SET time_zone = "' . $GLOBALS['old_tz'] . '"');
+ }
+
+ return PMA_exportOutputHandler($foot);
+ }
+
+ /**
+ * Outputs export header. It is the first method to be called, so all
+ * the required variables are initialized here.
+ *
+ * @return bool Whether it succeeded
+ */
+ public function exportHeader()
+ {
+ global $crlf, $cfg;
+ global $mysql_charset_map;
+
+ if (isset($GLOBALS['sql_compatibility'])) {
+ $tmp_compat = $GLOBALS['sql_compatibility'];
+ if ($tmp_compat == 'NONE') {
+ $tmp_compat = '';
+ }
+ $GLOBALS['dbi']->tryQuery('SET SQL_MODE="' . $tmp_compat . '"');
+ unset($tmp_compat);
+ }
+ $head = $this->_exportComment('phpMyAdmin SQL Dump')
+ . $this->_exportComment('version ' . PMA_VERSION)
+ . $this->_exportComment('http://www.phpmyadmin.net')
+ . $this->_exportComment();
+ $host_string = __('Host:') . ' ' . $cfg['Server']['host'];
+ if (! empty($cfg['Server']['port'])) {
+ $host_string .= ':' . $cfg['Server']['port'];
+ }
+ $head .= $this->_exportComment($host_string);
+ $head .=
+ $this->_exportComment(
+ __('Generation Time:') . ' '
+ . PMA_Util::localisedDate()
+ )
+ . $this->_exportComment(
+ __('Server version:') . ' ' . PMA_MYSQL_STR_VERSION
+ )
+ . $this->_exportComment(__('PHP Version:') . ' ' . phpversion())
+ . $this->_possibleCRLF();
+
+ if (isset($GLOBALS['sql_header_comment'])
+ && ! empty($GLOBALS['sql_header_comment'])
+ ) {
+ // '\n' is not a newline (like "\n" would be), it's the characters
+ // backslash and n, as explained on the export interface
+ $lines = explode('\n', $GLOBALS['sql_header_comment']);
+ $head .= $this->_exportComment();
+ foreach ($lines as $one_line) {
+ $head .= $this->_exportComment($one_line);
+ }
+ $head .= $this->_exportComment();
+ }
+
+ if (isset($GLOBALS['sql_disable_fk'])) {
+ $head .= 'SET FOREIGN_KEY_CHECKS=0;' . $crlf;
+ }
+
+ // We want exported AUTO_INCREMENT columns to have still same value,
+ // do this only for recent MySQL exports
+ if ((! isset($GLOBALS['sql_compatibility'])
+ || $GLOBALS['sql_compatibility'] == 'NONE')
+ && ! PMA_DRIZZLE
+ ) {
+ $head .= 'SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";' . $crlf;
+ }
+
+ if (isset($GLOBALS['sql_use_transaction'])) {
+ $head .= 'SET AUTOCOMMIT = 0;' . $crlf
+ . 'START TRANSACTION;' . $crlf;
+ }
+
+ /* Change timezone if we should export timestamps in UTC */
+ if (isset($GLOBALS['sql_utc_time']) && $GLOBALS['sql_utc_time']) {
+ $head .= 'SET time_zone = "+00:00";' . $crlf;
+ $GLOBALS['old_tz'] = $GLOBALS['dbi']
+ ->fetchValue('SELECT @@session.time_zone');
+ $GLOBALS['dbi']->query('SET time_zone = "+00:00"');
+ }
+
+ $head .= $this->_possibleCRLF();
+
+ if (! empty($GLOBALS['asfile']) && ! PMA_DRIZZLE) {
+ // we are saving as file, therefore we provide charset information
+ // so that a utility like the mysql client can interpret
+ // the file correctly
+ if (isset($GLOBALS['charset_of_file'])
+ && isset($mysql_charset_map[$GLOBALS['charset_of_file']])
+ ) {
+ // we got a charset from the export dialog
+ $set_names = $mysql_charset_map[$GLOBALS['charset_of_file']];
+ } else {
+ // by default we use the connection charset
+ $set_names = $mysql_charset_map['utf-8'];
+ }
+ $head .= $crlf
+ . '/*!40101 SET @OLD_CHARACTER_SET_CLIENT='
+ . '@@CHARACTER_SET_CLIENT */;' . $crlf
+ . '/*!40101 SET @OLD_CHARACTER_SET_RESULTS='
+ . '@@CHARACTER_SET_RESULTS */;' . $crlf
+ . '/*!40101 SET @OLD_COLLATION_CONNECTION='
+ . '@@COLLATION_CONNECTION */;'. $crlf
+ . '/*!40101 SET NAMES ' . $set_names . ' */;' . $crlf . $crlf;
+ }
+
+ return PMA_exportOutputHandler($head);
+ }
+
+ /**
+ * Outputs CREATE DATABASE statement
+ *
+ * @param string $db Database name
+ *
+ * @return bool Whether it succeeded
+ */
+ public function exportDBCreate($db)
+ {
+ global $crlf;
+
+ if (isset($GLOBALS['sql_compatibility'])) {
+ $compat = $GLOBALS['sql_compatibility'];
+ } else {
+ $compat = 'NONE';
+ }
+ if (isset($GLOBALS['sql_drop_database'])) {
+ if (! PMA_exportOutputHandler(
+ 'DROP DATABASE '
+ . (isset($GLOBALS['sql_backquotes'])
+ ? PMA_Util::backquoteCompat($db, $compat) : $db)
+ . ';' . $crlf
+ )) {
+ return false;
+ }
+ }
+ if (isset($GLOBALS['sql_create_database'])) {
+ $create_query = 'CREATE DATABASE IF NOT EXISTS '
+ . (isset($GLOBALS['sql_backquotes'])
+ ? PMA_Util::backquoteCompat($db, $compat) : $db);
+ $collation = PMA_getDbCollation($db);
+ if (PMA_DRIZZLE) {
+ $create_query .= ' COLLATE ' . $collation;
+ } else {
+ if (strpos($collation, '_')) {
+ $create_query .= ' DEFAULT CHARACTER SET '
+ . substr($collation, 0, strpos($collation, '_'))
+ . ' COLLATE ' . $collation;
+ } else {
+ $create_query .= ' DEFAULT CHARACTER SET ' . $collation;
+ }
+ }
+ $create_query .= ';' . $crlf;
+ if (! PMA_exportOutputHandler($create_query)) {
+ return false;
+ }
+ if (isset($GLOBALS['sql_backquotes'])
+ && ((isset($GLOBALS['sql_compatibility'])
+ && $GLOBALS['sql_compatibility'] == 'NONE')
+ || PMA_DRIZZLE)
+ ) {
+ $result = PMA_exportOutputHandler(
+ 'USE ' . PMA_Util::backquoteCompat($db, $compat)
+ . ';' . $crlf
+ );
+ } else {
+ $result = PMA_exportOutputHandler('USE ' . $db . ';' . $crlf);
+ }
+ return $result;
+ } else {
+ return true;
+ }
+ }
+
+ /**
+ * Outputs database header
+ *
+ * @param string $db Database name
+ *
+ * @return bool Whether it succeeded
+ */
+ public function exportDBHeader($db)
+ {
+ if (isset($GLOBALS['sql_compatibility'])) {
+ $compat = $GLOBALS['sql_compatibility'];
+ } else {
+ $compat = 'NONE';
+ }
+ $head = $this->_exportComment()
+ . $this->_exportComment(
+ __('Database:') . ' '
+ . (isset($GLOBALS['sql_backquotes'])
+ ? PMA_Util::backquoteCompat($db, $compat)
+ : '\'' . $db . '\'')
+ )
+ . $this->_exportComment();
+ return PMA_exportOutputHandler($head);
+ }
+
+ /**
+ * Outputs database footer
+ *
+ * @param string $db Database name
+ *
+ * @return bool Whether it succeeded
+ */
+ public function exportDBFooter($db)
+ {
+ global $crlf;
+
+ $result = true;
+ if (isset($GLOBALS['sql_constraints'])) {
+ $result = PMA_exportOutputHandler($GLOBALS['sql_constraints']);
+ unset($GLOBALS['sql_constraints']);
+ }
+
+ if (($GLOBALS['sql_structure_or_data'] == 'structure'
+ || $GLOBALS['sql_structure_or_data'] == 'structure_and_data')
+ && isset($GLOBALS['sql_procedure_function'])
+ ) {
+ $text = '';
+ $delimiter = '$$';
+
+ if (PMA_MYSQL_INT_VERSION > 50100) {
+ $event_names = $GLOBALS['dbi']->fetchResult(
+ 'SELECT EVENT_NAME FROM information_schema.EVENTS WHERE'
+ . ' EVENT_SCHEMA= \''
+ . PMA_Util::sqlAddSlashes($db, true)
+ . '\';'
+ );
+ } else {
+ $event_names = array();
+ }
+
+ if ($event_names) {
+ $text .= $crlf
+ . 'DELIMITER ' . $delimiter . $crlf;
+
+ $text .=
+ $this->_exportComment()
+ . $this->_exportComment(__('Events'))
+ . $this->_exportComment();
+
+ foreach ($event_names as $event_name) {
+ if (! empty($GLOBALS['sql_drop_table'])) {
+ $text .= 'DROP EVENT '
+ . PMA_Util::backquote($event_name)
+ . $delimiter . $crlf;
+ }
+ $text .= $GLOBALS['dbi']
+ ->getDefinition($db, 'EVENT', $event_name)
+ . $delimiter . $crlf . $crlf;
+ }
+
+ $text .= 'DELIMITER ;' . $crlf;
+ }
+
+ if (! empty($text)) {
+ $result = PMA_exportOutputHandler($text);
+ }
+ }
+ return $result;
+ }
+
+ /**
+ * Returns a stand-in CREATE definition to resolve view dependencies
+ *
+ * @param string $db the database name
+ * @param string $view the view name
+ * @param string $crlf the end of line sequence
+ *
+ * @return string resulting definition
+ */
+ public function getTableDefStandIn($db, $view, $crlf)
+ {
+ $create_query = '';
+ if (! empty($GLOBALS['sql_drop_table'])) {
+ $create_query .= 'DROP VIEW IF EXISTS '
+ . PMA_Util::backquote($view)
+ . ';' . $crlf;
+ }
+
+ $create_query .= 'CREATE TABLE ';
+
+ if (isset($GLOBALS['sql_if_not_exists'])
+ && $GLOBALS['sql_if_not_exists']
+ ) {
+ $create_query .= 'IF NOT EXISTS ';
+ }
+ $create_query .= PMA_Util::backquote($view) . ' (' . $crlf;
+ $tmp = array();
+ $columns = $GLOBALS['dbi']->getColumnsFull($db, $view);
+ foreach ($columns as $column_name => $definition) {
+ $tmp[] = PMA_Util::backquote($column_name) . ' ' .
+ $definition['Type'] . $crlf;
+ }
+ $create_query .= implode(',', $tmp) . ');';
+ return($create_query);
+ }
+
+ /**
+ * Returns CREATE definition that matches $view's structure
+ *
+ * @param string $db the database name
+ * @param string $view the view name
+ * @param string $crlf the end of line sequence
+ * @param bool $add_semicolon whether to add semicolon and end-of-line at
+ * the end
+ *
+ * @return string resulting schema
+ */
+ private function _getTableDefForView(
+ $db,
+ $view,
+ $crlf,
+ $add_semicolon = true
+ ) {
+ $create_query = "CREATE TABLE";
+ if (isset($GLOBALS['sql_if_not_exists'])) {
+ $create_query .= " IF NOT EXISTS ";
+ }
+ $create_query .= PMA_Util::backquote($view) . "(" . $crlf;
+
+ $columns = $GLOBALS['dbi']->getColumns($db, $view, null, true);
+
+ $firstCol = true;
+ foreach ($columns as $column) {
+ $extracted_columnspec = PMA_Util::extractColumnSpec($column['Type']);
+
+ if (! $firstCol) {
+ $create_query .= "," . $crlf;
+ }
+ $create_query .= " " . PMA_Util::backquote($column['Field']);
+ $create_query .= " " . $column['Type'];
+ if ($extracted_columnspec['can_contain_collation']
+ && ! empty($column['Collation'])
+ ) {
+ $create_query .= " COLLATE " . $column['Collation'];
+ }
+ if ($column['Null'] == 'NO') {
+ $create_query .= " NOT NULL";
+ }
+ if (isset($column['Default'])) {
+ $create_query .= " DEFAULT '"
+ . PMA_Util::sqlAddSlashes($column['Default']) . "'";
+ } else if ($column['Null'] == 'YES') {
+ $create_query .= " DEFAULT NULL";
+ }
+ if (! empty($column['Comment'])) {
+ $create_query .= " COMMENT '"
+ . PMA_Util::sqlAddSlashes($column['Comment']) . "'";
+ }
+ $firstCol = false;
+ }
+ $create_query .= $crlf . ")" . ($add_semicolon ? ';' : '') . $crlf;
+
+ if (isset($GLOBALS['sql_compatibility'])) {
+ $compat = $GLOBALS['sql_compatibility'];
+ } else {
+ $compat = 'NONE';
+ }
+ if ($compat == 'MSSQL') {
+ $create_query = $this->_makeCreateTableMSSQLCompatible(
+ $create_query
+ );
+ }
+ return $create_query;
+ }
+
+ /**
+ * Returns $table's CREATE definition
+ *
+ * @param string $db the database name
+ * @param string $table the table name
+ * @param string $crlf the end of line sequence
+ * @param string $error_url the url to go back in case of error
+ * @param bool $show_dates whether to include creation/update/check
+ * dates
+ * @param bool $add_semicolon whether to add semicolon and end-of-line at
+ * the end
+ * @param bool $view whether we're handling a view
+ *
+ * @return string resulting schema
+ */
+ public function getTableDef(
+ $db,
+ $table,
+ $crlf,
+ $error_url,
+ $show_dates = false,
+ $add_semicolon = true,
+ $view = false
+ ) {
+ global $sql_drop_table, $sql_backquotes, $sql_constraints,
+ $sql_constraints_query, $sql_drop_foreign_keys;
+
+ $schema_create = '';
+ $auto_increment = '';
+ $new_crlf = $crlf;
+
+ if (isset($GLOBALS['sql_compatibility'])) {
+ $compat = $GLOBALS['sql_compatibility'];
+ } else {
+ $compat = 'NONE';
+ }
+
+ // need to use PMA_DatabaseInterface::QUERY_STORE
+ // with $GLOBALS['dbi']->numRows() in mysqli
+ $result = $GLOBALS['dbi']->query(
+ 'SHOW TABLE STATUS FROM ' . PMA_Util::backquote($db)
+ . ' LIKE \'' . PMA_Util::sqlAddSlashes($table, true) . '\'',
+ null,
+ PMA_DatabaseInterface::QUERY_STORE
+ );
+ if ($result != false) {
+ if ($GLOBALS['dbi']->numRows($result) > 0) {
+ $tmpres = $GLOBALS['dbi']->fetchAssoc($result);
+ if (PMA_DRIZZLE && $show_dates) {
+ // Drizzle doesn't give Create_time and Update_time in
+ // SHOW TABLE STATUS, add it
+ $sql ="SELECT
+ TABLE_CREATION_TIME AS Create_time,
+ TABLE_UPDATE_TIME AS Update_time
+ FROM data_dictionary.TABLES
+ WHERE TABLE_SCHEMA = '"
+ . PMA_Util::sqlAddSlashes($db) . "'
+ AND TABLE_NAME = '"
+ . PMA_Util::sqlAddSlashes($table) . "'";
+ $tmpres = array_merge(
+ $GLOBALS['dbi']->fetchSingleRow($sql), $tmpres
+ );
+ }
+ // Here we optionally add the AUTO_INCREMENT next value,
+ // but starting with MySQL 5.0.24, the clause is already included
+ // in SHOW CREATE TABLE so we'll remove it below
+ // It's required for Drizzle because SHOW CREATE TABLE uses
+ // the value from table's creation time
+ if (isset($GLOBALS['sql_auto_increment'])
+ && ! empty($tmpres['Auto_increment'])
+ ) {
+ $auto_increment .= ' AUTO_INCREMENT='
+ . $tmpres['Auto_increment'] . ' ';
+ }
+
+ if ($show_dates
+ && isset($tmpres['Create_time'])
+ && ! empty($tmpres['Create_time'])
+ ) {
+ $schema_create .= $this->_exportComment(
+ __('Creation:') . ' '
+ . PMA_Util::localisedDate(
+ strtotime($tmpres['Create_time'])
+ )
+ );
+ $new_crlf = $this->_exportComment() . $crlf;
+ }
+
+ if ($show_dates
+ && isset($tmpres['Update_time'])
+ && ! empty($tmpres['Update_time'])
+ ) {
+ $schema_create .= $this->_exportComment(
+ __('Last update:') . ' '
+ . PMA_Util::localisedDate(
+ strtotime($tmpres['Update_time'])
+ )
+ );
+ $new_crlf = $this->_exportComment() . $crlf;
+ }
+
+ if ($show_dates
+ && isset($tmpres['Check_time'])
+ && ! empty($tmpres['Check_time'])
+ ) {
+ $schema_create .= $this->_exportComment(
+ __('Last check:') . ' '
+ . PMA_Util::localisedDate(
+ strtotime($tmpres['Check_time'])
+ )
+ );
+ $new_crlf = $this->_exportComment() . $crlf;
+ }
+ }
+ $GLOBALS['dbi']->freeResult($result);
+ }
+
+ $schema_create .= $new_crlf;
+
+ // no need to generate a DROP VIEW here, it was done earlier
+ if (! empty($sql_drop_table) && ! PMA_Table::isView($db, $table)) {
+ $schema_create .= 'DROP TABLE IF EXISTS '
+ . PMA_Util::backquote($table, $sql_backquotes) . ';'
+ . $crlf;
+ }
+
+ // Complete table dump,
+ // Whether to quote table and column names or not
+ // Drizzle always quotes names
+ if (! PMA_DRIZZLE) {
+ if ($sql_backquotes) {
+ $GLOBALS['dbi']->query('SET SQL_QUOTE_SHOW_CREATE = 1');
+ } else {
+ $GLOBALS['dbi']->query('SET SQL_QUOTE_SHOW_CREATE = 0');
+ }
+ }
+
+ // I don't see the reason why this unbuffered query could cause problems,
+ // because SHOW CREATE TABLE returns only one row, and we free the
+ // results below. Nonetheless, we got 2 user reports about this
+ // (see bug 1562533) so I removed the unbuffered mode.
+ // $result = $GLOBALS['dbi']->query('SHOW CREATE TABLE ' . backquote($db)
+ // . '.' . backquote($table), null, PMA_DatabaseInterface::QUERY_UNBUFFERED);
+ //
+ // Note: SHOW CREATE TABLE, at least in MySQL 5.1.23, does not
+ // produce a displayable result for the default value of a BIT
+ // column, nor does the mysqldump command. See MySQL bug 35796
+ $result = $GLOBALS['dbi']->tryQuery(
+ 'SHOW CREATE TABLE ' . PMA_Util::backquote($db) . '.'
+ . PMA_Util::backquote($table)
+ );
+ // an error can happen, for example the table is crashed
+ $tmp_error = $GLOBALS['dbi']->getError();
+ if ($tmp_error) {
+ return $this->_exportComment(__('in use') . '(' . $tmp_error . ')');
+ }
+
+ if ($result != false && ($row = $GLOBALS['dbi']->fetchRow($result))) {
+ $create_query = $row[1];
+ unset($row);
+
+ // Convert end of line chars to one that we want (note that MySQL
+ // doesn't return query it will accept in all cases)
+ if (strpos($create_query, "(\r\n ")) {
+ $create_query = str_replace("\r\n", $crlf, $create_query);
+ } elseif (strpos($create_query, "(\n ")) {
+ $create_query = str_replace("\n", $crlf, $create_query);
+ } elseif (strpos($create_query, "(\r ")) {
+ $create_query = str_replace("\r", $crlf, $create_query);
+ }
+
+ /*
+ * Drop database name from VIEW creation.
+ *
+ * This is a bit tricky, but we need to issue SHOW CREATE TABLE with
+ * database name, but we don't want name to show up in CREATE VIEW
+ * statement.
+ */
+ if ($view) {
+ $create_query = preg_replace(
+ '/' . PMA_Util::backquote($db) . '\./',
+ '',
+ $create_query
+ );
+ }
+
+ // Should we use IF NOT EXISTS?
+ if (isset($GLOBALS['sql_if_not_exists'])) {
+ $create_query = preg_replace(
+ '/^CREATE TABLE/',
+ 'CREATE TABLE IF NOT EXISTS',
+ $create_query
+ );
+ }
+
+ if ($compat == 'MSSQL') {
+ $create_query = $this->_makeCreateTableMSSQLCompatible(
+ $create_query
+ );
+ }
+
+ // Drizzle (checked on 2011.03.13) returns ROW_FORMAT surrounded
+ // with quotes, which is not accepted by parser
+ if (PMA_DRIZZLE) {
+ $create_query = preg_replace(
+ '/ROW_FORMAT=\'(\S+)\'/',
+ 'ROW_FORMAT=$1',
+ $create_query
+ );
+ }
+
+ // are there any constraints to cut out?
+ if (preg_match('@CONSTRAINT|FOREIGN[\s]+KEY@', $create_query)) {
+
+ // Split the query into lines, so we can easily handle it.
+ // We know lines are separated by $crlf (done few lines above).
+ $sql_lines = explode($crlf, $create_query);
+ $sql_count = count($sql_lines);
+
+ // lets find first line with constraints
+ for ($i = 0; $i < $sql_count; $i++) {
+ if (preg_match(
+ '@^[\s]*(CONSTRAINT|FOREIGN[\s]+KEY)@',
+ $sql_lines[$i]
+ )) {
+ break;
+ }
+ }
+
+ // If we really found a constraint
+ if ($i != $sql_count) {
+
+ // remove, from the end of create statement
+ $sql_lines[$i - 1] = preg_replace(
+ '@,$@',
+ '',
+ $sql_lines[$i - 1]
+ );
+
+ // prepare variable for constraints
+ if (! isset($sql_constraints)) {
+ if (isset($GLOBALS['no_constraints_comments'])) {
+ $sql_constraints = '';
+ } else {
+ $sql_constraints = $crlf
+ . $this->_exportComment()
+ . $this->_exportComment(
+ __('Constraints for dumped tables')
+ )
+ . $this->_exportComment();
+ }
+ }
+
+ // comments for current table
+ if (! isset($GLOBALS['no_constraints_comments'])) {
+ $sql_constraints .= $crlf
+ . $this->_exportComment()
+ . $this->_exportComment(
+ __('Constraints for table')
+ . ' '
+ . PMA_Util::backquoteCompat($table, $compat)
+ )
+ . $this->_exportComment();
+ }
+
+ // let's do the work
+ $sql_constraints_query .= 'ALTER TABLE '
+ . PMA_Util::backquoteCompat($table, $compat)
+ . $crlf;
+ $sql_constraints .= 'ALTER TABLE '
+ . PMA_Util::backquoteCompat($table, $compat)
+ . $crlf;
+ $sql_drop_foreign_keys .= 'ALTER TABLE '
+ . PMA_Util::backquoteCompat($db, $compat) . '.'
+ . PMA_Util::backquoteCompat($table, $compat)
+ . $crlf;
+
+ $first = true;
+ for ($j = $i; $j < $sql_count; $j++) {
+ if (preg_match(
+ '@CONSTRAINT|FOREIGN[\s]+KEY@',
+ $sql_lines[$j]
+ )) {
+ if (! $first) {
+ $sql_constraints .= $crlf;
+ }
+ if (strpos($sql_lines[$j], 'CONSTRAINT') === false) {
+ $tmp_str = preg_replace(
+ '/(FOREIGN[\s]+KEY)/',
+ 'ADD \1',
+ $sql_lines[$j]
+ );
+ $sql_constraints_query .= $tmp_str;
+ $sql_constraints .= $tmp_str;
+ } else {
+ $tmp_str = preg_replace(
+ '/(CONSTRAINT)/',
+ 'ADD \1',
+ $sql_lines[$j]
+ );
+ $sql_constraints_query .= $tmp_str;
+ $sql_constraints .= $tmp_str;
+ preg_match(
+ '/(CONSTRAINT)([\s])([\S]*)([\s])/',
+ $sql_lines[$j],
+ $matches
+ );
+ if (! $first) {
+ $sql_drop_foreign_keys .= ', ';
+ }
+ $sql_drop_foreign_keys .= 'DROP FOREIGN KEY '
+ . $matches[3];
+ }
+ $first = false;
+ } else {
+ break;
+ }
+ }
+ $sql_constraints .= ';' . $crlf;
+ $sql_constraints_query .= ';';
+
+ $create_query = implode(
+ $crlf,
+ array_slice($sql_lines, 0, $i)
+ )
+ . $crlf
+ . implode(
+ $crlf,
+ array_slice($sql_lines, $j, $sql_count - 1)
+ );
+ unset($sql_lines);
+ }
+ }
+ $schema_create .= $create_query;
+ }
+
+ // remove a possible "AUTO_INCREMENT = value" clause
+ // that could be there starting with MySQL 5.0.24
+ // in Drizzle it's useless as it contains the value given at table
+ // creation time
+ $schema_create = preg_replace(
+ '/AUTO_INCREMENT\s*=\s*([0-9])+/',
+ '',
+ $schema_create
+ );
+
+ $schema_create .= ($compat != 'MSSQL') ? $auto_increment : '';
+
+ $GLOBALS['dbi']->freeResult($result);
+ return $schema_create . ($add_semicolon ? ';' . $crlf : '');
+ } // end of the 'getTableDef()' function
+
+ /**
+ * Returns $table's comments, relations etc.
+ *
+ * @param string $db database name
+ * @param string $table table name
+ * @param string $crlf end of line sequence
+ * @param bool $do_relation whether to include relation comments
+ * @param bool $do_mime whether to include mime comments
+ *
+ * @return string resulting comments
+ */
+ private function _getTableComments(
+ $db,
+ $table,
+ $crlf,
+ $do_relation = false,
+ $do_mime = false
+ ) {
+ global $cfgRelation, $sql_backquotes;
+
+ $schema_create = '';
+
+ // Check if we can use Relations
+ if ($do_relation && ! empty($cfgRelation['relation'])) {
+ // Find which tables are related with the current one and write it in
+ // an array
+ $res_rel = PMA_getForeigners($db, $table);
+
+ if ($res_rel && count($res_rel) > 0) {
+ $have_rel = true;
+ } else {
+ $have_rel = false;
+ }
+ } else {
+ $have_rel = false;
+ } // end if
+
+ if ($do_mime && $cfgRelation['mimework']) {
+ if (! ($mime_map = PMA_getMIME($db, $table, true))) {
+ unset($mime_map);
+ }
+ }
+
+ if (isset($mime_map) && count($mime_map) > 0) {
+ $schema_create .= $this->_possibleCRLF()
+ . $this->_exportComment()
+ . $this->_exportComment(
+ __('MIME TYPES FOR TABLE'). ' '
+ . PMA_Util::backquote($table, $sql_backquotes) . ':'
+ );
+ @reset($mime_map);
+ foreach ($mime_map as $mime_field => $mime) {
+ $schema_create .=
+ $this->_exportComment(
+ ' '
+ . PMA_Util::backquote($mime_field, $sql_backquotes)
+ )
+ . $this->_exportComment(
+ ' '
+ . PMA_Util::backquote(
+ $mime['mimetype'],
+ $sql_backquotes
+ )
+ );
+ }
+ $schema_create .= $this->_exportComment();
+ }
+
+ if ($have_rel) {
+ $schema_create .= $this->_possibleCRLF()
+ . $this->_exportComment()
+ . $this->_exportComment(
+ __('RELATIONS FOR TABLE') . ' '
+ . PMA_Util::backquote($table, $sql_backquotes)
+ . ':'
+ );
+ foreach ($res_rel as $rel_field => $rel) {
+ $schema_create .=
+ $this->_exportComment(
+ ' '
+ . PMA_Util::backquote($rel_field, $sql_backquotes)
+ )
+ . $this->_exportComment(
+ ' '
+ . PMA_Util::backquote(
+ $rel['foreign_table'],
+ $sql_backquotes
+ )
+ . ' -> '
+ . PMA_Util::backquote(
+ $rel['foreign_field'],
+ $sql_backquotes
+ )
+ );
+ }
+ $schema_create .= $this->_exportComment();
+ }
+
+ return $schema_create;
+
+ } // end of the '_getTableComments()' function
+
+ /**
+ * Outputs table's structure
+ *
+ * @param string $db database name
+ * @param string $table table name
+ * @param string $crlf the end of line sequence
+ * @param string $error_url the url to go back in case of error
+ * @param string $export_mode 'create_table','triggers','create_view',
+ * 'stand_in'
+ * @param string $export_type 'server', 'database', 'table'
+ * @param bool $relation whether to include relation comments
+ * @param bool $comments whether to include the pmadb-style column
+ * comments as comments in the structure; this is
+ * deprecated but the parameter is left here
+ * because export.php calls exportStructure()
+ * also for other export types which use this
+ * parameter
+ * @param bool $mime whether to include mime comments
+ * @param bool $dates whether to include creation/update/check dates
+ *
+ * @return bool Whether it succeeded
+ */
+ public function exportStructure(
+ $db,
+ $table,
+ $crlf,
+ $error_url,
+ $export_mode,
+ $export_type,
+ $relation = false,
+ $comments = false,
+ $mime = false,
+ $dates = false
+ ) {
+ if (isset($GLOBALS['sql_compatibility'])) {
+ $compat = $GLOBALS['sql_compatibility'];
+ } else {
+ $compat = 'NONE';
+ }
+
+ $formatted_table_name = (isset($GLOBALS['sql_backquotes']))
+ ? PMA_Util::backquoteCompat($table, $compat)
+ : '\'' . $table . '\'';
+ $dump = $this->_possibleCRLF()
+ . $this->_exportComment(str_repeat('-', 56))
+ . $this->_possibleCRLF()
+ . $this->_exportComment();
+
+ switch($export_mode) {
+ case 'create_table':
+ $dump .= $this->_exportComment(
+ __('Table structure for table') . ' '. $formatted_table_name
+ );
+ $dump .= $this->_exportComment();
+ $dump .= $this->getTableDef($db, $table, $crlf, $error_url, $dates);
+ $dump .= $this->_getTableComments($db, $table, $crlf, $relation, $mime);
+ break;
+ case 'triggers':
+ $dump = '';
+ $triggers = $GLOBALS['dbi']->getTriggers($db, $table);
+ if ($triggers) {
+ $dump .= $this->_possibleCRLF()
+ . $this->_exportComment()
+ . $this->_exportComment(
+ __('Triggers') . ' ' . $formatted_table_name
+ )
+ . $this->_exportComment();
+ $delimiter = '//';
+ foreach ($triggers as $trigger) {
+ $dump .= $trigger['drop'] . ';' . $crlf;
+ $dump .= 'DELIMITER ' . $delimiter . $crlf;
+ $dump .= $trigger['create'];
+ $dump .= 'DELIMITER ;' . $crlf;
+ }
+ }
+ break;
+ case 'create_view':
+ if (empty($GLOBALS['sql_views_as_tables'])) {
+ $dump .=
+ $this->_exportComment(
+ __('Structure for view')
+ . ' '
+ . $formatted_table_name
+ )
+ . $this->_exportComment();
+ // delete the stand-in table previously created (if any)
+ if ($export_type != 'table') {
+ $dump .= 'DROP TABLE IF EXISTS '
+ . PMA_Util::backquote($table) . ';' . $crlf;
+ }
+ $dump .= $this->getTableDef(
+ $db, $table, $crlf, $error_url, $dates, true, true
+ );
+ } else {
+ $dump .=
+ $this->_exportComment(
+ sprintf(
+ __('Structure for view %s exported as a table'),
+ $formatted_table_name
+ )
+ )
+ . $this->_exportComment();
+ // delete the stand-in table previously created (if any)
+ if ($export_type != 'table') {
+ $dump .= 'DROP TABLE IF EXISTS '
+ . PMA_Util::backquote($table) . ';' . $crlf;
+ }
+ $dump .= $this->_getTableDefForView(
+ $db, $table, $crlf, true
+ );
+ }
+ break;
+ case 'stand_in':
+ $dump .=
+ $this->_exportComment(
+ __('Stand-in structure for view') . ' ' . $formatted_table_name
+ )
+ . $this->_exportComment();
+ // export a stand-in definition to resolve view dependencies
+ $dump .= $this->getTableDefStandIn($db, $table, $crlf);
+ } // end switch
+
+ // this one is built by getTableDef() to use in table copy/move
+ // but not in the case of export
+ unset($GLOBALS['sql_constraints_query']);
+
+ return PMA_exportOutputHandler($dump);
+ }
+
+ /**
+ * Outputs the content of a table in SQL format
+ *
+ * @param string $db database name
+ * @param string $table table name
+ * @param string $crlf the end of line sequence
+ * @param string $error_url the url to go back in case of error
+ * @param string $sql_query SQL query for obtaining data
+ *
+ * @return bool Whether it succeeded
+ */
+ public function exportData($db, $table, $crlf, $error_url, $sql_query)
+ {
+ global $current_row, $sql_backquotes;
+
+ if (isset($GLOBALS['sql_compatibility'])) {
+ $compat = $GLOBALS['sql_compatibility'];
+ } else {
+ $compat = 'NONE';
+ }
+
+ $formatted_table_name = (isset($GLOBALS['sql_backquotes']))
+ ? PMA_Util::backquoteCompat($table, $compat)
+ : '\'' . $table . '\'';
+
+ // Do not export data for a VIEW, unless asked to export the view as a table
+ // (For a VIEW, this is called only when exporting a single VIEW)
+ if (PMA_Table::isView($db, $table)
+ && empty($GLOBALS['sql_views_as_tables'])
+ ) {
+ $head = $this->_possibleCRLF()
+ . $this->_exportComment()
+ . $this->_exportComment('VIEW ' . ' ' . $formatted_table_name)
+ . $this->_exportComment(__('Data:') . ' ' . __('None'))
+ . $this->_exportComment()
+ . $this->_possibleCRLF();
+
+ if (! PMA_exportOutputHandler($head)) {
+ return false;
+ }
+ return true;
+ }
+
+ // analyze the query to get the true column names, not the aliases
+ // (this fixes an undefined index, also if Complete inserts
+ // are used, we did not get the true column name in case of aliases)
+ $analyzed_sql = PMA_SQP_analyze(PMA_SQP_parse($sql_query));
+
+ $result = $GLOBALS['dbi']->tryQuery(
+ $sql_query, null, PMA_DatabaseInterface::QUERY_UNBUFFERED
+ );
+ // a possible error: the table has crashed
+ $tmp_error = $GLOBALS['dbi']->getError();
+ if ($tmp_error) {
+ return PMA_exportOutputHandler(
+ $this->_exportComment(
+ __('Error reading data:') . ' (' . $tmp_error . ')'
+ )
+ );
+ }
+
+ if ($result != false) {
+ $fields_cnt = $GLOBALS['dbi']->numFields($result);
+
+ // Get field information
+ $fields_meta = $GLOBALS['dbi']->getFieldsMeta($result);
+ $field_flags = array();
+ for ($j = 0; $j < $fields_cnt; $j++) {
+ $field_flags[$j] = $GLOBALS['dbi']->fieldFlags($result, $j);
+ }
+
+ for ($j = 0; $j < $fields_cnt; $j++) {
+ if (isset($analyzed_sql[0]['select_expr'][$j]['column'])) {
+ $field_set[$j] = PMA_Util::backquoteCompat(
+ $analyzed_sql[0]['select_expr'][$j]['column'],
+ $compat,
+ $sql_backquotes
+ );
+ } else {
+ $field_set[$j] = PMA_Util::backquoteCompat(
+ $fields_meta[$j]->name,
+ $compat,
+ $sql_backquotes
+ );
+ }
+ }
+
+ if (isset($GLOBALS['sql_type'])
+ && $GLOBALS['sql_type'] == 'UPDATE'
+ ) {
+ // update
+ $schema_insert = 'UPDATE ';
+ if (isset($GLOBALS['sql_ignore'])) {
+ $schema_insert .= 'IGNORE ';
+ }
+ // avoid EOL blank
+ $schema_insert .= PMA_Util::backquoteCompat(
+ $table,
+ $compat,
+ $sql_backquotes
+ ) . ' SET';
+ } else {
+ // insert or replace
+ if (isset($GLOBALS['sql_type'])
+ && $GLOBALS['sql_type'] == 'REPLACE'
+ ) {
+ $sql_command = 'REPLACE';
+ } else {
+ $sql_command = 'INSERT';
+ }
+
+ // delayed inserts?
+ if (isset($GLOBALS['sql_delayed'])) {
+ $insert_delayed = ' DELAYED';
+ } else {
+ $insert_delayed = '';
+ }
+
+ // insert ignore?
+ if (isset($GLOBALS['sql_type'])
+ && $GLOBALS['sql_type'] == 'INSERT'
+ && isset($GLOBALS['sql_ignore'])
+ ) {
+ $insert_delayed .= ' IGNORE';
+ }
+ //truncate table before insert
+ if (isset($GLOBALS['sql_truncate'])
+ && $GLOBALS['sql_truncate']
+ && $sql_command == 'INSERT'
+ ) {
+ $truncate = 'TRUNCATE TABLE '
+ . PMA_Util::backquoteCompat(
+ $table,
+ $compat,
+ $sql_backquotes
+ ) . ";";
+ $truncatehead = $this->_possibleCRLF()
+ . $this->_exportComment()
+ . $this->_exportComment(
+ __('Truncate table before insert') . ' '
+ . $formatted_table_name
+ )
+ . $this->_exportComment()
+ . $crlf;
+ PMA_exportOutputHandler($truncatehead);
+ PMA_exportOutputHandler($truncate);
+ } else {
+ $truncate = '';
+ }
+
+ // scheme for inserting fields
+ if ($GLOBALS['sql_insert_syntax'] == 'complete'
+ || $GLOBALS['sql_insert_syntax'] == 'both'
+ ) {
+ $fields = implode(', ', $field_set);
+ $schema_insert = $sql_command . $insert_delayed .' INTO '
+ . PMA_Util::backquoteCompat(
+ $table,
+ $compat,
+ $sql_backquotes
+ )
+ // avoid EOL blank
+ . ' (' . $fields . ') VALUES';
+ } else {
+ $schema_insert = $sql_command . $insert_delayed .' INTO '
+ . PMA_Util::backquoteCompat(
+ $table,
+ $compat,
+ $sql_backquotes
+ )
+ . ' VALUES';
+ }
+ }
+
+ //\x08\\x09, not required
+ $search = array("\x00", "\x0a", "\x0d", "\x1a");
+ $replace = array('\0', '\n', '\r', '\Z');
+ $current_row = 0;
+ $query_size = 0;
+ if (($GLOBALS['sql_insert_syntax'] == 'extended'
+ || $GLOBALS['sql_insert_syntax'] == 'both')
+ && (! isset($GLOBALS['sql_type'])
+ || $GLOBALS['sql_type'] != 'UPDATE')
+ ) {
+ $separator = ',';
+ $schema_insert .= $crlf;
+ } else {
+ $separator = ';';
+ }
+
+ while ($row = $GLOBALS['dbi']->fetchRow($result)) {
+ if ($current_row == 0) {
+ $head = $this->_possibleCRLF()
+ . $this->_exportComment()
+ . $this->_exportComment(
+ __('Dumping data for table') . ' '
+ . $formatted_table_name
+ )
+ . $this->_exportComment()
+ . $crlf;
+ if (! PMA_exportOutputHandler($head)) {
+ return false;
+ }
+ }
+ // We need to SET IDENTITY_INSERT ON for MSSQL
+ if (isset($GLOBALS['sql_compatibility'])
+ && $GLOBALS['sql_compatibility'] == 'MSSQL'
+ && $current_row == 0
+ ) {
+ if (! PMA_exportOutputHandler(
+ 'SET IDENTITY_INSERT '
+ . PMA_Util::backquoteCompat(
+ $table,
+ $compat
+ )
+ . ' ON ;'.$crlf
+ )) {
+ return false;
+ }
+ }
+ $current_row++;
+ for ($j = 0; $j < $fields_cnt; $j++) {
+ // NULL
+ if (! isset($row[$j]) || is_null($row[$j])) {
+ $values[] = 'NULL';
+ } elseif ($fields_meta[$j]->numeric
+ && $fields_meta[$j]->type != 'timestamp'
+ && ! $fields_meta[$j]->blob
+ ) {
+ // a number
+ // timestamp is numeric on some MySQL 4.1, BLOBs are
+ // sometimes numeric
+ $values[] = $row[$j];
+ } elseif (stristr($field_flags[$j], 'BINARY')
+ && $fields_meta[$j]->blob
+ && isset($GLOBALS['sql_hex_for_blob'])
+ ) {
+ // a true BLOB
+ // - mysqldump only generates hex data when the --hex-blob
+ // option is used, for fields having the binary attribute
+ // no hex is generated
+ // - a TEXT field returns type blob but a real blob
+ // returns also the 'binary' flag
+
+ // empty blobs need to be different, but '0' is also empty
+ // :-(
+ if (empty($row[$j]) && $row[$j] != '0') {
+ $values[] = '\'\'';
+ } else {
+ $values[] = '0x' . bin2hex($row[$j]);
+ }
+ } elseif ($fields_meta[$j]->type == 'bit') {
+ // detection of 'bit' works only on mysqli extension
+ $values[] = "b'" . PMA_Util::sqlAddSlashes(
+ PMA_Util::printableBitValue(
+ $row[$j], $fields_meta[$j]->length
+ )
+ )
+ . "'";
+ } else {
+ // something else -> treat as a string
+ $values[] = '\''
+ . str_replace(
+ $search, $replace,
+ PMA_Util::sqlAddSlashes($row[$j])
+ )
+ . '\'';
+ } // end if
+ } // end for
+
+ // should we make update?
+ if (isset($GLOBALS['sql_type'])
+ && $GLOBALS['sql_type'] == 'UPDATE'
+ ) {
+
+ $insert_line = $schema_insert;
+ for ($i = 0; $i < $fields_cnt; $i++) {
+ if (0 == $i) {
+ $insert_line .= ' ';
+ }
+ if ($i > 0) {
+ // avoid EOL blank
+ $insert_line .= ',';
+ }
+ $insert_line .= $field_set[$i] . ' = ' . $values[$i];
+ }
+
+ list($tmp_unique_condition, $tmp_clause_is_unique)
+ = PMA_Util::getUniqueCondition(
+ $result,
+ $fields_cnt,
+ $fields_meta,
+ $row
+ );
+ $insert_line .= ' WHERE ' . $tmp_unique_condition;
+ unset($tmp_unique_condition, $tmp_clause_is_unique);
+
+ } else {
+
+ // Extended inserts case
+ if ($GLOBALS['sql_insert_syntax'] == 'extended'
+ || $GLOBALS['sql_insert_syntax'] == 'both'
+ ) {
+ if ($current_row == 1) {
+ $insert_line = $schema_insert . '('
+ . implode(', ', $values) . ')';
+ } else {
+ $insert_line = '(' . implode(', ', $values) . ')';
+ $sql_max_size = $GLOBALS['sql_max_query_size'];
+ if (isset($sql_max_size)
+ && $sql_max_size > 0
+ && $query_size + strlen($insert_line) > $sql_max_size
+ ) {
+ if (! PMA_exportOutputHandler(';' . $crlf)) {
+ return false;
+ }
+ $query_size = 0;
+ $current_row = 1;
+ $insert_line = $schema_insert . $insert_line;
+ }
+ }
+ $query_size += strlen($insert_line);
+ // Other inserts case
+ } else {
+ $insert_line = $schema_insert
+ . '('
+ . implode(', ', $values)
+ . ')';
+ }
+ }
+ unset($values);
+
+ if (! PMA_exportOutputHandler(
+ ($current_row == 1 ? '' : $separator . $crlf)
+ . $insert_line
+ )) {
+ return false;
+ }
+
+ } // end while
+
+ if ($current_row > 0) {
+ if (! PMA_exportOutputHandler(';' . $crlf)) {
+ return false;
+ }
+ }
+
+ // We need to SET IDENTITY_INSERT OFF for MSSQL
+ if (isset($GLOBALS['sql_compatibility'])
+ && $GLOBALS['sql_compatibility'] == 'MSSQL'
+ && $current_row > 0
+ ) {
+ $outputSucceeded = PMA_exportOutputHandler(
+ $crlf . 'SET IDENTITY_INSERT '
+ . PMA_Util::backquoteCompat($table, $compat)
+ . ' OFF;' . $crlf
+ );
+ if (! $outputSucceeded) {
+ return false;
+ }
+ }
+ } // end if ($result != false)
+ $GLOBALS['dbi']->freeResult($result);
+
+ return true;
+ } // end of the 'exportData()' function
+
+ /**
+ * Make a create table statement compatible with MSSQL
+ *
+ * @param string $create_query MySQL create table statement
+ *
+ * @return string MSSQL compatible create table statement
+ */
+ private function _makeCreateTableMSSQLCompatible($create_query)
+ {
+ // In MSSQL
+ // 1. No 'IF NOT EXISTS' in CREATE TABLE
+ // 2. DATE field doesn't exists, we will use DATETIME instead
+ // 3. UNSIGNED attribute doesn't exist
+ // 4. No length on INT, TINYINT, SMALLINT, BIGINT and no precision on
+ // FLOAT fields
+ // 5. No KEY and INDEX inside CREATE TABLE
+ // 6. DOUBLE field doesn't exists, we will use FLOAT instead
+
+ $create_query = preg_replace(
+ "/^CREATE TABLE IF NOT EXISTS/",
+ 'CREATE TABLE',
+ $create_query
+ );
+ // first we need to replace all lines ended with '" DATE ...,\n'
+ // last preg_replace preserve us from situation with date text
+ // inside DEFAULT field value
+ $create_query = preg_replace(
+ "/\" date DEFAULT NULL(,)?\n/",
+ '" datetime DEFAULT NULL$1' . "\n",
+ $create_query
+ );
+ $create_query = preg_replace(
+ "/\" date NOT NULL(,)?\n/",
+ '" datetime NOT NULL$1' . "\n",
+ $create_query
+ );
+ $create_query = preg_replace(
+ '/" date NOT NULL DEFAULT \'([^\'])/',
+ '" datetime NOT NULL DEFAULT \'$1',
+ $create_query
+ );
+
+ // next we need to replace all lines ended with ') UNSIGNED ...,'
+ // last preg_replace preserve us from situation with unsigned text
+ // inside DEFAULT field value
+ $create_query = preg_replace(
+ "/\) unsigned NOT NULL(,)?\n/",
+ ') NOT NULL$1' . "\n",
+ $create_query
+ );
+ $create_query = preg_replace(
+ "/\) unsigned DEFAULT NULL(,)?\n/",
+ ') DEFAULT NULL$1' . "\n",
+ $create_query
+ );
+ $create_query = preg_replace(
+ '/\) unsigned NOT NULL DEFAULT \'([^\'])/',
+ ') NOT NULL DEFAULT \'$1',
+ $create_query
+ );
+
+ // we need to replace all lines ended with
+ // '" INT|TINYINT([0-9]{1,}) ...,' last preg_replace preserve us
+ // from situation with int([0-9]{1,}) text inside DEFAULT field
+ // value
+ $create_query = preg_replace(
+ '/" (int|tinyint|smallint|bigint)\([0-9]+\) DEFAULT NULL(,)?\n/',
+ '" $1 DEFAULT NULL$2' . "\n",
+ $create_query
+ );
+ $create_query = preg_replace(
+ '/" (int|tinyint|smallint|bigint)\([0-9]+\) NOT NULL(,)?\n/',
+ '" $1 NOT NULL$2' . "\n",
+ $create_query
+ );
+ $create_query = preg_replace(
+ '/" (int|tinyint|smallint|bigint)\([0-9]+\) NOT NULL DEFAULT \'([^\'])/',
+ '" $1 NOT NULL DEFAULT \'$2',
+ $create_query
+ );
+
+ // we need to replace all lines ended with
+ // '" FLOAT|DOUBLE([0-9,]{1,}) ...,'
+ // last preg_replace preserve us from situation with
+ // float([0-9,]{1,}) text inside DEFAULT field value
+ $create_query = preg_replace(
+ '/" (float|double)(\([0-9]+,[0-9,]+\))? DEFAULT NULL(,)?\n/',
+ '" float DEFAULT NULL$3' . "\n",
+ $create_query
+ );
+ $create_query = preg_replace(
+ '/" (float|double)(\([0-9,]+,[0-9,]+\))? NOT NULL(,)?\n/',
+ '" float NOT NULL$3' . "\n",
+ $create_query
+ );
+ $create_query = preg_replace(
+ '/" (float|double)(\([0-9,]+,[0-9,]+\))? NOT NULL DEFAULT \'([^\'])/',
+ '" float NOT NULL DEFAULT \'$3',
+ $create_query
+ );
+
+ // @todo remove indexes from CREATE TABLE
+
+ return $create_query;
+ }
+}
diff --git a/libraries/plugins/export/ExportTexytext.class.php b/libraries/plugins/export/ExportTexytext.class.php
new file mode 100644
index 0000000000..1e6efa0da3
--- /dev/null
+++ b/libraries/plugins/export/ExportTexytext.class.php
@@ -0,0 +1,576 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Export to Texy! text.
+ *
+ * @package PhpMyAdmin-Export
+ * @subpackage Texy!text
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/* Get the export interface */
+require_once 'libraries/plugins/ExportPlugin.class.php';
+
+/**
+ * Handles the export for the Texy! text class
+ *
+ * @package PhpMyAdmin-Export
+ * @subpackage Texy!text
+ */
+class ExportTexytext extends ExportPlugin
+{
+ /**
+ * Constructor
+ */
+ public function __construct()
+ {
+ $this->setProperties();
+ }
+
+ /**
+ * Sets the export Texy! text properties
+ *
+ * @return void
+ */
+ protected function setProperties()
+ {
+ $props = 'libraries/properties/';
+ include_once "$props/plugins/ExportPluginProperties.class.php";
+ include_once "$props/options/groups/OptionsPropertyRootGroup.class.php";
+ include_once "$props/options/groups/OptionsPropertyMainGroup.class.php";
+ include_once "$props/options/items/RadioPropertyItem.class.php";
+ include_once "$props/options/items/BoolPropertyItem.class.php";
+ include_once "$props/options/items/TextPropertyItem.class.php";
+
+ $exportPluginProperties = new ExportPluginProperties();
+ $exportPluginProperties->setText('Texy! text');
+ $exportPluginProperties->setExtension('txt');
+ $exportPluginProperties->setMimeType('text/plain');
+ $exportPluginProperties->setOptionsText(__('Options'));
+
+ // create the root group that will be the options field for
+ // $exportPluginProperties
+ // this will be shown as "Format specific options"
+ $exportSpecificOptions = new OptionsPropertyRootGroup();
+ $exportSpecificOptions->setName("Format Specific Options");
+
+ // what to dump (structure/data/both) main group
+ $dumpWhat = new OptionsPropertyMainGroup();
+ $dumpWhat->setName("general_opts");
+ $dumpWhat->setText(__('Dump table'));
+ // create primary items and add them to the group
+ $leaf = new RadioPropertyItem();
+ $leaf->setName("structure_or_data");
+ $leaf->setValues(
+ array(
+ 'structure' => __('structure'),
+ 'data' => __('data'),
+ 'structure_and_data' => __('structure and data')
+ )
+ );
+ $dumpWhat->addProperty($leaf);
+ // add the main group to the root group
+ $exportSpecificOptions->addProperty($dumpWhat);
+
+ // data options main group
+ $dataOptions = new OptionsPropertyMainGroup();
+ $dataOptions->setName("data");
+ $dataOptions->setText(__('Data dump options'));
+ $dataOptions->setForce('structure');
+ // create primary items and add them to the group
+ $leaf = new BoolPropertyItem();
+ $leaf->setName("columns");
+ $leaf->setText(__('Put columns names in the first row'));
+ $dataOptions->addProperty($leaf);
+ $leaf = new TextPropertyItem();
+ $leaf->setName('null');
+ $leaf->setText(__('Replace NULL with:'));
+ $dataOptions->addProperty($leaf);
+ // add the main group to the root group
+ $exportSpecificOptions->addProperty($dataOptions);
+
+ // set the options for the export plugin property item
+ $exportPluginProperties->setOptions($exportSpecificOptions);
+ $this->properties = $exportPluginProperties;
+ }
+
+ /**
+ * This method is called when any PluginManager to which the observer
+ * is attached calls PluginManager::notify()
+ *
+ * @param SplSubject $subject The PluginManager notifying the observer
+ * of an update.
+ *
+ * @return void
+ */
+ public function update (SplSubject $subject)
+ {
+ }
+
+ /**
+ * Outputs export header
+ *
+ * @return bool Whether it succeeded
+ */
+ public function exportHeader ()
+ {
+ return true;
+ }
+
+ /**
+ * Outputs export footer
+ *
+ * @return bool Whether it succeeded
+ */
+ public function exportFooter ()
+ {
+ return true;
+ }
+
+ /**
+ * Outputs database header
+ *
+ * @param string $db Database name
+ *
+ * @return bool Whether it succeeded
+ */
+ public function exportDBHeader ($db)
+ {
+ return PMA_exportOutputHandler(
+ '===' . __('Database') . ' ' . $db . "\n\n"
+ );
+ }
+
+ /**
+ * Outputs database footer
+ *
+ * @param string $db Database name
+ *
+ * @return bool Whether it succeeded
+ */
+ public function exportDBFooter ($db)
+ {
+ return true;
+ }
+
+ /**
+ * Outputs CREATE DATABASE statement
+ *
+ * @param string $db Database name
+ *
+ * @return bool Whether it succeeded
+ */
+ public function exportDBCreate($db)
+ {
+ return true;
+ }
+ /**
+ * Outputs the content of a table in NHibernate format
+ *
+ * @param string $db database name
+ * @param string $table table name
+ * @param string $crlf the end of line sequence
+ * @param string $error_url the url to go back in case of error
+ * @param string $sql_query SQL query for obtaining data
+ *
+ * @return bool Whether it succeeded
+ */
+ public function exportData($db, $table, $crlf, $error_url, $sql_query)
+ {
+ global $what;
+
+ if (! PMA_exportOutputHandler(
+ '== ' . __('Dumping data for table') . ' ' . $table . "\n\n"
+ )) {
+ return false;
+ }
+
+ // Gets the data from the database
+ $result = $GLOBALS['dbi']->query(
+ $sql_query, null, PMA_DatabaseInterface::QUERY_UNBUFFERED
+ );
+ $fields_cnt = $GLOBALS['dbi']->numFields($result);
+
+ // If required, get fields name at the first line
+ if (isset($GLOBALS[$what . '_columns'])) {
+ $text_output = "|------\n";
+ for ($i = 0; $i < $fields_cnt; $i++) {
+ $text_output .= '|'
+ . htmlspecialchars(
+ stripslashes($GLOBALS['dbi']->fieldName($result, $i))
+ );
+ } // end for
+ $text_output .= "\n|------\n";
+ if (! PMA_exportOutputHandler($text_output)) {
+ return false;
+ }
+ } // end if
+
+ // Format the data
+ while ($row = $GLOBALS['dbi']->fetchRow($result)) {
+ $text_output = '';
+ for ($j = 0; $j < $fields_cnt; $j++) {
+ if (! isset($row[$j]) || is_null($row[$j])) {
+ $value = $GLOBALS[$what . '_null'];
+ } elseif ($row[$j] == '0' || $row[$j] != '') {
+ $value = $row[$j];
+ } else {
+ $value = ' ';
+ }
+ $text_output .= '|'
+ . str_replace(
+ '|', '&#124;', htmlspecialchars($value)
+ );
+ } // end for
+ $text_output .= "\n";
+ if (! PMA_exportOutputHandler($text_output)) {
+ return false;
+ }
+ } // end while
+ $GLOBALS['dbi']->freeResult($result);
+
+ return true;
+ }
+
+ /**
+ * Returns a stand-in CREATE definition to resolve view dependencies
+ *
+ * @param string $db the database name
+ * @param string $view the view name
+ * @param string $crlf the end of line sequence
+ *
+ * @return string resulting definition
+ */
+ function getTableDefStandIn($db, $view, $crlf)
+ {
+ $text_output = '';
+
+ /**
+ * Get the unique keys in the table
+ */
+ $unique_keys = array();
+ $keys = $GLOBALS['dbi']->getTableIndexes($db, $view);
+ foreach ($keys as $key) {
+ if ($key['Non_unique'] == 0) {
+ $unique_keys[] = $key['Column_name'];
+ }
+ }
+
+ /**
+ * Gets fields properties
+ */
+ $GLOBALS['dbi']->selectDb($db);
+
+ /**
+ * Displays the table structure
+ */
+
+ $text_output .= "|------\n"
+ . '|' . __('Column')
+ . '|' . __('Type')
+ . '|' . __('Null')
+ . '|' . __('Default')
+ . "\n|------\n";
+
+ $columns = $GLOBALS['dbi']->getColumns($db, $view);
+ foreach ($columns as $column) {
+ $text_output .= $this->formatOneColumnDefinition($column, $unique_keys);
+ $text_output .= "\n";
+ } // end foreach
+
+ return $text_output;
+ }
+
+ /**
+ * Returns $table's CREATE definition
+ *
+ * @param string $db the database name
+ * @param string $table the table name
+ * @param string $crlf the end of line sequence
+ * @param string $error_url the url to go back in case of error
+ * @param bool $do_relation whether to include relation comments
+ * @param bool $do_comments whether to include the pmadb-style column
+ * comments as comments in the structure;
+ * this is deprecated but the parameter is
+ * left here because export.php calls
+ * $this->exportStructure() also for other
+ * export types which use this parameter
+ * @param bool $do_mime whether to include mime comments
+ * @param bool $show_dates whether to include creation/update/check dates
+ * @param bool $add_semicolon whether to add semicolon and end-of-line
+ * at the end
+ * @param bool $view whether we're handling a view
+ *
+ * @return string resulting schema
+ */
+ function getTableDef(
+ $db,
+ $table,
+ $crlf,
+ $error_url,
+ $do_relation,
+ $do_comments,
+ $do_mime,
+ $show_dates = false,
+ $add_semicolon = true,
+ $view = false
+ ) {
+ global $cfgRelation;
+
+ $text_output = '';
+
+ /**
+ * Get the unique keys in the table
+ */
+ $unique_keys = array();
+ $keys = $GLOBALS['dbi']->getTableIndexes($db, $table);
+ foreach ($keys as $key) {
+ if ($key['Non_unique'] == 0) {
+ $unique_keys[] = $key['Column_name'];
+ }
+ }
+
+ /**
+ * Gets fields properties
+ */
+ $GLOBALS['dbi']->selectDb($db);
+
+ // Check if we can use Relations
+ if ($do_relation && ! empty($cfgRelation['relation'])) {
+ // Find which tables are related with the current one and write it in
+ // an array
+ $res_rel = PMA_getForeigners($db, $table);
+
+ if ($res_rel && count($res_rel) > 0) {
+ $have_rel = true;
+ } else {
+ $have_rel = false;
+ }
+ } else {
+ $have_rel = false;
+ } // end if
+
+ /**
+ * Displays the table structure
+ */
+
+ $columns_cnt = 4;
+ if ($do_relation && $have_rel) {
+ $columns_cnt++;
+ }
+ if ($do_comments && $cfgRelation['commwork']) {
+ $columns_cnt++;
+ }
+ if ($do_mime && $cfgRelation['mimework']) {
+ $columns_cnt++;
+ }
+
+ $text_output .= "|------\n";
+ $text_output .= '|' . __('Column');
+ $text_output .= '|' . __('Type');
+ $text_output .= '|' . __('Null');
+ $text_output .= '|' . __('Default');
+ if ($do_relation && $have_rel) {
+ $text_output .= '|' . __('Links to');
+ }
+ if ($do_comments) {
+ $text_output .= '|' . __('Comments');
+ $comments = PMA_getComments($db, $table);
+ }
+ if ($do_mime && $cfgRelation['mimework']) {
+ $text_output .= '|' . htmlspecialchars('MIME');
+ $mime_map = PMA_getMIME($db, $table, true);
+ }
+ $text_output .= "\n|------\n";
+
+ $columns = $GLOBALS['dbi']->getColumns($db, $table);
+ foreach ($columns as $column) {
+ $text_output .= $this->formatOneColumnDefinition($column, $unique_keys);
+ $field_name = $column['Field'];
+
+ if ($do_relation && $have_rel) {
+ $text_output .= '|'
+ . (isset($res_rel[$field_name])
+ ? htmlspecialchars(
+ $res_rel[$field_name]['foreign_table']
+ . ' (' . $res_rel[$field_name]['foreign_field'] . ')'
+ )
+ : '');
+ }
+ if ($do_comments && $cfgRelation['commwork']) {
+ $text_output .= '|'
+ . (isset($comments[$field_name])
+ ? htmlspecialchars($comments[$field_name])
+ : '');
+ }
+ if ($do_mime && $cfgRelation['mimework']) {
+ $text_output .= '|'
+ . (isset($mime_map[$field_name])
+ ? htmlspecialchars(
+ str_replace('_', '/', $mime_map[$field_name]['mimetype'])
+ )
+ : '');
+ }
+
+ $text_output .= "\n";
+ } // end foreach
+
+ return $text_output;
+ } // end of the '$this->getTableDef()' function
+
+ /**
+ * Outputs triggers
+ *
+ * @param string $db database name
+ * @param string $table table name
+ *
+ * @return string Formatted triggers list
+ */
+ function getTriggers($db, $table)
+ {
+ $text_output = "|------\n";
+ $text_output .= '|' . __('Column');
+ $dump = "|------\n";
+ $dump .= '|' . __('Name');
+ $dump .= '|' . __('Time');
+ $dump .= '|' . __('Event');
+ $dump .= '|' . __('Definition');
+ $dump .= "\n|------\n";
+
+ $triggers = $GLOBALS['dbi']->getTriggers($db, $table);
+
+ foreach ($triggers as $trigger) {
+ $dump .= '|' . $trigger['name'];
+ $dump .= '|' . $trigger['action_timing'];
+ $dump .= '|' . $trigger['event_manipulation'];
+ $dump .= '|' .
+ str_replace(
+ '|',
+ '&#124;',
+ htmlspecialchars($trigger['definition'])
+ );
+ $dump .= "\n";
+ }
+
+ return $dump;
+ }
+
+ /**
+ * Outputs table's structure
+ *
+ * @param string $db database name
+ * @param string $table table name
+ * @param string $crlf the end of line sequence
+ * @param string $error_url the url to go back in case of error
+ * @param string $export_mode 'create_table', 'triggers', 'create_view',
+ * 'stand_in'
+ * @param string $export_type 'server', 'database', 'table'
+ * @param bool $do_relation whether to include relation comments
+ * @param bool $do_comments whether to include the pmadb-style column
+ * comments as comments in the structure;
+ * this is deprecated but the parameter is
+ * left here because export.php calls
+ * $this->exportStructure() also for other
+ * export types which use this parameter
+ * @param bool $do_mime whether to include mime comments
+ * @param bool $dates whether to include creation/update/check dates
+ *
+ * @return bool Whether it succeeded
+ */
+ function exportStructure(
+ $db,
+ $table,
+ $crlf,
+ $error_url,
+ $export_mode,
+ $export_type,
+ $do_relation = false,
+ $do_comments = false,
+ $do_mime = false,
+ $dates = false
+ ) {
+ $dump = '';
+
+ switch($export_mode) {
+ case 'create_table':
+ $dump .= '== ' . __('Table structure for table') . ' ' .$table . "\n\n";
+ $dump .= $this->getTableDef(
+ $db, $table, $crlf, $error_url, $do_relation, $do_comments,
+ $do_mime, $dates
+ );
+ break;
+ case 'triggers':
+ $dump = '';
+ $triggers = $GLOBALS['dbi']->getTriggers($db, $table);
+ if ($triggers) {
+ $dump .= '== ' . __('Triggers') . ' ' .$table . "\n\n";
+ $dump .= $this->getTriggers($db, $table);
+ }
+ break;
+ case 'create_view':
+ $dump .= '== ' . __('Structure for view') . ' ' .$table . "\n\n";
+ $dump .= $this->getTableDef(
+ $db, $table, $crlf, $error_url, $do_relation, $do_comments,
+ $do_mime, $dates, true, true
+ );
+ break;
+ case 'stand_in':
+ $dump .= '== ' . __('Stand-in structure for view')
+ . ' ' .$table . "\n\n";
+ // export a stand-in definition to resolve view dependencies
+ $dump .= $this->getTableDefStandIn($db, $table, $crlf);
+ } // end switch
+
+ return PMA_exportOutputHandler($dump);
+ }
+
+ /**
+ * Formats the definition for one column
+ *
+ * @param array $column info about this column
+ * @param array $unique_keys unique keys for this table
+ *
+ * @return string Formatted column definition
+ */
+ function formatOneColumnDefinition(
+ $column, $unique_keys
+ ) {
+ $extracted_columnspec
+ = PMA_Util::extractColumnSpec($column['Type']);
+ $type = $extracted_columnspec['print_type'];
+ if (empty($type)) {
+ $type = '&nbsp;';
+ }
+
+ if (! isset($column['Default'])) {
+ if ($column['Null'] != 'NO') {
+ $column['Default'] = 'NULL';
+ }
+ }
+
+ $fmt_pre = '';
+ $fmt_post = '';
+ if (in_array($column['Field'], $unique_keys)) {
+ $fmt_pre = '**' . $fmt_pre;
+ $fmt_post = $fmt_post . '**';
+ }
+ if ($column['Key']=='PRI') {
+ $fmt_pre = '//' . $fmt_pre;
+ $fmt_post = $fmt_post . '//';
+ }
+ $definition = '|'
+ . $fmt_pre . htmlspecialchars($column['Field']) . $fmt_post;
+ $definition .= '|' . htmlspecialchars($type);
+ $definition .= '|'
+ . (($column['Null'] == '' || $column['Null'] == 'NO')
+ ? __('No') : __('Yes'));
+ $definition .= '|'
+ . htmlspecialchars(
+ isset($column['Default']) ? $column['Default'] : ''
+ );
+ return $definition;
+ }
+}
+?> \ No newline at end of file
diff --git a/libraries/plugins/export/ExportXml.class.php b/libraries/plugins/export/ExportXml.class.php
new file mode 100644
index 0000000000..e36b1103e2
--- /dev/null
+++ b/libraries/plugins/export/ExportXml.class.php
@@ -0,0 +1,546 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Set of functions used to build XML dumps of tables
+ *
+ * @package PhpMyAdmin-Export
+ * @subpackage XML
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+if (! strlen($GLOBALS['db'])) { /* Can't do server export */
+ $GLOBALS['skip_import'] = true;
+ return;
+}
+
+/* Get the export interface */
+require_once 'libraries/plugins/ExportPlugin.class.php';
+
+/**
+ * Handles the export for the XML class
+ *
+ * @package PhpMyAdmin-Export
+ * @subpackage XML
+ */
+class ExportXml extends ExportPlugin
+{
+ /**
+ * Table name
+ *
+ * @var string
+ */
+ private $_table;
+
+ /**
+ * Table names
+ *
+ * @var array
+ */
+ private $_tables;
+
+ /**
+ * Constructor
+ */
+ public function __construct()
+ {
+ $this->setProperties();
+ }
+
+ /**
+ * Initialize the local variables that are used for export PDF
+ *
+ * @return void
+ */
+ protected function initSpecificVariables()
+ {
+ global $table, $tables;
+ $this->_setTable($table);
+ $this->_setTables($tables);
+ }
+
+ /**
+ * Sets the export XML properties
+ *
+ * @return void
+ */
+ protected function setProperties()
+ {
+ $props = 'libraries/properties/';
+ include_once "$props/plugins/ExportPluginProperties.class.php";
+ include_once "$props/options/groups/OptionsPropertyRootGroup.class.php";
+ include_once "$props/options/groups/OptionsPropertyMainGroup.class.php";
+ include_once "$props/options/items/HiddenPropertyItem.class.php";
+ include_once "$props/options/items/BoolPropertyItem.class.php";
+
+ // create the export plugin property item
+ $exportPluginProperties = new ExportPluginProperties();
+ $exportPluginProperties->setText('XML');
+ $exportPluginProperties->setExtension('xml');
+ $exportPluginProperties->setMimeType('text/xml');
+ $exportPluginProperties->setOptionsText(__('Options'));
+
+ // create the root group that will be the options field for
+ // $exportPluginProperties
+ // this will be shown as "Format specific options"
+ $exportSpecificOptions = new OptionsPropertyRootGroup();
+ $exportSpecificOptions->setName("Format Specific Options");
+
+ // general options main group
+ $generalOptions = new OptionsPropertyMainGroup();
+ $generalOptions->setName("general_opts");
+ // create primary items and add them to the group
+ $leaf = new HiddenPropertyItem();
+ $leaf->setName("structure_or_data");
+ $generalOptions->addProperty($leaf);
+ // add the main group to the root group
+ $exportSpecificOptions->addProperty($generalOptions);
+
+ // export structure main group
+ $structure = new OptionsPropertyMainGroup();
+ $structure->setName("structure");
+ $structure->setText(__('Object creation options (all are recommended)'));
+ // create primary items and add them to the group
+ if (! PMA_DRIZZLE) {
+ $leaf = new BoolPropertyItem();
+ $leaf->setName("export_functions");
+ $leaf->setText(__('Functions'));
+ $structure->addProperty($leaf);
+ $leaf = new BoolPropertyItem();
+ $leaf->setName("export_procedures");
+ $leaf->setText(__('Procedures'));
+ $structure->addProperty($leaf);
+ }
+ $leaf = new BoolPropertyItem();
+ $leaf->setName("export_tables");
+ $leaf->setText(__('Tables'));
+ $structure->addProperty($leaf);
+ if (! PMA_DRIZZLE) {
+ $leaf = new BoolPropertyItem();
+ $leaf->setName("export_triggers");
+ $leaf->setText(__('Triggers'));
+ $structure->addProperty($leaf);
+ $leaf = new BoolPropertyItem();
+ $leaf->setName("export_views");
+ $leaf->setText(__('Views'));
+ $structure->addProperty($leaf);
+ }
+ $exportSpecificOptions->addProperty($structure);
+
+ // data main group
+ $data = new OptionsPropertyMainGroup();
+ $data->setName("data");
+ $data->setText(__('Data dump options'));
+ // create primary items and add them to the group
+ $leaf = new BoolPropertyItem();
+ $leaf->setName("export_contents");
+ $leaf->setText(__('Export contents'));
+ $data->addProperty($leaf);
+ $exportSpecificOptions->addProperty($data);
+
+ // set the options for the export plugin property item
+ $exportPluginProperties->setOptions($exportSpecificOptions);
+ $this->properties = $exportPluginProperties;
+ }
+
+ /**
+ * This method is called when any PluginManager to which the observer
+ * is attached calls PluginManager::notify()
+ *
+ * @param SplSubject $subject The PluginManager notifying the observer
+ * of an update.
+ *
+ * @return void
+ */
+ public function update (SplSubject $subject)
+ {
+ }
+
+ /**
+ * Outputs export header. It is the first method to be called, so all
+ * the required variables are initialized here.
+ *
+ * @return bool Whether it succeeded
+ */
+ public function exportHeader ()
+ {
+ $this->initSpecificVariables();
+ global $crlf, $cfg, $db;
+ $table = $this->_getTable();
+ $tables = $this->_getTables();
+
+ $export_struct = isset($GLOBALS['xml_export_functions'])
+ || isset($GLOBALS['xml_export_procedures'])
+ || isset($GLOBALS['xml_export_tables'])
+ || isset($GLOBALS['xml_export_triggers'])
+ || isset($GLOBALS['xml_export_views']);
+ $export_data = isset($GLOBALS['xml_export_contents']) ? true : false;
+
+ if ($GLOBALS['output_charset_conversion']) {
+ $charset = $GLOBALS['charset_of_file'];
+ } else {
+ $charset = 'utf-8';
+ }
+
+ $head = '<?xml version="1.0" encoding="' . $charset . '"?>' . $crlf
+ . '<!--' . $crlf
+ . '- phpMyAdmin XML Dump' . $crlf
+ . '- version ' . PMA_VERSION . $crlf
+ . '- http://www.phpmyadmin.net' . $crlf
+ . '-' . $crlf
+ . '- ' . __('Host:') . ' ' . $cfg['Server']['host'];
+ if (! empty($cfg['Server']['port'])) {
+ $head .= ':' . $cfg['Server']['port'];
+ }
+ $head .= $crlf
+ . '- ' . __('Generation Time:') . ' '
+ . PMA_Util::localisedDate() . $crlf
+ . '- ' . __('Server version:') . ' ' . PMA_MYSQL_STR_VERSION . $crlf
+ . '- ' . __('PHP Version:') . ' ' . phpversion() . $crlf
+ . '-->' . $crlf . $crlf;
+
+ $head .= '<pma_xml_export version="1.0"'
+ . (($export_struct)
+ ? ' xmlns:pma="http://www.phpmyadmin.net/some_doc_url/"'
+ : '')
+ . '>' . $crlf;
+
+ if ($export_struct) {
+ if (PMA_DRIZZLE) {
+ $result = $GLOBALS['dbi']->fetchResult(
+ "SELECT
+ 'utf8' AS DEFAULT_CHARACTER_SET_NAME,
+ DEFAULT_COLLATION_NAME
+ FROM data_dictionary.SCHEMAS
+ WHERE SCHEMA_NAME = '"
+ . PMA_Util::sqlAddSlashes($db) . "'"
+ );
+ } else {
+ $result = $GLOBALS['dbi']->fetchResult(
+ 'SELECT `DEFAULT_CHARACTER_SET_NAME`, `DEFAULT_COLLATION_NAME`'
+ . ' FROM `information_schema`.`SCHEMATA` WHERE `SCHEMA_NAME`'
+ . ' = \''.PMA_Util::sqlAddSlashes($db).'\' LIMIT 1'
+ );
+ }
+ $db_collation = $result[0]['DEFAULT_COLLATION_NAME'];
+ $db_charset = $result[0]['DEFAULT_CHARACTER_SET_NAME'];
+
+ $head .= ' <!--' . $crlf;
+ $head .= ' - Structure schemas' . $crlf;
+ $head .= ' -->' . $crlf;
+ $head .= ' <pma:structure_schemas>' . $crlf;
+ $head .= ' <pma:database name="' . htmlspecialchars($db)
+ . '" collation="' . $db_collation . '" charset="' . $db_charset
+ . '">' . $crlf;
+
+ if (count($tables) == 0) {
+ $tables[] = $table;
+ }
+
+ foreach ($tables as $table) {
+ // Export tables and views
+ $result = $GLOBALS['dbi']->fetchResult(
+ 'SHOW CREATE TABLE ' . PMA_Util::backquote($db) . '.'
+ . PMA_Util::backquote($table),
+ 0
+ );
+ $tbl = $result[$table][1];
+
+ $is_view = PMA_Table::isView($db, $table);
+
+ if ($is_view) {
+ $type = 'view';
+ } else {
+ $type = 'table';
+ }
+
+ if ($is_view && ! isset($GLOBALS['xml_export_views'])) {
+ continue;
+ }
+
+ if (! $is_view && ! isset($GLOBALS['xml_export_tables'])) {
+ continue;
+ }
+
+ $head .= ' <pma:' . $type . ' name="' . $table . '">'
+ . $crlf;
+
+ $tbl = " " . htmlspecialchars($tbl);
+ $tbl = str_replace("\n", "\n ", $tbl);
+
+ $head .= $tbl . ';' . $crlf;
+ $head .= ' </pma:' . $type . '>' . $crlf;
+
+ if (isset($GLOBALS['xml_export_triggers'])
+ && $GLOBALS['xml_export_triggers']
+ ) {
+ // Export triggers
+ $triggers = $GLOBALS['dbi']->getTriggers($db, $table);
+ if ($triggers) {
+ foreach ($triggers as $trigger) {
+ $code = $trigger['create'];
+ $head .= ' <pma:trigger name="'
+ . $trigger['name'] . '">' . $crlf;
+
+ // Do some formatting
+ $code = substr(rtrim($code), 0, -3);
+ $code = " " . htmlspecialchars($code);
+ $code = str_replace("\n", "\n ", $code);
+
+ $head .= $code . $crlf;
+ $head .= ' </pma:trigger>' . $crlf;
+ }
+
+ unset($trigger);
+ unset($triggers);
+ }
+ }
+ }
+
+ if (isset($GLOBALS['xml_export_functions'])
+ && $GLOBALS['xml_export_functions']
+ ) {
+ // Export functions
+ $functions = $GLOBALS['dbi']->getProceduresOrFunctions(
+ $db, 'FUNCTION'
+ );
+ if ($functions) {
+ foreach ($functions as $function) {
+ $head .= ' <pma:function name="'
+ . $function . '">' . $crlf;
+
+ // Do some formatting
+ $sql = $GLOBALS['dbi']->getDefinition(
+ $db, 'FUNCTION', $function
+ );
+ $sql = rtrim($sql);
+ $sql = " " . htmlspecialchars($sql);
+ $sql = str_replace("\n", "\n ", $sql);
+
+ $head .= $sql . $crlf;
+ $head .= ' </pma:function>' . $crlf;
+ }
+
+ unset($function);
+ unset($functions);
+ }
+ }
+
+ if (isset($GLOBALS['xml_export_procedures'])
+ && $GLOBALS['xml_export_procedures']
+ ) {
+ // Export procedures
+ $procedures = $GLOBALS['dbi']->getProceduresOrFunctions(
+ $db, 'PROCEDURE'
+ );
+ if ($procedures) {
+ foreach ($procedures as $procedure) {
+ $head .= ' <pma:procedure name="'
+ . $procedure . '">' . $crlf;
+
+ // Do some formatting
+ $sql = $GLOBALS['dbi']->getDefinition(
+ $db, 'PROCEDURE', $procedure
+ );
+ $sql = rtrim($sql);
+ $sql = " " . htmlspecialchars($sql);
+ $sql = str_replace("\n", "\n ", $sql);
+
+ $head .= $sql . $crlf;
+ $head .= ' </pma:procedure>' . $crlf;
+ }
+
+ unset($procedure);
+ unset($procedures);
+ }
+ }
+
+ unset($result);
+
+ $head .= ' </pma:database>' . $crlf;
+ $head .= ' </pma:structure_schemas>' . $crlf;
+
+ if ($export_data) {
+ $head .= $crlf;
+ }
+ }
+
+ return PMA_exportOutputHandler($head);
+ }
+
+ /**
+ * Outputs export footer
+ *
+ * @return bool Whether it succeeded
+ */
+ public function exportFooter ()
+ {
+ $foot = '</pma_xml_export>';
+
+ return PMA_exportOutputHandler($foot);
+ }
+
+ /**
+ * Outputs database header
+ *
+ * @param string $db Database name
+ *
+ * @return bool Whether it succeeded
+ */
+ public function exportDBHeader ($db)
+ {
+ global $crlf;
+
+ if (isset($GLOBALS['xml_export_contents'])
+ && $GLOBALS['xml_export_contents']
+ ) {
+ $head = ' <!--' . $crlf
+ . ' - ' . __('Database:') . ' ' . '\'' . $db . '\'' . $crlf
+ . ' -->' . $crlf
+ . ' <database name="' . htmlspecialchars($db) . '">' . $crlf;
+
+ return PMA_exportOutputHandler($head);
+ } else {
+ return true;
+ }
+ }
+
+ /**
+ * Outputs database footer
+ *
+ * @param string $db Database name
+ *
+ * @return bool Whether it succeeded
+ */
+ public function exportDBFooter ($db)
+ {
+ global $crlf;
+
+ if (isset($GLOBALS['xml_export_contents'])
+ && $GLOBALS['xml_export_contents']
+ ) {
+ return PMA_exportOutputHandler(' </database>' . $crlf);
+ } else {
+ return true;
+ }
+ }
+
+ /**
+ * Outputs CREATE DATABASE statement
+ *
+ * @param string $db Database name
+ *
+ * @return bool Whether it succeeded
+ */
+ public function exportDBCreate($db)
+ {
+ return true;
+ }
+
+ /**
+ * Outputs the content of a table in XML format
+ *
+ * @param string $db database name
+ * @param string $table table name
+ * @param string $crlf the end of line sequence
+ * @param string $error_url the url to go back in case of error
+ * @param string $sql_query SQL query for obtaining data
+ *
+ * @return bool Whether it succeeded
+ */
+ public function exportData ($db, $table, $crlf, $error_url, $sql_query)
+ {
+ if (isset($GLOBALS['xml_export_contents'])
+ && $GLOBALS['xml_export_contents']
+ ) {
+ $result = $GLOBALS['dbi']->query(
+ $sql_query, null, PMA_DatabaseInterface::QUERY_UNBUFFERED
+ );
+
+ $columns_cnt = $GLOBALS['dbi']->numFields($result);
+ $columns = array();
+ for ($i = 0; $i < $columns_cnt; $i++) {
+ $columns[$i] = stripslashes($GLOBALS['dbi']->fieldName($result, $i));
+ }
+ unset($i);
+
+ $buffer = ' <!-- ' . __('Table') . ' ' . $table . ' -->' . $crlf;
+ if (! PMA_exportOutputHandler($buffer)) {
+ return false;
+ }
+
+ while ($record = $GLOBALS['dbi']->fetchRow($result)) {
+ $buffer = ' <table name="'
+ . htmlspecialchars($table) . '">' . $crlf;
+ for ($i = 0; $i < $columns_cnt; $i++) {
+ // If a cell is NULL, still export it to preserve
+ // the XML structure
+ if (! isset($record[$i]) || is_null($record[$i])) {
+ $record[$i] = 'NULL';
+ }
+ $buffer .= ' <column name="'
+ . htmlspecialchars($columns[$i]) . '">'
+ . htmlspecialchars((string)$record[$i])
+ . '</column>' . $crlf;
+ }
+ $buffer .= ' </table>' . $crlf;
+
+ if (! PMA_exportOutputHandler($buffer)) {
+ return false;
+ }
+ }
+ $GLOBALS['dbi']->freeResult($result);
+ }
+
+ return true;
+ }
+
+
+ /* ~~~~~~~~~~~~~~~~~~~~ Getters and Setters ~~~~~~~~~~~~~~~~~~~~ */
+
+
+ /**
+ * Gets the table name
+ *
+ * @return string
+ */
+ private function _getTable()
+ {
+ return $this->_table;
+ }
+
+ /**
+ * Sets the table name
+ *
+ * @param string $table table name
+ *
+ * @return void
+ */
+ private function _setTable($table)
+ {
+ $this->_table = $table;
+ }
+
+ /**
+ * Gets the table names
+ *
+ * @return array
+ */
+ private function _getTables()
+ {
+ return $this->_tables;
+ }
+
+ /**
+ * Sets the table names
+ *
+ * @param array $tables table names
+ *
+ * @return void
+ */
+ private function _setTables($tables)
+ {
+ $this->_tables = $tables;
+ }
+}
+?>
diff --git a/libraries/plugins/export/ExportYaml.class.php b/libraries/plugins/export/ExportYaml.class.php
new file mode 100644
index 0000000000..a59fb3513f
--- /dev/null
+++ b/libraries/plugins/export/ExportYaml.class.php
@@ -0,0 +1,216 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Set of functions used to build YAML dumps of tables
+ *
+ * @package PhpMyAdmin-Export
+ * @subpackage YAML
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/* Get the export interface */
+require_once 'libraries/plugins/ExportPlugin.class.php';
+
+/**
+ * Handles the export for the YAML format
+ *
+ * @package PhpMyAdmin-Export
+ * @subpackage YAML
+ */
+class ExportYaml extends ExportPlugin
+{
+ /**
+ * Constructor
+ */
+ public function __construct()
+ {
+ $this->setProperties();
+ }
+
+ /**
+ * Sets the export YAML properties
+ *
+ * @return void
+ */
+ protected function setProperties()
+ {
+ $props = 'libraries/properties/';
+ include_once "$props/plugins/ExportPluginProperties.class.php";
+ include_once "$props/options/groups/OptionsPropertyRootGroup.class.php";
+ include_once "$props/options/groups/OptionsPropertyMainGroup.class.php";
+ include_once "$props/options/items/HiddenPropertyItem.class.php";
+
+ $exportPluginProperties = new ExportPluginProperties();
+ $exportPluginProperties->setText('YAML');
+ $exportPluginProperties->setExtension('yml');
+ $exportPluginProperties->setMimeType('text/yaml');
+ $exportPluginProperties->setForceFile(true);
+ $exportPluginProperties->setOptionsText(__('Options'));
+
+ // create the root group that will be the options field for
+ // $exportPluginProperties
+ // this will be shown as "Format specific options"
+ $exportSpecificOptions = new OptionsPropertyRootGroup();
+ $exportSpecificOptions->setName("Format Specific Options");
+
+ // general options main group
+ $generalOptions = new OptionsPropertyMainGroup();
+ $generalOptions->setName("general_opts");
+ // create primary items and add them to the group
+ $leaf = new HiddenPropertyItem();
+ $leaf->setName("structure_or_data");
+ $generalOptions->addProperty($leaf);
+ // add the main group to the root group
+ $exportSpecificOptions->addProperty($generalOptions);
+
+ // set the options for the export plugin property item
+ $exportPluginProperties->setOptions($exportSpecificOptions);
+ $this->properties = $exportPluginProperties;
+ }
+
+ /**
+ * This method is called when any PluginManager to which the observer
+ * is attached calls PluginManager::notify()
+ *
+ * @param SplSubject $subject The PluginManager notifying the observer
+ * of an update.
+ *
+ * @return void
+ */
+ public function update (SplSubject $subject)
+ {
+ }
+
+ /**
+ * Outputs export header
+ *
+ * @return bool Whether it succeeded
+ */
+ public function exportHeader ()
+ {
+ PMA_exportOutputHandler(
+ '%YAML 1.1' . $GLOBALS['crlf'] . '---' . $GLOBALS['crlf']
+ );
+ return true;
+ }
+
+ /**
+ * Outputs export footer
+ *
+ * @return bool Whether it succeeded
+ */
+ public function exportFooter ()
+ {
+ PMA_exportOutputHandler('...' . $GLOBALS['crlf']);
+ return true;
+ }
+
+ /**
+ * Outputs database header
+ *
+ * @param string $db Database name
+ *
+ * @return bool Whether it succeeded
+ */
+ public function exportDBHeader ($db)
+ {
+ return true;
+ }
+
+ /**
+ * Outputs database footer
+ *
+ * @param string $db Database name
+ *
+ * @return bool Whether it succeeded
+ */
+ public function exportDBFooter ($db)
+ {
+ return true;
+ }
+
+ /**
+ * Outputs CREATE DATABASE statement
+ *
+ * @param string $db Database name
+ *
+ * @return bool Whether it succeeded
+ */
+ public function exportDBCreate($db)
+ {
+ return true;
+ }
+
+ /**
+ * Outputs the content of a table in JSON format
+ *
+ * @param string $db database name
+ * @param string $table table name
+ * @param string $crlf the end of line sequence
+ * @param string $error_url the url to go back in case of error
+ * @param string $sql_query SQL query for obtaining data
+ *
+ * @return bool Whether it succeeded
+ */
+ public function exportData($db, $table, $crlf, $error_url, $sql_query)
+ {
+ $result = $GLOBALS['dbi']->query(
+ $sql_query, null, PMA_DatabaseInterface::QUERY_UNBUFFERED
+ );
+
+ $columns_cnt = $GLOBALS['dbi']->numFields($result);
+ for ($i = 0; $i < $columns_cnt; $i++) {
+ $columns[$i] = stripslashes($GLOBALS['dbi']->fieldName($result, $i));
+ }
+ unset($i);
+
+ $buffer = '';
+ $record_cnt = 0;
+ while ($record = $GLOBALS['dbi']->fetchRow($result)) {
+ $record_cnt++;
+
+ // Output table name as comment if this is the first record of the table
+ if ($record_cnt == 1) {
+ $buffer = '# ' . $db . '.' . $table . $crlf;
+ $buffer .= '-' . $crlf;
+ } else {
+ $buffer = '-' . $crlf;
+ }
+
+ for ($i = 0; $i < $columns_cnt; $i++) {
+ if (! isset($record[$i])) {
+ continue;
+ }
+
+ $column = $columns[$i];
+
+ if (is_null($record[$i])) {
+ $buffer .= ' ' . $column . ': null' . $crlf;
+ continue;
+ }
+
+ if (is_numeric($record[$i])) {
+ $buffer .= ' ' . $column . ': ' . $record[$i] . $crlf;
+ continue;
+ }
+
+ $record[$i] = str_replace(
+ array('\\', '"', "\n", "\r"),
+ array('\\\\', '\"', '\n', '\r'),
+ $record[$i]
+ );
+ $buffer .= ' ' . $column . ': "' . $record[$i] . '"' . $crlf;
+ }
+
+ if (! PMA_exportOutputHandler($buffer)) {
+ return false;
+ }
+ }
+ $GLOBALS['dbi']->freeResult($result);
+
+ return true;
+ } // end getTableYAML
+}
+?> \ No newline at end of file
diff --git a/libraries/plugins/export/PMA_ExportPdf.class.php b/libraries/plugins/export/PMA_ExportPdf.class.php
new file mode 100644
index 0000000000..29c823d816
--- /dev/null
+++ b/libraries/plugins/export/PMA_ExportPdf.class.php
@@ -0,0 +1,426 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * TableProperty class
+ *
+ * @package PhpMyAdmin-Export
+ * @subpackage PDF
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/* Get the PDF class */
+require_once 'libraries/PDF.class.php';
+
+/**
+ * Adapted from a LGPL script by Philip Clarke
+ *
+ * @package PhpMyAdmin-Export
+ * @subpackage PDF
+ */
+class PMA_ExportPdf extends PMA_PDF
+{
+ var $tablewidths;
+ var $headerset;
+
+ /**
+ * Add page if needed.
+ *
+ * @param float $h cell height. Default value: 0
+ * @param mixed $y starting y position, leave empty for current position
+ * @param boolean $addpage if true add a page, otherwise only return
+ * the true/false state
+ *
+ * @return boolean true in case of page break, false otherwise.
+ */
+ function checkPageBreak($h = 0, $y = '', $addpage = true)
+ {
+ if (TCPDF_STATIC::empty_string($y)) {
+ $y = $this->y;
+ }
+ $current_page = $this->page;
+ if ((($y + $h) > $this->PageBreakTrigger)
+ AND (! $this->InFooter)
+ AND ($this->AcceptPageBreak())
+ ) {
+ if ($addpage) {
+ //Automatic page break
+ $x = $this->x;
+ $this->AddPage($this->CurOrientation);
+ $this->y = $this->dataY;
+ $oldpage = $this->page - 1;
+
+ $this_page_orm = $this->pagedim[$this->page]['orm'];
+ $old_page_orm = $this->pagedim[$oldpage]['orm'];
+ $this_page_olm = $this->pagedim[$this->page]['olm'];
+ $old_page_olm = $this->pagedim[$oldpage]['olm'];
+ if ($this->rtl) {
+ if ($this_page_orm!= $old_page_orm) {
+ $this->x = $x - ($this_page_orm - $old_page_orm);
+ } else {
+ $this->x = $x;
+ }
+ } else {
+ if ($this_page_olm != $old_page_olm) {
+ $this->x = $x + ($this_page_olm - $old_page_olm);
+ } else {
+ $this->x = $x;
+ }
+ }
+ }
+ return true;
+ }
+ if ($current_page != $this->page) {
+ // account for columns mode
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * This method is used to render the page header.
+ *
+ * @return void
+ */
+ function Header()
+ {
+ global $maxY;
+ // We don't want automatic page breaks while generating header
+ // as this can lead to infinite recursion as auto generated page
+ // will want header as well causing another page break
+ // FIXME: Better approach might be to try to compact the content
+ $this->SetAutoPageBreak(false);
+ // Check if header for this page already exists
+ if (! isset($this->headerset[$this->page])) {
+ $fullwidth = 0;
+ foreach ($this->tablewidths as $width) {
+ $fullwidth += $width;
+ }
+ $this->SetY(($this->tMargin) - ($this->FontSizePt / $this->k) * 5);
+ $this->cellFontSize = $this->FontSizePt ;
+ $this->SetFont(
+ PMA_PDF_FONT,
+ '',
+ ($this->titleFontSize
+ ? $this->titleFontSize
+ : $this->FontSizePt)
+ );
+ $this->Cell(0, $this->FontSizePt, $this->titleText, 0, 1, 'C');
+ $this->SetFont(PMA_PDF_FONT, '', $this->cellFontSize);
+ $this->SetY(($this->tMargin) - ($this->FontSizePt / $this->k) * 2.5);
+ $this->Cell(
+ 0,
+ $this->FontSizePt,
+ __('Database:') . ' ' . $this->currentDb . ', '
+ . __('Table:') . ' ' . $this->currentTable,
+ 0, 1, 'L'
+ );
+ $l = ($this->lMargin);
+ foreach ($this->colTitles as $col => $txt) {
+ $this->SetXY($l, ($this->tMargin));
+ $this->MultiCell(
+ $this->tablewidths[$col],
+ $this->FontSizePt,
+ $txt
+ );
+ $l += $this->tablewidths[$col] ;
+ $maxY = ($maxY < $this->getY()) ? $this->getY() : $maxY ;
+ }
+ $this->SetXY($this->lMargin, $this->tMargin);
+ $this->setFillColor(200, 200, 200);
+ $l = ($this->lMargin);
+ foreach ($this->colTitles as $col => $txt) {
+ $this->SetXY($l, $this->tMargin);
+ $this->cell(
+ $this->tablewidths[$col],
+ $maxY-($this->tMargin),
+ '',
+ 1,
+ 0,
+ 'L',
+ 1
+ );
+ $this->SetXY($l, $this->tMargin);
+ $this->MultiCell(
+ $this->tablewidths[$col],
+ $this->FontSizePt,
+ $txt,
+ 0,
+ 'C'
+ );
+ $l += $this->tablewidths[$col];
+ }
+ $this->setFillColor(255, 255, 255);
+ // set headerset
+ $this->headerset[$this->page] = 1;
+ }
+
+ $this->dataY = $maxY;
+ $this->SetAutoPageBreak(true);
+ }
+
+ /**
+ * Generate table
+ *
+ * @param int $lineheight Height of line
+ *
+ * @return void
+ */
+ function morepagestable($lineheight = 8)
+ {
+ // some things to set and 'remember'
+ $l = $this->lMargin;
+ $startheight = $h = $this->dataY;
+ $startpage = $currpage = $this->page;
+
+ // calculate the whole width
+ $fullwidth = 0;
+ foreach ($this->tablewidths as $width) {
+ $fullwidth += $width;
+ }
+
+ // Now let's start to write the table
+ $row = 0;
+ $tmpheight = array();
+ $maxpage = $this->page;
+
+ while ($data = $GLOBALS['dbi']->fetchRow($this->results)) {
+ $this->page = $currpage;
+ // write the horizontal borders
+ $this->Line($l, $h, $fullwidth+$l, $h);
+ // write the content and remember the height of the highest col
+ foreach ($data as $col => $txt) {
+ $this->page = $currpage;
+ $this->SetXY($l, $h);
+ if ($this->tablewidths[$col] > 0) {
+ $this->MultiCell(
+ $this->tablewidths[$col],
+ $lineheight,
+ $txt,
+ 0,
+ $this->colAlign[$col]
+ );
+ $l += $this->tablewidths[$col];
+ }
+
+ if (! isset($tmpheight[$row.'-'.$this->page])) {
+ $tmpheight[$row.'-'.$this->page] = 0;
+ }
+ if ($tmpheight[$row.'-'.$this->page] < $this->GetY()) {
+ $tmpheight[$row.'-'.$this->page] = $this->GetY();
+ }
+ if ($this->page > $maxpage) {
+ $maxpage = $this->page;
+ }
+ unset($data[$col]);
+ }
+
+ // get the height we were in the last used page
+ $h = $tmpheight[$row.'-'.$maxpage];
+ // set the "pointer" to the left margin
+ $l = $this->lMargin;
+ // set the $currpage to the last page
+ $currpage = $maxpage;
+ unset($data[$row]);
+ $row++;
+ }
+ // draw the borders
+ // we start adding a horizontal line on the last page
+ $this->page = $maxpage;
+ $this->Line($l, $h, $fullwidth+$l, $h);
+ // now we start at the top of the document and walk down
+ for ($i = $startpage; $i <= $maxpage; $i++) {
+ $this->page = $i;
+ $l = $this->lMargin;
+ $t = ($i == $startpage) ? $startheight : $this->tMargin;
+ $lh = ($i == $maxpage) ? $h : $this->h-$this->bMargin;
+ $this->Line($l, $t, $l, $lh);
+ foreach ($this->tablewidths as $width) {
+ $l += $width;
+ $this->Line($l, $t, $l, $lh);
+ }
+ }
+ // set it to the last page, if not it'll cause some problems
+ $this->page = $maxpage;
+ }
+
+ /**
+ * Sets a set of attributes.
+ *
+ * @param array $attr array containing the attributes
+ *
+ * @return void
+ */
+ function setAttributes($attr = array())
+ {
+ foreach ($attr as $key => $val) {
+ $this->$key = $val ;
+ }
+ }
+
+ /**
+ * Defines the top margin.
+ * The method can be called before creating the first page.
+ *
+ * @param float $topMargin the margin
+ *
+ * @return void
+ */
+ function setTopMargin($topMargin)
+ {
+ $this->tMargin = $topMargin;
+ }
+
+ /**
+ * MySQL report
+ *
+ * @param string $query Query to execute
+ *
+ * @return void
+ */
+ function mysqlReport($query)
+ {
+ unset($this->tablewidths);
+ unset($this->colTitles);
+ unset($this->titleWidth);
+ unset($this->colFits);
+ unset($this->display_column);
+ unset($this->colAlign);
+
+ /**
+ * Pass 1 for column widths
+ */
+ $this->results = $GLOBALS['dbi']->query(
+ $query, null, PMA_DatabaseInterface::QUERY_UNBUFFERED
+ );
+ $this->numFields = $GLOBALS['dbi']->numFields($this->results);
+ $this->fields = $GLOBALS['dbi']->getFieldsMeta($this->results);
+
+ // sColWidth = starting col width (an average size width)
+ $availableWidth = $this->w - $this->lMargin - $this->rMargin;
+ $this->sColWidth = $availableWidth / $this->numFields;
+ $totalTitleWidth = 0;
+
+ // loop through results header and set initial
+ // col widths/ titles/ alignment
+ // if a col title is less than the starting col width,
+ // reduce that column size
+ $colFits = array();
+ for ($i = 0; $i < $this->numFields; $i++) {
+ $stringWidth = $this->getstringwidth($this->fields[$i]->name) + 6 ;
+ // save the real title's width
+ $titleWidth[$i] = $stringWidth;
+ $totalTitleWidth += $stringWidth;
+
+ // set any column titles less than the start width to
+ // the column title width
+ if ($stringWidth < $this->sColWidth) {
+ $colFits[$i] = $stringWidth ;
+ }
+ $this->colTitles[$i] = $this->fields[$i]->name;
+ $this->display_column[$i] = true;
+
+ switch ($this->fields[$i]->type) {
+ case 'int':
+ $this->colAlign[$i] = 'R';
+ break;
+ case 'blob':
+ case 'tinyblob':
+ case 'mediumblob':
+ case 'longblob':
+ /**
+ * @todo do not deactivate completely the display
+ * but show the field's name and [BLOB]
+ */
+ if (stristr($this->fields[$i]->flags, 'BINARY')) {
+ $this->display_column[$i] = false;
+ unset($this->colTitles[$i]);
+ }
+ $this->colAlign[$i] = 'L';
+ break;
+ default:
+ $this->colAlign[$i] = 'L';
+ }
+ }
+
+ // title width verification
+ if ($totalTitleWidth > $availableWidth) {
+ $adjustingMode = true;
+ } else {
+ $adjustingMode = false;
+ // we have enough space for all the titles at their
+ // original width so use the true title's width
+ foreach ($titleWidth as $key => $val) {
+ $colFits[$key] = $val;
+ }
+ }
+
+ // loop through the data; any column whose contents
+ // is greater than the column size is resized
+ /**
+ * @todo force here a LIMIT to avoid reading all rows
+ */
+ while ($row = $GLOBALS['dbi']->fetchRow($this->results)) {
+ foreach ($colFits as $key => $val) {
+ $stringWidth = $this->getstringwidth($row[$key]) + 6 ;
+ if ($adjustingMode && ($stringWidth > $this->sColWidth)) {
+ // any column whose data's width is bigger than
+ // the start width is now discarded
+ unset($colFits[$key]);
+ } else {
+ // if data's width is bigger than the current column width,
+ // enlarge the column (but avoid enlarging it if the
+ // data's width is very big)
+ if ($stringWidth > $val
+ && $stringWidth < ($this->sColWidth * 3)
+ ) {
+ $colFits[$key] = $stringWidth ;
+ }
+ }
+ }
+ }
+
+ $totAlreadyFitted = 0;
+ foreach ($colFits as $key => $val) {
+ // set fitted columns to smallest size
+ $this->tablewidths[$key] = $val;
+ // to work out how much (if any) space has been freed up
+ $totAlreadyFitted += $val;
+ }
+
+ if ($adjustingMode) {
+ $surplus = (sizeof($colFits) * $this->sColWidth) - $totAlreadyFitted;
+ $surplusToAdd = $surplus / ($this->numFields - sizeof($colFits));
+ } else {
+ $surplusToAdd = 0;
+ }
+
+ for ($i = 0; $i < $this->numFields; $i++) {
+ if (! in_array($i, array_keys($colFits))) {
+ $this->tablewidths[$i] = $this->sColWidth + $surplusToAdd;
+ }
+ if ($this->display_column[$i] == false) {
+ $this->tablewidths[$i] = 0;
+ }
+ }
+
+ ksort($this->tablewidths);
+
+ $GLOBALS['dbi']->freeResult($this->results);
+
+ // Pass 2
+
+ $this->results = $GLOBALS['dbi']->query(
+ $query, null, PMA_DatabaseInterface::QUERY_UNBUFFERED
+ );
+ $this->setY($this->tMargin);
+ $this->AddPage();
+ $this->SetFont(PMA_PDF_FONT, '', 9);
+ $this->morepagestable($this->FontSizePt);
+ $GLOBALS['dbi']->freeResult($this->results);
+
+ } // end of mysqlReport function
+
+} // end of PMA_Export_PDF class
+?>
diff --git a/libraries/plugins/export/README b/libraries/plugins/export/README
new file mode 100644
index 0000000000..68d5a26f13
--- /dev/null
+++ b/libraries/plugins/export/README
@@ -0,0 +1,276 @@
+This directory holds export plugins for phpMyAdmin. Any new plugin should
+basically follow the structure presented here. Official plugins need to
+have str* messages with their definition in language files, but if you build
+some plugins for your use, you can directly use texts in plugin.
+
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * [Name] export plugin for phpMyAdmin
+ *
+ * @package PhpMyAdmin-Export
+ * @subpackage [Name]
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/* Get the export interface */
+require_once 'libraries/plugins/ExportPlugin.class.php';
+
+/**
+ * Handles the export for the [Name] format
+ *
+ * @package PhpMyAdmin-Export
+ */
+class Export[Name] extends ExportPlugin
+{
+ /**
+ * optional - declare variables and descriptions
+ *
+ * @var type
+ */
+ private $_myOptionalVariable;
+
+ /**
+ * optional - declare global variables and descriptions
+ *
+ * @var type
+ */
+ private $_globalVariableName;
+
+ /**
+ * Constructor
+ */
+ public function __construct()
+ {
+ $this->setProperties();
+ }
+
+ // optional - declare global variables and use getters later
+ /**
+ * Initialize the local variables that are used specific for export SQL
+ *
+ * @global type $global_variable_name
+ * [..]
+ *
+ * @return void
+ */
+ protected function initSpecificVariables()
+ {
+ global $global_variable_name;
+ $this->_setGlobalVariableName($global_variable_name);
+ }
+
+ /**
+ * Sets the export plugin properties.
+ * Called in the constructor.
+ *
+ * @return void
+ */
+ protected function setProperties()
+ {
+ // set properties
+ $props = 'libraries/properties/';
+ // include the main class for properties for the export plug-ins
+ include_once "$props/plugins/ExportPluginProperties.class.php";
+ // include the group properties classes
+ include_once "$props/options/groups/OptionsPropertyRootGroup.class.php";
+ include_once "$props/options/groups/OptionsPropertyMainGroup.class.php";
+ // include the needed single property items
+ include_once "$props/options/items/RadioPropertyItem.class.php";
+
+ $exportPluginProperties = new ExportPluginProperties();
+ $exportPluginProperties->setText('[name]'); // the name of your plug-in
+ $exportPluginProperties->setExtension('[ext]'); // extension this plug-in can handle
+ $exportPluginProperties->setOptionsText(__('Options'));
+
+ // create the root group that will be the options field for
+ // $exportPluginProperties
+ // this will be shown as "Format specific options"
+ $exportSpecificOptions = new OptionsPropertyRootGroup();
+ $exportSpecificOptions->setName("Format Specific Options");
+
+ // general options main group
+ $generalOptions = new OptionsPropertyMainGroup();
+ $generalOptions->setName("general_opts");
+
+ // optional :
+ // create primary items and add them to the group
+ // type - one of the classes listed in libraries/properties/options/items/
+ // name - form element name
+ // text - description in GUI
+ // size - size of text element
+ // len - maximal size of input
+ // values - possible values of the item
+ $leaf = new RadioPropertyItem();
+ $leaf->setName("structure_or_data");
+ $leaf->setValues(
+ array(
+ 'structure' => __('structure'),
+ 'data' => __('data'),
+ 'structure_and_data' => __('structure and data')
+ )
+ );
+ $generalOptions->addProperty($leaf);
+
+ // add the main group to the root group
+ $exportSpecificOptions->addProperty($generalOptions);
+
+ // set the options for the export plugin property item
+ $exportPluginProperties->setOptions($exportSpecificOptions);
+ $this->properties = $exportPluginProperties;
+ }
+
+ /**
+ * This method is called when any PluginManager to which the observer
+ * is attached calls PluginManager::notify()
+ *
+ * @param SplSubject $subject The PluginManager notifying the observer
+ * of an update.
+ *
+ * @return void
+ */
+ public function update (SplSubject $subject)
+ {
+ }
+
+ /**
+ * Outputs export header
+ *
+ * @return bool Whether it succeeded
+ */
+ public function exportHeader ()
+ {
+ // implementation
+ return true;
+ }
+
+ /**
+ * Outputs export footer
+ *
+ * @return bool Whether it succeeded
+ */
+ public function exportFooter ()
+ {
+ // implementation
+ return true;
+ }
+
+ /**
+ * Outputs database header
+ *
+ * @param string $db Database name
+ *
+ * @return bool Whether it succeeded
+ */
+ public function exportDBHeader ($db)
+ {
+ // implementation
+ return true;
+ }
+
+ /**
+ * Outputs database footer
+ *
+ * @param string $db Database name
+ *
+ * @return bool Whether it succeeded
+ */
+ public function exportDBFooter ($db)
+ {
+ // implementation
+ return true;
+ }
+
+ /**
+ * Outputs CREATE DATABASE statement
+ *
+ * @param string $db Database name
+ *
+ * @return bool Whether it succeeded
+ */
+ public function exportDBCreate($db)
+ {
+ // implementation
+ return true;
+ }
+
+ /**
+ * Outputs the content of a table in [Name] format
+ *
+ * @param string $db database name
+ * @param string $table table name
+ * @param string $crlf the end of line sequence
+ * @param string $error_url the url to go back in case of error
+ * @param string $sql_query SQL query for obtaining data
+ *
+ * @return bool Whether it succeeded
+ */
+ public function exportData($db, $table, $crlf, $error_url, $sql_query)
+ {
+ // implementation;
+ return true;
+ }
+
+ // optional - implement other methods defined in ExportPlugin.class.php:
+ // - exportRoutines()
+ // - exportStructure()
+ // - getTableDefStandIn()
+ // - getTriggers()
+
+ // optional - implement other private methods in order to avoid
+ // having huge methods or avoid duplicate code. Make use of them
+ // as well as of the getters and setters declared both here
+ // and in the ExportPlugin class
+
+
+ // optional:
+ /* ~~~~~~~~~~~~~~~~~~~~ Getters and Setters ~~~~~~~~~~~~~~~~~~~~ */
+
+
+ /**
+ * Getter description
+ *
+ * @return type
+ */
+ private function _getMyOptionalVariable()
+ {
+ return $this->_myOptionalVariable;
+ }
+
+ /**
+ * Setter description
+ *
+ * @param type $my_optional_variable description
+ *
+ * @return void
+ */
+ private function _setMyOptionalVariable($my_optional_variable)
+ {
+ $this->_myOptionalVariable = $my_optional_variable;
+ }
+
+ /**
+ * Getter description
+ *
+ * @return type
+ */
+ private function _getGlobalVariableName()
+ {
+ return $this->_globalVariableName;
+ }
+
+ /**
+ * Setter description
+ *
+ * @param type $global_variable_name description
+ *
+ * @return void
+ */
+ private function _setGlobalVariableName($global_variable_name)
+ {
+ $this->_globalVariableName = $global_variable_name;
+ }
+}
+?> \ No newline at end of file
diff --git a/libraries/plugins/export/TableProperty.class.php b/libraries/plugins/export/TableProperty.class.php
new file mode 100644
index 0000000000..cf7c6fcbe4
--- /dev/null
+++ b/libraries/plugins/export/TableProperty.class.php
@@ -0,0 +1,286 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Holds the TableProperty class
+ *
+ * @package PhpMyAdmin-Export
+ * @subpackage CodeGen
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ * TableProperty class
+ *
+ * @package PhpMyAdmin-Export
+ * @subpackage CodeGen
+ */
+class TableProperty
+{
+ /**
+ * Name
+ *
+ * @var string
+ */
+ public $name;
+
+ /**
+ * Type
+ *
+ * @var string
+ */
+ public $type;
+
+ /**
+ * Wheter the key is nullable or not
+ *
+ * @var bool
+ */
+ public $nullable;
+
+ /**
+ * The key
+ *
+ * @var int
+ */
+ public $key;
+
+ /**
+ * Default value
+ *
+ * @var mixed
+ */
+ public $defaultValue;
+
+ /**
+ * Extension
+ *
+ * @var string
+ */
+ public $ext;
+
+ /**
+ * Constructor
+ *
+ * @param array $row table row
+ */
+ function __construct($row)
+ {
+ $this->name = trim($row[0]);
+ $this->type = trim($row[1]);
+ $this->nullable = trim($row[2]);
+ $this->key = trim($row[3]);
+ $this->defaultValue = trim($row[4]);
+ $this->ext = trim($row[5]);
+ }
+
+ /**
+ * Gets the pure type
+ *
+ * @return string type
+ */
+ function getPureType()
+ {
+ $pos = strpos($this->type, "(");
+ if ($pos > 0) {
+ return substr($this->type, 0, $pos);
+ }
+ return $this->type;
+ }
+
+ /**
+ * Tells whether the key is null or not
+ *
+ * @return bool true if the key is not null, false otherwise
+ */
+ function isNotNull()
+ {
+ return $this->nullable == "NO" ? "true" : "false";
+ }
+
+ /**
+ * Tells whether the key is unique or not
+ *
+ * @return bool true if the key is unique, false otherwise
+ */
+ function isUnique()
+ {
+ return $this->key == "PRI" || $this->key == "UNI" ? "true" : "false";
+ }
+
+ /**
+ * Gets the .NET primitive type
+ *
+ * @return string type
+ */
+ function getDotNetPrimitiveType()
+ {
+ if (strpos($this->type, "int") === 0) {
+ return "int";
+ }
+ if (strpos($this->type, "long") === 0) {
+ return "long";
+ }
+ if (strpos($this->type, "char") === 0) {
+ return "string";
+ }
+ if (strpos($this->type, "varchar") === 0) {
+ return "string";
+ }
+ if (strpos($this->type, "text") === 0) {
+ return "string";
+ }
+ if (strpos($this->type, "longtext") === 0) {
+ return "string";
+ }
+ if (strpos($this->type, "tinyint") === 0) {
+ return "bool";
+ }
+ if (strpos($this->type, "datetime") === 0) {
+ return "DateTime";
+ }
+ return "unknown";
+ }
+
+ /**
+ * Gets the .NET object type
+ *
+ * @return string type
+ */
+ function getDotNetObjectType()
+ {
+ if (strpos($this->type, "int") === 0) {
+ return "Int32";
+ }
+ if (strpos($this->type, "long") === 0) {
+ return "Long";
+ }
+ if (strpos($this->type, "char") === 0) {
+ return "String";
+ }
+ if (strpos($this->type, "varchar") === 0) {
+ return "String";
+ }
+ if (strpos($this->type, "text") === 0) {
+ return "String";
+ }
+ if (strpos($this->type, "longtext") === 0) {
+ return "String";
+ }
+ if (strpos($this->type, "tinyint") === 0) {
+ return "Boolean";
+ }
+ if (strpos($this->type, "datetime") === 0) {
+ return "DateTime";
+ }
+ return "Unknown";
+ }
+
+ /**
+ * Gets the index name
+ *
+ * @return string containing the name of the index
+ */
+ function getIndexName()
+ {
+ if (strlen($this->key) > 0) {
+ return "index=\""
+ . htmlspecialchars($this->name, ENT_COMPAT, 'UTF-8')
+ . "\"";
+ }
+ return "";
+ }
+
+ /**
+ * Tells whether the key is primary or not
+ *
+ * @return bool true if the key is primary, false otherwise
+ */
+ function isPK()
+ {
+ return $this->key=="PRI";
+ }
+
+ /**
+ * Formats a string for C#
+ *
+ * @param string $text string to be formatted
+ *
+ * @return string formatted text
+ */
+ function formatCs($text)
+ {
+ $text = str_replace(
+ "#name#",
+ ExportCodegen::cgMakeIdentifier($this->name, false),
+ $text
+ );
+ return $this->format($text);
+ }
+
+ /**
+ * Formats a string for XML
+ *
+ * @param string $text string to be formatted
+ *
+ * @return string formatted text
+ */
+ function formatXml($text)
+ {
+ $text = str_replace(
+ "#name#",
+ htmlspecialchars($this->name, ENT_COMPAT, 'UTF-8'),
+ $text
+ );
+ $text = str_replace(
+ "#indexName#",
+ $this->getIndexName(),
+ $text
+ );
+ return $this->format($text);
+ }
+
+ /**
+ * Formats a string
+ *
+ * @param string $text string to be formatted
+ *
+ * @return string formatted text
+ */
+ function format($text)
+ {
+ $text = str_replace(
+ "#ucfirstName#",
+ ExportCodegen::cgMakeIdentifier($this->name),
+ $text
+ );
+ $text = str_replace(
+ "#dotNetPrimitiveType#",
+ $this->getDotNetPrimitiveType(),
+ $text
+ );
+ $text = str_replace(
+ "#dotNetObjectType#",
+ $this->getDotNetObjectType(),
+ $text
+ );
+ $text = str_replace(
+ "#type#",
+ $this->getPureType(),
+ $text
+ );
+ $text = str_replace(
+ "#notNull#",
+ $this->isNotNull(),
+ $text
+ );
+ $text = str_replace(
+ "#unique#",
+ $this->isUnique(),
+ $text
+ );
+ return $text;
+ }
+}
+?> \ No newline at end of file
diff --git a/libraries/plugins/import/AbstractImportCsv.class.php b/libraries/plugins/import/AbstractImportCsv.class.php
new file mode 100644
index 0000000000..fbc891767e
--- /dev/null
+++ b/libraries/plugins/import/AbstractImportCsv.class.php
@@ -0,0 +1,90 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Super class of CSV import plugins for phpMyAdmin
+ *
+ * @package PhpMyAdmin-Import
+ * @subpackage CSV
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/* Get the import interface */
+require_once 'libraries/plugins/ImportPlugin.class.php';
+
+/**
+ * Super class of the import plugins for the CSV format
+ *
+ * @package PhpMyAdmin-Import
+ * @subpackage CSV
+ */
+abstract class AbstractImportCsv extends ImportPlugin
+{
+ /**
+ * Sets the import plugin properties.
+ * Called in the constructor.
+ *
+ * @return OptionsPropertyMainGroup OptionsPropertyMainGroup object of the plugin
+ */
+ protected function setProperties()
+ {
+ $props = 'libraries/properties/';
+ include_once "$props/plugins/ImportPluginProperties.class.php";
+ include_once "$props/options/groups/OptionsPropertyRootGroup.class.php";
+ include_once "$props/options/groups/OptionsPropertyMainGroup.class.php";
+ include_once "$props/options/items/BoolPropertyItem.class.php";
+ include_once "$props/options/items/TextPropertyItem.class.php";
+
+ $importPluginProperties = new ImportPluginProperties();
+ $importPluginProperties->setOptionsText(__('Options'));
+
+ // create the root group that will be the options field for
+ // $importPluginProperties
+ // this will be shown as "Format specific options"
+ $importSpecificOptions = new OptionsPropertyRootGroup();
+ $importSpecificOptions->setName("Format Specific Options");
+
+ // general options main group
+ $generalOptions = new OptionsPropertyMainGroup();
+ $generalOptions->setName("general_opts");
+
+ // create common items and add them to the group
+ $leaf = new BoolPropertyItem();
+ $leaf->setName("replace");
+ $leaf->setText(__('Replace table data with file'));
+ $generalOptions->addProperty($leaf);
+ $leaf = new TextPropertyItem();
+ $leaf->setName("terminated");
+ $leaf->setText(__('Columns separated with:'));
+ $leaf->setSize(2);
+ $generalOptions->addProperty($leaf);
+ $leaf = new TextPropertyItem();
+ $leaf->setName("enclosed");
+ $leaf->setText(__('Columns enclosed with:'));
+ $leaf->setSize(2);
+ $leaf->setLen(2);
+ $generalOptions->addProperty($leaf);
+ $leaf = new TextPropertyItem();
+ $leaf->setName("escaped");
+ $leaf->setText(__('Columns escaped with:'));
+ $leaf->setSize(2);
+ $leaf->setLen(2);
+ $generalOptions->addProperty($leaf);
+ $leaf = new TextPropertyItem();
+ $leaf->setName("new_line");
+ $leaf->setText(__('Lines terminated with:'));
+ $leaf->setSize(2);
+ $generalOptions->addProperty($leaf);
+
+ // add the main group to the root group
+ $importSpecificOptions->addProperty($generalOptions);
+
+ // set the options for the import plugin property item
+ $importPluginProperties->setOptions($importSpecificOptions);
+ $this->properties = $importPluginProperties;
+
+ return $generalOptions;
+ }
+}
+?> \ No newline at end of file
diff --git a/libraries/plugins/import/ImportCsv.class.php b/libraries/plugins/import/ImportCsv.class.php
new file mode 100644
index 0000000000..8e067d9d1a
--- /dev/null
+++ b/libraries/plugins/import/ImportCsv.class.php
@@ -0,0 +1,608 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * CSV import plugin for phpMyAdmin
+ *
+ * @todo add an option for handling NULL values
+ * @package PhpMyAdmin-Import
+ * @subpackage CSV
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/* Get the import interface */
+require_once 'libraries/plugins/import/AbstractImportCsv.class.php';
+
+/**
+ * Handles the import for the CSV format
+ *
+ * @package PhpMyAdmin-Import
+ * @subpackage CSV
+ */
+class ImportCsv extends AbstractImportCsv
+{
+ /**
+ * Whether to analyze tables
+ *
+ * @var bool
+ */
+ private $_analyze;
+
+ /**
+ * Constructor
+ */
+ public function __construct()
+ {
+ $this->setProperties();
+ }
+
+ /**
+ * Sets the import plugin properties.
+ * Called in the constructor.
+ *
+ * @return void
+ */
+ protected function setProperties()
+ {
+ $this->_setAnalyze(false);
+
+ if ($GLOBALS['plugin_param'] !== 'table') {
+ $this->_setAnalyze(true);
+ }
+
+ $generalOptions = parent::setProperties();
+ $this->properties->setText('CSV');
+ $this->properties->setExtension('csv');
+
+ if ($GLOBALS['plugin_param'] !== 'table') {
+ $leaf = new BoolPropertyItem();
+ $leaf->setName("col_names");
+ $leaf->setText(
+ __(
+ 'The first line of the file contains the table column names'
+ . ' <i>(if this is unchecked, the first line will become part'
+ . ' of the data)</i>'
+ )
+ );
+ $generalOptions->addProperty($leaf);
+ } else {
+ $hint = new PMA_Message(
+ __(
+ 'If the data in each row of the file is not'
+ . ' in the same order as in the database, list the corresponding'
+ . ' column names here. Column names must be separated by commas'
+ . ' and not enclosed in quotations.'
+ )
+ );
+ $leaf = new TextPropertyItem();
+ $leaf->setName("columns");
+ $leaf->setText(
+ __('Column names: ')
+ . PMA_Util::showHint($hint)
+ );
+ $generalOptions->addProperty($leaf);
+ }
+
+ $leaf = new BoolPropertyItem();
+ $leaf->setName("ignore");
+ $leaf->setText(__('Do not abort on INSERT error'));
+ $generalOptions->addProperty($leaf);
+
+ }
+
+ /**
+ * This method is called when any PluginManager to which the observer
+ * is attached calls PluginManager::notify()
+ *
+ * @param SplSubject $subject The PluginManager notifying the observer
+ * of an update.
+ *
+ * @return void
+ */
+ public function update (SplSubject $subject)
+ {
+ }
+
+ /**
+ * Handles the whole import logic
+ *
+ * @return void
+ */
+ public function doImport()
+ {
+ global $db, $table, $csv_terminated, $csv_enclosed, $csv_escaped,
+ $csv_new_line, $csv_columns, $err_url;
+ // $csv_replace and $csv_ignore should have been here,
+ // but we use directly from $_POST
+ global $error, $timeout_passed, $finished, $message;
+
+ $replacements = array(
+ '\\n' => "\n",
+ '\\t' => "\t",
+ '\\r' => "\r",
+ );
+ $csv_terminated = strtr($csv_terminated, $replacements);
+ $csv_enclosed = strtr($csv_enclosed, $replacements);
+ $csv_escaped = strtr($csv_escaped, $replacements);
+ $csv_new_line = strtr($csv_new_line, $replacements);
+
+ $param_error = false;
+ if (strlen($csv_terminated) != 1) {
+ $message = PMA_Message::error(
+ __('Invalid parameter for CSV import: %s')
+ );
+ $message->addParam(__('Columns terminated with'), false);
+ $error = true;
+ $param_error = true;
+ // The default dialog of MS Excel when generating a CSV produces a
+ // semi-colon-separated file with no chance of specifying the
+ // enclosing character. Thus, users who want to import this file
+ // tend to remove the enclosing character on the Import dialog.
+ // I could not find a test case where having no enclosing characters
+ // confuses this script.
+ // But the parser won't work correctly with strings so we allow just
+ // one character.
+ } elseif (strlen($csv_enclosed) > 1) {
+ $message = PMA_Message::error(
+ __('Invalid parameter for CSV import: %s')
+ );
+ $message->addParam(__('Columns enclosed with'), false);
+ $error = true;
+ $param_error = true;
+ } elseif (strlen($csv_escaped) != 1) {
+ $message = PMA_Message::error(
+ __('Invalid parameter for CSV import: %s')
+ );
+ $message->addParam(__('Columns escaped with'), false);
+ $error = true;
+ $param_error = true;
+ } elseif (strlen($csv_new_line) != 1 && $csv_new_line != 'auto') {
+ $message = PMA_Message::error(
+ __('Invalid parameter for CSV import: %s')
+ );
+ $message->addParam(__('Lines terminated with'), false);
+ $error = true;
+ $param_error = true;
+ }
+
+ // If there is an error in the parameters entered,
+ // indicate that immediately.
+ if ($param_error) {
+ PMA_Util::mysqlDie($message->getMessage(), '', '', $err_url);
+ }
+
+ $buffer = '';
+ $required_fields = 0;
+
+ if (! $this->_getAnalyze()) {
+ if (isset($_POST['csv_replace'])) {
+ $sql_template = 'REPLACE';
+ } else {
+ $sql_template = 'INSERT';
+ if (isset($_POST['csv_ignore'])) {
+ $sql_template .= ' IGNORE';
+ }
+ }
+ $sql_template .= ' INTO ' . PMA_Util::backquote($table);
+
+ $tmp_fields = $GLOBALS['dbi']->getColumns($db, $table);
+
+ if (empty($csv_columns)) {
+ $fields = $tmp_fields;
+ } else {
+ $sql_template .= ' (';
+ $fields = array();
+ $tmp = preg_split('/,( ?)/', $csv_columns);
+ foreach ($tmp as $key => $val) {
+ if (count($fields) > 0) {
+ $sql_template .= ', ';
+ }
+ /* Trim also `, if user already included backquoted fields */
+ $val = trim($val, " \t\r\n\0\x0B`");
+ $found = false;
+ foreach ($tmp_fields as $field) {
+ if ($field['Field'] == $val) {
+ $found = true;
+ break;
+ }
+ }
+ if (! $found) {
+ $message = PMA_Message::error(
+ __(
+ 'Invalid column (%s) specified! Ensure that columns'
+ . ' names are spelled correctly, separated by commas'
+ . ', and not enclosed in quotes.'
+ )
+ );
+ $message->addParam($val);
+ $error = true;
+ break;
+ }
+ $fields[] = $field;
+ $sql_template .= PMA_Util::backquote($val);
+ }
+ $sql_template .= ') ';
+ }
+
+ $required_fields = count($fields);
+
+ $sql_template .= ' VALUES (';
+ }
+
+ // Defaults for parser
+ $i = 0;
+ $len = 0;
+ $lastlen = null;
+ $line = 1;
+ $lasti = -1;
+ $values = array();
+ $csv_finish = false;
+
+ $tempRow = array();
+ $rows = array();
+ $col_names = array();
+ $tables = array();
+
+ $col_count = 0;
+ $max_cols = 0;
+
+ while (! ($finished && $i >= $len) && ! $error && ! $timeout_passed) {
+ $data = PMA_importGetNextChunk();
+ if ($data === false) {
+ // subtract data we didn't handle yet and stop processing
+ $GLOBALS['offset'] -= strlen($buffer);
+ break;
+ } elseif ($data === true) {
+ // Handle rest of buffer
+ } else {
+ // Append new data to buffer
+ $buffer .= $data;
+ unset($data);
+
+ // Force a trailing new line at EOF to prevent parsing problems
+ if ($finished && $buffer) {
+ $finalch = substr($buffer, -1);
+ if ($csv_new_line == 'auto'
+ && $finalch != "\r"
+ && $finalch != "\n"
+ ) {
+ $buffer .= "\n";
+ } elseif ($csv_new_line != 'auto'
+ && $finalch != $csv_new_line
+ ) {
+ $buffer .= $csv_new_line;
+ }
+ }
+
+ // Do not parse string when we're not at the end
+ // and don't have new line inside
+ if (($csv_new_line == 'auto'
+ && strpos($buffer, "\r") === false
+ && strpos($buffer, "\n") === false)
+ || ($csv_new_line != 'auto'
+ && strpos($buffer, $csv_new_line) === false)
+ ) {
+ continue;
+ }
+ }
+
+ // Current length of our buffer
+ $len = strlen($buffer);
+ // Currently parsed char
+ $ch = $buffer[$i];
+ while ($i < $len) {
+ // Deadlock protection
+ if ($lasti == $i && $lastlen == $len) {
+ $message = PMA_Message::error(
+ __('Invalid format of CSV input on line %d.')
+ );
+ $message->addParam($line);
+ $error = true;
+ break;
+ }
+ $lasti = $i;
+ $lastlen = $len;
+
+ // This can happen with auto EOL and \r at the end of buffer
+ if (! $csv_finish) {
+ // Grab empty field
+ if ($ch == $csv_terminated) {
+ if ($i == $len - 1) {
+ break;
+ }
+ $values[] = '';
+ $i++;
+ $ch = $buffer[$i];
+ continue;
+ }
+
+ // Grab one field
+ $fallbacki = $i;
+ if ($ch == $csv_enclosed) {
+ if ($i == $len - 1) {
+ break;
+ }
+ $need_end = true;
+ $i++;
+ $ch = $buffer[$i];
+ } else {
+ $need_end = false;
+ }
+ $fail = false;
+ $value = '';
+ while (($need_end
+ && ( $ch != $csv_enclosed || $csv_enclosed == $csv_escaped ))
+ || ( ! $need_end
+ && ! ( $ch == $csv_terminated
+ || $ch == $csv_new_line
+ || ( $csv_new_line == 'auto'
+ && ( $ch == "\r" || $ch == "\n" ) ) ) )
+ ) {
+ if ($ch == $csv_escaped) {
+ if ($i == $len - 1) {
+ $fail = true;
+ break;
+ }
+ $i++;
+ $ch = $buffer[$i];
+ if ($csv_enclosed == $csv_escaped
+ && ($ch == $csv_terminated
+ || $ch == $csv_new_line
+ || ($csv_new_line == 'auto'
+ && ($ch == "\r" || $ch == "\n")))
+ ) {
+ break;
+ }
+ }
+ $value .= $ch;
+ if ($i == $len - 1) {
+ if (! $finished) {
+ $fail = true;
+ }
+ break;
+ }
+ $i++;
+ $ch = $buffer[$i];
+ }
+
+ // unquoted NULL string
+ if (false === $need_end && $value === 'NULL') {
+ $value = null;
+ }
+
+ if ($fail) {
+ $i = $fallbacki;
+ $ch = $buffer[$i];
+ break;
+ }
+ // Need to strip trailing enclosing char?
+ if ($need_end && $ch == $csv_enclosed) {
+ if ($finished && $i == $len - 1) {
+ $ch = null;
+ } elseif ($i == $len - 1) {
+ $i = $fallbacki;
+ $ch = $buffer[$i];
+ break;
+ } else {
+ $i++;
+ $ch = $buffer[$i];
+ }
+ }
+ // Are we at the end?
+ if ($ch == $csv_new_line
+ || ($csv_new_line == 'auto' && ($ch == "\r" || $ch == "\n"))
+ || ($finished && $i == $len - 1)
+ ) {
+ $csv_finish = true;
+ }
+ // Go to next char
+ if ($ch == $csv_terminated) {
+ if ($i == $len - 1) {
+ $i = $fallbacki;
+ $ch = $buffer[$i];
+ break;
+ }
+ $i++;
+ $ch = $buffer[$i];
+ }
+ // If everything went okay, store value
+ $values[] = $value;
+ }
+
+ // End of line
+ if ($csv_finish
+ || $ch == $csv_new_line
+ || ($csv_new_line == 'auto' && ($ch == "\r" || $ch == "\n"))
+ ) {
+ if ($csv_new_line == 'auto' && $ch == "\r") { // Handle "\r\n"
+ if ($i >= ($len - 2) && ! $finished) {
+ break; // We need more data to decide new line
+ }
+ if ($buffer[$i + 1] == "\n") {
+ $i++;
+ }
+ }
+ // We didn't parse value till the end of line, so there was
+ // empty one
+ if (! $csv_finish) {
+ $values[] = '';
+ }
+
+ if ($this->_getAnalyze()) {
+ foreach ($values as $val) {
+ $tempRow[] = $val;
+ ++$col_count;
+ }
+
+ if ($col_count > $max_cols) {
+ $max_cols = $col_count;
+ }
+ $col_count = 0;
+
+ $rows[] = $tempRow;
+ $tempRow = array();
+ } else {
+ // Do we have correct count of values?
+ if (count($values) != $required_fields) {
+
+ // Hack for excel
+ if ($values[count($values) - 1] == ';') {
+ unset($values[count($values) - 1]);
+ } else {
+ $message = PMA_Message::error(
+ __(
+ 'Invalid column count in CSV input'
+ . ' on line %d.'
+ )
+ );
+ $message->addParam($line);
+ $error = true;
+ break;
+ }
+ }
+
+ $first = true;
+ $sql = $sql_template;
+ foreach ($values as $key => $val) {
+ if (! $first) {
+ $sql .= ', ';
+ }
+ if ($val === null) {
+ $sql .= 'NULL';
+ } else {
+ $sql .= '\''
+ . PMA_Util::sqlAddSlashes($val)
+ . '\'';
+ }
+
+ $first = false;
+ }
+ $sql .= ')';
+
+ /**
+ * @todo maybe we could add original line to verbose
+ * SQL in comment
+ */
+ PMA_importRunQuery($sql, $sql);
+ }
+
+ $line++;
+ $csv_finish = false;
+ $values = array();
+ $buffer = substr($buffer, $i + 1);
+ $len = strlen($buffer);
+ $i = 0;
+ $lasti = -1;
+ $ch = $buffer[0];
+ }
+ } // End of parser loop
+ } // End of import loop
+
+ if ($this->_getAnalyze()) {
+ /* Fill out all rows */
+ $num_rows = count($rows);
+ for ($i = 0; $i < $num_rows; ++$i) {
+ for ($j = count($rows[$i]); $j < $max_cols; ++$j) {
+ $rows[$i][] = 'NULL';
+ }
+ }
+
+ if (isset($_REQUEST['csv_col_names'])) {
+ $col_names = array_splice($rows, 0, 1);
+ $col_names = $col_names[0];
+ }
+
+ if ((isset($col_names) && count($col_names) != $max_cols)
+ || ! isset($col_names)
+ ) {
+ // Fill out column names
+ for ($i = 0; $i < $max_cols; ++$i) {
+ $col_names[] = 'COL '.($i+1);
+ }
+ }
+
+ if (strlen($db)) {
+ $result = $GLOBALS['dbi']->fetchResult('SHOW TABLES');
+ $tbl_name = 'TABLE '.(count($result) + 1);
+ } else {
+ $tbl_name = 'TBL_NAME';
+ }
+
+ $tables[] = array($tbl_name, $col_names, $rows);
+
+ /* Obtain the best-fit MySQL types for each column */
+ $analyses = array();
+ $analyses[] = PMA_analyzeTable($tables[0]);
+
+ /**
+ * string $db_name (no backquotes)
+ *
+ * array $table = array(table_name, array() column_names, array()() rows)
+ * array $tables = array of "$table"s
+ *
+ * array $analysis = array(array() column_types, array() column_sizes)
+ * array $analyses = array of "$analysis"s
+ *
+ * array $create = array of SQL strings
+ *
+ * array $options = an associative array of options
+ */
+
+ /* Set database name to the currently selected one, if applicable */
+ if (strlen($db)) {
+ $db_name = $db;
+ $options = array('create_db' => false);
+ } else {
+ $db_name = 'CSV_DB';
+ $options = null;
+ }
+
+ /* Non-applicable parameters */
+ $create = null;
+
+ /* Created and execute necessary SQL statements from data */
+ PMA_buildSQL($db_name, $tables, $analyses, $create, $options);
+
+ unset($tables);
+ unset($analyses);
+ }
+
+ // Commit any possible data in buffers
+ PMA_importRunQuery();
+
+ if (count($values) != 0 && ! $error) {
+ $message = PMA_Message::error(
+ __('Invalid format of CSV input on line %d.')
+ );
+ $message->addParam($line);
+ $error = true;
+ }
+ }
+
+
+ /* ~~~~~~~~~~~~~~~~~~~~ Getters and Setters ~~~~~~~~~~~~~~~~~~~~ */
+
+
+ /**
+ * Returns true if the table should be analyzed, false otherwise
+ *
+ * @return bool
+ */
+ private function _getAnalyze()
+ {
+ return $this->_analyze;
+ }
+
+ /**
+ * Sets to true if the table should be analyzed, false otherwise
+ *
+ * @param bool $analyze status
+ *
+ * @return void
+ */
+ private function _setAnalyze($analyze)
+ {
+ $this->_analyze = $analyze;
+ }
+}
diff --git a/libraries/plugins/import/ImportLdi.class.php b/libraries/plugins/import/ImportLdi.class.php
new file mode 100644
index 0000000000..1277938baf
--- /dev/null
+++ b/libraries/plugins/import/ImportLdi.class.php
@@ -0,0 +1,174 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * CSV import plugin for phpMyAdmin using LOAD DATA
+ *
+ * @package PhpMyAdmin-Import
+ * @subpackage LDI
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/* Get the import interface */
+require_once 'libraries/plugins/import/AbstractImportCsv.class.php';
+
+// We need relations enabled and we work only on database
+if ($GLOBALS['plugin_param'] !== 'table') {
+ $GLOBALS['skip_import'] = true;
+ return;
+}
+
+/**
+ * Handles the import for the CSV format using load data
+ *
+ * @package PhpMyAdmin-Import
+ * @subpackage LDI
+ */
+class ImportLdi extends AbstractImportCsv
+{
+ /**
+ * Constructor
+ */
+ public function __construct()
+ {
+ $this->setProperties();
+ }
+
+ /**
+ * Sets the import plugin properties.
+ * Called in the constructor.
+ *
+ * @return void
+ */
+ protected function setProperties()
+ {
+ if ($GLOBALS['cfg']['Import']['ldi_local_option'] == 'auto') {
+ $GLOBALS['cfg']['Import']['ldi_local_option'] = false;
+
+ $result = $GLOBALS['dbi']->tryQuery(
+ 'SHOW VARIABLES LIKE \'local\\_infile\';'
+ );
+ if ($result != false && $GLOBALS['dbi']->numRows($result) > 0) {
+ $tmp = $GLOBALS['dbi']->fetchRow($result);
+ if ($tmp[1] == 'ON') {
+ $GLOBALS['cfg']['Import']['ldi_local_option'] = true;
+ }
+ }
+ $GLOBALS['dbi']->freeResult($result);
+ unset($result);
+ }
+
+ $generalOptions = parent::setProperties();
+ $this->properties->setText('CSV using LOAD DATA');
+ $this->properties->setExtension('ldi');
+
+ $leaf = new TextPropertyItem();
+ $leaf->setName("columns");
+ $leaf->setText(__('Column names: '));
+ $generalOptions->addProperty($leaf);
+
+ $leaf = new BoolPropertyItem();
+ $leaf->setName("ignore");
+ $leaf->setText(__('Do not abort on INSERT error'));
+ $generalOptions->addProperty($leaf);
+
+ $leaf = new BoolPropertyItem();
+ $leaf->setName("local_option");
+ $leaf->setText(__('Use LOCAL keyword'));
+ $generalOptions->addProperty($leaf);
+ }
+
+ /**
+ * This method is called when any PluginManager to which the observer
+ * is attached calls PluginManager::notify()
+ *
+ * @param SplSubject $subject The PluginManager notifying the observer
+ * of an update.
+ *
+ * @return void
+ */
+ public function update (SplSubject $subject)
+ {
+ }
+
+ /**
+ * Handles the whole import logic
+ *
+ * @return void
+ */
+ public function doImport()
+ {
+ global $finished, $import_file, $compression, $charset_conversion, $table;
+ global $ldi_local_option, $ldi_replace, $ldi_ignore, $ldi_terminated,
+ $ldi_enclosed, $ldi_escaped, $ldi_new_line, $skip_queries, $ldi_columns;
+
+ if ($import_file == 'none'
+ || $compression != 'none'
+ || $charset_conversion
+ ) {
+ // We handle only some kind of data!
+ $GLOBALS['message'] = PMA_Message::error(
+ __('This plugin does not support compressed imports!')
+ );
+ $GLOBALS['error'] = true;
+ return;
+ }
+
+ $sql = 'LOAD DATA';
+ if (isset($ldi_local_option)) {
+ $sql .= ' LOCAL';
+ }
+ $sql .= ' INFILE \'' . PMA_Util::sqlAddSlashes($import_file) . '\'';
+ if (isset($ldi_replace)) {
+ $sql .= ' REPLACE';
+ } elseif (isset($ldi_ignore)) {
+ $sql .= ' IGNORE';
+ }
+ $sql .= ' INTO TABLE ' . PMA_Util::backquote($table);
+
+ if (strlen($ldi_terminated) > 0) {
+ $sql .= ' FIELDS TERMINATED BY \'' . $ldi_terminated . '\'';
+ }
+ if (strlen($ldi_enclosed) > 0) {
+ $sql .= ' ENCLOSED BY \''
+ . PMA_Util::sqlAddSlashes($ldi_enclosed) . '\'';
+ }
+ if (strlen($ldi_escaped) > 0) {
+ $sql .= ' ESCAPED BY \''
+ . PMA_Util::sqlAddSlashes($ldi_escaped) . '\'';
+ }
+ if (strlen($ldi_new_line) > 0) {
+ if ($ldi_new_line == 'auto') {
+ $ldi_new_line
+ = (PMA_Util::whichCrlf() == "\n")
+ ? '\n'
+ : '\r\n';
+ }
+ $sql .= ' LINES TERMINATED BY \'' . $ldi_new_line . '\'';
+ }
+ if ($skip_queries > 0) {
+ $sql .= ' IGNORE ' . $skip_queries . ' LINES';
+ $skip_queries = 0;
+ }
+ if (strlen($ldi_columns) > 0) {
+ $sql .= ' (';
+ $tmp = preg_split('/,( ?)/', $ldi_columns);
+ $cnt_tmp = count($tmp);
+ for ($i = 0; $i < $cnt_tmp; $i++) {
+ if ($i > 0) {
+ $sql .= ', ';
+ }
+ /* Trim also `, if user already included backquoted fields */
+ $sql .= PMA_Util::backquote(
+ trim($tmp[$i], " \t\r\n\0\x0B`")
+ );
+ } // end for
+ $sql .= ')';
+ }
+
+ PMA_importRunQuery($sql, $sql);
+ PMA_importRunQuery();
+ $finished = true;
+ }
+}
diff --git a/libraries/plugins/import/ImportMediawiki.class.php b/libraries/plugins/import/ImportMediawiki.class.php
new file mode 100644
index 0000000000..b7cd3bebe1
--- /dev/null
+++ b/libraries/plugins/import/ImportMediawiki.class.php
@@ -0,0 +1,573 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * MediaWiki import plugin for phpMyAdmin
+ *
+ * @package PhpMyAdmin-Import
+ * @subpackage MediaWiki
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/* Get the import interface */
+require_once 'libraries/plugins/ImportPlugin.class.php';
+
+/**
+ * Handles the import for the MediaWiki format
+ *
+ * @package PhpMyAdmin-Import
+ * @subpackage MediaWiki
+ */
+class ImportMediawiki extends ImportPlugin
+{
+ /**
+ * Whether to analyze tables
+ *
+ * @var bool
+ */
+ private $_analyze;
+
+ /**
+ * Constructor
+ */
+ public function __construct()
+ {
+ $this->setProperties();
+ }
+
+ /**
+ * Sets the import plugin properties.
+ * Called in the constructor.
+ *
+ * @return void
+ */
+ protected function setProperties()
+ {
+ $this->_setAnalyze(false);
+ if ($GLOBALS['plugin_param'] !== 'table') {
+ $this->_setAnalyze(true);
+ }
+
+ $props = 'libraries/properties/';
+ include_once "$props/plugins/ImportPluginProperties.class.php";
+
+ $importPluginProperties = new ImportPluginProperties();
+ $importPluginProperties->setText(__('MediaWiki Table'));
+ $importPluginProperties->setExtension('txt');
+ $importPluginProperties->setMimeType('text/plain');
+ $importPluginProperties->setOptions(array());
+ $importPluginProperties->setOptionsText(__('Options'));
+
+ $this->properties = $importPluginProperties;
+ }
+
+ /**
+ * This method is called when any PluginManager to which the observer
+ * is attached calls PluginManager::notify()
+ *
+ * @param SplSubject $subject The PluginManager notifying the observer
+ * of an update.
+ *
+ * @return void
+ */
+ public function update (SplSubject $subject)
+ {
+ }
+
+ /**
+ * Handles the whole import logic
+ *
+ * @return void
+ */
+ public function doImport()
+ {
+ global $error, $timeout_passed, $finished;
+
+ // Defaults for parser
+
+ // The buffer that will be used to store chunks read from the imported file
+ $buffer = '';
+
+ // Used as storage for the last part of the current chunk data
+ // Will be appended to the first line of the next chunk, if there is one
+ $last_chunk_line = '';
+
+ // Remembers whether the current buffer line is part of a comment
+ $inside_comment = false;
+ // Remembers whether the current buffer line is part of a data comment
+ $inside_data_comment = false;
+ // Remembers whether the current buffer line is part of a structure comment
+ $inside_structure_comment = false;
+
+ // MediaWiki only accepts "\n" as row terminator
+ $mediawiki_new_line = "\n";
+
+ // Initialize the name of the current table
+ $cur_table_name = "";
+
+ while (! $finished && ! $error && ! $timeout_passed ) {
+ $data = PMA_importGetNextChunk();
+
+ if ($data === false) {
+ // Subtract data we didn't handle yet and stop processing
+ $GLOBALS['offset'] -= strlen($buffer);
+ break;
+ } elseif ($data === true) {
+ // Handle rest of buffer
+ } else {
+ // Append new data to buffer
+ $buffer = $data;
+ unset($data);
+ // Don't parse string if we're not at the end
+ // and don't have a new line inside
+ if ( strpos($buffer, $mediawiki_new_line) === false ) {
+ continue;
+ }
+ }
+
+ // Because of reading chunk by chunk, the first line from the buffer
+ // contains only a portion of an actual line from the imported file.
+ // Therefore, we have to append it to the last line from the previous
+ // chunk. If we are at the first chunk, $last_chunk_line should be empty.
+ $buffer = $last_chunk_line . $buffer;
+
+ // Process the buffer line by line
+ $buffer_lines = explode($mediawiki_new_line, $buffer);
+
+ $full_buffer_lines_count = count($buffer_lines);
+ // If the reading is not finalised, the final line of the current chunk
+ // will not be complete
+ if (! $finished) {
+ $full_buffer_lines_count -= 1;
+ $last_chunk_line = $buffer_lines[$full_buffer_lines_count];
+ }
+
+ for ($line_nr = 0; $line_nr < $full_buffer_lines_count; ++ $line_nr) {
+ $cur_buffer_line = trim($buffer_lines[$line_nr]);
+
+ // If the line is empty, go to the next one
+ if ( $cur_buffer_line === '' ) {
+ continue;
+ }
+
+ $first_character = $cur_buffer_line[0];
+ $matches = array();
+
+ // Check beginnning of comment
+ if (! strcmp(substr($cur_buffer_line, 0, 4), "<!--")) {
+ $inside_comment = true;
+ continue;
+ } elseif ($inside_comment) {
+ // Check end of comment
+ if (! strcmp(substr($cur_buffer_line, 0, 4), "-->")) {
+ // Only data comments are closed. The structure comments
+ // will be closed when a data comment begins (in order to
+ // skip structure tables)
+ if ($inside_data_comment) {
+ $inside_data_comment = false;
+ }
+
+ // End comments that are not related to table structure
+ if (! $inside_structure_comment) {
+ $inside_comment = false;
+ }
+ } else {
+ // Check table name
+ $match_table_name = array();
+ if (preg_match(
+ "/^Table data for `(.*)`$/",
+ $cur_buffer_line,
+ $match_table_name
+ )
+ ) {
+ $cur_table_name = $match_table_name[1];
+ $inside_data_comment = true;
+
+ // End ignoring structure rows
+ if ($inside_structure_comment) {
+ $inside_structure_comment = false;
+ }
+ } elseif (preg_match(
+ "/^Table structure for `(.*)`$/",
+ $cur_buffer_line,
+ $match_table_name
+ )
+ ) {
+ // The structure comments will be ignored
+ $inside_structure_comment = true;
+ }
+ }
+ continue;
+ } elseif (preg_match('/^\{\|(.*)$/', $cur_buffer_line, $matches)) {
+ // Check start of table
+
+ // This will store all the column info on all rows from
+ // the current table read from the buffer
+ $cur_temp_table = array();
+
+ // Will be used as storage for the current row in the buffer
+ // Once all its columns are read, it will be added to
+ // $cur_temp_table and then it will be emptied
+ $cur_temp_line = array();
+
+ // Helps us differentiate the header columns
+ // from the normal columns
+ $in_table_header = false;
+ // End processing because the current line does not
+ // contain any column information
+ } elseif (substr($cur_buffer_line, 0, 2) === '|-'
+ || substr($cur_buffer_line, 0, 2) === '|+'
+ || substr($cur_buffer_line, 0, 2) === '|}'
+ ) {
+ // Check begin row or end table
+
+ // Add current line to the values storage
+ if (! empty($cur_temp_line)) {
+ // If the current line contains header cells
+ // ( marked with '!' ),
+ // it will be marked as table header
+ if ( $in_table_header ) {
+ // Set the header columns
+ $cur_temp_table_headers = $cur_temp_line;
+ } else {
+ // Normal line, add it to the table
+ $cur_temp_table [] = $cur_temp_line;
+ }
+ }
+
+ // Empty the temporary buffer
+ $cur_temp_line = array();
+
+ // No more processing required at the end of the table
+ if (substr($cur_buffer_line, 0, 2) === '|}') {
+ $current_table = array(
+ $cur_table_name,
+ $cur_temp_table_headers,
+ $cur_temp_table
+ );
+
+ // Import the current table data into the database
+ $this->_importDataOneTable($current_table);
+
+ // Reset table name
+ $cur_table_name = "";
+ }
+ // What's after the row tag is now only attributes
+
+ } elseif (($first_character === '|') || ($first_character === '!')) {
+ // Check cell elements
+
+ // Header cells
+ if ($first_character === '!') {
+ // Mark as table header, but treat as normal row
+ $cur_buffer_line = str_replace('!!', '||', $cur_buffer_line);
+ // Will be used to set $cur_temp_line as table header
+ $in_table_header = true;
+ } else {
+ $in_table_header = false;
+ }
+
+ // Loop through each table cell
+ $cells = $this->_explodeMarkup($cur_buffer_line);
+ foreach ($cells as $cell) {
+ // A cell could contain both parameters and data
+ $cell_data = explode('|', $cell, 2);
+
+ // A '|' inside an invalid link should not
+ // be mistaken as delimiting cell parameters
+ if (strpos($cell_data[0], '[[') === true ) {
+ if (count($cell_data) == 1) {
+ $cell = $cell_data[0];
+ } else {
+ $cell = $cell_data[1];
+ }
+ }
+
+ // Delete the beginning of the column, if there is one
+ $cell = trim($cell);
+ $col_start_chars = array( "|", "!");
+ foreach ($col_start_chars as $col_start_char) {
+ if (strpos($cell, $col_start_char) === 0) {
+ $cell = trim(substr($cell, 1));
+ }
+ }
+
+ // Add the cell to the row
+ $cur_temp_line [] = $cell;
+ } // foreach $cells
+ } else {
+ // If it's none of the above, then the current line has a bad
+ // format
+ $message = PMA_Message::error(
+ __('Invalid format of mediawiki input on line: <br />%s.')
+ );
+ $message->addParam($cur_buffer_line);
+ $error = true;
+ }
+ } // End treating full buffer lines
+ } // while - finished parsing buffer
+ }
+
+ /**
+ * Imports data from a single table
+ *
+ * @param array $table containing all table info:
+ * <code>
+ * $table[0] - string containing table name
+ * $table[1] - array[] of table headers
+ * $table[2] - array[][] of table content rows
+ * </code>
+ *
+ * @global bool $analyze whether to scan for column types
+ *
+ * @return void
+ */
+ private function _importDataOneTable ($table)
+ {
+ $analyze = $this->_getAnalyze();
+ if ($analyze) {
+ // Set the table name
+ $this->_setTableName($table[0]);
+
+ // Set generic names for table headers if they don't exist
+ $this->_setTableHeaders($table[1], $table[2][0]);
+
+ // Create the tables array to be used in PMA_buildSQL()
+ $tables = array();
+ $tables [] = array($table[0], $table[1], $table[2]);
+
+ // Obtain the best-fit MySQL types for each column
+ $analyses = array();
+ $analyses [] = PMA_analyzeTable($tables[0]);
+
+ $this->_executeImportTables($tables, $analyses);
+ }
+
+ // Commit any possible data in buffers
+ PMA_importRunQuery();
+ }
+
+ /**
+ * Sets the table name
+ *
+ * @param string &$table_name reference to the name of the table
+ *
+ * @return void
+ */
+ private function _setTableName(&$table_name)
+ {
+ if (empty($table_name)) {
+ $result = $GLOBALS['dbi']->fetchResult('SHOW TABLES');
+ // todo check if the name below already exists
+ $table_name = 'TABLE '.(count($result) + 1);
+ }
+ }
+
+ /**
+ * Set generic names for table headers, if they don't exist
+ *
+ * @param array &$table_headers reference to the array containing the headers
+ * of a table
+ * @param array $table_row array containing the first content row
+ *
+ * @return void
+ */
+ private function _setTableHeaders(&$table_headers, $table_row)
+ {
+ if (empty($table_headers)) {
+ // The first table row should contain the number of columns
+ // If they are not set, generic names will be given (COL 1, COL 2, etc)
+ $num_cols = count($table_row);
+ for ($i = 0; $i < $num_cols; ++ $i) {
+ $table_headers [$i] = 'COL '. ($i + 1);
+ }
+ }
+ }
+
+ /**
+ * Sets the database name and additional options and calls PMA_buildSQL()
+ * Used in PMA_importDataAllTables() and $this->_importDataOneTable()
+ *
+ * @param array &$tables structure:
+ * array(
+ * array(table_name, array() column_names, array()() rows)
+ * )
+ * @param array &$analyses structure:
+ * $analyses = array(
+ * array(array() column_types, array() column_sizes)
+ * )
+ *
+ * @global string $db name of the database to import in
+ *
+ * @return void
+ */
+ private function _executeImportTables(&$tables, &$analyses)
+ {
+ global $db;
+
+ // $db_name : The currently selected database name, if applicable
+ // No backquotes
+ // $options : An associative array of options
+ if (strlen($db)) {
+ $db_name = $db;
+ $options = array('create_db' => false);
+ } else {
+ $db_name = 'mediawiki_DB';
+ $options = null;
+ }
+
+ // Array of SQL strings
+ // Non-applicable parameters
+ $create = null;
+
+ // Create and execute necessary SQL statements from data
+ PMA_buildSQL($db_name, $tables, $analyses, $create, $options);
+
+ unset($tables);
+ unset($analyses);
+ }
+
+
+ /**
+ * Replaces all instances of the '||' separator between delimiters
+ * in a given string
+ *
+ * @param string $start_delim start delimiter
+ * @param string $end_delim end delimiter
+ * @param string $replace the string to be replaced with
+ * @param string $subject the text to be replaced
+ *
+ * @return string with replacements
+ */
+ private function _delimiterReplace($start_delim, $end_delim, $replace, $subject)
+ {
+ // String that will be returned
+ $cleaned = "";
+ // Possible states of current character
+ $inside_tag = false;
+ $inside_attribute = false;
+ // Attributes can be declared with either " or '
+ $start_attribute_character = false;
+
+ // The full separator is "||";
+ // This rembembers if the previous character was '|'
+ $partial_separator = false;
+
+ // Parse text char by char
+ for ($i = 0; $i < strlen($subject); $i ++) {
+ $cur_char = $subject[$i];
+ // Check for separators
+ if ($cur_char == '|') {
+ // If we're not inside a tag, then this is part of a real separator,
+ // so we append it to the current segment
+ if (! $inside_attribute) {
+ $cleaned .= $cur_char;
+ if ($partial_separator) {
+ $inside_tag = false;
+ $inside_attribute = false;
+ }
+ } elseif ($partial_separator) {
+ // If we are inside a tag, we replace the current char with
+ // the placeholder and append that to the current segment
+ $cleaned .= $replace;
+ }
+
+ // If the previous character was also '|', then this ends a
+ // full separator. If not, this may be the beginning of one
+ $partial_separator = ! $partial_separator;
+ } else {
+ // If we're inside a tag attribute and the current character is
+ // not '|', but the previous one was, it means that the single '|'
+ // was not appended, so we append it now
+ if ($partial_separator && $inside_attribute) {
+ $cleaned .= "|";
+ }
+ // If the char is different from "|", no separator can be formed
+ $partial_separator = false;
+
+ // any other character should be appended to the current segment
+ $cleaned .= $cur_char;
+
+ if ($cur_char == '<' && ! $inside_attribute) {
+ // start of a tag
+ $inside_tag = true;
+ } elseif ($cur_char == '>' && ! $inside_attribute) {
+ // end of a tag
+ $inside_tag = false;
+ } elseif (($cur_char == '"' || $cur_char == "'") && $inside_tag) {
+ // start or end of an attribute
+ if (! $inside_attribute) {
+ $inside_attribute = true;
+ // remember the attribute`s declaration character (" or ')
+ $start_attribute_character = $cur_char;
+ } else {
+ if ($cur_char == $start_attribute_character) {
+ $inside_attribute = false;
+ // unset attribute declaration character
+ $start_attribute_character = false;
+ }
+ }
+ }
+ }
+ } // end for each character in $subject
+
+ return $cleaned;
+ }
+
+ /**
+ * Separates a string into items, similarly to explode
+ * Uses the '||' separator (which is standard in the mediawiki format)
+ * and ignores any instances of it inside markup tags
+ * Used in parsing buffer lines containing data cells
+ *
+ * @param string $text text to be split
+ *
+ * @return array
+ */
+ private function _explodeMarkup($text)
+ {
+ $separator = "||";
+ $placeholder = "\x00";
+
+ // Remove placeholder instances
+ $text = str_replace($placeholder, '', $text);
+
+ // Replace instances of the separator inside HTML-like
+ // tags with the placeholder
+ $cleaned = $this->_delimiterReplace("<", ">", $placeholder, $text);
+ // Explode, then put the replaced separators back in
+ $items = explode($separator, $cleaned);
+ foreach ($items as $i => $str) {
+ $items[$i] = str_replace($placeholder, $separator, $str);
+ }
+
+ return $items;
+ }
+
+
+ /* ~~~~~~~~~~~~~~~~~~~~ Getters and Setters ~~~~~~~~~~~~~~~~~~~~ */
+
+
+ /**
+ * Returns true if the table should be analyzed, false otherwise
+ *
+ * @return bool
+ */
+ private function _getAnalyze()
+ {
+ return $this->_analyze;
+ }
+
+ /**
+ * Sets to true if the table should be analyzed, false otherwise
+ *
+ * @param bool $analyze status
+ *
+ * @return void
+ */
+ private function _setAnalyze($analyze)
+ {
+ $this->_analyze = $analyze;
+ }
+}
diff --git a/libraries/plugins/import/ImportOds.class.php b/libraries/plugins/import/ImportOds.class.php
new file mode 100644
index 0000000000..2b07e88eea
--- /dev/null
+++ b/libraries/plugins/import/ImportOds.class.php
@@ -0,0 +1,415 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * OpenDocument Spreadsheet import plugin for phpMyAdmin
+ *
+ * @todo Pretty much everything
+ * @todo Importing of accented characters seems to fail
+ * @package PhpMyAdmin-Import
+ * @subpackage ODS
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ * We need way to disable external XML entities processing.
+ */
+if (! function_exists('libxml_disable_entity_loader')) {
+ $GLOBALS['skip_import'] = true;
+ return;
+}
+
+/* Get the import interface */
+require_once 'libraries/plugins/ImportPlugin.class.php';
+
+/**
+ * Handles the import for the ODS format
+ *
+ * @package PhpMyAdmin-Import
+ * @subpackage ODS
+ */
+class ImportOds extends ImportPlugin
+{
+ /**
+ * Constructor
+ */
+ public function __construct()
+ {
+ $this->setProperties();
+ }
+
+ /**
+ * Sets the import plugin properties.
+ * Called in the constructor.
+ *
+ * @return void
+ */
+ protected function setProperties()
+ {
+ $props = 'libraries/properties/';
+ include_once "$props/plugins/ImportPluginProperties.class.php";
+ include_once "$props/options/groups/OptionsPropertyRootGroup.class.php";
+ include_once "$props/options/groups/OptionsPropertyMainGroup.class.php";
+ include_once "$props/options/items/BoolPropertyItem.class.php";
+
+ $importPluginProperties = new ImportPluginProperties();
+ $importPluginProperties->setText('OpenDocument Spreadsheet');
+ $importPluginProperties->setExtension('ods');
+ $importPluginProperties->setOptionsText(__('Options'));
+
+ // create the root group that will be the options field for
+ // $importPluginProperties
+ // this will be shown as "Format specific options"
+ $importSpecificOptions = new OptionsPropertyRootGroup();
+ $importSpecificOptions->setName("Format Specific Options");
+
+ // general options main group
+ $generalOptions = new OptionsPropertyMainGroup();
+ $generalOptions->setName("general_opts");
+ // create primary items and add them to the group
+ $leaf = new BoolPropertyItem();
+ $leaf->setName("col_names");
+ $leaf->setText(
+ __(
+ 'The first line of the file contains the table column names'
+ . ' <i>(if this is unchecked, the first line will become part'
+ . ' of the data)</i>'
+ )
+ );
+ $generalOptions->addProperty($leaf);
+ $leaf = new BoolPropertyItem();
+ $leaf->setName("empty_rows");
+ $leaf->setText(__('Do not import empty rows'));
+ $generalOptions->addProperty($leaf);
+ $leaf = new BoolPropertyItem();
+ $leaf->setName("recognize_percentages");
+ $leaf->setText(
+ __(
+ 'Import percentages as proper decimals <i>(ex. 12.00% to .12)</i>'
+ )
+ );
+ $generalOptions->addProperty($leaf);
+ $leaf = new BoolPropertyItem();
+ $leaf->setName("recognize_currency");
+ $leaf->setText(__('Import currencies <i>(ex. $5.00 to 5.00)</i>'));
+ $generalOptions->addProperty($leaf);
+
+ // add the main group to the root group
+ $importSpecificOptions->addProperty($generalOptions);
+
+ // set the options for the import plugin property item
+ $importPluginProperties->setOptions($importSpecificOptions);
+ $this->properties = $importPluginProperties;
+ }
+
+ /**
+ * This method is called when any PluginManager to which the observer
+ * is attached calls PluginManager::notify()
+ *
+ * @param SplSubject $subject The PluginManager notifying the observer
+ * of an update.
+ *
+ * @return void
+ */
+ public function update (SplSubject $subject)
+ {
+ }
+
+ /**
+ * Handles the whole import logic
+ *
+ * @return void
+ */
+ public function doImport()
+ {
+ global $db, $error, $timeout_passed, $finished;
+
+ $i = 0;
+ $len = 0;
+ $buffer = "";
+
+ /**
+ * Read in the file via PMA_importGetNextChunk so that
+ * it can process compressed files
+ */
+ while (! ($finished && $i >= $len) && ! $error && ! $timeout_passed) {
+ $data = PMA_importGetNextChunk();
+ if ($data === false) {
+ /* subtract data we didn't handle yet and stop processing */
+ $GLOBALS['offset'] -= strlen($buffer);
+ break;
+ } elseif ($data === true) {
+ /* Handle rest of buffer */
+ } else {
+ /* Append new data to buffer */
+ $buffer .= $data;
+ unset($data);
+ }
+ }
+
+ unset($data);
+
+ /**
+ * Disable loading of external XML entities.
+ */
+ libxml_disable_entity_loader();
+
+ /**
+ * Load the XML string
+ *
+ * The option LIBXML_COMPACT is specified because it can
+ * result in increased performance without the need to
+ * alter the code in any way. It's basically a freebee.
+ */
+ $xml = simplexml_load_string($buffer, "SimpleXMLElement", LIBXML_COMPACT);
+
+ unset($buffer);
+
+ if ($xml === false) {
+ $sheets = array();
+ $GLOBALS['message'] = PMA_Message::error(
+ __(
+ 'The XML file specified was either malformed or incomplete.'
+ . ' Please correct the issue and try again.'
+ )
+ );
+ $GLOBALS['error'] = true;
+ } else {
+ $root = $xml->children('office', true)->{'body'}->{'spreadsheet'};
+ if (empty($root)) {
+ $sheets = array();
+ $GLOBALS['message'] = PMA_Message::error(
+ __('Could not parse OpenDocument Spreadsheet!')
+ );
+ $GLOBALS['error'] = true;
+ } else {
+ $sheets = $root->children('table', true);
+ }
+ }
+
+ $tables = array();
+
+ $max_cols = 0;
+
+ $col_count = 0;
+ $col_names = array();
+
+ $tempRow = array();
+ $tempRows = array();
+ $rows = array();
+
+ /* Iterate over tables */
+ foreach ($sheets as $sheet) {
+ $col_names_in_first_row = isset($_REQUEST['ods_col_names']);
+
+ /* Iterate over rows */
+ foreach ($sheet as $row) {
+ $type = $row->getName();
+ if (! strcmp('table-row', $type)) {
+ /* Iterate over columns */
+ foreach ($row as $cell) {
+ $text = $cell->children('text', true);
+ $cell_attrs = $cell->attributes('office', true);
+
+ if (count($text) != 0) {
+ $attr = $cell->attributes('table', true);
+ $num_repeat = (int) $attr['number-columns-repeated'];
+ $num_iterations = $num_repeat ? $num_repeat : 1;
+
+ for ($k = 0; $k < $num_iterations; $k++) {
+ if ($_REQUEST['ods_recognize_percentages']
+ && ! strcmp(
+ 'percentage',
+ $cell_attrs['value-type']
+ )
+ ) {
+ $value = (double)$cell_attrs['value'];
+ } elseif ($_REQUEST['ods_recognize_currency']
+ && !strcmp('currency', $cell_attrs['value-type'])
+ ) {
+ $value = (double)$cell_attrs['value'];
+ } else {
+ /* We need to concatenate all paragraphs */
+ $values = array();
+ foreach ($text as $paragraph) {
+ $values[] = (string)$paragraph;
+ }
+ $value = implode("\n", $values);
+ }
+ if (! $col_names_in_first_row) {
+ $tempRow[] = $value;
+ } else {
+ $col_names[] = $value;
+ }
+
+ ++$col_count;
+ }
+ } else {
+ /* Number of blank columns repeated */
+ if ($col_count < count($row->children('table', true)) - 1
+ ) {
+ $attr = $cell->attributes('table', true);
+ $num_null = (int)$attr['number-columns-repeated'];
+
+ if ($num_null) {
+ if (! $col_names_in_first_row) {
+ for ($i = 0; $i < $num_null; ++$i) {
+ $tempRow[] = 'NULL';
+ ++$col_count;
+ }
+ } else {
+ for ($i = 0; $i < $num_null; ++$i) {
+ $col_names[] = PMA_getColumnAlphaName(
+ $col_count + 1
+ );
+ ++$col_count;
+ }
+ }
+ } else {
+ if (! $col_names_in_first_row) {
+ $tempRow[] = 'NULL';
+ } else {
+ $col_names[] = PMA_getColumnAlphaName(
+ $col_count + 1
+ );
+ }
+
+ ++$col_count;
+ }
+ }
+ }
+ }
+
+ /* Find the widest row */
+ if ($col_count > $max_cols) {
+ $max_cols = $col_count;
+ }
+
+ /* Don't include a row that is full of NULL values */
+ if (! $col_names_in_first_row) {
+ if ($_REQUEST['ods_empty_rows']) {
+ foreach ($tempRow as $cell) {
+ if (strcmp('NULL', $cell)) {
+ $tempRows[] = $tempRow;
+ break;
+ }
+ }
+ } else {
+ $tempRows[] = $tempRow;
+ }
+ }
+
+ $col_count = 0;
+ $col_names_in_first_row = false;
+ $tempRow = array();
+ }
+ }
+
+ /* Skip over empty sheets */
+ if (count($tempRows) == 0 || count($tempRows[0]) == 0) {
+ $col_names = array();
+ $tempRow = array();
+ $tempRows = array();
+ continue;
+ }
+
+ /**
+ * Fill out each row as necessary to make
+ * every one exactly as wide as the widest
+ * row. This included column names.
+ */
+
+ /* Fill out column names */
+ for ($i = count($col_names); $i < $max_cols; ++$i) {
+ $col_names[] = PMA_getColumnAlphaName($i + 1);
+ }
+
+ /* Fill out all rows */
+ $num_rows = count($tempRows);
+ for ($i = 0; $i < $num_rows; ++$i) {
+ for ($j = count($tempRows[$i]); $j < $max_cols; ++$j) {
+ $tempRows[$i][] = 'NULL';
+ }
+ }
+
+ /* Store the table name so we know where to place the row set */
+ $tbl_attr = $sheet->attributes('table', true);
+ $tables[] = array((string)$tbl_attr['name']);
+
+ /* Store the current sheet in the accumulator */
+ $rows[] = array((string)$tbl_attr['name'], $col_names, $tempRows);
+ $tempRows = array();
+ $col_names = array();
+ $max_cols = 0;
+ }
+
+ unset($tempRow);
+ unset($tempRows);
+ unset($col_names);
+ unset($sheets);
+ unset($xml);
+
+ /**
+ * Bring accumulated rows into the corresponding table
+ */
+ $num_tbls = count($tables);
+ for ($i = 0; $i < $num_tbls; ++$i) {
+ for ($j = 0; $j < count($rows); ++$j) {
+ if (! strcmp($tables[$i][TBL_NAME], $rows[$j][TBL_NAME])) {
+ if (! isset($tables[$i][COL_NAMES])) {
+ $tables[$i][] = $rows[$j][COL_NAMES];
+ }
+
+ $tables[$i][ROWS] = $rows[$j][ROWS];
+ }
+ }
+ }
+
+ /* No longer needed */
+ unset($rows);
+
+ /* Obtain the best-fit MySQL types for each column */
+ $analyses = array();
+
+ $len = count($tables);
+ for ($i = 0; $i < $len; ++$i) {
+ $analyses[] = PMA_analyzeTable($tables[$i]);
+ }
+
+ /**
+ * string $db_name (no backquotes)
+ *
+ * array $table = array(table_name, array() column_names, array()() rows)
+ * array $tables = array of "$table"s
+ *
+ * array $analysis = array(array() column_types, array() column_sizes)
+ * array $analyses = array of "$analysis"s
+ *
+ * array $create = array of SQL strings
+ *
+ * array $options = an associative array of options
+ */
+
+ /* Set database name to the currently selected one, if applicable */
+ if (strlen($db)) {
+ $db_name = $db;
+ $options = array('create_db' => false);
+ } else {
+ $db_name = 'ODS_DB';
+ $options = null;
+ }
+
+ /* Non-applicable parameters */
+ $create = null;
+
+ /* Created and execute necessary SQL statements from data */
+ PMA_buildSQL($db_name, $tables, $analyses, $create, $options);
+
+ unset($tables);
+ unset($analyses);
+
+ /* Commit any possible data in buffers */
+ PMA_importRunQuery();
+ }
+}
diff --git a/libraries/plugins/import/ImportShp.class.php b/libraries/plugins/import/ImportShp.class.php
new file mode 100644
index 0000000000..7a4c0c8375
--- /dev/null
+++ b/libraries/plugins/import/ImportShp.class.php
@@ -0,0 +1,335 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * ESRI Shape file import plugin for phpMyAdmin
+ *
+ * @package PhpMyAdmin-Import
+ * @subpackage ESRI_Shape
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+// Drizzle does not support GIS data types
+if (PMA_DRIZZLE) {
+ $GLOBALS['skip_import'] = true;
+ return;
+}
+
+/* Get the import interface*/
+require_once 'libraries/plugins/ImportPlugin.class.php';
+/* Get the ShapeFile class */
+require_once 'libraries/bfShapeFiles/ShapeFile.lib.php';
+require_once 'libraries/plugins/import/ShapeFile.class.php';
+require_once 'libraries/plugins/import/ShapeRecord.class.php';
+
+/**
+ * Handles the import for ESRI Shape files
+ *
+ * @package PhpMyAdmin-Import
+ * @subpackage ESRI_Shape
+ */
+class ImportShp extends ImportPlugin
+{
+ /**
+ * Constructor
+ */
+ public function __construct()
+ {
+ $this->setProperties();
+ }
+
+ /**
+ * Sets the import plugin properties.
+ * Called in the constructor.
+ *
+ * @return void
+ */
+ protected function setProperties()
+ {
+ $props = 'libraries/properties/';
+ include_once "$props/plugins/ImportPluginProperties.class.php";
+
+ $importPluginProperties = new ImportPluginProperties();
+ $importPluginProperties->setText(__('ESRI Shape File'));
+ $importPluginProperties->setExtension('shp');
+ $importPluginProperties->setOptions(array());
+ $importPluginProperties->setOptionsText(__('Options'));
+
+ $this->properties = $importPluginProperties;
+ }
+
+ /**
+ * This method is called when any PluginManager to which the observer
+ * is attached calls PluginManager::notify()
+ *
+ * @param SplSubject $subject The PluginManager notifying the observer
+ * of an update.
+ *
+ * @return void
+ */
+ public function update (SplSubject $subject)
+ {
+ }
+
+ /**
+ * Handles the whole import logic
+ *
+ * @return void
+ */
+ public function doImport()
+ {
+ global $db, $error, $finished, $compression,
+ $import_file, $local_import_file, $message;
+
+ $GLOBALS['finished'] = false;
+
+ $shp = new PMA_ShapeFile(1);
+ // If the zip archive has more than one file,
+ // get the correct content to the buffer from .shp file.
+ if ($compression == 'application/zip'
+ && PMA_getNoOfFilesInZip($import_file) > 1
+ ) {
+ $zip_content = PMA_getZipContents($import_file, '/^.*\.shp$/i');
+ $GLOBALS['import_text'] = $zip_content['data'];
+ }
+
+ $temp_dbf_file = false;
+ // We need dbase extension to handle .dbf file
+ if (extension_loaded('dbase')) {
+ // If we can extract the zip archive to 'TempDir'
+ // and use the files in it for import
+ if ($compression == 'application/zip'
+ && ! empty($GLOBALS['cfg']['TempDir'])
+ && is_writable($GLOBALS['cfg']['TempDir'])
+ ) {
+ $dbf_file_name = PMA_findFileFromZipArchive(
+ '/^.*\.dbf$/i', $import_file
+ );
+ // If the corresponding .dbf file is in the zip archive
+ if ($dbf_file_name) {
+ // Extract the .dbf file and point to it.
+ $extracted = PMA_zipExtract(
+ $import_file,
+ realpath($GLOBALS['cfg']['TempDir']),
+ array($dbf_file_name)
+ );
+ if ($extracted) {
+ $dbf_file_path = realpath($GLOBALS['cfg']['TempDir'])
+ . (PMA_IS_WINDOWS ? '\\' : '/') . $dbf_file_name;
+ $temp_dbf_file = true;
+ // Replace the .dbf with .*, as required
+ // by the bsShapeFiles library.
+ $file_name = substr(
+ $dbf_file_path, 0, strlen($dbf_file_path) - 4
+ ) . '.*';
+ $shp->FileName = $file_name;
+ }
+ }
+ } elseif (! empty($local_import_file)
+ && ! empty($GLOBALS['cfg']['UploadDir'])
+ && $compression == 'none'
+ ) {
+ // If file is in UploadDir, use .dbf file in the same UploadDir
+ // to load extra data.
+ // Replace the .shp with .*,
+ // so the bsShapeFiles library correctly locates .dbf file.
+ $file_name = substr($import_file, 0, strlen($import_file) - 4)
+ . '.*';
+ $shp->FileName = $file_name;
+ }
+ }
+
+ // Load data
+ $shp->loadFromFile('');
+ if ($shp->lastError != "") {
+ $error = true;
+ $message = PMA_Message::error(
+ __('There was an error importing the ESRI shape file: "%s".')
+ );
+ $message->addParam($shp->lastError);
+ return;
+ }
+
+ // Delete the .dbf file extracted to 'TempDir'
+ if ($temp_dbf_file
+ && isset($dbf_file_path)
+ && file_exists($dbf_file_path)
+ ) {
+ unlink($dbf_file_path);
+ }
+
+ $esri_types = array(
+ 0 => 'Null Shape',
+ 1 => 'Point',
+ 3 => 'PolyLine',
+ 5 => 'Polygon',
+ 8 => 'MultiPoint',
+ 11 => 'PointZ',
+ 13 => 'PolyLineZ',
+ 15 => 'PolygonZ',
+ 18 => 'MultiPointZ',
+ 21 => 'PointM',
+ 23 => 'PolyLineM',
+ 25 => 'PolygonM',
+ 28 => 'MultiPointM',
+ 31 => 'MultiPatch',
+ );
+
+ switch ($shp->shapeType) {
+ // ESRI Null Shape
+ case 0:
+ break;
+ // ESRI Point
+ case 1:
+ $gis_type = 'point';
+ break;
+ // ESRI PolyLine
+ case 3:
+ $gis_type = 'multilinestring';
+ break;
+ // ESRI Polygon
+ case 5:
+ $gis_type = 'multipolygon';
+ break;
+ // ESRI MultiPoint
+ case 8:
+ $gis_type = 'multipoint';
+ break;
+ default:
+ $error = true;
+ if (! isset($esri_types[$shp->shapeType])) {
+ $message = PMA_Message::error(
+ __(
+ 'You tried to import an invalid file or the imported file'
+ . ' contains invalid data'
+ )
+ );
+ } else {
+ $message = PMA_Message::error(
+ __('MySQL Spatial Extension does not support ESRI type "%s".')
+ );
+ $message->addParam($esri_types[$shp->shapeType]);
+ }
+ return;
+ }
+
+ if (isset($gis_type)) {
+ include_once './libraries/gis/pma_gis_factory.php';
+ $gis_obj = PMA_GIS_Factory::factory($gis_type);
+ } else {
+ $gis_obj = null;
+ }
+
+ $num_rows = count($shp->records);
+ // If .dbf file is loaded, the number of extra data columns
+ $num_data_cols = isset($shp->DBFHeader) ? count($shp->DBFHeader) : 0;
+
+ $rows = array();
+ $col_names = array();
+ if ($num_rows != 0) {
+ foreach ($shp->records as $record) {
+ $tempRow = array();
+ if ($gis_obj == null) {
+ $tempRow[] = null;
+ } else {
+ $tempRow[] = "GeomFromText('"
+ . $gis_obj->getShape($record->SHPData) . "')";
+ }
+
+ if (isset($shp->DBFHeader)) {
+ foreach ($shp->DBFHeader as $c) {
+ $cell = trim($record->DBFData[$c[0]]);
+
+ if (! strcmp($cell, '')) {
+ $cell = 'NULL';
+ }
+
+ $tempRow[] = $cell;
+ }
+ }
+ $rows[] = $tempRow;
+ }
+ }
+
+ if (count($rows) == 0) {
+ $error = true;
+ $message = PMA_Message::error(
+ __('The imported file does not contain any data')
+ );
+ return;
+ }
+
+ // Column names for spatial column and the rest of the columns,
+ // if they are available
+ $col_names[] = 'SPATIAL';
+ for ($n = 0; $n < $num_data_cols; $n++) {
+ $col_names[] = $shp->DBFHeader[$n][0];
+ }
+
+ // Set table name based on the number of tables
+ if (strlen($db)) {
+ $result = $GLOBALS['dbi']->fetchResult('SHOW TABLES');
+ $table_name = 'TABLE '.(count($result) + 1);
+ } else {
+ $table_name = 'TBL_NAME';
+ }
+ $tables = array(array($table_name, $col_names, $rows));
+
+ // Use data from shape file to chose best-fit MySQL types for each column
+ $analyses = array();
+ $analyses[] = PMA_analyzeTable($tables[0]);
+
+ $table_no = 0; $spatial_col = 0;
+ $analyses[$table_no][TYPES][$spatial_col] = GEOMETRY;
+ $analyses[$table_no][FORMATTEDSQL][$spatial_col] = true;
+
+ // Set database name to the currently selected one, if applicable
+ if (strlen($db)) {
+ $db_name = $db;
+ $options = array('create_db' => false);
+ } else {
+ $db_name = 'SHP_DB';
+ $options = null;
+ }
+
+ // Created and execute necessary SQL statements from data
+ $null_param = null;
+ PMA_buildSQL($db_name, $tables, $analyses, $null_param, $options);
+
+ unset($tables);
+ unset($analyses);
+
+ $finished = true;
+ $error = false;
+
+ // Commit any possible data in buffers
+ PMA_importRunQuery();
+ }
+
+ /**
+ * Returns specified number of bytes from the buffer.
+ * Buffer automatically fetches next chunk of data when the buffer
+ * falls short.
+ * Sets $eof when $GLOBALS['finished'] is set and the buffer falls short.
+ *
+ * @param int $length number of bytes
+ *
+ * @return string
+ */
+ public static function readFromBuffer($length)
+ {
+ global $buffer, $eof;
+
+ if (strlen($buffer) < $length) {
+ if ($GLOBALS['finished']) {
+ $eof = true;
+ } else {
+ $buffer .= PMA_importGetNextChunk();
+ }
+ }
+ $result = substr($buffer, 0, $length);
+ $buffer = substr($buffer, $length);
+ return $result;
+ }
+}
diff --git a/libraries/plugins/import/ImportSql.class.php b/libraries/plugins/import/ImportSql.class.php
new file mode 100644
index 0000000000..4ae9024a40
--- /dev/null
+++ b/libraries/plugins/import/ImportSql.class.php
@@ -0,0 +1,447 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * SQL import plugin for phpMyAdmin
+ *
+ * @package PhpMyAdmin-Import
+ * @subpackage SQL
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/* Get the import interface */
+require_once 'libraries/plugins/ImportPlugin.class.php';
+
+/**
+ * Handles the import for the SQL format
+ *
+ * @package PhpMyAdmin-Import
+ * @subpackage SQL
+ */
+class ImportSql extends ImportPlugin
+{
+ /**
+ * Constructor
+ */
+ public function __construct()
+ {
+ $this->setProperties();
+ }
+
+ /**
+ * Sets the import plugin properties.
+ * Called in the constructor.
+ *
+ * @return void
+ */
+ protected function setProperties()
+ {
+ $props = 'libraries/properties/';
+ include_once "$props/plugins/ImportPluginProperties.class.php";
+ include_once "$props/options/groups/OptionsPropertyRootGroup.class.php";
+ include_once "$props/options/groups/OptionsPropertyMainGroup.class.php";
+ include_once "$props/options/items/SelectPropertyItem.class.php";
+ include_once "$props/options/items/BoolPropertyItem.class.php";
+
+ $importPluginProperties = new ImportPluginProperties();
+ $importPluginProperties->setText('SQL');
+ $importPluginProperties->setExtension('sql');
+ $importPluginProperties->setOptionsText(__('Options'));
+
+ $compats = $GLOBALS['dbi']->getCompatibilities();
+ if (count($compats) > 0) {
+ $values = array();
+ foreach ($compats as $val) {
+ $values[$val] = $val;
+ }
+
+ // create the root group that will be the options field for
+ // $importPluginProperties
+ // this will be shown as "Format specific options"
+ $importSpecificOptions = new OptionsPropertyRootGroup();
+ $importSpecificOptions->setName("Format Specific Options");
+
+ // general options main group
+ $generalOptions = new OptionsPropertyMainGroup();
+ $generalOptions->setName("general_opts");
+ // create primary items and add them to the group
+ $leaf = new SelectPropertyItem();
+ $leaf->setName("compatibility");
+ $leaf->setText(__('SQL compatibility mode:'));
+ $leaf->setValues($values);
+ $leaf->setDoc(
+ array(
+ 'manual_MySQL_Database_Administration',
+ 'Server_SQL_mode',
+ )
+ );
+ $generalOptions->addProperty($leaf);
+ $leaf = new BoolPropertyItem();
+ $leaf->setName("no_auto_value_on_zero");
+ $leaf->setText(
+ __('Do not use <code>AUTO_INCREMENT</code> for zero values')
+ );
+ $leaf->setDoc(
+ array(
+ 'manual_MySQL_Database_Administration',
+ 'Server_SQL_mode',
+ 'sqlmode_no_auto_value_on_zero'
+ )
+ );
+ $generalOptions->addProperty($leaf);
+
+ // add the main group to the root group
+ $importSpecificOptions->addProperty($generalOptions);
+ // set the options for the import plugin property item
+ $importPluginProperties->setOptions($importSpecificOptions);
+ }
+
+ $this->properties = $importPluginProperties;
+ }
+
+ /**
+ * This method is called when any PluginManager to which the observer
+ * is attached calls PluginManager::notify()
+ *
+ * @param SplSubject $subject The PluginManager notifying the observer
+ * of an update.
+ *
+ * @return void
+ */
+ public function update (SplSubject $subject)
+ {
+ }
+
+ /**
+ * Handles the whole import logic
+ *
+ * @param array &$sql_data 2-element array with sql data
+ *
+ * @return void
+ */
+ public function doImport(&$sql_data = array())
+ {
+ global $error, $timeout_passed;
+
+ $buffer = '';
+ // Defaults for parser
+ $sql = '';
+ $start_pos = 0;
+ $i = 0;
+ $len= 0;
+ $big_value = 2147483647;
+ // include the space because it's mandatory
+ $delimiter_keyword = 'DELIMITER ';
+ $length_of_delimiter_keyword = strlen($delimiter_keyword);
+
+ if (isset($_POST['sql_delimiter'])) {
+ $sql_delimiter = $_POST['sql_delimiter'];
+ } else {
+ $sql_delimiter = ';';
+ }
+
+ // Handle compatibility options
+ $sql_modes = array();
+ if (isset($_REQUEST['sql_compatibility'])
+ && 'NONE' != $_REQUEST['sql_compatibility']
+ ) {
+ $sql_modes[] = $_REQUEST['sql_compatibility'];
+ }
+ if (isset($_REQUEST['sql_no_auto_value_on_zero'])) {
+ $sql_modes[] = 'NO_AUTO_VALUE_ON_ZERO';
+ }
+ if (count($sql_modes) > 0) {
+ $GLOBALS['dbi']->tryQuery(
+ 'SET SQL_MODE="' . implode(',', $sql_modes) . '"'
+ );
+ }
+ unset($sql_modes);
+
+ /**
+ * will be set in PMA_importGetNextChunk()
+ *
+ * @global boolean $GLOBALS['finished']
+ */
+ $GLOBALS['finished'] = false;
+
+ while (! ($GLOBALS['finished'] && $i >= $len)
+ && ! $error
+ && ! $timeout_passed
+ ) {
+ $data = PMA_importGetNextChunk();
+ if ($data === false) {
+ // subtract data we didn't handle yet and stop processing
+ $GLOBALS['offset'] -= strlen($buffer);
+ break;
+ } elseif ($data === true) {
+ // Handle rest of buffer
+ } else {
+ // Append new data to buffer
+ $buffer .= $data;
+ // free memory
+ unset($data);
+ // Do not parse string when we're not at the end
+ // and don't have ; inside
+ if ((strpos($buffer, $sql_delimiter, $i) === false)
+ && ! $GLOBALS['finished']
+ ) {
+ continue;
+ }
+ }
+
+ // Convert CR (but not CRLF) to LF otherwise all queries
+ // may not get executed on some platforms
+ $buffer = preg_replace("/\r($|[^\n])/", "\n$1", $buffer);
+
+ // Current length of our buffer
+ $len = strlen($buffer);
+
+ // Grab some SQL queries out of it
+ while ($i < $len) {
+ $found_delimiter = false;
+ // Find first interesting character
+ $old_i = $i;
+ // this is about 7 times faster that looking for each sequence i
+ // one by one with strpos()
+ $match = preg_match(
+ '/(\'|"|#|-- |\/\*|`|(?i)(?<![A-Z0-9_])'
+ . $delimiter_keyword . ')/',
+ $buffer,
+ $matches,
+ PREG_OFFSET_CAPTURE,
+ $i
+ );
+ if ($match) {
+ // in $matches, index 0 contains the match for the complete
+ // expression but we don't use it
+ $first_position = $matches[1][1];
+ } else {
+ $first_position = $big_value;
+ }
+ /**
+ * @todo we should not look for a delimiter that might be
+ * inside quotes (or even double-quotes)
+ */
+ // the cost of doing this one with preg_match() would be too high
+ $first_sql_delimiter = strpos($buffer, $sql_delimiter, $i);
+ if ($first_sql_delimiter === false) {
+ $first_sql_delimiter = $big_value;
+ } else {
+ $found_delimiter = true;
+ }
+
+ // set $i to the position of the first quote,
+ // comment.start or delimiter found
+ $i = min($first_position, $first_sql_delimiter);
+
+ if ($i == $big_value) {
+ // none of the above was found in the string
+
+ $i = $old_i;
+ if (! $GLOBALS['finished']) {
+ break;
+ }
+ // at the end there might be some whitespace...
+ if (trim($buffer) == '') {
+ $buffer = '';
+ $len = 0;
+ break;
+ }
+ // We hit end of query, go there!
+ $i = strlen($buffer) - 1;
+ }
+
+ // Grab current character
+ $ch = $buffer[$i];
+
+ // Quotes
+ if (strpos('\'"`', $ch) !== false) {
+ $quote = $ch;
+ $endq = false;
+ while (! $endq) {
+ // Find next quote
+ $pos = strpos($buffer, $quote, $i + 1);
+ /*
+ * Behave same as MySQL and accept end of query as end
+ * of backtick.
+ * I know this is sick, but MySQL behaves like this:
+ *
+ * SELECT * FROM `table
+ *
+ * is treated like
+ *
+ * SELECT * FROM `table`
+ */
+ if ($pos === false && $quote == '`' && $found_delimiter) {
+ $pos = $first_sql_delimiter - 1;
+ } elseif ($pos === false) { // No quote? Too short string
+ // We hit end of string => unclosed quote,
+ // but we handle it as end of query
+ if ($GLOBALS['finished']) {
+ $endq = true;
+ $i = $len - 1;
+ }
+ $found_delimiter = false;
+ break;
+ }
+ // Was not the quote escaped?
+ $j = $pos - 1;
+ while ($buffer[$j] == '\\') {
+ $j--;
+ }
+ // Even count means it was not escaped
+ $endq = (((($pos - 1) - $j) % 2) == 0);
+ // Skip the string
+ $i = $pos;
+
+ if ($first_sql_delimiter < $pos) {
+ $found_delimiter = false;
+ }
+ }
+ if (! $endq) {
+ break;
+ }
+ $i++;
+ // Aren't we at the end?
+ if ($GLOBALS['finished'] && $i == $len) {
+ $i--;
+ } else {
+ continue;
+ }
+ }
+
+ // Not enough data to decide
+ if ((($i == ($len - 1) && ($ch == '-' || $ch == '/'))
+ || ($i == ($len - 2) && (($ch == '-' && $buffer[$i + 1] == '-')
+ || ($ch == '/' && $buffer[$i + 1] == '*'))))
+ && ! $GLOBALS['finished']
+ ) {
+ break;
+ }
+
+ // Comments
+ if ($ch == '#'
+ || ($i < ($len - 1) && $ch == '-' && $buffer[$i + 1] == '-'
+ && (($i < ($len - 2) && $buffer[$i + 2] <= ' ')
+ || ($i == ($len - 1) && $GLOBALS['finished'])))
+ || ($i < ($len - 1) && $ch == '/' && $buffer[$i + 1] == '*')
+ ) {
+ // Copy current string to SQL
+ if ($start_pos != $i) {
+ $sql .= substr($buffer, $start_pos, $i - $start_pos);
+ }
+ // Skip the rest
+ $start_of_comment = $i;
+ // do not use PHP_EOL here instead of "\n", because the export
+ // file might have been produced on a different system
+ $i = strpos($buffer, $ch == '/' ? '*/' : "\n", $i);
+ // didn't we hit end of string?
+ if ($i === false) {
+ if ($GLOBALS['finished']) {
+ $i = $len - 1;
+ } else {
+ break;
+ }
+ }
+ // Skip *
+ if ($ch == '/') {
+ $i++;
+ }
+ // Skip last char
+ $i++;
+ // We need to send the comment part in case we are defining
+ // a procedure or function and comments in it are valuable
+ $sql .= substr(
+ $buffer,
+ $start_of_comment,
+ $i - $start_of_comment
+ );
+ // Next query part will start here
+ $start_pos = $i;
+ // Aren't we at the end?
+ if ($i == $len) {
+ $i--;
+ } else {
+ continue;
+ }
+ }
+ // Change delimiter, if redefined, and skip it
+ // (don't send to server!)
+ if (($i + $length_of_delimiter_keyword < $len)
+ && strtoupper(
+ substr($buffer, $i, $length_of_delimiter_keyword)
+ ) == $delimiter_keyword
+ ) {
+ // look for EOL on the character immediately after 'DELIMITER '
+ // (see previous comment about PHP_EOL)
+ $new_line_pos = strpos(
+ $buffer,
+ "\n",
+ $i + $length_of_delimiter_keyword
+ );
+ // it might happen that there is no EOL
+ if (false === $new_line_pos) {
+ $new_line_pos = $len;
+ }
+ $sql_delimiter = substr(
+ $buffer,
+ $i + $length_of_delimiter_keyword,
+ $new_line_pos - $i - $length_of_delimiter_keyword
+ );
+ $i = $new_line_pos + 1;
+ // Next query part will start here
+ $start_pos = $i;
+ continue;
+ }
+
+ // End of SQL
+ if ($found_delimiter
+ || ($GLOBALS['finished']
+ && ($i == $len - 1))
+ ) {
+ $tmp_sql = $sql;
+ if ($start_pos < $len) {
+ $length_to_grab = $i - $start_pos;
+
+ if (! $found_delimiter) {
+ $length_to_grab++;
+ }
+ $tmp_sql .= substr($buffer, $start_pos, $length_to_grab);
+ unset($length_to_grab);
+ }
+ // Do not try to execute empty SQL
+ if (! preg_match('/^([\s]*;)*$/', trim($tmp_sql))) {
+ $sql = $tmp_sql;
+ PMA_importRunQuery(
+ $sql,
+ substr($buffer, 0, $i + strlen($sql_delimiter)),
+ false,
+ $sql_data
+ );
+ $buffer = substr($buffer, $i + strlen($sql_delimiter));
+ // Reset parser:
+ $len = strlen($buffer);
+ $sql = '';
+ $i = 0;
+ $start_pos = 0;
+ // Any chance we will get a complete query?
+ //if ((strpos($buffer, ';') === false)
+ //&& ! $GLOBALS['finished']) {
+ if (strpos($buffer, $sql_delimiter) === false
+ && ! $GLOBALS['finished']
+ ) {
+ break;
+ }
+ } else {
+ $i++;
+ $start_pos = $i;
+ }
+ }
+ } // End of parser loop
+ } // End of import loop
+ // Commit any possible data in buffers
+ PMA_importRunQuery('', substr($buffer, 0, $len), false, $sql_data);
+ PMA_importRunQuery('', '', false, $sql_data);
+ }
+}
diff --git a/libraries/plugins/import/ImportXml.class.php b/libraries/plugins/import/ImportXml.class.php
new file mode 100644
index 0000000000..7c7afa099e
--- /dev/null
+++ b/libraries/plugins/import/ImportXml.class.php
@@ -0,0 +1,379 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * XML import plugin for phpMyAdmin
+ *
+ * @todo Improve efficiency
+ * @package PhpMyAdmin-Import
+ * @subpackage XML
+ */
+
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ * We need way to disable external XML entities processing.
+ */
+if (! function_exists('libxml_disable_entity_loader')) {
+ $GLOBALS['skip_import'] = true;
+ return;
+}
+
+/* Get the import interface */
+require_once 'libraries/plugins/ImportPlugin.class.php';
+
+/**
+ * Handles the import for the XML format
+ *
+ * @package PhpMyAdmin-Import
+ * @subpackage XML
+ */
+class ImportXml extends ImportPlugin
+{
+ /**
+ * Constructor
+ */
+ public function __construct()
+ {
+ $this->setProperties();
+ }
+
+ /**
+ * Sets the import plugin properties.
+ * Called in the constructor.
+ *
+ * @return void
+ */
+ protected function setProperties()
+ {
+ $props = 'libraries/properties/';
+ include_once "$props/plugins/ImportPluginProperties.class.php";
+
+ $importPluginProperties = new ImportPluginProperties();
+ $importPluginProperties->setText(__('XML'));
+ $importPluginProperties->setExtension('xml');
+ $importPluginProperties->setMimeType('text/xml');
+ $importPluginProperties->setOptions(array());
+ $importPluginProperties->setOptionsText(__('Options'));
+
+ $this->properties = $importPluginProperties;
+ }
+
+ /**
+ * This method is called when any PluginManager to which the observer
+ * is attached calls PluginManager::notify()
+ *
+ * @param SplSubject $subject The PluginManager notifying the observer
+ * of an update.
+ *
+ * @return void
+ */
+ public function update (SplSubject $subject)
+ {
+ }
+
+ /**
+ * Handles the whole import logic
+ *
+ * @return void
+ */
+ public function doImport()
+ {
+ global $error, $timeout_passed, $finished, $db;
+
+ $i = 0;
+ $len = 0;
+ $buffer = "";
+
+ /**
+ * Read in the file via PMA_importGetNextChunk so that
+ * it can process compressed files
+ */
+ while (! ($finished && $i >= $len) && ! $error && ! $timeout_passed) {
+ $data = PMA_importGetNextChunk();
+ if ($data === false) {
+ /* subtract data we didn't handle yet and stop processing */
+ $GLOBALS['offset'] -= strlen($buffer);
+ break;
+ } elseif ($data === true) {
+ /* Handle rest of buffer */
+ } else {
+ /* Append new data to buffer */
+ $buffer .= $data;
+ unset($data);
+ }
+ }
+
+ unset($data);
+
+ /**
+ * Disable loading of external XML entities.
+ */
+ libxml_disable_entity_loader();
+
+ /**
+ * Load the XML string
+ *
+ * The option LIBXML_COMPACT is specified because it can
+ * result in increased performance without the need to
+ * alter the code in any way. It's basically a freebee.
+ */
+ $xml = simplexml_load_string($buffer, "SimpleXMLElement", LIBXML_COMPACT);
+
+ unset($buffer);
+
+ /**
+ * The XML was malformed
+ */
+ if ($xml === false) {
+ PMA_Message::error(
+ __(
+ 'The XML file specified was either malformed or incomplete.'
+ . ' Please correct the issue and try again.'
+ )
+ )->display();
+ unset($xml);
+ $GLOBALS['finished'] = false;
+ return;
+ }
+
+ /**
+ * Table accumulator
+ */
+ $tables = array();
+ /**
+ * Row accumulator
+ */
+ $rows = array();
+
+ /**
+ * Temp arrays
+ */
+ $tempRow = array();
+ $tempCells = array();
+
+ /**
+ * CREATE code included (by default: no)
+ */
+ $struct_present = false;
+
+ /**
+ * Analyze the data in each table
+ */
+ $namespaces = $xml->getNameSpaces(true);
+
+ /**
+ * Get the database name, collation and charset
+ */
+ $db_attr = $xml->children($namespaces['pma'])
+ ->{'structure_schemas'}->{'database'};
+
+ if ($db_attr instanceof SimpleXMLElement) {
+ $db_attr = $db_attr->attributes();
+ $db_name = (string)$db_attr['name'];
+ $collation = (string)$db_attr['collation'];
+ $charset = (string)$db_attr['charset'];
+ } else {
+ /**
+ * If the structure section is not present
+ * get the database name from the data section
+ */
+ $db_attr = $xml->children()->attributes();
+ $db_name = (string)$db_attr['name'];
+ $collation = null;
+ $charset = null;
+ }
+
+ /**
+ * The XML was malformed
+ */
+ if ($db_name === null) {
+ PMA_Message::error(
+ __(
+ 'The XML file specified was either malformed or incomplete.'
+ . ' Please correct the issue and try again.'
+ )
+ )->display();
+ unset($xml);
+ $GLOBALS['finished'] = false;
+ return;
+ }
+
+ /**
+ * Retrieve the structure information
+ */
+ if (isset($namespaces['pma'])) {
+ /**
+ * Get structures for all tables
+ */
+ $struct = $xml->children($namespaces['pma']);
+
+ $create = array();
+
+ foreach ($struct as $val1) {
+ foreach ($val1 as $val2) {
+ // Need to select the correct database for the creation of
+ // tables, views, triggers, etc.
+ /**
+ * @todo Generating a USE here blocks importing of a table
+ * into another database.
+ */
+ $attrs = $val2->attributes();
+ $create[] = "USE "
+ . PMA_Util::backquote(
+ $attrs["name"]
+ );
+
+ foreach ($val2 as $val3) {
+ /**
+ * Remove the extra cosmetic spacing
+ */
+ $val3 = str_replace(" ", "", (string)$val3);
+ $create[] = $val3;
+ }
+ }
+ }
+
+ $struct_present = true;
+ }
+
+ /**
+ * Move down the XML tree to the actual data
+ */
+ $xml = $xml->children()->children();
+
+ $data_present = false;
+
+ /**
+ * Only attempt to analyze/collect data if there is data present
+ */
+ if ($xml && @count($xml->children())) {
+ $data_present = true;
+
+ /**
+ * Process all database content
+ */
+ foreach ($xml as $v1) {
+ $tbl_attr = $v1->attributes();
+
+ $isInTables = false;
+ for ($i = 0; $i < count($tables); ++$i) {
+ if (! strcmp($tables[$i][TBL_NAME], (string)$tbl_attr['name'])) {
+ $isInTables = true;
+ break;
+ }
+ }
+
+ if ($isInTables == false) {
+ $tables[] = array((string)$tbl_attr['name']);
+ }
+
+ foreach ($v1 as $v2) {
+ $row_attr = $v2->attributes();
+ if (! array_search((string)$row_attr['name'], $tempRow)) {
+ $tempRow[] = (string)$row_attr['name'];
+ }
+ $tempCells[] = (string)$v2;
+ }
+
+ $rows[] = array((string)$tbl_attr['name'], $tempRow, $tempCells);
+
+ $tempRow = array();
+ $tempCells = array();
+ }
+
+ unset($tempRow);
+ unset($tempCells);
+ unset($xml);
+
+ /**
+ * Bring accumulated rows into the corresponding table
+ */
+ $num_tbls = count($tables);
+ for ($i = 0; $i < $num_tbls; ++$i) {
+ for ($j = 0; $j < count($rows); ++$j) {
+ if (! strcmp($tables[$i][TBL_NAME], $rows[$j][TBL_NAME])) {
+ if (! isset($tables[$i][COL_NAMES])) {
+ $tables[$i][] = $rows[$j][COL_NAMES];
+ }
+
+ $tables[$i][ROWS][] = $rows[$j][ROWS];
+ }
+ }
+ }
+
+ unset($rows);
+
+ if (! $struct_present) {
+ $analyses = array();
+
+ $len = count($tables);
+ for ($i = 0; $i < $len; ++$i) {
+ $analyses[] = PMA_analyzeTable($tables[$i]);
+ }
+ }
+ }
+
+ unset($xml);
+ unset($tempCells);
+ unset($rows);
+
+ /**
+ * Only build SQL from data if there is data present
+ */
+ if ($data_present) {
+ /**
+ * Set values to NULL if they were not present
+ * to maintain PMA_buildSQL() call integrity
+ */
+ if (! isset($analyses)) {
+ $analyses = null;
+ if (! $struct_present) {
+ $create = null;
+ }
+ }
+ }
+
+ /**
+ * string $db_name (no backquotes)
+ *
+ * array $table = array(table_name, array() column_names, array()() rows)
+ * array $tables = array of "$table"s
+ *
+ * array $analysis = array(array() column_types, array() column_sizes)
+ * array $analyses = array of "$analysis"s
+ *
+ * array $create = array of SQL strings
+ *
+ * array $options = an associative array of options
+ */
+
+ /* Set database name to the currently selected one, if applicable */
+ if (strlen($db)) {
+ /* Override the database name in the XML file, if one is selected */
+ $db_name = $db;
+ $options = array('create_db' => false);
+ } else {
+ if ($db_name === null) {
+ $db_name = 'XML_DB';
+ }
+
+ /* Set database collation/charset */
+ $options = array(
+ 'db_collation' => $collation,
+ 'db_charset' => $charset,
+ );
+ }
+
+ /* Created and execute necessary SQL statements from data */
+ PMA_buildSQL($db_name, $tables, $analyses, $create, $options);
+
+ unset($analyses);
+ unset($tables);
+ unset($create);
+
+ /* Commit any possible data in buffers */
+ PMA_importRunQuery();
+ }
+}
diff --git a/libraries/plugins/import/README b/libraries/plugins/import/README
new file mode 100644
index 0000000000..815cf2b69c
--- /dev/null
+++ b/libraries/plugins/import/README
@@ -0,0 +1,172 @@
+This directory holds import plugins for phpMyAdmin. Any new plugin should
+basically follow the structure presented here. The messages must use our
+gettext mechanism, see http://wiki.phpmyadmin.net/pma/Gettext_for_developers.
+
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * [Name] import plugin for phpMyAdmin
+ *
+ * @package PhpMyAdmin-Import
+ * @subpackage [Name]
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/* Get the import interface */
+require_once 'libraries/plugins/ImportPlugin.class.php';
+
+/**
+ * Handles the import for the [Name] format
+ *
+ * @package PhpMyAdmin-Import
+ */
+class Import[Name] extends ImportPlugin
+{
+ /**
+ * optional - declare variables and descriptions
+ *
+ * @var type
+ */
+ private $_myOptionalVariable;
+
+ /**
+ * Constructor
+ */
+ public function __construct()
+ {
+ $this->setProperties();
+ }
+
+ /**
+ * Sets the import plugin properties.
+ * Called in the constructor.
+ *
+ * @return void
+ */
+ protected function setProperties()
+ {
+ // set properties
+ $props = 'libraries/properties/';
+ // include the main class for properties for the import plug-ins
+ include_once "$props/plugins/ImportPluginProperties.class.php";
+ // include the group properties classes
+ include_once "$props/options/groups/OptionsPropertyRootGroup.class.php";
+ include_once "$props/options/groups/OptionsPropertyMainGroup.class.php";
+ // include the needed single property items
+ include_once "$props/options/items/RadioPropertyItem.class.php";
+
+ $importPluginProperties = new ImportPluginProperties();
+ $importPluginProperties->setText('[name]'); // the name of your plug-in
+ $importPluginProperties->setExtension('[ext]'); // extension this plug-in can handle
+ $importPluginProperties->setOptionsText(__('Options'));
+
+ // create the root group that will be the options field for
+ // $importPluginProperties
+ // this will be shown as "Format specific options"
+ $importSpecificOptions = new OptionsPropertyRootGroup();
+ $importSpecificOptions->setName("Format Specific Options");
+
+ // general options main group
+ $generalOptions = new OptionsPropertyMainGroup();
+ $generalOptions->setName("general_opts");
+
+ // optional :
+ // create primary items and add them to the group
+ // type - one of the classes listed in libraries/properties/options/items/
+ // name - form element name
+ // text - description in GUI
+ // size - size of text element
+ // len - maximal size of input
+ // values - possible values of the item
+ $leaf = new RadioPropertyItem();
+ $leaf->setName("structure_or_data");
+ $leaf->setValues(
+ array(
+ 'structure' => __('structure'),
+ 'data' => __('data'),
+ 'structure_and_data' => __('structure and data')
+ )
+ );
+ $generalOptions->addProperty($leaf);
+
+ // add the main group to the root group
+ $importSpecificOptions->addProperty($generalOptions);
+
+ // set the options for the import plugin property item
+ $importPluginProperties->setOptions($importSpecificOptions);
+ $this->properties = $importPluginProperties;
+ }
+
+ /**
+ * This method is called when any PluginManager to which the observer
+ * is attached calls PluginManager::notify()
+ *
+ * @param SplSubject $subject The PluginManager notifying the observer
+ * of an update.
+ *
+ * @return void
+ */
+ public function update (SplSubject $subject)
+ {
+ }
+
+ /**
+ * Handles the whole import logic
+ *
+ * @return void
+ */
+ public function doImport()
+ {
+ // get globals (others are optional)
+ global $error, $timeout_passed, $finished;
+
+ $buffer = '';
+ while (! ($finished && $i >= $len) && ! $error && ! $timeout_passed) {
+ $data = PMA_importGetNextChunk();
+ if ($data === false) {
+ // subtract data we didn't handle yet and stop processing
+ $GLOBALS['offset'] -= strlen($buffer);
+ break;
+ } elseif ($data === true) {
+ // Handle rest of buffer
+ } else {
+ // Append new data to buffer
+ $buffer .= $data;
+ }
+ // PARSE $buffer here, post sql queries using:
+ PMA_importRunQuery($sql, $verbose_sql_with_comments);
+ } // End of import loop
+ // Commit any possible data in buffers
+ PMA_importRunQuery();
+ }
+
+
+ // optional:
+ /* ~~~~~~~~~~~~~~~~~~~~ Getters and Setters ~~~~~~~~~~~~~~~~~~~~ */
+
+
+ /**
+ * Getter description
+ *
+ * @return type
+ */
+ private function _getMyOptionalVariable()
+ {
+ return $this->_myOptionalVariable;
+ }
+
+ /**
+ * Setter description
+ *
+ * @param type $my_optional_variable description
+ *
+ * @return void
+ */
+ private function _setMyOptionalVariable($my_optional_variable)
+ {
+ $this->_myOptionalVariable = $my_optional_variable;
+ }
+}
+?>
diff --git a/libraries/plugins/import/ShapeFile.class.php b/libraries/plugins/import/ShapeFile.class.php
new file mode 100644
index 0000000000..bad2751579
--- /dev/null
+++ b/libraries/plugins/import/ShapeFile.class.php
@@ -0,0 +1,102 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * This class extends ShapeFile class to cater the following phpMyAdmin
+ * specific requirements.
+ *
+ * @package PhpMyAdmin-Import
+ * @subpackage ESRI_Shape
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ * 1) To load data from .dbf file only when the dBase extension is available.
+ * 2) To use PMA_importGetNextChunk() functionality to read data, rather than
+ * reading directly from a file. Using ImportShp::readFromBuffer() in place
+ * of fread(). This makes it possible to use compressions.
+ *
+ * @package PhpMyAdmin-Import
+ * @subpackage ESRI_Shape
+ */
+class PMA_ShapeFile extends ShapeFile
+{
+ /**
+ * Returns whether the 'dbase' extension is loaded
+ *
+ * @return boolean whether the 'dbase' extension is loaded
+ */
+ function _isDbaseLoaded()
+ {
+ return extension_loaded('dbase');
+ }
+
+ /**
+ * Loads ESRI shape data from the imported file
+ *
+ * @param string $FileName not used, it's here only to match the method
+ * signature of the method being overidden
+ *
+ * @return void
+ * @see ShapeFile::loadFromFile()
+ */
+ function loadFromFile($FileName)
+ {
+ $this->_loadHeaders();
+ $this->_loadRecords();
+ if ($this->_isDbaseLoaded()) {
+ $this->_closeDBFFile();
+ }
+ }
+
+ /**
+ * Loads metadata from the ESRI shape file header
+ *
+ * @return void
+ * @see ShapeFile::_loadHeaders()
+ */
+ function _loadHeaders()
+ {
+ ImportShp::readFromBuffer(24);
+ $this->fileLength = loadData("N", ImportShp::readFromBuffer(4));
+
+ ImportShp::readFromBuffer(4);
+ $this->shapeType = loadData("V", ImportShp::readFromBuffer(4));
+
+ $this->boundingBox = array();
+ $this->boundingBox["xmin"] = loadData("d", ImportShp::readFromBuffer(8));
+ $this->boundingBox["ymin"] = loadData("d", ImportShp::readFromBuffer(8));
+ $this->boundingBox["xmax"] = loadData("d", ImportShp::readFromBuffer(8));
+ $this->boundingBox["ymax"] = loadData("d", ImportShp::readFromBuffer(8));
+
+ if ($this->_isDbaseLoaded() && $this->_openDBFFile()) {
+ $this->DBFHeader = $this->_loadDBFHeader();
+ }
+ }
+
+ /**
+ * Loads geometry data from the ESRI shape file
+ *
+ * @return boolean|void
+ * @see ShapeFile::_loadRecords()
+ */
+ function _loadRecords()
+ {
+ global $eof;
+ ImportShp::readFromBuffer(32);
+ while (true) {
+ $record = new PMA_ShapeRecord(-1);
+ $record->loadFromFile($this->SHPFile, $this->DBFFile);
+ if ($record->lastError != "") {
+ return false;
+ }
+ if ($eof) {
+ break;
+ }
+
+ $this->records[] = $record;
+ }
+ }
+}
+?>
diff --git a/libraries/plugins/import/ShapeRecord.class.php b/libraries/plugins/import/ShapeRecord.class.php
new file mode 100644
index 0000000000..7317c00aa4
--- /dev/null
+++ b/libraries/plugins/import/ShapeRecord.class.php
@@ -0,0 +1,161 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * This class extends ShapeRecord class to cater the following phpMyAdmin
+ * specific requirements.
+ *
+ * @package PhpMyAdmin-Import
+ * @subpackage ESRI_Shape
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ * 1) To load data from .dbf file only when the dBase extension is available.
+ * 2) To use PMA_importGetNextChunk() functionality to read data, rather than
+ * reading directly from a file. Using ImportShp::readFromBuffer() in place
+ * of fread(). This makes it possible to use compressions.
+ *
+ * @package PhpMyAdmin-Import
+ * @subpackage ESRI_Shape
+ */
+class PMA_ShapeRecord extends ShapeRecord
+{
+ /**
+ * Loads a geometry data record from the file
+ *
+ * @param object &$SHPFile .shp file
+ * @param object &$DBFFile .dbf file
+ *
+ * @return void
+ * @see ShapeRecord::loadFromFile()
+ */
+ function loadFromFile(&$SHPFile, &$DBFFile)
+ {
+ $this->DBFFile = $DBFFile;
+ $this->_loadHeaders();
+
+ switch ($this->shapeType) {
+ case 0:
+ $this->_loadNullRecord();
+ break;
+ case 1:
+ $this->_loadPointRecord();
+ break;
+ case 3:
+ $this->_loadPolyLineRecord();
+ break;
+ case 5:
+ $this->_loadPolygonRecord();
+ break;
+ case 8:
+ $this->_loadMultiPointRecord();
+ break;
+ default:
+ $this->setError(
+ sprintf(
+ __("Geometry type '%s' is not supported by MySQL."),
+ $this->shapeType
+ )
+ );
+ break;
+ }
+ if (extension_loaded('dbase') && isset($this->DBFFile)) {
+ $this->_loadDBFData();
+ }
+ }
+
+ /**
+ * Loads metadata from the ESRI shape record header
+ *
+ * @return void
+ * @see ShapeRecord::_loadHeaders()
+ */
+ function _loadHeaders()
+ {
+ $this->recordNumber = loadData("N", ImportShp::readFromBuffer(4));
+ ImportShp::readFromBuffer(4);
+ $this->shapeType = loadData("V", ImportShp::readFromBuffer(4));
+ }
+
+ /**
+ * Loads data from a point record
+ *
+ * @return array
+ * @see ShapeRecord::_loadPoint()
+ */
+ function _loadPoint()
+ {
+ $data = array();
+
+ $data["x"] = loadData("d", ImportShp::readFromBuffer(8));
+ $data["y"] = loadData("d", ImportShp::readFromBuffer(8));
+
+ return $data;
+ }
+
+ /**
+ * Loads data from a multipoint record
+ *
+ * @return void
+ * @see ShapeRecord::_loadMultiPointRecord()
+ */
+ function _loadMultiPointRecord()
+ {
+ $this->SHPData = array();
+ $this->SHPData["xmin"] = loadData("d", ImportShp::readFromBuffer(8));
+ $this->SHPData["ymin"] = loadData("d", ImportShp::readFromBuffer(8));
+ $this->SHPData["xmax"] = loadData("d", ImportShp::readFromBuffer(8));
+ $this->SHPData["ymax"] = loadData("d", ImportShp::readFromBuffer(8));
+
+ $this->SHPData["numpoints"] = loadData("V", ImportShp::readFromBuffer(4));
+
+ for ($i = 0; $i <= $this->SHPData["numpoints"]; $i++) {
+ $this->SHPData["points"][] = $this->_loadPoint();
+ }
+ }
+
+ /**
+ * Loads data from a polyline record
+ *
+ * @return void
+ * @see ShapeRecord::_loadPolyLineRecord()
+ */
+ function _loadPolyLineRecord()
+ {
+ $this->SHPData = array();
+ $this->SHPData["xmin"] = loadData("d", ImportShp::readFromBuffer(8));
+ $this->SHPData["ymin"] = loadData("d", ImportShp::readFromBuffer(8));
+ $this->SHPData["xmax"] = loadData("d", ImportShp::readFromBuffer(8));
+ $this->SHPData["ymax"] = loadData("d", ImportShp::readFromBuffer(8));
+
+ $this->SHPData["numparts"] = loadData("V", ImportShp::readFromBuffer(4));
+ $this->SHPData["numpoints"] = loadData("V", ImportShp::readFromBuffer(4));
+
+ for ($i = 0; $i < $this->SHPData["numparts"]; $i++) {
+ $this->SHPData["parts"][$i] = loadData(
+ "V", ImportShp::readFromBuffer(4)
+ );
+ }
+
+ $readPoints = 0;
+ reset($this->SHPData["parts"]);
+ while (list($partIndex, $partData) = each($this->SHPData["parts"])) {
+ if (! isset($this->SHPData["parts"][$partIndex]["points"])
+ || !is_array($this->SHPData["parts"][$partIndex]["points"])
+ ) {
+ $this->SHPData["parts"][$partIndex] = array();
+ $this->SHPData["parts"][$partIndex]["points"] = array();
+ }
+ while (! in_array($readPoints, $this->SHPData["parts"])
+ && ($readPoints < ($this->SHPData["numpoints"]))
+ ) {
+ $this->SHPData["parts"][$partIndex]["points"][]
+ = $this->_loadPoint();
+ $readPoints++;
+ }
+ }
+ }
+}
+?>
diff --git a/libraries/plugins/import/upload/UploadApc.class.php b/libraries/plugins/import/upload/UploadApc.class.php
new file mode 100644
index 0000000000..3ddb7a7f84
--- /dev/null
+++ b/libraries/plugins/import/upload/UploadApc.class.php
@@ -0,0 +1,84 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Provides upload functionalities for the import plugins
+ *
+ * @package PhpMyAdmin
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/* Get the transformations interface */
+require_once 'libraries/plugins/UploadInterface.int.php';
+
+/**
+ * Implementation for the APC extension
+ *
+ * @package PhpMyAdmin
+ */
+class UploadApc implements UploadInterface
+{
+ /**
+ * Gets the specific upload ID Key
+ *
+ * @return string ID Key
+ */
+ public static function getIdKey()
+ {
+ return 'APC_UPLOAD_PROGRESS';
+ }
+
+ /**
+ * Returns upload status.
+ *
+ * This is implementation for APC extension.
+ *
+ * @param string $id upload id
+ *
+ * @return array|null
+ */
+ public static function getUploadStatus($id)
+ {
+ global $SESSION_KEY;
+
+ if (trim($id) == "") {
+ return null;
+ }
+ if (! array_key_exists($id, $_SESSION[$SESSION_KEY])) {
+ $_SESSION[$SESSION_KEY][$id] = array(
+ 'id' => $id,
+ 'finished' => false,
+ 'percent' => 0,
+ 'total' => 0,
+ 'complete' => 0,
+ 'plugin' => UploadApc::getIdKey()
+ );
+ }
+ $ret = $_SESSION[$SESSION_KEY][$id];
+
+ if (! PMA_Import_apcCheck() || $ret['finished']) {
+ return $ret;
+ }
+ $status = apc_fetch('upload_' . $id);
+
+ if ($status) {
+ $ret['finished'] = (bool)$status['done'];
+ $ret['total'] = $status['total'];
+ $ret['complete'] = $status['current'];
+
+ if ($ret['total'] > 0) {
+ $ret['percent'] = $ret['complete'] / $ret['total'] * 100;
+ }
+
+ if ($ret['percent'] == 100) {
+ $ret['finished'] = (bool)true;
+ }
+
+ $_SESSION[$SESSION_KEY][$id] = $ret;
+ }
+
+ return $ret;
+ }
+}
+?> \ No newline at end of file
diff --git a/libraries/plugins/import/upload/UploadNoplugin.class.php b/libraries/plugins/import/upload/UploadNoplugin.class.php
new file mode 100644
index 0000000000..90148ded4c
--- /dev/null
+++ b/libraries/plugins/import/upload/UploadNoplugin.class.php
@@ -0,0 +1,64 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Provides upload functionalities for the import plugins
+ *
+ * @package PhpMyAdmin
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/* Get the transformations interface */
+require_once 'libraries/plugins/UploadInterface.int.php';
+
+/**
+ * Implementation for no plugin
+ *
+ * @package PhpMyAdmin
+ */
+class UploadNoplugin implements UploadInterface
+{
+ /**
+ * Gets the specific upload ID Key
+ *
+ * @return string ID Key
+ */
+ public static function getIdKey()
+ {
+ return 'noplugin';
+ }
+
+ /**
+ * Returns upload status.
+ *
+ * This is implementation when no webserver support exists,
+ * so it returns just zeroes.
+ *
+ * @param string $id upload id
+ *
+ * @return array|null
+ */
+ public static function getUploadStatus($id)
+ {
+ global $SESSION_KEY;
+
+ if (trim($id) == "") {
+ return null;
+ }
+ if (! array_key_exists($id, $_SESSION[$SESSION_KEY])) {
+ $_SESSION[$SESSION_KEY][$id] = array(
+ 'id' => $id,
+ 'finished' => false,
+ 'percent' => 0,
+ 'total' => 0,
+ 'complete' => 0,
+ 'plugin' => UploadNoplugin::getIdKey()
+ );
+ }
+ $ret = $_SESSION[$SESSION_KEY][$id];
+
+ return $ret;
+ }
+}
+?> \ No newline at end of file
diff --git a/libraries/plugins/import/upload/UploadProgress.class.php b/libraries/plugins/import/upload/UploadProgress.class.php
new file mode 100644
index 0000000000..d7647b326a
--- /dev/null
+++ b/libraries/plugins/import/upload/UploadProgress.class.php
@@ -0,0 +1,94 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Provides upload functionalities for the import plugins
+ *
+ * @package PhpMyAdmin
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/* Get the transformations interface */
+require_once 'libraries/plugins/UploadInterface.int.php';
+
+/**
+ * Implementation for upload progress
+ *
+ * @package PhpMyAdmin
+ */
+class UploadProgress implements UploadInterface
+{
+ /**
+ * Gets the specific upload ID Key
+ *
+ * @return string ID Key
+ */
+ public static function getIdKey()
+ {
+ return 'UPLOAD_IDENTIFIER';
+ }
+
+ /**
+ * Returns upload status.
+ *
+ * This is implementation for upload progress
+ *
+ * @param string $id upload id
+ *
+ * @return array|null
+ */
+ public static function getUploadStatus($id)
+ {
+ global $SESSION_KEY;
+
+ if (trim($id) == "") {
+ return null;
+ }
+
+ if (! array_key_exists($id, $_SESSION[$SESSION_KEY])) {
+ $_SESSION[$SESSION_KEY][$id] = array(
+ 'id' => $id,
+ 'finished' => false,
+ 'percent' => 0,
+ 'total' => 0,
+ 'complete' => 0,
+ 'plugin' => UploadProgress::getIdKey()
+ );
+ }
+ $ret = $_SESSION[$SESSION_KEY][$id];
+
+ if (! PMA_Import_progressCheck() || $ret['finished']) {
+ return $ret;
+ }
+
+ $status = uploadprogress_get_info($id);
+
+ if ($status) {
+ if ($status['bytes_uploaded'] == $status['bytes_total']) {
+ $ret['finished'] = true;
+ } else {
+ $ret['finished'] = false;
+ }
+ $ret['total'] = $status['bytes_total'];
+ $ret['complete'] = $status['bytes_uploaded'];
+
+ if ($ret['total'] > 0) {
+ $ret['percent'] = $ret['complete'] / $ret['total'] * 100;
+ }
+ } else {
+ $ret = array(
+ 'id' => $id,
+ 'finished' => true,
+ 'percent' => 100,
+ 'total' => $ret['total'],
+ 'complete' => $ret['total'],
+ 'plugin' => UploadProgress::getIdKey()
+ );
+ }
+
+ $_SESSION[$SESSION_KEY][$id] = $ret;
+ return $ret;
+ }
+}
+?> \ No newline at end of file
diff --git a/libraries/plugins/import/upload/UploadSession.class.php b/libraries/plugins/import/upload/UploadSession.class.php
new file mode 100644
index 0000000000..d9bdd53105
--- /dev/null
+++ b/libraries/plugins/import/upload/UploadSession.class.php
@@ -0,0 +1,96 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Provides upload functionalities for the import plugins
+ *
+ * @package PhpMyAdmin
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/* Get the transformations interface */
+require_once 'libraries/plugins/UploadInterface.int.php';
+
+/**
+ * Implementation for session
+ *
+ * @package PhpMyAdmin
+ */
+class UploadSession implements UploadInterface
+{
+ /**
+ * Gets the specific upload ID Key
+ *
+ * @return string ID Key
+ */
+ public static function getIdKey()
+ {
+ return ini_get('session.upload_progress.name');
+ }
+
+ /**
+ * Returns upload status.
+ *
+ * This is implementation for session.upload_progress in PHP 5.4+.
+ *
+ * @param string $id upload id
+ *
+ * @return array|null
+ */
+ public static function getUploadStatus($id)
+ {
+ global $SESSION_KEY;
+
+ if (trim($id) == '') {
+ return null;
+ }
+
+ if (! array_key_exists($id, $_SESSION[$SESSION_KEY])) {
+ $_SESSION[$SESSION_KEY][$id] = array(
+ 'id' => $id,
+ 'finished' => false,
+ 'percent' => 0,
+ 'total' => 0,
+ 'complete' => 0,
+ 'plugin' => UploadSession::getIdKey()
+ );
+ }
+ $ret = $_SESSION[$SESSION_KEY][$id];
+
+ if (! PMA_Import_sessionCheck() || $ret['finished']) {
+ return $ret;
+ }
+
+ $status = false;
+ $sessionkey = ini_get('session.upload_progress.prefix') . $id;
+
+ if (isset($_SESSION[$sessionkey])) {
+ $status = $_SESSION[$sessionkey];
+ }
+
+ if ($status) {
+ $ret['finished'] = $status['done'];
+ $ret['total'] = $status['content_length'];
+ $ret['complete'] = $status['bytes_processed'];
+
+ if ($ret['total'] > 0) {
+ $ret['percent'] = $ret['complete'] / $ret['total'] * 100;
+ }
+ } else {
+ $ret = array(
+ 'id' => $id,
+ 'finished' => true,
+ 'percent' => 100,
+ 'total' => $ret['total'],
+ 'complete' => $ret['total'],
+ 'plugin' => UploadSession::getIdKey()
+ );
+ }
+
+ $_SESSION[$SESSION_KEY][$id] = $ret;
+
+ return $ret;
+ }
+}
+?> \ No newline at end of file
diff --git a/libraries/plugins/transformations/Application_Octetstream_Download.class.php b/libraries/plugins/transformations/Application_Octetstream_Download.class.php
new file mode 100644
index 0000000000..069eda719b
--- /dev/null
+++ b/libraries/plugins/transformations/Application_Octetstream_Download.class.php
@@ -0,0 +1,43 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Application OctetStream Download Transformations plugin for phpMyAdmin
+ *
+ * @package PhpMyAdmin-Transformations
+ * @subpackage Download
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+/* Get the download transformations interface */
+require_once 'abstract/DownloadTransformationsPlugin.class.php';
+
+/**
+ * Handles the download transformation for application octetstream
+ *
+ * @package PhpMyAdmin-Transformations
+ * @subpackage Download
+ */
+class Application_Octetstream_Download extends DownloadTransformationsPlugin
+{
+ /**
+ * Gets the plugin`s MIME type
+ *
+ * @return string
+ */
+ public static function getMIMEType()
+ {
+ return "Application";
+ }
+
+ /**
+ * Gets the plugin`s MIME subtype
+ *
+ * @return string
+ */
+ public static function getMIMESubtype()
+ {
+ return "OctetStream";
+ }
+}
+?> \ No newline at end of file
diff --git a/libraries/plugins/transformations/Application_Octetstream_Hex.class.php b/libraries/plugins/transformations/Application_Octetstream_Hex.class.php
new file mode 100644
index 0000000000..f6f4297460
--- /dev/null
+++ b/libraries/plugins/transformations/Application_Octetstream_Hex.class.php
@@ -0,0 +1,44 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Application OctetStream Hex Transformations plugin for phpMyAdmin
+ *
+ * @package PhpMyAdmin-Transformations
+ * @subpackage Hex
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/* Get the hex transformations interface */
+require_once 'abstract/HexTransformationsPlugin.class.php';
+
+/**
+ * Handles the hex transformation for application octetstream
+ *
+ * @package PhpMyAdmin-Transformations
+ * @subpackage Hex
+ */
+class Application_Octetstream_Hex extends HexTransformationsPlugin
+{
+ /**
+ * Gets the plugin`s MIME type
+ *
+ * @return string
+ */
+ public static function getMIMEType()
+ {
+ return "Application";
+ }
+
+ /**
+ * Gets the plugin`s MIME subtype
+ *
+ * @return string
+ */
+ public static function getMIMESubtype()
+ {
+ return "OctetStream";
+ }
+}
+?> \ No newline at end of file
diff --git a/libraries/plugins/transformations/Image_JPEG_Inline.class.php b/libraries/plugins/transformations/Image_JPEG_Inline.class.php
new file mode 100644
index 0000000000..501205a945
--- /dev/null
+++ b/libraries/plugins/transformations/Image_JPEG_Inline.class.php
@@ -0,0 +1,44 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Image JPEG Inline Transformations plugin for phpMyAdmin
+ *
+ * @package PhpMyAdmin-Transformations
+ * @subpackage Inline
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/* Get the inline transformations interface */
+require_once 'abstract/InlineTransformationsPlugin.class.php';
+
+/**
+ * Handles the inline transformation for image jpeg
+ *
+ * @package PhpMyAdmin-Transformations
+ * @subpackage Inline
+ */
+class Image_JPEG_Inline extends InlineTransformationsPlugin
+{
+ /**
+ * Gets the plugin`s MIME type
+ *
+ * @return string
+ */
+ public static function getMIMEType()
+ {
+ return "Image";
+ }
+
+ /**
+ * Gets the plugin`s MIME subtype
+ *
+ * @return string
+ */
+ public static function getMIMESubtype()
+ {
+ return "JPEG";
+ }
+}
+?> \ No newline at end of file
diff --git a/libraries/plugins/transformations/Image_JPEG_Link.class.php b/libraries/plugins/transformations/Image_JPEG_Link.class.php
new file mode 100644
index 0000000000..ca3c1d18ea
--- /dev/null
+++ b/libraries/plugins/transformations/Image_JPEG_Link.class.php
@@ -0,0 +1,44 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Image JPEG Link Transformations plugin for phpMyAdmin
+ *
+ * @package PhpMyAdmin-Transformations
+ * @subpackage Link
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/* Get the link transformations interface */
+require_once 'abstract/ImageLinkTransformationsPlugin.class.php';
+
+/**
+ * Handles the link transformation for image jpeg
+ *
+ * @package PhpMyAdmin-Transformations
+ * @subpackage Link
+ */
+class Image_JPEG_Link extends ImageLinkTransformationsPlugin
+{
+ /**
+ * Gets the plugin`s MIME type
+ *
+ * @return string
+ */
+ public static function getMIMEType()
+ {
+ return "Image";
+ }
+
+ /**
+ * Gets the plugin`s MIME subtype
+ *
+ * @return string
+ */
+ public static function getMIMESubtype()
+ {
+ return "JPEG";
+ }
+}
+?> \ No newline at end of file
diff --git a/libraries/plugins/transformations/Image_PNG_Inline.class.php b/libraries/plugins/transformations/Image_PNG_Inline.class.php
new file mode 100644
index 0000000000..40b709d87a
--- /dev/null
+++ b/libraries/plugins/transformations/Image_PNG_Inline.class.php
@@ -0,0 +1,44 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Image PNG Inline Transformations plugin for phpMyAdmin
+ *
+ * @package PhpMyAdmin-Transformations
+ * @subpackage Inline
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/* Get the inline transformations interface */
+require_once 'abstract/InlineTransformationsPlugin.class.php';
+
+/**
+ * Handles the inline transformation for image png
+ *
+ * @package PhpMyAdmin-Transformations
+ * @subpackage Inline
+ */
+class Image_PNG_Inline extends InlineTransformationsPlugin
+{
+ /**
+ * Gets the plugin`s MIME type
+ *
+ * @return string
+ */
+ public static function getMIMEType()
+ {
+ return "Image";
+ }
+
+ /**
+ * Gets the plugin`s MIME subtype
+ *
+ * @return string
+ */
+ public static function getMIMESubtype()
+ {
+ return "PNG";
+ }
+}
+?> \ No newline at end of file
diff --git a/libraries/plugins/transformations/README b/libraries/plugins/transformations/README
new file mode 100644
index 0000000000..7d7a1255d2
--- /dev/null
+++ b/libraries/plugins/transformations/README
@@ -0,0 +1,4 @@
+TRANSFORMATION USAGE (Garvin Hicking, <me@supergarv.de>)
+====================
+
+See the documentation for complete instructions on how to use transformation plugins.
diff --git a/libraries/plugins/transformations/TEMPLATE b/libraries/plugins/transformations/TEMPLATE
new file mode 100644
index 0000000000..6f116ce833
--- /dev/null
+++ b/libraries/plugins/transformations/TEMPLATE
@@ -0,0 +1,46 @@
+<?php
+// vim: expandtab sw=4 ts=4 sts=4:
+/**
+ * This file contains the basic structure for a specific MIME Type and Subtype
+ * transformations class.
+ * For instructions, read the documentation
+ *
+ * @package PhpMyAdmin-Transformations
+ * @subpackage [TransformationName]
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/* Get the [TransformationName] transformations interface */
+require_once 'abstract/[TransformationName]TransformationsPlugin.class.php';
+
+/**
+ * Handles the [TransformationName] transformation for [MIMEType] - [MIMESubtype]
+ *
+ * @package PhpMyAdmin
+ */
+class [MIMEType]_[MIMESubtype]_[TransformationName]
+ extends [TransformationName]TransformationsPlugin
+{
+ /**
+ * Gets the plugin`s MIME type
+ *
+ * @return string
+ */
+ public static function getMIMEType()
+ {
+ return "[MIMEType]";
+ }
+
+ /**
+ * Gets the plugin`s MIME subtype
+ *
+ * @return string
+ */
+ public static function getMIMESubtype()
+ {
+ return "[MIMESubtype]";
+ }
+}
+?>
diff --git a/libraries/plugins/transformations/TEMPLATE_ABSTRACT b/libraries/plugins/transformations/TEMPLATE_ABSTRACT
new file mode 100644
index 0000000000..5cc1d26bd0
--- /dev/null
+++ b/libraries/plugins/transformations/TEMPLATE_ABSTRACT
@@ -0,0 +1,89 @@
+<?php
+// vim: expandtab sw=4 ts=4 sts=4:
+/**
+ * This file contains the basic structure for an abstract class defining a
+ * transformation.
+ * For instructions, read the documentation
+ *
+ * @package PhpMyAdmin-Transformations
+ * @subpackage [TransformationName]
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/* Get the transformations interface */
+require_once 'libraries/plugins/TransformationsPlugin.class.php';
+
+/**
+ * Provides common methods for all of the [TransformationName] transformations plugins.
+ *
+ * @package PhpMyAdmin
+ */
+abstract class [TransformationName]TransformationsPlugin
+ extends TransformationsPlugin
+{
+ /**
+ * Gets the transformation description of the specific plugin
+ *
+ * @return string
+ */
+ public static function getInfo()
+ {
+ return __(
+ 'Description of the transformation.'
+ );
+ }
+
+ /**
+ * Does the actual work of each specific transformations plugin.
+ *
+ * @param string $buffer text to be transformed
+ * @param array $options transformation options
+ * @param string $meta meta information
+ *
+ * @return void
+ */
+ public function applyTransformation($buffer, $options = array(), $meta = '')
+ {
+ // possibly use a global transform and feed it with special options
+
+ // further operations on $buffer using the $options[] array.
+
+ // You can evaluate the propagated $meta Object. It's contained fields are described in http://www.php.net/mysql_fetch_field.
+ // This stored information can be used to get the field information about the transformed field.
+ // $meta->mimetype contains the original MimeType of the field (i.e. 'text/plain', 'image/jpeg' etc.)
+
+ return $buffer;
+ }
+
+ /**
+ * This method is called when any PluginManager to which the observer
+ * is attached calls PluginManager::notify()
+ *
+ * @param SplSubject $subject The PluginManager notifying the observer
+ * of an update.
+ *
+ * @todo implement
+ * @return void
+ */
+ public function update (SplSubject $subject)
+ {
+ ;
+ }
+
+
+ /* ~~~~~~~~~~~~~~~~~~~~ Getters and Setters ~~~~~~~~~~~~~~~~~~~~ */
+
+
+ /**
+ * Gets the TransformationName of the specific plugin
+ *
+ * @return string
+ */
+ public static function getName()
+ {
+ return "[TransformationName]";
+ }
+}
+?>
diff --git a/libraries/plugins/transformations/Text_Plain_Append.class.php b/libraries/plugins/transformations/Text_Plain_Append.class.php
new file mode 100644
index 0000000000..567c2cc608
--- /dev/null
+++ b/libraries/plugins/transformations/Text_Plain_Append.class.php
@@ -0,0 +1,45 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Text Plain Append Transformations plugin for phpMyAdmin
+ *
+ * @package PhpMyAdmin-Transformations
+ * @subpackage Append
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/* Get the append transformations interface */
+require_once 'abstract/AppendTransformationsPlugin.class.php';
+
+/**
+ * Handles the append transformation for text plain.
+ * Has one option: the text to be appended (default '')
+ *
+ * @package PhpMyAdmin-Transformations
+ * @subpackage Append
+ */
+class Text_Plain_Append extends AppendTransformationsPlugin
+{
+ /**
+ * Gets the plugin`s MIME type
+ *
+ * @return string
+ */
+ public static function getMIMEType()
+ {
+ return "Text";
+ }
+
+ /**
+ * Gets the plugin`s MIME subtype
+ *
+ * @return string
+ */
+ public static function getMIMESubtype()
+ {
+ return "Plain";
+ }
+}
+?> \ No newline at end of file
diff --git a/libraries/plugins/transformations/Text_Plain_Dateformat.class.php b/libraries/plugins/transformations/Text_Plain_Dateformat.class.php
new file mode 100644
index 0000000000..70db98aef7
--- /dev/null
+++ b/libraries/plugins/transformations/Text_Plain_Dateformat.class.php
@@ -0,0 +1,44 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Text Plain Date Format Transformations plugin for phpMyAdmin
+ *
+ * @package PhpMyAdmin-Transformations
+ * @subpackage DateFormat
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/* Get the date format transformations interface */
+require_once 'abstract/DateFormatTransformationsPlugin.class.php';
+
+/**
+ * Handles the date format transformation for text plain
+ *
+ * @package PhpMyAdmin-Transformations
+ * @subpackage DateFormat
+ */
+class Text_Plain_Dateformat extends DateFormatTransformationsPlugin
+{
+ /**
+ * Gets the plugin`s MIME type
+ *
+ * @return string
+ */
+ public static function getMIMEType()
+ {
+ return "Text";
+ }
+
+ /**
+ * Gets the plugin`s MIME subtype
+ *
+ * @return string
+ */
+ public static function getMIMESubtype()
+ {
+ return "Plain";
+ }
+}
+?> \ No newline at end of file
diff --git a/libraries/plugins/transformations/Text_Plain_External.class.php b/libraries/plugins/transformations/Text_Plain_External.class.php
new file mode 100644
index 0000000000..18da613183
--- /dev/null
+++ b/libraries/plugins/transformations/Text_Plain_External.class.php
@@ -0,0 +1,44 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Text Plain External Transformations plugin for phpMyAdmin
+ *
+ * @package PhpMyAdmin-Transformations
+ * @subpackage External
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/* Get the external transformations interface */
+require_once 'abstract/ExternalTransformationsPlugin.class.php';
+
+/**
+ * Handles the external transformation for text plain
+ *
+ * @package PhpMyAdmin-Transformations
+ * @subpackage External
+ */
+class Text_Plain_External extends ExternalTransformationsPlugin
+{
+ /**
+ * Gets the plugin`s MIME type
+ *
+ * @return string
+ */
+ public static function getMIMEType()
+ {
+ return "Text";
+ }
+
+ /**
+ * Gets the plugin`s MIME subtype
+ *
+ * @return string
+ */
+ public static function getMIMESubtype()
+ {
+ return "Plain";
+ }
+}
+?> \ No newline at end of file
diff --git a/libraries/plugins/transformations/Text_Plain_Formatted.class.php b/libraries/plugins/transformations/Text_Plain_Formatted.class.php
new file mode 100644
index 0000000000..3942a93e32
--- /dev/null
+++ b/libraries/plugins/transformations/Text_Plain_Formatted.class.php
@@ -0,0 +1,44 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Text Plain Formatted Transformations plugin for phpMyAdmin
+ *
+ * @package PhpMyAdmin-Transformations
+ * @subpackage Formatted
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/* Get the formatted transformations interface */
+require_once 'abstract/FormattedTransformationsPlugin.class.php';
+
+/**
+ * Handles the formatted transformation for text plain
+ *
+ * @package PhpMyAdmin-Transformations
+ * @subpackage Formatted
+ */
+class Text_Plain_Formatted extends FormattedTransformationsPlugin
+{
+ /**
+ * Gets the plugin`s MIME type
+ *
+ * @return string
+ */
+ public static function getMIMEType()
+ {
+ return "Text";
+ }
+
+ /**
+ * Gets the plugin`s MIME subtype
+ *
+ * @return string
+ */
+ public static function getMIMESubtype()
+ {
+ return "Plain";
+ }
+}
+?> \ No newline at end of file
diff --git a/libraries/plugins/transformations/Text_Plain_Imagelink.class.php b/libraries/plugins/transformations/Text_Plain_Imagelink.class.php
new file mode 100644
index 0000000000..670e52e8d7
--- /dev/null
+++ b/libraries/plugins/transformations/Text_Plain_Imagelink.class.php
@@ -0,0 +1,44 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Text Plain Image Link Transformations plugin for phpMyAdmin
+ *
+ * @package PhpMyAdmin-Transformations
+ * @subpackage ImageLink
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/* Get the image link transformations interface */
+require_once 'abstract/TextImageLinkTransformationsPlugin.class.php';
+
+/**
+ * Handles the image link transformation for text plain
+ *
+ * @package PhpMyAdmin-Transformations
+ * @subpackage ImageLink
+ */
+class Text_Plain_Imagelink extends TextImageLinkTransformationsPlugin
+{
+ /**
+ * Gets the plugin`s MIME type
+ *
+ * @return string
+ */
+ public static function getMIMEType()
+ {
+ return "Text";
+ }
+
+ /**
+ * Gets the plugin`s MIME subtype
+ *
+ * @return string
+ */
+ public static function getMIMESubtype()
+ {
+ return "Plain";
+ }
+}
+?> \ No newline at end of file
diff --git a/libraries/plugins/transformations/Text_Plain_Link.class.php b/libraries/plugins/transformations/Text_Plain_Link.class.php
new file mode 100644
index 0000000000..d1b17d5ec8
--- /dev/null
+++ b/libraries/plugins/transformations/Text_Plain_Link.class.php
@@ -0,0 +1,44 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Text Plain Link Transformations plugin for phpMyAdmin
+ *
+ * @package PhpMyAdmin-Transformations
+ * @subpackage Link
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/* Get the link transformations interface */
+require_once 'abstract/TextLinkTransformationsPlugin.class.php';
+
+/**
+ * Handles the link transformation for text plain
+ *
+ * @package PhpMyAdmin-Transformations
+ * @subpackage Link
+ */
+class Text_Plain_Link extends TextLinkTransformationsPlugin
+{
+ /**
+ * Gets the plugin`s MIME type
+ *
+ * @return string
+ */
+ public static function getMIMEType()
+ {
+ return "Text";
+ }
+
+ /**
+ * Gets the plugin`s MIME subtype
+ *
+ * @return string
+ */
+ public static function getMIMESubtype()
+ {
+ return "Plain";
+ }
+}
+?> \ No newline at end of file
diff --git a/libraries/plugins/transformations/Text_Plain_Longtoipv4.class.php b/libraries/plugins/transformations/Text_Plain_Longtoipv4.class.php
new file mode 100644
index 0000000000..9f58a45eb3
--- /dev/null
+++ b/libraries/plugins/transformations/Text_Plain_Longtoipv4.class.php
@@ -0,0 +1,44 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Text Plain Long To IPv4 Transformations plugin for phpMyAdmin
+ *
+ * @package PhpMyAdmin-Transformations
+ * @subpackage LongToIPv4
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/* Get the long to ipv4 transformations interface */
+require_once 'abstract/LongToIPv4TransformationsPlugin.class.php';
+
+/**
+ * Handles the long to ipv4 transformation for text plain
+ *
+ * @package PhpMyAdmin-Transformations
+ * @subpackage LongToIPv4
+ */
+class Text_Plain_Longtoipv4 extends LongToIPv4TransformationsPlugin
+{
+ /**
+ * Gets the plugin`s MIME type
+ *
+ * @return string
+ */
+ public static function getMIMEType()
+ {
+ return "Text";
+ }
+
+ /**
+ * Gets the plugin`s MIME subtype
+ *
+ * @return string
+ */
+ public static function getMIMESubtype()
+ {
+ return "Plain";
+ }
+}
+?> \ No newline at end of file
diff --git a/libraries/plugins/transformations/Text_Plain_Sql.class.php b/libraries/plugins/transformations/Text_Plain_Sql.class.php
new file mode 100644
index 0000000000..1987ba9144
--- /dev/null
+++ b/libraries/plugins/transformations/Text_Plain_Sql.class.php
@@ -0,0 +1,44 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Text Plain SQL Transformations plugin for phpMyAdmin
+ *
+ * @package PhpMyAdmin-Transformations
+ * @subpackage SQL
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/* Get the sql transformations interface */
+require_once 'abstract/SQLTransformationsPlugin.class.php';
+
+/**
+ * Handles the sql transformation for text plain
+ *
+ * @package PhpMyAdmin-Transformations
+ * @subpackage SQL
+ */
+class Text_Plain_Sql extends SQLTransformationsPlugin
+{
+ /**
+ * Gets the plugin`s MIME type
+ *
+ * @return string
+ */
+ public static function getMIMEType()
+ {
+ return "Text";
+ }
+
+ /**
+ * Gets the plugin`s MIME subtype
+ *
+ * @return string
+ */
+ public static function getMIMESubtype()
+ {
+ return "Plain";
+ }
+}
+?> \ No newline at end of file
diff --git a/libraries/plugins/transformations/Text_Plain_Substring.class.php b/libraries/plugins/transformations/Text_Plain_Substring.class.php
new file mode 100644
index 0000000000..051ae51cd3
--- /dev/null
+++ b/libraries/plugins/transformations/Text_Plain_Substring.class.php
@@ -0,0 +1,44 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Text Plain Substring Transformations plugin for phpMyAdmin
+ *
+ * @package PhpMyAdmin-Transformations
+ * @subpackage Substring
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/* Get the substring transformations interface */
+require_once 'abstract/SubstringTransformationsPlugin.class.php';
+
+/**
+ * Handles the substring transformation for text plain
+ *
+ * @package PhpMyAdmin-Transformations
+ * @subpackage Substring
+ */
+class Text_Plain_Substring extends SubstringTransformationsPlugin
+{
+ /**
+ * Gets the plugin`s MIME type
+ *
+ * @return string
+ */
+ public static function getMIMEType()
+ {
+ return "Text";
+ }
+
+ /**
+ * Gets the plugin`s MIME subtype
+ *
+ * @return string
+ */
+ public static function getMIMESubtype()
+ {
+ return "Plain";
+ }
+}
+?> \ No newline at end of file
diff --git a/libraries/plugins/transformations/abstract/AppendTransformationsPlugin.class.php b/libraries/plugins/transformations/abstract/AppendTransformationsPlugin.class.php
new file mode 100644
index 0000000000..86bb0be74b
--- /dev/null
+++ b/libraries/plugins/transformations/abstract/AppendTransformationsPlugin.class.php
@@ -0,0 +1,84 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Abstract class for the append transformations plugins
+ *
+ * @package PhpMyAdmin-Transformations
+ * @subpackage Append
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/* Get the transformations interface */
+require_once 'libraries/plugins/TransformationsPlugin.class.php';
+
+/**
+ * Provides common methods for all of the append transformations plugins.
+ *
+ * @package PhpMyAdmin-Transformations
+ * @subpackage Append
+ */
+abstract class AppendTransformationsPlugin extends TransformationsPlugin
+{
+ /**
+ * Gets the transformation description of the specific plugin
+ *
+ * @return string
+ */
+ public static function getInfo()
+ {
+ return __(
+ 'Appends text to a string. The only option is the text to be appended'
+ . ' (enclosed in single quotes, default empty string).'
+ );
+ }
+
+ /**
+ * Does the actual work of each specific transformations plugin.
+ *
+ * @param string $buffer text to be transformed
+ * @param array $options transformation options
+ * @param string $meta meta information
+ *
+ * @return string
+ */
+ public function applyTransformation($buffer, $options = array(), $meta = '')
+ {
+ if (! isset($options[0]) || $options[0] == '') {
+ $options[0] = '';
+ }
+ //just append the option to the original text
+ return $buffer . htmlspecialchars($options[0]);
+ }
+
+ /**
+ * This method is called when any PluginManager to which the observer
+ * is attached calls PluginManager::notify()
+ *
+ * @param SplSubject $subject The PluginManager notifying the observer
+ * of an update.
+ *
+ * @todo implement
+ * @return void
+ */
+ public function update (SplSubject $subject)
+ {
+ ;
+ }
+
+
+ /* ~~~~~~~~~~~~~~~~~~~~ Getters and Setters ~~~~~~~~~~~~~~~~~~~~ */
+
+
+ /**
+ * Gets the transformation name of the specific plugin
+ *
+ * @return string
+ */
+ public static function getName()
+ {
+ return "Append";
+ }
+}
+?>
diff --git a/libraries/plugins/transformations/abstract/DateFormatTransformationsPlugin.class.php b/libraries/plugins/transformations/abstract/DateFormatTransformationsPlugin.class.php
new file mode 100644
index 0000000000..b68793466f
--- /dev/null
+++ b/libraries/plugins/transformations/abstract/DateFormatTransformationsPlugin.class.php
@@ -0,0 +1,178 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Abstract class for the date format transformations plugins
+ *
+ * @package PhpMyAdmin-Transformations
+ * @subpackage DateFormat
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/* Get the transformations interface */
+require_once 'libraries/plugins/TransformationsPlugin.class.php';
+
+/**
+ * Provides common methods for all of the date format transformations plugins.
+ *
+ * @package PhpMyAdmin
+ */
+abstract class DateFormatTransformationsPlugin extends TransformationsPlugin
+{
+ /**
+ * Gets the transformation description of the specific plugin
+ *
+ * @return string
+ */
+ public static function getInfo()
+ {
+ return __(
+ 'Displays a TIME, TIMESTAMP, DATETIME or numeric unix timestamp'
+ . ' column as formatted date. The first option is the offset (in'
+ . ' hours) which will be added to the timestamp (Default: 0). Use'
+ . ' second option to specify a different date/time format string.'
+ . ' Third option determines whether you want to see local date or'
+ . ' UTC one (use "local" or "utc" strings) for that. According to'
+ . ' that, date format has different value - for "local" see the'
+ . ' documentation for PHP\'s strftime() function and for "utc" it'
+ . ' is done using gmdate() function.'
+ );
+ }
+
+ /**
+ * Does the actual work of each specific transformations plugin.
+ *
+ * @param string $buffer text to be transformed
+ * @param array $options transformation options
+ * @param string $meta meta information
+ *
+ * @return string
+ */
+ public function applyTransformation($buffer, $options = array(), $meta = '')
+ {
+ // possibly use a global transform and feed it with special options
+
+ // further operations on $buffer using the $options[] array.
+ if (empty($options[0])) {
+ $options[0] = 0;
+ }
+
+ if (empty($options[2])) {
+ $options[2] = 'local';
+ } else {
+ $options[2] = strtolower($options[2]);
+ }
+
+ if (empty($options[1])) {
+ if ($options[2] == 'local') {
+ $options[1] = __('%B %d, %Y at %I:%M %p');
+ } else {
+ $options[1] = 'Y-m-d H:i:s';
+ }
+ }
+
+ $timestamp = -1;
+
+ // INT columns will be treated as UNIX timestamps
+ // and need to be detected before the verification for
+ // MySQL TIMESTAMP
+ if ($meta->type == 'int') {
+ $timestamp = $buffer;
+
+ // Detect TIMESTAMP(6 | 8 | 10 | 12 | 14)
+ // TIMESTAMP (2 | 4) not supported here.
+ // (Note: prior to MySQL 4.1, TIMESTAMP has a display size
+ // for example TIMESTAMP(8) means YYYYMMDD)
+ } else if (preg_match('/^(\d{2}){3,7}$/', $buffer)) {
+
+ if (strlen($buffer) == 14 || strlen($buffer) == 8) {
+ $offset = 4;
+ } else {
+ $offset = 2;
+ }
+
+ $d = array();
+ $d['year'] = substr($buffer, 0, $offset);
+ $d['month'] = substr($buffer, $offset, 2);
+ $d['day'] = substr($buffer, $offset + 2, 2);
+ $d['hour'] = substr($buffer, $offset + 4, 2);
+ $d['minute'] = substr($buffer, $offset + 6, 2);
+ $d['second'] = substr($buffer, $offset + 8, 2);
+
+ if (checkdate($d['month'], $d['day'], $d['year'])) {
+ $timestamp = mktime(
+ $d['hour'],
+ $d['minute'],
+ $d['second'],
+ $d['month'],
+ $d['day'],
+ $d['year']
+ );
+ }
+ // If all fails, assume one of the dozens of valid strtime() syntaxes
+ // (http://www.gnu.org/manual/tar-1.12/html_chapter/tar_7.html)
+ } else {
+ if (preg_match('/^[0-9]\d{1,9}$/', $buffer)) {
+ $timestamp = (int)$buffer;
+ } else {
+ $timestamp = strtotime($buffer);
+ }
+ }
+
+ // If all above failed, maybe it's a Unix timestamp already?
+ if ($timestamp < 0 && preg_match('/^[1-9]\d{1,9}$/', $buffer)) {
+ $timestamp = $buffer;
+ }
+
+ // Reformat a valid timestamp
+ if ($timestamp >= 0) {
+ $timestamp -= $options[0] * 60 * 60;
+ $source = $buffer;
+ if ($options[2] == 'local') {
+ $text = PMA_Util::localisedDate(
+ $timestamp,
+ $options[1]
+ );
+ } elseif ($options[2] == 'utc') {
+ $text = gmdate($options[1], $timestamp);
+ } else {
+ $text = 'INVALID DATE TYPE';
+ }
+ $buffer = '<dfn onclick="alert(\'' . $source . '\');" title="'
+ . $source . '">' . $text . '</dfn>';
+ }
+
+ return $buffer;
+ }
+
+ /**
+ * This method is called when any PluginManager to which the observer
+ * is attached calls PluginManager::notify()
+ *
+ * @param SplSubject $subject The PluginManager notifying the observer
+ * of an update.
+ *
+ * @todo implement
+ * @return void
+ */
+ public function update (SplSubject $subject)
+ {
+ ;
+ }
+
+
+ /* ~~~~~~~~~~~~~~~~~~~~ Getters and Setters ~~~~~~~~~~~~~~~~~~~~ */
+
+
+ /**
+ * Gets the transformation name of the specific plugin
+ *
+ * @return string
+ */
+ public static function getName()
+ {
+ return "Date Format";
+ }
+}
+?> \ No newline at end of file
diff --git a/libraries/plugins/transformations/abstract/DownloadTransformationsPlugin.class.php b/libraries/plugins/transformations/abstract/DownloadTransformationsPlugin.class.php
new file mode 100644
index 0000000000..134fb7fb2a
--- /dev/null
+++ b/libraries/plugins/transformations/abstract/DownloadTransformationsPlugin.class.php
@@ -0,0 +1,110 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Abstract class for the download transformations plugins
+ *
+ * @package PhpMyAdmin-Transformations
+ * @subpackage Download
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/* Get the transformations interface */
+require_once 'libraries/plugins/TransformationsPlugin.class.php';
+
+/**
+ * Provides common methods for all of the download transformations plugins.
+ *
+ * @package PhpMyAdmin
+ */
+abstract class DownloadTransformationsPlugin extends TransformationsPlugin
+{
+ /**
+ * Gets the transformation description of the specific plugin
+ *
+ * @return string
+ */
+ public static function getInfo()
+ {
+ return __(
+ 'Displays a link to download the binary data of the column. You can'
+ . ' use the first option to specify the filename, or use the second'
+ . ' option as the name of a column which contains the filename. If'
+ . ' you use the second option, you need to set the first option to'
+ . ' the empty string.'
+ );
+ }
+
+ /**
+ * Does the actual work of each specific transformations plugin.
+ *
+ * @param string $buffer text to be transformed
+ * @param array $options transformation options
+ * @param string $meta meta information
+ *
+ * @return string
+ */
+ public function applyTransformation($buffer, $options = array(), $meta = '')
+ {
+ global $row, $fields_meta;
+
+ if (isset($options[0]) && !empty($options[0])) {
+ $cn = $options[0]; // filename
+ } else {
+ if (isset($options[1]) && !empty($options[1])) {
+ foreach ($fields_meta as $key => $val) {
+ if ($val->name == $options[1]) {
+ $pos = $key;
+ break;
+ }
+ }
+ if (isset($pos)) {
+ $cn = $row[$pos];
+ }
+ }
+ if (empty($cn)) {
+ $cn = 'binary_file.dat';
+ }
+ }
+
+ return sprintf(
+ '<a href="transformation_wrapper.php%s&amp;ct=application'
+ . '/octet-stream&amp;cn=%s" title="%s">%s</a>',
+ $options['wrapper_link'],
+ urlencode($cn),
+ htmlspecialchars($cn),
+ htmlspecialchars($cn)
+ );
+ }
+
+ /**
+ * This method is called when any PluginManager to which the observer
+ * is attached calls PluginManager::notify()
+ *
+ * @param SplSubject $subject The PluginManager notifying the observer
+ * of an update.
+ *
+ * @todo implement
+ * @return void
+ */
+ public function update (SplSubject $subject)
+ {
+ ;
+ }
+
+
+ /* ~~~~~~~~~~~~~~~~~~~~ Getters and Setters ~~~~~~~~~~~~~~~~~~~~ */
+
+
+ /**
+ * Gets the transformation name of the specific plugin
+ *
+ * @return string
+ */
+ public static function getName()
+ {
+ return "Download";
+ }
+}
+?> \ No newline at end of file
diff --git a/libraries/plugins/transformations/abstract/ExternalTransformationsPlugin.class.php b/libraries/plugins/transformations/abstract/ExternalTransformationsPlugin.class.php
new file mode 100644
index 0000000000..86ba1accba
--- /dev/null
+++ b/libraries/plugins/transformations/abstract/ExternalTransformationsPlugin.class.php
@@ -0,0 +1,182 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Abstract class for the external transformations plugins
+ *
+ * @package PhpMyAdmin-Transformations
+ * @subpackage External
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/* Get the transformations interface */
+require_once 'libraries/plugins/TransformationsPlugin.class.php';
+
+/**
+ * Provides common methods for all of the external transformations plugins.
+ *
+ * @package PhpMyAdmin
+ */
+abstract class ExternalTransformationsPlugin extends TransformationsPlugin
+{
+ /**
+ * Gets the transformation description of the specific plugin
+ *
+ * @return string
+ */
+ public static function getInfo()
+ {
+ return __(
+ 'LINUX ONLY: Launches an external application and feeds it the column'
+ . ' data via standard input. Returns the standard output of the'
+ . ' application. The default is Tidy, to pretty-print HTML code.'
+ . ' For security reasons, you have to manually edit the file'
+ . ' libraries/plugins/transformations/Text_Plain_External'
+ . '.class.php and list the tools you want to make available.'
+ . ' The first option is then the number of the program you want to'
+ . ' use and the second option is the parameters for the program.'
+ . ' The third option, if set to 1, will convert the output using'
+ . ' htmlspecialchars() (Default 1). The fourth option, if set to 1,'
+ . ' will prevent wrapping and ensure that the output appears all on'
+ . ' one line (Default 1).'
+ );
+ }
+
+ /**
+ * Enables no-wrapping
+ *
+ * @param array $options transformation options
+ *
+ * @return bool
+ */
+ public function applyTransformationNoWrap($options = array())
+ {
+ if (! isset($options[3]) || $options[3] == '') {
+ $nowrap = true;
+ } elseif ($options[3] == '1' || $options[3] == 1) {
+ $nowrap = true;
+ } else {
+ $nowrap = false;
+ }
+
+ return $nowrap;
+ }
+
+ /**
+ * Does the actual work of each specific transformations plugin.
+ *
+ * @param string $buffer text to be transformed
+ * @param array $options transformation options
+ * @param string $meta meta information
+ *
+ * @return string
+ */
+ public function applyTransformation($buffer, $options = array(), $meta = '')
+ {
+ // possibly use a global transform and feed it with special options
+
+ // further operations on $buffer using the $options[] array.
+
+ $allowed_programs = array();
+
+ //
+ // WARNING:
+ //
+ // It's up to administrator to allow anything here. Note that users may
+ // specify any parameters, so when programs allow output redirection or
+ // any other possibly dangerous operations, you should write wrapper
+ // script that will publish only functions you really want.
+ //
+ // Add here program definitions like (note that these are NOT safe
+ // programs):
+ //
+ //$allowed_programs[0] = '/usr/local/bin/tidy';
+ //$allowed_programs[1] = '/usr/local/bin/validate';
+
+ // no-op when no allowed programs
+ if (count($allowed_programs) == 0) {
+ return $buffer;
+ }
+
+ if (! isset($options[0])
+ || $options[0] == ''
+ || ! isset($allowed_programs[$options[0]])
+ ) {
+ $program = $allowed_programs[0];
+ } else {
+ $program = $allowed_programs[$options[0]];
+ }
+
+ if (!isset($options[1]) || $options[1] == '') {
+ $poptions = '-f /dev/null -i -wrap -q';
+ } else {
+ $poptions = $options[1];
+ }
+
+ if (!isset($options[2]) || $options[2] == '') {
+ $options[2] = 1;
+ }
+
+ if (!isset($options[3]) || $options[3] == '') {
+ $options[3] = 1;
+ }
+
+ // needs PHP >= 4.3.0
+ $newstring = '';
+ $descriptorspec = array(
+ 0 => array("pipe", "r"),
+ 1 => array("pipe", "w")
+ );
+ $process = proc_open($program . ' ' . $poptions, $descriptorspec, $pipes);
+ if (is_resource($process)) {
+ fwrite($pipes[0], $buffer);
+ fclose($pipes[0]);
+
+ while (!feof($pipes[1])) {
+ $newstring .= fgets($pipes[1], 1024);
+ }
+ fclose($pipes[1]);
+ // we don't currently use the return value
+ proc_close($process);
+ }
+
+ if ($options[2] == 1 || $options[2] == '2') {
+ $retstring = htmlspecialchars($newstring);
+ } else {
+ $retstring = $newstring;
+ }
+
+ return $retstring;
+ }
+
+ /**
+ * This method is called when any PluginManager to which the observer
+ * is attached calls PluginManager::notify()
+ *
+ * @param SplSubject $subject The PluginManager notifying the observer
+ * of an update.
+ *
+ * @todo implement
+ * @return void
+ */
+ public function update (SplSubject $subject)
+ {
+ ;
+ }
+
+
+ /* ~~~~~~~~~~~~~~~~~~~~ Getters and Setters ~~~~~~~~~~~~~~~~~~~~ */
+
+
+ /**
+ * Gets the transformation name of the specific plugin
+ *
+ * @return string
+ */
+ public static function getName()
+ {
+ return "External";
+ }
+}
+?> \ No newline at end of file
diff --git a/libraries/plugins/transformations/abstract/FormattedTransformationsPlugin.class.php b/libraries/plugins/transformations/abstract/FormattedTransformationsPlugin.class.php
new file mode 100644
index 0000000000..7a39e45a17
--- /dev/null
+++ b/libraries/plugins/transformations/abstract/FormattedTransformationsPlugin.class.php
@@ -0,0 +1,80 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Abstract class for the formatted transformations plugins
+ *
+ * @package PhpMyAdmin-Transformations
+ * @subpackage Formatted
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/* Get the transformations interface */
+require_once 'libraries/plugins/TransformationsPlugin.class.php';
+
+/**
+ * Provides common methods for all of the formatted transformations plugins.
+ *
+ * @package PhpMyAdmin
+ */
+abstract class FormattedTransformationsPlugin extends TransformationsPlugin
+{
+ /**
+ * Gets the transformation description of the specific plugin
+ *
+ * @return string
+ */
+ public static function getInfo()
+ {
+ return __(
+ 'Displays the contents of the column as-is, without running it'
+ . ' through htmlspecialchars(). That is, the column is assumed'
+ . ' to contain valid HTML.'
+ );
+ }
+
+ /**
+ * Does the actual work of each specific transformations plugin.
+ *
+ * @param string $buffer text to be transformed
+ * @param array $options transformation options
+ * @param string $meta meta information
+ *
+ * @return string
+ */
+ public function applyTransformation($buffer, $options = array(), $meta = '')
+ {
+ return $buffer;
+ }
+
+ /**
+ * This method is called when any PluginManager to which the observer
+ * is attached calls PluginManager::notify()
+ *
+ * @param SplSubject $subject The PluginManager notifying the observer
+ * of an update.
+ *
+ * @todo implement
+ * @return void
+ */
+ public function update (SplSubject $subject)
+ {
+ ;
+ }
+
+
+ /* ~~~~~~~~~~~~~~~~~~~~ Getters and Setters ~~~~~~~~~~~~~~~~~~~~ */
+
+
+ /**
+ * Gets the transformation name of the specific plugin
+ *
+ * @return string
+ */
+ public static function getName()
+ {
+ return "Formatted";
+ }
+}
+?> \ No newline at end of file
diff --git a/libraries/plugins/transformations/abstract/HexTransformationsPlugin.class.php b/libraries/plugins/transformations/abstract/HexTransformationsPlugin.class.php
new file mode 100644
index 0000000000..2809837b8b
--- /dev/null
+++ b/libraries/plugins/transformations/abstract/HexTransformationsPlugin.class.php
@@ -0,0 +1,91 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Abstract class for the hex transformations plugins
+ *
+ * @package PhpMyAdmin-Transformations
+ * @subpackage Hex
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/* Get the transformations interface */
+require_once 'libraries/plugins/TransformationsPlugin.class.php';
+
+/**
+ * Provides common methods for all of the hex transformations plugins.
+ *
+ * @package PhpMyAdmin
+ */
+abstract class HexTransformationsPlugin extends TransformationsPlugin
+{
+ /**
+ * Gets the transformation description of the specific plugin
+ *
+ * @return string
+ */
+ public static function getInfo()
+ {
+ return __(
+ 'Displays hexadecimal representation of data. Optional first'
+ . ' parameter specifies how often space will be added (defaults'
+ . ' to 2 nibbles).'
+ );
+ }
+
+ /**
+ * Does the actual work of each specific transformations plugin.
+ *
+ * @param string $buffer text to be transformed
+ * @param array $options transformation options
+ * @param string $meta meta information
+ *
+ * @return string
+ */
+ public function applyTransformation($buffer, $options = array(), $meta = '')
+ {
+ // possibly use a global transform and feed it with special options
+ if (!isset($options[0])) {
+ $options[0] = 2;
+ } else {
+ $options[0] = (int)$options[0];
+ }
+
+ if ($options[0] < 1) {
+ return bin2hex($buffer);
+ } else {
+ return chunk_split(bin2hex($buffer), $options[0], ' ');
+ }
+ }
+
+ /**
+ * This method is called when any PluginManager to which the observer
+ * is attached calls PluginManager::notify()
+ *
+ * @param SplSubject $subject The PluginManager notifying the observer
+ * of an update.
+ *
+ * @todo implement
+ * @return void
+ */
+ public function update (SplSubject $subject)
+ {
+ ;
+ }
+
+
+ /* ~~~~~~~~~~~~~~~~~~~~ Getters and Setters ~~~~~~~~~~~~~~~~~~~~ */
+
+
+ /**
+ * Gets the transformation name of the specific plugin
+ *
+ * @return string
+ */
+ public static function getName()
+ {
+ return "Hex";
+ }
+}
+?> \ No newline at end of file
diff --git a/libraries/plugins/transformations/abstract/ImageLinkTransformationsPlugin.class.php b/libraries/plugins/transformations/abstract/ImageLinkTransformationsPlugin.class.php
new file mode 100644
index 0000000000..d5059fdf48
--- /dev/null
+++ b/libraries/plugins/transformations/abstract/ImageLinkTransformationsPlugin.class.php
@@ -0,0 +1,87 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Abstract class for the link transformations plugins
+ *
+ * @package PhpMyAdmin-Transformations
+ * @subpackage Link
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/* Get the transformations interface */
+require_once 'libraries/plugins/TransformationsPlugin.class.php';
+/* For PMA_Transformation_globalHtmlReplace */
+require_once 'libraries/transformations.lib.php';
+
+/**
+ * Provides common methods for all of the link transformations plugins.
+ *
+ * @package PhpMyAdmin
+ */
+abstract class ImageLinkTransformationsPlugin extends TransformationsPlugin
+{
+ /**
+ * Gets the transformation description of the specific plugin
+ *
+ * @return string
+ */
+ public static function getInfo()
+ {
+ return __(
+ 'Displays a link to download this image.'
+ );
+ }
+
+ /**
+ * Does the actual work of each specific transformations plugin.
+ *
+ * @param string $buffer text to be transformed
+ * @param array $options transformation options
+ * @param string $meta meta information
+ *
+ * @return string
+ */
+ public function applyTransformation($buffer, $options = array(), $meta = '')
+ {
+ $transform_options = array (
+ 'string' => '<a href="transformation_wrapper.php'
+ . $options['wrapper_link'] . '" alt="[__BUFFER__]">[BLOB]</a>'
+ );
+ return PMA_Transformation_globalHtmlReplace(
+ $buffer,
+ $transform_options
+ );
+ }
+
+ /**
+ * This method is called when any PluginManager to which the observer
+ * is attached calls PluginManager::notify()
+ *
+ * @param SplSubject $subject The PluginManager notifying the observer
+ * of an update.
+ *
+ * @todo implement
+ * @return void
+ */
+ public function update (SplSubject $subject)
+ {
+ ;
+ }
+
+
+ /* ~~~~~~~~~~~~~~~~~~~~ Getters and Setters ~~~~~~~~~~~~~~~~~~~~ */
+
+
+ /**
+ * Gets the transformation name of the specific plugin
+ *
+ * @return string
+ */
+ public static function getName()
+ {
+ return "ImageLink";
+ }
+}
+?>
diff --git a/libraries/plugins/transformations/abstract/InlineTransformationsPlugin.class.php b/libraries/plugins/transformations/abstract/InlineTransformationsPlugin.class.php
new file mode 100644
index 0000000000..e6c9ca822c
--- /dev/null
+++ b/libraries/plugins/transformations/abstract/InlineTransformationsPlugin.class.php
@@ -0,0 +1,101 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Abstract class for the inline transformations plugins
+ *
+ * @package PhpMyAdmin-Transformations
+ * @subpackage Inline
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/* Get the transformations interface */
+require_once 'libraries/plugins/TransformationsPlugin.class.php';
+/* For PMA_Transformation_globalHtmlReplace */
+require_once 'libraries/transformations.lib.php';
+
+/**
+ * Provides common methods for all of the inline transformations plugins.
+ *
+ * @package PhpMyAdmin
+ */
+abstract class InlineTransformationsPlugin extends TransformationsPlugin
+{
+ /**
+ * Gets the transformation description of the specific plugin
+ *
+ * @return string
+ */
+ public static function getInfo()
+ {
+ return __(
+ 'Displays a clickable thumbnail. The options are the maximum width'
+ . ' and height in pixels. The original aspect ratio is preserved.'
+ );
+ }
+
+ /**
+ * Does the actual work of each specific transformations plugin.
+ *
+ * @param string $buffer text to be transformed
+ * @param array $options transformation options
+ * @param string $meta meta information
+ *
+ * @return string
+ */
+ public function applyTransformation($buffer, $options = array(), $meta = '')
+ {
+ if (PMA_IS_GD2) {
+ $transform_options = array (
+ 'string' => '<a href="transformation_wrapper.php'
+ . $options['wrapper_link']
+ . '" target="_blank"><img src="transformation_wrapper.php'
+ . $options['wrapper_link'] . '&amp;resize=jpeg&amp;newWidth='
+ . (isset($options[0]) ? $options[0] : '100') . '&amp;newHeight='
+ . (isset($options[1]) ? $options[1] : 100)
+ . '" alt="[__BUFFER__]" border="0" /></a>'
+ );
+ } else {
+ $transform_options = array (
+ 'string' => '<img src="transformation_wrapper.php'
+ . $options['wrapper_link']
+ . '" alt="[__BUFFER__]" width="320" height="240" />'
+ );
+ }
+ return PMA_Transformation_globalHtmlReplace(
+ $buffer,
+ $transform_options
+ );
+ }
+
+ /**
+ * This method is called when any PluginManager to which the observer
+ * is attached calls PluginManager::notify()
+ *
+ * @param SplSubject $subject The PluginManager notifying the observer
+ * of an update.
+ *
+ * @todo implement
+ * @return void
+ */
+ public function update (SplSubject $subject)
+ {
+ ;
+ }
+
+
+ /* ~~~~~~~~~~~~~~~~~~~~ Getters and Setters ~~~~~~~~~~~~~~~~~~~~ */
+
+
+ /**
+ * Gets the transformation name of the specific plugin
+ *
+ * @return string
+ */
+ public static function getName()
+ {
+ return "Inline";
+ }
+}
+?>
diff --git a/libraries/plugins/transformations/abstract/LongToIPv4TransformationsPlugin.class.php b/libraries/plugins/transformations/abstract/LongToIPv4TransformationsPlugin.class.php
new file mode 100644
index 0000000000..d1431ce42e
--- /dev/null
+++ b/libraries/plugins/transformations/abstract/LongToIPv4TransformationsPlugin.class.php
@@ -0,0 +1,83 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Abstract class for the long to IPv4 transformations plugins
+ *
+ * @package PhpMyAdmin-Transformations
+ * @subpackage LongToIPv4
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/* Get the transformations interface */
+require_once 'libraries/plugins/TransformationsPlugin.class.php';
+
+/**
+ * Provides common methods for all of the long to IPv4 transformations plugins.
+ *
+ * @package PhpMyAdmin
+ */
+abstract class LongToIPv4TransformationsPlugin extends TransformationsPlugin
+{
+ /**
+ * Gets the transformation description of the specific plugin
+ *
+ * @return string
+ */
+ public static function getInfo()
+ {
+ return __(
+ 'Converts an (IPv4) Internet network address into a string in'
+ . ' Internet standard dotted format.'
+ );
+ }
+
+ /**
+ * Does the actual work of each specific transformations plugin.
+ *
+ * @param string $buffer text to be transformed
+ * @param array $options transformation options
+ * @param string $meta meta information
+ *
+ * @return string
+ */
+ public function applyTransformation($buffer, $options = array(), $meta = '')
+ {
+ if ($buffer < 0 || $buffer > 4294967295) {
+ return $buffer;
+ }
+
+ return long2ip($buffer);
+ }
+
+ /**
+ * This method is called when any PluginManager to which the observer
+ * is attached calls PluginManager::notify()
+ *
+ * @param SplSubject $subject The PluginManager notifying the observer
+ * of an update.
+ *
+ * @todo implement
+ * @return void
+ */
+ public function update (SplSubject $subject)
+ {
+ ;
+ }
+
+
+ /* ~~~~~~~~~~~~~~~~~~~~ Getters and Setters ~~~~~~~~~~~~~~~~~~~~ */
+
+
+ /**
+ * Gets the transformation name of the specific plugin
+ *
+ * @return string
+ */
+ public static function getName()
+ {
+ return "Long To IPv4";
+ }
+}
+?> \ No newline at end of file
diff --git a/libraries/plugins/transformations/abstract/SQLTransformationsPlugin.class.php b/libraries/plugins/transformations/abstract/SQLTransformationsPlugin.class.php
new file mode 100644
index 0000000000..991f54d7d9
--- /dev/null
+++ b/libraries/plugins/transformations/abstract/SQLTransformationsPlugin.class.php
@@ -0,0 +1,82 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Abstract class for the SQL transformations plugins
+ *
+ * @package PhpMyAdmin-Transformations
+ * @subpackage SQL
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/* Get the transformations interface */
+require_once 'libraries/plugins/TransformationsPlugin.class.php';
+
+/**
+ * Provides common methods for all of the SQL transformations plugins.
+ *
+ * @package PhpMyAdmin
+ */
+abstract class SQLTransformationsPlugin extends TransformationsPlugin
+{
+ /**
+ * Gets the transformation description of the specific plugin
+ *
+ * @return string
+ */
+ public static function getInfo()
+ {
+ return __(
+ 'Formats text as SQL query with syntax highlighting.'
+ );
+ }
+
+ /**
+ * Does the actual work of each specific transformations plugin.
+ *
+ * @param string $buffer text to be transformed
+ * @param array $options transformation options
+ * @param string $meta meta information
+ *
+ * @return string
+ */
+ public function applyTransformation($buffer, $options = array(), $meta = '')
+ {
+ // see PMA_highlightSQL()
+ $result = PMA_Util::formatSql($buffer);
+ // Need to clear error state not to break subsequent queries display.
+ PMA_SQP_resetError();
+ return $result;
+ }
+
+ /**
+ * This method is called when any PluginManager to which the observer
+ * is attached calls PluginManager::notify()
+ *
+ * @param SplSubject $subject The PluginManager notifying the observer
+ * of an update.
+ *
+ * @todo implement
+ * @return void
+ */
+ public function update (SplSubject $subject)
+ {
+ ;
+ }
+
+
+ /* ~~~~~~~~~~~~~~~~~~~~ Getters and Setters ~~~~~~~~~~~~~~~~~~~~ */
+
+
+ /**
+ * Gets the transformation name of the specific plugin
+ *
+ * @return string
+ */
+ public static function getName()
+ {
+ return "SQL";
+ }
+}
+?>
diff --git a/libraries/plugins/transformations/abstract/SubstringTransformationsPlugin.class.php b/libraries/plugins/transformations/abstract/SubstringTransformationsPlugin.class.php
new file mode 100644
index 0000000000..b6fb75af13
--- /dev/null
+++ b/libraries/plugins/transformations/abstract/SubstringTransformationsPlugin.class.php
@@ -0,0 +1,118 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Abstract class for the substring transformations plugins
+ *
+ * @package PhpMyAdmin-Transformations
+ * @subpackage Substring
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/* Get the transformations interface */
+require_once 'libraries/plugins/TransformationsPlugin.class.php';
+
+/**
+ * Provides common methods for all of the substring transformations plugins.
+ *
+ * @package PhpMyAdmin
+ */
+abstract class SubstringTransformationsPlugin extends TransformationsPlugin
+{
+ /**
+ * Gets the transformation description of the specific plugin
+ *
+ * @return string
+ */
+ public static function getInfo()
+ {
+ return __(
+ 'Displays a part of a string. The first option is the number of'
+ . ' characters to skip from the beginning of the string (Default 0).'
+ . ' The second option is the number of characters to return (Default:'
+ . ' until end of string). The third option is the string to append'
+ . ' and/or prepend when truncation occurs (Default: "…").'
+ );
+ }
+
+ /**
+ * Does the actual work of each specific transformations plugin.
+ *
+ * @param string $buffer text to be transformed
+ * @param array $options transformation options
+ * @param string $meta meta information
+ *
+ * @return string
+ */
+ public function applyTransformation($buffer, $options = array(), $meta = '')
+ {
+ // possibly use a global transform and feed it with special options
+
+ // further operations on $buffer using the $options[] array.
+ if (!isset($options[0]) || $options[0] == '') {
+ $options[0] = 0;
+ }
+
+ if (!isset($options[1]) || $options[1] == '') {
+ $options[1] = 'all';
+ }
+
+ if (!isset($options[2]) || $options[2] == '') {
+ $options[2] = '…';
+ }
+
+ $newtext = '';
+ if ($options[1] != 'all') {
+ $newtext = $GLOBALS['PMA_String']->substr(
+ $buffer, $options[0], $options[1]
+ );
+ } else {
+ $newtext = $GLOBALS['PMA_String']->substr($buffer, $options[0]);
+ }
+
+ $length = strlen($newtext);
+ $baselength = strlen($buffer);
+ if ($length != $baselength) {
+ if ($options[0] != 0) {
+ $newtext = $options[2] . $newtext;
+ }
+
+ if (($length + $options[0]) != $baselength) {
+ $newtext .= $options[2];
+ }
+ }
+
+ return $newtext;
+ }
+
+ /**
+ * This method is called when any PluginManager to which the observer
+ * is attached calls PluginManager::notify()
+ *
+ * @param SplSubject $subject The PluginManager notifying the observer
+ * of an update.
+ *
+ * @todo implement
+ * @return void
+ */
+ public function update (SplSubject $subject)
+ {
+ ;
+ }
+
+
+ /* ~~~~~~~~~~~~~~~~~~~~ Getters and Setters ~~~~~~~~~~~~~~~~~~~~ */
+
+
+ /**
+ * Gets the transformation name of the specific plugin
+ *
+ * @return string
+ */
+ public static function getName()
+ {
+ return "Substring";
+ }
+}
+?>
diff --git a/libraries/plugins/transformations/abstract/TextImageLinkTransformationsPlugin.class.php b/libraries/plugins/transformations/abstract/TextImageLinkTransformationsPlugin.class.php
new file mode 100644
index 0000000000..a1824c9315
--- /dev/null
+++ b/libraries/plugins/transformations/abstract/TextImageLinkTransformationsPlugin.class.php
@@ -0,0 +1,96 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Abstract class for the image link transformations plugins
+ *
+ * @package PhpMyAdmin-Transformations
+ * @subpackage ImageLink
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/* Get the transformations interface */
+require_once 'libraries/plugins/TransformationsPlugin.class.php';
+/* For PMA_Transformation_globalHtmlReplace */
+require_once 'libraries/transformations.lib.php';
+
+/**
+ * Provides common methods for all of the image link transformations plugins.
+ *
+ * @package PhpMyAdmin
+ */
+abstract class TextImageLinkTransformationsPlugin extends TransformationsPlugin
+{
+ /**
+ * Gets the transformation description of the specific plugin
+ *
+ * @return string
+ */
+ public static function getInfo()
+ {
+ return __(
+ 'Displays an image and a link; the column contains the filename. The'
+ . ' first option is a URL prefix like "http://www.example.com/". The'
+ . ' second and third options are the width and the height in pixels.'
+ );
+ }
+
+ /**
+ * Does the actual work of each specific transformations plugin.
+ *
+ * @param string $buffer text to be transformed
+ * @param array $options transformation options
+ * @param string $meta meta information
+ *
+ * @return string
+ */
+ public function applyTransformation($buffer, $options = array(), $meta = '')
+ {
+ $transform_options = array (
+ 'string' => '<a href="' . (isset($options[0]) ? $options[0] : '')
+ . $buffer . '" target="_blank"><img src="'
+ . (isset($options[0]) ? $options[0] : '') . $buffer
+ . '" border="0" width="' . (isset($options[1]) ? $options[1] : 100)
+ . '" height="' . (isset($options[2]) ? $options[2] : 50) . '" />'
+ . $buffer . '</a>'
+ );
+
+ $buffer = PMA_Transformation_globalHtmlReplace(
+ $buffer,
+ $transform_options
+ );
+
+ return $buffer;
+ }
+
+ /**
+ * This method is called when any PluginManager to which the observer
+ * is attached calls PluginManager::notify()
+ *
+ * @param SplSubject $subject The PluginManager notifying the observer
+ * of an update.
+ *
+ * @todo implement
+ * @return void
+ */
+ public function update (SplSubject $subject)
+ {
+ ;
+ }
+
+
+ /* ~~~~~~~~~~~~~~~~~~~~ Getters and Setters ~~~~~~~~~~~~~~~~~~~~ */
+
+
+ /**
+ * Gets the transformation name of the specific plugin
+ *
+ * @return string
+ */
+ public static function getName()
+ {
+ return "Image Link";
+ }
+}
+?>
diff --git a/libraries/plugins/transformations/abstract/TextLinkTransformationsPlugin.class.php b/libraries/plugins/transformations/abstract/TextLinkTransformationsPlugin.class.php
new file mode 100644
index 0000000000..da8fb59d0e
--- /dev/null
+++ b/libraries/plugins/transformations/abstract/TextLinkTransformationsPlugin.class.php
@@ -0,0 +1,98 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Abstract class for the link transformations plugins
+ *
+ * @package PhpMyAdmin-Transformations
+ * @subpackage Link
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/* Get the transformations interface */
+require_once 'libraries/plugins/TransformationsPlugin.class.php';
+/* For PMA_Transformation_globalHtmlReplace */
+require_once 'libraries/transformations.lib.php';
+
+/**
+ * Provides common methods for all of the link transformations plugins.
+ *
+ * @package PhpMyAdmin
+ */
+abstract class TextLinkTransformationsPlugin extends TransformationsPlugin
+{
+ /**
+ * Gets the transformation description of the specific plugin
+ *
+ * @return string
+ */
+ public static function getInfo()
+ {
+ return __(
+ 'Displays a link; the column contains the filename. The first option'
+ . ' is a URL prefix like "http://www.example.com/". The second option'
+ . ' is a title for the link.'
+ );
+ }
+
+ /**
+ * Does the actual work of each specific transformations plugin.
+ *
+ * @param string $buffer text to be transformed
+ * @param array $options transformation options
+ * @param string $meta meta information
+ *
+ * @return string
+ */
+ public function applyTransformation($buffer, $options = array(), $meta = '')
+ {
+
+ $append_part = (isset($options[2]) && $options[2]) ? '' : $buffer;
+
+ $transform_options = array (
+ 'string' => '<a href="'
+ . PMA_linkURL((isset($options[0]) ? $options[0] : '') . $append_part)
+ . '" title="'
+ . htmlspecialchars(isset($options[1]) ? $options[1] : '')
+ . '" target="_new">'
+ . htmlspecialchars(isset($options[1]) ? $options[1] : $buffer)
+ . '</a>'
+ );
+
+ return PMA_Transformation_globalHtmlReplace(
+ $buffer,
+ $transform_options
+ );
+ }
+
+ /**
+ * This method is called when any PluginManager to which the observer
+ * is attached calls PluginManager::notify()
+ *
+ * @param SplSubject $subject The PluginManager notifying the observer
+ * of an update.
+ *
+ * @todo implement
+ * @return void
+ */
+ public function update (SplSubject $subject)
+ {
+ ;
+ }
+
+
+ /* ~~~~~~~~~~~~~~~~~~~~ Getters and Setters ~~~~~~~~~~~~~~~~~~~~ */
+
+
+ /**
+ * Gets the transformation name of the specific plugin
+ *
+ * @return string
+ */
+ public static function getName()
+ {
+ return "TextLink";
+ }
+}
+?>
diff --git a/libraries/plugins/transformations/generator_main_class.sh b/libraries/plugins/transformations/generator_main_class.sh
new file mode 100644
index 0000000000..05876671ac
--- /dev/null
+++ b/libraries/plugins/transformations/generator_main_class.sh
@@ -0,0 +1,16 @@
+#!/bin/bash
+#
+# Shell script that creates only the main class for a new transformation
+# plug-in, using a template
+#
+# $1: MIMEType
+# $2: MIMESubtype
+# $3: Transformation Name
+
+if [ $# != 3 ]
+then
+ echo -e "Usage: ./generator_main_class.sh MIMEType MIMESubtype TransformationName\n"
+ exit 65
+fi
+
+./generator_plugin.sh "$1" "$2" "$3" "--generate_only_main_class" \ No newline at end of file
diff --git a/libraries/plugins/transformations/generator_plugin.sh b/libraries/plugins/transformations/generator_plugin.sh
new file mode 100644
index 0000000000..225a2cb98a
--- /dev/null
+++ b/libraries/plugins/transformations/generator_plugin.sh
@@ -0,0 +1,64 @@
+#!/bin/bash
+#
+# Shell script that creates a new transformation plug-in (both main and
+# abstract class) using a template.
+#
+# The 'description' parameter will add a new entry in the language file.
+# Watch out for special escaping.
+#
+# $1: MIMEType
+# $2: MIMESubtype
+# $3: Transformation Name
+# $4: (optional) Description
+
+echo $#
+if [ $# -ne 3 -a $# -ne 4 ]; then
+ echo -e "Usage: ./generator_plugin.sh MIMEType MIMESubtype TransformationName [Description]\n"
+ exit 65
+fi
+
+# make sure that the MIME Type, MIME Subtype and Transformation names
+# are in the correct format
+
+# make all names lowercase
+MT="`echo $1 | tr [:upper:] [:lower:]`"
+MS="`echo $2 | tr [:upper:] [:lower:]`"
+TN="`echo $3 | tr [:upper:] [:lower:]`"
+# make first letter uppercase
+MT="${MT^}"
+MS="${MS^}"
+TN="${TN^}"
+# make the first letter after each underscore uppercase
+MT="`echo $MT`"
+MT="`echo $MT | sed -e 's/_./\U&\E/g'`"
+MS="`echo $MS`"
+MS="`echo $MS | sed -e 's/_./\U&\E/g'`"
+TN="`echo $TN`"
+TN="`echo $TN | sed -e 's/_./\U&\E/g'`"
+
+# define the name of the main class file and of its template
+ClassFile=$MT\_$MS\_$TN.class.php
+Template=TEMPLATE
+# define the name of the abstract class file and its template
+AbstractClassFile=abstract/"$TN"TransformationsPlugin.class.php
+AbstractTemplate=TEMPLATE_ABSTRACT
+# replace template names with argument names
+sed "s/\[MIMEType]/$MT/; s/\[MIMESubtype\]/$MS/; s/\[TransformationName\]/$TN/;" < $Template > $ClassFile
+echo "Created $ClassFile"
+
+GenerateAbstractClass=1
+if [ -n $4 ]; then
+ if [ "$4" == "--generate_only_main_class" ]; then
+ if [ -e $AbstractClassFile ]; then
+ GenerateAbstractClass=0
+ fi
+ fi
+fi
+
+if [ $GenerateAbstractClass -eq 1 ]; then
+ # replace template names with argument names
+ sed "s/\[TransformationName\]/$TN/; s/Description of the transformation./$4/;" < $AbstractTemplate > $AbstractClassFile
+ echo "Created $AbstractClassFile"
+fi
+
+echo "" \ No newline at end of file
diff --git a/libraries/pmd_common.php b/libraries/pmd_common.php
new file mode 100644
index 0000000000..65839910a4
--- /dev/null
+++ b/libraries/pmd_common.php
@@ -0,0 +1,284 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * @package PhpMyAdmin-Designer
+ */
+/**
+ * block attempts to directly run this script
+ */
+if (getcwd() == dirname(__FILE__)) {
+ die('Attack stopped');
+}
+
+/**
+ *
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+$GLOBALS['PMD']['STYLE'] = 'default';
+
+$cfgRelation = PMA_getRelationsParam();
+
+/**
+ * retrieves table info and stores it in $GLOBALS['PMD']
+ *
+ * @return array with table info
+ */
+function get_tables_info()
+{
+ $retval = array();
+
+ $GLOBALS['PMD']['TABLE_NAME'] = array();// that foreach no error
+ $GLOBALS['PMD']['OWNER'] = array();
+ $GLOBALS['PMD']['TABLE_NAME_SMALL'] = array();
+
+ $tables = $GLOBALS['dbi']->getTablesFull($GLOBALS['db']);
+ // seems to be needed later
+ $GLOBALS['dbi']->selectDb($GLOBALS['db']);
+ $i = 0;
+ foreach ($tables as $one_table) {
+ $GLOBALS['PMD']['TABLE_NAME'][$i]
+ = $GLOBALS['db'] . "." . $one_table['TABLE_NAME'];
+ $GLOBALS['PMD']['OWNER'][$i] = $GLOBALS['db'];
+ $GLOBALS['PMD']['TABLE_NAME_SMALL'][$i] = $one_table['TABLE_NAME'];
+
+ $GLOBALS['PMD_URL']['TABLE_NAME'][$i]
+ = urlencode($GLOBALS['db'] . "." . $one_table['TABLE_NAME']);
+ $GLOBALS['PMD_URL']['OWNER'][$i] = urlencode($GLOBALS['db']);
+ $GLOBALS['PMD_URL']['TABLE_NAME_SMALL'][$i]
+ = urlencode($one_table['TABLE_NAME']);
+
+ $GLOBALS['PMD_OUT']['TABLE_NAME'][$i] = htmlspecialchars(
+ $GLOBALS['db'] . "." . $one_table['TABLE_NAME'], ENT_QUOTES
+ );
+ $GLOBALS['PMD_OUT']['OWNER'][$i] = htmlspecialchars(
+ $GLOBALS['db'], ENT_QUOTES
+ );
+ $GLOBALS['PMD_OUT']['TABLE_NAME_SMALL'][$i] = htmlspecialchars(
+ $one_table['TABLE_NAME'], ENT_QUOTES
+ );
+
+ $GLOBALS['PMD']['TABLE_TYPE'][$i] = strtoupper($one_table['ENGINE']);
+
+ $DF = PMA_getDisplayField($GLOBALS['db'], $one_table['TABLE_NAME']);
+ if ($DF != '') {
+ $retval[$GLOBALS['PMD_URL']["TABLE_NAME_SMALL"][$i]] = urlencode($DF);
+ }
+
+ $i++;
+ }
+
+ return $retval;
+}
+
+/**
+ * retrieves table column info
+ *
+ * @return array table column nfo
+ */
+function get_columns_info()
+{
+ $GLOBALS['dbi']->selectDb($GLOBALS['db']);
+ $tab_column = array();
+ for ($i = 0, $cnt = count($GLOBALS['PMD']["TABLE_NAME"]); $i < $cnt; $i++) {
+ $fields_rs = $GLOBALS['dbi']->query(
+ $GLOBALS['dbi']->getColumnsSql(
+ $GLOBALS['db'],
+ $GLOBALS['PMD']["TABLE_NAME_SMALL"][$i],
+ null,
+ true
+ ),
+ null,
+ PMA_DatabaseInterface::QUERY_STORE
+ );
+ $tbl_name_i = $GLOBALS['PMD']['TABLE_NAME'][$i];
+ $j = 0;
+ while ($row = $GLOBALS['dbi']->fetchAssoc($fields_rs)) {
+ $tab_column[$tbl_name_i]['COLUMN_ID'][$j] = $j;
+ $tab_column[$tbl_name_i]['COLUMN_NAME'][$j] = $row['Field'];
+ $tab_column[$tbl_name_i]['TYPE'][$j] = $row['Type'];
+ $tab_column[$tbl_name_i]['NULLABLE'][$j] = $row['Null'];
+ $j++;
+ }
+ }
+ return $tab_column;
+}
+
+/**
+ * returns JavaScript code for intializing vars
+ *
+ * @return string JavaScript code
+ */
+function get_script_contr()
+{
+ $GLOBALS['dbi']->selectDb($GLOBALS['db']);
+ $con["C_NAME"] = array();
+ $i = 0;
+ $alltab_rs = $GLOBALS['dbi']->query(
+ 'SHOW TABLES FROM ' . PMA_Util::backquote($GLOBALS['db']),
+ null,
+ PMA_DatabaseInterface::QUERY_STORE
+ );
+ while ($val = @$GLOBALS['dbi']->fetchRow($alltab_rs)) {
+ $row = PMA_getForeigners($GLOBALS['db'], $val[0], '', 'internal');
+ //echo "<br> internal ".$GLOBALS['db']." - ".$val[0]." - ";
+ //print_r($row);
+ if ($row !== false) {
+ foreach ($row as $field => $value) {
+ $con['C_NAME'][$i] = '';
+ $con['DTN'][$i] = urlencode($GLOBALS['db'] . "." . $val[0]);
+ $con['DCN'][$i] = urlencode($field);
+ $con['STN'][$i] = urlencode(
+ $value['foreign_db'] . "." . $value['foreign_table']
+ );
+ $con['SCN'][$i] = urlencode($value['foreign_field']);
+ $i++;
+ }
+ }
+ $row = PMA_getForeigners($GLOBALS['db'], $val[0], '', 'foreign');
+ //echo "<br> INNO ";
+ //print_r($row);
+ if ($row !== false) {
+ foreach ($row as $field => $value) {
+ $con['C_NAME'][$i] = '';
+ $con['DTN'][$i] = urlencode($GLOBALS['db'].".".$val[0]);
+ $con['DCN'][$i] = urlencode($field);
+ $con['STN'][$i] = urlencode(
+ $value['foreign_db'].".".$value['foreign_table']
+ );
+ $con['SCN'][$i] = urlencode($value['foreign_field']);
+ $i++;
+ }
+ }
+ }
+
+ $ti = 0;
+ $retval = array();
+ for ($i = 0, $cnt = count($con["C_NAME"]); $i < $cnt; $i++) {
+ $c_name_i = $con['C_NAME'][$i];
+ $dtn_i = $con['DTN'][$i];
+ $retval[$ti] = array();
+ $retval[$ti][$c_name_i] = array();
+ if (in_array($dtn_i, $GLOBALS['PMD_URL']["TABLE_NAME"])
+ && in_array($con['STN'][$i], $GLOBALS['PMD_URL']["TABLE_NAME"])
+ ) {
+ $retval[$ti][$c_name_i][$dtn_i] = array();
+ $retval[$ti][$c_name_i][$dtn_i][$con['DCN'][$i]] = array(
+ 0 => $con['STN'][$i],
+ 1 => $con['SCN'][$i]
+ );
+ }
+ $ti++;
+ }
+ return $retval;
+}
+
+/**
+ * Returns UNIQUE and PRIMARY indices
+ *
+ * @return array unique or primary indices
+ */
+function get_pk_or_unique_keys()
+{
+ return get_all_keys(true);
+}
+
+/**
+ * returns all indices
+ *
+ * @param bool $unique_only whether to include only unique ones
+ *
+ * @return array indices
+ */
+function get_all_keys($unique_only = false)
+{
+ include_once './libraries/Index.class.php';
+
+ $keys = array();
+
+ foreach ($GLOBALS['PMD']['TABLE_NAME_SMALL'] as $I => $table) {
+ $schema = $GLOBALS['PMD']['OWNER'][$I];
+ // for now, take into account only the first index segment
+ foreach (PMA_Index::getFromTable($table, $schema) as $index) {
+ if ($unique_only && ! $index->isUnique()) {
+ continue;
+ }
+ $columns = $index->getColumns();
+ foreach ($columns as $column_name => $dummy) {
+ $keys[$schema . '.' .$table . '.' . $column_name] = 1;
+ }
+ }
+ }
+ return $keys;
+}
+
+/**
+ * Return script to create j_tab and h_tab arrays
+ *
+ * @return string
+ */
+function get_script_tabs()
+{
+ $retval = array(
+ 'j_tabs' => array(),
+ 'h_tabs' => array()
+ );
+
+ for ($i = 0, $cnt = count($GLOBALS['PMD']['TABLE_NAME']); $i < $cnt; $i++) {
+ $j = 0;
+ if (PMA_Util::isForeignKeySupported($GLOBALS['PMD']['TABLE_TYPE'][$i])) {
+ $j = 1;
+ }
+ $retval['j_tabs'][$GLOBALS['PMD_URL']['TABLE_NAME'][$i]] = $j;
+ $retval['h_tabs'][$GLOBALS['PMD_URL']['TABLE_NAME'][$i]] = 1;
+ }
+ return $retval;
+}
+
+/**
+ * Returns table position
+ *
+ * @return array table positions and sizes
+ */
+function get_tab_pos()
+{
+ $cfgRelation = PMA_getRelationsParam();
+
+ if (! $cfgRelation['designerwork']) {
+ return null;
+ }
+
+ $query = "
+ SELECT CONCAT_WS('.', `db_name`, `table_name`) AS `name`,
+ `x` AS `X`,
+ `y` AS `Y`,
+ `v` AS `V`,
+ `h` AS `H`
+ FROM " . PMA_Util::backquote($cfgRelation['db'])
+ . "." . PMA_Util::backquote($cfgRelation['designer_coords']);
+ $tab_pos = $GLOBALS['dbi']->fetchResult(
+ $query,
+ 'name',
+ null,
+ $GLOBALS['controllink'],
+ PMA_DatabaseInterface::QUERY_STORE
+ );
+ return count($tab_pos) ? $tab_pos : null;
+}
+
+/**
+ * Prepares XML output for js/pmd/ajax.js to display a message
+ *
+ */
+function PMD_return_upd($b, $ret)
+{
+ // not sure where this was defined...
+ global $K;
+
+ header("Content-Type: text/xml; charset=utf-8");
+ header("Cache-Control: no-cache");
+ die('<root act="relation_upd" return="'.$ret.'" b="'.$b.'" K="'.$K.'"></root>');
+}
+?>
diff --git a/libraries/properties/PropertyItem.class.php b/libraries/properties/PropertyItem.class.php
new file mode 100644
index 0000000000..83a5b26326
--- /dev/null
+++ b/libraries/properties/PropertyItem.class.php
@@ -0,0 +1,49 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * The top-level class of the object-oriented properties system.
+ *
+ * @package PhpMyAdmin
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ * Provides an interface for Property classes
+ *
+ * @package PhpMyAdmin
+ */
+abstract class PropertyItem
+{
+ /**
+ * Returns the property type ( either "Options", or "Plugin" ).
+ *
+ * @return string
+ */
+ public abstract function getPropertyType();
+
+ /**
+ * Returns the property item type of either an instance of
+ * - OptionsPropertyOneItem ( f.e. "bool", "text", "radio", etc ) or
+ * - OptionsPropertyGroup ( "root", "main" or "subgroup" )
+ * - PluginPropertyItem ( "export", "import", "transformations" )
+ *
+ * @return string
+ */
+ public abstract function getItemType();
+
+ /**
+ * Only overwritten in the OptionsPropertyGroup class:
+ * Used to tell whether we can use the current item as a group by calling
+ * the addProperty() or removeProperty() methods, which are not available
+ * for simple OptionsPropertyOneItem subclasses.
+ *
+ * @return string
+ */
+ public function getGroup()
+ {
+ return null;
+ }
+}
+?> \ No newline at end of file
diff --git a/libraries/properties/options/OptionsPropertyGroup.class.php b/libraries/properties/options/OptionsPropertyGroup.class.php
new file mode 100644
index 0000000000..fe2fe40f65
--- /dev/null
+++ b/libraries/properties/options/OptionsPropertyGroup.class.php
@@ -0,0 +1,102 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Superclass for the Property Group classes.
+ *
+ * @package PhpMyAdmin
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/* This class extends the OptionsPropertyItem class */
+require_once 'OptionsPropertyItem.class.php';
+
+/**
+ * Parents group property items and provides methods to manage groups of
+ * properties.
+ *
+ * @todo modify descriptions if needed, when the options are integrated
+ * @package PhpMyAdmin
+ */
+abstract class OptionsPropertyGroup extends OptionsPropertyItem
+{
+ /**
+ * Holds a group of properties (OptionsPropertyItem instances)
+ *
+ * @var array
+ */
+ private $_properties;
+
+ /**
+ * Adds a property to the group of properties
+ *
+ * @param OptionsPropertyItem $property the property instance to be added
+ * to the group
+ *
+ * @return void
+ */
+ public function addProperty($property)
+ {
+ if (! $this->getProperties() == null
+ && in_array($property, $this->getProperties(), true)
+ ) {
+ return;
+ }
+ $this->_properties [] = $property;
+ }
+
+ /**
+ * Removes a property from the group of properties
+ *
+ * @param OptionsPropertyItem $property the property instance to be removed
+ * from the group
+ *
+ * @return void
+ */
+ public function removeProperty($property)
+ {
+ $this->_properties = array_udiff(
+ $this->getProperties(),
+ array($property),
+ function ($a, $b) {
+ return ($a === $b ) ? 0 : 1;
+ }
+ );
+ }
+
+
+ /* ~~~~~~~~~~~~~~~~~~~~ Getters and Setters ~~~~~~~~~~~~~~~~~~~~ */
+
+
+ /**
+ * Gets the instance of the class
+ *
+ * @return array
+ */
+ public function getGroup()
+ {
+ return $this;
+ }
+
+ /**
+ * Gets the group of properties
+ *
+ * @return array
+ */
+ public function getProperties()
+ {
+ return $this->_properties;
+ }
+
+ /**
+ * Gets the number of properties
+ *
+ * @return int
+ */
+ public function getNrOfProperties()
+ {
+ return count($this->_properties);
+ }
+}
+?>
diff --git a/libraries/properties/options/OptionsPropertyItem.class.php b/libraries/properties/options/OptionsPropertyItem.class.php
new file mode 100644
index 0000000000..a1718a2fce
--- /dev/null
+++ b/libraries/properties/options/OptionsPropertyItem.class.php
@@ -0,0 +1,127 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * The top-level class of the "Options" subtree of the object-oriented
+ * properties system (the other subtree is "Plugin").
+ *
+ * @package PhpMyAdmin
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/* This class extends the PropertyItem class */
+require_once 'libraries/properties/PropertyItem.class.php';
+
+/**
+ * Superclass for
+ * - OptionsPropertyOneItem and
+ * - OptionsProperty Group
+ *
+ * @package PhpMyAdmin
+ */
+abstract class OptionsPropertyItem extends PropertyItem
+{
+ /**
+ * Name
+ *
+ * @var string
+ */
+ private $_name;
+
+ /**
+ * Text
+ *
+ * @var string
+ */
+ private $_text;
+
+ /**
+ * What to force
+ *
+ * @var string
+ */
+ private $_force;
+
+
+
+ /* ~~~~~~~~~~~~~~~~~~~~ Getters and Setters ~~~~~~~~~~~~~~~~~~~~ */
+
+
+ /**
+ * Gets the name
+ *
+ * @return string
+ */
+ public function getName()
+ {
+ return $this->_name;
+ }
+
+ /**
+ * Sets the name
+ *
+ * @param string $name name
+ *
+ * @return void
+ */
+ public function setName($name)
+ {
+ $this->_name = $name;
+ }
+
+ /**
+ * Gets the text
+ *
+ * @return string
+ */
+ public function getText()
+ {
+ return $this->_text;
+ }
+
+ /**
+ * Sets the text
+ *
+ * @param string $text text
+ *
+ * @return void
+ */
+ public function setText($text)
+ {
+ $this->_text = $text;
+ }
+
+ /**
+ * Gets the force parameter
+ *
+ * @return string
+ */
+ public function getForce()
+ {
+ return $this->_force;
+ }
+
+ /**
+ * Sets the force paramter
+ *
+ * @param string $force force parameter
+ *
+ * @return void
+ */
+ public function setForce($force)
+ {
+ $this->_force = $force;
+ }
+
+ /**
+ * Returns the property type ( either "options", or "plugin" ).
+ *
+ * @return string
+ */
+ public function getPropertyType()
+ {
+ return "options";
+ }
+}
+?> \ No newline at end of file
diff --git a/libraries/properties/options/OptionsPropertyOneItem.class.php b/libraries/properties/options/OptionsPropertyOneItem.class.php
new file mode 100644
index 0000000000..e19c15cd4f
--- /dev/null
+++ b/libraries/properties/options/OptionsPropertyOneItem.class.php
@@ -0,0 +1,172 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Superclass for the single Property Item classes.
+ *
+ * @package PhpMyAdmin
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/* This class extends the OptionsPropertyItem class */
+require_once 'OptionsPropertyItem.class.php';
+
+/**
+ * Parents only single property items (not groups).
+ * Defines possible options and getters and setters for them.
+ *
+ * @package PhpMyAdmin
+ */
+abstract class OptionsPropertyOneItem extends OptionsPropertyItem
+{
+ /**
+ * Whether to force or not
+ *
+ * @var bool
+ */
+ private $_force;
+
+ /**
+ * Values
+ *
+ * @var array
+ */
+ private $_values;
+
+ /**
+ * Doc
+ *
+ * @var string
+ */
+ private $_doc;
+
+ /**
+ * Length
+ *
+ * @var int
+ */
+ private $_len;
+
+ /**
+ * Size
+ *
+ * @var int
+ */
+ private $_size;
+
+
+ /* ~~~~~~~~~~~~~~~~~~~~ Getters and Setters ~~~~~~~~~~~~~~~~~~~~ */
+
+
+ /**
+ * Gets the force parameter
+ *
+ * @return string
+ */
+ public function getForce()
+ {
+ return $this->_force;
+ }
+
+ /**
+ * Sets the force parameter
+ *
+ * @param bool $force force parameter
+ *
+ * @return void
+ */
+ public function setForce($force)
+ {
+ $this->_force = $force;
+ }
+
+ /**
+ * Gets the values
+ *
+ * @return string
+ */
+ public function getValues()
+ {
+ return $this->_values;
+ }
+
+ /**
+ * Sets the values
+ *
+ * @param array $values values
+ *
+ * @return void
+ */
+ public function setValues($values)
+ {
+ $this->_values = $values;
+ }
+
+ /**
+ * Gets the type of the newline character
+ *
+ * @return string
+ */
+ public function getDoc()
+ {
+ return $this->_doc;
+ }
+
+ /**
+ * Sets the doc
+ *
+ * @param string $doc doc
+ *
+ * @return void
+ */
+ public function setDoc($doc)
+ {
+ $this->_doc = $doc;
+ }
+
+ /**
+ * Gets the length
+ *
+ * @return int
+ */
+ public function getLen()
+ {
+ return $this->_len;
+ }
+
+ /**
+ * Sets the length
+ *
+ * @param int $len length
+ *
+ * @return void
+ */
+ public function setLen($len)
+ {
+ $this->_len = $len;
+ }
+
+ /**
+ * Gets the size
+ *
+ * @return int
+ */
+ public function getSize()
+ {
+ return $this->_size;
+ }
+
+ /**
+ * Sets the size
+ *
+ * @param int $size size
+ *
+ * @return void
+ */
+ public function setSize($size)
+ {
+ $this->_size = $size;
+ }
+}
+?> \ No newline at end of file
diff --git a/libraries/properties/options/groups/OptionsPropertyMainGroup.class.php b/libraries/properties/options/groups/OptionsPropertyMainGroup.class.php
new file mode 100644
index 0000000000..4e69aa7ac8
--- /dev/null
+++ b/libraries/properties/options/groups/OptionsPropertyMainGroup.class.php
@@ -0,0 +1,35 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Holds the OptionsPropertyMainGroup class
+ *
+ * @package PhpMyAdmin
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/* This class extends the OptionsPropertyGroup class */
+require_once 'libraries/properties/options/OptionsPropertyGroup.class.php';
+
+/**
+ * Group property item class of type main
+ *
+ * @package PhpMyAdmin
+ */
+class OptionsPropertyMainGroup extends OptionsPropertyGroup
+{
+ /**
+ * Returns the property item type of either an instance of
+ * - OptionsPropertyOneItem ( f.e. "bool", "text", "radio", etc ) or
+ * - OptionsPropertyGroup ( "root", "main" or "subgroup" )
+ * - PluginPropertyItem ( "export", "import", "transformations" )
+ *
+ * @return string
+ */
+ public function getItemType()
+ {
+ return "main";
+ }
+}
+?> \ No newline at end of file
diff --git a/libraries/properties/options/groups/OptionsPropertyRootGroup.class.php b/libraries/properties/options/groups/OptionsPropertyRootGroup.class.php
new file mode 100644
index 0000000000..a081744fad
--- /dev/null
+++ b/libraries/properties/options/groups/OptionsPropertyRootGroup.class.php
@@ -0,0 +1,35 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Holds the OptionsPropertyRootGroup class
+ *
+ * @package PhpMyAdmin
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/* This class extends the OptionsPropertyGroup class */
+require_once 'libraries/properties/options/OptionsPropertyGroup.class.php';
+
+/**
+ * Group property item class of type root
+ *
+ * @package PhpMyAdmin
+ */
+class OptionsPropertyRootGroup extends OptionsPropertyGroup
+{
+ /**
+ * Returns the property item type of either an instance of
+ * - OptionsPropertyOneItem ( f.e. "bool", "text", "radio", etc ) or
+ * - OptionsPropertyGroup ( "root", "main" or "subgroup" )
+ * - PluginPropertyItem ( "export", "import", "transformations" )
+ *
+ * @return string
+ */
+ public function getItemType()
+ {
+ return "root";
+ }
+}
+?> \ No newline at end of file
diff --git a/libraries/properties/options/groups/OptionsPropertySubgroup.class.php b/libraries/properties/options/groups/OptionsPropertySubgroup.class.php
new file mode 100644
index 0000000000..0eeb524378
--- /dev/null
+++ b/libraries/properties/options/groups/OptionsPropertySubgroup.class.php
@@ -0,0 +1,68 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Holds the OptionsPropertySubgroup class
+ *
+ * @package PhpMyAdmin
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/* This class extends the OptionsPropertyGroup class */
+require_once 'libraries/properties/options/OptionsPropertyGroup.class.php';
+
+/**
+ * Group property item class of type subgroup
+ *
+ * @package PhpMyAdmin
+ */
+class OptionsPropertySubgroup extends OptionsPropertyGroup
+{
+ /**
+ * Subgroup Header
+ *
+ * @var string
+ */
+ private $_subgroupHeader;
+
+
+ /* ~~~~~~~~~~~~~~~~~~~~ Getters and Setters ~~~~~~~~~~~~~~~~~~~~ */
+
+
+ /**
+ * Returns the property item type of either an instance of
+ * - OptionsPropertyOneItem ( f.e. "bool", "text", "radio", etc ) or
+ * - OptionsPropertyGroup ( "root", "main" or "subgroup" )
+ * - PluginPropertyItem ( "export", "import", "transformations" )
+ *
+ * @return string
+ */
+ public function getItemType()
+ {
+ return "subgroup";
+ }
+
+ /**
+ * Gets the subgroup header
+ *
+ * @return string
+ */
+ public function getSubgroupHeader()
+ {
+ return $this->_subgroupHeader;
+ }
+
+ /**
+ * Sets the subgroup header
+ *
+ * @param string $subgroupHeader subgroup header
+ *
+ * @return void
+ */
+ public function setSubgroupHeader($subgroupHeader)
+ {
+ $this->_subgroupHeader = $subgroupHeader;
+ }
+}
+?> \ No newline at end of file
diff --git a/libraries/properties/options/items/BoolPropertyItem.class.php b/libraries/properties/options/items/BoolPropertyItem.class.php
new file mode 100644
index 0000000000..f33067fcd6
--- /dev/null
+++ b/libraries/properties/options/items/BoolPropertyItem.class.php
@@ -0,0 +1,35 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Holds the BoolPropertyItem class
+ *
+ * @package PhpMyAdmin
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/* This class extends the OptionsPropertyOneItem class */
+require_once 'libraries/properties/options/OptionsPropertyOneItem.class.php';
+
+/**
+ * Single property item class of type bool
+ *
+ * @package PhpMyAdmin
+ */
+class BoolPropertyItem extends OptionsPropertyOneItem
+{
+ /**
+ * Returns the property item type of either an instance of
+ * - OptionsPropertyOneItem ( f.e. "bool", "text", "radio", etc ) or
+ * - OptionsPropertyGroup ( "root", "main" or "subgroup" )
+ * - PluginPropertyItem ( "export", "import", "transformations" )
+ *
+ * @return string
+ */
+ public function getItemType()
+ {
+ return "bool";
+ }
+}
+?> \ No newline at end of file
diff --git a/libraries/properties/options/items/DocPropertyItem.class.php b/libraries/properties/options/items/DocPropertyItem.class.php
new file mode 100644
index 0000000000..55aff6147b
--- /dev/null
+++ b/libraries/properties/options/items/DocPropertyItem.class.php
@@ -0,0 +1,35 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Holds the DocPropertyItem class
+ *
+ * @package PhpMyAdmin
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/* This class extends the OptionsPropertyOneItem class */
+require_once 'libraries/properties/options/OptionsPropertyOneItem.class.php';
+
+/**
+ * Single property item class of type doc
+ *
+ * @package PhpMyAdmin
+ */
+class DocPropertyItem extends OptionsPropertyOneItem
+{
+ /**
+ * Returns the property item type of either an instance of
+ * - OptionsPropertyOneItem ( f.e. "bool", "text", "radio", etc ) or
+ * - OptionsPropertyGroup ( "root", "main" or "subgroup" )
+ * - PluginPropertyItem ( "export", "import", "transformations" )
+ *
+ * @return string
+ */
+ public function getItemType()
+ {
+ return "doc";
+ }
+}
+?> \ No newline at end of file
diff --git a/libraries/properties/options/items/HiddenPropertyItem.class.php b/libraries/properties/options/items/HiddenPropertyItem.class.php
new file mode 100644
index 0000000000..53bfe465af
--- /dev/null
+++ b/libraries/properties/options/items/HiddenPropertyItem.class.php
@@ -0,0 +1,35 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Holds the HiddenPropertyItem class
+ *
+ * @package PhpMyAdmin
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/* This class extends the OptionsPropertyOneItem class */
+require_once 'libraries/properties/options/OptionsPropertyOneItem.class.php';
+
+/**
+ * Single property item class of type hidden
+ *
+ * @package PhpMyAdmin
+ */
+class HiddenPropertyItem extends OptionsPropertyOneItem
+{
+ /**
+ * Returns the property item type of either an instance of
+ * - OptionsPropertyOneItem ( f.e. "bool", "text", "radio", etc ) or
+ * - OptionsPropertyGroup ( "root", "main" or "subgroup" )
+ * - PluginPropertyItem ( "export", "import", "transformations" )
+ *
+ * @return string
+ */
+ public function getItemType()
+ {
+ return "hidden";
+ }
+}
+?> \ No newline at end of file
diff --git a/libraries/properties/options/items/MessageOnlyPropertyItem.class.php b/libraries/properties/options/items/MessageOnlyPropertyItem.class.php
new file mode 100644
index 0000000000..98f2e709fc
--- /dev/null
+++ b/libraries/properties/options/items/MessageOnlyPropertyItem.class.php
@@ -0,0 +1,35 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Holds the MessageOnlyPropertyItem class
+ *
+ * @package PhpMyAdmin
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/* This class extends the OptionsPropertyOneItem class */
+require_once 'libraries/properties/options/OptionsPropertyOneItem.class.php';
+
+/**
+ * Single property item class of type messageOnly
+ *
+ * @package PhpMyAdmin
+ */
+class MessageOnlyPropertyItem extends OptionsPropertyOneItem
+{
+ /**
+ * Returns the property item type of either an instance of
+ * - OptionsPropertyOneItem ( f.e. "bool", "text", "radio", etc ) or
+ * - OptionsPropertyGroup ( "root", "main" or "subgroup" )
+ * - PluginPropertyItem ( "export", "import", "transformations" )
+ *
+ * @return string
+ */
+ public function getItemType()
+ {
+ return "messageOnly";
+ }
+}
+?> \ No newline at end of file
diff --git a/libraries/properties/options/items/NumberPropertyItem.class.php b/libraries/properties/options/items/NumberPropertyItem.class.php
new file mode 100644
index 0000000000..7c687c756e
--- /dev/null
+++ b/libraries/properties/options/items/NumberPropertyItem.class.php
@@ -0,0 +1,35 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Holds the TextPropertyItem class
+ *
+ * @package PhpMyAdmin
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/* This class extends the OptionsPropertyOneItem class */
+require_once 'libraries/properties/options/OptionsPropertyOneItem.class.php';
+
+/**
+ * Single property item class of type number
+ *
+ * @package PhpMyAdmin
+ */
+class NumberPropertyItem extends OptionsPropertyOneItem
+{
+ /**
+ * Returns the property item type of either an instance of
+ * - OptionsPropertyOneItem ( f.e. "bool", "text", "radio", etc ) or
+ * - OptionsPropertyGroup ( "root", "main" or "subgroup" )
+ * - PluginPropertyItem ( "export", "import", "transformations" )
+ *
+ * @return string
+ */
+ public function getItemType()
+ {
+ return "number";
+ }
+}
+?>
diff --git a/libraries/properties/options/items/RadioPropertyItem.class.php b/libraries/properties/options/items/RadioPropertyItem.class.php
new file mode 100644
index 0000000000..4d8ed7a21e
--- /dev/null
+++ b/libraries/properties/options/items/RadioPropertyItem.class.php
@@ -0,0 +1,35 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Holds the RadioPropertyItem class
+ *
+ * @package PhpMyAdmin
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/* This class extends the OptionsPropertyOneItem class */
+require_once 'libraries/properties/options/OptionsPropertyOneItem.class.php';
+
+/**
+ * Single property item class of type radio
+ *
+ * @package PhpMyAdmin
+ */
+class RadioPropertyItem extends OptionsPropertyOneItem
+{
+ /**
+ * Returns the property item type of either an instance of
+ * - OptionsPropertyOneItem ( f.e. "bool", "text", "radio", etc ) or
+ * - OptionsPropertyGroup ( "root", "main" or "subgroup" )
+ * - PluginPropertyItem ( "export", "import", "transformations" )
+ *
+ * @return string
+ */
+ public function getItemType()
+ {
+ return "radio";
+ }
+}
+?> \ No newline at end of file
diff --git a/libraries/properties/options/items/SelectPropertyItem.class.php b/libraries/properties/options/items/SelectPropertyItem.class.php
new file mode 100644
index 0000000000..28460c95ab
--- /dev/null
+++ b/libraries/properties/options/items/SelectPropertyItem.class.php
@@ -0,0 +1,35 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Holds the SelectPropertyItem class
+ *
+ * @package PhpMyAdmin
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/* This class extends the OptionsPropertyOneItem class */
+require_once 'libraries/properties/options/OptionsPropertyOneItem.class.php';
+
+/**
+ * Single property item class of type select
+ *
+ * @package PhpMyAdmin
+ */
+class SelectPropertyItem extends OptionsPropertyOneItem
+{
+ /**
+ * Returns the property item type of either an instance of
+ * - OptionsPropertyOneItem ( f.e. "bool", "text", "radio", etc ) or
+ * - OptionsPropertyGroup ( "root", "main" or "subgroup" )
+ * - PluginPropertyItem ( "export", "import", "transformations" )
+ *
+ * @return string
+ */
+ public function getItemType()
+ {
+ return "select";
+ }
+}
+?> \ No newline at end of file
diff --git a/libraries/properties/options/items/TextPropertyItem.class.php b/libraries/properties/options/items/TextPropertyItem.class.php
new file mode 100644
index 0000000000..9339cdf070
--- /dev/null
+++ b/libraries/properties/options/items/TextPropertyItem.class.php
@@ -0,0 +1,35 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Holds the TextPropertyItem class
+ *
+ * @package PhpMyAdmin
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/* This class extends the OptionsPropertyOneItem class */
+require_once 'libraries/properties/options/OptionsPropertyOneItem.class.php';
+
+/**
+ * Single property item class of type text
+ *
+ * @package PhpMyAdmin
+ */
+class TextPropertyItem extends OptionsPropertyOneItem
+{
+ /**
+ * Returns the property item type of either an instance of
+ * - OptionsPropertyOneItem ( f.e. "bool", "text", "radio", etc ) or
+ * - OptionsPropertyGroup ( "root", "main" or "subgroup" )
+ * - PluginPropertyItem ( "export", "import", "transformations" )
+ *
+ * @return string
+ */
+ public function getItemType()
+ {
+ return "text";
+ }
+}
+?> \ No newline at end of file
diff --git a/libraries/properties/plugins/ExportPluginProperties.class.php b/libraries/properties/plugins/ExportPluginProperties.class.php
new file mode 100644
index 0000000000..617089718e
--- /dev/null
+++ b/libraries/properties/plugins/ExportPluginProperties.class.php
@@ -0,0 +1,214 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Properties class for the export plug-in
+ *
+ * @package PhpMyAdmin
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/* This class extends the PluginPropertyItem class */
+require_once 'PluginPropertyItem.class.php';
+
+/**
+ * Defines possible options and getters and setters for them.
+ *
+ * @todo modify descriptions if needed, when the plug-in properties are integrated
+ * @package PhpMyAdmin
+ */
+class ExportPluginProperties extends PluginPropertyItem
+{
+ /**
+ * Text
+ *
+ * @var string
+ */
+ private $_text;
+
+ /**
+ * Extension
+ *
+ * @var string
+ */
+ private $_extension;
+
+ /**
+ * Options
+ *
+ * @var OptionsPropertyRootGroup
+ */
+ private $_options;
+
+ /**
+ * Options text
+ *
+ * @var string
+ */
+ private $_optionsText;
+
+ /**
+ * MIME Type
+ *
+ * @var string
+ */
+ private $_mimeType;
+
+ /**
+ * Whether to force or not
+ *
+ * @var bool
+ */
+ private $_forceFile;
+
+
+ /* ~~~~~~~~~~~~~~~~~~~~ Getters and Setters ~~~~~~~~~~~~~~~~~~~~ */
+
+
+ /**
+ * Returns the property item type of either an instance of
+ * - OptionsPropertyOneItem ( f.e. "bool", "text", "radio", etc ) or
+ * - OptionsPropertyGroup ( "root", "main" or "subgroup" )
+ * - PluginPropertyItem ( "export", "import", "transformations" )
+ *
+ * @return string
+ */
+ public function getItemType()
+ {
+ return "export";
+ }
+
+ /**
+ * Gets the text
+ *
+ * @return string
+ */
+ public function getText()
+ {
+ return $this->_text;
+ }
+
+ /**
+ * Sets the text
+ *
+ * @param string $text text
+ *
+ * @return void
+ */
+ public function setText($text)
+ {
+ $this->_text = $text;
+ }
+
+ /**
+ * Gets the extension
+ *
+ * @return string
+ */
+ public function getExtension()
+ {
+ return $this->_extension;
+ }
+
+ /**
+ * Sets the extension
+ *
+ * @param string $extension extension
+ *
+ * @return void
+ */
+ public function setExtension($extension)
+ {
+ $this->_extension = $extension;
+ }
+
+ /**
+ * Gets the options
+ *
+ * @return OptionsPropertyRootGroup
+ */
+ public function getOptions()
+ {
+ return $this->_options;
+ }
+
+ /**
+ * Sets the options
+ *
+ * @param OptionsPropertyRootGroup $options options
+ *
+ * @return void
+ */
+ public function setOptions($options)
+ {
+ $this->_options = $options;
+ }
+
+ /**
+ * Gets the options text
+ *
+ * @return string
+ */
+ public function getOptionsText()
+ {
+ return $this->_optionsText;
+ }
+
+ /**
+ * Sets the options text
+ *
+ * @param string $optionsText optionsText
+ *
+ * @return void
+ */
+ public function setOptionsText($optionsText)
+ {
+ $this->_optionsText = $optionsText;
+ }
+
+ /**
+ * Gets the MIME type
+ *
+ * @return string
+ */
+ public function getMimeType()
+ {
+ return $this->_mimeType;
+ }
+
+ /**
+ * Sets the MIME type
+ *
+ * @param string $mimeType MIME type
+ *
+ * @return void
+ */
+ public function setMimeType($mimeType)
+ {
+ $this->_mimeType = $mimeType;
+ }
+
+ /**
+ * Gets the force file parameter
+ *
+ * @return bool
+ */
+ public function getForceFile()
+ {
+ return $this->_forceFile;
+ }
+
+ /**
+ * Sets the force file parameter
+ *
+ * @param bool $forceFile the force file parameter
+ *
+ * @return void
+ */
+ public function setForceFile($forceFile)
+ {
+ $this->_forceFile = $forceFile;
+ }
+}
+?> \ No newline at end of file
diff --git a/libraries/properties/plugins/ImportPluginProperties.class.php b/libraries/properties/plugins/ImportPluginProperties.class.php
new file mode 100644
index 0000000000..65c3092cff
--- /dev/null
+++ b/libraries/properties/plugins/ImportPluginProperties.class.php
@@ -0,0 +1,184 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Properties class for the import plug-in
+ *
+ * @package PhpMyAdmin
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/* This class extends the PluginPropertyItem class */
+require_once 'PluginPropertyItem.class.php';
+
+/**
+ * Defines possible options and getters and setters for them.
+ *
+ * @package PhpMyAdmin
+ */
+class ImportPluginProperties extends PluginPropertyItem
+{
+ /**
+ * Text
+ *
+ * @var string
+ */
+ private $_text;
+
+ /**
+ * Extension
+ *
+ * @var string
+ */
+ private $_extension;
+
+ /**
+ * Options
+ *
+ * @var OptionsPropertyRootGroup
+ */
+ private $_options;
+
+ /**
+ * Options text
+ *
+ * @var string
+ */
+ private $_optionsText;
+
+ /**
+ * MIME Type
+ *
+ * @var string
+ */
+ private $_mimeType;
+
+
+ /* ~~~~~~~~~~~~~~~~~~~~ Getters and Setters ~~~~~~~~~~~~~~~~~~~~ */
+
+
+ /**
+ * Returns the property item type of either an instance of
+ * - OptionsPropertyOneItem ( f.e. "bool", "text", "radio", etc ) or
+ * - OptionsPropertyGroup ( "root", "main" or "subgroup" )
+ * - PluginPropertyItem ( "export", "import", "transformations" )
+ *
+ * @return string
+ */
+ public function getItemType()
+ {
+ return "import";
+ }
+
+ /**
+ * Gets the text
+ *
+ * @return string
+ */
+ public function getText()
+ {
+ return $this->_text;
+ }
+
+ /**
+ * Sets the text
+ *
+ * @param string $text text
+ *
+ * @return void
+ */
+ public function setText($text)
+ {
+ $this->_text = $text;
+ }
+
+ /**
+ * Gets the extension
+ *
+ * @return string
+ */
+ public function getExtension()
+ {
+ return $this->_extension;
+ }
+
+ /**
+ * Sets the extension
+ *
+ * @param string $extension extension
+ *
+ * @return void
+ */
+ public function setExtension($extension)
+ {
+ $this->_extension = $extension;
+ }
+
+ /**
+ * Gets the options
+ *
+ * @return OptionsPropertyRootGroup
+ */
+ public function getOptions()
+ {
+ return $this->_options;
+ }
+
+ /**
+ * Sets the options
+ *
+ * @param OptionsPropertyRootGroup $options options
+ *
+ * @return void
+ */
+ public function setOptions($options)
+ {
+ $this->_options = $options;
+ }
+
+ /**
+ * Gets the options text
+ *
+ * @return string
+ */
+ public function getOptionsText()
+ {
+ return $this->_optionsText;
+ }
+
+ /**
+ * Sets the options text
+ *
+ * @param string $optionsText options text
+ *
+ * @return void
+ */
+ public function setOptionsText($optionsText)
+ {
+ $this->_optionsText = $optionsText;
+ }
+
+ /**
+ * Gets the MIME type
+ *
+ * @return string
+ */
+ public function getMimeType()
+ {
+ return $this->_mimeType;
+ }
+
+ /**
+ * Sets the MIME type
+ *
+ * @param string $mimeType MIME type
+ *
+ * @return void
+ */
+ public function setMimeType($mimeType)
+ {
+ $this->_mimeType = $mimeType;
+ }
+}
+?> \ No newline at end of file
diff --git a/libraries/properties/plugins/PluginPropertyItem.class.php b/libraries/properties/plugins/PluginPropertyItem.class.php
new file mode 100644
index 0000000000..af46be2251
--- /dev/null
+++ b/libraries/properties/plugins/PluginPropertyItem.class.php
@@ -0,0 +1,36 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * The top-level class of the "Plugin" subtree of the object-oriented
+ * properties system (the other subtree is "Options").
+ *
+ * @package PhpMyAdmin
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/* This class extends the PropertyItem class */
+require_once 'libraries/properties/PropertyItem.class.php';
+
+/**
+ * Superclass for
+ * - ExportPluginProperties,
+ * - ImportPluginProperties and
+ * - TransformationsPluginProperties
+ *
+ * @package PhpMyAdmin
+ */
+abstract class PluginPropertyItem extends PropertyItem
+{
+ /**
+ * Returns the property type ( either "options", or "plugin" ).
+ *
+ * @return string
+ */
+ public function getPropertyType()
+ {
+ return "plugin";
+ }
+}
+?> \ No newline at end of file
diff --git a/libraries/relation.lib.php b/libraries/relation.lib.php
new file mode 100644
index 0000000000..d49ec14159
--- /dev/null
+++ b/libraries/relation.lib.php
@@ -0,0 +1,1497 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Set of functions used with the relation and pdf feature
+ *
+ * @package PhpMyAdmin
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ * Executes a query as controluser if possible, otherwise as normal user
+ *
+ * @param string $sql the query to execute
+ * @param boolean $show_error whether to display SQL error messages or not
+ * @param int $options query options
+ *
+ * @return integer the result set, or false if no result set
+ *
+ * @access public
+ *
+ */
+function PMA_queryAsControlUser($sql, $show_error = true, $options = 0)
+{
+ // Avoid caching of the number of rows affected; for example, this function
+ // is called for tracking purposes but we want to display the correct number
+ // of rows affected by the original query, not by the query generated for
+ // tracking.
+ $cache_affected_rows = false;
+
+ if ($show_error) {
+ $result = $GLOBALS['dbi']->query(
+ $sql,
+ $GLOBALS['controllink'],
+ $options,
+ $cache_affected_rows
+ );
+ } else {
+ $result = @$GLOBALS['dbi']->tryQuery(
+ $sql,
+ $GLOBALS['controllink'],
+ $options,
+ $cache_affected_rows
+ );
+ } // end if... else...
+
+ if ($result) {
+ return $result;
+ } else {
+ return false;
+ }
+} // end of the "PMA_queryAsControlUser()" function
+
+/**
+ * Returns current relation parameters
+ *
+ * @return array $cfgRelation
+ */
+function PMA_getRelationsParam()
+{
+ // avoid breakage if pmadb got unconfigured after login
+ if (! defined('TESTSUITE') && empty($GLOBALS['cfg']['Server']['pmadb'])) {
+ unset($_SESSION['relation'][$GLOBALS['server']]);
+ }
+ if (empty($_SESSION['relation'][$GLOBALS['server']])) {
+ $_SESSION['relation'][$GLOBALS['server']] = PMA_checkRelationsParam();
+ }
+
+ // just for BC but needs to be before PMA_getRelationsParamDiagnostic()
+ // which uses it
+ $GLOBALS['cfgRelation'] = $_SESSION['relation'][$GLOBALS['server']];
+
+ return $_SESSION['relation'][$GLOBALS['server']];
+}
+
+/**
+ * prints out diagnostic info for pma relation feature
+ *
+ * @param array $cfgRelation Relation configuration
+ *
+ * @return string
+ */
+function PMA_getRelationsParamDiagnostic($cfgRelation)
+{
+ $retval = '';
+
+ $messages['error'] = '<font color="red"><strong>'
+ . __('not OK')
+ . '</strong></font>'
+ . ' [ <a href="%s" target="documentation">'
+ . __('Documentation')
+ . '</a> ]';
+
+ $messages['ok'] = '<font color="green"><strong>'
+ . _pgettext('Correctly working', 'OK')
+ . '</strong></font>';
+
+ $messages['enabled'] = '<font color="green">' . __('Enabled') . '</font>';
+ $messages['disabled'] = '<font color="red">' . __('Disabled') . '</font>';
+
+ if (false === $GLOBALS['cfg']['Server']['pmadb']) {
+ $retval .= 'PMA Database ... '
+ . sprintf($messages['error'], 'pmadb')
+ . '<br />' . "\n"
+ . __('General relation features')
+ . ' <font color="green">' . __('Disabled')
+ . '</font>' . "\n";
+ } else {
+ $retval .= '<table>' . "\n";
+ $retval .= PMA_getDiagMessageForParameter(
+ 'pmadb',
+ $GLOBALS['cfg']['Server']['pmadb'],
+ $messages,
+ 'pmadb'
+ );
+ $retval .= PMA_getDiagMessageForParameter(
+ 'relation',
+ isset($cfgRelation['relation']),
+ $messages,
+ 'relation'
+ );
+ $retval .= PMA_getDiagMessageForFeature(
+ __('General relation features'),
+ 'relwork',
+ $messages
+ );
+ $retval .= PMA_getDiagMessageForParameter(
+ 'table_info',
+ isset($cfgRelation['table_info']),
+ $messages,
+ 'table_info'
+ );
+ $retval .= PMA_getDiagMessageForFeature(
+ __('Display Features'),
+ 'displaywork',
+ $messages
+ );
+ $retval .= PMA_getDiagMessageForParameter(
+ 'table_coords',
+ isset($cfgRelation['table_coords']),
+ $messages,
+ 'table_coords'
+ );
+ $retval .= PMA_getDiagMessageForParameter(
+ 'pdf_pages',
+ isset($cfgRelation['pdf_pages']),
+ $messages,
+ 'pdf_pages'
+ );
+ $retval .= PMA_getDiagMessageForFeature(
+ __('Creation of PDFs'),
+ 'pdfwork',
+ $messages
+ );
+ $retval .= PMA_getDiagMessageForParameter(
+ 'column_info',
+ isset($cfgRelation['column_info']),
+ $messages,
+ 'column_info'
+ );
+ $retval .= PMA_getDiagMessageForFeature(
+ __('Displaying Column Comments'),
+ 'commwork',
+ $messages,
+ false
+ );
+ $retval .= PMA_getDiagMessageForFeature(
+ __('Browser transformation'),
+ 'mimework',
+ $messages
+ );
+ if ($cfgRelation['commwork'] && ! $cfgRelation['mimework']) {
+ $retval .= '<tr><td colspan=2 class="left">';
+ $retval .= __('Please see the documentation on how to update your column_comments table.');
+ $retval .= '</td></tr>';
+ }
+ $retval .= PMA_getDiagMessageForParameter(
+ 'bookmarktable',
+ isset($cfgRelation['bookmark']),
+ $messages,
+ 'bookmark'
+ );
+ $retval .= PMA_getDiagMessageForFeature(
+ __('Bookmarked SQL query'),
+ 'bookmarkwork',
+ $messages
+ );
+ $retval .= PMA_getDiagMessageForParameter(
+ 'history',
+ isset($cfgRelation['history']),
+ $messages,
+ 'history'
+ );
+ $retval .= PMA_getDiagMessageForFeature(
+ __('SQL history'),
+ 'historywork',
+ $messages
+ );
+ $retval .= PMA_getDiagMessageForParameter(
+ 'designer_coords',
+ isset($cfgRelation['designer_coords']),
+ $messages,
+ 'designer_coords'
+ );
+ $retval .= PMA_getDiagMessageForFeature(
+ __('Designer'),
+ 'designerwork',
+ $messages
+ );
+ $retval .= PMA_getDiagMessageForParameter(
+ 'recent',
+ isset($cfgRelation['recent']),
+ $messages,
+ 'recent'
+ );
+ $retval .= PMA_getDiagMessageForFeature(
+ __('Persistent recently used tables'),
+ 'recentwork',
+ $messages
+ );
+ $retval .= PMA_getDiagMessageForParameter(
+ 'table_uiprefs',
+ isset($cfgRelation['table_uiprefs']),
+ $messages,
+ 'table_uiprefs'
+ );
+ $retval .= PMA_getDiagMessageForFeature(
+ __('Persistent tables\' UI preferences'),
+ 'uiprefswork',
+ $messages
+ );
+ $retval .= PMA_getDiagMessageForParameter(
+ 'tracking',
+ isset($cfgRelation['tracking']),
+ $messages,
+ 'tracking'
+ );
+ $retval .= PMA_getDiagMessageForFeature(
+ __('Tracking'),
+ 'trackingwork',
+ $messages
+ );
+ $retval .= PMA_getDiagMessageForParameter(
+ 'userconfig',
+ isset($cfgRelation['userconfig']),
+ $messages,
+ 'userconfig'
+ );
+ $retval .= PMA_getDiagMessageForFeature(
+ __('User preferences'),
+ 'userconfigwork',
+ $messages
+ );
+ $retval .= PMA_getDiagMessageForParameter(
+ 'users',
+ isset($cfgRelation['users']),
+ $messages,
+ 'users'
+ );
+ $retval .= PMA_getDiagMessageForParameter(
+ 'usergroups',
+ isset($cfgRelation['usergroups']),
+ $messages,
+ 'usergroups'
+ );
+ $retval .= PMA_getDiagMessageForFeature(
+ __('Configurable menus'),
+ 'menuswork',
+ $messages
+ );
+ $retval .= PMA_getDiagMessageForParameter(
+ 'navigationhiding',
+ isset($cfgRelation['navigationhiding']),
+ $messages,
+ 'navigationhiding'
+ );
+ $retval .= PMA_getDiagMessageForFeature(
+ __('Hide/show navigation items'),
+ 'navwork',
+ $messages
+ );
+ $retval .= '</table>' . "\n";
+
+ $retval .= '<p>' . __('Quick steps to setup advanced features:') . '</p>';
+ $retval .= '<ul>';
+ $retval .= '<li>';
+ $retval .= __(
+ 'Create the needed tables with the '
+ . '<code>examples/create_tables.sql</code>.'
+ );
+ $retval .= ' ' . PMA_Util::showDocu('setup', 'linked-tables');
+ $retval .= '</li>';
+ $retval .= '<li>';
+ $retval .= __('Create a pma user and give access to these tables.');
+ $retval .= ' ' . PMA_Util::showDocu('config', 'cfg_Servers_controluser');
+ $retval .= '</li>';
+ $retval .= '<li>';
+ $retval .= __(
+ 'Enable advanced features in configuration file '
+ . '(<code>config.inc.php</code>), for example by '
+ . 'starting from <code>config.sample.inc.php</code>.'
+ );
+ $retval .= ' ' . PMA_Util::showDocu('setup', 'quick-install');
+ $retval .= '</li>';
+ $retval .= '<li>';
+ $retval .= __(
+ 'Re-login to phpMyAdmin to load the updated configuration file.'
+ );
+ $retval .= '</li>';
+ $retval .= '</ul>';
+ }
+
+ return $retval;
+}
+
+/**
+ * prints out one diagnostic message for a feature
+ *
+ * @param string $feature_name feature name in a message string
+ * @param string $relation_parameter the $GLOBALS['cfgRelation'] parameter to check
+ * @param array $messages utility messages
+ * @param boolean $skip_line whether to skip a line after the message
+ *
+ * @return string
+ */
+function PMA_getDiagMessageForFeature($feature_name,
+ $relation_parameter, $messages, $skip_line = true
+) {
+ $retval = ' <tr><td colspan=2 class="right">' . $feature_name . ': ';
+ if ($GLOBALS['cfgRelation'][$relation_parameter]) {
+ $retval .= $messages['enabled'];
+ } else {
+ $retval .= $messages['disabled'];
+ }
+ $retval .= '</td></tr>';
+ if ($skip_line) {
+ $retval .= '<tr><td>&nbsp;</td></tr>';
+ }
+ return $retval;
+}
+
+/**
+ * prints out one diagnostic message for a configuration parameter
+ *
+ * @param string $parameter config parameter name to display
+ * @param boolean $relation_parameter_set whether this parameter is set
+ * @param array $messages utility messages
+ * @param string $doc_anchor anchor in documentation
+ *
+ * @return string
+ */
+function PMA_getDiagMessageForParameter($parameter,
+ $relation_parameter_set, $messages, $doc_anchor
+) {
+ $retval = '<tr><th class="left">';
+ $retval .= '$cfg[\'Servers\'][$i][\'' . $parameter . '\'] ... ';
+ $retval .= '</th><td class="right">';
+ if ($relation_parameter_set) {
+ $retval .= $messages['ok'];
+ } else {
+ $retval .= sprintf(
+ $messages['error'],
+ PMA_Util::getDocuLink('config', 'cfg_Servers_' . $doc_anchor)
+ );
+ }
+ $retval .= '</td></tr>' . "\n";
+ return $retval;
+}
+
+
+/**
+ * Defines the relation parameters for the current user
+ * just a copy of the functions used for relations ;-)
+ * but added some stuff to check what will work
+ *
+ * @access protected
+ * @return array the relation parameters for the current user
+ */
+function PMA_checkRelationsParam()
+{
+ $cfgRelation = array();
+ $cfgRelation['relwork'] = false;
+ $cfgRelation['displaywork'] = false;
+ $cfgRelation['bookmarkwork'] = false;
+ $cfgRelation['pdfwork'] = false;
+ $cfgRelation['commwork'] = false;
+ $cfgRelation['mimework'] = false;
+ $cfgRelation['historywork'] = false;
+ $cfgRelation['recentwork'] = false;
+ $cfgRelation['uiprefswork'] = false;
+ $cfgRelation['trackingwork'] = false;
+ $cfgRelation['designerwork'] = false;
+ $cfgRelation['userconfigwork'] = false;
+ $cfgRelation['menuswork'] = false;
+ $cfgRelation['navwork'] = false;
+ $cfgRelation['allworks'] = false;
+ $cfgRelation['user'] = null;
+ $cfgRelation['db'] = null;
+
+ if ($GLOBALS['server'] == 0
+ || empty($GLOBALS['cfg']['Server']['pmadb'])
+ || ! $GLOBALS['dbi']->selectDb($GLOBALS['cfg']['Server']['pmadb'], $GLOBALS['controllink'])
+ ) {
+ // No server selected -> no bookmark table
+ // we return the array with the falses in it,
+ // to avoid some 'Unitialized string offset' errors later
+ $GLOBALS['cfg']['Server']['pmadb'] = false;
+ return $cfgRelation;
+ }
+
+
+ $cfgRelation['user'] = $GLOBALS['cfg']['Server']['user'];
+ $cfgRelation['db'] = $GLOBALS['cfg']['Server']['pmadb'];
+
+ // Now I just check if all tables that i need are present so I can for
+ // example enable relations but not pdf...
+ // I was thinking of checking if they have all required columns but I
+ // fear it might be too slow
+
+ $tab_query = 'SHOW TABLES FROM '
+ . PMA_Util::backquote(
+ $GLOBALS['cfg']['Server']['pmadb']
+ );
+ $tab_rs = PMA_queryAsControlUser(
+ $tab_query, false, PMA_DatabaseInterface::QUERY_STORE
+ );
+
+ if (! $tab_rs) {
+ // query failed ... ?
+ //$GLOBALS['cfg']['Server']['pmadb'] = false;
+ return $cfgRelation;
+ }
+
+ while ($curr_table = @$GLOBALS['dbi']->fetchRow($tab_rs)) {
+ if ($curr_table[0] == $GLOBALS['cfg']['Server']['bookmarktable']) {
+ $cfgRelation['bookmark'] = $curr_table[0];
+ } elseif ($curr_table[0] == $GLOBALS['cfg']['Server']['relation']) {
+ $cfgRelation['relation'] = $curr_table[0];
+ } elseif ($curr_table[0] == $GLOBALS['cfg']['Server']['table_info']) {
+ $cfgRelation['table_info'] = $curr_table[0];
+ } elseif ($curr_table[0] == $GLOBALS['cfg']['Server']['table_coords']) {
+ $cfgRelation['table_coords'] = $curr_table[0];
+ } elseif ($curr_table[0] == $GLOBALS['cfg']['Server']['designer_coords']) {
+ $cfgRelation['designer_coords'] = $curr_table[0];
+ } elseif ($curr_table[0] == $GLOBALS['cfg']['Server']['column_info']) {
+ $cfgRelation['column_info'] = $curr_table[0];
+ } elseif ($curr_table[0] == $GLOBALS['cfg']['Server']['pdf_pages']) {
+ $cfgRelation['pdf_pages'] = $curr_table[0];
+ } elseif ($curr_table[0] == $GLOBALS['cfg']['Server']['history']) {
+ $cfgRelation['history'] = $curr_table[0];
+ } elseif ($curr_table[0] == $GLOBALS['cfg']['Server']['recent']) {
+ $cfgRelation['recent'] = $curr_table[0];
+ } elseif ($curr_table[0] == $GLOBALS['cfg']['Server']['table_uiprefs']) {
+ $cfgRelation['table_uiprefs'] = $curr_table[0];
+ } elseif ($curr_table[0] == $GLOBALS['cfg']['Server']['tracking']) {
+ $cfgRelation['tracking'] = $curr_table[0];
+ } elseif ($curr_table[0] == $GLOBALS['cfg']['Server']['userconfig']) {
+ $cfgRelation['userconfig'] = $curr_table[0];
+ } elseif ($curr_table[0] == $GLOBALS['cfg']['Server']['users']) {
+ $cfgRelation['users'] = $curr_table[0];
+ } elseif ($curr_table[0] == $GLOBALS['cfg']['Server']['usergroups']) {
+ $cfgRelation['usergroups'] = $curr_table[0];
+ } elseif ($curr_table[0] == $GLOBALS['cfg']['Server']['navigationhiding']) {
+ $cfgRelation['navigationhiding'] = $curr_table[0];
+ }
+ } // end while
+ $GLOBALS['dbi']->freeResult($tab_rs);
+
+ if (isset($cfgRelation['relation'])) {
+ $cfgRelation['relwork'] = true;
+ if (isset($cfgRelation['table_info'])) {
+ $cfgRelation['displaywork'] = true;
+ }
+ }
+
+ if (isset($cfgRelation['table_coords']) && isset($cfgRelation['pdf_pages'])) {
+ $cfgRelation['pdfwork'] = true;
+ }
+
+ if (isset($cfgRelation['column_info'])) {
+ $cfgRelation['commwork'] = true;
+ $cfgRelation['mimework'] = true;
+ }
+
+ if (isset($cfgRelation['history'])) {
+ $cfgRelation['historywork'] = true;
+ }
+
+ if (isset($cfgRelation['recent'])) {
+ $cfgRelation['recentwork'] = true;
+ }
+
+ if (isset($cfgRelation['table_uiprefs'])) {
+ $cfgRelation['uiprefswork'] = true;
+ }
+
+ if (isset($cfgRelation['tracking'])) {
+ $cfgRelation['trackingwork'] = true;
+ }
+
+ if (isset($cfgRelation['userconfig'])) {
+ $cfgRelation['userconfigwork'] = true;
+ }
+
+ // we do not absolutely need that the internal relations or the PDF
+ // schema feature be activated
+ if (isset($cfgRelation['designer_coords'])) {
+ $cfgRelation['designerwork'] = true;
+ }
+
+ if (isset($cfgRelation['bookmark'])) {
+ $cfgRelation['bookmarkwork'] = true;
+ }
+
+ if (isset($cfgRelation['users']) && isset($cfgRelation['usergroups'])) {
+ $cfgRelation['menuswork'] = true;
+ }
+
+ if (isset($cfgRelation['navigationhiding'])) {
+ $cfgRelation['navwork'] = true;
+ }
+
+ if ($cfgRelation['relwork'] && $cfgRelation['displaywork']
+ && $cfgRelation['pdfwork'] && $cfgRelation['commwork']
+ && $cfgRelation['mimework'] && $cfgRelation['historywork']
+ && $cfgRelation['recentwork'] && $cfgRelation['uiprefswork']
+ && $cfgRelation['trackingwork'] && $cfgRelation['userconfigwork']
+ && $cfgRelation['bookmarkwork'] && $cfgRelation['designerwork']
+ && $cfgRelation['menuswork'] && $cfgRelation['navwork']
+ ) {
+ $cfgRelation['allworks'] = true;
+ }
+
+ return $cfgRelation;
+} // end of the 'PMA_getRelationsParam()' function
+
+/**
+ * Gets all Relations to foreign tables for a given table or
+ * optionally a given column in a table
+ *
+ * @param string $db the name of the db to check for
+ * @param string $table the name of the table to check for
+ * @param string $column the name of the column to check for
+ * @param string $source the source for foreign key information
+ *
+ * @return array db,table,column
+ *
+ * @access public
+ */
+function PMA_getForeigners($db, $table, $column = '', $source = 'both')
+{
+ $cfgRelation = PMA_getRelationsParam();
+ $foreign = array();
+
+ if ($cfgRelation['relwork'] && ($source == 'both' || $source == 'internal')) {
+ $rel_query = '
+ SELECT `master_field`,
+ `foreign_db`,
+ `foreign_table`,
+ `foreign_field`
+ FROM ' . PMA_Util::backquote($cfgRelation['db'])
+ . '.' . PMA_Util::backquote($cfgRelation['relation']) . '
+ WHERE `master_db` = \'' . PMA_Util::sqlAddSlashes($db) . '\'
+ AND `master_table` = \'' . PMA_Util::sqlAddSlashes($table) . '\' ';
+ if (strlen($column)) {
+ $rel_query .= ' AND `master_field` = '
+ . '\'' . PMA_Util::sqlAddSlashes($column) . '\'';
+ }
+ $foreign = $GLOBALS['dbi']->fetchResult(
+ $rel_query, 'master_field', null, $GLOBALS['controllink']
+ );
+ }
+
+ if (($source == 'both' || $source == 'foreign') && strlen($table)) {
+
+ $show_create_table_query = 'SHOW CREATE TABLE '
+ . PMA_Util::backquote($db) . '.' . PMA_Util::backquote($table);
+ $show_create_table = $GLOBALS['dbi']->fetchValue($show_create_table_query, 0, 1);
+ $analyzed_sql = PMA_SQP_analyze(PMA_SQP_parse($show_create_table));
+
+ foreach ($analyzed_sql[0]['foreign_keys'] as $one_key) {
+ // The analyzer may return more than one column name in the
+ // index list or the ref_index_list; if this happens,
+ // the current logic just discards the whole index; having
+ // more than one index field is currently unsupported (see FAQ 3.6)
+ if (count($one_key['index_list']) == 1) {
+ foreach ($one_key['index_list'] as $i => $field) {
+ // If a foreign key is defined in the 'internal' source (pmadb)
+ // and as a native foreign key, we won't get it twice
+ // if $source='both' because we use $field as key
+
+ // The parser looks for a CONSTRAINT clause just before
+ // the FOREIGN KEY clause. It finds it (as output from
+ // SHOW CREATE TABLE) in MySQL 4.0.13, but not in older
+ // versions like 3.23.58.
+ // In those cases, the FOREIGN KEY parsing will put numbers
+ // like -1, 0, 1... instead of the constraint number.
+
+ if (isset($one_key['constraint'])) {
+ $foreign[$field]['constraint'] = $one_key['constraint'];
+ }
+
+ if (isset($one_key['ref_db_name'])) {
+ $foreign[$field]['foreign_db'] = $one_key['ref_db_name'];
+ } else {
+ $foreign[$field]['foreign_db'] = $db;
+ }
+ $foreign[$field]['foreign_table'] = $one_key['ref_table_name'];
+ $foreign[$field]['foreign_field'] = $one_key['ref_index_list'][$i];
+ if (isset($one_key['on_delete'])) {
+ $foreign[$field]['on_delete'] = $one_key['on_delete'];
+ }
+ if (isset($one_key['on_update'])) {
+ $foreign[$field]['on_update'] = $one_key['on_update'];
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Emulating relations for some information_schema and data_dictionary tables
+ */
+ $is_information_schema = strtolower($db) == 'information_schema';
+ $is_data_dictionary = PMA_DRIZZLE && strtolower($db) == 'data_dictionary';
+ if (($is_information_schema || $is_data_dictionary)
+ && ($source == 'internal' || $source == 'both')
+ ) {
+ if ($is_information_schema) {
+ $relations_key = 'information_schema_relations';
+ include_once './libraries/information_schema_relations.lib.php';
+ } else {
+ $relations_key = 'data_dictionary_relations';
+ include_once './libraries/data_dictionary_relations.lib.php';
+ }
+ if (isset($GLOBALS[$relations_key][$table])) {
+ foreach ($GLOBALS[$relations_key][$table] as $field => $relations) {
+ if ((! strlen($column) || $column == $field)
+ && (! isset($foreign[$field]) || ! strlen($foreign[$field]))
+ ) {
+ $foreign[$field] = $relations;
+ }
+ }
+ }
+ }
+
+ return $foreign;
+} // end of the 'PMA_getForeigners()' function
+
+/**
+ * Gets the display field of a table
+ *
+ * @param string $db the name of the db to check for
+ * @param string $table the name of the table to check for
+ *
+ * @return string field name
+ *
+ * @access public
+ */
+function PMA_getDisplayField($db, $table)
+{
+ $cfgRelation = PMA_getRelationsParam();
+
+ /**
+ * Try to fetch the display field from DB.
+ */
+ if ($cfgRelation['displaywork']) {
+ $disp_query = '
+ SELECT `display_field`
+ FROM ' . PMA_Util::backquote($cfgRelation['db'])
+ . '.' . PMA_Util::backquote($cfgRelation['table_info']) . '
+ WHERE `db_name` = \'' . PMA_Util::sqlAddSlashes($db) . '\'
+ AND `table_name` = \'' . PMA_Util::sqlAddSlashes($table) . '\'';
+
+ $row = $GLOBALS['dbi']->fetchSingleRow(
+ $disp_query, 'ASSOC', $GLOBALS['controllink']
+ );
+ if (isset($row['display_field'])) {
+ return $row['display_field'];
+ }
+ }
+
+ /**
+ * Emulating the display field for some information_schema tables.
+ */
+ if ($db == 'information_schema') {
+ switch ($table) {
+ case 'CHARACTER_SETS':
+ return 'DESCRIPTION';
+ case 'TABLES':
+ return 'TABLE_COMMENT';
+ }
+ }
+
+ /**
+ * No Luck...
+ */
+ return false;
+
+} // end of the 'PMA_getDisplayField()' function
+
+/**
+ * Gets the comments for all columns of a table or the db itself
+ *
+ * @param string $db the name of the db to check for
+ * @param string $table the name of the table to check for
+ *
+ * @return array [column_name] = comment
+ *
+ * @access public
+ */
+function PMA_getComments($db, $table = '')
+{
+ $comments = array();
+
+ if ($table != '') {
+ // MySQL native column comments
+ $columns = $GLOBALS['dbi']->getColumns($db, $table, null, true);
+ if ($columns) {
+ foreach ($columns as $column) {
+ if (! empty($column['Comment'])) {
+ $comments[$column['Field']] = $column['Comment'];
+ }
+ }
+ }
+ } else {
+ $comments[] = PMA_getDbComment($db);
+ }
+
+ return $comments;
+} // end of the 'PMA_getComments()' function
+
+/**
+ * Gets the comment for a db
+ *
+ * @param string $db the name of the db to check for
+ *
+ * @return string comment
+ *
+ * @access public
+ */
+function PMA_getDbComment($db)
+{
+ $cfgRelation = PMA_getRelationsParam();
+ $comment = '';
+
+ if ($cfgRelation['commwork']) {
+ // pmadb internal db comment
+ $com_qry = "
+ SELECT `comment`
+ FROM " . PMA_Util::backquote($cfgRelation['db'])
+ . "." . PMA_Util::backquote($cfgRelation['column_info']) . "
+ WHERE db_name = '" . PMA_Util::sqlAddSlashes($db) . "'
+ AND table_name = ''
+ AND column_name = '(db_comment)'";
+ $com_rs = PMA_queryAsControlUser(
+ $com_qry, true, PMA_DatabaseInterface::QUERY_STORE
+ );
+
+ if ($com_rs && $GLOBALS['dbi']->numRows($com_rs) > 0) {
+ $row = $GLOBALS['dbi']->fetchAssoc($com_rs);
+ $comment = $row['comment'];
+ }
+ $GLOBALS['dbi']->freeResult($com_rs);
+ }
+
+ return $comment;
+} // end of the 'PMA_getDbComment()' function
+
+/**
+ * Gets the comment for a db
+ *
+ * @access public
+ *
+ * @return string comment
+ */
+function PMA_getDbComments()
+{
+ $cfgRelation = PMA_getRelationsParam();
+ $comments = array();
+
+ if ($cfgRelation['commwork']) {
+ // pmadb internal db comment
+ $com_qry = "
+ SELECT `db_name`, `comment`
+ FROM " . PMA_Util::backquote($cfgRelation['db'])
+ . "." . PMA_Util::backquote($cfgRelation['column_info']) . "
+ WHERE `column_name` = '(db_comment)'";
+ $com_rs = PMA_queryAsControlUser(
+ $com_qry, true, PMA_DatabaseInterface::QUERY_STORE
+ );
+
+ if ($com_rs && $GLOBALS['dbi']->numRows($com_rs) > 0) {
+ while ($row = $GLOBALS['dbi']->fetchAssoc($com_rs)) {
+ $comments[$row['db_name']] = $row['comment'];
+ }
+ }
+ $GLOBALS['dbi']->freeResult($com_rs);
+ }
+
+ return $comments;
+} // end of the 'PMA_getDbComments()' function
+
+/**
+ * Set a database comment to a certain value.
+ *
+ * @param string $db the name of the db
+ * @param string $comment the value of the column
+ *
+ * @return boolean true, if comment-query was made.
+ *
+ * @access public
+ */
+function PMA_setDbComment($db, $comment = '')
+{
+ $cfgRelation = PMA_getRelationsParam();
+
+ if (! $cfgRelation['commwork']) {
+ return false;
+ }
+
+ if (strlen($comment)) {
+ $upd_query = 'INSERT INTO '
+ . PMA_Util::backquote($cfgRelation['db']) . '.'
+ . PMA_Util::backquote($cfgRelation['column_info'])
+ . ' (`db_name`, `table_name`, `column_name`, `comment`)'
+ . ' VALUES (\''
+ . PMA_Util::sqlAddSlashes($db)
+ . "', '', '(db_comment)', '"
+ . PMA_Util::sqlAddSlashes($comment)
+ . "') "
+ . ' ON DUPLICATE KEY UPDATE '
+ . "`comment` = '" . PMA_Util::sqlAddSlashes($comment) . "'";
+ } else {
+ $upd_query = 'DELETE FROM '
+ . PMA_Util::backquote($cfgRelation['db']) . '.'
+ . PMA_Util::backquote($cfgRelation['column_info'])
+ . ' WHERE `db_name` = \'' . PMA_Util::sqlAddSlashes($db) . '\'
+ AND `table_name` = \'\'
+ AND `column_name` = \'(db_comment)\'';
+ }
+
+ if (isset($upd_query)) {
+ return PMA_queryAsControlUser($upd_query);
+ }
+
+ return false;
+} // end of 'PMA_setDbComment()' function
+
+/**
+ * Set a SQL history entry
+ *
+ * @param string $db the name of the db
+ * @param string $table the name of the table
+ * @param string $username the username
+ * @param string $sqlquery the sql query
+ *
+ * @return void
+ *
+ * @access public
+ */
+function PMA_setHistory($db, $table, $username, $sqlquery)
+{
+ // Prevent to run this automatically on Footer class destroying in testsuite
+ if (defined('TESTSUITE')
+ || strlen($sqlquery) > $GLOBALS['cfg']['MaxCharactersInDisplayedSQL']
+ ) {
+ return;
+ }
+
+ $cfgRelation = PMA_getRelationsParam();
+
+ if (! isset($_SESSION['sql_history'])) {
+ $_SESSION['sql_history'] = array();
+ }
+
+ $key = md5($sqlquery . $db . $table);
+
+ if (isset($_SESSION['sql_history'][$key])) {
+ unset($_SESSION['sql_history'][$key]);
+ }
+
+ $_SESSION['sql_history'][$key] = array(
+ 'db' => $db,
+ 'table' => $table,
+ 'sqlquery' => $sqlquery,
+ );
+
+ if (count($_SESSION['sql_history']) > $GLOBALS['cfg']['QueryHistoryMax']) {
+ // history should not exceed a maximum count
+ array_shift($_SESSION['sql_history']);
+ }
+
+ if (! $cfgRelation['historywork'] || ! $GLOBALS['cfg']['QueryHistoryDB']) {
+ return;
+ }
+
+ PMA_queryAsControlUser(
+ 'INSERT INTO '
+ . PMA_Util::backquote($cfgRelation['db']) . '.'
+ . PMA_Util::backquote($cfgRelation['history']) . '
+ (`username`,
+ `db`,
+ `table`,
+ `timevalue`,
+ `sqlquery`)
+ VALUES
+ (\'' . PMA_Util::sqlAddSlashes($username) . '\',
+ \'' . PMA_Util::sqlAddSlashes($db) . '\',
+ \'' . PMA_Util::sqlAddSlashes($table) . '\',
+ NOW(),
+ \'' . PMA_Util::sqlAddSlashes($sqlquery) . '\')'
+ );
+} // end of 'PMA_setHistory()' function
+
+/**
+ * Gets a SQL history entry
+ *
+ * @param string $username the username
+ *
+ * @return array list of history items
+ *
+ * @access public
+ */
+function PMA_getHistory($username)
+{
+ $cfgRelation = PMA_getRelationsParam();
+
+ /**
+ * if db-based history is disabled but there exists a session-based
+ * history, use it
+ */
+ if (! $GLOBALS['cfg']['QueryHistoryDB'] && isset($_SESSION['sql_history'])) {
+ return array_reverse($_SESSION['sql_history']);
+ }
+
+ if (! $cfgRelation['historywork']) {
+ return false;
+ }
+
+ $hist_query = '
+ SELECT `db`,
+ `table`,
+ `sqlquery`
+ FROM ' . PMA_Util::backquote($cfgRelation['db'])
+ . '.' . PMA_Util::backquote($cfgRelation['history']) . '
+ WHERE `username` = \'' . PMA_Util::sqlAddSlashes($username) . '\'
+ ORDER BY `id` DESC';
+
+ return $GLOBALS['dbi']->fetchResult($hist_query, null, null, $GLOBALS['controllink']);
+} // end of 'PMA_getHistory()' function
+
+/**
+ * purges SQL history
+ *
+ * deletes entries that exceeds $cfg['QueryHistoryMax'], oldest first, for the
+ * given user
+ *
+ * @param string $username the username
+ *
+ * @return void
+ *
+ * @access public
+ */
+function PMA_purgeHistory($username)
+{
+ $cfgRelation = PMA_getRelationsParam();
+ if (! $GLOBALS['cfg']['QueryHistoryDB'] || ! $cfgRelation['historywork']) {
+ return;
+ }
+
+ if (! $cfgRelation['historywork']) {
+ return;
+ }
+
+ $search_query = '
+ SELECT `timevalue`
+ FROM ' . PMA_Util::backquote($cfgRelation['db'])
+ . '.' . PMA_Util::backquote($cfgRelation['history']) . '
+ WHERE `username` = \'' . PMA_Util::sqlAddSlashes($username) . '\'
+ ORDER BY `timevalue` DESC
+ LIMIT ' . $GLOBALS['cfg']['QueryHistoryMax'] . ', 1';
+
+ if ($max_time = $GLOBALS['dbi']->fetchValue($search_query, 0, 0, $GLOBALS['controllink'])) {
+ PMA_queryAsControlUser(
+ 'DELETE FROM '
+ . PMA_Util::backquote($cfgRelation['db']) . '.'
+ . PMA_Util::backquote($cfgRelation['history']) . '
+ WHERE `username` = \'' . PMA_Util::sqlAddSlashes($username) . '\'
+ AND `timevalue` <= \'' . $max_time . '\''
+ );
+ }
+} // end of 'PMA_purgeHistory()' function
+
+/**
+ * Prepares the dropdown for one mode
+ *
+ * @param array $foreign the keys and values for foreigns
+ * @param string $data the current data of the dropdown
+ * @param string $mode the needed mode
+ *
+ * @return array the <option value=""><option>s
+ *
+ * @access protected
+ */
+function PMA_buildForeignDropdown($foreign, $data, $mode)
+{
+ $reloptions = array();
+
+ // id-only is a special mode used when no foreign display column
+ // is available
+ if ($mode == 'id-content' || $mode == 'id-only') {
+ // sort for id-content
+ if ($GLOBALS['cfg']['NaturalOrder']) {
+ uksort($foreign, 'strnatcasecmp');
+ } else {
+ ksort($foreign);
+ }
+ } elseif ($mode == 'content-id') {
+ // sort for content-id
+ if ($GLOBALS['cfg']['NaturalOrder']) {
+ natcasesort($foreign);
+ } else {
+ asort($foreign);
+ }
+ }
+
+ foreach ($foreign as $key => $value) {
+ if ($GLOBALS['PMA_String']->strlen($value) <= $GLOBALS['cfg']['LimitChars']) {
+ $vtitle = '';
+ $value = htmlspecialchars($value);
+ } else {
+ $vtitle = htmlspecialchars($value);
+ $value = htmlspecialchars(
+ substr($value, 0, $GLOBALS['cfg']['LimitChars']) . '...'
+ );
+ }
+
+ $reloption = '<option value="' . htmlspecialchars($key) . '"';
+ if ($vtitle != '') {
+ $reloption .= ' title="' . $vtitle . '"';
+ }
+
+ if ((string) $key == (string) $data) {
+ $reloption .= ' selected="selected"';
+ }
+
+ if ($mode == 'content-id') {
+ $reloptions[] = $reloption . '>'
+ . $value . '&nbsp;-&nbsp;' . htmlspecialchars($key) . '</option>';
+ } elseif ($mode == 'id-content') {
+ $reloptions[] = $reloption . '>'
+ . htmlspecialchars($key) . '&nbsp;-&nbsp;' . $value . '</option>';
+ } elseif ($mode == 'id-only') {
+ $reloptions[] = $reloption . '>'
+ . htmlspecialchars($key) . '</option>';
+ }
+ } // end foreach
+
+ return $reloptions;
+} // end of 'PMA_buildForeignDropdown' function
+
+/**
+ * Outputs dropdown with values of foreign fields
+ *
+ * @param array $disp_row array of the displayed row
+ * @param string $foreign_field the foreign field
+ * @param string $foreign_display the foreign field to display
+ * @param string $data the current data of the dropdown (field in row)
+ * @param int $max maximum number of items in the dropdown
+ *
+ * @return string the <option value=""><option>s
+ *
+ * @access public
+ */
+function PMA_foreignDropdown($disp_row, $foreign_field, $foreign_display, $data,
+ $max = null
+) {
+ if (null === $max) {
+ $max = $GLOBALS['cfg']['ForeignKeyMaxLimit'];
+ }
+
+ $foreign = array();
+
+ // collect the data
+ foreach ($disp_row as $relrow) {
+ $key = $relrow[$foreign_field];
+
+ // if the display field has been defined for this foreign table
+ if ($foreign_display) {
+ $value = $relrow[$foreign_display];
+ } else {
+ $value = '';
+ } // end if ($foreign_display)
+
+ $foreign[$key] = $value;
+ } // end foreach
+
+ // put the dropdown sections in correct order
+ $top = array();
+ $bottom = array();
+ if ($foreign_display) {
+ if (PMA_isValid($GLOBALS['cfg']['ForeignKeyDropdownOrder'], 'array')) {
+ if (PMA_isValid($GLOBALS['cfg']['ForeignKeyDropdownOrder'][0])) {
+ $top = PMA_buildForeignDropdown(
+ $foreign,
+ $data,
+ $GLOBALS['cfg']['ForeignKeyDropdownOrder'][0]
+ );
+ }
+ if (PMA_isValid($GLOBALS['cfg']['ForeignKeyDropdownOrder'][1])) {
+ $bottom = PMA_buildForeignDropdown(
+ $foreign,
+ $data,
+ $GLOBALS['cfg']['ForeignKeyDropdownOrder'][1]
+ );
+ }
+ } else {
+ $top = PMA_buildForeignDropdown($foreign, $data, 'id-content');
+ $bottom = PMA_buildForeignDropdown($foreign, $data, 'content-id');
+ }
+ } else {
+ $top = PMA_buildForeignDropdown($foreign, $data, 'id-only');
+ }
+
+ // beginning of dropdown
+ $ret = '<option value="">&nbsp;</option>';
+ $top_count = count($top);
+ if ($max == -1 || $top_count < $max) {
+ $ret .= implode('', $top);
+ if ($foreign_display && $top_count > 0) {
+ // this empty option is to visually mark the beginning of the
+ // second series of values (bottom)
+ $ret .= '<option value="">&nbsp;</option>';
+ }
+ }
+ if ($foreign_display) {
+ $ret .= implode('', $bottom);
+ }
+
+ return $ret;
+} // end of 'PMA_foreignDropdown()' function
+
+/**
+ * Gets foreign keys in preparation for a drop-down selector
+ *
+ * @param array $foreigners array of the foreign keys
+ * @param string $field the foreign field name
+ * @param bool $override_total whether to override the total
+ * @param string $foreign_filter a possible filter
+ * @param string $foreign_limit a possible LIMIT clause
+ *
+ * @return array data about the foreign keys
+ *
+ * @access public
+ */
+
+function PMA_getForeignData(
+ $foreigners, $field, $override_total, $foreign_filter, $foreign_limit
+) {
+ // we always show the foreign field in the drop-down; if a display
+ // field is defined, we show it besides the foreign field
+ $foreign_link = false;
+ if ($foreigners && isset($foreigners[$field])) {
+ $foreigner = $foreigners[$field];
+ $foreign_db = $foreigner['foreign_db'];
+ $foreign_table = $foreigner['foreign_table'];
+ $foreign_field = $foreigner['foreign_field'];
+
+ // Count number of rows in the foreign table. Currently we do
+ // not use a drop-down if more than ForeignKeyMaxLimit rows in the
+ // foreign table,
+ // for speed reasons and because we need a better interface for this.
+ //
+ // We could also do the SELECT anyway, with a LIMIT, and ensure that
+ // the current value of the field is one of the choices.
+
+ $the_total = PMA_Table::countRecords($foreign_db, $foreign_table, true);
+
+ if ($override_total == true
+ || $the_total < $GLOBALS['cfg']['ForeignKeyMaxLimit']
+ ) {
+ // foreign_display can be false if no display field defined:
+ $foreign_display = PMA_getDisplayField($foreign_db, $foreign_table);
+
+ $f_query_main = 'SELECT ' . PMA_Util::backquote($foreign_field)
+ . (($foreign_display == false) ? '' : ', ' . PMA_Util::backquote($foreign_display));
+ $f_query_from = ' FROM ' . PMA_Util::backquote($foreign_db)
+ . '.' . PMA_Util::backquote($foreign_table);
+ $f_query_filter = empty($foreign_filter) ? '' : ' WHERE '
+ . PMA_Util::backquote($foreign_field)
+ . ' LIKE "%' . PMA_Util::sqlAddSlashes($foreign_filter, true) . '%"'
+ . (($foreign_display == false) ? '' : ' OR ' . PMA_Util::backquote($foreign_display)
+ . ' LIKE "%' . PMA_Util::sqlAddSlashes($foreign_filter, true) . '%"'
+ );
+ $f_query_order = ($foreign_display == false) ? '' :' ORDER BY '
+ . PMA_Util::backquote($foreign_table) . '.'
+ . PMA_Util::backquote($foreign_display);
+ $f_query_limit = isset($foreign_limit) ? $foreign_limit : '';
+
+ if (!empty($foreign_filter)) {
+ $res = $GLOBALS['dbi']->query(
+ 'SELECT COUNT(*)' . $f_query_from . $f_query_filter
+ );
+ if ($res) {
+ $the_total = $GLOBALS['dbi']->fetchValue($res);
+ @$GLOBALS['dbi']->freeResult($res);
+ } else {
+ $the_total = 0;
+ }
+ }
+
+ $disp = $GLOBALS['dbi']->query(
+ $f_query_main . $f_query_from . $f_query_filter
+ . $f_query_order . $f_query_limit
+ );
+ if ($disp && $GLOBALS['dbi']->numRows($disp) > 0) {
+ // If a resultset has been created, pre-cache it in the $disp_row
+ // array. This helps us from not needing to use mysql_data_seek by
+ // accessing a pre-cached PHP array. Usually those resultsets are
+ // not that big, so a performance hit should not be expected.
+ $disp_row = array();
+ while ($single_disp_row = @$GLOBALS['dbi']->fetchAssoc($disp)) {
+ $disp_row[] = $single_disp_row;
+ }
+ @$GLOBALS['dbi']->freeResult($disp);
+ }
+ } else {
+ $disp_row = null;
+ $foreign_link = true;
+ }
+ } // end if $foreigners
+
+ $foreignData['foreign_link'] = $foreign_link;
+ $foreignData['the_total'] = isset($the_total) ? $the_total : null;
+ $foreignData['foreign_display'] = (
+ isset($foreign_display) ? $foreign_display : null
+ );
+ $foreignData['disp_row'] = isset($disp_row) ? $disp_row : null;
+ $foreignData['foreign_field'] = isset($foreign_field) ? $foreign_field : null;
+ return $foreignData;
+} // end of 'PMA_getForeignData()' function
+
+/**
+ * Finds all related tables
+ *
+ * @param array $all_tables All the involved tables
+ * @param string $master The master table to form the LEFT JOIN clause
+ *
+ * @return string LEFT JOIN
+ * @access private
+ */
+function PMA_getRelatives($all_tables, $master)
+{
+ $fromclause = '';
+ $emerg = '';
+
+ // The list of tables that we still couldn't connect
+ $remaining_tables = $all_tables;
+ unset($remaining_tables[$master]);
+ // The list of allready connected tables
+ $known_tables[$master] = $master;
+ $run = 0;
+ while (count($remaining_tables) > 0) {
+ // Whether to go from master to foreign or vice versa
+ if ($run % 2 == 0) {
+ $from = 'master';
+ $to = 'foreign';
+ } else {
+ $from = 'foreign';
+ $to = 'master';
+ }
+ $in_know = '(\'' . implode('\', \'', $known_tables) . '\')';
+ $in_left = '(\'' . implode('\', \'', $remaining_tables) . '\')';
+ $rel_query = 'SELECT *'
+ . ' FROM ' . PMA_Util::backquote($GLOBALS['cfgRelation']['db'])
+ . '.' . PMA_Util::backquote($GLOBALS['cfgRelation']['relation'])
+ . ' WHERE ' . $from . '_db = \'' . PMA_Util::sqlAddSlashes($GLOBALS['db']) . '\''
+ . ' AND ' . $to . '_db = \'' . PMA_Util::sqlAddSlashes($GLOBALS['db']) . '\''
+ . ' AND ' . $from . '_table IN ' . $in_know
+ . ' AND ' . $to . '_table IN ' . $in_left;
+ $relations = @$GLOBALS['dbi']->query($rel_query, $GLOBALS['controllink']);
+ while ($row = $GLOBALS['dbi']->fetchAssoc($relations)) {
+ $found_table = $row[$to . '_table'];
+ if (isset($remaining_tables[$found_table])) {
+ $fromclause
+ .= "\n" . ' LEFT JOIN '
+ . PMA_Util::backquote($GLOBALS['db']) . '.' . PMA_Util::backquote($row[$to . '_table']) . ' ON '
+ . PMA_Util::backquote($row[$from . '_table']) . '.'
+ . PMA_Util::backquote($row[$from . '_field']) . ' = '
+ . PMA_Util::backquote($row[$to . '_table']) . '.'
+ . PMA_Util::backquote($row[$to . '_field']) . ' ';
+ $known_tables[$found_table] = $found_table;
+ unset($remaining_tables[$found_table]);
+ }
+ } // end while
+ $run++;
+ if ($run > 5) {
+ foreach ($remaining_tables as $table) {
+ $emerg .= ', ' . PMA_Util::backquote($table);
+ unset($remaining_tables[$table]);
+ }
+ }
+ } // end while
+ $fromclause = $emerg . $fromclause;
+ return $fromclause;
+} // end of the "PMA_getRelatives()" function
+
+/**
+ * Rename a field in relation tables
+ *
+ * usually called after a column in a table was renamed
+ *
+ * @param string $db database name
+ * @param string $table table name
+ * @param string $field old field name
+ * @param string $new_name new field name
+ *
+ * @return void
+ */
+function PMA_REL_renameField($db, $table, $field, $new_name)
+{
+ $cfgRelation = PMA_getRelationsParam();
+
+ if ($cfgRelation['displaywork']) {
+ $table_query = 'UPDATE '
+ . PMA_Util::backquote($cfgRelation['db']) . '.'
+ . PMA_Util::backquote($cfgRelation['table_info'])
+ . ' SET display_field = \'' . PMA_Util::sqlAddSlashes($new_name) . '\''
+ . ' WHERE db_name = \'' . PMA_Util::sqlAddSlashes($db) . '\''
+ . ' AND table_name = \'' . PMA_Util::sqlAddSlashes($table) . '\''
+ . ' AND display_field = \'' . PMA_Util::sqlAddSlashes($field) . '\'';
+ PMA_queryAsControlUser($table_query);
+ }
+
+ if ($cfgRelation['relwork']) {
+ $table_query = 'UPDATE '
+ . PMA_Util::backquote($cfgRelation['db']) . '.'
+ . PMA_Util::backquote($cfgRelation['relation'])
+ . ' SET master_field = \'' . PMA_Util::sqlAddSlashes($new_name) . '\''
+ . ' WHERE master_db = \'' . PMA_Util::sqlAddSlashes($db) . '\''
+ . ' AND master_table = \'' . PMA_Util::sqlAddSlashes($table) . '\''
+ . ' AND master_field = \'' . PMA_Util::sqlAddSlashes($field) . '\'';
+ PMA_queryAsControlUser($table_query);
+
+ $table_query = 'UPDATE '
+ . PMA_Util::backquote($cfgRelation['db']) . '.'
+ . PMA_Util::backquote($cfgRelation['relation'])
+ . ' SET foreign_field = \'' . PMA_Util::sqlAddSlashes($new_name) . '\''
+ . ' WHERE foreign_db = \'' . PMA_Util::sqlAddSlashes($db) . '\''
+ . ' AND foreign_table = \'' . PMA_Util::sqlAddSlashes($table) . '\''
+ . ' AND foreign_field = \'' . PMA_Util::sqlAddSlashes($field) . '\'';
+ PMA_queryAsControlUser($table_query);
+
+ } // end if relwork
+}
+
+
+/**
+ * Performs SQL query used for renaming table.
+ *
+ * @param string $table Relation table to use
+ * @param string $source_db Source database name
+ * @param string $target_db Target database name
+ * @param string $source_table Source table name
+ * @param string $target_table Target table name
+ * @param string $db_field Name of database field
+ * @param string $table_field Name of table field
+ *
+ * @return void
+ */
+function PMA_REL_renameSingleTable($table,
+ $source_db, $target_db,
+ $source_table, $target_table,
+ $db_field, $table_field
+) {
+ $query = 'UPDATE '
+ . PMA_Util::backquote($GLOBALS['cfgRelation']['db']) . '.'
+ . PMA_Util::backquote($GLOBALS['cfgRelation'][$table])
+ . ' SET '
+ . $db_field . ' = \'' . PMA_Util::sqlAddSlashes($target_db) . '\', '
+ . $table_field . ' = \'' . PMA_Util::sqlAddSlashes($target_table) . '\''
+ . ' WHERE '
+ . $db_field . ' = \'' . PMA_Util::sqlAddSlashes($source_db) . '\''
+ . ' AND '
+ . $table_field . ' = \'' . PMA_Util::sqlAddSlashes($source_table) . '\'';
+ PMA_queryAsControlUser($query);
+}
+
+
+/**
+ * Rename a table in relation tables
+ *
+ * usually called after table has been moved
+ *
+ * @param string $source_db Source database name
+ * @param string $target_db Target database name
+ * @param string $source_table Source table name
+ * @param string $target_table Target table name
+ *
+ * @return void
+ */
+function PMA_REL_renameTable($source_db, $target_db, $source_table, $target_table)
+{
+ // Move old entries from PMA-DBs to new table
+ if ($GLOBALS['cfgRelation']['commwork']) {
+ PMA_REL_renameSingleTable(
+ 'column_info',
+ $source_db, $target_db,
+ $source_table, $target_table,
+ 'db_name', 'table_name'
+ );
+ }
+
+ // updating bookmarks is not possible since only a single table is
+ // moved, and not the whole DB.
+
+ if ($GLOBALS['cfgRelation']['displaywork']) {
+ PMA_REL_renameSingleTable(
+ 'table_info',
+ $source_db, $target_db,
+ $source_table, $target_table,
+ 'db_name', 'table_name'
+ );
+ }
+
+ if ($GLOBALS['cfgRelation']['relwork']) {
+ PMA_REL_renameSingleTable(
+ 'relation',
+ $source_db, $target_db,
+ $source_table, $target_table,
+ 'foreign_db', 'foreign_table'
+ );
+
+ PMA_REL_renameSingleTable(
+ 'relation',
+ $source_db, $target_db,
+ $source_table, $target_table,
+ 'master_db', 'master_table'
+ );
+ }
+
+ /**
+ * @todo Can't get moving PDFs the right way. The page numbers
+ * always get screwed up independently from duplication because the
+ * numbers do not seem to be stored on a per-database basis. Would
+ * the author of pdf support please have a look at it?
+ */
+
+ if ($GLOBALS['cfgRelation']['pdfwork']) {
+ PMA_REL_renameSingleTable(
+ 'table_coords',
+ $source_db, $target_db,
+ $source_table, $target_table,
+ 'db_name', 'table_name'
+ );
+ }
+
+ if ($GLOBALS['cfgRelation']['designerwork']) {
+ PMA_REL_renameSingleTable(
+ 'designer_coords',
+ $source_db, $target_db,
+ $source_table, $target_table,
+ 'db_name', 'table_name'
+ );
+ }
+}
+
+/**
+ * Create a PDF page
+ *
+ * @param string $newpage name of the new PDF page
+ * @param array $cfgRelation Relation configuration
+ * @param string $db database name
+ *
+ * @return string $pdf_page_number
+ */
+function PMA_REL_createPage($newpage, $cfgRelation, $db)
+{
+ if (! isset($newpage) || $newpage == '') {
+ $newpage = __('no description');
+ }
+ $ins_query = 'INSERT INTO '
+ . PMA_Util::backquote($GLOBALS['cfgRelation']['db']) . '.'
+ . PMA_Util::backquote($cfgRelation['pdf_pages'])
+ . ' (db_name, page_descr)'
+ . ' VALUES (\''
+ . PMA_Util::sqlAddSlashes($db) . '\', \''
+ . PMA_Util::sqlAddSlashes($newpage) . '\')';
+ PMA_queryAsControlUser($ins_query, false);
+
+ return $GLOBALS['dbi']->insertId(
+ isset($GLOBALS['controllink']) ? $GLOBALS['controllink'] : ''
+ );
+}
+?>
diff --git a/libraries/relation_cleanup.lib.php b/libraries/relation_cleanup.lib.php
new file mode 100644
index 0000000000..52d8812f47
--- /dev/null
+++ b/libraries/relation_cleanup.lib.php
@@ -0,0 +1,183 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Set of functions used for cleaning up phpMyAdmin tables
+ *
+ * @package PhpMyAdmin
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ * Cleanup column related relation stuff
+ *
+ * @param string $db database name
+ * @param string $table table name
+ * @param string $column column name
+ *
+ * @return void
+ */
+function PMA_relationsCleanupColumn($db, $table, $column)
+{
+ $cfgRelation = PMA_getRelationsParam();
+
+ if ($cfgRelation['commwork']) {
+ $remove_query = 'DELETE FROM ' . PMA_Util::backquote($cfgRelation['db'])
+ . '.' . PMA_Util::backquote($cfgRelation['column_info'])
+ . ' WHERE db_name = \'' . PMA_Util::sqlAddSlashes($db) . '\''
+ . ' AND table_name = \'' . PMA_Util::sqlAddSlashes($table) . '\''
+ . ' AND column_name = \'' . PMA_Util::sqlAddSlashes($column) . '\'';
+ PMA_queryAsControlUser($remove_query);
+ }
+
+ if ($cfgRelation['displaywork']) {
+ $remove_query = 'DELETE FROM ' . PMA_Util::backquote($cfgRelation['db'])
+ . '.' . PMA_Util::backquote($cfgRelation['table_info'])
+ . ' WHERE db_name = \'' . PMA_Util::sqlAddSlashes($db) . '\''
+ . ' AND table_name = \'' . PMA_Util::sqlAddSlashes($table) . '\''
+ . ' AND display_field = \'' . PMA_Util::sqlAddSlashes($column) . '\'';
+ PMA_queryAsControlUser($remove_query);
+ }
+
+ if ($cfgRelation['relwork']) {
+ $remove_query = 'DELETE FROM ' . PMA_Util::backquote($cfgRelation['db'])
+ . '.' . PMA_Util::backquote($cfgRelation['relation'])
+ . ' WHERE master_db = \'' . PMA_Util::sqlAddSlashes($db) . '\''
+ . ' AND master_table = \'' . PMA_Util::sqlAddSlashes($table) . '\''
+ . ' AND master_field = \'' . PMA_Util::sqlAddSlashes($column) . '\'';
+ PMA_queryAsControlUser($remove_query);
+
+ $remove_query = 'DELETE FROM ' . PMA_Util::backquote($cfgRelation['db'])
+ . '.' . PMA_Util::backquote($cfgRelation['relation'])
+ . ' WHERE foreign_db = \'' . PMA_Util::sqlAddSlashes($db) . '\''
+ . ' AND foreign_table = \'' . PMA_Util::sqlAddSlashes($table) . '\''
+ . ' AND foreign_field = \'' . PMA_Util::sqlAddSlashes($column) . '\'';
+ PMA_queryAsControlUser($remove_query);
+ }
+}
+
+/**
+ * Cleanup table related relation stuff
+ *
+ * @param string $db database name
+ * @param string $table table name
+ *
+ * @return void
+ */
+function PMA_relationsCleanupTable($db, $table)
+{
+ $cfgRelation = PMA_getRelationsParam();
+
+ if ($cfgRelation['commwork']) {
+ $remove_query = 'DELETE FROM ' . PMA_Util::backquote($cfgRelation['db'])
+ . '.' . PMA_Util::backquote($cfgRelation['column_info'])
+ . ' WHERE db_name = \'' . PMA_Util::sqlAddSlashes($db) . '\''
+ . ' AND table_name = \'' . PMA_Util::sqlAddSlashes($table) . '\'';
+ PMA_queryAsControlUser($remove_query);
+ }
+
+ if ($cfgRelation['displaywork']) {
+ $remove_query = 'DELETE FROM ' . PMA_Util::backquote($cfgRelation['db'])
+ . '.' . PMA_Util::backquote($cfgRelation['table_info'])
+ . ' WHERE db_name = \'' . PMA_Util::sqlAddSlashes($db) . '\''
+ . ' AND table_name = \'' . PMA_Util::sqlAddSlashes($table) . '\'';
+ PMA_queryAsControlUser($remove_query);
+ }
+
+ if ($cfgRelation['pdfwork']) {
+ $remove_query = 'DELETE FROM ' . PMA_Util::backquote($cfgRelation['db'])
+ . '.' . PMA_Util::backquote($cfgRelation['table_coords'])
+ . ' WHERE db_name = \'' . PMA_Util::sqlAddSlashes($db) . '\''
+ . ' AND table_name = \'' . PMA_Util::sqlAddSlashes($table) . '\'';
+ PMA_queryAsControlUser($remove_query);
+ }
+
+ if ($cfgRelation['designerwork']) {
+ $remove_query = 'DELETE FROM ' . PMA_Util::backquote($cfgRelation['db'])
+ . '.' . PMA_Util::backquote($cfgRelation['designer_coords'])
+ . ' WHERE db_name = \'' . PMA_Util::sqlAddSlashes($db) . '\''
+ . ' AND table_name = \'' . PMA_Util::sqlAddSlashes($table) . '\'';
+ PMA_queryAsControlUser($remove_query);
+ }
+
+ if ($cfgRelation['relwork']) {
+ $remove_query = 'DELETE FROM ' . PMA_Util::backquote($cfgRelation['db'])
+ . '.' . PMA_Util::backquote($cfgRelation['relation'])
+ . ' WHERE master_db = \'' . PMA_Util::sqlAddSlashes($db) . '\''
+ . ' AND master_table = \'' . PMA_Util::sqlAddSlashes($table) . '\'';
+ PMA_queryAsControlUser($remove_query);
+
+ $remove_query = 'DELETE FROM ' . PMA_Util::backquote($cfgRelation['db'])
+ . '.' . PMA_Util::backquote($cfgRelation['relation'])
+ . ' WHERE foreign_db = \'' . PMA_Util::sqlAddSlashes($db) . '\''
+ . ' AND foreign_table = \'' . PMA_Util::sqlAddSlashes($table) . '\'';
+ PMA_queryAsControlUser($remove_query);
+ }
+}
+
+/**
+ * Cleanup database related relation stuff
+ *
+ * @param string $db database name
+ *
+ * @return void
+ */
+function PMA_relationsCleanupDatabase($db)
+{
+ $cfgRelation = PMA_getRelationsParam();
+
+ if ($cfgRelation['commwork']) {
+ $remove_query = 'DELETE FROM ' . PMA_Util::backquote($cfgRelation['db'])
+ . '.' . PMA_Util::backquote($cfgRelation['column_info'])
+ . ' WHERE db_name = \'' . PMA_Util::sqlAddSlashes($db) . '\'';
+ PMA_queryAsControlUser($remove_query);
+ }
+
+ if ($cfgRelation['bookmarkwork']) {
+ $remove_query = 'DELETE FROM ' . PMA_Util::backquote($cfgRelation['db'])
+ . '.' . PMA_Util::backquote($cfgRelation['bookmark'])
+ . ' WHERE dbase = \'' . PMA_Util::sqlAddSlashes($db) . '\'';
+ PMA_queryAsControlUser($remove_query);
+ }
+
+ if ($cfgRelation['displaywork']) {
+ $remove_query = 'DELETE FROM ' . PMA_Util::backquote($cfgRelation['db'])
+ . '.' . PMA_Util::backquote($cfgRelation['table_info'])
+ . ' WHERE db_name = \'' . PMA_Util::sqlAddSlashes($db) . '\'';
+ PMA_queryAsControlUser($remove_query);
+ }
+
+ if ($cfgRelation['pdfwork']) {
+ $remove_query = 'DELETE FROM ' . PMA_Util::backquote($cfgRelation['db'])
+ . '.' . PMA_Util::backquote($cfgRelation['pdf_pages'])
+ . ' WHERE db_name = \'' . PMA_Util::sqlAddSlashes($db) . '\'';
+ PMA_queryAsControlUser($remove_query);
+
+ $remove_query = 'DELETE FROM ' . PMA_Util::backquote($cfgRelation['db'])
+ . '.' . PMA_Util::backquote($cfgRelation['table_coords'])
+ . ' WHERE db_name = \'' . PMA_Util::sqlAddSlashes($db) . '\'';
+ PMA_queryAsControlUser($remove_query);
+ }
+
+ if ($cfgRelation['designerwork']) {
+ $remove_query = 'DELETE FROM ' . PMA_Util::backquote($cfgRelation['db'])
+ . '.' . PMA_Util::backquote($cfgRelation['designer_coords'])
+ . ' WHERE db_name = \'' . PMA_Util::sqlAddSlashes($db) . '\'';
+ PMA_queryAsControlUser($remove_query);
+ }
+
+ if ($cfgRelation['relwork']) {
+ $remove_query = 'DELETE FROM ' . PMA_Util::backquote($cfgRelation['db'])
+ . '.' . PMA_Util::backquote($cfgRelation['relation'])
+ . ' WHERE master_db = \'' . PMA_Util::sqlAddSlashes($db) . '\'';
+ PMA_queryAsControlUser($remove_query);
+
+ $remove_query = 'DELETE FROM ' . PMA_Util::backquote($cfgRelation['db'])
+ . '.' . PMA_Util::backquote($cfgRelation['relation'])
+ . ' WHERE foreign_db = \'' . PMA_Util::sqlAddSlashes($db) . '\'';
+ PMA_queryAsControlUser($remove_query);
+ }
+}
+
+?>
diff --git a/libraries/replication.inc.php b/libraries/replication.inc.php
new file mode 100644
index 0000000000..777a17cf8e
--- /dev/null
+++ b/libraries/replication.inc.php
@@ -0,0 +1,288 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Replication helpers
+ *
+ * @package PhpMyAdmin
+ */
+
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ * get master replication from server
+ */
+$server_master_replication = $GLOBALS['dbi']->fetchResult('SHOW MASTER STATUS');
+
+/**
+ * get slave replication from server
+ */
+$server_slave_replication = $GLOBALS['dbi']->fetchResult('SHOW SLAVE STATUS');
+
+/**
+ * replication types
+ */
+$replication_types = array('master', 'slave');
+
+
+/**
+ * define variables for master status
+ */
+$master_variables = array(
+ 'File',
+ 'Position',
+ 'Binlog_Do_DB',
+ 'Binlog_Ignore_DB',
+);
+
+/**
+ * Define variables for slave status
+ */
+$slave_variables = array(
+ 'Slave_IO_State',
+ 'Master_Host',
+ 'Master_User',
+ 'Master_Port',
+ 'Connect_Retry',
+ 'Master_Log_File',
+ 'Read_Master_Log_Pos',
+ 'Relay_Log_File',
+ 'Relay_Log_Pos',
+ 'Relay_Master_Log_File',
+ 'Slave_IO_Running',
+ 'Slave_SQL_Running',
+ 'Replicate_Do_DB',
+ 'Replicate_Ignore_DB',
+ 'Replicate_Do_Table',
+ 'Replicate_Ignore_Table',
+ 'Replicate_Wild_Do_Table',
+ 'Replicate_Wild_Ignore_Table',
+ 'Last_Errno',
+ 'Last_Error',
+ 'Skip_Counter',
+ 'Exec_Master_Log_Pos',
+ 'Relay_Log_Space',
+ 'Until_Condition',
+ 'Until_Log_File',
+ 'Until_Log_Pos',
+ 'Master_SSL_Allowed',
+ 'Master_SSL_CA_File',
+ 'Master_SSL_CA_Path',
+ 'Master_SSL_Cert',
+ 'Master_SSL_Cipher',
+ 'Master_SSL_Key',
+ 'Seconds_Behind_Master',
+);
+/**
+ * define important variables, which need to be watched for
+ * correct running of replication in slave mode
+ *
+ * @usedby PMA_getHtmlForReplicationStatusTable()
+ */
+// TODO change to regexp or something, to allow for negative match.
+// To e.g. highlight 'Last_Error'
+//
+$slave_variables_alerts = array(
+ 'Slave_IO_Running' => 'No',
+ 'Slave_SQL_Running' => 'No',
+);
+$slave_variables_oks = array(
+ 'Slave_IO_Running' => 'Yes',
+ 'Slave_SQL_Running' => 'Yes',
+);
+
+// check which replication is available and
+// set $server_{master/slave}_status and assign values
+
+// replication info is more easily passed to functions
+/*
+ * @todo use $replication_info everywhere instead of the generated variable names
+ */
+$replication_info = array();
+
+foreach ($replication_types as $type) {
+ if (count(${"server_{$type}_replication"}) > 0) {
+ ${"server_{$type}_status"} = true;
+ $replication_info[$type]['status'] = true;
+ } else {
+ ${"server_{$type}_status"} = false;
+ $replication_info[$type]['status'] = false;
+ }
+ if (${"server_{$type}_status"}) {
+ if ($type == "master") {
+ ${"server_{$type}_Do_DB"} = explode(
+ ",", $server_master_replication[0]["Binlog_Do_DB"]
+ );
+ $replication_info[$type]['Do_DB'] = ${"server_{$type}_Do_DB"};
+
+ ${"server_{$type}_Ignore_DB"} = explode(
+ ",", $server_master_replication[0]["Binlog_Ignore_DB"]
+ );
+ $replication_info[$type]['Ignore_DB'] = ${"server_{$type}_Ignore_DB"};
+ } elseif ($type == "slave") {
+ ${"server_{$type}_Do_DB"} = explode(
+ ",", $server_slave_replication[0]["Replicate_Do_DB"]
+ );
+ if (! empty(${"server_{$type}_Do_DB"})) {
+ $replication_info[$type]['Do_DB'] = ${"server_{$type}_Do_DB"};
+ }
+
+ ${"server_{$type}_Ignore_DB"} = explode(
+ ",", $server_slave_replication[0]["Replicate_Ignore_DB"]
+ );
+ $replication_info[$type]['Ignore_DB'] = ${"server_{$type}_Ignore_DB"};
+
+ ${"server_{$type}_Do_Table"} = explode(
+ ",", $server_slave_replication[0]["Replicate_Do_Table"]
+ );
+ $replication_info[$type]['Do_Table'] = ${"server_{$type}_Do_Table"};
+
+ ${"server_{$type}_Ignore_Table"} = explode(
+ ",", $server_slave_replication[0]["Replicate_Ignore_Table"]
+ );
+ $replication_info[$type]['Ignore_Table']
+ = ${"server_{$type}_Ignore_Table"};
+
+ ${"server_{$type}_Wild_Do_Table"} = explode(
+ ",", $server_slave_replication[0]["Replicate_Wild_Do_Table"]
+ );
+ $replication_info[$type]['Wild_Do_Table']
+ = ${"server_{$type}_Wild_Do_Table"};
+
+ ${"server_{$type}_Wild_Ignore_Table"} = explode(
+ ",", $server_slave_replication[0]["Replicate_Wild_Ignore_Table"]
+ );
+ $replication_info[$type]['Wild_Ignore_Table']
+ = ${"server_{$type}_Wild_Ignore_Table"};
+ }
+ }
+}
+
+/**
+ * Extracts database or table name from string
+ *
+ * @param string $string contains "dbname.tablename"
+ * @param string $what what to extract (db|table)
+ *
+ * @return $string the extracted part
+ */
+function PMA_extractDbOrTable($string, $what = 'db')
+{
+ $list = explode(".", $string);
+ if ('db' == $what) {
+ return $list[0];
+ } else {
+ return $list[1];
+ }
+}
+
+/**
+ * Configures replication slave
+ *
+ * @param string $action possible values: START or STOP
+ * @param string $control default: null,
+ * possible values: SQL_THREAD or IO_THREAD or null.
+ * If it is set to null, it controls both
+ * SQL_THREAD and IO_THREAD
+ * @param mixed $link mysql link
+ *
+ * @return mixed output of DatabaseInterface::tryQuery
+ */
+function PMA_Replication_Slave_control($action, $control = null, $link = null)
+{
+ $action = strtoupper($action);
+ $control = strtoupper($control);
+
+ if ($action != "START" && $action != "STOP") {
+ return -1;
+ }
+ if ($control != "SQL_THREAD" && $control != "IO_THREAD" && $control != null) {
+ return -1;
+ }
+
+ return $GLOBALS['dbi']->tryQuery($action . " SLAVE " . $control . ";", $link);
+}
+
+/**
+ * Changes master for replication slave
+ *
+ * @param string $user replication user on master
+ * @param string $password password for the user
+ * @param string $host master's hostname or IP
+ * @param int $port port, where mysql is running
+ * @param array $pos position of mysql replication,
+ * array should contain fields File and Position
+ * @param bool $stop shall we stop slave?
+ * @param bool $start shall we start slave?
+ * @param mixed $link mysql link
+ *
+ * @return string output of CHANGE MASTER mysql command
+ */
+function PMA_Replication_Slave_changeMaster($user, $password, $host, $port,
+ $pos, $stop = true, $start = true, $link = null
+) {
+ if ($stop) {
+ PMA_Replication_Slave_control("STOP", null, $link);
+ }
+
+ $out = $GLOBALS['dbi']->tryQuery(
+ 'CHANGE MASTER TO ' .
+ 'MASTER_HOST=\'' . $host . '\',' .
+ 'MASTER_PORT=' . ($port * 1) . ',' .
+ 'MASTER_USER=\'' . $user . '\',' .
+ 'MASTER_PASSWORD=\'' . $password . '\',' .
+ 'MASTER_LOG_FILE=\'' . $pos["File"] . '\',' .
+ 'MASTER_LOG_POS=' . $pos["Position"] . ';', $link
+ );
+
+ if ($start) {
+ PMA_Replication_Slave_control("START", null, $link);
+ }
+
+ return $out;
+}
+
+/**
+ * This function provides connection to remote mysql server
+ *
+ * @param string $user mysql username
+ * @param string $password password for the user
+ * @param string $host mysql server's hostname or IP
+ * @param int $port mysql remote port
+ * @param string $socket path to unix socket
+ *
+ * @return mixed $link mysql link on success
+ */
+function PMA_Replication_connectToMaster(
+ $user, $password, $host = null, $port = null, $socket = null
+) {
+ $server = array();
+ $server["host"] = $host;
+ $server["port"] = $port;
+ $server["socket"] = $socket;
+
+ // 5th parameter set to true means that it's an auxiliary connection
+ // and we must not go back to login page if it fails
+ return $GLOBALS['dbi']->connect($user, $password, false, $server, true);
+}
+/**
+ * Fetches position and file of current binary log on master
+ *
+ * @param mixed $link mysql link
+ *
+ * @return array an array containing File and Position in MySQL replication
+ * on master server, useful for PMA_Replication_Slave_changeMaster
+ */
+function PMA_Replication_Slave_binLogMaster($link = null)
+{
+ $data = $GLOBALS['dbi']->fetchResult('SHOW MASTER STATUS', null, null, $link);
+ $output = array();
+
+ if (! empty($data)) {
+ $output["File"] = $data[0]["File"];
+ $output["Position"] = $data[0]["Position"];
+ }
+ return $output;
+}
+?>
diff --git a/libraries/replication_gui.lib.php b/libraries/replication_gui.lib.php
new file mode 100644
index 0000000000..7c5af7b6e2
--- /dev/null
+++ b/libraries/replication_gui.lib.php
@@ -0,0 +1,1071 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ *
+ * @package PhpMyAdmin
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ * returns HTML for error message
+ *
+ * @return String HTML code
+ */
+function PMA_getHtmlForErrorMessage()
+{
+ $html = '';
+ if (isset($_SESSION['replication']['sr_action_status'])
+ && isset($_SESSION['replication']['sr_action_info'])
+ ) {
+ if ($_SESSION['replication']['sr_action_status'] == 'error') {
+ $error_message = $_SESSION['replication']['sr_action_info'];
+ $html .= PMA_Message::error($error_message)->getDisplay();
+ $_SESSION['replication']['sr_action_status'] = 'unknown';
+ } elseif ($_SESSION['replication']['sr_action_status'] == 'success') {
+ $success_message = $_SESSION['replication']['sr_action_info'];
+ $html .= PMA_Message::success($success_message)->getDisplay();
+ $_SESSION['replication']['sr_action_status'] = 'unknown';
+ }
+ }
+ return $html;
+}
+
+/**
+ * returns HTML for master replication
+ *
+ * @return String HTML code
+ */
+function PMA_getHtmlForMasterReplication()
+{
+ $html = '';
+ if (! isset($_REQUEST['repl_clear_scr'])) {
+ $html .= '<fieldset>';
+ $html .= '<legend>' . __('Master replication') . '</legend>';
+ $html .= __('This server is configured as master in a replication process.');
+ $html .= '<ul>';
+ $html .= ' <li><a href="#" id="master_status_href">';
+ $html .= __('Show master status') . '</a>';
+ $html .= PMA_getHtmlForReplicationStatusTable('master', true, false);
+ $html .= ' </li>';
+
+ $html .= ' <li><a href="#" id="master_slaves_href">';
+ $html .= __('Show connected slaves') . '</a>';
+ $html .= PMA_getHtmlForReplicationSlavesTable(true);
+ $html .= ' </li>';
+
+ $_url_params = $GLOBALS['url_params'];
+ $_url_params['mr_adduser'] = true;
+ $_url_params['repl_clear_scr'] = true;
+
+ $html .= ' <li><a href="server_replication.php';
+ $html .= PMA_URL_getCommon($_url_params)
+ . '" id="master_addslaveuser_href">';
+ $html .= __('Add slave replication user') . '</a></li>';
+ }
+
+ // Display 'Add replication slave user' form
+ if (isset($_REQUEST['mr_adduser'])) {
+ $html .= PMA_getHtmlForReplicationMasterAddSlaveuser();
+ } elseif (! isset($_REQUEST['repl_clear_scr'])) {
+ $html .= "</ul>";
+ $html .= "</fieldset>";
+ }
+
+ return $html;
+}
+
+/**
+ * returns HTML for master replication configuration
+ *
+ * @return String HTML code
+ */
+function PMA_getHtmlForMasterConfiguration()
+{
+ $html = '<fieldset>';
+ $html .= '<legend>' . __('Master configuration') . '</legend>';
+ $html .= __(
+ 'This server is not configured as master server in a '
+ . 'replication process. You can choose from either replicating '
+ . 'all databases and ignoring certain (useful if you want to replicate '
+ . 'majority of databases) or you can choose to ignore all databases by '
+ . 'default and allow only certain databases to be replicated. '
+ . 'Please select the mode:'
+ ) . '<br /><br />';
+
+ $html .= '<select name="db_type" id="db_type">';
+ $html .= '<option value="all">' . __('Replicate all databases; Ignore:');
+ $html .= '</option>';
+ $html .= '<option value="ign">' . __('Ignore all databases; Replicate:');
+ $html .= '</option>';
+ $html .= '</select>';
+ $html .= '<br /><br />';
+ $html .= __('Please select databases:') . '<br />';
+ $html .= PMA_getHtmlForReplicationDbMultibox();
+ $html .= '<br /><br />';
+ $html .= __(
+ 'Now, add the following lines at the end of [mysqld] section'
+ . ' in your my.cnf and please restart the MySQL server afterwards.'
+ ). '<br />';
+ $html .= '<pre id="rep"></pre>';
+ $html .= __(
+ 'Once you restarted MySQL server, please click on Go button. '
+ . 'Afterwards, you should see a message informing you, that this server'
+ . ' <b>is</b> configured as master.'
+ );
+ $html .= '</fieldset>';
+ $html .= '<fieldset class="tblFooters">';
+ $html .= ' <form method="post" action="server_replication.php" >';
+ $html .= PMA_URL_getHiddenInputs('', '');
+ $html .= ' <input type="submit" value="' . __('Go') . '" id="goButton" />';
+ $html .= ' </form>';
+ $html .= '</fieldset>';
+
+ return $html;
+}
+
+/**
+ * returns HTML for slave replication configuration
+ *
+ * @param bool $server_slave_status Whether it is Master or Slave
+ * @param Array $server_slave_replication Slave replication
+ *
+ * @return String HTML code
+ */
+function PMA_getHtmlForSlaveConfiguration(
+ $server_slave_status, $server_slave_replication
+) {
+ $html = '<fieldset>';
+ $html .= '<legend>' . __('Slave replication') . '</legend>';
+ if ($server_slave_status) {
+ $html .= '<div id="slave_configuration_gui">';
+
+ $_url_params = $GLOBALS['url_params'];
+ $_url_params['sr_take_action'] = true;
+ $_url_params['sr_slave_server_control'] = true;
+
+ if ($server_slave_replication[0]['Slave_IO_Running'] == 'No') {
+ $_url_params['sr_slave_action'] = 'start';
+ } else {
+ $_url_params['sr_slave_action'] = 'stop';
+ }
+
+ $_url_params['sr_slave_control_parm'] = 'IO_THREAD';
+ $slave_control_io_link = 'server_replication.php'
+ . PMA_URL_getCommon($_url_params);
+
+ if ($server_slave_replication[0]['Slave_SQL_Running'] == 'No') {
+ $_url_params['sr_slave_action'] = 'start';
+ } else {
+ $_url_params['sr_slave_action'] = 'stop';
+ }
+
+ $_url_params['sr_slave_control_parm'] = 'SQL_THREAD';
+ $slave_control_sql_link = 'server_replication.php'
+ . PMA_URL_getCommon($_url_params);
+
+ if ($server_slave_replication[0]['Slave_IO_Running'] == 'No'
+ || $server_slave_replication[0]['Slave_SQL_Running'] == 'No'
+ ) {
+ $_url_params['sr_slave_action'] = 'start';
+ } else {
+ $_url_params['sr_slave_action'] = 'stop';
+ }
+
+ $_url_params['sr_slave_control_parm'] = null;
+ $slave_control_full_link = 'server_replication.php'
+ . PMA_URL_getCommon($_url_params);
+
+ $_url_params['sr_slave_action'] = 'reset';
+ $slave_control_reset_link = 'server_replication.php'
+ . PMA_URL_getCommon($_url_params);
+
+ $_url_params = $GLOBALS['url_params'];
+ $_url_params['sr_slave_skip_error'] = true;
+ $slave_skip_error_link = 'server_replication.php'
+ . PMA_URL_getCommon($_url_params);
+
+ if ($server_slave_replication[0]['Slave_SQL_Running'] == 'No') {
+ $html .= PMA_Message::error(
+ __('Slave SQL Thread not running!')
+ )->getDisplay();
+ }
+ if ($server_slave_replication[0]['Slave_IO_Running'] == 'No') {
+ $html .= PMA_Message::error(
+ __('Slave IO Thread not running!')
+ )->getDisplay();
+ }
+
+ $_url_params = $GLOBALS['url_params'];
+ $_url_params['sl_configure'] = true;
+ $_url_params['repl_clear_scr'] = true;
+
+ $reconfiguremaster_link = 'server_replication.php'
+ . PMA_URL_getCommon($_url_params);
+
+ $html .= __('Server is configured as slave in a replication process. Would you like to:');
+ $html .= '<br />';
+ $html .= '<ul>';
+ $html .= ' <li><a href="#" id="slave_status_href">';
+ $html .= __('See slave status table') . '</a>';
+ $html .= PMA_getHtmlForReplicationStatusTable('slave', true, false);
+ $html .= ' </li>';
+
+ $html .= ' <li><a href="#" id="slave_control_href">';
+ $html .= __('Control slave:') . '</a>';
+ $html .= ' <div id="slave_control_gui" style="display: none">';
+ $html .= ' <ul>';
+ $html .= ' <li><a href="'. $slave_control_full_link . '">';
+ $html .= (($server_slave_replication[0]['Slave_IO_Running'] == 'No' ||
+ $server_slave_replication[0]['Slave_SQL_Running'] == 'No')
+ ? __('Full start')
+ : __('Full stop')) . ' </a></li>';
+ $html .= ' <li><a href="'. $slave_control_reset_link . '">';
+ $html .= __('Reset slave') . '</a></li>';
+ if ($server_slave_replication[0]['Slave_SQL_Running'] == 'No') {
+ $html .= ' <li><a href="' . $slave_control_sql_link . '">';
+ $html .= __('Start SQL Thread only') . '</a></li>';
+ } else {
+ $html .= ' <li><a href="' . $slave_control_sql_link . '">';
+ $html .= __('Stop SQL Thread only') . '</a></li>';
+ }
+ if ($server_slave_replication[0]['Slave_IO_Running'] == 'No') {
+ $html .= ' <li><a href="' . $slave_control_io_link . '">';
+ $html .= __('Start IO Thread only') . '</a></li>';
+ } else {
+ $html .= ' <li><a href="' . $slave_control_io_link . '">';
+ $html .= __('Stop IO Thread only') . '</a></li>';
+ }
+ $html .= ' </ul>';
+ $html .= ' </div>';
+ $html .= ' </li>';
+ $html .= ' <li>';
+ $html .= PMA_getHtmlForSlaveErrorManagement($slave_skip_error_link);
+ $html .= ' </li>';
+ $html .= ' <li><a href="' . $reconfiguremaster_link . '">';
+ $html .= __('Change or reconfigure master server') . '</a></li>';
+ $html .= '</ul>';
+ $html .= '</div>';
+
+ } elseif (! isset($_REQUEST['sl_configure'])) {
+ $_url_params = $GLOBALS['url_params'];
+ $_url_params['sl_configure'] = true;
+ $_url_params['repl_clear_scr'] = true;
+
+ $html .= sprintf(
+ __(
+ 'This server is not configured as slave in a replication process. '
+ . 'Would you like to <a href="%s">configure</a> it?'
+ ),
+ 'server_replication.php' . PMA_URL_getCommon($_url_params)
+ );
+ }
+ $html .= '</fieldset>';
+
+ return $html;
+}
+
+/**
+ * returns HTML for Slave Error Management
+ *
+ * @param String $slave_skip_error_link error link
+ *
+ * @return String HTML code
+ */
+function PMA_getHtmlForSlaveErrorManagement($slave_skip_error_link)
+{
+ $html = '<a href="#" id="slave_errormanagement_href">';
+ $html .= __('Error management:') . '</a>';
+ $html .= ' <div id="slave_errormanagement_gui" style="display: none">';
+ $html .= PMA_Message::error(
+ __('Skipping errors might lead into unsynchronized master and slave!')
+ )->getDisplay();
+ $html .= ' <ul>';
+ $html .= ' <li><a href="' . $slave_skip_error_link . '">';
+ $html .= __('Skip current error') . '</a></li>';
+ $html .= ' <li>' . __('Skip next');
+ $html .= ' <form method="post" action="server_replication.php">';
+ $html .= PMA_URL_getHiddenInputs('', '');
+ $html .= ' <input type="text" name="sr_skip_errors_count" value="1" ';
+ $html .= 'style="width: 30px" />' . __('errors.');
+ $html .= ' <input type="submit" name="sr_slave_skip_error" ';
+ $html .= 'value="' . __('Go') . '" />';
+ $html .= ' <input type="hidden" name="sr_take_action" value="1" />';
+ $html .= ' </form></li>';
+ $html .= ' </ul>';
+ $html .= ' </div>';
+ return $html;
+}
+
+/**
+ * returns HTML for not configure for a server replication
+ *
+ * @return String HTML code
+ */
+function PMA_getHtmlForNotServerReplication()
+{
+ $_url_params = $GLOBALS['url_params'];
+ $_url_params['mr_configure'] = true;
+
+ $html = '<fieldset>';
+ $html .= '<legend>' . __('Master replication') . '</legend>';
+ $html .= sprintf(
+ __(
+ 'This server is not configured as master in a replication process. '
+ . 'Would you like to <a href="%s">configure</a> it?'
+ ),
+ 'server_replication.php' . PMA_URL_getCommon($_url_params)
+ );
+ $html .= '</fieldset>';
+ return $html;
+}
+
+/**
+ * returns HTML code for selecting databases
+ *
+ * @return String HTML code
+ */
+function PMA_getHtmlForReplicationDbMultibox()
+{
+ $multi_values = '';
+ $multi_values .= '<select name="db_select[]" '
+ . 'size="6" multiple="multiple" id="db_select">';
+
+ foreach ($GLOBALS['pma']->databases as $current_db) {
+ if ($GLOBALS['dbi']->isSystemSchema($current_db)) {
+ continue;
+ }
+ /* TODO: where $selectall should come from? */
+ if (! empty($selectall)
+ || (isset($tmp_select)
+ && strpos(' ' . $tmp_select, '|' . $current_db . '|'))
+ ) {
+ $is_selected = ' selected="selected"';
+ } else {
+ $is_selected = '';
+ }
+ $current_db = htmlspecialchars($current_db);
+ $multi_values .= ' <option value="' . $current_db . '" ';
+ $multi_values .= $is_selected . '>';
+ $multi_values .= $current_db . '</option>';
+ } // end while
+
+ $multi_values .= '</select>';
+ $multi_values .= '<br /><a href="#" id="db_reset_href">';
+ $multi_values .= __('Uncheck All') . '</a>';
+
+ return $multi_values;
+}
+
+/**
+ * returns HTML for changing master
+ *
+ * @param String $submitname - submit button name
+ *
+ * @return String HTML code
+ */
+
+function PMA_getHtmlForReplicationChangeMaster($submitname)
+{
+ $html = '';
+ list($username_length, $hostname_length)
+ = PMA_replicationGetUsernameHostnameLength();
+
+ $html .= '<form method="post" action="server_replication.php">';
+ $html .= PMA_URL_getHiddenInputs('', '');
+ $html .= ' <fieldset id="fieldset_add_user_login">';
+ $html .= ' <legend>' . __('Slave configuration');
+ $html .= ' - ' . __('Change or reconfigure master server') . '</legend>';
+ $html .= __(
+ 'Make sure, you have unique server-id in your configuration file (my.cnf). '
+ . 'If not, please add the following line into [mysqld] section:'
+ );
+ $html .= '<br />';
+ $html .= '<pre>server-id=' . time() . '</pre>';
+
+ $html .= PMA_getHtmlForAddUserInputDiv(
+ array('text'=>__('User name:'), 'for'=>"text_username"),
+ array(
+ 'type'=>'text',
+ 'name'=>'username',
+ 'id'=>'text_username',
+ 'maxlength'=>$username_length,
+ 'title'=>__('User name'),
+ 'required'=>'required'
+ )
+ );
+
+ $html .= PMA_getHtmlForAddUserInputDiv(
+ array('text'=>__('Password:'), 'for'=>"text_pma_pw"),
+ array(
+ 'type'=>'password',
+ 'name'=>'pma_pw',
+ 'id'=>'text_pma_pw',
+ 'title'=>__('Password'),
+ 'required'=>'required'
+ )
+ );
+
+ $html .= PMA_getHtmlForAddUserInputDiv(
+ array('text'=>__('Host:'), 'for'=>"text_hostname"),
+ array(
+ 'type'=>'text',
+ 'name'=>'hostname',
+ 'id'=>'text_hostname',
+ 'maxlength'=>$hostname_length,
+ 'value'=>'',
+ 'required'=>'required'
+ )
+ );
+
+ $html .= PMA_getHtmlForAddUserInputDiv(
+ array('text'=>__('Port:'), 'for'=>"text_port"),
+ array(
+ 'type'=>'number',
+ 'name'=>'text_port',
+ 'id'=>'text_port',
+ 'maxlength'=>6,
+ 'value'=>'3306',
+ 'required'=>'required'
+ )
+ );
+
+ $html .= ' </fieldset>';
+ $html .= ' <fieldset id="fieldset_user_privtable_footer" class="tblFooters">';
+ $html .= ' <input type="hidden" name="sr_take_action" value="true" />';
+ $html .= ' <input type="hidden" name="' . $submitname . '" value="1" />';
+ $html .= ' <input type="submit" id="confslave_submit" value="';
+ $html .= __('Go') . '" />';
+ $html .= ' </fieldset>';
+ $html .= '</form>';
+
+ return $html;
+}
+
+/**
+ * returns HTML code for Add user input div
+ *
+ * @param Array $label_array label tag elements
+ * @param Array $input_array input tag elements
+ *
+ * @return String HTML code
+ */
+function PMA_getHtmlForAddUserInputDiv($label_array, $input_array)
+{
+ $html = ' <div class="item">';
+ $html .= ' <label for="' . $label_array['for'] . '">';
+ $html .= $label_array['text'] . '</label>';
+
+ $html .= ' <input ';
+ foreach ($input_array as $key=>$value) {
+ $html .= ' ' . $key . '="' . $value. '" ';
+ }
+ $html .= ' />';
+ $html .= ' </div>';
+ return $html;
+}
+
+/**
+ * This function returns html code for table with replication status.
+ *
+ * @param string $type either master or slave
+ * @param boolean $hidden if true, then default style is set to hidden,
+ * default value false
+ * @param boolean $title if true, then title is displayed, default true
+ *
+ * @return String HTML code
+ */
+function PMA_getHtmlForReplicationStatusTable($type, $hidden = false, $title = true)
+{
+ global ${"{$type}_variables"};
+ global ${"{$type}_variables_alerts"};
+ global ${"{$type}_variables_oks"};
+ global ${"server_{$type}_replication"};
+ global ${"strReplicationStatus_{$type}"};
+
+ $html = '';
+
+ // TODO check the Masters server id?
+ // seems to default to '1' when queried via SHOW VARIABLES ,
+ // but resulted in error on the master when slave connects
+ // [ERROR] Error reading packet from server: Misconfigured master
+ // - server id was not set ( server_errno=1236)
+ // [ERROR] Got fatal error 1236: 'Misconfigured master
+ // - server id was not set' from master when reading data from binary log
+ //
+ //$server_id = $GLOBALS['dbi']->fetchValue(
+ // "SHOW VARIABLES LIKE 'server_id'", 0, 1
+ //);
+
+ $html .= '<div id="replication_' . $type . '_section" style="';
+ $html .= ($hidden ? 'display: none;' : '') . '"> ';
+
+ if ($title) {
+ if ($type == 'master') {
+ $html .= '<h4><a name="replication_' . $type . '"></a>';
+ $html .= __('Master status') . '</h4>';
+ } else {
+ $html .= '<h4><a name="replication_' . $type . '"></a>';
+ $html .= __('Slave status') . '</h4>';
+ }
+ } else {
+ $html .= '<br />';
+ }
+
+ $html .= ' <table id="server' . $type . 'replicationsummary" class="data"> ';
+ $html .= ' <thead>';
+ $html .= ' <tr>';
+ $html .= ' <th>' . __('Variable') . '</th>';
+ $html .= ' <th>' . __('Value') . '</th>';
+ $html .= ' </tr>';
+ $html .= ' </thead>';
+ $html .= ' <tbody>';
+
+ $odd_row = true;
+ foreach (${"{$type}_variables"} as $variable) {
+ $html .= ' <tr class="' . ($odd_row ? 'odd' : 'even') . '">';
+ $html .= ' <td class="name">';
+ $html .= $variable;
+ $html .= ' </td>';
+ $html .= ' <td class="value">';
+
+
+ // TODO change to regexp or something, to allow for negative match
+ if (isset(${"{$type}_variables_alerts"}[$variable])
+ && ${"{$type}_variables_alerts"}[$variable] == ${"server_{$type}_replication"}[0][$variable]
+ ) {
+ $html .= '<span class="attention">';
+
+ } elseif (isset(${"{$type}_variables_oks"}[$variable])
+ && ${"{$type}_variables_oks"}[$variable]
+ == ${"server_{$type}_replication"}[0][$variable]
+ ) {
+ $html .= '<span class="allfine">';
+ } else {
+ $html .= '<span>';
+ }
+ // allow wrapping long table lists into multiple lines
+ static $variables_wrap = array(
+ 'Replicate_Do_DB', 'Replicate_Ignore_DB',
+ 'Replicate_Do_Table', 'Replicate_Ignore_Table',
+ 'Replicate_Wild_Do_Table', 'Replicate_Wild_Ignore_Table');
+ if (in_array($variable, $variables_wrap)) {
+ $html .= str_replace(
+ ',',
+ ', ',
+ ${"server_{$type}_replication"}[0][$variable]
+ );
+ } else {
+ $html .= ${"server_{$type}_replication"}[0][$variable];
+ }
+ $html .= '</span>';
+
+ $html .= ' </td>';
+ $html .= ' </tr>';
+
+ $odd_row = ! $odd_row;
+ }
+
+ $html .= ' </tbody>';
+ $html .= ' </table>';
+ $html .= ' <br />';
+ $html .= '</div>';
+
+ return $html;
+}
+
+/**
+ * returns html code for table with slave users connected to this master
+ *
+ * @param boolean $hidden - if true, then default style is set to hidden,
+ * - default value false
+ *
+ * @return string
+ */
+function PMA_getHtmlForReplicationSlavesTable($hidden = false)
+{
+ $html = '';
+ // Fetch data
+ $data = $GLOBALS['dbi']->fetchResult('SHOW SLAVE HOSTS', null, null);
+
+ $html .= ' <br />';
+ $html .= ' <div id="replication_slaves_section" style="';
+ $html .= ($hidden ? 'display: none;' : '') . '"> ';
+ $html .= ' <table class="data">';
+ $html .= ' <thead>';
+ $html .= ' <tr>';
+ $html .= ' <th>' . __('Server ID') . '</th>';
+ $html .= ' <th>' . __('Host') . '</th>';
+ $html .= ' </tr>';
+ $html .= ' </thead>';
+ $html .= ' <tbody>';
+
+ $odd_row = true;
+ foreach ($data as $slave) {
+ $html .= ' <tr class="' . ($odd_row ? 'odd' : 'even') . '">';
+ $html .= ' <td class="value">' . $slave['Server_id'] . '</td>';
+ $html .= ' <td class="value">' . $slave['Host'] . '</td>';
+ $html .= ' </tr>';
+
+ $odd_row = ! $odd_row;
+ }
+
+ $html .= ' </tbody>';
+ $html .= ' </table>';
+ $html .= ' <br />';
+ $html .= PMA_Message::notice(
+ __(
+ 'Only slaves started with the '
+ . '--report-host=host_name option are visible in this list.'
+ )
+ )->getDisplay();
+ $html .= ' <br />';
+ $html .= ' </div>';
+
+ return $html;
+}
+
+/**
+ * get the correct username and hostname lengths for this MySQL server
+ *
+ * @return array username length, hostname length
+ */
+
+function PMA_replicationGetUsernameHostnameLength()
+{
+ $fields_info = $GLOBALS['dbi']->getColumns('mysql', 'user');
+ $username_length = 16;
+ $hostname_length = 41;
+ foreach ($fields_info as $val) {
+ if ($val['Field'] == 'User') {
+ strtok($val['Type'], '()');
+ $v = strtok('()');
+ if (is_int($v)) {
+ $username_length = $v;
+ }
+ } elseif ($val['Field'] == 'Host') {
+ strtok($val['Type'], '()');
+ $v = strtok('()');
+ if (is_int($v)) {
+ $hostname_length = $v;
+ }
+ }
+ }
+ return array($username_length, $hostname_length);
+}
+
+/**
+ * returns html code to add a replication slave user to the master
+ *
+ * @return String HTML code
+ */
+function PMA_getHtmlForReplicationMasterAddSlaveuser()
+{
+ $html = '';
+ list($username_length, $hostname_length)
+ = PMA_replicationGetUsernameHostnameLength();
+
+ if (isset($_REQUEST['username']) && strlen($_REQUEST['username']) === 0) {
+ $GLOBALS['pred_username'] = 'any';
+ }
+ $html .= '<div id="master_addslaveuser_gui">';
+ $html .= '<form autocomplete="off" method="post" ';
+ $html .= 'action="server_privileges.php"';
+ $html .= ' onsubmit="return checkAddUser(this);">';
+ $html .= PMA_URL_getHiddenInputs('', '');
+ $html .= '<fieldset id="fieldset_add_user_login">'
+ . '<legend>' . __('Add slave replication user') . '</legend>'
+ . PMA_getHtmlForAddUserLoginForm($username_length)
+ . '<div class="item">'
+ . '<label for="select_pred_hostname">'
+ . ' ' . __('Host:')
+ . '</label>'
+ . '<span class="options">'
+ . ' <select name="pred_hostname" id="select_pred_hostname" title="'
+ . __('Host') . '"';
+
+ $_current_user = $GLOBALS['dbi']->fetchValue('SELECT USER();');
+ if (! empty($_current_user)) {
+ $thishost = str_replace(
+ "'",
+ '',
+ substr($_current_user, (strrpos($_current_user, '@') + 1))
+ );
+ if ($thishost == 'localhost' || $thishost == '127.0.0.1') {
+ unset($thishost);
+ }
+ }
+ $html .= ' onchange="if (this.value == \'any\') { hostname.value = \'%\'; } '
+ . 'else if (this.value == \'localhost\') '
+ . '{ hostname.value = \'localhost\'; } '
+ . (empty($thishost)
+ ? ''
+ : 'else if (this.value == \'thishost\') { hostname.value = \''
+ . addslashes(htmlspecialchars($thishost)) . '\'; } ')
+ . 'else if (this.value == \'hosttable\') { hostname.value = \'\'; } '
+ . 'else if (this.value == \'userdefined\') '
+ . '{ hostname.focus(); hostname.select(); }">'
+ . "\n";
+ unset($_current_user);
+
+ // when we start editing a user, $GLOBALS['pred_hostname'] is not defined
+ if (! isset($GLOBALS['pred_hostname']) && isset($_REQUEST['hostname'])) {
+ switch (strtolower($_REQUEST['hostname'])) {
+ case 'localhost':
+ case '127.0.0.1':
+ $GLOBALS['pred_hostname'] = 'localhost';
+ break;
+ case '%':
+ $GLOBALS['pred_hostname'] = 'any';
+ break;
+ default:
+ $GLOBALS['pred_hostname'] = 'userdefined';
+ break;
+ }
+ }
+ $html .= ' <option value="any"'
+ . ((isset($GLOBALS['pred_hostname']) && $GLOBALS['pred_hostname'] == 'any')
+ ? ' selected="selected"' : '') . '>' . __('Any host')
+ . '</option>'
+ . ' <option value="localhost"'
+ . ((isset($GLOBALS['pred_hostname'])
+ && $GLOBALS['pred_hostname'] == 'localhost')
+ ? ' selected="selected"' : '') . '>' . __('Local')
+ . '</option>';
+
+ if (!empty($thishost)) {
+ $html .= ' <option value="thishost"'
+ . ((isset($GLOBALS['pred_hostname'])
+ && $GLOBALS['pred_hostname'] == 'thishost')
+ ? ' selected="selected"' : '') . '>' . __('This Host')
+ . '</option>';
+ }
+ unset($thishost);
+
+ $html .= PMA_getHtmlForTableInfoForm($hostname_length);
+ $html .= '</form>';
+ $html .= '</div>';
+
+ return $html;
+}
+/**
+ * returns html code to add a replication slave user to the master
+ *
+ * @param int $username_length Username length
+ *
+ * @return String HTML code
+ */
+function PMA_getHtmlForAddUserLoginForm($username_length)
+{
+ $html = '<input type="hidden" name="grant_count" value="25" />'
+ . '<input type="hidden" name="createdb" id="createdb_0" value="0" />'
+ . '<input id="checkbox_Repl_slave_priv" type="hidden"'
+ . ' title="Needed for the replication slaves." '
+ . 'value="Y" name="Repl_slave_priv"/>'
+ . '<input id="checkbox_Repl_client_priv" type="hidden" '
+ . 'title="Needed for the replication slaves."'
+ . ' value="Y" name="Repl_client_priv"/> '
+ . '<input type="hidden" name="sr_take_action" value="true" />'
+ . '<div class="item">'
+ . '<label for="select_pred_username">'
+ . ' ' . __('User name:')
+ . '</label>'
+ . '<span class="options">'
+ . ' <select name="pred_username" id="select_pred_username" '
+ . 'title="' . __('User name') . '"'
+ . ' onchange="if (this.value == \'any\') { username.value = \'\'; } '
+ . 'else if (this.value == \'userdefined\') { '
+ . ' username.focus(); username.select(); }">'
+ . ' <option value="any"'
+ . ((isset($GLOBALS['pred_username'])
+ && $GLOBALS['pred_username'] == 'any') ? ' selected="selected"' : '')
+ . '>' . __('Any user') . '</option>'
+ . ' <option value="userdefined"'
+ . ((! isset($GLOBALS['pred_username'])
+ || $GLOBALS['pred_username'] == 'userdefined')
+ ? ' selected="selected"' : '')
+ . '>' . __('Use text field:') . '</option>'
+ . ' </select>'
+ . '</span>'
+ . '<input type="text" name="username" maxlength="'
+ . $username_length . '" title="' . __('User name') . '"'
+ . (empty($_REQUEST['username']) ? '' : ' value="'
+ . (isset($GLOBALS['new_username'])
+ ? $GLOBALS['new_username']
+ : $_REQUEST['username']) . '"')
+ . ' onchange="pred_username.value = \'userdefined\';" />'
+ . '</div>';
+
+ return $html;
+}
+
+/**
+ * returns HTML for TableInfoForm
+ *
+ * @param int $hostname_length Selected hostname length
+ *
+ * @return String HTML code
+ */
+function PMA_getHtmlForTableInfoForm($hostname_length)
+{
+ $html = ' <option value="hosttable"'
+ . ((isset($GLOBALS['pred_hostname'])
+ && $GLOBALS['pred_hostname'] == 'hosttable')
+ ? ' selected="selected"' : '') . '>' . __('Use Host Table')
+ . '</option>'
+ . ' <option value="userdefined"'
+ . ((isset($GLOBALS['pred_hostname'])
+ && $GLOBALS['pred_hostname'] == 'userdefined')
+ ? ' selected="selected"' : '')
+ . '>' . __('Use text field:') . '</option>'
+ . ' </select>'
+ . '</span>'
+ . '<input type="text" name="hostname" maxlength="'
+ . $hostname_length . '" value="'
+ . (isset($_REQUEST['hostname']) ? $_REQUEST['hostname'] : '')
+ . '" title="' . __('Host')
+ . '" onchange="pred_hostname.value = \'userdefined\';" />'
+ . PMA_Util::showHint(
+ __(
+ 'When Host table is used, this field is ignored '
+ . 'and values stored in Host table are used instead.'
+ )
+ )
+ . '</div>'
+ . '<div class="item">'
+ . '<label for="select_pred_password">'
+ . ' ' . __('Password:')
+ . '</label>'
+ . '<span class="options">'
+ . ' <select name="pred_password" id="select_pred_password" title="'
+ . __('Password') . '"'
+ . ' onchange="if (this.value == \'none\') '
+ . '{ pma_pw.value = \'\'; pma_pw2.value = \'\'; } '
+ . 'else if (this.value == \'userdefined\') '
+ . '{ pma_pw.focus(); pma_pw.select(); }">'
+ . ' <option value="none"';
+ if (isset($_REQUEST['username'])) {
+ $html .= ' selected="selected"';
+ }
+ $html .= '>' . __('No Password') . '</option>'
+ . ' <option value="userdefined"'
+ . (isset($_REQUEST['username']) ? '' : ' selected="selected"')
+ . '>' . __('Use text field:') . '</option>'
+ . ' </select>'
+ . '</span>'
+ . '<input type="password" id="text_pma_pw" name="pma_pw" title="'
+ . __('Password') . '" onchange="pred_password.value = \'userdefined\';" />'
+ . '</div>'
+ . '<div class="item">'
+ . '<label for="text_pma_pw2">'
+ . ' ' . __('Re-type:')
+ . '</label>'
+ . '<span class="options">&nbsp;</span>'
+ . '<input type="password" name="pma_pw2" id="text_pma_pw2" title="'
+ . __('Re-type') . '" onchange="pred_password.value = \'userdefined\';" />'
+ . '</div>'
+ . '<div class="item">'
+ . '<label for="button_generate_password">'
+ . ' ' . __('Generate Password:')
+ . '</label>'
+ . '<span class="options">'
+ . ' <input type="button" class="button" '
+ . 'id="button_generate_password" value="' . __('Generate')
+ . '" onclick="suggestPassword(this.form)" />'
+ . '</span>'
+ . '<input type="text" name="generated_pw" id="generated_pw" />'
+ . '</div>'
+ . '</fieldset>';
+ $html .= '<fieldset id="fieldset_user_privtable_footer" class="tblFooters">'
+ . ' <input type="hidden" name="adduser_submit" value="1" />'
+ . ' <input type="submit" id="adduser_submit" value="' . __('Go') . '" />'
+ . '</fieldset>';
+ return $html;
+}
+
+/**
+ * handle control requests
+ *
+ * @return NULL
+ */
+function PMA_handleControlRequest()
+{
+ if (isset($_REQUEST['sr_take_action'])) {
+ $refresh = false;
+ $result = null;
+ $messageSuccess = null;
+ $messageError = null;
+
+ if (isset($_REQUEST['slave_changemaster'])) {
+ $result = PMA_handleRequestForSlaveChangeMaster();
+ } elseif (isset($_REQUEST['sr_slave_server_control'])) {
+ $result = PMA_handleRequestForSlaveServerControl();
+ $refresh = true;
+
+ switch ($_REQUEST['sr_slave_action']) {
+ case 'start':
+ $messageSuccess = __('Replication started successfully.');
+ $messageError = __('Error starting replication.');
+ break;
+
+ case 'stop':
+ $messageSuccess = __('Replication stopped successfully.');
+ $messageError = __('Error stopping replication.');
+ break;
+
+ case 'reset':
+ $messageSuccess = __('Replication resetting successfully.');
+ $messageError = __('Error resetting replication.');
+ break;
+
+ default:
+ $messageSuccess = __('Success.');
+ $messageError = __('Error.');
+ break;
+ }
+ } elseif (isset($_REQUEST['sr_slave_skip_error'])) {
+ $result = PMA_handleRequestForSlaveSkipError();
+ }
+
+ if ($refresh) {
+ $response = PMA_Response::getInstance();
+ if ($response->isAjax()) {
+ $response->isSuccess($result);
+ $response->addJSON(
+ 'message',
+ $result ? PMA_Message::success($messageSuccess) : PMA_Message::error($messageError)
+ );
+ } else {
+ PMA_sendHeaderLocation(
+ $GLOBALS['cfg']['PmaAbsoluteUri'] . 'server_replication.php'
+ . PMA_URL_getCommon($GLOBALS['url_params'], '&')
+ );
+ }
+ }
+ unset($refresh);
+ }
+}
+/**
+ * handle control requests for Slave Change Master
+ *
+ * @return boolean
+ */
+function PMA_handleRequestForSlaveChangeMaster()
+{
+ $_SESSION['replication']['m_username'] = $sr['username']
+ = PMA_Util::sqlAddSlashes($_REQUEST['username']);
+ $_SESSION['replication']['m_password'] = $sr['pma_pw']
+ = PMA_Util::sqlAddSlashes($_REQUEST['pma_pw']);
+ $_SESSION['replication']['m_hostname'] = $sr['hostname']
+ = PMA_Util::sqlAddSlashes($_REQUEST['hostname']);
+ $_SESSION['replication']['m_port'] = $sr['port']
+ = PMA_Util::sqlAddSlashes($_REQUEST['port']);
+ $_SESSION['replication']['m_correct'] = '';
+ $_SESSION['replication']['sr_action_status'] = 'error';
+ $_SESSION['replication']['sr_action_info'] = __('Unknown error');
+
+ // Attempt to connect to the new master server
+ $link_to_master = PMA_Replication_connectToMaster(
+ $sr['username'], $sr['pma_pw'], $sr['hostname'], $sr['port']
+ );
+
+ if (! $link_to_master) {
+ $_SESSION['replication']['sr_action_status'] = 'error';
+ $_SESSION['replication']['sr_action_info'] = sprintf(
+ __('Unable to connect to master %s.'),
+ htmlspecialchars($sr['hostname'])
+ );
+ } else {
+ // Read the current master position
+ $position = PMA_Replication_Slave_binLogMaster($link_to_master);
+
+ if (empty($position)) {
+ $_SESSION['replication']['sr_action_status'] = 'error';
+ $_SESSION['replication']['sr_action_info']
+ = __(
+ 'Unable to read master log position. '
+ . 'Possible privilege problem on master.'
+ );
+ } else {
+ $_SESSION['replication']['m_correct'] = true;
+
+ if (! PMA_Replication_Slave_changeMaster(
+ $sr['username'],
+ $sr['pma_pw'],
+ $sr['hostname'],
+ $sr['port'],
+ $position,
+ true,
+ false
+ )
+ ) {
+ $_SESSION['replication']['sr_action_status'] = 'error';
+ $_SESSION['replication']['sr_action_info']
+ = __('Unable to change master');
+ } else {
+ $_SESSION['replication']['sr_action_status'] = 'success';
+ $_SESSION['replication']['sr_action_info'] = sprintf(
+ __('Master server changed successfully to %s'),
+ htmlspecialchars($sr['hostname'])
+ );
+ }
+ }
+ }
+
+ return $_SESSION['replication']['sr_action_status'] === 'success';
+}
+
+/**
+ * handle control requests for Slave Server Control
+ *
+ * @return boolean
+ */
+function PMA_handleRequestForSlaveServerControl()
+{
+ if (empty($_REQUEST['sr_slave_control_parm'])) {
+ $_REQUEST['sr_slave_control_parm'] = null;
+ }
+ if ($_REQUEST['sr_slave_action'] == 'reset') {
+ $qStop = PMA_Replication_Slave_control("STOP");
+ $qReset = $GLOBALS['dbi']->tryQuery("RESET SLAVE;");
+ $qStart = PMA_Replication_Slave_control("START");
+
+ $result = ($qStop !== false && $qStop !== -1 &&
+ $qReset !== false && $qReset !== -1 &&
+ $qStart !== false && $qStart !== -1);
+ } else {
+ $qControl = PMA_Replication_Slave_control(
+ $_REQUEST['sr_slave_action'],
+ $_REQUEST['sr_slave_control_parm']
+ );
+
+ $result = ($qControl !== false && $qControl !== -1);
+ }
+
+ return $result;
+}
+
+/**
+ * handle control requests for Slave Skip Error
+ *
+ * @return boolean
+ */
+function PMA_handleRequestForSlaveSkipError()
+{
+ $count = 1;
+ if (isset($_REQUEST['sr_skip_errors_count'])) {
+ $count = $_REQUEST['sr_skip_errors_count'] * 1;
+ }
+
+ $qStop = PMA_Replication_Slave_control("STOP");
+ $qSkip = $GLOBALS['dbi']->tryQuery("SET GLOBAL SQL_SLAVE_SKIP_COUNTER = ".$count.";");
+ $qStart = PMA_Replication_Slave_control("START");
+
+ $result = ($qStop !== false && $qStop !== -1 &&
+ $qSkip !== false && $qSkip !== -1 &&
+ $qStart !== false && $qStart !== -1);
+
+ return $result;
+}
+?>
diff --git a/libraries/rte/rte_events.lib.php b/libraries/rte/rte_events.lib.php
new file mode 100644
index 0000000000..77065970f2
--- /dev/null
+++ b/libraries/rte/rte_events.lib.php
@@ -0,0 +1,637 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Functions for event management.
+ *
+ * @package PhpMyAdmin
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ * Sets required globals
+ *
+ * @return void
+ */
+function PMA_EVN_setGlobals()
+{
+ global $event_status, $event_type, $event_interval;
+
+ $event_status = array(
+ 'query' => array('ENABLE',
+ 'DISABLE',
+ 'DISABLE ON SLAVE'),
+ 'display' => array('ENABLED',
+ 'DISABLED',
+ 'SLAVESIDE_DISABLED')
+ );
+ $event_type = array('RECURRING',
+ 'ONE TIME');
+ $event_interval = array('YEAR',
+ 'QUARTER',
+ 'MONTH',
+ 'DAY',
+ 'HOUR',
+ 'MINUTE',
+ 'WEEK',
+ 'SECOND',
+ 'YEAR_MONTH',
+ 'DAY_HOUR',
+ 'DAY_MINUTE',
+ 'DAY_SECOND',
+ 'HOUR_MINUTE',
+ 'HOUR_SECOND',
+ 'MINUTE_SECOND');
+}
+
+/**
+ * Main function for the events functionality
+ *
+ * @return void
+ */
+function PMA_EVN_main()
+{
+ global $db;
+
+ PMA_EVN_setGlobals();
+ /**
+ * Process all requests
+ */
+ PMA_EVN_handleEditor();
+ PMA_EVN_handleExport();
+ /**
+ * Display a list of available events
+ */
+ $columns = "`EVENT_NAME`, `EVENT_TYPE`, `STATUS`";
+ $where = "EVENT_SCHEMA='" . PMA_Util::sqlAddSlashes($db) . "'";
+ $query = "SELECT $columns FROM `INFORMATION_SCHEMA`.`EVENTS` "
+ . "WHERE $where ORDER BY `EVENT_NAME` ASC;";
+ $items = $GLOBALS['dbi']->fetchResult($query);
+ echo PMA_RTE_getList('event', $items);
+ /**
+ * Display a link for adding a new event, if
+ * the user has the privileges and a link to
+ * toggle the state of the event scheduler.
+ */
+ echo PMA_EVN_getFooterLinks();
+} // end PMA_EVN_main()
+
+/**
+ * Handles editor requests for adding or editing an item
+ *
+ * @return void
+ */
+function PMA_EVN_handleEditor()
+{
+ global $_REQUEST, $_POST, $errors, $db;
+
+ if (! empty($_REQUEST['editor_process_add'])
+ || ! empty($_REQUEST['editor_process_edit'])
+ ) {
+ $sql_query = '';
+
+ $item_query = PMA_EVN_getQueryFromRequest();
+
+ if (! count($errors)) { // set by PMA_RTN_getQueryFromRequest()
+ // Execute the created query
+ if (! empty($_REQUEST['editor_process_edit'])) {
+ // Backup the old trigger, in case something goes wrong
+ $create_item = $GLOBALS['dbi']->getDefinition(
+ $db,
+ 'EVENT',
+ $_REQUEST['item_original_name']
+ );
+ $drop_item = "DROP EVENT "
+ . PMA_Util::backquote($_REQUEST['item_original_name']) . ";\n";
+ $result = $GLOBALS['dbi']->tryQuery($drop_item);
+ if (! $result) {
+ $errors[] = sprintf(
+ __('The following query has failed: "%s"'),
+ htmlspecialchars($drop_item)
+ )
+ . '<br />'
+ . __('MySQL said: ') . $GLOBALS['dbi']->getError(null);
+ } else {
+ $result = $GLOBALS['dbi']->tryQuery($item_query);
+ if (! $result) {
+ $errors[] = sprintf(
+ __('The following query has failed: "%s"'),
+ htmlspecialchars($item_query)
+ )
+ . '<br />'
+ . __('MySQL said: ') . $GLOBALS['dbi']->getError(null);
+ // We dropped the old item, but were unable to create
+ // the new one. Try to restore the backup query
+ $result = $GLOBALS['dbi']->tryQuery($create_item);
+ if (! $result) {
+ // OMG, this is really bad! We dropped the query,
+ // failed to create a new one
+ // and now even the backup query does not execute!
+ // This should not happen, but we better handle
+ // this just in case.
+ $errors[] = __(
+ 'Sorry, we failed to restore the dropped event.'
+ )
+ . '<br />'
+ . __('The backed up query was:')
+ . "\"" . htmlspecialchars($create_item) . "\""
+ . '<br />'
+ . __('MySQL said: ') . $GLOBALS['dbi']->getError(null);
+ }
+ } else {
+ $message = PMA_Message::success(
+ __('Event %1$s has been modified.')
+ );
+ $message->addParam(
+ PMA_Util::backquote($_REQUEST['item_name'])
+ );
+ $sql_query = $drop_item . $item_query;
+ }
+ }
+ } else {
+ // 'Add a new item' mode
+ $result = $GLOBALS['dbi']->tryQuery($item_query);
+ if (! $result) {
+ $errors[] = sprintf(
+ __('The following query has failed: "%s"'),
+ htmlspecialchars($item_query)
+ )
+ . '<br /><br />'
+ . __('MySQL said: ') . $GLOBALS['dbi']->getError(null);
+ } else {
+ $message = PMA_Message::success(
+ __('Event %1$s has been created.')
+ );
+ $message->addParam(
+ PMA_Util::backquote($_REQUEST['item_name'])
+ );
+ $sql_query = $item_query;
+ }
+ }
+ }
+
+ if (count($errors)) {
+ $message = PMA_Message::error(__('<b>One or more errors have occurred while processing your request:</b>'));
+ $message->addString('<ul>');
+ foreach ($errors as $string) {
+ $message->addString('<li>' . $string . '</li>');
+ }
+ $message->addString('</ul>');
+ }
+
+ $output = PMA_Util::getMessage($message, $sql_query);
+ if ($GLOBALS['is_ajax_request']) {
+ $response = PMA_Response::getInstance();
+ if ($message->isSuccess()) {
+ $columns = "`EVENT_NAME`, `EVENT_TYPE`, `STATUS`";
+ $where = "EVENT_SCHEMA='" . PMA_Util::sqlAddSlashes($db) . "' "
+ . "AND EVENT_NAME='"
+ . PMA_Util::sqlAddSlashes($_REQUEST['item_name']) . "'";
+ $query = "SELECT " . $columns
+ . " FROM `INFORMATION_SCHEMA`.`EVENTS` WHERE " . $where. ";";
+ $event = $GLOBALS['dbi']->fetchSingleRow($query);
+ $response->addJSON(
+ 'name',
+ htmlspecialchars(strtoupper($_REQUEST['item_name']))
+ );
+ $response->addJSON('new_row', PMA_EVN_getRowForList($event));
+ $response->addJSON('insert', ! empty($event));
+ $response->addJSON('message', $output);
+ } else {
+ $response->isSuccess(false);
+ $response->addJSON('message', $message);
+ }
+ exit;
+ }
+ }
+ /**
+ * Display a form used to add/edit a trigger, if necessary
+ */
+ if (count($errors)
+ || (empty($_REQUEST['editor_process_add'])
+ && empty($_REQUEST['editor_process_edit'])
+ && (! empty($_REQUEST['add_item'])
+ || ! empty($_REQUEST['edit_item'])
+ || ! empty($_REQUEST['item_changetype'])))
+ ) { // FIXME: this must be simpler than that
+ $operation = '';
+ if (! empty($_REQUEST['item_changetype'])) {
+ $operation = 'change';
+ }
+ // Get the data for the form (if any)
+ if (! empty($_REQUEST['add_item'])) {
+ $title = PMA_RTE_getWord('add');
+ $item = PMA_EVN_getDataFromRequest();
+ $mode = 'add';
+ } else if (! empty($_REQUEST['edit_item'])) {
+ $title = __("Edit event");
+ if (! empty($_REQUEST['item_name'])
+ && empty($_REQUEST['editor_process_edit'])
+ && empty($_REQUEST['item_changetype'])
+ ) {
+ $item = PMA_EVN_getDataFromName($_REQUEST['item_name']);
+ if ($item !== false) {
+ $item['item_original_name'] = $item['item_name'];
+ }
+ } else {
+ $item = PMA_EVN_getDataFromRequest();
+ }
+ $mode = 'edit';
+ }
+ if ($item !== false) {
+ // Show form
+ $editor = PMA_EVN_getEditorForm($mode, $operation, $item);
+ if ($GLOBALS['is_ajax_request']) {
+ $response = PMA_Response::getInstance();
+ $response->addJSON('message', $editor);
+ $response->addJSON('title', $title);
+ } else {
+ echo "\n\n<h2>$title</h2>\n\n$editor";
+ unset($_POST);
+ }
+ exit;
+ } else {
+ $message = __('Error in processing request:') . ' ';
+ $message .= sprintf(
+ PMA_RTE_getWord('not_found'),
+ htmlspecialchars(PMA_Util::backquote($_REQUEST['item_name'])),
+ htmlspecialchars(PMA_Util::backquote($db))
+ );
+ $message = PMA_message::error($message);
+ if ($GLOBALS['is_ajax_request']) {
+ $response = PMA_Response::getInstance();
+ $response->isSuccess(false);
+ $response->addJSON('message', $message);
+ exit;
+ } else {
+ $message->display();
+ }
+ }
+ }
+} // end PMA_EVN_handleEditor()
+
+/**
+ * This function will generate the values that are required to for the editor
+ *
+ * @return array Data necessary to create the editor.
+ */
+function PMA_EVN_getDataFromRequest()
+{
+ $retval = array();
+ $indices = array('item_name',
+ 'item_original_name',
+ 'item_status',
+ 'item_execute_at',
+ 'item_interval_value',
+ 'item_interval_field',
+ 'item_starts',
+ 'item_ends',
+ 'item_definition',
+ 'item_preserve',
+ 'item_comment',
+ 'item_definer');
+ foreach ($indices as $index) {
+ $retval[$index] = isset($_REQUEST[$index]) ? $_REQUEST[$index] : '';
+ }
+ $retval['item_type'] = 'ONE TIME';
+ $retval['item_type_toggle'] = 'RECURRING';
+ if (isset($_REQUEST['item_type']) && $_REQUEST['item_type'] == 'RECURRING') {
+ $retval['item_type'] = 'RECURRING';
+ $retval['item_type_toggle'] = 'ONE TIME';
+ }
+ return $retval;
+} // end PMA_EVN_getDataFromRequest()
+
+/**
+ * This function will generate the values that are required to complete
+ * the "Edit event" form given the name of a event.
+ *
+ * @param string $name The name of the event.
+ *
+ * @return array Data necessary to create the editor.
+ */
+function PMA_EVN_getDataFromName($name)
+{
+ global $db;
+
+ $retval = array();
+ $columns = "`EVENT_NAME`, `STATUS`, `EVENT_TYPE`, `EXECUTE_AT`, "
+ . "`INTERVAL_VALUE`, `INTERVAL_FIELD`, `STARTS`, `ENDS`, "
+ . "`EVENT_DEFINITION`, `ON_COMPLETION`, `DEFINER`, `EVENT_COMMENT`";
+ $where = "EVENT_SCHEMA='" . PMA_Util::sqlAddSlashes($db) . "' "
+ . "AND EVENT_NAME='" . PMA_Util::sqlAddSlashes($name) . "'";
+ $query = "SELECT $columns FROM `INFORMATION_SCHEMA`.`EVENTS` WHERE $where;";
+ $item = $GLOBALS['dbi']->fetchSingleRow($query);
+ if (! $item) {
+ return false;
+ }
+ $retval['item_name'] = $item['EVENT_NAME'];
+ $retval['item_status'] = $item['STATUS'];
+ $retval['item_type'] = $item['EVENT_TYPE'];
+ if ($retval['item_type'] == 'RECURRING') {
+ $retval['item_type_toggle'] = 'ONE TIME';
+ } else {
+ $retval['item_type_toggle'] = 'RECURRING';
+ }
+ $retval['item_execute_at'] = $item['EXECUTE_AT'];
+ $retval['item_interval_value'] = $item['INTERVAL_VALUE'];
+ $retval['item_interval_field'] = $item['INTERVAL_FIELD'];
+ $retval['item_starts'] = $item['STARTS'];
+ $retval['item_ends'] = $item['ENDS'];
+ $retval['item_preserve'] = '';
+ if ($item['ON_COMPLETION'] == 'PRESERVE') {
+ $retval['item_preserve'] = " checked='checked'";
+ }
+ $retval['item_definition'] = $item['EVENT_DEFINITION'];
+ $retval['item_definer'] = $item['DEFINER'];
+ $retval['item_comment'] = $item['EVENT_COMMENT'];
+
+ return $retval;
+} // end PMA_EVN_getDataFromName()
+
+/**
+ * Displays a form used to add/edit an event
+ *
+ * @param string $mode If the editor will be used edit an event
+ * or add a new one: 'edit' or 'add'.
+ * @param string $operation If the editor was previously invoked with
+ * JS turned off, this will hold the name of
+ * the current operation
+ * @param array $item Data for the event returned by
+ * PMA_EVN_getDataFromRequest() or
+ * PMA_EVN_getDataFromName()
+ *
+ * @return string HTML code for the editor.
+ */
+function PMA_EVN_getEditorForm($mode, $operation, $item)
+{
+ global $db, $table, $event_status, $event_type, $event_interval;
+
+ // Escape special characters
+ $need_escape = array(
+ 'item_original_name',
+ 'item_name',
+ 'item_type',
+ 'item_execute_at',
+ 'item_interval_value',
+ 'item_starts',
+ 'item_ends',
+ 'item_definition',
+ 'item_definer',
+ 'item_comment'
+ );
+ foreach ($need_escape as $index) {
+ $item[$index] = htmlentities($item[$index], ENT_QUOTES);
+ }
+ $original_data = '';
+ if ($mode == 'edit') {
+ $original_data = "<input name='item_original_name' "
+ . "type='hidden' value='{$item['item_original_name']}'/>\n";
+ }
+ // Handle some logic first
+ if ($operation == 'change') {
+ if ($item['item_type'] == 'RECURRING') {
+ $item['item_type'] = 'ONE TIME';
+ $item['item_type_toggle'] = 'RECURRING';
+ } else {
+ $item['item_type'] = 'RECURRING';
+ $item['item_type_toggle'] = 'ONE TIME';
+ }
+ }
+ if ($item['item_type'] == 'ONE TIME') {
+ $isrecurring_class = ' hide';
+ $isonetime_class = '';
+ } else {
+ $isrecurring_class = '';
+ $isonetime_class = ' hide';
+ }
+ // Create the output
+ $retval = "";
+ $retval .= "<!-- START " . strtoupper($mode) . " EVENT FORM -->\n\n";
+ $retval .= "<form class='rte_form' action='db_events.php' method='post'>\n";
+ $retval .= "<input name='{$mode}_item' type='hidden' value='1' />\n";
+ $retval .= $original_data;
+ $retval .= PMA_URL_getHiddenInputs($db, $table) . "\n";
+ $retval .= "<fieldset>\n";
+ $retval .= "<legend>" . __('Details') . "</legend>\n";
+ $retval .= "<table class='rte_table' style='width: 100%'>\n";
+ $retval .= "<tr>\n";
+ $retval .= " <td style='width: 20%;'>" . __('Event name') . "</td>\n";
+ $retval .= " <td><input type='text' name='item_name' \n";
+ $retval .= " value='{$item['item_name']}'\n";
+ $retval .= " maxlength='64' /></td>\n";
+ $retval .= "</tr>\n";
+ $retval .= "<tr>\n";
+ $retval .= " <td>" . __('Status') . "</td>\n";
+ $retval .= " <td>\n";
+ $retval .= " <select name='item_status'>\n";
+ foreach ($event_status['display'] as $key => $value) {
+ $selected = "";
+ if (! empty($item['item_status']) && $item['item_status'] == $value) {
+ $selected = " selected='selected'";
+ }
+ $retval .= "<option$selected>$value</option>";
+ }
+ $retval .= " </select>\n";
+ $retval .= " </td>\n";
+ $retval .= "</tr>\n";
+
+ $retval .= "<tr>\n";
+ $retval .= " <td>" . __('Event type') . "</td>\n";
+ $retval .= " <td>\n";
+ if ($GLOBALS['is_ajax_request']) {
+ $retval .= " <select name='item_type'>";
+ foreach ($event_type as $key => $value) {
+ $selected = "";
+ if (! empty($item['item_type']) && $item['item_type'] == $value) {
+ $selected = " selected='selected'";
+ }
+ $retval .= "<option$selected>$value</option>";
+ }
+ $retval .= " </select>\n";
+ } else {
+ $retval .= " <input name='item_type' type='hidden' \n";
+ $retval .= " value='{$item['item_type']}' />\n";
+ $retval .= " <div style='width: 49%; float: left; text-align: center;"
+ . " font-weight: bold;'>\n";
+ $retval .= " {$item['item_type']}\n";
+ $retval .= " </div>\n";
+ $retval .= " <input style='width: 49%;' type='submit'\n";
+ $retval .= " name='item_changetype'\n";
+ $retval .= " value='";
+ $retval .= sprintf(__('Change to %s'), $item['item_type_toggle']);
+ $retval .= "' />\n";
+ }
+ $retval .= " </td>\n";
+ $retval .= "</tr>\n";
+ $retval .= "<tr class='onetime_event_row $isonetime_class'>\n";
+ $retval .= " <td>" . __('Execute at') . "</td>\n";
+ $retval .= " <td class='nowrap'>\n";
+ $retval .= " <input type='text' name='item_execute_at'\n";
+ $retval .= " value='{$item['item_execute_at']}'\n";
+ $retval .= " class='datetimefield' />\n";
+ $retval .= " </td>\n";
+ $retval .= "</tr>\n";
+ $retval .= "<tr class='recurring_event_row $isrecurring_class'>\n";
+ $retval .= " <td>" . __('Execute every') . "</td>\n";
+ $retval .= " <td>\n";
+ $retval .= " <input style='width: 49%;' type='text'\n";
+ $retval .= " name='item_interval_value'\n";
+ $retval .= " value='{$item['item_interval_value']}' />\n";
+ $retval .= " <select style='width: 49%;' name='item_interval_field'>";
+ foreach ($event_interval as $key => $value) {
+ $selected = "";
+ if (! empty($item['item_interval_field'])
+ && $item['item_interval_field'] == $value
+ ) {
+ $selected = " selected='selected'";
+ }
+ $retval .= "<option$selected>$value</option>";
+ }
+ $retval .= " </select>\n";
+ $retval .= " </td>\n";
+ $retval .= "</tr>\n";
+ $retval .= "<tr class='recurring_event_row$isrecurring_class'>\n";
+ $retval .= " <td>" . _pgettext('Start of recurring event', 'Start');
+ $retval .= " </td>\n";
+ $retval .= " <td class='nowrap'>\n";
+ $retval .= " <input type='text'\n name='item_starts'\n";
+ $retval .= " value='{$item['item_starts']}'\n";
+ $retval .= " class='datetimefield' />\n";
+ $retval .= " </td>\n";
+ $retval .= "</tr>\n";
+ $retval .= "<tr class='recurring_event_row$isrecurring_class'>\n";
+ $retval .= " <td>" . _pgettext('End of recurring event', 'End') . "</td>\n";
+ $retval .= " <td class='nowrap'>\n";
+ $retval .= " <input type='text' name='item_ends'\n";
+ $retval .= " value='{$item['item_ends']}'\n";
+ $retval .= " class='datetimefield' />\n";
+ $retval .= " </td>\n";
+ $retval .= "</tr>\n";
+ $retval .= "<tr>\n";
+ $retval .= " <td>" . __('Definition') . "</td>\n";
+ $retval .= " <td><textarea name='item_definition' rows='15' cols='40'>";
+ $retval .= $item['item_definition'];
+ $retval .= "</textarea></td>\n";
+ $retval .= "</tr>\n";
+ $retval .= "<tr>\n";
+ $retval .= " <td>" . __('On completion preserve') . "</td>\n";
+ $retval .= " <td><input type='checkbox'\n";
+ $retval .= " name='item_preserve'{$item['item_preserve']} /></td>\n";
+ $retval .= "</tr>\n";
+ $retval .= "<tr>\n";
+ $retval .= " <td>" . __('Definer') . "</td>\n";
+ $retval .= " <td><input type='text' name='item_definer'\n";
+ $retval .= " value='{$item['item_definer']}' /></td>\n";
+ $retval .= "</tr>\n";
+ $retval .= "<tr>\n";
+ $retval .= " <td>" . __('Comment') . "</td>\n";
+ $retval .= " <td><input type='text' name='item_comment' maxlength='64'\n";
+ $retval .= " value='{$item['item_comment']}' /></td>\n";
+ $retval .= "</tr>\n";
+ $retval .= "</table>\n";
+ $retval .= "</fieldset>\n";
+ if ($GLOBALS['is_ajax_request']) {
+ $retval .= "<input type='hidden' name='editor_process_{$mode}'\n";
+ $retval .= " value='true' />\n";
+ $retval .= "<input type='hidden' name='ajax_request' value='true' />\n";
+ } else {
+ $retval .= "<fieldset class='tblFooters'>\n";
+ $retval .= " <input type='submit' name='editor_process_{$mode}'\n";
+ $retval .= " value='" . __('Go') . "' />\n";
+ $retval .= "</fieldset>\n";
+ }
+ $retval .= "</form>\n\n";
+ $retval .= "<!-- END " . strtoupper($mode) . " EVENT FORM -->\n\n";
+
+ return $retval;
+} // end PMA_EVN_getEditorForm()
+
+/**
+ * Composes the query necessary to create an event from an HTTP request.
+ *
+ * @return string The CREATE EVENT query.
+ */
+function PMA_EVN_getQueryFromRequest()
+{
+ global $_REQUEST, $errors, $event_status, $event_type, $event_interval;
+
+ $query = 'CREATE ';
+ if (! empty($_REQUEST['item_definer'])) {
+ if (strpos($_REQUEST['item_definer'], '@') !== false) {
+ $arr = explode('@', $_REQUEST['item_definer']);
+ $query .= 'DEFINER=' . PMA_Util::backquote($arr[0]);
+ $query .= '@' . PMA_Util::backquote($arr[1]) . ' ';
+ } else {
+ $errors[] = __('The definer must be in the "username@hostname" format');
+ }
+ }
+ $query .= 'EVENT ';
+ if (! empty($_REQUEST['item_name'])) {
+ $query .= PMA_Util::backquote($_REQUEST['item_name']) . ' ';
+ } else {
+ $errors[] = __('You must provide an event name');
+ }
+ $query .= 'ON SCHEDULE ';
+ if (! empty($_REQUEST['item_type'])
+ && in_array($_REQUEST['item_type'], $event_type)
+ ) {
+ if ($_REQUEST['item_type'] == 'RECURRING') {
+ if (! empty($_REQUEST['item_interval_value'])
+ && !empty($_REQUEST['item_interval_field'])
+ && in_array($_REQUEST['item_interval_field'], $event_interval)
+ ) {
+ $query .= 'EVERY ' . intval($_REQUEST['item_interval_value']) . ' ';
+ $query .= $_REQUEST['item_interval_field'] . ' ';
+ } else {
+ $errors[] = __('You must provide a valid interval value for the event.');
+ }
+ if (! empty($_REQUEST['item_starts'])) {
+ $query .= "STARTS '"
+ . PMA_Util::sqlAddSlashes($_REQUEST['item_starts']) . "' ";
+ }
+ if (! empty($_REQUEST['item_ends'])) {
+ $query .= "ENDS '"
+ . PMA_Util::sqlAddSlashes($_REQUEST['item_ends']) . "' ";
+ }
+ } else {
+ if (! empty($_REQUEST['item_execute_at'])) {
+ $query .= "AT '"
+ . PMA_Util::sqlAddSlashes($_REQUEST['item_execute_at']) . "' ";
+ } else {
+ $errors[] = __('You must provide a valid execution time for the event.');
+ }
+ }
+ } else {
+ $errors[] = __('You must provide a valid type for the event.');
+ }
+ $query .= 'ON COMPLETION ';
+ if (empty($_REQUEST['item_preserve'])) {
+ $query .= 'NOT ';
+ }
+ $query .= 'PRESERVE ';
+ if (! empty($_REQUEST['item_status'])) {
+ foreach ($event_status['display'] as $key => $value) {
+ if ($value == $_REQUEST['item_status']) {
+ $query .= $event_status['query'][$key] . ' ';
+ break;
+ }
+ }
+ }
+ if (! empty($_REQUEST['item_comment'])) {
+ $query .= "COMMENT '" . PMA_Util::sqlAddslashes(
+ $_REQUEST['item_comment']
+ ) . "' ";
+ }
+ $query .= 'DO ';
+ if (! empty($_REQUEST['item_definition'])) {
+ $query .= $_REQUEST['item_definition'];
+ } else {
+ $errors[] = __('You must provide an event definition.');
+ }
+
+ return $query;
+} // end PMA_EVN_getQueryFromRequest()
+
+?>
diff --git a/libraries/rte/rte_export.lib.php b/libraries/rte/rte_export.lib.php
new file mode 100644
index 0000000000..8fd9a7c4d7
--- /dev/null
+++ b/libraries/rte/rte_export.lib.php
@@ -0,0 +1,122 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Common functions for the export functionality for Routines, Triggers and Events.
+ *
+ * @package PhpMyAdmin
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ * This function is called from one of the other functions in this file
+ * and it completes the handling of the export functionality.
+ *
+ * @param string $item_name The name of the item that we are exporting
+ * @param string $export_data The SQL query to create the requested item
+ *
+ * @return void
+ */
+function PMA_RTE_handleExport($item_name, $export_data)
+{
+ global $db;
+
+ $item_name = htmlspecialchars(PMA_Util::backquote($_GET['item_name']));
+ if ($export_data !== false) {
+ $export_data = '<textarea cols="40" rows="15" style="width: 100%;">'
+ . htmlspecialchars(trim($export_data)) . '</textarea>';
+ $title = sprintf(PMA_RTE_getWord('export'), $item_name);
+ if ($GLOBALS['is_ajax_request'] == true) {
+ $response = PMA_Response::getInstance();
+ $response->addJSON('message', $export_data);
+ $response->addJSON('title', $title);
+ exit;
+ } else {
+ echo "<fieldset>\n"
+ . "<legend>$title</legend>\n"
+ . $export_data
+ . "</fieldset>\n";
+ }
+ } else {
+ $_db = htmlspecialchars(PMA_Util::backquote($db));
+ $message = __('Error in processing request:') . ' '
+ . sprintf(PMA_RTE_getWord('not_found'), $item_name, $_db);
+ $response = PMA_message::error($message);
+ if ($GLOBALS['is_ajax_request'] == true) {
+ $response = PMA_Response::getInstance();
+ $response->isSuccess(false);
+ $response->addJSON('message', $message);
+ exit;
+ } else {
+ $response->display();
+ }
+ }
+} // end PMA_RTE_handleExport()
+
+/**
+ * If necessary, prepares event information and passes
+ * it to PMA_RTE_handleExport() for the actual export.
+ *
+ * @return void
+ */
+function PMA_EVN_handleExport()
+{
+ global $_GET, $db;
+
+ if (! empty($_GET['export_item']) && ! empty($_GET['item_name'])) {
+ $item_name = $_GET['item_name'];
+ $export_data = $GLOBALS['dbi']->getDefinition($db, 'EVENT', $item_name);
+ PMA_RTE_handleExport($item_name, $export_data);
+ }
+} // end PMA_EVN_handleExport()
+
+/**
+ * If necessary, prepares routine information and passes
+ * it to PMA_RTE_handleExport() for the actual export.
+ *
+ * @return void
+ */
+function PMA_RTN_handleExport()
+{
+ global $_GET, $db;
+
+ if ( ! empty($_GET['export_item'])
+ && ! empty($_GET['item_name'])
+ && ! empty($_GET['item_type'])
+ ) {
+ if ($_GET['item_type'] == 'FUNCTION' || $_GET['item_type'] == 'PROCEDURE') {
+ $export_data = $GLOBALS['dbi']->getDefinition(
+ $db,
+ $_GET['item_type'],
+ $_GET['item_name']
+ );
+ PMA_RTE_handleExport($_GET['item_name'], $export_data);
+ }
+ }
+} // end PMA_RTN_handleExport()
+
+/**
+ * If necessary, prepares trigger information and passes
+ * it to PMA_RTE_handleExport() for the actual export.
+ *
+ * @return void
+ */
+function PMA_TRI_handleExport()
+{
+ global $_GET, $db, $table;
+
+ if (! empty($_GET['export_item']) && ! empty($_GET['item_name'])) {
+ $item_name = $_GET['item_name'];
+ $triggers = $GLOBALS['dbi']->getTriggers($db, $table, '');
+ $export_data = false;
+ foreach ($triggers as $trigger) {
+ if ($trigger['name'] === $item_name) {
+ $export_data = $trigger['create'];
+ break;
+ }
+ }
+ PMA_RTE_handleExport($item_name, $export_data);
+ }
+} // end PMA_TRI_handleExport()
+?>
diff --git a/libraries/rte/rte_footer.lib.php b/libraries/rte/rte_footer.lib.php
new file mode 100644
index 0000000000..1ae765e440
--- /dev/null
+++ b/libraries/rte/rte_footer.lib.php
@@ -0,0 +1,127 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Common functions for generating the footer for Routines, Triggers and Events.
+ *
+ * @package PhpMyAdmin
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ * Creates a fieldset for adding a new item, if the user has the privileges.
+ *
+ * @param string $docu String used to create a link to the MySQL docs
+ * @param string $priv Privilege to check for adding a new item
+ * @param string $name MySQL name of the item
+ *
+ * @return string An HTML snippet with the link to add a new item
+ */
+function PMA_RTE_getFooterLinks($docu, $priv, $name)
+{
+ global $db, $url_query, $ajax_class;
+
+ $icon = 'b_' . strtolower($name) . '_add.png';
+ $retval = "";
+ $retval .= "<!-- ADD " . $name . " FORM START -->\n";
+ $retval .= "<fieldset class='left'>\n";
+ $retval .= "<legend>" . _pgettext('Create new procedure', 'New') . "</legend>\n";
+ $retval .= " <div class='wrap'>\n";
+ if (PMA_Util::currentUserHasPrivilege($priv, $db)) {
+ $retval .= " <a {$ajax_class['add']} ";
+ $retval .= "href='db_" . strtolower($name) . "s.php";
+ $retval .= "?$url_query&amp;add_item=1' onclick='$.datepicker.initialized = false;'>";
+ $retval .= PMA_Util::getIcon($icon);
+ $retval .= PMA_RTE_getWord('add') . "</a>\n";
+ } else {
+ $retval .= " " . PMA_Util::getIcon($icon);
+ $retval .= PMA_RTE_getWord('no_create') . "\n";
+ }
+ $retval .= " " . PMA_Util::showMySQLDocu($docu) . "\n";
+ $retval .= " </div>\n";
+ $retval .= "</fieldset>\n";
+ $retval .= "<!-- ADD " . $name . " FORM END -->\n\n";
+
+ return $retval;
+} // end PMA_RTE_getFooterLinks()
+
+/**
+ * Creates a fieldset for adding a new routine, if the user has the privileges.
+ *
+ * @return string HTML code with containing the fotter fieldset
+ */
+function PMA_RTN_getFooterLinks()
+{
+ return PMA_RTE_getFooterLinks('CREATE_PROCEDURE', 'CREATE ROUTINE', 'ROUTINE');
+}// end PMA_RTN_getFooterLinks()
+
+/**
+ * Creates a fieldset for adding a new trigger, if the user has the privileges.
+ *
+ * @return string HTML code with containing the fotter fieldset
+ */
+function PMA_TRI_getFooterLinks()
+{
+ return PMA_RTE_getFooterLinks('CREATE_TRIGGER', 'TRIGGER', 'TRIGGER');
+} // end PMA_TRI_getFooterLinks()
+
+/**
+ * Creates a fieldset for adding a new event, if the user has the privileges.
+ *
+ * @return string HTML code with containing the fotter fieldset
+ */
+function PMA_EVN_getFooterLinks()
+{
+ global $db, $url_query;
+
+ /**
+ * For events, we show the usual 'Add event' form and also
+ * a form for toggling the state of the event scheduler
+ */
+ // Init options for the event scheduler toggle functionality
+ $es_state = $GLOBALS['dbi']->fetchValue(
+ "SHOW GLOBAL VARIABLES LIKE 'event_scheduler'",
+ 0,
+ 1
+ );
+ $es_state = strtolower($es_state);
+ $options = array(
+ 0 => array(
+ 'label' => __('OFF'),
+ 'value' => "SET GLOBAL event_scheduler=\"OFF\"",
+ 'selected' => ($es_state != 'on')
+ ),
+ 1 => array(
+ 'label' => __('ON'),
+ 'value' => "SET GLOBAL event_scheduler=\"ON\"",
+ 'selected' => ($es_state == 'on')
+ )
+ );
+ // Generate output
+ $retval = "<!-- FOOTER LINKS START -->\n";
+ $retval .= "<div class='doubleFieldset'>\n";
+ // show the usual footer
+ $retval .= PMA_RTE_getFooterLinks('CREATE_EVENT', 'EVENT', 'EVENT');
+ $retval .= " <fieldset class='right'>\n";
+ $retval .= " <legend>\n";
+ $retval .= " " . __('Event scheduler status') . "\n";
+ $retval .= " </legend>\n";
+ $retval .= " <div class='wrap'>\n";
+ // show the toggle button
+ $retval .= PMA_Util::toggleButton(
+ "sql.php?$url_query&amp;goto=db_events.php" . urlencode("?db=$db"),
+ 'sql_query',
+ $options,
+ 'PMA_slidingMessage(data.sql_query);'
+ );
+ $retval .= " </div>\n";
+ $retval .= " </fieldset>\n";
+ $retval .= " <div style='clear: both;'></div>\n";
+ $retval .= "</div>";
+ $retval .= "<!-- FOOTER LINKS END -->\n";
+
+ return $retval;
+} // end PMA_EVN_getFooterLinks()
+
+?>
diff --git a/libraries/rte/rte_list.lib.php b/libraries/rte/rte_list.lib.php
new file mode 100644
index 0000000000..7cc0c1c6d3
--- /dev/null
+++ b/libraries/rte/rte_list.lib.php
@@ -0,0 +1,386 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Common functions for generating lists of Routines, Triggers and Events.
+ *
+ * @package PhpMyAdmin
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ * Creates a list of items containing the relevant
+ * information and some action links.
+ *
+ * @param string $type One of ['routine'|'trigger'|'event']
+ * @param array $items An array of items
+ *
+ * @return string HTML code of the list of items
+ */
+function PMA_RTE_getList($type, $items)
+{
+ global $table;
+
+ /**
+ * Conditional classes switch the list on or off
+ */
+ $class1 = 'hide';
+ $class2 = '';
+ if (! $items) {
+ $class1 = '';
+ $class2 = ' hide';
+ }
+ /**
+ * Generate output
+ */
+ $retval = "<!-- LIST OF " . PMA_RTE_getWord('docu') . " START -->\n";
+ $retval .= "<fieldset>\n";
+ $retval .= " <legend>\n";
+ $retval .= " " . PMA_RTE_getWord('title') . "\n";
+ $retval .= " " . PMA_Util::showMySQLDocu(PMA_RTE_getWord('docu')) . "\n";
+ $retval .= " </legend>\n";
+ $retval .= " <div class='$class1' id='nothing2display'>\n";
+ $retval .= " " . PMA_RTE_getWord('nothing') . "\n";
+ $retval .= " </div>\n";
+ $retval .= " <table class='data$class2'>\n";
+ $retval .= " <!-- TABLE HEADERS -->\n";
+ $retval .= " <tr>\n";
+ // th cells with a colspan need corresponding td cells, according to W3C
+ switch ($type) {
+ case 'routine':
+ $retval .= " <th>" . __('Name') . "</th>\n";
+ $retval .= " <th colspan='4'>" . __('Action') . "</th>\n";
+ $retval .= " <th>" . __('Type') . "</th>\n";
+ $retval .= " <th>" . __('Returns') . "</th>\n";
+ $retval .= " </tr>\n";
+ $retval .= " <tr style='display: none'>\n"; // see comment above
+ for ($i = 0; $i < 7; $i++) {
+ $retval .= " <td></td>\n";
+ }
+ break;
+ case 'trigger':
+ $retval .= " <th>" . __('Name') . "</th>\n";
+ if (empty($table)) {
+ $retval .= " <th>" . __('Table') . "</th>\n";
+ }
+ $retval .= " <th colspan='3'>" . __('Action') . "</th>\n";
+ $retval .= " <th>" . __('Time') . "</th>\n";
+ $retval .= " <th>" . __('Event') . "</th>\n";
+ $retval .= " </tr>\n";
+ $retval .= " <tr style='display: none'>\n"; // see comment above
+ for ($i = 0; $i < (empty($table) ? 7 : 6); $i++) {
+ $retval .= " <td></td>\n";
+ }
+ break;
+ case 'event':
+ $retval .= " <th>" . __('Name') . "</th>\n";
+ $retval .= " <th>" . __('Status') . "</th>\n";
+ $retval .= " <th colspan='3'>" . __('Action') . "</th>\n";
+ $retval .= " <th>" . __('Type') . "</th>\n";
+ $retval .= " </tr>\n";
+ $retval .= " <tr style='display: none'>\n"; // see comment above
+ for ($i = 0; $i < 6; $i++) {
+ $retval .= " <td></td>\n";
+ }
+ break;
+ default:
+ break;
+ }
+ $retval .= " </tr>\n";
+ $retval .= " <!-- TABLE DATA -->\n";
+ $ct = 0;
+ foreach ($items as $item) {
+ $rowclass = ($ct % 2 == 0) ? 'odd' : 'even';
+ if ($GLOBALS['is_ajax_request'] && empty($_REQUEST['ajax_page_request'])) {
+ $rowclass .= ' ajaxInsert hide';
+ }
+ // Get each row from the correct function
+ switch ($type) {
+ case 'routine':
+ $retval .= PMA_RTN_getRowForList($item, $rowclass);
+ break;
+ case 'trigger':
+ $retval .= PMA_TRI_getRowForList($item, $rowclass);
+ break;
+ case 'event':
+ $retval .= PMA_EVN_getRowForList($item, $rowclass);
+ break;
+ default:
+ break;
+ }
+ $ct++;
+ }
+ $retval .= " </table>\n";
+ $retval .= "</fieldset>\n";
+ $retval .= "<!-- LIST OF " . PMA_RTE_getWord('docu') . " END -->\n";
+
+ return $retval;
+} // end PMA_RTE_getList()
+
+/**
+ * Creates the contents for a row in the list of routines
+ *
+ * @param array $routine An array of routine data
+ * @param string $rowclass Empty or one of ['even'|'odd']
+ *
+ * @return string HTML code of a row for the list of routines
+ */
+function PMA_RTN_getRowForList($routine, $rowclass = '')
+{
+ global $ajax_class, $url_query, $db, $titles;
+
+ $sql_drop = sprintf(
+ 'DROP %s IF EXISTS %s',
+ $routine['ROUTINE_TYPE'],
+ PMA_Util::backquote($routine['SPECIFIC_NAME'])
+ );
+ $type_link = "item_type={$routine['ROUTINE_TYPE']}";
+
+ $retval = " <tr class='noclick $rowclass'>\n";
+ $retval .= " <td>\n";
+ $retval .= " <span class='drop_sql hide'>"
+ . htmlspecialchars($sql_drop) . "</span>\n";
+ $retval .= " <strong>\n";
+ $retval .= " "
+ . htmlspecialchars($routine['SPECIFIC_NAME']) . "\n";
+ $retval .= " </strong>\n";
+ $retval .= " </td>\n";
+ $retval .= " <td>\n";
+ if ($routine['ROUTINE_DEFINITION'] !== null
+ && PMA_Util::currentUserHasPrivilege('ALTER ROUTINE', $db)
+ && PMA_Util::currentUserHasPrivilege('CREATE ROUTINE', $db)
+ ) {
+ $retval .= ' <a ' . $ajax_class['edit']
+ . ' href="db_routines.php?'
+ . $url_query
+ . '&amp;edit_item=1'
+ . '&amp;item_name='
+ . urlencode($routine['SPECIFIC_NAME'])
+ . '&amp;' . $type_link
+ . '">' . $titles['Edit'] . "</a>\n";
+ } else {
+ $retval .= " {$titles['NoEdit']}\n";
+ }
+ $retval .= " </td>\n";
+ $retval .= " <td>\n";
+
+ // There is a problem with PMA_Util::currentUserHasPrivilege():
+ // it does not detect all kinds of privileges, for example
+ // a direct privilege on a specific routine. So, at this point,
+ // we show the Execute link, hoping that the user has the correct rights.
+ // Also, information_schema might be hiding the ROUTINE_DEFINITION
+ // but a routine with no input parameters can be nonetheless executed.
+
+ // Check if he routine has any input parameters. If it does,
+ // we will show a dialog to get values for these parameters,
+ // otherwise we can execute it directly.
+ $routine_details = PMA_RTN_getDataFromName(
+ $routine['SPECIFIC_NAME'],
+ $routine['ROUTINE_TYPE'],
+ false
+ );
+ if ($routine !== false) {
+ $execute_action = 'execute_routine';
+ for ($i=0; $i<$routine_details['item_num_params']; $i++) {
+ if ($routine_details['item_type'] == 'PROCEDURE'
+ && $routine_details['item_param_dir'][$i] == 'OUT'
+ ) {
+ continue;
+ }
+ $execute_action = 'execute_dialog';
+ break;
+ }
+ $retval .= ' <a ' . $ajax_class['exec']
+ . ' href="db_routines.php?'
+ . $url_query
+ . '&amp;' . $execute_action . '=1'
+ . '&amp;item_name='
+ . urlencode($routine['SPECIFIC_NAME'])
+ . '&amp;' . $type_link
+ . '">' . $titles['Execute'] . "</a>\n";
+ }
+
+ $retval .= " </td>\n";
+ $retval .= " <td>\n";
+ $retval .= ' <a ' . $ajax_class['export']
+ . ' href="db_routines.php?'
+ . $url_query
+ . '&amp;export_item=1'
+ . '&amp;item_name='
+ . urlencode($routine['SPECIFIC_NAME'])
+ . '&amp;' . $type_link
+ . '">' . $titles['Export'] . "</a>\n";
+ $retval .= " </td>\n";
+ $retval .= " <td>\n";
+ if (PMA_Util::currentUserHasPrivilege('ALTER ROUTINE', $db)) {
+ $retval .= ' <a ' . $ajax_class['drop']
+ . ' href="sql.php?'
+ . $url_query
+ . '&amp;sql_query=' . urlencode($sql_drop)
+ . '&amp;goto=db_routines.php'
+ . urlencode("?db={$db}")
+ . '" >' . $titles['Drop'] . "</a>\n";
+ } else {
+ $retval .= " {$titles['NoDrop']}\n";
+ }
+ $retval .= " </td>\n";
+ $retval .= " <td>\n";
+ $retval .= " {$routine['ROUTINE_TYPE']}\n";
+ $retval .= " </td>\n";
+ $retval .= " <td>\n";
+ $retval .= " "
+ . htmlspecialchars($routine['DTD_IDENTIFIER']) . "\n";
+ $retval .= " </td>\n";
+ $retval .= " </tr>\n";
+
+ return $retval;
+} // end PMA_RTN_getRowForList()
+
+/**
+ * Creates the contents for a row in the list of triggers
+ *
+ * @param array $trigger An array of routine data
+ * @param string $rowclass Empty or one of ['even'|'odd']
+ *
+ * @return string HTML code of a cell for the list of triggers
+ */
+function PMA_TRI_getRowForList($trigger, $rowclass = '')
+{
+ global $ajax_class, $url_query, $db, $table, $titles;
+
+ $retval = " <tr class='noclick $rowclass'>\n";
+ $retval .= " <td>\n";
+ $retval .= " <span class='drop_sql hide'>"
+ . htmlspecialchars($trigger['drop']) . "</span>\n";
+ $retval .= " <strong>\n";
+ $retval .= " " . htmlspecialchars($trigger['name']) . "\n";
+ $retval .= " </strong>\n";
+ $retval .= " </td>\n";
+ if (empty($table)) {
+ $retval .= " <td>\n";
+ $retval .= " <a href='db_triggers.php?{$url_query}"
+ . "&amp;table={$trigger['table']}'>"
+ . $trigger['table'] . "</a>\n";
+ $retval .= " </td>\n";
+ }
+ $retval .= " <td>\n";
+ if (PMA_Util::currentUserHasPrivilege('TRIGGER', $db, $table)) {
+ $retval .= ' <a ' . $ajax_class['edit']
+ . ' href="db_triggers.php?'
+ . $url_query
+ . '&amp;edit_item=1'
+ . '&amp;item_name='
+ . urlencode($trigger['name'])
+ . '">' . $titles['Edit'] . "</a>\n";
+ } else {
+ $retval .= " {$titles['NoEdit']}\n";
+ }
+ $retval .= " </td>\n";
+ $retval .= " <td>\n";
+ $retval .= ' <a ' . $ajax_class['export']
+ . ' href="db_triggers.php?'
+ . $url_query
+ . '&amp;export_item=1'
+ . '&amp;item_name='
+ . urlencode($trigger['name'])
+ . '">' . $titles['Export'] . "</a>\n";
+ $retval .= " </td>\n";
+ $retval .= " <td>\n";
+ if (PMA_Util::currentUserHasPrivilege('TRIGGER', $db)) {
+ $retval .= ' <a ' . $ajax_class['drop']
+ . ' href="sql.php?'
+ . $url_query
+ . '&amp;sql_query='
+ . urlencode($trigger['drop'])
+ . '&amp;goto=db_triggers.php'
+ . urlencode("?db={$db}")
+ . '" >' . $titles['Drop'] . "</a>\n";
+ } else {
+ $retval .= " {$titles['NoDrop']}\n";
+ }
+ $retval .= " </td>\n";
+ $retval .= " <td>\n";
+ $retval .= " {$trigger['action_timing']}\n";
+ $retval .= " </td>\n";
+ $retval .= " <td>\n";
+ $retval .= " {$trigger['event_manipulation']}\n";
+ $retval .= " </td>\n";
+ $retval .= " </tr>\n";
+
+ return $retval;
+} // end PMA_TRI_getRowForList()
+
+/**
+ * Creates the contents for a row in the list of events
+ *
+ * @param array $event An array of routine data
+ * @param string $rowclass Empty or one of ['even'|'odd']
+ *
+ * @return string HTML code of a cell for the list of events
+ */
+function PMA_EVN_getRowForList($event, $rowclass = '')
+{
+ global $ajax_class, $url_query, $db, $titles;
+
+ $sql_drop = sprintf(
+ 'DROP EVENT IF EXISTS %s',
+ PMA_Util::backquote($event['EVENT_NAME'])
+ );
+
+ $retval = " <tr class='noclick $rowclass'>\n";
+ $retval .= " <td>\n";
+ $retval .= " <span class='drop_sql hide'>"
+ . htmlspecialchars($sql_drop) . "</span>\n";
+ $retval .= " <strong>\n";
+ $retval .= " "
+ . htmlspecialchars($event['EVENT_NAME']) . "\n";
+ $retval .= " </strong>\n";
+ $retval .= " </td>\n";
+ $retval .= " <td>\n";
+ $retval .= " {$event['STATUS']}\n";
+ $retval .= " </td>\n";
+ $retval .= " <td>\n";
+ if (PMA_Util::currentUserHasPrivilege('EVENT', $db)) {
+ $retval .= ' <a ' . $ajax_class['edit']
+ . ' href="db_events.php?'
+ . $url_query
+ . '&amp;edit_item=1'
+ . '&amp;item_name='
+ . urlencode($event['EVENT_NAME'])
+ . '">' . $titles['Edit'] . "</a>\n";
+ } else {
+ $retval .= " {$titles['NoEdit']}\n";
+ }
+ $retval .= " </td>\n";
+ $retval .= " <td>\n";
+ $retval .= ' <a ' . $ajax_class['export']
+ . ' href="db_events.php?'
+ . $url_query
+ . '&amp;export_item=1'
+ . '&amp;item_name='
+ . urlencode($event['EVENT_NAME'])
+ . '">' . $titles['Export'] . "</a>\n";
+ $retval .= " </td>\n";
+ $retval .= " <td>\n";
+ if (PMA_Util::currentUserHasPrivilege('EVENT', $db)) {
+ $retval .= ' <a ' . $ajax_class['drop']
+ . ' href="sql.php?'
+ . $url_query
+ . '&amp;sql_query=' . urlencode($sql_drop)
+ . '&amp;goto=db_events.php'
+ . urlencode("?db={$db}")
+ . '" >' . $titles['Drop'] . "</a>\n";
+ } else {
+ $retval .= " {$titles['NoDrop']}\n";
+ }
+ $retval .= " </td>\n";
+ $retval .= " <td>\n";
+ $retval .= " {$event['EVENT_TYPE']}\n";
+ $retval .= " </td>\n";
+ $retval .= " </tr>\n";
+
+ return $retval;
+} // end PMA_EVN_getRowForList()
+
+?>
diff --git a/libraries/rte/rte_main.inc.php b/libraries/rte/rte_main.inc.php
new file mode 100644
index 0000000000..9abb9a3080
--- /dev/null
+++ b/libraries/rte/rte_main.inc.php
@@ -0,0 +1,96 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Common code for Routines, Triggers and Events.
+ *
+ * @package PhpMyAdmin
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ * Include all other files that are common
+ * to routines, triggers and events.
+ */
+require_once './libraries/rte/rte_words.lib.php';
+require_once './libraries/rte/rte_export.lib.php';
+require_once './libraries/rte/rte_list.lib.php';
+require_once './libraries/rte/rte_footer.lib.php';
+
+if ($GLOBALS['is_ajax_request'] != true) {
+ /**
+ * Displays the header and tabs
+ */
+ if (! empty($table) && in_array($table, $GLOBALS['dbi']->getTables($db))) {
+ include_once './libraries/tbl_common.inc.php';
+ } else {
+ $table = '';
+ include_once './libraries/db_common.inc.php';
+ include_once './libraries/db_info.inc.php';
+ }
+} else {
+ /**
+ * Since we did not include some libraries, we need
+ * to manually select the required database and
+ * create the missing $url_query variable
+ */
+ if (strlen($db)) {
+ $GLOBALS['dbi']->selectDb($db);
+ if (! isset($url_query)) {
+ $url_query = PMA_URL_getCommon($db, $table);
+ }
+ }
+}
+
+/**
+ * Generate the conditional classes that will
+ * be used to attach jQuery events to links
+ */
+$ajax_class = array(
+ 'add' => '',
+ 'edit' => '',
+ 'exec' => '',
+ 'drop' => '',
+ 'export' => ''
+);
+$ajax_class = array(
+ 'add' => 'class="ajax add_anchor"',
+ 'edit' => 'class="ajax edit_anchor"',
+ 'exec' => 'class="ajax exec_anchor"',
+ 'drop' => 'class="ajax drop_anchor"',
+ 'export' => 'class="ajax export_anchor"'
+);
+
+/**
+ * Create labels for the list
+ */
+$titles = PMA_Util::buildActionTitles();
+
+/**
+ * Keep a list of errors that occurred while
+ * processing an 'Add' or 'Edit' operation.
+ */
+$errors = array();
+
+
+/**
+ * Call the appropriate main function
+ */
+switch ($_PMA_RTE) {
+case 'RTN':
+ $type = null;
+ if (isset($_REQUEST['type'])) {
+ $type = $_REQUEST['type'];
+ }
+ PMA_RTN_main($type);
+ break;
+case 'TRI':
+ PMA_TRI_main();
+ break;
+case 'EVN':
+ PMA_EVN_main();
+ break;
+}
+
+?>
diff --git a/libraries/rte/rte_routines.lib.php b/libraries/rte/rte_routines.lib.php
new file mode 100644
index 0000000000..9aa9f69fa7
--- /dev/null
+++ b/libraries/rte/rte_routines.lib.php
@@ -0,0 +1,1702 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Functions for routine management.
+ *
+ * @package PhpMyAdmin
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ * Sets required globals
+ *
+ * @return void
+ */
+function PMA_RTN_setGlobals()
+{
+ global $param_directions, $param_opts_num, $param_sqldataaccess;
+
+ $param_directions = array('IN',
+ 'OUT',
+ 'INOUT');
+ $param_opts_num = array('UNSIGNED',
+ 'ZEROFILL',
+ 'UNSIGNED ZEROFILL');
+ $param_sqldataaccess = array('NO SQL',
+ 'CONTAINS SQL',
+ 'READS SQL DATA',
+ 'MODIFIES SQL DATA');
+}
+
+/**
+ * Main function for the routines functionality
+ *
+ * @param string $type 'FUNCTION' for functions,
+ * 'PROCEDURE' for procedures,
+ * null for both
+ *
+ * @return void
+ */
+function PMA_RTN_main($type)
+{
+ global $db;
+
+ PMA_RTN_setGlobals();
+ /**
+ * Process all requests
+ */
+ PMA_RTN_handleEditor();
+ PMA_RTN_handleExecute();
+ PMA_RTN_handleExport();
+ /**
+ * Display a list of available routines
+ */
+ $columns = "`SPECIFIC_NAME`, `ROUTINE_NAME`, `ROUTINE_TYPE`, ";
+ $columns .= "`DTD_IDENTIFIER`, `ROUTINE_DEFINITION`";
+ $where = "ROUTINE_SCHEMA='" . PMA_Util::sqlAddSlashes($db) . "'";
+ if (PMA_isValid($type, array('FUNCTION','PROCEDURE'))) {
+ $where .= " AND `ROUTINE_TYPE`='" . $type . "'";
+ }
+ $items = $GLOBALS['dbi']->fetchResult(
+ "SELECT $columns FROM `INFORMATION_SCHEMA`.`ROUTINES` WHERE $where;"
+ );
+ echo PMA_RTE_getList('routine', $items);
+ /**
+ * Display the form for adding a new routine, if the user has the privileges.
+ */
+ echo PMA_RTN_getFooterLinks();
+ /**
+ * Display a warning for users with PHP's old "mysql" extension.
+ */
+ if ($GLOBALS['cfg']['Server']['extension'] === 'mysql') {
+ trigger_error(
+ __(
+ 'You are using PHP\'s deprecated \'mysql\' extension, '
+ . 'which is not capable of handling multi queries. '
+ . '[strong]The execution of some stored routines may fail![/strong] '
+ . 'Please use the improved \'mysqli\' extension to '
+ . 'avoid any problems.'
+ ),
+ E_USER_WARNING
+ );
+ }
+} // end PMA_RTN_main()
+
+/**
+ * This function parses a string containing one parameter of a routine,
+ * as returned by PMA_RTN_parseAllParameters() and returns an array containing
+ * the information about this parameter.
+ *
+ * @param string $value A string containing one parameter of a routine
+ *
+ * @return array Parsed information about the input parameter
+ */
+function PMA_RTN_parseOneParameter($value)
+{
+ global $param_directions;
+
+ $retval = array(0 => '',
+ 1 => '',
+ 2 => '',
+ 3 => '',
+ 4 => '');
+ $parsed_param = PMA_SQP_parse($value);
+ $pos = 0;
+ if (in_array(strtoupper($parsed_param[$pos]['data']), $param_directions)) {
+ $retval[0] = strtoupper($parsed_param[0]['data']);
+ $pos++;
+ }
+ if ($parsed_param[$pos]['type'] == 'alpha_identifier'
+ || $parsed_param[$pos]['type'] == 'quote_backtick'
+ ) {
+ $retval[1] = PMA_Util::unQuote(
+ $parsed_param[$pos]['data']
+ );
+ $pos++;
+ }
+ $depth = 0;
+ $param_length = '';
+ $param_opts = array();
+ for ($i=$pos; $i<$parsed_param['len']; $i++) {
+ if (($parsed_param[$i]['type'] == 'alpha_columnType'
+ || $parsed_param[$i]['type'] == 'alpha_functionName') && $depth == 0
+ ) {
+ $retval[2] = strtoupper($parsed_param[$i]['data']);
+ } else if ($parsed_param[$i]['type'] == 'punct_bracket_open_round'
+ && $depth == 0
+ ) {
+ $depth = 1;
+ } else if ($parsed_param[$i]['type'] == 'punct_bracket_close_round'
+ && $depth == 1
+ ) {
+ $depth = 0;
+ } else if ($depth == 1) {
+ $param_length .= $parsed_param[$i]['data'];
+ } else if ($parsed_param[$i]['type'] == 'alpha_reservedWord'
+ && strtoupper($parsed_param[$i]['data']) == 'CHARSET' && $depth == 0
+ ) {
+ if ($parsed_param[$i+1]['type'] == 'alpha_charset'
+ || $parsed_param[$i+1]['type'] == 'alpha_identifier'
+ ) {
+ $param_opts[] = strtolower($parsed_param[$i+1]['data']);
+ }
+ } else if ($parsed_param[$i]['type'] == 'alpha_columnAttrib'
+ && $depth == 0
+ ) {
+ $param_opts[] = strtoupper($parsed_param[$i]['data']);
+ }
+ }
+ $retval[3] = $param_length;
+ sort($param_opts);
+ $retval[4] = implode(' ', $param_opts);
+
+ return $retval;
+} // end PMA_RTN_parseOneParameter()
+
+/**
+ * This function looks through the contents of a parsed
+ * SHOW CREATE [PROCEDURE | FUNCTION] query and extracts
+ * information about the routine's parameters.
+ *
+ * @param array $parsed_query Parsed query, returned by by PMA_SQP_parse()
+ * @param string $routine_type Routine type: 'PROCEDURE' or 'FUNCTION'
+ *
+ * @return array Information about the parameteres of a routine.
+ */
+function PMA_RTN_parseAllParameters($parsed_query, $routine_type)
+{
+ $retval = array();
+ $retval['num'] = 0;
+
+ // First get the list of parameters from the query
+ $buffer = '';
+ $params = array();
+ $fetching = false;
+ $depth = 0;
+ for ($i=0; $i<$parsed_query['len']; $i++) {
+ if ($parsed_query[$i]['type'] == 'alpha_reservedWord'
+ && $parsed_query[$i]['data'] == $routine_type
+ ) {
+ $fetching = true;
+ } else if ($fetching == true
+ && $parsed_query[$i]['type'] == 'punct_bracket_open_round'
+ ) {
+ $depth++;
+ if ($depth > 1) {
+ $buffer .= $parsed_query[$i]['data'] . ' ';
+ }
+ } else if ($fetching == true
+ && $parsed_query[$i]['type'] == 'punct_bracket_close_round'
+ ) {
+ $depth--;
+ if ($depth > 0) {
+ $buffer .= $parsed_query[$i]['data'] . ' ';
+ } else {
+ break;
+ }
+ } else if ($parsed_query[$i]['type'] == 'punct_listsep' && $depth == 1) {
+ $params[] = $buffer;
+ $retval['num']++;
+ $buffer = '';
+ } else if ($fetching == true && $depth > 0) {
+ $buffer .= $parsed_query[$i]['data'] . ' ';
+ }
+ }
+ if (! empty($buffer)) {
+ $params[] = $buffer;
+ $retval['num']++;
+ }
+ // Now parse each parameter individually
+ foreach ($params as $key => $value) {
+ list($retval['dir'][],
+ $retval['name'][],
+ $retval['type'][],
+ $retval['length'][],
+ $retval['opts'][]) = PMA_RTN_parseOneParameter($value);
+ }
+ // Since some indices of $retval may be still undefined, we fill
+ // them each with an empty array to avoid E_ALL errors in PHP.
+ foreach (array('dir', 'name', 'type', 'length', 'opts') as $key => $index) {
+ if (! isset($retval[$index])) {
+ $retval[$index] = array();
+ }
+ }
+
+ return $retval;
+} // end PMA_RTN_parseAllParameters()
+
+/**
+ * This function looks through the contents of a parsed
+ * SHOW CREATE [PROCEDURE | FUNCTION] query and extracts
+ * information about the routine's definer.
+ *
+ * @param array $parsed_query Parsed query, returned by PMA_SQP_parse()
+ *
+ * @return string The definer of a routine.
+ */
+function PMA_RTN_parseRoutineDefiner($parsed_query)
+{
+ $retval = '';
+ $fetching = false;
+ for ($i=0; $i<$parsed_query['len']; $i++) {
+ if ($parsed_query[$i]['type'] == 'alpha_reservedWord'
+ && $parsed_query[$i]['data'] == 'DEFINER'
+ ) {
+ $fetching = true;
+ } else if ($fetching == true
+ && $parsed_query[$i]['type'] != 'quote_backtick'
+ && substr($parsed_query[$i]['type'], 0, 5) != 'punct'
+ ) {
+ break;
+ } else if ($fetching == true
+ && $parsed_query[$i]['type'] == 'quote_backtick'
+ ) {
+ $retval .= PMA_Util::unQuote(
+ $parsed_query[$i]['data']
+ );
+ } else if ($fetching == true && $parsed_query[$i]['type'] == 'punct_user') {
+ $retval .= $parsed_query[$i]['data'];
+ }
+ }
+ return $retval;
+} // end PMA_RTN_parseRoutineDefiner()
+
+/**
+ * Handles editor requests for adding or editing an item
+ *
+ * @return void
+ */
+function PMA_RTN_handleEditor()
+{
+ global $_GET, $_POST, $_REQUEST, $GLOBALS, $db, $errors;
+
+ if (! empty($_REQUEST['editor_process_add'])
+ || ! empty($_REQUEST['editor_process_edit'])
+ ) {
+ /**
+ * Handle a request to create/edit a routine
+ */
+ $sql_query = '';
+ $routine_query = PMA_RTN_getQueryFromRequest();
+ if (! count($errors)) { // set by PMA_RTN_getQueryFromRequest()
+ // Execute the created query
+ if (! empty($_REQUEST['editor_process_edit'])) {
+ $isProcOrFunc = in_array(
+ $_REQUEST['item_original_type'],
+ array('PROCEDURE', 'FUNCTION')
+ );
+ if (!$isProcOrFunc) {
+ $errors[] = sprintf(
+ __('Invalid routine type: "%s"'),
+ htmlspecialchars($_REQUEST['item_original_type'])
+ );
+ } else {
+ // Backup the old routine, in case something goes wrong
+ $create_routine = $GLOBALS['dbi']->getDefinition(
+ $db, $_REQUEST['item_original_type'],
+ $_REQUEST['item_original_name']
+ );
+ $drop_routine = "DROP {$_REQUEST['item_original_type']} "
+ . PMA_Util::backquote($_REQUEST['item_original_name'])
+ . ";\n";
+ $result = $GLOBALS['dbi']->tryQuery($drop_routine);
+ if (! $result) {
+ $errors[] = sprintf(
+ __('The following query has failed: "%s"'),
+ htmlspecialchars($drop_routine)
+ )
+ . '<br />'
+ . __('MySQL said: ') . $GLOBALS['dbi']->getError(null);
+ } else {
+ $result = $GLOBALS['dbi']->tryQuery($routine_query);
+ if (! $result) {
+ $errors[] = sprintf(
+ __('The following query has failed: "%s"'),
+ htmlspecialchars($routine_query)
+ )
+ . '<br />'
+ . __('MySQL said: ') . $GLOBALS['dbi']->getError(null);
+ // We dropped the old routine,
+ // but were unable to create the new one
+ // Try to restore the backup query
+ $result = $GLOBALS['dbi']->tryQuery($create_routine);
+ if (! $result) {
+ // OMG, this is really bad! We dropped the query,
+ // failed to create a new one
+ // and now even the backup query does not execute!
+ // This should not happen, but we better handle
+ // this just in case.
+ $errors[] = __(
+ 'Sorry, we failed to restore'
+ . ' the dropped routine.'
+ )
+ . '<br />'
+ . __('The backed up query was:')
+ . "\"" . htmlspecialchars($create_routine) . "\""
+ . '<br />'
+ . __('MySQL said: ')
+ . $GLOBALS['dbi']->getError(null);
+ }
+ } else {
+ $message = PMA_Message::success(
+ __('Routine %1$s has been modified.')
+ );
+ $message->addParam(
+ PMA_Util::backquote($_REQUEST['item_name'])
+ );
+ $sql_query = $drop_routine . $routine_query;
+ }
+ }
+ }
+ } else {
+ // 'Add a new routine' mode
+ $result = $GLOBALS['dbi']->tryQuery($routine_query);
+ if (! $result) {
+ $errors[] = sprintf(
+ __('The following query has failed: "%s"'),
+ htmlspecialchars($routine_query)
+ )
+ . '<br /><br />'
+ . __('MySQL said: ') . $GLOBALS['dbi']->getError(null);
+ } else {
+ $message = PMA_Message::success(
+ __('Routine %1$s has been created.')
+ );
+ $message->addParam(
+ PMA_Util::backquote($_REQUEST['item_name'])
+ );
+ $sql_query = $routine_query;
+ }
+ }
+ }
+
+ if (count($errors)) {
+ $message = PMA_Message::error(
+ __(
+ '<b>One or more errors have occurred while'
+ . ' processing your request:</b>'
+ )
+ );
+ $message->addString('<ul>');
+ foreach ($errors as $string) {
+ $message->addString('<li>' . $string . '</li>');
+ }
+ $message->addString('</ul>');
+ }
+
+ $output = PMA_Util::getMessage($message, $sql_query);
+ if ($GLOBALS['is_ajax_request']) {
+ $response = PMA_Response::getInstance();
+ if ($message->isSuccess()) {
+ $columns = "`SPECIFIC_NAME`, `ROUTINE_NAME`, `ROUTINE_TYPE`,"
+ . " `DTD_IDENTIFIER`, `ROUTINE_DEFINITION`";
+ $where = "ROUTINE_SCHEMA='" . PMA_Util::sqlAddSlashes($db) . "' "
+ . "AND ROUTINE_NAME='"
+ . PMA_Util::sqlAddSlashes($_REQUEST['item_name']) . "'"
+ . "AND ROUTINE_TYPE='"
+ . PMA_Util::sqlAddSlashes($_REQUEST['item_type']) . "'";
+ $routine = $GLOBALS['dbi']->fetchSingleRow(
+ "SELECT $columns FROM `INFORMATION_SCHEMA`.`ROUTINES`"
+ . " WHERE $where;"
+ );
+ $response->addJSON(
+ 'name', htmlspecialchars(strtoupper($_REQUEST['item_name']))
+ );
+ $response->addJSON('new_row', PMA_RTN_getRowForList($routine));
+ $response->addJSON('insert', ! empty($routine));
+ $response->addJSON('message', $output);
+ } else {
+ $response->isSuccess(false);
+ $response->addJSON('message', $output);
+ }
+ exit;
+ }
+ }
+
+ /**
+ * Display a form used to add/edit a routine, if necessary
+ */
+ // FIXME: this must be simpler than that
+ if (count($errors)
+ || ( empty($_REQUEST['editor_process_add'])
+ && empty($_REQUEST['editor_process_edit'])
+ && (! empty($_REQUEST['add_item']) || ! empty($_REQUEST['edit_item'])
+ || ! empty($_REQUEST['routine_addparameter'])
+ || ! empty($_REQUEST['routine_removeparameter'])
+ || ! empty($_REQUEST['routine_changetype'])))
+ ) {
+ // Handle requests to add/remove parameters and changing routine type
+ // This is necessary when JS is disabled
+ $operation = '';
+ if (! empty($_REQUEST['routine_addparameter'])) {
+ $operation = 'add';
+ } else if (! empty($_REQUEST['routine_removeparameter'])) {
+ $operation = 'remove';
+ } else if (! empty($_REQUEST['routine_changetype'])) {
+ $operation = 'change';
+ }
+ // Get the data for the form (if any)
+ if (! empty($_REQUEST['add_item'])) {
+ $title = PMA_RTE_getWord('add');
+ $routine = PMA_RTN_getDataFromRequest();
+ $mode = 'add';
+ } else if (! empty($_REQUEST['edit_item'])) {
+ $title = __("Edit routine");
+ if (! $operation && ! empty($_REQUEST['item_name'])
+ && empty($_REQUEST['editor_process_edit'])
+ ) {
+ $routine = PMA_RTN_getDataFromName(
+ $_REQUEST['item_name'], $_REQUEST['item_type']
+ );
+ if ($routine !== false) {
+ $routine['item_original_name'] = $routine['item_name'];
+ $routine['item_original_type'] = $routine['item_type'];
+ }
+ } else {
+ $routine = PMA_RTN_getDataFromRequest();
+ }
+ $mode = 'edit';
+ }
+ if ($routine !== false) {
+ // Show form
+ $editor = PMA_RTN_getEditorForm($mode, $operation, $routine);
+ if ($GLOBALS['is_ajax_request']) {
+ $response = PMA_Response::getInstance();
+ $response->addJSON('message', $editor);
+ $response->addJSON('title', $title);
+ $response->addJSON('param_template', PMA_RTN_getParameterRow());
+ $response->addJSON('type', $routine['item_type']);
+ } else {
+ echo "\n\n<h2>$title</h2>\n\n$editor";
+ }
+ exit;
+ } else {
+ $message = __('Error in processing request:') . ' ';
+ $message .= sprintf(
+ PMA_RTE_getWord('not_found'),
+ htmlspecialchars(PMA_Util::backquote($_REQUEST['item_name'])),
+ htmlspecialchars(PMA_Util::backquote($db))
+ );
+ $message = PMA_message::error($message);
+ if ($GLOBALS['is_ajax_request']) {
+ $response->isSuccess(false);
+ $response->addJSON('message', $message);
+ exit;
+ } else {
+ $message->display();
+ }
+ }
+ }
+} // end PMA_RTN_handleEditor()
+
+/**
+ * This function will generate the values that are required to
+ * complete the editor form. It is especially necessary to handle
+ * the 'Add another parameter', 'Remove last parameter' and
+ * 'Change routine type' functionalities when JS is disabled.
+ *
+ * @return array Data necessary to create the routine editor.
+ */
+function PMA_RTN_getDataFromRequest()
+{
+ global $_REQUEST, $param_directions, $param_sqldataaccess;
+
+ $retval = array();
+ $indices = array('item_name',
+ 'item_original_name',
+ 'item_returnlength',
+ 'item_returnopts_num',
+ 'item_returnopts_text',
+ 'item_definition',
+ 'item_comment',
+ 'item_definer');
+ foreach ($indices as $key => $index) {
+ $retval[$index] = isset($_REQUEST[$index]) ? $_REQUEST[$index] : '';
+ }
+
+ $retval['item_type'] = 'PROCEDURE';
+ $retval['item_type_toggle'] = 'FUNCTION';
+ if (isset($_REQUEST['item_type']) && $_REQUEST['item_type'] == 'FUNCTION') {
+ $retval['item_type'] = 'FUNCTION';
+ $retval['item_type_toggle'] = 'PROCEDURE';
+ }
+ $retval['item_original_type'] = 'PROCEDURE';
+ if (isset($_REQUEST['item_original_type'])
+ && $_REQUEST['item_original_type'] == 'FUNCTION'
+ ) {
+ $retval['item_original_type'] = 'FUNCTION';
+ }
+ $retval['item_num_params'] = 0;
+ $retval['item_param_dir'] = array();
+ $retval['item_param_name'] = array();
+ $retval['item_param_type'] = array();
+ $retval['item_param_length'] = array();
+ $retval['item_param_opts_num'] = array();
+ $retval['item_param_opts_text'] = array();
+ if ( isset($_REQUEST['item_param_name'])
+ && isset($_REQUEST['item_param_type'])
+ && isset($_REQUEST['item_param_length'])
+ && isset($_REQUEST['item_param_opts_num'])
+ && isset($_REQUEST['item_param_opts_text'])
+ && is_array($_REQUEST['item_param_name'])
+ && is_array($_REQUEST['item_param_type'])
+ && is_array($_REQUEST['item_param_length'])
+ && is_array($_REQUEST['item_param_opts_num'])
+ && is_array($_REQUEST['item_param_opts_text'])
+ ) {
+ if ($_REQUEST['item_type'] == 'PROCEDURE') {
+ $retval['item_param_dir'] = $_REQUEST['item_param_dir'];
+ foreach ($retval['item_param_dir'] as $key => $value) {
+ if (! in_array($value, $param_directions, true)) {
+ $retval['item_param_dir'][$key] = '';
+ }
+ }
+ }
+ $retval['item_param_name'] = $_REQUEST['item_param_name'];
+ $retval['item_param_type'] = $_REQUEST['item_param_type'];
+ foreach ($retval['item_param_type'] as $key => $value) {
+ if (! in_array($value, PMA_Util::getSupportedDatatypes(), true)) {
+ $retval['item_param_type'][$key] = '';
+ }
+ }
+ $retval['item_param_length'] = $_REQUEST['item_param_length'];
+ $retval['item_param_opts_num'] = $_REQUEST['item_param_opts_num'];
+ $retval['item_param_opts_text'] = $_REQUEST['item_param_opts_text'];
+ $retval['item_num_params'] = max(
+ count($retval['item_param_name']),
+ count($retval['item_param_type']),
+ count($retval['item_param_length']),
+ count($retval['item_param_opts_num']),
+ count($retval['item_param_opts_text'])
+ );
+ }
+ $retval['item_returntype'] = '';
+ if (isset($_REQUEST['item_returntype'])
+ && in_array($_REQUEST['item_returntype'], PMA_Util::getSupportedDatatypes())
+ ) {
+ $retval['item_returntype'] = $_REQUEST['item_returntype'];
+ }
+
+ $retval['item_isdeterministic'] = '';
+ if (isset($_REQUEST['item_isdeterministic'])
+ && strtolower($_REQUEST['item_isdeterministic']) == 'on'
+ ) {
+ $retval['item_isdeterministic'] = " checked='checked'";
+ }
+ $retval['item_securitytype_definer'] = '';
+ $retval['item_securitytype_invoker'] = '';
+ if (isset($_REQUEST['item_securitytype'])) {
+ if ($_REQUEST['item_securitytype'] === 'DEFINER') {
+ $retval['item_securitytype_definer'] = " selected='selected'";
+ } else if ($_REQUEST['item_securitytype'] === 'INVOKER') {
+ $retval['item_securitytype_invoker'] = " selected='selected'";
+ }
+ }
+ $retval['item_sqldataaccess'] = '';
+ if (isset($_REQUEST['item_sqldataaccess'])
+ && in_array($_REQUEST['item_sqldataaccess'], $param_sqldataaccess, true)
+ ) {
+ $retval['item_sqldataaccess'] = $_REQUEST['item_sqldataaccess'];
+ }
+
+ return $retval;
+} // end function PMA_RTN_getDataFromRequest()
+
+/**
+ * This function will generate the values that are required to complete
+ * the "Edit routine" form given the name of a routine.
+ *
+ * @param string $name The name of the routine.
+ * @param string $type Type of routine (ROUTINE|PROCEDURE)
+ * @param bool $all Whether to return all data or just
+ * the info about parameters.
+ *
+ * @return array Data necessary to create the routine editor.
+ */
+function PMA_RTN_getDataFromName($name, $type, $all = true)
+{
+ global $db;
+
+ $retval = array();
+
+ // Build and execute the query
+ $fields = "SPECIFIC_NAME, ROUTINE_TYPE, DTD_IDENTIFIER, "
+ . "ROUTINE_DEFINITION, IS_DETERMINISTIC, SQL_DATA_ACCESS, "
+ . "ROUTINE_COMMENT, SECURITY_TYPE";
+ $where = "ROUTINE_SCHEMA='" . PMA_Util::sqlAddSlashes($db) . "' "
+ . "AND SPECIFIC_NAME='" . PMA_Util::sqlAddSlashes($name) . "'"
+ . "AND ROUTINE_TYPE='" . PMA_Util::sqlAddSlashes($type) . "'";
+ $query = "SELECT $fields FROM INFORMATION_SCHEMA.ROUTINES WHERE $where;";
+
+ $routine = $GLOBALS['dbi']->fetchSingleRow($query);
+
+ if (! $routine) {
+ return false;
+ }
+
+ // Get required data
+ $retval['item_name'] = $routine['SPECIFIC_NAME'];
+ $retval['item_type'] = $routine['ROUTINE_TYPE'];
+ $parsed_query = PMA_SQP_parse(
+ $GLOBALS['dbi']->getDefinition(
+ $db,
+ $routine['ROUTINE_TYPE'],
+ $routine['SPECIFIC_NAME']
+ )
+ );
+ $params = PMA_RTN_parseAllParameters($parsed_query, $routine['ROUTINE_TYPE']);
+ $retval['item_num_params'] = $params['num'];
+ $retval['item_param_dir'] = $params['dir'];
+ $retval['item_param_name'] = $params['name'];
+ $retval['item_param_type'] = $params['type'];
+ $retval['item_param_length'] = $params['length'];
+ $retval['item_param_opts_num'] = $params['opts'];
+ $retval['item_param_opts_text'] = $params['opts'];
+
+ // Get extra data
+ if ($all) {
+ if ($retval['item_type'] == 'FUNCTION') {
+ $retval['item_type_toggle'] = 'PROCEDURE';
+ } else {
+ $retval['item_type_toggle'] = 'FUNCTION';
+ }
+ $retval['item_returntype'] = '';
+ $retval['item_returnlength'] = '';
+ $retval['item_returnopts_num'] = '';
+ $retval['item_returnopts_text'] = '';
+ if (! empty($routine['DTD_IDENTIFIER'])) {
+ if (strlen($routine['DTD_IDENTIFIER']) > 63) {
+ // If the DTD_IDENTIFIER string from INFORMATION_SCHEMA is
+ // at least 64 characters, then it may actually have been
+ // chopped because that column is a varchar(64), so we will
+ // parse the output of SHOW CREATE query to get accurate
+ // information about the return variable.
+ $dtd = '';
+ $fetching = false;
+ for ($i=0; $i<$parsed_query['len']; $i++) {
+ if ($parsed_query[$i]['type'] == 'alpha_reservedWord'
+ && strtoupper($parsed_query[$i]['data']) == 'RETURNS'
+ ) {
+ $fetching = true;
+ } else if ($fetching == true
+ && $parsed_query[$i]['type'] == 'alpha_reservedWord'
+ ) {
+ // We will not be looking for options such as UNSIGNED
+ // or ZEROFILL because there is no way that a numeric
+ // field's DTD_IDENTIFIER can be longer than 64
+ // characters. We can safely assume that the return
+ // datatype is either ENUM or SET, so we only look
+ // for CHARSET.
+ $word = strtoupper($parsed_query[$i]['data']);
+ if ($word == 'CHARSET'
+ && ($parsed_query[$i+1]['type'] == 'alpha_charset'
+ || $parsed_query[$i+1]['type'] == 'alpha_identifier')
+ ) {
+ $dtd .= $word . ' ' . $parsed_query[$i+1]['data'];
+ }
+ break;
+ } else if ($fetching == true) {
+ $dtd .= $parsed_query[$i]['data'] . ' ';
+ }
+ }
+ $routine['DTD_IDENTIFIER'] = $dtd;
+ }
+ $returnparam = PMA_RTN_parseOneParameter($routine['DTD_IDENTIFIER']);
+ $retval['item_returntype'] = $returnparam[2];
+ $retval['item_returnlength'] = $returnparam[3];
+ $retval['item_returnopts_num'] = $returnparam[4];
+ $retval['item_returnopts_text'] = $returnparam[4];
+ }
+ $retval['item_definer'] = PMA_RTN_parseRoutineDefiner($parsed_query);
+ $retval['item_definition'] = $routine['ROUTINE_DEFINITION'];
+ $retval['item_isdeterministic'] = '';
+ if ($routine['IS_DETERMINISTIC'] == 'YES') {
+ $retval['item_isdeterministic'] = " checked='checked'";
+ }
+ $retval['item_securitytype_definer'] = '';
+ $retval['item_securitytype_invoker'] = '';
+ if ($routine['SECURITY_TYPE'] == 'DEFINER') {
+ $retval['item_securitytype_definer'] = " selected='selected'";
+ } else if ($routine['SECURITY_TYPE'] == 'INVOKER') {
+ $retval['item_securitytype_invoker'] = " selected='selected'";
+ }
+ $retval['item_sqldataaccess'] = $routine['SQL_DATA_ACCESS'];
+ $retval['item_comment'] = $routine['ROUTINE_COMMENT'];
+ }
+
+ return $retval;
+} // PMA_RTN_getDataFromName()
+
+/**
+ * Creates one row for the parameter table used in the routine editor.
+ *
+ * @param array $routine Data for the routine returned by
+ * PMA_RTN_getDataFromRequest() or
+ * PMA_RTN_getDataFromName()
+ * @param mixed $index Either a numeric index of the row being processed
+ * or NULL to create a template row for AJAX request
+ * @param string $class Class used to hide the direction column, if the
+ * row is for a stored function.
+ *
+ * @return string HTML code of one row of parameter table for the editor.
+ */
+function PMA_RTN_getParameterRow($routine = array(), $index = null, $class = '')
+{
+ global $param_directions, $param_opts_num, $titles;
+
+ if ($index === null) {
+ // template row for AJAX request
+ $i = 0;
+ $index = '%s';
+ $drop_class = '';
+ $routine = array(
+ 'item_param_dir' => array(0 => ''),
+ 'item_param_name' => array(0 => ''),
+ 'item_param_type' => array(0 => ''),
+ 'item_param_length' => array(0 => ''),
+ 'item_param_opts_num' => array(0 => ''),
+ 'item_param_opts_text' => array(0 => '')
+ );
+ } else if (! empty($routine)) {
+ // regular row for routine editor
+ $drop_class = ' hide';
+ $i = $index;
+ } else {
+ // No input data. This shouldn't happen,
+ // but better be safe than sorry.
+ return '';
+ }
+
+ // Create the output
+ $retval = "";
+ $retval .= " <tr>\n";
+ $retval .= " <td class='routine_direction_cell$class'>\n";
+ $retval .= " <select name='item_param_dir[$index]'>\n";
+ foreach ($param_directions as $key => $value) {
+ $selected = "";
+ if (! empty($routine['item_param_dir'][$i])
+ && $routine['item_param_dir'][$i] == $value
+ ) {
+ $selected = " selected='selected'";
+ }
+ $retval .= " <option$selected>$value</option>\n";
+ }
+ $retval .= " </select>\n";
+ $retval .= " </td>\n";
+ $retval .= " <td><input name='item_param_name[$index]' type='text'\n"
+ . " value='{$routine['item_param_name'][$i]}' /></td>\n";
+ $retval .= " <td><select name='item_param_type[$index]'>";
+ $retval .= PMA_Util::getSupportedDatatypes(
+ true, $routine['item_param_type'][$i]
+ ) . "\n";
+ $retval .= " </select></td>\n";
+ $retval .= " <td>\n";
+ $retval .= " <input id='item_param_length_$index'\n"
+ . " name='item_param_length[$index]' type='text'\n"
+ . " value='{$routine['item_param_length'][$i]}' />\n";
+ $retval .= " <div class='enum_hint'>\n";
+ $retval .= " <a href='#' class='open_enum_editor'>\n";
+ $retval .= " "
+ . PMA_Util::getImage('b_edit', '', array('title'=>__('ENUM/SET editor')))
+ . "\n";
+ $retval .= " </a>\n";
+ $retval .= " </div>\n";
+ $retval .= " </td>\n";
+ $retval .= " <td class='hide no_len'>---</td>\n";
+ $retval .= " <td class='routine_param_opts_text'>\n";
+ $retval .= PMA_generateCharsetDropdownBox(
+ PMA_CSDROPDOWN_CHARSET,
+ "item_param_opts_text[$index]",
+ null,
+ $routine['item_param_opts_text'][$i]
+ );
+ $retval .= " </td>\n";
+ $retval .= " <td class='hide no_opts'>---</td>\n";
+ $retval .= " <td class='routine_param_opts_num'>\n";
+ $retval .= " <select name='item_param_opts_num[$index]'>\n";
+ $retval .= " <option value=''></option>";
+ foreach ($param_opts_num as $key => $value) {
+ $selected = "";
+ if (! empty($routine['item_param_opts_num'][$i])
+ && $routine['item_param_opts_num'][$i] == $value
+ ) {
+ $selected = " selected='selected'";
+ }
+ $retval .= "<option$selected>$value</option>";
+ }
+ $retval .= "\n </select>\n";
+ $retval .= " </td>\n";
+ $retval .= " <td class='routine_param_remove$drop_class'>\n";
+ $retval .= " <a href='#' class='routine_param_remove_anchor'>\n";
+ $retval .= " {$titles['Drop']}\n";
+ $retval .= " </a>\n";
+ $retval .= " </td>\n";
+ $retval .= " </tr>\n";
+
+ return $retval;
+} // end PMA_RTN_getParameterRow()
+
+/**
+ * Displays a form used to add/edit a routine
+ *
+ * @param string $mode If the editor will be used edit a routine
+ * or add a new one: 'edit' or 'add'.
+ * @param string $operation If the editor was previously invoked with
+ * JS turned off, this will hold the name of
+ * the current operation
+ * @param array $routine Data for the routine returned by
+ * PMA_RTN_getDataFromRequest() or
+ * PMA_RTN_getDataFromName()
+ *
+ * @return string HTML code for the editor.
+ */
+function PMA_RTN_getEditorForm($mode, $operation, $routine)
+{
+ global $db, $errors, $param_sqldataaccess, $param_opts_num;
+
+ // Escape special characters
+ $need_escape = array(
+ 'item_original_name',
+ 'item_name',
+ 'item_returnlength',
+ 'item_definition',
+ 'item_definer',
+ 'item_comment'
+ );
+ foreach ($need_escape as $key => $index) {
+ $routine[$index] = htmlentities($routine[$index], ENT_QUOTES, 'UTF-8');
+ }
+ for ($i=0; $i<$routine['item_num_params']; $i++) {
+ $routine['item_param_name'][$i] = htmlentities(
+ $routine['item_param_name'][$i],
+ ENT_QUOTES
+ );
+ $routine['item_param_length'][$i] = htmlentities(
+ $routine['item_param_length'][$i],
+ ENT_QUOTES
+ );
+ }
+
+ // Handle some logic first
+ if ($operation == 'change') {
+ if ($routine['item_type'] == 'PROCEDURE') {
+ $routine['item_type'] = 'FUNCTION';
+ $routine['item_type_toggle'] = 'PROCEDURE';
+ } else {
+ $routine['item_type'] = 'PROCEDURE';
+ $routine['item_type_toggle'] = 'FUNCTION';
+ }
+ } else if ($operation == 'add'
+ || ($routine['item_num_params'] == 0 && $mode == 'add' && ! $errors)
+ ) {
+ $routine['item_param_dir'][] = '';
+ $routine['item_param_name'][] = '';
+ $routine['item_param_type'][] = '';
+ $routine['item_param_length'][] = '';
+ $routine['item_param_opts_num'][] = '';
+ $routine['item_param_opts_text'][] = '';
+ $routine['item_num_params']++;
+ } else if ($operation == 'remove') {
+ unset($routine['item_param_dir'][$routine['item_num_params']-1]);
+ unset($routine['item_param_name'][$routine['item_num_params']-1]);
+ unset($routine['item_param_type'][$routine['item_num_params']-1]);
+ unset($routine['item_param_length'][$routine['item_num_params']-1]);
+ unset($routine['item_param_opts_num'][$routine['item_num_params']-1]);
+ unset($routine['item_param_opts_text'][$routine['item_num_params']-1]);
+ $routine['item_num_params']--;
+ }
+ $disable_remove_parameter = '';
+ if (! $routine['item_num_params']) {
+ $disable_remove_parameter = " color: gray;' disabled='disabled";
+ }
+ $original_routine = '';
+ if ($mode == 'edit') {
+ $original_routine = "<input name='item_original_name' "
+ . "type='hidden' "
+ . "value='{$routine['item_original_name']}'/>\n"
+ . "<input name='item_original_type' "
+ . "type='hidden' "
+ . "value='{$routine['item_original_type']}'/>\n";
+ }
+ $isfunction_class = '';
+ $isprocedure_class = '';
+ $isfunction_select = '';
+ $isprocedure_select = '';
+ if ($routine['item_type'] == 'PROCEDURE') {
+ $isfunction_class = ' hide';
+ $isprocedure_select = " selected='selected'";
+ } else {
+ $isprocedure_class = ' hide';
+ $isfunction_select = " selected='selected'";
+ }
+
+ // Create the output
+ $retval = "";
+ $retval .= "<!-- START " . strtoupper($mode) . " ROUTINE FORM -->\n\n";
+ $retval .= "<form class='rte_form' action='db_routines.php' method='post'>\n";
+ $retval .= "<input name='{$mode}_item' type='hidden' value='1' />\n";
+ $retval .= $original_routine;
+ $retval .= PMA_URL_getHiddenInputs($db) . "\n";
+ $retval .= "<fieldset>\n";
+ $retval .= "<legend>" . __('Details') . "</legend>\n";
+ $retval .= "<table class='rte_table' style='width: 100%'>\n";
+ $retval .= "<tr>\n";
+ $retval .= " <td style='width: 20%;'>" . __('Routine name') . "</td>\n";
+ $retval .= " <td><input type='text' name='item_name' maxlength='64'\n";
+ $retval .= " value='{$routine['item_name']}' /></td>\n";
+ $retval .= "</tr>\n";
+ $retval .= "<tr>\n";
+ $retval .= " <td>" . __('Type') . "</td>\n";
+ $retval .= " <td>\n";
+ if ($GLOBALS['is_ajax_request']) {
+ $retval .= " <select name='item_type'>\n"
+ . "<option value='PROCEDURE'$isprocedure_select>PROCEDURE</option>\n"
+ . "<option value='FUNCTION'$isfunction_select>FUNCTION</option>\n"
+ . "</select>\n";
+ } else {
+ $retval .= "<input name='item_type' type='hidden'"
+ . " value='{$routine['item_type']}' />\n"
+ . "<div style='width: 49%; float: left; text-align: center;"
+ . " font-weight: bold;'>\n"
+ . $routine['item_type'] . "\n"
+ . "</div>\n"
+ . "<input style='width: 49%;' type='submit' name='routine_changetype'\n"
+ . " value='" . sprintf(__('Change to %s'), $routine['item_type_toggle'])
+ . "' />\n";
+ }
+ $retval .= " </td>\n";
+ $retval .= "</tr>\n";
+ $retval .= "<tr>\n";
+ $retval .= " <td>" . __('Parameters') . "</td>\n";
+ $retval .= " <td>\n";
+ // parameter handling start
+ $retval .= " <table class='routine_params_table'>\n";
+ $retval .= " <tr>\n";
+ $retval .= " <th class='routine_direction_cell$isprocedure_class'>"
+ . __('Direction') . "</th>\n";
+ $retval .= " <th>" . __('Name') . "</th>\n";
+ $retval .= " <th>" . __('Type') . "</th>\n";
+ $retval .= " <th>" . __('Length/Values') . "</th>\n";
+ $retval .= " <th colspan='2'>" . __('Options') . "</th>\n";
+ $retval .= " <th class='routine_param_remove hide'>&nbsp;</th>\n";
+ $retval .= " </tr>";
+ for ($i=0; $i<$routine['item_num_params']; $i++) { // each parameter
+ $retval .= PMA_RTN_getParameterRow($routine, $i, $isprocedure_class);
+ }
+ $retval .= " </table>\n";
+ $retval .= " </td>\n";
+ $retval .= "</tr>\n";
+ $retval .= "<tr>\n";
+ $retval .= " <td>&nbsp;</td>\n";
+ $retval .= " <td>\n";
+ $retval .= " <input style='width: 49%;' type='button' \n";
+ $retval .= " name='routine_addparameter'\n";
+ $retval .= " value='" . __('Add parameter') . "' />\n";
+ $retval .= " <input style='width: 49%;$disable_remove_parameter'\n";
+ $retval .= " type='submit' \n";
+ $retval .= " name='routine_removeparameter'\n";
+ $retval .= " value='" . __('Remove last parameter') . "' />\n";
+ $retval .= " </td>\n";
+ $retval .= "</tr>\n";
+ // parameter handling end
+ $retval .= "<tr class='routine_return_row$isfunction_class'>\n";
+ $retval .= " <td>" . __('Return type') . "</td>\n";
+ $retval .= " <td><select name='item_returntype'>\n";
+ $retval .= PMA_Util::getSupportedDatatypes(true, $routine['item_returntype'])
+ . "\n";
+ $retval .= " </select></td>\n";
+ $retval .= "</tr>\n";
+ $retval .= "<tr class='routine_return_row$isfunction_class'>\n";
+ $retval .= " <td>" . __('Return length/values') . "</td>\n";
+ $retval .= " <td><input type='text' name='item_returnlength'\n";
+ $retval .= " value='{$routine['item_returnlength']}' /></td>\n";
+ $retval .= " <td class='hide no_len'>---</td>\n";
+ $retval .= "</tr>\n";
+ $retval .= "<tr class='routine_return_row$isfunction_class'>\n";
+ $retval .= " <td>" . __('Return options') . "</td>\n";
+ $retval .= " <td><div>\n";
+ $retval .= PMA_generateCharsetDropdownBox(
+ PMA_CSDROPDOWN_CHARSET,
+ "item_returnopts_text",
+ null,
+ $routine['item_returnopts_text']
+ );
+ $retval .= "\n </div>\n";
+ $retval .= " <div><select name='item_returnopts_num'>\n";
+ $retval .= " <option value=''></option>";
+ foreach ($param_opts_num as $key => $value) {
+ $selected = "";
+ if (! empty($routine['item_returnopts_num'])
+ && $routine['item_returnopts_num'] == $value
+ ) {
+ $selected = " selected='selected'";
+ }
+ $retval .= "<option$selected>$value</option>";
+ }
+ $retval .= "\n </select></div>\n";
+ $retval .= " <div class='hide no_opts'>---</div>\n";
+ $retval .= "</td>\n";
+ $retval .= "</tr>\n";
+ $retval .= "<tr>\n";
+ $retval .= " <td>" . __('Definition') . "</td>\n";
+ $retval .= " <td><textarea name='item_definition' rows='15' cols='40'>";
+ $retval .= $routine['item_definition'];
+ $retval .= "</textarea></td>\n";
+ $retval .= "</tr>\n";
+ $retval .= "<tr>\n";
+ $retval .= " <td>" . __('Is deterministic') . "</td>\n";
+ $retval .= " <td><input type='checkbox' name='item_isdeterministic'"
+ . $routine['item_isdeterministic'] . " /></td>\n";
+ $retval .= "</tr>\n";
+ $retval .= "<tr>\n";
+ $retval .= " <td>" . __('Definer') . "</td>\n";
+ $retval .= " <td><input type='text' name='item_definer'\n";
+ $retval .= " value='{$routine['item_definer']}' /></td>\n";
+ $retval .= "</tr>\n";
+ $retval .= "<tr>\n";
+ $retval .= " <td>" . __('Security type') . "</td>\n";
+ $retval .= " <td><select name='item_securitytype'>\n";
+ $retval .= " <option value='DEFINER'"
+ . $routine['item_securitytype_definer'] . ">DEFINER</option>\n";
+ $retval .= " <option value='INVOKER'"
+ . $routine['item_securitytype_invoker'] . ">INVOKER</option>\n";
+ $retval .= " </select></td>\n";
+ $retval .= "</tr>\n";
+ $retval .= "<tr>\n";
+ $retval .= " <td>" . __('SQL data access') . "</td>\n";
+ $retval .= " <td><select name='item_sqldataaccess'>\n";
+ foreach ($param_sqldataaccess as $key => $value) {
+ $selected = "";
+ if ($routine['item_sqldataaccess'] == $value) {
+ $selected = " selected='selected'";
+ }
+ $retval .= " <option$selected>$value</option>\n";
+ }
+ $retval .= " </select></td>\n";
+ $retval .= "</tr>\n";
+ $retval .= "<tr>\n";
+ $retval .= " <td>" . __('Comment') . "</td>\n";
+ $retval .= " <td><input type='text' name='item_comment' maxlength='64'\n";
+ $retval .= " value='{$routine['item_comment']}' /></td>\n";
+ $retval .= "</tr>\n";
+ $retval .= "</table>\n";
+ $retval .= "</fieldset>\n";
+ if ($GLOBALS['is_ajax_request']) {
+ $retval .= "<input type='hidden' name='editor_process_{$mode}'\n";
+ $retval .= " value='true' />\n";
+ $retval .= "<input type='hidden' name='ajax_request' value='true' />\n";
+ } else {
+ $retval .= "<fieldset class='tblFooters'>\n";
+ $retval .= " <input type='submit' name='editor_process_{$mode}'\n";
+ $retval .= " value='" . __('Go') . "' />\n";
+ $retval .= "</fieldset>\n";
+ }
+ $retval .= "</form>\n\n";
+ $retval .= "<!-- END " . strtoupper($mode) . " ROUTINE FORM -->\n\n";
+
+ return $retval;
+} // end PMA_RTN_getEditorForm()
+
+/**
+ * Composes the query necessary to create a routine from an HTTP request.
+ *
+ * @return string The CREATE [ROUTINE | PROCEDURE] query.
+ */
+function PMA_RTN_getQueryFromRequest()
+{
+ global $_REQUEST, $errors, $param_sqldataaccess, $param_directions, $PMA_Types;
+
+ $_REQUEST['item_type'] = isset($_REQUEST['item_type'])
+ ? $_REQUEST['item_type'] : '';
+
+ $query = 'CREATE ';
+ if (! empty($_REQUEST['item_definer'])) {
+ if (strpos($_REQUEST['item_definer'], '@') !== false) {
+ $arr = explode('@', $_REQUEST['item_definer']);
+ $query .= 'DEFINER=' . PMA_Util::backquote($arr[0]);
+ $query .= '@' . PMA_Util::backquote($arr[1]) . ' ';
+ } else {
+ $errors[] = __('The definer must be in the "username@hostname" format');
+ }
+ }
+ if ($_REQUEST['item_type'] == 'FUNCTION'
+ || $_REQUEST['item_type'] == 'PROCEDURE'
+ ) {
+ $query .= $_REQUEST['item_type'] . ' ';
+ } else {
+ $errors[] = sprintf(
+ __('Invalid routine type: "%s"'),
+ htmlspecialchars($_REQUEST['item_type'])
+ );
+ }
+ if (! empty($_REQUEST['item_name'])) {
+ $query .= PMA_Util::backquote($_REQUEST['item_name']);
+ } else {
+ $errors[] = __('You must provide a routine name');
+ }
+ $params = '';
+ $warned_about_dir = false;
+ $warned_about_name = false;
+ $warned_about_length = false;
+
+ if ( ! empty($_REQUEST['item_param_name'])
+ && ! empty($_REQUEST['item_param_type'])
+ && ! empty($_REQUEST['item_param_length'])
+ && is_array($_REQUEST['item_param_name'])
+ && is_array($_REQUEST['item_param_type'])
+ && is_array($_REQUEST['item_param_length'])
+ ) {
+ $item_param_name = $_REQUEST['item_param_name'];
+ $item_param_type = $_REQUEST['item_param_type'];
+ $item_param_length = $_REQUEST['item_param_length'];
+
+ for ($i=0; $i < count($item_param_name); $i++) {
+ if (! empty($item_param_name[$i])
+ && ! empty($item_param_type[$i])
+ ) {
+ if ($_REQUEST['item_type'] == 'PROCEDURE'
+ && ! empty($_REQUEST['item_param_dir'][$i])
+ && in_array($_REQUEST['item_param_dir'][$i], $param_directions)
+ ) {
+ $params .= $_REQUEST['item_param_dir'][$i] . " "
+ . PMA_Util::backquote($item_param_name[$i])
+ . " " . $item_param_type[$i];
+ } else if ($_REQUEST['item_type'] == 'FUNCTION') {
+ $params .= PMA_Util::backquote($item_param_name[$i])
+ . " " . $item_param_type[$i];
+ } else if (! $warned_about_dir) {
+ $warned_about_dir = true;
+ $errors[] = sprintf(
+ __('Invalid direction "%s" given for parameter.'),
+ htmlspecialchars($_REQUEST['item_param_dir'][$i])
+ );
+ }
+ if ($item_param_length[$i] != ''
+ && !preg_match(
+ '@^(DATE|DATETIME|TIME|TINYBLOB|TINYTEXT|BLOB|TEXT|'
+ . 'MEDIUMBLOB|MEDIUMTEXT|LONGBLOB|LONGTEXT|'
+ . 'SERIAL|BOOLEAN)$@i',
+ $item_param_type[$i]
+ )
+ ) {
+ $params .= "(" . $item_param_length[$i] . ")";
+ } else if ($item_param_length[$i] == ''
+ && preg_match(
+ '@^(ENUM|SET|VARCHAR|VARBINARY)$@i',
+ $item_param_type[$i]
+ )
+ ) {
+ if (! $warned_about_length) {
+ $warned_about_length = true;
+ $errors[] = __(
+ 'You must provide length/values for routine parameters'
+ . ' of type ENUM, SET, VARCHAR and VARBINARY.'
+ );
+ }
+ }
+ if (! empty($_REQUEST['item_param_opts_text'][$i])) {
+ if ($PMA_Types->getTypeClass($item_param_type[$i]) == 'CHAR') {
+ $params .= ' CHARSET '
+ . strtolower($_REQUEST['item_param_opts_text'][$i]);
+ }
+ }
+ if (! empty($_REQUEST['item_param_opts_num'][$i])) {
+ if ($PMA_Types->getTypeClass($item_param_type[$i]) == 'NUMBER') {
+ $params .= ' '
+ . strtoupper($_REQUEST['item_param_opts_num'][$i]);
+ }
+ }
+ if ($i != (count($item_param_name) - 1)) {
+ $params .= ", ";
+ }
+ } else if (! $warned_about_name) {
+ $warned_about_name = true;
+ $errors[] = __(
+ 'You must provide a name and a type for each routine parameter.'
+ );
+ break;
+ }
+ }
+ }
+ $query .= "(" . $params . ") ";
+ if ($_REQUEST['item_type'] == 'FUNCTION') {
+ $item_returntype = isset($_REQUEST['item_returntype'])
+ ? $_REQUEST['item_returntype']
+ : null;
+
+ if (! empty($item_returntype)
+ && in_array(
+ $item_returntype, PMA_Util::getSupportedDatatypes()
+ )
+ ) {
+ $query .= "RETURNS " . $item_returntype;
+ } else {
+ $errors[] = __('You must provide a valid return type for the routine.');
+ }
+ if (! empty($_REQUEST['item_returnlength'])
+ && !preg_match(
+ '@^(DATE|DATETIME|TIME|TINYBLOB|TINYTEXT|BLOB|TEXT|'
+ . 'MEDIUMBLOB|MEDIUMTEXT|LONGBLOB|LONGTEXT|SERIAL|BOOLEAN)$@i',
+ $item_returntype
+ )
+ ) {
+ $query .= "(" . $_REQUEST['item_returnlength'] . ")";
+ } else if (empty($_REQUEST['item_returnlength'])
+ && preg_match(
+ '@^(ENUM|SET|VARCHAR|VARBINARY)$@i', $item_returntype
+ )
+ ) {
+ if (! $warned_about_length) {
+ $warned_about_length = true;
+ $errors[] = __(
+ 'You must provide length/values for routine parameters'
+ . ' of type ENUM, SET, VARCHAR and VARBINARY.'
+ );
+ }
+ }
+ if (! empty($_REQUEST['item_returnopts_text'])) {
+ if ($PMA_Types->getTypeClass($item_returntype) == 'CHAR') {
+ $query .= ' CHARSET '
+ . strtolower($_REQUEST['item_returnopts_text']);
+ }
+ }
+ if (! empty($_REQUEST['item_returnopts_num'])) {
+ if ($PMA_Types->getTypeClass($item_returntype) == 'NUMBER') {
+ $query .= ' ' . strtoupper($_REQUEST['item_returnopts_num']);
+ }
+ }
+ $query .= ' ';
+ }
+ if (! empty($_REQUEST['item_comment'])) {
+ $query .= "COMMENT '" . PMA_Util::sqlAddslashes($_REQUEST['item_comment'])
+ . "' ";
+ }
+ if (isset($_REQUEST['item_isdeterministic'])) {
+ $query .= 'DETERMINISTIC ';
+ } else {
+ $query .= 'NOT DETERMINISTIC ';
+ }
+ if (! empty($_REQUEST['item_sqldataaccess'])
+ && in_array($_REQUEST['item_sqldataaccess'], $param_sqldataaccess)
+ ) {
+ $query .= $_REQUEST['item_sqldataaccess'] . ' ';
+ }
+ if (! empty($_REQUEST['item_securitytype'])) {
+ if ($_REQUEST['item_securitytype'] == 'DEFINER'
+ || $_REQUEST['item_securitytype'] == 'INVOKER'
+ ) {
+ $query .= 'SQL SECURITY ' . $_REQUEST['item_securitytype'] . ' ';
+ }
+ }
+ if (! empty($_REQUEST['item_definition'])) {
+ $query .= $_REQUEST['item_definition'];
+ } else {
+ $errors[] = __('You must provide a routine definition.');
+ }
+
+ return $query;
+} // end PMA_RTN_getQueryFromRequest()
+
+/**
+ * Handles requests for executing a routine
+ *
+ * @return void
+ */
+function PMA_RTN_handleExecute()
+{
+ global $_GET, $_POST, $_REQUEST, $GLOBALS, $db;
+
+ /**
+ * Handle all user requests other than the default of listing routines
+ */
+ if (! empty($_REQUEST['execute_routine']) && ! empty($_REQUEST['item_name'])) {
+ // Build the queries
+ $routine = PMA_RTN_getDataFromName(
+ $_REQUEST['item_name'], $_REQUEST['item_type'], false
+ );
+ if ($routine !== false) {
+ $queries = array();
+ $end_query = array();
+ $args = array();
+ $all_functions = $GLOBALS['PMA_Types']->getAllFunctions();
+ for ($i=0; $i<$routine['item_num_params']; $i++) {
+ if (isset($_REQUEST['params'][$routine['item_param_name'][$i]])) {
+ $value = $_REQUEST['params'][$routine['item_param_name'][$i]];
+ if (is_array($value)) { // is SET type
+ $value = implode(',', $value);
+ }
+ $value = PMA_Util::sqlAddSlashes($value);
+ if (! empty($_REQUEST['funcs'][$routine['item_param_name'][$i]])
+ && in_array(
+ $_REQUEST['funcs'][$routine['item_param_name'][$i]],
+ $all_functions
+ )
+ ) {
+ $queries[] = "SET @p$i="
+ . $_REQUEST['funcs'][$routine['item_param_name'][$i]]
+ . "('$value');\n";
+ } else {
+ $queries[] = "SET @p$i='$value';\n";
+ }
+ $args[] = "@p$i";
+ } else {
+ $args[] = "@p$i";
+ }
+ if ($routine['item_type'] == 'PROCEDURE') {
+ if ($routine['item_param_dir'][$i] == 'OUT'
+ || $routine['item_param_dir'][$i] == 'INOUT'
+ ) {
+ $end_query[] = "@p$i AS "
+ . PMA_Util::backquote($routine['item_param_name'][$i]);
+ }
+ }
+ }
+ if ($routine['item_type'] == 'PROCEDURE') {
+ $queries[] = "CALL " . PMA_Util::backquote($routine['item_name'])
+ . "(" . implode(', ', $args) . ");\n";
+ if (count($end_query)) {
+ $queries[] = "SELECT " . implode(', ', $end_query) . ";\n";
+ }
+ } else {
+ $queries[] = "SELECT " . PMA_Util::backquote($routine['item_name'])
+ . "(" . implode(', ', $args) . ") "
+ . "AS " . PMA_Util::backquote($routine['item_name'])
+ . ";\n";
+ }
+
+ // Get all the queries as one SQL statement
+ $multiple_query = implode("", $queries);
+
+ $outcome = true;
+ $affected = 0;
+
+ // Execute query
+ if (! $GLOBALS['dbi']->tryMultiQuery($multiple_query)) {
+ $outcome = false;
+ }
+
+ // Generate output
+ if ($outcome) {
+
+ // Pass the SQL queries through the "pretty printer"
+ $output = PMA_Util::formatSql(implode($queries, "\n"));
+
+ // Display results
+ $output .= "<fieldset><legend>";
+ $output .= sprintf(
+ __('Execution results of routine %s'),
+ PMA_Util::backquote(htmlspecialchars($routine['item_name']))
+ );
+ $output .= "</legend>";
+
+ $num_of_rusults_set_to_display = 0;
+
+ do {
+
+ $result = $GLOBALS['dbi']->storeResult();
+ $num_rows = $GLOBALS['dbi']->numRows($result);
+
+ if (($result !== false) && ($num_rows > 0)) {
+
+ $output .= "<table><tr>";
+ foreach ($GLOBALS['dbi']->getFieldsMeta($result)
+ as $key => $field) {
+ $output .= "<th>";
+ $output .= htmlspecialchars($field->name);
+ $output .= "</th>";
+ }
+ $output .= "</tr>";
+
+ $color_class = 'odd';
+
+ while ($row = $GLOBALS['dbi']->fetchAssoc($result)) {
+ $output .= "<tr>";
+ foreach ($row as $key => $value) {
+ if ($value === null) {
+ $value = '<i>NULL</i>';
+ } else {
+ $value = htmlspecialchars($value);
+ }
+ $output .= "<td class='" . $color_class . "'>"
+ . $value . "</td>";
+ }
+ $output .= "</tr>";
+ $color_class = ($color_class == 'odd') ? 'even' : 'odd';
+ }
+
+ $output .= "</table>";
+ $num_of_rusults_set_to_display++;
+ $affected = $num_rows;
+
+ }
+
+ if (! $GLOBALS['dbi']->moreResults()) {
+ break;
+ }
+
+ $output .= "<br/>";
+
+ $GLOBALS['dbi']->freeResult($result);
+
+ } while ($GLOBALS['dbi']->nextResult());
+
+ $output .= "</fieldset>";
+
+ $message = __('Your SQL query has been executed successfully');
+ if ($routine['item_type'] == 'PROCEDURE') {
+ $message .= '<br />';
+
+ // TODO : message need to be modified according to the
+ // output from the routine
+ $message .= sprintf(
+ _ngettext(
+ '%d row affected by the last statement inside the procedure',
+ '%d rows affected by the last statement inside the procedure',
+ $affected
+ ),
+ $affected
+ );
+ }
+ $message = PMA_message::success($message);
+
+ if ($num_of_rusults_set_to_display == 0) {
+ $notice = __(
+ 'MySQL returned an empty result set (i.e. zero rows).'
+ );
+ $output .= PMA_message::notice($notice)->getDisplay();
+ }
+
+ } else {
+ $output = '';
+ $message = PMA_message::error(
+ sprintf(
+ __('The following query has failed: "%s"'),
+ htmlspecialchars($multiple_query)
+ )
+ . '<br /><br />'
+ . __('MySQL said: ') . $GLOBALS['dbi']->getError(null)
+ );
+ }
+
+ // Print/send output
+ if ($GLOBALS['is_ajax_request']) {
+ $response = PMA_Response::getInstance();
+ $response->isSuccess($message->isSuccess());
+ $response->addJSON('message', $message->getDisplay() . $output);
+ $response->addJSON('dialog', false);
+ exit;
+ } else {
+ echo $message->getDisplay() . $output;
+ if ($message->isError()) {
+ // At least one query has failed, so shouldn't
+ // execute any more queries, so we quit.
+ exit;
+ }
+ unset($_POST);
+ // Now deliberately fall through to displaying the routines list
+ }
+ } else {
+ $message = __('Error in processing request:') . ' ';
+ $message .= sprintf(
+ PMA_RTE_getWord('not_found'),
+ htmlspecialchars(PMA_Util::backquote($_REQUEST['item_name'])),
+ htmlspecialchars(PMA_Util::backquote($db))
+ );
+ $message = PMA_message::error($message);
+ if ($GLOBALS['is_ajax_request']) {
+ $response = PMA_Response::getInstance();
+ $response->isSuccess(false);
+ $response->addJSON('message', $message);
+ exit;
+ } else {
+ echo $message->getDisplay();
+ unset($_POST);
+ }
+ }
+ } else if (! empty($_GET['execute_dialog']) && ! empty($_GET['item_name'])) {
+ /**
+ * Display the execute form for a routine.
+ */
+ $routine = PMA_RTN_getDataFromName(
+ $_GET['item_name'], $_GET['item_type'], true
+ );
+ if ($routine !== false) {
+ $form = PMA_RTN_getExecuteForm($routine);
+ if ($GLOBALS['is_ajax_request'] == true) {
+ $title = __("Execute routine") . " " . PMA_Util::backquote(
+ htmlentities($_GET['item_name'], ENT_QUOTES)
+ );
+ $response = PMA_Response::getInstance();
+ $response->addJSON('message', $form);
+ $response->addJSON('title', $title);
+ $response->addJSON('dialog', true);
+ } else {
+ echo "\n\n<h2>" . __("Execute routine") . "</h2>\n\n";
+ echo $form;
+ }
+ exit;
+ } else if (($GLOBALS['is_ajax_request'] == true)) {
+ $message = __('Error in processing request:') . ' ';
+ $message .= sprintf(
+ PMA_RTE_getWord('not_found'),
+ htmlspecialchars(PMA_Util::backquote($_REQUEST['item_name'])),
+ htmlspecialchars(PMA_Util::backquote($db))
+ );
+ $message = PMA_message::error($message);
+
+ $response = PMA_Response::getInstance();
+ $response->isSuccess(false);
+ $response->addJSON('message', $message);
+ exit;
+ }
+ }
+}
+
+/**
+ * Creates the HTML code that shows the routine execution dialog.
+ *
+ * @param array $routine Data for the routine returned by
+ * PMA_RTN_getDataFromName()
+ *
+ * @return string HTML code for the routine execution dialog.
+ */
+function PMA_RTN_getExecuteForm($routine)
+{
+ global $db, $cfg;
+
+ // Escape special characters
+ $routine['item_name'] = htmlentities($routine['item_name'], ENT_QUOTES);
+ for ($i=0; $i<$routine['item_num_params']; $i++) {
+ $routine['item_param_name'][$i] = htmlentities(
+ $routine['item_param_name'][$i],
+ ENT_QUOTES
+ );
+ }
+
+ // Create the output
+ $retval = "";
+ $retval .= "<!-- START ROUTINE EXECUTE FORM -->\n\n";
+ $retval .= "<form action='db_routines.php' method='post' class='rte_form ajax' onsubmit='return false'>\n";
+ $retval .= "<input type='hidden' name='item_name'\n";
+ $retval .= " value='{$routine['item_name']}' />\n";
+ $retval .= "<input type='hidden' name='item_type'\n";
+ $retval .= " value='{$routine['item_type']}' />\n";
+ $retval .= PMA_URL_getHiddenInputs($db) . "\n";
+ $retval .= "<fieldset>\n";
+ if ($GLOBALS['is_ajax_request'] != true) {
+ $retval .= "<legend>{$routine['item_name']}</legend>\n";
+ $retval .= "<table class='rte_table'>\n";
+ $retval .= "<caption class='tblHeaders'>\n";
+ $retval .= __('Routine parameters');
+ $retval .= "</caption>\n";
+ } else {
+ $retval .= "<legend>" . __('Routine parameters') . "</legend>\n";
+ $retval .= "<table class='rte_table' style='width: 100%;'>\n";
+ }
+ $retval .= "<tr>\n";
+ $retval .= "<th>" . __('Name') . "</th>\n";
+ $retval .= "<th>" . __('Type') . "</th>\n";
+ if ($cfg['ShowFunctionFields']) {
+ $retval .= "<th>" . __('Function') . "</th>\n";
+ }
+ $retval .= "<th>" . __('Value') . "</th>\n";
+ $retval .= "</tr>\n";
+ // Get a list of data types that are not yet supported.
+ $no_support_types = PMA_Util::unsupportedDatatypes();
+ for ($i=0; $i<$routine['item_num_params']; $i++) { // Each parameter
+ if ($routine['item_type'] == 'PROCEDURE'
+ && $routine['item_param_dir'][$i] == 'OUT'
+ ) {
+ continue;
+ }
+ $rowclass = ($i % 2 == 0) ? 'even' : 'odd';
+ $retval .= "\n<tr class='$rowclass'>\n";
+ $retval .= "<td>{$routine['item_param_name'][$i]}</td>\n";
+ $retval .= "<td>{$routine['item_param_type'][$i]}</td>\n";
+ if ($cfg['ShowFunctionFields']) {
+ $retval .= "<td>\n";
+ if (stristr($routine['item_param_type'][$i], 'enum')
+ || stristr($routine['item_param_type'][$i], 'set')
+ || in_array(
+ strtolower($routine['item_param_type'][$i]), $no_support_types
+ )
+ ) {
+ $retval .= "--\n";
+ } else {
+ $field = array(
+ 'True_Type' => strtolower($routine['item_param_type'][$i]),
+ 'Type' => '',
+ 'Key' => '',
+ 'Field' => '',
+ 'Default' => '',
+ 'first_timestamp' => false
+ );
+ $retval .= "<select name='funcs["
+ . $routine['item_param_name'][$i] . "]'>";
+ $retval .= PMA_Util::getFunctionsForField($field, false);
+ $retval .= "</select>";
+ }
+ $retval .= "</td>\n";
+ }
+ // Append a class to date/time fields so that
+ // jQuery can attach a datepicker to them
+ $class = '';
+ if ($routine['item_param_type'][$i] == 'DATETIME'
+ || $routine['item_param_type'][$i] == 'TIMESTAMP'
+ ) {
+ $class = 'datetimefield';
+ } else if ($routine['item_param_type'][$i] == 'DATE') {
+ $class = 'datefield';
+ }
+ $retval .= "<td class='nowrap'>\n";
+ if (in_array($routine['item_param_type'][$i], array('ENUM', 'SET'))) {
+ $tokens = PMA_SQP_parse($routine['item_param_length'][$i]);
+ if ($routine['item_param_type'][$i] == 'ENUM') {
+ $input_type = 'radio';
+ } else {
+ $input_type = 'checkbox';
+ }
+ for ($j=0; $j<$tokens['len']; $j++) {
+ if ($tokens[$j]['type'] != 'punct_listsep') {
+ $tokens[$j]['data'] = htmlentities(
+ PMA_Util::unquote($tokens[$j]['data']),
+ ENT_QUOTES
+ );
+ $retval .= "<input name='params["
+ . $routine['item_param_name'][$i] . "][]' "
+ . "value='" . $tokens[$j]['data'] . "' type='"
+ . $input_type . "' />"
+ . $tokens[$j]['data'] . "<br />\n";
+ }
+ }
+ } else if (in_array(
+ strtolower($routine['item_param_type'][$i]), $no_support_types
+ )) {
+ $retval .= "\n";
+ } else {
+ $retval .= "<input class='$class' type='text' name='params["
+ . $routine['item_param_name'][$i] . "]' />\n";
+ }
+ $retval .= "</td>\n";
+ $retval .= "</tr>\n";
+ }
+ $retval .= "\n</table>\n";
+ if ($GLOBALS['is_ajax_request'] != true) {
+ $retval .= "</fieldset>\n\n";
+ $retval .= "<fieldset class='tblFooters'>\n";
+ $retval .= " <input type='submit' name='execute_routine'\n";
+ $retval .= " value='" . __('Go') . "' />\n";
+ $retval .= "</fieldset>\n";
+ } else {
+ $retval .= "<input type='hidden' name='execute_routine' value='true' />";
+ $retval .= "<input type='hidden' name='ajax_request' value='true' />";
+ }
+ $retval .= "</form>\n\n";
+ $retval .= "<!-- END ROUTINE EXECUTE FORM -->\n\n";
+
+ return $retval;
+} // end PMA_RTN_getExecuteForm()
+
+?>
diff --git a/libraries/rte/rte_triggers.lib.php b/libraries/rte/rte_triggers.lib.php
new file mode 100644
index 0000000000..e8157a41a8
--- /dev/null
+++ b/libraries/rte/rte_triggers.lib.php
@@ -0,0 +1,486 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Functions for trigger management.
+ *
+ * @package PhpMyAdmin
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ * Sets required globals
+ *
+ * @return void
+ */
+function PMA_TRI_setGlobals()
+{
+ global $action_timings, $event_manipulations;
+
+ // Some definitions for triggers
+ $action_timings = array('BEFORE',
+ 'AFTER');
+ $event_manipulations = array('INSERT',
+ 'UPDATE',
+ 'DELETE');
+}
+
+/**
+ * Main function for the triggers functionality
+ *
+ * @return void
+ */
+function PMA_TRI_main()
+{
+ global $db, $table;
+
+ PMA_TRI_setGlobals();
+ /**
+ * Process all requests
+ */
+ PMA_TRI_handleEditor();
+ PMA_TRI_handleExport();
+ /**
+ * Display a list of available triggers
+ */
+ $items = $GLOBALS['dbi']->getTriggers($db, $table);
+ echo PMA_RTE_getList('trigger', $items);
+ /**
+ * Display a link for adding a new trigger,
+ * if the user has the necessary privileges
+ */
+ echo PMA_TRI_getFooterLinks();
+} // end PMA_TRI_main()
+
+/**
+ * Handles editor requests for adding or editing an item
+ *
+ * @return void
+ */
+function PMA_TRI_handleEditor()
+{
+ global $_REQUEST, $_POST, $errors, $db, $table;
+
+ if (! empty($_REQUEST['editor_process_add'])
+ || ! empty($_REQUEST['editor_process_edit'])
+ ) {
+ $sql_query = '';
+
+ $item_query = PMA_TRI_getQueryFromRequest();
+
+ if (! count($errors)) { // set by PMA_RTN_getQueryFromRequest()
+ // Execute the created query
+ if (! empty($_REQUEST['editor_process_edit'])) {
+ // Backup the old trigger, in case something goes wrong
+ $trigger = PMA_TRI_getDataFromName($_REQUEST['item_original_name']);
+ $create_item = $trigger['create'];
+ $drop_item = $trigger['drop'] . ';';
+ $result = $GLOBALS['dbi']->tryQuery($drop_item);
+ if (! $result) {
+ $errors[] = sprintf(
+ __('The following query has failed: "%s"'),
+ htmlspecialchars($drop_item)
+ )
+ . '<br />'
+ . __('MySQL said: ') . $GLOBALS['dbi']->getError(null);
+ } else {
+ $result = $GLOBALS['dbi']->tryQuery($item_query);
+ if (! $result) {
+ $errors[] = sprintf(
+ __('The following query has failed: "%s"'),
+ htmlspecialchars($item_query)
+ )
+ . '<br />'
+ . __('MySQL said: ') . $GLOBALS['dbi']->getError(null);
+ // We dropped the old item, but were unable to create the
+ // new one. Try to restore the backup query.
+ $result = $GLOBALS['dbi']->tryQuery($create_item);
+ if (! $result) {
+ // OMG, this is really bad! We dropped the query,
+ // failed to create a new one
+ // and now even the backup query does not execute!
+ // This should not happen, but we better handle
+ // this just in case.
+ $errors[] = __(
+ 'Sorry, we failed to restore the dropped trigger.'
+ )
+ . '<br />'
+ . __('The backed up query was:')
+ . "\"" . htmlspecialchars($create_item) . "\""
+ . '<br />'
+ . __('MySQL said: ') . $GLOBALS['dbi']->getError(null);
+ }
+ } else {
+ $message = PMA_Message::success(
+ __('Trigger %1$s has been modified.')
+ );
+ $message->addParam(
+ PMA_Util::backquote($_REQUEST['item_name'])
+ );
+ $sql_query = $drop_item . $item_query;
+ }
+ }
+ } else {
+ // 'Add a new item' mode
+ $result = $GLOBALS['dbi']->tryQuery($item_query);
+ if (! $result) {
+ $errors[] = sprintf(
+ __('The following query has failed: "%s"'),
+ htmlspecialchars($item_query)
+ )
+ . '<br /><br />'
+ . __('MySQL said: ') . $GLOBALS['dbi']->getError(null);
+ } else {
+ $message = PMA_Message::success(
+ __('Trigger %1$s has been created.')
+ );
+ $message->addParam(
+ PMA_Util::backquote($_REQUEST['item_name'])
+ );
+ $sql_query = $item_query;
+ }
+ }
+ }
+
+ if (count($errors)) {
+ $message = PMA_Message::error(__('<b>One or more errors have occurred while processing your request:</b>'));
+ $message->addString('<ul>');
+ foreach ($errors as $string) {
+ $message->addString('<li>' . $string . '</li>');
+ }
+ $message->addString('</ul>');
+ }
+
+ $output = PMA_Util::getMessage($message, $sql_query);
+ if ($GLOBALS['is_ajax_request']) {
+ $response = PMA_Response::getInstance();
+ if ($message->isSuccess()) {
+ $items = $GLOBALS['dbi']->getTriggers($db, $table, '');
+ $trigger = false;
+ foreach ($items as $value) {
+ if ($value['name'] == $_REQUEST['item_name']) {
+ $trigger = $value;
+ }
+ }
+ $insert = false;
+ if (empty($table)
+ || ($trigger !== false && $table == $trigger['table'])
+ ) {
+ $insert = true;
+ $response->addJSON('new_row', PMA_TRI_getRowForList($trigger));
+ $response->addJSON(
+ 'name',
+ htmlspecialchars(
+ strtoupper($_REQUEST['item_name'])
+ )
+ );
+ }
+ $response->addJSON('insert', $insert);
+ $response->addJSON('message', $output);
+ } else {
+ $response->addJSON('message', $message);
+ $response->isSuccess(false);
+ }
+ exit;
+ }
+ }
+
+ /**
+ * Display a form used to add/edit a trigger, if necessary
+ */
+ if (count($errors)
+ || (empty($_REQUEST['editor_process_add'])
+ && empty($_REQUEST['editor_process_edit'])
+ && (! empty($_REQUEST['add_item'])
+ || ! empty($_REQUEST['edit_item']))) // FIXME: this must be simpler than that
+ ) {
+ // Get the data for the form (if any)
+ if (! empty($_REQUEST['add_item'])) {
+ $title = PMA_RTE_getWord('add');
+ $item = PMA_TRI_getDataFromRequest();
+ $mode = 'add';
+ } else if (! empty($_REQUEST['edit_item'])) {
+ $title = __("Edit trigger");
+ if (! empty($_REQUEST['item_name'])
+ && empty($_REQUEST['editor_process_edit'])
+ ) {
+ $item = PMA_TRI_getDataFromName($_REQUEST['item_name']);
+ if ($item !== false) {
+ $item['item_original_name'] = $item['item_name'];
+ }
+ } else {
+ $item = PMA_TRI_getDataFromRequest();
+ }
+ $mode = 'edit';
+ }
+ if ($item !== false) {
+ // Show form
+ $editor = PMA_TRI_getEditorForm($mode, $item);
+ if ($GLOBALS['is_ajax_request']) {
+ $response = PMA_Response::getInstance();
+ $response->addJSON('message', $editor);
+ $response->addJSON('title', $title);
+ } else {
+ echo "\n\n<h2>$title</h2>\n\n$editor";
+ unset($_POST);
+ }
+ exit;
+ } else {
+ $message = __('Error in processing request:') . ' ';
+ $message .= sprintf(
+ PMA_RTE_getWord('not_found'),
+ htmlspecialchars(PMA_Util::backquote($_REQUEST['item_name'])),
+ htmlspecialchars(PMA_Util::backquote($db))
+ );
+ $message = PMA_message::error($message);
+ if ($GLOBALS['is_ajax_request']) {
+ $response = PMA_Response::getInstance();
+ $response->isSuccess(false);
+ $response->addJSON('message', $message);
+ exit;
+ } else {
+ $message->display();
+ }
+ }
+ }
+} // end PMA_TRI_handleEditor()
+
+/**
+ * This function will generate the values that are required to for the editor
+ *
+ * @return array Data necessary to create the editor.
+ */
+function PMA_TRI_getDataFromRequest()
+{
+ $retval = array();
+ $indices = array('item_name',
+ 'item_table',
+ 'item_original_name',
+ 'item_action_timing',
+ 'item_event_manipulation',
+ 'item_definition',
+ 'item_definer');
+ foreach ($indices as $index) {
+ $retval[$index] = isset($_REQUEST[$index]) ? $_REQUEST[$index] : '';
+ }
+ return $retval;
+} // end PMA_TRI_getDataFromRequest()
+
+/**
+ * This function will generate the values that are required to complete
+ * the "Edit trigger" form given the name of a trigger.
+ *
+ * @param string $name The name of the trigger.
+ *
+ * @return array Data necessary to create the editor.
+ */
+function PMA_TRI_getDataFromName($name)
+{
+ global $db, $table, $_REQUEST;
+
+ $temp = array();
+ $items = $GLOBALS['dbi']->getTriggers($db, $table, '');
+ foreach ($items as $value) {
+ if ($value['name'] == $name) {
+ $temp = $value;
+ }
+ }
+ if (empty($temp)) {
+ return false;
+ } else {
+ $retval = array();
+ $retval['create'] = $temp['create'];
+ $retval['drop'] = $temp['drop'];
+ $retval['item_name'] = $temp['name'];
+ $retval['item_table'] = $temp['table'];
+ $retval['item_action_timing'] = $temp['action_timing'];
+ $retval['item_event_manipulation'] = $temp['event_manipulation'];
+ $retval['item_definition'] = $temp['definition'];
+ $retval['item_definer'] = $temp['definer'];
+ return $retval;
+ }
+} // end PMA_TRI_getDataFromName()
+
+/**
+ * Displays a form used to add/edit a trigger
+ *
+ * @param string $mode If the editor will be used edit a trigger
+ * or add a new one: 'edit' or 'add'.
+ * @param array $item Data for the trigger returned by PMA_TRI_getDataFromRequest()
+ * or PMA_TRI_getDataFromName()
+ *
+ * @return string HTML code for the editor.
+ */
+function PMA_TRI_getEditorForm($mode, $item)
+{
+ global $db, $table, $event_manipulations, $action_timings;
+
+ // Escape special characters
+ $need_escape = array(
+ 'item_original_name',
+ 'item_name',
+ 'item_definition',
+ 'item_definer'
+ );
+ foreach ($need_escape as $key => $index) {
+ $item[$index] = htmlentities($item[$index], ENT_QUOTES, 'UTF-8');
+ }
+ $original_data = '';
+ if ($mode == 'edit') {
+ $original_data = "<input name='item_original_name' "
+ . "type='hidden' value='{$item['item_original_name']}'/>\n";
+ }
+ $query = "SELECT `TABLE_NAME` FROM `INFORMATION_SCHEMA`.`TABLES` ";
+ $query .= "WHERE `TABLE_SCHEMA`='" . PMA_Util::sqlAddSlashes($db) . "' ";
+ $query .= "AND `TABLE_TYPE`='BASE TABLE'";
+ $tables = $GLOBALS['dbi']->fetchResult($query);
+
+ // Create the output
+ $retval = "";
+ $retval .= "<!-- START " . strtoupper($mode) . " TRIGGER FORM -->\n\n";
+ $retval .= "<form class='rte_form' action='db_triggers.php' method='post'>\n";
+ $retval .= "<input name='{$mode}_item' type='hidden' value='1' />\n";
+ $retval .= $original_data;
+ $retval .= PMA_URL_getHiddenInputs($db, $table) . "\n";
+ $retval .= "<fieldset>\n";
+ $retval .= "<legend>" . __('Details') . "</legend>\n";
+ $retval .= "<table class='rte_table' style='width: 100%'>\n";
+ $retval .= "<tr>\n";
+ $retval .= " <td style='width: 20%;'>" . __('Trigger name') . "</td>\n";
+ $retval .= " <td><input type='text' name='item_name' maxlength='64'\n";
+ $retval .= " value='{$item['item_name']}' /></td>\n";
+ $retval .= "</tr>\n";
+ $retval .= "<tr>\n";
+ $retval .= " <td>" . __('Table') . "</td>\n";
+ $retval .= " <td>\n";
+ $retval .= " <select name='item_table'>\n";
+ foreach ($tables as $key => $value) {
+ $selected = "";
+ if ($mode == 'add' && $value == $table) {
+ $selected = " selected='selected'";
+ } else if ($mode == 'edit' && $value == $item['item_table']) {
+ $selected = " selected='selected'";
+ }
+ $retval .= "<option$selected>";
+ $retval .= htmlspecialchars($value);
+ $retval .= "</option>\n";
+ }
+ $retval .= " </select>\n";
+ $retval .= " </td>\n";
+ $retval .= "</tr>\n";
+ $retval .= "<tr>\n";
+ $retval .= " <td>" . _pgettext('Trigger action time', 'Time') . "</td>\n";
+ $retval .= " <td><select name='item_timing'>\n";
+ foreach ($action_timings as $key => $value) {
+ $selected = "";
+ if (! empty($item['item_action_timing'])
+ && $item['item_action_timing'] == $value
+ ) {
+ $selected = " selected='selected'";
+ }
+ $retval .= "<option$selected>$value</option>";
+ }
+ $retval .= " </select></td>\n";
+ $retval .= "</tr>\n";
+ $retval .= "<tr>\n";
+ $retval .= " <td>" . __('Event') . "</td>\n";
+ $retval .= " <td><select name='item_event'>\n";
+ foreach ($event_manipulations as $key => $value) {
+ $selected = "";
+ if (! empty($item['item_event_manipulation'])
+ && $item['item_event_manipulation'] == $value
+ ) {
+ $selected = " selected='selected'";
+ }
+ $retval .= "<option$selected>$value</option>";
+ }
+ $retval .= " </select></td>\n";
+ $retval .= "</tr>\n";
+ $retval .= "<tr>\n";
+ $retval .= " <td>" . __('Definition') . "</td>\n";
+ $retval .= " <td><textarea name='item_definition' rows='15' cols='40'>";
+ $retval .= $item['item_definition'];
+ $retval .= "</textarea></td>\n";
+ $retval .= "</tr>\n";
+ $retval .= "<tr>\n";
+ $retval .= " <td>" . __('Definer') . "</td>\n";
+ $retval .= " <td><input type='text' name='item_definer'\n";
+ $retval .= " value='{$item['item_definer']}' /></td>\n";
+ $retval .= "</tr>\n";
+ $retval .= "</table>\n";
+ $retval .= "</fieldset>\n";
+ if ($GLOBALS['is_ajax_request']) {
+ $retval .= "<input type='hidden' name='editor_process_{$mode}'\n";
+ $retval .= " value='true' />\n";
+ $retval .= "<input type='hidden' name='ajax_request' value='true' />\n";
+ } else {
+ $retval .= "<fieldset class='tblFooters'>\n";
+ $retval .= " <input type='submit' name='editor_process_{$mode}'\n";
+ $retval .= " value='" . __('Go') . "' />\n";
+ $retval .= "</fieldset>\n";
+ }
+ $retval .= "</form>\n\n";
+ $retval .= "<!-- END " . strtoupper($mode) . " TRIGGER FORM -->\n\n";
+
+ return $retval;
+} // end PMA_TRI_getEditorForm()
+
+/**
+ * Composes the query necessary to create a trigger from an HTTP request.
+ *
+ * @return string The CREATE TRIGGER query.
+ */
+function PMA_TRI_getQueryFromRequest()
+{
+ global $_REQUEST, $db, $errors, $action_timings, $event_manipulations;
+
+ $query = 'CREATE ';
+ if (! empty($_REQUEST['item_definer'])) {
+ if (strpos($_REQUEST['item_definer'], '@') !== false) {
+ $arr = explode('@', $_REQUEST['item_definer']);
+ $query .= 'DEFINER=' . PMA_Util::backquote($arr[0]);
+ $query .= '@' . PMA_Util::backquote($arr[1]) . ' ';
+ } else {
+ $errors[] = __('The definer must be in the "username@hostname" format');
+ }
+ }
+ $query .= 'TRIGGER ';
+ if (! empty($_REQUEST['item_name'])) {
+ $query .= PMA_Util::backquote($_REQUEST['item_name']) . ' ';
+ } else {
+ $errors[] = __('You must provide a trigger name');
+ }
+ if (! empty($_REQUEST['item_timing'])
+ && in_array($_REQUEST['item_timing'], $action_timings)
+ ) {
+ $query .= $_REQUEST['item_timing'] . ' ';
+ } else {
+ $errors[] = __('You must provide a valid timing for the trigger');
+ }
+ if (! empty($_REQUEST['item_event'])
+ && in_array($_REQUEST['item_event'], $event_manipulations)
+ ) {
+ $query .= $_REQUEST['item_event'] . ' ';
+ } else {
+ $errors[] = __('You must provide a valid event for the trigger');
+ }
+ $query .= 'ON ';
+ if (! empty($_REQUEST['item_table'])
+ && in_array($_REQUEST['item_table'], $GLOBALS['dbi']->getTables($db))
+ ) {
+ $query .= PMA_Util::backquote($_REQUEST['item_table']);
+ } else {
+ $errors[] = __('You must provide a valid table name');
+ }
+ $query .= ' FOR EACH ROW ';
+ if (! empty($_REQUEST['item_definition'])) {
+ $query .= $_REQUEST['item_definition'];
+ } else {
+ $errors[] = __('You must provide a trigger definition.');
+ }
+
+ return $query;
+} // end PMA_TRI_getQueryFromRequest()
+
+?>
diff --git a/libraries/rte/rte_words.lib.php b/libraries/rte/rte_words.lib.php
new file mode 100644
index 0000000000..85210bfdbf
--- /dev/null
+++ b/libraries/rte/rte_words.lib.php
@@ -0,0 +1,69 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Helper functions for RTE
+ *
+ * @package PhpMyAdmin
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ * This function is used to retreive some language strings that are used
+ * in functionalities that are common to routines, triggers and events.
+ *
+ * @param string $index The index of the string to get
+ *
+ * @return string The requested string or an empty string, if not available
+ */
+function PMA_RTE_getWord($index)
+{
+ global $_PMA_RTE;
+
+ switch ($_PMA_RTE) {
+ case 'RTN':
+ $words = array(
+ 'add' => __('Add routine'),
+ 'docu' => 'STORED_ROUTINES',
+ 'export' => __('Export of routine %s'),
+ 'human' => __('routine'),
+ 'no_create' => __('You do not have the necessary privileges to create a routine'),
+ 'not_found' => __('No routine with name %1$s found in database %2$s'),
+ 'nothing' => __('There are no routines to display.'),
+ 'title' => __('Routines'),
+ );
+ break;
+ case 'TRI':
+ $words = array(
+ 'add' => __('Add trigger'),
+ 'docu' => 'TRIGGERS',
+ 'export' => __('Export of trigger %s'),
+ 'human' => __('trigger'),
+ 'no_create' => __('You do not have the necessary privileges to create a trigger'),
+ 'not_found' => __('No trigger with name %1$s found in database %2$s'),
+ 'nothing' => __('There are no triggers to display.'),
+ 'title' => __('Triggers'),
+ );
+ break;
+ case 'EVN':
+ $words = array(
+ 'add' => __('Add event'),
+ 'docu' => 'EVENTS',
+ 'export' => __('Export of event %s'),
+ 'human' => __('event'),
+ 'no_create' => __('You do not have the necessary privileges to create an event'),
+ 'not_found' => __('No event with name %1$s found in database %2$s'),
+ 'nothing' => __('There are no events to display.'),
+ 'title' => __('Events'),
+ );
+ break;
+ default:
+ $words = array();
+ break;
+ }
+
+ return isset($words[$index]) ? $words[$index] : '';
+} // end PMA_RTE_getWord()
+
+?>
diff --git a/libraries/sanitizing.lib.php b/libraries/sanitizing.lib.php
new file mode 100644
index 0000000000..7e6e84db5b
--- /dev/null
+++ b/libraries/sanitizing.lib.php
@@ -0,0 +1,191 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * This is in a separate script because it's called from a number of scripts
+ *
+ * @package PhpMyAdmin
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ * Checks whether given link is valid
+ *
+ * @param string $url URL to check
+ *
+ * @return boolean True if string can be used as link
+ */
+function PMA_checkLink($url)
+{
+ $valid_starts = array(
+ 'http://',
+ 'https://',
+ './url.php?url=http%3A%2F%2F',
+ './url.php?url=https%3A%2F%2F',
+ './doc/html/',
+ );
+ if (defined('PMA_SETUP')) {
+ $valid_starts[] = '?page=form&';
+ $valid_starts[] = '?page=servers&';
+ }
+ foreach ($valid_starts as $val) {
+ if (substr($url, 0, strlen($val)) == $val) {
+ return true;
+ }
+ }
+ return false;
+}
+
+/**
+ * Callback function for replacing [a@link@target] links in bb code.
+ *
+ * @param array $found Array of preg matches
+ *
+ * @return string Replaced string
+ */
+function PMA_replaceBBLink($found)
+{
+ /* Check for valid link */
+ if (! PMA_checkLink($found[1])) {
+ return $found[0];
+ }
+ /* a-z and _ allowed in target */
+ if (! empty($found[3]) && preg_match('/[^a-z_]+/i', $found[3])) {
+ return $found[0];
+ }
+
+ /* Construct target */
+ $target = '';
+ if (! empty($found[3])) {
+ $target = ' target="' . $found[3] . '"';
+ }
+
+ /* Construct url */
+ if (substr($found[1], 0, 4) == 'http') {
+ $url = PMA_linkURL($found[1]);
+ } else {
+ $url = $found[1];
+ }
+
+ return '<a href="' . $url . '"' . $target . '>';
+}
+
+/**
+ * Callback function for replacing [doc@anchor] links in bb code.
+ *
+ * @param array $found Array of preg matches
+ *
+ * @return string Replaced string
+ */
+function PMA_replaceDocLink($found)
+{
+ $anchor = $found[1];
+ if (strncmp('faq', $anchor, 3) == 0) {
+ $page = 'faq';
+ } else if (strncmp('cfg', $anchor, 3) == 0) {
+ $page = 'cfg';
+ } else {
+ /* Guess */
+ $page = 'setup';
+ }
+ $link = PMA_Util::getDocuLink($page, $anchor);
+ return '<a href="' . $link . '" target="documentation">';
+}
+
+/**
+ * Sanitizes $message, taking into account our special codes
+ * for formatting.
+ *
+ * If you want to include result in element attribute, you should escape it.
+ *
+ * Examples:
+ *
+ * <p><?php echo PMA_sanitize($foo); ?></p>
+ *
+ * <a title="<?php echo PMA_sanitize($foo, true); ?>">bar</a>
+ *
+ * @param string $message the message
+ * @param boolean $escape whether to escape html in result
+ * @param boolean $safe whether string is safe (can keep < and > chars)
+ *
+ * @return string the sanitized message
+ */
+function PMA_sanitize($message, $escape = false, $safe = false)
+{
+ if (!$safe) {
+ $message = strtr($message, array('<' => '&lt;', '>' => '&gt;'));
+ }
+
+ /* Interpret bb code */
+ $replace_pairs = array(
+ '[em]' => '<em>',
+ '[/em]' => '</em>',
+ '[strong]' => '<strong>',
+ '[/strong]' => '</strong>',
+ '[code]' => '<code>',
+ '[/code]' => '</code>',
+ '[kbd]' => '<kbd>',
+ '[/kbd]' => '</kbd>',
+ '[br]' => '<br />',
+ '[/a]' => '</a>',
+ '[/doc]' => '</a>',
+ '[sup]' => '<sup>',
+ '[/sup]' => '</sup>',
+ // used in common.inc.php:
+ '[conferr]' => '<iframe src="show_config_errors.php" />',
+ );
+
+ $message = strtr($message, $replace_pairs);
+
+ /* Match links in bb code ([a@url@target], where @target is options) */
+ $pattern = '/\[a@([^]"@]*)(@([^]"]*))?\]/';
+
+ /* Find and replace all links */
+ $message = preg_replace_callback($pattern, 'PMA_replaceBBLink', $message);
+
+ /* Replace documentation links */
+ $message = preg_replace_callback(
+ '/\[doc@([a-zA-Z0-9_-]+)\]/',
+ 'PMA_replaceDocLink',
+ $message
+ );
+
+ /* Possibly escape result */
+ if ($escape) {
+ $message = htmlspecialchars($message);
+ }
+
+ return $message;
+}
+
+
+/**
+ * Sanitize a filename by removing anything besides legit characters
+ *
+ * Intended usecase:
+ * When using a filename in a Content-Disposition header
+ * the value should not contain ; or "
+ *
+ * When exporting, avoiding generation of an unexpected double-extension file
+ *
+ * @param string $filename The filename
+ * @param boolean $replaceDots Whether to also replace dots
+ *
+ * @return string the sanitized filename
+ *
+ */
+function PMA_sanitizeFilename($filename, $replaceDots = false)
+{
+ $pattern = '/[^A-Za-z0-9_';
+ // if we don't have to replace dots
+ if (! $replaceDots) {
+ // then add the dot to the list of legit characters
+ $pattern .= '.';
+ }
+ $pattern .= '-]/';
+ $filename = preg_replace($pattern, '_', $filename);
+ return $filename;
+}
+
+?>
diff --git a/libraries/schema/Dia_Relation_Schema.class.php b/libraries/schema/Dia_Relation_Schema.class.php
new file mode 100644
index 0000000000..8282b4a1fe
--- /dev/null
+++ b/libraries/schema/Dia_Relation_Schema.class.php
@@ -0,0 +1,847 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ *
+ * @package PhpMyAdmin
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+require_once 'Export_Relation_Schema.class.php';
+
+/**
+ * This Class inherits the XMLwriter class and
+ * helps in developing structure of DIA Schema Export
+ *
+ * @package PhpMyAdmin
+ * @access public
+ * @see http://php.net/manual/en/book.xmlwriter.php
+ */
+class PMA_DIA extends XMLWriter
+{
+ public $title;
+ public $author;
+ public $font;
+ public $fontSize;
+
+ /**
+ * The "PMA_DIA" constructor
+ *
+ * Upon instantiation This starts writing the Dia XML document
+ *
+ * @see XMLWriter::openMemory(),XMLWriter::setIndent(),XMLWriter::startDocument()
+ */
+ function __construct()
+ {
+ $this->openMemory();
+ /*
+ * Set indenting using three spaces,
+ * so output is formatted
+ */
+
+ $this->setIndent(true);
+ $this->setIndentString(' ');
+ /*
+ * Create the XML document
+ */
+
+ $this->startDocument('1.0', 'UTF-8');
+ }
+
+ /**
+ * Starts Dia Document
+ *
+ * dia document starts by first initializing dia:diagram tag
+ * then dia:diagramdata contains all the attributes that needed
+ * to define the document, then finally a Layer starts which
+ * holds all the objects.
+ *
+ * @param string $paper the size of the paper/document
+ * @param float $topMargin top margin of the paper/document in cm
+ * @param float $bottomMargin bottom margin of the paper/document in cm
+ * @param float $leftMargin left margin of the paper/document in cm
+ * @param float $rightMargin right margin of the paper/document in cm
+ * @param string $portrait document will be portrait or landscape
+ *
+ * @return void
+ *
+ * @access public
+ * @see XMLWriter::startElement(),XMLWriter::writeAttribute(),
+ * XMLWriter::writeRaw()
+ */
+ function startDiaDoc($paper, $topMargin, $bottomMargin, $leftMargin,
+ $rightMargin, $portrait
+ ) {
+ if ($portrait == 'P') {
+ $isPortrait='true';
+ } else {
+ $isPortrait='false';
+ }
+ $this->startElement('dia:diagram');
+ $this->writeAttribute('xmlns:dia', 'http://www.lysator.liu.se/~alla/dia/');
+ $this->startElement('dia:diagramdata');
+ $this->writeRaw(
+ '<dia:attribute name="background">
+ <dia:color val="#ffffff"/>
+ </dia:attribute>
+ <dia:attribute name="pagebreak">
+ <dia:color val="#000099"/>
+ </dia:attribute>
+ <dia:attribute name="paper">
+ <dia:composite type="paper">
+ <dia:attribute name="name">
+ <dia:string>#' . $paper . '#</dia:string>
+ </dia:attribute>
+ <dia:attribute name="tmargin">
+ <dia:real val="' . $topMargin . '"/>
+ </dia:attribute>
+ <dia:attribute name="bmargin">
+ <dia:real val="' . $bottomMargin . '"/>
+ </dia:attribute>
+ <dia:attribute name="lmargin">
+ <dia:real val="' . $leftMargin . '"/>
+ </dia:attribute>
+ <dia:attribute name="rmargin">
+ <dia:real val="' . $rightMargin . '"/>
+ </dia:attribute>
+ <dia:attribute name="is_portrait">
+ <dia:boolean val="' . $isPortrait . '"/>
+ </dia:attribute>
+ <dia:attribute name="scaling">
+ <dia:real val="1"/>
+ </dia:attribute>
+ <dia:attribute name="fitto">
+ <dia:boolean val="false"/>
+ </dia:attribute>
+ </dia:composite>
+ </dia:attribute>
+ <dia:attribute name="grid">
+ <dia:composite type="grid">
+ <dia:attribute name="width_x">
+ <dia:real val="1"/>
+ </dia:attribute>
+ <dia:attribute name="width_y">
+ <dia:real val="1"/>
+ </dia:attribute>
+ <dia:attribute name="visible_x">
+ <dia:int val="1"/>
+ </dia:attribute>
+ <dia:attribute name="visible_y">
+ <dia:int val="1"/>
+ </dia:attribute>
+ <dia:composite type="color"/>
+ </dia:composite>
+ </dia:attribute>
+ <dia:attribute name="color">
+ <dia:color val="#d8e5e5"/>
+ </dia:attribute>
+ <dia:attribute name="guides">
+ <dia:composite type="guides">
+ <dia:attribute name="hguides"/>
+ <dia:attribute name="vguides"/>
+ </dia:composite>
+ </dia:attribute>'
+ );
+ $this->endElement();
+ $this->startElement('dia:layer');
+ $this->writeAttribute('name', 'Background');
+ $this->writeAttribute('visible', 'true');
+ $this->writeAttribute('active', 'true');
+ }
+
+ /**
+ * Ends Dia Document
+ *
+ * @return void
+ * @access public
+ * @see XMLWriter::endElement(),XMLWriter::endDocument()
+ */
+ function endDiaDoc()
+ {
+ $this->endElement();
+ $this->endDocument();
+ }
+
+ /**
+ * Output Dia Document for download
+ *
+ * @param string $fileName name of the dia document
+ *
+ * @return void
+ * @access public
+ * @see XMLWriter::flush()
+ */
+ function showOutput($fileName)
+ {
+ if (ob_get_clean()) {
+ ob_end_clean();
+ }
+ $output = $this->flush();
+ PMA_Response::getInstance()->disable();
+ PMA_downloadHeader(
+ $fileName . '.dia', 'application/x-dia-diagram', strlen($output)
+ );
+ print $output;
+ }
+}
+
+/**
+ * Table preferences/statistics
+ *
+ * This class preserves the table co-ordinates,fields
+ * and helps in drawing/generating the Tables in dia XML document.
+ *
+ * @package PhpMyAdmin
+ * @name Table_Stats_Dia
+ * @see PMA_DIA
+ */
+class Table_Stats_Dia
+{
+ /**
+ * Defines properties
+ */
+ public $tableName;
+ public $fields = array();
+ public $x, $y;
+ public $primary = array();
+ public $tableId;
+ public $tableColor;
+
+ /**
+ * The "Table_Stats_Dia" constructor
+ *
+ * @param string $tableName The table name
+ * @param integer $pageNumber The current page number (from the
+ * $cfg['Servers'][$i]['table_coords'] table)
+ * @param boolean $showKeys Whether to display ONLY keys or not
+ *
+ * @global object $dia The current dia document
+ * @global array $cfgRelation The relations settings
+ * @global string $db The current db name
+ *
+ * @see PMA_DIA
+ */
+ function __construct($tableName, $pageNumber, $showKeys = false)
+ {
+ global $dia, $cfgRelation, $db;
+
+ $this->tableName = $tableName;
+ $sql = 'DESCRIBE ' . PMA_Util::backquote($tableName);
+ $result = $GLOBALS['dbi']->tryQuery(
+ $sql, null, PMA_DatabaseInterface::QUERY_STORE
+ );
+ if (! $result || ! $GLOBALS['dbi']->numRows($result)) {
+ $dia->dieSchema(
+ $pageNumber, "DIA",
+ sprintf(__('The %s table doesn\'t exist!'), $tableName)
+ );
+ }
+ /*
+ * load fields
+ * check to see if it will load all fields or only the foreign keys
+ */
+ if ($showKeys) {
+ $indexes = PMA_Index::getFromTable($this->tableName, $db);
+ $all_columns = array();
+ foreach ($indexes as $index) {
+ $all_columns = array_merge(
+ $all_columns,
+ array_flip(array_keys($index->getColumns()))
+ );
+ }
+ $this->fields = array_keys($all_columns);
+ } else {
+ while ($row = $GLOBALS['dbi']->fetchRow($result)) {
+ $this->fields[] = $row[0];
+ }
+ }
+
+ $sql = 'SELECT x, y FROM '
+ . PMA_Util::backquote($GLOBALS['cfgRelation']['db']) . '.'
+ . PMA_Util::backquote($cfgRelation['table_coords'])
+ . ' WHERE db_name = \'' . PMA_Util::sqlAddSlashes($db) . '\''
+ . ' AND table_name = \''
+ . PMA_Util::sqlAddSlashes($tableName) . '\''
+ . ' AND pdf_page_number = ' . $pageNumber;
+ $result = PMA_queryAsControlUser(
+ $sql, false, PMA_DatabaseInterface::QUERY_STORE
+ );
+ if (! $result || ! $GLOBALS['dbi']->numRows($result)) {
+ $dia->dieSchema(
+ $pageNumber,
+ "DIA",
+ sprintf(
+ __('Please configure the coordinates for table %s'),
+ $tableName
+ )
+ );
+ }
+ list($this->x, $this->y) = $GLOBALS['dbi']->fetchRow($result);
+ $this->x = (double) $this->x;
+ $this->y = (double) $this->y;
+ /*
+ * displayfield
+ */
+ $this->displayfield = PMA_getDisplayField($db, $tableName);
+ /*
+ * index
+ */
+ $result = $GLOBALS['dbi']->query(
+ 'SHOW INDEX FROM ' . PMA_Util::backquote($tableName) . ';',
+ null,
+ PMA_DatabaseInterface::QUERY_STORE
+ );
+ if ($GLOBALS['dbi']->numRows($result) > 0) {
+ while ($row = $GLOBALS['dbi']->fetchAssoc($result)) {
+ if ($row['Key_name'] == 'PRIMARY') {
+ $this->primary[] = $row['Column_name'];
+ }
+ }
+ }
+ /**
+ * Every object in Dia document needs an ID to identify
+ * so, we used a static variable to keep the things unique
+ */
+ PMA_Dia_Relation_Schema::$objectId += 1;
+ $this->tableId = PMA_Dia_Relation_Schema::$objectId;
+ }
+
+ /**
+ * Do draw the table
+ *
+ * Tables are generated using object type Database - Table
+ * primary fields are underlined in tables. Dia object
+ * is used to generate the XML of Dia Document. Database Table
+ * Object and their attributes are involved in the combination
+ * of displaing Database - Table on Dia Document.
+ *
+ * @param boolean $changeColor Whether to show color for tables text or not
+ * if changeColor is true then an array of $listOfColors will be used to choose
+ * the random colors for tables text we can change/add more colors to this array
+ *
+ * @return void
+ *
+ * @global object $dia The current Dia document
+ *
+ * @access public
+ * @see PMA_DIA
+ */
+ public function tableDraw($changeColor)
+ {
+ global $dia;
+
+ if ($changeColor) {
+ $listOfColors = array(
+ 'FF0000',
+ '000099',
+ '00FF00'
+ );
+ shuffle($listOfColors);
+ $this->tableColor = '#' . $listOfColors[0] . '';
+ } else {
+ $this->tableColor = '#000000';
+ }
+
+ $factor = 0.1;
+
+ $dia->startElement('dia:object');
+ $dia->writeAttribute('type', 'Database - Table');
+ $dia->writeAttribute('version', '0');
+ $dia->writeAttribute('id', '' . $this->tableId . '');
+ $dia->writeRaw(
+ '<dia:attribute name="obj_pos">
+ <dia:point val="'
+ . ($this->x * $factor) . ',' . ($this->y * $factor) . '"/>
+ </dia:attribute>
+ <dia:attribute name="obj_bb">
+ <dia:rectangle val="'
+ .($this->x * $factor) . ',' . ($this->y * $factor) . ';9.97,9.2"/>
+ </dia:attribute>
+ <dia:attribute name="meta">
+ <dia:composite type="dict"/>
+ </dia:attribute>
+ <dia:attribute name="elem_corner">
+ <dia:point val="'
+ . ($this->x * $factor) . ',' . ($this->y * $factor) . '"/>
+ </dia:attribute>
+ <dia:attribute name="elem_width">
+ <dia:real val="5.9199999999999999"/>
+ </dia:attribute>
+ <dia:attribute name="elem_height">
+ <dia:real val="3.5"/>
+ </dia:attribute>
+ <dia:attribute name="text_colour">
+ <dia:color val="' . $this->tableColor . '"/>
+ </dia:attribute>
+ <dia:attribute name="line_colour">
+ <dia:color val="#000000"/>
+ </dia:attribute>
+ <dia:attribute name="fill_colour">
+ <dia:color val="#ffffff"/>
+ </dia:attribute>
+ <dia:attribute name="line_width">
+ <dia:real val="0.10000000000000001"/>
+ </dia:attribute>
+ <dia:attribute name="name">
+ <dia:string>#' . $this->tableName . '#</dia:string>
+ </dia:attribute>
+ <dia:attribute name="comment">
+ <dia:string>##</dia:string>
+ </dia:attribute>
+ <dia:attribute name="visible_comment">
+ <dia:boolean val="false"/>
+ </dia:attribute>
+ <dia:attribute name="tagging_comment">
+ <dia:boolean val="false"/>
+ </dia:attribute>
+ <dia:attribute name="underline_primary_key">
+ <dia:boolean val="true"/>
+ </dia:attribute>
+ <dia:attribute name="bold_primary_keys">
+ <dia:boolean val="true"/>
+ </dia:attribute>
+ <dia:attribute name="normal_font">
+ <dia:font family="monospace" style="0" name="Courier"/>
+ </dia:attribute>
+ <dia:attribute name="name_font">
+ <dia:font family="sans" style="80" name="Helvetica-Bold"/>
+ </dia:attribute>
+ <dia:attribute name="comment_font">
+ <dia:font family="sans" style="0" name="Helvetica"/>
+ </dia:attribute>
+ <dia:attribute name="normal_font_height">
+ <dia:real val="0.80000000000000004"/>
+ </dia:attribute>
+ <dia:attribute name="name_font_height">
+ <dia:real val="0.69999999999999996"/>
+ </dia:attribute>
+ <dia:attribute name="comment_font_height">
+ <dia:real val="0.69999999999999996"/>
+ </dia:attribute>'
+ );
+
+ $dia->startElement('dia:attribute');
+ $dia->writeAttribute('name', 'attributes');
+
+ foreach ($this->fields as $field) {
+ $dia->writeRaw(
+ '<dia:composite type="table_attribute">
+ <dia:attribute name="name">
+ <dia:string>#' . $field . '#</dia:string>
+ </dia:attribute>
+ <dia:attribute name="type">
+ <dia:string>##</dia:string>
+ </dia:attribute>
+ <dia:attribute name="comment">
+ <dia:string>##</dia:string>
+ </dia:attribute>'
+ );
+ unset($pm);
+ $pm = 'false';
+ if (in_array($field, $this->primary)) {
+ $pm = 'true';
+ }
+ if ($field == $this->displayfield) {
+ $pm = 'false';
+ }
+ $dia->writeRaw(
+ '<dia:attribute name="primary_key">
+ <dia:boolean val="' . $pm . '"/>
+ </dia:attribute>
+ <dia:attribute name="nullable">
+ <dia:boolean val="false"/>
+ </dia:attribute>
+ <dia:attribute name="unique">
+ <dia:boolean val="' . $pm . '"/>
+ </dia:attribute>
+ </dia:composite>'
+ );
+ }
+ $dia->endElement();
+ $dia->endElement();
+ }
+}
+
+/**
+ * Relation preferences/statistics
+ *
+ * This class fetches the table master and foreign fields positions
+ * and helps in generating the Table references and then connects
+ * master table's master field to foreign table's foreign key
+ * in dia XML document.
+ *
+ * @package PhpMyAdmin
+ * @name Relation_Stats_Dia
+ * @see PMA_DIA
+ */
+class Relation_Stats_Dia
+{
+ /**
+ * Defines properties
+ */
+ public $srcConnPointsRight;
+ public $srcConnPointsLeft;
+ public $destConnPointsRight;
+ public $destConnPointsLeft;
+ public $masterTableId;
+ public $foreignTableId;
+ public $masterTablePos;
+ public $foreignTablePos;
+ public $referenceColor;
+
+ /**
+ * The "Relation_Stats_Dia" constructor
+ *
+ * @param string $master_table The master table name
+ * @param string $master_field The relation field in the master table
+ * @param string $foreign_table The foreign table name
+ * @param string $foreign_field The relation field in the foreign table
+ *
+ * @see Relation_Stats_Dia::_getXy
+ */
+ function __construct($master_table, $master_field, $foreign_table,
+ $foreign_field
+ ) {
+ $src_pos = $this->_getXy($master_table, $master_field);
+ $dest_pos = $this->_getXy($foreign_table, $foreign_field);
+ $this->srcConnPointsLeft = $src_pos[0];
+ $this->srcConnPointsRight = $src_pos[1];
+ $this->destConnPointsLeft = $dest_pos[0];
+ $this->destConnPointsRight = $dest_pos[1];
+ $this->masterTablePos = $src_pos[2];
+ $this->foreignTablePos = $dest_pos[2];
+ $this->masterTableId = $master_table->tableId;
+ $this->foreignTableId = $foreign_table->tableId;
+ }
+
+ /**
+ * Each Table object have connection points
+ * which is used to connect to other objects in Dia
+ * we detect the position of key in fields and
+ * then determines its left and right connection
+ * points.
+ *
+ * @param string $table The current table name
+ * @param string $column The relation column name
+ *
+ * @return array Table right,left connection points and key position
+ *
+ * @access private
+ */
+ private function _getXy($table, $column)
+ {
+ $pos = array_search($column, $table->fields);
+ // left, right, position
+ $value = 12;
+ if ($pos != 0) {
+ return array($pos + $value + $pos, $pos + $value + $pos + 1, $pos);
+ }
+ return array($pos + $value , $pos + $value + 1, $pos);
+ }
+
+ /**
+ * Draws relation references
+ *
+ * connects master table's master field to foreign table's
+ * forein field using Dia object type Database - Reference
+ * Dia object is used to generate the XML of Dia Document.
+ * Database reference Object and their attributes are involved
+ * in the combination of displaing Database - reference on Dia Document.
+ *
+ * @param boolean $changeColor Whether to use one color per relation or not
+ * if changeColor is true then an array of $listOfColors will be used to choose
+ * the random colors for references lines. we can change/add more colors to this
+ *
+ * @return boolean|void
+ *
+ * @global object $dia The current Dia document
+ *
+ * @access public
+ * @see PMA_PDF
+ */
+ public function relationDraw($changeColor)
+ {
+ global $dia;
+
+ PMA_Dia_Relation_Schema::$objectId += 1;
+ /*
+ * if source connection points and destination connection
+ * points are same then return it false and don't draw that
+ * relation
+ */
+ if ( $this->srcConnPointsRight == $this->destConnPointsRight) {
+ if ( $this->srcConnPointsLeft == $this->destConnPointsLeft) {
+ return false;
+ }
+ }
+
+ if ($changeColor) {
+ $listOfColors = array(
+ 'FF0000',
+ '000099',
+ '00FF00'
+ );
+ shuffle($listOfColors);
+ $this->referenceColor = '#' . $listOfColors[0] . '';
+ } else {
+ $this->referenceColor = '#000000';
+ }
+
+ $dia->writeRaw(
+ '<dia:object type="Database - Reference" version="0" id="'
+ . PMA_Dia_Relation_Schema::$objectId . '">
+ <dia:attribute name="obj_pos">
+ <dia:point val="3.27,18.9198"/>
+ </dia:attribute>
+ <dia:attribute name="obj_bb">
+ <dia:rectangle val="2.27,8.7175;17.7679,18.9198"/>
+ </dia:attribute>
+ <dia:attribute name="meta">
+ <dia:composite type="dict"/>
+ </dia:attribute>
+ <dia:attribute name="orth_points">
+ <dia:point val="3.27,18.9198"/>
+ <dia:point val="2.27,18.9198"/>
+ <dia:point val="2.27,14.1286"/>
+ <dia:point val="17.7679,14.1286"/>
+ <dia:point val="17.7679,9.3375"/>
+ <dia:point val="16.7679,9.3375"/>
+ </dia:attribute>
+ <dia:attribute name="orth_orient">
+ <dia:enum val="0"/>
+ <dia:enum val="1"/>
+ <dia:enum val="0"/>
+ <dia:enum val="1"/>
+ <dia:enum val="0"/>
+ </dia:attribute>
+ <dia:attribute name="orth_autoroute">
+ <dia:boolean val="true"/>
+ </dia:attribute>
+ <dia:attribute name="text_colour">
+ <dia:color val="#000000"/>
+ </dia:attribute>
+ <dia:attribute name="line_colour">
+ <dia:color val="' . $this->referenceColor . '"/>
+ </dia:attribute>
+ <dia:attribute name="line_width">
+ <dia:real val="0.10000000000000001"/>
+ </dia:attribute>
+ <dia:attribute name="line_style">
+ <dia:enum val="0"/>
+ <dia:real val="1"/>
+ </dia:attribute>
+ <dia:attribute name="corner_radius">
+ <dia:real val="0"/>
+ </dia:attribute>
+ <dia:attribute name="end_arrow">
+ <dia:enum val="22"/>
+ </dia:attribute>
+ <dia:attribute name="end_arrow_length">
+ <dia:real val="0.5"/>
+ </dia:attribute>
+ <dia:attribute name="end_arrow_width">
+ <dia:real val="0.5"/>
+ </dia:attribute>
+ <dia:attribute name="start_point_desc">
+ <dia:string>#1#</dia:string>
+ </dia:attribute>
+ <dia:attribute name="end_point_desc">
+ <dia:string>#n#</dia:string>
+ </dia:attribute>
+ <dia:attribute name="normal_font">
+ <dia:font family="monospace" style="0" name="Courier"/>
+ </dia:attribute>
+ <dia:attribute name="normal_font_height">
+ <dia:real val="0.59999999999999998"/>
+ </dia:attribute>
+ <dia:connections>
+ <dia:connection handle="0" to="'
+ . $this->masterTableId . '" connection="'
+ . $this->srcConnPointsRight . '"/>
+ <dia:connection handle="1" to="'
+ . $this->foreignTableId . '" connection="'
+ . $this->destConnPointsRight . '"/>
+ </dia:connections>
+ </dia:object>'
+ );
+ }
+}
+
+/**
+ * Dia Relation Schema Class
+ *
+ * Purpose of this class is to generate the Dia XML Document
+ * which is used for representing the database diagrams in Dia IDE
+ * This class uses Database Table and Reference Objects of Dia and with
+ * the combination of these objects actually helps in preparing Dia XML.
+ *
+ * Dia XML is generated by using XMLWriter php extension and this class
+ * inherits Export_Relation_Schema class has common functionality added
+ * to this class
+ *
+ * @package PhpMyAdmin
+ * @name Dia_Relation_Schema
+ */
+class PMA_Dia_Relation_Schema extends PMA_Export_Relation_Schema
+{
+ /**
+ * Defines properties
+ */
+ private $_tables = array();
+ private $_relations = array();
+ private $_topMargin = 2.8222000598907471;
+ private $_bottomMargin = 2.8222000598907471;
+ private $_leftMargin = 2.8222000598907471;
+ private $_rightMargin = 2.8222000598907471;
+ public static $objectId = 0;
+
+ /**
+ * The "PMA_Dia_Relation_Schema" constructor
+ *
+ * Upon instantiation This outputs the Dia XML document
+ * that user can download
+ *
+ * @see PMA_DIA,Table_Stats_Dia,Relation_Stats_Dia
+ */
+ function __construct()
+ {
+ global $dia,$db;
+
+ $this->setPageNumber($_POST['pdf_page_number']);
+ $this->setShowGrid(isset($_POST['show_grid']));
+ $this->setShowColor($_POST['show_color']);
+ $this->setShowKeys(isset($_POST['show_keys']));
+ $this->setOrientation(isset($_POST['orientation']));
+ $this->setPaper($_POST['paper']);
+ $this->setExportType($_POST['export_type']);
+
+ $dia = new PMA_DIA();
+ $dia->startDiaDoc(
+ $this->paper, $this->_topMargin, $this->_bottomMargin,
+ $this->_leftMargin, $this->_rightMargin, $this->orientation
+ );
+ $alltables = $this->getAllTables($db, $this->pageNumber);
+ foreach ($alltables as $table) {
+ if (! isset($this->tables[$table])) {
+ $this->_tables[$table] = new Table_Stats_Dia(
+ $table, $this->pageNumber, $this->showKeys
+ );
+ }
+ }
+
+ $seen_a_relation = false;
+ foreach ($alltables as $one_table) {
+ $exist_rel = PMA_getForeigners($db, $one_table, '', 'both');
+ if ($exist_rel) {
+ $seen_a_relation = true;
+ foreach ($exist_rel as $master_field => $rel) {
+ /* put the foreign table on the schema only if selected
+ * by the user
+ * (do not use array_search() because we would have to
+ * to do a === false and this is not PHP3 compatible)
+ */
+ if (in_array($rel['foreign_table'], $alltables)) {
+ $this->_addRelation(
+ $one_table, $master_field, $rel['foreign_table'],
+ $rel['foreign_field'], $this->showKeys
+ );
+ }
+ }
+ }
+ }
+ $this->_drawTables($this->showColor);
+
+ if ($seen_a_relation) {
+ $this->_drawRelations($this->showColor);
+ }
+ $dia->endDiaDoc();
+ }
+
+ /**
+ * Output Dia Document for download
+ *
+ * @return void
+ * @access public
+ */
+ function showOutput()
+ {
+ global $dia, $db;
+ $dia->showOutput($db . '-' . $this->pageNumber);
+ }
+
+ /**
+ * Defines relation objects
+ *
+ * @param string $masterTable The master table name
+ * @param string $masterField The relation field in the master table
+ * @param string $foreignTable The foreign table name
+ * @param string $foreignField The relation field in the foreign table
+ * @param bool $showKeys Whether to display ONLY keys or not
+ *
+ * @return void
+ *
+ * @access private
+ * @see Table_Stats_Dia::__construct(),Relation_Stats_Dia::__construct()
+ */
+ private function _addRelation($masterTable, $masterField, $foreignTable,
+ $foreignField, $showKeys
+ ) {
+ if (! isset($this->_tables[$masterTable])) {
+ $this->_tables[$masterTable] = new Table_Stats_Dia(
+ $masterTable, $this->pageNumber, $showKeys
+ );
+ }
+ if (! isset($this->_tables[$foreignTable])) {
+ $this->_tables[$foreignTable] = new Table_Stats_Dia(
+ $foreignTable, $this->pageNumber, $showKeys
+ );
+ }
+ $this->_relations[] = new Relation_Stats_Dia(
+ $this->_tables[$masterTable], $masterField,
+ $this->_tables[$foreignTable], $foreignField
+ );
+ }
+
+ /**
+ * Draws relation references
+ *
+ * connects master table's master field to
+ * foreign table's forein field using Dia object
+ * type Database - Reference
+ *
+ * @param boolean $changeColor Whether to use one color per relation or not
+ *
+ * @return void
+ *
+ * @access private
+ * @see Relation_Stats_Dia::relationDraw()
+ */
+ private function _drawRelations($changeColor)
+ {
+ foreach ($this->_relations as $relation) {
+ $relation->relationDraw($changeColor);
+ }
+ }
+
+ /**
+ * Draws tables
+ *
+ * Tables are generated using Dia object type Database - Table
+ * primary fields are underlined and bold in tables
+ *
+ * @param boolean $changeColor Whether to show color for tables text or not
+ *
+ * @return void
+ *
+ * @access private
+ * @see Table_Stats_Dia::tableDraw()
+ */
+ private function _drawTables($changeColor)
+ {
+ foreach ($this->_tables as $table) {
+ $table->tableDraw($changeColor);
+ }
+ }
+}
+?>
diff --git a/libraries/schema/Eps_Relation_Schema.class.php b/libraries/schema/Eps_Relation_Schema.class.php
new file mode 100644
index 0000000000..1a3a3536c9
--- /dev/null
+++ b/libraries/schema/Eps_Relation_Schema.class.php
@@ -0,0 +1,973 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ *
+ * @package PhpMyAdmin
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+require_once 'Export_Relation_Schema.class.php';
+
+/**
+ * This Class is EPS Library and
+ * helps in developing structure of EPS Schema Export
+ *
+ * @package PhpMyAdmin
+ * @access public
+ * @see http://php.net/manual/en/book.xmlwriter.php
+ */
+
+class PMA_EPS
+{
+ public $font;
+ public $fontSize;
+ public $stringCommands;
+
+ /**
+ * The "PMA_EPS" constructor
+ *
+ * Upon instantiation This starts writing the EPS Document.
+ * %!PS-Adobe-3.0 EPSF-3.0 This is the MUST first comment to include
+ * it shows/tells that the Post Script document is purely under
+ * Document Structuring Convention [DSC] and is Compliant
+ * Encapsulated Post Script Document
+ *
+ * @access public
+ */
+ function __construct()
+ {
+ $this->stringCommands = "";
+ $this->stringCommands .= "%!PS-Adobe-3.0 EPSF-3.0 \n";
+ }
+
+ /**
+ * Set document title
+ *
+ * @param string $value sets the title text
+ *
+ * @return void
+ *
+ * @access public
+ */
+ function setTitle($value)
+ {
+ $this->stringCommands .= '%%Title: ' . $value . "\n";
+ }
+
+ /**
+ * Set document author
+ *
+ * @param string $value sets the author
+ *
+ * @return void
+ *
+ * @access public
+ */
+ function setAuthor($value)
+ {
+ $this->stringCommands .= '%%Creator: ' . $value . "\n";
+ }
+
+ /**
+ * Set document creation date
+ *
+ * @param string $value sets the date
+ *
+ * @return void
+ *
+ * @access public
+ */
+ function setDate($value)
+ {
+ $this->stringCommands .= '%%CreationDate: ' . $value . "\n";
+ }
+
+ /**
+ * Set document orientation
+ *
+ * @param string $value sets the author
+ *
+ * @return void
+ *
+ * @access public
+ */
+ function setOrientation($value)
+ {
+ $this->stringCommands .= "%%PageOrder: Ascend \n";
+ if ($value == "L") {
+ $value = "Landscape";
+ $this->stringCommands .= '%%Orientation: ' . $value . "\n";
+ } else {
+ $value = "Portrait";
+ $this->stringCommands .= '%%Orientation: ' . $value . "\n";
+ }
+ $this->stringCommands .= "%%EndComments \n";
+ $this->stringCommands .= "%%Pages 1 \n";
+ $this->stringCommands .= "%%BoundingBox: 72 150 144 170 \n";
+ }
+
+ /**
+ * Set the font and size
+ *
+ * font can be set whenever needed in EPS
+ *
+ * @param string $value sets the font name e.g Arial
+ * @param integer $size sets the size of the font e.g 10
+ *
+ * @return void
+ *
+ * @access public
+ */
+ function setFont($value, $size)
+ {
+ $this->font = $value;
+ $this->fontSize = $size;
+ $this->stringCommands .= "/" . $value . " findfont % Get the basic font\n";
+ $this->stringCommands .= "" . $size . " scalefont % Scale the font to $size points\n";
+ $this->stringCommands .= "setfont % Make it the current font\n";
+ }
+
+ /**
+ * Get the font
+ *
+ * @return string return the font name e.g Arial
+ * @access public
+ */
+ function getFont()
+ {
+ return $this->font;
+ }
+
+ /**
+ * Get the font Size
+ *
+ * @return string return the size of the font e.g 10
+ * @access public
+ */
+ function getFontSize()
+ {
+ return $this->fontSize;
+ }
+
+ /**
+ * Draw the line
+ *
+ * drawing the lines from x,y source to x,y destination and set the
+ * width of the line. lines helps in showing relationships of tables
+ *
+ * @param integer $x_from The x_from attribute defines the start
+ * left position of the element
+ * @param integer $y_from The y_from attribute defines the start
+ * right position of the element
+ * @param integer $x_to The x_to attribute defines the end
+ * left position of the element
+ * @param integer $y_to The y_to attribute defines the end
+ * right position of the element
+ * @param integer $lineWidth Sets the width of the line e.g 2
+ *
+ * @return void
+ *
+ * @access public
+ */
+ function line($x_from = 0, $y_from = 0, $x_to = 0, $y_to = 0, $lineWidth = 0)
+ {
+ $this->stringCommands .= $lineWidth . " setlinewidth \n";
+ $this->stringCommands .= $x_from . ' ' . $y_from . " moveto \n";
+ $this->stringCommands .= $x_to . ' ' . $y_to . " lineto \n";
+ $this->stringCommands .= "stroke \n";
+ }
+
+ /**
+ * Draw the rectangle
+ *
+ * drawing the rectangle from x,y source to x,y destination and set the
+ * width of the line. rectangles drawn around the text shown of fields
+ *
+ * @param integer $x_from The x_from attribute defines the start
+ left position of the element
+ * @param integer $y_from The y_from attribute defines the start
+ right position of the element
+ * @param integer $x_to The x_to attribute defines the end
+ left position of the element
+ * @param integer $y_to The y_to attribute defines the end
+ right position of the element
+ * @param integer $lineWidth Sets the width of the line e.g 2
+ *
+ * @return void
+ *
+ * @access public
+ */
+ function rect($x_from, $y_from, $x_to, $y_to, $lineWidth)
+ {
+ $this->stringCommands .= $lineWidth . " setlinewidth \n";
+ $this->stringCommands .= "newpath \n";
+ $this->stringCommands .= $x_from . " " . $y_from . " moveto \n";
+ $this->stringCommands .= "0 " . $y_to . " rlineto \n";
+ $this->stringCommands .= $x_to . " 0 rlineto \n";
+ $this->stringCommands .= "0 -" . $y_to . " rlineto \n";
+ $this->stringCommands .= "closepath \n";
+ $this->stringCommands .= "stroke \n";
+ }
+
+ /**
+ * Set the current point
+ *
+ * The moveto operator takes two numbers off the stack and treats
+ * them as x and y coordinates to which to move. The coordinates
+ * specified become the current point.
+ *
+ * @param integer $x The x attribute defines the left position of the element
+ * @param integer $y The y attribute defines the right position of the element
+ *
+ * @return void
+ *
+ * @access public
+ */
+ function moveTo($x, $y)
+ {
+ $this->stringCommands .= $x . ' ' . $y . " moveto \n";
+ }
+
+ /**
+ * Output/Display the text
+ *
+ * @param string $text The string to be displayed
+ *
+ * @return void
+ *
+ * @access public
+ */
+ function show($text)
+ {
+ $this->stringCommands .= '(' . $text . ") show \n";
+ }
+
+ /**
+ * Output the text at specified co-ordinates
+ *
+ * @param string $text String to be displayed
+ * @param integer $x X attribute defines the left position of the element
+ * @param integer $y Y attribute defines the right position of the element
+ *
+ * @return void
+ *
+ * @access public
+ */
+ function showXY($text, $x, $y)
+ {
+ $this->moveTo($x, $y);
+ $this->show($text);
+ }
+
+ /**
+ * get width of string/text
+ *
+ * EPS text width is calcualted depending on font name
+ * and font size. It is very important to know the width of text
+ * because rectangle is drawn around it.
+ *
+ * This is a bit hardcore method. I didn't found any other better than this.
+ * if someone found better than this. would love to hear that method
+ *
+ * @param string $text string that width will be calculated
+ * @param integer $font name of the font like Arial,sans-serif etc
+ * @param integer $fontSize size of font
+ *
+ * @return integer width of the text
+ *
+ * @access public
+ */
+ function getStringWidth($text,$font,$fontSize)
+ {
+ /*
+ * Start by counting the width, giving each character a modifying value
+ */
+ $count = 0;
+ $count = $count + ((strlen($text) - strlen(str_replace(array("i", "j", "l"), "", $text))) * 0.23);//ijl
+ $count = $count + ((strlen($text) - strlen(str_replace(array("f"), "", $text))) * 0.27);//f
+ $count = $count + ((strlen($text) - strlen(str_replace(array("t", "I"), "", $text))) * 0.28);//tI
+ $count = $count + ((strlen($text) - strlen(str_replace(array("r"), "", $text))) * 0.34);//r
+ $count = $count + ((strlen($text) - strlen(str_replace(array("1"), "", $text))) * 0.49);//1
+ $count = $count + ((strlen($text) - strlen(str_replace(array("c", "k", "s", "v", "x", "y", "z", "J"), "", $text))) * 0.5);//cksvxyzJ
+ $count = $count + ((strlen($text) - strlen(str_replace(array("a", "b", "d", "e", "g", "h", "n", "o", "p", "q", "u", "L", "0", "2", "3", "4", "5", "6", "7", "8", "9"), "", $text))) * 0.56);//abdeghnopquL023456789
+ $count = $count + ((strlen($text) - strlen(str_replace(array("F", "T", "Z"), "", $text))) * 0.61);//FTZ
+ $count = $count + ((strlen($text) - strlen(str_replace(array("A", "B", "E", "K", "P", "S", "V", "X", "Y"), "", $text))) * 0.67);//ABEKPSVXY
+ $count = $count + ((strlen($text) - strlen(str_replace(array("w", "C", "D", "H", "N", "R", "U"), "", $text))) * 0.73);//wCDHNRU
+ $count = $count + ((strlen($text) - strlen(str_replace(array("G", "O", "Q"), "", $text))) * 0.78);//GOQ
+ $count = $count + ((strlen($text) - strlen(str_replace(array("m", "M"), "", $text))) * 0.84);//mM
+ $count = $count + ((strlen($text) - strlen(str_replace("W", "", $text))) * .95);//W
+ $count = $count + ((strlen($text) - strlen(str_replace(" ", "", $text))) * .28);//" "
+ $text = str_replace(" ", "", $text);//remove the " "'s
+ $count = $count + (strlen(preg_replace("/[a-z0-9]/i", "", $text)) * 0.3); //all other chrs
+
+ $modifier = 1;
+ $font = strtolower($font);
+ switch ($font) {
+ /*
+ * no modifier for arial and sans-serif
+ */
+ case 'arial':
+ case 'sans-serif':
+ break;
+ /*
+ * .92 modifer for time, serif, brushscriptstd, and californian fb
+ */
+ case 'times':
+ case 'serif':
+ case 'brushscriptstd':
+ case 'californian fb':
+ $modifier = .92;
+ break;
+ /*
+ * 1.23 modifier for broadway
+ */
+ case 'broadway':
+ $modifier = 1.23;
+ break;
+ }
+ $textWidth = $count*$fontSize;
+ return ceil($textWidth*$modifier);
+ }
+
+ /**
+ * Ends EPS Document
+ *
+ * @return void
+ * @access public
+ */
+ function endEpsDoc()
+ {
+ $this->stringCommands .= "showpage \n";
+ }
+
+ /**
+ * Output EPS Document for download
+ *
+ * @param string $fileName name of the eps document
+ *
+ * @return void
+ *
+ * @access public
+ */
+ function showOutput($fileName)
+ {
+ // if(ob_get_clean()){
+ //ob_end_clean();
+ //}
+ $output = $this->stringCommands;
+ PMA_Response::getInstance()->disable();
+ PMA_downloadHeader($fileName . '.eps', 'image/x-eps', strlen($output));
+ print $output;
+ }
+}
+
+/**
+ * Table preferences/statistics
+ *
+ * This class preserves the table co-ordinates,fields
+ * and helps in drawing/generating the Tables in EPS.
+ *
+ * @package PhpMyAdmin
+ * @name Table_Stats_Eps
+ * @see PMA_EPS
+ */
+class Table_Stats_Eps
+{
+ /**
+ * Defines properties
+ */
+
+ private $_tableName;
+ private $_showInfo = false;
+
+ public $width = 0;
+ public $height;
+ public $fields = array();
+ public $heightCell = 0;
+ public $currentCell = 0;
+ public $x, $y;
+ public $primary = array();
+
+ /**
+ * The "Table_Stats_Eps" constructor
+ *
+ * @param string $tableName The table name
+ * @param string $font The font name
+ * @param integer $fontSize The font size
+ * @param integer $pageNumber Page number
+ * @param integer &$same_wide_width The max width among tables
+ * @param boolean $showKeys Whether to display keys or not
+ * @param boolean $showInfo Whether to display table position or not
+ *
+ * @global object $eps The current eps document
+ * @global integer The current page number (from the
+ * $cfg['Servers'][$i]['table_coords'] table)
+ * @global array $cfgRelation The relations settings
+ * @global string $db The current db name
+ *
+ * @access private
+ * @see PMA_EPS, Table_Stats_Eps::Table_Stats_setWidth,
+ * Table_Stats_Eps::Table_Stats_setHeight
+ */
+ function __construct(
+ $tableName, $font, $fontSize, $pageNumber, &$same_wide_width,
+ $showKeys = false, $showInfo = false
+ ) {
+ global $eps, $cfgRelation, $db;
+
+ $this->_tableName = $tableName;
+ $sql = 'DESCRIBE ' . PMA_Util::backquote($tableName);
+ $result = $GLOBALS['dbi']->tryQuery(
+ $sql, null, PMA_DatabaseInterface::QUERY_STORE
+ );
+ if (! $result || ! $GLOBALS['dbi']->numRows($result)) {
+ $eps->dieSchema(
+ $pageNumber, "EPS",
+ sprintf(__('The %s table doesn\'t exist!'), $tableName)
+ );
+ }
+
+ /*
+ * load fields
+ * check to see if it will load all fields or only the foreign keys
+ */
+ if ($showKeys) {
+ $indexes = PMA_Index::getFromTable($this->_tableName, $db);
+ $all_columns = array();
+ foreach ($indexes as $index) {
+ $all_columns = array_merge(
+ $all_columns,
+ array_flip(array_keys($index->getColumns()))
+ );
+ }
+ $this->fields = array_keys($all_columns);
+ } else {
+ while ($row = $GLOBALS['dbi']->fetchRow($result)) {
+ $this->fields[] = $row[0];
+ }
+ }
+
+ $this->_showInfo = $showInfo;
+
+ // height and width
+ $this->_setHeightTable($fontSize);
+
+ // setWidth must me after setHeight, because title
+ // can include table height which changes table width
+ $this->_setWidthTable($font, $fontSize);
+ if ($same_wide_width < $this->width) {
+ $same_wide_width = $this->width;
+ }
+
+ // x and y
+ $sql = 'SELECT x, y FROM '
+ . PMA_Util::backquote($GLOBALS['cfgRelation']['db']) . '.'
+ . PMA_Util::backquote($cfgRelation['table_coords'])
+ . ' WHERE db_name = \'' . PMA_Util::sqlAddSlashes($db) . '\''
+ . ' AND table_name = \'' . PMA_Util::sqlAddSlashes($tableName) . '\''
+ . ' AND pdf_page_number = ' . $pageNumber;
+ $result = PMA_queryAsControlUser(
+ $sql, false, PMA_DatabaseInterface::QUERY_STORE
+ );
+
+ if (! $result || ! $GLOBALS['dbi']->numRows($result)) {
+ $eps->dieSchema(
+ $pageNumber, "EPS",
+ sprintf(
+ __('Please configure the coordinates for table %s'),
+ $tableName
+ )
+ );
+ }
+ list($this->x, $this->y) = $GLOBALS['dbi']->fetchRow($result);
+ $this->x = (double) $this->x;
+ $this->y = (double) $this->y;
+ // displayfield
+ $this->displayfield = PMA_getDisplayField($db, $tableName);
+ // index
+ $result = $GLOBALS['dbi']->query(
+ 'SHOW INDEX FROM ' . PMA_Util::backquote($tableName) . ';',
+ null, PMA_DatabaseInterface::QUERY_STORE
+ );
+ if ($GLOBALS['dbi']->numRows($result) > 0) {
+ while ($row = $GLOBALS['dbi']->fetchAssoc($result)) {
+ if ($row['Key_name'] == 'PRIMARY') {
+ $this->primary[] = $row['Column_name'];
+ }
+ }
+ }
+ }
+
+ /**
+ * Returns title of the current table,
+ * title can have the dimensions/co-ordinates of the table
+ *
+ * @return string The relation/table name
+ * @access private
+ */
+ private function _getTitle()
+ {
+ return ($this->_showInfo
+ ? sprintf('%.0f', $this->width) . 'x' . sprintf('%.0f', $this->heightCell)
+ : '') . ' ' . $this->_tableName;
+ }
+
+ /**
+ * Sets the width of the table
+ *
+ * @param string $font The font name
+ * @param integer $fontSize The font size
+ *
+ * @global object $eps The current eps document
+ *
+ * @return void
+ *
+ * @access private
+ * @see PMA_EPS
+ */
+ private function _setWidthTable($font,$fontSize)
+ {
+ global $eps;
+
+ foreach ($this->fields as $field) {
+ $this->width = max(
+ $this->width,
+ $eps->getStringWidth($field, $font, $fontSize)
+ );
+ }
+ $this->width += $eps->getStringWidth(' ', $font, $fontSize);
+ /*
+ * it is unknown what value must be added, because
+ * table title is affected by the tabe width value
+ */
+ while ($this->width < $eps->getStringWidth($this->_getTitle(), $font, $fontSize)) {
+ $this->width += 7;
+ }
+ }
+
+ /**
+ * Sets the height of the table
+ *
+ * @param integer $fontSize The font size
+ *
+ * @return void
+ * @access private
+ */
+ private function _setHeightTable($fontSize)
+ {
+ $this->heightCell = $fontSize + 4;
+ $this->height = (count($this->fields) + 1) * $this->heightCell;
+ }
+
+ /**
+ * Draw the table
+ *
+ * @param boolean $showColor Whether to display color
+ *
+ * @global object $eps The current eps document
+ *
+ * @return void
+ *
+ * @access public
+ * @see PMA_EPS,PMA_EPS::line,PMA_EPS::rect
+ */
+ public function tableDraw($showColor)
+ {
+ global $eps;
+ //echo $this->_tableName.'<br />';
+ $eps->rect($this->x, $this->y + 12, $this->width, $this->heightCell, 1);
+ $eps->showXY($this->_getTitle(), $this->x + 5, $this->y + 14);
+ foreach ($this->fields as $field) {
+ $this->currentCell += $this->heightCell;
+ $showColor = 'none';
+ if ($showColor) {
+ if (in_array($field, $this->primary)) {
+ $showColor = '#0c0';
+ }
+ if ($field == $this->displayfield) {
+ $showColor = 'none';
+ }
+ }
+ $eps->rect(
+ $this->x, $this->y + 12 + $this->currentCell,
+ $this->width, $this->heightCell, 1
+ );
+ $eps->showXY($field, $this->x + 5, $this->y + 14 + $this->currentCell);
+ }
+ }
+}
+
+/**
+ * Relation preferences/statistics
+ *
+ * This class fetches the table master and foreign fields positions
+ * and helps in generating the Table references and then connects
+ * master table's master field to foreign table's foreign key
+ * in EPS document.
+ *
+ * @package PhpMyAdmin
+ * @name Relation_Stats_Eps
+ * @see PMA_EPS
+ */
+class Relation_Stats_Eps
+{
+ /**
+ * Defines properties
+ */
+ public $xSrc, $ySrc;
+ public $srcDir ;
+ public $destDir;
+ public $xDest, $yDest;
+ public $wTick = 10;
+
+ /**
+ * The "Relation_Stats_Eps" constructor
+ *
+ * @param string $master_table The master table name
+ * @param string $master_field The relation field in the master table
+ * @param string $foreign_table The foreign table name
+ * @param string $foreign_field The relation field in the foreign table
+ *
+ * @see Relation_Stats_Eps::_getXy
+ */
+ function __construct($master_table, $master_field, $foreign_table, $foreign_field)
+ {
+ $src_pos = $this->_getXy($master_table, $master_field);
+ $dest_pos = $this->_getXy($foreign_table, $foreign_field);
+ /*
+ * [0] is x-left
+ * [1] is x-right
+ * [2] is y
+ */
+ $src_left = $src_pos[0] - $this->wTick;
+ $src_right = $src_pos[1] + $this->wTick;
+ $dest_left = $dest_pos[0] - $this->wTick;
+ $dest_right = $dest_pos[1] + $this->wTick;
+
+ $d1 = abs($src_left - $dest_left);
+ $d2 = abs($src_right - $dest_left);
+ $d3 = abs($src_left - $dest_right);
+ $d4 = abs($src_right - $dest_right);
+ $d = min($d1, $d2, $d3, $d4);
+
+ if ($d == $d1) {
+ $this->xSrc = $src_pos[0];
+ $this->srcDir = -1;
+ $this->xDest = $dest_pos[0];
+ $this->destDir = -1;
+ } elseif ($d == $d2) {
+ $this->xSrc = $src_pos[1];
+ $this->srcDir = 1;
+ $this->xDest = $dest_pos[0];
+ $this->destDir = -1;
+ } elseif ($d == $d3) {
+ $this->xSrc = $src_pos[0];
+ $this->srcDir = -1;
+ $this->xDest = $dest_pos[1];
+ $this->destDir = 1;
+ } else {
+ $this->xSrc = $src_pos[1];
+ $this->srcDir = 1;
+ $this->xDest = $dest_pos[1];
+ $this->destDir = 1;
+ }
+ $this->ySrc = $src_pos[2] + 10;
+ $this->yDest = $dest_pos[2] + 10;
+ }
+
+ /**
+ * Gets arrows coordinates
+ *
+ * @param string $table The current table name
+ * @param string $column The relation column name
+ *
+ * @return array Arrows coordinates
+ *
+ * @access private
+ */
+ private function _getXy($table, $column)
+ {
+ $pos = array_search($column, $table->fields);
+ // x_left, x_right, y
+ return array(
+ $table->x,
+ $table->x + $table->width,
+ $table->y + ($pos + 1.5) * $table->heightCell
+ );
+ }
+
+ /**
+ * draws relation links and arrows
+ * shows foreign key relations
+ *
+ * @param boolean $changeColor Whether to use one color per relation or not
+ *
+ * @global object $eps The current EPS document
+ *
+ * @access public
+ * @see PMA_EPS
+ *
+ * @return void
+ */
+ public function relationDraw($changeColor)
+ {
+ global $eps;
+
+ if ($changeColor) {
+ $listOfColors = array(
+ 'red',
+ 'grey',
+ 'black',
+ 'yellow',
+ 'green',
+ 'cyan',
+ ' orange'
+ );
+ shuffle($listOfColors);
+ $color = $listOfColors[0];
+ } else {
+ $color = 'black';
+ }
+ // draw a line like -- to foreign field
+ $eps->line(
+ $this->xSrc,
+ $this->ySrc,
+ $this->xSrc + $this->srcDir * $this->wTick,
+ $this->ySrc,
+ 1
+ );
+ // draw a line like -- to master field
+ $eps->line(
+ $this->xDest + $this->destDir * $this->wTick,
+ $this->yDest,
+ $this->xDest,
+ $this->yDest,
+ 1
+ );
+ // draw a line that connects to master field line and foreign field line
+ $eps->line(
+ $this->xSrc + $this->srcDir * $this->wTick,
+ $this->ySrc,
+ $this->xDest + $this->destDir * $this->wTick,
+ $this->yDest,
+ 1
+ );
+ $root2 = 2 * sqrt(2);
+ $eps->line(
+ $this->xSrc + $this->srcDir * $this->wTick * 0.75,
+ $this->ySrc,
+ $this->xSrc + $this->srcDir * (0.75 - 1 / $root2) * $this->wTick,
+ $this->ySrc + $this->wTick / $root2,
+ 1
+ );
+ $eps->line(
+ $this->xSrc + $this->srcDir * $this->wTick * 0.75,
+ $this->ySrc,
+ $this->xSrc + $this->srcDir * (0.75 - 1 / $root2) * $this->wTick,
+ $this->ySrc - $this->wTick / $root2,
+ 1
+ );
+ $eps->line(
+ $this->xDest + $this->destDir * $this->wTick / 2,
+ $this->yDest,
+ $this->xDest + $this->destDir * (0.5 + 1 / $root2) * $this->wTick,
+ $this->yDest + $this->wTick / $root2,
+ 1
+ );
+ $eps->line(
+ $this->xDest + $this->destDir * $this->wTick / 2,
+ $this->yDest,
+ $this->xDest + $this->destDir * (0.5 + 1 / $root2) * $this->wTick,
+ $this->yDest - $this->wTick / $root2,
+ 1
+ );
+ }
+}
+/*
+* end of the "Relation_Stats_Eps" class
+*/
+
+/**
+ * EPS Relation Schema Class
+ *
+ * Purpose of this class is to generate the EPS Document
+ * which is used for representing the database diagrams.
+ * This class uses post script commands and with
+ * the combination of these commands actually helps in preparing EPS Document.
+ *
+ * This class inherits Export_Relation_Schema class has common functionality added
+ * to this class
+ *
+ * @package PhpMyAdmin
+ * @name Eps_Relation_Schema
+ */
+class PMA_Eps_Relation_Schema extends PMA_Export_Relation_Schema
+{
+ private $_tables = array();
+ private $_relations = array();
+
+ /**
+ * The "PMA_EPS_Relation_Schema" constructor
+ *
+ * Upon instantiation This starts writing the EPS document
+ * user will be prompted for download as .eps extension
+ *
+ * @see PMA_EPS
+ */
+ function __construct()
+ {
+ global $eps,$db;
+
+ $this->setPageNumber($_POST['pdf_page_number']);
+ $this->setShowColor(isset($_POST['show_color']));
+ $this->setShowKeys(isset($_POST['show_keys']));
+ $this->setTableDimension(isset($_POST['show_table_dimension']));
+ $this->setAllTablesSameWidth(isset($_POST['all_tables_same_width']));
+ $this->setOrientation($_POST['orientation']);
+ $this->setExportType($_POST['export_type']);
+
+ $eps = new PMA_EPS();
+ $eps->setTitle(
+ sprintf(
+ __('Schema of the %s database - Page %s'),
+ $db,
+ $this->pageNumber
+ )
+ );
+ $eps->setAuthor('phpMyAdmin ' . PMA_VERSION);
+ $eps->setDate(date("j F Y, g:i a"));
+ $eps->setOrientation($this->orientation);
+ $eps->setFont('Verdana', '10');
+
+ $alltables = $this->getAllTables($db, $this->pageNumber);
+
+ foreach ($alltables as $table) {
+ if (! isset($this->_tables[$table])) {
+ $this->_tables[$table] = new Table_Stats_Eps(
+ $table, $eps->getFont(), $eps->getFontSize(), $this->pageNumber,
+ $this->_tablewidth, $this->showKeys, $this->tableDimension
+ );
+ }
+
+ if ($this->sameWide) {
+ $this->_tables[$table]->width = $this->_tablewidth;
+ }
+ }
+
+ $seen_a_relation = false;
+ foreach ($alltables as $one_table) {
+ $exist_rel = PMA_getForeigners($db, $one_table, '', 'both');
+ if ($exist_rel) {
+ $seen_a_relation = true;
+ foreach ($exist_rel as $master_field => $rel) {
+ /* put the foreign table on the schema only if selected
+ * by the user
+ * (do not use array_search() because we would have to
+ * to do a === false and this is not PHP3 compatible)
+ */
+ if (in_array($rel['foreign_table'], $alltables)) {
+ $this->_addRelation(
+ $one_table, $eps->getFont(), $eps->getFontSize(),
+ $master_field, $rel['foreign_table'],
+ $rel['foreign_field'], $this->tableDimension
+ );
+ }
+ }
+ }
+ }
+ if ($seen_a_relation) {
+ $this->_drawRelations($this->showColor);
+ }
+
+ $this->_drawTables($this->showColor);
+ $eps->endEpsDoc();
+ }
+
+ /**
+ * Output Eps Document for download
+ *
+ * @return void
+ * @access public
+ */
+ function showOutput()
+ {
+ global $eps,$db;
+ $eps->showOutput($db . '-' . $this->pageNumber);
+ }
+
+ /**
+ * Defines relation objects
+ *
+ * @param string $masterTable The master table name
+ * @param string $font The font
+ * @param int $fontSize The font size
+ * @param string $masterField The relation field in the master table
+ * @param string $foreignTable The foreign table name
+ * @param string $foreignField The relation field in the foreign table
+ * @param boolean $showInfo Whether to display table position or not
+ *
+ * @return void
+ *
+ * @access private
+ * @see _setMinMax,Table_Stats_Eps::__construct(),Relation_Stats_Eps::__construct()
+ */
+ private function _addRelation(
+ $masterTable, $font, $fontSize, $masterField,
+ $foreignTable, $foreignField, $showInfo
+ ) {
+ if (! isset($this->_tables[$masterTable])) {
+ $this->_tables[$masterTable] = new Table_Stats_Eps(
+ $masterTable, $font, $fontSize, $this->pageNumber,
+ $this->_tablewidth, false, $showInfo
+ );
+ }
+ if (! isset($this->_tables[$foreignTable])) {
+ $this->_tables[$foreignTable] = new Table_Stats_Eps(
+ $foreignTable, $font, $fontSize, $this->pageNumber,
+ $this->_tablewidth, false, $showInfo
+ );
+ }
+ $this->_relations[] = new Relation_Stats_Eps(
+ $this->_tables[$masterTable], $masterField,
+ $this->_tables[$foreignTable], $foreignField
+ );
+ }
+
+ /**
+ * Draws relation arrows and lines connects master table's master field to
+ * foreign table's forein field
+ *
+ * @param boolean $changeColor Whether to use one color per relation or not
+ *
+ * @return void
+ *
+ * @access private
+ * @see Relation_Stats_Eps::relationDraw()
+ */
+ private function _drawRelations($changeColor)
+ {
+ foreach ($this->_relations as $relation) {
+ $relation->relationDraw($changeColor);
+ }
+ }
+
+ /**
+ * Draws tables
+ *
+ * @param boolean $changeColor Whether to show color for primary fields or not
+ *
+ * @return void
+ *
+ * @access private
+ * @see Table_Stats_Eps::Table_Stats_tableDraw()
+ */
+ private function _drawTables($changeColor)
+ {
+ foreach ($this->_tables as $table) {
+ $table->tableDraw($changeColor);
+ }
+ }
+}
+?>
diff --git a/libraries/schema/Export_Relation_Schema.class.php b/libraries/schema/Export_Relation_Schema.class.php
new file mode 100644
index 0000000000..012f42d9db
--- /dev/null
+++ b/libraries/schema/Export_Relation_Schema.class.php
@@ -0,0 +1,251 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ *
+ * @package PhpMyAdmin
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ * This class is inherited by all schema classes
+ * It contains those methods which are common in them
+ * it works like factory pattern
+ *
+ * @package PhpMyAdmin
+ */
+class PMA_Export_Relation_Schema
+{
+ private $_pageTitle;
+ public $showGrid;
+ public $showColor;
+ public $tableDimension;
+ public $sameWide;
+ public $withDoc;
+ public $showKeys;
+ public $orientation;
+ public $paper;
+ public $pageNumber;
+
+ /**
+ * Set Page Number
+ *
+ * @param integer $value Page Number of the document to be created
+ *
+ * @return void
+ *
+ * @access public
+ */
+ public function setPageNumber($value)
+ {
+ $this->pageNumber = isset($value) ? $value : 1;
+ }
+
+ /**
+ * Set Show Grid
+ *
+ * @param boolean $value show grid of the document or not
+ *
+ * @return void
+ *
+ * @access public
+ */
+ public function setShowGrid($value)
+ {
+ $this->showGrid = (isset($value) && $value == 'on') ? 1 : 0;
+ }
+
+ /**
+ * Sets showColor
+ *
+ * @param string $value 'on' to set the the variable
+ *
+ * @return void
+ */
+ public function setShowColor($value)
+ {
+ $this->showColor = (isset($value) && $value == 'on') ? 1 : 0;
+ }
+
+ /**
+ * Set Table Dimension
+ *
+ * @param boolean $value show table co-ordinates or not
+ *
+ * @return void
+ *
+ * @access public
+ */
+ public function setTableDimension($value)
+ {
+ $this->tableDimension = (isset($value) && $value == 'on') ? 1 : 0;
+ }
+
+ /**
+ * Set same width of All Tables
+ *
+ * @param boolean $value set same width of all tables or not
+ *
+ * @return void
+ *
+ * @access public
+ */
+ public function setAllTablesSameWidth($value)
+ {
+ $this->sameWide = (isset($value) && $value == 'on') ? 1 : 0;
+ }
+
+ /**
+ * Set Data Dictionary
+ *
+ * @param boolean $value show selected database data dictionary or not
+ *
+ * @return void
+ *
+ * @access public
+ */
+ public function setWithDataDictionary($value)
+ {
+ $this->withDoc = (isset($value) && $value == 'on') ? 1 : 0;
+ }
+
+ /**
+ * Set Show only keys
+ *
+ * @param boolean $value show only keys or not
+ *
+ * @return void
+ *
+ * @access public
+ */
+ public function setShowKeys($value)
+ {
+ $this->showKeys = (isset($value) && $value == 'on') ? 1 : 0;
+ }
+
+ /**
+ * Set Orientation
+ *
+ * @param string $value Orientation will be portrait or landscape
+ *
+ * @return void
+ *
+ * @access public
+ */
+ public function setOrientation($value)
+ {
+ $this->orientation = (isset($value) && $value == 'P') ? 'P' : 'L';
+ }
+
+ /**
+ * Set type of paper
+ *
+ * @param string $value paper type can be A4 etc
+ *
+ * @return void
+ *
+ * @access public
+ */
+ public function setPaper($value)
+ {
+ $this->paper = isset($value) ? $value : 'A4';
+ }
+
+ /**
+ * Set title of the page
+ *
+ * @param string $title title of the page displayed at top of the document
+ *
+ * @return void
+ *
+ * @access public
+ */
+ public function setPageTitle($title)
+ {
+ $this->_pageTitle=$title;
+ }
+
+ /**
+ * Set type of export relational schema
+ *
+ * @param string $value can be pdf,svg,dia,eps etc
+ *
+ * @return void
+ *
+ * @access public
+ */
+ public function setExportType($value)
+ {
+ $this->exportType=$value;
+ }
+
+ /**
+ * get all tables involved or included in page
+ *
+ * @param string $db name of the database
+ * @param integer $pageNumber page no. whose tables will be fetched in an array
+ *
+ * @return Array an array of tables
+ *
+ * @access public
+ */
+ public function getAllTables($db, $pageNumber)
+ {
+ global $cfgRelation;
+
+ // Get All tables
+ $tab_sql = 'SELECT table_name FROM '
+ . PMA_Util::backquote($GLOBALS['cfgRelation']['db']) . '.'
+ . PMA_Util::backquote($cfgRelation['table_coords'])
+ . ' WHERE db_name = \'' . PMA_Util::sqlAddSlashes($db) . '\''
+ . ' AND pdf_page_number = ' . $pageNumber;
+
+ $tab_rs = PMA_queryAsControlUser(
+ $tab_sql, null, PMA_DatabaseInterface::QUERY_STORE
+ );
+ if (! $tab_rs || ! $GLOBALS['dbi']->numRows($tab_rs) > 0) {
+ $this->dieSchema('', __('This page does not contain any tables!'));
+ }
+ //Fix undefined error
+ $alltables = array();
+ while ($curr_table = @$GLOBALS['dbi']->fetchAssoc($tab_rs)) {
+ $alltables[] = PMA_Util::sqlAddSlashes($curr_table['table_name']);
+ }
+ return $alltables;
+ }
+
+ /**
+ * Displays an error message
+ *
+ * @param integer $pageNumber ID of the chosen page
+ * @param string $type Schema Type
+ * @param string $error_message The error mesage
+ *
+ * @global array the PMA configuration array
+ * @global string $db the current database name
+ *
+ * @access public
+ *
+ * @return void
+ */
+ function dieSchema($pageNumber, $type = '', $error_message = '')
+ {
+ global $db;
+
+ echo "<p><strong>" . __("SCHEMA ERROR: ") . $type . "</strong></p>" . "\n";
+ if (!empty($error_message)) {
+ $error_message = htmlspecialchars($error_message);
+ }
+ echo '<p>' . "\n";
+ echo ' ' . $error_message . "\n";
+ echo '</p>' . "\n";
+ echo '<a href="schema_edit.php?' . PMA_URL_getCommon($db)
+ . '&do=selectpage&chpage=' . htmlspecialchars($pageNumber)
+ . '&action_choose=0'
+ . '">' . __('Back') . '</a>';
+ echo "\n";
+ exit;
+ }
+}
+?>
diff --git a/libraries/schema/Pdf_Relation_Schema.class.php b/libraries/schema/Pdf_Relation_Schema.class.php
new file mode 100644
index 0000000000..7d881ec465
--- /dev/null
+++ b/libraries/schema/Pdf_Relation_Schema.class.php
@@ -0,0 +1,1467 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * PDF schema handling
+ *
+ * @package PhpMyAdmin
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ * Skip the plugin if TCPDF is not available.
+ */
+if (! file_exists(TCPDF_INC)) {
+ $GLOBALS['skip_import'] = true;
+ return;
+}
+
+/**
+ * block attempts to directly run this script
+ */
+if (getcwd() == dirname(__FILE__)) {
+ die('Attack stopped');
+}
+
+require_once 'Export_Relation_Schema.class.php';
+require_once './libraries/PDF.class.php';
+
+/**
+ * Extends the "TCPDF" class and helps
+ * in developing the structure of PDF Schema Export
+ *
+ * @access public
+ * @package PhpMyAdmin
+ * @see TCPDF
+ */
+class PMA_Schema_PDF extends PMA_PDF
+{
+ /**
+ * Defines properties
+ */
+ var $_xMin;
+ var $_yMin;
+ var $leftMargin = 10;
+ var $topMargin = 10;
+ var $scale;
+ var $PMA_links;
+ var $Outlines = array();
+ var $def_outlines;
+ var $widths;
+ private $_ff = PMA_PDF_FONT;
+
+ /**
+ * Sets the value for margins
+ *
+ * @param float $c_margin margin
+ *
+ * @return void
+ */
+ public function setCMargin($c_margin)
+ {
+ $this->cMargin = $c_margin;
+ }
+
+ /**
+ * Sets the scaling factor, defines minimum coordinates and margins
+ *
+ * @param float $scale The scaling factor
+ * @param float $xMin The minimum X coordinate
+ * @param float $yMin The minimum Y coordinate
+ * @param float $leftMargin The left margin
+ * @param float $topMargin The top margin
+ *
+ * @access public
+ *
+ * @return void
+ */
+ function setScale($scale = 1, $xMin = 0, $yMin = 0,
+ $leftMargin = -1, $topMargin = -1
+ ) {
+ $this->scale = $scale;
+ $this->_xMin = $xMin;
+ $this->_yMin = $yMin;
+ if ($this->leftMargin != -1) {
+ $this->leftMargin = $leftMargin;
+ }
+ if ($this->topMargin != -1) {
+ $this->topMargin = $topMargin;
+ }
+ }
+
+ /**
+ * Outputs a scaled cell
+ *
+ * @param float $w The cell width
+ * @param float $h The cell height
+ * @param string $txt The text to output
+ * @param mixed $border Whether to add borders or not
+ * @param integer $ln Where to put the cursor once the output is done
+ * @param string $align Align mode
+ * @param integer $fill Whether to fill the cell with a color or not
+ * @param string $link Link
+ *
+ * @access public
+ *
+ * @return void
+ *
+ * @see TCPDF::Cell()
+ */
+ function cellScale($w, $h = 0, $txt = '', $border = 0, $ln = 0,
+ $align = '', $fill = 0, $link = ''
+ ) {
+ $h = $h / $this->scale;
+ $w = $w / $this->scale;
+ $this->Cell($w, $h, $txt, $border, $ln, $align, $fill, $link);
+ }
+
+ /**
+ * Draws a scaled line
+ *
+ * @param float $x1 The horizontal position of the starting point
+ * @param float $y1 The vertical position of the starting point
+ * @param float $x2 The horizontal position of the ending point
+ * @param float $y2 The vertical position of the ending point
+ *
+ * @access public
+ *
+ * @return void
+ *
+ * @see TCPDF::Line()
+ */
+ function lineScale($x1, $y1, $x2, $y2)
+ {
+ $x1 = ($x1 - $this->_xMin) / $this->scale + $this->leftMargin;
+ $y1 = ($y1 - $this->_yMin) / $this->scale + $this->topMargin;
+ $x2 = ($x2 - $this->_xMin) / $this->scale + $this->leftMargin;
+ $y2 = ($y2 - $this->_yMin) / $this->scale + $this->topMargin;
+ $this->Line($x1, $y1, $x2, $y2);
+ }
+
+ /**
+ * Sets x and y scaled positions
+ *
+ * @param float $x The x position
+ * @param float $y The y position
+ *
+ * @access public
+ *
+ * @return void
+ *
+ * @see TCPDF::SetXY()
+ */
+ function setXyScale($x, $y)
+ {
+ $x = ($x - $this->_xMin) / $this->scale + $this->leftMargin;
+ $y = ($y - $this->_yMin) / $this->scale + $this->topMargin;
+ $this->SetXY($x, $y);
+ }
+
+ /**
+ * Sets the X scaled positions
+ *
+ * @param float $x The x position
+ *
+ * @access public
+ *
+ * @return void
+ *
+ * @see TCPDF::SetX()
+ */
+ function setXScale($x)
+ {
+ $x = ($x - $this->_xMin) / $this->scale + $this->leftMargin;
+ $this->SetX($x);
+ }
+
+ /**
+ * Sets the scaled font size
+ *
+ * @param float $size The font size (in points)
+ *
+ * @access public
+ *
+ * @return void
+ *
+ * @see TCPDF::SetFontSize()
+ */
+ function setFontSizeScale($size)
+ {
+ // Set font size in points
+ $size = $size / $this->scale;
+ $this->SetFontSize($size);
+ }
+
+ /**
+ * Sets the scaled line width
+ *
+ * @param float $width The line width
+ *
+ * @access public
+ *
+ * @return void
+ *
+ * @see TCPDF::SetLineWidth()
+ */
+ function setLineWidthScale($width)
+ {
+ $width = $width / $this->scale;
+ $this->SetLineWidth($width);
+ }
+
+ /**
+ * This method is used to render the page header.
+ *
+ * @return void
+ *
+ * @see TCPDF::Header()
+ */
+ function Header()
+ {
+ // We only show this if we find something in the new pdf_pages table
+
+ // This function must be named "Header" to work with the TCPDF library
+ global $cfgRelation, $db, $pdf_page_number, $with_doc;
+ if ($with_doc) {
+ $test_query = 'SELECT * FROM '
+ . PMA_Util::backquote($GLOBALS['cfgRelation']['db']) . '.'
+ . PMA_Util::backquote($cfgRelation['pdf_pages'])
+ . ' WHERE db_name = \'' . PMA_Util::sqlAddSlashes($db) . '\''
+ . ' AND page_nr = \'' . $pdf_page_number . '\'';
+ $test_rs = PMA_queryAsControlUser($test_query);
+ $pages = @$GLOBALS['dbi']->fetchAssoc($test_rs);
+ $this->SetFont($this->_ff, 'B', 14);
+ $this->Cell(0, 6, ucfirst($pages['page_descr']), 'B', 1, 'C');
+ $this->SetFont($this->_ff, '');
+ $this->Ln();
+ }
+ }
+
+ /**
+ * This function must be named "Footer" to work with the TCPDF library
+ *
+ * @return void
+ *
+ * @see PMA_PDF::Footer()
+ */
+ function Footer()
+ {
+ global $with_doc;
+ if ($with_doc) {
+ parent::Footer();
+ }
+ }
+
+ /**
+ * Sets widths
+ *
+ * @param array $w array of widths
+ *
+ * @return void
+ */
+ function SetWidths($w)
+ {
+ // column widths
+ $this->widths = $w;
+ }
+
+ /**
+ * Generates table row.
+ *
+ * @param array $data Data for table
+ * @param array $links Links for table cells
+ *
+ * @return void
+ */
+ function Row($data, $links)
+ {
+ // line height
+ $nb = 0;
+ $data_cnt = count($data);
+ for ($i = 0;$i < $data_cnt;$i++) {
+ $nb = max($nb, $this->NbLines($this->widths[$i], $data[$i]));
+ }
+ $il = $this->FontSize;
+ $h = ($il + 1) * $nb;
+ // page break if necessary
+ $this->CheckPageBreak($h);
+ // draw the cells
+ $data_cnt = count($data);
+ for ($i = 0;$i < $data_cnt;$i++) {
+ $w = $this->widths[$i];
+ // save current position
+ $x = $this->GetX();
+ $y = $this->GetY();
+ // draw the border
+ $this->Rect($x, $y, $w, $h);
+ if (isset($links[$i])) {
+ $this->Link($x, $y, $w, $h, $links[$i]);
+ }
+ // print text
+ $this->MultiCell($w, $il + 1, $data[$i], 0, 'L');
+ // go to right side
+ $this->SetXY($x + $w, $y);
+ }
+ // go to line
+ $this->Ln($h);
+ }
+
+ /**
+ * Compute number of lines used by a multicell of width w
+ *
+ * @param int $w width
+ * @param string $txt text
+ *
+ * @return int
+ */
+ function NbLines($w, $txt)
+ {
+ $cw = &$this->CurrentFont['cw'];
+ if ($w == 0) {
+ $w = $this->w - $this->rMargin - $this->x;
+ }
+ $wmax = ($w-2 * $this->cMargin) * 1000 / $this->FontSize;
+ $s = str_replace("\r", '', $txt);
+ $nb = strlen($s);
+ if ($nb > 0 and $s[$nb-1] == "\n") {
+ $nb--;
+ }
+ $sep = -1;
+ $i = 0;
+ $j = 0;
+ $l = 0;
+ $nl = 1;
+ while ($i < $nb) {
+ $c = $s[$i];
+ if ($c == "\n") {
+ $i++;
+ $sep = -1;
+ $j = $i;
+ $l = 0;
+ $nl++;
+ continue;
+ }
+ if ($c == ' ') {
+ $sep = $i;
+ }
+ $l += isset($cw[ord($c)])?$cw[ord($c)]:0 ;
+ if ($l > $wmax) {
+ if ($sep == -1) {
+ if ($i == $j) {
+ $i++;
+ }
+ } else {
+ $i = $sep + 1;
+ }
+ $sep = -1;
+ $j = $i;
+ $l = 0;
+ $nl++;
+ } else {
+ $i++;
+ }
+ }
+ return $nl;
+ }
+}
+
+/**
+ * Table preferences/statistics
+ *
+ * This class preserves the table co-ordinates,fields
+ * and helps in drawing/generating the Tables in PDF document.
+ *
+ * @name Table_Stats_Pdf
+ * @package PhpMyAdmin
+ * @see PMA_Schema_PDF
+ */
+class Table_Stats_Pdf
+{
+ /**
+ * Defines properties
+ */
+ private $_tableName;
+ private $_showInfo = false;
+
+ public $nb_fiels;
+ public $width = 0;
+ public $height;
+ public $fields = array();
+ public $heightCell = 6;
+ public $x, $y;
+ public $primary = array();
+ private $_ff = PMA_PDF_FONT;
+
+ /**
+ * The "Table_Stats_Pdf" constructor
+ *
+ * @param string $tableName The table name
+ * @param integer $fontSize The font size
+ * @param integer $pageNumber The current page number (from the
+ * $cfg['Servers'][$i]['table_coords'] table)
+ * @param integer &$sameWideWidth The max. with among tables
+ * @param boolean $showKeys Whether to display keys or not
+ * @param boolean $showInfo Whether to display table position or not
+ *
+ * @global object $pdf The current PDF document
+ * @global array $cfgRelation The relations settings
+ * @global string $db The current db name
+ *
+ * @see PMA_Schema_PDF, Table_Stats_Pdf::Table_Stats_setWidth,
+ * Table_Stats_Pdf::Table_Stats_setHeight
+ */
+ function __construct($tableName, $fontSize, $pageNumber, &$sameWideWidth,
+ $showKeys = false, $showInfo = false
+ ) {
+ global $pdf, $cfgRelation, $db;
+
+ $this->_tableName = $tableName;
+ $sql = 'DESCRIBE ' . PMA_Util::backquote($tableName);
+ $result = $GLOBALS['dbi']->tryQuery(
+ $sql, null, PMA_DatabaseInterface::QUERY_STORE
+ );
+ if (! $result || ! $GLOBALS['dbi']->numRows($result)) {
+ $pdf->Error(sprintf(__('The %s table doesn\'t exist!'), $tableName));
+ }
+ // load fields
+ //check to see if it will load all fields or only the foreign keys
+ if ($showKeys) {
+ $indexes = PMA_Index::getFromTable($this->_tableName, $db);
+ $all_columns = array();
+ foreach ($indexes as $index) {
+ $all_columns = array_merge(
+ $all_columns,
+ array_flip(array_keys($index->getColumns()))
+ );
+ }
+ $this->fields = array_keys($all_columns);
+ } else {
+ while ($row = $GLOBALS['dbi']->fetchRow($result)) {
+ $this->fields[] = $row[0];
+ }
+ }
+
+ $this->_showInfo = $showInfo;
+ $this->_setHeight();
+ /*
+ * setWidth must me after setHeight, because title
+ * can include table height which changes table width
+ */
+ $this->_setWidth($fontSize);
+ if ($sameWideWidth < $this->width) {
+ $sameWideWidth = $this->width;
+ }
+ $sql = 'SELECT x, y FROM '
+ . PMA_Util::backquote($GLOBALS['cfgRelation']['db']) . '.'
+ . PMA_Util::backquote($cfgRelation['table_coords'])
+ . ' WHERE db_name = \'' . PMA_Util::sqlAddSlashes($db) . '\''
+ . ' AND table_name = \'' . PMA_Util::sqlAddSlashes($tableName) . '\''
+ . ' AND pdf_page_number = ' . $pageNumber;
+ $result = PMA_queryAsControlUser(
+ $sql, false, PMA_DatabaseInterface::QUERY_STORE
+ );
+ if (! $result || ! $GLOBALS['dbi']->numRows($result)) {
+ $pdf->Error(
+ sprintf(
+ __('Please configure the coordinates for table %s'),
+ $tableName
+ )
+ );
+ }
+ list($this->x, $this->y) = $GLOBALS['dbi']->fetchRow($result);
+ $this->x = (double) $this->x;
+ $this->y = (double) $this->y;
+ /*
+ * displayfield
+ */
+ $this->displayfield = PMA_getDisplayField($db, $tableName);
+ /*
+ * index
+ */
+ $result = $GLOBALS['dbi']->query(
+ 'SHOW INDEX FROM ' . PMA_Util::backquote($tableName) . ';',
+ null, PMA_DatabaseInterface::QUERY_STORE
+ );
+ if ($GLOBALS['dbi']->numRows($result) > 0) {
+ while ($row = $GLOBALS['dbi']->fetchAssoc($result)) {
+ if ($row['Key_name'] == 'PRIMARY') {
+ $this->primary[] = $row['Column_name'];
+ }
+ }
+ }
+ }
+
+ /**
+ * Returns title of the current table,
+ * title can have the dimensions of the table
+ *
+ * @return string
+ */
+ private function _getTitle()
+ {
+ $ret = '';
+ if ($this->_showInfo) {
+ $ret = sprintf('%.0fx%0.f', $this->width, $this->height);
+ }
+ return $ret . ' ' . $this->_tableName;
+ }
+
+ /**
+ * Sets the width of the table
+ *
+ * @param integer $fontSize The font size
+ *
+ * @global object $pdf The current PDF document
+ *
+ * @access private
+ *
+ * @return void
+ *
+ * @see PMA_Schema_PDF
+ */
+ private function _setWidth($fontSize)
+ {
+ global $pdf;
+
+ foreach ($this->fields as $field) {
+ $this->width = max($this->width, $pdf->GetStringWidth($field));
+ }
+ $this->width += $pdf->GetStringWidth(' ');
+ $pdf->SetFont($this->_ff, 'B', $fontSize);
+ /*
+ * it is unknown what value must be added, because
+ * table title is affected by the tabe width value
+ */
+ while ($this->width < $pdf->GetStringWidth($this->_getTitle())) {
+ $this->width += 5;
+ }
+ $pdf->SetFont($this->_ff, '', $fontSize);
+ }
+
+ /**
+ * Sets the height of the table
+ *
+ * @return void
+ *
+ * @access private
+ */
+ private function _setHeight()
+ {
+ $this->height = (count($this->fields) + 1) * $this->heightCell;
+ }
+
+ /**
+ * Do draw the table
+ *
+ * @param integer $fontSize The font size
+ * @param boolean $withDoc Whether to include links to documentation
+ * @param boolean|integer $setColor Whether to display color
+ *
+ * @global object $pdf The current PDF document
+ *
+ * @access public
+ *
+ * @return void
+ *
+ * @see PMA_Schema_PDF
+ */
+ public function tableDraw($fontSize, $withDoc, $setColor = 0)
+ {
+ global $pdf, $withDoc;
+
+ $pdf->setXyScale($this->x, $this->y);
+ $pdf->SetFont($this->_ff, 'B', $fontSize);
+ if ($setColor) {
+ $pdf->SetTextColor(200);
+ $pdf->SetFillColor(0, 0, 128);
+ }
+ if ($withDoc) {
+ $pdf->SetLink($pdf->PMA_links['RT'][$this->_tableName]['-'], -1);
+ } else {
+ $pdf->PMA_links['doc'][$this->_tableName]['-'] = '';
+ }
+
+ $pdf->cellScale(
+ $this->width,
+ $this->heightCell,
+ $this->_getTitle(),
+ 1,
+ 1,
+ 'C',
+ $setColor,
+ $pdf->PMA_links['doc'][$this->_tableName]['-']
+ );
+ $pdf->setXScale($this->x);
+ $pdf->SetFont($this->_ff, '', $fontSize);
+ $pdf->SetTextColor(0);
+ $pdf->SetFillColor(255);
+
+ foreach ($this->fields as $field) {
+ if ($setColor) {
+ if (in_array($field, $this->primary)) {
+ $pdf->SetFillColor(215, 121, 123);
+ }
+ if ($field == $this->displayfield) {
+ $pdf->SetFillColor(142, 159, 224);
+ }
+ }
+ if ($withDoc) {
+ $pdf->SetLink($pdf->PMA_links['RT'][$this->_tableName][$field], -1);
+ } else {
+ $pdf->PMA_links['doc'][$this->_tableName][$field] = '';
+ }
+
+ $pdf->cellScale(
+ $this->width,
+ $this->heightCell,
+ ' ' . $field,
+ 1,
+ 1,
+ 'L',
+ $setColor,
+ $pdf->PMA_links['doc'][$this->_tableName][$field]
+ );
+ $pdf->setXScale($this->x);
+ $pdf->SetFillColor(255);
+ }
+ }
+}
+
+/**
+ * Relation preferences/statistics
+ *
+ * This class fetches the table master and foreign fields positions
+ * and helps in generating the Table references and then connects
+ * master table's master field to foreign table's foreign key
+ * in PDF document.
+ *
+ * @name Relation_Stats_Pdf
+ * @package PhpMyAdmin
+ * @see PMA_Schema_PDF::SetDrawColor, PMA_Schema_PDF::setLineWidthScale,
+ * PMA_Schema_PDF::lineScale
+ */
+class Relation_Stats_Pdf
+{
+ /**
+ * Defines properties
+ */
+ public $xSrc, $ySrc;
+ public $srcDir;
+ public $destDir;
+ public $xDest, $yDest;
+ public $wTick = 5;
+
+ /**
+ * The "Relation_Stats_Pdf" constructor
+ *
+ * @param string $master_table The master table name
+ * @param string $master_field The relation field in the master table
+ * @param string $foreign_table The foreign table name
+ * @param string $foreign_field The relation field in the foreign table
+ *
+ * @see Relation_Stats_Pdf::_getXy
+ */
+ function __construct($master_table, $master_field, $foreign_table,
+ $foreign_field
+ ) {
+ $src_pos = $this->_getXy($master_table, $master_field);
+ $dest_pos = $this->_getXy($foreign_table, $foreign_field);
+ /*
+ * [0] is x-left
+ * [1] is x-right
+ * [2] is y
+ */
+ $src_left = $src_pos[0] - $this->wTick;
+ $src_right = $src_pos[1] + $this->wTick;
+ $dest_left = $dest_pos[0] - $this->wTick;
+ $dest_right = $dest_pos[1] + $this->wTick;
+
+ $d1 = abs($src_left - $dest_left);
+ $d2 = abs($src_right - $dest_left);
+ $d3 = abs($src_left - $dest_right);
+ $d4 = abs($src_right - $dest_right);
+ $d = min($d1, $d2, $d3, $d4);
+
+ if ($d == $d1) {
+ $this->xSrc = $src_pos[0];
+ $this->srcDir = -1;
+ $this->xDest = $dest_pos[0];
+ $this->destDir = -1;
+ } elseif ($d == $d2) {
+ $this->xSrc = $src_pos[1];
+ $this->srcDir = 1;
+ $this->xDest = $dest_pos[0];
+ $this->destDir = -1;
+ } elseif ($d == $d3) {
+ $this->xSrc = $src_pos[0];
+ $this->srcDir = -1;
+ $this->xDest = $dest_pos[1];
+ $this->destDir = 1;
+ } else {
+ $this->xSrc = $src_pos[1];
+ $this->srcDir = 1;
+ $this->xDest = $dest_pos[1];
+ $this->destDir = 1;
+ }
+ $this->ySrc = $src_pos[2];
+ $this->yDest = $dest_pos[2];
+ }
+
+ /**
+ * Gets arrows coordinates
+ *
+ * @param string $table The current table name
+ * @param string $column The relation column name
+ *
+ * @return array Arrows coordinates
+ *
+ * @access private
+ */
+ private function _getXy($table, $column)
+ {
+ $pos = array_search($column, $table->fields);
+ // x_left, x_right, y
+ return array(
+ $table->x,
+ $table->x + $table->width,
+ $table->y + ($pos + 1.5) * $table->heightCell
+ );
+ }
+
+ /**
+ * draws relation links and arrows shows foreign key relations
+ *
+ * @param boolean $changeColor Whether to use one color per relation or not
+ * @param integer $i The id of the link to draw
+ *
+ * @global object $pdf The current PDF document
+ *
+ * @access public
+ *
+ * @return void
+ *
+ * @see PMA_Schema_PDF
+ */
+ public function relationDraw($changeColor, $i)
+ {
+ global $pdf;
+
+ if ($changeColor) {
+ $d = $i % 6;
+ $j = ($i - $d) / 6;
+ $j = $j % 4;
+ $j++;
+ $case = array(
+ array(1, 0, 0),
+ array(0, 1, 0),
+ array(0, 0, 1),
+ array(1, 1, 0),
+ array(1, 0, 1),
+ array(0, 1, 1)
+ );
+ list ($a, $b, $c) = $case[$d];
+ $e = (1 - ($j - 1) / 6);
+ $pdf->SetDrawColor($a * 255 * $e, $b * 255 * $e, $c * 255 * $e);
+ } else {
+ $pdf->SetDrawColor(0);
+ }
+ $pdf->setLineWidthScale(0.2);
+ $pdf->lineScale(
+ $this->xSrc,
+ $this->ySrc,
+ $this->xSrc + $this->srcDir * $this->wTick,
+ $this->ySrc
+ );
+ $pdf->lineScale(
+ $this->xDest + $this->destDir * $this->wTick,
+ $this->yDest,
+ $this->xDest,
+ $this->yDest
+ );
+ $pdf->setLineWidthScale(0.1);
+ $pdf->lineScale(
+ $this->xSrc + $this->srcDir * $this->wTick,
+ $this->ySrc,
+ $this->xDest + $this->destDir * $this->wTick,
+ $this->yDest
+ );
+ /*
+ * Draws arrows ->
+ */
+ $root2 = 2 * sqrt(2);
+ $pdf->lineScale(
+ $this->xSrc + $this->srcDir * $this->wTick * 0.75,
+ $this->ySrc,
+ $this->xSrc + $this->srcDir * (0.75 - 1 / $root2) * $this->wTick,
+ $this->ySrc + $this->wTick / $root2
+ );
+ $pdf->lineScale(
+ $this->xSrc + $this->srcDir * $this->wTick * 0.75,
+ $this->ySrc,
+ $this->xSrc + $this->srcDir * (0.75 - 1 / $root2) * $this->wTick,
+ $this->ySrc - $this->wTick / $root2
+ );
+
+ $pdf->lineScale(
+ $this->xDest + $this->destDir * $this->wTick / 2,
+ $this->yDest,
+ $this->xDest + $this->destDir * (0.5 + 1 / $root2) * $this->wTick,
+ $this->yDest + $this->wTick / $root2
+ );
+ $pdf->lineScale(
+ $this->xDest + $this->destDir * $this->wTick / 2,
+ $this->yDest,
+ $this->xDest + $this->destDir * (0.5 + 1 / $root2) * $this->wTick,
+ $this->yDest - $this->wTick / $root2
+ );
+ $pdf->SetDrawColor(0);
+ }
+}
+
+/**
+ * Pdf Relation Schema Class
+ *
+ * Purpose of this class is to generate the PDF Document. PDF is widely
+ * used format for documenting text,fonts,images and 3d vector graphics.
+ *
+ * This class inherits Export_Relation_Schema class has common functionality added
+ * to this class
+ *
+ * @name Pdf_Relation_Schema
+ * @package PhpMyAdmin
+ */
+class PMA_Pdf_Relation_Schema extends PMA_Export_Relation_Schema
+{
+ /**
+ * Defines properties
+ */
+ private $_tables = array();
+ private $_ff = PMA_PDF_FONT;
+ private $_xMax = 0;
+ private $_yMax = 0;
+ private $_scale;
+ private $_xMin = 100000;
+ private $_yMin = 100000;
+ private $_topMargin = 10;
+ private $_bottomMargin = 10;
+ private $_leftMargin = 10;
+ private $_rightMargin = 10;
+ private $_tablewidth;
+
+ /**
+ * The "PMA_Pdf_Relation_Schema" constructor
+ *
+ * @global object $pdf The current PDF Schema document
+ * @global string $db The current db name
+ * @global array The relations settings
+ * @access private
+ * @see PMA_Schema_PDF
+ */
+ function __construct()
+ {
+ global $pdf, $db;
+
+ $this->setPageNumber($_POST['pdf_page_number']);
+ $this->setShowGrid(isset($_POST['show_grid']));
+ $this->setShowColor(isset($_POST['show_color']));
+ $this->setShowKeys(isset($_POST['show_keys']));
+ $this->setTableDimension(isset($_POST['show_table_dimension']));
+ $this->setAllTablesSameWidth(isset($_POST['all_tables_same_width']));
+ $this->setWithDataDictionary($_POST['with_doc']);
+ $this->setOrientation($_POST['orientation']);
+ $this->setPaper($_POST['paper']);
+ $this->setExportType($_POST['export_type']);
+
+ // Initializes a new document
+ $pdf = new PMA_Schema_PDF($this->orientation, 'mm', $this->paper);
+ $pdf->SetTitle(
+ sprintf(
+ __('Schema of the %s database - Page %s'),
+ $GLOBALS['db'],
+ $this->pageNumber
+ )
+ );
+ $pdf->setCMargin(0);
+ $pdf->Open();
+ $pdf->SetAutoPageBreak('auto');
+ $alltables = $this->getAllTables($db, $this->pageNumber);
+
+ if ($this->withDoc) {
+ $pdf->SetAutoPageBreak('auto', 15);
+ $pdf->setCMargin(1);
+ $this->dataDictionaryDoc($alltables);
+ $pdf->SetAutoPageBreak('auto');
+ $pdf->setCMargin(0);
+ }
+
+ $pdf->Addpage();
+
+ if ($this->withDoc) {
+ $pdf->SetLink($pdf->PMA_links['RT']['-'], -1);
+ $pdf->Bookmark(__('Relational schema'));
+ $pdf->SetAlias('{00}', $pdf->PageNo());
+ $this->_topMargin = 28;
+ $this->_bottomMargin = 28;
+ }
+
+ /* snip */
+ foreach ($alltables as $table) {
+ if (! isset($this->_tables[$table])) {
+ $this->_tables[$table] = new Table_Stats_Pdf(
+ $table,
+ null,
+ $this->pageNumber,
+ $this->_tablewidth,
+ $this->showKeys,
+ $this->tableDimension
+ );
+ }
+ if ($this->sameWide) {
+ $this->_tables[$table]->width = $this->_tablewidth;
+ }
+ $this->_setMinMax($this->_tables[$table]);
+ }
+
+ // Defines the scale factor
+ $this->_scale = ceil(
+ max(
+ ($this->_xMax - $this->_xMin)
+ / ($pdf->getPageWidth() - $this->_rightMargin - $this->_leftMargin),
+ ($this->_yMax - $this->_yMin)
+ / ($pdf->getPageHeight() - $this->_topMargin - $this->_bottomMargin)
+ ) * 100
+ ) / 100;
+
+ $pdf->setScale(
+ $this->_scale,
+ $this->_xMin,
+ $this->_yMin,
+ $this->_leftMargin,
+ $this->_topMargin
+ );
+ // Builds and save the PDF document
+ $pdf->setLineWidthScale(0.1);
+
+ if ($this->showGrid) {
+ $pdf->SetFontSize(10);
+ $this->_strokeGrid();
+ }
+ $pdf->setFontSizeScale(14);
+ // previous logic was checking master tables and foreign tables
+ // but I think that looping on every table of the pdf page as a master
+ // and finding its foreigns is OK (then we can support innodb)
+ $seen_a_relation = false;
+ foreach ($alltables as $one_table) {
+ $exist_rel = PMA_getForeigners($db, $one_table, '', 'both');
+ if ($exist_rel) {
+ $seen_a_relation = true;
+ foreach ($exist_rel as $master_field => $rel) {
+ // put the foreign table on the schema only if selected
+ // by the user
+ // (do not use array_search() because we would have to
+ // to do a === false and this is not PHP3 compatible)
+ if (in_array($rel['foreign_table'], $alltables)) {
+ $this->_addRelation(
+ $one_table,
+ $master_field,
+ $rel['foreign_table'],
+ $rel['foreign_field'],
+ $this->tableDimension
+ );
+ }
+ } // end while
+ } // end if
+ } // end while
+
+ if ($seen_a_relation) {
+ $this->_drawRelations($this->showColor);
+ }
+ $this->_drawTables($this->showColor);
+ }
+
+ /**
+ * Output Pdf Document for download
+ *
+ * @return void
+ * @access public
+ */
+ function showOutput()
+ {
+ $this->_showOutput($this->pageNumber);
+ }
+
+ /**
+ * Sets X and Y minimum and maximum for a table cell
+ *
+ * @param string $table The table name of which sets XY co-ordinates
+ *
+ * @return void
+ *
+ * @access private
+ */
+ private function _setMinMax($table)
+ {
+ $this->_xMax = max($this->_xMax, $table->x + $table->width);
+ $this->_yMax = max($this->_yMax, $table->y + $table->height);
+ $this->_xMin = min($this->_xMin, $table->x);
+ $this->_yMin = min($this->_yMin, $table->y);
+ }
+
+ /**
+ * Defines relation objects
+ *
+ * @param string $masterTable The master table name
+ * @param string $masterField The relation field in the master table
+ * @param string $foreignTable The foreign table name
+ * @param string $foreignField The relation field in the foreign table
+ * @param boolean $showInfo Whether to display table position or not
+ *
+ * @access private
+ *
+ * @return void
+ *
+ * @see _setMinMax
+ */
+ private function _addRelation($masterTable, $masterField, $foreignTable,
+ $foreignField, $showInfo
+ ) {
+ if (! isset($this->_tables[$masterTable])) {
+ $this->_tables[$masterTable] = new Table_Stats_Pdf(
+ $masterTable, null, $this->pageNumber,
+ $this->_tablewidth, false, $showInfo
+ );
+ $this->_setMinMax($this->_tables[$masterTable]);
+ }
+ if (! isset($this->_tables[$foreignTable])) {
+ $this->_tables[$foreignTable] = new Table_Stats_Pdf(
+ $foreignTable, null, $this->pageNumber,
+ $this->_tablewidth, false, $showInfo
+ );
+ $this->_setMinMax($this->_tables[$foreignTable]);
+ }
+ $this->relations[] = new Relation_Stats_Pdf(
+ $this->_tables[$masterTable], $masterField,
+ $this->_tables[$foreignTable], $foreignField
+ );
+ }
+
+ /**
+ * Draws the grid
+ *
+ * @global object $pdf the current PMA_Schema_PDF instance
+ *
+ * @access private
+ *
+ * @return void
+ *
+ * @see PMA_Schema_PDF
+ */
+ private function _strokeGrid()
+ {
+ global $pdf;
+
+ $gridSize = 10;
+ $labelHeight = 4;
+ $labelWidth = 5;
+ if ($this->withDoc) {
+ $topSpace = 6;
+ $bottomSpace = 15;
+ } else {
+ $topSpace = 0;
+ $bottomSpace = 0;
+ }
+
+ $pdf->SetMargins(0, 0);
+ $pdf->SetDrawColor(200, 200, 200);
+ // Draws horizontal lines
+ for ($l = 0; $l <= intval(($pdf->getPageHeight() - $topSpace - $bottomSpace) / $gridSize); $l++) {
+ $pdf->line(
+ 0, $l * $gridSize + $topSpace,
+ $pdf->getPageWidth(), $l * $gridSize + $topSpace
+ );
+ // Avoid duplicates
+ if ($l > 0
+ && $l <= intval(($pdf->getPageHeight() - $topSpace - $bottomSpace - $labelHeight) / $gridSize)
+ ) {
+ $pdf->SetXY(0, $l * $gridSize + $topSpace);
+ $label = (string) sprintf(
+ '%.0f',
+ ($l * $gridSize + $topSpace - $this->_topMargin)
+ * $this->_scale + $this->_yMin
+ );
+ $pdf->Cell($labelWidth, $labelHeight, ' ' . $label);
+ } // end if
+ } // end for
+ // Draws vertical lines
+ for ($j = 0; $j <= intval($pdf->getPageWidth() / $gridSize); $j++) {
+ $pdf->line(
+ $j * $gridSize,
+ $topSpace,
+ $j * $gridSize,
+ $pdf->getPageHeight() - $bottomSpace
+ );
+ $pdf->SetXY($j * $gridSize, $topSpace);
+ $label = (string) sprintf(
+ '%.0f',
+ ($j * $gridSize - $this->_leftMargin) * $this->_scale + $this->_xMin
+ );
+ $pdf->Cell($labelWidth, $labelHeight, $label);
+ }
+ }
+
+ /**
+ * Draws relation arrows
+ *
+ * @param boolean $changeColor Whether to use one color per relation or not
+ *
+ * @access private
+ *
+ * @return void
+ *
+ * @see Relation_Stats_Pdf::relationdraw()
+ */
+ private function _drawRelations($changeColor)
+ {
+ $i = 0;
+ foreach ($this->relations as $relation) {
+ $relation->relationDraw($changeColor, $i);
+ $i++;
+ }
+ }
+
+ /**
+ * Draws tables
+ *
+ * @param boolean|integer $changeColor Whether to display table position or not
+ *
+ * @access private
+ *
+ * @return void
+ *
+ * @see Table_Stats_Pdf::tableDraw()
+ */
+ private function _drawTables($changeColor = 0)
+ {
+ foreach ($this->_tables as $table) {
+ $table->tableDraw(null, $this->withDoc, $changeColor);
+ }
+ }
+
+ /**
+ * Ouputs the PDF document to a file
+ * or sends the output to browser
+ *
+ * @param integer $pageNumber page number
+ *
+ * @global object $pdf The current PDF document
+ * @global string $cfgRelation The current database name
+ * @global integer The current page number (from the
+ * $cfg['Servers'][$i]['table_coords'] table)
+ * @access private
+ *
+ * @return void
+ *
+ * @see PMA_Schema_PDF
+ */
+ private function _showOutput($pageNumber)
+ {
+ global $pdf, $cfgRelation;
+
+ // Get the name of this pdfpage to use as filename
+ $_name_sql = 'SELECT page_descr FROM '
+ . PMA_Util::backquote($GLOBALS['cfgRelation']['db']) . '.'
+ . PMA_Util::backquote($cfgRelation['pdf_pages'])
+ . ' WHERE page_nr = ' . $pageNumber;
+ $_name_rs = PMA_queryAsControlUser($_name_sql);
+ if ($_name_rs) {
+ $_name_row = $GLOBALS['dbi']->fetchRow($_name_rs);
+ $filename = $_name_row[0] . '.pdf';
+ }
+ if (empty($filename)) {
+ $filename = $pageNumber . '.pdf';
+ }
+ $pdf->Download($filename);
+ }
+
+ /**
+ * Generates data dictionary pages.
+ *
+ * @param array $alltables Tables to document.
+ *
+ * @return void
+ */
+ public function dataDictionaryDoc($alltables)
+ {
+ global $db, $pdf, $orientation, $paper;
+ // TOC
+ $pdf->addpage($_POST['orientation']);
+ $pdf->Cell(0, 9, __('Table of contents'), 1, 0, 'C');
+ $pdf->Ln(15);
+ $i = 1;
+ foreach ($alltables as $table) {
+ $pdf->PMA_links['doc'][$table]['-'] = $pdf->AddLink();
+ $pdf->SetX(10);
+ // $pdf->Ln(1);
+ $pdf->Cell(
+ 0, 6, __('Page number:') . ' {' . sprintf("%02d", $i) . '}', 0, 0,
+ 'R', 0, $pdf->PMA_links['doc'][$table]['-']
+ );
+ $pdf->SetX(10);
+ $pdf->Cell(
+ 0, 6, $i . ' ' . $table, 0, 1,
+ 'L', 0, $pdf->PMA_links['doc'][$table]['-']
+ );
+ // $pdf->Ln(1);
+ $fields = $GLOBALS['dbi']->getColumns($GLOBALS['db'], $table);
+ foreach ($fields as $row) {
+ $pdf->SetX(20);
+ $field_name = $row['Field'];
+ $pdf->PMA_links['doc'][$table][$field_name] = $pdf->AddLink();
+ //$pdf->Cell(
+ // 0, 6, $field_name, 0, 1,
+ // 'L', 0, $pdf->PMA_links['doc'][$table][$field_name]
+ //);
+ }
+ $i++;
+ }
+ $pdf->PMA_links['RT']['-'] = $pdf->AddLink();
+ $pdf->SetX(10);
+ $pdf->Cell(
+ 0, 6, __('Page number:') . ' {00}', 0, 0,
+ 'R', 0, $pdf->PMA_links['RT']['-']
+ );
+ $pdf->SetX(10);
+ $pdf->Cell(
+ 0, 6, $i . ' ' . __('Relational schema'), 0, 1,
+ 'L', 0, $pdf->PMA_links['RT']['-']
+ );
+ $z = 0;
+ foreach ($alltables as $table) {
+ $z++;
+ $pdf->SetAutoPageBreak(true, 15);
+ $pdf->addpage($_POST['orientation']);
+ $pdf->Bookmark($table);
+ $pdf->SetAlias('{' . sprintf("%02d", $z) . '}', $pdf->PageNo());
+ $pdf->PMA_links['RT'][$table]['-'] = $pdf->AddLink();
+ $pdf->SetLink($pdf->PMA_links['doc'][$table]['-'], -1);
+ $pdf->SetFont($this->_ff, 'B', 18);
+ $pdf->Cell(
+ 0, 8, $z . ' ' . $table, 1, 1,
+ 'C', 0, $pdf->PMA_links['RT'][$table]['-']
+ );
+ $pdf->SetFont($this->_ff, '', 8);
+ $pdf->ln();
+
+ $cfgRelation = PMA_getRelationsParam();
+ $comments = PMA_getComments($db, $table);
+ if ($cfgRelation['mimework']) {
+ $mime_map = PMA_getMIME($db, $table, true);
+ }
+
+ /**
+ * Gets table informations
+ */
+ $showtable = PMA_Table::sGetStatusInfo($db, $table);
+ $show_comment = isset($showtable['Comment'])
+ ? $showtable['Comment']
+ : '';
+ $create_time = isset($showtable['Create_time'])
+ ? PMA_Util::localisedDate(
+ strtotime($showtable['Create_time'])
+ )
+ : '';
+ $update_time = isset($showtable['Update_time'])
+ ? PMA_Util::localisedDate(
+ strtotime($showtable['Update_time'])
+ )
+ : '';
+ $check_time = isset($showtable['Check_time'])
+ ? PMA_Util::localisedDate(
+ strtotime($showtable['Check_time'])
+ )
+ : '';
+
+ /**
+ * Gets table keys and retains them
+ */
+ $result = $GLOBALS['dbi']->query(
+ 'SHOW KEYS FROM ' . PMA_Util::backquote($table) . ';'
+ );
+ $primary = '';
+ $indexes = array();
+ $lastIndex = '';
+ $indexes_info = array();
+ $indexes_data = array();
+ $pk_array = array(); // will be use to emphasis prim. keys in the table
+ // view
+ while ($row = $GLOBALS['dbi']->fetchAssoc($result)) {
+ // Backups the list of primary keys
+ if ($row['Key_name'] == 'PRIMARY') {
+ $primary .= $row['Column_name'] . ', ';
+ $pk_array[$row['Column_name']] = 1;
+ }
+ // Retains keys informations
+ if ($row['Key_name'] != $lastIndex) {
+ $indexes[] = $row['Key_name'];
+ $lastIndex = $row['Key_name'];
+ }
+ $indexes_info[$row['Key_name']]['Sequences'][]
+ = $row['Seq_in_index'];
+ $indexes_info[$row['Key_name']]['Non_unique'] = $row['Non_unique'];
+ if (isset($row['Cardinality'])) {
+ $indexes_info[$row['Key_name']]['Cardinality']
+ = $row['Cardinality'];
+ }
+ // I don't know what does following column mean....
+ // $indexes_info[$row['Key_name']]['Packed'] = $row['Packed'];
+ $indexes_info[$row['Key_name']]['Comment'] = $row['Comment'];
+
+ $indexes_data[$row['Key_name']][$row['Seq_in_index']]['Column_name']
+ = $row['Column_name'];
+ if (isset($row['Sub_part'])) {
+ $indexes_data[$row['Key_name']][$row['Seq_in_index']]['Sub_part']
+ = $row['Sub_part'];
+ }
+ } // end while
+ if ($result) {
+ $GLOBALS['dbi']->freeResult($result);
+ }
+
+ /**
+ * Gets fields properties
+ */
+ $columns = $GLOBALS['dbi']->getColumns($db, $table);
+ // Check if we can use Relations
+ if (!empty($cfgRelation['relation'])) {
+ // Find which tables are related with the current one and write it in
+ // an array
+ $res_rel = PMA_getForeigners($db, $table);
+
+ if (count($res_rel) > 0) {
+ $have_rel = true;
+ } else {
+ $have_rel = false;
+ }
+ } else {
+ $have_rel = false;
+ } // end if
+
+ /**
+ * Displays the comments of the table if MySQL >= 3.23
+ */
+
+ $break = false;
+ if (! empty($show_comment)) {
+ $pdf->Cell(0, 3, __('Table comments:') . ' ' . $show_comment, 0, 1);
+ $break = true;
+ }
+
+ if (! empty($create_time)) {
+ $pdf->Cell(0, 3, __('Creation:') . ' ' . $create_time, 0, 1);
+ $break = true;
+ }
+
+ if (! empty($update_time)) {
+ $pdf->Cell(0, 3, __('Last update:') . ' ' . $update_time, 0, 1);
+ $break = true;
+ }
+
+ if (! empty($check_time)) {
+ $pdf->Cell(0, 3, __('Last check:') . ' ' . $check_time, 0, 1);
+ $break = true;
+ }
+
+ if ($break == true) {
+ $pdf->Cell(0, 3, '', 0, 1);
+ $pdf->Ln();
+ }
+
+ $pdf->SetFont($this->_ff, 'B');
+ if (isset($orientation) && $orientation == 'L') {
+ $pdf->Cell(25, 8, __('Column'), 1, 0, 'C');
+ $pdf->Cell(20, 8, __('Type'), 1, 0, 'C');
+ $pdf->Cell(20, 8, __('Attributes'), 1, 0, 'C');
+ $pdf->Cell(10, 8, __('Null'), 1, 0, 'C');
+ $pdf->Cell(20, 8, __('Default'), 1, 0, 'C');
+ $pdf->Cell(25, 8, __('Extra'), 1, 0, 'C');
+ $pdf->Cell(45, 8, __('Links to'), 1, 0, 'C');
+
+ if ($paper == 'A4') {
+ $comments_width = 67;
+ } else {
+ // this is really intended for 'letter'
+ /**
+ * @todo find optimal width for all formats
+ */
+ $comments_width = 50;
+ }
+ $pdf->Cell($comments_width, 8, __('Comments'), 1, 0, 'C');
+ $pdf->Cell(45, 8, 'MIME', 1, 1, 'C');
+ $pdf->SetWidths(
+ array(25, 20, 20, 10, 20, 25, 45, $comments_width, 45)
+ );
+ } else {
+ $pdf->Cell(20, 8, __('Column'), 1, 0, 'C');
+ $pdf->Cell(20, 8, __('Type'), 1, 0, 'C');
+ $pdf->Cell(20, 8, __('Attributes'), 1, 0, 'C');
+ $pdf->Cell(10, 8, __('Null'), 1, 0, 'C');
+ $pdf->Cell(15, 8, __('Default'), 1, 0, 'C');
+ $pdf->Cell(15, 8, __('Extra'), 1, 0, 'C');
+ $pdf->Cell(30, 8, __('Links to'), 1, 0, 'C');
+ $pdf->Cell(30, 8, __('Comments'), 1, 0, 'C');
+ $pdf->Cell(30, 8, 'MIME', 1, 1, 'C');
+ $pdf->SetWidths(array(20, 20, 20, 10, 15, 15, 30, 30, 30));
+ }
+ $pdf->SetFont($this->_ff, '');
+
+ foreach ($columns as $row) {
+ $extracted_columnspec
+ = PMA_Util::extractColumnSpec($row['Type']);
+ $type = $extracted_columnspec['print_type'];
+ $attribute = $extracted_columnspec['attribute'];
+ if (! isset($row['Default'])) {
+ if ($row['Null'] != '' && $row['Null'] != 'NO') {
+ $row['Default'] = 'NULL';
+ }
+ }
+ $field_name = $row['Field'];
+ // $pdf->Ln();
+ $pdf->PMA_links['RT'][$table][$field_name] = $pdf->AddLink();
+ $pdf->Bookmark($field_name, 1, -1);
+ $pdf->SetLink($pdf->PMA_links['doc'][$table][$field_name], -1);
+ $pdf_row = array(
+ $field_name,
+ $type,
+ $attribute,
+ ($row['Null'] == '' || $row['Null'] == 'NO')
+ ? __('No')
+ : __('Yes'),
+ (isset($row['Default']) ? $row['Default'] : ''),
+ $row['Extra'],
+ (isset($res_rel[$field_name])
+ ? $res_rel[$field_name]['foreign_table'] . ' -> ' . $res_rel[$field_name]['foreign_field']
+ : ''),
+ (isset($comments[$field_name])
+ ? $comments[$field_name]
+ : ''),
+ (isset($mime_map) && isset($mime_map[$field_name])
+ ? str_replace('_', '/', $mime_map[$field_name]['mimetype'])
+ : '')
+ );
+ $links[0] = $pdf->PMA_links['RT'][$table][$field_name];
+ if (isset($res_rel[$field_name]['foreign_table'])
+ && isset($res_rel[$field_name]['foreign_field'])
+ && isset($pdf->PMA_links['doc'][$res_rel[$field_name]['foreign_table']][$res_rel[$field_name]['foreign_field']])
+ ) {
+ $links[6] = $pdf->PMA_links['doc'][$res_rel[$field_name]['foreign_table']][$res_rel[$field_name]['foreign_field']];
+ } else {
+ unset($links[6]);
+ }
+ $pdf->Row($pdf_row, $links);
+ } // end foreach
+ $pdf->SetFont($this->_ff, '', 14);
+ } //end each
+ }
+}
+?>
diff --git a/libraries/schema/Svg_Relation_Schema.class.php b/libraries/schema/Svg_Relation_Schema.class.php
new file mode 100644
index 0000000000..5304ab239e
--- /dev/null
+++ b/libraries/schema/Svg_Relation_Schema.class.php
@@ -0,0 +1,1014 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Classes to create relation schema in SVG format.
+ *
+ * @package PhpMyAdmin
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+require_once 'Export_Relation_Schema.class.php';
+
+/**
+ * This Class inherits the XMLwriter class and
+ * helps in developing structure of SVG Schema Export
+ *
+ * @package PhpMyAdmin
+ * @access public
+ * @see http://php.net/manual/en/book.xmlwriter.php
+ */
+class PMA_SVG extends XMLWriter
+{
+ public $title;
+ public $author;
+ public $font;
+ public $fontSize;
+
+ /**
+ * The "PMA_SVG" constructor
+ *
+ * Upon instantiation This starts writing the Svg XML document
+ *
+ * @see XMLWriter::openMemory(),XMLWriter::setIndent(),XMLWriter::startDocument()
+ */
+ function __construct()
+ {
+ $this->openMemory();
+ /*
+ * Set indenting using three spaces,
+ * so output is formatted
+ */
+
+ $this->setIndent(true);
+ $this->setIndentString(' ');
+ /*
+ * Create the XML document
+ */
+
+ $this->startDocument('1.0', 'UTF-8');
+ $this->startDtd(
+ 'svg', '-//W3C//DTD SVG 1.1//EN',
+ 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd'
+ );
+ $this->endDtd();
+ }
+
+ /**
+ * Set document title
+ *
+ * @param string $value sets the title text
+ *
+ * @return void
+ * @access public
+ */
+ function setTitle($value)
+ {
+ $this->title = $value;
+ }
+
+ /**
+ * Set document author
+ *
+ * @param string $value sets the author
+ *
+ * @return void
+ * @access public
+ */
+ function setAuthor($value)
+ {
+ $this->author = $value;
+ }
+
+ /**
+ * Set document font
+ *
+ * @param string $value sets the font e.g Arial, Sans-serif etc
+ *
+ * @return void
+ * @access public
+ */
+ function setFont($value)
+ {
+ $this->font = $value;
+ }
+
+ /**
+ * Get document font
+ *
+ * @return string returns the font name
+ * @access public
+ */
+ function getFont()
+ {
+ return $this->font;
+ }
+
+ /**
+ * Set document font size
+ *
+ * @param string $value sets the font size in pixels
+ *
+ * @return void
+ * @access public
+ */
+ function setFontSize($value)
+ {
+ $this->fontSize = $value;
+ }
+
+ /**
+ * Get document font size
+ *
+ * @return string returns the font size
+ * @access public
+ */
+ function getFontSize()
+ {
+ return $this->fontSize;
+ }
+
+ /**
+ * Starts Svg Document
+ *
+ * svg document starts by first initializing svg tag
+ * which contains all the attributes and namespace that needed
+ * to define the svg document
+ *
+ * @param integer $width total width of the Svg document
+ * @param integer $height total height of the Svg document
+ *
+ * @return void
+ * @access public
+ *
+ * @see XMLWriter::startElement(),XMLWriter::writeAttribute()
+ */
+ function startSvgDoc($width,$height)
+ {
+ $this->startElement('svg');
+ $this->writeAttribute('width', $width);
+ $this->writeAttribute('height', $height);
+ $this->writeAttribute('xmlns', 'http://www.w3.org/2000/svg');
+ $this->writeAttribute('version', '1.1');
+ }
+
+ /**
+ * Ends Svg Document
+ *
+ * @return void
+ * @access public
+ * @see XMLWriter::endElement(),XMLWriter::endDocument()
+ */
+ function endSvgDoc()
+ {
+ $this->endElement();
+ $this->endDocument();
+ }
+
+ /**
+ * output Svg Document
+ *
+ * svg document prompted to the user for download
+ * Svg document saved in .svg extension and can be
+ * easily changeable by using any svg IDE
+ *
+ * @param string $fileName file name
+ *
+ * @return void
+ * @access public
+ * @see XMLWriter::startElement(),XMLWriter::writeAttribute()
+ */
+ function showOutput($fileName)
+ {
+ //ob_get_clean();
+ $output = $this->flush();
+ PMA_Response::getInstance()->disable();
+ PMA_downloadHeader($fileName . '.svg', 'image/svg+xml', strlen($output));
+ print $output;
+ }
+
+ /**
+ * Draws Svg elements
+ *
+ * SVG has some predefined shape elements like rectangle & text
+ * and other elements who have x,y co-ordinates are drawn.
+ * specify their width and height and can give styles too.
+ *
+ * @param string $name Svg element name
+ * @param integer $x The x attr defines the left position of the element
+ * (e.g. x="0" places the element 0 pixels from the left of the browser window)
+ * @param integer $y The y attribute defines the top position of the element
+ * (e.g. y="0" places the element 0 pixels from the top of the browser window)
+ * @param integer $width The width attribute defines the width the element
+ * @param integer $height The height attribute defines the height the element
+ * @param string $text The text attribute defines the text the element
+ * @param string $styles The style attribute defines the style the element
+ * styles can be defined like CSS styles
+ *
+ * @return void
+ * @access public
+ *
+ * @see XMLWriter::startElement(), XMLWriter::writeAttribute(),
+ * XMLWriter::text(), XMLWriter::endElement()
+ */
+ function printElement($name, $x, $y, $width = '', $height = '',
+ $text = '', $styles = ''
+ ) {
+ $this->startElement($name);
+ $this->writeAttribute('width', $width);
+ $this->writeAttribute('height', $height);
+ $this->writeAttribute('x', $x);
+ $this->writeAttribute('y', $y);
+ $this->writeAttribute('style', $styles);
+ if (isset($text)) {
+ $this->writeAttribute('font-family', $this->font);
+ $this->writeAttribute('font-size', $this->fontSize);
+ $this->text($text);
+ }
+ $this->endElement();
+ }
+
+ /**
+ * Draws Svg Line element
+ *
+ * Svg line element is drawn for connecting the tables.
+ * arrows are also drawn by specify its start and ending
+ * co-ordinates
+ *
+ * @param string $name Svg element name i.e line
+ * @param integer $x1 Defines the start of the line on the x-axis
+ * @param integer $y1 Defines the start of the line on the y-axis
+ * @param integer $x2 Defines the end of the line on the x-axis
+ * @param integer $y2 Defines the end of the line on the y-axis
+ * @param string $styles The style attribute defines the style the element
+ * styles can be defined like CSS styles
+ *
+ * @return void
+ * @access public
+ *
+ * @see XMLWriter::startElement(), XMLWriter::writeAttribute(),
+ * XMLWriter::endElement()
+ */
+ function printElementLine($name,$x1,$y1,$x2,$y2,$styles)
+ {
+ $this->startElement($name);
+ $this->writeAttribute('x1', $x1);
+ $this->writeAttribute('y1', $y1);
+ $this->writeAttribute('x2', $x2);
+ $this->writeAttribute('y2', $y2);
+ $this->writeAttribute('style', $styles);
+ $this->endElement();
+ }
+
+ /**
+ * get width of string/text
+ *
+ * Svg text element width is calculated depending on font name
+ * and font size. It is very important to know the width of text
+ * because rectangle is drawn around it.
+ *
+ * This is a bit hardcore method. I didn't found any other than this.
+ *
+ * @param string $text string that width will be calculated
+ * @param integer $font name of the font like Arial,sans-serif etc
+ * @param integer $fontSize size of font
+ *
+ * @return integer width of the text
+ * @access public
+ */
+ function getStringWidth($text, $font, $fontSize)
+ {
+ // list of characters and their width modifiers
+ $charLists = array();
+
+ //ijl
+ $charLists[] = array("chars" => array("i", "j", "l"), "modifier" => 0.23);
+ //f
+ $charLists[] = array("chars" => array("f"), "modifier" => 0.27);
+ //tI
+ $charLists[] = array("chars" => array("t", "I"), "modifier" => 0.28);
+ //r
+ $charLists[] = array("chars" => array("r"), "modifier" => 0.34);
+ //1
+ $charLists[] = array("chars" => array("1"), "modifier" => 0.49);
+ //cksvxyzJ
+ $charLists[] = array(
+ "chars" => array("c", "k", "s", "v", "x", "y", "z", "J"),
+ "modifier" => 0.5
+ );
+ //abdeghnopquL023456789
+ $charLists[] = array(
+ "chars" => array(
+ "a", "b", "d", "e", "g", "h", "n", "o", "p", "q", "u", "L",
+ "0", "2", "3", "4", "5", "6", "7", "8", "9"
+ ),
+ "modifier" => 0.56
+ );
+ //FTZ
+ $charLists[] = array("chars" => array("F", "T", "Z"), "modifier" => 0.61);
+ //ABEKPSVXY
+ $charLists[] = array(
+ "chars" => array("A", "B", "E", "K", "P", "S", "V", "X", "Y"),
+ "modifier" => 0.67
+ );
+ //wCDHNRU
+ $charLists[] = array(
+ "chars" => array("w", "C", "D", "H", "N", "R", "U"),
+ "modifier" => 0.73
+ );
+ //GOQ
+ $charLists[] = array("chars" => array("G", "O", "Q"), "modifier" => 0.78);
+ //mM
+ $charLists[] = array("chars" => array("m", "M"), "modifier" => 0.84);
+ //W
+ $charLists[] = array("chars" => array("W"), "modifier" => 0.95);
+ //" "
+ $charLists[] = array("chars" => array(" "), "modifier" => 0.28);
+
+ /*
+ * Start by counting the width, giving each character a modifying value
+ */
+ $count = 0;
+
+ foreach ($charLists as $charList) {
+ $count += ((strlen($text)
+ - strlen(str_replace($charList["chars"], "", $text))
+ ) * $charList["modifier"]);
+ }
+
+ $text = str_replace(" ", "", $text);//remove the " "'s
+ //all other chars
+ $count = $count + (strlen(preg_replace("/[a-z0-9]/i", "", $text)) * 0.3);
+
+ $modifier = 1;
+ $font = strtolower($font);
+ switch ($font) {
+ /*
+ * no modifier for arial and sans-serif
+ */
+ case 'arial':
+ case 'sans-serif':
+ break;
+ /*
+ * .92 modifer for time, serif, brushscriptstd, and californian fb
+ */
+ case 'times':
+ case 'serif':
+ case 'brushscriptstd':
+ case 'californian fb':
+ $modifier = .92;
+ break;
+ /*
+ * 1.23 modifier for broadway
+ */
+ case 'broadway':
+ $modifier = 1.23;
+ break;
+ }
+ $textWidth = $count*$fontSize;
+ return ceil($textWidth*$modifier);
+ }
+}
+
+/**
+ * Table preferences/statistics
+ *
+ * This class preserves the table co-ordinates,fields
+ * and helps in drawing/generating the Tables in SVG XML document.
+ *
+ * @package PhpMyAdmin
+ * @name Table_Stats_Svg
+ * @see PMA_SVG
+ */
+class Table_Stats_Svg
+{
+ /**
+ * Defines properties
+ */
+
+ private $_tableName;
+ private $_showInfo = false;
+
+ public $width = 0;
+ public $height;
+ public $fields = array();
+ public $heightCell = 0;
+ public $currentCell = 0;
+ public $x, $y;
+ public $primary = array();
+
+ /**
+ * The "Table_Stats_Svg" constructor
+ *
+ * @param string $tableName The table name
+ * @param string $font Font face
+ * @param integer $fontSize The font size
+ * @param integer $pageNumber Page number
+ * @param integer &$same_wide_width The max. with among tables
+ * @param boolean $showKeys Whether to display keys or not
+ * @param boolean $showInfo Whether to display table position or not
+ *
+ * @global object $svg The current SVG image document
+ * @global integer The current page number (from the
+ * $cfg['Servers'][$i]['table_coords'] table)
+ * @global array $cfgRelation The relations settings
+ * @global string $db The current db name
+ *
+ * @access private
+ *
+ * @see PMA_SVG, Table_Stats_Svg::Table_Stats_setWidth,
+ * Table_Stats_Svg::Table_Stats_setHeight
+ */
+ function __construct(
+ $tableName, $font, $fontSize, $pageNumber,
+ &$same_wide_width, $showKeys = false, $showInfo = false
+ ) {
+ global $svg, $cfgRelation, $db;
+
+ $this->_tableName = $tableName;
+ $sql = 'DESCRIBE ' . PMA_Util::backquote($tableName);
+ $result = $GLOBALS['dbi']->tryQuery(
+ $sql, null, PMA_DatabaseInterface::QUERY_STORE
+ );
+ if (! $result || ! $GLOBALS['dbi']->numRows($result)) {
+ $svg->dieSchema(
+ $pageNumber,
+ "SVG",
+ sprintf(__('The %s table doesn\'t exist!'), $tableName)
+ );
+ }
+
+ /*
+ * load fields
+ * check to see if it will load all fields or only the foreign keys
+ */
+
+ if ($showKeys) {
+ $indexes = PMA_Index::getFromTable($this->_tableName, $db);
+ $all_columns = array();
+ foreach ($indexes as $index) {
+ $all_columns = array_merge(
+ $all_columns,
+ array_flip(array_keys($index->getColumns()))
+ );
+ }
+ $this->fields = array_keys($all_columns);
+ } else {
+ while ($row = $GLOBALS['dbi']->fetchRow($result)) {
+ $this->fields[] = $row[0];
+ }
+ }
+
+ $this->_showInfo = $showInfo;
+
+ // height and width
+ $this->_setHeightTable($fontSize);
+
+ // setWidth must me after setHeight, because title
+ // can include table height which changes table width
+ $this->_setWidthTable($font, $fontSize);
+ if ($same_wide_width < $this->width) {
+ $same_wide_width = $this->width;
+ }
+
+ // x and y
+ $sql = 'SELECT x, y FROM '
+ . PMA_Util::backquote($GLOBALS['cfgRelation']['db']) . '.'
+ . PMA_Util::backquote($cfgRelation['table_coords'])
+ . ' WHERE db_name = \'' . PMA_Util::sqlAddSlashes($db) . '\''
+ . ' AND table_name = \'' . PMA_Util::sqlAddSlashes($tableName) . '\''
+ . ' AND pdf_page_number = ' . $pageNumber;
+ $result = PMA_queryAsControlUser(
+ $sql, false, PMA_DatabaseInterface::QUERY_STORE
+ );
+
+ if (! $result || ! $GLOBALS['dbi']->numRows($result)) {
+ $svg->dieSchema(
+ $pageNumber,
+ "SVG",
+ sprintf(
+ __('Please configure the coordinates for table %s'),
+ $tableName
+ )
+ );
+ }
+ list($this->x, $this->y) = $GLOBALS['dbi']->fetchRow($result);
+ $this->x = (double) $this->x;
+ $this->y = (double) $this->y;
+ // displayfield
+ $this->displayfield = PMA_getDisplayField($db, $tableName);
+ // index
+ $result = $GLOBALS['dbi']->query(
+ 'SHOW INDEX FROM ' . PMA_Util::backquote($tableName) . ';',
+ null,
+ PMA_DatabaseInterface::QUERY_STORE
+ );
+ if ($GLOBALS['dbi']->numRows($result) > 0) {
+ while ($row = $GLOBALS['dbi']->fetchAssoc($result)) {
+ if ($row['Key_name'] == 'PRIMARY') {
+ $this->primary[] = $row['Column_name'];
+ }
+ }
+ }
+ }
+
+ /**
+ * Returns title of the current table,
+ * title can have the dimensions/co-ordinates of the table
+ *
+ * @return string title of the current table
+ * @access private
+ */
+ private function _getTitle()
+ {
+ return ($this->_showInfo
+ ? sprintf('%.0f', $this->width) . 'x'
+ . sprintf('%.0f', $this->heightCell)
+ : ''
+ ) . ' ' . $this->_tableName;
+ }
+
+ /**
+ * Sets the width of the table
+ *
+ * @param string $font The font size
+ * @param integer $fontSize The font size
+ *
+ * @global object $svg The current SVG image document
+ *
+ * @return void
+ * @access private
+ *
+ * @see PMA_SVG
+ */
+ private function _setWidthTable($font,$fontSize)
+ {
+ global $svg;
+
+ foreach ($this->fields as $field) {
+ $this->width = max(
+ $this->width,
+ $svg->getStringWidth($field, $font, $fontSize)
+ );
+ }
+ $this->width += $svg->getStringWidth(' ', $font, $fontSize);
+
+ /*
+ * it is unknown what value must be added, because
+ * table title is affected by the tabe width value
+ */
+ while ($this->width
+ < $svg->getStringWidth($this->_getTitle(), $font, $fontSize)
+ ) {
+ $this->width += 7;
+ }
+ }
+
+ /**
+ * Sets the height of the table
+ *
+ * @param integer $fontSize font size
+ *
+ * @return void
+ * @access private
+ */
+ function _setHeightTable($fontSize)
+ {
+ $this->heightCell = $fontSize + 4;
+ $this->height = (count($this->fields) + 1) * $this->heightCell;
+ }
+
+ /**
+ * draw the table
+ *
+ * @param boolean $showColor Whether to display color
+ *
+ * @global object $svg The current SVG image document
+ *
+ * @access public
+ * @return void
+ *
+ * @see PMA_SVG,PMA_SVG::printElement
+ */
+ public function tableDraw($showColor)
+ {
+ global $svg;
+ //echo $this->_tableName.'<br />';
+ $svg->printElement(
+ 'rect', $this->x, $this->y, $this->width,
+ $this->heightCell, null, 'fill:red;stroke:black;'
+ );
+ $svg->printElement(
+ 'text', $this->x + 5, $this->y+ 14, $this->width, $this->heightCell,
+ $this->_getTitle(), 'fill:none;stroke:black;'
+ );
+ foreach ($this->fields as $field) {
+ $this->currentCell += $this->heightCell;
+ $showColor = 'none';
+ if ($showColor) {
+ if (in_array($field, $this->primary)) {
+ $showColor = '#0c0';
+ }
+ if ($field == $this->displayfield) {
+ $showColor = 'none';
+ }
+ }
+ $svg->printElement(
+ 'rect', $this->x, $this->y + $this->currentCell, $this->width,
+ $this->heightCell, null, 'fill:'.$showColor.';stroke:black;'
+ );
+ $svg->printElement(
+ 'text', $this->x + 5, $this->y + 14 + $this->currentCell,
+ $this->width, $this->heightCell, $field, 'fill:none;stroke:black;'
+ );
+ }
+ }
+}
+
+
+/**
+ * Relation preferences/statistics
+ *
+ * This class fetches the table master and foreign fields positions
+ * and helps in generating the Table references and then connects
+ * master table's master field to foreign table's foreign key
+ * in SVG XML document.
+ *
+ * @package PhpMyAdmin
+ * @name Relation_Stats_Svg
+ * @see PMA_SVG::printElementLine
+ */
+class Relation_Stats_Svg
+{
+ /**
+ * Defines properties
+ */
+ public $xSrc, $ySrc;
+ public $srcDir ;
+ public $destDir;
+ public $xDest, $yDest;
+ public $wTick = 10;
+
+ /**
+ * The "Relation_Stats_Svg" constructor
+ *
+ * @param string $master_table The master table name
+ * @param string $master_field The relation field in the master table
+ * @param string $foreign_table The foreign table name
+ * @param string $foreign_field The relation field in the foreign table
+ *
+ * @see Relation_Stats_Svg::_getXy
+ */
+ function __construct($master_table, $master_field, $foreign_table,
+ $foreign_field
+ ) {
+ $src_pos = $this->_getXy($master_table, $master_field);
+ $dest_pos = $this->_getXy($foreign_table, $foreign_field);
+ /*
+ * [0] is x-left
+ * [1] is x-right
+ * [2] is y
+ */
+ $src_left = $src_pos[0] - $this->wTick;
+ $src_right = $src_pos[1] + $this->wTick;
+ $dest_left = $dest_pos[0] - $this->wTick;
+ $dest_right = $dest_pos[1] + $this->wTick;
+
+ $d1 = abs($src_left - $dest_left);
+ $d2 = abs($src_right - $dest_left);
+ $d3 = abs($src_left - $dest_right);
+ $d4 = abs($src_right - $dest_right);
+ $d = min($d1, $d2, $d3, $d4);
+
+ if ($d == $d1) {
+ $this->xSrc = $src_pos[0];
+ $this->srcDir = -1;
+ $this->xDest = $dest_pos[0];
+ $this->destDir = -1;
+ } elseif ($d == $d2) {
+ $this->xSrc = $src_pos[1];
+ $this->srcDir = 1;
+ $this->xDest = $dest_pos[0];
+ $this->destDir = -1;
+ } elseif ($d == $d3) {
+ $this->xSrc = $src_pos[0];
+ $this->srcDir = -1;
+ $this->xDest = $dest_pos[1];
+ $this->destDir = 1;
+ } else {
+ $this->xSrc = $src_pos[1];
+ $this->srcDir = 1;
+ $this->xDest = $dest_pos[1];
+ $this->destDir = 1;
+ }
+ $this->ySrc = $src_pos[2];
+ $this->yDest = $dest_pos[2];
+ }
+
+ /**
+ * Gets arrows coordinates
+ *
+ * @param string $table The current table name
+ * @param string $column The relation column name
+ *
+ * @return array Arrows coordinates
+ * @access private
+ */
+ function _getXy($table, $column)
+ {
+ $pos = array_search($column, $table->fields);
+ // x_left, x_right, y
+ return array(
+ $table->x,
+ $table->x + $table->width,
+ $table->y + ($pos + 1.5) * $table->heightCell
+ );
+ }
+
+ /**
+ * draws relation links and arrows shows foreign key relations
+ *
+ * @param boolean $changeColor Whether to use one color per relation or not
+ *
+ * @global object $svg The current SVG image document
+ *
+ * @return void
+ * @access public
+ *
+ * @see PMA_SVG
+ */
+ public function relationDraw($changeColor)
+ {
+ global $svg;
+
+ if ($changeColor) {
+ $listOfColors = array(
+ 'red',
+ 'grey',
+ 'black',
+ 'yellow',
+ 'green',
+ 'cyan',
+ ' orange'
+ );
+ shuffle($listOfColors);
+ $color = $listOfColors[0];
+ } else {
+ $color = 'black';
+ }
+
+ $svg->printElementLine(
+ 'line', $this->xSrc, $this->ySrc,
+ $this->xSrc + $this->srcDir * $this->wTick, $this->ySrc,
+ 'fill:' . $color . ';stroke:black;stroke-width:2;'
+ );
+ $svg->printElementLine(
+ 'line', $this->xDest + $this->destDir * $this->wTick,
+ $this->yDest, $this->xDest, $this->yDest,
+ 'fill:' . $color . ';stroke:black;stroke-width:2;'
+ );
+ $svg->printElementLine(
+ 'line', $this->xSrc + $this->srcDir * $this->wTick, $this->ySrc,
+ $this->xDest + $this->destDir * $this->wTick, $this->yDest,
+ 'fill:' . $color . ';stroke:' . $color . ';stroke-width:1;'
+ );
+ $root2 = 2 * sqrt(2);
+ $svg->printElementLine(
+ 'line', $this->xSrc + $this->srcDir * $this->wTick * 0.75, $this->ySrc,
+ $this->xSrc + $this->srcDir * (0.75 - 1 / $root2) * $this->wTick,
+ $this->ySrc + $this->wTick / $root2,
+ 'fill:' . $color . ';stroke:black;stroke-width:2;'
+ );
+ $svg->printElementLine(
+ 'line', $this->xSrc + $this->srcDir * $this->wTick * 0.75, $this->ySrc,
+ $this->xSrc + $this->srcDir * (0.75 - 1 / $root2) * $this->wTick,
+ $this->ySrc - $this->wTick / $root2,
+ 'fill:' . $color . ';stroke:black;stroke-width:2;'
+ );
+ $svg->printElementLine(
+ 'line', $this->xDest + $this->destDir * $this->wTick / 2, $this->yDest,
+ $this->xDest + $this->destDir * (0.5 + 1 / $root2) * $this->wTick,
+ $this->yDest + $this->wTick / $root2,
+ 'fill:' . $color . ';stroke:black;stroke-width:2;'
+ );
+ $svg->printElementLine(
+ 'line', $this->xDest + $this->destDir * $this->wTick / 2, $this->yDest,
+ $this->xDest + $this->destDir * (0.5 + 1 / $root2) * $this->wTick,
+ $this->yDest - $this->wTick / $root2,
+ 'fill:' . $color . ';stroke:black;stroke-width:2;'
+ );
+ }
+}
+/*
+* end of the "Relation_Stats_Svg" class
+*/
+
+/**
+ * Svg Relation Schema Class
+ *
+ * Purpose of this class is to generate the SVG XML Document because
+ * SVG defines the graphics in XML format which is used for representing
+ * the database diagrams as vector image. This class actually helps
+ * in preparing SVG XML format.
+ *
+ * SVG XML is generated by using XMLWriter php extension and this class
+ * inherits Export_Relation_Schema class has common functionality added
+ * to this class
+ *
+ * @package PhpMyAdmin
+ * @name Svg_Relation_Schema
+ */
+class PMA_Svg_Relation_Schema extends PMA_Export_Relation_Schema
+{
+
+ private $_tables = array();
+ private $_relations = array();
+ private $_xMax = 0;
+ private $_yMax = 0;
+ private $_xMin = 100000;
+ private $_yMin = 100000;
+ private $_tablewidth;
+
+ /**
+ * The "PMA_Svg_Relation_Schema" constructor
+ *
+ * Upon instantiation This starts writing the SVG XML document
+ * user will be prompted for download as .svg extension
+ *
+ * @see PMA_SVG
+ */
+ function __construct()
+ {
+ global $svg,$db;
+
+ $this->setPageNumber($_POST['pdf_page_number']);
+ $this->setShowColor(isset($_POST['show_color']));
+ $this->setShowKeys(isset($_POST['show_keys']));
+ $this->setTableDimension(isset($_POST['show_table_dimension']));
+ $this->setAllTablesSameWidth(isset($_POST['all_tables_same_width']));
+ $this->setExportType($_POST['export_type']);
+
+ $svg = new PMA_SVG();
+ $svg->setTitle(
+ sprintf(
+ __('Schema of the %s database - Page %s'),
+ $db,
+ $this->pageNumber
+ )
+ );
+ $svg->SetAuthor('phpMyAdmin ' . PMA_VERSION);
+ $svg->setFont('Arial');
+ $svg->setFontSize('16px');
+ $svg->startSvgDoc('1000px', '1000px');
+ $alltables = $this->getAllTables($db, $this->pageNumber);
+
+ foreach ($alltables as $table) {
+ if (! isset($this->_tables[$table])) {
+ $this->_tables[$table] = new Table_Stats_Svg(
+ $table, $svg->getFont(), $svg->getFontSize(), $this->pageNumber,
+ $this->_tablewidth, $this->showKeys, $this->tableDimension
+ );
+ }
+
+ if ($this->sameWide) {
+ $this->_tables[$table]->width = $this->_tablewidth;
+ }
+ $this->_setMinMax($this->_tables[$table]);
+ }
+ $seen_a_relation = false;
+ foreach ($alltables as $one_table) {
+ $exist_rel = PMA_getForeigners($db, $one_table, '', 'both');
+ if ($exist_rel) {
+ $seen_a_relation = true;
+ foreach ($exist_rel as $master_field => $rel) {
+ /* put the foreign table on the schema only if selected
+ * by the user
+ * (do not use array_search() because we would have to
+ * to do a === false and this is not PHP3 compatible)
+ */
+ if (in_array($rel['foreign_table'], $alltables)) {
+ $this->_addRelation(
+ $one_table, $svg->getFont(), $svg->getFontSize(),
+ $master_field, $rel['foreign_table'],
+ $rel['foreign_field'], $this->tableDimension
+ );
+ }
+ }
+ }
+ }
+ if ($seen_a_relation) {
+ $this->_drawRelations($this->showColor);
+ }
+
+ $this->_drawTables($this->showColor);
+ $svg->endSvgDoc();
+ }
+
+ /**
+ * Output Svg Document for download
+ *
+ * @return void
+ * @access public
+ */
+ function showOutput()
+ {
+ global $svg,$db;
+ $svg->showOutput($db.'-'.$this->pageNumber);
+ }
+
+
+ /**
+ * Sets X and Y minimum and maximum for a table cell
+ *
+ * @param string $table The table name
+ *
+ * @return void
+ * @access private
+ */
+ private function _setMinMax($table)
+ {
+ $this->_xMax = max($this->_xMax, $table->x + $table->width);
+ $this->_yMax = max($this->_yMax, $table->y + $table->height);
+ $this->_xMin = min($this->_xMin, $table->x);
+ $this->_yMin = min($this->_yMin, $table->y);
+ }
+
+ /**
+ * Defines relation objects
+ *
+ * @param string $masterTable The master table name
+ * @param string $font The font face
+ * @param int $fontSize Font size
+ * @param string $masterField The relation field in the master table
+ * @param string $foreignTable The foreign table name
+ * @param string $foreignField The relation field in the foreign table
+ * @param boolean $showInfo Whether to display table position or not
+ *
+ * @access private
+ * @return void
+ *
+ * @see _setMinMax,Table_Stats_Svg::__construct(),
+ * Relation_Stats_Svg::__construct()
+ */
+ private function _addRelation(
+ $masterTable,$font,$fontSize, $masterField,
+ $foreignTable, $foreignField, $showInfo
+ ) {
+ if (! isset($this->_tables[$masterTable])) {
+ $this->_tables[$masterTable] = new Table_Stats_Svg(
+ $masterTable, $font, $fontSize, $this->pageNumber,
+ $this->_tablewidth, false, $showInfo
+ );
+ $this->_setMinMax($this->_tables[$masterTable]);
+ }
+ if (! isset($this->_tables[$foreignTable])) {
+ $this->_tables[$foreignTable] = new Table_Stats_Svg(
+ $foreignTable, $font, $fontSize, $this->pageNumber,
+ $this->_tablewidth, false, $showInfo
+ );
+ $this->_setMinMax($this->_tables[$foreignTable]);
+ }
+ $this->_relations[] = new Relation_Stats_Svg(
+ $this->_tables[$masterTable], $masterField,
+ $this->_tables[$foreignTable], $foreignField
+ );
+ }
+
+ /**
+ * Draws relation arrows and lines
+ * connects master table's master field to
+ * foreign table's forein field
+ *
+ * @param boolean $changeColor Whether to use one color per relation or not
+ *
+ * @return void
+ * @access private
+ *
+ * @see Relation_Stats_Svg::relationDraw()
+ */
+ private function _drawRelations($changeColor)
+ {
+ foreach ($this->_relations as $relation) {
+ $relation->relationDraw($changeColor);
+ }
+ }
+
+ /**
+ * Draws tables
+ *
+ * @param boolean $changeColor Whether to show color for primary fields or not
+ *
+ * @return void
+ * @access private
+ *
+ * @see Table_Stats_Svg::Table_Stats_tableDraw()
+ */
+ private function _drawTables($changeColor)
+ {
+ foreach ($this->_tables as $table) {
+ $table->tableDraw($changeColor);
+ }
+ }
+}
+?>
diff --git a/libraries/schema/User_Schema.class.php b/libraries/schema/User_Schema.class.php
new file mode 100644
index 0000000000..b92c732025
--- /dev/null
+++ b/libraries/schema/User_Schema.class.php
@@ -0,0 +1,979 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Schema support library
+ *
+ * @package PhpMyAdmin-schema
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+require_once 'libraries/schema/Export_Relation_Schema.class.php';
+
+/**
+ * This Class interacts with the user to gather the information
+ * about their tables for which they want to export the relational schema
+ * export options are shown to user from they can choose
+ *
+ * @package PhpMyAdmin-schema
+ */
+
+class PMA_User_Schema
+{
+
+ public $chosenPage;
+ public $autoLayoutForeign;
+ public $autoLayoutInternal;
+ public $pageNumber;
+ public $c_table_rows;
+ public $action;
+
+ /**
+ * Sets action to be performed with schema.
+ *
+ * @param string $value action name
+ *
+ * @return void
+ */
+ public function setAction($value)
+ {
+ $this->action = $value;
+ }
+
+ /**
+ * This function will process the user defined pages
+ * and tables which will be exported as Relational schema
+ * you can set the table positions on the paper via scratchboard
+ * for table positions, put the x,y co-ordinates
+ *
+ * $this->action tells what the Schema is supposed to do
+ * create and select a page, generate schema etc
+ *
+ * @access public
+ * @return void
+ */
+ public function processUserChoice()
+ {
+ global $db, $cfgRelation;
+
+ if (isset($this->action)) {
+ switch ($this->action) {
+ case 'selectpage':
+ $this->chosenPage = $_REQUEST['chpage'];
+ if ('1' == $_REQUEST['action_choose']) {
+ $this->deleteCoordinates(
+ $db,
+ $cfgRelation,
+ $this->chosenPage
+ );
+ $this->deletePages(
+ $db,
+ $cfgRelation,
+ $this->chosenPage
+ );
+ $this->chosenPage = 0;
+ }
+ break;
+ case 'createpage':
+ $this->pageNumber = PMA_REL_createPage(
+ $_POST['newpage'],
+ $cfgRelation,
+ $db
+ );
+ $this->autoLayoutForeign = isset($_POST['auto_layout_foreign'])
+ ? "1"
+ : null;
+ $this->autoLayoutInternal = isset($_POST['auto_layout_internal'])
+ ? "1"
+ : null;
+ $this->processRelations(
+ $db,
+ $this->pageNumber,
+ $cfgRelation
+ );
+ break;
+ case 'edcoord':
+ $this->chosenPage = $_POST['chpage'];
+ $this->c_table_rows = $_POST['c_table_rows'];
+ $this->_editCoordinates($db, $cfgRelation);
+ break;
+ case 'delete_old_references':
+ $this->_deleteTableRows(
+ $_POST['delrow'],
+ $cfgRelation,
+ $db,
+ $_POST['chpage']
+ );
+ break;
+ case 'process_export':
+ $this->_processExportSchema();
+ break;
+
+ } // end switch
+ } // end if (isset($do))
+
+ }
+
+ /**
+ * shows/displays the HTML FORM to create the page
+ *
+ * @param string $db name of the selected database
+ *
+ * @return void
+ * @access public
+ */
+ public function showCreatePageDialog($db)
+ {
+ $htmlString = '<form method="post" action="schema_edit.php"'
+ . ' name="frm_create_page">'
+ . '<fieldset>'
+ . '<legend>'
+ . __('Create a page')
+ . '</legend>'
+ . PMA_URL_getHiddenInputs($db)
+ . '<input type="hidden" name="do" value="createpage" />'
+ . '<table>'
+ . '<tr>'
+ . '<td><label for="id_newpage">' . __('Page name') . '</label></td>'
+ . '<td>'
+ . '<input type="text" name="newpage" id="id_newpage"'
+ . ' size="20" maxlength="50" />'
+ . '</td>'
+ . '</tr>'
+ . '<tr>'
+ . __('Automatic layout based on') . '</td>'
+ . '<td>'
+ . '<input type="checkbox" name="auto_layout_internal"'
+ . ' id="id_auto_layout_internal" /><label for="id_auto_layout_internal">'
+ . __('Internal relations') . '</label><br />';
+
+ /*
+ * Check to see whether INNODB and PBXT storage engines
+ * are Available in MYSQL PACKAGE
+ * If available, then provide AutoLayout for Foreign Keys in Schema View
+ */
+
+ if (PMA_StorageEngine::isValid('InnoDB')
+ || PMA_StorageEngine::isValid('PBXT')
+ ) {
+ $htmlString .= '<input type="checkbox" name="auto_layout_foreign"'
+ . ' id="id_auto_layout_foreign" />'
+ . '<label for="id_auto_layout_foreign">'
+ . __('FOREIGN KEY') . '</label><br />';
+ }
+
+ $htmlString .= '</td></tr>'
+ . '</table>'
+ . '</fieldset>'
+ . '<fieldset class="tblFooters">'
+ . '<input type="submit" value="' . __('Go') . '" />'
+ . '</fieldset>'
+ . '</form>';
+
+ echo $htmlString;
+ }
+
+ /**
+ * shows/displays the created page names in a drop down list
+ * User can select any page number and edit it using dashboard etc
+ *
+ * @return void
+ * @access public
+ */
+ public function selectPage()
+ {
+ global $db,$table,$cfgRelation;
+ $page_query = 'SELECT * FROM '
+ . PMA_Util::backquote($GLOBALS['cfgRelation']['db']) . '.'
+ . PMA_Util::backquote($cfgRelation['pdf_pages'])
+ . ' WHERE db_name = \'' . PMA_Util::sqlAddSlashes($db) . '\'';
+ $page_rs = PMA_queryAsControlUser(
+ $page_query, false, PMA_DatabaseInterface::QUERY_STORE
+ );
+
+ if ($page_rs && $GLOBALS['dbi']->numRows($page_rs) > 0) {
+ ?>
+ <form method="get" action="schema_edit.php" name="frm_select_page">
+ <fieldset>
+ <legend>
+ <?php echo __('Please choose a page to edit') . "\n"; ?>
+ </legend>
+ <?php echo PMA_URL_getHiddenInputs($db, $table); ?>
+ <input type="hidden" name="do" value="selectpage" />
+ <select name="chpage" id="chpage" class="autosubmit">
+ <option value="0"><?php echo __('Select page'); ?></option>
+ <?php
+ while ($curr_page = $GLOBALS['dbi']->fetchAssoc($page_rs)) {
+ echo "\n" . ' '
+ . '<option value="' . $curr_page['page_nr'] . '"';
+ if (isset($this->chosenPage)
+ && $this->chosenPage == $curr_page['page_nr']
+ ) {
+ echo ' selected="selected"';
+ }
+ echo '>' . $curr_page['page_nr'] . ': '
+ . htmlspecialchars($curr_page['page_descr']) . '</option>';
+ } // end while
+ echo "\n";
+ ?>
+ </select>
+ <?php
+ $choices = array(
+ '0' => __('Edit'),
+ '1' => __('Delete')
+ );
+ echo PMA_Util::getRadioFields(
+ 'action_choose', $choices, '0', false
+ );
+ unset($choices);
+ ?>
+ </fieldset>
+ <fieldset class="tblFooters">
+ <input type="submit" value="<?php echo __('Go'); ?>" /><br />
+ </fieldset>
+ </form>
+ <?php
+ } // end IF
+ echo "\n";
+ } // end function
+
+ /**
+ * A dashboard is displayed to AutoLayout the position of tables
+ * users can drag n drop the tables and change their positions
+ *
+ * @return void
+ * @access public
+ */
+ public function showTableDashBoard()
+ {
+ global $db, $cfgRelation, $table, $with_field_names;
+ /*
+ * We will need an array of all tables in this db
+ */
+ $selectboxall = array('--');
+ $alltab_rs = $GLOBALS['dbi']->query(
+ 'SHOW TABLES FROM ' . PMA_Util::backquote($db) . ';',
+ null,
+ PMA_DatabaseInterface::QUERY_STORE
+ );
+ while ($val = @$GLOBALS['dbi']->fetchRow($alltab_rs)) {
+ $selectboxall[] = $val[0];
+ }
+
+ $tabExist = array();
+
+ /*
+ * Now if we already have chosen a page number then we should
+ * show the tables involved
+ */
+ if (isset($this->chosenPage) && $this->chosenPage > 0) {
+ echo "\n";
+ echo "<h2>" . __('Select Tables') . "</h2>";
+ $page_query = 'SELECT * FROM '
+ . PMA_Util::backquote($GLOBALS['cfgRelation']['db'])
+ . '.' . PMA_Util::backquote($cfgRelation['table_coords'])
+ . ' WHERE db_name = \'' . PMA_Util::sqlAddSlashes($db) . '\''
+ . ' AND pdf_page_number = \''
+ . PMA_Util::sqlAddSlashes($this->chosenPage) . '\'';
+ $page_rs = PMA_queryAsControlUser($page_query, false);
+ $array_sh_page = array();
+ while ($temp_sh_page = @$GLOBALS['dbi']->fetchAssoc($page_rs)) {
+ $array_sh_page[] = $temp_sh_page;
+ }
+ /*
+ * Display WYSIWYG parts
+ */
+
+ if (! isset($_POST['with_field_names'])
+ && ! isset($_POST['showwysiwyg'])
+ ) {
+ $with_field_names = true;
+ } elseif (isset($_POST['with_field_names'])) {
+ $with_field_names = true;
+ }
+ $this->_displayScratchboardTables($array_sh_page);
+
+ echo '<form method="post" action="schema_edit.php" name="edcoord">';
+
+ echo PMA_URL_getHiddenInputs($db, $table);
+ echo '<input type="hidden" name="chpage" '
+ . 'value="' . htmlspecialchars($this->chosenPage) . '" />';
+ echo '<input type="hidden" name="do" value="edcoord" />';
+ echo '<table>';
+ echo '<tr>';
+ echo '<th>' . __('Table') . '</th>';
+ echo '<th>' . __('Delete') . '</th>';
+ echo '<th>X</th>';
+ echo '<th>Y</th>';
+ echo '</tr>';
+
+ if (isset($ctable)) {
+ unset($ctable);
+ }
+
+ /*
+ * Add one more empty row
+ */
+ $array_sh_page[] = array(
+ 'table_name' => '',
+ 'x' => '0',
+ 'y' => '0',
+ );
+
+ $i = 0;
+ $odd_row = true;
+ foreach ($array_sh_page as $sh_page) {
+ $_mtab = $sh_page['table_name'];
+ if (! empty($_mtab)) {
+ $tabExist[$_mtab] = false;
+ }
+
+ echo '<tr class="noclick ';
+ if ($odd_row) {
+ echo 'odd';
+ } else {
+ echo 'even';
+ }
+ echo '">';
+ $odd_row = ! $odd_row;
+
+ echo '<td>';
+ echo '<select name="c_table_' . $i . '[name]">';
+
+ foreach ($selectboxall as $value) {
+ echo '<option value="' . htmlspecialchars($value) . '"';
+ if (! empty($_mtab) && $value == $_mtab) {
+ echo ' selected="selected"';
+ $tabExist[$_mtab] = true;
+ }
+ echo '>' . htmlspecialchars($value) . '</option>';
+ }
+ echo '</select>';
+ echo '</td>';
+
+ echo '<td>';
+ echo '<input type="checkbox" id="id_c_table_' . $i .'" '
+ . 'name="c_table_' . $i . '[delete]" value="y" />';
+ echo '<label for="id_c_table_' . $i .'">'
+ . __('Delete') . '</label>';
+ echo '</td>';
+
+ echo '<td>';
+ echo '<input type="text" class="position-change" data-axis="left" '
+ . 'data-number="' . $i . '" id="c_table_' . $i . '_x" '
+ . 'name="c_table_' . $i . '[x]" value="'
+ . $sh_page['x'] . '" />';
+ echo '</td>';
+
+ echo '<td>';
+ echo '<input type="text" class="position-change" data-axis="top" '
+ . 'data-number="' . $i . '" id="c_table_' . $i . '_y" '
+ . 'name="c_table_' . $i . '[y]" value="'
+ . $sh_page['y'] . '" />';
+ echo '</td>';
+ echo '</tr>';
+ $i++;
+ }
+
+ echo '</table>';
+
+ echo '<input type="hidden" name="c_table_rows" value="' . $i . '" />';
+ echo '<input type="hidden" id="showwysiwyg" name="showwysiwyg" value="'
+ . ((isset($showwysiwyg) && $showwysiwyg == '1') ? '1' : '0')
+ . '" />';
+ echo '<input type="checkbox" id="id_with_field_names" '
+ . 'name="with_field_names" '
+ . (isset($with_field_names) ? 'checked="checked"' : ''). ' />';
+ echo '<label for="id_with_field_names">'
+ . __('Column names') . '</label><br />';
+ echo '<input type="submit" value="' . __('Save') . '" />';
+ echo '</form>' . "\n\n";
+ } // end if
+
+ if (isset($tabExist)) {
+ $this->_deleteTables($db, $this->chosenPage, $tabExist);
+ }
+ }
+
+ /**
+ * show Export relational schema generation options
+ * user can select export type of his own choice
+ * and the attributes related to it
+ *
+ * @return void
+ * @access public
+ */
+
+ public function displaySchemaGenerationOptions()
+ {
+ global $cfg, $db, $test_rs, $chpage;
+
+ $htmlString = '<form method="post" action="schema_export.php"'
+ . ' class="disableAjax">'
+ . '<fieldset>'
+ . '<legend>'
+ . PMA_URL_getHiddenInputs($db);
+
+ if (PMA_Util::showIcons('ActionLinksMode')) {
+ $htmlString .= PMA_Util::getImage('b_views.png');
+ }
+
+ /*
+ * TODO: This list should be generated dynamically based on list of
+ * available plugins.
+ */
+ $htmlString .= __('Display relational schema')
+ . ':'
+ . '</legend>'
+ . '<select name="export_type" id="export_type">';
+ if (file_exists(TCPDF_INC)) {
+ $htmlString .= '<option value="pdf" selected="selected">PDF</option>';
+ }
+ $htmlString .=
+ '<option value="svg">SVG</option>'
+ . '<option value="dia">DIA</option>'
+ . '<option value="eps">EPS</option>'
+ . '</select>'
+ . '<label>' . __('Select Export Relational Type') . '</label><br />';
+ if (isset($test_rs)) {
+ $htmlString .= '<label for="pdf_page_number_opt">'
+ . __('Page number:')
+ . '</label>'
+ . '<select name="pdf_page_number" id="pdf_page_number_opt">';
+
+ while ($pages = @$GLOBALS['dbi']->fetchAssoc($test_rs)) {
+ $htmlString .= '<option value="' . $pages['page_nr'] . '">'
+ . $pages['page_nr'] . ': '
+ . htmlspecialchars($pages['page_descr'])
+ . '</option>' . "\n";
+ } // end while
+ $GLOBALS['dbi']->freeResult($test_rs);
+ unset($test_rs);
+
+ $htmlString .= '</select><br />';
+ } else {
+ $htmlString .= '<input type="hidden" name="pdf_page_number"'
+ . ' value="' . htmlspecialchars($this->chosenPage) . '" />';
+ }
+ $htmlString .= '<input type="hidden" name="do" value="process_export" />'
+ . '<input type="hidden" name="chpage" value="' . $chpage . '" />'
+ . '<input type="checkbox" name="show_grid" id="show_grid_opt" />'
+ . '<label for="show_grid_opt">' . __('Show grid') . '</label><br />'
+ . '<input type="checkbox" name="show_color"'
+ . ' id="show_color_opt" checked="checked" />'
+ . '<label for="show_color_opt">' . __('Show color') . '</label>'
+ . '<br />'
+ . '<input type="checkbox" name="show_table_dimension"'
+ . ' id="show_table_dim_opt" />'
+ . '<label for="show_table_dim_opt">'
+ . __('Show dimension of tables')
+ . '</label><br />'
+ . '<input type="checkbox" name="all_tables_same_width"'
+ . ' id="all_tables_same_width" />'
+ . '<label for="all_tables_same_width">'
+ . __('Same width for all tables')
+ . '</label><br />'
+ . '<input type="checkbox" name="with_doc"'
+ . ' id="with_doc" checked="checked" />'
+ . '<label for="with_doc">' . __('Data Dictionary') . '</label><br />'
+ . '<input type="checkbox" name="show_keys" id="show_keys" />'
+ . '<label for="show_keys">' . __('Only show keys') . '</label><br />'
+ . '<select name="orientation" id="orientation_opt" class="paper-change">'
+ . '<option value="L">' . __('Landscape'). '</option>'
+ . '<option value="P">' . __('Portrait') . '</option>'
+ . '</select>'
+ . '<label for="orientation_opt">' . __('Orientation') . '</label>'
+ . '<br />'
+ . '<select name="paper" id="paper_opt" class="paper-change">';
+
+ foreach ($cfg['PDFPageSizes'] as $val) {
+ $htmlString .= '<option value="' . $val . '"';
+ if ($val == $cfg['PDFDefaultPageSize']) {
+ $htmlString .= ' selected="selected"';
+ }
+ $htmlString .= ' >' . $val . '</option>' . "\n";
+ }
+
+ $htmlString .= '</select>'
+ . '<label for="paper_opt">' . __('Paper size') . '</label>'
+ . '</fieldset>'
+ . '<fieldset class="tblFooters">'
+ . '<input type="submit" value="' . __('Go') . '" />'
+ . '</fieldset>'
+ . '</form>';
+
+ echo $htmlString;
+ }
+
+ /**
+ * Check if there are tables that need to be deleted in dashboard,
+ * if there are, ask the user for allowance
+ *
+ * @param string $db name of database selected
+ * @param integer $chpage selected page
+ * @param array $tabExist array of booleans
+ *
+ * @return void
+ * @access private
+ */
+ private function _deleteTables($db, $chpage, $tabExist)
+ {
+ $_strtrans = '';
+ $_strname = '';
+ $shoot = false;
+ if (empty($tabExist) || ! is_array($tabExist)) {
+ return;
+ }
+ foreach ($tabExist as $key => $value) {
+ if (! $value) {
+ $_strtrans .= '<input type="hidden" name="delrow[]" value="'
+ . htmlspecialchars($key) . '" />' . "\n";
+ $_strname .= '<li>' . htmlspecialchars($key) . '</li>' . "\n";
+ $shoot = true;
+ }
+ }
+ if (! $shoot) {
+ return;
+ }
+ echo '<br /><form action="schema_edit.php" method="post">' . "\n"
+ . PMA_URL_getHiddenInputs($db)
+ . '<input type="hidden" name="do" value="delete_old_references" />'
+ . "\n"
+ . '<input type="hidden" name="chpage" value="'
+ . htmlspecialchars($chpage) . '" />' . "\n"
+ . __(
+ 'The current page has references to tables that no longer exist.'
+ . ' Would you like to delete those references?'
+ )
+ . '<ul>' . "\n"
+ . $_strname
+ . '</ul>' . "\n"
+ . $_strtrans
+ . '<input type="submit" value="' . __('Go') . '" />' . "\n"
+ . '</form>';
+ }
+
+ /**
+ * Check if there are tables that need to be deleted in dashboard,
+ * if there are, ask the user for allowance
+ *
+ * @param array $array_sh_page array of tables on page
+ *
+ * @return void
+ * @access private
+ */
+ private function _displayScratchboardTables($array_sh_page)
+ {
+ global $with_field_names, $db;
+
+ echo '<form method="post" action="schema_edit.php" name="dragdrop">';
+ echo '<input type="button" name="dragdrop" id="toggle-dragdrop" '
+ . 'value="' . __('Toggle scratchboard') . '" />';
+ echo '<input type="button" name="dragdropreset" id="reset-dragdrop" '
+ . 'value="' . __('Reset') . '" />';
+ echo '</form>';
+ echo '<div id="pdflayout" class="pdflayout" style="visibility: hidden;">';
+
+ $i = 0;
+
+ foreach ($array_sh_page as $temp_sh_page) {
+ $drag_x = $temp_sh_page['x'];
+ $drag_y = $temp_sh_page['y'];
+
+ echo '<div id="table_' . $i . '" '
+ . 'data-number="' . $i .'" '
+ . 'data-x="' . $drag_x . '" '
+ . 'data-y="' . $drag_y . '" '
+ . 'class="pdflayout_table"'
+ . '>'
+ . '<u>'
+ . htmlspecialchars($temp_sh_page['table_name'])
+ . '</u>';
+
+ if (isset($with_field_names)) {
+ $fields = $GLOBALS['dbi']->getColumns(
+ $db, $temp_sh_page['table_name']
+ );
+ // if the table has been dropped from outside phpMyAdmin,
+ // we can no longer obtain its columns list
+ if ($fields) {
+ foreach ($fields as $row) {
+ echo '<br />' . htmlspecialchars($row['Field']) . "\n";
+ }
+ }
+ }
+ echo '</div>' . "\n";
+ $i++;
+ }
+
+ echo '</div>';
+ }
+
+ /**
+ * delete the table rows with table co-ordinates
+ *
+ * @param int $delrow delete selected table from list of tables
+ * @param array $cfgRelation relation settings
+ * @param string $db database name
+ * @param integer $chpage selected page for adding relations etc
+ *
+ * @return void
+ * @access private
+ */
+ private function _deleteTableRows($delrow,$cfgRelation,$db,$chpage)
+ {
+ foreach ($delrow as $current_row) {
+ $del_query = 'DELETE FROM '
+ . PMA_Util::backquote($GLOBALS['cfgRelation']['db']) . '.'
+ . PMA_Util::backquote($cfgRelation['table_coords']) . ' ' . "\n"
+ . ' WHERE db_name = \''
+ . PMA_Util::sqlAddSlashes($db) . '\'' . "\n"
+ . ' AND table_name = \''
+ . PMA_Util::sqlAddSlashes($current_row) . '\'' . "\n"
+ . ' AND pdf_page_number = \''
+ . PMA_Util::sqlAddSlashes($chpage) . '\'';
+ PMA_queryAsControlUser($del_query, false);
+ }
+ }
+
+ /**
+ * get all the export options and verify
+ * call and include the appropriate Schema Class depending on $export_type
+ *
+ * @return void
+ * @access private
+ */
+ private function _processExportSchema()
+ {
+ /**
+ * Settings for relation stuff
+ */
+ include_once './libraries/transformations.lib.php';
+ include_once './libraries/Index.class.php';
+ /**
+ * default is PDF, otherwise validate it's only letters a-z
+ */
+ global $db,$export_type;
+
+ if (! isset($export_type) || ! preg_match('/^[a-zA-Z]+$/', $export_type)) {
+ $export_type = 'pdf';
+ }
+ $GLOBALS['dbi']->selectDb($db);
+
+ $path = PMA_securePath(ucfirst($export_type));
+ $filename = 'libraries/schema/' . $path . '_Relation_Schema.class.php';
+ if (!file_exists($filename)) {
+ PMA_Export_Relation_Schema::dieSchema(
+ $_POST['chpage'],
+ $export_type,
+ __('File doesn\'t exist')
+ );
+ }
+ $GLOBALS['skip_import'] = false;
+ include $filename;
+ if ( $GLOBALS['skip_import']) {
+ PMA_Export_Relation_Schema::dieSchema(
+ $_POST['chpage'],
+ $export_type,
+ __('Plugin is disabled')
+ );
+ }
+ $class_name = 'PMA_' . $path . '_Relation_Schema';
+ $obj_schema = new $class_name();
+ $obj_schema->showOutput();
+ }
+
+ /**
+ * delete X and Y coordinates
+ *
+ * @param string $db The database name
+ * @param array $cfgRelation relation settings
+ * @param integer $choosePage selected page for adding relations etc
+ *
+ * @return void
+ * @access private
+ */
+ public function deleteCoordinates($db, $cfgRelation, $choosePage)
+ {
+ $query = 'DELETE FROM '
+ . PMA_Util::backquote($GLOBALS['cfgRelation']['db']) . '.'
+ . PMA_Util::backquote($cfgRelation['table_coords'])
+ . ' WHERE db_name = \'' . PMA_Util::sqlAddSlashes($db) . '\''
+ . ' AND pdf_page_number = \''
+ . PMA_Util::sqlAddSlashes($choosePage) . '\'';
+ PMA_queryAsControlUser($query, false);
+ }
+
+ /**
+ * delete pages
+ *
+ * @param string $db The database name
+ * @param array $cfgRelation relation settings
+ * @param integer $choosePage selected page for adding relations etc
+ *
+ * @return void
+ * @access private
+ */
+ public function deletePages($db, $cfgRelation, $choosePage)
+ {
+ $query = 'DELETE FROM '
+ . PMA_Util::backquote($GLOBALS['cfgRelation']['db']) . '.'
+ . PMA_Util::backquote($cfgRelation['pdf_pages'])
+ . ' WHERE db_name = \'' . PMA_Util::sqlAddSlashes($db) . '\''
+ . ' AND page_nr = \'' . PMA_Util::sqlAddSlashes($choosePage) . '\'';
+ PMA_queryAsControlUser($query, false);
+ }
+
+ /**
+ * process internal and foreign key relations
+ *
+ * @param string $db The database name
+ * @param integer $pageNumber document number/Id
+ * @param array $cfgRelation relation settings
+ *
+ * @return void
+ * @access private
+ */
+ public function processRelations($db, $pageNumber, $cfgRelation)
+ {
+ /*
+ * A u t o m a t i c l a y o u t
+ *
+ * There are 2 kinds of relations in PMA
+ * 1) Internal Relations 2) Foreign Key Relations
+ */
+ if (isset($this->autoLayoutInternal) || isset($this->autoLayoutForeign)) {
+ $all_tables = array();
+ }
+
+ if (isset($this->autoLayoutForeign)) {
+ /*
+ * get the tables list
+ * who support FOREIGN KEY, it's not
+ * important that we group together InnoDB tables
+ * and PBXT tables, as this logic is just to put
+ * the tables on the layout, not to determine relations
+ */
+ $tables = $GLOBALS['dbi']->getTablesFull($db);
+ $foreignkey_tables = array();
+ foreach ($tables as $table_name => $table_properties) {
+ if (PMA_Util::isForeignKeySupported($table_properties['ENGINE'])) {
+ $foreignkey_tables[] = $table_name;
+ }
+ }
+ $all_tables = $foreignkey_tables;
+ /*
+ * could be improved by finding the tables which have the
+ * most references keys and placing them at the beginning
+ * of the array (so that they are all center of schema)
+ */
+ unset($tables, $foreignkey_tables);
+ }
+
+ if (isset($this->autoLayoutInternal)) {
+ /*
+ * get the tables list who support Internal Relations;
+ * This type of relations will be created when
+ * you setup the PMA tables correctly
+ */
+ $master_tables = 'SELECT COUNT(master_table), master_table'
+ . ' FROM ' . PMA_Util::backquote($GLOBALS['cfgRelation']['db']) . '.'
+ . PMA_Util::backquote($cfgRelation['relation'])
+ . ' WHERE master_db = \'' . PMA_Util::sqlAddSlashes($db) . '\''
+ . ' GROUP BY master_table'
+ . ' ORDER BY COUNT(master_table) DESC';
+ $master_tables_rs = PMA_queryAsControlUser(
+ $master_tables, false, PMA_DatabaseInterface::QUERY_STORE
+ );
+ if ($master_tables_rs
+ && $GLOBALS['dbi']->numRows($master_tables_rs) > 0
+ ) {
+ /* first put all the master tables at beginning
+ * of the list, so they are near the center of
+ * the schema
+ */
+ while (list(, $master_table)
+ = $GLOBALS['dbi']->fetchRow($master_tables_rs)
+ ) {
+ $all_tables[] = $master_table;
+ }
+
+ /* Now for each master, add its foreigns into an array
+ * of foreign tables, if not already there
+ * (a foreign might be foreign for more than
+ * one table, and might be a master itself)
+ */
+
+ $foreign_tables = array();
+ foreach ($all_tables as $master_table) {
+ $foreigners = PMA_getForeigners($db, $master_table);
+ foreach ($foreigners as $foreigner) {
+ if (! in_array(
+ $foreigner['foreign_table'], $foreign_tables
+ )) {
+ $foreign_tables[] = $foreigner['foreign_table'];
+ }
+ }
+ }
+
+ /*
+ * Now merge the master and foreign arrays/tables
+ */
+ foreach ($foreign_tables as $foreign_table) {
+ if (! in_array($foreign_table, $all_tables)) {
+ $all_tables[] = $foreign_table;
+ }
+ }
+ }
+ }
+
+ if (isset($this->autoLayoutInternal) || isset($this->autoLayoutForeign)) {
+ $this->addRelationCoordinates(
+ $all_tables, $pageNumber, $db, $cfgRelation
+ );
+ }
+
+ $this->chosenPage = $pageNumber;
+ }
+
+ /**
+ * Add X and Y coordinates for a table
+ *
+ * @param array $all_tables A list of all tables involved
+ * @param integer $pageNumber document number/Id
+ * @param string $db The database name
+ * @param array $cfgRelation relation settings
+ *
+ * @return void
+ * @access private
+ */
+ public function addRelationCoordinates(
+ $all_tables, $pageNumber, $db, $cfgRelation
+ ) {
+ /*
+ * Now generate the coordinates for the schema
+ * in a clockwise spiral and add to co-ordinates table
+ */
+ $pos_x = 300;
+ $pos_y = 300;
+ $delta = 110;
+ $delta_mult = 1.10;
+ $direction = "right";
+ foreach ($all_tables as $current_table) {
+ /*
+ * save current table's coordinates
+ */
+ $insert_query = 'INSERT INTO '
+ . PMA_Util::backquote($GLOBALS['cfgRelation']['db']) . '.'
+ . PMA_Util::backquote($cfgRelation['table_coords']) . ' '
+ . '(db_name, table_name, pdf_page_number, x, y) '
+ . 'VALUES (\'' . PMA_Util::sqlAddSlashes($db) . '\', \''
+ . PMA_Util::sqlAddSlashes($current_table) . '\',' . $pageNumber
+ . ',' . $pos_x . ',' . $pos_y . ')';
+ PMA_queryAsControlUser($insert_query, false);
+
+ /*
+ * compute for the next table
+ */
+ switch ($direction) {
+ case 'right':
+ $pos_x += $delta;
+ $direction = "down";
+ $delta *= $delta_mult;
+ break;
+ case 'down':
+ $pos_y += $delta;
+ $direction = "left";
+ $delta *= $delta_mult;
+ break;
+ case 'left':
+ $pos_x -= $delta;
+ $direction = "up";
+ $delta *= $delta_mult;
+ break;
+ case 'up':
+ $pos_y -= $delta;
+ $direction = "right";
+ $delta *= $delta_mult;
+ break;
+ }
+ }
+ }
+
+ /**
+ * update X and Y coordinates for a table
+ *
+ * @param string $db The database name
+ * @param array $cfgRelation relation settings
+ *
+ * @return void
+ * @access private
+ */
+ private function _editCoordinates($db, $cfgRelation)
+ {
+ for ($i = 0; $i < $this->c_table_rows; $i++) {
+ $arrvalue = $_POST['c_table_' . $i];
+
+ if (! isset($arrvalue['x']) || $arrvalue['x'] == '') {
+ $arrvalue['x'] = 0;
+ }
+ if (! isset($arrvalue['y']) || $arrvalue['y'] == '') {
+ $arrvalue['y'] = 0;
+ }
+ if (isset($arrvalue['name']) && $arrvalue['name'] != '--') {
+ $test_query = 'SELECT * FROM '
+ . PMA_Util::backquote($GLOBALS['cfgRelation']['db']) . '.'
+ . PMA_Util::backquote($cfgRelation['table_coords'])
+ . ' WHERE db_name = \'' . PMA_Util::sqlAddSlashes($db) . '\''
+ . ' AND table_name = \''
+ . PMA_Util::sqlAddSlashes($arrvalue['name']) . '\''
+ . ' AND pdf_page_number = \''
+ . PMA_Util::sqlAddSlashes($this->chosenPage) . '\'';
+ $test_rs = PMA_queryAsControlUser(
+ $test_query, false, PMA_DatabaseInterface::QUERY_STORE
+ );
+ //echo $test_query;
+ if ($test_rs && $GLOBALS['dbi']->numRows($test_rs) > 0) {
+ if (isset($arrvalue['delete']) && $arrvalue['delete'] == 'y') {
+ $ch_query = 'DELETE FROM '
+ . PMA_Util::backquote($GLOBALS['cfgRelation']['db'])
+ . '.'
+ . PMA_Util::backquote($cfgRelation['table_coords'])
+ . ' WHERE db_name = \''
+ . PMA_Util::sqlAddSlashes($db) . '\''
+ . ' AND table_name = \''
+ . PMA_Util::sqlAddSlashes($arrvalue['name']) . '\''
+ . ' AND pdf_page_number = \''
+ . PMA_Util::sqlAddSlashes($this->chosenPage) . '\'';
+ } else {
+ $ch_query = 'UPDATE '
+ . PMA_Util::backquote($GLOBALS['cfgRelation']['db'])
+ . '.' . PMA_Util::backquote($cfgRelation['table_coords'])
+ . ' '
+ . 'SET x = ' . $arrvalue['x'] . ', y= ' . $arrvalue['y']
+ . ' WHERE db_name = \''
+ . PMA_Util::sqlAddSlashes($db) . '\''
+ . ' AND table_name = \''
+ . PMA_Util::sqlAddSlashes($arrvalue['name']) . '\''
+ . ' AND pdf_page_number = \''
+ . PMA_Util::sqlAddSlashes($this->chosenPage) . '\'';
+ }
+ } else {
+ $ch_query = 'INSERT INTO '
+ . PMA_Util::backquote($GLOBALS['cfgRelation']['db'])
+ . '.' . PMA_Util::backquote($cfgRelation['table_coords'])
+ . ' '
+ . '(db_name, table_name, pdf_page_number, x, y) '
+ . 'VALUES (\'' . PMA_Util::sqlAddSlashes($db) . '\', \''
+ . PMA_Util::sqlAddSlashes($arrvalue['name']) . '\', \''
+ . PMA_Util::sqlAddSlashes($this->chosenPage) . '\','
+ . $arrvalue['x'] . ',' . $arrvalue['y'] . ')';
+ }
+ //echo $ch_query;
+ PMA_queryAsControlUser($ch_query, false);
+ } // end if
+ } // end for
+ }
+}
+?>
diff --git a/libraries/select_lang.lib.php b/libraries/select_lang.lib.php
new file mode 100644
index 0000000000..da5835f378
--- /dev/null
+++ b/libraries/select_lang.lib.php
@@ -0,0 +1,571 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * phpMyAdmin Language Loading File
+ *
+ * @package PhpMyAdmin
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ * Returns language name
+ *
+ * @param string $tmplang Language code
+ *
+ * @return string
+ */
+function PMA_languageName($tmplang)
+{
+ $lang_name = ucfirst(substr(strrchr($tmplang[0], '|'), 1));
+
+ // Include native name if non empty
+ if (!empty($tmplang[2])) {
+ $lang_name = $tmplang[2] . ' - ' . $lang_name;
+ }
+
+ return $lang_name;
+}
+
+/**
+ * Tries to find the language to use
+ *
+ * @return bool success if valid lang is found, otherwise false
+ */
+function PMA_langCheck()
+{
+ // check forced language
+ if (! empty($GLOBALS['cfg']['Lang'])) {
+ if (PMA_langSet($GLOBALS['cfg']['Lang'])) {
+ return true;
+ } else {
+ $GLOBALS['lang_failed_cfg'] = $GLOBALS['cfg']['Lang'];
+ }
+ }
+
+ // Don't use REQUEST in following code as it might be confused by cookies
+ // with same name. Check user requested language (POST)
+ if (! empty($_POST['lang'])) {
+ if (PMA_langSet($_POST['lang'])) {
+ return true;
+ } elseif (!is_string($_POST['lang'])) {
+ /* Faked request, don't care on localisation */
+ $GLOBALS['lang_failed_request'] = 'Yes';
+ } else {
+ $GLOBALS['lang_failed_request'] = $_POST['lang'];
+ }
+ }
+
+ // check user requested language (GET)
+ if (! empty($_GET['lang'])) {
+ if (PMA_langSet($_GET['lang'])) {
+ return true;
+ } elseif (!is_string($_GET['lang'])) {
+ /* Faked request, don't care on localisation */
+ $GLOBALS['lang_failed_request'] = 'Yes';
+ } else {
+ $GLOBALS['lang_failed_request'] = $_GET['lang'];
+ }
+ }
+
+ // check previous set language
+ if (! empty($_COOKIE['pma_lang'])) {
+ if (PMA_langSet($_COOKIE['pma_lang'])) {
+ return true;
+ } elseif (!is_string($_COOKIE['pma_lang'])) {
+ /* Faked request, don't care on localisation */
+ $GLOBALS['lang_failed_cookie'] = 'Yes';
+ } else {
+ $GLOBALS['lang_failed_cookie'] = $_COOKIE['pma_lang'];
+ }
+ }
+
+ // try to find out user's language by checking its HTTP_ACCEPT_LANGUAGE variable;
+ // prevent XSS
+ $accepted_languages = PMA_getenv('HTTP_ACCEPT_LANGUAGE');
+ if ($accepted_languages && false === strpos($accepted_languages, '<')) {
+ foreach (explode(',', $accepted_languages) as $lang) {
+ if (PMA_langDetect($lang, 1)) {
+ return true;
+ }
+ }
+ }
+ unset($accepted_languages);
+
+ // try to find out user's language by checking its HTTP_USER_AGENT variable
+ if (PMA_langDetect(PMA_getenv('HTTP_USER_AGENT'), 2)) {
+ return true;
+ }
+
+ // Didn't catch any valid lang : we use the default settings
+ if (PMA_langSet($GLOBALS['cfg']['DefaultLang'])) {
+ return true;
+ }
+
+ return false;
+}
+
+/**
+ * checks given lang and sets it if valid
+ * returns true on success, otherwise false
+ *
+ * @param string &$lang language to set
+ *
+ * @return bool success
+ */
+function PMA_langSet(&$lang)
+{
+ /* Partial backward compatibility with 3.3 and older branches */
+ $lang = str_replace('-utf-8', '', $lang);
+
+ if (!is_string($lang)
+ || empty($lang)
+ || empty($GLOBALS['available_languages'][$lang])
+ ) {
+ return false;
+ }
+ $GLOBALS['lang'] = $lang;
+ return true;
+}
+
+/**
+ * Analyzes some PHP environment variables to find the most probable language
+ * that should be used
+ *
+ * @param string $str string to analyze
+ * @param integer $envType type of the PHP environment variable which value is $str
+ *
+ * @return bool true on success, otherwise false
+ *
+ * @access private
+ */
+function PMA_langDetect($str, $envType)
+{
+ if (empty($str)) {
+ return false;
+ }
+ if (empty($GLOBALS['available_languages'])) {
+ return false;
+ }
+
+ foreach ($GLOBALS['available_languages'] as $lang => $value) {
+ // $envType = 1 for the 'HTTP_ACCEPT_LANGUAGE' environment variable,
+ // 2 for the 'HTTP_USER_AGENT' one
+ $expr = $value[0];
+ if (strpos($expr, '[-_]') === false) {
+ $expr = str_replace('|', '([-_][[:alpha:]]{2,3})?|', $expr);
+ }
+ if (($envType == 1 && preg_match('/^(' . addcslashes($expr, '/') . ')(;q=[0-9]\\.[0-9])?$/i', $str))
+ || ($envType == 2 && preg_match('/(\(|\[|;[[:space:]])(' . addcslashes($expr, '/') . ')(;|\]|\))/i', $str))
+ ) {
+ if (PMA_langSet($lang)) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+} // end of the 'PMA_langDetect()' function
+
+
+/**
+ * All the supported languages have to be listed in the array below.
+ * 1. The key must be the "official" ISO 639 language code and, if required,
+ * the dialect code. It can also contain some information about the
+ * charset (see the Russian case).
+ * 2. The first of the values associated to the key is used in a regular
+ * expression to find some keywords corresponding to the language inside two
+ * environment variables.
+ * These values contain:
+ * - the "official" ISO language code and, if required, the dialect code
+ * too ('bu' for Bulgarian, 'fr([-_][[:alpha:]]{2})?' for all French
+ * dialects, 'zh[-_]tw' for Chinese traditional...), the dialect has to
+ * be specified first;
+ * - the '|' character (it means 'OR');
+ * - the full language name.
+ * 3. The second value associated to the key is the language code as defined by
+ * the RFC1766.
+ * 4. The third value is its native name in html entities or UTF-8.
+ *
+ * Beware that the sorting order (first values associated to keys by
+ * alphabetical reverse order in the array) is important: 'zh-tw' (chinese
+ * traditional) must be detected before 'zh' (chinese simplified) for
+ * example.
+ *
+ * @param string $lang language
+ *
+ * @return array
+ */
+function PMA_langDetails($lang)
+{
+ switch ($lang) {
+ case 'af':
+ return array('af|afrikaans', 'af', '');
+ case 'ar':
+ return array('ar|arabic', 'ar', '&#1575;&#1604;&#1593;&#1585;&#1576;&#1610;&#1577;');
+ case 'az':
+ return array('az|azerbaijani', 'az', 'Az&#601;rbaycanca');
+ case 'bn':
+ return array('bn|bangla', 'bn', 'বাংলা');
+ case 'be':
+ return array('be|belarusian', 'be', '&#1041;&#1077;&#1083;&#1072;&#1088;&#1091;&#1089;&#1082;&#1072;&#1103;');
+ case 'be@latin':
+ return array('be[-_]lat|belarusian latin', 'be-lat', 'Bie&#0322;aruskaja');
+ case 'bg':
+ return array('bg|bulgarian', 'bg', '&#1041;&#1098;&#1083;&#1075;&#1072;&#1088;&#1089;&#1082;&#1080;');
+ case 'bs':
+ return array('bs|bosnian', 'bs', 'Bosanski');
+ case 'br':
+ return array('br|breton', 'br', 'Brezhoneg');
+ case 'ca':
+ return array('ca|catalan', 'ca', 'Catal&agrave;');
+ case 'ckb':
+ return array('ckb', 'ckb', 'سۆرانی');
+ case 'cs':
+ return array('cs|czech', 'cs', 'Čeština');
+ case 'cy':
+ return array('cy|welsh', 'cy', 'Cymraeg');
+ case 'da':
+ return array('da|danish', 'da', 'Dansk');
+ case 'de':
+ return array('de|german', 'de', 'Deutsch');
+ case 'el':
+ return array('el|greek', 'el', '&Epsilon;&lambda;&lambda;&eta;&nu;&iota;&kappa;&#940;');
+ case 'en':
+ return array('en|english', 'en', '');
+ case 'en_GB':
+ return array('en[_-]gb|english (United Kingdom)', 'en-gb', '');
+ case 'es':
+ return array('es|spanish', 'es', 'Espa&ntilde;ol');
+ case 'et':
+ return array('et|estonian', 'et', 'Eesti');
+ case 'eu':
+ return array('eu|basque', 'eu', 'Euskara');
+ case 'fa':
+ return array('fa|persian', 'fa', '&#1601;&#1575;&#1585;&#1587;&#1740;');
+ case 'fi':
+ return array('fi|finnish', 'fi', 'Suomi');
+ case 'fr':
+ return array('fr|french', 'fr', 'Fran&ccedil;ais');
+ case 'gl':
+ return array('gl|galician', 'gl', 'Galego');
+ case 'he':
+ return array('he|hebrew', 'he', '&#1506;&#1489;&#1512;&#1497;&#1514;');
+ case 'hi':
+ return array('hi|hindi', 'hi', '&#2361;&#2367;&#2344;&#2381;&#2342;&#2368;');
+ case 'hr':
+ return array('hr|croatian', 'hr', 'Hrvatski');
+ case 'hu':
+ return array('hu|hungarian', 'hu', 'Magyar');
+ case 'hy':
+ return array('hy|armenian', 'hy', 'Õ€Õ¡ÕµÕ¥Ö€Õ§Õ¶');
+ case 'ia':
+ return array('ia|interlingua', 'ia', 'Interlingua');
+ case 'id':
+ return array('id|indonesian', 'id', 'Bahasa Indonesia');
+ case 'it':
+ return array('it|italian', 'it', 'Italiano');
+ case 'ja':
+ return array('ja|japanese', 'ja', '&#26085;&#26412;&#35486;');
+ case 'ko':
+ return array('ko|korean', 'ko', '&#54620;&#44397;&#50612;');
+ case 'ka':
+ return array('ka|georgian', 'ka', '&#4325;&#4304;&#4320;&#4311;&#4323;&#4314;&#4312;');
+ case 'kk':
+ return array('kk|kazakh', 'kk', 'Қазақ');
+ case 'kn':
+ return array('kn|kannada', 'kn', 'ಕನà³à²¨à²¡');
+ case 'ky':
+ return array('ky|kyrgyz', 'ky', 'Кыргызча');
+ case 'lt':
+ return array('lt|lithuanian', 'lt', 'Lietuvi&#371;');
+ case 'lv':
+ return array('lv|latvian', 'lv', 'Latvie&scaron;u');
+ case 'mk':
+ return array('mk|macedonian', 'mk', 'Macedonian');
+ case 'ml':
+ return array('ml|malayalam', 'ml', 'Malayalam');
+ case 'mn':
+ return array('mn|mongolian', 'mn', '&#1052;&#1086;&#1085;&#1075;&#1086;&#1083;');
+ case 'ms':
+ return array('ms|malay', 'ms', 'Bahasa Melayu');
+ case 'nl':
+ return array('nl|dutch', 'nl', 'Nederlands');
+ case 'nb':
+ return array('nb|norwegian', 'nb', 'Norsk');
+ case 'pa':
+ return array('pa|punjabi', 'pa', 'ਪੰਜਾਬੀ');
+ case 'pl':
+ return array('pl|polish', 'pl', 'Polski');
+ case 'pt_BR':
+ return array('pt[-_]br|brazilian portuguese', 'pt-BR', 'Portugu&ecirc;s');
+ case 'pt':
+ return array('pt|portuguese', 'pt', 'Portugu&ecirc;s');
+ case 'ro':
+ return array('ro|romanian', 'ro', 'Rom&acirc;n&#259;');
+ case 'ru':
+ return array('ru|russian', 'ru', '&#1056;&#1091;&#1089;&#1089;&#1082;&#1080;&#1081;');
+ case 'si':
+ return array('si|sinhala', 'si', '&#3523;&#3538;&#3458;&#3524;&#3517;');
+ case 'sk':
+ return array('sk|slovak', 'sk', 'Sloven&#269;ina');
+ case 'sl':
+ return array('sl|slovenian', 'sl', 'Sloven&scaron;&#269;ina');
+ case 'sq':
+ return array('sq|albanian', 'sq', 'Shqip');
+ case 'sr@latin':
+ return array('sr[-_]lat|serbian latin', 'sr-lat', 'Srpski');
+ case 'sr':
+ return array('sr|serbian', 'sr', '&#1057;&#1088;&#1087;&#1089;&#1082;&#1080;');
+ case 'sv':
+ return array('sv|swedish', 'sv', 'Svenska');
+ case 'ta':
+ return array('ta|tamil', 'ta', 'தமிழà¯');
+ case 'te':
+ return array('te|telugu', 'te', 'తెలà±à°—à±');
+ case 'th':
+ return array('th|thai', 'th', '&#3616;&#3634;&#3625;&#3634;&#3652;&#3607;&#3618;');
+ case 'tk':
+ return array('tk|turkmen', 'tk', 'türkmençe');
+ case 'tr':
+ return array('tr|turkish', 'tr', 'T&uuml;rk&ccedil;e');
+ case 'tt':
+ return array('tt|tatarish', 'tt', 'Tatar&ccedil;a');
+ case 'ug':
+ return array('ug|uyghur', 'ug', 'ئۇيغۇرچە');
+ case 'uk':
+ return array('uk|ukrainian', 'uk', '&#1059;&#1082;&#1088;&#1072;&#1111;&#1085;&#1089;&#1100;&#1082;&#1072;');
+ case 'ur':
+ return array('ur|urdu', 'ur', 'اÙردوÙ');
+ case 'uz@latin':
+ return array('uz[-_]lat|uzbek-latin', 'uz-lat', 'O&lsquo;zbekcha');
+ case 'uz':
+ return array('uz[-_]cyr|uzbek-cyrillic', 'uz-cyr', '&#1038;&#1079;&#1073;&#1077;&#1082;&#1095;&#1072;');
+ case 'vls':
+ return array('vls|flemish', 'vls', 'West-Vlams');
+ case 'zh_TW':
+ return array('zh[-_](tw|hk)|chinese traditional', 'zh-TW', '&#20013;&#25991;');
+ case 'zh_CN':
+ // only TW and HK use traditional Chinese while others (CN, SG, MY)
+ // use simplified Chinese
+ return array(
+ 'zh(?![-_](tw|hk))([-_][[:alpha:]]{2,3})?|chinese simplified',
+ 'zh',
+ '&#20013;&#25991;'
+ );
+ }
+ return array("$lang|$lang", $lang, $lang);
+}
+
+/**
+ * Returns list of languages supported by phpMyAdmin
+ *
+ * @return array
+ */
+function PMA_langList()
+{
+ /* We can always speak English */
+ $result = array('en' => PMA_langDetails('en'));
+
+ /* Check for existing directory */
+ if (!is_dir($GLOBALS['lang_path'])) {
+ return $result;
+ }
+
+ /* Open the directory */
+ $handle = @opendir($GLOBALS['lang_path']);
+ /* This can happen if the kit is English-only */
+ if ($handle === false) {
+ return $result;
+ }
+
+ /* Process all files */
+ while (false !== ($file = readdir($handle))) {
+ if ($file != "."
+ && $file != ".."
+ && file_exists($GLOBALS['lang_path'] . '/' . $file . '/LC_MESSAGES/phpmyadmin.mo')
+ ) {
+ $result[$file] = PMA_langDetails($file);
+ }
+ }
+ /* Close the handle */
+ closedir($handle);
+
+ return $result;
+}
+
+/**
+ * @global string path to the translations directory;
+ * may be absent if the kit is English-only
+ */
+$GLOBALS['lang_path'] = './locale/';
+
+/**
+ * Load gettext functions.
+ */
+require_once GETTEXT_INC;
+
+/**
+ * @global string interface language
+ */
+$GLOBALS['lang'] = 'en';
+/**
+ * @global boolean whether loading lang from cfg failed
+ */
+$GLOBALS['lang_failed_cfg'] = false;
+/**
+ * @global boolean whether loading lang from cookie failed
+ */
+$GLOBALS['lang_failed_cookie'] = false;
+/**
+ * @global boolean whether loading lang from user request failed
+ */
+$GLOBALS['lang_failed_request'] = false;
+/**
+ * @global string text direction ltr or rtl
+ */
+$GLOBALS['text_dir'] = 'ltr';
+
+/**
+ * @global array supported languages
+ */
+$GLOBALS['available_languages'] = PMA_langList();
+
+// Language filtering support
+if (! empty($GLOBALS['cfg']['FilterLanguages'])) {
+ $new_lang = array();
+ foreach ($GLOBALS['available_languages'] as $key => $val) {
+ if (preg_match('@' . $GLOBALS['cfg']['FilterLanguages'] . '@', $key)) {
+ $new_lang[$key] = $val;
+ }
+ }
+ if (count($new_lang) > 0) {
+ $GLOBALS['available_languages'] = $new_lang;
+ }
+ unset($key, $val, $new_lang);
+}
+
+/**
+ * @global array MySQL charsets map
+ */
+$GLOBALS['mysql_charset_map'] = array(
+ 'big5' => 'big5',
+ 'cp-866' => 'cp866',
+ 'euc-jp' => 'ujis',
+ 'euc-kr' => 'euckr',
+ 'gb2312' => 'gb2312',
+ 'gbk' => 'gbk',
+ 'iso-8859-1' => 'latin1',
+ 'iso-8859-2' => 'latin2',
+ 'iso-8859-7' => 'greek',
+ 'iso-8859-8' => 'hebrew',
+ 'iso-8859-8-i' => 'hebrew',
+ 'iso-8859-9' => 'latin5',
+ 'iso-8859-13' => 'latin7',
+ 'iso-8859-15' => 'latin1',
+ 'koi8-r' => 'koi8r',
+ 'shift_jis' => 'sjis',
+ 'tis-620' => 'tis620',
+ 'utf-8' => 'utf8',
+ 'windows-1250' => 'cp1250',
+ 'windows-1251' => 'cp1251',
+ 'windows-1252' => 'latin1',
+ 'windows-1256' => 'cp1256',
+ 'windows-1257' => 'cp1257',
+);
+
+/*
+ * Do the work!
+ */
+
+if (! PMA_langCheck()) {
+ // fallback language
+ $fall_back_lang = 'en';
+ $line = __LINE__;
+ if (! PMA_langSet($fall_back_lang)) {
+ trigger_error(
+ 'phpMyAdmin-ERROR: invalid lang code: '
+ . __FILE__ . '#' . $line . ', check hard coded fall back language.',
+ E_USER_WARNING
+ );
+ // stop execution
+ // and tell the user that his chosen language is invalid
+ PMA_fatalError(
+ 'Could not load any language, '
+ . 'please check your language settings and folder.'
+ );
+ }
+}
+
+// Set locale
+_setlocale(LC_MESSAGES, $GLOBALS['lang']);
+_bindtextdomain('phpmyadmin', $GLOBALS['lang_path']);
+_bind_textdomain_codeset('phpmyadmin', 'UTF-8');
+_textdomain('phpmyadmin');
+
+/**
+ * Messages for phpMyAdmin.
+ *
+ * These messages are here for easy transition to Gettext.
+ * You should not add any messages here, use instead gettext directly
+ * in your template/PHP file.
+ */
+
+if (! function_exists('__')) {
+ PMA_fatalError('Bad invocation!');
+}
+
+/* Text direction for language */
+if (in_array($GLOBALS['lang'], array('ar', 'fa', 'he', 'ur'))) {
+ $GLOBALS['text_dir'] = 'rtl';
+} else {
+ $GLOBALS['text_dir'] = 'ltr';
+}
+
+/* TCPDF */
+$GLOBALS['l'] = array();
+
+/* TCPDF settings */
+$GLOBALS['l']['a_meta_charset'] = 'UTF-8';
+$GLOBALS['l']['a_meta_dir'] = $GLOBALS['text_dir'];
+$GLOBALS['l']['a_meta_language'] = $GLOBALS['lang'];
+
+/* TCPDF translations */
+$GLOBALS['l']['w_page'] = __('Page number:');
+
+
+// now, that we have loaded the language strings we can send the errors
+if ($GLOBALS['lang_failed_cfg']) {
+ trigger_error(
+ sprintf(
+ __('Unknown language: %1$s.'),
+ htmlspecialchars($GLOBALS['lang_failed_cfg'])
+ ),
+ E_USER_ERROR
+ );
+}
+if ($GLOBALS['lang_failed_cookie']) {
+ trigger_error(
+ sprintf(
+ __('Unknown language: %1$s.'),
+ htmlspecialchars($GLOBALS['lang_failed_cookie'])
+ ),
+ E_USER_ERROR
+ );
+}
+if ($GLOBALS['lang_failed_request']) {
+ trigger_error(
+ sprintf(
+ __('Unknown language: %1$s.'),
+ htmlspecialchars($GLOBALS['lang_failed_request'])
+ ),
+ E_USER_ERROR
+ );
+}
+
+unset(
+ $line, $fall_back_lang, $GLOBALS['lang_failed_cfg'],
+ $GLOBALS['lang_failed_cookie'], $GLOBALS['lang_failed_request']
+);
+?>
diff --git a/libraries/select_server.lib.php b/libraries/select_server.lib.php
new file mode 100644
index 0000000000..7f72c7d110
--- /dev/null
+++ b/libraries/select_server.lib.php
@@ -0,0 +1,111 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Code for displaying server selection
+ *
+ * @package PhpMyAdmin
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ * Renders the server selection in list or selectbox form, or option tags only
+ *
+ * @param boolean $not_only_options whether to include form tags or not
+ * @param boolean $ommit_fieldset whether to ommit fieldset tag or not
+ *
+ * @return string
+ */
+function PMA_selectServer($not_only_options, $ommit_fieldset)
+{
+ $retval = '';
+
+ // Show as list?
+ if ($not_only_options) {
+ $list = $GLOBALS['cfg']['DisplayServersList'];
+ $not_only_options =! $list;
+ } else {
+ $list = false;
+ }
+
+ if ($not_only_options) {
+ $retval .= '<form method="post" action="'
+ . $GLOBALS['cfg']['DefaultTabServer'] . '" class="disableAjax">';
+ $retval .= PMA_URL_getHiddenInputs();
+
+ if (! $ommit_fieldset) {
+ $retval .= '<fieldset>';
+ }
+ $retval .= '<label for="select_server">'
+ . __('Current Server:') . '</label> ';
+
+ $retval .= '<select name="server" id="select_server" class="autosubmit">';
+ $retval .= '<option value="">(' . __('Servers') . ') ...</option>' . "\n";
+ } elseif ($list) {
+ $retval .= __('Current Server:') . '<br />';
+ $retval .= '<ul id="list_server">';
+ }
+
+ foreach ($GLOBALS['cfg']['Servers'] as $key => $server) {
+ if (empty($server['host'])) {
+ continue;
+ }
+
+ if (!empty($GLOBALS['server']) && (int) $GLOBALS['server'] === (int) $key) {
+ $selected = 1;
+ } else {
+ $selected = 0;
+ }
+ if (!empty($server['verbose'])) {
+ $label = $server['verbose'];
+ } else {
+ $label = $server['host'];
+ if (!empty($server['port'])) {
+ $label .= ':' . $server['port'];
+ }
+ }
+ if (! empty($server['only_db'])) {
+ if (! is_array($server['only_db'])) {
+ $label .= ' - ' . $server['only_db'];
+ // try to avoid displaying a too wide selector
+ } elseif (count($server['only_db']) < 4) {
+ $label .= ' - ' . implode(', ', $server['only_db']);
+ }
+ }
+ if (!empty($server['user']) && $server['auth_type'] == 'config') {
+ $label .= ' (' . $server['user'] . ')';
+ }
+
+ if ($list) {
+ $retval .= '<li>';
+ if ($selected) {
+ $retval .= '<strong>' . htmlspecialchars($label) . '</strong>';
+ } else {
+
+ $retval .= '<a class="disableAjax item" href="'
+ . $GLOBALS['cfg']['DefaultTabServer']
+ . PMA_URL_getCommon(array('server' => $key))
+ . '" >' . htmlspecialchars($label) . '</a>';
+ }
+ $retval .= '</li>';
+ } else {
+ $retval .= '<option value="' . $key . '" '
+ . ($selected ? ' selected="selected"' : '') . '>'
+ . htmlspecialchars($label) . '</option>' . "\n";
+ }
+ } // end while
+
+ if ($not_only_options) {
+ $retval .= '</select>';
+ if (! $ommit_fieldset) {
+ $retval .= '</fieldset>';
+ }
+ $retval .= '</form>';
+ } elseif ($list) {
+ $retval .= '</ul>';
+ }
+
+ return $retval;
+}
+?>
diff --git a/libraries/server_bin_log.lib.php b/libraries/server_bin_log.lib.php
new file mode 100644
index 0000000000..a33a72f4e7
--- /dev/null
+++ b/libraries/server_bin_log.lib.php
@@ -0,0 +1,245 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+
+/**
+ * functions for displaying server binary log
+ *
+ * @usedby server_binlog.php
+ *
+ * @package PhpMyAdmin
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ * Returns the html for log selector.
+ *
+ * @param Array $binary_log_file_names Binary logs file names
+ * @param Array $url_params links parameters
+ *
+ * @return string
+ */
+function PMA_getLogSelector($binary_log_file_names, $url_params)
+{
+ $html = "";
+ if (count($binary_log_file_names) > 1) {
+ $html .= '<form action="server_binlog.php" method="get">';
+ $html .= PMA_URL_getHiddenInputs($url_params);
+ $html .= '<fieldset><legend>';
+ $html .= __('Select binary log to view');
+ $html .= '</legend><select name="log">';
+ $full_size = 0;
+ foreach ($binary_log_file_names as $each_log) {
+ $html .= '<option value="' . $each_log['Log_name'] . '"';
+ if ($each_log['Log_name'] == $_REQUEST['log']) {
+ $html .= ' selected="selected"';
+ }
+ $html .= '>' . $each_log['Log_name'];
+ if (isset($each_log['File_size'])) {
+ $full_size += $each_log['File_size'];
+ $html .= ' ('
+ . implode(
+ ' ',
+ PMA_Util::formatByteDown(
+ $each_log['File_size'], 3, 2
+ )
+ )
+ . ')';
+ }
+ $html .= '</option>';
+ }
+ $html .= '</select> ';
+ $html .= count($binary_log_file_names) . ' ' . __('Files') . ', ';
+ if ($full_size > 0) {
+ $html .= implode(
+ ' ', PMA_Util::formatByteDown($full_size)
+ );
+ }
+ $html .= '</fieldset>';
+ $html .= '<fieldset class="tblFooters">';
+ $html .= '<input type="submit" value="' . __('Go') . '" />';
+ $html .= '</fieldset>';
+ $html .= '</form>';
+ }
+
+ return $html;
+}
+
+/**
+ * Returns the html for binary log information.
+ *
+ * @param Array $binary_log_file_names Binary logs file names
+ * @param Array $url_params links parameters
+ *
+ * @return string
+ */
+function PMA_getLogInfo($binary_log_file_names, $url_params)
+{
+ /**
+ * Need to find the real end of rows?
+ */
+ if (! isset($_REQUEST['pos'])) {
+ $pos = 0;
+ } else {
+ /* We need this to be a integer */
+ $pos = (int) $_REQUEST['pos'];
+ }
+
+ $sql_query = 'SHOW BINLOG EVENTS';
+ if (! empty($_REQUEST['log'])) {
+ $sql_query .= ' IN \'' . $_REQUEST['log'] . '\'';
+ }
+ $sql_query .= ' LIMIT ' . $pos . ', ' . (int) $GLOBALS['cfg']['MaxRows'];
+
+ /**
+ * Sends the query
+ */
+ $result = $GLOBALS['dbi']->query($sql_query);
+
+ /**
+ * prepare some vars for displaying the result table
+ */
+ // Gets the list of fields properties
+ if (isset($result) && $result) {
+ $num_rows = $GLOBALS['dbi']->numRows($result);
+ } else {
+ $num_rows = 0;
+ }
+
+ if (empty($_REQUEST['dontlimitchars'])) {
+ $dontlimitchars = false;
+ } else {
+ $dontlimitchars = true;
+ $url_params['dontlimitchars'] = 1;
+ }
+
+ //html output
+ $html = PMA_Util::getMessage(PMA_Message::success(), $sql_query);
+ $html .= '<table cellpadding="2" cellspacing="1" id="binlogTable">'
+ . '<thead>'
+ . '<tr>'
+ . '<td colspan="6" class="center">';
+
+ $html .= PMA_getNavigationRow($url_params, $pos, $num_rows, $dontlimitchars);
+
+ $html .= '</td>'
+ . '</tr>'
+ . '<tr>'
+ . '<th>' . __('Log name') . '</th>'
+ . '<th>' . __('Position') . '</th>'
+ . '<th>' . __('Event type') . '</th>'
+ . '<th>' . __('Server ID') . '</th>'
+ . '<th>' . __('Original position') . '</th>'
+ . '<th>' . __('Information') . '</th>'
+ . '</tr>'
+ . '</thead>'
+ . '<tbody>';
+
+ $html .= PMA_getAllLogItemInfo($result, $dontlimitchars);
+
+ $html .= '</tbody>'
+ . '</table>';
+
+ return $html;
+}
+
+/**
+ * Returns the html for Navigation Row.
+ *
+ * @param Array $url_params Links parameters
+ * @param int $pos Position to display
+ * @param int $num_rows Number of results row
+ * @param bool $dontlimitchars Whether limit chars
+ *
+ * @return string
+ */
+function PMA_getNavigationRow($url_params, $pos, $num_rows, $dontlimitchars)
+{
+ $html = "";
+ // we do not know how much rows are in the binlog
+ // so we can just force 'NEXT' button
+ if ($pos > 0) {
+ $this_url_params = $url_params;
+ if ($pos > $GLOBALS['cfg']['MaxRows']) {
+ $this_url_params['pos'] = $pos - $GLOBALS['cfg']['MaxRows'];
+ }
+
+ $html .= '<a href="server_binlog.php'
+ . PMA_URL_getCommon($this_url_params) . '"';
+ if (PMA_Util::showIcons('TableNavigationLinksMode')) {
+ $html .= ' title="' . _pgettext('Previous page', 'Previous') . '">';
+ } else {
+ $html .= '>' . _pgettext('Previous page', 'Previous');
+ } // end if... else...
+ $html .= ' &lt; </a> - ';
+ }
+
+ $this_url_params = $url_params;
+ if ($pos > 0) {
+ $this_url_params['pos'] = $pos;
+ }
+ if ($dontlimitchars) {
+ unset($this_url_params['dontlimitchars']);
+ $tempTitle = __('Truncate Shown Queries');
+ $tempImgMode = 'partial';
+ } else {
+ $this_url_params['dontlimitchars'] = 1;
+ $tempTitle = __('Show Full Queries');
+ $tempImgMode = 'full';
+ }
+ $html .= '<a href="server_binlog.php' . PMA_URL_getCommon($this_url_params)
+ . '" title="' . $tempTitle . '">'
+ . '<img src="' .$GLOBALS['pmaThemeImage'] . 's_' . $tempImgMode . 'text.png"'
+ . 'alt="' . $tempTitle . '" /></a>';
+
+ // we do not now how much rows are in the binlog
+ // so we can just force 'NEXT' button
+ if ($num_rows >= $GLOBALS['cfg']['MaxRows']) {
+ $this_url_params = $url_params;
+ $this_url_params['pos'] = $pos + $GLOBALS['cfg']['MaxRows'];
+ $html .= ' - <a href="server_binlog.php'
+ . PMA_URL_getCommon($this_url_params)
+ . '"';
+ if (PMA_Util::showIcons('TableNavigationLinksMode')) {
+ $html .= ' title="' . _pgettext('Next page', 'Next') . '">';
+ } else {
+ $html .= '>' . _pgettext('Next page', 'Next');
+ } // end if... else...
+ $html .= ' &gt; </a>';
+ }
+
+ return $html;
+}
+
+/**
+ * Returns the html for all binary log items.
+ *
+ * @param resource $result MySQL Query result
+ * @param bool $dontlimitchars Whether limit chars
+ *
+ * @return string
+ */
+function PMA_getAllLogItemInfo($result, $dontlimitchars)
+{
+ $html = "";
+ $odd_row = true;
+ while ($value = $GLOBALS['dbi']->fetchAssoc($result)) {
+ $html .= '<tr class="noclick ' . ($odd_row ? 'odd' : 'even') . '">'
+ . '<td>&nbsp;' . $value['Log_name'] . '&nbsp;</td>'
+ . '<td class="right">&nbsp;' . $value['Pos'] . '&nbsp;</td>'
+ . '<td>&nbsp;' . $value['Event_type'] . '&nbsp;</td>'
+ . '<td class="right">&nbsp;' . $value['Server_id'] . '&nbsp;</td>'
+ . '<td class="right">&nbsp;'
+ . (isset($value['Orig_log_pos'])
+ ? $value['Orig_log_pos'] : $value['End_log_pos'])
+ . '&nbsp;</td>'
+ . '<td>&nbsp;' . PMA_Util::formatSql($value['Info'], ! $dontlimitchars)
+ . '&nbsp;</td></tr>';
+
+ $odd_row = !$odd_row;
+ }
+ return $html;
+}
+
+?>
diff --git a/libraries/server_collations.lib.php b/libraries/server_collations.lib.php
new file mode 100644
index 0000000000..3c34d4ce5d
--- /dev/null
+++ b/libraries/server_collations.lib.php
@@ -0,0 +1,110 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+
+/**
+ * functions for displaying server Character Sets and Collations
+ *
+ * @usedby server_collations.php
+ *
+ * @package PhpMyAdmin
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ * Returns the html for server Character Sets and Collations.
+ *
+ * @param Array $mysql_charsets Mysql Charsets list
+ * @param Array $mysql_collations Mysql Collations list
+ * @param Array $mysql_charsets_descriptions Charsets descriptions
+ * @param Array $mysql_default_collations Default Collations list
+ * @param Array $mysql_collations_available Available Collations list
+ *
+ * @return string
+ */
+function PMA_getHtmlForCharsets($mysql_charsets, $mysql_collations,
+ $mysql_charsets_descriptions, $mysql_default_collations,
+ $mysql_collations_available
+) {
+ /**
+ * Outputs the result
+ */
+ $html = '<div id="div_mysql_charset_collations">' . "\n"
+ . '<table class="data noclick">' . "\n"
+ . '<tr><th>' . __('Collation') . '</th>' . "\n"
+ . ' <th>' . __('Description') . '</th>' . "\n"
+ . '</tr>' . "\n";
+
+ $i = 0;
+ $table_row_count = count($mysql_charsets) + count($mysql_collations);
+
+ foreach ($mysql_charsets as $current_charset) {
+ if ($i >= $table_row_count / 2) {
+ $i = 0;
+ $html .= '</table>' . "\n"
+ . '<table class="data noclick">' . "\n"
+ . '<tr><th>' . __('Collation') . '</th>' . "\n"
+ . ' <th>' . __('Description') . '</th>' . "\n"
+ . '</tr>' . "\n";
+ }
+ $i++;
+ $html .= '<tr><th colspan="2" class="right">' . "\n"
+ . ' ' . htmlspecialchars($current_charset) . "\n"
+ . (empty($mysql_charsets_descriptions[$current_charset])
+ ? ''
+ : ' (<i>' . htmlspecialchars(
+ $mysql_charsets_descriptions[$current_charset]
+ ) . '</i>)' . "\n")
+ . ' </th>' . "\n"
+ . '</tr>' . "\n";
+
+ $html .= PMA_getHtmlForCollationCurrentCharset(
+ $current_charset,
+ $mysql_collations,
+ $i,
+ $mysql_default_collations,
+ $mysql_collations_available
+ );
+ }
+ unset($table_row_count);
+ $html .= '</table>' . "\n"
+ . '</div>' . "\n";
+
+ return $html;
+}
+
+/**
+ * Returns the html for Collations of Current Charset.
+ *
+ * @param String $current_charset Current Charset
+ * @param Array $mysql_collations Collations list
+ * @param int &$i Display Index
+ * @param Array $mysql_default_collations Default Collations list
+ * @param Array $mysql_collations_available Available Collations list
+ *
+ * @return string
+ */
+function PMA_getHtmlForCollationCurrentCharset(
+ $current_charset, $mysql_collations, &$i,
+ $mysql_default_collations, $mysql_collations_available
+) {
+ $odd_row = true;
+ $html = '';
+ foreach ($mysql_collations[$current_charset] as $current_collation) {
+ $i++;
+ $html .= '<tr class="'
+ . ($odd_row ? 'odd' : 'even')
+ . ($mysql_default_collations[$current_charset] == $current_collation
+ ? ' marked'
+ : '')
+ . ($mysql_collations_available[$current_collation] ? '' : ' disabled')
+ . '">' . "\n"
+ . ' <td>' . htmlspecialchars($current_collation) . '</td>' . "\n"
+ . ' <td>' . PMA_getCollationDescr($current_collation) . '</td>' . "\n"
+ . '</tr>' . "\n";
+ $odd_row = !$odd_row;
+ }
+ return $html;
+}
+?>
diff --git a/libraries/server_common.inc.php b/libraries/server_common.inc.php
new file mode 100644
index 0000000000..19f3845321
--- /dev/null
+++ b/libraries/server_common.inc.php
@@ -0,0 +1,51 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Shared code for server pages
+ *
+ * @package PhpMyAdmin
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ * Handles some variables that may have been sent by the calling script
+ * Note: this can be called also from the db panel to get the privileges of
+ * a db, in which case we want to keep displaying the tabs of
+ * the Database panel
+ */
+if (empty($viewing_mode)) {
+ $db = $table = '';
+}
+
+/**
+ * Set parameters for links
+ */
+$url_query = PMA_URL_getCommon($db);
+
+/**
+ * Defines the urls to return to in case of error in a sql statement
+ */
+$err_url = 'index.php' . $url_query;
+
+/**
+ * @global boolean Checks for superuser privileges
+ */
+$is_superuser = $GLOBALS['dbi']->isSuperuser();
+
+// now, select the mysql db
+if ($is_superuser && ! PMA_DRIZZLE) {
+ $GLOBALS['dbi']->selectDb('mysql', $userlink);
+}
+
+PMA_Util::checkParameters(
+ array('is_superuser', 'url_query'), false
+);
+
+/**
+ * shared functions for server page
+ */
+require_once './libraries/server_common.lib.php';
+
+?>
diff --git a/libraries/server_common.lib.php b/libraries/server_common.lib.php
new file mode 100644
index 0000000000..16c43c93af
--- /dev/null
+++ b/libraries/server_common.lib.php
@@ -0,0 +1,67 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Shared code for server pages
+ *
+ * @package PhpMyAdmin
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ * Returns the html for the sub-page heading
+ *
+ * @param string $type Sub page type
+ * @param string $link Link to the official MySQL documentation
+ * @param bool $is_image Display image or icon, true: image, false: icon
+ *
+ * @return string
+ */
+function PMA_getHtmlForSubPageHeader($type, $link='', $is_image=true)
+{
+ //array contains Sub page icon and text
+ $header = array();
+
+ $header['variables']['image'] = 's_vars.png';
+ $header['variables']['text'] = __('Server variables and settings');
+
+ $header['engines']['image'] = 'b_engine.png';
+ $header['engines']['text'] = __('Storage Engines');
+
+ $header['plugins']['image'] = 'b_engine.png';
+ $header['plugins']['text'] = __('Plugins');
+
+ $header['binlog']['image'] = 's_tbl.png';
+ $header['binlog']['text'] = __('Binary log');
+
+ $header['collations']['image'] = 's_asci.png';
+ $header['collations']['text'] = __('Character Sets and Collations');
+
+ $header['replication']['image'] = 's_replication.png';
+ $header['replication']['text'] = __('Replication');
+
+ $header['database_statistics']['image'] = 's_db.png';
+ $header['database_statistics']['text'] = __('Databases statistics');
+
+ $header['databases']['image'] = 's_db.png';
+ $header['databases']['text'] = __('Databases');
+
+ $header['privileges']['image'] = 'b_usrlist.png';
+ $header['privileges']['text'] = __('Privileges');
+
+ if ($is_image) {
+ $html = '<h2>' . "\n"
+ . PMA_Util::getImage($header[$type]['image'])
+ . ' ' . $header[$type]['text'] . "\n"
+ . $link . '</h2>' . "\n";
+ } else {
+ $html = '<h2>' . "\n"
+ . PMA_Util::getIcon($header[$type]['image'])
+ . ' ' . $header[$type]['text'] . "\n"
+ . $link . '</h2>' . "\n";
+ }
+ return $html;
+}
+
+?>
diff --git a/libraries/server_databases.lib.php b/libraries/server_databases.lib.php
new file mode 100644
index 0000000000..15d439df3b
--- /dev/null
+++ b/libraries/server_databases.lib.php
@@ -0,0 +1,509 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+
+/**
+ * functions for displaying server databases
+ *
+ * @usedby server_databases.php
+ *
+ * @package PhpMyAdmin
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ * Returns the html for Database List
+ *
+ * @param Array $databases GBI return databases
+ * @param int $databases_count database count
+ * @param int $pos display pos
+ * @param Array $dbstats database status
+ * @param string $sort_by sort by string
+ * @param string $sort_order sort order string
+ * @param bool $is_superuser User status
+ * @param Array $cfg configuration
+ * @param string $replication_types replication types
+ * @param string $replication_info replication info
+ * @param string $url_query url query
+ *
+ * @return string
+ */
+function PMA_getHtmlForDatabase(
+ $databases, $databases_count, $pos, $dbstats,
+ $sort_by, $sort_order, $is_superuser, $cfg,
+ $replication_types, $replication_info, $url_query
+) {
+ $html = '<div id="tableslistcontainer">';
+ reset($databases);
+ $first_database = current($databases);
+ // table col order
+ $column_order = PMA_getColumnOrder();
+
+ $_url_params = array(
+ 'pos' => $pos,
+ 'dbstats' => $dbstats,
+ 'sort_by' => $sort_by,
+ 'sort_order' => $sort_order,
+ );
+
+ $html .= PMA_Util::getListNavigator(
+ $databases_count, $pos, $_url_params, 'server_databases.php',
+ 'frame_content', $GLOBALS['cfg']['MaxDbList']
+ );
+
+ $_url_params['pos'] = $pos;
+
+ $html .= '<form class="ajax" action="server_databases.php" ';
+ $html .= 'method="post" name="dbStatsForm" id="dbStatsForm">' . "\n";
+ $html .= PMA_URL_getHiddenInputs($_url_params);
+
+ $_url_params['sort_by'] = 'SCHEMA_NAME';
+ $_url_params['sort_order']
+ = ($sort_by == 'SCHEMA_NAME' && $sort_order == 'asc') ? 'desc' : 'asc';
+
+ $html .= '<table id="tabledatabases" class="data">' . "\n"
+ . '<thead>' . "\n"
+ . '<tr>' . "\n";
+
+ $html .= PMA_getHtmlForColumnOrderWithSort(
+ $is_superuser,
+ $cfg['AllowUserDropDatabase'],
+ $_url_params,
+ $sort_by,
+ $sort_order,
+ $column_order,
+ $first_database
+ );
+
+ $html .= PMA_getHtmlForReplicationType(
+ $is_superuser,
+ $replication_types,
+ $cfg['ActionLinksMode']
+ );
+
+ $html .= '</tr>' . "\n"
+ . '</thead>' . "\n";
+
+ list($output, $column_order) = PMA_getHtmlAndColumnOrderForDatabaseList(
+ $databases,
+ $is_superuser,
+ $url_query,
+ $column_order,
+ $replication_types,
+ $replication_info
+ );
+ $html .= $output;
+ unset($output);
+
+ $html .= PMA_getHtmlForTableFooter(
+ $cfg['AllowUserDropDatabase'],
+ $is_superuser,
+ $databases_count,
+ $column_order,
+ $replication_types,
+ $first_database
+ );
+
+ $html .= '</table>' . "\n";
+
+ $html .= PMA_getHtmlForTableFooterButtons(
+ $cfg['AllowUserDropDatabase'],
+ $is_superuser,
+ $sort_by,
+ $sort_order,
+ $dbstats
+ );
+
+ if (empty($dbstats)) {
+ //we should put notice above database list
+ $html = PMA_getHtmlForNoticeEnableStatistics($url_query, $html);
+ }
+ $html .= '</form>';
+ $html .= '</div>';
+
+ return $html;
+}
+
+/**
+ * Returns the html for Table footer buttons
+ *
+ * @param bool $is_allowUserDropDatabase Allow user drop database
+ * @param bool $is_superuser User status
+ * @param string $sort_by sort by string
+ * @param string $sort_order sort order string
+ * @param Array $dbstats database status
+ *
+ * @return string
+ */
+function PMA_getHtmlForTableFooterButtons(
+ $is_allowUserDropDatabase, $is_superuser,
+ $sort_by, $sort_order, $dbstats
+) {
+ $html = "";
+ if ($is_superuser || $is_allowUserDropDatabase) {
+ $html .= '<img class="selectallarrow" src="'
+ . $GLOBALS['pmaThemeImage'] . 'arrow_' . $GLOBALS['text_dir'] . '.png"'
+ . ' width="38" height="22" alt="' . __('With selected:') . '" />' . "\n"
+ . '<input type="checkbox" id="dbStatsForm_checkall" '
+ . 'class="checkall_box" title="' . __('Check All') . '" /> '
+ . '<label for="dbStatsForm_checkall">' . __('Check All') . '</label> '
+ . '<i style="margin-left: 2em">' . __('With selected:') . '</i>' . "\n";
+ $html .= PMA_Util::getButtonOrImage(
+ '',
+ 'mult_submit' . ' ajax',
+ 'drop_selected_dbs',
+ __('Drop'), 'b_deltbl.png'
+ );
+ }
+ return $html;
+}
+
+/**
+ * Returns the html for Table footer
+ *
+ * @param bool $is_allowUserDropDatabase Allow user drop database
+ * @param bool $is_superuser User status
+ * @param Array $databases_count Database count
+ * @param string $column_order column order
+ * @param array $replication_types replication types
+ * @param string $first_database First database
+ *
+ * @return string
+ */
+function PMA_getHtmlForTableFooter(
+ $is_allowUserDropDatabase, $is_superuser,
+ $databases_count, $column_order,
+ $replication_types, $first_database
+) {
+ $html = '<tfoot><tr>' . "\n";
+ if ($is_superuser || $is_allowUserDropDatabase) {
+ $html .= ' <th></th>' . "\n";
+ }
+ $html .= ' <th>' . __('Total') . ': <span id="databases_count">'
+ . $databases_count . '</span></th>' . "\n";
+
+ $html .= PMA_getHtmlForColumnOrder($column_order, $first_database);
+
+ foreach ($replication_types as $type) {
+ if ($GLOBALS["server_" . $type. "_status"]) {
+ $html .= ' <th></th>' . "\n";
+ }
+ }
+
+ if ($is_superuser) {
+ $html .= ' <th></th>' . "\n";
+ }
+ $html .= '</tr>' . "\n";
+ $html .= '</tfoot>' . "\n";
+ return $html;
+}
+
+/**
+ * Returns the html for Database List and Column order
+ *
+ * @param array $databases GBI return databases
+ * @param bool $is_superuser User status
+ * @param Array $url_query Url query
+ * @param string $column_order column order
+ * @param string $replication_types replication types
+ * @param string $replication_info replication info
+ *
+ * @return Array
+ */
+function PMA_getHtmlAndColumnOrderForDatabaseList(
+ $databases, $is_superuser, $url_query,
+ $column_order, $replication_types, $replication_info
+) {
+ $odd_row = true;
+ $html = '<tbody>' . "\n";
+
+ foreach ($databases as $current) {
+ $tr_class = $odd_row ? 'odd' : 'even';
+ if ($GLOBALS['dbi']->isSystemSchema($current['SCHEMA_NAME'], true)) {
+ $tr_class .= ' noclick';
+ }
+ $html .= '<tr class="' . $tr_class . '">' . "\n";
+ $odd_row = ! $odd_row;
+
+ list($column_order, $generated_html) = PMA_buildHtmlForDb(
+ $current,
+ $is_superuser,
+ $url_query,
+ $column_order,
+ $replication_types,
+ $replication_info
+ );
+
+ $html .= $generated_html;
+
+ $html .= '</tr>' . "\n";
+ } // end foreach ($databases as $key => $current)
+ unset($current, $odd_row);
+ $html .= '</tbody>';
+ return array($html, $column_order);
+}
+
+/**
+ * Returns the html for Column Order
+ *
+ * @param array $column_order Column order
+ * @param array $first_database The first display database
+ *
+ * @return string
+ */
+function PMA_getHtmlForColumnOrder($column_order, $first_database)
+{
+ $html = "";
+ foreach ($column_order as $stat_name => $stat) {
+ if (array_key_exists($stat_name, $first_database)) {
+ if ($stat['format'] === 'byte') {
+ list($value, $unit)
+ = PMA_Util::formatByteDown($stat['footer'], 3, 1);
+ } elseif ($stat['format'] === 'number') {
+ $value = PMA_Util::formatNumber($stat['footer'], 0);
+ } else {
+ $value = htmlentities($stat['footer'], 0);
+ }
+ $html .= ' <th class="value">';
+ if (isset($stat['description_function'])) {
+ $html .= '<dfn title="'
+ . $stat['description_function']($stat['footer']) . '">';
+ }
+ $html .= $value;
+ if (isset($stat['description_function'])) {
+ $html .= '</dfn>';
+ }
+ $html .= '</th>' . "\n";
+ if ($stat['format'] === 'byte') {
+ $html .= ' <th class="unit">' . $unit . '</th>' . "\n";
+ }
+ }
+ }
+
+ return $html;
+}
+
+
+/**
+ * Returns the html for Column Order with Sort
+ *
+ * @param bool $is_superuser User status
+ * @param bool $is_allowUserDropDatabase Allow user drop database
+ * @param Array $_url_params Url params
+ * @param string $sort_by sort colume name
+ * @param string $sort_order order
+ * @param array $column_order column order
+ * @param array $first_database database to show
+ *
+ * @return string
+ */
+function PMA_getHtmlForColumnOrderWithSort(
+ $is_superuser, $is_allowUserDropDatabase,
+ $_url_params, $sort_by, $sort_order,
+ $column_order, $first_database
+) {
+ $html = ($is_superuser || $is_allowUserDropDatabase
+ ? ' <th></th>' . "\n"
+ : '')
+ . ' <th><a href="server_databases.php'
+ . PMA_URL_getCommon($_url_params) . '">' . "\n"
+ . ' ' . __('Database') . "\n"
+ . ($sort_by == 'SCHEMA_NAME'
+ ? ' ' . PMA_Util::getImage(
+ 's_' . $sort_order . '.png',
+ ($sort_order == 'asc' ? __('Ascending') : __('Descending'))
+ ) . "\n"
+ : ''
+ )
+ . ' </a></th>' . "\n";
+ $table_columns = 3;
+ foreach ($column_order as $stat_name => $stat) {
+ if (array_key_exists($stat_name, $first_database)) {
+ if ($stat['format'] === 'byte') {
+ $table_columns += 2;
+ $colspan = ' colspan="2"';
+ } else {
+ $table_columns++;
+ $colspan = '';
+ }
+ $_url_params['sort_by'] = $stat_name;
+ $_url_params['sort_order']
+ = ($sort_by == $stat_name && $sort_order == 'desc') ? 'asc' : 'desc';
+ $html .= ' <th' . $colspan . '>'
+ . '<a href="server_databases.php'
+ . PMA_URL_getCommon($_url_params) . '">' . "\n"
+ . ' ' . $stat['disp_name'] . "\n"
+ . ($sort_by == $stat_name
+ ? ' ' . PMA_Util::getImage(
+ 's_' . $sort_order . '.png',
+ ($sort_order == 'asc' ? __('Ascending') : __('Descending'))
+ ) . "\n"
+ : ''
+ )
+ . ' </a></th>' . "\n";
+ }
+ }
+ return $html;
+}
+
+
+/**
+ * Returns the html for Enable Statistics
+ *
+ * @param bool $url_query Url query
+ * @param string $html html for database list
+ *
+ * @return string
+ */
+function PMA_getHtmlForNoticeEnableStatistics($url_query, $html)
+{
+ $notice = PMA_Message::notice(
+ __(
+ 'Note: Enabling the database statistics here might cause '
+ . 'heavy traffic between the web server and the MySQL server.'
+ )
+ )->getDisplay();
+ //we should put notice above database list
+ $html = $notice . $html;
+ $html .= '<ul><li id="li_switch_dbstats"><strong>' . "\n";
+ $html .= '<a href="server_databases.php?' . $url_query . '&amp;dbstats=1"'
+ . ' title="' . __('Enable Statistics') . '">' . "\n"
+ . ' ' . __('Enable Statistics');
+ $html .= '</a></strong><br />' . "\n";
+ $html .= '</li>' . "\n" . '</ul>' . "\n";
+
+ return $html;
+}
+
+/**
+ * Returns the html for database replication types
+ *
+ * @param bool $is_superuser User status
+ * @param Array $replication_types replication types
+ * @param bool $cfg_inconic cfg about Properties Iconic
+ *
+ * @return string
+ */
+function PMA_getHtmlForReplicationType(
+ $is_superuser, $replication_types, $cfg_inconic
+) {
+ $html = '';
+ foreach ($replication_types as $type) {
+ if ($type == "master") {
+ $name = __('Master replication');
+ } elseif ($type == "slave") {
+ $name = __('Slave replication');
+ }
+
+ if ($GLOBALS["server_{$type}_status"]) {
+ $html .= ' <th>'. $name .'</th>' . "\n";
+ }
+ }
+
+ if ($is_superuser && ! PMA_DRIZZLE) {
+ $html .= ' <th>' . ($cfg_inconic ? '' : __('Action')) . "\n"
+ . ' </th>' . "\n";
+ }
+ return $html;
+}
+
+/**
+ * Returns the array about $sort_order and $sort_by
+ *
+ * @return Array
+ */
+function PMA_getListForSortDatabase()
+{
+ /**
+ * avoids 'undefined index' errors
+ */
+ $sort_by = '';
+ $sort_order = '';
+ if (empty($_REQUEST['sort_by'])) {
+ $sort_by = 'SCHEMA_NAME';
+ } else {
+ $sort_by_whitelist = array(
+ 'SCHEMA_NAME',
+ 'DEFAULT_COLLATION_NAME',
+ 'SCHEMA_TABLES',
+ 'SCHEMA_TABLE_ROWS',
+ 'SCHEMA_DATA_LENGTH',
+ 'SCHEMA_INDEX_LENGTH',
+ 'SCHEMA_LENGTH',
+ 'SCHEMA_DATA_FREE'
+ );
+ if (in_array($_REQUEST['sort_by'], $sort_by_whitelist)) {
+ $sort_by = $_REQUEST['sort_by'];
+ } else {
+ $sort_by = 'SCHEMA_NAME';
+ }
+ }
+
+ if (isset($_REQUEST['sort_order'])
+ && strtolower($_REQUEST['sort_order']) == 'desc'
+ ) {
+ $sort_order = 'desc';
+ } else {
+ $sort_order = 'asc';
+ }
+
+ return array($sort_by, $sort_order);
+}
+
+/**
+ * Deal with Drops multiple databases
+ *
+ * @return null
+ */
+function PMA_dropMultiDatabases()
+{
+ if (! isset($_REQUEST['selected_dbs']) && ! isset($_REQUEST['query_type'])) {
+ $message = PMA_Message::error(__('No databases selected.'));
+ } else {
+ $action = 'server_databases.php';
+ $submit_mult = 'drop_db';
+ $err_url = 'server_databases.php?' . PMA_URL_getCommon();
+ if (isset($_REQUEST['selected_dbs'])
+ && !isset($_REQUEST['is_js_confirmed'])
+ ) {
+ $selected_db = $_REQUEST['selected_dbs'];
+ }
+ if (isset($_REQUEST['is_js_confirmed'])) {
+ $_REQUEST = array(
+ 'query_type' => $submit_mult,
+ 'selected' => $_REQUEST['selected_dbs'],
+ 'mult_btn' => __('Yes'),
+ 'db' => $GLOBALS['db'],
+ 'table' => $GLOBALS['table']);
+ }
+ //the following variables will be used on mult_submits.inc.php
+ global $query_type, $selected, $mult_btn;
+
+ include 'libraries/mult_submits.inc.php';
+ unset($action, $submit_mult, $err_url, $selected_db, $GLOBALS['db']);
+ if (empty($message)) {
+ if ($mult_btn == __('Yes')) {
+ $number_of_databases = count($selected);
+ } else {
+ $number_of_databases = 0;
+ }
+ $message = PMA_Message::success(
+ _ngettext(
+ '%1$d database has been dropped successfully.',
+ '%1$d databases have been dropped successfully.',
+ $number_of_databases
+ )
+ );
+ $message->addParam($number_of_databases);
+ }
+ }
+ if ($GLOBALS['is_ajax_request'] && $message instanceof PMA_Message) {
+ $response = PMA_Response::getInstance();
+ $response->isSuccess($message->isSuccess());
+ $response->addJSON('message', $message);
+ exit;
+ }
+}
+
+?>
diff --git a/libraries/server_engines.lib.php b/libraries/server_engines.lib.php
new file mode 100644
index 0000000000..f8c9710171
--- /dev/null
+++ b/libraries/server_engines.lib.php
@@ -0,0 +1,146 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+
+/**
+ * functions for displaying server engines
+ *
+ * @usedby server_engines.php
+ *
+ * @package PhpMyAdmin
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ * setup HTML for server Engines information
+ *
+ * @return string
+ */
+function PMA_getHtmlForServerEngines()
+{
+ /**
+ * Did the user request information about a certain storage engine?
+ */
+ $html = '';
+ if (empty($_REQUEST['engine'])
+ || ! PMA_StorageEngine::isValid($_REQUEST['engine'])
+ ) {
+ $html .= PMA_getHtmlForAllServerEngines();
+ } else {
+ $html .= PMA_getHtmlForSpecifiedServerEngines();
+ }
+
+ return $html;
+}
+
+/**
+ * setup HTML for server all Engines information
+ *
+ * @return string
+ */
+function PMA_getHtmlForAllServerEngines()
+{
+ /**
+ * Displays the table header
+ */
+ $html = '<table class="noclick">' . "\n"
+ . '<thead>' . "\n"
+ . '<tr><th>' . __('Storage Engine') . '</th>' . "\n"
+ . ' <th>' . __('Description') . '</th>' . "\n"
+ . '</tr>' . "\n"
+ . '</thead>' . "\n"
+ . '<tbody>' . "\n";
+
+
+ /**
+ * Listing the storage engines
+ */
+ $odd_row = true;
+ foreach (PMA_StorageEngine::getStorageEngines() as $engine => $details) {
+ $html .= '<tr class="'
+ . ($odd_row ? 'odd' : 'even')
+ . ($details['Support'] == 'NO' || $details['Support'] == 'DISABLED'
+ ? ' disabled' : '')
+ . '">' . "\n"
+ . ' <td><a rel="newpage" href="server_engines.php'
+ . PMA_URL_getCommon(array('engine' => $engine)) . '">' . "\n"
+ . ' ' . htmlspecialchars($details['Engine']) . "\n"
+ . ' </a></td>' . "\n"
+ . ' <td>' . htmlspecialchars($details['Comment']) . '</td>' . "\n"
+ . '</tr>' . "\n";
+ $odd_row = !$odd_row;
+ }
+
+ unset($odd_row, $engine, $details);
+ $html .= '</tbody>' . "\n"
+ . '</table>' . "\n";
+
+ return $html;
+}
+
+/**
+ * setup HTML for a given Storage Engine
+ *
+ * @return string
+ */
+function PMA_getHtmlForSpecifiedServerEngines()
+{
+ /**
+ * Displays details about a given Storage Engine
+ */
+ $html = '';
+ $engine_plugin = PMA_StorageEngine::getEngine($_REQUEST['engine']);
+ $html .= '<h2>' . "\n"
+ . PMA_Util::getImage('b_engine.png')
+ . ' ' . htmlspecialchars($engine_plugin->getTitle()) . "\n"
+ . ' ' . PMA_Util::showMySQLDocu($engine_plugin->getMysqlHelpPage())
+ . "\n" . '</h2>' . "\n\n";
+ $html .= '<p>' . "\n"
+ . ' <em>' . "\n"
+ . ' ' . htmlspecialchars($engine_plugin->getComment()) . "\n"
+ . ' </em>' . "\n"
+ . '</p>' . "\n\n";
+ $infoPages = $engine_plugin->getInfoPages();
+ if (! empty($infoPages) && is_array($infoPages)) {
+ $html .= '<p>' . "\n"
+ . ' <strong>[</strong>' . "\n";
+ if (empty($_REQUEST['page'])) {
+ $html .= ' <strong>' . __('Variables') . '</strong>' . "\n";
+ } else {
+ $html .= ' <a href="server_engines.php'
+ . PMA_URL_getCommon(array('engine' => $_REQUEST['engine']))
+ . '">' . __('Variables') . '</a>' . "\n";
+ }
+ foreach ($infoPages as $current => $label) {
+ $html .= ' <strong>|</strong>' . "\n";
+ if (isset($_REQUEST['page']) && $_REQUEST['page'] == $current) {
+ $html .= ' <strong>' . $label . '</strong>' . "\n";
+ } else {
+ $html .= ' <a href="server_engines.php'
+ . PMA_URL_getCommon(
+ array('engine' => $_REQUEST['engine'], 'page' => $current)
+ )
+ . '">' . htmlspecialchars($label) . '</a>' . "\n";
+ }
+ }
+ unset($current, $label);
+ $html .= ' <strong>]</strong>' . "\n"
+ . '</p>' . "\n\n";
+ }
+ unset($infoPages, $page_output);
+ if (! empty($_REQUEST['page'])) {
+ $page_output = $engine_plugin->getPage($_REQUEST['page']);
+ }
+ if (! empty($page_output)) {
+ $html .= $page_output;
+ } else {
+ $html .= '<p> ' . $engine_plugin->getSupportInformationMessage() . "\n"
+ . '</p>' . "\n"
+ . $engine_plugin->getHtmlVariables();
+ }
+
+ return $html;
+}
+
+?>
diff --git a/libraries/server_plugins.lib.php b/libraries/server_plugins.lib.php
new file mode 100644
index 0000000000..fb387b25f7
--- /dev/null
+++ b/libraries/server_plugins.lib.php
@@ -0,0 +1,205 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+
+/**
+ * functions for displaying server plugins
+ *
+ * @usedby server_plugins.php
+ *
+ * @package PhpMyAdmin
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ * Returns the html for plugin and module Info.
+ *
+ * @param Array $plugins Plugin list
+ *
+ * @param Array $modules Module list
+ *
+ * @return string
+ */
+function PMA_getPluginAndModuleInfo($plugins, $modules)
+{
+ $html = '<script type="text/javascript">';
+ $html .= 'pma_theme_image = "' . $GLOBALS['pmaThemeImage'] . '"';
+ $html .= '</script>';
+ $html .= '<div id="pluginsTabs">';
+ $html .= '<ul>';
+ $html .= '<li><a href="#plugins_plugins">' . __('Plugins') . '</a></li>';
+ $html .= '<li><a href="#plugins_modules">' . __('Modules') . '</a></li>';
+ $html .= '</ul>';
+ $html .= PMA_getPluginTab($plugins);
+ $html .= PMA_getModuleTab($modules);
+ $html .= '</div>';
+ return $html;
+}
+
+/**
+ * Returns the html for plugin Tab.
+ *
+ * @param Array $plugins list
+ *
+ * @return string
+ */
+function PMA_getPluginTab($plugins)
+{
+ $html = '<div id="plugins_plugins">';
+ $html .= '<div id="sectionlinks">';
+
+ foreach ($plugins as $plugin_type => $plugin_list) {
+ $key = 'plugins-' . preg_replace('/[^a-z]/', '', strtolower($plugin_type));
+ $html .= '<a href="#' . $key . '">'
+ . htmlspecialchars($plugin_type) . '</a>' . "\n";
+ }
+
+ $html .= '</div>';
+ $html .= '<br />';
+
+ foreach ($plugins as $plugin_type => $plugin_list) {
+ $key = 'plugins-' . preg_replace('/[^a-z]/', '', strtolower($plugin_type));
+ sort($plugin_list);
+
+ $html .= '<table class="data_full_width" id="' . $key . '">';
+ $html .= '<caption class="tblHeaders">';
+ $html .= '<a class="top" href="#serverinfo">';
+ $html .= __('Begin');
+ $html .= PMA_Util::getImage('s_asc.png');
+ $html .= '</a>';
+ $html .= htmlspecialchars($plugin_type);
+ $html .= '</caption>';
+ $html .= '<thead>';
+ $html .= '<tr>';
+ $html .= '<th>' . __('Plugin') . '</th>';
+ $html .= '<th>' . __('Module') . '</th>';
+ $html .= '<th>' . __('Library') . '</th>';
+ $html .= '<th>' . __('Version') . '</th>';
+ $html .= '<th>' . __('Author') . '</th>';
+ $html .= '<th>' . __('License') . '</th>';
+ $html .= '</tr>';
+ $html .= '</thead>';
+ $html .= '<tbody>';
+
+ $html .= PMA_getPluginList($plugin_list);
+
+ $html .= '</tbody>';
+ $html .= '</table>';
+ }
+ $html .= '</div>';
+ return $html;
+}
+
+/**
+ * Returns the html for plugin List.
+ *
+ * @param Array $plugin_list list
+ *
+ * @return string
+ */
+function PMA_getPluginList($plugin_list)
+{
+ $html = "";
+ $odd_row = false;
+ foreach ($plugin_list as $plugin) {
+ $odd_row = !$odd_row;
+ $html .= '<tr class="noclick ' . ($odd_row ? 'odd' : 'even') . '">';
+ $html .= '<th>' . htmlspecialchars($plugin['plugin_name']) . '</th>';
+ $html .= '<td>' . htmlspecialchars($plugin['module_name']) . '</td>';
+ $html .= '<td>' . htmlspecialchars($plugin['module_library']) . '</td>';
+ $html .= '<td>' . htmlspecialchars($plugin['module_version']) . '</td>';
+ $html .= '<td>' . htmlspecialchars($plugin['module_author']) . '</td>';
+ $html .= '<td>' . htmlspecialchars($plugin['module_license']) . '</td>';
+ $html .= '</tr>';
+ }
+ return $html;
+}
+
+/**
+ * Returns the html for Module Tab.
+ *
+ * @param Array $modules list
+ *
+ * @return string
+ */
+function PMA_getModuleTab($modules)
+{
+ $html = '<div id="plugins_modules">';
+ $html .= '<table class="data_full_width">';
+ $html .= '<thead>';
+ $html .= '<tr>';
+ $html .= '<th>' . __('Module') . '</th>';
+ $html .= '<th>' . __('Description') . '</th>';
+ $html .= '<th>' . __('Library') . '</th>';
+ $html .= '<th>' . __('Version') . '</th>';
+ $html .= '<th>' . __('Author') . '</th>';
+ $html .= '<th>' . __('License') . '</th>';
+ $html .= '</tr>';
+ $html .= '</thead>';
+ $html .= '<tbody>';
+
+ $html .= PMA_getModuleList($modules);
+ $html .= '</tbody>';
+ $html .= '</table>';
+ $html .= '</div>';
+ return $html;
+}
+
+/**
+ * Returns the html for module List.
+ *
+ * @param Array $modules list
+ *
+ * @return string
+ */
+function PMA_getModuleList($modules)
+{
+ $html = "";
+ $odd_row = false;
+ foreach ($modules as $module_name => $module) {
+ $odd_row = !$odd_row;
+ $html .= '<tr class="noclick ' . ($odd_row ? 'odd' : 'even') . '">';
+ $html .= '<th rowspan="2">' . htmlspecialchars($module_name) . '</th>';
+ $html .= '<td>' . htmlspecialchars($module['info']['module_description'])
+ . '</td>';
+ $html .= '<td>' . htmlspecialchars($module['info']['module_library'])
+ . '</td>';
+ $html .= '<td>' . htmlspecialchars($module['info']['module_version'])
+ . '</td>';
+ $html .= '<td>' . htmlspecialchars($module['info']['module_author'])
+ . '</td>';
+ $html .= '<td>' . htmlspecialchars($module['info']['module_license'])
+ . '</td>';
+ $html .= '</tr>';
+ $html .= '<tr class="noclick ' . ($odd_row ? 'odd' : 'even') . '">';
+ $html .= '<td colspan="5">';
+ $html .= '<table>';
+ $html .= '<tbody>';
+
+ foreach ($module['plugins'] as $plugin_type => $plugin_list) {
+ $html .= '<tr class="noclick">';
+ $html .= '<td><b class="plugin-type">'
+ . htmlspecialchars($plugin_type) . '</b></td>';
+ $html .= '<td>';
+ for ($i = 0; $i < count($plugin_list); $i++) {
+ $html .= ($i != 0 ? '<br />' : '')
+ . htmlspecialchars($plugin_list[$i]['plugin_name']);
+ if (!$plugin_list[$i]['is_active']) {
+ $html .= ' <small class="attention">' . __('disabled')
+ . '</small>';
+ }
+ }
+ $html .= '</td>';
+ $html .= '</tr>';
+ }
+
+ $html .= '</tbody>';
+ $html .= '</table>';
+ $html .= '</td>';
+ $html .= '</tr>';
+ }
+ return $html;
+}
+
+?>
diff --git a/libraries/server_privileges.lib.php b/libraries/server_privileges.lib.php
new file mode 100644
index 0000000000..aa72a282fc
--- /dev/null
+++ b/libraries/server_privileges.lib.php
@@ -0,0 +1,4235 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * set of functions with the Privileges section in pma
+ *
+ * @package PhpMyAdmin
+ */
+
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ * Get Html for User Group Dialog
+ *
+ * @param string $username username
+ * @param bool $is_menuswork Is menuswork set in configuration
+ *
+ * @return string html
+ */
+function PMA_getHtmlForUserGroupDialog($username, $is_menuswork)
+{
+ $html = '';
+ if (! empty($_REQUEST['edit_user_group_dialog']) && $is_menuswork) {
+ $dialog = PMA_getHtmlToChooseUserGroup($username);
+ $response = PMA_Response::getInstance();
+ if ($GLOBALS['is_ajax_request']) {
+ $response->addJSON('message', $dialog);
+ exit;
+ } else {
+ $html .= $dialog;
+ }
+ }
+
+ return $html;
+}
+
+/**
+ * Escapes wildcard in a database+table specification
+ * before using it in a GRANT statement.
+ *
+ * Escaping a wildcard character in a GRANT is only accepted at the global
+ * or database level, not at table level; this is why I remove
+ * the escaping character. Internally, in mysql.tables_priv.Db there are
+ * no escaping (for example test_db) but in mysql.db you'll see test\_db
+ * for a db-specific privilege.
+ *
+ * @param string $dbname Database name
+ * @param string $tablename Table name
+ *
+ * @return string the escaped (if necessary) database.table
+ */
+function PMA_wildcardEscapeForGrant($dbname, $tablename)
+{
+ if (! strlen($dbname)) {
+ $db_and_table = '*.*';
+ } else {
+ if (strlen($tablename)) {
+ $db_and_table = PMA_Util::backquote(
+ PMA_Util::unescapeMysqlWildcards($dbname)
+ )
+ . '.' . PMA_Util::backquote($tablename);
+ } else {
+ $db_and_table = PMA_Util::backquote($dbname) . '.*';
+ }
+ }
+ return $db_and_table;
+}
+
+/**
+ * Generates a condition on the user name
+ *
+ * @param string $initial the user's initial
+ *
+ * @return string the generated condition
+ */
+function PMA_rangeOfUsers($initial = '')
+{
+ // strtolower() is used because the User field
+ // might be BINARY, so LIKE would be case sensitive
+ if (! empty($initial)) {
+ $ret = " WHERE `User` LIKE '"
+ . PMA_Util::sqlAddSlashes($initial, true) . "%'"
+ . " OR `User` LIKE '"
+ . PMA_Util::sqlAddSlashes(strtolower($initial), true) . "%'";
+ } else {
+ $ret = '';
+ }
+ return $ret;
+} // end function
+
+/**
+ * Extracts the privilege information of a priv table row
+ *
+ * @param array $row the row
+ * @param boolean $enableHTML add <dfn> tag with tooltips
+ * @param boolean $tablePrivs whether row contains table privileges
+ *
+ * @global resource $user_link the database connection
+ *
+ * @return array
+ */
+function PMA_extractPrivInfo($row = '', $enableHTML = false, $tablePrivs = false)
+{
+ if ($tablePrivs) {
+ $grants = PMA_getTableGrantsArray();
+ } else {
+ $grants = PMA_getGrantsArray();
+ }
+
+ if (! empty($row) && isset($row['Table_priv'])) {
+ $row1 = $GLOBALS['dbi']->fetchSingleRow(
+ 'SHOW COLUMNS FROM `mysql`.`tables_priv` LIKE \'Table_priv\';',
+ 'ASSOC', $GLOBALS['userlink']
+ );
+ $av_grants = explode(
+ '\',\'',
+ substr($row1['Type'], 5, strlen($row1['Type']) - 7)
+ );
+ unset($row1);
+ $users_grants = explode(',', $row['Table_priv']);
+ foreach ($av_grants as $current_grant) {
+ $row[$current_grant . '_priv']
+ = in_array($current_grant, $users_grants) ? 'Y' : 'N';
+ }
+ unset($current_grant);
+ }
+
+ $privs = array();
+ $allPrivileges = true;
+ foreach ($grants as $current_grant) {
+ if ((! empty($row) && isset($row[$current_grant[0]]))
+ || (empty($row) && isset($GLOBALS[$current_grant[0]]))
+ ) {
+ if ((! empty($row) && $row[$current_grant[0]] == 'Y')
+ || (empty($row)
+ && ($GLOBALS[$current_grant[0]] == 'Y'
+ || (is_array($GLOBALS[$current_grant[0]])
+ && count($GLOBALS[$current_grant[0]]) == $_REQUEST['column_count']
+ && empty($GLOBALS[$current_grant[0] . '_none']))))
+ ) {
+ if ($enableHTML) {
+ $privs[] = '<dfn title="' . $current_grant[2] . '">'
+ . $current_grant[1] . '</dfn>';
+ } else {
+ $privs[] = $current_grant[1];
+ }
+ } elseif (! empty($GLOBALS[$current_grant[0]])
+ && is_array($GLOBALS[$current_grant[0]])
+ && empty($GLOBALS[$current_grant[0] . '_none'])
+ ) {
+ if ($enableHTML) {
+ $priv_string = '<dfn title="' . $current_grant[2] . '">'
+ . $current_grant[1] . '</dfn>';
+ } else {
+ $priv_string = $current_grant[1];
+ }
+ $privs[] = $priv_string . ' (`'
+ . join('`, `', $GLOBALS[$current_grant[0]]) . '`)';
+ } else {
+ $allPrivileges = false;
+ }
+ }
+ }
+ if (empty($privs)) {
+ if ($enableHTML) {
+ $privs[] = '<dfn title="' . __('No privileges.') . '">USAGE</dfn>';
+ } else {
+ $privs[] = 'USAGE';
+ }
+ } elseif ($allPrivileges
+ && (! isset($_POST['grant_count']) || count($privs) == $_POST['grant_count'])
+ ) {
+ if ($enableHTML) {
+ $privs = array('<dfn title="'
+ . __('Includes all privileges except GRANT.')
+ . '">ALL PRIVILEGES</dfn>'
+ );
+ } else {
+ $privs = array('ALL PRIVILEGES');
+ }
+ }
+ return $privs;
+} // end of the 'PMA_extractPrivInfo()' function
+
+/**
+ * Returns an array of table grants and their descriptions
+ *
+ * @return array array of table grants
+ */
+function PMA_getTableGrantsArray()
+{
+ return array(
+ array(
+ 'Delete',
+ 'DELETE',
+ $GLOBALS['strPrivDescDelete']
+ ),
+ array(
+ 'Create',
+ 'CREATE',
+ $GLOBALS['strPrivDescCreateTbl']
+ ),
+ array(
+ 'Drop',
+ 'DROP',
+ $GLOBALS['strPrivDescDropTbl']
+ ),
+ array(
+ 'Index',
+ 'INDEX',
+ $GLOBALS['strPrivDescIndex']
+ ),
+ array(
+ 'Alter',
+ 'ALTER',
+ $GLOBALS['strPrivDescAlter']
+ ),
+ array(
+ 'Create View',
+ 'CREATE_VIEW',
+ $GLOBALS['strPrivDescCreateView']
+ ),
+ array(
+ 'Show view',
+ 'SHOW_VIEW',
+ $GLOBALS['strPrivDescShowView']
+ ),
+ array(
+ 'Trigger',
+ 'TRIGGER',
+ $GLOBALS['strPrivDescTrigger']
+ ),
+ );
+}
+
+/**
+ * Get the grants array which contains all the privilege types
+ * and relevent grant messages
+ *
+ * @return array
+ */
+function PMA_getGrantsArray()
+{
+ return array(
+ array(
+ 'Select_priv',
+ 'SELECT',
+ __('Allows reading data.')
+ ),
+ array(
+ 'Insert_priv',
+ 'INSERT',
+ __('Allows inserting and replacing data.')
+ ),
+ array(
+ 'Update_priv',
+ 'UPDATE',
+ __('Allows changing data.')
+ ),
+ array(
+ 'Delete_priv',
+ 'DELETE',
+ __('Allows deleting data.')
+ ),
+ array(
+ 'Create_priv',
+ 'CREATE',
+ __('Allows creating new databases and tables.')
+ ),
+ array(
+ 'Drop_priv',
+ 'DROP',
+ __('Allows dropping databases and tables.')
+ ),
+ array(
+ 'Reload_priv',
+ 'RELOAD',
+ __('Allows reloading server settings and flushing the server\'s caches.')
+ ),
+ array(
+ 'Shutdown_priv',
+ 'SHUTDOWN',
+ __('Allows shutting down the server.')
+ ),
+ array(
+ 'Process_priv',
+ 'PROCESS',
+ __('Allows viewing processes of all users')
+ ),
+ array(
+ 'File_priv',
+ 'FILE',
+ __('Allows importing data from and exporting data into files.')
+ ),
+ array(
+ 'References_priv',
+ 'REFERENCES',
+ __('Has no effect in this MySQL version.')
+ ),
+ array(
+ 'Index_priv',
+ 'INDEX',
+ __('Allows creating and dropping indexes.')
+ ),
+ array(
+ 'Alter_priv',
+ 'ALTER',
+ __('Allows altering the structure of existing tables.')
+ ),
+ array(
+ 'Show_db_priv',
+ 'SHOW DATABASES',
+ __('Gives access to the complete list of databases.')
+ ),
+ array(
+ 'Super_priv',
+ 'SUPER',
+ __(
+ 'Allows connecting, even if maximum number of connections '
+ . 'is reached; required for most administrative operations '
+ . 'like setting global variables or killing threads of other users.'
+ )
+ ),
+ array(
+ 'Create_tmp_table_priv',
+ 'CREATE TEMPORARY TABLES',
+ __('Allows creating temporary tables.')
+ ),
+ array(
+ 'Lock_tables_priv',
+ 'LOCK TABLES',
+ __('Allows locking tables for the current thread.')
+ ),
+ array(
+ 'Repl_slave_priv',
+ 'REPLICATION SLAVE',
+ __('Needed for the replication slaves.')
+ ),
+ array(
+ 'Repl_client_priv',
+ 'REPLICATION CLIENT',
+ __('Allows the user to ask where the slaves / masters are.')
+ ),
+ array(
+ 'Create_view_priv',
+ 'CREATE VIEW',
+ __('Allows creating new views.')
+ ),
+ array(
+ 'Event_priv',
+ 'EVENT',
+ __('Allows to set up events for the event scheduler')
+ ),
+ array(
+ 'Trigger_priv',
+ 'TRIGGER',
+ __('Allows creating and dropping triggers')
+ ),
+ // for table privs:
+ array(
+ 'Create View_priv',
+ 'CREATE VIEW',
+ __('Allows creating new views.')
+ ),
+ array(
+ 'Show_view_priv',
+ 'SHOW VIEW',
+ __('Allows performing SHOW CREATE VIEW queries.')
+ ),
+ // for table privs:
+ array(
+ 'Show view_priv',
+ 'SHOW VIEW',
+ __('Allows performing SHOW CREATE VIEW queries.')
+ ),
+ array(
+ 'Create_routine_priv',
+ 'CREATE ROUTINE',
+ __('Allows creating stored routines.')
+ ),
+ array(
+ 'Alter_routine_priv',
+ 'ALTER ROUTINE',
+ __('Allows altering and dropping stored routines.')
+ ),
+ array(
+ 'Create_user_priv',
+ 'CREATE USER',
+ __('Allows creating, dropping and renaming user accounts.')
+ ),
+ array(
+ 'Execute_priv',
+ 'EXECUTE',
+ __('Allows executing stored routines.')
+ ),
+ );
+}
+
+/**
+ * Displays on which column(s) a table-specific privilege is granted
+ *
+ * @param array $columns columns array
+ * @param array $row first row from result or boolean false
+ * @param string $name_for_select privilege types - Select_priv, Insert_priv
+ * Update_priv, References_priv
+ * @param string $priv_for_header privilege for header
+ * @param string $name privilege name: insert, select, update, references
+ * @param string $name_for_dfn name for dfn
+ * @param string $name_for_current name for current
+ *
+ * @return string $html_output html snippet
+ */
+function PMA_getHtmlForDisplayColumnPrivileges($columns, $row, $name_for_select,
+ $priv_for_header, $name, $name_for_dfn, $name_for_current
+) {
+ $html_output = '<div class="item" id="div_item_' . $name . '">' . "\n"
+ . '<label for="select_' . $name . '_priv">' . "\n"
+ . '<code><dfn title="' . $name_for_dfn . '">'
+ . $priv_for_header . '</dfn></code>' . "\n"
+ . '</label><br />' . "\n"
+ . '<select id="select_' . $name . '_priv" name="'
+ . $name_for_select . '[]" multiple="multiple" size="8">' . "\n";
+
+ foreach ($columns as $current_column => $current_column_privileges) {
+ $html_output .= '<option '
+ . 'value="' . htmlspecialchars($current_column) . '"';
+ if ($row[$name_for_select] == 'Y'
+ || $current_column_privileges[$name_for_current]
+ ) {
+ $html_output .= ' selected="selected"';
+ }
+ $html_output .= '>'
+ . htmlspecialchars($current_column) . '</option>' . "\n";
+ }
+
+ $html_output .= '</select>' . "\n"
+ . '<i>' . __('Or') . '</i>' . "\n"
+ . '<label for="checkbox_' . $name_for_select
+ . '_none"><input type="checkbox"'
+ . ' name="' . $name_for_select . '_none" id="checkbox_'
+ . $name_for_select . '_none" title="'
+ . _pgettext('None privileges', 'None') . '" />'
+ . _pgettext('None privileges', 'None') . '</label>' . "\n"
+ . '</div>' . "\n";
+ return $html_output;
+} // end function
+
+/**
+ * Get sql query for display privileges table
+ *
+ * @param string $db the database
+ * @param string $table the table
+ * @param string $username username for database connection
+ * @param string $hostname hostname for database connection
+ *
+ * @return string sql query
+ */
+function PMA_getSqlQueryForDisplayPrivTable($db, $table, $username, $hostname)
+{
+ if ($db == '*') {
+ return "SELECT * FROM `mysql`.`user`"
+ ." WHERE `User` = '" . PMA_Util::sqlAddSlashes($username) . "'"
+ ." AND `Host` = '" . PMA_Util::sqlAddSlashes($hostname) . "';";
+ } elseif ($table == '*') {
+ return "SELECT * FROM `mysql`.`db`"
+ ." WHERE `User` = '" . PMA_Util::sqlAddSlashes($username) . "'"
+ ." AND `Host` = '" . PMA_Util::sqlAddSlashes($hostname) . "'"
+ ." AND '" . PMA_Util::unescapeMysqlWildcards($db) . "'"
+ ." LIKE `Db`;";
+ }
+ return "SELECT `Table_priv`"
+ ." FROM `mysql`.`tables_priv`"
+ ." WHERE `User` = '" . PMA_Util::sqlAddSlashes($username) . "'"
+ ." AND `Host` = '" . PMA_Util::sqlAddSlashes($hostname) . "'"
+ ." AND `Db` = '" . PMA_Util::unescapeMysqlWildcards($db) . "'"
+ ." AND `Table_name` = '" . PMA_Util::sqlAddSlashes($table) . "';";
+}
+
+/**
+ * Displays a dropdown to select the user group
+ * with menu items configured to each of them.
+ *
+ * @param string $username username
+ *
+ * @return string html to select the user group
+ */
+function PMA_getHtmlToChooseUserGroup($username)
+{
+ $html_output = '<form class="ajax" id="changeUserGroupForm"'
+ . ' action="server_privileges.php" method="post">';
+ $params = array('username' => $username);
+ $html_output .= PMA_URL_getHiddenInputs($params);
+ $html_output .= '<fieldset id="fieldset_user_group_selection">';
+ $html_output .= '<legend>' . __('User group') . '</legend>';
+
+ $groupTable = PMA_Util::backquote($GLOBALS['cfg']['Server']['pmadb'])
+ . "." . PMA_Util::backquote($GLOBALS['cfg']['Server']['usergroups']);
+ $userTable = PMA_Util::backquote($GLOBALS['cfg']['Server']['pmadb'])
+ . "." . PMA_Util::backquote($GLOBALS['cfg']['Server']['users']);
+
+ $userGroups = array();
+ $sql_query = "SELECT DISTINCT `usergroup` FROM " . $groupTable;
+ $result = PMA_queryAsControlUser($sql_query, false);
+ if ($result) {
+ while ($row = $GLOBALS['dbi']->fetchRow($result)) {
+ $userGroups[] = $row[0];
+ }
+ }
+ $GLOBALS['dbi']->freeResult($result);
+
+ $userGroup = '';
+ if (isset($GLOBALS['username'])) {
+ $sql_query = "SELECT `usergroup` FROM " . $userTable
+ . " WHERE `username` = '" . PMA_Util::sqlAddSlashes($username) . "'";
+ $userGroup = $GLOBALS['dbi']->fetchValue(
+ $sql_query, 0, 0, $GLOBALS['controllink']
+ );
+ }
+
+ $html_output .= __('User group') . ': ';
+ $html_output .= '<select name="userGroup">';
+ $html_output .= '<option value=""></option>';
+ foreach ($userGroups as $oneUserGroup) {
+ $html_output .= '<option value="' . htmlspecialchars($oneUserGroup) . '"'
+ . ($oneUserGroup == $userGroup ? ' selected="selected"' : '')
+ . '>'
+ . htmlspecialchars($oneUserGroup)
+ . '</option>';
+ }
+ $html_output .= '</select>';
+ $html_output .= '<input type="hidden" name="changeUserGroup" value="1">';
+ $html_output .= '</fieldset>';
+ $html_output .= '</form>';
+ return $html_output;
+}
+
+/**
+ * Sets the user group from request values
+ *
+ * @param string $username username
+ * @param string $userGroup user group to set
+ *
+ * @return void
+ */
+function PMA_setUserGroup($username, $userGroup)
+{
+ $userTable = PMA_Util::backquote($GLOBALS['cfg']['Server']['pmadb'])
+ . "." . PMA_Util::backquote($GLOBALS['cfg']['Server']['users']);
+
+ $sql_query = "SELECT `usergroup` FROM " . $userTable
+ . " WHERE `username` = '" . PMA_Util::sqlAddSlashes($username) . "'";
+ $oldUserGroup = $GLOBALS['dbi']->fetchValue(
+ $sql_query, 0, 0, $GLOBALS['controllink']
+ );
+
+ if ($oldUserGroup === false) {
+ $upd_query = "INSERT INTO " . $userTable . "(`username`, `usergroup`)"
+ . " VALUES ('" . PMA_Util::sqlAddSlashes($username) . "', "
+ . "'" . PMA_Util::sqlAddSlashes($userGroup) . "')";
+ } else {
+ if (empty($userGroup)) {
+ $upd_query = "DELETE FROM " . $userTable
+ . " WHERE `username`='" . PMA_Util::sqlAddSlashes($username) . "'";
+ } elseif ($oldUserGroup != $userGroup) {
+ $upd_query = "UPDATE " . $userTable
+ . " SET `usergroup`='" . PMA_Util::sqlAddSlashes($userGroup) . "'"
+ . " WHERE `username`='" . PMA_Util::sqlAddSlashes($username) . "'";
+ }
+ }
+ if (isset($upd_query)) {
+ PMA_queryAsControlUser($upd_query);
+ }
+}
+
+/**
+ * Displays the privileges form table
+ *
+ * @param string $db the database
+ * @param string $table the table
+ * @param boolean $submit wheather to display the submit button or not
+ *
+ * @global array $cfg the phpMyAdmin configuration
+ * @global ressource $user_link the database connection
+ *
+ * @return string html snippet
+ */
+function PMA_getHtmlToDisplayPrivilegesTable($db = '*',
+ $table = '*', $submit = true
+) {
+ $html_output = '';
+
+ if ($db == '*') {
+ $table = '*';
+ }
+
+ if (isset($GLOBALS['username'])) {
+ $username = $GLOBALS['username'];
+ $hostname = $GLOBALS['hostname'];
+ $sql_query = PMA_getSqlQueryForDisplayPrivTable(
+ $db, $table, $username, $hostname
+ );
+ $row = $GLOBALS['dbi']->fetchSingleRow($sql_query);
+ }
+ if (empty($row)) {
+ if ($table == '*') {
+ if ($db == '*') {
+ $sql_query = 'SHOW COLUMNS FROM `mysql`.`user`;';
+ } elseif ($table == '*') {
+ $sql_query = 'SHOW COLUMNS FROM `mysql`.`db`;';
+ }
+ $res = $GLOBALS['dbi']->query($sql_query);
+ while ($row1 = $GLOBALS['dbi']->fetchRow($res)) {
+ if (substr($row1[0], 0, 4) == 'max_') {
+ $row[$row1[0]] = 0;
+ } else {
+ $row[$row1[0]] = 'N';
+ }
+ }
+ $GLOBALS['dbi']->freeResult($res);
+ } else {
+ $row = array('Table_priv' => '');
+ }
+ }
+ if (isset($row['Table_priv'])) {
+ $row1 = $GLOBALS['dbi']->fetchSingleRow(
+ 'SHOW COLUMNS FROM `mysql`.`tables_priv` LIKE \'Table_priv\';',
+ 'ASSOC', $GLOBALS['userlink']
+ );
+ // note: in MySQL 5.0.3 we get "Create View', 'Show view';
+ // the View for Create is spelled with uppercase V
+ // the view for Show is spelled with lowercase v
+ // and there is a space between the words
+
+ $av_grants = explode(
+ '\',\'',
+ substr(
+ $row1['Type'],
+ strpos($row1['Type'], '(') + 2,
+ strpos($row1['Type'], ')') - strpos($row1['Type'], '(') - 3
+ )
+ );
+ unset($row1);
+ $users_grants = explode(',', $row['Table_priv']);
+
+ foreach ($av_grants as $current_grant) {
+ $row[$current_grant . '_priv']
+ = in_array($current_grant, $users_grants) ? 'Y' : 'N';
+ }
+ unset($row['Table_priv'], $current_grant, $av_grants, $users_grants);
+
+ // get columns
+ $res = $GLOBALS['dbi']->tryQuery(
+ 'SHOW COLUMNS FROM '
+ . PMA_Util::backquote(
+ PMA_Util::unescapeMysqlWildcards($db)
+ )
+ . '.' . PMA_Util::backquote($table) . ';'
+ );
+ $columns = array();
+ if ($res) {
+ while ($row1 = $GLOBALS['dbi']->fetchRow($res)) {
+ $columns[$row1[0]] = array(
+ 'Select' => false,
+ 'Insert' => false,
+ 'Update' => false,
+ 'References' => false
+ );
+ }
+ $GLOBALS['dbi']->freeResult($res);
+ }
+ unset($res, $row1);
+ }
+ // table-specific privileges
+ if (! empty($columns)) {
+ $html_output .= PMA_getHtmlForTableSpecificPrivileges(
+ $username, $hostname, $db, $table, $columns, $row
+ );
+ } else {
+ // global or db-specific
+ $html_output .= PMA_getHtmlForGlobalOrDbSpecificPrivs($db, $table, $row);
+ }
+ $html_output .= '</fieldset>' . "\n";
+ if ($submit) {
+ $html_output .= '<fieldset id="fieldset_user_privtable_footer" '
+ . 'class="tblFooters">' . "\n"
+ . '<input type="submit" name="update_privs" '
+ . 'value="' . __('Go') . '" />' . "\n"
+ . '</fieldset>' . "\n";
+ }
+ return $html_output;
+} // end of the 'PMA_displayPrivTable()' function
+
+/**
+ * Get HTML for "Resource limits"
+ *
+ * @param array $row first row from result or boolean false
+ *
+ * @return string html snippet
+ */
+function PMA_getHtmlForDisplayResourceLimits($row)
+{
+ $html_output = '<fieldset>' . "\n"
+ . '<legend>' . __('Resource limits') . '</legend>' . "\n"
+ . '<p><small>'
+ . '<i>' . __('Note: Setting these options to 0 (zero) removes the limit.')
+ . '</i></small></p>' . "\n";
+
+ $html_output .= '<div class="item">' . "\n"
+ . '<label for="text_max_questions">'
+ . '<code><dfn title="'
+ . __(
+ 'Limits the number of queries the user may send to the server per hour.'
+ )
+ . '">'
+ . 'MAX QUERIES PER HOUR'
+ . '</dfn></code></label>' . "\n"
+ . '<input type="number" name="max_questions" id="text_max_questions" '
+ . 'value="' . $row['max_questions'] . '" '
+ . 'size="6" maxlength="11" min="0" '
+ . 'title="'
+ . __(
+ 'Limits the number of queries the user may send to the server per hour.'
+ )
+ . '" />' . "\n"
+ . '</div>' . "\n";
+
+ $html_output .= '<div class="item">' . "\n"
+ . '<label for="text_max_updates">'
+ . '<code><dfn title="'
+ . __(
+ 'Limits the number of commands that change any table '
+ . 'or database the user may execute per hour.'
+ ) . '">'
+ . 'MAX UPDATES PER HOUR'
+ . '</dfn></code></label>' . "\n"
+ . '<input type="number" name="max_updates" id="text_max_updates" '
+ . 'value="' . $row['max_updates'] . '" size="6" maxlength="11" min="0" '
+ . 'title="'
+ . __(
+ 'Limits the number of commands that change any table '
+ . 'or database the user may execute per hour.'
+ )
+ . '" />' . "\n"
+ . '</div>' . "\n";
+
+ $html_output .= '<div class="item">' . "\n"
+ . '<label for="text_max_connections">'
+ . '<code><dfn title="'
+ . __(
+ 'Limits the number of new connections the user may open per hour.'
+ ) . '">'
+ . 'MAX CONNECTIONS PER HOUR'
+ . '</dfn></code></label>' . "\n"
+ . '<input type="number" name="max_connections" id="text_max_connections" '
+ . 'value="' . $row['max_connections'] . '" size="6" maxlength="11" min="0" '
+ . 'title="' . __(
+ 'Limits the number of new connections the user may open per hour.'
+ )
+ . '" />' . "\n"
+ . '</div>' . "\n";
+
+ $html_output .= '<div class="item">' . "\n"
+ . '<label for="text_max_user_connections">'
+ . '<code><dfn title="'
+ . __('Limits the number of simultaneous connections the user may have.')
+ . '">'
+ . 'MAX USER_CONNECTIONS'
+ . '</dfn></code></label>' . "\n"
+ . '<input type="number" name="max_user_connections" '
+ . 'id="text_max_user_connections" '
+ . 'value="' . $row['max_user_connections'] . '" size="6" maxlength="11" '
+ . 'title="'
+ . __('Limits the number of simultaneous connections the user may have.')
+ . '" />' . "\n"
+ . '</div>' . "\n";
+
+ $html_output .= '</fieldset>' . "\n";
+
+ return $html_output;
+}
+
+/**
+ * Get the HTML snippet for table specific privileges
+ *
+ * @param string $username username for database connection
+ * @param string $hostname hostname for database connection
+ * @param string $db the database
+ * @param string $table the table
+ * @param boolean $columns columns array
+ * @param array $row current privileges row
+ *
+ * @return string $html_output
+ */
+function PMA_getHtmlForTableSpecificPrivileges(
+ $username, $hostname, $db, $table, $columns, $row
+) {
+ $res = $GLOBALS['dbi']->query(
+ 'SELECT `Column_name`, `Column_priv`'
+ .' FROM `mysql`.`columns_priv`'
+ .' WHERE `User`'
+ .' = \'' . PMA_Util::sqlAddSlashes($username) . "'"
+ .' AND `Host`'
+ .' = \'' . PMA_Util::sqlAddSlashes($hostname) . "'"
+ .' AND `Db`'
+ .' = \'' . PMA_Util::sqlAddSlashes(
+ PMA_Util::unescapeMysqlWildcards($db)
+ ) . "'"
+ .' AND `Table_name`'
+ .' = \'' . PMA_Util::sqlAddSlashes($table) . '\';'
+ );
+
+ while ($row1 = $GLOBALS['dbi']->fetchRow($res)) {
+ $row1[1] = explode(',', $row1[1]);
+ foreach ($row1[1] as $current) {
+ $columns[$row1[0]][$current] = true;
+ }
+ }
+ $GLOBALS['dbi']->freeResult($res);
+ unset($res, $row1, $current);
+
+ $html_output = '<input type="hidden" name="grant_count" '
+ . 'value="' . count($row) . '" />' . "\n"
+ . '<input type="hidden" name="column_count" '
+ . 'value="' . count($columns) . '" />' . "\n"
+ . '<fieldset id="fieldset_user_priv">' . "\n"
+ . '<legend>' . __('Table-specific privileges')
+ . PMA_Util::showHint(
+ __('Note: MySQL privilege names are expressed in English')
+ )
+ . '</legend>' . "\n";
+
+ // privs that are attached to a specific column
+ $html_output .= PMA_getHtmlForAttachedPrivilegesToTableSpecificColumn(
+ $columns, $row
+ );
+
+ // privs that are not attached to a specific column
+ $html_output .= '<div class="item">' . "\n"
+ . PMA_getHtmlForNotAttachedPrivilegesToTableSpecificColumn($row)
+ . '</div>' . "\n";
+
+ // for Safari 2.0.2
+ $html_output .= '<div class="clearfloat"></div>' . "\n";
+
+ return $html_output;
+}
+
+/**
+ * Get HTML snippet for privileges that are attached to a specific column
+ *
+ * @param string $columns olumns array
+ * @param array $row first row from result or boolean false
+ *
+ * @return string $html_output
+ */
+function PMA_getHtmlForAttachedPrivilegesToTableSpecificColumn($columns, $row)
+{
+ $html_output = PMA_getHtmlForDisplayColumnPrivileges(
+ $columns, $row, 'Select_priv', 'SELECT',
+ 'select', __('Allows reading data.'), 'Select'
+ );
+
+ $html_output .= PMA_getHtmlForDisplayColumnPrivileges(
+ $columns, $row, 'Insert_priv', 'INSERT',
+ 'insert', __('Allows inserting and replacing data.'), 'Insert'
+ );
+
+ $html_output .= PMA_getHtmlForDisplayColumnPrivileges(
+ $columns, $row, 'Update_priv', 'UPDATE',
+ 'update', __('Allows changing data.'), 'Update'
+ );
+
+ $html_output .= PMA_getHtmlForDisplayColumnPrivileges(
+ $columns, $row, 'References_priv', 'REFERENCES', 'references',
+ __('Has no effect in this MySQL version.'), 'References'
+ );
+ return $html_output;
+}
+
+/**
+ * Get HTML for privileges that are not attached to a specific column
+ *
+ * @param array $row first row from result or boolean false
+ *
+ * @return string $html_output
+ */
+function PMA_getHtmlForNotAttachedPrivilegesToTableSpecificColumn($row)
+{
+ $html_output = '';
+ foreach ($row as $current_grant => $current_grant_value) {
+ $grant_type = substr($current_grant, 0, (strlen($current_grant) - 5));
+ if (in_array($grant_type, array('Select', 'Insert', 'Update', 'References'))
+ ) {
+ continue;
+ }
+ // make a substitution to match the messages variables;
+ // also we must substitute the grant we get, because we can't generate
+ // a form variable containing blanks (those would get changed to
+ // an underscore when receiving the POST)
+ if ($current_grant == 'Create View_priv') {
+ $tmp_current_grant = 'CreateView_priv';
+ $current_grant = 'Create_view_priv';
+ } elseif ($current_grant == 'Show view_priv') {
+ $tmp_current_grant = 'ShowView_priv';
+ $current_grant = 'Show_view_priv';
+ } else {
+ $tmp_current_grant = $current_grant;
+ }
+
+ $html_output .= '<div class="item">' . "\n"
+ . '<input type="checkbox"'
+ . ' name="' . $current_grant . '" id="checkbox_' . $current_grant
+ . '" value="Y" '
+ . ($current_grant_value == 'Y' ? 'checked="checked" ' : '')
+ . 'title="';
+
+ $html_output .= (isset($GLOBALS[
+ 'strPrivDesc' . substr(
+ $tmp_current_grant, 0, (strlen($tmp_current_grant) - 5)
+ )
+ ] )
+ ? $GLOBALS[
+ 'strPrivDesc' . substr(
+ $tmp_current_grant, 0, (strlen($tmp_current_grant) - 5)
+ )
+ ]
+ : $GLOBALS[
+ 'strPrivDesc' . substr(
+ $tmp_current_grant, 0, (strlen($tmp_current_grant) - 5)
+ ) . 'Tbl'
+ ]
+ )
+ . '"/>' . "\n";
+
+ $html_output .= '<label for="checkbox_' . $current_grant
+ . '"><code><dfn title="'
+ . (isset($GLOBALS[
+ 'strPrivDesc' . substr(
+ $tmp_current_grant, 0, (strlen($tmp_current_grant) - 5)
+ )
+ ])
+ ? $GLOBALS[
+ 'strPrivDesc' . substr(
+ $tmp_current_grant, 0, (strlen($tmp_current_grant) - 5)
+ )
+ ]
+ : $GLOBALS[
+ 'strPrivDesc' . substr(
+ $tmp_current_grant, 0, (strlen($tmp_current_grant) - 5)
+ ) . 'Tbl'
+ ]
+ )
+ . '">'
+ . strtoupper(
+ substr($current_grant, 0, strlen($current_grant) - 5)
+ )
+ . '</dfn></code></label>' . "\n"
+ . '</div>' . "\n";
+ } // end foreach ()
+ return $html_output;
+}
+
+/**
+ * Get HTML for global or database specific privileges
+ *
+ * @param string $db the database
+ * @param string $table the table
+ * @param string $row first row from result or boolean false
+ *
+ * @return string $html_output
+ */
+function PMA_getHtmlForGlobalOrDbSpecificPrivs($db, $table, $row)
+{
+ $privTable_names = array(0 => __('Data'),
+ 1 => __('Structure'),
+ 2 => __('Administration')
+ );
+ $privTable = array();
+ // d a t a
+ $privTable[0] = PMA_getDataPrivilegeTable($db);
+
+ // s t r u c t u r e
+ $privTable[1] = PMA_getStructurePrivilegeTable($table, $row);
+
+ // a d m i n i s t r a t i o n
+ $privTable[2] = PMA_getAdministrationPrivilegeTable($db);
+
+ $html_output = '<input type="hidden" name="grant_count" value="'
+ . (count($privTable[0])
+ + count($privTable[1])
+ + count($privTable[2])
+ - (isset($row['Grant_priv']) ? 1 : 0)
+ )
+ . '" />';
+ $html_output .= '<fieldset id="fieldset_user_global_rights"><legend>';
+ if ($db == '*') {
+ $html_output .= __('Global privileges');
+ } else if ($table == '*') {
+ $html_output .= __('Database-specific privileges');
+ } else {
+ $html_output .= __('Table-specific privileges');
+ }
+ $html_output .= '<input type="checkbox" id="addUsersForm_checkall" '
+ . 'class="checkall_box" title="' . __('Check All') . '" /> '
+ . '<label for="addUsersForm_checkall">' . __('Check All') . '</label> ';
+ $html_output .= '</legend>';
+ $html_output .= '<p><small><i>'
+ . __('Note: MySQL privilege names are expressed in English')
+ . '</i></small></p>';
+
+ // Output the Global privilege tables with checkboxes
+ $html_output .= PMA_getHtmlForGlobalPrivTableWithCheckboxes(
+ $privTable, $privTable_names, $row
+ );
+
+ // The "Resource limits" box is not displayed for db-specific privs
+ if ($db == '*') {
+ $html_output .= PMA_getHtmlForDisplayResourceLimits($row);
+ }
+ // for Safari 2.0.2
+ $html_output .= '<div class="clearfloat"></div>';
+
+ return $html_output;
+}
+
+/**
+ * Get data privilege table as an array
+ *
+ * @param string $db the database
+ *
+ * @return string data privilege table
+ */
+function PMA_getDataPrivilegeTable($db)
+{
+ $data_privTable = array(
+ array('Select', 'SELECT', __('Allows reading data.')),
+ array('Insert', 'INSERT', __('Allows inserting and replacing data.')),
+ array('Update', 'UPDATE', __('Allows changing data.')),
+ array('Delete', 'DELETE', __('Allows deleting data.'))
+ );
+ if ($db == '*') {
+ $data_privTable[]
+ = array('File',
+ 'FILE',
+ __('Allows importing data from and exporting data into files.')
+ );
+ }
+ return $data_privTable;
+}
+
+/**
+ * Get structure privilege table as an array
+ *
+ * @param string $table the table
+ * @param array $row first row from result or boolean false
+ *
+ * @return string structure privilege table
+ */
+function PMA_getStructurePrivilegeTable($table, $row)
+{
+ $structure_privTable = array(
+ array('Create',
+ 'CREATE',
+ ($table == '*'
+ ? __('Allows creating new databases and tables.')
+ : __('Allows creating new tables.')
+ )
+ ),
+ array('Alter',
+ 'ALTER',
+ __('Allows altering the structure of existing tables.')
+ ),
+ array('Index', 'INDEX', __('Allows creating and dropping indexes.')),
+ array('Drop',
+ 'DROP',
+ ($table == '*'
+ ? __('Allows dropping databases and tables.')
+ : __('Allows dropping tables.')
+ )
+ ),
+ array('Create_tmp_table',
+ 'CREATE TEMPORARY TABLES',
+ __('Allows creating temporary tables.')
+ ),
+ array('Show_view',
+ 'SHOW VIEW',
+ __('Allows performing SHOW CREATE VIEW queries.')
+ ),
+ array('Create_routine',
+ 'CREATE ROUTINE',
+ __('Allows creating stored routines.')
+ ),
+ array('Alter_routine',
+ 'ALTER ROUTINE',
+ __('Allows altering and dropping stored routines.')
+ ),
+ array('Execute', 'EXECUTE', __('Allows executing stored routines.')),
+ );
+ // this one is for a db-specific priv: Create_view_priv
+ if (isset($row['Create_view_priv'])) {
+ $structure_privTable[] = array('Create_view',
+ 'CREATE VIEW',
+ __('Allows creating new views.')
+ );
+ }
+ // this one is for a table-specific priv: Create View_priv
+ if (isset($row['Create View_priv'])) {
+ $structure_privTable[] = array('Create View',
+ 'CREATE VIEW',
+ __('Allows creating new views.')
+ );
+ }
+ if (isset($row['Event_priv'])) {
+ // MySQL 5.1.6
+ $structure_privTable[] = array('Event',
+ 'EVENT',
+ __('Allows to set up events for the event scheduler')
+ );
+ $structure_privTable[] = array('Trigger',
+ 'TRIGGER',
+ __('Allows creating and dropping triggers')
+ );
+ }
+ return $structure_privTable;
+}
+
+/**
+ * Get administration privilege table as an array
+ *
+ * @param string $db the table
+ *
+ * @return string administration privilege table
+ */
+function PMA_getAdministrationPrivilegeTable($db)
+{
+ $administration_privTable = array(
+ array('Grant',
+ 'GRANT',
+ __(
+ 'Allows adding users and privileges '
+ . 'without reloading the privilege tables.'
+ )
+ ),
+ );
+ if ($db == '*') {
+ $administration_privTable[] = array('Super',
+ 'SUPER',
+ __(
+ 'Allows connecting, even if maximum number '
+ . 'of connections is reached; required for '
+ . 'most administrative operations like '
+ . 'setting global variables or killing threads of other users.'
+ )
+ );
+ $administration_privTable[] = array('Process',
+ 'PROCESS',
+ __('Allows viewing processes of all users')
+ );
+ $administration_privTable[] = array('Reload',
+ 'RELOAD',
+ __('Allows reloading server settings and flushing the server\'s caches.')
+ );
+ $administration_privTable[] = array('Shutdown',
+ 'SHUTDOWN',
+ __('Allows shutting down the server.')
+ );
+ $administration_privTable[] = array('Show_db',
+ 'SHOW DATABASES',
+ __('Gives access to the complete list of databases.')
+ );
+ }
+ $administration_privTable[] = array('Lock_tables',
+ 'LOCK TABLES',
+ __('Allows locking tables for the current thread.')
+ );
+ $administration_privTable[] = array('References',
+ 'REFERENCES',
+ __('Has no effect in this MySQL version.')
+ );
+ if ($db == '*') {
+ $administration_privTable[] = array('Repl_client',
+ 'REPLICATION CLIENT',
+ __('Allows the user to ask where the slaves / masters are.')
+ );
+ $administration_privTable[] = array('Repl_slave',
+ 'REPLICATION SLAVE',
+ __('Needed for the replication slaves.')
+ );
+ $administration_privTable[] = array('Create_user',
+ 'CREATE USER',
+ __('Allows creating, dropping and renaming user accounts.')
+ );
+ }
+ return $administration_privTable;
+}
+
+/**
+ * Get HTML snippet for global privileges table with check boxes
+ *
+ * @param array $privTable privileges table array
+ * @param array $privTable_names names of the privilege tables
+ * (Data, Structure, Administration)
+ * @param array $row first row from result or boolean false
+ *
+ * @return string $html_output
+ */
+function PMA_getHtmlForGlobalPrivTableWithCheckboxes(
+ $privTable, $privTable_names, $row
+) {
+ $html_output = '';
+ foreach ($privTable as $i => $table) {
+ $html_output .= '<fieldset>' . "\n"
+ . '<legend>' . $privTable_names[$i] . '</legend>' . "\n";
+ foreach ($table as $priv) {
+ $html_output .= '<div class="item">' . "\n"
+ . '<input type="checkbox" class="checkall"'
+ . ' name="' . $priv[0] . '_priv" '
+ . 'id="checkbox_' . $priv[0] . '_priv"'
+ . ' value="Y" title="' . $priv[2] . '"'
+ . (($row[$priv[0] . '_priv'] == 'Y')
+ ? ' checked="checked"'
+ : ''
+ )
+ . '/>' . "\n"
+ . '<label for="checkbox_' . $priv[0] . '_priv">'
+ . '<code><dfn title="' . $priv[2] . '">'
+ . $priv[1] . '</dfn></code></label>' . "\n"
+ . '</div>' . "\n";
+ }
+ $html_output .= '</fieldset>' . "\n";
+ }
+ return $html_output;
+}
+
+/**
+ * Displays the fields used by the "new user" form as well as the
+ * "change login information / copy user" form.
+ *
+ * @param string $mode are we creating a new user or are we just
+ * changing one? (allowed values: 'new', 'change')
+ *
+ * @global array $cfg the phpMyAdmin configuration
+ * @global ressource $user_link the database connection
+ *
+ * @return string $html_output a HTML snippet
+ */
+function PMA_getHtmlForDisplayLoginInformationFields($mode = 'new')
+{
+ list($username_length, $hostname_length) = PMA_getUsernameAndHostnameLength();
+
+ if (isset($GLOBALS['username']) && strlen($GLOBALS['username']) === 0) {
+ $GLOBALS['pred_username'] = 'any';
+ }
+ $html_output = '<fieldset id="fieldset_add_user_login">' . "\n"
+ . '<legend>' . __('Login Information') . '</legend>' . "\n"
+ . '<div class="item">' . "\n"
+ . '<label for="select_pred_username">' . "\n"
+ . ' ' . __('User name:') . "\n"
+ . '</label>' . "\n"
+ . '<span class="options">' . "\n";
+
+ $html_output .= '<select name="pred_username" id="select_pred_username" '
+ . 'title="' . __('User name') . '"' . "\n";
+
+
+ $html_output .= ' onchange="'
+ . 'if (this.value == \'any\') {'
+ . ' username.value = \'\'; '
+ . ' user_exists_warning.style.display = \'none\'; '
+ . '} else if (this.value == \'userdefined\') {'
+ . ' username.focus(); username.select(); '
+ . '}">' . "\n";
+
+ $html_output .= '<option value="any"'
+ . ((isset($GLOBALS['pred_username']) && $GLOBALS['pred_username'] == 'any')
+ ? ' selected="selected"'
+ : '') . '>'
+ . __('Any user')
+ . '</option>' . "\n";
+
+ $html_output .= '<option value="userdefined"'
+ . ((! isset($GLOBALS['pred_username'])
+ || $GLOBALS['pred_username'] == 'userdefined'
+ )
+ ? ' selected="selected"'
+ : '') . '>'
+ . __('Use text field')
+ . ':</option>' . "\n";
+
+ $html_output .= '</select>' . "\n"
+ . '</span>' . "\n";
+
+ $html_output .= '<input type="text" name="username" class="autofocus"'
+ . ' maxlength="' . $username_length . '" title="' . __('User name') . '"'
+ . (empty($GLOBALS['username'])
+ ? ''
+ : ' value="' . htmlspecialchars(
+ isset($GLOBALS['new_username'])
+ ? $GLOBALS['new_username']
+ : $GLOBALS['username']
+ ) . '"'
+ )
+ . ' onchange="pred_username.value = \'userdefined\';" />' . "\n";
+
+ $html_output .= '<div id="user_exists_warning"'
+ . ' name="user_exists_warning" style="display:none;">'
+ . PMA_Message::notice(
+ __(
+ 'An account already exists with the same username '
+ . 'but possibly a different hostname. '
+ )
+ )->getDisplay()
+ . '</div>';
+ $html_output .= '</div>';
+
+ $html_output .= '<div class="item">' . "\n"
+ . '<label for="select_pred_hostname">' . "\n"
+ . ' ' . __('Host:') . "\n"
+ . '</label>' . "\n";
+
+ $html_output .= '<span class="options">' . "\n"
+ . ' <select name="pred_hostname" id="select_pred_hostname" '
+ . 'title="' . __('Host') . '"' . "\n";
+ $_current_user = $GLOBALS['dbi']->fetchValue('SELECT USER();');
+ if (! empty($_current_user)) {
+ $thishost = str_replace(
+ "'",
+ '',
+ substr($_current_user, (strrpos($_current_user, '@') + 1))
+ );
+ if ($thishost == 'localhost' || $thishost == '127.0.0.1') {
+ unset($thishost);
+ }
+ }
+ $html_output .= ' onchange="'
+ . 'if (this.value == \'any\') { '
+ . ' hostname.value = \'%\'; '
+ . '} else if (this.value == \'localhost\') { '
+ . ' hostname.value = \'localhost\'; '
+ . '} '
+ . (empty($thishost)
+ ? ''
+ : 'else if (this.value == \'thishost\') { '
+ . ' hostname.value = \'' . addslashes(htmlspecialchars($thishost))
+ . '\'; '
+ . '} '
+ )
+ . 'else if (this.value == \'hosttable\') { '
+ . ' hostname.value = \'\'; '
+ . '} else if (this.value == \'userdefined\') {'
+ . ' hostname.focus(); hostname.select(); '
+ . '}">' . "\n";
+ unset($_current_user);
+
+ // when we start editing a user, $GLOBALS['pred_hostname'] is not defined
+ if (! isset($GLOBALS['pred_hostname']) && isset($GLOBALS['hostname'])) {
+ switch (strtolower($GLOBALS['hostname'])) {
+ case 'localhost':
+ case '127.0.0.1':
+ $GLOBALS['pred_hostname'] = 'localhost';
+ break;
+ case '%':
+ $GLOBALS['pred_hostname'] = 'any';
+ break;
+ default:
+ $GLOBALS['pred_hostname'] = 'userdefined';
+ break;
+ }
+ }
+ $html_output .= '<option value="any"'
+ . ((isset($GLOBALS['pred_hostname'])
+ && $GLOBALS['pred_hostname'] == 'any'
+ )
+ ? ' selected="selected"'
+ : '') . '>'
+ . __('Any host')
+ . '</option>' . "\n"
+ . '<option value="localhost"'
+ . ((isset($GLOBALS['pred_hostname'])
+ && $GLOBALS['pred_hostname'] == 'localhost'
+ )
+ ? ' selected="selected"'
+ : '') . '>'
+ . __('Local')
+ . '</option>' . "\n";
+ if (! empty($thishost)) {
+ $html_output .= '<option value="thishost"'
+ . ((isset($GLOBALS['pred_hostname'])
+ && $GLOBALS['pred_hostname'] == 'thishost'
+ )
+ ? ' selected="selected"'
+ : '') . '>'
+ . __('This Host')
+ . '</option>' . "\n";
+ }
+ unset($thishost);
+ $html_output .= '<option value="hosttable"'
+ . ((isset($GLOBALS['pred_hostname'])
+ && $GLOBALS['pred_hostname'] == 'hosttable'
+ )
+ ? ' selected="selected"'
+ : '') . '>'
+ . __('Use Host Table')
+ . '</option>' . "\n";
+
+ $html_output .= '<option value="userdefined"'
+ . ((isset($GLOBALS['pred_hostname'])
+ && $GLOBALS['pred_hostname'] == 'userdefined'
+ )
+ ? ' selected="selected"'
+ : '') . '>'
+ . __('Use text field:') . '</option>' . "\n"
+ . '</select>' . "\n"
+ . '</span>' . "\n";
+
+ $html_output .= '<input type="text" name="hostname" maxlength="'
+ . $hostname_length . '" value="'
+ // use default value of '%' to match with the default 'Any host'
+ . htmlspecialchars(isset($GLOBALS['hostname']) ? $GLOBALS['hostname'] : '%')
+ . '" title="' . __('Host')
+ . '" onchange="pred_hostname.value = \'userdefined\';" />' . "\n"
+ . PMA_Util::showHint(
+ __(
+ 'When Host table is used, this field is ignored '
+ . 'and values stored in Host table are used instead.'
+ )
+ )
+ . '</div>' . "\n";
+
+ $html_output .= '<div class="item">' . "\n"
+ . '<label for="select_pred_password">' . "\n"
+ . ' ' . __('Password:') . "\n"
+ . '</label>' . "\n"
+ . '<span class="options">' . "\n"
+ . '<select name="pred_password" id="select_pred_password" title="'
+ . __('Password') . '"' . "\n";
+
+ $html_output .= ' onchange="'
+ . 'if (this.value == \'none\') { '
+ . ' pma_pw.value = \'\'; pma_pw2.value = \'\'; '
+ . '} else if (this.value == \'userdefined\') { '
+ . ' pma_pw.focus(); pma_pw.select(); '
+ . '}">' . "\n"
+ . ($mode == 'change' ? '<option value="keep" selected="selected">'
+ . __('Do not change the password')
+ . '</option>' . "\n" : '')
+ . '<option value="none"';
+
+ if (isset($GLOBALS['username']) && $mode != 'change') {
+ $html_output .= ' selected="selected"';
+ }
+ $html_output .= '>' . __('No Password') . '</option>' . "\n"
+ . '<option value="userdefined"'
+ . (isset($GLOBALS['username']) ? '' : ' selected="selected"') . '>'
+ . __('Use text field')
+ . ':</option>' . "\n"
+ . '</select>' . "\n"
+ . '</span>' . "\n"
+ . '<input type="password" id="text_pma_pw" name="pma_pw" '
+ . 'title="' . __('Password') . '" '
+ . 'onchange="pred_password.value = \'userdefined\';" />' . "\n"
+ . '</div>' . "\n";
+
+ $html_output .= '<div class="item" '
+ . 'id="div_element_before_generate_password">' . "\n"
+ . '<label for="text_pma_pw2">' . "\n"
+ . ' ' . __('Re-type:') . "\n"
+ . '</label>' . "\n"
+ . '<span class="options">&nbsp;</span>' . "\n"
+ . '<input type="password" name="pma_pw2" id="text_pma_pw2" '
+ . 'title="' . __('Re-type') . '" '
+ . 'onchange="pred_password.value = \'userdefined\';" />' . "\n"
+ . '</div>' . "\n"
+ // Generate password added here via jQuery
+ . '</fieldset>' . "\n";
+
+ return $html_output;
+} // end of the 'PMA_displayUserAndHostFields()' function
+
+/**
+ * Get username and hostname length
+ *
+ * @return array username length and hostname length
+ */
+function PMA_getUsernameAndHostnameLength()
+{
+ $fields_info = $GLOBALS['dbi']->getColumns('mysql', 'user', null, true);
+ $username_length = 16;
+ $hostname_length = 41;
+ foreach ($fields_info as $val) {
+ if ($val['Field'] == 'User') {
+ strtok($val['Type'], '()');
+ $value = strtok('()');
+ if (is_int($value)) {
+ $username_length = $value;
+ }
+ } elseif ($val['Field'] == 'Host') {
+ strtok($val['Type'], '()');
+ $value = strtok('()');
+ if (is_int($value)) {
+ $hostname_length = $value;
+ }
+ }
+ }
+ return array($username_length, $hostname_length);
+}
+
+/**
+ * Returns all the grants for a certain user on a certain host
+ * Used in the export privileges for all users section
+ *
+ * @param string $user User name
+ * @param string $host Host name
+ *
+ * @return string containing all the grants text
+ */
+function PMA_getGrants($user, $host)
+{
+ $grants = $GLOBALS['dbi']->fetchResult(
+ "SHOW GRANTS FOR '"
+ . PMA_Util::sqlAddSlashes($user) . "'@'"
+ . PMA_Util::sqlAddSlashes($host) . "'"
+ );
+ $response = '';
+ foreach ($grants as $one_grant) {
+ $response .= $one_grant . ";\n\n";
+ }
+ return $response;
+} // end of the 'PMA_getGrants()' function
+
+/**
+ * Update password and get message for password updating
+ *
+ * @param string $err_url error url
+ * @param string $username username
+ * @param string $hostname hostname
+ *
+ * @return string $message success or error message after updating password
+ */
+function PMA_updatePassword($err_url, $username, $hostname)
+{
+ // similar logic in user_password.php
+ $message = '';
+
+ if (empty($_REQUEST['nopass'])
+ && isset($_POST['pma_pw'])
+ && isset($_POST['pma_pw2'])
+ ) {
+ if ($_POST['pma_pw'] != $_POST['pma_pw2']) {
+ $message = PMA_Message::error(__('The passwords aren\'t the same!'));
+ } elseif (empty($_POST['pma_pw']) || empty($_POST['pma_pw2'])) {
+ $message = PMA_Message::error(__('The password is empty!'));
+ }
+ }
+
+ // here $nopass could be == 1
+ if (empty($message)) {
+
+ $hashing_function
+ = (! empty($_REQUEST['pw_hash']) && $_REQUEST['pw_hash'] == 'old'
+ ? 'OLD_'
+ : ''
+ )
+ . 'PASSWORD';
+
+ // in $sql_query which will be displayed, hide the password
+ $sql_query = 'SET PASSWORD FOR \''
+ . PMA_Util::sqlAddSlashes($username)
+ . '\'@\'' . PMA_Util::sqlAddSlashes($hostname) . '\' = '
+ . (($_POST['pma_pw'] == '')
+ ? '\'\''
+ : $hashing_function . '(\''
+ . preg_replace('@.@s', '*', $_POST['pma_pw']) . '\')');
+
+ $local_query = 'SET PASSWORD FOR \''
+ . PMA_Util::sqlAddSlashes($username)
+ . '\'@\'' . PMA_Util::sqlAddSlashes($hostname) . '\' = '
+ . (($_POST['pma_pw'] == '') ? '\'\'' : $hashing_function
+ . '(\'' . PMA_Util::sqlAddSlashes($_POST['pma_pw']) . '\')');
+
+ $GLOBALS['dbi']->tryQuery($local_query)
+ or PMA_Util::mysqlDie(
+ $GLOBALS['dbi']->getError(), $sql_query, false, $err_url
+ );
+ $message = PMA_Message::success(
+ __('The password for %s was changed successfully.')
+ );
+ $message->addParam(
+ '\'' . htmlspecialchars($username)
+ . '\'@\'' . htmlspecialchars($hostname) . '\''
+ );
+ }
+ return $message;
+}
+
+/**
+ * Revokes privileges and get message and SQL query for privileges revokes
+ *
+ * @param string $db_and_table wildcard Escaped database+table specification
+ * @param string $dbname database name
+ * @param string $tablename table name
+ * @param string $username username
+ * @param string $hostname host name
+ *
+ * @return array ($message, $sql_query)
+ */
+function PMA_getMessageAndSqlQueryForPrivilegesRevoke($db_and_table, $dbname,
+ $tablename, $username, $hostname
+) {
+ $db_and_table = PMA_wildcardEscapeForGrant($dbname, $tablename);
+
+ $sql_query0 = 'REVOKE ALL PRIVILEGES ON ' . $db_and_table
+ . ' FROM \''
+ . PMA_Util::sqlAddSlashes($username) . '\'@\''
+ . PMA_Util::sqlAddSlashes($hostname) . '\';';
+
+ $sql_query1 = 'REVOKE GRANT OPTION ON ' . $db_and_table
+ . ' FROM \'' . PMA_Util::sqlAddSlashes($username) . '\'@\''
+ . PMA_Util::sqlAddSlashes($hostname) . '\';';
+
+ $GLOBALS['dbi']->query($sql_query0);
+ if (! $GLOBALS['dbi']->tryQuery($sql_query1)) {
+ // this one may fail, too...
+ $sql_query1 = '';
+ }
+ $sql_query = $sql_query0 . ' ' . $sql_query1;
+ $message = PMA_Message::success(
+ __('You have revoked the privileges for %s.')
+ );
+ $message->addParam(
+ '\'' . htmlspecialchars($username)
+ . '\'@\'' . htmlspecialchars($hostname) . '\''
+ );
+
+ return array($message, $sql_query);
+}
+
+/**
+ * Get a WITH clause for 'update privileges' and 'add user'
+ *
+ * @return string $sql_query
+ */
+function PMA_getWithClauseForAddUserAndUpdatePrivs()
+{
+ $sql_query = '';
+ if (isset($_POST['Grant_priv']) && $_POST['Grant_priv'] == 'Y') {
+ $sql_query .= ' GRANT OPTION';
+ }
+ if (isset($_POST['max_questions'])) {
+ $max_questions = max(0, (int)$_POST['max_questions']);
+ $sql_query .= ' MAX_QUERIES_PER_HOUR ' . $max_questions;
+ }
+ if (isset($_POST['max_connections'])) {
+ $max_connections = max(0, (int)$_POST['max_connections']);
+ $sql_query .= ' MAX_CONNECTIONS_PER_HOUR ' . $max_connections;
+ }
+ if (isset($_POST['max_updates'])) {
+ $max_updates = max(0, (int)$_POST['max_updates']);
+ $sql_query .= ' MAX_UPDATES_PER_HOUR ' . $max_updates;
+ }
+ if (isset($_POST['max_user_connections'])) {
+ $max_user_connections = max(0, (int)$_POST['max_user_connections']);
+ $sql_query .= ' MAX_USER_CONNECTIONS ' . $max_user_connections;
+ }
+ return ((!empty($sql_query)) ? 'WITH' . $sql_query : '');
+}
+
+/**
+ * Get HTML for addUsersForm, This function call if isset($_REQUEST['adduser'])
+ *
+ * @param string $dbname database name
+ *
+ * @return string HTML for addUserForm
+ */
+function PMA_getHtmlForAddUser($dbname)
+{
+ $html_output = '<h2>' . "\n"
+ . PMA_Util::getIcon('b_usradd.png') . __('Add user') . "\n"
+ . '</h2>' . "\n"
+ . '<form name="usersForm" class="ajax" id="addUsersForm"'
+ . ' action="server_privileges.php" method="post" autocomplete="off" >' . "\n"
+ . PMA_URL_getHiddenInputs('', '')
+ . PMA_getHtmlForDisplayLoginInformationFields('new');
+
+ $html_output .= '<fieldset id="fieldset_add_user_database">' . "\n"
+ . '<legend>' . __('Database for user') . '</legend>' . "\n";
+
+ $html_output .= PMA_Util::getCheckbox(
+ 'createdb-1',
+ __('Create database with same name and grant all privileges.'),
+ false, false
+ );
+ $html_output .= '<br />' . "\n";
+ $html_output .= PMA_Util::getCheckbox(
+ 'createdb-2',
+ __('Grant all privileges on wildcard name (username\\_%).'),
+ false, false
+ );
+ $html_output .= '<br />' . "\n";
+
+ if (! empty($dbname) ) {
+ $html_output .= PMA_Util::getCheckbox(
+ 'createdb-3',
+ sprintf(
+ __('Grant all privileges on database &quot;%s&quot;.'),
+ htmlspecialchars($dbname)
+ ),
+ true,
+ false
+ );
+ $html_output .= '<input type="hidden" name="dbname" value="'
+ . htmlspecialchars($dbname) . '" />' . "\n";
+ $html_output .= '<br />' . "\n";
+ }
+
+ $html_output .= '</fieldset>' . "\n";
+ $html_output .= PMA_getHtmlToDisplayPrivilegesTable('*', '*', false);
+ $html_output .= '<fieldset id="fieldset_add_user_footer" class="tblFooters">'
+ . "\n"
+ . '<input type="submit" name="adduser_submit" '
+ . 'value="' . __('Go') . '" />' . "\n"
+ . '</fieldset>' . "\n"
+ . '</form>' . "\n";
+
+ return $html_output;
+}
+
+/**
+ * Get the list of privileges and list of compared privileges as strings
+ * and return a array that contains both strings
+ *
+ * @return array $list_of_privileges, $list_of_compared_privileges
+ */
+function PMA_getListOfPrivilegesAndComparedPrivileges()
+{
+ $list_of_privileges
+ = '`User`, '
+ . '`Host`, '
+ . '`Select_priv`, '
+ . '`Insert_priv`, '
+ . '`Update_priv`, '
+ . '`Delete_priv`, '
+ . '`Create_priv`, '
+ . '`Drop_priv`, '
+ . '`Grant_priv`, '
+ . '`Index_priv`, '
+ . '`Alter_priv`, '
+ . '`References_priv`, '
+ . '`Create_tmp_table_priv`, '
+ . '`Lock_tables_priv`, '
+ . '`Create_view_priv`, '
+ . '`Show_view_priv`, '
+ . '`Create_routine_priv`, '
+ . '`Alter_routine_priv`, '
+ . '`Execute_priv`';
+
+ $list_of_compared_privileges
+ = '`Select_priv` = \'N\''
+ . ' AND `Insert_priv` = \'N\''
+ . ' AND `Update_priv` = \'N\''
+ . ' AND `Delete_priv` = \'N\''
+ . ' AND `Create_priv` = \'N\''
+ . ' AND `Drop_priv` = \'N\''
+ . ' AND `Grant_priv` = \'N\''
+ . ' AND `References_priv` = \'N\''
+ . ' AND `Create_tmp_table_priv` = \'N\''
+ . ' AND `Lock_tables_priv` = \'N\''
+ . ' AND `Create_view_priv` = \'N\''
+ . ' AND `Show_view_priv` = \'N\''
+ . ' AND `Create_routine_priv` = \'N\''
+ . ' AND `Alter_routine_priv` = \'N\''
+ . ' AND `Execute_priv` = \'N\'';
+
+ if (PMA_MYSQL_INT_VERSION >= 50106) {
+ $list_of_privileges .=
+ ', `Event_priv`, '
+ . '`Trigger_priv`';
+ $list_of_compared_privileges .=
+ ' AND `Event_priv` = \'N\''
+ . ' AND `Trigger_priv` = \'N\'';
+ }
+ return array($list_of_privileges, $list_of_compared_privileges);
+}
+
+/**
+ * Get the HTML for user form and check the privileges for a particular database.
+ *
+ * @param string $db database name
+ *
+ * @return string $html_output
+ */
+function PMA_getHtmlForSpecificDbPrivileges($db)
+{
+ // check the privileges for a particular database.
+ $html_output = '<form id="usersForm" action="server_privileges.php">'
+ . '<fieldset>' . "\n";
+ $html_output .= '<legend>' . "\n"
+ . PMA_Util::getIcon('b_usrcheck.png')
+ . ' '
+ . sprintf(
+ __('Users having access to &quot;%s&quot;'),
+ '<a href="' . $GLOBALS['cfg']['DefaultTabDatabase'] . '?'
+ . PMA_URL_getCommon($db) . '">'
+ . htmlspecialchars($db)
+ . '</a>'
+ )
+ . "\n"
+ . '</legend>' . "\n";
+
+ $html_output .= '<table id="dbspecificuserrights" class="data">' . "\n"
+ . '<thead>' . "\n"
+ . '<tr><th>' . __('User') . '</th>' . "\n"
+ . '<th>' . __('Host') . '</th>' . "\n"
+ . '<th>' . __('Type') . '</th>' . "\n"
+ . '<th>' . __('Privileges') . '</th>' . "\n"
+ . '<th>' . __('Grant') . '</th>' . "\n"
+ . '<th>' . __('Action') . '</th>' . "\n"
+ . '</tr>' . "\n"
+ . '</thead>' . "\n";
+ $odd_row = true;
+ // now, we build the table...
+ list($list_of_privileges, $list_of_compared_privileges)
+ = PMA_getListOfPrivilegesAndComparedPrivileges();
+
+ $sql_query = '(SELECT ' . $list_of_privileges . ', `Db`, \'d\' AS `Type`'
+ .' FROM `mysql`.`db`'
+ .' WHERE \'' . PMA_Util::sqlAddSlashes($db)
+ . "'"
+ .' LIKE `Db`'
+ .' AND NOT (' . $list_of_compared_privileges. ')) '
+ .'UNION '
+ .'(SELECT ' . $list_of_privileges . ', \'*\' AS `Db`, \'g\' AS `Type`'
+ .' FROM `mysql`.`user` '
+ .' WHERE NOT (' . $list_of_compared_privileges . ')) '
+ .' ORDER BY `User` ASC,'
+ .' `Host` ASC,'
+ .' `Db` ASC;';
+ $res = $GLOBALS['dbi']->query($sql_query);
+
+ $privMap = array();
+ while ($row = $GLOBALS['dbi']->fetchAssoc($res)) {
+ $user = $row['User'];
+ $host = $row['Host'];
+ if (! isset($privMap[$user])) {
+ $privMap[$user] = array();
+ }
+ if (! isset($privMap[$user][$host])) {
+ $privMap[$user][$host] = array();
+ }
+ $privMap[$user][$host][] = $row;
+ }
+
+ $html_output .= PMA_getHtmlTableBodyForSpecificDbOrTablePrivs($privMap, $db);
+ $html_output .= '</table>'
+ . '</fieldset>'
+ . '</form>' . "\n";
+
+ if ($GLOBALS['is_ajax_request'] == true
+ && empty($_REQUEST['ajax_page_request'])
+ ) {
+ $message = PMA_Message::success(__('User has been added.'));
+ $response = PMA_Response::getInstance();
+ $response->addJSON('message', $message);
+ $response->addJSON('user_form', $html_output);
+ exit;
+ } else {
+ // Offer to create a new user for the current database
+ $html_output .= '<fieldset id="fieldset_add_user">' . "\n"
+ . '<legend>' . _pgettext('Create new user', 'New') . '</legend>' . "\n";
+
+ $html_output .= '<a href="server_privileges.php'
+ . PMA_URL_getCommon(
+ array(
+ 'adduser' => 1,
+ 'dbname' => $db,
+ )
+ )
+ .'" rel="'
+ . PMA_URL_getCommon(array('checkprivsdb' => $db))
+ . '" class="ajax" name="db_specific">' . "\n"
+ . PMA_Util::getIcon('b_usradd.png')
+ . ' ' . __('Add user') . '</a>' . "\n";
+
+ $html_output .= '</fieldset>' . "\n";
+ }
+ return $html_output;
+}
+
+/**
+ * Get the HTML for user form and check the privileges for a particular table.
+ *
+ * @param string $db database name
+ * @param string $table table name
+ *
+ * @return string $html_output
+ */
+function PMA_getHtmlForSpecificTablePrivileges($db, $table)
+{
+ // check the privileges for a particular table.
+ $html_output = '<form id="usersForm" action="server_privileges.php">';
+ $html_output .= '<fieldset>';
+ $html_output .= '<legend>'
+ . PMA_Util::getIcon('b_usrcheck.png')
+ . sprintf(
+ __('Users having access to &quot;%s&quot;'),
+ '<a href="' . $GLOBALS['cfg']['DefaultTabTable']
+ . PMA_URL_getCommon(
+ array(
+ 'db' => $db,
+ 'table' => $table,
+ )
+ ) . '">'
+ . htmlspecialchars($db) . '.' . htmlspecialchars($table)
+ . '</a>'
+ )
+ . '</legend>';
+
+ $html_output .= '<table id="tablespecificuserrights" class="data">';
+ $html_output .= '<thead>'
+ . '<tr><th>' . __('User') . '</th>'
+ . '<th>' . __('Host') . '</th>'
+ . '<th>' . __('Type') . '</th>'
+ . '<th>' . __('Privileges') . '</th>'
+ . '<th>' . __('Grant') . '</th>'
+ . '<th>' . __('Action') . '</th>'
+ . '</tr>'
+ . '</thead>';
+
+ list($list_of_privileges, $list_of_compared_privileges)
+ = PMA_getListOfPrivilegesAndComparedPrivileges();
+ $sql_query
+ = "("
+ . " SELECT " . $list_of_privileges . ", '*' AS `Db`, 'g' AS `Type`"
+ . " FROM `mysql`.`user`"
+ . " WHERE NOT (" . $list_of_compared_privileges . ")"
+ . ")"
+ . " UNION "
+ . "("
+ . " SELECT " . $list_of_privileges . ", `Db`, 'd' AS `Type`"
+ . " FROM `mysql`.`db`"
+ . " WHERE '" . PMA_Util::sqlAddSlashes($db) . "' LIKE `Db`"
+ . " AND NOT (" . $list_of_compared_privileges. ")"
+ . ")"
+ . " ORDER BY `User` ASC, `Host` ASC, `Db` ASC;";
+ $res = $GLOBALS['dbi']->query($sql_query);
+
+ $privMap = array();
+ while ($row = $GLOBALS['dbi']->fetchAssoc($res)) {
+ $user = $row['User'];
+ $host = $row['Host'];
+ if (! isset($privMap[$user])) {
+ $privMap[$user] = array();
+ }
+ if (! isset($privMap[$user][$host])) {
+ $privMap[$user][$host] = array();
+ }
+ $privMap[$user][$host][] = $row;
+ }
+
+ $sql_query = "SELECT `User`, `Host`, `Db`,"
+ . " 't' AS `Type`, `Table_name`, `Table_priv`"
+ . " FROM `mysql`.`tables_priv`"
+ . " WHERE '" . PMA_Util::sqlAddSlashes($db) . "' LIKE `Db`"
+ . " AND '" . PMA_Util::sqlAddSlashes($table) . "' LIKE `Table_name`"
+ . " AND NOT (`Table_priv` = '' AND Column_priv = '')"
+ . " ORDER BY `User` ASC, `Host` ASC, `Db` ASC, `Table_priv` ASC;";
+ $res = $GLOBALS['dbi']->query($sql_query);
+
+ while ($row = $GLOBALS['dbi']->fetchAssoc($res)) {
+ $user = $row['User'];
+ $host = $row['Host'];
+ if (! isset($privMap[$user])) {
+ $privMap[$user] = array();
+ }
+ if (! isset($privMap[$user][$host])) {
+ $privMap[$user][$host] = array();
+ }
+ $privMap[$user][$host][] = $row;
+ }
+
+ $html_output .= PMA_getHtmlTableBodyForSpecificDbOrTablePrivs(
+ $privMap, $db, $table
+ );
+ $html_output .= '</table>';
+ $html_output .= '</fieldset>';
+ $html_output .= '</form>';
+
+ // Offer to create a new user for the current database
+ $html_output .= '<fieldset id="fieldset_add_user">'
+ . '<legend>' . _pgettext('Create new user', 'New') . '</legend>';
+ $html_output .= '<a href="server_privileges.php'
+ . PMA_URL_getCommon(
+ array(
+ 'adduser' => 1,
+ 'dbname' => $db,
+ 'tablename' => $table
+ )
+ )
+ . '" rel="' . PMA_URL_getCommon(
+ array('checkprivsdb' => $db, 'checkprivstable' => $table)
+ )
+ . '" class="ajax" name="table_specific">'
+ . PMA_Util::getIcon('b_usradd.png') . __('Add user') . '</a>';
+
+ $html_output .= '</fieldset>';
+ return $html_output;
+}
+
+/**
+ * Get HTML snippet for table body of specific database or table privileges
+ *
+ * @param array $privMap priviledge map
+ * @param boolean $db database
+ * @param boolean $table table
+ *
+ * @return string $html_output
+ */
+function PMA_getHtmlTableBodyForSpecificDbOrTablePrivs($privMap, $db, $table = null)
+{
+ $html_output = '<tbody>';
+ $odd_row = true;
+ if (! empty($privMap)) {
+ foreach ($privMap as $current_user => $val) {
+ foreach ($val as $current_host => $current_privileges) {
+ $html_output .= '<tr class="noclick '
+ . ($odd_row ? 'odd' : 'even') . '">';
+
+ // user
+ $html_output .= '<td';
+ if (count($current_privileges) > 1) {
+ $html_output .= ' rowspan="' . count($current_privileges) . '"';
+ }
+ $html_output .= '>';
+ if (empty($current_user)) {
+ $html_output .= '<span style="color: #FF0000">'
+ . __('Any') . '</span>';
+ } else {
+ $html_output .= htmlspecialchars($current_user);
+ }
+ $html_output .= '</td>';
+
+ // host
+ $html_output .= '<td';
+ if (count($current_privileges) > 1) {
+ $html_output .= ' rowspan="' . count($current_privileges) . '"';
+ }
+ $html_output .= '>';
+ $html_output .= htmlspecialchars($current_host);
+ $html_output .= '</td>';
+
+ for ($i = 0; $i < count($current_privileges); $i++) {
+ $current = $current_privileges[$i];
+
+ // type
+ $html_output .= '<td>';
+ if ($current['Type'] == 'g') {
+ $html_output .= __('global');
+ } elseif ($current['Type'] == 'd') {
+ if ($current['Db'] == PMA_Util::escapeMysqlWildcards($db)) {
+ $html_output .= __('database-specific');
+ } else {
+ $html_output .= __('wildcard'). ': '
+ . '<code>'
+ . htmlspecialchars($current['Db'])
+ . '</code>';
+ }
+ } elseif ($current['Type'] == 't') {
+ $html_output .= __('table-specific');
+ }
+ $html_output .= '</td>';
+
+ // privileges
+ $html_output .= '<td>';
+ if (isset($current['Table_name'])) {
+ $privList = explode(',', $current['Table_priv']);
+ $privs = array();
+ $grantsArr = PMA_getTableGrantsArray();
+ foreach ($grantsArr as $grant) {
+ $privs[$grant[0]] = 'N';
+ foreach ($privList as $priv) {
+ if ($grant[0] == $priv) {
+ $privs[$grant[0]] = 'Y';
+ }
+ }
+ }
+ $html_output .= '<code>'
+ . join(
+ ',',
+ PMA_extractPrivInfo($privs, true, true)
+ )
+ . '</code>';
+ } else {
+ $html_output .= '<code>'
+ . join(
+ ',',
+ PMA_extractPrivInfo($current, true, false)
+ )
+ . '</code>';
+ }
+ $html_output .= '</td>';
+
+ // grant
+ $html_output .= '<td>';
+ $containsGrant = false;
+ if (isset($current['Table_name'])) {
+ $privList = explode(',', $current['Table_priv']);
+ foreach ($privList as $priv) {
+ if ($priv == 'Grant') {
+ $containsGrant = true;
+ }
+ }
+ } else {
+ $containsGrant = $current['Grant_priv'] == 'Y';
+ }
+ $html_output .= ($containsGrant ? __('Yes') : __('No'));
+ $html_output .= '</td>';
+
+ // action
+ $html_output .= '<td>';
+ $specific_db = (isset($current['Db']) && $current['Db'] != '*')
+ ? $current['Db'] : '';
+ $specific_table = (isset($current['Table_name'])
+ && $current['Table_name'] != '*')
+ ? $current['Table_name'] : '';
+ $html_output .= PMA_getUserEditLink(
+ $current_user,
+ $current_host,
+ $specific_db,
+ $specific_table
+ );
+ $html_output .= '</td>';
+
+ $html_output .= '</tr>';
+ if (($i + 1) < count($current_privileges)) {
+ $html_output .= '<tr class="noclick '
+ . ($odd_row ? 'odd' : 'even') . '">';
+ }
+ }
+ $odd_row = ! $odd_row;
+ }
+ }
+ } else {
+ $html_output .= '<tr class="odd">'
+ . '<td colspan="6">'
+ . __('No user found.')
+ . '</td>'
+ . '</tr>';
+ }
+ $html_output .= '</tbody>';
+
+ return $html_output;
+}
+
+/**
+ * Returns edit link for a user.
+ *
+ * @param string $username User name
+ * @param string $hostname Host name
+ * @param string $dbname Database name
+ * @param string $tablename Table name
+ *
+ * @return string HTML code with link
+ */
+function PMA_getUserEditLink($username, $hostname, $dbname = '', $tablename = '')
+{
+ return '<a class="edit_user_anchor ajax"'
+ . ' href="server_privileges.php'
+ . PMA_URL_getCommon(
+ array(
+ 'username' => $username,
+ 'hostname' => $hostname,
+ 'dbname' => $dbname,
+ 'tablename' => $tablename,
+ )
+ )
+ . '">'
+ . PMA_Util::getIcon('b_usredit.png', __('Edit Privileges'))
+ . '</a>';
+}
+
+/**
+ * Returns revoke link for a user.
+ *
+ * @param string $username User name
+ * @param string $hostname Host name
+ * @param string $dbname Database name
+ * @param string $tablename Table name
+ *
+ * @return string HTML code with link
+ */
+function PMA_getUserRevokeLink($username, $hostname, $dbname = '', $tablename = '')
+{
+ return '<a href="server_privileges.php'
+ . PMA_URL_getCommon(
+ array(
+ 'username' => $username,
+ 'hostname' => $hostname,
+ 'dbname' => $dbname,
+ 'tablename' => $tablename,
+ 'revokeall' => 1,
+ )
+ )
+ . '">'
+ . PMA_Util::getIcon('b_usrdrop.png', __('Revoke'))
+ . '</a>';
+}
+
+/**
+ * Returns export link for a user.
+ *
+ * @param string $username User name
+ * @param string $hostname Host name
+ * @param string $initial Initial value
+ *
+ * @return HTML code with link
+ */
+function PMA_getUserExportLink($username, $hostname, $initial = '')
+{
+ return '<a class="export_user_anchor ajax"'
+ . ' href="server_privileges.php'
+ . PMA_URL_getCommon(
+ array(
+ 'username' => $username,
+ 'hostname' => $hostname,
+ 'initial' => $initial,
+ 'export' => 1,
+ )
+ )
+ . '">'
+ . PMA_Util::getIcon('b_tblexport.png', __('Export'))
+ . '</a>';
+}
+
+/**
+ * Returns user group edit link
+ *
+ * @param string $username User name
+ *
+ * @return HTML code with link
+ */
+function PMA_getUserGroupEditLink($username)
+{
+ return '<a class="edit_user_group_anchor ajax"'
+ . ' href="server_privileges.php'
+ . PMA_URL_getCommon(array('username' => $username))
+ . '">'
+ . PMA_Util::getIcon('b_usrlist.png', __('Edit user group'))
+ . '</a>';
+}
+
+/**
+ * Returns number of defined user groups
+ *
+ * @return integer $user_group_count
+ */
+function PMA_getUserGroupCount()
+{
+ $user_group_table = PMA_Util::backquote($GLOBALS['cfg']['Server']['pmadb'])
+ . '.' . PMA_Util::backquote($GLOBALS['cfg']['Server']['usergroups']);
+ $sql_query = 'SELECT COUNT(*) FROM ' . $user_group_table;
+ $user_group_count = $GLOBALS['dbi']->fetchValue(
+ $sql_query, 0, 0, $GLOBALS['controllink']
+ );
+
+ return $user_group_count;
+}
+
+/**
+ * This function return the extra data array for the ajax behavior
+ *
+ * @param string $password password
+ * @param string $sql_query sql query
+ * @param string $hostname hostname
+ * @param string $username username
+ *
+ * @return array $extra_data
+ */
+function PMA_getExtraDataForAjaxBehavior(
+ $password, $sql_query, $hostname, $username
+) {
+ if (isset($GLOBALS['dbname'])) {
+ //if (preg_match('/\\\\(?:_|%)/i', $dbname)) {
+ if (preg_match('/(?<!\\\\)(?:_|%)/i', $GLOBALS['dbname'])) {
+ $dbname_is_wildcard = true;
+ } else {
+ $dbname_is_wildcard = false;
+ }
+ }
+
+ $user_group_count = 0;
+ if ($GLOBALS['cfgRelation']['menuswork']) {
+ $user_group_count = PMA_getUserGroupCount();
+ }
+
+ $extra_data = array();
+ if (strlen($sql_query)) {
+ $extra_data['sql_query']
+ = PMA_Util::getMessage(null, $sql_query);
+ }
+
+ if (isset($_REQUEST['adduser_submit']) || isset($_REQUEST['change_copy'])) {
+ /**
+ * generate html on the fly for the new user that was just created.
+ */
+ $new_user_string = '<tr>'."\n"
+ . '<td> <input type="checkbox" name="selected_usr[]" '
+ . 'id="checkbox_sel_users_"'
+ . 'value="'
+ . htmlspecialchars($username)
+ . '&amp;#27;' . htmlspecialchars($hostname) . '" />'
+ . '</td>' . "\n"
+ . '<td><label for="checkbox_sel_users_">'
+ . (empty($_REQUEST['username'])
+ ? '<span style="color: #FF0000">' . __('Any') . '</span>'
+ : htmlspecialchars($username) ) . '</label></td>' . "\n"
+ . '<td>' . htmlspecialchars($hostname) . '</td>' . "\n";
+
+ $new_user_string .= '<td>';
+
+ if (! empty($password) || isset($_POST['pma_pw'])) {
+ $new_user_string .= __('Yes');
+ } else {
+ $new_user_string .= '<span style="color: #FF0000">'
+ . __('No')
+ . '</span>';
+ };
+
+ $new_user_string .= '</td>'."\n";
+ $new_user_string .= '<td>'
+ . '<code>' . join(', ', PMA_extractPrivInfo('', true)) . '</code>'
+ . '</td>'; //Fill in privileges here
+
+ // if $cfg['Servers'][$i]['users'] and $cfg['Servers'][$i]['usergroups'] are
+ // enabled
+ $cfgRelation = PMA_getRelationsParam();
+ if (isset($cfgRelation['users']) && isset($cfgRelation['usergroups'])) {
+ $new_user_string .= '<td class="usrGroup"></td>';
+ }
+
+ $new_user_string .= '<td>';
+
+ if ((isset($_POST['Grant_priv']) && $_POST['Grant_priv'] == 'Y')) {
+ $new_user_string .= __('Yes');
+ } else {
+ $new_user_string .= __('No');
+ }
+
+ $new_user_string .='</td>';
+
+ $new_user_string .= '<td>'
+ . PMA_getUserEditLink($username, $hostname)
+ . '</td>' . "\n";
+
+ if (isset($cfgRelation['menuswork']) && $user_group_count > 0) {
+ $new_user_string .= '<td>'
+ . PMA_getUserGroupEditLink($username)
+ . '</td>' . "\n";
+ }
+
+ $new_user_string .= '<td>'
+ . PMA_getUserExportLink(
+ $username,
+ $hostname,
+ isset($_GET['initial']) ? $_GET['initial'] : ''
+ )
+ . '</td>' . "\n";
+
+ $new_user_string .= '</tr>';
+
+ $extra_data['new_user_string'] = $new_user_string;
+
+ /**
+ * Generate the string for this alphabet's initial, to update the user
+ * pagination
+ */
+ $new_user_initial = strtoupper(substr($username, 0, 1));
+ $new_user_initial_string = '<a href="server_privileges.php'
+ . PMA_URL_getCommon(array('initial' => $new_user_initial)) .'">'
+ . $new_user_initial . '</a>';
+ $extra_data['new_user_initial'] = $new_user_initial;
+ $extra_data['new_user_initial_string'] = $new_user_initial_string;
+ }
+
+ if (isset($_POST['update_privs'])) {
+ $extra_data['db_specific_privs'] = false;
+ $extra_data['db_wildcard_privs'] = false;
+ if (isset($dbname_is_wildcard)) {
+ $extra_data['db_specific_privs'] = ! $dbname_is_wildcard;
+ $extra_data['db_wildcard_privs'] = $dbname_is_wildcard;
+ }
+ $new_privileges = join(', ', PMA_extractPrivInfo('', true));
+
+ $extra_data['new_privileges'] = $new_privileges;
+ }
+
+ if (isset($_REQUEST['validate_username'])) {
+ $sql_query = "SELECT * FROM `mysql`.`user` WHERE `User` = '"
+ . $_REQUEST['username'] . "';";
+ $res = $GLOBALS['dbi']->query($sql_query);
+ $row = $GLOBALS['dbi']->fetchRow($res);
+ if (empty($row)) {
+ $extra_data['user_exists'] = false;
+ } else {
+ $extra_data['user_exists'] = true;
+ }
+ }
+
+ return $extra_data;
+}
+
+/**
+ * Get the HTML snippet for change user login information
+ *
+ * @param string $username username
+ * @param string $hostname host name
+ *
+ * @return string HTML snippet
+ */
+function PMA_getChangeLoginInformationHtmlForm($username, $hostname)
+{
+ $choices = array(
+ '4' => __('… keep the old one.'),
+ '1' => __('… delete the old one from the user tables.'),
+ '2' => __(
+ '… revoke all active privileges from '
+ . 'the old one and delete it afterwards.'
+ ),
+ '3' => __(
+ '… delete the old one from the user tables '
+ . 'and reload the privileges afterwards.'
+ )
+ );
+
+ $class = ' ajax';
+ $html_output = '<form action="server_privileges.php" '
+ . 'method="post" class="copyUserForm' . $class .'">' . "\n"
+ . PMA_URL_getHiddenInputs('', '')
+ . '<input type="hidden" name="old_username" '
+ . 'value="' . htmlspecialchars($username) . '" />' . "\n"
+ . '<input type="hidden" name="old_hostname" '
+ . 'value="' . htmlspecialchars($hostname) . '" />' . "\n"
+ . '<fieldset id="fieldset_change_copy_user">' . "\n"
+ . '<legend>' . __('Change Login Information / Copy User')
+ . '</legend>' . "\n"
+ . PMA_getHtmlForDisplayLoginInformationFields('change');
+
+ $html_output .= '<fieldset id="fieldset_mode">' . "\n"
+ . ' <legend>'
+ . __('Create a new user with the same privileges and …')
+ . '</legend>' . "\n";
+ $html_output .= PMA_Util::getRadioFields(
+ 'mode', $choices, '4', true
+ );
+ $html_output .= '</fieldset>' . "\n"
+ . '</fieldset>' . "\n";
+
+ $html_output .= '<fieldset id="fieldset_change_copy_user_footer" '
+ . 'class="tblFooters">' . "\n"
+ . '<input type="submit" name="change_copy" '
+ . 'value="' . __('Go') . '" />' . "\n"
+ . '</fieldset>' . "\n"
+ . '</form>' . "\n";
+
+ return $html_output;
+}
+
+/**
+ * Provide a line with links to the relevant database and table
+ *
+ * @param string $url_dbname url database name that urlencode() string
+ * @param string $dbname database name
+ * @param string $tablename table name
+ *
+ * @return string HTML snippet
+ */
+function PMA_getLinkToDbAndTable($url_dbname, $dbname, $tablename)
+{
+ $html_output = '[ ' . __('Database')
+ . ' <a href="' . $GLOBALS['cfg']['DefaultTabDatabase']
+ . PMA_URL_getCommon(
+ array(
+ 'db' => $url_dbname,
+ 'reload' => 1
+ )
+ )
+ . '">'
+ . htmlspecialchars($dbname) . ': '
+ . PMA_Util::getTitleForTarget(
+ $GLOBALS['cfg']['DefaultTabDatabase']
+ )
+ . "</a> ]\n";
+
+ if (strlen($tablename)) {
+ $html_output .= ' [ ' . __('Table') . ' <a href="'
+ . $GLOBALS['cfg']['DefaultTabTable']
+ . PMA_URL_getCommon(
+ array(
+ 'db' => $url_dbname,
+ 'table' => $tablename,
+ 'reload' => 1,
+ )
+ )
+ . '">' . htmlspecialchars($tablename) . ': '
+ . PMA_Util::getTitleForTarget(
+ $GLOBALS['cfg']['DefaultTabTable']
+ )
+ . "</a> ]\n";
+ }
+ return $html_output;
+}
+
+/**
+ * no db name given, so we want all privs for the given user
+ * db name was given, so we want all user specific rights for this db
+ * So this function returns user rights as an array
+ *
+ * @param array $tables tables
+ * @param string $user_host_condition a where clause that containd user's host
+ * condition
+ * @param string $dbname database name
+ *
+ * @return array $db_rights database rights
+ */
+function PMA_getUserSpecificRights($tables, $user_host_condition, $dbname)
+{
+ if (! strlen($dbname)) {
+ $tables_to_search_for_users = array(
+ 'tables_priv', 'columns_priv',
+ );
+ $dbOrTableName = 'Db';
+ } else {
+ $user_host_condition .=
+ ' AND `Db`'
+ .' LIKE \''
+ . PMA_Util::sqlAddSlashes($dbname, true) . "'";
+ $tables_to_search_for_users = array('columns_priv',);
+ $dbOrTableName = 'Table_name';
+ }
+
+ $db_rights_sqls = array();
+ foreach ($tables_to_search_for_users as $table_search_in) {
+ if (in_array($table_search_in, $tables)) {
+ $db_rights_sqls[] = '
+ SELECT DISTINCT `' . $dbOrTableName .'`
+ FROM `mysql`.' . PMA_Util::backquote($table_search_in)
+ . $user_host_condition;
+ }
+ }
+
+ $user_defaults = array(
+ $dbOrTableName => '',
+ 'Grant_priv' => 'N',
+ 'privs' => array('USAGE'),
+ 'Column_priv' => true,
+ );
+
+ // for the rights
+ $db_rights = array();
+
+ $db_rights_sql = '(' . implode(') UNION (', $db_rights_sqls) . ')'
+ .' ORDER BY `' . $dbOrTableName .'` ASC';
+
+ $db_rights_result = $GLOBALS['dbi']->query($db_rights_sql);
+
+ while ($db_rights_row = $GLOBALS['dbi']->fetchAssoc($db_rights_result)) {
+ $db_rights_row = array_merge($user_defaults, $db_rights_row);
+ if (! strlen($dbname)) {
+ // only Db names in the table `mysql`.`db` uses wildcards
+ // as we are in the db specific rights display we want
+ // all db names escaped, also from other sources
+ $db_rights_row['Db'] = PMA_Util::escapeMysqlWildcards(
+ $db_rights_row['Db']
+ );
+ }
+ $db_rights[$db_rights_row[$dbOrTableName]] = $db_rights_row;
+ }
+
+ $GLOBALS['dbi']->freeResult($db_rights_result);
+
+ if (! strlen($dbname)) {
+ $sql_query = 'SELECT * FROM `mysql`.`db`'
+ . $user_host_condition . ' ORDER BY `Db` ASC';
+ } else {
+ $sql_query = 'SELECT `Table_name`,'
+ .' `Table_priv`,'
+ .' IF(`Column_priv` = _latin1 \'\', 0, 1)'
+ .' AS \'Column_priv\''
+ .' FROM `mysql`.`tables_priv`'
+ . $user_host_condition
+ .' ORDER BY `Table_name` ASC;';
+ }
+
+ $result = $GLOBALS['dbi']->query($sql_query);
+ $sql_query = '';
+
+ while ($row = $GLOBALS['dbi']->fetchAssoc($result)) {
+ if (isset($db_rights[$row[$dbOrTableName]])) {
+ $db_rights[$row[$dbOrTableName]]
+ = array_merge($db_rights[$row[$dbOrTableName]], $row);
+ } else {
+ $db_rights[$row[$dbOrTableName]] = $row;
+ }
+ if (! strlen($dbname)) {
+ // there are db specific rights for this user
+ // so we can drop this db rights
+ $db_rights[$row['Db']]['can_delete'] = true;
+ }
+ }
+ $GLOBALS['dbi']->freeResult($result);
+ return $db_rights;
+}
+
+/**
+ * Display user rights in table rows(Table specific or database specific privs)
+ *
+ * @param array $db_rights user's database rights array
+ * @param string $dbname database name
+ * @param string $hostname host name
+ * @param string $username username
+ *
+ * @return array $found_rows, $html_output
+ */
+function PMA_getHtmlForDisplayUserRightsInRows($db_rights, $dbname,
+ $hostname, $username
+) {
+ $html_output = '';
+ $found_rows = array();
+ // display rows
+ if (count($db_rights) < 1) {
+ $html_output .= '<tr class="odd">' . "\n"
+ . '<td colspan="6"><center><i>' . __('None') . '</i></center></td>' . "\n"
+ . '</tr>' . "\n";
+ } else {
+ $odd_row = true;
+ //while ($row = $GLOBALS['dbi']->fetchAssoc($res)) {
+ foreach ($db_rights as $row) {
+ $found_rows[] = (! strlen($dbname)) ? $row['Db'] : $row['Table_name'];
+
+ $html_output .= '<tr class="' . ($odd_row ? 'odd' : 'even') . '">' . "\n"
+ . '<td>'
+ . htmlspecialchars(
+ (! strlen($dbname)) ? $row['Db'] : $row['Table_name']
+ )
+ . '</td>' . "\n"
+ . '<td><code>' . "\n"
+ . ' '
+ . join(
+ ',' . "\n" . ' ',
+ PMA_extractPrivInfo($row, true)
+ ) . "\n"
+ . '</code></td>' . "\n"
+ . '<td>'
+ . ((((! strlen($dbname)) && $row['Grant_priv'] == 'Y')
+ || (strlen($dbname)
+ && in_array('Grant', explode(',', $row['Table_priv']))))
+ ? __('Yes')
+ : __('No'))
+ . '</td>' . "\n"
+ . '<td>';
+ if (! empty($row['Table_privs']) || ! empty ($row['Column_priv'])) {
+ $html_output .= __('Yes');
+ } else {
+ $html_output .= __('No');
+ }
+ $html_output .= '</td>' . "\n"
+ . '<td>';
+ $html_output .= PMA_getUserEditLink(
+ $username,
+ $hostname,
+ (! strlen($dbname)) ? $row['Db'] : $dbname,
+ (! strlen($dbname)) ? '' : $row['Table_name']
+ );
+ $html_output .= '</td>' . "\n"
+ . ' <td>';
+ if (! empty($row['can_delete'])
+ || isset($row['Table_name'])
+ && strlen($row['Table_name'])
+ ) {
+ $html_output .= PMA_getUserRevokeLink(
+ $username,
+ $hostname,
+ (! strlen($dbname)) ? $row['Db'] : $dbname,
+ (! strlen($dbname)) ? '' : $row['Table_name']
+ );
+ }
+ $html_output .= '</td>' . "\n"
+ . '</tr>' . "\n";
+ $odd_row = ! $odd_row;
+ } // end while
+ } //end if
+ return array($found_rows, $html_output);
+}
+
+/**
+ * Get a HTML table for display user's tabel specific or database specific rights
+ *
+ * @param string $username username
+ * @param string $hostname host name
+ * @param string $dbname database name
+ *
+ * @return array $html_output, $found_rows
+ */
+function PMA_getTableForDisplayAllTableSpecificRights(
+ $username, $hostname, $dbname
+) {
+ // table header
+ $html_output = PMA_URL_getHiddenInputs('', '')
+ . '<input type="hidden" name="username" '
+ . 'value="' . htmlspecialchars($username) . '" />' . "\n"
+ . '<input type="hidden" name="hostname" '
+ . 'value="' . htmlspecialchars($hostname) . '" />' . "\n"
+ . '<fieldset>' . "\n"
+ . '<legend>'
+ . (! strlen($dbname)
+ ? __('Database-specific privileges')
+ : __('Table-specific privileges')
+ )
+ . '</legend>' . "\n"
+ . '<table class="data">' . "\n"
+ . '<thead>' . "\n"
+ . '<tr><th>'
+ . (! strlen($dbname) ? __('Database') : __('Table'))
+ . '</th>' . "\n"
+ . '<th>' . __('Privileges') . '</th>' . "\n"
+ . '<th>' . __('Grant') . '</th>' . "\n"
+ . '<th>'
+ . (! strlen($dbname)
+ ? __('Table-specific privileges')
+ : __('Column-specific privileges')
+ )
+ . '</th>' . "\n"
+ . '<th colspan="2">' . __('Action') . '</th>' . "\n"
+ . '</tr>' . "\n"
+ . '</thead>' . "\n";
+
+ $user_host_condition = ' WHERE `User`'
+ . ' = \'' . PMA_Util::sqlAddSlashes($username) . "'"
+ . ' AND `Host`'
+ . ' = \'' . PMA_Util::sqlAddSlashes($hostname) . "'";
+
+ // table body
+ // get data
+
+ // we also want privielgs for this user not in table `db` but in other table
+ $tables = $GLOBALS['dbi']->fetchResult('SHOW TABLES FROM `mysql`;');
+
+ /**
+ * no db name given, so we want all privs for the given user
+ * db name was given, so we want all user specific rights for this db
+ */
+ $db_rights = PMA_getUserSpecificRights($tables, $user_host_condition, $dbname);
+
+ ksort($db_rights);
+
+ $html_output .= '<tbody>' . "\n";
+ // display rows
+ list ($found_rows, $html_out) = PMA_getHtmlForDisplayUserRightsInRows(
+ $db_rights, $dbname, $hostname, $username
+ );
+
+ $html_output .= $html_out;
+ $html_output .= '</tbody>' . "\n";
+ $html_output .='</table>' . "\n";
+
+ return array($html_output, $found_rows);
+}
+
+/**
+ * Get HTML for display select db
+ *
+ * @param array $found_rows isset($dbname)) ? $row['Db'] : $row['Table_name']
+ *
+ * @return string HTML snippet
+ */
+function PMA_getHtmlForDisplaySelectDbInEditPrivs($found_rows)
+{
+ // we already have the list of databases from libraries/common.inc.php
+ // via $pma = new PMA;
+ $pred_db_array = $GLOBALS['pma']->databases;
+
+ $databases_to_skip = array('information_schema', 'performance_schema');
+
+ $html_output = '<label for="text_dbname">'
+ . __('Add privileges on the following database:') . '</label>' . "\n";
+ if (! empty($pred_db_array)) {
+ $html_output .= '<select name="pred_dbname" class="autosubmit">' . "\n"
+ . '<option value="" selected="selected">'
+ . __('Use text field:') . '</option>' . "\n";
+ foreach ($pred_db_array as $current_db) {
+ if (in_array($current_db, $databases_to_skip)) {
+ continue;
+ }
+ $current_db_show = $current_db;
+ $current_db = PMA_Util::escapeMysqlWildcards($current_db);
+ // cannot use array_diff() once, outside of the loop,
+ // because the list of databases has special characters
+ // already escaped in $found_rows,
+ // contrary to the output of SHOW DATABASES
+ if (empty($found_rows) || ! in_array($current_db, $found_rows)) {
+ $html_output .= '<option value="'
+ . htmlspecialchars($current_db) . '">'
+ . htmlspecialchars($current_db_show) . '</option>' . "\n";
+ }
+ }
+ $html_output .= '</select>' . "\n";
+ }
+ $html_output .= '<input type="text" id="text_dbname" name="dbname" '
+ . 'required="required" />'
+ . "\n"
+ . PMA_Util::showHint(
+ __('Wildcards % and _ should be escaped with a \ to use them literally.')
+ );
+ return $html_output;
+}
+
+/**
+ * Get HTML for display table in edit privilege
+ *
+ * @param string $dbname database naame
+ * @param array $found_rows isset($dbname)) ? $row['Db'] : $row['Table_name']
+ *
+ * @return string HTML snippet
+ */
+function PMA_displayTablesInEditPrivs($dbname, $found_rows)
+{
+ $html_output = '<input type="hidden" name="dbname"
+ '. 'value="' . htmlspecialchars($dbname) . '"/>' . "\n";
+ $html_output .= '<label for="text_tablename">'
+ . __('Add privileges on the following table:') . '</label>' . "\n";
+
+ $result = @$GLOBALS['dbi']->tryQuery(
+ 'SHOW TABLES FROM ' . PMA_Util::backquote(
+ PMA_Util::unescapeMysqlWildcards($dbname)
+ ) . ';',
+ null,
+ PMA_DatabaseInterface::QUERY_STORE
+ );
+
+ if ($result) {
+ $pred_tbl_array = array();
+ while ($row = $GLOBALS['dbi']->fetchRow($result)) {
+ if (! isset($found_rows) || ! in_array($row[0], $found_rows)) {
+ $pred_tbl_array[] = $row[0];
+ }
+ }
+ $GLOBALS['dbi']->freeResult($result);
+
+ if (! empty($pred_tbl_array)) {
+ $html_output .= '<select name="pred_tablename" '
+ . 'class="autosubmit">' . "\n"
+ . '<option value="" selected="selected">' . __('Use text field')
+ . ':</option>' . "\n";
+ foreach ($pred_tbl_array as $current_table) {
+ $html_output .= '<option '
+ . 'value="' . htmlspecialchars($current_table) . '">'
+ . htmlspecialchars($current_table)
+ . '</option>' . "\n";
+ }
+ $html_output .= '</select>' . "\n";
+ }
+ }
+ $html_output .= '<input type="text" id="text_tablename" name="tablename" />'
+ . "\n";
+
+ return $html_output;
+}
+
+/**
+ * Get HTML for display the users overview
+ * (if less than 50 users, display them immediately)
+ *
+ * @param array $result ran sql query
+ * @param array $db_rights user's database rights array
+ * @param string $pmaThemeImage a image source link
+ * @param string $text_dir text directory
+ *
+ * @return string HTML snippet
+ */
+function PMA_getUsersOverview($result, $db_rights, $pmaThemeImage, $text_dir)
+{
+ while ($row = $GLOBALS['dbi']->fetchAssoc($result)) {
+ $row['privs'] = PMA_extractPrivInfo($row, true);
+ $db_rights[$row['User']][$row['Host']] = $row;
+ }
+ @$GLOBALS['dbi']->freeResult($result);
+ $user_group_count = 0;
+ if ($GLOBALS['cfgRelation']['menuswork']) {
+ $user_group_count = PMA_getUserGroupCount();
+ }
+
+ $html_output
+ = '<form name="usersForm" id="usersForm" action="server_privileges.php" '
+ . 'method="post">' . "\n"
+ . PMA_URL_getHiddenInputs('', '')
+ . '<table id="tableuserrights" class="data">' . "\n"
+ . '<thead>' . "\n"
+ . '<tr><th></th>' . "\n"
+ . '<th>' . __('User') . '</th>' . "\n"
+ . '<th>' . __('Host') . '</th>' . "\n"
+ . '<th>' . __('Password') . '</th>' . "\n"
+ . '<th>' . __('Global privileges') . ' '
+ . PMA_Util::showHint(
+ __('Note: MySQL privilege names are expressed in English')
+ )
+ . '</th>' . "\n";
+ if ($GLOBALS['cfgRelation']['menuswork']) {
+ $html_output .= '<th>' . __('User group') . '</th>' . "\n";
+ }
+ $html_output .= '<th>' . __('Grant') . '</th>' . "\n"
+ . '<th colspan="' . ($user_group_count > 0 ? '3' : '2') . '">'
+ . __('Action') . '</th>' . "\n"
+ . '</tr>' . "\n"
+ . '</thead>' . "\n";
+
+ $html_output .= '<tbody>' . "\n";
+ $html_output .= PMA_getTableBodyForUserRightsTable($db_rights);
+ $html_output .= '</tbody>'
+ . '</table>' . "\n";
+
+ $html_output .= '<div style="float:left;">'
+ . '<img class="selectallarrow"'
+ . ' src="' . $pmaThemeImage . 'arrow_' . $text_dir . '.png"'
+ . ' width="38" height="22"'
+ . ' alt="' . __('With selected:') . '" />' . "\n"
+ . '<input type="checkbox" id="usersForm_checkall" class="checkall_box" '
+ . 'title="' . __('Check All') . '" /> '
+ . '<label for="usersForm_checkall">' . __('Check All') . '</label> '
+ . '<i style="margin-left: 2em">' . __('With selected:') . '</i>' . "\n";
+
+ $html_output .= PMA_Util::getButtonOrImage(
+ 'submit_mult', 'mult_submit', 'submit_mult_export',
+ __('Export'), 'b_tblexport.png', 'export'
+ );
+ $html_output .= '<input type="hidden" name="initial" '
+ . 'value="' . (isset($_GET['initial']) ? $_GET['initial'] : '') . '" />';
+ $html_output .= '</div>'
+ . '<div class="clear_both" style="clear:both"></div>';
+
+ // add/delete user fieldset
+ $html_output .= PMA_getFieldsetForAddDeleteUser();
+ $html_output .= '</form>' . "\n";
+
+ return $html_output;
+}
+
+/**
+ * Get table body for 'tableuserrights' table in userform
+ *
+ * @param array $db_rights user's database rights array
+ *
+ * @return string HTML snippet
+ */
+function PMA_getTableBodyForUserRightsTable($db_rights)
+{
+ if ($GLOBALS['cfgRelation']['menuswork']) {
+ $users_table = PMA_Util::backquote($GLOBALS['cfg']['Server']['pmadb'])
+ . "." . PMA_Util::backquote($GLOBALS['cfg']['Server']['users']);
+ $sql_query = 'SELECT * FROM ' . $users_table;
+ $result = PMA_queryAsControlUser($sql_query, false);
+ $group_assignment = array();
+ if ($result) {
+ while ($row = $GLOBALS['dbi']->fetchAssoc($result)) {
+ $group_assignment[$row['username']] = $row['usergroup'];
+ }
+ }
+ $GLOBALS['dbi']->freeResult($result);
+
+ $user_group_count = PMA_getUserGroupCount();
+ }
+
+ $odd_row = true;
+ $index_checkbox = 0;
+ $html_output = '';
+ foreach ($db_rights as $user) {
+ ksort($user);
+ foreach ($user as $host) {
+ $index_checkbox++;
+ $html_output .= '<tr class="' . ($odd_row ? 'odd' : 'even') . '">'
+ . "\n";
+ $html_output .= '<td>'
+ . '<input type="checkbox" class="checkall" name="selected_usr[]" '
+ . 'id="checkbox_sel_users_'
+ . $index_checkbox . '" value="'
+ . htmlspecialchars($host['User'] . '&amp;#27;' . $host['Host'])
+ . '"'
+ . ' /></td>' . "\n";
+
+ $html_output .= '<td><label '
+ . 'for="checkbox_sel_users_' . $index_checkbox . '">'
+ . (empty($host['User'])
+ ? '<span style="color: #FF0000">' . __('Any') . '</span>'
+ : htmlspecialchars($host['User'])) . '</label></td>' . "\n"
+ . '<td>' . htmlspecialchars($host['Host']) . '</td>' . "\n";
+
+ $html_output .= '<td>';
+ switch ($host['Password']) {
+ case 'Y':
+ $html_output .= __('Yes');
+ break;
+ case 'N':
+ $html_output .= '<span style="color: #FF0000">' . __('No')
+ . '</span>';
+ break;
+ // this happens if this is a definition not coming from mysql.user
+ default:
+ $html_output .= '--'; // in future version, replace by "not present"
+ break;
+ } // end switch
+ $html_output .= '</td>' . "\n";
+
+ $html_output .= '<td><code>' . "\n"
+ . '' . implode(',' . "\n" . ' ', $host['privs']) . "\n"
+ . '</code></td>' . "\n";
+ if ($GLOBALS['cfgRelation']['menuswork']) {
+ $html_output .= '<td class="usrGroup">' . "\n"
+ . (isset($group_assignment[$host['User']])
+ ? $group_assignment[$host['User']]
+ : ''
+ )
+ . '</td>' . "\n";
+ }
+ $html_output .= '<td>'
+ . ($host['Grant_priv'] == 'Y' ? __('Yes') : __('No'))
+ . '</td>' . "\n";
+
+ $html_output .= '<td class="center">'
+ . PMA_getUserEditLink(
+ $host['User'],
+ $host['Host']
+ )
+ . '</td>';
+ if ($GLOBALS['cfgRelation']['menuswork'] && $user_group_count > 0) {
+ if (empty($host['User'])) {
+ $html_output .= '<td class="center"></td>';
+ } else {
+ $html_output .= '<td class="center">'
+ . PMA_getUserGroupEditLink($host['User'])
+ .'</td>';
+ }
+ }
+ $html_output .= '<td class="center">'
+ . PMA_getUserExportLink(
+ $host['User'],
+ $host['Host'],
+ isset($_GET['initial']) ? $_GET['initial'] : ''
+ )
+ . '</td>';
+ $html_output .= '</tr>';
+ $odd_row = ! $odd_row;
+ }
+ }
+ return $html_output;
+}
+
+/**
+ * Get HTML fieldset for Add/Delete user
+ *
+ * @return string HTML snippet
+ */
+function PMA_getFieldsetForAddDeleteUser()
+{
+ $html_output = '<fieldset id="fieldset_add_user">' . "\n";
+ $html_output .= '<a href="server_privileges.php'
+ . PMA_URL_getCommon(array('adduser' => 1))
+ . '" class="ajax">' . "\n"
+ . PMA_Util::getIcon('b_usradd.png')
+ . ' ' . __('Add user') . '</a>' . "\n";
+ $html_output .= '</fieldset>' . "\n";
+
+ $html_output .= '<fieldset id="fieldset_delete_user">'
+ . '<legend>' . "\n"
+ . PMA_Util::getIcon('b_usrdrop.png')
+ . ' ' . __('Remove selected users') . '' . "\n"
+ . '</legend>' . "\n";
+
+ $html_output .= '<input type="hidden" name="mode" value="2" />' . "\n"
+ . '('
+ . __(
+ 'Revoke all active privileges from the users '
+ . 'and delete them afterwards.'
+ )
+ . ')'
+ . '<br />' . "\n";
+
+ $html_output .= '<input type="checkbox" '
+ . 'title="'
+ . __('Drop the databases that have the same names as the users.')
+ . '" '
+ . 'name="drop_users_db" id="checkbox_drop_users_db" />' . "\n";
+
+ $html_output .= '<label for="checkbox_drop_users_db" '
+ . 'title="'
+ . __('Drop the databases that have the same names as the users.')
+ . '">' . "\n"
+ . ' '
+ . __('Drop the databases that have the same names as the users.')
+ . "\n"
+ . '</label>' . "\n"
+ . '</fieldset>' . "\n";
+
+ $html_output .= '<fieldset id="fieldset_delete_user_footer" class="tblFooters">'
+ . "\n";
+ $html_output .= '<input type="submit" name="delete" '
+ . 'value="' . __('Go') . '" id="buttonGo" '
+ . 'class="ajax"/>' . "\n";
+
+ $html_output .= '</fieldset>' . "\n";
+
+ return $html_output;
+}
+
+/**
+ * Get HTML for Displays the initials
+ *
+ * @param array $array_initials array for all initials, even non A-Z
+ *
+ * @return string HTML snippet
+ */
+function PMA_getHtmlForDisplayTheInitials($array_initials)
+{
+ // initialize to false the letters A-Z
+ for ($letter_counter = 1; $letter_counter < 27; $letter_counter++) {
+ if (! isset($array_initials[chr($letter_counter + 64)])) {
+ $array_initials[chr($letter_counter + 64)] = false;
+ }
+ }
+
+ $initials = $GLOBALS['dbi']->tryQuery(
+ 'SELECT DISTINCT UPPER(LEFT(`User`,1)) FROM `user` ORDER BY `User` ASC',
+ null,
+ PMA_DatabaseInterface::QUERY_STORE
+ );
+ while (list($tmp_initial) = $GLOBALS['dbi']->fetchRow($initials)) {
+ $array_initials[$tmp_initial] = true;
+ }
+
+ // Display the initials, which can be any characters, not
+ // just letters. For letters A-Z, we add the non-used letters
+ // as greyed out.
+
+ uksort($array_initials, "strnatcasecmp");
+
+ $html_output = '<table id="initials_table" <cellspacing="5">'
+ . '<tr>';
+ foreach ($array_initials as $tmp_initial => $initial_was_found) {
+ if (! empty($tmp_initial)) {
+ if ($initial_was_found) {
+ $html_output .= '<td>'
+ . '<a class="ajax"'
+ . ' href="server_privileges.php'
+ . PMA_URL_getCommon(array('initial' => $tmp_initial))
+ . '">' . $tmp_initial
+ . '</a>'
+ . '</td>' . "\n";
+ } else {
+ $html_output .= '<td>' . $tmp_initial . '</td>';
+ }
+ }
+ }
+ $html_output .= '<td>'
+ . '<a href="server_privileges.php'
+ . PMA_URL_getCommon(array('showall' => 1))
+ . '" class="nowrap">[' . __('Show all') . ']</a></td>' . "\n";
+ $html_output .= '</tr></table>';
+
+ return $html_output;
+}
+
+/**
+ * Get the database rigths array for Display user overview
+ *
+ * @return array $db_rights database rights array
+ */
+function PMA_getDbRightsForUserOverview()
+{
+ // we also want users not in table `user` but in other table
+ $tables = $GLOBALS['dbi']->fetchResult('SHOW TABLES FROM `mysql`;');
+
+ $tables_to_search_for_users = array(
+ 'user', 'db', 'tables_priv', 'columns_priv', 'procs_priv',
+ );
+
+ $db_rights_sqls = array();
+ foreach ($tables_to_search_for_users as $table_search_in) {
+ if (in_array($table_search_in, $tables)) {
+ $db_rights_sqls[] = 'SELECT DISTINCT `User`, `Host` FROM `mysql`.`'
+ . $table_search_in . '` '
+ . (isset($_GET['initial'])
+ ? PMA_rangeOfUsers($_GET['initial'])
+ : '');
+ }
+ }
+ $user_defaults = array(
+ 'User' => '',
+ 'Host' => '%',
+ 'Password' => '?',
+ 'Grant_priv' => 'N',
+ 'privs' => array('USAGE'),
+ );
+
+ // for the rights
+ $db_rights = array();
+
+ $db_rights_sql = '(' . implode(') UNION (', $db_rights_sqls) . ')'
+ .' ORDER BY `User` ASC, `Host` ASC';
+
+ $db_rights_result = $GLOBALS['dbi']->query($db_rights_sql);
+
+ while ($db_rights_row = $GLOBALS['dbi']->fetchAssoc($db_rights_result)) {
+ $db_rights_row = array_merge($user_defaults, $db_rights_row);
+ $db_rights[$db_rights_row['User']][$db_rights_row['Host']]
+ = $db_rights_row;
+ }
+ $GLOBALS['dbi']->freeResult($db_rights_result);
+ ksort($db_rights);
+
+ return $db_rights;
+}
+
+/**
+ * Delete user and get message and sql query for delete user in privileges
+ *
+ * @param string $queries queries
+ *
+ * @return PMA_message
+ */
+function PMA_deleteUser($queries)
+{
+ if (empty($queries)) {
+ $message = PMA_Message::error(__('No users selected for deleting!'));
+ } else {
+ if ($_REQUEST['mode'] == 3) {
+ $queries[] = '# ' . __('Reloading the privileges') . ' …';
+ $queries[] = 'FLUSH PRIVILEGES;';
+ }
+ $drop_user_error = '';
+ foreach ($queries as $sql_query) {
+ if ($sql_query{0} != '#') {
+ if (! $GLOBALS['dbi']->tryQuery($sql_query, $GLOBALS['userlink'])) {
+ $drop_user_error .= $GLOBALS['dbi']->getError() . "\n";
+ }
+ }
+ }
+ // tracking sets this, causing the deleted db to be shown in navi
+ unset($GLOBALS['db']);
+
+ $sql_query = join("\n", $queries);
+ if (! empty($drop_user_error)) {
+ $message = PMA_Message::rawError($drop_user_error);
+ } else {
+ $message = PMA_Message::success(
+ __('The selected users have been deleted successfully.')
+ );
+ }
+ }
+ return array($sql_query, $message);
+}
+
+/**
+ * Update the privileges and return the success or error message
+ *
+ * @param string $username username
+ * @param string $hostname host name
+ * @param string $tablename table name
+ * @param string $dbname database name
+ *
+ * @return PMA_message success message or error message for update
+ */
+function PMA_updatePrivileges($username, $hostname, $tablename, $dbname)
+{
+ $db_and_table = PMA_wildcardEscapeForGrant($dbname, $tablename);
+
+ $sql_query0 = 'REVOKE ALL PRIVILEGES ON ' . $db_and_table
+ . ' FROM \'' . PMA_Util::sqlAddSlashes($username)
+ . '\'@\'' . PMA_Util::sqlAddSlashes($hostname) . '\';';
+
+ if (! isset($_POST['Grant_priv']) || $_POST['Grant_priv'] != 'Y') {
+ $sql_query1 = 'REVOKE GRANT OPTION ON ' . $db_and_table
+ . ' FROM \'' . PMA_Util::sqlAddSlashes($username) . '\'@\''
+ . PMA_Util::sqlAddSlashes($hostname) . '\';';
+ } else {
+ $sql_query1 = '';
+ }
+
+ // Should not do a GRANT USAGE for a table-specific privilege, it
+ // causes problems later (cannot revoke it)
+ if (! (strlen($tablename) && 'USAGE' == implode('', PMA_extractPrivInfo()))) {
+ $sql_query2 = 'GRANT ' . join(', ', PMA_extractPrivInfo())
+ . ' ON ' . $db_and_table
+ . ' TO \'' . PMA_Util::sqlAddSlashes($username) . '\'@\''
+ . PMA_Util::sqlAddSlashes($hostname) . '\'';
+
+ if ((isset($_POST['Grant_priv']) && $_POST['Grant_priv'] == 'Y')
+ || (! strlen($dbname)
+ && (isset($_POST['max_questions']) || isset($_POST['max_connections'])
+ || isset($_POST['max_updates'])
+ || isset($_POST['max_user_connections'])))
+ ) {
+ $sql_query2 .= PMA_getWithClauseForAddUserAndUpdatePrivs();
+ }
+ $sql_query2 .= ';';
+ }
+ if (! $GLOBALS['dbi']->tryQuery($sql_query0)) {
+ // This might fail when the executing user does not have
+ // ALL PRIVILEGES himself.
+ // See https://sourceforge.net/p/phpmyadmin/bugs/3270/
+ $sql_query0 = '';
+ }
+ if (! empty($sql_query1) && ! $GLOBALS['dbi']->tryQuery($sql_query1)) {
+ // this one may fail, too...
+ $sql_query1 = '';
+ }
+ if (! empty($sql_query2)) {
+ $GLOBALS['dbi']->query($sql_query2);
+ } else {
+ $sql_query2 = '';
+ }
+ $sql_query = $sql_query0 . ' ' . $sql_query1 . ' ' . $sql_query2;
+ $message = PMA_Message::success(__('You have updated the privileges for %s.'));
+ $message->addParam(
+ '\'' . htmlspecialchars($username)
+ . '\'@\'' . htmlspecialchars($hostname) . '\''
+ );
+
+ return array($sql_query, $message);
+}
+
+/**
+ * Get List of information: Changes / copies a user
+ *
+ * @return array()
+ */
+function PMA_getDataForChangeOrCopyUser()
+{
+ $row = null;
+ $queries = null;
+ $password = null;
+
+ if (isset($_REQUEST['change_copy'])) {
+ $user_host_condition = ' WHERE `User` = '
+ . "'". PMA_Util::sqlAddSlashes($_REQUEST['old_username']) . "'"
+ . ' AND `Host` = '
+ . "'" . PMA_Util::sqlAddSlashes($_REQUEST['old_hostname']) . "';";
+ $row = $GLOBALS['dbi']->fetchSingleRow(
+ 'SELECT * FROM `mysql`.`user` ' . $user_host_condition
+ );
+ if (! $row) {
+ $response = PMA_Response::getInstance();
+ $response->addHTML(
+ PMA_Message::notice(__('No user found.'))->getDisplay()
+ );
+ unset($_REQUEST['change_copy']);
+ } else {
+ extract($row, EXTR_OVERWRITE);
+ // Recent MySQL versions have the field "Password" in mysql.user,
+ // so the previous extract creates $Password but this script
+ // uses $password
+ if (! isset($password) && isset($Password)) {
+ $password = $Password;
+ }
+ $queries = array();
+ }
+ }
+
+ return array($queries, $password);
+}
+
+/**
+ * Update Data for information: Deletes users
+ *
+ * @param array $queries queries array
+ *
+ * @return array
+ */
+function PMA_getDataForDeleteUsers($queries)
+{
+ if (isset($_REQUEST['change_copy'])) {
+ $selected_usr = array(
+ $_REQUEST['old_username'] . '&amp;#27;' . $_REQUEST['old_hostname']
+ );
+ } else {
+ $selected_usr = $_REQUEST['selected_usr'];
+ $queries = array();
+ }
+ foreach ($selected_usr as $each_user) {
+ list($this_user, $this_host) = explode('&amp;#27;', $each_user);
+ $queries[] = '# '
+ . sprintf(
+ __('Deleting %s'),
+ '\'' . $this_user . '\'@\'' . $this_host . '\''
+ )
+ . ' ...';
+ $queries[] = 'DROP USER \''
+ . PMA_Util::sqlAddSlashes($this_user)
+ . '\'@\'' . PMA_Util::sqlAddSlashes($this_host) . '\';';
+
+ if (isset($_REQUEST['drop_users_db'])) {
+ $queries[] = 'DROP DATABASE IF EXISTS '
+ . PMA_Util::backquote($this_user) . ';';
+ $GLOBALS['reload'] = true;
+ }
+ }
+ return $queries;
+}
+
+/**
+ * update Message For Reload
+ *
+ * @return array
+ */
+function PMA_updateMessageForReload()
+{
+ $message = null;
+ if (isset($_REQUEST['flush_privileges'])) {
+ $sql_query = 'FLUSH PRIVILEGES;';
+ $GLOBALS['dbi']->query($sql_query);
+ $message = PMA_Message::success(
+ __('The privileges were reloaded successfully.')
+ );
+ }
+
+ if (isset($_REQUEST['validate_username'])) {
+ $message = PMA_Message::success();
+ }
+
+ return $message;
+}
+
+/**
+ * update Data For Queries from queries_for_display
+ *
+ * @param array $queries queries array
+ * @param array $queries_for_display queries arry for display
+ *
+ * @return null
+ */
+function PMA_getDataForQueries($queries, $queries_for_display)
+{
+ $tmp_count = 0;
+ foreach ($queries as $sql_query) {
+ if ($sql_query{0} != '#') {
+ $GLOBALS['dbi']->query($sql_query);
+ }
+ // when there is a query containing a hidden password, take it
+ // instead of the real query sent
+ if (isset($queries_for_display[$tmp_count])) {
+ $queries[$tmp_count] = $queries_for_display[$tmp_count];
+ }
+ $tmp_count++;
+ }
+
+ return $queries;
+}
+
+/**
+ * update Data for information: Adds a user
+ *
+ * @param string $dbname db name
+ * @param string $username user name
+ * @param string $hostname host name
+ * @param string $password password
+ * @param bool $is_menuwork is_menuwork set?
+ *
+ * @return array
+ */
+function PMA_addUser(
+ $dbname, $username, $hostname,
+ $password, $is_menuwork
+) {
+ $_add_user_error = false;
+ $message = null;
+ $queries = null;
+ $queries_for_display = null;
+ $sql_query = null;
+ if (isset($_REQUEST['adduser_submit']) || isset($_REQUEST['change_copy'])) {
+ $sql_query = '';
+ if ($_POST['pred_username'] == 'any') {
+ $username = '';
+ }
+ switch ($_POST['pred_hostname']) {
+ case 'any':
+ $hostname = '%';
+ break;
+ case 'localhost':
+ $hostname = 'localhost';
+ break;
+ case 'hosttable':
+ $hostname = '';
+ break;
+ case 'thishost':
+ $_user_name = $GLOBALS['dbi']->fetchValue('SELECT USER()');
+ $hostname = substr($_user_name, (strrpos($_user_name, '@') + 1));
+ unset($_user_name);
+ break;
+ }
+ $sql = "SELECT '1' FROM `mysql`.`user`"
+ . " WHERE `User` = '" . PMA_Util::sqlAddSlashes($username) . "'"
+ . " AND `Host` = '" . PMA_Util::sqlAddSlashes($hostname) . "';";
+ if ($GLOBALS['dbi']->fetchValue($sql) == 1) {
+ $message = PMA_Message::error(__('The user %s already exists!'));
+ $message->addParam(
+ '[em]\'' . $username . '\'@\'' . $hostname . '\'[/em]'
+ );
+ $_REQUEST['adduser'] = true;
+ $_add_user_error = true;
+ } else {
+ list($create_user_real, $create_user_show, $real_sql_query, $sql_query)
+ = PMA_getSqlQueriesForDisplayAndAddUser(
+ $username, $hostname, (isset ($password) ? $password : '')
+ );
+
+ if (empty($_REQUEST['change_copy'])) {
+ $_error = false;
+
+ if (isset($create_user_real)) {
+ if (! $GLOBALS['dbi']->tryQuery($create_user_real)) {
+ $_error = true;
+ }
+ $sql_query = $create_user_show . $sql_query;
+ }
+ list($sql_query, $message) = PMA_addUserAndCreateDatabase(
+ $_error, $real_sql_query, $sql_query, $username, $hostname,
+ isset($dbname) ? $dbname : null
+ );
+ if (! empty($_REQUEST['userGroup']) && $is_menuwork) {
+ PMA_setUserGroup($GLOBALS['username'], $_REQUEST['userGroup']);
+ }
+
+ } else {
+ if (isset($create_user_real)) {
+ $queries[] = $create_user_real;
+ }
+ $queries[] = $real_sql_query;
+ // we put the query containing the hidden password in
+ // $queries_for_display, at the same position occupied
+ // by the real query in $queries
+ $tmp_count = count($queries);
+ if (isset($create_user_real)) {
+ $queries_for_display[$tmp_count - 2] = $create_user_show;
+ }
+ $queries_for_display[$tmp_count - 1] = $sql_query;
+ }
+ unset($res, $real_sql_query);
+ }
+ }
+
+ return array(
+ $message, $queries, $queries_for_display, $sql_query, $_add_user_error
+ );
+}
+
+/**
+ * Update DB information: DB, Table, isWildcard
+ *
+ * @return array
+ */
+function PMA_getDataForDBInfo()
+{
+ $username = null;
+ $hostname = null;
+ $dbname = null;
+ $tablename = null;
+ $db_and_table = null;
+ $dbname_is_wildcard = null;
+
+ if (isset ($_REQUEST['username'])) {
+ $username = $_REQUEST['username'];
+ }
+ if (isset ($_REQUEST['hostname'])) {
+ $hostname = $_REQUEST['hostname'];
+ }
+ /**
+ * Checks if a dropdown box has been used for selecting a database / table
+ */
+ if (PMA_isValid($_REQUEST['pred_tablename'])) {
+ $tablename = $_REQUEST['pred_tablename'];
+ } elseif (PMA_isValid($_REQUEST['tablename'])) {
+ $tablename = $_REQUEST['tablename'];
+ } else {
+ unset($tablename);
+ }
+
+ if (PMA_isValid($_REQUEST['pred_dbname'])) {
+ $dbname = $_REQUEST['pred_dbname'];
+ unset($pred_dbname);
+ } elseif (PMA_isValid($_REQUEST['dbname'])) {
+ $dbname = $_REQUEST['dbname'];
+ } else {
+ unset($dbname);
+ unset($tablename);
+ }
+
+ if (isset($dbname)) {
+ $unescaped_db = PMA_Util::unescapeMysqlWildcards($dbname);
+ $db_and_table = PMA_Util::backquote($unescaped_db) . '.';
+ if (isset($tablename)) {
+ $db_and_table .= PMA_Util::backquote($tablename);
+ } else {
+ $db_and_table .= '*';
+ }
+ } else {
+ $db_and_table = '*.*';
+ }
+
+ // check if given $dbname is a wildcard or not
+ if (isset($dbname)) {
+ //if (preg_match('/\\\\(?:_|%)/i', $dbname)) {
+ if (preg_match('/(?<!\\\\)(?:_|%)/i', $dbname)) {
+ $dbname_is_wildcard = true;
+ } else {
+ $dbname_is_wildcard = false;
+ }
+ }
+
+ return array(
+ $username, $hostname,
+ isset($dbname)? $dbname : null,
+ isset($tablename)? $tablename : null,
+ $db_and_table,
+ $dbname_is_wildcard,
+ );
+}
+
+/**
+ * Get title and textarea for export user definition in Privileges
+ *
+ * @param string $username username
+ * @param string $hostname host name
+ *
+ * @return array ($title, $export)
+ */
+function PMA_getListForExportUserDefinition($username, $hostname)
+{
+ $export = '<textarea class="export" cols="' . $GLOBALS['cfg']['TextareaCols']
+ . '" rows="' . $GLOBALS['cfg']['TextareaRows'] . '">';
+
+ if (isset($_REQUEST['selected_usr'])) {
+ // export privileges for selected users
+ $title = __('Privileges');
+ foreach ($_REQUEST['selected_usr'] as $export_user) {
+ $export_username = substr($export_user, 0, strpos($export_user, '&'));
+ $export_hostname = substr($export_user, strrpos($export_user, ';') + 1);
+ $export .= '# '
+ . sprintf(
+ __('Privileges for %s'),
+ '`' . htmlspecialchars($export_username)
+ . '`@`' . htmlspecialchars($export_hostname) . '`'
+ )
+ . "\n\n";
+ $export .= PMA_getGrants($export_username, $export_hostname) . "\n";
+ }
+ } else {
+ // export privileges for a single user
+ $title = __('User') . ' `' . htmlspecialchars($username)
+ . '`@`' . htmlspecialchars($hostname) . '`';
+ $export .= PMA_getGrants($username, $hostname);
+ }
+ // remove trailing whitespace
+ $export = trim($export);
+
+ $export .= '</textarea>';
+
+ return array($title, $export);
+}
+
+/**
+ * Get HTML for display Add userfieldset
+ *
+ * @return string html output
+ */
+function PMA_getAddUserHtmlFieldset()
+{
+ return '<fieldset id="fieldset_add_user">' . "\n"
+ . '<a href="server_privileges.php'
+ . PMA_URL_getCommon(array('adduser' => 1))
+ . '" class="ajax">' . "\n"
+ . PMA_Util::getIcon('b_usradd.png')
+ . ' ' . __('Add user') . '</a>' . "\n"
+ . '</fieldset>' . "\n";
+}
+
+/**
+ * Get HTML header for display User's properties
+ *
+ * @param boolean $dbname_is_wildcard whether database name is wildcard or not
+ * @param string $url_dbname url database name that urlencode() string
+ * @param string $dbname database name
+ * @param string $username username
+ * @param string $hostname host name
+ * @param string $tablename table name
+ *
+ * @return string $html_output
+ */
+function PMA_getHtmlHeaderForDisplayUserProperties(
+ $dbname_is_wildcard, $url_dbname, $dbname, $username, $hostname, $tablename
+) {
+ $html_output = '<h2>' . "\n"
+ . PMA_Util::getIcon('b_usredit.png')
+ . __('Edit Privileges:') . ' '
+ . __('User');
+
+ if (! empty($dbname)) {
+ $html_output .= ' <i><a href="server_privileges.php'
+ . PMA_URL_getCommon(
+ array(
+ 'username' => $username,
+ 'hostname' => $hostname,
+ 'dbname' => '',
+ 'tablename' => '',
+ )
+ )
+ . '">\'' . htmlspecialchars($username)
+ . '\'@\'' . htmlspecialchars($hostname)
+ . '\'</a></i>' . "\n";
+
+ $html_output .= ' - ';
+ $html_output .= $dbname_is_wildcard ? __('Databases') : __('Database');
+ if (! empty($_REQUEST['tablename'])) {
+ $html_output .= ' <i><a href="server_privileges.php'
+ . PMA_URL_getCommon(
+ array(
+ 'username' => $username,
+ 'hostname' => $hostname,
+ 'dbname' => $url_dbname,
+ 'tablename' => '',
+ )
+ )
+ . '">' . htmlspecialchars($dbname)
+ . '</a></i>';
+
+ $html_output .= ' - ' . __('Table')
+ . ' <i>' . htmlspecialchars($tablename) . '</i>';
+ } else {
+ $html_output .= ' <i>' . htmlspecialchars($dbname) . '</i>';
+ }
+
+ } else {
+ $html_output .= ' <i>\'' . htmlspecialchars($username)
+ . '\'@\'' . htmlspecialchars($hostname)
+ . '\'</i>' . "\n";
+
+ }
+ $html_output .= '</h2>' . "\n";
+
+ return $html_output;
+}
+
+/**
+ * Get HTML snippet for display user overview page
+ *
+ * @param string $pmaThemeImage a image source link
+ * @param string $text_dir text directory
+ *
+ * @return string $html_output
+ */
+function PMA_getHtmlForDisplayUserOverviewPage($pmaThemeImage, $text_dir)
+{
+ $html_output = '<h2>' . "\n"
+ . PMA_Util::getIcon('b_usrlist.png')
+ . __('Users overview') . "\n"
+ . '</h2>' . "\n";
+
+ $sql_query = 'SELECT *,' .
+ " IF(`Password` = _latin1 '', 'N', 'Y') AS 'Password'" .
+ ' FROM `mysql`.`user`';
+
+ $sql_query .= (isset($_REQUEST['initial'])
+ ? PMA_rangeOfUsers($_REQUEST['initial'])
+ : '');
+
+ $sql_query .= ' ORDER BY `User` ASC, `Host` ASC;';
+ $res = $GLOBALS['dbi']->tryQuery(
+ $sql_query, null, PMA_DatabaseInterface::QUERY_STORE
+ );
+
+ if (! $res) {
+ // the query failed! This may have two reasons:
+ // - the user does not have enough privileges
+ // - the privilege tables use a structure of an earlier version.
+ // so let's try a more simple query
+
+ $sql_query = 'SELECT * FROM `mysql`.`user`';
+ $res = $GLOBALS['dbi']->tryQuery(
+ $sql_query, null, PMA_DatabaseInterface::QUERY_STORE
+ );
+
+ if (! $res) {
+ $html_output .= PMA_Message::error(__('No Privileges'))->getDisplay();
+ $GLOBALS['dbi']->freeResult($res);
+ unset($res);
+ } else {
+ // This message is hardcoded because I will replace it by
+ // a automatic repair feature soon.
+ $raw = 'Your privilege table structure seems to be older than'
+ . ' this MySQL version!<br />'
+ . 'Please run the <code>mysql_upgrade</code> command'
+ . '(<code>mysql_fix_privilege_tables</code> on older systems)'
+ . ' that should be included in your MySQL server distribution'
+ . ' to solve this problem!';
+ $html_output .= PMA_Message::rawError($raw)->getDisplay();
+ }
+ } else {
+ $db_rights = PMA_getDbRightsForUserOverview();
+ // for all initials, even non A-Z
+ $array_initials = array();
+
+ /**
+ * Displays the initials
+ * Also not necassary if there is less than 20 privileges
+ */
+ if ($GLOBALS['dbi']->numRows($res) > 20 ) {
+ $html_output .= PMA_getHtmlForDisplayTheInitials($array_initials);
+ }
+
+ /**
+ * Display the user overview
+ * (if less than 50 users, display them immediately)
+ */
+ if (isset($_REQUEST['initial'])
+ || isset($_REQUEST['showall'])
+ || $GLOBALS['dbi']->numRows($res) < 50
+ ) {
+ $html_output .= PMA_getUsersOverview(
+ $res, $db_rights, $pmaThemeImage, $text_dir
+ );
+ } else {
+ $html_output .= PMA_getAddUserHtmlFieldset();
+ } // end if (display overview)
+
+ if (! $GLOBALS['is_ajax_request']
+ || ! empty($_REQUEST['ajax_page_request'])
+ ) {
+ $flushnote = new PMA_Message(
+ __(
+ 'Note: phpMyAdmin gets the users\' privileges directly '
+ . 'from MySQL\'s privilege tables. The content of these tables '
+ . 'may differ from the privileges the server uses, '
+ . 'if they have been changed manually. In this case, '
+ . 'you should %sreload the privileges%s before you continue.'
+ ),
+ PMA_Message::NOTICE
+ );
+ $flushLink = '<a href="server_privileges.php'
+ . PMA_URL_getCommon(array('flush_privileges' => 1))
+ . '" id="reload_privileges_anchor">';
+ $flushnote->addParam(
+ $flushLink,
+ false
+ );
+ $flushnote->addParam('</a>', false);
+ $html_output .= $flushnote->getDisplay();
+ }
+ }
+
+ return $html_output;
+}
+
+/**
+ * Get HTML snippet for display user properties
+ *
+ * @param boolean $dbname_is_wildcard whether database name is wildcard or not
+ * @param string $url_dbname url database name that urlencode() string
+ * @param string $username username
+ * @param string $hostname host name
+ * @param string $dbname database name
+ * @param string $tablename table name
+ *
+ * @return string $html_output
+ */
+function PMA_getHtmlForDisplayUserProperties($dbname_is_wildcard,$url_dbname,
+ $username, $hostname, $dbname, $tablename
+) {
+ $html_output = PMA_getHtmlHeaderForDisplayUserProperties(
+ $dbname_is_wildcard, $url_dbname, $dbname, $username, $hostname, $tablename
+ );
+
+ $sql = "SELECT '1' FROM `mysql`.`user`"
+ . " WHERE `User` = '" . PMA_Util::sqlAddSlashes($username) . "'"
+ . " AND `Host` = '" . PMA_Util::sqlAddSlashes($hostname) . "';";
+
+ $user_does_not_exists = (bool) ! $GLOBALS['dbi']->fetchValue($sql);
+
+ if ($user_does_not_exists) {
+ $html_output .= PMA_Message::error(
+ __('The selected user was not found in the privilege table.')
+ )->getDisplay();
+ $html_output .= PMA_getHtmlForDisplayLoginInformationFields();
+ //exit;
+ }
+
+ $class = ' class="ajax"';
+ $_params = array(
+ 'username' => $username,
+ 'hostname' => $hostname,
+ );
+ if (strlen($dbname)) {
+ $_params['dbname'] = $dbname;
+ if (strlen($tablename)) {
+ $_params['tablename'] = $tablename;
+ }
+ }
+
+ $html_output .= '<form' . $class . ' name="usersForm" id="addUsersForm"'
+ . ' action="server_privileges.php" method="post">' . "\n";
+ $html_output .= PMA_URL_getHiddenInputs($_params);
+ $html_output .= PMA_getHtmlToDisplayPrivilegesTable(
+ PMA_ifSetOr($dbname, '*', 'length'),
+ PMA_ifSetOr($tablename, '*', 'length')
+ );
+
+ $html_output .= '</form>' . "\n";
+
+ if (! strlen($tablename) && empty($dbname_is_wildcard)) {
+
+ // no table name was given, display all table specific rights
+ // but only if $dbname contains no wildcards
+
+ $html_output .= '<form action="server_privileges.php" '
+ . 'id="db_or_table_specific_priv" method="post">' . "\n";
+
+ // unescape wildcards in dbname at table level
+ $unescaped_db = PMA_Util::unescapeMysqlWildcards($dbname);
+ list($html_rightsTable, $found_rows)
+ = PMA_getTableForDisplayAllTableSpecificRights(
+ $username, $hostname, $unescaped_db
+ );
+ $html_output .= $html_rightsTable;
+
+ if (! strlen($dbname)) {
+ // no database name was given, display select db
+ $html_output .= PMA_getHtmlForDisplaySelectDbInEditPrivs($found_rows);
+
+ } else {
+ $html_output .= PMA_displayTablesInEditPrivs($dbname, $found_rows);
+ }
+ $html_output .= '</fieldset>' . "\n";
+
+ $html_output .= '<fieldset class="tblFooters">' . "\n"
+ . ' <input type="submit" value="' . __('Go') . '" />'
+ . '</fieldset>' . "\n"
+ . '</form>' . "\n";
+ }
+
+ // Provide a line with links to the relevant database and table
+ if (strlen($dbname) && empty($dbname_is_wildcard)) {
+ $html_output .= PMA_getLinkToDbAndTable($url_dbname, $dbname, $tablename);
+
+ }
+
+ if (! strlen($dbname) && ! $user_does_not_exists) {
+ //change login information
+ $html_output .= PMA_getHtmlForChangePassword($username, $hostname);
+ $html_output .= PMA_getChangeLoginInformationHtmlForm($username, $hostname);
+ }
+
+ return $html_output;
+}
+
+/**
+ * Get queries for Table privileges to change or copy user
+ *
+ * @param string $user_host_condition user host condition to
+ select relevent table privileges
+ * @param array $queries queries array
+ * @param string $username username
+ * @param string $hostname host name
+ *
+ * @return array $queries
+ */
+function PMA_getTablePrivsQueriesForChangeOrCopyUser($user_host_condition,
+ $queries, $username, $hostname
+) {
+ $res = $GLOBALS['dbi']->query(
+ 'SELECT `Db`, `Table_name`, `Table_priv` FROM `mysql`.`tables_priv`'
+ . $user_host_condition,
+ $GLOBALS['userlink'],
+ PMA_DatabaseInterface::QUERY_STORE
+ );
+ while ($row = $GLOBALS['dbi']->fetchAssoc($res)) {
+
+ $res2 = $GLOBALS['dbi']->query(
+ 'SELECT `Column_name`, `Column_priv`'
+ .' FROM `mysql`.`columns_priv`'
+ .' WHERE `User`'
+ .' = \'' . PMA_Util::sqlAddSlashes($_REQUEST['old_username']) . "'"
+ .' AND `Host`'
+ .' = \'' . PMA_Util::sqlAddSlashes($_REQUEST['old_username']) . '\''
+ .' AND `Db`'
+ .' = \'' . PMA_Util::sqlAddSlashes($row['Db']) . "'"
+ .' AND `Table_name`'
+ .' = \'' . PMA_Util::sqlAddSlashes($row['Table_name']) . "'"
+ .';',
+ null,
+ PMA_DatabaseInterface::QUERY_STORE
+ );
+
+ $tmp_privs1 = PMA_extractPrivInfo($row);
+ $tmp_privs2 = array(
+ 'Select' => array(),
+ 'Insert' => array(),
+ 'Update' => array(),
+ 'References' => array()
+ );
+
+ while ($row2 = $GLOBALS['dbi']->fetchAssoc($res2)) {
+ $tmp_array = explode(',', $row2['Column_priv']);
+ if (in_array('Select', $tmp_array)) {
+ $tmp_privs2['Select'][] = $row2['Column_name'];
+ }
+ if (in_array('Insert', $tmp_array)) {
+ $tmp_privs2['Insert'][] = $row2['Column_name'];
+ }
+ if (in_array('Update', $tmp_array)) {
+ $tmp_privs2['Update'][] = $row2['Column_name'];
+ }
+ if (in_array('References', $tmp_array)) {
+ $tmp_privs2['References'][] = $row2['Column_name'];
+ }
+ }
+ if (count($tmp_privs2['Select']) > 0 && ! in_array('SELECT', $tmp_privs1)) {
+ $tmp_privs1[] = 'SELECT (`' . join('`, `', $tmp_privs2['Select']) . '`)';
+ }
+ if (count($tmp_privs2['Insert']) > 0 && ! in_array('INSERT', $tmp_privs1)) {
+ $tmp_privs1[] = 'INSERT (`' . join('`, `', $tmp_privs2['Insert']) . '`)';
+ }
+ if (count($tmp_privs2['Update']) > 0 && ! in_array('UPDATE', $tmp_privs1)) {
+ $tmp_privs1[] = 'UPDATE (`' . join('`, `', $tmp_privs2['Update']) . '`)';
+ }
+ if (count($tmp_privs2['References']) > 0
+ && ! in_array('REFERENCES', $tmp_privs1)
+ ) {
+ $tmp_privs1[]
+ = 'REFERENCES (`' . join('`, `', $tmp_privs2['References']) . '`)';
+ }
+
+ $queries[] = 'GRANT ' . join(', ', $tmp_privs1)
+ . ' ON ' . PMA_Util::backquote($row['Db']) . '.'
+ . PMA_Util::backquote($row['Table_name'])
+ . ' TO \'' . PMA_Util::sqlAddSlashes($username)
+ . '\'@\'' . PMA_Util::sqlAddSlashes($hostname) . '\''
+ . (in_array('Grant', explode(',', $row['Table_priv']))
+ ? ' WITH GRANT OPTION;'
+ : ';');
+ }
+ return $queries;
+}
+
+/**
+ * Get queries for database specific privileges for change or copy user
+ *
+ * @param array $queries queries array with string
+ * @param string $username username
+ * @param string $hostname host name
+ *
+ * @return array $queries
+ */
+function PMA_getDbSpecificPrivsQueriesForChangeOrCopyUser(
+ $queries, $username, $hostname
+) {
+ $user_host_condition = ' WHERE `User`'
+ .' = \'' . PMA_Util::sqlAddSlashes($_REQUEST['old_username']) . "'"
+ .' AND `Host`'
+ .' = \'' . PMA_Util::sqlAddSlashes($_REQUEST['old_hostname']) . '\';';
+
+ $res = $GLOBALS['dbi']->query(
+ 'SELECT * FROM `mysql`.`db`' . $user_host_condition
+ );
+
+ while ($row = $GLOBALS['dbi']->fetchAssoc($res)) {
+ $queries[] = 'GRANT ' . join(', ', PMA_extractPrivInfo($row))
+ .' ON ' . PMA_Util::backquote($row['Db']) . '.*'
+ .' TO \'' . PMA_Util::sqlAddSlashes($username)
+ . '\'@\'' . PMA_Util::sqlAddSlashes($hostname) . '\''
+ . ($row['Grant_priv'] == 'Y' ? ' WITH GRANT OPTION;' : ';');
+ }
+ $GLOBALS['dbi']->freeResult($res);
+
+ $queries = PMA_getTablePrivsQueriesForChangeOrCopyUser(
+ $user_host_condition, $queries, $username, $hostname
+ );
+
+ return $queries;
+}
+
+/**
+ * Prepares queries for adding users and
+ * also create database and return query and message
+ *
+ * @param boolean $_error whether user create or not
+ * @param string $real_sql_query SQL query for add a user
+ * @param string $sql_query SQL query to be displayed
+ * @param string $username username
+ * @param string $hostname host name
+ * @param string $dbname database name
+ *
+ * @return array $sql_query, $message
+ */
+function PMA_addUserAndCreateDatabase($_error, $real_sql_query, $sql_query,
+ $username, $hostname, $dbname
+) {
+ if ($_error || ! $GLOBALS['dbi']->tryQuery($real_sql_query)) {
+ $_REQUEST['createdb-1'] = $_REQUEST['createdb-2']
+ = $_REQUEST['createdb-3'] = false;
+ $message = PMA_Message::rawError($GLOBALS['dbi']->getError());
+ } else {
+ $message = PMA_Message::success(__('You have added a new user.'));
+ }
+
+ if (isset($_REQUEST['createdb-1'])) {
+ // Create database with same name and grant all privileges
+ $q = 'CREATE DATABASE IF NOT EXISTS '
+ . PMA_Util::backquote(
+ PMA_Util::sqlAddSlashes($username)
+ ) . ';';
+ $sql_query .= $q;
+ if (! $GLOBALS['dbi']->tryQuery($q)) {
+ $message = PMA_Message::rawError($GLOBALS['dbi']->getError());
+ }
+
+ /**
+ * Reload the navigation
+ */
+ $GLOBALS['reload'] = true;
+ $GLOBALS['db'] = $username;
+
+ $q = 'GRANT ALL PRIVILEGES ON '
+ . PMA_Util::backquote(
+ PMA_Util::escapeMysqlWildcards(
+ PMA_Util::sqlAddSlashes($username)
+ )
+ ) . '.* TO \''
+ . PMA_Util::sqlAddSlashes($username)
+ . '\'@\'' . PMA_Util::sqlAddSlashes($hostname) . '\';';
+ $sql_query .= $q;
+ if (! $GLOBALS['dbi']->tryQuery($q)) {
+ $message = PMA_Message::rawError($GLOBALS['dbi']->getError());
+ }
+ }
+
+ if (isset($_REQUEST['createdb-2'])) {
+ // Grant all privileges on wildcard name (username\_%)
+ $q = 'GRANT ALL PRIVILEGES ON '
+ . PMA_Util::backquote(
+ PMA_Util::sqlAddSlashes($username) . '\_%'
+ ) . '.* TO \''
+ . PMA_Util::sqlAddSlashes($username)
+ . '\'@\'' . PMA_Util::sqlAddSlashes($hostname) . '\';';
+ $sql_query .= $q;
+ if (! $GLOBALS['dbi']->tryQuery($q)) {
+ $message = PMA_Message::rawError($GLOBALS['dbi']->getError());
+ }
+ }
+
+ if (isset($_REQUEST['createdb-3'])) {
+ // Grant all privileges on the specified database to the new user
+ $q = 'GRANT ALL PRIVILEGES ON '
+ . PMA_Util::backquote(
+ PMA_Util::sqlAddSlashes($dbname)
+ ) . '.* TO \''
+ . PMA_Util::sqlAddSlashes($username)
+ . '\'@\'' . PMA_Util::sqlAddSlashes($hostname) . '\';';
+ $sql_query .= $q;
+ if (! $GLOBALS['dbi']->tryQuery($q)) {
+ $message = PMA_Message::rawError($GLOBALS['dbi']->getError());
+ }
+ }
+ return array($sql_query, $message);
+}
+
+/**
+ * Get SQL queries for Display and Add user
+ *
+ * @param string $username usernam
+ * @param string $hostname host name
+ * @param string $password password
+ *
+ * @return array ($create_user_real, $create_user_show,$real_sql_query, $sql_query)
+ */
+function PMA_getSqlQueriesForDisplayAndAddUser($username, $hostname, $password)
+{
+ $sql_query = '';
+ $create_user_real = 'CREATE USER \''
+ . PMA_Util::sqlAddSlashes($username) . '\'@\''
+ . PMA_Util::sqlAddSlashes($hostname) . '\'';
+
+ $real_sql_query = 'GRANT ' . join(', ', PMA_extractPrivInfo()) . ' ON *.* TO \''
+ . PMA_Util::sqlAddSlashes($username) . '\'@\''
+ . PMA_Util::sqlAddSlashes($hostname) . '\'';
+
+ if ($_POST['pred_password'] != 'none' && $_POST['pred_password'] != 'keep') {
+ $sql_query = $real_sql_query . ' IDENTIFIED BY \'***\'';
+ $real_sql_query .= ' IDENTIFIED BY \''
+ . PMA_Util::sqlAddSlashes($_POST['pma_pw']) . '\'';
+ if (isset($create_user_real)) {
+ $create_user_show = $create_user_real . ' IDENTIFIED BY \'***\'';
+ $create_user_real .= ' IDENTIFIED BY \''
+ . PMA_Util::sqlAddSlashes($_POST['pma_pw']) . '\'';
+ }
+ } else {
+ if ($_POST['pred_password'] == 'keep' && ! empty($password)) {
+ $real_sql_query .= ' IDENTIFIED BY PASSWORD \'' . $password . '\'';
+ if (isset($create_user_real)) {
+ $create_user_real .= ' IDENTIFIED BY PASSWORD \'' . $password . '\'';
+ }
+ }
+ $sql_query = $real_sql_query;
+ if (isset($create_user_real)) {
+ $create_user_show = $create_user_real;
+ }
+ }
+
+ if ((isset($_POST['Grant_priv']) && $_POST['Grant_priv'] == 'Y')
+ || (isset($_POST['max_questions']) || isset($_POST['max_connections'])
+ || isset($_POST['max_updates']) || isset($_POST['max_user_connections']))
+ ) {
+ $with_clause = PMA_getWithClauseForAddUserAndUpdatePrivs();
+ $real_sql_query .= ' ' . $with_clause;
+ $sql_query .= ' ' . $with_clause;
+ }
+
+ if (isset($create_user_real)) {
+ $create_user_real .= ';';
+ $create_user_show .= ';';
+ }
+ $real_sql_query .= ';';
+ $sql_query .= ';';
+
+ return array($create_user_real,
+ $create_user_show,
+ $real_sql_query,
+ $sql_query
+ );
+}
+?>
diff --git a/libraries/server_status.lib.php b/libraries/server_status.lib.php
new file mode 100644
index 0000000000..4c12d3099a
--- /dev/null
+++ b/libraries/server_status.lib.php
@@ -0,0 +1,546 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+
+/**
+ * functions for displaying server status
+ *
+ * @usedby server_status.php
+ *
+ * @package PhpMyAdmin
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ * Prints server status information: processes, connections and traffic
+ *
+ * @param Object $ServerStatusData An instance of the PMA_ServerStatusData class
+ *
+ * @return string
+ */
+function PMA_getHtmlForServerStatus($ServerStatusData)
+{
+ //display the server state General Information
+ $retval = PMA_getHtmlForServerStateGeneralInfo($ServerStatusData);
+
+ //display the server state traffic information
+ $retval .= PMA_getHtmlForServerStateTraffic($ServerStatusData);
+
+ //display the server state connection information
+ $retval .= PMA_getHtmlForServerStateConnections($ServerStatusData);
+
+ //display the server Process List information
+ $retval .= PMA_getHtmlForServerProcesslist($ServerStatusData);
+
+ return $retval;
+}
+
+/**
+ * Prints server state General information
+ *
+ * @param Object $ServerStatusData An instance of the PMA_ServerStatusData class
+ *
+ * @return string
+ */
+function PMA_getHtmlForServerStateGeneralInfo($ServerStatusData)
+{
+ $start_time = $GLOBALS['dbi']->fetchValue(
+ 'SELECT UNIX_TIMESTAMP() - ' . $ServerStatusData->status['Uptime']
+ );
+
+ $retval = '<h3>';
+ $bytes_received = $ServerStatusData->status['Bytes_received'];
+ $bytes_sent = $ServerStatusData->status['Bytes_sent'];
+ $retval .= sprintf(
+ __('Network traffic since startup: %s'),
+ implode(
+ ' ',
+ PMA_Util::formatByteDown(
+ $bytes_received + $bytes_sent,
+ 3,
+ 1
+ )
+ )
+ );
+ $retval .= '</h3>';
+ $retval .= '<p>';
+ $retval .= sprintf(
+ __('This MySQL server has been running for %1$s. It started up on %2$s.'),
+ PMA_Util::timespanFormat($ServerStatusData->status['Uptime']),
+ PMA_Util::localisedDate($start_time)
+ ) . "\n";
+ $retval .= '</p>';
+
+ if ($GLOBALS['server_master_status'] || $GLOBALS['server_slave_status']) {
+ $retval .= '<p class="notice">';
+ if ($GLOBALS['server_master_status'] && $GLOBALS['server_slave_status']) {
+ $retval .= __(
+ 'This MySQL server works as <b>master</b> and '
+ . '<b>slave</b> in <b>replication</b> process.'
+ );
+ } elseif ($GLOBALS['server_master_status']) {
+ $retval .= __(
+ 'This MySQL server works as <b>master</b> '
+ . 'in <b>replication</b> process.'
+ );
+ } elseif ($GLOBALS['server_slave_status']) {
+ $retval .= __(
+ 'This MySQL server works as <b>slave</b> '
+ . 'in <b>replication</b> process.'
+ );
+ }
+ $retval .= '</p>';
+ }
+
+ /*
+ * if the server works as master or slave in replication process,
+ * display useful information
+ */
+ if ($GLOBALS['server_master_status'] || $GLOBALS['server_slave_status']) {
+ $retval .= '<hr class="clearfloat" />';
+ $retval .= '<h3><a name="replication">';
+ $retval .= __('Replication status');
+ $retval .= '</a></h3>';
+ foreach ($GLOBALS['replication_types'] as $type) {
+ if (isset(${"server_{$type}_status"}) && ${"server_{$type}_status"}) {
+ $retval .= PMA_getHtmlForReplicationStatusTable($type);
+ }
+ }
+ }
+
+ return $retval;
+}
+
+/**
+ * Prints server state traffic information
+ *
+ * @param Object $ServerStatusData An instance of the PMA_ServerStatusData class
+ *
+ * @return string
+ */
+function PMA_getHtmlForServerStateTraffic($ServerStatusData)
+{
+ $hour_factor = 3600 / $ServerStatusData->status['Uptime'];
+ $retval = '<table id="serverstatustraffic" class="data noclick">';
+ $retval .= '<thead>';
+ $retval .= '<tr>';
+ $retval .= '<th colspan="2">';
+ $retval .= __('Traffic') . '&nbsp;';
+ $retval .= PMA_Util::showHint(
+ __(
+ 'On a busy server, the byte counters may overrun, so those statistics '
+ . 'as reported by the MySQL server may be incorrect.'
+ )
+ );
+ $retval .= '</th>';
+ $retval .= '<th>&oslash; ' . __('per hour') . '</th>';
+ $retval .= '</tr>';
+ $retval .= '</thead>';
+ $retval .= '<tbody>';
+ $retval .= '<tr class="odd">';
+ $retval .= '<th class="name">' . __('Received') . '</th>';
+ $retval .= '<td class="value">';
+ $retval .= implode(
+ ' ',
+ PMA_Util::formatByteDown(
+ $ServerStatusData->status['Bytes_received'], 3, 1
+ )
+ );
+ $retval .= '</td>';
+ $retval .= '<td class="value">';
+ $retval .= implode(
+ ' ',
+ PMA_Util::formatByteDown(
+ $ServerStatusData->status['Bytes_received'] * $hour_factor, 3, 1
+ )
+ );
+ $retval .= '</td>';
+ $retval .= '</tr>';
+ $retval .= '<tr class="even">';
+ $retval .= '<th class="name">' . __('Sent') . '</th>';
+ $retval .= '<td class="value">';
+ $retval .= implode(
+ ' ',
+ PMA_Util::formatByteDown(
+ $ServerStatusData->status['Bytes_sent'], 3, 1
+ )
+ );
+ $retval .= '</td>';
+ $retval .= '<td class="value"><?php echo';
+ $retval .= implode(
+ ' ',
+ PMA_Util::formatByteDown(
+ $ServerStatusData->status['Bytes_sent'] * $hour_factor, 3, 1
+ )
+ );
+ $retval .= '</td>';
+ $retval .= '</tr>';
+ $retval .= '<tr class="odd">';
+ $retval .= '<th class="name">' . __('Total') . '</th>';
+ $retval .= '<td class="value">';
+ $bytes_received = $ServerStatusData->status['Bytes_received'];
+ $bytes_sent = $ServerStatusData->status['Bytes_sent'];
+ $retval .= implode(
+ ' ',
+ PMA_Util::formatByteDown(
+ $bytes_received + $bytes_sent, 3, 1
+ )
+ );
+ $retval .= '</td>';
+ $retval .= '<td class="value">';
+ $bytes_received = $ServerStatusData->status['Bytes_received'];
+ $bytes_sent = $ServerStatusData->status['Bytes_sent'];
+ $retval .= implode(
+ ' ',
+ PMA_Util::formatByteDown(
+ ($bytes_received + $bytes_sent) * $hour_factor, 3, 1
+ )
+ );
+ $retval .= '</td>';
+ $retval .= '</tr>';
+ $retval .= '</tbody>';
+ $retval .= '</table>';
+ return $retval;
+}
+
+/**
+ * Prints server state connections information
+ *
+ * @param Object $ServerStatusData An instance of the PMA_ServerStatusData class
+ *
+ * @return string
+ */
+function PMA_getHtmlForServerStateConnections($ServerStatusData)
+{
+ $hour_factor = 3600 / $ServerStatusData->status['Uptime'];
+ $retval = '<table id="serverstatusconnections" class="data noclick">';
+ $retval .= '<thead>';
+ $retval .= '<tr>';
+ $retval .= '<th colspan="2">' . __('Connections') . '</th>';
+ $retval .= '<th>&oslash; ' . __('per hour') . '</th>';
+ $retval .= '<th>%</th>';
+ $retval .= '</tr>';
+ $retval .= '</thead>';
+ $retval .= '<tbody>';
+ $retval .= '<tr class="odd">';
+ $retval .= '<th class="name">' . __('max. concurrent connections') . '</th>';
+ $retval .= '<td class="value">';
+ $retval .= PMA_Util::formatNumber(
+ $ServerStatusData->status['Max_used_connections'], 0
+ );
+ $retval .= '</td>';
+ $retval .= '<td class="value">--- </td>';
+ $retval .= '<td class="value">--- </td>';
+ $retval .= '</tr>';
+ $retval .= '<tr class="even">';
+ $retval .= '<th class="name">' . __('Failed attempts') . '</th>';
+ $retval .= '<td class="value">';
+ $retval .= PMA_Util::formatNumber(
+ $ServerStatusData->status['Aborted_connects'], 4, 1, true
+ );
+ $retval .= '</td>';
+ $retval .= '<td class="value">';
+ $retval .= PMA_Util::formatNumber(
+ $ServerStatusData->status['Aborted_connects'] * $hour_factor, 4, 2, true
+ );
+ $retval .= '</td>';
+ $retval .= '<td class="value">';
+ if ($ServerStatusData->status['Connections'] > 0) {
+ $abortNum = $ServerStatusData->status['Aborted_connects'];
+ $connectNum = $ServerStatusData->status['Connections'];
+
+ $retval .= PMA_Util::formatNumber(
+ $abortNum * 100 / $connectNum,
+ 0, 2, true
+ );
+ $retval .= '%';
+ } else {
+ $retval .= '--- ';
+ }
+ $retval .= '</td>';
+ $retval .= '</tr>';
+ $retval .= '<tr class="odd">';
+ $retval .= '<th class="name">' . __('Aborted') . '</th>';
+ $retval .= '<td class="value">';
+ $retval .= PMA_Util::formatNumber(
+ $ServerStatusData->status['Aborted_clients'], 4, 1, true
+ );
+ $retval .= '</td>';
+ $retval .= '<td class="value">';
+ $retval .= PMA_Util::formatNumber(
+ $ServerStatusData->status['Aborted_clients'] * $hour_factor, 4, 2, true
+ );
+ $retval .= '</td>';
+ $retval .= '<td class="value">';
+ if ($ServerStatusData->status['Connections'] > 0) {
+ $abortNum = $ServerStatusData->status['Aborted_clients'];
+ $connectNum = $ServerStatusData->status['Connections'];
+
+ $retval .= PMA_Util::formatNumber(
+ $abortNum * 100 / $connectNum,
+ 0, 2, true
+ );
+ $retval .= '%';
+ } else {
+ $retval .= '--- ';
+ }
+ $retval .= '</td>';
+ $retval .= '</tr>';
+ $retval .= '<tr class="even">';
+ $retval .= '<th class="name">' . __('Total') . '</th>';
+ $retval .= '<td class="value">';
+ $retval .= PMA_Util::formatNumber(
+ $ServerStatusData->status['Connections'], 4, 0
+ );
+ $retval .= '</td>';
+ $retval .= '<td class="value">';
+ $retval .= PMA_Util::formatNumber(
+ $ServerStatusData->status['Connections'] * $hour_factor, 4, 2
+ );
+ $retval .= '</td>';
+ $retval .= '<td class="value">';
+ $retval .= PMA_Util::formatNumber(100, 0, 2);
+ $retval .= '%</td>';
+ $retval .= '</tr>';
+ $retval .= '</tbody>';
+ $retval .= '</table>';
+
+ return $retval;
+}
+
+/**
+ * Prints Server Process list
+ *
+ * @param Object $ServerStatusData An instance of the PMA_ServerStatusData class
+ *
+ * @return string
+ */
+function PMA_getHtmlForServerProcesslist($ServerStatusData)
+{
+ $url_params = array();
+
+ $show_full_sql = ! empty($_REQUEST['full']);
+ if ($show_full_sql) {
+ $url_params['full'] = 1;
+ $full_text_link = 'server_status.php' . PMA_URL_getCommon(
+ array(), 'html', '?'
+ );
+ } else {
+ $full_text_link = 'server_status.php' . PMA_URL_getCommon(
+ array('full' => 1)
+ );
+ }
+
+ // This array contains display name and real column name of each
+ // sortable column in the table
+ $sortable_columns = array(
+ array(
+ 'column_name' => __('ID'),
+ 'order_by_field' => 'Id'
+ ),
+ array(
+ 'column_name' => __('User'),
+ 'order_by_field' => 'User'
+ ),
+ array(
+ 'column_name' => __('Host'),
+ 'order_by_field' => 'Host'
+ ),
+ array(
+ 'column_name' => __('Database'),
+ 'order_by_field' => 'db'
+ ),
+ array(
+ 'column_name' => __('Command'),
+ 'order_by_field' => 'Command'
+ ),
+ array(
+ 'column_name' => __('Time'),
+ 'order_by_field' => 'Time'
+ ),
+ array(
+ 'column_name' => __('Status'),
+ 'order_by_field' => 'State'
+ ),
+ array(
+ 'column_name' => __('SQL query'),
+ 'order_by_field' => 'Info'
+ )
+ );
+ $sortable_columns_count = count($sortable_columns);
+
+ if (PMA_DRIZZLE) {
+ $left_str = 'left(p.info, '
+ . (int)$GLOBALS['cfg']['MaxCharactersInDisplayedSQL'] . ')';
+ $sql_query = "SELECT
+ p.id AS Id,
+ p.username AS User,
+ p.host AS Host,
+ p.db AS db,
+ p.command AS Command,
+ p.time AS Time,
+ p.state AS State,"
+ . ($show_full_sql ? 's.query' : $left_str )
+ . " AS Info FROM data_dictionary.PROCESSLIST p "
+ . ($show_full_sql
+ ? 'LEFT JOIN data_dictionary.SESSIONS s ON s.session_id = p.id'
+ : '');
+ if (! empty($_REQUEST['order_by_field'])
+ && ! empty($_REQUEST['sort_order'])
+ ) {
+ $sql_query .= ' ORDER BY p.' . $_REQUEST['order_by_field'] . ' '
+ . $_REQUEST['sort_order'];
+ }
+ } else {
+ $sql_query = $show_full_sql
+ ? 'SHOW FULL PROCESSLIST'
+ : 'SHOW PROCESSLIST';
+ if (! empty($_REQUEST['order_by_field'])
+ && ! empty($_REQUEST['sort_order'])
+ ) {
+ $sql_query = 'SELECT * FROM `INFORMATION_SCHEMA`.`PROCESSLIST` '
+ . 'ORDER BY `'
+ . $_REQUEST['order_by_field'] . '` ' . $_REQUEST['sort_order'];
+ }
+ }
+
+ $result = $GLOBALS['dbi']->query($sql_query);
+
+ $retval = '<table id="tableprocesslist" '
+ . 'class="data clearfloat noclick sortable">';
+ $retval .= '<thead>';
+ $retval .= '<tr>';
+ $retval .= '<th>' . __('Processes') . '</th>';
+ foreach ($sortable_columns as $column) {
+
+ $is_sorted = ! empty($_REQUEST['order_by_field'])
+ && ! empty($_REQUEST['sort_order'])
+ && ($_REQUEST['order_by_field'] == $column['order_by_field']);
+
+ $column['sort_order'] = 'ASC';
+ if ($is_sorted && $_REQUEST['sort_order'] === 'ASC') {
+ $column['sort_order'] = 'DESC';
+ }
+
+ if ($is_sorted) {
+ if ($_REQUEST['sort_order'] == 'ASC') {
+ $asc_display_style = 'inline';
+ $desc_display_style = 'none';
+ } elseif ($_REQUEST['sort_order'] == 'DESC') {
+ $desc_display_style = 'inline';
+ $asc_display_style = 'none';
+ }
+ }
+
+ $retval .= '<th>';
+ $columnUrl = PMA_URL_getCommon($column);
+ $retval .= '<a href="server_status.php' . $columnUrl . '" ';
+ if ($is_sorted) {
+ $retval .= 'onmouseout="$(\'.soimg\').toggle()" '
+ . 'onmouseover="$(\'.soimg\').toggle()"';
+ }
+ $retval .= '>';
+
+ $retval .= $column['column_name'];
+
+ if ($is_sorted) {
+ $retval .= '<img class="icon ic_s_desc soimg" alt="'
+ . __('Descending') . '" title="" src="themes/dot.gif" '
+ . 'style="display: ' . $desc_display_style . '" />';
+ $retval .= '<img class="icon ic_s_asc soimg hide" alt="'
+ . __('Ascending') . '" title="" src="themes/dot.gif" '
+ . 'style="display: ' . $asc_display_style . '" />';
+ }
+
+ $retval .= '</a>';
+
+ if (! PMA_DRIZZLE && (0 === --$sortable_columns_count)) {
+ $retval .= '<a href="' . $full_text_link . '">';
+ if ($show_full_sql) {
+ $retval .= PMA_Util::getImage(
+ 's_partialtext.png',
+ __('Truncate Shown Queries')
+ );
+ } else {
+ $retval .= PMA_Util::getImage(
+ 's_fulltext.png',
+ __('Show Full Queries')
+ );
+ }
+ $retval .= '</a>';
+ }
+ $retval .= '</th>';
+ }
+
+ $retval .= '</tr>';
+ $retval .= '</thead>';
+ $retval .= '<tbody>';
+
+ $odd_row = true;
+ while ($process = $GLOBALS['dbi']->fetchAssoc($result)) {
+ $retval .= PMA_getHtmlForServerProcessItem(
+ $process,
+ $odd_row,
+ $show_full_sql
+ );
+ $odd_row = ! $odd_row;
+ }
+ $retval .= '</tbody>';
+ $retval .= '</table>';
+
+ return $retval;
+}
+
+/**
+ * Prints Every Item of Server Process
+ *
+ * @param Array $process data of Every Item of Server Process
+ * @param bool $odd_row display odd row or not
+ * @param bool $show_full_sql show full sql or not
+ *
+ * @return string
+ */
+function PMA_getHtmlForServerProcessItem($process, $odd_row, $show_full_sql)
+{
+ // Array keys need to modify due to the way it has used
+ // to display column values
+ if (! empty($_REQUEST['order_by_field']) && ! empty($_REQUEST['sort_order']) ) {
+ foreach (array_keys($process) as $key) {
+ $new_key = ucfirst(strtolower($key));
+ $process[$new_key] = $process[$key];
+ unset($process[$key]);
+ }
+ }
+
+ $url_params['kill'] = $process['Id'];
+ $kill_process = 'server_status.php' . PMA_URL_getCommon($url_params);
+
+ $retval = '<tr class="' . ($odd_row ? 'odd' : 'even') . '">';
+ $retval .= '<td><a href="' . $kill_process . '">' . __('Kill') . '</a></td>';
+ $retval .= '<td class="value">' . $process['Id'] . '</td>';
+ $retval .= '<td>' . htmlspecialchars($process['User']) . '</td>';
+ $retval .= '<td>' . htmlspecialchars($process['Host']) . '</td>';
+ $retval .= '<td>' . ((! isset($process['db']) || ! strlen($process['db']))
+ ? '<i>' . __('None') . '</i>'
+ : htmlspecialchars($process['db'])) . '</td>';
+ $retval .= '<td>' . htmlspecialchars($process['Command']) . '</td>';
+ $retval .= '<td class="value">' . $process['Time'] . '</td>';
+ $processStatusStr = empty($process['State']) ? '---' : $process['State'];
+ $retval .= '<td>' . $processStatusStr . '</td>';
+ $retval .= '<td>';
+
+ if (empty($process['Info'])) {
+ $retval .= '---';
+ } else {
+ $retval .= PMA_Util::formatSql($process['Info'], ! $show_full_sql);
+ }
+ $retval .= '</td>';
+ $retval .= '</tr>';
+
+ return $retval;
+}
+
+?>
+
+
diff --git a/libraries/server_status_advisor.lib.php b/libraries/server_status_advisor.lib.php
new file mode 100644
index 0000000000..421c11efc8
--- /dev/null
+++ b/libraries/server_status_advisor.lib.php
@@ -0,0 +1,70 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+
+/**
+ * functions for displaying server status sub item: advisor
+ *
+ * @usedby server_status_advisor.php
+ *
+ * @package PhpMyAdmin
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ * Returns html with Advisor
+ *
+ * @return string
+ */
+function PMA_getHtmlForAdvisor()
+{
+ $output = '<a href="#openAdvisorInstructions">';
+ $output .= PMA_Util::getIcon('b_help.png', __('Instructions'));
+ $output .= '</a>';
+ $output .= '<div id="statustabs_advisor"></div>';
+ $output .= '<div id="advisorInstructionsDialog" style="display:none;">';
+ $output .= '<p>';
+ $output .= __(
+ 'The Advisor system can provide recommendations '
+ . 'on server variables by analyzing the server status variables.'
+ );
+ $output .= '</p>';
+ $output .= '<p>';
+ $output .= __(
+ 'Do note however that this system provides recommendations '
+ . 'based on simple calculations and by rule of thumb which may '
+ . 'not necessarily apply to your system.'
+ );
+ $output .= '</p>';
+ $output .= '<p>';
+ $output .= __(
+ 'Prior to changing any of the configuration, be sure to know '
+ . 'what you are changing (by reading the documentation) and how '
+ . 'to undo the change. Wrong tuning can have a very negative '
+ . 'effect on performance.'
+ );
+ $output .= '</p>';
+ $output .= '<p>';
+ $output .= __(
+ 'The best way to tune your system would be to change only one '
+ . 'setting at a time, observe or benchmark your database, and undo '
+ . 'the change if there was no clearly measurable improvement.'
+ );
+ $output .= '</p>';
+ $output .= '</div>';
+ $output .= '<div id="advisorData" style="display:none;">';
+ $advisor = new Advisor();
+ $output .= htmlspecialchars(
+ json_encode(
+ $advisor->run()
+ )
+ );
+ $output .= '</div>';
+
+ return $output;
+}
+
+?>
+
+
diff --git a/libraries/server_status_monitor.lib.php b/libraries/server_status_monitor.lib.php
new file mode 100644
index 0000000000..a2c6f35396
--- /dev/null
+++ b/libraries/server_status_monitor.lib.php
@@ -0,0 +1,798 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+
+/**
+ * functions for displaying server status sub item: monitor
+ *
+ * @usedby server_status_monitor.php
+ *
+ * @package PhpMyAdmin
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ * Prints html with monitor
+ *
+ * @param object $ServerStatusData An instance of the PMA_ServerStatusData class
+ *
+ * @return string
+ */
+function PMA_getHtmlForMonitor($ServerStatusData)
+{
+ $retval = PMA_getHtmlForTabLinks();
+
+ $retval .= PMA_getHtmlForSettingsDialog();
+
+ $retval .= PMA_getHtmlForInstructionsDialog();
+
+ $retval .= PMA_getHtmlForAddChartDialog();
+
+ if (! PMA_DRIZZLE) {
+ $retval .= PMA_getHtmlForAnalyseDialog();
+ }
+
+ $retval .= '<table class="clearfloat" id="chartGrid"></table>';
+ $retval .= '<div id="logTable">';
+ $retval .= '<br/>';
+ $retval .= '</div>';
+
+ $retval .= '<script type="text/javascript">';
+ $retval .= 'variableNames = [ ';
+ $i=0;
+ foreach ($ServerStatusData->status as $name=>$value) {
+ if (is_numeric($value)) {
+ if ($i++ > 0) {
+ $retval .= ", ";
+ }
+ $retval .= "'" . $name . "'";
+ }
+ }
+ $retval .= '];';
+ $retval .= '</script>';
+
+ return $retval;
+}
+
+/**
+ * Builds a <select> list for refresh rates
+ *
+ * @param string $name Name of select
+ * @param int $defaultRate Currently chosen rate
+ * @param array $refreshRates List of refresh rates
+ *
+ * @return string
+ */
+function PMA_getHtmlForRefreshList($name,
+ $defaultRate = 5,
+ $refreshRates = Array(1, 2, 5, 10, 20, 40, 60, 120, 300, 600)
+) {
+ $return = '<select name="' . $name . '" id="id_' . $name
+ . '" class="refreshRate">';
+ foreach ($refreshRates as $rate) {
+ $selected = ($rate == $defaultRate)?' selected="selected"':'';
+ $return .= '<option value="' . $rate . '"' . $selected . '>';
+ if ($rate < 60) {
+ $return .= sprintf(_ngettext('%d second', '%d seconds', $rate), $rate);
+ } else {
+ $rate = $rate / 60;
+ $return .= sprintf(_ngettext('%d minute', '%d minutes', $rate), $rate);
+ }
+ $return .= '</option>';
+ }
+ $return .= '</select>';
+ return $return;
+}
+
+/**
+ * Returns html for Analyse Dialog
+ *
+ * @return string
+ */
+function PMA_getHtmlForAnalyseDialog()
+{
+ $retval = '<div id="logAnalyseDialog" title="';
+ $retval .= __('Log statistics') . '" style="display:none;">';
+ $retval .= '<p>' . __('Selected time range:');
+ $retval .= '<input type="text" name="dateStart"'
+ . ' class="datetimefield" value="" /> - ';
+ $retval .= '<input type="text" name="dateEnd" class="datetimefield" value="" />';
+ $retval .= '</p>';
+ $retval .= '<input type="checkbox" id="limitTypes"'
+ . ' value="1" checked="checked" />';
+ $retval .= '<label for="limitTypes">';
+ $retval .= __('Only retrieve SELECT,INSERT,UPDATE and DELETE Statements');
+ $retval .= '</label>';
+ $retval .= '<br/>';
+ $retval .= '<input type="checkbox" id="removeVariables"'
+ . ' value="1" checked="checked" />';
+ $retval .= '<label for="removeVariables">';
+ $retval .= __('Remove variable data in INSERT statements for better grouping');
+ $retval .= '</label>';
+ $retval .= '<p>';
+ $retval .= __(
+ 'Choose from which log you want the statistics to be generated from.'
+ );
+ $retval .= '</p>';
+ $retval .= '<p>';
+ $retval .= __('Results are grouped by query text.');
+ $retval .= '</p>';
+ $retval .= '</div>';
+ $retval .= '<div id="queryAnalyzerDialog" title="';
+ $retval .= __('Query analyzer') . '" style="display:none;">';
+ $retval .= '<textarea id="sqlquery"> </textarea>';
+ $retval .= '<p></p>';
+ $retval .= '<div class="placeHolder"></div>';
+ $retval .= '</div>';
+
+ return $retval;
+}
+
+/**
+ * Returns html for Instructions Dialog
+ *
+ * @return string
+ */
+function PMA_getHtmlForInstructionsDialog()
+{
+ $retval = '<div id="monitorInstructionsDialog" title="';
+ $retval .= __('Monitor Instructions') . '" style="display:none;">';
+ $retval .= __(
+ 'The phpMyAdmin Monitor can assist you in optimizing the server'
+ . ' configuration and track down time intensive queries. For the latter you'
+ . ' will need to set log_output to \'TABLE\' and have either the'
+ . ' slow_query_log or general_log enabled. Note however, that the'
+ . ' general_log produces a lot of data and increases server load'
+ . ' by up to 15%.'
+ );
+
+ if (PMA_MYSQL_INT_VERSION < 50106) {
+ $retval .= '<p>';
+ $retval .= PMA_Util::getImage('s_attention.png');
+ $retval .= __(
+ 'Unfortunately your Database server does not support logging to table,'
+ . ' which is a requirement for analyzing the database logs with'
+ . ' phpMyAdmin. Logging to table is supported by MySQL 5.1.6 and'
+ . ' onwards. You may still use the server charting features however.'
+ );
+ $retval .= '</p>';
+ } else {
+ $retval .= '<p></p>';
+ $retval .= '<img class="ajaxIcon" src="';
+ $retval .= $GLOBALS['pmaThemeImage'] . 'ajax_clock_small.gif"';
+ $retval .= ' alt="' . __('Loading…') . '" />';
+ $retval .= '<div class="ajaxContent"></div>';
+ $retval .= '<div class="monitorUse" style="display:none;">';
+ $retval .= '<p></p>';
+ $retval .= '<strong>';
+ $retval .= __('Using the monitor:');
+ $retval .= '</strong><p>';
+ $retval .= __(
+ 'Your browser will refresh all displayed charts in a regular interval.'
+ . ' You may add charts and change the refresh rate under \'Settings\','
+ . ' or remove any chart using the cog icon on each respective chart.'
+ );
+ $retval .= '</p><p>';
+ $retval .= __(
+ 'To display queries from the logs, select the relevant time span on any'
+ . ' chart by holding down the left mouse button and panning over the'
+ . ' chart. Once confirmed, this will load a table of grouped queries,'
+ . ' there you may click on any occurring SELECT statements to further'
+ . ' analyze them.'
+ );
+ $retval .= '</p>';
+ $retval .= '<p>';
+ $retval .= PMA_Util::getImage('s_attention.png');
+ $retval .= '<strong>';
+ $retval .= __('Please note:');
+ $retval .= '</strong><br />';
+ $retval .= __(
+ 'Enabling the general_log may increase the server load by'
+ . ' 5-15%. Also be aware that generating statistics from the logs is a'
+ . ' load intensive task, so it is advisable to select only a small time'
+ . ' span and to disable the general_log and empty its table once'
+ . ' monitoring is not required any more.'
+ );
+ $retval .= '</p>';
+ $retval .= '</div>';
+ }
+ $retval .= '</div>';
+
+ return $retval;
+}
+
+/**
+ * Returns html for addChartDialog
+ *
+ * @return string
+ */
+function PMA_getHtmlForAddChartDialog()
+{
+ $retval = '<div id="addChartDialog" title="'
+ . __('Add chart') . '" style="display:none;">';
+ $retval .= '<div id="tabGridVariables">';
+ $retval .= '<p><input type="text" name="chartTitle" value="'
+ . __('Chart Title') . '" /></p>';
+ $retval .= '<input type="radio" name="chartType"'
+ . ' value="preset" id="chartPreset" />';
+ $retval .= '<label for="chartPreset">' . __('Preset chart') . '</label>';
+ $retval .= '<select name="presetCharts"></select><br/>';
+ $retval .= '<input type="radio" name="chartType" value="variable" '
+ . 'id="chartStatusVar" checked="checked" />';
+ $retval .= '<label for="chartStatusVar">';
+ $retval .= __('Status variable(s)');
+ $retval .= '</label><br/>';
+ $retval .= '<div id="chartVariableSettings">';
+ $retval .= '<label for="chartSeries">' . __('Select series:') . '</label><br />';
+ $retval .= '<select id="chartSeries" name="varChartList" size="1">';
+ $retval .= '<option>' . __('Commonly monitored') . '</option>';
+ $retval .= '<option>Processes</option>';
+ $retval .= '<option>Questions</option>';
+ $retval .= '<option>Connections</option>';
+ $retval .= '<option>Bytes_sent</option>';
+ $retval .= '<option>Bytes_received</option>';
+ $retval .= '<option>Threads_connected</option>';
+ $retval .= '<option>Created_tmp_disk_tables</option>';
+ $retval .= '<option>Handler_read_first</option>';
+ $retval .= '<option>Innodb_buffer_pool_wait_free</option>';
+ $retval .= '<option>Key_reads</option>';
+ $retval .= '<option>Open_tables</option>';
+ $retval .= '<option>Select_full_join</option>';
+ $retval .= '<option>Slow_queries</option>';
+ $retval .= '</select><br />';
+ $retval .= '<label for="variableInput">';
+ $retval .= __('or type variable name:');
+ $retval .= ' </label>';
+ $retval .= '<input type="text" name="variableInput" id="variableInput" />';
+ $retval .= '<p></p>';
+ $retval .= '<input type="checkbox" name="differentialValue"'
+ . ' id="differentialValue" value="differential" checked="checked" />';
+ $retval .= '<label for="differentialValue">';
+ $retval .= __('Display as differential value');
+ $retval .= '</label><br />';
+ $retval .= '<input type="checkbox" id="useDivisor"'
+ . ' name="useDivisor" value="1" />';
+ $retval .= '<label for="useDivisor">' . __('Apply a divisor') . '</label>';
+ $retval .= '<span class="divisorInput" style="display:none;">';
+ $retval .= '<input type="text" name="valueDivisor" size="4" value="1" />';
+ $retval .= '(<a href="#kibDivisor">' . __('KiB') . '</a>, ';
+ $retval .= '<a href="#mibDivisor">' . __('MiB') . '</a>)';
+ $retval .= '</span><br />';
+ $retval .= '<input type="checkbox" id="useUnit" name="useUnit" value="1" />';
+ $retval .= '<label for="useUnit">';
+ $retval .= __('Append unit to data values');
+ $retval .= '</label>';
+ $retval .= '<span class="unitInput" style="display:none;">';
+ $retval .= '<input type="text" name="valueUnit" size="4" value="" />';
+ $retval .= '</span>';
+ $retval .= '<p>';
+ $retval .= '<a href="#submitAddSeries"><b>' . __('Add this series') . '</b></a>';
+ $retval .= '<span id="clearSeriesLink" style="display:none;">';
+ $retval .= ' | <a href="#submitClearSeries">' . __('Clear series') . '</a>';
+ $retval .= '</span>';
+ $retval .= '</p>';
+ $retval .= __('Series in Chart:');
+ $retval .= '<br/>';
+ $retval .= '<span id="seriesPreview">';
+ $retval .= '<i>' . __('None') . '</i>';
+ $retval .= '</span>';
+ $retval .= '</div>';
+ $retval .= '</div>';
+ $retval .= '</div>';
+
+ return $retval;
+}
+
+/**
+ * Returns html with Tab Links
+ *
+ * @return string
+ */
+function PMA_getHtmlForTabLinks()
+{
+ $retval = '<div class="tabLinks">';
+ $retval .= '<a href="#pauseCharts">';
+ $retval .= PMA_Util::getImage('play.png') . __('Start Monitor');
+ $retval .= '</a>';
+ $retval .= '<a href="#settingsPopup" class="popupLink">';
+ $retval .= PMA_Util::getImage('s_cog.png') . __('Settings');
+ $retval .= '</a>';
+ if (! PMA_DRIZZLE) {
+ $retval .= '<a href="#monitorInstructionsDialog">';
+ $retval .= PMA_Util::getImage('b_help.png') . __('Instructions/Setup');
+ }
+ $retval .= '<a href="#endChartEditMode" style="display:none;">';
+ $retval .= PMA_Util::getImage('s_okay.png');
+ $retval .= __('Done dragging (rearranging) charts');
+ $retval .= '</a>';
+ $retval .= '</div>';
+
+ return $retval;
+}
+
+/**
+ * Returns html with Settings dialog
+ *
+ * @return string
+ */
+function PMA_getHtmlForSettingsDialog()
+{
+ $retval = '<div class="popupContent settingsPopup">';
+ $retval .= '<a href="#addNewChart">';
+ $retval .= PMA_Util::getImage('b_chart.png') . __('Add chart');
+ $retval .= '</a>';
+ $retval .= '<a href="#rearrangeCharts">';
+ $retval .= PMA_Util::getImage('b_tblops.png') . __('Enable charts dragging');
+ $retval .= '</a>';
+ $retval .= '<div class="clearfloat paddingtop"></div>';
+ $retval .= '<div class="floatleft">';
+ $retval .= __('Refresh rate') . '<br />';
+ $retval .= PMA_getHtmlForRefreshList(
+ 'gridChartRefresh',
+ 5,
+ Array(2, 3, 4, 5, 10, 20, 40, 60, 120, 300, 600, 1200)
+ );
+ $retval .= '<br />';
+ $retval .= '</div>';
+ $retval .= '<div class="floatleft">';
+ $retval .= __('Chart columns');
+ $retval .= '<br />';
+ $retval .= '<select name="chartColumns">';
+ $retval .= '<option>1</option>';
+ $retval .= '<option>2</option>';
+ $retval .= '<option>3</option>';
+ $retval .= '<option>4</option>';
+ $retval .= '<option>5</option>';
+ $retval .= '<option>6</option>';
+ $retval .= '<option>7</option>';
+ $retval .= '<option>8</option>';
+ $retval .= '<option>9</option>';
+ $retval .= '<option>10</option>';
+ $retval .= '</select>';
+ $retval .= '</div>';
+ $retval .= '<div class="clearfloat paddingtop">';
+ $retval .= '<b>' . __('Chart arrangement') . '</b> ';
+ $retval .= PMA_Util::showHint(
+ __(
+ 'The arrangement of the charts is stored to the browsers local storage. '
+ . 'You may want to export it if you have a complicated set up.'
+ )
+ );
+ $retval .= '<br/>';
+ $retval .= '<a class="ajax" href="#importMonitorConfig">';
+ $retval .= __('Import');
+ $retval .= '</a>';
+ $retval .= '&nbsp;&nbsp;';
+ $retval .= '<a class="disableAjax" href="#exportMonitorConfig">';
+ $retval .= __('Export');
+ $retval .= '</a>';
+ $retval .= '&nbsp;&nbsp;';
+ $retval .= '<a href="#clearMonitorConfig">';
+ $retval .= __('Reset to default');
+ $retval .= '</a>';
+ $retval .= '</div>';
+ $retval .= '</div>';
+
+ return $retval;
+}
+
+
+/**
+ * Define some data and links needed on the client side
+ *
+ * @param object $ServerStatusData An instance of the PMA_ServerStatusData class
+ *
+ * @return string
+ */
+function PMA_getHtmlForClientSideDataAndLinks($ServerStatusData)
+{
+ /**
+ * Define some data needed on the client side
+ */
+ $input = '<input type="hidden" name="%s" value="%s" />';
+ $form = '<form id="js_data" class="hide">';
+ $form .= sprintf($input, 'server_time', microtime(true) * 1000);
+ $form .= sprintf($input, 'server_os', PHP_OS);
+ $form .= sprintf($input, 'is_superuser', $GLOBALS['dbi']->isSuperuser());
+ $form .= sprintf($input, 'server_db_isLocal', $ServerStatusData->db_isLocal);
+ $form .= '</form>';
+ /**
+ * Define some links used on client side
+ */
+ $links = '<div id="profiling_docu" class="hide">';
+ $links .= PMA_Util::showMySQLDocu('general-thread-states');
+ $links .= '</div>';
+ $links .= '<div id="explain_docu" class="hide">';
+ $links .= PMA_Util::showMySQLDocu('explain-output');
+ $links .= '</div>';
+
+ return $form . $links;
+}
+
+/***************************Ajax request function***********************************/
+
+/**
+ * Returns JSon for real-time charting data
+ *
+ * @return Array
+ */
+function PMA_getJsonForChartingData()
+{
+ $ret = json_decode($_REQUEST['requiredData'], true);
+ $statusVars = array();
+ $serverVars = array();
+ $sysinfo = $cpuload = $memory = 0;
+ $pName = '';
+
+ /* Accumulate all required variables and data */
+ // For each chart
+ foreach ($ret as $chart_id => $chartNodes) {
+ // For each data series
+ foreach ($chartNodes as $node_id => $nodeDataPoints) {
+ // For each data point in the series (usually just 1)
+ foreach ($nodeDataPoints as $point_id => $dataPoint) {
+ $pName = $dataPoint['name'];
+
+ switch ($dataPoint['type']) {
+ /* We only collect the status and server variables here to
+ * read them all in one query,
+ * and only afterwards assign them.
+ * Also do some white list filtering on the names
+ */
+ case 'servervar':
+ if (! preg_match('/[^a-zA-Z_]+/', $pName)) {
+ $serverVars[] = $pName;
+ }
+ break;
+
+ case 'statusvar':
+ if (! preg_match('/[^a-zA-Z_]+/', $pName)) {
+ $statusVars[] = $pName;
+ }
+ break;
+
+ case 'proc':
+ $result = $GLOBALS['dbi']->query('SHOW PROCESSLIST');
+ $ret[$chart_id][$node_id][$point_id]['value']
+ = $GLOBALS['dbi']->numRows($result);
+ break;
+
+ case 'cpu':
+ if (!$sysinfo) {
+ include_once 'libraries/sysinfo.lib.php';
+ $sysinfo = PMA_getSysInfo();
+ }
+ if (!$cpuload) {
+ $cpuload = $sysinfo->loadavg();
+ }
+
+ if (PMA_getSysInfoOs() == 'Linux') {
+ $ret[$chart_id][$node_id][$point_id]['idle']
+ = $cpuload['idle'];
+ $ret[$chart_id][$node_id][$point_id]['busy']
+ = $cpuload['busy'];
+ } else {
+ $ret[$chart_id][$node_id][$point_id]['value']
+ = $cpuload['loadavg'];
+ }
+
+ break;
+
+ case 'memory':
+ if (!$sysinfo) {
+ include_once 'libraries/sysinfo.lib.php';
+ $sysinfo = PMA_getSysInfo();
+ }
+ if (!$memory) {
+ $memory = $sysinfo->memory();
+ }
+
+ $ret[$chart_id][$node_id][$point_id]['value']
+ = $memory[$pName];
+ break;
+ } /* switch */
+ } /* foreach */
+ } /* foreach */
+ } /* foreach */
+
+ // Retrieve all required status variables
+ if (count($statusVars)) {
+ $statusVarValues = $GLOBALS['dbi']->fetchResult(
+ "SHOW GLOBAL STATUS WHERE Variable_name='"
+ . implode("' OR Variable_name='", $statusVars) . "'",
+ 0,
+ 1
+ );
+ } else {
+ $statusVarValues = array();
+ }
+
+ // Retrieve all required server variables
+ if (count($serverVars)) {
+ $serverVarValues = $GLOBALS['dbi']->fetchResult(
+ "SHOW GLOBAL VARIABLES WHERE Variable_name='"
+ . implode("' OR Variable_name='", $serverVars) . "'",
+ 0,
+ 1
+ );
+ } else {
+ $serverVarValues = array();
+ }
+
+ // ...and now assign them
+ foreach ($ret as $chart_id => $chartNodes) {
+ foreach ($chartNodes as $node_id => $nodeDataPoints) {
+ foreach ($nodeDataPoints as $point_id => $dataPoint) {
+ switch($dataPoint['type']) {
+ case 'statusvar':
+ $ret[$chart_id][$node_id][$point_id]['value']
+ = $statusVarValues[$dataPoint['name']];
+ break;
+ case 'servervar':
+ $ret[$chart_id][$node_id][$point_id]['value']
+ = $serverVarValues[$dataPoint['name']];
+ break;
+ }
+ }
+ }
+ }
+
+ $ret['x'] = microtime(true) * 1000;
+ return $ret;
+}
+
+/**
+ * Returns JSon for log data with type: slow
+ *
+ * @param int $start Unix Time: Start time for query
+ * @param int $end Unix Time: End time for query
+ *
+ * @return Array
+ */
+function PMA_getJsonForLogDataTypeSlow($start, $end)
+{
+ $q = 'SELECT start_time, user_host, ';
+ $q .= 'Sec_to_Time(Sum(Time_to_Sec(query_time))) as query_time, ';
+ $q .= 'Sec_to_Time(Sum(Time_to_Sec(lock_time))) as lock_time, ';
+ $q .= 'SUM(rows_sent) AS rows_sent, ';
+ $q .= 'SUM(rows_examined) AS rows_examined, db, sql_text, ';
+ $q .= 'COUNT(sql_text) AS \'#\' ';
+ $q .= 'FROM `mysql`.`slow_log` ';
+ $q .= 'WHERE start_time > FROM_UNIXTIME(' . $start . ') ';
+ $q .= 'AND start_time < FROM_UNIXTIME(' . $end . ') GROUP BY sql_text';
+
+ $result = $GLOBALS['dbi']->tryQuery($q);
+
+ $return = array('rows' => array(), 'sum' => array());
+ $type = '';
+
+ while ($row = $GLOBALS['dbi']->fetchAssoc($result)) {
+ $type = strtolower(
+ substr($row['sql_text'], 0, strpos($row['sql_text'], ' '))
+ );
+
+ switch($type) {
+ case 'insert':
+ case 'update':
+ //Cut off big inserts and updates, but append byte count instead
+ if (strlen($row['sql_text']) > 220) {
+ $implode_sql_text = implode(
+ ' ',
+ PMA_Util::formatByteDown(
+ strlen($row['sql_text']), 2, 2
+ )
+ );
+ $row['sql_text'] = substr($row['sql_text'], 0, 200)
+ . '... [' . $implode_sql_text . ']';
+ }
+ break;
+ default:
+ break;
+ }
+
+ if (! isset($return['sum'][$type])) {
+ $return['sum'][$type] = 0;
+ }
+ $return['sum'][$type] += $row['#'];
+ $return['rows'][] = $row;
+ }
+
+ $return['sum']['TOTAL'] = array_sum($return['sum']);
+ $return['numRows'] = count($return['rows']);
+
+ $GLOBALS['dbi']->freeResult($result);
+ return $return;
+}
+
+/**
+ * Returns JSon for log data with type: general
+ *
+ * @param int $start Unix Time: Start time for query
+ * @param int $end Unix Time: End time for query
+ *
+ * @return Array
+ */
+function PMA_getJsonForLogDataTypeGeneral($start, $end)
+{
+ $limitTypes = '';
+ if (isset($_REQUEST['limitTypes']) && $_REQUEST['limitTypes']) {
+ $limitTypes
+ = 'AND argument REGEXP \'^(INSERT|SELECT|UPDATE|DELETE)\' ';
+ }
+
+ $q = 'SELECT TIME(event_time) as event_time, user_host, thread_id, ';
+ $q .= 'server_id, argument, count(argument) as \'#\' ';
+ $q .= 'FROM `mysql`.`general_log` ';
+ $q .= 'WHERE command_type=\'Query\' ';
+ $q .= 'AND event_time > FROM_UNIXTIME(' . $start . ') ';
+ $q .= 'AND event_time < FROM_UNIXTIME(' . $end . ') ';
+ $q .= $limitTypes . 'GROUP by argument'; // HAVING count > 1';
+
+ $result = $GLOBALS['dbi']->tryQuery($q);
+
+ $return = array('rows' => array(), 'sum' => array());
+ $type = '';
+ $insertTables = array();
+ $insertTablesFirst = -1;
+ $i = 0;
+ $removeVars = isset($_REQUEST['removeVariables'])
+ && $_REQUEST['removeVariables'];
+
+ while ($row = $GLOBALS['dbi']->fetchAssoc($result)) {
+ preg_match('/^(\w+)\s/', $row['argument'], $match);
+ $type = strtolower($match[1]);
+
+ if (! isset($return['sum'][$type])) {
+ $return['sum'][$type] = 0;
+ }
+ $return['sum'][$type] += $row['#'];
+
+ switch($type) {
+ case 'insert':
+ // Group inserts if selected
+ if ($removeVars
+ && preg_match(
+ '/^INSERT INTO (`|\'|"|)([^\s\\1]+)\\1/i',
+ $row['argument'], $matches
+ )
+ ) {
+ $insertTables[$matches[2]]++;
+ if ($insertTables[$matches[2]] > 1) {
+ $return['rows'][$insertTablesFirst]['#']
+ = $insertTables[$matches[2]];
+
+ // Add a ... to the end of this query to indicate that
+ // there's been other queries
+ $temp = $return['rows'][$insertTablesFirst]['argument'];
+ if ($temp[strlen($temp) - 1] != '.') {
+ $return['rows'][$insertTablesFirst]['argument']
+ .= '<br/>...';
+ }
+
+ // Group this value, thus do not add to the result list
+ continue 2;
+ } else {
+ $insertTablesFirst = $i;
+ $insertTables[$matches[2]] += $row['#'] - 1;
+ }
+ }
+ // No break here
+
+ case 'update':
+ // Cut off big inserts and updates,
+ // but append byte count therefor
+ if (strlen($row['argument']) > 220) {
+ $row['argument'] = substr($row['argument'], 0, 200)
+ . '... ['
+ . implode(
+ ' ',
+ PMA_Util::formatByteDown(
+ strlen($row['argument']),
+ 2,
+ 2
+ )
+ )
+ . ']';
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ $return['rows'][] = $row;
+ $i++;
+ }
+
+ $return['sum']['TOTAL'] = array_sum($return['sum']);
+ $return['numRows'] = count($return['rows']);
+
+ $GLOBALS['dbi']->freeResult($result);
+
+ return $return;
+}
+/**
+ * Returns JSon for logging vars
+ *
+ * @return Array
+ */
+function PMA_getJsonForLoggingVars()
+{
+ if (isset($_REQUEST['varName']) && isset($_REQUEST['varValue'])) {
+ $value = PMA_Util::sqlAddSlashes($_REQUEST['varValue']);
+ if (! is_numeric($value)) {
+ $value="'" . $value . "'";
+ }
+
+ if (! preg_match("/[^a-zA-Z0-9_]+/", $_REQUEST['varName'])) {
+ $GLOBALS['dbi']->query(
+ 'SET GLOBAL ' . $_REQUEST['varName'] . ' = ' . $value
+ );
+ }
+
+ }
+
+ $loggingVars = $GLOBALS['dbi']->fetchResult(
+ 'SHOW GLOBAL VARIABLES WHERE Variable_name IN'
+ . ' ("general_log","slow_query_log","long_query_time","log_output")',
+ 0,
+ 1
+ );
+ return $loggingVars;
+}
+
+/**
+ * Returns JSon for query_analyzer
+ *
+ * @return Array
+ */
+function PMA_getJsonForQueryAnalyzer()
+{
+ $return = array();
+
+ if (strlen($_REQUEST['database'])) {
+ $GLOBALS['dbi']->selectDb($_REQUEST['database']);
+ }
+
+ if ($profiling = PMA_Util::profilingSupported()) {
+ $GLOBALS['dbi']->query('SET PROFILING=1;');
+ }
+
+ // Do not cache query
+ $query = preg_replace(
+ '/^(\s*SELECT)/i',
+ '\\1 SQL_NO_CACHE',
+ $_REQUEST['query']
+ );
+
+ $result = $GLOBALS['dbi']->tryQuery($query);
+ $return['affectedRows'] = $GLOBALS['cached_affected_rows'];
+
+ $result = $GLOBALS['dbi']->tryQuery('EXPLAIN ' . $query);
+ while ($row = $GLOBALS['dbi']->fetchAssoc($result)) {
+ $return['explain'][] = $row;
+ }
+
+ // In case an error happened
+ $return['error'] = $GLOBALS['dbi']->getError();
+
+ $GLOBALS['dbi']->freeResult($result);
+
+ if ($profiling) {
+ $return['profiling'] = array();
+ $result = $GLOBALS['dbi']->tryQuery(
+ 'SELECT seq,state,duration FROM INFORMATION_SCHEMA.PROFILING'
+ . ' WHERE QUERY_ID=1 ORDER BY seq'
+ );
+ while ($row = $GLOBALS['dbi']->fetchAssoc($result)) {
+ $return['profiling'][]= $row;
+ }
+ $GLOBALS['dbi']->freeResult($result);
+ }
+ return $return;
+}
+
+?>
+
+
diff --git a/libraries/server_status_queries.lib.php b/libraries/server_status_queries.lib.php
new file mode 100644
index 0000000000..49edbd92f3
--- /dev/null
+++ b/libraries/server_status_queries.lib.php
@@ -0,0 +1,153 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+
+/**
+ * functions for displaying query statistics for the server
+ *
+ * @usedby server_status_queries.php
+ *
+ * @package PhpMyAdmin
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ * Returns the html content for the query statistics
+ *
+ * @param object $ServerStatusData An instance of the PMA_ServerStatusData class
+ *
+ * @return string
+ */
+function PMA_getHtmlForQueryStatistics($ServerStatusData)
+{
+ $retval = '';
+
+ $hour_factor = 3600 / $ServerStatusData->status['Uptime'];
+ $used_queries = $ServerStatusData->used_queries;
+ $total_queries = array_sum($used_queries);
+
+ $retval .= '<h3 id="serverstatusqueries">';
+ /* l10n: Questions is the name of a MySQL Status variable */
+ $retval .= sprintf(
+ __('Questions since startup: %s'),
+ PMA_Util::formatNumber($total_queries, 0)
+ );
+ $retval .= ' ';
+ $retval .= PMA_Util::showMySQLDocu(
+ 'server-status-variables',
+ false,
+ 'statvar_Questions'
+ );
+ $retval .= '<br />';
+ $retval .= '<span>';
+ $retval .= '&oslash; ' . __('per hour:') . ' ';
+ $retval .= PMA_Util::formatNumber($total_queries * $hour_factor, 0);
+ $retval .= '<br />';
+ $retval .= '&oslash; ' . __('per minute:') . ' ';
+ $retval .= PMA_Util::formatNumber(
+ $total_queries * 60 / $ServerStatusData->status['Uptime'],
+ 0
+ );
+ $retval .= '<br />';
+ if ($total_queries / $ServerStatusData->status['Uptime'] >= 1) {
+ $retval .= '&oslash; ' . __('per second:') . ' ';
+ $retval .= PMA_Util::formatNumber(
+ $total_queries / $ServerStatusData->status['Uptime'],
+ 0
+ );
+ }
+ $retval .= '</span>';
+ $retval .= '</h3>';
+
+ $retval .= PMA_getHtmlForServerStatusQueriesDetails($ServerStatusData);
+
+ return $retval;
+}
+
+/**
+ * Returns the html content for the query details
+ *
+ * @param object $ServerStatusData An instance of the PMA_ServerStatusData class
+ *
+ * @return string
+ */
+function PMA_getHtmlForServerStatusQueriesDetails($ServerStatusData)
+{
+ $hour_factor = 3600 / $ServerStatusData->status['Uptime'];
+ $used_queries = $ServerStatusData->used_queries;
+ $total_queries = array_sum($used_queries);
+ // reverse sort by value to show most used statements first
+ arsort($used_queries);
+
+ $odd_row = true;
+
+ //(- $ServerStatusData->status['Connections']);
+ $perc_factor = 100 / $total_queries;
+
+ $retval = '<table id="serverstatusqueriesdetails" '
+ . 'class="data sortable noclick">';
+ $retval .= '<col class="namecol" />';
+ $retval .= '<col class="valuecol" span="3" />';
+ $retval .= '<thead>';
+ $retval .= '<tr><th>' . __('Statements') . '</th>';
+ $retval .= '<th>';
+ /* l10n: # = Amount of queries */
+ $retval .= __('#');
+ $retval .= '</th>';
+ $retval .= '<th>&oslash; ' . __('per hour') . '</th>';
+ $retval .= '<th>%</th>';
+ $retval .= '</tr>';
+ $retval .= '</thead>';
+ $retval .= '<tbody>';
+
+ $chart_json = array();
+ $query_sum = array_sum($used_queries);
+ $other_sum = 0;
+ foreach ($used_queries as $name => $value) {
+ $odd_row = !$odd_row;
+ // For the percentage column, use Questions - Connections, because
+ // the number of connections is not an item of the Query types
+ // but is included in Questions. Then the total of the percentages is 100.
+ $name = str_replace(array('Com_', '_'), array('', ' '), $name);
+ // Group together values that make out less than 2% into "Other", but only
+ // if we have more than 6 fractions already
+ if ($value < $query_sum * 0.02 && count($chart_json)>6) {
+ $other_sum += $value;
+ } else {
+ $chart_json[$name] = $value;
+ }
+ $retval .= '<tr class="';
+ $retval .= $odd_row ? 'odd' : 'even';
+ $retval .= '">';
+ $retval .= '<th class="name">' . htmlspecialchars($name) . '</th>';
+ $retval .= '<td class="value">';
+ $retval .= htmlspecialchars(PMA_Util::formatNumber($value, 5, 0, true));
+ $retval .= '</td>';
+ $retval .= '<td class="value">';
+ $retval .= htmlspecialchars(
+ PMA_Util::formatNumber($value * $hour_factor, 4, 1, true)
+ );
+ $retval .= '</td>';
+ $retval .= '<td class="value">';
+ $retval .= htmlspecialchars(
+ PMA_Util::formatNumber($value * $perc_factor, 0, 2)
+ );
+ $retval .= '</td>';
+ $retval .= '</tr>';
+ }
+ $retval .= '</tbody>';
+ $retval .= '</table>';
+
+ $retval .= '<div id="serverstatusquerieschart"></div>';
+ $retval .= '<div id="serverstatusquerieschart_data" style="display:none;">';
+ if ($other_sum > 0) {
+ $chart_json[__('Other')] = $other_sum;
+ }
+ $retval .= htmlspecialchars(json_encode($chart_json));
+ $retval .= '</div>';
+
+ return $retval;
+}
+
+?>
diff --git a/libraries/server_status_variables.lib.php b/libraries/server_status_variables.lib.php
new file mode 100644
index 0000000000..b052165457
--- /dev/null
+++ b/libraries/server_status_variables.lib.php
@@ -0,0 +1,765 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+
+/**
+ * functions for displaying server status variables
+ *
+ * @usedby server_status_variables.php
+ *
+ * @package PhpMyAdmin
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ * Returns the html for the list filter
+ *
+ * @param Object $ServerStatusData An instance of the PMA_ServerStatusData class
+ *
+ * @return string
+ */
+function PMA_getHtmlForFilter($ServerStatusData)
+{
+ $filterAlert = '';
+ if (! empty($_REQUEST['filterAlert'])) {
+ $filterAlert = ' checked="checked"';
+ }
+ $filterText = '';
+ if (! empty($_REQUEST['filterText'])) {
+ $filterText = htmlspecialchars($_REQUEST['filterText']);
+ }
+ $dontFormat = '';
+ if (! empty($_REQUEST['dontFormat'])) {
+ $dontFormat = ' checked="checked"';
+ }
+
+ $retval = '';
+ $retval .= '<fieldset id="tableFilter">';
+ $retval .= '<legend>' . __('Filters') . '</legend>';
+ $retval .= '<form action="server_status_variables.php?'
+ . PMA_URL_getCommon() . '">';
+ $retval .= '<input type="submit" value="' . __('Refresh') . '" />';
+ $retval .= '<div class="formelement">';
+ $retval .= '<label for="filterText">' . __('Containing the word:') . '</label>';
+ $retval .= '<input name="filterText" type="text" id="filterText" '
+ . 'style="vertical-align: baseline;" value="' . $filterText . '" />';
+ $retval .= '</div>';
+ $retval .= '<div class="formelement">';
+ $retval .= '<input' . $filterAlert . ' type="checkbox" '
+ . 'name="filterAlert" id="filterAlert" />';
+ $retval .= '<label for="filterAlert">';
+ $retval .= __('Show only alert values');
+ $retval .= '</label>';
+ $retval .= '</div>';
+ $retval .= '<div class="formelement">';
+ $retval .= '<select id="filterCategory" name="filterCategory">';
+ $retval .= '<option value="">' . __('Filter by category…') . '</option>';
+
+ foreach ($ServerStatusData->sections as $section_id => $section_name) {
+ if (isset($ServerStatusData->categoryUsed[$section_id])) {
+ if (! empty($_REQUEST['filterCategory'])
+ && $_REQUEST['filterCategory'] == $section_id
+ ) {
+ $selected = ' selected="selected"';
+ } else {
+ $selected = '';
+ }
+ $retval .= '<option' . $selected . ' value="' . $section_id. '">';
+ $retval .= htmlspecialchars($section_name) . '</option>';
+ }
+ }
+ $retval .= '</select>';
+ $retval .= '</div>';
+ $retval .= '<div class="formelement">';
+ $retval .= '<input' . $dontFormat . ' type="checkbox" '
+ . 'name="dontFormat" id="dontFormat" />';
+ $retval .= '<label for="dontFormat">';
+ $retval .= __('Show unformatted values');
+ $retval .= '</label>';
+ $retval .= '</div>';
+ $retval .= '</form>';
+ $retval .= '</fieldset>';
+
+ return $retval;
+}
+
+/**
+ * Prints the suggestion links
+ *
+ * @param Object $ServerStatusData An instance of the PMA_ServerStatusData class
+ *
+ * @return string
+ */
+function PMA_getHtmlForLinkSuggestions($ServerStatusData)
+{
+ $retval = '<div id="linkSuggestions" class="defaultLinks" '
+ . 'style="display:none">';
+ $retval .= '<p class="notice">' . __('Related links:');
+ foreach ($ServerStatusData->links as $section_name => $section_links) {
+ $retval .= '<span class="status_' . $section_name . '"> ';
+ $i=0;
+ foreach ($section_links as $link_name => $link_url) {
+ if ($i > 0) {
+ $retval .= ', ';
+ }
+ if ('doc' == $link_name) {
+ $retval .= PMA_Util::showMySQLDocu($link_url);
+ } else {
+ $retval .= '<a href="' . $link_url . '">' . $link_name . '</a>';
+ }
+ $i++;
+ }
+ $retval .= '</span>';
+ }
+ unset($link_url, $link_name, $i);
+ $retval .= '</p>';
+ $retval .= '</div>';
+
+ return $retval;
+}
+
+/**
+ * Returns a table with variables information
+ *
+ * @param Object $ServerStatusData An instance of the PMA_ServerStatusData class
+ *
+ * @return string
+ */
+function PMA_getHtmlForVariablesList($ServerStatusData)
+{
+ $retval = '';
+ $strShowStatus = PMA_getStatusVariablesDescriptions();
+ /**
+ * define some alerts
+ */
+ // name => max value before alert
+ $alerts = array(
+ // lower is better
+ // variable => max value
+ 'Aborted_clients' => 0,
+ 'Aborted_connects' => 0,
+
+ 'Binlog_cache_disk_use' => 0,
+
+ 'Created_tmp_disk_tables' => 0,
+
+ 'Handler_read_rnd' => 0,
+ 'Handler_read_rnd_next' => 0,
+
+ 'Innodb_buffer_pool_pages_dirty' => 0,
+ 'Innodb_buffer_pool_reads' => 0,
+ 'Innodb_buffer_pool_wait_free' => 0,
+ 'Innodb_log_waits' => 0,
+ 'Innodb_row_lock_time_avg' => 10, // ms
+ 'Innodb_row_lock_time_max' => 50, // ms
+ 'Innodb_row_lock_waits' => 0,
+
+ 'Slow_queries' => 0,
+ 'Delayed_errors' => 0,
+ 'Select_full_join' => 0,
+ 'Select_range_check' => 0,
+ 'Sort_merge_passes' => 0,
+ 'Opened_tables' => 0,
+ 'Table_locks_waited' => 0,
+ 'Qcache_lowmem_prunes' => 0,
+
+ 'Qcache_free_blocks' =>
+ isset($ServerStatusData->server_status['Qcache_total_blocks'])
+ ? $ServerStatusData->server_status['Qcache_total_blocks'] / 5
+ : 0,
+ 'Slow_launch_threads' => 0,
+
+ // depends on Key_read_requests
+ // normaly lower then 1:0.01
+ 'Key_reads' => isset($ServerStatusData->status['Key_read_requests'])
+ ? (0.01 * $ServerStatusData->status['Key_read_requests']) : 0,
+ // depends on Key_write_requests
+ // normaly nearly 1:1
+ 'Key_writes' => isset($ServerStatusData->status['Key_write_requests'])
+ ? (0.9 * $ServerStatusData->status['Key_write_requests']) : 0,
+
+ 'Key_buffer_fraction' => 0.5,
+
+ // alert if more than 95% of thread cache is in use
+ 'Threads_cached' => isset($ServerStatusData->variables['thread_cache_size'])
+ ? 0.95 * $ServerStatusData->variables['thread_cache_size'] : 0
+
+ // higher is better
+ // variable => min value
+ //'Handler read key' => '> ',
+ );
+
+ $retval .= PMA_getHtmlForRenderVariables(
+ $ServerStatusData,
+ $alerts,
+ $strShowStatus
+ );
+
+ return $retval;
+}
+
+/**
+ * Returns HTML for render variables list
+ *
+ * @param Object $ServerStatusData An instance of the PMA_ServerStatusData class
+ * @param Array $alerts Alert Array
+ * @param Array $strShowStatus Status Array
+ *
+ * @return string
+ */
+function PMA_getHtmlForRenderVariables($ServerStatusData, $alerts, $strShowStatus)
+{
+ $retval = '<table class="data sortable noclick" id="serverstatusvariables">';
+ $retval .= '<col class="namecol" />';
+ $retval .= '<col class="valuecol" />';
+ $retval .= '<col class="descrcol" />';
+ $retval .= '<thead>';
+ $retval .= '<tr>';
+ $retval .= '<th>' . __('Variable') . '</th>';
+ $retval .= '<th>' . __('Value') . '</th>';
+ $retval .= '<th>' . __('Description') . '</th>';
+ $retval .= '</tr>';
+ $retval .= '</thead>';
+ $retval .= '<tbody>';
+
+ $odd_row = false;
+ foreach ($ServerStatusData->status as $name => $value) {
+ $odd_row = !$odd_row;
+ $retval .= '<tr class="' . ($odd_row ? 'odd' : 'even')
+ . (isset($ServerStatusData->allocationMap[$name])
+ ?' s_' . $ServerStatusData->allocationMap[$name]
+ : '')
+ . '">';
+
+ $retval .= '<th class="name">';
+ $retval .= htmlspecialchars(str_replace('_', ' ', $name));
+ // Fields containing % are calculated,
+ // they can not be described in MySQL documentation
+ if (strpos($name, '%') === false) {
+ $retval .= PMA_Util::showMySQLDocu(
+ 'server-status-variables',
+ false,
+ 'statvar_' . $name
+ );
+ }
+ $retval .= '</th>';
+
+ $retval .= '<td class="value"><span class="formatted">';
+ if (isset($alerts[$name])) {
+ if ($value > $alerts[$name]) {
+ $retval .= '<span class="attention">';
+ } else {
+ $retval .= '<span class="allfine">';
+ }
+ }
+ if ('%' === substr($name, -1, 1)) {
+ $retval .= htmlspecialchars(PMA_Util::formatNumber($value, 0, 2)) . ' %';
+ } elseif (strpos($name, 'Uptime') !== false) {
+ $retval .= htmlspecialchars(
+ PMA_Util::timespanFormat($value)
+ );
+ } elseif (is_numeric($value) && $value > 1000) {
+ $retval .= '<abbr title="'
+ // makes available the raw value as a title
+ . htmlspecialchars(PMA_Util::formatNumber($value, 0))
+ . '">'
+ . htmlspecialchars(PMA_Util::formatNumber($value, 3, 1))
+ . '</abbr>';
+ } elseif (is_numeric($value)) {
+ $retval .= htmlspecialchars(PMA_Util::formatNumber($value, 3, 1));
+ } else {
+ $retval .= htmlspecialchars($value);
+ }
+ if (isset($alerts[$name])) {
+ $retval .= '</span>';
+ }
+ $retval .= '</span>';
+ $retval .= '<span style="display:none;" class="original">';
+ if (isset($alerts[$name])) {
+ if ($value > $alerts[$name]) {
+ $retval .= '<span class="attention">';
+ } else {
+ $retval .= '<span class="allfine">';
+ }
+ }
+ $retval .= $value;
+ if (isset($alerts[$name])) {
+ $retval .= '</span>';
+ }
+ $retval .= '</span>';
+ $retval .= '</td>';
+ $retval .= '<td class="descr">';
+
+ if (isset($strShowStatus[$name])) {
+ $retval .= $strShowStatus[$name];
+ }
+
+ if (isset($ServerStatusData->links[$name])) {
+ foreach ($ServerStatusData->links[$name] as $link_name => $link_url) {
+ if ('doc' == $link_name) {
+ $retval .= PMA_Util::showMySQLDocu($link_url);
+ } else {
+ $retval .= ' <a href="' . $link_url . '">' . $link_name . '</a>';
+ }
+ }
+ unset($link_url, $link_name);
+ }
+ $retval .= '</td>';
+ $retval .= '</tr>';
+ }
+ $retval .= '</tbody>';
+ $retval .= '</table>';
+
+ return $retval;
+}
+
+/**
+ * Returns a list of variable descriptions
+ *
+ * @return array
+ */
+function PMA_getStatusVariablesDescriptions()
+{
+ /**
+ * Messages are built using the message name
+ */
+ return array(
+ 'Aborted_clients' => __(
+ 'The number of connections that were aborted because the client died'
+ . ' without closing the connection properly.'
+ ),
+ 'Aborted_connects' => __(
+ 'The number of failed attempts to connect to the MySQL server.'
+ ),
+ 'Binlog_cache_disk_use' => __(
+ 'The number of transactions that used the temporary binary log cache'
+ . ' but that exceeded the value of binlog_cache_size and used a'
+ . ' temporary file to store statements from the transaction.'
+ ),
+ 'Binlog_cache_use' => __(
+ 'The number of transactions that used the temporary binary log cache.'
+ ),
+ 'Connections' => __(
+ 'The number of connection attempts (successful or not)'
+ . ' to the MySQL server.'
+ ),
+ 'Created_tmp_disk_tables' => __(
+ 'The number of temporary tables on disk created automatically by'
+ . ' the server while executing statements. If'
+ . ' Created_tmp_disk_tables is big, you may want to increase the'
+ . ' tmp_table_size value to cause temporary tables to be'
+ . ' memory-based instead of disk-based.'
+ ),
+ 'Created_tmp_files' => __(
+ 'How many temporary files mysqld has created.'
+ ),
+ 'Created_tmp_tables' => __(
+ 'The number of in-memory temporary tables created automatically'
+ . ' by the server while executing statements.'
+ ),
+ 'Delayed_errors' => __(
+ 'The number of rows written with INSERT DELAYED for which some'
+ . ' error occurred (probably duplicate key).'
+ ),
+ 'Delayed_insert_threads' => __(
+ 'The number of INSERT DELAYED handler threads in use. Every'
+ . ' different table on which one uses INSERT DELAYED gets'
+ . ' its own thread.'
+ ),
+ 'Delayed_writes' => __(
+ 'The number of INSERT DELAYED rows written.'
+ ),
+ 'Flush_commands' => __(
+ 'The number of executed FLUSH statements.'
+ ),
+ 'Handler_commit' => __(
+ 'The number of internal COMMIT statements.'
+ ),
+ 'Handler_delete' => __(
+ 'The number of times a row was deleted from a table.'
+ ),
+ 'Handler_discover' => __(
+ 'The MySQL server can ask the NDB Cluster storage engine if it'
+ . ' knows about a table with a given name. This is called discovery.'
+ . ' Handler_discover indicates the number of time tables have been'
+ . ' discovered.'
+ ),
+ 'Handler_read_first' => __(
+ 'The number of times the first entry was read from an index. If this'
+ . ' is high, it suggests that the server is doing a lot of full'
+ . ' index scans; for example, SELECT col1 FROM foo, assuming that'
+ . ' col1 is indexed.'
+ ),
+ 'Handler_read_key' => __(
+ 'The number of requests to read a row based on a key. If this is'
+ . ' high, it is a good indication that your queries and tables'
+ . ' are properly indexed.'
+ ),
+ 'Handler_read_next' => __(
+ 'The number of requests to read the next row in key order. This is'
+ . ' incremented if you are querying an index column with a range'
+ . ' constraint or if you are doing an index scan.'
+ ),
+ 'Handler_read_prev' => __(
+ 'The number of requests to read the previous row in key order.'
+ . ' This read method is mainly used to optimize ORDER BY … DESC.'
+ ),
+ 'Handler_read_rnd' => __(
+ 'The number of requests to read a row based on a fixed position.'
+ . ' This is high if you are doing a lot of queries that require'
+ . ' sorting of the result. You probably have a lot of queries that'
+ . ' require MySQL to scan whole tables or you have joins that'
+ . ' don\'t use keys properly.'
+ ),
+ 'Handler_read_rnd_next' => __(
+ 'The number of requests to read the next row in the data file.'
+ . ' This is high if you are doing a lot of table scans. Generally'
+ . ' this suggests that your tables are not properly indexed or that'
+ . ' your queries are not written to take advantage of the indexes'
+ . ' you have.'
+ ),
+ 'Handler_rollback' => __(
+ 'The number of internal ROLLBACK statements.'
+ ),
+ 'Handler_update' => __(
+ 'The number of requests to update a row in a table.'
+ ),
+ 'Handler_write' => __(
+ 'The number of requests to insert a row in a table.'
+ ),
+ 'Innodb_buffer_pool_pages_data' => __(
+ 'The number of pages containing data (dirty or clean).'
+ ),
+ 'Innodb_buffer_pool_pages_dirty' => __(
+ 'The number of pages currently dirty.'
+ ),
+ 'Innodb_buffer_pool_pages_flushed' => __(
+ 'The number of buffer pool pages that have been requested'
+ . ' to be flushed.'
+ ),
+ 'Innodb_buffer_pool_pages_free' => __(
+ 'The number of free pages.'
+ ),
+ 'Innodb_buffer_pool_pages_latched' => __(
+ 'The number of latched pages in InnoDB buffer pool. These are pages'
+ . ' currently being read or written or that can\'t be flushed or'
+ . ' removed for some other reason.'
+ ),
+ 'Innodb_buffer_pool_pages_misc' => __(
+ 'The number of pages busy because they have been allocated for'
+ . ' administrative overhead such as row locks or the adaptive'
+ . ' hash index. This value can also be calculated as'
+ . ' Innodb_buffer_pool_pages_total - Innodb_buffer_pool_pages_free'
+ . ' - Innodb_buffer_pool_pages_data.'
+ ),
+ 'Innodb_buffer_pool_pages_total' => __(
+ 'Total size of buffer pool, in pages.'
+ ),
+ 'Innodb_buffer_pool_read_ahead_rnd' => __(
+ 'The number of "random" read-aheads InnoDB initiated. This happens'
+ . ' when a query is to scan a large portion of a table but in'
+ . ' random order.'
+ ),
+ 'Innodb_buffer_pool_read_ahead_seq' => __(
+ 'The number of sequential read-aheads InnoDB initiated. This'
+ . ' happens when InnoDB does a sequential full table scan.'
+ ),
+ 'Innodb_buffer_pool_read_requests' => __(
+ 'The number of logical read requests InnoDB has done.'
+ ),
+ 'Innodb_buffer_pool_reads' => __(
+ 'The number of logical reads that InnoDB could not satisfy'
+ . ' from buffer pool and had to do a single-page read.'
+ ),
+ 'Innodb_buffer_pool_wait_free' => __(
+ 'Normally, writes to the InnoDB buffer pool happen in the'
+ . ' background. However, if it\'s necessary to read or create a page'
+ . ' and no clean pages are available, it\'s necessary to wait for'
+ . ' pages to be flushed first. This counter counts instances of'
+ . ' these waits. If the buffer pool size was set properly, this'
+ . ' value should be small.'
+ ),
+ 'Innodb_buffer_pool_write_requests' => __(
+ 'The number writes done to the InnoDB buffer pool.'
+ ),
+ 'Innodb_data_fsyncs' => __(
+ 'The number of fsync() operations so far.'
+ ),
+ 'Innodb_data_pending_fsyncs' => __(
+ 'The current number of pending fsync() operations.'
+ ),
+ 'Innodb_data_pending_reads' => __(
+ 'The current number of pending reads.'
+ ),
+ 'Innodb_data_pending_writes' => __(
+ 'The current number of pending writes.'
+ ),
+ 'Innodb_data_read' => __(
+ 'The amount of data read so far, in bytes.'
+ ),
+ 'Innodb_data_reads' => __(
+ 'The total number of data reads.'
+ ),
+ 'Innodb_data_writes' => __(
+ 'The total number of data writes.'
+ ),
+ 'Innodb_data_written' => __(
+ 'The amount of data written so far, in bytes.'
+ ),
+ 'Innodb_dblwr_pages_written' => __(
+ 'The number of pages that have been written for'
+ . ' doublewrite operations.'
+ ),
+ 'Innodb_dblwr_writes' => __(
+ 'The number of doublewrite operations that have been performed.'
+ ),
+ 'Innodb_log_waits' => __(
+ 'The number of waits we had because log buffer was too small and'
+ . ' we had to wait for it to be flushed before continuing.'
+ ),
+ 'Innodb_log_write_requests' => __(
+ 'The number of log write requests.'
+ ),
+ 'Innodb_log_writes' => __(
+ 'The number of physical writes to the log file.'
+ ),
+ 'Innodb_os_log_fsyncs' => __(
+ 'The number of fsync() writes done to the log file.'
+ ),
+ 'Innodb_os_log_pending_fsyncs' => __(
+ 'The number of pending log file fsyncs.'
+ ),
+ 'Innodb_os_log_pending_writes' => __(
+ 'Pending log file writes.'
+ ),
+ 'Innodb_os_log_written' => __(
+ 'The number of bytes written to the log file.'
+ ),
+ 'Innodb_pages_created' => __(
+ 'The number of pages created.'
+ ),
+ 'Innodb_page_size' => __(
+ 'The compiled-in InnoDB page size (default 16KB). Many values are'
+ . ' counted in pages; the page size allows them to be easily'
+ . ' converted to bytes.'
+ ),
+ 'Innodb_pages_read' => __(
+ 'The number of pages read.'
+ ),
+ 'Innodb_pages_written' => __(
+ 'The number of pages written.'
+ ),
+ 'Innodb_row_lock_current_waits' => __(
+ 'The number of row locks currently being waited for.'
+ ),
+ 'Innodb_row_lock_time_avg' => __(
+ 'The average time to acquire a row lock, in milliseconds.'
+ ),
+ 'Innodb_row_lock_time' => __(
+ 'The total time spent in acquiring row locks, in milliseconds.'
+ ),
+ 'Innodb_row_lock_time_max' => __(
+ 'The maximum time to acquire a row lock, in milliseconds.'
+ ),
+ 'Innodb_row_lock_waits' => __(
+ 'The number of times a row lock had to be waited for.'
+ ),
+ 'Innodb_rows_deleted' => __(
+ 'The number of rows deleted from InnoDB tables.'
+ ),
+ 'Innodb_rows_inserted' => __(
+ 'The number of rows inserted in InnoDB tables.'
+ ),
+ 'Innodb_rows_read' => __(
+ 'The number of rows read from InnoDB tables.'
+ ),
+ 'Innodb_rows_updated' => __(
+ 'The number of rows updated in InnoDB tables.'
+ ),
+ 'Key_blocks_not_flushed' => __(
+ 'The number of key blocks in the key cache that have changed but'
+ . ' haven\'t yet been flushed to disk. It used to be known as'
+ . ' Not_flushed_key_blocks.'
+ ),
+ 'Key_blocks_unused' => __(
+ 'The number of unused blocks in the key cache. You can use this'
+ . ' value to determine how much of the key cache is in use.'
+ ),
+ 'Key_blocks_used' => __(
+ 'The number of used blocks in the key cache. This value is a'
+ . ' high-water mark that indicates the maximum number of blocks'
+ . ' that have ever been in use at one time.'
+ ),
+ 'Key_buffer_fraction_%' => __(
+ 'Percentage of used key cache (calculated value)'
+ ),
+ 'Key_read_requests' => __(
+ 'The number of requests to read a key block from the cache.'
+ ),
+ 'Key_reads' => __(
+ 'The number of physical reads of a key block from disk. If Key_reads'
+ . ' is big, then your key_buffer_size value is probably too small.'
+ . ' The cache miss rate can be calculated as'
+ . ' Key_reads/Key_read_requests.'
+ ),
+ 'Key_read_ratio_%' => __(
+ 'Key cache miss calculated as rate of physical reads compared'
+ . ' to read requests (calculated value)'
+ ),
+ 'Key_write_requests' => __(
+ 'The number of requests to write a key block to the cache.'
+ ),
+ 'Key_writes' => __(
+ 'The number of physical writes of a key block to disk.'
+ ),
+ 'Key_write_ratio_%' => __(
+ 'Percentage of physical writes compared'
+ . ' to write requests (calculated value)'
+ ),
+ 'Last_query_cost' => __(
+ 'The total cost of the last compiled query as computed by the query'
+ . ' optimizer. Useful for comparing the cost of different query'
+ . ' plans for the same query. The default value of 0 means that'
+ . ' no query has been compiled yet.'
+ ),
+ 'Max_used_connections' => __(
+ 'The maximum number of connections that have been in use'
+ . ' simultaneously since the server started.'
+ ),
+ 'Not_flushed_delayed_rows' => __(
+ 'The number of rows waiting to be written in INSERT DELAYED queues.'
+ ),
+ 'Opened_tables' => __(
+ 'The number of tables that have been opened. If opened tables is'
+ . ' big, your table cache value is probably too small.'
+ ),
+ 'Open_files' => __(
+ 'The number of files that are open.'
+ ),
+ 'Open_streams' => __(
+ 'The number of streams that are open (used mainly for logging).'
+ ),
+ 'Open_tables' => __(
+ 'The number of tables that are open.'
+ ),
+ 'Qcache_free_blocks' => __(
+ 'The number of free memory blocks in query cache. High numbers can'
+ . ' indicate fragmentation issues, which may be solved by issuing'
+ . ' a FLUSH QUERY CACHE statement.'
+ ),
+ 'Qcache_free_memory' => __(
+ 'The amount of free memory for query cache.'
+ ),
+ 'Qcache_hits' => __(
+ 'The number of cache hits.'
+ ),
+ 'Qcache_inserts' => __(
+ 'The number of queries added to the cache.'
+ ),
+ 'Qcache_lowmem_prunes' => __(
+ 'The number of queries that have been removed from the cache to'
+ . ' free up memory for caching new queries. This information can'
+ . ' help you tune the query cache size. The query cache uses a'
+ . ' least recently used (LRU) strategy to decide which queries'
+ . ' to remove from the cache.'
+ ),
+ 'Qcache_not_cached' => __(
+ 'The number of non-cached queries (not cachable, or not cached'
+ . ' due to the query_cache_type setting).'
+ ),
+ 'Qcache_queries_in_cache' => __(
+ 'The number of queries registered in the cache.'
+ ),
+ 'Qcache_total_blocks' => __(
+ 'The total number of blocks in the query cache.'
+ ),
+ 'Rpl_status' => __(
+ 'The status of failsafe replication (not yet implemented).'
+ ),
+ 'Select_full_join' => __(
+ 'The number of joins that do not use indexes. If this value is'
+ . ' not 0, you should carefully check the indexes of your tables.'
+ ),
+ 'Select_full_range_join' => __(
+ 'The number of joins that used a range search on a reference table.'
+ ),
+ 'Select_range_check' => __(
+ 'The number of joins without keys that check for key usage after'
+ . ' each row. (If this is not 0, you should carefully check the'
+ . ' indexes of your tables.)'
+ ),
+ 'Select_range' => __(
+ 'The number of joins that used ranges on the first table. (It\'s'
+ . ' normally not critical even if this is big.)'
+ ),
+ 'Select_scan' => __(
+ 'The number of joins that did a full scan of the first table.'
+ ),
+ 'Slave_open_temp_tables' => __(
+ 'The number of temporary tables currently'
+ . ' open by the slave SQL thread.'
+ ),
+ 'Slave_retried_transactions' => __(
+ 'Total (since startup) number of times the replication slave SQL'
+ . ' thread has retried transactions.'
+ ),
+ 'Slave_running' => __(
+ 'This is ON if this server is a slave that is connected to a master.'
+ ),
+ 'Slow_launch_threads' => __(
+ 'The number of threads that have taken more than slow_launch_time'
+ . ' seconds to create.'
+ ),
+ 'Slow_queries' => __(
+ 'The number of queries that have taken more than long_query_time'
+ . ' seconds.'
+ ),
+ 'Sort_merge_passes' => __(
+ 'The number of merge passes the sort algorithm has had to do.'
+ . ' If this value is large, you should consider increasing the'
+ . ' value of the sort_buffer_size system variable.'
+ ),
+ 'Sort_range' => __(
+ 'The number of sorts that were done with ranges.'
+ ),
+ 'Sort_rows' => __(
+ 'The number of sorted rows.'
+ ),
+ 'Sort_scan' => __(
+ 'The number of sorts that were done by scanning the table.'
+ ),
+ 'Table_locks_immediate' => __(
+ 'The number of times that a table lock was acquired immediately.'
+ ),
+ 'Table_locks_waited' => __(
+ 'The number of times that a table lock could not be acquired'
+ . ' immediately and a wait was needed. If this is high, and you have'
+ . ' performance problems, you should first optimize your queries,'
+ . ' and then either split your table or tables or use replication.'
+ ),
+ 'Threads_cached' => __(
+ 'The number of threads in the thread cache. The cache hit rate can'
+ . ' be calculated as Threads_created/Connections. If this value is'
+ . ' red you should raise your thread_cache_size.'
+ ),
+ 'Threads_connected' => __(
+ 'The number of currently open connections.'
+ ),
+ 'Threads_created' => __(
+ 'The number of threads created to handle connections. If'
+ . ' Threads_created is big, you may want to increase the'
+ . ' thread_cache_size value. (Normally this doesn\'t give a notable'
+ . ' performance improvement if you have a good thread'
+ . ' implementation.)'
+ ),
+ 'Threads_cache_hitrate_%' => __(
+ 'Thread cache hit rate (calculated value)'
+ ),
+ 'Threads_running' => __(
+ 'The number of threads that are not sleeping.'
+ )
+ );
+}
+
+?>
diff --git a/libraries/server_user_groups.lib.php b/libraries/server_user_groups.lib.php
new file mode 100644
index 0000000000..671cc026ef
--- /dev/null
+++ b/libraries/server_user_groups.lib.php
@@ -0,0 +1,355 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * set of functions for user group handling
+ *
+ * @package PhpMyAdmin
+ */
+
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ * Return HTML to list the users belonging to a given user group
+ *
+ * @param string $userGroup user group name
+ *
+ * @return string HTML to list the users belonging to a given user group
+ */
+function PMA_getHtmlForListingUsersofAGroup($userGroup)
+{
+ $html_output = '<h2>'
+ . sprintf(__('Users of \'%s\' user group'), htmlspecialchars($userGroup))
+ . '</h2>';
+
+ $usersTable = PMA_Util::backquote($GLOBALS['cfg']['Server']['pmadb'])
+ . "." . PMA_Util::backquote($GLOBALS['cfg']['Server']['users']);
+ $sql_query = "SELECT `username` FROM " . $usersTable
+ . " WHERE `usergroup`='" . PMA_Util::sqlAddSlashes($userGroup) . "'";
+ $result = PMA_queryAsControlUser($sql_query, false);
+ if ($result) {
+ if ($GLOBALS['dbi']->numRows($result) == 0) {
+ $html_output .= '<p>'
+ . __('No users were found belonging to this user group.')
+ . '</p>';
+ } else {
+ $html_output .= '<table>'
+ . '<thead><tr><th>#</th><th>' . __('User') . '</th></tr></thead>'
+ . '<tbody>';
+ $i = 0;
+ while ($row = $GLOBALS['dbi']->fetchRow($result)) {
+ $i++;
+ $html_output .= '<tr>'
+ . '<td>' . $i . ' </td>'
+ . '<td>' . htmlspecialchars($row[0]) . '</td>'
+ . '</tr>';
+ }
+ $html_output .= '</tbody>'
+ . '</table>';
+ }
+ }
+ $GLOBALS['dbi']->freeResult($result);
+ return $html_output;
+}
+
+/**
+ * Returns HTML for the 'user groups' table
+ *
+ * @return string HTML for the 'user groups' table
+ */
+function PMA_getHtmlForUserGroupsTable()
+{
+ $tabs = PMA_Util::getMenuTabList();
+
+ $html_output = '<h2>' . __('User groups') . '</h2>';
+ $groupTable = PMA_Util::backquote($GLOBALS['cfg']['Server']['pmadb'])
+ . "." . PMA_Util::backquote($GLOBALS['cfg']['Server']['usergroups']);
+ $sql_query = "SELECT * FROM " . $groupTable . " ORDER BY `usergroup` ASC";
+ $result = PMA_queryAsControlUser($sql_query, false);
+
+ if ($result && $GLOBALS['dbi']->numRows($result)) {
+ $html_output .= '<form name="userGroupsForm" id="userGroupsForm"'
+ . ' action="server_privileges.php" method="post">';
+ $html_output .= PMA_URL_getHiddenInputs();
+ $html_output .= '<table id="userGroupsTable">';
+ $html_output .= '<thead><tr>';
+ $html_output .= '<th style="white-space: nowrap">'
+ . __('User group') . '</th>';
+ $html_output .= '<th>' . __('Server level tabs') . '</th>';
+ $html_output .= '<th>' . __('Database level tabs') . '</th>';
+ $html_output .= '<th>' . __('Table level tabs') . '</th>';
+ $html_output .= '<th>' . __('Action') . '</th>';
+ $html_output .= '</tr></thead>';
+ $html_output .= '<tbody>';
+
+ $odd = true;
+ $userGroups = array();
+ while ($row = $GLOBALS['dbi']->fetchAssoc($result)) {
+ $groupName = $row['usergroup'];
+ if (! isset($userGroups[$groupName])) {
+ $userGroups[$groupName] = array();
+ }
+ $userGroups[$groupName][$row['tab']] = $row['allowed'];
+ }
+ foreach ($userGroups as $groupName => $tabs) {
+ $html_output .= '<tr class="' . ($odd ? 'odd' : 'even') . '">';
+ $html_output .= '<td>' . htmlspecialchars($groupName) . '</td>';
+ $html_output .= '<td>' . _getAllowedTabNames($tabs, 'server') . '</td>';
+ $html_output .= '<td>' . _getAllowedTabNames($tabs, 'db') . '</td>';
+ $html_output .= '<td>' . _getAllowedTabNames($tabs, 'table') . '</td>';
+
+ $html_output .= '<td>';
+ $html_output .= '<a class="" href="server_user_groups.php'
+ . PMA_URL_getCommon(
+ array(
+ 'viewUsers' => 1, 'userGroup' => $groupName
+ )
+ )
+ . '">'
+ . PMA_Util::getIcon('b_usrlist.png', __('View users')) . '</a>';
+ $html_output .= '&nbsp;&nbsp;';
+ $html_output .= '<a class="" href="server_user_groups.php'
+ . PMA_URL_getCommon(
+ array(
+ 'editUserGroup' => 1, 'userGroup' => $groupName
+ )
+ )
+ . '">'
+ . PMA_Util::getIcon('b_edit.png', __('Edit')) . '</a>';
+ $html_output .= '&nbsp;&nbsp;';
+ $html_output .= '<a class="deleteUserGroup ajax"'
+ . ' href="server_user_groups.php'
+ . PMA_URL_getCommon(
+ array(
+ 'deleteUserGroup' => 1, 'userGroup' => $groupName
+ )
+ )
+ . '">'
+ . PMA_Util::getIcon('b_drop.png', __('Delete')) . '</a>';
+ $html_output .= '</td>';
+
+ $html_output .= '</tr>';
+
+ $odd = ! $odd;
+ }
+
+ $html_output .= '</tbody>';
+ $html_output .= '</table>';
+ $html_output .= '</form>';
+ }
+ $GLOBALS['dbi']->freeResult($result);
+
+ $html_output .= '<fieldset id="fieldset_add_user_group">';
+ $html_output .= '<a href="server_user_groups.php'
+ . PMA_URL_getCommon(array('addUserGroup' => 1)) . '">'
+ . PMA_Util::getIcon('b_usradd.png')
+ . __('Add user group') . '</a>';
+ $html_output .= '</fieldset>';
+
+ return $html_output;
+}
+
+/**
+ * Returns the list of allowed menu tab names
+ * based on a data row from usergroup table.
+ *
+ * @param array $row row of usergroup table
+ * @param string $level 'server', 'db' or 'table'
+ *
+ * @return string comma seperated list of allowed menu tab names
+ */
+function _getAllowedTabNames($row, $level)
+{
+ $tabNames = array();
+ $tabs = PMA_Util::getMenuTabList($level);
+ foreach ($tabs as $tab => $tabName) {
+ if (! isset($row[$level . '_' . $tab])
+ || $row[$level . '_' . $tab] == 'Y'
+ ) {
+ $tabNames[] = $tabName;
+ }
+ }
+ return implode(', ', $tabNames);
+}
+
+/**
+ * Deletes a user group
+ *
+ * @param string $userGroup user group name
+ *
+ * @return void
+ */
+function PMA_deleteUserGroup($userGroup)
+{
+ $userTable = PMA_Util::backquote($GLOBALS['cfg']['Server']['pmadb'])
+ . "." . PMA_Util::backquote($GLOBALS['cfg']['Server']['users']);
+ $groupTable = PMA_Util::backquote($GLOBALS['cfg']['Server']['pmadb'])
+ . "." . PMA_Util::backquote($GLOBALS['cfg']['Server']['usergroups']);
+ $sql_query = "DELETE FROM " . $userTable
+ . " WHERE `usergroup`='" . PMA_Util::sqlAddSlashes($userGroup) . "'";
+ PMA_queryAsControlUser($sql_query, true);
+ $sql_query = "DELETE FROM " . $groupTable
+ . " WHERE `usergroup`='" . PMA_Util::sqlAddSlashes($userGroup) . "'";
+ PMA_queryAsControlUser($sql_query, true);
+}
+
+/**
+ * Returns HTML for add/edit user group dialog
+ *
+ * @param string $userGroup name of the user group in case of editing
+ *
+ * @return string HTML for add/edit user group dialog
+ */
+function PMA_getHtmlToEditUserGroup($userGroup = null)
+{
+ $html_output = '';
+ if ($userGroup == null) {
+ $html_output .= '<h2>' . __('Add user group') . '</h2>';
+ } else {
+ $html_output .= '<h2>'
+ . sprintf(__('Edit user group: \'%s\''), htmlspecialchars($userGroup))
+ . '</h2>';
+ }
+
+ $html_output .= '<form name="userGroupForm" id="userGroupForm"'
+ . ' action="server_user_groups.php" method="post">';
+ $urlParams = array();
+ if ($userGroup != null) {
+ $urlParams['userGroup'] = $userGroup;
+ $urlParams['editUserGroupSubmit'] = '1';
+ } else {
+ $urlParams['addUserGroupSubmit'] = '1';
+ }
+ $html_output .= PMA_URL_getHiddenInputs($urlParams);
+
+ $html_output .= '<fieldset id="fieldset_user_group_rights">';
+ $html_output .= '<legend>' . __('User group menu assignments')
+ . '&nbsp;&nbsp;&nbsp;'
+ . '<input type="checkbox" class="checkall_box" title="Check All">'
+ . '<label for="addUsersForm_checkall">' . __('Check All') .'</label>'
+ . '</legend>';
+
+ if ($userGroup == null) {
+ $html_output .= '<label for="userGroup">' . __('Group name:') . '</label>';
+ $html_output .= '<input type="text" name="userGroup" '
+ . 'autocomplete="off" required="required" />';
+ $html_output .= '<div class="clearfloat"></div>';
+ }
+
+ $allowedTabs = array(
+ 'server' => array(),
+ 'db' => array(),
+ 'table' => array()
+ );
+ if ($userGroup != null) {
+ $groupTable = PMA_Util::backquote($GLOBALS['cfg']['Server']['pmadb'])
+ . "." . PMA_Util::backquote($GLOBALS['cfg']['Server']['usergroups']);
+ $sql_query = "SELECT * FROM " . $groupTable
+ . " WHERE `usergroup`='" . PMA_Util::sqlAddSlashes($userGroup) . "'";
+ $result = PMA_queryAsControlUser($sql_query, false);
+ if ($result) {
+ while ($row = $GLOBALS['dbi']->fetchAssoc($result)) {
+ $key = $row['tab'];
+ $value = $row['allowed'];
+ if (substr($key, 0, 7) == 'server_' && $value == 'Y') {
+ $allowedTabs['server'][] = substr($key, 7);
+ } elseif (substr($key, 0, 3) == 'db_' && $value == 'Y') {
+ $allowedTabs['db'][] = substr($key, 3);
+ } elseif (substr($key, 0, 6) == 'table_' && $value == 'Y') {
+ $allowedTabs['table'][] = substr($key, 6);
+ }
+ }
+ }
+ $GLOBALS['dbi']->freeResult($result);
+ }
+
+ $html_output .= _getTabList(
+ __('Server-level tabs'), 'server', $allowedTabs['server']
+ );
+ $html_output .= _getTabList(
+ __('Database-level tabs'), 'db', $allowedTabs['db']
+ );
+ $html_output .= _getTabList(
+ __('Table-level tabs'), 'table', $allowedTabs['table']
+ );
+
+ $html_output .= '</fieldset>';
+
+ $html_output .= '<fieldset id="fieldset_user_group_rights_footer"'
+ . ' class="tblFooters">';
+ $html_output .= '<input type="submit" name="update_privs" value="Go">';
+ $html_output .= '</fieldset>';
+
+ return $html_output;
+}
+
+/**
+ * Returns HTML for checkbox groups to choose
+ * tabs of 'server', 'db' or 'table' levels.
+ *
+ * @param string $title title of the checkbox group
+ * @param string $level 'server', 'db' or 'table'
+ * @param array $selected array of selected allowed tabs
+ *
+ * @return string HTML for checkbox groups
+ */
+function _getTabList($title, $level, $selected)
+{
+ $tabs = PMA_Util::getMenuTabList($level);
+ $html_output = '<fieldset>';
+ $html_output .= '<legend>' . $title . '</legend>';
+ foreach ($tabs as $tab => $tabName) {
+ $html_output .= '<div class="item">';
+ $html_output .= '<input type="checkbox" class="checkall"'
+ . (in_array($tab, $selected) ? ' checked="checked"' : '')
+ . ' name="' . $level . '_' . $tab . '" value="Y" />';
+ $html_output .= '<label for="' . $level . '_' . $tab . '">'
+ . '<code>' . $tabName . '</code>'
+ . '</label>';
+ $html_output .= '</div>';
+ }
+ $html_output .= '</fieldset>';
+ return $html_output;
+}
+
+/**
+ * Add/update a user group with allowed menu tabs.
+ *
+ * @param string $userGroup user group name
+ * @param boolean $new whether this is a new user group
+ *
+ * @return void
+ */
+function PMA_editUserGroup($userGroup, $new = false)
+{
+ $tabs = PMA_Util::getMenuTabList();
+ $groupTable = PMA_Util::backquote($GLOBALS['cfg']['Server']['pmadb'])
+ . "." . PMA_Util::backquote($GLOBALS['cfg']['Server']['usergroups']);
+
+ if (! $new) {
+ $sql_query = "DELETE FROM " . $groupTable
+ . " WHERE `usergroup`='" . PMA_Util::sqlAddSlashes($userGroup) . "';";
+ PMA_queryAsControlUser($sql_query, true);
+ }
+
+ $sql_query = "INSERT INTO " . $groupTable
+ . "(`usergroup`, `tab`, `allowed`)"
+ . " VALUES ";
+ $first = true;
+ foreach ($tabs as $tabGroupName => $tabGroup) {
+ foreach ($tabs[$tabGroupName] as $tab => $tabName) {
+ if (! $first) {
+ $sql_query .= ", ";
+ }
+ $tabName = $tabGroupName . '_' . $tab;
+ $allowed = isset($_REQUEST[$tabName]) && $_REQUEST[$tabName] == 'Y';
+ $sql_query .= "('" . $userGroup . "', '" . $tabName . "', '"
+ . ($allowed ? "Y" : "N") . "')";
+ $first = false;
+ }
+ }
+ $sql_query .= ";";
+ PMA_queryAsControlUser($sql_query, true);
+}
+?> \ No newline at end of file
diff --git a/libraries/server_users.lib.php b/libraries/server_users.lib.php
new file mode 100644
index 0000000000..57d59b7657
--- /dev/null
+++ b/libraries/server_users.lib.php
@@ -0,0 +1,55 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * set of common functions for sub tabs in server level `Users` page
+ *
+ * @package PhpMyAdmin
+ */
+
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ * Get HTML for secondary level menu tabs on 'Users' page
+ *
+ * @param string $selfUrl Url of the file
+ *
+ * @return string HTML for secondary level menu tabs on 'Users' page
+ */
+function PMA_getHtmlForSubMenusOnUsersPage($selfUrl)
+{
+ $url_params = PMA_URL_getCommon();
+ $items = array(
+ array(
+ 'name' => __('Users overview'),
+ 'url' => 'server_privileges.php',
+ 'specific_params' => '&viewing_mode=server'
+ ),
+ array(
+ 'name' => __('User groups'),
+ 'url' => 'server_user_groups.php',
+ 'specific_params' => ''
+ )
+ );
+
+ $retval = '<ul id="topmenu2">';
+ foreach ($items as $item) {
+ $class = '';
+ if ($item['url'] === $selfUrl) {
+ $class = ' class="tabactive"';
+ }
+ $retval .= '<li>';
+ $retval .= '<a' . $class;
+ $retval .= ' href="' . $item['url']
+ . '?' . $url_params . $item['specific_params'] . '">';
+ $retval .= $item['name'];
+ $retval .= '</a>';
+ $retval .= '</li>';
+ }
+ $retval .= '</ul>';
+ $retval .= '<div class="clearfloat"></div>';
+
+ return $retval;
+}
+?> \ No newline at end of file
diff --git a/libraries/server_variables.lib.php b/libraries/server_variables.lib.php
new file mode 100644
index 0000000000..57c5bdf3b3
--- /dev/null
+++ b/libraries/server_variables.lib.php
@@ -0,0 +1,1684 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+
+/**
+ * functions for displaying server variables
+ *
+ * @usedby server_variables.php
+ *
+ * @package PhpMyAdmin
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ * Get Ajax return when $_REQUEST['type'] === 'getval'
+ *
+ * @param Array $variable_doc_links documentation links
+ *
+ * @return null
+ */
+function PMA_getAjaxReturnForGetVal($variable_doc_links)
+{
+ $response = PMA_Response::getInstance();
+
+ // Send with correct charset
+ header('Content-Type: text/html; charset=UTF-8');
+ $varValue = $GLOBALS['dbi']->fetchSingleRow(
+ 'SHOW GLOBAL VARIABLES WHERE Variable_name="'
+ . PMA_Util::sqlAddSlashes($_REQUEST['varName']) . '";',
+ 'NUM'
+ );
+ if (isset($variable_doc_links[$_REQUEST['varName']][3])
+ && $variable_doc_links[$_REQUEST['varName']][3] == 'byte'
+ ) {
+ $response->addJSON(
+ 'message',
+ implode(
+ ' ', PMA_Util::formatByteDown($varValue[1], 3, 3)
+ )
+ );
+ } else {
+ $response->addJSON(
+ 'message',
+ $varValue[1]
+ );
+ }
+}
+/**
+ * Get Ajax return when $_REQUEST['type'] === 'setval'
+ *
+ * @param Array $variable_doc_links documentation links
+ *
+ * @return null
+ */
+function PMA_getAjaxReturnForSetVal($variable_doc_links)
+{
+ $response = PMA_Response::getInstance();
+
+ $value = $_REQUEST['varValue'];
+ $matches = array();
+
+ if (isset($variable_doc_links[$_REQUEST['varName']][3])
+ && $variable_doc_links[$_REQUEST['varName']][3] == 'byte'
+ && preg_match(
+ '/^\s*(\d+(\.\d+)?)\s*(mb|kb|mib|kib|gb|gib)\s*$/i',
+ $value,
+ $matches
+ )
+ ) {
+ $exp = array(
+ 'kb' => 1,
+ 'kib' => 1,
+ 'mb' => 2,
+ 'mib' => 2,
+ 'gb' => 3,
+ 'gib' => 3
+ );
+ $value = floatval($matches[1]) * PMA_Util::pow(
+ 1024,
+ $exp[strtolower($matches[3])]
+ );
+ } else {
+ $value = PMA_Util::sqlAddSlashes($value);
+ }
+
+ if (! is_numeric($value)) {
+ $value="'" . $value . "'";
+ }
+
+ if (! preg_match("/[^a-zA-Z0-9_]+/", $_REQUEST['varName'])
+ && $GLOBALS['dbi']->query(
+ 'SET GLOBAL ' . $_REQUEST['varName'] . ' = ' . $value
+ )
+ ) {
+ // Some values are rounded down etc.
+ $varValue = $GLOBALS['dbi']->fetchSingleRow(
+ 'SHOW GLOBAL VARIABLES WHERE Variable_name="'
+ . PMA_Util::sqlAddSlashes($_REQUEST['varName'])
+ . '";', 'NUM'
+ );
+ $response->addJSON(
+ 'variable',
+ PMA_formatVariable(
+ $_REQUEST['varName'],
+ $varValue[1],
+ $variable_doc_links
+ )
+ );
+ } else {
+ $response->isSuccess(false);
+ $response->addJSON(
+ 'error',
+ __('Setting variable failed')
+ );
+ }
+}
+
+/**
+ * Format Variable
+ *
+ * @param string $name variable name
+ * @param number $value variable value
+ * @param array $variable_doc_links documentation links
+ *
+ * @return string formatted string
+ */
+function PMA_formatVariable($name, $value, $variable_doc_links)
+{
+ if (is_numeric($value)) {
+ if (isset($variable_doc_links[$name][3])
+ && $variable_doc_links[$name][3]=='byte'
+ ) {
+ return '<abbr title="'
+ . PMA_Util::formatNumber($value, 0) . '">'
+ . implode(' ', PMA_Util::formatByteDown($value, 3, 3))
+ . '</abbr>';
+ } else {
+ return PMA_Util::formatNumber($value, 0);
+ }
+ }
+ return htmlspecialchars($value);
+}
+
+/**
+ * Prints link templates
+ *
+ * @return string
+ */
+function PMA_getHtmlForLinkTemplates()
+{
+ $url = 'server_variables.php' . PMA_URL_getCommon(array());
+ $output = '<a style="display: none;" href="#" class="editLink">';
+ $output .= PMA_Util::getIcon('b_edit.png', __('Edit')) . '</a>';
+ $output .= '<a style="display: none;" href="'
+ . $url . '" class="ajax saveLink">';
+ $output .= PMA_Util::getIcon('b_save.png', __('Save')) . '</a> ';
+ $output .= '<a style="display: none;" href="#" class="cancelLink">';
+ $output .= PMA_Util::getIcon('b_close.png', __('Cancel')) . '</a> ';
+ $output .= PMA_Util::getImage(
+ 'b_help.png',
+ __('Documentation'),
+ array(
+ 'style' => 'display:none',
+ 'id' => 'docImage'
+ )
+ );
+
+ return $output;
+}
+
+/**
+ * Prints Html for Server Variables
+ *
+ * @param Array $variable_doc_links documentation links
+ *
+ * @return string
+ */
+function PMA_getHtmlForServerVariables($variable_doc_links)
+{
+ $value = ! empty($_REQUEST['filter'])
+ ? htmlspecialchars($_REQUEST['filter'])
+ : '';
+ $output = '<fieldset id="tableFilter">'
+ . '<legend>' . __('Filters') . '</legend>'
+ . '<div class="formelement">'
+ . '<label for="filterText">' . __('Containing the word:') . '</label>'
+ . '<input name="filterText" type="text" id="filterText"'
+ . ' style="vertical-align: baseline;" value="' . $value . '" />'
+ . '</div>'
+ . '</fieldset>';
+
+ $output .= '<div id="serverVariables" class="data filteredData noclick">'
+ . '<div class="var-header var-row">'
+ . '<div class="var-name">' . __('Variable') . '</div>'
+ . '<div class="var-value valueHeader">'
+ . __('Session value') . ' / ' . __('Global value')
+ . '</div>'
+ . '<div style="clear:both"></div>'
+ . '</div>';
+
+ $output .= PMA_getHtmlForServerVariablesItems($variable_doc_links);
+
+ $output .= '</div>';
+
+ return $output;
+}
+
+
+/**
+ * Prints Html for Server Variables Items
+ *
+ * @param Array $variable_doc_links documentation links
+ *
+ * @return string
+ */
+function PMA_getHtmlForServerVariablesItems($variable_doc_links)
+{
+ /**
+ * Sends the queries and buffers the results
+ */
+ $serverVarsSession
+ = $GLOBALS['dbi']->fetchResult('SHOW SESSION VARIABLES;', 0, 1);
+ $serverVars = $GLOBALS['dbi']->fetchResult('SHOW GLOBAL VARIABLES;', 0, 1);
+
+ $output = '';
+ $odd_row = true;
+ foreach ($serverVars as $name => $value) {
+ $has_session_value = isset($serverVarsSession[$name])
+ && $serverVarsSession[$name] != $value;
+ $row_class = ($odd_row ? ' odd' : ' even')
+ . ($has_session_value ? ' diffSession' : '');
+
+ $output .= '<div class="var-row' . $row_class . '">'
+ . '<div class="var-name">';
+
+ // To display variable documentation link
+ if (isset($variable_doc_links[$name])) {
+ $output .= '<span title="'
+ . htmlspecialchars(str_replace('_', ' ', $name)) . '">';
+ $output .= PMA_Util::showMySQLDocu(
+ $variable_doc_links[$name][1],
+ false,
+ $variable_doc_links[$name][2] . '_' . $variable_doc_links[$name][0],
+ true
+ );
+ $output .= htmlspecialchars(str_replace('_', ' ', $name));
+ $output .= '</a>';
+ $output .= '</span>';
+ } else {
+ $output .= htmlspecialchars(str_replace('_', ' ', $name));
+ }
+ $output .= '</div>'
+ . '<div class="var-value value'
+ . ($GLOBALS['dbi']->isSuperuser() ? ' editable' : '') . '">&nbsp;'
+ . PMA_formatVariable($name, $value, $variable_doc_links)
+ . '</div>'
+ . '<div style="clear:both"></div>'
+ . '</div>';
+
+ if ($has_session_value) {
+ $output .= '<div class="var-row' . ($odd_row ? ' odd' : ' even') . '">'
+ . '<div class="var-name session">(' . __('Session value') . ')</div>'
+ . '<div class="var-value value">&nbsp;'
+ . PMA_formatVariable(
+ $name,
+ $serverVarsSession[$name],
+ $variable_doc_links
+ ) . '</div>'
+ . '<div style="clear:both"></div>'
+ . '</div>';
+ }
+
+ $odd_row = ! $odd_row;
+ }
+
+ return $output;
+}
+
+/**
+ * Returns Array of documentation links
+ *
+ * $variable_doc_links[string $name] = array(
+ * string $anchor,
+ * string $chapter,
+ * string $type);
+ * string $name: name of the system variable
+ * string $anchor: anchor to the documentation page
+ * string $chapter: chapter of "HTML, one page per chapter" documentation
+ * string $type: type of system variable
+ * string $format: if set to 'byte' it will format the variable
+ * with PMA_Util::formatByteDown()
+ *
+ * @return array
+ */
+function PMA_getArrayForDocumentLinks()
+{
+ $variable_doc_links = array();
+ $variable_doc_links['auto_increment_increment'] = array(
+ 'auto_increment_increment',
+ 'replication-options-master',
+ 'sysvar');
+ $variable_doc_links['auto_increment_offset'] = array(
+ 'auto_increment_offset',
+ 'replication-options-master',
+ 'sysvar');
+ $variable_doc_links['autocommit'] = array(
+ 'autocommit',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['automatic_sp_privileges'] = array(
+ 'automatic_sp_privileges',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['back_log'] = array(
+ 'back_log',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['basedir'] = array(
+ 'basedir',
+ 'server-options',
+ 'option_mysqld');
+ $variable_doc_links['big_tables'] = array(
+ 'big-tables',
+ 'server-options',
+ 'option_mysqld');
+ $variable_doc_links['bind_address'] = array(
+ 'bind-address',
+ 'server-options',
+ 'option_mysqld');
+ $variable_doc_links['binlog_cache_size'] = array(
+ 'binlog_cache_size',
+ 'replication-options-binary-log',
+ 'sysvar',
+ 'byte');
+ $variable_doc_links['binlog_direct_non_transactional_updates'] = array(
+ 'binlog_direct_non_transactional_updates',
+ 'replication-options-binary-log',
+ 'sysvar');
+ $variable_doc_links['binlog_format'] = array(
+ 'binlog-format',
+ 'server-options',
+ 'sysvar');
+ $variable_doc_links['binlog_stmt_cache_size'] = array(
+ 'binlog_stmt_cache_size',
+ 'replication-options-binary-log',
+ 'sysvar',
+ 'byte');
+ $variable_doc_links['bulk_insert_buffer_size'] = array(
+ 'bulk_insert_buffer_size',
+ 'server-system-variables',
+ 'sysvar',
+ 'byte');
+ $variable_doc_links['character_set_client'] = array(
+ 'character_set_client',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['character_set_connection'] = array(
+ 'character_set_connection',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['character_set_database'] = array(
+ 'character_set_database',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['character_set_filesystem'] = array(
+ 'character-set-filesystem',
+ 'server-options',
+ 'option_mysqld');
+ $variable_doc_links['character_set_results'] = array(
+ 'character_set_results',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['character_set_server'] = array(
+ 'character-set-server',
+ 'server-options',
+ 'option_mysqld');
+ $variable_doc_links['character_set_system'] = array(
+ 'character_set_system',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['character_sets_dir'] = array(
+ 'character-sets-dir',
+ 'server-options',
+ 'option_mysqld');
+ $variable_doc_links['collation_connection'] = array(
+ 'collation_connection',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['collation_database'] = array(
+ 'collation_database',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['collation_server'] = array(
+ 'collation-server',
+ 'server-options',
+ 'option_mysqld');
+ $variable_doc_links['completion_type'] = array(
+ 'completion_type',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['concurrent_insert'] = array(
+ 'concurrent_insert',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['connect_timeout'] = array(
+ 'connect_timeout',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['datadir'] = array(
+ 'datadir',
+ 'server-options',
+ 'option_mysqld');
+ $variable_doc_links['date_format'] = array(
+ 'date_format',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['datetime_format'] = array(
+ 'datetime_format',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['debug'] = array(
+ 'debug',
+ 'server-options',
+ 'option_mysqld');
+ $variable_doc_links['debug_sync'] = array(
+ 'debug_sync',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['default_storage_engine'] = array(
+ 'default-storage-engine',
+ 'server-options',
+ 'option_mysqld');
+ $variable_doc_links['default_week_format'] = array(
+ 'default_week_format',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['delay_key_write'] = array(
+ 'delay-key-write',
+ 'server-options',
+ 'option_mysqld');
+ $variable_doc_links['delayed_insert_limit'] = array(
+ 'delayed_insert_limit',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['delayed_insert_timeout'] = array(
+ 'delayed_insert_timeout',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['delayed_queue_size'] = array(
+ 'delayed_queue_size',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['div_precision_increment'] = array(
+ 'div_precision_increment',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['engine_condition_pushdown'] = array(
+ 'engine-condition-pushdown',
+ 'server-options',
+ 'option_mysqld');
+ $variable_doc_links['error_count'] = array(
+ 'error_count',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['event_scheduler'] = array(
+ 'event-scheduler',
+ 'server-options',
+ 'option_mysqld');
+ $variable_doc_links['expire_logs_days'] = array(
+ 'expire_logs_days',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['external_user'] = array(
+ 'external_user',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['flush'] = array(
+ 'flush',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['flush_time'] = array(
+ 'flush_time',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['foreign_key_checks'] = array(
+ 'foreign_key_checks',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['ft_boolean_syntax'] = array(
+ 'ft_boolean_syntax',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['ft_max_word_len'] = array(
+ 'ft_max_word_len',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['ft_min_word_len'] = array(
+ 'ft_min_word_len',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['ft_query_expansion_limit'] = array(
+ 'ft_query_expansion_limit',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['ft_stopword_file'] = array(
+ 'ft_stopword_file',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['general_log'] = array(
+ 'general-log',
+ 'server-options',
+ 'option_mysqld');
+ $variable_doc_links['general_log_file'] = array(
+ 'general_log_file',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['group_concat_max_len'] = array(
+ 'group_concat_max_len',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['have_compress'] = array(
+ 'have_compress',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['have_crypt'] = array(
+ 'have_crypt',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['have_csv'] = array(
+ 'have_csv',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['have_dynamic_loading'] = array(
+ 'have_dynamic_loading',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['have_geometry'] = array(
+ 'have_geometry',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['have_innodb'] = array(
+ 'have_innodb',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['have_openssl'] = array(
+ 'have_openssl',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['have_partitioning'] = array(
+ 'have_partitioning',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['have_profiling'] = array(
+ 'have_profiling',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['have_query_cache'] = array(
+ 'have_query_cache',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['have_rtree_keys'] = array(
+ 'have_rtree_keys',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['have_ssl'] = array(
+ 'have_ssl',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['have_symlink'] = array(
+ 'have_symlink',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['hostname'] = array(
+ 'hostname',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['identity'] = array(
+ 'identity',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['ignore_builtin_innodb'] = array(
+ 'ignore-builtin-innodb',
+ 'innodb-parameters',
+ 'option_mysqld');
+ $variable_doc_links['init_connect'] = array(
+ 'init_connect',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['init_file'] = array(
+ 'init-file',
+ 'server-options',
+ 'option_mysqld');
+ $variable_doc_links['init_slave'] = array(
+ 'init_slave',
+ 'replication-options-slave',
+ 'sysvar');
+ $variable_doc_links['innodb_adaptive_flushing'] = array(
+ 'innodb_adaptive_flushing',
+ 'innodb-parameters',
+ 'sysvar');
+ $variable_doc_links['innodb_adaptive_hash_index'] = array(
+ 'innodb_adaptive_hash_index',
+ 'innodb-parameters',
+ 'sysvar');
+ $variable_doc_links['innodb_additional_mem_pool_size'] = array(
+ 'innodb_additional_mem_pool_size',
+ 'innodb-parameters',
+ 'sysvar',
+ 'byte');
+ $variable_doc_links['innodb_autoextend_increment'] = array(
+ 'innodb_autoextend_increment',
+ 'innodb-parameters',
+ 'sysvar');
+ $variable_doc_links['innodb_autoinc_lock_mode'] = array(
+ 'innodb_autoinc_lock_mode',
+ 'innodb-parameters',
+ 'sysvar');
+ $variable_doc_links['innodb_buffer_pool_instances'] = array(
+ 'innodb_buffer_pool_instances',
+ 'innodb-parameters',
+ 'sysvar');
+ $variable_doc_links['innodb_buffer_pool_size'] = array(
+ 'innodb_buffer_pool_size',
+ 'innodb-parameters',
+ 'sysvar',
+ 'byte');
+ $variable_doc_links['innodb_change_buffering'] = array(
+ 'innodb_change_buffering',
+ 'innodb-parameters',
+ 'sysvar');
+ $variable_doc_links['innodb_checksums'] = array(
+ 'innodb_checksums',
+ 'innodb-parameters',
+ 'sysvar');
+ $variable_doc_links['innodb_commit_concurrency'] = array(
+ 'innodb_commit_concurrency',
+ 'innodb-parameters',
+ 'sysvar');
+ $variable_doc_links['innodb_concurrency_tickets'] = array(
+ 'innodb_concurrency_tickets',
+ 'innodb-parameters',
+ 'sysvar');
+ $variable_doc_links['innodb_data_file_path'] = array(
+ 'innodb_data_file_path',
+ 'innodb-parameters',
+ 'sysvar');
+ $variable_doc_links['innodb_data_home_dir'] = array(
+ 'innodb_data_home_dir',
+ 'innodb-parameters',
+ 'sysvar');
+ $variable_doc_links['innodb_doublewrite'] = array(
+ 'innodb_doublewrite',
+ 'innodb-parameters',
+ 'sysvar');
+ $variable_doc_links['innodb_fast_shutdown'] = array(
+ 'innodb_fast_shutdown',
+ 'innodb-parameters',
+ 'sysvar');
+ $variable_doc_links['innodb_file_format'] = array(
+ 'innodb_file_format',
+ 'innodb-parameters',
+ 'sysvar');
+ $variable_doc_links['innodb_file_format_check'] = array(
+ 'innodb_file_format_check',
+ 'innodb-parameters',
+ 'sysvar');
+ $variable_doc_links['innodb_file_format_max'] = array(
+ 'innodb_file_format_max',
+ 'innodb-parameters',
+ 'sysvar');
+ $variable_doc_links['innodb_file_per_table'] = array(
+ 'innodb_file_per_table',
+ 'innodb-parameters',
+ 'sysvar');
+ $variable_doc_links['innodb_flush_log_at_trx_commit'] = array(
+ 'innodb_flush_log_at_trx_commit',
+ 'innodb-parameters',
+ 'sysvar');
+ $variable_doc_links['innodb_flush_method'] = array(
+ 'innodb_flush_method',
+ 'innodb-parameters',
+ 'sysvar');
+ $variable_doc_links['innodb_force_recovery'] = array(
+ 'innodb_force_recovery',
+ 'innodb-parameters',
+ 'sysvar');
+ $variable_doc_links['innodb_io_capacity'] = array(
+ 'innodb_io_capacity',
+ 'innodb-parameters',
+ 'sysvar');
+ $variable_doc_links['innodb_lock_wait_timeout'] = array(
+ 'innodb_lock_wait_timeout',
+ 'innodb-parameters',
+ 'sysvar');
+ $variable_doc_links['innodb_locks_unsafe_for_binlog'] = array(
+ 'innodb_locks_unsafe_for_binlog',
+ 'innodb-parameters',
+ 'sysvar');
+ $variable_doc_links['innodb_log_buffer_size'] = array(
+ 'innodb_log_buffer_size',
+ 'innodb-parameters',
+ 'sysvar',
+ 'byte');
+ $variable_doc_links['innodb_log_file_size'] = array(
+ 'innodb_log_file_size',
+ 'innodb-parameters',
+ 'sysvar',
+ 'byte');
+ $variable_doc_links['innodb_log_files_in_group'] = array(
+ 'innodb_log_files_in_group',
+ 'innodb-parameters',
+ 'sysvar');
+ $variable_doc_links['innodb_log_group_home_dir'] = array(
+ 'innodb_log_group_home_dir',
+ 'innodb-parameters',
+ 'sysvar');
+ $variable_doc_links['innodb_max_dirty_pages_pct'] = array(
+ 'innodb_max_dirty_pages_pct',
+ 'innodb-parameters',
+ 'sysvar');
+ $variable_doc_links['innodb_max_purge_lag'] = array(
+ 'innodb_max_purge_lag',
+ 'innodb-parameters',
+ 'sysvar');
+ $variable_doc_links['innodb_mirrored_log_groups'] = array(
+ 'innodb_mirrored_log_groups',
+ 'innodb-parameters',
+ 'sysvar');
+ $variable_doc_links['innodb_old_blocks_pct'] = array(
+ 'innodb_old_blocks_pct',
+ 'innodb-parameters',
+ 'sysvar');
+ $variable_doc_links['innodb_old_blocks_time'] = array(
+ 'innodb_old_blocks_time',
+ 'innodb-parameters',
+ 'sysvar');
+ $variable_doc_links['innodb_open_files'] = array(
+ 'innodb_open_files',
+ 'innodb-parameters',
+ 'sysvar');
+ $variable_doc_links['innodb_purge_batch_size'] = array(
+ 'innodb_purge_batch_size',
+ 'innodb-parameters',
+ 'sysvar');
+ $variable_doc_links['innodb_purge_threads'] = array(
+ 'innodb_purge_threads',
+ 'innodb-parameters',
+ 'sysvar');
+ $variable_doc_links['innodb_read_ahead_threshold'] = array(
+ 'innodb_read_ahead_threshold',
+ 'innodb-parameters',
+ 'sysvar');
+ $variable_doc_links['innodb_read_io_threads'] = array(
+ 'innodb_read_io_threads',
+ 'innodb-parameters',
+ 'sysvar');
+ $variable_doc_links['innodb_replication_delay'] = array(
+ 'innodb_replication_delay',
+ 'innodb-parameters',
+ 'sysvar');
+ $variable_doc_links['innodb_rollback_on_timeout'] = array(
+ 'innodb_rollback_on_timeout',
+ 'innodb-parameters',
+ 'sysvar');
+ $variable_doc_links['innodb_spin_wait_delay'] = array(
+ 'innodb_spin_wait_delay',
+ 'innodb-parameters',
+ 'sysvar');
+ $variable_doc_links['innodb_stats_on_metadata'] = array(
+ 'innodb_stats_on_metadata',
+ 'innodb-parameters',
+ 'sysvar');
+ $variable_doc_links['innodb_stats_sample_pages'] = array(
+ 'innodb_stats_sample_pages',
+ 'innodb-parameters',
+ 'sysvar');
+ $variable_doc_links['innodb_strict_mode'] = array(
+ 'innodb_strict_mode',
+ 'innodb-parameters',
+ 'sysvar');
+ $variable_doc_links['innodb_support_xa'] = array(
+ 'innodb_support_xa',
+ 'innodb-parameters',
+ 'sysvar');
+ $variable_doc_links['innodb_sync_spin_loops'] = array(
+ 'innodb_sync_spin_loops',
+ 'innodb-parameters',
+ 'sysvar');
+ $variable_doc_links['innodb_table_locks'] = array(
+ 'innodb_table_locks',
+ 'innodb-parameters',
+ 'sysvar');
+ $variable_doc_links['innodb_thread_concurrency'] = array(
+ 'innodb_thread_concurrency',
+ 'innodb-parameters',
+ 'sysvar');
+ $variable_doc_links['innodb_thread_sleep_delay'] = array(
+ 'innodb_thread_sleep_delay',
+ 'innodb-parameters',
+ 'sysvar');
+ $variable_doc_links['innodb_use_native_aio'] = array(
+ 'innodb_use_native_aio',
+ 'innodb-parameters',
+ 'sysvar');
+ $variable_doc_links['innodb_use_sys_malloc'] = array(
+ 'innodb_use_sys_malloc',
+ 'innodb-parameters',
+ 'sysvar');
+ $variable_doc_links['innodb_version'] = array(
+ 'innodb_version',
+ 'innodb-parameters',
+ 'sysvar');
+ $variable_doc_links['innodb_write_io_threads'] = array(
+ 'innodb_write_io_threads',
+ 'innodb-parameters',
+ 'sysvar');
+ $variable_doc_links['insert_id'] = array(
+ 'insert_id',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['interactive_timeout'] = array(
+ 'interactive_timeout',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['join_buffer_size'] = array(
+ 'join_buffer_size',
+ 'server-system-variables',
+ 'sysvar',
+ 'byte');
+ $variable_doc_links['keep_files_on_create'] = array(
+ 'keep_files_on_create',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['key_buffer_size'] = array(
+ 'key_buffer_size',
+ 'server-system-variables',
+ 'sysvar',
+ 'byte');
+ $variable_doc_links['key_cache_age_threshold'] = array(
+ 'key_cache_age_threshold',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['key_cache_block_size'] = array(
+ 'key_cache_block_size',
+ 'server-system-variables',
+ 'sysvar',
+ 'byte');
+ $variable_doc_links['key_cache_division_limit'] = array(
+ 'key_cache_division_limit',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['language'] = array(
+ 'language',
+ 'server-options',
+ 'option_mysqld');
+ $variable_doc_links['large_files_support'] = array(
+ 'large_files_support',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['large_page_size'] = array(
+ 'large_page_size',
+ 'server-system-variables',
+ 'sysvar',
+ 'byte');
+ $variable_doc_links['large_pages'] = array(
+ 'large-pages',
+ 'server-options',
+ 'option_mysqld');
+ $variable_doc_links['last_insert_id'] = array(
+ 'last_insert_id',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['lc_messages'] = array(
+ 'lc-messages',
+ 'server-options',
+ 'option_mysqld');
+ $variable_doc_links['lc_messages_dir'] = array(
+ 'lc-messages-dir',
+ 'server-options',
+ 'option_mysqld');
+ $variable_doc_links['lc_time_names'] = array(
+ 'lc_time_names',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['license'] = array(
+ 'license',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['local_infile'] = array(
+ 'local_infile',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['lock_wait_timeout'] = array(
+ 'lock_wait_timeout',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['locked_in_memory'] = array(
+ 'locked_in_memory',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['log'] = array(
+ 'log',
+ 'server-options',
+ 'option_mysqld');
+ $variable_doc_links['log_bin'] = array(
+ 'log_bin',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['log-bin'] = array(
+ 'log-bin',
+ 'replication-options-binary-log',
+ 'option_mysqld');
+ $variable_doc_links['log_bin_trust_function_creators'] = array(
+ 'log-bin-trust-function-creators',
+ 'replication-options-binary-log',
+ 'option_mysqld');
+ $variable_doc_links['log_error'] = array(
+ 'log-error',
+ 'server-options',
+ 'option_mysqld');
+ $variable_doc_links['log_output'] = array(
+ 'log-output',
+ 'server-options',
+ 'option_mysqld');
+ $variable_doc_links['log_queries_not_using_indexes'] = array(
+ 'log-queries-not-using-indexes',
+ 'server-options',
+ 'option_mysqld');
+ $variable_doc_links['log_slave_updates'] = array(
+ 'log-slave-updates',
+ 'replication-options-slave',
+ 'option_mysqld');
+ $variable_doc_links['log_slow_queries'] = array(
+ 'log-slow-queries',
+ 'server-options',
+ 'option_mysqld');
+ $variable_doc_links['log_warnings'] = array(
+ 'log-warnings',
+ 'server-options',
+ 'option_mysqld');
+ $variable_doc_links['long_query_time'] = array(
+ 'long_query_time',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['low_priority_updates'] = array(
+ 'low-priority-updates',
+ 'server-options',
+ 'option_mysqld');
+ $variable_doc_links['lower_case_file_system'] = array(
+ 'lower_case_file_system',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['lower_case_table_names'] = array(
+ 'lower_case_table_names',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['master-bind'] = array(
+ '',
+ 'replication-options',
+ 0);
+ $variable_doc_links['max_allowed_packet'] = array(
+ 'max_allowed_packet',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['max_binlog_cache_size'] = array(
+ 'max_binlog_cache_size',
+ 'replication-options-binary-log',
+ 'sysvar',
+ 'byte');
+ $variable_doc_links['max_binlog_size'] = array(
+ 'max_binlog_size',
+ 'replication-options-binary-log',
+ 'sysvar',
+ 'byte');
+ $variable_doc_links['max_binlog_stmt_cache_size'] = array(
+ 'max_binlog_stmt_cache_size',
+ 'replication-options-binary-log',
+ 'sysvar',
+ 'byte');
+ $variable_doc_links['max_connect_errors'] = array(
+ 'max_connect_errors',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['max_connections'] = array(
+ 'max_connections',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['max_delayed_threads'] = array(
+ 'max_delayed_threads',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['max_error_count'] = array(
+ 'max_error_count',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['max_heap_table_size'] = array(
+ 'max_heap_table_size',
+ 'server-system-variables',
+ 'sysvar',
+ 'byte');
+ $variable_doc_links['max_insert_delayed_threads'] = array(
+ 'max_insert_delayed_threads',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['max_join_size'] = array(
+ 'max_join_size',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['max_length_for_sort_data'] = array(
+ 'max_length_for_sort_data',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['max_prepared_stmt_count'] = array(
+ 'max_prepared_stmt_count',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['max_relay_log_size'] = array(
+ 'max_relay_log_size',
+ 'server-system-variables',
+ 'sysvar',
+ 'byte');
+ $variable_doc_links['max_seeks_for_key'] = array(
+ 'max_seeks_for_key',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['max_sort_length'] = array(
+ 'max_sort_length',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['max_sp_recursion_depth'] = array(
+ 'max_sp_recursion_depth',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['max_tmp_tables'] = array(
+ 'max_tmp_tables',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['max_user_connections'] = array(
+ 'max_user_connections',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['max_write_lock_count'] = array(
+ 'max_write_lock_count',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['memlock'] = array(
+ 'memlock',
+ 'server-options',
+ 'option_mysqld');
+ $variable_doc_links['min_examined_row_limit'] = array(
+ 'min-examined-row-limit',
+ 'server-options',
+ 'option_mysqld');
+ $variable_doc_links['myisam_data_pointer_size'] = array(
+ 'myisam_data_pointer_size',
+ 'server-system-variables',
+ 'sysvar',
+ 'byte');
+ $variable_doc_links['myisam_max_sort_file_size'] = array(
+ 'myisam_max_sort_file_size',
+ 'server-system-variables',
+ 'sysvar',
+ 'byte');
+ $variable_doc_links['myisam_mmap_size'] = array(
+ 'myisam_mmap_size',
+ 'server-system-variables',
+ 'sysvar',
+ 'byte');
+ $variable_doc_links['myisam_recover_options'] = array(
+ 'myisam_recover_options',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['myisam_repair_threads'] = array(
+ 'myisam_repair_threads',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['myisam_sort_buffer_size'] = array(
+ 'myisam_sort_buffer_size',
+ 'server-system-variables',
+ 'sysvar',
+ 'byte');
+ $variable_doc_links['myisam_stats_method'] = array(
+ 'myisam_stats_method',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['myisam_use_mmap'] = array(
+ 'myisam_use_mmap',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['named_pipe'] = array(
+ 'named_pipe',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['net_buffer_length'] = array(
+ 'net_buffer_length',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['net_read_timeout'] = array(
+ 'net_read_timeout',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['net_retry_count'] = array(
+ 'net_retry_count',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['net_write_timeout'] = array(
+ 'net_write_timeout',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['new'] = array(
+ 'new',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['old'] = array(
+ 'old',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['old_alter_table'] = array(
+ 'old-alter-table',
+ 'server-options',
+ 'option_mysqld');
+ $variable_doc_links['old_passwords'] = array(
+ 'old-passwords',
+ 'server-options',
+ 'option_mysqld');
+ $variable_doc_links['open_files_limit'] = array(
+ 'open-files-limit',
+ 'server-options',
+ 'option_mysqld');
+ $variable_doc_links['optimizer_prune_level'] = array(
+ 'optimizer_prune_level',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['optimizer_search_depth'] = array(
+ 'optimizer_search_depth',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['optimizer_switch'] = array(
+ 'optimizer_switch',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['partition'] = array(
+ 'partition',
+ 'server-options',
+ 'option_mysqld');
+ $variable_doc_links['performance_schema'] = array(
+ 'performance_schema',
+ 'performance-schema-system-variables',
+ 'sysvar');
+ $variable_doc_links['performance_schema_events_waits_history_long_size'] = array(
+ 'performance_schema_events_waits_history_long_size',
+ 'performance-schema-system-variables',
+ 'sysvar');
+ $variable_doc_links['performance_schema_events_waits_history_size'] = array(
+ 'performance_schema_events_waits_history_size',
+ 'performance-schema-system-variables',
+ 'sysvar');
+ $variable_doc_links['performance_schema_max_cond_classes'] = array(
+ 'performance_schema_max_cond_classes',
+ 'performance-schema-system-variables',
+ 'sysvar');
+ $variable_doc_links['performance_schema_max_cond_instances'] = array(
+ 'performance_schema_max_cond_instances',
+ 'performance-schema-system-variables',
+ 'sysvar');
+ $variable_doc_links['performance_schema_max_file_classes'] = array(
+ 'performance_schema_max_file_classes',
+ 'performance-schema-system-variables',
+ 'sysvar');
+ $variable_doc_links['performance_schema_max_file_handles'] = array(
+ 'performance_schema_max_file_handles',
+ 'performance-schema-system-variables',
+ 'sysvar');
+ $variable_doc_links['performance_schema_max_file_instances'] = array(
+ 'performance_schema_max_file_instances',
+ 'performance-schema-system-variables',
+ 'sysvar');
+ $variable_doc_links['performance_schema_max_mutex_classes'] = array(
+ 'performance_schema_max_mutex_classes',
+ 'performance-schema-system-variables',
+ 'sysvar');
+ $variable_doc_links['performance_schema_max_mutex_instances'] = array(
+ 'performance_schema_max_mutex_instances',
+ 'performance-schema-system-variables',
+ 'sysvar');
+ $variable_doc_links['performance_schema_max_rwlock_classes'] = array(
+ 'performance_schema_max_rwlock_classes',
+ 'performance-schema-system-variables',
+ 'sysvar');
+ $variable_doc_links['performance_schema_max_rwlock_instances'] = array(
+ 'performance_schema_max_rwlock_instances',
+ 'performance-schema-system-variables',
+ 'sysvar');
+ $variable_doc_links['performance_schema_max_table_handles'] = array(
+ 'performance_schema_max_table_handles',
+ 'performance-schema-system-variables',
+ 'sysvar');
+ $variable_doc_links['performance_schema_max_table_instances'] = array(
+ 'performance_schema_max_table_instances',
+ 'performance-schema-system-variables',
+ 'sysvar');
+ $variable_doc_links['performance_schema_max_thread_classes'] = array(
+ 'performance_schema_max_thread_classes',
+ 'performance-schema-system-variables',
+ 'sysvar');
+ $variable_doc_links['performance_schema_max_thread_instances'] = array(
+ 'performance_schema_max_thread_instances',
+ 'performance-schema-system-variables',
+ 'sysvar');
+ $variable_doc_links['pid_file'] = array(
+ 'pid-file',
+ 'server-options',
+ 'option_mysqld');
+ $variable_doc_links['plugin_dir'] = array(
+ 'plugin_dir',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['port'] = array(
+ 'port',
+ 'server-options',
+ 'option_mysqld');
+ $variable_doc_links['preload_buffer_size'] = array(
+ 'preload_buffer_size',
+ 'server-system-variables',
+ 'sysvar',
+ 'byte');
+ $variable_doc_links['profiling'] = array(
+ 'profiling',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['profiling_history_size'] = array(
+ 'profiling_history_size',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['protocol_version'] = array(
+ 'protocol_version',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['proxy_user'] = array(
+ 'proxy_user',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['pseudo_thread_id'] = array(
+ 'pseudo_thread_id',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['query_alloc_block_size'] = array(
+ 'query_alloc_block_size',
+ 'server-system-variables',
+ 'sysvar',
+ 'byte');
+ $variable_doc_links['query_cache_limit'] = array(
+ 'query_cache_limit',
+ 'server-system-variables',
+ 'sysvar',
+ 'byte');
+ $variable_doc_links['query_cache_min_res_unit'] = array(
+ 'query_cache_min_res_unit',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['query_cache_size'] = array(
+ 'query_cache_size',
+ 'server-system-variables',
+ 'sysvar',
+ 'byte');
+ $variable_doc_links['query_cache_type'] = array(
+ 'query_cache_type',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['query_cache_wlock_invalidate'] = array(
+ 'query_cache_wlock_invalidate',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['query_prealloc_size'] = array(
+ 'query_prealloc_size',
+ 'server-system-variables',
+ 'sysvar',
+ 'byte');
+ $variable_doc_links['rand_seed1'] = array(
+ 'rand_seed1',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['rand_seed2'] = array(
+ 'rand_seed2',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['range_alloc_block_size'] = array(
+ 'range_alloc_block_size',
+ 'server-system-variables',
+ 'sysvar',
+ 'byte');
+ $variable_doc_links['read_buffer_size'] = array(
+ 'read_buffer_size',
+ 'server-system-variables',
+ 'sysvar',
+ 'byte');
+ $variable_doc_links['read_only'] = array(
+ 'read_only',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['read_rnd_buffer_size'] = array(
+ 'read_rnd_buffer_size',
+ 'server-system-variables',
+ 'sysvar',
+ 'byte');
+ $variable_doc_links['relay-log-index'] = array(
+ 'relay-log-index',
+ 'replication-options-slave',
+ 'option_mysqld');
+ $variable_doc_links['relay_log_index'] = array(
+ 'relay_log_index',
+ 'replication-options-slave',
+ 'sysvar');
+ $variable_doc_links['relay_log_info_file'] = array(
+ 'relay_log_info_file',
+ 'replication-options-slave',
+ 'sysvar');
+ $variable_doc_links['relay_log_purge'] = array(
+ 'relay_log_purge',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['relay_log_recovery'] = array(
+ 'relay_log_recovery',
+ 'replication-options-slave',
+ 'sysvar');
+ $variable_doc_links['relay_log_space_limit'] = array(
+ 'relay_log_space_limit',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['report_host'] = array(
+ 'report-host',
+ 'replication-options-slave',
+ 'option_mysqld');
+ $variable_doc_links['report_password'] = array(
+ 'report-password',
+ 'replication-options-slave',
+ 'option_mysqld');
+ $variable_doc_links['report_port'] = array(
+ 'report-port',
+ 'replication-options-slave',
+ 'option_mysqld');
+ $variable_doc_links['report_user'] = array(
+ 'report-user',
+ 'replication-options-slave',
+ 'option_mysqld');
+ $variable_doc_links['rpl_recovery_rank'] = array(
+ 'rpl_recovery_rank',
+ 'replication-options-slave',
+ 'option_mysqld');
+ $variable_doc_links['rpl_semi_sync_master_enabled'] = array(
+ 'rpl_semi_sync_master_enabled',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['rpl_semi_sync_master_timeout'] = array(
+ 'rpl_semi_sync_master_timeout',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['rpl_semi_sync_master_trace_level'] = array(
+ 'rpl_semi_sync_master_trace_level',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['rpl_semi_sync_master_wait_no_slave'] = array(
+ 'rpl_semi_sync_master_wait_no_slave',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['rpl_semi_sync_slave_enabled'] = array(
+ 'rpl_semi_sync_slave_enabled',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['rpl_semi_sync_slave_trace_level'] = array(
+ 'rpl_semi_sync_slave_trace_level',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['safe_show_database'] = array(
+ 'safe-show-database',
+ 'server-options',
+ 'option_mysqld');
+ $variable_doc_links['secure_auth'] = array(
+ 'secure-auth',
+ 'server-options',
+ 'option_mysqld');
+ $variable_doc_links['secure_file_priv'] = array(
+ 'secure-file-priv',
+ 'server-options',
+ 'option_mysqld');
+ $variable_doc_links['server_id'] = array(
+ 'server-id',
+ 'replication-options',
+ 'option_mysqld');
+ $variable_doc_links['shared_memory'] = array(
+ 'shared_memory',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['shared_memory_base_name'] = array(
+ 'shared_memory_base_name',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['skip_external_locking'] = array(
+ 'skip-external-locking',
+ 'server-options',
+ 'option_mysqld');
+ $variable_doc_links['skip_name_resolve'] = array(
+ 'skip-name-resolve',
+ 'server-options',
+ 'option_mysqld');
+ $variable_doc_links['skip_networking'] = array(
+ 'skip-networking',
+ 'server-options',
+ 'option_mysqld');
+ $variable_doc_links['skip_show_database'] = array(
+ 'skip-show-database',
+ 'server-options',
+ 'option_mysqld');
+ $variable_doc_links['slave_compressed_protocol'] = array(
+ 'slave_compressed_protocol',
+ 'replication-options-slave',
+ 'sysvar');
+ $variable_doc_links['slave_exec_mode'] = array(
+ 'slave_exec_mode',
+ 'replication-options-slave',
+ 'sysvar');
+ $variable_doc_links['slave_load_tmpdir'] = array(
+ 'slave-load-tmpdir',
+ 'replication-options-slave',
+ 'option_mysqld');
+ $variable_doc_links['slave_net_timeout'] = array(
+ 'slave-net-timeout',
+ 'replication-options-slave',
+ 'option_mysqld');
+ $variable_doc_links['slave_skip_errors'] = array(
+ 'slave-skip-errors',
+ 'replication-options-slave',
+ 'option_mysqld');
+ $variable_doc_links['slave_transaction_retries'] = array(
+ 'slave_transaction_retries',
+ 'replication-options-slave',
+ 'sysvar');
+ $variable_doc_links['slave_type_conversions'] = array(
+ 'slave_type_conversions',
+ 'replication-options-slave',
+ 'sysvar');
+ $variable_doc_links['slow_launch_time'] = array(
+ 'slow_launch_time',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['slow_query_log'] = array(
+ 'slow-query-log',
+ 'server-options',
+ 'server-system-variables');
+ $variable_doc_links['slow_query_log_file'] = array(
+ 'slow_query_log_file',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['socket'] = array(
+ 'socket',
+ 'server-options',
+ 'option_mysqld');
+ $variable_doc_links['sort_buffer_size'] = array(
+ 'sort_buffer_size',
+ 'server-system-variables',
+ 'sysvar',
+ 'byte');
+ $variable_doc_links['sql_auto_is_null'] = array(
+ 'sql_auto_is_null',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['sql_big_selects'] = array(
+ 'sql_big_selects',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['sql_big_tables'] = array(
+ 'big-tables',
+ 'server-options',
+ 'server-system-variables');
+ $variable_doc_links['sql_buffer_result'] = array(
+ 'sql_buffer_result',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['sql_log_bin'] = array(
+ 'sql_log_bin',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['sql_log_off'] = array(
+ 'sql_log_off',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['sql_log_update'] = array(
+ 'sql_log_update',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['sql_low_priority_updates'] = array(
+ 'sql_low_priority_updates',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['sql_max_join_size'] = array(
+ 'sql_max_join_size',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['sql_mode'] = array(
+ 'sql-mode',
+ 'server-options',
+ 'option_mysqld');
+ $variable_doc_links['sql_notes'] = array(
+ 'sql_notes',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['sql_quote_show_create'] = array(
+ 'sql_quote_show_create',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['sql_safe_updates'] = array(
+ 'sql_safe_updates',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['sql_select_limit'] = array(
+ 'sql_select_limit',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['sql_slave_skip_counter'] = array(
+ 'sql_slave_skip_counter',
+ 'replication-options-slave',
+ 'sysvar');
+ $variable_doc_links['sql_warnings'] = array(
+ 'sql_warnings',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['ssl_ca'] = array(
+ 'ssl-ca',
+ 'ssl-options',
+ 'option_general');
+ $variable_doc_links['ssl_capath'] = array(
+ 'ssl-capath',
+ 'ssl-options',
+ 'option_general');
+ $variable_doc_links['ssl_cert'] = array(
+ 'ssl-cert',
+ 'ssl-options',
+ 'option_general');
+ $variable_doc_links['ssl_cipher'] = array(
+ 'ssl-cipher',
+ 'ssl-options',
+ 'option_general');
+ $variable_doc_links['ssl_key'] = array(
+ 'ssl-key',
+ 'ssl-options',
+ 'option_general');
+ $variable_doc_links['storage_engine'] = array(
+ 'storage_engine',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['sync_binlog'] = array(
+ 'sync_binlog',
+ 'replication-options-binary-log',
+ 'sysvar');
+ $variable_doc_links['sync_frm'] = array(
+ 'sync_frm',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['sync_master_info'] = array(
+ 'sync_master_info',
+ 'replication-options-slave',
+ 'sysvar');
+ $variable_doc_links['sync_relay_log'] = array(
+ 'sync_relay_log',
+ 'replication-options-slave',
+ 'sysvar');
+ $variable_doc_links['sync_relay_log_info'] = array(
+ 'sync_relay_log_info',
+ 'replication-options-slave',
+ 'sysvar');
+ $variable_doc_links['system_time_zone'] = array(
+ 'system_time_zone',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['table_definition_cache'] = array(
+ 'table_definition_cache',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['table_lock_wait_timeout'] = array(
+ 'table_lock_wait_timeout',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['table_open_cache'] = array(
+ 'table_open_cache',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['table_type'] = array(
+ 'table_type',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['thread_cache_size'] = array(
+ 'thread_cache_size',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['thread_concurrency'] = array(
+ 'thread_concurrency',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['thread_handling'] = array(
+ 'thread_handling',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['thread_stack'] = array(
+ 'thread_stack',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['time_format'] = array(
+ 'time_format',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['time_zone'] = array(
+ 'time_zone',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['timed_mutexes'] = array(
+ 'timed_mutexes',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['timestamp'] = array(
+ 'timestamp',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['tmp_table_size'] = array(
+ 'tmp_table_size',
+ 'server-system-variables',
+ 'sysvar',
+ 'byte');
+ $variable_doc_links['tmpdir'] = array(
+ 'tmpdir',
+ 'server-options',
+ 'option_mysqld');
+ $variable_doc_links['transaction_alloc_block_size'] = array(
+ 'transaction_alloc_block_size',
+ 'server-system-variables',
+ 'sysvar',
+ 'byte');
+ $variable_doc_links['transaction_prealloc_size'] = array(
+ 'transaction_prealloc_size',
+ 'server-system-variables',
+ 'sysvar',
+ 'byte');
+ $variable_doc_links['tx_isolation'] = array(
+ 'tx_isolation',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['unique_checks'] = array(
+ 'unique_checks',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['updatable_views_with_limit'] = array(
+ 'updatable_views_with_limit',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['version'] = array(
+ 'version',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['version_comment'] = array(
+ 'version_comment',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['version_compile_machine'] = array(
+ 'version_compile_machine',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['version_compile_os'] = array(
+ 'version_compile_os',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['wait_timeout'] = array(
+ 'wait_timeout',
+ 'server-system-variables',
+ 'sysvar');
+ $variable_doc_links['warning_count'] = array(
+ 'warning_count',
+ 'server-system-variables',
+ 'sysvar');
+ return $variable_doc_links;
+}
+
+?>
+
+
diff --git a/libraries/session.inc.php b/libraries/session.inc.php
new file mode 100644
index 0000000000..e77ad7d574
--- /dev/null
+++ b/libraries/session.inc.php
@@ -0,0 +1,125 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * session handling
+ *
+ * @todo add failover or warn if sessions are not configured properly
+ * @todo add an option to use mm-module for session handler
+ *
+ * @package PhpMyAdmin
+ * @see http://www.php.net/session
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+// verify if PHP supports session, die if it does not
+
+if (!@function_exists('session_name')) {
+ PMA_warnMissingExtension('session', true);
+} elseif (ini_get('session.auto_start') == true && session_name() != 'phpMyAdmin') {
+ // Do not delete the existing session, it might be used by other
+ // applications; instead just close it.
+ session_write_close();
+}
+
+// disable starting of sessions before all settings are done
+// does not work, besides how it is written in php manual
+//ini_set('session.auto_start', 0);
+
+// session cookie settings
+session_set_cookie_params(
+ 0, $GLOBALS['PMA_Config']->getCookiePath(),
+ '', $GLOBALS['PMA_Config']->isHttps(), true
+);
+
+// cookies are safer (use @ini_set() in case this function is disabled)
+@ini_set('session.use_cookies', true);
+
+// optionally set session_save_path
+$path = $GLOBALS['PMA_Config']->get('SessionSavePath');
+if (!empty($path)) {
+ session_save_path($path);
+}
+
+// but not all user allow cookies
+@ini_set('session.use_only_cookies', false);
+// do not force transparent session ids, see bug #3398788
+//@ini_set('session.use_trans_sid', true);
+@ini_set(
+ 'url_rewriter.tags',
+ 'a=href,frame=src,input=src,form=fakeentry,fieldset='
+);
+//ini_set('arg_separator.output', '&amp;');
+
+// delete session/cookies when browser is closed
+@ini_set('session.cookie_lifetime', 0);
+
+// warn but dont work with bug
+@ini_set('session.bug_compat_42', false);
+@ini_set('session.bug_compat_warn', true);
+
+// use more secure session ids
+@ini_set('session.hash_function', 1);
+
+// some pages (e.g. stylesheet) may be cached on clients, but not in shared
+// proxy servers
+session_cache_limiter('private');
+
+// start the session
+// on some servers (for example, sourceforge.net), we get a permission error
+// on the session data directory, so I add some "@"
+
+// See bug #1538132. This would block normal behavior on a cluster
+//ini_set('session.save_handler', 'files');
+
+$session_name = 'phpMyAdmin';
+@session_name($session_name);
+
+if (! isset($_COOKIE[$session_name])) {
+ // on first start of session we check for errors
+ // f.e. session dir cannot be accessed - session file not created
+ $orig_error_count = $GLOBALS['error_handler']->countErrors();
+ $r = session_start();
+ if ($r !== true
+ || $orig_error_count != $GLOBALS['error_handler']->countErrors()
+ ) {
+ setcookie($session_name, '', 1);
+ /*
+ * Session initialization is done before selecting language, so we
+ * can not use translations here.
+ */
+ PMA_fatalError(
+ 'Cannot start session without errors, please check errors given '
+ . 'in your PHP and/or webserver log file and configure your PHP '
+ . 'installation properly. Also ensure that cookies are enabled '
+ . 'in your browser.'
+ );
+ }
+ unset($orig_error_count);
+} else {
+ session_start();
+}
+
+/**
+ * Token which is used for authenticating access queries.
+ * (we use "space PMA_token space" to prevent overwriting)
+ */
+if (! isset($_SESSION[' PMA_token '])) {
+ $_SESSION[' PMA_token '] = md5(uniqid(rand(), true));
+}
+
+/**
+ * tries to secure session from hijacking and fixation
+ * should be called before login and after successfull login
+ * (only required if sensitive information stored in session)
+ *
+ * @return void
+ */
+function PMA_secureSession()
+{
+ // prevent session fixation and XSS
+ session_regenerate_id(true);
+ $_SESSION[' PMA_token '] = md5(uniqid(rand(), true));
+}
+?>
diff --git a/libraries/special_schema_links.lib.php b/libraries/special_schema_links.lib.php
new file mode 100644
index 0000000000..e28848d54f
--- /dev/null
+++ b/libraries/special_schema_links.lib.php
@@ -0,0 +1,406 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Links configuration for MySQL system tables
+ *
+ * @package PhpMyAdmin
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ * This global variable represent the details for generating links inside
+ * special schemas like mysql, information_schema etc.
+ * Major element represent a schema.
+ * All the strings in this array represented in lower case
+ * This global variable has not modified anywhere
+ *
+ * Variable structure ex:
+ * $GLOBALS['special_schema_links'] = array(
+ * // Database name is the major element
+ * 'mysql' => array(
+ * // Table name
+ * 'db' => array(
+ * // Column name
+ * 'user' => array(
+ * // Main url param (can be an array where represent sql)
+ * 'link_param' => 'username',
+ * // Other url params
+ * 'link_dependancy_params' => array(
+ * 0 => array(
+ * // URL parameter name
+ * // (can be array where url param has static value)
+ * 'param_info' => 'hostname',
+ * // Column name related to url param
+ * 'column_name' => 'host'
+ * )
+ * ),
+ * // Page to link
+ * 'default_page' => 'server_privileges.php'
+ * )
+ * )
+ * )
+ * );
+ *
+ */
+$GLOBALS['special_schema_links'] = array(
+ 'mysql' => array(
+ 'db' => array(
+ 'db' => array(
+ 'link_param' => 'db',
+ 'default_page' => $GLOBALS['cfg']['DefaultTabDatabase']
+ ),
+ 'user' => array(
+ 'link_param' => 'username',
+ 'link_dependancy_params' => array(
+ 0 => array(
+ 'param_info' => 'hostname',
+ 'column_name' => 'host'
+ )
+ ),
+ 'default_page' => 'server_privileges.php'
+ )
+ ),
+ 'proc' => array(
+ 'db' => array(
+ 'link_param' => 'db',
+ 'default_page' => $GLOBALS['cfg']['DefaultTabDatabase']
+ )
+ ),
+ 'user' => array(
+ 'user' => array(
+ 'link_param' => 'username',
+ 'link_dependancy_params' => array(
+ 0 => array(
+ 'param_info' => 'hostname',
+ 'column_name' => 'host'
+ )
+ ),
+ 'default_page' => 'server_privileges.php'
+ )
+ )
+ ),
+ 'information_schema' => array(
+ 'columns' => array(
+ 'table_schema' => array(
+ 'link_param' => 'db',
+ 'default_page' => $GLOBALS['cfg']['DefaultTabDatabase']
+ ),
+ 'table_name' => array(
+ 'link_param' => 'table',
+ 'link_dependancy_params' => array(
+ 0 => array(
+ 'param_info' => 'db',
+ 'column_name' => 'table_schema'
+ )
+ ),
+ 'default_page' => $GLOBALS['cfg']['DefaultTabTable']
+ ),
+ 'column_name' => array(
+ 'link_param' => array(
+ 'sql_query',
+ 'table_schema',
+ 'table_name'
+ ),
+ 'link_dependancy_params' => array(
+ 0 => array(
+ 'param_info' => 'db',
+ 'column_name' => 'table_schema'
+ ),
+ 1 => array(
+ 'param_info' => 'table',
+ 'column_name' => 'table_name'
+ )
+ ),
+ 'default_page' => 'sql.php'
+ )
+ ),
+ 'column_privileges' => array(
+ 'table_schema' => array(
+ 'link_param' => 'db',
+ 'default_page' => $GLOBALS['cfg']['DefaultTabDatabase']
+ )
+ ),
+ 'events' => array(
+ 'event_schema' => array(
+ 'link_param' => 'db',
+ 'default_page' => $GLOBALS['cfg']['DefaultTabDatabase']
+ )
+ ),
+ 'files' => array(
+ 'table_schema' => array(
+ 'link_param' => 'db',
+ 'default_page' => $GLOBALS['cfg']['DefaultTabDatabase']
+ )
+ ),
+ 'key_column_usage' => array(
+ 'table_name' => array(
+ 'link_param' => 'table',
+ 'link_dependancy_params' => array(
+ 0 => array(
+ 'param_info' => 'db',
+ 'column_name' => 'constraint_schema'
+ )
+ ),
+ 'default_page' => $GLOBALS['cfg']['DefaultTabTable']
+ ),
+ 'column_name' => array(
+ 'link_param' => array(
+ 'sql_query',
+ 'table_schema',
+ 'table_name'
+ ),
+ 'link_dependancy_params' => array(
+ 0 => array(
+ 'param_info' => 'db',
+ 'column_name' => 'table_schema'
+ ),
+ 1 => array(
+ 'param_info' => 'table',
+ 'column_name' => 'table_name'
+ )
+ ),
+ 'default_page' => 'sql.php'
+ ),
+ 'constraint_schema' => array(
+ 'link_param' => 'db',
+ 'default_page' => $GLOBALS['cfg']['DefaultTabDatabase']
+ ),
+ 'table_schema' => array(
+ 'link_param' => 'db',
+ 'default_page' => $GLOBALS['cfg']['DefaultTabDatabase']
+ ),
+ 'referenced_table_schema' => array(
+ 'link_param' => 'db',
+ 'default_page' => $GLOBALS['cfg']['DefaultTabDatabase']
+ ),
+ 'referenced_table_name' => array(
+ 'link_param' => 'table',
+ 'link_dependancy_params' => array(
+ 0 => array(
+ 'param_info' => 'db',
+ 'column_name' => 'referenced_table_schema'
+ )
+ ),
+ 'default_page' => $GLOBALS['cfg']['DefaultTabTable']
+ ),
+ 'referenced_column_name' => array(
+ 'link_param' => array(
+ 'sql_query',
+ 'referenced_table_schema',
+ 'referenced_table_name'
+ ),
+ 'link_dependancy_params' => array(
+ 0 => array(
+ 'param_info' => 'db',
+ 'column_name' => 'referenced_table_schema'
+ ),
+ 1 => array(
+ 'param_info' => 'table',
+ 'column_name' => 'referenced_table_name'
+ )
+ ),
+ 'default_page' => 'sql.php'
+ )
+ ),
+ 'parameters' => array(
+ 'specific_schema' => array(
+ 'link_param' => 'db',
+ 'default_page' => $GLOBALS['cfg']['DefaultTabDatabase']
+ )
+ ),
+ 'partitions' => array(
+ 'table_schema' => array(
+ 'link_param' => 'db',
+ 'default_page' => $GLOBALS['cfg']['DefaultTabDatabase']
+ ),
+ 'table_name' => array(
+ 'link_param' => 'table',
+ 'link_dependancy_params' => array(
+ 0 => array(
+ 'param_info' => 'db',
+ 'column_name' => 'table_schema'
+ )
+ ),
+ 'default_page' => $GLOBALS['cfg']['DefaultTabTable']
+ )
+ ),
+ 'processlist' => array(
+ 'db' => array(
+ 'link_param' => 'db',
+ 'default_page' => $GLOBALS['cfg']['DefaultTabDatabase']
+ ),
+ 'user' => array(
+ 'link_param' => 'username',
+ 'link_dependancy_params' => array(
+ 0 => array(
+ 'param_info' => 'hostname',
+ 'column_name' => 'host'
+ )
+ ),
+ 'default_page' => 'server_privileges.php'
+ )
+ ),
+ 'referential_constraints' => array(
+ 'constraint_schema' => array(
+ 'link_param' => 'db',
+ 'default_page' => $GLOBALS['cfg']['DefaultTabDatabase']
+ ),
+ 'unique_constraint_schema' => array(
+ 'link_param' => 'db',
+ 'default_page' => $GLOBALS['cfg']['DefaultTabDatabase']
+ ),
+ 'table_name' => array(
+ 'link_param' => 'table',
+ 'link_dependancy_params' => array(
+ 0 => array(
+ 'param_info' => 'db',
+ 'column_name' => 'constraint_schema'
+ )
+ ),
+ 'default_page' => $GLOBALS['cfg']['DefaultTabTable']
+ ),
+ 'referenced_table_name' => array(
+ 'link_param' => 'table',
+ 'link_dependancy_params' => array(
+ 0 => array(
+ 'param_info' => 'db',
+ 'column_name' => 'constraint_schema'
+ )
+ ),
+ 'default_page' => $GLOBALS['cfg']['DefaultTabTable']
+ )
+ ),
+ 'routines' => array(
+ 'routine_name' => array(
+ 'link_param' => 'item_name',
+ 'link_dependancy_params' => array(
+ 0 => array(
+ 'param_info' => 'db',
+ 'column_name' => 'routine_schema'
+ ),
+ 1 => array(
+ 'param_info' => 'item_type',
+ 'column_name' => 'routine_type'
+ )
+ ),
+ 'default_page' => 'db_routines.php'
+ ),
+ 'routine_schema' => array(
+ 'link_param' => 'db',
+ 'default_page' => $GLOBALS['cfg']['DefaultTabDatabase']
+ ),
+ ),
+ 'schemata' => array(
+ 'schema_name' => array(
+ 'link_param' => 'db',
+ 'default_page' => $GLOBALS['cfg']['DefaultTabDatabase']
+ )
+ ),
+ 'schema_privileges' => array(
+ 'table_schema' => array(
+ 'link_param' => 'db',
+ 'default_page' => $GLOBALS['cfg']['DefaultTabDatabase']
+ )
+ ),
+ 'statistics' => array(
+ 'table_schema' => array(
+ 'link_param' => 'db',
+ 'default_page' => $GLOBALS['cfg']['DefaultTabDatabase']
+ ),
+ 'index_schema' => array(
+ 'link_param' => 'db',
+ 'default_page' => $GLOBALS['cfg']['DefaultTabDatabase']
+ ),
+ 'table_name' => array(
+ 'link_param' => 'table',
+ 'link_dependancy_params' => array(
+ 0 => array(
+ 'param_info' => 'db',
+ 'column_name' => 'table_schema'
+ )
+ ),
+ 'default_page' => $GLOBALS['cfg']['DefaultTabTable']
+ ),
+ 'column_name' => array(
+ 'link_param' => array(
+ 'sql_query',
+ 'table_schema',
+ 'table_name'
+ ),
+ 'link_dependancy_params' => array(
+ 0 => array(
+ 'param_info' => 'db',
+ 'column_name' => 'table_schema'
+ ),
+ 1 => array(
+ 'param_info' => 'table',
+ 'column_name' => 'table_name'
+ )
+ ),
+ 'default_page' => 'sql.php'
+ )
+ ),
+ 'tables' => array(
+ 'table_name' => array(
+ 'link_param' => 'table',
+ 'link_dependancy_params' => array(
+ 0 => array(
+ 'param_info' => 'db',
+ 'column_name' => 'table_schema'
+ )
+ ),
+ 'default_page' => $GLOBALS['cfg']['DefaultTabTable']
+ ),
+ 'table_schema' => array(
+ 'link_param' => 'db',
+ 'default_page' => $GLOBALS['cfg']['DefaultTabDatabase']
+ )
+ ),
+ 'table_constraints' => array(
+ 'table_name' => array(
+ 'link_param' => 'table',
+ 'link_dependancy_params' => array(
+ 0 => array(
+ 'param_info' => 'db',
+ 'column_name' => 'table_schema'
+ )
+ ),
+ 'default_page' => $GLOBALS['cfg']['DefaultTabTable']
+ ),
+ 'constraint_schema' => array(
+ 'link_param' => 'db',
+ 'default_page' => $GLOBALS['cfg']['DefaultTabDatabase']
+ ),
+ 'table_schema' => array(
+ 'link_param' => 'db',
+ 'default_page' => $GLOBALS['cfg']['DefaultTabDatabase']
+ )
+ ),
+ 'table_privileges' => array(
+ 'table_schema' => array(
+ 'link_param' => 'db',
+ 'default_page' => $GLOBALS['cfg']['DefaultTabDatabase']
+ )
+ ),
+ 'triggers' => array(
+ 'trigger_schema' => array(
+ 'link_param' => 'db',
+ 'default_page' => $GLOBALS['cfg']['DefaultTabDatabase']
+ ),
+ 'event_object_schema' => array(
+ 'link_param' => 'db',
+ 'default_page' => $GLOBALS['cfg']['DefaultTabDatabase']
+ )
+ ),
+ 'views' => array(
+ 'table_schema' => array(
+ 'link_param' => 'db',
+ 'default_page' => $GLOBALS['cfg']['DefaultTabDatabase']
+ )
+ )
+ )
+);
+
+?>
diff --git a/libraries/sql.lib.php b/libraries/sql.lib.php
new file mode 100644
index 0000000000..b3056d7ec5
--- /dev/null
+++ b/libraries/sql.lib.php
@@ -0,0 +1,2387 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * set of functions for the sql executor
+ *
+ * @package PhpMyAdmin
+ */
+if (!defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ * Get the database name inside a query
+ *
+ * @param string $sql SQL query
+ * @param array $databases array with all databases
+ *
+ * @return string $db new database name
+ */
+function PMA_getNewDatabase($sql, $databases)
+{
+ $db = '';
+ // loop through all the databases
+ foreach ($databases as $database) {
+ if (strpos($sql, $database['SCHEMA_NAME']) !== false) {
+ $db = $database['SCHEMA_NAME'];
+ break;
+ }
+ }
+ return $db;
+}
+
+/**
+ * Get the table name in a sql query
+ * If there are several tables in the SQL query,
+ * first table wil lreturn
+ *
+ * @param string $sql SQL query
+ * @param array $tables array of names in current database
+ *
+ * @return string $table table name
+ */
+function PMA_getTableNameBySQL($sql, $tables)
+{
+ $table = '';
+
+ // loop through all the tables in the database
+ foreach ($tables as $tbl) {
+ if (strpos($sql, $tbl)) {
+ $table .= ' ' . $tbl;
+ }
+ }
+
+ if (count(explode(' ', trim($table))) > 1) {
+ $tmp_array = explode(' ', trim($table));
+ return $tmp_array[0];
+ }
+
+ return trim($table);
+}
+
+
+/**
+ * Generate table html when SQL statement have multiple queries
+ * which return displayable results
+ *
+ * @param object $displayResultsObject PMA_DisplayResults object
+ * @param string $db database name
+ * @param array $sql_data information about SQL statement
+ * @param string $goto URL to go back in case of errors
+ * @param string $pmaThemeImage path for theme images directory
+ * @param string $printview whether printview is enabled
+ * @param string $url_query URL query
+ * @param array $disp_mode the display mode
+ * @param string $sql_limit_to_append limit clause
+ * @param bool $editable whether editable or not
+ *
+ * @return string $table_html html content
+ */
+function PMA_getTableHtmlForMultipleQueries(
+ $displayResultsObject, $db, $sql_data, $goto, $pmaThemeImage,
+ $printview, $url_query, $disp_mode, $sql_limit_to_append,
+ $editable
+) {
+ $table_html = '';
+
+ $tables_array = $GLOBALS['dbi']->getTables($db);
+ $databases_array = $GLOBALS['dbi']->getDatabasesFull();
+ $multi_sql = implode(";", $sql_data['valid_sql']);
+ $querytime_before = array_sum(explode(' ', microtime()));
+
+ // Assignment for variable is not needed since the results are
+ // looping using the connection
+ @$GLOBALS['dbi']->tryMultiQuery($multi_sql);
+
+ $querytime_after = array_sum(explode(' ', microtime()));
+ $querytime = $querytime_after - $querytime_before;
+ $sql_no = 0;
+
+ do {
+ $analyzed_sql = array();
+ $is_affected = false;
+
+ $result = $GLOBALS['dbi']->storeResult();
+ $fields_meta = ($result !== false)
+ ? $GLOBALS['dbi']->getFieldsMeta($result)
+ : array();
+ $fields_cnt = count($fields_meta);
+
+ // Initialize needed params related to each query in multiquery statement
+ if (isset($sql_data['valid_sql'][$sql_no])) {
+ // 'Use' query can change the database
+ if (stripos($sql_data['valid_sql'][$sql_no], "use ")) {
+ $db = PMA_getNewDatabase(
+ $sql_data['valid_sql'][$sql_no],
+ $databases_array
+ );
+ }
+
+ $table = PMA_getTableNameBySQL(
+ $sql_data['valid_sql'][$sql_no],
+ $tables_array
+ );
+
+ // for the use of the parse_analyze.inc.php
+ $sql_query = $sql_data['valid_sql'][$sql_no];
+
+ // Parse and analyze the query
+ include 'libraries/parse_analyze.inc.php';
+
+ $unlim_num_rows = PMA_Table::countRecords($db, $table, true);
+ $showtable = PMA_Table::sGetStatusInfo($db, $table, null, true);
+ $url_query = PMA_URL_getCommon($db, $table);
+
+ // Handle remembered sorting order, only for single table query
+ if ($GLOBALS['cfg']['RememberSorting']
+ && ! ($is_count || $is_export || $is_func || $is_analyse)
+ && isset($analyzed_sql[0]['select_expr'])
+ && (count($analyzed_sql[0]['select_expr']) == 0)
+ && isset($analyzed_sql[0]['queryflags']['select_from'])
+ && count($analyzed_sql[0]['table_ref']) == 1
+ ) {
+ PMA_handleSortOrder(
+ $db,
+ $table,
+ $analyzed_sql,
+ $sql_data['valid_sql'][$sql_no]
+ );
+ }
+
+ // Do append a "LIMIT" clause?
+ if (($_SESSION['tmpval']['max_rows'] != 'all')
+ && ! ($is_count || $is_export || $is_func || $is_analyse)
+ && isset($analyzed_sql[0]['queryflags']['select_from'])
+ && ! isset($analyzed_sql[0]['queryflags']['offset'])
+ && empty($analyzed_sql[0]['limit_clause'])
+ ) {
+ $sql_limit_to_append = ' LIMIT '
+ . $_SESSION['tmpval']['pos']
+ . ', ' . $_SESSION['tmpval']['max_rows'] . " ";
+ $sql_data['valid_sql'][$sql_no] = PMA_getSqlWithLimitClause(
+ $sql_data['valid_sql'][$sql_no],
+ $analyzed_sql,
+ $sql_limit_to_append
+ );
+ }
+
+ // Set the needed properties related to executing sql query
+ $displayResultsObject->__set('db', $db);
+ $displayResultsObject->__set('table', $table);
+ $displayResultsObject->__set('goto', $goto);
+ }
+
+ if (! $is_affected) {
+ $num_rows = ($result) ? @$GLOBALS['dbi']->numRows($result) : 0;
+ } elseif (! isset($num_rows)) {
+ $num_rows = @$GLOBALS['dbi']->affectedRows();
+ }
+
+ if (isset($sql_data['valid_sql'][$sql_no])) {
+
+ $displayResultsObject->__set(
+ 'sql_query',
+ $sql_data['valid_sql'][$sql_no]
+ );
+ $displayResultsObject->setProperties(
+ $unlim_num_rows, $fields_meta, $is_count, $is_export, $is_func,
+ $is_analyse, $num_rows, $fields_cnt, $querytime, $pmaThemeImage,
+ $GLOBALS['text_dir'], $is_maint, $is_explain, $is_show,
+ $showtable, $printview, $url_query, $editable
+ );
+ }
+
+ if ($num_rows == 0) {
+ continue;
+ }
+
+ // With multiple results, operations are limied
+ $disp_mode = 'nnnn000000';
+ $is_limited_display = true;
+
+ // Collect the tables
+ $table_html .= $displayResultsObject->getTable(
+ $result, $disp_mode, $analyzed_sql, $is_limited_display
+ );
+
+ // Free the result to save the memory
+ $GLOBALS['dbi']->freeResult($result);
+
+ $sql_no++;
+
+ } while ($GLOBALS['dbi']->moreResults() && $GLOBALS['dbi']->nextResult());
+
+ return $table_html;
+}
+
+/**
+ * Handle remembered sorting order, only for single table query
+ *
+ * @param string $db database name
+ * @param string $table table name
+ * @param array &$analyzed_sql_results the analyzed query results
+ * @param string &$full_sql_query SQL query
+ *
+ * @return void
+ */
+function PMA_handleSortOrder(
+ $db, $table, &$analyzed_sql_results, &$full_sql_query
+) {
+ $pmatable = new PMA_Table($table, $db);
+ if (empty($analyzed_sql_results['analyzed_sql'][0]['order_by_clause'])) {
+ $sorted_col = $pmatable->getUiProp(PMA_Table::PROP_SORTED_COLUMN);
+ if ($sorted_col) {
+ //remove the tablename from retrieved preference
+ //to get just the column name and the sort order
+ $sorted_col = str_replace(
+ PMA_Util::backquote($table) . '.', '', $sorted_col
+ );
+ // retrieve the remembered sorting order for current table
+ $sql_order_to_append = ' ORDER BY ' . $sorted_col . ' ';
+ $full_sql_query
+ = $analyzed_sql_results['analyzed_sql'][0]['section_before_limit']
+ . $sql_order_to_append
+ . $analyzed_sql_results['analyzed_sql'][0]['limit_clause']
+ . ' '
+ . $analyzed_sql_results['analyzed_sql'][0]['section_after_limit'];
+
+ // update the $analyzed_sql
+ $analyzed_sql_results['analyzed_sql'][0]['section_before_limit']
+ .= $sql_order_to_append;
+ $analyzed_sql_results['analyzed_sql'][0]['order_by_clause']
+ = $sorted_col;
+ }
+ } else {
+ // store the remembered table into session
+ $pmatable->setUiProp(
+ PMA_Table::PROP_SORTED_COLUMN,
+ $analyzed_sql_results['analyzed_sql'][0]['order_by_clause']
+ );
+ }
+}
+
+/**
+ * Append limit clause to SQL query
+ *
+ * @param string $full_sql_query SQL query
+ * @param array $analyzed_sql the analyzed query
+ * @param string $sql_limit_to_append clause to append
+ *
+ * @return string limit clause appended SQL query
+ */
+function PMA_getSqlWithLimitClause($full_sql_query, $analyzed_sql,
+ $sql_limit_to_append
+) {
+ return $analyzed_sql[0]['section_before_limit'] . "\n"
+ . $sql_limit_to_append . $analyzed_sql[0]['section_after_limit'];
+}
+
+
+/**
+ * Get column name from a drop SQL statement
+ *
+ * @param string $sql SQL query
+ *
+ * @return string $drop_column Name of the column
+ */
+function PMA_getColumnNameInColumnDropSql($sql)
+{
+ $tmpArray1 = explode('DROP', $sql);
+ $str_to_check = trim($tmpArray1[1]);
+
+ if (stripos($str_to_check, 'COLUMN') !== false) {
+ $tmpArray2 = explode('COLUMN', $str_to_check);
+ $str_to_check = trim($tmpArray2[1]);
+ }
+
+ $tmpArray3 = explode(' ', $str_to_check);
+ $str_to_check = trim($tmpArray3[0]);
+
+ $drop_column = str_replace(';', '', trim($str_to_check));
+ $drop_column = str_replace('`', '', $drop_column);
+
+ return $drop_column;
+}
+
+/**
+ * Verify whether the result set contains all the columns
+ * of at least one unique key
+ *
+ * @param string $db database name
+ * @param string $table table name
+ * @param array $fields_meta meta fields
+ *
+ * @return boolean whether the result set contains a unique key
+ */
+function PMA_resultSetContainsUniqueKey($db, $table, $fields_meta)
+{
+ $resultSetColumnNames = array();
+ foreach ($fields_meta as $oneMeta) {
+ $resultSetColumnNames[] = $oneMeta->name;
+ }
+ foreach (PMA_Index::getFromTable($table, $db) as $index) {
+ if ($index->isUnique()) {
+ $indexColumns = $index->getColumns();
+ $numberFound = 0;
+ foreach ($indexColumns as $indexColumnName => $dummy) {
+ if (in_array($indexColumnName, $resultSetColumnNames)) {
+ $numberFound++;
+ }
+ }
+ if ($numberFound == count($indexColumns)) {
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+/**
+ * Get the HTML for relational column dropdown
+ * During grid edit, if we have a relational field, returns the html for the
+ * dropdown
+ *
+ * @param string $db current database
+ * @param string $table current table
+ * @param string $column current column
+ * @param string $curr_value current selected value
+ *
+ * @return string $dropdown html for the dropdown
+ */
+function PMA_getHtmlForRelationalColumnDropdown($db, $table, $column, $curr_value)
+{
+ $foreigners = PMA_getForeigners($db, $table, $column);
+
+ $display_field = PMA_getDisplayField(
+ $foreigners[$column]['foreign_db'],
+ $foreigners[$column]['foreign_table']
+ );
+
+ $foreignData = PMA_getForeignData($foreigners, $column, false, '', '');
+
+ if ($foreignData['disp_row'] == null) {
+ //Handle the case when number of values
+ //is more than $cfg['ForeignKeyMaxLimit']
+ $_url_params = array(
+ 'db' => $db,
+ 'table' => $table,
+ 'field' => $column
+ );
+
+ $dropdown = '<span class="curr_value">'
+ . htmlspecialchars($_REQUEST['curr_value'])
+ . '</span>'
+ . '<a href="browse_foreigners.php'
+ . PMA_URL_getCommon($_url_params) . '"'
+ . ' target="_blank" class="browse_foreign" ' .'>'
+ . __('Browse foreign values')
+ . '</a>';
+ } else {
+ $dropdown = PMA_foreignDropdown(
+ $foreignData['disp_row'],
+ $foreignData['foreign_field'],
+ $foreignData['foreign_display'],
+ $curr_value,
+ $GLOBALS['cfg']['ForeignKeyMaxLimit']
+ );
+ $dropdown = '<select>' . $dropdown . '</select>';
+ }
+
+ return $dropdown;
+}
+
+/**
+ * Get the HTML for the header of the page in print view if print view is selected.
+ * Otherwise returns null.
+ *
+ * @param string $db current database
+ * @param string $sql_query current sql query
+ * @param int $num_rows the number of rows in result
+ *
+ * @return string $header html for the header
+ */
+function PMA_getHtmlForPrintViewHeader($db, $sql_query, $num_rows)
+{
+ $response = PMA_Response::getInstance();
+ $header = $response->getHeader();
+ if (isset($_REQUEST['printview']) && $_REQUEST['printview'] == '1') {
+ PMA_Util::checkParameters(array('db', 'sql_query'));
+ $header->enablePrintView();
+ $hostname = '';
+ if ( $GLOBALS['cfg']['Server']['verbose']) {
+ $hostname = $GLOBALS['cfg']['Server']['verbose'];
+ } else {
+ $hostname = $GLOBALS['cfg']['Server']['host'];
+ if (! empty( $GLOBALS['cfg']['Server']['port'])) {
+ $hostname .= $GLOBALS['cfg']['Server']['port'];
+ }
+ }
+
+ $versions = "phpMyAdmin&nbsp;" . PMA_VERSION;
+ $versions .= "&nbsp;/&nbsp;";
+ $versions .= "MySQL&nbsp;" . PMA_MYSQL_STR_VERSION;
+
+ $print_view_header = '';
+ $print_view_header .= "<h1>" . __('SQL result') . "</h1>";
+ $print_view_header .= "<p>";
+ $print_view_header .= "<strong>" . __('Host:')
+ . "</strong> $hostname<br />";
+ $print_view_header .= "<strong>" . __('Database:') . "</strong> "
+ . htmlspecialchars($db) . "<br />";
+ $print_view_header .= "<strong>" . __('Generation Time:') . "</strong> "
+ . PMA_Util::localisedDate() . "<br />";
+ $print_view_header .= "<strong>" . __('Generated by:')
+ . "</strong> $versions<br />";
+ $print_view_header .= "<strong>" . __('SQL query:') . "</strong> "
+ . htmlspecialchars($sql_query) . ";";
+ if (isset($num_rows)) {
+ $print_view_header .= "<br />";
+ $print_view_header .= "<strong>" . __('Rows:') . "</strong> $num_rows";
+ }
+ $print_view_header .= "</p>";
+ } else {
+ $print_view_header = null;
+ }
+
+ return $print_view_header;
+}
+
+/**
+ * Get the HTML for the profiling table and accompanying chart if profiling is set.
+ * Otherwise returns null
+ *
+ * @param string $url_query url query
+ * @param string $db current database
+ * @param array $profiling_results array containing the profiling info
+ *
+ * @return string $profiling_table html for the profiling table and chart
+ */
+function PMA_getHtmlForProfilingChart($url_query, $db, $profiling_results)
+{
+ if (isset($profiling_results)) {
+ $pma_token = $_SESSION[' PMA_token '];
+ $url_query = (isset($url_query) ? $url_query : PMA_URL_getCommon($db));
+
+ $profiling_table = '';
+ $profiling_table .= '<fieldset><legend>' . __('Profiling')
+ . '</legend>' . "\n";
+ $profiling_table .= '<div style="float: left;">';
+ $profiling_table .= '<h3>' . __('Detailed profile') . '</h3>';
+ $profiling_table .= '<table id="profiletable"><thead>' . "\n";
+ $profiling_table .= ' <tr>' . "\n";
+ $profiling_table .= ' <th>' . __('Order')
+ . '<div class="sorticon"></div></th>' . "\n";
+ $profiling_table .= ' <th>' . __('State')
+ . PMA_Util::showMySQLDocu('general-thread-states')
+ . '<div class="sorticon"></div></th>' . "\n";
+ $profiling_table .= ' <th>' . __('Time')
+ . '<div class="sorticon"></div></th>' . "\n";
+ $profiling_table .= ' </tr></thead><tbody>' . "\n";
+ list($detailed_table, $chart_json, $profiling_stats)
+ = PMA_analyzeAndGetTableHtmlForProfilingResults($profiling_results);
+ $profiling_table .= $detailed_table;
+ $profiling_table .= '</tbody></table>' . "\n";
+ $profiling_table .= '</div>';
+
+ $profiling_table .= '<div style="float: left; margin-left:10px;">';
+ $profiling_table .= '<h3>' . __('Summary by state') . '</h3>';
+ $profiling_table .= '<table id="profilesummarytable"><thead>' . "\n";
+ $profiling_table .= ' <tr>' . "\n";
+ $profiling_table .= ' <th>' . __('State')
+ . PMA_Util::showMySQLDocu('general-thread-states')
+ . '<div class="sorticon"></div></th>' . "\n";
+ $profiling_table .= ' <th>' . __('Total Time')
+ . '<div class="sorticon"></div></th>' . "\n";
+ $profiling_table .= ' <th>' . __('% Time')
+ . '<div class="sorticon"></div></th>' . "\n";
+ $profiling_table .= ' <th>' . __('Calls')
+ . '<div class="sorticon"></div></th>' . "\n";
+ $profiling_table .= ' <th>' . __('ø Time')
+ . '<div class="sorticon"></div></th>' . "\n";
+ $profiling_table .= ' </tr></thead><tbody>' . "\n";
+ $profiling_table .= PMA_getTableHtmlForProfilingSummaryByState(
+ $profiling_stats
+ );
+ $profiling_table .= '</tbody></table>' . "\n";
+
+ $profiling_table .= <<<EOT
+<script type="text/javascript">
+ pma_token = '$pma_token';
+ url_query = '$url_query';
+</script>
+EOT;
+ $profiling_table .= "</div>";
+
+ //require_once 'libraries/chart.lib.php';
+ $profiling_table .= '<div id="profilingChartData" style="display:none;">';
+ $profiling_table .= json_encode($chart_json);
+ $profiling_table .= '</div>';
+ $profiling_table .= '<div id="profilingchart" style="display:none;">';
+ $profiling_table .= '</div>';
+ $profiling_table .= '<script type="text/javascript">';
+ $profiling_table .= 'makeProfilingChart();';
+ $profiling_table .= 'initProfilingTables();';
+ $profiling_table .= '</script>';
+ $profiling_table .= '</fieldset>' . "\n";
+ } else {
+ $profiling_table = null;
+ }
+ return $profiling_table;
+}
+
+/**
+ * Function to get HTML for detailed profiling results table, profiling stats, and
+ * $chart_json for displaying the chart.
+ *
+ * @param array $profiling_results profiling results
+ *
+ * @return mixed
+ */
+function PMA_analyzeAndGetTableHtmlForProfilingResults(
+ $profiling_results
+) {
+ $profiling_stats = array(
+ 'total_time' => 0,
+ 'states' => array(),
+ );
+ $chart_json = Array();
+ $i = 1;
+ $table = '';
+ foreach ($profiling_results as $one_result) {
+ if (isset($profiling_stats['states'][ucwords($one_result['Status'])])) {
+ $states = $profiling_stats['states'];
+ $states[ucwords($one_result['Status'])]['time']
+ += $one_result['Duration'];
+ $states[ucwords($one_result['Status'])]['calls']++;
+ } else {
+ $profiling_stats['states'][ucwords($one_result['Status'])] = array(
+ 'total_time' => $one_result['Duration'],
+ 'calls' => 1,
+ );
+ }
+ $profiling_stats['total_time'] += $one_result['Duration'];
+
+ $table .= ' <tr>' . "\n";
+ $table .= '<td>' . $i++ . '</td>' . "\n";
+ $table .= '<td>' . ucwords($one_result['Status'])
+ . '</td>' . "\n";
+ $table .= '<td class="right">'
+ . (PMA_Util::formatNumber($one_result['Duration'], 3, 1))
+ . 's<span style="display:none;" class="rawvalue">'
+ . $one_result['Duration'] . '</span></td>' . "\n";
+ if (isset($chart_json[ucwords($one_result['Status'])])) {
+ $chart_json[ucwords($one_result['Status'])]
+ += $one_result['Duration'];
+ } else {
+ $chart_json[ucwords($one_result['Status'])]
+ = $one_result['Duration'];
+ }
+ }
+ return array($table, $chart_json, $profiling_stats);
+}
+
+/**
+ * Function to get HTML for summary by state table
+ *
+ * @param array $profiling_stats profiling stats
+ *
+ * @return string $table html for the table
+ */
+function PMA_getTableHtmlForProfilingSummaryByState($profiling_stats)
+{
+ $table = '';
+ foreach ($profiling_stats['states'] as $name => $stats) {
+ $table .= ' <tr>' . "\n";
+ $table .= '<td>' . $name . '</td>' . "\n";
+ $table .= '<td align="right">'
+ . PMA_Util::formatNumber($stats['total_time'], 3, 1)
+ . 's<span style="display:none;" class="rawvalue">'
+ . $stats['total_time'] . '</span></td>' . "\n";
+ $table .= '<td align="right">'
+ . PMA_Util::formatNumber(
+ 100 * ($stats['total_time'] / $profiling_stats['total_time']),
+ 0, 2
+ )
+ . '%</td>' . "\n";
+ $table .= '<td align="right">' . $stats['calls'] . '</td>'
+ . "\n";
+ $table .= '<td align="right">'
+ . PMA_Util::formatNumber(
+ $stats['total_time'] / $stats['calls'], 3, 1
+ )
+ . 's<span style="display:none;" class="rawvalue">'
+ . number_format($stats['total_time'] / $stats['calls'], 8, '.', '')
+ . '</span></td>' . "\n";
+ $table .= ' </tr>' . "\n";
+ }
+ return $table;
+}
+
+/**
+ * Get the HTML for the enum column dropdown
+ * During grid edit, if we have a enum field, returns the html for the
+ * dropdown
+ *
+ * @param string $db current database
+ * @param string $table current table
+ * @param string $column current column
+ * @param string $curr_value currently selected value
+ *
+ * @return string $dropdown html for the dropdown
+ */
+function PMA_getHtmlForEnumColumnDropdown($db, $table, $column, $curr_value)
+{
+ $values = PMA_getValuesForColumn($db, $table, $column);
+ $dropdown = '<option value="">&nbsp;</option>';
+ $dropdown .= PMA_getHtmlForOptionsList($values, array($curr_value));
+ $dropdown = '<select>' . $dropdown . '</select>';
+ return $dropdown;
+}
+
+/**
+ * Get the HTML for the set column dropdown
+ * During grid edit, if we have a set field, returns the html for the
+ * dropdown
+ *
+ * @param string $db current database
+ * @param string $table current table
+ * @param string $column current column
+ * @param string $curr_value currently selected value
+ *
+ * @return string $dropdown html for the set column
+ */
+function PMA_getHtmlForSetColumn($db, $table, $column, $curr_value)
+{
+ $values = PMA_getValuesForColumn($db, $table, $column);
+ $dropdown = '';
+
+ //converts characters of $curr_value to HTML entities
+ $converted_curr_value = htmlentities(
+ $curr_value, ENT_COMPAT, "UTF-8"
+ );
+
+ $selected_values = explode(',', $converted_curr_value);
+ $dropdown .= PMA_getHtmlForOptionsList($values, $selected_values);
+
+ $select_size = (sizeof($values) > 10) ? 10 : sizeof($values);
+ $dropdown = '<select multiple="multiple" size="' . $select_size . '">'
+ . $dropdown . '</select>';
+
+ return $dropdown;
+}
+
+/**
+ * Get all the values for a enum column or set column in a table
+ *
+ * @param string $db current database
+ * @param string $table current table
+ * @param string $column current column
+ *
+ * @return array $values array containing the value list for the column
+ */
+function PMA_getValuesForColumn($db, $table, $column)
+{
+ $field_info_query = $GLOBALS['dbi']->getColumnsSql($db, $table, $column);
+
+ $field_info_result = $GLOBALS['dbi']->fetchResult(
+ $field_info_query, null, null, null, PMA_DatabaseInterface::QUERY_STORE
+ );
+
+ $values = PMA_Util::parseEnumSetValues($field_info_result[0]['Type']);
+
+ return $values;
+}
+
+/**
+ * Get HTML for options list
+ *
+ * @param array $values set of values
+ * @param array $selected_values currently selected values
+ *
+ * @return string $options HTML for options list
+ */
+function PMA_getHtmlForOptionsList($values, $selected_values)
+{
+ $options = '';
+ foreach ($values as $value) {
+ $options .= '<option value="' . $value . '"';
+ if (in_array($value, $selected_values, true)) {
+ $options .= ' selected="selected" ';
+ }
+ $options .= '>' . $value . '</option>';
+ }
+ return $options;
+}
+
+/**
+ * Function to get html for bookmark support if bookmarks are enabled. Else will
+ * return null
+ *
+ * @param string $disp_mode display mode
+ * @param bool $cfgBookmark configuration setting for bookmarking
+ * @param string $sql_query sql query
+ * @param string $db current database
+ * @param string $table current table
+ * @param string $complete_query complete query
+ * @param string $bkm_user bookmarking user
+ *
+ * @return string $html
+ */
+function PMA_getHtmlForBookmark($disp_mode, $cfgBookmark, $sql_query, $db, $table,
+ $complete_query, $bkm_user
+) {
+ if ($disp_mode[7] == '1'
+ && (! empty($cfgBookmark) && empty($_GET['id_bookmark']))
+ && ! empty($sql_query)
+ ) {
+ $html = "\n";
+ $goto = 'sql.php'
+ . PMA_URL_getCommon(
+ array(
+ 'db' => $db,
+ 'table' => $table,
+ 'sql_query' => $sql_query,
+ 'id_bookmark'=> 1,
+ )
+ );
+ $bkm_sql_query = urlencode(
+ isset($complete_query) ? $complete_query : $sql_query
+ );
+ $html = '<form action="sql.php" method="post"'
+ . ' onsubmit="return ! emptyFormElements(this,'
+ . '\'bkm_fields[bkm_label]\');"'
+ . ' id="bookmarkQueryForm">';
+ $html .= PMA_URL_getHiddenInputs();
+ $html .= '<input type="hidden" name="goto" value="' . $goto . '" />';
+ $html .= '<input type="hidden" name="bkm_fields[bkm_database]"'
+ . ' value="' . htmlspecialchars($db) . '" />';
+ $html .= '<input type="hidden" name="bkm_fields[bkm_user]"'
+ . ' value="' . $bkm_user . '" />';
+ $html .= '<input type="hidden" name="bkm_fields[bkm_sql_query]"'
+ . ' value="'
+ . $bkm_sql_query
+ . '" />';
+ $html .= '<fieldset>';
+ $html .= '<legend>';
+ $html .= PMA_Util::getIcon(
+ 'b_bookmark.png', __('Bookmark this SQL query'), true
+ );
+ $html .= '</legend>';
+ $html .= '<div class="formelement">';
+ $html .= '<label for="fields_label_">' . __('Label:') . '</label>';
+ $html .= '<input type="text" id="fields_label_"'
+ . ' name="bkm_fields[bkm_label]" value="" />';
+ $html .= '</div>';
+ $html .= '<div class="formelement">';
+ $html .= '<input type="checkbox" name="bkm_all_users"'
+ . ' id="bkm_all_users" value="true" />';
+ $html .= '<label for="bkm_all_users">'
+ . __('Let every user access this bookmark')
+ . '</label>';
+ $html .= '</div>';
+ $html .= '<div class="clearfloat"></div>';
+ $html .= '</fieldset>';
+ $html .= '<fieldset class="tblFooters">';
+ $html .= '<input type="hidden" name="store_bkm" value="1" />';
+ $html .= '<input type="submit"'
+ . ' value="' . __('Bookmark this SQL query') . '" />';
+ $html .= '</fieldset>';
+ $html .= '</form>';
+
+ } else {
+ $html = null;
+ }
+
+ return $html;
+}
+
+/**
+ * Function to check whether to remember the sorting order or not
+ *
+ * @param array $analyzed_sql_results the analyzed query and other varibles set
+ * after analyzing the query
+ *
+ * @return boolean
+ */
+function PMA_isRememberSortingOrder($analyzed_sql_results)
+{
+ $select_from = isset(
+ $analyzed_sql_results['analyzed_sql'][0]['queryflags']['select_from']
+ );
+ if ($GLOBALS['cfg']['RememberSorting']
+ && ! ($analyzed_sql_results['is_count']
+ || $analyzed_sql_results['is_export']
+ || $analyzed_sql_results['is_func']
+ || $analyzed_sql_results['is_analyse'])
+ && isset($analyzed_sql_results['analyzed_sql'][0]['select_expr'])
+ && (count($analyzed_sql_results['analyzed_sql'][0]['select_expr']) == 0)
+ && $select_from
+ && count($analyzed_sql_results['analyzed_sql'][0]['table_ref']) == 1
+ ) {
+ return true;
+ } else {
+ return false;
+ }
+}
+
+/**
+ * Function to check whether the LIMIT clause should be appended or not
+ *
+ * @param array $analyzed_sql_results the analyzed query and other varibles set
+ * after analyzing the query
+ *
+ * @return boolean
+ */
+function PMA_isAppendLimitClause($analyzed_sql_results)
+{
+ $select_from = isset(
+ $analyzed_sql_results['analyzed_sql'][0]['queryflags']['select_from']
+ );
+ if (($_SESSION['tmpval']['max_rows'] != 'all')
+ && ! ($analyzed_sql_results['is_export']
+ || $analyzed_sql_results['is_analyse'])
+ && ($select_from || $analyzed_sql_results['is_subquery'])
+ && ! isset($analyzed_sql_results['analyzed_sql'][0]['queryflags']['offset'])
+ && empty($analyzed_sql_results['analyzed_sql'][0]['limit_clause'])
+ ) {
+ return true;
+ } else {
+ return false;
+ }
+}
+
+/**
+ * Function to check whether this query is for just browsing
+ *
+ * @param array $analyzed_sql_results the analyzed query and other varibles set
+ * after analyzing the query
+ * @param boolean $find_real_end whether the real end should be found
+ *
+ * @return boolean
+ */
+function PMA_isJustBrowsing($analyzed_sql_results, $find_real_end)
+{
+ $distinct = isset(
+ $analyzed_sql_results['analyzed_sql'][0]['queryflags']['distinct']
+ );
+
+ $table_name = isset(
+ $analyzed_sql_results['analyzed_sql'][0]['table_ref'][1]['table_name']
+ );
+ if (! $analyzed_sql_results['is_group']
+ && ! isset($analyzed_sql_results['analyzed_sql'][0]['queryflags']['union'])
+ && ! $distinct
+ && ! $table_name
+ && (empty($analyzed_sql_results['analyzed_sql'][0]['where_clause'])
+ || $analyzed_sql_results['analyzed_sql'][0]['where_clause'] == '1 ')
+ && ! isset($find_real_end)
+ && !$analyzed_sql_results['is_subquery']
+ ) {
+ return true;
+ } else {
+ return false;
+ }
+}
+
+/**
+ * Function to check whether the reated transformation information shoul be deleted
+ *
+ * @param array $analyzed_sql_results the analyzed query and other varibles set
+ * after analyzing the query
+ *
+ * @return boolean
+ */
+function PMA_isDeleteTransformationInfo($analyzed_sql_results)
+{
+ if (!empty($analyzed_sql_results['analyzed_sql'][0]['querytype'])
+ && (($analyzed_sql_results['analyzed_sql'][0]['querytype'] == 'ALTER')
+ || ($analyzed_sql_results['analyzed_sql'][0]['querytype'] == 'DROP'))
+ ) {
+ return true;
+ } else {
+ return false;
+ }
+}
+
+/**
+ * Function to check whether the user has rights to drop the database
+ *
+ * @param array $analyzed_sql_results the analyzed query and other varibles set
+ * after analyzing the query
+ * @param boolean $allowUserDropDatabase whether the user is allowed to drop db
+ * @param boolean $is_superuser whether this user is a superuser
+ *
+ * @return boolean
+ */
+function PMA_hasNoRightsToDropDatabase($analyzed_sql_results,
+ $allowUserDropDatabase, $is_superuser
+) {
+ if (! defined('PMA_CHK_DROP')
+ && ! $allowUserDropDatabase
+ && isset ($analyzed_sql_results['drop_database'])
+ && $analyzed_sql_results['drop_database'] == 1
+ && ! $is_superuser
+ ) {
+ return true;
+ } else {
+ return false;
+ }
+}
+
+/**
+ * Function to set the column order
+ *
+ * @param PMA_Table $pmatable PMA_Table instance
+ *
+ * @return boolean $retval
+ */
+function PMA_setColumnOrder($pmatable)
+{
+ $col_order = explode(',', $_REQUEST['col_order']);
+ $retval = $pmatable->setUiProp(
+ PMA_Table::PROP_COLUMN_ORDER,
+ $col_order,
+ $_REQUEST['table_create_time']
+ );
+ if (gettype($retval) != 'boolean') {
+ $response = PMA_Response::getInstance();
+ $response->isSuccess(false);
+ $response->addJSON('message', $retval->getString());
+ exit;
+ }
+
+ return $retval;
+}
+
+/**
+ * Function to set the column visibility
+ *
+ * @param PMA_Table $pmatable PMA_Table instance
+ *
+ * @return boolean $retval
+ */
+function PMA_setColumnVisibility($pmatable)
+{
+ $col_visib = explode(',', $_REQUEST['col_visib']);
+ $retval = $pmatable->setUiProp(
+ PMA_Table::PROP_COLUMN_VISIB, $col_visib,
+ $_REQUEST['table_create_time']
+ );
+ if (gettype($retval) != 'boolean') {
+ $response = PMA_Response::getInstance();
+ $response->isSuccess(false);
+ $response->addJSON('message', $retval->getString());
+ exit;
+ }
+ return $retval;
+}
+
+/**
+ * Function to check the request for setting the column order or visibility
+ *
+ * @param String $table the current table
+ * @param String $db the current database
+ *
+ * @return void
+ */
+function PMA_setColumnOrderOrVisibility($table, $db)
+{
+ $pmatable = new PMA_Table($table, $db);
+ $retval = false;
+
+ // set column order
+ if (isset($_REQUEST['col_order'])) {
+ $retval = PMA_setColumnOrder($pmatable);
+ }
+
+ // set column visibility
+ if ($retval === true && isset($_REQUEST['col_visib'])) {
+ $retval = PMA_setColumnVisibility($pmatable);
+ }
+
+ $response = PMA_Response::getInstance();
+ $response->isSuccess($retval == true);
+ exit;
+}
+
+/**
+ * Function to add a bookmark
+ *
+ * @param String $pmaAbsoluteUri absolute URI
+ * @param String $goto goto page URL
+ *
+ * @return void
+ */
+function PMA_addBookmark($pmaAbsoluteUri, $goto)
+{
+ $result = PMA_Bookmark_save(
+ $_POST['bkm_fields'],
+ (isset($_POST['bkm_all_users'])
+ && $_POST['bkm_all_users'] == 'true' ? true : false
+ )
+ );
+ $response = PMA_Response::getInstance();
+ if ($response->isAjax()) {
+ if ($result) {
+ $msg = PMA_message::success(__('Bookmark %s created'));
+ $msg->addParam($_POST['bkm_fields']['bkm_label']);
+ $response->addJSON('message', $msg);
+ } else {
+ $msg = PMA_message::error(__('Bookmark not created'));
+ $response->isSuccess(false);
+ $response->addJSON('message', $msg);
+ }
+ exit;
+ } else {
+ // go back to sql.php to redisplay query; do not use &amp; in this case:
+ /**
+ * @todo In which scenario does this happen?
+ */
+ PMA_sendHeaderLocation(
+ $pmaAbsoluteUri . $goto
+ . '&label=' . $_POST['bkm_fields']['bkm_label']
+ );
+ }
+}
+
+/**
+ * Function to find the real end of rows
+ *
+ * @param String $db the current database
+ * @param String $table the current table
+ *
+ * @return mixed the number of rows if "retain" param is true, otherwise true
+ */
+function PMA_findRealEndOfRows($db, $table)
+{
+ $unlim_num_rows = PMA_Table::countRecords($db, $table, true);
+ $_SESSION['tmpval']['pos'] = PMA_getStartPosToDisplayRow($unlim_num_rows);
+
+ return $unlim_num_rows;
+}
+
+/**
+ * Function to get values for the relational columns
+ *
+ * @param String $db the current database
+ * @param String $table the current table
+ * @param String $display_field display field
+ *
+ * @return void
+ */
+function PMA_getRelationalValues($db, $table, $display_field)
+{
+ $column = $_REQUEST['column'];
+ if ($_SESSION['tmpval']['relational_display'] == 'D'
+ && isset($display_field)
+ && strlen($display_field)
+ && isset($_REQUEST['relation_key_or_display_column'])
+ && $_REQUEST['relation_key_or_display_column']
+ ) {
+ $curr_value = $_REQUEST['relation_key_or_display_column'];
+ } else {
+ $curr_value = $_REQUEST['curr_value'];
+ }
+ $dropdown = PMA_getHtmlForRelationalColumnDropdown(
+ $db, $table, $column, $curr_value
+ );
+ $response = PMA_Response::getInstance();
+ $response->addJSON('dropdown', $dropdown);
+ exit;
+}
+
+/**
+ * Function to get values for Enum or Set Columns
+ *
+ * @param String $db the current database
+ * @param String $table the current table
+ * @param String $columnType whether enum or set
+ *
+ * @return void
+ */
+function PMA_getEnumOrSetValues($db, $table, $columnType)
+{
+ $column = $_REQUEST['column'];
+ $curr_value = $_REQUEST['curr_value'];
+ $response = PMA_Response::getInstance();
+ if ($columnType == "enum") {
+ $dropdown = PMA_getHtmlForEnumColumnDropdown(
+ $db, $table, $column, $curr_value
+ );
+ $response->addJSON('dropdown', $dropdown);
+ } else {
+ $select = PMA_getHtmlForSetColumn($db, $table, $column, $curr_value);
+ $response->addJSON('select', $select);
+ }
+ exit;
+}
+
+/**
+ * Function to append the limit clause
+ *
+ * @param String $full_sql_query full sql query
+ * @param array $analyzed_sql analyzed sql query
+ * @param String $display_query display query
+ *
+ * @return array
+ */
+function PMA_appendLimitClause($full_sql_query, $analyzed_sql, $display_query)
+{
+ $sql_limit_to_append = ' LIMIT ' . $_SESSION['tmpval']['pos']
+ . ', ' . $_SESSION['tmpval']['max_rows'] . " ";
+ $full_sql_query = PMA_getSqlWithLimitClause(
+ $full_sql_query,
+ $analyzed_sql,
+ $sql_limit_to_append
+ );
+
+ /**
+ * @todo pretty printing of this modified query
+ */
+ if ($display_query) {
+ // if the analysis of the original query revealed that we found
+ // a section_after_limit, we now have to analyze $display_query
+ // to display it correctly
+
+ if (! empty($analyzed_sql[0]['section_after_limit'])
+ && trim($analyzed_sql[0]['section_after_limit']) != ';'
+ ) {
+ $analyzed_display_query = PMA_SQP_analyze(
+ PMA_SQP_parse($display_query)
+ );
+ $display_query = $analyzed_display_query[0]['section_before_limit']
+ . "\n" . $sql_limit_to_append
+ . $analyzed_display_query[0]['section_after_limit'];
+ }
+ }
+
+ return array($sql_limit_to_append, $full_sql_query, isset(
+ $analyzed_display_query)
+ ? $analyzed_display_query : null,
+ isset($display_query) ? $display_query : null
+ );
+}
+
+/**
+ * Function to get the default sql query for browsing page
+ *
+ * @param String $db the current database
+ * @param String $table the current table
+ *
+ * @return String $sql_query the default $sql_query for browse page
+ */
+function PMA_getDefaultSqlQueryForBrowse($db, $table)
+{
+ include_once 'libraries/bookmark.lib.php';
+ $book_sql_query = PMA_Bookmark_get(
+ $db,
+ '\'' . PMA_Util::sqlAddSlashes($table) . '\'',
+ 'label',
+ false,
+ true
+ );
+
+ if (! empty($book_sql_query)) {
+ $GLOBALS['using_bookmark_message'] = PMA_message::notice(
+ __('Using bookmark "%s" as default browse query.')
+ );
+ $GLOBALS['using_bookmark_message']->addParam($table);
+ $GLOBALS['using_bookmark_message']->addMessage(
+ PMA_Util::showDocu('faq', 'faq6-22')
+ );
+ $sql_query = $book_sql_query;
+ } else {
+ $sql_query = 'SELECT * FROM ' . PMA_Util::backquote($table);
+ }
+ unset($book_sql_query);
+
+ return $sql_query;
+}
+
+/**
+ * Responds an error when an error happens when executing the query
+ *
+ * @param boolean $is_gotofile whether goto file or not
+ * @param String $error error after executing the query
+ * @param String $full_sql_query full sql query
+ *
+ * @return void
+ */
+function PMA_handleQueryExecuteError($is_gotofile, $error, $full_sql_query)
+{
+ if ($is_gotofile) {
+ $message = PMA_Message::rawError($error);
+ $response = PMA_Response::getInstance();
+ $response->isSuccess(false);
+ $response->addJSON('message', $message);
+ } else {
+ PMA_Util::mysqlDie($error, $full_sql_query, '', '');
+ }
+ exit;
+}
+
+/**
+ * Function to store the query as a bookmark
+ *
+ * @param String $db the current database
+ * @param String $bkm_user the bookmarking user
+ * @param String $sql_query_for_bookmark the query to be stored in bookmark
+ * @param String $bkm_label bookmark label
+ * @param boolean $bkm_replace whether to replace existing bookmarks
+ *
+ * @return void
+ */
+function PMA_storeTheQueryAsBookmark($db, $bkm_user, $sql_query_for_bookmark,
+ $bkm_label, $bkm_replace
+) {
+ include_once 'libraries/bookmark.lib.php';
+ $bfields = array(
+ 'bkm_database' => $db,
+ 'bkm_user' => $bkm_user,
+ 'bkm_sql_query' => urlencode($sql_query_for_bookmark),
+ 'bkm_label' => $bkm_label
+ );
+
+ // Should we replace bookmark?
+ if (isset($bkm_replace)) {
+ $bookmarks = PMA_Bookmark_getList($db);
+ foreach ($bookmarks as $key => $val) {
+ if ($val == $bkm_label) {
+ PMA_Bookmark_delete($db, $key);
+ }
+ }
+ }
+
+ PMA_Bookmark_save($bfields, isset($_POST['bkm_all_users']));
+
+}
+
+/**
+ * Function to execute the SQL query and set the execution time
+ *
+ * @param String $full_sql_query the full sql query
+ *
+ * @return mixed $result the results after running the query
+ */
+function PMA_executeQueryAndStoreResults($full_sql_query)
+{
+ // Measure query time.
+ $querytime_before = array_sum(explode(' ', microtime()));
+
+ $result = @$GLOBALS['dbi']->tryQuery(
+ $full_sql_query, null, PMA_DatabaseInterface::QUERY_STORE
+ );
+ $querytime_after = array_sum(explode(' ', microtime()));
+
+ $GLOBALS['querytime'] = $querytime_after - $querytime_before;
+
+ // If a stored procedure was called, there may be more results that are
+ // queued up and waiting to be flushed from the buffer. So let's do that.
+ do {
+ $GLOBALS['dbi']->storeResult();
+ if (! $GLOBALS['dbi']->moreResults()) {
+ break;
+ }
+ } while ($GLOBALS['dbi']->nextResult());
+
+ return $result;
+}
+
+/**
+ * Function to get the affected or changed number of rows after executing a query
+ *
+ * @param boolean $is_affected whether the query affected a table
+ * @param mixed $result results of executing the query
+ * @param int $num_rows number of rows affected or changed
+ *
+ * @return int $num_rows number of rows affected or changed
+ */
+function PMA_getNumberOfRowsAffectedOrChanged($is_affected, $result, $num_rows)
+{
+ if (! $is_affected) {
+ $num_rows = ($result) ? @$GLOBALS['dbi']->numRows($result) : 0;
+ } elseif (! isset($num_rows)) {
+ $num_rows = @$GLOBALS['dbi']->affectedRows();
+ }
+
+ return $num_rows;
+}
+
+/**
+ * Checks if the current database has changed
+ * This could happen if the user sends a query like "USE `database`;"
+ *
+ * @param String $db the database in the query
+ *
+ * @return int $reload whether to reload the navigation(1) or not(0)
+ */
+function PMA_hasCurrentDbChanged($db)
+{
+ // Checks if the current database has changed
+ // This could happen if the user sends a query like "USE `database`;"
+ $reload = 0;
+ if (strlen($db)) {
+ $current_db = $GLOBALS['dbi']->fetchValue('SELECT DATABASE()');
+ // $current_db is false, except when a USE statement was sent
+ if ($current_db != false && $db !== $current_db) {
+ $reload = 1;
+ }
+ }
+
+ return $reload;
+}
+
+/**
+ * If a table, database or column gets dropped, clean comments.
+ *
+ * @param String $db current database
+ * @param String $table current table
+ * @param String $dropped_column dropped column if any
+ * @param bool $purge whether purge set or not
+ * @param array $extra_data extra data
+ *
+ * @return array $extra_data
+ */
+function PMA_cleanupRelations($db, $table, $dropped_column, $purge, $extra_data)
+{
+ include_once 'libraries/relation_cleanup.lib.php';
+
+ if (isset($purge) && $purge == 1) {
+ if (strlen($table) && strlen($db)) {
+ PMA_relationsCleanupTable($db, $table);
+ } elseif (strlen($db)) {
+ PMA_relationsCleanupDatabase($db);
+ }
+ }
+
+ if (isset($dropped_column)
+ && !empty($dropped_column)
+ && strlen($db)
+ && strlen($table)
+ ) {
+ PMA_relationsCleanupColumn($db, $table, $dropped_column);
+ if (isset($extra_data)) {
+ // to refresh the list of indexes (Ajax mode)
+ $extra_data['indexes_list'] = PMA_Index::getView($table, $db);
+ }
+ }
+
+ return $extra_data;
+}
+
+/**
+ * Function to count the total number of rows for the same 'SELECT' query without
+ * the 'LIMIT' clause that may have been programatically added
+ *
+ * @param int $num_rows number of rows affected/changed by the query
+ * @param bool $is_select whether the query is SELECT or not
+ * @param bool $justBrowsing whether just browsing or not
+ * @param string $db the current database
+ * @param string $table the current table
+ * @param array $parsed_sql parsed sql
+ * @param array $analyzed_sql_results the analyzed query and other varibles set
+ * after analyzing the query
+ *
+ * @return int $unlim_num_rows unlimited number of rows
+ */
+function PMA_countQueryResults(
+ $num_rows, $is_select, $justBrowsing,
+ $db, $table, $parsed_sql, $analyzed_sql_results
+) {
+ if (!PMA_isAppendLimitClause($analyzed_sql_results)) {
+ // if we did not append a limit, set this to get a correct
+ // "Showing rows..." message
+ // $_SESSION['tmpval']['max_rows'] = 'all';
+ $unlim_num_rows = $num_rows;
+ } elseif ($is_select || $analyzed_sql_results['is_subquery']) {
+ // c o u n t q u e r y
+
+ // If we are "just browsing", there is only one table,
+ // and no WHERE clause (or just 'WHERE 1 '),
+ // we do a quick count (which uses MaxExactCount) because
+ // SQL_CALC_FOUND_ROWS is not quick on large InnoDB tables
+
+ // However, do not count again if we did it previously
+ // due to $find_real_end == true
+ if ($justBrowsing) {
+ $unlim_num_rows = PMA_Table::countRecords(
+ $db,
+ $table,
+ true
+ );
+
+ } else {
+ // add select expression after the SQL_CALC_FOUND_ROWS
+
+ // for UNION, just adding SQL_CALC_FOUND_ROWS
+ // after the first SELECT works.
+
+ // take the left part, could be:
+ // SELECT
+ // (SELECT
+
+ $analyzed_sql = $analyzed_sql_results['analyzed_sql'];
+
+ $count_query = PMA_SQP_format(
+ $parsed_sql,
+ 'query_only',
+ 0,
+ $analyzed_sql[0]['position_of_first_select'] + 1
+ );
+ $count_query .= ' SQL_CALC_FOUND_ROWS ';
+ // add everything that was after the first SELECT
+ $count_query .= PMA_SQP_format(
+ $parsed_sql,
+ 'query_only',
+ $analyzed_sql[0]['position_of_first_select'] + 1
+ );
+ // ensure there is no semicolon at the end of the
+ // count query because we'll probably add
+ // a LIMIT 1 clause after it
+ $count_query = rtrim($count_query);
+ $count_query = rtrim($count_query, ';');
+
+ // if using SQL_CALC_FOUND_ROWS, add a LIMIT to avoid
+ // long delays. Returned count will be complete anyway.
+ // (but a LIMIT would disrupt results in an UNION)
+
+ if (! isset($analyzed_sql[0]['queryflags']['union'])) {
+ $count_query .= ' LIMIT 1';
+ }
+
+ // run the count query
+
+ $GLOBALS['dbi']->tryQuery($count_query);
+ // if (mysql_error()) {
+ // void.
+ // I tried the case
+ // (SELECT `User`, `Host`, `Db`, `Select_priv` FROM `db`)
+ // UNION (SELECT `User`, `Host`, "%" AS "Db",
+ // `Select_priv`
+ // FROM `user`) ORDER BY `User`, `Host`, `Db`;
+ // and although the generated count_query is wrong
+ // the SELECT FOUND_ROWS() work! (maybe it gets the
+ // count from the latest query that worked)
+ //
+ // another case where the count_query is wrong:
+ // SELECT COUNT(*), f1 from t1 group by f1
+ // and you click to sort on count(*)
+ // }
+ $unlim_num_rows = $GLOBALS['dbi']->fetchValue('SELECT FOUND_ROWS()');
+ } // end else "just browsing"
+ } else {// not $is_select
+ $unlim_num_rows = 0;
+ }
+
+ return $unlim_num_rows;
+}
+
+/**
+ * Function to handle all aspects relating to executing the query
+ *
+ * @param array $analyzed_sql_results analyzed sql results
+ * @param String $full_sql_query full sql query
+ * @param boolean $is_gotofile whether to go to a file
+ * @param String $db current database
+ * @param String $table current table
+ * @param boolean $find_real_end whether to find the real end
+ * @param String $sql_query_for_bookmark sql query to be stored as bookmark
+ * @param array $extra_data extra data
+ *
+ * @return mixed
+ */
+function PMA_executeTheQuery($analyzed_sql_results, $full_sql_query, $is_gotofile,
+ $db, $table, $find_real_end, $sql_query_for_bookmark, $extra_data
+) {
+ // Only if we ask to see the php code
+ if (isset($GLOBALS['show_as_php']) || ! empty($GLOBALS['validatequery'])) {
+ $result = null;
+ $num_rows = 0;
+ $unlim_num_rows = 0;
+ } else { // If we don't ask to see the php code
+ if (isset($_SESSION['profiling']) && PMA_Util::profilingSupported()) {
+ $GLOBALS['dbi']->query('SET PROFILING=1;');
+ }
+
+ $result = PMA_executeQueryAndStoreResults($full_sql_query);
+
+ // Displays an error message if required and stop parsing the script
+ $error = $GLOBALS['dbi']->getError();
+ if ($error) {
+ PMA_handleQueryExecuteError($is_gotofile, $error, $full_sql_query);
+ }
+
+ // If there are no errors and bookmarklabel was given,
+ // store the query as a bookmark
+ if (! empty($_POST['bkm_label']) && ! empty($sql_query_for_bookmark)) {
+ PMA_storeTheQueryAsBookmark(
+ $db, $GLOBALS['cfg']['Bookmark']['user'],
+ $sql_query_for_bookmark, $_POST['bkm_label'],
+ isset($_POST['bkm_replace']) ? $_POST['bkm_replace'] : null
+ );
+ } // end store bookmarks
+
+ // Gets the number of rows affected/returned
+ // (This must be done immediately after the query because
+ // mysql_affected_rows() reports about the last query done)
+ $num_rows = PMA_getNumberOfRowsAffectedOrChanged(
+ $analyzed_sql_results['is_affected'], $result,
+ isset($num_rows) ? $num_rows : null
+ );
+
+ // Grabs the profiling results
+ if (isset($_SESSION['profiling']) && PMA_Util::profilingSupported()) {
+ $profiling_results = $GLOBALS['dbi']->fetchResult('SHOW PROFILE;');
+ }
+
+ $justBrowsing = PMA_isJustBrowsing(
+ $analyzed_sql_results, isset($find_real_end) ? $find_real_end : null
+ );
+
+ $unlim_num_rows = PMA_countQueryResults(
+ $num_rows, $analyzed_sql_results['is_select'], $justBrowsing, $db,
+ $table, $analyzed_sql_results['parsed_sql'], $analyzed_sql_results
+ );
+
+ $extra_data = PMA_cleanupRelations(
+ isset($db) ? $db : '', isset($table) ? $table : '',
+ isset($_REQUEST['dropped_column']) ? $_REQUEST['dropped_column'] : null,
+ isset($_REQUEST['purge']) ? $_REQUEST['purge'] : null,
+ isset($extra_data) ? $extra_data : null
+ );
+ }
+
+ return array($result, $num_rows, $unlim_num_rows,
+ isset($profiling_results) ? $profiling_results : null,
+ isset($justBrowsing) ? $justBrowsing : null, $extra_data
+ );
+}
+/**
+ * Delete related tranformatioinformationn information
+ *
+ * @param String $db current database
+ * @param String $table current table
+ * @param array $analyzed_sql analyzed sql query
+ *
+ * @return void
+ */
+function PMA_deleteTransformationInfo($db, $table, $analyzed_sql)
+{
+ include_once 'libraries/transformations.lib.php';
+ if ($analyzed_sql[0]['querytype'] == 'ALTER') {
+ if (stripos($analyzed_sql[0]['unsorted_query'], 'DROP') !== false) {
+ $drop_column = PMA_getColumnNameInColumnDropSql(
+ $analyzed_sql[0]['unsorted_query']
+ );
+
+ if ($drop_column != '') {
+ PMA_clearTransformations($db, $table, $drop_column);
+ }
+ }
+
+ } else if (($analyzed_sql[0]['querytype'] == 'DROP') && ($table != '')) {
+ PMA_clearTransformations($db, $table);
+ }
+}
+
+/**
+ * Function to get the message for the no rows returned case
+ *
+ * @param string $message_to_show message to show
+ * @param array $analyzed_sql_results analyzed sql results
+ * @param int $num_rows number of rows
+ *
+ * @return string $message
+ */
+function PMA_getMessageForNoRowsReturned($message_to_show, $analyzed_sql_results,
+ $num_rows
+) {
+ if ($analyzed_sql_results['is_delete']) {
+ $message = PMA_Message::getMessageForDeletedRows($num_rows);
+ } elseif ($analyzed_sql_results['is_insert']) {
+ if ($analyzed_sql_results['is_replace']) {
+ // For replace we get DELETED + INSERTED row count,
+ // so we have to call it affected
+ $message = PMA_Message::getMessageForAffectedRows($num_rows);
+ } else {
+ $message = PMA_Message::getMessageForInsertedRows($num_rows);
+ }
+ $insert_id = $GLOBALS['dbi']->insertId();
+ if ($insert_id != 0) {
+ // insert_id is id of FIRST record inserted in one insert,
+ // so if we inserted multiple rows, we had to increment this
+ $message->addMessage('[br]');
+ // need to use a temporary because the Message class
+ // currently supports adding parameters only to the first
+ // message
+ $_inserted = PMA_Message::notice(__('Inserted row id: %1$d'));
+ $_inserted->addParam($insert_id + $num_rows - 1);
+ $message->addMessage($_inserted);
+ }
+ } elseif ($analyzed_sql_results['is_affected']) {
+ $message = PMA_Message::getMessageForAffectedRows($num_rows);
+
+ // Ok, here is an explanation for the !$is_select.
+ // The form generated by sql_query_form.lib.php
+ // and db_sql.php has many submit buttons
+ // on the same form, and some confusion arises from the
+ // fact that $message_to_show is sent for every case.
+ // The $message_to_show containing a success message and sent with
+ // the form should not have priority over errors
+ } elseif (! empty($message_to_show) && ! $analyzed_sql_results['is_select']) {
+ $message = PMA_Message::rawSuccess(htmlspecialchars($message_to_show));
+ } elseif (! empty($GLOBALS['show_as_php'])) {
+ $message = PMA_Message::success(__('Showing as PHP code'));
+ } elseif (isset($GLOBALS['show_as_php'])) {
+ /* User disable showing as PHP, query is only displayed */
+ $message = PMA_Message::notice(__('Showing SQL query'));
+ } elseif (! empty($GLOBALS['validatequery'])) {
+ $message = PMA_Message::notice(__('Validated SQL'));
+ } else {
+ $message = PMA_Message::success(
+ __('MySQL returned an empty result set (i.e. zero rows).')
+ );
+ }
+
+ if (isset($GLOBALS['querytime'])) {
+ $_querytime = PMA_Message::notice('(' . __('Query took %01.4f sec') . ')');
+ $_querytime->addParam($GLOBALS['querytime']);
+ $message->addMessage($_querytime);
+ }
+
+ return $message;
+}
+
+/**
+ * Function to send the Ajax response when no rows returned
+ *
+ * @param string $message message to be send
+ * @param array $analyzed_sql analyzed sql
+ * @param object $displayResultsObject DisplayResult instance
+ * @param array $extra_data extra data
+ *
+ * @return void
+ */
+function PMA_sendAjaxResponseForNoResultsReturned($message, $analyzed_sql,
+ $displayResultsObject, $extra_data
+) {
+ /**
+ * @todo find a better way to make getMessage() in Header.class.php
+ * output the intended message
+ */
+ $GLOBALS['message'] = $message;
+
+ if ($GLOBALS['cfg']['ShowSQL']) {
+ $extra_data['sql_query'] = PMA_Util::getMessage(
+ $message, $GLOBALS['sql_query'], 'success'
+ );
+ }
+ if (isset($GLOBALS['reload']) && $GLOBALS['reload'] == 1) {
+ $extra_data['reload'] = 1;
+ $extra_data['db'] = $GLOBALS['db'];
+ }
+ $response = PMA_Response::getInstance();
+ $response->isSuccess($message->isSuccess());
+ // No need to manually send the message
+ // The Response class will handle that automatically
+ $query__type = PMA_DisplayResults::QUERY_TYPE_SELECT;
+ if ($analyzed_sql[0]['querytype'] == $query__type) {
+ $createViewHTML = $displayResultsObject->getCreateViewQueryResultOp(
+ $analyzed_sql
+ );
+ $response->addHTML($createViewHTML.'<br />');
+ }
+
+ $response->addJSON(isset($extra_data) ? $extra_data : array());
+ if (empty($_REQUEST['ajax_page_request'])) {
+ $response->addJSON('message', $message);
+ exit;
+ }
+}
+
+/**
+ * Function to respond back when the query returns zero rows
+ * This method is called
+ * 1-> When browsing an empty table
+ * 2-> When executing a query on a non empty table which returns zero results
+ * 3-> When executing a query on an empty table
+ * 4-> When executing an INSERT, UPDATE, DEDETE query from the SQL tab
+ * 5-> When deleting a row from BROWSE tab
+ * 6-> When searching using the SEARCH tab which returns zero results
+ * 7-> When changing the structure of the table except change operation
+ *
+ * @param array $analyzed_sql_results analyzed sql results
+ * @param string $db current database
+ * @param string $table current table
+ * @param string $message_to_show message to show
+ * @param int $num_rows number of rows
+ * @param object $displayResultsObject DisplayResult instance
+ * @param array $extra_data extra data
+ *
+ * @return void
+ */
+function PMA_sendQueryResponseForNoResultsReturned($analyzed_sql_results, $db,
+ $table, $message_to_show, $num_rows, $displayResultsObject, $extra_data
+) {
+ if (PMA_isDeleteTransformationInfo($analyzed_sql_results)) {
+ PMA_deleteTransformationInfo(
+ $db, $table, $analyzed_sql_results['analyzed_sql']
+ );
+ }
+
+ $message = PMA_getMessageForNoRowsReturned(
+ isset($message_to_show) ? $message_to_show : null, $analyzed_sql_results,
+ $num_rows
+ );
+ if (!isset($GLOBALS['show_as_php'])) {
+ PMA_sendAjaxResponseForNoResultsReturned(
+ $message, $analyzed_sql_results['analyzed_sql'],
+ $displayResultsObject,
+ isset($extra_data) ? $extra_data : null
+ );
+ }
+ exit();
+}
+
+/**
+ * Function to send response for ajax grid edit
+ *
+ * @param object $result result of the executed query
+ *
+ * @return void
+ */
+function PMA_sendResponseForGridEdit($result)
+{
+ $row = $GLOBALS['dbi']->fetchRow($result);
+ $response = PMA_Response::getInstance();
+ $response->addJSON('value', $row[0]);
+ exit;
+}
+
+/**
+ * Function to get html for the sql query results div
+ *
+ * @param string $previous_update_query_html html for the previously executed query
+ * @param string $profiling_chart_html html for profiling
+ * @param object $missing_unique_column_msg message for the missing unique column
+ * @param object $bookmark_created_msg message for bookmark creation
+ * @param string $table_html html for the table for displaying sql
+ * results
+ * @param string $indexes_problems_html html for displaying errors in indexes
+ * @param string $bookmark_support_html html for displaying bookmark form
+ * @param string $print_button_html html for the print button in printview
+ *
+ * @return string $html_output
+ */
+function PMA_getHtmlForSqlQueryResults($previous_update_query_html,
+ $profiling_chart_html, $missing_unique_column_msg, $bookmark_created_msg,
+ $table_html, $indexes_problems_html, $bookmark_support_html, $print_button_html
+) {
+ //begin the sqlqueryresults div here. container div
+ $html_output = '<div id="sqlqueryresults" class="ajax">';
+ $html_output .= isset($previous_update_query_html)
+ ? $previous_update_query_html : '';
+ $html_output .= isset($profiling_chart_html) ? $profiling_chart_html : '';
+ $html_output .= isset($missing_unique_column_msg)
+ ? $missing_unique_column_msg->getDisplay() : '';
+ $html_output .= isset($bookmark_created_msg)
+ ? $bookmark_created_msg->getDisplay() : '';
+ $html_output .= $table_html;
+ $html_output .= isset($indexes_problems_html) ? $indexes_problems_html : '';
+ $html_output .= isset($bookmark_support_html) ? $bookmark_support_html : '';
+ $html_output .= isset($print_button_html) ? $print_button_html : '';
+ $html_output .= '</div>'; // end sqlqueryresults div
+
+ return $html_output;
+}
+
+/**
+ * Returns a message for successful creation of a bookmark or null if a bookmark
+ * was not created
+ *
+ * @return PMA_message $bookmark_created_msg
+ */
+function PMA_getBookmarkCreatedMessage()
+{
+ if (isset($_GET['label'])) {
+ $bookmark_created_msg = PMA_message::success(__('Bookmark %s created'));
+ $bookmark_created_msg->addParam($_GET['label']);
+ } else {
+ $bookmark_created_msg = null;
+ }
+
+ return $bookmark_created_msg;
+}
+
+/**
+ * Function to get html for the sql query results table
+ *
+ * @param array $sql_data sql data
+ * @param object $displayResultsObject instance of DisplayResult.class
+ * @param string $db current database
+ * @param string $goto goto page url
+ * @param string $pmaThemeImage theme image uri
+ * @param string $url_query url query
+ * @param string $disp_mode display mode
+ * @param string $sql_limit_to_append sql limit to append
+ * @param bool $editable whether the result table is editable or not
+ * @param int $unlim_num_rows unlimited number of rows
+ * @param int $num_rows number of rows
+ * @param bool $showtable whether to show table or not
+ * @param object $result result of the executed query
+ * @param array $analyzed_sql_results analyzed sql results
+ *
+ * @return String
+ */
+function PMA_getHtmlForSqlQueryResultsTable($sql_data, $displayResultsObject, $db,
+ $goto, $pmaThemeImage, $url_query, $disp_mode, $sql_limit_to_append,
+ $editable, $unlim_num_rows, $num_rows, $showtable, $result,
+ $analyzed_sql_results
+) {
+ $printview = isset($_REQUEST['printview']) ? $_REQUEST['printview'] : null;
+ if (! empty($sql_data) && ($sql_data['valid_queries'] > 1)
+ || $analyzed_sql_results['is_procedure']
+ ) {
+ $_SESSION['is_multi_query'] = true;
+ $table_html = PMA_getTableHtmlForMultipleQueries(
+ $displayResultsObject, $db, $sql_data, $goto,
+ $pmaThemeImage, $printview, $url_query,
+ $disp_mode, $sql_limit_to_append, $editable
+ );
+ } else {
+ if (isset($result) && $result) {
+ $fields_meta = $GLOBALS['dbi']->getFieldsMeta($result);
+ $fields_cnt = count($fields_meta);
+ }
+ $_SESSION['is_multi_query'] = false;
+ $displayResultsObject->setProperties(
+ $unlim_num_rows, $fields_meta, $analyzed_sql_results['is_count'],
+ $analyzed_sql_results['is_export'], $analyzed_sql_results['is_func'],
+ $analyzed_sql_results['is_analyse'], $num_rows,
+ $fields_cnt, $GLOBALS['querytime'], $pmaThemeImage, $GLOBALS['text_dir'],
+ $analyzed_sql_results['is_maint'], $analyzed_sql_results['is_explain'],
+ $analyzed_sql_results['is_show'], $showtable, $printview, $url_query,
+ $editable
+ );
+
+ $table_html = $displayResultsObject->getTable(
+ $result, $disp_mode, $analyzed_sql_results['analyzed_sql']
+ );
+ $GLOBALS['dbi']->freeResult($result);
+ }
+
+ return $table_html;
+}
+
+/**
+ * Function to get html for the previous query if there is such. If not will return
+ * null
+ *
+ * @param string $disp_query display query
+ * @param bool $showSql whether to show sql
+ * @param array $sql_data sql data
+ * @param string $disp_message display message
+ *
+ * @return string $previous_update_query_html
+ */
+function PMA_getHtmlForPreviousUpdateQuery($disp_query, $showSql, $sql_data,
+ $disp_message
+) {
+ // previous update query (from tbl_replace)
+ if (isset($disp_query) && ($showSql == true) && empty($sql_data)) {
+ $previous_update_query_html = PMA_Util::getMessage(
+ $disp_message, $disp_query, 'success'
+ );
+ } else {
+ $previous_update_query_html = null;
+ }
+
+ return $previous_update_query_html;
+}
+
+/**
+ * To get the message if a column index is missing. If not will return null
+ *
+ * @param string $table current table
+ * @param string $db current database
+ * @param boolean $editable whether the results table can be editable or not
+ * @param string $disp_mode display mode
+ *
+ * @return PMA_message $message
+ */
+function PMA_getMessageIfMissingColumnIndex($table, $db, $editable, $disp_mode)
+{
+ if (!empty($table) && ($GLOBALS['dbi']->isSystemSchema($db) || !$editable)) {
+ $missing_unique_column_msg = PMA_message::notice(
+ __(
+ 'Current selection does not contain a unique column.'
+ . ' Grid edit, checkbox, Edit, Copy and Delete features'
+ . ' are not available.'
+ )
+ );
+ } else {
+ $missing_unique_column_msg = null;
+ }
+
+ return $missing_unique_column_msg;
+}
+
+/**
+ * Function to get html to display problems in indexes
+ *
+ * @param string $query_type query type
+ * @param bool $selected whether check table, optimize table, analyze
+ * table or repair table has been selected with
+ * respect to the selected tables from the
+ * database structure page.
+ * @param string $db current database
+ *
+ * @return string
+ */
+function PMA_getHtmlForIndexesProblems($query_type, $selected, $db)
+{
+ // BEGIN INDEX CHECK See if indexes should be checked.
+ if (isset($query_type)
+ && $query_type == 'check_tbl'
+ && isset($selected)
+ && is_array($selected)
+ ) {
+ $indexes_problems_html = '';
+ foreach ($selected as $idx => $tbl_name) {
+ $check = PMA_Index::findDuplicates($tbl_name, $db);
+ if (! empty($check)) {
+ $indexes_problems_html .= sprintf(
+ __('Problems with indexes of table `%s`'), $tbl_name
+ );
+ $indexes_problems_html .= $check;
+ }
+ }
+ } else {
+ $indexes_problems_html = null;
+ }
+
+ return $indexes_problems_html;
+}
+
+/**
+ * Function to get the html for the print button in printview
+ *
+ * @return string $print_button_html html for the print button
+ */
+function PMA_getHtmlForPrintButton()
+{
+ // Do print the page if required
+ if (isset($_REQUEST['printview']) && $_REQUEST['printview'] == '1') {
+ $print_button_html = PMA_Util::getButton();
+ } else {
+ $print_button_html = null;
+ }
+
+ return $print_button_html;
+}
+
+/**
+ * Function to display results when the executed query returns non empty results
+ *
+ * @param array $result executed query results
+ * @param bool $justBrowsing whether just browsing or not
+ * @param array $analyzed_sql_results analysed sql results
+ * @param string $db current database
+ * @param string $table current table
+ * @param string $disp_mode display mode
+ * @param string $message message to show
+ * @param array $sql_data sql data
+ * @param object $displayResultsObject Instance of DisplyResults.class
+ * @param string $goto goto page url
+ * @param string $pmaThemeImage uri of the theme image
+ * @param string $sql_limit_to_append sql limit to append
+ * @param int $unlim_num_rows unlimited number of rows
+ * @param int $num_rows number of rows
+ * @param string $full_sql_query full sql query
+ * @param string $disp_query display query
+ * @param string $disp_message display message
+ * @param array $profiling_results profiling results
+ * @param string $query_type query type
+ * @param bool $selected whether check table, optimize table, analyze
+ * table or repair table has been selected with
+ * respect to the selected tables from the
+ * database structure page.
+ * @param string $sql_query sql query
+ * @param string $complete_query complete sql query
+ *
+ * @return void
+ */
+function PMA_sendQueryResponseForResultsReturned($result, $justBrowsing,
+ $analyzed_sql_results, $db, $table, $disp_mode, $message, $sql_data,
+ $displayResultsObject, $goto, $pmaThemeImage, $sql_limit_to_append,
+ $unlim_num_rows, $num_rows, $full_sql_query, $disp_query,
+ $disp_message, $profiling_results, $query_type, $selected, $sql_query,
+ $complete_query
+) {
+ // If we are retrieving the full value of a truncated field or the original
+ // value of a transformed field, show it here
+ if (isset($_REQUEST['grid_edit']) && $_REQUEST['grid_edit'] == true) {
+ PMA_sendResponseForGridEdit($result);
+ // script has exited at this point
+ }
+
+ // Gets the list of fields properties
+ if (isset($result) && $result) {
+ $fields_meta = $GLOBALS['dbi']->getFieldsMeta($result);
+ }
+
+ // Should be initialized these parameters before parsing
+ $showtable = isset($showtable) ? $showtable : null;
+ $url_query = isset($url_query) ? $url_query : null;
+
+ $response = PMA_Response::getInstance();
+ $header = $response->getHeader();
+ $scripts = $header->getScripts();
+
+ // hide edit and delete links:
+ // - for information_schema
+ // - if the result set does not contain all the columns of a unique key
+ // (unless this is an updatable view)
+
+ $sele_exp_cls = $analyzed_sql_results['analyzed_sql'][0]['select_expr_clause'];
+ $updatableView
+ = trim($sele_exp_cls) == '*'
+ && PMA_Table::isUpdatableView($db, $table);
+
+ $has_unique = PMA_resultSetContainsUniqueKey(
+ $db, $table, $fields_meta
+ );
+
+ $editable = $has_unique || $updatableView;
+
+ // Displays the results in a table
+ if (empty($disp_mode)) {
+ // see the "PMA_setDisplayMode()" function in
+ // libraries/DisplayResults.class.php
+ $disp_mode = 'urdr111101';
+ }
+ if (!empty($table) && ($GLOBALS['dbi']->isSystemSchema($db) || !$editable)) {
+ $disp_mode = 'nnnn110111';
+ }
+ if ( isset($_REQUEST['printview']) && $_REQUEST['printview'] == '1') {
+ $disp_mode = 'nnnn000000';
+ }
+
+ if (isset($_REQUEST['table_maintenance'])) {
+ $scripts->addFile('makegrid.js');
+ $scripts->addFile('sql.js');
+ $table_maintenance_html = '';
+ if (isset($message)) {
+ $message = PMA_Message::success($message);
+ $table_maintenance_html = PMA_Util::getMessage(
+ $message, $GLOBALS['sql_query'], 'success'
+ );
+ }
+ $table_maintenance_html .= PMA_getHtmlForSqlQueryResultsTable(
+ isset($sql_data) ? $sql_data : null, $displayResultsObject, $db, $goto,
+ $pmaThemeImage, $url_query, $disp_mode, $sql_limit_to_append,
+ false, $unlim_num_rows, $num_rows, $showtable, $result,
+ $analyzed_sql_results
+ );
+ if (empty($sql_data) || ($sql_data['valid_queries'] = 1)) {
+ $response->addHTML($table_maintenance_html);
+ exit();
+ }
+ }
+
+ if (!isset($_REQUEST['printview']) || $_REQUEST['printview'] != '1') {
+ $scripts->addFile('makegrid.js');
+ $scripts->addFile('sql.js');
+ unset($GLOBALS['message']);
+ //we don't need to buffer the output in getMessage here.
+ //set a global variable and check against it in the function
+ $GLOBALS['buffer_message'] = false;
+ }
+
+ $print_view_header_html = PMA_getHtmlForPrintViewHeader(
+ $db, $full_sql_query, $num_rows
+ );
+
+ $previous_update_query_html = PMA_getHtmlForPreviousUpdateQuery(
+ isset($disp_query) ? $disp_query : null,
+ $GLOBALS['cfg']['ShowSQL'], isset($sql_data) ? $sql_data : null,
+ isset($disp_message) ? $disp_message : null
+ );
+
+ $profiling_chart_html = PMA_getHtmlForProfilingChart(
+ $disp_mode, $db, isset($profiling_results) ? $profiling_results : null
+ );
+
+ $missing_unique_column_msg = PMA_getMessageIfMissingColumnIndex(
+ $table, $db, $editable, $disp_mode
+ );
+
+ $bookmark_created_msg = PMA_getBookmarkCreatedMessage();
+
+ $table_html = PMA_getHtmlForSqlQueryResultsTable(
+ isset($sql_data) ? $sql_data : null, $displayResultsObject, $db, $goto,
+ $pmaThemeImage, $url_query, $disp_mode, $sql_limit_to_append,
+ $editable, $unlim_num_rows, $num_rows, $showtable, $result,
+ $analyzed_sql_results
+ );
+
+ $indexes_problems_html = PMA_getHtmlForIndexesProblems(
+ isset($query_type) ? $query_type : null,
+ isset($selected) ? $selected : null, $db
+ );
+
+ if (isset($GLOBALS['cfg']['Bookmark'])) {
+ $bookmark_support_html = PMA_getHtmlForBookmark(
+ $disp_mode,
+ $GLOBALS['cfg']['Bookmark'],
+ $sql_query, $db, $table,
+ isset($complete_query) ? $complete_query : $sql_query,
+ $GLOBALS['cfg']['Bookmark']['user']
+ );
+ } else {
+ $bookmark_support_html = '';
+ }
+
+ $print_button_html = PMA_getHtmlForPrintButton();
+
+ $html_output = isset($table_maintenance_html) ? $table_maintenance_html : '';
+
+ $html_output .= isset($print_view_header_html) ? $print_view_header_html : '';
+
+ $html_output .= PMA_getHtmlForSqlQueryResults(
+ $previous_update_query_html, $profiling_chart_html,
+ $missing_unique_column_msg, $bookmark_created_msg,
+ $table_html, $indexes_problems_html, $bookmark_support_html,
+ $print_button_html
+ );
+
+ $response->addHTML($html_output);
+
+ exit();
+}
+
+/**
+ * Function to send response for both empty results and non empty results
+ *
+ * @param int $num_rows number of rows returned by the executed query
+ * @param int $unlim_num_rows unlimited number of rows
+ * @param bool $is_affected is affected
+ * @param string $db current database
+ * @param string $table current table
+ * @param string $message_to_show message to show
+ * @param array $analyzed_sql_results analyzed Sql Results
+ * @param object $displayResultsObject Instance of DisplayResult class
+ * @param array $extra_data extra data
+ * @param array $result executed query results
+ * @param bool $justBrowsing whether just browsing or not
+ * @param string $disp_mode disply mode
+ * @param object $message message
+ * @param array $sql_data sql data
+ * @param string $goto goto page url
+ * @param string $pmaThemeImage uri of the PMA theme image
+ * @param string $sql_limit_to_append sql limit to append
+ * @param string $full_sql_query full sql query
+ * @param string $disp_query display query
+ * @param string $disp_message display message
+ * @param array $profiling_results profiling results
+ * @param string $query_type query type
+ * @param bool $selected whether check table, optimize table, analyze
+ * table or repair table has been selected with
+ * respect to the selected tables from the
+ * database structure page.
+ * @param string $sql_query sql query
+ * @param string $complete_query complete query
+ *
+ * @return void
+ */
+function PMA_sendQueryResponse($num_rows, $unlim_num_rows, $is_affected,
+ $db, $table, $message_to_show, $analyzed_sql_results, $displayResultsObject,
+ $extra_data, $result, $justBrowsing, $disp_mode,$message, $sql_data,
+ $goto, $pmaThemeImage, $sql_limit_to_append, $full_sql_query,
+ $disp_query, $disp_message, $profiling_results, $query_type, $selected,
+ $sql_query, $complete_query
+) {
+ // No rows returned -> move back to the calling page
+ if ((0 == $num_rows && 0 == $unlim_num_rows) || $is_affected) {
+ PMA_sendQueryResponseForNoResultsReturned(
+ $analyzed_sql_results, $db, $table,
+ isset($message_to_show) ? $message_to_show : null,
+ $num_rows, $displayResultsObject, $extra_data
+ );
+
+ } else {
+ // At least one row is returned -> displays a table with results
+ PMA_sendQueryResponseForResultsReturned(
+ isset($result) ? $result : null, $justBrowsing, $analyzed_sql_results,
+ $db, $table, isset($disp_mode) ? $disp_mode : null,
+ isset($message) ? $message : null, isset($sql_data) ? $sql_data : null,
+ $displayResultsObject, $goto, $pmaThemeImage,
+ $sql_limit_to_append, $unlim_num_rows,
+ $num_rows, $full_sql_query,
+ isset($disp_query) ? $disp_query : null,
+ isset($disp_message) ? $disp_message : null, $profiling_results,
+ isset($query_type) ? $query_type : null,
+ isset($selected) ? $selected : null, $sql_query,
+ isset($complete_query) ? $complete_query : null
+ );
+ } // end rows returned
+}
+
+/**
+ * Function to execute the query and send the response
+ *
+ * @param array $analyzed_sql_results analysed sql results
+ * @param bool $is_gotofile whether goto file or not
+ * @param string $db current database
+ * @param string $table current table
+ * @param bool $find_real_end whether to find real end or not
+ * @param string $sql_query_for_bookmark the sql query to be stored as bookmark
+ * @param array $extra_data extra data
+ * @param bool $is_affected whether affected or not
+ * @param string $message_to_show message to show
+ * @param string $disp_mode display mode
+ * @param string $message message
+ * @param array $sql_data sql data
+ * @param string $goto goto page url
+ * @param string $pmaThemeImage uri of the PMA theme image
+ * @param string $disp_query display query
+ * @param string $disp_message display message
+ * @param string $query_type query type
+ * @param string $sql_query sql query
+ * @param bool $selected whether check table, optimize table,
+ * analyze table or repair table has been
+ * selected with respect to the selected
+ * tables from the database structure page
+ * @param string $complete_query complete query
+ *
+ * @return void
+ */
+function PMA_executeQueryAndSendQueryResponse($analyzed_sql_results,
+ $is_gotofile, $db, $table, $find_real_end, $sql_query_for_bookmark,
+ $extra_data, $is_affected, $message_to_show, $disp_mode, $message,
+ $sql_data, $goto, $pmaThemeImage, $disp_query, $disp_message,
+ $query_type, $sql_query, $selected, $complete_query
+) {
+ // Include PMA_Index class for use in PMA_DisplayResults class
+ include_once './libraries/Index.class.php';
+
+ include 'libraries/DisplayResults.class.php';
+
+ // Handle remembered sorting order, only for single table query
+ // Handling is not required when it's a union query
+ // (the parser never sets the 'union' key to 0)
+ if (PMA_isRememberSortingOrder($analyzed_sql_results)
+ && ! isset($analyzed_sql_results['analyzed_sql'][0]['queryflags']['union'])
+ ) {
+ PMA_handleSortOrder($db, $table, $analyzed_sql_results, $sql_query);
+ }
+
+ $displayResultsObject = new PMA_DisplayResults(
+ $GLOBALS['db'], $GLOBALS['table'], $GLOBALS['goto'], $sql_query
+ );
+ $displayResultsObject->setConfigParamsForDisplayTable();
+
+ // assign default full_sql_query
+ $full_sql_query = $sql_query;
+
+ // Do append a "LIMIT" clause?
+ if (PMA_isAppendLimitClause($analyzed_sql_results)) {
+ list($sql_limit_to_append,
+ $full_sql_query, $analyzed_display_query, $display_query
+ ) = PMA_appendLimitClause(
+ $full_sql_query, $analyzed_sql_results['analyzed_sql'],
+ isset($display_query)
+ );
+ } else {
+ $sql_limit_to_append = '';
+ }
+
+ $GLOBALS['reload'] = PMA_hasCurrentDbChanged($db);
+ $GLOBALS['dbi']->selectDb($db);
+
+ // Execute the query
+ list($result, $num_rows, $unlim_num_rows, $profiling_results,
+ $justBrowsing, $extra_data
+ ) = PMA_executeTheQuery(
+ $analyzed_sql_results,
+ $full_sql_query,
+ $is_gotofile,
+ $db,
+ $table,
+ isset($find_real_end) ? $find_real_end : null,
+ isset($sql_query_for_bookmark) ? $sql_query_for_bookmark : null,
+ isset($extra_data) ? $extra_data : null
+ );
+
+ PMA_sendQueryResponse(
+ $num_rows,
+ $unlim_num_rows,
+ $is_affected,
+ $db,
+ $table,
+ isset($message_to_show) ? $message_to_show : null,
+ $analyzed_sql_results,
+ $displayResultsObject,
+ $extra_data,
+ isset($result) ? $result : null,
+ $justBrowsing,
+ isset($disp_mode) ? $disp_mode : null,
+ isset($message) ? $message : null,
+ isset($sql_data) ? $sql_data : null,
+ $goto,
+ $pmaThemeImage,
+ $sql_limit_to_append,
+ $full_sql_query,
+ isset($disp_query) ? $disp_query : null,
+ isset($disp_message) ? $disp_message : null,
+ $profiling_results,
+ isset($query_type) ? $query_type : null,
+ isset($selected) ? $selected : null,
+ $sql_query,
+ isset($complete_query) ? $complete_query : null
+ );
+}
+
+/**
+ * Function to define pos to display a row
+ *
+ * @param Int $number_of_line Number of the line to display
+ * @param Int $max_rows Number of rows by page
+ *
+ * @return Int Start position to display the line
+ */
+function PMA_getStartPosToDisplayRow($number_of_line, $max_rows = null)
+{
+ if (null === $max_rows) {
+ $max_rows = $_SESSION['tmpval']['max_rows'];
+ }
+
+ return @((ceil($number_of_line / $max_rows) - 1) * $max_rows);
+}
+
+/**
+ * Function to calculate new pos if pos is higher than number of rows
+ * of displayed table
+ *
+ * @param String $db Database name
+ * @param String $table Table name
+ * @param Int $pos Initial position
+ *
+ * @return Int Number of pos to display last page
+ */
+function PMA_calculatePosForLastPage($db, $table, $pos)
+{
+ if (null === $pos) {
+ $pos = $_SESSION['tmpval']['pos'];
+ }
+
+ $unlim_num_rows = PMA_Table::countRecords($db, $table, true);
+ //If position is higher than number of rows
+ if ($unlim_num_rows <= $pos && 0 != $pos) {
+ $pos = PMA_getStartPosToDisplayRow($unlim_num_rows);
+ }
+
+ return $pos;
+}
+
+?>
diff --git a/libraries/sql_query_form.lib.php b/libraries/sql_query_form.lib.php
new file mode 100644
index 0000000000..be33019ec8
--- /dev/null
+++ b/libraries/sql_query_form.lib.php
@@ -0,0 +1,520 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * functions for displaying the sql query form
+ *
+ * @usedby server_sql.php
+ * @usedby db_sql.php
+ * @usedby tbl_sql.php
+ * @usedby tbl_structure.php
+ * @usedby tbl_tracking.php
+ * @usedby querywindow.php
+ * @package PhpMyAdmin
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ *
+ */
+require_once './libraries/file_listing.lib.php'; // used for file listing
+require_once './libraries/bookmark.lib.php'; // used for bookmarks
+
+/**
+ * return HTML for the sql query boxes
+ *
+ * @param boolean|string $query query to display in the textarea
+ * or true to display last executed
+ * @param boolean|string $display_tab sql|files|history|full|false
+ * what part to display
+ * false if not inside querywindow
+ * @param string $delimiter delimeter
+ *
+ * @return string
+ *
+ * @usedby server_sql.php
+ * @usedby db_sql.php
+ * @usedby tbl_sql.php
+ * @usedby tbl_structure.php
+ * @usedby tbl_tracking.php
+ * @usedby querywindow.php
+ */
+function PMA_getHtmlForSqlQueryForm(
+ $query = true, $display_tab = false, $delimiter = ';'
+) {
+ $html = '';
+ // check tab to display if inside querywindow
+ if (! $display_tab) {
+ $display_tab = 'full';
+ $is_querywindow = false;
+ } else {
+ $is_querywindow = true;
+ }
+
+ // query to show
+ if (true === $query) {
+ $query = $GLOBALS['sql_query'];
+ }
+
+ // set enctype to multipart for file uploads
+ if ($GLOBALS['is_upload']) {
+ $enctype = ' enctype="multipart/form-data"';
+ } else {
+ $enctype = '';
+ }
+
+ $table = '';
+ $db = '';
+ if (! strlen($GLOBALS['db'])) {
+ // prepare for server related
+ $goto = empty($GLOBALS['goto']) ?
+ 'server_sql.php' : $GLOBALS['goto'];
+ } elseif (! strlen($GLOBALS['table'])) {
+ // prepare for db related
+ $db = $GLOBALS['db'];
+ $goto = empty($GLOBALS['goto']) ?
+ 'db_sql.php' : $GLOBALS['goto'];
+ } else {
+ $table = $GLOBALS['table'];
+ $db = $GLOBALS['db'];
+ $goto = empty($GLOBALS['goto']) ?
+ 'tbl_sql.php' : $GLOBALS['goto'];
+ }
+
+ // start output
+ if ($is_querywindow) {
+ $html .= '<form method="post" id="sqlqueryform"';
+ $html .= ' action="import.php" ' . $enctype . ' name="sqlform">';
+ } else {
+ $html .= '<form method="post" action="import.php" ' . $enctype;
+ $html .= ' class="ajax"';
+ $html .= ' id="sqlqueryform" name="sqlform">' . "\n";
+ }
+
+ if ($is_querywindow) {
+ $html .= '<input type="hidden" name="focus_querywindow"'
+ .' value="true" />' . "\n";
+ if ($display_tab != 'sql' && $display_tab != 'full') {
+ $html .= '<input type="hidden" name="sql_query"'
+ .' value="" />' . "\n";
+ $html .= '<input type="hidden" name="show_query"'
+ .' value="1" />' . "\n";
+ }
+ }
+ $html .= '<input type="hidden" name="is_js_confirmed" value="0" />'
+ . "\n" . PMA_URL_getHiddenInputs($db, $table) . "\n"
+ .'<input type="hidden" name="pos" value="0" />' . "\n"
+ .'<input type="hidden" name="goto" value="'
+ .htmlspecialchars($goto) . '" />' . "\n"
+ .'<input type="hidden" name="message_to_show" value="'
+ . __('Your SQL query has been executed successfully') . '" />'
+ . "\n" .'<input type="hidden" name="prev_sql_query" value="'
+ . htmlspecialchars($query) . '" />' . "\n";
+
+ // display querybox
+ if ($display_tab === 'full' || $display_tab === 'sql') {
+ $html .= PMA_getHtmlForSqlQueryFormInsert(
+ $query, $is_querywindow, $delimiter
+ );
+ }
+
+ // display uploads
+ if ($display_tab === 'files' && $GLOBALS['is_upload']) {
+ $html .= PMA_getHtmlForSqlQueryFormUpload();
+ }
+
+ // Bookmark Support
+ if ($display_tab === 'full' || $display_tab === 'history') {
+ if (! empty($GLOBALS['cfg']['Bookmark'])) {
+ $html .= PMA_getHtmlForSqlQueryFormBookmark();
+ }
+ }
+
+ // Encoding setting form appended by Y.Kawada
+ if (function_exists('PMA_Kanji_encodingForm')) {
+ $html .= PMA_Kanji_encodingForm();
+ }
+
+ $html .= '</form>' . "\n";
+ // print an empty div, which will be later filled with
+ // the sql query results by ajax
+ $html .= '<div id="sqlqueryresults"></div>';
+
+ return $html;
+}
+
+/**
+ * return HTML for Sql Query Form Insert
+ *
+ * @param string $query query to display in the textarea
+ * @param boolean $is_querywindow if inside querywindow or not
+ * @param string $delimiter default delimiter to use
+ *
+ * @return string
+ *
+ * @usedby PMA_getHtmlForSqlQueryForm()
+ */
+function PMA_getHtmlForSqlQueryFormInsert(
+ $query = '', $is_querywindow = false, $delimiter = ';'
+) {
+ // enable auto select text in textarea
+ if ($GLOBALS['cfg']['TextareaAutoSelect']) {
+ $auto_sel = ' onclick="selectContent(this, sql_box_locked, true);"';
+ } else {
+ $auto_sel = '';
+ }
+
+ // enable locking if inside query window
+ if ($is_querywindow) {
+ $locking = ' onkeypress="document.sqlform.elements[\'LockFromUpdate\'].'
+ .'checked = true;"';
+ $height = $GLOBALS['cfg']['TextareaRows'] * 1.25;
+ } else {
+ $locking = '';
+ $height = $GLOBALS['cfg']['TextareaRows'] * 2;
+ }
+
+ $table = '';
+ $db = '';
+ $fields_list = array();
+ if (! strlen($GLOBALS['db'])) {
+ // prepare for server related
+ $legend = sprintf(
+ __('Run SQL query/queries on server %s'),
+ '&quot;' . htmlspecialchars(
+ ! empty($GLOBALS['cfg']['Servers'][$GLOBALS['server']]['verbose'])
+ ? $GLOBALS['cfg']['Servers'][$GLOBALS['server']]['verbose']
+ : $GLOBALS['cfg']['Servers'][$GLOBALS['server']]['host']
+ ) . '&quot;'
+ );
+ } elseif (! strlen($GLOBALS['table'])) {
+ // prepare for db related
+ $db = $GLOBALS['db'];
+ // if you want navigation:
+ $tmp_db_link = '<a href="' . $GLOBALS['cfg']['DefaultTabDatabase']
+ . '?' . PMA_URL_getCommon($db) . '"';
+ if ($is_querywindow) {
+ $tmp_db_link .= ' target="_self"'
+ . ' onclick="this.target=window.opener.frame_content.name"';
+ }
+ $tmp_db_link .= '>'
+ . htmlspecialchars($db) . '</a>';
+ // else use
+ // $tmp_db_link = htmlspecialchars($db);
+ $legend = sprintf(__('Run SQL query/queries on database %s'), $tmp_db_link);
+ if (empty($query)) {
+ $query = PMA_Util::expandUserString(
+ $GLOBALS['cfg']['DefaultQueryDatabase'], 'backquote'
+ );
+ }
+ } else {
+ $table = $GLOBALS['table'];
+ $db = $GLOBALS['db'];
+ // Get the list and number of fields
+ // we do a try_query here, because we could be in the query window,
+ // trying to synchonize and the table has not yet been created
+ $fields_list = $GLOBALS['dbi']->getColumns(
+ $db, $GLOBALS['table'], null, true
+ );
+
+ $tmp_db_link = '<a href="' . $GLOBALS['cfg']['DefaultTabDatabase']
+ . '?' . PMA_URL_getCommon($db) . '"';
+ if ($is_querywindow) {
+ $tmp_db_link .= 'target="_parent" '
+ . 'onclick="window.opener.location.href = \''
+ . $GLOBALS['cfg']['DefaultTabDatabase']
+ . '?' . PMA_URL_getCommon($db).'\';return false;"';
+ }
+ $tmp_db_link .= '>'
+ . htmlspecialchars($db) . '</a>';
+ // else use
+ // $tmp_db_link = htmlspecialchars($db);
+ $legend = sprintf(__('Run SQL query/queries on database %s'), $tmp_db_link);
+ if (empty($query)) {
+ $query = PMA_Util::expandUserString(
+ $GLOBALS['cfg']['DefaultQueryTable'], 'backquote'
+ );
+ }
+ }
+ $legend .= ': ' . PMA_Util::showMySQLDocu('SELECT');
+
+ if (count($fields_list)) {
+ $sqlquerycontainer_id = 'sqlquerycontainer';
+ } else {
+ $sqlquerycontainer_id = 'sqlquerycontainerfull';
+ }
+
+ $html = '<a id="querybox"></a>'
+ . '<div id="queryboxcontainer">'
+ . '<fieldset id="queryboxf">';
+ $html .= '<legend>' . $legend . '</legend>';
+ $html .= '<div id="queryfieldscontainer">';
+ $html .= '<div id="' . $sqlquerycontainer_id . '">'
+ . '<textarea tabindex="100" name="sql_query" id="sqlquery"'
+ . ' cols="' . $GLOBALS['cfg']['TextareaCols'] . '"'
+ . ' rows="' . $height . '"'
+ . ' dir="' . $GLOBALS['text_dir'] . '"'
+ . $auto_sel . $locking . '>'
+ . htmlspecialchars($query)
+ . '</textarea>';
+ // Add buttons to generate query easily for
+ // select all, single select, insert, update and delete
+ if (count($fields_list)) {
+ $html .= '<input type="button" value="SELECT *" id="selectall"'
+ . ' class="button sqlbutton" />';
+ $html .= '<input type="button" value="SELECT" id="select"'
+ . ' class="button sqlbutton" />';
+ $html .= '<input type="button" value="INSERT" id="insert"'
+ . ' class="button sqlbutton" />';
+ $html .= '<input type="button" value="UPDATE" id="update"'
+ . ' class="button sqlbutton" />';
+ $html .= '<input type="button" value="DELETE" id="delete"'
+ . ' class="button sqlbutton" />';
+ }
+ $html .= '<input type="button" value="' . __('Clear') . '" id="clear"'
+ . ' class="button sqlbutton" />';
+ $html .= '</div>' . "\n";
+
+ if (count($fields_list)) {
+ $html .= '<div id="tablefieldscontainer">'
+ . '<label>' . __('Columns') . '</label>'
+ . '<select id="tablefields" name="dummy" '
+ . 'size="' . ($GLOBALS['cfg']['TextareaRows'] - 2) . '" '
+ . 'multiple="multiple" ondblclick="insertValueQuery()">';
+ foreach ($fields_list as $field) {
+ $html .= '<option value="'
+ . PMA_Util::backquote(htmlspecialchars($field['Field'])) . '"';
+ if (isset($field['Field'])
+ && strlen($field['Field'])
+ && isset($field['Comment'])
+ ) {
+ $html .= ' title="' . htmlspecialchars($field['Comment']) . '"';
+ }
+ $html .= '>' . htmlspecialchars($field['Field']) . '</option>' . "\n";
+ }
+ $html .= '</select>'
+ . '<div id="tablefieldinsertbuttoncontainer">';
+ if (PMA_Util::showIcons('ActionLinksMode')) {
+ $html .= '<input type="button" class="button" name="insert"'
+ . ' value="&lt;&lt;" onclick="insertValueQuery()"'
+ . ' title="' . __('Insert') . '" />';
+ } else {
+ $html .= '<input type="button" class="button" name="insert"'
+ . ' value="' . __('Insert') . '"'
+ . ' onclick="insertValueQuery()" />';
+ }
+ $html .= '</div>' . "\n"
+ .'</div>' . "\n";
+ }
+
+ $html .= '<div class="clearfloat"></div>' . "\n";
+ $html .= '</div>' . "\n";
+
+ if (! empty($GLOBALS['cfg']['Bookmark'])) {
+ $html .= '<div id="bookmarkoptions">';
+ $html .= '<div class="formelement">';
+ $html .= '<label for="bkm_label">'
+ . __('Bookmark this SQL query:') . '</label>';
+ $html .= '<input type="text" name="bkm_label" id="bkm_label"'
+ . ' tabindex="110" value="" />';
+ $html .= '</div>';
+ $html .= '<div class="formelement">';
+ $html .= '<input type="checkbox" name="bkm_all_users" tabindex="111"'
+ . ' id="id_bkm_all_users" value="true" />';
+ $html .= '<label for="id_bkm_all_users">'
+ . __('Let every user access this bookmark') . '</label>';
+ $html .= '</div>';
+ $html .= '<div class="formelement">';
+ $html .= '<input type="checkbox" name="bkm_replace" tabindex="112"'
+ . ' id="id_bkm_replace" value="true" />';
+ $html .= '<label for="id_bkm_replace">'
+ . __('Replace existing bookmark of same name') . '</label>';
+ $html .= '</div>';
+ $html .= '</div>';
+ }
+
+ $html .= '<div class="clearfloat"></div>' . "\n";
+ $html .= '</fieldset>' . "\n"
+ .'</div>' . "\n";
+
+ $html .= '<fieldset id="queryboxfooter" class="tblFooters">' . "\n";
+ $html .= '<div class="formelement">' . "\n";
+
+ if ($is_querywindow) {
+ $html .= '<input type="checkbox" '
+ . 'name="LockFromUpdate" checked="checked" tabindex="120" '
+ . 'id="checkbox_lock" /> <label for="checkbox_lock">'
+ . __('Do not overwrite this query from outside the window')
+ . '</label>';
+ }
+ $html .= '</div>' . "\n";
+ $html .= '<div class="formelement">' . "\n";
+ $html .= '<label for="id_sql_delimiter">[ ' . __('Delimiter')
+ .'</label>' . "\n";
+ $html .= '<input type="text" name="sql_delimiter" tabindex="131" size="3" '
+ . 'value="' . $delimiter . '" '
+ . 'id="id_sql_delimiter" /> ]';
+
+ $html .= '<input type="checkbox" name="show_query" value="1" '
+ . 'id="checkbox_show_query" tabindex="132" checked="checked" />'
+ . '<label for="checkbox_show_query">' . __('Show this query here again')
+ . '</label>';
+
+ if (! $is_querywindow) {
+ $html .= '<input type="checkbox" name="retain_query_box" value="1" '
+ . 'id="retain_query_box" tabindex="133" '
+ . ($GLOBALS['cfg']['RetainQueryBox'] === false
+ ? '' : ' checked="checked"')
+ . ' />'
+ . '<label for="retain_query_box">' . __('Retain query box')
+ . '</label>';
+ }
+ $html .= '</div>' . "\n";
+ $html .= '<input type="submit" id="button_submit_query" name="SQL"';
+ if ($is_querywindow){
+ $html .= 'onclick="var form = this.parentNode.parentNode;'
+ . ' window.opener.name = \'sqlParentWindow\';'
+ . ' form.target = \'sqlParentWindow\';'
+ . ' return checkSqlQuery(form);"';
+ }
+ $html .= ' tabindex="200" value="' . __('Go') . '" />' . "\n";
+ $html .= '<div class="clearfloat"></div>' . "\n";
+ $html .= '</fieldset>' . "\n";
+
+ return $html;
+}
+
+/**
+ * return HTML for sql Query Form Bookmark
+ *
+ * @return string|void
+ *
+ * @usedby PMA_getHtmlForSqlQueryForm()
+ */
+function PMA_getHtmlForSqlQueryFormBookmark()
+{
+ $bookmark_list = PMA_Bookmark_getList($GLOBALS['db']);
+ if (! $bookmark_list || count($bookmark_list) < 1) {
+ return;
+ }
+
+ $html = '<fieldset id="fieldsetBookmarkOptions">';
+ $html .= '<legend>';
+ $html .= __('Bookmarked SQL query') . '</legend>' . "\n";
+ $html .= '<div class="formelement">';
+ $html .= '<select name="id_bookmark" id="id_bookmark">' . "\n";
+ $html .= '<option value="">&nbsp;</option>' . "\n";
+ foreach ($bookmark_list as $key => $value) {
+ $html .= '<option value="' . htmlspecialchars($key) . '">'
+ .htmlspecialchars($value) . '</option>' . "\n";
+ }
+ // &nbsp; is required for correct display with styles/line height
+ $html .= '</select>&nbsp;' . "\n";
+ $html .= '</div>' . "\n";
+ $html .= '<div class="formelement">' . "\n";
+ $html .= __('Variable');
+ $html .= PMA_Util::showDocu('faq', 'faqbookmark');
+ $html .= '<input type="text" name="bookmark_variable" class="textfield"'
+ .' size="10" />' . "\n";
+ $html .= '</div>' . "\n";
+ $html .= '<div class="formelement">' . "\n";
+ $html .= '<input type="radio" name="action_bookmark" value="0"'
+ .' id="radio_bookmark_exe" checked="checked" />'
+ .'<label for="radio_bookmark_exe">' . __('Submit')
+ .'</label>' . "\n";
+ $html .= '<input type="radio" name="action_bookmark" value="1"'
+ .' id="radio_bookmark_view" />'
+ .'<label for="radio_bookmark_view">' . __('View only')
+ .'</label>' . "\n";
+ $html .= '<input type="radio" name="action_bookmark" value="2"'
+ .' id="radio_bookmark_del" />'
+ .'<label for="radio_bookmark_del">' . __('Delete')
+ .'</label>' . "\n";
+ $html .= '</div>' . "\n";
+ $html .= '<div class="clearfloat"></div>' . "\n";
+ $html .= '</fieldset>' . "\n";
+
+ $html .= '<fieldset id="fieldsetBookmarkOptionsFooter" class="tblFooters">' . "\n";
+ $html .= '<input type="submit" name="SQL" id="button_submit_bookmark" value="'
+ . __('Go') . '" />';
+ $html .= '<div class="clearfloat"></div>' . "\n";
+ $html .= '</fieldset>' . "\n";
+
+ return $html;
+}
+
+/**
+ * return HTML for Sql Query Form Upload
+ *
+ * @return string
+ *
+ * @usedby PMA_getHtmlForSqlQueryForm()
+ */
+function PMA_getHtmlForSqlQueryFormUpload()
+{
+ global $timeout_passed, $local_import_file;
+
+ $errors = array();
+
+ // we allow only SQL here
+ $matcher = '@\.sql(\.(' . PMA_supportedDecompressions() . '))?$@';
+
+ if (!empty($GLOBALS['cfg']['UploadDir'])) {
+ $files = PMA_getFileSelectOptions(
+ PMA_Util::userDir($GLOBALS['cfg']['UploadDir']), $matcher,
+ (isset($timeout_passed) && $timeout_passed && isset($local_import_file))
+ ? $local_import_file
+ : ''
+ );
+ } else {
+ $files = '';
+ }
+
+ // start output
+ $html = '<fieldset id="">';
+ $html .= '<legend>';
+ $html .= __('Browse your computer:') . '</legend>';
+ $html .= '<div class="formelement">';
+ $html .= '<input type="file" name="sql_file" class="textfield" /> ';
+ $html .= PMA_Util::getFormattedMaximumUploadSize($GLOBALS['max_upload_size']);
+ // some browsers should respect this :)
+ $html .= PMA_Util::generateHiddenMaxFileSize($GLOBALS['max_upload_size']) . "\n";
+ $html .= '</div>';
+
+ if ($files === false) {
+ $errors[] = PMA_Message::error(
+ __('The directory you set for upload work cannot be reached.')
+ );
+ } elseif (!empty($files)) {
+ $html .= '<div class="formelement">';
+ $html .= '<strong>' . __('web server upload directory:') .'</strong>' . "\n";
+ $html .= '<select size="1" name="sql_localfile">' . "\n";
+ $html .= '<option value="" selected="selected"></option>' . "\n";
+ $html .= $files;
+ $html .= '</select>' . "\n";
+ $html .= '</div>';
+ }
+
+ $html .= '<div class="clearfloat"></div>' . "\n";
+ $html .= '</fieldset>';
+
+
+ $html .= '<fieldset id="" class="tblFooters">';
+ $html .= __('Character set of the file:') . "\n";
+ $html .= PMA_generateCharsetDropdownBox(
+ PMA_CSDROPDOWN_CHARSET,
+ 'charset_of_file', null, 'utf8', false
+ );
+ $html .= '<input type="submit" name="SQL" value="' . __('Go')
+ .'" />' . "\n";
+ $html .= '<div class="clearfloat"></div>' . "\n";
+ $html .= '</fieldset>';
+
+ foreach ($errors as $error) {
+ $html .= $error->getDisplay();
+ }
+
+ return $html;
+}
+?>
diff --git a/libraries/sqlparser.data.php b/libraries/sqlparser.data.php
new file mode 100644
index 0000000000..f688e642f5
--- /dev/null
+++ b/libraries/sqlparser.data.php
@@ -0,0 +1,990 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * SQL Parser Matching Data
+ *
+ * Copyright 2002 Robin Johnson <robbat2@users.sourceforge.net>
+ * http://www.orbis-terrarum.net/?l=people.robbat2
+ *
+ * This data is used by the SQL Parser to recognize keywords
+ *
+ * It has been extracted from the lex.h file in the MySQL BK tree
+ * (around 4.0.2) as well as the MySQL documentation.
+ *
+ * It's easier to use only uppercase for proper sorting. In case of
+ * doubt, use the test case to verify.
+ *
+ * @package PhpMyAdmin
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+if (! isset($GLOBALS['sql_delimiter'])) {
+ $GLOBALS['sql_delimiter'] = ';';
+}
+
+/**
+ * @global array MySQL function names
+ */
+$PMA_SQPdata_function_name = array (
+ 'ABS',
+ 'ACOS',
+ 'ADDDATE',
+ 'ADDTIME',
+ 'AES_DECRYPT',
+ 'AES_ENCRYPT',
+ 'AREA', // polygon-property-functions.html
+ 'ASBINARY',
+ 'ASCII',
+ 'ASIN',
+ 'ASTEXT',
+ 'ATAN',
+ 'ATAN2',
+ 'AVG',
+ 'BDMPOLYFROMTEXT',
+ 'BDMPOLYFROMWKB',
+ 'BDPOLYFROMTEXT',
+ 'BDPOLYFROMWKB',
+ 'BENCHMARK',
+ 'BIN',
+ 'BIT_AND',
+ 'BIT_COUNT',
+ 'BIT_LENGTH',
+ 'BIT_OR',
+ 'BIT_XOR', // group-by-functions.html
+ 'BOUNDARY', // general-geometry-property-functions.html
+ 'BUFFER',
+ 'CAST',
+ 'CEIL',
+ 'CEILING',
+ 'CENTROID', // multipolygon-property-functions.html
+ 'CHAR', // string-functions.html
+ 'CHARACTER_LENGTH',
+ 'CHARSET', // information-functions.html
+ 'CHAR_LENGTH',
+ 'COALESCE',
+ 'COERCIBILITY', // information-functions.html
+ 'COLLATION', // information-functions.html
+ 'COMPRESS', // string-functions.html
+ 'CONCAT',
+ 'CONCAT_WS',
+ 'CONNECTION_ID',
+ 'CONTAINS',
+ 'CONV',
+ 'CONVERT',
+ 'CONVERT_TZ',
+ 'CONVEXHULL',
+ 'COS',
+ 'COT',
+ 'COUNT',
+ 'CRC32', // mathematical-functions.html
+ 'CROSSES',
+ 'CURDATE',
+ 'CURRENT_DATE',
+ 'CURRENT_TIME',
+ 'CURRENT_TIMESTAMP',
+ 'CURRENT_USER',
+ 'CURTIME',
+ 'DATABASE',
+ 'DATE', // date-and-time-functions.html
+ 'DATEDIFF', // date-and-time-functions.html
+ 'DATE_ADD',
+ 'DATE_DIFF',
+ 'DATE_FORMAT',
+ 'DATE_SUB',
+ 'DAY',
+ 'DAYNAME',
+ 'DAYOFMONTH',
+ 'DAYOFWEEK',
+ 'DAYOFYEAR',
+ 'DECODE',
+ 'DEFAULT', // miscellaneous-functions.html
+ 'DEGREES',
+ 'DES_DECRYPT',
+ 'DES_ENCRYPT',
+ 'DIFFERENCE',
+ 'DIMENSION', // general-geometry-property-functions.html
+ 'DISJOINT',
+ 'DISTANCE',
+ 'ELT',
+ 'ENCODE',
+ 'ENCRYPT',
+ 'ENDPOINT', // linestring-property-functions.html
+ 'ENVELOPE', // general-geometry-property-functions.html
+ 'EQUALS',
+ 'EXP',
+ 'EXPORT_SET',
+ 'EXTERIORRING', // polygon-property-functions.html
+ 'EXTRACT',
+ 'EXTRACTVALUE', // xml-functions.html
+ 'FIELD',
+ 'FIND_IN_SET',
+ 'FLOOR',
+ 'FORMAT',
+ 'FOUND_ROWS',
+ 'FROM_DAYS',
+ 'FROM_UNIXTIME',
+ 'GEOMCOLLFROMTEXT',
+ 'GEOMCOLLFROMWKB',
+ 'GEOMETRYCOLLECTION',
+ 'GEOMETRYCOLLECTIONFROMTEXT',
+ 'GEOMETRYCOLLECTIONFROMWKB',
+ 'GEOMETRYFROMTEXT',
+ 'GEOMETRYFROMWKB',
+ 'GEOMETRYN', // geometrycollection-property-functions.html
+ 'GEOMETRYTYPE', // general-geometry-property-functions.html
+ 'GEOMFROMTEXT',
+ 'GEOMFROMWKB',
+ 'GET_FORMAT',
+ 'GET_LOCK',
+ 'GLENGTH', // linestring-property-functions.html
+ 'GREATEST',
+ 'GROUP_CONCAT',
+ 'GROUP_UNIQUE_USERS',
+ 'HEX',
+ 'HOUR',
+ 'IF', //control-flow-functions.html
+ 'IFNULL',
+ 'INET_ATON',
+ 'INET_NTOA',
+ 'INSERT', // string-functions.html
+ 'INSTR',
+ 'INTERIORRINGN', // polygon-property-functions.html
+ 'INTERSECTION',
+ 'INTERSECTS',
+ 'INTERVAL',
+ 'ISCLOSED', // multilinestring-property-functions.html
+ 'ISEMPTY', // general-geometry-property-functions.html
+ 'ISNULL',
+ 'ISRING', // linestring-property-functions.html
+ 'ISSIMPLE', // general-geometry-property-functions.html
+ 'IS_FREE_LOCK',
+ 'IS_USED_LOCK', // miscellaneous-functions.html
+ 'LAST_DAY',
+ 'LAST_INSERT_ID',
+ 'LCASE',
+ 'LEAST',
+ 'LEFT',
+ 'LENGTH',
+ 'LINEFROMTEXT',
+ 'LINEFROMWKB',
+ 'LINESTRING',
+ 'LINESTRINGFROMTEXT',
+ 'LINESTRINGFROMWKB',
+ 'LN',
+ 'LOAD_FILE',
+ 'LOCALTIME',
+ 'LOCALTIMESTAMP',
+ 'LOCATE',
+ 'LOG',
+ 'LOG10',
+ 'LOG2',
+ 'LOWER',
+ 'LPAD',
+ 'LTRIM',
+ 'MAKEDATE',
+ 'MAKETIME',
+ 'MAKE_SET',
+ 'MASTER_POS_WAIT',
+ 'MAX',
+ 'MBRCONTAINS',
+ 'MBRDISJOINT',
+ 'MBREQUAL',
+ 'MBRINTERSECTS',
+ 'MBROVERLAPS',
+ 'MBRTOUCHES',
+ 'MBRWITHIN',
+ 'MD5',
+ 'MICROSECOND',
+ 'MID',
+ 'MIN',
+ 'MINUTE',
+ 'MLINEFROMTEXT',
+ 'MLINEFROMWKB',
+ 'MOD',
+ 'MONTH',
+ 'MONTHNAME',
+ 'MPOINTFROMTEXT',
+ 'MPOINTFROMWKB',
+ 'MPOLYFROMTEXT',
+ 'MPOLYFROMWKB',
+ 'MULTILINESTRING',
+ 'MULTILINESTRINGFROMTEXT',
+ 'MULTILINESTRINGFROMWKB',
+ 'MULTIPOINT',
+ 'MULTIPOINTFROMTEXT',
+ 'MULTIPOINTFROMWKB',
+ 'MULTIPOLYGON',
+ 'MULTIPOLYGONFROMTEXT',
+ 'MULTIPOLYGONFROMWKB',
+ 'NAME_CONST', // NAME_CONST()
+ 'NOW',
+ 'NULLIF',
+ 'NUMGEOMETRIES', // geometrycollection-property-functions.html
+ 'NUMINTERIORRINGS', // polygon-property-functions.html
+ 'NUMPOINTS', // linestring-property-functions.html
+ 'OCT',
+ 'OCTET_LENGTH',
+ 'OLD_PASSWORD',
+ 'ORD',
+ 'OVERLAPS',
+ 'PASSWORD',
+ 'PERIOD_ADD',
+ 'PERIOD_DIFF',
+ 'PI',
+ 'POINT',
+ 'POINTFROMTEXT',
+ 'POINTFROMWKB',
+ 'POINTN', // inestring-property-functions.html
+ 'POINTONSURFACE', // multipolygon-property-functions.html
+ 'POLYFROMTEXT',
+ 'POLYFROMWKB',
+ 'POLYGON',
+ 'POLYGONFROMTEXT',
+ 'POLYGONFROMWKB',
+ 'POSITION',
+ 'POW',
+ 'POWER',
+ 'QUARTER',
+ 'QUOTE',
+ 'RADIANS',
+ 'RAND',
+ 'RELATED',
+ 'RELEASE_LOCK',
+ 'REPEAT',
+ 'REPLACE', // string-functions.html
+ 'REVERSE',
+ 'RIGHT',
+ 'ROUND',
+ 'ROW_COUNT', // information-functions.html
+ 'RPAD',
+ 'RTRIM',
+ 'SCHEMA', // information-functions.html
+ 'SECOND',
+ 'SEC_TO_TIME',
+ 'SESSION_USER',
+ 'SHA',
+ 'SHA1',
+ 'SIGN',
+ 'SIN',
+ 'SLEEP', // miscellaneous-functions.html
+ 'SOUNDEX',
+ 'SPACE',
+ 'SQRT',
+ 'SRID', // general-geometry-property-functions.html
+ 'STARTPOINT', // linestring-property-functions.html
+ 'STD',
+ 'STDDEV',
+ 'STDDEV_POP', // group-by-functions.html
+ 'STDDEV_SAMP', // group-by-functions.html
+ 'STRCMP',
+ 'STR_TO_DATE',
+ 'SUBDATE',
+ 'SUBSTR',
+ 'SUBSTRING',
+ 'SUBSTRING_INDEX',
+ 'SUBTIME',
+ 'SUM',
+ 'SYMDIFFERENCE',
+ 'SYSDATE',
+ 'SYSTEM_USER',
+ 'TAN',
+ 'TIME',
+ 'TIMEDIFF',
+ 'TIMESTAMP',
+ 'TIMESTAMPADD',
+ 'TIMESTAMPDIFF',
+ 'TIME_FORMAT',
+ 'TIME_TO_SEC',
+ 'TOUCHES',
+ 'TO_DAYS',
+ 'TRIM',
+ 'TRUNCATE', // mathematical-functions.html
+ 'UCASE',
+ 'UNCOMPRESS', // string-functions.html
+ 'UNCOMPRESSED_LENGTH', // string-functions.html
+ 'UNHEX', // string-functions.html
+ 'UNIQUE_USERS',
+ 'UNIX_TIMESTAMP',
+ 'UPDATEXML', // xml-functions.html
+ 'UPPER',
+ 'USER',
+ 'UTC_DATE',
+ 'UTC_TIME',
+ 'UTC_TIMESTAMP',
+ 'UUID', // miscellaneous-functions.html
+ 'VARIANCE', // group-by-functions.html
+ 'VAR_POP', // group-by-functions.html
+ 'VAR_SAMP', // group-by-functions.html
+ 'VERSION',
+ 'WEEK',
+ 'WEEKDAY',
+ 'WEEKOFYEAR',
+ 'WITHIN',
+ 'X', // point-property-functions.html
+ 'Y', // point-property-functions.html
+ 'YEAR',
+ 'YEARWEEK'
+);
+
+/**
+ * @global array MySQL attributes
+ */
+$PMA_SQPdata_column_attrib = array (
+ 'ARCHIVE', // Engine
+ 'ASCII',
+ 'AUTO_INCREMENT',
+ 'BDB', // Engine
+ 'BERKELEYDB', // Engine alias BDB
+ 'BINARY',
+ 'BLACKHOLE', // Engine
+ 'CSV', // Engine
+ 'DEFAULT',
+ 'EXAMPLE', // Engine
+ 'FEDERATED', // Engine
+ 'HEAP', // Engine
+ 'INNOBASE', // Engine alias InnoDB
+ 'INNODB', // Engine InnoDB
+ 'ISAM', // Engine
+ 'MARIA', // Engine
+ 'MEMORY', // Engine alias HEAP, but preferred
+ 'MERGE', // Engine
+ 'MRG_ISAM', // Engine
+ 'MRG_MYISAM', // Engine alias MERGE
+ 'MYISAM', // Engine MyISAM
+ 'NATIONAL',
+ 'NDB', // Engine alias NDBCLUSTER
+ 'NDBCLUSTER', // Engine
+ 'PRECISION',
+ 'UNDEFINED',
+ 'UNICODE',
+ 'UNSIGNED',
+ 'VARYING',
+ 'ZEROFILL'
+);
+
+/**
+ * words that are reserved by MySQL and may not be used as identifiers without
+ * quotes
+ *
+ * @see http://dev.mysql.com/doc/refman/5.5/en/reserved-words.html
+ *
+ * @global array MySQL reserved words
+ */
+$PMA_SQPdata_reserved_word = array (
+ 'ACCESSIBLE', // 5.1
+ 'ACTION',
+ 'ADD',
+ 'AFTER',
+ 'AGAINST',
+ 'AGGREGATE',
+ 'ALGORITHM',
+ 'ALL',
+ 'ALTER',
+ 'ANALYSE',
+ 'ANALYZE',
+ 'AND',
+ 'AS',
+ 'ASC',
+ 'AUTOCOMMIT',
+ 'AUTO_INCREMENT',
+ 'AVG_ROW_LENGTH',
+ 'BACKUP',
+ 'BEGIN',
+ 'BETWEEN',
+ 'BINLOG',
+ 'BOTH',
+ 'BY',
+ 'CALL',
+ 'CASCADE',
+ 'CASE',
+ 'CHANGE',
+ 'CHANGED',
+ 'CHARSET',
+ 'CHECK',
+ 'CHECKSUM',
+ 'COLLATE',
+ 'COLLATION',
+ 'COLUMN',
+ 'COLUMNS',
+ 'COMMENT',
+ 'COMMIT',
+ 'COMMITTED',
+ 'COMPRESSED',
+ 'CONCURRENT',
+ 'CONSTRAINT',
+ 'CONTAINS',
+ 'CONVERT',
+ 'CREATE',
+ 'CROSS',
+ 'CURRENT_TIMESTAMP',
+ 'DATABASE',
+ 'DATABASES',
+ 'DAY',
+ 'DAY_HOUR',
+ 'DAY_MINUTE',
+ 'DAY_SECOND',
+ 'DECLARE',
+ 'DEFINER',
+ 'DELAYED',
+ 'DELAY_KEY_WRITE',
+ 'DELETE',
+ 'DESC',
+ 'DESCRIBE',
+ 'DETERMINISTIC',
+ 'DISTINCT',
+ 'DISTINCTROW',
+ 'DIV',
+ 'DO',
+ 'DROP',
+ 'DUMPFILE',
+ 'DUPLICATE',
+ 'DYNAMIC',
+ 'ELSE',
+ 'ENCLOSED',
+ 'END',
+ 'ENGINE',
+ 'ENGINES',
+ 'ESCAPE',
+ 'ESCAPED',
+ 'EVENTS',
+ 'EXECUTE',
+ 'EXISTS',
+ 'EXPLAIN',
+ 'EXTENDED',
+ 'FALSE',
+ 'FAST',
+ 'FIELDS',
+ 'FILE',
+ 'FIRST',
+ 'FIXED',
+ 'FLUSH',
+ 'FOR',
+ 'FORCE',
+ 'FOREIGN',
+ 'FROM',
+ 'FULL',
+ 'FULLTEXT',
+ 'FUNCTION',
+ 'GEMINI',
+ 'GEMINI_SPIN_RETRIES',
+ 'GENERAL',
+ 'GLOBAL',
+ 'GRANT',
+ 'GRANTS',
+ 'GROUP',
+ 'HAVING',
+ 'HEAP',
+ 'HIGH_PRIORITY',
+ 'HOSTS',
+ 'HOUR',
+ 'HOUR_MINUTE',
+ 'HOUR_SECOND',
+ 'IDENTIFIED',
+ 'IF',
+ 'IGNORE',
+ 'IGNORE_SERVER_IDS',
+ 'IN',
+ 'INDEX',
+ 'INDEXES',
+ 'INFILE',
+ 'INNER',
+ 'INSERT',
+ 'INSERT_ID',
+ 'INSERT_METHOD',
+ 'INTERVAL',
+ 'INTO',
+ 'INVOKER',
+ 'IS',
+ 'ISOLATION',
+ 'JOIN',
+ 'KEY',
+ 'KEYS',
+ 'KILL',
+ 'LAST_INSERT_ID',
+ 'LEADING',
+ 'LEFT',
+ 'LIKE',
+ 'LIMIT',
+ 'LINEAR', // 5.1
+ 'LINES',
+ 'LOAD',
+ 'LOCAL',
+ 'LOCK',
+ 'LOCKS',
+ 'LOGS',
+ 'LOW_PRIORITY',
+ 'MARIA', // 5.1 ?
+ 'MASTER_CONNECT_RETRY',
+ 'MASTER_HEARTBEAT_PERIOD',
+ 'MASTER_HOST',
+ 'MASTER_LOG_FILE',
+ 'MASTER_LOG_POS',
+ 'MASTER_PASSWORD',
+ 'MASTER_PORT',
+ 'MASTER_USER',
+ 'MATCH',
+ 'MAXVALUE',
+ 'MAX_CONNECTIONS_PER_HOUR',
+ 'MAX_QUERIES_PER_HOUR',
+ 'MAX_ROWS',
+ 'MAX_UPDATES_PER_HOUR',
+ 'MAX_USER_CONNECTIONS',
+ 'MEDIUM',
+ 'MERGE',
+ 'MINUTE',
+ 'MINUTE_SECOND',
+ 'MIN_ROWS',
+ 'MODE',
+ 'MODIFY',
+ 'MONTH',
+ 'MRG_MYISAM',
+ 'MYISAM',
+ 'NAMES',
+ 'NATURAL',
+ // 'NO' is not allowed in SQL-99 but is allowed in MySQL
+ //'NO',
+ 'NOT',
+ 'NULL',
+ 'OFFSET',
+ 'ON',
+ 'OPEN',
+ 'OPTIMIZE',
+ 'OPTION',
+ 'OPTIONALLY',
+ 'OR',
+ 'ORDER',
+ 'OUTER',
+ 'OUTFILE',
+ 'PACK_KEYS',
+ 'PAGE', // 5.1-maria ?
+ 'PAGE_CHECKSUM', // 5.1
+ 'PARTIAL',
+ 'PARTITION', // 5.1
+ 'PARTITIONS', // 5.1
+ 'PASSWORD',
+ 'PRIMARY',
+ 'PRIVILEGES',
+ 'PROCEDURE',
+ 'PROCESS',
+ 'PROCESSLIST',
+ 'PURGE',
+ 'QUICK',
+ 'RAID0',
+ 'RAID_CHUNKS',
+ 'RAID_CHUNKSIZE',
+ 'RAID_TYPE',
+ 'RANGE', // 5.1
+ 'READ',
+ 'READ_ONLY', // 5.1
+ 'READ_WRITE', // 5.1
+ 'REFERENCES',
+ 'REGEXP',
+ 'RELOAD',
+ 'RENAME',
+ 'REPAIR',
+ 'REPEATABLE',
+ 'REPLACE',
+ 'REPLICATION',
+ 'RESET',
+ 'RESIGNAL',
+ 'RESTORE',
+ 'RESTRICT',
+ 'RETURN',
+ 'RETURNS',
+ 'REVOKE',
+ 'RIGHT',
+ 'RLIKE',
+ 'ROLLBACK',
+ 'ROW',
+ 'ROWS',
+ 'ROW_FORMAT',
+ 'SECOND',
+ 'SECURITY',
+ 'SELECT',
+ 'SEPARATOR',
+ 'SERIALIZABLE',
+ 'SESSION',
+ 'SHARE',
+ 'SHOW',
+ 'SHUTDOWN',
+ 'SIGNAL',
+ 'SLAVE',
+ 'SLOW',
+ 'SONAME',
+ 'SOUNDS', // string-functions.html
+ 'SQL',
+ 'SQL_AUTO_IS_NULL',
+ 'SQL_BIG_RESULT',
+ 'SQL_BIG_SELECTS',
+ 'SQL_BIG_TABLES',
+ 'SQL_BUFFER_RESULT',
+ 'SQL_CACHE',
+ 'SQL_CALC_FOUND_ROWS',
+ 'SQL_LOG_BIN',
+ 'SQL_LOG_OFF',
+ 'SQL_LOG_UPDATE',
+ 'SQL_LOW_PRIORITY_UPDATES',
+ 'SQL_MAX_JOIN_SIZE',
+ 'SQL_NO_CACHE',
+ 'SQL_QUOTE_SHOW_CREATE',
+ 'SQL_SAFE_UPDATES',
+ 'SQL_SELECT_LIMIT',
+ 'SQL_SLAVE_SKIP_COUNTER',
+ 'SQL_SMALL_RESULT',
+ 'SQL_WARNINGS',
+ 'START',
+ 'STARTING',
+ 'STATUS',
+ 'STOP',
+ 'STORAGE',
+ 'STRAIGHT_JOIN',
+ 'STRING',
+ 'STRIPED',
+ 'SUPER',
+ 'TABLE',
+ 'TABLES',
+ 'TEMPORARY',
+ 'TERMINATED',
+ 'THEN',
+ 'TO',
+ 'TRAILING',
+ 'TRANSACTIONAL', // 5.1 ?
+ 'TRUE',
+ 'TRUNCATE',
+ 'TYPE',
+ 'TYPES',
+ 'UNCOMMITTED',
+ 'UNION',
+ 'UNIQUE',
+ 'UNLOCK',
+ 'UPDATE',
+ 'USAGE',
+ 'USE',
+ 'USING',
+ 'VALUES',
+ 'VARIABLES',
+ 'VIEW',
+ 'WHEN',
+ 'WHERE',
+ 'WITH',
+ 'WORK',
+ 'WRITE',
+ 'XOR',
+ 'YEAR_MONTH'
+);
+
+/**
+ * words forbidden to be used as column or table name without quotes
+ * as seen in http://dev.mysql.com/doc/refman/5.6/en/reserved-words.html
+ *
+ * @global array MySQL forbidden words
+ */
+$PMA_SQPdata_forbidden_word = array (
+ 'ACCESSIBLE',
+ 'ADD',
+ 'ALL',
+ 'ALTER',
+ 'ANALYZE',
+ 'AND',
+ 'AS',
+ 'ASC',
+ 'ASENSITIVE',
+ 'BEFORE',
+ 'BETWEEN',
+ 'BIGINT',
+ 'BINARY',
+ 'BLOB',
+ 'BOTH',
+ 'BY',
+ 'CALL',
+ 'CASCADE',
+ 'CASE',
+ 'CHANGE',
+ 'CHAR',
+ 'CHARACTER',
+ 'CHECK',
+ 'COLLATE',
+ 'COLUMN',
+ 'CONDITION',
+ 'CONSTRAINT',
+ 'CONTINUE',
+ 'CONVERT',
+ 'CREATE',
+ 'CROSS',
+ 'CURRENT_DATE',
+ 'CURRENT_TIME',
+ 'CURRENT_TIMESTAMP',
+ 'CURRENT_USER',
+ 'CURSOR',
+ 'DATABASE',
+ 'DATABASES',
+ 'DAY_HOUR',
+ 'DAY_MICROSECOND',
+ 'DAY_MINUTE',
+ 'DAY_SECOND',
+ 'DEC',
+ 'DECIMAL',
+ 'DECLARE',
+ 'DEFAULT',
+ 'DELAYED',
+ 'DELETE',
+ 'DESC',
+ 'DESCRIBE',
+ 'DETERMINISTIC',
+ 'DISTINCT',
+ 'DISTINCTROW',
+ 'DIV',
+ 'DOUBLE',
+ 'DROP',
+ 'DUAL',
+ 'EACH',
+ 'ELSE',
+ 'ELSEIF',
+ 'ENCLOSED',
+ 'ESCAPED',
+ 'EXISTS',
+ 'EXIT',
+ 'EXPLAIN',
+ 'FALSE',
+ 'FETCH',
+ 'FLOAT',
+ 'FLOAT4',
+ 'FLOAT8',
+ 'FOR',
+ 'FORCE',
+ 'FOREIGN',
+ 'FROM',
+ 'FULLTEXT',
+ 'GENERAL',
+ 'GET',
+ 'GRANT',
+ 'GROUP',
+ 'HAVING',
+ 'HIGH_PRIORITY',
+ 'HOUR_MICROSECOND',
+ 'HOUR_MINUTE',
+ 'HOUR_SECOND',
+ 'IF',
+ 'IGNORE',
+ 'IGNORE_SERVER_IDS',
+ 'IN',
+ 'INDEX',
+ 'INFILE',
+ 'INNER',
+ 'INOUT',
+ 'INSENSITIVE',
+ 'INSERT',
+ 'INT',
+ 'INT1',
+ 'INT2',
+ 'INT3',
+ 'INT4',
+ 'INT8',
+ 'INTEGER',
+ 'INTERVAL',
+ 'INTO',
+ 'IO_AFTER_GTIDS',
+ 'IO_BEFORE_GTIDS',
+ 'IS',
+ 'ITERATE',
+ 'JOIN',
+ 'KEY',
+ 'KEYS',
+ 'KILL',
+ 'LEADING',
+ 'LEAVE',
+ 'LEFT',
+ 'LIKE',
+ 'LIMIT',
+ 'LINEAR',
+ 'LINES',
+ 'LOAD',
+ 'LOCALTIME',
+ 'LOCALTIMESTAMP',
+ 'LOCK',
+ 'LONG',
+ 'LONGBLOB',
+ 'LONGTEXT',
+ 'LOOP',
+ 'LOW_PRIORITY',
+ 'MASTER_BIND',
+ 'MASTER_HEARTBEAT_PERIOD',
+ 'MASTER_SSL_VERIFY_SERVER_CERT',
+ 'MATCH',
+ 'MAXVALUE',
+ 'MEDIUMBLOB',
+ 'MEDIUMINT',
+ 'MEDIUMTEXT',
+ 'MIDDLEINT',
+ 'MINUTE_MICROSECOND',
+ 'MINUTE_SECOND',
+ 'MOD',
+ 'MODIFIES',
+ 'NATURAL',
+ 'NOT',
+ 'NO_WRITE_TO_BINLOG',
+ 'NULL',
+ 'NUMERIC',
+ 'ON',
+ 'ONE_SHOT',
+ 'OPTIMIZE',
+ 'OPTION',
+ 'OPTIONALLY',
+ 'OR',
+ 'ORDER',
+ 'OUT',
+ 'OUTER',
+ 'OUTFILE',
+ 'PARTITION',
+ 'PRECISION',
+ 'PRIMARY',
+ 'PROCEDURE',
+ 'PURGE',
+ 'RANGE',
+ 'READ',
+ 'READS',
+ 'READ_WRITE',
+ 'REAL',
+ 'REFERENCES',
+ 'REGEXP',
+ 'RELEASE',
+ 'RENAME',
+ 'REPEAT',
+ 'REPLACE',
+ 'REQUIRE',
+ 'RESIGNAL',
+ 'RESTRICT',
+ 'RETURN',
+ 'REVOKE',
+ 'RIGHT',
+ 'RLIKE',
+ 'SCHEMA',
+ 'SCHEMAS',
+ 'SECOND_MICROSECOND',
+ 'SELECT',
+ 'SENSITIVE',
+ 'SEPARATOR',
+ 'SET',
+ 'SHOW',
+ 'SIGNAL',
+ 'SLOW',
+ 'SMALLINT',
+ 'SPATIAL',
+ 'SPECIFIC',
+ 'SQL',
+ 'SQLEXCEPTION',
+ 'SQLSTATE',
+ 'SQLWARNING',
+ 'SQL_AFTER_GTIDS',
+ 'SQL_BEFORE_GTIDS',
+ 'SQL_BIG_RESULT',
+ 'SQL_CALC_FOUND_ROWS',
+ 'SQL_SMALL_RESULT',
+ 'SSL',
+ 'STARTING',
+ 'STRAIGHT_JOIN',
+ 'TABLE',
+ 'TERMINATED',
+ 'THEN',
+ 'TINYBLOB',
+ 'TINYINT',
+ 'TINYTEXT',
+ 'TO',
+ 'TRAILING',
+ 'TRIGGER',
+ 'TRUE',
+ 'UNDO',
+ 'UNION',
+ 'UNIQUE',
+ 'UNLOCK',
+ 'UNSIGNED',
+ 'UPDATE',
+ 'USAGE',
+ 'USE',
+ 'USING',
+ 'UTC_DATE',
+ 'UTC_TIME',
+ 'UTC_TIMESTAMP',
+ 'VALUES',
+ 'VARBINARY',
+ 'VARCHAR',
+ 'VARCHARACTER',
+ 'VARYING',
+ 'WHEN',
+ 'WHERE',
+ 'WHILE',
+ 'WITH',
+ 'WRITE',
+ 'XOR',
+ 'YEAR_MONTH',
+ 'ZEROFILL'
+);
+
+/**
+ * the MySQL column/data types
+ *
+ * @see http://dev.mysql.com/doc/refman/5.1/en/data-types.html
+ * @see http://dev.mysql.com/doc/refman/5.1/en/mysql-spatial-datatypes.html
+ *
+ * @global array MySQL column types
+ */
+$PMA_SQPdata_column_type = array (
+ 'BIGINT',
+ 'BINARY',
+ 'BIT',
+ 'BLOB',
+ 'BOOL',
+ 'BOOLEAN', // numeric-type-overview.html
+ 'CHAR',
+ 'CHARACTER',
+ 'DATE',
+ 'DATETIME',
+ 'DEC',
+ 'DECIMAL',
+ 'DOUBLE',
+ 'ENUM',
+ 'FLOAT',
+ 'FLOAT4',
+ 'FLOAT8',
+ 'GEOMETRY', // spatial
+ 'GEOMETRYCOLLECTION', // spatial
+ 'INT',
+ 'INT1',
+ 'INT2',
+ 'INT3',
+ 'INT4',
+ 'INT8',
+ 'INTEGER',
+ 'LINESTRING', // spatial
+ 'LONG',
+ 'LONGBLOB',
+ 'LONGTEXT',
+ 'MEDIUMBLOB',
+ 'MEDIUMINT',
+ 'MEDIUMTEXT',
+ 'MIDDLEINT',
+ 'MULTILINESTRING', // spatial
+ 'MULTIPOINT', // spatial
+ 'MULTIPOLYGON', // spatial
+ 'NCHAR',
+ 'NUMERIC',
+ 'POINT', // spatial
+ 'POLYGON', // spatial
+ 'REAL',
+ 'SERIAL', // alias
+ 'SET',
+ 'SMALLINT',
+ 'TEXT',
+ 'TIME',
+ 'TIMESTAMP',
+ 'TINYBLOB',
+ 'TINYINT',
+ 'TINYTEXT',
+ 'VARBINARY',
+ 'VARCHAR',
+ 'YEAR'
+);
+
+?>
diff --git a/libraries/sqlparser.lib.php b/libraries/sqlparser.lib.php
new file mode 100644
index 0000000000..00bdfc14f6
--- /dev/null
+++ b/libraries/sqlparser.lib.php
@@ -0,0 +1,2807 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/** SQL Parser Functions for phpMyAdmin
+ *
+ * These functions define an SQL parser system, capable of understanding and
+ * extracting data from a MySQL type SQL query.
+ *
+ * The basic procedure for using the new SQL parser:
+ * On any page that needs to extract data from a query or to pretty-print a
+ * query, you need code like this up at the top:
+ *
+ * ($sql contains the query)
+ * $parsed_sql = PMA_SQP_parse($sql);
+ *
+ * If you want to extract data from it then, you just need to run
+ * $sql_info = PMA_SQP_analyze($parsed_sql);
+ *
+ * See comments in PMA_SQP_analyze for the returned info
+ * from the analyzer.
+ *
+ * If you want a pretty-printed version of the query, do:
+ * $string = PMA_SQP_format($parsed_sql);
+ * (note that that you need to have syntax.css.php included somehow in your
+ * page for it to work, I recommend '<link rel="stylesheet" type="text/css"
+ * href="syntax.css.php" />' at the moment.)
+ *
+ * @package PhpMyAdmin
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ * Include the string handling class as we use it heavily
+ */
+require_once './libraries/string.inc.php';
+
+/**
+ * Include data for the SQL Parser
+ */
+require_once './libraries/sqlparser.data.php';
+
+/**
+ * Charset information
+ */
+if (!defined('TESTSUITE') && ! PMA_DRIZZLE) {
+ include_once './libraries/mysql_charsets.inc.php';
+}
+if (! isset($mysql_charsets)) {
+ $mysql_charsets = array();
+ $mysql_collations_flat = array();
+}
+
+/**
+ * Stores parsed elemented of query to array.
+ *
+ * Currently we don't need the $pos (token position in query)
+ * for other purposes than LIMIT clause verification,
+ * so many calls to this function do not include the 4th parameter
+ *
+ * @param array &$arr Array to store element
+ * @param string $type Type of element
+ * @param string $data Data (text) of element
+ * @param int &$arrsize Size of array
+ * @param int $pos Position of an element
+ *
+ * @return void
+ */
+function PMA_SQP_arrayAdd(&$arr, $type, $data, &$arrsize, $pos = 0)
+{
+ $arr[] = array('type' => $type, 'data' => $data, 'pos' => $pos);
+ $arrsize++;
+} // end of the "PMA_SQP_arrayAdd()" function
+
+/**
+ * Reset the error variable for the SQL parser
+ *
+ * @access public
+ *
+ * @return void
+ */
+function PMA_SQP_resetError()
+{
+ global $SQP_errorString;
+ $SQP_errorString = '';
+ unset($SQP_errorString);
+}
+
+/**
+ * Get the contents of the error variable for the SQL parser
+ *
+ * @return string Error string from SQL parser
+ *
+ * @access public
+ */
+function PMA_SQP_getErrorString()
+{
+ global $SQP_errorString;
+ return isset($SQP_errorString) ? $SQP_errorString : '';
+}
+
+/**
+ * Check if the SQL parser hit an error
+ *
+ * @return boolean error state
+ *
+ * @access public
+ */
+function PMA_SQP_isError()
+{
+ global $SQP_errorString;
+ return isset($SQP_errorString) && !empty($SQP_errorString);
+}
+
+/**
+ * Set an error message for the system
+ *
+ * @param string $message The error message
+ * @param string $sql The failing SQL query
+ *
+ * @return void
+ *
+ * @access private
+ * @scope SQL Parser internal
+ */
+function PMA_SQP_throwError($message, $sql)
+{
+ global $SQP_errorString;
+ $SQP_errorString = '<p>'
+ . __(
+ 'There seems to be an error in your SQL query. The MySQL server '
+ . 'error output below, if there is any, may also help you in '
+ . 'diagnosing the problem.'
+ )
+ . '</p>' . "\n"
+ . '<pre>' . "\n"
+ . 'ERROR: ' . $message . "\n"
+ . 'SQL: ' . htmlspecialchars($sql) . "\n"
+ . '</pre>' . "\n";
+
+} // end of the "PMA_SQP_throwError()" function
+
+
+/**
+ * Do display the bug report
+ *
+ * @param string $message The error message
+ * @param string $sql The failing SQL query
+ *
+ * @return void
+ *
+ * @access public
+ */
+function PMA_SQP_bug($message, $sql)
+{
+ global $SQP_errorString;
+ $debugstr = 'ERROR: ' . $message . "\n";
+ $debugstr .= 'MySQL: ' . PMA_MYSQL_STR_VERSION . "\n";
+ $debugstr .= 'USR OS, AGENT, VER: ' . PMA_USR_OS . ' ';
+ $debugstr .= PMA_USR_BROWSER_AGENT . ' ' . PMA_USR_BROWSER_VER . "\n";
+ $debugstr .= 'PMA: ' . PMA_VERSION . "\n";
+ $debugstr .= 'PHP VER,OS: ' . PMA_PHP_STR_VERSION . ' ' . PHP_OS . "\n";
+ $debugstr .= 'LANG: ' . $GLOBALS['lang'] . "\n";
+ $debugstr .= 'SQL: ' . htmlspecialchars($sql);
+
+ $encodedstr = $debugstr;
+ if (@function_exists('gzcompress')) {
+ $encodedstr = gzcompress($debugstr, 9);
+ }
+ $encodedstr = preg_replace(
+ "/(\015\012)|(\015)|(\012)/",
+ '<br />' . "\n",
+ chunk_split(base64_encode($encodedstr))
+ );
+
+
+ $SQP_errorString .= __(
+ 'There is a chance that you may have found a bug in the SQL parser. '
+ . 'Please examine your query closely, and check that the quotes are '
+ . 'correct and not mis-matched. Other possible failure causes may be '
+ . 'that you are uploading a file with binary outside of a quoted text '
+ . 'area. You can also try your query on the MySQL command line '
+ . 'interface. The MySQL server error output below, if there is any, '
+ . 'may also help you in diagnosing the problem. If you still have '
+ . 'problems or if the parser fails where the command line interface '
+ . 'succeeds, please reduce your SQL query input to the single query '
+ . 'that causes problems, and submit a bug report with the data chunk '
+ . 'in the CUT section below:'
+ );
+ $SQP_errorString .= '<br />' . "\n"
+ . '----' . __('BEGIN CUT') . '----' . '<br />' . "\n"
+ . $encodedstr . "\n"
+ . '----' . __('END CUT') . '----' . '<br />' . "\n";
+
+ $SQP_errorString .= '----' . __('BEGIN RAW') . '----<br />' . "\n"
+ . '<pre>' . "\n"
+ . $debugstr
+ . '</pre>' . "\n"
+ . '----' . __('END RAW') . '----<br />' . "\n";
+
+} // end of the "PMA_SQP_bug()" function
+
+
+/**
+ * Parses the SQL queries
+ *
+ * @param string $sql The SQL query list
+ *
+ * @return mixed Most of times, nothing...
+ *
+ * @global array The current PMA configuration
+ * @global array MySQL column attributes
+ * @global array MySQL reserved words
+ * @global array MySQL column types
+ * @global array MySQL function names
+ * @global array List of available character sets
+ * @global array List of available collations
+ *
+ * @access public
+ */
+function PMA_SQP_parse($sql)
+{
+ static $PMA_SQPdata_column_attrib, $PMA_SQPdata_reserved_word;
+ static $PMA_SQPdata_column_type;
+ static $PMA_SQPdata_function_name, $PMA_SQPdata_forbidden_word;
+ global $mysql_charsets, $mysql_collations_flat;
+
+ // Convert all line feeds to Unix style
+ $sql = str_replace("\r\n", "\n", $sql);
+ $sql = str_replace("\r", "\n", $sql);
+
+ $len = $GLOBALS['PMA_String']->strlen($sql);
+ if ($len == 0) {
+ return array();
+ }
+
+ // Create local hashtables
+ if (!isset($PMA_SQPdata_column_attrib)) {
+ $PMA_SQPdata_column_attrib = array_flip(
+ $GLOBALS['PMA_SQPdata_column_attrib']
+ );
+ $PMA_SQPdata_function_name = array_flip(
+ $GLOBALS['PMA_SQPdata_function_name']
+ );
+ $PMA_SQPdata_reserved_word = array_flip(
+ $GLOBALS['PMA_SQPdata_reserved_word']
+ );
+ $PMA_SQPdata_forbidden_word = array_flip(
+ $GLOBALS['PMA_SQPdata_forbidden_word']
+ );
+ $PMA_SQPdata_column_type = array_flip(
+ $GLOBALS['PMA_SQPdata_column_type']
+ );
+ }
+
+ $sql_array = array();
+ $sql_array['raw'] = $sql;
+ $count1 = 0;
+ $count2 = 0;
+ $punct_queryend = ';';
+ $punct_qualifier = '.';
+ $punct_listsep = ',';
+ $bracket_list = '()[]{}';
+ $allpunct_list = '-,;:!?/.^~\*&%+<=>|';
+ $allpunct_list_pair = array(
+ '!=' => 1,
+ '&&' => 1,
+ ':=' => 1,
+ '<<' => 1,
+ '<=' => 1,
+ '<=>' => 1,
+ '<>' => 1,
+ '>=' => 1,
+ '>>' => 1,
+ '||' => 1,
+ '==' => 1
+ );
+ $quote_list = '\'"`';
+ $arraysize = 0;
+
+ $previous_was_space = false;
+ $this_was_space = false;
+ $previous_was_bracket = false;
+ $this_was_bracket = false;
+ $previous_was_punct = false;
+ $this_was_punct = false;
+ $previous_was_listsep = false;
+ $this_was_listsep = false;
+ $previous_was_quote = false;
+ $this_was_quote = false;
+
+ while ($count2 < $len) {
+ $c = $GLOBALS['PMA_String']->substr($sql, $count2, 1);
+ $count1 = $count2;
+
+ $previous_was_space = $this_was_space;
+ $this_was_space = false;
+ $previous_was_bracket = $this_was_bracket;
+ $this_was_bracket = false;
+ $previous_was_punct = $this_was_punct;
+ $this_was_punct = false;
+ $previous_was_listsep = $this_was_listsep;
+ $this_was_listsep = false;
+ $previous_was_quote = $this_was_quote;
+ $this_was_quote = false;
+
+ if (($c == "\n")) {
+ $this_was_space = true;
+ $count2++;
+ PMA_SQP_arrayAdd($sql_array, 'white_newline', '', $arraysize);
+ continue;
+ }
+
+ // Checks for white space
+ if ($GLOBALS['PMA_String']->isSpace($c)) {
+ $this_was_space = true;
+ $count2++;
+ continue;
+ }
+
+ // Checks for comment lines.
+ // MySQL style #
+ // C style /* */
+ // ANSI style --
+ $next_c = $GLOBALS['PMA_String']->substr($sql, $count2 + 1, 1);
+ if (($c == '#')
+ || (($count2 + 1 < $len) && ($c == '/') && ($next_c == '*'))
+ || (($count2 + 2 == $len) && ($c == '-') && ($next_c == '-'))
+ || (($count2 + 2 < $len) && ($c == '-') && ($next_c == '-') && (($GLOBALS['PMA_String']->substr($sql, $count2 + 2, 1) <= ' ')))
+ ) {
+ $count2++;
+ $pos = 0;
+ $type = 'bad';
+ switch ($c) {
+ case '#':
+ $type = 'mysql';
+ case '-':
+ $type = 'ansi';
+ $pos = $GLOBALS['PMA_String']->strpos($sql, "\n", $count2);
+ break;
+ case '/':
+ $type = 'c';
+ $pos = $GLOBALS['PMA_String']->strpos($sql, '*/', $count2);
+ $pos += 2;
+ break;
+ default:
+ break;
+ } // end switch
+ $count2 = ($pos < $count2) ? $len : $pos;
+ $str = $GLOBALS['PMA_String']->substr($sql, $count1, $count2 - $count1);
+ PMA_SQP_arrayAdd($sql_array, 'comment_' . $type, $str, $arraysize);
+ continue;
+ } // end if
+
+ // Checks for something inside quotation marks
+ if ($GLOBALS['PMA_String']->strpos($quote_list, $c) !== false) {
+ $startquotepos = $count2;
+ $quotetype = $c;
+ $count2++;
+ $pos = $count2;
+ $oldpos = 0;
+ do {
+ $oldpos = $pos;
+ $pos = $GLOBALS['PMA_String']->strpos(' ' . $sql, $quotetype, $oldpos + 1) - 1;
+ // ($pos === false)
+ if ($pos < 0) {
+ if ($c == '`') {
+ /*
+ * Behave same as MySQL and accept end of query as end
+ * of backtick.
+ * I know this is sick, but MySQL behaves like this:
+ *
+ * SELECT * FROM `table
+ *
+ * is treated like
+ *
+ * SELECT * FROM `table`
+ */
+ $pos_quote_separator = $GLOBALS['PMA_String']->strpos(
+ ' ' . $sql, $GLOBALS['sql_delimiter'], $oldpos + 1
+ ) - 1;
+ if ($pos_quote_separator < 0) {
+ $len += 1;
+ $sql .= '`';
+ $sql_array['raw'] .= '`';
+ $pos = $len;
+ } else {
+ $len += 1;
+ $sql = $GLOBALS['PMA_String']->substr($sql, 0, $pos_quote_separator)
+ . '`' . $GLOBALS['PMA_String']->substr($sql, $pos_quote_separator);
+ $sql_array['raw'] = $sql;
+ $pos = $pos_quote_separator;
+ }
+ if (class_exists('PMA_Message')
+ && $GLOBALS['is_ajax_request'] != true
+ ) {
+ PMA_Message::notice(
+ __('Automatically appended backtick to the end of query!')
+ )->display();
+ }
+ } else {
+ $debugstr = __('Unclosed quote')
+ . ' @ ' . $startquotepos. "\n"
+ . 'STR: ' . htmlspecialchars($quotetype);
+ PMA_SQP_throwError($debugstr, $sql);
+ return $sql_array;
+ }
+ }
+
+ // If the quote is the first character, it can't be
+ // escaped, so don't do the rest of the code
+ if ($pos == 0) {
+ break;
+ }
+
+ // Checks for MySQL escaping using a \
+ // And checks for ANSI escaping using the $quotetype character
+ if (($pos < $len)
+ && $GLOBALS['PMA_String']->charIsEscaped($sql, $pos)
+ && $c != '`'
+ ) {
+ $pos ++;
+ continue;
+ } elseif (($pos + 1 < $len)
+ && ($GLOBALS['PMA_String']->substr($sql, $pos, 1) == $quotetype)
+ && ($GLOBALS['PMA_String']->substr($sql, $pos + 1, 1) == $quotetype)
+ ) {
+ $pos = $pos + 2;
+ continue;
+ } else {
+ break;
+ }
+ } while ($len > $pos); // end do
+
+ $count2 = $pos;
+ $count2++;
+ $type = 'quote_';
+ switch ($quotetype) {
+ case '\'':
+ $type .= 'single';
+ $this_was_quote = true;
+ break;
+ case '"':
+ $type .= 'double';
+ $this_was_quote = true;
+ break;
+ case '`':
+ $type .= 'backtick';
+ $this_was_quote = true;
+ break;
+ default:
+ break;
+ } // end switch
+ $data = $GLOBALS['PMA_String']->substr($sql, $count1, $count2 - $count1);
+ PMA_SQP_arrayAdd($sql_array, $type, $data, $arraysize);
+ continue;
+ }
+
+ // Checks for brackets
+ if ($GLOBALS['PMA_String']->strpos($bracket_list, $c) !== false) {
+ // All bracket tokens are only one item long
+ $this_was_bracket = true;
+ $count2++;
+ $type_type = '';
+ if ($GLOBALS['PMA_String']->strpos('([{', $c) !== false) {
+ $type_type = 'open';
+ } else {
+ $type_type = 'close';
+ }
+
+ $type_style = '';
+ if ($GLOBALS['PMA_String']->strpos('()', $c) !== false) {
+ $type_style = 'round';
+ } elseif ($GLOBALS['PMA_String']->strpos('[]', $c) !== false) {
+ $type_style = 'square';
+ } else {
+ $type_style = 'curly';
+ }
+
+ $type = 'punct_bracket_' . $type_type . '_' . $type_style;
+ PMA_SQP_arrayAdd($sql_array, $type, $c, $arraysize);
+ continue;
+ }
+
+ /* DEBUG
+ echo '<pre>1';
+ var_dump($GLOBALS['PMA_String']->isSqlIdentifier($c, false));
+ var_dump($c == '@');
+ var_dump($c == '.');
+ var_dump($GLOBALS['PMA_String']->isDigit($GLOBALS['PMA_String']->substr($sql, $count2 + 1, 1)));
+ var_dump($previous_was_space);
+ var_dump($previous_was_bracket);
+ var_dump($previous_was_listsep);
+ echo '</pre>';
+ */
+
+ // Checks for identifier (alpha or numeric)
+ if ($GLOBALS['PMA_String']->isSqlIdentifier($c, false)
+ || $c == '@'
+ || ($c == '.'
+ && $GLOBALS['PMA_String']->isDigit($GLOBALS['PMA_String']->substr($sql, $count2 + 1, 1))
+ && ($previous_was_space || $previous_was_bracket || $previous_was_listsep))
+ ) {
+ /* DEBUG
+ echo $GLOBALS['PMA_String']->substr($sql, $count2);
+ echo '<hr />';
+ */
+
+ $count2++;
+
+ /**
+ * @todo a @ can also be present in expressions like
+ * FROM 'user'@'%' or TO 'user'@'%'
+ * in this case, the @ is wrongly marked as alpha_variable
+ */
+ $is_identifier = $previous_was_punct;
+ $is_sql_variable = $c == '@' && ! $previous_was_quote;
+ $is_user = $c == '@' && $previous_was_quote;
+ $is_digit = (
+ !$is_identifier
+ && !$is_sql_variable
+ && $GLOBALS['PMA_String']->isDigit($c)
+ );
+ $is_hex_digit = (
+ $is_digit
+ && $c == '0'
+ && $count2 < $len
+ && $GLOBALS['PMA_String']->substr($sql, $count2, 1) == 'x'
+ );
+ $is_float_digit = $c == '.';
+ $is_float_digit_exponent = false;
+
+ /* DEBUG
+ echo '<pre>2';
+ var_dump($is_identifier);
+ var_dump($is_sql_variable);
+ var_dump($is_digit);
+ var_dump($is_float_digit);
+ echo '</pre>';
+ */
+
+ // Fast skip is especially needed for huge BLOB data
+ if ($is_hex_digit) {
+ $count2++;
+ $pos = strspn($sql, '0123456789abcdefABCDEF', $count2);
+ if ($pos > $count2) {
+ $count2 = $pos;
+ }
+ unset($pos);
+ } elseif ($is_digit) {
+ $pos = strspn($sql, '0123456789', $count2);
+ if ($pos > $count2) {
+ $count2 = $pos;
+ }
+ unset($pos);
+ }
+
+ while (($count2 < $len) && $GLOBALS['PMA_String']->isSqlIdentifier($GLOBALS['PMA_String']->substr($sql, $count2, 1), ($is_sql_variable || $is_digit))) {
+ $c2 = $GLOBALS['PMA_String']->substr($sql, $count2, 1);
+ if ($is_sql_variable && ($c2 == '.')) {
+ $count2++;
+ continue;
+ }
+ if ($is_digit && (!$is_hex_digit) && ($c2 == '.')) {
+ $count2++;
+ if (!$is_float_digit) {
+ $is_float_digit = true;
+ continue;
+ } else {
+ $debugstr = __('Invalid Identifer')
+ . ' @ ' . ($count1+1) . "\n"
+ . 'STR: ' . htmlspecialchars(
+ $GLOBALS['PMA_String']->substr($sql, $count1, $count2 - $count1)
+ );
+ PMA_SQP_throwError($debugstr, $sql);
+ return $sql_array;
+ }
+ }
+ if ($is_digit && (!$is_hex_digit) && (($c2 == 'e') || ($c2 == 'E'))) {
+ if (!$is_float_digit_exponent) {
+ $is_float_digit_exponent = true;
+ $is_float_digit = true;
+ $count2++;
+ continue;
+ } else {
+ $is_digit = false;
+ $is_float_digit = false;
+ }
+ }
+ if (($is_hex_digit && $GLOBALS['PMA_String']->isHexDigit($c2)) || ($is_digit && $GLOBALS['PMA_String']->isDigit($c2))) {
+ $count2++;
+ continue;
+ } else {
+ $is_digit = false;
+ $is_hex_digit = false;
+ }
+
+ $count2++;
+ } // end while
+
+ $l = $count2 - $count1;
+ $str = $GLOBALS['PMA_String']->substr($sql, $count1, $l);
+
+ $type = '';
+ if ($is_digit || $is_float_digit || $is_hex_digit) {
+ $type = 'digit';
+ if ($is_float_digit) {
+ $type .= '_float';
+ } elseif ($is_hex_digit) {
+ $type .= '_hex';
+ } else {
+ $type .= '_integer';
+ }
+ } elseif ($is_user) {
+ $type = 'punct_user';
+ } elseif ($is_sql_variable != false) {
+ $type = 'alpha_variable';
+ } else {
+ $type = 'alpha';
+ } // end if... else....
+ PMA_SQP_arrayAdd($sql_array, $type, $str, $arraysize, $count2);
+
+ continue;
+ }
+
+ // Checks for punct
+ if ($GLOBALS['PMA_String']->strpos($allpunct_list, $c) !== false) {
+ while (($count2 < $len) && $GLOBALS['PMA_String']->strpos($allpunct_list, $GLOBALS['PMA_String']->substr($sql, $count2, 1)) !== false) {
+ $count2++;
+ }
+ $l = $count2 - $count1;
+ if ($l == 1) {
+ $punct_data = $c;
+ } else {
+ $punct_data = $GLOBALS['PMA_String']->substr($sql, $count1, $l);
+ }
+
+ // Special case, sometimes, althought two characters are
+ // adjectent directly, they ACTUALLY need to be seperate
+ /* DEBUG
+ echo '<pre>';
+ var_dump($l);
+ var_dump($punct_data);
+ echo '</pre>';
+ */
+
+ if ($l == 1) {
+ $t_suffix = '';
+ switch ($punct_data) {
+ case $punct_queryend:
+ $t_suffix = '_queryend';
+ break;
+ case $punct_qualifier:
+ $t_suffix = '_qualifier';
+ $this_was_punct = true;
+ break;
+ case $punct_listsep:
+ $this_was_listsep = true;
+ $t_suffix = '_listsep';
+ break;
+ default:
+ break;
+ }
+ PMA_SQP_arrayAdd($sql_array, 'punct' . $t_suffix, $punct_data, $arraysize);
+ } elseif ($punct_data == $GLOBALS['sql_delimiter'] || isset($allpunct_list_pair[$punct_data])) {
+ // Ok, we have one of the valid combined punct expressions
+ PMA_SQP_arrayAdd($sql_array, 'punct', $punct_data, $arraysize);
+ } else {
+ // Bad luck, lets split it up more
+ $first = $punct_data[0];
+ $last2 = $punct_data[$l - 2] . $punct_data[$l - 1];
+ $last = $punct_data[$l - 1];
+ if (($first == ',') || ($first == ';') || ($first == '.') || ($first == '*')) {
+ $count2 = $count1 + 1;
+ $punct_data = $first;
+ } elseif (($last2 == '/*') || (($last2 == '--') && ($count2 == $len || $GLOBALS['PMA_String']->substr($sql, $count2, 1) <= ' '))) {
+ $count2 -= 2;
+ $punct_data = $GLOBALS['PMA_String']->substr($sql, $count1, $count2 - $count1);
+ } elseif (($last == '-') || ($last == '+') || ($last == '!')) {
+ $count2--;
+ $punct_data = $GLOBALS['PMA_String']->substr($sql, $count1, $count2 - $count1);
+ } elseif ($last != '~') {
+ /**
+ * @todo for negation operator, split in 2 tokens ?
+ * "select x&~1 from t"
+ * becomes "select x & ~ 1 from t" ?
+ */
+ $debugstr = __('Unknown Punctuation String')
+ . ' @ ' . ($count1+1) . "\n"
+ . 'STR: ' . htmlspecialchars($punct_data);
+ PMA_SQP_throwError($debugstr, $sql);
+ return $sql_array;
+ }
+ PMA_SQP_arrayAdd($sql_array, 'punct', $punct_data, $arraysize);
+ continue;
+ } // end if... elseif... else
+ continue;
+ }
+
+ // DEBUG
+ $count2++;
+
+ $debugstr = 'C1 C2 LEN: ' . $count1 . ' ' . $count2 . ' ' . $len . "\n"
+ . 'STR: ' . $GLOBALS['PMA_String']->substr($sql, $count1, $count2 - $count1) . "\n";
+ PMA_SQP_bug($debugstr, $sql);
+ return $sql_array;
+
+ } // end while ($count2 < $len)
+
+ /*
+ echo '<pre>';
+ print_r($sql_array);
+ echo '</pre>';
+ */
+
+ if ($arraysize > 0) {
+ $t_next = $sql_array[0]['type'];
+ $t_prev = '';
+ $t_bef_prev = '';
+ $t_cur = '';
+ $d_next = $sql_array[0]['data'];
+ $d_prev = '';
+ $d_bef_prev = '';
+ $d_cur = '';
+ $d_next_upper = $t_next == 'alpha' ? strtoupper($d_next) : $d_next;
+ $d_prev_upper = '';
+ $d_bef_prev_upper = '';
+ $d_cur_upper = '';
+ }
+
+ for ($i = 0; $i < $arraysize; $i++) {
+ $t_bef_prev = $t_prev;
+ $t_prev = $t_cur;
+ $t_cur = $t_next;
+ $d_bef_prev = $d_prev;
+ $d_prev = $d_cur;
+ $d_cur = $d_next;
+ $d_bef_prev_upper = $d_prev_upper;
+ $d_prev_upper = $d_cur_upper;
+ $d_cur_upper = $d_next_upper;
+ if (($i + 1) < $arraysize) {
+ $t_next = $sql_array[$i + 1]['type'];
+ $d_next = $sql_array[$i + 1]['data'];
+ $d_next_upper = $t_next == 'alpha' ? strtoupper($d_next) : $d_next;
+ } else {
+ $t_next = '';
+ $d_next = '';
+ $d_next_upper = '';
+ }
+
+ //DEBUG echo "[prev: <strong>".$d_prev."</strong> ".$t_prev."][cur: <strong>".$d_cur."</strong> ".$t_cur."][next: <strong>".$d_next."</strong> ".$t_next."]<br />";
+
+ if ($t_cur == 'alpha') {
+ $t_suffix = '_identifier';
+ // for example: `thebit` bit(8) NOT NULL DEFAULT b'0'
+ if ($t_prev == 'alpha' && $d_prev == 'DEFAULT' && $d_cur == 'b' && $t_next == 'quote_single') {
+ $t_suffix = '_bitfield_constant_introducer';
+ } elseif (($t_next == 'punct_qualifier') || ($t_prev == 'punct_qualifier')) {
+ $t_suffix = '_identifier';
+ } elseif (($t_next == 'punct_bracket_open_round')
+ && isset($PMA_SQPdata_function_name[$d_cur_upper])
+ ) {
+ /**
+ * @todo 2005-10-16: in the case of a CREATE TABLE containing
+ * a TIMESTAMP, since TIMESTAMP() is also a function, it's
+ * found here and the token is wrongly marked as alpha_functionName.
+ * But we compensate for this when analysing for timestamp_not_null
+ * later in this script.
+ *
+ * Same applies to CHAR vs. CHAR() function.
+ */
+ $t_suffix = '_functionName';
+ /* There are functions which might be as well column types */
+ } elseif (isset($PMA_SQPdata_column_type[$d_cur_upper])) {
+ $t_suffix = '_columnType';
+
+ /**
+ * Temporary fix for bugs #621357 and #2027720
+ *
+ * @todo FIX PROPERLY NEEDS OVERHAUL OF SQL TOKENIZER
+ */
+ if (($d_cur_upper == 'SET' || $d_cur_upper == 'BINARY') && $t_next != 'punct_bracket_open_round') {
+ $t_suffix = '_reservedWord';
+ }
+ //END OF TEMPORARY FIX
+
+ // CHARACTER is a synonym for CHAR, but can also be meant as
+ // CHARACTER SET. In this case, we have a reserved word.
+ if ($d_cur_upper == 'CHARACTER' && $d_next_upper == 'SET') {
+ $t_suffix = '_reservedWord';
+ }
+
+ // experimental
+ // current is a column type, so previous must not be
+ // a reserved word but an identifier
+ // CREATE TABLE SG_Persons (first varchar(64))
+
+ //if ($sql_array[$i-1]['type'] =='alpha_reservedWord') {
+ // $sql_array[$i-1]['type'] = 'alpha_identifier';
+ //}
+
+ } elseif (isset($PMA_SQPdata_reserved_word[$d_cur_upper])) {
+ $t_suffix = '_reservedWord';
+ } elseif (isset($PMA_SQPdata_column_attrib[$d_cur_upper])) {
+ $t_suffix = '_columnAttrib';
+ // INNODB is a MySQL table type, but in "SHOW INNODB STATUS",
+ // it should be regarded as a reserved word.
+ if ($d_cur_upper == 'INNODB'
+ && $d_prev_upper == 'SHOW'
+ && $d_next_upper == 'STATUS'
+ ) {
+ $t_suffix = '_reservedWord';
+ }
+
+ if ($d_cur_upper == 'DEFAULT' && $d_next_upper == 'CHARACTER') {
+ $t_suffix = '_reservedWord';
+ }
+ // Binary as character set
+ if ($d_cur_upper == 'BINARY'
+ && (($d_bef_prev_upper == 'CHARACTER' && $d_prev_upper == 'SET')
+ || ($d_bef_prev_upper == 'SET' && $d_prev_upper == '=')
+ || ($d_bef_prev_upper == 'CHARSET' && $d_prev_upper == '=')
+ || $d_prev_upper == 'CHARSET')
+ && in_array($d_cur, $mysql_charsets)
+ ) {
+ $t_suffix = '_charset';
+ }
+ } elseif (in_array($d_cur, $mysql_charsets)
+ || in_array($d_cur, $mysql_collations_flat)
+ || ($d_cur{0} == '_' && in_array(substr($d_cur, 1), $mysql_charsets))
+ ) {
+ $t_suffix = '_charset';
+ } else {
+ // Do nothing
+ }
+ // check if present in the list of forbidden words
+ if ($t_suffix == '_reservedWord'
+ && isset($PMA_SQPdata_forbidden_word[$d_cur_upper])
+ ) {
+ $sql_array[$i]['forbidden'] = true;
+ } else {
+ $sql_array[$i]['forbidden'] = false;
+ }
+ $sql_array[$i]['type'] .= $t_suffix;
+ }
+ } // end for
+
+ // Stores the size of the array inside the array, as count() is a slow
+ // operation.
+ $sql_array['len'] = $arraysize;
+
+ // DEBUG echo 'After parsing<pre>'; print_r($sql_array); echo '</pre>';
+ // Sends the data back
+ return $sql_array;
+} // end of the "PMA_SQP_parse()" function
+
+/**
+ * Checks for token types being what we want...
+ *
+ * @param string $toCheck String of type that we have
+ * @param string $whatWeWant String of type that we want
+ *
+ * @return boolean result of check
+ *
+ * @access private
+ */
+function PMA_SQP_typeCheck($toCheck, $whatWeWant)
+{
+ $typeSeparator = '_';
+ if (strcmp($whatWeWant, $toCheck) == 0) {
+ return true;
+ } else {
+ if (strpos($whatWeWant, $typeSeparator) === false) {
+ return strncmp(
+ $whatWeWant, $toCheck,
+ strpos($toCheck, $typeSeparator)
+ ) == 0;
+ } else {
+ return false;
+ }
+ }
+}
+
+
+/**
+ * Analyzes SQL queries
+ *
+ * @param array $arr The SQL queries
+ *
+ * @return array The analyzed SQL queries
+ *
+ * @access public
+ */
+function PMA_SQP_analyze($arr)
+{
+ if ($arr == array() || ! isset($arr['len'])) {
+ return array();
+ }
+ $result = array();
+ $size = $arr['len'];
+ $subresult = array(
+ 'querytype' => '',
+ 'select_expr_clause'=> '', // the whole stuff between SELECT and FROM , except DISTINCT
+ 'position_of_first_select' => '', // the array index
+ 'from_clause'=> '',
+ 'group_by_clause'=> '',
+ 'order_by_clause'=> '',
+ 'having_clause' => '',
+ 'limit_clause' => '',
+ 'where_clause' => '',
+ 'where_clause_identifiers' => array(),
+ 'unsorted_query' => '',
+ 'queryflags' => array(),
+ 'select_expr' => array(),
+ 'table_ref' => array(),
+ 'foreign_keys' => array(),
+ 'create_table_fields' => array()
+ );
+ $subresult_empty = $subresult;
+ $seek_queryend = false;
+ $seen_end_of_table_ref = false;
+ $number_of_brackets_in_extract = 0;
+ $number_of_brackets_in_group_concat = 0;
+
+ $number_of_brackets = 0;
+ $in_subquery = false;
+ $seen_subquery = false;
+ $seen_from = false;
+
+ // for SELECT EXTRACT(YEAR_MONTH FROM CURDATE())
+ // we must not use CURDATE as a table_ref
+ // so we track whether we are in the EXTRACT()
+ $in_extract = false;
+
+ // for GROUP_CONCAT(...)
+ $in_group_concat = false;
+
+ /* Description of analyzer results
+ *
+ * db, table, column, alias
+ * ------------------------
+ *
+ * Inside the $subresult array, we create ['select_expr'] and ['table_ref']
+ * arrays.
+ *
+ * The SELECT syntax (simplified) is
+ *
+ * SELECT
+ * select_expression,...
+ * [FROM [table_references]
+ *
+ *
+ * ['select_expr'] is filled with each expression, the key represents the
+ * expression position in the list (0-based) (so we don't lose track of
+ * multiple occurences of the same column).
+ *
+ * ['table_ref'] is filled with each table ref, same thing for the key.
+ *
+ * I create all sub-values empty, even if they are
+ * not present (for example no select_expression alias).
+ *
+ * There is a debug section at the end of loop #1, if you want to
+ * see the exact contents of select_expr and table_ref
+ *
+ * queryflags
+ * ----------
+ *
+ * In $subresult, array 'queryflags' is filled, according to what we
+ * find in the query.
+ *
+ * Currently, those are generated:
+ *
+ * ['queryflags']['select_from'] = 1; if this is a real SELECT...FROM
+ * ['queryflags']['drop_database'] = 1;if this is a DROP DATABASE
+ * ['queryflags']['reload'] = 1; for the purpose of reloading the
+ * navigation bar
+ * ['queryflags']['distinct'] = 1; for a DISTINCT
+ * ['queryflags']['union'] = 1; for a UNION
+ * ['queryflags']['join'] = 1; for a JOIN
+ * ['queryflags']['offset'] = 1; for the presence of OFFSET
+ * ['queryflags']['procedure'] = 1; for the presence of PROCEDURE
+ * ['queryflags']['is_explain'] = 1; for the presence of EXPLAIN
+ * ['queryflags']['is_delete'] = 1; for the presence of DELETE
+ * ['queryflags']['is_affected'] = 1; for the presence of UPDATE, DELETE
+ * or INSERT|LOAD DATA|REPLACE
+ * ['queryflags']['is_replace'] = 1; for the presence of REPLACE
+ * ['queryflags']['is_insert'] = 1; for the presence of INSERT
+ * ['queryflags']['is_maint'] = 1; for the presence of CHECK|ANALYZE
+ * |REPAIR|OPTIMIZE TABLE
+ * ['queryflags']['is_show'] = 1; for the presence of SHOW
+ * ['queryflags']['is_analyse'] = 1; for the presence of PROCEDURE ANALYSE
+ * ['queryflags']['is_export'] = 1; for the presence of INTO OUTFILE
+ * ['queryflags']['is_group'] = 1; for the presence of GROUP BY|HAVING|
+ * SELECT DISTINCT
+ * ['queryflags']['is_func'] = 1; for the presence of SUM|AVG|STD|STDDEV
+ * |MIN|MAX|BIT_OR|BIT_AND
+ * ['queryflags']['is_count'] = 1; for the presence of SELECT COUNT
+ * ['queryflags']['is_procedure'] = 1; for the presence of CALL
+ * ['queryflags']['is_subquery'] = 1; contains a subquery
+ *
+ * query clauses
+ * -------------
+ *
+ * The select is splitted in those clauses:
+ * ['select_expr_clause']
+ * ['from_clause']
+ * ['group_by_clause']
+ * ['order_by_clause']
+ * ['having_clause']
+ * ['limit_clause']
+ * ['where_clause']
+ *
+ * The identifiers of the WHERE clause are put into the array
+ * ['where_clause_identifier']
+ *
+ * For a SELECT, the whole query without the ORDER BY clause is put into
+ * ['unsorted_query']
+ *
+ * foreign keys
+ * ------------
+ * The CREATE TABLE may contain FOREIGN KEY clauses, so they get
+ * analyzed and ['foreign_keys'] is an array filled with
+ * the constraint name, the index list,
+ * the REFERENCES table name and REFERENCES index list,
+ * and ON UPDATE | ON DELETE clauses
+ *
+ * position_of_first_select
+ * ------------------------
+ *
+ * The array index of the first SELECT we find. Will be used to
+ * insert a SQL_CALC_FOUND_ROWS.
+ *
+ * create_table_fields
+ * -------------------
+ *
+ * Used to detect the DEFAULT CURRENT_TIMESTAMP and
+ * ON UPDATE CURRENT_TIMESTAMP clauses of the CREATE TABLE query.
+ * Also used to store the default value of the field.
+ * An array, each element is the identifier name.
+ * Note that for now, the timestamp_not_null element is created
+ * even for non-TIMESTAMP fields.
+ *
+ * Sub-elements: ['type'] which contains the column type
+ * optional (currently they are never false but can be absent):
+ * ['default_current_timestamp'] boolean
+ * ['on_update_current_timestamp'] boolean
+ * ['timestamp_not_null'] boolean
+ *
+ * section_before_limit, section_after_limit
+ * -----------------------------------------
+ *
+ * Marks the point of the query where we can insert a LIMIT clause;
+ * so the section_before_limit will contain the left part before
+ * a possible LIMIT clause
+ *
+ *
+ * End of description of analyzer results
+ */
+
+ // must be sorted
+ // TODO: current logic checks for only one word, so I put only the
+ // first word of the reserved expressions that end a table ref;
+ // maybe this is not ok (the first word might mean something else)
+ // $words_ending_table_ref = array(
+ // 'FOR UPDATE',
+ // 'GROUP BY',
+ // 'HAVING',
+ // 'LIMIT',
+ // 'LOCK IN SHARE MODE',
+ // 'ORDER BY',
+ // 'PROCEDURE',
+ // 'UNION',
+ // 'WHERE'
+ // );
+ $words_ending_table_ref = array(
+ 'FOR' => 1,
+ 'GROUP' => 1,
+ 'HAVING' => 1,
+ 'LIMIT' => 1,
+ 'LOCK' => 1,
+ 'ORDER' => 1,
+ 'PROCEDURE' => 1,
+ 'UNION' => 1,
+ 'WHERE' => 1
+ );
+
+ $words_ending_clauses = array(
+ 'FOR' => 1,
+ 'LIMIT' => 1,
+ 'LOCK' => 1,
+ 'PROCEDURE' => 1,
+ 'UNION' => 1
+ );
+
+ $supported_query_types = array(
+ 'SELECT' => 1,
+ /*
+ // Support for these additional query types will come later on.
+ 'DELETE' => 1,
+ 'INSERT' => 1,
+ 'REPLACE' => 1,
+ 'TRUNCATE' => 1,
+ 'UPDATE' => 1,
+ 'EXPLAIN' => 1,
+ 'DESCRIBE' => 1,
+ 'SHOW' => 1,
+ 'CREATE' => 1,
+ 'SET' => 1,
+ 'ALTER' => 1
+ */
+ );
+
+ // loop #1 for each token: select_expr, table_ref for SELECT
+
+ for ($i = 0; $i < $size; $i++) {
+ //DEBUG echo "Loop1 <strong>" . $arr[$i]['data']
+ //. "</strong> (" . $arr[$i]['type'] . ")<br />";
+
+ // High speed seek for locating the end of the current query
+ if ($seek_queryend == true) {
+ if ($arr[$i]['type'] == 'punct_queryend') {
+ $seek_queryend = false;
+ } else {
+ continue;
+ } // end if (type == punct_queryend)
+ } // end if ($seek_queryend)
+
+ /**
+ * Note: do not split if this is a punct_queryend for the first and only
+ * query
+ * @todo when we find a UNION, should we split in another subresult?
+ */
+ if ($arr[$i]['type'] == 'punct_queryend' && ($i + 1 != $size)) {
+ $result[] = $subresult;
+ $subresult = $subresult_empty;
+ continue;
+ } // end if (type == punct_queryend)
+
+ // ==============================================================
+ if ($arr[$i]['type'] == 'punct_bracket_open_round') {
+ $number_of_brackets++;
+ if ($in_extract) {
+ $number_of_brackets_in_extract++;
+ }
+ if ($in_group_concat) {
+ $number_of_brackets_in_group_concat++;
+ }
+ }
+ // ==============================================================
+ if ($arr[$i]['type'] == 'punct_bracket_close_round') {
+ $number_of_brackets--;
+ if ($number_of_brackets == 0) {
+ $in_subquery = false;
+ }
+ if ($in_extract) {
+ $number_of_brackets_in_extract--;
+ if ($number_of_brackets_in_extract == 0) {
+ $in_extract = false;
+ }
+ }
+ if ($in_group_concat) {
+ $number_of_brackets_in_group_concat--;
+ if ($number_of_brackets_in_group_concat == 0) {
+ $in_group_concat = false;
+ }
+ }
+ }
+
+ if ($in_subquery) {
+ /**
+ * skip the subquery to avoid setting
+ * select_expr or table_ref with the contents
+ * of this subquery; this is to avoid a bug when
+ * trying to edit the results of
+ * select * from child where not exists (select id from
+ * parent where child.parent_id = parent.id);
+ */
+ continue;
+ }
+ // ==============================================================
+ if ($arr[$i]['type'] == 'alpha_functionName') {
+ $upper_data = strtoupper($arr[$i]['data']);
+ if ($upper_data =='EXTRACT') {
+ $in_extract = true;
+ $number_of_brackets_in_extract = 0;
+ }
+ if ($upper_data =='GROUP_CONCAT') {
+ $in_group_concat = true;
+ $number_of_brackets_in_group_concat = 0;
+ }
+ }
+
+ // ==============================================================
+ if ($arr[$i]['type'] == 'alpha_reservedWord') {
+ // We don't know what type of query yet, so run this
+ if ($subresult['querytype'] == '') {
+ $subresult['querytype'] = strtoupper($arr[$i]['data']);
+ } // end if (querytype was empty)
+
+ // Check if we support this type of query
+ if (!isset($supported_query_types[$subresult['querytype']])) {
+ // Skip ahead to the next one if we don't
+ $seek_queryend = true;
+ continue;
+ } // end if (query not supported)
+
+ // upper once
+ $upper_data = strtoupper($arr[$i]['data']);
+ /**
+ * @todo reset for each query?
+ */
+
+ if ($upper_data == 'SELECT') {
+ if ($number_of_brackets > 0) {
+ $in_subquery = true;
+ $seen_subquery = true;
+ $subresult['queryflags']['is_subquery'] = 1;
+ // this is a subquery so do not analyze inside it
+ continue;
+ }
+ $seen_from = false;
+ $previous_was_identifier = false;
+ $current_select_expr = -1;
+ $seen_end_of_table_ref = false;
+ } // end if (data == SELECT)
+
+ if ($upper_data =='FROM' && !$in_extract) {
+ $current_table_ref = -1;
+ $seen_from = true;
+ $previous_was_identifier = false;
+ $save_table_ref = true;
+ } // end if (data == FROM)
+
+ // here, do not 'continue' the loop, as we have more work for
+ // reserved words below
+ } // end if (type == alpha_reservedWord)
+
+ // ==============================
+ if ($arr[$i]['type'] == 'quote_backtick'
+ || $arr[$i]['type'] == 'quote_double'
+ || $arr[$i]['type'] == 'quote_single'
+ || $arr[$i]['type'] == 'alpha_identifier'
+ || ($arr[$i]['type'] == 'alpha_reservedWord'
+ && $arr[$i]['forbidden'] == false)
+ ) {
+ switch ($arr[$i]['type']) {
+ case 'alpha_identifier':
+ case 'alpha_reservedWord':
+ /**
+ * this is not a real reservedWord, because it's not
+ * present in the list of forbidden words, for example
+ * "storage" which can be used as an identifier
+ *
+ */
+ $identifier = $arr[$i]['data'];
+ break;
+
+ case 'quote_backtick':
+ case 'quote_double':
+ case 'quote_single':
+ $identifier = PMA_Util::unQuote($arr[$i]['data']);
+ break;
+ } // end switch
+
+ if ($subresult['querytype'] == 'SELECT'
+ && ! $in_group_concat
+ && ! ($seen_subquery && $arr[$i - 1]['type'] == 'punct_bracket_close_round')
+ ) {
+ if (!$seen_from) {
+ if ($previous_was_identifier && isset($chain)) {
+ // found alias for this select_expr, save it
+ // but only if we got something in $chain
+ // (for example, SELECT COUNT(*) AS cnt
+ // puts nothing in $chain, so we avoid
+ // setting the alias)
+ $alias_for_select_expr = $identifier;
+ } else {
+ $chain[] = $identifier;
+ $previous_was_identifier = true;
+
+ } // end if !$previous_was_identifier
+ } else {
+ // ($seen_from)
+ if ($save_table_ref && !$seen_end_of_table_ref) {
+ if ($previous_was_identifier) {
+ // found alias for table ref
+ // save it for later
+ $alias_for_table_ref = $identifier;
+ } else {
+ $chain[] = $identifier;
+ $previous_was_identifier = true;
+
+ } // end if ($previous_was_identifier)
+ } // end if ($save_table_ref &&!$seen_end_of_table_ref)
+ } // end if (!$seen_from)
+ } // end if (querytype SELECT)
+ } // end if (quote_backtick or double quote or alpha_identifier)
+
+ // ===================================
+ if ($arr[$i]['type'] == 'punct_qualifier') {
+ // to be able to detect an identifier following another
+ $previous_was_identifier = false;
+ continue;
+ } // end if (punct_qualifier)
+
+ /**
+ * @todo check if 3 identifiers following one another -> error
+ */
+
+ // s a v e a s e l e c t e x p r
+ // finding a list separator or FROM
+ // means that we must save the current chain of identifiers
+ // into a select expression
+
+ // for now, we only save a select expression if it contains
+ // at least one identifier, as we are interested in checking
+ // the columns and table names, so in "select * from persons",
+ // the "*" is not saved
+
+ if (isset($chain) && !$seen_end_of_table_ref
+ && ((!$seen_from && $arr[$i]['type'] == 'punct_listsep')
+ || ($arr[$i]['type'] == 'alpha_reservedWord' && $upper_data == 'FROM'))
+ ) {
+ $size_chain = count($chain);
+ $current_select_expr++;
+ $subresult['select_expr'][$current_select_expr] = array(
+ 'expr' => '',
+ 'alias' => '',
+ 'db' => '',
+ 'table_name' => '',
+ 'table_true_name' => '',
+ 'column' => ''
+ );
+
+ if (isset($alias_for_select_expr) && strlen($alias_for_select_expr)) {
+ // we had found an alias for this select expression
+ $subresult['select_expr'][$current_select_expr]['alias'] = $alias_for_select_expr;
+ unset($alias_for_select_expr);
+ }
+ // there is at least a column
+ $subresult['select_expr'][$current_select_expr]['column'] = $chain[$size_chain - 1];
+ $subresult['select_expr'][$current_select_expr]['expr'] = $chain[$size_chain - 1];
+
+ // maybe a table
+ if ($size_chain > 1) {
+ $subresult['select_expr'][$current_select_expr]['table_name'] = $chain[$size_chain - 2];
+ // we assume for now that this is also the true name
+ $subresult['select_expr'][$current_select_expr]['table_true_name'] = $chain[$size_chain - 2];
+ $subresult['select_expr'][$current_select_expr]['expr']
+ = $subresult['select_expr'][$current_select_expr]['table_name']
+ . '.' . $subresult['select_expr'][$current_select_expr]['expr'];
+ } // end if ($size_chain > 1)
+
+ // maybe a db
+ if ($size_chain > 2) {
+ $subresult['select_expr'][$current_select_expr]['db'] = $chain[$size_chain - 3];
+ $subresult['select_expr'][$current_select_expr]['expr']
+ = $subresult['select_expr'][$current_select_expr]['db']
+ . '.' . $subresult['select_expr'][$current_select_expr]['expr'];
+ } // end if ($size_chain > 2)
+ unset($chain);
+
+ /**
+ * @todo explain this:
+ */
+ if (($arr[$i]['type'] == 'alpha_reservedWord')
+ && ($upper_data != 'FROM')
+ ) {
+ $previous_was_identifier = true;
+ }
+
+ } // end if (save a select expr)
+
+
+ //======================================
+ // s a v e a t a b l e r e f
+ //======================================
+
+ // maybe we just saw the end of table refs
+ // but the last table ref has to be saved
+ // or we are at the last token
+ // or we just got a reserved word
+ /**
+ * @todo there could be another query after this one
+ */
+
+ if (isset($chain) && $seen_from && $save_table_ref
+ && ($arr[$i]['type'] == 'punct_listsep'
+ || ($arr[$i]['type'] == 'alpha_reservedWord' && $upper_data != "AS")
+ || $seen_end_of_table_ref
+ || $i == $size - 1)
+ ) {
+
+ $size_chain = count($chain);
+ $current_table_ref++;
+ $subresult['table_ref'][$current_table_ref] = array(
+ 'expr' => '',
+ 'db' => '',
+ 'table_name' => '',
+ 'table_alias' => '',
+ 'table_true_name' => ''
+ );
+ if (isset($alias_for_table_ref) && strlen($alias_for_table_ref)) {
+ $subresult['table_ref'][$current_table_ref]['table_alias'] = $alias_for_table_ref;
+ unset($alias_for_table_ref);
+ }
+ $subresult['table_ref'][$current_table_ref]['table_name'] = $chain[$size_chain - 1];
+ // we assume for now that this is also the true name
+ $subresult['table_ref'][$current_table_ref]['table_true_name'] = $chain[$size_chain - 1];
+ $subresult['table_ref'][$current_table_ref]['expr']
+ = $subresult['table_ref'][$current_table_ref]['table_name'];
+ // maybe a db
+ if ($size_chain > 1) {
+ $subresult['table_ref'][$current_table_ref]['db'] = $chain[$size_chain - 2];
+ $subresult['table_ref'][$current_table_ref]['expr']
+ = $subresult['table_ref'][$current_table_ref]['db']
+ . '.' . $subresult['table_ref'][$current_table_ref]['expr'];
+ } // end if ($size_chain > 1)
+
+ // add the table alias into the whole expression
+ $subresult['table_ref'][$current_table_ref]['expr']
+ .= ' ' . $subresult['table_ref'][$current_table_ref]['table_alias'];
+
+ unset($chain);
+ $previous_was_identifier = true;
+ //continue;
+
+ } // end if (save a table ref)
+
+
+ // when we have found all table refs,
+ // for each table_ref alias, put the true name of the table
+ // in the corresponding select expressions
+
+ if (isset($current_table_ref)
+ && ($seen_end_of_table_ref || $i == $size-1)
+ && $subresult != $subresult_empty
+ ) {
+ for ($tr=0; $tr <= $current_table_ref; $tr++) {
+ $alias = $subresult['table_ref'][$tr]['table_alias'];
+ $truename = $subresult['table_ref'][$tr]['table_true_name'];
+ for ($se=0; $se <= $current_select_expr; $se++) {
+ if (isset($alias)
+ && strlen($alias)
+ && $subresult['select_expr'][$se]['table_true_name'] == $alias
+ ) {
+ $subresult['select_expr'][$se]['table_true_name'] = $truename;
+ } // end if (found the alias)
+ } // end for (select expressions)
+
+ } // end for (table refs)
+ } // end if (set the true names)
+
+
+ // e n d i n g l o o p #1
+ // set the $previous_was_identifier to false if the current
+ // token is not an identifier
+ if (($arr[$i]['type'] != 'alpha_identifier')
+ && ($arr[$i]['type'] != 'quote_double')
+ && ($arr[$i]['type'] != 'quote_single')
+ && ($arr[$i]['type'] != 'quote_backtick')
+ ) {
+ $previous_was_identifier = false;
+ } // end if
+
+ // however, if we are on AS, we must keep the $previous_was_identifier
+ if (($arr[$i]['type'] == 'alpha_reservedWord')
+ && ($upper_data == 'AS')
+ ) {
+ $previous_was_identifier = true;
+ }
+
+ if (($arr[$i]['type'] == 'alpha_reservedWord')
+ && ($upper_data =='ON' || $upper_data =='USING')
+ ) {
+ $save_table_ref = false;
+ } // end if (data == ON)
+
+ if (($arr[$i]['type'] == 'alpha_reservedWord')
+ && ($upper_data =='JOIN' || $upper_data =='FROM')
+ ) {
+ $save_table_ref = true;
+ } // end if (data == JOIN)
+
+ /**
+ * no need to check the end of table ref if we already did
+ *
+ * @todo maybe add "&& $seen_from"
+ */
+ if (!$seen_end_of_table_ref) {
+ // if this is the last token, it implies that we have
+ // seen the end of table references
+ // Check for the end of table references
+ //
+ // Note: if we are analyzing a GROUP_CONCAT clause,
+ // we might find a word that seems to indicate that
+ // we have found the end of table refs (like ORDER)
+ // but it's a modifier of the GROUP_CONCAT so
+ // it's not the real end of table refs
+ if (($i == $size-1)
+ || ($arr[$i]['type'] == 'alpha_reservedWord'
+ && !$in_group_concat
+ && isset($words_ending_table_ref[$upper_data]))
+ ) {
+ $seen_end_of_table_ref = true;
+ // to be able to save the last table ref, but do not
+ // set it true if we found a word like "ON" that has
+ // already set it to false
+ if (isset($save_table_ref) && $save_table_ref != false) {
+ $save_table_ref = true;
+ } //end if
+
+ } // end if (check for end of table ref)
+ } //end if (!$seen_end_of_table_ref)
+
+ if ($seen_end_of_table_ref) {
+ $save_table_ref = false;
+ } // end if
+
+ } // end for $i (loop #1)
+
+ //DEBUG
+ /*
+ if (isset($current_select_expr)) {
+ for ($trace=0; $trace<=$current_select_expr; $trace++) {
+ echo "<br />";
+ reset ($subresult['select_expr'][$trace]);
+ while (list ($key, $val) = each ($subresult['select_expr'][$trace]))
+ echo "sel expr $trace $key => $val<br />\n";
+ }
+ }
+
+ if (isset($current_table_ref)) {
+ echo "current_table_ref = " . $current_table_ref . "<br>";
+ for ($trace=0; $trace<=$current_table_ref; $trace++) {
+
+ echo "<br />";
+ reset ($subresult['table_ref'][$trace]);
+ while (list ($key, $val) = each ($subresult['table_ref'][$trace]))
+ echo "table ref $trace $key => $val<br />\n";
+ }
+ }
+ */
+ // -------------------------------------------------------
+
+
+ // loop #2: - queryflags
+ // - querytype (for queries != 'SELECT')
+ // - section_before_limit, section_after_limit
+ //
+ // we will also need this queryflag in loop 2
+ // so set it here
+ if (isset($current_table_ref) && $current_table_ref > -1) {
+ $subresult['queryflags']['select_from'] = 1;
+ }
+
+ $section_before_limit = '';
+ $section_after_limit = ''; // truly the section after the limit clause
+ $seen_reserved_word = false;
+ $seen_group = false;
+ $seen_order = false;
+ $seen_order_by = false;
+ $in_group_by = false; // true when we are inside the GROUP BY clause
+ $in_order_by = false; // true when we are inside the ORDER BY clause
+ $in_having = false; // true when we are inside the HAVING clause
+ $in_select_expr = false; // true when we are inside the select expr clause
+ $in_where = false; // true when we are inside the WHERE clause
+ $seen_limit = false; // true if we have seen a LIMIT clause
+ $in_limit = false; // true when we are inside the LIMIT clause
+ $after_limit = false; // true when we are after the LIMIT clause
+ $in_from = false; // true when we are in the FROM clause
+ $in_group_concat = false;
+ $first_reserved_word = '';
+ $current_identifier = '';
+ $unsorted_query = $arr['raw']; // in case there is no ORDER BY
+ $number_of_brackets = 0;
+ $in_subquery = false;
+
+ for ($i = 0; $i < $size; $i++) {
+ //DEBUG echo "Loop2 <strong>" . $arr[$i]['data']
+ //. "</strong> (" . $arr[$i]['type'] . ")<br />";
+
+ if ($arr[$i]['type'] == 'punct_bracket_open_round') {
+ $number_of_brackets++;
+ }
+
+ if ($arr[$i]['type'] == 'punct_bracket_close_round') {
+ $number_of_brackets--;
+ if ($number_of_brackets == 0) {
+ $in_subquery = false;
+ }
+ }
+
+ if ($arr[$i]['type'] == 'alpha_reservedWord') {
+ $upper_data = strtoupper($arr[$i]['data']);
+
+ if ($upper_data == 'SELECT' && $number_of_brackets > 0) {
+ $in_subquery = true;
+ }
+
+ if (!$seen_reserved_word) {
+ $first_reserved_word = $upper_data;
+ $subresult['querytype'] = $upper_data;
+ $seen_reserved_word = true;
+
+ if ($first_reserved_word == 'SELECT') {
+ $position_of_first_select = $i;
+ }
+
+ if ($first_reserved_word == 'EXPLAIN') {
+ $subresult['queryflags']['is_explain'] = 1;
+ }
+
+ if ($first_reserved_word == 'DELETE') {
+ $subresult['queryflags']['is_delete'] = 1;
+ $subresult['queryflags']['is_affected'] = 1;
+ }
+
+ if ($first_reserved_word == 'UPDATE') {
+ $subresult['queryflags']['is_affected'] = 1;
+ }
+
+ if ($first_reserved_word == 'REPLACE') {
+ $subresult['queryflags']['is_replace'] = 1;
+ }
+
+ if ($first_reserved_word == 'SHOW') {
+ $subresult['queryflags']['is_show'] = 1;
+ }
+
+ } else {
+ // for the presence of DROP DATABASE
+ if ($first_reserved_word == 'DROP' && $upper_data == 'DATABASE') {
+ $subresult['queryflags']['drop_database'] = 1;
+ }
+ // A table has to be created, renamed, dropped -> navi panel
+ // should be reloaded
+ $keywords1 = array('CREATE', 'ALTER', 'DROP');
+ $keywords2 = array('VIEW', 'TABLE', 'DATABASE', 'SCHEMA');
+ if (in_array($first_reserved_word, $keywords1)
+ && in_array($upper_data, $keywords2)
+ ) {
+ $subresult['queryflags']['reload'] = 1;
+ }
+
+ // for the presence of INSERT|LOAD DATA
+ if (in_array($first_reserved_word, array('INSERT', 'LOAD'))
+ && $upper_data == 'REPLACE'
+ ) {
+ $subresult['queryflags']['is_insert'] = 1;
+ $subresult['queryflags']['is_affected'] = 1;
+ }
+
+ // for the presence of CHECK|ANALYZE|REPAIR|OPTIMIZE TABLE
+ $keywords = array(
+ 'CHECK', 'ANALYZE', 'REPAIR', 'OPTIMIZE'
+ );
+ if (in_array($first_reserved_word, $keywords)
+ && $upper_data == 'TABLE'
+ ) {
+ $subresult['queryflags']['is_maint'] = 1;
+ }
+ }
+
+ if ($upper_data == 'LIMIT' && ! $in_subquery) {
+ $section_before_limit = substr($arr['raw'], 0, $arr[$i]['pos'] - 5);
+ $in_limit = true;
+ $seen_limit = true;
+ $limit_clause = '';
+ $in_order_by = false; // @todo maybe others to set false
+ }
+
+ if ($upper_data == 'PROCEDURE') {
+ $subresult['queryflags']['procedure'] = 1;
+ $in_limit = false;
+ $after_limit = true;
+
+ // for the presence of PROCEDURE ANALYSE
+ if (isset($subresult['queryflags']['select_from'])
+ && $subresult['queryflags']['select_from'] == 1
+ && ($i + 1) < $size
+ && $arr[$i + 1]['type'] == 'alpha_reservedWord'
+ && strtoupper($arr[$i + 1]['data']) == 'ANALYSE'
+ ) {
+ $subresult['queryflags']['is_analyse'] = 1;
+ }
+ }
+
+ // for the presence of INTO OUTFILE
+ if ($upper_data == 'INTO'
+ && isset($subresult['queryflags']['select_from'])
+ && $subresult['queryflags']['select_from'] == 1
+ && ($i + 1) < $size
+ && $arr[$i + 1]['type'] == 'alpha_reservedWord'
+ && strtoupper($arr[$i + 1]['data']) == 'OUTFILE'
+ ) {
+ $subresult['queryflags']['is_export'] = 1;
+ }
+ /**
+ * @todo set also to false if we find FOR UPDATE or LOCK IN SHARE MODE
+ */
+ if ($upper_data == 'SELECT') {
+ $in_select_expr = true;
+ $select_expr_clause = '';
+
+ // for the presence of SELECT COUNT
+ if (isset($subresult['queryflags']['select_from'])
+ && $subresult['queryflags']['select_from'] == 1
+ && !isset($subresult['queryflags']['is_group'])
+ && ($i + 1) < $size
+ && $arr[$i + 1]['type'] == 'alpha_functionName'
+ && strtoupper($arr[$i + 1]['data']) == 'COUNT'
+ ) {
+ $subresult['queryflags']['is_count'] = 1;
+ }
+ }
+
+ if ($upper_data == 'DISTINCT' && !$in_group_concat) {
+ $subresult['queryflags']['distinct'] = 1;
+ }
+
+ if ($upper_data == 'UNION') {
+ $subresult['queryflags']['union'] = 1;
+ }
+
+ if ($upper_data == 'JOIN') {
+ $subresult['queryflags']['join'] = 1;
+ }
+
+ if ($upper_data == 'OFFSET') {
+ $subresult['queryflags']['offset'] = 1;
+ }
+
+ // for the presence of CALL
+ if ($upper_data == 'CALL') {
+ $subresult['queryflags']['is_procedure'] = 1;
+ }
+
+ // if this is a real SELECT...FROM
+ if ($upper_data == 'FROM'
+ && isset($subresult['queryflags']['select_from'])
+ && $subresult['queryflags']['select_from'] == 1
+ ) {
+ $in_from = true;
+ $from_clause = '';
+ $in_select_expr = false;
+ }
+
+
+ // (we could have less resetting of variables to false
+ // if we trust that the query respects the standard
+ // MySQL order for clauses)
+
+ // we use $seen_group and $seen_order because we are looking
+ // for the BY
+ if ($upper_data == 'GROUP') {
+ $seen_group = true;
+ $seen_order = false;
+ $in_having = false;
+ $in_order_by = false;
+ $in_where = false;
+ $in_select_expr = false;
+ $in_from = false;
+
+ // for the presence of GROUP BY|HAVING|SELECT DISTINCT
+ if (isset($subresult['queryflags']['select_from'])
+ && $subresult['queryflags']['select_from'] == 1
+ && ($i + 1) < $size
+ && $arr[$i + 1]['type'] == 'alpha_reservedWord'
+ && in_array(strtoupper($arr[$i + 1]['data']), array("BY", "HAVING", "SELECT"))
+ && ($i + 2) < $size
+ && $arr[$i + 2]['type'] == 'alpha_reservedWord'
+ && strtoupper($arr[$i + 2]['data']) == 'DISTINCT'
+ ) {
+ $subresult['queryflags']['is_group'] = 1;
+ }
+ }
+ if ($upper_data == 'ORDER' && !$in_group_concat) {
+ $seen_order = true;
+ $seen_group = false;
+ $in_having = false;
+ $in_group_by = false;
+ $in_where = false;
+ $in_select_expr = false;
+ $in_from = false;
+ }
+ if ($upper_data == 'HAVING') {
+ $in_having = true;
+ $having_clause = '';
+ $seen_group = false;
+ $seen_order = false;
+ $in_group_by = false;
+ $in_order_by = false;
+ $in_where = false;
+ $in_select_expr = false;
+ $in_from = false;
+ }
+
+ if ($upper_data == 'WHERE') {
+ $in_where = true;
+ $where_clause = '';
+ $where_clause_identifiers = array();
+ $seen_group = false;
+ $seen_order = false;
+ $in_group_by = false;
+ $in_order_by = false;
+ $in_having = false;
+ $in_select_expr = false;
+ $in_from = false;
+ }
+
+ if ($upper_data == 'BY') {
+ if ($seen_group) {
+ $in_group_by = true;
+ $group_by_clause = '';
+ }
+ if ($seen_order) {
+ $seen_order_by = true;
+ // Here we assume that the ORDER BY keywords took
+ // exactly 8 characters.
+ // We use $GLOBALS['PMA_String']->substr() to be charset-safe; otherwise
+ // if the table name contains accents, the unsorted
+ // query would be missing some characters.
+ $unsorted_query = $GLOBALS['PMA_String']->substr(
+ $arr['raw'], 0, $arr[$i]['pos'] - 8
+ );
+ $in_order_by = true;
+ $order_by_clause = '';
+ }
+ }
+
+ // if we find one of the words that could end the clause
+ if (isset($words_ending_clauses[$upper_data])) {
+
+ $in_group_by = false;
+ $in_order_by = false;
+ $in_having = false;
+ $in_where = false;
+ $in_select_expr = false;
+ $in_from = false;
+ }
+
+ } // endif (reservedWord)
+
+ // do not add a space after a function name
+ /**
+ * @todo can we combine loop 2 and loop 1? some code is repeated here...
+ */
+
+ $sep = ' ';
+ if ($arr[$i]['type'] == 'alpha_functionName') {
+ $sep='';
+ $upper_data = strtoupper($arr[$i]['data']);
+ if ($upper_data =='GROUP_CONCAT') {
+ $in_group_concat = true;
+ $number_of_brackets_in_group_concat = 0;
+ }
+ }
+
+ if ($arr[$i]['type'] == 'punct_bracket_open_round') {
+ if ($in_group_concat) {
+ $number_of_brackets_in_group_concat++;
+ }
+ }
+ if ($arr[$i]['type'] == 'punct_bracket_close_round') {
+ if ($in_group_concat) {
+ $number_of_brackets_in_group_concat--;
+ if ($number_of_brackets_in_group_concat == 0) {
+ $in_group_concat = false;
+ }
+ }
+ }
+
+
+ // do not add a space after an identifier if followed by a dot
+ if ($arr[$i]['type'] == 'alpha_identifier'
+ && $i < $size - 1 && $arr[$i + 1]['data'] == '.'
+ ) {
+ $sep = '';
+ }
+
+ // do not add a space after a dot if followed by an identifier
+ if ($arr[$i]['data'] == '.' && $i < $size - 1
+ && $arr[$i + 1]['type'] == 'alpha_identifier'
+ ) {
+ $sep = '';
+ }
+
+ // for the presence of INSERT|LOAD DATA
+ if ($arr[$i]['type'] == 'alpha_identifier'
+ && strtoupper($arr[$i]['data']) == 'DATA'
+ && ($i - 1) >= 0
+ && $arr[$i - 1]['type'] == 'alpha_reservedWord'
+ && in_array(strtoupper($arr[$i - 1]['data']), array("INSERT", "LOAD"))
+ ) {
+ $subresult['queryflags']['is_insert'] = 1;
+ $subresult['queryflags']['is_affected'] = 1;
+ }
+
+ // for the presence of SUM|AVG|STD|STDDEV|MIN|MAX|BIT_OR|BIT_AND
+ if ($arr[$i]['type'] == 'alpha_functionName'
+ && in_array(strtoupper($arr[$i]['data']), array("SUM","AVG","STD","STDDEV","MIN","MAX","BIT_OR","BIT_AND"))
+ && isset($subresult['queryflags']['select_from'])
+ && $subresult['queryflags']['select_from'] == 1
+ && !isset($subresult['queryflags']['is_group'])
+ ) {
+ $subresult['queryflags']['is_func'] = 1;
+ }
+
+ if ($in_select_expr && $upper_data != 'SELECT'
+ && $upper_data != 'DISTINCT'
+ ) {
+ $select_expr_clause .= $arr[$i]['data'] . $sep;
+ }
+ if ($in_from && $upper_data != 'FROM') {
+ $from_clause .= $arr[$i]['data'] . $sep;
+ }
+ if ($in_group_by && $upper_data != 'GROUP' && $upper_data != 'BY') {
+ $group_by_clause .= $arr[$i]['data'] . $sep;
+ }
+ if ($in_order_by && $upper_data != 'ORDER' && $upper_data != 'BY') {
+ // add a space only before ASC or DESC
+ // not around the dot between dbname and tablename
+ if ($arr[$i]['type'] == 'alpha_reservedWord') {
+ $order_by_clause .= $sep;
+ }
+ $order_by_clause .= $arr[$i]['data'];
+ }
+ if ($in_having && $upper_data != 'HAVING') {
+ $having_clause .= $arr[$i]['data'] . $sep;
+ }
+ if ($in_where && $upper_data != 'WHERE') {
+ $where_clause .= $arr[$i]['data'] . $sep;
+
+ if (($arr[$i]['type'] == 'quote_backtick')
+ || ($arr[$i]['type'] == 'alpha_identifier')
+ ) {
+ $where_clause_identifiers[] = $arr[$i]['data'];
+ }
+ }
+
+ // to grab the rest of the query after the ORDER BY clause
+ if (isset($subresult['queryflags']['select_from'])
+ && $subresult['queryflags']['select_from'] == 1
+ && ! $in_order_by
+ && $seen_order_by
+ && $upper_data != 'BY'
+ ) {
+ $unsorted_query .= $arr[$i]['data'];
+ if ($arr[$i]['type'] != 'punct_bracket_open_round'
+ && $arr[$i]['type'] != 'punct_bracket_close_round'
+ && $arr[$i]['type'] != 'punct'
+ ) {
+ $unsorted_query .= $sep;
+ }
+ }
+
+ if ($in_limit) {
+ if ($upper_data == 'OFFSET') {
+ $limit_clause .= $sep;
+ }
+ $limit_clause .= $arr[$i]['data'];
+ if ($upper_data == 'LIMIT' || $upper_data == 'OFFSET') {
+ $limit_clause .= $sep;
+ }
+ }
+ if ($after_limit && $seen_limit) {
+ $section_after_limit .= $arr[$i]['data'] . $sep;
+ }
+
+ // clear $upper_data for next iteration
+ $upper_data='';
+ } // end for $i (loop #2)
+ if (empty($section_before_limit)) {
+ $section_before_limit = $arr['raw'];
+ }
+
+ // -----------------------------------------------------
+ // loop #3: foreign keys and MySQL 4.1.2+ TIMESTAMP options
+ // (for now, check only the first query)
+ // (for now, identifiers are assumed to be backquoted)
+
+ // If we find that we are dealing with a CREATE TABLE query,
+ // we look for the next punct_bracket_open_round, which
+ // introduces the fields list. Then, when we find a
+ // quote_backtick, it must be a field, so we put it into
+ // the create_table_fields array. Even if this field is
+ // not a timestamp, it will be useful when logic has been
+ // added for complete field attributes analysis.
+
+ $seen_foreign = false;
+ $seen_references = false;
+ $seen_constraint = false;
+ $foreign_key_number = -1;
+ $seen_create_table = false;
+ $seen_create = false;
+ $seen_alter = false;
+ $in_create_table_fields = false;
+ $brackets_level = 0;
+ $in_timestamp_options = false;
+ $seen_default = false;
+
+ for ($i = 0; $i < $size; $i++) {
+ if ($arr[$i]['type'] == 'alpha_reservedWord') {
+ $upper_data = strtoupper($arr[$i]['data']);
+
+ if ($upper_data == 'NOT' && $in_timestamp_options) {
+ $create_table_fields[$current_identifier]['timestamp_not_null'] = true;
+
+ }
+
+ if ($upper_data == 'CREATE') {
+ $seen_create = true;
+ }
+
+ if ($upper_data == 'ALTER') {
+ $seen_alter = true;
+ }
+
+ if ($upper_data == 'TABLE' && $seen_create) {
+ $seen_create_table = true;
+ $create_table_fields = array();
+ }
+
+ if ($upper_data == 'CURRENT_TIMESTAMP') {
+ if ($in_timestamp_options) {
+ if ($seen_default) {
+ $create_table_fields[$current_identifier]['default_current_timestamp'] = true;
+ }
+ }
+ }
+
+ if ($upper_data == 'CONSTRAINT') {
+ $foreign_key_number++;
+ $seen_foreign = false;
+ $seen_references = false;
+ $seen_constraint = true;
+ }
+ if ($upper_data == 'FOREIGN') {
+ $seen_foreign = true;
+ $seen_references = false;
+ $seen_constraint = false;
+ }
+ if ($upper_data == 'REFERENCES') {
+ $seen_foreign = false;
+ $seen_references = true;
+ $seen_constraint = false;
+ }
+
+
+ // Cases covered:
+
+ // [ON DELETE {CASCADE | SET NULL | NO ACTION | RESTRICT}]
+ // [ON UPDATE {CASCADE | SET NULL | NO ACTION | RESTRICT}]
+
+ // but we set ['on_delete'] or ['on_cascade'] to
+ // CASCADE | SET_NULL | NO_ACTION | RESTRICT
+
+ // ON UPDATE CURRENT_TIMESTAMP
+
+ if ($upper_data == 'ON') {
+ if (isset($arr[$i+1]) && $arr[$i+1]['type'] == 'alpha_reservedWord') {
+ $second_upper_data = strtoupper($arr[$i+1]['data']);
+ if ($second_upper_data == 'DELETE') {
+ $clause = 'on_delete';
+ }
+ if ($second_upper_data == 'UPDATE') {
+ $clause = 'on_update';
+ }
+ // ugly workaround because currently, NO is not
+ // in the list of reserved words in sqlparser.data
+ // (we got a bug report about not being able to use
+ // 'no' as an identifier)
+ if (isset($clause)
+ && ($arr[$i+2]['type'] == 'alpha_reservedWord'
+ || ($arr[$i+2]['type'] == 'alpha_identifier'
+ && strtoupper($arr[$i+2]['data'])=='NO'))
+ ) {
+ $third_upper_data = strtoupper($arr[$i+2]['data']);
+ if ($third_upper_data == 'CASCADE'
+ || $third_upper_data == 'RESTRICT'
+ ) {
+ $value = $third_upper_data;
+ } elseif ($third_upper_data == 'SET'
+ || $third_upper_data == 'NO'
+ ) {
+ if ($arr[$i+3]['type'] == 'alpha_reservedWord') {
+ $value = $third_upper_data . '_'
+ . strtoupper($arr[$i+3]['data']);
+ }
+ } elseif ($third_upper_data == 'CURRENT_TIMESTAMP') {
+ if ($clause == 'on_update'
+ && $in_timestamp_options
+ ) {
+ $create_table_fields[$current_identifier]['on_update_current_timestamp'] = true;
+ $seen_default = false;
+ }
+
+ } else {
+ $value = '';
+ }
+ if (!empty($value)) {
+ $foreign[$foreign_key_number][$clause] = $value;
+ }
+ unset($clause);
+ } // endif (isset($clause))
+ }
+ }
+
+ } // end of reserved words analysis
+
+
+ if ($arr[$i]['type'] == 'punct_bracket_open_round') {
+ $brackets_level++;
+ if ($seen_create_table && $brackets_level == 1) {
+ $in_create_table_fields = true;
+ }
+ }
+
+
+ if ($arr[$i]['type'] == 'punct_bracket_close_round') {
+ $brackets_level--;
+ if ($seen_references) {
+ $seen_references = false;
+ }
+ if ($seen_create_table && $brackets_level == 0) {
+ $in_create_table_fields = false;
+ }
+ }
+
+ if (($arr[$i]['type'] == 'alpha_columnAttrib')) {
+ $upper_data = strtoupper($arr[$i]['data']);
+ if ($seen_create_table && $in_create_table_fields) {
+ if ($upper_data == 'DEFAULT') {
+ $seen_default = true;
+ $create_table_fields[$current_identifier]['default_value'] = $arr[$i + 1]['data'];
+ }
+ }
+ }
+
+ /**
+ * @see @todo 2005-10-16 note: the "or" part here is a workaround for a bug
+ */
+ if (($arr[$i]['type'] == 'alpha_columnType')
+ || ($arr[$i]['type'] == 'alpha_functionName' && $seen_create_table)
+ ) {
+ $upper_data = strtoupper($arr[$i]['data']);
+ if ($seen_create_table && $in_create_table_fields
+ && isset($current_identifier)
+ ) {
+ $create_table_fields[$current_identifier]['type'] = $upper_data;
+ if ($upper_data == 'TIMESTAMP') {
+ $arr[$i]['type'] = 'alpha_columnType';
+ $in_timestamp_options = true;
+ } else {
+ $in_timestamp_options = false;
+ if ($upper_data == 'CHAR') {
+ $arr[$i]['type'] = 'alpha_columnType';
+ }
+ }
+ }
+ }
+
+
+ if ($arr[$i]['type'] == 'quote_backtick'
+ || $arr[$i]['type'] == 'alpha_identifier'
+ ) {
+
+ if ($arr[$i]['type'] == 'quote_backtick') {
+ // remove backquotes
+ $identifier = PMA_Util::unQuote($arr[$i]['data']);
+ } else {
+ $identifier = $arr[$i]['data'];
+ }
+
+ if ($seen_create_table && $in_create_table_fields) {
+ $current_identifier = $identifier;
+ // we set this one even for non TIMESTAMP type
+ $create_table_fields[$current_identifier]['timestamp_not_null'] = false;
+ }
+
+ if ($seen_constraint) {
+ $foreign[$foreign_key_number]['constraint'] = $identifier;
+ }
+
+ if ($seen_foreign && $brackets_level > 0) {
+ $foreign[$foreign_key_number]['index_list'][] = $identifier;
+ }
+
+ if ($seen_references) {
+ if ($seen_alter && $brackets_level > 0) {
+ $foreign[$foreign_key_number]['ref_index_list'][] = $identifier;
+ // here, the first bracket level corresponds to the
+ // bracket of CREATE TABLE
+ // so if we are on level 2, it must be the index list
+ // of the foreign key REFERENCES
+ } elseif ($brackets_level > 1) {
+ $foreign[$foreign_key_number]['ref_index_list'][] = $identifier;
+ } elseif ($arr[$i+1]['type'] == 'punct_qualifier') {
+ // identifier is `db`.`table`
+ // the first pass will pick the db name
+ // the next pass will pick the table name
+ $foreign[$foreign_key_number]['ref_db_name'] = $identifier;
+ } else {
+ // identifier is `table`
+ $foreign[$foreign_key_number]['ref_table_name'] = $identifier;
+ }
+ }
+ }
+ } // end for $i (loop #3)
+
+
+ // Fill the $subresult array
+
+ if (isset($create_table_fields)) {
+ $subresult['create_table_fields'] = $create_table_fields;
+ }
+
+ if (isset($foreign)) {
+ $subresult['foreign_keys'] = $foreign;
+ }
+
+ if (isset($select_expr_clause)) {
+ $subresult['select_expr_clause'] = $select_expr_clause;
+ }
+ if (isset($from_clause)) {
+ $subresult['from_clause'] = $from_clause;
+ }
+ if (isset($group_by_clause)) {
+ $subresult['group_by_clause'] = $group_by_clause;
+ }
+ if (isset($order_by_clause)) {
+ $subresult['order_by_clause'] = $order_by_clause;
+ }
+ if (isset($having_clause)) {
+ $subresult['having_clause'] = $having_clause;
+ }
+ if (isset($limit_clause)) {
+ $subresult['limit_clause'] = $limit_clause;
+ }
+ if (isset($where_clause)) {
+ $subresult['where_clause'] = $where_clause;
+ }
+ if (isset($unsorted_query) && !empty($unsorted_query)) {
+ $subresult['unsorted_query'] = $unsorted_query;
+ }
+ if (isset($where_clause_identifiers)) {
+ $subresult['where_clause_identifiers'] = $where_clause_identifiers;
+ }
+
+ if (isset($position_of_first_select)) {
+ $subresult['position_of_first_select'] = $position_of_first_select;
+ $subresult['section_before_limit'] = $section_before_limit;
+ $subresult['section_after_limit'] = $section_after_limit;
+ }
+
+ // They are naughty and didn't have a trailing semi-colon,
+ // then still handle it properly
+ if ($subresult['querytype'] != '') {
+ $result[] = $subresult;
+ }
+ return $result;
+} // end of the "PMA_SQP_analyze()" function
+
+
+/**
+ * Formats SQL queries
+ *
+ * @param array $arr The SQL queries
+ * @param string $mode formatting mode
+ * @param integer $start_token starting token
+ * @param integer $number_of_tokens number of tokens to format, -1 = all
+ *
+ * @return string The formatted SQL queries
+ *
+ * @access public
+ */
+function PMA_SQP_format(
+ $arr, $mode='text', $start_token=0,
+ $number_of_tokens=-1
+) {
+ //DEBUG echo 'in Format<pre>'; print_r($arr); echo '</pre>';
+ // then check for an array
+ if (! is_array($arr)) {
+ return htmlspecialchars($arr);
+ }
+ // first check for the SQL parser having hit an error
+ if (PMA_SQP_isError()) {
+ return htmlspecialchars($arr['raw']);
+ }
+ // else do it properly
+ switch ($mode) {
+ case 'query_only':
+ $str = '';
+ $html_line_break = "\n";
+ break;
+ case 'text':
+ $str = '';
+ $html_line_break = '<br />';
+ break;
+ } // end switch
+ $indent = 0;
+ $bracketlevel = 0;
+ $functionlevel = 0;
+ $infunction = false;
+ $space_punct_listsep = ' ';
+ $space_punct_listsep_function_name = ' ';
+ // $space_alpha_reserved_word = '<br />'."\n";
+ $space_alpha_reserved_word = ' ';
+
+ $keywords_with_brackets_1before = array(
+ 'INDEX' => 1,
+ 'KEY' => 1,
+ 'ON' => 1,
+ 'USING' => 1
+ );
+
+ $keywords_with_brackets_2before = array(
+ 'IGNORE' => 1,
+ 'INDEX' => 1,
+ 'INTO' => 1,
+ 'KEY' => 1,
+ 'PRIMARY' => 1,
+ 'PROCEDURE' => 1,
+ 'REFERENCES' => 1,
+ 'UNIQUE' => 1,
+ 'USE' => 1
+ );
+
+ // These reserved words do NOT get a newline placed near them.
+ $keywords_no_newline = array(
+ 'AS' => 1,
+ 'ASC' => 1,
+ 'DESC' => 1,
+ 'DISTINCT' => 1,
+ 'DUPLICATE' => 1,
+ 'HOUR' => 1,
+ 'INTERVAL' => 1,
+ 'IS' => 1,
+ 'LIKE' => 1,
+ 'NOT' => 1,
+ 'NULL' => 1,
+ 'ON' => 1,
+ 'REGEXP' => 1
+ );
+
+ // These reserved words introduce a privilege list
+ $keywords_priv_list = array(
+ 'GRANT' => 1,
+ 'REVOKE' => 1
+ );
+
+ if ($number_of_tokens == -1) {
+ $number_of_tokens = $arr['len'];
+ }
+ $typearr = array();
+ if ($number_of_tokens >= 0) {
+ $typearr[0] = '';
+ $typearr[1] = '';
+ $typearr[2] = '';
+ $typearr[3] = $arr[$start_token]['type'];
+ }
+
+ $in_priv_list = false;
+ for ($i = $start_token; $i < $number_of_tokens; $i++) {
+ // DEBUG echo "Loop format <strong>" . $arr[$i]['data']
+ // . "</strong> " . $arr[$i]['type'] . "<br />";
+ $before = '';
+ $after = '';
+ // array_shift($typearr);
+ /*
+ 0 prev2
+ 1 prev
+ 2 current
+ 3 next
+ */
+ if (($i + 1) < $number_of_tokens) {
+ $typearr[4] = $arr[$i + 1]['type'];
+ } else {
+ $typearr[4] = '';
+ }
+
+ for ($j=0; $j<4; $j++) {
+ $typearr[$j] = $typearr[$j + 1];
+ }
+
+ switch ($typearr[2]) {
+ case 'alpha_bitfield_constant_introducer':
+ $before = ' ';
+ $after = '';
+ break;
+ case 'white_newline':
+ $before = '';
+ break;
+ case 'punct_bracket_open_round':
+ $bracketlevel++;
+ $infunction = false;
+ $keyword_brackets_2before = isset(
+ $keywords_with_brackets_2before[strtoupper($arr[$i - 2]['data'])]
+ );
+ $keyword_brackets_1before = isset(
+ $keywords_with_brackets_1before[strtoupper($arr[$i - 1]['data'])]
+ );
+ // Make sure this array is sorted!
+ if (($typearr[1] == 'alpha_functionName')
+ || ($typearr[1] == 'alpha_columnType') || ($typearr[1] == 'punct')
+ || ($typearr[3] == 'digit_integer') || ($typearr[3] == 'digit_hex')
+ || ($typearr[3] == 'digit_float')
+ || ($typearr[0] == 'alpha_reservedWord' && $keyword_brackets_2before)
+ || ($typearr[1] == 'alpha_reservedWord' && $keyword_brackets_1before)
+ ) {
+ $functionlevel++;
+ $infunction = true;
+ $after .= ' ';
+ } else {
+ $indent++;
+ if ($mode != 'query_only') {
+ $after .= '<div class="syntax_indent' . $indent . '">';
+ } else {
+ $after .= ' ';
+ }
+ }
+ break;
+ case 'alpha_identifier':
+ if (($typearr[1] == 'punct_qualifier')
+ || ($typearr[3] == 'punct_qualifier')
+ ) {
+ $after = '';
+ $before = '';
+ }
+ // for example SELECT 1 somealias
+ if ($typearr[1] == 'digit_integer') {
+ $before = ' ';
+ }
+ if (($typearr[3] == 'alpha_columnType')
+ || ($typearr[3] == 'alpha_identifier')
+ ) {
+ $after .= ' ';
+ }
+ break;
+ case 'punct_user':
+ case 'punct_qualifier':
+ $before = '';
+ $after = '';
+ break;
+ case 'punct_listsep':
+ if ($infunction == true) {
+ $after .= $space_punct_listsep_function_name;
+ } else {
+ $after .= $space_punct_listsep;
+ }
+ break;
+ case 'punct_queryend':
+ if (($typearr[3] != 'comment_mysql')
+ && ($typearr[3] != 'comment_ansi')
+ && $typearr[3] != 'comment_c'
+ ) {
+ $after .= $html_line_break;
+ $after .= $html_line_break;
+ }
+ $space_punct_listsep = ' ';
+ $space_punct_listsep_function_name = ' ';
+ $space_alpha_reserved_word = ' ';
+ $in_priv_list = false;
+ break;
+ case 'comment_mysql':
+ case 'comment_ansi':
+ $after .= $html_line_break;
+ break;
+ case 'punct':
+ $before .= ' ';
+
+ // workaround for
+ // select * from mytable limit 0,-1
+ // (a side effect of this workaround is that
+ // select 20 - 9
+ // becomes
+ // select 20 -9
+ // )
+ if ($typearr[3] != 'digit_integer') {
+ $after .= ' ';
+ }
+ break;
+ case 'punct_bracket_close_round':
+ // only close bracket level when it was opened before
+ if ($bracketlevel > 0) {
+ $bracketlevel--;
+ if ($infunction == true) {
+ $functionlevel--;
+ $after .= ' ';
+ $before .= ' ';
+ } else {
+ $indent--;
+ $before .= ($mode != 'query_only' ? '</div>' : ' ');
+ }
+ $infunction = ($functionlevel > 0) ? true : false;
+ }
+ break;
+ case 'alpha_columnType':
+ if ($typearr[3] == 'alpha_columnAttrib') {
+ $after .= ' ';
+ }
+ if ($typearr[1] == 'alpha_columnType') {
+ $before .= ' ';
+ }
+ break;
+ case 'alpha_columnAttrib':
+
+ // ALTER TABLE tbl_name AUTO_INCREMENT = 1
+ // COLLATE LATIN1_GENERAL_CI DEFAULT
+ if ($typearr[1] == 'alpha_identifier'
+ || $typearr[1] == 'alpha_charset'
+ ) {
+ $before .= ' ';
+ }
+ if (($typearr[3] == 'alpha_columnAttrib')
+ || ($typearr[3] == 'quote_single')
+ || ($typearr[3] == 'digit_integer')
+ ) {
+ $after .= ' ';
+ }
+ // workaround for
+ // AUTO_INCREMENT = 31DEFAULT_CHARSET = utf-8
+
+ if ($typearr[2] == 'alpha_columnAttrib'
+ && $typearr[3] == 'alpha_reservedWord'
+ ) {
+ $before .= ' ';
+ }
+ // workaround for
+ // select * from mysql.user where binary user="root"
+ // binary is marked as alpha_columnAttrib
+ // but should be marked as a reserved word
+ if (strtoupper($arr[$i]['data']) == 'BINARY'
+ && $typearr[3] == 'alpha_identifier'
+ ) {
+ $after .= ' ';
+ }
+ break;
+ case 'alpha_functionName':
+ break;
+ case 'alpha_reservedWord':
+ // do not uppercase the reserved word if we are calling
+ // this function in query_only mode, because we need
+ // the original query (otherwise we get problems with
+ // semi-reserved words like "storage" which is legal
+ // as an identifier name)
+
+ if ($mode != 'query_only') {
+ $arr[$i]['data'] = strtoupper($arr[$i]['data']);
+ }
+
+ if ((($typearr[1] != 'alpha_reservedWord')
+ || (($typearr[1] == 'alpha_reservedWord')
+ && isset($keywords_no_newline[strtoupper($arr[$i - 1]['data'])])))
+ && ($typearr[1] != 'punct_level_plus')
+ && (!isset($keywords_no_newline[$arr[$i]['data']]))
+ ) {
+ // do not put a space before the first token, because
+ // we use a lot of pattern matching checking for the
+ // first reserved word at beginning of query
+ // so do not put a newline before
+ //
+ // also we must not be inside a privilege list
+ if ($i > 0) {
+ // the alpha_identifier exception is there to
+ // catch cases like
+ // GRANT SELECT ON mydb.mytable TO myuser@localhost
+ // (else, we get mydb.mytableTO)
+ //
+ // the quote_single exception is there to
+ // catch cases like
+ // GRANT ... TO 'marc'@'domain.com' IDENTIFIED...
+ /**
+ * @todo fix all cases and find why this happens
+ */
+
+ if (!$in_priv_list
+ || $typearr[1] == 'alpha_identifier'
+ || $typearr[1] == 'quote_single'
+ || $typearr[1] == 'white_newline'
+ ) {
+ $before .= $space_alpha_reserved_word;
+ }
+ } else {
+ // on first keyword, check if it introduces a
+ // privilege list
+ if (isset($keywords_priv_list[$arr[$i]['data']])) {
+ $in_priv_list = true;
+ }
+ }
+ } else {
+ $before .= ' ';
+ }
+
+ switch ($arr[$i]['data']) {
+ case 'CREATE':
+ case 'ALTER':
+ case 'DROP':
+ case 'RENAME';
+ case 'TRUNCATE':
+ case 'ANALYZE':
+ case 'ANALYSE':
+ case 'OPTIMIZE':
+ if (!$in_priv_list) {
+ $space_punct_listsep = $html_line_break;
+ $space_alpha_reserved_word = ' ';
+ }
+ break;
+ case 'EVENT':
+ case 'TABLESPACE':
+ case 'TABLE':
+ case 'FUNCTION':
+ case 'INDEX':
+ case 'PROCEDURE':
+ case 'SERVER':
+ case 'TRIGGER':
+ case 'DATABASE':
+ case 'VIEW':
+ case 'GROUP':
+ break;
+ case 'SET':
+ if (!$in_priv_list) {
+ $space_punct_listsep = $html_line_break;
+ $space_alpha_reserved_word = ' ';
+ }
+ break;
+ case 'EXPLAIN':
+ case 'DESCRIBE':
+ case 'DELETE':
+ case 'SHOW':
+ case 'UPDATE':
+ if (!$in_priv_list) {
+ $space_punct_listsep = $html_line_break;
+ $space_alpha_reserved_word = ' ';
+ }
+ break;
+ case 'INSERT':
+ case 'REPLACE':
+ if (!$in_priv_list) {
+ $space_punct_listsep = $html_line_break;
+ $space_alpha_reserved_word = $html_line_break;
+ }
+ break;
+ case 'VALUES':
+ $space_punct_listsep = ' ';
+ $space_alpha_reserved_word = $html_line_break;
+ break;
+ case 'SELECT':
+ $space_punct_listsep = ' ';
+ $space_alpha_reserved_word = $html_line_break;
+ break;
+ case 'CALL':
+ case 'DO':
+ case 'HANDLER':
+ break;
+ default:
+ break;
+ } // end switch ($arr[$i]['data'])
+
+ $after .= ' ';
+ break;
+ case 'digit_integer':
+ case 'digit_float':
+ case 'digit_hex':
+ /**
+ * @todo could there be other types preceding a digit?
+ */
+ if ($typearr[1] == 'alpha_reservedWord') {
+ $after .= ' ';
+ }
+ if ($infunction && $typearr[3] == 'punct_bracket_close_round') {
+ $after .= ' ';
+ }
+ if ($typearr[1] == 'alpha_columnAttrib') {
+ $before .= ' ';
+ }
+ break;
+ case 'alpha_variable':
+ $after = ' ';
+ break;
+ case 'quote_double':
+ case 'quote_single':
+ // workaround: for the query
+ // REVOKE SELECT ON `base2\_db`.* FROM 'user'@'%'
+ // the @ is incorrectly marked as alpha_variable
+ // in the parser, and here, the '%' gets a blank before,
+ // which is a syntax error
+ if ($typearr[1] != 'punct_user'
+ && $typearr[1] != 'alpha_bitfield_constant_introducer'
+ ) {
+ $before .= ' ';
+ }
+ if ($infunction && $typearr[3] == 'punct_bracket_close_round') {
+ $after .= ' ';
+ }
+ break;
+ case 'quote_backtick':
+ // here we check for punct_user to handle correctly
+ // DEFINER = `username`@`%`
+ // where @ is the punct_user and `%` is the quote_backtick
+ if ($typearr[3] != 'punct_qualifier'
+ && $typearr[3] != 'alpha_variable'
+ && $typearr[3] != 'punct_user'
+ ) {
+ $after .= ' ';
+ }
+ if ($typearr[1] != 'punct_qualifier'
+ && $typearr[1] != 'alpha_variable'
+ && $typearr[1] != 'punct_user'
+ ) {
+ $before .= ' ';
+ }
+ break;
+ default:
+ break;
+ } // end switch ($typearr[2])
+
+ /*
+ if ($typearr[3] != 'punct_qualifier') {
+ $after .= ' ';
+ }
+ $after .= "\n";
+ */
+ $str .= $before;
+ if ($mode == 'text') {
+ $str .= htmlspecialchars($arr[$i]['data']);
+ } else {
+ $str .= $arr[$i]['data'];
+ }
+ $str .= $after;
+ } // end for
+ // close unclosed indent levels
+ while ($indent > 0) {
+ $indent--;
+ $str .= ($mode != 'query_only' ? '</div>' : ' ');
+ }
+
+ return $str;
+} // end of the "PMA_SQP_format()" function
+
+/**
+ * Gets SQL queries with no format
+ *
+ * @param array $arr The SQL queries list
+ *
+ * @return string The SQL queries with no format
+ *
+ * @access public
+ */
+function PMA_SQP_formatNone($arr)
+{
+ $formatted_sql = htmlspecialchars($arr['raw']);
+ $formatted_sql = preg_replace(
+ "@((\015\012)|(\015)|(\012)){3,}@",
+ "\n\n",
+ $formatted_sql
+ );
+
+ return $formatted_sql;
+} // end of the "PMA_SQP_formatNone()" function
+
+/**
+ * Checks whether a given name is MySQL reserved word
+ *
+ * @param string $column The word to be checked
+ *
+ * @return boolean whether true or false
+ */
+function PMA_SQP_isKeyWord($column)
+{
+ global $PMA_SQPdata_forbidden_word;
+ return in_array(strtoupper($column), $PMA_SQPdata_forbidden_word);
+}
+
+
+/**
+ * Get Parser Data Map from sqlparser.data.php
+ *
+ * @return Array Parser Data Map from sqlparser.data.php
+ */
+function PMA_SQP_getParserDataMap()
+{
+ include 'libraries/sqlparser.data.php';
+ return array(
+ 'PMA_SQPdata_function_name' => $PMA_SQPdata_function_name,
+ 'PMA_SQPdata_column_attrib' => $PMA_SQPdata_column_attrib,
+ 'PMA_SQPdata_reserved_word' => $PMA_SQPdata_reserved_word,
+ 'PMA_SQPdata_forbidden_word' => $PMA_SQPdata_forbidden_word,
+ 'PMA_SQPdata_column_type' => $PMA_SQPdata_column_type,
+ );
+}
+/**
+ * Get Parser analyze Map from parse_analyze_inc.php
+ *
+ * @param array $sql_query The SQL string
+ * @param array $db Current DB
+ *
+ * @return Array analyze Map from parse_analyze_inc.php
+ */
+function PMA_SQP_getParserAnalyzeMap($sql_query, $db)
+{
+ include 'libraries/parse_analyze.inc.php';
+ return $analyzed_sql_results;
+}
+
+?>
diff --git a/libraries/sqlvalidator.class.php b/libraries/sqlvalidator.class.php
new file mode 100644
index 0000000000..9efab5cf63
--- /dev/null
+++ b/libraries/sqlvalidator.class.php
@@ -0,0 +1,457 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * PHP interface to MimerSQL Validator
+ *
+ * Copyright 2002, 2003 Robin Johnson <robbat2@users.sourceforge.net>
+ * http://www.orbis-terrarum.net/?l=people.robbat2
+ *
+ * All data is transported over HTTP-SOAP
+ * And uses either the PEAR SOAP Module or PHP SOAP extension
+ *
+ * Install instructions for PEAR SOAP:
+ * Make sure you have a really recent PHP with PEAR support
+ * run this: "pear install Mail_Mime Net_DIME SOAP"
+ *
+ * @access public
+ *
+ * @package PhpMyAdmin
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ * Load SOAP client.
+ */
+if (class_exists('SOAPClient')) {
+ $GLOBALS['sqlvalidator_error'] = false;
+ $GLOBALS['sqlvalidator_soap'] = 'PHP';
+} else {
+ @include_once 'SOAP/Client.php';
+ if (class_exists('SOAP_Client')) {
+ $GLOBALS['sqlvalidator_soap'] = 'PEAR';
+ $GLOBALS['sqlvalidator_error'] = false;
+ } else {
+ $GLOBALS['sqlvalidator_soap'] = 'NONE';
+ $GLOBALS['sqlvalidator_error'] = true;
+ PMA_warnMissingExtension('soap');
+ }
+}
+
+if (!$GLOBALS['sqlvalidator_error']) {
+ // Ok, we have SOAP Support, so let's use it!
+
+ /**
+ * @package PhpMyAdmin
+ */
+ class PMA_SQLValidator
+ {
+ var $url;
+ var $service_name;
+ var $wsdl;
+ var $output_type;
+
+ var $username;
+ var $password;
+ var $calling_program;
+ var $calling_program_version;
+ var $target_dbms;
+ var $target_dbms_version;
+ var $connectionTechnology;
+ var $connection_technology_version;
+ var $interactive;
+
+ var $service_link = null;
+ var $session_data = null;
+
+
+ /**
+ * Private functions - You don't need to mess with these
+ */
+
+ /**
+ * Service opening
+ *
+ * @param string $url URL of Mimer SQL Validator WSDL file
+ *
+ * @return object Object to use
+ *
+ * @access private
+ */
+ function _openService($url)
+ {
+ if ($GLOBALS['sqlvalidator_soap'] == 'PHP') {
+ $obj = new SOAPClient($url);
+ } else {
+ $obj = new SOAP_Client($url, true);
+ }
+ return $obj;
+ } // end of the "openService()" function
+
+
+ /**
+ * Service initializer to connect to server
+ *
+ * @param object $obj Service object
+ * @param string $username Username
+ * @param string $password Password
+ * @param string $calling_program Name of calling program
+ * @param string $calling_program_version Version of calling program
+ * @param string $target_dbms Target DBMS
+ * @param string $target_dbms_version Version of target DBMS
+ * @param string $connection_technology Connection Technology
+ * @param string $connection_technology_version Con. Technology version
+ * @param integer $interactive boolean 1/0 to specify if
+ * we are an interactive system
+ *
+ * @return object stdClass return object with data
+ *
+ * @access private
+ */
+ function _openSession($obj, $username, $password, $calling_program,
+ $calling_program_version, $target_dbms, $target_dbms_version,
+ $connection_technology, $connection_technology_version, $interactive
+ ) {
+ $use_array = array(
+ "a_userName" => $username,
+ "a_password" => $password,
+ "a_callingProgram" => $calling_program,
+ "a_callingProgramVersion" => $calling_program_version,
+ "a_targetDbms" => $target_dbms,
+ "a_targetDbmsVersion" => $target_dbms_version,
+ "a_connectionTechnology" => $connection_technology,
+ "a_connectionTechnologyVersion" => $connection_technology_version,
+ "a_interactive" => $interactive,
+ );
+
+ if ($GLOBALS['sqlvalidator_soap'] == 'PHP') {
+ $ret = $obj->__soapCall("openSession", $use_array);
+ } else {
+ $ret = $obj->call("openSession", $use_array);
+ }
+
+ return $ret;
+ } // end of the "_openSession()" function
+
+
+ /**
+ * Validator sytem call
+ *
+ * @param object $obj Service object
+ * @param object $session Session object
+ * @param string $sql SQL Query to validate
+ * @param string $method Data return type
+ *
+ * @return object stClass return with data
+ *
+ * @access private
+ */
+ function _validateSQL($obj, $session, $sql, $method)
+ {
+ $use_array = array(
+ "a_sessionId" => $session->sessionId,
+ "a_sessionKey" => $session->sessionKey,
+ "a_SQL" => $sql,
+ "a_resultType" => $this->output_type,
+ );
+
+ if ($GLOBALS['sqlvalidator_soap'] == 'PHP') {
+ $res = $obj->__soapCall("validateSQL", $use_array);
+ } else {
+ $res = $obj->call("validateSQL", $use_array);
+ }
+
+ return $res;
+ } // end of the "validateSQL()" function
+
+
+ /**
+ * Validator sytem call
+ *
+ * @param string $sql SQL Query to validate
+ *
+ * @return object stdClass return with data
+ *
+ * @access private
+ *
+ * @see validateSQL()
+ */
+ function _validate($sql)
+ {
+ $ret = $this->_validateSQL(
+ $this->service_link, $this->session_data, $sql, $this->output_type
+ );
+ return $ret;
+ } // end of the "validate()" function
+
+
+ /**
+ * Public functions
+ */
+
+ /**
+ * Constructor
+ *
+ * @access public
+ */
+ function __construct()
+ {
+ $this->url = 'http://sqlvalidator.mimer.com/v1/services';
+ $this->service_name = 'SQL99Validator';
+ $this->wsdl = '?wsdl';
+
+ $this->output_type = 'html';
+
+ $this->username = 'anonymous';
+ $this->password = '';
+ $this->calling_program = 'PHP_SQLValidator';
+ $this->calling_program_version = PMA_VERSION;
+ $this->target_dbms = 'N/A';
+ $this->target_dbms_version = 'N/A';
+ $this->connection_technology = 'PHP';
+ $this->connection_technology_version = phpversion();
+ $this->interactive = 1;
+
+ $this->service_link = null;
+ $this->session_data = null;
+ } // end of the "PMA_SQLValidator()" function
+
+
+ /**
+ * Sets credentials
+ *
+ * @param string $username the username
+ * @param string $password the password
+ *
+ * @return void
+ * @access public
+ */
+ function setCredentials($username, $password)
+ {
+ $this->username = $username;
+ $this->password = $password;
+ } // end of the "setCredentials()" function
+
+
+ /**
+ * Sets the calling program
+ *
+ * @param string $calling_program the calling program name
+ * @param string $calling_program_version the calling program revision
+ *
+ * @return void
+ * @access public
+ */
+ function setCallingProgram($calling_program, $calling_program_version)
+ {
+ $this->calling_program = $calling_program;
+ $this->calling_program_version = $calling_program_version;
+ } // end of the "setCallingProgram()" function
+
+
+ /**
+ * Appends the calling program
+ *
+ * @param string $calling_program the calling program name
+ * @param string $calling_program_version the calling program revision
+ *
+ * @return void
+ * @access public
+ */
+ function appendCallingProgram($calling_program, $calling_program_version)
+ {
+ $this->calling_program .= ' - ' . $calling_program;
+ $this->calling_program_version .= ' - ' . $calling_program_version;
+ } // end of the "appendCallingProgram()" function
+
+
+ /**
+ * Sets the target DBMS
+ *
+ * @param string $target_dbms the target DBMS name
+ * @param string $target_dbms_version the target DBMS revision
+ *
+ * @return void
+ * @access public
+ */
+ function setTargetDbms($target_dbms, $target_dbms_version)
+ {
+ $this->target_dbms = $target_dbms;
+ $this->target_dbms_version = $target_dbms_version;
+ } // end of the "setTargetDbms()" function
+
+
+ /**
+ * Appends the target DBMS
+ *
+ * @param string $target_dbms the target DBMS name
+ * @param string $target_dbms_version the target DBMS revision
+ *
+ * @return void
+ * @access public
+ */
+ function appendTargetDbms($target_dbms, $target_dbms_version)
+ {
+ $this->target_dbms .= ' - ' . $target_dbms;
+ $this->target_dbms_version .= ' - ' . $target_dbms_version;
+ } // end of the "appendTargetDbms()" function
+
+
+ /**
+ * Sets the connection technology used
+ *
+ * @param string $connection_technology the con. technology name
+ * @param string $connection_technology_version the con. technology revision
+ *
+ * @return void
+ * @access public
+ */
+ function setConnectionTechnology(
+ $connection_technology, $connection_technology_version
+ ) {
+ $this->connection_technology = $connection_technology;
+ $this->connection_technology_version = $connection_technology_version;
+ } // end of the "setConnectionTechnology()" function
+
+
+ /**
+ * Appends the connection technology used
+ *
+ * @param string $connection_technology the con. technology name
+ * @param string $connection_technology_version the con. technology revision
+ *
+ * @return void
+ * @access public
+ */
+ function appendConnectionTechnology(
+ $connection_technology, $connection_technology_version
+ ) {
+ $this->connection_technology .= ' - ' . $connection_technology;
+ $this->connection_technology_version .= ' - ' . $connection_technology_version;
+ } // end of the "appendConnectionTechnology()" function
+
+
+ /**
+ * Sets whether interactive mode should be used or not
+ *
+ * @param integer $interactive whether interactive mode should be used or not
+ *
+ * @return void
+ * @access public
+ */
+ function setInteractive($interactive)
+ {
+ $this->interactive = $interactive;
+ } // end of the "setInteractive()" function
+
+
+ /**
+ * Sets the output type to use
+ *
+ * @param string $output_type the output type to use
+ *
+ * @return void
+ * @access public
+ */
+ function setOutputType($output_type)
+ {
+ $this->output_type = $output_type;
+ } // end of the "setOutputType()" function
+
+
+ /**
+ * Starts service
+ *
+ * @return void
+ * @access public
+ */
+ function startService()
+ {
+ $this->service_link = $this->_openService(
+ $this->url . '/' . $this->service_name . $this->wsdl
+ );
+ } // end of the "startService()" function
+
+
+ /**
+ * Starts session
+ *
+ * @return void
+ * @access public
+ */
+ function startSession()
+ {
+ $this->session_data = $this->_openSession(
+ $this->service_link, $this->username, $this->password,
+ $this->calling_program, $this->calling_program_version,
+ $this->target_dbms, $this->target_dbms_version,
+ $this->connection_technology, $this->connection_technology_version,
+ true // FIXME: Are we to tell them that we are interactive?
+ );
+
+ if (isset($this->session_data)
+ && ($this->session_data != null)
+ && ($this->session_data->target != $this->url)
+ ) {
+ // Reopens the service on the new URL that was provided
+ $this->url = $this->session_data->target;
+ $this->startService();
+ }
+ } // end of the "startSession()" function
+
+
+ /**
+ * Do start service and session
+ *
+ * @return void
+ * @access public
+ */
+ function start()
+ {
+ $this->startService();
+ $this->startSession();
+ } // end of the "start()" function
+
+
+ /**
+ * Call to determine just if a query is valid or not.
+ *
+ * @param string $sql SQL statement to validate
+ *
+ * @return string Validator string from Mimer
+ *
+ * @see _validate
+ */
+ function isValid($sql)
+ {
+ $res = $this->_validate($sql);
+ return $res->standard;
+ } // end of the "isValid()" function
+
+
+ /**
+ * Call for complete validator response
+ *
+ * @param string $sql SQL statement to validate
+ *
+ * @return string Validator string from Mimer
+ *
+ * @see _validate
+ */
+ function validationString($sql)
+ {
+ $res = $this->_validate($sql);
+ return $res->data;
+
+ } // end of the "validationString()" function
+ } // end class PMA_SQLValidator
+
+ //add an extra check to ensure that the class was defined without errors
+ if (!class_exists('PMA_SQLValidator')) {
+ $GLOBALS['sqlvalidator_error'] = true;
+ }
+
+} // end else
+
+?>
diff --git a/libraries/sqlvalidator.lib.php b/libraries/sqlvalidator.lib.php
new file mode 100644
index 0000000000..df59d1b7be
--- /dev/null
+++ b/libraries/sqlvalidator.lib.php
@@ -0,0 +1,107 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * SQL Validator interface for phpMyAdmin
+ *
+ * Copyright 2002 Robin Johnson <robbat2@users.sourceforge.net>
+ * http://www.orbis-terrarum.net/?l=people.robbat2
+ *
+ * This function uses the Mimer SQL Validator service
+ * <http://developer.mimer.com/validator/index.htm> from phpMyAdmin
+ *
+ * Copyright for Server side validator systems:
+ * "All SQL statements are stored anonymously for statistical purposes.
+ * Mimer SQL Validator, Copyright 2002 Upright Database Technology.
+ * All rights reserved."
+ *
+ * All data is transported over HTTP-SOAP
+ * And uses the PEAR SOAP Module
+ *
+ * Install instructions for PEAR SOAP
+ * Make sure you have a really recent PHP with PEAR support
+ * run this: "pear install Mail_Mime Net_DIME SOAP"
+ *
+ * Enable the SQL Validator options in the configuration file
+ * $cfg['SQLQuery']['Validate'] = true;
+ * $cfg['SQLValidator']['use'] = true;
+ *
+ * Also set a username and password if you have a private one
+ *
+ * @package PhpMyAdmin
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ * We need the PEAR libraries, so do a minimum version check first
+ * I'm not sure if PEAR was available before this point
+ * For now we actually use a configuration flag
+ */
+if ($cfg['SQLValidator']['use'] == true) {
+ include_once './libraries/sqlvalidator.class.php';
+} // if ($cfg['SQLValidator']['use'] == true)
+
+
+/**
+ * This function utilizes the Mimer SQL Validator service
+ * to validate an SQL query
+ *
+ * <http://developer.mimer.com/validator/index.htm>
+ *
+ * @param string $sql SQL query to validate
+ *
+ * @return string Validator result string
+ *
+ * @global array $cfg The PMA configuration array
+ */
+function PMA_validateSQL($sql)
+{
+ global $cfg;
+
+ $str = '';
+
+ if ($cfg['SQLValidator']['use']) {
+ if (isset($GLOBALS['sqlvalidator_error'])
+ && $GLOBALS['sqlvalidator_error']
+ ) {
+ $str = sprintf(
+ __('The SQL validator could not be initialized. Please check if you have installed the necessary PHP extensions as described in the %sdocumentation%s.'),
+ '<a href="' . PMA_Util::getDocuLink('faq', 'faqsqlvalidator')
+ . '" target="documentation">',
+ '</a>'
+ );
+ } else {
+ // create new class instance
+ $srv = new PMA_SQLValidator();
+
+ // Checks for username settings
+ // The class defaults to anonymous with an empty password
+ // automatically
+ if ($cfg['SQLValidator']['username'] != '') {
+ $srv->setCredentials(
+ $cfg['SQLValidator']['username'],
+ $cfg['SQLValidator']['password']
+ );
+ }
+
+ // Identify ourselves to the server properly...
+ $srv->appendCallingProgram('phpMyAdmin', PMA_VERSION);
+
+ // ... and specify what database system we are using
+ $srv->setTargetDbms('MySQL', PMA_MYSQL_STR_VERSION);
+
+ // Log on to service
+ $srv->start();
+
+ // Do service validation
+ $str = $srv->validationString($sql);
+ }
+
+ } // end if
+
+ // Gives string back to caller
+ return $str;
+} // end of the "PMA_validateSQL()" function
+
+?>
diff --git a/libraries/string.inc.php b/libraries/string.inc.php
new file mode 100644
index 0000000000..96273bc614
--- /dev/null
+++ b/libraries/string.inc.php
@@ -0,0 +1,18 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Include specialized String handling for phpMyAdmin
+ *
+ * @package PhpMyAdmin-String
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ * Include the string handling class
+ */
+require_once 'libraries/String.class.php';
+
+$PMA_String = new PMA_String();
+?>
diff --git a/libraries/structure.lib.php b/libraries/structure.lib.php
new file mode 100644
index 0000000000..960ccf8b60
--- /dev/null
+++ b/libraries/structure.lib.php
@@ -0,0 +1,2673 @@
+<?php
+
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * set of functions for structure section in pma
+ *
+ * @package PhpMyAdmin
+ */
+if (!defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ * Get the HTML links for action links
+ * Actions are, Browse, Search, Browse table label, empty table
+ *
+ * @param array $current_table current table
+ * @param boolean $table_is_view Is table view or not
+ * @param string $tbl_url_query table url query
+ * @param array $titles titles and icons for action links
+ * @param string $truename table name
+ * @param boolean $db_is_information_schema is database information schema or not
+ * @param string $url_query url query
+ *
+ * @return array ($browse_table, $search_table, $browse_table_label, $empty_table,
+ * $tracking_icon)
+ */
+function PMA_getHtmlForActionLinks($current_table, $table_is_view, $tbl_url_query,
+ $titles, $truename, $db_is_information_schema, $url_query
+) {
+ $empty_table = '';
+
+ if ($current_table['TABLE_ROWS'] > 0 || $table_is_view) {
+ $may_have_rows = true;
+ } else {
+ $may_have_rows = false;
+ }
+
+ $browse_table = '<a href="sql.php?' . $tbl_url_query . '&amp;pos=0">';
+ if ($may_have_rows) {
+ $browse_table .= $titles['Browse'];
+ } else {
+ $browse_table .= $titles['NoBrowse'];
+ }
+ $browse_table .= '</a>';
+
+ $search_table = '<a href="tbl_select.php?' . $tbl_url_query . '">';
+ if ($may_have_rows) {
+ $search_table .= $titles['Search'];
+ } else {
+ $search_table .= $titles['NoSearch'];
+ }
+ $search_table .= '</a>';
+
+ $browse_table_label = '<a href="sql.php?' . $tbl_url_query . '&amp;pos=0">'
+ . $truename . '</a>';
+
+ if (!$db_is_information_schema) {
+ $empty_table = '<a class="truncate_table_anchor ajax"';
+ $empty_table .= ' href="sql.php?' . $tbl_url_query
+ . '&amp;sql_query=';
+ $empty_table .= urlencode(
+ 'TRUNCATE ' . PMA_Util::backquote($current_table['TABLE_NAME'])
+ );
+ $empty_table .= '&amp;message_to_show='
+ . urlencode(
+ sprintf(
+ __('Table %s has been emptied'),
+ htmlspecialchars($current_table['TABLE_NAME'])
+ )
+ )
+ . '">';
+ if ($may_have_rows) {
+ $empty_table .= $titles['Empty'];
+ } else {
+ $empty_table .= $titles['NoEmpty'];
+ }
+ $empty_table .= '</a>';
+ // truncating views doesn't work
+ if ($table_is_view) {
+ $empty_table = '&nbsp;';
+ }
+ }
+
+ $tracking_icon = '';
+ if (PMA_Tracker::isActive()) {
+ if (PMA_Tracker::isTracked($GLOBALS["db"], $truename)) {
+ $tracking_icon = '<a href="tbl_tracking.php?' . $url_query
+ . '&amp;table=' . $truename . '">'
+ . PMA_Util::getImage(
+ 'eye.png', __('Tracking is active.')
+ )
+ . '</a>';
+ } elseif (PMA_Tracker::getVersion($GLOBALS["db"], $truename) > 0) {
+ $tracking_icon = '<a href="tbl_tracking.php?' . $url_query
+ . '&amp;table=' . $truename . '">'
+ . PMA_Util::getImage(
+ 'eye_grey.png', __('Tracking is not active.')
+ )
+ . '</a>';
+ }
+ }
+
+ return array($browse_table,
+ $search_table,
+ $browse_table_label,
+ $empty_table,
+ $tracking_icon
+ );
+}
+
+/**
+ * Get table drop query and drop message
+ *
+ * @param boolean $table_is_view Is table view or not
+ * @param string $current_table current table
+ *
+ * @return array ($drop_query, $drop_message)
+ */
+function PMA_getTableDropQueryAndMessage($table_is_view, $current_table)
+{
+ $drop_query = 'DROP '
+ . (($table_is_view || $current_table['ENGINE'] == null) ? 'VIEW' : 'TABLE')
+ . ' ' . PMA_Util::backquote(
+ $current_table['TABLE_NAME']
+ );
+ $drop_message = sprintf(
+ (($table_is_view || $current_table['ENGINE'] == null)
+ ? __('View %s has been dropped')
+ : __('Table %s has been dropped')),
+ str_replace(
+ ' ',
+ '&nbsp;',
+ htmlspecialchars($current_table['TABLE_NAME'])
+ )
+ );
+ return array($drop_query, $drop_message);
+}
+
+/**
+ * Get HTML body for table summery
+ *
+ * @param integer $num_tables number of tables
+ * @param boolean $server_slave_status server slave state
+ * @param boolean $db_is_information_schema whether database is information
+ * schema or not
+ * @param integer $sum_entries sum entries
+ * @param string $db_collation collation of given db
+ * @param boolean $is_show_stats whether stats is show or not
+ * @param double $sum_size sum size
+ * @param double $overhead_size overhead size
+ * @param string $create_time_all create time
+ * @param string $update_time_all update time
+ * @param string $check_time_all check time
+ * @param integer $sum_row_count_pre sum row count pre
+ *
+ * @return string $html_output
+ */
+function PMA_getHtmlBodyForTableSummary($num_tables, $server_slave_status,
+ $db_is_information_schema, $sum_entries, $db_collation, $is_show_stats,
+ $sum_size, $overhead_size, $create_time_all, $update_time_all,
+ $check_time_all, $sum_row_count_pre
+) {
+ if ($is_show_stats) {
+ list($sum_formatted, $unit) = PMA_Util::formatByteDown(
+ $sum_size, 3, 1
+ );
+ list($overhead_formatted, $overhead_unit)
+ = PMA_Util::formatByteDown($overhead_size, 3, 1);
+ }
+
+ $html_output = '<tbody id="tbl_summary_row">'
+ . '<tr><th></th>';
+ $html_output .= '<th class="tbl_num nowrap">';
+ $html_output .= sprintf(
+ _ngettext('%s table', '%s tables', $num_tables),
+ PMA_Util::formatNumber($num_tables, 0)
+ );
+ $html_output .= '</th>';
+
+ if ($server_slave_status) {
+ $html_output .= '<th>' . __('Replication') . '</th>' . "\n";
+ }
+ $html_output .= '<th colspan="'. ($db_is_information_schema ? 3 : 6) . '">'
+ . __('Sum')
+ . '</th>';
+ $html_output .= '<th class="value tbl_rows">'
+ . $sum_row_count_pre . PMA_Util::formatNumber($sum_entries, 0)
+ . '</th>';
+
+ if (!($GLOBALS['cfg']['PropertiesNumColumns'] > 1)) {
+ $default_engine = $GLOBALS['dbi']->fetchValue(
+ 'SHOW VARIABLES LIKE \'storage_engine\';',
+ 0,
+ 1
+ );
+ $html_output .= '<th class="center">' . "\n"
+ . '<dfn title="'
+ . sprintf(
+ __('%s is the default storage engine on this MySQL server.'),
+ $default_engine
+ )
+ . '">' .$default_engine . '</dfn></th>' . "\n";
+ // we got a case where $db_collation was empty
+ $html_output .= '<th>' . "\n";
+
+ if (! empty($db_collation)) {
+ $html_output .= '<dfn title="'
+ . PMA_getCollationDescr($db_collation)
+ . ' (' . __('Default') . ')">'
+ . $db_collation
+ . '</dfn>';
+ }
+ $html_output .= '</th>';
+ }
+ if ($is_show_stats) {
+ $html_output .= '<th class="value tbl_size">'
+ . $sum_formatted . ' ' . $unit
+ . '</th>';
+ $html_output .= '<th class="value tbl_overhead">'
+ . $overhead_formatted . ' ' . $overhead_unit
+ . '</th>';
+ }
+
+ if ($GLOBALS['cfg']['ShowDbStructureCreation']) {
+ $html_output .= '<th class="value tbl_creation">' . "\n"
+ . ' '
+ . ($create_time_all
+ ? PMA_Util::localisedDate(strtotime($create_time_all))
+ : '-'
+ )
+ . '</th>';
+ }
+
+ if ($GLOBALS['cfg']['ShowDbStructureLastUpdate']) {
+ $html_output .= '<th class="value tbl_last_update">' . "\n"
+ . ' '
+ . ($update_time_all
+ ? PMA_Util::localisedDate(strtotime($update_time_all))
+ : '-'
+ )
+ . '</th>';
+ }
+
+ if ($GLOBALS['cfg']['ShowDbStructureLastCheck']) {
+ $html_output .= '<th class="value tbl_last_check">' . "\n"
+ . ' '
+ . ($check_time_all
+ ? PMA_Util::localisedDate(strtotime($check_time_all))
+ : '-'
+ )
+ . '</th>';
+ }
+ $html_output .= '</tr>'
+ . '</tbody>';
+
+ return $html_output;
+}
+
+/**
+ * Get HTML for "check all" check box with "with selected" dropdown
+ *
+ * @param string $pmaThemeImage pma theme image url
+ * @param string $text_dir url for text directory
+ * @param string $overhead_check overhead check
+ * @param boolean $db_is_information_schema whether database is information
+ * schema or not
+ * @param string $hidden_fields hidden fields
+ *
+ * @return string $html_output
+ */
+function PMA_getHtmlForCheckAllTables($pmaThemeImage, $text_dir,
+ $overhead_check, $db_is_information_schema, $hidden_fields
+) {
+ $html_output = '<div class="clearfloat">';
+ $html_output .= '<img class="selectallarrow" '
+ . 'src="' .$pmaThemeImage .'arrow_'.$text_dir.'.png' . '"'
+ . 'width="38" height="22" alt="' . __('With selected:') . '" />';
+
+ $html_output .= '<input type="checkbox" id="tablesForm_checkall" '
+ . 'class="checkall_box" title="' . __('Check All') .'" />';
+ $html_output .= '<label for="tablesForm_checkall">' .__('Check All') . '</label>';
+
+ if ($overhead_check != '') {
+ $html_output .= PMA_getHtmlForCheckTablesHavingOverheadlink(
+ $overhead_check
+ );
+ }
+
+ $html_output .= '<select name="submit_mult" class="autosubmit" '
+ . 'style="margin: 0 3em 0 3em;">';
+
+ $html_output .= '<option value="' . __('With selected:')
+ . '" selected="selected">'
+ . __('With selected:') . '</option>' . "\n";
+ $html_output .= '<option value="export" >'
+ . __('Export') . '</option>' . "\n";
+ $html_output .= '<option value="print" >'
+ . __('Print view') . '</option>' . "\n";
+
+ if (!$db_is_information_schema
+ && !$GLOBALS['cfg']['DisableMultiTableMaintenance']
+ ) {
+ $html_output .= '<option value="empty_tbl" >'
+ . __('Empty') . '</option>' . "\n";
+ $html_output .= '<option value="drop_tbl" >'
+ . __('Drop') . '</option>' . "\n";
+ $html_output .= '<option value="check_tbl" >'
+ . __('Check table') . '</option>' . "\n";
+ if (!PMA_DRIZZLE) {
+ $html_output .= '<option value="optimize_tbl" >'
+ . __('Optimize table') . '</option>' . "\n";
+ $html_output .= '<option value="repair_tbl" >'
+ . __('Repair table') . '</option>' . "\n";
+ }
+ $html_output .= '<option value="analyze_tbl" >'
+ . __('Analyze table') . '</option>' . "\n";
+ $html_output .= '<option value="add_prefix_tbl" >'
+ . __('Add prefix to table') . '</option>' . "\n";
+ $html_output .= '<option value="replace_prefix_tbl" >'
+ . __('Replace table prefix') . '</option>' . "\n";
+ $html_output .= '<option value="copy_tbl_change_prefix" >'
+ . __('Copy table with prefix') . '</option>' . "\n";
+ }
+ $html_output .= '</select>'
+ . implode("\n", $hidden_fields) . "\n";
+ $html_output .= '</div>';
+
+ return $html_output;
+}
+
+/**
+ * Get HTML code for "Check tables having overhead" link
+ *
+ * @param string $overhead_check overhead check
+ *
+ * @return string $html_output
+ */
+function PMA_getHtmlForCheckTablesHavingOverheadlink($overhead_check)
+{
+ return ' / '
+ . '<a href="#" onclick="unMarkAllRows(\'tablesForm\');'
+ . $overhead_check . 'return false;">'
+ . __('Check tables having overhead')
+ . '</a>';
+}
+
+
+/**
+ * Get HTML links for "Print view" options
+ *
+ * @param string $url_query url query
+ *
+ * @return string $html_output
+ */
+function PMA_getHtmlForTablePrintViewLink($url_query)
+{
+ return '<p>'
+ . '<a href="db_printview.php?' . $url_query . '" target="print_view">'
+ . PMA_Util::getIcon(
+ 'b_print.png',
+ __('Print view'),
+ true
+ ) . '</a>';
+}
+
+/**
+ * Get HTML links "Data Dictionary" options
+ *
+ * @param string $url_query url query
+ *
+ * @return string $html_output
+ */
+function PMA_getHtmlForDataDictionaryLink($url_query)
+{
+ return '<a href="db_datadict.php?' . $url_query . '" target="print_view">'
+ . PMA_Util::getIcon(
+ 'b_tblanalyse.png',
+ __('Data Dictionary'),
+ true
+ ) . '</a>'
+ . '</p>';
+}
+
+/**
+ * Get Time for Create time, update time and check time
+ *
+ * @param array $current_table current table
+ * @param string $time_label Create_time, Update_time, Check_time
+ * @param integer $time_all time
+ *
+ * @return array ($time, $time_all)
+ */
+function PMA_getTimeForCreateUpdateCheck($current_table, $time_label, $time_all)
+{
+ $showtable = PMA_Table::sGetStatusInfo(
+ $GLOBALS['db'],
+ $current_table['TABLE_NAME'],
+ null,
+ true
+ );
+ $time = isset($showtable[$time_label])
+ ? $showtable[$time_label]
+ : false;
+
+ // show oldest creation date in summary row
+ if ($time && (!$time_all || $time < $time_all)) {
+ $time_all = $time;
+ }
+ return array($time, $time_all);
+}
+
+/**
+ * Get HTML for each table row of the database structure table,
+ * And this function returns $odd_row param also
+ *
+ * @param integer $curr current entry
+ * @param boolean $odd_row whether row is odd or not
+ * @param boolean $table_is_view whether table is view or not
+ * @param array $current_table current table
+ * @param string $browse_table_label browse table label action link
+ * @param string $tracking_icon tracking icon
+ * @param boolean $server_slave_status server slave state
+ * @param string $browse_table browse table action link
+ * @param string $tbl_url_query table url query
+ * @param string $search_table search table action link
+ * @param boolean $db_is_information_schema whether db is information schema or not
+ * @param array $titles titles array
+ * @param string $empty_table empty table action link
+ * @param string $drop_query table dropt query
+ * @param string $drop_message table drop message
+ * @param string $collation collation
+ * @param string $formatted_size formatted size
+ * @param string $unit unit
+ * @param string $overhead overhead
+ * @param string $create_time create time
+ * @param string $update_time last update time
+ * @param string $check_time last check time
+ * @param boolean $is_show_stats whether stats is show or not
+ * @param boolean $ignored ignored
+ * @param boolean $do do
+ * @param integer $colspan_for_structure colspan for structure
+ *
+ * @return array $html_output, $odd_row
+ */
+function PMA_getHtmlForStructureTableRow(
+ $curr, $odd_row, $table_is_view, $current_table,
+ $browse_table_label, $tracking_icon,$server_slave_status,
+ $browse_table, $tbl_url_query, $search_table,
+ $db_is_information_schema,$titles, $empty_table, $drop_query, $drop_message,
+ $collation, $formatted_size, $unit, $overhead, $create_time, $update_time,
+ $check_time,$is_show_stats, $ignored, $do, $colspan_for_structure
+) {
+ $html_output = '<tr class="' . ($odd_row ? 'odd' : 'even');
+ $odd_row = ! $odd_row;
+ $html_output .= ($table_is_view ? ' is_view' : '')
+ .'" id="row_tbl_' . $curr . '">';
+
+ $html_output .= '<td class="center">'
+ . '<input type="checkbox" name="selected_tbl[]" class="checkall" '
+ . 'value="' . htmlspecialchars($current_table['TABLE_NAME']) . '" '
+ . 'id="checkbox_tbl_' . $curr .'" /></td>';
+
+ $html_output .= '<th>'
+ . $browse_table_label
+ . (! empty($tracking_icon) ? $tracking_icon : '')
+ . '</th>';
+
+ if ($server_slave_status) {
+ $html_output .= '<td class="center">'
+ . ($ignored
+ ? PMA_Util::getImage('s_cancel.png', 'NOT REPLICATED')
+ : '')
+ . ($do
+ ? PMA_Util::getImage('s_success.png', 'REPLICATED')
+ : '')
+ . '</td>';
+ }
+
+ $html_output .= '<td class="center">' . $browse_table . '</td>';
+ $html_output .= '<td class="center">'
+ . '<a href="tbl_structure.php?' . $tbl_url_query . '">'
+ . $titles['Structure'] . '</a></td>';
+ $html_output .= '<td class="center">' . $search_table . '</td>';
+
+ if (! $db_is_information_schema) {
+ $html_output .= PMA_getHtmlForInsertEmptyDropActionLinks(
+ $tbl_url_query, $table_is_view,
+ $titles, $empty_table, $current_table, $drop_query, $drop_message
+ );
+ } // end if (! $db_is_information_schema)
+
+ // there is a null value in the ENGINE
+ // - when the table needs to be repaired, or
+ // - when it's a view
+ // so ensure that we'll display "in use" below for a table
+ // that needs to be repaired
+ if (isset($current_table['TABLE_ROWS'])
+ && ($current_table['ENGINE'] != null
+ || $table_is_view)
+ ) {
+ $html_output .= PMA_getHtmlForNotNullEngineViewTable(
+ $table_is_view, $current_table, $collation, $is_show_stats,
+ $tbl_url_query, $formatted_size, $unit, $overhead, $create_time,
+ $update_time, $check_time
+ );
+ } elseif ($table_is_view) {
+ $html_output .= PMA_getHtmlForViewTable($is_show_stats);
+ } else {
+ $html_output .= PMA_getHtmlForRepairtable(
+ $colspan_for_structure,
+ $db_is_information_schema
+ );
+ } // end if (isset($current_table['TABLE_ROWS'])) else
+ $html_output .= '</tr>';
+
+ return array($html_output, $odd_row);
+}
+
+/**
+ * Get HTML for Insert/Empty/Drop action links
+ *
+ * @param string $tbl_url_query table url query
+ * @param boolean $table_is_view whether table is view or not
+ * @param array $titles titles array
+ * @param string $empty_table HTML link for empty table
+ * @param array $current_table current table
+ * @param string $drop_query query for drop table
+ * @param string $drop_message table drop message
+ *
+ * @return string $html_output
+ */
+function PMA_getHtmlForInsertEmptyDropActionLinks($tbl_url_query, $table_is_view,
+ $titles, $empty_table, $current_table, $drop_query, $drop_message
+) {
+ $html_output = '<td class="insert_table center">'
+ . '<a href="tbl_change.php?' . $tbl_url_query . '">'
+ . $titles['Insert']
+ . '</a></td>';
+ $html_output .= '<td class="center">' . $empty_table . '</td>';
+ $html_output .= '<td class="center">';
+ $html_output .= '<a ';
+ $html_output .= 'class="ajax drop_table_anchor';
+ if ($table_is_view || $current_table['ENGINE'] == null) {
+ // this class is used in db_structure.js to display the
+ // correct confirmation message
+ $html_output .= ' view';
+ }
+ $html_output .= '"';
+ $html_output .= 'href="sql.php?' . $tbl_url_query
+ . '&amp;reload=1&amp;purge=1&amp;sql_query='
+ . urlencode($drop_query) . '&amp;message_to_show='
+ . urlencode($drop_message) . '" >'
+ . $titles['Drop'] . '</a></td>';
+
+ return $html_output;
+}
+
+/**
+ * Get HTML for show stats
+ *
+ * @param string $tbl_url_query tabel url query
+ * @param string $formatted_size formatted size
+ * @param string $unit unit
+ * @param string $overhead overhead
+ *
+ * @return string $html_output
+ */
+function PMA_getHtmlForShowStats($tbl_url_query, $formatted_size,
+ $unit, $overhead
+) {
+ $html_output = '<td class="value tbl_size"><a'
+ . 'href="tbl_structure.php?' . $tbl_url_query . '#showusage" >'
+ . '<span>' . $formatted_size . '</span> '
+ . '<span class="unit">' . $unit . '</span>'
+ . '</a></td>';
+ $html_output .= '<td class="value tbl_overhead">' . $overhead . '</td>';
+
+ return $html_output;
+}
+
+/**
+ * Get HTML to show database structure creation, last update and last checkx time
+ *
+ * @param string $create_time create time
+ * @param string $update_time last update time
+ * @param string $check_time last check time
+ *
+ * @return string $html_output
+ */
+function PMA_getHtmlForStructureTimes($create_time, $update_time, $check_time)
+{
+ $html_output = '';
+ if ($GLOBALS['cfg']['ShowDbStructureCreation']) {
+ $html_output .= '<td class="value tbl_creation">'
+ . ($create_time
+ ? PMA_Util::localisedDate(strtotime($create_time))
+ : '-' )
+ . '</td>';
+ } // end if
+ if ($GLOBALS['cfg']['ShowDbStructureLastUpdate']) {
+ $html_output .= '<td class="value tbl_last_update">'
+ . ($update_time
+ ? PMA_Util::localisedDate(strtotime($update_time))
+ : '-' )
+ . '</td>';
+ } // end if
+ if ($GLOBALS['cfg']['ShowDbStructureLastCheck']) {
+ $html_output .= '<td class="value tbl_last_check">'
+ . ($check_time
+ ? PMA_Util::localisedDate(strtotime($check_time))
+ : '-' )
+ . '</td>';
+ }
+ return $html_output;
+}
+
+/**
+ * Get HTML for ENGINE value not null or view tables that are not empty tables
+ *
+ * @param boolean $table_is_view whether table is view
+ * @param array $current_table current table
+ * @param string $collation collation
+ * @param boolean $is_show_stats whether atats show or not
+ * @param string $tbl_url_query table url query
+ * @param string $formatted_size formatted size
+ * @param string $unit unit
+ * @param string $overhead overhead
+ * @param string $create_time create time
+ * @param string $update_time update time
+ * @param string $check_time check time
+ *
+ * @return string $html_output
+ */
+function PMA_getHtmlForNotNullEngineViewTable($table_is_view, $current_table,
+ $collation, $is_show_stats, $tbl_url_query, $formatted_size, $unit,
+ $overhead, $create_time, $update_time, $check_time
+) {
+ $html_output = '';
+ $row_count_pre = '';
+ $show_superscript = '';
+ if ($table_is_view) {
+ // Drizzle views use FunctionEngine, and the only place where they are
+ // available are I_S and D_D schemas, where we do exact counting
+ if ($current_table['TABLE_ROWS'] >= $GLOBALS['cfg']['MaxExactCountViews']
+ && $current_table['ENGINE'] != 'FunctionEngine'
+ ) {
+ $row_count_pre = '~';
+ $sum_row_count_pre = '~';
+ $show_superscript = PMA_Util::showHint(
+ PMA_sanitize(
+ sprintf(
+ __('This view has at least this number of rows. Please refer to %sdocumentation%s.'),
+ '[doc@cfg_MaxExactCountViews]',
+ '[/doc]'
+ )
+ )
+ );
+ }
+ } elseif ($current_table['ENGINE'] == 'InnoDB'
+ && (! $current_table['COUNTED'])
+ ) {
+ // InnoDB table: we did not get an accurate row count
+ $row_count_pre = '~';
+ $sum_row_count_pre = '~';
+ $show_superscript = '';
+ }
+
+ $html_output .= '<td class="value tbl_rows">'
+ . $row_count_pre . PMA_Util::formatNumber(
+ $current_table['TABLE_ROWS'], 0
+ )
+ . $show_superscript . '</td>';
+
+ if (!($GLOBALS['cfg']['PropertiesNumColumns'] > 1)) {
+ $html_output .= '<td class="nowrap">'
+ . ($table_is_view ? __('View') : $current_table['ENGINE'])
+ . '</td>';
+ if (strlen($collation)) {
+ $html_output .= '<td class="nowrap">' . $collation . '</td>';
+ }
+ }
+
+ if ($is_show_stats) {
+ $html_output .= PMA_getHtmlForShowStats(
+ $tbl_url_query, $formatted_size, $unit, $overhead
+ );
+ }
+
+ $html_output .= PMA_getHtmlForStructureTimes(
+ $create_time, $update_time, $check_time
+ );
+
+ return $html_output;
+}
+
+/**
+ * Get HTML snippet view table
+ *
+ * @param type $is_show_stats whether stats show or not
+ *
+ * @return string $html_output
+ */
+function PMA_getHtmlForViewTable($is_show_stats)
+{
+ $html_output = '<td class="value">-</td>'
+ . '<td>' . __('View') . '</td>'
+ . '<td>---</td>';
+ if ($is_show_stats) {
+ $html_output .= '<td class="value">-</td>'
+ . '<td class="value">-</td>';
+ }
+ return $html_output;
+}
+
+/**
+ * display "in use" below for a table that needs to be repaired
+ *
+ * @param integer $colspan_for_structure colspan for structure
+ * @param boolean $db_is_information_schema whether db is information schema or not
+ *
+ * @return string HTML snippet
+ */
+function PMA_getHtmlForRepairtable(
+ $colspan_for_structure,
+ $db_is_information_schema
+) {
+ return '<td colspan="'
+ . ($colspan_for_structure - ($db_is_information_schema ? 5 : 8)) . '"'
+ . 'class="center">'
+ . __('in use')
+ . '</td>';
+}
+
+/**
+ * display table header (<table><thead>...</thead><tbody>)
+ *
+ * @param boolean $db_is_information_schema whether db is information schema or not
+ * @param boolean $replication whether to sho replication status
+ *
+ * @return string html data
+ */
+function PMA_tableHeader($db_is_information_schema = false, $replication = false)
+{
+ $cnt = 0; // Let's count the columns...
+
+ if ($db_is_information_schema) {
+ $action_colspan = 3;
+ } else {
+ $action_colspan = 6;
+ }
+
+ $html_output = '<table class="data">' . "\n"
+ .'<thead>' . "\n"
+ .'<tr><th></th>' . "\n"
+ .'<th>'
+ . PMA_sortableTableHeader(__('Table'), 'table')
+ . '</th>' . "\n";
+ if ($replication) {
+ $html_output .= '<th>' . "\n"
+ .' ' . __('Replication') . "\n"
+ .'</th>';
+ }
+ $html_output .= '<th colspan="' . $action_colspan . '">' . "\n"
+ .' ' . __('Action') . "\n"
+ .'</th>'
+ // larger values are more interesting so default sort order is DESC
+ .'<th>' . PMA_sortableTableHeader(__('Rows'), 'records', 'DESC')
+ . PMA_Util::showHint(
+ PMA_sanitize(
+ __('May be approximate. See [doc@faq3-11]FAQ 3.11[/doc]')
+ )
+ ) . "\n"
+ .'</th>' . "\n";
+ if (!($GLOBALS['cfg']['PropertiesNumColumns'] > 1)) {
+ $html_output .= '<th>' . PMA_sortableTableHeader(__('Type'), 'type')
+ . '</th>' . "\n";
+ $cnt++;
+ $html_output .= '<th>'
+ . PMA_sortableTableHeader(__('Collation'), 'collation')
+ . '</th>' . "\n";
+ $cnt++;
+ }
+ if ($GLOBALS['is_show_stats']) {
+ // larger values are more interesting so default sort order is DESC
+ $html_output .= '<th>'
+ . PMA_sortableTableHeader(__('Size'), 'size', 'DESC')
+ . '</th>' . "\n"
+ // larger values are more interesting so default sort order is DESC
+ . '<th>'
+ . PMA_sortableTableHeader(__('Overhead'), 'overhead', 'DESC')
+ . '</th>' . "\n";
+ $cnt += 2;
+ }
+ if ($GLOBALS['cfg']['ShowDbStructureCreation']) {
+ // larger values are more interesting so default sort order is DESC
+ $html_output .= '<th>'
+ . PMA_sortableTableHeader(__('Creation'), 'creation', 'DESC')
+ . '</th>' . "\n";
+ $cnt += 2;
+ }
+ if ($GLOBALS['cfg']['ShowDbStructureLastUpdate']) {
+ // larger values are more interesting so default sort order is DESC
+ $html_output .= '<th>'
+ . PMA_sortableTableHeader(__('Last update'), 'last_update', 'DESC')
+ . '</th>' . "\n";
+ $cnt += 2;
+ }
+ if ($GLOBALS['cfg']['ShowDbStructureLastCheck']) {
+ // larger values are more interesting so default sort order is DESC
+ $html_output .= '<th>'
+ . PMA_sortableTableHeader(__('Last check'), 'last_check', 'DESC')
+ . '</th>' . "\n";
+ $cnt += 2;
+ }
+ $html_output .= '</tr>' . "\n";
+ $html_output .= '</thead>' . "\n";
+ $html_output .= '<tbody>' . "\n";
+ $GLOBALS['colspan_for_structure'] = $cnt + $action_colspan + 3;
+
+ return $html_output;
+}
+
+/**
+ * Creates a clickable column header for table information
+ *
+ * @param string $title title to use for the link
+ * @param string $sort corresponds to sortable data name mapped in
+ * libraries/db_info.inc.php
+ * @param string $initial_sort_order initial sort order
+ *
+ * @return string link to be displayed in the table header
+ */
+function PMA_sortableTableHeader($title, $sort, $initial_sort_order = 'ASC')
+{
+ // Set some defaults
+ $requested_sort = 'table';
+ $requested_sort_order = $future_sort_order = $initial_sort_order;
+
+ // If the user requested a sort
+ if (isset($_REQUEST['sort'])) {
+ $requested_sort = $_REQUEST['sort'];
+
+ if (isset($_REQUEST['sort_order'])) {
+ $requested_sort_order = $_REQUEST['sort_order'];
+ }
+ }
+
+ $order_img = '';
+ $order_link_params = array();
+ $order_link_params['title'] = __('Sort');
+
+ // If this column was requested to be sorted.
+ if ($requested_sort == $sort) {
+ if ($requested_sort_order == 'ASC') {
+ $future_sort_order = 'DESC';
+ // current sort order is ASC
+ $order_img = ' ' . PMA_Util::getImage(
+ 's_asc.png',
+ __('Ascending'),
+ array('class' => 'sort_arrow', 'title' => '')
+ );
+ $order_img .= ' ' . PMA_Util::getImage(
+ 's_desc.png',
+ __('Descending'),
+ array('class' => 'sort_arrow hide', 'title' => '')
+ );
+ // but on mouse over, show the reverse order (DESC)
+ $order_link_params['onmouseover'] = "$('.sort_arrow').toggle();";
+ // on mouse out, show current sort order (ASC)
+ $order_link_params['onmouseout'] = "$('.sort_arrow').toggle();";
+ } else {
+ $future_sort_order = 'ASC';
+ // current sort order is DESC
+ $order_img = ' ' . PMA_Util::getImage(
+ 's_asc.png',
+ __('Ascending'),
+ array('class' => 'sort_arrow hide', 'title' => '')
+ );
+ $order_img .= ' ' . PMA_Util::getImage(
+ 's_desc.png',
+ __('Descending'),
+ array('class' => 'sort_arrow', 'title' => '')
+ );
+ // but on mouse over, show the reverse order (ASC)
+ $order_link_params['onmouseover'] = "$('.sort_arrow').toggle();";
+ // on mouse out, show current sort order (DESC)
+ $order_link_params['onmouseout'] = "$('.sort_arrow').toggle();";
+ }
+ }
+
+ $_url_params = array(
+ 'db' => $_REQUEST['db'],
+ );
+
+ $url = 'db_structure.php' . PMA_URL_getCommon($_url_params);
+ // We set the position back to 0 every time they sort.
+ $url .= "&amp;pos=0&amp;sort=$sort&amp;sort_order=$future_sort_order";
+ if (! empty($_REQUEST['tbl_type'])) {
+ $url .= "&amp;tbl_type=" . $_REQUEST['tbl_type'];
+ }
+ if (! empty($_REQUEST['tbl_group'])) {
+ $url .= "&amp;tbl_group=" . $_REQUEST['tbl_group'];
+ }
+
+ return PMA_Util::linkOrButton(
+ $url, $title . $order_img, $order_link_params
+ );
+}
+
+/**
+ * Get the alias ant truname
+ *
+ * @param string $tooltip_aliasname tooltip alias name
+ * @param array $current_table current table
+ * @param string $tooltip_truename tooltip true name
+ *
+ * @return array ($alias, $truename)
+ */
+function PMA_getAliasAndTrueName($tooltip_aliasname, $current_table,
+ $tooltip_truename
+) {
+ $alias = (! empty($tooltip_aliasname)
+ && isset($tooltip_aliasname[$current_table['TABLE_NAME']])
+ )
+ ? str_replace(
+ ' ', '&nbsp;',
+ htmlspecialchars($tooltip_truename[$current_table['TABLE_NAME']])
+ )
+ : str_replace(
+ ' ', '&nbsp;',
+ htmlspecialchars($current_table['TABLE_NAME'])
+ );
+ $truename = (! empty($tooltip_truename)
+ && isset($tooltip_truename[$current_table['TABLE_NAME']])
+ )
+ ? str_replace(
+ ' ', '&nbsp;',
+ htmlspecialchars($tooltip_truename[$current_table['TABLE_NAME']])
+ )
+ : str_replace(
+ ' ', '&nbsp;',
+ htmlspecialchars($current_table['TABLE_NAME'])
+ );
+
+ return array($alias, $truename);
+}
+
+/**
+ * Get the server slave state
+ *
+ * @param boolean $server_slave_status server slave state
+ * @param string $truename true name
+ *
+ * @return array ($do, $ignored)
+ */
+function PMA_getServerSlaveStatus($server_slave_status, $truename)
+{
+ $ignored = false;
+ $do = false;
+ include_once 'libraries/replication.inc.php';
+ if ($server_slave_status) {
+ if ((strlen(array_search($truename, $server_slave_Do_Table)) > 0)
+ || (strlen(array_search($GLOBALS['db'], $server_slave_Do_DB)) > 0)
+ || (count($server_slave_Do_DB) == 1 && count($server_slave_Ignore_DB) == 1)
+ ) {
+ $do = true;
+ }
+ foreach ($server_slave_Wild_Do_Table as $db_table) {
+ $table_part = PMA_extractDbOrTable($db_table, 'table');
+ if (($GLOBALS['db'] == PMA_extractDbOrTable($db_table, 'db'))
+ && (preg_match("@^" . substr($table_part, 0, strlen($table_part) - 1) . "@", $truename))
+ ) {
+ $do = true;
+ }
+ }
+
+ if ((strlen(array_search($truename, $server_slave_Ignore_Table)) > 0)
+ || (strlen(array_search($GLOBALS['db'], $server_slave_Ignore_DB)) > 0)
+ ) {
+ $ignored = true;
+ }
+ foreach ($server_slave_Wild_Ignore_Table as $db_table) {
+ $table_part = PMA_extractDbOrTable($db_table, 'table');
+ if (($db == PMA_extractDbOrTable($db_table))
+ && (preg_match("@^" . substr($table_part, 0, strlen($table_part) - 1) . "@", $truename))
+ ) {
+ $ignored = true;
+ }
+ }
+ }
+ return array($do, $ignored);
+}
+
+/**
+ * Get the value set for ENGINE table,
+ * $current_table, $formatted_size, $unit, $formatted_overhead,
+ * $overhead_unit, $overhead_size, $table_is_view
+ *
+ * @param array $current_table current table
+ * @param boolean $db_is_information_schema whether db is information schema or not
+ * @param boolean $is_show_stats whether stats show or not
+ * @param boolean $table_is_view whether table is view or not
+ * @param double $sum_size totle table size
+ * @param double $overhead_size overhead size
+ *
+ * @return array
+ */
+function PMA_getStuffForEngineTypeTable($current_table, $db_is_information_schema,
+ $is_show_stats, $table_is_view, $sum_size, $overhead_size
+) {
+ $formatted_size = '-';
+ $unit = '';
+ $formatted_overhead = '';
+ $overhead_unit = '';
+
+ switch ( $current_table['ENGINE']) {
+ // MyISAM, ISAM or Heap table: Row count, data size and index size
+ // are accurate; data size is accurate for ARCHIVE
+ case 'MyISAM' :
+ case 'ISAM' :
+ case 'HEAP' :
+ case 'MEMORY' :
+ case 'ARCHIVE' :
+ case 'Aria' :
+ case 'Maria' :
+ list($current_table, $formatted_size, $unit, $formatted_overhead,
+ $overhead_unit, $overhead_size, $sum_size) = PMA_getValuesForAriaTable(
+ $db_is_information_schema, $current_table, $is_show_stats,
+ $sum_size, $overhead_size, $formatted_size, $unit,
+ $formatted_overhead, $overhead_unit
+ );
+ break;
+ case 'InnoDB' :
+ case 'PBMS' :
+ // InnoDB table: Row count is not accurate but data and index sizes are.
+ // PBMS table in Drizzle: TABLE_ROWS is taken from table cache,
+ // so it may be unavailable
+ list($current_table, $formatted_size, $unit, $sum_size)
+ = PMA_getValuesForInnodbTable($current_table, $is_show_stats, $sum_size);
+ //$display_rows = ' - ';
+ break;
+ // Mysql 5.0.x (and lower) uses MRG_MyISAM
+ // and MySQL 5.1.x (and higher) uses MRG_MYISAM
+ // Both are aliases for MERGE
+ case 'MRG_MyISAM' :
+ case 'MRG_MYISAM' :
+ case 'MERGE' :
+ case 'BerkeleyDB' :
+ // Merge or BerkleyDB table: Only row count is accurate.
+ if ($is_show_stats) {
+ $formatted_size = ' - ';
+ $unit = '';
+ }
+ break;
+ // for a view, the ENGINE is sometimes reported as null,
+ // or on some servers it's reported as "SYSTEM VIEW"
+ case null :
+ case 'SYSTEM VIEW' :
+ case 'FunctionEngine' :
+ // if table is broken, Engine is reported as null, so one more test
+ if ($current_table['TABLE_TYPE'] == 'VIEW') {
+ // countRecords() takes care of $cfg['MaxExactCountViews']
+ $current_table['TABLE_ROWS'] = PMA_Table::countRecords(
+ $GLOBALS['db'], $current_table['TABLE_NAME'],
+ true, true
+ );
+ $table_is_view = true;
+ }
+ break;
+ default :
+ // Unknown table type.
+ if ($is_show_stats) {
+ $formatted_size = __('unknown');
+ $unit = '';
+ }
+ } // end switch
+
+ return array($current_table, $formatted_size, $unit, $formatted_overhead,
+ $overhead_unit, $overhead_size, $table_is_view, $sum_size
+ );
+}
+
+/**
+ * Get values for ARIA/MARIA tables
+ * $current_table, $formatted_size, $unit, $formatted_overhead,
+ * $overhead_unit, $overhead_size
+ *
+ * @param boolean $db_is_information_schema whether db is information schema or not
+ * @param array $current_table current table
+ * @param boolean $is_show_stats whether stats show or not
+ * @param double $sum_size sum size
+ * @param double $overhead_size overhead size
+ * @param number $formatted_size formatted size
+ * @param string $unit unit
+ * @param number $formatted_overhead overhead formatted
+ * @param string $overhead_unit overhead unit
+ *
+ * @return array
+ */
+function PMA_getValuesForAriaTable($db_is_information_schema, $current_table,
+ $is_show_stats, $sum_size, $overhead_size, $formatted_size, $unit,
+ $formatted_overhead, $overhead_unit
+) {
+ if ($db_is_information_schema) {
+ $current_table['Rows'] = PMA_Table::countRecords(
+ $GLOBALS['db'], $current_table['Name']
+ );
+ }
+
+ if ($is_show_stats) {
+ $tblsize = doubleval($current_table['Data_length'])
+ + doubleval($current_table['Index_length']);
+ $sum_size += $tblsize;
+ list($formatted_size, $unit) = PMA_Util::formatByteDown(
+ $tblsize, 3, ($tblsize > 0) ? 1 : 0
+ );
+ if (isset($current_table['Data_free']) && $current_table['Data_free'] > 0) {
+ list($formatted_overhead, $overhead_unit)
+ = PMA_Util::formatByteDown(
+ $current_table['Data_free'], 3,
+ (($current_table['Data_free'] > 0) ? 1 : 0)
+ );
+ $overhead_size += $current_table['Data_free'];
+ }
+ }
+ return array($current_table, $formatted_size, $unit, $formatted_overhead,
+ $overhead_unit, $overhead_size, $sum_size
+ );
+}
+
+/**
+ * Get values for InnoDB table
+ * $current_table, $formatted_size, $unit, $sum_size
+ *
+ * @param array $current_table current table
+ * @param boolean $is_show_stats whether stats show or not
+ * @param double $sum_size sum size
+ *
+ * @return array
+ */
+function PMA_getValuesForInnodbTable($current_table, $is_show_stats, $sum_size)
+{
+ $formatted_size = $unit = '';
+
+ if (($current_table['ENGINE'] == 'InnoDB'
+ && $current_table['TABLE_ROWS'] < $GLOBALS['cfg']['MaxExactCount'])
+ || !isset($current_table['TABLE_ROWS'])
+ ) {
+ $current_table['COUNTED'] = true;
+ $current_table['TABLE_ROWS'] = PMA_Table::countRecords(
+ $GLOBALS['db'], $current_table['TABLE_NAME'],
+ true, false
+ );
+ } else {
+ $current_table['COUNTED'] = false;
+ }
+
+ // Drizzle doesn't provide data and index length, check for null
+ if ($is_show_stats && $current_table['Data_length'] !== null) {
+ $tblsize = $current_table['Data_length'] + $current_table['Index_length'];
+ $sum_size += $tblsize;
+ list($formatted_size, $unit) = PMA_Util::formatByteDown(
+ $tblsize, 3, (($tblsize > 0) ? 1 : 0)
+ );
+ }
+
+ return array($current_table, $formatted_size, $unit, $sum_size);
+}
+
+/**
+ * table structure
+ */
+
+/**
+ * Get the HTML snippet for structure table table header
+ *
+ * @param boolean $db_is_information_schema whether db is information schema or not
+ * @param boolean $tbl_is_view whether table is view or not
+ *
+ * @return string $html_output
+ */
+function PMA_getHtmlForTableStructureHeader(
+ $db_is_information_schema,
+ $tbl_is_view
+) {
+ $html_output = '<thead>';
+ $html_output .= '<tr>';
+ $html_output .= '<th></th>'
+ . '<th>#</th>'
+ . '<th>' . __('Name') . '</th>'
+ . '<th>' . __('Type'). '</th>'
+ . '<th>' . __('Collation') . '</th>'
+ . '<th>' . __('Attributes') . '</th>'
+ . '<th>' . __('Null') . '</th>'
+ . '<th>' . __('Default') . '</th>'
+ . '<th>' . __('Extra') . '</th>';
+
+ if ($db_is_information_schema || $tbl_is_view) {
+ $html_output .= '<th>' . __('View') . '</th>';
+ } else { /* see tbl_structure.js, function moreOptsMenuResize() */
+ $colspan = 9;
+ if (PMA_DRIZZLE) {
+ $colspan -= 2;
+ }
+ if (PMA_Util::showIcons('ActionLinksMode')) {
+ $colspan--;
+ }
+ $html_output .= '<th colspan="' . $colspan . '" '
+ . 'class="action">' . __('Action') . '</th>';
+ }
+ $html_output .= '</tr>'
+ . '</thead>';
+
+ return $html_output;
+}
+
+/**
+ * Get HTML for structure table's rows and return $odd_row parameter also
+ * For "Action" Column, this function contains only HTML code for "Change"
+ * and "Drop"
+ *
+ * @param array $row current row
+ * @param string $rownum row number
+ * @param string $displayed_field_name displayed field name
+ * @param string $type_nowrap type nowrap
+ * @param array $extracted_columnspec associative array containing type,
+ * spec_in_brackets and possibly
+ * enum_set_values (another array)
+ * @param string $type_mime mime type
+ * @param string $field_charset field charset
+ * @param string $attribute attribute (BINARY, UNSIGNED,
+ * UNSIGNED ZEROFILL,
+ * on update CURRENT_TIMESTAMP)
+ * @param boolean $tbl_is_view whether tables is view or not
+ * @param boolean $db_is_information_schema whether db is information schema or not
+ * @param string $url_query url query
+ * @param string $field_encoded field encoded
+ * @param array $titles tittles array
+ * @param string $table table
+ *
+ * @return array ($html_output, $odd_row)
+ */
+function PMA_getHtmlTableStructureRow($row, $rownum,
+ $displayed_field_name, $type_nowrap, $extracted_columnspec, $type_mime,
+ $field_charset, $attribute, $tbl_is_view, $db_is_information_schema,
+ $url_query, $field_encoded, $titles, $table
+) {
+ $html_output = '<td class="center">'
+ . '<input type="checkbox" class="checkall" name="selected_fld[]" '
+ . 'value="' . htmlspecialchars($row['Field']) . '" '
+ . 'id="checkbox_row_' . $rownum . '"/>'
+ . '</td>';
+
+ $html_output .= '<td class="right">'
+ . $rownum
+ . '</td>';
+
+ $html_output .= '<th class="nowrap">'
+ . '<label for="checkbox_row_' . $rownum . '">'
+ . $displayed_field_name . '</label>'
+ . '</th>';
+
+ $html_output .= '<td' . $type_nowrap . '>'
+ .'<bdo dir="ltr" lang="en">'
+ . $extracted_columnspec['displayed_type'] . $type_mime
+ . '</bdo></td>';
+
+ $html_output .= '<td>' .
+ (empty($field_charset)
+ ? ''
+ : '<dfn title="' . PMA_getCollationDescr($field_charset) . '">'
+ . $field_charset . '</dfn>'
+ )
+ . '</td>';
+
+ $html_output .= '<td class="column_attribute nowrap">'
+ . $attribute . '</td>';
+ $html_output .= '<td>'
+ . (($row['Null'] == 'YES') ? __('Yes') : __('No')) . ' </td>';
+
+ $html_output .= '<td class="nowrap">';
+ if (isset($row['Default'])) {
+ if ($extracted_columnspec['type'] == 'bit') {
+ // here, $row['Default'] contains something like b'010'
+ $html_output .= PMA_Util::convertBitDefaultValue($row['Default']);
+ } else {
+ $html_output .= $row['Default'];
+ }
+ } else {
+ $html_output .= '<i>' . _pgettext('None for default', 'None') . '</i>';
+ }
+ $html_output .= '</td>';
+
+ $html_output .= '<td class="nowrap">' . strtoupper($row['Extra']) . '</td>';
+
+ $html_output .= PMA_getHtmlForDropColumn(
+ $tbl_is_view, $db_is_information_schema,
+ $url_query, $field_encoded,
+ $titles, $table, $row
+ );
+
+ return $html_output;
+}
+
+/**
+ * Get HTML code for "Drop" Action link
+ *
+ * @param boolean $tbl_is_view whether tables is view or not
+ * @param boolean $db_is_information_schema whether db is information schema or not
+ * @param string $url_query url query
+ * @param string $field_encoded field encoded
+ * @param array $titles tittles array
+ * @param string $table table
+ * @param array $row current row
+ *
+ * @return string $html_output
+ */
+function PMA_getHtmlForDropColumn($tbl_is_view, $db_is_information_schema,
+ $url_query, $field_encoded, $titles, $table, $row
+) {
+ $html_output = '';
+
+ if (! $tbl_is_view && ! $db_is_information_schema) {
+ $html_output .= '<td class="edit center">'
+ . '<a class="change_column_anchor ajax"'
+ . ' href="tbl_structure.php?'
+ . $url_query . '&amp;field=' . $field_encoded
+ . '&amp;change_column=1">'
+ . $titles['Change'] . '</a>' . '</td>';
+ $html_output .= '<td class="drop center">'
+ . '<a class="drop_column_anchor ajax"'
+ . ' href="sql.php?' . $url_query . '&amp;sql_query='
+ . urlencode(
+ 'ALTER TABLE ' . PMA_Util::backquote($table)
+ . ' DROP ' . PMA_Util::backquote($row['Field']) . ';'
+ )
+ . '&amp;dropped_column=' . urlencode($row['Field'])
+ . '&amp;message_to_show=' . urlencode(
+ sprintf(
+ __('Column %s has been dropped'),
+ htmlspecialchars($row['Field'])
+ )
+ ) . '" >'
+ . $titles['Drop'] . '</a>'
+ . '</td>';
+ }
+
+ return $html_output;
+}
+
+/**
+ * Get HTML for "check all" check box with "with selected" actions in table
+ * structure
+ *
+ * @param string $pmaThemeImage pma theme image url
+ * @param string $text_dir test directory
+ * @param boolean $tbl_is_view whether table is view or not
+ * @param boolean $db_is_information_schema whether db is information schema or not
+ * @param string $tbl_storage_engine table storage engine
+ *
+ * @return string $html_output
+ */
+function PMA_getHtmlForCheckAllTableColumn($pmaThemeImage, $text_dir,
+ $tbl_is_view, $db_is_information_schema, $tbl_storage_engine
+) {
+ $html_output = '<img class="selectallarrow" '
+ . 'src="' . $pmaThemeImage . 'arrow_' . $text_dir . '.png' . '"'
+ . 'width="38" height="22" alt="' . __('With selected:') . '" />';
+
+ $html_output .= '<input type="checkbox" id="fieldsForm_checkall" '
+ . 'class="checkall_box" title="' . __('Check All') . '" />'
+ . '<label for="fieldsForm_checkall">' . __('Check All') . '</label>';
+
+ $html_output .= '<i style="margin-left: 2em">'
+ . __('With selected:') . '</i>';
+
+ $html_output .= PMA_Util::getButtonOrImage(
+ 'submit_mult', 'mult_submit', 'submit_mult_browse',
+ __('Browse'), 'b_browse.png', 'browse'
+ );
+
+ if (! $tbl_is_view && ! $db_is_information_schema) {
+ $html_output .= PMA_Util::getButtonOrImage(
+ 'submit_mult', 'mult_submit change_columns_anchor ajax',
+ 'submit_mult_change', __('Change'), 'b_edit.png', 'change'
+ );
+ $html_output .= PMA_Util::getButtonOrImage(
+ 'submit_mult', 'mult_submit', 'submit_mult_drop',
+ __('Drop'), 'b_drop.png', 'drop'
+ );
+ if ('ARCHIVE' != $tbl_storage_engine) {
+ $html_output .= PMA_Util::getButtonOrImage(
+ 'submit_mult', 'mult_submit', 'submit_mult_primary',
+ __('Primary'), 'b_primary.png', 'primary'
+ );
+ $html_output .= PMA_Util::getButtonOrImage(
+ 'submit_mult', 'mult_submit', 'submit_mult_unique',
+ __('Unique'), 'b_unique.png', 'unique'
+ );
+ $html_output .= PMA_Util::getButtonOrImage(
+ 'submit_mult', 'mult_submit', 'submit_mult_index',
+ __('Index'), 'b_index.png', 'index'
+ );
+ }
+
+ if (! empty($tbl_storage_engine) && $tbl_storage_engine == 'MYISAM') {
+ $html_output .= PMA_Util::getButtonOrImage(
+ 'submit_mult', 'mult_submit', 'submit_mult_spatial',
+ __('Spatial'), 'b_spatial.png', 'spatial'
+ );
+ }
+ if (! empty($tbl_storage_engine)
+ && ($tbl_storage_engine == 'MYISAM'
+ || $tbl_storage_engine == 'ARIA'
+ || $tbl_storage_engine == 'MARIA')
+ ) {
+ $html_output .= PMA_Util::getButtonOrImage(
+ 'submit_mult', 'mult_submit', 'submit_mult_fulltext',
+ __('Fulltext'), 'b_ftext.png', 'ftext'
+ );
+ }
+ }
+ return $html_output;
+}
+
+/**
+ * Get HTML for move columns dialog
+ *
+ * @return string $html_output
+ */
+function PMA_getHtmlDivForMoveColumnsDialog()
+{
+ $html_output = '<div id="move_columns_dialog" '
+ . 'title="' . __('Move columns') . '" style="display: none">';
+
+ $html_output .= '<p>'
+ . __('Move the columns by dragging them up and down.') . '</p>';
+
+ $html_output .= '<form action="tbl_structure.php">'
+ . '<div>'
+ . PMA_URL_getHiddenInputs($GLOBALS['db'], $GLOBALS['table'])
+ . '<ul></ul>'
+ . '</div>'
+ . '</form>'
+ . '</div>';
+
+ return $html_output;
+}
+
+/**
+ * Get HTML for edit views'
+ *
+ * @param string $url_params URL parameters
+ *
+ * @return string $html_output
+ */
+function PMA_getHtmlForEditView($url_params)
+{
+ $query = "SELECT `VIEW_DEFINITION`, `CHECK_OPTION`, `DEFINER`, `SECURITY_TYPE`"
+ . " FROM `INFORMATION_SCHEMA`.`VIEWS`"
+ . " WHERE TABLE_SCHEMA='" . PMA_Util::sqlAddSlashes($GLOBALS['db']) . "'"
+ . " AND TABLE_NAME='" . PMA_Util::sqlAddSlashes($GLOBALS['table']) . "';";
+ $item = $GLOBALS['dbi']->fetchSingleRow($query);
+
+ $view = array(
+ 'operation' => 'alter',
+ 'definer' => $item['DEFINER'],
+ 'sql_security' => $item['SECURITY_TYPE'],
+ 'name' => $GLOBALS['table'],
+ 'as' => $item['VIEW_DEFINITION'],
+ 'with' => $item['CHECK_OPTION'],
+ );
+ $url = 'view_create.php' . PMA_URL_getCommon($url_params) . '&amp;';
+ $url .= implode(
+ '&amp;',
+ array_map(
+ function ($key, $val) {
+ return 'view[' . urlencode($key) . ']=' . urlencode($val);
+ },
+ array_keys($view),
+ $view
+ )
+ );
+ $html_output = PMA_Util::linkOrButton(
+ $url,
+ PMA_Util::getIcon('b_edit.png', __('Edit view'), true)
+ );
+ return $html_output;
+}
+
+/**
+ * Get HTML links for 'Print view', 'Relation view', 'Propose table structure',
+ * 'Track table' and 'Move columns'
+ *
+ * @param string $url_query url query
+ * @param boolean $tbl_is_view whether table is view or not
+ * @param boolean $db_is_information_schema whether db is information schema or not
+ * @param string $tbl_storage_engine table storage engine
+ * @param array $cfgRelation current relation parameters
+ *
+ * @return string $html_output
+ */
+function PMA_getHtmlForOptionalActionLinks($url_query, $tbl_is_view,
+ $db_is_information_schema, $tbl_storage_engine, $cfgRelation
+) {
+ $html_output = '<a href="tbl_printview.php?' . $url_query . '" target="print_view">'
+ . PMA_Util::getIcon('b_print.png', __('Print view'), true)
+ . '</a>';
+
+ if (! $tbl_is_view && ! $db_is_information_schema) {
+ // if internal relations are available, or foreign keys are supported
+ // ($tbl_storage_engine comes from libraries/tbl_info.inc.php
+
+ if ($cfgRelation['relwork']
+ || PMA_Util::isForeignKeySupported($tbl_storage_engine)
+ ) {
+ $html_output .= '<a href="tbl_relation.php?' . $url_query . '">'
+ . PMA_Util::getIcon(
+ 'b_relations.png', __('Relation view'), true
+ )
+ . '</a>';
+ }
+ if (!PMA_DRIZZLE) {
+ $html_output .= '<a href="sql.php?' . $url_query
+ . '&amp;session_max_rows=all&amp;sql_query=' . urlencode(
+ 'SELECT * FROM ' . PMA_Util::backquote($GLOBALS['table'])
+ . ' PROCEDURE ANALYSE()'
+ ) . '">'
+ . PMA_Util::getIcon(
+ 'b_tblanalyse.png',
+ __('Propose table structure'),
+ true
+ )
+ . '</a>';
+ $html_output .= PMA_Util::showMySQLDocu('procedure_analyse') . "\n";
+ }
+ if (PMA_Tracker::isActive()) {
+ $html_output .= '<a href="tbl_tracking.php?' . $url_query . '">'
+ . PMA_Util::getIcon('eye.png', __('Track table'), true)
+ . '</a>';
+ }
+ $html_output .= '<a href="#" id="move_columns_anchor">'
+ . PMA_Util::getIcon('b_move.png', __('Move columns'), true)
+ . '</a>';
+ }
+
+ return $html_output;
+}
+
+/**
+ * Get HTML snippet for "Add column" feature in structure table
+ *
+ * @param array $columns_list column list array
+ *
+ * @return string $html_output
+ */
+function PMA_getHtmlForAddColumn($columns_list)
+{
+ $html_output = '<form method="post" action="tbl_addfield.php" '
+ . 'id="addColumns" name="addColumns" '
+ . 'onsubmit="return checkFormElementInRange('
+ . 'this, \'num_fields\', \'' . str_replace(
+ '\'',
+ '\\\'',
+ __('You have to add at least one column.')
+ ) . '\', 1)'
+ . '">';
+
+ $html_output .= PMA_URL_getHiddenInputs(
+ $GLOBALS['db'],
+ $GLOBALS['table']
+ );
+ if (PMA_Util::showIcons('ActionLinksMode')) {
+ $html_output .=PMA_Util::getImage(
+ 'b_insrow.png',
+ __('Add column')
+ );
+ }
+ $num_fields = '<input type="number" name="num_fields" size="2" '
+ . 'maxlength="2" value="1" onfocus="this.select()" '
+ . 'min="1" required />';
+ $html_output .= sprintf(__('Add %s column(s)'), $num_fields);
+
+ // I tried displaying the drop-down inside the label but with Firefox
+ // the drop-down was blinking
+ $column_selector = '<select name="after_field" '
+ . 'onclick="this.form.field_where[2].checked=true" '
+ . 'onchange="this.form.field_where[2].checked=true">';
+
+ foreach ($columns_list as $one_column_name) {
+ $column_selector .= '<option '
+ . 'value="' . htmlspecialchars($one_column_name) . '">'
+ . htmlspecialchars($one_column_name)
+ . '</option>';
+ }
+ $column_selector .= '</select>';
+
+ $choices = array(
+ 'last' => __('At End of Table'),
+ 'first' => __('At Beginning of Table'),
+ 'after' => sprintf(__('After %s'), '')
+ );
+ $html_output .= PMA_Util::getRadioFields(
+ 'field_where', $choices, 'last', false
+ );
+ $html_output .= $column_selector;
+ $html_output .= '<input type="submit" value="' . __('Go') . '" />'
+ . '</form>';
+
+ return $html_output;
+}
+
+/**
+ * Get HTML snippet for table rows in the Information ->Space usage table
+ *
+ * @param boolean $odd_row whether current row is odd or even
+ * @param string $name type of usage
+ * @param string $value value of usage
+ * @param string $unit unit
+ *
+ * @return string $html_output
+ */
+function PMA_getHtmlForSpaceUsageTableRow($odd_row, $name, $value, $unit)
+{
+ $html_output = '<tr class="' . (($odd_row = !$odd_row) ? 'odd' : 'even') . '">';
+ $html_output .= '<th class="name">' . $name . '</th>';
+ $html_output .= '<td class="value">' . $value . '</td>';
+ $html_output .= '<td class="unit">' . $unit . '</td>';
+ $html_output .= '</tr>';
+
+ return $html_output;
+}
+
+/**
+ * Get HTML for Optimize link if overhead in Information fieldset
+ *
+ * @param string $url_query URL query
+ *
+ * @return string $html_output
+ */
+function PMA_getHtmlForOptimizeLink($url_query)
+{
+ $html_output = '<tr class="tblFooters">';
+ $html_output .= '<td colspan="3" class="center">';
+ $html_output .= '<a href="sql.php?' . $url_query
+ . '&pos=0&amp;sql_query=' . urlencode(
+ 'OPTIMIZE TABLE ' . PMA_Util::backquote($GLOBALS['table'])
+ )
+ . '">'
+ . PMA_Util::getIcon('b_tbloptimize.png', __('Optimize table'))
+ . '</a>';
+ $html_output .= '</td>';
+ $html_output .= '</tr>';
+
+ return $html_output;
+}
+
+/**
+ * Get HTML for 'Row statistics' table row
+ *
+ * @param boolean $odd_row whether current row is odd or even
+ * @param string $name statement name
+ * @param mixed $value value
+ *
+ * @return string $html_output
+ */
+function PMA_getHtmlForRowStatsTableRow($odd_row, $name, $value)
+{
+ $html_output = '<tr class="' . (($odd_row = !$odd_row) ? 'odd' : 'even') . '">';
+ $html_output .= '<th class="name">' . $name . '</th>';
+ $html_output .= '<td class="value">' . $value . '</td>';
+ $html_output .= '</tr>';
+
+ return $html_output;
+}
+
+/**
+ * Get HTML snippet for display Row statistics table
+ *
+ * @param array $showtable show table array
+ * @param string $tbl_collation table collation
+ * @param boolean $is_innodb whether table is innob or not
+ * @param boolean $mergetable Checks if current table is a merge table
+ * @param integer $avg_size average size
+ * @param string $avg_unit average unit
+ *
+ * @return string $html_output
+ */
+function getHtmlForRowStatsTable($showtable, $tbl_collation,
+ $is_innodb, $mergetable, $avg_size, $avg_unit
+) {
+ $odd_row = false;
+ $html_output = '<table id="tablerowstats" class="data">';
+ $html_output .= '<caption class="tblHeaders">'
+ . __('Row statistics') . '</caption>';
+ $html_output .= '<tbody>';
+
+ if (isset($showtable['Row_format'])) {
+ if ($showtable['Row_format'] == 'Fixed') {
+ $value = __('static');
+ } elseif ($showtable['Row_format'] == 'Dynamic') {
+ $value = __('dynamic');
+ } else {
+ $value = $showtable['Row_format'];
+ }
+ $html_output .= PMA_getHtmlForRowStatsTableRow(
+ $odd_row, __('Format'), $value
+ );
+ $odd_row = !$odd_row;
+ }
+ if (! empty($showtable['Create_options'])) {
+ if ($showtable['Create_options'] == 'partitioned') {
+ $value = __('partitioned');
+ } else {
+ $value = $showtable['Create_options'];
+ }
+ $html_output .= PMA_getHtmlForRowStatsTableRow(
+ $odd_row, __('Options'), $value
+ );
+ $odd_row = !$odd_row;
+ }
+ if (!empty($tbl_collation)) {
+ $value = '<dfn title="' . PMA_getCollationDescr($tbl_collation) . '">'
+ . $tbl_collation . '</dfn>';
+ $html_output .= PMA_getHtmlForRowStatsTableRow(
+ $odd_row, __('Collation'), $value
+ );
+ $odd_row = !$odd_row;
+ }
+ if (!$is_innodb && isset($showtable['Rows'])) {
+ $html_output .= PMA_getHtmlForRowStatsTableRow(
+ $odd_row,
+ __('Rows'),
+ PMA_Util::formatNumber($showtable['Rows'], 0)
+ );
+ $odd_row = !$odd_row;
+ }
+ if (!$is_innodb
+ && isset($showtable['Avg_row_length'])
+ && $showtable['Avg_row_length'] > 0
+ ) {
+ list($avg_row_length_value, $avg_row_length_unit)
+ = PMA_Util::formatByteDown(
+ $showtable['Avg_row_length'],
+ 6,
+ 1
+ );
+ $html_output .= PMA_getHtmlForRowStatsTableRow(
+ $odd_row,
+ __('Row length'),
+ ($avg_row_length_value . ' ' . $avg_row_length_unit)
+ );
+ unset($avg_row_length_value, $avg_row_length_unit);
+ $odd_row = !$odd_row;
+ }
+ if (!$is_innodb
+ && isset($showtable['Data_length'])
+ && $showtable['Rows'] > 0
+ && $mergetable == false
+ ) {
+ $html_output .= PMA_getHtmlForRowStatsTableRow(
+ $odd_row,
+ __('Row size'),
+ ($avg_size . ' ' . $avg_unit)
+ );
+ $odd_row = !$odd_row;
+ }
+ if (isset($showtable['Auto_increment'])) {
+ $html_output .= PMA_getHtmlForRowStatsTableRow(
+ $odd_row,
+ __('Next autoindex'),
+ PMA_Util::formatNumber($showtable['Auto_increment'], 0)
+ );
+ $odd_row = !$odd_row;
+ }
+ if (isset($showtable['Create_time'])) {
+ $html_output .= PMA_getHtmlForRowStatsTableRow(
+ $odd_row,
+ __('Creation'),
+ PMA_Util::localisedDate(strtotime($showtable['Create_time']))
+ );
+ $odd_row = !$odd_row;
+ }
+ if (isset($showtable['Update_time'])) {
+ $html_output .= PMA_getHtmlForRowStatsTableRow(
+ $odd_row,
+ __('Last update'),
+ PMA_Util::localisedDate(strtotime($showtable['Update_time']))
+ );
+ $odd_row = !$odd_row;
+ }
+ if (isset($showtable['Check_time'])) {
+ $html_output .= PMA_getHtmlForRowStatsTableRow(
+ $odd_row,
+ __('Last check'),
+ PMA_Util::localisedDate(strtotime($showtable['Check_time']))
+ );
+ }
+ $html_output .= '</tbody>'
+ . '</table>';
+
+ return $html_output;
+}
+
+/**
+ * Get HTML snippet for action row in structure table,
+ * This function returns common HTML <td> for Primary, Unique, Index,
+ * Spatial actions
+ *
+ * @param array $type column type
+ * @param array $tbl_storage_engine table storage engine
+ * @param string $class class attribute for <td>
+ * @param boolean $hasField has field
+ * @param boolean $hasLinkClass has <a> the class attribute
+ * @param string $url_query url query
+ * @param boolean $primary primary if set, false otherwise
+ * @param string $syntax Sql syntax
+ * @param string $message message to show
+ * @param string $action action
+ * @param array $titles titles array
+ * @param array $row current row
+ * @param boolean $isPrimary is primary action
+ *
+ * @return array $html_output, $action_enabled
+ */
+function PMA_getHtmlForActionRowInStructureTable($type, $tbl_storage_engine,
+ $class, $hasField, $hasLinkClass, $url_query, $primary, $syntax,
+ $message, $action, $titles, $row, $isPrimary
+) {
+ $html_output = '<li class="'. $class .'">';
+
+ if ($type == 'text'
+ || $type == 'blob'
+ || 'ARCHIVE' == $tbl_storage_engine
+ || $hasField
+ ) {
+ $html_output .= $titles['No' . $action];
+ $action_enabled = false;
+ } else {
+ $html_output .= '<a rel="samepage" '
+ . ($hasLinkClass ? 'class="ajax add_primary_key_anchor" ' :
+ ($action=='Index' ? 'class="ajax add_index_anchor"' :
+ ($action=='Unique' ? 'class="ajax add_unique_anchor"' : ' ')
+ )
+ )
+ . 'href="sql.php?' . $url_query . '&amp;sql_query='
+ . urlencode(
+ 'ALTER TABLE ' . PMA_Util::backquote($GLOBALS['table'])
+ . ($isPrimary ? ($primary ? ' DROP PRIMARY KEY,' : '') : '')
+ . ' ' . $syntax . '('
+ . PMA_Util::backquote($row['Field']) . ');'
+ )
+ . '&amp;message_to_show=' . urlencode(
+ sprintf(
+ $message,
+ htmlspecialchars($row['Field'])
+ )
+ ) . '" >'
+ . $titles[$action] . '</a>';
+ $action_enabled = true;
+ }
+ $html_output .= '</li>';
+
+ return array($html_output, $action_enabled);
+}
+
+/**
+ * Get HTML for fulltext action,
+ * and this function returns $fulltext_enabled boolean value also
+ *
+ * @param string $tbl_storage_engine table storage engine
+ * @param string $type column type
+ * @param string $url_query url query
+ * @param array $row current row
+ * @param array $titles titles array
+ *
+ * @return array $html_output, $fulltext_enabled
+ */
+function PMA_getHtmlForFullTextAction($tbl_storage_engine, $type, $url_query,
+ $row, $titles
+) {
+ $html_output = '<li class="fulltext nowrap">';
+ if (! empty($tbl_storage_engine)
+ && ($tbl_storage_engine == 'MYISAM'
+ || $tbl_storage_engine == 'ARIA'
+ || $tbl_storage_engine == 'MARIA'
+ || ($tbl_storage_engine == 'INNODB' && PMA_MYSQL_INT_VERSION >= 50604))
+ && (strpos(' ' . $type, 'text') || strpos(' ' . $type, 'char'))
+ ) {
+ $html_output .= '<a rel="samepage" href="sql.php?' . $url_query . '&amp;sql_query='
+ . urlencode(
+ 'ALTER TABLE ' . PMA_Util::backquote($GLOBALS['table'])
+ . ' ADD FULLTEXT(' . PMA_Util::backquote($row['Field'])
+ . ');'
+ )
+ . '&amp;message_to_show='
+ . urlencode(
+ sprintf(
+ __('An index has been added on %s'),
+ htmlspecialchars($row['Field'])
+ )
+ )
+ . '">';
+ $html_output .= $titles['IdxFulltext'] . '</a>';
+ $fulltext_enabled = true;
+ } else {
+ $html_output .= $titles['NoIdxFulltext'];
+ $fulltext_enabled = false;
+ }
+ $html_output .= '</li>';
+ return array($html_output, $fulltext_enabled);
+}
+
+/**
+ * Get HTML snippet for "Distinc Value" action
+ *
+ * @param string $url_query url query
+ * @param array $row current row
+ * @param array $titles titles array
+ *
+ * @return string $html_output
+ */
+function PMA_getHtmlForDistinctValueAction($url_query, $row, $titles)
+{
+ $html_output = '<li class="browse nowrap">';
+ $html_output .= '<a href="sql.php?' . $url_query . '&amp;sql_query='
+ . urlencode(
+ 'SELECT COUNT(*) AS ' . PMA_Util::backquote(__('Rows'))
+ . ', ' . PMA_Util::backquote($row['Field'])
+ . ' FROM ' . PMA_Util::backquote($GLOBALS['table'])
+ . ' GROUP BY ' . PMA_Util::backquote($row['Field'])
+ . ' ORDER BY ' . PMA_Util::backquote($row['Field'])
+ )
+ . '">'
+ . $titles['DistinctValues']
+ . '</a>';
+ $html_output .= '</li>';
+
+ return $html_output;
+}
+
+/**
+ * Get HTML snippet for Actions in table structure
+ *
+ * @param string $type column type
+ * @param string $tbl_storage_engine table storage engine
+ * @param boolean $primary primary if set, false otherwise
+ * @param string $field_name column name
+ * @param string $url_query url query
+ * @param array $titles titles array
+ * @param array $row current row
+ * @param string $rownum row number
+ * @param array $hidden_titles hidden titles
+ * @param array $columns_with_unique_index columns with unique index
+ *
+ * @return string $html_output;
+ */
+function PMA_getHtmlForActionsInTableStructure($type, $tbl_storage_engine,
+ $primary, $field_name, $url_query, $titles, $row, $rownum, $hidden_titles,
+ $columns_with_unique_index
+) {
+ $html_output = '<td><ul class="table-structure-actions resizable-menu">';
+ list($primary, $primary_enabled)
+ = PMA_getHtmlForActionRowInStructureTable(
+ $type, $tbl_storage_engine,
+ 'primary nowrap',
+ ($primary && $primary->hasColumn($field_name)),
+ true, $url_query, $primary,
+ 'ADD PRIMARY KEY',
+ __('A primary key has been added on %s'),
+ 'Primary', $titles, $row, true
+ );
+ $html_output .= $primary;
+ list($unique, $unique_enabled)
+ = PMA_getHtmlForActionRowInStructureTable(
+ $type, $tbl_storage_engine,
+ 'add_unique nowrap',
+ isset($columns_with_unique_index[$field_name]),
+ false, $url_query, $primary, 'ADD UNIQUE',
+ __('An index has been added on %s'),
+ 'Unique', $titles, $row, false
+ );
+ $html_output .= $unique;
+ list($index, $index_enabled)
+ = PMA_getHtmlForActionRowInStructureTable(
+ $type, $tbl_storage_engine,
+ 'add_index nowrap', false, false, $url_query,
+ $primary, 'ADD INDEX', __('An index has been added on %s'),
+ 'Index', $titles, $row, false
+ );
+ $html_output .= $index;
+ if (!PMA_DRIZZLE) {
+ $spatial_types = array(
+ 'geometry', 'point', 'linestring', 'polygon', 'multipoint',
+ 'multilinestring', 'multipolygon', 'geomtrycollection'
+ );
+ list($spatial, $spatial_enabled)
+ = PMA_getHtmlForActionRowInStructureTable(
+ $type, $tbl_storage_engine,
+ 'spatial nowrap',
+ (! in_array($type, $spatial_types)
+ || 'MYISAM' != $tbl_storage_engine
+ ),
+ false, $url_query, $primary, 'ADD SPATIAL',
+ __('An index has been added on %s'), 'Spatial',
+ $titles, $row, false
+ );
+ $html_output .= $spatial;
+
+ // FULLTEXT is possible on TEXT, CHAR and VARCHAR
+ list ($fulltext, $fulltext_enabled) = PMA_getHtmlForFullTextAction(
+ $tbl_storage_engine, $type, $url_query, $row, $titles
+ );
+ $html_output .= $fulltext;
+ }
+ $html_output .= PMA_getHtmlForDistinctValueAction($url_query, $row, $titles);
+ $html_output .= '</ul></td>';
+ return $html_output;
+}
+
+/**
+ * Get hidden action titles (image and string)
+ *
+ * @return array $hidden_titles
+ */
+function PMA_getHiddenTitlesArray()
+{
+ $hidden_titles = array();
+ $hidden_titles['DistinctValues'] = PMA_Util::getIcon(
+ 'b_browse.png', __('Distinct values'), true
+ );
+ $hidden_titles['Primary'] = PMA_Util::getIcon(
+ 'b_primary.png', __('Add primary key'), true
+ );
+ $hidden_titles['NoPrimary'] = PMA_Util::getIcon(
+ 'bd_primary.png', __('Add primary key'), true
+ );
+ $hidden_titles['Index'] = PMA_Util::getIcon(
+ 'b_index.png', __('Add index'), true
+ );
+ $hidden_titles['NoIndex'] = PMA_Util::getIcon(
+ 'bd_index.png', __('Add index'), true
+ );
+ $hidden_titles['Unique'] = PMA_Util::getIcon(
+ 'b_unique.png', __('Add unique index'), true
+ );
+ $hidden_titles['NoUnique'] = PMA_Util::getIcon(
+ 'bd_unique.png', __('Add unique index'), true
+ );
+ $hidden_titles['Spatial'] = PMA_Util::getIcon(
+ 'b_spatial.png', __('Add SPATIAL index'), true
+ );
+ $hidden_titles['NoSpatial'] = PMA_Util::getIcon(
+ 'bd_spatial.png', __('Add SPATIAL index'), true
+ );
+ $hidden_titles['IdxFulltext'] = PMA_Util::getIcon(
+ 'b_ftext.png', __('Add FULLTEXT index'), true
+ );
+ $hidden_titles['NoIdxFulltext'] = PMA_Util::getIcon(
+ 'bd_ftext.png', __('Add FULLTEXT index'), true
+ );
+
+ return $hidden_titles;
+}
+
+/**
+ * Get action titles (image or string array
+ *
+ * @return array $titles
+ */
+function PMA_getActionTitlesArray()
+{
+ $titles = array();
+ $titles['Change']
+ = PMA_Util::getIcon('b_edit.png', __('Change'));
+ $titles['Drop']
+ = PMA_Util::getIcon('b_drop.png', __('Drop'));
+ $titles['NoDrop']
+ = PMA_Util::getIcon('b_drop.png', __('Drop'));
+ $titles['Primary']
+ = PMA_Util::getIcon('b_primary.png', __('Primary'));
+ $titles['Index']
+ = PMA_Util::getIcon('b_index.png', __('Index'));
+ $titles['Unique']
+ = PMA_Util::getIcon('b_unique.png', __('Unique'));
+ $titles['Spatial']
+ = PMA_Util::getIcon('b_spatial.png', __('Spatial'));
+ $titles['IdxFulltext']
+ = PMA_Util::getIcon('b_ftext.png', __('Fulltext'));
+ $titles['NoPrimary']
+ = PMA_Util::getIcon('bd_primary.png', __('Primary'));
+ $titles['NoIndex']
+ = PMA_Util::getIcon('bd_index.png', __('Index'));
+ $titles['NoUnique']
+ = PMA_Util::getIcon('bd_unique.png', __('Unique'));
+ $titles['NoSpatial']
+ = PMA_Util::getIcon('bd_spatial.png', __('Spatial'));
+ $titles['NoIdxFulltext']
+ = PMA_Util::getIcon('bd_ftext.png', __('Fulltext'));
+ $titles['DistinctValues']
+ = PMA_Util::getIcon('b_browse.png', __('Distinct values'));
+
+ return $titles;
+}
+
+/**
+ * Get HTML snippet for display table statistics
+ *
+ * @param array $showtable full table status info
+ * @param integer $table_info_num_rows table info number of rows
+ * @param boolean $tbl_is_view whether table is view or not
+ * @param boolean $db_is_information_schema whether db is information schema or not
+ * @param string $tbl_storage_engine table storage engine
+ * @param string $url_query url query
+ * @param string $tbl_collation table collation
+ *
+ * @return string $html_output
+ */
+function PMA_getHtmlForDisplayTableStats($showtable, $table_info_num_rows,
+ $tbl_is_view, $db_is_information_schema, $tbl_storage_engine, $url_query,
+ $tbl_collation
+) {
+ $html_output = '<div id="tablestatistics">';
+ if (empty($showtable)) {
+ $showtable = PMA_Table::sGetStatusInfo(
+ $GLOBALS['db'], $GLOBALS['table'], null, true
+ );
+ }
+
+ $nonisam = false;
+ $is_innodb = (isset($showtable['Type']) && $showtable['Type'] == 'InnoDB');
+ if (isset($showtable['Type'])
+ && ! preg_match('@ISAM|HEAP@i', $showtable['Type'])
+ ) {
+ $nonisam = true;
+ }
+
+ // Gets some sizes
+
+ $mergetable = PMA_Table::isMerge($GLOBALS['db'], $GLOBALS['table']);
+
+ // this is to display for example 261.2 MiB instead of 268k KiB
+ $max_digits = 3;
+ $decimals = 1;
+ list($data_size, $data_unit) = PMA_Util::formatByteDown(
+ $showtable['Data_length'], $max_digits, $decimals
+ );
+ if ($mergetable == false) {
+ list($index_size, $index_unit) = PMA_Util::formatByteDown(
+ $showtable['Index_length'], $max_digits, $decimals
+ );
+ }
+ // InnoDB returns a huge value in Data_free, do not use it
+ if (! $is_innodb
+ && isset($showtable['Data_free'])
+ && $showtable['Data_free'] > 0
+ ) {
+ list($free_size, $free_unit) = PMA_Util::formatByteDown(
+ $showtable['Data_free'], $max_digits, $decimals
+ );
+ list($effect_size, $effect_unit) = PMA_Util::formatByteDown(
+ $showtable['Data_length'] + $showtable['Index_length'] - $showtable['Data_free'],
+ $max_digits, $decimals
+ );
+ } else {
+ list($effect_size, $effect_unit) = PMA_Util::formatByteDown(
+ $showtable['Data_length'] + $showtable['Index_length'],
+ $max_digits, $decimals
+ );
+ }
+ list($tot_size, $tot_unit) = PMA_Util::formatByteDown(
+ $showtable['Data_length'] + $showtable['Index_length'],
+ $max_digits, $decimals
+ );
+ if ($table_info_num_rows > 0) {
+ list($avg_size, $avg_unit) = PMA_Util::formatByteDown(
+ ($showtable['Data_length'] + $showtable['Index_length']) / $showtable['Rows'],
+ 6, 1
+ );
+ }
+
+ // Displays them
+ $odd_row = false;
+
+ $html_output .= '<fieldset>'
+ . '<legend>' . __('Information') . '</legend>'
+ . '<a id="showusage"></a>';
+
+ if (! $tbl_is_view && ! $db_is_information_schema) {
+ $html_output .= '<table id="tablespaceusage" class="data">'
+ . '<caption class="tblHeaders">' . __('Space usage') . '</caption>'
+ . '<tbody>';
+
+ $html_output .= PMA_getHtmlForSpaceUsageTableRow(
+ $odd_row, __('Data'), $data_size, $data_unit
+ );
+ $odd_row = !$odd_row;
+
+ if (isset($index_size)) {
+ $html_output .= PMA_getHtmlForSpaceUsageTableRow(
+ $odd_row, __('Index'), $index_size, $index_unit
+ );
+ $odd_row = !$odd_row;
+ }
+
+ if (isset($free_size)) {
+ $html_output .= PMA_getHtmlForSpaceUsageTableRow(
+ $odd_row, __('Overhead'), $free_size, $free_unit
+ );
+ $html_output .= PMA_getHtmlForSpaceUsageTableRow(
+ $odd_row, __('Effective'), $effect_size, $effect_unit
+ );
+ $odd_row = !$odd_row;
+ }
+ if (isset($tot_size) && $mergetable == false) {
+ $html_output .= PMA_getHtmlForSpaceUsageTableRow(
+ $odd_row, __('Total'), $tot_size, $tot_unit
+ );
+ $odd_row = !$odd_row;
+ }
+ // Optimize link if overhead
+ if (isset($free_size) && !PMA_DRIZZLE
+ && ($tbl_storage_engine == 'MYISAM'
+ || $tbl_storage_engine == 'ARIA'
+ || $tbl_storage_engine == 'MARIA'
+ || $tbl_storage_engine == 'BDB')
+ ) {
+ $html_output .= PMA_getHtmlForOptimizeLink($url_query);
+ }
+ $html_output .= '</tbody>'
+ . '</table>';
+ }
+
+ $html_output .= getHtmlForRowStatsTable(
+ $showtable, $tbl_collation,
+ $is_innodb, $mergetable,
+ (isset ($avg_size) ? $avg_size : ''),
+ (isset ($avg_unit) ? $avg_unit : '')
+ );
+
+ $html_output .= '</fieldset>'
+ . '</div>';
+
+ return $html_output;
+}
+
+/**
+ * Displays HTML for changing one or more columns
+ *
+ * @param string $db database name
+ * @param string $table table name
+ * @param array $selected the selected columns
+ * @param string $action target script to call
+ *
+ * @return boolean $regenerate true if error occurred
+ *
+ */
+function PMA_displayHtmlForColumnChange($db, $table, $selected, $action)
+{
+ // $selected comes from multi_submits.inc.php
+ if (empty($selected)) {
+ $selected[] = $_REQUEST['field'];
+ $selected_cnt = 1;
+ } else { // from a multiple submit
+ $selected_cnt = count($selected);
+ }
+
+ /**
+ * @todo optimize in case of multiple fields to modify
+ */
+ for ($i = 0; $i < $selected_cnt; $i++) {
+ $fields_meta[] = $GLOBALS['dbi']->getColumns($db, $table, $selected[$i], true);
+ }
+ $num_fields = count($fields_meta);
+ // set these globals because tbl_columns_definition_form.inc.php
+ // verifies them
+ // @todo: refactor tbl_columns_definition_form.inc.php so that it uses
+ // function params
+ $GLOBALS['action'] = 'tbl_structure.php';
+ $GLOBALS['num_fields'] = $num_fields;
+
+ // Get more complete field information.
+ // For now, this is done to obtain MySQL 4.1.2+ new TIMESTAMP options
+ // and to know when there is an empty DEFAULT value.
+ // Later, if the analyser returns more information, it
+ // could be executed to replace the info given by SHOW FULL COLUMNS FROM.
+ /**
+ * @todo put this code into a require()
+ * or maybe make it part of $GLOBALS['dbi']->getColumns();
+ */
+
+ // We also need this to correctly learn if a TIMESTAMP is NOT NULL, since
+ // SHOW FULL COLUMNS says NULL and SHOW CREATE TABLE says NOT NULL (tested
+ // in MySQL 4.0.25).
+
+ $show_create_table = $GLOBALS['dbi']->fetchValue(
+ 'SHOW CREATE TABLE ' . PMA_Util::backquote($db) . '.' . PMA_Util::backquote($table),
+ 0, 1
+ );
+ $analyzed_sql = PMA_SQP_analyze(PMA_SQP_parse($show_create_table));
+ unset($show_create_table);
+ /**
+ * Form for changing properties.
+ */
+ include 'libraries/tbl_columns_definition_form.inc.php';
+}
+
+
+/**
+ * Update the table's structure based on $_REQUEST
+ *
+ * @param string $db database name
+ * @param string $table table name
+ *
+ * @return boolean $regenerate true if error occurred
+ *
+ */
+function PMA_updateColumns($db, $table)
+{
+ $err_url = 'tbl_structure.php?' . PMA_URL_getCommon($db, $table);
+ $regenerate = false;
+ $field_cnt = count($_REQUEST['field_name']);
+ $key_fields = array();
+ $changes = array();
+
+ for ($i = 0; $i < $field_cnt; $i++) {
+ $changes[] = 'CHANGE ' . PMA_Table::generateAlter(
+ isset($_REQUEST['field_orig'][$i])
+ ? $_REQUEST['field_orig'][$i]
+ : '',
+ $_REQUEST['field_name'][$i],
+ $_REQUEST['field_type'][$i],
+ $_REQUEST['field_length'][$i],
+ $_REQUEST['field_attribute'][$i],
+ isset($_REQUEST['field_collation'][$i])
+ ? $_REQUEST['field_collation'][$i]
+ : '',
+ isset($_REQUEST['field_null'][$i])
+ ? $_REQUEST['field_null'][$i]
+ : 'NOT NULL',
+ $_REQUEST['field_default_type'][$i],
+ $_REQUEST['field_default_value'][$i],
+ isset($_REQUEST['field_extra'][$i])
+ ? $_REQUEST['field_extra'][$i]
+ : false,
+ isset($_REQUEST['field_comments'][$i])
+ ? $_REQUEST['field_comments'][$i]
+ : '',
+ $key_fields,
+ $i,
+ isset($_REQUEST['field_move_to'][$i])
+ ? $_REQUEST['field_move_to'][$i]
+ : ''
+ );
+ } // end for
+
+ // Builds the primary keys statements and updates the table
+ $key_query = '';
+ /**
+ * this is a little bit more complex
+ *
+ * @todo if someone selects A_I when altering a column we need to check:
+ * - no other column with A_I
+ * - the column has an index, if not create one
+ *
+ if (count($key_fields)) {
+ $fields = array();
+ foreach ($key_fields as $each_field) {
+ if (isset($_REQUEST['field_name'][$each_field]) && strlen($_REQUEST['field_name'][$each_field])) {
+ $fields[] = PMA_Util::backquote($_REQUEST['field_name'][$each_field]);
+ }
+ } // end for
+ $key_query = ', ADD KEY (' . implode(', ', $fields) . ') ';
+ }
+ */
+
+ // To allow replication, we first select the db to use and then run queries
+ // on this db.
+ if (! $GLOBALS['dbi']->selectDb($db)) {
+ PMA_Util::mysqlDie(
+ $GLOBALS['dbi']->getError(),
+ 'USE ' . PMA_Util::backquote($db) . ';',
+ '',
+ $err_url
+ );
+ }
+ $sql_query = 'ALTER TABLE ' . PMA_Util::backquote($table) . ' ';
+ $sql_query .= implode(', ', $changes) . $key_query;
+ $sql_query .= ';';
+ $result = $GLOBALS['dbi']->tryQuery($sql_query);
+
+ $response = PMA_Response::getInstance();
+ if ($result !== false) {
+ $message = PMA_Message::success(
+ __('Table %1$s has been altered successfully')
+ );
+ $message->addParam($table);
+
+ /**
+ * If comments were sent, enable relation stuff
+ */
+ include_once 'libraries/transformations.lib.php';
+
+ // update field names in relation
+ if (isset($_REQUEST['field_orig']) && is_array($_REQUEST['field_orig'])) {
+ foreach ($_REQUEST['field_orig'] as $fieldindex => $fieldcontent) {
+ if ($_REQUEST['field_name'][$fieldindex] != $fieldcontent) {
+ PMA_REL_renameField(
+ $db, $table, $fieldcontent,
+ $_REQUEST['field_name'][$fieldindex]
+ );
+ }
+ }
+ }
+
+ // update mime types
+ if (isset($_REQUEST['field_mimetype'])
+ && is_array($_REQUEST['field_mimetype'])
+ && $GLOBALS['cfg']['BrowseMIME']
+ ) {
+ foreach ($_REQUEST['field_mimetype'] as $fieldindex => $mimetype) {
+ if (isset($_REQUEST['field_name'][$fieldindex])
+ && strlen($_REQUEST['field_name'][$fieldindex])
+ ) {
+ PMA_setMIME(
+ $db, $table, $_REQUEST['field_name'][$fieldindex],
+ $mimetype,
+ $_REQUEST['field_transformation'][$fieldindex],
+ $_REQUEST['field_transformation_options'][$fieldindex]
+ );
+ }
+ }
+ }
+
+ $response->addHTML(
+ PMA_Util::getMessage($message, $sql_query, 'success')
+ );
+ } else {
+ // An error happened while inserting/updating a table definition
+ $response->isSuccess(false);
+ $response->addJSON(
+ 'message',
+ PMA_Message::rawError(__('Query error') . ':<br />'.$GLOBALS['dbi']->getError())
+ );
+ $regenerate = true;
+ }
+ return $regenerate;
+}
+
+/**
+ * Moves columns in the table's structure based on $_REQUEST
+ *
+ * @param string $db database name
+ * @param string $table table name
+ *
+ * @return void
+ */
+function PMA_moveColumns($db, $table)
+{
+ $GLOBALS['dbi']->selectDb($db);
+
+ /*
+ * load the definitions for all columns
+ */
+ $columns = $GLOBALS['dbi']->getColumnsFull($db, $table);
+ $column_names = array_keys($columns);
+ $changes = array();
+ $we_dont_change_keys = array();
+
+ // move columns from first to last
+ for ($i = 0, $l = count($_REQUEST['move_columns']); $i < $l; $i++) {
+ $column = $_REQUEST['move_columns'][$i];
+ // is this column already correctly placed?
+ if ($column_names[$i] == $column) {
+ continue;
+ }
+
+ // it is not, let's move it to index $i
+ $data = $columns[$column];
+ $extracted_columnspec = PMA_Util::extractColumnSpec($data['Type']);
+ if (isset($data['Extra']) && $data['Extra'] == 'on update CURRENT_TIMESTAMP') {
+ $extracted_columnspec['attribute'] = $data['Extra'];
+ unset($data['Extra']);
+ }
+ $current_timestamp = false;
+ if (($data['Type'] == 'timestamp' || $data['Type'] == 'datetime')
+ && $data['Default'] == 'CURRENT_TIMESTAMP'
+ ) {
+ $current_timestamp = true;
+ }
+ $default_type
+ = $data['Null'] === 'YES' && $data['Default'] === null
+ ? 'NULL'
+ : ($current_timestamp
+ ? 'CURRENT_TIMESTAMP'
+ : ($data['Default'] === null
+ ? 'NONE'
+ : 'USER_DEFINED'));
+
+ $changes[] = 'CHANGE ' . PMA_Table::generateAlter(
+ $column,
+ $column,
+ strtoupper($extracted_columnspec['type']),
+ $extracted_columnspec['spec_in_brackets'],
+ $extracted_columnspec['attribute'],
+ isset($data['Collation']) ? $data['Collation'] : '',
+ $data['Null'] === 'YES' ? 'NULL' : 'NOT NULL',
+ $default_type,
+ $current_timestamp ? '' : $data['Default'],
+ isset($data['Extra']) && $data['Extra'] !== '' ? $data['Extra'] : false,
+ isset($data['COLUMN_COMMENT']) && $data['COLUMN_COMMENT'] !== ''
+ ? $data['COLUMN_COMMENT'] : false,
+ $we_dont_change_keys,
+ $i,
+ $i === 0 ? '-first' : $column_names[$i - 1]
+ );
+ // update current column_names array, first delete old position
+ for ($j = 0, $ll = count($column_names); $j < $ll; $j++) {
+ if ($column_names[$j] == $column) {
+ unset($column_names[$j]);
+ }
+ }
+ // insert moved column
+ array_splice($column_names, $i, 0, $column);
+ }
+ $response = PMA_Response::getInstance();
+ if (empty($changes)) { // should never happen
+ $response->isSuccess(false);
+ exit;
+ }
+ $move_query = 'ALTER TABLE ' . PMA_Util::backquote($table) . ' ';
+ $move_query .= implode(', ', $changes);
+ // move columns
+ $GLOBALS['dbi']->tryQuery($move_query);
+ $tmp_error = $GLOBALS['dbi']->getError();
+ if ($tmp_error) {
+ $response->isSuccess(false);
+ $response->addJSON('message', PMA_Message::error($tmp_error));
+ } else {
+ $message = PMA_Message::success(
+ __('The columns have been moved successfully.')
+ );
+ $response->addJSON('message', $message);
+ $response->addJSON('columns', $column_names);
+ }
+ exit;
+}
+
+/**
+ * Get columns with unique index
+ *
+ * @param string $db database name
+ * @param string $table tablename
+ *
+ * @return array $columns_with_unique_index An array of columns with unique index,
+ * with $column name as the array key
+ */
+function PMA_getColumnsWithUniqueIndex($db ,$table)
+{
+ $columns_with_unique_index = array();
+ foreach (PMA_Index::getFromTable($table, $db) as $index) {
+ if ($index->isUnique() && $index->getChoice() == 'UNIQUE') {
+ $columns = $index->getColumns();
+ foreach ($columns as $column_name => $dummy) {
+ $columns_with_unique_index[$column_name] = 1;
+ }
+ }
+ }
+ return $columns_with_unique_index;
+}
+
+/**
+ * Check column names for MySQL reserved words
+ *
+ * @param string $db database name
+ * @param string $table tablename
+ *
+ * @return array $messages array of PMA_Messages
+ */
+function PMA_getReservedWordColumnNameMessages($db ,$table)
+{
+ $messages = array();
+ if ($GLOBALS['cfg']['ReservedWordDisableWarning'] === false) {
+ $pma_table = new PMA_Table($table, $db);
+ $columns = $pma_table->getReservedColumnNames();
+ if (!empty($columns)) {
+ foreach ($columns as $column) {
+ $msg = PMA_message::notice(
+ __('The column name \'%s\' is a MySQL reserved keyword.')
+ );
+ $msg->addParam($column);
+ $messages[] = $msg;
+ }
+ }
+ }
+ return $messages;
+}
+
+/**
+ * Function to get the type of command for multiple field handling
+ *
+ * @return string
+ */
+function PMA_getMultipleFieldCommandType()
+{
+ $submit_mult = null;
+
+ if (isset($_REQUEST['submit_mult_change_x'])) {
+ $submit_mult = 'change';
+ } elseif (isset($_REQUEST['submit_mult_drop_x'])) {
+ $submit_mult = 'drop';
+ } elseif (isset($_REQUEST['submit_mult_primary_x'])) {
+ $submit_mult = 'primary';
+ } elseif (isset($_REQUEST['submit_mult_index_x'])) {
+ $submit_mult = 'index';
+ } elseif (isset($_REQUEST['submit_mult_unique_x'])) {
+ $submit_mult = 'unique';
+ } elseif (isset($_REQUEST['submit_mult_spatial_x'])) {
+ $submit_mult = 'spatial';
+ } elseif (isset($_REQUEST['submit_mult_fulltext_x'])) {
+ $submit_mult = 'ftext';
+ } elseif (isset($_REQUEST['submit_mult_browse_x'])) {
+ $submit_mult = 'browse';
+ } elseif (isset($_REQUEST['submit_mult'])) {
+ $submit_mult = $_REQUEST['submit_mult'];
+ } elseif (isset($_REQUEST['mult_btn']) && $_REQUEST['mult_btn'] == __('Yes')) {
+ $submit_mult = 'row_delete';
+ if (isset($_REQUEST['selected'])) {
+ $_REQUEST['selected_fld'] = $_REQUEST['selected'];
+ }
+ }
+
+ return $submit_mult;
+}
+
+/**
+ * Function to display table browse for selected columns
+ *
+ * @param string $db current database
+ * @param string $table current table
+ * @param string $goto goto page url
+ * @param string $pmaThemeImage URI of the pma theme image
+ *
+ * @return void
+ */
+function PMA_displayTableBrowseForSelectedColumns($db, $table, $goto,
+ $pmaThemeImage
+) {
+ $GLOBALS['active_page'] = 'sql.php';
+ $sql_query = '';
+ foreach ($_REQUEST['selected_fld'] as $idx => $sval) {
+ if ($sql_query == '') {
+ $sql_query .= 'SELECT ' . PMA_Util::backquote($sval);
+ } else {
+ $sql_query .= ', ' . PMA_Util::backquote($sval);
+ }
+ }
+ $sql_query .= ' FROM ' . PMA_Util::backquote($db)
+ . '.' . PMA_Util::backquote($table);
+
+ // Parse and analyze the query
+ include_once 'libraries/parse_analyze.inc.php';
+
+ include_once 'libraries/sql.lib.php';
+
+ PMA_executeQueryAndSendQueryResponse(
+ $analyzed_sql_results, false, $db, $table, null, null, null, false,
+ null, null, null, null, $goto, $pmaThemeImage, null, null,
+ null, $sql_query, null, null
+ );
+}
+?>
diff --git a/libraries/sysinfo.lib.php b/libraries/sysinfo.lib.php
new file mode 100644
index 0000000000..72db3738c2
--- /dev/null
+++ b/libraries/sysinfo.lib.php
@@ -0,0 +1,361 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Library for extracting information about system memory and cpu.
+ * Currently supports all Windows and Linux plattforms
+ *
+ * This code is based on the OS Classes from the phpsysinfo project
+ * (http://phpsysinfo.sourceforge.net/)
+ *
+ * @package PhpMyAdmin-sysinfo
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+define(
+ 'MEMORY_REGEXP',
+ '/^(MemTotal|MemFree|Cached|Buffers|SwapCached|SwapTotal|SwapFree):'
+ . '\s+(.*)\s*kB/im'
+);
+
+/**
+ * Returns OS type used for sysinfo class
+ *
+ * @param string $php_os PHP_OS constant
+ *
+ * @return string
+ */
+function PMA_getSysInfoOs($php_os = PHP_OS)
+{
+
+ // look for common UNIX-like systems
+ $unix_like = array('FreeBSD', 'DragonFly');
+ if (in_array($php_os, $unix_like)) {
+ $php_os = 'Linux';
+ }
+
+ return ucfirst($php_os);
+}
+
+/**
+ * Gets sysinfo class mathing current OS
+ *
+ * @return PMA_SysInfo|mixed sysinfo class
+ */
+function PMA_getSysInfo()
+{
+ $php_os = PMA_getSysInfoOs();
+ $supported = array('Linux', 'WINNT', 'SunOS');
+
+ if (in_array($php_os, $supported)) {
+ $class_name = 'PMA_SysInfo' . $php_os;
+ $ret = new $class_name();
+ if ($ret->supported()) {
+ return $ret;
+ }
+ }
+
+ return new PMA_SysInfo();
+}
+
+/**
+ * Basic sysinfo class not providing any real data.
+ *
+ * @package PhpMyAdmin-sysinfo
+ */
+class PMA_SysInfo
+{
+ public $os = PHP_OS;
+
+ /**
+ * Gets load information
+ *
+ * @return array with load data
+ */
+ public function loadavg()
+ {
+ return array('loadavg' => 0);
+ }
+
+ /**
+ * Gets information about memory usage
+ *
+ * @return array with memory usage data
+ */
+ public function memory()
+ {
+ return array();
+ }
+
+ /**
+ * Checks whether class is supported in this environment
+ *
+ * @return true on success
+ */
+ public function supported()
+ {
+ return true;
+ }
+}
+
+/**
+ * Windows NT based SysInfo class
+ *
+ * @package PhpMyAdmin-sysinfo
+ */
+class PMA_SysInfoWinnt extends PMA_SysInfo
+{
+ private $_wmi;
+
+ public $os = 'WINNT';
+
+ /**
+ * Constructor to access to wmi database.
+ */
+ public function __construct()
+ {
+ if (!class_exists('COM')) {
+ $this->_wmi = null;
+ } else {
+ // initialize the wmi object
+ $objLocator = new COM('WbemScripting.SWbemLocator');
+ $this->_wmi = $objLocator->ConnectServer();
+ }
+ }
+
+ /**
+ * Gets load information
+ *
+ * @return array with load data
+ */
+ function loadavg()
+ {
+ $loadavg = "";
+ $sum = 0;
+ $buffer = $this->_getWMI('Win32_Processor', array('LoadPercentage'));
+
+ foreach ($buffer as $load) {
+ $value = $load['LoadPercentage'];
+ $loadavg .= $value . ' ';
+ $sum += $value;
+ }
+
+ return array('loadavg' => $sum / count($buffer));
+ }
+
+ /**
+ * Checks whether class is supported in this environment
+ *
+ * @return true on success
+ */
+ public function supported()
+ {
+ return !is_null($this->_wmi);
+ }
+
+ /**
+ * Reads data from WMI
+ *
+ * @param string $strClass Class to read
+ * @param array $strValue Values to read
+ *
+ * @return array with results
+ */
+ private function _getWMI($strClass, $strValue = array())
+ {
+ $arrData = array();
+ $value = "";
+
+ $objWEBM = $this->_wmi->Get($strClass);
+ $arrProp = $objWEBM->Properties_;
+ $arrWEBMCol = $objWEBM->Instances_();
+ foreach ($arrWEBMCol as $objItem) {
+ if (is_array($arrProp)) {
+ reset($arrProp);
+ }
+ $arrInstance = array();
+ foreach ($arrProp as $propItem) {
+ $name = $propItem->Name;
+ if ( empty($strValue) || in_array($name, $strValue)) {
+ $value = $objItem->$name;
+ $arrInstance[$name] = trim($value);
+ }
+ }
+ $arrData[] = $arrInstance;
+ }
+ return $arrData;
+ }
+
+ /**
+ * Gets information about memory usage
+ *
+ * @return array with memory usage data
+ */
+ function memory()
+ {
+ $buffer = $this->_getWMI(
+ "Win32_OperatingSystem",
+ array('TotalVisibleMemorySize', 'FreePhysicalMemory')
+ );
+ $mem = Array();
+ $mem['MemTotal'] = $buffer[0]['TotalVisibleMemorySize'];
+ $mem['MemFree'] = $buffer[0]['FreePhysicalMemory'];
+ $mem['MemUsed'] = $mem['MemTotal'] - $mem['MemFree'];
+
+ $buffer = $this->_getWMI('Win32_PageFileUsage');
+
+ $mem['SwapTotal'] = 0;
+ $mem['SwapUsed'] = 0;
+ $mem['SwapPeak'] = 0;
+
+ foreach ($buffer as $swapdevice) {
+ $mem['SwapTotal'] += $swapdevice['AllocatedBaseSize'] * 1024;
+ $mem['SwapUsed'] += $swapdevice['CurrentUsage'] * 1024;
+ $mem['SwapPeak'] += $swapdevice['PeakUsage'] * 1024;
+ }
+
+ return $mem;
+ }
+}
+
+/**
+ * Linux based SysInfo class
+ *
+ * @package PhpMyAdmin-sysinfo
+ */
+class PMA_SysInfoLinux extends PMA_SysInfo
+{
+ public $os = 'Linux';
+
+ /**
+ * Gets load information
+ *
+ * @return array with load data
+ */
+ function loadavg()
+ {
+ $buf = file_get_contents('/proc/stat');
+ $nums = preg_split("/\s+/", substr($buf, 0, strpos($buf, "\n")));
+ return Array(
+ 'busy' => $nums[1] + $nums[2] + $nums[3],
+ 'idle' => intval($nums[4])
+ );
+ }
+
+ /**
+ * Checks whether class is supported in this environment
+ *
+ * @return true on success
+ */
+ public function supported()
+ {
+ return is_readable('/proc/meminfo') && is_readable('/proc/stat');
+ }
+
+
+ /**
+ * Gets information about memory usage
+ *
+ * @return array with memory usage data
+ */
+ function memory()
+ {
+ preg_match_all(
+ MEMORY_REGEXP,
+ file_get_contents('/proc/meminfo'),
+ $matches
+ );
+
+ $mem = array_combine($matches[1], $matches[2]);
+
+ $memTotal = isset($mem['MemTotal']) ? $mem['MemTotal'] : 0;
+ $memFree = isset($mem['MemFree']) ? $mem['MemFree'] : 0;
+ $cached = isset($mem['Cached']) ? $mem['Cached'] : 0;
+ $buffers = isset($mem['Buffers']) ? $mem['Buffers'] : 0;
+ $swapTotal = isset($mem['SwapTotal']) ? $mem['SwapTotal'] : 0;
+ $swapFree = isset($mem['SwapFree']) ? $mem['SwapFree'] : 0;
+ $swapCached = isset($mem['SwapCached']) ? $mem['SwapCached'] : 0;
+
+ $mem['MemUsed']
+ = $memTotal - $memFree - $cached - $buffers;
+ $mem['SwapUsed']
+ = $swapTotal - $swapFree - $swapCached;
+
+ foreach ($mem as $idx => $value) {
+ $mem[$idx] = intval($value);
+ }
+ return $mem;
+ }
+}
+
+/**
+ * SunOS based SysInfo class
+ *
+ * @package PhpMyAdmin-sysinfo
+ */
+class PMA_SysInfoSunos extends PMA_SysInfo
+{
+ public $os = 'SunOS';
+
+ /**
+ * Read value from kstat
+ *
+ * @param string $key Key to read
+ *
+ * @return string with value
+ */
+ private function _kstat($key)
+ {
+ if ($m = shell_exec('kstat -p d '.$key)) {
+ list($key, $value) = preg_split("/\t/", trim($m), 2);
+ return $value;
+ } else {
+ return '';
+ }
+ }
+
+ /**
+ * Gets load information
+ *
+ * @return array with load data
+ */
+ public function loadavg()
+ {
+ $load1 = $this->_kstat('unix:0:system_misc:avenrun_1min');
+
+ return array('loadavg' => $load1);
+ }
+
+ /**
+ * Checks whether class is supported in this environment
+ *
+ * @return true on success
+ */
+ public function supported()
+ {
+ return is_readable('/proc/meminfo');
+ }
+
+
+ /**
+ * Gets information about memory usage
+ *
+ * @return array with memory usage data
+ */
+ public function memory()
+ {
+ $pagesize = $this->_kstat('unix:0:seg_cache:slab_size');
+ $mem['MemTotal']
+ = $this->_kstat('unix:0:system_pages:pagestotal') * $pagesize;
+ $mem['MemUsed']
+ = $this->_kstat('unix:0:system_pages:pageslocked') * $pagesize;
+ $mem['MemFree']
+ = $this->_kstat('unix:0:system_pages:pagesfree') * $pagesize;
+ $mem['SwapTotal'] = $this->_kstat('unix:0:vminfo:swap_avail') / 1024;
+ $mem['SwapUsed'] = $this->_kstat('unix:0:vminfo:swap_alloc') / 1024;
+ $mem['SwapFree'] = $this->_kstat('unix:0:vminfo:swap_free') / 1024;
+
+ return $mem;
+ }
+}
diff --git a/libraries/tbl_chart.lib.php b/libraries/tbl_chart.lib.php
new file mode 100644
index 0000000000..e1f8680e9b
--- /dev/null
+++ b/libraries/tbl_chart.lib.php
@@ -0,0 +1,295 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+
+/**
+ * functions for displaying chart
+ *
+ * @usedby tbl_chart.php
+ *
+ * @package PhpMyAdmin
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ * Function to get html for pma_token and url_query
+ *
+ * @param string $url_query url query
+ *
+ * @return string
+ */
+function PMA_getHtmlForPmaTokenAndUrlQuery($url_query)
+{
+ $htmlString = '<script type="text/javascript">'
+ . "pma_token = '" . $_SESSION[' PMA_token '] . "';"
+ . "url_query = '" . $url_query . "';"
+ . '</script>';
+ return $htmlString;
+}
+
+/**
+ * Function to get html for the chart type options
+ *
+ * @return string
+ */
+function PMA_getHtmlForChartTypeOptions()
+{
+ $html = '<input type="radio" name="chartType" value="bar" id="radio_bar" />'
+ . '<label for ="radio_bar">' . _pgettext('Chart type', 'Bar') . '</label>'
+ . '<input type="radio" name="chartType" value="column" id="radio_column" />'
+ . '<label for ="radio_column">' . _pgettext('Chart type', 'Column')
+ . '</label>'
+ . '<input type="radio" name="chartType" value="line" id="radio_line"'
+ . ' checked="checked" />'
+ . '<label for ="radio_line">' . _pgettext('Chart type', 'Line') . '</label>'
+ . '<input type="radio" name="chartType" value="spline" id="radio_spline" />'
+ . '<label for ="radio_spline">' . _pgettext('Chart type', 'Spline')
+ . '</label>'
+ . '<input type="radio" name="chartType" value="area" id="radio_area" />'
+ . '<label for ="radio_area">' . _pgettext('Chart type', 'Area') . '</label>'
+ . '<span class="span_pie" style="display:none;">'
+ . '<input type="radio" name="chartType" value="pie" id="radio_pie" />'
+ . '<label for ="radio_pie">' . _pgettext('Chart type', 'Pie') . '</label>'
+ . '</span>'
+ . '<span class="span_timeline" style="display:none;">'
+ . '<input type="radio" name="chartType" '
+ . 'value="timeline" id="radio_timeline" />'
+ . '<label for ="radio_timeline">' . _pgettext('Chart type', 'Timeline')
+ . '</label>'
+ . '</span>'
+ . '<br /><br />';
+
+ return $html;
+}
+
+/**
+ * Function to get html for the bar stacked option
+ *
+ * @return string
+ */
+function PMA_getHtmlForStackedOption()
+{
+ $html = '<span class="barStacked">'
+ . '<input type="checkbox" name="barStacked" value="1"'
+ . ' id="checkbox_barStacked" />'
+ . '<label for ="checkbox_barStacked">' . __('Stacked') . '</label>'
+ . '</span>'
+ . '<br /><br />';
+
+ return $html;
+}
+
+/**
+ * Function to get html for the chart x axis options
+ *
+ * @param array $keys keys
+ * @param int &$yaxis y axis
+ *
+ * @return string
+ */
+function PMA_getHtmlForChartXAxisOptions($keys, &$yaxis)
+{
+ $htmlString = '<div style="float:left; padding-left:40px;">'
+ . '<label for="select_chartXAxis">' . __('X-Axis:') . '</label>'
+ . '<select name="chartXAxis" id="select_chartXAxis">';
+
+ foreach ($keys as $idx => $key) {
+ if ($yaxis === null) {
+ $htmlString .= '<option value="' . htmlspecialchars($idx)
+ . '" selected="selected">' . htmlspecialchars($key) . '</option>';
+ $yaxis = $idx;
+ } else {
+ $htmlString .= '<option value="' . htmlspecialchars($idx) . '">'
+ . htmlspecialchars($key) . '</option>';
+ }
+ }
+ $htmlString .= '</select>';
+
+ return $htmlString;
+}
+
+
+/**
+ * Function to get html for chart series options
+ *
+ * @param array $keys keys
+ * @param array $fields_meta fields meta
+ * @param array $numeric_types numeric types
+ * @param int $yaxis y axis
+ * @param int $numeric_column_count numeric column count
+ *
+ * @return string
+ */
+function PMA_getHtmlForChartSeriesOptions($keys, $fields_meta, $numeric_types,
+ $yaxis, $numeric_column_count
+) {
+ $htmlString = '<br />'
+ . '<label for="select_chartSeries">' . __('Series:') . '</label>'
+ . '<select name="chartSeries" id="select_chartSeries" multiple="multiple">';
+
+ foreach ($keys as $idx => $key) {
+ if (in_array($fields_meta[$idx]->type, $numeric_types)) {
+ if ($idx == $yaxis && $numeric_column_count > 1) {
+ $htmlString .= '<option value="' . htmlspecialchars($idx) . '">'
+ . htmlspecialchars($key) . '</option>';
+ } else {
+ $htmlString .= '<option value="' . htmlspecialchars($idx)
+ . '" selected="selected">' . htmlspecialchars($key)
+ . '</option>';
+ }
+ }
+ }
+ $htmlString .= '</select>';
+ return $htmlString;
+}
+
+/**
+ * Function to get html for date time columns
+ *
+ * @param array $keys keys
+ * @param array $fields_meta fields meta
+ *
+ * @return string
+ */
+function PMA_getHtmlForDateTimeCols($keys, $fields_meta)
+{
+ $htmlString = '<input type="hidden" name="dateTimeCols" value="';
+
+ $date_time_types = array('date', 'datetime', 'timestamp');
+ foreach ($keys as $idx => $key) {
+ if (in_array($fields_meta[$idx]->type, $date_time_types)) {
+ $htmlString .= $idx . " ";
+ }
+ }
+ $htmlString .= '" />';
+
+ return $htmlString;
+}
+
+/**
+ * Function to get html for the table axis label options
+ *
+ * @param int $yaxis y axis
+ * @param array $keys keys
+ *
+ * @return string
+ */
+function PMA_getHtmlForTableAxisLabelOptions($yaxis, $keys)
+{
+ $htmlString = '<div style="float:left; padding-left:40px;">'
+ . '<label for="xaxis_label">' . __('X-Axis label:') . '</label>'
+ . '<input style="margin-top:0;" type="text" name="xaxis_label" id="xaxis_label"'
+ . ' value="'
+ . (($yaxis == -1) ? __('X Values') : htmlspecialchars($keys[$yaxis]))
+ . '" /><br />'
+ . '<label for="yaxis_label">' . __('Y-Axis label:') . '</label>'
+ . '<input type="text" name="yaxis_label" id="yaxis_label" value="'
+ . __('Y Values') . '" /><br />'
+ . '</div>';
+
+ return $htmlString;
+}
+
+/**
+ * Function to get html for the start row and number of rows options
+ *
+ * @param string $sql_query sql query
+ *
+ * @return string
+ */
+function PMA_getHtmlForStartAndNumberOfRowsOptions($sql_query)
+{
+ $htmlString = '<p style="clear:both;">&nbsp;</p>'
+ . '<fieldset>'
+ . '<div>'
+ . '<label for="pos">' . __('Start row:') . '</label>'
+ . '<input type="text" name="pos" size="3" value="'
+ . $_SESSION['tmpval']['pos'] . '" />'
+ . '<label for="session_max_rows">'
+ . __('Number of rows:') . '</label>'
+ . '<input type="text" name="session_max_rows" size="3" value="'
+ . (($_SESSION['tmpval']['max_rows'] != 'all')
+ ? $_SESSION['tmpval']['max_rows']
+ : $GLOBALS['cfg']['MaxRows'])
+ . '" />'
+ . '<input type="submit" name="submit" class="Go" value="' . __('Go')
+ . '" />'
+ . '<input type="hidden" name="sql_query" value="'
+ . htmlspecialchars($sql_query) . '" />'
+ . '</div>'
+ . '</fieldset>';
+
+ return $htmlString;
+}
+
+/**
+ * Function to get html for the chart area div
+ *
+ * @return string
+ */
+function PMA_getHtmlForChartAreaDiv()
+{
+ $htmlString = '<p style="clear:both;">&nbsp;</p>'
+ . '<div id="resizer" style="width:600px; height:400px;">'
+ . '<div id="querychart">'
+ . '</div>'
+ . '</div>';
+
+ return $htmlString;
+}
+
+/**
+ * Function to get html for displaying table chart
+ *
+ * @param string $url_query url query
+ * @param array $url_params url parameters
+ * @param array $keys keys
+ * @param array $fields_meta fields meta
+ * @param array $numeric_types numeric types
+ * @param int $numeric_column_count numeric column count
+ * @param string $sql_query sql query
+ *
+ * @return string
+ */
+function PMA_getHtmlForTableChartDisplay($url_query, $url_params, $keys,
+ $fields_meta, $numeric_types, $numeric_column_count, $sql_query
+) {
+ // pma_token/url_query needed for chart export
+ $htmlString = PMA_getHtmlForPmaTokenAndUrlQuery($url_query);
+ $htmlString .= '<!-- Display Chart options -->'
+ . '<div id="div_view_options">'
+ . '<form method="post" id="tblchartform" action="tbl_chart.php" '
+ . 'class="ajax">'
+ . PMA_URL_getHiddenInputs($url_params)
+ . '<fieldset>'
+ . '<legend>' . __('Display chart') . '</legend>'
+ . '<div style="float:left; width:420px;">';
+ $htmlString .= PMA_getHtmlForChartTypeOptions();
+ $htmlString .= PMA_getHtmlForStackedOption();
+
+ $htmlString .= '<input type="text" name="chartTitle" value="'
+ . __('Chart title')
+ . '">'
+ . '</div>';
+ $yaxis = null;
+ $htmlString .= PMA_getHtmlForChartXAxisOptions($keys, $yaxis);
+ $htmlString .= PMA_getHtmlForChartSeriesOptions(
+ $keys, $fields_meta, $numeric_types, $yaxis, $numeric_column_count
+ );
+ $htmlString .= PMA_getHtmlForDateTimeCols($keys, $fields_meta);
+ $htmlString .= '</div>';
+
+ $htmlString .= PMA_getHtmlForTableAxisLabelOptions($yaxis, $keys);
+ $htmlString .= PMA_getHtmlForStartAndNumberOfRowsOptions($sql_query);
+
+ $htmlString .= PMA_getHtmlForChartAreaDiv();
+
+ $htmlString .= '</fieldset>'
+ . '</form>'
+ . '</div>';
+
+ return $htmlString;
+}
+?>
+
diff --git a/libraries/tbl_columns_definition_form.inc.php b/libraries/tbl_columns_definition_form.inc.php
new file mode 100644
index 0000000000..a911ca6dba
--- /dev/null
+++ b/libraries/tbl_columns_definition_form.inc.php
@@ -0,0 +1,159 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Display form for changing/adding table fields/columns.
+ * Included by tbl_addfield.php and tbl_create.php
+ *
+ * @package PhpMyAdmin
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ * Check parameters
+ */
+require_once './libraries/Util.class.php';
+
+PMA_Util::checkParameters(array('db', 'table', 'action', 'num_fields'));
+
+
+// Get available character sets and storage engines
+require_once './libraries/mysql_charsets.inc.php';
+require_once './libraries/StorageEngine.class.php';
+
+/**
+ * Class for partition management
+ */
+require_once './libraries/Partition.class.php';
+
+require_once './libraries/tbl_columns_definition_form.lib.php';
+
+/**
+ * We are in transition between old-style echo and new-style PMA_Response
+ * so this script generates $html and at the bottom, either echos it
+ * or uses addHTML on it.
+ *
+ * Initialize $html in case this variable was used by a caller
+ * (yes, this script should be refactored into functions)
+ */
+
+$length_values_input_size = 8;
+
+$_form_params = PMA_getFormsParameters(
+ $db, $table, $action, isset($num_fields) ? $num_fields : null,
+ isset($selected) ? $selected : null
+);
+
+$is_backup = ($action != 'tbl_create.php' && $action != 'tbl_addfield.php');
+
+require_once './libraries/transformations.lib.php';
+$cfgRelation = PMA_getRelationsParam();
+
+
+$comments_map = PMA_getComments($db, $table);
+
+if (isset($fields_meta)) {
+ $move_columns = PMA_getMoveColumns($db, $table);
+}
+
+if ($cfgRelation['mimework'] && $GLOBALS['cfg']['BrowseMIME']) {
+ $mime_map = PMA_getMIME($db, $table);
+ $available_mime = PMA_getAvailableMIMEtypes();
+}
+
+$header_cells = PMA_getHeaderCells(
+ $is_backup, isset($fields_meta) ? $fields_meta : null,
+ $cfgRelation['mimework'], $db, $table
+);
+
+// workaround for field_fulltext, because its submitted indices contain
+// the index as a value, not a key. Inserted here for easier maintaineance
+// and less code to change in existing files.
+if (isset($field_fulltext) && is_array($field_fulltext)) {
+ foreach ($field_fulltext as $fulltext_nr => $fulltext_indexkey) {
+ $submit_fulltext[$fulltext_indexkey] = $fulltext_indexkey;
+ }
+}
+if (isset($_REQUEST['submit_num_fields'])) {
+ //if adding new fields, set regenerate to keep the original values
+ $regenerate = 1;
+}
+for ($columnNumber = 0; $columnNumber < $num_fields; $columnNumber++) {
+ if (! empty($regenerate)) {
+ list($columnMeta, $submit_length, $submit_attribute,
+ $submit_default_current_timestamp, $comments_map, $mime_map)
+ = PMA_handleRegeneration(
+ $columnNumber,
+ isset($available_mime) ? $mime_map : null,
+ $comments_map, $mime_map
+ );
+ } elseif (isset($fields_meta[$columnNumber])) {
+ $columnMeta = PMA_getColumnMetaForDefault(
+ $fields_meta[$columnNumber],
+ isset($analyzed_sql[0]['create_table_fields']
+ [$fields_meta[$columnNumber]['Field']]['default_value'])
+ );
+ }
+
+ if (isset($columnMeta['Type'])) {
+ $extracted_columnspec = PMA_Util::extractColumnSpec($columnMeta['Type']);
+ if ($extracted_columnspec['type'] == 'bit') {
+ $columnMeta['Default']
+ = PMA_Util::convertBitDefaultValue($columnMeta['Default']);
+ }
+ $type = $extracted_columnspec['type'];
+ $length = $extracted_columnspec['spec_in_brackets'];
+ } else {
+ // creating a column
+ $columnMeta['Type'] = '';
+ $type = '';
+ $length = '';
+ }
+
+ // some types, for example longtext, are reported as
+ // "longtext character set latin7" when their charset and / or collation
+ // differs from the ones of the corresponding database.
+ $tmp = strpos($type, 'character set');
+ if ($tmp) {
+ $type = substr($type, 0, $tmp - 1);
+ }
+ // rtrim the type, for cases like "float unsigned"
+ $type = rtrim($type);
+
+
+ if (isset($submit_length) && $submit_length != false) {
+ $length = $submit_length;
+ }
+
+
+ // old column attributes
+ if ($is_backup) {
+ $_form_params = PMA_getFormParamsForOldColumn(
+ $columnMeta, $length, $_form_params, $columnNumber
+ );
+ }
+
+ $content_cells[$columnNumber] = PMA_getHtmlForColumnAttributes(
+ $columnNumber, isset($columnMeta) ? $columnMeta : null, strtoupper($type),
+ $length_values_input_size, $length,
+ isset($default_current_timestamp) ? $default_current_timestamp : null,
+ isset($extracted_columnspec) ? $extracted_columnspec : null,
+ isset($submit_attribute) ? $submit_attribute : null,
+ isset($analyzed_sql) ? $analyzed_sql : null,
+ isset($submit_default_current_timestamp)
+ ? $submit_default_current_timestamp : null,
+ $comments_map, isset($fields_meta) ? $fields_meta : null, $is_backup,
+ isset($move_columns) ? $move_columns : null, $cfgRelation, $available_mime,
+ $mime_map
+ );
+} // end for
+
+$html = PMA_getHtmlForTableCreateOrAddField(
+ $action, $_form_params, $content_cells, $header_cells
+);
+
+unset($_form_params);
+
+PMA_Response::getInstance()->addHTML($html);
+?>
diff --git a/libraries/tbl_columns_definition_form.lib.php b/libraries/tbl_columns_definition_form.lib.php
new file mode 100644
index 0000000000..b598be7bf0
--- /dev/null
+++ b/libraries/tbl_columns_definition_form.lib.php
@@ -0,0 +1,1283 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * set of functions used by tbl_columns_definitions_form.inc.php
+ *
+ * @package PhpMyAdmin
+ */
+if (!defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ * Function to get form parameters
+ *
+ * @param string $db database
+ * @param string $table table
+ * @param string $action action
+ * @param int $num_fields number of fields
+ * @param bool $selected selected
+ *
+ * @return array $form_params form parameters
+ */
+function PMA_getFormsParameters($db, $table, $action, $num_fields, $selected)
+{
+ $form_params = array(
+ 'db' => $db
+ );
+
+ if ($action == 'tbl_create.php') {
+ $form_params['reload'] = 1;
+ } elseif ($action == 'tbl_addfield.php') {
+ $form_params['field_where'] = $_REQUEST['field_where'];
+ $form_params['after_field'] = $_REQUEST['after_field'];
+ $form_params['table'] = $table;
+ } else {
+ $form_params['table'] = $table;
+ }
+
+ if (isset($num_fields)) {
+ $form_params['orig_num_fields'] = $num_fields;
+ }
+
+ if (isset($_REQUEST['field_where'])) {
+ $form_params['orig_field_where'] = $_REQUEST['field_where'];
+ }
+
+ if (isset($_REQUEST['after_field'])) {
+ $form_params['orig_after_field'] = $_REQUEST['after_field'];
+ }
+
+ if (isset($selected) && is_array($selected)) {
+ foreach ($selected as $o_fld_nr => $o_fld_val) {
+ $form_params['selected[' . $o_fld_nr . ']'] = $o_fld_val;
+ }
+ }
+
+ return $form_params;
+}
+
+/**
+ * Function to get html for table comments, storage engine, collation and
+ * partition definition
+ *
+ * @return string
+ */
+function PMA_getHtmlForTableConfigurations()
+{
+ $html = '<table>'
+ . '<tr class="vtop">'
+ . '<th>' . __('Table comments:') . '</th>'
+ . '<td width="25">&nbsp;</td>'
+ . '<th>' . __('Storage Engine:')
+ . PMA_Util::showMySQLDocu('Storage_engines')
+ . '</th>'
+ . '<td width="25">&nbsp;</td>'
+ . '<th>' . __('Collation:') . '</th>'
+ . '</tr>'
+ . '<tr><td><input type="text" name="comment" size="40" maxlength="80"'
+ . ' value="'
+ . (isset($_REQUEST['comment'])
+ ? htmlspecialchars($_REQUEST['comment'])
+ : '')
+ . '" class="textfield" />'
+ . '</td>'
+ . '<td width="25">&nbsp;</td>'
+ . '<td>'
+ . PMA_StorageEngine::getHtmlSelect(
+ 'tbl_storage_engine', null,
+ (isset($_REQUEST['tbl_storage_engine'])
+ ? $_REQUEST['tbl_storage_engine']
+ : null
+ )
+ )
+ . '</td>'
+ . '<td width="25">&nbsp;</td>'
+ . '<td>'
+ . PMA_generateCharsetDropdownBox(
+ PMA_CSDROPDOWN_COLLATION, 'tbl_collation', null,
+ (isset($_REQUEST['tbl_collation'])
+ ? $_REQUEST['tbl_collation']
+ : null
+ ),
+ false, 3
+ )
+ . '</td>'
+ . '</tr>';
+
+ if (PMA_Partition::havePartitioning()) {
+ $html .= '<tr class="vtop">'
+ . '<th>' . __('PARTITION definition:') . '&nbsp;'
+ . PMA_Util::showMySQLDocu('Partitioning')
+ . '</th>'
+ . '</tr>'
+ . '<tr>'
+ . '<td>'
+ . '<textarea name="partition_definition" id="partitiondefinition"'
+ . ' cols="' . $GLOBALS['cfg']['TextareaCols'] . '"'
+ . ' rows="' . $GLOBALS['cfg']['TextareaRows'] . '"'
+ . ' dir="' . $GLOBALS['text_dir'] . '">'
+ . (isset($_REQUEST['partition_definition'])
+ ? htmlspecialchars($_REQUEST['partition_definition'])
+ : '')
+ . '</textarea>'
+ . '</td>'
+ . '</tr>';
+ }
+ $html .= '</table>'
+ . '<br />';
+
+ return $html;
+}
+
+/**
+ * Function to get html for the footer
+ *
+ * @return string
+ */
+function PMA_getHtmlForFooter()
+{
+ $html = '<fieldset class="tblFooters">'
+ . '<input type="submit" name="do_save_data" value="' . __('Save') . '" />'
+ . '</fieldset>'
+ . '<div id="properties_message"></div>'
+ . '</form>';
+
+ $html .= '<div id="popup_background"></div>';
+
+ return $html;
+}
+
+/**
+ * Function to get html for table create table name and number of fields
+ *
+ * @return string
+ */
+function PMA_getHtmlForTableNameAndNoOfColumns()
+{
+ $html = '<table>'
+ . '<tr class="vmiddle">'
+ . '<td>' . __('Table name')
+ . ':&nbsp;<input type="text" name="table" size="40" maxlength="80"'
+ . ' value="'
+ . (isset($_REQUEST['table']) ? htmlspecialchars($_REQUEST['table']) : '')
+ . '" class="textfield" autofocus required />'
+ . '</td>'
+ . '<td>';
+ $html .= sprintf(
+ __('Add %s column(s)'), '<input type="text" id="added_fields" '
+ . 'name="added_fields" size="2" value="1" onfocus="this.select'
+ . '()" />'
+ );
+
+ $html .= '<input type="submit" name="submit_num_fields"'
+ . 'value="' . __('Go') . '"'
+ . ' onclick="return'
+ . ' checkFormElementInRange(this.form, \'added_fields\', \''
+ . str_replace(
+ '\'', '\\\'', __('You have to add at least one column.')
+ ) . '\', 1)" />';
+
+ $html .= '</td>'
+ . '</tr>'
+ . '</table>';
+
+ return $html;
+}
+
+/**
+ * Function to get html for table field definitions
+ *
+ * @param array $header_cells header cells
+ * @param array $content_cells content cells
+ *
+ * @return string
+ */
+function PMA_getHtmlForTableFieldDefinitions($header_cells, $content_cells)
+{
+ $html = '<table id="table_columns" class="noclick">';
+ $html .= '<caption class="tblHeaders">' . __('Structure')
+ . PMA_Util::showMySQLDocu('CREATE_TABLE') . '</caption>';
+
+ $html .= '<tr>';
+ foreach ($header_cells as $header_val) {
+ $html .= '<th>' . $header_val . '</th>';
+ }
+ $html .= '</tr>';
+
+ $odd_row = true;
+ foreach ($content_cells as $content_row) {
+ $html .= '<tr class="' . ($odd_row ? 'odd' : 'even') . '">';
+ $odd_row = ! $odd_row;
+
+ if (is_array($content_row)) {
+ foreach ($content_row as $content_row_val) {
+ $html .= '<td class="center">' . $content_row_val . '</td>';
+ }
+ }
+ $html .= '</tr>';
+ }
+ $html .= '</table>'
+ . '<br />';
+
+ return $html;
+}
+
+/**
+ * Function to get html for the create table or field add view
+ *
+ * @param string $action action
+ * @param array $form_params forms parameters
+ * @param array $content_cells content cells
+ * @param array $header_cells header cells
+ *
+ * @return string
+ */
+function PMA_getHtmlForTableCreateOrAddField($action, $form_params, $content_cells,
+ $header_cells
+) {
+ $html = '<form method="post" action="' . $action . '" class="'
+ . ($action == 'tbl_create.php' ? 'create_table' : 'append_fields')
+ . '_form ajax">';
+ $html .= PMA_URL_getHiddenInputs($form_params);
+
+ if ($action == 'tbl_create.php') {
+ $html .= PMA_getHtmlForTableNameAndNoOfColumns();
+ }
+
+ if (is_array($content_cells) && is_array($header_cells)) {
+ $html .= PMA_getHtmlForTableFieldDefinitions($header_cells, $content_cells);
+ }
+
+ if ($action == 'tbl_create.php') {
+ $html .= PMA_getHtmlForTableConfigurations();
+ }
+
+ $html .= PMA_getHtmlForFooter();
+
+ return $html;
+}
+
+/**
+ * Function to get header cells
+ *
+ * @param bool $is_backup whether backup or not
+ * @param array $columnMeta column meta data
+ * @param bool $mimework whether mimework or not
+ * @param string $db current database
+ * @param string $table current table
+ *
+ * @return array
+ */
+function PMA_getHeaderCells($is_backup, $columnMeta, $mimework, $db, $table)
+{
+ $header_cells = array();
+ $header_cells[] = __('Name');
+ $header_cells[] = __('Type')
+ . PMA_Util::showMySQLDocu('data-types');
+ $header_cells[] = __('Length/Values')
+ . PMA_Util::showHint(
+ __(
+ 'If column type is "enum" or "set", please enter the values using'
+ . ' this format: \'a\',\'b\',\'c\'…<br />If you ever need to put'
+ . ' a backslash ("\") or a single quote ("\'") amongst those'
+ . ' values, precede it with a backslash (for example \'\\\\xyz\''
+ . ' or \'a\\\'b\').'
+ )
+ );
+ $header_cells[] = __('Default')
+ . PMA_Util::showHint(
+ __(
+ 'For default values, please enter just a single value,'
+ . ' without backslash escaping or quotes, using this format: a'
+ )
+ );
+ $header_cells[] = __('Collation');
+ $header_cells[] = __('Attributes');
+ $header_cells[] = __('Null');
+
+ // We could remove this 'if' and let the key information be shown and
+ // editable. However, for this to work, structure.lib.php must be modified
+ // to use the key fields, as tbl_addfield does.
+ if (! $is_backup) {
+ $header_cells[] = __('Index');
+ }
+
+ $header_cells[] = '<abbr title="AUTO_INCREMENT">A_I</abbr>';
+ $header_cells[] = __('Comments');
+
+ if (isset($columnMeta)) {
+ $header_cells[] = __('Move column');
+ }
+
+ if ($mimework && $GLOBALS['cfg']['BrowseMIME']) {
+ $header_cells[] = __('MIME type');
+ $header_cells[] = '<a href="transformation_overview.php?'
+ . PMA_URL_getCommon($db, $table)
+ . '" target="_blank">'
+ . __('Browser transformation')
+ . '</a>';
+ $header_cells[] = __('Transformation options')
+ . PMA_Util::showHint(
+ __(
+ 'Please enter the values for transformation options using this'
+ . ' format: \'a\', 100, b,\'c\'…<br />If you ever need to put'
+ . ' a backslash ("\") or a single quote ("\'") amongst those'
+ . ' values, precede it with a backslash (for example \'\\\\xyz\''
+ . ' or \'a\\\'b\').'
+ )
+ );
+ }
+
+ return $header_cells;
+}
+
+/**
+ * Function for moving, load all available column names
+ *
+ * @param string $db current database
+ * @param string $table current table
+ *
+ * @return array
+ */
+function PMA_getMoveColumns($db, $table)
+{
+ $move_columns_sql_query = 'SELECT * FROM '
+ . PMA_Util::backquote($db)
+ . '.'
+ . PMA_Util::backquote($table)
+ . ' LIMIT 1';
+ $move_columns_sql_result = $GLOBALS['dbi']->tryQuery($move_columns_sql_query);
+ $move_columns = $GLOBALS['dbi']->getFieldsMeta($move_columns_sql_result);
+
+ return $move_columns;
+}
+
+/**
+ * Function to get row data for regenerating previous when error occurred.
+ *
+ * @param int $columnNumber coulmn number
+ * @param array $submit_fulltext submit full text
+ *
+ * @return array
+ */
+function PMA_getRowDataForRegeneration($columnNumber, $submit_fulltext)
+{
+ $columnMeta['Field'] = isset($_REQUEST['field_name'][$columnNumber])
+ ? $_REQUEST['field_name'][$columnNumber]
+ : false;
+ $columnMeta['Type'] = isset($_REQUEST['field_type'][$columnNumber])
+ ? $_REQUEST['field_type'][$columnNumber]
+ : false;
+ $columnMeta['Collation'] = isset($_REQUEST['field_collation'][$columnNumber])
+ ? $_REQUEST['field_collation'][$columnNumber]
+ : '';
+ $columnMeta['Null'] = isset($_REQUEST['field_null'][$columnNumber])
+ ? $_REQUEST['field_null'][$columnNumber]
+ : '';
+
+ $columnMeta['Key'] = '';
+ if (isset($_REQUEST['field_key'][$columnNumber])) {
+ $parts = explode('_', $_REQUEST['field_key'][$columnNumber], 2);
+ if (count($parts) == 2 && $parts[1] == $columnNumber) {
+ switch ($parts[0]) {
+ case 'primary':
+ $columnMeta['Key'] = 'PRI';
+ break;
+ case 'index':
+ $columnMeta['Key'] = 'MUL';
+ break;
+ case 'unique':
+ $columnMeta['Key'] = 'UNI';
+ break;
+ case 'fulltext':
+ $columnMeta['Key'] = 'FULLTEXT';
+ break;
+ }
+ }
+ }
+
+ // put None in the drop-down for Default, when someone adds a field
+ $columnMeta['DefaultType']
+ = isset($_REQUEST['field_default_type'][$columnNumber])
+ ? $_REQUEST['field_default_type'][$columnNumber]
+ : 'NONE';
+ $columnMeta['DefaultValue']
+ = isset($_REQUEST['field_default_value'][$columnNumber])
+ ? $_REQUEST['field_default_value'][$columnNumber]
+ : '';
+
+ switch ($columnMeta['DefaultType']) {
+ case 'NONE' :
+ $columnMeta['Default'] = null;
+ break;
+ case 'USER_DEFINED' :
+ $columnMeta['Default'] = $columnMeta['DefaultValue'];
+ break;
+ case 'NULL' :
+ case 'CURRENT_TIMESTAMP' :
+ $columnMeta['Default'] = $columnMeta['DefaultType'];
+ break;
+ }
+
+ $columnMeta['Extra']
+ = (isset($_REQUEST['field_extra'][$columnNumber])
+ ? $_REQUEST['field_extra'][$columnNumber]
+ : false);
+ $columnMeta['Comment']
+ = (isset($submit_fulltext[$columnNumber])
+ && ($submit_fulltext[$columnNumber] == $columnNumber)
+ ? 'FULLTEXT'
+ : false);
+
+ return $columnMeta;
+}
+
+/**
+ * Function to get submit properties for regenerating previous when error occurred.
+ *
+ * @param int $columnNumber coulmn number
+ *
+ * @return array
+ */
+function PMA_getSubmitPropertiesForRegeneration($columnNumber)
+{
+ $submit_length
+ = (isset($_REQUEST['field_length'][$columnNumber])
+ ? $_REQUEST['field_length'][$columnNumber]
+ : false);
+ $submit_attribute
+ = (isset($_REQUEST['field_attribute'][$columnNumber])
+ ? $_REQUEST['field_attribute'][$columnNumber]
+ : false);
+
+ $submit_default_current_timestamp
+ = (isset($_REQUEST['field_default_current_timestamp'][$columnNumber])
+ ? true
+ : false);
+
+ return array(
+ $submit_length, $submit_attribute, $submit_default_current_timestamp
+ );
+}
+
+/**
+ * An error happened with previous inputs, so we will restore the data
+ * to embed it once again in this form.
+ *
+ * @param int $columnNumber coulmn number
+ * @param array $submit_fulltext submit full text
+ * @param array $comments_map comments map
+ * @param array $mime_map mime map
+ *
+ * @return array
+ */
+function PMA_handleRegeneration($columnNumber, $submit_fulltext, $comments_map,
+ $mime_map
+) {
+ $columnMeta = PMA_getRowDataForRegeneration(
+ $columnNumber, isset($submit_fulltext) ? $submit_fulltext : null
+ );
+
+ list($submit_length, $submit_attribute, $submit_default_current_timestamp)
+ = PMA_getSubmitPropertiesForRegeneration($columnNumber);
+
+ if (isset($_REQUEST['field_comments'][$columnNumber])) {
+ $comments_map[$columnMeta['Field']]
+ = $_REQUEST['field_comments'][$columnNumber];
+ }
+
+ if (isset($_REQUEST['field_mimetype'][$columnNumber])) {
+ $mime_map[$columnMeta['Field']]['mimetype']
+ = $_REQUEST['field_mimetype'][$columnNumber];
+ }
+
+ if (isset($_REQUEST['field_transformation'][$columnNumber])) {
+ $mime_map[$columnMeta['Field']]['transformation']
+ = $_REQUEST['field_transformation'][$columnNumber];
+ }
+
+ if (isset($_REQUEST['field_transformation_options'][$columnNumber])) {
+ $mime_map[$columnMeta['Field']]['transformation_options']
+ = $_REQUEST['field_transformation_options'][$columnNumber];
+ }
+
+ return array(
+ $columnMeta, $submit_length, $submit_attribute,
+ $submit_default_current_timestamp, $comments_map, $mime_map
+ );
+}
+
+/**
+ * Function to get row data for $columnMeta set
+ *
+ * @param array $columnMeta column meta
+ * @param bool $isDefault whether the row value is default
+ *
+ * @return array
+ */
+function PMA_getColumnMetaForDefault($columnMeta, $isDefault)
+{
+ switch ($columnMeta['Default']) {
+ case null:
+ if ($columnMeta['Null'] == 'YES') {
+ $columnMeta['DefaultType'] = 'NULL';
+ $columnMeta['DefaultValue'] = '';
+ // SHOW FULL COLUMNS does not report the case
+ // when there is a DEFAULT value which is empty so we need to use the
+ // results of SHOW CREATE TABLE
+ } elseif ($isDefault) {
+ $columnMeta['DefaultType'] = 'USER_DEFINED';
+ $columnMeta['DefaultValue'] = $columnMeta['Default'];
+ } else {
+ $columnMeta['DefaultType'] = 'NONE';
+ $columnMeta['DefaultValue'] = '';
+ }
+ break;
+ case 'CURRENT_TIMESTAMP':
+ $columnMeta['DefaultType'] = 'CURRENT_TIMESTAMP';
+ $columnMeta['DefaultValue'] = '';
+ break;
+ default:
+ $columnMeta['DefaultType'] = 'USER_DEFINED';
+ $columnMeta['DefaultValue'] = $columnMeta['Default'];
+ break;
+ }
+
+ return $columnMeta;
+}
+
+/**
+ * Function to get html for the column name
+ *
+ * @param int $columnNumber column number
+ * @param int $ci cell index
+ * @param int $ci_offset cell index offset
+ * @param array $columnMeta column meta
+ *
+ * @return string
+ */
+function PMA_getHtmlForColumnName($columnNumber, $ci, $ci_offset, $columnMeta)
+{
+ $html = '<input id="field_' . $columnNumber . '_' . ($ci - $ci_offset)
+ . '"' . ' type="text" name="field_name[' . $columnNumber . ']"'
+ . ' maxlength="64" class="textfield" title="' . __('Column') . '"'
+ . ' size="10"'
+ . ' value="'
+ . (isset($columnMeta['Field'])
+ ? htmlspecialchars($columnMeta['Field']) : '')
+ . '"' . ' />';
+
+ return $html;
+}
+
+/**
+ * Function to get html for the column type
+ *
+ * @param int $columnNumber column number
+ * @param int $ci cell index
+ * @param int $ci_offset cell index offset
+ * @param string $type_upper type inuppercase
+ *
+ * @return string
+ */
+function PMA_getHtmlForColumnType($columnNumber, $ci, $ci_offset, $type_upper)
+{
+ $select_id = 'field_' . $columnNumber . '_' . ($ci - $ci_offset);
+ $html = '<select class="column_type" name="field_type[' .
+ $columnNumber . ']"' .' id="' . $select_id . '">';
+ $html .= PMA_Util::getSupportedDatatypes(true, $type_upper);
+ $html .= ' </select>';
+
+ return $html;
+}
+
+/**
+ * Function to get html for transformation option
+ *
+ * @param int $columnNumber column number
+ * @param int $ci cell index
+ * @param int $ci_offset cell index offset
+ * @param array $columnMeta column meta
+ * @param array $mime_map mime map
+ *
+ * @return string
+ */
+function PMA_getHtmlForTransformationOption($columnNumber, $ci, $ci_offset,
+ $columnMeta, $mime_map
+) {
+ $val = isset($columnMeta['Field'])
+ && isset($mime_map[$columnMeta['Field']]['transformation_options'])
+ ? htmlspecialchars(
+ $mime_map[$columnMeta['Field']]
+ ['transformation_options']
+ )
+ : '';
+
+ $html = '<input id="field_' . $columnNumber . '_'
+ . ($ci - $ci_offset) . '"' . ' type="text" '
+ . 'name="field_transformation_options[' . $columnNumber . ']"'
+ . ' size="16" class="textfield"'
+ . ' value="' . $val . '"'
+ . ' />';
+
+ return $html;
+}
+
+/**
+ * Function to get html for mime type
+ *
+ * @param int $columnNumber column number
+ * @param int $ci cell index
+ * @param int $ci_offset cell index offset
+ * @param array $available_mime available mime
+ * @param array $columnMeta column meta
+ * @param array $mime_map mime map
+ *
+ * @return string
+ */
+function PMA_getHtmlForMimeType($columnNumber, $ci, $ci_offset,
+ $available_mime, $columnMeta, $mime_map
+) {
+ $html = '<select id="field_' . $columnNumber . '_'
+ . ($ci - $ci_offset)
+ . '" size="1" name="field_mimetype[' . $columnNumber . ']">';
+ $html .= ' <option value="">&nbsp;</option>';
+
+ if (is_array($available_mime['mimetype'])) {
+ foreach ($available_mime['mimetype'] as $mimetype) {
+ $checked = (isset($columnMeta['Field'])
+ && isset($mime_map[$columnMeta['Field']]['mimetype'])
+ && ($mime_map[$columnMeta['Field']]['mimetype']
+ == str_replace('/', '_', $mimetype))
+ ? 'selected '
+ : '');
+ $html .= ' <option value="'
+ . str_replace('/', '_', $mimetype) . '" ' . $checked . '>'
+ . htmlspecialchars($mimetype) . '</option>';
+ }
+ }
+
+ $html .= '</select>';
+
+ return $html;
+}
+
+/**
+ * Function to get html for browser transformation
+ *
+ * @param int $columnNumber column number
+ * @param int $ci cell index
+ * @param int $ci_offset cell index offset
+ * @param array $available_mime available mime
+ * @param array $columnMeta column meta
+ * @param array $mime_map mime map
+ *
+ * @return string
+ */
+function PMA_getHtmlForBrowserTransformation($columnNumber, $ci, $ci_offset,
+ $available_mime, $columnMeta, $mime_map
+) {
+ $html = '<select id="field_' . $columnNumber . '_'
+ . ($ci - $ci_offset) . '" size="1" name="field_transformation['
+ . $columnNumber . ']">';
+ $html .= ' <option value="" title="' . __('None')
+ . '"></option>';
+ if (is_array($available_mime['transformation'])) {
+ foreach ($available_mime['transformation'] as $mimekey => $transform) {
+ $checked = isset($columnMeta['Field'])
+ && isset($mime_map[$columnMeta['Field']]['transformation'])
+ && preg_match(
+ '@' . preg_quote(
+ $available_mime['transformation_file'][$mimekey]
+ ) . '3?@i',
+ $mime_map[$columnMeta['Field']]['transformation']
+ )
+ ? 'selected '
+ : '';
+ $tooltip = PMA_getTransformationDescription(
+ $available_mime['transformation_file'][$mimekey], false
+ );
+ $html .= '<option value="'
+ . $available_mime['transformation_file'][$mimekey] . '" '
+ . $checked . ' title="' . htmlspecialchars($tooltip) . '">'
+ . htmlspecialchars($transform) . '</option>';
+ }
+ }
+
+ $html .= '</select>';
+
+ return $html;
+}
+
+/**
+ * Function to get html for move column
+ *
+ * @param int $columnNumber column number
+ * @param int $ci cell index
+ * @param int $ci_offset cell index offset
+ * @param array $move_columns move columns
+ * @param array $columnMeta column meta
+ *
+ * @return string
+ */
+function PMA_getHtmlForMoveColumn($columnNumber, $ci, $ci_offset, $move_columns,
+ $columnMeta
+) {
+ $html = '<select id="field_' . $columnNumber . '_'
+ . ($ci - $ci_offset) . '"' . ' name="field_move_to[' . $columnNumber
+ . ']" size="1" width="5em">'
+ . '<option value="" selected="selected">&nbsp;</option>';
+ // find index of current column
+ $current_index = 0;
+ for ($mi = 0, $cols = count($move_columns); $mi < $cols; $mi++) {
+ if ($move_columns[$mi]->name == $columnMeta['Field']) {
+ $current_index = $mi;
+ break;
+ }
+ }
+
+ $html .= '<option value="-first"'
+ . ($current_index == 0 ? ' disabled="disabled"' : '')
+ . '>' . __('first') . '</option>';
+ for ($mi = 0, $cols = count($move_columns); $mi < $cols; $mi++) {
+ $html .=
+ '<option value="' . htmlspecialchars($move_columns[$mi]->name) . '"'
+ . (($current_index == $mi || $current_index == $mi + 1)
+ ? ' disabled="disabled"'
+ : '')
+ .'>'
+ . sprintf(
+ __('after %s'),
+ PMA_Util::backquote(
+ htmlspecialchars(
+ $move_columns[$mi]->name
+ )
+ )
+ )
+ . '</option>';
+ }
+
+ $html .= '</select>';
+
+ return $html;
+}
+
+/**
+ * Function to get html for column comment
+ *
+ * @param int $columnNumber column number
+ * @param int $ci cell index
+ * @param int $ci_offset cell index offset
+ * @param array $columnMeta column meta
+ * @param array $comments_map comments map
+ *
+ * @return string
+ */
+function PMA_getHtmlForColumnComment($columnNumber, $ci, $ci_offset, $columnMeta,
+ $comments_map
+) {
+ $html = '<input id="field_' . $columnNumber . '_' . ($ci - $ci_offset)
+ . '"' . ' type="text" name="field_comments[' . $columnNumber
+ . ']" size="12"'
+ . ' value="' . (isset($columnMeta['Field'])
+ && is_array($comments_map)
+ && isset($comments_map[$columnMeta['Field']])
+ ? htmlspecialchars($comments_map[$columnMeta['Field']])
+ : '') . '"'
+ . ' class="textfield" />';
+
+ return $html;
+}
+
+/**
+ * Function get html for column auto increment
+ *
+ * @param int $columnNumber column number
+ * @param int $ci cell index
+ * @param int $ci_offset cell index offset
+ * @param array $columnMeta column meta
+ *
+ * @return string
+ */
+function PMA_getHtmlForColumnAutoIncrement($columnNumber, $ci, $ci_offset,
+ $columnMeta
+) {
+ $html = '<input name="field_extra[' . $columnNumber . ']"'
+ . ' id="field_' . $columnNumber . '_' . ($ci - $ci_offset) . '"';
+ if (isset($columnMeta['Extra'])
+ && strtolower($columnMeta['Extra']) == 'auto_increment'
+ ) {
+ $html .= ' checked="checked"';
+ }
+
+ $html .= ' type="checkbox" value="AUTO_INCREMENT" />';
+
+ return $html;
+}
+
+/**
+ * Function to get html for the column indexes
+ *
+ * @param int $columnNumber column number
+ * @param int $ci cell index
+ * @param int $ci_offset cell index offset
+ * @param array $columnMeta column meta
+ *
+ * @return string
+ */
+function PMA_getHtmlForColumnIndexes($columnNumber, $ci, $ci_offset, $columnMeta)
+{
+ $html = '<select name="field_key[' . $columnNumber . ']"'
+ . ' id="field_' . $columnNumber . '_' . ($ci - $ci_offset) . '">';
+ $html .= '<option value="none_' . $columnNumber . '">---</option>';
+
+ $html .= PMA_getHtmlForIndexTypeOption(
+ $columnNumber, $columnMeta, 'Primary', 'PRI'
+ );
+ $html .= PMA_getHtmlForIndexTypeOption(
+ $columnNumber, $columnMeta, 'Unique', 'UNI'
+ );
+ $html .= PMA_getHtmlForIndexTypeOption(
+ $columnNumber, $columnMeta, 'Index', 'MUL'
+ );
+ if (!PMA_DRIZZLE) {
+ $html .= PMA_getHtmlForIndexTypeOption(
+ $columnNumber, $columnMeta, 'Fulltext', 'FULLTEXT'
+ );
+ }
+
+ $html .= '</select>';
+
+ return $html;
+}
+
+/**
+ * Function to get html for the index options
+ *
+ * @param int $columnNumber column number
+ * @param array $columnMeta column meta
+ * @param string $type index type
+ * @param string $key column meta key
+ *
+ * @return string
+ */
+
+function PMA_getHtmlForIndexTypeOption($columnNumber, $columnMeta, $type, $key)
+{
+ $html = '<option value="' . strtolower($type) . '_' . $columnNumber
+ . '" title="'
+ . __($type) . '"';
+ if (isset($columnMeta['Key']) && $columnMeta['Key'] == $key) {
+ $html .= ' selected="selected"';
+ }
+ $html .= '>' . strtoupper($type) . '</option>';
+
+ return $html;
+}
+
+
+/**
+ * Function to get html for column null
+ *
+ * @param int $columnNumber column number
+ * @param int $ci cell index
+ * @param int $ci_offset cell index offset
+ * @param array $columnMeta column meta
+ *
+ * @return string
+ */
+function PMA_getHtmlForColumnNull($columnNumber, $ci, $ci_offset, $columnMeta)
+{
+ $html = '<input name="field_null[' . $columnNumber . ']"'
+ . ' id="field_' . $columnNumber . '_' . ($ci - $ci_offset) . '"';
+ if (! empty($columnMeta['Null'])
+ && $columnMeta['Null'] != 'NO'
+ && $columnMeta['Null'] != 'NOT NULL'
+ ) {
+ $html .= ' checked="checked"';
+ }
+
+ $html .= ' type="checkbox" value="NULL" class="allow_null"/>';
+
+ return $html;
+}
+
+/**
+ * Function to get html for column attribute
+ *
+ * @param int $columnNumber column number
+ * @param int $ci cell index
+ * @param int $ci_offset cell index offset
+ * @param array $extracted_columnspec extracted column
+ * @param array $columnMeta column meta
+ * @param bool $submit_attribute submit attribute
+ * @param array $analyzed_sql analyzed sql
+ * @param bool $submit_default_current_timestamp submit default current time stamp
+ *
+ * @return string
+ */
+function PMA_getHtmlForColumnAttribute($columnNumber, $ci, $ci_offset,
+ $extracted_columnspec, $columnMeta, $submit_attribute, $analyzed_sql,
+ $submit_default_current_timestamp
+) {
+ $html = '<select style="font-size: 70%;"'
+ . ' name="field_attribute[' . $columnNumber . ']"'
+ . ' id="field_' . $columnNumber . '_' . ($ci - $ci_offset) . '">';
+
+ $attribute = '';
+ if (isset($extracted_columnspec)) {
+ $attribute = $extracted_columnspec['attribute'];
+ }
+
+ if (isset($columnMeta['Extra'])
+ && $columnMeta['Extra'] == 'on update CURRENT_TIMESTAMP'
+ ) {
+ $attribute = 'on update CURRENT_TIMESTAMP';
+ }
+
+ if (isset($submit_attribute) && $submit_attribute != false) {
+ $attribute = $submit_attribute;
+ }
+
+ // here, we have a TIMESTAMP that SHOW FULL COLUMNS reports as having the
+ // NULL attribute, but SHOW CREATE TABLE says the contrary. Believe
+ // the latter.
+ $create_table_fields = $analyzed_sql[0]['create_table_fields'];
+ if (PMA_MYSQL_INT_VERSION < 50025
+ && isset($columnMeta['Field'])
+ && isset($create_table_fields[$columnMeta['Field']]['type'])
+ && $create_table_fields[$columnMeta['Field']]['type'] == 'TIMESTAMP'
+ && $create_table_fields[$columnMeta['Field']]['timestamp_not_null'] == true
+ ) {
+ $columnMeta['Null'] = '';
+ }
+
+ // MySQL 4.1.2+ TIMESTAMP options
+ // (if on_update_current_timestamp is set, then it's TRUE)
+ if (isset($columnMeta['Field'])) {
+ $field = $create_table_fields[$columnMeta['Field']];
+ }
+
+ if (isset($field)
+ && isset($field['on_update_current_timestamp'])
+ ) {
+ $attribute = 'on update CURRENT_TIMESTAMP';
+ }
+ if ((isset($columnMeta['Field'])
+ && isset($field['default_current_timestamp']))
+ || (isset($submit_default_current_timestamp)
+ && $submit_default_current_timestamp)
+ ) {
+ $default_current_timestamp = true;
+ } else {
+ $default_current_timestamp = false;
+ }
+
+ $attribute_types = $GLOBALS['PMA_Types']->getAttributes();
+ $cnt_attribute_types = count($attribute_types);
+ for ($j = 0; $j < $cnt_attribute_types; $j++) {
+ $html
+ .= ' <option value="' . $attribute_types[$j] . '"';
+ if (strtoupper($attribute) == strtoupper($attribute_types[$j])) {
+ $html .= ' selected="selected"';
+ }
+ $html .= '>' . $attribute_types[$j] . '</option>';
+ }
+
+ $html .= '</select>';
+
+ return $html;
+}
+
+/**
+ * Function to get html for column collation
+ *
+ * @param int $columnNumber column number
+ * @param int $ci cell index
+ * @param int $ci_offset cell index offset
+ * @param array $columnMeta column meta
+ *
+ * @return string
+ */
+function PMA_getHtmlForColumnCollation($columnNumber, $ci, $ci_offset, $columnMeta)
+{
+ $tmp_collation
+ = empty($columnMeta['Collation']) ? null : $columnMeta['Collation'];
+ $html = PMA_generateCharsetDropdownBox(
+ PMA_CSDROPDOWN_COLLATION, 'field_collation[' . $columnNumber . ']',
+ 'field_' . $columnNumber . '_' . ($ci - $ci_offset), $tmp_collation, false
+ );
+
+ return $html;
+}
+
+/**
+ * Function get html for column length
+ *
+ * @param int $columnNumber column number
+ * @param int $ci cell index
+ * @param int $ci_offset cell index offset
+ * @param int $length_values_input_size length values input size
+ * @param int $length_to_display length to disply
+ *
+ * @return string
+ */
+function PMA_getHtmlForColumnLength($columnNumber, $ci, $ci_offset,
+ $length_values_input_size, $length_to_display
+) {
+ $html = '<input id="field_' . $columnNumber . '_' . ($ci - $ci_offset)
+ . '"' . ' type="text" name="field_length[' . $columnNumber . ']" size="'
+ . $length_values_input_size . '"' . ' value="' . htmlspecialchars(
+ $length_to_display
+ )
+ . '"'
+ . ' class="textfield" />'
+ . '<p class="enum_notice" id="enum_notice_' . $columnNumber . '_'
+ . ($ci - $ci_offset)
+ . '">';
+ $html .= __('ENUM or SET data too long?')
+ . '<a href="#" class="open_enum_editor"> '
+ . __('Get more editing space') . '</a>'
+ . '</p>';
+
+ return $html;
+}
+
+/**
+ * Function to get html for the default column
+ *
+ * @param int $columnNumber column number
+ * @param int $ci cell index
+ * @param int $ci_offset cell index offset
+ * @param string $type_upper type upper
+ * @param string $default_current_timestamp default current timestamp
+ * @param array $columnMeta column meta
+ *
+ * @return string
+ */
+function PMA_getHtmlForColumnDefault($columnNumber, $ci, $ci_offset, $type_upper,
+ $default_current_timestamp, $columnMeta
+) {
+ // here we put 'NONE' as the default value of drop-down; otherwise
+ // users would have problems if they forget to enter the default
+ // value (example, for an INT)
+ $default_options = array(
+ 'NONE' => _pgettext('for default', 'None'),
+ 'USER_DEFINED' => __('As defined:'),
+ 'NULL' => 'NULL',
+ 'CURRENT_TIMESTAMP' => 'CURRENT_TIMESTAMP',
+ );
+
+ // for a TIMESTAMP, do not show the string "CURRENT_TIMESTAMP" as a default
+ // value
+ if ($type_upper == 'TIMESTAMP'
+ && ! empty($default_current_timestamp)
+ && isset($columnMeta['Default'])
+ ) {
+ $columnMeta['Default'] = '';
+ }
+
+ if ($type_upper == 'BIT') {
+ $columnMeta['DefaultValue']
+ = PMA_Util::convertBitDefaultValue($columnMeta['DefaultValue']);
+ }
+
+ $html = '<select name="field_default_type[' . $columnNumber
+ . ']" id="field_' . $columnNumber . '_' . ($ci - $ci_offset)
+ . '" class="default_type">';
+ foreach ($default_options as $key => $value) {
+ $html .= '<option value="' . $key . '"';
+ // is only set when we go back to edit a field's structure
+ if (isset($columnMeta['DefaultType'])
+ && $columnMeta['DefaultType'] == $key
+ ) {
+ $html .= ' selected="selected"';
+ }
+ $html .= ' >' . $value . '</option>';
+ }
+ $html .= '</select>';
+ $html .= '<br />';
+ $html .= '<input type="text"'
+ . ' name="field_default_value[' . $columnNumber . ']" size="12"'
+ . ' value="' . (isset($columnMeta['DefaultValue'])
+ ? htmlspecialchars($columnMeta['DefaultValue'])
+ : '') . '"'
+ . ' class="textfield default_value" />';
+
+ return $html;
+}
+
+/**
+ * Function to get html for column attributes
+ *
+ * @param int $columnNumber column number
+ * @param array $columnMeta column meta
+ * @param string $type_upper type upper
+ * @param int $length_values_input_size length values input size
+ * @param int $length length
+ * @param string $default_current_timestamp default current time stamp
+ * @param array $extracted_columnspec extracted column spec
+ * @param string $submit_attribute submit attribute
+ * @param array $analyzed_sql analyzed sql
+ * @param string $submit_default_current_timestamp submit default current time stamp
+ * @param array $comments_map comments map
+ * @param array $fields_meta fields map
+ * @param bool $is_backup is backup
+ * @param array $move_columns move columns
+ * @param array $cfgRelation configuration relation
+ * @param array $available_mime available mime
+ * @param array $mime_map mime map
+ *
+ * @return array
+ */
+function PMA_getHtmlForColumnAttributes($columnNumber, $columnMeta, $type_upper,
+ $length_values_input_size, $length, $default_current_timestamp,
+ $extracted_columnspec, $submit_attribute, $analyzed_sql,
+ $submit_default_current_timestamp, $comments_map, $fields_meta, $is_backup,
+ $move_columns, $cfgRelation, $available_mime, $mime_map
+) {
+ // Cell index: If certain fields get left out, the counter shouldn't change.
+ $ci = 0;
+ // Everytime a cell shall be left out the STRG-jumping feature, $ci_offset
+ // has to be incremented ($ci_offset++)
+ $ci_offset = -1;
+
+ $content_cell = array();
+
+ // column name
+ $content_cell[$ci] = PMA_getHtmlForColumnName(
+ $columnNumber, $ci, $ci_offset, isset($columnMeta) ? $columnMeta : null
+ );
+ $ci++;
+
+ // column type
+ $content_cell[$ci] = PMA_getHtmlForColumnType(
+ $columnNumber, $ci, $ci_offset, $type_upper
+ );
+ $ci++;
+
+ // column length
+ $content_cell[$ci] = PMA_getHtmlForColumnLength(
+ $columnNumber, $ci, $ci_offset, $length_values_input_size, $length
+ );
+ $ci++;
+
+ // column default
+ $content_cell[$ci] = PMA_getHtmlForColumnDefault(
+ $columnNumber, $ci, $ci_offset,
+ isset($type_upper) ? $type_upper : null,
+ isset($default_current_timestamp) ? $default_current_timestamp : null,
+ isset($columnMeta) ? $columnMeta : null
+ );
+ $ci++;
+
+ // column collation
+ $content_cell[$ci] = PMA_getHtmlForColumnCollation(
+ $columnNumber, $ci, $ci_offset, $columnMeta
+ );
+ $ci++;
+
+ // column attribute
+ $content_cell[$ci] = PMA_getHtmlForColumnAttribute(
+ $columnNumber, $ci, $ci_offset,
+ isset($extracted_columnspec) ? $extracted_columnspec : null,
+ isset($columnMeta) ? $columnMeta : null,
+ isset($submit_attribute) ? $submit_attribute : null,
+ isset($analyzed_sql) ? $analyzed_sql : null,
+ isset($submit_default_current_timestamp)
+ ? $submit_default_current_timestamp : null
+ );
+ $ci++;
+
+ // column NULL
+ $content_cell[$ci] = PMA_getHtmlForColumnNull(
+ $columnNumber, $ci, $ci_offset, isset($columnMeta) ? $columnMeta : null
+ );
+ $ci++;
+
+ // column indexes
+ // See my other comment about this 'if'.
+ if (!$is_backup) {
+ $content_cell[$ci] = PMA_getHtmlForColumnIndexes(
+ $columnNumber, $ci, $ci_offset, $columnMeta
+ );
+ $ci++;
+ } // end if ($action ==...)
+
+ // column auto_increment
+ $content_cell[$ci] = PMA_getHtmlForColumnAutoIncrement(
+ $columnNumber, $ci, $ci_offset, $columnMeta
+ );
+ $ci++;
+
+ // column comments
+ $content_cell[$ci] = PMA_getHtmlForColumnComment(
+ $columnNumber, $ci, $ci_offset, isset($columnMeta) ? $columnMeta : null,
+ $comments_map
+ );
+ $ci++;
+
+ // move column
+ if (isset($fields_meta)) {
+ $content_cell[$ci] = PMA_getHtmlForMoveColumn(
+ $columnNumber, $ci, $ci_offset, $move_columns, $columnMeta
+ );
+ $ci++;
+ }
+
+ if ($cfgRelation['mimework']
+ && $GLOBALS['cfg']['BrowseMIME']
+ && $cfgRelation['commwork']
+ ) {
+ // Column Mime-type
+ $content_cell[$ci] = PMA_getHtmlForMimeType(
+ $columnNumber, $ci, $ci_offset, $available_mime, $columnMeta, $mime_map
+ );
+ $ci++;
+
+ // Column Browser transformation
+ $content_cell[$ci] = PMA_getHtmlForBrowserTransformation(
+ $columnNumber, $ci, $ci_offset, $available_mime, $columnMeta, $mime_map
+ );
+ $ci++;
+
+ // column Transformation options
+ $content_cell[$ci] = PMA_getHtmlForTransformationOption(
+ $columnNumber, $ci, $ci_offset, isset($columnMeta) ? $columnMeta : null,
+ isset($mime_map) ? $mime_map : null
+ );
+ }
+
+ return $content_cell;
+}
+
+/**
+ * Function to get form parameters for old column
+ *
+ * @param array $columnMeta column meta
+ * @param int $length length
+ * @param array $form_params form parameters
+ * @param int $columnNumber column/field number
+ *
+ * @return array
+ */
+function PMA_getFormParamsForOldColumn(
+ $columnMeta, $length, $form_params, $columnNumber
+) {
+ if (isset($columnMeta['Field'])) {
+ $form_params['field_orig[' . $columnNumber . ']']
+ = $columnMeta['Field'];
+ } else {
+ $form_params['field_orig[' . $columnNumber . ']'] = '';
+ }
+ // old column length
+ $form_params['field_length_orig[' . $columnNumber . ']'] = $length;
+
+ // old column default
+ $form_params['field_default_orig[' . $columnNumber . ']']
+ = (isset($columnMeta['Default']) ? $columnMeta['Default'] : '');
+
+ return $form_params;
+}
+?>
diff --git a/libraries/tbl_common.inc.php b/libraries/tbl_common.inc.php
new file mode 100644
index 0000000000..ea079474f8
--- /dev/null
+++ b/libraries/tbl_common.inc.php
@@ -0,0 +1,63 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Common includes for the table level views
+ *
+ * @package PhpMyAdmin
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ * Gets some core libraries
+ */
+require_once './libraries/bookmark.lib.php';
+
+// Check parameters
+PMA_Util::checkParameters(array('db', 'table'));
+
+$db_is_information_schema = $GLOBALS['dbi']->isSystemSchema($db);
+
+/**
+ * Set parameters for links
+ * @deprecated
+ */
+$url_query = PMA_URL_getCommon($db, $table);
+
+/**
+ * Set parameters for links
+ */
+$url_params = array();
+$url_params['db'] = $db;
+$url_params['table'] = $table;
+
+/**
+ * Defines the urls to return to in case of error in a sql statement
+ */
+$err_url_0 = $cfg['DefaultTabDatabase']
+ . PMA_URL_getCommon(array('db' => $db,));
+$err_url = $cfg['DefaultTabTable'] . PMA_URL_getCommon($url_params);
+
+
+/**
+ * Ensures the database and the table exist (else move to the "parent" script)
+ */
+require_once './libraries/db_table_exists.lib.php';
+
+if (PMA_Tracker::isActive()
+ && PMA_Tracker::isTracked($GLOBALS["db"], $GLOBALS["table"])
+ && ! isset($_REQUEST['submit_deactivate_now'])
+) {
+ $temp_msg = '<a href="tbl_tracking.php?' . $url_query . '">';
+ $temp_msg .= sprintf(
+ __('Tracking of %s is activated.'),
+ htmlspecialchars($GLOBALS["db"] . '.' . $GLOBALS["table"])
+ );
+ $temp_msg .= '</a>';
+
+ $msg = PMA_Message::notice($temp_msg);
+ $msg->display();
+}
+
+?>
diff --git a/libraries/tbl_gis_visualization.lib.php b/libraries/tbl_gis_visualization.lib.php
new file mode 100644
index 0000000000..eac9740520
--- /dev/null
+++ b/libraries/tbl_gis_visualization.lib.php
@@ -0,0 +1,358 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Functions used to generate GIS visualizations.
+ *
+ * @package PhpMyAdmin
+ */
+if (!defined('PHPMYADMIN')) {
+ exit;
+}
+
+require_once 'libraries/sql.lib.php';
+
+/**
+ * Returns a modified sql query with only the label column
+ * and spatial column(wrapped with 'ASTEXT()' function).
+ *
+ * @param string $sql_query original sql query
+ * @param array $visualizationSettings settings for the visualization
+ *
+ * @return string the modified sql query.
+ */
+function PMA_GIS_modifyQuery($sql_query, $visualizationSettings)
+{
+ $modified_query = 'SELECT ';
+
+ $analyzed_query = PMA_SQP_analyze(PMA_SQP_parse($sql_query));
+ // If select clause is not *
+ if (trim($analyzed_query[0]['select_expr_clause']) != '*') {
+ // If label column is chosen add it to the query
+ if (isset($visualizationSettings['labelColumn'])
+ && $visualizationSettings['labelColumn'] != ''
+ ) {
+ // Check to see whether an alias has been used on the label column
+ $is_label_alias = false;
+ foreach ($analyzed_query[0]['select_expr'] as $select) {
+ if ($select['alias'] == $visualizationSettings['labelColumn']) {
+ $modified_query .= sanitize($select) . ' AS `'
+ . $select['alias'] . '`, ';
+ $is_label_alias = true;
+ break;
+ }
+ }
+ // If no alias have been used on the label column
+ if (! $is_label_alias) {
+ foreach ($analyzed_query[0]['select_expr'] as $select) {
+ if ($select['column'] == $visualizationSettings['labelColumn']) {
+ $modified_query .= sanitize($select) . ', ';
+ }
+ }
+ }
+ }
+
+ // Check to see whether an alias has been used on the spatial column
+ $is_spatial_alias = false;
+ foreach ($analyzed_query[0]['select_expr'] as $select) {
+ if ($select['alias'] == $visualizationSettings['spatialColumn']) {
+ $sanitized = sanitize($select);
+ $modified_query .= 'ASTEXT(' . $sanitized . ') AS `'
+ . $select['alias'] . '`, ';
+ // Get the SRID
+ $modified_query .= 'SRID(' . $sanitized . ') AS `srid` ';
+ $is_spatial_alias = true;
+ break;
+ }
+ }
+ // If no alias have been used on the spatial column
+ if (! $is_spatial_alias) {
+ foreach ($analyzed_query[0]['select_expr'] as $select) {
+ if ($select['column'] == $visualizationSettings['spatialColumn']) {
+ $sanitized = sanitize($select);
+ $modified_query .= 'ASTEXT(' . $sanitized
+ . ') AS `' . $select['column'] . '`, ';
+ // Get the SRID
+ $modified_query .= 'SRID(' . $sanitized . ') AS `srid` ';
+ }
+ }
+ }
+ // If select clause is *
+ } else {
+ // If label column is chosen add it to the query
+ if (isset($visualizationSettings['labelColumn'])
+ && $visualizationSettings['labelColumn'] != ''
+ ) {
+ $modified_query .= '`' . $visualizationSettings['labelColumn'] .'`, ';
+ }
+
+ // Wrap the spatial column with 'ASTEXT()' function and add it
+ $modified_query .= 'ASTEXT(`' . $visualizationSettings['spatialColumn']
+ . '`) AS `' . $visualizationSettings['spatialColumn'] . '`, ';
+
+ // Get the SRID
+ $modified_query .= 'SRID(`' . $visualizationSettings['spatialColumn']
+ . '`) AS `srid` ';
+ }
+
+ // Append the rest of the query
+ $from_pos = stripos($sql_query, 'FROM');
+ $modified_query .= substr($sql_query, $from_pos);
+ return $modified_query;
+}
+
+/**
+ * Local function to sanitize the expression taken
+ * from the results of PMA_SQP_analyze function.
+ *
+ * @param array $select Select to sanitize.
+ *
+ * @return string Sanitized string.
+ */
+function sanitize($select)
+{
+ $table_col = $select['table_name'] . "." . $select['column'];
+ $db_table_col = $select['db'] . "." . $select['table_name']
+ . "." . $select['column'];
+
+ if ($select['expr'] == $select['column']) {
+ return "`" . $select['column'] . "`";
+ } elseif ($select['expr'] == $table_col) {
+ return "`" . $select['table_name'] . "`.`" . $select['column'] . "`";
+ } elseif ($select['expr'] == $db_table_col) {
+ return "`" . $select['db'] . "`.`" . $select['table_name']
+ . "`.`" . $select['column'] . "`";
+ }
+ return $select['expr'];
+}
+
+/**
+ * Formats a visualization for the GIS query results.
+ *
+ * @param array $data Data for the status chart
+ * @param array &$visualizationSettings Settings used to generate the chart
+ * @param string $format Format of the visulaization
+ *
+ * @return string|void HTML and JS code for the GIS visualization
+ */
+function PMA_GIS_visualizationResults($data, &$visualizationSettings, $format)
+{
+ include_once './libraries/gis/pma_gis_visualization.php';
+ include_once './libraries/gis/pma_gis_factory.php';
+
+ if (! isset($data[0])) {
+ // empty data
+ return __('No data found for GIS visualization.');
+ } else {
+ $visualization = new PMA_GIS_Visualization($data, $visualizationSettings);
+ if ($visualizationSettings != null) {
+ foreach ($visualization->getSettings() as $setting => $val) {
+ if (! isset($visualizationSettings[$setting])) {
+ $visualizationSettings[$setting] = $val;
+ }
+ }
+ }
+ if ($format == 'svg') {
+ return $visualization->asSvg();
+ } elseif ($format == 'png') {
+ return $visualization->asPng();
+ } elseif ($format == 'ol') {
+ return $visualization->asOl();
+ }
+ }
+}
+
+/**
+ * Generate visualization for the GIS query results and save it to a file.
+ *
+ * @param array $data data for the status chart
+ * @param array $visualizationSettings settings used to generate the chart
+ * @param string $format format of the visulaization
+ * @param string $fileName file name
+ *
+ * @return file File containing the visualization
+ */
+function PMA_GIS_saveToFile($data, $visualizationSettings, $format, $fileName)
+{
+ include_once './libraries/gis/pma_gis_visualization.php';
+ include_once './libraries/gis/pma_gis_factory.php';
+
+ if (isset($data[0])) {
+ $visualization = new PMA_GIS_Visualization($data, $visualizationSettings);
+
+ if ($format == 'svg') {
+ $visualization->toFileAsSvg($fileName);
+ } elseif ($format == 'png') {
+ $visualization->toFileAsPng($fileName);
+ } elseif ($format == 'pdf') {
+ $visualization->toFileAsPdf($fileName);
+ }
+ }
+}
+
+/**
+ * Function to get html for the lebel column and spatial column
+ *
+ * @param string $column the column type. i.e either "labelColumn"
+ * or "spatialColumn"
+ * @param array $columnCandidates the list of select options
+ * @param array $visualizationSettings visualization settings
+ *
+ * @return string $html
+ */
+function PMA_getHtmlForColumn($column, $columnCandidates, $visualizationSettings)
+{
+ $html = '<tr><td><label for="labelColumn">';
+ $html .= ($column=="labelColumn") ? __("Label column") : __("Spatial column");
+ $html .= '</label></td>';
+
+ $html .= '<td><select name="visualizationSettings[' . $column . ']" id="'
+ . $column . '">';
+
+ if ($column == "labelColumn") {
+ $html .= '<option value="">' . __("-- None --") . '</option>';
+ }
+
+ $html .= PMA_getHtmlForOptionsList(
+ $columnCandidates, array($visualizationSettings[$column])
+ );
+
+ $html .= '</select></td>';
+ $html .= '</tr>';
+
+ return $html;
+}
+
+/**
+ * Function to get HTML for the option of using open street maps
+ *
+ * @param boolean $isSelected the default value
+ *
+ * @return string HTML string
+ */
+function PMA_getHtmlForUseOpenStreetMaps($isSelected)
+{
+ $html = '<tr><td class="choice" colspan="2">';
+ $html .= '<input type="checkbox" name="visualizationSettings[choice]"'
+ . 'id="choice" value="useBaseLayer"';
+ if ($isSelected) {
+ $html .= ' checked="checked"';
+ }
+ $html .= '/>';
+ $html .= '<label for="choice">';
+ $html .= __("Use OpenStreetMaps as Base Layer");
+ $html .= '</label>';
+ $html .= '</td></tr>';
+
+ return $html;
+}
+
+/**
+ * Function to generate HTML for the GIS visualization page
+ *
+ * @param array $url_params url parameters
+ * @param array $labelCandidates list of candidates for the label
+ * @param array $spatialCandidates list of candidates for the spatial column
+ * @param array $visualizationSettings visualization settings
+ * @param String $sql_query the sql query
+ * @param String $visualization HTML and js code for the visualization
+ * @param boolean $svg_support whether svg download format is supported
+ * @param array $data array of visualizing data
+ *
+ * @return string HTML code for the GIS visualization
+ */
+function PMA_getHtmlForGisVisualization(
+ $url_params, $labelCandidates, $spatialCandidates, $visualizationSettings,
+ $sql_query, $visualization, $svg_support, $data
+) {
+ $html = '<div id="div_view_options">';
+ $html .= '<fieldset>';
+ $html .= '<legend>' . __('Display GIS Visualization') . '</legend>';
+
+ $html .= '<div style="width: 400px; float: left;">';
+ $html .= '<form method="post" action="tbl_gis_visualization.php">';
+ $html .= PMA_URL_getHiddenInputs($url_params);
+ $html .= '<table class="gis_table">';
+
+ $html .= PMA_getHtmlForColumn(
+ "labelColumn", $labelCandidates, $visualizationSettings
+ );
+
+ $html .= PMA_getHtmlForColumn(
+ "spatialColumn", $spatialCandidates, $visualizationSettings
+ );
+
+ $html .= '<tr><td></td>';
+ $html .= '<td class="button"><input type="submit"';
+ $html .= ' name="displayVisualizationBtn" value="';
+ $html .= __('Redraw');
+ $html .= '" /></td></tr>';
+
+ if (! $GLOBALS['PMA_Config']->isHttps()) {
+ $isSelected = isset($visualizationSettings['choice']) ? true : false;
+ $html .= PMA_getHtmlForUseOpenStreetMaps($isSelected);
+ }
+
+ $html .= '</table>';
+ $html .= '<input type="hidden" name="displayVisualization" value="redraw">';
+ $html .= '<input type="hidden" name="sql_query" value="';
+ $html .= htmlspecialchars($sql_query) . '" />';
+ $html .= '</form>';
+ $html .= '</div>';
+
+ $html .= '<div style="float:left;">';
+ $html .= '<form method="post" class="disableAjax"';
+ $html .= ' action="tbl_gis_visualization.php">';
+ $html .= PMA_URL_getHiddenInputs($url_params);
+ $html .= '<table class="gis_table">';
+ $html .= '<tr><td><label for="fileName">';
+ $html .= __("File name") . '</label></td>';
+ $html .= '<td><input type="text" name="fileName" id="fileName" /></td></tr>';
+
+ $html .= '<tr><td><label for="fileFormat">';
+ $html .= __("Format") . '</label></td>';
+ $html .= '<td><select name="fileFormat" id="fileFormat">';
+ $html .= '<option value="png">PNG</option>';
+ $html .= '<option value="pdf">PDF</option>';
+
+ if ($svg_support) {
+ $html .= '<option value="svg" selected="selected">SVG</option>';
+ }
+ $html .= '</select></td></tr>';
+
+ $html .= '<tr><td></td>';
+ $html .= '<td class="button"><input type="submit" name="saveToFileBtn" value="';
+ $html .= __('Download') . '" /></td></tr>';
+ $html .= '</table>';
+
+ $html .= '<input type="hidden" name="saveToFile" value="download">';
+ $html .= '<input type="hidden" name="sql_query" value="';
+ $html .= htmlspecialchars($sql_query) . '" />';
+ $html .= '</form>';
+ $html .= '</div>';
+
+ $html .= '<div style="clear:both;">&nbsp;</div>';
+
+ $html .= '<div id="placeholder" style="width:';
+ $html .= htmlspecialchars($visualizationSettings['width']) . 'px;height:';
+ $html .= htmlspecialchars($visualizationSettings['height']) . 'px;">';
+ $html .= $visualization;
+ $html .= '</div>';
+
+ $html .= '<div id="openlayersmap"></div>';
+ $html .= '<input type="hidden" id="pmaThemeImage" value="';
+ $html .= $GLOBALS['pmaThemeImage'] . '" />';
+ $html .= '<script language="javascript" type="text/javascript">';
+ $html .= 'function drawOpenLayers()';
+ $html .= '{';
+
+ if (! $GLOBALS['PMA_Config']->isHttps()) {
+ $html .= PMA_GIS_visualizationResults($data, $visualizationSettings, 'ol');
+ }
+ $html .= '}';
+ $html .= '</script>';
+ $html .= '</fieldset>';
+ $html .= '</div>';
+
+ return $html;
+}
+?>
diff --git a/libraries/tbl_indexes.lib.php b/libraries/tbl_indexes.lib.php
new file mode 100644
index 0000000000..aca3ee52ea
--- /dev/null
+++ b/libraries/tbl_indexes.lib.php
@@ -0,0 +1,406 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Set of functions related to table indexes
+ *
+ * @package PhpMyAdmin
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ * Function to get the name and type of the columns of a table
+ *
+ * @param string $db current database
+ * @param string $table current table
+ *
+ * @return array
+ */
+function PMA_getNameAndTypeOfTheColumns($db, $table)
+{
+ $columns = array();
+ foreach ($GLOBALS['dbi']->getColumnsFull($db, $table) as $row) {
+ if (preg_match('@^(set|enum)\((.+)\)$@i', $row['Type'], $tmp)) {
+ $tmp[2] = substr(
+ preg_replace('@([^,])\'\'@', '\\1\\\'', ',' . $tmp[2]), 1
+ );
+ $columns[$row['Field']] = $tmp[1] . '('
+ . str_replace(',', ', ', $tmp[2]) . ')';
+ } else {
+ $columns[$row['Field']] = $row['Type'];
+ }
+ } // end while
+
+ return $columns;
+}
+
+/**
+ * Function to handle the creation or edit of an index
+ *
+ * @param string $db current db
+ * @param string $table current table
+ * @param Object $index current index
+ *
+ * @return void
+ */
+function PMA_handleCreateOrEditIndex($db, $table, $index)
+{
+ $error = false;
+
+ $sql_query = PMA_getSqlQueryForIndexCreateOrEdit($db, $table, $index, $error);
+
+ if (! $error) {
+ $GLOBALS['dbi']->query($sql_query);
+ $message = PMA_Message::success(
+ __('Table %1$s has been altered successfully')
+ );
+ $message->addParam($table);
+
+ if ($GLOBALS['is_ajax_request'] == true) {
+ $response = PMA_Response::getInstance();
+ $response->addJSON('message', $message);
+ $response->addJSON('index_table', PMA_Index::getView($table, $db));
+ $response->addJSON(
+ 'sql_query',
+ PMA_Util::getMessage(null, $sql_query)
+ );
+ } else {
+ $active_page = 'tbl_structure.php';
+ include 'tbl_structure.php';
+ }
+ exit;
+ } else {
+ $response = PMA_Response::getInstance();
+ $response->isSuccess(false);
+ $response->addJSON('message', $error);
+ exit;
+ }
+}
+
+/**
+ * Function to get the sql query for index creation or edit
+ *
+ * @param string $db current db
+ * @param string $table current table
+ * @param Object $index current index
+ * @param bool &$error whether error occoured or not
+ *
+ * @return string
+ */
+function PMA_getSqlQueryForIndexCreateOrEdit($db, $table, $index, &$error)
+{
+ // $sql_query is the one displayed in the query box
+ $sql_query = 'ALTER TABLE ' . PMA_Util::backquote($db)
+ . '.' . PMA_Util::backquote($table);
+
+ // Drops the old index
+ if (! empty($_REQUEST['old_index'])) {
+ if ($_REQUEST['old_index'] == 'PRIMARY') {
+ $sql_query .= ' DROP PRIMARY KEY,';
+ } else {
+ $sql_query .= ' DROP INDEX '
+ . PMA_Util::backquote($_REQUEST['old_index']) . ',';
+ }
+ } // end if
+
+ // Builds the new one
+ switch ($index->getType()) {
+ case 'PRIMARY':
+ if ($index->getName() == '') {
+ $index->setName('PRIMARY');
+ } elseif ($index->getName() != 'PRIMARY') {
+ $error = PMA_Message::error(
+ __('The name of the primary key must be "PRIMARY"!')
+ );
+ }
+ $sql_query .= ' ADD PRIMARY KEY';
+ break;
+ case 'FULLTEXT':
+ case 'UNIQUE':
+ case 'INDEX':
+ case 'SPATIAL':
+ if ($index->getName() == 'PRIMARY') {
+ $error = PMA_Message::error(__('Can\'t rename index to PRIMARY!'));
+ }
+ $sql_query .= ' ADD ' . $index->getType() . ' '
+ . ($index->getName() ? PMA_Util::backquote($index->getName()) : '');
+ break;
+ } // end switch
+
+ $index_fields = array();
+ foreach ($index->getColumns() as $key => $column) {
+ $index_fields[$key] = PMA_Util::backquote($column->getName());
+ if ($column->getSubPart()) {
+ $index_fields[$key] .= '(' . $column->getSubPart() . ')';
+ }
+ } // end while
+
+ if (empty($index_fields)) {
+ $error = PMA_Message::error(__('No index parts defined!'));
+ } else {
+ $sql_query .= ' (' . implode(', ', $index_fields) . ')';
+ }
+
+ if (PMA_MYSQL_INT_VERSION > 50500) {
+ $sql_query .= "COMMENT '"
+ . PMA_Util::sqlAddSlashes($index->getComment())
+ . "'";
+ }
+ $sql_query .= ';';
+
+ return $sql_query;
+}
+
+/**
+ * Function to prepare the form values for index
+ *
+ * @param string $db curent database
+ * @param string $table current table
+ *
+ * @return PMA_Index
+ */
+function PMA_prepareFormValues($db, $table)
+{
+ if (isset($_REQUEST['index'])) {
+ if (is_array($_REQUEST['index'])) {
+ // coming already from form
+ $index = new PMA_Index($_REQUEST['index']);
+ } else {
+ $index = PMA_Index::singleton($db, $table, $_REQUEST['index']);
+ }
+ } else {
+ $index = new PMA_Index;
+ }
+
+ return $index;
+}
+
+/**
+ * Function to get the number of fields for the form
+ *
+ * @param Object $index index
+ *
+ * @return int
+ */
+function PMA_getNumberOfFieldsForForm($index)
+{
+ if (isset($_REQUEST['index']) && is_array($_REQUEST['index'])) {
+ // coming already from form
+ $add_fields
+ = count($_REQUEST['index']['columns']['names'])
+ - $index->getColumnCount();
+ if (isset($_REQUEST['add_fields'])) {
+ $add_fields += $_REQUEST['added_fields'];
+ }
+ } elseif (isset($_REQUEST['create_index'])) {
+ $add_fields = $_REQUEST['added_fields'];
+ } else {
+ $add_fields = 1;
+ }// end preparing form values
+
+ return $add_fields;
+}
+
+/**
+ * Function to get form parameters
+ *
+ * @param string $db current db
+ * @param string $table current table
+ *
+ * @return array
+ */
+function PMA_getFormParameters($db, $table)
+{
+ $form_params = array(
+ 'db' => $db,
+ 'table' => $table,
+ );
+
+ if (isset($_REQUEST['create_index'])) {
+ $form_params['create_index'] = 1;
+ } elseif (isset($_REQUEST['old_index'])) {
+ $form_params['old_index'] = $_REQUEST['old_index'];
+ } elseif (isset($_REQUEST['index'])) {
+ $form_params['old_index'] = $_REQUEST['index'];
+ }
+
+ return $form_params;
+}
+
+/**
+ * Function to get html for displaying the index form
+ *
+ * @param array $fields fields
+ * @param Object $index index
+ * @param array $form_params form parameters
+ * @param int $add_fields number of fields in the form
+ *
+ * @return string
+ */
+function PMA_getHtmlForIndexForm($fields, $index, $form_params, $add_fields)
+{
+ $html = "";
+ $html .= '<form action="tbl_indexes.php" method="post" name="index_frm" id="'
+ . 'index_frm" class="ajax"'
+ . 'onsubmit="if (typeof(this.elements[\'index[Key_name]\'].disabled) !='
+ . ' \'undefined\') {'
+ . 'this.elements[\'index[Key_name]\'].disabled = false}">';
+
+ $html .= PMA_URL_getHiddenInputs($form_params);
+
+ $html .= '<fieldset id="index_edit_fields">';
+
+ $html .= '<div class="index_info">';
+
+ $html .= '<div>'
+ . '<div class="label">'
+ . '<strong>'
+ . '<label for="input_index_name">'
+ . __('Index name:')
+ . PMA_Util::showHint(
+ PMA_Message::notice(
+ __(
+ '"PRIMARY" <b>must</b> be the name of'
+ . ' and <b>only of</b> a primary key!'
+ )
+ )
+ )
+ . '</label>'
+ . '</strong>'
+ . '</div>'
+ . '<input type="text" name="index[Key_name]" id="input_index_name"'
+ . ' size="25"'
+ . 'value="' . htmlspecialchars($index->getName()) . '"'
+ . 'onfocus="this.select()" />'
+ . '</div>';
+
+ if (PMA_MYSQL_INT_VERSION > 50500) {
+ $html .= '<div>'
+ . '<div class="label">'
+ . '<strong>'
+ . '<label for="input_index_comment">'
+ . __('Comment:')
+ . '</label>'
+ . '</strong>'
+ . '</div>'
+ . '<input type="text" name="index[Index_comment]" '
+ . 'id="input_index_comment" size="30"'
+ . 'value="' . htmlspecialchars($index->getComment()) . '"'
+ . 'onfocus="this.select()" />'
+ . '</div>';
+ }
+
+ $html .= '<div>'
+ . '<div class="label">'
+ . '<strong>'
+ . '<label for="select_index_type">'
+ . __('Index type:')
+ . PMA_Util::showMySQLDocu('ALTER_TABLE')
+ . '</label>'
+ . '</strong>'
+ . '</div>'
+ . '<select name="index[Index_type]" id="select_index_type" >'
+ . $index->generateIndexSelector()
+ . '</select>'
+ . '</div>';
+
+ $html .= '<div class="clearfloat"></div>';
+
+ $html .= '</div>';
+
+ $html .= '<table id="index_columns">';
+
+ $html .= '<thead>'
+ . '<tr>'
+ . '<th>' . __('Column') . '</th>'
+ . '<th>' . __('Size') . '</th>'
+ . '</tr>'
+ . '</thead>';
+
+ $odd_row = true;
+ $spatial_types = array(
+ 'geometry', 'point', 'linestring', 'polygon', 'multipoint',
+ 'multilinestring', 'multipolygon', 'geomtrycollection'
+ );
+ $html .= '<tbody>';
+ foreach ($index->getColumns() as $column) {
+ $html .= '<tr class="';
+ $html .= $odd_row ? 'odd' : 'even';
+ $html .= 'noclick">';
+ $html .= '<td>';
+ $html .= '<select name="index[columns][names][]">';
+ $html .= '<option value="">-- ' . __('Ignore') . ' --</option>';
+ foreach ($fields as $field_name => $field_type) {
+ if (($index->getType() != 'FULLTEXT'
+ || preg_match('/(char|text)/i', $field_type))
+ && ($index->getType() != 'SPATIAL'
+ || in_array($field_type, $spatial_types))
+ ) {
+ $html .= '<option value="' . htmlspecialchars($field_name) . '"'
+ . (($field_name == $column->getName())
+ ? ' selected="selected"'
+ : '') . '>'
+ . htmlspecialchars($field_name) . ' ['
+ . htmlspecialchars($field_type) . ']'
+ . '</option>' . "\n";
+ }
+ } // end foreach $fields
+ $html .= '</select>';
+ $html .= '</td>';
+ $html .= '<td>';
+ $html .= '<input type="text" size="5" onfocus="this.select()"'
+ . 'name="index[columns][sub_parts][]" value="';
+ if ($index->getType() != 'SPATIAL') {
+ $html .= $column->getSubPart();
+ }
+ $html .= '"/>';
+ $html .= '</td>';
+ $html .= '</tr>';
+ $odd_row = !$odd_row;
+ } // end foreach $edited_index_info['Sequences']
+
+ for ($i = 0; $i < $add_fields; $i++) {
+ $html .= '<tr class="';
+ $html .= $odd_row ? 'odd' : 'even';
+ $html .= 'noclick">';
+ $html .= '<td>';
+ $html .= '<select name="index[columns][names][]">';
+ $html .= '<option value="">-- ' . __('Ignore') . ' --</option>';
+ foreach ($fields as $field_name => $field_type) {
+ $html .= '<option value="' . htmlspecialchars($field_name) . '">'
+ . htmlspecialchars($field_name) . ' ['
+ . htmlspecialchars($field_type) . ']'
+ . '</option>' . "\n";
+ } // end foreach $fields
+ $html .= '</select>';
+ $html .= '</td>';
+ $html .= '<td>'
+ . '<input type="text" size="5" onfocus="this.select()"'
+ . 'name="index[columns][sub_parts][]" value="" />'
+ . '</td>';
+ $html .= '</tr>';
+ $odd_row = !$odd_row;
+ } // end foreach $edited_index_info['Sequences']
+
+ $html .= '</tbody>';
+
+ $html .= '</table>';
+
+ $html .= '</fieldset>';
+
+ $html .= '<fieldset class="tblFooters">';
+
+ $btn_value = sprintf(__('Add %s column(s) to index'), 1);
+ $html .= '<div class="slider"></div>';
+ $html .= '<div class="add_fields">';
+ $html .= '<input type="submit" value="' . $btn_value . '" />';
+ $html .= '</div>';
+
+ $html .= '</fieldset>';
+
+ $html .= '</form>';
+
+ return $html;
+}
+?> \ No newline at end of file
diff --git a/libraries/tbl_info.inc.php b/libraries/tbl_info.inc.php
new file mode 100644
index 0000000000..c1ed2cd75e
--- /dev/null
+++ b/libraries/tbl_info.inc.php
@@ -0,0 +1,105 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * extracts table properties from create statement
+ *
+ * @todo should be handled by class Table
+ * @todo this should be recoded as functions, to avoid messing with global variables
+ *
+ * @package PhpMyAdmin
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+// Check parameters
+PMA_Util::checkParameters(array('db', 'table'));
+
+/**
+ * Defining global variables, in case this script is included by a function.
+ */
+global $showtable, $tbl_is_view, $tbl_storage_engine, $show_comment, $tbl_collation,
+ $table_info_num_rows, $auto_increment;
+
+/**
+ * Gets table informations
+ */
+// Seems we need to do this in MySQL 5.0.2,
+// otherwise error #1046, no database selected
+$GLOBALS['dbi']->selectDb($GLOBALS['db']);
+
+
+/**
+ * Holds information about the current table
+ *
+ * @todo replace this by PMA_Table
+ * @global array $GLOBALS['showtable']
+ * @name $showtable
+ */
+$GLOBALS['showtable'] = array();
+
+// PMA_Table::sGetStatusInfo() does caching by default, but here
+// we force reading of the current table status
+// if $reread_info is true (for example, coming from tbl_operations.php
+// and we just changed the table's storage engine)
+$GLOBALS['showtable'] = PMA_Table::sGetStatusInfo(
+ $GLOBALS['db'],
+ $GLOBALS['table'],
+ null,
+ (isset($reread_info) && $reread_info ? true : false)
+);
+
+// need this test because when we are creating a table, we get 0 rows
+// from the SHOW TABLE query
+// and we don't want to mess up the $tbl_storage_engine coming from the form
+
+if ($showtable) {
+ if (PMA_Table::isView($GLOBALS['db'], $GLOBALS['table'])) {
+ $tbl_is_view = true;
+ $tbl_storage_engine = __('View');
+ $show_comment = null;
+ } else {
+ $tbl_is_view = false;
+ $tbl_storage_engine = isset($showtable['Engine'])
+ ? strtoupper($showtable['Engine'])
+ : '';
+ $show_comment = '';
+ if (isset($showtable['Comment'])) {
+ $show_comment = $showtable['Comment'];
+ }
+ }
+ $tbl_collation = empty($showtable['Collation'])
+ ? ''
+ : $showtable['Collation'];
+
+ if (null === $showtable['Rows']) {
+ $showtable['Rows'] = PMA_Table::countRecords(
+ $GLOBALS['db'], $showtable['Name'], true
+ );
+ }
+ $table_info_num_rows = isset($showtable['Rows']) ? $showtable['Rows'] : 0;
+ $row_format = isset($showtable['Row_format']) ? $showtable['Row_format'] : '';
+ $auto_increment = isset($showtable['Auto_increment'])
+ ? $showtable['Auto_increment']
+ : '';
+
+ $create_options = isset($showtable['Create_options'])
+ ? explode(' ', $showtable['Create_options'])
+ : array();
+
+ // export create options by its name as variables into global namespace
+ // f.e. pack_keys=1 becomes available as $pack_keys with value of '1'
+ unset($pack_keys);
+ foreach ($create_options as $each_create_option) {
+ $each_create_option = explode('=', $each_create_option);
+ if (isset($each_create_option[1])) {
+ $$each_create_option[0] = $each_create_option[1];
+ }
+ }
+ // we need explicit DEFAULT value here (different from '0')
+ $pack_keys = (! isset($pack_keys) || strlen($pack_keys) == 0)
+ ? 'DEFAULT'
+ : $pack_keys;
+ unset($create_options, $each_create_option);
+} // end if
+?>
diff --git a/libraries/tbl_printview.lib.php b/libraries/tbl_printview.lib.php
new file mode 100644
index 0000000000..d21895e94e
--- /dev/null
+++ b/libraries/tbl_printview.lib.php
@@ -0,0 +1,600 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Set of functions related to show table print view
+ *
+ * @package PhpMyAdmin
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ * return html for tables' info
+ *
+ * @param array $the_tables selected tables
+ *
+ * @return string
+ */
+function PMA_getHtmlForTablesInfo($the_tables)
+{
+ $html = '';
+ $multi_tables = (count($the_tables) > 1);
+
+ if ($multi_tables) {
+ $tbl_list = '';
+ foreach ($the_tables as $key => $table) {
+ $tbl_list .= (empty($tbl_list) ? '' : ', ')
+ . PMA_Util::backquote($table);
+ }
+ $html .= '<strong>'. __('Showing tables:') . ' '
+ . htmlspecialchars($tbl_list) . '</strong>' . "\n";
+ $html .= '<hr />' . "\n";
+ } // end if
+
+ return $html;
+}
+
+
+/**
+ * return html for print view footer
+ *
+ * @return string
+ */
+function PMA_getHtmlForPrintViewFooter()
+{
+ $html = PMA_Util::getButton();
+ $html .= "<div id='PMA_disable_floating_menubar'></div>\n";
+
+ return $html;
+}
+
+/**
+ * return html for Print View Columns
+ *
+ * @param array $columns columns list
+ * @param array $analyzed_sql analyzed sql
+ * @param array $pk_array primary key array
+ * @param bool $have_rel have relation?
+ * @param array $res_rel relations array
+ * @param string $db database name
+ * @param string $table table name
+ * @param array $cfgRelation config from PMA_getRelationsParam
+ *
+ * @return string
+ */
+function PMA_getHtmlForPrintViewColumns(
+ $columns, $analyzed_sql, $pk_array, $have_rel,
+ $res_rel, $db, $table, $cfgRelation
+) {
+ $html = '';
+ foreach ($columns as $row) {
+ $extracted_columnspec = PMA_Util::extractColumnSpec($row['Type']);
+ $type = $extracted_columnspec['print_type'];
+ $attribute = $extracted_columnspec['attribute'];
+
+ if (! isset($row['Default'])) {
+ if ($row['Null'] != '' && $row['Null'] != 'NO') {
+ $row['Default'] = '<i>NULL</i>';
+ }
+ } else {
+ $row['Default'] = htmlspecialchars($row['Default']);
+ }
+ $field_name = htmlspecialchars($row['Field']);
+
+ // here, we have a TIMESTAMP that SHOW FULL COLUMNS reports as having the
+ // NULL attribute, but SHOW CREATE TABLE says the contrary. Believe
+ // the latter.
+ /**
+ * @todo merge this logic with the one in tbl_structure.php
+ * or move it in a function similar to $GLOBALS['dbi']->getColumnsFull()
+ * but based on SHOW CREATE TABLE because information_schema
+ * cannot be trusted in this case (MySQL bug)
+ */
+ $analyzed_for_field = $analyzed_sql[0]['create_table_fields'][$field_name];
+ if (! empty($analyzed_for_field['type'])
+ && $analyzed_for_field['type'] == 'TIMESTAMP'
+ && $analyzed_for_field['timestamp_not_null']
+ ) {
+ $row['Null'] = '';
+ }
+
+ $html .= "\n";
+ $html .= '<tr><td>';
+
+ if (isset($pk_array[$row['Field']])) {
+ $html .= ' <u>' . $field_name . '</u>' . "\n";
+ } else {
+ $html .= ' ' . $field_name . "\n";
+ }
+ $html .= '</td>';
+ $html .= '<td>' . $type. '<bdo dir="ltr"></bdo></td>';
+ $html .= '<td>';
+ $html .= (($row['Null'] == '' || $row['Null'] == 'NO')
+ ? __('No')
+ : __('Yes'));
+ $html .= '&nbsp;</td>';
+ $html .= '<td>';
+ if (isset($row['Default'])) {
+ $html .= $row['Default'];
+ }
+ $html .= '&nbsp;</td>';
+ if ($have_rel) {
+ $html .= ' <td>';
+ if (isset($res_rel[$field_name])) {
+ $html .= htmlspecialchars(
+ $res_rel[$field_name]['foreign_table']
+ . ' -> ' . $res_rel[$field_name]['foreign_field']
+ );
+ }
+ $html .= '&nbsp;</td>' . "\n";
+ }
+ $html .= ' <td>';
+ $comments = PMA_getComments($db, $table);
+ if (isset($comments[$field_name])) {
+ $html .= htmlspecialchars($comments[$field_name]);
+ }
+ $html .= '&nbsp;</td>' . "\n";
+ if ($cfgRelation['mimework']) {
+ $mime_map = PMA_getMIME($db, $table, true);
+
+ $html .= ' <td>';
+ if (isset($mime_map[$field_name])) {
+ $html .= htmlspecialchars(
+ str_replace('_', '/', $mime_map[$field_name]['mimetype'])
+ );
+ }
+ $html .= '&nbsp;</td>' . "\n";
+ }
+ $html .= '</tr>';
+ } // end foreach
+
+ return $html;
+}
+
+/**
+ * return html for Row Statistic
+ *
+ * @param array $showtable showing table information
+ * @param int $cell_align_left cell align left
+ * @param int $avg_size avg size
+ * @param int $avg_unit avg unit
+ * @param bool $mergetable is merge table?
+ *
+ * @return string
+ */
+function PMA_getHtmlForRowStatistics(
+ $showtable, $cell_align_left, $avg_size, $avg_unit, $mergetable
+) {
+ $html = '<td width="20">&nbsp;</td>';
+
+ // Rows Statistic
+ $html .= "\n";
+ $html .= '<td class="vtop">';
+ $html .= '<big>' . __('Row Statistics:') . '</big>';
+ $html .= '<table width="100%">';
+ if (isset($showtable['Row_format'])) {
+ $html .= "\n";
+ $html .= '<tr>';
+ $html .= '<td>' . __('Format') . '</td>';
+ $html .= '<td class="' . $cell_align_left . '">';
+ if ($showtable['Row_format'] == 'Fixed') {
+ $html .= __('static');
+ } elseif ($showtable['Row_format'] == 'Dynamic') {
+ $html .= __('dynamic');
+ } else {
+ $html .= $showtable['Row_format'];
+ }
+ $html .= '</td>';
+ $html .= '</tr>';
+ }
+ if (isset($showtable['Rows'])) {
+ $html .= "\n";
+ $html .= '<tr>';
+ $html .= '<td>' . __('Rows') . '</td>';
+ $html .= '<td class="right">';
+ $html .= PMA_Util::formatNumber($showtable['Rows'], 0);
+ $html .= '</td>';
+ $html .= '</tr>';
+ }
+ if (isset($showtable['Avg_row_length'])
+ && $showtable['Avg_row_length'] > 0
+ ) {
+ $html .= "\n";
+ $html .= '<tr>';
+ $html .= '<td>' . __('Row length') . '&nbsp;&oslash;</td>';
+ $html .= '<td>';
+ $html .= PMA_Util::formatNumber(
+ $showtable['Avg_row_length'], 0
+ );
+ $html .= '</td>';
+ $html .= '</tr>';
+ }
+ if (isset($showtable['Data_length'])
+ && $showtable['Rows'] > 0
+ && $mergetable == false
+ ) {
+ $html .= "\n";
+ $html .= '<tr>';
+ $html .= '<td>' . __('Row size') . '&nbsp;&oslash;</td>';
+ $html .= '<td class="right">';
+ $html .= $avg_size . ' ' . $avg_unit;
+ $html .= '</td>';
+ $html .= '</tr>';
+ }
+ if (isset($showtable['Auto_increment'])) {
+ $html .= "\n";
+ $html .= '<tr>';
+ $html .= '<td>' . __('Next autoindex'). ' </td>';
+ $html .= '<td class="right">';
+ $html .= PMA_Util::formatNumber(
+ $showtable['Auto_increment'], 0
+ );
+ $html .= '</td>';
+ $html .= '</tr>';
+ }
+ if (isset($showtable['Create_time'])) {
+ $html .= "\n";
+ $html .= '<tr>';
+ $html .= '<td>' . __('Creation') . '</td>';
+ $html .= '<td class="right">';
+ $html .= PMA_Util::localisedDate(
+ strtotime($showtable['Create_time'])
+ );
+ $html .= '</td>';
+ $html .= '</tr>';
+ }
+ if (isset($showtable['Update_time'])) {
+ $html .= "\n";
+ $html .= '<tr>';
+ $html .= '<td>' . __('Last update') . '</td>';
+ $html .= '<td class="right">';
+ $html .= PMA_Util::localisedDate(
+ strtotime($showtable['Update_time'])
+ );
+ $html .= '</td>';
+ $html .= '</tr>';
+ }
+ if (isset($showtable['Check_time'])) {
+ $html .= "\n";
+ $html .= '<tr>';
+ $html .= '<td>' . __('Last check') . '</td>';
+ $html .= '<td class="right">';
+ $html .= PMA_Util::localisedDate(
+ strtotime($showtable['Check_time'])
+ );
+ $html .= '</td>';
+ $html .= '</tr>';
+ }
+
+ return $html;
+}
+
+/**
+ * return html for Space Usage
+ *
+ * @param int $data_size data size
+ * @param int $data_unit data unit
+ * @param int $index_size index size
+ * @param int $index_unit index unit
+ * @param int $free_size free size
+ * @param int $free_unit free unit
+ * @param int $effect_size effect size
+ * @param int $effect_unit effect unit
+ * @param int $tot_size total size
+ * @param int $tot_unit total unit
+ * @param bool $mergetable is merge table?
+ *
+ * @return string
+ */
+function PMA_getHtmlForSpaceUsage(
+ $data_size, $data_unit, $index_size, $index_unit,
+ $free_size, $free_unit, $effect_size, $effect_unit,
+ $tot_size, $tot_unit, $mergetable
+) {
+ $html = '<table cellspacing="0" cellpadding="0">';
+ $html .= "\n";
+ $html .= '<tr>';
+
+ // Space usage
+ $html .= '<td class="vtop">';
+ $html .= '<big>' . __('Space usage:') . '</big>';
+ $html .= '<table width="100%">';
+ $html .= '<tr>';
+ $html .= '<td style="padding-right: 10px">' . __('Data') . '</td>';
+ $html .= '<td class="right">' . $data_size . '</td>';
+ $html .= '<td>' . $data_unit . '</td>';
+ $html .= '</tr>';
+ if (isset($index_size)) {
+ $html .= "\n";
+ $html .= '<tr>';
+ $html .= '<td style="padding-right: 10px">' . __('Index') . '</td>';
+ $html .= '<td class="right">' . $index_size . '</td>';
+ $html .= '<td>' . $index_unit. '</td>';
+ $html .= '</tr>';
+ }
+ if (isset($free_size)) {
+ $html .= "\n";
+ $html .= '<tr style="color: #bb0000">';
+ $html .= '<td style="padding-right: 10px">';
+ $html .= __('Overhead');
+ $html .= '</td>';
+ $html .= '<td class="right">' . $free_size . '</td>';
+ $html .= '<td>' . $free_unit . '</td>';
+ $html .= '</tr>';
+ $html .= '<tr>';
+ $html .= '<td style="padding-right: 10px">';
+ $html .= __('Effective');
+ $html .= '</td>';
+ $html .= '<td class="right">' . $effect_size . '</td>';
+ $html .= '<td>' . $effect_unit . '</td>';
+ $html .= '</tr>';
+ }
+ if (isset($tot_size) && $mergetable == false) {
+ $html .= "\n";
+ $html .= '<tr>';
+ $html .= '<td style="padding-right: 10px">' . __('Total') . '</td>';
+ $html .= '<td class="right">' . $tot_size . '</td>';
+ $html .= '<td>' . $tot_unit . '</td>';
+ $html .= '</tr>';
+ }
+ $html .= "\n";
+ $html .= '</table>';
+
+ return $html;
+}
+/**
+ * return html for Space Usage And Row Statistic
+ *
+ * @param array $showtable showing table information
+ * @param string $db database
+ * @param string $table table
+ * @param int $cell_align_left cell align left
+ *
+ * @return string
+ */
+function PMA_getHtmlForSpaceUsageAndRowStatistics(
+ $showtable, $db, $table, $cell_align_left
+) {
+ $html = '';
+ $nonisam = false;
+ if (isset($showtable['Type'])
+ && ! preg_match('@ISAM|HEAP@i', $showtable['Type'])
+ ) {
+ $nonisam = true;
+ }
+ if ($nonisam == false) {
+ // Gets some sizes
+
+ $mergetable = PMA_Table::isMerge($db, $table);
+
+ list($data_size, $data_unit) = PMA_Util::formatByteDown(
+ $showtable['Data_length']
+ );
+ if ($mergetable == false) {
+ list($index_size, $index_unit)
+ = PMA_Util::formatByteDown(
+ $showtable['Index_length']
+ );
+ }
+ if (isset($showtable['Data_free']) && $showtable['Data_free'] > 0) {
+ list($free_size, $free_unit)
+ = PMA_Util::formatByteDown(
+ $showtable['Data_free']
+ );
+ list($effect_size, $effect_unit)
+ = PMA_Util::formatByteDown(
+ $showtable['Data_length'] + $showtable['Index_length']
+ - $showtable['Data_free']
+ );
+ } else {
+ unset($free_size);
+ unset($free_unit);
+ list($effect_size, $effect_unit)
+ = PMA_Util::formatByteDown(
+ $showtable['Data_length'] + $showtable['Index_length']
+ );
+ }
+ list($tot_size, $tot_unit) = PMA_Util::formatByteDown(
+ $showtable['Data_length'] + $showtable['Index_length']
+ );
+ $num_rows = (isset($showtable['Rows']) ? $showtable['Rows'] : 0);
+ if ($num_rows > 0) {
+ list($avg_size, $avg_unit)
+ = PMA_Util::formatByteDown(
+ ($showtable['Data_length'] + $showtable['Index_length'])
+ / $showtable['Rows'],
+ 6,
+ 1
+ );
+ }
+
+ // Displays them
+ $html .= '<br /><br />';
+ $html .= PMA_getHtmlForSpaceUsage(
+ $data_size, $data_unit,
+ isset($index_size)? $index_size : null,
+ isset($index_unit)? $index_unit : null,
+ isset($free_size)? $free_size : null,
+ isset($free_unit)? $free_unit : null,
+ isset($effect_size)? $effect_size : null,
+ isset($effect_unit)? $effect_unit : null,
+ isset($tot_size)? $tot_size : null,
+ isset($tot_unit)? $tot_unit : null,
+ $mergetable
+ );
+
+ $html .= '</td>';
+ $html .= PMA_getHtmlForRowStatistics(
+ $showtable, $cell_align_left,
+ isset($avg_size)? $avg_size: 0,
+ isset($avg_unit)? $avg_unit: 0,
+ $mergetable
+ );
+ $html .= "\n";
+ $html .= '</table>';
+ $html .= '</td>';
+ $html .= '</tr>';
+ $html .= '</table>';
+ } // end if ($nonisam == false)
+
+ return $html;
+}
+
+/**
+ * return html for Table Structure
+ *
+ * @param bool $have_rel whether have relation
+ * @param array $tbl_is_view Is a table view?
+ * @param array $columns columns list
+ * @param array $analyzed_sql analyzed sql
+ * @param array $pk_array primary key array
+ * @param array $res_rel relations array
+ * @param string $db database
+ * @param string $table table
+ * @param array $cfgRelation config from PMA_getRelationsParam
+ * @param array $cfg global config
+ * @param array $showtable showing table information
+ * @param int $cell_align_left cell align left
+ *
+ * @return string
+ */
+function PMA_getHtmlForTableStructure(
+ $have_rel, $tbl_is_view, $columns, $analyzed_sql,
+ $pk_array, $res_rel, $db, $table, $cfgRelation,
+ $cfg, $showtable, $cell_align_left
+) {
+ /**
+ * Displays the table structure
+ */
+ $html = '<table style="width: 100%;">';
+ $html .= '<thead>';
+ $html .= '<tr>';
+ $html .= '<th>' . __('Column') . '</th>';
+ $html .= '<th>' . __('Type') . '</th>';
+ $html .= '<th>' . __('Null') . '</th>';
+ $html .= '<th>' . __('Default') . '</th>';
+ if ($have_rel) {
+ $html .= '<th>' . __('Links to') . '</th>' . "\n";
+ }
+ $html .= ' <th>' . __('Comments') . '</th>' . "\n";
+ if ($cfgRelation['mimework']) {
+ $html .= ' <th>MIME</th>' . "\n";
+ }
+ $html .= '</tr>';
+ $html .= '</thead>';
+ $html .= '<tbody>';
+ $html .= PMA_getHtmlForPrintViewColumns(
+ $columns, $analyzed_sql, $pk_array, $have_rel,
+ $res_rel, $db, $table, $cfgRelation
+ );
+ $html .= '</tbody>';
+ $html .= '</table>';
+ if (! $tbl_is_view && !$GLOBALS['dbi']->isSystemSchema($db)) {
+ /**
+ * Displays indexes
+ */
+ $html .= PMA_Index::getView($table, $db, true);
+
+ /**
+ * Displays Space usage and row statistics
+ *
+ */
+ if ($cfg['ShowStats']) {
+ $html .= PMA_getHtmlForSpaceUsageAndRowStatistics(
+ $showtable, $db, $table, $cell_align_left
+ );
+ } // end if ($cfg['ShowStats'])
+ }
+
+ return $html;
+}
+
+/**
+ * return html for tables' detail
+ *
+ * @param array $the_tables tables list
+ * @param string $db database name
+ * @param array $cfg global config
+ * @param array $cfgRelation config from PMA_getRelationsParam
+ * @param array $pk_array primary key array
+ * @param int $cell_align_left cell align left
+ *
+ * @return string
+ */
+function PMA_getHtmlForTablesDetail(
+ $the_tables, $db, $cfg, $cfgRelation, $pk_array, $cell_align_left
+) {
+ $html = '';
+ $tables_cnt = count($the_tables);
+ $multi_tables = (count($the_tables) > 1);
+ $counter = 0;
+
+ foreach ($the_tables as $key => $table) {
+ if ($counter + 1 >= $tables_cnt) {
+ $breakstyle = '';
+ } else {
+ $breakstyle = ' style="page-break-after: always;"';
+ }
+ $counter++;
+ $html .= '<div' . $breakstyle . '>' . "\n";
+ $html .= '<h1>' . htmlspecialchars($table) . '</h1>' . "\n";
+
+ /**
+ * Gets table informations
+ */
+ $showtable = PMA_Table::sGetStatusInfo($db, $table);
+ $num_rows = (isset($showtable['Rows']) ? $showtable['Rows'] : 0);
+ $show_comment = (isset($showtable['Comment']) ? $showtable['Comment'] : '');
+
+ $tbl_is_view = PMA_Table::isView($db, $table);
+
+ /**
+ * Gets fields properties
+ */
+ $columns = $GLOBALS['dbi']->getColumns($db, $table);
+
+ // We need this to correctly learn if a TIMESTAMP is NOT NULL, since
+ // SHOW FULL FIELDS or INFORMATION_SCHEMA incorrectly says NULL
+ // and SHOW CREATE TABLE says NOT NULL (tested
+ // in MySQL 4.0.25 and 5.0.21, http://bugs.mysql.com/20910).
+
+ $show_create_table = $GLOBALS['dbi']->fetchValue(
+ 'SHOW CREATE TABLE ' . PMA_Util::backquote($db) . '.'
+ . PMA_Util::backquote($table),
+ 0, 1
+ );
+ $analyzed_sql = PMA_SQP_analyze(PMA_SQP_parse($show_create_table));
+
+ // Check if we can use Relations
+ // Find which tables are related with the current one and write it in
+ // an array
+ $res_rel = PMA_getForeigners($db, $table);
+ $have_rel = (bool) count($res_rel);
+
+ /**
+ * Displays the comments of the table if MySQL >= 3.23
+ */
+ if (!empty($show_comment)) {
+ $html .= __('Table comments:') . ' '
+ . htmlspecialchars($show_comment) . '<br /><br />';
+ }
+
+ $html .= PMA_getHtmlForTableStructure(
+ $have_rel, $tbl_is_view, $columns, $analyzed_sql,
+ $pk_array, $res_rel, $db, $table, $cfgRelation,
+ $cfg, $showtable, $cell_align_left
+ );
+
+ if ($multi_tables) {
+ unset($num_rows, $show_comment);
+ $html .= '<hr />' . "\n";
+ } // end if
+ $html .= '</div>' . "\n";
+
+ } // end while
+
+ return $html;
+}
+
+?>
diff --git a/libraries/tbl_relation.lib.php b/libraries/tbl_relation.lib.php
new file mode 100644
index 0000000000..7c11281ce9
--- /dev/null
+++ b/libraries/tbl_relation.lib.php
@@ -0,0 +1,1036 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Functions for the table relation page
+ *
+ * @package PhpMyAdmin
+ */
+
+/**
+ * Generate dropdown choices
+ *
+ * @param string $dropdown_question Message to display
+ * @param string $select_name Name of the <select> field
+ * @param array $choices Choices for dropdown
+ * @param string $selected_value Selected value
+ *
+ * @return string The html code for existing value (for selected)
+ *
+ * @access public
+ */
+function PMA_generateDropdown(
+ $dropdown_question, $select_name, $choices, $selected_value
+) {
+ $html_output = htmlspecialchars($dropdown_question) . '&nbsp;&nbsp;'
+ . '<select name="' . htmlspecialchars($select_name) . '">' . "\n";
+
+ foreach ($choices as $one_value => $one_label) {
+ $html_output .= '<option value="' . htmlspecialchars($one_value) . '"';
+ if ($selected_value == $one_value) {
+ $html_output .= ' selected="selected" ';
+ }
+ $html_output .= '>' . htmlspecialchars($one_label) . '</option>' . "\n";
+ }
+ $html_output .= '</select>' . "\n";
+
+ return $html_output;
+}
+
+/**
+ * Split a string on backquote pairs
+ *
+ * @param string $text original string
+ *
+ * @return array containing the elements (and their surrounding backquotes)
+ *
+ * @access public
+ */
+function PMA_backquoteSplit($text)
+{
+ $elements = array();
+ $final_pos = strlen($text) - 1;
+ $pos = 0;
+ while ($pos <= $final_pos) {
+ $first_backquote = strpos($text, '`', $pos);
+ $second_backquote = strpos($text, '`', $first_backquote + 1);
+ // after the second one, there might be another one which means
+ // this is an escaped backquote
+ if ($second_backquote < $final_pos && '`' == $text[$second_backquote + 1]) {
+ $second_backquote = strpos($text, '`', $second_backquote + 2);
+ }
+ if (false === $first_backquote || false === $second_backquote) {
+ break;
+ }
+ $elements[] = substr(
+ $text, $first_backquote, $second_backquote - $first_backquote + 1
+ );
+ $pos = $second_backquote + 1;
+ }
+ return($elements);
+}
+
+/**
+ * Returns the DROP query for a foreign key constraint
+ *
+ * @param string $table table of the foreign key
+ * @param string $fk foreign key name
+ *
+ * @return string DROP query for the foreign key constraint
+ */
+function PMA_getSQLToDropForeignKey($table, $fk)
+{
+ return 'ALTER TABLE ' . PMA_Util::backquote($table)
+ . ' DROP FOREIGN KEY ' . PMA_Util::backquote($fk) . ';';
+}
+
+/**
+ * Returns the SQL query for foreign key constraint creation
+ *
+ * @param string $table table name
+ * @param string $field field name
+ * @param string $foreignDb foreign database name
+ * @param string $foreignTable foreign table name
+ * @param string $foreignField foreign field name
+ * @param string $name name of the constraint
+ * @param string $onDelete on delete action
+ * @param string $onUpdate on update action
+ *
+ * @return string SQL query for foreign key constraint creation
+ */
+function PMA_getSQLToCreateForeignKey($table, $field, $foreignDb, $foreignTable,
+ $foreignField, $name = null, $onDelete = null, $onUpdate = null
+) {
+ $sql_query = 'ALTER TABLE ' . PMA_Util::backquote($table) . ' ADD ';
+ // if user entered a constraint name
+ if (! empty($name)) {
+ $sql_query .= ' CONSTRAINT ' . PMA_Util::backquote($name);
+ }
+
+ $sql_query .= ' FOREIGN KEY (' . PMA_Util::backquote($field) . ')'
+ . ' REFERENCES ' . PMA_Util::backquote($foreignDb)
+ . '.' . PMA_Util::backquote($foreignTable)
+ . '(' . PMA_Util::backquote($foreignField) . ')';
+
+ if (! empty($onDelete)) {
+ $sql_query .= ' ON DELETE ' . $onDelete;
+ }
+ if (! empty($onUpdate)) {
+ $sql_query .= ' ON UPDATE ' . $onUpdate;
+ }
+ $sql_query .= ';';
+
+ return $sql_query;
+}
+
+/**
+ * Creates and populates dropdowns to select foreign db/table/column
+ *
+ * @param string $name name of the dropdowns
+ * @param array $values dropdown values
+ * @param string|boolean $foreign value of the item to be selected
+ * @param string $title title to show on hovering the dropdown
+ *
+ * @return string HTML for the dropdown
+ */
+function PMA_generateRelationalDropdown(
+ $name, $values = array(), $foreign = false, $title = ''
+) {
+ $html_output = '<select name="' . $name . '" title="' . $title . '">';
+ $html_output .= '<option value=""></option>';
+
+ $seen_key = false;
+ foreach ($values as $value) {
+ $html_output .= '<option value="' . htmlspecialchars($value) . '"';
+ if ($foreign && $value == $foreign) {
+ $html_output .= ' selected="selected"';
+ $seen_key = true;
+ }
+ $html_output .= '>' . htmlspecialchars($value) . '</option>';
+ }
+
+ if ($foreign && ! $seen_key) {
+ $html_output .= '<option value="' . htmlspecialchars($foreign) . '"'
+ . ' selected="selected">' . htmlspecialchars($foreign) . '</option>';
+ }
+ $html_output .= '</select>';
+ return $html_output;
+}
+
+/**
+ * Function to get html for the common form
+ *
+ * @param string $db current database
+ * @param string $table current table
+ * @param array $columns columns
+ * @param array $cfgRelation configuration relation
+ * @param string $tbl_storage_engine table storage engine
+ * @param array $existrel db, table, column
+ * @param array $existrel_foreign db, table, column
+ * @param array $options_array options array
+ *
+ * @return string
+ */
+function PMA_getHtmlForCommonForm($db, $table, $columns, $cfgRelation,
+ $tbl_storage_engine, $existrel, $existrel_foreign, $options_array
+) {
+ $html_output = PMA_getHtmlForCommonFormHeader($db, $table);
+
+ if (count($columns) > 0) {
+ $html_output .= PMA_getHtmlForCommonFormRows(
+ $columns, $cfgRelation, $tbl_storage_engine,
+ $existrel, $existrel_foreign, $options_array, $db, $table
+ );
+ } // end if (we have columns in this table)
+
+ $html_output .= PMA_getHtmlForCommonFormFooter();
+
+ return $html_output;
+}
+
+/**
+ * Function to get html for the common form rows
+ *
+ * @param array $columns columns
+ * @param array $cfgRelation configuration relation
+ * @param string $tbl_storage_engine table storage engine
+ * @param array $existrel existed relations
+ * @param array $existrel_foreign existed relations for foreign keys
+ * @param array $options_array options array
+ * @param string $db current database
+ * @param string $table current table
+ *
+ * @return string
+ */
+function PMA_getHtmlForCommonFormRows($columns, $cfgRelation, $tbl_storage_engine,
+ $existrel, $existrel_foreign, $options_array, $db, $table
+) {
+ foreach ($columns as $row) {
+ $save_row[] = $row;
+ }
+
+ $saved_row_cnt = count($save_row);
+
+ $html_output = '<fieldset>'
+ . '<legend>' . __('Relations'). '</legend>'
+ . '<table id="relationalTable">';
+
+ $html_output .= PMA_getHtmlForCommonFormTableHeaders(
+ $cfgRelation, $tbl_storage_engine
+ );
+
+ $odd_row = true;
+ for ($i = 0; $i < $saved_row_cnt; $i++) {
+ $html_output .= PMA_getHtmlForRow(
+ $save_row, $i, $odd_row, $cfgRelation, $existrel, $db,
+ $tbl_storage_engine, $existrel_foreign, $options_array
+ );
+ $odd_row = ! $odd_row;
+ } // end for
+
+ $html_output .= '</table>' . "\n"
+ . '</fieldset>' . "\n";
+
+ if ($cfgRelation['displaywork']) {
+ $html_output .= PMA_getHtmlForDisplayFieldInfos($db, $table, $save_row);
+ }
+
+ return $html_output;
+}
+
+/**
+ * Function to get html for an entire row in common form
+ *
+ * @param array $save_row save row
+ * @param int $i counter
+ * @param bool $odd_row whether odd row or not
+ * @param array $cfgRelation configuration relation
+ * @param array $existrel db, table, column
+ * @param string $db current db
+ * @param string $tbl_storage_engine table storage engine
+ * @param array $existrel_foreign db, table, column
+ * @param array $options_array options array
+ *
+ * @return string
+ */
+function PMA_getHtmlForRow($save_row, $i, $odd_row, $cfgRelation, $existrel, $db,
+ $tbl_storage_engine, $existrel_foreign, $options_array
+) {
+ $myfield = $save_row[$i]['Field'];
+ // Use an md5 as array index to avoid having special characters
+ // in the name attribute (see bug #1746964 )
+ $myfield_md5 = md5($myfield);
+ $myfield_html = htmlspecialchars($myfield);
+
+ $html_output = '<tr class="' . ($odd_row ? 'odd' : 'even') . '">'
+ . '<td class="center">'
+ . '<strong>' . $myfield_html . '</strong>'
+ . '<input type="hidden" name="fields_name[' . $myfield_md5 . ']"'
+ . ' value="' . $myfield_html . '"/>'
+ . '</td>';
+
+ if ($cfgRelation['relwork']) {
+ $html_output .= '<td>';
+
+ $foreign_db = false;
+ $foreign_table = false;
+ $foreign_column = false;
+
+ // database dropdown
+ if (isset($existrel[$myfield])) {
+ $foreign_db = $existrel[$myfield]['foreign_db'];
+ } else {
+ $foreign_db = $db;
+ }
+ $html_output .= PMA_generateRelationalDropdown(
+ 'destination_db[' . $myfield_md5 . ']',
+ $GLOBALS['pma']->databases,
+ $foreign_db,
+ __('Database')
+ );
+ // end of database dropdown
+
+ // table dropdown
+ $tables = array();
+ if ($foreign_db) {
+ if (isset($existrel[$myfield])) {
+ $foreign_table = $existrel[$myfield]['foreign_table'];
+ }
+ $tables_rs = $GLOBALS['dbi']->query(
+ 'SHOW TABLES FROM ' . PMA_Util::backquote($foreign_db),
+ null,
+ PMA_DatabaseInterface::QUERY_STORE
+ );
+ while ($row = $GLOBALS['dbi']->fetchRow($tables_rs)) {
+ $tables[] = $row[0];
+ }
+ }
+ $html_output .= PMA_generateRelationalDropdown(
+ 'destination_table[' . $myfield_md5 . ']',
+ $tables,
+ $foreign_table,
+ __('Table')
+ );
+ // end of table dropdown
+
+ // column dropdown
+ $columns = array();
+ if ($foreign_db && $foreign_table) {
+ if (isset($existrel[$myfield])) {
+ $foreign_column = $existrel[$myfield]['foreign_field'];
+ }
+ $table_obj = new PMA_Table($foreign_table, $foreign_db);
+ $columns = $table_obj->getUniqueColumns(false, false);
+ }
+ $html_output .= PMA_generateRelationalDropdown(
+ 'destination_column[' . $myfield_md5 . ']',
+ $columns,
+ $foreign_column,
+ __('Column')
+ );
+ // end of column dropdown
+
+ $html_output .= '</td>';
+ } // end if (internal relations)
+
+ if (PMA_Util::isForeignKeySupported($tbl_storage_engine)) {
+ $html_output .= PMA_getHtmlForForeignKey(
+ $save_row, $i, $existrel_foreign, $myfield, $db,
+ $myfield_md5, $tbl_storage_engine, $options_array
+ );
+ } // end if (InnoDB)
+ $html_output .= '</tr>';
+
+ return $html_output;
+}
+
+/**
+ * Function to get html for the common form header
+ *
+ * @param string $db current database
+ * @param string $table current table
+ *
+ * @return string
+ */
+function PMA_getHtmlForCommonFormHeader($db, $table)
+{
+ return '<form method="post" action="tbl_relation.php">' . "\n"
+ . PMA_URL_getHiddenInputs($db, $table);
+}
+
+/**
+ * Function to get html for the common form footer
+ *
+ * @return string
+ */
+function PMA_getHtmlForCommonFormFooter()
+{
+ return '<fieldset class="tblFooters">'
+ . '<input type="submit" value="' . __('Save') . '" />'
+ . '</fieldset>'
+ . '</form>';
+}
+
+/**
+ * Function to get html for display field infos
+ *
+ * @param string $db current database
+ * @param string $table current table
+ * @param array $save_row save row
+ *
+ * @return string
+ */
+function PMA_getHtmlForDisplayFieldInfos($db, $table, $save_row)
+{
+ $disp = PMA_getDisplayField($db, $table);
+ $html_output = '<fieldset>'
+ . '<label>' . __('Choose column to display:') . '</label>'
+ . '<select name="display_field">'
+ . '<option value="">---</option>';
+
+ foreach ($save_row as $row) {
+ $html_output .= '<option value="'
+ . htmlspecialchars($row['Field']) . '"';
+ if (isset($disp) && $row['Field'] == $disp) {
+ $html_output .= ' selected="selected"';
+ }
+ $html_output .= '>' . htmlspecialchars($row['Field'])
+ . '</option>'. "\n";
+ } // end while
+
+ $html_output .= '</select>'
+ . '</fieldset>';
+
+ return $html_output;
+}
+
+/**
+ * Function to get html for the common form title headers
+ *
+ * @param array $cfgRelation configuration relation
+ * @param string $tbl_storage_engine table storage engine
+ *
+ * @return string
+ */
+function PMA_getHtmlForCommonFormTableHeaders($cfgRelation, $tbl_storage_engine)
+{
+ $html_output = '<tr><th>' . __('Column') . '</th>';
+
+ if ($cfgRelation['relwork']) {
+ $html_output .= '<th>' . __('Internal relation');
+ if (PMA_Util::isForeignKeySupported($tbl_storage_engine)) {
+ $html_output .= PMA_Util::showHint(
+ __(
+ 'An internal relation is not necessary when a corresponding'
+ . ' FOREIGN KEY relation exists.'
+ )
+ );
+ }
+ $html_output .= '</th>';
+ }
+
+ if (PMA_Util::isForeignKeySupported($tbl_storage_engine)) {
+ // this does not have to be translated, it's part of the MySQL syntax
+ $html_output .= '<th colspan="2">' . __('Foreign key constraint')
+ . ' (' . $tbl_storage_engine . ')';
+ $html_output .= '</th>';
+ }
+ $html_output .= '</tr>';
+
+ return $html_output;
+}
+
+/**
+ * Function to get html for the foreign key
+ *
+ * @param array $save_row save row
+ * @param int $i counter
+ * @param array $existrel_foreign db, table, columns
+ * @param string $myfield my field
+ * @param string $db current database
+ * @param string $myfield_md5 my field md5
+ * @param string $tbl_storage_engine table storage engine
+ * @param array $options_array options array
+ *
+ * @return string
+ */
+function PMA_getHtmlForForeignKey($save_row, $i, $existrel_foreign, $myfield, $db,
+ $myfield_md5, $tbl_storage_engine, $options_array
+) {
+ $html_output = '<td>';
+ if (! empty($save_row[$i]['Key'])) {
+
+ $foreign_db = false;
+ $foreign_table = false;
+ $foreign_column = false;
+
+ // foreign database dropdown
+ if (isset($existrel_foreign[$myfield])) {
+ $foreign_db = $existrel_foreign[$myfield]['foreign_db'];
+ } else {
+ $foreign_db = $db;
+ }
+ $html_output .= '<span class="formelement clearfloat">';
+ $html_output .= PMA_generateRelationalDropdown(
+ 'destination_foreign_db[' . $myfield_md5 . ']',
+ $GLOBALS['pma']->databases,
+ $foreign_db,
+ __('Database')
+ );
+ // end of foreign database dropdown
+
+ // foreign table dropdown
+ $tables = array();
+ if ($foreign_db) {
+ if (isset($existrel_foreign[$myfield])) {
+ $foreign_table = $existrel_foreign[$myfield]['foreign_table'];
+ }
+ // In Drizzle, 'SHOW TABLE STATUS' will show status only for the tables
+ // which are currently in the table cache. Hence we have to use
+ // 'SHOW TABLES' and manully retrieve table engine values.
+ if (PMA_DRIZZLE) {
+ $tables_rs = $GLOBALS['dbi']->query(
+ 'SHOW TABLES FROM ' . PMA_Util::backquote($foreign_db),
+ null,
+ PMA_DatabaseInterface::QUERY_STORE
+ );
+ while ($row = $GLOBALS['dbi']->fetchArray($tables_rs)) {
+ $engine = PMA_Table::sGetStatusInfo(
+ $foreign_db,
+ $row[0],
+ 'Engine'
+ );
+ if (isset($engine)
+ && strtoupper($engine) == $tbl_storage_engine
+ ) {
+ $tables[] = $row[0];
+ }
+ }
+ } else {
+ $tables_rs = $GLOBALS['dbi']->query(
+ 'SHOW TABLE STATUS FROM ' . PMA_Util::backquote($foreign_db),
+ null,
+ PMA_DatabaseInterface::QUERY_STORE
+ );
+ while ($row = $GLOBALS['dbi']->fetchRow($tables_rs)) {
+ if (isset($row[1])
+ && strtoupper($row[1]) == $tbl_storage_engine
+ ) {
+ $tables[] = $row[0];
+ }
+ }
+ }
+ }
+ $html_output .= PMA_generateRelationalDropdown(
+ 'destination_foreign_table[' . $myfield_md5 . ']',
+ $tables,
+ $foreign_table,
+ __('Table')
+ );
+ // end of foreign table dropdown
+
+ // foreign column dropdown
+ $columns = array();
+ if ($foreign_db && $foreign_table) {
+ if (isset($existrel_foreign[$myfield])) {
+ $foreign_column = $existrel_foreign[$myfield]['foreign_field'];
+ }
+ $table_obj = new PMA_Table($foreign_table, $foreign_db);
+ $columns = $table_obj->getUniqueColumns(false, false);
+ }
+ $html_output .= PMA_generateRelationalDropdown(
+ 'destination_foreign_column[' . $myfield_md5 . ']',
+ $columns,
+ $foreign_column,
+ __('Column')
+ );
+ $html_output .= '</span>';
+ // end of foreign column dropdown
+
+ // For constraint name
+ $html_output .= '<span class="formelement clearfloat">';
+ $constraint_name = isset($existrel_foreign[$myfield]['constraint'])
+ ? $existrel_foreign[$myfield]['constraint'] : '';
+ $html_output .= __('Constraint name');
+ $html_output .= '<input type="text" name="constraint_name['
+ . $myfield_md5 . ']"'
+ . ' value="' . $constraint_name . '"/>';
+ $html_output .= '</span>' . "\n";
+
+ $html_output .= '<span class="formelement clearfloat">';
+ // For ON DELETE and ON UPDATE, the default action
+ // is RESTRICT as per MySQL doc; however, a SHOW CREATE TABLE
+ // won't display the clause if it's set as RESTRICT.
+ $on_delete = isset($existrel_foreign[$myfield]['on_delete'])
+ ? $existrel_foreign[$myfield]['on_delete'] : 'RESTRICT';
+ $html_output .= PMA_generateDropdown(
+ 'ON DELETE',
+ 'on_delete[' . $myfield_md5 . ']',
+ $options_array,
+ $on_delete
+ );
+ $html_output .= '</span>' . "\n";
+
+ $html_output .= '<span class="formelement clearfloat">' . "\n";
+ $on_update = isset($existrel_foreign[$myfield]['on_update'])
+ ? $existrel_foreign[$myfield]['on_update'] : 'RESTRICT';
+ $html_output .= PMA_generateDropdown(
+ 'ON UPDATE',
+ 'on_update[' . $myfield_md5 . ']',
+ $options_array,
+ $on_update
+ );
+ $html_output .= '</span>' . "\n";
+ } else {
+ $html_output .= __('No index defined! Create one below');
+ } // end if (a key exists)
+ $html_output .= '</td>';
+
+ return $html_output;
+}
+
+/**
+ * Function to send html for table or column dropdown list
+ *
+ * @return void
+ */
+function PMA_sendHtmlForTableOrColumnDropdownList()
+{
+ if (isset($_REQUEST['foreignTable'])) { // if both db and table are selected
+ PMA_sendHtmlForColumnDropdownList();
+ } else { // if only the db is selected
+ PMA_sendHtmlForTableDropdownList();
+ }
+ exit;
+}
+
+/**
+ * Function to send html for column dropdown list
+ *
+ * @return void
+ */
+function PMA_sendHtmlForColumnDropdownList()
+{
+ $response = PMA_Response::getInstance();
+
+ $foreignTable = $_REQUEST['foreignTable'];
+ $table_obj = new PMA_Table($foreignTable, $_REQUEST['foreignDb']);
+ $columns = array();
+ foreach ($table_obj->getUniqueColumns(false, false) as $column) {
+ $columns[] = htmlspecialchars($column);
+ }
+ $response->addJSON('columns', $columns);
+}
+
+/**
+ * Function to send html for table dropdown list
+ *
+ * @return void
+ */
+function PMA_sendHtmlForTableDropdownList()
+{
+ $response = PMA_Response::getInstance();
+ $tables = array();
+
+ $foreign = isset($_REQUEST['foreign']) && $_REQUEST['foreign'] === 'true';
+ if ($foreign) {
+ $tbl_storage_engine = strtoupper(
+ PMA_Table::sGetStatusInfo(
+ $_REQUEST['db'],
+ $_REQUEST['table'],
+ 'Engine'
+ )
+ );
+ }
+
+ // In Drizzle, 'SHOW TABLE STATUS' will show status only for the tables
+ // which are currently in the table cache. Hence we have to use 'SHOW TABLES'
+ // and manully retrieve table engine values.
+ if ($foreign && ! PMA_DRIZZLE) {
+ $query = 'SHOW TABLE STATUS FROM '
+ . PMA_Util::backquote($_REQUEST['foreignDb']);
+ $tables_rs = $GLOBALS['dbi']->query(
+ $query,
+ null,
+ PMA_DatabaseInterface::QUERY_STORE
+ );
+
+ while ($row = $GLOBALS['dbi']->fetchArray($tables_rs)) {
+ if (isset($row['Engine'])
+ && strtoupper($row['Engine']) == $tbl_storage_engine
+ ) {
+ $tables[] = htmlspecialchars($row['Name']);
+ }
+ }
+ } else {
+ $query = 'SHOW TABLES FROM '
+ . PMA_Util::backquote($_REQUEST['foreignDb']);
+ $tables_rs = $GLOBALS['dbi']->query(
+ $query,
+ null,
+ PMA_DatabaseInterface::QUERY_STORE
+ );
+ while ($row = $GLOBALS['dbi']->fetchArray($tables_rs)) {
+ if ($foreign && PMA_DRIZZLE) {
+ $engine = strtoupper(
+ PMA_Table::sGetStatusInfo(
+ $_REQUEST['foreignDb'],
+ $row[0],
+ 'Engine'
+ )
+ );
+ if (isset($engine) && $engine == $tbl_storage_engine) {
+ $tables[] = htmlspecialchars($row[0]);
+ }
+ } else {
+ $tables[] = htmlspecialchars($row[0]);
+ }
+ }
+ }
+ $response->addJSON('tables', $tables);
+}
+
+/**
+ * Function to handle update for display field
+ *
+ * @param string $disp field name
+ * @param string $display_field display field
+ * @param string $db current database
+ * @param string $table current table
+ * @param array $cfgRelation configuration relation
+ *
+ * @return void
+ */
+function PMA_handleUpdateForDisplayField($disp, $display_field, $db, $table,
+ $cfgRelation
+) {
+ $upd_query = PMA_getQueryForDisplayUpdate(
+ $disp, $display_field, $db, $table, $cfgRelation
+ );
+ if ($upd_query) {
+ PMA_queryAsControlUser($upd_query);
+ }
+}
+
+/**
+ * Function to get display query for handlingdisplay update
+ *
+ * @param string $disp field name
+ * @param string $display_field display field
+ * @param string $db current database
+ * @param string $table current table
+ * @param array $cfgRelation configuration relation
+ *
+ * @return string
+ */
+function PMA_getQueryForDisplayUpdate($disp, $display_field, $db, $table,
+ $cfgRelation
+) {
+ $upd_query = false;
+ if ($disp) {
+ if ($display_field != '') {
+ $upd_query = 'UPDATE '
+ . PMA_Util::backquote($GLOBALS['cfgRelation']['db'])
+ . '.' . PMA_Util::backquote($cfgRelation['table_info'])
+ . ' SET display_field = \''
+ . PMA_Util::sqlAddSlashes($display_field) . '\''
+ . ' WHERE db_name = \'' . PMA_Util::sqlAddSlashes($db) . '\''
+ . ' AND table_name = \'' . PMA_Util::sqlAddSlashes($table) . '\'';
+ } else {
+ $upd_query = 'DELETE FROM '
+ . PMA_Util::backquote($GLOBALS['cfgRelation']['db'])
+ . '.' . PMA_Util::backquote($cfgRelation['table_info'])
+ . ' WHERE db_name = \'' . PMA_Util::sqlAddSlashes($db) . '\''
+ . ' AND table_name = \'' . PMA_Util::sqlAddSlashes($table) . '\'';
+ }
+ } elseif ($display_field != '') {
+ $upd_query = 'INSERT INTO '
+ . PMA_Util::backquote($GLOBALS['cfgRelation']['db'])
+ . '.' . PMA_Util::backquote($cfgRelation['table_info'])
+ . '(db_name, table_name, display_field) VALUES('
+ . '\'' . PMA_Util::sqlAddSlashes($db) . '\','
+ . '\'' . PMA_Util::sqlAddSlashes($table) . '\','
+ . '\'' . PMA_Util::sqlAddSlashes($display_field) . '\')';
+ }
+
+ return $upd_query;
+}
+
+/**
+ * Function to handle updates for internal relations
+ *
+ * @param string $destination_db destination database
+ * @param string $multi_edit_columns_name multi edit column name
+ * @param string $destination_table destination table
+ * @param string $destination_column destination column
+ * @param array $cfgRelation configuration relation
+ * @param string $db current database
+ * @param string $table current table
+ * @param array $existrel db, table, column
+ *
+ * @return void
+ */
+function PMA_handleUpdatesForInternalRelations($destination_db,
+ $multi_edit_columns_name, $destination_table, $destination_column, $cfgRelation,
+ $db, $table, $existrel
+) {
+ foreach ($destination_db as $master_field_md5 => $foreign_db) {
+ $upd_query = PMA_getQueryForInternalRelationUpdate(
+ $multi_edit_columns_name,
+ $master_field_md5, $foreign_db, $destination_table, $destination_column,
+ $cfgRelation, $db, $table, isset($existrel) ? $existrel : null
+ );
+ if ($upd_query) {
+ PMA_queryAsControlUser($upd_query);
+ }
+ }
+}
+
+/**
+ * Function to get update query for updating internal relations
+ *
+ * @param string $multi_edit_columns_name multi edit column names
+ * @param string $master_field_md5 master field md5
+ * @param string $foreign_db foreign database
+ * @param string $destination_table destination table
+ * @param string $destination_column destination column
+ * @param array $cfgRelation configuration relation
+ * @param string $db current database
+ * @param string $table current table
+ * @param array $existrel db, table, column
+ *
+ * @return string
+ */
+function PMA_getQueryForInternalRelationUpdate($multi_edit_columns_name,
+ $master_field_md5, $foreign_db, $destination_table, $destination_column,
+ $cfgRelation, $db, $table, $existrel
+) {
+ $upd_query = false;
+
+ // Map the fieldname's md5 back to its real name
+ $master_field = $multi_edit_columns_name[$master_field_md5];
+
+ $foreign_table = $destination_table[$master_field_md5];
+ $foreign_field = $destination_column[$master_field_md5];
+ if (! empty($foreign_db)
+ && ! empty($foreign_table)
+ && ! empty($foreign_field)
+ ) {
+ if (! isset($existrel[$master_field])) {
+ $upd_query = 'INSERT INTO '
+ . PMA_Util::backquote($GLOBALS['cfgRelation']['db'])
+ . '.' . PMA_Util::backquote($cfgRelation['relation'])
+ . '(master_db, master_table, master_field, foreign_db,'
+ . ' foreign_table, foreign_field)'
+ . ' values('
+ . '\'' . PMA_Util::sqlAddSlashes($db) . '\', '
+ . '\'' . PMA_Util::sqlAddSlashes($table) . '\', '
+ . '\'' . PMA_Util::sqlAddSlashes($master_field) . '\', '
+ . '\'' . PMA_Util::sqlAddSlashes($foreign_db) . '\', '
+ . '\'' . PMA_Util::sqlAddSlashes($foreign_table) . '\','
+ . '\'' . PMA_Util::sqlAddSlashes($foreign_field) . '\')';
+
+ } elseif ($existrel[$master_field]['foreign_db'] != $foreign_db
+ || $existrel[$master_field]['foreign_table'] != $foreign_table
+ || $existrel[$master_field]['foreign_field'] != $foreign_field
+ ) {
+ $upd_query = 'UPDATE '
+ . PMA_Util::backquote($GLOBALS['cfgRelation']['db'])
+ . '.' . PMA_Util::backquote($cfgRelation['relation']) . ' SET'
+ . ' foreign_db = \''
+ . PMA_Util::sqlAddSlashes($foreign_db) . '\', '
+ . ' foreign_table = \''
+ . PMA_Util::sqlAddSlashes($foreign_table) . '\', '
+ . ' foreign_field = \''
+ . PMA_Util::sqlAddSlashes($foreign_field) . '\' '
+ . ' WHERE master_db = \''
+ . PMA_Util::sqlAddSlashes($db) . '\''
+ . ' AND master_table = \''
+ . PMA_Util::sqlAddSlashes($table) . '\''
+ . ' AND master_field = \''
+ . PMA_Util::sqlAddSlashes($master_field) . '\'';
+ } // end if... else....
+ } elseif (isset($existrel[$master_field])) {
+ $upd_query = 'DELETE FROM '
+ . PMA_Util::backquote($GLOBALS['cfgRelation']['db'])
+ . '.' . PMA_Util::backquote($cfgRelation['relation'])
+ . ' WHERE master_db = \'' . PMA_Util::sqlAddSlashes($db) . '\''
+ . ' AND master_table = \'' . PMA_Util::sqlAddSlashes($table) . '\''
+ . ' AND master_field = \'' . PMA_Util::sqlAddSlashes($master_field)
+ . '\'';
+ } // end if... else....
+
+ return $upd_query;
+}
+
+/**
+ * Function to handle foreign key updates
+ *
+ * @param string $destination_foreign_db destination foreign database
+ * @param string $multi_edit_columns_name multi edit column names
+ * @param string $destination_foreign_table destination foreign table
+ * @param string $destination_foreign_column destination foreign column
+ * @param array $options_array options array
+ * @param string $table current table
+ * @param array $existrel_foreign db, table, column
+ *
+ * @return string
+ */
+function PMA_handleUpdatesForForeignKeys($destination_foreign_db,
+ $multi_edit_columns_name, $destination_foreign_table,
+ $destination_foreign_column, $options_array, $table, $existrel_foreign
+) {
+ $html_output = '';
+ $display_query = '';
+ $seen_error = false;
+ foreach ($destination_foreign_db as $master_field_md5 => $foreign_db) {
+ $html_output .= PMA_handleUpdateForForeignKey(
+ $multi_edit_columns_name, $master_field_md5,
+ $destination_foreign_table, $destination_foreign_column, $options_array,
+ $existrel_foreign, $table, $seen_error, $display_query, $foreign_db
+ );
+ } // end foreach
+ if (! empty($display_query) && ! $seen_error) {
+ $GLOBALS['display_query'] = $display_query;
+ $html_output = PMA_Util::getMessage(
+ __('Your SQL query has been executed successfully'),
+ null, 'success'
+ );
+ }
+
+ return $html_output;
+}
+
+/**
+ * Function to handle update for a foreign key
+ *
+ * @param array $multi_edit_columns_name multu edit columns name
+ * @param string $master_field_md5 master field md5
+ * @param string $destination_foreign_table destination foreign table
+ * @param string $destination_foreign_column destination foreign column
+ * @param array $options_array options array
+ * @param array $existrel_foreign db, table, column
+ * @param string $table current table
+ * @param bool &$seen_error whether seen error
+ * @param string &$display_query display query
+ * @param string $foreign_db foreign database
+ *
+ * @return string
+ */
+function PMA_handleUpdateForForeignKey($multi_edit_columns_name, $master_field_md5,
+ $destination_foreign_table, $destination_foreign_column, $options_array,
+ $existrel_foreign, $table, &$seen_error, &$display_query, $foreign_db
+) {
+ $html_output = '';
+ $create = false;
+ $drop = false;
+
+ // Map the fieldname's md5 back to its real name
+ $master_field = $multi_edit_columns_name[$master_field_md5];
+
+ $foreign_table = $destination_foreign_table[$master_field_md5];
+ $foreign_field = $destination_foreign_column[$master_field_md5];
+ if (! empty($foreign_db)
+ && ! empty($foreign_table)
+ && ! empty($foreign_field)
+ ) {
+ if ( isset($existrel_foreign[$master_field])) {
+ $constraint_name = $existrel_foreign[$master_field]['constraint'];
+ $on_delete = ! empty(
+ $existrel_foreign[$master_field]['on_delete'])
+ ? $existrel_foreign[$master_field]['on_delete'] : 'RESTRICT';
+ $on_update = ! empty(
+ $existrel_foreign[$master_field]['on_update'])
+ ? $existrel_foreign[$master_field]['on_update'] : 'RESTRICT';
+ }
+ if (! isset($existrel_foreign[$master_field])) {
+ // no key defined for this field
+ $create = true;
+ } elseif ($existrel_foreign[$master_field]['foreign_db'] != $foreign_db
+ || $existrel_foreign[$master_field]['foreign_table'] != $foreign_table
+ || $existrel_foreign[$master_field]['foreign_field'] != $foreign_field
+ || $_REQUEST['constraint_name'][$master_field_md5] != $constraint_name
+ || ($_REQUEST['on_delete'][$master_field_md5] != $on_delete)
+ || ($_REQUEST['on_update'][$master_field_md5] != $on_update)
+ ) {
+ // another foreign key is already defined for this field
+ // or an option has been changed for ON DELETE or ON UPDATE
+ $drop = true;
+ $create = true;
+ } // end if... else....
+ } elseif (isset($existrel_foreign[$master_field])) {
+ $drop = true;
+ } // end if... else....
+
+ $tmp_error_drop = false;
+ if ($drop) {
+ $drop_query = PMA_getSQLToDropForeignKey(
+ $table, $existrel_foreign[$master_field]['constraint']
+ );
+ $display_query .= $drop_query . "\n";
+ $GLOBALS['dbi']->tryQuery($drop_query);
+ $tmp_error_drop = $GLOBALS['dbi']->getError();
+
+ if (! empty($tmp_error_drop)) {
+ $seen_error = true;
+ $html_output .= PMA_Util::mysqlDie(
+ $tmp_error_drop, $drop_query, false, '', false
+ );
+ return $html_output;
+ }
+ }
+ $tmp_error_create = false;
+ if ($create) {
+ $create_query = PMA_getSQLToCreateForeignKey(
+ $table, $master_field, $foreign_db, $foreign_table, $foreign_field,
+ $_REQUEST['constraint_name'][$master_field_md5],
+ $options_array[$_REQUEST['on_delete'][$master_field_md5]],
+ $options_array[$_REQUEST['on_update'][$master_field_md5]]
+ );
+
+ $display_query .= $create_query . "\n";
+ $GLOBALS['dbi']->tryQuery($create_query);
+ $tmp_error_create = $GLOBALS['dbi']->getError();
+ if (! empty($tmp_error_create)) {
+ $seen_error = true;
+
+ if (substr($tmp_error_create, 1, 4) == '1005') {
+ $message = PMA_Message::error(
+ __('Error creating foreign key on %1$s (check data types)')
+ );
+ $message->addParam($master_field);
+ $html_output .= $message->getDisplay();
+ } else {
+ $html_output .= PMA_Util::mysqlDie(
+ $tmp_error_create, $create_query, false, '', false
+ );
+ }
+ $html_output .= PMA_Util::showMySQLDocu(
+ 'InnoDB_foreign_key_constraints'
+ ) . "\n";
+ }
+
+ // this is an alteration and the old constraint has been dropped
+ // without creation of a new one
+ if ($drop && $create && empty($tmp_error_drop)
+ && ! empty($tmp_error_create)
+ ) {
+ // a rollback may be better here
+ $sql_query_recreate = '# Restoring the dropped constraint...' . "\n";
+ $sql_query_recreate .= PMA_getSQLToCreateForeignKey(
+ $table,
+ $master_field,
+ $existrel_foreign[$master_field]['foreign_db'],
+ $existrel_foreign[$master_field]['foreign_table'],
+ $existrel_foreign[$master_field]['foreign_field'],
+ $existrel_foreign[$master_field]['constraint'],
+ $options_array[$existrel_foreign[$master_field]['on_delete']],
+ $options_array[$existrel_foreign[$master_field]['on_update']]
+ );
+ $display_query .= $sql_query_recreate . "\n";
+ $GLOBALS['dbi']->tryQuery($sql_query_recreate);
+ }
+ }
+
+ return $html_output;
+}
+?>
diff --git a/libraries/tbl_tracking.lib.php b/libraries/tbl_tracking.lib.php
new file mode 100644
index 0000000000..5cfa56ab21
--- /dev/null
+++ b/libraries/tbl_tracking.lib.php
@@ -0,0 +1,1210 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Functions used to generate table tracking
+ *
+ * @package PhpMyAdmin
+ */
+
+/**
+ * Filters tracking entries
+ *
+ * @param array $data the entries to filter
+ * @param string $filter_ts_from "from" date
+ * @param string $filter_ts_to "to" date
+ * @param string $filter_users users
+ *
+ * @return array filtered entries
+ */
+function PMA_filterTracking(
+ $data, $filter_ts_from, $filter_ts_to, $filter_users
+) {
+ $tmp_entries = array();
+ $id = 0;
+ foreach ($data as $entry) {
+ $timestamp = strtotime($entry['date']);
+ $filtered_user = in_array($entry['username'], $filter_users);
+ if ($timestamp >= $filter_ts_from
+ && $timestamp <= $filter_ts_to
+ && (in_array('*', $filter_users) || $filtered_user)
+ ) {
+ $tmp_entries[] = array(
+ 'id' => $id,
+ 'timestamp' => $timestamp,
+ 'username' => $entry['username'],
+ 'statement' => $entry['statement']
+ );
+ }
+ $id++;
+ }
+ return($tmp_entries);
+}
+
+/**
+ * Function to get html for data definition and data manipulation statements
+ *
+ * @param string $url_query url query
+ * @param int $last_version last version
+ *
+ * @return string
+ */
+function PMA_getHtmlForDataDefinitionAndManipulationStatements($url_query,
+ $last_version
+) {
+ $html = '<div id="div_create_version">';
+ $html .= '<form method="post" action="tbl_tracking.php?' . $url_query . '">';
+ $html .= PMA_URL_getHiddenInputs($GLOBALS['db'], $GLOBALS['table']);
+ $html .= '<fieldset>';
+ $html .= '<legend>';
+ $html .= sprintf(
+ __('Create version %1$s of %2$s'),
+ ($last_version + 1),
+ htmlspecialchars($GLOBALS['db'] . '.' . $GLOBALS['table'])
+ );
+ $html .= '</legend>';
+ $html .= '<input type="hidden" name="version" value="' . ($last_version + 1)
+ . '" />';
+ $html .= '<p>' . __('Track these data definition statements:')
+ . '</p>';
+ $html .= '<input type="checkbox" name="alter_table" value="true"'
+ . ' checked="checked" /> ALTER TABLE<br/>';
+ $html .= '<input type="checkbox" name="rename_table" value="true"'
+ . ' checked="checked" /> RENAME TABLE<br/>';
+ $html .= '<input type="checkbox" name="create_table" value="true"'
+ . ' checked="checked" /> CREATE TABLE<br/>';
+ $html .= '<input type="checkbox" name="drop_table" value="true"'
+ . ' checked="checked" /> DROP TABLE<br/>';
+ $html .= '<br/>';
+ $html .= '<input type="checkbox" name="create_index" value="true"'
+ . ' checked="checked" /> CREATE INDEX<br/>';
+ $html .= '<input type="checkbox" name="drop_index" value="true"'
+ . ' checked="checked" /> DROP INDEX<br/>';
+ $html .= '<p>' . __('Track these data manipulation statements:') . '</p>';
+ $html .= '<input type="checkbox" name="insert" value="true"'
+ . ' checked="checked" /> INSERT<br/>';
+ $html .= '<input type="checkbox" name="update" value="true"'
+ . ' checked="checked" /> UPDATE<br/>';
+ $html .= '<input type="checkbox" name="delete" value="true"'
+ . ' checked="checked" /> DELETE<br/>';
+ $html .= '<input type="checkbox" name="truncate" value="true"'
+ . ' checked="checked" /> TRUNCATE<br/>';
+ $html .= '</fieldset>';
+
+ $html .= '<fieldset class="tblFooters">';
+ $html .= '<input type="hidden" name="submit_create_version" value="1" />';
+ $html .= '<input type="submit" value="' . __('Create version') . '" />';
+ $html .= '</fieldset>';
+
+ $html .= '</form>';
+ $html .= '</div>';
+
+ return $html;
+}
+
+/**
+ * Function to get html for activate tracking
+ *
+ * @param string $url_query url query
+ * @param int $last_version last version
+ *
+ * @return string
+ */
+function PMA_getHtmlForActivateTracking($url_query, $last_version)
+{
+ $html = '<div id="div_activate_tracking">';
+ $html .= '<form method="post" action="tbl_tracking.php?' . $url_query . '">';
+ $html .= '<fieldset>';
+ $html .= '<legend>';
+ $html .= sprintf(
+ __('Activate tracking for %s'),
+ htmlspecialchars($GLOBALS['db'] . '.' . $GLOBALS['table'])
+ );
+ $html .= '</legend>';
+ $html .= '<input type="hidden" name="version" value="' . $last_version . '" />';
+ $html .= '<input type="hidden" name="submit_activate_now" value="1" />';
+ $html .= '<input type="submit" value="' . __('Activate now') . '" />';
+ $html .= '</fieldset>';
+ $html .= '</form>';
+ $html .= '</div>';
+
+ return $html;
+}
+
+/**
+ * Function to get html for deactivating tracking
+ *
+ * @param string $url_query url query
+ * @param int $last_version last version
+ *
+ * @return string
+ */
+function PMA_getHtmlForDeactivateTracking($url_query, $last_version)
+{
+ $html = '<div id="div_deactivate_tracking">';
+ $html .= '<form method="post" action="tbl_tracking.php?' . $url_query . '">';
+ $html .= '<fieldset>';
+ $html .= '<legend>';
+ $html .= sprintf(
+ __('Deactivate tracking for %s'),
+ htmlspecialchars($GLOBALS['db'] . '.' . $GLOBALS['table'])
+ );
+ $html .= '</legend>';
+ $html .= '<input type="hidden" name="version" value="' . $last_version . '" />';
+ $html .= '<input type="hidden" name="submit_deactivate_now" value="1" />';
+ $html .= '<input type="submit" value="' . __('Deactivate now') . '" />';
+ $html .= '</fieldset>';
+ $html .= '</form>';
+ $html .= '</div>';
+
+ return $html;
+}
+
+/**
+ * Function to get the list versions of the table
+ *
+ * @return array
+ */
+function PMA_getListOfVersionsOfTable()
+{
+ $sql_query = " SELECT * FROM " .
+ PMA_Util::backquote($GLOBALS['cfg']['Server']['pmadb']) . "." .
+ PMA_Util::backquote($GLOBALS['cfg']['Server']['tracking']) .
+ " WHERE db_name = '" . PMA_Util::sqlAddSlashes($_REQUEST['db']) . "' ".
+ " AND table_name = '" . PMA_Util::sqlAddSlashes($_REQUEST['table']) ."' ".
+ " ORDER BY version DESC ";
+
+ return PMA_queryAsControlUser($sql_query);
+}
+
+/**
+ * Function to get html for displaying last version number
+ *
+ * @param array $sql_result sql result
+ * @param int $last_version last version
+ * @param array $url_params url parameters
+ * @param string $url_query url query
+ *
+ * @return string
+ */
+function PMA_getHtmlForTableVersionDetails($sql_result, $last_version, $url_params,
+ $url_query
+) {
+ $html = '<table id="versions" class="data">';
+ $html .= '<thead>';
+ $html .= '<tr>';
+ $html .= '<th>' . __('Database') . '</th>';
+ $html .= '<th>' . __('Table') . '</th>';
+ $html .= '<th>' . __('Version') . '</th>';
+ $html .= '<th>' . __('Created') . '</th>';
+ $html .= '<th>' . __('Updated') . '</th>';
+ $html .= '<th>' . __('Status') . '</th>';
+ $html .= '<th>' . __('Show') . '</th>';
+ $html .= '</tr>';
+ $html .= '</thead>';
+ $html .= '<tbody>';
+
+ $style = 'odd';
+ $GLOBALS['dbi']->dataSeek($sql_result, 0);
+ while ($version = $GLOBALS['dbi']->fetchArray($sql_result)) {
+ if ($version['tracking_active'] == 1) {
+ $version_status = __('active');
+ } else {
+ $version_status = __('not active');
+ }
+ if ($version['version'] == $last_version) {
+ if ($version['tracking_active'] == 1) {
+ $tracking_active = true;
+ } else {
+ $tracking_active = false;
+ }
+ }
+ $html .= '<tr class="noclick ' . $style . '">';
+ $html .= '<td>' . htmlspecialchars($version['db_name']) . '</td>';
+ $html .= '<td>' . htmlspecialchars($version['table_name']) . '</td>';
+ $html .= '<td>' . htmlspecialchars($version['version']) . '</td>';
+ $html .= '<td>' . htmlspecialchars($version['date_created']) . '</td>';
+ $html .= '<td>' . htmlspecialchars($version['date_updated']) . '</td>';
+ $html .= '<td>' . $version_status . '</td>';
+ $html .= '<td><a href="tbl_tracking.php';
+ $html .= PMA_URL_getCommon(
+ $url_params + array(
+ 'report' => 'true', 'version' => $version['version']
+ )
+ );
+ $html .= '">' . __('Tracking report') . '</a>';
+ $html .= '| <a href="tbl_tracking.php';
+ $html .= PMA_URL_getCommon(
+ $url_params + array(
+ 'snapshot' => 'true', 'version' => $version['version']
+ )
+ );
+ $html .= '">' . __('Structure snapshot') . '</a>';
+ $html .= '</td>';
+ $html .= '</tr>';
+
+ if ($style == 'even') {
+ $style = 'odd';
+ } else {
+ $style = 'even';
+ }
+ }
+
+ $html .= '</tbody>';
+ $html .= '</table>';
+
+ if ($tracking_active) {
+ $html .= PMA_getHtmlForDeactivateTracking($url_query, $last_version);
+ } else {
+ $html .= PMA_getHtmlForActivateTracking($url_query, $last_version);
+ }
+
+ return $html;
+}
+
+/**
+ * Function to get the last version number of a table
+ *
+ * @param array $sql_result sql result
+ *
+ * @return int
+ */
+function PMA_getTableLastVersionNumber($sql_result)
+{
+ $maxversion = $GLOBALS['dbi']->fetchArray($sql_result);
+ $last_version = $maxversion['version'];
+
+ return $last_version;
+}
+
+/**
+ * Function to get sql results for selectable tables
+ *
+ * @return array
+ */
+function PMA_getSQLResultForSelectableTables()
+{
+ include_once 'libraries/relation.lib.php';
+
+ $sql_query = " SELECT DISTINCT db_name, table_name FROM " .
+ PMA_Util::backquote($GLOBALS['cfg']['Server']['pmadb']) . "." .
+ PMA_Util::backquote($GLOBALS['cfg']['Server']['tracking']) .
+ " WHERE db_name = '" . PMA_Util::sqlAddSlashes($GLOBALS['db']) . "' " .
+ " ORDER BY db_name, table_name";
+
+ return PMA_queryAsControlUser($sql_query);
+}
+
+/**
+ * Function to get html for selectable table rows
+ *
+ * @param array $selectable_tables_sql_result sql results for selectable rows
+ * @param string $url_query url query
+ *
+ * @return string
+ */
+function PMA_getHtmlForSelectableTables($selectable_tables_sql_result, $url_query)
+{
+ $html = '<form method="post" action="tbl_tracking.php?' . $url_query . '">';
+ $html .= '<select name="table">';
+ while ($entries = $GLOBALS['dbi']->fetchArray($selectable_tables_sql_result)) {
+ if (PMA_Tracker::isTracked($entries['db_name'], $entries['table_name'])) {
+ $status = ' (' . __('active') . ')';
+ } else {
+ $status = ' (' . __('not active') . ')';
+ }
+ if ($entries['table_name'] == $_REQUEST['table']) {
+ $s = ' selected="selected"';
+ } else {
+ $s = '';
+ }
+ $html .= '<option value="' . htmlspecialchars($entries['table_name'])
+ . '"' . $s . '>' . htmlspecialchars($entries['db_name']) . ' . '
+ . htmlspecialchars($entries['table_name']) . $status . '</option>'
+ . "\n";
+ }
+ $html .= '</select>';
+ $html .= '<input type="hidden" name="show_versions_submit" value="1" />';
+ $html .= '<input type="submit" value="' . __('Show versions') . '" />';
+ $html .= '</form>';
+
+ return $html;
+}
+
+/**
+ * Function to get html for tracking report and tracking report export
+ *
+ * @param string $url_query url query
+ * @param array $data data
+ * @param array $url_params url params
+ * @param boolean $selection_schema selection schema
+ * @param boolean $selection_data selection data
+ * @param boolean $selection_both selection both
+ * @param int $filter_ts_to filter time stamp from
+ * @param int $filter_ts_from filter time stamp tp
+ * @param array $filter_users filter users
+ *
+ * @return string
+ */
+function PMA_getHtmlForTrackingReport($url_query, $data, $url_params,
+ $selection_schema, $selection_data, $selection_both, $filter_ts_to,
+ $filter_ts_from, $filter_users
+) {
+ $html = '<h3>' . __('Tracking report')
+ . ' [<a href="tbl_tracking.php?' . $url_query . '">' . __('Close')
+ . '</a>]</h3>';
+
+ $html .= '<small>' . __('Tracking statements') . ' '
+ . htmlspecialchars($data['tracking']) . '</small><br/>';
+ $html .= '<br/>';
+
+ $html .= '<form method="post" action="tbl_tracking.php'
+ . PMA_URL_getCommon(
+ $url_params + array(
+ 'report' => 'true', 'version' => $_REQUEST['version']
+ )
+ )
+ . '">';
+
+ $str1 = '<select name="logtype">'
+ . '<option value="schema"'
+ . ($selection_schema ? ' selected="selected"' : '') . '>'
+ . __('Structure only') . '</option>'
+ . '<option value="data"'
+ . ($selection_data ? ' selected="selected"' : ''). '>'
+ . __('Data only') . '</option>'
+ . '<option value="schema_and_data"'
+ . ($selection_both ? ' selected="selected"' : '') . '>'
+ . __('Structure and data') . '</option>'
+ . '</select>';
+ $str2 = '<input type="text" name="date_from" value="'
+ . htmlspecialchars($_REQUEST['date_from']) . '" size="19" />';
+ $str3 = '<input type="text" name="date_to" value="'
+ . htmlspecialchars($_REQUEST['date_to']) . '" size="19" />';
+ $str4 = '<input type="text" name="users" value="'
+ . htmlspecialchars($_REQUEST['users']) . '" />';
+ $str5 = '<input type="hidden" name="list_report" value="1" />'
+ . '<input type="submit" value="' . __('Go') . '" />';
+
+ $html .= sprintf(
+ __('Show %1$s with dates from %2$s to %3$s by user %4$s %5$s'),
+ $str1, $str2, $str3, $str4, $str5
+ );
+
+ // Prepare delete link content here
+ $drop_image_or_text = '';
+ if (PMA_Util::showIcons('ActionsLinksMode')) {
+ $drop_image_or_text .= PMA_Util::getImage(
+ 'b_drop.png', __('Delete tracking data row from report')
+ );
+ }
+ if (PMA_Util::showText('ActionLinksMode')) {
+ $drop_image_or_text .= __('Delete');
+ }
+
+ /*
+ * First, list tracked data definition statements
+ */
+ if (count($data['ddlog']) == 0 && count($data['dmlog']) == 0) {
+ $msg = PMA_Message::notice(__('No data'));
+ $msg->display();
+ }
+
+ if ($selection_schema || $selection_both && count($data['ddlog']) > 0) {
+ list($temp, $ddlog_count) = PMA_getHtmlForDataDefinitionStatements(
+ $data, $filter_users, $filter_ts_from, $filter_ts_to, $url_params,
+ $drop_image_or_text
+ );
+ $html .= $temp;
+ unset($temp);
+ } //endif
+
+ /*
+ * Secondly, list tracked data manipulation statements
+ */
+ if (($selection_data || $selection_both) && count($data['dmlog']) > 0) {
+ $html .= PMA_getHtmlForDataManipulationStatements(
+ $data, $filter_users, $filter_ts_from, $filter_ts_to, $url_params,
+ $ddlog_count, $drop_image_or_text
+ );
+ }
+ $html .= '</form>';
+ $html .= '<form method="post" action="tbl_tracking.php'
+ . PMA_URL_getCommon(
+ $url_params + array(
+ 'report' => 'true', 'version' => $_REQUEST['version']
+ )
+ )
+ . '">';
+ $html .= sprintf(
+ __('Show %1$s with dates from %2$s to %3$s by user %4$s %5$s'),
+ $str1, $str2, $str3, $str4, $str5
+ );
+
+ $str_export1 = '<select name="export_type">'
+ . '<option value="sqldumpfile">' . __('SQL dump (file download)')
+ . '</option>'
+ . '<option value="sqldump">' . __('SQL dump') . '</option>'
+ . '<option value="execution" onclick="alert(\''
+ . PMA_escapeJsString(
+ __('This option will replace your table and contained data.')
+ )
+ .'\')">' . __('SQL execution') . '</option>' . '</select>';
+
+ $str_export2 = '<input type="hidden" name="report_export" value="1" />'
+ . '<input type="submit" value="' . __('Go') .'" />';
+ $html .= '</form>';
+ $html .= '<form class="disableAjax" method="post" action="tbl_tracking.php'
+ . PMA_URL_getCommon(
+ $url_params
+ + array('report' => 'true', 'version' => $_REQUEST['version'])
+ )
+ . '">';
+ $html .= '<input type="hidden" name="logtype" value="'
+ . htmlspecialchars($_REQUEST['logtype']) . '" />';
+ $html .= '<input type="hidden" name="date_from" value="'
+ . htmlspecialchars($_REQUEST['date_from']) . '" />';
+ $html .= '<input type="hidden" name="date_to" value="'
+ . htmlspecialchars($_REQUEST['date_to']) . '" />';
+ $html .= '<input type="hidden" name="users" value="'
+ . htmlspecialchars($_REQUEST['users']) . '" />';
+ $html .= "<br/>" . sprintf(__('Export as %s'), $str_export1)
+ . $str_export2 . "<br/>";
+ $html .= '</form>';
+ $html .= "<br/><br/><hr/><br/>\n";
+
+ return $html;
+}
+/**
+ * Function to get html for data manipulation statements
+ *
+ * @param array $data data
+ * @param array $filter_users filter users
+ * @param int $filter_ts_from filter time staml from
+ * @param int $filter_ts_to filter time stamp to
+ * @param array $url_params url parameters
+ * @param int $ddlog_count data definition log count
+ * @param string $drop_image_or_text drop image or text
+ *
+ * @return string
+ */
+function PMA_getHtmlForDataManipulationStatements($data, $filter_users,
+ $filter_ts_from, $filter_ts_to, $url_params, $ddlog_count,
+ $drop_image_or_text
+) {
+ $i = $ddlog_count;
+ $html = '<table id="dml_versions" class="data" width="100%">';
+ $html .= '<thead>';
+ $html .= '<tr>';
+ $html .= '<th width="18">#</th>';
+ $html .= '<th width="100">' . __('Date') . '</th>';
+ $html .= '<th width="60">' . __('Username') . '</th>';
+ $html .= '<th>' . __('Data manipulation statement') . '</th>';
+ $html .= '<th>' . __('Delete') . '</th>';
+ $html .= '</tr>';
+ $html .= '</thead>';
+ $html .= '<tbody>';
+
+ $style = 'odd';
+ foreach ($data['dmlog'] as $entry) {
+ $html .= PMA_getHtmlForDataManipulationStatement(
+ $entry, $filter_users, $filter_ts_from, $filter_ts_to, $style, $i,
+ $url_params, $ddlog_count, $drop_image_or_text
+ );
+ if ($style == 'even') {
+ $style = 'odd';
+ } else {
+ $style = 'even';
+ }
+ $i++;
+ }
+ $html .= '</tbody>';
+ $html .= '</table>';
+
+ return $html;
+}
+
+/**
+ * Function to get html for one data manipulation statement
+ *
+ * @param array $entry entry
+ * @param array $filter_users filter users
+ * @param int $filter_ts_from filter time stamp from
+ * @param int $filter_ts_to filter time stamp to
+ * @param string $style style
+ * @param int $i field number
+ * @param array $url_params url parameters
+ * @param int $ddlog_count data definition log count
+ * @param string $drop_image_or_text drop image or text
+ *
+ * @return string
+ */
+function PMA_getHtmlForDataManipulationStatement($entry, $filter_users,
+ $filter_ts_from, $filter_ts_to, $style, $i, $url_params, $ddlog_count,
+ $drop_image_or_text
+) {
+ $statement = PMA_Util::formatSql($entry['statement'], true);
+ $timestamp = strtotime($entry['date']);
+ $filtered_user = in_array($entry['username'], $filter_users);
+ $html = null;
+
+ if ($timestamp >= $filter_ts_from
+ && $timestamp <= $filter_ts_to
+ && (in_array('*', $filter_users) || $filtered_user)
+ ) {
+ $html = '<tr class="noclick ' . $style . '">';
+ $html .= '<td><small>' . $i . '</small></td>';
+ $html .= '<td><small>'
+ . htmlspecialchars($entry['date']) . '</small></td>';
+ $html .= '<td><small>'
+ . htmlspecialchars($entry['username']) . '</small></td>';
+ $html .= '<td>' . $statement . '</td>';
+ $html .= '<td class="nowrap"><a href="tbl_tracking.php?'
+ . PMA_URL_getCommon(
+ $url_params + array(
+ 'report' => 'true',
+ 'version' => $_REQUEST['version'],
+ 'delete_dmlog' => ($i - $ddlog_count),
+ )
+ )
+ . '">'
+ . $drop_image_or_text
+ . '</a></td>';
+ $html .= '</tr>';
+ }
+
+ return $html;
+}
+/**
+ * Function to get html for data definition statements in schema snapshot
+ *
+ * @param array $data data
+ * @param array $filter_users filter users
+ * @param int $filter_ts_from filter time stamp from
+ * @param int $filter_ts_to filter time stamp to
+ * @param array $url_params url parameters
+ * @param string $drop_image_or_text drop image or text
+ *
+ * @return string
+ */
+function PMA_getHtmlForDataDefinitionStatements($data, $filter_users,
+ $filter_ts_from, $filter_ts_to, $url_params, $drop_image_or_text
+) {
+ $i = 1;
+ $html = '<table id="ddl_versions" class="data" width="100%">';
+ $html .= '<thead>';
+ $html .= '<tr>';
+ $html .= '<th width="18">#</th>';
+ $html .= '<th width="100">' . __('Date') . '</th>';
+ $html .= '<th width="60">' . __('Username') . '</th>';
+ $html .= '<th>' . __('Data definition statement') . '</th>';
+ $html .= '<th>' . __('Delete') . '</th>';
+ $html .= '</tr>';
+ $html .= '</thead>';
+ $html .= '<tbody>';
+
+ $style = 'odd';
+ foreach ($data['ddlog'] as $entry) {
+ $html .= PMA_getHtmlForDataDefinitionStatement(
+ $entry, $filter_users, $filter_ts_from, $filter_ts_to, $style, $i,
+ $url_params, $drop_image_or_text
+ );
+ if ($style == 'even') {
+ $style = 'odd';
+ } else {
+ $style = 'even';
+ }
+ $i++;
+ }
+ $html .= '</tbody>';
+ $html .= '</table>';
+
+ return array($html, $i);
+}
+/**
+ * Function to get html for a data definition statement in schema snapshot
+ *
+ * @param array $entry entry
+ * @param array $filter_users filter users
+ * @param int $filter_ts_from filter time stamp from
+ * @param int $filter_ts_to filter time stamp to
+ * @param string $style style
+ * @param int $i column number
+ * @param array $url_params url parameters
+ * @param string $drop_image_or_text drop image or text
+ *
+ * @return string
+ */
+function PMA_getHtmlForDataDefinitionStatement($entry, $filter_users,
+ $filter_ts_from, $filter_ts_to, $style, $i, $url_params, $drop_image_or_text
+) {
+ $statement = PMA_Util::formatSql($entry['statement'], true);
+ $timestamp = strtotime($entry['date']);
+ $filtered_user = in_array($entry['username'], $filter_users);
+ $html = null;
+
+ if ($timestamp >= $filter_ts_from
+ && $timestamp <= $filter_ts_to
+ && (in_array('*', $filter_users) || $filtered_user)
+ ) {
+ $html = '<tr class="noclick ' . $style . '">';
+ $html .= '<td><small>' . $i . '</small></td>';
+ $html .= '<td><small>'
+ . htmlspecialchars($entry['date']) . '</small></td>';
+ $html .= '<td><small>'
+ . htmlspecialchars($entry['username']) . '</small></td>';
+ $html .= '<td>' . $statement . '</td>';
+ $html .= '<td class="nowrap"><a href="tbl_tracking.php'
+ . PMA_URL_getCommon(
+ $url_params + array(
+ 'report' => 'true',
+ 'version' => $_REQUEST['version'],
+ 'delete_ddlog' => ($i - 1),
+ )
+ )
+ . '">' . $drop_image_or_text
+ . '</a></td>';
+ $html .= '</tr>';
+ }
+
+ return $html;
+}
+/**
+ * Function to get html for schema snapshot
+ *
+ * @param string $url_query url query
+ *
+ * @return string
+ */
+function PMA_getHtmlForSchemaSnapshot($url_query)
+{
+ $html = '<h3>' . __('Structure snapshot')
+ . ' [<a href="tbl_tracking.php?' . $url_query . '">' . __('Close')
+ . '</a>]</h3>';
+ $data = PMA_Tracker::getTrackedData(
+ $_REQUEST['db'], $_REQUEST['table'], $_REQUEST['version']
+ );
+
+ // Get first DROP TABLE/VIEW and CREATE TABLE/VIEW statements
+ $drop_create_statements = $data['ddlog'][0]['statement'];
+
+ if (strstr($data['ddlog'][0]['statement'], 'DROP TABLE')
+ || strstr($data['ddlog'][0]['statement'], 'DROP VIEW')
+ ) {
+ $drop_create_statements .= $data['ddlog'][1]['statement'];
+ }
+ // Print SQL code
+ $html .= PMA_Util::getMessage(
+ sprintf(
+ __('Version %s snapshot (SQL code)'),
+ htmlspecialchars($_REQUEST['version'])
+ ),
+ $drop_create_statements
+ );
+
+ // Unserialize snapshot
+ $temp = unserialize($data['schema_snapshot']);
+ $columns = $temp['COLUMNS'];
+ $indexes = $temp['INDEXES'];
+ $html .= PMA_getHtmlForColumns($columns);
+
+ if (count($indexes) > 0) {
+ $html .= PMA_getHtmlForIndexes($indexes);
+ } // endif
+ $html .= '<br /><hr /><br />';
+
+ return $html;
+}
+
+/**
+ * Function to get html for displaying columns in the schema snapshot
+ *
+ * @param array $columns columns
+ *
+ * @return string
+ */
+function PMA_getHtmlForColumns($columns)
+{
+ $html = '<h3>' . __('Structure') . '</h3>';
+ $html .= '<table id="tablestructure" class="data">';
+ $html .= '<thead>';
+ $html .= '<tr>';
+ $html .= '<th>' . __('Column') . '</th>';
+ $html .= '<th>' . __('Type') . '</th>';
+ $html .= '<th>' . __('Collation') . '</th>';
+ $html .= '<th>' . __('Null') . '</th>';
+ $html .= '<th>' . __('Default') . '</th>';
+ $html .= '<th>' . __('Extra') . '</th>';
+ $html .= '<th>' . __('Comment') . '</th>';
+ $html .= '</tr>';
+ $html .= '</thead>';
+ $html .= '<tbody>';
+ $style = 'odd';
+ foreach ($columns as $field) {
+ $html .= PMA_getHtmlForField($field, $style);
+ if ($style == 'even') {
+ $style = 'odd';
+ } else {
+ $style = 'even';
+ }
+ }
+
+ $html .= '</tbody>';
+ $html .= '</table>';
+
+ return $html;
+}
+
+/**
+ * Function to get html for field
+ *
+ * @param array $field field
+ * @param string $style style
+ *
+ * @return string
+ */
+function PMA_getHtmlForField($field, $style)
+{
+ $html = '<tr class="noclick ' . $style . '">';
+ if ($field['Key'] == 'PRI') {
+ $html .= '<td><b><u>' . htmlspecialchars($field['Field']) . '</u></b></td>';
+ } else {
+ $html .= '<td><b>' . htmlspecialchars($field['Field']) . '</b></td>';
+ }
+ $html .= "\n";
+ $html .= '<td>' . htmlspecialchars($field['Type']) . '</td>';
+ $html .= '<td>' . htmlspecialchars($field['Collation']) . '</td>';
+ $html .= '<td>' . (($field['Null'] == 'YES') ? __('Yes') : __('No')) . '</td>';
+ $html .= '<td>';
+ if (isset($field['Default'])) {
+ $extracted_columnspec = PMA_Util::extractColumnSpec($field['Type']);
+ if ($extracted_columnspec['type'] == 'bit') {
+ // here, $field['Default'] contains something like b'010'
+ $html .= PMA_Util::convertBitDefaultValue($field['Default']);
+ } else {
+ $html .= htmlspecialchars($field['Default']);
+ }
+ } else {
+ if ($field['Null'] == 'YES') {
+ $html .= '<i>NULL</i>';
+ } else {
+ $html .= '<i>' . _pgettext('None for default', 'None') . '</i>';
+ }
+ }
+ $html .= '</td>';
+ $html .= '<td>' . htmlspecialchars($field['Extra']) . '</td>';
+ $html .= '<td>' . htmlspecialchars($field['Comment']) . '</td>';
+ $html .= '</tr>';
+
+ return $html;
+}
+
+/**
+ * Fuunction to get html for the indexes in schema snapshot
+ *
+ * @param array $indexes indexes
+ *
+ * @return string
+ */
+function PMA_getHtmlForIndexes($indexes)
+{
+ $html = '<h3>' . __('Indexes') . '</h3>';
+ $html .= '<table id="tablestructure_indexes" class="data">';
+ $html .= '<thead>';
+ $html .= '<tr>';
+ $html .= '<th>' . __('Keyname') . '</th>';
+ $html .= '<th>' . __('Type') . '</th>';
+ $html .= '<th>' . __('Unique') . '</th>';
+ $html .= '<th>' . __('Packed') . '</th>';
+ $html .= '<th>' . __('Column') . '</th>';
+ $html .= '<th>' . __('Cardinality') . '</th>';
+ $html .= '<th>' . __('Collation') . '</th>';
+ $html .= '<th>' . __('Null') . '</th>';
+ $html .= '<th>' . __('Comment') . '</th>';
+ $html .= '</tr>';
+ $html .= '<tbody>';
+
+ $style = 'odd';
+ foreach ($indexes as $index) {
+ $html .= PMA_getHtmlForIndex($index, $style);
+ if ($style == 'even') {
+ $style = 'odd';
+ } else {
+ $style = 'even';
+ }
+ }
+ $html .= '</tbody>';
+ $html .= '</table>';
+ return $html;
+}
+
+/**
+ * Funtion to get html for an index in schema snapshot
+ *
+ * @param array $index index
+ * @param string $style style
+ *
+ * @return string
+ */
+function PMA_getHtmlForIndex($index, $style)
+{
+ if ($index['Non_unique'] == 0) {
+ $str_unique = __('Yes');
+ } else {
+ $str_unique = __('No');
+ }
+ if ($index['Packed'] != '') {
+ $str_packed = __('Yes');
+ } else {
+ $str_packed = __('No');
+ }
+
+ $html = '<tr class="noclick ' . $style . '">';
+ $html .= '<td><b>' . htmlspecialchars($index['Key_name']) . '</b></td>';
+ $html .= '<td>' . htmlspecialchars($index['Index_type']) . '</td>';
+ $html .= '<td>' . $str_unique . '</td>';
+ $html .= '<td>' . $str_packed . '</td>';
+ $html .= '<td>' . htmlspecialchars($index['Column_name']) . '</td>';
+ $html .= '<td>' . htmlspecialchars($index['Cardinality']) . '</td>';
+ $html .= '<td>' . htmlspecialchars($index['Collation']) . '</td>';
+ $html .= '<td>' . htmlspecialchars($index['Null']) . '</td>';
+ $html .= '<td>' . htmlspecialchars($index['Comment']) . '</td>';
+ $html .= '</tr>';
+
+ return $html;
+}
+
+/**
+ * Function to handle the tracking report
+ *
+ * @param array &$data tracked data
+ *
+ * @return void
+ */
+function PMA_deleteTrackingReportRows(&$data)
+{
+ if (isset($_REQUEST['delete_ddlog'])) {
+ // Delete ddlog row data
+ PMA_handleDeleteDataDefinitionsLog($data);
+ }
+
+ if (isset($_REQUEST['delete_dmlog'])) {
+ // Delete dmlog row data
+ PMA_handleDeleteDataManipulationLog($data);
+ }
+}
+
+/**
+ * Function to handle the delete ddlog row data
+ *
+ * @param array &$data tracked data
+ *
+ * @return void
+ */
+function PMA_handleDeleteDataDefinitionsLog(&$data)
+{
+ $delete_id = $_REQUEST['delete_ddlog'];
+
+ // Only in case of valable id
+ if ($delete_id == (int)$delete_id) {
+ unset($data['ddlog'][$delete_id]);
+
+ $successfullyDeleted = PMA_Tracker::changeTrackingData(
+ $_REQUEST['db'], $_REQUEST['table'],
+ $_REQUEST['version'], 'DDL', $data['ddlog']
+ );
+ if ($successfullyDeleted) {
+ $msg = PMA_Message::success(
+ __('Tracking data definition successfully deleted')
+ );
+ } else {
+ $msg = PMA_Message::rawError(__('Query error'));
+ }
+ $msg->display();
+ }
+}
+
+/**
+ * Function to handle the delete of fmlog rows
+ *
+ * @param array &$data tracked data
+ *
+ * @return void
+ */
+function PMA_handleDeleteDataManipulationLog(&$data)
+{
+ $delete_id = $_REQUEST['delete_dmlog'];
+
+ // Only in case of valable id
+ if ($delete_id == (int)$delete_id) {
+ unset($data['dmlog'][$delete_id]);
+
+ $successfullyDeleted = PMA_Tracker::changeTrackingData(
+ $_REQUEST['db'], $_REQUEST['table'],
+ $_REQUEST['version'], 'DML', $data['dmlog']
+ );
+ if ($successfullyDeleted) {
+ $msg = PMA_Message::success(
+ __('Tracking data manipulation successfully deleted')
+ );
+ } else {
+ $msg = PMA_Message::rawError(__('Query error'));
+ }
+ $msg->display();
+ }
+}
+
+/**
+ * Function to export as sql dump
+ *
+ * @param array $entries entries
+ *
+ * @return void
+ */
+function PMA_exportAsSQLDump($entries)
+{
+ $new_query = "# "
+ . __(
+ 'You can execute the dump by creating and using a temporary database. '
+ . 'Please ensure that you have the privileges to do so.'
+ )
+ . "\n"
+ . "# " . __('Comment out these two lines if you do not need them.') . "\n"
+ . "\n"
+ . "CREATE database IF NOT EXISTS pma_temp_db; \n"
+ . "USE pma_temp_db; \n"
+ . "\n";
+
+ foreach ($entries as $entry) {
+ $new_query .= $entry['statement'];
+ }
+ $msg = PMA_Message::success(
+ __('SQL statements exported. Please copy the dump or execute it.')
+ );
+ $msg->display();
+
+ $db_temp = $GLOBALS['db'];
+ $table_temp = $GLOBALS['table'];
+
+ $GLOBALS['db'] = $GLOBALS['table'] = '';
+ include_once './libraries/sql_query_form.lib.php';
+
+ PMA_getHtmlForSqlQueryForm($new_query, 'sql');
+
+ $GLOBALS['db'] = $db_temp;
+ $GLOBALS['table'] = $table_temp;
+}
+
+/**
+ * Function to export as sql execution
+ *
+ * @param array $entries entries
+ *
+ * @return array
+ */
+function PMA_exportAsSQLExecution($entries)
+{
+ foreach ($entries as $entry) {
+ $sql_result = $GLOBALS['dbi']->query("/*NOTRACK*/\n" . $entry['statement']);
+ }
+ $msg = PMA_Message::success(__('SQL statements executed.'));
+ $msg->display();
+
+ return $sql_result;
+}
+
+/**
+ * Function to export as entries
+ *
+ * @param array $entries entries
+ *
+ * @return void
+ */
+function PMA_exportAsFileDownload($entries)
+{
+ @ini_set('url_rewriter.tags', '');
+
+ $dump = "# " . sprintf(
+ __('Tracking report for table `%s`'), htmlspecialchars($_REQUEST['table'])
+ )
+ . "\n" . "# " . date('Y-m-d H:i:s') . "\n";
+ foreach ($entries as $entry) {
+ $dump .= $entry['statement'];
+ }
+ $filename = 'log_' . htmlspecialchars($_REQUEST['table']) . '.sql';
+ PMA_downloadHeader($filename, 'text/x-sql', strlen($dump));
+
+ $response = PMA_Response::getInstance();
+ $response->addHTML($dump);
+
+ exit();
+}
+
+/**
+ * Function to activate tracking
+ *
+ * @return void
+ */
+function PMA_activateTracking()
+{
+ $activated = PMA_Tracker::activateTracking(
+ $GLOBALS['db'], $GLOBALS['table'], $_REQUEST['version']
+ );
+ if ($activated) {
+ $msg = PMA_Message::success(
+ sprintf(
+ __('Tracking for %1$s was activated at version %2$s.'),
+ htmlspecialchars($GLOBALS['db'] . '.' . $GLOBALS['table']),
+ htmlspecialchars($_REQUEST['version'])
+ )
+ );
+ $msg->display();
+ }
+}
+
+/**
+ * Function to deactivate tracking
+ *
+ * @return void
+ */
+function PMA_deactivateTracking()
+{
+ $deactivated = PMA_Tracker::deactivateTracking(
+ $GLOBALS['db'], $GLOBALS['table'], $_REQUEST['version']
+ );
+ if ($deactivated) {
+ $msg = PMA_Message::success(
+ sprintf(
+ __('Tracking for %1$s was deactivated at version %2$s.'),
+ htmlspecialchars($GLOBALS['db'] . '.' . $GLOBALS['table']),
+ htmlspecialchars($_REQUEST['version'])
+ )
+ );
+ $msg->display();
+ }
+}
+
+/**
+ * Function to get tracking set
+ *
+ * @return string
+ */
+function PMA_getTrackingSet()
+{
+ $tracking_set = '';
+
+ if ($_REQUEST['alter_table'] == true) {
+ $tracking_set .= 'ALTER TABLE,';
+ }
+ if ($_REQUEST['rename_table'] == true) {
+ $tracking_set .= 'RENAME TABLE,';
+ }
+ if ($_REQUEST['create_table'] == true) {
+ $tracking_set .= 'CREATE TABLE,';
+ }
+ if ($_REQUEST['drop_table'] == true) {
+ $tracking_set .= 'DROP TABLE,';
+ }
+ if ($_REQUEST['create_index'] == true) {
+ $tracking_set .= 'CREATE INDEX,';
+ }
+ if ($_REQUEST['drop_index'] == true) {
+ $tracking_set .= 'DROP INDEX,';
+ }
+ if ($_REQUEST['insert'] == true) {
+ $tracking_set .= 'INSERT,';
+ }
+ if ($_REQUEST['update'] == true) {
+ $tracking_set .= 'UPDATE,';
+ }
+ if ($_REQUEST['delete'] == true) {
+ $tracking_set .= 'DELETE,';
+ }
+ if ($_REQUEST['truncate'] == true) {
+ $tracking_set .= 'TRUNCATE,';
+ }
+ $tracking_set = rtrim($tracking_set, ',');
+
+ return $tracking_set;
+}
+
+/**
+ * Function to create the tracking version
+ *
+ * @return void
+ */
+function PMA_createTrackingVersion()
+{
+ $tracking_set = PMA_getTrackingSet();
+
+ $versionCreated = PMA_Tracker::createVersion(
+ $GLOBALS['db'],
+ $GLOBALS['table'],
+ $_REQUEST['version'],
+ $tracking_set,
+ PMA_Table::isView($GLOBALS['db'], $GLOBALS['table'])
+ );
+ if ($versionCreated) {
+ $msg = PMA_Message::success(
+ sprintf(
+ __('Version %1$s was created, tracking for %2$s is active.'),
+ htmlspecialchars($_REQUEST['version']),
+ htmlspecialchars($GLOBALS['db'] . '.' . $GLOBALS['table'])
+ )
+ );
+ $msg->display();
+ }
+}
+
+/**
+ * Function to get the entries
+ *
+ * @param array $data data
+ * @param int $filter_ts_from filter time stamp from
+ * @param int $filter_ts_to filter time stamp to
+ * @param array $filter_users filter users
+ *
+ * @return array
+ */
+function PMA_getEntries($data, $filter_ts_from, $filter_ts_to, $filter_users)
+{
+ $entries = array();
+ // Filtering data definition statements
+ if ($_REQUEST['logtype'] == 'schema'
+ || $_REQUEST['logtype'] == 'schema_and_data'
+ ) {
+ $entries = array_merge(
+ $entries,
+ PMA_filterTracking(
+ $data['ddlog'], $filter_ts_from, $filter_ts_to, $filter_users
+ )
+ );
+ }
+
+ // Filtering data manipulation statements
+ if ($_REQUEST['logtype'] == 'data'
+ || $_REQUEST['logtype'] == 'schema_and_data'
+ ) {
+ $entries = array_merge(
+ $entries,
+ PMA_filterTracking(
+ $data['dmlog'], $filter_ts_from, $filter_ts_to, $filter_users
+ )
+ );
+ }
+
+ // Sort it
+ foreach ($entries as $key => $row) {
+ $ids[$key] = $row['id'];
+ $timestamps[$key] = $row['timestamp'];
+ $usernames[$key] = $row['username'];
+ $statements[$key] = $row['statement'];
+ }
+
+ array_multisort(
+ $timestamps, SORT_ASC, $ids, SORT_ASC, $usernames,
+ SORT_ASC, $statements, SORT_ASC, $entries
+ );
+
+ return $entries;
+}
+?>
diff --git a/libraries/tbl_views.lib.php b/libraries/tbl_views.lib.php
new file mode 100644
index 0000000000..955e2d5064
--- /dev/null
+++ b/libraries/tbl_views.lib.php
@@ -0,0 +1,159 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Set of functions related to applying transformations for VIEWs
+ *
+ * @package PhpMyAdmin
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+
+/**
+ * Get the column details of VIEW with its original references
+ *
+ * @param string $sql_query SQL for original resource
+ * @param array $view_columns Columns of VIEW if defined new column names
+ *
+ * @return array $column_map Details of VIEW columns
+ */
+function PMA_getColumnMap($sql_query, $view_columns)
+{
+
+ $column_map = array();
+ // Select query which give results for VIEW
+ $real_source_result = $GLOBALS['dbi']->tryQuery($sql_query);
+
+ if ($real_source_result !== false) {
+
+ $real_source_fields_meta = $GLOBALS['dbi']->getFieldsMeta(
+ $real_source_result
+ );
+
+ if (count($real_source_fields_meta) > 0) {
+
+ for ($i=0; $i<count($real_source_fields_meta); $i++) {
+
+ $map = array();
+ $map['table_name'] = $real_source_fields_meta[$i]->table;
+ $map['refering_column'] = $real_source_fields_meta[$i]->name;
+
+ if (count($view_columns) > 1) {
+ $map['real_column'] = $view_columns[$i];
+ }
+
+ $column_map[] = $map;
+
+ }
+
+ }
+
+ }
+ unset($real_source_result);
+
+ return $column_map;
+
+}
+
+
+/**
+ * Get existing data on tranformations applyed for
+ * columns in a particular table
+ *
+ * @param string $db Database name looking for
+ *
+ * @return mysqli_result Result of executed SQL query
+ */
+function PMA_getExistingTranformationData($db)
+{
+ $cfgRelation = PMA_getRelationsParam();
+
+ // Get the existing transformation details of the same database
+ // from pma__column_info table
+ $pma_transformation_sql = 'SELECT * FROM '
+ . PMA_Util::backquote($cfgRelation['db']) . '.'
+ . PMA_Util::backquote($cfgRelation['column_info'])
+ . ' WHERE `db_name` = \''
+ . PMA_Util::sqlAddSlashes($db) . '\'';
+
+ return $GLOBALS['dbi']->tryQuery($pma_transformation_sql);
+
+}
+
+
+/**
+ * Get SQL query for store new transformation details of a VIEW
+ *
+ * @param mysqli_result $pma_tranformation_data Result set of SQL execution
+ * @param array $column_map Details of VIEW columns
+ * @param string $view_name Name of the VIEW
+ * @param string $db Database name of the VIEW
+ *
+ * @return string $new_transformations_sql SQL query for new tranformations
+ */
+function PMA_getNewTransformationDataSql(
+ $pma_tranformation_data, $column_map, $view_name, $db
+) {
+ $cfgRelation = PMA_getRelationsParam();
+
+ // Need to store new transformation details for VIEW
+ $new_transformations_sql = 'INSERT INTO '
+ . PMA_Util::backquote($cfgRelation['db']) . '.'
+ . PMA_Util::backquote($cfgRelation['column_info'])
+ . ' (`db_name`, `table_name`, `column_name`, `comment`, '
+ . '`mimetype`, `transformation`, `transformation_options`)'
+ . ' VALUES ';
+
+ $column_count = 0;
+ $add_comma = false;
+
+ while ($data_row = $GLOBALS['dbi']->fetchAssoc($pma_tranformation_data)) {
+
+ foreach ($column_map as $column) {
+
+ if ($data_row['table_name'] == $column['table_name']
+ && $data_row['column_name'] == $column['refering_column']
+ ) {
+
+ $new_transformations_sql .= $add_comma ? ', ' : '';
+
+ $new_transformations_sql .= '('
+ . '\'' . $db . '\', '
+ . '\'' . $view_name . '\', '
+ . '\'';
+
+ $new_transformations_sql .= (isset($column['real_column']))
+ ? $column['real_column']
+ : $column['refering_column'];
+
+ $new_transformations_sql .= '\', '
+ . '\'' . $data_row['comment'] . '\', '
+ . '\'' . $data_row['mimetype'] . '\', '
+ . '\'' . $data_row['transformation'] . '\', '
+ . '\''
+ . PMA_Util::sqlAddSlashes(
+ $data_row['transformation_options']
+ )
+ . '\')';
+
+ $add_comma = true;
+ $column_count++;
+ break;
+
+ }
+
+ }
+
+ if ($column_count == count($column_map)) {
+ break;
+ }
+
+ }
+
+ return ($column_count > 0) ? $new_transformations_sql : '';
+
+}
+
+
+?>
diff --git a/libraries/tcpdf/LICENSE.TXT b/libraries/tcpdf/LICENSE.TXT
new file mode 100644
index 0000000000..daf21f7d3e
--- /dev/null
+++ b/libraries/tcpdf/LICENSE.TXT
@@ -0,0 +1,858 @@
+**********************************************************************
+* TCPDF LICENSE
+**********************************************************************
+
+ TCPDF 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 3 of the
+ License, or (at your option) any later version.
+
+**********************************************************************
+**********************************************************************
+
+ GNU LESSER GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://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.
+
+**********************************************************************
+**********************************************************************
+
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users. We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors. 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
+them 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 prevent others from denying you
+these rights or asking you to surrender the rights. Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received. 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.
+
+ Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+ For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software. For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+ Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so. This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software. The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable. Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products. If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+ Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary. To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey 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;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If 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 convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Use with the GNU Affero General Public License.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU 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 that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ 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.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+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.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ 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
+state 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 3 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, see <http://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+ <program> Copyright (C) <year> <name of author>
+ This program 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, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+<http://www.gnu.org/licenses/>.
+
+ The GNU 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. But first, please read
+<http://www.gnu.org/philosophy/why-not-lgpl.html>.
+
+**********************************************************************
+**********************************************************************
diff --git a/libraries/tcpdf/README.TXT b/libraries/tcpdf/README.TXT
new file mode 100644
index 0000000000..a20a2c9fee
--- /dev/null
+++ b/libraries/tcpdf/README.TXT
@@ -0,0 +1,111 @@
+TCPDF - README
+============================================================
+
+I WISH TO IMPROVE AND EXPAND TCPDF BUT I NEED YOUR SUPPORT.
+PLEASE MAKE A DONATION:
+http://sourceforge.net/donate/index.php?group_id=128076
+
+------------------------------------------------------------
+
+Name: TCPDF
+Version: 6.0.039
+Release date: 2013-10-13
+Author: Nicola Asuni
+
+Copyright (c) 2002-2013:
+ Nicola Asuni
+ Tecnick.com LTD
+ www.tecnick.com
+
+URLs:
+ http://www.tcpdf.org
+ http://www.sourceforge.net/projects/tcpdf
+
+Description:
+ TCPDF is a PHP class for generating PDF files on-the-fly without requiring external extensions.
+ This library includes also a class to extract data from existing PDF documents and
+ classes to generate 1D and 2D barcodes in various formats.
+
+Main Features:
+ * no external libraries are required for the basic functions;
+ * all standard page formats, custom page formats, custom margins and units of measure;
+ * UTF-8 Unicode and Right-To-Left languages;
+ * TrueTypeUnicode, OpenTypeUnicode v1, TrueType, OpenType v1, Type1 and CID-0 fonts;
+ * font subsetting;
+ * methods to publish some XHTML + CSS code, Javascript and Forms;
+ * images, graphic (geometric figures) and transformation methods;
+ * supports JPEG, PNG and SVG images natively, all images supported by GD (GD, GD2, GD2PART, GIF, JPEG, PNG, BMP, XBM, XPM) and all images supported via ImagMagick (http: www.imagemagick.org/www/formats.html)
+ * 1D and 2D barcodes: CODE 39, ANSI MH10.8M-1983, USD-3, 3 of 9, CODE 93, USS-93, Standard 2 of 5, Interleaved 2 of 5, CODE 128 A/B/C, 2 and 5 Digits UPC-Based Extention, EAN 8, EAN 13, UPC-A, UPC-E, MSI, POSTNET, PLANET, RMS4CC (Royal Mail 4-state Customer Code), CBC (Customer Bar Code), KIX (Klant index - Customer index), Intelligent Mail Barcode, Onecode, USPS-B-3200, CODABAR, CODE 11, PHARMACODE, PHARMACODE TWO-TRACKS, Datamatrix, QR-Code, PDF417;
+ * JPEG and PNG ICC profiles, Grayscale, RGB, CMYK, Spot Colors and Transparencies;
+ * automatic page header and footer management;
+ * document encryption up to 256 bit and digital signature certifications;
+ * transactions to UNDO commands;
+ * PDF annotations, including links, text and file attachments;
+ * text rendering modes (fill, stroke and clipping);
+ * multiple columns mode;
+ * no-write page regions;
+ * bookmarks, named destinations and table of content;
+ * text hyphenation;
+ * text stretching and spacing (tracking);
+ * automatic page break, line break and text alignments including justification;
+ * automatic page numbering and page groups;
+ * move and delete pages;
+ * page compression (requires php-zlib extension);
+ * XOBject Templates;
+ * Layers and object visibility.
+ * PDF/A-1b support.
+
+Installation (full instructions on http: www.tcpdf.org):
+ 1. copy the folder on your Web server
+ 2. set your installation path and other parameters on the config/tcpdf_config.php
+ 3. call the examples/example_001.php page with your browser to see an example
+
+Source Code Documentation:
+ http://www.tcpdf.org
+
+Additional Documentation:
+ http://www.tcpdf.org
+
+License:
+ Copyright (C) 2002-2013 Nicola Asuni - Tecnick.com LTD
+
+ TCPDF 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 3 of the
+ License, or (at your option) any later version.
+
+ TCPDF 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 License
+ along with TCPDF. If not, see
+ <http://www.tecnick.com/pagefiles/tcpdf/LICENSE.TXT>.
+
+ See LICENSE.TXT file for more information.
+
+Third party fonts:
+
+ This library may include third party font files released with different licenses.
+
+ All the PHP files on the fonts directory are subject to the general TCPDF license (GNU-LGPLv3),
+ they do not contain any binary data but just a description of the general properties of a particular font.
+ These files can be also generated on the fly using the font utilities and TCPDF methods.
+
+ All the original binary TTF font files have been renamed for compatibility with TCPDF and compressed using the gzcompress PHP function that uses the ZLIB data format (.z files).
+
+ The binary files (.z) that begins with the prefix "free" have been extracted from the GNU FreeFont collection (GNU-GPLv3).
+ The binary files (.z) that begins with the prefix "pdfa" have been derived from the GNU FreeFont, so they are subject to the same license.
+ For the details of Copyright, License and other information, please check the files inside the directory fonts/freefont-20120503
+ Link : http://www.gnu.org/software/freefont/
+
+ The binary files (.z) that begins with the prefix "dejavu" have been extracted from the DejaVu fonts 2.33 (Bitstream) collection.
+ For the details of Copyright, License and other information, please check the files inside the directory fonts/dejavu-fonts-ttf-2.33
+ Link : http://dejavu-fonts.org
+
+ The binary files (.z) that begins with the prefix "ae" have been extracted from the Arabeyes.org collection (GNU-GPLv2).
+ Link : http://projects.arabeyes.org/
+
+
+============================================================
diff --git a/libraries/tcpdf/config/tcpdf_config.php b/libraries/tcpdf/config/tcpdf_config.php
new file mode 100644
index 0000000000..e6c1bf0d17
--- /dev/null
+++ b/libraries/tcpdf/config/tcpdf_config.php
@@ -0,0 +1,219 @@
+<?php
+//============================================================+
+// File name : tcpdf_config.php
+// Begin : 2004-06-11
+// Last Update : 2013-05-16
+//
+// Description : Configuration file for TCPDF.
+// Author : Nicola Asuni - Tecnick.com LTD - www.tecnick.com - info@tecnick.com
+// License : GNU-LGPL v3 (http://www.gnu.org/copyleft/lesser.html)
+// -------------------------------------------------------------------
+// Copyright (C) 2004-2013 Nicola Asuni - Tecnick.com LTD
+//
+// This file is part of TCPDF software library.
+//
+// TCPDF 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 3 of the
+// License, or (at your option) any later version.
+//
+// TCPDF 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 TCPDF. If not, see <http://www.gnu.org/licenses/>.
+//
+// See LICENSE.TXT file for more information.
+//============================================================+
+
+/**
+ * Configuration file for TCPDF.
+ * @author Nicola Asuni
+ * @package com.tecnick.tcpdf
+ * @version 4.9.005
+ * @since 2004-10-27
+ */
+
+// If you define the constant K_TCPDF_EXTERNAL_CONFIG, the following settings will be ignored.
+
+/**
+ * Installation path (/var/www/tcpdf/).
+ * By default it is automatically calculated but you can also set it as a fixed string to improve performances.
+ */
+//define ('K_PATH_MAIN', '');
+
+/**
+ * URL path to tcpdf installation folder (http://localhost/tcpdf/).
+ * By default it is automatically set but you can also set it as a fixed string to improve performances.
+ */
+//define ('K_PATH_URL', '');
+
+/**
+ * Path for PDF fonts.
+ * By default it is automatically set but you can also set it as a fixed string to improve performances.
+ */
+//define ('K_PATH_FONTS', K_PATH_MAIN.'fonts/');
+
+/**
+ * Default images directory.
+ * By default it is automatically set but you can also set it as a fixed string to improve performances.
+ */
+//define ('K_PATH_IMAGES', '');
+
+/**
+ * Deafult image logo used be the default Header() method.
+ * Please set here your own logo or an empty string to disable it.
+ */
+//define ('PDF_HEADER_LOGO', '');
+
+/**
+ * Header logo image width in user units.
+ */
+//define ('PDF_HEADER_LOGO_WIDTH', 0);
+
+/**
+ * Cache directory for temporary files (full path).
+ */
+define ('K_PATH_CACHE', sys_get_temp_dir().'/');
+
+/**
+ * Generic name for a blank image.
+ */
+define ('K_BLANK_IMAGE', '_blank.png');
+
+/**
+ * Page format.
+ */
+define ('PDF_PAGE_FORMAT', 'A4');
+
+/**
+ * Page orientation (P=portrait, L=landscape).
+ */
+define ('PDF_PAGE_ORIENTATION', 'P');
+
+/**
+ * Document creator.
+ */
+define ('PDF_CREATOR', 'TCPDF');
+
+/**
+ * Document author.
+ */
+define ('PDF_AUTHOR', 'TCPDF');
+
+/**
+ * Header title.
+ */
+define ('PDF_HEADER_TITLE', 'TCPDF Example');
+
+/**
+ * Header description string.
+ */
+define ('PDF_HEADER_STRING', "by Nicola Asuni - Tecnick.com\nwww.tcpdf.org");
+
+/**
+ * Document unit of measure [pt=point, mm=millimeter, cm=centimeter, in=inch].
+ */
+define ('PDF_UNIT', 'mm');
+
+/**
+ * Header margin.
+ */
+define ('PDF_MARGIN_HEADER', 5);
+
+/**
+ * Footer margin.
+ */
+define ('PDF_MARGIN_FOOTER', 10);
+
+/**
+ * Top margin.
+ */
+define ('PDF_MARGIN_TOP', 27);
+
+/**
+ * Bottom margin.
+ */
+define ('PDF_MARGIN_BOTTOM', 25);
+
+/**
+ * Left margin.
+ */
+define ('PDF_MARGIN_LEFT', 15);
+
+/**
+ * Right margin.
+ */
+define ('PDF_MARGIN_RIGHT', 15);
+
+/**
+ * Default main font name.
+ */
+define ('PDF_FONT_NAME_MAIN', 'helvetica');
+
+/**
+ * Default main font size.
+ */
+define ('PDF_FONT_SIZE_MAIN', 10);
+
+/**
+ * Default data font name.
+ */
+define ('PDF_FONT_NAME_DATA', 'helvetica');
+
+/**
+ * Default data font size.
+ */
+define ('PDF_FONT_SIZE_DATA', 8);
+
+/**
+ * Default monospaced font name.
+ */
+define ('PDF_FONT_MONOSPACED', 'courier');
+
+/**
+ * Ratio used to adjust the conversion of pixels to user units.
+ */
+define ('PDF_IMAGE_SCALE_RATIO', 1.25);
+
+/**
+ * Magnification factor for titles.
+ */
+define('HEAD_MAGNIFICATION', 1.1);
+
+/**
+ * Height of cell respect font height.
+ */
+define('K_CELL_HEIGHT_RATIO', 1.25);
+
+/**
+ * Title magnification respect main font size.
+ */
+define('K_TITLE_MAGNIFICATION', 1.3);
+
+/**
+ * Reduction factor for small font.
+ */
+define('K_SMALL_RATIO', 2/3);
+
+/**
+ * Set to true to enable the special procedure used to avoid the overlappind of symbols on Thai language.
+ */
+define('K_THAI_TOPCHARS', true);
+
+/**
+ * If true allows to call TCPDF methods using HTML syntax
+ * IMPORTANT: For security reason, disable this feature if you are printing user HTML content.
+ */
+define('K_TCPDF_CALLS_IN_HTML', true);
+
+/**
+ * If true adn PHP version is greater than 5, then the Error() method throw new exception instead of terminating the execution.
+ */
+define('K_TCPDF_THROW_EXCEPTION_ERROR', false);
+
+//============================================================+
+// END OF FILE
+//============================================================+
diff --git a/libraries/tcpdf/fonts/dejavu-fonts-ttf-2.33/LICENSE b/libraries/tcpdf/fonts/dejavu-fonts-ttf-2.33/LICENSE
new file mode 100644
index 0000000000..254e2cc42a
--- /dev/null
+++ b/libraries/tcpdf/fonts/dejavu-fonts-ttf-2.33/LICENSE
@@ -0,0 +1,99 @@
+Fonts are (c) Bitstream (see below). DejaVu changes are in public domain.
+Glyphs imported from Arev fonts are (c) Tavmjong Bah (see below)
+
+Bitstream Vera Fonts Copyright
+------------------------------
+
+Copyright (c) 2003 by Bitstream, Inc. All Rights Reserved. Bitstream Vera is
+a trademark of Bitstream, Inc.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of the fonts accompanying this license ("Fonts") and associated
+documentation files (the "Font Software"), to reproduce and distribute the
+Font Software, including without limitation the rights to use, copy, merge,
+publish, distribute, and/or sell copies of the Font Software, and to permit
+persons to whom the Font Software is furnished to do so, subject to the
+following conditions:
+
+The above copyright and trademark notices and this permission notice shall
+be included in all copies of one or more of the Font Software typefaces.
+
+The Font Software may be modified, altered, or added to, and in particular
+the designs of glyphs or characters in the Fonts may be modified and
+additional glyphs or characters may be added to the Fonts, only if the fonts
+are renamed to names not containing either the words "Bitstream" or the word
+"Vera".
+
+This License becomes null and void to the extent applicable to Fonts or Font
+Software that has been modified and is distributed under the "Bitstream
+Vera" names.
+
+The Font Software may be sold as part of a larger software package but no
+copy of one or more of the Font Software typefaces may be sold by itself.
+
+THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT,
+TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL BITSTREAM OR THE GNOME
+FOUNDATION BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING
+ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
+THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE
+FONT SOFTWARE.
+
+Except as contained in this notice, the names of Gnome, the Gnome
+Foundation, and Bitstream Inc., shall not be used in advertising or
+otherwise to promote the sale, use or other dealings in this Font Software
+without prior written authorization from the Gnome Foundation or Bitstream
+Inc., respectively. For further information, contact: fonts at gnome dot
+org.
+
+Arev Fonts Copyright
+------------------------------
+
+Copyright (c) 2006 by Tavmjong Bah. All Rights Reserved.
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of the fonts accompanying this license ("Fonts") and
+associated documentation files (the "Font Software"), to reproduce
+and distribute the modifications to the Bitstream Vera Font Software,
+including without limitation the rights to use, copy, merge, publish,
+distribute, and/or sell copies of the Font Software, and to permit
+persons to whom the Font Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright and trademark notices and this permission notice
+shall be included in all copies of one or more of the Font Software
+typefaces.
+
+The Font Software may be modified, altered, or added to, and in
+particular the designs of glyphs or characters in the Fonts may be
+modified and additional glyphs or characters may be added to the
+Fonts, only if the fonts are renamed to names not containing either
+the words "Tavmjong Bah" or the word "Arev".
+
+This License becomes null and void to the extent applicable to Fonts
+or Font Software that has been modified and is distributed under the
+"Tavmjong Bah Arev" names.
+
+The Font Software may be sold as part of a larger software package but
+no copy of one or more of the Font Software typefaces may be sold by
+itself.
+
+THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
+OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL
+TAVMJONG BAH BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
+DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
+OTHER DEALINGS IN THE FONT SOFTWARE.
+
+Except as contained in this notice, the name of Tavmjong Bah shall not
+be used in advertising or otherwise to promote the sale, use or other
+dealings in this Font Software without prior written authorization
+from Tavmjong Bah. For further information, contact: tavmjong @ free
+. fr.
+
+$Id: LICENSE 2133 2007-11-28 02:46:28Z lechimp $
diff --git a/libraries/tcpdf/fonts/dejavusans.ctg.z b/libraries/tcpdf/fonts/dejavusans.ctg.z
new file mode 100644
index 0000000000..cceabb6543
--- /dev/null
+++ b/libraries/tcpdf/fonts/dejavusans.ctg.z
Binary files differ
diff --git a/libraries/tcpdf/fonts/dejavusans.php b/libraries/tcpdf/fonts/dejavusans.php
new file mode 100644
index 0000000000..3cd08131a7
--- /dev/null
+++ b/libraries/tcpdf/fonts/dejavusans.php
@@ -0,0 +1,15 @@
+<?php
+// TCPDF FONT FILE DESCRIPTION
+$type='TrueTypeUnicode';
+$name='DejaVuSans';
+$up=-63;
+$ut=44;
+$dw=600;
+$diff='';
+$originalsize=720012;
+$enc='';
+$file='dejavusans.z';
+$ctg='dejavusans.ctg.z';
+$desc=array('Flags'=>32,'FontBBox'=>'[-1021 -415 1681 1167]','ItalicAngle'=>0,'Ascent'=>928,'Descent'=>-236,'Leading'=>0,'CapHeight'=>729,'XHeight'=>547,'StemV'=>34,'StemH'=>15,'AvgWidth'=>507,'MaxWidth'=>1735,'MissingWidth'=>600);
+$cw=array(0=>600,32=>318,33=>401,34=>460,35=>838,36=>636,37=>950,38=>780,39=>275,40=>390,41=>390,42=>500,43=>838,44=>318,45=>361,46=>318,47=>337,48=>636,49=>636,50=>636,51=>636,52=>636,53=>636,54=>636,55=>636,56=>636,57=>636,58=>337,59=>337,60=>838,61=>838,62=>838,63=>531,64=>1000,65=>684,66=>686,67=>698,68=>770,69=>632,70=>575,71=>775,72=>752,73=>295,74=>295,75=>656,76=>557,77=>863,78=>748,79=>787,80=>603,81=>787,82=>695,83=>635,84=>611,85=>732,86=>684,87=>989,88=>685,89=>611,90=>685,91=>390,92=>337,93=>390,94=>838,95=>500,96=>500,97=>613,98=>635,99=>550,100=>635,101=>615,102=>352,103=>635,104=>634,105=>278,106=>278,107=>579,108=>278,109=>974,110=>634,111=>612,112=>635,113=>635,114=>411,115=>521,116=>392,117=>634,118=>592,119=>818,120=>592,121=>592,122=>525,123=>636,124=>337,125=>636,126=>838,160=>318,161=>401,162=>636,163=>636,164=>636,165=>636,166=>337,167=>500,168=>500,169=>1000,170=>471,171=>612,172=>838,173=>361,174=>1000,175=>500,176=>500,177=>838,178=>401,179=>401,180=>500,181=>636,182=>636,183=>318,184=>500,185=>401,186=>471,187=>612,188=>969,189=>969,190=>969,191=>531,192=>684,193=>684,194=>684,195=>684,196=>684,197=>684,198=>974,199=>698,200=>632,201=>632,202=>632,203=>632,204=>295,205=>295,206=>295,207=>295,208=>775,209=>748,210=>787,211=>787,212=>787,213=>787,214=>787,215=>838,216=>787,217=>732,218=>732,219=>732,220=>732,221=>611,222=>605,223=>630,224=>613,225=>613,226=>613,227=>613,228=>613,229=>613,230=>982,231=>550,232=>615,233=>615,234=>615,235=>615,236=>278,237=>278,238=>278,239=>278,240=>612,241=>634,242=>612,243=>612,244=>612,245=>612,246=>612,247=>838,248=>612,249=>634,250=>634,251=>634,252=>634,253=>592,254=>635,255=>592,256=>684,257=>613,258=>684,259=>613,260=>684,261=>613,262=>698,263=>550,264=>698,265=>550,266=>698,267=>550,268=>698,269=>550,270=>770,271=>635,272=>775,273=>635,274=>632,275=>615,276=>632,277=>615,278=>632,279=>615,280=>632,281=>615,282=>632,283=>615,284=>775,285=>635,286=>775,287=>635,288=>775,289=>635,290=>775,291=>635,292=>752,293=>634,294=>916,295=>695,296=>295,297=>278,298=>295,299=>278,300=>295,301=>278,302=>295,303=>278,304=>295,305=>278,306=>590,307=>556,308=>295,309=>278,310=>656,311=>579,312=>579,313=>557,314=>278,315=>557,316=>278,317=>557,318=>375,319=>557,320=>342,321=>562,322=>284,323=>748,324=>634,325=>748,326=>634,327=>748,328=>634,329=>813,330=>748,331=>634,332=>787,333=>612,334=>787,335=>612,336=>787,337=>612,338=>1070,339=>1023,340=>695,341=>411,342=>695,343=>411,344=>695,345=>411,346=>635,347=>521,348=>635,349=>521,350=>635,351=>521,352=>635,353=>521,354=>611,355=>392,356=>611,357=>392,358=>611,359=>392,360=>732,361=>634,362=>732,363=>634,364=>732,365=>634,366=>732,367=>634,368=>732,369=>634,370=>732,371=>634,372=>989,373=>818,374=>611,375=>592,376=>611,377=>685,378=>525,379=>685,380=>525,381=>685,382=>525,383=>352,384=>635,385=>735,386=>686,387=>635,388=>686,389=>635,390=>703,391=>698,392=>550,393=>775,394=>819,395=>686,396=>635,397=>612,398=>632,399=>787,400=>614,401=>575,402=>352,403=>775,404=>687,405=>984,406=>354,407=>295,408=>746,409=>579,410=>278,411=>592,412=>974,413=>748,414=>634,415=>787,416=>913,417=>612,418=>949,419=>759,420=>652,421=>635,422=>695,423=>635,424=>521,425=>632,426=>336,427=>392,428=>611,429=>392,430=>611,431=>858,432=>634,433=>764,434=>721,435=>744,436=>730,437=>685,438=>525,439=>666,440=>666,441=>578,442=>525,443=>636,444=>666,445=>578,446=>510,447=>635,448=>295,449=>492,450=>459,451=>295,452=>1422,453=>1299,454=>1154,455=>835,456=>787,457=>457,458=>931,459=>924,460=>797,461=>684,462=>613,463=>295,464=>278,465=>787,466=>612,467=>732,468=>634,469=>732,470=>634,471=>732,472=>634,473=>732,474=>634,475=>732,476=>634,477=>615,478=>684,479=>613,480=>684,481=>613,482=>974,483=>982,484=>775,485=>635,486=>775,487=>635,488=>656,489=>579,490=>787,491=>612,492=>787,493=>612,494=>666,495=>578,496=>278,497=>1422,498=>1299,499=>1154,500=>775,501=>635,502=>1113,503=>682,504=>748,505=>634,506=>684,507=>613,508=>974,509=>982,510=>787,511=>612,512=>684,513=>613,514=>684,515=>613,516=>632,517=>615,518=>632,519=>615,520=>295,521=>278,522=>295,523=>278,524=>787,525=>612,526=>787,527=>612,528=>695,529=>411,530=>695,531=>411,532=>732,533=>634,534=>732,535=>634,536=>635,537=>521,538=>611,539=>392,540=>627,541=>521,542=>752,543=>634,544=>735,545=>838,546=>698,547=>610,548=>685,549=>525,550=>684,551=>613,552=>632,553=>615,554=>787,555=>612,556=>787,557=>612,558=>787,559=>612,560=>787,561=>612,562=>611,563=>592,564=>475,565=>843,566=>477,567=>278,568=>998,569=>998,570=>684,571=>698,572=>550,573=>557,574=>611,575=>521,576=>525,577=>603,578=>479,579=>686,580=>732,581=>684,582=>632,583=>615,584=>295,585=>278,586=>781,587=>635,588=>695,589=>411,590=>611,591=>592,592=>600,593=>635,594=>635,595=>635,596=>549,597=>550,598=>635,599=>696,600=>615,601=>615,602=>819,603=>541,604=>532,605=>775,606=>664,607=>278,608=>696,609=>635,610=>629,611=>596,612=>596,613=>634,614=>634,615=>634,616=>278,617=>338,618=>372,619=>396,620=>487,621=>278,622=>706,623=>974,624=>974,625=>974,626=>646,627=>642,628=>634,629=>612,630=>858,631=>728,632=>660,633=>414,634=>414,635=>414,636=>411,637=>411,638=>530,639=>530,640=>604,641=>604,642=>521,643=>336,644=>336,645=>461,646=>336,647=>392,648=>392,649=>634,650=>618,651=>598,652=>592,653=>818,654=>592,655=>611,656=>525,657=>525,658=>578,659=>578,660=>510,661=>510,662=>510,663=>510,664=>787,665=>580,666=>664,667=>708,668=>654,669=>292,670=>667,671=>507,672=>727,673=>510,674=>510,675=>1014,676=>1058,677=>1013,678=>824,679=>610,680=>778,681=>848,682=>641,683=>654,684=>515,685=>515,686=>661,687=>664,688=>404,689=>399,690=>175,691=>259,692=>295,693=>296,694=>379,695=>515,696=>373,697=>278,698=>460,699=>318,700=>318,701=>318,702=>307,703=>307,704=>370,705=>370,706=>500,707=>500,708=>500,709=>500,710=>500,711=>500,712=>275,713=>500,714=>500,715=>500,716=>275,717=>500,718=>500,719=>500,720=>337,721=>337,722=>307,723=>307,724=>500,725=>500,726=>390,727=>317,728=>500,729=>500,730=>500,731=>500,732=>500,733=>500,734=>315,735=>500,736=>426,737=>166,738=>373,739=>444,740=>370,741=>493,742=>493,743=>493,744=>493,745=>493,748=>500,749=>500,750=>518,755=>500,759=>500,768=>0,769=>0,770=>0,771=>0,772=>0,773=>0,774=>0,775=>0,776=>0,777=>0,778=>0,779=>0,780=>0,781=>0,782=>0,783=>0,784=>0,785=>0,786=>0,787=>0,788=>0,789=>0,790=>0,791=>0,792=>0,793=>0,794=>0,795=>0,796=>0,797=>0,798=>0,799=>0,800=>0,801=>0,802=>0,803=>0,804=>0,805=>0,806=>0,807=>0,808=>0,809=>0,810=>0,811=>0,812=>0,813=>0,814=>0,815=>0,816=>0,817=>0,818=>0,819=>0,820=>0,821=>0,822=>0,823=>0,824=>0,825=>0,826=>0,827=>0,828=>0,829=>0,830=>0,831=>0,832=>0,833=>0,834=>0,835=>0,836=>0,837=>0,838=>0,839=>0,840=>0,841=>0,842=>0,843=>0,844=>0,845=>0,846=>0,847=>0,849=>0,850=>0,851=>0,855=>0,856=>0,858=>0,860=>0,861=>0,862=>0,863=>0,864=>0,865=>0,866=>0,880=>654,881=>568,882=>862,883=>647,884=>278,885=>278,886=>748,887=>650,890=>500,891=>549,892=>550,893=>549,894=>337,900=>500,901=>500,902=>692,903=>318,904=>746,905=>871,906=>408,908=>813,910=>825,911=>826,912=>338,913=>684,914=>686,915=>557,916=>684,917=>632,918=>685,919=>752,920=>787,921=>295,922=>656,923=>684,924=>863,925=>748,926=>632,927=>787,928=>752,929=>603,931=>632,932=>611,933=>611,934=>787,935=>685,936=>787,937=>764,938=>295,939=>611,940=>659,941=>541,942=>634,943=>338,944=>579,945=>659,946=>638,947=>592,948=>612,949=>541,950=>544,951=>634,952=>612,953=>338,954=>589,955=>592,956=>636,957=>559,958=>558,959=>612,960=>602,961=>635,962=>587,963=>634,964=>602,965=>579,966=>660,967=>578,968=>660,969=>837,970=>338,971=>579,972=>612,973=>579,974=>837,975=>656,976=>614,977=>619,978=>699,979=>842,980=>699,981=>660,982=>837,983=>664,984=>787,985=>612,986=>648,987=>587,988=>575,989=>458,990=>660,991=>660,992=>865,993=>627,994=>934,995=>837,996=>758,997=>659,998=>792,999=>615,1000=>687,1001=>607,1002=>768,1003=>625,1004=>699,1005=>612,1006=>611,1007=>536,1008=>664,1009=>635,1010=>550,1011=>278,1012=>787,1013=>615,1014=>615,1015=>605,1016=>635,1017=>698,1018=>863,1019=>651,1020=>635,1021=>703,1022=>698,1023=>703,1024=>632,1025=>632,1026=>786,1027=>610,1028=>698,1029=>635,1030=>295,1031=>295,1032=>295,1033=>1094,1034=>1045,1035=>786,1036=>710,1037=>748,1038=>609,1039=>752,1040=>684,1041=>686,1042=>686,1043=>610,1044=>781,1045=>632,1046=>1077,1047=>641,1048=>748,1049=>748,1050=>710,1051=>752,1052=>863,1053=>752,1054=>787,1055=>752,1056=>603,1057=>698,1058=>611,1059=>609,1060=>861,1061=>685,1062=>776,1063=>686,1064=>1069,1065=>1094,1066=>833,1067=>882,1068=>686,1069=>698,1070=>1080,1071=>695,1072=>613,1073=>617,1074=>589,1075=>525,1076=>691,1077=>615,1078=>901,1079=>532,1080=>650,1081=>650,1082=>604,1083=>639,1084=>754,1085=>654,1086=>612,1087=>654,1088=>635,1089=>550,1090=>583,1091=>592,1092=>855,1093=>592,1094=>681,1095=>591,1096=>915,1097=>942,1098=>707,1099=>790,1100=>589,1101=>549,1102=>842,1103=>602,1104=>615,1105=>615,1106=>625,1107=>525,1108=>549,1109=>521,1110=>278,1111=>278,1112=>278,1113=>902,1114=>898,1115=>652,1116=>604,1117=>650,1118=>592,1119=>654,1120=>934,1121=>837,1122=>771,1123=>672,1124=>942,1125=>749,1126=>879,1127=>783,1128=>1160,1129=>1001,1130=>787,1131=>612,1132=>1027,1133=>824,1134=>636,1135=>541,1136=>856,1137=>876,1138=>787,1139=>612,1140=>781,1141=>665,1142=>781,1143=>665,1144=>992,1145=>904,1146=>953,1147=>758,1148=>1180,1149=>1028,1150=>934,1151=>837,1152=>698,1153=>550,1154=>502,1155=>0,1156=>0,1157=>0,1158=>0,1159=>0,1160=>418,1161=>418,1162=>772,1163=>677,1164=>686,1165=>589,1166=>603,1167=>635,1168=>610,1169=>525,1170=>675,1171=>590,1172=>624,1173=>530,1174=>1077,1175=>901,1176=>641,1177=>532,1178=>710,1179=>604,1180=>710,1181=>604,1182=>710,1183=>604,1184=>856,1185=>832,1186=>752,1187=>661,1188=>1014,1189=>877,1190=>1081,1191=>916,1192=>878,1193=>693,1194=>698,1195=>550,1196=>611,1197=>583,1198=>611,1199=>592,1200=>611,1201=>592,1202=>685,1203=>592,1204=>934,1205=>807,1206=>686,1207=>591,1208=>686,1209=>591,1210=>686,1211=>634,1212=>941,1213=>728,1214=>941,1215=>728,1216=>295,1217=>1077,1218=>901,1219=>656,1220=>604,1221=>776,1222=>670,1223=>752,1224=>661,1225=>776,1226=>681,1227=>686,1228=>591,1229=>888,1230=>774,1231=>278,1232=>684,1233=>613,1234=>684,1235=>613,1236=>974,1237=>982,1238=>632,1239=>615,1240=>787,1241=>615,1242=>787,1243=>615,1244=>1077,1245=>901,1246=>641,1247=>532,1248=>666,1249=>578,1250=>748,1251=>650,1252=>748,1253=>650,1254=>787,1255=>612,1256=>787,1257=>612,1258=>787,1259=>612,1260=>698,1261=>549,1262=>609,1263=>592,1264=>609,1265=>592,1266=>609,1267=>592,1268=>686,1269=>591,1270=>610,1271=>525,1272=>882,1273=>790,1274=>675,1275=>590,1276=>685,1277=>592,1278=>685,1279=>592,1280=>686,1281=>589,1282=>1006,1283=>897,1284=>975,1285=>869,1286=>679,1287=>588,1288=>1072,1289=>957,1290=>1113,1291=>967,1292=>775,1293=>660,1294=>773,1295=>711,1296=>614,1297=>541,1298=>752,1299=>639,1300=>1169,1301=>994,1302=>894,1303=>864,1304=>1032,1305=>986,1306=>787,1307=>635,1308=>989,1309=>818,1310=>710,1311=>604,1312=>1081,1313=>905,1314=>1081,1315=>912,1316=>793,1317=>683,1329=>766,1330=>732,1331=>753,1332=>753,1333=>732,1334=>772,1335=>640,1336=>732,1337=>859,1338=>753,1339=>691,1340=>533,1341=>922,1342=>863,1343=>732,1344=>716,1345=>766,1346=>753,1347=>767,1348=>792,1349=>728,1350=>729,1351=>757,1352=>732,1353=>713,1354=>800,1355=>768,1356=>792,1357=>732,1358=>753,1359=>705,1360=>694,1361=>744,1362=>538,1363=>811,1364=>757,1365=>787,1366=>790,1369=>307,1370=>318,1371=>234,1372=>361,1373=>238,1374=>405,1375=>500,1377=>974,1378=>634,1379=>658,1380=>663,1381=>634,1382=>635,1383=>515,1384=>634,1385=>738,1386=>658,1387=>634,1388=>271,1389=>980,1390=>623,1391=>634,1392=>634,1393=>608,1394=>634,1395=>629,1396=>634,1397=>271,1398=>634,1399=>499,1400=>634,1401=>404,1402=>974,1403=>560,1404=>648,1405=>634,1406=>634,1407=>974,1408=>634,1409=>633,1410=>435,1411=>974,1412=>636,1413=>609,1414=>805,1415=>812,1417=>337,1418=>361,1456=>0,1457=>0,1458=>0,1459=>0,1460=>0,1461=>0,1462=>0,1463=>0,1464=>0,1465=>0,1466=>0,1467=>0,1468=>0,1469=>0,1470=>361,1471=>0,1472=>295,1473=>0,1474=>0,1475=>295,1478=>441,1479=>0,1488=>668,1489=>578,1490=>412,1491=>546,1492=>653,1493=>272,1494=>346,1495=>653,1496=>648,1497=>224,1498=>537,1499=>529,1500=>568,1501=>664,1502=>679,1503=>272,1504=>400,1505=>649,1506=>626,1507=>640,1508=>625,1509=>540,1510=>593,1511=>709,1512=>564,1513=>708,1514=>657,1520=>471,1521=>423,1522=>331,1523=>416,1524=>645,1542=>637,1543=>637,1545=>757,1546=>977,1548=>323,1557=>0,1563=>318,1567=>531,1569=>470,1570=>278,1571=>278,1572=>483,1573=>278,1574=>783,1575=>278,1576=>941,1577=>524,1578=>941,1579=>941,1580=>646,1581=>646,1582=>646,1583=>445,1584=>445,1585=>483,1586=>483,1587=>1221,1588=>1221,1589=>1209,1590=>1209,1591=>925,1592=>925,1593=>597,1594=>597,1600=>293,1601=>1037,1602=>776,1603=>824,1604=>727,1605=>619,1606=>734,1607=>524,1608=>483,1609=>783,1610=>783,1611=>0,1612=>0,1613=>0,1614=>0,1615=>0,1616=>0,1617=>0,1618=>0,1619=>0,1620=>0,1621=>0,1623=>0,1626=>500,1632=>537,1633=>537,1634=>537,1635=>537,1636=>537,1637=>537,1638=>537,1639=>537,1640=>537,1641=>537,1642=>537,1643=>325,1644=>318,1645=>545,1646=>941,1647=>776,1648=>0,1652=>292,1657=>941,1658=>941,1659=>941,1660=>941,1661=>941,1662=>941,1663=>941,1664=>941,1665=>646,1666=>646,1667=>646,1668=>646,1669=>646,1670=>646,1671=>646,1672=>445,1673=>445,1674=>445,1675=>445,1676=>445,1677=>445,1678=>445,1679=>445,1680=>445,1681=>483,1682=>483,1683=>498,1684=>530,1685=>610,1686=>530,1687=>483,1688=>483,1689=>483,1690=>1221,1691=>1221,1692=>1221,1693=>1209,1694=>1209,1695=>925,1696=>597,1697=>1037,1698=>1037,1699=>1037,1700=>1037,1701=>1037,1702=>1037,1703=>776,1704=>776,1705=>895,1706=>1054,1707=>895,1708=>824,1709=>824,1710=>824,1711=>895,1712=>895,1713=>895,1714=>895,1715=>895,1716=>895,1717=>727,1718=>727,1719=>727,1720=>727,1721=>734,1722=>734,1723=>734,1724=>734,1725=>734,1726=>698,1727=>646,1734=>483,1740=>783,1742=>783,1749=>524,1776=>537,1777=>537,1778=>537,1779=>537,1780=>537,1781=>537,1782=>537,1783=>537,1784=>537,1785=>537,1984=>636,1985=>636,1986=>636,1987=>636,1988=>636,1989=>636,1990=>636,1991=>636,1992=>636,1993=>636,1994=>278,1995=>571,1996=>424,1997=>592,1998=>654,1999=>654,2000=>594,2001=>654,2002=>829,2003=>438,2004=>438,2005=>559,2006=>612,2007=>350,2008=>959,2009=>473,2010=>783,2011=>654,2012=>625,2013=>734,2014=>530,2015=>724,2016=>473,2017=>625,2018=>594,2019=>530,2020=>530,2021=>522,2022=>594,2023=>594,2027=>0,2028=>0,2029=>0,2030=>0,2031=>0,2032=>0,2033=>0,2034=>0,2035=>0,2036=>313,2037=>313,2040=>560,2041=>560,2042=>361,3647=>636,3713=>670,3714=>684,3716=>688,3719=>482,3720=>628,3722=>684,3725=>688,3732=>669,3733=>642,3734=>645,3735=>655,3737=>659,3738=>625,3739=>625,3740=>745,3741=>767,3742=>687,3743=>687,3745=>702,3746=>688,3747=>684,3749=>649,3751=>632,3754=>703,3755=>819,3757=>633,3758=>684,3759=>788,3760=>632,3761=>0,3762=>539,3763=>539,3764=>0,3765=>0,3766=>0,3767=>0,3768=>0,3769=>0,3771=>0,3772=>0,3773=>663,3776=>375,3777=>657,3778=>460,3779=>547,3780=>491,3782=>674,3784=>0,3785=>0,3786=>0,3787=>0,3788=>0,3789=>0,3792=>636,3793=>641,3794=>641,3795=>670,3796=>625,3797=>625,3798=>703,3799=>670,3800=>674,3801=>677,3804=>1028,3805=>1028,4256=>840,4257=>690,4258=>642,4259=>759,4260=>591,4261=>686,4262=>789,4263=>811,4264=>467,4265=>565,4266=>789,4267=>793,4268=>584,4269=>837,4270=>750,4271=>688,4272=>811,4273=>584,4274=>584,4275=>837,4276=>837,4277=>646,4278=>604,4279=>584,4280=>596,4281=>584,4282=>721,4283=>795,4284=>584,4285=>566,4286=>584,4287=>669,4288=>799,4289=>542,4290=>664,4291=>542,4292=>565,4293=>674,4304=>508,4305=>508,4306=>533,4307=>785,4308=>522,4309=>517,4310=>508,4311=>797,4312=>507,4313=>518,4314=>1058,4315=>522,4316=>523,4317=>783,4318=>518,4319=>523,4320=>792,4321=>523,4322=>656,4323=>524,4324=>788,4325=>523,4326=>782,4327=>523,4328=>522,4329=>522,4330=>566,4331=>523,4332=>523,4333=>489,4334=>522,4335=>498,4336=>517,4337=>560,4338=>508,4339=>508,4340=>508,4341=>563,4342=>824,4343=>595,4344=>522,4345=>554,4346=>553,4347=>586,4348=>304,5121=>684,5122=>684,5123=>684,5124=>684,5125=>769,5126=>769,5127=>769,5129=>769,5130=>769,5131=>769,5132=>835,5133=>834,5134=>835,5135=>834,5136=>835,5137=>834,5138=>967,5139=>1007,5140=>967,5141=>1007,5142=>769,5143=>967,5144=>1007,5145=>967,5146=>1007,5147=>769,5149=>256,5150=>543,5151=>423,5152=>423,5153=>389,5154=>389,5155=>393,5156=>389,5157=>466,5158=>385,5159=>256,5160=>389,5161=>389,5162=>389,5163=>1090,5164=>909,5165=>953,5166=>1117,5167=>684,5168=>684,5169=>684,5170=>684,5171=>729,5172=>729,5173=>729,5175=>729,5176=>729,5177=>729,5178=>835,5179=>684,5180=>835,5181=>834,5182=>835,5183=>834,5184=>967,5185=>1007,5186=>967,5187=>1007,5188=>967,5189=>1007,5190=>967,5191=>1007,5192=>729,5193=>508,5194=>192,5196=>732,5197=>732,5198=>732,5199=>732,5200=>730,5201=>730,5202=>730,5204=>730,5205=>730,5206=>730,5207=>921,5208=>889,5209=>921,5210=>889,5211=>921,5212=>889,5213=>928,5214=>900,5215=>928,5216=>900,5217=>947,5218=>900,5219=>947,5220=>900,5221=>947,5222=>434,5223=>877,5224=>877,5225=>866,5226=>890,5227=>628,5228=>628,5229=>628,5230=>628,5231=>628,5232=>628,5233=>628,5234=>628,5235=>628,5236=>860,5237=>771,5238=>815,5239=>816,5240=>815,5241=>816,5242=>860,5243=>771,5244=>860,5245=>771,5246=>815,5247=>816,5248=>815,5249=>816,5250=>815,5251=>407,5252=>407,5253=>750,5254=>775,5255=>750,5256=>775,5257=>628,5258=>628,5259=>628,5260=>628,5261=>628,5262=>628,5263=>628,5264=>628,5265=>628,5266=>860,5267=>771,5268=>815,5269=>816,5270=>815,5271=>816,5272=>860,5273=>771,5274=>860,5275=>771,5276=>815,5277=>816,5278=>815,5279=>816,5280=>815,5281=>435,5282=>435,5283=>610,5284=>557,5285=>557,5286=>557,5287=>610,5288=>610,5289=>610,5290=>557,5291=>557,5292=>749,5293=>769,5294=>746,5295=>764,5296=>746,5297=>764,5298=>749,5299=>769,5300=>749,5301=>769,5302=>746,5303=>764,5304=>746,5305=>764,5306=>746,5307=>386,5308=>508,5309=>386,5312=>852,5313=>852,5314=>852,5315=>852,5316=>852,5317=>852,5318=>852,5319=>852,5320=>852,5321=>1069,5322=>1035,5323=>1059,5324=>852,5325=>1059,5326=>852,5327=>852,5328=>600,5329=>453,5330=>600,5331=>852,5332=>852,5333=>852,5334=>852,5335=>852,5336=>852,5337=>852,5338=>852,5339=>852,5340=>1069,5341=>1035,5342=>1059,5343=>1030,5344=>1059,5345=>1030,5346=>1069,5347=>1035,5348=>1069,5349=>1035,5350=>1083,5351=>1030,5352=>1083,5353=>1030,5354=>600,5356=>729,5357=>603,5358=>603,5359=>603,5360=>603,5361=>603,5362=>603,5363=>603,5364=>603,5365=>603,5366=>834,5367=>754,5368=>792,5369=>771,5370=>792,5371=>771,5372=>834,5373=>754,5374=>834,5375=>754,5376=>792,5377=>771,5378=>792,5379=>771,5380=>792,5381=>418,5382=>420,5383=>418,5392=>712,5393=>712,5394=>712,5395=>892,5396=>892,5397=>892,5398=>892,5399=>910,5400=>872,5401=>910,5402=>872,5403=>910,5404=>872,5405=>1140,5406=>1100,5407=>1140,5408=>1100,5409=>1140,5410=>1100,5411=>1140,5412=>1100,5413=>641,5414=>627,5415=>627,5416=>627,5417=>627,5418=>627,5419=>627,5420=>627,5421=>627,5422=>627,5423=>844,5424=>781,5425=>816,5426=>818,5427=>816,5428=>818,5429=>844,5430=>781,5431=>844,5432=>781,5433=>816,5434=>818,5435=>816,5436=>818,5437=>816,5438=>418,5440=>389,5441=>484,5442=>916,5443=>916,5444=>916,5445=>916,5446=>916,5447=>916,5448=>603,5449=>603,5450=>603,5451=>603,5452=>603,5453=>603,5454=>834,5455=>754,5456=>418,5458=>729,5459=>684,5460=>684,5461=>684,5462=>684,5463=>726,5464=>726,5465=>726,5466=>726,5467=>924,5468=>1007,5469=>508,5470=>732,5471=>732,5472=>732,5473=>732,5474=>732,5475=>732,5476=>730,5477=>730,5478=>730,5479=>730,5480=>947,5481=>900,5482=>508,5492=>831,5493=>831,5494=>831,5495=>831,5496=>831,5497=>831,5498=>831,5499=>563,5500=>752,5501=>484,5502=>1047,5503=>1047,5504=>1047,5505=>1047,5506=>1047,5507=>1047,5508=>1047,5509=>825,5514=>831,5515=>831,5516=>831,5517=>831,5518=>1259,5519=>1259,5520=>1259,5521=>1002,5522=>1002,5523=>1259,5524=>1259,5525=>700,5526=>1073,5536=>852,5537=>852,5538=>852,5539=>852,5540=>852,5541=>852,5542=>600,5543=>643,5544=>643,5545=>643,5546=>643,5547=>643,5548=>643,5549=>643,5550=>418,5551=>628,5598=>770,5601=>767,5702=>468,5703=>468,5742=>444,5743=>1047,5744=>1310,5745=>1632,5746=>1632,5747=>1375,5748=>1375,5749=>1632,5750=>1632,5760=>477,5761=>493,5762=>712,5763=>931,5764=>1150,5765=>1370,5766=>493,5767=>712,5768=>931,5769=>1150,5770=>1370,5771=>498,5772=>718,5773=>938,5774=>1159,5775=>1379,5776=>493,5777=>712,5778=>930,5779=>1149,5780=>1370,5781=>498,5782=>752,5783=>789,5784=>1205,5785=>1150,5786=>683,5787=>507,5788=>507,7424=>592,7425=>717,7426=>982,7427=>586,7428=>550,7429=>605,7430=>605,7431=>491,7432=>541,7433=>278,7434=>395,7435=>579,7436=>583,7437=>754,7438=>650,7439=>612,7440=>550,7441=>684,7442=>684,7443=>684,7444=>1023,7446=>612,7447=>612,7448=>524,7449=>602,7450=>602,7451=>583,7452=>574,7453=>737,7454=>948,7455=>638,7456=>592,7457=>818,7458=>525,7459=>526,7462=>583,7463=>592,7464=>564,7465=>524,7466=>590,7467=>639,7468=>431,7469=>613,7470=>432,7472=>485,7473=>398,7474=>398,7475=>488,7476=>474,7477=>186,7478=>186,7479=>413,7480=>351,7481=>543,7482=>471,7483=>471,7484=>496,7485=>439,7486=>380,7487=>438,7488=>385,7489=>461,7490=>623,7491=>392,7492=>392,7493=>405,7494=>648,7495=>428,7496=>405,7497=>417,7498=>417,7499=>360,7500=>359,7501=>405,7502=>179,7503=>426,7504=>623,7505=>409,7506=>414,7507=>370,7508=>414,7509=>414,7510=>428,7511=>295,7512=>405,7513=>470,7514=>623,7515=>417,7517=>402,7518=>373,7519=>385,7520=>416,7521=>364,7522=>179,7523=>259,7524=>405,7525=>417,7526=>402,7527=>373,7528=>412,7529=>416,7530=>364,7543=>635,7544=>474,7547=>372,7549=>667,7557=>278,7579=>405,7580=>370,7581=>370,7582=>414,7583=>360,7584=>296,7585=>233,7586=>405,7587=>405,7588=>261,7589=>250,7590=>261,7591=>261,7592=>234,7593=>250,7594=>235,7595=>376,7596=>623,7597=>623,7598=>411,7599=>479,7600=>409,7601=>414,7602=>414,7603=>360,7604=>287,7605=>295,7606=>508,7607=>418,7608=>361,7609=>406,7610=>417,7611=>366,7612=>437,7613=>366,7614=>392,7615=>414,7620=>0,7621=>0,7622=>0,7623=>0,7624=>0,7625=>0,7680=>684,7681=>613,7682=>686,7683=>635,7684=>686,7685=>635,7686=>686,7687=>635,7688=>698,7689=>550,7690=>770,7691=>635,7692=>770,7693=>635,7694=>770,7695=>635,7696=>770,7697=>635,7698=>770,7699=>635,7700=>632,7701=>615,7702=>632,7703=>615,7704=>632,7705=>615,7706=>632,7707=>615,7708=>632,7709=>615,7710=>575,7711=>352,7712=>775,7713=>635,7714=>752,7715=>634,7716=>752,7717=>634,7718=>752,7719=>634,7720=>752,7721=>634,7722=>752,7723=>634,7724=>295,7725=>278,7726=>295,7727=>278,7728=>656,7729=>579,7730=>656,7731=>579,7732=>656,7733=>579,7734=>557,7735=>288,7736=>557,7737=>288,7738=>557,7739=>278,7740=>557,7741=>278,7742=>863,7743=>974,7744=>863,7745=>974,7746=>863,7747=>974,7748=>748,7749=>634,7750=>748,7751=>634,7752=>748,7753=>634,7754=>748,7755=>634,7756=>787,7757=>612,7758=>787,7759=>612,7760=>787,7761=>612,7762=>787,7763=>612,7764=>603,7765=>635,7766=>603,7767=>635,7768=>695,7769=>411,7770=>695,7771=>411,7772=>695,7773=>411,7774=>695,7775=>411,7776=>635,7777=>521,7778=>635,7779=>521,7780=>635,7781=>521,7782=>635,7783=>521,7784=>635,7785=>521,7786=>611,7787=>392,7788=>611,7789=>392,7790=>611,7791=>392,7792=>611,7793=>392,7794=>732,7795=>634,7796=>732,7797=>634,7798=>732,7799=>634,7800=>732,7801=>634,7802=>732,7803=>634,7804=>684,7805=>592,7806=>684,7807=>592,7808=>989,7809=>818,7810=>989,7811=>818,7812=>989,7813=>818,7814=>989,7815=>818,7816=>989,7817=>818,7818=>685,7819=>592,7820=>685,7821=>592,7822=>611,7823=>592,7824=>685,7825=>525,7826=>685,7827=>525,7828=>685,7829=>525,7830=>634,7831=>392,7832=>818,7833=>592,7834=>613,7835=>352,7836=>352,7837=>352,7838=>769,7839=>612,7840=>684,7841=>613,7842=>684,7843=>613,7844=>684,7845=>613,7846=>684,7847=>613,7848=>684,7849=>613,7850=>684,7851=>613,7852=>684,7853=>613,7854=>684,7855=>613,7856=>684,7857=>613,7858=>684,7859=>613,7860=>684,7861=>613,7862=>684,7863=>613,7864=>632,7865=>615,7866=>632,7867=>615,7868=>632,7869=>615,7870=>632,7871=>615,7872=>632,7873=>615,7874=>632,7875=>615,7876=>632,7877=>615,7878=>632,7879=>615,7880=>295,7881=>278,7882=>295,7883=>278,7884=>787,7885=>612,7886=>787,7887=>612,7888=>787,7889=>612,7890=>787,7891=>612,7892=>787,7893=>612,7894=>787,7895=>612,7896=>787,7897=>612,7898=>913,7899=>612,7900=>913,7901=>612,7902=>913,7903=>612,7904=>913,7905=>612,7906=>913,7907=>612,7908=>732,7909=>634,7910=>732,7911=>634,7912=>858,7913=>634,7914=>858,7915=>634,7916=>858,7917=>634,7918=>858,7919=>634,7920=>858,7921=>634,7922=>611,7923=>592,7924=>611,7925=>592,7926=>611,7927=>592,7928=>611,7929=>592,7930=>769,7931=>477,7936=>659,7937=>659,7938=>659,7939=>659,7940=>659,7941=>659,7942=>659,7943=>659,7944=>684,7945=>684,7946=>877,7947=>877,7948=>769,7949=>801,7950=>708,7951=>743,7952=>541,7953=>541,7954=>541,7955=>541,7956=>541,7957=>541,7960=>711,7961=>711,7962=>966,7963=>975,7964=>898,7965=>928,7968=>634,7969=>634,7970=>634,7971=>634,7972=>634,7973=>634,7974=>634,7975=>634,7976=>837,7977=>835,7978=>1086,7979=>1089,7980=>1027,7981=>1051,7982=>934,7983=>947,7984=>338,7985=>338,7986=>338,7987=>338,7988=>338,7989=>338,7990=>338,7991=>338,7992=>380,7993=>374,7994=>635,7995=>635,7996=>570,7997=>600,7998=>489,7999=>493,8000=>612,8001=>612,8002=>612,8003=>612,8004=>612,8005=>612,8008=>804,8009=>848,8010=>1095,8011=>1100,8012=>938,8013=>970,8016=>579,8017=>579,8018=>579,8019=>579,8020=>579,8021=>579,8022=>579,8023=>579,8025=>784,8027=>998,8029=>1012,8031=>897,8032=>837,8033=>837,8034=>837,8035=>837,8036=>837,8037=>837,8038=>837,8039=>837,8040=>802,8041=>843,8042=>1089,8043=>1095,8044=>946,8045=>972,8046=>921,8047=>952,8048=>659,8049=>659,8050=>541,8051=>548,8052=>634,8053=>654,8054=>338,8055=>338,8056=>612,8057=>612,8058=>579,8059=>579,8060=>837,8061=>837,8064=>659,8065=>659,8066=>659,8067=>659,8068=>659,8069=>659,8070=>659,8071=>659,8072=>684,8073=>684,8074=>877,8075=>877,8076=>769,8077=>801,8078=>708,8079=>743,8080=>634,8081=>634,8082=>634,8083=>634,8084=>634,8085=>634,8086=>634,8087=>634,8088=>837,8089=>835,8090=>1086,8091=>1089,8092=>1027,8093=>1051,8094=>934,8095=>947,8096=>837,8097=>837,8098=>837,8099=>837,8100=>837,8101=>837,8102=>837,8103=>837,8104=>802,8105=>843,8106=>1089,8107=>1095,8108=>946,8109=>972,8110=>921,8111=>952,8112=>659,8113=>659,8114=>659,8115=>659,8116=>659,8118=>659,8119=>659,8120=>684,8121=>684,8122=>716,8123=>692,8124=>684,8125=>500,8126=>500,8127=>500,8128=>500,8129=>500,8130=>634,8131=>634,8132=>654,8134=>634,8135=>634,8136=>805,8137=>746,8138=>931,8139=>871,8140=>752,8141=>500,8142=>500,8143=>500,8144=>338,8145=>338,8146=>338,8147=>338,8150=>338,8151=>338,8152=>295,8153=>295,8154=>475,8155=>408,8157=>500,8158=>500,8159=>500,8160=>579,8161=>579,8162=>579,8163=>579,8164=>635,8165=>635,8166=>579,8167=>579,8168=>611,8169=>611,8170=>845,8171=>825,8172=>685,8173=>500,8174=>500,8175=>500,8178=>837,8179=>837,8180=>837,8182=>837,8183=>837,8184=>941,8185=>813,8186=>922,8187=>826,8188=>764,8189=>500,8190=>500,8192=>500,8193=>1000,8194=>500,8195=>1000,8196=>330,8197=>250,8198=>167,8199=>636,8200=>318,8201=>200,8202=>100,8203=>0,8204=>0,8205=>0,8206=>0,8207=>0,8208=>361,8209=>361,8210=>636,8211=>500,8212=>1000,8213=>1000,8214=>500,8215=>500,8216=>318,8217=>318,8218=>318,8219=>318,8220=>518,8221=>518,8222=>518,8223=>518,8224=>500,8225=>500,8226=>590,8227=>590,8228=>334,8229=>667,8230=>1000,8231=>318,8232=>0,8233=>0,8234=>0,8235=>0,8236=>0,8237=>0,8238=>0,8239=>200,8240=>1342,8241=>1735,8242=>227,8243=>374,8244=>520,8245=>227,8246=>374,8247=>520,8248=>339,8249=>400,8250=>400,8251=>838,8252=>485,8253=>531,8254=>500,8255=>804,8256=>804,8257=>250,8258=>1000,8259=>500,8260=>167,8261=>390,8262=>390,8263=>922,8264=>733,8265=>733,8266=>497,8267=>636,8268=>500,8269=>500,8270=>500,8271=>337,8272=>804,8273=>500,8274=>450,8275=>1000,8276=>804,8277=>838,8278=>586,8279=>663,8280=>838,8281=>838,8282=>318,8283=>797,8284=>838,8285=>318,8286=>318,8287=>222,8288=>0,8289=>0,8290=>0,8291=>0,8292=>0,8298=>0,8299=>0,8300=>0,8301=>0,8302=>0,8303=>0,8304=>401,8305=>179,8308=>401,8309=>401,8310=>401,8311=>401,8312=>401,8313=>401,8314=>528,8315=>528,8316=>528,8317=>246,8318=>246,8319=>398,8320=>401,8321=>401,8322=>401,8323=>401,8324=>401,8325=>401,8326=>401,8327=>401,8328=>401,8329=>401,8330=>528,8331=>528,8332=>528,8333=>246,8334=>246,8336=>392,8337=>417,8338=>414,8339=>444,8340=>417,8341=>404,8342=>426,8343=>166,8344=>623,8345=>398,8346=>428,8347=>373,8348=>295,8352=>877,8353=>636,8354=>636,8355=>636,8356=>636,8357=>974,8358=>748,8359=>1272,8360=>1074,8361=>989,8362=>784,8363=>636,8364=>636,8365=>636,8366=>636,8367=>1272,8368=>636,8369=>636,8370=>636,8371=>636,8372=>774,8373=>636,8376=>636,8377=>636,8400=>0,8401=>0,8406=>0,8407=>0,8411=>0,8412=>0,8417=>0,8448=>1019,8449=>1019,8450=>698,8451=>1123,8452=>642,8453=>1019,8454=>1067,8455=>614,8456=>698,8457=>952,8459=>988,8460=>754,8461=>850,8462=>634,8463=>634,8464=>470,8465=>697,8466=>720,8467=>413,8468=>818,8469=>801,8470=>1040,8471=>1000,8472=>697,8473=>701,8474=>787,8475=>798,8476=>814,8477=>792,8478=>896,8479=>684,8480=>1020,8481=>1074,8482=>1000,8483=>684,8484=>745,8485=>578,8486=>764,8487=>764,8488=>616,8489=>338,8490=>656,8491=>684,8492=>786,8493=>703,8494=>854,8495=>592,8496=>605,8497=>786,8498=>575,8499=>1069,8500=>462,8501=>745,8502=>674,8503=>466,8504=>645,8505=>380,8506=>926,8507=>1194,8508=>702,8509=>728,8510=>654,8511=>849,8512=>811,8513=>775,8514=>557,8515=>557,8516=>611,8517=>819,8518=>708,8519=>615,8520=>351,8521=>351,8523=>780,8526=>526,8528=>969,8529=>969,8530=>1370,8531=>969,8532=>969,8533=>969,8534=>969,8535=>969,8536=>969,8537=>969,8538=>969,8539=>969,8540=>969,8541=>969,8542=>969,8543=>568,8544=>295,8545=>492,8546=>689,8547=>923,8548=>684,8549=>922,8550=>1120,8551=>1317,8552=>917,8553=>685,8554=>933,8555=>1131,8556=>557,8557=>698,8558=>770,8559=>863,8560=>278,8561=>458,8562=>637,8563=>812,8564=>592,8565=>811,8566=>991,8567=>1170,8568=>819,8569=>592,8570=>822,8571=>1002,8572=>278,8573=>550,8574=>635,8575=>974,8576=>1245,8577=>770,8578=>1245,8579=>703,8580=>549,8581=>698,8585=>969,8592=>838,8593=>838,8594=>838,8595=>838,8596=>838,8597=>838,8598=>838,8599=>838,8600=>838,8601=>838,8602=>838,8603=>838,8604=>838,8605=>838,8606=>838,8607=>838,8608=>838,8609=>838,8610=>838,8611=>838,8612=>838,8613=>838,8614=>838,8615=>838,8616=>838,8617=>838,8618=>838,8619=>838,8620=>838,8621=>838,8622=>838,8623=>838,8624=>838,8625=>838,8626=>838,8627=>838,8628=>838,8629=>838,8630=>838,8631=>838,8632=>838,8633=>838,8634=>838,8635=>838,8636=>838,8637=>838,8638=>838,8639=>838,8640=>838,8641=>838,8642=>838,8643=>838,8644=>838,8645=>838,8646=>838,8647=>838,8648=>838,8649=>838,8650=>838,8651=>838,8652=>838,8653=>838,8654=>838,8655=>838,8656=>838,8657=>838,8658=>838,8659=>838,8660=>838,8661=>838,8662=>838,8663=>838,8664=>838,8665=>838,8666=>838,8667=>838,8668=>838,8669=>838,8670=>838,8671=>838,8672=>838,8673=>838,8674=>838,8675=>838,8676=>838,8677=>838,8678=>838,8679=>838,8680=>838,8681=>838,8682=>838,8683=>838,8684=>838,8685=>838,8686=>838,8687=>838,8688=>838,8689=>838,8690=>838,8691=>838,8692=>838,8693=>838,8694=>838,8695=>838,8696=>838,8697=>838,8698=>838,8699=>838,8700=>838,8701=>838,8702=>838,8703=>838,8704=>684,8705=>636,8706=>517,8707=>632,8708=>632,8709=>871,8710=>669,8711=>669,8712=>871,8713=>871,8714=>718,8715=>871,8716=>871,8717=>718,8718=>636,8719=>757,8720=>757,8721=>674,8722=>838,8723=>838,8724=>838,8725=>337,8726=>637,8727=>838,8728=>626,8729=>626,8730=>637,8731=>637,8732=>637,8733=>714,8734=>833,8735=>838,8736=>896,8737=>896,8738=>838,8739=>500,8740=>500,8741=>500,8742=>500,8743=>732,8744=>732,8745=>732,8746=>732,8747=>521,8748=>789,8749=>1057,8750=>521,8751=>789,8752=>1057,8753=>521,8754=>521,8755=>521,8756=>636,8757=>636,8758=>260,8759=>636,8760=>838,8761=>838,8762=>838,8763=>838,8764=>838,8765=>838,8766=>838,8767=>838,8768=>375,8769=>838,8770=>838,8771=>838,8772=>838,8773=>838,8774=>838,8775=>838,8776=>838,8777=>838,8778=>838,8779=>838,8780=>838,8781=>838,8782=>838,8783=>838,8784=>838,8785=>838,8786=>839,8787=>839,8788=>1000,8789=>1000,8790=>838,8791=>838,8792=>838,8793=>838,8794=>838,8795=>838,8796=>838,8797=>838,8798=>838,8799=>838,8800=>838,8801=>838,8802=>838,8803=>838,8804=>838,8805=>838,8806=>838,8807=>838,8808=>838,8809=>838,8810=>1047,8811=>1047,8812=>464,8813=>838,8814=>838,8815=>838,8816=>838,8817=>838,8818=>838,8819=>838,8820=>838,8821=>838,8822=>838,8823=>838,8824=>838,8825=>838,8826=>838,8827=>838,8828=>838,8829=>838,8830=>838,8831=>838,8832=>838,8833=>838,8834=>838,8835=>838,8836=>838,8837=>838,8838=>838,8839=>838,8840=>838,8841=>838,8842=>838,8843=>838,8844=>732,8845=>732,8846=>732,8847=>838,8848=>838,8849=>838,8850=>838,8851=>780,8852=>780,8853=>838,8854=>838,8855=>838,8856=>838,8857=>838,8858=>838,8859=>838,8860=>838,8861=>838,8862=>838,8863=>838,8864=>838,8865=>838,8866=>871,8867=>871,8868=>871,8869=>871,8870=>521,8871=>521,8872=>871,8873=>871,8874=>871,8875=>871,8876=>871,8877=>871,8878=>871,8879=>871,8880=>838,8881=>838,8882=>838,8883=>838,8884=>838,8885=>838,8886=>1000,8887=>1000,8888=>838,8889=>838,8890=>521,8891=>732,8892=>732,8893=>732,8894=>838,8895=>838,8896=>820,8897=>820,8898=>820,8899=>820,8900=>494,8901=>318,8902=>626,8903=>838,8904=>1000,8905=>1000,8906=>1000,8907=>1000,8908=>1000,8909=>838,8910=>732,8911=>732,8912=>838,8913=>838,8914=>838,8915=>838,8916=>838,8917=>838,8918=>838,8919=>838,8920=>1422,8921=>1422,8922=>838,8923=>838,8924=>838,8925=>838,8926=>838,8927=>838,8928=>838,8929=>838,8930=>838,8931=>838,8932=>838,8933=>838,8934=>838,8935=>838,8936=>838,8937=>838,8938=>838,8939=>838,8940=>838,8941=>838,8942=>1000,8943=>1000,8944=>1000,8945=>1000,8946=>1000,8947=>871,8948=>718,8949=>871,8950=>871,8951=>718,8952=>871,8953=>871,8954=>1000,8955=>871,8956=>718,8957=>871,8958=>718,8959=>871,8960=>602,8961=>602,8962=>635,8963=>838,8964=>838,8965=>838,8966=>838,8967=>488,8968=>390,8969=>390,8970=>390,8971=>390,8972=>809,8973=>809,8974=>809,8975=>809,8976=>838,8977=>513,8984=>1000,8985=>838,8988=>469,8989=>469,8990=>469,8991=>469,8992=>521,8993=>521,8996=>1152,8997=>1152,8998=>1414,8999=>1152,9000=>1443,9003=>1414,9004=>873,9075=>338,9076=>635,9077=>837,9082=>659,9085=>757,9095=>1152,9108=>873,9115=>500,9116=>500,9117=>500,9118=>500,9119=>500,9120=>500,9121=>500,9122=>500,9123=>500,9124=>500,9125=>500,9126=>500,9127=>750,9128=>750,9129=>750,9130=>750,9131=>750,9132=>750,9133=>750,9134=>521,9166=>838,9167=>945,9187=>873,9189=>769,9192=>636,9250=>635,9251=>635,9312=>896,9313=>896,9314=>896,9315=>896,9316=>896,9317=>896,9318=>896,9319=>896,9320=>896,9321=>896,9472=>602,9473=>602,9474=>602,9475=>602,9476=>602,9477=>602,9478=>602,9479=>602,9480=>602,9481=>602,9482=>602,9483=>602,9484=>602,9485=>602,9486=>602,9487=>602,9488=>602,9489=>602,9490=>602,9491=>602,9492=>602,9493=>602,9494=>602,9495=>602,9496=>602,9497=>602,9498=>602,9499=>602,9500=>602,9501=>602,9502=>602,9503=>602,9504=>602,9505=>602,9506=>602,9507=>602,9508=>602,9509=>602,9510=>602,9511=>602,9512=>602,9513=>602,9514=>602,9515=>602,9516=>602,9517=>602,9518=>602,9519=>602,9520=>602,9521=>602,9522=>602,9523=>602,9524=>602,9525=>602,9526=>602,9527=>602,9528=>602,9529=>602,9530=>602,9531=>602,9532=>602,9533=>602,9534=>602,9535=>602,9536=>602,9537=>602,9538=>602,9539=>602,9540=>602,9541=>602,9542=>602,9543=>602,9544=>602,9545=>602,9546=>602,9547=>602,9548=>602,9549=>602,9550=>602,9551=>602,9552=>602,9553=>602,9554=>602,9555=>602,9556=>602,9557=>602,9558=>602,9559=>602,9560=>602,9561=>602,9562=>602,9563=>602,9564=>602,9565=>602,9566=>602,9567=>602,9568=>602,9569=>602,9570=>602,9571=>602,9572=>602,9573=>602,9574=>602,9575=>602,9576=>602,9577=>602,9578=>602,9579=>602,9580=>602,9581=>602,9582=>602,9583=>602,9584=>602,9585=>602,9586=>602,9587=>602,9588=>602,9589=>602,9590=>602,9591=>602,9592=>602,9593=>602,9594=>602,9595=>602,9596=>602,9597=>602,9598=>602,9599=>602,9600=>769,9601=>769,9602=>769,9603=>769,9604=>769,9605=>769,9606=>769,9607=>769,9608=>769,9609=>769,9610=>769,9611=>769,9612=>769,9613=>769,9614=>769,9615=>769,9616=>769,9617=>769,9618=>769,9619=>769,9620=>769,9621=>769,9622=>769,9623=>769,9624=>769,9625=>769,9626=>769,9627=>769,9628=>769,9629=>769,9630=>769,9631=>769,9632=>945,9633=>945,9634=>945,9635=>945,9636=>945,9637=>945,9638=>945,9639=>945,9640=>945,9641=>945,9642=>678,9643=>678,9644=>945,9645=>945,9646=>550,9647=>550,9648=>769,9649=>769,9650=>769,9651=>769,9652=>502,9653=>502,9654=>769,9655=>769,9656=>502,9657=>502,9658=>769,9659=>769,9660=>769,9661=>769,9662=>502,9663=>502,9664=>769,9665=>769,9666=>502,9667=>502,9668=>769,9669=>769,9670=>769,9671=>769,9672=>769,9673=>873,9674=>494,9675=>873,9676=>873,9677=>873,9678=>873,9679=>873,9680=>873,9681=>873,9682=>873,9683=>873,9684=>873,9685=>873,9686=>527,9687=>527,9688=>791,9689=>970,9690=>970,9691=>970,9692=>387,9693=>387,9694=>387,9695=>387,9696=>873,9697=>873,9698=>769,9699=>769,9700=>769,9701=>769,9702=>590,9703=>945,9704=>945,9705=>945,9706=>945,9707=>945,9708=>769,9709=>769,9710=>769,9711=>1119,9712=>945,9713=>945,9714=>945,9715=>945,9716=>873,9717=>873,9718=>873,9719=>873,9720=>769,9721=>769,9722=>769,9723=>830,9724=>830,9725=>732,9726=>732,9727=>769,9728=>896,9729=>1000,9730=>896,9731=>896,9732=>896,9733=>896,9734=>896,9735=>573,9736=>896,9737=>896,9738=>888,9739=>888,9740=>671,9741=>1013,9742=>1246,9743=>1250,9744=>896,9745=>896,9746=>896,9747=>532,9748=>896,9749=>896,9750=>896,9751=>896,9752=>896,9753=>896,9754=>896,9755=>896,9756=>896,9757=>609,9758=>896,9759=>609,9760=>896,9761=>896,9762=>896,9763=>896,9764=>669,9765=>746,9766=>649,9767=>784,9768=>545,9769=>896,9770=>896,9771=>896,9772=>710,9773=>896,9774=>896,9775=>896,9776=>896,9777=>896,9778=>896,9779=>896,9780=>896,9781=>896,9782=>896,9783=>896,9784=>896,9785=>1042,9786=>1042,9787=>1042,9788=>896,9789=>896,9790=>896,9791=>614,9792=>732,9793=>732,9794=>896,9795=>896,9796=>896,9797=>896,9798=>896,9799=>896,9800=>896,9801=>896,9802=>896,9803=>896,9804=>896,9805=>896,9806=>896,9807=>896,9808=>896,9809=>896,9810=>896,9811=>896,9812=>896,9813=>896,9814=>896,9815=>896,9816=>896,9817=>896,9818=>896,9819=>896,9820=>896,9821=>896,9822=>896,9823=>896,9824=>896,9825=>896,9826=>896,9827=>896,9828=>896,9829=>896,9830=>896,9831=>896,9832=>896,9833=>472,9834=>638,9835=>896,9836=>896,9837=>472,9838=>357,9839=>484,9840=>748,9841=>766,9842=>896,9843=>896,9844=>896,9845=>896,9846=>896,9847=>896,9848=>896,9849=>896,9850=>896,9851=>896,9852=>896,9853=>896,9854=>896,9855=>896,9856=>869,9857=>869,9858=>869,9859=>869,9860=>869,9861=>869,9862=>896,9863=>896,9864=>896,9865=>896,9866=>896,9867=>896,9868=>896,9869=>896,9870=>896,9871=>896,9872=>896,9873=>896,9874=>896,9875=>896,9876=>896,9877=>541,9878=>896,9879=>896,9880=>896,9881=>896,9882=>896,9883=>896,9884=>896,9888=>896,9889=>702,9890=>1004,9891=>1089,9892=>1175,9893=>903,9894=>838,9895=>838,9896=>838,9897=>838,9898=>838,9899=>838,9900=>838,9901=>838,9902=>838,9903=>838,9904=>844,9905=>838,9906=>732,9907=>732,9908=>732,9909=>732,9910=>850,9911=>732,9912=>732,9920=>838,9921=>838,9922=>838,9923=>838,9954=>732,9985=>838,9986=>838,9987=>838,9988=>838,9990=>838,9991=>838,9992=>838,9993=>838,9996=>838,9997=>838,9998=>838,9999=>838,10000=>838,10001=>838,10002=>838,10003=>838,10004=>838,10005=>838,10006=>838,10007=>838,10008=>838,10009=>838,10010=>838,10011=>838,10012=>838,10013=>838,10014=>838,10015=>838,10016=>838,10017=>838,10018=>838,10019=>838,10020=>838,10021=>838,10022=>838,10023=>838,10025=>838,10026=>838,10027=>838,10028=>838,10029=>838,10030=>838,10031=>838,10032=>838,10033=>838,10034=>838,10035=>838,10036=>838,10037=>838,10038=>838,10039=>838,10040=>838,10041=>838,10042=>838,10043=>838,10044=>838,10045=>838,10046=>838,10047=>838,10048=>838,10049=>838,10050=>838,10051=>838,10052=>838,10053=>838,10054=>838,10055=>838,10056=>838,10057=>838,10058=>838,10059=>838,10061=>896,10063=>896,10064=>896,10065=>896,10066=>896,10070=>896,10072=>838,10073=>838,10074=>838,10075=>322,10076=>322,10077=>538,10078=>538,10081=>838,10082=>838,10083=>838,10084=>838,10085=>838,10086=>838,10087=>838,10088=>838,10089=>838,10090=>838,10091=>838,10092=>838,10093=>838,10094=>838,10095=>838,10096=>838,10097=>838,10098=>838,10099=>838,10100=>838,10101=>838,10102=>896,10103=>896,10104=>896,10105=>896,10106=>896,10107=>896,10108=>896,10109=>896,10110=>896,10111=>896,10112=>838,10113=>838,10114=>838,10115=>838,10116=>838,10117=>838,10118=>838,10119=>838,10120=>838,10121=>838,10122=>838,10123=>838,10124=>838,10125=>838,10126=>838,10127=>838,10128=>838,10129=>838,10130=>838,10131=>838,10132=>838,10136=>838,10137=>838,10138=>838,10139=>838,10140=>838,10141=>838,10142=>838,10143=>838,10144=>838,10145=>838,10146=>838,10147=>838,10148=>838,10149=>838,10150=>838,10151=>838,10152=>838,10153=>838,10154=>838,10155=>838,10156=>838,10157=>838,10158=>838,10159=>838,10161=>838,10162=>838,10163=>838,10164=>838,10165=>838,10166=>838,10167=>838,10168=>838,10169=>838,10170=>838,10171=>838,10172=>838,10173=>838,10174=>838,10181=>390,10182=>390,10208=>494,10214=>495,10215=>495,10216=>390,10217=>390,10218=>556,10219=>556,10224=>838,10225=>838,10226=>838,10227=>838,10228=>1157,10229=>1434,10230=>1434,10231=>1434,10232=>1434,10233=>1434,10234=>1434,10235=>1434,10236=>1434,10237=>1434,10238=>1434,10239=>1434,10240=>732,10241=>732,10242=>732,10243=>732,10244=>732,10245=>732,10246=>732,10247=>732,10248=>732,10249=>732,10250=>732,10251=>732,10252=>732,10253=>732,10254=>732,10255=>732,10256=>732,10257=>732,10258=>732,10259=>732,10260=>732,10261=>732,10262=>732,10263=>732,10264=>732,10265=>732,10266=>732,10267=>732,10268=>732,10269=>732,10270=>732,10271=>732,10272=>732,10273=>732,10274=>732,10275=>732,10276=>732,10277=>732,10278=>732,10279=>732,10280=>732,10281=>732,10282=>732,10283=>732,10284=>732,10285=>732,10286=>732,10287=>732,10288=>732,10289=>732,10290=>732,10291=>732,10292=>732,10293=>732,10294=>732,10295=>732,10296=>732,10297=>732,10298=>732,10299=>732,10300=>732,10301=>732,10302=>732,10303=>732,10304=>732,10305=>732,10306=>732,10307=>732,10308=>732,10309=>732,10310=>732,10311=>732,10312=>732,10313=>732,10314=>732,10315=>732,10316=>732,10317=>732,10318=>732,10319=>732,10320=>732,10321=>732,10322=>732,10323=>732,10324=>732,10325=>732,10326=>732,10327=>732,10328=>732,10329=>732,10330=>732,10331=>732,10332=>732,10333=>732,10334=>732,10335=>732,10336=>732,10337=>732,10338=>732,10339=>732,10340=>732,10341=>732,10342=>732,10343=>732,10344=>732,10345=>732,10346=>732,10347=>732,10348=>732,10349=>732,10350=>732,10351=>732,10352=>732,10353=>732,10354=>732,10355=>732,10356=>732,10357=>732,10358=>732,10359=>732,10360=>732,10361=>732,10362=>732,10363=>732,10364=>732,10365=>732,10366=>732,10367=>732,10368=>732,10369=>732,10370=>732,10371=>732,10372=>732,10373=>732,10374=>732,10375=>732,10376=>732,10377=>732,10378=>732,10379=>732,10380=>732,10381=>732,10382=>732,10383=>732,10384=>732,10385=>732,10386=>732,10387=>732,10388=>732,10389=>732,10390=>732,10391=>732,10392=>732,10393=>732,10394=>732,10395=>732,10396=>732,10397=>732,10398=>732,10399=>732,10400=>732,10401=>732,10402=>732,10403=>732,10404=>732,10405=>732,10406=>732,10407=>732,10408=>732,10409=>732,10410=>732,10411=>732,10412=>732,10413=>732,10414=>732,10415=>732,10416=>732,10417=>732,10418=>732,10419=>732,10420=>732,10421=>732,10422=>732,10423=>732,10424=>732,10425=>732,10426=>732,10427=>732,10428=>732,10429=>732,10430=>732,10431=>732,10432=>732,10433=>732,10434=>732,10435=>732,10436=>732,10437=>732,10438=>732,10439=>732,10440=>732,10441=>732,10442=>732,10443=>732,10444=>732,10445=>732,10446=>732,10447=>732,10448=>732,10449=>732,10450=>732,10451=>732,10452=>732,10453=>732,10454=>732,10455=>732,10456=>732,10457=>732,10458=>732,10459=>732,10460=>732,10461=>732,10462=>732,10463=>732,10464=>732,10465=>732,10466=>732,10467=>732,10468=>732,10469=>732,10470=>732,10471=>732,10472=>732,10473=>732,10474=>732,10475=>732,10476=>732,10477=>732,10478=>732,10479=>732,10480=>732,10481=>732,10482=>732,10483=>732,10484=>732,10485=>732,10486=>732,10487=>732,10488=>732,10489=>732,10490=>732,10491=>732,10492=>732,10493=>732,10494=>732,10495=>732,10502=>838,10503=>838,10506=>838,10507=>838,10560=>683,10561=>683,10627=>734,10628=>734,10702=>838,10703=>1000,10704=>1000,10705=>1000,10706=>1000,10707=>1000,10708=>1000,10709=>1000,10731=>494,10746=>838,10747=>838,10752=>1000,10753=>1000,10754=>1000,10764=>1325,10765=>521,10766=>521,10767=>521,10768=>521,10769=>521,10770=>521,10771=>521,10772=>521,10773=>521,10774=>521,10775=>521,10776=>521,10777=>521,10778=>521,10779=>521,10780=>521,10799=>838,10877=>838,10878=>838,10879=>838,10880=>838,10881=>838,10882=>838,10883=>838,10884=>838,10885=>838,10886=>838,10887=>838,10888=>838,10889=>838,10890=>838,10891=>838,10892=>838,10893=>838,10894=>838,10895=>838,10896=>838,10897=>838,10898=>838,10899=>838,10900=>838,10901=>838,10902=>838,10903=>838,10904=>838,10905=>838,10906=>838,10907=>838,10908=>838,10909=>838,10910=>838,10911=>838,10912=>838,10926=>838,10927=>838,10928=>838,10929=>838,10930=>838,10931=>838,10932=>838,10933=>838,10934=>838,10935=>838,10936=>838,10937=>838,10938=>838,11001=>838,11002=>838,11008=>838,11009=>838,11010=>838,11011=>838,11012=>838,11013=>838,11014=>838,11015=>838,11016=>838,11017=>838,11018=>838,11019=>838,11020=>838,11021=>838,11022=>836,11023=>836,11024=>836,11025=>836,11026=>945,11027=>945,11028=>945,11029=>945,11030=>769,11031=>769,11032=>769,11033=>769,11034=>945,11039=>869,11040=>869,11041=>873,11042=>873,11043=>873,11044=>1119,11091=>869,11092=>869,11360=>557,11361=>278,11362=>557,11363=>603,11364=>695,11365=>613,11366=>392,11367=>752,11368=>634,11369=>656,11370=>579,11371=>685,11372=>525,11373=>781,11374=>863,11375=>684,11376=>781,11377=>734,11378=>1128,11379=>961,11380=>592,11381=>654,11382=>568,11383=>660,11385=>414,11386=>612,11387=>491,11388=>175,11389=>431,11390=>635,11391=>685,11568=>646,11569=>888,11570=>888,11571=>682,11572=>684,11573=>635,11574=>562,11575=>684,11576=>684,11577=>632,11578=>632,11579=>683,11580=>875,11581=>685,11582=>491,11583=>685,11584=>888,11585=>888,11586=>300,11587=>627,11588=>752,11589=>656,11590=>527,11591=>685,11592=>645,11593=>632,11594=>502,11595=>953,11596=>778,11597=>748,11598=>621,11599=>295,11600=>778,11601=>295,11602=>752,11603=>633,11604=>888,11605=>888,11606=>752,11607=>320,11608=>749,11609=>888,11610=>888,11611=>698,11612=>768,11613=>685,11614=>698,11615=>622,11616=>684,11617=>752,11618=>632,11619=>788,11620=>567,11621=>788,11631=>515,11800=>531,11810=>390,11811=>390,11812=>390,11813=>390,11822=>531,19904=>896,19905=>896,19906=>896,19907=>896,19908=>896,19909=>896,19910=>896,19911=>896,19912=>896,19913=>896,19914=>896,19915=>896,19916=>896,19917=>896,19918=>896,19919=>896,19920=>896,19921=>896,19922=>896,19923=>896,19924=>896,19925=>896,19926=>896,19927=>896,19928=>896,19929=>896,19930=>896,19931=>896,19932=>896,19933=>896,19934=>896,19935=>896,19936=>896,19937=>896,19938=>896,19939=>896,19940=>896,19941=>896,19942=>896,19943=>896,19944=>896,19945=>896,19946=>896,19947=>896,19948=>896,19949=>896,19950=>896,19951=>896,19952=>896,19953=>896,19954=>896,19955=>896,19956=>896,19957=>896,19958=>896,19959=>896,19960=>896,19961=>896,19962=>896,19963=>896,19964=>896,19965=>896,19966=>896,19967=>896,42564=>635,42565=>521,42566=>354,42567=>338,42572=>1180,42573=>1028,42576=>1029,42577=>906,42580=>1080,42581=>842,42582=>977,42583=>843,42594=>1062,42595=>912,42596=>1066,42597=>901,42598=>1178,42599=>1008,42600=>787,42601=>612,42602=>855,42603=>712,42604=>1358,42605=>1019,42606=>879,42634=>782,42635=>685,42636=>611,42637=>583,42644=>686,42645=>634,42760=>493,42761=>493,42762=>493,42763=>493,42764=>493,42765=>493,42766=>493,42767=>493,42768=>493,42769=>493,42770=>493,42771=>493,42772=>493,42773=>493,42774=>493,42779=>369,42780=>369,42781=>252,42782=>252,42783=>252,42786=>385,42787=>356,42788=>472,42789=>472,42790=>752,42791=>634,42792=>878,42793=>709,42794=>614,42795=>541,42800=>491,42801=>521,42802=>1250,42803=>985,42804=>1203,42805=>990,42806=>1142,42807=>981,42808=>971,42809=>818,42810=>971,42811=>818,42812=>959,42813=>818,42814=>703,42815=>549,42822=>680,42823=>392,42824=>582,42825=>427,42826=>807,42827=>704,42830=>1358,42831=>1019,42832=>603,42833=>635,42834=>734,42835=>774,42838=>787,42839=>635,42852=>605,42853=>635,42854=>605,42855=>635,42880=>557,42881=>278,42882=>735,42883=>634,42889=>337,42890=>376,42891=>401,42892=>275,42893=>686,42894=>487,42896=>772,42897=>667,43002=>915,43003=>575,43004=>603,43005=>863,43006=>295,43007=>1199,61184=>213,61185=>238,61186=>257,61187=>264,61188=>267,61189=>238,61190=>213,61191=>238,61192=>257,61193=>264,61194=>257,61195=>238,61196=>213,61197=>238,61198=>257,61199=>264,61200=>257,61201=>238,61202=>213,61203=>238,61204=>267,61205=>264,61206=>257,61207=>238,61208=>213,61209=>275,61440=>977,61441=>977,61442=>977,61443=>977,63173=>612,64256=>689,64257=>630,64258=>630,64259=>967,64260=>967,64261=>686,64262=>861,64275=>1202,64276=>1202,64277=>1196,64278=>1186,64279=>1529,64285=>224,64286=>0,64287=>331,64288=>636,64289=>856,64290=>774,64291=>906,64292=>771,64293=>843,64294=>855,64295=>807,64296=>875,64297=>838,64298=>708,64299=>708,64300=>708,64301=>708,64302=>668,64303=>668,64304=>668,64305=>578,64306=>412,64307=>546,64308=>653,64309=>355,64310=>406,64312=>648,64313=>330,64314=>537,64315=>529,64316=>568,64318=>679,64320=>399,64321=>649,64323=>640,64324=>625,64326=>593,64327=>709,64328=>564,64329=>708,64330=>657,64331=>272,64332=>578,64333=>529,64334=>625,64335=>629,64338=>941,64339=>982,64340=>278,64341=>302,64342=>941,64343=>982,64344=>278,64345=>302,64346=>941,64347=>982,64348=>278,64349=>302,64350=>941,64351=>982,64352=>278,64353=>302,64354=>941,64355=>982,64356=>278,64357=>302,64358=>941,64359=>982,64360=>278,64361=>302,64362=>1037,64363=>1035,64364=>478,64365=>506,64366=>1037,64367=>1035,64368=>478,64369=>506,64370=>646,64371=>646,64372=>618,64373=>646,64374=>646,64375=>646,64376=>618,64377=>646,64378=>646,64379=>646,64380=>618,64381=>646,64382=>646,64383=>646,64384=>618,64385=>646,64386=>445,64387=>525,64388=>445,64389=>525,64390=>445,64391=>525,64392=>445,64393=>525,64394=>483,64395=>552,64396=>483,64397=>552,64398=>895,64399=>895,64400=>476,64401=>552,64402=>895,64403=>895,64404=>476,64405=>552,64406=>895,64407=>895,64408=>476,64409=>552,64410=>895,64411=>895,64412=>476,64413=>552,64414=>734,64415=>761,64416=>734,64417=>761,64418=>278,64419=>302,64426=>698,64427=>632,64428=>527,64429=>461,64467=>824,64468=>843,64469=>476,64470=>552,64473=>483,64474=>517,64488=>278,64489=>302,64508=>783,64509=>833,64510=>278,64511=>302,65024=>0,65025=>0,65026=>0,65027=>0,65028=>0,65029=>0,65030=>0,65031=>0,65032=>0,65033=>0,65034=>0,65035=>0,65036=>0,65037=>0,65038=>0,65039=>0,65056=>0,65057=>0,65058=>0,65059=>0,65136=>293,65137=>293,65138=>293,65139=>262,65140=>293,65142=>293,65143=>293,65144=>293,65145=>293,65146=>293,65147=>293,65148=>293,65149=>293,65150=>293,65151=>293,65152=>470,65153=>278,65154=>305,65155=>278,65156=>305,65157=>483,65158=>517,65159=>278,65160=>305,65161=>783,65162=>833,65163=>278,65164=>302,65165=>278,65166=>305,65167=>941,65168=>982,65169=>278,65170=>302,65171=>524,65172=>536,65173=>941,65174=>982,65175=>278,65176=>302,65177=>941,65178=>982,65179=>278,65180=>302,65181=>646,65182=>646,65183=>618,65184=>646,65185=>646,65186=>646,65187=>618,65188=>646,65189=>646,65190=>646,65191=>618,65192=>646,65193=>445,65194=>525,65195=>445,65196=>525,65197=>483,65198=>552,65199=>483,65200=>552,65201=>1221,65202=>1275,65203=>838,65204=>892,65205=>1221,65206=>1275,65207=>838,65208=>892,65209=>1209,65210=>1225,65211=>849,65212=>867,65213=>1209,65214=>1225,65215=>849,65216=>867,65217=>925,65218=>949,65219=>796,65220=>820,65221=>925,65222=>949,65223=>796,65224=>820,65225=>597,65226=>532,65227=>597,65228=>482,65229=>597,65230=>532,65231=>523,65232=>482,65233=>1037,65234=>1035,65235=>478,65236=>506,65237=>776,65238=>834,65239=>478,65240=>506,65241=>824,65242=>843,65243=>476,65244=>552,65245=>727,65246=>757,65247=>305,65248=>331,65249=>619,65250=>666,65251=>536,65252=>578,65253=>734,65254=>761,65255=>278,65256=>302,65257=>524,65258=>536,65259=>527,65260=>461,65261=>483,65262=>517,65263=>783,65264=>833,65265=>783,65266=>833,65267=>278,65268=>302,65269=>570,65270=>597,65271=>570,65272=>597,65273=>570,65274=>597,65275=>570,65276=>597,65279=>0,65529=>0,65530=>0,65531=>0,65532=>0,65533=>1025,65535=>600);
+// --- EOF ---
diff --git a/libraries/tcpdf/fonts/dejavusans.z b/libraries/tcpdf/fonts/dejavusans.z
new file mode 100644
index 0000000000..114ed7d33b
--- /dev/null
+++ b/libraries/tcpdf/fonts/dejavusans.z
Binary files differ
diff --git a/libraries/tcpdf/fonts/dejavusansb.ctg.z b/libraries/tcpdf/fonts/dejavusansb.ctg.z
new file mode 100644
index 0000000000..0d1713200f
--- /dev/null
+++ b/libraries/tcpdf/fonts/dejavusansb.ctg.z
Binary files differ
diff --git a/libraries/tcpdf/fonts/dejavusansb.php b/libraries/tcpdf/fonts/dejavusansb.php
new file mode 100644
index 0000000000..ee8a5b6590
--- /dev/null
+++ b/libraries/tcpdf/fonts/dejavusansb.php
@@ -0,0 +1,15 @@
+<?php
+// TCPDF FONT FILE DESCRIPTION
+$type='TrueTypeUnicode';
+$name='DejaVuSans-Bold';
+$up=-63;
+$ut=44;
+$dw=600;
+$diff='';
+$originalsize=672300;
+$enc='';
+$file='dejavusansb.z';
+$ctg='dejavusansb.ctg.z';
+$desc=array('Flags'=>32,'FontBBox'=>'[-1069 -415 1975 1174]','ItalicAngle'=>0,'Ascent'=>928,'Descent'=>-236,'Leading'=>0,'CapHeight'=>729,'XHeight'=>547,'StemV'=>60,'StemH'=>26,'AvgWidth'=>573,'MaxWidth'=>2016,'MissingWidth'=>600);
+$cw=array(0=>600,32=>348,33=>456,34=>521,35=>838,36=>696,37=>1002,38=>872,39=>306,40=>457,41=>457,42=>523,43=>838,44=>380,45=>415,46=>380,47=>365,48=>696,49=>696,50=>696,51=>696,52=>696,53=>696,54=>696,55=>696,56=>696,57=>696,58=>400,59=>400,60=>838,61=>838,62=>838,63=>580,64=>1000,65=>774,66=>762,67=>734,68=>830,69=>683,70=>683,71=>821,72=>837,73=>372,74=>372,75=>775,76=>637,77=>995,78=>837,79=>850,80=>733,81=>850,82=>770,83=>720,84=>682,85=>812,86=>774,87=>1103,88=>771,89=>724,90=>725,91=>457,92=>365,93=>457,94=>838,95=>500,96=>500,97=>675,98=>716,99=>593,100=>716,101=>678,102=>435,103=>716,104=>712,105=>343,106=>343,107=>665,108=>343,109=>1042,110=>712,111=>687,112=>716,113=>716,114=>493,115=>595,116=>478,117=>712,118=>652,119=>924,120=>645,121=>652,122=>582,123=>712,124=>365,125=>712,126=>838,160=>348,161=>456,162=>696,163=>696,164=>636,165=>696,166=>365,167=>500,168=>500,169=>1000,170=>564,171=>646,172=>838,173=>415,174=>1000,175=>500,176=>500,177=>838,178=>438,179=>438,180=>500,181=>736,182=>636,183=>380,184=>500,185=>438,186=>564,187=>646,188=>1035,189=>1035,190=>1035,191=>580,192=>774,193=>774,194=>774,195=>774,196=>774,197=>774,198=>1085,199=>734,200=>683,201=>683,202=>683,203=>683,204=>372,205=>372,206=>372,207=>372,208=>838,209=>837,210=>850,211=>850,212=>850,213=>850,214=>850,215=>838,216=>850,217=>812,218=>812,219=>812,220=>812,221=>724,222=>738,223=>719,224=>675,225=>675,226=>675,227=>675,228=>675,229=>675,230=>1048,231=>593,232=>678,233=>678,234=>678,235=>678,236=>343,237=>343,238=>343,239=>343,240=>687,241=>712,242=>687,243=>687,244=>687,245=>687,246=>687,247=>838,248=>687,249=>712,250=>712,251=>712,252=>712,253=>652,254=>716,255=>652,256=>774,257=>675,258=>774,259=>675,260=>774,261=>675,262=>734,263=>593,264=>734,265=>593,266=>734,267=>593,268=>734,269=>593,270=>830,271=>716,272=>838,273=>716,274=>683,275=>678,276=>683,277=>678,278=>683,279=>678,280=>683,281=>678,282=>683,283=>678,284=>821,285=>716,286=>821,287=>716,288=>821,289=>716,290=>821,291=>716,292=>837,293=>712,294=>974,295=>790,296=>372,297=>343,298=>372,299=>343,300=>372,301=>343,302=>372,303=>343,304=>372,305=>343,306=>744,307=>686,308=>372,309=>343,310=>775,311=>665,312=>665,313=>637,314=>343,315=>637,316=>343,317=>637,318=>479,319=>637,320=>557,321=>642,322=>371,323=>837,324=>712,325=>837,326=>712,327=>837,328=>712,329=>983,330=>837,331=>712,332=>850,333=>687,334=>850,335=>687,336=>850,337=>687,338=>1167,339=>1094,340=>770,341=>493,342=>770,343=>493,344=>770,345=>493,346=>720,347=>595,348=>720,349=>595,350=>720,351=>595,352=>720,353=>595,354=>682,355=>478,356=>682,357=>478,358=>682,359=>478,360=>812,361=>712,362=>812,363=>712,364=>812,365=>712,366=>812,367=>712,368=>812,369=>712,370=>812,371=>712,372=>1103,373=>924,374=>724,375=>652,376=>724,377=>725,378=>582,379=>725,380=>582,381=>725,382=>582,383=>435,384=>716,385=>811,386=>762,387=>716,388=>762,389=>716,390=>734,391=>734,392=>593,393=>838,394=>879,395=>757,396=>716,397=>688,398=>683,399=>849,400=>696,401=>683,402=>435,403=>821,404=>793,405=>1045,406=>436,407=>389,408=>775,409=>665,410=>360,411=>592,412=>1042,413=>837,414=>712,415=>850,416=>874,417=>687,418=>1083,419=>912,420=>782,421=>716,422=>770,423=>720,424=>595,425=>683,426=>552,427=>478,428=>707,429=>478,430=>682,431=>835,432=>712,433=>850,434=>813,435=>797,436=>778,437=>725,438=>582,439=>772,440=>772,441=>641,442=>582,443=>696,444=>772,445=>641,446=>573,447=>716,448=>372,449=>659,450=>544,451=>372,452=>1555,453=>1412,454=>1298,455=>1009,456=>980,457=>686,458=>1209,459=>1180,460=>1055,461=>774,462=>675,463=>372,464=>343,465=>850,466=>687,467=>812,468=>712,469=>812,470=>712,471=>812,472=>712,473=>812,474=>712,475=>812,476=>712,477=>678,478=>774,479=>675,480=>774,481=>675,482=>1085,483=>1048,484=>821,485=>716,486=>821,487=>716,488=>775,489=>665,490=>850,491=>687,492=>850,493=>687,494=>772,495=>582,496=>343,497=>1555,498=>1412,499=>1298,500=>821,501=>716,502=>1289,503=>787,504=>837,505=>712,506=>774,507=>675,508=>1085,509=>1048,510=>850,511=>687,512=>774,513=>675,514=>774,515=>675,516=>683,517=>678,518=>683,519=>678,520=>372,521=>343,522=>372,523=>343,524=>850,525=>687,526=>850,527=>687,528=>770,529=>493,530=>770,531=>493,532=>812,533=>712,534=>812,535=>712,536=>720,537=>595,538=>682,539=>478,540=>690,541=>607,542=>837,543=>712,544=>837,545=>865,546=>809,547=>659,548=>725,549=>582,550=>774,551=>675,552=>683,553=>678,554=>850,555=>687,556=>850,557=>687,558=>850,559=>687,560=>850,561=>687,562=>724,563=>652,564=>492,565=>867,566=>512,567=>343,568=>1088,569=>1088,570=>774,571=>734,572=>593,573=>637,574=>682,575=>595,576=>582,577=>782,578=>614,579=>762,580=>812,581=>774,582=>683,583=>678,584=>372,585=>343,586=>860,587=>791,588=>770,589=>493,590=>724,591=>652,592=>675,593=>716,594=>716,595=>716,596=>593,597=>593,598=>717,599=>792,600=>678,601=>678,602=>876,603=>557,604=>545,605=>815,606=>731,607=>343,608=>792,609=>716,610=>627,611=>644,612=>635,613=>712,614=>712,615=>712,616=>545,617=>440,618=>545,619=>559,620=>693,621=>343,622=>841,623=>1042,624=>1042,625=>1042,626=>712,627=>793,628=>707,629=>687,630=>909,631=>681,632=>796,633=>538,634=>538,635=>650,636=>493,637=>493,638=>596,639=>596,640=>642,641=>642,642=>595,643=>415,644=>435,645=>605,646=>552,647=>478,648=>478,649=>920,650=>772,651=>670,652=>652,653=>924,654=>652,655=>724,656=>694,657=>684,658=>641,659=>641,660=>573,661=>573,662=>573,663=>573,664=>850,665=>633,666=>731,667=>685,668=>691,669=>343,670=>732,671=>539,672=>792,673=>573,674=>573,675=>1156,676=>1214,677=>1155,678=>974,679=>769,680=>929,681=>1026,682=>792,683=>780,684=>591,685=>415,686=>677,687=>789,688=>456,689=>456,690=>219,691=>315,692=>315,693=>315,694=>411,695=>591,696=>417,697=>302,698=>521,699=>380,700=>380,701=>380,702=>366,703=>366,704=>326,705=>326,706=>500,707=>500,708=>500,709=>500,710=>500,711=>500,712=>306,713=>500,714=>500,715=>500,716=>306,717=>500,718=>500,719=>500,720=>337,721=>337,722=>366,723=>366,724=>500,725=>500,726=>416,727=>328,728=>500,729=>500,730=>500,731=>500,732=>500,733=>500,734=>351,735=>500,736=>412,737=>219,738=>381,739=>413,740=>326,741=>500,742=>500,743=>500,744=>500,745=>500,748=>500,749=>500,750=>657,755=>500,759=>500,768=>0,769=>0,770=>0,771=>0,772=>0,773=>0,774=>0,775=>0,776=>0,777=>0,778=>0,779=>0,780=>0,781=>0,782=>0,783=>0,784=>0,785=>0,786=>0,787=>0,788=>0,789=>0,790=>0,791=>0,792=>0,793=>0,794=>0,795=>0,796=>0,797=>0,798=>0,799=>0,800=>0,801=>0,802=>0,803=>0,804=>0,805=>0,806=>0,807=>0,808=>0,809=>0,810=>0,811=>0,812=>0,813=>0,814=>0,815=>0,816=>0,817=>0,818=>0,819=>0,820=>0,821=>0,822=>0,823=>0,824=>0,825=>0,826=>0,827=>0,828=>0,829=>0,830=>0,831=>0,832=>0,833=>0,834=>0,835=>0,836=>0,837=>0,838=>0,839=>0,840=>0,841=>0,842=>0,843=>0,844=>0,845=>0,846=>0,847=>0,849=>0,850=>0,851=>0,855=>0,856=>0,858=>0,860=>0,861=>0,862=>0,863=>0,864=>0,865=>0,866=>0,880=>698,881=>565,882=>1022,883=>836,884=>302,885=>302,886=>837,887=>701,890=>500,891=>593,892=>550,893=>549,894=>400,900=>441,901=>500,902=>797,903=>380,904=>846,905=>1009,906=>563,908=>891,910=>980,911=>894,912=>390,913=>774,914=>762,915=>637,916=>774,917=>683,918=>725,919=>837,920=>850,921=>372,922=>775,923=>774,924=>995,925=>837,926=>632,927=>850,928=>837,929=>733,931=>683,932=>682,933=>724,934=>850,935=>771,936=>850,937=>850,938=>372,939=>724,940=>687,941=>557,942=>712,943=>390,944=>675,945=>687,946=>716,947=>681,948=>687,949=>557,950=>591,951=>712,952=>687,953=>390,954=>710,955=>633,956=>736,957=>681,958=>591,959=>687,960=>791,961=>716,962=>593,963=>779,964=>638,965=>675,966=>782,967=>645,968=>794,969=>869,970=>390,971=>675,972=>687,973=>675,974=>869,975=>775,976=>651,977=>661,978=>746,979=>981,980=>746,981=>796,982=>869,983=>744,984=>850,985=>687,986=>734,987=>593,988=>683,989=>494,990=>702,991=>660,992=>919,993=>627,994=>1093,995=>837,996=>832,997=>716,998=>928,999=>744,1000=>733,1001=>650,1002=>789,1003=>671,1004=>752,1005=>716,1006=>682,1007=>590,1008=>744,1009=>716,1010=>593,1011=>343,1012=>850,1013=>645,1014=>644,1015=>738,1016=>716,1017=>734,1018=>995,1019=>732,1020=>716,1021=>698,1022=>734,1023=>698,1024=>683,1025=>683,1026=>878,1027=>637,1028=>734,1029=>720,1030=>372,1031=>372,1032=>372,1033=>1154,1034=>1130,1035=>878,1036=>817,1037=>837,1038=>771,1039=>837,1040=>774,1041=>762,1042=>762,1043=>637,1044=>891,1045=>683,1046=>1224,1047=>710,1048=>837,1049=>837,1050=>817,1051=>831,1052=>995,1053=>837,1054=>850,1055=>837,1056=>733,1057=>734,1058=>682,1059=>771,1060=>992,1061=>771,1062=>928,1063=>808,1064=>1235,1065=>1326,1066=>939,1067=>1036,1068=>762,1069=>734,1070=>1174,1071=>770,1072=>675,1073=>698,1074=>633,1075=>522,1076=>808,1077=>678,1078=>995,1079=>581,1080=>701,1081=>701,1082=>679,1083=>732,1084=>817,1085=>691,1086=>687,1087=>691,1088=>716,1089=>593,1090=>580,1091=>652,1092=>992,1093=>645,1094=>741,1095=>687,1096=>1062,1097=>1105,1098=>751,1099=>904,1100=>632,1101=>593,1102=>972,1103=>642,1104=>678,1105=>678,1106=>714,1107=>522,1108=>593,1109=>595,1110=>343,1111=>343,1112=>343,1113=>991,1114=>956,1115=>734,1116=>679,1117=>701,1118=>652,1119=>691,1120=>1093,1121=>869,1122=>840,1123=>736,1124=>1012,1125=>839,1126=>992,1127=>832,1128=>1358,1129=>1121,1130=>850,1131=>687,1132=>1236,1133=>1007,1134=>696,1135=>557,1136=>1075,1137=>1061,1138=>850,1139=>687,1140=>850,1141=>695,1142=>850,1143=>695,1144=>1148,1145=>1043,1146=>1074,1147=>863,1148=>1405,1149=>1173,1150=>1093,1151=>869,1152=>734,1153=>593,1154=>652,1155=>0,1156=>0,1157=>0,1158=>0,1159=>0,1160=>418,1161=>418,1162=>957,1163=>807,1164=>762,1165=>611,1166=>733,1167=>716,1168=>637,1169=>522,1170=>666,1171=>543,1172=>808,1173=>669,1174=>1224,1175=>995,1176=>710,1177=>581,1178=>775,1179=>679,1180=>817,1181=>679,1182=>817,1183=>679,1184=>1015,1185=>826,1186=>956,1187=>808,1188=>1103,1189=>874,1190=>1273,1191=>1017,1192=>952,1193=>858,1194=>734,1195=>593,1196=>682,1197=>580,1198=>724,1199=>652,1200=>724,1201=>652,1202=>771,1203=>645,1204=>1112,1205=>1000,1206=>808,1207=>687,1208=>808,1209=>687,1210=>808,1211=>712,1212=>1026,1213=>810,1214=>1026,1215=>810,1216=>372,1217=>1224,1218=>995,1219=>775,1220=>630,1221=>951,1222=>805,1223=>837,1224=>691,1225=>957,1226=>807,1227=>808,1228=>687,1229=>1115,1230=>933,1231=>343,1232=>774,1233=>675,1234=>774,1235=>675,1236=>1085,1237=>1048,1238=>683,1239=>678,1240=>849,1241=>678,1242=>849,1243=>678,1244=>1224,1245=>995,1246=>710,1247=>581,1248=>772,1249=>641,1250=>837,1251=>701,1252=>837,1253=>701,1254=>850,1255=>687,1256=>850,1257=>687,1258=>850,1259=>687,1260=>734,1261=>593,1262=>771,1263=>652,1264=>771,1265=>652,1266=>771,1267=>652,1268=>808,1269=>687,1270=>637,1271=>522,1272=>1036,1273=>904,1274=>666,1275=>543,1276=>771,1277=>645,1278=>771,1279=>645,1280=>762,1281=>608,1282=>1159,1283=>893,1284=>1119,1285=>920,1286=>828,1287=>693,1288=>1242,1289=>1017,1290=>1289,1291=>1013,1292=>839,1293=>638,1294=>938,1295=>803,1296=>696,1297=>557,1298=>831,1299=>732,1300=>1286,1301=>1068,1302=>1065,1303=>979,1304=>1082,1305=>1013,1306=>850,1307=>716,1308=>1103,1309=>924,1310=>817,1311=>679,1312=>1267,1313=>1059,1314=>1273,1315=>1017,1316=>957,1317=>807,1329=>813,1330=>729,1331=>728,1332=>731,1333=>729,1334=>733,1335=>652,1336=>720,1337=>903,1338=>728,1339=>666,1340=>558,1341=>961,1342=>788,1343=>713,1344=>651,1345=>730,1346=>715,1347=>704,1348=>780,1349=>689,1350=>715,1351=>708,1352=>731,1353=>677,1354=>867,1355=>711,1356=>780,1357=>731,1358=>715,1359=>693,1360=>666,1361=>698,1362=>576,1363=>833,1364=>698,1365=>763,1366=>855,1369=>330,1370=>342,1371=>308,1372=>374,1373=>313,1374=>461,1375=>468,1377=>938,1378=>642,1379=>704,1380=>708,1381=>642,1382=>644,1383=>565,1384=>642,1385=>756,1386=>704,1387=>643,1388=>310,1389=>984,1390=>638,1391=>643,1392=>643,1393=>603,1394=>643,1395=>642,1396=>643,1397=>309,1398=>643,1399=>486,1400=>643,1401=>366,1402=>938,1403=>573,1404=>666,1405=>643,1406=>643,1407=>934,1408=>643,1409=>643,1410=>479,1411=>934,1412=>648,1413=>620,1414=>813,1415=>812,1417=>360,1418=>374,1456=>0,1457=>0,1458=>0,1459=>0,1460=>0,1461=>0,1462=>0,1463=>0,1464=>0,1465=>0,1466=>0,1467=>0,1468=>0,1469=>0,1470=>415,1471=>0,1472=>372,1473=>0,1474=>0,1475=>372,1478=>497,1479=>0,1488=>728,1489=>610,1490=>447,1491=>588,1492=>687,1493=>343,1494=>400,1495=>687,1496=>679,1497=>294,1498=>578,1499=>566,1500=>605,1501=>696,1502=>724,1503=>343,1504=>453,1505=>680,1506=>666,1507=>675,1508=>658,1509=>661,1510=>653,1511=>736,1512=>602,1513=>758,1514=>683,1520=>664,1521=>567,1522=>519,1523=>444,1524=>710,1542=>667,1543=>667,1545=>884,1546=>1157,1548=>380,1557=>0,1563=>400,1567=>580,1569=>511,1570=>343,1571=>343,1572=>622,1573=>343,1574=>917,1575=>343,1576=>1005,1577=>590,1578=>1005,1579=>1005,1580=>721,1581=>721,1582=>721,1583=>513,1584=>513,1585=>576,1586=>576,1587=>1380,1588=>1380,1589=>1345,1590=>1345,1591=>1039,1592=>1039,1593=>683,1594=>683,1600=>342,1601=>1162,1602=>894,1603=>917,1604=>868,1605=>733,1606=>854,1607=>590,1608=>622,1609=>917,1610=>917,1611=>0,1612=>0,1613=>0,1614=>0,1615=>0,1616=>0,1617=>0,1618=>0,1619=>0,1620=>0,1621=>0,1623=>0,1626=>500,1632=>610,1633=>610,1634=>610,1635=>610,1636=>610,1637=>610,1638=>610,1639=>610,1640=>610,1641=>610,1642=>610,1643=>374,1644=>380,1645=>545,1646=>1005,1647=>894,1648=>0,1652=>292,1657=>1005,1658=>1005,1659=>1005,1660=>1005,1661=>1005,1662=>1005,1663=>1005,1664=>1005,1665=>721,1666=>721,1667=>721,1668=>721,1669=>721,1670=>721,1671=>721,1672=>445,1673=>445,1674=>445,1675=>445,1676=>445,1677=>445,1678=>445,1679=>445,1680=>445,1681=>576,1682=>576,1683=>576,1684=>576,1685=>681,1686=>576,1687=>576,1688=>576,1689=>576,1690=>1380,1691=>1380,1692=>1380,1693=>1345,1694=>1345,1695=>1039,1696=>683,1697=>1162,1698=>1162,1699=>1162,1700=>1162,1701=>1162,1702=>1162,1703=>894,1704=>894,1705=>1024,1706=>1271,1707=>1024,1708=>917,1709=>917,1710=>917,1711=>1024,1712=>1024,1713=>1024,1714=>1024,1715=>1024,1716=>1024,1717=>868,1718=>868,1719=>868,1720=>868,1721=>854,1722=>854,1723=>854,1724=>854,1725=>854,1726=>938,1727=>721,1734=>622,1740=>917,1742=>917,1749=>590,1776=>610,1777=>610,1778=>610,1779=>610,1780=>610,1781=>610,1782=>610,1783=>610,1784=>610,1785=>610,1984=>696,1985=>696,1986=>696,1987=>696,1988=>696,1989=>696,1990=>696,1991=>696,1992=>696,1993=>696,1994=>343,1995=>547,1996=>543,1997=>652,1998=>691,1999=>691,2000=>594,2001=>691,2002=>904,2003=>551,2004=>551,2005=>627,2006=>688,2007=>444,2008=>1022,2009=>506,2010=>826,2011=>691,2012=>652,2013=>912,2014=>627,2015=>707,2016=>506,2017=>652,2018=>574,2019=>627,2020=>627,2021=>627,2022=>574,2023=>574,2027=>0,2028=>0,2029=>0,2030=>0,2031=>0,2032=>0,2033=>0,2034=>0,2035=>0,2036=>380,2037=>380,2040=>691,2041=>691,2042=>415,3647=>696,3713=>790,3714=>748,3716=>749,3719=>569,3720=>742,3722=>744,3725=>761,3732=>706,3733=>704,3734=>747,3735=>819,3737=>730,3738=>727,3739=>727,3740=>922,3741=>827,3742=>866,3743=>866,3745=>836,3746=>761,3747=>770,3749=>769,3751=>713,3754=>827,3755=>1031,3757=>724,3758=>784,3759=>934,3760=>688,3761=>0,3762=>610,3763=>610,3764=>0,3765=>0,3766=>0,3767=>0,3768=>0,3769=>0,3771=>0,3772=>0,3773=>670,3776=>516,3777=>860,3778=>516,3779=>650,3780=>632,3782=>759,3784=>0,3785=>0,3786=>0,3787=>0,3788=>0,3789=>0,3792=>771,3793=>771,3794=>693,3795=>836,3796=>729,3797=>729,3798=>849,3799=>790,3800=>759,3801=>910,3804=>1363,3805=>1363,4256=>918,4257=>744,4258=>739,4259=>837,4260=>649,4261=>773,4262=>857,4263=>889,4264=>530,4265=>633,4266=>857,4267=>900,4268=>643,4269=>903,4270=>814,4271=>752,4272=>869,4273=>643,4274=>643,4275=>886,4276=>886,4277=>733,4278=>653,4279=>643,4280=>646,4281=>643,4282=>790,4283=>902,4284=>633,4285=>619,4286=>643,4287=>778,4288=>892,4289=>601,4290=>742,4291=>616,4292=>633,4293=>742,4304=>553,4305=>552,4306=>596,4307=>815,4308=>562,4309=>563,4310=>553,4311=>827,4312=>553,4313=>543,4314=>1074,4315=>563,4316=>563,4317=>812,4318=>552,4319=>591,4320=>822,4321=>563,4322=>690,4323=>583,4324=>813,4325=>562,4326=>813,4327=>563,4328=>563,4329=>563,4330=>632,4331=>563,4332=>563,4333=>552,4334=>563,4335=>563,4336=>558,4337=>604,4338=>552,4339=>552,4340=>553,4341=>605,4342=>852,4343=>635,4344=>563,4345=>596,4346=>542,4347=>684,4348=>368,5121=>774,5122=>774,5123=>774,5124=>774,5125=>905,5126=>905,5127=>905,5129=>905,5130=>905,5131=>905,5132=>1018,5133=>1009,5134=>1018,5135=>1009,5136=>1018,5137=>1009,5138=>1149,5139=>1140,5140=>1149,5141=>1140,5142=>905,5143=>1149,5144=>1142,5145=>1149,5146=>1142,5147=>905,5149=>310,5150=>529,5151=>425,5152=>425,5153=>395,5154=>395,5155=>395,5156=>395,5157=>564,5158=>470,5159=>310,5160=>395,5161=>395,5162=>395,5163=>1213,5164=>986,5165=>1216,5166=>1297,5167=>774,5168=>774,5169=>774,5170=>774,5171=>886,5172=>886,5173=>886,5175=>886,5176=>886,5177=>886,5178=>1018,5179=>1009,5180=>1018,5181=>1009,5182=>1018,5183=>1009,5184=>1149,5185=>1140,5186=>1149,5187=>1140,5188=>1149,5189=>1142,5190=>1149,5191=>1142,5192=>886,5193=>576,5194=>229,5196=>812,5197=>812,5198=>812,5199=>812,5200=>815,5201=>815,5202=>815,5204=>815,5205=>815,5206=>815,5207=>1056,5208=>1048,5209=>1056,5210=>1048,5211=>1056,5212=>1048,5213=>1060,5214=>1054,5215=>1060,5216=>1054,5217=>1060,5218=>1052,5219=>1060,5220=>1052,5221=>1060,5222=>483,5223=>1005,5224=>1005,5225=>1023,5226=>1017,5227=>743,5228=>743,5229=>743,5230=>743,5231=>743,5232=>743,5233=>743,5234=>743,5235=>743,5236=>1029,5237=>975,5238=>980,5239=>975,5240=>980,5241=>975,5242=>1029,5243=>975,5244=>1029,5245=>975,5246=>980,5247=>975,5248=>980,5249=>975,5250=>980,5251=>501,5252=>501,5253=>938,5254=>938,5255=>938,5256=>938,5257=>743,5258=>743,5259=>743,5260=>743,5261=>743,5262=>743,5263=>743,5264=>743,5265=>743,5266=>1029,5267=>975,5268=>1029,5269=>975,5270=>1029,5271=>975,5272=>1029,5273=>975,5274=>1029,5275=>975,5276=>1029,5277=>975,5278=>1029,5279=>975,5280=>1029,5281=>501,5282=>501,5283=>626,5284=>626,5285=>626,5286=>626,5287=>626,5288=>626,5289=>626,5290=>626,5291=>626,5292=>881,5293=>854,5294=>863,5295=>874,5296=>863,5297=>874,5298=>881,5299=>874,5300=>881,5301=>874,5302=>863,5303=>874,5304=>863,5305=>874,5306=>863,5307=>436,5308=>548,5309=>436,5312=>988,5313=>988,5314=>988,5315=>988,5316=>931,5317=>931,5318=>931,5319=>931,5320=>931,5321=>1238,5322=>1247,5323=>1200,5324=>1228,5325=>1200,5326=>1228,5327=>931,5328=>660,5329=>497,5330=>660,5331=>988,5332=>988,5333=>988,5334=>988,5335=>931,5336=>931,5337=>931,5338=>931,5339=>931,5340=>1231,5341=>1247,5342=>1283,5343=>1228,5344=>1283,5345=>1228,5346=>1228,5347=>1214,5348=>1228,5349=>1214,5350=>1283,5351=>1228,5352=>1283,5353=>1228,5354=>660,5356=>886,5357=>730,5358=>730,5359=>730,5360=>730,5361=>730,5362=>730,5363=>730,5364=>730,5365=>730,5366=>998,5367=>958,5368=>967,5369=>989,5370=>967,5371=>989,5372=>998,5373=>958,5374=>998,5375=>958,5376=>967,5377=>989,5378=>967,5379=>989,5380=>967,5381=>493,5382=>460,5383=>493,5392=>923,5393=>923,5394=>923,5395=>1136,5396=>1136,5397=>1136,5398=>1136,5399=>1209,5400=>1202,5401=>1209,5402=>1202,5403=>1209,5404=>1202,5405=>1431,5406=>1420,5407=>1431,5408=>1420,5409=>1431,5410=>1420,5411=>1431,5412=>1420,5413=>746,5414=>776,5415=>776,5416=>776,5417=>776,5418=>776,5419=>776,5420=>776,5421=>776,5422=>776,5423=>1003,5424=>1003,5425=>1013,5426=>996,5427=>1013,5428=>996,5429=>1003,5430=>1003,5431=>1003,5432=>1003,5433=>1013,5434=>996,5435=>1013,5436=>996,5437=>1013,5438=>495,5440=>395,5441=>510,5442=>1033,5443=>1033,5444=>976,5445=>976,5446=>976,5447=>976,5448=>733,5449=>733,5450=>733,5451=>733,5452=>733,5453=>733,5454=>1003,5455=>959,5456=>495,5458=>886,5459=>774,5460=>774,5461=>774,5462=>774,5463=>928,5464=>928,5465=>928,5466=>928,5467=>1172,5468=>1142,5469=>602,5470=>812,5471=>812,5472=>812,5473=>812,5474=>812,5475=>812,5476=>815,5477=>815,5478=>815,5479=>815,5480=>1060,5481=>1052,5482=>548,5492=>977,5493=>977,5494=>977,5495=>977,5496=>977,5497=>977,5498=>977,5499=>618,5500=>837,5501=>510,5502=>1238,5503=>1238,5504=>1238,5505=>1238,5506=>1238,5507=>1238,5508=>1238,5509=>989,5514=>977,5515=>977,5516=>977,5517=>977,5518=>1591,5519=>1591,5520=>1591,5521=>1295,5522=>1295,5523=>1591,5524=>1591,5525=>848,5526=>1273,5536=>988,5537=>988,5538=>931,5539=>931,5540=>931,5541=>931,5542=>660,5543=>776,5544=>776,5545=>776,5546=>776,5547=>776,5548=>776,5549=>776,5550=>495,5551=>743,5598=>830,5601=>830,5702=>496,5703=>496,5742=>413,5743=>1238,5744=>1591,5745=>2016,5746=>2016,5747=>1720,5748=>1678,5749=>2016,5750=>2016,5760=>543,5761=>637,5762=>945,5763=>1254,5764=>1563,5765=>1871,5766=>627,5767=>936,5768=>1254,5769=>1559,5770=>1871,5771=>569,5772=>877,5773=>1187,5774=>1497,5775=>1807,5776=>637,5777=>945,5778=>1240,5779=>1555,5780=>1871,5781=>569,5782=>569,5783=>789,5784=>1234,5785=>1559,5786=>740,5787=>638,5788=>638,7424=>652,7425=>833,7426=>1048,7427=>608,7428=>593,7429=>676,7430=>676,7431=>559,7432=>557,7433=>343,7434=>494,7435=>665,7436=>539,7437=>817,7438=>701,7439=>687,7440=>593,7441=>660,7442=>660,7443=>660,7444=>1094,7446=>687,7447=>687,7448=>556,7449=>642,7450=>642,7451=>580,7452=>634,7453=>737,7454=>948,7455=>695,7456=>652,7457=>924,7458=>582,7459=>646,7462=>539,7463=>652,7464=>691,7465=>556,7466=>781,7467=>732,7468=>487,7469=>683,7470=>480,7472=>523,7473=>430,7474=>430,7475=>517,7476=>527,7477=>234,7478=>234,7479=>488,7480=>401,7481=>626,7482=>527,7483=>527,7484=>535,7485=>509,7486=>461,7487=>485,7488=>430,7489=>511,7490=>695,7491=>458,7492=>458,7493=>479,7494=>712,7495=>479,7496=>479,7497=>479,7498=>479,7499=>386,7500=>386,7501=>479,7502=>219,7503=>487,7504=>664,7505=>456,7506=>488,7507=>414,7508=>488,7509=>488,7510=>479,7511=>388,7512=>456,7513=>462,7514=>664,7515=>501,7517=>451,7518=>429,7519=>433,7520=>493,7521=>406,7522=>219,7523=>315,7524=>456,7525=>501,7526=>451,7527=>429,7528=>451,7529=>493,7530=>406,7543=>716,7544=>527,7547=>545,7549=>747,7557=>514,7579=>479,7580=>414,7581=>414,7582=>488,7583=>386,7584=>377,7585=>348,7586=>479,7587=>456,7588=>347,7589=>281,7590=>347,7591=>347,7592=>431,7593=>326,7594=>330,7595=>370,7596=>664,7597=>664,7598=>562,7599=>562,7600=>448,7601=>488,7602=>542,7603=>422,7604=>396,7605=>388,7606=>583,7607=>494,7608=>399,7609=>451,7610=>501,7611=>417,7612=>523,7613=>470,7614=>455,7615=>425,7620=>0,7621=>0,7622=>0,7623=>0,7624=>0,7625=>0,7680=>774,7681=>675,7682=>762,7683=>716,7684=>762,7685=>716,7686=>762,7687=>716,7688=>734,7689=>593,7690=>830,7691=>716,7692=>830,7693=>716,7694=>830,7695=>716,7696=>830,7697=>716,7698=>830,7699=>716,7700=>683,7701=>678,7702=>683,7703=>678,7704=>683,7705=>678,7706=>683,7707=>678,7708=>683,7709=>678,7710=>683,7711=>435,7712=>821,7713=>716,7714=>837,7715=>712,7716=>837,7717=>712,7718=>837,7719=>712,7720=>837,7721=>712,7722=>837,7723=>712,7724=>372,7725=>343,7726=>372,7727=>343,7728=>775,7729=>665,7730=>775,7731=>665,7732=>775,7733=>665,7734=>637,7735=>343,7736=>637,7737=>343,7738=>637,7739=>343,7740=>637,7741=>343,7742=>995,7743=>1042,7744=>995,7745=>1042,7746=>995,7747=>1042,7748=>837,7749=>712,7750=>837,7751=>712,7752=>837,7753=>712,7754=>837,7755=>712,7756=>850,7757=>687,7758=>850,7759=>687,7760=>850,7761=>687,7762=>850,7763=>687,7764=>733,7765=>716,7766=>733,7767=>716,7768=>770,7769=>493,7770=>770,7771=>493,7772=>770,7773=>493,7774=>770,7775=>493,7776=>720,7777=>595,7778=>720,7779=>595,7780=>720,7781=>595,7782=>720,7783=>595,7784=>720,7785=>595,7786=>682,7787=>478,7788=>682,7789=>478,7790=>682,7791=>478,7792=>682,7793=>478,7794=>812,7795=>712,7796=>812,7797=>712,7798=>812,7799=>712,7800=>812,7801=>712,7802=>812,7803=>712,7804=>774,7805=>652,7806=>774,7807=>652,7808=>1103,7809=>924,7810=>1103,7811=>924,7812=>1103,7813=>924,7814=>1103,7815=>924,7816=>1103,7817=>924,7818=>771,7819=>645,7820=>771,7821=>645,7822=>724,7823=>652,7824=>725,7825=>582,7826=>725,7827=>582,7828=>725,7829=>582,7830=>712,7831=>478,7832=>924,7833=>652,7834=>675,7835=>435,7836=>435,7837=>435,7838=>896,7839=>687,7840=>774,7841=>675,7842=>774,7843=>675,7844=>774,7845=>675,7846=>774,7847=>675,7848=>774,7849=>675,7850=>774,7851=>675,7852=>774,7853=>675,7854=>774,7855=>675,7856=>774,7857=>675,7858=>774,7859=>675,7860=>774,7861=>675,7862=>774,7863=>675,7864=>683,7865=>678,7866=>683,7867=>678,7868=>683,7869=>678,7870=>683,7871=>678,7872=>683,7873=>678,7874=>683,7875=>678,7876=>683,7877=>678,7878=>683,7879=>678,7880=>372,7881=>343,7882=>372,7883=>343,7884=>850,7885=>687,7886=>850,7887=>687,7888=>850,7889=>687,7890=>850,7891=>687,7892=>850,7893=>687,7894=>850,7895=>687,7896=>850,7897=>687,7898=>874,7899=>687,7900=>874,7901=>687,7902=>874,7903=>687,7904=>874,7905=>687,7906=>874,7907=>687,7908=>812,7909=>712,7910=>812,7911=>712,7912=>835,7913=>712,7914=>835,7915=>712,7916=>835,7917=>712,7918=>835,7919=>712,7920=>835,7921=>712,7922=>724,7923=>652,7924=>724,7925=>652,7926=>724,7927=>652,7928=>724,7929=>652,7930=>953,7931=>644,7936=>687,7937=>687,7938=>687,7939=>687,7940=>687,7941=>687,7942=>687,7943=>687,7944=>774,7945=>774,7946=>1041,7947=>1043,7948=>935,7949=>963,7950=>835,7951=>859,7952=>557,7953=>557,7954=>557,7955=>557,7956=>557,7957=>557,7960=>792,7961=>794,7962=>1100,7963=>1096,7964=>1023,7965=>1052,7968=>712,7969=>712,7970=>712,7971=>712,7972=>712,7973=>712,7974=>712,7975=>712,7976=>945,7977=>951,7978=>1250,7979=>1250,7980=>1180,7981=>1206,7982=>1054,7983=>1063,7984=>390,7985=>390,7986=>390,7987=>390,7988=>390,7989=>390,7990=>390,7991=>390,7992=>483,7993=>489,7994=>777,7995=>785,7996=>712,7997=>738,7998=>604,7999=>604,8000=>687,8001=>687,8002=>687,8003=>687,8004=>687,8005=>687,8008=>892,8009=>933,8010=>1221,8011=>1224,8012=>1053,8013=>1082,8016=>675,8017=>675,8018=>675,8019=>675,8020=>675,8021=>675,8022=>675,8023=>675,8025=>930,8027=>1184,8029=>1199,8031=>1049,8032=>869,8033=>869,8034=>869,8035=>869,8036=>869,8037=>869,8038=>869,8039=>869,8040=>909,8041=>958,8042=>1246,8043=>1251,8044=>1076,8045=>1105,8046=>1028,8047=>1076,8048=>687,8049=>687,8050=>557,8051=>557,8052=>712,8053=>712,8054=>390,8055=>390,8056=>687,8057=>687,8058=>675,8059=>675,8060=>869,8061=>869,8064=>687,8065=>687,8066=>687,8067=>687,8068=>687,8069=>687,8070=>687,8071=>687,8072=>774,8073=>774,8074=>1041,8075=>1043,8076=>935,8077=>963,8078=>835,8079=>859,8080=>712,8081=>712,8082=>712,8083=>712,8084=>712,8085=>712,8086=>712,8087=>712,8088=>945,8089=>951,8090=>1250,8091=>1250,8092=>1180,8093=>1206,8094=>1054,8095=>1063,8096=>869,8097=>869,8098=>869,8099=>869,8100=>869,8101=>869,8102=>869,8103=>869,8104=>909,8105=>958,8106=>1246,8107=>1251,8108=>1076,8109=>1105,8110=>1028,8111=>1076,8112=>687,8113=>687,8114=>687,8115=>687,8116=>687,8118=>687,8119=>687,8120=>774,8121=>774,8122=>876,8123=>797,8124=>774,8125=>500,8126=>500,8127=>500,8128=>500,8129=>500,8130=>712,8131=>712,8132=>712,8134=>712,8135=>712,8136=>929,8137=>846,8138=>1080,8139=>1009,8140=>837,8141=>500,8142=>500,8143=>500,8144=>390,8145=>390,8146=>390,8147=>390,8150=>390,8151=>390,8152=>372,8153=>372,8154=>621,8155=>563,8157=>500,8158=>500,8159=>500,8160=>675,8161=>675,8162=>675,8163=>675,8164=>716,8165=>716,8166=>675,8167=>675,8168=>724,8169=>724,8170=>1020,8171=>980,8172=>838,8173=>500,8174=>500,8175=>500,8178=>869,8179=>869,8180=>869,8182=>869,8183=>869,8184=>1065,8185=>891,8186=>1084,8187=>894,8188=>850,8189=>500,8190=>500,8192=>500,8193=>1000,8194=>500,8195=>1000,8196=>330,8197=>250,8198=>167,8199=>696,8200=>380,8201=>200,8202=>100,8203=>0,8204=>0,8205=>0,8206=>0,8207=>0,8208=>415,8209=>415,8210=>696,8211=>500,8212=>1000,8213=>1000,8214=>500,8215=>500,8216=>380,8217=>380,8218=>380,8219=>380,8220=>657,8221=>657,8222=>657,8223=>657,8224=>500,8225=>500,8226=>639,8227=>639,8228=>333,8229=>667,8230=>1000,8231=>348,8232=>0,8233=>0,8234=>0,8235=>0,8236=>0,8237=>0,8238=>0,8239=>200,8240=>1440,8241=>1887,8242=>264,8243=>447,8244=>630,8245=>264,8246=>447,8247=>630,8248=>733,8249=>412,8250=>412,8251=>972,8252=>627,8253=>580,8254=>500,8255=>828,8256=>828,8257=>329,8258=>1023,8259=>500,8260=>167,8261=>457,8262=>457,8263=>1030,8264=>829,8265=>829,8266=>513,8267=>636,8268=>500,8269=>500,8270=>523,8271=>400,8272=>828,8273=>523,8274=>556,8275=>1000,8276=>828,8277=>838,8278=>684,8279=>813,8280=>838,8281=>838,8282=>380,8283=>872,8284=>838,8285=>380,8286=>380,8287=>222,8288=>0,8289=>0,8290=>0,8291=>0,8292=>0,8298=>0,8299=>0,8300=>0,8301=>0,8302=>0,8303=>0,8304=>438,8305=>219,8308=>438,8309=>438,8310=>438,8311=>438,8312=>438,8313=>438,8314=>528,8315=>528,8316=>528,8317=>288,8318=>288,8319=>456,8320=>438,8321=>438,8322=>438,8323=>438,8324=>438,8325=>438,8326=>438,8327=>438,8328=>438,8329=>438,8330=>528,8331=>528,8332=>528,8333=>288,8334=>288,8336=>458,8337=>479,8338=>488,8339=>413,8340=>479,8341=>456,8342=>487,8343=>219,8344=>664,8345=>456,8346=>479,8347=>381,8348=>388,8352=>929,8353=>696,8354=>696,8355=>696,8356=>696,8357=>1042,8358=>837,8359=>1518,8360=>1205,8361=>1103,8362=>904,8363=>696,8364=>696,8365=>696,8366=>696,8367=>1392,8368=>696,8369=>696,8370=>696,8371=>696,8372=>859,8373=>696,8376=>696,8377=>696,8400=>0,8401=>0,8406=>0,8407=>0,8411=>0,8412=>0,8417=>0,8448=>1120,8449=>1170,8450=>734,8451=>1211,8452=>896,8453=>1091,8454=>1144,8455=>614,8456=>698,8457=>1086,8459=>1073,8460=>913,8461=>888,8462=>712,8463=>712,8464=>597,8465=>697,8466=>856,8467=>472,8468=>974,8469=>837,8470=>1203,8471=>1000,8472=>697,8473=>750,8474=>850,8475=>938,8476=>814,8477=>801,8478=>896,8479=>710,8480=>1020,8481=>1281,8482=>1000,8483=>755,8484=>754,8485=>578,8486=>850,8487=>850,8488=>763,8489=>338,8490=>775,8491=>774,8492=>928,8493=>818,8494=>854,8495=>636,8496=>729,8497=>808,8498=>683,8499=>1184,8500=>465,8501=>794,8502=>731,8503=>494,8504=>684,8505=>380,8506=>945,8507=>1348,8508=>790,8509=>737,8510=>654,8511=>863,8512=>840,8513=>775,8514=>557,8515=>637,8516=>760,8517=>830,8518=>716,8519=>678,8520=>343,8521=>343,8523=>872,8526=>547,8528=>1035,8529=>1035,8530=>1483,8531=>1035,8532=>1035,8533=>1035,8534=>1035,8535=>1035,8536=>1035,8537=>1035,8538=>1035,8539=>1035,8540=>1035,8541=>1035,8542=>1035,8543=>615,8544=>372,8545=>659,8546=>945,8547=>1099,8548=>774,8549=>1099,8550=>1386,8551=>1672,8552=>1121,8553=>771,8554=>1120,8555=>1407,8556=>637,8557=>734,8558=>830,8559=>995,8560=>343,8561=>607,8562=>872,8563=>984,8564=>652,8565=>962,8566=>1227,8567=>1491,8568=>969,8569=>645,8570=>969,8571=>1233,8572=>343,8573=>593,8574=>716,8575=>1042,8576=>1289,8577=>830,8578=>1289,8579=>734,8580=>593,8581=>734,8585=>1035,8592=>838,8593=>838,8594=>838,8595=>838,8596=>838,8597=>838,8598=>838,8599=>838,8600=>838,8601=>838,8602=>838,8603=>838,8604=>838,8605=>838,8606=>838,8607=>838,8608=>838,8609=>838,8610=>838,8611=>838,8612=>838,8613=>838,8614=>838,8615=>838,8616=>838,8617=>838,8618=>838,8619=>838,8620=>838,8621=>838,8622=>838,8623=>838,8624=>838,8625=>838,8626=>838,8627=>838,8628=>838,8629=>838,8630=>838,8631=>838,8632=>838,8633=>838,8634=>838,8635=>838,8636=>838,8637=>838,8638=>838,8639=>838,8640=>838,8641=>838,8642=>838,8643=>838,8644=>838,8645=>838,8646=>838,8647=>838,8648=>838,8649=>838,8650=>838,8651=>838,8652=>838,8653=>838,8654=>838,8655=>838,8656=>838,8657=>838,8658=>838,8659=>838,8660=>838,8661=>838,8662=>838,8663=>838,8664=>838,8665=>838,8666=>838,8667=>838,8668=>838,8669=>838,8670=>838,8671=>838,8672=>838,8673=>838,8674=>838,8675=>838,8676=>838,8677=>838,8678=>838,8679=>838,8680=>838,8681=>838,8682=>838,8683=>838,8684=>838,8685=>838,8686=>838,8687=>838,8688=>838,8689=>838,8690=>838,8691=>838,8692=>838,8693=>838,8694=>838,8695=>838,8696=>838,8697=>838,8698=>838,8699=>838,8700=>838,8701=>838,8702=>838,8703=>838,8704=>774,8705=>696,8706=>544,8707=>683,8708=>683,8709=>856,8710=>697,8711=>697,8712=>896,8713=>896,8714=>750,8715=>896,8716=>896,8717=>750,8718=>636,8719=>787,8720=>787,8721=>718,8722=>838,8723=>838,8724=>696,8725=>365,8726=>696,8727=>838,8728=>626,8729=>380,8730=>667,8731=>667,8732=>667,8733=>712,8734=>833,8735=>838,8736=>896,8737=>896,8738=>838,8739=>500,8740=>500,8741=>500,8742=>500,8743=>812,8744=>812,8745=>812,8746=>812,8747=>610,8748=>929,8749=>1295,8750=>563,8751=>977,8752=>1313,8753=>563,8754=>563,8755=>563,8756=>696,8757=>696,8758=>294,8759=>696,8760=>838,8761=>838,8762=>838,8763=>838,8764=>838,8765=>838,8766=>838,8767=>838,8768=>375,8769=>838,8770=>838,8771=>838,8772=>838,8773=>838,8774=>838,8775=>838,8776=>838,8777=>838,8778=>838,8779=>838,8780=>838,8781=>838,8782=>838,8783=>838,8784=>838,8785=>838,8786=>838,8787=>838,8788=>1063,8789=>1063,8790=>838,8791=>838,8792=>838,8793=>838,8794=>838,8795=>838,8796=>838,8797=>838,8798=>838,8799=>838,8800=>838,8801=>838,8802=>838,8803=>838,8804=>838,8805=>838,8806=>838,8807=>838,8808=>841,8809=>841,8810=>1047,8811=>1047,8812=>500,8813=>838,8814=>838,8815=>838,8816=>838,8817=>838,8818=>838,8819=>838,8820=>838,8821=>838,8822=>838,8823=>838,8824=>838,8825=>838,8826=>838,8827=>838,8828=>838,8829=>838,8830=>838,8831=>838,8832=>838,8833=>838,8834=>838,8835=>838,8836=>838,8837=>838,8838=>838,8839=>838,8840=>838,8841=>838,8842=>838,8843=>838,8844=>812,8845=>812,8846=>812,8847=>838,8848=>838,8849=>838,8850=>838,8851=>796,8852=>796,8853=>838,8854=>838,8855=>838,8856=>838,8857=>838,8858=>838,8859=>838,8860=>838,8861=>838,8862=>838,8863=>838,8864=>838,8865=>838,8866=>914,8867=>914,8868=>914,8869=>914,8870=>542,8871=>542,8872=>914,8873=>914,8874=>914,8875=>914,8876=>914,8877=>914,8878=>914,8879=>914,8880=>838,8881=>838,8882=>838,8883=>838,8884=>838,8885=>838,8886=>1000,8887=>1000,8888=>838,8889=>838,8890=>542,8891=>812,8892=>812,8893=>812,8894=>838,8895=>838,8896=>843,8897=>843,8898=>843,8899=>843,8900=>494,8901=>380,8902=>626,8903=>838,8904=>1000,8905=>1000,8906=>1000,8907=>1000,8908=>1000,8909=>838,8910=>812,8911=>812,8912=>838,8913=>838,8914=>838,8915=>838,8916=>838,8917=>838,8918=>838,8919=>838,8920=>1422,8921=>1422,8922=>838,8923=>838,8924=>838,8925=>838,8926=>838,8927=>838,8928=>838,8929=>838,8930=>838,8931=>838,8932=>838,8933=>838,8934=>838,8935=>838,8936=>838,8937=>838,8938=>838,8939=>838,8940=>838,8941=>838,8942=>1000,8943=>1000,8944=>1000,8945=>1000,8946=>1158,8947=>896,8948=>750,8949=>896,8950=>896,8951=>750,8952=>896,8953=>896,8954=>1158,8955=>896,8956=>750,8957=>896,8958=>750,8959=>896,8960=>602,8961=>602,8962=>716,8963=>838,8964=>838,8965=>838,8966=>838,8967=>488,8968=>457,8969=>457,8970=>457,8971=>457,8972=>809,8973=>809,8974=>809,8975=>809,8976=>838,8977=>539,8984=>928,8985=>838,8988=>469,8989=>469,8990=>469,8991=>469,8992=>610,8993=>610,8996=>1152,8997=>1152,8998=>1414,8999=>1152,9000=>1443,9003=>1414,9004=>873,9075=>390,9076=>716,9077=>869,9082=>687,9085=>863,9095=>1152,9108=>873,9115=>500,9116=>500,9117=>500,9118=>500,9119=>500,9120=>500,9121=>500,9122=>500,9123=>500,9124=>500,9125=>500,9126=>500,9127=>750,9128=>750,9129=>750,9130=>750,9131=>750,9132=>750,9133=>750,9134=>610,9166=>838,9167=>945,9187=>873,9189=>769,9192=>696,9250=>716,9251=>716,9312=>847,9313=>847,9314=>847,9315=>847,9316=>847,9317=>847,9318=>847,9319=>847,9320=>847,9321=>847,9600=>769,9601=>769,9602=>769,9603=>769,9604=>769,9605=>769,9606=>769,9607=>769,9608=>769,9609=>769,9610=>769,9611=>769,9612=>769,9613=>769,9614=>769,9615=>769,9616=>769,9617=>769,9618=>769,9619=>769,9620=>769,9621=>769,9622=>769,9623=>769,9624=>769,9625=>769,9626=>769,9627=>769,9628=>769,9629=>769,9630=>769,9631=>769,9632=>945,9633=>945,9634=>945,9635=>945,9636=>945,9637=>945,9638=>945,9639=>945,9640=>945,9641=>945,9642=>678,9643=>678,9644=>945,9645=>945,9646=>550,9647=>550,9648=>769,9649=>769,9650=>769,9651=>769,9652=>502,9653=>502,9654=>769,9655=>769,9656=>502,9657=>502,9658=>769,9659=>769,9660=>769,9661=>769,9662=>502,9663=>502,9664=>769,9665=>769,9666=>502,9667=>502,9668=>769,9669=>769,9670=>769,9671=>769,9672=>769,9673=>873,9674=>494,9675=>873,9676=>873,9677=>873,9678=>873,9679=>873,9680=>873,9681=>873,9682=>873,9683=>873,9684=>873,9685=>873,9686=>527,9687=>527,9688=>840,9689=>970,9690=>970,9691=>970,9692=>387,9693=>387,9694=>387,9695=>387,9696=>769,9697=>769,9698=>769,9699=>769,9700=>769,9701=>769,9702=>639,9703=>945,9704=>945,9705=>945,9706=>945,9707=>945,9708=>769,9709=>769,9710=>769,9711=>1119,9712=>945,9713=>945,9714=>945,9715=>945,9716=>873,9717=>873,9718=>873,9719=>873,9720=>769,9721=>769,9722=>769,9723=>830,9724=>830,9725=>732,9726=>732,9727=>769,9728=>896,9729=>1000,9730=>896,9731=>896,9732=>896,9733=>896,9734=>896,9735=>573,9736=>896,9737=>896,9738=>888,9739=>888,9740=>671,9741=>1013,9742=>1246,9743=>1250,9744=>896,9745=>896,9746=>896,9747=>532,9748=>896,9749=>896,9750=>896,9751=>896,9752=>896,9753=>896,9754=>896,9755=>896,9756=>896,9757=>609,9758=>896,9759=>609,9760=>896,9761=>896,9762=>896,9763=>896,9764=>669,9765=>746,9766=>649,9767=>784,9768=>545,9769=>896,9770=>896,9771=>896,9772=>710,9773=>896,9774=>896,9775=>896,9776=>896,9777=>896,9778=>896,9779=>896,9780=>896,9781=>896,9782=>896,9783=>896,9784=>896,9785=>1042,9786=>1042,9787=>1042,9788=>896,9789=>896,9790=>896,9791=>614,9792=>732,9793=>732,9794=>896,9795=>896,9796=>896,9797=>896,9798=>896,9799=>896,9800=>896,9801=>896,9802=>896,9803=>896,9804=>896,9805=>896,9806=>896,9807=>896,9808=>896,9809=>896,9810=>896,9811=>896,9812=>896,9813=>896,9814=>896,9815=>896,9816=>896,9817=>896,9818=>896,9819=>896,9820=>896,9821=>896,9822=>896,9823=>896,9824=>896,9825=>896,9826=>896,9827=>896,9828=>896,9829=>896,9830=>896,9831=>896,9832=>896,9833=>472,9834=>638,9835=>896,9836=>896,9837=>472,9838=>357,9839=>484,9840=>748,9841=>766,9842=>896,9843=>896,9844=>896,9845=>896,9846=>896,9847=>896,9848=>896,9849=>896,9850=>896,9851=>896,9852=>896,9853=>896,9854=>896,9855=>896,9856=>869,9857=>869,9858=>869,9859=>869,9860=>869,9861=>869,9862=>896,9863=>896,9864=>896,9865=>896,9866=>896,9867=>896,9868=>896,9869=>896,9870=>896,9871=>896,9872=>896,9873=>896,9874=>896,9875=>896,9876=>896,9877=>541,9878=>896,9879=>896,9880=>896,9881=>896,9882=>896,9883=>896,9884=>896,9888=>896,9889=>702,9890=>1004,9891=>1089,9892=>1175,9893=>903,9894=>838,9895=>838,9896=>838,9897=>838,9898=>838,9899=>838,9900=>838,9901=>838,9902=>838,9903=>838,9904=>844,9905=>838,9906=>732,9907=>732,9908=>732,9909=>732,9910=>850,9911=>732,9912=>732,9920=>838,9921=>838,9922=>838,9923=>838,9954=>732,9985=>838,9986=>838,9987=>838,9988=>838,9990=>838,9991=>838,9992=>838,9993=>838,9996=>838,9997=>838,9998=>838,9999=>838,10000=>838,10001=>838,10002=>838,10003=>838,10004=>838,10005=>838,10006=>838,10007=>838,10008=>838,10009=>838,10010=>838,10011=>838,10012=>838,10013=>838,10014=>838,10015=>838,10016=>838,10017=>838,10018=>838,10019=>838,10020=>838,10021=>838,10022=>838,10023=>838,10025=>838,10026=>838,10027=>838,10028=>838,10029=>838,10030=>838,10031=>838,10032=>838,10033=>838,10034=>838,10035=>838,10036=>838,10037=>838,10038=>838,10039=>838,10040=>838,10041=>838,10042=>838,10043=>838,10044=>838,10045=>838,10046=>838,10047=>838,10048=>838,10049=>838,10050=>838,10051=>838,10052=>838,10053=>838,10054=>838,10055=>838,10056=>838,10057=>838,10058=>838,10059=>838,10061=>896,10063=>896,10064=>896,10065=>896,10066=>896,10070=>896,10072=>838,10073=>838,10074=>838,10075=>347,10076=>347,10077=>587,10078=>587,10081=>838,10082=>838,10083=>838,10084=>838,10085=>838,10086=>838,10087=>838,10088=>838,10089=>838,10090=>838,10091=>838,10092=>838,10093=>838,10094=>838,10095=>838,10096=>838,10097=>838,10098=>838,10099=>838,10100=>838,10101=>838,10102=>847,10103=>847,10104=>847,10105=>847,10106=>847,10107=>847,10108=>847,10109=>847,10110=>847,10111=>847,10112=>838,10113=>838,10114=>838,10115=>838,10116=>838,10117=>838,10118=>838,10119=>838,10120=>838,10121=>838,10122=>838,10123=>838,10124=>838,10125=>838,10126=>838,10127=>838,10128=>838,10129=>838,10130=>838,10131=>838,10132=>838,10136=>838,10137=>838,10138=>838,10139=>838,10140=>838,10141=>838,10142=>838,10143=>838,10144=>838,10145=>838,10146=>838,10147=>838,10148=>838,10149=>838,10150=>838,10151=>838,10152=>838,10153=>838,10154=>838,10155=>838,10156=>838,10157=>838,10158=>838,10159=>838,10161=>838,10162=>838,10163=>838,10164=>838,10165=>838,10166=>838,10167=>838,10168=>838,10169=>838,10170=>838,10171=>838,10172=>838,10173=>838,10174=>838,10181=>457,10182=>457,10208=>494,10214=>487,10215=>487,10216=>457,10217=>457,10218=>721,10219=>721,10224=>838,10225=>838,10226=>838,10227=>838,10228=>1157,10229=>1434,10230=>1434,10231=>1434,10232=>1434,10233=>1434,10234=>1434,10235=>1434,10236=>1434,10237=>1434,10238=>1434,10239=>1434,10240=>781,10241=>781,10242=>781,10243=>781,10244=>781,10245=>781,10246=>781,10247=>781,10248=>781,10249=>781,10250=>781,10251=>781,10252=>781,10253=>781,10254=>781,10255=>781,10256=>781,10257=>781,10258=>781,10259=>781,10260=>781,10261=>781,10262=>781,10263=>781,10264=>781,10265=>781,10266=>781,10267=>781,10268=>781,10269=>781,10270=>781,10271=>781,10272=>781,10273=>781,10274=>781,10275=>781,10276=>781,10277=>781,10278=>781,10279=>781,10280=>781,10281=>781,10282=>781,10283=>781,10284=>781,10285=>781,10286=>781,10287=>781,10288=>781,10289=>781,10290=>781,10291=>781,10292=>781,10293=>781,10294=>781,10295=>781,10296=>781,10297=>781,10298=>781,10299=>781,10300=>781,10301=>781,10302=>781,10303=>781,10304=>781,10305=>781,10306=>781,10307=>781,10308=>781,10309=>781,10310=>781,10311=>781,10312=>781,10313=>781,10314=>781,10315=>781,10316=>781,10317=>781,10318=>781,10319=>781,10320=>781,10321=>781,10322=>781,10323=>781,10324=>781,10325=>781,10326=>781,10327=>781,10328=>781,10329=>781,10330=>781,10331=>781,10332=>781,10333=>781,10334=>781,10335=>781,10336=>781,10337=>781,10338=>781,10339=>781,10340=>781,10341=>781,10342=>781,10343=>781,10344=>781,10345=>781,10346=>781,10347=>781,10348=>781,10349=>781,10350=>781,10351=>781,10352=>781,10353=>781,10354=>781,10355=>781,10356=>781,10357=>781,10358=>781,10359=>781,10360=>781,10361=>781,10362=>781,10363=>781,10364=>781,10365=>781,10366=>781,10367=>781,10368=>781,10369=>781,10370=>781,10371=>781,10372=>781,10373=>781,10374=>781,10375=>781,10376=>781,10377=>781,10378=>781,10379=>781,10380=>781,10381=>781,10382=>781,10383=>781,10384=>781,10385=>781,10386=>781,10387=>781,10388=>781,10389=>781,10390=>781,10391=>781,10392=>781,10393=>781,10394=>781,10395=>781,10396=>781,10397=>781,10398=>781,10399=>781,10400=>781,10401=>781,10402=>781,10403=>781,10404=>781,10405=>781,10406=>781,10407=>781,10408=>781,10409=>781,10410=>781,10411=>781,10412=>781,10413=>781,10414=>781,10415=>781,10416=>781,10417=>781,10418=>781,10419=>781,10420=>781,10421=>781,10422=>781,10423=>781,10424=>781,10425=>781,10426=>781,10427=>781,10428=>781,10429=>781,10430=>781,10431=>781,10432=>781,10433=>781,10434=>781,10435=>781,10436=>781,10437=>781,10438=>781,10439=>781,10440=>781,10441=>781,10442=>781,10443=>781,10444=>781,10445=>781,10446=>781,10447=>781,10448=>781,10449=>781,10450=>781,10451=>781,10452=>781,10453=>781,10454=>781,10455=>781,10456=>781,10457=>781,10458=>781,10459=>781,10460=>781,10461=>781,10462=>781,10463=>781,10464=>781,10465=>781,10466=>781,10467=>781,10468=>781,10469=>781,10470=>781,10471=>781,10472=>781,10473=>781,10474=>781,10475=>781,10476=>781,10477=>781,10478=>781,10479=>781,10480=>781,10481=>781,10482=>781,10483=>781,10484=>781,10485=>781,10486=>781,10487=>781,10488=>781,10489=>781,10490=>781,10491=>781,10492=>781,10493=>781,10494=>781,10495=>781,10502=>838,10503=>838,10506=>838,10507=>838,10560=>838,10561=>838,10627=>753,10628=>753,10702=>838,10703=>1046,10704=>1046,10705=>1000,10706=>1000,10707=>1000,10708=>1000,10709=>1000,10731=>494,10746=>838,10747=>838,10752=>1000,10753=>1000,10754=>1000,10764=>1661,10765=>563,10766=>563,10767=>563,10768=>563,10769=>563,10770=>563,10771=>563,10772=>563,10773=>563,10774=>563,10775=>563,10776=>563,10777=>563,10778=>563,10779=>563,10780=>563,10799=>838,10877=>838,10878=>838,10879=>838,10880=>838,10881=>838,10882=>838,10883=>838,10884=>838,10885=>838,10886=>838,10887=>838,10888=>838,10889=>838,10890=>838,10891=>838,10892=>838,10893=>838,10894=>838,10895=>838,10896=>838,10897=>838,10898=>838,10899=>838,10900=>838,10901=>838,10902=>838,10903=>838,10904=>838,10905=>838,10906=>838,10907=>838,10908=>838,10909=>838,10910=>838,10911=>838,10912=>838,10926=>838,10927=>838,10928=>838,10929=>838,10930=>838,10931=>838,10932=>838,10933=>838,10934=>838,10935=>838,10936=>838,10937=>838,10938=>838,11001=>838,11002=>838,11008=>838,11009=>838,11010=>838,11011=>838,11012=>838,11013=>838,11014=>838,11015=>838,11016=>838,11017=>838,11018=>838,11019=>838,11020=>838,11021=>838,11022=>838,11023=>838,11024=>838,11025=>838,11026=>945,11027=>945,11028=>945,11029=>945,11030=>769,11031=>769,11032=>769,11033=>769,11034=>945,11039=>869,11040=>869,11041=>873,11042=>873,11043=>873,11044=>1119,11091=>869,11092=>869,11360=>637,11361=>360,11362=>637,11363=>733,11364=>770,11365=>675,11366=>478,11367=>956,11368=>712,11369=>775,11370=>665,11371=>725,11372=>582,11373=>860,11374=>995,11375=>774,11376=>860,11377=>778,11378=>1221,11379=>1056,11380=>652,11381=>698,11382=>565,11383=>782,11385=>538,11386=>687,11387=>559,11388=>219,11389=>487,11390=>720,11391=>725,11568=>691,11569=>941,11570=>941,11571=>725,11572=>725,11573=>725,11574=>676,11575=>774,11576=>774,11577=>683,11578=>683,11579=>802,11580=>989,11581=>761,11582=>623,11583=>761,11584=>941,11585=>941,11586=>373,11587=>740,11588=>837,11589=>914,11590=>672,11591=>737,11592=>680,11593=>683,11594=>602,11595=>1039,11596=>778,11597=>837,11598=>683,11599=>372,11600=>778,11601=>373,11602=>725,11603=>691,11604=>941,11605=>941,11606=>837,11607=>373,11608=>836,11609=>941,11610=>941,11611=>734,11612=>876,11613=>771,11614=>734,11615=>683,11616=>774,11617=>837,11618=>683,11619=>850,11620=>697,11621=>850,11631=>716,11800=>580,11810=>457,11811=>457,11812=>457,11813=>457,11822=>580,19904=>896,19905=>896,19906=>896,19907=>896,19908=>896,19909=>896,19910=>896,19911=>896,19912=>896,19913=>896,19914=>896,19915=>896,19916=>896,19917=>896,19918=>896,19919=>896,19920=>896,19921=>896,19922=>896,19923=>896,19924=>896,19925=>896,19926=>896,19927=>896,19928=>896,19929=>896,19930=>896,19931=>896,19932=>896,19933=>896,19934=>896,19935=>896,19936=>896,19937=>896,19938=>896,19939=>896,19940=>896,19941=>896,19942=>896,19943=>896,19944=>896,19945=>896,19946=>896,19947=>896,19948=>896,19949=>896,19950=>896,19951=>896,19952=>896,19953=>896,19954=>896,19955=>896,19956=>896,19957=>896,19958=>896,19959=>896,19960=>896,19961=>896,19962=>896,19963=>896,19964=>896,19965=>896,19966=>896,19967=>896,42564=>720,42565=>595,42566=>436,42567=>440,42572=>1405,42573=>1173,42576=>1234,42577=>1027,42580=>1174,42581=>972,42582=>1093,42583=>958,42594=>1085,42595=>924,42596=>1096,42597=>912,42598=>1260,42599=>997,42600=>850,42601=>687,42602=>1037,42603=>868,42604=>1406,42605=>1106,42606=>961,42634=>963,42635=>787,42636=>682,42637=>580,42644=>808,42645=>712,42760=>500,42761=>500,42762=>500,42763=>500,42764=>500,42765=>500,42766=>500,42767=>500,42768=>500,42769=>500,42770=>500,42771=>500,42772=>500,42773=>500,42774=>500,42779=>400,42780=>400,42781=>287,42782=>287,42783=>287,42786=>444,42787=>390,42788=>540,42789=>540,42790=>837,42791=>712,42792=>1031,42793=>857,42794=>696,42795=>557,42800=>559,42801=>595,42802=>1349,42803=>1052,42804=>1284,42805=>1064,42806=>1216,42807=>1054,42808=>1079,42809=>922,42810=>1079,42811=>922,42812=>1035,42813=>922,42814=>698,42815=>549,42822=>850,42823=>542,42824=>683,42825=>531,42826=>918,42827=>814,42830=>1406,42831=>1106,42832=>733,42833=>716,42834=>948,42835=>937,42838=>850,42839=>716,42852=>738,42853=>716,42854=>738,42855=>716,42880=>637,42881=>343,42882=>837,42883=>712,42889=>400,42890=>386,42891=>456,42892=>306,42893=>808,42894=>693,42896=>928,42897=>768,43002=>1062,43003=>683,43004=>733,43005=>995,43006=>372,43007=>1325,61184=>216,61185=>242,61186=>267,61187=>277,61188=>282,61189=>242,61190=>216,61191=>242,61192=>267,61193=>277,61194=>267,61195=>242,61196=>216,61197=>242,61198=>267,61199=>277,61200=>267,61201=>242,61202=>216,61203=>242,61204=>282,61205=>277,61206=>267,61207=>242,61208=>216,61209=>282,63173=>687,64256=>810,64257=>741,64258=>741,64259=>1115,64260=>1116,64261=>808,64262=>1020,64275=>1388,64276=>1384,64277=>1378,64278=>1384,64279=>1713,64285=>294,64286=>0,64287=>519,64288=>665,64289=>939,64290=>788,64291=>920,64292=>786,64293=>857,64294=>869,64295=>821,64296=>890,64297=>838,64298=>758,64299=>758,64300=>758,64301=>758,64302=>728,64303=>728,64304=>728,64305=>610,64306=>447,64307=>588,64308=>687,64309=>437,64310=>485,64312=>679,64313=>435,64314=>578,64315=>566,64316=>605,64318=>724,64320=>453,64321=>680,64323=>675,64324=>658,64326=>653,64327=>736,64328=>602,64329=>758,64330=>683,64331=>343,64332=>610,64333=>566,64334=>658,64335=>710,64338=>1005,64339=>1059,64340=>375,64341=>408,64342=>1005,64343=>1059,64344=>375,64345=>408,64346=>1005,64347=>1059,64348=>375,64349=>408,64350=>1005,64351=>1059,64352=>375,64353=>408,64354=>1005,64355=>1059,64356=>375,64357=>408,64358=>1005,64359=>1059,64360=>375,64361=>408,64362=>1162,64363=>1191,64364=>655,64365=>720,64366=>1162,64367=>1191,64368=>655,64369=>720,64370=>721,64371=>721,64372=>721,64373=>721,64374=>721,64375=>721,64376=>721,64377=>721,64378=>721,64379=>721,64380=>721,64381=>721,64382=>721,64383=>721,64384=>721,64385=>721,64386=>513,64387=>578,64388=>513,64389=>578,64390=>513,64391=>578,64392=>513,64393=>578,64394=>576,64395=>622,64396=>576,64397=>622,64398=>1024,64399=>1024,64400=>582,64401=>582,64402=>1024,64403=>1024,64404=>582,64405=>582,64406=>1024,64407=>1024,64408=>582,64409=>582,64410=>1024,64411=>1024,64412=>582,64413=>582,64414=>854,64415=>900,64416=>854,64417=>900,64418=>375,64419=>408,64426=>938,64427=>880,64428=>693,64429=>660,64467=>824,64468=>843,64469=>476,64470=>552,64473=>622,64474=>627,64488=>375,64489=>408,64508=>917,64509=>1012,64510=>375,64511=>408,65024=>0,65025=>0,65026=>0,65027=>0,65028=>0,65029=>0,65030=>0,65031=>0,65032=>0,65033=>0,65034=>0,65035=>0,65036=>0,65037=>0,65038=>0,65039=>0,65056=>0,65057=>0,65058=>0,65059=>0,65136=>342,65137=>342,65138=>342,65139=>346,65140=>342,65142=>342,65143=>342,65144=>342,65145=>342,65146=>342,65147=>342,65148=>342,65149=>342,65150=>342,65151=>342,65152=>511,65153=>343,65154=>375,65155=>343,65156=>375,65157=>622,65158=>627,65159=>343,65160=>375,65161=>917,65162=>917,65163=>375,65164=>408,65165=>343,65166=>375,65167=>1005,65168=>1059,65169=>375,65170=>408,65171=>590,65172=>606,65173=>1005,65174=>1059,65175=>375,65176=>408,65177=>1005,65178=>1059,65179=>375,65180=>408,65181=>721,65182=>721,65183=>721,65184=>721,65185=>721,65186=>721,65187=>721,65188=>721,65189=>721,65190=>721,65191=>721,65192=>721,65193=>513,65194=>578,65195=>513,65196=>578,65197=>576,65198=>622,65199=>576,65200=>622,65201=>1380,65202=>1414,65203=>983,65204=>1018,65205=>1380,65206=>1414,65207=>983,65208=>1018,65209=>1345,65210=>1364,65211=>966,65212=>985,65213=>1345,65214=>1364,65215=>966,65216=>985,65217=>1039,65218=>1071,65219=>942,65220=>974,65221=>1039,65222=>1071,65223=>942,65224=>974,65225=>683,65226=>683,65227=>683,65228=>564,65229=>683,65230=>683,65231=>683,65232=>564,65233=>1162,65234=>1191,65235=>655,65236=>720,65237=>894,65238=>901,65239=>655,65240=>720,65241=>917,65242=>931,65243=>582,65244=>582,65245=>868,65246=>893,65247=>375,65248=>408,65249=>733,65250=>784,65251=>619,65252=>670,65253=>854,65254=>900,65255=>375,65256=>408,65257=>590,65258=>606,65259=>693,65260=>660,65261=>622,65262=>627,65263=>917,65264=>1012,65265=>917,65266=>1012,65267=>375,65268=>408,65269=>745,65270=>759,65271=>745,65272=>759,65273=>745,65274=>759,65275=>745,65276=>759,65279=>0,65529=>0,65530=>0,65531=>0,65532=>0,65533=>1113,65535=>600);
+// --- EOF ---
diff --git a/libraries/tcpdf/fonts/dejavusansb.z b/libraries/tcpdf/fonts/dejavusansb.z
new file mode 100644
index 0000000000..ec19021e54
--- /dev/null
+++ b/libraries/tcpdf/fonts/dejavusansb.z
Binary files differ
diff --git a/libraries/tcpdf/fonts/helvetica.php b/libraries/tcpdf/fonts/helvetica.php
new file mode 100644
index 0000000000..d1aa6d851d
--- /dev/null
+++ b/libraries/tcpdf/fonts/helvetica.php
@@ -0,0 +1,13 @@
+<?php
+// TCPDF FONT FILE DESCRIPTION
+$type='core';
+$name='Helvetica';
+$up=-100;
+$ut=50;
+$dw=513;
+$diff='';
+$enc='';
+$desc=array('Flags'=>32,'FontBBox'=>'[-166 -225 1000 931]','ItalicAngle'=>0,'Ascent'=>931,'Descent'=>-225,'Leading'=>0,'CapHeight'=>718,'XHeight'=>523,'StemV'=>88,'StemH'=>76,'AvgWidth'=>513,'MaxWidth'=>1015,'MissingWidth'=>513);
+$cw=array(0=>500,1=>500,2=>500,3=>500,4=>500,5=>500,6=>500,7=>500,8=>500,9=>500,10=>500,11=>500,12=>500,13=>500,14=>500,15=>500,16=>500,17=>500,18=>500,19=>500,20=>500,21=>500,22=>500,23=>500,24=>500,25=>500,26=>500,27=>500,28=>500,29=>500,30=>500,31=>500,32=>278,33=>278,34=>355,35=>556,36=>556,37=>889,38=>667,39=>191,40=>333,41=>333,42=>389,43=>584,44=>278,45=>333,46=>278,47=>278,48=>556,49=>556,50=>556,51=>556,52=>556,53=>556,54=>556,55=>556,56=>556,57=>556,58=>278,59=>278,60=>584,61=>584,62=>584,63=>556,64=>1015,65=>667,66=>667,67=>722,68=>722,69=>667,70=>611,71=>778,72=>722,73=>278,74=>500,75=>667,76=>556,77=>833,78=>722,79=>778,80=>667,81=>778,82=>722,83=>667,84=>611,85=>722,86=>667,87=>944,88=>667,89=>667,90=>611,91=>278,92=>278,93=>277,94=>469,95=>556,96=>333,97=>556,98=>556,99=>500,100=>556,101=>556,102=>278,103=>556,104=>556,105=>222,106=>222,107=>500,108=>222,109=>833,110=>556,111=>556,112=>556,113=>556,114=>333,115=>500,116=>278,117=>556,118=>500,119=>722,120=>500,121=>500,122=>500,123=>334,124=>260,125=>334,126=>584,127=>500,128=>655,129=>500,130=>222,131=>278,132=>333,133=>1000,134=>556,135=>556,136=>333,137=>1000,138=>667,139=>250,140=>1000,141=>500,142=>611,143=>500,144=>500,145=>222,146=>221,147=>333,148=>333,149=>350,150=>556,151=>1000,152=>333,153=>1000,154=>500,155=>250,156=>938,157=>500,158=>500,159=>667,160=>278,161=>278,162=>556,163=>556,164=>556,165=>556,166=>260,167=>556,168=>333,169=>737,170=>370,171=>448,172=>584,173=>333,174=>737,175=>333,176=>606,177=>584,178=>350,179=>350,180=>333,181=>556,182=>537,183=>278,184=>333,185=>350,186=>365,187=>448,188=>869,189=>869,190=>879,191=>556,192=>667,193=>667,194=>667,195=>667,196=>667,197=>667,198=>1000,199=>722,200=>667,201=>667,202=>667,203=>667,204=>278,205=>278,206=>278,207=>278,208=>722,209=>722,210=>778,211=>778,212=>778,213=>778,214=>778,215=>584,216=>778,217=>722,218=>722,219=>722,220=>722,221=>667,222=>666,223=>611,224=>556,225=>556,226=>556,227=>556,228=>556,229=>556,230=>896,231=>500,232=>556,233=>556,234=>556,235=>556,236=>251,237=>251,238=>251,239=>251,240=>556,241=>556,242=>556,243=>556,244=>556,245=>556,246=>556,247=>584,248=>611,249=>556,250=>556,251=>556,252=>556,253=>500,254=>555,255=>500);
+
+// --- EOF ---
diff --git a/libraries/tcpdf/include/tcpdf_colors.php b/libraries/tcpdf/include/tcpdf_colors.php
new file mode 100644
index 0000000000..64752852fb
--- /dev/null
+++ b/libraries/tcpdf/include/tcpdf_colors.php
@@ -0,0 +1,462 @@
+<?php
+//============================================================+
+// File name : tcpdf_colors.php
+// Version : 1.0.002
+// Begin : 2002-04-09
+// Last Update : 2013-09-30
+// Author : Nicola Asuni - Tecnick.com LTD - www.tecnick.com - info@tecnick.com
+// License : GNU-LGPL v3 (http://www.gnu.org/copyleft/lesser.html)
+// -------------------------------------------------------------------
+// Copyright (C) 2002-2013 Nicola Asuni - Tecnick.com LTD
+//
+// This file is part of TCPDF software library.
+//
+// TCPDF 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 3 of the
+// License, or (at your option) any later version.
+//
+// TCPDF 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 TCPDF. If not, see <http://www.gnu.org/licenses/>.
+//
+// See LICENSE.TXT file for more information.
+// -------------------------------------------------------------------
+//
+// Description : Array of WEB safe colors
+//
+//============================================================+
+
+/**
+ * @file
+ * PHP color class for TCPDF
+ * @author Nicola Asuni
+ * @package com.tecnick.tcpdf
+ */
+
+/**
+ * @class TCPDF_COLORS
+ * PHP color class for TCPDF
+ * @package com.tecnick.tcpdf
+ * @version 1.0.002
+ * @author Nicola Asuni - info@tecnick.com
+ */
+class TCPDF_COLORS {
+
+ /**
+ * Array of WEB safe colors
+ * @public static
+ */
+ public static $webcolor = 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',
+ 'dkgray' => 'a9a9a9',
+ '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',
+ 'ltgray' => 'd3d3d3',
+ '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'
+ ); // end of web colors
+
+ /**
+ * Array of valid JavaScript color names
+ * @public static
+ */
+ public static $jscolor = array ('transparent', 'black', 'white', 'red', 'green', 'blue', 'cyan', 'magenta', 'yellow', 'dkGray', 'gray', 'ltGray');
+
+ /**
+ * Array of Spot colors (C,M,Y,K,name)
+ * Color keys must be in lowercase and without spaces.
+ * As long as no open standard for spot colours exists, you have to buy a colour book by one of the colour manufacturers and insert the values and names of spot colours directly.
+ * Common industry standard spot colors are: ANPA-COLOR, DIC, FOCOLTONE, GCMI, HKS, PANTONE, TOYO, TRUMATCH.
+ * @public static
+ */
+ public static $spotcolor = array (
+ // special registration colors
+ 'none' => array( 0, 0, 0, 0, 'None'),
+ 'all' => array(100, 100, 100, 100, 'All'),
+ // standard CMYK colors
+ 'cyan' => array(100, 0, 0, 0, 'Cyan'),
+ 'magenta' => array( 0, 100, 0, 0, 'Magenta'),
+ 'yellow' => array( 0, 0, 100, 0, 'Yellow'),
+ 'key' => array( 0, 0, 0, 100, 'Key'),
+ // alias
+ 'white' => array( 0, 0, 0, 0, 'White'),
+ 'black' => array( 0, 0, 0, 100, 'Black'),
+ // standard RGB colors
+ 'red' => array( 0, 100, 100, 0, 'Red'),
+ 'green' => array(100, 0, 100, 0, 'Green'),
+ 'blue' => array(100, 100, 0, 0, 'Blue'),
+ // Add here standard spot colors or dynamically define them with AddSpotColor()
+ // ...
+ ); // end of spot colors
+
+ // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+ /**
+ * Return the Spot color array.
+ * @param $name (string) Name of the spot color.
+ * @param $spotc (array) Reference to an array of spot colors.
+ * @return (array) Spot color array or false if not defined.
+ * @since 5.9.125 (2011-10-03)
+ * @public static
+ */
+ public static function getSpotColor($name, &$spotc) {
+ if (isset($spotc[$name])) {
+ return $spotc[$name];
+ }
+ $color = preg_replace('/[\s]*/', '', $name); // remove extra spaces
+ $color = strtolower($color);
+ if (isset(self::$spotcolor[$color])) {
+ if (!isset($spotc[$name])) {
+ $i = (1 + count($spotc));
+ $spotc[$name] = array('C' => self::$spotcolor[$color][0], 'M' => self::$spotcolor[$color][1], 'Y' => self::$spotcolor[$color][2], 'K' => self::$spotcolor[$color][3], 'name' => self::$spotcolor[$color][4], 'i' => $i);
+ }
+ return $spotc[self::$spotcolor[$color][4]];
+ }
+ return false;
+ }
+
+ /**
+ * Returns an array (RGB or CMYK) from an html color name, or a six-digit (i.e. #3FE5AA), or three-digit (i.e. #7FF) hexadecimal color, or a javascript color array, or javascript color name.
+ * @param $hcolor (string) HTML color.
+ * @param $spotc (array) Reference to an array of spot colors.
+ * @param $defcol (array) Color to return in case of error.
+ * @return array RGB or CMYK color, or false in case of error.
+ * @public static
+ */
+ public static function convertHTMLColorToDec($hcolor, &$spotc, $defcol=array('R'=>128,'G'=>128,'B'=>128)) {
+ $color = preg_replace('/[\s]*/', '', $hcolor); // remove extra spaces
+ $color = strtolower($color);
+ // check for javascript color array syntax
+ if (strpos($color, '[') !== false) {
+ if (preg_match('/[\[][\"\'](t|g|rgb|cmyk)[\"\'][\,]?([0-9\.]*)[\,]?([0-9\.]*)[\,]?([0-9\.]*)[\,]?([0-9\.]*)[\]]/', $color, $m) > 0) {
+ $returncolor = array();
+ switch ($m[1]) {
+ case 'cmyk': {
+ // RGB
+ $returncolor['C'] = max(0, min(100, (floatval($m[2]) * 100)));
+ $returncolor['M'] = max(0, min(100, (floatval($m[3]) * 100)));
+ $returncolor['Y'] = max(0, min(100, (floatval($m[4]) * 100)));
+ $returncolor['K'] = max(0, min(100, (floatval($m[5]) * 100)));
+ break;
+ }
+ case 'rgb': {
+ // RGB
+ $returncolor['R'] = max(0, min(255, (floatval($m[2]) * 255)));
+ $returncolor['G'] = max(0, min(255, (floatval($m[3]) * 255)));
+ $returncolor['B'] = max(0, min(255, (floatval($m[4]) * 255)));
+ break;
+ }
+ case 'g': {
+ // grayscale
+ $returncolor['G'] = max(0, min(255, (floatval($m[2]) * 255)));
+ break;
+ }
+ case 't':
+ default: {
+ // transparent (empty array)
+ break;
+ }
+ }
+ return $returncolor;
+ }
+ } elseif (($dotpos = strpos($color, '.')) !== false) {
+ // remove class parent (i.e.: color.red)
+ $color = substr($color, ($dotpos + 1));
+ if ($color == 'transparent') {
+ // transparent (empty array)
+ return array();
+ }
+ }
+ if (strlen($color) == 0) {
+ return $defcol;
+ }
+ // RGB ARRAY
+ if (substr($color, 0, 3) == 'rgb') {
+ $codes = substr($color, 4);
+ $codes = str_replace(')', '', $codes);
+ $returncolor = explode(',', $codes);
+ foreach ($returncolor as $key => $val) {
+ if (strpos($val, '%') > 0) {
+ // percentage
+ $returncolor[$key] = (255 * intval($val) / 100);
+ } else {
+ $returncolor[$key] = intval($val);
+ }
+ // normalize value
+ $returncolor[$key] = max(0, min(255, $returncolor[$key]));
+ }
+ return $returncolor;
+ }
+ // CMYK ARRAY
+ if (substr($color, 0, 4) == 'cmyk') {
+ $codes = substr($color, 5);
+ $codes = str_replace(')', '', $codes);
+ $returncolor = explode(',', $codes);
+ foreach ($returncolor as $key => $val) {
+ if (strpos($val, '%') !== false) {
+ // percentage
+ $returncolor[$key] = (100 * intval($val) / 100);
+ } else {
+ $returncolor[$key] = intval($val);
+ }
+ // normalize value
+ $returncolor[$key] = max(0, min(100, $returncolor[$key]));
+ }
+ return $returncolor;
+ }
+ if ($color{0} != '#') {
+ // COLOR NAME
+ if (isset(self::$webcolor[$color])) {
+ // web color
+ $color_code = self::$webcolor[$color];
+ } else {
+ // spot color
+ $returncolor = self::getSpotColor($color, $spotc);
+ if ($returncolor === false) {
+ $returncolor = $defcol;
+ }
+ return $returncolor;
+ }
+ } else {
+ $color_code = substr($color, 1);
+ }
+ // HEXADECIMAL REPRESENTATION
+ switch (strlen($color_code)) {
+ case 3: {
+ // 3-digit RGB hexadecimal representation
+ $r = substr($color_code, 0, 1);
+ $g = substr($color_code, 1, 1);
+ $b = substr($color_code, 2, 1);
+ $returncolor = array();
+ $returncolor['R'] = max(0, min(255, hexdec($r.$r)));
+ $returncolor['G'] = max(0, min(255, hexdec($g.$g)));
+ $returncolor['B'] = max(0, min(255, hexdec($b.$b)));
+ break;
+ }
+ case 6: {
+ // 6-digit RGB hexadecimal representation
+ $returncolor = array();
+ $returncolor['R'] = max(0, min(255, hexdec(substr($color_code, 0, 2))));
+ $returncolor['G'] = max(0, min(255, hexdec(substr($color_code, 2, 2))));
+ $returncolor['B'] = max(0, min(255, hexdec(substr($color_code, 4, 2))));
+ break;
+ }
+ case 8: {
+ // 8-digit CMYK hexadecimal representation
+ $returncolor = array();
+ $returncolor['C'] = max(0, min(100, round(hexdec(substr($color_code, 0, 2)) / 2.55)));
+ $returncolor['M'] = max(0, min(100, round(hexdec(substr($color_code, 2, 2)) / 2.55)));
+ $returncolor['Y'] = max(0, min(100, round(hexdec(substr($color_code, 4, 2)) / 2.55)));
+ $returncolor['K'] = max(0, min(100, round(hexdec(substr($color_code, 6, 2)) / 2.55)));
+ break;
+ }
+ default: {
+ $returncolor = $defcol;
+ break;
+ }
+ }
+ return $returncolor;
+ }
+
+ /**
+ * Convert a color array into a string representation.
+ * @param $c (array) Array of colors.
+ * @return (string) The color array representation.
+ * @since 5.9.137 (2011-12-01)
+ * @public static
+ */
+ public static function getColorStringFromArray($c) {
+ $c = array_values($c);
+ $color = '[';
+ switch (count($c)) {
+ case 4: {
+ // CMYK
+ $color .= sprintf('%F %F %F %F', (max(0, min(100, floatval($c[0]))) / 100), (max(0, min(100, floatval($c[1]))) / 100), (max(0, min(100, floatval($c[2]))) / 100), (max(0, min(100, floatval($c[3]))) / 100));
+ break;
+ }
+ case 3: {
+ // RGB
+ $color .= sprintf('%F %F %F', (max(0, min(255, floatval($c[0]))) / 255), (max(0, min(255, floatval($c[1]))) / 255), (max(0, min(255, floatval($c[2]))) / 255));
+ break;
+ }
+ case 1: {
+ // grayscale
+ $color .= sprintf('%F', (max(0, min(255, floatval($c[0]))) / 255));
+ break;
+ }
+ }
+ $color .= ']';
+ return $color;
+ }
+
+ /**
+ * Convert color to javascript color.
+ * @param $color (string) color name or "#RRGGBB"
+ * @protected
+ * @since 2.1.002 (2008-02-12)
+ * @public static
+ */
+ public static function _JScolor($color) {
+ if (substr($color, 0, 1) == '#') {
+ return sprintf("['RGB',%F,%F,%F]", (hexdec(substr($color, 1, 2)) / 255), (hexdec(substr($color, 3, 2)) / 255), (hexdec(substr($color, 5, 2)) / 255));
+ }
+ if (!in_array($color, self::$jscolor)) {
+ // default transparent color
+ $color = $jscolor[0];
+ }
+ return 'color.'.$color;
+ }
+
+
+} // END OF TCPDF_COLORS CLASS
+
+//============================================================+
+// END OF FILE
+//============================================================+
diff --git a/libraries/tcpdf/include/tcpdf_font_data.php b/libraries/tcpdf/include/tcpdf_font_data.php
new file mode 100644
index 0000000000..974e72ec72
--- /dev/null
+++ b/libraries/tcpdf/include/tcpdf_font_data.php
@@ -0,0 +1,18447 @@
+<?php
+//============================================================+
+// File name : tcpdf_font_data.php
+// Version : 1.0.001
+// Begin : 2008-01-01
+// Last Update : 2013-04-01
+// Author : Nicola Asuni - Tecnick.com LTD - www.tecnick.com - info@tecnick.com
+// License : GNU-LGPL v3 (http://www.gnu.org/copyleft/lesser.html)
+// -------------------------------------------------------------------
+// Copyright (C) 2008-2013 Nicola Asuni - Tecnick.com LTD
+//
+// This file is part of TCPDF software library.
+//
+// TCPDF 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 3 of the
+// License, or (at your option) any later version.
+//
+// TCPDF 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 TCPDF. If not, see <http://www.gnu.org/licenses/>.
+//
+// See LICENSE.TXT file for more information.
+// -------------------------------------------------------------------
+//
+// Description : Unicode data and encoding maps for TCPDF.
+//
+//============================================================+
+
+/**
+ * @file
+ * Unicode data and encoding maps for TCPDF.
+ * @author Nicola Asuni
+ * @package com.tecnick.tcpdf
+ */
+
+/**
+ * @class TCPDF_FONT_DATA
+ * Unicode data and encoding maps for TCPDF.
+ * @package com.tecnick.tcpdf
+ * @version 1.0.001
+ * @author Nicola Asuni - info@tecnick.com
+ */
+class TCPDF_FONT_DATA {
+
+/**
+ * Unicode code for Left-to-Right Mark.
+ * @public
+ */
+public static $uni_LRM = 8206;
+
+/**
+ * Unicode code for Right-to-Left Mark.
+ * @public
+ */
+public static $uni_RLM = 8207;
+
+/**
+ * Unicode code for Left-to-Right Embedding.
+ * @public
+ */
+public static $uni_LRE = 8234;
+
+/**
+ * Unicode code for Right-to-Left Embedding.
+ * @public
+ */
+public static $uni_RLE = 8235;
+
+/**
+ * Unicode code for Pop Directional Format.
+ * @public
+ */
+public static $uni_PDF = 8236;
+
+/**
+ * Unicode code for Left-to-Right Override.
+ * @public
+ */
+public static $uni_LRO = 8237;
+
+/**
+ * Unicode code for Right-to-Left Override.
+ * @public
+ */
+public static $uni_RLO = 8238;
+
+/**
+ * Pattern to test RTL (Righ-To-Left) strings using regular expressions.
+ * @public
+ */
+public static $uni_RE_PATTERN_RTL = "/(
+ \xD6\xBE # R
+ | \xD7[\x80\x83\x86\x90-\xAA\xB0-\xB4] # R
+ | \xDF[\x80-\xAA\xB4\xB5\xBA] # R
+ | \xE2\x80\x8F # R
+ | \xEF\xAC[\x9D\x9F\xA0-\xA8\xAA-\xB6\xB8-\xBC\xBE] # R
+ | \xEF\xAD[\x80\x81\x83\x84\x86-\x8F] # R
+ | \xF0\x90\xA0[\x80-\x85\x88\x8A-\xB5\xB7\xB8\xBC\xBF] # R
+ | \xF0\x90\xA4[\x80-\x99] # R
+ | \xF0\x90\xA8[\x80\x90-\x93\x95-\x97\x99-\xB3] # R
+ | \xF0\x90\xA9[\x80-\x87\x90-\x98] # R
+ | \xE2\x80[\xAB\xAE] # RLE & RLO
+ )/x";
+
+/**
+ * Pattern to test Arabic strings using regular expressions. Source: http://www.w3.org/International/questions/qa-forms-utf-8
+ * @public
+ */
+public static $uni_RE_PATTERN_ARABIC = "/(
+ \xD8[\x80-\x83\x8B\x8D\x9B\x9E\x9F\xA1-\xBA] # AL
+ | \xD9[\x80-\x8A\xAD-\xAF\xB1-\xBF] # AL
+ | \xDA[\x80-\xBF] # AL
+ | \xDB[\x80-\x95\x9D\xA5\xA6\xAE\xAF\xBA-\xBF] # AL
+ | \xDC[\x80-\x8D\x90\x92-\xAF] # AL
+ | \xDD[\x8D-\xAD] # AL
+ | \xDE[\x80-\xA5\xB1] # AL
+ | \xEF\xAD[\x90-\xBF] # AL
+ | \xEF\xAE[\x80-\xB1] # AL
+ | \xEF\xAF[\x93-\xBF] # AL
+ | \xEF[\xB0-\xB3][\x80-\xBF] # AL
+ | \xEF\xB4[\x80-\xBD] # AL
+ | \xEF\xB5[\x90-\xBF] # AL
+ | \xEF\xB6[\x80-\x8F\x92-\xBF] # AL
+ | \xEF\xB7[\x80-\x87\xB0-\xBC] # AL
+ | \xEF\xB9[\xB0-\xB4\xB6-\xBF] # AL
+ | \xEF\xBA[\x80-\xBF] # AL
+ | \xEF\xBB[\x80-\xBC] # AL
+ | \xD9[\xA0-\xA9\xAB\xAC] # AN
+ )/x";
+
+/**
+ * Array of Unicode types.
+ * @public
+ */
+public static $uni_type = array(
+0=>'BN',
+1=>'BN',
+2=>'BN',
+3=>'BN',
+4=>'BN',
+5=>'BN',
+6=>'BN',
+7=>'BN',
+8=>'BN',
+9=>'S',
+10=>'B',
+11=>'S',
+12=>'WS',
+13=>'B',
+14=>'BN',
+15=>'BN',
+16=>'BN',
+17=>'BN',
+18=>'BN',
+19=>'BN',
+20=>'BN',
+21=>'BN',
+22=>'BN',
+23=>'BN',
+24=>'BN',
+25=>'BN',
+26=>'BN',
+27=>'BN',
+28=>'B',
+29=>'B',
+30=>'B',
+31=>'S',
+32=>'WS',
+33=>'ON',
+34=>'ON',
+35=>'ET',
+36=>'ET',
+37=>'ET',
+38=>'ON',
+39=>'ON',
+40=>'ON',
+41=>'ON',
+42=>'ON',
+43=>'ES',
+44=>'CS',
+45=>'ES',
+46=>'CS',
+47=>'CS',
+48=>'EN',
+49=>'EN',
+50=>'EN',
+51=>'EN',
+52=>'EN',
+53=>'EN',
+54=>'EN',
+55=>'EN',
+56=>'EN',
+57=>'EN',
+58=>'CS',
+59=>'ON',
+60=>'ON',
+61=>'ON',
+62=>'ON',
+63=>'ON',
+64=>'ON',
+65=>'L',
+66=>'L',
+67=>'L',
+68=>'L',
+69=>'L',
+70=>'L',
+71=>'L',
+72=>'L',
+73=>'L',
+74=>'L',
+75=>'L',
+76=>'L',
+77=>'L',
+78=>'L',
+79=>'L',
+80=>'L',
+81=>'L',
+82=>'L',
+83=>'L',
+84=>'L',
+85=>'L',
+86=>'L',
+87=>'L',
+88=>'L',
+89=>'L',
+90=>'L',
+91=>'ON',
+92=>'ON',
+93=>'ON',
+94=>'ON',
+95=>'ON',
+96=>'ON',
+97=>'L',
+98=>'L',
+99=>'L',
+100=>'L',
+101=>'L',
+102=>'L',
+103=>'L',
+104=>'L',
+105=>'L',
+106=>'L',
+107=>'L',
+108=>'L',
+109=>'L',
+110=>'L',
+111=>'L',
+112=>'L',
+113=>'L',
+114=>'L',
+115=>'L',
+116=>'L',
+117=>'L',
+118=>'L',
+119=>'L',
+120=>'L',
+121=>'L',
+122=>'L',
+123=>'ON',
+124=>'ON',
+125=>'ON',
+126=>'ON',
+127=>'BN',
+128=>'BN',
+129=>'BN',
+130=>'BN',
+131=>'BN',
+132=>'BN',
+133=>'B',
+134=>'BN',
+135=>'BN',
+136=>'BN',
+137=>'BN',
+138=>'BN',
+139=>'BN',
+140=>'BN',
+141=>'BN',
+142=>'BN',
+143=>'BN',
+144=>'BN',
+145=>'BN',
+146=>'BN',
+147=>'BN',
+148=>'BN',
+149=>'BN',
+150=>'BN',
+151=>'BN',
+152=>'BN',
+153=>'BN',
+154=>'BN',
+155=>'BN',
+156=>'BN',
+157=>'BN',
+158=>'BN',
+159=>'BN',
+160=>'CS',
+161=>'ON',
+162=>'ET',
+163=>'ET',
+164=>'ET',
+165=>'ET',
+166=>'ON',
+167=>'ON',
+168=>'ON',
+169=>'ON',
+170=>'L',
+171=>'ON',
+172=>'ON',
+173=>'BN',
+174=>'ON',
+175=>'ON',
+176=>'ET',
+177=>'ET',
+178=>'EN',
+179=>'EN',
+180=>'ON',
+181=>'L',
+182=>'ON',
+183=>'ON',
+184=>'ON',
+185=>'EN',
+186=>'L',
+187=>'ON',
+188=>'ON',
+189=>'ON',
+190=>'ON',
+191=>'ON',
+192=>'L',
+193=>'L',
+194=>'L',
+195=>'L',
+196=>'L',
+197=>'L',
+198=>'L',
+199=>'L',
+200=>'L',
+201=>'L',
+202=>'L',
+203=>'L',
+204=>'L',
+205=>'L',
+206=>'L',
+207=>'L',
+208=>'L',
+209=>'L',
+210=>'L',
+211=>'L',
+212=>'L',
+213=>'L',
+214=>'L',
+215=>'ON',
+216=>'L',
+217=>'L',
+218=>'L',
+219=>'L',
+220=>'L',
+221=>'L',
+222=>'L',
+223=>'L',
+224=>'L',
+225=>'L',
+226=>'L',
+227=>'L',
+228=>'L',
+229=>'L',
+230=>'L',
+231=>'L',
+232=>'L',
+233=>'L',
+234=>'L',
+235=>'L',
+236=>'L',
+237=>'L',
+238=>'L',
+239=>'L',
+240=>'L',
+241=>'L',
+242=>'L',
+243=>'L',
+244=>'L',
+245=>'L',
+246=>'L',
+247=>'ON',
+248=>'L',
+249=>'L',
+250=>'L',
+251=>'L',
+252=>'L',
+253=>'L',
+254=>'L',
+255=>'L',
+256=>'L',
+257=>'L',
+258=>'L',
+259=>'L',
+260=>'L',
+261=>'L',
+262=>'L',
+263=>'L',
+264=>'L',
+265=>'L',
+266=>'L',
+267=>'L',
+268=>'L',
+269=>'L',
+270=>'L',
+271=>'L',
+272=>'L',
+273=>'L',
+274=>'L',
+275=>'L',
+276=>'L',
+277=>'L',
+278=>'L',
+279=>'L',
+280=>'L',
+281=>'L',
+282=>'L',
+283=>'L',
+284=>'L',
+285=>'L',
+286=>'L',
+287=>'L',
+288=>'L',
+289=>'L',
+290=>'L',
+291=>'L',
+292=>'L',
+293=>'L',
+294=>'L',
+295=>'L',
+296=>'L',
+297=>'L',
+298=>'L',
+299=>'L',
+300=>'L',
+301=>'L',
+302=>'L',
+303=>'L',
+304=>'L',
+305=>'L',
+306=>'L',
+307=>'L',
+308=>'L',
+309=>'L',
+310=>'L',
+311=>'L',
+312=>'L',
+313=>'L',
+314=>'L',
+315=>'L',
+316=>'L',
+317=>'L',
+318=>'L',
+319=>'L',
+320=>'L',
+321=>'L',
+322=>'L',
+323=>'L',
+324=>'L',
+325=>'L',
+326=>'L',
+327=>'L',
+328=>'L',
+329=>'L',
+330=>'L',
+331=>'L',
+332=>'L',
+333=>'L',
+334=>'L',
+335=>'L',
+336=>'L',
+337=>'L',
+338=>'L',
+339=>'L',
+340=>'L',
+341=>'L',
+342=>'L',
+343=>'L',
+344=>'L',
+345=>'L',
+346=>'L',
+347=>'L',
+348=>'L',
+349=>'L',
+350=>'L',
+351=>'L',
+352=>'L',
+353=>'L',
+354=>'L',
+355=>'L',
+356=>'L',
+357=>'L',
+358=>'L',
+359=>'L',
+360=>'L',
+361=>'L',
+362=>'L',
+363=>'L',
+364=>'L',
+365=>'L',
+366=>'L',
+367=>'L',
+368=>'L',
+369=>'L',
+370=>'L',
+371=>'L',
+372=>'L',
+373=>'L',
+374=>'L',
+375=>'L',
+376=>'L',
+377=>'L',
+378=>'L',
+379=>'L',
+380=>'L',
+381=>'L',
+382=>'L',
+383=>'L',
+384=>'L',
+385=>'L',
+386=>'L',
+387=>'L',
+388=>'L',
+389=>'L',
+390=>'L',
+391=>'L',
+392=>'L',
+393=>'L',
+394=>'L',
+395=>'L',
+396=>'L',
+397=>'L',
+398=>'L',
+399=>'L',
+400=>'L',
+401=>'L',
+402=>'L',
+403=>'L',
+404=>'L',
+405=>'L',
+406=>'L',
+407=>'L',
+408=>'L',
+409=>'L',
+410=>'L',
+411=>'L',
+412=>'L',
+413=>'L',
+414=>'L',
+415=>'L',
+416=>'L',
+417=>'L',
+418=>'L',
+419=>'L',
+420=>'L',
+421=>'L',
+422=>'L',
+423=>'L',
+424=>'L',
+425=>'L',
+426=>'L',
+427=>'L',
+428=>'L',
+429=>'L',
+430=>'L',
+431=>'L',
+432=>'L',
+433=>'L',
+434=>'L',
+435=>'L',
+436=>'L',
+437=>'L',
+438=>'L',
+439=>'L',
+440=>'L',
+441=>'L',
+442=>'L',
+443=>'L',
+444=>'L',
+445=>'L',
+446=>'L',
+447=>'L',
+448=>'L',
+449=>'L',
+450=>'L',
+451=>'L',
+452=>'L',
+453=>'L',
+454=>'L',
+455=>'L',
+456=>'L',
+457=>'L',
+458=>'L',
+459=>'L',
+460=>'L',
+461=>'L',
+462=>'L',
+463=>'L',
+464=>'L',
+465=>'L',
+466=>'L',
+467=>'L',
+468=>'L',
+469=>'L',
+470=>'L',
+471=>'L',
+472=>'L',
+473=>'L',
+474=>'L',
+475=>'L',
+476=>'L',
+477=>'L',
+478=>'L',
+479=>'L',
+480=>'L',
+481=>'L',
+482=>'L',
+483=>'L',
+484=>'L',
+485=>'L',
+486=>'L',
+487=>'L',
+488=>'L',
+489=>'L',
+490=>'L',
+491=>'L',
+492=>'L',
+493=>'L',
+494=>'L',
+495=>'L',
+496=>'L',
+497=>'L',
+498=>'L',
+499=>'L',
+500=>'L',
+501=>'L',
+502=>'L',
+503=>'L',
+504=>'L',
+505=>'L',
+506=>'L',
+507=>'L',
+508=>'L',
+509=>'L',
+510=>'L',
+511=>'L',
+512=>'L',
+513=>'L',
+514=>'L',
+515=>'L',
+516=>'L',
+517=>'L',
+518=>'L',
+519=>'L',
+520=>'L',
+521=>'L',
+522=>'L',
+523=>'L',
+524=>'L',
+525=>'L',
+526=>'L',
+527=>'L',
+528=>'L',
+529=>'L',
+530=>'L',
+531=>'L',
+532=>'L',
+533=>'L',
+534=>'L',
+535=>'L',
+536=>'L',
+537=>'L',
+538=>'L',
+539=>'L',
+540=>'L',
+541=>'L',
+542=>'L',
+543=>'L',
+544=>'L',
+545=>'L',
+546=>'L',
+547=>'L',
+548=>'L',
+549=>'L',
+550=>'L',
+551=>'L',
+552=>'L',
+553=>'L',
+554=>'L',
+555=>'L',
+556=>'L',
+557=>'L',
+558=>'L',
+559=>'L',
+560=>'L',
+561=>'L',
+562=>'L',
+563=>'L',
+564=>'L',
+565=>'L',
+566=>'L',
+567=>'L',
+568=>'L',
+569=>'L',
+570=>'L',
+571=>'L',
+572=>'L',
+573=>'L',
+574=>'L',
+575=>'L',
+576=>'L',
+577=>'L',
+578=>'L',
+579=>'L',
+580=>'L',
+581=>'L',
+582=>'L',
+583=>'L',
+584=>'L',
+585=>'L',
+586=>'L',
+587=>'L',
+588=>'L',
+589=>'L',
+590=>'L',
+591=>'L',
+592=>'L',
+593=>'L',
+594=>'L',
+595=>'L',
+596=>'L',
+597=>'L',
+598=>'L',
+599=>'L',
+600=>'L',
+601=>'L',
+602=>'L',
+603=>'L',
+604=>'L',
+605=>'L',
+606=>'L',
+607=>'L',
+608=>'L',
+609=>'L',
+610=>'L',
+611=>'L',
+612=>'L',
+613=>'L',
+614=>'L',
+615=>'L',
+616=>'L',
+617=>'L',
+618=>'L',
+619=>'L',
+620=>'L',
+621=>'L',
+622=>'L',
+623=>'L',
+624=>'L',
+625=>'L',
+626=>'L',
+627=>'L',
+628=>'L',
+629=>'L',
+630=>'L',
+631=>'L',
+632=>'L',
+633=>'L',
+634=>'L',
+635=>'L',
+636=>'L',
+637=>'L',
+638=>'L',
+639=>'L',
+640=>'L',
+641=>'L',
+642=>'L',
+643=>'L',
+644=>'L',
+645=>'L',
+646=>'L',
+647=>'L',
+648=>'L',
+649=>'L',
+650=>'L',
+651=>'L',
+652=>'L',
+653=>'L',
+654=>'L',
+655=>'L',
+656=>'L',
+657=>'L',
+658=>'L',
+659=>'L',
+660=>'L',
+661=>'L',
+662=>'L',
+663=>'L',
+664=>'L',
+665=>'L',
+666=>'L',
+667=>'L',
+668=>'L',
+669=>'L',
+670=>'L',
+671=>'L',
+672=>'L',
+673=>'L',
+674=>'L',
+675=>'L',
+676=>'L',
+677=>'L',
+678=>'L',
+679=>'L',
+680=>'L',
+681=>'L',
+682=>'L',
+683=>'L',
+684=>'L',
+685=>'L',
+686=>'L',
+687=>'L',
+688=>'L',
+689=>'L',
+690=>'L',
+691=>'L',
+692=>'L',
+693=>'L',
+694=>'L',
+695=>'L',
+696=>'L',
+697=>'ON',
+698=>'ON',
+699=>'L',
+700=>'L',
+701=>'L',
+702=>'L',
+703=>'L',
+704=>'L',
+705=>'L',
+706=>'ON',
+707=>'ON',
+708=>'ON',
+709=>'ON',
+710=>'ON',
+711=>'ON',
+712=>'ON',
+713=>'ON',
+714=>'ON',
+715=>'ON',
+716=>'ON',
+717=>'ON',
+718=>'ON',
+719=>'ON',
+720=>'L',
+721=>'L',
+722=>'ON',
+723=>'ON',
+724=>'ON',
+725=>'ON',
+726=>'ON',
+727=>'ON',
+728=>'ON',
+729=>'ON',
+730=>'ON',
+731=>'ON',
+732=>'ON',
+733=>'ON',
+734=>'ON',
+735=>'ON',
+736=>'L',
+737=>'L',
+738=>'L',
+739=>'L',
+740=>'L',
+741=>'ON',
+742=>'ON',
+743=>'ON',
+744=>'ON',
+745=>'ON',
+746=>'ON',
+747=>'ON',
+748=>'ON',
+749=>'ON',
+750=>'L',
+751=>'ON',
+752=>'ON',
+753=>'ON',
+754=>'ON',
+755=>'ON',
+756=>'ON',
+757=>'ON',
+758=>'ON',
+759=>'ON',
+760=>'ON',
+761=>'ON',
+762=>'ON',
+763=>'ON',
+764=>'ON',
+765=>'ON',
+766=>'ON',
+767=>'ON',
+768=>'NSM',
+769=>'NSM',
+770=>'NSM',
+771=>'NSM',
+772=>'NSM',
+773=>'NSM',
+774=>'NSM',
+775=>'NSM',
+776=>'NSM',
+777=>'NSM',
+778=>'NSM',
+779=>'NSM',
+780=>'NSM',
+781=>'NSM',
+782=>'NSM',
+783=>'NSM',
+784=>'NSM',
+785=>'NSM',
+786=>'NSM',
+787=>'NSM',
+788=>'NSM',
+789=>'NSM',
+790=>'NSM',
+791=>'NSM',
+792=>'NSM',
+793=>'NSM',
+794=>'NSM',
+795=>'NSM',
+796=>'NSM',
+797=>'NSM',
+798=>'NSM',
+799=>'NSM',
+800=>'NSM',
+801=>'NSM',
+802=>'NSM',
+803=>'NSM',
+804=>'NSM',
+805=>'NSM',
+806=>'NSM',
+807=>'NSM',
+808=>'NSM',
+809=>'NSM',
+810=>'NSM',
+811=>'NSM',
+812=>'NSM',
+813=>'NSM',
+814=>'NSM',
+815=>'NSM',
+816=>'NSM',
+817=>'NSM',
+818=>'NSM',
+819=>'NSM',
+820=>'NSM',
+821=>'NSM',
+822=>'NSM',
+823=>'NSM',
+824=>'NSM',
+825=>'NSM',
+826=>'NSM',
+827=>'NSM',
+828=>'NSM',
+829=>'NSM',
+830=>'NSM',
+831=>'NSM',
+832=>'NSM',
+833=>'NSM',
+834=>'NSM',
+835=>'NSM',
+836=>'NSM',
+837=>'NSM',
+838=>'NSM',
+839=>'NSM',
+840=>'NSM',
+841=>'NSM',
+842=>'NSM',
+843=>'NSM',
+844=>'NSM',
+845=>'NSM',
+846=>'NSM',
+847=>'NSM',
+848=>'NSM',
+849=>'NSM',
+850=>'NSM',
+851=>'NSM',
+852=>'NSM',
+853=>'NSM',
+854=>'NSM',
+855=>'NSM',
+856=>'NSM',
+857=>'NSM',
+858=>'NSM',
+859=>'NSM',
+860=>'NSM',
+861=>'NSM',
+862=>'NSM',
+863=>'NSM',
+864=>'NSM',
+865=>'NSM',
+866=>'NSM',
+867=>'NSM',
+868=>'NSM',
+869=>'NSM',
+870=>'NSM',
+871=>'NSM',
+872=>'NSM',
+873=>'NSM',
+874=>'NSM',
+875=>'NSM',
+876=>'NSM',
+877=>'NSM',
+878=>'NSM',
+879=>'NSM',
+884=>'ON',
+885=>'ON',
+890=>'L',
+891=>'L',
+892=>'L',
+893=>'L',
+894=>'ON',
+900=>'ON',
+901=>'ON',
+902=>'L',
+903=>'ON',
+904=>'L',
+905=>'L',
+906=>'L',
+908=>'L',
+910=>'L',
+911=>'L',
+912=>'L',
+913=>'L',
+914=>'L',
+915=>'L',
+916=>'L',
+917=>'L',
+918=>'L',
+919=>'L',
+920=>'L',
+921=>'L',
+922=>'L',
+923=>'L',
+924=>'L',
+925=>'L',
+926=>'L',
+927=>'L',
+928=>'L',
+929=>'L',
+931=>'L',
+932=>'L',
+933=>'L',
+934=>'L',
+935=>'L',
+936=>'L',
+937=>'L',
+938=>'L',
+939=>'L',
+940=>'L',
+941=>'L',
+942=>'L',
+943=>'L',
+944=>'L',
+945=>'L',
+946=>'L',
+947=>'L',
+948=>'L',
+949=>'L',
+950=>'L',
+951=>'L',
+952=>'L',
+953=>'L',
+954=>'L',
+955=>'L',
+956=>'L',
+957=>'L',
+958=>'L',
+959=>'L',
+960=>'L',
+961=>'L',
+962=>'L',
+963=>'L',
+964=>'L',
+965=>'L',
+966=>'L',
+967=>'L',
+968=>'L',
+969=>'L',
+970=>'L',
+971=>'L',
+972=>'L',
+973=>'L',
+974=>'L',
+976=>'L',
+977=>'L',
+978=>'L',
+979=>'L',
+980=>'L',
+981=>'L',
+982=>'L',
+983=>'L',
+984=>'L',
+985=>'L',
+986=>'L',
+987=>'L',
+988=>'L',
+989=>'L',
+990=>'L',
+991=>'L',
+992=>'L',
+993=>'L',
+994=>'L',
+995=>'L',
+996=>'L',
+997=>'L',
+998=>'L',
+999=>'L',
+1000=>'L',
+1001=>'L',
+1002=>'L',
+1003=>'L',
+1004=>'L',
+1005=>'L',
+1006=>'L',
+1007=>'L',
+1008=>'L',
+1009=>'L',
+1010=>'L',
+1011=>'L',
+1012=>'L',
+1013=>'L',
+1014=>'ON',
+1015=>'L',
+1016=>'L',
+1017=>'L',
+1018=>'L',
+1019=>'L',
+1020=>'L',
+1021=>'L',
+1022=>'L',
+1023=>'L',
+1024=>'L',
+1025=>'L',
+1026=>'L',
+1027=>'L',
+1028=>'L',
+1029=>'L',
+1030=>'L',
+1031=>'L',
+1032=>'L',
+1033=>'L',
+1034=>'L',
+1035=>'L',
+1036=>'L',
+1037=>'L',
+1038=>'L',
+1039=>'L',
+1040=>'L',
+1041=>'L',
+1042=>'L',
+1043=>'L',
+1044=>'L',
+1045=>'L',
+1046=>'L',
+1047=>'L',
+1048=>'L',
+1049=>'L',
+1050=>'L',
+1051=>'L',
+1052=>'L',
+1053=>'L',
+1054=>'L',
+1055=>'L',
+1056=>'L',
+1057=>'L',
+1058=>'L',
+1059=>'L',
+1060=>'L',
+1061=>'L',
+1062=>'L',
+1063=>'L',
+1064=>'L',
+1065=>'L',
+1066=>'L',
+1067=>'L',
+1068=>'L',
+1069=>'L',
+1070=>'L',
+1071=>'L',
+1072=>'L',
+1073=>'L',
+1074=>'L',
+1075=>'L',
+1076=>'L',
+1077=>'L',
+1078=>'L',
+1079=>'L',
+1080=>'L',
+1081=>'L',
+1082=>'L',
+1083=>'L',
+1084=>'L',
+1085=>'L',
+1086=>'L',
+1087=>'L',
+1088=>'L',
+1089=>'L',
+1090=>'L',
+1091=>'L',
+1092=>'L',
+1093=>'L',
+1094=>'L',
+1095=>'L',
+1096=>'L',
+1097=>'L',
+1098=>'L',
+1099=>'L',
+1100=>'L',
+1101=>'L',
+1102=>'L',
+1103=>'L',
+1104=>'L',
+1105=>'L',
+1106=>'L',
+1107=>'L',
+1108=>'L',
+1109=>'L',
+1110=>'L',
+1111=>'L',
+1112=>'L',
+1113=>'L',
+1114=>'L',
+1115=>'L',
+1116=>'L',
+1117=>'L',
+1118=>'L',
+1119=>'L',
+1120=>'L',
+1121=>'L',
+1122=>'L',
+1123=>'L',
+1124=>'L',
+1125=>'L',
+1126=>'L',
+1127=>'L',
+1128=>'L',
+1129=>'L',
+1130=>'L',
+1131=>'L',
+1132=>'L',
+1133=>'L',
+1134=>'L',
+1135=>'L',
+1136=>'L',
+1137=>'L',
+1138=>'L',
+1139=>'L',
+1140=>'L',
+1141=>'L',
+1142=>'L',
+1143=>'L',
+1144=>'L',
+1145=>'L',
+1146=>'L',
+1147=>'L',
+1148=>'L',
+1149=>'L',
+1150=>'L',
+1151=>'L',
+1152=>'L',
+1153=>'L',
+1154=>'L',
+1155=>'NSM',
+1156=>'NSM',
+1157=>'NSM',
+1158=>'NSM',
+1160=>'NSM',
+1161=>'NSM',
+1162=>'L',
+1163=>'L',
+1164=>'L',
+1165=>'L',
+1166=>'L',
+1167=>'L',
+1168=>'L',
+1169=>'L',
+1170=>'L',
+1171=>'L',
+1172=>'L',
+1173=>'L',
+1174=>'L',
+1175=>'L',
+1176=>'L',
+1177=>'L',
+1178=>'L',
+1179=>'L',
+1180=>'L',
+1181=>'L',
+1182=>'L',
+1183=>'L',
+1184=>'L',
+1185=>'L',
+1186=>'L',
+1187=>'L',
+1188=>'L',
+1189=>'L',
+1190=>'L',
+1191=>'L',
+1192=>'L',
+1193=>'L',
+1194=>'L',
+1195=>'L',
+1196=>'L',
+1197=>'L',
+1198=>'L',
+1199=>'L',
+1200=>'L',
+1201=>'L',
+1202=>'L',
+1203=>'L',
+1204=>'L',
+1205=>'L',
+1206=>'L',
+1207=>'L',
+1208=>'L',
+1209=>'L',
+1210=>'L',
+1211=>'L',
+1212=>'L',
+1213=>'L',
+1214=>'L',
+1215=>'L',
+1216=>'L',
+1217=>'L',
+1218=>'L',
+1219=>'L',
+1220=>'L',
+1221=>'L',
+1222=>'L',
+1223=>'L',
+1224=>'L',
+1225=>'L',
+1226=>'L',
+1227=>'L',
+1228=>'L',
+1229=>'L',
+1230=>'L',
+1231=>'L',
+1232=>'L',
+1233=>'L',
+1234=>'L',
+1235=>'L',
+1236=>'L',
+1237=>'L',
+1238=>'L',
+1239=>'L',
+1240=>'L',
+1241=>'L',
+1242=>'L',
+1243=>'L',
+1244=>'L',
+1245=>'L',
+1246=>'L',
+1247=>'L',
+1248=>'L',
+1249=>'L',
+1250=>'L',
+1251=>'L',
+1252=>'L',
+1253=>'L',
+1254=>'L',
+1255=>'L',
+1256=>'L',
+1257=>'L',
+1258=>'L',
+1259=>'L',
+1260=>'L',
+1261=>'L',
+1262=>'L',
+1263=>'L',
+1264=>'L',
+1265=>'L',
+1266=>'L',
+1267=>'L',
+1268=>'L',
+1269=>'L',
+1270=>'L',
+1271=>'L',
+1272=>'L',
+1273=>'L',
+1274=>'L',
+1275=>'L',
+1276=>'L',
+1277=>'L',
+1278=>'L',
+1279=>'L',
+1280=>'L',
+1281=>'L',
+1282=>'L',
+1283=>'L',
+1284=>'L',
+1285=>'L',
+1286=>'L',
+1287=>'L',
+1288=>'L',
+1289=>'L',
+1290=>'L',
+1291=>'L',
+1292=>'L',
+1293=>'L',
+1294=>'L',
+1295=>'L',
+1296=>'L',
+1297=>'L',
+1298=>'L',
+1299=>'L',
+1329=>'L',
+1330=>'L',
+1331=>'L',
+1332=>'L',
+1333=>'L',
+1334=>'L',
+1335=>'L',
+1336=>'L',
+1337=>'L',
+1338=>'L',
+1339=>'L',
+1340=>'L',
+1341=>'L',
+1342=>'L',
+1343=>'L',
+1344=>'L',
+1345=>'L',
+1346=>'L',
+1347=>'L',
+1348=>'L',
+1349=>'L',
+1350=>'L',
+1351=>'L',
+1352=>'L',
+1353=>'L',
+1354=>'L',
+1355=>'L',
+1356=>'L',
+1357=>'L',
+1358=>'L',
+1359=>'L',
+1360=>'L',
+1361=>'L',
+1362=>'L',
+1363=>'L',
+1364=>'L',
+1365=>'L',
+1366=>'L',
+1369=>'L',
+1370=>'L',
+1371=>'L',
+1372=>'L',
+1373=>'L',
+1374=>'L',
+1375=>'L',
+1377=>'L',
+1378=>'L',
+1379=>'L',
+1380=>'L',
+1381=>'L',
+1382=>'L',
+1383=>'L',
+1384=>'L',
+1385=>'L',
+1386=>'L',
+1387=>'L',
+1388=>'L',
+1389=>'L',
+1390=>'L',
+1391=>'L',
+1392=>'L',
+1393=>'L',
+1394=>'L',
+1395=>'L',
+1396=>'L',
+1397=>'L',
+1398=>'L',
+1399=>'L',
+1400=>'L',
+1401=>'L',
+1402=>'L',
+1403=>'L',
+1404=>'L',
+1405=>'L',
+1406=>'L',
+1407=>'L',
+1408=>'L',
+1409=>'L',
+1410=>'L',
+1411=>'L',
+1412=>'L',
+1413=>'L',
+1414=>'L',
+1415=>'L',
+1417=>'L',
+1418=>'ON',
+1425=>'NSM',
+1426=>'NSM',
+1427=>'NSM',
+1428=>'NSM',
+1429=>'NSM',
+1430=>'NSM',
+1431=>'NSM',
+1432=>'NSM',
+1433=>'NSM',
+1434=>'NSM',
+1435=>'NSM',
+1436=>'NSM',
+1437=>'NSM',
+1438=>'NSM',
+1439=>'NSM',
+1440=>'NSM',
+1441=>'NSM',
+1442=>'NSM',
+1443=>'NSM',
+1444=>'NSM',
+1445=>'NSM',
+1446=>'NSM',
+1447=>'NSM',
+1448=>'NSM',
+1449=>'NSM',
+1450=>'NSM',
+1451=>'NSM',
+1452=>'NSM',
+1453=>'NSM',
+1454=>'NSM',
+1455=>'NSM',
+1456=>'NSM',
+1457=>'NSM',
+1458=>'NSM',
+1459=>'NSM',
+1460=>'NSM',
+1461=>'NSM',
+1462=>'NSM',
+1463=>'NSM',
+1464=>'NSM',
+1465=>'NSM',
+1466=>'NSM',
+1467=>'NSM',
+1468=>'NSM',
+1469=>'NSM',
+1470=>'R',
+1471=>'NSM',
+1472=>'R',
+1473=>'NSM',
+1474=>'NSM',
+1475=>'R',
+1476=>'NSM',
+1477=>'NSM',
+1478=>'R',
+1479=>'NSM',
+1488=>'R',
+1489=>'R',
+1490=>'R',
+1491=>'R',
+1492=>'R',
+1493=>'R',
+1494=>'R',
+1495=>'R',
+1496=>'R',
+1497=>'R',
+1498=>'R',
+1499=>'R',
+1500=>'R',
+1501=>'R',
+1502=>'R',
+1503=>'R',
+1504=>'R',
+1505=>'R',
+1506=>'R',
+1507=>'R',
+1508=>'R',
+1509=>'R',
+1510=>'R',
+1511=>'R',
+1512=>'R',
+1513=>'R',
+1514=>'R',
+1520=>'R',
+1521=>'R',
+1522=>'R',
+1523=>'R',
+1524=>'R',
+1536=>'AL',
+1537=>'AL',
+1538=>'AL',
+1539=>'AL',
+1547=>'AL',
+1548=>'CS',
+1549=>'AL',
+1550=>'ON',
+1551=>'ON',
+1552=>'NSM',
+1553=>'NSM',
+1554=>'NSM',
+1555=>'NSM',
+1556=>'NSM',
+1557=>'NSM',
+1563=>'AL',
+1566=>'AL',
+1567=>'AL',
+1569=>'AL',
+1570=>'AL',
+1571=>'AL',
+1572=>'AL',
+1573=>'AL',
+1574=>'AL',
+1575=>'AL',
+1576=>'AL',
+1577=>'AL',
+1578=>'AL',
+1579=>'AL',
+1580=>'AL',
+1581=>'AL',
+1582=>'AL',
+1583=>'AL',
+1584=>'AL',
+1585=>'AL',
+1586=>'AL',
+1587=>'AL',
+1588=>'AL',
+1589=>'AL',
+1590=>'AL',
+1591=>'AL',
+1592=>'AL',
+1593=>'AL',
+1594=>'AL',
+1600=>'AL',
+1601=>'AL',
+1602=>'AL',
+1603=>'AL',
+1604=>'AL',
+1605=>'AL',
+1606=>'AL',
+1607=>'AL',
+1608=>'AL',
+1609=>'AL',
+1610=>'AL',
+1611=>'NSM',
+1612=>'NSM',
+1613=>'NSM',
+1614=>'NSM',
+1615=>'NSM',
+1616=>'NSM',
+1617=>'NSM',
+1618=>'NSM',
+1619=>'NSM',
+1620=>'NSM',
+1621=>'NSM',
+1622=>'NSM',
+1623=>'NSM',
+1624=>'NSM',
+1625=>'NSM',
+1626=>'NSM',
+1627=>'NSM',
+1628=>'NSM',
+1629=>'NSM',
+1630=>'NSM',
+1632=>'AN',
+1633=>'AN',
+1634=>'AN',
+1635=>'AN',
+1636=>'AN',
+1637=>'AN',
+1638=>'AN',
+1639=>'AN',
+1640=>'AN',
+1641=>'AN',
+1642=>'ET',
+1643=>'AN',
+1644=>'AN',
+1645=>'AL',
+1646=>'AL',
+1647=>'AL',
+1648=>'NSM',
+1649=>'AL',
+1650=>'AL',
+1651=>'AL',
+1652=>'AL',
+1653=>'AL',
+1654=>'AL',
+1655=>'AL',
+1656=>'AL',
+1657=>'AL',
+1658=>'AL',
+1659=>'AL',
+1660=>'AL',
+1661=>'AL',
+1662=>'AL',
+1663=>'AL',
+1664=>'AL',
+1665=>'AL',
+1666=>'AL',
+1667=>'AL',
+1668=>'AL',
+1669=>'AL',
+1670=>'AL',
+1671=>'AL',
+1672=>'AL',
+1673=>'AL',
+1674=>'AL',
+1675=>'AL',
+1676=>'AL',
+1677=>'AL',
+1678=>'AL',
+1679=>'AL',
+1680=>'AL',
+1681=>'AL',
+1682=>'AL',
+1683=>'AL',
+1684=>'AL',
+1685=>'AL',
+1686=>'AL',
+1687=>'AL',
+1688=>'AL',
+1689=>'AL',
+1690=>'AL',
+1691=>'AL',
+1692=>'AL',
+1693=>'AL',
+1694=>'AL',
+1695=>'AL',
+1696=>'AL',
+1697=>'AL',
+1698=>'AL',
+1699=>'AL',
+1700=>'AL',
+1701=>'AL',
+1702=>'AL',
+1703=>'AL',
+1704=>'AL',
+1705=>'AL',
+1706=>'AL',
+1707=>'AL',
+1708=>'AL',
+1709=>'AL',
+1710=>'AL',
+1711=>'AL',
+1712=>'AL',
+1713=>'AL',
+1714=>'AL',
+1715=>'AL',
+1716=>'AL',
+1717=>'AL',
+1718=>'AL',
+1719=>'AL',
+1720=>'AL',
+1721=>'AL',
+1722=>'AL',
+1723=>'AL',
+1724=>'AL',
+1725=>'AL',
+1726=>'AL',
+1727=>'AL',
+1728=>'AL',
+1729=>'AL',
+1730=>'AL',
+1731=>'AL',
+1732=>'AL',
+1733=>'AL',
+1734=>'AL',
+1735=>'AL',
+1736=>'AL',
+1737=>'AL',
+1738=>'AL',
+1739=>'AL',
+1740=>'AL',
+1741=>'AL',
+1742=>'AL',
+1743=>'AL',
+1744=>'AL',
+1745=>'AL',
+1746=>'AL',
+1747=>'AL',
+1748=>'AL',
+1749=>'AL',
+1750=>'NSM',
+1751=>'NSM',
+1752=>'NSM',
+1753=>'NSM',
+1754=>'NSM',
+1755=>'NSM',
+1756=>'NSM',
+1757=>'AL',
+1758=>'NSM',
+1759=>'NSM',
+1760=>'NSM',
+1761=>'NSM',
+1762=>'NSM',
+1763=>'NSM',
+1764=>'NSM',
+1765=>'AL',
+1766=>'AL',
+1767=>'NSM',
+1768=>'NSM',
+1769=>'ON',
+1770=>'NSM',
+1771=>'NSM',
+1772=>'NSM',
+1773=>'NSM',
+1774=>'AL',
+1775=>'AL',
+1776=>'EN',
+1777=>'EN',
+1778=>'EN',
+1779=>'EN',
+1780=>'EN',
+1781=>'EN',
+1782=>'EN',
+1783=>'EN',
+1784=>'EN',
+1785=>'EN',
+1786=>'AL',
+1787=>'AL',
+1788=>'AL',
+1789=>'AL',
+1790=>'AL',
+1791=>'AL',
+1792=>'AL',
+1793=>'AL',
+1794=>'AL',
+1795=>'AL',
+1796=>'AL',
+1797=>'AL',
+1798=>'AL',
+1799=>'AL',
+1800=>'AL',
+1801=>'AL',
+1802=>'AL',
+1803=>'AL',
+1804=>'AL',
+1805=>'AL',
+1807=>'BN',
+1808=>'AL',
+1809=>'NSM',
+1810=>'AL',
+1811=>'AL',
+1812=>'AL',
+1813=>'AL',
+1814=>'AL',
+1815=>'AL',
+1816=>'AL',
+1817=>'AL',
+1818=>'AL',
+1819=>'AL',
+1820=>'AL',
+1821=>'AL',
+1822=>'AL',
+1823=>'AL',
+1824=>'AL',
+1825=>'AL',
+1826=>'AL',
+1827=>'AL',
+1828=>'AL',
+1829=>'AL',
+1830=>'AL',
+1831=>'AL',
+1832=>'AL',
+1833=>'AL',
+1834=>'AL',
+1835=>'AL',
+1836=>'AL',
+1837=>'AL',
+1838=>'AL',
+1839=>'AL',
+1840=>'NSM',
+1841=>'NSM',
+1842=>'NSM',
+1843=>'NSM',
+1844=>'NSM',
+1845=>'NSM',
+1846=>'NSM',
+1847=>'NSM',
+1848=>'NSM',
+1849=>'NSM',
+1850=>'NSM',
+1851=>'NSM',
+1852=>'NSM',
+1853=>'NSM',
+1854=>'NSM',
+1855=>'NSM',
+1856=>'NSM',
+1857=>'NSM',
+1858=>'NSM',
+1859=>'NSM',
+1860=>'NSM',
+1861=>'NSM',
+1862=>'NSM',
+1863=>'NSM',
+1864=>'NSM',
+1865=>'NSM',
+1866=>'NSM',
+1869=>'AL',
+1870=>'AL',
+1871=>'AL',
+1872=>'AL',
+1873=>'AL',
+1874=>'AL',
+1875=>'AL',
+1876=>'AL',
+1877=>'AL',
+1878=>'AL',
+1879=>'AL',
+1880=>'AL',
+1881=>'AL',
+1882=>'AL',
+1883=>'AL',
+1884=>'AL',
+1885=>'AL',
+1886=>'AL',
+1887=>'AL',
+1888=>'AL',
+1889=>'AL',
+1890=>'AL',
+1891=>'AL',
+1892=>'AL',
+1893=>'AL',
+1894=>'AL',
+1895=>'AL',
+1896=>'AL',
+1897=>'AL',
+1898=>'AL',
+1899=>'AL',
+1900=>'AL',
+1901=>'AL',
+1920=>'AL',
+1921=>'AL',
+1922=>'AL',
+1923=>'AL',
+1924=>'AL',
+1925=>'AL',
+1926=>'AL',
+1927=>'AL',
+1928=>'AL',
+1929=>'AL',
+1930=>'AL',
+1931=>'AL',
+1932=>'AL',
+1933=>'AL',
+1934=>'AL',
+1935=>'AL',
+1936=>'AL',
+1937=>'AL',
+1938=>'AL',
+1939=>'AL',
+1940=>'AL',
+1941=>'AL',
+1942=>'AL',
+1943=>'AL',
+1944=>'AL',
+1945=>'AL',
+1946=>'AL',
+1947=>'AL',
+1948=>'AL',
+1949=>'AL',
+1950=>'AL',
+1951=>'AL',
+1952=>'AL',
+1953=>'AL',
+1954=>'AL',
+1955=>'AL',
+1956=>'AL',
+1957=>'AL',
+1958=>'NSM',
+1959=>'NSM',
+1960=>'NSM',
+1961=>'NSM',
+1962=>'NSM',
+1963=>'NSM',
+1964=>'NSM',
+1965=>'NSM',
+1966=>'NSM',
+1967=>'NSM',
+1968=>'NSM',
+1969=>'AL',
+1984=>'R',
+1985=>'R',
+1986=>'R',
+1987=>'R',
+1988=>'R',
+1989=>'R',
+1990=>'R',
+1991=>'R',
+1992=>'R',
+1993=>'R',
+1994=>'R',
+1995=>'R',
+1996=>'R',
+1997=>'R',
+1998=>'R',
+1999=>'R',
+2000=>'R',
+2001=>'R',
+2002=>'R',
+2003=>'R',
+2004=>'R',
+2005=>'R',
+2006=>'R',
+2007=>'R',
+2008=>'R',
+2009=>'R',
+2010=>'R',
+2011=>'R',
+2012=>'R',
+2013=>'R',
+2014=>'R',
+2015=>'R',
+2016=>'R',
+2017=>'R',
+2018=>'R',
+2019=>'R',
+2020=>'R',
+2021=>'R',
+2022=>'R',
+2023=>'R',
+2024=>'R',
+2025=>'R',
+2026=>'R',
+2027=>'NSM',
+2028=>'NSM',
+2029=>'NSM',
+2030=>'NSM',
+2031=>'NSM',
+2032=>'NSM',
+2033=>'NSM',
+2034=>'NSM',
+2035=>'NSM',
+2036=>'R',
+2037=>'R',
+2038=>'ON',
+2039=>'ON',
+2040=>'ON',
+2041=>'ON',
+2042=>'R',
+2305=>'NSM',
+2306=>'NSM',
+2307=>'L',
+2308=>'L',
+2309=>'L',
+2310=>'L',
+2311=>'L',
+2312=>'L',
+2313=>'L',
+2314=>'L',
+2315=>'L',
+2316=>'L',
+2317=>'L',
+2318=>'L',
+2319=>'L',
+2320=>'L',
+2321=>'L',
+2322=>'L',
+2323=>'L',
+2324=>'L',
+2325=>'L',
+2326=>'L',
+2327=>'L',
+2328=>'L',
+2329=>'L',
+2330=>'L',
+2331=>'L',
+2332=>'L',
+2333=>'L',
+2334=>'L',
+2335=>'L',
+2336=>'L',
+2337=>'L',
+2338=>'L',
+2339=>'L',
+2340=>'L',
+2341=>'L',
+2342=>'L',
+2343=>'L',
+2344=>'L',
+2345=>'L',
+2346=>'L',
+2347=>'L',
+2348=>'L',
+2349=>'L',
+2350=>'L',
+2351=>'L',
+2352=>'L',
+2353=>'L',
+2354=>'L',
+2355=>'L',
+2356=>'L',
+2357=>'L',
+2358=>'L',
+2359=>'L',
+2360=>'L',
+2361=>'L',
+2364=>'NSM',
+2365=>'L',
+2366=>'L',
+2367=>'L',
+2368=>'L',
+2369=>'NSM',
+2370=>'NSM',
+2371=>'NSM',
+2372=>'NSM',
+2373=>'NSM',
+2374=>'NSM',
+2375=>'NSM',
+2376=>'NSM',
+2377=>'L',
+2378=>'L',
+2379=>'L',
+2380=>'L',
+2381=>'NSM',
+2384=>'L',
+2385=>'NSM',
+2386=>'NSM',
+2387=>'NSM',
+2388=>'NSM',
+2392=>'L',
+2393=>'L',
+2394=>'L',
+2395=>'L',
+2396=>'L',
+2397=>'L',
+2398=>'L',
+2399=>'L',
+2400=>'L',
+2401=>'L',
+2402=>'NSM',
+2403=>'NSM',
+2404=>'L',
+2405=>'L',
+2406=>'L',
+2407=>'L',
+2408=>'L',
+2409=>'L',
+2410=>'L',
+2411=>'L',
+2412=>'L',
+2413=>'L',
+2414=>'L',
+2415=>'L',
+2416=>'L',
+2427=>'L',
+2428=>'L',
+2429=>'L',
+2430=>'L',
+2431=>'L',
+2433=>'NSM',
+2434=>'L',
+2435=>'L',
+2437=>'L',
+2438=>'L',
+2439=>'L',
+2440=>'L',
+2441=>'L',
+2442=>'L',
+2443=>'L',
+2444=>'L',
+2447=>'L',
+2448=>'L',
+2451=>'L',
+2452=>'L',
+2453=>'L',
+2454=>'L',
+2455=>'L',
+2456=>'L',
+2457=>'L',
+2458=>'L',
+2459=>'L',
+2460=>'L',
+2461=>'L',
+2462=>'L',
+2463=>'L',
+2464=>'L',
+2465=>'L',
+2466=>'L',
+2467=>'L',
+2468=>'L',
+2469=>'L',
+2470=>'L',
+2471=>'L',
+2472=>'L',
+2474=>'L',
+2475=>'L',
+2476=>'L',
+2477=>'L',
+2478=>'L',
+2479=>'L',
+2480=>'L',
+2482=>'L',
+2486=>'L',
+2487=>'L',
+2488=>'L',
+2489=>'L',
+2492=>'NSM',
+2493=>'L',
+2494=>'L',
+2495=>'L',
+2496=>'L',
+2497=>'NSM',
+2498=>'NSM',
+2499=>'NSM',
+2500=>'NSM',
+2503=>'L',
+2504=>'L',
+2507=>'L',
+2508=>'L',
+2509=>'NSM',
+2510=>'L',
+2519=>'L',
+2524=>'L',
+2525=>'L',
+2527=>'L',
+2528=>'L',
+2529=>'L',
+2530=>'NSM',
+2531=>'NSM',
+2534=>'L',
+2535=>'L',
+2536=>'L',
+2537=>'L',
+2538=>'L',
+2539=>'L',
+2540=>'L',
+2541=>'L',
+2542=>'L',
+2543=>'L',
+2544=>'L',
+2545=>'L',
+2546=>'ET',
+2547=>'ET',
+2548=>'L',
+2549=>'L',
+2550=>'L',
+2551=>'L',
+2552=>'L',
+2553=>'L',
+2554=>'L',
+2561=>'NSM',
+2562=>'NSM',
+2563=>'L',
+2565=>'L',
+2566=>'L',
+2567=>'L',
+2568=>'L',
+2569=>'L',
+2570=>'L',
+2575=>'L',
+2576=>'L',
+2579=>'L',
+2580=>'L',
+2581=>'L',
+2582=>'L',
+2583=>'L',
+2584=>'L',
+2585=>'L',
+2586=>'L',
+2587=>'L',
+2588=>'L',
+2589=>'L',
+2590=>'L',
+2591=>'L',
+2592=>'L',
+2593=>'L',
+2594=>'L',
+2595=>'L',
+2596=>'L',
+2597=>'L',
+2598=>'L',
+2599=>'L',
+2600=>'L',
+2602=>'L',
+2603=>'L',
+2604=>'L',
+2605=>'L',
+2606=>'L',
+2607=>'L',
+2608=>'L',
+2610=>'L',
+2611=>'L',
+2613=>'L',
+2614=>'L',
+2616=>'L',
+2617=>'L',
+2620=>'NSM',
+2622=>'L',
+2623=>'L',
+2624=>'L',
+2625=>'NSM',
+2626=>'NSM',
+2631=>'NSM',
+2632=>'NSM',
+2635=>'NSM',
+2636=>'NSM',
+2637=>'NSM',
+2649=>'L',
+2650=>'L',
+2651=>'L',
+2652=>'L',
+2654=>'L',
+2662=>'L',
+2663=>'L',
+2664=>'L',
+2665=>'L',
+2666=>'L',
+2667=>'L',
+2668=>'L',
+2669=>'L',
+2670=>'L',
+2671=>'L',
+2672=>'NSM',
+2673=>'NSM',
+2674=>'L',
+2675=>'L',
+2676=>'L',
+2689=>'NSM',
+2690=>'NSM',
+2691=>'L',
+2693=>'L',
+2694=>'L',
+2695=>'L',
+2696=>'L',
+2697=>'L',
+2698=>'L',
+2699=>'L',
+2700=>'L',
+2701=>'L',
+2703=>'L',
+2704=>'L',
+2705=>'L',
+2707=>'L',
+2708=>'L',
+2709=>'L',
+2710=>'L',
+2711=>'L',
+2712=>'L',
+2713=>'L',
+2714=>'L',
+2715=>'L',
+2716=>'L',
+2717=>'L',
+2718=>'L',
+2719=>'L',
+2720=>'L',
+2721=>'L',
+2722=>'L',
+2723=>'L',
+2724=>'L',
+2725=>'L',
+2726=>'L',
+2727=>'L',
+2728=>'L',
+2730=>'L',
+2731=>'L',
+2732=>'L',
+2733=>'L',
+2734=>'L',
+2735=>'L',
+2736=>'L',
+2738=>'L',
+2739=>'L',
+2741=>'L',
+2742=>'L',
+2743=>'L',
+2744=>'L',
+2745=>'L',
+2748=>'NSM',
+2749=>'L',
+2750=>'L',
+2751=>'L',
+2752=>'L',
+2753=>'NSM',
+2754=>'NSM',
+2755=>'NSM',
+2756=>'NSM',
+2757=>'NSM',
+2759=>'NSM',
+2760=>'NSM',
+2761=>'L',
+2763=>'L',
+2764=>'L',
+2765=>'NSM',
+2768=>'L',
+2784=>'L',
+2785=>'L',
+2786=>'NSM',
+2787=>'NSM',
+2790=>'L',
+2791=>'L',
+2792=>'L',
+2793=>'L',
+2794=>'L',
+2795=>'L',
+2796=>'L',
+2797=>'L',
+2798=>'L',
+2799=>'L',
+2801=>'ET',
+2817=>'NSM',
+2818=>'L',
+2819=>'L',
+2821=>'L',
+2822=>'L',
+2823=>'L',
+2824=>'L',
+2825=>'L',
+2826=>'L',
+2827=>'L',
+2828=>'L',
+2831=>'L',
+2832=>'L',
+2835=>'L',
+2836=>'L',
+2837=>'L',
+2838=>'L',
+2839=>'L',
+2840=>'L',
+2841=>'L',
+2842=>'L',
+2843=>'L',
+2844=>'L',
+2845=>'L',
+2846=>'L',
+2847=>'L',
+2848=>'L',
+2849=>'L',
+2850=>'L',
+2851=>'L',
+2852=>'L',
+2853=>'L',
+2854=>'L',
+2855=>'L',
+2856=>'L',
+2858=>'L',
+2859=>'L',
+2860=>'L',
+2861=>'L',
+2862=>'L',
+2863=>'L',
+2864=>'L',
+2866=>'L',
+2867=>'L',
+2869=>'L',
+2870=>'L',
+2871=>'L',
+2872=>'L',
+2873=>'L',
+2876=>'NSM',
+2877=>'L',
+2878=>'L',
+2879=>'NSM',
+2880=>'L',
+2881=>'NSM',
+2882=>'NSM',
+2883=>'NSM',
+2887=>'L',
+2888=>'L',
+2891=>'L',
+2892=>'L',
+2893=>'NSM',
+2902=>'NSM',
+2903=>'L',
+2908=>'L',
+2909=>'L',
+2911=>'L',
+2912=>'L',
+2913=>'L',
+2918=>'L',
+2919=>'L',
+2920=>'L',
+2921=>'L',
+2922=>'L',
+2923=>'L',
+2924=>'L',
+2925=>'L',
+2926=>'L',
+2927=>'L',
+2928=>'L',
+2929=>'L',
+2946=>'NSM',
+2947=>'L',
+2949=>'L',
+2950=>'L',
+2951=>'L',
+2952=>'L',
+2953=>'L',
+2954=>'L',
+2958=>'L',
+2959=>'L',
+2960=>'L',
+2962=>'L',
+2963=>'L',
+2964=>'L',
+2965=>'L',
+2969=>'L',
+2970=>'L',
+2972=>'L',
+2974=>'L',
+2975=>'L',
+2979=>'L',
+2980=>'L',
+2984=>'L',
+2985=>'L',
+2986=>'L',
+2990=>'L',
+2991=>'L',
+2992=>'L',
+2993=>'L',
+2994=>'L',
+2995=>'L',
+2996=>'L',
+2997=>'L',
+2998=>'L',
+2999=>'L',
+3000=>'L',
+3001=>'L',
+3006=>'L',
+3007=>'L',
+3008=>'NSM',
+3009=>'L',
+3010=>'L',
+3014=>'L',
+3015=>'L',
+3016=>'L',
+3018=>'L',
+3019=>'L',
+3020=>'L',
+3021=>'NSM',
+3031=>'L',
+3046=>'L',
+3047=>'L',
+3048=>'L',
+3049=>'L',
+3050=>'L',
+3051=>'L',
+3052=>'L',
+3053=>'L',
+3054=>'L',
+3055=>'L',
+3056=>'L',
+3057=>'L',
+3058=>'L',
+3059=>'ON',
+3060=>'ON',
+3061=>'ON',
+3062=>'ON',
+3063=>'ON',
+3064=>'ON',
+3065=>'ET',
+3066=>'ON',
+3073=>'L',
+3074=>'L',
+3075=>'L',
+3077=>'L',
+3078=>'L',
+3079=>'L',
+3080=>'L',
+3081=>'L',
+3082=>'L',
+3083=>'L',
+3084=>'L',
+3086=>'L',
+3087=>'L',
+3088=>'L',
+3090=>'L',
+3091=>'L',
+3092=>'L',
+3093=>'L',
+3094=>'L',
+3095=>'L',
+3096=>'L',
+3097=>'L',
+3098=>'L',
+3099=>'L',
+3100=>'L',
+3101=>'L',
+3102=>'L',
+3103=>'L',
+3104=>'L',
+3105=>'L',
+3106=>'L',
+3107=>'L',
+3108=>'L',
+3109=>'L',
+3110=>'L',
+3111=>'L',
+3112=>'L',
+3114=>'L',
+3115=>'L',
+3116=>'L',
+3117=>'L',
+3118=>'L',
+3119=>'L',
+3120=>'L',
+3121=>'L',
+3122=>'L',
+3123=>'L',
+3125=>'L',
+3126=>'L',
+3127=>'L',
+3128=>'L',
+3129=>'L',
+3134=>'NSM',
+3135=>'NSM',
+3136=>'NSM',
+3137=>'L',
+3138=>'L',
+3139=>'L',
+3140=>'L',
+3142=>'NSM',
+3143=>'NSM',
+3144=>'NSM',
+3146=>'NSM',
+3147=>'NSM',
+3148=>'NSM',
+3149=>'NSM',
+3157=>'NSM',
+3158=>'NSM',
+3168=>'L',
+3169=>'L',
+3174=>'L',
+3175=>'L',
+3176=>'L',
+3177=>'L',
+3178=>'L',
+3179=>'L',
+3180=>'L',
+3181=>'L',
+3182=>'L',
+3183=>'L',
+3202=>'L',
+3203=>'L',
+3205=>'L',
+3206=>'L',
+3207=>'L',
+3208=>'L',
+3209=>'L',
+3210=>'L',
+3211=>'L',
+3212=>'L',
+3214=>'L',
+3215=>'L',
+3216=>'L',
+3218=>'L',
+3219=>'L',
+3220=>'L',
+3221=>'L',
+3222=>'L',
+3223=>'L',
+3224=>'L',
+3225=>'L',
+3226=>'L',
+3227=>'L',
+3228=>'L',
+3229=>'L',
+3230=>'L',
+3231=>'L',
+3232=>'L',
+3233=>'L',
+3234=>'L',
+3235=>'L',
+3236=>'L',
+3237=>'L',
+3238=>'L',
+3239=>'L',
+3240=>'L',
+3242=>'L',
+3243=>'L',
+3244=>'L',
+3245=>'L',
+3246=>'L',
+3247=>'L',
+3248=>'L',
+3249=>'L',
+3250=>'L',
+3251=>'L',
+3253=>'L',
+3254=>'L',
+3255=>'L',
+3256=>'L',
+3257=>'L',
+3260=>'NSM',
+3261=>'L',
+3262=>'L',
+3263=>'L',
+3264=>'L',
+3265=>'L',
+3266=>'L',
+3267=>'L',
+3268=>'L',
+3270=>'L',
+3271=>'L',
+3272=>'L',
+3274=>'L',
+3275=>'L',
+3276=>'NSM',
+3277=>'NSM',
+3285=>'L',
+3286=>'L',
+3294=>'L',
+3296=>'L',
+3297=>'L',
+3298=>'NSM',
+3299=>'NSM',
+3302=>'L',
+3303=>'L',
+3304=>'L',
+3305=>'L',
+3306=>'L',
+3307=>'L',
+3308=>'L',
+3309=>'L',
+3310=>'L',
+3311=>'L',
+3313=>'ON',
+3314=>'ON',
+3330=>'L',
+3331=>'L',
+3333=>'L',
+3334=>'L',
+3335=>'L',
+3336=>'L',
+3337=>'L',
+3338=>'L',
+3339=>'L',
+3340=>'L',
+3342=>'L',
+3343=>'L',
+3344=>'L',
+3346=>'L',
+3347=>'L',
+3348=>'L',
+3349=>'L',
+3350=>'L',
+3351=>'L',
+3352=>'L',
+3353=>'L',
+3354=>'L',
+3355=>'L',
+3356=>'L',
+3357=>'L',
+3358=>'L',
+3359=>'L',
+3360=>'L',
+3361=>'L',
+3362=>'L',
+3363=>'L',
+3364=>'L',
+3365=>'L',
+3366=>'L',
+3367=>'L',
+3368=>'L',
+3370=>'L',
+3371=>'L',
+3372=>'L',
+3373=>'L',
+3374=>'L',
+3375=>'L',
+3376=>'L',
+3377=>'L',
+3378=>'L',
+3379=>'L',
+3380=>'L',
+3381=>'L',
+3382=>'L',
+3383=>'L',
+3384=>'L',
+3385=>'L',
+3390=>'L',
+3391=>'L',
+3392=>'L',
+3393=>'NSM',
+3394=>'NSM',
+3395=>'NSM',
+3398=>'L',
+3399=>'L',
+3400=>'L',
+3402=>'L',
+3403=>'L',
+3404=>'L',
+3405=>'NSM',
+3415=>'L',
+3424=>'L',
+3425=>'L',
+3430=>'L',
+3431=>'L',
+3432=>'L',
+3433=>'L',
+3434=>'L',
+3435=>'L',
+3436=>'L',
+3437=>'L',
+3438=>'L',
+3439=>'L',
+3458=>'L',
+3459=>'L',
+3461=>'L',
+3462=>'L',
+3463=>'L',
+3464=>'L',
+3465=>'L',
+3466=>'L',
+3467=>'L',
+3468=>'L',
+3469=>'L',
+3470=>'L',
+3471=>'L',
+3472=>'L',
+3473=>'L',
+3474=>'L',
+3475=>'L',
+3476=>'L',
+3477=>'L',
+3478=>'L',
+3482=>'L',
+3483=>'L',
+3484=>'L',
+3485=>'L',
+3486=>'L',
+3487=>'L',
+3488=>'L',
+3489=>'L',
+3490=>'L',
+3491=>'L',
+3492=>'L',
+3493=>'L',
+3494=>'L',
+3495=>'L',
+3496=>'L',
+3497=>'L',
+3498=>'L',
+3499=>'L',
+3500=>'L',
+3501=>'L',
+3502=>'L',
+3503=>'L',
+3504=>'L',
+3505=>'L',
+3507=>'L',
+3508=>'L',
+3509=>'L',
+3510=>'L',
+3511=>'L',
+3512=>'L',
+3513=>'L',
+3514=>'L',
+3515=>'L',
+3517=>'L',
+3520=>'L',
+3521=>'L',
+3522=>'L',
+3523=>'L',
+3524=>'L',
+3525=>'L',
+3526=>'L',
+3530=>'NSM',
+3535=>'L',
+3536=>'L',
+3537=>'L',
+3538=>'NSM',
+3539=>'NSM',
+3540=>'NSM',
+3542=>'NSM',
+3544=>'L',
+3545=>'L',
+3546=>'L',
+3547=>'L',
+3548=>'L',
+3549=>'L',
+3550=>'L',
+3551=>'L',
+3570=>'L',
+3571=>'L',
+3572=>'L',
+3585=>'L',
+3586=>'L',
+3587=>'L',
+3588=>'L',
+3589=>'L',
+3590=>'L',
+3591=>'L',
+3592=>'L',
+3593=>'L',
+3594=>'L',
+3595=>'L',
+3596=>'L',
+3597=>'L',
+3598=>'L',
+3599=>'L',
+3600=>'L',
+3601=>'L',
+3602=>'L',
+3603=>'L',
+3604=>'L',
+3605=>'L',
+3606=>'L',
+3607=>'L',
+3608=>'L',
+3609=>'L',
+3610=>'L',
+3611=>'L',
+3612=>'L',
+3613=>'L',
+3614=>'L',
+3615=>'L',
+3616=>'L',
+3617=>'L',
+3618=>'L',
+3619=>'L',
+3620=>'L',
+3621=>'L',
+3622=>'L',
+3623=>'L',
+3624=>'L',
+3625=>'L',
+3626=>'L',
+3627=>'L',
+3628=>'L',
+3629=>'L',
+3630=>'L',
+3631=>'L',
+3632=>'L',
+3633=>'NSM',
+3634=>'L',
+3635=>'L',
+3636=>'NSM',
+3637=>'NSM',
+3638=>'NSM',
+3639=>'NSM',
+3640=>'NSM',
+3641=>'NSM',
+3642=>'NSM',
+3647=>'ET',
+3648=>'L',
+3649=>'L',
+3650=>'L',
+3651=>'L',
+3652=>'L',
+3653=>'L',
+3654=>'L',
+3655=>'NSM',
+3656=>'NSM',
+3657=>'NSM',
+3658=>'NSM',
+3659=>'NSM',
+3660=>'NSM',
+3661=>'NSM',
+3662=>'NSM',
+3663=>'L',
+3664=>'L',
+3665=>'L',
+3666=>'L',
+3667=>'L',
+3668=>'L',
+3669=>'L',
+3670=>'L',
+3671=>'L',
+3672=>'L',
+3673=>'L',
+3674=>'L',
+3675=>'L',
+3713=>'L',
+3714=>'L',
+3716=>'L',
+3719=>'L',
+3720=>'L',
+3722=>'L',
+3725=>'L',
+3732=>'L',
+3733=>'L',
+3734=>'L',
+3735=>'L',
+3737=>'L',
+3738=>'L',
+3739=>'L',
+3740=>'L',
+3741=>'L',
+3742=>'L',
+3743=>'L',
+3745=>'L',
+3746=>'L',
+3747=>'L',
+3749=>'L',
+3751=>'L',
+3754=>'L',
+3755=>'L',
+3757=>'L',
+3758=>'L',
+3759=>'L',
+3760=>'L',
+3761=>'NSM',
+3762=>'L',
+3763=>'L',
+3764=>'NSM',
+3765=>'NSM',
+3766=>'NSM',
+3767=>'NSM',
+3768=>'NSM',
+3769=>'NSM',
+3771=>'NSM',
+3772=>'NSM',
+3773=>'L',
+3776=>'L',
+3777=>'L',
+3778=>'L',
+3779=>'L',
+3780=>'L',
+3782=>'L',
+3784=>'NSM',
+3785=>'NSM',
+3786=>'NSM',
+3787=>'NSM',
+3788=>'NSM',
+3789=>'NSM',
+3792=>'L',
+3793=>'L',
+3794=>'L',
+3795=>'L',
+3796=>'L',
+3797=>'L',
+3798=>'L',
+3799=>'L',
+3800=>'L',
+3801=>'L',
+3804=>'L',
+3805=>'L',
+3840=>'L',
+3841=>'L',
+3842=>'L',
+3843=>'L',
+3844=>'L',
+3845=>'L',
+3846=>'L',
+3847=>'L',
+3848=>'L',
+3849=>'L',
+3850=>'L',
+3851=>'L',
+3852=>'L',
+3853=>'L',
+3854=>'L',
+3855=>'L',
+3856=>'L',
+3857=>'L',
+3858=>'L',
+3859=>'L',
+3860=>'L',
+3861=>'L',
+3862=>'L',
+3863=>'L',
+3864=>'NSM',
+3865=>'NSM',
+3866=>'L',
+3867=>'L',
+3868=>'L',
+3869=>'L',
+3870=>'L',
+3871=>'L',
+3872=>'L',
+3873=>'L',
+3874=>'L',
+3875=>'L',
+3876=>'L',
+3877=>'L',
+3878=>'L',
+3879=>'L',
+3880=>'L',
+3881=>'L',
+3882=>'L',
+3883=>'L',
+3884=>'L',
+3885=>'L',
+3886=>'L',
+3887=>'L',
+3888=>'L',
+3889=>'L',
+3890=>'L',
+3891=>'L',
+3892=>'L',
+3893=>'NSM',
+3894=>'L',
+3895=>'NSM',
+3896=>'L',
+3897=>'NSM',
+3898=>'ON',
+3899=>'ON',
+3900=>'ON',
+3901=>'ON',
+3902=>'L',
+3903=>'L',
+3904=>'L',
+3905=>'L',
+3906=>'L',
+3907=>'L',
+3908=>'L',
+3909=>'L',
+3910=>'L',
+3911=>'L',
+3913=>'L',
+3914=>'L',
+3915=>'L',
+3916=>'L',
+3917=>'L',
+3918=>'L',
+3919=>'L',
+3920=>'L',
+3921=>'L',
+3922=>'L',
+3923=>'L',
+3924=>'L',
+3925=>'L',
+3926=>'L',
+3927=>'L',
+3928=>'L',
+3929=>'L',
+3930=>'L',
+3931=>'L',
+3932=>'L',
+3933=>'L',
+3934=>'L',
+3935=>'L',
+3936=>'L',
+3937=>'L',
+3938=>'L',
+3939=>'L',
+3940=>'L',
+3941=>'L',
+3942=>'L',
+3943=>'L',
+3944=>'L',
+3945=>'L',
+3946=>'L',
+3953=>'NSM',
+3954=>'NSM',
+3955=>'NSM',
+3956=>'NSM',
+3957=>'NSM',
+3958=>'NSM',
+3959=>'NSM',
+3960=>'NSM',
+3961=>'NSM',
+3962=>'NSM',
+3963=>'NSM',
+3964=>'NSM',
+3965=>'NSM',
+3966=>'NSM',
+3967=>'L',
+3968=>'NSM',
+3969=>'NSM',
+3970=>'NSM',
+3971=>'NSM',
+3972=>'NSM',
+3973=>'L',
+3974=>'NSM',
+3975=>'NSM',
+3976=>'L',
+3977=>'L',
+3978=>'L',
+3979=>'L',
+3984=>'NSM',
+3985=>'NSM',
+3986=>'NSM',
+3987=>'NSM',
+3988=>'NSM',
+3989=>'NSM',
+3990=>'NSM',
+3991=>'NSM',
+3993=>'NSM',
+3994=>'NSM',
+3995=>'NSM',
+3996=>'NSM',
+3997=>'NSM',
+3998=>'NSM',
+3999=>'NSM',
+4000=>'NSM',
+4001=>'NSM',
+4002=>'NSM',
+4003=>'NSM',
+4004=>'NSM',
+4005=>'NSM',
+4006=>'NSM',
+4007=>'NSM',
+4008=>'NSM',
+4009=>'NSM',
+4010=>'NSM',
+4011=>'NSM',
+4012=>'NSM',
+4013=>'NSM',
+4014=>'NSM',
+4015=>'NSM',
+4016=>'NSM',
+4017=>'NSM',
+4018=>'NSM',
+4019=>'NSM',
+4020=>'NSM',
+4021=>'NSM',
+4022=>'NSM',
+4023=>'NSM',
+4024=>'NSM',
+4025=>'NSM',
+4026=>'NSM',
+4027=>'NSM',
+4028=>'NSM',
+4030=>'L',
+4031=>'L',
+4032=>'L',
+4033=>'L',
+4034=>'L',
+4035=>'L',
+4036=>'L',
+4037=>'L',
+4038=>'NSM',
+4039=>'L',
+4040=>'L',
+4041=>'L',
+4042=>'L',
+4043=>'L',
+4044=>'L',
+4047=>'L',
+4048=>'L',
+4049=>'L',
+4096=>'L',
+4097=>'L',
+4098=>'L',
+4099=>'L',
+4100=>'L',
+4101=>'L',
+4102=>'L',
+4103=>'L',
+4104=>'L',
+4105=>'L',
+4106=>'L',
+4107=>'L',
+4108=>'L',
+4109=>'L',
+4110=>'L',
+4111=>'L',
+4112=>'L',
+4113=>'L',
+4114=>'L',
+4115=>'L',
+4116=>'L',
+4117=>'L',
+4118=>'L',
+4119=>'L',
+4120=>'L',
+4121=>'L',
+4122=>'L',
+4123=>'L',
+4124=>'L',
+4125=>'L',
+4126=>'L',
+4127=>'L',
+4128=>'L',
+4129=>'L',
+4131=>'L',
+4132=>'L',
+4133=>'L',
+4134=>'L',
+4135=>'L',
+4137=>'L',
+4138=>'L',
+4140=>'L',
+4141=>'NSM',
+4142=>'NSM',
+4143=>'NSM',
+4144=>'NSM',
+4145=>'L',
+4146=>'NSM',
+4150=>'NSM',
+4151=>'NSM',
+4152=>'L',
+4153=>'NSM',
+4160=>'L',
+4161=>'L',
+4162=>'L',
+4163=>'L',
+4164=>'L',
+4165=>'L',
+4166=>'L',
+4167=>'L',
+4168=>'L',
+4169=>'L',
+4170=>'L',
+4171=>'L',
+4172=>'L',
+4173=>'L',
+4174=>'L',
+4175=>'L',
+4176=>'L',
+4177=>'L',
+4178=>'L',
+4179=>'L',
+4180=>'L',
+4181=>'L',
+4182=>'L',
+4183=>'L',
+4184=>'NSM',
+4185=>'NSM',
+4256=>'L',
+4257=>'L',
+4258=>'L',
+4259=>'L',
+4260=>'L',
+4261=>'L',
+4262=>'L',
+4263=>'L',
+4264=>'L',
+4265=>'L',
+4266=>'L',
+4267=>'L',
+4268=>'L',
+4269=>'L',
+4270=>'L',
+4271=>'L',
+4272=>'L',
+4273=>'L',
+4274=>'L',
+4275=>'L',
+4276=>'L',
+4277=>'L',
+4278=>'L',
+4279=>'L',
+4280=>'L',
+4281=>'L',
+4282=>'L',
+4283=>'L',
+4284=>'L',
+4285=>'L',
+4286=>'L',
+4287=>'L',
+4288=>'L',
+4289=>'L',
+4290=>'L',
+4291=>'L',
+4292=>'L',
+4293=>'L',
+4304=>'L',
+4305=>'L',
+4306=>'L',
+4307=>'L',
+4308=>'L',
+4309=>'L',
+4310=>'L',
+4311=>'L',
+4312=>'L',
+4313=>'L',
+4314=>'L',
+4315=>'L',
+4316=>'L',
+4317=>'L',
+4318=>'L',
+4319=>'L',
+4320=>'L',
+4321=>'L',
+4322=>'L',
+4323=>'L',
+4324=>'L',
+4325=>'L',
+4326=>'L',
+4327=>'L',
+4328=>'L',
+4329=>'L',
+4330=>'L',
+4331=>'L',
+4332=>'L',
+4333=>'L',
+4334=>'L',
+4335=>'L',
+4336=>'L',
+4337=>'L',
+4338=>'L',
+4339=>'L',
+4340=>'L',
+4341=>'L',
+4342=>'L',
+4343=>'L',
+4344=>'L',
+4345=>'L',
+4346=>'L',
+4347=>'L',
+4348=>'L',
+4352=>'L',
+4353=>'L',
+4354=>'L',
+4355=>'L',
+4356=>'L',
+4357=>'L',
+4358=>'L',
+4359=>'L',
+4360=>'L',
+4361=>'L',
+4362=>'L',
+4363=>'L',
+4364=>'L',
+4365=>'L',
+4366=>'L',
+4367=>'L',
+4368=>'L',
+4369=>'L',
+4370=>'L',
+4371=>'L',
+4372=>'L',
+4373=>'L',
+4374=>'L',
+4375=>'L',
+4376=>'L',
+4377=>'L',
+4378=>'L',
+4379=>'L',
+4380=>'L',
+4381=>'L',
+4382=>'L',
+4383=>'L',
+4384=>'L',
+4385=>'L',
+4386=>'L',
+4387=>'L',
+4388=>'L',
+4389=>'L',
+4390=>'L',
+4391=>'L',
+4392=>'L',
+4393=>'L',
+4394=>'L',
+4395=>'L',
+4396=>'L',
+4397=>'L',
+4398=>'L',
+4399=>'L',
+4400=>'L',
+4401=>'L',
+4402=>'L',
+4403=>'L',
+4404=>'L',
+4405=>'L',
+4406=>'L',
+4407=>'L',
+4408=>'L',
+4409=>'L',
+4410=>'L',
+4411=>'L',
+4412=>'L',
+4413=>'L',
+4414=>'L',
+4415=>'L',
+4416=>'L',
+4417=>'L',
+4418=>'L',
+4419=>'L',
+4420=>'L',
+4421=>'L',
+4422=>'L',
+4423=>'L',
+4424=>'L',
+4425=>'L',
+4426=>'L',
+4427=>'L',
+4428=>'L',
+4429=>'L',
+4430=>'L',
+4431=>'L',
+4432=>'L',
+4433=>'L',
+4434=>'L',
+4435=>'L',
+4436=>'L',
+4437=>'L',
+4438=>'L',
+4439=>'L',
+4440=>'L',
+4441=>'L',
+4447=>'L',
+4448=>'L',
+4449=>'L',
+4450=>'L',
+4451=>'L',
+4452=>'L',
+4453=>'L',
+4454=>'L',
+4455=>'L',
+4456=>'L',
+4457=>'L',
+4458=>'L',
+4459=>'L',
+4460=>'L',
+4461=>'L',
+4462=>'L',
+4463=>'L',
+4464=>'L',
+4465=>'L',
+4466=>'L',
+4467=>'L',
+4468=>'L',
+4469=>'L',
+4470=>'L',
+4471=>'L',
+4472=>'L',
+4473=>'L',
+4474=>'L',
+4475=>'L',
+4476=>'L',
+4477=>'L',
+4478=>'L',
+4479=>'L',
+4480=>'L',
+4481=>'L',
+4482=>'L',
+4483=>'L',
+4484=>'L',
+4485=>'L',
+4486=>'L',
+4487=>'L',
+4488=>'L',
+4489=>'L',
+4490=>'L',
+4491=>'L',
+4492=>'L',
+4493=>'L',
+4494=>'L',
+4495=>'L',
+4496=>'L',
+4497=>'L',
+4498=>'L',
+4499=>'L',
+4500=>'L',
+4501=>'L',
+4502=>'L',
+4503=>'L',
+4504=>'L',
+4505=>'L',
+4506=>'L',
+4507=>'L',
+4508=>'L',
+4509=>'L',
+4510=>'L',
+4511=>'L',
+4512=>'L',
+4513=>'L',
+4514=>'L',
+4520=>'L',
+4521=>'L',
+4522=>'L',
+4523=>'L',
+4524=>'L',
+4525=>'L',
+4526=>'L',
+4527=>'L',
+4528=>'L',
+4529=>'L',
+4530=>'L',
+4531=>'L',
+4532=>'L',
+4533=>'L',
+4534=>'L',
+4535=>'L',
+4536=>'L',
+4537=>'L',
+4538=>'L',
+4539=>'L',
+4540=>'L',
+4541=>'L',
+4542=>'L',
+4543=>'L',
+4544=>'L',
+4545=>'L',
+4546=>'L',
+4547=>'L',
+4548=>'L',
+4549=>'L',
+4550=>'L',
+4551=>'L',
+4552=>'L',
+4553=>'L',
+4554=>'L',
+4555=>'L',
+4556=>'L',
+4557=>'L',
+4558=>'L',
+4559=>'L',
+4560=>'L',
+4561=>'L',
+4562=>'L',
+4563=>'L',
+4564=>'L',
+4565=>'L',
+4566=>'L',
+4567=>'L',
+4568=>'L',
+4569=>'L',
+4570=>'L',
+4571=>'L',
+4572=>'L',
+4573=>'L',
+4574=>'L',
+4575=>'L',
+4576=>'L',
+4577=>'L',
+4578=>'L',
+4579=>'L',
+4580=>'L',
+4581=>'L',
+4582=>'L',
+4583=>'L',
+4584=>'L',
+4585=>'L',
+4586=>'L',
+4587=>'L',
+4588=>'L',
+4589=>'L',
+4590=>'L',
+4591=>'L',
+4592=>'L',
+4593=>'L',
+4594=>'L',
+4595=>'L',
+4596=>'L',
+4597=>'L',
+4598=>'L',
+4599=>'L',
+4600=>'L',
+4601=>'L',
+4608=>'L',
+4609=>'L',
+4610=>'L',
+4611=>'L',
+4612=>'L',
+4613=>'L',
+4614=>'L',
+4615=>'L',
+4616=>'L',
+4617=>'L',
+4618=>'L',
+4619=>'L',
+4620=>'L',
+4621=>'L',
+4622=>'L',
+4623=>'L',
+4624=>'L',
+4625=>'L',
+4626=>'L',
+4627=>'L',
+4628=>'L',
+4629=>'L',
+4630=>'L',
+4631=>'L',
+4632=>'L',
+4633=>'L',
+4634=>'L',
+4635=>'L',
+4636=>'L',
+4637=>'L',
+4638=>'L',
+4639=>'L',
+4640=>'L',
+4641=>'L',
+4642=>'L',
+4643=>'L',
+4644=>'L',
+4645=>'L',
+4646=>'L',
+4647=>'L',
+4648=>'L',
+4649=>'L',
+4650=>'L',
+4651=>'L',
+4652=>'L',
+4653=>'L',
+4654=>'L',
+4655=>'L',
+4656=>'L',
+4657=>'L',
+4658=>'L',
+4659=>'L',
+4660=>'L',
+4661=>'L',
+4662=>'L',
+4663=>'L',
+4664=>'L',
+4665=>'L',
+4666=>'L',
+4667=>'L',
+4668=>'L',
+4669=>'L',
+4670=>'L',
+4671=>'L',
+4672=>'L',
+4673=>'L',
+4674=>'L',
+4675=>'L',
+4676=>'L',
+4677=>'L',
+4678=>'L',
+4679=>'L',
+4680=>'L',
+4682=>'L',
+4683=>'L',
+4684=>'L',
+4685=>'L',
+4688=>'L',
+4689=>'L',
+4690=>'L',
+4691=>'L',
+4692=>'L',
+4693=>'L',
+4694=>'L',
+4696=>'L',
+4698=>'L',
+4699=>'L',
+4700=>'L',
+4701=>'L',
+4704=>'L',
+4705=>'L',
+4706=>'L',
+4707=>'L',
+4708=>'L',
+4709=>'L',
+4710=>'L',
+4711=>'L',
+4712=>'L',
+4713=>'L',
+4714=>'L',
+4715=>'L',
+4716=>'L',
+4717=>'L',
+4718=>'L',
+4719=>'L',
+4720=>'L',
+4721=>'L',
+4722=>'L',
+4723=>'L',
+4724=>'L',
+4725=>'L',
+4726=>'L',
+4727=>'L',
+4728=>'L',
+4729=>'L',
+4730=>'L',
+4731=>'L',
+4732=>'L',
+4733=>'L',
+4734=>'L',
+4735=>'L',
+4736=>'L',
+4737=>'L',
+4738=>'L',
+4739=>'L',
+4740=>'L',
+4741=>'L',
+4742=>'L',
+4743=>'L',
+4744=>'L',
+4746=>'L',
+4747=>'L',
+4748=>'L',
+4749=>'L',
+4752=>'L',
+4753=>'L',
+4754=>'L',
+4755=>'L',
+4756=>'L',
+4757=>'L',
+4758=>'L',
+4759=>'L',
+4760=>'L',
+4761=>'L',
+4762=>'L',
+4763=>'L',
+4764=>'L',
+4765=>'L',
+4766=>'L',
+4767=>'L',
+4768=>'L',
+4769=>'L',
+4770=>'L',
+4771=>'L',
+4772=>'L',
+4773=>'L',
+4774=>'L',
+4775=>'L',
+4776=>'L',
+4777=>'L',
+4778=>'L',
+4779=>'L',
+4780=>'L',
+4781=>'L',
+4782=>'L',
+4783=>'L',
+4784=>'L',
+4786=>'L',
+4787=>'L',
+4788=>'L',
+4789=>'L',
+4792=>'L',
+4793=>'L',
+4794=>'L',
+4795=>'L',
+4796=>'L',
+4797=>'L',
+4798=>'L',
+4800=>'L',
+4802=>'L',
+4803=>'L',
+4804=>'L',
+4805=>'L',
+4808=>'L',
+4809=>'L',
+4810=>'L',
+4811=>'L',
+4812=>'L',
+4813=>'L',
+4814=>'L',
+4815=>'L',
+4816=>'L',
+4817=>'L',
+4818=>'L',
+4819=>'L',
+4820=>'L',
+4821=>'L',
+4822=>'L',
+4824=>'L',
+4825=>'L',
+4826=>'L',
+4827=>'L',
+4828=>'L',
+4829=>'L',
+4830=>'L',
+4831=>'L',
+4832=>'L',
+4833=>'L',
+4834=>'L',
+4835=>'L',
+4836=>'L',
+4837=>'L',
+4838=>'L',
+4839=>'L',
+4840=>'L',
+4841=>'L',
+4842=>'L',
+4843=>'L',
+4844=>'L',
+4845=>'L',
+4846=>'L',
+4847=>'L',
+4848=>'L',
+4849=>'L',
+4850=>'L',
+4851=>'L',
+4852=>'L',
+4853=>'L',
+4854=>'L',
+4855=>'L',
+4856=>'L',
+4857=>'L',
+4858=>'L',
+4859=>'L',
+4860=>'L',
+4861=>'L',
+4862=>'L',
+4863=>'L',
+4864=>'L',
+4865=>'L',
+4866=>'L',
+4867=>'L',
+4868=>'L',
+4869=>'L',
+4870=>'L',
+4871=>'L',
+4872=>'L',
+4873=>'L',
+4874=>'L',
+4875=>'L',
+4876=>'L',
+4877=>'L',
+4878=>'L',
+4879=>'L',
+4880=>'L',
+4882=>'L',
+4883=>'L',
+4884=>'L',
+4885=>'L',
+4888=>'L',
+4889=>'L',
+4890=>'L',
+4891=>'L',
+4892=>'L',
+4893=>'L',
+4894=>'L',
+4895=>'L',
+4896=>'L',
+4897=>'L',
+4898=>'L',
+4899=>'L',
+4900=>'L',
+4901=>'L',
+4902=>'L',
+4903=>'L',
+4904=>'L',
+4905=>'L',
+4906=>'L',
+4907=>'L',
+4908=>'L',
+4909=>'L',
+4910=>'L',
+4911=>'L',
+4912=>'L',
+4913=>'L',
+4914=>'L',
+4915=>'L',
+4916=>'L',
+4917=>'L',
+4918=>'L',
+4919=>'L',
+4920=>'L',
+4921=>'L',
+4922=>'L',
+4923=>'L',
+4924=>'L',
+4925=>'L',
+4926=>'L',
+4927=>'L',
+4928=>'L',
+4929=>'L',
+4930=>'L',
+4931=>'L',
+4932=>'L',
+4933=>'L',
+4934=>'L',
+4935=>'L',
+4936=>'L',
+4937=>'L',
+4938=>'L',
+4939=>'L',
+4940=>'L',
+4941=>'L',
+4942=>'L',
+4943=>'L',
+4944=>'L',
+4945=>'L',
+4946=>'L',
+4947=>'L',
+4948=>'L',
+4949=>'L',
+4950=>'L',
+4951=>'L',
+4952=>'L',
+4953=>'L',
+4954=>'L',
+4959=>'NSM',
+4960=>'L',
+4961=>'L',
+4962=>'L',
+4963=>'L',
+4964=>'L',
+4965=>'L',
+4966=>'L',
+4967=>'L',
+4968=>'L',
+4969=>'L',
+4970=>'L',
+4971=>'L',
+4972=>'L',
+4973=>'L',
+4974=>'L',
+4975=>'L',
+4976=>'L',
+4977=>'L',
+4978=>'L',
+4979=>'L',
+4980=>'L',
+4981=>'L',
+4982=>'L',
+4983=>'L',
+4984=>'L',
+4985=>'L',
+4986=>'L',
+4987=>'L',
+4988=>'L',
+4992=>'L',
+4993=>'L',
+4994=>'L',
+4995=>'L',
+4996=>'L',
+4997=>'L',
+4998=>'L',
+4999=>'L',
+5000=>'L',
+5001=>'L',
+5002=>'L',
+5003=>'L',
+5004=>'L',
+5005=>'L',
+5006=>'L',
+5007=>'L',
+5008=>'ON',
+5009=>'ON',
+5010=>'ON',
+5011=>'ON',
+5012=>'ON',
+5013=>'ON',
+5014=>'ON',
+5015=>'ON',
+5016=>'ON',
+5017=>'ON',
+5024=>'L',
+5025=>'L',
+5026=>'L',
+5027=>'L',
+5028=>'L',
+5029=>'L',
+5030=>'L',
+5031=>'L',
+5032=>'L',
+5033=>'L',
+5034=>'L',
+5035=>'L',
+5036=>'L',
+5037=>'L',
+5038=>'L',
+5039=>'L',
+5040=>'L',
+5041=>'L',
+5042=>'L',
+5043=>'L',
+5044=>'L',
+5045=>'L',
+5046=>'L',
+5047=>'L',
+5048=>'L',
+5049=>'L',
+5050=>'L',
+5051=>'L',
+5052=>'L',
+5053=>'L',
+5054=>'L',
+5055=>'L',
+5056=>'L',
+5057=>'L',
+5058=>'L',
+5059=>'L',
+5060=>'L',
+5061=>'L',
+5062=>'L',
+5063=>'L',
+5064=>'L',
+5065=>'L',
+5066=>'L',
+5067=>'L',
+5068=>'L',
+5069=>'L',
+5070=>'L',
+5071=>'L',
+5072=>'L',
+5073=>'L',
+5074=>'L',
+5075=>'L',
+5076=>'L',
+5077=>'L',
+5078=>'L',
+5079=>'L',
+5080=>'L',
+5081=>'L',
+5082=>'L',
+5083=>'L',
+5084=>'L',
+5085=>'L',
+5086=>'L',
+5087=>'L',
+5088=>'L',
+5089=>'L',
+5090=>'L',
+5091=>'L',
+5092=>'L',
+5093=>'L',
+5094=>'L',
+5095=>'L',
+5096=>'L',
+5097=>'L',
+5098=>'L',
+5099=>'L',
+5100=>'L',
+5101=>'L',
+5102=>'L',
+5103=>'L',
+5104=>'L',
+5105=>'L',
+5106=>'L',
+5107=>'L',
+5108=>'L',
+5121=>'L',
+5122=>'L',
+5123=>'L',
+5124=>'L',
+5125=>'L',
+5126=>'L',
+5127=>'L',
+5128=>'L',
+5129=>'L',
+5130=>'L',
+5131=>'L',
+5132=>'L',
+5133=>'L',
+5134=>'L',
+5135=>'L',
+5136=>'L',
+5137=>'L',
+5138=>'L',
+5139=>'L',
+5140=>'L',
+5141=>'L',
+5142=>'L',
+5143=>'L',
+5144=>'L',
+5145=>'L',
+5146=>'L',
+5147=>'L',
+5148=>'L',
+5149=>'L',
+5150=>'L',
+5151=>'L',
+5152=>'L',
+5153=>'L',
+5154=>'L',
+5155=>'L',
+5156=>'L',
+5157=>'L',
+5158=>'L',
+5159=>'L',
+5160=>'L',
+5161=>'L',
+5162=>'L',
+5163=>'L',
+5164=>'L',
+5165=>'L',
+5166=>'L',
+5167=>'L',
+5168=>'L',
+5169=>'L',
+5170=>'L',
+5171=>'L',
+5172=>'L',
+5173=>'L',
+5174=>'L',
+5175=>'L',
+5176=>'L',
+5177=>'L',
+5178=>'L',
+5179=>'L',
+5180=>'L',
+5181=>'L',
+5182=>'L',
+5183=>'L',
+5184=>'L',
+5185=>'L',
+5186=>'L',
+5187=>'L',
+5188=>'L',
+5189=>'L',
+5190=>'L',
+5191=>'L',
+5192=>'L',
+5193=>'L',
+5194=>'L',
+5195=>'L',
+5196=>'L',
+5197=>'L',
+5198=>'L',
+5199=>'L',
+5200=>'L',
+5201=>'L',
+5202=>'L',
+5203=>'L',
+5204=>'L',
+5205=>'L',
+5206=>'L',
+5207=>'L',
+5208=>'L',
+5209=>'L',
+5210=>'L',
+5211=>'L',
+5212=>'L',
+5213=>'L',
+5214=>'L',
+5215=>'L',
+5216=>'L',
+5217=>'L',
+5218=>'L',
+5219=>'L',
+5220=>'L',
+5221=>'L',
+5222=>'L',
+5223=>'L',
+5224=>'L',
+5225=>'L',
+5226=>'L',
+5227=>'L',
+5228=>'L',
+5229=>'L',
+5230=>'L',
+5231=>'L',
+5232=>'L',
+5233=>'L',
+5234=>'L',
+5235=>'L',
+5236=>'L',
+5237=>'L',
+5238=>'L',
+5239=>'L',
+5240=>'L',
+5241=>'L',
+5242=>'L',
+5243=>'L',
+5244=>'L',
+5245=>'L',
+5246=>'L',
+5247=>'L',
+5248=>'L',
+5249=>'L',
+5250=>'L',
+5251=>'L',
+5252=>'L',
+5253=>'L',
+5254=>'L',
+5255=>'L',
+5256=>'L',
+5257=>'L',
+5258=>'L',
+5259=>'L',
+5260=>'L',
+5261=>'L',
+5262=>'L',
+5263=>'L',
+5264=>'L',
+5265=>'L',
+5266=>'L',
+5267=>'L',
+5268=>'L',
+5269=>'L',
+5270=>'L',
+5271=>'L',
+5272=>'L',
+5273=>'L',
+5274=>'L',
+5275=>'L',
+5276=>'L',
+5277=>'L',
+5278=>'L',
+5279=>'L',
+5280=>'L',
+5281=>'L',
+5282=>'L',
+5283=>'L',
+5284=>'L',
+5285=>'L',
+5286=>'L',
+5287=>'L',
+5288=>'L',
+5289=>'L',
+5290=>'L',
+5291=>'L',
+5292=>'L',
+5293=>'L',
+5294=>'L',
+5295=>'L',
+5296=>'L',
+5297=>'L',
+5298=>'L',
+5299=>'L',
+5300=>'L',
+5301=>'L',
+5302=>'L',
+5303=>'L',
+5304=>'L',
+5305=>'L',
+5306=>'L',
+5307=>'L',
+5308=>'L',
+5309=>'L',
+5310=>'L',
+5311=>'L',
+5312=>'L',
+5313=>'L',
+5314=>'L',
+5315=>'L',
+5316=>'L',
+5317=>'L',
+5318=>'L',
+5319=>'L',
+5320=>'L',
+5321=>'L',
+5322=>'L',
+5323=>'L',
+5324=>'L',
+5325=>'L',
+5326=>'L',
+5327=>'L',
+5328=>'L',
+5329=>'L',
+5330=>'L',
+5331=>'L',
+5332=>'L',
+5333=>'L',
+5334=>'L',
+5335=>'L',
+5336=>'L',
+5337=>'L',
+5338=>'L',
+5339=>'L',
+5340=>'L',
+5341=>'L',
+5342=>'L',
+5343=>'L',
+5344=>'L',
+5345=>'L',
+5346=>'L',
+5347=>'L',
+5348=>'L',
+5349=>'L',
+5350=>'L',
+5351=>'L',
+5352=>'L',
+5353=>'L',
+5354=>'L',
+5355=>'L',
+5356=>'L',
+5357=>'L',
+5358=>'L',
+5359=>'L',
+5360=>'L',
+5361=>'L',
+5362=>'L',
+5363=>'L',
+5364=>'L',
+5365=>'L',
+5366=>'L',
+5367=>'L',
+5368=>'L',
+5369=>'L',
+5370=>'L',
+5371=>'L',
+5372=>'L',
+5373=>'L',
+5374=>'L',
+5375=>'L',
+5376=>'L',
+5377=>'L',
+5378=>'L',
+5379=>'L',
+5380=>'L',
+5381=>'L',
+5382=>'L',
+5383=>'L',
+5384=>'L',
+5385=>'L',
+5386=>'L',
+5387=>'L',
+5388=>'L',
+5389=>'L',
+5390=>'L',
+5391=>'L',
+5392=>'L',
+5393=>'L',
+5394=>'L',
+5395=>'L',
+5396=>'L',
+5397=>'L',
+5398=>'L',
+5399=>'L',
+5400=>'L',
+5401=>'L',
+5402=>'L',
+5403=>'L',
+5404=>'L',
+5405=>'L',
+5406=>'L',
+5407=>'L',
+5408=>'L',
+5409=>'L',
+5410=>'L',
+5411=>'L',
+5412=>'L',
+5413=>'L',
+5414=>'L',
+5415=>'L',
+5416=>'L',
+5417=>'L',
+5418=>'L',
+5419=>'L',
+5420=>'L',
+5421=>'L',
+5422=>'L',
+5423=>'L',
+5424=>'L',
+5425=>'L',
+5426=>'L',
+5427=>'L',
+5428=>'L',
+5429=>'L',
+5430=>'L',
+5431=>'L',
+5432=>'L',
+5433=>'L',
+5434=>'L',
+5435=>'L',
+5436=>'L',
+5437=>'L',
+5438=>'L',
+5439=>'L',
+5440=>'L',
+5441=>'L',
+5442=>'L',
+5443=>'L',
+5444=>'L',
+5445=>'L',
+5446=>'L',
+5447=>'L',
+5448=>'L',
+5449=>'L',
+5450=>'L',
+5451=>'L',
+5452=>'L',
+5453=>'L',
+5454=>'L',
+5455=>'L',
+5456=>'L',
+5457=>'L',
+5458=>'L',
+5459=>'L',
+5460=>'L',
+5461=>'L',
+5462=>'L',
+5463=>'L',
+5464=>'L',
+5465=>'L',
+5466=>'L',
+5467=>'L',
+5468=>'L',
+5469=>'L',
+5470=>'L',
+5471=>'L',
+5472=>'L',
+5473=>'L',
+5474=>'L',
+5475=>'L',
+5476=>'L',
+5477=>'L',
+5478=>'L',
+5479=>'L',
+5480=>'L',
+5481=>'L',
+5482=>'L',
+5483=>'L',
+5484=>'L',
+5485=>'L',
+5486=>'L',
+5487=>'L',
+5488=>'L',
+5489=>'L',
+5490=>'L',
+5491=>'L',
+5492=>'L',
+5493=>'L',
+5494=>'L',
+5495=>'L',
+5496=>'L',
+5497=>'L',
+5498=>'L',
+5499=>'L',
+5500=>'L',
+5501=>'L',
+5502=>'L',
+5503=>'L',
+5504=>'L',
+5505=>'L',
+5506=>'L',
+5507=>'L',
+5508=>'L',
+5509=>'L',
+5510=>'L',
+5511=>'L',
+5512=>'L',
+5513=>'L',
+5514=>'L',
+5515=>'L',
+5516=>'L',
+5517=>'L',
+5518=>'L',
+5519=>'L',
+5520=>'L',
+5521=>'L',
+5522=>'L',
+5523=>'L',
+5524=>'L',
+5525=>'L',
+5526=>'L',
+5527=>'L',
+5528=>'L',
+5529=>'L',
+5530=>'L',
+5531=>'L',
+5532=>'L',
+5533=>'L',
+5534=>'L',
+5535=>'L',
+5536=>'L',
+5537=>'L',
+5538=>'L',
+5539=>'L',
+5540=>'L',
+5541=>'L',
+5542=>'L',
+5543=>'L',
+5544=>'L',
+5545=>'L',
+5546=>'L',
+5547=>'L',
+5548=>'L',
+5549=>'L',
+5550=>'L',
+5551=>'L',
+5552=>'L',
+5553=>'L',
+5554=>'L',
+5555=>'L',
+5556=>'L',
+5557=>'L',
+5558=>'L',
+5559=>'L',
+5560=>'L',
+5561=>'L',
+5562=>'L',
+5563=>'L',
+5564=>'L',
+5565=>'L',
+5566=>'L',
+5567=>'L',
+5568=>'L',
+5569=>'L',
+5570=>'L',
+5571=>'L',
+5572=>'L',
+5573=>'L',
+5574=>'L',
+5575=>'L',
+5576=>'L',
+5577=>'L',
+5578=>'L',
+5579=>'L',
+5580=>'L',
+5581=>'L',
+5582=>'L',
+5583=>'L',
+5584=>'L',
+5585=>'L',
+5586=>'L',
+5587=>'L',
+5588=>'L',
+5589=>'L',
+5590=>'L',
+5591=>'L',
+5592=>'L',
+5593=>'L',
+5594=>'L',
+5595=>'L',
+5596=>'L',
+5597=>'L',
+5598=>'L',
+5599=>'L',
+5600=>'L',
+5601=>'L',
+5602=>'L',
+5603=>'L',
+5604=>'L',
+5605=>'L',
+5606=>'L',
+5607=>'L',
+5608=>'L',
+5609=>'L',
+5610=>'L',
+5611=>'L',
+5612=>'L',
+5613=>'L',
+5614=>'L',
+5615=>'L',
+5616=>'L',
+5617=>'L',
+5618=>'L',
+5619=>'L',
+5620=>'L',
+5621=>'L',
+5622=>'L',
+5623=>'L',
+5624=>'L',
+5625=>'L',
+5626=>'L',
+5627=>'L',
+5628=>'L',
+5629=>'L',
+5630=>'L',
+5631=>'L',
+5632=>'L',
+5633=>'L',
+5634=>'L',
+5635=>'L',
+5636=>'L',
+5637=>'L',
+5638=>'L',
+5639=>'L',
+5640=>'L',
+5641=>'L',
+5642=>'L',
+5643=>'L',
+5644=>'L',
+5645=>'L',
+5646=>'L',
+5647=>'L',
+5648=>'L',
+5649=>'L',
+5650=>'L',
+5651=>'L',
+5652=>'L',
+5653=>'L',
+5654=>'L',
+5655=>'L',
+5656=>'L',
+5657=>'L',
+5658=>'L',
+5659=>'L',
+5660=>'L',
+5661=>'L',
+5662=>'L',
+5663=>'L',
+5664=>'L',
+5665=>'L',
+5666=>'L',
+5667=>'L',
+5668=>'L',
+5669=>'L',
+5670=>'L',
+5671=>'L',
+5672=>'L',
+5673=>'L',
+5674=>'L',
+5675=>'L',
+5676=>'L',
+5677=>'L',
+5678=>'L',
+5679=>'L',
+5680=>'L',
+5681=>'L',
+5682=>'L',
+5683=>'L',
+5684=>'L',
+5685=>'L',
+5686=>'L',
+5687=>'L',
+5688=>'L',
+5689=>'L',
+5690=>'L',
+5691=>'L',
+5692=>'L',
+5693=>'L',
+5694=>'L',
+5695=>'L',
+5696=>'L',
+5697=>'L',
+5698=>'L',
+5699=>'L',
+5700=>'L',
+5701=>'L',
+5702=>'L',
+5703=>'L',
+5704=>'L',
+5705=>'L',
+5706=>'L',
+5707=>'L',
+5708=>'L',
+5709=>'L',
+5710=>'L',
+5711=>'L',
+5712=>'L',
+5713=>'L',
+5714=>'L',
+5715=>'L',
+5716=>'L',
+5717=>'L',
+5718=>'L',
+5719=>'L',
+5720=>'L',
+5721=>'L',
+5722=>'L',
+5723=>'L',
+5724=>'L',
+5725=>'L',
+5726=>'L',
+5727=>'L',
+5728=>'L',
+5729=>'L',
+5730=>'L',
+5731=>'L',
+5732=>'L',
+5733=>'L',
+5734=>'L',
+5735=>'L',
+5736=>'L',
+5737=>'L',
+5738=>'L',
+5739=>'L',
+5740=>'L',
+5741=>'L',
+5742=>'L',
+5743=>'L',
+5744=>'L',
+5745=>'L',
+5746=>'L',
+5747=>'L',
+5748=>'L',
+5749=>'L',
+5750=>'L',
+5760=>'WS',
+5761=>'L',
+5762=>'L',
+5763=>'L',
+5764=>'L',
+5765=>'L',
+5766=>'L',
+5767=>'L',
+5768=>'L',
+5769=>'L',
+5770=>'L',
+5771=>'L',
+5772=>'L',
+5773=>'L',
+5774=>'L',
+5775=>'L',
+5776=>'L',
+5777=>'L',
+5778=>'L',
+5779=>'L',
+5780=>'L',
+5781=>'L',
+5782=>'L',
+5783=>'L',
+5784=>'L',
+5785=>'L',
+5786=>'L',
+5787=>'ON',
+5788=>'ON',
+5792=>'L',
+5793=>'L',
+5794=>'L',
+5795=>'L',
+5796=>'L',
+5797=>'L',
+5798=>'L',
+5799=>'L',
+5800=>'L',
+5801=>'L',
+5802=>'L',
+5803=>'L',
+5804=>'L',
+5805=>'L',
+5806=>'L',
+5807=>'L',
+5808=>'L',
+5809=>'L',
+5810=>'L',
+5811=>'L',
+5812=>'L',
+5813=>'L',
+5814=>'L',
+5815=>'L',
+5816=>'L',
+5817=>'L',
+5818=>'L',
+5819=>'L',
+5820=>'L',
+5821=>'L',
+5822=>'L',
+5823=>'L',
+5824=>'L',
+5825=>'L',
+5826=>'L',
+5827=>'L',
+5828=>'L',
+5829=>'L',
+5830=>'L',
+5831=>'L',
+5832=>'L',
+5833=>'L',
+5834=>'L',
+5835=>'L',
+5836=>'L',
+5837=>'L',
+5838=>'L',
+5839=>'L',
+5840=>'L',
+5841=>'L',
+5842=>'L',
+5843=>'L',
+5844=>'L',
+5845=>'L',
+5846=>'L',
+5847=>'L',
+5848=>'L',
+5849=>'L',
+5850=>'L',
+5851=>'L',
+5852=>'L',
+5853=>'L',
+5854=>'L',
+5855=>'L',
+5856=>'L',
+5857=>'L',
+5858=>'L',
+5859=>'L',
+5860=>'L',
+5861=>'L',
+5862=>'L',
+5863=>'L',
+5864=>'L',
+5865=>'L',
+5866=>'L',
+5867=>'L',
+5868=>'L',
+5869=>'L',
+5870=>'L',
+5871=>'L',
+5872=>'L',
+5888=>'L',
+5889=>'L',
+5890=>'L',
+5891=>'L',
+5892=>'L',
+5893=>'L',
+5894=>'L',
+5895=>'L',
+5896=>'L',
+5897=>'L',
+5898=>'L',
+5899=>'L',
+5900=>'L',
+5902=>'L',
+5903=>'L',
+5904=>'L',
+5905=>'L',
+5906=>'NSM',
+5907=>'NSM',
+5908=>'NSM',
+5920=>'L',
+5921=>'L',
+5922=>'L',
+5923=>'L',
+5924=>'L',
+5925=>'L',
+5926=>'L',
+5927=>'L',
+5928=>'L',
+5929=>'L',
+5930=>'L',
+5931=>'L',
+5932=>'L',
+5933=>'L',
+5934=>'L',
+5935=>'L',
+5936=>'L',
+5937=>'L',
+5938=>'NSM',
+5939=>'NSM',
+5940=>'NSM',
+5941=>'L',
+5942=>'L',
+5952=>'L',
+5953=>'L',
+5954=>'L',
+5955=>'L',
+5956=>'L',
+5957=>'L',
+5958=>'L',
+5959=>'L',
+5960=>'L',
+5961=>'L',
+5962=>'L',
+5963=>'L',
+5964=>'L',
+5965=>'L',
+5966=>'L',
+5967=>'L',
+5968=>'L',
+5969=>'L',
+5970=>'NSM',
+5971=>'NSM',
+5984=>'L',
+5985=>'L',
+5986=>'L',
+5987=>'L',
+5988=>'L',
+5989=>'L',
+5990=>'L',
+5991=>'L',
+5992=>'L',
+5993=>'L',
+5994=>'L',
+5995=>'L',
+5996=>'L',
+5998=>'L',
+5999=>'L',
+6000=>'L',
+6002=>'NSM',
+6003=>'NSM',
+6016=>'L',
+6017=>'L',
+6018=>'L',
+6019=>'L',
+6020=>'L',
+6021=>'L',
+6022=>'L',
+6023=>'L',
+6024=>'L',
+6025=>'L',
+6026=>'L',
+6027=>'L',
+6028=>'L',
+6029=>'L',
+6030=>'L',
+6031=>'L',
+6032=>'L',
+6033=>'L',
+6034=>'L',
+6035=>'L',
+6036=>'L',
+6037=>'L',
+6038=>'L',
+6039=>'L',
+6040=>'L',
+6041=>'L',
+6042=>'L',
+6043=>'L',
+6044=>'L',
+6045=>'L',
+6046=>'L',
+6047=>'L',
+6048=>'L',
+6049=>'L',
+6050=>'L',
+6051=>'L',
+6052=>'L',
+6053=>'L',
+6054=>'L',
+6055=>'L',
+6056=>'L',
+6057=>'L',
+6058=>'L',
+6059=>'L',
+6060=>'L',
+6061=>'L',
+6062=>'L',
+6063=>'L',
+6064=>'L',
+6065=>'L',
+6066=>'L',
+6067=>'L',
+6068=>'L',
+6069=>'L',
+6070=>'L',
+6071=>'NSM',
+6072=>'NSM',
+6073=>'NSM',
+6074=>'NSM',
+6075=>'NSM',
+6076=>'NSM',
+6077=>'NSM',
+6078=>'L',
+6079=>'L',
+6080=>'L',
+6081=>'L',
+6082=>'L',
+6083=>'L',
+6084=>'L',
+6085=>'L',
+6086=>'NSM',
+6087=>'L',
+6088=>'L',
+6089=>'NSM',
+6090=>'NSM',
+6091=>'NSM',
+6092=>'NSM',
+6093=>'NSM',
+6094=>'NSM',
+6095=>'NSM',
+6096=>'NSM',
+6097=>'NSM',
+6098=>'NSM',
+6099=>'NSM',
+6100=>'L',
+6101=>'L',
+6102=>'L',
+6103=>'L',
+6104=>'L',
+6105=>'L',
+6106=>'L',
+6107=>'ET',
+6108=>'L',
+6109=>'NSM',
+6112=>'L',
+6113=>'L',
+6114=>'L',
+6115=>'L',
+6116=>'L',
+6117=>'L',
+6118=>'L',
+6119=>'L',
+6120=>'L',
+6121=>'L',
+6128=>'ON',
+6129=>'ON',
+6130=>'ON',
+6131=>'ON',
+6132=>'ON',
+6133=>'ON',
+6134=>'ON',
+6135=>'ON',
+6136=>'ON',
+6137=>'ON',
+6144=>'ON',
+6145=>'ON',
+6146=>'ON',
+6147=>'ON',
+6148=>'ON',
+6149=>'ON',
+6150=>'ON',
+6151=>'ON',
+6152=>'ON',
+6153=>'ON',
+6154=>'ON',
+6155=>'NSM',
+6156=>'NSM',
+6157=>'NSM',
+6158=>'WS',
+6160=>'L',
+6161=>'L',
+6162=>'L',
+6163=>'L',
+6164=>'L',
+6165=>'L',
+6166=>'L',
+6167=>'L',
+6168=>'L',
+6169=>'L',
+6176=>'L',
+6177=>'L',
+6178=>'L',
+6179=>'L',
+6180=>'L',
+6181=>'L',
+6182=>'L',
+6183=>'L',
+6184=>'L',
+6185=>'L',
+6186=>'L',
+6187=>'L',
+6188=>'L',
+6189=>'L',
+6190=>'L',
+6191=>'L',
+6192=>'L',
+6193=>'L',
+6194=>'L',
+6195=>'L',
+6196=>'L',
+6197=>'L',
+6198=>'L',
+6199=>'L',
+6200=>'L',
+6201=>'L',
+6202=>'L',
+6203=>'L',
+6204=>'L',
+6205=>'L',
+6206=>'L',
+6207=>'L',
+6208=>'L',
+6209=>'L',
+6210=>'L',
+6211=>'L',
+6212=>'L',
+6213=>'L',
+6214=>'L',
+6215=>'L',
+6216=>'L',
+6217=>'L',
+6218=>'L',
+6219=>'L',
+6220=>'L',
+6221=>'L',
+6222=>'L',
+6223=>'L',
+6224=>'L',
+6225=>'L',
+6226=>'L',
+6227=>'L',
+6228=>'L',
+6229=>'L',
+6230=>'L',
+6231=>'L',
+6232=>'L',
+6233=>'L',
+6234=>'L',
+6235=>'L',
+6236=>'L',
+6237=>'L',
+6238=>'L',
+6239=>'L',
+6240=>'L',
+6241=>'L',
+6242=>'L',
+6243=>'L',
+6244=>'L',
+6245=>'L',
+6246=>'L',
+6247=>'L',
+6248=>'L',
+6249=>'L',
+6250=>'L',
+6251=>'L',
+6252=>'L',
+6253=>'L',
+6254=>'L',
+6255=>'L',
+6256=>'L',
+6257=>'L',
+6258=>'L',
+6259=>'L',
+6260=>'L',
+6261=>'L',
+6262=>'L',
+6263=>'L',
+6272=>'L',
+6273=>'L',
+6274=>'L',
+6275=>'L',
+6276=>'L',
+6277=>'L',
+6278=>'L',
+6279=>'L',
+6280=>'L',
+6281=>'L',
+6282=>'L',
+6283=>'L',
+6284=>'L',
+6285=>'L',
+6286=>'L',
+6287=>'L',
+6288=>'L',
+6289=>'L',
+6290=>'L',
+6291=>'L',
+6292=>'L',
+6293=>'L',
+6294=>'L',
+6295=>'L',
+6296=>'L',
+6297=>'L',
+6298=>'L',
+6299=>'L',
+6300=>'L',
+6301=>'L',
+6302=>'L',
+6303=>'L',
+6304=>'L',
+6305=>'L',
+6306=>'L',
+6307=>'L',
+6308=>'L',
+6309=>'L',
+6310=>'L',
+6311=>'L',
+6312=>'L',
+6313=>'NSM',
+6400=>'L',
+6401=>'L',
+6402=>'L',
+6403=>'L',
+6404=>'L',
+6405=>'L',
+6406=>'L',
+6407=>'L',
+6408=>'L',
+6409=>'L',
+6410=>'L',
+6411=>'L',
+6412=>'L',
+6413=>'L',
+6414=>'L',
+6415=>'L',
+6416=>'L',
+6417=>'L',
+6418=>'L',
+6419=>'L',
+6420=>'L',
+6421=>'L',
+6422=>'L',
+6423=>'L',
+6424=>'L',
+6425=>'L',
+6426=>'L',
+6427=>'L',
+6428=>'L',
+6432=>'NSM',
+6433=>'NSM',
+6434=>'NSM',
+6435=>'L',
+6436=>'L',
+6437=>'L',
+6438=>'L',
+6439=>'NSM',
+6440=>'NSM',
+6441=>'NSM',
+6442=>'NSM',
+6443=>'NSM',
+6448=>'L',
+6449=>'L',
+6450=>'NSM',
+6451=>'L',
+6452=>'L',
+6453=>'L',
+6454=>'L',
+6455=>'L',
+6456=>'L',
+6457=>'NSM',
+6458=>'NSM',
+6459=>'NSM',
+6464=>'ON',
+6468=>'ON',
+6469=>'ON',
+6470=>'L',
+6471=>'L',
+6472=>'L',
+6473=>'L',
+6474=>'L',
+6475=>'L',
+6476=>'L',
+6477=>'L',
+6478=>'L',
+6479=>'L',
+6480=>'L',
+6481=>'L',
+6482=>'L',
+6483=>'L',
+6484=>'L',
+6485=>'L',
+6486=>'L',
+6487=>'L',
+6488=>'L',
+6489=>'L',
+6490=>'L',
+6491=>'L',
+6492=>'L',
+6493=>'L',
+6494=>'L',
+6495=>'L',
+6496=>'L',
+6497=>'L',
+6498=>'L',
+6499=>'L',
+6500=>'L',
+6501=>'L',
+6502=>'L',
+6503=>'L',
+6504=>'L',
+6505=>'L',
+6506=>'L',
+6507=>'L',
+6508=>'L',
+6509=>'L',
+6512=>'L',
+6513=>'L',
+6514=>'L',
+6515=>'L',
+6516=>'L',
+6528=>'L',
+6529=>'L',
+6530=>'L',
+6531=>'L',
+6532=>'L',
+6533=>'L',
+6534=>'L',
+6535=>'L',
+6536=>'L',
+6537=>'L',
+6538=>'L',
+6539=>'L',
+6540=>'L',
+6541=>'L',
+6542=>'L',
+6543=>'L',
+6544=>'L',
+6545=>'L',
+6546=>'L',
+6547=>'L',
+6548=>'L',
+6549=>'L',
+6550=>'L',
+6551=>'L',
+6552=>'L',
+6553=>'L',
+6554=>'L',
+6555=>'L',
+6556=>'L',
+6557=>'L',
+6558=>'L',
+6559=>'L',
+6560=>'L',
+6561=>'L',
+6562=>'L',
+6563=>'L',
+6564=>'L',
+6565=>'L',
+6566=>'L',
+6567=>'L',
+6568=>'L',
+6569=>'L',
+6576=>'L',
+6577=>'L',
+6578=>'L',
+6579=>'L',
+6580=>'L',
+6581=>'L',
+6582=>'L',
+6583=>'L',
+6584=>'L',
+6585=>'L',
+6586=>'L',
+6587=>'L',
+6588=>'L',
+6589=>'L',
+6590=>'L',
+6591=>'L',
+6592=>'L',
+6593=>'L',
+6594=>'L',
+6595=>'L',
+6596=>'L',
+6597=>'L',
+6598=>'L',
+6599=>'L',
+6600=>'L',
+6601=>'L',
+6608=>'L',
+6609=>'L',
+6610=>'L',
+6611=>'L',
+6612=>'L',
+6613=>'L',
+6614=>'L',
+6615=>'L',
+6616=>'L',
+6617=>'L',
+6622=>'ON',
+6623=>'ON',
+6624=>'ON',
+6625=>'ON',
+6626=>'ON',
+6627=>'ON',
+6628=>'ON',
+6629=>'ON',
+6630=>'ON',
+6631=>'ON',
+6632=>'ON',
+6633=>'ON',
+6634=>'ON',
+6635=>'ON',
+6636=>'ON',
+6637=>'ON',
+6638=>'ON',
+6639=>'ON',
+6640=>'ON',
+6641=>'ON',
+6642=>'ON',
+6643=>'ON',
+6644=>'ON',
+6645=>'ON',
+6646=>'ON',
+6647=>'ON',
+6648=>'ON',
+6649=>'ON',
+6650=>'ON',
+6651=>'ON',
+6652=>'ON',
+6653=>'ON',
+6654=>'ON',
+6655=>'ON',
+6656=>'L',
+6657=>'L',
+6658=>'L',
+6659=>'L',
+6660=>'L',
+6661=>'L',
+6662=>'L',
+6663=>'L',
+6664=>'L',
+6665=>'L',
+6666=>'L',
+6667=>'L',
+6668=>'L',
+6669=>'L',
+6670=>'L',
+6671=>'L',
+6672=>'L',
+6673=>'L',
+6674=>'L',
+6675=>'L',
+6676=>'L',
+6677=>'L',
+6678=>'L',
+6679=>'NSM',
+6680=>'NSM',
+6681=>'L',
+6682=>'L',
+6683=>'L',
+6686=>'L',
+6687=>'L',
+6912=>'NSM',
+6913=>'NSM',
+6914=>'NSM',
+6915=>'NSM',
+6916=>'L',
+6917=>'L',
+6918=>'L',
+6919=>'L',
+6920=>'L',
+6921=>'L',
+6922=>'L',
+6923=>'L',
+6924=>'L',
+6925=>'L',
+6926=>'L',
+6927=>'L',
+6928=>'L',
+6929=>'L',
+6930=>'L',
+6931=>'L',
+6932=>'L',
+6933=>'L',
+6934=>'L',
+6935=>'L',
+6936=>'L',
+6937=>'L',
+6938=>'L',
+6939=>'L',
+6940=>'L',
+6941=>'L',
+6942=>'L',
+6943=>'L',
+6944=>'L',
+6945=>'L',
+6946=>'L',
+6947=>'L',
+6948=>'L',
+6949=>'L',
+6950=>'L',
+6951=>'L',
+6952=>'L',
+6953=>'L',
+6954=>'L',
+6955=>'L',
+6956=>'L',
+6957=>'L',
+6958=>'L',
+6959=>'L',
+6960=>'L',
+6961=>'L',
+6962=>'L',
+6963=>'L',
+6964=>'NSM',
+6965=>'L',
+6966=>'NSM',
+6967=>'NSM',
+6968=>'NSM',
+6969=>'NSM',
+6970=>'NSM',
+6971=>'L',
+6972=>'NSM',
+6973=>'L',
+6974=>'L',
+6975=>'L',
+6976=>'L',
+6977=>'L',
+6978=>'NSM',
+6979=>'L',
+6980=>'L',
+6981=>'L',
+6982=>'L',
+6983=>'L',
+6984=>'L',
+6985=>'L',
+6986=>'L',
+6987=>'L',
+6992=>'L',
+6993=>'L',
+6994=>'L',
+6995=>'L',
+6996=>'L',
+6997=>'L',
+6998=>'L',
+6999=>'L',
+7000=>'L',
+7001=>'L',
+7002=>'L',
+7003=>'L',
+7004=>'L',
+7005=>'L',
+7006=>'L',
+7007=>'L',
+7008=>'L',
+7009=>'L',
+7010=>'L',
+7011=>'L',
+7012=>'L',
+7013=>'L',
+7014=>'L',
+7015=>'L',
+7016=>'L',
+7017=>'L',
+7018=>'L',
+7019=>'NSM',
+7020=>'NSM',
+7021=>'NSM',
+7022=>'NSM',
+7023=>'NSM',
+7024=>'NSM',
+7025=>'NSM',
+7026=>'NSM',
+7027=>'NSM',
+7028=>'L',
+7029=>'L',
+7030=>'L',
+7031=>'L',
+7032=>'L',
+7033=>'L',
+7034=>'L',
+7035=>'L',
+7036=>'L',
+7424=>'L',
+7425=>'L',
+7426=>'L',
+7427=>'L',
+7428=>'L',
+7429=>'L',
+7430=>'L',
+7431=>'L',
+7432=>'L',
+7433=>'L',
+7434=>'L',
+7435=>'L',
+7436=>'L',
+7437=>'L',
+7438=>'L',
+7439=>'L',
+7440=>'L',
+7441=>'L',
+7442=>'L',
+7443=>'L',
+7444=>'L',
+7445=>'L',
+7446=>'L',
+7447=>'L',
+7448=>'L',
+7449=>'L',
+7450=>'L',
+7451=>'L',
+7452=>'L',
+7453=>'L',
+7454=>'L',
+7455=>'L',
+7456=>'L',
+7457=>'L',
+7458=>'L',
+7459=>'L',
+7460=>'L',
+7461=>'L',
+7462=>'L',
+7463=>'L',
+7464=>'L',
+7465=>'L',
+7466=>'L',
+7467=>'L',
+7468=>'L',
+7469=>'L',
+7470=>'L',
+7471=>'L',
+7472=>'L',
+7473=>'L',
+7474=>'L',
+7475=>'L',
+7476=>'L',
+7477=>'L',
+7478=>'L',
+7479=>'L',
+7480=>'L',
+7481=>'L',
+7482=>'L',
+7483=>'L',
+7484=>'L',
+7485=>'L',
+7486=>'L',
+7487=>'L',
+7488=>'L',
+7489=>'L',
+7490=>'L',
+7491=>'L',
+7492=>'L',
+7493=>'L',
+7494=>'L',
+7495=>'L',
+7496=>'L',
+7497=>'L',
+7498=>'L',
+7499=>'L',
+7500=>'L',
+7501=>'L',
+7502=>'L',
+7503=>'L',
+7504=>'L',
+7505=>'L',
+7506=>'L',
+7507=>'L',
+7508=>'L',
+7509=>'L',
+7510=>'L',
+7511=>'L',
+7512=>'L',
+7513=>'L',
+7514=>'L',
+7515=>'L',
+7516=>'L',
+7517=>'L',
+7518=>'L',
+7519=>'L',
+7520=>'L',
+7521=>'L',
+7522=>'L',
+7523=>'L',
+7524=>'L',
+7525=>'L',
+7526=>'L',
+7527=>'L',
+7528=>'L',
+7529=>'L',
+7530=>'L',
+7531=>'L',
+7532=>'L',
+7533=>'L',
+7534=>'L',
+7535=>'L',
+7536=>'L',
+7537=>'L',
+7538=>'L',
+7539=>'L',
+7540=>'L',
+7541=>'L',
+7542=>'L',
+7543=>'L',
+7544=>'L',
+7545=>'L',
+7546=>'L',
+7547=>'L',
+7548=>'L',
+7549=>'L',
+7550=>'L',
+7551=>'L',
+7552=>'L',
+7553=>'L',
+7554=>'L',
+7555=>'L',
+7556=>'L',
+7557=>'L',
+7558=>'L',
+7559=>'L',
+7560=>'L',
+7561=>'L',
+7562=>'L',
+7563=>'L',
+7564=>'L',
+7565=>'L',
+7566=>'L',
+7567=>'L',
+7568=>'L',
+7569=>'L',
+7570=>'L',
+7571=>'L',
+7572=>'L',
+7573=>'L',
+7574=>'L',
+7575=>'L',
+7576=>'L',
+7577=>'L',
+7578=>'L',
+7579=>'L',
+7580=>'L',
+7581=>'L',
+7582=>'L',
+7583=>'L',
+7584=>'L',
+7585=>'L',
+7586=>'L',
+7587=>'L',
+7588=>'L',
+7589=>'L',
+7590=>'L',
+7591=>'L',
+7592=>'L',
+7593=>'L',
+7594=>'L',
+7595=>'L',
+7596=>'L',
+7597=>'L',
+7598=>'L',
+7599=>'L',
+7600=>'L',
+7601=>'L',
+7602=>'L',
+7603=>'L',
+7604=>'L',
+7605=>'L',
+7606=>'L',
+7607=>'L',
+7608=>'L',
+7609=>'L',
+7610=>'L',
+7611=>'L',
+7612=>'L',
+7613=>'L',
+7614=>'L',
+7615=>'L',
+7616=>'NSM',
+7617=>'NSM',
+7618=>'NSM',
+7619=>'NSM',
+7620=>'NSM',
+7621=>'NSM',
+7622=>'NSM',
+7623=>'NSM',
+7624=>'NSM',
+7625=>'NSM',
+7626=>'NSM',
+7678=>'NSM',
+7679=>'NSM',
+7680=>'L',
+7681=>'L',
+7682=>'L',
+7683=>'L',
+7684=>'L',
+7685=>'L',
+7686=>'L',
+7687=>'L',
+7688=>'L',
+7689=>'L',
+7690=>'L',
+7691=>'L',
+7692=>'L',
+7693=>'L',
+7694=>'L',
+7695=>'L',
+7696=>'L',
+7697=>'L',
+7698=>'L',
+7699=>'L',
+7700=>'L',
+7701=>'L',
+7702=>'L',
+7703=>'L',
+7704=>'L',
+7705=>'L',
+7706=>'L',
+7707=>'L',
+7708=>'L',
+7709=>'L',
+7710=>'L',
+7711=>'L',
+7712=>'L',
+7713=>'L',
+7714=>'L',
+7715=>'L',
+7716=>'L',
+7717=>'L',
+7718=>'L',
+7719=>'L',
+7720=>'L',
+7721=>'L',
+7722=>'L',
+7723=>'L',
+7724=>'L',
+7725=>'L',
+7726=>'L',
+7727=>'L',
+7728=>'L',
+7729=>'L',
+7730=>'L',
+7731=>'L',
+7732=>'L',
+7733=>'L',
+7734=>'L',
+7735=>'L',
+7736=>'L',
+7737=>'L',
+7738=>'L',
+7739=>'L',
+7740=>'L',
+7741=>'L',
+7742=>'L',
+7743=>'L',
+7744=>'L',
+7745=>'L',
+7746=>'L',
+7747=>'L',
+7748=>'L',
+7749=>'L',
+7750=>'L',
+7751=>'L',
+7752=>'L',
+7753=>'L',
+7754=>'L',
+7755=>'L',
+7756=>'L',
+7757=>'L',
+7758=>'L',
+7759=>'L',
+7760=>'L',
+7761=>'L',
+7762=>'L',
+7763=>'L',
+7764=>'L',
+7765=>'L',
+7766=>'L',
+7767=>'L',
+7768=>'L',
+7769=>'L',
+7770=>'L',
+7771=>'L',
+7772=>'L',
+7773=>'L',
+7774=>'L',
+7775=>'L',
+7776=>'L',
+7777=>'L',
+7778=>'L',
+7779=>'L',
+7780=>'L',
+7781=>'L',
+7782=>'L',
+7783=>'L',
+7784=>'L',
+7785=>'L',
+7786=>'L',
+7787=>'L',
+7788=>'L',
+7789=>'L',
+7790=>'L',
+7791=>'L',
+7792=>'L',
+7793=>'L',
+7794=>'L',
+7795=>'L',
+7796=>'L',
+7797=>'L',
+7798=>'L',
+7799=>'L',
+7800=>'L',
+7801=>'L',
+7802=>'L',
+7803=>'L',
+7804=>'L',
+7805=>'L',
+7806=>'L',
+7807=>'L',
+7808=>'L',
+7809=>'L',
+7810=>'L',
+7811=>'L',
+7812=>'L',
+7813=>'L',
+7814=>'L',
+7815=>'L',
+7816=>'L',
+7817=>'L',
+7818=>'L',
+7819=>'L',
+7820=>'L',
+7821=>'L',
+7822=>'L',
+7823=>'L',
+7824=>'L',
+7825=>'L',
+7826=>'L',
+7827=>'L',
+7828=>'L',
+7829=>'L',
+7830=>'L',
+7831=>'L',
+7832=>'L',
+7833=>'L',
+7834=>'L',
+7835=>'L',
+7840=>'L',
+7841=>'L',
+7842=>'L',
+7843=>'L',
+7844=>'L',
+7845=>'L',
+7846=>'L',
+7847=>'L',
+7848=>'L',
+7849=>'L',
+7850=>'L',
+7851=>'L',
+7852=>'L',
+7853=>'L',
+7854=>'L',
+7855=>'L',
+7856=>'L',
+7857=>'L',
+7858=>'L',
+7859=>'L',
+7860=>'L',
+7861=>'L',
+7862=>'L',
+7863=>'L',
+7864=>'L',
+7865=>'L',
+7866=>'L',
+7867=>'L',
+7868=>'L',
+7869=>'L',
+7870=>'L',
+7871=>'L',
+7872=>'L',
+7873=>'L',
+7874=>'L',
+7875=>'L',
+7876=>'L',
+7877=>'L',
+7878=>'L',
+7879=>'L',
+7880=>'L',
+7881=>'L',
+7882=>'L',
+7883=>'L',
+7884=>'L',
+7885=>'L',
+7886=>'L',
+7887=>'L',
+7888=>'L',
+7889=>'L',
+7890=>'L',
+7891=>'L',
+7892=>'L',
+7893=>'L',
+7894=>'L',
+7895=>'L',
+7896=>'L',
+7897=>'L',
+7898=>'L',
+7899=>'L',
+7900=>'L',
+7901=>'L',
+7902=>'L',
+7903=>'L',
+7904=>'L',
+7905=>'L',
+7906=>'L',
+7907=>'L',
+7908=>'L',
+7909=>'L',
+7910=>'L',
+7911=>'L',
+7912=>'L',
+7913=>'L',
+7914=>'L',
+7915=>'L',
+7916=>'L',
+7917=>'L',
+7918=>'L',
+7919=>'L',
+7920=>'L',
+7921=>'L',
+7922=>'L',
+7923=>'L',
+7924=>'L',
+7925=>'L',
+7926=>'L',
+7927=>'L',
+7928=>'L',
+7929=>'L',
+7936=>'L',
+7937=>'L',
+7938=>'L',
+7939=>'L',
+7940=>'L',
+7941=>'L',
+7942=>'L',
+7943=>'L',
+7944=>'L',
+7945=>'L',
+7946=>'L',
+7947=>'L',
+7948=>'L',
+7949=>'L',
+7950=>'L',
+7951=>'L',
+7952=>'L',
+7953=>'L',
+7954=>'L',
+7955=>'L',
+7956=>'L',
+7957=>'L',
+7960=>'L',
+7961=>'L',
+7962=>'L',
+7963=>'L',
+7964=>'L',
+7965=>'L',
+7968=>'L',
+7969=>'L',
+7970=>'L',
+7971=>'L',
+7972=>'L',
+7973=>'L',
+7974=>'L',
+7975=>'L',
+7976=>'L',
+7977=>'L',
+7978=>'L',
+7979=>'L',
+7980=>'L',
+7981=>'L',
+7982=>'L',
+7983=>'L',
+7984=>'L',
+7985=>'L',
+7986=>'L',
+7987=>'L',
+7988=>'L',
+7989=>'L',
+7990=>'L',
+7991=>'L',
+7992=>'L',
+7993=>'L',
+7994=>'L',
+7995=>'L',
+7996=>'L',
+7997=>'L',
+7998=>'L',
+7999=>'L',
+8000=>'L',
+8001=>'L',
+8002=>'L',
+8003=>'L',
+8004=>'L',
+8005=>'L',
+8008=>'L',
+8009=>'L',
+8010=>'L',
+8011=>'L',
+8012=>'L',
+8013=>'L',
+8016=>'L',
+8017=>'L',
+8018=>'L',
+8019=>'L',
+8020=>'L',
+8021=>'L',
+8022=>'L',
+8023=>'L',
+8025=>'L',
+8027=>'L',
+8029=>'L',
+8031=>'L',
+8032=>'L',
+8033=>'L',
+8034=>'L',
+8035=>'L',
+8036=>'L',
+8037=>'L',
+8038=>'L',
+8039=>'L',
+8040=>'L',
+8041=>'L',
+8042=>'L',
+8043=>'L',
+8044=>'L',
+8045=>'L',
+8046=>'L',
+8047=>'L',
+8048=>'L',
+8049=>'L',
+8050=>'L',
+8051=>'L',
+8052=>'L',
+8053=>'L',
+8054=>'L',
+8055=>'L',
+8056=>'L',
+8057=>'L',
+8058=>'L',
+8059=>'L',
+8060=>'L',
+8061=>'L',
+8064=>'L',
+8065=>'L',
+8066=>'L',
+8067=>'L',
+8068=>'L',
+8069=>'L',
+8070=>'L',
+8071=>'L',
+8072=>'L',
+8073=>'L',
+8074=>'L',
+8075=>'L',
+8076=>'L',
+8077=>'L',
+8078=>'L',
+8079=>'L',
+8080=>'L',
+8081=>'L',
+8082=>'L',
+8083=>'L',
+8084=>'L',
+8085=>'L',
+8086=>'L',
+8087=>'L',
+8088=>'L',
+8089=>'L',
+8090=>'L',
+8091=>'L',
+8092=>'L',
+8093=>'L',
+8094=>'L',
+8095=>'L',
+8096=>'L',
+8097=>'L',
+8098=>'L',
+8099=>'L',
+8100=>'L',
+8101=>'L',
+8102=>'L',
+8103=>'L',
+8104=>'L',
+8105=>'L',
+8106=>'L',
+8107=>'L',
+8108=>'L',
+8109=>'L',
+8110=>'L',
+8111=>'L',
+8112=>'L',
+8113=>'L',
+8114=>'L',
+8115=>'L',
+8116=>'L',
+8118=>'L',
+8119=>'L',
+8120=>'L',
+8121=>'L',
+8122=>'L',
+8123=>'L',
+8124=>'L',
+8125=>'ON',
+8126=>'L',
+8127=>'ON',
+8128=>'ON',
+8129=>'ON',
+8130=>'L',
+8131=>'L',
+8132=>'L',
+8134=>'L',
+8135=>'L',
+8136=>'L',
+8137=>'L',
+8138=>'L',
+8139=>'L',
+8140=>'L',
+8141=>'ON',
+8142=>'ON',
+8143=>'ON',
+8144=>'L',
+8145=>'L',
+8146=>'L',
+8147=>'L',
+8150=>'L',
+8151=>'L',
+8152=>'L',
+8153=>'L',
+8154=>'L',
+8155=>'L',
+8157=>'ON',
+8158=>'ON',
+8159=>'ON',
+8160=>'L',
+8161=>'L',
+8162=>'L',
+8163=>'L',
+8164=>'L',
+8165=>'L',
+8166=>'L',
+8167=>'L',
+8168=>'L',
+8169=>'L',
+8170=>'L',
+8171=>'L',
+8172=>'L',
+8173=>'ON',
+8174=>'ON',
+8175=>'ON',
+8178=>'L',
+8179=>'L',
+8180=>'L',
+8182=>'L',
+8183=>'L',
+8184=>'L',
+8185=>'L',
+8186=>'L',
+8187=>'L',
+8188=>'L',
+8189=>'ON',
+8190=>'ON',
+8192=>'WS',
+8193=>'WS',
+8194=>'WS',
+8195=>'WS',
+8196=>'WS',
+8197=>'WS',
+8198=>'WS',
+8199=>'WS',
+8200=>'WS',
+8201=>'WS',
+8202=>'WS',
+8203=>'BN',
+8204=>'BN',
+8205=>'BN',
+8206=>'L',
+8207=>'R',
+8208=>'ON',
+8209=>'ON',
+8210=>'ON',
+8211=>'ON',
+8212=>'ON',
+8213=>'ON',
+8214=>'ON',
+8215=>'ON',
+8216=>'ON',
+8217=>'ON',
+8218=>'ON',
+8219=>'ON',
+8220=>'ON',
+8221=>'ON',
+8222=>'ON',
+8223=>'ON',
+8224=>'ON',
+8225=>'ON',
+8226=>'ON',
+8227=>'ON',
+8228=>'ON',
+8229=>'ON',
+8230=>'ON',
+8231=>'ON',
+8232=>'WS',
+8233=>'B',
+8234=>'LRE',
+8235=>'RLE',
+8236=>'PDF',
+8237=>'LRO',
+8238=>'RLO',
+8239=>'CS',
+8240=>'ET',
+8241=>'ET',
+8242=>'ET',
+8243=>'ET',
+8244=>'ET',
+8245=>'ON',
+8246=>'ON',
+8247=>'ON',
+8248=>'ON',
+8249=>'ON',
+8250=>'ON',
+8251=>'ON',
+8252=>'ON',
+8253=>'ON',
+8254=>'ON',
+8255=>'ON',
+8256=>'ON',
+8257=>'ON',
+8258=>'ON',
+8259=>'ON',
+8260=>'CS',
+8261=>'ON',
+8262=>'ON',
+8263=>'ON',
+8264=>'ON',
+8265=>'ON',
+8266=>'ON',
+8267=>'ON',
+8268=>'ON',
+8269=>'ON',
+8270=>'ON',
+8271=>'ON',
+8272=>'ON',
+8273=>'ON',
+8274=>'ON',
+8275=>'ON',
+8276=>'ON',
+8277=>'ON',
+8278=>'ON',
+8279=>'ON',
+8280=>'ON',
+8281=>'ON',
+8282=>'ON',
+8283=>'ON',
+8284=>'ON',
+8285=>'ON',
+8286=>'ON',
+8287=>'WS',
+8288=>'BN',
+8289=>'BN',
+8290=>'BN',
+8291=>'BN',
+8298=>'BN',
+8299=>'BN',
+8300=>'BN',
+8301=>'BN',
+8302=>'BN',
+8303=>'BN',
+8304=>'EN',
+8305=>'L',
+8308=>'EN',
+8309=>'EN',
+8310=>'EN',
+8311=>'EN',
+8312=>'EN',
+8313=>'EN',
+8314=>'ES',
+8315=>'ES',
+8316=>'ON',
+8317=>'ON',
+8318=>'ON',
+8319=>'L',
+8320=>'EN',
+8321=>'EN',
+8322=>'EN',
+8323=>'EN',
+8324=>'EN',
+8325=>'EN',
+8326=>'EN',
+8327=>'EN',
+8328=>'EN',
+8329=>'EN',
+8330=>'ES',
+8331=>'ES',
+8332=>'ON',
+8333=>'ON',
+8334=>'ON',
+8336=>'L',
+8337=>'L',
+8338=>'L',
+8339=>'L',
+8340=>'L',
+8352=>'ET',
+8353=>'ET',
+8354=>'ET',
+8355=>'ET',
+8356=>'ET',
+8357=>'ET',
+8358=>'ET',
+8359=>'ET',
+8360=>'ET',
+8361=>'ET',
+8362=>'ET',
+8363=>'ET',
+8364=>'ET',
+8365=>'ET',
+8366=>'ET',
+8367=>'ET',
+8368=>'ET',
+8369=>'ET',
+8370=>'ET',
+8371=>'ET',
+8372=>'ET',
+8373=>'ET',
+8400=>'NSM',
+8401=>'NSM',
+8402=>'NSM',
+8403=>'NSM',
+8404=>'NSM',
+8405=>'NSM',
+8406=>'NSM',
+8407=>'NSM',
+8408=>'NSM',
+8409=>'NSM',
+8410=>'NSM',
+8411=>'NSM',
+8412=>'NSM',
+8413=>'NSM',
+8414=>'NSM',
+8415=>'NSM',
+8416=>'NSM',
+8417=>'NSM',
+8418=>'NSM',
+8419=>'NSM',
+8420=>'NSM',
+8421=>'NSM',
+8422=>'NSM',
+8423=>'NSM',
+8424=>'NSM',
+8425=>'NSM',
+8426=>'NSM',
+8427=>'NSM',
+8428=>'NSM',
+8429=>'NSM',
+8430=>'NSM',
+8431=>'NSM',
+8448=>'ON',
+8449=>'ON',
+8450=>'L',
+8451=>'ON',
+8452=>'ON',
+8453=>'ON',
+8454=>'ON',
+8455=>'L',
+8456=>'ON',
+8457=>'ON',
+8458=>'L',
+8459=>'L',
+8460=>'L',
+8461=>'L',
+8462=>'L',
+8463=>'L',
+8464=>'L',
+8465=>'L',
+8466=>'L',
+8467=>'L',
+8468=>'ON',
+8469=>'L',
+8470=>'ON',
+8471=>'ON',
+8472=>'ON',
+8473=>'L',
+8474=>'L',
+8475=>'L',
+8476=>'L',
+8477=>'L',
+8478=>'ON',
+8479=>'ON',
+8480=>'ON',
+8481=>'ON',
+8482=>'ON',
+8483=>'ON',
+8484=>'L',
+8485=>'ON',
+8486=>'L',
+8487=>'ON',
+8488=>'L',
+8489=>'ON',
+8490=>'L',
+8491=>'L',
+8492=>'L',
+8493=>'L',
+8494=>'ET',
+8495=>'L',
+8496=>'L',
+8497=>'L',
+8498=>'L',
+8499=>'L',
+8500=>'L',
+8501=>'L',
+8502=>'L',
+8503=>'L',
+8504=>'L',
+8505=>'L',
+8506=>'ON',
+8507=>'ON',
+8508=>'L',
+8509=>'L',
+8510=>'L',
+8511=>'L',
+8512=>'ON',
+8513=>'ON',
+8514=>'ON',
+8515=>'ON',
+8516=>'ON',
+8517=>'L',
+8518=>'L',
+8519=>'L',
+8520=>'L',
+8521=>'L',
+8522=>'ON',
+8523=>'ON',
+8524=>'ON',
+8525=>'ON',
+8526=>'L',
+8531=>'ON',
+8532=>'ON',
+8533=>'ON',
+8534=>'ON',
+8535=>'ON',
+8536=>'ON',
+8537=>'ON',
+8538=>'ON',
+8539=>'ON',
+8540=>'ON',
+8541=>'ON',
+8542=>'ON',
+8543=>'ON',
+8544=>'L',
+8545=>'L',
+8546=>'L',
+8547=>'L',
+8548=>'L',
+8549=>'L',
+8550=>'L',
+8551=>'L',
+8552=>'L',
+8553=>'L',
+8554=>'L',
+8555=>'L',
+8556=>'L',
+8557=>'L',
+8558=>'L',
+8559=>'L',
+8560=>'L',
+8561=>'L',
+8562=>'L',
+8563=>'L',
+8564=>'L',
+8565=>'L',
+8566=>'L',
+8567=>'L',
+8568=>'L',
+8569=>'L',
+8570=>'L',
+8571=>'L',
+8572=>'L',
+8573=>'L',
+8574=>'L',
+8575=>'L',
+8576=>'L',
+8577=>'L',
+8578=>'L',
+8579=>'L',
+8580=>'L',
+8592=>'ON',
+8593=>'ON',
+8594=>'ON',
+8595=>'ON',
+8596=>'ON',
+8597=>'ON',
+8598=>'ON',
+8599=>'ON',
+8600=>'ON',
+8601=>'ON',
+8602=>'ON',
+8603=>'ON',
+8604=>'ON',
+8605=>'ON',
+8606=>'ON',
+8607=>'ON',
+8608=>'ON',
+8609=>'ON',
+8610=>'ON',
+8611=>'ON',
+8612=>'ON',
+8613=>'ON',
+8614=>'ON',
+8615=>'ON',
+8616=>'ON',
+8617=>'ON',
+8618=>'ON',
+8619=>'ON',
+8620=>'ON',
+8621=>'ON',
+8622=>'ON',
+8623=>'ON',
+8624=>'ON',
+8625=>'ON',
+8626=>'ON',
+8627=>'ON',
+8628=>'ON',
+8629=>'ON',
+8630=>'ON',
+8631=>'ON',
+8632=>'ON',
+8633=>'ON',
+8634=>'ON',
+8635=>'ON',
+8636=>'ON',
+8637=>'ON',
+8638=>'ON',
+8639=>'ON',
+8640=>'ON',
+8641=>'ON',
+8642=>'ON',
+8643=>'ON',
+8644=>'ON',
+8645=>'ON',
+8646=>'ON',
+8647=>'ON',
+8648=>'ON',
+8649=>'ON',
+8650=>'ON',
+8651=>'ON',
+8652=>'ON',
+8653=>'ON',
+8654=>'ON',
+8655=>'ON',
+8656=>'ON',
+8657=>'ON',
+8658=>'ON',
+8659=>'ON',
+8660=>'ON',
+8661=>'ON',
+8662=>'ON',
+8663=>'ON',
+8664=>'ON',
+8665=>'ON',
+8666=>'ON',
+8667=>'ON',
+8668=>'ON',
+8669=>'ON',
+8670=>'ON',
+8671=>'ON',
+8672=>'ON',
+8673=>'ON',
+8674=>'ON',
+8675=>'ON',
+8676=>'ON',
+8677=>'ON',
+8678=>'ON',
+8679=>'ON',
+8680=>'ON',
+8681=>'ON',
+8682=>'ON',
+8683=>'ON',
+8684=>'ON',
+8685=>'ON',
+8686=>'ON',
+8687=>'ON',
+8688=>'ON',
+8689=>'ON',
+8690=>'ON',
+8691=>'ON',
+8692=>'ON',
+8693=>'ON',
+8694=>'ON',
+8695=>'ON',
+8696=>'ON',
+8697=>'ON',
+8698=>'ON',
+8699=>'ON',
+8700=>'ON',
+8701=>'ON',
+8702=>'ON',
+8703=>'ON',
+8704=>'ON',
+8705=>'ON',
+8706=>'ON',
+8707=>'ON',
+8708=>'ON',
+8709=>'ON',
+8710=>'ON',
+8711=>'ON',
+8712=>'ON',
+8713=>'ON',
+8714=>'ON',
+8715=>'ON',
+8716=>'ON',
+8717=>'ON',
+8718=>'ON',
+8719=>'ON',
+8720=>'ON',
+8721=>'ON',
+8722=>'ES',
+8723=>'ET',
+8724=>'ON',
+8725=>'ON',
+8726=>'ON',
+8727=>'ON',
+8728=>'ON',
+8729=>'ON',
+8730=>'ON',
+8731=>'ON',
+8732=>'ON',
+8733=>'ON',
+8734=>'ON',
+8735=>'ON',
+8736=>'ON',
+8737=>'ON',
+8738=>'ON',
+8739=>'ON',
+8740=>'ON',
+8741=>'ON',
+8742=>'ON',
+8743=>'ON',
+8744=>'ON',
+8745=>'ON',
+8746=>'ON',
+8747=>'ON',
+8748=>'ON',
+8749=>'ON',
+8750=>'ON',
+8751=>'ON',
+8752=>'ON',
+8753=>'ON',
+8754=>'ON',
+8755=>'ON',
+8756=>'ON',
+8757=>'ON',
+8758=>'ON',
+8759=>'ON',
+8760=>'ON',
+8761=>'ON',
+8762=>'ON',
+8763=>'ON',
+8764=>'ON',
+8765=>'ON',
+8766=>'ON',
+8767=>'ON',
+8768=>'ON',
+8769=>'ON',
+8770=>'ON',
+8771=>'ON',
+8772=>'ON',
+8773=>'ON',
+8774=>'ON',
+8775=>'ON',
+8776=>'ON',
+8777=>'ON',
+8778=>'ON',
+8779=>'ON',
+8780=>'ON',
+8781=>'ON',
+8782=>'ON',
+8783=>'ON',
+8784=>'ON',
+8785=>'ON',
+8786=>'ON',
+8787=>'ON',
+8788=>'ON',
+8789=>'ON',
+8790=>'ON',
+8791=>'ON',
+8792=>'ON',
+8793=>'ON',
+8794=>'ON',
+8795=>'ON',
+8796=>'ON',
+8797=>'ON',
+8798=>'ON',
+8799=>'ON',
+8800=>'ON',
+8801=>'ON',
+8802=>'ON',
+8803=>'ON',
+8804=>'ON',
+8805=>'ON',
+8806=>'ON',
+8807=>'ON',
+8808=>'ON',
+8809=>'ON',
+8810=>'ON',
+8811=>'ON',
+8812=>'ON',
+8813=>'ON',
+8814=>'ON',
+8815=>'ON',
+8816=>'ON',
+8817=>'ON',
+8818=>'ON',
+8819=>'ON',
+8820=>'ON',
+8821=>'ON',
+8822=>'ON',
+8823=>'ON',
+8824=>'ON',
+8825=>'ON',
+8826=>'ON',
+8827=>'ON',
+8828=>'ON',
+8829=>'ON',
+8830=>'ON',
+8831=>'ON',
+8832=>'ON',
+8833=>'ON',
+8834=>'ON',
+8835=>'ON',
+8836=>'ON',
+8837=>'ON',
+8838=>'ON',
+8839=>'ON',
+8840=>'ON',
+8841=>'ON',
+8842=>'ON',
+8843=>'ON',
+8844=>'ON',
+8845=>'ON',
+8846=>'ON',
+8847=>'ON',
+8848=>'ON',
+8849=>'ON',
+8850=>'ON',
+8851=>'ON',
+8852=>'ON',
+8853=>'ON',
+8854=>'ON',
+8855=>'ON',
+8856=>'ON',
+8857=>'ON',
+8858=>'ON',
+8859=>'ON',
+8860=>'ON',
+8861=>'ON',
+8862=>'ON',
+8863=>'ON',
+8864=>'ON',
+8865=>'ON',
+8866=>'ON',
+8867=>'ON',
+8868=>'ON',
+8869=>'ON',
+8870=>'ON',
+8871=>'ON',
+8872=>'ON',
+8873=>'ON',
+8874=>'ON',
+8875=>'ON',
+8876=>'ON',
+8877=>'ON',
+8878=>'ON',
+8879=>'ON',
+8880=>'ON',
+8881=>'ON',
+8882=>'ON',
+8883=>'ON',
+8884=>'ON',
+8885=>'ON',
+8886=>'ON',
+8887=>'ON',
+8888=>'ON',
+8889=>'ON',
+8890=>'ON',
+8891=>'ON',
+8892=>'ON',
+8893=>'ON',
+8894=>'ON',
+8895=>'ON',
+8896=>'ON',
+8897=>'ON',
+8898=>'ON',
+8899=>'ON',
+8900=>'ON',
+8901=>'ON',
+8902=>'ON',
+8903=>'ON',
+8904=>'ON',
+8905=>'ON',
+8906=>'ON',
+8907=>'ON',
+8908=>'ON',
+8909=>'ON',
+8910=>'ON',
+8911=>'ON',
+8912=>'ON',
+8913=>'ON',
+8914=>'ON',
+8915=>'ON',
+8916=>'ON',
+8917=>'ON',
+8918=>'ON',
+8919=>'ON',
+8920=>'ON',
+8921=>'ON',
+8922=>'ON',
+8923=>'ON',
+8924=>'ON',
+8925=>'ON',
+8926=>'ON',
+8927=>'ON',
+8928=>'ON',
+8929=>'ON',
+8930=>'ON',
+8931=>'ON',
+8932=>'ON',
+8933=>'ON',
+8934=>'ON',
+8935=>'ON',
+8936=>'ON',
+8937=>'ON',
+8938=>'ON',
+8939=>'ON',
+8940=>'ON',
+8941=>'ON',
+8942=>'ON',
+8943=>'ON',
+8944=>'ON',
+8945=>'ON',
+8946=>'ON',
+8947=>'ON',
+8948=>'ON',
+8949=>'ON',
+8950=>'ON',
+8951=>'ON',
+8952=>'ON',
+8953=>'ON',
+8954=>'ON',
+8955=>'ON',
+8956=>'ON',
+8957=>'ON',
+8958=>'ON',
+8959=>'ON',
+8960=>'ON',
+8961=>'ON',
+8962=>'ON',
+8963=>'ON',
+8964=>'ON',
+8965=>'ON',
+8966=>'ON',
+8967=>'ON',
+8968=>'ON',
+8969=>'ON',
+8970=>'ON',
+8971=>'ON',
+8972=>'ON',
+8973=>'ON',
+8974=>'ON',
+8975=>'ON',
+8976=>'ON',
+8977=>'ON',
+8978=>'ON',
+8979=>'ON',
+8980=>'ON',
+8981=>'ON',
+8982=>'ON',
+8983=>'ON',
+8984=>'ON',
+8985=>'ON',
+8986=>'ON',
+8987=>'ON',
+8988=>'ON',
+8989=>'ON',
+8990=>'ON',
+8991=>'ON',
+8992=>'ON',
+8993=>'ON',
+8994=>'ON',
+8995=>'ON',
+8996=>'ON',
+8997=>'ON',
+8998=>'ON',
+8999=>'ON',
+9000=>'ON',
+9001=>'ON',
+9002=>'ON',
+9003=>'ON',
+9004=>'ON',
+9005=>'ON',
+9006=>'ON',
+9007=>'ON',
+9008=>'ON',
+9009=>'ON',
+9010=>'ON',
+9011=>'ON',
+9012=>'ON',
+9013=>'ON',
+9014=>'L',
+9015=>'L',
+9016=>'L',
+9017=>'L',
+9018=>'L',
+9019=>'L',
+9020=>'L',
+9021=>'L',
+9022=>'L',
+9023=>'L',
+9024=>'L',
+9025=>'L',
+9026=>'L',
+9027=>'L',
+9028=>'L',
+9029=>'L',
+9030=>'L',
+9031=>'L',
+9032=>'L',
+9033=>'L',
+9034=>'L',
+9035=>'L',
+9036=>'L',
+9037=>'L',
+9038=>'L',
+9039=>'L',
+9040=>'L',
+9041=>'L',
+9042=>'L',
+9043=>'L',
+9044=>'L',
+9045=>'L',
+9046=>'L',
+9047=>'L',
+9048=>'L',
+9049=>'L',
+9050=>'L',
+9051=>'L',
+9052=>'L',
+9053=>'L',
+9054=>'L',
+9055=>'L',
+9056=>'L',
+9057=>'L',
+9058=>'L',
+9059=>'L',
+9060=>'L',
+9061=>'L',
+9062=>'L',
+9063=>'L',
+9064=>'L',
+9065=>'L',
+9066=>'L',
+9067=>'L',
+9068=>'L',
+9069=>'L',
+9070=>'L',
+9071=>'L',
+9072=>'L',
+9073=>'L',
+9074=>'L',
+9075=>'L',
+9076=>'L',
+9077=>'L',
+9078=>'L',
+9079=>'L',
+9080=>'L',
+9081=>'L',
+9082=>'L',
+9083=>'ON',
+9084=>'ON',
+9085=>'ON',
+9086=>'ON',
+9087=>'ON',
+9088=>'ON',
+9089=>'ON',
+9090=>'ON',
+9091=>'ON',
+9092=>'ON',
+9093=>'ON',
+9094=>'ON',
+9095=>'ON',
+9096=>'ON',
+9097=>'ON',
+9098=>'ON',
+9099=>'ON',
+9100=>'ON',
+9101=>'ON',
+9102=>'ON',
+9103=>'ON',
+9104=>'ON',
+9105=>'ON',
+9106=>'ON',
+9107=>'ON',
+9108=>'ON',
+9109=>'L',
+9110=>'ON',
+9111=>'ON',
+9112=>'ON',
+9113=>'ON',
+9114=>'ON',
+9115=>'ON',
+9116=>'ON',
+9117=>'ON',
+9118=>'ON',
+9119=>'ON',
+9120=>'ON',
+9121=>'ON',
+9122=>'ON',
+9123=>'ON',
+9124=>'ON',
+9125=>'ON',
+9126=>'ON',
+9127=>'ON',
+9128=>'ON',
+9129=>'ON',
+9130=>'ON',
+9131=>'ON',
+9132=>'ON',
+9133=>'ON',
+9134=>'ON',
+9135=>'ON',
+9136=>'ON',
+9137=>'ON',
+9138=>'ON',
+9139=>'ON',
+9140=>'ON',
+9141=>'ON',
+9142=>'ON',
+9143=>'ON',
+9144=>'ON',
+9145=>'ON',
+9146=>'ON',
+9147=>'ON',
+9148=>'ON',
+9149=>'ON',
+9150=>'ON',
+9151=>'ON',
+9152=>'ON',
+9153=>'ON',
+9154=>'ON',
+9155=>'ON',
+9156=>'ON',
+9157=>'ON',
+9158=>'ON',
+9159=>'ON',
+9160=>'ON',
+9161=>'ON',
+9162=>'ON',
+9163=>'ON',
+9164=>'ON',
+9165=>'ON',
+9166=>'ON',
+9167=>'ON',
+9168=>'ON',
+9169=>'ON',
+9170=>'ON',
+9171=>'ON',
+9172=>'ON',
+9173=>'ON',
+9174=>'ON',
+9175=>'ON',
+9176=>'ON',
+9177=>'ON',
+9178=>'ON',
+9179=>'ON',
+9180=>'ON',
+9181=>'ON',
+9182=>'ON',
+9183=>'ON',
+9184=>'ON',
+9185=>'ON',
+9186=>'ON',
+9187=>'ON',
+9188=>'ON',
+9189=>'ON',
+9190=>'ON',
+9191=>'ON',
+9216=>'ON',
+9217=>'ON',
+9218=>'ON',
+9219=>'ON',
+9220=>'ON',
+9221=>'ON',
+9222=>'ON',
+9223=>'ON',
+9224=>'ON',
+9225=>'ON',
+9226=>'ON',
+9227=>'ON',
+9228=>'ON',
+9229=>'ON',
+9230=>'ON',
+9231=>'ON',
+9232=>'ON',
+9233=>'ON',
+9234=>'ON',
+9235=>'ON',
+9236=>'ON',
+9237=>'ON',
+9238=>'ON',
+9239=>'ON',
+9240=>'ON',
+9241=>'ON',
+9242=>'ON',
+9243=>'ON',
+9244=>'ON',
+9245=>'ON',
+9246=>'ON',
+9247=>'ON',
+9248=>'ON',
+9249=>'ON',
+9250=>'ON',
+9251=>'ON',
+9252=>'ON',
+9253=>'ON',
+9254=>'ON',
+9280=>'ON',
+9281=>'ON',
+9282=>'ON',
+9283=>'ON',
+9284=>'ON',
+9285=>'ON',
+9286=>'ON',
+9287=>'ON',
+9288=>'ON',
+9289=>'ON',
+9290=>'ON',
+9312=>'ON',
+9313=>'ON',
+9314=>'ON',
+9315=>'ON',
+9316=>'ON',
+9317=>'ON',
+9318=>'ON',
+9319=>'ON',
+9320=>'ON',
+9321=>'ON',
+9322=>'ON',
+9323=>'ON',
+9324=>'ON',
+9325=>'ON',
+9326=>'ON',
+9327=>'ON',
+9328=>'ON',
+9329=>'ON',
+9330=>'ON',
+9331=>'ON',
+9332=>'ON',
+9333=>'ON',
+9334=>'ON',
+9335=>'ON',
+9336=>'ON',
+9337=>'ON',
+9338=>'ON',
+9339=>'ON',
+9340=>'ON',
+9341=>'ON',
+9342=>'ON',
+9343=>'ON',
+9344=>'ON',
+9345=>'ON',
+9346=>'ON',
+9347=>'ON',
+9348=>'ON',
+9349=>'ON',
+9350=>'ON',
+9351=>'ON',
+9352=>'EN',
+9353=>'EN',
+9354=>'EN',
+9355=>'EN',
+9356=>'EN',
+9357=>'EN',
+9358=>'EN',
+9359=>'EN',
+9360=>'EN',
+9361=>'EN',
+9362=>'EN',
+9363=>'EN',
+9364=>'EN',
+9365=>'EN',
+9366=>'EN',
+9367=>'EN',
+9368=>'EN',
+9369=>'EN',
+9370=>'EN',
+9371=>'EN',
+9372=>'L',
+9373=>'L',
+9374=>'L',
+9375=>'L',
+9376=>'L',
+9377=>'L',
+9378=>'L',
+9379=>'L',
+9380=>'L',
+9381=>'L',
+9382=>'L',
+9383=>'L',
+9384=>'L',
+9385=>'L',
+9386=>'L',
+9387=>'L',
+9388=>'L',
+9389=>'L',
+9390=>'L',
+9391=>'L',
+9392=>'L',
+9393=>'L',
+9394=>'L',
+9395=>'L',
+9396=>'L',
+9397=>'L',
+9398=>'L',
+9399=>'L',
+9400=>'L',
+9401=>'L',
+9402=>'L',
+9403=>'L',
+9404=>'L',
+9405=>'L',
+9406=>'L',
+9407=>'L',
+9408=>'L',
+9409=>'L',
+9410=>'L',
+9411=>'L',
+9412=>'L',
+9413=>'L',
+9414=>'L',
+9415=>'L',
+9416=>'L',
+9417=>'L',
+9418=>'L',
+9419=>'L',
+9420=>'L',
+9421=>'L',
+9422=>'L',
+9423=>'L',
+9424=>'L',
+9425=>'L',
+9426=>'L',
+9427=>'L',
+9428=>'L',
+9429=>'L',
+9430=>'L',
+9431=>'L',
+9432=>'L',
+9433=>'L',
+9434=>'L',
+9435=>'L',
+9436=>'L',
+9437=>'L',
+9438=>'L',
+9439=>'L',
+9440=>'L',
+9441=>'L',
+9442=>'L',
+9443=>'L',
+9444=>'L',
+9445=>'L',
+9446=>'L',
+9447=>'L',
+9448=>'L',
+9449=>'L',
+9450=>'ON',
+9451=>'ON',
+9452=>'ON',
+9453=>'ON',
+9454=>'ON',
+9455=>'ON',
+9456=>'ON',
+9457=>'ON',
+9458=>'ON',
+9459=>'ON',
+9460=>'ON',
+9461=>'ON',
+9462=>'ON',
+9463=>'ON',
+9464=>'ON',
+9465=>'ON',
+9466=>'ON',
+9467=>'ON',
+9468=>'ON',
+9469=>'ON',
+9470=>'ON',
+9471=>'ON',
+9472=>'ON',
+9473=>'ON',
+9474=>'ON',
+9475=>'ON',
+9476=>'ON',
+9477=>'ON',
+9478=>'ON',
+9479=>'ON',
+9480=>'ON',
+9481=>'ON',
+9482=>'ON',
+9483=>'ON',
+9484=>'ON',
+9485=>'ON',
+9486=>'ON',
+9487=>'ON',
+9488=>'ON',
+9489=>'ON',
+9490=>'ON',
+9491=>'ON',
+9492=>'ON',
+9493=>'ON',
+9494=>'ON',
+9495=>'ON',
+9496=>'ON',
+9497=>'ON',
+9498=>'ON',
+9499=>'ON',
+9500=>'ON',
+9501=>'ON',
+9502=>'ON',
+9503=>'ON',
+9504=>'ON',
+9505=>'ON',
+9506=>'ON',
+9507=>'ON',
+9508=>'ON',
+9509=>'ON',
+9510=>'ON',
+9511=>'ON',
+9512=>'ON',
+9513=>'ON',
+9514=>'ON',
+9515=>'ON',
+9516=>'ON',
+9517=>'ON',
+9518=>'ON',
+9519=>'ON',
+9520=>'ON',
+9521=>'ON',
+9522=>'ON',
+9523=>'ON',
+9524=>'ON',
+9525=>'ON',
+9526=>'ON',
+9527=>'ON',
+9528=>'ON',
+9529=>'ON',
+9530=>'ON',
+9531=>'ON',
+9532=>'ON',
+9533=>'ON',
+9534=>'ON',
+9535=>'ON',
+9536=>'ON',
+9537=>'ON',
+9538=>'ON',
+9539=>'ON',
+9540=>'ON',
+9541=>'ON',
+9542=>'ON',
+9543=>'ON',
+9544=>'ON',
+9545=>'ON',
+9546=>'ON',
+9547=>'ON',
+9548=>'ON',
+9549=>'ON',
+9550=>'ON',
+9551=>'ON',
+9552=>'ON',
+9553=>'ON',
+9554=>'ON',
+9555=>'ON',
+9556=>'ON',
+9557=>'ON',
+9558=>'ON',
+9559=>'ON',
+9560=>'ON',
+9561=>'ON',
+9562=>'ON',
+9563=>'ON',
+9564=>'ON',
+9565=>'ON',
+9566=>'ON',
+9567=>'ON',
+9568=>'ON',
+9569=>'ON',
+9570=>'ON',
+9571=>'ON',
+9572=>'ON',
+9573=>'ON',
+9574=>'ON',
+9575=>'ON',
+9576=>'ON',
+9577=>'ON',
+9578=>'ON',
+9579=>'ON',
+9580=>'ON',
+9581=>'ON',
+9582=>'ON',
+9583=>'ON',
+9584=>'ON',
+9585=>'ON',
+9586=>'ON',
+9587=>'ON',
+9588=>'ON',
+9589=>'ON',
+9590=>'ON',
+9591=>'ON',
+9592=>'ON',
+9593=>'ON',
+9594=>'ON',
+9595=>'ON',
+9596=>'ON',
+9597=>'ON',
+9598=>'ON',
+9599=>'ON',
+9600=>'ON',
+9601=>'ON',
+9602=>'ON',
+9603=>'ON',
+9604=>'ON',
+9605=>'ON',
+9606=>'ON',
+9607=>'ON',
+9608=>'ON',
+9609=>'ON',
+9610=>'ON',
+9611=>'ON',
+9612=>'ON',
+9613=>'ON',
+9614=>'ON',
+9615=>'ON',
+9616=>'ON',
+9617=>'ON',
+9618=>'ON',
+9619=>'ON',
+9620=>'ON',
+9621=>'ON',
+9622=>'ON',
+9623=>'ON',
+9624=>'ON',
+9625=>'ON',
+9626=>'ON',
+9627=>'ON',
+9628=>'ON',
+9629=>'ON',
+9630=>'ON',
+9631=>'ON',
+9632=>'ON',
+9633=>'ON',
+9634=>'ON',
+9635=>'ON',
+9636=>'ON',
+9637=>'ON',
+9638=>'ON',
+9639=>'ON',
+9640=>'ON',
+9641=>'ON',
+9642=>'ON',
+9643=>'ON',
+9644=>'ON',
+9645=>'ON',
+9646=>'ON',
+9647=>'ON',
+9648=>'ON',
+9649=>'ON',
+9650=>'ON',
+9651=>'ON',
+9652=>'ON',
+9653=>'ON',
+9654=>'ON',
+9655=>'ON',
+9656=>'ON',
+9657=>'ON',
+9658=>'ON',
+9659=>'ON',
+9660=>'ON',
+9661=>'ON',
+9662=>'ON',
+9663=>'ON',
+9664=>'ON',
+9665=>'ON',
+9666=>'ON',
+9667=>'ON',
+9668=>'ON',
+9669=>'ON',
+9670=>'ON',
+9671=>'ON',
+9672=>'ON',
+9673=>'ON',
+9674=>'ON',
+9675=>'ON',
+9676=>'ON',
+9677=>'ON',
+9678=>'ON',
+9679=>'ON',
+9680=>'ON',
+9681=>'ON',
+9682=>'ON',
+9683=>'ON',
+9684=>'ON',
+9685=>'ON',
+9686=>'ON',
+9687=>'ON',
+9688=>'ON',
+9689=>'ON',
+9690=>'ON',
+9691=>'ON',
+9692=>'ON',
+9693=>'ON',
+9694=>'ON',
+9695=>'ON',
+9696=>'ON',
+9697=>'ON',
+9698=>'ON',
+9699=>'ON',
+9700=>'ON',
+9701=>'ON',
+9702=>'ON',
+9703=>'ON',
+9704=>'ON',
+9705=>'ON',
+9706=>'ON',
+9707=>'ON',
+9708=>'ON',
+9709=>'ON',
+9710=>'ON',
+9711=>'ON',
+9712=>'ON',
+9713=>'ON',
+9714=>'ON',
+9715=>'ON',
+9716=>'ON',
+9717=>'ON',
+9718=>'ON',
+9719=>'ON',
+9720=>'ON',
+9721=>'ON',
+9722=>'ON',
+9723=>'ON',
+9724=>'ON',
+9725=>'ON',
+9726=>'ON',
+9727=>'ON',
+9728=>'ON',
+9729=>'ON',
+9730=>'ON',
+9731=>'ON',
+9732=>'ON',
+9733=>'ON',
+9734=>'ON',
+9735=>'ON',
+9736=>'ON',
+9737=>'ON',
+9738=>'ON',
+9739=>'ON',
+9740=>'ON',
+9741=>'ON',
+9742=>'ON',
+9743=>'ON',
+9744=>'ON',
+9745=>'ON',
+9746=>'ON',
+9747=>'ON',
+9748=>'ON',
+9749=>'ON',
+9750=>'ON',
+9751=>'ON',
+9752=>'ON',
+9753=>'ON',
+9754=>'ON',
+9755=>'ON',
+9756=>'ON',
+9757=>'ON',
+9758=>'ON',
+9759=>'ON',
+9760=>'ON',
+9761=>'ON',
+9762=>'ON',
+9763=>'ON',
+9764=>'ON',
+9765=>'ON',
+9766=>'ON',
+9767=>'ON',
+9768=>'ON',
+9769=>'ON',
+9770=>'ON',
+9771=>'ON',
+9772=>'ON',
+9773=>'ON',
+9774=>'ON',
+9775=>'ON',
+9776=>'ON',
+9777=>'ON',
+9778=>'ON',
+9779=>'ON',
+9780=>'ON',
+9781=>'ON',
+9782=>'ON',
+9783=>'ON',
+9784=>'ON',
+9785=>'ON',
+9786=>'ON',
+9787=>'ON',
+9788=>'ON',
+9789=>'ON',
+9790=>'ON',
+9791=>'ON',
+9792=>'ON',
+9793=>'ON',
+9794=>'ON',
+9795=>'ON',
+9796=>'ON',
+9797=>'ON',
+9798=>'ON',
+9799=>'ON',
+9800=>'ON',
+9801=>'ON',
+9802=>'ON',
+9803=>'ON',
+9804=>'ON',
+9805=>'ON',
+9806=>'ON',
+9807=>'ON',
+9808=>'ON',
+9809=>'ON',
+9810=>'ON',
+9811=>'ON',
+9812=>'ON',
+9813=>'ON',
+9814=>'ON',
+9815=>'ON',
+9816=>'ON',
+9817=>'ON',
+9818=>'ON',
+9819=>'ON',
+9820=>'ON',
+9821=>'ON',
+9822=>'ON',
+9823=>'ON',
+9824=>'ON',
+9825=>'ON',
+9826=>'ON',
+9827=>'ON',
+9828=>'ON',
+9829=>'ON',
+9830=>'ON',
+9831=>'ON',
+9832=>'ON',
+9833=>'ON',
+9834=>'ON',
+9835=>'ON',
+9836=>'ON',
+9837=>'ON',
+9838=>'ON',
+9839=>'ON',
+9840=>'ON',
+9841=>'ON',
+9842=>'ON',
+9843=>'ON',
+9844=>'ON',
+9845=>'ON',
+9846=>'ON',
+9847=>'ON',
+9848=>'ON',
+9849=>'ON',
+9850=>'ON',
+9851=>'ON',
+9852=>'ON',
+9853=>'ON',
+9854=>'ON',
+9855=>'ON',
+9856=>'ON',
+9857=>'ON',
+9858=>'ON',
+9859=>'ON',
+9860=>'ON',
+9861=>'ON',
+9862=>'ON',
+9863=>'ON',
+9864=>'ON',
+9865=>'ON',
+9866=>'ON',
+9867=>'ON',
+9868=>'ON',
+9869=>'ON',
+9870=>'ON',
+9871=>'ON',
+9872=>'ON',
+9873=>'ON',
+9874=>'ON',
+9875=>'ON',
+9876=>'ON',
+9877=>'ON',
+9878=>'ON',
+9879=>'ON',
+9880=>'ON',
+9881=>'ON',
+9882=>'ON',
+9883=>'ON',
+9884=>'ON',
+9888=>'ON',
+9889=>'ON',
+9890=>'ON',
+9891=>'ON',
+9892=>'ON',
+9893=>'ON',
+9894=>'ON',
+9895=>'ON',
+9896=>'ON',
+9897=>'ON',
+9898=>'ON',
+9899=>'ON',
+9900=>'L',
+9901=>'ON',
+9902=>'ON',
+9903=>'ON',
+9904=>'ON',
+9905=>'ON',
+9906=>'ON',
+9985=>'ON',
+9986=>'ON',
+9987=>'ON',
+9988=>'ON',
+9990=>'ON',
+9991=>'ON',
+9992=>'ON',
+9993=>'ON',
+9996=>'ON',
+9997=>'ON',
+9998=>'ON',
+9999=>'ON',
+10000=>'ON',
+10001=>'ON',
+10002=>'ON',
+10003=>'ON',
+10004=>'ON',
+10005=>'ON',
+10006=>'ON',
+10007=>'ON',
+10008=>'ON',
+10009=>'ON',
+10010=>'ON',
+10011=>'ON',
+10012=>'ON',
+10013=>'ON',
+10014=>'ON',
+10015=>'ON',
+10016=>'ON',
+10017=>'ON',
+10018=>'ON',
+10019=>'ON',
+10020=>'ON',
+10021=>'ON',
+10022=>'ON',
+10023=>'ON',
+10025=>'ON',
+10026=>'ON',
+10027=>'ON',
+10028=>'ON',
+10029=>'ON',
+10030=>'ON',
+10031=>'ON',
+10032=>'ON',
+10033=>'ON',
+10034=>'ON',
+10035=>'ON',
+10036=>'ON',
+10037=>'ON',
+10038=>'ON',
+10039=>'ON',
+10040=>'ON',
+10041=>'ON',
+10042=>'ON',
+10043=>'ON',
+10044=>'ON',
+10045=>'ON',
+10046=>'ON',
+10047=>'ON',
+10048=>'ON',
+10049=>'ON',
+10050=>'ON',
+10051=>'ON',
+10052=>'ON',
+10053=>'ON',
+10054=>'ON',
+10055=>'ON',
+10056=>'ON',
+10057=>'ON',
+10058=>'ON',
+10059=>'ON',
+10061=>'ON',
+10063=>'ON',
+10064=>'ON',
+10065=>'ON',
+10066=>'ON',
+10070=>'ON',
+10072=>'ON',
+10073=>'ON',
+10074=>'ON',
+10075=>'ON',
+10076=>'ON',
+10077=>'ON',
+10078=>'ON',
+10081=>'ON',
+10082=>'ON',
+10083=>'ON',
+10084=>'ON',
+10085=>'ON',
+10086=>'ON',
+10087=>'ON',
+10088=>'ON',
+10089=>'ON',
+10090=>'ON',
+10091=>'ON',
+10092=>'ON',
+10093=>'ON',
+10094=>'ON',
+10095=>'ON',
+10096=>'ON',
+10097=>'ON',
+10098=>'ON',
+10099=>'ON',
+10100=>'ON',
+10101=>'ON',
+10102=>'ON',
+10103=>'ON',
+10104=>'ON',
+10105=>'ON',
+10106=>'ON',
+10107=>'ON',
+10108=>'ON',
+10109=>'ON',
+10110=>'ON',
+10111=>'ON',
+10112=>'ON',
+10113=>'ON',
+10114=>'ON',
+10115=>'ON',
+10116=>'ON',
+10117=>'ON',
+10118=>'ON',
+10119=>'ON',
+10120=>'ON',
+10121=>'ON',
+10122=>'ON',
+10123=>'ON',
+10124=>'ON',
+10125=>'ON',
+10126=>'ON',
+10127=>'ON',
+10128=>'ON',
+10129=>'ON',
+10130=>'ON',
+10131=>'ON',
+10132=>'ON',
+10136=>'ON',
+10137=>'ON',
+10138=>'ON',
+10139=>'ON',
+10140=>'ON',
+10141=>'ON',
+10142=>'ON',
+10143=>'ON',
+10144=>'ON',
+10145=>'ON',
+10146=>'ON',
+10147=>'ON',
+10148=>'ON',
+10149=>'ON',
+10150=>'ON',
+10151=>'ON',
+10152=>'ON',
+10153=>'ON',
+10154=>'ON',
+10155=>'ON',
+10156=>'ON',
+10157=>'ON',
+10158=>'ON',
+10159=>'ON',
+10161=>'ON',
+10162=>'ON',
+10163=>'ON',
+10164=>'ON',
+10165=>'ON',
+10166=>'ON',
+10167=>'ON',
+10168=>'ON',
+10169=>'ON',
+10170=>'ON',
+10171=>'ON',
+10172=>'ON',
+10173=>'ON',
+10174=>'ON',
+10176=>'ON',
+10177=>'ON',
+10178=>'ON',
+10179=>'ON',
+10180=>'ON',
+10181=>'ON',
+10182=>'ON',
+10183=>'ON',
+10184=>'ON',
+10185=>'ON',
+10186=>'ON',
+10192=>'ON',
+10193=>'ON',
+10194=>'ON',
+10195=>'ON',
+10196=>'ON',
+10197=>'ON',
+10198=>'ON',
+10199=>'ON',
+10200=>'ON',
+10201=>'ON',
+10202=>'ON',
+10203=>'ON',
+10204=>'ON',
+10205=>'ON',
+10206=>'ON',
+10207=>'ON',
+10208=>'ON',
+10209=>'ON',
+10210=>'ON',
+10211=>'ON',
+10212=>'ON',
+10213=>'ON',
+10214=>'ON',
+10215=>'ON',
+10216=>'ON',
+10217=>'ON',
+10218=>'ON',
+10219=>'ON',
+10224=>'ON',
+10225=>'ON',
+10226=>'ON',
+10227=>'ON',
+10228=>'ON',
+10229=>'ON',
+10230=>'ON',
+10231=>'ON',
+10232=>'ON',
+10233=>'ON',
+10234=>'ON',
+10235=>'ON',
+10236=>'ON',
+10237=>'ON',
+10238=>'ON',
+10239=>'ON',
+10240=>'L',
+10241=>'L',
+10242=>'L',
+10243=>'L',
+10244=>'L',
+10245=>'L',
+10246=>'L',
+10247=>'L',
+10248=>'L',
+10249=>'L',
+10250=>'L',
+10251=>'L',
+10252=>'L',
+10253=>'L',
+10254=>'L',
+10255=>'L',
+10256=>'L',
+10257=>'L',
+10258=>'L',
+10259=>'L',
+10260=>'L',
+10261=>'L',
+10262=>'L',
+10263=>'L',
+10264=>'L',
+10265=>'L',
+10266=>'L',
+10267=>'L',
+10268=>'L',
+10269=>'L',
+10270=>'L',
+10271=>'L',
+10272=>'L',
+10273=>'L',
+10274=>'L',
+10275=>'L',
+10276=>'L',
+10277=>'L',
+10278=>'L',
+10279=>'L',
+10280=>'L',
+10281=>'L',
+10282=>'L',
+10283=>'L',
+10284=>'L',
+10285=>'L',
+10286=>'L',
+10287=>'L',
+10288=>'L',
+10289=>'L',
+10290=>'L',
+10291=>'L',
+10292=>'L',
+10293=>'L',
+10294=>'L',
+10295=>'L',
+10296=>'L',
+10297=>'L',
+10298=>'L',
+10299=>'L',
+10300=>'L',
+10301=>'L',
+10302=>'L',
+10303=>'L',
+10304=>'L',
+10305=>'L',
+10306=>'L',
+10307=>'L',
+10308=>'L',
+10309=>'L',
+10310=>'L',
+10311=>'L',
+10312=>'L',
+10313=>'L',
+10314=>'L',
+10315=>'L',
+10316=>'L',
+10317=>'L',
+10318=>'L',
+10319=>'L',
+10320=>'L',
+10321=>'L',
+10322=>'L',
+10323=>'L',
+10324=>'L',
+10325=>'L',
+10326=>'L',
+10327=>'L',
+10328=>'L',
+10329=>'L',
+10330=>'L',
+10331=>'L',
+10332=>'L',
+10333=>'L',
+10334=>'L',
+10335=>'L',
+10336=>'L',
+10337=>'L',
+10338=>'L',
+10339=>'L',
+10340=>'L',
+10341=>'L',
+10342=>'L',
+10343=>'L',
+10344=>'L',
+10345=>'L',
+10346=>'L',
+10347=>'L',
+10348=>'L',
+10349=>'L',
+10350=>'L',
+10351=>'L',
+10352=>'L',
+10353=>'L',
+10354=>'L',
+10355=>'L',
+10356=>'L',
+10357=>'L',
+10358=>'L',
+10359=>'L',
+10360=>'L',
+10361=>'L',
+10362=>'L',
+10363=>'L',
+10364=>'L',
+10365=>'L',
+10366=>'L',
+10367=>'L',
+10368=>'L',
+10369=>'L',
+10370=>'L',
+10371=>'L',
+10372=>'L',
+10373=>'L',
+10374=>'L',
+10375=>'L',
+10376=>'L',
+10377=>'L',
+10378=>'L',
+10379=>'L',
+10380=>'L',
+10381=>'L',
+10382=>'L',
+10383=>'L',
+10384=>'L',
+10385=>'L',
+10386=>'L',
+10387=>'L',
+10388=>'L',
+10389=>'L',
+10390=>'L',
+10391=>'L',
+10392=>'L',
+10393=>'L',
+10394=>'L',
+10395=>'L',
+10396=>'L',
+10397=>'L',
+10398=>'L',
+10399=>'L',
+10400=>'L',
+10401=>'L',
+10402=>'L',
+10403=>'L',
+10404=>'L',
+10405=>'L',
+10406=>'L',
+10407=>'L',
+10408=>'L',
+10409=>'L',
+10410=>'L',
+10411=>'L',
+10412=>'L',
+10413=>'L',
+10414=>'L',
+10415=>'L',
+10416=>'L',
+10417=>'L',
+10418=>'L',
+10419=>'L',
+10420=>'L',
+10421=>'L',
+10422=>'L',
+10423=>'L',
+10424=>'L',
+10425=>'L',
+10426=>'L',
+10427=>'L',
+10428=>'L',
+10429=>'L',
+10430=>'L',
+10431=>'L',
+10432=>'L',
+10433=>'L',
+10434=>'L',
+10435=>'L',
+10436=>'L',
+10437=>'L',
+10438=>'L',
+10439=>'L',
+10440=>'L',
+10441=>'L',
+10442=>'L',
+10443=>'L',
+10444=>'L',
+10445=>'L',
+10446=>'L',
+10447=>'L',
+10448=>'L',
+10449=>'L',
+10450=>'L',
+10451=>'L',
+10452=>'L',
+10453=>'L',
+10454=>'L',
+10455=>'L',
+10456=>'L',
+10457=>'L',
+10458=>'L',
+10459=>'L',
+10460=>'L',
+10461=>'L',
+10462=>'L',
+10463=>'L',
+10464=>'L',
+10465=>'L',
+10466=>'L',
+10467=>'L',
+10468=>'L',
+10469=>'L',
+10470=>'L',
+10471=>'L',
+10472=>'L',
+10473=>'L',
+10474=>'L',
+10475=>'L',
+10476=>'L',
+10477=>'L',
+10478=>'L',
+10479=>'L',
+10480=>'L',
+10481=>'L',
+10482=>'L',
+10483=>'L',
+10484=>'L',
+10485=>'L',
+10486=>'L',
+10487=>'L',
+10488=>'L',
+10489=>'L',
+10490=>'L',
+10491=>'L',
+10492=>'L',
+10493=>'L',
+10494=>'L',
+10495=>'L',
+10496=>'ON',
+10497=>'ON',
+10498=>'ON',
+10499=>'ON',
+10500=>'ON',
+10501=>'ON',
+10502=>'ON',
+10503=>'ON',
+10504=>'ON',
+10505=>'ON',
+10506=>'ON',
+10507=>'ON',
+10508=>'ON',
+10509=>'ON',
+10510=>'ON',
+10511=>'ON',
+10512=>'ON',
+10513=>'ON',
+10514=>'ON',
+10515=>'ON',
+10516=>'ON',
+10517=>'ON',
+10518=>'ON',
+10519=>'ON',
+10520=>'ON',
+10521=>'ON',
+10522=>'ON',
+10523=>'ON',
+10524=>'ON',
+10525=>'ON',
+10526=>'ON',
+10527=>'ON',
+10528=>'ON',
+10529=>'ON',
+10530=>'ON',
+10531=>'ON',
+10532=>'ON',
+10533=>'ON',
+10534=>'ON',
+10535=>'ON',
+10536=>'ON',
+10537=>'ON',
+10538=>'ON',
+10539=>'ON',
+10540=>'ON',
+10541=>'ON',
+10542=>'ON',
+10543=>'ON',
+10544=>'ON',
+10545=>'ON',
+10546=>'ON',
+10547=>'ON',
+10548=>'ON',
+10549=>'ON',
+10550=>'ON',
+10551=>'ON',
+10552=>'ON',
+10553=>'ON',
+10554=>'ON',
+10555=>'ON',
+10556=>'ON',
+10557=>'ON',
+10558=>'ON',
+10559=>'ON',
+10560=>'ON',
+10561=>'ON',
+10562=>'ON',
+10563=>'ON',
+10564=>'ON',
+10565=>'ON',
+10566=>'ON',
+10567=>'ON',
+10568=>'ON',
+10569=>'ON',
+10570=>'ON',
+10571=>'ON',
+10572=>'ON',
+10573=>'ON',
+10574=>'ON',
+10575=>'ON',
+10576=>'ON',
+10577=>'ON',
+10578=>'ON',
+10579=>'ON',
+10580=>'ON',
+10581=>'ON',
+10582=>'ON',
+10583=>'ON',
+10584=>'ON',
+10585=>'ON',
+10586=>'ON',
+10587=>'ON',
+10588=>'ON',
+10589=>'ON',
+10590=>'ON',
+10591=>'ON',
+10592=>'ON',
+10593=>'ON',
+10594=>'ON',
+10595=>'ON',
+10596=>'ON',
+10597=>'ON',
+10598=>'ON',
+10599=>'ON',
+10600=>'ON',
+10601=>'ON',
+10602=>'ON',
+10603=>'ON',
+10604=>'ON',
+10605=>'ON',
+10606=>'ON',
+10607=>'ON',
+10608=>'ON',
+10609=>'ON',
+10610=>'ON',
+10611=>'ON',
+10612=>'ON',
+10613=>'ON',
+10614=>'ON',
+10615=>'ON',
+10616=>'ON',
+10617=>'ON',
+10618=>'ON',
+10619=>'ON',
+10620=>'ON',
+10621=>'ON',
+10622=>'ON',
+10623=>'ON',
+10624=>'ON',
+10625=>'ON',
+10626=>'ON',
+10627=>'ON',
+10628=>'ON',
+10629=>'ON',
+10630=>'ON',
+10631=>'ON',
+10632=>'ON',
+10633=>'ON',
+10634=>'ON',
+10635=>'ON',
+10636=>'ON',
+10637=>'ON',
+10638=>'ON',
+10639=>'ON',
+10640=>'ON',
+10641=>'ON',
+10642=>'ON',
+10643=>'ON',
+10644=>'ON',
+10645=>'ON',
+10646=>'ON',
+10647=>'ON',
+10648=>'ON',
+10649=>'ON',
+10650=>'ON',
+10651=>'ON',
+10652=>'ON',
+10653=>'ON',
+10654=>'ON',
+10655=>'ON',
+10656=>'ON',
+10657=>'ON',
+10658=>'ON',
+10659=>'ON',
+10660=>'ON',
+10661=>'ON',
+10662=>'ON',
+10663=>'ON',
+10664=>'ON',
+10665=>'ON',
+10666=>'ON',
+10667=>'ON',
+10668=>'ON',
+10669=>'ON',
+10670=>'ON',
+10671=>'ON',
+10672=>'ON',
+10673=>'ON',
+10674=>'ON',
+10675=>'ON',
+10676=>'ON',
+10677=>'ON',
+10678=>'ON',
+10679=>'ON',
+10680=>'ON',
+10681=>'ON',
+10682=>'ON',
+10683=>'ON',
+10684=>'ON',
+10685=>'ON',
+10686=>'ON',
+10687=>'ON',
+10688=>'ON',
+10689=>'ON',
+10690=>'ON',
+10691=>'ON',
+10692=>'ON',
+10693=>'ON',
+10694=>'ON',
+10695=>'ON',
+10696=>'ON',
+10697=>'ON',
+10698=>'ON',
+10699=>'ON',
+10700=>'ON',
+10701=>'ON',
+10702=>'ON',
+10703=>'ON',
+10704=>'ON',
+10705=>'ON',
+10706=>'ON',
+10707=>'ON',
+10708=>'ON',
+10709=>'ON',
+10710=>'ON',
+10711=>'ON',
+10712=>'ON',
+10713=>'ON',
+10714=>'ON',
+10715=>'ON',
+10716=>'ON',
+10717=>'ON',
+10718=>'ON',
+10719=>'ON',
+10720=>'ON',
+10721=>'ON',
+10722=>'ON',
+10723=>'ON',
+10724=>'ON',
+10725=>'ON',
+10726=>'ON',
+10727=>'ON',
+10728=>'ON',
+10729=>'ON',
+10730=>'ON',
+10731=>'ON',
+10732=>'ON',
+10733=>'ON',
+10734=>'ON',
+10735=>'ON',
+10736=>'ON',
+10737=>'ON',
+10738=>'ON',
+10739=>'ON',
+10740=>'ON',
+10741=>'ON',
+10742=>'ON',
+10743=>'ON',
+10744=>'ON',
+10745=>'ON',
+10746=>'ON',
+10747=>'ON',
+10748=>'ON',
+10749=>'ON',
+10750=>'ON',
+10751=>'ON',
+10752=>'ON',
+10753=>'ON',
+10754=>'ON',
+10755=>'ON',
+10756=>'ON',
+10757=>'ON',
+10758=>'ON',
+10759=>'ON',
+10760=>'ON',
+10761=>'ON',
+10762=>'ON',
+10763=>'ON',
+10764=>'ON',
+10765=>'ON',
+10766=>'ON',
+10767=>'ON',
+10768=>'ON',
+10769=>'ON',
+10770=>'ON',
+10771=>'ON',
+10772=>'ON',
+10773=>'ON',
+10774=>'ON',
+10775=>'ON',
+10776=>'ON',
+10777=>'ON',
+10778=>'ON',
+10779=>'ON',
+10780=>'ON',
+10781=>'ON',
+10782=>'ON',
+10783=>'ON',
+10784=>'ON',
+10785=>'ON',
+10786=>'ON',
+10787=>'ON',
+10788=>'ON',
+10789=>'ON',
+10790=>'ON',
+10791=>'ON',
+10792=>'ON',
+10793=>'ON',
+10794=>'ON',
+10795=>'ON',
+10796=>'ON',
+10797=>'ON',
+10798=>'ON',
+10799=>'ON',
+10800=>'ON',
+10801=>'ON',
+10802=>'ON',
+10803=>'ON',
+10804=>'ON',
+10805=>'ON',
+10806=>'ON',
+10807=>'ON',
+10808=>'ON',
+10809=>'ON',
+10810=>'ON',
+10811=>'ON',
+10812=>'ON',
+10813=>'ON',
+10814=>'ON',
+10815=>'ON',
+10816=>'ON',
+10817=>'ON',
+10818=>'ON',
+10819=>'ON',
+10820=>'ON',
+10821=>'ON',
+10822=>'ON',
+10823=>'ON',
+10824=>'ON',
+10825=>'ON',
+10826=>'ON',
+10827=>'ON',
+10828=>'ON',
+10829=>'ON',
+10830=>'ON',
+10831=>'ON',
+10832=>'ON',
+10833=>'ON',
+10834=>'ON',
+10835=>'ON',
+10836=>'ON',
+10837=>'ON',
+10838=>'ON',
+10839=>'ON',
+10840=>'ON',
+10841=>'ON',
+10842=>'ON',
+10843=>'ON',
+10844=>'ON',
+10845=>'ON',
+10846=>'ON',
+10847=>'ON',
+10848=>'ON',
+10849=>'ON',
+10850=>'ON',
+10851=>'ON',
+10852=>'ON',
+10853=>'ON',
+10854=>'ON',
+10855=>'ON',
+10856=>'ON',
+10857=>'ON',
+10858=>'ON',
+10859=>'ON',
+10860=>'ON',
+10861=>'ON',
+10862=>'ON',
+10863=>'ON',
+10864=>'ON',
+10865=>'ON',
+10866=>'ON',
+10867=>'ON',
+10868=>'ON',
+10869=>'ON',
+10870=>'ON',
+10871=>'ON',
+10872=>'ON',
+10873=>'ON',
+10874=>'ON',
+10875=>'ON',
+10876=>'ON',
+10877=>'ON',
+10878=>'ON',
+10879=>'ON',
+10880=>'ON',
+10881=>'ON',
+10882=>'ON',
+10883=>'ON',
+10884=>'ON',
+10885=>'ON',
+10886=>'ON',
+10887=>'ON',
+10888=>'ON',
+10889=>'ON',
+10890=>'ON',
+10891=>'ON',
+10892=>'ON',
+10893=>'ON',
+10894=>'ON',
+10895=>'ON',
+10896=>'ON',
+10897=>'ON',
+10898=>'ON',
+10899=>'ON',
+10900=>'ON',
+10901=>'ON',
+10902=>'ON',
+10903=>'ON',
+10904=>'ON',
+10905=>'ON',
+10906=>'ON',
+10907=>'ON',
+10908=>'ON',
+10909=>'ON',
+10910=>'ON',
+10911=>'ON',
+10912=>'ON',
+10913=>'ON',
+10914=>'ON',
+10915=>'ON',
+10916=>'ON',
+10917=>'ON',
+10918=>'ON',
+10919=>'ON',
+10920=>'ON',
+10921=>'ON',
+10922=>'ON',
+10923=>'ON',
+10924=>'ON',
+10925=>'ON',
+10926=>'ON',
+10927=>'ON',
+10928=>'ON',
+10929=>'ON',
+10930=>'ON',
+10931=>'ON',
+10932=>'ON',
+10933=>'ON',
+10934=>'ON',
+10935=>'ON',
+10936=>'ON',
+10937=>'ON',
+10938=>'ON',
+10939=>'ON',
+10940=>'ON',
+10941=>'ON',
+10942=>'ON',
+10943=>'ON',
+10944=>'ON',
+10945=>'ON',
+10946=>'ON',
+10947=>'ON',
+10948=>'ON',
+10949=>'ON',
+10950=>'ON',
+10951=>'ON',
+10952=>'ON',
+10953=>'ON',
+10954=>'ON',
+10955=>'ON',
+10956=>'ON',
+10957=>'ON',
+10958=>'ON',
+10959=>'ON',
+10960=>'ON',
+10961=>'ON',
+10962=>'ON',
+10963=>'ON',
+10964=>'ON',
+10965=>'ON',
+10966=>'ON',
+10967=>'ON',
+10968=>'ON',
+10969=>'ON',
+10970=>'ON',
+10971=>'ON',
+10972=>'ON',
+10973=>'ON',
+10974=>'ON',
+10975=>'ON',
+10976=>'ON',
+10977=>'ON',
+10978=>'ON',
+10979=>'ON',
+10980=>'ON',
+10981=>'ON',
+10982=>'ON',
+10983=>'ON',
+10984=>'ON',
+10985=>'ON',
+10986=>'ON',
+10987=>'ON',
+10988=>'ON',
+10989=>'ON',
+10990=>'ON',
+10991=>'ON',
+10992=>'ON',
+10993=>'ON',
+10994=>'ON',
+10995=>'ON',
+10996=>'ON',
+10997=>'ON',
+10998=>'ON',
+10999=>'ON',
+11000=>'ON',
+11001=>'ON',
+11002=>'ON',
+11003=>'ON',
+11004=>'ON',
+11005=>'ON',
+11006=>'ON',
+11007=>'ON',
+11008=>'ON',
+11009=>'ON',
+11010=>'ON',
+11011=>'ON',
+11012=>'ON',
+11013=>'ON',
+11014=>'ON',
+11015=>'ON',
+11016=>'ON',
+11017=>'ON',
+11018=>'ON',
+11019=>'ON',
+11020=>'ON',
+11021=>'ON',
+11022=>'ON',
+11023=>'ON',
+11024=>'ON',
+11025=>'ON',
+11026=>'ON',
+11027=>'ON',
+11028=>'ON',
+11029=>'ON',
+11030=>'ON',
+11031=>'ON',
+11032=>'ON',
+11033=>'ON',
+11034=>'ON',
+11040=>'ON',
+11041=>'ON',
+11042=>'ON',
+11043=>'ON',
+11264=>'L',
+11265=>'L',
+11266=>'L',
+11267=>'L',
+11268=>'L',
+11269=>'L',
+11270=>'L',
+11271=>'L',
+11272=>'L',
+11273=>'L',
+11274=>'L',
+11275=>'L',
+11276=>'L',
+11277=>'L',
+11278=>'L',
+11279=>'L',
+11280=>'L',
+11281=>'L',
+11282=>'L',
+11283=>'L',
+11284=>'L',
+11285=>'L',
+11286=>'L',
+11287=>'L',
+11288=>'L',
+11289=>'L',
+11290=>'L',
+11291=>'L',
+11292=>'L',
+11293=>'L',
+11294=>'L',
+11295=>'L',
+11296=>'L',
+11297=>'L',
+11298=>'L',
+11299=>'L',
+11300=>'L',
+11301=>'L',
+11302=>'L',
+11303=>'L',
+11304=>'L',
+11305=>'L',
+11306=>'L',
+11307=>'L',
+11308=>'L',
+11309=>'L',
+11310=>'L',
+11312=>'L',
+11313=>'L',
+11314=>'L',
+11315=>'L',
+11316=>'L',
+11317=>'L',
+11318=>'L',
+11319=>'L',
+11320=>'L',
+11321=>'L',
+11322=>'L',
+11323=>'L',
+11324=>'L',
+11325=>'L',
+11326=>'L',
+11327=>'L',
+11328=>'L',
+11329=>'L',
+11330=>'L',
+11331=>'L',
+11332=>'L',
+11333=>'L',
+11334=>'L',
+11335=>'L',
+11336=>'L',
+11337=>'L',
+11338=>'L',
+11339=>'L',
+11340=>'L',
+11341=>'L',
+11342=>'L',
+11343=>'L',
+11344=>'L',
+11345=>'L',
+11346=>'L',
+11347=>'L',
+11348=>'L',
+11349=>'L',
+11350=>'L',
+11351=>'L',
+11352=>'L',
+11353=>'L',
+11354=>'L',
+11355=>'L',
+11356=>'L',
+11357=>'L',
+11358=>'L',
+11360=>'L',
+11361=>'L',
+11362=>'L',
+11363=>'L',
+11364=>'L',
+11365=>'L',
+11366=>'L',
+11367=>'L',
+11368=>'L',
+11369=>'L',
+11370=>'L',
+11371=>'L',
+11372=>'L',
+11380=>'L',
+11381=>'L',
+11382=>'L',
+11383=>'L',
+11392=>'L',
+11393=>'L',
+11394=>'L',
+11395=>'L',
+11396=>'L',
+11397=>'L',
+11398=>'L',
+11399=>'L',
+11400=>'L',
+11401=>'L',
+11402=>'L',
+11403=>'L',
+11404=>'L',
+11405=>'L',
+11406=>'L',
+11407=>'L',
+11408=>'L',
+11409=>'L',
+11410=>'L',
+11411=>'L',
+11412=>'L',
+11413=>'L',
+11414=>'L',
+11415=>'L',
+11416=>'L',
+11417=>'L',
+11418=>'L',
+11419=>'L',
+11420=>'L',
+11421=>'L',
+11422=>'L',
+11423=>'L',
+11424=>'L',
+11425=>'L',
+11426=>'L',
+11427=>'L',
+11428=>'L',
+11429=>'L',
+11430=>'L',
+11431=>'L',
+11432=>'L',
+11433=>'L',
+11434=>'L',
+11435=>'L',
+11436=>'L',
+11437=>'L',
+11438=>'L',
+11439=>'L',
+11440=>'L',
+11441=>'L',
+11442=>'L',
+11443=>'L',
+11444=>'L',
+11445=>'L',
+11446=>'L',
+11447=>'L',
+11448=>'L',
+11449=>'L',
+11450=>'L',
+11451=>'L',
+11452=>'L',
+11453=>'L',
+11454=>'L',
+11455=>'L',
+11456=>'L',
+11457=>'L',
+11458=>'L',
+11459=>'L',
+11460=>'L',
+11461=>'L',
+11462=>'L',
+11463=>'L',
+11464=>'L',
+11465=>'L',
+11466=>'L',
+11467=>'L',
+11468=>'L',
+11469=>'L',
+11470=>'L',
+11471=>'L',
+11472=>'L',
+11473=>'L',
+11474=>'L',
+11475=>'L',
+11476=>'L',
+11477=>'L',
+11478=>'L',
+11479=>'L',
+11480=>'L',
+11481=>'L',
+11482=>'L',
+11483=>'L',
+11484=>'L',
+11485=>'L',
+11486=>'L',
+11487=>'L',
+11488=>'L',
+11489=>'L',
+11490=>'L',
+11491=>'L',
+11492=>'L',
+11493=>'ON',
+11494=>'ON',
+11495=>'ON',
+11496=>'ON',
+11497=>'ON',
+11498=>'ON',
+11513=>'ON',
+11514=>'ON',
+11515=>'ON',
+11516=>'ON',
+11517=>'ON',
+11518=>'ON',
+11519=>'ON',
+11520=>'L',
+11521=>'L',
+11522=>'L',
+11523=>'L',
+11524=>'L',
+11525=>'L',
+11526=>'L',
+11527=>'L',
+11528=>'L',
+11529=>'L',
+11530=>'L',
+11531=>'L',
+11532=>'L',
+11533=>'L',
+11534=>'L',
+11535=>'L',
+11536=>'L',
+11537=>'L',
+11538=>'L',
+11539=>'L',
+11540=>'L',
+11541=>'L',
+11542=>'L',
+11543=>'L',
+11544=>'L',
+11545=>'L',
+11546=>'L',
+11547=>'L',
+11548=>'L',
+11549=>'L',
+11550=>'L',
+11551=>'L',
+11552=>'L',
+11553=>'L',
+11554=>'L',
+11555=>'L',
+11556=>'L',
+11557=>'L',
+11568=>'L',
+11569=>'L',
+11570=>'L',
+11571=>'L',
+11572=>'L',
+11573=>'L',
+11574=>'L',
+11575=>'L',
+11576=>'L',
+11577=>'L',
+11578=>'L',
+11579=>'L',
+11580=>'L',
+11581=>'L',
+11582=>'L',
+11583=>'L',
+11584=>'L',
+11585=>'L',
+11586=>'L',
+11587=>'L',
+11588=>'L',
+11589=>'L',
+11590=>'L',
+11591=>'L',
+11592=>'L',
+11593=>'L',
+11594=>'L',
+11595=>'L',
+11596=>'L',
+11597=>'L',
+11598=>'L',
+11599=>'L',
+11600=>'L',
+11601=>'L',
+11602=>'L',
+11603=>'L',
+11604=>'L',
+11605=>'L',
+11606=>'L',
+11607=>'L',
+11608=>'L',
+11609=>'L',
+11610=>'L',
+11611=>'L',
+11612=>'L',
+11613=>'L',
+11614=>'L',
+11615=>'L',
+11616=>'L',
+11617=>'L',
+11618=>'L',
+11619=>'L',
+11620=>'L',
+11621=>'L',
+11631=>'L',
+11648=>'L',
+11649=>'L',
+11650=>'L',
+11651=>'L',
+11652=>'L',
+11653=>'L',
+11654=>'L',
+11655=>'L',
+11656=>'L',
+11657=>'L',
+11658=>'L',
+11659=>'L',
+11660=>'L',
+11661=>'L',
+11662=>'L',
+11663=>'L',
+11664=>'L',
+11665=>'L',
+11666=>'L',
+11667=>'L',
+11668=>'L',
+11669=>'L',
+11670=>'L',
+11680=>'L',
+11681=>'L',
+11682=>'L',
+11683=>'L',
+11684=>'L',
+11685=>'L',
+11686=>'L',
+11688=>'L',
+11689=>'L',
+11690=>'L',
+11691=>'L',
+11692=>'L',
+11693=>'L',
+11694=>'L',
+11696=>'L',
+11697=>'L',
+11698=>'L',
+11699=>'L',
+11700=>'L',
+11701=>'L',
+11702=>'L',
+11704=>'L',
+11705=>'L',
+11706=>'L',
+11707=>'L',
+11708=>'L',
+11709=>'L',
+11710=>'L',
+11712=>'L',
+11713=>'L',
+11714=>'L',
+11715=>'L',
+11716=>'L',
+11717=>'L',
+11718=>'L',
+11720=>'L',
+11721=>'L',
+11722=>'L',
+11723=>'L',
+11724=>'L',
+11725=>'L',
+11726=>'L',
+11728=>'L',
+11729=>'L',
+11730=>'L',
+11731=>'L',
+11732=>'L',
+11733=>'L',
+11734=>'L',
+11736=>'L',
+11737=>'L',
+11738=>'L',
+11739=>'L',
+11740=>'L',
+11741=>'L',
+11742=>'L',
+11776=>'ON',
+11777=>'ON',
+11778=>'ON',
+11779=>'ON',
+11780=>'ON',
+11781=>'ON',
+11782=>'ON',
+11783=>'ON',
+11784=>'ON',
+11785=>'ON',
+11786=>'ON',
+11787=>'ON',
+11788=>'ON',
+11789=>'ON',
+11790=>'ON',
+11791=>'ON',
+11792=>'ON',
+11793=>'ON',
+11794=>'ON',
+11795=>'ON',
+11796=>'ON',
+11797=>'ON',
+11798=>'ON',
+11799=>'ON',
+11804=>'ON',
+11805=>'ON',
+11904=>'ON',
+11905=>'ON',
+11906=>'ON',
+11907=>'ON',
+11908=>'ON',
+11909=>'ON',
+11910=>'ON',
+11911=>'ON',
+11912=>'ON',
+11913=>'ON',
+11914=>'ON',
+11915=>'ON',
+11916=>'ON',
+11917=>'ON',
+11918=>'ON',
+11919=>'ON',
+11920=>'ON',
+11921=>'ON',
+11922=>'ON',
+11923=>'ON',
+11924=>'ON',
+11925=>'ON',
+11926=>'ON',
+11927=>'ON',
+11928=>'ON',
+11929=>'ON',
+11931=>'ON',
+11932=>'ON',
+11933=>'ON',
+11934=>'ON',
+11935=>'ON',
+11936=>'ON',
+11937=>'ON',
+11938=>'ON',
+11939=>'ON',
+11940=>'ON',
+11941=>'ON',
+11942=>'ON',
+11943=>'ON',
+11944=>'ON',
+11945=>'ON',
+11946=>'ON',
+11947=>'ON',
+11948=>'ON',
+11949=>'ON',
+11950=>'ON',
+11951=>'ON',
+11952=>'ON',
+11953=>'ON',
+11954=>'ON',
+11955=>'ON',
+11956=>'ON',
+11957=>'ON',
+11958=>'ON',
+11959=>'ON',
+11960=>'ON',
+11961=>'ON',
+11962=>'ON',
+11963=>'ON',
+11964=>'ON',
+11965=>'ON',
+11966=>'ON',
+11967=>'ON',
+11968=>'ON',
+11969=>'ON',
+11970=>'ON',
+11971=>'ON',
+11972=>'ON',
+11973=>'ON',
+11974=>'ON',
+11975=>'ON',
+11976=>'ON',
+11977=>'ON',
+11978=>'ON',
+11979=>'ON',
+11980=>'ON',
+11981=>'ON',
+11982=>'ON',
+11983=>'ON',
+11984=>'ON',
+11985=>'ON',
+11986=>'ON',
+11987=>'ON',
+11988=>'ON',
+11989=>'ON',
+11990=>'ON',
+11991=>'ON',
+11992=>'ON',
+11993=>'ON',
+11994=>'ON',
+11995=>'ON',
+11996=>'ON',
+11997=>'ON',
+11998=>'ON',
+11999=>'ON',
+12000=>'ON',
+12001=>'ON',
+12002=>'ON',
+12003=>'ON',
+12004=>'ON',
+12005=>'ON',
+12006=>'ON',
+12007=>'ON',
+12008=>'ON',
+12009=>'ON',
+12010=>'ON',
+12011=>'ON',
+12012=>'ON',
+12013=>'ON',
+12014=>'ON',
+12015=>'ON',
+12016=>'ON',
+12017=>'ON',
+12018=>'ON',
+12019=>'ON',
+12032=>'ON',
+12033=>'ON',
+12034=>'ON',
+12035=>'ON',
+12036=>'ON',
+12037=>'ON',
+12038=>'ON',
+12039=>'ON',
+12040=>'ON',
+12041=>'ON',
+12042=>'ON',
+12043=>'ON',
+12044=>'ON',
+12045=>'ON',
+12046=>'ON',
+12047=>'ON',
+12048=>'ON',
+12049=>'ON',
+12050=>'ON',
+12051=>'ON',
+12052=>'ON',
+12053=>'ON',
+12054=>'ON',
+12055=>'ON',
+12056=>'ON',
+12057=>'ON',
+12058=>'ON',
+12059=>'ON',
+12060=>'ON',
+12061=>'ON',
+12062=>'ON',
+12063=>'ON',
+12064=>'ON',
+12065=>'ON',
+12066=>'ON',
+12067=>'ON',
+12068=>'ON',
+12069=>'ON',
+12070=>'ON',
+12071=>'ON',
+12072=>'ON',
+12073=>'ON',
+12074=>'ON',
+12075=>'ON',
+12076=>'ON',
+12077=>'ON',
+12078=>'ON',
+12079=>'ON',
+12080=>'ON',
+12081=>'ON',
+12082=>'ON',
+12083=>'ON',
+12084=>'ON',
+12085=>'ON',
+12086=>'ON',
+12087=>'ON',
+12088=>'ON',
+12089=>'ON',
+12090=>'ON',
+12091=>'ON',
+12092=>'ON',
+12093=>'ON',
+12094=>'ON',
+12095=>'ON',
+12096=>'ON',
+12097=>'ON',
+12098=>'ON',
+12099=>'ON',
+12100=>'ON',
+12101=>'ON',
+12102=>'ON',
+12103=>'ON',
+12104=>'ON',
+12105=>'ON',
+12106=>'ON',
+12107=>'ON',
+12108=>'ON',
+12109=>'ON',
+12110=>'ON',
+12111=>'ON',
+12112=>'ON',
+12113=>'ON',
+12114=>'ON',
+12115=>'ON',
+12116=>'ON',
+12117=>'ON',
+12118=>'ON',
+12119=>'ON',
+12120=>'ON',
+12121=>'ON',
+12122=>'ON',
+12123=>'ON',
+12124=>'ON',
+12125=>'ON',
+12126=>'ON',
+12127=>'ON',
+12128=>'ON',
+12129=>'ON',
+12130=>'ON',
+12131=>'ON',
+12132=>'ON',
+12133=>'ON',
+12134=>'ON',
+12135=>'ON',
+12136=>'ON',
+12137=>'ON',
+12138=>'ON',
+12139=>'ON',
+12140=>'ON',
+12141=>'ON',
+12142=>'ON',
+12143=>'ON',
+12144=>'ON',
+12145=>'ON',
+12146=>'ON',
+12147=>'ON',
+12148=>'ON',
+12149=>'ON',
+12150=>'ON',
+12151=>'ON',
+12152=>'ON',
+12153=>'ON',
+12154=>'ON',
+12155=>'ON',
+12156=>'ON',
+12157=>'ON',
+12158=>'ON',
+12159=>'ON',
+12160=>'ON',
+12161=>'ON',
+12162=>'ON',
+12163=>'ON',
+12164=>'ON',
+12165=>'ON',
+12166=>'ON',
+12167=>'ON',
+12168=>'ON',
+12169=>'ON',
+12170=>'ON',
+12171=>'ON',
+12172=>'ON',
+12173=>'ON',
+12174=>'ON',
+12175=>'ON',
+12176=>'ON',
+12177=>'ON',
+12178=>'ON',
+12179=>'ON',
+12180=>'ON',
+12181=>'ON',
+12182=>'ON',
+12183=>'ON',
+12184=>'ON',
+12185=>'ON',
+12186=>'ON',
+12187=>'ON',
+12188=>'ON',
+12189=>'ON',
+12190=>'ON',
+12191=>'ON',
+12192=>'ON',
+12193=>'ON',
+12194=>'ON',
+12195=>'ON',
+12196=>'ON',
+12197=>'ON',
+12198=>'ON',
+12199=>'ON',
+12200=>'ON',
+12201=>'ON',
+12202=>'ON',
+12203=>'ON',
+12204=>'ON',
+12205=>'ON',
+12206=>'ON',
+12207=>'ON',
+12208=>'ON',
+12209=>'ON',
+12210=>'ON',
+12211=>'ON',
+12212=>'ON',
+12213=>'ON',
+12214=>'ON',
+12215=>'ON',
+12216=>'ON',
+12217=>'ON',
+12218=>'ON',
+12219=>'ON',
+12220=>'ON',
+12221=>'ON',
+12222=>'ON',
+12223=>'ON',
+12224=>'ON',
+12225=>'ON',
+12226=>'ON',
+12227=>'ON',
+12228=>'ON',
+12229=>'ON',
+12230=>'ON',
+12231=>'ON',
+12232=>'ON',
+12233=>'ON',
+12234=>'ON',
+12235=>'ON',
+12236=>'ON',
+12237=>'ON',
+12238=>'ON',
+12239=>'ON',
+12240=>'ON',
+12241=>'ON',
+12242=>'ON',
+12243=>'ON',
+12244=>'ON',
+12245=>'ON',
+12272=>'ON',
+12273=>'ON',
+12274=>'ON',
+12275=>'ON',
+12276=>'ON',
+12277=>'ON',
+12278=>'ON',
+12279=>'ON',
+12280=>'ON',
+12281=>'ON',
+12282=>'ON',
+12283=>'ON',
+12288=>'WS',
+12289=>'ON',
+12290=>'ON',
+12291=>'ON',
+12292=>'ON',
+12293=>'L',
+12294=>'L',
+12295=>'L',
+12296=>'ON',
+12297=>'ON',
+12298=>'ON',
+12299=>'ON',
+12300=>'ON',
+12301=>'ON',
+12302=>'ON',
+12303=>'ON',
+12304=>'ON',
+12305=>'ON',
+12306=>'ON',
+12307=>'ON',
+12308=>'ON',
+12309=>'ON',
+12310=>'ON',
+12311=>'ON',
+12312=>'ON',
+12313=>'ON',
+12314=>'ON',
+12315=>'ON',
+12316=>'ON',
+12317=>'ON',
+12318=>'ON',
+12319=>'ON',
+12320=>'ON',
+12321=>'L',
+12322=>'L',
+12323=>'L',
+12324=>'L',
+12325=>'L',
+12326=>'L',
+12327=>'L',
+12328=>'L',
+12329=>'L',
+12330=>'NSM',
+12331=>'NSM',
+12332=>'NSM',
+12333=>'NSM',
+12334=>'NSM',
+12335=>'NSM',
+12336=>'ON',
+12337=>'L',
+12338=>'L',
+12339=>'L',
+12340=>'L',
+12341=>'L',
+12342=>'ON',
+12343=>'ON',
+12344=>'L',
+12345=>'L',
+12346=>'L',
+12347=>'L',
+12348=>'L',
+12349=>'ON',
+12350=>'ON',
+12351=>'ON',
+12353=>'L',
+12354=>'L',
+12355=>'L',
+12356=>'L',
+12357=>'L',
+12358=>'L',
+12359=>'L',
+12360=>'L',
+12361=>'L',
+12362=>'L',
+12363=>'L',
+12364=>'L',
+12365=>'L',
+12366=>'L',
+12367=>'L',
+12368=>'L',
+12369=>'L',
+12370=>'L',
+12371=>'L',
+12372=>'L',
+12373=>'L',
+12374=>'L',
+12375=>'L',
+12376=>'L',
+12377=>'L',
+12378=>'L',
+12379=>'L',
+12380=>'L',
+12381=>'L',
+12382=>'L',
+12383=>'L',
+12384=>'L',
+12385=>'L',
+12386=>'L',
+12387=>'L',
+12388=>'L',
+12389=>'L',
+12390=>'L',
+12391=>'L',
+12392=>'L',
+12393=>'L',
+12394=>'L',
+12395=>'L',
+12396=>'L',
+12397=>'L',
+12398=>'L',
+12399=>'L',
+12400=>'L',
+12401=>'L',
+12402=>'L',
+12403=>'L',
+12404=>'L',
+12405=>'L',
+12406=>'L',
+12407=>'L',
+12408=>'L',
+12409=>'L',
+12410=>'L',
+12411=>'L',
+12412=>'L',
+12413=>'L',
+12414=>'L',
+12415=>'L',
+12416=>'L',
+12417=>'L',
+12418=>'L',
+12419=>'L',
+12420=>'L',
+12421=>'L',
+12422=>'L',
+12423=>'L',
+12424=>'L',
+12425=>'L',
+12426=>'L',
+12427=>'L',
+12428=>'L',
+12429=>'L',
+12430=>'L',
+12431=>'L',
+12432=>'L',
+12433=>'L',
+12434=>'L',
+12435=>'L',
+12436=>'L',
+12437=>'L',
+12438=>'L',
+12441=>'NSM',
+12442=>'NSM',
+12443=>'ON',
+12444=>'ON',
+12445=>'L',
+12446=>'L',
+12447=>'L',
+12448=>'ON',
+12449=>'L',
+12450=>'L',
+12451=>'L',
+12452=>'L',
+12453=>'L',
+12454=>'L',
+12455=>'L',
+12456=>'L',
+12457=>'L',
+12458=>'L',
+12459=>'L',
+12460=>'L',
+12461=>'L',
+12462=>'L',
+12463=>'L',
+12464=>'L',
+12465=>'L',
+12466=>'L',
+12467=>'L',
+12468=>'L',
+12469=>'L',
+12470=>'L',
+12471=>'L',
+12472=>'L',
+12473=>'L',
+12474=>'L',
+12475=>'L',
+12476=>'L',
+12477=>'L',
+12478=>'L',
+12479=>'L',
+12480=>'L',
+12481=>'L',
+12482=>'L',
+12483=>'L',
+12484=>'L',
+12485=>'L',
+12486=>'L',
+12487=>'L',
+12488=>'L',
+12489=>'L',
+12490=>'L',
+12491=>'L',
+12492=>'L',
+12493=>'L',
+12494=>'L',
+12495=>'L',
+12496=>'L',
+12497=>'L',
+12498=>'L',
+12499=>'L',
+12500=>'L',
+12501=>'L',
+12502=>'L',
+12503=>'L',
+12504=>'L',
+12505=>'L',
+12506=>'L',
+12507=>'L',
+12508=>'L',
+12509=>'L',
+12510=>'L',
+12511=>'L',
+12512=>'L',
+12513=>'L',
+12514=>'L',
+12515=>'L',
+12516=>'L',
+12517=>'L',
+12518=>'L',
+12519=>'L',
+12520=>'L',
+12521=>'L',
+12522=>'L',
+12523=>'L',
+12524=>'L',
+12525=>'L',
+12526=>'L',
+12527=>'L',
+12528=>'L',
+12529=>'L',
+12530=>'L',
+12531=>'L',
+12532=>'L',
+12533=>'L',
+12534=>'L',
+12535=>'L',
+12536=>'L',
+12537=>'L',
+12538=>'L',
+12539=>'ON',
+12540=>'L',
+12541=>'L',
+12542=>'L',
+12543=>'L',
+12549=>'L',
+12550=>'L',
+12551=>'L',
+12552=>'L',
+12553=>'L',
+12554=>'L',
+12555=>'L',
+12556=>'L',
+12557=>'L',
+12558=>'L',
+12559=>'L',
+12560=>'L',
+12561=>'L',
+12562=>'L',
+12563=>'L',
+12564=>'L',
+12565=>'L',
+12566=>'L',
+12567=>'L',
+12568=>'L',
+12569=>'L',
+12570=>'L',
+12571=>'L',
+12572=>'L',
+12573=>'L',
+12574=>'L',
+12575=>'L',
+12576=>'L',
+12577=>'L',
+12578=>'L',
+12579=>'L',
+12580=>'L',
+12581=>'L',
+12582=>'L',
+12583=>'L',
+12584=>'L',
+12585=>'L',
+12586=>'L',
+12587=>'L',
+12588=>'L',
+12593=>'L',
+12594=>'L',
+12595=>'L',
+12596=>'L',
+12597=>'L',
+12598=>'L',
+12599=>'L',
+12600=>'L',
+12601=>'L',
+12602=>'L',
+12603=>'L',
+12604=>'L',
+12605=>'L',
+12606=>'L',
+12607=>'L',
+12608=>'L',
+12609=>'L',
+12610=>'L',
+12611=>'L',
+12612=>'L',
+12613=>'L',
+12614=>'L',
+12615=>'L',
+12616=>'L',
+12617=>'L',
+12618=>'L',
+12619=>'L',
+12620=>'L',
+12621=>'L',
+12622=>'L',
+12623=>'L',
+12624=>'L',
+12625=>'L',
+12626=>'L',
+12627=>'L',
+12628=>'L',
+12629=>'L',
+12630=>'L',
+12631=>'L',
+12632=>'L',
+12633=>'L',
+12634=>'L',
+12635=>'L',
+12636=>'L',
+12637=>'L',
+12638=>'L',
+12639=>'L',
+12640=>'L',
+12641=>'L',
+12642=>'L',
+12643=>'L',
+12644=>'L',
+12645=>'L',
+12646=>'L',
+12647=>'L',
+12648=>'L',
+12649=>'L',
+12650=>'L',
+12651=>'L',
+12652=>'L',
+12653=>'L',
+12654=>'L',
+12655=>'L',
+12656=>'L',
+12657=>'L',
+12658=>'L',
+12659=>'L',
+12660=>'L',
+12661=>'L',
+12662=>'L',
+12663=>'L',
+12664=>'L',
+12665=>'L',
+12666=>'L',
+12667=>'L',
+12668=>'L',
+12669=>'L',
+12670=>'L',
+12671=>'L',
+12672=>'L',
+12673=>'L',
+12674=>'L',
+12675=>'L',
+12676=>'L',
+12677=>'L',
+12678=>'L',
+12679=>'L',
+12680=>'L',
+12681=>'L',
+12682=>'L',
+12683=>'L',
+12684=>'L',
+12685=>'L',
+12686=>'L',
+12688=>'L',
+12689=>'L',
+12690=>'L',
+12691=>'L',
+12692=>'L',
+12693=>'L',
+12694=>'L',
+12695=>'L',
+12696=>'L',
+12697=>'L',
+12698=>'L',
+12699=>'L',
+12700=>'L',
+12701=>'L',
+12702=>'L',
+12703=>'L',
+12704=>'L',
+12705=>'L',
+12706=>'L',
+12707=>'L',
+12708=>'L',
+12709=>'L',
+12710=>'L',
+12711=>'L',
+12712=>'L',
+12713=>'L',
+12714=>'L',
+12715=>'L',
+12716=>'L',
+12717=>'L',
+12718=>'L',
+12719=>'L',
+12720=>'L',
+12721=>'L',
+12722=>'L',
+12723=>'L',
+12724=>'L',
+12725=>'L',
+12726=>'L',
+12727=>'L',
+12736=>'ON',
+12737=>'ON',
+12738=>'ON',
+12739=>'ON',
+12740=>'ON',
+12741=>'ON',
+12742=>'ON',
+12743=>'ON',
+12744=>'ON',
+12745=>'ON',
+12746=>'ON',
+12747=>'ON',
+12748=>'ON',
+12749=>'ON',
+12750=>'ON',
+12751=>'ON',
+12784=>'L',
+12785=>'L',
+12786=>'L',
+12787=>'L',
+12788=>'L',
+12789=>'L',
+12790=>'L',
+12791=>'L',
+12792=>'L',
+12793=>'L',
+12794=>'L',
+12795=>'L',
+12796=>'L',
+12797=>'L',
+12798=>'L',
+12799=>'L',
+12800=>'L',
+12801=>'L',
+12802=>'L',
+12803=>'L',
+12804=>'L',
+12805=>'L',
+12806=>'L',
+12807=>'L',
+12808=>'L',
+12809=>'L',
+12810=>'L',
+12811=>'L',
+12812=>'L',
+12813=>'L',
+12814=>'L',
+12815=>'L',
+12816=>'L',
+12817=>'L',
+12818=>'L',
+12819=>'L',
+12820=>'L',
+12821=>'L',
+12822=>'L',
+12823=>'L',
+12824=>'L',
+12825=>'L',
+12826=>'L',
+12827=>'L',
+12828=>'L',
+12829=>'ON',
+12830=>'ON',
+12832=>'L',
+12833=>'L',
+12834=>'L',
+12835=>'L',
+12836=>'L',
+12837=>'L',
+12838=>'L',
+12839=>'L',
+12840=>'L',
+12841=>'L',
+12842=>'L',
+12843=>'L',
+12844=>'L',
+12845=>'L',
+12846=>'L',
+12847=>'L',
+12848=>'L',
+12849=>'L',
+12850=>'L',
+12851=>'L',
+12852=>'L',
+12853=>'L',
+12854=>'L',
+12855=>'L',
+12856=>'L',
+12857=>'L',
+12858=>'L',
+12859=>'L',
+12860=>'L',
+12861=>'L',
+12862=>'L',
+12863=>'L',
+12864=>'L',
+12865=>'L',
+12866=>'L',
+12867=>'L',
+12880=>'ON',
+12881=>'ON',
+12882=>'ON',
+12883=>'ON',
+12884=>'ON',
+12885=>'ON',
+12886=>'ON',
+12887=>'ON',
+12888=>'ON',
+12889=>'ON',
+12890=>'ON',
+12891=>'ON',
+12892=>'ON',
+12893=>'ON',
+12894=>'ON',
+12895=>'ON',
+12896=>'L',
+12897=>'L',
+12898=>'L',
+12899=>'L',
+12900=>'L',
+12901=>'L',
+12902=>'L',
+12903=>'L',
+12904=>'L',
+12905=>'L',
+12906=>'L',
+12907=>'L',
+12908=>'L',
+12909=>'L',
+12910=>'L',
+12911=>'L',
+12912=>'L',
+12913=>'L',
+12914=>'L',
+12915=>'L',
+12916=>'L',
+12917=>'L',
+12918=>'L',
+12919=>'L',
+12920=>'L',
+12921=>'L',
+12922=>'L',
+12923=>'L',
+12924=>'ON',
+12925=>'ON',
+12926=>'ON',
+12927=>'L',
+12928=>'L',
+12929=>'L',
+12930=>'L',
+12931=>'L',
+12932=>'L',
+12933=>'L',
+12934=>'L',
+12935=>'L',
+12936=>'L',
+12937=>'L',
+12938=>'L',
+12939=>'L',
+12940=>'L',
+12941=>'L',
+12942=>'L',
+12943=>'L',
+12944=>'L',
+12945=>'L',
+12946=>'L',
+12947=>'L',
+12948=>'L',
+12949=>'L',
+12950=>'L',
+12951=>'L',
+12952=>'L',
+12953=>'L',
+12954=>'L',
+12955=>'L',
+12956=>'L',
+12957=>'L',
+12958=>'L',
+12959=>'L',
+12960=>'L',
+12961=>'L',
+12962=>'L',
+12963=>'L',
+12964=>'L',
+12965=>'L',
+12966=>'L',
+12967=>'L',
+12968=>'L',
+12969=>'L',
+12970=>'L',
+12971=>'L',
+12972=>'L',
+12973=>'L',
+12974=>'L',
+12975=>'L',
+12976=>'L',
+12977=>'ON',
+12978=>'ON',
+12979=>'ON',
+12980=>'ON',
+12981=>'ON',
+12982=>'ON',
+12983=>'ON',
+12984=>'ON',
+12985=>'ON',
+12986=>'ON',
+12987=>'ON',
+12988=>'ON',
+12989=>'ON',
+12990=>'ON',
+12991=>'ON',
+12992=>'L',
+12993=>'L',
+12994=>'L',
+12995=>'L',
+12996=>'L',
+12997=>'L',
+12998=>'L',
+12999=>'L',
+13000=>'L',
+13001=>'L',
+13002=>'L',
+13003=>'L',
+13004=>'ON',
+13005=>'ON',
+13006=>'ON',
+13007=>'ON',
+13008=>'L',
+13009=>'L',
+13010=>'L',
+13011=>'L',
+13012=>'L',
+13013=>'L',
+13014=>'L',
+13015=>'L',
+13016=>'L',
+13017=>'L',
+13018=>'L',
+13019=>'L',
+13020=>'L',
+13021=>'L',
+13022=>'L',
+13023=>'L',
+13024=>'L',
+13025=>'L',
+13026=>'L',
+13027=>'L',
+13028=>'L',
+13029=>'L',
+13030=>'L',
+13031=>'L',
+13032=>'L',
+13033=>'L',
+13034=>'L',
+13035=>'L',
+13036=>'L',
+13037=>'L',
+13038=>'L',
+13039=>'L',
+13040=>'L',
+13041=>'L',
+13042=>'L',
+13043=>'L',
+13044=>'L',
+13045=>'L',
+13046=>'L',
+13047=>'L',
+13048=>'L',
+13049=>'L',
+13050=>'L',
+13051=>'L',
+13052=>'L',
+13053=>'L',
+13054=>'L',
+13056=>'L',
+13057=>'L',
+13058=>'L',
+13059=>'L',
+13060=>'L',
+13061=>'L',
+13062=>'L',
+13063=>'L',
+13064=>'L',
+13065=>'L',
+13066=>'L',
+13067=>'L',
+13068=>'L',
+13069=>'L',
+13070=>'L',
+13071=>'L',
+13072=>'L',
+13073=>'L',
+13074=>'L',
+13075=>'L',
+13076=>'L',
+13077=>'L',
+13078=>'L',
+13079=>'L',
+13080=>'L',
+13081=>'L',
+13082=>'L',
+13083=>'L',
+13084=>'L',
+13085=>'L',
+13086=>'L',
+13087=>'L',
+13088=>'L',
+13089=>'L',
+13090=>'L',
+13091=>'L',
+13092=>'L',
+13093=>'L',
+13094=>'L',
+13095=>'L',
+13096=>'L',
+13097=>'L',
+13098=>'L',
+13099=>'L',
+13100=>'L',
+13101=>'L',
+13102=>'L',
+13103=>'L',
+13104=>'L',
+13105=>'L',
+13106=>'L',
+13107=>'L',
+13108=>'L',
+13109=>'L',
+13110=>'L',
+13111=>'L',
+13112=>'L',
+13113=>'L',
+13114=>'L',
+13115=>'L',
+13116=>'L',
+13117=>'L',
+13118=>'L',
+13119=>'L',
+13120=>'L',
+13121=>'L',
+13122=>'L',
+13123=>'L',
+13124=>'L',
+13125=>'L',
+13126=>'L',
+13127=>'L',
+13128=>'L',
+13129=>'L',
+13130=>'L',
+13131=>'L',
+13132=>'L',
+13133=>'L',
+13134=>'L',
+13135=>'L',
+13136=>'L',
+13137=>'L',
+13138=>'L',
+13139=>'L',
+13140=>'L',
+13141=>'L',
+13142=>'L',
+13143=>'L',
+13144=>'L',
+13145=>'L',
+13146=>'L',
+13147=>'L',
+13148=>'L',
+13149=>'L',
+13150=>'L',
+13151=>'L',
+13152=>'L',
+13153=>'L',
+13154=>'L',
+13155=>'L',
+13156=>'L',
+13157=>'L',
+13158=>'L',
+13159=>'L',
+13160=>'L',
+13161=>'L',
+13162=>'L',
+13163=>'L',
+13164=>'L',
+13165=>'L',
+13166=>'L',
+13167=>'L',
+13168=>'L',
+13169=>'L',
+13170=>'L',
+13171=>'L',
+13172=>'L',
+13173=>'L',
+13174=>'L',
+13175=>'ON',
+13176=>'ON',
+13177=>'ON',
+13178=>'ON',
+13179=>'L',
+13180=>'L',
+13181=>'L',
+13182=>'L',
+13183=>'L',
+13184=>'L',
+13185=>'L',
+13186=>'L',
+13187=>'L',
+13188=>'L',
+13189=>'L',
+13190=>'L',
+13191=>'L',
+13192=>'L',
+13193=>'L',
+13194=>'L',
+13195=>'L',
+13196=>'L',
+13197=>'L',
+13198=>'L',
+13199=>'L',
+13200=>'L',
+13201=>'L',
+13202=>'L',
+13203=>'L',
+13204=>'L',
+13205=>'L',
+13206=>'L',
+13207=>'L',
+13208=>'L',
+13209=>'L',
+13210=>'L',
+13211=>'L',
+13212=>'L',
+13213=>'L',
+13214=>'L',
+13215=>'L',
+13216=>'L',
+13217=>'L',
+13218=>'L',
+13219=>'L',
+13220=>'L',
+13221=>'L',
+13222=>'L',
+13223=>'L',
+13224=>'L',
+13225=>'L',
+13226=>'L',
+13227=>'L',
+13228=>'L',
+13229=>'L',
+13230=>'L',
+13231=>'L',
+13232=>'L',
+13233=>'L',
+13234=>'L',
+13235=>'L',
+13236=>'L',
+13237=>'L',
+13238=>'L',
+13239=>'L',
+13240=>'L',
+13241=>'L',
+13242=>'L',
+13243=>'L',
+13244=>'L',
+13245=>'L',
+13246=>'L',
+13247=>'L',
+13248=>'L',
+13249=>'L',
+13250=>'L',
+13251=>'L',
+13252=>'L',
+13253=>'L',
+13254=>'L',
+13255=>'L',
+13256=>'L',
+13257=>'L',
+13258=>'L',
+13259=>'L',
+13260=>'L',
+13261=>'L',
+13262=>'L',
+13263=>'L',
+13264=>'L',
+13265=>'L',
+13266=>'L',
+13267=>'L',
+13268=>'L',
+13269=>'L',
+13270=>'L',
+13271=>'L',
+13272=>'L',
+13273=>'L',
+13274=>'L',
+13275=>'L',
+13276=>'L',
+13277=>'L',
+13278=>'ON',
+13279=>'ON',
+13280=>'L',
+13281=>'L',
+13282=>'L',
+13283=>'L',
+13284=>'L',
+13285=>'L',
+13286=>'L',
+13287=>'L',
+13288=>'L',
+13289=>'L',
+13290=>'L',
+13291=>'L',
+13292=>'L',
+13293=>'L',
+13294=>'L',
+13295=>'L',
+13296=>'L',
+13297=>'L',
+13298=>'L',
+13299=>'L',
+13300=>'L',
+13301=>'L',
+13302=>'L',
+13303=>'L',
+13304=>'L',
+13305=>'L',
+13306=>'L',
+13307=>'L',
+13308=>'L',
+13309=>'L',
+13310=>'L',
+13311=>'ON',
+13312=>'L',
+19893=>'L',
+19904=>'ON',
+19905=>'ON',
+19906=>'ON',
+19907=>'ON',
+19908=>'ON',
+19909=>'ON',
+19910=>'ON',
+19911=>'ON',
+19912=>'ON',
+19913=>'ON',
+19914=>'ON',
+19915=>'ON',
+19916=>'ON',
+19917=>'ON',
+19918=>'ON',
+19919=>'ON',
+19920=>'ON',
+19921=>'ON',
+19922=>'ON',
+19923=>'ON',
+19924=>'ON',
+19925=>'ON',
+19926=>'ON',
+19927=>'ON',
+19928=>'ON',
+19929=>'ON',
+19930=>'ON',
+19931=>'ON',
+19932=>'ON',
+19933=>'ON',
+19934=>'ON',
+19935=>'ON',
+19936=>'ON',
+19937=>'ON',
+19938=>'ON',
+19939=>'ON',
+19940=>'ON',
+19941=>'ON',
+19942=>'ON',
+19943=>'ON',
+19944=>'ON',
+19945=>'ON',
+19946=>'ON',
+19947=>'ON',
+19948=>'ON',
+19949=>'ON',
+19950=>'ON',
+19951=>'ON',
+19952=>'ON',
+19953=>'ON',
+19954=>'ON',
+19955=>'ON',
+19956=>'ON',
+19957=>'ON',
+19958=>'ON',
+19959=>'ON',
+19960=>'ON',
+19961=>'ON',
+19962=>'ON',
+19963=>'ON',
+19964=>'ON',
+19965=>'ON',
+19966=>'ON',
+19967=>'ON',
+19968=>'L',
+40891=>'L',
+40960=>'L',
+40961=>'L',
+40962=>'L',
+40963=>'L',
+40964=>'L',
+40965=>'L',
+40966=>'L',
+40967=>'L',
+40968=>'L',
+40969=>'L',
+40970=>'L',
+40971=>'L',
+40972=>'L',
+40973=>'L',
+40974=>'L',
+40975=>'L',
+40976=>'L',
+40977=>'L',
+40978=>'L',
+40979=>'L',
+40980=>'L',
+40981=>'L',
+40982=>'L',
+40983=>'L',
+40984=>'L',
+40985=>'L',
+40986=>'L',
+40987=>'L',
+40988=>'L',
+40989=>'L',
+40990=>'L',
+40991=>'L',
+40992=>'L',
+40993=>'L',
+40994=>'L',
+40995=>'L',
+40996=>'L',
+40997=>'L',
+40998=>'L',
+40999=>'L',
+41000=>'L',
+41001=>'L',
+41002=>'L',
+41003=>'L',
+41004=>'L',
+41005=>'L',
+41006=>'L',
+41007=>'L',
+41008=>'L',
+41009=>'L',
+41010=>'L',
+41011=>'L',
+41012=>'L',
+41013=>'L',
+41014=>'L',
+41015=>'L',
+41016=>'L',
+41017=>'L',
+41018=>'L',
+41019=>'L',
+41020=>'L',
+41021=>'L',
+41022=>'L',
+41023=>'L',
+41024=>'L',
+41025=>'L',
+41026=>'L',
+41027=>'L',
+41028=>'L',
+41029=>'L',
+41030=>'L',
+41031=>'L',
+41032=>'L',
+41033=>'L',
+41034=>'L',
+41035=>'L',
+41036=>'L',
+41037=>'L',
+41038=>'L',
+41039=>'L',
+41040=>'L',
+41041=>'L',
+41042=>'L',
+41043=>'L',
+41044=>'L',
+41045=>'L',
+41046=>'L',
+41047=>'L',
+41048=>'L',
+41049=>'L',
+41050=>'L',
+41051=>'L',
+41052=>'L',
+41053=>'L',
+41054=>'L',
+41055=>'L',
+41056=>'L',
+41057=>'L',
+41058=>'L',
+41059=>'L',
+41060=>'L',
+41061=>'L',
+41062=>'L',
+41063=>'L',
+41064=>'L',
+41065=>'L',
+41066=>'L',
+41067=>'L',
+41068=>'L',
+41069=>'L',
+41070=>'L',
+41071=>'L',
+41072=>'L',
+41073=>'L',
+41074=>'L',
+41075=>'L',
+41076=>'L',
+41077=>'L',
+41078=>'L',
+41079=>'L',
+41080=>'L',
+41081=>'L',
+41082=>'L',
+41083=>'L',
+41084=>'L',
+41085=>'L',
+41086=>'L',
+41087=>'L',
+41088=>'L',
+41089=>'L',
+41090=>'L',
+41091=>'L',
+41092=>'L',
+41093=>'L',
+41094=>'L',
+41095=>'L',
+41096=>'L',
+41097=>'L',
+41098=>'L',
+41099=>'L',
+41100=>'L',
+41101=>'L',
+41102=>'L',
+41103=>'L',
+41104=>'L',
+41105=>'L',
+41106=>'L',
+41107=>'L',
+41108=>'L',
+41109=>'L',
+41110=>'L',
+41111=>'L',
+41112=>'L',
+41113=>'L',
+41114=>'L',
+41115=>'L',
+41116=>'L',
+41117=>'L',
+41118=>'L',
+41119=>'L',
+41120=>'L',
+41121=>'L',
+41122=>'L',
+41123=>'L',
+41124=>'L',
+41125=>'L',
+41126=>'L',
+41127=>'L',
+41128=>'L',
+41129=>'L',
+41130=>'L',
+41131=>'L',
+41132=>'L',
+41133=>'L',
+41134=>'L',
+41135=>'L',
+41136=>'L',
+41137=>'L',
+41138=>'L',
+41139=>'L',
+41140=>'L',
+41141=>'L',
+41142=>'L',
+41143=>'L',
+41144=>'L',
+41145=>'L',
+41146=>'L',
+41147=>'L',
+41148=>'L',
+41149=>'L',
+41150=>'L',
+41151=>'L',
+41152=>'L',
+41153=>'L',
+41154=>'L',
+41155=>'L',
+41156=>'L',
+41157=>'L',
+41158=>'L',
+41159=>'L',
+41160=>'L',
+41161=>'L',
+41162=>'L',
+41163=>'L',
+41164=>'L',
+41165=>'L',
+41166=>'L',
+41167=>'L',
+41168=>'L',
+41169=>'L',
+41170=>'L',
+41171=>'L',
+41172=>'L',
+41173=>'L',
+41174=>'L',
+41175=>'L',
+41176=>'L',
+41177=>'L',
+41178=>'L',
+41179=>'L',
+41180=>'L',
+41181=>'L',
+41182=>'L',
+41183=>'L',
+41184=>'L',
+41185=>'L',
+41186=>'L',
+41187=>'L',
+41188=>'L',
+41189=>'L',
+41190=>'L',
+41191=>'L',
+41192=>'L',
+41193=>'L',
+41194=>'L',
+41195=>'L',
+41196=>'L',
+41197=>'L',
+41198=>'L',
+41199=>'L',
+41200=>'L',
+41201=>'L',
+41202=>'L',
+41203=>'L',
+41204=>'L',
+41205=>'L',
+41206=>'L',
+41207=>'L',
+41208=>'L',
+41209=>'L',
+41210=>'L',
+41211=>'L',
+41212=>'L',
+41213=>'L',
+41214=>'L',
+41215=>'L',
+41216=>'L',
+41217=>'L',
+41218=>'L',
+41219=>'L',
+41220=>'L',
+41221=>'L',
+41222=>'L',
+41223=>'L',
+41224=>'L',
+41225=>'L',
+41226=>'L',
+41227=>'L',
+41228=>'L',
+41229=>'L',
+41230=>'L',
+41231=>'L',
+41232=>'L',
+41233=>'L',
+41234=>'L',
+41235=>'L',
+41236=>'L',
+41237=>'L',
+41238=>'L',
+41239=>'L',
+41240=>'L',
+41241=>'L',
+41242=>'L',
+41243=>'L',
+41244=>'L',
+41245=>'L',
+41246=>'L',
+41247=>'L',
+41248=>'L',
+41249=>'L',
+41250=>'L',
+41251=>'L',
+41252=>'L',
+41253=>'L',
+41254=>'L',
+41255=>'L',
+41256=>'L',
+41257=>'L',
+41258=>'L',
+41259=>'L',
+41260=>'L',
+41261=>'L',
+41262=>'L',
+41263=>'L',
+41264=>'L',
+41265=>'L',
+41266=>'L',
+41267=>'L',
+41268=>'L',
+41269=>'L',
+41270=>'L',
+41271=>'L',
+41272=>'L',
+41273=>'L',
+41274=>'L',
+41275=>'L',
+41276=>'L',
+41277=>'L',
+41278=>'L',
+41279=>'L',
+41280=>'L',
+41281=>'L',
+41282=>'L',
+41283=>'L',
+41284=>'L',
+41285=>'L',
+41286=>'L',
+41287=>'L',
+41288=>'L',
+41289=>'L',
+41290=>'L',
+41291=>'L',
+41292=>'L',
+41293=>'L',
+41294=>'L',
+41295=>'L',
+41296=>'L',
+41297=>'L',
+41298=>'L',
+41299=>'L',
+41300=>'L',
+41301=>'L',
+41302=>'L',
+41303=>'L',
+41304=>'L',
+41305=>'L',
+41306=>'L',
+41307=>'L',
+41308=>'L',
+41309=>'L',
+41310=>'L',
+41311=>'L',
+41312=>'L',
+41313=>'L',
+41314=>'L',
+41315=>'L',
+41316=>'L',
+41317=>'L',
+41318=>'L',
+41319=>'L',
+41320=>'L',
+41321=>'L',
+41322=>'L',
+41323=>'L',
+41324=>'L',
+41325=>'L',
+41326=>'L',
+41327=>'L',
+41328=>'L',
+41329=>'L',
+41330=>'L',
+41331=>'L',
+41332=>'L',
+41333=>'L',
+41334=>'L',
+41335=>'L',
+41336=>'L',
+41337=>'L',
+41338=>'L',
+41339=>'L',
+41340=>'L',
+41341=>'L',
+41342=>'L',
+41343=>'L',
+41344=>'L',
+41345=>'L',
+41346=>'L',
+41347=>'L',
+41348=>'L',
+41349=>'L',
+41350=>'L',
+41351=>'L',
+41352=>'L',
+41353=>'L',
+41354=>'L',
+41355=>'L',
+41356=>'L',
+41357=>'L',
+41358=>'L',
+41359=>'L',
+41360=>'L',
+41361=>'L',
+41362=>'L',
+41363=>'L',
+41364=>'L',
+41365=>'L',
+41366=>'L',
+41367=>'L',
+41368=>'L',
+41369=>'L',
+41370=>'L',
+41371=>'L',
+41372=>'L',
+41373=>'L',
+41374=>'L',
+41375=>'L',
+41376=>'L',
+41377=>'L',
+41378=>'L',
+41379=>'L',
+41380=>'L',
+41381=>'L',
+41382=>'L',
+41383=>'L',
+41384=>'L',
+41385=>'L',
+41386=>'L',
+41387=>'L',
+41388=>'L',
+41389=>'L',
+41390=>'L',
+41391=>'L',
+41392=>'L',
+41393=>'L',
+41394=>'L',
+41395=>'L',
+41396=>'L',
+41397=>'L',
+41398=>'L',
+41399=>'L',
+41400=>'L',
+41401=>'L',
+41402=>'L',
+41403=>'L',
+41404=>'L',
+41405=>'L',
+41406=>'L',
+41407=>'L',
+41408=>'L',
+41409=>'L',
+41410=>'L',
+41411=>'L',
+41412=>'L',
+41413=>'L',
+41414=>'L',
+41415=>'L',
+41416=>'L',
+41417=>'L',
+41418=>'L',
+41419=>'L',
+41420=>'L',
+41421=>'L',
+41422=>'L',
+41423=>'L',
+41424=>'L',
+41425=>'L',
+41426=>'L',
+41427=>'L',
+41428=>'L',
+41429=>'L',
+41430=>'L',
+41431=>'L',
+41432=>'L',
+41433=>'L',
+41434=>'L',
+41435=>'L',
+41436=>'L',
+41437=>'L',
+41438=>'L',
+41439=>'L',
+41440=>'L',
+41441=>'L',
+41442=>'L',
+41443=>'L',
+41444=>'L',
+41445=>'L',
+41446=>'L',
+41447=>'L',
+41448=>'L',
+41449=>'L',
+41450=>'L',
+41451=>'L',
+41452=>'L',
+41453=>'L',
+41454=>'L',
+41455=>'L',
+41456=>'L',
+41457=>'L',
+41458=>'L',
+41459=>'L',
+41460=>'L',
+41461=>'L',
+41462=>'L',
+41463=>'L',
+41464=>'L',
+41465=>'L',
+41466=>'L',
+41467=>'L',
+41468=>'L',
+41469=>'L',
+41470=>'L',
+41471=>'L',
+41472=>'L',
+41473=>'L',
+41474=>'L',
+41475=>'L',
+41476=>'L',
+41477=>'L',
+41478=>'L',
+41479=>'L',
+41480=>'L',
+41481=>'L',
+41482=>'L',
+41483=>'L',
+41484=>'L',
+41485=>'L',
+41486=>'L',
+41487=>'L',
+41488=>'L',
+41489=>'L',
+41490=>'L',
+41491=>'L',
+41492=>'L',
+41493=>'L',
+41494=>'L',
+41495=>'L',
+41496=>'L',
+41497=>'L',
+41498=>'L',
+41499=>'L',
+41500=>'L',
+41501=>'L',
+41502=>'L',
+41503=>'L',
+41504=>'L',
+41505=>'L',
+41506=>'L',
+41507=>'L',
+41508=>'L',
+41509=>'L',
+41510=>'L',
+41511=>'L',
+41512=>'L',
+41513=>'L',
+41514=>'L',
+41515=>'L',
+41516=>'L',
+41517=>'L',
+41518=>'L',
+41519=>'L',
+41520=>'L',
+41521=>'L',
+41522=>'L',
+41523=>'L',
+41524=>'L',
+41525=>'L',
+41526=>'L',
+41527=>'L',
+41528=>'L',
+41529=>'L',
+41530=>'L',
+41531=>'L',
+41532=>'L',
+41533=>'L',
+41534=>'L',
+41535=>'L',
+41536=>'L',
+41537=>'L',
+41538=>'L',
+41539=>'L',
+41540=>'L',
+41541=>'L',
+41542=>'L',
+41543=>'L',
+41544=>'L',
+41545=>'L',
+41546=>'L',
+41547=>'L',
+41548=>'L',
+41549=>'L',
+41550=>'L',
+41551=>'L',
+41552=>'L',
+41553=>'L',
+41554=>'L',
+41555=>'L',
+41556=>'L',
+41557=>'L',
+41558=>'L',
+41559=>'L',
+41560=>'L',
+41561=>'L',
+41562=>'L',
+41563=>'L',
+41564=>'L',
+41565=>'L',
+41566=>'L',
+41567=>'L',
+41568=>'L',
+41569=>'L',
+41570=>'L',
+41571=>'L',
+41572=>'L',
+41573=>'L',
+41574=>'L',
+41575=>'L',
+41576=>'L',
+41577=>'L',
+41578=>'L',
+41579=>'L',
+41580=>'L',
+41581=>'L',
+41582=>'L',
+41583=>'L',
+41584=>'L',
+41585=>'L',
+41586=>'L',
+41587=>'L',
+41588=>'L',
+41589=>'L',
+41590=>'L',
+41591=>'L',
+41592=>'L',
+41593=>'L',
+41594=>'L',
+41595=>'L',
+41596=>'L',
+41597=>'L',
+41598=>'L',
+41599=>'L',
+41600=>'L',
+41601=>'L',
+41602=>'L',
+41603=>'L',
+41604=>'L',
+41605=>'L',
+41606=>'L',
+41607=>'L',
+41608=>'L',
+41609=>'L',
+41610=>'L',
+41611=>'L',
+41612=>'L',
+41613=>'L',
+41614=>'L',
+41615=>'L',
+41616=>'L',
+41617=>'L',
+41618=>'L',
+41619=>'L',
+41620=>'L',
+41621=>'L',
+41622=>'L',
+41623=>'L',
+41624=>'L',
+41625=>'L',
+41626=>'L',
+41627=>'L',
+41628=>'L',
+41629=>'L',
+41630=>'L',
+41631=>'L',
+41632=>'L',
+41633=>'L',
+41634=>'L',
+41635=>'L',
+41636=>'L',
+41637=>'L',
+41638=>'L',
+41639=>'L',
+41640=>'L',
+41641=>'L',
+41642=>'L',
+41643=>'L',
+41644=>'L',
+41645=>'L',
+41646=>'L',
+41647=>'L',
+41648=>'L',
+41649=>'L',
+41650=>'L',
+41651=>'L',
+41652=>'L',
+41653=>'L',
+41654=>'L',
+41655=>'L',
+41656=>'L',
+41657=>'L',
+41658=>'L',
+41659=>'L',
+41660=>'L',
+41661=>'L',
+41662=>'L',
+41663=>'L',
+41664=>'L',
+41665=>'L',
+41666=>'L',
+41667=>'L',
+41668=>'L',
+41669=>'L',
+41670=>'L',
+41671=>'L',
+41672=>'L',
+41673=>'L',
+41674=>'L',
+41675=>'L',
+41676=>'L',
+41677=>'L',
+41678=>'L',
+41679=>'L',
+41680=>'L',
+41681=>'L',
+41682=>'L',
+41683=>'L',
+41684=>'L',
+41685=>'L',
+41686=>'L',
+41687=>'L',
+41688=>'L',
+41689=>'L',
+41690=>'L',
+41691=>'L',
+41692=>'L',
+41693=>'L',
+41694=>'L',
+41695=>'L',
+41696=>'L',
+41697=>'L',
+41698=>'L',
+41699=>'L',
+41700=>'L',
+41701=>'L',
+41702=>'L',
+41703=>'L',
+41704=>'L',
+41705=>'L',
+41706=>'L',
+41707=>'L',
+41708=>'L',
+41709=>'L',
+41710=>'L',
+41711=>'L',
+41712=>'L',
+41713=>'L',
+41714=>'L',
+41715=>'L',
+41716=>'L',
+41717=>'L',
+41718=>'L',
+41719=>'L',
+41720=>'L',
+41721=>'L',
+41722=>'L',
+41723=>'L',
+41724=>'L',
+41725=>'L',
+41726=>'L',
+41727=>'L',
+41728=>'L',
+41729=>'L',
+41730=>'L',
+41731=>'L',
+41732=>'L',
+41733=>'L',
+41734=>'L',
+41735=>'L',
+41736=>'L',
+41737=>'L',
+41738=>'L',
+41739=>'L',
+41740=>'L',
+41741=>'L',
+41742=>'L',
+41743=>'L',
+41744=>'L',
+41745=>'L',
+41746=>'L',
+41747=>'L',
+41748=>'L',
+41749=>'L',
+41750=>'L',
+41751=>'L',
+41752=>'L',
+41753=>'L',
+41754=>'L',
+41755=>'L',
+41756=>'L',
+41757=>'L',
+41758=>'L',
+41759=>'L',
+41760=>'L',
+41761=>'L',
+41762=>'L',
+41763=>'L',
+41764=>'L',
+41765=>'L',
+41766=>'L',
+41767=>'L',
+41768=>'L',
+41769=>'L',
+41770=>'L',
+41771=>'L',
+41772=>'L',
+41773=>'L',
+41774=>'L',
+41775=>'L',
+41776=>'L',
+41777=>'L',
+41778=>'L',
+41779=>'L',
+41780=>'L',
+41781=>'L',
+41782=>'L',
+41783=>'L',
+41784=>'L',
+41785=>'L',
+41786=>'L',
+41787=>'L',
+41788=>'L',
+41789=>'L',
+41790=>'L',
+41791=>'L',
+41792=>'L',
+41793=>'L',
+41794=>'L',
+41795=>'L',
+41796=>'L',
+41797=>'L',
+41798=>'L',
+41799=>'L',
+41800=>'L',
+41801=>'L',
+41802=>'L',
+41803=>'L',
+41804=>'L',
+41805=>'L',
+41806=>'L',
+41807=>'L',
+41808=>'L',
+41809=>'L',
+41810=>'L',
+41811=>'L',
+41812=>'L',
+41813=>'L',
+41814=>'L',
+41815=>'L',
+41816=>'L',
+41817=>'L',
+41818=>'L',
+41819=>'L',
+41820=>'L',
+41821=>'L',
+41822=>'L',
+41823=>'L',
+41824=>'L',
+41825=>'L',
+41826=>'L',
+41827=>'L',
+41828=>'L',
+41829=>'L',
+41830=>'L',
+41831=>'L',
+41832=>'L',
+41833=>'L',
+41834=>'L',
+41835=>'L',
+41836=>'L',
+41837=>'L',
+41838=>'L',
+41839=>'L',
+41840=>'L',
+41841=>'L',
+41842=>'L',
+41843=>'L',
+41844=>'L',
+41845=>'L',
+41846=>'L',
+41847=>'L',
+41848=>'L',
+41849=>'L',
+41850=>'L',
+41851=>'L',
+41852=>'L',
+41853=>'L',
+41854=>'L',
+41855=>'L',
+41856=>'L',
+41857=>'L',
+41858=>'L',
+41859=>'L',
+41860=>'L',
+41861=>'L',
+41862=>'L',
+41863=>'L',
+41864=>'L',
+41865=>'L',
+41866=>'L',
+41867=>'L',
+41868=>'L',
+41869=>'L',
+41870=>'L',
+41871=>'L',
+41872=>'L',
+41873=>'L',
+41874=>'L',
+41875=>'L',
+41876=>'L',
+41877=>'L',
+41878=>'L',
+41879=>'L',
+41880=>'L',
+41881=>'L',
+41882=>'L',
+41883=>'L',
+41884=>'L',
+41885=>'L',
+41886=>'L',
+41887=>'L',
+41888=>'L',
+41889=>'L',
+41890=>'L',
+41891=>'L',
+41892=>'L',
+41893=>'L',
+41894=>'L',
+41895=>'L',
+41896=>'L',
+41897=>'L',
+41898=>'L',
+41899=>'L',
+41900=>'L',
+41901=>'L',
+41902=>'L',
+41903=>'L',
+41904=>'L',
+41905=>'L',
+41906=>'L',
+41907=>'L',
+41908=>'L',
+41909=>'L',
+41910=>'L',
+41911=>'L',
+41912=>'L',
+41913=>'L',
+41914=>'L',
+41915=>'L',
+41916=>'L',
+41917=>'L',
+41918=>'L',
+41919=>'L',
+41920=>'L',
+41921=>'L',
+41922=>'L',
+41923=>'L',
+41924=>'L',
+41925=>'L',
+41926=>'L',
+41927=>'L',
+41928=>'L',
+41929=>'L',
+41930=>'L',
+41931=>'L',
+41932=>'L',
+41933=>'L',
+41934=>'L',
+41935=>'L',
+41936=>'L',
+41937=>'L',
+41938=>'L',
+41939=>'L',
+41940=>'L',
+41941=>'L',
+41942=>'L',
+41943=>'L',
+41944=>'L',
+41945=>'L',
+41946=>'L',
+41947=>'L',
+41948=>'L',
+41949=>'L',
+41950=>'L',
+41951=>'L',
+41952=>'L',
+41953=>'L',
+41954=>'L',
+41955=>'L',
+41956=>'L',
+41957=>'L',
+41958=>'L',
+41959=>'L',
+41960=>'L',
+41961=>'L',
+41962=>'L',
+41963=>'L',
+41964=>'L',
+41965=>'L',
+41966=>'L',
+41967=>'L',
+41968=>'L',
+41969=>'L',
+41970=>'L',
+41971=>'L',
+41972=>'L',
+41973=>'L',
+41974=>'L',
+41975=>'L',
+41976=>'L',
+41977=>'L',
+41978=>'L',
+41979=>'L',
+41980=>'L',
+41981=>'L',
+41982=>'L',
+41983=>'L',
+41984=>'L',
+41985=>'L',
+41986=>'L',
+41987=>'L',
+41988=>'L',
+41989=>'L',
+41990=>'L',
+41991=>'L',
+41992=>'L',
+41993=>'L',
+41994=>'L',
+41995=>'L',
+41996=>'L',
+41997=>'L',
+41998=>'L',
+41999=>'L',
+42000=>'L',
+42001=>'L',
+42002=>'L',
+42003=>'L',
+42004=>'L',
+42005=>'L',
+42006=>'L',
+42007=>'L',
+42008=>'L',
+42009=>'L',
+42010=>'L',
+42011=>'L',
+42012=>'L',
+42013=>'L',
+42014=>'L',
+42015=>'L',
+42016=>'L',
+42017=>'L',
+42018=>'L',
+42019=>'L',
+42020=>'L',
+42021=>'L',
+42022=>'L',
+42023=>'L',
+42024=>'L',
+42025=>'L',
+42026=>'L',
+42027=>'L',
+42028=>'L',
+42029=>'L',
+42030=>'L',
+42031=>'L',
+42032=>'L',
+42033=>'L',
+42034=>'L',
+42035=>'L',
+42036=>'L',
+42037=>'L',
+42038=>'L',
+42039=>'L',
+42040=>'L',
+42041=>'L',
+42042=>'L',
+42043=>'L',
+42044=>'L',
+42045=>'L',
+42046=>'L',
+42047=>'L',
+42048=>'L',
+42049=>'L',
+42050=>'L',
+42051=>'L',
+42052=>'L',
+42053=>'L',
+42054=>'L',
+42055=>'L',
+42056=>'L',
+42057=>'L',
+42058=>'L',
+42059=>'L',
+42060=>'L',
+42061=>'L',
+42062=>'L',
+42063=>'L',
+42064=>'L',
+42065=>'L',
+42066=>'L',
+42067=>'L',
+42068=>'L',
+42069=>'L',
+42070=>'L',
+42071=>'L',
+42072=>'L',
+42073=>'L',
+42074=>'L',
+42075=>'L',
+42076=>'L',
+42077=>'L',
+42078=>'L',
+42079=>'L',
+42080=>'L',
+42081=>'L',
+42082=>'L',
+42083=>'L',
+42084=>'L',
+42085=>'L',
+42086=>'L',
+42087=>'L',
+42088=>'L',
+42089=>'L',
+42090=>'L',
+42091=>'L',
+42092=>'L',
+42093=>'L',
+42094=>'L',
+42095=>'L',
+42096=>'L',
+42097=>'L',
+42098=>'L',
+42099=>'L',
+42100=>'L',
+42101=>'L',
+42102=>'L',
+42103=>'L',
+42104=>'L',
+42105=>'L',
+42106=>'L',
+42107=>'L',
+42108=>'L',
+42109=>'L',
+42110=>'L',
+42111=>'L',
+42112=>'L',
+42113=>'L',
+42114=>'L',
+42115=>'L',
+42116=>'L',
+42117=>'L',
+42118=>'L',
+42119=>'L',
+42120=>'L',
+42121=>'L',
+42122=>'L',
+42123=>'L',
+42124=>'L',
+42128=>'ON',
+42129=>'ON',
+42130=>'ON',
+42131=>'ON',
+42132=>'ON',
+42133=>'ON',
+42134=>'ON',
+42135=>'ON',
+42136=>'ON',
+42137=>'ON',
+42138=>'ON',
+42139=>'ON',
+42140=>'ON',
+42141=>'ON',
+42142=>'ON',
+42143=>'ON',
+42144=>'ON',
+42145=>'ON',
+42146=>'ON',
+42147=>'ON',
+42148=>'ON',
+42149=>'ON',
+42150=>'ON',
+42151=>'ON',
+42152=>'ON',
+42153=>'ON',
+42154=>'ON',
+42155=>'ON',
+42156=>'ON',
+42157=>'ON',
+42158=>'ON',
+42159=>'ON',
+42160=>'ON',
+42161=>'ON',
+42162=>'ON',
+42163=>'ON',
+42164=>'ON',
+42165=>'ON',
+42166=>'ON',
+42167=>'ON',
+42168=>'ON',
+42169=>'ON',
+42170=>'ON',
+42171=>'ON',
+42172=>'ON',
+42173=>'ON',
+42174=>'ON',
+42175=>'ON',
+42176=>'ON',
+42177=>'ON',
+42178=>'ON',
+42179=>'ON',
+42180=>'ON',
+42181=>'ON',
+42182=>'ON',
+42752=>'ON',
+42753=>'ON',
+42754=>'ON',
+42755=>'ON',
+42756=>'ON',
+42757=>'ON',
+42758=>'ON',
+42759=>'ON',
+42760=>'ON',
+42761=>'ON',
+42762=>'ON',
+42763=>'ON',
+42764=>'ON',
+42765=>'ON',
+42766=>'ON',
+42767=>'ON',
+42768=>'ON',
+42769=>'ON',
+42770=>'ON',
+42771=>'ON',
+42772=>'ON',
+42773=>'ON',
+42774=>'ON',
+42775=>'ON',
+42776=>'ON',
+42777=>'ON',
+42778=>'ON',
+42784=>'ON',
+42785=>'ON',
+43008=>'L',
+43009=>'L',
+43010=>'NSM',
+43011=>'L',
+43012=>'L',
+43013=>'L',
+43014=>'NSM',
+43015=>'L',
+43016=>'L',
+43017=>'L',
+43018=>'L',
+43019=>'NSM',
+43020=>'L',
+43021=>'L',
+43022=>'L',
+43023=>'L',
+43024=>'L',
+43025=>'L',
+43026=>'L',
+43027=>'L',
+43028=>'L',
+43029=>'L',
+43030=>'L',
+43031=>'L',
+43032=>'L',
+43033=>'L',
+43034=>'L',
+43035=>'L',
+43036=>'L',
+43037=>'L',
+43038=>'L',
+43039=>'L',
+43040=>'L',
+43041=>'L',
+43042=>'L',
+43043=>'L',
+43044=>'L',
+43045=>'NSM',
+43046=>'NSM',
+43047=>'L',
+43048=>'ON',
+43049=>'ON',
+43050=>'ON',
+43051=>'ON',
+43072=>'L',
+43073=>'L',
+43074=>'L',
+43075=>'L',
+43076=>'L',
+43077=>'L',
+43078=>'L',
+43079=>'L',
+43080=>'L',
+43081=>'L',
+43082=>'L',
+43083=>'L',
+43084=>'L',
+43085=>'L',
+43086=>'L',
+43087=>'L',
+43088=>'L',
+43089=>'L',
+43090=>'L',
+43091=>'L',
+43092=>'L',
+43093=>'L',
+43094=>'L',
+43095=>'L',
+43096=>'L',
+43097=>'L',
+43098=>'L',
+43099=>'L',
+43100=>'L',
+43101=>'L',
+43102=>'L',
+43103=>'L',
+43104=>'L',
+43105=>'L',
+43106=>'L',
+43107=>'L',
+43108=>'L',
+43109=>'L',
+43110=>'L',
+43111=>'L',
+43112=>'L',
+43113=>'L',
+43114=>'L',
+43115=>'L',
+43116=>'L',
+43117=>'L',
+43118=>'L',
+43119=>'L',
+43120=>'L',
+43121=>'L',
+43122=>'L',
+43123=>'L',
+43124=>'ON',
+43125=>'ON',
+43126=>'ON',
+43127=>'ON',
+44032=>'L',
+55203=>'L',
+55296=>'L',
+56191=>'L',
+56192=>'L',
+56319=>'L',
+56320=>'L',
+57343=>'L',
+57344=>'L',
+63743=>'L',
+63744=>'L',
+63745=>'L',
+63746=>'L',
+63747=>'L',
+63748=>'L',
+63749=>'L',
+63750=>'L',
+63751=>'L',
+63752=>'L',
+63753=>'L',
+63754=>'L',
+63755=>'L',
+63756=>'L',
+63757=>'L',
+63758=>'L',
+63759=>'L',
+63760=>'L',
+63761=>'L',
+63762=>'L',
+63763=>'L',
+63764=>'L',
+63765=>'L',
+63766=>'L',
+63767=>'L',
+63768=>'L',
+63769=>'L',
+63770=>'L',
+63771=>'L',
+63772=>'L',
+63773=>'L',
+63774=>'L',
+63775=>'L',
+63776=>'L',
+63777=>'L',
+63778=>'L',
+63779=>'L',
+63780=>'L',
+63781=>'L',
+63782=>'L',
+63783=>'L',
+63784=>'L',
+63785=>'L',
+63786=>'L',
+63787=>'L',
+63788=>'L',
+63789=>'L',
+63790=>'L',
+63791=>'L',
+63792=>'L',
+63793=>'L',
+63794=>'L',
+63795=>'L',
+63796=>'L',
+63797=>'L',
+63798=>'L',
+63799=>'L',
+63800=>'L',
+63801=>'L',
+63802=>'L',
+63803=>'L',
+63804=>'L',
+63805=>'L',
+63806=>'L',
+63807=>'L',
+63808=>'L',
+63809=>'L',
+63810=>'L',
+63811=>'L',
+63812=>'L',
+63813=>'L',
+63814=>'L',
+63815=>'L',
+63816=>'L',
+63817=>'L',
+63818=>'L',
+63819=>'L',
+63820=>'L',
+63821=>'L',
+63822=>'L',
+63823=>'L',
+63824=>'L',
+63825=>'L',
+63826=>'L',
+63827=>'L',
+63828=>'L',
+63829=>'L',
+63830=>'L',
+63831=>'L',
+63832=>'L',
+63833=>'L',
+63834=>'L',
+63835=>'L',
+63836=>'L',
+63837=>'L',
+63838=>'L',
+63839=>'L',
+63840=>'L',
+63841=>'L',
+63842=>'L',
+63843=>'L',
+63844=>'L',
+63845=>'L',
+63846=>'L',
+63847=>'L',
+63848=>'L',
+63849=>'L',
+63850=>'L',
+63851=>'L',
+63852=>'L',
+63853=>'L',
+63854=>'L',
+63855=>'L',
+63856=>'L',
+63857=>'L',
+63858=>'L',
+63859=>'L',
+63860=>'L',
+63861=>'L',
+63862=>'L',
+63863=>'L',
+63864=>'L',
+63865=>'L',
+63866=>'L',
+63867=>'L',
+63868=>'L',
+63869=>'L',
+63870=>'L',
+63871=>'L',
+63872=>'L',
+63873=>'L',
+63874=>'L',
+63875=>'L',
+63876=>'L',
+63877=>'L',
+63878=>'L',
+63879=>'L',
+63880=>'L',
+63881=>'L',
+63882=>'L',
+63883=>'L',
+63884=>'L',
+63885=>'L',
+63886=>'L',
+63887=>'L',
+63888=>'L',
+63889=>'L',
+63890=>'L',
+63891=>'L',
+63892=>'L',
+63893=>'L',
+63894=>'L',
+63895=>'L',
+63896=>'L',
+63897=>'L',
+63898=>'L',
+63899=>'L',
+63900=>'L',
+63901=>'L',
+63902=>'L',
+63903=>'L',
+63904=>'L',
+63905=>'L',
+63906=>'L',
+63907=>'L',
+63908=>'L',
+63909=>'L',
+63910=>'L',
+63911=>'L',
+63912=>'L',
+63913=>'L',
+63914=>'L',
+63915=>'L',
+63916=>'L',
+63917=>'L',
+63918=>'L',
+63919=>'L',
+63920=>'L',
+63921=>'L',
+63922=>'L',
+63923=>'L',
+63924=>'L',
+63925=>'L',
+63926=>'L',
+63927=>'L',
+63928=>'L',
+63929=>'L',
+63930=>'L',
+63931=>'L',
+63932=>'L',
+63933=>'L',
+63934=>'L',
+63935=>'L',
+63936=>'L',
+63937=>'L',
+63938=>'L',
+63939=>'L',
+63940=>'L',
+63941=>'L',
+63942=>'L',
+63943=>'L',
+63944=>'L',
+63945=>'L',
+63946=>'L',
+63947=>'L',
+63948=>'L',
+63949=>'L',
+63950=>'L',
+63951=>'L',
+63952=>'L',
+63953=>'L',
+63954=>'L',
+63955=>'L',
+63956=>'L',
+63957=>'L',
+63958=>'L',
+63959=>'L',
+63960=>'L',
+63961=>'L',
+63962=>'L',
+63963=>'L',
+63964=>'L',
+63965=>'L',
+63966=>'L',
+63967=>'L',
+63968=>'L',
+63969=>'L',
+63970=>'L',
+63971=>'L',
+63972=>'L',
+63973=>'L',
+63974=>'L',
+63975=>'L',
+63976=>'L',
+63977=>'L',
+63978=>'L',
+63979=>'L',
+63980=>'L',
+63981=>'L',
+63982=>'L',
+63983=>'L',
+63984=>'L',
+63985=>'L',
+63986=>'L',
+63987=>'L',
+63988=>'L',
+63989=>'L',
+63990=>'L',
+63991=>'L',
+63992=>'L',
+63993=>'L',
+63994=>'L',
+63995=>'L',
+63996=>'L',
+63997=>'L',
+63998=>'L',
+63999=>'L',
+64000=>'L',
+64001=>'L',
+64002=>'L',
+64003=>'L',
+64004=>'L',
+64005=>'L',
+64006=>'L',
+64007=>'L',
+64008=>'L',
+64009=>'L',
+64010=>'L',
+64011=>'L',
+64012=>'L',
+64013=>'L',
+64014=>'L',
+64015=>'L',
+64016=>'L',
+64017=>'L',
+64018=>'L',
+64019=>'L',
+64020=>'L',
+64021=>'L',
+64022=>'L',
+64023=>'L',
+64024=>'L',
+64025=>'L',
+64026=>'L',
+64027=>'L',
+64028=>'L',
+64029=>'L',
+64030=>'L',
+64031=>'L',
+64032=>'L',
+64033=>'L',
+64034=>'L',
+64035=>'L',
+64036=>'L',
+64037=>'L',
+64038=>'L',
+64039=>'L',
+64040=>'L',
+64041=>'L',
+64042=>'L',
+64043=>'L',
+64044=>'L',
+64045=>'L',
+64048=>'L',
+64049=>'L',
+64050=>'L',
+64051=>'L',
+64052=>'L',
+64053=>'L',
+64054=>'L',
+64055=>'L',
+64056=>'L',
+64057=>'L',
+64058=>'L',
+64059=>'L',
+64060=>'L',
+64061=>'L',
+64062=>'L',
+64063=>'L',
+64064=>'L',
+64065=>'L',
+64066=>'L',
+64067=>'L',
+64068=>'L',
+64069=>'L',
+64070=>'L',
+64071=>'L',
+64072=>'L',
+64073=>'L',
+64074=>'L',
+64075=>'L',
+64076=>'L',
+64077=>'L',
+64078=>'L',
+64079=>'L',
+64080=>'L',
+64081=>'L',
+64082=>'L',
+64083=>'L',
+64084=>'L',
+64085=>'L',
+64086=>'L',
+64087=>'L',
+64088=>'L',
+64089=>'L',
+64090=>'L',
+64091=>'L',
+64092=>'L',
+64093=>'L',
+64094=>'L',
+64095=>'L',
+64096=>'L',
+64097=>'L',
+64098=>'L',
+64099=>'L',
+64100=>'L',
+64101=>'L',
+64102=>'L',
+64103=>'L',
+64104=>'L',
+64105=>'L',
+64106=>'L',
+64112=>'L',
+64113=>'L',
+64114=>'L',
+64115=>'L',
+64116=>'L',
+64117=>'L',
+64118=>'L',
+64119=>'L',
+64120=>'L',
+64121=>'L',
+64122=>'L',
+64123=>'L',
+64124=>'L',
+64125=>'L',
+64126=>'L',
+64127=>'L',
+64128=>'L',
+64129=>'L',
+64130=>'L',
+64131=>'L',
+64132=>'L',
+64133=>'L',
+64134=>'L',
+64135=>'L',
+64136=>'L',
+64137=>'L',
+64138=>'L',
+64139=>'L',
+64140=>'L',
+64141=>'L',
+64142=>'L',
+64143=>'L',
+64144=>'L',
+64145=>'L',
+64146=>'L',
+64147=>'L',
+64148=>'L',
+64149=>'L',
+64150=>'L',
+64151=>'L',
+64152=>'L',
+64153=>'L',
+64154=>'L',
+64155=>'L',
+64156=>'L',
+64157=>'L',
+64158=>'L',
+64159=>'L',
+64160=>'L',
+64161=>'L',
+64162=>'L',
+64163=>'L',
+64164=>'L',
+64165=>'L',
+64166=>'L',
+64167=>'L',
+64168=>'L',
+64169=>'L',
+64170=>'L',
+64171=>'L',
+64172=>'L',
+64173=>'L',
+64174=>'L',
+64175=>'L',
+64176=>'L',
+64177=>'L',
+64178=>'L',
+64179=>'L',
+64180=>'L',
+64181=>'L',
+64182=>'L',
+64183=>'L',
+64184=>'L',
+64185=>'L',
+64186=>'L',
+64187=>'L',
+64188=>'L',
+64189=>'L',
+64190=>'L',
+64191=>'L',
+64192=>'L',
+64193=>'L',
+64194=>'L',
+64195=>'L',
+64196=>'L',
+64197=>'L',
+64198=>'L',
+64199=>'L',
+64200=>'L',
+64201=>'L',
+64202=>'L',
+64203=>'L',
+64204=>'L',
+64205=>'L',
+64206=>'L',
+64207=>'L',
+64208=>'L',
+64209=>'L',
+64210=>'L',
+64211=>'L',
+64212=>'L',
+64213=>'L',
+64214=>'L',
+64215=>'L',
+64216=>'L',
+64217=>'L',
+64256=>'L',
+64257=>'L',
+64258=>'L',
+64259=>'L',
+64260=>'L',
+64261=>'L',
+64262=>'L',
+64275=>'L',
+64276=>'L',
+64277=>'L',
+64278=>'L',
+64279=>'L',
+64285=>'R',
+64286=>'NSM',
+64287=>'R',
+64288=>'R',
+64289=>'R',
+64290=>'R',
+64291=>'R',
+64292=>'R',
+64293=>'R',
+64294=>'R',
+64295=>'R',
+64296=>'R',
+64297=>'ES',
+64298=>'R',
+64299=>'R',
+64300=>'R',
+64301=>'R',
+64302=>'R',
+64303=>'R',
+64304=>'R',
+64305=>'R',
+64306=>'R',
+64307=>'R',
+64308=>'R',
+64309=>'R',
+64310=>'R',
+64312=>'R',
+64313=>'R',
+64314=>'R',
+64315=>'R',
+64316=>'R',
+64318=>'R',
+64320=>'R',
+64321=>'R',
+64323=>'R',
+64324=>'R',
+64326=>'R',
+64327=>'R',
+64328=>'R',
+64329=>'R',
+64330=>'R',
+64331=>'R',
+64332=>'R',
+64333=>'R',
+64334=>'R',
+64335=>'R',
+64336=>'AL',
+64337=>'AL',
+64338=>'AL',
+64339=>'AL',
+64340=>'AL',
+64341=>'AL',
+64342=>'AL',
+64343=>'AL',
+64344=>'AL',
+64345=>'AL',
+64346=>'AL',
+64347=>'AL',
+64348=>'AL',
+64349=>'AL',
+64350=>'AL',
+64351=>'AL',
+64352=>'AL',
+64353=>'AL',
+64354=>'AL',
+64355=>'AL',
+64356=>'AL',
+64357=>'AL',
+64358=>'AL',
+64359=>'AL',
+64360=>'AL',
+64361=>'AL',
+64362=>'AL',
+64363=>'AL',
+64364=>'AL',
+64365=>'AL',
+64366=>'AL',
+64367=>'AL',
+64368=>'AL',
+64369=>'AL',
+64370=>'AL',
+64371=>'AL',
+64372=>'AL',
+64373=>'AL',
+64374=>'AL',
+64375=>'AL',
+64376=>'AL',
+64377=>'AL',
+64378=>'AL',
+64379=>'AL',
+64380=>'AL',
+64381=>'AL',
+64382=>'AL',
+64383=>'AL',
+64384=>'AL',
+64385=>'AL',
+64386=>'AL',
+64387=>'AL',
+64388=>'AL',
+64389=>'AL',
+64390=>'AL',
+64391=>'AL',
+64392=>'AL',
+64393=>'AL',
+64394=>'AL',
+64395=>'AL',
+64396=>'AL',
+64397=>'AL',
+64398=>'AL',
+64399=>'AL',
+64400=>'AL',
+64401=>'AL',
+64402=>'AL',
+64403=>'AL',
+64404=>'AL',
+64405=>'AL',
+64406=>'AL',
+64407=>'AL',
+64408=>'AL',
+64409=>'AL',
+64410=>'AL',
+64411=>'AL',
+64412=>'AL',
+64413=>'AL',
+64414=>'AL',
+64415=>'AL',
+64416=>'AL',
+64417=>'AL',
+64418=>'AL',
+64419=>'AL',
+64420=>'AL',
+64421=>'AL',
+64422=>'AL',
+64423=>'AL',
+64424=>'AL',
+64425=>'AL',
+64426=>'AL',
+64427=>'AL',
+64428=>'AL',
+64429=>'AL',
+64430=>'AL',
+64431=>'AL',
+64432=>'AL',
+64433=>'AL',
+64467=>'AL',
+64468=>'AL',
+64469=>'AL',
+64470=>'AL',
+64471=>'AL',
+64472=>'AL',
+64473=>'AL',
+64474=>'AL',
+64475=>'AL',
+64476=>'AL',
+64477=>'AL',
+64478=>'AL',
+64479=>'AL',
+64480=>'AL',
+64481=>'AL',
+64482=>'AL',
+64483=>'AL',
+64484=>'AL',
+64485=>'AL',
+64486=>'AL',
+64487=>'AL',
+64488=>'AL',
+64489=>'AL',
+64490=>'AL',
+64491=>'AL',
+64492=>'AL',
+64493=>'AL',
+64494=>'AL',
+64495=>'AL',
+64496=>'AL',
+64497=>'AL',
+64498=>'AL',
+64499=>'AL',
+64500=>'AL',
+64501=>'AL',
+64502=>'AL',
+64503=>'AL',
+64504=>'AL',
+64505=>'AL',
+64506=>'AL',
+64507=>'AL',
+64508=>'AL',
+64509=>'AL',
+64510=>'AL',
+64511=>'AL',
+64512=>'AL',
+64513=>'AL',
+64514=>'AL',
+64515=>'AL',
+64516=>'AL',
+64517=>'AL',
+64518=>'AL',
+64519=>'AL',
+64520=>'AL',
+64521=>'AL',
+64522=>'AL',
+64523=>'AL',
+64524=>'AL',
+64525=>'AL',
+64526=>'AL',
+64527=>'AL',
+64528=>'AL',
+64529=>'AL',
+64530=>'AL',
+64531=>'AL',
+64532=>'AL',
+64533=>'AL',
+64534=>'AL',
+64535=>'AL',
+64536=>'AL',
+64537=>'AL',
+64538=>'AL',
+64539=>'AL',
+64540=>'AL',
+64541=>'AL',
+64542=>'AL',
+64543=>'AL',
+64544=>'AL',
+64545=>'AL',
+64546=>'AL',
+64547=>'AL',
+64548=>'AL',
+64549=>'AL',
+64550=>'AL',
+64551=>'AL',
+64552=>'AL',
+64553=>'AL',
+64554=>'AL',
+64555=>'AL',
+64556=>'AL',
+64557=>'AL',
+64558=>'AL',
+64559=>'AL',
+64560=>'AL',
+64561=>'AL',
+64562=>'AL',
+64563=>'AL',
+64564=>'AL',
+64565=>'AL',
+64566=>'AL',
+64567=>'AL',
+64568=>'AL',
+64569=>'AL',
+64570=>'AL',
+64571=>'AL',
+64572=>'AL',
+64573=>'AL',
+64574=>'AL',
+64575=>'AL',
+64576=>'AL',
+64577=>'AL',
+64578=>'AL',
+64579=>'AL',
+64580=>'AL',
+64581=>'AL',
+64582=>'AL',
+64583=>'AL',
+64584=>'AL',
+64585=>'AL',
+64586=>'AL',
+64587=>'AL',
+64588=>'AL',
+64589=>'AL',
+64590=>'AL',
+64591=>'AL',
+64592=>'AL',
+64593=>'AL',
+64594=>'AL',
+64595=>'AL',
+64596=>'AL',
+64597=>'AL',
+64598=>'AL',
+64599=>'AL',
+64600=>'AL',
+64601=>'AL',
+64602=>'AL',
+64603=>'AL',
+64604=>'AL',
+64605=>'AL',
+64606=>'AL',
+64607=>'AL',
+64608=>'AL',
+64609=>'AL',
+64610=>'AL',
+64611=>'AL',
+64612=>'AL',
+64613=>'AL',
+64614=>'AL',
+64615=>'AL',
+64616=>'AL',
+64617=>'AL',
+64618=>'AL',
+64619=>'AL',
+64620=>'AL',
+64621=>'AL',
+64622=>'AL',
+64623=>'AL',
+64624=>'AL',
+64625=>'AL',
+64626=>'AL',
+64627=>'AL',
+64628=>'AL',
+64629=>'AL',
+64630=>'AL',
+64631=>'AL',
+64632=>'AL',
+64633=>'AL',
+64634=>'AL',
+64635=>'AL',
+64636=>'AL',
+64637=>'AL',
+64638=>'AL',
+64639=>'AL',
+64640=>'AL',
+64641=>'AL',
+64642=>'AL',
+64643=>'AL',
+64644=>'AL',
+64645=>'AL',
+64646=>'AL',
+64647=>'AL',
+64648=>'AL',
+64649=>'AL',
+64650=>'AL',
+64651=>'AL',
+64652=>'AL',
+64653=>'AL',
+64654=>'AL',
+64655=>'AL',
+64656=>'AL',
+64657=>'AL',
+64658=>'AL',
+64659=>'AL',
+64660=>'AL',
+64661=>'AL',
+64662=>'AL',
+64663=>'AL',
+64664=>'AL',
+64665=>'AL',
+64666=>'AL',
+64667=>'AL',
+64668=>'AL',
+64669=>'AL',
+64670=>'AL',
+64671=>'AL',
+64672=>'AL',
+64673=>'AL',
+64674=>'AL',
+64675=>'AL',
+64676=>'AL',
+64677=>'AL',
+64678=>'AL',
+64679=>'AL',
+64680=>'AL',
+64681=>'AL',
+64682=>'AL',
+64683=>'AL',
+64684=>'AL',
+64685=>'AL',
+64686=>'AL',
+64687=>'AL',
+64688=>'AL',
+64689=>'AL',
+64690=>'AL',
+64691=>'AL',
+64692=>'AL',
+64693=>'AL',
+64694=>'AL',
+64695=>'AL',
+64696=>'AL',
+64697=>'AL',
+64698=>'AL',
+64699=>'AL',
+64700=>'AL',
+64701=>'AL',
+64702=>'AL',
+64703=>'AL',
+64704=>'AL',
+64705=>'AL',
+64706=>'AL',
+64707=>'AL',
+64708=>'AL',
+64709=>'AL',
+64710=>'AL',
+64711=>'AL',
+64712=>'AL',
+64713=>'AL',
+64714=>'AL',
+64715=>'AL',
+64716=>'AL',
+64717=>'AL',
+64718=>'AL',
+64719=>'AL',
+64720=>'AL',
+64721=>'AL',
+64722=>'AL',
+64723=>'AL',
+64724=>'AL',
+64725=>'AL',
+64726=>'AL',
+64727=>'AL',
+64728=>'AL',
+64729=>'AL',
+64730=>'AL',
+64731=>'AL',
+64732=>'AL',
+64733=>'AL',
+64734=>'AL',
+64735=>'AL',
+64736=>'AL',
+64737=>'AL',
+64738=>'AL',
+64739=>'AL',
+64740=>'AL',
+64741=>'AL',
+64742=>'AL',
+64743=>'AL',
+64744=>'AL',
+64745=>'AL',
+64746=>'AL',
+64747=>'AL',
+64748=>'AL',
+64749=>'AL',
+64750=>'AL',
+64751=>'AL',
+64752=>'AL',
+64753=>'AL',
+64754=>'AL',
+64755=>'AL',
+64756=>'AL',
+64757=>'AL',
+64758=>'AL',
+64759=>'AL',
+64760=>'AL',
+64761=>'AL',
+64762=>'AL',
+64763=>'AL',
+64764=>'AL',
+64765=>'AL',
+64766=>'AL',
+64767=>'AL',
+64768=>'AL',
+64769=>'AL',
+64770=>'AL',
+64771=>'AL',
+64772=>'AL',
+64773=>'AL',
+64774=>'AL',
+64775=>'AL',
+64776=>'AL',
+64777=>'AL',
+64778=>'AL',
+64779=>'AL',
+64780=>'AL',
+64781=>'AL',
+64782=>'AL',
+64783=>'AL',
+64784=>'AL',
+64785=>'AL',
+64786=>'AL',
+64787=>'AL',
+64788=>'AL',
+64789=>'AL',
+64790=>'AL',
+64791=>'AL',
+64792=>'AL',
+64793=>'AL',
+64794=>'AL',
+64795=>'AL',
+64796=>'AL',
+64797=>'AL',
+64798=>'AL',
+64799=>'AL',
+64800=>'AL',
+64801=>'AL',
+64802=>'AL',
+64803=>'AL',
+64804=>'AL',
+64805=>'AL',
+64806=>'AL',
+64807=>'AL',
+64808=>'AL',
+64809=>'AL',
+64810=>'AL',
+64811=>'AL',
+64812=>'AL',
+64813=>'AL',
+64814=>'AL',
+64815=>'AL',
+64816=>'AL',
+64817=>'AL',
+64818=>'AL',
+64819=>'AL',
+64820=>'AL',
+64821=>'AL',
+64822=>'AL',
+64823=>'AL',
+64824=>'AL',
+64825=>'AL',
+64826=>'AL',
+64827=>'AL',
+64828=>'AL',
+64829=>'AL',
+64830=>'ON',
+64831=>'ON',
+64848=>'AL',
+64849=>'AL',
+64850=>'AL',
+64851=>'AL',
+64852=>'AL',
+64853=>'AL',
+64854=>'AL',
+64855=>'AL',
+64856=>'AL',
+64857=>'AL',
+64858=>'AL',
+64859=>'AL',
+64860=>'AL',
+64861=>'AL',
+64862=>'AL',
+64863=>'AL',
+64864=>'AL',
+64865=>'AL',
+64866=>'AL',
+64867=>'AL',
+64868=>'AL',
+64869=>'AL',
+64870=>'AL',
+64871=>'AL',
+64872=>'AL',
+64873=>'AL',
+64874=>'AL',
+64875=>'AL',
+64876=>'AL',
+64877=>'AL',
+64878=>'AL',
+64879=>'AL',
+64880=>'AL',
+64881=>'AL',
+64882=>'AL',
+64883=>'AL',
+64884=>'AL',
+64885=>'AL',
+64886=>'AL',
+64887=>'AL',
+64888=>'AL',
+64889=>'AL',
+64890=>'AL',
+64891=>'AL',
+64892=>'AL',
+64893=>'AL',
+64894=>'AL',
+64895=>'AL',
+64896=>'AL',
+64897=>'AL',
+64898=>'AL',
+64899=>'AL',
+64900=>'AL',
+64901=>'AL',
+64902=>'AL',
+64903=>'AL',
+64904=>'AL',
+64905=>'AL',
+64906=>'AL',
+64907=>'AL',
+64908=>'AL',
+64909=>'AL',
+64910=>'AL',
+64911=>'AL',
+64914=>'AL',
+64915=>'AL',
+64916=>'AL',
+64917=>'AL',
+64918=>'AL',
+64919=>'AL',
+64920=>'AL',
+64921=>'AL',
+64922=>'AL',
+64923=>'AL',
+64924=>'AL',
+64925=>'AL',
+64926=>'AL',
+64927=>'AL',
+64928=>'AL',
+64929=>'AL',
+64930=>'AL',
+64931=>'AL',
+64932=>'AL',
+64933=>'AL',
+64934=>'AL',
+64935=>'AL',
+64936=>'AL',
+64937=>'AL',
+64938=>'AL',
+64939=>'AL',
+64940=>'AL',
+64941=>'AL',
+64942=>'AL',
+64943=>'AL',
+64944=>'AL',
+64945=>'AL',
+64946=>'AL',
+64947=>'AL',
+64948=>'AL',
+64949=>'AL',
+64950=>'AL',
+64951=>'AL',
+64952=>'AL',
+64953=>'AL',
+64954=>'AL',
+64955=>'AL',
+64956=>'AL',
+64957=>'AL',
+64958=>'AL',
+64959=>'AL',
+64960=>'AL',
+64961=>'AL',
+64962=>'AL',
+64963=>'AL',
+64964=>'AL',
+64965=>'AL',
+64966=>'AL',
+64967=>'AL',
+65008=>'AL',
+65009=>'AL',
+65010=>'AL',
+65011=>'AL',
+65012=>'AL',
+65013=>'AL',
+65014=>'AL',
+65015=>'AL',
+65016=>'AL',
+65017=>'AL',
+65018=>'AL',
+65019=>'AL',
+65020=>'AL',
+65021=>'ON',
+65024=>'NSM',
+65025=>'NSM',
+65026=>'NSM',
+65027=>'NSM',
+65028=>'NSM',
+65029=>'NSM',
+65030=>'NSM',
+65031=>'NSM',
+65032=>'NSM',
+65033=>'NSM',
+65034=>'NSM',
+65035=>'NSM',
+65036=>'NSM',
+65037=>'NSM',
+65038=>'NSM',
+65039=>'NSM',
+65040=>'ON',
+65041=>'ON',
+65042=>'ON',
+65043=>'ON',
+65044=>'ON',
+65045=>'ON',
+65046=>'ON',
+65047=>'ON',
+65048=>'ON',
+65049=>'ON',
+65056=>'NSM',
+65057=>'NSM',
+65058=>'NSM',
+65059=>'NSM',
+65072=>'ON',
+65073=>'ON',
+65074=>'ON',
+65075=>'ON',
+65076=>'ON',
+65077=>'ON',
+65078=>'ON',
+65079=>'ON',
+65080=>'ON',
+65081=>'ON',
+65082=>'ON',
+65083=>'ON',
+65084=>'ON',
+65085=>'ON',
+65086=>'ON',
+65087=>'ON',
+65088=>'ON',
+65089=>'ON',
+65090=>'ON',
+65091=>'ON',
+65092=>'ON',
+65093=>'ON',
+65094=>'ON',
+65095=>'ON',
+65096=>'ON',
+65097=>'ON',
+65098=>'ON',
+65099=>'ON',
+65100=>'ON',
+65101=>'ON',
+65102=>'ON',
+65103=>'ON',
+65104=>'CS',
+65105=>'ON',
+65106=>'CS',
+65108=>'ON',
+65109=>'CS',
+65110=>'ON',
+65111=>'ON',
+65112=>'ON',
+65113=>'ON',
+65114=>'ON',
+65115=>'ON',
+65116=>'ON',
+65117=>'ON',
+65118=>'ON',
+65119=>'ET',
+65120=>'ON',
+65121=>'ON',
+65122=>'ES',
+65123=>'ES',
+65124=>'ON',
+65125=>'ON',
+65126=>'ON',
+65128=>'ON',
+65129=>'ET',
+65130=>'ET',
+65131=>'ON',
+65136=>'AL',
+65137=>'AL',
+65138=>'AL',
+65139=>'AL',
+65140=>'AL',
+65142=>'AL',
+65143=>'AL',
+65144=>'AL',
+65145=>'AL',
+65146=>'AL',
+65147=>'AL',
+65148=>'AL',
+65149=>'AL',
+65150=>'AL',
+65151=>'AL',
+65152=>'AL',
+65153=>'AL',
+65154=>'AL',
+65155=>'AL',
+65156=>'AL',
+65157=>'AL',
+65158=>'AL',
+65159=>'AL',
+65160=>'AL',
+65161=>'AL',
+65162=>'AL',
+65163=>'AL',
+65164=>'AL',
+65165=>'AL',
+65166=>'AL',
+65167=>'AL',
+65168=>'AL',
+65169=>'AL',
+65170=>'AL',
+65171=>'AL',
+65172=>'AL',
+65173=>'AL',
+65174=>'AL',
+65175=>'AL',
+65176=>'AL',
+65177=>'AL',
+65178=>'AL',
+65179=>'AL',
+65180=>'AL',
+65181=>'AL',
+65182=>'AL',
+65183=>'AL',
+65184=>'AL',
+65185=>'AL',
+65186=>'AL',
+65187=>'AL',
+65188=>'AL',
+65189=>'AL',
+65190=>'AL',
+65191=>'AL',
+65192=>'AL',
+65193=>'AL',
+65194=>'AL',
+65195=>'AL',
+65196=>'AL',
+65197=>'AL',
+65198=>'AL',
+65199=>'AL',
+65200=>'AL',
+65201=>'AL',
+65202=>'AL',
+65203=>'AL',
+65204=>'AL',
+65205=>'AL',
+65206=>'AL',
+65207=>'AL',
+65208=>'AL',
+65209=>'AL',
+65210=>'AL',
+65211=>'AL',
+65212=>'AL',
+65213=>'AL',
+65214=>'AL',
+65215=>'AL',
+65216=>'AL',
+65217=>'AL',
+65218=>'AL',
+65219=>'AL',
+65220=>'AL',
+65221=>'AL',
+65222=>'AL',
+65223=>'AL',
+65224=>'AL',
+65225=>'AL',
+65226=>'AL',
+65227=>'AL',
+65228=>'AL',
+65229=>'AL',
+65230=>'AL',
+65231=>'AL',
+65232=>'AL',
+65233=>'AL',
+65234=>'AL',
+65235=>'AL',
+65236=>'AL',
+65237=>'AL',
+65238=>'AL',
+65239=>'AL',
+65240=>'AL',
+65241=>'AL',
+65242=>'AL',
+65243=>'AL',
+65244=>'AL',
+65245=>'AL',
+65246=>'AL',
+65247=>'AL',
+65248=>'AL',
+65249=>'AL',
+65250=>'AL',
+65251=>'AL',
+65252=>'AL',
+65253=>'AL',
+65254=>'AL',
+65255=>'AL',
+65256=>'AL',
+65257=>'AL',
+65258=>'AL',
+65259=>'AL',
+65260=>'AL',
+65261=>'AL',
+65262=>'AL',
+65263=>'AL',
+65264=>'AL',
+65265=>'AL',
+65266=>'AL',
+65267=>'AL',
+65268=>'AL',
+65269=>'AL',
+65270=>'AL',
+65271=>'AL',
+65272=>'AL',
+65273=>'AL',
+65274=>'AL',
+65275=>'AL',
+65276=>'AL',
+65279=>'BN',
+65281=>'ON',
+65282=>'ON',
+65283=>'ET',
+65284=>'ET',
+65285=>'ET',
+65286=>'ON',
+65287=>'ON',
+65288=>'ON',
+65289=>'ON',
+65290=>'ON',
+65291=>'ES',
+65292=>'CS',
+65293=>'ES',
+65294=>'CS',
+65295=>'CS',
+65296=>'EN',
+65297=>'EN',
+65298=>'EN',
+65299=>'EN',
+65300=>'EN',
+65301=>'EN',
+65302=>'EN',
+65303=>'EN',
+65304=>'EN',
+65305=>'EN',
+65306=>'CS',
+65307=>'ON',
+65308=>'ON',
+65309=>'ON',
+65310=>'ON',
+65311=>'ON',
+65312=>'ON',
+65313=>'L',
+65314=>'L',
+65315=>'L',
+65316=>'L',
+65317=>'L',
+65318=>'L',
+65319=>'L',
+65320=>'L',
+65321=>'L',
+65322=>'L',
+65323=>'L',
+65324=>'L',
+65325=>'L',
+65326=>'L',
+65327=>'L',
+65328=>'L',
+65329=>'L',
+65330=>'L',
+65331=>'L',
+65332=>'L',
+65333=>'L',
+65334=>'L',
+65335=>'L',
+65336=>'L',
+65337=>'L',
+65338=>'L',
+65339=>'ON',
+65340=>'ON',
+65341=>'ON',
+65342=>'ON',
+65343=>'ON',
+65344=>'ON',
+65345=>'L',
+65346=>'L',
+65347=>'L',
+65348=>'L',
+65349=>'L',
+65350=>'L',
+65351=>'L',
+65352=>'L',
+65353=>'L',
+65354=>'L',
+65355=>'L',
+65356=>'L',
+65357=>'L',
+65358=>'L',
+65359=>'L',
+65360=>'L',
+65361=>'L',
+65362=>'L',
+65363=>'L',
+65364=>'L',
+65365=>'L',
+65366=>'L',
+65367=>'L',
+65368=>'L',
+65369=>'L',
+65370=>'L',
+65371=>'ON',
+65372=>'ON',
+65373=>'ON',
+65374=>'ON',
+65375=>'ON',
+65376=>'ON',
+65377=>'ON',
+65378=>'ON',
+65379=>'ON',
+65380=>'ON',
+65381=>'ON',
+65382=>'L',
+65383=>'L',
+65384=>'L',
+65385=>'L',
+65386=>'L',
+65387=>'L',
+65388=>'L',
+65389=>'L',
+65390=>'L',
+65391=>'L',
+65392=>'L',
+65393=>'L',
+65394=>'L',
+65395=>'L',
+65396=>'L',
+65397=>'L',
+65398=>'L',
+65399=>'L',
+65400=>'L',
+65401=>'L',
+65402=>'L',
+65403=>'L',
+65404=>'L',
+65405=>'L',
+65406=>'L',
+65407=>'L',
+65408=>'L',
+65409=>'L',
+65410=>'L',
+65411=>'L',
+65412=>'L',
+65413=>'L',
+65414=>'L',
+65415=>'L',
+65416=>'L',
+65417=>'L',
+65418=>'L',
+65419=>'L',
+65420=>'L',
+65421=>'L',
+65422=>'L',
+65423=>'L',
+65424=>'L',
+65425=>'L',
+65426=>'L',
+65427=>'L',
+65428=>'L',
+65429=>'L',
+65430=>'L',
+65431=>'L',
+65432=>'L',
+65433=>'L',
+65434=>'L',
+65435=>'L',
+65436=>'L',
+65437=>'L',
+65438=>'L',
+65439=>'L',
+65440=>'L',
+65441=>'L',
+65442=>'L',
+65443=>'L',
+65444=>'L',
+65445=>'L',
+65446=>'L',
+65447=>'L',
+65448=>'L',
+65449=>'L',
+65450=>'L',
+65451=>'L',
+65452=>'L',
+65453=>'L',
+65454=>'L',
+65455=>'L',
+65456=>'L',
+65457=>'L',
+65458=>'L',
+65459=>'L',
+65460=>'L',
+65461=>'L',
+65462=>'L',
+65463=>'L',
+65464=>'L',
+65465=>'L',
+65466=>'L',
+65467=>'L',
+65468=>'L',
+65469=>'L',
+65470=>'L',
+65474=>'L',
+65475=>'L',
+65476=>'L',
+65477=>'L',
+65478=>'L',
+65479=>'L',
+65482=>'L',
+65483=>'L',
+65484=>'L',
+65485=>'L',
+65486=>'L',
+65487=>'L',
+65490=>'L',
+65491=>'L',
+65492=>'L',
+65493=>'L',
+65494=>'L',
+65495=>'L',
+65498=>'L',
+65499=>'L',
+65500=>'L',
+65504=>'ET',
+65505=>'ET',
+65506=>'ON',
+65507=>'ON',
+65508=>'ON',
+65509=>'ET',
+65510=>'ET',
+65512=>'ON',
+65513=>'ON',
+65514=>'ON',
+65515=>'ON',
+65516=>'ON',
+65517=>'ON',
+65518=>'ON',
+65529=>'ON',
+65530=>'ON',
+65531=>'ON',
+65532=>'ON',
+65533=>'ON',
+65536=>'L',
+65537=>'L',
+65538=>'L',
+65539=>'L',
+65540=>'L',
+65541=>'L',
+65542=>'L',
+65543=>'L',
+65544=>'L',
+65545=>'L',
+65546=>'L',
+65547=>'L',
+65549=>'L',
+65550=>'L',
+65551=>'L',
+65552=>'L',
+65553=>'L',
+65554=>'L',
+65555=>'L',
+65556=>'L',
+65557=>'L',
+65558=>'L',
+65559=>'L',
+65560=>'L',
+65561=>'L',
+65562=>'L',
+65563=>'L',
+65564=>'L',
+65565=>'L',
+65566=>'L',
+65567=>'L',
+65568=>'L',
+65569=>'L',
+65570=>'L',
+65571=>'L',
+65572=>'L',
+65573=>'L',
+65574=>'L',
+65576=>'L',
+65577=>'L',
+65578=>'L',
+65579=>'L',
+65580=>'L',
+65581=>'L',
+65582=>'L',
+65583=>'L',
+65584=>'L',
+65585=>'L',
+65586=>'L',
+65587=>'L',
+65588=>'L',
+65589=>'L',
+65590=>'L',
+65591=>'L',
+65592=>'L',
+65593=>'L',
+65594=>'L',
+65596=>'L',
+65597=>'L',
+65599=>'L',
+65600=>'L',
+65601=>'L',
+65602=>'L',
+65603=>'L',
+65604=>'L',
+65605=>'L',
+65606=>'L',
+65607=>'L',
+65608=>'L',
+65609=>'L',
+65610=>'L',
+65611=>'L',
+65612=>'L',
+65613=>'L',
+65616=>'L',
+65617=>'L',
+65618=>'L',
+65619=>'L',
+65620=>'L',
+65621=>'L',
+65622=>'L',
+65623=>'L',
+65624=>'L',
+65625=>'L',
+65626=>'L',
+65627=>'L',
+65628=>'L',
+65629=>'L',
+65664=>'L',
+65665=>'L',
+65666=>'L',
+65667=>'L',
+65668=>'L',
+65669=>'L',
+65670=>'L',
+65671=>'L',
+65672=>'L',
+65673=>'L',
+65674=>'L',
+65675=>'L',
+65676=>'L',
+65677=>'L',
+65678=>'L',
+65679=>'L',
+65680=>'L',
+65681=>'L',
+65682=>'L',
+65683=>'L',
+65684=>'L',
+65685=>'L',
+65686=>'L',
+65687=>'L',
+65688=>'L',
+65689=>'L',
+65690=>'L',
+65691=>'L',
+65692=>'L',
+65693=>'L',
+65694=>'L',
+65695=>'L',
+65696=>'L',
+65697=>'L',
+65698=>'L',
+65699=>'L',
+65700=>'L',
+65701=>'L',
+65702=>'L',
+65703=>'L',
+65704=>'L',
+65705=>'L',
+65706=>'L',
+65707=>'L',
+65708=>'L',
+65709=>'L',
+65710=>'L',
+65711=>'L',
+65712=>'L',
+65713=>'L',
+65714=>'L',
+65715=>'L',
+65716=>'L',
+65717=>'L',
+65718=>'L',
+65719=>'L',
+65720=>'L',
+65721=>'L',
+65722=>'L',
+65723=>'L',
+65724=>'L',
+65725=>'L',
+65726=>'L',
+65727=>'L',
+65728=>'L',
+65729=>'L',
+65730=>'L',
+65731=>'L',
+65732=>'L',
+65733=>'L',
+65734=>'L',
+65735=>'L',
+65736=>'L',
+65737=>'L',
+65738=>'L',
+65739=>'L',
+65740=>'L',
+65741=>'L',
+65742=>'L',
+65743=>'L',
+65744=>'L',
+65745=>'L',
+65746=>'L',
+65747=>'L',
+65748=>'L',
+65749=>'L',
+65750=>'L',
+65751=>'L',
+65752=>'L',
+65753=>'L',
+65754=>'L',
+65755=>'L',
+65756=>'L',
+65757=>'L',
+65758=>'L',
+65759=>'L',
+65760=>'L',
+65761=>'L',
+65762=>'L',
+65763=>'L',
+65764=>'L',
+65765=>'L',
+65766=>'L',
+65767=>'L',
+65768=>'L',
+65769=>'L',
+65770=>'L',
+65771=>'L',
+65772=>'L',
+65773=>'L',
+65774=>'L',
+65775=>'L',
+65776=>'L',
+65777=>'L',
+65778=>'L',
+65779=>'L',
+65780=>'L',
+65781=>'L',
+65782=>'L',
+65783=>'L',
+65784=>'L',
+65785=>'L',
+65786=>'L',
+65792=>'L',
+65793=>'ON',
+65794=>'L',
+65799=>'L',
+65800=>'L',
+65801=>'L',
+65802=>'L',
+65803=>'L',
+65804=>'L',
+65805=>'L',
+65806=>'L',
+65807=>'L',
+65808=>'L',
+65809=>'L',
+65810=>'L',
+65811=>'L',
+65812=>'L',
+65813=>'L',
+65814=>'L',
+65815=>'L',
+65816=>'L',
+65817=>'L',
+65818=>'L',
+65819=>'L',
+65820=>'L',
+65821=>'L',
+65822=>'L',
+65823=>'L',
+65824=>'L',
+65825=>'L',
+65826=>'L',
+65827=>'L',
+65828=>'L',
+65829=>'L',
+65830=>'L',
+65831=>'L',
+65832=>'L',
+65833=>'L',
+65834=>'L',
+65835=>'L',
+65836=>'L',
+65837=>'L',
+65838=>'L',
+65839=>'L',
+65840=>'L',
+65841=>'L',
+65842=>'L',
+65843=>'L',
+65847=>'L',
+65848=>'L',
+65849=>'L',
+65850=>'L',
+65851=>'L',
+65852=>'L',
+65853=>'L',
+65854=>'L',
+65855=>'L',
+65856=>'ON',
+65857=>'ON',
+65858=>'ON',
+65859=>'ON',
+65860=>'ON',
+65861=>'ON',
+65862=>'ON',
+65863=>'ON',
+65864=>'ON',
+65865=>'ON',
+65866=>'ON',
+65867=>'ON',
+65868=>'ON',
+65869=>'ON',
+65870=>'ON',
+65871=>'ON',
+65872=>'ON',
+65873=>'ON',
+65874=>'ON',
+65875=>'ON',
+65876=>'ON',
+65877=>'ON',
+65878=>'ON',
+65879=>'ON',
+65880=>'ON',
+65881=>'ON',
+65882=>'ON',
+65883=>'ON',
+65884=>'ON',
+65885=>'ON',
+65886=>'ON',
+65887=>'ON',
+65888=>'ON',
+65889=>'ON',
+65890=>'ON',
+65891=>'ON',
+65892=>'ON',
+65893=>'ON',
+65894=>'ON',
+65895=>'ON',
+65896=>'ON',
+65897=>'ON',
+65898=>'ON',
+65899=>'ON',
+65900=>'ON',
+65901=>'ON',
+65902=>'ON',
+65903=>'ON',
+65904=>'ON',
+65905=>'ON',
+65906=>'ON',
+65907=>'ON',
+65908=>'ON',
+65909=>'ON',
+65910=>'ON',
+65911=>'ON',
+65912=>'ON',
+65913=>'ON',
+65914=>'ON',
+65915=>'ON',
+65916=>'ON',
+65917=>'ON',
+65918=>'ON',
+65919=>'ON',
+65920=>'ON',
+65921=>'ON',
+65922=>'ON',
+65923=>'ON',
+65924=>'ON',
+65925=>'ON',
+65926=>'ON',
+65927=>'ON',
+65928=>'ON',
+65929=>'ON',
+65930=>'ON',
+66304=>'L',
+66305=>'L',
+66306=>'L',
+66307=>'L',
+66308=>'L',
+66309=>'L',
+66310=>'L',
+66311=>'L',
+66312=>'L',
+66313=>'L',
+66314=>'L',
+66315=>'L',
+66316=>'L',
+66317=>'L',
+66318=>'L',
+66319=>'L',
+66320=>'L',
+66321=>'L',
+66322=>'L',
+66323=>'L',
+66324=>'L',
+66325=>'L',
+66326=>'L',
+66327=>'L',
+66328=>'L',
+66329=>'L',
+66330=>'L',
+66331=>'L',
+66332=>'L',
+66333=>'L',
+66334=>'L',
+66336=>'L',
+66337=>'L',
+66338=>'L',
+66339=>'L',
+66352=>'L',
+66353=>'L',
+66354=>'L',
+66355=>'L',
+66356=>'L',
+66357=>'L',
+66358=>'L',
+66359=>'L',
+66360=>'L',
+66361=>'L',
+66362=>'L',
+66363=>'L',
+66364=>'L',
+66365=>'L',
+66366=>'L',
+66367=>'L',
+66368=>'L',
+66369=>'L',
+66370=>'L',
+66371=>'L',
+66372=>'L',
+66373=>'L',
+66374=>'L',
+66375=>'L',
+66376=>'L',
+66377=>'L',
+66378=>'L',
+66432=>'L',
+66433=>'L',
+66434=>'L',
+66435=>'L',
+66436=>'L',
+66437=>'L',
+66438=>'L',
+66439=>'L',
+66440=>'L',
+66441=>'L',
+66442=>'L',
+66443=>'L',
+66444=>'L',
+66445=>'L',
+66446=>'L',
+66447=>'L',
+66448=>'L',
+66449=>'L',
+66450=>'L',
+66451=>'L',
+66452=>'L',
+66453=>'L',
+66454=>'L',
+66455=>'L',
+66456=>'L',
+66457=>'L',
+66458=>'L',
+66459=>'L',
+66460=>'L',
+66461=>'L',
+66463=>'L',
+66464=>'L',
+66465=>'L',
+66466=>'L',
+66467=>'L',
+66468=>'L',
+66469=>'L',
+66470=>'L',
+66471=>'L',
+66472=>'L',
+66473=>'L',
+66474=>'L',
+66475=>'L',
+66476=>'L',
+66477=>'L',
+66478=>'L',
+66479=>'L',
+66480=>'L',
+66481=>'L',
+66482=>'L',
+66483=>'L',
+66484=>'L',
+66485=>'L',
+66486=>'L',
+66487=>'L',
+66488=>'L',
+66489=>'L',
+66490=>'L',
+66491=>'L',
+66492=>'L',
+66493=>'L',
+66494=>'L',
+66495=>'L',
+66496=>'L',
+66497=>'L',
+66498=>'L',
+66499=>'L',
+66504=>'L',
+66505=>'L',
+66506=>'L',
+66507=>'L',
+66508=>'L',
+66509=>'L',
+66510=>'L',
+66511=>'L',
+66512=>'L',
+66513=>'L',
+66514=>'L',
+66515=>'L',
+66516=>'L',
+66517=>'L',
+66560=>'L',
+66561=>'L',
+66562=>'L',
+66563=>'L',
+66564=>'L',
+66565=>'L',
+66566=>'L',
+66567=>'L',
+66568=>'L',
+66569=>'L',
+66570=>'L',
+66571=>'L',
+66572=>'L',
+66573=>'L',
+66574=>'L',
+66575=>'L',
+66576=>'L',
+66577=>'L',
+66578=>'L',
+66579=>'L',
+66580=>'L',
+66581=>'L',
+66582=>'L',
+66583=>'L',
+66584=>'L',
+66585=>'L',
+66586=>'L',
+66587=>'L',
+66588=>'L',
+66589=>'L',
+66590=>'L',
+66591=>'L',
+66592=>'L',
+66593=>'L',
+66594=>'L',
+66595=>'L',
+66596=>'L',
+66597=>'L',
+66598=>'L',
+66599=>'L',
+66600=>'L',
+66601=>'L',
+66602=>'L',
+66603=>'L',
+66604=>'L',
+66605=>'L',
+66606=>'L',
+66607=>'L',
+66608=>'L',
+66609=>'L',
+66610=>'L',
+66611=>'L',
+66612=>'L',
+66613=>'L',
+66614=>'L',
+66615=>'L',
+66616=>'L',
+66617=>'L',
+66618=>'L',
+66619=>'L',
+66620=>'L',
+66621=>'L',
+66622=>'L',
+66623=>'L',
+66624=>'L',
+66625=>'L',
+66626=>'L',
+66627=>'L',
+66628=>'L',
+66629=>'L',
+66630=>'L',
+66631=>'L',
+66632=>'L',
+66633=>'L',
+66634=>'L',
+66635=>'L',
+66636=>'L',
+66637=>'L',
+66638=>'L',
+66639=>'L',
+66640=>'L',
+66641=>'L',
+66642=>'L',
+66643=>'L',
+66644=>'L',
+66645=>'L',
+66646=>'L',
+66647=>'L',
+66648=>'L',
+66649=>'L',
+66650=>'L',
+66651=>'L',
+66652=>'L',
+66653=>'L',
+66654=>'L',
+66655=>'L',
+66656=>'L',
+66657=>'L',
+66658=>'L',
+66659=>'L',
+66660=>'L',
+66661=>'L',
+66662=>'L',
+66663=>'L',
+66664=>'L',
+66665=>'L',
+66666=>'L',
+66667=>'L',
+66668=>'L',
+66669=>'L',
+66670=>'L',
+66671=>'L',
+66672=>'L',
+66673=>'L',
+66674=>'L',
+66675=>'L',
+66676=>'L',
+66677=>'L',
+66678=>'L',
+66679=>'L',
+66680=>'L',
+66681=>'L',
+66682=>'L',
+66683=>'L',
+66684=>'L',
+66685=>'L',
+66686=>'L',
+66687=>'L',
+66688=>'L',
+66689=>'L',
+66690=>'L',
+66691=>'L',
+66692=>'L',
+66693=>'L',
+66694=>'L',
+66695=>'L',
+66696=>'L',
+66697=>'L',
+66698=>'L',
+66699=>'L',
+66700=>'L',
+66701=>'L',
+66702=>'L',
+66703=>'L',
+66704=>'L',
+66705=>'L',
+66706=>'L',
+66707=>'L',
+66708=>'L',
+66709=>'L',
+66710=>'L',
+66711=>'L',
+66712=>'L',
+66713=>'L',
+66714=>'L',
+66715=>'L',
+66716=>'L',
+66717=>'L',
+66720=>'L',
+66721=>'L',
+66722=>'L',
+66723=>'L',
+66724=>'L',
+66725=>'L',
+66726=>'L',
+66727=>'L',
+66728=>'L',
+66729=>'L',
+67584=>'R',
+67585=>'R',
+67586=>'R',
+67587=>'R',
+67588=>'R',
+67589=>'R',
+67592=>'R',
+67594=>'R',
+67595=>'R',
+67596=>'R',
+67597=>'R',
+67598=>'R',
+67599=>'R',
+67600=>'R',
+67601=>'R',
+67602=>'R',
+67603=>'R',
+67604=>'R',
+67605=>'R',
+67606=>'R',
+67607=>'R',
+67608=>'R',
+67609=>'R',
+67610=>'R',
+67611=>'R',
+67612=>'R',
+67613=>'R',
+67614=>'R',
+67615=>'R',
+67616=>'R',
+67617=>'R',
+67618=>'R',
+67619=>'R',
+67620=>'R',
+67621=>'R',
+67622=>'R',
+67623=>'R',
+67624=>'R',
+67625=>'R',
+67626=>'R',
+67627=>'R',
+67628=>'R',
+67629=>'R',
+67630=>'R',
+67631=>'R',
+67632=>'R',
+67633=>'R',
+67634=>'R',
+67635=>'R',
+67636=>'R',
+67637=>'R',
+67639=>'R',
+67640=>'R',
+67644=>'R',
+67647=>'R',
+67840=>'R',
+67841=>'R',
+67842=>'R',
+67843=>'R',
+67844=>'R',
+67845=>'R',
+67846=>'R',
+67847=>'R',
+67848=>'R',
+67849=>'R',
+67850=>'R',
+67851=>'R',
+67852=>'R',
+67853=>'R',
+67854=>'R',
+67855=>'R',
+67856=>'R',
+67857=>'R',
+67858=>'R',
+67859=>'R',
+67860=>'R',
+67861=>'R',
+67862=>'R',
+67863=>'R',
+67864=>'R',
+67865=>'R',
+67871=>'ON',
+68096=>'R',
+68097=>'NSM',
+68098=>'NSM',
+68099=>'NSM',
+68101=>'NSM',
+68102=>'NSM',
+68108=>'NSM',
+68109=>'NSM',
+68110=>'NSM',
+68111=>'NSM',
+68112=>'R',
+68113=>'R',
+68114=>'R',
+68115=>'R',
+68117=>'R',
+68118=>'R',
+68119=>'R',
+68121=>'R',
+68122=>'R',
+68123=>'R',
+68124=>'R',
+68125=>'R',
+68126=>'R',
+68127=>'R',
+68128=>'R',
+68129=>'R',
+68130=>'R',
+68131=>'R',
+68132=>'R',
+68133=>'R',
+68134=>'R',
+68135=>'R',
+68136=>'R',
+68137=>'R',
+68138=>'R',
+68139=>'R',
+68140=>'R',
+68141=>'R',
+68142=>'R',
+68143=>'R',
+68144=>'R',
+68145=>'R',
+68146=>'R',
+68147=>'R',
+68152=>'NSM',
+68153=>'NSM',
+68154=>'NSM',
+68159=>'NSM',
+68160=>'R',
+68161=>'R',
+68162=>'R',
+68163=>'R',
+68164=>'R',
+68165=>'R',
+68166=>'R',
+68167=>'R',
+68176=>'R',
+68177=>'R',
+68178=>'R',
+68179=>'R',
+68180=>'R',
+68181=>'R',
+68182=>'R',
+68183=>'R',
+68184=>'R',
+73728=>'L',
+73729=>'L',
+73730=>'L',
+73731=>'L',
+73732=>'L',
+73733=>'L',
+73734=>'L',
+73735=>'L',
+73736=>'L',
+73737=>'L',
+73738=>'L',
+73739=>'L',
+73740=>'L',
+73741=>'L',
+73742=>'L',
+73743=>'L',
+73744=>'L',
+73745=>'L',
+73746=>'L',
+73747=>'L',
+73748=>'L',
+73749=>'L',
+73750=>'L',
+73751=>'L',
+73752=>'L',
+73753=>'L',
+73754=>'L',
+73755=>'L',
+73756=>'L',
+73757=>'L',
+73758=>'L',
+73759=>'L',
+73760=>'L',
+73761=>'L',
+73762=>'L',
+73763=>'L',
+73764=>'L',
+73765=>'L',
+73766=>'L',
+73767=>'L',
+73768=>'L',
+73769=>'L',
+73770=>'L',
+73771=>'L',
+73772=>'L',
+73773=>'L',
+73774=>'L',
+73775=>'L',
+73776=>'L',
+73777=>'L',
+73778=>'L',
+73779=>'L',
+73780=>'L',
+73781=>'L',
+73782=>'L',
+73783=>'L',
+73784=>'L',
+73785=>'L',
+73786=>'L',
+73787=>'L',
+73788=>'L',
+73789=>'L',
+73790=>'L',
+73791=>'L',
+73792=>'L',
+73793=>'L',
+73794=>'L',
+73795=>'L',
+73796=>'L',
+73797=>'L',
+73798=>'L',
+73799=>'L',
+73800=>'L',
+73801=>'L',
+73802=>'L',
+73803=>'L',
+73804=>'L',
+73805=>'L',
+73806=>'L',
+73807=>'L',
+73808=>'L',
+73809=>'L',
+73810=>'L',
+73811=>'L',
+73812=>'L',
+73813=>'L',
+73814=>'L',
+73815=>'L',
+73816=>'L',
+73817=>'L',
+73818=>'L',
+73819=>'L',
+73820=>'L',
+73821=>'L',
+73822=>'L',
+73823=>'L',
+73824=>'L',
+73825=>'L',
+73826=>'L',
+73827=>'L',
+73828=>'L',
+73829=>'L',
+73830=>'L',
+73831=>'L',
+73832=>'L',
+73833=>'L',
+73834=>'L',
+73835=>'L',
+73836=>'L',
+73837=>'L',
+73838=>'L',
+73839=>'L',
+73840=>'L',
+73841=>'L',
+73842=>'L',
+73843=>'L',
+73844=>'L',
+73845=>'L',
+73846=>'L',
+73847=>'L',
+73848=>'L',
+73849=>'L',
+73850=>'L',
+73851=>'L',
+73852=>'L',
+73853=>'L',
+73854=>'L',
+73855=>'L',
+73856=>'L',
+73857=>'L',
+73858=>'L',
+73859=>'L',
+73860=>'L',
+73861=>'L',
+73862=>'L',
+73863=>'L',
+73864=>'L',
+73865=>'L',
+73866=>'L',
+73867=>'L',
+73868=>'L',
+73869=>'L',
+73870=>'L',
+73871=>'L',
+73872=>'L',
+73873=>'L',
+73874=>'L',
+73875=>'L',
+73876=>'L',
+73877=>'L',
+73878=>'L',
+73879=>'L',
+73880=>'L',
+73881=>'L',
+73882=>'L',
+73883=>'L',
+73884=>'L',
+73885=>'L',
+73886=>'L',
+73887=>'L',
+73888=>'L',
+73889=>'L',
+73890=>'L',
+73891=>'L',
+73892=>'L',
+73893=>'L',
+73894=>'L',
+73895=>'L',
+73896=>'L',
+73897=>'L',
+73898=>'L',
+73899=>'L',
+73900=>'L',
+73901=>'L',
+73902=>'L',
+73903=>'L',
+73904=>'L',
+73905=>'L',
+73906=>'L',
+73907=>'L',
+73908=>'L',
+73909=>'L',
+73910=>'L',
+73911=>'L',
+73912=>'L',
+73913=>'L',
+73914=>'L',
+73915=>'L',
+73916=>'L',
+73917=>'L',
+73918=>'L',
+73919=>'L',
+73920=>'L',
+73921=>'L',
+73922=>'L',
+73923=>'L',
+73924=>'L',
+73925=>'L',
+73926=>'L',
+73927=>'L',
+73928=>'L',
+73929=>'L',
+73930=>'L',
+73931=>'L',
+73932=>'L',
+73933=>'L',
+73934=>'L',
+73935=>'L',
+73936=>'L',
+73937=>'L',
+73938=>'L',
+73939=>'L',
+73940=>'L',
+73941=>'L',
+73942=>'L',
+73943=>'L',
+73944=>'L',
+73945=>'L',
+73946=>'L',
+73947=>'L',
+73948=>'L',
+73949=>'L',
+73950=>'L',
+73951=>'L',
+73952=>'L',
+73953=>'L',
+73954=>'L',
+73955=>'L',
+73956=>'L',
+73957=>'L',
+73958=>'L',
+73959=>'L',
+73960=>'L',
+73961=>'L',
+73962=>'L',
+73963=>'L',
+73964=>'L',
+73965=>'L',
+73966=>'L',
+73967=>'L',
+73968=>'L',
+73969=>'L',
+73970=>'L',
+73971=>'L',
+73972=>'L',
+73973=>'L',
+73974=>'L',
+73975=>'L',
+73976=>'L',
+73977=>'L',
+73978=>'L',
+73979=>'L',
+73980=>'L',
+73981=>'L',
+73982=>'L',
+73983=>'L',
+73984=>'L',
+73985=>'L',
+73986=>'L',
+73987=>'L',
+73988=>'L',
+73989=>'L',
+73990=>'L',
+73991=>'L',
+73992=>'L',
+73993=>'L',
+73994=>'L',
+73995=>'L',
+73996=>'L',
+73997=>'L',
+73998=>'L',
+73999=>'L',
+74000=>'L',
+74001=>'L',
+74002=>'L',
+74003=>'L',
+74004=>'L',
+74005=>'L',
+74006=>'L',
+74007=>'L',
+74008=>'L',
+74009=>'L',
+74010=>'L',
+74011=>'L',
+74012=>'L',
+74013=>'L',
+74014=>'L',
+74015=>'L',
+74016=>'L',
+74017=>'L',
+74018=>'L',
+74019=>'L',
+74020=>'L',
+74021=>'L',
+74022=>'L',
+74023=>'L',
+74024=>'L',
+74025=>'L',
+74026=>'L',
+74027=>'L',
+74028=>'L',
+74029=>'L',
+74030=>'L',
+74031=>'L',
+74032=>'L',
+74033=>'L',
+74034=>'L',
+74035=>'L',
+74036=>'L',
+74037=>'L',
+74038=>'L',
+74039=>'L',
+74040=>'L',
+74041=>'L',
+74042=>'L',
+74043=>'L',
+74044=>'L',
+74045=>'L',
+74046=>'L',
+74047=>'L',
+74048=>'L',
+74049=>'L',
+74050=>'L',
+74051=>'L',
+74052=>'L',
+74053=>'L',
+74054=>'L',
+74055=>'L',
+74056=>'L',
+74057=>'L',
+74058=>'L',
+74059=>'L',
+74060=>'L',
+74061=>'L',
+74062=>'L',
+74063=>'L',
+74064=>'L',
+74065=>'L',
+74066=>'L',
+74067=>'L',
+74068=>'L',
+74069=>'L',
+74070=>'L',
+74071=>'L',
+74072=>'L',
+74073=>'L',
+74074=>'L',
+74075=>'L',
+74076=>'L',
+74077=>'L',
+74078=>'L',
+74079=>'L',
+74080=>'L',
+74081=>'L',
+74082=>'L',
+74083=>'L',
+74084=>'L',
+74085=>'L',
+74086=>'L',
+74087=>'L',
+74088=>'L',
+74089=>'L',
+74090=>'L',
+74091=>'L',
+74092=>'L',
+74093=>'L',
+74094=>'L',
+74095=>'L',
+74096=>'L',
+74097=>'L',
+74098=>'L',
+74099=>'L',
+74100=>'L',
+74101=>'L',
+74102=>'L',
+74103=>'L',
+74104=>'L',
+74105=>'L',
+74106=>'L',
+74107=>'L',
+74108=>'L',
+74109=>'L',
+74110=>'L',
+74111=>'L',
+74112=>'L',
+74113=>'L',
+74114=>'L',
+74115=>'L',
+74116=>'L',
+74117=>'L',
+74118=>'L',
+74119=>'L',
+74120=>'L',
+74121=>'L',
+74122=>'L',
+74123=>'L',
+74124=>'L',
+74125=>'L',
+74126=>'L',
+74127=>'L',
+74128=>'L',
+74129=>'L',
+74130=>'L',
+74131=>'L',
+74132=>'L',
+74133=>'L',
+74134=>'L',
+74135=>'L',
+74136=>'L',
+74137=>'L',
+74138=>'L',
+74139=>'L',
+74140=>'L',
+74141=>'L',
+74142=>'L',
+74143=>'L',
+74144=>'L',
+74145=>'L',
+74146=>'L',
+74147=>'L',
+74148=>'L',
+74149=>'L',
+74150=>'L',
+74151=>'L',
+74152=>'L',
+74153=>'L',
+74154=>'L',
+74155=>'L',
+74156=>'L',
+74157=>'L',
+74158=>'L',
+74159=>'L',
+74160=>'L',
+74161=>'L',
+74162=>'L',
+74163=>'L',
+74164=>'L',
+74165=>'L',
+74166=>'L',
+74167=>'L',
+74168=>'L',
+74169=>'L',
+74170=>'L',
+74171=>'L',
+74172=>'L',
+74173=>'L',
+74174=>'L',
+74175=>'L',
+74176=>'L',
+74177=>'L',
+74178=>'L',
+74179=>'L',
+74180=>'L',
+74181=>'L',
+74182=>'L',
+74183=>'L',
+74184=>'L',
+74185=>'L',
+74186=>'L',
+74187=>'L',
+74188=>'L',
+74189=>'L',
+74190=>'L',
+74191=>'L',
+74192=>'L',
+74193=>'L',
+74194=>'L',
+74195=>'L',
+74196=>'L',
+74197=>'L',
+74198=>'L',
+74199=>'L',
+74200=>'L',
+74201=>'L',
+74202=>'L',
+74203=>'L',
+74204=>'L',
+74205=>'L',
+74206=>'L',
+74207=>'L',
+74208=>'L',
+74209=>'L',
+74210=>'L',
+74211=>'L',
+74212=>'L',
+74213=>'L',
+74214=>'L',
+74215=>'L',
+74216=>'L',
+74217=>'L',
+74218=>'L',
+74219=>'L',
+74220=>'L',
+74221=>'L',
+74222=>'L',
+74223=>'L',
+74224=>'L',
+74225=>'L',
+74226=>'L',
+74227=>'L',
+74228=>'L',
+74229=>'L',
+74230=>'L',
+74231=>'L',
+74232=>'L',
+74233=>'L',
+74234=>'L',
+74235=>'L',
+74236=>'L',
+74237=>'L',
+74238=>'L',
+74239=>'L',
+74240=>'L',
+74241=>'L',
+74242=>'L',
+74243=>'L',
+74244=>'L',
+74245=>'L',
+74246=>'L',
+74247=>'L',
+74248=>'L',
+74249=>'L',
+74250=>'L',
+74251=>'L',
+74252=>'L',
+74253=>'L',
+74254=>'L',
+74255=>'L',
+74256=>'L',
+74257=>'L',
+74258=>'L',
+74259=>'L',
+74260=>'L',
+74261=>'L',
+74262=>'L',
+74263=>'L',
+74264=>'L',
+74265=>'L',
+74266=>'L',
+74267=>'L',
+74268=>'L',
+74269=>'L',
+74270=>'L',
+74271=>'L',
+74272=>'L',
+74273=>'L',
+74274=>'L',
+74275=>'L',
+74276=>'L',
+74277=>'L',
+74278=>'L',
+74279=>'L',
+74280=>'L',
+74281=>'L',
+74282=>'L',
+74283=>'L',
+74284=>'L',
+74285=>'L',
+74286=>'L',
+74287=>'L',
+74288=>'L',
+74289=>'L',
+74290=>'L',
+74291=>'L',
+74292=>'L',
+74293=>'L',
+74294=>'L',
+74295=>'L',
+74296=>'L',
+74297=>'L',
+74298=>'L',
+74299=>'L',
+74300=>'L',
+74301=>'L',
+74302=>'L',
+74303=>'L',
+74304=>'L',
+74305=>'L',
+74306=>'L',
+74307=>'L',
+74308=>'L',
+74309=>'L',
+74310=>'L',
+74311=>'L',
+74312=>'L',
+74313=>'L',
+74314=>'L',
+74315=>'L',
+74316=>'L',
+74317=>'L',
+74318=>'L',
+74319=>'L',
+74320=>'L',
+74321=>'L',
+74322=>'L',
+74323=>'L',
+74324=>'L',
+74325=>'L',
+74326=>'L',
+74327=>'L',
+74328=>'L',
+74329=>'L',
+74330=>'L',
+74331=>'L',
+74332=>'L',
+74333=>'L',
+74334=>'L',
+74335=>'L',
+74336=>'L',
+74337=>'L',
+74338=>'L',
+74339=>'L',
+74340=>'L',
+74341=>'L',
+74342=>'L',
+74343=>'L',
+74344=>'L',
+74345=>'L',
+74346=>'L',
+74347=>'L',
+74348=>'L',
+74349=>'L',
+74350=>'L',
+74351=>'L',
+74352=>'L',
+74353=>'L',
+74354=>'L',
+74355=>'L',
+74356=>'L',
+74357=>'L',
+74358=>'L',
+74359=>'L',
+74360=>'L',
+74361=>'L',
+74362=>'L',
+74363=>'L',
+74364=>'L',
+74365=>'L',
+74366=>'L',
+74367=>'L',
+74368=>'L',
+74369=>'L',
+74370=>'L',
+74371=>'L',
+74372=>'L',
+74373=>'L',
+74374=>'L',
+74375=>'L',
+74376=>'L',
+74377=>'L',
+74378=>'L',
+74379=>'L',
+74380=>'L',
+74381=>'L',
+74382=>'L',
+74383=>'L',
+74384=>'L',
+74385=>'L',
+74386=>'L',
+74387=>'L',
+74388=>'L',
+74389=>'L',
+74390=>'L',
+74391=>'L',
+74392=>'L',
+74393=>'L',
+74394=>'L',
+74395=>'L',
+74396=>'L',
+74397=>'L',
+74398=>'L',
+74399=>'L',
+74400=>'L',
+74401=>'L',
+74402=>'L',
+74403=>'L',
+74404=>'L',
+74405=>'L',
+74406=>'L',
+74407=>'L',
+74408=>'L',
+74409=>'L',
+74410=>'L',
+74411=>'L',
+74412=>'L',
+74413=>'L',
+74414=>'L',
+74415=>'L',
+74416=>'L',
+74417=>'L',
+74418=>'L',
+74419=>'L',
+74420=>'L',
+74421=>'L',
+74422=>'L',
+74423=>'L',
+74424=>'L',
+74425=>'L',
+74426=>'L',
+74427=>'L',
+74428=>'L',
+74429=>'L',
+74430=>'L',
+74431=>'L',
+74432=>'L',
+74433=>'L',
+74434=>'L',
+74435=>'L',
+74436=>'L',
+74437=>'L',
+74438=>'L',
+74439=>'L',
+74440=>'L',
+74441=>'L',
+74442=>'L',
+74443=>'L',
+74444=>'L',
+74445=>'L',
+74446=>'L',
+74447=>'L',
+74448=>'L',
+74449=>'L',
+74450=>'L',
+74451=>'L',
+74452=>'L',
+74453=>'L',
+74454=>'L',
+74455=>'L',
+74456=>'L',
+74457=>'L',
+74458=>'L',
+74459=>'L',
+74460=>'L',
+74461=>'L',
+74462=>'L',
+74463=>'L',
+74464=>'L',
+74465=>'L',
+74466=>'L',
+74467=>'L',
+74468=>'L',
+74469=>'L',
+74470=>'L',
+74471=>'L',
+74472=>'L',
+74473=>'L',
+74474=>'L',
+74475=>'L',
+74476=>'L',
+74477=>'L',
+74478=>'L',
+74479=>'L',
+74480=>'L',
+74481=>'L',
+74482=>'L',
+74483=>'L',
+74484=>'L',
+74485=>'L',
+74486=>'L',
+74487=>'L',
+74488=>'L',
+74489=>'L',
+74490=>'L',
+74491=>'L',
+74492=>'L',
+74493=>'L',
+74494=>'L',
+74495=>'L',
+74496=>'L',
+74497=>'L',
+74498=>'L',
+74499=>'L',
+74500=>'L',
+74501=>'L',
+74502=>'L',
+74503=>'L',
+74504=>'L',
+74505=>'L',
+74506=>'L',
+74507=>'L',
+74508=>'L',
+74509=>'L',
+74510=>'L',
+74511=>'L',
+74512=>'L',
+74513=>'L',
+74514=>'L',
+74515=>'L',
+74516=>'L',
+74517=>'L',
+74518=>'L',
+74519=>'L',
+74520=>'L',
+74521=>'L',
+74522=>'L',
+74523=>'L',
+74524=>'L',
+74525=>'L',
+74526=>'L',
+74527=>'L',
+74528=>'L',
+74529=>'L',
+74530=>'L',
+74531=>'L',
+74532=>'L',
+74533=>'L',
+74534=>'L',
+74535=>'L',
+74536=>'L',
+74537=>'L',
+74538=>'L',
+74539=>'L',
+74540=>'L',
+74541=>'L',
+74542=>'L',
+74543=>'L',
+74544=>'L',
+74545=>'L',
+74546=>'L',
+74547=>'L',
+74548=>'L',
+74549=>'L',
+74550=>'L',
+74551=>'L',
+74552=>'L',
+74553=>'L',
+74554=>'L',
+74555=>'L',
+74556=>'L',
+74557=>'L',
+74558=>'L',
+74559=>'L',
+74560=>'L',
+74561=>'L',
+74562=>'L',
+74563=>'L',
+74564=>'L',
+74565=>'L',
+74566=>'L',
+74567=>'L',
+74568=>'L',
+74569=>'L',
+74570=>'L',
+74571=>'L',
+74572=>'L',
+74573=>'L',
+74574=>'L',
+74575=>'L',
+74576=>'L',
+74577=>'L',
+74578=>'L',
+74579=>'L',
+74580=>'L',
+74581=>'L',
+74582=>'L',
+74583=>'L',
+74584=>'L',
+74585=>'L',
+74586=>'L',
+74587=>'L',
+74588=>'L',
+74589=>'L',
+74590=>'L',
+74591=>'L',
+74592=>'L',
+74593=>'L',
+74594=>'L',
+74595=>'L',
+74596=>'L',
+74597=>'L',
+74598=>'L',
+74599=>'L',
+74600=>'L',
+74601=>'L',
+74602=>'L',
+74603=>'L',
+74604=>'L',
+74605=>'L',
+74606=>'L',
+74752=>'L',
+74753=>'L',
+74754=>'L',
+74755=>'L',
+74756=>'L',
+74757=>'L',
+74758=>'L',
+74759=>'L',
+74760=>'L',
+74761=>'L',
+74762=>'L',
+74763=>'L',
+74764=>'L',
+74765=>'L',
+74766=>'L',
+74767=>'L',
+74768=>'L',
+74769=>'L',
+74770=>'L',
+74771=>'L',
+74772=>'L',
+74773=>'L',
+74774=>'L',
+74775=>'L',
+74776=>'L',
+74777=>'L',
+74778=>'L',
+74779=>'L',
+74780=>'L',
+74781=>'L',
+74782=>'L',
+74783=>'L',
+74784=>'L',
+74785=>'L',
+74786=>'L',
+74787=>'L',
+74788=>'L',
+74789=>'L',
+74790=>'L',
+74791=>'L',
+74792=>'L',
+74793=>'L',
+74794=>'L',
+74795=>'L',
+74796=>'L',
+74797=>'L',
+74798=>'L',
+74799=>'L',
+74800=>'L',
+74801=>'L',
+74802=>'L',
+74803=>'L',
+74804=>'L',
+74805=>'L',
+74806=>'L',
+74807=>'L',
+74808=>'L',
+74809=>'L',
+74810=>'L',
+74811=>'L',
+74812=>'L',
+74813=>'L',
+74814=>'L',
+74815=>'L',
+74816=>'L',
+74817=>'L',
+74818=>'L',
+74819=>'L',
+74820=>'L',
+74821=>'L',
+74822=>'L',
+74823=>'L',
+74824=>'L',
+74825=>'L',
+74826=>'L',
+74827=>'L',
+74828=>'L',
+74829=>'L',
+74830=>'L',
+74831=>'L',
+74832=>'L',
+74833=>'L',
+74834=>'L',
+74835=>'L',
+74836=>'L',
+74837=>'L',
+74838=>'L',
+74839=>'L',
+74840=>'L',
+74841=>'L',
+74842=>'L',
+74843=>'L',
+74844=>'L',
+74845=>'L',
+74846=>'L',
+74847=>'L',
+74848=>'L',
+74849=>'L',
+74850=>'L',
+74864=>'L',
+74865=>'L',
+74866=>'L',
+74867=>'L',
+118784=>'L',
+118785=>'L',
+118786=>'L',
+118787=>'L',
+118788=>'L',
+118789=>'L',
+118790=>'L',
+118791=>'L',
+118792=>'L',
+118793=>'L',
+118794=>'L',
+118795=>'L',
+118796=>'L',
+118797=>'L',
+118798=>'L',
+118799=>'L',
+118800=>'L',
+118801=>'L',
+118802=>'L',
+118803=>'L',
+118804=>'L',
+118805=>'L',
+118806=>'L',
+118807=>'L',
+118808=>'L',
+118809=>'L',
+118810=>'L',
+118811=>'L',
+118812=>'L',
+118813=>'L',
+118814=>'L',
+118815=>'L',
+118816=>'L',
+118817=>'L',
+118818=>'L',
+118819=>'L',
+118820=>'L',
+118821=>'L',
+118822=>'L',
+118823=>'L',
+118824=>'L',
+118825=>'L',
+118826=>'L',
+118827=>'L',
+118828=>'L',
+118829=>'L',
+118830=>'L',
+118831=>'L',
+118832=>'L',
+118833=>'L',
+118834=>'L',
+118835=>'L',
+118836=>'L',
+118837=>'L',
+118838=>'L',
+118839=>'L',
+118840=>'L',
+118841=>'L',
+118842=>'L',
+118843=>'L',
+118844=>'L',
+118845=>'L',
+118846=>'L',
+118847=>'L',
+118848=>'L',
+118849=>'L',
+118850=>'L',
+118851=>'L',
+118852=>'L',
+118853=>'L',
+118854=>'L',
+118855=>'L',
+118856=>'L',
+118857=>'L',
+118858=>'L',
+118859=>'L',
+118860=>'L',
+118861=>'L',
+118862=>'L',
+118863=>'L',
+118864=>'L',
+118865=>'L',
+118866=>'L',
+118867=>'L',
+118868=>'L',
+118869=>'L',
+118870=>'L',
+118871=>'L',
+118872=>'L',
+118873=>'L',
+118874=>'L',
+118875=>'L',
+118876=>'L',
+118877=>'L',
+118878=>'L',
+118879=>'L',
+118880=>'L',
+118881=>'L',
+118882=>'L',
+118883=>'L',
+118884=>'L',
+118885=>'L',
+118886=>'L',
+118887=>'L',
+118888=>'L',
+118889=>'L',
+118890=>'L',
+118891=>'L',
+118892=>'L',
+118893=>'L',
+118894=>'L',
+118895=>'L',
+118896=>'L',
+118897=>'L',
+118898=>'L',
+118899=>'L',
+118900=>'L',
+118901=>'L',
+118902=>'L',
+118903=>'L',
+118904=>'L',
+118905=>'L',
+118906=>'L',
+118907=>'L',
+118908=>'L',
+118909=>'L',
+118910=>'L',
+118911=>'L',
+118912=>'L',
+118913=>'L',
+118914=>'L',
+118915=>'L',
+118916=>'L',
+118917=>'L',
+118918=>'L',
+118919=>'L',
+118920=>'L',
+118921=>'L',
+118922=>'L',
+118923=>'L',
+118924=>'L',
+118925=>'L',
+118926=>'L',
+118927=>'L',
+118928=>'L',
+118929=>'L',
+118930=>'L',
+118931=>'L',
+118932=>'L',
+118933=>'L',
+118934=>'L',
+118935=>'L',
+118936=>'L',
+118937=>'L',
+118938=>'L',
+118939=>'L',
+118940=>'L',
+118941=>'L',
+118942=>'L',
+118943=>'L',
+118944=>'L',
+118945=>'L',
+118946=>'L',
+118947=>'L',
+118948=>'L',
+118949=>'L',
+118950=>'L',
+118951=>'L',
+118952=>'L',
+118953=>'L',
+118954=>'L',
+118955=>'L',
+118956=>'L',
+118957=>'L',
+118958=>'L',
+118959=>'L',
+118960=>'L',
+118961=>'L',
+118962=>'L',
+118963=>'L',
+118964=>'L',
+118965=>'L',
+118966=>'L',
+118967=>'L',
+118968=>'L',
+118969=>'L',
+118970=>'L',
+118971=>'L',
+118972=>'L',
+118973=>'L',
+118974=>'L',
+118975=>'L',
+118976=>'L',
+118977=>'L',
+118978=>'L',
+118979=>'L',
+118980=>'L',
+118981=>'L',
+118982=>'L',
+118983=>'L',
+118984=>'L',
+118985=>'L',
+118986=>'L',
+118987=>'L',
+118988=>'L',
+118989=>'L',
+118990=>'L',
+118991=>'L',
+118992=>'L',
+118993=>'L',
+118994=>'L',
+118995=>'L',
+118996=>'L',
+118997=>'L',
+118998=>'L',
+118999=>'L',
+119000=>'L',
+119001=>'L',
+119002=>'L',
+119003=>'L',
+119004=>'L',
+119005=>'L',
+119006=>'L',
+119007=>'L',
+119008=>'L',
+119009=>'L',
+119010=>'L',
+119011=>'L',
+119012=>'L',
+119013=>'L',
+119014=>'L',
+119015=>'L',
+119016=>'L',
+119017=>'L',
+119018=>'L',
+119019=>'L',
+119020=>'L',
+119021=>'L',
+119022=>'L',
+119023=>'L',
+119024=>'L',
+119025=>'L',
+119026=>'L',
+119027=>'L',
+119028=>'L',
+119029=>'L',
+119040=>'L',
+119041=>'L',
+119042=>'L',
+119043=>'L',
+119044=>'L',
+119045=>'L',
+119046=>'L',
+119047=>'L',
+119048=>'L',
+119049=>'L',
+119050=>'L',
+119051=>'L',
+119052=>'L',
+119053=>'L',
+119054=>'L',
+119055=>'L',
+119056=>'L',
+119057=>'L',
+119058=>'L',
+119059=>'L',
+119060=>'L',
+119061=>'L',
+119062=>'L',
+119063=>'L',
+119064=>'L',
+119065=>'L',
+119066=>'L',
+119067=>'L',
+119068=>'L',
+119069=>'L',
+119070=>'L',
+119071=>'L',
+119072=>'L',
+119073=>'L',
+119074=>'L',
+119075=>'L',
+119076=>'L',
+119077=>'L',
+119078=>'L',
+119082=>'L',
+119083=>'L',
+119084=>'L',
+119085=>'L',
+119086=>'L',
+119087=>'L',
+119088=>'L',
+119089=>'L',
+119090=>'L',
+119091=>'L',
+119092=>'L',
+119093=>'L',
+119094=>'L',
+119095=>'L',
+119096=>'L',
+119097=>'L',
+119098=>'L',
+119099=>'L',
+119100=>'L',
+119101=>'L',
+119102=>'L',
+119103=>'L',
+119104=>'L',
+119105=>'L',
+119106=>'L',
+119107=>'L',
+119108=>'L',
+119109=>'L',
+119110=>'L',
+119111=>'L',
+119112=>'L',
+119113=>'L',
+119114=>'L',
+119115=>'L',
+119116=>'L',
+119117=>'L',
+119118=>'L',
+119119=>'L',
+119120=>'L',
+119121=>'L',
+119122=>'L',
+119123=>'L',
+119124=>'L',
+119125=>'L',
+119126=>'L',
+119127=>'L',
+119128=>'L',
+119129=>'L',
+119130=>'L',
+119131=>'L',
+119132=>'L',
+119133=>'L',
+119134=>'L',
+119135=>'L',
+119136=>'L',
+119137=>'L',
+119138=>'L',
+119139=>'L',
+119140=>'L',
+119141=>'L',
+119142=>'L',
+119143=>'NSM',
+119144=>'NSM',
+119145=>'NSM',
+119146=>'L',
+119147=>'L',
+119148=>'L',
+119149=>'L',
+119150=>'L',
+119151=>'L',
+119152=>'L',
+119153=>'L',
+119154=>'L',
+119155=>'BN',
+119156=>'BN',
+119157=>'BN',
+119158=>'BN',
+119159=>'BN',
+119160=>'BN',
+119161=>'BN',
+119162=>'BN',
+119163=>'NSM',
+119164=>'NSM',
+119165=>'NSM',
+119166=>'NSM',
+119167=>'NSM',
+119168=>'NSM',
+119169=>'NSM',
+119170=>'NSM',
+119171=>'L',
+119172=>'L',
+119173=>'NSM',
+119174=>'NSM',
+119175=>'NSM',
+119176=>'NSM',
+119177=>'NSM',
+119178=>'NSM',
+119179=>'NSM',
+119180=>'L',
+119181=>'L',
+119182=>'L',
+119183=>'L',
+119184=>'L',
+119185=>'L',
+119186=>'L',
+119187=>'L',
+119188=>'L',
+119189=>'L',
+119190=>'L',
+119191=>'L',
+119192=>'L',
+119193=>'L',
+119194=>'L',
+119195=>'L',
+119196=>'L',
+119197=>'L',
+119198=>'L',
+119199=>'L',
+119200=>'L',
+119201=>'L',
+119202=>'L',
+119203=>'L',
+119204=>'L',
+119205=>'L',
+119206=>'L',
+119207=>'L',
+119208=>'L',
+119209=>'L',
+119210=>'NSM',
+119211=>'NSM',
+119212=>'NSM',
+119213=>'NSM',
+119214=>'L',
+119215=>'L',
+119216=>'L',
+119217=>'L',
+119218=>'L',
+119219=>'L',
+119220=>'L',
+119221=>'L',
+119222=>'L',
+119223=>'L',
+119224=>'L',
+119225=>'L',
+119226=>'L',
+119227=>'L',
+119228=>'L',
+119229=>'L',
+119230=>'L',
+119231=>'L',
+119232=>'L',
+119233=>'L',
+119234=>'L',
+119235=>'L',
+119236=>'L',
+119237=>'L',
+119238=>'L',
+119239=>'L',
+119240=>'L',
+119241=>'L',
+119242=>'L',
+119243=>'L',
+119244=>'L',
+119245=>'L',
+119246=>'L',
+119247=>'L',
+119248=>'L',
+119249=>'L',
+119250=>'L',
+119251=>'L',
+119252=>'L',
+119253=>'L',
+119254=>'L',
+119255=>'L',
+119256=>'L',
+119257=>'L',
+119258=>'L',
+119259=>'L',
+119260=>'L',
+119261=>'L',
+119296=>'ON',
+119297=>'ON',
+119298=>'ON',
+119299=>'ON',
+119300=>'ON',
+119301=>'ON',
+119302=>'ON',
+119303=>'ON',
+119304=>'ON',
+119305=>'ON',
+119306=>'ON',
+119307=>'ON',
+119308=>'ON',
+119309=>'ON',
+119310=>'ON',
+119311=>'ON',
+119312=>'ON',
+119313=>'ON',
+119314=>'ON',
+119315=>'ON',
+119316=>'ON',
+119317=>'ON',
+119318=>'ON',
+119319=>'ON',
+119320=>'ON',
+119321=>'ON',
+119322=>'ON',
+119323=>'ON',
+119324=>'ON',
+119325=>'ON',
+119326=>'ON',
+119327=>'ON',
+119328=>'ON',
+119329=>'ON',
+119330=>'ON',
+119331=>'ON',
+119332=>'ON',
+119333=>'ON',
+119334=>'ON',
+119335=>'ON',
+119336=>'ON',
+119337=>'ON',
+119338=>'ON',
+119339=>'ON',
+119340=>'ON',
+119341=>'ON',
+119342=>'ON',
+119343=>'ON',
+119344=>'ON',
+119345=>'ON',
+119346=>'ON',
+119347=>'ON',
+119348=>'ON',
+119349=>'ON',
+119350=>'ON',
+119351=>'ON',
+119352=>'ON',
+119353=>'ON',
+119354=>'ON',
+119355=>'ON',
+119356=>'ON',
+119357=>'ON',
+119358=>'ON',
+119359=>'ON',
+119360=>'ON',
+119361=>'ON',
+119362=>'NSM',
+119363=>'NSM',
+119364=>'NSM',
+119365=>'ON',
+119552=>'ON',
+119553=>'ON',
+119554=>'ON',
+119555=>'ON',
+119556=>'ON',
+119557=>'ON',
+119558=>'ON',
+119559=>'ON',
+119560=>'ON',
+119561=>'ON',
+119562=>'ON',
+119563=>'ON',
+119564=>'ON',
+119565=>'ON',
+119566=>'ON',
+119567=>'ON',
+119568=>'ON',
+119569=>'ON',
+119570=>'ON',
+119571=>'ON',
+119572=>'ON',
+119573=>'ON',
+119574=>'ON',
+119575=>'ON',
+119576=>'ON',
+119577=>'ON',
+119578=>'ON',
+119579=>'ON',
+119580=>'ON',
+119581=>'ON',
+119582=>'ON',
+119583=>'ON',
+119584=>'ON',
+119585=>'ON',
+119586=>'ON',
+119587=>'ON',
+119588=>'ON',
+119589=>'ON',
+119590=>'ON',
+119591=>'ON',
+119592=>'ON',
+119593=>'ON',
+119594=>'ON',
+119595=>'ON',
+119596=>'ON',
+119597=>'ON',
+119598=>'ON',
+119599=>'ON',
+119600=>'ON',
+119601=>'ON',
+119602=>'ON',
+119603=>'ON',
+119604=>'ON',
+119605=>'ON',
+119606=>'ON',
+119607=>'ON',
+119608=>'ON',
+119609=>'ON',
+119610=>'ON',
+119611=>'ON',
+119612=>'ON',
+119613=>'ON',
+119614=>'ON',
+119615=>'ON',
+119616=>'ON',
+119617=>'ON',
+119618=>'ON',
+119619=>'ON',
+119620=>'ON',
+119621=>'ON',
+119622=>'ON',
+119623=>'ON',
+119624=>'ON',
+119625=>'ON',
+119626=>'ON',
+119627=>'ON',
+119628=>'ON',
+119629=>'ON',
+119630=>'ON',
+119631=>'ON',
+119632=>'ON',
+119633=>'ON',
+119634=>'ON',
+119635=>'ON',
+119636=>'ON',
+119637=>'ON',
+119638=>'ON',
+119648=>'L',
+119649=>'L',
+119650=>'L',
+119651=>'L',
+119652=>'L',
+119653=>'L',
+119654=>'L',
+119655=>'L',
+119656=>'L',
+119657=>'L',
+119658=>'L',
+119659=>'L',
+119660=>'L',
+119661=>'L',
+119662=>'L',
+119663=>'L',
+119664=>'L',
+119665=>'L',
+119808=>'L',
+119809=>'L',
+119810=>'L',
+119811=>'L',
+119812=>'L',
+119813=>'L',
+119814=>'L',
+119815=>'L',
+119816=>'L',
+119817=>'L',
+119818=>'L',
+119819=>'L',
+119820=>'L',
+119821=>'L',
+119822=>'L',
+119823=>'L',
+119824=>'L',
+119825=>'L',
+119826=>'L',
+119827=>'L',
+119828=>'L',
+119829=>'L',
+119830=>'L',
+119831=>'L',
+119832=>'L',
+119833=>'L',
+119834=>'L',
+119835=>'L',
+119836=>'L',
+119837=>'L',
+119838=>'L',
+119839=>'L',
+119840=>'L',
+119841=>'L',
+119842=>'L',
+119843=>'L',
+119844=>'L',
+119845=>'L',
+119846=>'L',
+119847=>'L',
+119848=>'L',
+119849=>'L',
+119850=>'L',
+119851=>'L',
+119852=>'L',
+119853=>'L',
+119854=>'L',
+119855=>'L',
+119856=>'L',
+119857=>'L',
+119858=>'L',
+119859=>'L',
+119860=>'L',
+119861=>'L',
+119862=>'L',
+119863=>'L',
+119864=>'L',
+119865=>'L',
+119866=>'L',
+119867=>'L',
+119868=>'L',
+119869=>'L',
+119870=>'L',
+119871=>'L',
+119872=>'L',
+119873=>'L',
+119874=>'L',
+119875=>'L',
+119876=>'L',
+119877=>'L',
+119878=>'L',
+119879=>'L',
+119880=>'L',
+119881=>'L',
+119882=>'L',
+119883=>'L',
+119884=>'L',
+119885=>'L',
+119886=>'L',
+119887=>'L',
+119888=>'L',
+119889=>'L',
+119890=>'L',
+119891=>'L',
+119892=>'L',
+119894=>'L',
+119895=>'L',
+119896=>'L',
+119897=>'L',
+119898=>'L',
+119899=>'L',
+119900=>'L',
+119901=>'L',
+119902=>'L',
+119903=>'L',
+119904=>'L',
+119905=>'L',
+119906=>'L',
+119907=>'L',
+119908=>'L',
+119909=>'L',
+119910=>'L',
+119911=>'L',
+119912=>'L',
+119913=>'L',
+119914=>'L',
+119915=>'L',
+119916=>'L',
+119917=>'L',
+119918=>'L',
+119919=>'L',
+119920=>'L',
+119921=>'L',
+119922=>'L',
+119923=>'L',
+119924=>'L',
+119925=>'L',
+119926=>'L',
+119927=>'L',
+119928=>'L',
+119929=>'L',
+119930=>'L',
+119931=>'L',
+119932=>'L',
+119933=>'L',
+119934=>'L',
+119935=>'L',
+119936=>'L',
+119937=>'L',
+119938=>'L',
+119939=>'L',
+119940=>'L',
+119941=>'L',
+119942=>'L',
+119943=>'L',
+119944=>'L',
+119945=>'L',
+119946=>'L',
+119947=>'L',
+119948=>'L',
+119949=>'L',
+119950=>'L',
+119951=>'L',
+119952=>'L',
+119953=>'L',
+119954=>'L',
+119955=>'L',
+119956=>'L',
+119957=>'L',
+119958=>'L',
+119959=>'L',
+119960=>'L',
+119961=>'L',
+119962=>'L',
+119963=>'L',
+119964=>'L',
+119966=>'L',
+119967=>'L',
+119970=>'L',
+119973=>'L',
+119974=>'L',
+119977=>'L',
+119978=>'L',
+119979=>'L',
+119980=>'L',
+119982=>'L',
+119983=>'L',
+119984=>'L',
+119985=>'L',
+119986=>'L',
+119987=>'L',
+119988=>'L',
+119989=>'L',
+119990=>'L',
+119991=>'L',
+119992=>'L',
+119993=>'L',
+119995=>'L',
+119997=>'L',
+119998=>'L',
+119999=>'L',
+120000=>'L',
+120001=>'L',
+120002=>'L',
+120003=>'L',
+120005=>'L',
+120006=>'L',
+120007=>'L',
+120008=>'L',
+120009=>'L',
+120010=>'L',
+120011=>'L',
+120012=>'L',
+120013=>'L',
+120014=>'L',
+120015=>'L',
+120016=>'L',
+120017=>'L',
+120018=>'L',
+120019=>'L',
+120020=>'L',
+120021=>'L',
+120022=>'L',
+120023=>'L',
+120024=>'L',
+120025=>'L',
+120026=>'L',
+120027=>'L',
+120028=>'L',
+120029=>'L',
+120030=>'L',
+120031=>'L',
+120032=>'L',
+120033=>'L',
+120034=>'L',
+120035=>'L',
+120036=>'L',
+120037=>'L',
+120038=>'L',
+120039=>'L',
+120040=>'L',
+120041=>'L',
+120042=>'L',
+120043=>'L',
+120044=>'L',
+120045=>'L',
+120046=>'L',
+120047=>'L',
+120048=>'L',
+120049=>'L',
+120050=>'L',
+120051=>'L',
+120052=>'L',
+120053=>'L',
+120054=>'L',
+120055=>'L',
+120056=>'L',
+120057=>'L',
+120058=>'L',
+120059=>'L',
+120060=>'L',
+120061=>'L',
+120062=>'L',
+120063=>'L',
+120064=>'L',
+120065=>'L',
+120066=>'L',
+120067=>'L',
+120068=>'L',
+120069=>'L',
+120071=>'L',
+120072=>'L',
+120073=>'L',
+120074=>'L',
+120077=>'L',
+120078=>'L',
+120079=>'L',
+120080=>'L',
+120081=>'L',
+120082=>'L',
+120083=>'L',
+120084=>'L',
+120086=>'L',
+120087=>'L',
+120088=>'L',
+120089=>'L',
+120090=>'L',
+120091=>'L',
+120092=>'L',
+120094=>'L',
+120095=>'L',
+120096=>'L',
+120097=>'L',
+120098=>'L',
+120099=>'L',
+120100=>'L',
+120101=>'L',
+120102=>'L',
+120103=>'L',
+120104=>'L',
+120105=>'L',
+120106=>'L',
+120107=>'L',
+120108=>'L',
+120109=>'L',
+120110=>'L',
+120111=>'L',
+120112=>'L',
+120113=>'L',
+120114=>'L',
+120115=>'L',
+120116=>'L',
+120117=>'L',
+120118=>'L',
+120119=>'L',
+120120=>'L',
+120121=>'L',
+120123=>'L',
+120124=>'L',
+120125=>'L',
+120126=>'L',
+120128=>'L',
+120129=>'L',
+120130=>'L',
+120131=>'L',
+120132=>'L',
+120134=>'L',
+120138=>'L',
+120139=>'L',
+120140=>'L',
+120141=>'L',
+120142=>'L',
+120143=>'L',
+120144=>'L',
+120146=>'L',
+120147=>'L',
+120148=>'L',
+120149=>'L',
+120150=>'L',
+120151=>'L',
+120152=>'L',
+120153=>'L',
+120154=>'L',
+120155=>'L',
+120156=>'L',
+120157=>'L',
+120158=>'L',
+120159=>'L',
+120160=>'L',
+120161=>'L',
+120162=>'L',
+120163=>'L',
+120164=>'L',
+120165=>'L',
+120166=>'L',
+120167=>'L',
+120168=>'L',
+120169=>'L',
+120170=>'L',
+120171=>'L',
+120172=>'L',
+120173=>'L',
+120174=>'L',
+120175=>'L',
+120176=>'L',
+120177=>'L',
+120178=>'L',
+120179=>'L',
+120180=>'L',
+120181=>'L',
+120182=>'L',
+120183=>'L',
+120184=>'L',
+120185=>'L',
+120186=>'L',
+120187=>'L',
+120188=>'L',
+120189=>'L',
+120190=>'L',
+120191=>'L',
+120192=>'L',
+120193=>'L',
+120194=>'L',
+120195=>'L',
+120196=>'L',
+120197=>'L',
+120198=>'L',
+120199=>'L',
+120200=>'L',
+120201=>'L',
+120202=>'L',
+120203=>'L',
+120204=>'L',
+120205=>'L',
+120206=>'L',
+120207=>'L',
+120208=>'L',
+120209=>'L',
+120210=>'L',
+120211=>'L',
+120212=>'L',
+120213=>'L',
+120214=>'L',
+120215=>'L',
+120216=>'L',
+120217=>'L',
+120218=>'L',
+120219=>'L',
+120220=>'L',
+120221=>'L',
+120222=>'L',
+120223=>'L',
+120224=>'L',
+120225=>'L',
+120226=>'L',
+120227=>'L',
+120228=>'L',
+120229=>'L',
+120230=>'L',
+120231=>'L',
+120232=>'L',
+120233=>'L',
+120234=>'L',
+120235=>'L',
+120236=>'L',
+120237=>'L',
+120238=>'L',
+120239=>'L',
+120240=>'L',
+120241=>'L',
+120242=>'L',
+120243=>'L',
+120244=>'L',
+120245=>'L',
+120246=>'L',
+120247=>'L',
+120248=>'L',
+120249=>'L',
+120250=>'L',
+120251=>'L',
+120252=>'L',
+120253=>'L',
+120254=>'L',
+120255=>'L',
+120256=>'L',
+120257=>'L',
+120258=>'L',
+120259=>'L',
+120260=>'L',
+120261=>'L',
+120262=>'L',
+120263=>'L',
+120264=>'L',
+120265=>'L',
+120266=>'L',
+120267=>'L',
+120268=>'L',
+120269=>'L',
+120270=>'L',
+120271=>'L',
+120272=>'L',
+120273=>'L',
+120274=>'L',
+120275=>'L',
+120276=>'L',
+120277=>'L',
+120278=>'L',
+120279=>'L',
+120280=>'L',
+120281=>'L',
+120282=>'L',
+120283=>'L',
+120284=>'L',
+120285=>'L',
+120286=>'L',
+120287=>'L',
+120288=>'L',
+120289=>'L',
+120290=>'L',
+120291=>'L',
+120292=>'L',
+120293=>'L',
+120294=>'L',
+120295=>'L',
+120296=>'L',
+120297=>'L',
+120298=>'L',
+120299=>'L',
+120300=>'L',
+120301=>'L',
+120302=>'L',
+120303=>'L',
+120304=>'L',
+120305=>'L',
+120306=>'L',
+120307=>'L',
+120308=>'L',
+120309=>'L',
+120310=>'L',
+120311=>'L',
+120312=>'L',
+120313=>'L',
+120314=>'L',
+120315=>'L',
+120316=>'L',
+120317=>'L',
+120318=>'L',
+120319=>'L',
+120320=>'L',
+120321=>'L',
+120322=>'L',
+120323=>'L',
+120324=>'L',
+120325=>'L',
+120326=>'L',
+120327=>'L',
+120328=>'L',
+120329=>'L',
+120330=>'L',
+120331=>'L',
+120332=>'L',
+120333=>'L',
+120334=>'L',
+120335=>'L',
+120336=>'L',
+120337=>'L',
+120338=>'L',
+120339=>'L',
+120340=>'L',
+120341=>'L',
+120342=>'L',
+120343=>'L',
+120344=>'L',
+120345=>'L',
+120346=>'L',
+120347=>'L',
+120348=>'L',
+120349=>'L',
+120350=>'L',
+120351=>'L',
+120352=>'L',
+120353=>'L',
+120354=>'L',
+120355=>'L',
+120356=>'L',
+120357=>'L',
+120358=>'L',
+120359=>'L',
+120360=>'L',
+120361=>'L',
+120362=>'L',
+120363=>'L',
+120364=>'L',
+120365=>'L',
+120366=>'L',
+120367=>'L',
+120368=>'L',
+120369=>'L',
+120370=>'L',
+120371=>'L',
+120372=>'L',
+120373=>'L',
+120374=>'L',
+120375=>'L',
+120376=>'L',
+120377=>'L',
+120378=>'L',
+120379=>'L',
+120380=>'L',
+120381=>'L',
+120382=>'L',
+120383=>'L',
+120384=>'L',
+120385=>'L',
+120386=>'L',
+120387=>'L',
+120388=>'L',
+120389=>'L',
+120390=>'L',
+120391=>'L',
+120392=>'L',
+120393=>'L',
+120394=>'L',
+120395=>'L',
+120396=>'L',
+120397=>'L',
+120398=>'L',
+120399=>'L',
+120400=>'L',
+120401=>'L',
+120402=>'L',
+120403=>'L',
+120404=>'L',
+120405=>'L',
+120406=>'L',
+120407=>'L',
+120408=>'L',
+120409=>'L',
+120410=>'L',
+120411=>'L',
+120412=>'L',
+120413=>'L',
+120414=>'L',
+120415=>'L',
+120416=>'L',
+120417=>'L',
+120418=>'L',
+120419=>'L',
+120420=>'L',
+120421=>'L',
+120422=>'L',
+120423=>'L',
+120424=>'L',
+120425=>'L',
+120426=>'L',
+120427=>'L',
+120428=>'L',
+120429=>'L',
+120430=>'L',
+120431=>'L',
+120432=>'L',
+120433=>'L',
+120434=>'L',
+120435=>'L',
+120436=>'L',
+120437=>'L',
+120438=>'L',
+120439=>'L',
+120440=>'L',
+120441=>'L',
+120442=>'L',
+120443=>'L',
+120444=>'L',
+120445=>'L',
+120446=>'L',
+120447=>'L',
+120448=>'L',
+120449=>'L',
+120450=>'L',
+120451=>'L',
+120452=>'L',
+120453=>'L',
+120454=>'L',
+120455=>'L',
+120456=>'L',
+120457=>'L',
+120458=>'L',
+120459=>'L',
+120460=>'L',
+120461=>'L',
+120462=>'L',
+120463=>'L',
+120464=>'L',
+120465=>'L',
+120466=>'L',
+120467=>'L',
+120468=>'L',
+120469=>'L',
+120470=>'L',
+120471=>'L',
+120472=>'L',
+120473=>'L',
+120474=>'L',
+120475=>'L',
+120476=>'L',
+120477=>'L',
+120478=>'L',
+120479=>'L',
+120480=>'L',
+120481=>'L',
+120482=>'L',
+120483=>'L',
+120484=>'L',
+120485=>'L',
+120488=>'L',
+120489=>'L',
+120490=>'L',
+120491=>'L',
+120492=>'L',
+120493=>'L',
+120494=>'L',
+120495=>'L',
+120496=>'L',
+120497=>'L',
+120498=>'L',
+120499=>'L',
+120500=>'L',
+120501=>'L',
+120502=>'L',
+120503=>'L',
+120504=>'L',
+120505=>'L',
+120506=>'L',
+120507=>'L',
+120508=>'L',
+120509=>'L',
+120510=>'L',
+120511=>'L',
+120512=>'L',
+120513=>'L',
+120514=>'L',
+120515=>'L',
+120516=>'L',
+120517=>'L',
+120518=>'L',
+120519=>'L',
+120520=>'L',
+120521=>'L',
+120522=>'L',
+120523=>'L',
+120524=>'L',
+120525=>'L',
+120526=>'L',
+120527=>'L',
+120528=>'L',
+120529=>'L',
+120530=>'L',
+120531=>'L',
+120532=>'L',
+120533=>'L',
+120534=>'L',
+120535=>'L',
+120536=>'L',
+120537=>'L',
+120538=>'L',
+120539=>'L',
+120540=>'L',
+120541=>'L',
+120542=>'L',
+120543=>'L',
+120544=>'L',
+120545=>'L',
+120546=>'L',
+120547=>'L',
+120548=>'L',
+120549=>'L',
+120550=>'L',
+120551=>'L',
+120552=>'L',
+120553=>'L',
+120554=>'L',
+120555=>'L',
+120556=>'L',
+120557=>'L',
+120558=>'L',
+120559=>'L',
+120560=>'L',
+120561=>'L',
+120562=>'L',
+120563=>'L',
+120564=>'L',
+120565=>'L',
+120566=>'L',
+120567=>'L',
+120568=>'L',
+120569=>'L',
+120570=>'L',
+120571=>'L',
+120572=>'L',
+120573=>'L',
+120574=>'L',
+120575=>'L',
+120576=>'L',
+120577=>'L',
+120578=>'L',
+120579=>'L',
+120580=>'L',
+120581=>'L',
+120582=>'L',
+120583=>'L',
+120584=>'L',
+120585=>'L',
+120586=>'L',
+120587=>'L',
+120588=>'L',
+120589=>'L',
+120590=>'L',
+120591=>'L',
+120592=>'L',
+120593=>'L',
+120594=>'L',
+120595=>'L',
+120596=>'L',
+120597=>'L',
+120598=>'L',
+120599=>'L',
+120600=>'L',
+120601=>'L',
+120602=>'L',
+120603=>'L',
+120604=>'L',
+120605=>'L',
+120606=>'L',
+120607=>'L',
+120608=>'L',
+120609=>'L',
+120610=>'L',
+120611=>'L',
+120612=>'L',
+120613=>'L',
+120614=>'L',
+120615=>'L',
+120616=>'L',
+120617=>'L',
+120618=>'L',
+120619=>'L',
+120620=>'L',
+120621=>'L',
+120622=>'L',
+120623=>'L',
+120624=>'L',
+120625=>'L',
+120626=>'L',
+120627=>'L',
+120628=>'L',
+120629=>'L',
+120630=>'L',
+120631=>'L',
+120632=>'L',
+120633=>'L',
+120634=>'L',
+120635=>'L',
+120636=>'L',
+120637=>'L',
+120638=>'L',
+120639=>'L',
+120640=>'L',
+120641=>'L',
+120642=>'L',
+120643=>'L',
+120644=>'L',
+120645=>'L',
+120646=>'L',
+120647=>'L',
+120648=>'L',
+120649=>'L',
+120650=>'L',
+120651=>'L',
+120652=>'L',
+120653=>'L',
+120654=>'L',
+120655=>'L',
+120656=>'L',
+120657=>'L',
+120658=>'L',
+120659=>'L',
+120660=>'L',
+120661=>'L',
+120662=>'L',
+120663=>'L',
+120664=>'L',
+120665=>'L',
+120666=>'L',
+120667=>'L',
+120668=>'L',
+120669=>'L',
+120670=>'L',
+120671=>'L',
+120672=>'L',
+120673=>'L',
+120674=>'L',
+120675=>'L',
+120676=>'L',
+120677=>'L',
+120678=>'L',
+120679=>'L',
+120680=>'L',
+120681=>'L',
+120682=>'L',
+120683=>'L',
+120684=>'L',
+120685=>'L',
+120686=>'L',
+120687=>'L',
+120688=>'L',
+120689=>'L',
+120690=>'L',
+120691=>'L',
+120692=>'L',
+120693=>'L',
+120694=>'L',
+120695=>'L',
+120696=>'L',
+120697=>'L',
+120698=>'L',
+120699=>'L',
+120700=>'L',
+120701=>'L',
+120702=>'L',
+120703=>'L',
+120704=>'L',
+120705=>'L',
+120706=>'L',
+120707=>'L',
+120708=>'L',
+120709=>'L',
+120710=>'L',
+120711=>'L',
+120712=>'L',
+120713=>'L',
+120714=>'L',
+120715=>'L',
+120716=>'L',
+120717=>'L',
+120718=>'L',
+120719=>'L',
+120720=>'L',
+120721=>'L',
+120722=>'L',
+120723=>'L',
+120724=>'L',
+120725=>'L',
+120726=>'L',
+120727=>'L',
+120728=>'L',
+120729=>'L',
+120730=>'L',
+120731=>'L',
+120732=>'L',
+120733=>'L',
+120734=>'L',
+120735=>'L',
+120736=>'L',
+120737=>'L',
+120738=>'L',
+120739=>'L',
+120740=>'L',
+120741=>'L',
+120742=>'L',
+120743=>'L',
+120744=>'L',
+120745=>'L',
+120746=>'L',
+120747=>'L',
+120748=>'L',
+120749=>'L',
+120750=>'L',
+120751=>'L',
+120752=>'L',
+120753=>'L',
+120754=>'L',
+120755=>'L',
+120756=>'L',
+120757=>'L',
+120758=>'L',
+120759=>'L',
+120760=>'L',
+120761=>'L',
+120762=>'L',
+120763=>'L',
+120764=>'L',
+120765=>'L',
+120766=>'L',
+120767=>'L',
+120768=>'L',
+120769=>'L',
+120770=>'L',
+120771=>'L',
+120772=>'L',
+120773=>'L',
+120774=>'L',
+120775=>'L',
+120776=>'L',
+120777=>'L',
+120778=>'L',
+120779=>'L',
+120782=>'EN',
+120783=>'EN',
+120784=>'EN',
+120785=>'EN',
+120786=>'EN',
+120787=>'EN',
+120788=>'EN',
+120789=>'EN',
+120790=>'EN',
+120791=>'EN',
+120792=>'EN',
+120793=>'EN',
+120794=>'EN',
+120795=>'EN',
+120796=>'EN',
+120797=>'EN',
+120798=>'EN',
+120799=>'EN',
+120800=>'EN',
+120801=>'EN',
+120802=>'EN',
+120803=>'EN',
+120804=>'EN',
+120805=>'EN',
+120806=>'EN',
+120807=>'EN',
+120808=>'EN',
+120809=>'EN',
+120810=>'EN',
+120811=>'EN',
+120812=>'EN',
+120813=>'EN',
+120814=>'EN',
+120815=>'EN',
+120816=>'EN',
+120817=>'EN',
+120818=>'EN',
+120819=>'EN',
+120820=>'EN',
+120821=>'EN',
+120822=>'EN',
+120823=>'EN',
+120824=>'EN',
+120825=>'EN',
+120826=>'EN',
+120827=>'EN',
+120828=>'EN',
+120829=>'EN',
+120830=>'EN',
+120831=>'EN',
+131072=>'L',
+173782=>'L',
+194560=>'L',
+194561=>'L',
+194562=>'L',
+194563=>'L',
+194564=>'L',
+194565=>'L',
+194566=>'L',
+194567=>'L',
+194568=>'L',
+194569=>'L',
+194570=>'L',
+194571=>'L',
+194572=>'L',
+194573=>'L',
+194574=>'L',
+194575=>'L',
+194576=>'L',
+194577=>'L',
+194578=>'L',
+194579=>'L',
+194580=>'L',
+194581=>'L',
+194582=>'L',
+194583=>'L',
+194584=>'L',
+194585=>'L',
+194586=>'L',
+194587=>'L',
+194588=>'L',
+194589=>'L',
+194590=>'L',
+194591=>'L',
+194592=>'L',
+194593=>'L',
+194594=>'L',
+194595=>'L',
+194596=>'L',
+194597=>'L',
+194598=>'L',
+194599=>'L',
+194600=>'L',
+194601=>'L',
+194602=>'L',
+194603=>'L',
+194604=>'L',
+194605=>'L',
+194606=>'L',
+194607=>'L',
+194608=>'L',
+194609=>'L',
+194610=>'L',
+194611=>'L',
+194612=>'L',
+194613=>'L',
+194614=>'L',
+194615=>'L',
+194616=>'L',
+194617=>'L',
+194618=>'L',
+194619=>'L',
+194620=>'L',
+194621=>'L',
+194622=>'L',
+194623=>'L',
+194624=>'L',
+194625=>'L',
+194626=>'L',
+194627=>'L',
+194628=>'L',
+194629=>'L',
+194630=>'L',
+194631=>'L',
+194632=>'L',
+194633=>'L',
+194634=>'L',
+194635=>'L',
+194636=>'L',
+194637=>'L',
+194638=>'L',
+194639=>'L',
+194640=>'L',
+194641=>'L',
+194642=>'L',
+194643=>'L',
+194644=>'L',
+194645=>'L',
+194646=>'L',
+194647=>'L',
+194648=>'L',
+194649=>'L',
+194650=>'L',
+194651=>'L',
+194652=>'L',
+194653=>'L',
+194654=>'L',
+194655=>'L',
+194656=>'L',
+194657=>'L',
+194658=>'L',
+194659=>'L',
+194660=>'L',
+194661=>'L',
+194662=>'L',
+194663=>'L',
+194664=>'L',
+194665=>'L',
+194666=>'L',
+194667=>'L',
+194668=>'L',
+194669=>'L',
+194670=>'L',
+194671=>'L',
+194672=>'L',
+194673=>'L',
+194674=>'L',
+194675=>'L',
+194676=>'L',
+194677=>'L',
+194678=>'L',
+194679=>'L',
+194680=>'L',
+194681=>'L',
+194682=>'L',
+194683=>'L',
+194684=>'L',
+194685=>'L',
+194686=>'L',
+194687=>'L',
+194688=>'L',
+194689=>'L',
+194690=>'L',
+194691=>'L',
+194692=>'L',
+194693=>'L',
+194694=>'L',
+194695=>'L',
+194696=>'L',
+194697=>'L',
+194698=>'L',
+194699=>'L',
+194700=>'L',
+194701=>'L',
+194702=>'L',
+194703=>'L',
+194704=>'L',
+194705=>'L',
+194706=>'L',
+194707=>'L',
+194708=>'L',
+194709=>'L',
+194710=>'L',
+194711=>'L',
+194712=>'L',
+194713=>'L',
+194714=>'L',
+194715=>'L',
+194716=>'L',
+194717=>'L',
+194718=>'L',
+194719=>'L',
+194720=>'L',
+194721=>'L',
+194722=>'L',
+194723=>'L',
+194724=>'L',
+194725=>'L',
+194726=>'L',
+194727=>'L',
+194728=>'L',
+194729=>'L',
+194730=>'L',
+194731=>'L',
+194732=>'L',
+194733=>'L',
+194734=>'L',
+194735=>'L',
+194736=>'L',
+194737=>'L',
+194738=>'L',
+194739=>'L',
+194740=>'L',
+194741=>'L',
+194742=>'L',
+194743=>'L',
+194744=>'L',
+194745=>'L',
+194746=>'L',
+194747=>'L',
+194748=>'L',
+194749=>'L',
+194750=>'L',
+194751=>'L',
+194752=>'L',
+194753=>'L',
+194754=>'L',
+194755=>'L',
+194756=>'L',
+194757=>'L',
+194758=>'L',
+194759=>'L',
+194760=>'L',
+194761=>'L',
+194762=>'L',
+194763=>'L',
+194764=>'L',
+194765=>'L',
+194766=>'L',
+194767=>'L',
+194768=>'L',
+194769=>'L',
+194770=>'L',
+194771=>'L',
+194772=>'L',
+194773=>'L',
+194774=>'L',
+194775=>'L',
+194776=>'L',
+194777=>'L',
+194778=>'L',
+194779=>'L',
+194780=>'L',
+194781=>'L',
+194782=>'L',
+194783=>'L',
+194784=>'L',
+194785=>'L',
+194786=>'L',
+194787=>'L',
+194788=>'L',
+194789=>'L',
+194790=>'L',
+194791=>'L',
+194792=>'L',
+194793=>'L',
+194794=>'L',
+194795=>'L',
+194796=>'L',
+194797=>'L',
+194798=>'L',
+194799=>'L',
+194800=>'L',
+194801=>'L',
+194802=>'L',
+194803=>'L',
+194804=>'L',
+194805=>'L',
+194806=>'L',
+194807=>'L',
+194808=>'L',
+194809=>'L',
+194810=>'L',
+194811=>'L',
+194812=>'L',
+194813=>'L',
+194814=>'L',
+194815=>'L',
+194816=>'L',
+194817=>'L',
+194818=>'L',
+194819=>'L',
+194820=>'L',
+194821=>'L',
+194822=>'L',
+194823=>'L',
+194824=>'L',
+194825=>'L',
+194826=>'L',
+194827=>'L',
+194828=>'L',
+194829=>'L',
+194830=>'L',
+194831=>'L',
+194832=>'L',
+194833=>'L',
+194834=>'L',
+194835=>'L',
+194836=>'L',
+194837=>'L',
+194838=>'L',
+194839=>'L',
+194840=>'L',
+194841=>'L',
+194842=>'L',
+194843=>'L',
+194844=>'L',
+194845=>'L',
+194846=>'L',
+194847=>'L',
+194848=>'L',
+194849=>'L',
+194850=>'L',
+194851=>'L',
+194852=>'L',
+194853=>'L',
+194854=>'L',
+194855=>'L',
+194856=>'L',
+194857=>'L',
+194858=>'L',
+194859=>'L',
+194860=>'L',
+194861=>'L',
+194862=>'L',
+194863=>'L',
+194864=>'L',
+194865=>'L',
+194866=>'L',
+194867=>'L',
+194868=>'L',
+194869=>'L',
+194870=>'L',
+194871=>'L',
+194872=>'L',
+194873=>'L',
+194874=>'L',
+194875=>'L',
+194876=>'L',
+194877=>'L',
+194878=>'L',
+194879=>'L',
+194880=>'L',
+194881=>'L',
+194882=>'L',
+194883=>'L',
+194884=>'L',
+194885=>'L',
+194886=>'L',
+194887=>'L',
+194888=>'L',
+194889=>'L',
+194890=>'L',
+194891=>'L',
+194892=>'L',
+194893=>'L',
+194894=>'L',
+194895=>'L',
+194896=>'L',
+194897=>'L',
+194898=>'L',
+194899=>'L',
+194900=>'L',
+194901=>'L',
+194902=>'L',
+194903=>'L',
+194904=>'L',
+194905=>'L',
+194906=>'L',
+194907=>'L',
+194908=>'L',
+194909=>'L',
+194910=>'L',
+194911=>'L',
+194912=>'L',
+194913=>'L',
+194914=>'L',
+194915=>'L',
+194916=>'L',
+194917=>'L',
+194918=>'L',
+194919=>'L',
+194920=>'L',
+194921=>'L',
+194922=>'L',
+194923=>'L',
+194924=>'L',
+194925=>'L',
+194926=>'L',
+194927=>'L',
+194928=>'L',
+194929=>'L',
+194930=>'L',
+194931=>'L',
+194932=>'L',
+194933=>'L',
+194934=>'L',
+194935=>'L',
+194936=>'L',
+194937=>'L',
+194938=>'L',
+194939=>'L',
+194940=>'L',
+194941=>'L',
+194942=>'L',
+194943=>'L',
+194944=>'L',
+194945=>'L',
+194946=>'L',
+194947=>'L',
+194948=>'L',
+194949=>'L',
+194950=>'L',
+194951=>'L',
+194952=>'L',
+194953=>'L',
+194954=>'L',
+194955=>'L',
+194956=>'L',
+194957=>'L',
+194958=>'L',
+194959=>'L',
+194960=>'L',
+194961=>'L',
+194962=>'L',
+194963=>'L',
+194964=>'L',
+194965=>'L',
+194966=>'L',
+194967=>'L',
+194968=>'L',
+194969=>'L',
+194970=>'L',
+194971=>'L',
+194972=>'L',
+194973=>'L',
+194974=>'L',
+194975=>'L',
+194976=>'L',
+194977=>'L',
+194978=>'L',
+194979=>'L',
+194980=>'L',
+194981=>'L',
+194982=>'L',
+194983=>'L',
+194984=>'L',
+194985=>'L',
+194986=>'L',
+194987=>'L',
+194988=>'L',
+194989=>'L',
+194990=>'L',
+194991=>'L',
+194992=>'L',
+194993=>'L',
+194994=>'L',
+194995=>'L',
+194996=>'L',
+194997=>'L',
+194998=>'L',
+194999=>'L',
+195000=>'L',
+195001=>'L',
+195002=>'L',
+195003=>'L',
+195004=>'L',
+195005=>'L',
+195006=>'L',
+195007=>'L',
+195008=>'L',
+195009=>'L',
+195010=>'L',
+195011=>'L',
+195012=>'L',
+195013=>'L',
+195014=>'L',
+195015=>'L',
+195016=>'L',
+195017=>'L',
+195018=>'L',
+195019=>'L',
+195020=>'L',
+195021=>'L',
+195022=>'L',
+195023=>'L',
+195024=>'L',
+195025=>'L',
+195026=>'L',
+195027=>'L',
+195028=>'L',
+195029=>'L',
+195030=>'L',
+195031=>'L',
+195032=>'L',
+195033=>'L',
+195034=>'L',
+195035=>'L',
+195036=>'L',
+195037=>'L',
+195038=>'L',
+195039=>'L',
+195040=>'L',
+195041=>'L',
+195042=>'L',
+195043=>'L',
+195044=>'L',
+195045=>'L',
+195046=>'L',
+195047=>'L',
+195048=>'L',
+195049=>'L',
+195050=>'L',
+195051=>'L',
+195052=>'L',
+195053=>'L',
+195054=>'L',
+195055=>'L',
+195056=>'L',
+195057=>'L',
+195058=>'L',
+195059=>'L',
+195060=>'L',
+195061=>'L',
+195062=>'L',
+195063=>'L',
+195064=>'L',
+195065=>'L',
+195066=>'L',
+195067=>'L',
+195068=>'L',
+195069=>'L',
+195070=>'L',
+195071=>'L',
+195072=>'L',
+195073=>'L',
+195074=>'L',
+195075=>'L',
+195076=>'L',
+195077=>'L',
+195078=>'L',
+195079=>'L',
+195080=>'L',
+195081=>'L',
+195082=>'L',
+195083=>'L',
+195084=>'L',
+195085=>'L',
+195086=>'L',
+195087=>'L',
+195088=>'L',
+195089=>'L',
+195090=>'L',
+195091=>'L',
+195092=>'L',
+195093=>'L',
+195094=>'L',
+195095=>'L',
+195096=>'L',
+195097=>'L',
+195098=>'L',
+195099=>'L',
+195100=>'L',
+195101=>'L',
+917505=>'BN',
+917536=>'BN',
+917537=>'BN',
+917538=>'BN',
+917539=>'BN',
+917540=>'BN',
+917541=>'BN',
+917542=>'BN',
+917543=>'BN',
+917544=>'BN',
+917545=>'BN',
+917546=>'BN',
+917547=>'BN',
+917548=>'BN',
+917549=>'BN',
+917550=>'BN',
+917551=>'BN',
+917552=>'BN',
+917553=>'BN',
+917554=>'BN',
+917555=>'BN',
+917556=>'BN',
+917557=>'BN',
+917558=>'BN',
+917559=>'BN',
+917560=>'BN',
+917561=>'BN',
+917562=>'BN',
+917563=>'BN',
+917564=>'BN',
+917565=>'BN',
+917566=>'BN',
+917567=>'BN',
+917568=>'BN',
+917569=>'BN',
+917570=>'BN',
+917571=>'BN',
+917572=>'BN',
+917573=>'BN',
+917574=>'BN',
+917575=>'BN',
+917576=>'BN',
+917577=>'BN',
+917578=>'BN',
+917579=>'BN',
+917580=>'BN',
+917581=>'BN',
+917582=>'BN',
+917583=>'BN',
+917584=>'BN',
+917585=>'BN',
+917586=>'BN',
+917587=>'BN',
+917588=>'BN',
+917589=>'BN',
+917590=>'BN',
+917591=>'BN',
+917592=>'BN',
+917593=>'BN',
+917594=>'BN',
+917595=>'BN',
+917596=>'BN',
+917597=>'BN',
+917598=>'BN',
+917599=>'BN',
+917600=>'BN',
+917601=>'BN',
+917602=>'BN',
+917603=>'BN',
+917604=>'BN',
+917605=>'BN',
+917606=>'BN',
+917607=>'BN',
+917608=>'BN',
+917609=>'BN',
+917610=>'BN',
+917611=>'BN',
+917612=>'BN',
+917613=>'BN',
+917614=>'BN',
+917615=>'BN',
+917616=>'BN',
+917617=>'BN',
+917618=>'BN',
+917619=>'BN',
+917620=>'BN',
+917621=>'BN',
+917622=>'BN',
+917623=>'BN',
+917624=>'BN',
+917625=>'BN',
+917626=>'BN',
+917627=>'BN',
+917628=>'BN',
+917629=>'BN',
+917630=>'BN',
+917631=>'BN',
+917760=>'NSM',
+917761=>'NSM',
+917762=>'NSM',
+917763=>'NSM',
+917764=>'NSM',
+917765=>'NSM',
+917766=>'NSM',
+917767=>'NSM',
+917768=>'NSM',
+917769=>'NSM',
+917770=>'NSM',
+917771=>'NSM',
+917772=>'NSM',
+917773=>'NSM',
+917774=>'NSM',
+917775=>'NSM',
+917776=>'NSM',
+917777=>'NSM',
+917778=>'NSM',
+917779=>'NSM',
+917780=>'NSM',
+917781=>'NSM',
+917782=>'NSM',
+917783=>'NSM',
+917784=>'NSM',
+917785=>'NSM',
+917786=>'NSM',
+917787=>'NSM',
+917788=>'NSM',
+917789=>'NSM',
+917790=>'NSM',
+917791=>'NSM',
+917792=>'NSM',
+917793=>'NSM',
+917794=>'NSM',
+917795=>'NSM',
+917796=>'NSM',
+917797=>'NSM',
+917798=>'NSM',
+917799=>'NSM',
+917800=>'NSM',
+917801=>'NSM',
+917802=>'NSM',
+917803=>'NSM',
+917804=>'NSM',
+917805=>'NSM',
+917806=>'NSM',
+917807=>'NSM',
+917808=>'NSM',
+917809=>'NSM',
+917810=>'NSM',
+917811=>'NSM',
+917812=>'NSM',
+917813=>'NSM',
+917814=>'NSM',
+917815=>'NSM',
+917816=>'NSM',
+917817=>'NSM',
+917818=>'NSM',
+917819=>'NSM',
+917820=>'NSM',
+917821=>'NSM',
+917822=>'NSM',
+917823=>'NSM',
+917824=>'NSM',
+917825=>'NSM',
+917826=>'NSM',
+917827=>'NSM',
+917828=>'NSM',
+917829=>'NSM',
+917830=>'NSM',
+917831=>'NSM',
+917832=>'NSM',
+917833=>'NSM',
+917834=>'NSM',
+917835=>'NSM',
+917836=>'NSM',
+917837=>'NSM',
+917838=>'NSM',
+917839=>'NSM',
+917840=>'NSM',
+917841=>'NSM',
+917842=>'NSM',
+917843=>'NSM',
+917844=>'NSM',
+917845=>'NSM',
+917846=>'NSM',
+917847=>'NSM',
+917848=>'NSM',
+917849=>'NSM',
+917850=>'NSM',
+917851=>'NSM',
+917852=>'NSM',
+917853=>'NSM',
+917854=>'NSM',
+917855=>'NSM',
+917856=>'NSM',
+917857=>'NSM',
+917858=>'NSM',
+917859=>'NSM',
+917860=>'NSM',
+917861=>'NSM',
+917862=>'NSM',
+917863=>'NSM',
+917864=>'NSM',
+917865=>'NSM',
+917866=>'NSM',
+917867=>'NSM',
+917868=>'NSM',
+917869=>'NSM',
+917870=>'NSM',
+917871=>'NSM',
+917872=>'NSM',
+917873=>'NSM',
+917874=>'NSM',
+917875=>'NSM',
+917876=>'NSM',
+917877=>'NSM',
+917878=>'NSM',
+917879=>'NSM',
+917880=>'NSM',
+917881=>'NSM',
+917882=>'NSM',
+917883=>'NSM',
+917884=>'NSM',
+917885=>'NSM',
+917886=>'NSM',
+917887=>'NSM',
+917888=>'NSM',
+917889=>'NSM',
+917890=>'NSM',
+917891=>'NSM',
+917892=>'NSM',
+917893=>'NSM',
+917894=>'NSM',
+917895=>'NSM',
+917896=>'NSM',
+917897=>'NSM',
+917898=>'NSM',
+917899=>'NSM',
+917900=>'NSM',
+917901=>'NSM',
+917902=>'NSM',
+917903=>'NSM',
+917904=>'NSM',
+917905=>'NSM',
+917906=>'NSM',
+917907=>'NSM',
+917908=>'NSM',
+917909=>'NSM',
+917910=>'NSM',
+917911=>'NSM',
+917912=>'NSM',
+917913=>'NSM',
+917914=>'NSM',
+917915=>'NSM',
+917916=>'NSM',
+917917=>'NSM',
+917918=>'NSM',
+917919=>'NSM',
+917920=>'NSM',
+917921=>'NSM',
+917922=>'NSM',
+917923=>'NSM',
+917924=>'NSM',
+917925=>'NSM',
+917926=>'NSM',
+917927=>'NSM',
+917928=>'NSM',
+917929=>'NSM',
+917930=>'NSM',
+917931=>'NSM',
+917932=>'NSM',
+917933=>'NSM',
+917934=>'NSM',
+917935=>'NSM',
+917936=>'NSM',
+917937=>'NSM',
+917938=>'NSM',
+917939=>'NSM',
+917940=>'NSM',
+917941=>'NSM',
+917942=>'NSM',
+917943=>'NSM',
+917944=>'NSM',
+917945=>'NSM',
+917946=>'NSM',
+917947=>'NSM',
+917948=>'NSM',
+917949=>'NSM',
+917950=>'NSM',
+917951=>'NSM',
+917952=>'NSM',
+917953=>'NSM',
+917954=>'NSM',
+917955=>'NSM',
+917956=>'NSM',
+917957=>'NSM',
+917958=>'NSM',
+917959=>'NSM',
+917960=>'NSM',
+917961=>'NSM',
+917962=>'NSM',
+917963=>'NSM',
+917964=>'NSM',
+917965=>'NSM',
+917966=>'NSM',
+917967=>'NSM',
+917968=>'NSM',
+917969=>'NSM',
+917970=>'NSM',
+917971=>'NSM',
+917972=>'NSM',
+917973=>'NSM',
+917974=>'NSM',
+917975=>'NSM',
+917976=>'NSM',
+917977=>'NSM',
+917978=>'NSM',
+917979=>'NSM',
+917980=>'NSM',
+917981=>'NSM',
+917982=>'NSM',
+917983=>'NSM',
+917984=>'NSM',
+917985=>'NSM',
+917986=>'NSM',
+917987=>'NSM',
+917988=>'NSM',
+917989=>'NSM',
+917990=>'NSM',
+917991=>'NSM',
+917992=>'NSM',
+917993=>'NSM',
+917994=>'NSM',
+917995=>'NSM',
+917996=>'NSM',
+917997=>'NSM',
+917998=>'NSM',
+917999=>'NSM',
+983040=>'L',
+1048573=>'L',
+1048576=>'L',
+1114109=>'L'
+);
+
+/**
+ * Mirror unicode characters. For information on bidi mirroring, see UAX #9: Bidirectional Algorithm, at http://www.unicode.org/unicode/reports/tr9/
+ * @public
+ */
+public static $uni_mirror = array (
+0x0028=>0x0029,
+0x0029=>0x0028,
+0x003C=>0x003E,
+0x003E=>0x003C,
+0x005B=>0x005D,
+0x005D=>0x005B,
+0x007B=>0x007D,
+0x007D=>0x007B,
+0x00AB=>0x00BB,
+0x00BB=>0x00AB,
+0x0F3A=>0x0F3B,
+0x0F3B=>0x0F3A,
+0x0F3C=>0x0F3D,
+0x0F3D=>0x0F3C,
+0x169B=>0x169C,
+0x169C=>0x169B,
+0x2018=>0x2019,
+0x2019=>0x2018,
+0x201C=>0x201D,
+0x201D=>0x201C,
+0x2039=>0x203A,
+0x203A=>0x2039,
+0x2045=>0x2046,
+0x2046=>0x2045,
+0x207D=>0x207E,
+0x207E=>0x207D,
+0x208D=>0x208E,
+0x208E=>0x208D,
+0x2208=>0x220B,
+0x2209=>0x220C,
+0x220A=>0x220D,
+0x220B=>0x2208,
+0x220C=>0x2209,
+0x220D=>0x220A,
+0x2215=>0x29F5,
+0x223C=>0x223D,
+0x223D=>0x223C,
+0x2243=>0x22CD,
+0x2252=>0x2253,
+0x2253=>0x2252,
+0x2254=>0x2255,
+0x2255=>0x2254,
+0x2264=>0x2265,
+0x2265=>0x2264,
+0x2266=>0x2267,
+0x2267=>0x2266,
+0x2268=>0x2269,
+0x2269=>0x2268,
+0x226A=>0x226B,
+0x226B=>0x226A,
+0x226E=>0x226F,
+0x226F=>0x226E,
+0x2270=>0x2271,
+0x2271=>0x2270,
+0x2272=>0x2273,
+0x2273=>0x2272,
+0x2274=>0x2275,
+0x2275=>0x2274,
+0x2276=>0x2277,
+0x2277=>0x2276,
+0x2278=>0x2279,
+0x2279=>0x2278,
+0x227A=>0x227B,
+0x227B=>0x227A,
+0x227C=>0x227D,
+0x227D=>0x227C,
+0x227E=>0x227F,
+0x227F=>0x227E,
+0x2280=>0x2281,
+0x2281=>0x2280,
+0x2282=>0x2283,
+0x2283=>0x2282,
+0x2284=>0x2285,
+0x2285=>0x2284,
+0x2286=>0x2287,
+0x2287=>0x2286,
+0x2288=>0x2289,
+0x2289=>0x2288,
+0x228A=>0x228B,
+0x228B=>0x228A,
+0x228F=>0x2290,
+0x2290=>0x228F,
+0x2291=>0x2292,
+0x2292=>0x2291,
+0x2298=>0x29B8,
+0x22A2=>0x22A3,
+0x22A3=>0x22A2,
+0x22A6=>0x2ADE,
+0x22A8=>0x2AE4,
+0x22A9=>0x2AE3,
+0x22AB=>0x2AE5,
+0x22B0=>0x22B1,
+0x22B1=>0x22B0,
+0x22B2=>0x22B3,
+0x22B3=>0x22B2,
+0x22B4=>0x22B5,
+0x22B5=>0x22B4,
+0x22B6=>0x22B7,
+0x22B7=>0x22B6,
+0x22C9=>0x22CA,
+0x22CA=>0x22C9,
+0x22CB=>0x22CC,
+0x22CC=>0x22CB,
+0x22CD=>0x2243,
+0x22D0=>0x22D1,
+0x22D1=>0x22D0,
+0x22D6=>0x22D7,
+0x22D7=>0x22D6,
+0x22D8=>0x22D9,
+0x22D9=>0x22D8,
+0x22DA=>0x22DB,
+0x22DB=>0x22DA,
+0x22DC=>0x22DD,
+0x22DD=>0x22DC,
+0x22DE=>0x22DF,
+0x22DF=>0x22DE,
+0x22E0=>0x22E1,
+0x22E1=>0x22E0,
+0x22E2=>0x22E3,
+0x22E3=>0x22E2,
+0x22E4=>0x22E5,
+0x22E5=>0x22E4,
+0x22E6=>0x22E7,
+0x22E7=>0x22E6,
+0x22E8=>0x22E9,
+0x22E9=>0x22E8,
+0x22EA=>0x22EB,
+0x22EB=>0x22EA,
+0x22EC=>0x22ED,
+0x22ED=>0x22EC,
+0x22F0=>0x22F1,
+0x22F1=>0x22F0,
+0x22F2=>0x22FA,
+0x22F3=>0x22FB,
+0x22F4=>0x22FC,
+0x22F6=>0x22FD,
+0x22F7=>0x22FE,
+0x22FA=>0x22F2,
+0x22FB=>0x22F3,
+0x22FC=>0x22F4,
+0x22FD=>0x22F6,
+0x22FE=>0x22F7,
+0x2308=>0x2309,
+0x2309=>0x2308,
+0x230A=>0x230B,
+0x230B=>0x230A,
+0x2329=>0x232A,
+0x232A=>0x2329,
+0x2768=>0x2769,
+0x2769=>0x2768,
+0x276A=>0x276B,
+0x276B=>0x276A,
+0x276C=>0x276D,
+0x276D=>0x276C,
+0x276E=>0x276F,
+0x276F=>0x276E,
+0x2770=>0x2771,
+0x2771=>0x2770,
+0x2772=>0x2773,
+0x2773=>0x2772,
+0x2774=>0x2775,
+0x2775=>0x2774,
+0x27C3=>0x27C4,
+0x27C4=>0x27C3,
+0x27C5=>0x27C6,
+0x27C6=>0x27C5,
+0x27D5=>0x27D6,
+0x27D6=>0x27D5,
+0x27DD=>0x27DE,
+0x27DE=>0x27DD,
+0x27E2=>0x27E3,
+0x27E3=>0x27E2,
+0x27E4=>0x27E5,
+0x27E5=>0x27E4,
+0x27E6=>0x27E7,
+0x27E7=>0x27E6,
+0x27E8=>0x27E9,
+0x27E9=>0x27E8,
+0x27EA=>0x27EB,
+0x27EB=>0x27EA,
+0x2983=>0x2984,
+0x2984=>0x2983,
+0x2985=>0x2986,
+0x2986=>0x2985,
+0x2987=>0x2988,
+0x2988=>0x2987,
+0x2989=>0x298A,
+0x298A=>0x2989,
+0x298B=>0x298C,
+0x298C=>0x298B,
+0x298D=>0x2990,
+0x298E=>0x298F,
+0x298F=>0x298E,
+0x2990=>0x298D,
+0x2991=>0x2992,
+0x2992=>0x2991,
+0x2993=>0x2994,
+0x2994=>0x2993,
+0x2995=>0x2996,
+0x2996=>0x2995,
+0x2997=>0x2998,
+0x2998=>0x2997,
+0x29B8=>0x2298,
+0x29C0=>0x29C1,
+0x29C1=>0x29C0,
+0x29C4=>0x29C5,
+0x29C5=>0x29C4,
+0x29CF=>0x29D0,
+0x29D0=>0x29CF,
+0x29D1=>0x29D2,
+0x29D2=>0x29D1,
+0x29D4=>0x29D5,
+0x29D5=>0x29D4,
+0x29D8=>0x29D9,
+0x29D9=>0x29D8,
+0x29DA=>0x29DB,
+0x29DB=>0x29DA,
+0x29F5=>0x2215,
+0x29F8=>0x29F9,
+0x29F9=>0x29F8,
+0x29FC=>0x29FD,
+0x29FD=>0x29FC,
+0x2A2B=>0x2A2C,
+0x2A2C=>0x2A2B,
+0x2A2D=>0x2A2E,
+0x2A2E=>0x2A2D,
+0x2A34=>0x2A35,
+0x2A35=>0x2A34,
+0x2A3C=>0x2A3D,
+0x2A3D=>0x2A3C,
+0x2A64=>0x2A65,
+0x2A65=>0x2A64,
+0x2A79=>0x2A7A,
+0x2A7A=>0x2A79,
+0x2A7D=>0x2A7E,
+0x2A7E=>0x2A7D,
+0x2A7F=>0x2A80,
+0x2A80=>0x2A7F,
+0x2A81=>0x2A82,
+0x2A82=>0x2A81,
+0x2A83=>0x2A84,
+0x2A84=>0x2A83,
+0x2A8B=>0x2A8C,
+0x2A8C=>0x2A8B,
+0x2A91=>0x2A92,
+0x2A92=>0x2A91,
+0x2A93=>0x2A94,
+0x2A94=>0x2A93,
+0x2A95=>0x2A96,
+0x2A96=>0x2A95,
+0x2A97=>0x2A98,
+0x2A98=>0x2A97,
+0x2A99=>0x2A9A,
+0x2A9A=>0x2A99,
+0x2A9B=>0x2A9C,
+0x2A9C=>0x2A9B,
+0x2AA1=>0x2AA2,
+0x2AA2=>0x2AA1,
+0x2AA6=>0x2AA7,
+0x2AA7=>0x2AA6,
+0x2AA8=>0x2AA9,
+0x2AA9=>0x2AA8,
+0x2AAA=>0x2AAB,
+0x2AAB=>0x2AAA,
+0x2AAC=>0x2AAD,
+0x2AAD=>0x2AAC,
+0x2AAF=>0x2AB0,
+0x2AB0=>0x2AAF,
+0x2AB3=>0x2AB4,
+0x2AB4=>0x2AB3,
+0x2ABB=>0x2ABC,
+0x2ABC=>0x2ABB,
+0x2ABD=>0x2ABE,
+0x2ABE=>0x2ABD,
+0x2ABF=>0x2AC0,
+0x2AC0=>0x2ABF,
+0x2AC1=>0x2AC2,
+0x2AC2=>0x2AC1,
+0x2AC3=>0x2AC4,
+0x2AC4=>0x2AC3,
+0x2AC5=>0x2AC6,
+0x2AC6=>0x2AC5,
+0x2ACD=>0x2ACE,
+0x2ACE=>0x2ACD,
+0x2ACF=>0x2AD0,
+0x2AD0=>0x2ACF,
+0x2AD1=>0x2AD2,
+0x2AD2=>0x2AD1,
+0x2AD3=>0x2AD4,
+0x2AD4=>0x2AD3,
+0x2AD5=>0x2AD6,
+0x2AD6=>0x2AD5,
+0x2ADE=>0x22A6,
+0x2AE3=>0x22A9,
+0x2AE4=>0x22A8,
+0x2AE5=>0x22AB,
+0x2AEC=>0x2AED,
+0x2AED=>0x2AEC,
+0x2AF7=>0x2AF8,
+0x2AF8=>0x2AF7,
+0x2AF9=>0x2AFA,
+0x2AFA=>0x2AF9,
+0x2E02=>0x2E03,
+0x2E03=>0x2E02,
+0x2E04=>0x2E05,
+0x2E05=>0x2E04,
+0x2E09=>0x2E0A,
+0x2E0A=>0x2E09,
+0x2E0C=>0x2E0D,
+0x2E0D=>0x2E0C,
+0x2E1C=>0x2E1D,
+0x2E1D=>0x2E1C,
+0x3008=>0x3009,
+0x3009=>0x3008,
+0x300A=>0x300B,
+0x300B=>0x300A,
+0x300C=>0x300D,
+0x300D=>0x300C,
+0x300E=>0x300F,
+0x300F=>0x300E,
+0x3010=>0x3011,
+0x3011=>0x3010,
+0x3014=>0x3015,
+0x3015=>0x3014,
+0x3016=>0x3017,
+0x3017=>0x3016,
+0x3018=>0x3019,
+0x3019=>0x3018,
+0x301A=>0x301B,
+0x301B=>0x301A,
+0x301D=>0x301E,
+0x301E=>0x301D,
+0xFE59=>0xFE5A,
+0xFE5A=>0xFE59,
+0xFE5B=>0xFE5C,
+0xFE5C=>0xFE5B,
+0xFE5D=>0xFE5E,
+0xFE5E=>0xFE5D,
+0xFE64=>0xFE65,
+0xFE65=>0xFE64,
+0xFF08=>0xFF09,
+0xFF09=>0xFF08,
+0xFF1C=>0xFF1E,
+0xFF1E=>0xFF1C,
+0xFF3B=>0xFF3D,
+0xFF3D=>0xFF3B,
+0xFF5B=>0xFF5D,
+0xFF5D=>0xFF5B,
+0xFF5F=>0xFF60,
+0xFF60=>0xFF5F,
+0xFF62=>0xFF63,
+0xFF63=>0xFF62);
+
+/**
+ * Arabic shape substitutions: char code => (isolated, final, initial, medial).
+ * @public
+ */
+public static $uni_arabicsubst = array(
+1569=>array(65152),
+1570=>array(65153, 65154, 65153, 65154),
+1571=>array(65155, 65156, 65155, 65156),
+1572=>array(65157, 65158),
+1573=>array(65159, 65160, 65159, 65160),
+1574=>array(65161, 65162, 65163, 65164),
+1575=>array(65165, 65166, 65165, 65166),
+1576=>array(65167, 65168, 65169, 65170),
+1577=>array(65171, 65172),
+1578=>array(65173, 65174, 65175, 65176),
+1579=>array(65177, 65178, 65179, 65180),
+1580=>array(65181, 65182, 65183, 65184),
+1581=>array(65185, 65186, 65187, 65188),
+1582=>array(65189, 65190, 65191, 65192),
+1583=>array(65193, 65194, 65193, 65194),
+1584=>array(65195, 65196, 65195, 65196),
+1585=>array(65197, 65198, 65197, 65198),
+1586=>array(65199, 65200, 65199, 65200),
+1587=>array(65201, 65202, 65203, 65204),
+1588=>array(65205, 65206, 65207, 65208),
+1589=>array(65209, 65210, 65211, 65212),
+1590=>array(65213, 65214, 65215, 65216),
+1591=>array(65217, 65218, 65219, 65220),
+1592=>array(65221, 65222, 65223, 65224),
+1593=>array(65225, 65226, 65227, 65228),
+1594=>array(65229, 65230, 65231, 65232),
+1601=>array(65233, 65234, 65235, 65236),
+1602=>array(65237, 65238, 65239, 65240),
+1603=>array(65241, 65242, 65243, 65244),
+1604=>array(65245, 65246, 65247, 65248),
+1605=>array(65249, 65250, 65251, 65252),
+1606=>array(65253, 65254, 65255, 65256),
+1607=>array(65257, 65258, 65259, 65260),
+1608=>array(65261, 65262, 65261, 65262),
+1609=>array(65263, 65264, 64488, 64489),
+1610=>array(65265, 65266, 65267, 65268),
+1649=>array(64336, 64337),
+1655=>array(64477),
+1657=>array(64358, 64359, 64360, 64361),
+1658=>array(64350, 64351, 64352, 64353),
+1659=>array(64338, 64339, 64340, 64341),
+1662=>array(64342, 64343, 64344, 64345),
+1663=>array(64354, 64355, 64356, 64357),
+1664=>array(64346, 64347, 64348, 64349),
+1667=>array(64374, 64375, 64376, 64377),
+1668=>array(64370, 64371, 64372, 64373),
+1670=>array(64378, 64379, 64380, 64381),
+1671=>array(64382, 64383, 64384, 64385),
+1672=>array(64392, 64393),
+1676=>array(64388, 64389),
+1677=>array(64386, 64387),
+1678=>array(64390, 64391),
+1681=>array(64396, 64397),
+1688=>array(64394, 64395, 64394, 64395),
+1700=>array(64362, 64363, 64364, 64365),
+1702=>array(64366, 64367, 64368, 64369),
+1705=>array(64398, 64399, 64400, 64401),
+1709=>array(64467, 64468, 64469, 64470),
+1711=>array(64402, 64403, 64404, 64405),
+1713=>array(64410, 64411, 64412, 64413),
+1715=>array(64406, 64407, 64408, 64409),
+1722=>array(64414, 64415),
+1723=>array(64416, 64417, 64418, 64419),
+1726=>array(64426, 64427, 64428, 64429),
+1728=>array(64420, 64421),
+1729=>array(64422, 64423, 64424, 64425),
+1733=>array(64480, 64481),
+1734=>array(64473, 64474),
+1735=>array(64471, 64472),
+1736=>array(64475, 64476),
+1737=>array(64482, 64483),
+1739=>array(64478, 64479),
+1740=>array(64508, 64509, 64510, 64511),
+1744=>array(64484, 64485, 64486, 64487),
+1746=>array(64430, 64431),
+1747=>array(64432, 64433)
+);
+
+/**
+ * Arabic laa letter: (char code => isolated, final, initial, medial).
+ * @public
+ */
+public static $uni_laa_array = array (
+1570 =>array(65269, 65270, 65269, 65270),
+1571 =>array(65271, 65272, 65271, 65272),
+1573 =>array(65273, 65274, 65273, 65274),
+1575 =>array(65275, 65276, 65275, 65276)
+);
+
+/**
+ * Array of character substitutions for sequences of two diacritics symbols.
+ * Putting the combining mark and character in the same glyph allows us to avoid the two marks overlapping each other in an illegible manner.
+ * second NSM char code => substitution char
+ * @public
+ */
+public static $uni_diacritics = array (
+1612=>64606, # Shadda + Dammatan
+1613=>64607, # Shadda + Kasratan
+1614=>64608, # Shadda + Fatha
+1615=>64609, # Shadda + Damma
+1616=>64610 # Shadda + Kasra
+);
+
+/**
+ * Array of character substitutions from UTF-8 Unicode to Latin1.
+ * @public
+ */
+public static $uni_utf8tolatin = array (
+8364=>128, # Euro1
+338=>140, # OE
+352=>138, # Scaron
+376=>159, # Ydieresis
+381=>142, # Zcaron2
+8226=>149, # bullet3
+710=>136, # circumflex
+8224=>134, # dagger
+8225=>135, # daggerdbl
+8230=>133, # ellipsis
+8212=>151, # emdash
+8211=>150, # endash
+402=>131, # florin
+8249=>139, # guilsinglleft
+8250=>155, # guilsinglright
+339=>156, # oe
+8240=>137, # perthousand
+8222=>132, # quotedblbase
+8220=>147, # quotedblleft
+8221=>148, # quotedblright
+8216=>145, # quoteleft
+8217=>146, # quoteright
+8218=>130, # quotesinglbase
+353=>154, # scaron
+732=>152, # tilde
+8482=>153, # trademark
+382=>158 # zcaron2
+);
+
+/**
+ * Array of Encoding Maps.
+ * @public static
+ */
+public static $encmap = array(
+
+// encoding map for: cp874
+'cp874' => array(0=>'.notdef',1=>'.notdef',2=>'.notdef',3=>'.notdef',4=>'.notdef',5=>'.notdef',6=>'.notdef',7=>'.notdef',8=>'.notdef',9=>'.notdef',10=>'.notdef',11=>'.notdef',12=>'.notdef',13=>'.notdef',14=>'.notdef',15=>'.notdef',16=>'.notdef',17=>'.notdef',18=>'.notdef',19=>'.notdef',20=>'.notdef',21=>'.notdef',22=>'.notdef',23=>'.notdef',24=>'.notdef',25=>'.notdef',26=>'.notdef',27=>'.notdef',28=>'.notdef',29=>'.notdef',30=>'.notdef',31=>'.notdef',32=>'space',33=>'exclam',34=>'quotedbl',35=>'numbersign',36=>'dollar',37=>'percent',38=>'ampersand',39=>'quotesingle',40=>'parenleft',41=>'parenright',42=>'asterisk',43=>'plus',44=>'comma',45=>'hyphen',46=>'period',47=>'slash',48=>'zero',49=>'one',50=>'two',51=>'three',52=>'four',53=>'five',54=>'six',55=>'seven',56=>'eight',57=>'nine',58=>'colon',59=>'semicolon',60=>'less',61=>'equal',62=>'greater',63=>'question',64=>'at',65=>'A',66=>'B',67=>'C',68=>'D',69=>'E',70=>'F',71=>'G',72=>'H',73=>'I',74=>'J',75=>'K',76=>'L',77=>'M',78=>'N',79=>'O',80=>'P',81=>'Q',82=>'R',83=>'S',84=>'T',85=>'U',86=>'V',87=>'W',88=>'X',89=>'Y',90=>'Z',91=>'bracketleft',92=>'backslash',93=>'bracketright',94=>'asciicircum',95=>'underscore',96=>'grave',97=>'a',98=>'b',99=>'c',100=>'d',101=>'e',102=>'f',103=>'g',104=>'h',105=>'i',106=>'j',107=>'k',108=>'l',109=>'m',110=>'n',111=>'o',112=>'p',113=>'q',114=>'r',115=>'s',116=>'t',117=>'u',118=>'v',119=>'w',120=>'x',121=>'y',122=>'z',123=>'braceleft',124=>'bar',125=>'braceright',126=>'asciitilde',127=>'.notdef',128=>'Euro',129=>'.notdef',130=>'.notdef',131=>'.notdef',132=>'.notdef',133=>'ellipsis',134=>'.notdef',135=>'.notdef',136=>'.notdef',137=>'.notdef',138=>'.notdef',139=>'.notdef',140=>'.notdef',141=>'.notdef',142=>'.notdef',143=>'.notdef',144=>'.notdef',145=>'quoteleft',146=>'quoteright',147=>'quotedblleft',148=>'quotedblright',149=>'bullet',150=>'endash',151=>'emdash',152=>'.notdef',153=>'.notdef',154=>'.notdef',155=>'.notdef',156=>'.notdef',157=>'.notdef',158=>'.notdef',159=>'.notdef',160=>'space',161=>'kokaithai',162=>'khokhaithai',163=>'khokhuatthai',164=>'khokhwaithai',165=>'khokhonthai',166=>'khorakhangthai',167=>'ngonguthai',168=>'chochanthai',169=>'chochingthai',170=>'chochangthai',171=>'sosothai',172=>'chochoethai',173=>'yoyingthai',174=>'dochadathai',175=>'topatakthai',176=>'thothanthai',177=>'thonangmonthothai',178=>'thophuthaothai',179=>'nonenthai',180=>'dodekthai',181=>'totaothai',182=>'thothungthai',183=>'thothahanthai',184=>'thothongthai',185=>'nonuthai',186=>'bobaimaithai',187=>'poplathai',188=>'phophungthai',189=>'fofathai',190=>'phophanthai',191=>'fofanthai',192=>'phosamphaothai',193=>'momathai',194=>'yoyakthai',195=>'roruathai',196=>'ruthai',197=>'lolingthai',198=>'luthai',199=>'wowaenthai',200=>'sosalathai',201=>'sorusithai',202=>'sosuathai',203=>'hohipthai',204=>'lochulathai',205=>'oangthai',206=>'honokhukthai',207=>'paiyannoithai',208=>'saraathai',209=>'maihanakatthai',210=>'saraaathai',211=>'saraamthai',212=>'saraithai',213=>'saraiithai',214=>'sarauethai',215=>'saraueethai',216=>'sarauthai',217=>'sarauuthai',218=>'phinthuthai',219=>'.notdef',220=>'.notdef',221=>'.notdef',222=>'.notdef',223=>'bahtthai',224=>'saraethai',225=>'saraaethai',226=>'saraothai',227=>'saraaimaimuanthai',228=>'saraaimaimalaithai',229=>'lakkhangyaothai',230=>'maiyamokthai',231=>'maitaikhuthai',232=>'maiekthai',233=>'maithothai',234=>'maitrithai',235=>'maichattawathai',236=>'thanthakhatthai',237=>'nikhahitthai',238=>'yamakkanthai',239=>'fongmanthai',240=>'zerothai',241=>'onethai',242=>'twothai',243=>'threethai',244=>'fourthai',245=>'fivethai',246=>'sixthai',247=>'seventhai',248=>'eightthai',249=>'ninethai',250=>'angkhankhuthai',251=>'khomutthai',252=>'.notdef',253=>'.notdef',254=>'.notdef',255=>'.notdef'),
+
+// encoding map for: cp1250
+'cp1250' => array(0=>'.notdef',1=>'.notdef',2=>'.notdef',3=>'.notdef',4=>'.notdef',5=>'.notdef',6=>'.notdef',7=>'.notdef',8=>'.notdef',9=>'.notdef',10=>'.notdef',11=>'.notdef',12=>'.notdef',13=>'.notdef',14=>'.notdef',15=>'.notdef',16=>'.notdef',17=>'.notdef',18=>'.notdef',19=>'.notdef',20=>'.notdef',21=>'.notdef',22=>'.notdef',23=>'.notdef',24=>'.notdef',25=>'.notdef',26=>'.notdef',27=>'.notdef',28=>'.notdef',29=>'.notdef',30=>'.notdef',31=>'.notdef',32=>'space',33=>'exclam',34=>'quotedbl',35=>'numbersign',36=>'dollar',37=>'percent',38=>'ampersand',39=>'quotesingle',40=>'parenleft',41=>'parenright',42=>'asterisk',43=>'plus',44=>'comma',45=>'hyphen',46=>'period',47=>'slash',48=>'zero',49=>'one',50=>'two',51=>'three',52=>'four',53=>'five',54=>'six',55=>'seven',56=>'eight',57=>'nine',58=>'colon',59=>'semicolon',60=>'less',61=>'equal',62=>'greater',63=>'question',64=>'at',65=>'A',66=>'B',67=>'C',68=>'D',69=>'E',70=>'F',71=>'G',72=>'H',73=>'I',74=>'J',75=>'K',76=>'L',77=>'M',78=>'N',79=>'O',80=>'P',81=>'Q',82=>'R',83=>'S',84=>'T',85=>'U',86=>'V',87=>'W',88=>'X',89=>'Y',90=>'Z',91=>'bracketleft',92=>'backslash',93=>'bracketright',94=>'asciicircum',95=>'underscore',96=>'grave',97=>'a',98=>'b',99=>'c',100=>'d',101=>'e',102=>'f',103=>'g',104=>'h',105=>'i',106=>'j',107=>'k',108=>'l',109=>'m',110=>'n',111=>'o',112=>'p',113=>'q',114=>'r',115=>'s',116=>'t',117=>'u',118=>'v',119=>'w',120=>'x',121=>'y',122=>'z',123=>'braceleft',124=>'bar',125=>'braceright',126=>'asciitilde',127=>'.notdef',128=>'Euro',129=>'.notdef',130=>'quotesinglbase',131=>'.notdef',132=>'quotedblbase',133=>'ellipsis',134=>'dagger',135=>'daggerdbl',136=>'.notdef',137=>'perthousand',138=>'Scaron',139=>'guilsinglleft',140=>'Sacute',141=>'Tcaron',142=>'Zcaron',143=>'Zacute',144=>'.notdef',145=>'quoteleft',146=>'quoteright',147=>'quotedblleft',148=>'quotedblright',149=>'bullet',150=>'endash',151=>'emdash',152=>'.notdef',153=>'trademark',154=>'scaron',155=>'guilsinglright',156=>'sacute',157=>'tcaron',158=>'zcaron',159=>'zacute',160=>'space',161=>'caron',162=>'breve',163=>'Lslash',164=>'currency',165=>'Aogonek',166=>'brokenbar',167=>'section',168=>'dieresis',169=>'copyright',170=>'Scedilla',171=>'guillemotleft',172=>'logicalnot',173=>'hyphen',174=>'registered',175=>'Zdotaccent',176=>'degree',177=>'plusminus',178=>'ogonek',179=>'lslash',180=>'acute',181=>'mu',182=>'paragraph',183=>'periodcentered',184=>'cedilla',185=>'aogonek',186=>'scedilla',187=>'guillemotright',188=>'Lcaron',189=>'hungarumlaut',190=>'lcaron',191=>'zdotaccent',192=>'Racute',193=>'Aacute',194=>'Acircumflex',195=>'Abreve',196=>'Adieresis',197=>'Lacute',198=>'Cacute',199=>'Ccedilla',200=>'Ccaron',201=>'Eacute',202=>'Eogonek',203=>'Edieresis',204=>'Ecaron',205=>'Iacute',206=>'Icircumflex',207=>'Dcaron',208=>'Dcroat',209=>'Nacute',210=>'Ncaron',211=>'Oacute',212=>'Ocircumflex',213=>'Ohungarumlaut',214=>'Odieresis',215=>'multiply',216=>'Rcaron',217=>'Uring',218=>'Uacute',219=>'Uhungarumlaut',220=>'Udieresis',221=>'Yacute',222=>'Tcommaaccent',223=>'germandbls',224=>'racute',225=>'aacute',226=>'acircumflex',227=>'abreve',228=>'adieresis',229=>'lacute',230=>'cacute',231=>'ccedilla',232=>'ccaron',233=>'eacute',234=>'eogonek',235=>'edieresis',236=>'ecaron',237=>'iacute',238=>'icircumflex',239=>'dcaron',240=>'dcroat',241=>'nacute',242=>'ncaron',243=>'oacute',244=>'ocircumflex',245=>'ohungarumlaut',246=>'odieresis',247=>'divide',248=>'rcaron',249=>'uring',250=>'uacute',251=>'uhungarumlaut',252=>'udieresis',253=>'yacute',254=>'tcommaaccent',255=>'dotaccent'),
+
+// encoding map for: cp1251
+'cp1251' => array(0=>'.notdef',1=>'.notdef',2=>'.notdef',3=>'.notdef',4=>'.notdef',5=>'.notdef',6=>'.notdef',7=>'.notdef',8=>'.notdef',9=>'.notdef',10=>'.notdef',11=>'.notdef',12=>'.notdef',13=>'.notdef',14=>'.notdef',15=>'.notdef',16=>'.notdef',17=>'.notdef',18=>'.notdef',19=>'.notdef',20=>'.notdef',21=>'.notdef',22=>'.notdef',23=>'.notdef',24=>'.notdef',25=>'.notdef',26=>'.notdef',27=>'.notdef',28=>'.notdef',29=>'.notdef',30=>'.notdef',31=>'.notdef',32=>'space',33=>'exclam',34=>'quotedbl',35=>'numbersign',36=>'dollar',37=>'percent',38=>'ampersand',39=>'quotesingle',40=>'parenleft',41=>'parenright',42=>'asterisk',43=>'plus',44=>'comma',45=>'hyphen',46=>'period',47=>'slash',48=>'zero',49=>'one',50=>'two',51=>'three',52=>'four',53=>'five',54=>'six',55=>'seven',56=>'eight',57=>'nine',58=>'colon',59=>'semicolon',60=>'less',61=>'equal',62=>'greater',63=>'question',64=>'at',65=>'A',66=>'B',67=>'C',68=>'D',69=>'E',70=>'F',71=>'G',72=>'H',73=>'I',74=>'J',75=>'K',76=>'L',77=>'M',78=>'N',79=>'O',80=>'P',81=>'Q',82=>'R',83=>'S',84=>'T',85=>'U',86=>'V',87=>'W',88=>'X',89=>'Y',90=>'Z',91=>'bracketleft',92=>'backslash',93=>'bracketright',94=>'asciicircum',95=>'underscore',96=>'grave',97=>'a',98=>'b',99=>'c',100=>'d',101=>'e',102=>'f',103=>'g',104=>'h',105=>'i',106=>'j',107=>'k',108=>'l',109=>'m',110=>'n',111=>'o',112=>'p',113=>'q',114=>'r',115=>'s',116=>'t',117=>'u',118=>'v',119=>'w',120=>'x',121=>'y',122=>'z',123=>'braceleft',124=>'bar',125=>'braceright',126=>'asciitilde',127=>'.notdef',128=>'afii10051',129=>'afii10052',130=>'quotesinglbase',131=>'afii10100',132=>'quotedblbase',133=>'ellipsis',134=>'dagger',135=>'daggerdbl',136=>'Euro',137=>'perthousand',138=>'afii10058',139=>'guilsinglleft',140=>'afii10059',141=>'afii10061',142=>'afii10060',143=>'afii10145',144=>'afii10099',145=>'quoteleft',146=>'quoteright',147=>'quotedblleft',148=>'quotedblright',149=>'bullet',150=>'endash',151=>'emdash',152=>'.notdef',153=>'trademark',154=>'afii10106',155=>'guilsinglright',156=>'afii10107',157=>'afii10109',158=>'afii10108',159=>'afii10193',160=>'space',161=>'afii10062',162=>'afii10110',163=>'afii10057',164=>'currency',165=>'afii10050',166=>'brokenbar',167=>'section',168=>'afii10023',169=>'copyright',170=>'afii10053',171=>'guillemotleft',172=>'logicalnot',173=>'hyphen',174=>'registered',175=>'afii10056',176=>'degree',177=>'plusminus',178=>'afii10055',179=>'afii10103',180=>'afii10098',181=>'mu',182=>'paragraph',183=>'periodcentered',184=>'afii10071',185=>'afii61352',186=>'afii10101',187=>'guillemotright',188=>'afii10105',189=>'afii10054',190=>'afii10102',191=>'afii10104',192=>'afii10017',193=>'afii10018',194=>'afii10019',195=>'afii10020',196=>'afii10021',197=>'afii10022',198=>'afii10024',199=>'afii10025',200=>'afii10026',201=>'afii10027',202=>'afii10028',203=>'afii10029',204=>'afii10030',205=>'afii10031',206=>'afii10032',207=>'afii10033',208=>'afii10034',209=>'afii10035',210=>'afii10036',211=>'afii10037',212=>'afii10038',213=>'afii10039',214=>'afii10040',215=>'afii10041',216=>'afii10042',217=>'afii10043',218=>'afii10044',219=>'afii10045',220=>'afii10046',221=>'afii10047',222=>'afii10048',223=>'afii10049',224=>'afii10065',225=>'afii10066',226=>'afii10067',227=>'afii10068',228=>'afii10069',229=>'afii10070',230=>'afii10072',231=>'afii10073',232=>'afii10074',233=>'afii10075',234=>'afii10076',235=>'afii10077',236=>'afii10078',237=>'afii10079',238=>'afii10080',239=>'afii10081',240=>'afii10082',241=>'afii10083',242=>'afii10084',243=>'afii10085',244=>'afii10086',245=>'afii10087',246=>'afii10088',247=>'afii10089',248=>'afii10090',249=>'afii10091',250=>'afii10092',251=>'afii10093',252=>'afii10094',253=>'afii10095',254=>'afii10096',255=>'afii10097'),
+
+// encoding map for: cp1252
+'cp1252' => array(0=>'.notdef',1=>'.notdef',2=>'.notdef',3=>'.notdef',4=>'.notdef',5=>'.notdef',6=>'.notdef',7=>'.notdef',8=>'.notdef',9=>'.notdef',10=>'.notdef',11=>'.notdef',12=>'.notdef',13=>'.notdef',14=>'.notdef',15=>'.notdef',16=>'.notdef',17=>'.notdef',18=>'.notdef',19=>'.notdef',20=>'.notdef',21=>'.notdef',22=>'.notdef',23=>'.notdef',24=>'.notdef',25=>'.notdef',26=>'.notdef',27=>'.notdef',28=>'.notdef',29=>'.notdef',30=>'.notdef',31=>'.notdef',32=>'space',33=>'exclam',34=>'quotedbl',35=>'numbersign',36=>'dollar',37=>'percent',38=>'ampersand',39=>'quotesingle',40=>'parenleft',41=>'parenright',42=>'asterisk',43=>'plus',44=>'comma',45=>'hyphen',46=>'period',47=>'slash',48=>'zero',49=>'one',50=>'two',51=>'three',52=>'four',53=>'five',54=>'six',55=>'seven',56=>'eight',57=>'nine',58=>'colon',59=>'semicolon',60=>'less',61=>'equal',62=>'greater',63=>'question',64=>'at',65=>'A',66=>'B',67=>'C',68=>'D',69=>'E',70=>'F',71=>'G',72=>'H',73=>'I',74=>'J',75=>'K',76=>'L',77=>'M',78=>'N',79=>'O',80=>'P',81=>'Q',82=>'R',83=>'S',84=>'T',85=>'U',86=>'V',87=>'W',88=>'X',89=>'Y',90=>'Z',91=>'bracketleft',92=>'backslash',93=>'bracketright',94=>'asciicircum',95=>'underscore',96=>'grave',97=>'a',98=>'b',99=>'c',100=>'d',101=>'e',102=>'f',103=>'g',104=>'h',105=>'i',106=>'j',107=>'k',108=>'l',109=>'m',110=>'n',111=>'o',112=>'p',113=>'q',114=>'r',115=>'s',116=>'t',117=>'u',118=>'v',119=>'w',120=>'x',121=>'y',122=>'z',123=>'braceleft',124=>'bar',125=>'braceright',126=>'asciitilde',127=>'.notdef',128=>'Euro',129=>'.notdef',130=>'quotesinglbase',131=>'florin',132=>'quotedblbase',133=>'ellipsis',134=>'dagger',135=>'daggerdbl',136=>'circumflex',137=>'perthousand',138=>'Scaron',139=>'guilsinglleft',140=>'OE',141=>'.notdef',142=>'Zcaron',143=>'.notdef',144=>'.notdef',145=>'quoteleft',146=>'quoteright',147=>'quotedblleft',148=>'quotedblright',149=>'bullet',150=>'endash',151=>'emdash',152=>'tilde',153=>'trademark',154=>'scaron',155=>'guilsinglright',156=>'oe',157=>'.notdef',158=>'zcaron',159=>'Ydieresis',160=>'space',161=>'exclamdown',162=>'cent',163=>'sterling',164=>'currency',165=>'yen',166=>'brokenbar',167=>'section',168=>'dieresis',169=>'copyright',170=>'ordfeminine',171=>'guillemotleft',172=>'logicalnot',173=>'hyphen',174=>'registered',175=>'macron',176=>'degree',177=>'plusminus',178=>'twosuperior',179=>'threesuperior',180=>'acute',181=>'mu',182=>'paragraph',183=>'periodcentered',184=>'cedilla',185=>'onesuperior',186=>'ordmasculine',187=>'guillemotright',188=>'onequarter',189=>'onehalf',190=>'threequarters',191=>'questiondown',192=>'Agrave',193=>'Aacute',194=>'Acircumflex',195=>'Atilde',196=>'Adieresis',197=>'Aring',198=>'AE',199=>'Ccedilla',200=>'Egrave',201=>'Eacute',202=>'Ecircumflex',203=>'Edieresis',204=>'Igrave',205=>'Iacute',206=>'Icircumflex',207=>'Idieresis',208=>'Eth',209=>'Ntilde',210=>'Ograve',211=>'Oacute',212=>'Ocircumflex',213=>'Otilde',214=>'Odieresis',215=>'multiply',216=>'Oslash',217=>'Ugrave',218=>'Uacute',219=>'Ucircumflex',220=>'Udieresis',221=>'Yacute',222=>'Thorn',223=>'germandbls',224=>'agrave',225=>'aacute',226=>'acircumflex',227=>'atilde',228=>'adieresis',229=>'aring',230=>'ae',231=>'ccedilla',232=>'egrave',233=>'eacute',234=>'ecircumflex',235=>'edieresis',236=>'igrave',237=>'iacute',238=>'icircumflex',239=>'idieresis',240=>'eth',241=>'ntilde',242=>'ograve',243=>'oacute',244=>'ocircumflex',245=>'otilde',246=>'odieresis',247=>'divide',248=>'oslash',249=>'ugrave',250=>'uacute',251=>'ucircumflex',252=>'udieresis',253=>'yacute',254=>'thorn',255=>'ydieresis'),
+
+// encoding map for: cp1253
+'cp1253' => array(0=>'.notdef',1=>'.notdef',2=>'.notdef',3=>'.notdef',4=>'.notdef',5=>'.notdef',6=>'.notdef',7=>'.notdef',8=>'.notdef',9=>'.notdef',10=>'.notdef',11=>'.notdef',12=>'.notdef',13=>'.notdef',14=>'.notdef',15=>'.notdef',16=>'.notdef',17=>'.notdef',18=>'.notdef',19=>'.notdef',20=>'.notdef',21=>'.notdef',22=>'.notdef',23=>'.notdef',24=>'.notdef',25=>'.notdef',26=>'.notdef',27=>'.notdef',28=>'.notdef',29=>'.notdef',30=>'.notdef',31=>'.notdef',32=>'space',33=>'exclam',34=>'quotedbl',35=>'numbersign',36=>'dollar',37=>'percent',38=>'ampersand',39=>'quotesingle',40=>'parenleft',41=>'parenright',42=>'asterisk',43=>'plus',44=>'comma',45=>'hyphen',46=>'period',47=>'slash',48=>'zero',49=>'one',50=>'two',51=>'three',52=>'four',53=>'five',54=>'six',55=>'seven',56=>'eight',57=>'nine',58=>'colon',59=>'semicolon',60=>'less',61=>'equal',62=>'greater',63=>'question',64=>'at',65=>'A',66=>'B',67=>'C',68=>'D',69=>'E',70=>'F',71=>'G',72=>'H',73=>'I',74=>'J',75=>'K',76=>'L',77=>'M',78=>'N',79=>'O',80=>'P',81=>'Q',82=>'R',83=>'S',84=>'T',85=>'U',86=>'V',87=>'W',88=>'X',89=>'Y',90=>'Z',91=>'bracketleft',92=>'backslash',93=>'bracketright',94=>'asciicircum',95=>'underscore',96=>'grave',97=>'a',98=>'b',99=>'c',100=>'d',101=>'e',102=>'f',103=>'g',104=>'h',105=>'i',106=>'j',107=>'k',108=>'l',109=>'m',110=>'n',111=>'o',112=>'p',113=>'q',114=>'r',115=>'s',116=>'t',117=>'u',118=>'v',119=>'w',120=>'x',121=>'y',122=>'z',123=>'braceleft',124=>'bar',125=>'braceright',126=>'asciitilde',127=>'.notdef',128=>'Euro',129=>'.notdef',130=>'quotesinglbase',131=>'florin',132=>'quotedblbase',133=>'ellipsis',134=>'dagger',135=>'daggerdbl',136=>'.notdef',137=>'perthousand',138=>'.notdef',139=>'guilsinglleft',140=>'.notdef',141=>'.notdef',142=>'.notdef',143=>'.notdef',144=>'.notdef',145=>'quoteleft',146=>'quoteright',147=>'quotedblleft',148=>'quotedblright',149=>'bullet',150=>'endash',151=>'emdash',152=>'.notdef',153=>'trademark',154=>'.notdef',155=>'guilsinglright',156=>'.notdef',157=>'.notdef',158=>'.notdef',159=>'.notdef',160=>'space',161=>'dieresistonos',162=>'Alphatonos',163=>'sterling',164=>'currency',165=>'yen',166=>'brokenbar',167=>'section',168=>'dieresis',169=>'copyright',170=>'.notdef',171=>'guillemotleft',172=>'logicalnot',173=>'hyphen',174=>'registered',175=>'afii00208',176=>'degree',177=>'plusminus',178=>'twosuperior',179=>'threesuperior',180=>'tonos',181=>'mu',182=>'paragraph',183=>'periodcentered',184=>'Epsilontonos',185=>'Etatonos',186=>'Iotatonos',187=>'guillemotright',188=>'Omicrontonos',189=>'onehalf',190=>'Upsilontonos',191=>'Omegatonos',192=>'iotadieresistonos',193=>'Alpha',194=>'Beta',195=>'Gamma',196=>'Delta',197=>'Epsilon',198=>'Zeta',199=>'Eta',200=>'Theta',201=>'Iota',202=>'Kappa',203=>'Lambda',204=>'Mu',205=>'Nu',206=>'Xi',207=>'Omicron',208=>'Pi',209=>'Rho',210=>'.notdef',211=>'Sigma',212=>'Tau',213=>'Upsilon',214=>'Phi',215=>'Chi',216=>'Psi',217=>'Omega',218=>'Iotadieresis',219=>'Upsilondieresis',220=>'alphatonos',221=>'epsilontonos',222=>'etatonos',223=>'iotatonos',224=>'upsilondieresistonos',225=>'alpha',226=>'beta',227=>'gamma',228=>'delta',229=>'epsilon',230=>'zeta',231=>'eta',232=>'theta',233=>'iota',234=>'kappa',235=>'lambda',236=>'mu',237=>'nu',238=>'xi',239=>'omicron',240=>'pi',241=>'rho',242=>'sigma1',243=>'sigma',244=>'tau',245=>'upsilon',246=>'phi',247=>'chi',248=>'psi',249=>'omega',250=>'iotadieresis',251=>'upsilondieresis',252=>'omicrontonos',253=>'upsilontonos',254=>'omegatonos',255=>'.notdef'),
+
+// encoding map for: cp1254
+'cp1254' => array(0=>'.notdef',1=>'.notdef',2=>'.notdef',3=>'.notdef',4=>'.notdef',5=>'.notdef',6=>'.notdef',7=>'.notdef',8=>'.notdef',9=>'.notdef',10=>'.notdef',11=>'.notdef',12=>'.notdef',13=>'.notdef',14=>'.notdef',15=>'.notdef',16=>'.notdef',17=>'.notdef',18=>'.notdef',19=>'.notdef',20=>'.notdef',21=>'.notdef',22=>'.notdef',23=>'.notdef',24=>'.notdef',25=>'.notdef',26=>'.notdef',27=>'.notdef',28=>'.notdef',29=>'.notdef',30=>'.notdef',31=>'.notdef',32=>'space',33=>'exclam',34=>'quotedbl',35=>'numbersign',36=>'dollar',37=>'percent',38=>'ampersand',39=>'quotesingle',40=>'parenleft',41=>'parenright',42=>'asterisk',43=>'plus',44=>'comma',45=>'hyphen',46=>'period',47=>'slash',48=>'zero',49=>'one',50=>'two',51=>'three',52=>'four',53=>'five',54=>'six',55=>'seven',56=>'eight',57=>'nine',58=>'colon',59=>'semicolon',60=>'less',61=>'equal',62=>'greater',63=>'question',64=>'at',65=>'A',66=>'B',67=>'C',68=>'D',69=>'E',70=>'F',71=>'G',72=>'H',73=>'I',74=>'J',75=>'K',76=>'L',77=>'M',78=>'N',79=>'O',80=>'P',81=>'Q',82=>'R',83=>'S',84=>'T',85=>'U',86=>'V',87=>'W',88=>'X',89=>'Y',90=>'Z',91=>'bracketleft',92=>'backslash',93=>'bracketright',94=>'asciicircum',95=>'underscore',96=>'grave',97=>'a',98=>'b',99=>'c',100=>'d',101=>'e',102=>'f',103=>'g',104=>'h',105=>'i',106=>'j',107=>'k',108=>'l',109=>'m',110=>'n',111=>'o',112=>'p',113=>'q',114=>'r',115=>'s',116=>'t',117=>'u',118=>'v',119=>'w',120=>'x',121=>'y',122=>'z',123=>'braceleft',124=>'bar',125=>'braceright',126=>'asciitilde',127=>'.notdef',128=>'Euro',129=>'.notdef',130=>'quotesinglbase',131=>'florin',132=>'quotedblbase',133=>'ellipsis',134=>'dagger',135=>'daggerdbl',136=>'circumflex',137=>'perthousand',138=>'Scaron',139=>'guilsinglleft',140=>'OE',141=>'.notdef',142=>'.notdef',143=>'.notdef',144=>'.notdef',145=>'quoteleft',146=>'quoteright',147=>'quotedblleft',148=>'quotedblright',149=>'bullet',150=>'endash',151=>'emdash',152=>'tilde',153=>'trademark',154=>'scaron',155=>'guilsinglright',156=>'oe',157=>'.notdef',158=>'.notdef',159=>'Ydieresis',160=>'space',161=>'exclamdown',162=>'cent',163=>'sterling',164=>'currency',165=>'yen',166=>'brokenbar',167=>'section',168=>'dieresis',169=>'copyright',170=>'ordfeminine',171=>'guillemotleft',172=>'logicalnot',173=>'hyphen',174=>'registered',175=>'macron',176=>'degree',177=>'plusminus',178=>'twosuperior',179=>'threesuperior',180=>'acute',181=>'mu',182=>'paragraph',183=>'periodcentered',184=>'cedilla',185=>'onesuperior',186=>'ordmasculine',187=>'guillemotright',188=>'onequarter',189=>'onehalf',190=>'threequarters',191=>'questiondown',192=>'Agrave',193=>'Aacute',194=>'Acircumflex',195=>'Atilde',196=>'Adieresis',197=>'Aring',198=>'AE',199=>'Ccedilla',200=>'Egrave',201=>'Eacute',202=>'Ecircumflex',203=>'Edieresis',204=>'Igrave',205=>'Iacute',206=>'Icircumflex',207=>'Idieresis',208=>'Gbreve',209=>'Ntilde',210=>'Ograve',211=>'Oacute',212=>'Ocircumflex',213=>'Otilde',214=>'Odieresis',215=>'multiply',216=>'Oslash',217=>'Ugrave',218=>'Uacute',219=>'Ucircumflex',220=>'Udieresis',221=>'Idotaccent',222=>'Scedilla',223=>'germandbls',224=>'agrave',225=>'aacute',226=>'acircumflex',227=>'atilde',228=>'adieresis',229=>'aring',230=>'ae',231=>'ccedilla',232=>'egrave',233=>'eacute',234=>'ecircumflex',235=>'edieresis',236=>'igrave',237=>'iacute',238=>'icircumflex',239=>'idieresis',240=>'gbreve',241=>'ntilde',242=>'ograve',243=>'oacute',244=>'ocircumflex',245=>'otilde',246=>'odieresis',247=>'divide',248=>'oslash',249=>'ugrave',250=>'uacute',251=>'ucircumflex',252=>'udieresis',253=>'dotlessi',254=>'scedilla',255=>'ydieresis'),
+
+// encoding map for: cp1255
+'cp1255' => array(0=>'.notdef',1=>'.notdef',2=>'.notdef',3=>'.notdef',4=>'.notdef',5=>'.notdef',6=>'.notdef',7=>'.notdef',8=>'.notdef',9=>'.notdef',10=>'.notdef',11=>'.notdef',12=>'.notdef',13=>'.notdef',14=>'.notdef',15=>'.notdef',16=>'.notdef',17=>'.notdef',18=>'.notdef',19=>'.notdef',20=>'.notdef',21=>'.notdef',22=>'.notdef',23=>'.notdef',24=>'.notdef',25=>'.notdef',26=>'.notdef',27=>'.notdef',28=>'.notdef',29=>'.notdef',30=>'.notdef',31=>'.notdef',32=>'space',33=>'exclam',34=>'quotedbl',35=>'numbersign',36=>'dollar',37=>'percent',38=>'ampersand',39=>'quotesingle',40=>'parenleft',41=>'parenright',42=>'asterisk',43=>'plus',44=>'comma',45=>'hyphen',46=>'period',47=>'slash',48=>'zero',49=>'one',50=>'two',51=>'three',52=>'four',53=>'five',54=>'six',55=>'seven',56=>'eight',57=>'nine',58=>'colon',59=>'semicolon',60=>'less',61=>'equal',62=>'greater',63=>'question',64=>'at',65=>'A',66=>'B',67=>'C',68=>'D',69=>'E',70=>'F',71=>'G',72=>'H',73=>'I',74=>'J',75=>'K',76=>'L',77=>'M',78=>'N',79=>'O',80=>'P',81=>'Q',82=>'R',83=>'S',84=>'T',85=>'U',86=>'V',87=>'W',88=>'X',89=>'Y',90=>'Z',91=>'bracketleft',92=>'backslash',93=>'bracketright',94=>'asciicircum',95=>'underscore',96=>'grave',97=>'a',98=>'b',99=>'c',100=>'d',101=>'e',102=>'f',103=>'g',104=>'h',105=>'i',106=>'j',107=>'k',108=>'l',109=>'m',110=>'n',111=>'o',112=>'p',113=>'q',114=>'r',115=>'s',116=>'t',117=>'u',118=>'v',119=>'w',120=>'x',121=>'y',122=>'z',123=>'braceleft',124=>'bar',125=>'braceright',126=>'asciitilde',127=>'.notdef',128=>'Euro',129=>'.notdef',130=>'quotesinglbase',131=>'florin',132=>'quotedblbase',133=>'ellipsis',134=>'dagger',135=>'daggerdbl',136=>'circumflex',137=>'perthousand',138=>'.notdef',139=>'guilsinglleft',140=>'.notdef',141=>'.notdef',142=>'.notdef',143=>'.notdef',144=>'.notdef',145=>'quoteleft',146=>'quoteright',147=>'quotedblleft',148=>'quotedblright',149=>'bullet',150=>'endash',151=>'emdash',152=>'tilde',153=>'trademark',154=>'.notdef',155=>'guilsinglright',156=>'.notdef',157=>'.notdef',158=>'.notdef',159=>'.notdef',160=>'space',161=>'exclamdown',162=>'cent',163=>'sterling',164=>'afii57636',165=>'yen',166=>'brokenbar',167=>'section',168=>'dieresis',169=>'copyright',170=>'multiply',171=>'guillemotleft',172=>'logicalnot',173=>'sfthyphen',174=>'registered',175=>'macron',176=>'degree',177=>'plusminus',178=>'twosuperior',179=>'threesuperior',180=>'acute',181=>'mu',182=>'paragraph',183=>'middot',184=>'cedilla',185=>'onesuperior',186=>'divide',187=>'guillemotright',188=>'onequarter',189=>'onehalf',190=>'threequarters',191=>'questiondown',192=>'afii57799',193=>'afii57801',194=>'afii57800',195=>'afii57802',196=>'afii57793',197=>'afii57794',198=>'afii57795',199=>'afii57798',200=>'afii57797',201=>'afii57806',202=>'.notdef',203=>'afii57796',204=>'afii57807',205=>'afii57839',206=>'afii57645',207=>'afii57841',208=>'afii57842',209=>'afii57804',210=>'afii57803',211=>'afii57658',212=>'afii57716',213=>'afii57717',214=>'afii57718',215=>'gereshhebrew',216=>'gershayimhebrew',217=>'.notdef',218=>'.notdef',219=>'.notdef',220=>'.notdef',221=>'.notdef',222=>'.notdef',223=>'.notdef',224=>'afii57664',225=>'afii57665',226=>'afii57666',227=>'afii57667',228=>'afii57668',229=>'afii57669',230=>'afii57670',231=>'afii57671',232=>'afii57672',233=>'afii57673',234=>'afii57674',235=>'afii57675',236=>'afii57676',237=>'afii57677',238=>'afii57678',239=>'afii57679',240=>'afii57680',241=>'afii57681',242=>'afii57682',243=>'afii57683',244=>'afii57684',245=>'afii57685',246=>'afii57686',247=>'afii57687',248=>'afii57688',249=>'afii57689',250=>'afii57690',251=>'.notdef',252=>'.notdef',253=>'afii299',254=>'afii300',255=>'.notdef'),
+
+// encoding map for: cp1256
+'cp1256' => array(0=>'.notdef',1=>'.notdef',2=>'.notdef',3=>'.notdef',4=>'.notdef',5=>'.notdef',6=>'.notdef',7=>'.notdef',8=>'.notdef',9=>'.notdef',10=>'.notdef',11=>'.notdef',12=>'.notdef',13=>'.notdef',14=>'.notdef',15=>'.notdef',16=>'.notdef',17=>'.notdef',18=>'.notdef',19=>'.notdef',20=>'.notdef',21=>'.notdef',22=>'.notdef',23=>'.notdef',24=>'.notdef',25=>'.notdef',26=>'.notdef',27=>'.notdef',28=>'.notdef',29=>'.notdef',30=>'.notdef',31=>'.notdef',32=>'space',33=>'exclam',34=>'quotedbl',35=>'numbersign',36=>'dollar',37=>'percent',38=>'ampersand',39=>'quotesingle',40=>'parenleft',41=>'parenright',42=>'asterisk',43=>'plus',44=>'comma',45=>'hyphen',46=>'period',47=>'slash',48=>'zero',49=>'one',50=>'two',51=>'three',52=>'four',53=>'five',54=>'six',55=>'seven',56=>'eight',57=>'nine',58=>'colon',59=>'semicolon',60=>'less',61=>'equal',62=>'greater',63=>'question',64=>'at',65=>'A',66=>'B',67=>'C',68=>'D',69=>'E',70=>'F',71=>'G',72=>'H',73=>'I',74=>'J',75=>'K',76=>'L',77=>'M',78=>'N',79=>'O',80=>'P',81=>'Q',82=>'R',83=>'S',84=>'T',85=>'U',86=>'V',87=>'W',88=>'X',89=>'Y',90=>'Z',91=>'bracketleft',92=>'backslash',93=>'bracketright',94=>'asciicircum',95=>'underscore',96=>'grave',97=>'a',98=>'b',99=>'c',100=>'d',101=>'e',102=>'f',103=>'g',104=>'h',105=>'i',106=>'j',107=>'k',108=>'l',109=>'m',110=>'n',111=>'o',112=>'p',113=>'q',114=>'r',115=>'s',116=>'t',117=>'u',118=>'v',119=>'w',120=>'x',121=>'y',122=>'z',123=>'braceleft',124=>'bar',125=>'braceright',126=>'asciitilde',127=>'.notdef',128=>'Euro',129=>'afii57506',130=>'quotesinglbase',131=>'florin',132=>'quotedblbase',133=>'ellipsis',134=>'dagger',135=>'daggerdbl',136=>'circumflex',137=>'perthousand',138=>'afii57511',139=>'guilsinglleft',140=>'OE',141=>'afii57507',142=>'afii57508',143=>'afii57512',144=>'afii57509',145=>'quoteleft',146=>'quoteright',147=>'quotedblleft',148=>'quotedblright',149=>'bullet',150=>'endash',151=>'emdash',152=>'.notdef',153=>'trademark',154=>'afii57513',155=>'guilsinglright',156=>'oe',157=>'afii61664',158=>'afii301',159=>'afii57514',160=>'space',161=>'afii57388',162=>'cent',163=>'sterling',164=>'currency',165=>'yen',166=>'brokenbar',167=>'section',168=>'dieresis',169=>'copyright',170=>'.notdef',171=>'guillemotleft',172=>'logicalnot',173=>'hyphen',174=>'registered',175=>'macron',176=>'degree',177=>'plusminus',178=>'twosuperior',179=>'threesuperior',180=>'acute',181=>'mu',182=>'paragraph',183=>'periodcentered',184=>'cedilla',185=>'onesuperior',186=>'afii57403',187=>'guillemotright',188=>'onequarter',189=>'onehalf',190=>'threequarters',191=>'afii57407',192=>'.notdef',193=>'afii57409',194=>'afii57410',195=>'afii57411',196=>'afii57412',197=>'afii57413',198=>'afii57414',199=>'afii57415',200=>'afii57416',201=>'afii57417',202=>'afii57418',203=>'afii57419',204=>'afii57420',205=>'afii57421',206=>'afii57422',207=>'afii57423',208=>'afii57424',209=>'afii57425',210=>'afii57426',211=>'afii57427',212=>'afii57428',213=>'afii57429',214=>'afii57430',215=>'multiply',216=>'afii57431',217=>'afii57432',218=>'afii57433',219=>'afii57434',220=>'afii57440',221=>'afii57441',222=>'afii57442',223=>'afii57443',224=>'agrave',225=>'afii57444',226=>'acircumflex',227=>'afii57445',228=>'afii57446',229=>'afii57470',230=>'afii57448',231=>'ccedilla',232=>'egrave',233=>'eacute',234=>'ecircumflex',235=>'edieresis',236=>'afii57449',237=>'afii57450',238=>'icircumflex',239=>'idieresis',240=>'afii57451',241=>'afii57452',242=>'afii57453',243=>'afii57454',244=>'ocircumflex',245=>'afii57455',246=>'afii57456',247=>'divide',248=>'afii57457',249=>'ugrave',250=>'afii57458',251=>'ucircumflex',252=>'udieresis',253=>'afii299',254=>'afii300',255=>'afii57519'),
+
+// encoding map for: cp1257
+'cp1257' => array(0=>'.notdef',1=>'.notdef',2=>'.notdef',3=>'.notdef',4=>'.notdef',5=>'.notdef',6=>'.notdef',7=>'.notdef',8=>'.notdef',9=>'.notdef',10=>'.notdef',11=>'.notdef',12=>'.notdef',13=>'.notdef',14=>'.notdef',15=>'.notdef',16=>'.notdef',17=>'.notdef',18=>'.notdef',19=>'.notdef',20=>'.notdef',21=>'.notdef',22=>'.notdef',23=>'.notdef',24=>'.notdef',25=>'.notdef',26=>'.notdef',27=>'.notdef',28=>'.notdef',29=>'.notdef',30=>'.notdef',31=>'.notdef',32=>'space',33=>'exclam',34=>'quotedbl',35=>'numbersign',36=>'dollar',37=>'percent',38=>'ampersand',39=>'quotesingle',40=>'parenleft',41=>'parenright',42=>'asterisk',43=>'plus',44=>'comma',45=>'hyphen',46=>'period',47=>'slash',48=>'zero',49=>'one',50=>'two',51=>'three',52=>'four',53=>'five',54=>'six',55=>'seven',56=>'eight',57=>'nine',58=>'colon',59=>'semicolon',60=>'less',61=>'equal',62=>'greater',63=>'question',64=>'at',65=>'A',66=>'B',67=>'C',68=>'D',69=>'E',70=>'F',71=>'G',72=>'H',73=>'I',74=>'J',75=>'K',76=>'L',77=>'M',78=>'N',79=>'O',80=>'P',81=>'Q',82=>'R',83=>'S',84=>'T',85=>'U',86=>'V',87=>'W',88=>'X',89=>'Y',90=>'Z',91=>'bracketleft',92=>'backslash',93=>'bracketright',94=>'asciicircum',95=>'underscore',96=>'grave',97=>'a',98=>'b',99=>'c',100=>'d',101=>'e',102=>'f',103=>'g',104=>'h',105=>'i',106=>'j',107=>'k',108=>'l',109=>'m',110=>'n',111=>'o',112=>'p',113=>'q',114=>'r',115=>'s',116=>'t',117=>'u',118=>'v',119=>'w',120=>'x',121=>'y',122=>'z',123=>'braceleft',124=>'bar',125=>'braceright',126=>'asciitilde',127=>'.notdef',128=>'Euro',129=>'.notdef',130=>'quotesinglbase',131=>'.notdef',132=>'quotedblbase',133=>'ellipsis',134=>'dagger',135=>'daggerdbl',136=>'.notdef',137=>'perthousand',138=>'.notdef',139=>'guilsinglleft',140=>'.notdef',141=>'dieresis',142=>'caron',143=>'cedilla',144=>'.notdef',145=>'quoteleft',146=>'quoteright',147=>'quotedblleft',148=>'quotedblright',149=>'bullet',150=>'endash',151=>'emdash',152=>'.notdef',153=>'trademark',154=>'.notdef',155=>'guilsinglright',156=>'.notdef',157=>'macron',158=>'ogonek',159=>'.notdef',160=>'space',161=>'.notdef',162=>'cent',163=>'sterling',164=>'currency',165=>'.notdef',166=>'brokenbar',167=>'section',168=>'Oslash',169=>'copyright',170=>'Rcommaaccent',171=>'guillemotleft',172=>'logicalnot',173=>'hyphen',174=>'registered',175=>'AE',176=>'degree',177=>'plusminus',178=>'twosuperior',179=>'threesuperior',180=>'acute',181=>'mu',182=>'paragraph',183=>'periodcentered',184=>'oslash',185=>'onesuperior',186=>'rcommaaccent',187=>'guillemotright',188=>'onequarter',189=>'onehalf',190=>'threequarters',191=>'ae',192=>'Aogonek',193=>'Iogonek',194=>'Amacron',195=>'Cacute',196=>'Adieresis',197=>'Aring',198=>'Eogonek',199=>'Emacron',200=>'Ccaron',201=>'Eacute',202=>'Zacute',203=>'Edotaccent',204=>'Gcommaaccent',205=>'Kcommaaccent',206=>'Imacron',207=>'Lcommaaccent',208=>'Scaron',209=>'Nacute',210=>'Ncommaaccent',211=>'Oacute',212=>'Omacron',213=>'Otilde',214=>'Odieresis',215=>'multiply',216=>'Uogonek',217=>'Lslash',218=>'Sacute',219=>'Umacron',220=>'Udieresis',221=>'Zdotaccent',222=>'Zcaron',223=>'germandbls',224=>'aogonek',225=>'iogonek',226=>'amacron',227=>'cacute',228=>'adieresis',229=>'aring',230=>'eogonek',231=>'emacron',232=>'ccaron',233=>'eacute',234=>'zacute',235=>'edotaccent',236=>'gcommaaccent',237=>'kcommaaccent',238=>'imacron',239=>'lcommaaccent',240=>'scaron',241=>'nacute',242=>'ncommaaccent',243=>'oacute',244=>'omacron',245=>'otilde',246=>'odieresis',247=>'divide',248=>'uogonek',249=>'lslash',250=>'sacute',251=>'umacron',252=>'udieresis',253=>'zdotaccent',254=>'zcaron',255=>'dotaccent'),
+
+// encoding map for: cp1258
+'cp1258' => array(0=>'.notdef',1=>'.notdef',2=>'.notdef',3=>'.notdef',4=>'.notdef',5=>'.notdef',6=>'.notdef',7=>'.notdef',8=>'.notdef',9=>'.notdef',10=>'.notdef',11=>'.notdef',12=>'.notdef',13=>'.notdef',14=>'.notdef',15=>'.notdef',16=>'.notdef',17=>'.notdef',18=>'.notdef',19=>'.notdef',20=>'.notdef',21=>'.notdef',22=>'.notdef',23=>'.notdef',24=>'.notdef',25=>'.notdef',26=>'.notdef',27=>'.notdef',28=>'.notdef',29=>'.notdef',30=>'.notdef',31=>'.notdef',32=>'space',33=>'exclam',34=>'quotedbl',35=>'numbersign',36=>'dollar',37=>'percent',38=>'ampersand',39=>'quotesingle',40=>'parenleft',41=>'parenright',42=>'asterisk',43=>'plus',44=>'comma',45=>'hyphen',46=>'period',47=>'slash',48=>'zero',49=>'one',50=>'two',51=>'three',52=>'four',53=>'five',54=>'six',55=>'seven',56=>'eight',57=>'nine',58=>'colon',59=>'semicolon',60=>'less',61=>'equal',62=>'greater',63=>'question',64=>'at',65=>'A',66=>'B',67=>'C',68=>'D',69=>'E',70=>'F',71=>'G',72=>'H',73=>'I',74=>'J',75=>'K',76=>'L',77=>'M',78=>'N',79=>'O',80=>'P',81=>'Q',82=>'R',83=>'S',84=>'T',85=>'U',86=>'V',87=>'W',88=>'X',89=>'Y',90=>'Z',91=>'bracketleft',92=>'backslash',93=>'bracketright',94=>'asciicircum',95=>'underscore',96=>'grave',97=>'a',98=>'b',99=>'c',100=>'d',101=>'e',102=>'f',103=>'g',104=>'h',105=>'i',106=>'j',107=>'k',108=>'l',109=>'m',110=>'n',111=>'o',112=>'p',113=>'q',114=>'r',115=>'s',116=>'t',117=>'u',118=>'v',119=>'w',120=>'x',121=>'y',122=>'z',123=>'braceleft',124=>'bar',125=>'braceright',126=>'asciitilde',127=>'.notdef',128=>'Euro',129=>'.notdef',130=>'quotesinglbase',131=>'florin',132=>'quotedblbase',133=>'ellipsis',134=>'dagger',135=>'daggerdbl',136=>'circumflex',137=>'perthousand',138=>'.notdef',139=>'guilsinglleft',140=>'OE',141=>'.notdef',142=>'.notdef',143=>'.notdef',144=>'.notdef',145=>'quoteleft',146=>'quoteright',147=>'quotedblleft',148=>'quotedblright',149=>'bullet',150=>'endash',151=>'emdash',152=>'tilde',153=>'trademark',154=>'.notdef',155=>'guilsinglright',156=>'oe',157=>'.notdef',158=>'.notdef',159=>'Ydieresis',160=>'space',161=>'exclamdown',162=>'cent',163=>'sterling',164=>'currency',165=>'yen',166=>'brokenbar',167=>'section',168=>'dieresis',169=>'copyright',170=>'ordfeminine',171=>'guillemotleft',172=>'logicalnot',173=>'hyphen',174=>'registered',175=>'macron',176=>'degree',177=>'plusminus',178=>'twosuperior',179=>'threesuperior',180=>'acute',181=>'mu',182=>'paragraph',183=>'periodcentered',184=>'cedilla',185=>'onesuperior',186=>'ordmasculine',187=>'guillemotright',188=>'onequarter',189=>'onehalf',190=>'threequarters',191=>'questiondown',192=>'Agrave',193=>'Aacute',194=>'Acircumflex',195=>'Abreve',196=>'Adieresis',197=>'Aring',198=>'AE',199=>'Ccedilla',200=>'Egrave',201=>'Eacute',202=>'Ecircumflex',203=>'Edieresis',204=>'gravecomb',205=>'Iacute',206=>'Icircumflex',207=>'Idieresis',208=>'Dcroat',209=>'Ntilde',210=>'hookabovecomb',211=>'Oacute',212=>'Ocircumflex',213=>'Ohorn',214=>'Odieresis',215=>'multiply',216=>'Oslash',217=>'Ugrave',218=>'Uacute',219=>'Ucircumflex',220=>'Udieresis',221=>'Uhorn',222=>'tildecomb',223=>'germandbls',224=>'agrave',225=>'aacute',226=>'acircumflex',227=>'abreve',228=>'adieresis',229=>'aring',230=>'ae',231=>'ccedilla',232=>'egrave',233=>'eacute',234=>'ecircumflex',235=>'edieresis',236=>'acutecomb',237=>'iacute',238=>'icircumflex',239=>'idieresis',240=>'dcroat',241=>'ntilde',242=>'dotbelowcomb',243=>'oacute',244=>'ocircumflex',245=>'ohorn',246=>'odieresis',247=>'divide',248=>'oslash',249=>'ugrave',250=>'uacute',251=>'ucircumflex',252=>'udieresis',253=>'uhorn',254=>'dong',255=>'ydieresis'),
+
+// encoding map for: iso-8859-1
+'iso-8859-1' => array(0=>'.notdef',1=>'.notdef',2=>'.notdef',3=>'.notdef',4=>'.notdef',5=>'.notdef',6=>'.notdef',7=>'.notdef',8=>'.notdef',9=>'.notdef',10=>'.notdef',11=>'.notdef',12=>'.notdef',13=>'.notdef',14=>'.notdef',15=>'.notdef',16=>'.notdef',17=>'.notdef',18=>'.notdef',19=>'.notdef',20=>'.notdef',21=>'.notdef',22=>'.notdef',23=>'.notdef',24=>'.notdef',25=>'.notdef',26=>'.notdef',27=>'.notdef',28=>'.notdef',29=>'.notdef',30=>'.notdef',31=>'.notdef',32=>'space',33=>'exclam',34=>'quotedbl',35=>'numbersign',36=>'dollar',37=>'percent',38=>'ampersand',39=>'quotesingle',40=>'parenleft',41=>'parenright',42=>'asterisk',43=>'plus',44=>'comma',45=>'hyphen',46=>'period',47=>'slash',48=>'zero',49=>'one',50=>'two',51=>'three',52=>'four',53=>'five',54=>'six',55=>'seven',56=>'eight',57=>'nine',58=>'colon',59=>'semicolon',60=>'less',61=>'equal',62=>'greater',63=>'question',64=>'at',65=>'A',66=>'B',67=>'C',68=>'D',69=>'E',70=>'F',71=>'G',72=>'H',73=>'I',74=>'J',75=>'K',76=>'L',77=>'M',78=>'N',79=>'O',80=>'P',81=>'Q',82=>'R',83=>'S',84=>'T',85=>'U',86=>'V',87=>'W',88=>'X',89=>'Y',90=>'Z',91=>'bracketleft',92=>'backslash',93=>'bracketright',94=>'asciicircum',95=>'underscore',96=>'grave',97=>'a',98=>'b',99=>'c',100=>'d',101=>'e',102=>'f',103=>'g',104=>'h',105=>'i',106=>'j',107=>'k',108=>'l',109=>'m',110=>'n',111=>'o',112=>'p',113=>'q',114=>'r',115=>'s',116=>'t',117=>'u',118=>'v',119=>'w',120=>'x',121=>'y',122=>'z',123=>'braceleft',124=>'bar',125=>'braceright',126=>'asciitilde',127=>'.notdef',128=>'.notdef',129=>'.notdef',130=>'.notdef',131=>'.notdef',132=>'.notdef',133=>'.notdef',134=>'.notdef',135=>'.notdef',136=>'.notdef',137=>'.notdef',138=>'.notdef',139=>'.notdef',140=>'.notdef',141=>'.notdef',142=>'.notdef',143=>'.notdef',144=>'.notdef',145=>'.notdef',146=>'.notdef',147=>'.notdef',148=>'.notdef',149=>'.notdef',150=>'.notdef',151=>'.notdef',152=>'.notdef',153=>'.notdef',154=>'.notdef',155=>'.notdef',156=>'.notdef',157=>'.notdef',158=>'.notdef',159=>'.notdef',160=>'space',161=>'exclamdown',162=>'cent',163=>'sterling',164=>'currency',165=>'yen',166=>'brokenbar',167=>'section',168=>'dieresis',169=>'copyright',170=>'ordfeminine',171=>'guillemotleft',172=>'logicalnot',173=>'hyphen',174=>'registered',175=>'macron',176=>'degree',177=>'plusminus',178=>'twosuperior',179=>'threesuperior',180=>'acute',181=>'mu',182=>'paragraph',183=>'periodcentered',184=>'cedilla',185=>'onesuperior',186=>'ordmasculine',187=>'guillemotright',188=>'onequarter',189=>'onehalf',190=>'threequarters',191=>'questiondown',192=>'Agrave',193=>'Aacute',194=>'Acircumflex',195=>'Atilde',196=>'Adieresis',197=>'Aring',198=>'AE',199=>'Ccedilla',200=>'Egrave',201=>'Eacute',202=>'Ecircumflex',203=>'Edieresis',204=>'Igrave',205=>'Iacute',206=>'Icircumflex',207=>'Idieresis',208=>'Eth',209=>'Ntilde',210=>'Ograve',211=>'Oacute',212=>'Ocircumflex',213=>'Otilde',214=>'Odieresis',215=>'multiply',216=>'Oslash',217=>'Ugrave',218=>'Uacute',219=>'Ucircumflex',220=>'Udieresis',221=>'Yacute',222=>'Thorn',223=>'germandbls',224=>'agrave',225=>'aacute',226=>'acircumflex',227=>'atilde',228=>'adieresis',229=>'aring',230=>'ae',231=>'ccedilla',232=>'egrave',233=>'eacute',234=>'ecircumflex',235=>'edieresis',236=>'igrave',237=>'iacute',238=>'icircumflex',239=>'idieresis',240=>'eth',241=>'ntilde',242=>'ograve',243=>'oacute',244=>'ocircumflex',245=>'otilde',246=>'odieresis',247=>'divide',248=>'oslash',249=>'ugrave',250=>'uacute',251=>'ucircumflex',252=>'udieresis',253=>'yacute',254=>'thorn',255=>'ydieresis'),
+
+// encoding map for: iso-8859-2
+'iso-8859-2' => array(0=>'.notdef',1=>'.notdef',2=>'.notdef',3=>'.notdef',4=>'.notdef',5=>'.notdef',6=>'.notdef',7=>'.notdef',8=>'.notdef',9=>'.notdef',10=>'.notdef',11=>'.notdef',12=>'.notdef',13=>'.notdef',14=>'.notdef',15=>'.notdef',16=>'.notdef',17=>'.notdef',18=>'.notdef',19=>'.notdef',20=>'.notdef',21=>'.notdef',22=>'.notdef',23=>'.notdef',24=>'.notdef',25=>'.notdef',26=>'.notdef',27=>'.notdef',28=>'.notdef',29=>'.notdef',30=>'.notdef',31=>'.notdef',32=>'space',33=>'exclam',34=>'quotedbl',35=>'numbersign',36=>'dollar',37=>'percent',38=>'ampersand',39=>'quotesingle',40=>'parenleft',41=>'parenright',42=>'asterisk',43=>'plus',44=>'comma',45=>'hyphen',46=>'period',47=>'slash',48=>'zero',49=>'one',50=>'two',51=>'three',52=>'four',53=>'five',54=>'six',55=>'seven',56=>'eight',57=>'nine',58=>'colon',59=>'semicolon',60=>'less',61=>'equal',62=>'greater',63=>'question',64=>'at',65=>'A',66=>'B',67=>'C',68=>'D',69=>'E',70=>'F',71=>'G',72=>'H',73=>'I',74=>'J',75=>'K',76=>'L',77=>'M',78=>'N',79=>'O',80=>'P',81=>'Q',82=>'R',83=>'S',84=>'T',85=>'U',86=>'V',87=>'W',88=>'X',89=>'Y',90=>'Z',91=>'bracketleft',92=>'backslash',93=>'bracketright',94=>'asciicircum',95=>'underscore',96=>'grave',97=>'a',98=>'b',99=>'c',100=>'d',101=>'e',102=>'f',103=>'g',104=>'h',105=>'i',106=>'j',107=>'k',108=>'l',109=>'m',110=>'n',111=>'o',112=>'p',113=>'q',114=>'r',115=>'s',116=>'t',117=>'u',118=>'v',119=>'w',120=>'x',121=>'y',122=>'z',123=>'braceleft',124=>'bar',125=>'braceright',126=>'asciitilde',127=>'.notdef',128=>'.notdef',129=>'.notdef',130=>'.notdef',131=>'.notdef',132=>'.notdef',133=>'.notdef',134=>'.notdef',135=>'.notdef',136=>'.notdef',137=>'.notdef',138=>'.notdef',139=>'.notdef',140=>'.notdef',141=>'.notdef',142=>'.notdef',143=>'.notdef',144=>'.notdef',145=>'.notdef',146=>'.notdef',147=>'.notdef',148=>'.notdef',149=>'.notdef',150=>'.notdef',151=>'.notdef',152=>'.notdef',153=>'.notdef',154=>'.notdef',155=>'.notdef',156=>'.notdef',157=>'.notdef',158=>'.notdef',159=>'.notdef',160=>'space',161=>'Aogonek',162=>'breve',163=>'Lslash',164=>'currency',165=>'Lcaron',166=>'Sacute',167=>'section',168=>'dieresis',169=>'Scaron',170=>'Scedilla',171=>'Tcaron',172=>'Zacute',173=>'hyphen',174=>'Zcaron',175=>'Zdotaccent',176=>'degree',177=>'aogonek',178=>'ogonek',179=>'lslash',180=>'acute',181=>'lcaron',182=>'sacute',183=>'caron',184=>'cedilla',185=>'scaron',186=>'scedilla',187=>'tcaron',188=>'zacute',189=>'hungarumlaut',190=>'zcaron',191=>'zdotaccent',192=>'Racute',193=>'Aacute',194=>'Acircumflex',195=>'Abreve',196=>'Adieresis',197=>'Lacute',198=>'Cacute',199=>'Ccedilla',200=>'Ccaron',201=>'Eacute',202=>'Eogonek',203=>'Edieresis',204=>'Ecaron',205=>'Iacute',206=>'Icircumflex',207=>'Dcaron',208=>'Dcroat',209=>'Nacute',210=>'Ncaron',211=>'Oacute',212=>'Ocircumflex',213=>'Ohungarumlaut',214=>'Odieresis',215=>'multiply',216=>'Rcaron',217=>'Uring',218=>'Uacute',219=>'Uhungarumlaut',220=>'Udieresis',221=>'Yacute',222=>'Tcommaaccent',223=>'germandbls',224=>'racute',225=>'aacute',226=>'acircumflex',227=>'abreve',228=>'adieresis',229=>'lacute',230=>'cacute',231=>'ccedilla',232=>'ccaron',233=>'eacute',234=>'eogonek',235=>'edieresis',236=>'ecaron',237=>'iacute',238=>'icircumflex',239=>'dcaron',240=>'dcroat',241=>'nacute',242=>'ncaron',243=>'oacute',244=>'ocircumflex',245=>'ohungarumlaut',246=>'odieresis',247=>'divide',248=>'rcaron',249=>'uring',250=>'uacute',251=>'uhungarumlaut',252=>'udieresis',253=>'yacute',254=>'tcommaaccent',255=>'dotaccent'),
+
+// encoding map for: iso-8859-4
+'iso-8859-4' => array(0=>'.notdef',1=>'.notdef',2=>'.notdef',3=>'.notdef',4=>'.notdef',5=>'.notdef',6=>'.notdef',7=>'.notdef',8=>'.notdef',9=>'.notdef',10=>'.notdef',11=>'.notdef',12=>'.notdef',13=>'.notdef',14=>'.notdef',15=>'.notdef',16=>'.notdef',17=>'.notdef',18=>'.notdef',19=>'.notdef',20=>'.notdef',21=>'.notdef',22=>'.notdef',23=>'.notdef',24=>'.notdef',25=>'.notdef',26=>'.notdef',27=>'.notdef',28=>'.notdef',29=>'.notdef',30=>'.notdef',31=>'.notdef',32=>'space',33=>'exclam',34=>'quotedbl',35=>'numbersign',36=>'dollar',37=>'percent',38=>'ampersand',39=>'quotesingle',40=>'parenleft',41=>'parenright',42=>'asterisk',43=>'plus',44=>'comma',45=>'hyphen',46=>'period',47=>'slash',48=>'zero',49=>'one',50=>'two',51=>'three',52=>'four',53=>'five',54=>'six',55=>'seven',56=>'eight',57=>'nine',58=>'colon',59=>'semicolon',60=>'less',61=>'equal',62=>'greater',63=>'question',64=>'at',65=>'A',66=>'B',67=>'C',68=>'D',69=>'E',70=>'F',71=>'G',72=>'H',73=>'I',74=>'J',75=>'K',76=>'L',77=>'M',78=>'N',79=>'O',80=>'P',81=>'Q',82=>'R',83=>'S',84=>'T',85=>'U',86=>'V',87=>'W',88=>'X',89=>'Y',90=>'Z',91=>'bracketleft',92=>'backslash',93=>'bracketright',94=>'asciicircum',95=>'underscore',96=>'grave',97=>'a',98=>'b',99=>'c',100=>'d',101=>'e',102=>'f',103=>'g',104=>'h',105=>'i',106=>'j',107=>'k',108=>'l',109=>'m',110=>'n',111=>'o',112=>'p',113=>'q',114=>'r',115=>'s',116=>'t',117=>'u',118=>'v',119=>'w',120=>'x',121=>'y',122=>'z',123=>'braceleft',124=>'bar',125=>'braceright',126=>'asciitilde',127=>'.notdef',128=>'.notdef',129=>'.notdef',130=>'.notdef',131=>'.notdef',132=>'.notdef',133=>'.notdef',134=>'.notdef',135=>'.notdef',136=>'.notdef',137=>'.notdef',138=>'.notdef',139=>'.notdef',140=>'.notdef',141=>'.notdef',142=>'.notdef',143=>'.notdef',144=>'.notdef',145=>'.notdef',146=>'.notdef',147=>'.notdef',148=>'.notdef',149=>'.notdef',150=>'.notdef',151=>'.notdef',152=>'.notdef',153=>'.notdef',154=>'.notdef',155=>'.notdef',156=>'.notdef',157=>'.notdef',158=>'.notdef',159=>'.notdef',160=>'space',161=>'Aogonek',162=>'kgreenlandic',163=>'Rcommaaccent',164=>'currency',165=>'Itilde',166=>'Lcommaaccent',167=>'section',168=>'dieresis',169=>'Scaron',170=>'Emacron',171=>'Gcommaaccent',172=>'Tbar',173=>'hyphen',174=>'Zcaron',175=>'macron',176=>'degree',177=>'aogonek',178=>'ogonek',179=>'rcommaaccent',180=>'acute',181=>'itilde',182=>'lcommaaccent',183=>'caron',184=>'cedilla',185=>'scaron',186=>'emacron',187=>'gcommaaccent',188=>'tbar',189=>'Eng',190=>'zcaron',191=>'eng',192=>'Amacron',193=>'Aacute',194=>'Acircumflex',195=>'Atilde',196=>'Adieresis',197=>'Aring',198=>'AE',199=>'Iogonek',200=>'Ccaron',201=>'Eacute',202=>'Eogonek',203=>'Edieresis',204=>'Edotaccent',205=>'Iacute',206=>'Icircumflex',207=>'Imacron',208=>'Dcroat',209=>'Ncommaaccent',210=>'Omacron',211=>'Kcommaaccent',212=>'Ocircumflex',213=>'Otilde',214=>'Odieresis',215=>'multiply',216=>'Oslash',217=>'Uogonek',218=>'Uacute',219=>'Ucircumflex',220=>'Udieresis',221=>'Utilde',222=>'Umacron',223=>'germandbls',224=>'amacron',225=>'aacute',226=>'acircumflex',227=>'atilde',228=>'adieresis',229=>'aring',230=>'ae',231=>'iogonek',232=>'ccaron',233=>'eacute',234=>'eogonek',235=>'edieresis',236=>'edotaccent',237=>'iacute',238=>'icircumflex',239=>'imacron',240=>'dcroat',241=>'ncommaaccent',242=>'omacron',243=>'kcommaaccent',244=>'ocircumflex',245=>'otilde',246=>'odieresis',247=>'divide',248=>'oslash',249=>'uogonek',250=>'uacute',251=>'ucircumflex',252=>'udieresis',253=>'utilde',254=>'umacron',255=>'dotaccent'),
+
+// encoding map for: iso-8859-5
+'iso-8859-5' => array(0=>'.notdef',1=>'.notdef',2=>'.notdef',3=>'.notdef',4=>'.notdef',5=>'.notdef',6=>'.notdef',7=>'.notdef',8=>'.notdef',9=>'.notdef',10=>'.notdef',11=>'.notdef',12=>'.notdef',13=>'.notdef',14=>'.notdef',15=>'.notdef',16=>'.notdef',17=>'.notdef',18=>'.notdef',19=>'.notdef',20=>'.notdef',21=>'.notdef',22=>'.notdef',23=>'.notdef',24=>'.notdef',25=>'.notdef',26=>'.notdef',27=>'.notdef',28=>'.notdef',29=>'.notdef',30=>'.notdef',31=>'.notdef',32=>'space',33=>'exclam',34=>'quotedbl',35=>'numbersign',36=>'dollar',37=>'percent',38=>'ampersand',39=>'quotesingle',40=>'parenleft',41=>'parenright',42=>'asterisk',43=>'plus',44=>'comma',45=>'hyphen',46=>'period',47=>'slash',48=>'zero',49=>'one',50=>'two',51=>'three',52=>'four',53=>'five',54=>'six',55=>'seven',56=>'eight',57=>'nine',58=>'colon',59=>'semicolon',60=>'less',61=>'equal',62=>'greater',63=>'question',64=>'at',65=>'A',66=>'B',67=>'C',68=>'D',69=>'E',70=>'F',71=>'G',72=>'H',73=>'I',74=>'J',75=>'K',76=>'L',77=>'M',78=>'N',79=>'O',80=>'P',81=>'Q',82=>'R',83=>'S',84=>'T',85=>'U',86=>'V',87=>'W',88=>'X',89=>'Y',90=>'Z',91=>'bracketleft',92=>'backslash',93=>'bracketright',94=>'asciicircum',95=>'underscore',96=>'grave',97=>'a',98=>'b',99=>'c',100=>'d',101=>'e',102=>'f',103=>'g',104=>'h',105=>'i',106=>'j',107=>'k',108=>'l',109=>'m',110=>'n',111=>'o',112=>'p',113=>'q',114=>'r',115=>'s',116=>'t',117=>'u',118=>'v',119=>'w',120=>'x',121=>'y',122=>'z',123=>'braceleft',124=>'bar',125=>'braceright',126=>'asciitilde',127=>'.notdef',128=>'.notdef',129=>'.notdef',130=>'.notdef',131=>'.notdef',132=>'.notdef',133=>'.notdef',134=>'.notdef',135=>'.notdef',136=>'.notdef',137=>'.notdef',138=>'.notdef',139=>'.notdef',140=>'.notdef',141=>'.notdef',142=>'.notdef',143=>'.notdef',144=>'.notdef',145=>'.notdef',146=>'.notdef',147=>'.notdef',148=>'.notdef',149=>'.notdef',150=>'.notdef',151=>'.notdef',152=>'.notdef',153=>'.notdef',154=>'.notdef',155=>'.notdef',156=>'.notdef',157=>'.notdef',158=>'.notdef',159=>'.notdef',160=>'space',161=>'afii10023',162=>'afii10051',163=>'afii10052',164=>'afii10053',165=>'afii10054',166=>'afii10055',167=>'afii10056',168=>'afii10057',169=>'afii10058',170=>'afii10059',171=>'afii10060',172=>'afii10061',173=>'hyphen',174=>'afii10062',175=>'afii10145',176=>'afii10017',177=>'afii10018',178=>'afii10019',179=>'afii10020',180=>'afii10021',181=>'afii10022',182=>'afii10024',183=>'afii10025',184=>'afii10026',185=>'afii10027',186=>'afii10028',187=>'afii10029',188=>'afii10030',189=>'afii10031',190=>'afii10032',191=>'afii10033',192=>'afii10034',193=>'afii10035',194=>'afii10036',195=>'afii10037',196=>'afii10038',197=>'afii10039',198=>'afii10040',199=>'afii10041',200=>'afii10042',201=>'afii10043',202=>'afii10044',203=>'afii10045',204=>'afii10046',205=>'afii10047',206=>'afii10048',207=>'afii10049',208=>'afii10065',209=>'afii10066',210=>'afii10067',211=>'afii10068',212=>'afii10069',213=>'afii10070',214=>'afii10072',215=>'afii10073',216=>'afii10074',217=>'afii10075',218=>'afii10076',219=>'afii10077',220=>'afii10078',221=>'afii10079',222=>'afii10080',223=>'afii10081',224=>'afii10082',225=>'afii10083',226=>'afii10084',227=>'afii10085',228=>'afii10086',229=>'afii10087',230=>'afii10088',231=>'afii10089',232=>'afii10090',233=>'afii10091',234=>'afii10092',235=>'afii10093',236=>'afii10094',237=>'afii10095',238=>'afii10096',239=>'afii10097',240=>'afii61352',241=>'afii10071',242=>'afii10099',243=>'afii10100',244=>'afii10101',245=>'afii10102',246=>'afii10103',247=>'afii10104',248=>'afii10105',249=>'afii10106',250=>'afii10107',251=>'afii10108',252=>'afii10109',253=>'section',254=>'afii10110',255=>'afii10193'),
+
+// encoding map for: iso-8859-7
+'iso-8859-7' => array(0=>'.notdef',1=>'.notdef',2=>'.notdef',3=>'.notdef',4=>'.notdef',5=>'.notdef',6=>'.notdef',7=>'.notdef',8=>'.notdef',9=>'.notdef',10=>'.notdef',11=>'.notdef',12=>'.notdef',13=>'.notdef',14=>'.notdef',15=>'.notdef',16=>'.notdef',17=>'.notdef',18=>'.notdef',19=>'.notdef',20=>'.notdef',21=>'.notdef',22=>'.notdef',23=>'.notdef',24=>'.notdef',25=>'.notdef',26=>'.notdef',27=>'.notdef',28=>'.notdef',29=>'.notdef',30=>'.notdef',31=>'.notdef',32=>'space',33=>'exclam',34=>'quotedbl',35=>'numbersign',36=>'dollar',37=>'percent',38=>'ampersand',39=>'quotesingle',40=>'parenleft',41=>'parenright',42=>'asterisk',43=>'plus',44=>'comma',45=>'hyphen',46=>'period',47=>'slash',48=>'zero',49=>'one',50=>'two',51=>'three',52=>'four',53=>'five',54=>'six',55=>'seven',56=>'eight',57=>'nine',58=>'colon',59=>'semicolon',60=>'less',61=>'equal',62=>'greater',63=>'question',64=>'at',65=>'A',66=>'B',67=>'C',68=>'D',69=>'E',70=>'F',71=>'G',72=>'H',73=>'I',74=>'J',75=>'K',76=>'L',77=>'M',78=>'N',79=>'O',80=>'P',81=>'Q',82=>'R',83=>'S',84=>'T',85=>'U',86=>'V',87=>'W',88=>'X',89=>'Y',90=>'Z',91=>'bracketleft',92=>'backslash',93=>'bracketright',94=>'asciicircum',95=>'underscore',96=>'grave',97=>'a',98=>'b',99=>'c',100=>'d',101=>'e',102=>'f',103=>'g',104=>'h',105=>'i',106=>'j',107=>'k',108=>'l',109=>'m',110=>'n',111=>'o',112=>'p',113=>'q',114=>'r',115=>'s',116=>'t',117=>'u',118=>'v',119=>'w',120=>'x',121=>'y',122=>'z',123=>'braceleft',124=>'bar',125=>'braceright',126=>'asciitilde',127=>'.notdef',128=>'.notdef',129=>'.notdef',130=>'.notdef',131=>'.notdef',132=>'.notdef',133=>'.notdef',134=>'.notdef',135=>'.notdef',136=>'.notdef',137=>'.notdef',138=>'.notdef',139=>'.notdef',140=>'.notdef',141=>'.notdef',142=>'.notdef',143=>'.notdef',144=>'.notdef',145=>'.notdef',146=>'.notdef',147=>'.notdef',148=>'.notdef',149=>'.notdef',150=>'.notdef',151=>'.notdef',152=>'.notdef',153=>'.notdef',154=>'.notdef',155=>'.notdef',156=>'.notdef',157=>'.notdef',158=>'.notdef',159=>'.notdef',160=>'space',161=>'quoteleft',162=>'quoteright',163=>'sterling',164=>'.notdef',165=>'.notdef',166=>'brokenbar',167=>'section',168=>'dieresis',169=>'copyright',170=>'.notdef',171=>'guillemotleft',172=>'logicalnot',173=>'hyphen',174=>'.notdef',175=>'afii00208',176=>'degree',177=>'plusminus',178=>'twosuperior',179=>'threesuperior',180=>'tonos',181=>'dieresistonos',182=>'Alphatonos',183=>'periodcentered',184=>'Epsilontonos',185=>'Etatonos',186=>'Iotatonos',187=>'guillemotright',188=>'Omicrontonos',189=>'onehalf',190=>'Upsilontonos',191=>'Omegatonos',192=>'iotadieresistonos',193=>'Alpha',194=>'Beta',195=>'Gamma',196=>'Delta',197=>'Epsilon',198=>'Zeta',199=>'Eta',200=>'Theta',201=>'Iota',202=>'Kappa',203=>'Lambda',204=>'Mu',205=>'Nu',206=>'Xi',207=>'Omicron',208=>'Pi',209=>'Rho',210=>'.notdef',211=>'Sigma',212=>'Tau',213=>'Upsilon',214=>'Phi',215=>'Chi',216=>'Psi',217=>'Omega',218=>'Iotadieresis',219=>'Upsilondieresis',220=>'alphatonos',221=>'epsilontonos',222=>'etatonos',223=>'iotatonos',224=>'upsilondieresistonos',225=>'alpha',226=>'beta',227=>'gamma',228=>'delta',229=>'epsilon',230=>'zeta',231=>'eta',232=>'theta',233=>'iota',234=>'kappa',235=>'lambda',236=>'mu',237=>'nu',238=>'xi',239=>'omicron',240=>'pi',241=>'rho',242=>'sigma1',243=>'sigma',244=>'tau',245=>'upsilon',246=>'phi',247=>'chi',248=>'psi',249=>'omega',250=>'iotadieresis',251=>'upsilondieresis',252=>'omicrontonos',253=>'upsilontonos',254=>'omegatonos',255=>'.notdef'),
+
+// encoding map for: iso-8859-9
+'iso-8859-9' => array(0=>'.notdef',1=>'.notdef',2=>'.notdef',3=>'.notdef',4=>'.notdef',5=>'.notdef',6=>'.notdef',7=>'.notdef',8=>'.notdef',9=>'.notdef',10=>'.notdef',11=>'.notdef',12=>'.notdef',13=>'.notdef',14=>'.notdef',15=>'.notdef',16=>'.notdef',17=>'.notdef',18=>'.notdef',19=>'.notdef',20=>'.notdef',21=>'.notdef',22=>'.notdef',23=>'.notdef',24=>'.notdef',25=>'.notdef',26=>'.notdef',27=>'.notdef',28=>'.notdef',29=>'.notdef',30=>'.notdef',31=>'.notdef',32=>'space',33=>'exclam',34=>'quotedbl',35=>'numbersign',36=>'dollar',37=>'percent',38=>'ampersand',39=>'quotesingle',40=>'parenleft',41=>'parenright',42=>'asterisk',43=>'plus',44=>'comma',45=>'hyphen',46=>'period',47=>'slash',48=>'zero',49=>'one',50=>'two',51=>'three',52=>'four',53=>'five',54=>'six',55=>'seven',56=>'eight',57=>'nine',58=>'colon',59=>'semicolon',60=>'less',61=>'equal',62=>'greater',63=>'question',64=>'at',65=>'A',66=>'B',67=>'C',68=>'D',69=>'E',70=>'F',71=>'G',72=>'H',73=>'I',74=>'J',75=>'K',76=>'L',77=>'M',78=>'N',79=>'O',80=>'P',81=>'Q',82=>'R',83=>'S',84=>'T',85=>'U',86=>'V',87=>'W',88=>'X',89=>'Y',90=>'Z',91=>'bracketleft',92=>'backslash',93=>'bracketright',94=>'asciicircum',95=>'underscore',96=>'grave',97=>'a',98=>'b',99=>'c',100=>'d',101=>'e',102=>'f',103=>'g',104=>'h',105=>'i',106=>'j',107=>'k',108=>'l',109=>'m',110=>'n',111=>'o',112=>'p',113=>'q',114=>'r',115=>'s',116=>'t',117=>'u',118=>'v',119=>'w',120=>'x',121=>'y',122=>'z',123=>'braceleft',124=>'bar',125=>'braceright',126=>'asciitilde',127=>'.notdef',128=>'.notdef',129=>'.notdef',130=>'.notdef',131=>'.notdef',132=>'.notdef',133=>'.notdef',134=>'.notdef',135=>'.notdef',136=>'.notdef',137=>'.notdef',138=>'.notdef',139=>'.notdef',140=>'.notdef',141=>'.notdef',142=>'.notdef',143=>'.notdef',144=>'.notdef',145=>'.notdef',146=>'.notdef',147=>'.notdef',148=>'.notdef',149=>'.notdef',150=>'.notdef',151=>'.notdef',152=>'.notdef',153=>'.notdef',154=>'.notdef',155=>'.notdef',156=>'.notdef',157=>'.notdef',158=>'.notdef',159=>'.notdef',160=>'space',161=>'exclamdown',162=>'cent',163=>'sterling',164=>'currency',165=>'yen',166=>'brokenbar',167=>'section',168=>'dieresis',169=>'copyright',170=>'ordfeminine',171=>'guillemotleft',172=>'logicalnot',173=>'hyphen',174=>'registered',175=>'macron',176=>'degree',177=>'plusminus',178=>'twosuperior',179=>'threesuperior',180=>'acute',181=>'mu',182=>'paragraph',183=>'periodcentered',184=>'cedilla',185=>'onesuperior',186=>'ordmasculine',187=>'guillemotright',188=>'onequarter',189=>'onehalf',190=>'threequarters',191=>'questiondown',192=>'Agrave',193=>'Aacute',194=>'Acircumflex',195=>'Atilde',196=>'Adieresis',197=>'Aring',198=>'AE',199=>'Ccedilla',200=>'Egrave',201=>'Eacute',202=>'Ecircumflex',203=>'Edieresis',204=>'Igrave',205=>'Iacute',206=>'Icircumflex',207=>'Idieresis',208=>'Gbreve',209=>'Ntilde',210=>'Ograve',211=>'Oacute',212=>'Ocircumflex',213=>'Otilde',214=>'Odieresis',215=>'multiply',216=>'Oslash',217=>'Ugrave',218=>'Uacute',219=>'Ucircumflex',220=>'Udieresis',221=>'Idotaccent',222=>'Scedilla',223=>'germandbls',224=>'agrave',225=>'aacute',226=>'acircumflex',227=>'atilde',228=>'adieresis',229=>'aring',230=>'ae',231=>'ccedilla',232=>'egrave',233=>'eacute',234=>'ecircumflex',235=>'edieresis',236=>'igrave',237=>'iacute',238=>'icircumflex',239=>'idieresis',240=>'gbreve',241=>'ntilde',242=>'ograve',243=>'oacute',244=>'ocircumflex',245=>'otilde',246=>'odieresis',247=>'divide',248=>'oslash',249=>'ugrave',250=>'uacute',251=>'ucircumflex',252=>'udieresis',253=>'dotlessi',254=>'scedilla',255=>'ydieresis'),
+
+// encoding map for: iso-8859-11
+'iso-8859-11' => array(0=>'.notdef',1=>'.notdef',2=>'.notdef',3=>'.notdef',4=>'.notdef',5=>'.notdef',6=>'.notdef',7=>'.notdef',8=>'.notdef',9=>'.notdef',10=>'.notdef',11=>'.notdef',12=>'.notdef',13=>'.notdef',14=>'.notdef',15=>'.notdef',16=>'.notdef',17=>'.notdef',18=>'.notdef',19=>'.notdef',20=>'.notdef',21=>'.notdef',22=>'.notdef',23=>'.notdef',24=>'.notdef',25=>'.notdef',26=>'.notdef',27=>'.notdef',28=>'.notdef',29=>'.notdef',30=>'.notdef',31=>'.notdef',32=>'space',33=>'exclam',34=>'quotedbl',35=>'numbersign',36=>'dollar',37=>'percent',38=>'ampersand',39=>'quotesingle',40=>'parenleft',41=>'parenright',42=>'asterisk',43=>'plus',44=>'comma',45=>'hyphen',46=>'period',47=>'slash',48=>'zero',49=>'one',50=>'two',51=>'three',52=>'four',53=>'five',54=>'six',55=>'seven',56=>'eight',57=>'nine',58=>'colon',59=>'semicolon',60=>'less',61=>'equal',62=>'greater',63=>'question',64=>'at',65=>'A',66=>'B',67=>'C',68=>'D',69=>'E',70=>'F',71=>'G',72=>'H',73=>'I',74=>'J',75=>'K',76=>'L',77=>'M',78=>'N',79=>'O',80=>'P',81=>'Q',82=>'R',83=>'S',84=>'T',85=>'U',86=>'V',87=>'W',88=>'X',89=>'Y',90=>'Z',91=>'bracketleft',92=>'backslash',93=>'bracketright',94=>'asciicircum',95=>'underscore',96=>'grave',97=>'a',98=>'b',99=>'c',100=>'d',101=>'e',102=>'f',103=>'g',104=>'h',105=>'i',106=>'j',107=>'k',108=>'l',109=>'m',110=>'n',111=>'o',112=>'p',113=>'q',114=>'r',115=>'s',116=>'t',117=>'u',118=>'v',119=>'w',120=>'x',121=>'y',122=>'z',123=>'braceleft',124=>'bar',125=>'braceright',126=>'asciitilde',127=>'.notdef',128=>'.notdef',129=>'.notdef',130=>'.notdef',131=>'.notdef',132=>'.notdef',133=>'.notdef',134=>'.notdef',135=>'.notdef',136=>'.notdef',137=>'.notdef',138=>'.notdef',139=>'.notdef',140=>'.notdef',141=>'.notdef',142=>'.notdef',143=>'.notdef',144=>'.notdef',145=>'.notdef',146=>'.notdef',147=>'.notdef',148=>'.notdef',149=>'.notdef',150=>'.notdef',151=>'.notdef',152=>'.notdef',153=>'.notdef',154=>'.notdef',155=>'.notdef',156=>'.notdef',157=>'.notdef',158=>'.notdef',159=>'.notdef',160=>'space',161=>'kokaithai',162=>'khokhaithai',163=>'khokhuatthai',164=>'khokhwaithai',165=>'khokhonthai',166=>'khorakhangthai',167=>'ngonguthai',168=>'chochanthai',169=>'chochingthai',170=>'chochangthai',171=>'sosothai',172=>'chochoethai',173=>'yoyingthai',174=>'dochadathai',175=>'topatakthai',176=>'thothanthai',177=>'thonangmonthothai',178=>'thophuthaothai',179=>'nonenthai',180=>'dodekthai',181=>'totaothai',182=>'thothungthai',183=>'thothahanthai',184=>'thothongthai',185=>'nonuthai',186=>'bobaimaithai',187=>'poplathai',188=>'phophungthai',189=>'fofathai',190=>'phophanthai',191=>'fofanthai',192=>'phosamphaothai',193=>'momathai',194=>'yoyakthai',195=>'roruathai',196=>'ruthai',197=>'lolingthai',198=>'luthai',199=>'wowaenthai',200=>'sosalathai',201=>'sorusithai',202=>'sosuathai',203=>'hohipthai',204=>'lochulathai',205=>'oangthai',206=>'honokhukthai',207=>'paiyannoithai',208=>'saraathai',209=>'maihanakatthai',210=>'saraaathai',211=>'saraamthai',212=>'saraithai',213=>'saraiithai',214=>'sarauethai',215=>'saraueethai',216=>'sarauthai',217=>'sarauuthai',218=>'phinthuthai',219=>'.notdef',220=>'.notdef',221=>'.notdef',222=>'.notdef',223=>'bahtthai',224=>'saraethai',225=>'saraaethai',226=>'saraothai',227=>'saraaimaimuanthai',228=>'saraaimaimalaithai',229=>'lakkhangyaothai',230=>'maiyamokthai',231=>'maitaikhuthai',232=>'maiekthai',233=>'maithothai',234=>'maitrithai',235=>'maichattawathai',236=>'thanthakhatthai',237=>'nikhahitthai',238=>'yamakkanthai',239=>'fongmanthai',240=>'zerothai',241=>'onethai',242=>'twothai',243=>'threethai',244=>'fourthai',245=>'fivethai',246=>'sixthai',247=>'seventhai',248=>'eightthai',249=>'ninethai',250=>'angkhankhuthai',251=>'khomutthai',252=>'.notdef',253=>'.notdef',254=>'.notdef',255=>'.notdef'),
+
+// encoding map for: iso-8859-15
+'iso-8859-15' => array(0=>'.notdef',1=>'.notdef',2=>'.notdef',3=>'.notdef',4=>'.notdef',5=>'.notdef',6=>'.notdef',7=>'.notdef',8=>'.notdef',9=>'.notdef',10=>'.notdef',11=>'.notdef',12=>'.notdef',13=>'.notdef',14=>'.notdef',15=>'.notdef',16=>'.notdef',17=>'.notdef',18=>'.notdef',19=>'.notdef',20=>'.notdef',21=>'.notdef',22=>'.notdef',23=>'.notdef',24=>'.notdef',25=>'.notdef',26=>'.notdef',27=>'.notdef',28=>'.notdef',29=>'.notdef',30=>'.notdef',31=>'.notdef',32=>'space',33=>'exclam',34=>'quotedbl',35=>'numbersign',36=>'dollar',37=>'percent',38=>'ampersand',39=>'quotesingle',40=>'parenleft',41=>'parenright',42=>'asterisk',43=>'plus',44=>'comma',45=>'hyphen',46=>'period',47=>'slash',48=>'zero',49=>'one',50=>'two',51=>'three',52=>'four',53=>'five',54=>'six',55=>'seven',56=>'eight',57=>'nine',58=>'colon',59=>'semicolon',60=>'less',61=>'equal',62=>'greater',63=>'question',64=>'at',65=>'A',66=>'B',67=>'C',68=>'D',69=>'E',70=>'F',71=>'G',72=>'H',73=>'I',74=>'J',75=>'K',76=>'L',77=>'M',78=>'N',79=>'O',80=>'P',81=>'Q',82=>'R',83=>'S',84=>'T',85=>'U',86=>'V',87=>'W',88=>'X',89=>'Y',90=>'Z',91=>'bracketleft',92=>'backslash',93=>'bracketright',94=>'asciicircum',95=>'underscore',96=>'grave',97=>'a',98=>'b',99=>'c',100=>'d',101=>'e',102=>'f',103=>'g',104=>'h',105=>'i',106=>'j',107=>'k',108=>'l',109=>'m',110=>'n',111=>'o',112=>'p',113=>'q',114=>'r',115=>'s',116=>'t',117=>'u',118=>'v',119=>'w',120=>'x',121=>'y',122=>'z',123=>'braceleft',124=>'bar',125=>'braceright',126=>'asciitilde',127=>'.notdef',128=>'.notdef',129=>'.notdef',130=>'.notdef',131=>'.notdef',132=>'.notdef',133=>'.notdef',134=>'.notdef',135=>'.notdef',136=>'.notdef',137=>'.notdef',138=>'.notdef',139=>'.notdef',140=>'.notdef',141=>'.notdef',142=>'.notdef',143=>'.notdef',144=>'.notdef',145=>'.notdef',146=>'.notdef',147=>'.notdef',148=>'.notdef',149=>'.notdef',150=>'.notdef',151=>'.notdef',152=>'.notdef',153=>'.notdef',154=>'.notdef',155=>'.notdef',156=>'.notdef',157=>'.notdef',158=>'.notdef',159=>'.notdef',160=>'space',161=>'exclamdown',162=>'cent',163=>'sterling',164=>'Euro',165=>'yen',166=>'Scaron',167=>'section',168=>'scaron',169=>'copyright',170=>'ordfeminine',171=>'guillemotleft',172=>'logicalnot',173=>'hyphen',174=>'registered',175=>'macron',176=>'degree',177=>'plusminus',178=>'twosuperior',179=>'threesuperior',180=>'Zcaron',181=>'mu',182=>'paragraph',183=>'periodcentered',184=>'zcaron',185=>'onesuperior',186=>'ordmasculine',187=>'guillemotright',188=>'OE',189=>'oe',190=>'Ydieresis',191=>'questiondown',192=>'Agrave',193=>'Aacute',194=>'Acircumflex',195=>'Atilde',196=>'Adieresis',197=>'Aring',198=>'AE',199=>'Ccedilla',200=>'Egrave',201=>'Eacute',202=>'Ecircumflex',203=>'Edieresis',204=>'Igrave',205=>'Iacute',206=>'Icircumflex',207=>'Idieresis',208=>'Eth',209=>'Ntilde',210=>'Ograve',211=>'Oacute',212=>'Ocircumflex',213=>'Otilde',214=>'Odieresis',215=>'multiply',216=>'Oslash',217=>'Ugrave',218=>'Uacute',219=>'Ucircumflex',220=>'Udieresis',221=>'Yacute',222=>'Thorn',223=>'germandbls',224=>'agrave',225=>'aacute',226=>'acircumflex',227=>'atilde',228=>'adieresis',229=>'aring',230=>'ae',231=>'ccedilla',232=>'egrave',233=>'eacute',234=>'ecircumflex',235=>'edieresis',236=>'igrave',237=>'iacute',238=>'icircumflex',239=>'idieresis',240=>'eth',241=>'ntilde',242=>'ograve',243=>'oacute',244=>'ocircumflex',245=>'otilde',246=>'odieresis',247=>'divide',248=>'oslash',249=>'ugrave',250=>'uacute',251=>'ucircumflex',252=>'udieresis',253=>'yacute',254=>'thorn',255=>'ydieresis'),
+
+// encoding map for: iso-8859-16
+'iso-8859-16' => array(0=>'.notdef',1=>'.notdef',2=>'.notdef',3=>'.notdef',4=>'.notdef',5=>'.notdef',6=>'.notdef',7=>'.notdef',8=>'.notdef',9=>'.notdef',10=>'.notdef',11=>'.notdef',12=>'.notdef',13=>'.notdef',14=>'.notdef',15=>'.notdef',16=>'.notdef',17=>'.notdef',18=>'.notdef',19=>'.notdef',20=>'.notdef',21=>'.notdef',22=>'.notdef',23=>'.notdef',24=>'.notdef',25=>'.notdef',26=>'.notdef',27=>'.notdef',28=>'.notdef',29=>'.notdef',30=>'.notdef',31=>'.notdef',32=>'space',33=>'exclam',34=>'quotedbl',35=>'numbersign',36=>'dollar',37=>'percent',38=>'ampersand',39=>'quotesingle',40=>'parenleft',41=>'parenright',42=>'asterisk',43=>'plus',44=>'comma',45=>'hyphen',46=>'period',47=>'slash',48=>'zero',49=>'one',50=>'two',51=>'three',52=>'four',53=>'five',54=>'six',55=>'seven',56=>'eight',57=>'nine',58=>'colon',59=>'semicolon',60=>'less',61=>'equal',62=>'greater',63=>'question',64=>'at',65=>'A',66=>'B',67=>'C',68=>'D',69=>'E',70=>'F',71=>'G',72=>'H',73=>'I',74=>'J',75=>'K',76=>'L',77=>'M',78=>'N',79=>'O',80=>'P',81=>'Q',82=>'R',83=>'S',84=>'T',85=>'U',86=>'V',87=>'W',88=>'X',89=>'Y',90=>'Z',91=>'bracketleft',92=>'backslash',93=>'bracketright',94=>'asciicircum',95=>'underscore',96=>'grave',97=>'a',98=>'b',99=>'c',100=>'d',101=>'e',102=>'f',103=>'g',104=>'h',105=>'i',106=>'j',107=>'k',108=>'l',109=>'m',110=>'n',111=>'o',112=>'p',113=>'q',114=>'r',115=>'s',116=>'t',117=>'u',118=>'v',119=>'w',120=>'x',121=>'y',122=>'z',123=>'braceleft',124=>'bar',125=>'braceright',126=>'asciitilde',127=>'.notdef',128=>'.notdef',129=>'.notdef',130=>'.notdef',131=>'.notdef',132=>'.notdef',133=>'.notdef',134=>'.notdef',135=>'.notdef',136=>'.notdef',137=>'.notdef',138=>'.notdef',139=>'.notdef',140=>'.notdef',141=>'.notdef',142=>'.notdef',143=>'.notdef',144=>'.notdef',145=>'.notdef',146=>'.notdef',147=>'.notdef',148=>'.notdef',149=>'.notdef',150=>'.notdef',151=>'.notdef',152=>'.notdef',153=>'.notdef',154=>'.notdef',155=>'.notdef',156=>'.notdef',157=>'.notdef',158=>'.notdef',159=>'.notdef',160=>'space',161=>'Aogonek',162=>'aogonek',163=>'Lslash',164=>'Euro',165=>'quotedblbase',166=>'Scaron',167=>'section',168=>'scaron',169=>'copyright',170=>'Scommaaccent',171=>'guillemotleft',172=>'Zacute',173=>'hyphen',174=>'zacute',175=>'Zdotaccent',176=>'degree',177=>'plusminus',178=>'Ccaron',179=>'lslash',180=>'Zcaron',181=>'quotedblright',182=>'paragraph',183=>'periodcentered',184=>'zcaron',185=>'ccaron',186=>'scommaaccent',187=>'guillemotright',188=>'OE',189=>'oe',190=>'Ydieresis',191=>'zdotaccent',192=>'Agrave',193=>'Aacute',194=>'Acircumflex',195=>'Abreve',196=>'Adieresis',197=>'Cacute',198=>'AE',199=>'Ccedilla',200=>'Egrave',201=>'Eacute',202=>'Ecircumflex',203=>'Edieresis',204=>'Igrave',205=>'Iacute',206=>'Icircumflex',207=>'Idieresis',208=>'Dcroat',209=>'Nacute',210=>'Ograve',211=>'Oacute',212=>'Ocircumflex',213=>'Ohungarumlaut',214=>'Odieresis',215=>'Sacute',216=>'Uhungarumlaut',217=>'Ugrave',218=>'Uacute',219=>'Ucircumflex',220=>'Udieresis',221=>'Eogonek',222=>'Tcommaaccent',223=>'germandbls',224=>'agrave',225=>'aacute',226=>'acircumflex',227=>'abreve',228=>'adieresis',229=>'cacute',230=>'ae',231=>'ccedilla',232=>'egrave',233=>'eacute',234=>'ecircumflex',235=>'edieresis',236=>'igrave',237=>'iacute',238=>'icircumflex',239=>'idieresis',240=>'dcroat',241=>'nacute',242=>'ograve',243=>'oacute',244=>'ocircumflex',245=>'ohungarumlaut',246=>'odieresis',247=>'sacute',248=>'uhungarumlaut',249=>'ugrave',250=>'uacute',251=>'ucircumflex',252=>'udieresis',253=>'eogonek',254=>'tcommaaccent',255=>'ydieresis'),
+
+// encoding map for: koi8-r
+'koi8-r' => array(0=>'.notdef',1=>'.notdef',2=>'.notdef',3=>'.notdef',4=>'.notdef',5=>'.notdef',6=>'.notdef',7=>'.notdef',8=>'.notdef',9=>'.notdef',10=>'.notdef',11=>'.notdef',12=>'.notdef',13=>'.notdef',14=>'.notdef',15=>'.notdef',16=>'.notdef',17=>'.notdef',18=>'.notdef',19=>'.notdef',20=>'.notdef',21=>'.notdef',22=>'.notdef',23=>'.notdef',24=>'.notdef',25=>'.notdef',26=>'.notdef',27=>'.notdef',28=>'.notdef',29=>'.notdef',30=>'.notdef',31=>'.notdef',32=>'space',33=>'exclam',34=>'quotedbl',35=>'numbersign',36=>'dollar',37=>'percent',38=>'ampersand',39=>'quotesingle',40=>'parenleft',41=>'parenright',42=>'asterisk',43=>'plus',44=>'comma',45=>'hyphen',46=>'period',47=>'slash',48=>'zero',49=>'one',50=>'two',51=>'three',52=>'four',53=>'five',54=>'six',55=>'seven',56=>'eight',57=>'nine',58=>'colon',59=>'semicolon',60=>'less',61=>'equal',62=>'greater',63=>'question',64=>'at',65=>'A',66=>'B',67=>'C',68=>'D',69=>'E',70=>'F',71=>'G',72=>'H',73=>'I',74=>'J',75=>'K',76=>'L',77=>'M',78=>'N',79=>'O',80=>'P',81=>'Q',82=>'R',83=>'S',84=>'T',85=>'U',86=>'V',87=>'W',88=>'X',89=>'Y',90=>'Z',91=>'bracketleft',92=>'backslash',93=>'bracketright',94=>'asciicircum',95=>'underscore',96=>'grave',97=>'a',98=>'b',99=>'c',100=>'d',101=>'e',102=>'f',103=>'g',104=>'h',105=>'i',106=>'j',107=>'k',108=>'l',109=>'m',110=>'n',111=>'o',112=>'p',113=>'q',114=>'r',115=>'s',116=>'t',117=>'u',118=>'v',119=>'w',120=>'x',121=>'y',122=>'z',123=>'braceleft',124=>'bar',125=>'braceright',126=>'asciitilde',127=>'.notdef',128=>'SF100000',129=>'SF110000',130=>'SF010000',131=>'SF030000',132=>'SF020000',133=>'SF040000',134=>'SF080000',135=>'SF090000',136=>'SF060000',137=>'SF070000',138=>'SF050000',139=>'upblock',140=>'dnblock',141=>'block',142=>'lfblock',143=>'rtblock',144=>'ltshade',145=>'shade',146=>'dkshade',147=>'integraltp',148=>'filledbox',149=>'periodcentered',150=>'radical',151=>'approxequal',152=>'lessequal',153=>'greaterequal',154=>'space',155=>'integralbt',156=>'degree',157=>'twosuperior',158=>'periodcentered',159=>'divide',160=>'SF430000',161=>'SF240000',162=>'SF510000',163=>'afii10071',164=>'SF520000',165=>'SF390000',166=>'SF220000',167=>'SF210000',168=>'SF250000',169=>'SF500000',170=>'SF490000',171=>'SF380000',172=>'SF280000',173=>'SF270000',174=>'SF260000',175=>'SF360000',176=>'SF370000',177=>'SF420000',178=>'SF190000',179=>'afii10023',180=>'SF200000',181=>'SF230000',182=>'SF470000',183=>'SF480000',184=>'SF410000',185=>'SF450000',186=>'SF460000',187=>'SF400000',188=>'SF540000',189=>'SF530000',190=>'SF440000',191=>'copyright',192=>'afii10096',193=>'afii10065',194=>'afii10066',195=>'afii10088',196=>'afii10069',197=>'afii10070',198=>'afii10086',199=>'afii10068',200=>'afii10087',201=>'afii10074',202=>'afii10075',203=>'afii10076',204=>'afii10077',205=>'afii10078',206=>'afii10079',207=>'afii10080',208=>'afii10081',209=>'afii10097',210=>'afii10082',211=>'afii10083',212=>'afii10084',213=>'afii10085',214=>'afii10072',215=>'afii10067',216=>'afii10094',217=>'afii10093',218=>'afii10073',219=>'afii10090',220=>'afii10095',221=>'afii10091',222=>'afii10089',223=>'afii10092',224=>'afii10048',225=>'afii10017',226=>'afii10018',227=>'afii10040',228=>'afii10021',229=>'afii10022',230=>'afii10038',231=>'afii10020',232=>'afii10039',233=>'afii10026',234=>'afii10027',235=>'afii10028',236=>'afii10029',237=>'afii10030',238=>'afii10031',239=>'afii10032',240=>'afii10033',241=>'afii10049',242=>'afii10034',243=>'afii10035',244=>'afii10036',245=>'afii10037',246=>'afii10024',247=>'afii10019',248=>'afii10046',249=>'afii10045',250=>'afii10025',251=>'afii10042',252=>'afii10047',253=>'afii10043',254=>'afii10041',255=>'afii10044'),
+
+// encoding map for: koi8-u
+'koi8-u' => array(0=>'.notdef',1=>'.notdef',2=>'.notdef',3=>'.notdef',4=>'.notdef',5=>'.notdef',6=>'.notdef',7=>'.notdef',8=>'.notdef',9=>'.notdef',10=>'.notdef',11=>'.notdef',12=>'.notdef',13=>'.notdef',14=>'.notdef',15=>'.notdef',16=>'.notdef',17=>'.notdef',18=>'.notdef',19=>'.notdef',20=>'.notdef',21=>'.notdef',22=>'.notdef',23=>'.notdef',24=>'.notdef',25=>'.notdef',26=>'.notdef',27=>'.notdef',28=>'.notdef',29=>'.notdef',30=>'.notdef',31=>'.notdef',32=>'space',33=>'exclam',34=>'quotedbl',35=>'numbersign',36=>'dollar',37=>'percent',38=>'ampersand',39=>'quotesingle',40=>'parenleft',41=>'parenright',42=>'asterisk',43=>'plus',44=>'comma',45=>'hyphen',46=>'period',47=>'slash',48=>'zero',49=>'one',50=>'two',51=>'three',52=>'four',53=>'five',54=>'six',55=>'seven',56=>'eight',57=>'nine',58=>'colon',59=>'semicolon',60=>'less',61=>'equal',62=>'greater',63=>'question',64=>'at',65=>'A',66=>'B',67=>'C',68=>'D',69=>'E',70=>'F',71=>'G',72=>'H',73=>'I',74=>'J',75=>'K',76=>'L',77=>'M',78=>'N',79=>'O',80=>'P',81=>'Q',82=>'R',83=>'S',84=>'T',85=>'U',86=>'V',87=>'W',88=>'X',89=>'Y',90=>'Z',91=>'bracketleft',92=>'backslash',93=>'bracketright',94=>'asciicircum',95=>'underscore',96=>'grave',97=>'a',98=>'b',99=>'c',100=>'d',101=>'e',102=>'f',103=>'g',104=>'h',105=>'i',106=>'j',107=>'k',108=>'l',109=>'m',110=>'n',111=>'o',112=>'p',113=>'q',114=>'r',115=>'s',116=>'t',117=>'u',118=>'v',119=>'w',120=>'x',121=>'y',122=>'z',123=>'braceleft',124=>'bar',125=>'braceright',126=>'asciitilde',127=>'.notdef',128=>'SF100000',129=>'SF110000',130=>'SF010000',131=>'SF030000',132=>'SF020000',133=>'SF040000',134=>'SF080000',135=>'SF090000',136=>'SF060000',137=>'SF070000',138=>'SF050000',139=>'upblock',140=>'dnblock',141=>'block',142=>'lfblock',143=>'rtblock',144=>'ltshade',145=>'shade',146=>'dkshade',147=>'integraltp',148=>'filledbox',149=>'bullet',150=>'radical',151=>'approxequal',152=>'lessequal',153=>'greaterequal',154=>'space',155=>'integralbt',156=>'degree',157=>'twosuperior',158=>'periodcentered',159=>'divide',160=>'SF430000',161=>'SF240000',162=>'SF510000',163=>'afii10071',164=>'afii10101',165=>'SF390000',166=>'afii10103',167=>'afii10104',168=>'SF250000',169=>'SF500000',170=>'SF490000',171=>'SF380000',172=>'SF280000',173=>'afii10098',174=>'SF260000',175=>'SF360000',176=>'SF370000',177=>'SF420000',178=>'SF190000',179=>'afii10023',180=>'afii10053',181=>'SF230000',182=>'afii10055',183=>'afii10056',184=>'SF410000',185=>'SF450000',186=>'SF460000',187=>'SF400000',188=>'SF540000',189=>'afii10050',190=>'SF440000',191=>'copyright',192=>'afii10096',193=>'afii10065',194=>'afii10066',195=>'afii10088',196=>'afii10069',197=>'afii10070',198=>'afii10086',199=>'afii10068',200=>'afii10087',201=>'afii10074',202=>'afii10075',203=>'afii10076',204=>'afii10077',205=>'afii10078',206=>'afii10079',207=>'afii10080',208=>'afii10081',209=>'afii10097',210=>'afii10082',211=>'afii10083',212=>'afii10084',213=>'afii10085',214=>'afii10072',215=>'afii10067',216=>'afii10094',217=>'afii10093',218=>'afii10073',219=>'afii10090',220=>'afii10095',221=>'afii10091',222=>'afii10089',223=>'afii10092',224=>'afii10048',225=>'afii10017',226=>'afii10018',227=>'afii10040',228=>'afii10021',229=>'afii10022',230=>'afii10038',231=>'afii10020',232=>'afii10039',233=>'afii10026',234=>'afii10027',235=>'afii10028',236=>'afii10029',237=>'afii10030',238=>'afii10031',239=>'afii10032',240=>'afii10033',241=>'afii10049',242=>'afii10034',243=>'afii10035',244=>'afii10036',245=>'afii10037',246=>'afii10024',247=>'afii10019',248=>'afii10046',249=>'afii10045',250=>'afii10025',251=>'afii10042',252=>'afii10047',253=>'afii10043',254=>'afii10041',255=>'afii10044'),
+
+// encoding map for: symbol
+'symbol' => array(0=>'.notdef',1=>'.notdef',2=>'.notdef',3=>'.notdef',4=>'.notdef',5=>'.notdef',6=>'.notdef',7=>'.notdef',8=>'.notdef',9=>'.notdef',10=>'.notdef',11=>'.notdef',12=>'.notdef',13=>'.notdef',14=>'.notdef',15=>'.notdef',16=>'.notdef',17=>'.notdef',18=>'.notdef',19=>'.notdef',20=>'.notdef',21=>'.notdef',22=>'.notdef',23=>'.notdef',24=>'.notdef',25=>'.notdef',26=>'.notdef',27=>'.notdef',28=>'.notdef',29=>'.notdef',30=>'.notdef',31=>'.notdef',32=>'space',33=>'exclam',34=>'universal',35=>'numbersign',36=>'existential',37=>'percent',38=>'ampersand',39=>'suchthat',40=>'parenleft',41=>'parenright',42=>'asteriskmath',43=>'plus',44=>'comma',45=>'minus',46=>'period',47=>'slash',48=>'zero',49=>'one',50=>'two',51=>'three',52=>'four',53=>'five',54=>'six',55=>'seven',56=>'eight',57=>'nine',58=>'colon',59=>'semicolon',60=>'less',61=>'equal',62=>'greater',63=>'question',64=>'congruent',65=>'Alpha',66=>'Beta',67=>'Chi',68=>'Delta',69=>'Epsilon',70=>'Phi',71=>'Gamma',72=>'Eta',73=>'Iota',74=>'theta1',75=>'Kappa',76=>'Lambda',77=>'Mu',78=>'Nu',79=>'Omicron',80=>'Pi',81=>'Theta',82=>'Rho',83=>'Sigma',84=>'Tau',85=>'Upsilon',86=>'sigma1',87=>'Omega',88=>'Xi',89=>'Psi',90=>'Zeta',91=>'bracketleft',92=>'therefore',93=>'bracketright',94=>'perpendicular',95=>'underscore',96=>'radicalex',97=>'alpha',98=>'beta',99=>'chi',100=>'delta',101=>'epsilon',102=>'phi',103=>'gamma',104=>'eta',105=>'iota',106=>'phi1',107=>'kappa',108=>'lambda',109=>'mu',110=>'nu',111=>'omicron',112=>'pi',113=>'theta',114=>'rho',115=>'sigma',116=>'tau',117=>'upsilon',118=>'omega1',119=>'omega',120=>'xi',121=>'psi',122=>'zeta',123=>'braceleft',124=>'bar',125=>'braceright',126=>'similar',127=>'.notdef',128=>'.notdef',129=>'.notdef',130=>'.notdef',131=>'.notdef',132=>'.notdef',133=>'.notdef',134=>'.notdef',135=>'.notdef',136=>'.notdef',137=>'.notdef',138=>'.notdef',139=>'.notdef',140=>'.notdef',141=>'.notdef',142=>'.notdef',143=>'.notdef',144=>'.notdef',145=>'.notdef',146=>'.notdef',147=>'.notdef',148=>'.notdef',149=>'.notdef',150=>'.notdef',151=>'.notdef',152=>'.notdef',153=>'.notdef',154=>'.notdef',155=>'.notdef',156=>'.notdef',157=>'.notdef',158=>'.notdef',159=>'.notdef',160=>'Euro',161=>'Upsilon1',162=>'minute',163=>'lessequal',164=>'fraction',165=>'infinity',166=>'florin',167=>'club',168=>'diamond',169=>'heart',170=>'spade',171=>'arrowboth',172=>'arrowleft',173=>'arrowup',174=>'arrowright',175=>'arrowdown',176=>'degree',177=>'plusminus',178=>'second',179=>'greaterequal',180=>'multiply',181=>'proportional',182=>'partialdiff',183=>'bullet',184=>'divide',185=>'notequal',186=>'equivalence',187=>'approxequal',188=>'ellipsis',189=>'arrowvertex',190=>'arrowhorizex',191=>'carriagereturn',192=>'aleph',193=>'Ifraktur',194=>'Rfraktur',195=>'weierstrass',196=>'circlemultiply',197=>'circleplus',198=>'emptyset',199=>'intersection',200=>'union',201=>'propersuperset',202=>'reflexsuperset',203=>'notsubset',204=>'propersubset',205=>'reflexsubset',206=>'element',207=>'notelement',208=>'angle',209=>'gradient',210=>'registerserif',211=>'copyrightserif',212=>'trademarkserif',213=>'product',214=>'radical',215=>'dotmath',216=>'logicalnot',217=>'logicaland',218=>'logicalor',219=>'arrowdblboth',220=>'arrowdblleft',221=>'arrowdblup',222=>'arrowdblright',223=>'arrowdbldown',224=>'lozenge',225=>'angleleft',226=>'registersans',227=>'copyrightsans',228=>'trademarksans',229=>'summation',230=>'parenlefttp',231=>'parenleftex',232=>'parenleftbt',233=>'bracketlefttp',234=>'bracketleftex',235=>'bracketleftbt',236=>'bracelefttp',237=>'braceleftmid',238=>'braceleftbt',239=>'braceex',240=>'.notdef',241=>'angleright',242=>'integral',243=>'integraltp',244=>'integralex',245=>'integralbt',246=>'parenrighttp',247=>'parenrightex',248=>'parenrightbt',249=>'bracketrighttp',250=>'bracketrightex',251=>'bracketrightbt',252=>'bracerighttp',253=>'bracerightmid',254=>'bracerightbt',255=>'.notdef',1226=>'registered',1227=>'copyright',1228=>'trademark')
+
+); // end of encoding maps
+
+/**
+ * ToUnicode map for Identity-H stream
+ * @public static
+ */
+public static $uni_identity_h = "/CIDInit /ProcSet findresource begin\n12 dict begin\nbegincmap\n/CIDSystemInfo << /Registry (Adobe) /Ordering (UCS) /Supplement 0 >> def\n/CMapName /Adobe-Identity-UCS def\n/CMapType 2 def\n/WMode 0 def\n1 begincodespacerange\n<0000> <FFFF>\nendcodespacerange\n100 beginbfrange\n<0000> <00ff> <0000>\n<0100> <01ff> <0100>\n<0200> <02ff> <0200>\n<0300> <03ff> <0300>\n<0400> <04ff> <0400>\n<0500> <05ff> <0500>\n<0600> <06ff> <0600>\n<0700> <07ff> <0700>\n<0800> <08ff> <0800>\n<0900> <09ff> <0900>\n<0a00> <0aff> <0a00>\n<0b00> <0bff> <0b00>\n<0c00> <0cff> <0c00>\n<0d00> <0dff> <0d00>\n<0e00> <0eff> <0e00>\n<0f00> <0fff> <0f00>\n<1000> <10ff> <1000>\n<1100> <11ff> <1100>\n<1200> <12ff> <1200>\n<1300> <13ff> <1300>\n<1400> <14ff> <1400>\n<1500> <15ff> <1500>\n<1600> <16ff> <1600>\n<1700> <17ff> <1700>\n<1800> <18ff> <1800>\n<1900> <19ff> <1900>\n<1a00> <1aff> <1a00>\n<1b00> <1bff> <1b00>\n<1c00> <1cff> <1c00>\n<1d00> <1dff> <1d00>\n<1e00> <1eff> <1e00>\n<1f00> <1fff> <1f00>\n<2000> <20ff> <2000>\n<2100> <21ff> <2100>\n<2200> <22ff> <2200>\n<2300> <23ff> <2300>\n<2400> <24ff> <2400>\n<2500> <25ff> <2500>\n<2600> <26ff> <2600>\n<2700> <27ff> <2700>\n<2800> <28ff> <2800>\n<2900> <29ff> <2900>\n<2a00> <2aff> <2a00>\n<2b00> <2bff> <2b00>\n<2c00> <2cff> <2c00>\n<2d00> <2dff> <2d00>\n<2e00> <2eff> <2e00>\n<2f00> <2fff> <2f00>\n<3000> <30ff> <3000>\n<3100> <31ff> <3100>\n<3200> <32ff> <3200>\n<3300> <33ff> <3300>\n<3400> <34ff> <3400>\n<3500> <35ff> <3500>\n<3600> <36ff> <3600>\n<3700> <37ff> <3700>\n<3800> <38ff> <3800>\n<3900> <39ff> <3900>\n<3a00> <3aff> <3a00>\n<3b00> <3bff> <3b00>\n<3c00> <3cff> <3c00>\n<3d00> <3dff> <3d00>\n<3e00> <3eff> <3e00>\n<3f00> <3fff> <3f00>\n<4000> <40ff> <4000>\n<4100> <41ff> <4100>\n<4200> <42ff> <4200>\n<4300> <43ff> <4300>\n<4400> <44ff> <4400>\n<4500> <45ff> <4500>\n<4600> <46ff> <4600>\n<4700> <47ff> <4700>\n<4800> <48ff> <4800>\n<4900> <49ff> <4900>\n<4a00> <4aff> <4a00>\n<4b00> <4bff> <4b00>\n<4c00> <4cff> <4c00>\n<4d00> <4dff> <4d00>\n<4e00> <4eff> <4e00>\n<4f00> <4fff> <4f00>\n<5000> <50ff> <5000>\n<5100> <51ff> <5100>\n<5200> <52ff> <5200>\n<5300> <53ff> <5300>\n<5400> <54ff> <5400>\n<5500> <55ff> <5500>\n<5600> <56ff> <5600>\n<5700> <57ff> <5700>\n<5800> <58ff> <5800>\n<5900> <59ff> <5900>\n<5a00> <5aff> <5a00>\n<5b00> <5bff> <5b00>\n<5c00> <5cff> <5c00>\n<5d00> <5dff> <5d00>\n<5e00> <5eff> <5e00>\n<5f00> <5fff> <5f00>\n<6000> <60ff> <6000>\n<6100> <61ff> <6100>\n<6200> <62ff> <6200>\n<6300> <63ff> <6300>\nendbfrange\n100 beginbfrange\n<6400> <64ff> <6400>\n<6500> <65ff> <6500>\n<6600> <66ff> <6600>\n<6700> <67ff> <6700>\n<6800> <68ff> <6800>\n<6900> <69ff> <6900>\n<6a00> <6aff> <6a00>\n<6b00> <6bff> <6b00>\n<6c00> <6cff> <6c00>\n<6d00> <6dff> <6d00>\n<6e00> <6eff> <6e00>\n<6f00> <6fff> <6f00>\n<7000> <70ff> <7000>\n<7100> <71ff> <7100>\n<7200> <72ff> <7200>\n<7300> <73ff> <7300>\n<7400> <74ff> <7400>\n<7500> <75ff> <7500>\n<7600> <76ff> <7600>\n<7700> <77ff> <7700>\n<7800> <78ff> <7800>\n<7900> <79ff> <7900>\n<7a00> <7aff> <7a00>\n<7b00> <7bff> <7b00>\n<7c00> <7cff> <7c00>\n<7d00> <7dff> <7d00>\n<7e00> <7eff> <7e00>\n<7f00> <7fff> <7f00>\n<8000> <80ff> <8000>\n<8100> <81ff> <8100>\n<8200> <82ff> <8200>\n<8300> <83ff> <8300>\n<8400> <84ff> <8400>\n<8500> <85ff> <8500>\n<8600> <86ff> <8600>\n<8700> <87ff> <8700>\n<8800> <88ff> <8800>\n<8900> <89ff> <8900>\n<8a00> <8aff> <8a00>\n<8b00> <8bff> <8b00>\n<8c00> <8cff> <8c00>\n<8d00> <8dff> <8d00>\n<8e00> <8eff> <8e00>\n<8f00> <8fff> <8f00>\n<9000> <90ff> <9000>\n<9100> <91ff> <9100>\n<9200> <92ff> <9200>\n<9300> <93ff> <9300>\n<9400> <94ff> <9400>\n<9500> <95ff> <9500>\n<9600> <96ff> <9600>\n<9700> <97ff> <9700>\n<9800> <98ff> <9800>\n<9900> <99ff> <9900>\n<9a00> <9aff> <9a00>\n<9b00> <9bff> <9b00>\n<9c00> <9cff> <9c00>\n<9d00> <9dff> <9d00>\n<9e00> <9eff> <9e00>\n<9f00> <9fff> <9f00>\n<a000> <a0ff> <a000>\n<a100> <a1ff> <a100>\n<a200> <a2ff> <a200>\n<a300> <a3ff> <a300>\n<a400> <a4ff> <a400>\n<a500> <a5ff> <a500>\n<a600> <a6ff> <a600>\n<a700> <a7ff> <a700>\n<a800> <a8ff> <a800>\n<a900> <a9ff> <a900>\n<aa00> <aaff> <aa00>\n<ab00> <abff> <ab00>\n<ac00> <acff> <ac00>\n<ad00> <adff> <ad00>\n<ae00> <aeff> <ae00>\n<af00> <afff> <af00>\n<b000> <b0ff> <b000>\n<b100> <b1ff> <b100>\n<b200> <b2ff> <b200>\n<b300> <b3ff> <b300>\n<b400> <b4ff> <b400>\n<b500> <b5ff> <b500>\n<b600> <b6ff> <b600>\n<b700> <b7ff> <b700>\n<b800> <b8ff> <b800>\n<b900> <b9ff> <b900>\n<ba00> <baff> <ba00>\n<bb00> <bbff> <bb00>\n<bc00> <bcff> <bc00>\n<bd00> <bdff> <bd00>\n<be00> <beff> <be00>\n<bf00> <bfff> <bf00>\n<c000> <c0ff> <c000>\n<c100> <c1ff> <c100>\n<c200> <c2ff> <c200>\n<c300> <c3ff> <c300>\n<c400> <c4ff> <c400>\n<c500> <c5ff> <c500>\n<c600> <c6ff> <c600>\n<c700> <c7ff> <c700>\nendbfrange\n56 beginbfrange\n<c800> <c8ff> <c800>\n<c900> <c9ff> <c900>\n<ca00> <caff> <ca00>\n<cb00> <cbff> <cb00>\n<cc00> <ccff> <cc00>\n<cd00> <cdff> <cd00>\n<ce00> <ceff> <ce00>\n<cf00> <cfff> <cf00>\n<d000> <d0ff> <d000>\n<d100> <d1ff> <d100>\n<d200> <d2ff> <d200>\n<d300> <d3ff> <d300>\n<d400> <d4ff> <d400>\n<d500> <d5ff> <d500>\n<d600> <d6ff> <d600>\n<d700> <d7ff> <d700>\n<d800> <d8ff> <d800>\n<d900> <d9ff> <d900>\n<da00> <daff> <da00>\n<db00> <dbff> <db00>\n<dc00> <dcff> <dc00>\n<dd00> <ddff> <dd00>\n<de00> <deff> <de00>\n<df00> <dfff> <df00>\n<e000> <e0ff> <e000>\n<e100> <e1ff> <e100>\n<e200> <e2ff> <e200>\n<e300> <e3ff> <e300>\n<e400> <e4ff> <e400>\n<e500> <e5ff> <e500>\n<e600> <e6ff> <e600>\n<e700> <e7ff> <e700>\n<e800> <e8ff> <e800>\n<e900> <e9ff> <e900>\n<ea00> <eaff> <ea00>\n<eb00> <ebff> <eb00>\n<ec00> <ecff> <ec00>\n<ed00> <edff> <ed00>\n<ee00> <eeff> <ee00>\n<ef00> <efff> <ef00>\n<f000> <f0ff> <f000>\n<f100> <f1ff> <f100>\n<f200> <f2ff> <f200>\n<f300> <f3ff> <f300>\n<f400> <f4ff> <f400>\n<f500> <f5ff> <f500>\n<f600> <f6ff> <f600>\n<f700> <f7ff> <f700>\n<f800> <f8ff> <f800>\n<f900> <f9ff> <f900>\n<fa00> <faff> <fa00>\n<fb00> <fbff> <fb00>\n<fc00> <fcff> <fc00>\n<fd00> <fdff> <fd00>\n<fe00> <feff> <fe00>\n<ff00> <ffff> <ff00>\nendbfrange\nendcmap\nCMapName currentdict /CMap defineresource pop\nend\nend";
+
+} // END OF TCPDF_FONT_DATA CLASS
+
+//============================================================+
+// END OF FILE
+//============================================================+
diff --git a/libraries/tcpdf/include/tcpdf_fonts.php b/libraries/tcpdf/include/tcpdf_fonts.php
new file mode 100644
index 0000000000..53f4eb5d60
--- /dev/null
+++ b/libraries/tcpdf/include/tcpdf_fonts.php
@@ -0,0 +1,2562 @@
+<?php
+//============================================================+
+// File name : tcpdf_fonts.php
+// Version : 1.0.009
+// Begin : 2008-01-01
+// Last Update : 2013-09-04
+// Author : Nicola Asuni - Tecnick.com LTD - www.tecnick.com - info@tecnick.com
+// License : GNU-LGPL v3 (http://www.gnu.org/copyleft/lesser.html)
+// -------------------------------------------------------------------
+// Copyright (C) 2008-2013 Nicola Asuni - Tecnick.com LTD
+//
+// This file is part of TCPDF software library.
+//
+// TCPDF 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 3 of the
+// License, or (at your option) any later version.
+//
+// TCPDF 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 TCPDF. If not, see <http://www.gnu.org/licenses/>.
+//
+// See LICENSE.TXT file for more information.
+// -------------------------------------------------------------------
+//
+// Description :Font methods for TCPDF library.
+//
+//============================================================+
+
+/**
+ * @file
+ * Unicode data and font methods for TCPDF library.
+ * @author Nicola Asuni
+ * @package com.tecnick.tcpdf
+ */
+
+/**
+ * @class TCPDF_FONTS
+ * Font methods for TCPDF library.
+ * @package com.tecnick.tcpdf
+ * @version 1.0.009
+ * @author Nicola Asuni - info@tecnick.com
+ */
+class TCPDF_FONTS {
+
+ /**
+ * Convert and add the selected TrueType or Type1 font to the fonts folder (that must be writeable).
+ * @param $fontfile (string) Font file (full path).
+ * @param $fonttype (string) Font type. Leave empty for autodetect mode. Valid values are: TrueTypeUnicode, TrueType, Type1, CID0JP = CID-0 Japanese, CID0KR = CID-0 Korean, CID0CS = CID-0 Chinese Simplified, CID0CT = CID-0 Chinese Traditional.
+ * @param $enc (string) Name of the encoding table to use. Leave empty for default mode. Omit this parameter for TrueType Unicode and symbolic fonts like Symbol or ZapfDingBats.
+ * @param $flags (int) Unsigned 32-bit integer containing flags specifying various characteristics of the font (PDF32000:2008 - 9.8.2 Font Descriptor Flags): +1 for fixed font; +4 for symbol or +32 for non-symbol; +64 for italic. Fixed and Italic mode are generally autodetected so you have to set it to 32 = non-symbolic font (default) or 4 = symbolic font.
+ * @param $outpath (string) Output path for generated font files (must be writeable by the web server). Leave empty for default font folder.
+ * @param $platid (int) Platform ID for CMAP table to extract (when building a Unicode font for Windows this value should be 3, for Macintosh should be 1).
+ * @param $encid (int) Encoding ID for CMAP table to extract (when building a Unicode font for Windows this value should be 1, for Macintosh should be 0). When Platform ID is 3, legal values for Encoding ID are: 0=Symbol, 1=Unicode, 2=ShiftJIS, 3=PRC, 4=Big5, 5=Wansung, 6=Johab, 7=Reserved, 8=Reserved, 9=Reserved, 10=UCS-4.
+ * @param $addcbbox (boolean) If true includes the character bounding box information on the php font file.
+ * @param $link (boolean) If true link to system font instead of copying the font data (not transportable) - Note: do not work with Type1 fonts.
+ * @return (string) TCPDF font name or boolean false in case of error.
+ * @author Nicola Asuni
+ * @since 5.9.123 (2010-09-30)
+ * @public static
+ */
+ public static function addTTFfont($fontfile, $fonttype='', $enc='', $flags=32, $outpath='', $platid=3, $encid=1, $addcbbox=false, $link=false) {
+ if (!file_exists($fontfile)) {
+ // Could not find file
+ return false;
+ }
+ // font metrics
+ $fmetric = array();
+ // build new font name for TCPDF compatibility
+ $font_path_parts = pathinfo($fontfile);
+ if (!isset($font_path_parts['filename'])) {
+ $font_path_parts['filename'] = substr($font_path_parts['basename'], 0, -(strlen($font_path_parts['extension']) + 1));
+ }
+ $font_name = strtolower($font_path_parts['filename']);
+ $font_name = preg_replace('/[^a-z0-9_]/', '', $font_name);
+ $search = array('bold', 'oblique', 'italic', 'regular');
+ $replace = array('b', 'i', 'i', '');
+ $font_name = str_replace($search, $replace, $font_name);
+ if (empty($font_name)) {
+ // set generic name
+ $font_name = 'tcpdffont';
+ }
+ // set output path
+ if (empty($outpath)) {
+ $outpath = self::_getfontpath();
+ }
+ // check if this font already exist
+ if (@file_exists($outpath.$font_name.'.php')) {
+ // this font already exist (delete it from fonts folder to rebuild it)
+ return $font_name;
+ }
+ $fmetric['file'] = $font_name;
+ $fmetric['ctg'] = $font_name.'.ctg.z';
+ // get font data
+ $font = file_get_contents($fontfile);
+ $fmetric['originalsize'] = strlen($font);
+ // autodetect font type
+ if (empty($fonttype)) {
+ if (TCPDF_STATIC::_getULONG($font, 0) == 0x10000) {
+ // True Type (Unicode or not)
+ $fonttype = 'TrueTypeUnicode';
+ } elseif (substr($font, 0, 4) == 'OTTO') {
+ // Open Type (Unicode or not)
+ //Unsupported font format: OpenType with CFF data
+ return false;
+ } else {
+ // Type 1
+ $fonttype = 'Type1';
+ }
+ }
+ // set font type
+ switch ($fonttype) {
+ case 'CID0CT':
+ case 'CID0CS':
+ case 'CID0KR':
+ case 'CID0JP': {
+ $fmetric['type'] = 'cidfont0';
+ break;
+ }
+ case 'Type1': {
+ $fmetric['type'] = 'Type1';
+ if (empty($enc) AND (($flags & 4) == 0)) {
+ $enc = 'cp1252';
+ }
+ break;
+ }
+ case 'TrueType': {
+ $fmetric['type'] = 'TrueType';
+ break;
+ }
+ case 'TrueTypeUnicode':
+ default: {
+ $fmetric['type'] = 'TrueTypeUnicode';
+ break;
+ }
+ }
+ // set encoding maps (if any)
+ $fmetric['enc'] = preg_replace('/[^A-Za-z0-9_\-]/', '', $enc);
+ $fmetric['diff'] = '';
+ if (($fmetric['type'] == 'TrueType') OR ($fmetric['type'] == 'Type1')) {
+ if (!empty($enc) AND ($enc != 'cp1252') AND isset(TCPDF_FONT_DATA::$encmap[$enc])) {
+ // build differences from reference encoding
+ $enc_ref = TCPDF_FONT_DATA::$encmap['cp1252'];
+ $enc_target = TCPDF_FONT_DATA::$encmap[$enc];
+ $last = 0;
+ for ($i = 32; $i <= 255; ++$i) {
+ if ($enc_target != $enc_ref[$i]) {
+ if ($i != ($last + 1)) {
+ $fmetric['diff'] .= $i.' ';
+ }
+ $last = $i;
+ $fmetric['diff'] .= '/'.$enc_target[$i].' ';
+ }
+ }
+ }
+ }
+ // parse the font by type
+ if ($fmetric['type'] == 'Type1') {
+ // ---------- TYPE 1 ----------
+ // read first segment
+ $a = unpack('Cmarker/Ctype/Vsize', substr($font, 0, 6));
+ if ($a['marker'] != 128) {
+ // Font file is not a valid binary Type1
+ return false;
+ }
+ $fmetric['size1'] = $a['size'];
+ $data = substr($font, 6, $fmetric['size1']);
+ // read second segment
+ $a = unpack('Cmarker/Ctype/Vsize', substr($font, (6 + $fmetric['size1']), 6));
+ if ($a['marker'] != 128) {
+ // Font file is not a valid binary Type1
+ return false;
+ }
+ $fmetric['size2'] = $a['size'];
+ $encrypted = substr($font, (12 + $fmetric['size1']), $fmetric['size2']);
+ $data .= $encrypted;
+ // store compressed font
+ $fmetric['file'] .= '.z';
+ $fp = fopen($outpath.$fmetric['file'], 'wb');
+ fwrite($fp, gzcompress($data));
+ fclose($fp);
+ // get font info
+ $fmetric['Flags'] = $flags;
+ preg_match ('#/FullName[\s]*\(([^\)]*)#', $font, $matches);
+ $fmetric['name'] = preg_replace('/[^a-zA-Z0-9_\-]/', '', $matches[1]);
+ preg_match('#/FontBBox[\s]*{([^}]*)#', $font, $matches);
+ $fmetric['bbox'] = trim($matches[1]);
+ $bv = explode(' ', $fmetric['bbox']);
+ $fmetric['Ascent'] = intval($bv[3]);
+ $fmetric['Descent'] = intval($bv[1]);
+ preg_match('#/ItalicAngle[\s]*([0-9\+\-]*)#', $font, $matches);
+ $fmetric['italicAngle'] = intval($matches[1]);
+ if ($fmetric['italicAngle'] != 0) {
+ $fmetric['Flags'] |= 64;
+ }
+ preg_match('#/UnderlinePosition[\s]*([0-9\+\-]*)#', $font, $matches);
+ $fmetric['underlinePosition'] = intval($matches[1]);
+ preg_match('#/UnderlineThickness[\s]*([0-9\+\-]*)#', $font, $matches);
+ $fmetric['underlineThickness'] = intval($matches[1]);
+ preg_match('#/isFixedPitch[\s]*([^\s]*)#', $font, $matches);
+ if ($matches[1] == 'true') {
+ $fmetric['Flags'] |= 1;
+ }
+ // get internal map
+ $imap = array();
+ if (preg_match_all('#dup[\s]([0-9]+)[\s]*/([^\s]*)[\s]put#sU', $font, $fmap, PREG_SET_ORDER) > 0) {
+ foreach ($fmap as $v) {
+ $imap[$v[2]] = $v[1];
+ }
+ }
+ // decrypt eexec encrypted part
+ $r = 55665; // eexec encryption constant
+ $c1 = 52845;
+ $c2 = 22719;
+ $elen = strlen($encrypted);
+ $eplain = '';
+ for ($i = 0; $i < $elen; ++$i) {
+ $chr = ord($encrypted[$i]);
+ $eplain .= chr($chr ^ ($r >> 8));
+ $r = ((($chr + $r) * $c1 + $c2) % 65536);
+ }
+ if (preg_match('#/ForceBold[\s]*([^\s]*)#', $eplain, $matches) > 0) {
+ if ($matches[1] == 'true') {
+ $fmetric['Flags'] |= 0x40000;
+ }
+ }
+ if (preg_match('#/StdVW[\s]*\[([^\]]*)#', $eplain, $matches) > 0) {
+ $fmetric['StemV'] = intval($matches[1]);
+ } else {
+ $fmetric['StemV'] = 70;
+ }
+ if (preg_match('#/StdHW[\s]*\[([^\]]*)#', $eplain, $matches) > 0) {
+ $fmetric['StemH'] = intval($matches[1]);
+ } else {
+ $fmetric['StemH'] = 30;
+ }
+ if (preg_match('#/BlueValues[\s]*\[([^\]]*)#', $eplain, $matches) > 0) {
+ $bv = explode(' ', $matches[1]);
+ if (count($bv) >= 6) {
+ $v1 = intval($bv[2]);
+ $v2 = intval($bv[4]);
+ if ($v1 <= $v2) {
+ $fmetric['XHeight'] = $v1;
+ $fmetric['CapHeight'] = $v2;
+ } else {
+ $fmetric['XHeight'] = $v2;
+ $fmetric['CapHeight'] = $v1;
+ }
+ } else {
+ $fmetric['XHeight'] = 450;
+ $fmetric['CapHeight'] = 700;
+ }
+ } else {
+ $fmetric['XHeight'] = 450;
+ $fmetric['CapHeight'] = 700;
+ }
+ // get the number of random bytes at the beginning of charstrings
+ if (preg_match('#/lenIV[\s]*([0-9]*)#', $eplain, $matches) > 0) {
+ $lenIV = intval($matches[1]);
+ } else {
+ $lenIV = 4;
+ }
+ $fmetric['Leading'] = 0;
+ // get charstring data
+ $eplain = substr($eplain, (strpos($eplain, '/CharStrings') + 1));
+ preg_match_all('#/([A-Za-z0-9\.]*)[\s][0-9]+[\s]RD[\s](.*)[\s]ND#sU', $eplain, $matches, PREG_SET_ORDER);
+ if (!empty($enc) AND isset(TCPDF_FONT_DATA::$encmap[$enc])) {
+ $enc_map = TCPDF_FONT_DATA::$encmap[$enc];
+ } else {
+ $enc_map = false;
+ }
+ $fmetric['cw'] = '';
+ $fmetric['MaxWidth'] = 0;
+ $cwidths = array();
+ foreach ($matches as $k => $v) {
+ $cid = 0;
+ if (isset($imap[$v[1]])) {
+ $cid = $imap[$v[1]];
+ } elseif ($enc_map !== false) {
+ $cid = array_search($v[1], $enc_map);
+ if ($cid === false) {
+ $cid = 0;
+ } elseif ($cid > 1000) {
+ $cid -= 1000;
+ }
+ }
+ // decrypt charstring encrypted part
+ $r = 4330; // charstring encryption constant
+ $c1 = 52845;
+ $c2 = 22719;
+ $cd = $v[2];
+ $clen = strlen($cd);
+ $ccom = array();
+ for ($i = 0; $i < $clen; ++$i) {
+ $chr = ord($cd[$i]);
+ $ccom[] = ($chr ^ ($r >> 8));
+ $r = ((($chr + $r) * $c1 + $c2) % 65536);
+ }
+ // decode numbers
+ $cdec = array();
+ $ck = 0;
+ $i = $lenIV;
+ while ($i < $clen) {
+ if ($ccom[$i] < 32) {
+ $cdec[$ck] = $ccom[$i];
+ if (($ck > 0) AND ($cdec[$ck] == 13)) {
+ // hsbw command: update width
+ $cwidths[$cid] = $cdec[($ck - 1)];
+ }
+ ++$i;
+ } elseif (($ccom[$i] >= 32) AND ($ccom[$i] <= 246)) {
+ $cdec[$ck] = ($ccom[$i] - 139);
+ ++$i;
+ } elseif (($ccom[$i] >= 247) AND ($ccom[$i] <= 250)) {
+ $cdec[$ck] = ((($ccom[$i] - 247) * 256) + $ccom[($i + 1)] + 108);
+ $i += 2;
+ } elseif (($ccom[$i] >= 251) AND ($ccom[$i] <= 254)) {
+ $cdec[$ck] = ((-($ccom[$i] - 251) * 256) - $ccom[($i + 1)] - 108);
+ $i += 2;
+ } elseif ($ccom[$i] == 255) {
+ $sval = chr($ccom[($i + 1)]).chr($ccom[($i + 2)]).chr($ccom[($i + 3)]).chr($ccom[($i + 4)]);
+ $vsval = unpack('li', $sval);
+ $cdec[$ck] = $vsval['i'];
+ $i += 5;
+ }
+ ++$ck;
+ }
+ } // end for each matches
+ $fmetric['MissingWidth'] = $cwidths[0];
+ $fmetric['MaxWidth'] = $fmetric['MissingWidth'];
+ $fmetric['AvgWidth'] = 0;
+ // set chars widths
+ for ($cid = 0; $cid <= 255; ++$cid) {
+ if (isset($cwidths[$cid])) {
+ if ($cwidths[$cid] > $fmetric['MaxWidth']) {
+ $fmetric['MaxWidth'] = $cwidths[$cid];
+ }
+ $fmetric['AvgWidth'] += $cwidths[$cid];
+ $fmetric['cw'] .= ','.$cid.'=>'.$cwidths[$cid];
+ } else {
+ $fmetric['cw'] .= ','.$cid.'=>'.$fmetric['MissingWidth'];
+ }
+ }
+ $fmetric['AvgWidth'] = round($fmetric['AvgWidth'] / count($cwidths));
+ } else {
+ // ---------- TRUE TYPE ----------
+ if ($fmetric['type'] != 'cidfont0') {
+ if ($link) {
+ // creates a symbolic link to the existing font
+ symlink($fontfile, $outpath.$fmetric['file']);
+ } else {
+ // store compressed font
+ $fmetric['file'] .= '.z';
+ $fp = fopen($outpath.$fmetric['file'], 'wb');
+ fwrite($fp, gzcompress($font));
+ fclose($fp);
+ }
+ }
+ $offset = 0; // offset position of the font data
+ if (TCPDF_STATIC::_getULONG($font, $offset) != 0x10000) {
+ // sfnt version must be 0x00010000 for TrueType version 1.0.
+ return false;
+ }
+ $offset += 4;
+ // get number of tables
+ $numTables = TCPDF_STATIC::_getUSHORT($font, $offset);
+ $offset += 2;
+ // skip searchRange, entrySelector and rangeShift
+ $offset += 6;
+ // tables array
+ $table = array();
+ // ---------- get tables ----------
+ for ($i = 0; $i < $numTables; ++$i) {
+ // get table info
+ $tag = substr($font, $offset, 4);
+ $offset += 4;
+ $table[$tag] = array();
+ $table[$tag]['checkSum'] = TCPDF_STATIC::_getULONG($font, $offset);
+ $offset += 4;
+ $table[$tag]['offset'] = TCPDF_STATIC::_getULONG($font, $offset);
+ $offset += 4;
+ $table[$tag]['length'] = TCPDF_STATIC::_getULONG($font, $offset);
+ $offset += 4;
+ }
+ // check magicNumber
+ $offset = $table['head']['offset'] + 12;
+ if (TCPDF_STATIC::_getULONG($font, $offset) != 0x5F0F3CF5) {
+ // magicNumber must be 0x5F0F3CF5
+ return false;
+ }
+ $offset += 4;
+ $offset += 2; // skip flags
+ // get FUnits
+ $fmetric['unitsPerEm'] = TCPDF_STATIC::_getUSHORT($font, $offset);
+ $offset += 2;
+ // units ratio constant
+ $urk = (1000 / $fmetric['unitsPerEm']);
+ $offset += 16; // skip created, modified
+ $xMin = round(TCPDF_STATIC::_getFWORD($font, $offset) * $urk);
+ $offset += 2;
+ $yMin = round(TCPDF_STATIC::_getFWORD($font, $offset) * $urk);
+ $offset += 2;
+ $xMax = round(TCPDF_STATIC::_getFWORD($font, $offset) * $urk);
+ $offset += 2;
+ $yMax = round(TCPDF_STATIC::_getFWORD($font, $offset) * $urk);
+ $offset += 2;
+ $fmetric['bbox'] = ''.$xMin.' '.$yMin.' '.$xMax.' '.$yMax.'';
+ $macStyle = TCPDF_STATIC::_getUSHORT($font, $offset);
+ $offset += 2;
+ // PDF font flags
+ $fmetric['Flags'] = $flags;
+ if (($macStyle & 2) == 2) {
+ // italic flag
+ $fmetric['Flags'] |= 64;
+ }
+ // get offset mode (indexToLocFormat : 0 = short, 1 = long)
+ $offset = $table['head']['offset'] + 50;
+ $short_offset = (TCPDF_STATIC::_getSHORT($font, $offset) == 0);
+ $offset += 2;
+ // get the offsets to the locations of the glyphs in the font, relative to the beginning of the glyphData table
+ $indexToLoc = array();
+ $offset = $table['loca']['offset'];
+ if ($short_offset) {
+ // short version
+ $tot_num_glyphs = floor($table['loca']['length'] / 2); // numGlyphs + 1
+ for ($i = 0; $i < $tot_num_glyphs; ++$i) {
+ $indexToLoc[$i] = TCPDF_STATIC::_getUSHORT($font, $offset) * 2;
+ $offset += 2;
+ }
+ } else {
+ // long version
+ $tot_num_glyphs = floor($table['loca']['length'] / 4); // numGlyphs + 1
+ for ($i = 0; $i < $tot_num_glyphs; ++$i) {
+ $indexToLoc[$i] = TCPDF_STATIC::_getULONG($font, $offset);
+ $offset += 4;
+ }
+ }
+ // get glyphs indexes of chars from cmap table
+ $offset = $table['cmap']['offset'] + 2;
+ $numEncodingTables = TCPDF_STATIC::_getUSHORT($font, $offset);
+ $offset += 2;
+ $encodingTables = array();
+ for ($i = 0; $i < $numEncodingTables; ++$i) {
+ $encodingTables[$i]['platformID'] = TCPDF_STATIC::_getUSHORT($font, $offset);
+ $offset += 2;
+ $encodingTables[$i]['encodingID'] = TCPDF_STATIC::_getUSHORT($font, $offset);
+ $offset += 2;
+ $encodingTables[$i]['offset'] = TCPDF_STATIC::_getULONG($font, $offset);
+ $offset += 4;
+ }
+ // ---------- get os/2 metrics ----------
+ $offset = $table['OS/2']['offset'];
+ $offset += 2; // skip version
+ // xAvgCharWidth
+ $fmetric['AvgWidth'] = round(TCPDF_STATIC::_getFWORD($font, $offset) * $urk);
+ $offset += 2;
+ // usWeightClass
+ $usWeightClass = round(TCPDF_STATIC::_getUFWORD($font, $offset) * $urk);
+ // estimate StemV and StemH (400 = usWeightClass for Normal - Regular font)
+ $fmetric['StemV'] = round((70 * $usWeightClass) / 400);
+ $fmetric['StemH'] = round((30 * $usWeightClass) / 400);
+ $offset += 2;
+ $offset += 2; // usWidthClass
+ $fsType = TCPDF_STATIC::_getSHORT($font, $offset);
+ $offset += 2;
+ if ($fsType == 2) {
+ // This Font cannot be modified, embedded or exchanged in any manner without first obtaining permission of the legal owner.
+ return false;
+ }
+ // ---------- get font name ----------
+ $fmetric['name'] = '';
+ $offset = $table['name']['offset'];
+ $offset += 2; // skip Format selector (=0).
+ // Number of NameRecords that follow n.
+ $numNameRecords = TCPDF_STATIC::_getUSHORT($font, $offset);
+ $offset += 2;
+ // Offset to start of string storage (from start of table).
+ $stringStorageOffset = TCPDF_STATIC::_getUSHORT($font, $offset);
+ $offset += 2;
+ for ($i = 0; $i < $numNameRecords; ++$i) {
+ $offset += 6; // skip Platform ID, Platform-specific encoding ID, Language ID.
+ // Name ID.
+ $nameID = TCPDF_STATIC::_getUSHORT($font, $offset);
+ $offset += 2;
+ if ($nameID == 6) {
+ // String length (in bytes).
+ $stringLength = TCPDF_STATIC::_getUSHORT($font, $offset);
+ $offset += 2;
+ // String offset from start of storage area (in bytes).
+ $stringOffset = TCPDF_STATIC::_getUSHORT($font, $offset);
+ $offset += 2;
+ $offset = ($table['name']['offset'] + $stringStorageOffset + $stringOffset);
+ $fmetric['name'] = substr($font, $offset, $stringLength);
+ $fmetric['name'] = preg_replace('/[^a-zA-Z0-9_\-]/', '', $fmetric['name']);
+ break;
+ } else {
+ $offset += 4; // skip String length, String offset
+ }
+ }
+ if (empty($fmetric['name'])) {
+ $fmetric['name'] = $font_name;
+ }
+ // ---------- get post data ----------
+ $offset = $table['post']['offset'];
+ $offset += 4; // skip Format Type
+ $fmetric['italicAngle'] = TCPDF_STATIC::_getFIXED($font, $offset);
+ $offset += 4;
+ $fmetric['underlinePosition'] = round(TCPDF_STATIC::_getFWORD($font, $offset) * $urk);
+ $offset += 2;
+ $fmetric['underlineThickness'] = round(TCPDF_STATIC::_getFWORD($font, $offset) * $urk);
+ $offset += 2;
+ $isFixedPitch = (TCPDF_STATIC::_getULONG($font, $offset) == 0) ? false : true;
+ $offset += 2;
+ if ($isFixedPitch) {
+ $fmetric['Flags'] |= 1;
+ }
+ // ---------- get hhea data ----------
+ $offset = $table['hhea']['offset'];
+ $offset += 4; // skip Table version number
+ // Ascender
+ $fmetric['Ascent'] = round(TCPDF_STATIC::_getFWORD($font, $offset) * $urk);
+ $offset += 2;
+ // Descender
+ $fmetric['Descent'] = round(TCPDF_STATIC::_getFWORD($font, $offset) * $urk);
+ $offset += 2;
+ // LineGap
+ $fmetric['Leading'] = round(TCPDF_STATIC::_getFWORD($font, $offset) * $urk);
+ $offset += 2;
+ // advanceWidthMax
+ $fmetric['MaxWidth'] = round(TCPDF_STATIC::_getUFWORD($font, $offset) * $urk);
+ $offset += 2;
+ $offset += 22; // skip some values
+ // get the number of hMetric entries in hmtx table
+ $numberOfHMetrics = TCPDF_STATIC::_getUSHORT($font, $offset);
+ // ---------- get maxp data ----------
+ $offset = $table['maxp']['offset'];
+ $offset += 4; // skip Table version number
+ // get the the number of glyphs in the font.
+ $numGlyphs = TCPDF_STATIC::_getUSHORT($font, $offset);
+ // ---------- get CIDToGIDMap ----------
+ $ctg = array();
+ foreach ($encodingTables as $enctable) {
+ // get only specified Platform ID and Encoding ID
+ if (($enctable['platformID'] == $platid) AND ($enctable['encodingID'] == $encid)) {
+ $offset = $table['cmap']['offset'] + $enctable['offset'];
+ $format = TCPDF_STATIC::_getUSHORT($font, $offset);
+ $offset += 2;
+ switch ($format) {
+ case 0: { // Format 0: Byte encoding table
+ $offset += 4; // skip length and version/language
+ for ($c = 0; $c < 256; ++$c) {
+ $g = TCPDF_STATIC::_getBYTE($font, $offset);
+ $ctg[$c] = $g;
+ ++$offset;
+ }
+ break;
+ }
+ case 2: { // Format 2: High-byte mapping through table
+ $offset += 4; // skip length and version/language
+ $numSubHeaders = 0;
+ for ($i = 0; $i < 256; ++$i) {
+ // Array that maps high bytes to subHeaders: value is subHeader index * 8.
+ $subHeaderKeys[$i] = (TCPDF_STATIC::_getUSHORT($font, $offset) / 8);
+ $offset += 2;
+ if ($numSubHeaders < $subHeaderKeys[$i]) {
+ $numSubHeaders = $subHeaderKeys[$i];
+ }
+ }
+ // the number of subHeaders is equal to the max of subHeaderKeys + 1
+ ++$numSubHeaders;
+ // read subHeader structures
+ $subHeaders = array();
+ $numGlyphIndexArray = 0;
+ for ($k = 0; $k < $numSubHeaders; ++$k) {
+ $subHeaders[$k]['firstCode'] = TCPDF_STATIC::_getUSHORT($font, $offset);
+ $offset += 2;
+ $subHeaders[$k]['entryCount'] = TCPDF_STATIC::_getUSHORT($font, $offset);
+ $offset += 2;
+ $subHeaders[$k]['idDelta'] = TCPDF_STATIC::_getUSHORT($font, $offset);
+ $offset += 2;
+ $subHeaders[$k]['idRangeOffset'] = TCPDF_STATIC::_getUSHORT($font, $offset);
+ $offset += 2;
+ $subHeaders[$k]['idRangeOffset'] -= (2 + (($numSubHeaders - $k - 1) * 8));
+ $subHeaders[$k]['idRangeOffset'] /= 2;
+ $numGlyphIndexArray += $subHeaders[$k]['entryCount'];
+ }
+ for ($k = 0; $k < $numGlyphIndexArray; ++$k) {
+ $glyphIndexArray[$k] = TCPDF_STATIC::_getUSHORT($font, $offset);
+ $offset += 2;
+ }
+ for ($i = 0; $i < 256; ++$i) {
+ $k = $subHeaderKeys[$i];
+ if ($k == 0) {
+ // one byte code
+ $c = $i;
+ $g = $glyphIndexArray[0];
+ $ctg[$c] = $g;
+ } else {
+ // two bytes code
+ $start_byte = $subHeaders[$k]['firstCode'];
+ $end_byte = $start_byte + $subHeaders[$k]['entryCount'];
+ for ($j = $start_byte; $j < $end_byte; ++$j) {
+ // combine high and low bytes
+ $c = (($i << 8) + $j);
+ $idRangeOffset = ($subHeaders[$k]['idRangeOffset'] + $j - $subHeaders[$k]['firstCode']);
+ $g = ($glyphIndexArray[$idRangeOffset] + $idDelta[$k]) % 65536;
+ if ($g < 0) {
+ $g = 0;
+ }
+ $ctg[$c] = $g;
+ }
+ }
+ }
+ break;
+ }
+ case 4: { // Format 4: Segment mapping to delta values
+ $length = TCPDF_STATIC::_getUSHORT($font, $offset);
+ $offset += 2;
+ $offset += 2; // skip version/language
+ $segCount = floor(TCPDF_STATIC::_getUSHORT($font, $offset) / 2);
+ $offset += 2;
+ $offset += 6; // skip searchRange, entrySelector, rangeShift
+ $endCount = array(); // array of end character codes for each segment
+ for ($k = 0; $k < $segCount; ++$k) {
+ $endCount[$k] = TCPDF_STATIC::_getUSHORT($font, $offset);
+ $offset += 2;
+ }
+ $offset += 2; // skip reservedPad
+ $startCount = array(); // array of start character codes for each segment
+ for ($k = 0; $k < $segCount; ++$k) {
+ $startCount[$k] = TCPDF_STATIC::_getUSHORT($font, $offset);
+ $offset += 2;
+ }
+ $idDelta = array(); // delta for all character codes in segment
+ for ($k = 0; $k < $segCount; ++$k) {
+ $idDelta[$k] = TCPDF_STATIC::_getUSHORT($font, $offset);
+ $offset += 2;
+ }
+ $idRangeOffset = array(); // Offsets into glyphIdArray or 0
+ for ($k = 0; $k < $segCount; ++$k) {
+ $idRangeOffset[$k] = TCPDF_STATIC::_getUSHORT($font, $offset);
+ $offset += 2;
+ }
+ $gidlen = (floor($length / 2) - 8 - (4 * $segCount));
+ $glyphIdArray = array(); // glyph index array
+ for ($k = 0; $k < $gidlen; ++$k) {
+ $glyphIdArray[$k] = TCPDF_STATIC::_getUSHORT($font, $offset);
+ $offset += 2;
+ }
+ for ($k = 0; $k < $segCount; ++$k) {
+ for ($c = $startCount[$k]; $c <= $endCount[$k]; ++$c) {
+ if ($idRangeOffset[$k] == 0) {
+ $g = ($idDelta[$k] + $c) % 65536;
+ } else {
+ $gid = (floor($idRangeOffset[$k] / 2) + ($c - $startCount[$k]) - ($segCount - $k));
+ $g = ($glyphIdArray[$gid] + $idDelta[$k]) % 65536;
+ }
+ if ($g < 0) {
+ $g = 0;
+ }
+ $ctg[$c] = $g;
+ }
+ }
+ break;
+ }
+ case 6: { // Format 6: Trimmed table mapping
+ $offset += 4; // skip length and version/language
+ $firstCode = TCPDF_STATIC::_getUSHORT($font, $offset);
+ $offset += 2;
+ $entryCount = TCPDF_STATIC::_getUSHORT($font, $offset);
+ $offset += 2;
+ for ($k = 0; $k < $entryCount; ++$k) {
+ $c = ($k + $firstCode);
+ $g = TCPDF_STATIC::_getUSHORT($font, $offset);
+ $offset += 2;
+ $ctg[$c] = $g;
+ }
+ break;
+ }
+ case 8: { // Format 8: Mixed 16-bit and 32-bit coverage
+ $offset += 10; // skip reserved, length and version/language
+ for ($k = 0; $k < 8192; ++$k) {
+ $is32[$k] = TCPDF_STATIC::_getBYTE($font, $offset);
+ ++$offset;
+ }
+ $nGroups = TCPDF_STATIC::_getULONG($font, $offset);
+ $offset += 4;
+ for ($i = 0; $i < $nGroups; ++$i) {
+ $startCharCode = TCPDF_STATIC::_getULONG($font, $offset);
+ $offset += 4;
+ $endCharCode = TCPDF_STATIC::_getULONG($font, $offset);
+ $offset += 4;
+ $startGlyphID = TCPDF_STATIC::_getULONG($font, $offset);
+ $offset += 4;
+ for ($k = $startCharCode; $k <= $endCharCode; ++$k) {
+ $is32idx = floor($c / 8);
+ if ((isset($is32[$is32idx])) AND (($is32[$is32idx] & (1 << (7 - ($c % 8)))) == 0)) {
+ $c = $k;
+ } else {
+ // 32 bit format
+ // convert to decimal (http://www.unicode.org/faq//utf_bom.html#utf16-4)
+ //LEAD_OFFSET = (0xD800 - (0x10000 >> 10)) = 55232
+ //SURROGATE_OFFSET = (0x10000 - (0xD800 << 10) - 0xDC00) = -56613888
+ $c = ((55232 + ($k >> 10)) << 10) + (0xDC00 + ($k & 0x3FF)) -56613888;
+ }
+ $ctg[$c] = 0;
+ ++$startGlyphID;
+ }
+ }
+ break;
+ }
+ case 10: { // Format 10: Trimmed array
+ $offset += 10; // skip reserved, length and version/language
+ $startCharCode = TCPDF_STATIC::_getULONG($font, $offset);
+ $offset += 4;
+ $numChars = TCPDF_STATIC::_getULONG($font, $offset);
+ $offset += 4;
+ for ($k = 0; $k < $numChars; ++$k) {
+ $c = ($k + $startCharCode);
+ $g = TCPDF_STATIC::_getUSHORT($font, $offset);
+ $ctg[$c] = $g;
+ $offset += 2;
+ }
+ break;
+ }
+ case 12: { // Format 12: Segmented coverage
+ $offset += 10; // skip length and version/language
+ $nGroups = TCPDF_STATIC::_getULONG($font, $offset);
+ $offset += 4;
+ for ($k = 0; $k < $nGroups; ++$k) {
+ $startCharCode = TCPDF_STATIC::_getULONG($font, $offset);
+ $offset += 4;
+ $endCharCode = TCPDF_STATIC::_getULONG($font, $offset);
+ $offset += 4;
+ $startGlyphCode = TCPDF_STATIC::_getULONG($font, $offset);
+ $offset += 4;
+ for ($c = $startCharCode; $c <= $endCharCode; ++$c) {
+ $ctg[$c] = $startGlyphCode;
+ ++$startGlyphCode;
+ }
+ }
+ break;
+ }
+ case 13: { // Format 13: Many-to-one range mappings
+ // to be implemented ...
+ break;
+ }
+ case 14: { // Format 14: Unicode Variation Sequences
+ // to be implemented ...
+ break;
+ }
+ }
+ }
+ }
+ if (!isset($ctg[0])) {
+ $ctg[0] = 0;
+ }
+ // get xHeight (height of x)
+ $offset = ($table['glyf']['offset'] + $indexToLoc[$ctg[120]] + 4);
+ $yMin = TCPDF_STATIC::_getFWORD($font, $offset);
+ $offset += 4;
+ $yMax = TCPDF_STATIC::_getFWORD($font, $offset);
+ $offset += 2;
+ $fmetric['XHeight'] = round(($yMax - $yMin) * $urk);
+ // get CapHeight (height of H)
+ $offset = ($table['glyf']['offset'] + $indexToLoc[$ctg[72]] + 4);
+ $yMin = TCPDF_STATIC::_getFWORD($font, $offset);
+ $offset += 4;
+ $yMax = TCPDF_STATIC::_getFWORD($font, $offset);
+ $offset += 2;
+ $fmetric['CapHeight'] = round(($yMax - $yMin) * $urk);
+ // ceate widths array
+ $cw = array();
+ $offset = $table['hmtx']['offset'];
+ for ($i = 0 ; $i < $numberOfHMetrics; ++$i) {
+ $cw[$i] = round(TCPDF_STATIC::_getUFWORD($font, $offset) * $urk);
+ $offset += 4; // skip lsb
+ }
+ if ($numberOfHMetrics < $numGlyphs) {
+ // fill missing widths with the last value
+ $cw = array_pad($cw, $numGlyphs, $cw[($numberOfHMetrics - 1)]);
+ }
+ $fmetric['MissingWidth'] = $cw[0];
+ $fmetric['cw'] = '';
+ for ($cid = 0; $cid <= 65535; ++$cid) {
+ if (isset($ctg[$cid])) {
+ if (isset($cw[$ctg[$cid]])) {
+ $fmetric['cw'] .= ','.$cid.'=>'.$cw[$ctg[$cid]];
+ }
+ if ($addcbbox AND isset($indexToLoc[$ctg[$cid]])) {
+ $offset = ($table['glyf']['offset'] + $indexToLoc[$ctg[$cid]]);
+ $xMin = round(TCPDF_STATIC::_getFWORD($font, $offset + 2)) * $urk;
+ $yMin = round(TCPDF_STATIC::_getFWORD($font, $offset + 4)) * $urk;
+ $xMax = round(TCPDF_STATIC::_getFWORD($font, $offset + 6)) * $urk;
+ $yMax = round(TCPDF_STATIC::_getFWORD($font, $offset + 8)) * $urk;
+ $fmetric['cbbox'] .= ','.$cid.'=>array('.$xMin.','.$yMin.','.$xMax.','.$yMax.')';
+ }
+ }
+ }
+ } // end of true type
+ if (($fmetric['type'] == 'TrueTypeUnicode') AND (count($ctg) == 256)) {
+ $fmetric['type'] == 'TrueType';
+ }
+ // ---------- create php font file ----------
+ $pfile = '<'.'?'.'php'."\n";
+ $pfile .= '// TCPDF FONT FILE DESCRIPTION'."\n";
+ $pfile .= '$type=\''.$fmetric['type'].'\';'."\n";
+ $pfile .= '$name=\''.$fmetric['name'].'\';'."\n";
+ $pfile .= '$up='.$fmetric['underlinePosition'].';'."\n";
+ $pfile .= '$ut='.$fmetric['underlineThickness'].';'."\n";
+ if ($fmetric['MissingWidth'] > 0) {
+ $pfile .= '$dw='.$fmetric['MissingWidth'].';'."\n";
+ } else {
+ $pfile .= '$dw='.$fmetric['AvgWidth'].';'."\n";
+ }
+ $pfile .= '$diff=\''.$fmetric['diff'].'\';'."\n";
+ if ($fmetric['type'] == 'Type1') {
+ // Type 1
+ $pfile .= '$enc=\''.$fmetric['enc'].'\';'."\n";
+ $pfile .= '$file=\''.$fmetric['file'].'\';'."\n";
+ $pfile .= '$size1='.$fmetric['size1'].';'."\n";
+ $pfile .= '$size2='.$fmetric['size2'].';'."\n";
+ } else {
+ $pfile .= '$originalsize='.$fmetric['originalsize'].';'."\n";
+ if ($fmetric['type'] == 'cidfont0') {
+ // CID-0
+ switch ($fonttype) {
+ case 'CID0JP': {
+ $pfile .= '// Japanese'."\n";
+ $pfile .= '$enc=\'UniJIS-UTF16-H\';'."\n";
+ $pfile .= '$cidinfo=array(\'Registry\'=>\'Adobe\', \'Ordering\'=>\'Japan1\',\'Supplement\'=>5);'."\n";
+ $pfile .= 'include(dirname(__FILE__).\'/uni2cid_aj16.php\');'."\n";
+ break;
+ }
+ case 'CID0KR': {
+ $pfile .= '// Korean'."\n";
+ $pfile .= '$enc=\'UniKS-UTF16-H\';'."\n";
+ $pfile .= '$cidinfo=array(\'Registry\'=>\'Adobe\', \'Ordering\'=>\'Korea1\',\'Supplement\'=>0);'."\n";
+ $pfile .= 'include(dirname(__FILE__).\'/uni2cid_ak12.php\');'."\n";
+ break;
+ }
+ case 'CID0CS': {
+ $pfile .= '// Chinese Simplified'."\n";
+ $pfile .= '$enc=\'UniGB-UTF16-H\';'."\n";
+ $pfile .= '$cidinfo=array(\'Registry\'=>\'Adobe\', \'Ordering\'=>\'GB1\',\'Supplement\'=>2);'."\n";
+ $pfile .= 'include(dirname(__FILE__).\'/uni2cid_ag15.php\');'."\n";
+ break;
+ }
+ case 'CID0CT':
+ default: {
+ $pfile .= '// Chinese Traditional'."\n";
+ $pfile .= '$enc=\'UniCNS-UTF16-H\';'."\n";
+ $pfile .= '$cidinfo=array(\'Registry\'=>\'Adobe\', \'Ordering\'=>\'CNS1\',\'Supplement\'=>0);'."\n";
+ $pfile .= 'include(dirname(__FILE__).\'/uni2cid_aj16.php\');'."\n";
+ break;
+ }
+ }
+ } else {
+ // TrueType
+ $pfile .= '$enc=\''.$fmetric['enc'].'\';'."\n";
+ $pfile .= '$file=\''.$fmetric['file'].'\';'."\n";
+ $pfile .= '$ctg=\''.$fmetric['ctg'].'\';'."\n";
+ // create CIDToGIDMap
+ $cidtogidmap = str_pad('', 131072, "\x00"); // (256 * 256 * 2) = 131072
+ foreach ($ctg as $cid => $gid) {
+ $cidtogidmap = self::updateCIDtoGIDmap($cidtogidmap, $cid, $ctg[$cid]);
+ }
+ // store compressed CIDToGIDMap
+ $fp = fopen($outpath.$fmetric['ctg'], 'wb');
+ fwrite($fp, gzcompress($cidtogidmap));
+ fclose($fp);
+ }
+ }
+ $pfile .= '$desc=array(';
+ $pfile .= '\'Flags\'=>'.$fmetric['Flags'].',';
+ $pfile .= '\'FontBBox\'=>\'['.$fmetric['bbox'].']\',';
+ $pfile .= '\'ItalicAngle\'=>'.$fmetric['italicAngle'].',';
+ $pfile .= '\'Ascent\'=>'.$fmetric['Ascent'].',';
+ $pfile .= '\'Descent\'=>'.$fmetric['Descent'].',';
+ $pfile .= '\'Leading\'=>'.$fmetric['Leading'].',';
+ $pfile .= '\'CapHeight\'=>'.$fmetric['CapHeight'].',';
+ $pfile .= '\'XHeight\'=>'.$fmetric['XHeight'].',';
+ $pfile .= '\'StemV\'=>'.$fmetric['StemV'].',';
+ $pfile .= '\'StemH\'=>'.$fmetric['StemH'].',';
+ $pfile .= '\'AvgWidth\'=>'.$fmetric['AvgWidth'].',';
+ $pfile .= '\'MaxWidth\'=>'.$fmetric['MaxWidth'].',';
+ $pfile .= '\'MissingWidth\'=>'.$fmetric['MissingWidth'].'';
+ $pfile .= ');'."\n";
+ if (isset($fmetric['cbbox'])) {
+ $pfile .= '$cbbox=array('.substr($fmetric['cbbox'], 1).');'."\n";
+ }
+ $pfile .= '$cw=array('.substr($fmetric['cw'], 1).');'."\n";
+ $pfile .= '// --- EOF ---'."\n";
+ // store file
+ $fp = fopen($outpath.$font_name.'.php', 'w');
+ fwrite($fp, $pfile);
+ fclose($fp);
+ // return TCPDF font name
+ return $font_name;
+ }
+
+ /**
+ * Returs the checksum of a TTF table.
+ * @param $table (string) table to check
+ * @param $length (int) length of table in bytes
+ * @return int checksum
+ * @author Nicola Asuni
+ * @since 5.2.000 (2010-06-02)
+ * @public static
+ */
+ public static function _getTTFtableChecksum($table, $length) {
+ $sum = 0;
+ $tlen = ($length / 4);
+ $offset = 0;
+ for ($i = 0; $i < $tlen; ++$i) {
+ $v = unpack('Ni', substr($table, $offset, 4));
+ $sum += $v['i'];
+ $offset += 4;
+ }
+ $sum = unpack('Ni', pack('N', $sum));
+ return $sum['i'];
+ }
+
+ /**
+ * Returns a subset of the TrueType font data without the unused glyphs.
+ * @param $font (string) TrueType font data.
+ * @param $subsetchars (array) Array of used characters (the glyphs to keep).
+ * @return (string) A subset of TrueType font data without the unused glyphs.
+ * @author Nicola Asuni
+ * @since 5.2.000 (2010-06-02)
+ * @public static
+ */
+ public static function _getTrueTypeFontSubset($font, $subsetchars) {
+ ksort($subsetchars);
+ $offset = 0; // offset position of the font data
+ if (TCPDF_STATIC::_getULONG($font, $offset) != 0x10000) {
+ // sfnt version must be 0x00010000 for TrueType version 1.0.
+ return $font;
+ }
+ $offset += 4;
+ // get number of tables
+ $numTables = TCPDF_STATIC::_getUSHORT($font, $offset);
+ $offset += 2;
+ // skip searchRange, entrySelector and rangeShift
+ $offset += 6;
+ // tables array
+ $table = array();
+ // for each table
+ for ($i = 0; $i < $numTables; ++$i) {
+ // get table info
+ $tag = substr($font, $offset, 4);
+ $offset += 4;
+ $table[$tag] = array();
+ $table[$tag]['checkSum'] = TCPDF_STATIC::_getULONG($font, $offset);
+ $offset += 4;
+ $table[$tag]['offset'] = TCPDF_STATIC::_getULONG($font, $offset);
+ $offset += 4;
+ $table[$tag]['length'] = TCPDF_STATIC::_getULONG($font, $offset);
+ $offset += 4;
+ }
+ // check magicNumber
+ $offset = $table['head']['offset'] + 12;
+ if (TCPDF_STATIC::_getULONG($font, $offset) != 0x5F0F3CF5) {
+ // magicNumber must be 0x5F0F3CF5
+ return $font;
+ }
+ $offset += 4;
+ // get offset mode (indexToLocFormat : 0 = short, 1 = long)
+ $offset = $table['head']['offset'] + 50;
+ $short_offset = (TCPDF_STATIC::_getSHORT($font, $offset) == 0);
+ $offset += 2;
+ // get the offsets to the locations of the glyphs in the font, relative to the beginning of the glyphData table
+ $indexToLoc = array();
+ $offset = $table['loca']['offset'];
+ if ($short_offset) {
+ // short version
+ $tot_num_glyphs = floor($table['loca']['length'] / 2); // numGlyphs + 1
+ for ($i = 0; $i < $tot_num_glyphs; ++$i) {
+ $indexToLoc[$i] = TCPDF_STATIC::_getUSHORT($font, $offset) * 2;
+ $offset += 2;
+ }
+ } else {
+ // long version
+ $tot_num_glyphs = ($table['loca']['length'] / 4); // numGlyphs + 1
+ for ($i = 0; $i < $tot_num_glyphs; ++$i) {
+ $indexToLoc[$i] = TCPDF_STATIC::_getULONG($font, $offset);
+ $offset += 4;
+ }
+ }
+ // get glyphs indexes of chars from cmap table
+ $subsetglyphs = array(); // glyph IDs on key
+ $subsetglyphs[0] = true; // character codes that do not correspond to any glyph in the font should be mapped to glyph index 0
+ $offset = $table['cmap']['offset'] + 2;
+ $numEncodingTables = TCPDF_STATIC::_getUSHORT($font, $offset);
+ $offset += 2;
+ $encodingTables = array();
+ for ($i = 0; $i < $numEncodingTables; ++$i) {
+ $encodingTables[$i]['platformID'] = TCPDF_STATIC::_getUSHORT($font, $offset);
+ $offset += 2;
+ $encodingTables[$i]['encodingID'] = TCPDF_STATIC::_getUSHORT($font, $offset);
+ $offset += 2;
+ $encodingTables[$i]['offset'] = TCPDF_STATIC::_getULONG($font, $offset);
+ $offset += 4;
+ }
+ foreach ($encodingTables as $enctable) {
+ // get all platforms and encodings
+ $offset = $table['cmap']['offset'] + $enctable['offset'];
+ $format = TCPDF_STATIC::_getUSHORT($font, $offset);
+ $offset += 2;
+ switch ($format) {
+ case 0: { // Format 0: Byte encoding table
+ $offset += 4; // skip length and version/language
+ for ($c = 0; $c < 256; ++$c) {
+ if (isset($subsetchars[$c])) {
+ $g = TCPDF_STATIC::_getBYTE($font, $offset);
+ $subsetglyphs[$g] = true;
+ }
+ ++$offset;
+ }
+ break;
+ }
+ case 2: { // Format 2: High-byte mapping through table
+ $offset += 4; // skip length and version/language
+ $numSubHeaders = 0;
+ for ($i = 0; $i < 256; ++$i) {
+ // Array that maps high bytes to subHeaders: value is subHeader index * 8.
+ $subHeaderKeys[$i] = (TCPDF_STATIC::_getUSHORT($font, $offset) / 8);
+ $offset += 2;
+ if ($numSubHeaders < $subHeaderKeys[$i]) {
+ $numSubHeaders = $subHeaderKeys[$i];
+ }
+ }
+ // the number of subHeaders is equal to the max of subHeaderKeys + 1
+ ++$numSubHeaders;
+ // read subHeader structures
+ $subHeaders = array();
+ $numGlyphIndexArray = 0;
+ for ($k = 0; $k < $numSubHeaders; ++$k) {
+ $subHeaders[$k]['firstCode'] = TCPDF_STATIC::_getUSHORT($font, $offset);
+ $offset += 2;
+ $subHeaders[$k]['entryCount'] = TCPDF_STATIC::_getUSHORT($font, $offset);
+ $offset += 2;
+ $subHeaders[$k]['idDelta'] = TCPDF_STATIC::_getUSHORT($font, $offset);
+ $offset += 2;
+ $subHeaders[$k]['idRangeOffset'] = TCPDF_STATIC::_getUSHORT($font, $offset);
+ $offset += 2;
+ $subHeaders[$k]['idRangeOffset'] -= (2 + (($numSubHeaders - $k - 1) * 8));
+ $subHeaders[$k]['idRangeOffset'] /= 2;
+ $numGlyphIndexArray += $subHeaders[$k]['entryCount'];
+ }
+ for ($k = 0; $k < $numGlyphIndexArray; ++$k) {
+ $glyphIndexArray[$k] = TCPDF_STATIC::_getUSHORT($font, $offset);
+ $offset += 2;
+ }
+ for ($i = 0; $i < 256; ++$i) {
+ $k = $subHeaderKeys[$i];
+ if ($k == 0) {
+ // one byte code
+ $c = $i;
+ if (isset($subsetchars[$c])) {
+ $g = $glyphIndexArray[0];
+ $subsetglyphs[$g] = true;
+ }
+ } else {
+ // two bytes code
+ $start_byte = $subHeaders[$k]['firstCode'];
+ $end_byte = $start_byte + $subHeaders[$k]['entryCount'];
+ for ($j = $start_byte; $j < $end_byte; ++$j) {
+ // combine high and low bytes
+ $c = (($i << 8) + $j);
+ if (isset($subsetchars[$c])) {
+ $idRangeOffset = ($subHeaders[$k]['idRangeOffset'] + $j - $subHeaders[$k]['firstCode']);
+ $g = ($glyphIndexArray[$idRangeOffset] + $idDelta[$k]) % 65536;
+ if ($g < 0) {
+ $g = 0;
+ }
+ $subsetglyphs[$g] = true;
+ }
+ }
+ }
+ }
+ break;
+ }
+ case 4: { // Format 4: Segment mapping to delta values
+ $length = TCPDF_STATIC::_getUSHORT($font, $offset);
+ $offset += 2;
+ $offset += 2; // skip version/language
+ $segCount = floor(TCPDF_STATIC::_getUSHORT($font, $offset) / 2);
+ $offset += 2;
+ $offset += 6; // skip searchRange, entrySelector, rangeShift
+ $endCount = array(); // array of end character codes for each segment
+ for ($k = 0; $k < $segCount; ++$k) {
+ $endCount[$k] = TCPDF_STATIC::_getUSHORT($font, $offset);
+ $offset += 2;
+ }
+ $offset += 2; // skip reservedPad
+ $startCount = array(); // array of start character codes for each segment
+ for ($k = 0; $k < $segCount; ++$k) {
+ $startCount[$k] = TCPDF_STATIC::_getUSHORT($font, $offset);
+ $offset += 2;
+ }
+ $idDelta = array(); // delta for all character codes in segment
+ for ($k = 0; $k < $segCount; ++$k) {
+ $idDelta[$k] = TCPDF_STATIC::_getUSHORT($font, $offset);
+ $offset += 2;
+ }
+ $idRangeOffset = array(); // Offsets into glyphIdArray or 0
+ for ($k = 0; $k < $segCount; ++$k) {
+ $idRangeOffset[$k] = TCPDF_STATIC::_getUSHORT($font, $offset);
+ $offset += 2;
+ }
+ $gidlen = (floor($length / 2) - 8 - (4 * $segCount));
+ $glyphIdArray = array(); // glyph index array
+ for ($k = 0; $k < $gidlen; ++$k) {
+ $glyphIdArray[$k] = TCPDF_STATIC::_getUSHORT($font, $offset);
+ $offset += 2;
+ }
+ for ($k = 0; $k < $segCount; ++$k) {
+ for ($c = $startCount[$k]; $c <= $endCount[$k]; ++$c) {
+ if (isset($subsetchars[$c])) {
+ if ($idRangeOffset[$k] == 0) {
+ $g = ($idDelta[$k] + $c) % 65536;
+ } else {
+ $gid = (floor($idRangeOffset[$k] / 2) + ($c - $startCount[$k]) - ($segCount - $k));
+ $g = ($glyphIdArray[$gid] + $idDelta[$k]) % 65536;
+ }
+ if ($g < 0) {
+ $g = 0;
+ }
+ $subsetglyphs[$g] = true;
+ }
+ }
+ }
+ break;
+ }
+ case 6: { // Format 6: Trimmed table mapping
+ $offset += 4; // skip length and version/language
+ $firstCode = TCPDF_STATIC::_getUSHORT($font, $offset);
+ $offset += 2;
+ $entryCount = TCPDF_STATIC::_getUSHORT($font, $offset);
+ $offset += 2;
+ for ($k = 0; $k < $entryCount; ++$k) {
+ $c = ($k + $firstCode);
+ if (isset($subsetchars[$c])) {
+ $g = TCPDF_STATIC::_getUSHORT($font, $offset);
+ $subsetglyphs[$g] = true;
+ }
+ $offset += 2;
+ }
+ break;
+ }
+ case 8: { // Format 8: Mixed 16-bit and 32-bit coverage
+ $offset += 10; // skip reserved, length and version/language
+ for ($k = 0; $k < 8192; ++$k) {
+ $is32[$k] = TCPDF_STATIC::_getBYTE($font, $offset);
+ ++$offset;
+ }
+ $nGroups = TCPDF_STATIC::_getULONG($font, $offset);
+ $offset += 4;
+ for ($i = 0; $i < $nGroups; ++$i) {
+ $startCharCode = TCPDF_STATIC::_getULONG($font, $offset);
+ $offset += 4;
+ $endCharCode = TCPDF_STATIC::_getULONG($font, $offset);
+ $offset += 4;
+ $startGlyphID = TCPDF_STATIC::_getULONG($font, $offset);
+ $offset += 4;
+ for ($k = $startCharCode; $k <= $endCharCode; ++$k) {
+ $is32idx = floor($c / 8);
+ if ((isset($is32[$is32idx])) AND (($is32[$is32idx] & (1 << (7 - ($c % 8)))) == 0)) {
+ $c = $k;
+ } else {
+ // 32 bit format
+ // convert to decimal (http://www.unicode.org/faq//utf_bom.html#utf16-4)
+ //LEAD_OFFSET = (0xD800 - (0x10000 >> 10)) = 55232
+ //SURROGATE_OFFSET = (0x10000 - (0xD800 << 10) - 0xDC00) = -56613888
+ $c = ((55232 + ($k >> 10)) << 10) + (0xDC00 + ($k & 0x3FF)) -56613888;
+ }
+ if (isset($subsetchars[$c])) {
+ $subsetglyphs[$startGlyphID] = true;
+ }
+ ++$startGlyphID;
+ }
+ }
+ break;
+ }
+ case 10: { // Format 10: Trimmed array
+ $offset += 10; // skip reserved, length and version/language
+ $startCharCode = TCPDF_STATIC::_getULONG($font, $offset);
+ $offset += 4;
+ $numChars = TCPDF_STATIC::_getULONG($font, $offset);
+ $offset += 4;
+ for ($k = 0; $k < $numChars; ++$k) {
+ $c = ($k + $startCharCode);
+ if (isset($subsetchars[$c])) {
+ $g = TCPDF_STATIC::_getUSHORT($font, $offset);
+ $subsetglyphs[$g] = true;
+ }
+ $offset += 2;
+ }
+ break;
+ }
+ case 12: { // Format 12: Segmented coverage
+ $offset += 10; // skip length and version/language
+ $nGroups = TCPDF_STATIC::_getULONG($font, $offset);
+ $offset += 4;
+ for ($k = 0; $k < $nGroups; ++$k) {
+ $startCharCode = TCPDF_STATIC::_getULONG($font, $offset);
+ $offset += 4;
+ $endCharCode = TCPDF_STATIC::_getULONG($font, $offset);
+ $offset += 4;
+ $startGlyphCode = TCPDF_STATIC::_getULONG($font, $offset);
+ $offset += 4;
+ for ($c = $startCharCode; $c <= $endCharCode; ++$c) {
+ if (isset($subsetchars[$c])) {
+ $subsetglyphs[$startGlyphCode] = true;
+ }
+ ++$startGlyphCode;
+ }
+ }
+ break;
+ }
+ case 13: { // Format 13: Many-to-one range mappings
+ // to be implemented ...
+ break;
+ }
+ case 14: { // Format 14: Unicode Variation Sequences
+ // to be implemented ...
+ break;
+ }
+ }
+ }
+ // include all parts of composite glyphs
+ $new_sga = $subsetglyphs;
+ while (!empty($new_sga)) {
+ $sga = $new_sga;
+ $new_sga = array();
+ foreach ($sga as $key => $val) {
+ if (isset($indexToLoc[$key])) {
+ $offset = ($table['glyf']['offset'] + $indexToLoc[$key]);
+ $numberOfContours = TCPDF_STATIC::_getSHORT($font, $offset);
+ $offset += 2;
+ if ($numberOfContours < 0) { // composite glyph
+ $offset += 8; // skip xMin, yMin, xMax, yMax
+ do {
+ $flags = TCPDF_STATIC::_getUSHORT($font, $offset);
+ $offset += 2;
+ $glyphIndex = TCPDF_STATIC::_getUSHORT($font, $offset);
+ $offset += 2;
+ if (!isset($subsetglyphs[$glyphIndex])) {
+ // add missing glyphs
+ $new_sga[$glyphIndex] = true;
+ }
+ // skip some bytes by case
+ if ($flags & 1) {
+ $offset += 4;
+ } else {
+ $offset += 2;
+ }
+ if ($flags & 8) {
+ $offset += 2;
+ } elseif ($flags & 64) {
+ $offset += 4;
+ } elseif ($flags & 128) {
+ $offset += 8;
+ }
+ } while ($flags & 32);
+ }
+ }
+ }
+ $subsetglyphs += $new_sga;
+ }
+ // sort glyphs by key (and remove duplicates)
+ ksort($subsetglyphs);
+ // build new glyf and loca tables
+ $glyf = '';
+ $loca = '';
+ $offset = 0;
+ $glyf_offset = $table['glyf']['offset'];
+ for ($i = 0; $i < $tot_num_glyphs; ++$i) {
+ if (isset($subsetglyphs[$i])) {
+ $length = ($indexToLoc[($i + 1)] - $indexToLoc[$i]);
+ $glyf .= substr($font, ($glyf_offset + $indexToLoc[$i]), $length);
+ } else {
+ $length = 0;
+ }
+ if ($short_offset) {
+ $loca .= pack('n', floor($offset / 2));
+ } else {
+ $loca .= pack('N', $offset);
+ }
+ $offset += $length;
+ }
+ // array of table names to preserve (loca and glyf tables will be added later)
+ // the cmap table is not needed and shall not be present, since the mapping from character codes to glyph descriptions is provided separately
+ $table_names = array ('head', 'hhea', 'hmtx', 'maxp', 'cvt ', 'fpgm', 'prep'); // minimum required table names
+ // get the tables to preserve
+ $offset = 12;
+ foreach ($table as $tag => $val) {
+ if (in_array($tag, $table_names)) {
+ $table[$tag]['data'] = substr($font, $table[$tag]['offset'], $table[$tag]['length']);
+ if ($tag == 'head') {
+ // set the checkSumAdjustment to 0
+ $table[$tag]['data'] = substr($table[$tag]['data'], 0, 8)."\x0\x0\x0\x0".substr($table[$tag]['data'], 12);
+ }
+ $pad = 4 - ($table[$tag]['length'] % 4);
+ if ($pad != 4) {
+ // the length of a table must be a multiple of four bytes
+ $table[$tag]['length'] += $pad;
+ $table[$tag]['data'] .= str_repeat("\x0", $pad);
+ }
+ $table[$tag]['offset'] = $offset;
+ $offset += $table[$tag]['length'];
+ // check sum is not changed (so keep the following line commented)
+ //$table[$tag]['checkSum'] = self::_getTTFtableChecksum($table[$tag]['data'], $table[$tag]['length']);
+ } else {
+ unset($table[$tag]);
+ }
+ }
+ // add loca
+ $table['loca']['data'] = $loca;
+ $table['loca']['length'] = strlen($loca);
+ $pad = 4 - ($table['loca']['length'] % 4);
+ if ($pad != 4) {
+ // the length of a table must be a multiple of four bytes
+ $table['loca']['length'] += $pad;
+ $table['loca']['data'] .= str_repeat("\x0", $pad);
+ }
+ $table['loca']['offset'] = $offset;
+ $table['loca']['checkSum'] = self::_getTTFtableChecksum($table['loca']['data'], $table['loca']['length']);
+ $offset += $table['loca']['length'];
+ // add glyf
+ $table['glyf']['data'] = $glyf;
+ $table['glyf']['length'] = strlen($glyf);
+ $pad = 4 - ($table['glyf']['length'] % 4);
+ if ($pad != 4) {
+ // the length of a table must be a multiple of four bytes
+ $table['glyf']['length'] += $pad;
+ $table['glyf']['data'] .= str_repeat("\x0", $pad);
+ }
+ $table['glyf']['offset'] = $offset;
+ $table['glyf']['checkSum'] = self::_getTTFtableChecksum($table['glyf']['data'], $table['glyf']['length']);
+ // rebuild font
+ $font = '';
+ $font .= pack('N', 0x10000); // sfnt version
+ $numTables = count($table);
+ $font .= pack('n', $numTables); // numTables
+ $entrySelector = floor(log($numTables, 2));
+ $searchRange = pow(2, $entrySelector) * 16;
+ $rangeShift = ($numTables * 16) - $searchRange;
+ $font .= pack('n', $searchRange); // searchRange
+ $font .= pack('n', $entrySelector); // entrySelector
+ $font .= pack('n', $rangeShift); // rangeShift
+ $offset = ($numTables * 16);
+ foreach ($table as $tag => $data) {
+ $font .= $tag; // tag
+ $font .= pack('N', $data['checkSum']); // checkSum
+ $font .= pack('N', ($data['offset'] + $offset)); // offset
+ $font .= pack('N', $data['length']); // length
+ }
+ foreach ($table as $data) {
+ $font .= $data['data'];
+ }
+ // set checkSumAdjustment on head table
+ $checkSumAdjustment = 0xB1B0AFBA - self::_getTTFtableChecksum($font, strlen($font));
+ $font = substr($font, 0, $table['head']['offset'] + 8).pack('N', $checkSumAdjustment).substr($font, $table['head']['offset'] + 12);
+ return $font;
+ }
+
+ /**
+ * Outputs font widths
+ * @param $font (array) font data
+ * @param $cidoffset (int) offset for CID values
+ * @return PDF command string for font widths
+ * @author Nicola Asuni
+ * @since 4.4.000 (2008-12-07)
+ * @public static
+ */
+ public static function _putfontwidths($font, $cidoffset=0) {
+ ksort($font['cw']);
+ $rangeid = 0;
+ $range = array();
+ $prevcid = -2;
+ $prevwidth = -1;
+ $interval = false;
+ // for each character
+ foreach ($font['cw'] as $cid => $width) {
+ $cid -= $cidoffset;
+ if ($font['subset'] AND (!isset($font['subsetchars'][$cid]))) {
+ // ignore the unused characters (font subsetting)
+ continue;
+ }
+ if ($width != $font['dw']) {
+ if ($cid == ($prevcid + 1)) {
+ // consecutive CID
+ if ($width == $prevwidth) {
+ if ($width == $range[$rangeid][0]) {
+ $range[$rangeid][] = $width;
+ } else {
+ array_pop($range[$rangeid]);
+ // new range
+ $rangeid = $prevcid;
+ $range[$rangeid] = array();
+ $range[$rangeid][] = $prevwidth;
+ $range[$rangeid][] = $width;
+ }
+ $interval = true;
+ $range[$rangeid]['interval'] = true;
+ } else {
+ if ($interval) {
+ // new range
+ $rangeid = $cid;
+ $range[$rangeid] = array();
+ $range[$rangeid][] = $width;
+ } else {
+ $range[$rangeid][] = $width;
+ }
+ $interval = false;
+ }
+ } else {
+ // new range
+ $rangeid = $cid;
+ $range[$rangeid] = array();
+ $range[$rangeid][] = $width;
+ $interval = false;
+ }
+ $prevcid = $cid;
+ $prevwidth = $width;
+ }
+ }
+ // optimize ranges
+ $prevk = -1;
+ $nextk = -1;
+ $prevint = false;
+ foreach ($range as $k => $ws) {
+ $cws = count($ws);
+ if (($k == $nextk) AND (!$prevint) AND ((!isset($ws['interval'])) OR ($cws < 4))) {
+ if (isset($range[$k]['interval'])) {
+ unset($range[$k]['interval']);
+ }
+ $range[$prevk] = array_merge($range[$prevk], $range[$k]);
+ unset($range[$k]);
+ } else {
+ $prevk = $k;
+ }
+ $nextk = $k + $cws;
+ if (isset($ws['interval'])) {
+ if ($cws > 3) {
+ $prevint = true;
+ } else {
+ $prevint = false;
+ }
+ if (isset($range[$k]['interval'])) {
+ unset($range[$k]['interval']);
+ }
+ --$nextk;
+ } else {
+ $prevint = false;
+ }
+ }
+ // output data
+ $w = '';
+ foreach ($range as $k => $ws) {
+ if (count(array_count_values($ws)) == 1) {
+ // interval mode is more compact
+ $w .= ' '.$k.' '.($k + count($ws) - 1).' '.$ws[0];
+ } else {
+ // range mode
+ $w .= ' '.$k.' [ '.implode(' ', $ws).' ]';
+ }
+ }
+ return '/W ['.$w.' ]';
+ }
+
+ /**
+ * Returns the unicode caracter specified by the value
+ * @param $c (int) UTF-8 value
+ * @param $unicode (boolean) True if we are in unicode mode, false otherwise.
+ * @return Returns the specified character.
+ * @since 2.3.000 (2008-03-05)
+ * @public static
+ */
+ public static function unichr($c, $unicode=true) {
+ if (!$unicode) {
+ return chr($c);
+ } elseif ($c <= 0x7F) {
+ // one byte
+ return chr($c);
+ } elseif ($c <= 0x7FF) {
+ // two bytes
+ return chr(0xC0 | $c >> 6).chr(0x80 | $c & 0x3F);
+ } elseif ($c <= 0xFFFF) {
+ // three bytes
+ return chr(0xE0 | $c >> 12).chr(0x80 | $c >> 6 & 0x3F).chr(0x80 | $c & 0x3F);
+ } elseif ($c <= 0x10FFFF) {
+ // four bytes
+ return chr(0xF0 | $c >> 18).chr(0x80 | $c >> 12 & 0x3F).chr(0x80 | $c >> 6 & 0x3F).chr(0x80 | $c & 0x3F);
+ } else {
+ return '';
+ }
+ }
+
+ /**
+ * Returns the unicode caracter specified by UTF-8 value
+ * @param $c (int) UTF-8 value
+ * @return Returns the specified character.
+ * @public static
+ */
+ public static function unichrUnicode($c) {
+ return self::unichr($c, true);
+ }
+
+ /**
+ * Returns the unicode caracter specified by ASCII value
+ * @param $c (int) UTF-8 value
+ * @return Returns the specified character.
+ * @public static
+ */
+ public static function unichrASCII($c) {
+ return self::unichr($c, false);
+ }
+
+ /**
+ * Converts array of UTF-8 characters to UTF16-BE string.<br>
+ * Based on: http://www.faqs.org/rfcs/rfc2781.html
+ * <pre>
+ * Encoding UTF-16:
+ *
+ * Encoding of a single character from an ISO 10646 character value to
+ * UTF-16 proceeds as follows. Let U be the character number, no greater
+ * than 0x10FFFF.
+ *
+ * 1) If U < 0x10000, encode U as a 16-bit unsigned integer and
+ * terminate.
+ *
+ * 2) Let U' = U - 0x10000. Because U is less than or equal to 0x10FFFF,
+ * U' must be less than or equal to 0xFFFFF. That is, U' can be
+ * represented in 20 bits.
+ *
+ * 3) Initialize two 16-bit unsigned integers, W1 and W2, to 0xD800 and
+ * 0xDC00, respectively. These integers each have 10 bits free to
+ * encode the character value, for a total of 20 bits.
+ *
+ * 4) Assign the 10 high-order bits of the 20-bit U' to the 10 low-order
+ * bits of W1 and the 10 low-order bits of U' to the 10 low-order
+ * bits of W2. Terminate.
+ *
+ * Graphically, steps 2 through 4 look like:
+ * U' = yyyyyyyyyyxxxxxxxxxx
+ * W1 = 110110yyyyyyyyyy
+ * W2 = 110111xxxxxxxxxx
+ * </pre>
+ * @param $unicode (array) array containing UTF-8 unicode values
+ * @param $setbom (boolean) if true set the Byte Order Mark (BOM = 0xFEFF)
+ * @return string
+ * @protected
+ * @author Nicola Asuni
+ * @since 2.1.000 (2008-01-08)
+ * @public static
+ */
+ public static function arrUTF8ToUTF16BE($unicode, $setbom=false) {
+ $outstr = ''; // string to be returned
+ if ($setbom) {
+ $outstr .= "\xFE\xFF"; // Byte Order Mark (BOM)
+ }
+ foreach ($unicode as $char) {
+ if ($char == 0x200b) {
+ // skip Unicode Character 'ZERO WIDTH SPACE' (DEC:8203, U+200B)
+ } elseif ($char == 0xFFFD) {
+ $outstr .= "\xFF\xFD"; // replacement character
+ } elseif ($char < 0x10000) {
+ $outstr .= chr($char >> 0x08);
+ $outstr .= chr($char & 0xFF);
+ } else {
+ $char -= 0x10000;
+ $w1 = 0xD800 | ($char >> 0x0a);
+ $w2 = 0xDC00 | ($char & 0x3FF);
+ $outstr .= chr($w1 >> 0x08);
+ $outstr .= chr($w1 & 0xFF);
+ $outstr .= chr($w2 >> 0x08);
+ $outstr .= chr($w2 & 0xFF);
+ }
+ }
+ return $outstr;
+ }
+
+ /**
+ * Convert an array of UTF8 values to array of unicode characters
+ * @param $ta (array) The input array of UTF8 values.
+ * @param $isunicode (boolean) True for Unicode mode, false otherwise.
+ * @return Return array of unicode characters
+ * @since 4.5.037 (2009-04-07)
+ * @public static
+ */
+ public static function UTF8ArrayToUniArray($ta, $isunicode=true) {
+ if ($isunicode) {
+ return array_map(array('self', 'unichrUnicode'), $ta);
+ }
+ return array_map(array('self', 'unichrASCII'), $ta);
+ }
+
+ /**
+ * Extract a slice of the $strarr array and return it as string.
+ * @param $strarr (string) The input array of characters.
+ * @param $start (int) the starting element of $strarr.
+ * @param $end (int) first element that will not be returned.
+ * @param $unicode (boolean) True if we are in unicode mode, false otherwise.
+ * @return Return part of a string
+ * @public static
+ */
+ public static function UTF8ArrSubString($strarr, $start='', $end='', $unicode=true) {
+ if (strlen($start) == 0) {
+ $start = 0;
+ }
+ if (strlen($end) == 0) {
+ $end = count($strarr);
+ }
+ $string = '';
+ for ($i = $start; $i < $end; ++$i) {
+ $string .= self::unichr($strarr[$i], $unicode);
+ }
+ return $string;
+ }
+
+ /**
+ * Extract a slice of the $uniarr array and return it as string.
+ * @param $uniarr (string) The input array of characters.
+ * @param $start (int) the starting element of $strarr.
+ * @param $end (int) first element that will not be returned.
+ * @return Return part of a string
+ * @since 4.5.037 (2009-04-07)
+ * @public static
+ */
+ public static function UniArrSubString($uniarr, $start='', $end='') {
+ if (strlen($start) == 0) {
+ $start = 0;
+ }
+ if (strlen($end) == 0) {
+ $end = count($uniarr);
+ }
+ $string = '';
+ for ($i=$start; $i < $end; ++$i) {
+ $string .= $uniarr[$i];
+ }
+ return $string;
+ }
+
+ /**
+ * Update the CIDToGIDMap string with a new value.
+ * @param $map (string) CIDToGIDMap.
+ * @param $cid (int) CID value.
+ * @param $gid (int) GID value.
+ * @return (string) CIDToGIDMap.
+ * @author Nicola Asuni
+ * @since 5.9.123 (2011-09-29)
+ * @public static
+ */
+ public static function updateCIDtoGIDmap($map, $cid, $gid) {
+ if (($cid >= 0) AND ($cid <= 0xFFFF) AND ($gid >= 0)) {
+ if ($gid > 0xFFFF) {
+ $gid -= 0x10000;
+ }
+ $map[($cid * 2)] = chr($gid >> 8);
+ $map[(($cid * 2) + 1)] = chr($gid & 0xFF);
+ }
+ return $map;
+ }
+
+ /**
+ * Return fonts path
+ * @return string
+ * @public static
+ */
+ public static function _getfontpath() {
+ if (!defined('K_PATH_FONTS') AND is_dir($fdir = realpath(dirname(__FILE__).'/../fonts'))) {
+ if (substr($fdir, -1) != '/') {
+ $fdir .= '/';
+ }
+ define('K_PATH_FONTS', $fdir);
+ }
+ return defined('K_PATH_FONTS') ? K_PATH_FONTS : '';
+ }
+
+ /**
+ * Return font full path
+ * @param $file (string) Font file name.
+ * @param $fontdir (string) Font directory (set to false fto search on default directories)
+ * @return string Font full path or empty string
+ * @author Nicola Asuni
+ * @since 6.0.025
+ * @public static
+ */
+ public static function getFontFullPath($file, $fontdir=false) {
+ $fontfile = '';
+ // search files on various directories
+ if (($fontdir !== false) AND @file_exists($fontdir.$file)) {
+ $fontfile = $fontdir.$file;
+ } elseif (@file_exists(self::_getfontpath().$file)) {
+ $fontfile = self::_getfontpath().$file;
+ } elseif (@file_exists($file)) {
+ $fontfile = $file;
+ }
+ return $fontfile;
+ }
+
+ /**
+ * Converts UTF-8 characters array to array of Latin1 characters array<br>
+ * @param $unicode (array) array containing UTF-8 unicode values
+ * @return array
+ * @author Nicola Asuni
+ * @since 4.8.023 (2010-01-15)
+ * @public static
+ */
+ public static function UTF8ArrToLatin1Arr($unicode) {
+ $outarr = array(); // array to be returned
+ foreach ($unicode as $char) {
+ if ($char < 256) {
+ $outarr[] = $char;
+ } elseif (array_key_exists($char, TCPDF_FONT_DATA::$uni_utf8tolatin)) {
+ // map from UTF-8
+ $outarr[] = TCPDF_FONT_DATA::$uni_utf8tolatin[$char];
+ } elseif ($char == 0xFFFD) {
+ // skip
+ } else {
+ $outarr[] = 63; // '?' character
+ }
+ }
+ return $outarr;
+ }
+
+ /**
+ * Converts UTF-8 characters array to array of Latin1 string<br>
+ * @param $unicode (array) array containing UTF-8 unicode values
+ * @return array
+ * @author Nicola Asuni
+ * @since 4.8.023 (2010-01-15)
+ * @public static
+ */
+ public static function UTF8ArrToLatin1($unicode) {
+ $outstr = ''; // string to be returned
+ foreach ($unicode as $char) {
+ if ($char < 256) {
+ $outstr .= chr($char);
+ } elseif (array_key_exists($char, TCPDF_FONT_DATA::$uni_utf8tolatin)) {
+ // map from UTF-8
+ $outstr .= chr(TCPDF_FONT_DATA::$uni_utf8tolatin[$char]);
+ } elseif ($char == 0xFFFD) {
+ // skip
+ } else {
+ $outstr .= '?';
+ }
+ }
+ return $outstr;
+ }
+
+ /**
+ * Converts UTF-8 character to integer value.<br>
+ * Invalid byte sequences will be replaced with 0xFFFD (replacement character)<br>
+ * Based on: http://www.faqs.org/rfcs/rfc3629.html
+ * <pre>
+ * Char. number range | UTF-8 octet sequence
+ * (hexadecimal) | (binary)
+ * --------------------+-----------------------------------------------
+ * 0000 0000-0000 007F | 0xxxxxxx
+ * 0000 0080-0000 07FF | 110xxxxx 10xxxxxx
+ * 0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx
+ * 0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
+ * ---------------------------------------------------------------------
+ *
+ * ABFN notation:
+ * ---------------------------------------------------------------------
+ * UTF8-octets = *( UTF8-char )
+ * UTF8-char = UTF8-1 / UTF8-2 / UTF8-3 / UTF8-4
+ * UTF8-1 = %x00-7F
+ * UTF8-2 = %xC2-DF UTF8-tail
+ *
+ * UTF8-3 = %xE0 %xA0-BF UTF8-tail / %xE1-EC 2( UTF8-tail ) /
+ * %xED %x80-9F UTF8-tail / %xEE-EF 2( UTF8-tail )
+ * UTF8-4 = %xF0 %x90-BF 2( UTF8-tail ) / %xF1-F3 3( UTF8-tail ) /
+ * %xF4 %x80-8F 2( UTF8-tail )
+ * UTF8-tail = %x80-BF
+ * ---------------------------------------------------------------------
+ * </pre>
+ * @param $uch (string) character string to process.
+ * @return integer Unicode value
+ * @author Nicola Asuni
+ * @public static
+ */
+ public static function uniord($uch) {
+ if (function_exists('mb_convert_encoding')) {
+ list(, $char) = @unpack('N', mb_convert_encoding($uch, 'UCS-4BE', 'UTF-8'));
+ if ($char >= 0) {
+ return $char;
+ }
+ }
+ $bytes = array(); // array containing single character byte sequences
+ $countbytes = 0;
+ $numbytes = 1; // number of octetc needed to represent the UTF-8 character
+ $length = strlen($uch);
+ for ($i = 0; $i < $length; ++$i) {
+ $char = ord($uch[$i]); // get one string character at time
+ if ($countbytes == 0) { // get starting octect
+ if ($char <= 0x7F) {
+ return $char; // use the character "as is" because is ASCII
+ } elseif (($char >> 0x05) == 0x06) { // 2 bytes character (0x06 = 110 BIN)
+ $bytes[] = ($char - 0xC0) << 0x06;
+ ++$countbytes;
+ $numbytes = 2;
+ } elseif (($char >> 0x04) == 0x0E) { // 3 bytes character (0x0E = 1110 BIN)
+ $bytes[] = ($char - 0xE0) << 0x0C;
+ ++$countbytes;
+ $numbytes = 3;
+ } elseif (($char >> 0x03) == 0x1E) { // 4 bytes character (0x1E = 11110 BIN)
+ $bytes[] = ($char - 0xF0) << 0x12;
+ ++$countbytes;
+ $numbytes = 4;
+ } else {
+ // use replacement character for other invalid sequences
+ return 0xFFFD;
+ }
+ } elseif (($char >> 0x06) == 0x02) { // bytes 2, 3 and 4 must start with 0x02 = 10 BIN
+ $bytes[] = $char - 0x80;
+ ++$countbytes;
+ if ($countbytes == $numbytes) {
+ // compose UTF-8 bytes to a single unicode value
+ $char = $bytes[0];
+ for ($j = 1; $j < $numbytes; ++$j) {
+ $char += ($bytes[$j] << (($numbytes - $j - 1) * 0x06));
+ }
+ if ((($char >= 0xD800) AND ($char <= 0xDFFF)) OR ($char >= 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.
+ return 0xFFFD; // use replacement character
+ } else {
+ return $char;
+ }
+ }
+ } else {
+ // use replacement character for other invalid sequences
+ return 0xFFFD;
+ }
+ }
+ return 0xFFFD;
+ }
+
+ /**
+ * Converts UTF-8 strings to codepoints array.<br>
+ * Invalid byte sequences will be replaced with 0xFFFD (replacement character)<br>
+ * @param $str (string) string to process.
+ * @param $isunicode (boolean) True when the documetn is in Unicode mode, false otherwise.
+ * @param $currentfont (array) Reference to current font array.
+ * @return array containing codepoints (UTF-8 characters values)
+ * @author Nicola Asuni
+ * @public static
+ */
+ public static function UTF8StringToArray($str, $isunicode=true, &$currentfont) {
+ if ($isunicode) {
+ // requires PCRE unicode support turned on
+ $chars = TCPDF_STATIC::pregSplit('//','u', $str, -1, PREG_SPLIT_NO_EMPTY);
+ $carr = array_map(array('self', 'uniord'), $chars);
+ } else {
+ $chars = str_split($str);
+ $carr = array_map('ord', $chars);
+ }
+ $currentfont['subsetchars'] += array_fill_keys($carr, true);
+ return $carr;
+ }
+
+ /**
+ * Converts UTF-8 strings to Latin1 when using the standard 14 core fonts.<br>
+ * @param $str (string) string to process.
+ * @param $isunicode (boolean) True when the documetn is in Unicode mode, false otherwise.
+ * @param $currentfont (array) Reference to current font array.
+ * @return string
+ * @since 3.2.000 (2008-06-23)
+ * @public static
+ */
+ public static function UTF8ToLatin1($str, $isunicode=true, &$currentfont) {
+ $unicode = self::UTF8StringToArray($str, $isunicode, $currentfont); // array containing UTF-8 unicode values
+ return self::UTF8ArrToLatin1($unicode);
+ }
+
+ /**
+ * Converts UTF-8 strings to UTF16-BE.<br>
+ * @param $str (string) string to process.
+ * @param $setbom (boolean) if true set the Byte Order Mark (BOM = 0xFEFF)
+ * @param $isunicode (boolean) True when the documetn is in Unicode mode, false otherwise.
+ * @param $currentfont (array) Reference to current font array.
+ * @return string
+ * @author Nicola Asuni
+ * @since 1.53.0.TC005 (2005-01-05)
+ * @public static
+ */
+ public static function UTF8ToUTF16BE($str, $setbom=false, $isunicode=true, &$currentfont) {
+ if (!$isunicode) {
+ return $str; // string is not in unicode
+ }
+ $unicode = self::UTF8StringToArray($str, $isunicode, $currentfont); // array containing UTF-8 unicode values
+ return self::arrUTF8ToUTF16BE($unicode, $setbom);
+ }
+
+ /**
+ * Reverse the RLT substrings using the Bidirectional Algorithm (http://unicode.org/reports/tr9/).
+ * @param $str (string) string to manipulate.
+ * @param $setbom (bool) if true set the Byte Order Mark (BOM = 0xFEFF)
+ * @param $forcertl (bool) if true forces RTL text direction
+ * @param $isunicode (boolean) True if the document is in Unicode mode, false otherwise.
+ * @param $currentfont (array) Reference to current font array.
+ * @return string
+ * @author Nicola Asuni
+ * @since 2.1.000 (2008-01-08)
+ * @public static
+ */
+ public static function utf8StrRev($str, $setbom=false, $forcertl=false, $isunicode=true, &$currentfont) {
+ return self::utf8StrArrRev(self::UTF8StringToArray($str, $isunicode, $currentfont), $str, $setbom, $forcertl, $isunicode, $currentfont);
+ }
+
+ /**
+ * Reverse the RLT substrings array using the Bidirectional Algorithm (http://unicode.org/reports/tr9/).
+ * @param $arr (array) array of unicode values.
+ * @param $str (string) string to manipulate (or empty value).
+ * @param $setbom (bool) if true set the Byte Order Mark (BOM = 0xFEFF)
+ * @param $forcertl (bool) if true forces RTL text direction
+ * @param $isunicode (boolean) True if the document is in Unicode mode, false otherwise.
+ * @param $currentfont (array) Reference to current font array.
+ * @return string
+ * @author Nicola Asuni
+ * @since 4.9.000 (2010-03-27)
+ * @public static
+ */
+ public static function utf8StrArrRev($arr, $str='', $setbom=false, $forcertl=false, $isunicode=true, &$currentfont) {
+ return self::arrUTF8ToUTF16BE(self::utf8Bidi($arr, $str, $forcertl, $isunicode, $currentfont), $setbom);
+ }
+
+ /**
+ * Reverse the RLT substrings using the Bidirectional Algorithm (http://unicode.org/reports/tr9/).
+ * @param $ta (array) array of characters composing the string.
+ * @param $str (string) string to process
+ * @param $forcertl (bool) if 'R' forces RTL, if 'L' forces LTR
+ * @param $isunicode (boolean) True if the document is in Unicode mode, false otherwise.
+ * @param $currentfont (array) Reference to current font array.
+ * @return array of unicode chars
+ * @author Nicola Asuni
+ * @since 2.4.000 (2008-03-06)
+ * @public static
+ */
+ public static function utf8Bidi($ta, $str='', $forcertl=false, $isunicode=true, &$currentfont) {
+ // paragraph embedding level
+ $pel = 0;
+ // max level
+ $maxlevel = 0;
+ if (TCPDF_STATIC::empty_string($str)) {
+ // create string from array
+ $str = self::UTF8ArrSubString($ta, '', '', $isunicode);
+ }
+ // check if string contains arabic text
+ if (preg_match(TCPDF_FONT_DATA::$uni_RE_PATTERN_ARABIC, $str)) {
+ $arabic = true;
+ } else {
+ $arabic = false;
+ }
+ // check if string contains RTL text
+ if (!($forcertl OR $arabic OR preg_match(TCPDF_FONT_DATA::$uni_RE_PATTERN_RTL, $str))) {
+ return $ta;
+ }
+
+ // get number of chars
+ $numchars = count($ta);
+
+ if ($forcertl == 'R') {
+ $pel = 1;
+ } elseif ($forcertl == 'L') {
+ $pel = 0;
+ } else {
+ // P2. In each paragraph, find the first character of type L, AL, or R.
+ // P3. If a character is found in P2 and it is of type AL or R, then set the paragraph embedding level to one; otherwise, set it to zero.
+ for ($i=0; $i < $numchars; ++$i) {
+ $type = TCPDF_FONT_DATA::$uni_type[$ta[$i]];
+ if ($type == 'L') {
+ $pel = 0;
+ break;
+ } elseif (($type == 'AL') OR ($type == 'R')) {
+ $pel = 1;
+ break;
+ }
+ }
+ }
+
+ // Current Embedding Level
+ $cel = $pel;
+ // directional override status
+ $dos = 'N';
+ $remember = array();
+ // start-of-level-run
+ $sor = $pel % 2 ? 'R' : 'L';
+ $eor = $sor;
+
+ // Array of characters data
+ $chardata = Array();
+
+ // X1. Begin by setting the current embedding level to the paragraph embedding level. Set the directional override status to neutral. Process each character iteratively, applying rules X2 through X9. Only embedding levels from 0 to 61 are valid in this phase.
+ // In the resolution of levels in rules I1 and I2, the maximum embedding level of 62 can be reached.
+ for ($i=0; $i < $numchars; ++$i) {
+ if ($ta[$i] == TCPDF_FONT_DATA::$uni_RLE) {
+ // X2. With each RLE, compute the least greater odd embedding level.
+ // a. If this new level would be valid, then this embedding code is valid. Remember (push) the current embedding level and override status. Reset the current level to this new level, and reset the override status to neutral.
+ // b. If the new level would not be valid, then this code is invalid. Do not change the current level or override status.
+ $next_level = $cel + ($cel % 2) + 1;
+ if ($next_level < 62) {
+ $remember[] = array('num' => TCPDF_FONT_DATA::$uni_RLE, 'cel' => $cel, 'dos' => $dos);
+ $cel = $next_level;
+ $dos = 'N';
+ $sor = $eor;
+ $eor = $cel % 2 ? 'R' : 'L';
+ }
+ } elseif ($ta[$i] == TCPDF_FONT_DATA::$uni_LRE) {
+ // X3. With each LRE, compute the least greater even embedding level.
+ // a. If this new level would be valid, then this embedding code is valid. Remember (push) the current embedding level and override status. Reset the current level to this new level, and reset the override status to neutral.
+ // b. If the new level would not be valid, then this code is invalid. Do not change the current level or override status.
+ $next_level = $cel + 2 - ($cel % 2);
+ if ( $next_level < 62 ) {
+ $remember[] = array('num' => TCPDF_FONT_DATA::$uni_LRE, 'cel' => $cel, 'dos' => $dos);
+ $cel = $next_level;
+ $dos = 'N';
+ $sor = $eor;
+ $eor = $cel % 2 ? 'R' : 'L';
+ }
+ } elseif ($ta[$i] == TCPDF_FONT_DATA::$uni_RLO) {
+ // X4. With each RLO, compute the least greater odd embedding level.
+ // a. If this new level would be valid, then this embedding code is valid. Remember (push) the current embedding level and override status. Reset the current level to this new level, and reset the override status to right-to-left.
+ // b. If the new level would not be valid, then this code is invalid. Do not change the current level or override status.
+ $next_level = $cel + ($cel % 2) + 1;
+ if ($next_level < 62) {
+ $remember[] = array('num' => TCPDF_FONT_DATA::$uni_RLO, 'cel' => $cel, 'dos' => $dos);
+ $cel = $next_level;
+ $dos = 'R';
+ $sor = $eor;
+ $eor = $cel % 2 ? 'R' : 'L';
+ }
+ } elseif ($ta[$i] == TCPDF_FONT_DATA::$uni_LRO) {
+ // X5. With each LRO, compute the least greater even embedding level.
+ // a. If this new level would be valid, then this embedding code is valid. Remember (push) the current embedding level and override status. Reset the current level to this new level, and reset the override status to left-to-right.
+ // b. If the new level would not be valid, then this code is invalid. Do not change the current level or override status.
+ $next_level = $cel + 2 - ($cel % 2);
+ if ( $next_level < 62 ) {
+ $remember[] = array('num' => TCPDF_FONT_DATA::$uni_LRO, 'cel' => $cel, 'dos' => $dos);
+ $cel = $next_level;
+ $dos = 'L';
+ $sor = $eor;
+ $eor = $cel % 2 ? 'R' : 'L';
+ }
+ } elseif ($ta[$i] == TCPDF_FONT_DATA::$uni_PDF) {
+ // X7. With each PDF, determine the matching embedding or override code. If there was a valid matching code, restore (pop) the last remembered (pushed) embedding level and directional override.
+ if (count($remember)) {
+ $last = count($remember ) - 1;
+ if (($remember[$last]['num'] == TCPDF_FONT_DATA::$uni_RLE) OR
+ ($remember[$last]['num'] == TCPDF_FONT_DATA::$uni_LRE) OR
+ ($remember[$last]['num'] == TCPDF_FONT_DATA::$uni_RLO) OR
+ ($remember[$last]['num'] == TCPDF_FONT_DATA::$uni_LRO)) {
+ $match = array_pop($remember);
+ $cel = $match['cel'];
+ $dos = $match['dos'];
+ $sor = $eor;
+ $eor = ($cel > $match['cel'] ? $cel : $match['cel']) % 2 ? 'R' : 'L';
+ }
+ }
+ } elseif (($ta[$i] != TCPDF_FONT_DATA::$uni_RLE) AND
+ ($ta[$i] != TCPDF_FONT_DATA::$uni_LRE) AND
+ ($ta[$i] != TCPDF_FONT_DATA::$uni_RLO) AND
+ ($ta[$i] != TCPDF_FONT_DATA::$uni_LRO) AND
+ ($ta[$i] != TCPDF_FONT_DATA::$uni_PDF)) {
+ // X6. For all types besides RLE, LRE, RLO, LRO, and PDF:
+ // a. Set the level of the current character to the current embedding level.
+ // b. Whenever the directional override status is not neutral, reset the current character type to the directional override status.
+ if ($dos != 'N') {
+ $chardir = $dos;
+ } else {
+ if (isset(TCPDF_FONT_DATA::$uni_type[$ta[$i]])) {
+ $chardir = TCPDF_FONT_DATA::$uni_type[$ta[$i]];
+ } else {
+ $chardir = 'L';
+ }
+ }
+ // stores string characters and other information
+ $chardata[] = array('char' => $ta[$i], 'level' => $cel, 'type' => $chardir, 'sor' => $sor, 'eor' => $eor);
+ }
+ } // end for each char
+
+ // X8. All explicit directional embeddings and overrides are completely terminated at the end of each paragraph. Paragraph separators are not included in the embedding.
+ // X9. Remove all RLE, LRE, RLO, LRO, PDF, and BN codes.
+ // X10. The remaining rules are applied to each run of characters at the same level. For each run, determine the start-of-level-run (sor) and end-of-level-run (eor) type, either L or R. This depends on the higher of the two levels on either side of the boundary (at the start or end of the paragraph, the level of the 'other' run is the base embedding level). If the higher level is odd, the type is R; otherwise, it is L.
+
+ // 3.3.3 Resolving Weak Types
+ // Weak types are now resolved one level run at a time. At level run boundaries where the type of the character on the other side of the boundary is required, the type assigned to sor or eor is used.
+ // Nonspacing marks are now resolved based on the previous characters.
+ $numchars = count($chardata);
+
+ // W1. Examine each nonspacing mark (NSM) in the level run, and change the type of the NSM to the type of the previous character. If the NSM is at the start of the level run, it will get the type of sor.
+ $prevlevel = -1; // track level changes
+ $levcount = 0; // counts consecutive chars at the same level
+ for ($i=0; $i < $numchars; ++$i) {
+ if ($chardata[$i]['type'] == 'NSM') {
+ if ($levcount) {
+ $chardata[$i]['type'] = $chardata[$i]['sor'];
+ } elseif ($i > 0) {
+ $chardata[$i]['type'] = $chardata[($i-1)]['type'];
+ }
+ }
+ if ($chardata[$i]['level'] != $prevlevel) {
+ $levcount = 0;
+ } else {
+ ++$levcount;
+ }
+ $prevlevel = $chardata[$i]['level'];
+ }
+
+ // W2. Search backward from each instance of a European number until the first strong type (R, L, AL, or sor) is found. If an AL is found, change the type of the European number to Arabic number.
+ $prevlevel = -1;
+ $levcount = 0;
+ for ($i=0; $i < $numchars; ++$i) {
+ if ($chardata[$i]['char'] == 'EN') {
+ for ($j=$levcount; $j >= 0; $j--) {
+ if ($chardata[$j]['type'] == 'AL') {
+ $chardata[$i]['type'] = 'AN';
+ } elseif (($chardata[$j]['type'] == 'L') OR ($chardata[$j]['type'] == 'R')) {
+ break;
+ }
+ }
+ }
+ if ($chardata[$i]['level'] != $prevlevel) {
+ $levcount = 0;
+ } else {
+ ++$levcount;
+ }
+ $prevlevel = $chardata[$i]['level'];
+ }
+
+ // W3. Change all ALs to R.
+ for ($i=0; $i < $numchars; ++$i) {
+ if ($chardata[$i]['type'] == 'AL') {
+ $chardata[$i]['type'] = 'R';
+ }
+ }
+
+ // W4. A single European separator between two European numbers changes to a European number. A single common separator between two numbers of the same type changes to that type.
+ $prevlevel = -1;
+ $levcount = 0;
+ for ($i=0; $i < $numchars; ++$i) {
+ if (($levcount > 0) AND (($i+1) < $numchars) AND ($chardata[($i+1)]['level'] == $prevlevel)) {
+ if (($chardata[$i]['type'] == 'ES') AND ($chardata[($i-1)]['type'] == 'EN') AND ($chardata[($i+1)]['type'] == 'EN')) {
+ $chardata[$i]['type'] = 'EN';
+ } elseif (($chardata[$i]['type'] == 'CS') AND ($chardata[($i-1)]['type'] == 'EN') AND ($chardata[($i+1)]['type'] == 'EN')) {
+ $chardata[$i]['type'] = 'EN';
+ } elseif (($chardata[$i]['type'] == 'CS') AND ($chardata[($i-1)]['type'] == 'AN') AND ($chardata[($i+1)]['type'] == 'AN')) {
+ $chardata[$i]['type'] = 'AN';
+ }
+ }
+ if ($chardata[$i]['level'] != $prevlevel) {
+ $levcount = 0;
+ } else {
+ ++$levcount;
+ }
+ $prevlevel = $chardata[$i]['level'];
+ }
+
+ // W5. A sequence of European terminators adjacent to European numbers changes to all European numbers.
+ $prevlevel = -1;
+ $levcount = 0;
+ for ($i=0; $i < $numchars; ++$i) {
+ if ($chardata[$i]['type'] == 'ET') {
+ if (($levcount > 0) AND ($chardata[($i-1)]['type'] == 'EN')) {
+ $chardata[$i]['type'] = 'EN';
+ } else {
+ $j = $i+1;
+ while (($j < $numchars) AND ($chardata[$j]['level'] == $prevlevel)) {
+ if ($chardata[$j]['type'] == 'EN') {
+ $chardata[$i]['type'] = 'EN';
+ break;
+ } elseif ($chardata[$j]['type'] != 'ET') {
+ break;
+ }
+ ++$j;
+ }
+ }
+ }
+ if ($chardata[$i]['level'] != $prevlevel) {
+ $levcount = 0;
+ } else {
+ ++$levcount;
+ }
+ $prevlevel = $chardata[$i]['level'];
+ }
+
+ // W6. Otherwise, separators and terminators change to Other Neutral.
+ $prevlevel = -1;
+ $levcount = 0;
+ for ($i=0; $i < $numchars; ++$i) {
+ if (($chardata[$i]['type'] == 'ET') OR ($chardata[$i]['type'] == 'ES') OR ($chardata[$i]['type'] == 'CS')) {
+ $chardata[$i]['type'] = 'ON';
+ }
+ if ($chardata[$i]['level'] != $prevlevel) {
+ $levcount = 0;
+ } else {
+ ++$levcount;
+ }
+ $prevlevel = $chardata[$i]['level'];
+ }
+
+ //W7. Search backward from each instance of a European number until the first strong type (R, L, or sor) is found. If an L is found, then change the type of the European number to L.
+ $prevlevel = -1;
+ $levcount = 0;
+ for ($i=0; $i < $numchars; ++$i) {
+ if ($chardata[$i]['char'] == 'EN') {
+ for ($j=$levcount; $j >= 0; $j--) {
+ if ($chardata[$j]['type'] == 'L') {
+ $chardata[$i]['type'] = 'L';
+ } elseif ($chardata[$j]['type'] == 'R') {
+ break;
+ }
+ }
+ }
+ if ($chardata[$i]['level'] != $prevlevel) {
+ $levcount = 0;
+ } else {
+ ++$levcount;
+ }
+ $prevlevel = $chardata[$i]['level'];
+ }
+
+ // N1. A sequence of neutrals takes the direction of the surrounding strong text if the text on both sides has the same direction. European and Arabic numbers act as if they were R in terms of their influence on neutrals. Start-of-level-run (sor) and end-of-level-run (eor) are used at level run boundaries.
+ $prevlevel = -1;
+ $levcount = 0;
+ for ($i=0; $i < $numchars; ++$i) {
+ if (($levcount > 0) AND (($i+1) < $numchars) AND ($chardata[($i+1)]['level'] == $prevlevel)) {
+ if (($chardata[$i]['type'] == 'N') AND ($chardata[($i-1)]['type'] == 'L') AND ($chardata[($i+1)]['type'] == 'L')) {
+ $chardata[$i]['type'] = 'L';
+ } elseif (($chardata[$i]['type'] == 'N') AND
+ (($chardata[($i-1)]['type'] == 'R') OR ($chardata[($i-1)]['type'] == 'EN') OR ($chardata[($i-1)]['type'] == 'AN')) AND
+ (($chardata[($i+1)]['type'] == 'R') OR ($chardata[($i+1)]['type'] == 'EN') OR ($chardata[($i+1)]['type'] == 'AN'))) {
+ $chardata[$i]['type'] = 'R';
+ } elseif ($chardata[$i]['type'] == 'N') {
+ // N2. Any remaining neutrals take the embedding direction
+ $chardata[$i]['type'] = $chardata[$i]['sor'];
+ }
+ } elseif (($levcount == 0) AND (($i+1) < $numchars) AND ($chardata[($i+1)]['level'] == $prevlevel)) {
+ // first char
+ if (($chardata[$i]['type'] == 'N') AND ($chardata[$i]['sor'] == 'L') AND ($chardata[($i+1)]['type'] == 'L')) {
+ $chardata[$i]['type'] = 'L';
+ } elseif (($chardata[$i]['type'] == 'N') AND
+ (($chardata[$i]['sor'] == 'R') OR ($chardata[$i]['sor'] == 'EN') OR ($chardata[$i]['sor'] == 'AN')) AND
+ (($chardata[($i+1)]['type'] == 'R') OR ($chardata[($i+1)]['type'] == 'EN') OR ($chardata[($i+1)]['type'] == 'AN'))) {
+ $chardata[$i]['type'] = 'R';
+ } elseif ($chardata[$i]['type'] == 'N') {
+ // N2. Any remaining neutrals take the embedding direction
+ $chardata[$i]['type'] = $chardata[$i]['sor'];
+ }
+ } elseif (($levcount > 0) AND ((($i+1) == $numchars) OR (($i+1) < $numchars) AND ($chardata[($i+1)]['level'] != $prevlevel))) {
+ //last char
+ if (($chardata[$i]['type'] == 'N') AND ($chardata[($i-1)]['type'] == 'L') AND ($chardata[$i]['eor'] == 'L')) {
+ $chardata[$i]['type'] = 'L';
+ } elseif (($chardata[$i]['type'] == 'N') AND
+ (($chardata[($i-1)]['type'] == 'R') OR ($chardata[($i-1)]['type'] == 'EN') OR ($chardata[($i-1)]['type'] == 'AN')) AND
+ (($chardata[$i]['eor'] == 'R') OR ($chardata[$i]['eor'] == 'EN') OR ($chardata[$i]['eor'] == 'AN'))) {
+ $chardata[$i]['type'] = 'R';
+ } elseif ($chardata[$i]['type'] == 'N') {
+ // N2. Any remaining neutrals take the embedding direction
+ $chardata[$i]['type'] = $chardata[$i]['sor'];
+ }
+ } elseif ($chardata[$i]['type'] == 'N') {
+ // N2. Any remaining neutrals take the embedding direction
+ $chardata[$i]['type'] = $chardata[$i]['sor'];
+ }
+ if ($chardata[$i]['level'] != $prevlevel) {
+ $levcount = 0;
+ } else {
+ ++$levcount;
+ }
+ $prevlevel = $chardata[$i]['level'];
+ }
+
+ // I1. For all characters with an even (left-to-right) embedding direction, those of type R go up one level and those of type AN or EN go up two levels.
+ // I2. For all characters with an odd (right-to-left) embedding direction, those of type L, EN or AN go up one level.
+ for ($i=0; $i < $numchars; ++$i) {
+ $odd = $chardata[$i]['level'] % 2;
+ if ($odd) {
+ if (($chardata[$i]['type'] == 'L') OR ($chardata[$i]['type'] == 'AN') OR ($chardata[$i]['type'] == 'EN')) {
+ $chardata[$i]['level'] += 1;
+ }
+ } else {
+ if ($chardata[$i]['type'] == 'R') {
+ $chardata[$i]['level'] += 1;
+ } elseif (($chardata[$i]['type'] == 'AN') OR ($chardata[$i]['type'] == 'EN')) {
+ $chardata[$i]['level'] += 2;
+ }
+ }
+ $maxlevel = max($chardata[$i]['level'],$maxlevel);
+ }
+
+ // L1. On each line, reset the embedding level of the following characters to the paragraph embedding level:
+ // 1. Segment separators,
+ // 2. Paragraph separators,
+ // 3. Any sequence of whitespace characters preceding a segment separator or paragraph separator, and
+ // 4. Any sequence of white space characters at the end of the line.
+ for ($i=0; $i < $numchars; ++$i) {
+ if (($chardata[$i]['type'] == 'B') OR ($chardata[$i]['type'] == 'S')) {
+ $chardata[$i]['level'] = $pel;
+ } elseif ($chardata[$i]['type'] == 'WS') {
+ $j = $i+1;
+ while ($j < $numchars) {
+ if ((($chardata[$j]['type'] == 'B') OR ($chardata[$j]['type'] == 'S')) OR
+ (($j == ($numchars-1)) AND ($chardata[$j]['type'] == 'WS'))) {
+ $chardata[$i]['level'] = $pel;
+ break;
+ } elseif ($chardata[$j]['type'] != 'WS') {
+ break;
+ }
+ ++$j;
+ }
+ }
+ }
+
+ // Arabic Shaping
+ // Cursively connected scripts, such as Arabic or Syriac, require the selection of positional character shapes that depend on adjacent characters. Shaping is logically applied after the Bidirectional Algorithm is used and is limited to characters within the same directional run.
+ if ($arabic) {
+ $endedletter = array(1569,1570,1571,1572,1573,1575,1577,1583,1584,1585,1586,1608,1688);
+ $alfletter = array(1570,1571,1573,1575);
+ $chardata2 = $chardata;
+ $laaletter = false;
+ $charAL = array();
+ $x = 0;
+ for ($i=0; $i < $numchars; ++$i) {
+ if ((TCPDF_FONT_DATA::$uni_type[$chardata[$i]['char']] == 'AL') OR ($chardata[$i]['char'] == 32) OR ($chardata[$i]['char'] == 8204)) {
+ $charAL[$x] = $chardata[$i];
+ $charAL[$x]['i'] = $i;
+ $chardata[$i]['x'] = $x;
+ ++$x;
+ }
+ }
+ $numAL = $x;
+ for ($i=0; $i < $numchars; ++$i) {
+ $thischar = $chardata[$i];
+ if ($i > 0) {
+ $prevchar = $chardata[($i-1)];
+ } else {
+ $prevchar = false;
+ }
+ if (($i+1) < $numchars) {
+ $nextchar = $chardata[($i+1)];
+ } else {
+ $nextchar = false;
+ }
+ if (TCPDF_FONT_DATA::$uni_type[$thischar['char']] == 'AL') {
+ $x = $thischar['x'];
+ if ($x > 0) {
+ $prevchar = $charAL[($x-1)];
+ } else {
+ $prevchar = false;
+ }
+ if (($x+1) < $numAL) {
+ $nextchar = $charAL[($x+1)];
+ } else {
+ $nextchar = false;
+ }
+ // if laa letter
+ if (($prevchar !== false) AND ($prevchar['char'] == 1604) AND (in_array($thischar['char'], $alfletter))) {
+ $arabicarr = TCPDF_FONT_DATA::$uni_laa_array;
+ $laaletter = true;
+ if ($x > 1) {
+ $prevchar = $charAL[($x-2)];
+ } else {
+ $prevchar = false;
+ }
+ } else {
+ $arabicarr = TCPDF_FONT_DATA::$uni_arabicsubst;
+ $laaletter = false;
+ }
+ if (($prevchar !== false) AND ($nextchar !== false) AND
+ ((TCPDF_FONT_DATA::$uni_type[$prevchar['char']] == 'AL') OR (TCPDF_FONT_DATA::$uni_type[$prevchar['char']] == 'NSM')) AND
+ ((TCPDF_FONT_DATA::$uni_type[$nextchar['char']] == 'AL') OR (TCPDF_FONT_DATA::$uni_type[$nextchar['char']] == 'NSM')) AND
+ ($prevchar['type'] == $thischar['type']) AND
+ ($nextchar['type'] == $thischar['type']) AND
+ ($nextchar['char'] != 1567)) {
+ if (in_array($prevchar['char'], $endedletter)) {
+ if (isset($arabicarr[$thischar['char']][2])) {
+ // initial
+ $chardata2[$i]['char'] = $arabicarr[$thischar['char']][2];
+ }
+ } else {
+ if (isset($arabicarr[$thischar['char']][3])) {
+ // medial
+ $chardata2[$i]['char'] = $arabicarr[$thischar['char']][3];
+ }
+ }
+ } elseif (($nextchar !== false) AND
+ ((TCPDF_FONT_DATA::$uni_type[$nextchar['char']] == 'AL') OR (TCPDF_FONT_DATA::$uni_type[$nextchar['char']] == 'NSM')) AND
+ ($nextchar['type'] == $thischar['type']) AND
+ ($nextchar['char'] != 1567)) {
+ if (isset($arabicarr[$chardata[$i]['char']][2])) {
+ // initial
+ $chardata2[$i]['char'] = $arabicarr[$thischar['char']][2];
+ }
+ } elseif ((($prevchar !== false) AND
+ ((TCPDF_FONT_DATA::$uni_type[$prevchar['char']] == 'AL') OR (TCPDF_FONT_DATA::$uni_type[$prevchar['char']] == 'NSM')) AND
+ ($prevchar['type'] == $thischar['type'])) OR
+ (($nextchar !== false) AND ($nextchar['char'] == 1567))) {
+ // final
+ if (($i > 1) AND ($thischar['char'] == 1607) AND
+ ($chardata[$i-1]['char'] == 1604) AND
+ ($chardata[$i-2]['char'] == 1604)) {
+ //Allah Word
+ // mark characters to delete with false
+ $chardata2[$i-2]['char'] = false;
+ $chardata2[$i-1]['char'] = false;
+ $chardata2[$i]['char'] = 65010;
+ } else {
+ if (($prevchar !== false) AND in_array($prevchar['char'], $endedletter)) {
+ if (isset($arabicarr[$thischar['char']][0])) {
+ // isolated
+ $chardata2[$i]['char'] = $arabicarr[$thischar['char']][0];
+ }
+ } else {
+ if (isset($arabicarr[$thischar['char']][1])) {
+ // final
+ $chardata2[$i]['char'] = $arabicarr[$thischar['char']][1];
+ }
+ }
+ }
+ } elseif (isset($arabicarr[$thischar['char']][0])) {
+ // isolated
+ $chardata2[$i]['char'] = $arabicarr[$thischar['char']][0];
+ }
+ // if laa letter
+ if ($laaletter) {
+ // mark characters to delete with false
+ $chardata2[($charAL[($x-1)]['i'])]['char'] = false;
+ }
+ } // end if AL (Arabic Letter)
+ } // end for each char
+ /*
+ * Combining characters that can occur with Arabic Shadda (0651 HEX, 1617 DEC) are replaced.
+ * Putting the combining mark and shadda in the same glyph allows us to avoid the two marks overlapping each other in an illegible manner.
+ */
+ for ($i = 0; $i < ($numchars-1); ++$i) {
+ if (($chardata2[$i]['char'] == 1617) AND (isset(TCPDF_FONT_DATA::$uni_diacritics[($chardata2[$i+1]['char'])]))) {
+ // check if the subtitution font is defined on current font
+ if (isset($currentfont['cw'][(TCPDF_FONT_DATA::$uni_diacritics[($chardata2[$i+1]['char'])])])) {
+ $chardata2[$i]['char'] = false;
+ $chardata2[$i+1]['char'] = TCPDF_FONT_DATA::$uni_diacritics[($chardata2[$i+1]['char'])];
+ }
+ }
+ }
+ // remove marked characters
+ foreach ($chardata2 as $key => $value) {
+ if ($value['char'] === false) {
+ unset($chardata2[$key]);
+ }
+ }
+ $chardata = array_values($chardata2);
+ $numchars = count($chardata);
+ unset($chardata2);
+ unset($arabicarr);
+ unset($laaletter);
+ unset($charAL);
+ }
+
+ // L2. From the highest level found in the text to the lowest odd level on each line, including intermediate levels not actually present in the text, reverse any contiguous sequence of characters that are at that level or higher.
+ for ($j=$maxlevel; $j > 0; $j--) {
+ $ordarray = Array();
+ $revarr = Array();
+ $onlevel = false;
+ for ($i=0; $i < $numchars; ++$i) {
+ if ($chardata[$i]['level'] >= $j) {
+ $onlevel = true;
+ if (isset(TCPDF_FONT_DATA::$uni_mirror[$chardata[$i]['char']])) {
+ // L4. A character is depicted by a mirrored glyph if and only if (a) the resolved directionality of that character is R, and (b) the Bidi_Mirrored property value of that character is true.
+ $chardata[$i]['char'] = TCPDF_FONT_DATA::$uni_mirror[$chardata[$i]['char']];
+ }
+ $revarr[] = $chardata[$i];
+ } else {
+ if ($onlevel) {
+ $revarr = array_reverse($revarr);
+ $ordarray = array_merge($ordarray, $revarr);
+ $revarr = Array();
+ $onlevel = false;
+ }
+ $ordarray[] = $chardata[$i];
+ }
+ }
+ if ($onlevel) {
+ $revarr = array_reverse($revarr);
+ $ordarray = array_merge($ordarray, $revarr);
+ }
+ $chardata = $ordarray;
+ }
+ $ordarray = array();
+ foreach ($chardata as $cd) {
+ $ordarray[] = $cd['char'];
+ // store char values for subsetting
+ $currentfont['subsetchars'][$cd['char']] = true;
+ }
+ return $ordarray;
+ }
+
+ /**
+ * Get a reference font size.
+ * @param $size (string) String containing font size value.
+ * @param $refsize (float) Reference font size in points.
+ * @return float value in points
+ * @public static
+ */
+ public static function getFontRefSize($size, $refsize=12) {
+ switch ($size) {
+ case 'xx-small': {
+ $size = ($refsize - 4);
+ break;
+ }
+ case 'x-small': {
+ $size = ($refsize - 3);
+ break;
+ }
+ case 'small': {
+ $size = ($refsize - 2);
+ break;
+ }
+ case 'medium': {
+ $size = $refsize;
+ break;
+ }
+ case 'large': {
+ $size = ($refsize + 2);
+ break;
+ }
+ case 'x-large': {
+ $size = ($refsize + 4);
+ break;
+ }
+ case 'xx-large': {
+ $size = ($refsize + 6);
+ break;
+ }
+ case 'smaller': {
+ $size = ($refsize - 3);
+ break;
+ }
+ case 'larger': {
+ $size = ($refsize + 3);
+ break;
+ }
+ }
+ return $size;
+ }
+
+} // END OF TCPDF_FONTS CLASS
+
+//============================================================+
+// END OF FILE
+//============================================================+
diff --git a/libraries/tcpdf/include/tcpdf_images.php b/libraries/tcpdf/include/tcpdf_images.php
new file mode 100644
index 0000000000..525649cb05
--- /dev/null
+++ b/libraries/tcpdf/include/tcpdf_images.php
@@ -0,0 +1,355 @@
+<?php
+//============================================================+
+// File name : tcpdf_images.php
+// Version : 1.0.001
+// Begin : 2002-08-03
+// Last Update : 2013-08-05
+// Author : Nicola Asuni - Tecnick.com LTD - www.tecnick.com - info@tecnick.com
+// License : GNU-LGPL v3 (http://www.gnu.org/copyleft/lesser.html)
+// -------------------------------------------------------------------
+// Copyright (C) 2002-2013 Nicola Asuni - Tecnick.com LTD
+//
+// This file is part of TCPDF software library.
+//
+// TCPDF 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 3 of the
+// License, or (at your option) any later version.
+//
+// TCPDF 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 License
+// along with TCPDF. If not, see
+// <http://www.tecnick.com/pagefiles/tcpdf/LICENSE.TXT>.
+//
+// See LICENSE.TXT file for more information.
+// -------------------------------------------------------------------
+//
+// Description :
+// Static image methods used by the TCPDF class.
+//
+//============================================================+
+
+/**
+ * @file
+ * This is a PHP class that contains static image methods for the TCPDF class.<br>
+ * @package com.tecnick.tcpdf
+ * @author Nicola Asuni
+ * @version 1.0.001
+ */
+
+/**
+ * @class TCPDF_IMAGES
+ * Static image methods used by the TCPDF class.
+ * @package com.tecnick.tcpdf
+ * @brief PHP class for generating PDF documents without requiring external extensions.
+ * @version 1.0.000
+ * @author Nicola Asuni - info@tecnick.com
+ */
+class TCPDF_IMAGES {
+
+ /**
+ * Array of hinheritable SVG properties.
+ * @since 5.0.000 (2010-05-02)
+ * @public static
+ */
+ public static $svginheritprop = array('clip-rule', 'color', 'color-interpolation', 'color-interpolation-filters', 'color-profile', 'color-rendering', 'cursor', 'direction', 'fill', 'fill-opacity', 'fill-rule', 'font', 'font-family', 'font-size', 'font-size-adjust', 'font-stretch', 'font-style', 'font-variant', 'font-weight', 'glyph-orientation-horizontal', 'glyph-orientation-vertical', 'image-rendering', 'kerning', 'letter-spacing', 'marker', 'marker-end', 'marker-mid', 'marker-start', 'pointer-events', 'shape-rendering', 'stroke', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', 'text-anchor', 'text-rendering', 'visibility', 'word-spacing', 'writing-mode');
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+ /**
+ * Return the image type given the file name or array returned by getimagesize() function.
+ * @param $imgfile (string) image file name
+ * @param $iminfo (array) array of image information returned by getimagesize() function.
+ * @return string image type
+ * @since 4.8.017 (2009-11-27)
+ * @public static
+ */
+ public static function getImageFileType($imgfile, $iminfo=array()) {
+ $type = '';
+ if (isset($iminfo['mime']) AND !empty($iminfo['mime'])) {
+ $mime = explode('/', $iminfo['mime']);
+ if ((count($mime) > 1) AND ($mime[0] == 'image') AND (!empty($mime[1]))) {
+ $type = strtolower(trim($mime[1]));
+ }
+ }
+ if (empty($type)) {
+ $fileinfo = pathinfo($imgfile);
+ if (isset($fileinfo['extension']) AND (!TCPDF_STATIC::empty_string($fileinfo['extension']))) {
+ $type = strtolower(trim($fileinfo['extension']));
+ }
+ }
+ if ($type == 'jpg') {
+ $type = 'jpeg';
+ }
+ return $type;
+ }
+
+ /**
+ * Set the transparency for the given GD image.
+ * @param $new_image (image) GD image object
+ * @param $image (image) GD image object.
+ * return GD image object.
+ * @since 4.9.016 (2010-04-20)
+ * @public static
+ */
+ public static function setGDImageTransparency($new_image, $image) {
+ // transparency index
+ $tid = imagecolortransparent($image);
+ // default transparency color
+ $tcol = array('red' => 255, 'green' => 255, 'blue' => 255);
+ if ($tid >= 0) {
+ // get the colors for the transparency index
+ $tcol = imagecolorsforindex($image, $tid);
+ }
+ $tid = imagecolorallocate($new_image, $tcol['red'], $tcol['green'], $tcol['blue']);
+ imagefill($new_image, 0, 0, $tid);
+ imagecolortransparent($new_image, $tid);
+ return $new_image;
+ }
+
+ /**
+ * Convert the loaded image to a PNG and then return a structure for the PDF creator.
+ * This function requires GD library and write access to the directory defined on K_PATH_CACHE constant.
+ * @param $image (image) Image object.
+ * return image PNG image object.
+ * @since 4.9.016 (2010-04-20)
+ * @public static
+ */
+ public static function _toPNG($image) {
+ // set temporary image file name
+ $tempname = TCPDF_STATIC::getObjFilename('png');
+ // turn off interlaced mode
+ imageinterlace($image, 0);
+ // create temporary PNG image
+ imagepng($image, $tempname);
+ // remove image from memory
+ imagedestroy($image);
+ // get PNG image data
+ $retvars = self::_parsepng($tempname);
+ // tidy up by removing temporary image
+ unlink($tempname);
+ return $retvars;
+ }
+
+ /**
+ * Convert the loaded image to a JPEG and then return a structure for the PDF creator.
+ * This function requires GD library and write access to the directory defined on K_PATH_CACHE constant.
+ * @param $image (image) Image object.
+ * @param $quality (int) JPEG quality.
+ * return image JPEG image object.
+ * @public static
+ */
+ public static function _toJPEG($image, $quality) {
+ $tempname = TCPDF_STATIC::getObjFilename('jpg');
+ imagejpeg($image, $tempname, $quality);
+ imagedestroy($image);
+ $retvars = self::_parsejpeg($tempname);
+ // tidy up by removing temporary image
+ unlink($tempname);
+ return $retvars;
+ }
+
+ /**
+ * Extract info from a JPEG file without using the GD library.
+ * @param $file (string) image file to parse
+ * @return array structure containing the image data
+ * @public static
+ */
+ public static function _parsejpeg($file) {
+ $a = getimagesize($file);
+ if (empty($a)) {
+ //Missing or incorrect image file
+ return false;
+ }
+ if ($a[2] != 2) {
+ // Not a JPEG file
+ return false;
+ }
+ // bits per pixel
+ $bpc = isset($a['bits']) ? intval($a['bits']) : 8;
+ // number of image channels
+ if (!isset($a['channels'])) {
+ $channels = 3;
+ } else {
+ $channels = intval($a['channels']);
+ }
+ // default colour space
+ switch ($channels) {
+ case 1: {
+ $colspace = 'DeviceGray';
+ break;
+ }
+ case 3: {
+ $colspace = 'DeviceRGB';
+ break;
+ }
+ case 4: {
+ $colspace = 'DeviceCMYK';
+ break;
+ }
+ default: {
+ $channels = 3;
+ $colspace = 'DeviceRGB';
+ break;
+ }
+ }
+ // get file content
+ $data = file_get_contents($file);
+ // check for embedded ICC profile
+ $icc = array();
+ $offset = 0;
+ while (($pos = strpos($data, "ICC_PROFILE\0", $offset)) !== false) {
+ // get ICC sequence length
+ $length = (TCPDF_STATIC::_getUSHORT($data, ($pos - 2)) - 16);
+ // marker sequence number
+ $msn = max(1, ord($data[($pos + 12)]));
+ // number of markers (total of APP2 used)
+ $nom = max(1, ord($data[($pos + 13)]));
+ // get sequence segment
+ $icc[($msn - 1)] = substr($data, ($pos + 14), $length);
+ // move forward to next sequence
+ $offset = ($pos + 14 + $length);
+ }
+ // order and compact ICC segments
+ if (count($icc) > 0) {
+ ksort($icc);
+ $icc = implode('', $icc);
+ if ((ord($icc{36}) != 0x61) OR (ord($icc{37}) != 0x63) OR (ord($icc{38}) != 0x73) OR (ord($icc{39}) != 0x70)) {
+ // invalid ICC profile
+ $icc = false;
+ }
+ } else {
+ $icc = false;
+ }
+ return array('w' => $a[0], 'h' => $a[1], 'ch' => $channels, 'icc' => $icc, 'cs' => $colspace, 'bpc' => $bpc, 'f' => 'DCTDecode', 'data' => $data);
+ }
+
+ /**
+ * Extract info from a PNG file without using the GD library.
+ * @param $file (string) image file to parse
+ * @return array structure containing the image data
+ * @public static
+ */
+ public static function _parsepng($file) {
+ $f = fopen($file, 'rb');
+ if ($f === false) {
+ // Can't open image file
+ return false;
+ }
+ //Check signature
+ if (fread($f, 8) != chr(137).'PNG'.chr(13).chr(10).chr(26).chr(10)) {
+ // Not a PNG file
+ return false;
+ }
+ //Read header chunk
+ fread($f, 4);
+ if (fread($f, 4) != 'IHDR') {
+ //Incorrect PNG file
+ return false;
+ }
+ $w = TCPDF_STATIC::_freadint($f);
+ $h = TCPDF_STATIC::_freadint($f);
+ $bpc = ord(fread($f, 1));
+ $ct = ord(fread($f, 1));
+ if ($ct == 0) {
+ $colspace = 'DeviceGray';
+ } elseif ($ct == 2) {
+ $colspace = 'DeviceRGB';
+ } elseif ($ct == 3) {
+ $colspace = 'Indexed';
+ } else {
+ // alpha channel
+ fclose($f);
+ return 'pngalpha';
+ }
+ if (ord(fread($f, 1)) != 0) {
+ // Unknown compression method
+ fclose($f);
+ return false;
+ }
+ if (ord(fread($f, 1)) != 0) {
+ // Unknown filter method
+ fclose($f);
+ return false;
+ }
+ if (ord(fread($f, 1)) != 0) {
+ // Interlacing not supported
+ fclose($f);
+ return false;
+ }
+ fread($f, 4);
+ $channels = ($ct == 2 ? 3 : 1);
+ $parms = '/DecodeParms << /Predictor 15 /Colors '.$channels.' /BitsPerComponent '.$bpc.' /Columns '.$w.' >>';
+ //Scan chunks looking for palette, transparency and image data
+ $pal = '';
+ $trns = '';
+ $data = '';
+ $icc = false;
+ do {
+ $n = TCPDF_STATIC::_freadint($f);
+ $type = fread($f, 4);
+ if ($type == 'PLTE') {
+ // read palette
+ $pal = TCPDF_STATIC::rfread($f, $n);
+ fread($f, 4);
+ } elseif ($type == 'tRNS') {
+ // read transparency info
+ $t = TCPDF_STATIC::rfread($f, $n);
+ if ($ct == 0) {
+ $trns = array(ord($t{1}));
+ } elseif ($ct == 2) {
+ $trns = array(ord($t{1}), ord($t{3}), ord($t{5}));
+ } else {
+ $pos = strpos($t, chr(0));
+ if ($pos !== false) {
+ $trns = array($pos);
+ }
+ }
+ fread($f, 4);
+ } elseif ($type == 'IDAT') {
+ // read image data block
+ $data .= TCPDF_STATIC::rfread($f, $n);
+ fread($f, 4);
+ } elseif ($type == 'iCCP') {
+ // skip profile name
+ $len = 0;
+ while ((ord(fread($f, 1)) > 0) AND ($len < 80)) {
+ ++$len;
+ }
+ // skip null separator
+ fread($f, 1);
+ // get compression method
+ if (ord(fread($f, 1)) != 0) {
+ // Unknown filter method
+ fclose($f);
+ return false;
+ }
+ // read ICC Color Profile
+ $icc = TCPDF_STATIC::rfread($f, ($n - $len - 2));
+ // decompress profile
+ $icc = gzuncompress($icc);
+ fread($f, 4);
+ } elseif ($type == 'IEND') {
+ break;
+ } else {
+ TCPDF_STATIC::rfread($f, $n + 4);
+ }
+ } while ($n);
+ if (($colspace == 'Indexed') AND (empty($pal))) {
+ // Missing palette
+ fclose($f);
+ return false;
+ }
+ fclose($f);
+ return array('w' => $w, 'h' => $h, 'ch' => $channels, 'icc' => $icc, 'cs' => $colspace, 'bpc' => $bpc, 'f' => 'FlateDecode', 'parms' => $parms, 'pal' => $pal, 'trns' => $trns, 'data' => $data);
+ }
+
+} // END OF TCPDF_IMAGES CLASS
+
+//============================================================+
+// END OF FILE
+//============================================================+
diff --git a/libraries/tcpdf/include/tcpdf_static.php b/libraries/tcpdf/include/tcpdf_static.php
new file mode 100644
index 0000000000..6e2f15f72a
--- /dev/null
+++ b/libraries/tcpdf/include/tcpdf_static.php
@@ -0,0 +1,2837 @@
+<?php
+//============================================================+
+// File name : tcpdf_static.php
+// Version : 1.0.002
+// Begin : 2002-08-03
+// Last Update : 2013-09-14
+// Author : Nicola Asuni - Tecnick.com LTD - www.tecnick.com - info@tecnick.com
+// License : GNU-LGPL v3 (http://www.gnu.org/copyleft/lesser.html)
+// -------------------------------------------------------------------
+// Copyright (C) 2002-2013 Nicola Asuni - Tecnick.com LTD
+//
+// This file is part of TCPDF software library.
+//
+// TCPDF 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 3 of the
+// License, or (at your option) any later version.
+//
+// TCPDF 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 License
+// along with TCPDF. If not, see
+// <http://www.tecnick.com/pagefiles/tcpdf/LICENSE.TXT>.
+//
+// See LICENSE.TXT file for more information.
+// -------------------------------------------------------------------
+//
+// Description :
+// Static methods used by the TCPDF class.
+//
+//============================================================+
+
+/**
+ * @file
+ * This is a PHP class that contains static methods for the TCPDF class.<br>
+ * @package com.tecnick.tcpdf
+ * @author Nicola Asuni
+ * @version 1.0.002
+ */
+
+/**
+ * @class TCPDF_STATIC
+ * Static methods used by the TCPDF class.
+ * @package com.tecnick.tcpdf
+ * @brief PHP class for generating PDF documents without requiring external extensions.
+ * @version 1.0.002
+ * @author Nicola Asuni - info@tecnick.com
+ */
+class TCPDF_STATIC {
+
+ /**
+ * Current TCPDF version.
+ * @private static
+ */
+ private static $tcpdf_version = '6.0.039';
+
+ /**
+ * String alias for total number of pages.
+ * @public static
+ */
+ public static $alias_tot_pages = '{:ptp:}';
+
+ /**
+ * String alias for page number.
+ * @public static
+ */
+ public static $alias_num_page = '{:pnp:}';
+
+ /**
+ * String alias for total number of pages in a single group.
+ * @public static
+ */
+ public static $alias_group_tot_pages = '{:ptg:}';
+
+ /**
+ * String alias for group page number.
+ * @public static
+ */
+ public static $alias_group_num_page = '{:png:}';
+
+ /**
+ * String alias for right shift compensation used to correctly align page numbers on the right.
+ * @public static
+ */
+ public static $alias_right_shift = '{rsc:';
+
+ /**
+ * Encryption padding string.
+ * @public static
+ */
+ public static $enc_padding = "\x28\xBF\x4E\x5E\x4E\x75\x8A\x41\x64\x00\x4E\x56\xFF\xFA\x01\x08\x2E\x2E\x00\xB6\xD0\x68\x3E\x80\x2F\x0C\xA9\xFE\x64\x53\x69\x7A";
+
+ /**
+ * ByteRange placemark used during digital signature process.
+ * @since 4.6.028 (2009-08-25)
+ * @public static
+ */
+ public static $byterange_string = '/ByteRange[0 ********** ********** **********]';
+
+ /**
+ * Array page boxes names
+ * @public static
+ */
+ public static $pageboxes = array('MediaBox', 'CropBox', 'BleedBox', 'TrimBox', 'ArtBox');
+
+ // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+ /**
+ * Return the current TCPDF version.
+ * @return TCPDF version string
+ * @since 5.9.012 (2010-11-10)
+ * @public static
+ */
+ public static function getTCPDFVersion() {
+ return self::$tcpdf_version;
+ }
+
+ /**
+ * Return the current TCPDF producer.
+ * @return TCPDF producer string
+ * @since 6.0.000 (2013-03-16)
+ * @public static
+ */
+ public static function getTCPDFProducer() {
+ return "\x54\x43\x50\x44\x46\x20".self::getTCPDFVersion()."\x20\x28\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x74\x63\x70\x64\x66\x2e\x6f\x72\x67\x29";
+ }
+
+ /**
+ * Sets the current active configuration setting of magic_quotes_runtime (if the set_magic_quotes_runtime function exist)
+ * @param $mqr (boolean) FALSE for off, TRUE for on.
+ * @since 4.6.025 (2009-08-17)
+ * @public static
+ */
+ public static function set_mqr($mqr) {
+ if (!defined('PHP_VERSION_ID')) {
+ $version = PHP_VERSION;
+ define('PHP_VERSION_ID', (($version{0} * 10000) + ($version{2} * 100) + $version{4}));
+ }
+ if (PHP_VERSION_ID < 50300) {
+ @set_magic_quotes_runtime($mqr);
+ }
+ }
+
+ /**
+ * Gets the current active configuration setting of magic_quotes_runtime (if the get_magic_quotes_runtime function exist)
+ * @return Returns 0 if magic quotes runtime is off or get_magic_quotes_runtime doesn't exist, 1 otherwise.
+ * @since 4.6.025 (2009-08-17)
+ * @public static
+ */
+ public static function get_mqr() {
+ if (!defined('PHP_VERSION_ID')) {
+ $version = PHP_VERSION;
+ define('PHP_VERSION_ID', (($version{0} * 10000) + ($version{2} * 100) + $version{4}));
+ }
+ if (PHP_VERSION_ID < 50300) {
+ return @get_magic_quotes_runtime();
+ }
+ return 0;
+ }
+
+ /**
+ * Get page dimensions from format name.
+ * @param $format (mixed) The format name. It can be: <ul>
+ * <li><b>ISO 216 A Series + 2 SIS 014711 extensions</b></li>
+ * <li>A0 (841x1189 mm ; 33.11x46.81 in)</li>
+ * <li>A1 (594x841 mm ; 23.39x33.11 in)</li>
+ * <li>A2 (420x594 mm ; 16.54x23.39 in)</li>
+ * <li>A3 (297x420 mm ; 11.69x16.54 in)</li>
+ * <li>A4 (210x297 mm ; 8.27x11.69 in)</li>
+ * <li>A5 (148x210 mm ; 5.83x8.27 in)</li>
+ * <li>A6 (105x148 mm ; 4.13x5.83 in)</li>
+ * <li>A7 (74x105 mm ; 2.91x4.13 in)</li>
+ * <li>A8 (52x74 mm ; 2.05x2.91 in)</li>
+ * <li>A9 (37x52 mm ; 1.46x2.05 in)</li>
+ * <li>A10 (26x37 mm ; 1.02x1.46 in)</li>
+ * <li>A11 (18x26 mm ; 0.71x1.02 in)</li>
+ * <li>A12 (13x18 mm ; 0.51x0.71 in)</li>
+ * <li><b>ISO 216 B Series + 2 SIS 014711 extensions</b></li>
+ * <li>B0 (1000x1414 mm ; 39.37x55.67 in)</li>
+ * <li>B1 (707x1000 mm ; 27.83x39.37 in)</li>
+ * <li>B2 (500x707 mm ; 19.69x27.83 in)</li>
+ * <li>B3 (353x500 mm ; 13.90x19.69 in)</li>
+ * <li>B4 (250x353 mm ; 9.84x13.90 in)</li>
+ * <li>B5 (176x250 mm ; 6.93x9.84 in)</li>
+ * <li>B6 (125x176 mm ; 4.92x6.93 in)</li>
+ * <li>B7 (88x125 mm ; 3.46x4.92 in)</li>
+ * <li>B8 (62x88 mm ; 2.44x3.46 in)</li>
+ * <li>B9 (44x62 mm ; 1.73x2.44 in)</li>
+ * <li>B10 (31x44 mm ; 1.22x1.73 in)</li>
+ * <li>B11 (22x31 mm ; 0.87x1.22 in)</li>
+ * <li>B12 (15x22 mm ; 0.59x0.87 in)</li>
+ * <li><b>ISO 216 C Series + 2 SIS 014711 extensions + 2 EXTENSION</b></li>
+ * <li>C0 (917x1297 mm ; 36.10x51.06 in)</li>
+ * <li>C1 (648x917 mm ; 25.51x36.10 in)</li>
+ * <li>C2 (458x648 mm ; 18.03x25.51 in)</li>
+ * <li>C3 (324x458 mm ; 12.76x18.03 in)</li>
+ * <li>C4 (229x324 mm ; 9.02x12.76 in)</li>
+ * <li>C5 (162x229 mm ; 6.38x9.02 in)</li>
+ * <li>C6 (114x162 mm ; 4.49x6.38 in)</li>
+ * <li>C7 (81x114 mm ; 3.19x4.49 in)</li>
+ * <li>C8 (57x81 mm ; 2.24x3.19 in)</li>
+ * <li>C9 (40x57 mm ; 1.57x2.24 in)</li>
+ * <li>C10 (28x40 mm ; 1.10x1.57 in)</li>
+ * <li>C11 (20x28 mm ; 0.79x1.10 in)</li>
+ * <li>C12 (14x20 mm ; 0.55x0.79 in)</li>
+ * <li>C76 (81x162 mm ; 3.19x6.38 in)</li>
+ * <li>DL (110x220 mm ; 4.33x8.66 in)</li>
+ * <li><b>SIS 014711 E Series</b></li>
+ * <li>E0 (879x1241 mm ; 34.61x48.86 in)</li>
+ * <li>E1 (620x879 mm ; 24.41x34.61 in)</li>
+ * <li>E2 (440x620 mm ; 17.32x24.41 in)</li>
+ * <li>E3 (310x440 mm ; 12.20x17.32 in)</li>
+ * <li>E4 (220x310 mm ; 8.66x12.20 in)</li>
+ * <li>E5 (155x220 mm ; 6.10x8.66 in)</li>
+ * <li>E6 (110x155 mm ; 4.33x6.10 in)</li>
+ * <li>E7 (78x110 mm ; 3.07x4.33 in)</li>
+ * <li>E8 (55x78 mm ; 2.17x3.07 in)</li>
+ * <li>E9 (39x55 mm ; 1.54x2.17 in)</li>
+ * <li>E10 (27x39 mm ; 1.06x1.54 in)</li>
+ * <li>E11 (19x27 mm ; 0.75x1.06 in)</li>
+ * <li>E12 (13x19 mm ; 0.51x0.75 in)</li>
+ * <li><b>SIS 014711 G Series</b></li>
+ * <li>G0 (958x1354 mm ; 37.72x53.31 in)</li>
+ * <li>G1 (677x958 mm ; 26.65x37.72 in)</li>
+ * <li>G2 (479x677 mm ; 18.86x26.65 in)</li>
+ * <li>G3 (338x479 mm ; 13.31x18.86 in)</li>
+ * <li>G4 (239x338 mm ; 9.41x13.31 in)</li>
+ * <li>G5 (169x239 mm ; 6.65x9.41 in)</li>
+ * <li>G6 (119x169 mm ; 4.69x6.65 in)</li>
+ * <li>G7 (84x119 mm ; 3.31x4.69 in)</li>
+ * <li>G8 (59x84 mm ; 2.32x3.31 in)</li>
+ * <li>G9 (42x59 mm ; 1.65x2.32 in)</li>
+ * <li>G10 (29x42 mm ; 1.14x1.65 in)</li>
+ * <li>G11 (21x29 mm ; 0.83x1.14 in)</li>
+ * <li>G12 (14x21 mm ; 0.55x0.83 in)</li>
+ * <li><b>ISO Press</b></li>
+ * <li>RA0 (860x1220 mm ; 33.86x48.03 in)</li>
+ * <li>RA1 (610x860 mm ; 24.02x33.86 in)</li>
+ * <li>RA2 (430x610 mm ; 16.93x24.02 in)</li>
+ * <li>RA3 (305x430 mm ; 12.01x16.93 in)</li>
+ * <li>RA4 (215x305 mm ; 8.46x12.01 in)</li>
+ * <li>SRA0 (900x1280 mm ; 35.43x50.39 in)</li>
+ * <li>SRA1 (640x900 mm ; 25.20x35.43 in)</li>
+ * <li>SRA2 (450x640 mm ; 17.72x25.20 in)</li>
+ * <li>SRA3 (320x450 mm ; 12.60x17.72 in)</li>
+ * <li>SRA4 (225x320 mm ; 8.86x12.60 in)</li>
+ * <li><b>German DIN 476</b></li>
+ * <li>4A0 (1682x2378 mm ; 66.22x93.62 in)</li>
+ * <li>2A0 (1189x1682 mm ; 46.81x66.22 in)</li>
+ * <li><b>Variations on the ISO Standard</b></li>
+ * <li>A2_EXTRA (445x619 mm ; 17.52x24.37 in)</li>
+ * <li>A3+ (329x483 mm ; 12.95x19.02 in)</li>
+ * <li>A3_EXTRA (322x445 mm ; 12.68x17.52 in)</li>
+ * <li>A3_SUPER (305x508 mm ; 12.01x20.00 in)</li>
+ * <li>SUPER_A3 (305x487 mm ; 12.01x19.17 in)</li>
+ * <li>A4_EXTRA (235x322 mm ; 9.25x12.68 in)</li>
+ * <li>A4_SUPER (229x322 mm ; 9.02x12.68 in)</li>
+ * <li>SUPER_A4 (227x356 mm ; 8.94x14.02 in)</li>
+ * <li>A4_LONG (210x348 mm ; 8.27x13.70 in)</li>
+ * <li>F4 (210x330 mm ; 8.27x12.99 in)</li>
+ * <li>SO_B5_EXTRA (202x276 mm ; 7.95x10.87 in)</li>
+ * <li>A5_EXTRA (173x235 mm ; 6.81x9.25 in)</li>
+ * <li><b>ANSI Series</b></li>
+ * <li>ANSI_E (864x1118 mm ; 34.00x44.00 in)</li>
+ * <li>ANSI_D (559x864 mm ; 22.00x34.00 in)</li>
+ * <li>ANSI_C (432x559 mm ; 17.00x22.00 in)</li>
+ * <li>ANSI_B (279x432 mm ; 11.00x17.00 in)</li>
+ * <li>ANSI_A (216x279 mm ; 8.50x11.00 in)</li>
+ * <li><b>Traditional 'Loose' North American Paper Sizes</b></li>
+ * <li>LEDGER, USLEDGER (432x279 mm ; 17.00x11.00 in)</li>
+ * <li>TABLOID, USTABLOID, BIBLE, ORGANIZERK (279x432 mm ; 11.00x17.00 in)</li>
+ * <li>LETTER, USLETTER, ORGANIZERM (216x279 mm ; 8.50x11.00 in)</li>
+ * <li>LEGAL, USLEGAL (216x356 mm ; 8.50x14.00 in)</li>
+ * <li>GLETTER, GOVERNMENTLETTER (203x267 mm ; 8.00x10.50 in)</li>
+ * <li>JLEGAL, JUNIORLEGAL (203x127 mm ; 8.00x5.00 in)</li>
+ * <li><b>Other North American Paper Sizes</b></li>
+ * <li>QUADDEMY (889x1143 mm ; 35.00x45.00 in)</li>
+ * <li>SUPER_B (330x483 mm ; 13.00x19.00 in)</li>
+ * <li>QUARTO (229x279 mm ; 9.00x11.00 in)</li>
+ * <li>FOLIO, GOVERNMENTLEGAL (216x330 mm ; 8.50x13.00 in)</li>
+ * <li>EXECUTIVE, MONARCH (184x267 mm ; 7.25x10.50 in)</li>
+ * <li>MEMO, STATEMENT, ORGANIZERL (140x216 mm ; 5.50x8.50 in)</li>
+ * <li>FOOLSCAP (210x330 mm ; 8.27x13.00 in)</li>
+ * <li>COMPACT (108x171 mm ; 4.25x6.75 in)</li>
+ * <li>ORGANIZERJ (70x127 mm ; 2.75x5.00 in)</li>
+ * <li><b>Canadian standard CAN 2-9.60M</b></li>
+ * <li>P1 (560x860 mm ; 22.05x33.86 in)</li>
+ * <li>P2 (430x560 mm ; 16.93x22.05 in)</li>
+ * <li>P3 (280x430 mm ; 11.02x16.93 in)</li>
+ * <li>P4 (215x280 mm ; 8.46x11.02 in)</li>
+ * <li>P5 (140x215 mm ; 5.51x8.46 in)</li>
+ * <li>P6 (107x140 mm ; 4.21x5.51 in)</li>
+ * <li><b>North American Architectural Sizes</b></li>
+ * <li>ARCH_E (914x1219 mm ; 36.00x48.00 in)</li>
+ * <li>ARCH_E1 (762x1067 mm ; 30.00x42.00 in)</li>
+ * <li>ARCH_D (610x914 mm ; 24.00x36.00 in)</li>
+ * <li>ARCH_C, BROADSHEET (457x610 mm ; 18.00x24.00 in)</li>
+ * <li>ARCH_B (305x457 mm ; 12.00x18.00 in)</li>
+ * <li>ARCH_A (229x305 mm ; 9.00x12.00 in)</li>
+ * <li><b>Announcement Envelopes</b></li>
+ * <li>ANNENV_A2 (111x146 mm ; 4.37x5.75 in)</li>
+ * <li>ANNENV_A6 (121x165 mm ; 4.75x6.50 in)</li>
+ * <li>ANNENV_A7 (133x184 mm ; 5.25x7.25 in)</li>
+ * <li>ANNENV_A8 (140x206 mm ; 5.50x8.12 in)</li>
+ * <li>ANNENV_A10 (159x244 mm ; 6.25x9.62 in)</li>
+ * <li>ANNENV_SLIM (98x225 mm ; 3.87x8.87 in)</li>
+ * <li><b>Commercial Envelopes</b></li>
+ * <li>COMMENV_N6_1/4 (89x152 mm ; 3.50x6.00 in)</li>
+ * <li>COMMENV_N6_3/4 (92x165 mm ; 3.62x6.50 in)</li>
+ * <li>COMMENV_N8 (98x191 mm ; 3.87x7.50 in)</li>
+ * <li>COMMENV_N9 (98x225 mm ; 3.87x8.87 in)</li>
+ * <li>COMMENV_N10 (105x241 mm ; 4.12x9.50 in)</li>
+ * <li>COMMENV_N11 (114x263 mm ; 4.50x10.37 in)</li>
+ * <li>COMMENV_N12 (121x279 mm ; 4.75x11.00 in)</li>
+ * <li>COMMENV_N14 (127x292 mm ; 5.00x11.50 in)</li>
+ * <li><b>Catalogue Envelopes</b></li>
+ * <li>CATENV_N1 (152x229 mm ; 6.00x9.00 in)</li>
+ * <li>CATENV_N1_3/4 (165x241 mm ; 6.50x9.50 in)</li>
+ * <li>CATENV_N2 (165x254 mm ; 6.50x10.00 in)</li>
+ * <li>CATENV_N3 (178x254 mm ; 7.00x10.00 in)</li>
+ * <li>CATENV_N6 (191x267 mm ; 7.50x10.50 in)</li>
+ * <li>CATENV_N7 (203x279 mm ; 8.00x11.00 in)</li>
+ * <li>CATENV_N8 (210x286 mm ; 8.25x11.25 in)</li>
+ * <li>CATENV_N9_1/2 (216x267 mm ; 8.50x10.50 in)</li>
+ * <li>CATENV_N9_3/4 (222x286 mm ; 8.75x11.25 in)</li>
+ * <li>CATENV_N10_1/2 (229x305 mm ; 9.00x12.00 in)</li>
+ * <li>CATENV_N12_1/2 (241x318 mm ; 9.50x12.50 in)</li>
+ * <li>CATENV_N13_1/2 (254x330 mm ; 10.00x13.00 in)</li>
+ * <li>CATENV_N14_1/4 (286x311 mm ; 11.25x12.25 in)</li>
+ * <li>CATENV_N14_1/2 (292x368 mm ; 11.50x14.50 in)</li>
+ * <li><b>Japanese (JIS P 0138-61) Standard B-Series</b></li>
+ * <li>JIS_B0 (1030x1456 mm ; 40.55x57.32 in)</li>
+ * <li>JIS_B1 (728x1030 mm ; 28.66x40.55 in)</li>
+ * <li>JIS_B2 (515x728 mm ; 20.28x28.66 in)</li>
+ * <li>JIS_B3 (364x515 mm ; 14.33x20.28 in)</li>
+ * <li>JIS_B4 (257x364 mm ; 10.12x14.33 in)</li>
+ * <li>JIS_B5 (182x257 mm ; 7.17x10.12 in)</li>
+ * <li>JIS_B6 (128x182 mm ; 5.04x7.17 in)</li>
+ * <li>JIS_B7 (91x128 mm ; 3.58x5.04 in)</li>
+ * <li>JIS_B8 (64x91 mm ; 2.52x3.58 in)</li>
+ * <li>JIS_B9 (45x64 mm ; 1.77x2.52 in)</li>
+ * <li>JIS_B10 (32x45 mm ; 1.26x1.77 in)</li>
+ * <li>JIS_B11 (22x32 mm ; 0.87x1.26 in)</li>
+ * <li>JIS_B12 (16x22 mm ; 0.63x0.87 in)</li>
+ * <li><b>PA Series</b></li>
+ * <li>PA0 (840x1120 mm ; 33.07x44.09 in)</li>
+ * <li>PA1 (560x840 mm ; 22.05x33.07 in)</li>
+ * <li>PA2 (420x560 mm ; 16.54x22.05 in)</li>
+ * <li>PA3 (280x420 mm ; 11.02x16.54 in)</li>
+ * <li>PA4 (210x280 mm ; 8.27x11.02 in)</li>
+ * <li>PA5 (140x210 mm ; 5.51x8.27 in)</li>
+ * <li>PA6 (105x140 mm ; 4.13x5.51 in)</li>
+ * <li>PA7 (70x105 mm ; 2.76x4.13 in)</li>
+ * <li>PA8 (52x70 mm ; 2.05x2.76 in)</li>
+ * <li>PA9 (35x52 mm ; 1.38x2.05 in)</li>
+ * <li>PA10 (26x35 mm ; 1.02x1.38 in)</li>
+ * <li><b>Standard Photographic Print Sizes</b></li>
+ * <li>PASSPORT_PHOTO (35x45 mm ; 1.38x1.77 in)</li>
+ * <li>E (82x120 mm ; 3.25x4.72 in)</li>
+ * <li>3R, L (89x127 mm ; 3.50x5.00 in)</li>
+ * <li>4R, KG (102x152 mm ; 4.02x5.98 in)</li>
+ * <li>4D (120x152 mm ; 4.72x5.98 in)</li>
+ * <li>5R, 2L (127x178 mm ; 5.00x7.01 in)</li>
+ * <li>6R, 8P (152x203 mm ; 5.98x7.99 in)</li>
+ * <li>8R, 6P (203x254 mm ; 7.99x10.00 in)</li>
+ * <li>S8R, 6PW (203x305 mm ; 7.99x12.01 in)</li>
+ * <li>10R, 4P (254x305 mm ; 10.00x12.01 in)</li>
+ * <li>S10R, 4PW (254x381 mm ; 10.00x15.00 in)</li>
+ * <li>11R (279x356 mm ; 10.98x14.02 in)</li>
+ * <li>S11R (279x432 mm ; 10.98x17.01 in)</li>
+ * <li>12R (305x381 mm ; 12.01x15.00 in)</li>
+ * <li>S12R (305x456 mm ; 12.01x17.95 in)</li>
+ * <li><b>Common Newspaper Sizes</b></li>
+ * <li>NEWSPAPER_BROADSHEET (750x600 mm ; 29.53x23.62 in)</li>
+ * <li>NEWSPAPER_BERLINER (470x315 mm ; 18.50x12.40 in)</li>
+ * <li>NEWSPAPER_COMPACT, NEWSPAPER_TABLOID (430x280 mm ; 16.93x11.02 in)</li>
+ * <li><b>Business Cards</b></li>
+ * <li>CREDIT_CARD, BUSINESS_CARD, BUSINESS_CARD_ISO7810 (54x86 mm ; 2.13x3.37 in)</li>
+ * <li>BUSINESS_CARD_ISO216 (52x74 mm ; 2.05x2.91 in)</li>
+ * <li>BUSINESS_CARD_IT, BUSINESS_CARD_UK, BUSINESS_CARD_FR, BUSINESS_CARD_DE, BUSINESS_CARD_ES (55x85 mm ; 2.17x3.35 in)</li>
+ * <li>BUSINESS_CARD_US, BUSINESS_CARD_CA (51x89 mm ; 2.01x3.50 in)</li>
+ * <li>BUSINESS_CARD_JP (55x91 mm ; 2.17x3.58 in)</li>
+ * <li>BUSINESS_CARD_HK (54x90 mm ; 2.13x3.54 in)</li>
+ * <li>BUSINESS_CARD_AU, BUSINESS_CARD_DK, BUSINESS_CARD_SE (55x90 mm ; 2.17x3.54 in)</li>
+ * <li>BUSINESS_CARD_RU, BUSINESS_CARD_CZ, BUSINESS_CARD_FI, BUSINESS_CARD_HU, BUSINESS_CARD_IL (50x90 mm ; 1.97x3.54 in)</li>
+ * <li><b>Billboards</b></li>
+ * <li>4SHEET (1016x1524 mm ; 40.00x60.00 in)</li>
+ * <li>6SHEET (1200x1800 mm ; 47.24x70.87 in)</li>
+ * <li>12SHEET (3048x1524 mm ; 120.00x60.00 in)</li>
+ * <li>16SHEET (2032x3048 mm ; 80.00x120.00 in)</li>
+ * <li>32SHEET (4064x3048 mm ; 160.00x120.00 in)</li>
+ * <li>48SHEET (6096x3048 mm ; 240.00x120.00 in)</li>
+ * <li>64SHEET (8128x3048 mm ; 320.00x120.00 in)</li>
+ * <li>96SHEET (12192x3048 mm ; 480.00x120.00 in)</li>
+ * <li><b>Old Imperial English (some are still used in USA)</b></li>
+ * <li>EN_EMPEROR (1219x1829 mm ; 48.00x72.00 in)</li>
+ * <li>EN_ANTIQUARIAN (787x1346 mm ; 31.00x53.00 in)</li>
+ * <li>EN_GRAND_EAGLE (730x1067 mm ; 28.75x42.00 in)</li>
+ * <li>EN_DOUBLE_ELEPHANT (679x1016 mm ; 26.75x40.00 in)</li>
+ * <li>EN_ATLAS (660x864 mm ; 26.00x34.00 in)</li>
+ * <li>EN_COLOMBIER (597x876 mm ; 23.50x34.50 in)</li>
+ * <li>EN_ELEPHANT (584x711 mm ; 23.00x28.00 in)</li>
+ * <li>EN_DOUBLE_DEMY (572x902 mm ; 22.50x35.50 in)</li>
+ * <li>EN_IMPERIAL (559x762 mm ; 22.00x30.00 in)</li>
+ * <li>EN_PRINCESS (546x711 mm ; 21.50x28.00 in)</li>
+ * <li>EN_CARTRIDGE (533x660 mm ; 21.00x26.00 in)</li>
+ * <li>EN_DOUBLE_LARGE_POST (533x838 mm ; 21.00x33.00 in)</li>
+ * <li>EN_ROYAL (508x635 mm ; 20.00x25.00 in)</li>
+ * <li>EN_SHEET, EN_HALF_POST (495x597 mm ; 19.50x23.50 in)</li>
+ * <li>EN_SUPER_ROYAL (483x686 mm ; 19.00x27.00 in)</li>
+ * <li>EN_DOUBLE_POST (483x775 mm ; 19.00x30.50 in)</li>
+ * <li>EN_MEDIUM (445x584 mm ; 17.50x23.00 in)</li>
+ * <li>EN_DEMY (445x572 mm ; 17.50x22.50 in)</li>
+ * <li>EN_LARGE_POST (419x533 mm ; 16.50x21.00 in)</li>
+ * <li>EN_COPY_DRAUGHT (406x508 mm ; 16.00x20.00 in)</li>
+ * <li>EN_POST (394x489 mm ; 15.50x19.25 in)</li>
+ * <li>EN_CROWN (381x508 mm ; 15.00x20.00 in)</li>
+ * <li>EN_PINCHED_POST (375x470 mm ; 14.75x18.50 in)</li>
+ * <li>EN_BRIEF (343x406 mm ; 13.50x16.00 in)</li>
+ * <li>EN_FOOLSCAP (343x432 mm ; 13.50x17.00 in)</li>
+ * <li>EN_SMALL_FOOLSCAP (337x419 mm ; 13.25x16.50 in)</li>
+ * <li>EN_POTT (318x381 mm ; 12.50x15.00 in)</li>
+ * <li><b>Old Imperial Belgian</b></li>
+ * <li>BE_GRAND_AIGLE (700x1040 mm ; 27.56x40.94 in)</li>
+ * <li>BE_COLOMBIER (620x850 mm ; 24.41x33.46 in)</li>
+ * <li>BE_DOUBLE_CARRE (620x920 mm ; 24.41x36.22 in)</li>
+ * <li>BE_ELEPHANT (616x770 mm ; 24.25x30.31 in)</li>
+ * <li>BE_PETIT_AIGLE (600x840 mm ; 23.62x33.07 in)</li>
+ * <li>BE_GRAND_JESUS (550x730 mm ; 21.65x28.74 in)</li>
+ * <li>BE_JESUS (540x730 mm ; 21.26x28.74 in)</li>
+ * <li>BE_RAISIN (500x650 mm ; 19.69x25.59 in)</li>
+ * <li>BE_GRAND_MEDIAN (460x605 mm ; 18.11x23.82 in)</li>
+ * <li>BE_DOUBLE_POSTE (435x565 mm ; 17.13x22.24 in)</li>
+ * <li>BE_COQUILLE (430x560 mm ; 16.93x22.05 in)</li>
+ * <li>BE_PETIT_MEDIAN (415x530 mm ; 16.34x20.87 in)</li>
+ * <li>BE_RUCHE (360x460 mm ; 14.17x18.11 in)</li>
+ * <li>BE_PROPATRIA (345x430 mm ; 13.58x16.93 in)</li>
+ * <li>BE_LYS (317x397 mm ; 12.48x15.63 in)</li>
+ * <li>BE_POT (307x384 mm ; 12.09x15.12 in)</li>
+ * <li>BE_ROSETTE (270x347 mm ; 10.63x13.66 in)</li>
+ * <li><b>Old Imperial French</b></li>
+ * <li>FR_UNIVERS (1000x1300 mm ; 39.37x51.18 in)</li>
+ * <li>FR_DOUBLE_COLOMBIER (900x1260 mm ; 35.43x49.61 in)</li>
+ * <li>FR_GRANDE_MONDE (900x1260 mm ; 35.43x49.61 in)</li>
+ * <li>FR_DOUBLE_SOLEIL (800x1200 mm ; 31.50x47.24 in)</li>
+ * <li>FR_DOUBLE_JESUS (760x1120 mm ; 29.92x44.09 in)</li>
+ * <li>FR_GRAND_AIGLE (750x1060 mm ; 29.53x41.73 in)</li>
+ * <li>FR_PETIT_AIGLE (700x940 mm ; 27.56x37.01 in)</li>
+ * <li>FR_DOUBLE_RAISIN (650x1000 mm ; 25.59x39.37 in)</li>
+ * <li>FR_JOURNAL (650x940 mm ; 25.59x37.01 in)</li>
+ * <li>FR_COLOMBIER_AFFICHE (630x900 mm ; 24.80x35.43 in)</li>
+ * <li>FR_DOUBLE_CAVALIER (620x920 mm ; 24.41x36.22 in)</li>
+ * <li>FR_CLOCHE (600x800 mm ; 23.62x31.50 in)</li>
+ * <li>FR_SOLEIL (600x800 mm ; 23.62x31.50 in)</li>
+ * <li>FR_DOUBLE_CARRE (560x900 mm ; 22.05x35.43 in)</li>
+ * <li>FR_DOUBLE_COQUILLE (560x880 mm ; 22.05x34.65 in)</li>
+ * <li>FR_JESUS (560x760 mm ; 22.05x29.92 in)</li>
+ * <li>FR_RAISIN (500x650 mm ; 19.69x25.59 in)</li>
+ * <li>FR_CAVALIER (460x620 mm ; 18.11x24.41 in)</li>
+ * <li>FR_DOUBLE_COURONNE (460x720 mm ; 18.11x28.35 in)</li>
+ * <li>FR_CARRE (450x560 mm ; 17.72x22.05 in)</li>
+ * <li>FR_COQUILLE (440x560 mm ; 17.32x22.05 in)</li>
+ * <li>FR_DOUBLE_TELLIERE (440x680 mm ; 17.32x26.77 in)</li>
+ * <li>FR_DOUBLE_CLOCHE (400x600 mm ; 15.75x23.62 in)</li>
+ * <li>FR_DOUBLE_POT (400x620 mm ; 15.75x24.41 in)</li>
+ * <li>FR_ECU (400x520 mm ; 15.75x20.47 in)</li>
+ * <li>FR_COURONNE (360x460 mm ; 14.17x18.11 in)</li>
+ * <li>FR_TELLIERE (340x440 mm ; 13.39x17.32 in)</li>
+ * <li>FR_POT (310x400 mm ; 12.20x15.75 in)</li>
+ * </ul>
+ * @return array containing page width and height in points
+ * @since 5.0.010 (2010-05-17)
+ * @public static
+ */
+ public static function getPageSizeFromFormat($format) {
+ // Paper cordinates are calculated in this way: (inches * 72) where (1 inch = 25.4 mm)
+ switch (strtoupper($format)) {
+ // ISO 216 A Series + 2 SIS 014711 extensions
+ case 'A0' : {$pf = array( 2383.937, 3370.394); break;}
+ case 'A1' : {$pf = array( 1683.780, 2383.937); break;}
+ case 'A2' : {$pf = array( 1190.551, 1683.780); break;}
+ case 'A3' : {$pf = array( 841.890, 1190.551); break;}
+ case 'A4' : {$pf = array( 595.276, 841.890); break;}
+ case 'A5' : {$pf = array( 419.528, 595.276); break;}
+ case 'A6' : {$pf = array( 297.638, 419.528); break;}
+ case 'A7' : {$pf = array( 209.764, 297.638); break;}
+ case 'A8' : {$pf = array( 147.402, 209.764); break;}
+ case 'A9' : {$pf = array( 104.882, 147.402); break;}
+ case 'A10': {$pf = array( 73.701, 104.882); break;}
+ case 'A11': {$pf = array( 51.024, 73.701); break;}
+ case 'A12': {$pf = array( 36.850, 51.024); break;}
+ // ISO 216 B Series + 2 SIS 014711 extensions
+ case 'B0' : {$pf = array( 2834.646, 4008.189); break;}
+ case 'B1' : {$pf = array( 2004.094, 2834.646); break;}
+ case 'B2' : {$pf = array( 1417.323, 2004.094); break;}
+ case 'B3' : {$pf = array( 1000.630, 1417.323); break;}
+ case 'B4' : {$pf = array( 708.661, 1000.630); break;}
+ case 'B5' : {$pf = array( 498.898, 708.661); break;}
+ case 'B6' : {$pf = array( 354.331, 498.898); break;}
+ case 'B7' : {$pf = array( 249.449, 354.331); break;}
+ case 'B8' : {$pf = array( 175.748, 249.449); break;}
+ case 'B9' : {$pf = array( 124.724, 175.748); break;}
+ case 'B10': {$pf = array( 87.874, 124.724); break;}
+ case 'B11': {$pf = array( 62.362, 87.874); break;}
+ case 'B12': {$pf = array( 42.520, 62.362); break;}
+ // ISO 216 C Series + 2 SIS 014711 extensions + 2 EXTENSION
+ case 'C0' : {$pf = array( 2599.370, 3676.535); break;}
+ case 'C1' : {$pf = array( 1836.850, 2599.370); break;}
+ case 'C2' : {$pf = array( 1298.268, 1836.850); break;}
+ case 'C3' : {$pf = array( 918.425, 1298.268); break;}
+ case 'C4' : {$pf = array( 649.134, 918.425); break;}
+ case 'C5' : {$pf = array( 459.213, 649.134); break;}
+ case 'C6' : {$pf = array( 323.150, 459.213); break;}
+ case 'C7' : {$pf = array( 229.606, 323.150); break;}
+ case 'C8' : {$pf = array( 161.575, 229.606); break;}
+ case 'C9' : {$pf = array( 113.386, 161.575); break;}
+ case 'C10': {$pf = array( 79.370, 113.386); break;}
+ case 'C11': {$pf = array( 56.693, 79.370); break;}
+ case 'C12': {$pf = array( 39.685, 56.693); break;}
+ case 'C76': {$pf = array( 229.606, 459.213); break;}
+ case 'DL' : {$pf = array( 311.811, 623.622); break;}
+ // SIS 014711 E Series
+ case 'E0' : {$pf = array( 2491.654, 3517.795); break;}
+ case 'E1' : {$pf = array( 1757.480, 2491.654); break;}
+ case 'E2' : {$pf = array( 1247.244, 1757.480); break;}
+ case 'E3' : {$pf = array( 878.740, 1247.244); break;}
+ case 'E4' : {$pf = array( 623.622, 878.740); break;}
+ case 'E5' : {$pf = array( 439.370, 623.622); break;}
+ case 'E6' : {$pf = array( 311.811, 439.370); break;}
+ case 'E7' : {$pf = array( 221.102, 311.811); break;}
+ case 'E8' : {$pf = array( 155.906, 221.102); break;}
+ case 'E9' : {$pf = array( 110.551, 155.906); break;}
+ case 'E10': {$pf = array( 76.535, 110.551); break;}
+ case 'E11': {$pf = array( 53.858, 76.535); break;}
+ case 'E12': {$pf = array( 36.850, 53.858); break;}
+ // SIS 014711 G Series
+ case 'G0' : {$pf = array( 2715.591, 3838.110); break;}
+ case 'G1' : {$pf = array( 1919.055, 2715.591); break;}
+ case 'G2' : {$pf = array( 1357.795, 1919.055); break;}
+ case 'G3' : {$pf = array( 958.110, 1357.795); break;}
+ case 'G4' : {$pf = array( 677.480, 958.110); break;}
+ case 'G5' : {$pf = array( 479.055, 677.480); break;}
+ case 'G6' : {$pf = array( 337.323, 479.055); break;}
+ case 'G7' : {$pf = array( 238.110, 337.323); break;}
+ case 'G8' : {$pf = array( 167.244, 238.110); break;}
+ case 'G9' : {$pf = array( 119.055, 167.244); break;}
+ case 'G10': {$pf = array( 82.205, 119.055); break;}
+ case 'G11': {$pf = array( 59.528, 82.205); break;}
+ case 'G12': {$pf = array( 39.685, 59.528); break;}
+ // ISO Press
+ case 'RA0': {$pf = array( 2437.795, 3458.268); break;}
+ case 'RA1': {$pf = array( 1729.134, 2437.795); break;}
+ case 'RA2': {$pf = array( 1218.898, 1729.134); break;}
+ case 'RA3': {$pf = array( 864.567, 1218.898); break;}
+ case 'RA4': {$pf = array( 609.449, 864.567); break;}
+ case 'SRA0': {$pf = array( 2551.181, 3628.346); break;}
+ case 'SRA1': {$pf = array( 1814.173, 2551.181); break;}
+ case 'SRA2': {$pf = array( 1275.591, 1814.173); break;}
+ case 'SRA3': {$pf = array( 907.087, 1275.591); break;}
+ case 'SRA4': {$pf = array( 637.795, 907.087); break;}
+ // German DIN 476
+ case '4A0': {$pf = array( 4767.874, 6740.787); break;}
+ case '2A0': {$pf = array( 3370.394, 4767.874); break;}
+ // Variations on the ISO Standard
+ case 'A2_EXTRA' : {$pf = array( 1261.417, 1754.646); break;}
+ case 'A3+' : {$pf = array( 932.598, 1369.134); break;}
+ case 'A3_EXTRA' : {$pf = array( 912.756, 1261.417); break;}
+ case 'A3_SUPER' : {$pf = array( 864.567, 1440.000); break;}
+ case 'SUPER_A3' : {$pf = array( 864.567, 1380.472); break;}
+ case 'A4_EXTRA' : {$pf = array( 666.142, 912.756); break;}
+ case 'A4_SUPER' : {$pf = array( 649.134, 912.756); break;}
+ case 'SUPER_A4' : {$pf = array( 643.465, 1009.134); break;}
+ case 'A4_LONG' : {$pf = array( 595.276, 986.457); break;}
+ case 'F4' : {$pf = array( 595.276, 935.433); break;}
+ case 'SO_B5_EXTRA': {$pf = array( 572.598, 782.362); break;}
+ case 'A5_EXTRA' : {$pf = array( 490.394, 666.142); break;}
+ // ANSI Series
+ case 'ANSI_E': {$pf = array( 2448.000, 3168.000); break;}
+ case 'ANSI_D': {$pf = array( 1584.000, 2448.000); break;}
+ case 'ANSI_C': {$pf = array( 1224.000, 1584.000); break;}
+ case 'ANSI_B': {$pf = array( 792.000, 1224.000); break;}
+ case 'ANSI_A': {$pf = array( 612.000, 792.000); break;}
+ // Traditional 'Loose' North American Paper Sizes
+ case 'USLEDGER':
+ case 'LEDGER' : {$pf = array( 1224.000, 792.000); break;}
+ case 'ORGANIZERK':
+ case 'BIBLE':
+ case 'USTABLOID':
+ case 'TABLOID': {$pf = array( 792.000, 1224.000); break;}
+ case 'ORGANIZERM':
+ case 'USLETTER':
+ case 'LETTER' : {$pf = array( 612.000, 792.000); break;}
+ case 'USLEGAL':
+ case 'LEGAL' : {$pf = array( 612.000, 1008.000); break;}
+ case 'GOVERNMENTLETTER':
+ case 'GLETTER': {$pf = array( 576.000, 756.000); break;}
+ case 'JUNIORLEGAL':
+ case 'JLEGAL' : {$pf = array( 576.000, 360.000); break;}
+ // Other North American Paper Sizes
+ case 'QUADDEMY': {$pf = array( 2520.000, 3240.000); break;}
+ case 'SUPER_B': {$pf = array( 936.000, 1368.000); break;}
+ case 'QUARTO': {$pf = array( 648.000, 792.000); break;}
+ case 'GOVERNMENTLEGAL':
+ case 'FOLIO': {$pf = array( 612.000, 936.000); break;}
+ case 'MONARCH':
+ case 'EXECUTIVE': {$pf = array( 522.000, 756.000); break;}
+ case 'ORGANIZERL':
+ case 'STATEMENT':
+ case 'MEMO': {$pf = array( 396.000, 612.000); break;}
+ case 'FOOLSCAP': {$pf = array( 595.440, 936.000); break;}
+ case 'COMPACT': {$pf = array( 306.000, 486.000); break;}
+ case 'ORGANIZERJ': {$pf = array( 198.000, 360.000); break;}
+ // Canadian standard CAN 2-9.60M
+ case 'P1': {$pf = array( 1587.402, 2437.795); break;}
+ case 'P2': {$pf = array( 1218.898, 1587.402); break;}
+ case 'P3': {$pf = array( 793.701, 1218.898); break;}
+ case 'P4': {$pf = array( 609.449, 793.701); break;}
+ case 'P5': {$pf = array( 396.850, 609.449); break;}
+ case 'P6': {$pf = array( 303.307, 396.850); break;}
+ // North American Architectural Sizes
+ case 'ARCH_E' : {$pf = array( 2592.000, 3456.000); break;}
+ case 'ARCH_E1': {$pf = array( 2160.000, 3024.000); break;}
+ case 'ARCH_D' : {$pf = array( 1728.000, 2592.000); break;}
+ case 'BROADSHEET':
+ case 'ARCH_C' : {$pf = array( 1296.000, 1728.000); break;}
+ case 'ARCH_B' : {$pf = array( 864.000, 1296.000); break;}
+ case 'ARCH_A' : {$pf = array( 648.000, 864.000); break;}
+ // --- North American Envelope Sizes ---
+ // - Announcement Envelopes
+ case 'ANNENV_A2' : {$pf = array( 314.640, 414.000); break;}
+ case 'ANNENV_A6' : {$pf = array( 342.000, 468.000); break;}
+ case 'ANNENV_A7' : {$pf = array( 378.000, 522.000); break;}
+ case 'ANNENV_A8' : {$pf = array( 396.000, 584.640); break;}
+ case 'ANNENV_A10' : {$pf = array( 450.000, 692.640); break;}
+ case 'ANNENV_SLIM': {$pf = array( 278.640, 638.640); break;}
+ // - Commercial Envelopes
+ case 'COMMENV_N6_1/4': {$pf = array( 252.000, 432.000); break;}
+ case 'COMMENV_N6_3/4': {$pf = array( 260.640, 468.000); break;}
+ case 'COMMENV_N8' : {$pf = array( 278.640, 540.000); break;}
+ case 'COMMENV_N9' : {$pf = array( 278.640, 638.640); break;}
+ case 'COMMENV_N10' : {$pf = array( 296.640, 684.000); break;}
+ case 'COMMENV_N11' : {$pf = array( 324.000, 746.640); break;}
+ case 'COMMENV_N12' : {$pf = array( 342.000, 792.000); break;}
+ case 'COMMENV_N14' : {$pf = array( 360.000, 828.000); break;}
+ // - Catalogue Envelopes
+ case 'CATENV_N1' : {$pf = array( 432.000, 648.000); break;}
+ case 'CATENV_N1_3/4' : {$pf = array( 468.000, 684.000); break;}
+ case 'CATENV_N2' : {$pf = array( 468.000, 720.000); break;}
+ case 'CATENV_N3' : {$pf = array( 504.000, 720.000); break;}
+ case 'CATENV_N6' : {$pf = array( 540.000, 756.000); break;}
+ case 'CATENV_N7' : {$pf = array( 576.000, 792.000); break;}
+ case 'CATENV_N8' : {$pf = array( 594.000, 810.000); break;}
+ case 'CATENV_N9_1/2' : {$pf = array( 612.000, 756.000); break;}
+ case 'CATENV_N9_3/4' : {$pf = array( 630.000, 810.000); break;}
+ case 'CATENV_N10_1/2': {$pf = array( 648.000, 864.000); break;}
+ case 'CATENV_N12_1/2': {$pf = array( 684.000, 900.000); break;}
+ case 'CATENV_N13_1/2': {$pf = array( 720.000, 936.000); break;}
+ case 'CATENV_N14_1/4': {$pf = array( 810.000, 882.000); break;}
+ case 'CATENV_N14_1/2': {$pf = array( 828.000, 1044.000); break;}
+ // Japanese (JIS P 0138-61) Standard B-Series
+ case 'JIS_B0' : {$pf = array( 2919.685, 4127.244); break;}
+ case 'JIS_B1' : {$pf = array( 2063.622, 2919.685); break;}
+ case 'JIS_B2' : {$pf = array( 1459.843, 2063.622); break;}
+ case 'JIS_B3' : {$pf = array( 1031.811, 1459.843); break;}
+ case 'JIS_B4' : {$pf = array( 728.504, 1031.811); break;}
+ case 'JIS_B5' : {$pf = array( 515.906, 728.504); break;}
+ case 'JIS_B6' : {$pf = array( 362.835, 515.906); break;}
+ case 'JIS_B7' : {$pf = array( 257.953, 362.835); break;}
+ case 'JIS_B8' : {$pf = array( 181.417, 257.953); break;}
+ case 'JIS_B9' : {$pf = array( 127.559, 181.417); break;}
+ case 'JIS_B10': {$pf = array( 90.709, 127.559); break;}
+ case 'JIS_B11': {$pf = array( 62.362, 90.709); break;}
+ case 'JIS_B12': {$pf = array( 45.354, 62.362); break;}
+ // PA Series
+ case 'PA0' : {$pf = array( 2381.102, 3174.803,); break;}
+ case 'PA1' : {$pf = array( 1587.402, 2381.102); break;}
+ case 'PA2' : {$pf = array( 1190.551, 1587.402); break;}
+ case 'PA3' : {$pf = array( 793.701, 1190.551); break;}
+ case 'PA4' : {$pf = array( 595.276, 793.701); break;}
+ case 'PA5' : {$pf = array( 396.850, 595.276); break;}
+ case 'PA6' : {$pf = array( 297.638, 396.850); break;}
+ case 'PA7' : {$pf = array( 198.425, 297.638); break;}
+ case 'PA8' : {$pf = array( 147.402, 198.425); break;}
+ case 'PA9' : {$pf = array( 99.213, 147.402); break;}
+ case 'PA10': {$pf = array( 73.701, 99.213); break;}
+ // Standard Photographic Print Sizes
+ case 'PASSPORT_PHOTO': {$pf = array( 99.213, 127.559); break;}
+ case 'E' : {$pf = array( 233.858, 340.157); break;}
+ case 'L':
+ case '3R' : {$pf = array( 252.283, 360.000); break;}
+ case 'KG':
+ case '4R' : {$pf = array( 289.134, 430.866); break;}
+ case '4D' : {$pf = array( 340.157, 430.866); break;}
+ case '2L':
+ case '5R' : {$pf = array( 360.000, 504.567); break;}
+ case '8P':
+ case '6R' : {$pf = array( 430.866, 575.433); break;}
+ case '6P':
+ case '8R' : {$pf = array( 575.433, 720.000); break;}
+ case '6PW':
+ case 'S8R' : {$pf = array( 575.433, 864.567); break;}
+ case '4P':
+ case '10R' : {$pf = array( 720.000, 864.567); break;}
+ case '4PW':
+ case 'S10R': {$pf = array( 720.000, 1080.000); break;}
+ case '11R' : {$pf = array( 790.866, 1009.134); break;}
+ case 'S11R': {$pf = array( 790.866, 1224.567); break;}
+ case '12R' : {$pf = array( 864.567, 1080.000); break;}
+ case 'S12R': {$pf = array( 864.567, 1292.598); break;}
+ // Common Newspaper Sizes
+ case 'NEWSPAPER_BROADSHEET': {$pf = array( 2125.984, 1700.787); break;}
+ case 'NEWSPAPER_BERLINER' : {$pf = array( 1332.283, 892.913); break;}
+ case 'NEWSPAPER_TABLOID':
+ case 'NEWSPAPER_COMPACT' : {$pf = array( 1218.898, 793.701); break;}
+ // Business Cards
+ case 'CREDIT_CARD':
+ case 'BUSINESS_CARD':
+ case 'BUSINESS_CARD_ISO7810': {$pf = array( 153.014, 242.646); break;}
+ case 'BUSINESS_CARD_ISO216' : {$pf = array( 147.402, 209.764); break;}
+ case 'BUSINESS_CARD_IT':
+ case 'BUSINESS_CARD_UK':
+ case 'BUSINESS_CARD_FR':
+ case 'BUSINESS_CARD_DE':
+ case 'BUSINESS_CARD_ES' : {$pf = array( 155.906, 240.945); break;}
+ case 'BUSINESS_CARD_CA':
+ case 'BUSINESS_CARD_US' : {$pf = array( 144.567, 252.283); break;}
+ case 'BUSINESS_CARD_JP' : {$pf = array( 155.906, 257.953); break;}
+ case 'BUSINESS_CARD_HK' : {$pf = array( 153.071, 255.118); break;}
+ case 'BUSINESS_CARD_AU':
+ case 'BUSINESS_CARD_DK':
+ case 'BUSINESS_CARD_SE' : {$pf = array( 155.906, 255.118); break;}
+ case 'BUSINESS_CARD_RU':
+ case 'BUSINESS_CARD_CZ':
+ case 'BUSINESS_CARD_FI':
+ case 'BUSINESS_CARD_HU':
+ case 'BUSINESS_CARD_IL' : {$pf = array( 141.732, 255.118); break;}
+ // Billboards
+ case '4SHEET' : {$pf = array( 2880.000, 4320.000); break;}
+ case '6SHEET' : {$pf = array( 3401.575, 5102.362); break;}
+ case '12SHEET': {$pf = array( 8640.000, 4320.000); break;}
+ case '16SHEET': {$pf = array( 5760.000, 8640.000); break;}
+ case '32SHEET': {$pf = array(11520.000, 8640.000); break;}
+ case '48SHEET': {$pf = array(17280.000, 8640.000); break;}
+ case '64SHEET': {$pf = array(23040.000, 8640.000); break;}
+ case '96SHEET': {$pf = array(34560.000, 8640.000); break;}
+ // Old European Sizes
+ // - Old Imperial English Sizes
+ case 'EN_EMPEROR' : {$pf = array( 3456.000, 5184.000); break;}
+ case 'EN_ANTIQUARIAN' : {$pf = array( 2232.000, 3816.000); break;}
+ case 'EN_GRAND_EAGLE' : {$pf = array( 2070.000, 3024.000); break;}
+ case 'EN_DOUBLE_ELEPHANT' : {$pf = array( 1926.000, 2880.000); break;}
+ case 'EN_ATLAS' : {$pf = array( 1872.000, 2448.000); break;}
+ case 'EN_COLOMBIER' : {$pf = array( 1692.000, 2484.000); break;}
+ case 'EN_ELEPHANT' : {$pf = array( 1656.000, 2016.000); break;}
+ case 'EN_DOUBLE_DEMY' : {$pf = array( 1620.000, 2556.000); break;}
+ case 'EN_IMPERIAL' : {$pf = array( 1584.000, 2160.000); break;}
+ case 'EN_PRINCESS' : {$pf = array( 1548.000, 2016.000); break;}
+ case 'EN_CARTRIDGE' : {$pf = array( 1512.000, 1872.000); break;}
+ case 'EN_DOUBLE_LARGE_POST': {$pf = array( 1512.000, 2376.000); break;}
+ case 'EN_ROYAL' : {$pf = array( 1440.000, 1800.000); break;}
+ case 'EN_SHEET':
+ case 'EN_HALF_POST' : {$pf = array( 1404.000, 1692.000); break;}
+ case 'EN_SUPER_ROYAL' : {$pf = array( 1368.000, 1944.000); break;}
+ case 'EN_DOUBLE_POST' : {$pf = array( 1368.000, 2196.000); break;}
+ case 'EN_MEDIUM' : {$pf = array( 1260.000, 1656.000); break;}
+ case 'EN_DEMY' : {$pf = array( 1260.000, 1620.000); break;}
+ case 'EN_LARGE_POST' : {$pf = array( 1188.000, 1512.000); break;}
+ case 'EN_COPY_DRAUGHT' : {$pf = array( 1152.000, 1440.000); break;}
+ case 'EN_POST' : {$pf = array( 1116.000, 1386.000); break;}
+ case 'EN_CROWN' : {$pf = array( 1080.000, 1440.000); break;}
+ case 'EN_PINCHED_POST' : {$pf = array( 1062.000, 1332.000); break;}
+ case 'EN_BRIEF' : {$pf = array( 972.000, 1152.000); break;}
+ case 'EN_FOOLSCAP' : {$pf = array( 972.000, 1224.000); break;}
+ case 'EN_SMALL_FOOLSCAP' : {$pf = array( 954.000, 1188.000); break;}
+ case 'EN_POTT' : {$pf = array( 900.000, 1080.000); break;}
+ // - Old Imperial Belgian Sizes
+ case 'BE_GRAND_AIGLE' : {$pf = array( 1984.252, 2948.031); break;}
+ case 'BE_COLOMBIER' : {$pf = array( 1757.480, 2409.449); break;}
+ case 'BE_DOUBLE_CARRE': {$pf = array( 1757.480, 2607.874); break;}
+ case 'BE_ELEPHANT' : {$pf = array( 1746.142, 2182.677); break;}
+ case 'BE_PETIT_AIGLE' : {$pf = array( 1700.787, 2381.102); break;}
+ case 'BE_GRAND_JESUS' : {$pf = array( 1559.055, 2069.291); break;}
+ case 'BE_JESUS' : {$pf = array( 1530.709, 2069.291); break;}
+ case 'BE_RAISIN' : {$pf = array( 1417.323, 1842.520); break;}
+ case 'BE_GRAND_MEDIAN': {$pf = array( 1303.937, 1714.961); break;}
+ case 'BE_DOUBLE_POSTE': {$pf = array( 1233.071, 1601.575); break;}
+ case 'BE_COQUILLE' : {$pf = array( 1218.898, 1587.402); break;}
+ case 'BE_PETIT_MEDIAN': {$pf = array( 1176.378, 1502.362); break;}
+ case 'BE_RUCHE' : {$pf = array( 1020.472, 1303.937); break;}
+ case 'BE_PROPATRIA' : {$pf = array( 977.953, 1218.898); break;}
+ case 'BE_LYS' : {$pf = array( 898.583, 1125.354); break;}
+ case 'BE_POT' : {$pf = array( 870.236, 1088.504); break;}
+ case 'BE_ROSETTE' : {$pf = array( 765.354, 983.622); break;}
+ // - Old Imperial French Sizes
+ case 'FR_UNIVERS' : {$pf = array( 2834.646, 3685.039); break;}
+ case 'FR_DOUBLE_COLOMBIER' : {$pf = array( 2551.181, 3571.654); break;}
+ case 'FR_GRANDE_MONDE' : {$pf = array( 2551.181, 3571.654); break;}
+ case 'FR_DOUBLE_SOLEIL' : {$pf = array( 2267.717, 3401.575); break;}
+ case 'FR_DOUBLE_JESUS' : {$pf = array( 2154.331, 3174.803); break;}
+ case 'FR_GRAND_AIGLE' : {$pf = array( 2125.984, 3004.724); break;}
+ case 'FR_PETIT_AIGLE' : {$pf = array( 1984.252, 2664.567); break;}
+ case 'FR_DOUBLE_RAISIN' : {$pf = array( 1842.520, 2834.646); break;}
+ case 'FR_JOURNAL' : {$pf = array( 1842.520, 2664.567); break;}
+ case 'FR_COLOMBIER_AFFICHE': {$pf = array( 1785.827, 2551.181); break;}
+ case 'FR_DOUBLE_CAVALIER' : {$pf = array( 1757.480, 2607.874); break;}
+ case 'FR_CLOCHE' : {$pf = array( 1700.787, 2267.717); break;}
+ case 'FR_SOLEIL' : {$pf = array( 1700.787, 2267.717); break;}
+ case 'FR_DOUBLE_CARRE' : {$pf = array( 1587.402, 2551.181); break;}
+ case 'FR_DOUBLE_COQUILLE' : {$pf = array( 1587.402, 2494.488); break;}
+ case 'FR_JESUS' : {$pf = array( 1587.402, 2154.331); break;}
+ case 'FR_RAISIN' : {$pf = array( 1417.323, 1842.520); break;}
+ case 'FR_CAVALIER' : {$pf = array( 1303.937, 1757.480); break;}
+ case 'FR_DOUBLE_COURONNE' : {$pf = array( 1303.937, 2040.945); break;}
+ case 'FR_CARRE' : {$pf = array( 1275.591, 1587.402); break;}
+ case 'FR_COQUILLE' : {$pf = array( 1247.244, 1587.402); break;}
+ case 'FR_DOUBLE_TELLIERE' : {$pf = array( 1247.244, 1927.559); break;}
+ case 'FR_DOUBLE_CLOCHE' : {$pf = array( 1133.858, 1700.787); break;}
+ case 'FR_DOUBLE_POT' : {$pf = array( 1133.858, 1757.480); break;}
+ case 'FR_ECU' : {$pf = array( 1133.858, 1474.016); break;}
+ case 'FR_COURONNE' : {$pf = array( 1020.472, 1303.937); break;}
+ case 'FR_TELLIERE' : {$pf = array( 963.780, 1247.244); break;}
+ case 'FR_POT' : {$pf = array( 878.740, 1133.858); break;}
+ // DEFAULT ISO A4
+ default: {$pf = array( 595.276, 841.890); break;}
+ }
+ return $pf;
+ }
+
+ /**
+ * Set page boundaries.
+ * @param $page (int) page number
+ * @param $type (string) valid values are: <ul><li>'MediaBox' : the boundaries of the physical medium on which the page shall be displayed or printed;</li><li>'CropBox' : the visible region of default user space;</li><li>'BleedBox' : the region to which the contents of the page shall be clipped when output in a production environment;</li><li>'TrimBox' : the intended dimensions of the finished page after trimming;</li><li>'ArtBox' : the page's meaningful content (including potential white space).</li></ul>
+ * @param $llx (float) lower-left x coordinate in user units.
+ * @param $lly (float) lower-left y coordinate in user units.
+ * @param $urx (float) upper-right x coordinate in user units.
+ * @param $ury (float) upper-right y coordinate in user units.
+ * @param $points (boolean) If true uses user units as unit of measure, otherwise uses PDF points.
+ * @param $k (float) Scale factor (number of points in user unit).
+ * @param $pagedim (array) Array of page dimensions.
+ * @return pagedim array of page dimensions.
+ * @since 5.0.010 (2010-05-17)
+ * @public static
+ */
+ public static function setPageBoxes($page, $type, $llx, $lly, $urx, $ury, $points=false, $k, $pagedim=array()) {
+ if (!isset($pagedim[$page])) {
+ // initialize array
+ $pagedim[$page] = array();
+ }
+ if (!in_array($type, self::$pageboxes)) {
+ return;
+ }
+ if ($points) {
+ $k = 1;
+ }
+ $pagedim[$page][$type]['llx'] = ($llx * $k);
+ $pagedim[$page][$type]['lly'] = ($lly * $k);
+ $pagedim[$page][$type]['urx'] = ($urx * $k);
+ $pagedim[$page][$type]['ury'] = ($ury * $k);
+ return $pagedim;
+ }
+
+ /**
+ * Swap X and Y coordinates of page boxes (change page boxes orientation).
+ * @param $page (int) page number
+ * @param $pagedim (array) Array of page dimensions.
+ * @return pagedim array of page dimensions.
+ * @since 5.0.010 (2010-05-17)
+ * @public static
+ */
+ public static function swapPageBoxCoordinates($page, $pagedim) {
+ foreach (self::$pageboxes as $type) {
+ // swap X and Y coordinates
+ if (isset($pagedim[$page][$type])) {
+ $tmp = $pagedim[$page][$type]['llx'];
+ $pagedim[$page][$type]['llx'] = $pagedim[$page][$type]['lly'];
+ $pagedim[$page][$type]['lly'] = $tmp;
+ $tmp = $pagedim[$page][$type]['urx'];
+ $pagedim[$page][$type]['urx'] = $pagedim[$page][$type]['ury'];
+ $pagedim[$page][$type]['ury'] = $tmp;
+ }
+ }
+ return $pagedim;
+ }
+
+ /**
+ * Get the canonical page layout mode.
+ * @param $layout (string) The page layout. Possible values are:<ul><li>SinglePage Display one page at a time</li><li>OneColumn Display the pages in one column</li><li>TwoColumnLeft Display the pages in two columns, with odd-numbered pages on the left</li><li>TwoColumnRight Display the pages in two columns, with odd-numbered pages on the right</li><li>TwoPageLeft (PDF 1.5) Display the pages two at a time, with odd-numbered pages on the left</li><li>TwoPageRight (PDF 1.5) Display the pages two at a time, with odd-numbered pages on the right</li></ul>
+ * @return (string) Canonical page layout name.
+ * @public static
+ */
+ public static function getPageLayoutMode($layout='SinglePage') {
+ switch ($layout) {
+ case 'default':
+ case 'single':
+ case 'SinglePage': {
+ $layout_mode = 'SinglePage';
+ break;
+ }
+ case 'continuous':
+ case 'OneColumn': {
+ $layout_mode = 'OneColumn';
+ break;
+ }
+ case 'two':
+ case 'TwoColumnLeft': {
+ $layout_mode = 'TwoColumnLeft';
+ break;
+ }
+ case 'TwoColumnRight': {
+ $layout_mode = 'TwoColumnRight';
+ break;
+ }
+ case 'TwoPageLeft': {
+ $layout_mode = 'TwoPageLeft';
+ break;
+ }
+ case 'TwoPageRight': {
+ $layout_mode = 'TwoPageRight';
+ break;
+ }
+ default: {
+ $layout_mode = 'SinglePage';
+ }
+ }
+ return $layout_mode;
+ }
+
+ /**
+ * Get the canonical page layout mode.
+ * @param $mode (string) A name object specifying how the document should be displayed when opened:<ul><li>UseNone Neither document outline nor thumbnail images visible</li><li>UseOutlines Document outline visible</li><li>UseThumbs Thumbnail images visible</li><li>FullScreen Full-screen mode, with no menu bar, window controls, or any other window visible</li><li>UseOC (PDF 1.5) Optional content group panel visible</li><li>UseAttachments (PDF 1.6) Attachments panel visible</li></ul>
+ * @return (string) Canonical page mode name.
+ * @public static
+ */
+ public static function getPageMode($mode='UseNone') {
+ switch ($mode) {
+ case 'UseNone': {
+ $page_mode = 'UseNone';
+ break;
+ }
+ case 'UseOutlines': {
+ $page_mode = 'UseOutlines';
+ break;
+ }
+ case 'UseThumbs': {
+ $page_mode = 'UseThumbs';
+ break;
+ }
+ case 'FullScreen': {
+ $page_mode = 'FullScreen';
+ break;
+ }
+ case 'UseOC': {
+ $page_mode = 'UseOC';
+ break;
+ }
+ case '': {
+ $page_mode = 'UseAttachments';
+ break;
+ }
+ default: {
+ $page_mode = 'UseNone';
+ }
+ }
+ return $page_mode;
+ }
+
+ /**
+ * Check if the URL exist.
+ * @param $url (string) URL to check.
+ * @return Boolean true if the URl exist, false otherwise.
+ * @since 5.9.204 (2013-01-28)
+ * @public static
+ */
+ public static function isValidURL($url) {
+ $headers = @get_headers($url);
+ return (strpos($headers[0], '200') !== false);
+ }
+
+ /**
+ * Removes SHY characters from text.
+ * Unicode Data:<ul>
+ * <li>Name : SOFT HYPHEN, commonly abbreviated as SHY</li>
+ * <li>HTML Entity (decimal): "&amp;#173;"</li>
+ * <li>HTML Entity (hex): "&amp;#xad;"</li>
+ * <li>HTML Entity (named): "&amp;shy;"</li>
+ * <li>How to type in Microsoft Windows: [Alt +00AD] or [Alt 0173]</li>
+ * <li>UTF-8 (hex): 0xC2 0xAD (c2ad)</li>
+ * <li>UTF-8 character: chr(194).chr(173)</li>
+ * </ul>
+ * @param $txt (string) input string
+ * @param $unicode (boolean) True if we are in unicode mode, false otherwise.
+ * @return string without SHY characters.
+ * @since (4.5.019) 2009-02-28
+ * @public static
+ */
+ public static function removeSHY($txt='', $unicode=true) {
+ $txt = preg_replace('/([\\xc2]{1}[\\xad]{1})/', '', $txt);
+ if (!$unicode) {
+ $txt = preg_replace('/([\\xad]{1})/', '', $txt);
+ }
+ return $txt;
+ }
+
+
+ /**
+ * Get the border mode accounting for multicell position (opens bottom side of multicell crossing pages)
+ * @param $brd (mixed) Indicates if borders must be drawn around the cell block. The value can be a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul>or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul> or an array of line styles for each border group: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)))
+ * @param $position (string) multicell position: 'start', 'middle', 'end'
+ * @param $opencell (boolean) True when the cell is left open at the page bottom, false otherwise.
+ * @return border mode array
+ * @since 4.4.002 (2008-12-09)
+ * @public static
+ */
+ public static function getBorderMode($brd, $position='start', $opencell=true) {
+ if ((!$opencell) OR empty($brd)) {
+ return $brd;
+ }
+ if ($brd == 1) {
+ $brd = 'LTRB';
+ }
+ if (is_string($brd)) {
+ // convert string to array
+ $slen = strlen($brd);
+ $newbrd = array();
+ for ($i = 0; $i < $slen; ++$i) {
+ $newbrd[$brd[$i]] = array('cap' => 'square', 'join' => 'miter');
+ }
+ $brd = $newbrd;
+ }
+ foreach ($brd as $border => $style) {
+ switch ($position) {
+ case 'start': {
+ if (strpos($border, 'B') !== false) {
+ // remove bottom line
+ $newkey = str_replace('B', '', $border);
+ if (strlen($newkey) > 0) {
+ $brd[$newkey] = $style;
+ }
+ unset($brd[$border]);
+ }
+ break;
+ }
+ case 'middle': {
+ if (strpos($border, 'B') !== false) {
+ // remove bottom line
+ $newkey = str_replace('B', '', $border);
+ if (strlen($newkey) > 0) {
+ $brd[$newkey] = $style;
+ }
+ unset($brd[$border]);
+ $border = $newkey;
+ }
+ if (strpos($border, 'T') !== false) {
+ // remove bottom line
+ $newkey = str_replace('T', '', $border);
+ if (strlen($newkey) > 0) {
+ $brd[$newkey] = $style;
+ }
+ unset($brd[$border]);
+ }
+ break;
+ }
+ case 'end': {
+ if (strpos($border, 'T') !== false) {
+ // remove bottom line
+ $newkey = str_replace('T', '', $border);
+ if (strlen($newkey) > 0) {
+ $brd[$newkey] = $style;
+ }
+ unset($brd[$border]);
+ }
+ break;
+ }
+ }
+ }
+ return $brd;
+ }
+
+ /**
+ * Determine whether a string is empty.
+ * @param $str (string) string to be checked
+ * @return boolean true if string is empty
+ * @since 4.5.044 (2009-04-16)
+ * @public static
+ */
+ public static function empty_string($str) {
+ return (is_null($str) OR (is_string($str) AND (strlen($str) == 0)));
+ }
+
+ /**
+ * Returns a temporary filename for caching object on filesystem.
+ * @param $name (string) Prefix to add to the file name.
+ * @return string filename.
+ * @since 4.5.000 (2008-12-31)
+ * @public static
+ */
+ public static function getObjFilename($name) {
+ return tempnam(K_PATH_CACHE, $name.'_');
+ }
+
+ /**
+ * Add "\" before "\", "(" and ")"
+ * @param $s (string) string to escape.
+ * @return string escaped string.
+ * @public static
+ */
+ public static function _escape($s) {
+ // the chr(13) substitution fixes the Bugs item #1421290.
+ return strtr($s, array(')' => '\\)', '(' => '\\(', '\\' => '\\\\', chr(13) => '\r'));
+ }
+
+ /**
+ * Escape some special characters (&lt; &gt; &amp;) for XML output.
+ * @param $str (string) Input string to convert.
+ * @return converted string
+ * @since 5.9.121 (2011-09-28)
+ * @public static
+ */
+ public static function _escapeXML($str) {
+ $replaceTable = array("\0" => '', '&' => '&amp;', '<' => '&lt;', '>' => '&gt;');
+ $str = strtr($str, $replaceTable);
+ return $str;
+ }
+
+ /**
+ * Creates a copy of a class object
+ * @param $object (object) class object to be cloned
+ * @return cloned object
+ * @since 4.5.029 (2009-03-19)
+ * @public static
+ */
+ public static function objclone($object) {
+ if (($object instanceof Imagick) AND (version_compare(phpversion('imagick'), '3.0.1') !== 1)) {
+ // on the versions after 3.0.1 the clone() method was deprecated in favour of clone keyword
+ return @$object->clone();
+ }
+ return @clone($object);
+ }
+
+ /**
+ * Ouput input data and compress it if possible.
+ * @param $data (string) Data to output.
+ * @param $length (int) Data length in bytes.
+ * @since 5.9.086
+ * @public static
+ */
+ public static function sendOutputData($data, $length) {
+ if (!isset($_SERVER['HTTP_ACCEPT_ENCODING']) OR empty($_SERVER['HTTP_ACCEPT_ENCODING'])) {
+ // the content length may vary if the server is using compression
+ header('Content-Length: '.$length);
+ }
+ echo $data;
+ }
+
+ /**
+ * Replace page number aliases with number.
+ * @param $page (string) Page content.
+ * @param $replace (array) Array of replacements (array keys are replacement strings, values are alias arrays).
+ * @param $diff (int) If passed, this will be set to the total char number difference between alias and replacements.
+ * @return replaced page content and updated $diff parameter as array.
+ * @public static
+ */
+ public static function replacePageNumAliases($page, $replace, $diff=0) {
+ foreach ($replace as $rep) {
+ foreach ($rep[3] as $a) {
+ if (strpos($page, $a) !== false) {
+ $page = str_replace($a, $rep[0], $page);
+ $diff += ($rep[2] - $rep[1]);
+ }
+ }
+ }
+ return array($page, $diff);
+ }
+
+ /**
+ * Returns timestamp in seconds from formatted date-time.
+ * @param $date (string) Formatted date-time.
+ * @return int seconds.
+ * @since 5.9.152 (2012-03-23)
+ * @public static
+ */
+ public static function getTimestamp($date) {
+ if (($date[0] == 'D') AND ($date[1] == ':')) {
+ // remove date prefix if present
+ $date = substr($date, 2);
+ }
+ return strtotime($date);
+ }
+
+ /**
+ * Returns a formatted date-time.
+ * @param $time (int) Time in seconds.
+ * @return string escaped date string.
+ * @since 5.9.152 (2012-03-23)
+ * @public static
+ */
+ public static function getFormattedDate($time) {
+ return substr_replace(date('YmdHisO', intval($time)), '\'', (0 - 2), 0).'\'';
+ }
+
+ /**
+ * Get ULONG from string (Big Endian 32-bit unsigned integer).
+ * @param $str (string) string from where to extract value
+ * @param $offset (int) point from where to read the data
+ * @return int 32 bit value
+ * @author Nicola Asuni
+ * @since 5.2.000 (2010-06-02)
+ * @public static
+ */
+ public static function _getULONG($str, $offset) {
+ $v = unpack('Ni', substr($str, $offset, 4));
+ return $v['i'];
+ }
+
+ /**
+ * Get USHORT from string (Big Endian 16-bit unsigned integer).
+ * @param $str (string) string from where to extract value
+ * @param $offset (int) point from where to read the data
+ * @return int 16 bit value
+ * @author Nicola Asuni
+ * @since 5.2.000 (2010-06-02)
+ * @public static
+ */
+ public static function _getUSHORT($str, $offset) {
+ $v = unpack('ni', substr($str, $offset, 2));
+ return $v['i'];
+ }
+
+ /**
+ * Get SHORT from string (Big Endian 16-bit signed integer).
+ * @param $str (string) String from where to extract value.
+ * @param $offset (int) Point from where to read the data.
+ * @return int 16 bit value
+ * @author Nicola Asuni
+ * @since 5.2.000 (2010-06-02)
+ * @public static
+ */
+ public static function _getSHORT($str, $offset) {
+ $v = unpack('si', substr($str, $offset, 2));
+ return $v['i'];
+ }
+
+ /**
+ * Get FWORD from string (Big Endian 16-bit signed integer).
+ * @param $str (string) String from where to extract value.
+ * @param $offset (int) Point from where to read the data.
+ * @return int 16 bit value
+ * @author Nicola Asuni
+ * @since 5.9.123 (2011-09-30)
+ * @public static
+ */
+ public static function _getFWORD($str, $offset) {
+ $v = self::_getUSHORT($str, $offset);
+ if ($v > 0x7fff) {
+ $v -= 0x10000;
+ }
+ return $v;
+ }
+
+ /**
+ * Get UFWORD from string (Big Endian 16-bit unsigned integer).
+ * @param $str (string) string from where to extract value
+ * @param $offset (int) point from where to read the data
+ * @return int 16 bit value
+ * @author Nicola Asuni
+ * @since 5.9.123 (2011-09-30)
+ * @public static
+ */
+ public static function _getUFWORD($str, $offset) {
+ $v = self::_getUSHORT($str, $offset);
+ return $v;
+ }
+
+ /**
+ * Get FIXED from string (32-bit signed fixed-point number (16.16).
+ * @param $str (string) string from where to extract value
+ * @param $offset (int) point from where to read the data
+ * @return int 16 bit value
+ * @author Nicola Asuni
+ * @since 5.9.123 (2011-09-30)
+ * @public static
+ */
+ public static function _getFIXED($str, $offset) {
+ // mantissa
+ $m = self::_getFWORD($str, $offset);
+ // fraction
+ $f = self::_getUSHORT($str, ($offset + 2));
+ $v = floatval(''.$m.'.'.$f.'');
+ return $v;
+ }
+
+ /**
+ * Get BYTE from string (8-bit unsigned integer).
+ * @param $str (string) String from where to extract value.
+ * @param $offset (int) Point from where to read the data.
+ * @return int 8 bit value
+ * @author Nicola Asuni
+ * @since 5.2.000 (2010-06-02)
+ * @public static
+ */
+ public static function _getBYTE($str, $offset) {
+ $v = unpack('Ci', substr($str, $offset, 1));
+ return $v['i'];
+ }
+ /**
+ * Binary-safe and URL-safe file read.
+ * Reads up to length bytes from the file pointer referenced by handle. Reading stops as soon as one of the following conditions is met: length bytes have been read; EOF (end of file) is reached.
+ * @param $handle (resource)
+ * @param $length (int)
+ * @return Returns the read string or FALSE in case of error.
+ * @author Nicola Asuni
+ * @since 4.5.027 (2009-03-16)
+ * @public static
+ */
+ public static function rfread($handle, $length) {
+ $data = fread($handle, $length);
+ if ($data === false) {
+ return false;
+ }
+ $rest = ($length - strlen($data));
+ if ($rest > 0) {
+ $data .= self::rfread($handle, $rest);
+ }
+ return $data;
+ }
+
+ /**
+ * Read a 4-byte (32 bit) integer from file.
+ * @param $f (string) file name.
+ * @return 4-byte integer
+ * @public static
+ */
+ public static function _freadint($f) {
+ $a = unpack('Ni', fread($f, 4));
+ return $a['i'];
+ }
+
+ /**
+ * Returns a string containing random data to be used as a seed for encryption methods.
+ * @param $seed (string) starting seed value
+ * @return string containing random data
+ * @author Nicola Asuni
+ * @since 5.9.006 (2010-10-19)
+ * @public static
+ */
+ public static function getRandomSeed($seed='') {
+ $seed .= microtime();
+ if (function_exists('openssl_random_pseudo_bytes') AND (strtoupper(substr(PHP_OS, 0, 3)) !== 'WIN')) {
+ // this is not used on windows systems because it is very slow for a know bug
+ $seed .= openssl_random_pseudo_bytes(512);
+ } else {
+ for ($i = 0; $i < 23; ++$i) {
+ $seed .= uniqid('', true);
+ }
+ }
+ $seed .= uniqid('', true);
+ $seed .= rand();
+ $seed .= getmypid();
+ $seed .= __FILE__;
+ if (isset($_SERVER['REMOTE_ADDR'])) {
+ $seed .= $_SERVER['REMOTE_ADDR'];
+ }
+ if (isset($_SERVER['HTTP_USER_AGENT'])) {
+ $seed .= $_SERVER['HTTP_USER_AGENT'];
+ }
+ if (isset($_SERVER['HTTP_ACCEPT'])) {
+ $seed .= $_SERVER['HTTP_ACCEPT'];
+ }
+ if (isset($_SERVER['HTTP_ACCEPT_ENCODING'])) {
+ $seed .= $_SERVER['HTTP_ACCEPT_ENCODING'];
+ }
+ if (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {
+ $seed .= $_SERVER['HTTP_ACCEPT_LANGUAGE'];
+ }
+ if (isset($_SERVER['HTTP_ACCEPT_CHARSET'])) {
+ $seed .= $_SERVER['HTTP_ACCEPT_CHARSET'];
+ }
+ $seed .= rand();
+ $seed .= uniqid('', true);
+ $seed .= microtime();
+ return $seed;
+ }
+
+ /**
+ * Encrypts a string using MD5 and returns it's value as a binary string.
+ * @param $str (string) input string
+ * @return String MD5 encrypted binary string
+ * @since 2.0.000 (2008-01-02)
+ * @public static
+ */
+ public static function _md5_16($str) {
+ return pack('H*', md5($str));
+ }
+
+ /**
+ * Returns the input text exrypted using AES algorithm and the specified key.
+ * This method requires mcrypt.
+ * @param $key (string) encryption key
+ * @param $text (String) input text to be encrypted
+ * @return String encrypted text
+ * @author Nicola Asuni
+ * @since 5.0.005 (2010-05-11)
+ * @public static
+ */
+ public static function _AES($key, $text) {
+ // padding (RFC 2898, PKCS #5: Password-Based Cryptography Specification Version 2.0)
+ $padding = 16 - (strlen($text) % 16);
+ $text .= str_repeat(chr($padding), $padding);
+ $iv = mcrypt_create_iv(mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC), MCRYPT_RAND);
+ $text = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, $text, MCRYPT_MODE_CBC, $iv);
+ $text = $iv.$text;
+ return $text;
+ }
+
+ /**
+ * Returns the input text encrypted using RC4 algorithm and the specified key.
+ * RC4 is the standard encryption algorithm used in PDF format
+ * @param $key (string) Encryption key.
+ * @param $text (String) Input text to be encrypted.
+ * @param $last_enc_key (String) Reference to last RC4 key encrypted.
+ * @param $last_enc_key_c (String) Reference to last RC4 computed key.
+ * @return String encrypted text
+ * @since 2.0.000 (2008-01-02)
+ * @author Klemen Vodopivec, Nicola Asuni
+ * @public static
+ */
+ public static function _RC4($key, $text, &$last_enc_key, &$last_enc_key_c) {
+ if (function_exists('mcrypt_encrypt') AND ($out = @mcrypt_encrypt(MCRYPT_ARCFOUR, $key, $text, MCRYPT_MODE_STREAM, ''))) {
+ // try to use mcrypt function if exist
+ return $out;
+ }
+ if ($last_enc_key != $key) {
+ $k = str_repeat($key, ((256 / strlen($key)) + 1));
+ $rc4 = range(0, 255);
+ $j = 0;
+ for ($i = 0; $i < 256; ++$i) {
+ $t = $rc4[$i];
+ $j = ($j + $t + ord($k[$i])) % 256;
+ $rc4[$i] = $rc4[$j];
+ $rc4[$j] = $t;
+ }
+ $last_enc_key = $key;
+ $last_enc_key_c = $rc4;
+ } else {
+ $rc4 = $last_enc_key_c;
+ }
+ $len = strlen($text);
+ $a = 0;
+ $b = 0;
+ $out = '';
+ for ($i = 0; $i < $len; ++$i) {
+ $a = ($a + 1) % 256;
+ $t = $rc4[$a];
+ $b = ($b + $t) % 256;
+ $rc4[$a] = $rc4[$b];
+ $rc4[$b] = $t;
+ $k = $rc4[($rc4[$a] + $rc4[$b]) % 256];
+ $out .= chr(ord($text[$i]) ^ $k);
+ }
+ return $out;
+ }
+
+ /**
+ * Return the premission code used on encryption (P value).
+ * @param $permissions (Array) the set of permissions (specify the ones you want to block).
+ * @param $mode (int) encryption strength: 0 = RC4 40 bit; 1 = RC4 128 bit; 2 = AES 128 bit; 3 = AES 256 bit.
+ * @since 5.0.005 (2010-05-12)
+ * @author Nicola Asuni
+ * @public static
+ */
+ public static function getUserPermissionCode($permissions, $mode=0) {
+ $options = array(
+ 'owner' => 2, // bit 2 -- inverted logic: cleared by default
+ 'print' => 4, // bit 3
+ 'modify' => 8, // bit 4
+ 'copy' => 16, // bit 5
+ 'annot-forms' => 32, // bit 6
+ 'fill-forms' => 256, // bit 9
+ 'extract' => 512, // bit 10
+ 'assemble' => 1024,// bit 11
+ 'print-high' => 2048 // bit 12
+ );
+ $protection = 2147422012; // 32 bit: (01111111 11111111 00001111 00111100)
+ foreach ($permissions as $permission) {
+ if (isset($options[$permission])) {
+ if (($mode > 0) OR ($options[$permission] <= 32)) {
+ // set only valid permissions
+ if ($options[$permission] == 2) {
+ // the logic for bit 2 is inverted (cleared by default)
+ $protection += $options[$permission];
+ } else {
+ $protection -= $options[$permission];
+ }
+ }
+ }
+ }
+ return $protection;
+ }
+
+ /**
+ * Convert hexadecimal string to string
+ * @param $bs (string) byte-string to convert
+ * @return String
+ * @since 5.0.005 (2010-05-12)
+ * @author Nicola Asuni
+ * @public static
+ */
+ public static function convertHexStringToString($bs) {
+ $string = ''; // string to be returned
+ $bslength = strlen($bs);
+ if (($bslength % 2) != 0) {
+ // padding
+ $bs .= '0';
+ ++$bslength;
+ }
+ for ($i = 0; $i < $bslength; $i += 2) {
+ $string .= chr(hexdec($bs[$i].$bs[($i + 1)]));
+ }
+ return $string;
+ }
+
+ /**
+ * Convert string to hexadecimal string (byte string)
+ * @param $s (string) string to convert
+ * @return byte string
+ * @since 5.0.010 (2010-05-17)
+ * @author Nicola Asuni
+ * @public static
+ */
+ public static function convertStringToHexString($s) {
+ $bs = '';
+ $chars = preg_split('//', $s, -1, PREG_SPLIT_NO_EMPTY);
+ foreach ($chars as $c) {
+ $bs .= sprintf('%02s', dechex(ord($c)));
+ }
+ return $bs;
+ }
+
+ /**
+ * Convert encryption P value to a string of bytes, low-order byte first.
+ * @param $protection (string) 32bit encryption permission value (P value)
+ * @return String
+ * @since 5.0.005 (2010-05-12)
+ * @author Nicola Asuni
+ * @public static
+ */
+ public static function getEncPermissionsString($protection) {
+ $binprot = sprintf('%032b', $protection);
+ $str = chr(bindec(substr($binprot, 24, 8)));
+ $str .= chr(bindec(substr($binprot, 16, 8)));
+ $str .= chr(bindec(substr($binprot, 8, 8)));
+ $str .= chr(bindec(substr($binprot, 0, 8)));
+ return $str;
+ }
+
+ /**
+ * Encode a name object.
+ * @param $name (string) Name object to encode.
+ * @return (string) Encoded name object.
+ * @author Nicola Asuni
+ * @since 5.9.097 (2011-06-23)
+ * @public static
+ */
+ public static function encodeNameObject($name) {
+ $escname = '';
+ $length = strlen($name);
+ for ($i = 0; $i < $length; ++$i) {
+ $chr = $name[$i];
+ if (preg_match('/[0-9a-zA-Z]/', $chr) == 1) {
+ $escname .= $chr;
+ } else {
+ $escname .= sprintf('#%02X', ord($chr));
+ }
+ }
+ return $escname;
+ }
+
+ /**
+ * Convert JavaScript form fields properties array to Annotation Properties array.
+ * @param $prop (array) javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
+ * @param $spot_colors (array) Reference to spot colors array.
+ * @param $rtl (boolean) True if in Right-To-Left text direction mode, false otherwise.
+ * @return array of annotation properties
+ * @author Nicola Asuni
+ * @since 4.8.000 (2009-09-06)
+ * @public static
+ */
+ public static function getAnnotOptFromJSProp($prop, &$spot_colors, $rtl=false) {
+ if (isset($prop['aopt']) AND is_array($prop['aopt'])) {
+ // the annotation options area lready defined
+ return $prop['aopt'];
+ }
+ $opt = array(); // value to be returned
+ // alignment: Controls how the text is laid out within the text field.
+ if (isset($prop['alignment'])) {
+ switch ($prop['alignment']) {
+ case 'left': {
+ $opt['q'] = 0;
+ break;
+ }
+ case 'center': {
+ $opt['q'] = 1;
+ break;
+ }
+ case 'right': {
+ $opt['q'] = 2;
+ break;
+ }
+ default: {
+ $opt['q'] = ($rtl)?2:0;
+ break;
+ }
+ }
+ }
+ // lineWidth: Specifies the thickness of the border when stroking the perimeter of a field's rectangle.
+ if (isset($prop['lineWidth'])) {
+ $linewidth = intval($prop['lineWidth']);
+ } else {
+ $linewidth = 1;
+ }
+ // borderStyle: The border style for a field.
+ if (isset($prop['borderStyle'])) {
+ switch ($prop['borderStyle']) {
+ case 'border.d':
+ case 'dashed': {
+ $opt['border'] = array(0, 0, $linewidth, array(3, 2));
+ $opt['bs'] = array('w'=>$linewidth, 's'=>'D', 'd'=>array(3, 2));
+ break;
+ }
+ case 'border.b':
+ case 'beveled': {
+ $opt['border'] = array(0, 0, $linewidth);
+ $opt['bs'] = array('w'=>$linewidth, 's'=>'B');
+ break;
+ }
+ case 'border.i':
+ case 'inset': {
+ $opt['border'] = array(0, 0, $linewidth);
+ $opt['bs'] = array('w'=>$linewidth, 's'=>'I');
+ break;
+ }
+ case 'border.u':
+ case 'underline': {
+ $opt['border'] = array(0, 0, $linewidth);
+ $opt['bs'] = array('w'=>$linewidth, 's'=>'U');
+ break;
+ }
+ case 'border.s':
+ case 'solid': {
+ $opt['border'] = array(0, 0, $linewidth);
+ $opt['bs'] = array('w'=>$linewidth, 's'=>'S');
+ break;
+ }
+ default: {
+ break;
+ }
+ }
+ }
+ if (isset($prop['border']) AND is_array($prop['border'])) {
+ $opt['border'] = $prop['border'];
+ }
+ if (!isset($opt['mk'])) {
+ $opt['mk'] = array();
+ }
+ if (!isset($opt['mk']['if'])) {
+ $opt['mk']['if'] = array();
+ }
+ $opt['mk']['if']['a'] = array(0.5, 0.5);
+ // buttonAlignX: Controls how space is distributed from the left of the button face with respect to the icon.
+ if (isset($prop['buttonAlignX'])) {
+ $opt['mk']['if']['a'][0] = $prop['buttonAlignX'];
+ }
+ // buttonAlignY: Controls how unused space is distributed from the bottom of the button face with respect to the icon.
+ if (isset($prop['buttonAlignY'])) {
+ $opt['mk']['if']['a'][1] = $prop['buttonAlignY'];
+ }
+ // buttonFitBounds: If true, the extent to which the icon may be scaled is set to the bounds of the button field.
+ if (isset($prop['buttonFitBounds']) AND ($prop['buttonFitBounds'] == 'true')) {
+ $opt['mk']['if']['fb'] = true;
+ }
+ // buttonScaleHow: Controls how the icon is scaled (if necessary) to fit inside the button face.
+ if (isset($prop['buttonScaleHow'])) {
+ switch ($prop['buttonScaleHow']) {
+ case 'scaleHow.proportional': {
+ $opt['mk']['if']['s'] = 'P';
+ break;
+ }
+ case 'scaleHow.anamorphic': {
+ $opt['mk']['if']['s'] = 'A';
+ break;
+ }
+ }
+ }
+ // buttonScaleWhen: Controls when an icon is scaled to fit inside the button face.
+ if (isset($prop['buttonScaleWhen'])) {
+ switch ($prop['buttonScaleWhen']) {
+ case 'scaleWhen.always': {
+ $opt['mk']['if']['sw'] = 'A';
+ break;
+ }
+ case 'scaleWhen.never': {
+ $opt['mk']['if']['sw'] = 'N';
+ break;
+ }
+ case 'scaleWhen.tooBig': {
+ $opt['mk']['if']['sw'] = 'B';
+ break;
+ }
+ case 'scaleWhen.tooSmall': {
+ $opt['mk']['if']['sw'] = 'S';
+ break;
+ }
+ }
+ }
+ // buttonPosition: Controls how the text and the icon of the button are positioned with respect to each other within the button face.
+ if (isset($prop['buttonPosition'])) {
+ switch ($prop['buttonPosition']) {
+ case 0:
+ case 'position.textOnly': {
+ $opt['mk']['tp'] = 0;
+ break;
+ }
+ case 1:
+ case 'position.iconOnly': {
+ $opt['mk']['tp'] = 1;
+ break;
+ }
+ case 2:
+ case 'position.iconTextV': {
+ $opt['mk']['tp'] = 2;
+ break;
+ }
+ case 3:
+ case 'position.textIconV': {
+ $opt['mk']['tp'] = 3;
+ break;
+ }
+ case 4:
+ case 'position.iconTextH': {
+ $opt['mk']['tp'] = 4;
+ break;
+ }
+ case 5:
+ case 'position.textIconH': {
+ $opt['mk']['tp'] = 5;
+ break;
+ }
+ case 6:
+ case 'position.overlay': {
+ $opt['mk']['tp'] = 6;
+ break;
+ }
+ }
+ }
+ // fillColor: Specifies the background color for a field.
+ if (isset($prop['fillColor'])) {
+ if (is_array($prop['fillColor'])) {
+ $opt['mk']['bg'] = $prop['fillColor'];
+ } else {
+ $opt['mk']['bg'] = TCPDF_COLORS::convertHTMLColorToDec($prop['fillColor'], $spot_colors);
+ }
+ }
+ // strokeColor: Specifies the stroke color for a field that is used to stroke the rectangle of the field with a line as large as the line width.
+ if (isset($prop['strokeColor'])) {
+ if (is_array($prop['strokeColor'])) {
+ $opt['mk']['bc'] = $prop['strokeColor'];
+ } else {
+ $opt['mk']['bc'] = TCPDF_COLORS::convertHTMLColorToDec($prop['strokeColor'], $spot_colors);
+ }
+ }
+ // rotation: The rotation of a widget in counterclockwise increments.
+ if (isset($prop['rotation'])) {
+ $opt['mk']['r'] = $prop['rotation'];
+ }
+ // charLimit: Limits the number of characters that a user can type into a text field.
+ if (isset($prop['charLimit'])) {
+ $opt['maxlen'] = intval($prop['charLimit']);
+ }
+ if (!isset($ff)) {
+ $ff = 0; // default value
+ }
+ // readonly: The read-only characteristic of a field. If a field is read-only, the user can see the field but cannot change it.
+ if (isset($prop['readonly']) AND ($prop['readonly'] == 'true')) {
+ $ff += 1 << 0;
+ }
+ // required: Specifies whether a field requires a value.
+ if (isset($prop['required']) AND ($prop['required'] == 'true')) {
+ $ff += 1 << 1;
+ }
+ // multiline: Controls how text is wrapped within the field.
+ if (isset($prop['multiline']) AND ($prop['multiline'] == 'true')) {
+ $ff += 1 << 12;
+ }
+ // password: Specifies whether the field should display asterisks when data is entered in the field.
+ if (isset($prop['password']) AND ($prop['password'] == 'true')) {
+ $ff += 1 << 13;
+ }
+ // NoToggleToOff: If set, exactly one radio button shall be selected at all times; selecting the currently selected button has no effect.
+ if (isset($prop['NoToggleToOff']) AND ($prop['NoToggleToOff'] == 'true')) {
+ $ff += 1 << 14;
+ }
+ // Radio: If set, the field is a set of radio buttons.
+ if (isset($prop['Radio']) AND ($prop['Radio'] == 'true')) {
+ $ff += 1 << 15;
+ }
+ // Pushbutton: If set, the field is a pushbutton that does not retain a permanent value.
+ if (isset($prop['Pushbutton']) AND ($prop['Pushbutton'] == 'true')) {
+ $ff += 1 << 16;
+ }
+ // Combo: If set, the field is a combo box; if clear, the field is a list box.
+ if (isset($prop['Combo']) AND ($prop['Combo'] == 'true')) {
+ $ff += 1 << 17;
+ }
+ // editable: Controls whether a combo box is editable.
+ if (isset($prop['editable']) AND ($prop['editable'] == 'true')) {
+ $ff += 1 << 18;
+ }
+ // Sort: If set, the field's option items shall be sorted alphabetically.
+ if (isset($prop['Sort']) AND ($prop['Sort'] == 'true')) {
+ $ff += 1 << 19;
+ }
+ // fileSelect: If true, sets the file-select flag in the Options tab of the text field (Field is Used for File Selection).
+ if (isset($prop['fileSelect']) AND ($prop['fileSelect'] == 'true')) {
+ $ff += 1 << 20;
+ }
+ // multipleSelection: If true, indicates that a list box allows a multiple selection of items.
+ if (isset($prop['multipleSelection']) AND ($prop['multipleSelection'] == 'true')) {
+ $ff += 1 << 21;
+ }
+ // doNotSpellCheck: If true, spell checking is not performed on this editable text field.
+ if (isset($prop['doNotSpellCheck']) AND ($prop['doNotSpellCheck'] == 'true')) {
+ $ff += 1 << 22;
+ }
+ // doNotScroll: If true, the text field does not scroll and the user, therefore, is limited by the rectangular region designed for the field.
+ if (isset($prop['doNotScroll']) AND ($prop['doNotScroll'] == 'true')) {
+ $ff += 1 << 23;
+ }
+ // comb: If set to true, the field background is drawn as series of boxes (one for each character in the value of the field) and each character of the content is drawn within those boxes. The number of boxes drawn is determined from the charLimit property. It applies only to text fields. The setter will also raise if any of the following field properties are also set multiline, password, and fileSelect. A side-effect of setting this property is that the doNotScroll property is also set.
+ if (isset($prop['comb']) AND ($prop['comb'] == 'true')) {
+ $ff += 1 << 24;
+ }
+ // radiosInUnison: If false, even if a group of radio buttons have the same name and export value, they behave in a mutually exclusive fashion, like HTML radio buttons.
+ if (isset($prop['radiosInUnison']) AND ($prop['radiosInUnison'] == 'true')) {
+ $ff += 1 << 25;
+ }
+ // richText: If true, the field allows rich text formatting.
+ if (isset($prop['richText']) AND ($prop['richText'] == 'true')) {
+ $ff += 1 << 25;
+ }
+ // commitOnSelChange: Controls whether a field value is committed after a selection change.
+ if (isset($prop['commitOnSelChange']) AND ($prop['commitOnSelChange'] == 'true')) {
+ $ff += 1 << 26;
+ }
+ $opt['ff'] = $ff;
+ // defaultValue: The default value of a field - that is, the value that the field is set to when the form is reset.
+ if (isset($prop['defaultValue'])) {
+ $opt['dv'] = $prop['defaultValue'];
+ }
+ $f = 4; // default value for annotation flags
+ // readonly: The read-only characteristic of a field. If a field is read-only, the user can see the field but cannot change it.
+ if (isset($prop['readonly']) AND ($prop['readonly'] == 'true')) {
+ $f += 1 << 6;
+ }
+ // display: Controls whether the field is hidden or visible on screen and in print.
+ if (isset($prop['display'])) {
+ if ($prop['display'] == 'display.visible') {
+ //
+ } elseif ($prop['display'] == 'display.hidden') {
+ $f += 1 << 1;
+ } elseif ($prop['display'] == 'display.noPrint') {
+ $f -= 1 << 2;
+ } elseif ($prop['display'] == 'display.noView') {
+ $f += 1 << 5;
+ }
+ }
+ $opt['f'] = $f;
+ // currentValueIndices: Reads and writes single or multiple values of a list box or combo box.
+ if (isset($prop['currentValueIndices']) AND is_array($prop['currentValueIndices'])) {
+ $opt['i'] = $prop['currentValueIndices'];
+ }
+ // value: The value of the field data that the user has entered.
+ if (isset($prop['value'])) {
+ if (is_array($prop['value'])) {
+ $opt['opt'] = array();
+ foreach ($prop['value'] AS $key => $optval) {
+ // exportValues: An array of strings representing the export values for the field.
+ if (isset($prop['exportValues'][$key])) {
+ $opt['opt'][$key] = array($prop['exportValues'][$key], $prop['value'][$key]);
+ } else {
+ $opt['opt'][$key] = $prop['value'][$key];
+ }
+ }
+ } else {
+ $opt['v'] = $prop['value'];
+ }
+ }
+ // richValue: This property specifies the text contents and formatting of a rich text field.
+ if (isset($prop['richValue'])) {
+ $opt['rv'] = $prop['richValue'];
+ }
+ // submitName: If nonempty, used during form submission instead of name. Only applicable if submitting in HTML format (that is, URL-encoded).
+ if (isset($prop['submitName'])) {
+ $opt['tm'] = $prop['submitName'];
+ }
+ // name: Fully qualified field name.
+ if (isset($prop['name'])) {
+ $opt['t'] = $prop['name'];
+ }
+ // userName: The user name (short description string) of the field.
+ if (isset($prop['userName'])) {
+ $opt['tu'] = $prop['userName'];
+ }
+ // highlight: Defines how a button reacts when a user clicks it.
+ if (isset($prop['highlight'])) {
+ switch ($prop['highlight']) {
+ case 'none':
+ case 'highlight.n': {
+ $opt['h'] = 'N';
+ break;
+ }
+ case 'invert':
+ case 'highlight.i': {
+ $opt['h'] = 'i';
+ break;
+ }
+ case 'push':
+ case 'highlight.p': {
+ $opt['h'] = 'P';
+ break;
+ }
+ case 'outline':
+ case 'highlight.o': {
+ $opt['h'] = 'O';
+ break;
+ }
+ }
+ }
+ // Unsupported options:
+ // - calcOrderIndex: Changes the calculation order of fields in the document.
+ // - delay: Delays the redrawing of a field's appearance.
+ // - defaultStyle: This property defines the default style attributes for the form field.
+ // - style: Allows the user to set the glyph style of a check box or radio button.
+ // - textColor, textFont, textSize
+ return $opt;
+ }
+
+ /**
+ * Format the page numbers.
+ * This method can be overriden for custom formats.
+ * @param $num (int) page number
+ * @since 4.2.005 (2008-11-06)
+ * @public static
+ */
+ public static function formatPageNumber($num) {
+ return number_format((float)$num, 0, '', '.');
+ }
+
+ /**
+ * Format the page numbers on the Table Of Content.
+ * This method can be overriden for custom formats.
+ * @param $num (int) page number
+ * @since 4.5.001 (2009-01-04)
+ * @see addTOC(), addHTMLTOC()
+ * @public static
+ */
+ public static function formatTOCPageNumber($num) {
+ return number_format((float)$num, 0, '', '.');
+ }
+
+ /**
+ * Extracts the CSS properties from a CSS string.
+ * @param $cssdata (string) string containing CSS definitions.
+ * @return An array where the keys are the CSS selectors and the values are the CSS properties.
+ * @author Nicola Asuni
+ * @since 5.1.000 (2010-05-25)
+ * @public static
+ */
+ public static function extractCSSproperties($cssdata) {
+ if (empty($cssdata)) {
+ return array();
+ }
+ // remove comments
+ $cssdata = preg_replace('/\/\*[^\*]*\*\//', '', $cssdata);
+ // remove newlines and multiple spaces
+ $cssdata = preg_replace('/[\s]+/', ' ', $cssdata);
+ // remove some spaces
+ $cssdata = preg_replace('/[\s]*([;:\{\}]{1})[\s]*/', '\\1', $cssdata);
+ // remove empty blocks
+ $cssdata = preg_replace('/([^\}\{]+)\{\}/', '', $cssdata);
+ // replace media type parenthesis
+ $cssdata = preg_replace('/@media[\s]+([^\{]*)\{/i', '@media \\1§', $cssdata);
+ $cssdata = preg_replace('/\}\}/si', '}§', $cssdata);
+ // trim string
+ $cssdata = trim($cssdata);
+ // find media blocks (all, braille, embossed, handheld, print, projection, screen, speech, tty, tv)
+ $cssblocks = array();
+ $matches = array();
+ if (preg_match_all('/@media[\s]+([^\§]*)§([^§]*)§/i', $cssdata, $matches) > 0) {
+ foreach ($matches[1] as $key => $type) {
+ $cssblocks[$type] = $matches[2][$key];
+ }
+ // remove media blocks
+ $cssdata = preg_replace('/@media[\s]+([^\§]*)§([^§]*)§/i', '', $cssdata);
+ }
+ // keep 'all' and 'print' media, other media types are discarded
+ if (isset($cssblocks['all']) AND !empty($cssblocks['all'])) {
+ $cssdata .= $cssblocks['all'];
+ }
+ if (isset($cssblocks['print']) AND !empty($cssblocks['print'])) {
+ $cssdata .= $cssblocks['print'];
+ }
+ // reset css blocks array
+ $cssblocks = array();
+ $matches = array();
+ // explode css data string into array
+ if (substr($cssdata, -1) == '}') {
+ // remove last parethesis
+ $cssdata = substr($cssdata, 0, -1);
+ }
+ $matches = explode('}', $cssdata);
+ foreach ($matches as $key => $block) {
+ // index 0 contains the CSS selector, index 1 contains CSS properties
+ $cssblocks[$key] = explode('{', $block);
+ if (!isset($cssblocks[$key][1])) {
+ // remove empty definitions
+ unset($cssblocks[$key]);
+ }
+ }
+ // split groups of selectors (comma-separated list of selectors)
+ foreach ($cssblocks as $key => $block) {
+ if (strpos($block[0], ',') > 0) {
+ $selectors = explode(',', $block[0]);
+ foreach ($selectors as $sel) {
+ $cssblocks[] = array(0 => trim($sel), 1 => $block[1]);
+ }
+ unset($cssblocks[$key]);
+ }
+ }
+ // covert array to selector => properties
+ $cssdata = array();
+ foreach ($cssblocks as $block) {
+ $selector = $block[0];
+ // calculate selector's specificity
+ $matches = array();
+ $a = 0; // the declaration is not from is a 'style' attribute
+ $b = intval(preg_match_all('/[\#]/', $selector, $matches)); // number of ID attributes
+ $c = intval(preg_match_all('/[\[\.]/', $selector, $matches)); // number of other attributes
+ $c += intval(preg_match_all('/[\:]link|visited|hover|active|focus|target|lang|enabled|disabled|checked|indeterminate|root|nth|first|last|only|empty|contains|not/i', $selector, $matches)); // number of pseudo-classes
+ $d = intval(preg_match_all('/[\>\+\~\s]{1}[a-zA-Z0-9]+/', ' '.$selector, $matches)); // number of element names
+ $d += intval(preg_match_all('/[\:][\:]/', $selector, $matches)); // number of pseudo-elements
+ $specificity = $a.$b.$c.$d;
+ // add specificity to the beginning of the selector
+ $cssdata[$specificity.' '.$selector] = $block[1];
+ }
+ // sort selectors alphabetically to account for specificity
+ ksort($cssdata, SORT_STRING);
+ // return array
+ return $cssdata;
+ }
+
+ /**
+ * Cleanup HTML code (requires HTML Tidy library).
+ * @param $html (string) htmlcode to fix
+ * @param $default_css (string) CSS commands to add
+ * @param $tagvs (array) parameters for setHtmlVSpace method
+ * @param $tidy_options (array) options for tidy_parse_string function
+ * @param $tagvspaces (array) Array of vertical spaces for tags.
+ * @return string XHTML code cleaned up
+ * @author Nicola Asuni
+ * @since 5.9.017 (2010-11-16)
+ * @see setHtmlVSpace()
+ * @public static
+ */
+ public static function fixHTMLCode($html, $default_css='', $tagvs='', $tidy_options='', &$tagvspaces) {
+ // configure parameters for HTML Tidy
+ if ($tidy_options === '') {
+ $tidy_options = array (
+ 'clean' => 1,
+ 'drop-empty-paras' => 0,
+ 'drop-proprietary-attributes' => 1,
+ 'fix-backslash' => 1,
+ 'hide-comments' => 1,
+ 'join-styles' => 1,
+ 'lower-literals' => 1,
+ 'merge-divs' => 1,
+ 'merge-spans' => 1,
+ 'output-xhtml' => 1,
+ 'word-2000' => 1,
+ 'wrap' => 0,
+ 'output-bom' => 0,
+ //'char-encoding' => 'utf8',
+ //'input-encoding' => 'utf8',
+ //'output-encoding' => 'utf8'
+ );
+ }
+ // clean up the HTML code
+ $tidy = tidy_parse_string($html, $tidy_options);
+ // fix the HTML
+ $tidy->cleanRepair();
+ // get the CSS part
+ $tidy_head = tidy_get_head($tidy);
+ $css = $tidy_head->value;
+ $css = preg_replace('/<style([^>]+)>/ims', '<style>', $css);
+ $css = preg_replace('/<\/style>(.*)<style>/ims', "\n", $css);
+ $css = str_replace('/*<![CDATA[*/', '', $css);
+ $css = str_replace('/*]]>*/', '', $css);
+ preg_match('/<style>(.*)<\/style>/ims', $css, $matches);
+ if (isset($matches[1])) {
+ $css = strtolower($matches[1]);
+ } else {
+ $css = '';
+ }
+ // include default css
+ $css = '<style>'.$default_css.$css.'</style>';
+ // get the body part
+ $tidy_body = tidy_get_body($tidy);
+ $html = $tidy_body->value;
+ // fix some self-closing tags
+ $html = str_replace('<br>', '<br />', $html);
+ // remove some empty tag blocks
+ $html = preg_replace('/<div([^\>]*)><\/div>/', '', $html);
+ $html = preg_replace('/<p([^\>]*)><\/p>/', '', $html);
+ if ($tagvs !== '') {
+ // set vertical space for some XHTML tags
+ $tagvspaces = $tagvs;
+ }
+ // return the cleaned XHTML code + CSS
+ return $css.$html;
+ }
+
+ /**
+ * Returns true if the CSS selector is valid for the selected HTML tag
+ * @param $dom (array) array of HTML tags and properties
+ * @param $key (int) key of the current HTML tag
+ * @param $selector (string) CSS selector string
+ * @return true if the selector is valid, false otherwise
+ * @since 5.1.000 (2010-05-25)
+ * @public static
+ */
+ public static function isValidCSSSelectorForTag($dom, $key, $selector) {
+ $valid = false; // value to be returned
+ $tag = $dom[$key]['value'];
+ $class = array();
+ if (isset($dom[$key]['attribute']['class']) AND !empty($dom[$key]['attribute']['class'])) {
+ $class = explode(' ', strtolower($dom[$key]['attribute']['class']));
+ }
+ $id = '';
+ if (isset($dom[$key]['attribute']['id']) AND !empty($dom[$key]['attribute']['id'])) {
+ $id = strtolower($dom[$key]['attribute']['id']);
+ }
+ $selector = preg_replace('/([\>\+\~\s]{1})([\.]{1})([^\>\+\~\s]*)/si', '\\1*.\\3', $selector);
+ $matches = array();
+ if (preg_match_all('/([\>\+\~\s]{1})([a-zA-Z0-9\*]+)([^\>\+\~\s]*)/si', $selector, $matches, PREG_PATTERN_ORDER | PREG_OFFSET_CAPTURE) > 0) {
+ $parentop = array_pop($matches[1]);
+ $operator = $parentop[0];
+ $offset = $parentop[1];
+ $lasttag = array_pop($matches[2]);
+ $lasttag = strtolower(trim($lasttag[0]));
+ if (($lasttag == '*') OR ($lasttag == $tag)) {
+ // the last element on selector is our tag or 'any tag'
+ $attrib = array_pop($matches[3]);
+ $attrib = strtolower(trim($attrib[0]));
+ if (!empty($attrib)) {
+ // check if matches class, id, attribute, pseudo-class or pseudo-element
+ switch ($attrib{0}) {
+ case '.': { // class
+ if (in_array(substr($attrib, 1), $class)) {
+ $valid = true;
+ }
+ break;
+ }
+ case '#': { // ID
+ if (substr($attrib, 1) == $id) {
+ $valid = true;
+ }
+ break;
+ }
+ case '[': { // attribute
+ $attrmatch = array();
+ if (preg_match('/\[([a-zA-Z0-9]*)[\s]*([\~\^\$\*\|\=]*)[\s]*["]?([^"\]]*)["]?\]/i', $attrib, $attrmatch) > 0) {
+ $att = strtolower($attrmatch[1]);
+ $val = $attrmatch[3];
+ if (isset($dom[$key]['attribute'][$att])) {
+ switch ($attrmatch[2]) {
+ case '=': {
+ if ($dom[$key]['attribute'][$att] == $val) {
+ $valid = true;
+ }
+ break;
+ }
+ case '~=': {
+ if (in_array($val, explode(' ', $dom[$key]['attribute'][$att]))) {
+ $valid = true;
+ }
+ break;
+ }
+ case '^=': {
+ if ($val == substr($dom[$key]['attribute'][$att], 0, strlen($val))) {
+ $valid = true;
+ }
+ break;
+ }
+ case '$=': {
+ if ($val == substr($dom[$key]['attribute'][$att], -strlen($val))) {
+ $valid = true;
+ }
+ break;
+ }
+ case '*=': {
+ if (strpos($dom[$key]['attribute'][$att], $val) !== false) {
+ $valid = true;
+ }
+ break;
+ }
+ case '|=': {
+ if ($dom[$key]['attribute'][$att] == $val) {
+ $valid = true;
+ } elseif (preg_match('/'.$val.'[\-]{1}/i', $dom[$key]['attribute'][$att]) > 0) {
+ $valid = true;
+ }
+ break;
+ }
+ default: {
+ $valid = true;
+ }
+ }
+ }
+ }
+ break;
+ }
+ case ':': { // pseudo-class or pseudo-element
+ if ($attrib{1} == ':') { // pseudo-element
+ // pseudo-elements are not supported!
+ // (::first-line, ::first-letter, ::before, ::after)
+ } else { // pseudo-class
+ // pseudo-classes are not supported!
+ // (:root, :nth-child(n), :nth-last-child(n), :nth-of-type(n), :nth-last-of-type(n), :first-child, :last-child, :first-of-type, :last-of-type, :only-child, :only-of-type, :empty, :link, :visited, :active, :hover, :focus, :target, :lang(fr), :enabled, :disabled, :checked)
+ }
+ break;
+ }
+ } // end of switch
+ } else {
+ $valid = true;
+ }
+ if ($valid AND ($offset > 0)) {
+ $valid = false;
+ // check remaining selector part
+ $selector = substr($selector, 0, $offset);
+ switch ($operator) {
+ case ' ': { // descendant of an element
+ while ($dom[$key]['parent'] > 0) {
+ if (self::isValidCSSSelectorForTag($dom, $dom[$key]['parent'], $selector)) {
+ $valid = true;
+ break;
+ } else {
+ $key = $dom[$key]['parent'];
+ }
+ }
+ break;
+ }
+ case '>': { // child of an element
+ $valid = self::isValidCSSSelectorForTag($dom, $dom[$key]['parent'], $selector);
+ break;
+ }
+ case '+': { // immediately preceded by an element
+ for ($i = ($key - 1); $i > $dom[$key]['parent']; --$i) {
+ if ($dom[$i]['tag'] AND $dom[$i]['opening']) {
+ $valid = self::isValidCSSSelectorForTag($dom, $i, $selector);
+ break;
+ }
+ }
+ break;
+ }
+ case '~': { // preceded by an element
+ for ($i = ($key - 1); $i > $dom[$key]['parent']; --$i) {
+ if ($dom[$i]['tag'] AND $dom[$i]['opening']) {
+ if (self::isValidCSSSelectorForTag($dom, $i, $selector)) {
+ break;
+ }
+ }
+ }
+ break;
+ }
+ }
+ }
+ }
+ }
+ return $valid;
+ }
+
+ /**
+ * Returns the styles array that apply for the selected HTML tag.
+ * @param $dom (array) array of HTML tags and properties
+ * @param $key (int) key of the current HTML tag
+ * @param $css (array) array of CSS properties
+ * @return array containing CSS properties
+ * @since 5.1.000 (2010-05-25)
+ * @public static
+ */
+ public static function getCSSdataArray($dom, $key, $css) {
+ $cssarray = array(); // style to be returned
+ // get parent CSS selectors
+ $selectors = array();
+ if (isset($dom[($dom[$key]['parent'])]['csssel'])) {
+ $selectors = $dom[($dom[$key]['parent'])]['csssel'];
+ }
+ // get all styles that apply
+ foreach($css as $selector => $style) {
+ $pos = strpos($selector, ' ');
+ // get specificity
+ $specificity = substr($selector, 0, $pos);
+ // remove specificity
+ $selector = substr($selector, $pos);
+ // check if this selector apply to current tag
+ if (self::isValidCSSSelectorForTag($dom, $key, $selector)) {
+ if (!in_array($selector, $selectors)) {
+ // add style if not already added on parent selector
+ $cssarray[] = array('k' => $selector, 's' => $specificity, 'c' => $style);
+ $selectors[] = $selector;
+ }
+ }
+ }
+ if (isset($dom[$key]['attribute']['style'])) {
+ // attach inline style (latest properties have high priority)
+ $cssarray[] = array('k' => '', 's' => '1000', 'c' => $dom[$key]['attribute']['style']);
+ }
+ // order the css array to account for specificity
+ $cssordered = array();
+ foreach ($cssarray as $key => $val) {
+ $skey = sprintf('%04d', $key);
+ $cssordered[$val['s'].'_'.$skey] = $val;
+ }
+ // sort selectors alphabetically to account for specificity
+ ksort($cssordered, SORT_STRING);
+ return array($selectors, $cssordered);
+ }
+
+ /**
+ * Compact CSS data array into single string.
+ * @param $css (array) array of CSS properties
+ * @return string containing merged CSS properties
+ * @since 5.9.070 (2011-04-19)
+ * @public static
+ */
+ public static function getTagStyleFromCSSarray($css) {
+ $tagstyle = ''; // value to be returned
+ foreach ($css as $style) {
+ // split single css commands
+ $csscmds = explode(';', $style['c']);
+ foreach ($csscmds as $cmd) {
+ if (!empty($cmd)) {
+ $pos = strpos($cmd, ':');
+ if ($pos !== false) {
+ $cmd = substr($cmd, 0, ($pos + 1));
+ if (strpos($tagstyle, $cmd) !== false) {
+ // remove duplicate commands (last commands have high priority)
+ $tagstyle = preg_replace('/'.$cmd.'[^;]+/i', '', $tagstyle);
+ }
+ }
+ }
+ }
+ $tagstyle .= ';'.$style['c'];
+ }
+ // remove multiple semicolons
+ $tagstyle = preg_replace('/[;]+/', ';', $tagstyle);
+ return $tagstyle;
+ }
+
+ /**
+ * Returns the Roman representation of an integer number
+ * @param $number (int) number to convert
+ * @return string roman representation of the specified number
+ * @since 4.4.004 (2008-12-10)
+ * @public static
+ */
+ public static function intToRoman($number) {
+ $roman = '';
+ while ($number >= 1000) {
+ $roman .= 'M';
+ $number -= 1000;
+ }
+ while ($number >= 900) {
+ $roman .= 'CM';
+ $number -= 900;
+ }
+ while ($number >= 500) {
+ $roman .= 'D';
+ $number -= 500;
+ }
+ while ($number >= 400) {
+ $roman .= 'CD';
+ $number -= 400;
+ }
+ while ($number >= 100) {
+ $roman .= 'C';
+ $number -= 100;
+ }
+ while ($number >= 90) {
+ $roman .= 'XC';
+ $number -= 90;
+ }
+ while ($number >= 50) {
+ $roman .= 'L';
+ $number -= 50;
+ }
+ while ($number >= 40) {
+ $roman .= 'XL';
+ $number -= 40;
+ }
+ while ($number >= 10) {
+ $roman .= 'X';
+ $number -= 10;
+ }
+ while ($number >= 9) {
+ $roman .= 'IX';
+ $number -= 9;
+ }
+ while ($number >= 5) {
+ $roman .= 'V';
+ $number -= 5;
+ }
+ while ($number >= 4) {
+ $roman .= 'IV';
+ $number -= 4;
+ }
+ while ($number >= 1) {
+ $roman .= 'I';
+ --$number;
+ }
+ return $roman;
+ }
+
+ /**
+ * Find position of last occurrence of a substring in a string
+ * @param $haystack (string) The string to search in.
+ * @param $needle (string) substring to search.
+ * @param $offset (int) May be specified to begin searching an arbitrary number of characters into the string.
+ * @return Returns the position where the needle exists. Returns FALSE if the needle was not found.
+ * @since 4.8.038 (2010-03-13)
+ * @public static
+ */
+ public static function revstrpos($haystack, $needle, $offset = 0) {
+ $length = strlen($haystack);
+ $offset = ($offset > 0)?($length - $offset):abs($offset);
+ $pos = strpos(strrev($haystack), strrev($needle), $offset);
+ return ($pos === false)?false:($length - $pos - strlen($needle));
+ }
+
+ /**
+ * Serialize an array of parameters to be used with TCPDF tag in HTML code.
+ * @param $pararray (array) parameters array
+ * @return sting containing serialized data
+ * @since 4.9.006 (2010-04-02)
+ * @public static
+ */
+ public static function serializeTCPDFtagParameters($pararray) {
+ return urlencode(serialize($pararray));
+ }
+
+ /**
+ * Returns an array of hyphenation patterns.
+ * @param $file (string) TEX file containing hypenation patterns. TEX pattrns can be downloaded from http://www.ctan.org/tex-archive/language/hyph-utf8/tex/generic/hyph-utf8/patterns/
+ * @return array of hyphenation patterns
+ * @author Nicola Asuni
+ * @since 4.9.012 (2010-04-12)
+ * @public static
+ */
+ public static function getHyphenPatternsFromTEX($file) {
+ // TEX patterns are available at:
+ // http://www.ctan.org/tex-archive/language/hyph-utf8/tex/generic/hyph-utf8/patterns/
+ $data = file_get_contents($file);
+ $patterns = array();
+ // remove comments
+ $data = preg_replace('/\%[^\n]*/', '', $data);
+ // extract the patterns part
+ preg_match('/\\\\patterns\{([^\}]*)\}/i', $data, $matches);
+ $data = trim(substr($matches[0], 10, -1));
+ // extract each pattern
+ $patterns_array = preg_split('/[\s]+/', $data);
+ // create new language array of patterns
+ $patterns = array();
+ foreach($patterns_array as $val) {
+ if (!TCPDF_STATIC::empty_string($val)) {
+ $val = trim($val);
+ $val = str_replace('\'', '\\\'', $val);
+ $key = preg_replace('/[0-9]+/', '', $val);
+ $patterns[$key] = $val;
+ }
+ }
+ return $patterns;
+ }
+
+ /**
+ * Get the Path-Painting Operators.
+ * @param $style (string) Style of rendering. Possible values are:
+ * <ul>
+ * <li>S or D: Stroke the path.</li>
+ * <li>s or d: Close and stroke the path.</li>
+ * <li>f or F: Fill the path, using the nonzero winding number rule to determine the region to fill.</li>
+ * <li>f* or F*: Fill the path, using the even-odd rule to determine the region to fill.</li>
+ * <li>B or FD or DF: Fill and then stroke the path, using the nonzero winding number rule to determine the region to fill.</li>
+ * <li>B* or F*D or DF*: Fill and then stroke the path, using the even-odd rule to determine the region to fill.</li>
+ * <li>b or fd or df: Close, fill, and then stroke the path, using the nonzero winding number rule to determine the region to fill.</li>
+ * <li>b or f*d or df*: Close, fill, and then stroke the path, using the even-odd rule to determine the region to fill.</li>
+ * <li>CNZ: Clipping mode using the even-odd rule to determine which regions lie inside the clipping path.</li>
+ * <li>CEO: Clipping mode using the nonzero winding number rule to determine which regions lie inside the clipping path</li>
+ * <li>n: End the path object without filling or stroking it.</li>
+ * </ul>
+ * @param $default (string) default style
+ * @author Nicola Asuni
+ * @since 5.0.000 (2010-04-30)
+ * @public static
+ */
+ public static function getPathPaintOperator($style, $default='S') {
+ $op = '';
+ switch($style) {
+ case 'S':
+ case 'D': {
+ $op = 'S';
+ break;
+ }
+ case 's':
+ case 'd': {
+ $op = 's';
+ break;
+ }
+ case 'f':
+ case 'F': {
+ $op = 'f';
+ break;
+ }
+ case 'f*':
+ case 'F*': {
+ $op = 'f*';
+ break;
+ }
+ case 'B':
+ case 'FD':
+ case 'DF': {
+ $op = 'B';
+ break;
+ }
+ case 'B*':
+ case 'F*D':
+ case 'DF*': {
+ $op = 'B*';
+ break;
+ }
+ case 'b':
+ case 'fd':
+ case 'df': {
+ $op = 'b';
+ break;
+ }
+ case 'b*':
+ case 'f*d':
+ case 'df*': {
+ $op = 'b*';
+ break;
+ }
+ case 'CNZ': {
+ $op = 'W n';
+ break;
+ }
+ case 'CEO': {
+ $op = 'W* n';
+ break;
+ }
+ case 'n': {
+ $op = 'n';
+ break;
+ }
+ default: {
+ if (!empty($default)) {
+ $op = self::getPathPaintOperator($default, '');
+ } else {
+ $op = '';
+ }
+ }
+ }
+ return $op;
+ }
+
+ /**
+ * Get the product of two SVG tranformation matrices
+ * @param $ta (array) first SVG tranformation matrix
+ * @param $tb (array) second SVG tranformation matrix
+ * @return transformation array
+ * @author Nicola Asuni
+ * @since 5.0.000 (2010-05-02)
+ * @public static
+ */
+ public static function getTransformationMatrixProduct($ta, $tb) {
+ $tm = array();
+ $tm[0] = ($ta[0] * $tb[0]) + ($ta[2] * $tb[1]);
+ $tm[1] = ($ta[1] * $tb[0]) + ($ta[3] * $tb[1]);
+ $tm[2] = ($ta[0] * $tb[2]) + ($ta[2] * $tb[3]);
+ $tm[3] = ($ta[1] * $tb[2]) + ($ta[3] * $tb[3]);
+ $tm[4] = ($ta[0] * $tb[4]) + ($ta[2] * $tb[5]) + $ta[4];
+ $tm[5] = ($ta[1] * $tb[4]) + ($ta[3] * $tb[5]) + $ta[5];
+ return $tm;
+ }
+
+ /**
+ * Get the tranformation matrix from SVG transform attribute
+ * @param $attribute (string) transformation
+ * @return array of transformations
+ * @author Nicola Asuni
+ * @since 5.0.000 (2010-05-02)
+ * @public static
+ */
+ public static function getSVGTransformMatrix($attribute) {
+ // identity matrix
+ $tm = array(1, 0, 0, 1, 0, 0);
+ $transform = array();
+ if (preg_match_all('/(matrix|translate|scale|rotate|skewX|skewY)[\s]*\(([^\)]+)\)/si', $attribute, $transform, PREG_SET_ORDER) > 0) {
+ foreach ($transform as $key => $data) {
+ if (!empty($data[2])) {
+ $a = 1;
+ $b = 0;
+ $c = 0;
+ $d = 1;
+ $e = 0;
+ $f = 0;
+ $regs = array();
+ switch ($data[1]) {
+ case 'matrix': {
+ if (preg_match('/([a-z0-9\-\.]+)[\,\s]+([a-z0-9\-\.]+)[\,\s]+([a-z0-9\-\.]+)[\,\s]+([a-z0-9\-\.]+)[\,\s]+([a-z0-9\-\.]+)[\,\s]+([a-z0-9\-\.]+)/si', $data[2], $regs)) {
+ $a = $regs[1];
+ $b = $regs[2];
+ $c = $regs[3];
+ $d = $regs[4];
+ $e = $regs[5];
+ $f = $regs[6];
+ }
+ break;
+ }
+ case 'translate': {
+ if (preg_match('/([a-z0-9\-\.]+)[\,\s]+([a-z0-9\-\.]+)/si', $data[2], $regs)) {
+ $e = $regs[1];
+ $f = $regs[2];
+ } elseif (preg_match('/([a-z0-9\-\.]+)/si', $data[2], $regs)) {
+ $e = $regs[1];
+ }
+ break;
+ }
+ case 'scale': {
+ if (preg_match('/([a-z0-9\-\.]+)[\,\s]+([a-z0-9\-\.]+)/si', $data[2], $regs)) {
+ $a = $regs[1];
+ $d = $regs[2];
+ } elseif (preg_match('/([a-z0-9\-\.]+)/si', $data[2], $regs)) {
+ $a = $regs[1];
+ $d = $a;
+ }
+ break;
+ }
+ case 'rotate': {
+ if (preg_match('/([0-9\-\.]+)[\,\s]+([a-z0-9\-\.]+)[\,\s]+([a-z0-9\-\.]+)/si', $data[2], $regs)) {
+ $ang = deg2rad($regs[1]);
+ $x = $regs[2];
+ $y = $regs[3];
+ $a = cos($ang);
+ $b = sin($ang);
+ $c = -$b;
+ $d = $a;
+ $e = ($x * (1 - $a)) - ($y * $c);
+ $f = ($y * (1 - $d)) - ($x * $b);
+ } elseif (preg_match('/([0-9\-\.]+)/si', $data[2], $regs)) {
+ $ang = deg2rad($regs[1]);
+ $a = cos($ang);
+ $b = sin($ang);
+ $c = -$b;
+ $d = $a;
+ $e = 0;
+ $f = 0;
+ }
+ break;
+ }
+ case 'skewX': {
+ if (preg_match('/([0-9\-\.]+)/si', $data[2], $regs)) {
+ $c = tan(deg2rad($regs[1]));
+ }
+ break;
+ }
+ case 'skewY': {
+ if (preg_match('/([0-9\-\.]+)/si', $data[2], $regs)) {
+ $b = tan(deg2rad($regs[1]));
+ }
+ break;
+ }
+ }
+ $tm = self::getTransformationMatrixProduct($tm, array($a, $b, $c, $d, $e, $f));
+ }
+ }
+ }
+ return $tm;
+ }
+
+ /**
+ * Returns the angle in radiants between two vectors
+ * @param $x1 (int) X coordinate of first vector point
+ * @param $y1 (int) Y coordinate of first vector point
+ * @param $x2 (int) X coordinate of second vector point
+ * @param $y2 (int) Y coordinate of second vector point
+ * @author Nicola Asuni
+ * @since 5.0.000 (2010-05-04)
+ * @public static
+ */
+ public static function getVectorsAngle($x1, $y1, $x2, $y2) {
+ $dprod = ($x1 * $x2) + ($y1 * $y2);
+ $dist1 = sqrt(($x1 * $x1) + ($y1 * $y1));
+ $dist2 = sqrt(($x2 * $x2) + ($y2 * $y2));
+ $angle = acos($dprod / ($dist1 * $dist2));
+ if (is_nan($angle)) {
+ $angle = M_PI;
+ }
+ if ((($x1 * $y2) - ($x2 * $y1)) < 0) {
+ $angle *= -1;
+ }
+ return $angle;
+ }
+
+ /**
+ * Split string by a regular expression.
+ * This is a wrapper for the preg_split function to avoid the bug: https://bugs.php.net/bug.php?id=45850
+ * @param $pattern (string) The regular expression pattern to search for without the modifiers, as a string.
+ * @param $modifiers (string) The modifiers part of the pattern,
+ * @param $subject (string) The input string.
+ * @param $limit (int) If specified, then only substrings up to limit are returned with the rest of the string being placed in the last substring. A limit of -1, 0 or NULL means "no limit" and, as is standard across PHP, you can use NULL to skip to the flags parameter.
+ * @param $flags (int) The flags as specified on the preg_split PHP function.
+ * @return Returns an array containing substrings of subject split along boundaries matched by pattern.modifier
+ * @author Nicola Asuni
+ * @since 6.0.023
+ * @public static
+ */
+ public static function pregSplit($pattern, $modifiers, $subject, $limit=NULL, $flags=NULL) {
+ // the bug only happens on PHP 5.2 when using the u modifier
+ if ((strpos($modifiers, 'u') === FALSE) OR (count(preg_split('//u', "\n\t", -1, PREG_SPLIT_NO_EMPTY)) == 2)) {
+ return preg_split($pattern.$modifiers, $subject, $limit, $flags);
+ }
+ // preg_split is bugged - try alternative solution
+ $ret = array();
+ while (($nl = strpos($subject, "\n")) !== FALSE) {
+ $ret = array_merge($ret, preg_split($pattern.$modifiers, substr($subject, 0, $nl), $limit, $flags));
+ $ret[] = "\n";
+ $subject = substr($subject, ($nl + 1));
+ }
+ if (strlen($subject) > 0) {
+ $ret = array_merge($ret, preg_split($pattern.$modifiers, $subject, $limit, $flags));
+ }
+ return $ret;
+ }
+
+ /**
+ * Reads entire file into a string.
+ * The file can be also an URL.
+ * @param $file (string) Name of the file or URL to read.
+ * @return The function returns the read data or FALSE on failure.
+ * @author Nicola Asuni
+ * @since 6.0.025
+ * @public static
+ */
+ public static function fileGetContents($file) {
+ // array of possible alternative paths/URLs
+ $alt = array($file);
+ // replace URL relative path with full real server path
+ if ((strlen($file) > 1)
+ AND ($file[0] == '/')
+ AND ($file[1] != '/')
+ AND !empty($_SERVER['DOCUMENT_ROOT'])
+ AND ($_SERVER['DOCUMENT_ROOT'] != '/')) {
+ $findroot = strpos($file, $_SERVER['DOCUMENT_ROOT']);
+ if (($findroot === false) OR ($findroot > 1)) {
+ if (substr($_SERVER['DOCUMENT_ROOT'], -1) == '/') {
+ $tmp = substr($_SERVER['DOCUMENT_ROOT'], 0, -1).$file;
+ } else {
+ $tmp = $_SERVER['DOCUMENT_ROOT'].$file;
+ }
+ $alt[] = htmlspecialchars_decode(urldecode($tmp));
+ }
+ }
+ // URL mode
+ $url = $file;
+ // check for missing protocol
+ if (preg_match('%^/{2}%', $url)) {
+ if (preg_match('%^([^:]+:)//%i', K_PATH_URL, $match)) {
+ $url = $match[1].str_replace(' ', '%20', $url);
+ $alt[] = $url;
+ }
+ }
+ $urldata = @parse_url($url);
+ if (!isset($urldata['query']) OR (strlen($urldata['query']) <= 0)) {
+ if (strpos($url, K_PATH_URL) === 0) {
+ // convert URL to full server path
+ $tmp = str_replace(K_PATH_URL, K_PATH_MAIN, $url);
+ $tmp = htmlspecialchars_decode(urldecode($tmp));
+ $alt[] = $tmp;
+ }
+ }
+ foreach ($alt as $f) {
+ $ret = @file_get_contents($f);
+ if (($ret === FALSE)
+ AND !ini_get('allow_url_fopen')
+ AND function_exists('curl_init')
+ AND preg_match('%^(https?|ftp)://%', $f)) {
+ // try to get remote file data using cURL
+ $cs = curl_init(); // curl session
+ curl_setopt($cs, CURLOPT_URL, $file);
+ curl_setopt($cs, CURLOPT_BINARYTRANSFER, true);
+ curl_setopt($cs, CURLOPT_FAILONERROR, true);
+ curl_setopt($cs, CURLOPT_RETURNTRANSFER, true);
+ if ((ini_get('open_basedir') == '') AND (!ini_get('safe_mode'))) {
+ curl_setopt($cs, CURLOPT_FOLLOWLOCATION, true);
+ }
+ curl_setopt($cs, CURLOPT_CONNECTTIMEOUT, 5);
+ curl_setopt($cs, CURLOPT_TIMEOUT, 30);
+ curl_setopt($cs, CURLOPT_SSL_VERIFYPEER, false);
+ curl_setopt($cs, CURLOPT_SSL_VERIFYHOST, false);
+ curl_setopt($cs, CURLOPT_USERAGENT, 'TCPDF');
+ $ret = curl_exec($cs);
+ curl_close($cs);
+ }
+ if ($ret !== FALSE) {
+ break;
+ }
+ }
+ return $ret;
+ }
+
+} // END OF TCPDF_STATIC CLASS
+
+//============================================================+
+// END OF FILE
+//============================================================+
diff --git a/libraries/tcpdf/tcpdf.php b/libraries/tcpdf/tcpdf.php
new file mode 100644
index 0000000000..c8fd9846f1
--- /dev/null
+++ b/libraries/tcpdf/tcpdf.php
@@ -0,0 +1,24121 @@
+<?php
+//============================================================+
+// File name : tcpdf.php
+// Version : 6.0.039
+// Begin : 2002-08-03
+// Last Update : 2013-10-13
+// Author : Nicola Asuni - Tecnick.com LTD - www.tecnick.com - info@tecnick.com
+// License : GNU-LGPL v3 (http://www.gnu.org/copyleft/lesser.html)
+// -------------------------------------------------------------------
+// Copyright (C) 2002-2013 Nicola Asuni - Tecnick.com LTD
+//
+// This file is part of TCPDF software library.
+//
+// TCPDF is free software: you can ioredistribute it and/or modify it
+// under the terms of the GNU Lesser General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// TCPDF 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 License
+// along with TCPDF. If not, see
+// <http://www.tecnick.com/pagefiles/tcpdf/LICENSE.TXT>.
+//
+// See LICENSE.TXT file for more information.
+// -------------------------------------------------------------------
+//
+// Description :
+// This is a PHP class for generating PDF documents without requiring external extensions.
+//
+// NOTE:
+// This class was originally derived in 2002 from the Public
+// Domain FPDF class by Olivier Plathey (http://www.fpdf.org),
+// but now is almost entirely rewritten and contains thousands of
+// new lines of code and hundreds new features.
+//
+// Main features:
+// * no external libraries are required for the basic functions;
+// * all standard page formats, custom page formats, custom margins and units of measure;
+// * UTF-8 Unicode and Right-To-Left languages;
+// * TrueTypeUnicode, TrueType, Type1 and CID-0 fonts;
+// * font subsetting;
+// * methods to publish some XHTML + CSS code, Javascript and Forms;
+// * images, graphic (geometric figures) and transformation methods;
+// * supports JPEG, PNG and SVG images natively, all images supported by GD (GD, GD2, GD2PART, GIF, JPEG, PNG, BMP, XBM, XPM) and all images supported via ImagMagick (http://www.imagemagick.org/www/formats.html)
+// * 1D and 2D barcodes: CODE 39, ANSI MH10.8M-1983, USD-3, 3 of 9, CODE 93, USS-93, Standard 2 of 5, Interleaved 2 of 5, CODE 128 A/B/C, 2 and 5 Digits UPC-Based Extention, EAN 8, EAN 13, UPC-A, UPC-E, MSI, POSTNET, PLANET, RMS4CC (Royal Mail 4-state Customer Code), CBC (Customer Bar Code), KIX (Klant index - Customer index), Intelligent Mail Barcode, Onecode, USPS-B-3200, CODABAR, CODE 11, PHARMACODE, PHARMACODE TWO-TRACKS, Datamatrix, QR-Code, PDF417;
+// * JPEG and PNG ICC profiles, Grayscale, RGB, CMYK, Spot Colors and Transparencies;
+// * automatic page header and footer management;
+// * document encryption up to 256 bit and digital signature certifications;
+// * transactions to UNDO commands;
+// * PDF annotations, including links, text and file attachments;
+// * text rendering modes (fill, stroke and clipping);
+// * multiple columns mode;
+// * no-write page regions;
+// * bookmarks, named destinations and table of content;
+// * text hyphenation;
+// * text stretching and spacing (tracking);
+// * automatic page break, line break and text alignments including justification;
+// * automatic page numbering and page groups;
+// * move and delete pages;
+// * page compression (requires php-zlib extension);
+// * XOBject Templates;
+// * Layers and object visibility.
+// * PDF/A-1b support
+//============================================================+
+
+/**
+ * @file
+ * This is a PHP class for generating PDF documents without requiring external extensions.<br>
+ * TCPDF project (http://www.tcpdf.org) was originally derived in 2002 from the Public Domain FPDF class by Olivier Plathey (http://www.fpdf.org), but now is almost entirely rewritten.<br>
+ * <h3>TCPDF main features are:</h3>
+ * <ul>
+ * <li>no external libraries are required for the basic functions;</li>
+ * <li>all standard page formats, custom page formats, custom margins and units of measure;</li>
+ * <li>UTF-8 Unicode and Right-To-Left languages;</li>
+ * <li>TrueTypeUnicode, TrueType, Type1 and CID-0 fonts;</li>
+ * <li>font subsetting;</li>
+ * <li>methods to publish some XHTML + CSS code, Javascript and Forms;</li>
+ * <li>images, graphic (geometric figures) and transformation methods;
+ * <li>supports JPEG, PNG and SVG images natively, all images supported by GD (GD, GD2, GD2PART, GIF, JPEG, PNG, BMP, XBM, XPM) and all images supported via ImagMagick (http://www.imagemagick.org/www/formats.html)</li>
+ * <li>1D and 2D barcodes: CODE 39, ANSI MH10.8M-1983, USD-3, 3 of 9, CODE 93, USS-93, Standard 2 of 5, Interleaved 2 of 5, CODE 128 A/B/C, 2 and 5 Digits UPC-Based Extention, EAN 8, EAN 13, UPC-A, UPC-E, MSI, POSTNET, PLANET, RMS4CC (Royal Mail 4-state Customer Code), CBC (Customer Bar Code), KIX (Klant index - Customer index), Intelligent Mail Barcode, Onecode, USPS-B-3200, CODABAR, CODE 11, PHARMACODE, PHARMACODE TWO-TRACKS, Datamatrix, QR-Code, PDF417;</li>
+ * <li>JPEG and PNG ICC profiles, Grayscale, RGB, CMYK, Spot Colors and Transparencies;</li>
+ * <li>automatic page header and footer management;</li>
+ * <li>document encryption up to 256 bit and digital signature certifications;</li>
+ * <li>transactions to UNDO commands;</li>
+ * <li>PDF annotations, including links, text and file attachments;</li>
+ * <li>text rendering modes (fill, stroke and clipping);</li>
+ * <li>multiple columns mode;</li>
+ * <li>no-write page regions;</li>
+ * <li>bookmarks, named destinations and table of content;</li>
+ * <li>text hyphenation;</li>
+ * <li>text stretching and spacing (tracking);</li>
+ * <li>automatic page break, line break and text alignments including justification;</li>
+ * <li>automatic page numbering and page groups;</li>
+ * <li>move and delete pages;</li>
+ * <li>page compression (requires php-zlib extension);</li>
+ * <li>XOBject Templates;</li>
+ * <li>Layers and object visibility;</li>
+ * <li>PDF/A-1b support.</li>
+ * </ul>
+ * Tools to encode your unicode fonts are on fonts/utils directory.</p>
+ * @package com.tecnick.tcpdf
+ * @author Nicola Asuni
+ * @version 6.0.039
+ */
+
+// TCPDF configuration
+require_once(dirname(__FILE__).'/tcpdf_autoconfig.php');
+// TCPDF static font methods and data
+require_once(dirname(__FILE__).'/include/tcpdf_font_data.php');
+// TCPDF static font methods and data
+require_once(dirname(__FILE__).'/include/tcpdf_fonts.php');
+// TCPDF static color methods and data
+require_once(dirname(__FILE__).'/include/tcpdf_colors.php');
+// TCPDF static image methods and data
+require_once(dirname(__FILE__).'/include/tcpdf_images.php');
+// TCPDF static methods and data
+require_once(dirname(__FILE__).'/include/tcpdf_static.php');
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+/**
+ * @class TCPDF
+ * PHP class for generating PDF documents without requiring external extensions.
+ * TCPDF project (http://www.tcpdf.org) has been originally derived in 2002 from the Public Domain FPDF class by Olivier Plathey (http://www.fpdf.org), but now is almost entirely rewritten.<br>
+ * @package com.tecnick.tcpdf
+ * @brief PHP class for generating PDF documents without requiring external extensions.
+ * @version 6.0.039
+ * @author Nicola Asuni - info@tecnick.com
+ */
+class TCPDF {
+
+ // Protected properties
+
+ /**
+ * Current page number.
+ * @protected
+ */
+ protected $page;
+
+ /**
+ * Current object number.
+ * @protected
+ */
+ protected $n;
+
+ /**
+ * Array of object offsets.
+ * @protected
+ */
+ protected $offsets = array();
+
+ /**
+ * Array of object IDs for each page.
+ * @protected
+ */
+ protected $pageobjects = array();
+
+ /**
+ * Buffer holding in-memory PDF.
+ * @protected
+ */
+ protected $buffer;
+
+ /**
+ * Array containing pages.
+ * @protected
+ */
+ protected $pages = array();
+
+ /**
+ * Current document state.
+ * @protected
+ */
+ protected $state;
+
+ /**
+ * Compression flag.
+ * @protected
+ */
+ protected $compress;
+
+ /**
+ * Current page orientation (P = Portrait, L = Landscape).
+ * @protected
+ */
+ protected $CurOrientation;
+
+ /**
+ * Page dimensions.
+ * @protected
+ */
+ protected $pagedim = array();
+
+ /**
+ * Scale factor (number of points in user unit).
+ * @protected
+ */
+ protected $k;
+
+ /**
+ * Width of page format in points.
+ * @protected
+ */
+ protected $fwPt;
+
+ /**
+ * Height of page format in points.
+ * @protected
+ */
+ protected $fhPt;
+
+ /**
+ * Current width of page in points.
+ * @protected
+ */
+ protected $wPt;
+
+ /**
+ * Current height of page in points.
+ * @protected
+ */
+ protected $hPt;
+
+ /**
+ * Current width of page in user unit.
+ * @protected
+ */
+ protected $w;
+
+ /**
+ * Current height of page in user unit.
+ * @protected
+ */
+ protected $h;
+
+ /**
+ * Left margin.
+ * @protected
+ */
+ protected $lMargin;
+
+ /**
+ * Right margin.
+ * @protected
+ */
+ protected $rMargin;
+
+ /**
+ * Cell left margin (used by regions).
+ * @protected
+ */
+ protected $clMargin;
+
+ /**
+ * Cell right margin (used by regions).
+ * @protected
+ */
+ protected $crMargin;
+
+ /**
+ * Top margin.
+ * @protected
+ */
+ protected $tMargin;
+
+ /**
+ * Page break margin.
+ * @protected
+ */
+ protected $bMargin;
+
+ /**
+ * Array of cell internal paddings ('T' => top, 'R' => right, 'B' => bottom, 'L' => left).
+ * @since 5.9.000 (2010-10-03)
+ * @protected
+ */
+ protected $cell_padding = array('T' => 0, 'R' => 0, 'B' => 0, 'L' => 0);
+
+ /**
+ * Array of cell margins ('T' => top, 'R' => right, 'B' => bottom, 'L' => left).
+ * @since 5.9.000 (2010-10-04)
+ * @protected
+ */
+ protected $cell_margin = array('T' => 0, 'R' => 0, 'B' => 0, 'L' => 0);
+
+ /**
+ * Current horizontal position in user unit for cell positioning.
+ * @protected
+ */
+ protected $x;
+
+ /**
+ * Current vertical position in user unit for cell positioning.
+ * @protected
+ */
+ protected $y;
+
+ /**
+ * Height of last cell printed.
+ * @protected
+ */
+ protected $lasth;
+
+ /**
+ * Line width in user unit.
+ * @protected
+ */
+ protected $LineWidth;
+
+ /**
+ * Array of standard font names.
+ * @protected
+ */
+ protected $CoreFonts;
+
+ /**
+ * Array of used fonts.
+ * @protected
+ */
+ protected $fonts = array();
+
+ /**
+ * Array of font files.
+ * @protected
+ */
+ protected $FontFiles = array();
+
+ /**
+ * Array of encoding differences.
+ * @protected
+ */
+ protected $diffs = array();
+
+ /**
+ * Array of used images.
+ * @protected
+ */
+ protected $images = array();
+
+ /**
+ * Array of cached files.
+ * @protected
+ */
+ protected $cached_files = array();
+
+ /**
+ * Array of Annotations in pages.
+ * @protected
+ */
+ protected $PageAnnots = array();
+
+ /**
+ * Array of internal links.
+ * @protected
+ */
+ protected $links = array();
+
+ /**
+ * Current font family.
+ * @protected
+ */
+ protected $FontFamily;
+
+ /**
+ * Current font style.
+ * @protected
+ */
+ protected $FontStyle;
+
+ /**
+ * Current font ascent (distance between font top and baseline).
+ * @protected
+ * @since 2.8.000 (2007-03-29)
+ */
+ protected $FontAscent;
+
+ /**
+ * Current font descent (distance between font bottom and baseline).
+ * @protected
+ * @since 2.8.000 (2007-03-29)
+ */
+ protected $FontDescent;
+
+ /**
+ * Underlining flag.
+ * @protected
+ */
+ protected $underline;
+
+ /**
+ * Overlining flag.
+ * @protected
+ */
+ protected $overline;
+
+ /**
+ * Current font info.
+ * @protected
+ */
+ protected $CurrentFont;
+
+ /**
+ * Current font size in points.
+ * @protected
+ */
+ protected $FontSizePt;
+
+ /**
+ * Current font size in user unit.
+ * @protected
+ */
+ protected $FontSize;
+
+ /**
+ * Commands for drawing color.
+ * @protected
+ */
+ protected $DrawColor;
+
+ /**
+ * Commands for filling color.
+ * @protected
+ */
+ protected $FillColor;
+
+ /**
+ * Commands for text color.
+ * @protected
+ */
+ protected $TextColor;
+
+ /**
+ * Indicates whether fill and text colors are different.
+ * @protected
+ */
+ protected $ColorFlag;
+
+ /**
+ * Automatic page breaking.
+ * @protected
+ */
+ protected $AutoPageBreak;
+
+ /**
+ * Threshold used to trigger page breaks.
+ * @protected
+ */
+ protected $PageBreakTrigger;
+
+ /**
+ * Flag set when processing page header.
+ * @protected
+ */
+ protected $InHeader = false;
+
+ /**
+ * Flag set when processing page footer.
+ * @protected
+ */
+ protected $InFooter = false;
+
+ /**
+ * Zoom display mode.
+ * @protected
+ */
+ protected $ZoomMode;
+
+ /**
+ * Layout display mode.
+ * @protected
+ */
+ protected $LayoutMode;
+
+ /**
+ * If true set the document information dictionary in Unicode.
+ * @protected
+ */
+ protected $docinfounicode = true;
+
+ /**
+ * Document title.
+ * @protected
+ */
+ protected $title = '';
+
+ /**
+ * Document subject.
+ * @protected
+ */
+ protected $subject = '';
+
+ /**
+ * Document author.
+ * @protected
+ */
+ protected $author = '';
+
+ /**
+ * Document keywords.
+ * @protected
+ */
+ protected $keywords = '';
+
+ /**
+ * Document creator.
+ * @protected
+ */
+ protected $creator = '';
+
+ /**
+ * Starting page number.
+ * @protected
+ */
+ protected $starting_page_number = 1;
+
+ /**
+ * The right-bottom (or left-bottom for RTL) corner X coordinate of last inserted image.
+ * @since 2002-07-31
+ * @author Nicola Asuni
+ * @protected
+ */
+ protected $img_rb_x;
+
+ /**
+ * The right-bottom corner Y coordinate of last inserted image.
+ * @since 2002-07-31
+ * @author Nicola Asuni
+ * @protected
+ */
+ protected $img_rb_y;
+
+ /**
+ * Adjusting factor to convert pixels to user units.
+ * @since 2004-06-14
+ * @author Nicola Asuni
+ * @protected
+ */
+ protected $imgscale = 1;
+
+ /**
+ * Boolean flag set to true when the input text is unicode (require unicode fonts).
+ * @since 2005-01-02
+ * @author Nicola Asuni
+ * @protected
+ */
+ protected $isunicode = false;
+
+ /**
+ * PDF version.
+ * @since 1.5.3
+ * @protected
+ */
+ protected $PDFVersion = '1.7';
+
+ /**
+ * ID of the stored default header template (-1 = not set).
+ * @protected
+ */
+ protected $header_xobjid = -1;
+
+ /**
+ * If true reset the Header Xobject template at each page
+ * @protected
+ */
+ protected $header_xobj_autoreset = false;
+
+ /**
+ * Minimum distance between header and top page margin.
+ * @protected
+ */
+ protected $header_margin;
+
+ /**
+ * Minimum distance between footer and bottom page margin.
+ * @protected
+ */
+ protected $footer_margin;
+
+ /**
+ * Original left margin value.
+ * @protected
+ * @since 1.53.0.TC013
+ */
+ protected $original_lMargin;
+
+ /**
+ * Original right margin value.
+ * @protected
+ * @since 1.53.0.TC013
+ */
+ protected $original_rMargin;
+
+ /**
+ * Default font used on page header.
+ * @protected
+ */
+ protected $header_font;
+
+ /**
+ * Default font used on page footer.
+ * @protected
+ */
+ protected $footer_font;
+
+ /**
+ * Language templates.
+ * @protected
+ */
+ protected $l;
+
+ /**
+ * Barcode to print on page footer (only if set).
+ * @protected
+ */
+ protected $barcode = false;
+
+ /**
+ * Boolean flag to print/hide page header.
+ * @protected
+ */
+ protected $print_header = true;
+
+ /**
+ * Boolean flag to print/hide page footer.
+ * @protected
+ */
+ protected $print_footer = true;
+
+ /**
+ * Header image logo.
+ * @protected
+ */
+ protected $header_logo = '';
+
+ /**
+ * Width of header image logo in user units.
+ * @protected
+ */
+ protected $header_logo_width = 30;
+
+ /**
+ * Title to be printed on default page header.
+ * @protected
+ */
+ protected $header_title = '';
+
+ /**
+ * String to pring on page header after title.
+ * @protected
+ */
+ protected $header_string = '';
+
+ /**
+ * Color for header text (RGB array).
+ * @since 5.9.174 (2012-07-25)
+ * @protected
+ */
+ protected $header_text_color = array(0,0,0);
+
+ /**
+ * Color for header line (RGB array).
+ * @since 5.9.174 (2012-07-25)
+ * @protected
+ */
+ protected $header_line_color = array(0,0,0);
+
+ /**
+ * Color for footer text (RGB array).
+ * @since 5.9.174 (2012-07-25)
+ * @protected
+ */
+ protected $footer_text_color = array(0,0,0);
+
+ /**
+ * Color for footer line (RGB array).
+ * @since 5.9.174 (2012-07-25)
+ * @protected
+ */
+ protected $footer_line_color = array(0,0,0);
+
+ /**
+ * Text shadow data array.
+ * @since 5.9.174 (2012-07-25)
+ * @protected
+ */
+ protected $txtshadow = array('enabled'=>false, 'depth_w'=>0, 'depth_h'=>0, 'color'=>false, 'opacity'=>1, 'blend_mode'=>'Normal');
+
+ /**
+ * Default number of columns for html table.
+ * @protected
+ */
+ protected $default_table_columns = 4;
+
+ // variables for html parser
+
+ /**
+ * HTML PARSER: array to store current link and rendering styles.
+ * @protected
+ */
+ protected $HREF = array();
+
+ /**
+ * List of available fonts on filesystem.
+ * @protected
+ */
+ protected $fontlist = array();
+
+ /**
+ * Current foreground color.
+ * @protected
+ */
+ protected $fgcolor;
+
+ /**
+ * HTML PARSER: array of boolean values, true in case of ordered list (OL), false otherwise.
+ * @protected
+ */
+ protected $listordered = array();
+
+ /**
+ * HTML PARSER: array count list items on nested lists.
+ * @protected
+ */
+ protected $listcount = array();
+
+ /**
+ * HTML PARSER: current list nesting level.
+ * @protected
+ */
+ protected $listnum = 0;
+
+ /**
+ * HTML PARSER: indent amount for lists.
+ * @protected
+ */
+ protected $listindent = 0;
+
+ /**
+ * HTML PARSER: current list indententation level.
+ * @protected
+ */
+ protected $listindentlevel = 0;
+
+ /**
+ * Current background color.
+ * @protected
+ */
+ protected $bgcolor;
+
+ /**
+ * Temporary font size in points.
+ * @protected
+ */
+ protected $tempfontsize = 10;
+
+ /**
+ * Spacer string for LI tags.
+ * @protected
+ */
+ protected $lispacer = '';
+
+ /**
+ * Default encoding.
+ * @protected
+ * @since 1.53.0.TC010
+ */
+ protected $encoding = 'UTF-8';
+
+ /**
+ * PHP internal encoding.
+ * @protected
+ * @since 1.53.0.TC016
+ */
+ protected $internal_encoding;
+
+ /**
+ * Boolean flag to indicate if the document language is Right-To-Left.
+ * @protected
+ * @since 2.0.000
+ */
+ protected $rtl = false;
+
+ /**
+ * Boolean flag used to force RTL or LTR string direction.
+ * @protected
+ * @since 2.0.000
+ */
+ protected $tmprtl = false;
+
+ // --- Variables used for document encryption:
+
+ /**
+ * IBoolean flag indicating whether document is protected.
+ * @protected
+ * @since 2.0.000 (2008-01-02)
+ */
+ protected $encrypted;
+
+ /**
+ * Array containing encryption settings.
+ * @protected
+ * @since 5.0.005 (2010-05-11)
+ */
+ protected $encryptdata = array();
+
+ /**
+ * Last RC4 key encrypted (cached for optimisation).
+ * @protected
+ * @since 2.0.000 (2008-01-02)
+ */
+ protected $last_enc_key;
+
+ /**
+ * Last RC4 computed key.
+ * @protected
+ * @since 2.0.000 (2008-01-02)
+ */
+ protected $last_enc_key_c;
+
+ /**
+ * File ID (used on document trailer).
+ * @protected
+ * @since 5.0.005 (2010-05-12)
+ */
+ protected $file_id;
+
+ // --- bookmark ---
+
+ /**
+ * Outlines for bookmark.
+ * @protected
+ * @since 2.1.002 (2008-02-12)
+ */
+ protected $outlines = array();
+
+ /**
+ * Outline root for bookmark.
+ * @protected
+ * @since 2.1.002 (2008-02-12)
+ */
+ protected $OutlineRoot;
+
+ // --- javascript and form ---
+
+ /**
+ * Javascript code.
+ * @protected
+ * @since 2.1.002 (2008-02-12)
+ */
+ protected $javascript = '';
+
+ /**
+ * Javascript counter.
+ * @protected
+ * @since 2.1.002 (2008-02-12)
+ */
+ protected $n_js;
+
+ /**
+ * line through state
+ * @protected
+ * @since 2.8.000 (2008-03-19)
+ */
+ protected $linethrough;
+
+ /**
+ * Array with additional document-wide usage rights for the document.
+ * @protected
+ * @since 5.8.014 (2010-08-23)
+ */
+ protected $ur = array();
+
+ /**
+ * DPI (Dot Per Inch) Document Resolution (do not change).
+ * @protected
+ * @since 3.0.000 (2008-03-27)
+ */
+ protected $dpi = 72;
+
+ /**
+ * Array of page numbers were a new page group was started (the page numbers are the keys of the array).
+ * @protected
+ * @since 3.0.000 (2008-03-27)
+ */
+ protected $newpagegroup = array();
+
+ /**
+ * Array that contains the number of pages in each page group.
+ * @protected
+ * @since 3.0.000 (2008-03-27)
+ */
+ protected $pagegroups = array();
+
+ /**
+ * Current page group number.
+ * @protected
+ * @since 3.0.000 (2008-03-27)
+ */
+ protected $currpagegroup = 0;
+
+ /**
+ * Array of transparency objects and parameters.
+ * @protected
+ * @since 3.0.000 (2008-03-27)
+ */
+ protected $extgstates;
+
+ /**
+ * Set the default JPEG compression quality (1-100).
+ * @protected
+ * @since 3.0.000 (2008-03-27)
+ */
+ protected $jpeg_quality;
+
+ /**
+ * Default cell height ratio.
+ * @protected
+ * @since 3.0.014 (2008-05-23)
+ */
+ protected $cell_height_ratio = K_CELL_HEIGHT_RATIO;
+
+ /**
+ * PDF viewer preferences.
+ * @protected
+ * @since 3.1.000 (2008-06-09)
+ */
+ protected $viewer_preferences;
+
+ /**
+ * A name object specifying how the document should be displayed when opened.
+ * @protected
+ * @since 3.1.000 (2008-06-09)
+ */
+ protected $PageMode;
+
+ /**
+ * Array for storing gradient information.
+ * @protected
+ * @since 3.1.000 (2008-06-09)
+ */
+ protected $gradients = array();
+
+ /**
+ * Array used to store positions inside the pages buffer (keys are the page numbers).
+ * @protected
+ * @since 3.2.000 (2008-06-26)
+ */
+ protected $intmrk = array();
+
+ /**
+ * Array used to store positions inside the pages buffer (keys are the page numbers).
+ * @protected
+ * @since 5.7.000 (2010-08-03)
+ */
+ protected $bordermrk = array();
+
+ /**
+ * Array used to store page positions to track empty pages (keys are the page numbers).
+ * @protected
+ * @since 5.8.007 (2010-08-18)
+ */
+ protected $emptypagemrk = array();
+
+ /**
+ * Array used to store content positions inside the pages buffer (keys are the page numbers).
+ * @protected
+ * @since 4.6.021 (2009-07-20)
+ */
+ protected $cntmrk = array();
+
+ /**
+ * Array used to store footer positions of each page.
+ * @protected
+ * @since 3.2.000 (2008-07-01)
+ */
+ protected $footerpos = array();
+
+ /**
+ * Array used to store footer length of each page.
+ * @protected
+ * @since 4.0.014 (2008-07-29)
+ */
+ protected $footerlen = array();
+
+ /**
+ * Boolean flag to indicate if a new line is created.
+ * @protected
+ * @since 3.2.000 (2008-07-01)
+ */
+ protected $newline = true;
+
+ /**
+ * End position of the latest inserted line.
+ * @protected
+ * @since 3.2.000 (2008-07-01)
+ */
+ protected $endlinex = 0;
+
+ /**
+ * PDF string for width value of the last line.
+ * @protected
+ * @since 4.0.006 (2008-07-16)
+ */
+ protected $linestyleWidth = '';
+
+ /**
+ * PDF string for CAP value of the last line.
+ * @protected
+ * @since 4.0.006 (2008-07-16)
+ */
+ protected $linestyleCap = '0 J';
+
+ /**
+ * PDF string for join value of the last line.
+ * @protected
+ * @since 4.0.006 (2008-07-16)
+ */
+ protected $linestyleJoin = '0 j';
+
+ /**
+ * PDF string for dash value of the last line.
+ * @protected
+ * @since 4.0.006 (2008-07-16)
+ */
+ protected $linestyleDash = '[] 0 d';
+
+ /**
+ * Boolean flag to indicate if marked-content sequence is open.
+ * @protected
+ * @since 4.0.013 (2008-07-28)
+ */
+ protected $openMarkedContent = false;
+
+ /**
+ * Count the latest inserted vertical spaces on HTML.
+ * @protected
+ * @since 4.0.021 (2008-08-24)
+ */
+ protected $htmlvspace = 0;
+
+ /**
+ * Array of Spot colors.
+ * @protected
+ * @since 4.0.024 (2008-09-12)
+ */
+ protected $spot_colors = array();
+
+ /**
+ * Symbol used for HTML unordered list items.
+ * @protected
+ * @since 4.0.028 (2008-09-26)
+ */
+ protected $lisymbol = '';
+
+ /**
+ * String used to mark the beginning and end of EPS image blocks.
+ * @protected
+ * @since 4.1.000 (2008-10-18)
+ */
+ protected $epsmarker = 'x#!#EPS#!#x';
+
+ /**
+ * Array of transformation matrix.
+ * @protected
+ * @since 4.2.000 (2008-10-29)
+ */
+ protected $transfmatrix = array();
+
+ /**
+ * Current key for transformation matrix.
+ * @protected
+ * @since 4.8.005 (2009-09-17)
+ */
+ protected $transfmatrix_key = 0;
+
+ /**
+ * Booklet mode for double-sided pages.
+ * @protected
+ * @since 4.2.000 (2008-10-29)
+ */
+ protected $booklet = false;
+
+ /**
+ * Epsilon value used for float calculations.
+ * @protected
+ * @since 4.2.000 (2008-10-29)
+ */
+ protected $feps = 0.005;
+
+ /**
+ * Array used for custom vertical spaces for HTML tags.
+ * @protected
+ * @since 4.2.001 (2008-10-30)
+ */
+ protected $tagvspaces = array();
+
+ /**
+ * HTML PARSER: custom indent amount for lists. Negative value means disabled.
+ * @protected
+ * @since 4.2.007 (2008-11-12)
+ */
+ protected $customlistindent = -1;
+
+ /**
+ * Boolean flag to indicate if the border of the cell sides that cross the page should be removed.
+ * @protected
+ * @since 4.2.010 (2008-11-14)
+ */
+ protected $opencell = true;
+
+ /**
+ * Array of files to embedd.
+ * @protected
+ * @since 4.4.000 (2008-12-07)
+ */
+ protected $embeddedfiles = array();
+
+ /**
+ * Boolean flag to indicate if we are inside a PRE tag.
+ * @protected
+ * @since 4.4.001 (2008-12-08)
+ */
+ protected $premode = false;
+
+ /**
+ * Array used to store positions of graphics transformation blocks inside the page buffer.
+ * keys are the page numbers
+ * @protected
+ * @since 4.4.002 (2008-12-09)
+ */
+ protected $transfmrk = array();
+
+ /**
+ * Default color for html links.
+ * @protected
+ * @since 4.4.003 (2008-12-09)
+ */
+ protected $htmlLinkColorArray = array(0, 0, 255);
+
+ /**
+ * Default font style to add to html links.
+ * @protected
+ * @since 4.4.003 (2008-12-09)
+ */
+ protected $htmlLinkFontStyle = 'U';
+
+ /**
+ * Counts the number of pages.
+ * @protected
+ * @since 4.5.000 (2008-12-31)
+ */
+ protected $numpages = 0;
+
+ /**
+ * Array containing page lengths in bytes.
+ * @protected
+ * @since 4.5.000 (2008-12-31)
+ */
+ protected $pagelen = array();
+
+ /**
+ * Counts the number of pages.
+ * @protected
+ * @since 4.5.000 (2008-12-31)
+ */
+ protected $numimages = 0;
+
+ /**
+ * Store the image keys.
+ * @protected
+ * @since 4.5.000 (2008-12-31)
+ */
+ protected $imagekeys = array();
+
+ /**
+ * Length of the buffer in bytes.
+ * @protected
+ * @since 4.5.000 (2008-12-31)
+ */
+ protected $bufferlen = 0;
+
+ /**
+ * If true enables disk caching.
+ * @protected
+ * @since 4.5.000 (2008-12-31)
+ */
+ protected $diskcache = false;
+
+ /**
+ * Counts the number of fonts.
+ * @protected
+ * @since 4.5.000 (2009-01-02)
+ */
+ protected $numfonts = 0;
+
+ /**
+ * Store the font keys.
+ * @protected
+ * @since 4.5.000 (2009-01-02)
+ */
+ protected $fontkeys = array();
+
+ /**
+ * Store the font object IDs.
+ * @protected
+ * @since 4.8.001 (2009-09-09)
+ */
+ protected $font_obj_ids = array();
+
+ /**
+ * Store the fage status (true when opened, false when closed).
+ * @protected
+ * @since 4.5.000 (2009-01-02)
+ */
+ protected $pageopen = array();
+
+ /**
+ * Default monospace font.
+ * @protected
+ * @since 4.5.025 (2009-03-10)
+ */
+ protected $default_monospaced_font = 'courier';
+
+ /**
+ * Cloned copy of the current class object.
+ * @protected
+ * @since 4.5.029 (2009-03-19)
+ */
+ protected $objcopy;
+
+ /**
+ * Array used to store the lengths of cache files.
+ * @protected
+ * @since 4.5.029 (2009-03-19)
+ */
+ protected $cache_file_length = array();
+
+ /**
+ * Table header content to be repeated on each new page.
+ * @protected
+ * @since 4.5.030 (2009-03-20)
+ */
+ protected $thead = '';
+
+ /**
+ * Margins used for table header.
+ * @protected
+ * @since 4.5.030 (2009-03-20)
+ */
+ protected $theadMargins = array();
+
+ /**
+ * Boolean flag to enable document digital signature.
+ * @protected
+ * @since 4.6.005 (2009-04-24)
+ */
+ protected $sign = false;
+
+ /**
+ * Digital signature data.
+ * @protected
+ * @since 4.6.005 (2009-04-24)
+ */
+ protected $signature_data = array();
+
+ /**
+ * Digital signature max length.
+ * @protected
+ * @since 4.6.005 (2009-04-24)
+ */
+ protected $signature_max_length = 11742;
+
+ /**
+ * Data for digital signature appearance.
+ * @protected
+ * @since 5.3.011 (2010-06-16)
+ */
+ protected $signature_appearance = array('page' => 1, 'rect' => '0 0 0 0');
+
+ /**
+ * Array of empty digital signature appearances.
+ * @protected
+ * @since 5.9.101 (2011-07-06)
+ */
+ protected $empty_signature_appearance = array();
+
+ /**
+ * Regular expression used to find blank characters (required for word-wrapping).
+ * @protected
+ * @since 4.6.006 (2009-04-28)
+ */
+ protected $re_spaces = '/[^\S\xa0]/';
+
+ /**
+ * Array of $re_spaces parts.
+ * @protected
+ * @since 5.5.011 (2010-07-09)
+ */
+ protected $re_space = array('p' => '[^\S\xa0]', 'm' => '');
+
+ /**
+ * Digital signature object ID.
+ * @protected
+ * @since 4.6.022 (2009-06-23)
+ */
+ protected $sig_obj_id = 0;
+
+ /**
+ * ID of page objects.
+ * @protected
+ * @since 4.7.000 (2009-08-29)
+ */
+ protected $page_obj_id = array();
+
+ /**
+ * List of form annotations IDs.
+ * @protected
+ * @since 4.8.000 (2009-09-07)
+ */
+ protected $form_obj_id = array();
+
+ /**
+ * Deafult Javascript field properties. Possible values are described on official Javascript for Acrobat API reference. Annotation options can be directly specified using the 'aopt' entry.
+ * @protected
+ * @since 4.8.000 (2009-09-07)
+ */
+ protected $default_form_prop = array('lineWidth'=>1, 'borderStyle'=>'solid', 'fillColor'=>array(255, 255, 255), 'strokeColor'=>array(128, 128, 128));
+
+ /**
+ * Javascript objects array.
+ * @protected
+ * @since 4.8.000 (2009-09-07)
+ */
+ protected $js_objects = array();
+
+ /**
+ * Current form action (used during XHTML rendering).
+ * @protected
+ * @since 4.8.000 (2009-09-07)
+ */
+ protected $form_action = '';
+
+ /**
+ * Current form encryption type (used during XHTML rendering).
+ * @protected
+ * @since 4.8.000 (2009-09-07)
+ */
+ protected $form_enctype = 'application/x-www-form-urlencoded';
+
+ /**
+ * Current method to submit forms.
+ * @protected
+ * @since 4.8.000 (2009-09-07)
+ */
+ protected $form_mode = 'post';
+
+ /**
+ * List of fonts used on form fields (fontname => fontkey).
+ * @protected
+ * @since 4.8.001 (2009-09-09)
+ */
+ protected $annotation_fonts = array();
+
+ /**
+ * List of radio buttons parent objects.
+ * @protected
+ * @since 4.8.001 (2009-09-09)
+ */
+ protected $radiobutton_groups = array();
+
+ /**
+ * List of radio group objects IDs.
+ * @protected
+ * @since 4.8.001 (2009-09-09)
+ */
+ protected $radio_groups = array();
+
+ /**
+ * Text indentation value (used for text-indent CSS attribute).
+ * @protected
+ * @since 4.8.006 (2009-09-23)
+ */
+ protected $textindent = 0;
+
+ /**
+ * Store page number when startTransaction() is called.
+ * @protected
+ * @since 4.8.006 (2009-09-23)
+ */
+ protected $start_transaction_page = 0;
+
+ /**
+ * Store Y position when startTransaction() is called.
+ * @protected
+ * @since 4.9.001 (2010-03-28)
+ */
+ protected $start_transaction_y = 0;
+
+ /**
+ * True when we are printing the thead section on a new page.
+ * @protected
+ * @since 4.8.027 (2010-01-25)
+ */
+ protected $inthead = false;
+
+ /**
+ * Array of column measures (width, space, starting Y position).
+ * @protected
+ * @since 4.9.001 (2010-03-28)
+ */
+ protected $columns = array();
+
+ /**
+ * Number of colums.
+ * @protected
+ * @since 4.9.001 (2010-03-28)
+ */
+ protected $num_columns = 1;
+
+ /**
+ * Current column number.
+ * @protected
+ * @since 4.9.001 (2010-03-28)
+ */
+ protected $current_column = 0;
+
+ /**
+ * Starting page for columns.
+ * @protected
+ * @since 4.9.001 (2010-03-28)
+ */
+ protected $column_start_page = 0;
+
+ /**
+ * Maximum page and column selected.
+ * @protected
+ * @since 5.8.000 (2010-08-11)
+ */
+ protected $maxselcol = array('page' => 0, 'column' => 0);
+
+ /**
+ * Array of: X difference between table cell x start and starting page margin, cellspacing, cellpadding.
+ * @protected
+ * @since 5.8.000 (2010-08-11)
+ */
+ protected $colxshift = array('x' => 0, 's' => array('H' => 0, 'V' => 0), 'p' => array('L' => 0, 'T' => 0, 'R' => 0, 'B' => 0));
+
+ /**
+ * Text rendering mode: 0 = Fill text; 1 = Stroke text; 2 = Fill, then stroke text; 3 = Neither fill nor stroke text (invisible); 4 = Fill text and add to path for clipping; 5 = Stroke text and add to path for clipping; 6 = Fill, then stroke text and add to path for clipping; 7 = Add text to path for clipping.
+ * @protected
+ * @since 4.9.008 (2010-04-03)
+ */
+ protected $textrendermode = 0;
+
+ /**
+ * Text stroke width in doc units.
+ * @protected
+ * @since 4.9.008 (2010-04-03)
+ */
+ protected $textstrokewidth = 0;
+
+ /**
+ * Current stroke color.
+ * @protected
+ * @since 4.9.008 (2010-04-03)
+ */
+ protected $strokecolor;
+
+ /**
+ * Default unit of measure for document.
+ * @protected
+ * @since 5.0.000 (2010-04-22)
+ */
+ protected $pdfunit = 'mm';
+
+ /**
+ * Boolean flag true when we are on TOC (Table Of Content) page.
+ * @protected
+ */
+ protected $tocpage = false;
+
+ /**
+ * Boolean flag: if true convert vector images (SVG, EPS) to raster image using GD or ImageMagick library.
+ * @protected
+ * @since 5.0.000 (2010-04-26)
+ */
+ protected $rasterize_vector_images = false;
+
+ /**
+ * Boolean flag: if true enables font subsetting by default.
+ * @protected
+ * @since 5.3.002 (2010-06-07)
+ */
+ protected $font_subsetting = true;
+
+ /**
+ * Array of default graphic settings.
+ * @protected
+ * @since 5.5.008 (2010-07-02)
+ */
+ protected $default_graphic_vars = array();
+
+ /**
+ * Array of XObjects.
+ * @protected
+ * @since 5.8.014 (2010-08-23)
+ */
+ protected $xobjects = array();
+
+ /**
+ * Boolean value true when we are inside an XObject.
+ * @protected
+ * @since 5.8.017 (2010-08-24)
+ */
+ protected $inxobj = false;
+
+ /**
+ * Current XObject ID.
+ * @protected
+ * @since 5.8.017 (2010-08-24)
+ */
+ protected $xobjid = '';
+
+ /**
+ * Percentage of character stretching.
+ * @protected
+ * @since 5.9.000 (2010-09-29)
+ */
+ protected $font_stretching = 100;
+
+ /**
+ * Increases or decreases the space between characters in a text by the specified amount (tracking).
+ * @protected
+ * @since 5.9.000 (2010-09-29)
+ */
+ protected $font_spacing = 0;
+
+ /**
+ * Array of no-write regions.
+ * ('page' => page number or empy for current page, 'xt' => X top, 'yt' => Y top, 'xb' => X bottom, 'yb' => Y bottom, 'side' => page side 'L' = left or 'R' = right)
+ * @protected
+ * @since 5.9.003 (2010-10-14)
+ */
+ protected $page_regions = array();
+
+ /**
+ * Boolean value true when page region check is active.
+ * @protected
+ */
+ protected $check_page_regions = true;
+
+ /**
+ * Array of PDF layers data.
+ * @protected
+ * @since 5.9.102 (2011-07-13)
+ */
+ protected $pdflayers = array();
+
+ /**
+ * A dictionary of names and corresponding destinations (Dests key on document Catalog).
+ * @protected
+ * @since 5.9.097 (2011-06-23)
+ */
+ protected $dests = array();
+
+ /**
+ * Object ID for Named Destinations
+ * @protected
+ * @since 5.9.097 (2011-06-23)
+ */
+ protected $n_dests;
+
+ /**
+ * Embedded Files Names
+ * @protected
+ * @since 5.9.204 (2013-01-23)
+ */
+ protected $efnames = array();
+
+ /**
+ * Directory used for the last SVG image.
+ * @protected
+ * @since 5.0.000 (2010-05-05)
+ */
+ protected $svgdir = '';
+
+ /**
+ * Deafult unit of measure for SVG.
+ * @protected
+ * @since 5.0.000 (2010-05-02)
+ */
+ protected $svgunit = 'px';
+
+ /**
+ * Array of SVG gradients.
+ * @protected
+ * @since 5.0.000 (2010-05-02)
+ */
+ protected $svggradients = array();
+
+ /**
+ * ID of last SVG gradient.
+ * @protected
+ * @since 5.0.000 (2010-05-02)
+ */
+ protected $svggradientid = 0;
+
+ /**
+ * Boolean value true when in SVG defs group.
+ * @protected
+ * @since 5.0.000 (2010-05-02)
+ */
+ protected $svgdefsmode = false;
+
+ /**
+ * Array of SVG defs.
+ * @protected
+ * @since 5.0.000 (2010-05-02)
+ */
+ protected $svgdefs = array();
+
+ /**
+ * Boolean value true when in SVG clipPath tag.
+ * @protected
+ * @since 5.0.000 (2010-04-26)
+ */
+ protected $svgclipmode = false;
+
+ /**
+ * Array of SVG clipPath commands.
+ * @protected
+ * @since 5.0.000 (2010-05-02)
+ */
+ protected $svgclippaths = array();
+
+ /**
+ * Array of SVG clipPath tranformation matrix.
+ * @protected
+ * @since 5.8.022 (2010-08-31)
+ */
+ protected $svgcliptm = array();
+
+ /**
+ * ID of last SVG clipPath.
+ * @protected
+ * @since 5.0.000 (2010-05-02)
+ */
+ protected $svgclipid = 0;
+
+ /**
+ * SVG text.
+ * @protected
+ * @since 5.0.000 (2010-05-02)
+ */
+ protected $svgtext = '';
+
+ /**
+ * SVG text properties.
+ * @protected
+ * @since 5.8.013 (2010-08-23)
+ */
+ protected $svgtextmode = array();
+
+ /**
+ * Array of SVG properties.
+ * @protected
+ * @since 5.0.000 (2010-05-02)
+ */
+ protected $svgstyles = array(array(
+ 'alignment-baseline' => 'auto',
+ 'baseline-shift' => 'baseline',
+ 'clip' => 'auto',
+ 'clip-path' => 'none',
+ 'clip-rule' => 'nonzero',
+ 'color' => 'black',
+ 'color-interpolation' => 'sRGB',
+ 'color-interpolation-filters' => 'linearRGB',
+ 'color-profile' => 'auto',
+ 'color-rendering' => 'auto',
+ 'cursor' => 'auto',
+ 'direction' => 'ltr',
+ 'display' => 'inline',
+ 'dominant-baseline' => 'auto',
+ 'enable-background' => 'accumulate',
+ 'fill' => 'black',
+ 'fill-opacity' => 1,
+ 'fill-rule' => 'nonzero',
+ 'filter' => 'none',
+ 'flood-color' => 'black',
+ 'flood-opacity' => 1,
+ 'font' => '',
+ 'font-family' => 'helvetica',
+ 'font-size' => 'medium',
+ 'font-size-adjust' => 'none',
+ 'font-stretch' => 'normal',
+ 'font-style' => 'normal',
+ 'font-variant' => 'normal',
+ 'font-weight' => 'normal',
+ 'glyph-orientation-horizontal' => '0deg',
+ 'glyph-orientation-vertical' => 'auto',
+ 'image-rendering' => 'auto',
+ 'kerning' => 'auto',
+ 'letter-spacing' => 'normal',
+ 'lighting-color' => 'white',
+ 'marker' => '',
+ 'marker-end' => 'none',
+ 'marker-mid' => 'none',
+ 'marker-start' => 'none',
+ 'mask' => 'none',
+ 'opacity' => 1,
+ 'overflow' => 'auto',
+ 'pointer-events' => 'visiblePainted',
+ 'shape-rendering' => 'auto',
+ 'stop-color' => 'black',
+ 'stop-opacity' => 1,
+ 'stroke' => 'none',
+ 'stroke-dasharray' => 'none',
+ 'stroke-dashoffset' => 0,
+ 'stroke-linecap' => 'butt',
+ 'stroke-linejoin' => 'miter',
+ 'stroke-miterlimit' => 4,
+ 'stroke-opacity' => 1,
+ 'stroke-width' => 1,
+ 'text-anchor' => 'start',
+ 'text-decoration' => 'none',
+ 'text-rendering' => 'auto',
+ 'unicode-bidi' => 'normal',
+ 'visibility' => 'visible',
+ 'word-spacing' => 'normal',
+ 'writing-mode' => 'lr-tb',
+ 'text-color' => 'black',
+ 'transfmatrix' => array(1, 0, 0, 1, 0, 0)
+ ));
+
+ /**
+ * If true force sRGB color profile for all document.
+ * @protected
+ * @since 5.9.121 (2011-09-28)
+ */
+ protected $force_srgb = false;
+
+ /**
+ * If true set the document to PDF/A mode.
+ * @protected
+ * @since 5.9.121 (2011-09-27)
+ */
+ protected $pdfa_mode = false;
+
+ /**
+ * Document creation date-time
+ * @protected
+ * @since 5.9.152 (2012-03-22)
+ */
+ protected $doc_creation_timestamp;
+
+ /**
+ * Document modification date-time
+ * @protected
+ * @since 5.9.152 (2012-03-22)
+ */
+ protected $doc_modification_timestamp;
+
+ /**
+ * Custom XMP data.
+ * @protected
+ * @since 5.9.128 (2011-10-06)
+ */
+ protected $custom_xmp = '';
+
+ /**
+ * Overprint mode array.
+ * (Check the "Entries in a Graphics State Parameter Dictionary" on PDF 32000-1:2008).
+ * @protected
+ * @since 5.9.152 (2012-03-23)
+ */
+ protected $overprint = array('OP' => false, 'op' => false, 'OPM' => 0);
+
+ /**
+ * Alpha mode array.
+ * (Check the "Entries in a Graphics State Parameter Dictionary" on PDF 32000-1:2008).
+ * @protected
+ * @since 5.9.152 (2012-03-23)
+ */
+ protected $alpha = array('CA' => 1, 'ca' => 1, 'BM' => '/Normal', 'AIS' => false);
+
+ /**
+ * Define the page boundaries boxes to be set on document.
+ * @protected
+ * @since 5.9.152 (2012-03-23)
+ */
+ protected $page_boxes = array('MediaBox', 'CropBox', 'BleedBox', 'TrimBox', 'ArtBox');
+
+ /**
+ * If true print TCPDF meta link.
+ * @protected
+ * @since 5.9.152 (2012-03-23)
+ */
+ protected $tcpdflink = true;
+
+ /**
+ * Cache array for computed GD gamma values.
+ * @protected
+ * @since 5.9.1632 (2012-06-05)
+ */
+ protected $gdgammacache = array();
+
+ //------------------------------------------------------------
+ // METHODS
+ //------------------------------------------------------------
+
+ /**
+ * This is the class constructor.
+ * It allows to set up the page format, the orientation and the measure unit used in all the methods (except for the font sizes).
+ *
+ * IMPORTANT: Please note that this method sets the mb_internal_encoding to ASCII, so if you are using the mbstring module functions with TCPDF you need to correctly set/unset the mb_internal_encoding when needed.
+ *
+ * @param $orientation (string) page orientation. Possible values are (case insensitive):<ul><li>P or Portrait (default)</li><li>L or Landscape</li><li>'' (empty string) for automatic orientation</li></ul>
+ * @param $unit (string) User measure unit. Possible values are:<ul><li>pt: point</li><li>mm: millimeter (default)</li><li>cm: centimeter</li><li>in: inch</li></ul><br />A point equals 1/72 of inch, that is to say about 0.35 mm (an inch being 2.54 cm). This is a very common unit in typography; font sizes are expressed in that unit.
+ * @param $format (mixed) The format used for pages. It can be either: one of the string values specified at getPageSizeFromFormat() or an array of parameters specified at setPageFormat().
+ * @param $unicode (boolean) TRUE means that the input text is unicode (default = true)
+ * @param $encoding (string) Charset encoding (used only when converting back html entities); default is UTF-8.
+ * @param $diskcache (boolean) If TRUE reduce the RAM memory usage by caching temporary data on filesystem (slower).
+ * @param $pdfa (boolean) If TRUE set the document to PDF/A mode.
+ * @public
+ * @see getPageSizeFromFormat(), setPageFormat()
+ */
+ public function __construct($orientation='P', $unit='mm', $format='A4', $unicode=true, $encoding='UTF-8', $diskcache=false, $pdfa=false) {
+ /* Set internal character encoding to ASCII */
+ if (function_exists('mb_internal_encoding') AND mb_internal_encoding()) {
+ $this->internal_encoding = mb_internal_encoding();
+ mb_internal_encoding('ASCII');
+ }
+ $this->font_obj_ids = array();
+ $this->page_obj_id = array();
+ $this->form_obj_id = array();
+ // set pdf/a mode
+ $this->pdfa_mode = $pdfa;
+ $this->force_srgb = false;
+ // set disk caching
+ $this->diskcache = $diskcache ? true : false;
+ // set language direction
+ $this->rtl = false;
+ $this->tmprtl = false;
+ // some checks
+ $this->_dochecks();
+ // initialization of properties
+ $this->isunicode = $unicode;
+ $this->page = 0;
+ $this->transfmrk[0] = array();
+ $this->pagedim = array();
+ $this->n = 2;
+ $this->buffer = '';
+ $this->pages = array();
+ $this->state = 0;
+ $this->fonts = array();
+ $this->FontFiles = array();
+ $this->diffs = array();
+ $this->images = array();
+ $this->links = array();
+ $this->gradients = array();
+ $this->InFooter = false;
+ $this->lasth = 0;
+ $this->FontFamily = defined('PDF_FONT_NAME_MAIN')?PDF_FONT_NAME_MAIN:'helvetica';
+ $this->FontStyle = '';
+ $this->FontSizePt = 12;
+ $this->underline = false;
+ $this->overline = false;
+ $this->linethrough = false;
+ $this->DrawColor = '0 G';
+ $this->FillColor = '0 g';
+ $this->TextColor = '0 g';
+ $this->ColorFlag = false;
+ $this->pdflayers = array();
+ // encryption values
+ $this->encrypted = false;
+ $this->last_enc_key = '';
+ // standard Unicode fonts
+ $this->CoreFonts = array(
+ 'courier'=>'Courier',
+ 'courierB'=>'Courier-Bold',
+ 'courierI'=>'Courier-Oblique',
+ 'courierBI'=>'Courier-BoldOblique',
+ 'helvetica'=>'Helvetica',
+ 'helveticaB'=>'Helvetica-Bold',
+ 'helveticaI'=>'Helvetica-Oblique',
+ 'helveticaBI'=>'Helvetica-BoldOblique',
+ 'times'=>'Times-Roman',
+ 'timesB'=>'Times-Bold',
+ 'timesI'=>'Times-Italic',
+ 'timesBI'=>'Times-BoldItalic',
+ 'symbol'=>'Symbol',
+ 'zapfdingbats'=>'ZapfDingbats'
+ );
+ // set scale factor
+ $this->setPageUnit($unit);
+ // set page format and orientation
+ $this->setPageFormat($format, $orientation);
+ // page margins (1 cm)
+ $margin = 28.35 / $this->k;
+ $this->SetMargins($margin, $margin);
+ $this->clMargin = $this->lMargin;
+ $this->crMargin = $this->rMargin;
+ // internal cell padding
+ $cpadding = $margin / 10;
+ $this->setCellPaddings($cpadding, 0, $cpadding, 0);
+ // cell margins
+ $this->setCellMargins(0, 0, 0, 0);
+ // line width (0.2 mm)
+ $this->LineWidth = 0.57 / $this->k;
+ $this->linestyleWidth = sprintf('%F w', ($this->LineWidth * $this->k));
+ $this->linestyleCap = '0 J';
+ $this->linestyleJoin = '0 j';
+ $this->linestyleDash = '[] 0 d';
+ // automatic page break
+ $this->SetAutoPageBreak(true, (2 * $margin));
+ // full width display mode
+ $this->SetDisplayMode('fullwidth');
+ // compression
+ $this->SetCompression();
+ // set default PDF version number
+ $this->setPDFVersion();
+ $this->tcpdflink = true;
+ $this->encoding = $encoding;
+ $this->HREF = array();
+ $this->getFontsList();
+ $this->fgcolor = array('R' => 0, 'G' => 0, 'B' => 0);
+ $this->strokecolor = array('R' => 0, 'G' => 0, 'B' => 0);
+ $this->bgcolor = array('R' => 255, 'G' => 255, 'B' => 255);
+ $this->extgstates = array();
+ $this->setTextShadow();
+ // user's rights
+ $this->sign = false;
+ $this->ur['enabled'] = false;
+ $this->ur['document'] = '/FullSave';
+ $this->ur['annots'] = '/Create/Delete/Modify/Copy/Import/Export';
+ $this->ur['form'] = '/Add/Delete/FillIn/Import/Export/SubmitStandalone/SpawnTemplate';
+ $this->ur['signature'] = '/Modify';
+ $this->ur['ef'] = '/Create/Delete/Modify/Import';
+ $this->ur['formex'] = '';
+ $this->signature_appearance = array('page' => 1, 'rect' => '0 0 0 0', 'name' => 'Signature');
+ $this->empty_signature_appearance = array();
+ // set default JPEG quality
+ $this->jpeg_quality = 75;
+ // initialize some settings
+ TCPDF_FONTS::utf8Bidi(array(''), '', false, $this->isunicode, $this->CurrentFont);
+ // set default font
+ $this->SetFont($this->FontFamily, $this->FontStyle, $this->FontSizePt);
+ // check if PCRE Unicode support is enabled
+ if ($this->isunicode AND (@preg_match('/\pL/u', 'a') == 1)) {
+ // PCRE unicode support is turned ON
+ // \p{Z} or \p{Separator}: any kind of Unicode whitespace or invisible separator.
+ // \p{Lo} or \p{Other_Letter}: a Unicode letter or ideograph that does not have lowercase and uppercase variants.
+ // \p{Lo} is needed because Chinese characters are packed next to each other without spaces in between.
+ //$this->setSpacesRE('/[^\S\P{Z}\P{Lo}\xa0]/u');
+ $this->setSpacesRE('/[^\S\P{Z}\xa0]/u');
+ } else {
+ // PCRE unicode support is turned OFF
+ $this->setSpacesRE('/[^\S\xa0]/');
+ }
+ $this->default_form_prop = array('lineWidth'=>1, 'borderStyle'=>'solid', 'fillColor'=>array(255, 255, 255), 'strokeColor'=>array(128, 128, 128));
+ // set file ID for trailer
+ $serformat = (is_array($format) ? serialize($format) : $format);
+ $this->file_id = md5(TCPDF_STATIC::getRandomSeed('TCPDF'.$orientation.$unit.$serformat.$encoding));
+ // set document creation and modification timestamp
+ $this->doc_creation_timestamp = time();
+ $this->doc_modification_timestamp = $this->doc_creation_timestamp;
+ // get default graphic vars
+ $this->default_graphic_vars = $this->getGraphicVars();
+ $this->header_xobj_autoreset = false;
+ $this->custom_xmp = '';
+ }
+
+ /**
+ * Default destructor.
+ * @public
+ * @since 1.53.0.TC016
+ */
+ public function __destruct() {
+ // restore internal encoding
+ if (isset($this->internal_encoding) AND !empty($this->internal_encoding)) {
+ mb_internal_encoding($this->internal_encoding);
+ }
+ // unset all class variables
+ $this->_destroy(true);
+ }
+
+ /**
+ * Set the units of measure for the document.
+ * @param $unit (string) User measure unit. Possible values are:<ul><li>pt: point</li><li>mm: millimeter (default)</li><li>cm: centimeter</li><li>in: inch</li></ul><br />A point equals 1/72 of inch, that is to say about 0.35 mm (an inch being 2.54 cm). This is a very common unit in typography; font sizes are expressed in that unit.
+ * @public
+ * @since 3.0.015 (2008-06-06)
+ */
+ public function setPageUnit($unit) {
+ $unit = strtolower($unit);
+ //Set scale factor
+ switch ($unit) {
+ // points
+ case 'px':
+ case 'pt': {
+ $this->k = 1;
+ break;
+ }
+ // millimeters
+ case 'mm': {
+ $this->k = $this->dpi / 25.4;
+ break;
+ }
+ // centimeters
+ case 'cm': {
+ $this->k = $this->dpi / 2.54;
+ break;
+ }
+ // inches
+ case 'in': {
+ $this->k = $this->dpi;
+ break;
+ }
+ // unsupported unit
+ default : {
+ $this->Error('Incorrect unit: '.$unit);
+ break;
+ }
+ }
+ $this->pdfunit = $unit;
+ if (isset($this->CurOrientation)) {
+ $this->setPageOrientation($this->CurOrientation);
+ }
+ }
+
+ /**
+ * Change the format of the current page
+ * @param $format (mixed) The format used for pages. It can be either: one of the string values specified at getPageSizeFromFormat() documentation or an array of two numners (width, height) or an array containing the following measures and options:<ul>
+ * <li>['format'] = page format name (one of the above);</li>
+ * <li>['Rotate'] : The number of degrees by which the page shall be rotated clockwise when displayed or printed. The value shall be a multiple of 90.</li>
+ * <li>['PZ'] : The page's preferred zoom (magnification) factor.</li>
+ * <li>['MediaBox'] : the boundaries of the physical medium on which the page shall be displayed or printed:</li>
+ * <li>['MediaBox']['llx'] : lower-left x coordinate in points</li>
+ * <li>['MediaBox']['lly'] : lower-left y coordinate in points</li>
+ * <li>['MediaBox']['urx'] : upper-right x coordinate in points</li>
+ * <li>['MediaBox']['ury'] : upper-right y coordinate in points</li>
+ * <li>['CropBox'] : the visible region of default user space:</li>
+ * <li>['CropBox']['llx'] : lower-left x coordinate in points</li>
+ * <li>['CropBox']['lly'] : lower-left y coordinate in points</li>
+ * <li>['CropBox']['urx'] : upper-right x coordinate in points</li>
+ * <li>['CropBox']['ury'] : upper-right y coordinate in points</li>
+ * <li>['BleedBox'] : the region to which the contents of the page shall be clipped when output in a production environment:</li>
+ * <li>['BleedBox']['llx'] : lower-left x coordinate in points</li>
+ * <li>['BleedBox']['lly'] : lower-left y coordinate in points</li>
+ * <li>['BleedBox']['urx'] : upper-right x coordinate in points</li>
+ * <li>['BleedBox']['ury'] : upper-right y coordinate in points</li>
+ * <li>['TrimBox'] : the intended dimensions of the finished page after trimming:</li>
+ * <li>['TrimBox']['llx'] : lower-left x coordinate in points</li>
+ * <li>['TrimBox']['lly'] : lower-left y coordinate in points</li>
+ * <li>['TrimBox']['urx'] : upper-right x coordinate in points</li>
+ * <li>['TrimBox']['ury'] : upper-right y coordinate in points</li>
+ * <li>['ArtBox'] : the extent of the page's meaningful content:</li>
+ * <li>['ArtBox']['llx'] : lower-left x coordinate in points</li>
+ * <li>['ArtBox']['lly'] : lower-left y coordinate in points</li>
+ * <li>['ArtBox']['urx'] : upper-right x coordinate in points</li>
+ * <li>['ArtBox']['ury'] : upper-right y coordinate in points</li>
+ * <li>['BoxColorInfo'] :specify the colours and other visual characteristics that should be used in displaying guidelines on the screen for each of the possible page boundaries other than the MediaBox:</li>
+ * <li>['BoxColorInfo'][BOXTYPE]['C'] : an array of three numbers in the range 0-255, representing the components in the DeviceRGB colour space.</li>
+ * <li>['BoxColorInfo'][BOXTYPE]['W'] : the guideline width in default user units</li>
+ * <li>['BoxColorInfo'][BOXTYPE]['S'] : the guideline style: S = Solid; D = Dashed</li>
+ * <li>['BoxColorInfo'][BOXTYPE]['D'] : dash array defining a pattern of dashes and gaps to be used in drawing dashed guidelines</li>
+ * <li>['trans'] : the style and duration of the visual transition to use when moving from another page to the given page during a presentation</li>
+ * <li>['trans']['Dur'] : The page's display duration (also called its advance timing): the maximum length of time, in seconds, that the page shall be displayed during presentations before the viewer application shall automatically advance to the next page.</li>
+ * <li>['trans']['S'] : transition style : Split, Blinds, Box, Wipe, Dissolve, Glitter, R, Fly, Push, Cover, Uncover, Fade</li>
+ * <li>['trans']['D'] : The duration of the transition effect, in seconds.</li>
+ * <li>['trans']['Dm'] : (Split and Blinds transition styles only) The dimension in which the specified transition effect shall occur: H = Horizontal, V = Vertical. Default value: H.</li>
+ * <li>['trans']['M'] : (Split, Box and Fly transition styles only) The direction of motion for the specified transition effect: I = Inward from the edges of the page, O = Outward from the center of the pageDefault value: I.</li>
+ * <li>['trans']['Di'] : (Wipe, Glitter, Fly, Cover, Uncover and Push transition styles only) The direction in which the specified transition effect shall moves, expressed in degrees counterclockwise starting from a left-to-right direction. If the value is a number, it shall be one of: 0 = Left to right, 90 = Bottom to top (Wipe only), 180 = Right to left (Wipe only), 270 = Top to bottom, 315 = Top-left to bottom-right (Glitter only). If the value is a name, it shall be None, which is relevant only for the Fly transition when the value of SS is not 1.0. Default value: 0.</li>
+ * <li>['trans']['SS'] : (Fly transition style only) The starting or ending scale at which the changes shall be drawn. If M specifies an inward transition, the scale of the changes drawn shall progress from SS to 1.0 over the course of the transition. If M specifies an outward transition, the scale of the changes drawn shall progress from 1.0 to SS over the course of the transition. Default: 1.0.</li>
+ * <li>['trans']['B'] : (Fly transition style only) If true, the area that shall be flown in is rectangular and opaque. Default: false.</li>
+ * </ul>
+ * @param $orientation (string) page orientation. Possible values are (case insensitive):<ul>
+ * <li>P or Portrait (default)</li>
+ * <li>L or Landscape</li>
+ * <li>'' (empty string) for automatic orientation</li>
+ * </ul>
+ * @protected
+ * @since 3.0.015 (2008-06-06)
+ * @see getPageSizeFromFormat()
+ */
+ protected function setPageFormat($format, $orientation='P') {
+ if (!empty($format) AND isset($this->pagedim[$this->page])) {
+ // remove inherited values
+ unset($this->pagedim[$this->page]);
+ }
+ if (is_string($format)) {
+ // get page measures from format name
+ $pf = TCPDF_STATIC::getPageSizeFromFormat($format);
+ $this->fwPt = $pf[0];
+ $this->fhPt = $pf[1];
+ } else {
+ // the boundaries of the physical medium on which the page shall be displayed or printed
+ if (isset($format['MediaBox'])) {
+ $this->pagedim = TCPDF_STATIC::setPageBoxes($this->page, 'MediaBox', $format['MediaBox']['llx'], $format['MediaBox']['lly'], $format['MediaBox']['urx'], $format['MediaBox']['ury'], false, $this->k, $this->pagedim);
+ $this->fwPt = (($format['MediaBox']['urx'] - $format['MediaBox']['llx']) * $this->k);
+ $this->fhPt = (($format['MediaBox']['ury'] - $format['MediaBox']['lly']) * $this->k);
+ } else {
+ if (isset($format[0]) AND is_numeric($format[0]) AND isset($format[1]) AND is_numeric($format[1])) {
+ $pf = array(($format[0] * $this->k), ($format[1] * $this->k));
+ } else {
+ if (!isset($format['format'])) {
+ // default value
+ $format['format'] = 'A4';
+ }
+ $pf = TCPDF_STATIC::getPageSizeFromFormat($format['format']);
+ }
+ $this->fwPt = $pf[0];
+ $this->fhPt = $pf[1];
+ $this->pagedim = TCPDF_STATIC::setPageBoxes($this->page, 'MediaBox', 0, 0, $this->fwPt, $this->fhPt, true, $this->k, $this->pagedim);
+ }
+ // the visible region of default user space
+ if (isset($format['CropBox'])) {
+ $this->pagedim = TCPDF_STATIC::setPageBoxes($this->page, 'CropBox', $format['CropBox']['llx'], $format['CropBox']['lly'], $format['CropBox']['urx'], $format['CropBox']['ury'], false, $this->k, $this->pagedim);
+ }
+ // the region to which the contents of the page shall be clipped when output in a production environment
+ if (isset($format['BleedBox'])) {
+ $this->pagedim = TCPDF_STATIC::setPageBoxes($this->page, 'BleedBox', $format['BleedBox']['llx'], $format['BleedBox']['lly'], $format['BleedBox']['urx'], $format['BleedBox']['ury'], false, $this->k, $this->pagedim);
+ }
+ // the intended dimensions of the finished page after trimming
+ if (isset($format['TrimBox'])) {
+ $this->pagedim = TCPDF_STATIC::setPageBoxes($this->page, 'TrimBox', $format['TrimBox']['llx'], $format['TrimBox']['lly'], $format['TrimBox']['urx'], $format['TrimBox']['ury'], false, $this->k, $this->pagedim);
+ }
+ // the page's meaningful content (including potential white space)
+ if (isset($format['ArtBox'])) {
+ $this->pagedim = TCPDF_STATIC::setPageBoxes($this->page, 'ArtBox', $format['ArtBox']['llx'], $format['ArtBox']['lly'], $format['ArtBox']['urx'], $format['ArtBox']['ury'], false, $this->k, $this->pagedim);
+ }
+ // specify the colours and other visual characteristics that should be used in displaying guidelines on the screen for the various page boundaries
+ if (isset($format['BoxColorInfo'])) {
+ $this->pagedim[$this->page]['BoxColorInfo'] = $format['BoxColorInfo'];
+ }
+ if (isset($format['Rotate']) AND (($format['Rotate'] % 90) == 0)) {
+ // The number of degrees by which the page shall be rotated clockwise when displayed or printed. The value shall be a multiple of 90.
+ $this->pagedim[$this->page]['Rotate'] = intval($format['Rotate']);
+ }
+ if (isset($format['PZ'])) {
+ // The page's preferred zoom (magnification) factor
+ $this->pagedim[$this->page]['PZ'] = floatval($format['PZ']);
+ }
+ if (isset($format['trans'])) {
+ // The style and duration of the visual transition to use when moving from another page to the given page during a presentation
+ if (isset($format['trans']['Dur'])) {
+ // The page's display duration
+ $this->pagedim[$this->page]['trans']['Dur'] = floatval($format['trans']['Dur']);
+ }
+ $stansition_styles = array('Split', 'Blinds', 'Box', 'Wipe', 'Dissolve', 'Glitter', 'R', 'Fly', 'Push', 'Cover', 'Uncover', 'Fade');
+ if (isset($format['trans']['S']) AND in_array($format['trans']['S'], $stansition_styles)) {
+ // The transition style that shall be used when moving to this page from another during a presentation
+ $this->pagedim[$this->page]['trans']['S'] = $format['trans']['S'];
+ $valid_effect = array('Split', 'Blinds');
+ $valid_vals = array('H', 'V');
+ if (isset($format['trans']['Dm']) AND in_array($format['trans']['S'], $valid_effect) AND in_array($format['trans']['Dm'], $valid_vals)) {
+ $this->pagedim[$this->page]['trans']['Dm'] = $format['trans']['Dm'];
+ }
+ $valid_effect = array('Split', 'Box', 'Fly');
+ $valid_vals = array('I', 'O');
+ if (isset($format['trans']['M']) AND in_array($format['trans']['S'], $valid_effect) AND in_array($format['trans']['M'], $valid_vals)) {
+ $this->pagedim[$this->page]['trans']['M'] = $format['trans']['M'];
+ }
+ $valid_effect = array('Wipe', 'Glitter', 'Fly', 'Cover', 'Uncover', 'Push');
+ if (isset($format['trans']['Di']) AND in_array($format['trans']['S'], $valid_effect)) {
+ if (((($format['trans']['Di'] == 90) OR ($format['trans']['Di'] == 180)) AND ($format['trans']['S'] == 'Wipe'))
+ OR (($format['trans']['Di'] == 315) AND ($format['trans']['S'] == 'Glitter'))
+ OR (($format['trans']['Di'] == 0) OR ($format['trans']['Di'] == 270))) {
+ $this->pagedim[$this->page]['trans']['Di'] = intval($format['trans']['Di']);
+ }
+ }
+ if (isset($format['trans']['SS']) AND ($format['trans']['S'] == 'Fly')) {
+ $this->pagedim[$this->page]['trans']['SS'] = floatval($format['trans']['SS']);
+ }
+ if (isset($format['trans']['B']) AND ($format['trans']['B'] === true) AND ($format['trans']['S'] == 'Fly')) {
+ $this->pagedim[$this->page]['trans']['B'] = 'true';
+ }
+ } else {
+ $this->pagedim[$this->page]['trans']['S'] = 'R';
+ }
+ if (isset($format['trans']['D'])) {
+ // The duration of the transition effect, in seconds
+ $this->pagedim[$this->page]['trans']['D'] = floatval($format['trans']['D']);
+ } else {
+ $this->pagedim[$this->page]['trans']['D'] = 1;
+ }
+ }
+ }
+ $this->setPageOrientation($orientation);
+ }
+
+ /**
+ * Set page orientation.
+ * @param $orientation (string) page orientation. Possible values are (case insensitive):<ul><li>P or Portrait (default)</li><li>L or Landscape</li><li>'' (empty string) for automatic orientation</li></ul>
+ * @param $autopagebreak (boolean) Boolean indicating if auto-page-break mode should be on or off.
+ * @param $bottommargin (float) bottom margin of the page.
+ * @public
+ * @since 3.0.015 (2008-06-06)
+ */
+ public function setPageOrientation($orientation, $autopagebreak='', $bottommargin='') {
+ if (!isset($this->pagedim[$this->page]['MediaBox'])) {
+ // the boundaries of the physical medium on which the page shall be displayed or printed
+ $this->pagedim = TCPDF_STATIC::setPageBoxes($this->page, 'MediaBox', 0, 0, $this->fwPt, $this->fhPt, true, $this->k, $this->pagedim);
+ }
+ if (!isset($this->pagedim[$this->page]['CropBox'])) {
+ // the visible region of default user space
+ $this->pagedim = TCPDF_STATIC::setPageBoxes($this->page, 'CropBox', $this->pagedim[$this->page]['MediaBox']['llx'], $this->pagedim[$this->page]['MediaBox']['lly'], $this->pagedim[$this->page]['MediaBox']['urx'], $this->pagedim[$this->page]['MediaBox']['ury'], true, $this->k, $this->pagedim);
+ }
+ if (!isset($this->pagedim[$this->page]['BleedBox'])) {
+ // the region to which the contents of the page shall be clipped when output in a production environment
+ $this->pagedim = TCPDF_STATIC::setPageBoxes($this->page, 'BleedBox', $this->pagedim[$this->page]['CropBox']['llx'], $this->pagedim[$this->page]['CropBox']['lly'], $this->pagedim[$this->page]['CropBox']['urx'], $this->pagedim[$this->page]['CropBox']['ury'], true, $this->k, $this->pagedim);
+ }
+ if (!isset($this->pagedim[$this->page]['TrimBox'])) {
+ // the intended dimensions of the finished page after trimming
+ $this->pagedim = TCPDF_STATIC::setPageBoxes($this->page, 'TrimBox', $this->pagedim[$this->page]['CropBox']['llx'], $this->pagedim[$this->page]['CropBox']['lly'], $this->pagedim[$this->page]['CropBox']['urx'], $this->pagedim[$this->page]['CropBox']['ury'], true, $this->k, $this->pagedim);
+ }
+ if (!isset($this->pagedim[$this->page]['ArtBox'])) {
+ // the page's meaningful content (including potential white space)
+ $this->pagedim = TCPDF_STATIC::setPageBoxes($this->page, 'ArtBox', $this->pagedim[$this->page]['CropBox']['llx'], $this->pagedim[$this->page]['CropBox']['lly'], $this->pagedim[$this->page]['CropBox']['urx'], $this->pagedim[$this->page]['CropBox']['ury'], true, $this->k, $this->pagedim);
+ }
+ if (!isset($this->pagedim[$this->page]['Rotate'])) {
+ // The number of degrees by which the page shall be rotated clockwise when displayed or printed. The value shall be a multiple of 90.
+ $this->pagedim[$this->page]['Rotate'] = 0;
+ }
+ if (!isset($this->pagedim[$this->page]['PZ'])) {
+ // The page's preferred zoom (magnification) factor
+ $this->pagedim[$this->page]['PZ'] = 1;
+ }
+ if ($this->fwPt > $this->fhPt) {
+ // landscape
+ $default_orientation = 'L';
+ } else {
+ // portrait
+ $default_orientation = 'P';
+ }
+ $valid_orientations = array('P', 'L');
+ if (empty($orientation)) {
+ $orientation = $default_orientation;
+ } else {
+ $orientation = strtoupper($orientation{0});
+ }
+ if (in_array($orientation, $valid_orientations) AND ($orientation != $default_orientation)) {
+ $this->CurOrientation = $orientation;
+ $this->wPt = $this->fhPt;
+ $this->hPt = $this->fwPt;
+ } else {
+ $this->CurOrientation = $default_orientation;
+ $this->wPt = $this->fwPt;
+ $this->hPt = $this->fhPt;
+ }
+ if ((abs($this->pagedim[$this->page]['MediaBox']['urx'] - $this->hPt) < $this->feps) AND (abs($this->pagedim[$this->page]['MediaBox']['ury'] - $this->wPt) < $this->feps)){
+ // swap X and Y coordinates (change page orientation)
+ $this->pagedim = TCPDF_STATIC::swapPageBoxCoordinates($this->page, $this->pagedim);
+ }
+ $this->w = ($this->wPt / $this->k);
+ $this->h = ($this->hPt / $this->k);
+ if (TCPDF_STATIC::empty_string($autopagebreak)) {
+ if (isset($this->AutoPageBreak)) {
+ $autopagebreak = $this->AutoPageBreak;
+ } else {
+ $autopagebreak = true;
+ }
+ }
+ if (TCPDF_STATIC::empty_string($bottommargin)) {
+ if (isset($this->bMargin)) {
+ $bottommargin = $this->bMargin;
+ } else {
+ // default value = 2 cm
+ $bottommargin = 2 * 28.35 / $this->k;
+ }
+ }
+ $this->SetAutoPageBreak($autopagebreak, $bottommargin);
+ // store page dimensions
+ $this->pagedim[$this->page]['w'] = $this->wPt;
+ $this->pagedim[$this->page]['h'] = $this->hPt;
+ $this->pagedim[$this->page]['wk'] = $this->w;
+ $this->pagedim[$this->page]['hk'] = $this->h;
+ $this->pagedim[$this->page]['tm'] = $this->tMargin;
+ $this->pagedim[$this->page]['bm'] = $bottommargin;
+ $this->pagedim[$this->page]['lm'] = $this->lMargin;
+ $this->pagedim[$this->page]['rm'] = $this->rMargin;
+ $this->pagedim[$this->page]['pb'] = $autopagebreak;
+ $this->pagedim[$this->page]['or'] = $this->CurOrientation;
+ $this->pagedim[$this->page]['olm'] = $this->original_lMargin;
+ $this->pagedim[$this->page]['orm'] = $this->original_rMargin;
+ }
+
+ /**
+ * Set regular expression to detect withespaces or word separators.
+ * The pattern delimiter must be the forward-slash character "/".
+ * Some example patterns are:
+ * <pre>
+ * Non-Unicode or missing PCRE unicode support: "/[^\S\xa0]/"
+ * Unicode and PCRE unicode support: "/[^\S\P{Z}\xa0]/u"
+ * Unicode and PCRE unicode support in Chinese mode: "/[^\S\P{Z}\P{Lo}\xa0]/u"
+ * if PCRE unicode support is turned ON ("\P" is the negate class of "\p"):
+ * "\p{Z}" or "\p{Separator}": any kind of Unicode whitespace or invisible separator.
+ * "\p{Lo}" or "\p{Other_Letter}": a Unicode letter or ideograph that does not have lowercase and uppercase variants.
+ * "\p{Lo}" is needed for Chinese characters because are packed next to each other without spaces in between.
+ * </pre>
+ * @param $re (string) regular expression (leave empty for default).
+ * @public
+ * @since 4.6.016 (2009-06-15)
+ */
+ public function setSpacesRE($re='/[^\S\xa0]/') {
+ $this->re_spaces = $re;
+ $re_parts = explode('/', $re);
+ // get pattern parts
+ $this->re_space = array();
+ if (isset($re_parts[1]) AND !empty($re_parts[1])) {
+ $this->re_space['p'] = $re_parts[1];
+ } else {
+ $this->re_space['p'] = '[\s]';
+ }
+ // set pattern modifiers
+ if (isset($re_parts[2]) AND !empty($re_parts[2])) {
+ $this->re_space['m'] = $re_parts[2];
+ } else {
+ $this->re_space['m'] = '';
+ }
+ }
+
+ /**
+ * Enable or disable Right-To-Left language mode
+ * @param $enable (Boolean) if true enable Right-To-Left language mode.
+ * @param $resetx (Boolean) if true reset the X position on direction change.
+ * @public
+ * @since 2.0.000 (2008-01-03)
+ */
+ public function setRTL($enable, $resetx=true) {
+ $enable = $enable ? true : false;
+ $resetx = ($resetx AND ($enable != $this->rtl));
+ $this->rtl = $enable;
+ $this->tmprtl = false;
+ if ($resetx) {
+ $this->Ln(0);
+ }
+ }
+
+ /**
+ * Return the RTL status
+ * @return boolean
+ * @public
+ * @since 4.0.012 (2008-07-24)
+ */
+ public function getRTL() {
+ return $this->rtl;
+ }
+
+ /**
+ * Force temporary RTL language direction
+ * @param $mode (mixed) can be false, 'L' for LTR or 'R' for RTL
+ * @public
+ * @since 2.1.000 (2008-01-09)
+ */
+ public function setTempRTL($mode) {
+ $newmode = false;
+ switch (strtoupper($mode)) {
+ case 'LTR':
+ case 'L': {
+ if ($this->rtl) {
+ $newmode = 'L';
+ }
+ break;
+ }
+ case 'RTL':
+ case 'R': {
+ if (!$this->rtl) {
+ $newmode = 'R';
+ }
+ break;
+ }
+ case false:
+ default: {
+ $newmode = false;
+ break;
+ }
+ }
+ $this->tmprtl = $newmode;
+ }
+
+ /**
+ * Return the current temporary RTL status
+ * @return boolean
+ * @public
+ * @since 4.8.014 (2009-11-04)
+ */
+ public function isRTLTextDir() {
+ return ($this->rtl OR ($this->tmprtl == 'R'));
+ }
+
+ /**
+ * Set the last cell height.
+ * @param $h (float) cell height.
+ * @author Nicola Asuni
+ * @public
+ * @since 1.53.0.TC034
+ */
+ public function setLastH($h) {
+ $this->lasth = $h;
+ }
+
+ /**
+ * Reset the last cell height.
+ * @public
+ * @since 5.9.000 (2010-10-03)
+ */
+ public function resetLastH() {
+ $this->lasth = ($this->FontSize * $this->cell_height_ratio) + $this->cell_padding['T'] + $this->cell_padding['B'];
+ }
+
+ /**
+ * Get the last cell height.
+ * @return last cell height
+ * @public
+ * @since 4.0.017 (2008-08-05)
+ */
+ public function getLastH() {
+ return $this->lasth;
+ }
+
+ /**
+ * Set the adjusting factor to convert pixels to user units.
+ * @param $scale (float) adjusting factor to convert pixels to user units.
+ * @author Nicola Asuni
+ * @public
+ * @since 1.5.2
+ */
+ public function setImageScale($scale) {
+ $this->imgscale = $scale;
+ }
+
+ /**
+ * Returns the adjusting factor to convert pixels to user units.
+ * @return float adjusting factor to convert pixels to user units.
+ * @author Nicola Asuni
+ * @public
+ * @since 1.5.2
+ */
+ public function getImageScale() {
+ return $this->imgscale;
+ }
+
+ /**
+ * Returns an array of page dimensions:
+ * <ul><li>$this->pagedim[$this->page]['w'] = page width in points</li><li>$this->pagedim[$this->page]['h'] = height in points</li><li>$this->pagedim[$this->page]['wk'] = page width in user units</li><li>$this->pagedim[$this->page]['hk'] = page height in user units</li><li>$this->pagedim[$this->page]['tm'] = top margin</li><li>$this->pagedim[$this->page]['bm'] = bottom margin</li><li>$this->pagedim[$this->page]['lm'] = left margin</li><li>$this->pagedim[$this->page]['rm'] = right margin</li><li>$this->pagedim[$this->page]['pb'] = auto page break</li><li>$this->pagedim[$this->page]['or'] = page orientation</li><li>$this->pagedim[$this->page]['olm'] = original left margin</li><li>$this->pagedim[$this->page]['orm'] = original right margin</li><li>$this->pagedim[$this->page]['Rotate'] = The number of degrees by which the page shall be rotated clockwise when displayed or printed. The value shall be a multiple of 90.</li><li>$this->pagedim[$this->page]['PZ'] = The page's preferred zoom (magnification) factor.</li><li>$this->pagedim[$this->page]['trans'] : the style and duration of the visual transition to use when moving from another page to the given page during a presentation<ul><li>$this->pagedim[$this->page]['trans']['Dur'] = The page's display duration (also called its advance timing): the maximum length of time, in seconds, that the page shall be displayed during presentations before the viewer application shall automatically advance to the next page.</li><li>$this->pagedim[$this->page]['trans']['S'] = transition style : Split, Blinds, Box, Wipe, Dissolve, Glitter, R, Fly, Push, Cover, Uncover, Fade</li><li>$this->pagedim[$this->page]['trans']['D'] = The duration of the transition effect, in seconds.</li><li>$this->pagedim[$this->page]['trans']['Dm'] = (Split and Blinds transition styles only) The dimension in which the specified transition effect shall occur: H = Horizontal, V = Vertical. Default value: H.</li><li>$this->pagedim[$this->page]['trans']['M'] = (Split, Box and Fly transition styles only) The direction of motion for the specified transition effect: I = Inward from the edges of the page, O = Outward from the center of the pageDefault value: I.</li><li>$this->pagedim[$this->page]['trans']['Di'] = (Wipe, Glitter, Fly, Cover, Uncover and Push transition styles only) The direction in which the specified transition effect shall moves, expressed in degrees counterclockwise starting from a left-to-right direction. If the value is a number, it shall be one of: 0 = Left to right, 90 = Bottom to top (Wipe only), 180 = Right to left (Wipe only), 270 = Top to bottom, 315 = Top-left to bottom-right (Glitter only). If the value is a name, it shall be None, which is relevant only for the Fly transition when the value of SS is not 1.0. Default value: 0.</li><li>$this->pagedim[$this->page]['trans']['SS'] = (Fly transition style only) The starting or ending scale at which the changes shall be drawn. If M specifies an inward transition, the scale of the changes drawn shall progress from SS to 1.0 over the course of the transition. If M specifies an outward transition, the scale of the changes drawn shall progress from 1.0 to SS over the course of the transition. Default: 1.0. </li><li>$this->pagedim[$this->page]['trans']['B'] = (Fly transition style only) If true, the area that shall be flown in is rectangular and opaque. Default: false.</li></ul></li><li>$this->pagedim[$this->page]['MediaBox'] : the boundaries of the physical medium on which the page shall be displayed or printed<ul><li>$this->pagedim[$this->page]['MediaBox']['llx'] = lower-left x coordinate in points</li><li>$this->pagedim[$this->page]['MediaBox']['lly'] = lower-left y coordinate in points</li><li>$this->pagedim[$this->page]['MediaBox']['urx'] = upper-right x coordinate in points</li><li>$this->pagedim[$this->page]['MediaBox']['ury'] = upper-right y coordinate in points</li></ul></li><li>$this->pagedim[$this->page]['CropBox'] : the visible region of default user space<ul><li>$this->pagedim[$this->page]['CropBox']['llx'] = lower-left x coordinate in points</li><li>$this->pagedim[$this->page]['CropBox']['lly'] = lower-left y coordinate in points</li><li>$this->pagedim[$this->page]['CropBox']['urx'] = upper-right x coordinate in points</li><li>$this->pagedim[$this->page]['CropBox']['ury'] = upper-right y coordinate in points</li></ul></li><li>$this->pagedim[$this->page]['BleedBox'] : the region to which the contents of the page shall be clipped when output in a production environment<ul><li>$this->pagedim[$this->page]['BleedBox']['llx'] = lower-left x coordinate in points</li><li>$this->pagedim[$this->page]['BleedBox']['lly'] = lower-left y coordinate in points</li><li>$this->pagedim[$this->page]['BleedBox']['urx'] = upper-right x coordinate in points</li><li>$this->pagedim[$this->page]['BleedBox']['ury'] = upper-right y coordinate in points</li></ul></li><li>$this->pagedim[$this->page]['TrimBox'] : the intended dimensions of the finished page after trimming<ul><li>$this->pagedim[$this->page]['TrimBox']['llx'] = lower-left x coordinate in points</li><li>$this->pagedim[$this->page]['TrimBox']['lly'] = lower-left y coordinate in points</li><li>$this->pagedim[$this->page]['TrimBox']['urx'] = upper-right x coordinate in points</li><li>$this->pagedim[$this->page]['TrimBox']['ury'] = upper-right y coordinate in points</li></ul></li><li>$this->pagedim[$this->page]['ArtBox'] : the extent of the page's meaningful content<ul><li>$this->pagedim[$this->page]['ArtBox']['llx'] = lower-left x coordinate in points</li><li>$this->pagedim[$this->page]['ArtBox']['lly'] = lower-left y coordinate in points</li><li>$this->pagedim[$this->page]['ArtBox']['urx'] = upper-right x coordinate in points</li><li>$this->pagedim[$this->page]['ArtBox']['ury'] = upper-right y coordinate in points</li></ul></li></ul>
+ * @param $pagenum (int) page number (empty = current page)
+ * @return array of page dimensions.
+ * @author Nicola Asuni
+ * @public
+ * @since 4.5.027 (2009-03-16)
+ */
+ public function getPageDimensions($pagenum='') {
+ if (empty($pagenum)) {
+ $pagenum = $this->page;
+ }
+ return $this->pagedim[$pagenum];
+ }
+
+ /**
+ * Returns the page width in units.
+ * @param $pagenum (int) page number (empty = current page)
+ * @return int page width.
+ * @author Nicola Asuni
+ * @public
+ * @since 1.5.2
+ * @see getPageDimensions()
+ */
+ public function getPageWidth($pagenum='') {
+ if (empty($pagenum)) {
+ return $this->w;
+ }
+ return $this->pagedim[$pagenum]['w'];
+ }
+
+ /**
+ * Returns the page height in units.
+ * @param $pagenum (int) page number (empty = current page)
+ * @return int page height.
+ * @author Nicola Asuni
+ * @public
+ * @since 1.5.2
+ * @see getPageDimensions()
+ */
+ public function getPageHeight($pagenum='') {
+ if (empty($pagenum)) {
+ return $this->h;
+ }
+ return $this->pagedim[$pagenum]['h'];
+ }
+
+ /**
+ * Returns the page break margin.
+ * @param $pagenum (int) page number (empty = current page)
+ * @return int page break margin.
+ * @author Nicola Asuni
+ * @public
+ * @since 1.5.2
+ * @see getPageDimensions()
+ */
+ public function getBreakMargin($pagenum='') {
+ if (empty($pagenum)) {
+ return $this->bMargin;
+ }
+ return $this->pagedim[$pagenum]['bm'];
+ }
+
+ /**
+ * Returns the scale factor (number of points in user unit).
+ * @return int scale factor.
+ * @author Nicola Asuni
+ * @public
+ * @since 1.5.2
+ */
+ public function getScaleFactor() {
+ return $this->k;
+ }
+
+ /**
+ * Defines the left, top and right margins.
+ * @param $left (float) Left margin.
+ * @param $top (float) Top margin.
+ * @param $right (float) Right margin. Default value is the left one.
+ * @param $keepmargins (boolean) if true overwrites the default page margins
+ * @public
+ * @since 1.0
+ * @see SetLeftMargin(), SetTopMargin(), SetRightMargin(), SetAutoPageBreak()
+ */
+ public function SetMargins($left, $top, $right=-1, $keepmargins=false) {
+ //Set left, top and right margins
+ $this->lMargin = $left;
+ $this->tMargin = $top;
+ if ($right == -1) {
+ $right = $left;
+ }
+ $this->rMargin = $right;
+ if ($keepmargins) {
+ // overwrite original values
+ $this->original_lMargin = $this->lMargin;
+ $this->original_rMargin = $this->rMargin;
+ }
+ }
+
+ /**
+ * Defines the left margin. The method can be called before creating the first page. If the current abscissa gets out of page, it is brought back to the margin.
+ * @param $margin (float) The margin.
+ * @public
+ * @since 1.4
+ * @see SetTopMargin(), SetRightMargin(), SetAutoPageBreak(), SetMargins()
+ */
+ public function SetLeftMargin($margin) {
+ //Set left margin
+ $this->lMargin = $margin;
+ if (($this->page > 0) AND ($this->x < $margin)) {
+ $this->x = $margin;
+ }
+ }
+
+ /**
+ * Defines the top margin. The method can be called before creating the first page.
+ * @param $margin (float) The margin.
+ * @public
+ * @since 1.5
+ * @see SetLeftMargin(), SetRightMargin(), SetAutoPageBreak(), SetMargins()
+ */
+ public function SetTopMargin($margin) {
+ //Set top margin
+ $this->tMargin = $margin;
+ if (($this->page > 0) AND ($this->y < $margin)) {
+ $this->y = $margin;
+ }
+ }
+
+ /**
+ * Defines the right margin. The method can be called before creating the first page.
+ * @param $margin (float) The margin.
+ * @public
+ * @since 1.5
+ * @see SetLeftMargin(), SetTopMargin(), SetAutoPageBreak(), SetMargins()
+ */
+ public function SetRightMargin($margin) {
+ $this->rMargin = $margin;
+ if (($this->page > 0) AND ($this->x > ($this->w - $margin))) {
+ $this->x = $this->w - $margin;
+ }
+ }
+
+ /**
+ * Set the same internal Cell padding for top, right, bottom, left-
+ * @param $pad (float) internal padding.
+ * @public
+ * @since 2.1.000 (2008-01-09)
+ * @see getCellPaddings(), setCellPaddings()
+ */
+ public function SetCellPadding($pad) {
+ if ($pad >= 0) {
+ $this->cell_padding['L'] = $pad;
+ $this->cell_padding['T'] = $pad;
+ $this->cell_padding['R'] = $pad;
+ $this->cell_padding['B'] = $pad;
+ }
+ }
+
+ /**
+ * Set the internal Cell paddings.
+ * @param $left (float) left padding
+ * @param $top (float) top padding
+ * @param $right (float) right padding
+ * @param $bottom (float) bottom padding
+ * @public
+ * @since 5.9.000 (2010-10-03)
+ * @see getCellPaddings(), SetCellPadding()
+ */
+ public function setCellPaddings($left='', $top='', $right='', $bottom='') {
+ if (($left !== '') AND ($left >= 0)) {
+ $this->cell_padding['L'] = $left;
+ }
+ if (($top !== '') AND ($top >= 0)) {
+ $this->cell_padding['T'] = $top;
+ }
+ if (($right !== '') AND ($right >= 0)) {
+ $this->cell_padding['R'] = $right;
+ }
+ if (($bottom !== '') AND ($bottom >= 0)) {
+ $this->cell_padding['B'] = $bottom;
+ }
+ }
+
+ /**
+ * Get the internal Cell padding array.
+ * @return array of padding values
+ * @public
+ * @since 5.9.000 (2010-10-03)
+ * @see setCellPaddings(), SetCellPadding()
+ */
+ public function getCellPaddings() {
+ return $this->cell_padding;
+ }
+
+ /**
+ * Set the internal Cell margins.
+ * @param $left (float) left margin
+ * @param $top (float) top margin
+ * @param $right (float) right margin
+ * @param $bottom (float) bottom margin
+ * @public
+ * @since 5.9.000 (2010-10-03)
+ * @see getCellMargins()
+ */
+ public function setCellMargins($left='', $top='', $right='', $bottom='') {
+ if (($left !== '') AND ($left >= 0)) {
+ $this->cell_margin['L'] = $left;
+ }
+ if (($top !== '') AND ($top >= 0)) {
+ $this->cell_margin['T'] = $top;
+ }
+ if (($right !== '') AND ($right >= 0)) {
+ $this->cell_margin['R'] = $right;
+ }
+ if (($bottom !== '') AND ($bottom >= 0)) {
+ $this->cell_margin['B'] = $bottom;
+ }
+ }
+
+ /**
+ * Get the internal Cell margin array.
+ * @return array of margin values
+ * @public
+ * @since 5.9.000 (2010-10-03)
+ * @see setCellMargins()
+ */
+ public function getCellMargins() {
+ return $this->cell_margin;
+ }
+
+ /**
+ * Adjust the internal Cell padding array to take account of the line width.
+ * @param $brd (mixed) Indicates if borders must be drawn around the cell. The value can be a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul> or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul> or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)))
+ * @return array of adjustments
+ * @public
+ * @since 5.9.000 (2010-10-03)
+ */
+ protected function adjustCellPadding($brd=0) {
+ if (empty($brd)) {
+ return;
+ }
+ if (is_string($brd)) {
+ // convert string to array
+ $slen = strlen($brd);
+ $newbrd = array();
+ for ($i = 0; $i < $slen; ++$i) {
+ $newbrd[$brd[$i]] = true;
+ }
+ $brd = $newbrd;
+ } elseif (($brd === 1) OR ($brd === true) OR (is_numeric($brd) AND (intval($brd) > 0))) {
+ $brd = array('LRTB' => true);
+ }
+ if (!is_array($brd)) {
+ return;
+ }
+ // store current cell padding
+ $cp = $this->cell_padding;
+ // select border mode
+ if (isset($brd['mode'])) {
+ $mode = $brd['mode'];
+ unset($brd['mode']);
+ } else {
+ $mode = 'normal';
+ }
+ // process borders
+ foreach ($brd as $border => $style) {
+ $line_width = $this->LineWidth;
+ if (is_array($style) AND isset($style['width'])) {
+ // get border width
+ $line_width = $style['width'];
+ }
+ $adj = 0; // line width inside the cell
+ switch ($mode) {
+ case 'ext': {
+ $adj = 0;
+ break;
+ }
+ case 'int': {
+ $adj = $line_width;
+ break;
+ }
+ case 'normal':
+ default: {
+ $adj = ($line_width / 2);
+ break;
+ }
+ }
+ // correct internal cell padding if required to avoid overlap between text and lines
+ if ((strpos($border,'T') !== false) AND ($this->cell_padding['T'] < $adj)) {
+ $this->cell_padding['T'] = $adj;
+ }
+ if ((strpos($border,'R') !== false) AND ($this->cell_padding['R'] < $adj)) {
+ $this->cell_padding['R'] = $adj;
+ }
+ if ((strpos($border,'B') !== false) AND ($this->cell_padding['B'] < $adj)) {
+ $this->cell_padding['B'] = $adj;
+ }
+ if ((strpos($border,'L') !== false) AND ($this->cell_padding['L'] < $adj)) {
+ $this->cell_padding['L'] = $adj;
+ }
+ }
+ return array('T' => ($this->cell_padding['T'] - $cp['T']), 'R' => ($this->cell_padding['R'] - $cp['R']), 'B' => ($this->cell_padding['B'] - $cp['B']), 'L' => ($this->cell_padding['L'] - $cp['L']));
+ }
+
+ /**
+ * Enables or disables the automatic page breaking mode. When enabling, the second parameter is the distance from the bottom of the page that defines the triggering limit. By default, the mode is on and the margin is 2 cm.
+ * @param $auto (boolean) Boolean indicating if mode should be on or off.
+ * @param $margin (float) Distance from the bottom of the page.
+ * @public
+ * @since 1.0
+ * @see Cell(), MultiCell(), AcceptPageBreak()
+ */
+ public function SetAutoPageBreak($auto, $margin=0) {
+ $this->AutoPageBreak = $auto ? true : false;
+ $this->bMargin = $margin;
+ $this->PageBreakTrigger = $this->h - $margin;
+ }
+
+ /**
+ * Return the auto-page-break mode (true or false).
+ * @return boolean auto-page-break mode
+ * @public
+ * @since 5.9.088
+ */
+ public function getAutoPageBreak() {
+ return $this->AutoPageBreak;
+ }
+
+ /**
+ * Defines the way the document is to be displayed by the viewer.
+ * @param $zoom (mixed) The zoom to use. It can be one of the following string values or a number indicating the zooming factor to use. <ul><li>fullpage: displays the entire page on screen </li><li>fullwidth: uses maximum width of window</li><li>real: uses real size (equivalent to 100% zoom)</li><li>default: uses viewer default mode</li></ul>
+ * @param $layout (string) The page layout. Possible values are:<ul><li>SinglePage Display one page at a time</li><li>OneColumn Display the pages in one column</li><li>TwoColumnLeft Display the pages in two columns, with odd-numbered pages on the left</li><li>TwoColumnRight Display the pages in two columns, with odd-numbered pages on the right</li><li>TwoPageLeft (PDF 1.5) Display the pages two at a time, with odd-numbered pages on the left</li><li>TwoPageRight (PDF 1.5) Display the pages two at a time, with odd-numbered pages on the right</li></ul>
+ * @param $mode (string) A name object specifying how the document should be displayed when opened:<ul><li>UseNone Neither document outline nor thumbnail images visible</li><li>UseOutlines Document outline visible</li><li>UseThumbs Thumbnail images visible</li><li>FullScreen Full-screen mode, with no menu bar, window controls, or any other window visible</li><li>UseOC (PDF 1.5) Optional content group panel visible</li><li>UseAttachments (PDF 1.6) Attachments panel visible</li></ul>
+ * @public
+ * @since 1.2
+ */
+ public function SetDisplayMode($zoom, $layout='SinglePage', $mode='UseNone') {
+ if (($zoom == 'fullpage') OR ($zoom == 'fullwidth') OR ($zoom == 'real') OR ($zoom == 'default') OR (!is_string($zoom))) {
+ $this->ZoomMode = $zoom;
+ } else {
+ $this->Error('Incorrect zoom display mode: '.$zoom);
+ }
+ $this->LayoutMode = TCPDF_STATIC::getPageLayoutMode($layout);
+ $this->PageMode = TCPDF_STATIC::getPageMode($mode);
+ }
+
+ /**
+ * Activates or deactivates page compression. When activated, the internal representation of each page is compressed, which leads to a compression ratio of about 2 for the resulting document. Compression is on by default.
+ * Note: the Zlib extension is required for this feature. If not present, compression will be turned off.
+ * @param $compress (boolean) Boolean indicating if compression must be enabled.
+ * @public
+ * @since 1.4
+ */
+ public function SetCompression($compress=true) {
+ if (function_exists('gzcompress')) {
+ $this->compress = $compress ? true : false;
+ } else {
+ $this->compress = false;
+ }
+ }
+
+ /**
+ * Set flag to force sRGB_IEC61966-2.1 black scaled ICC color profile for the whole document.
+ * @param $mode (boolean) If true force sRGB output intent.
+ * @public
+ * @since 5.9.121 (2011-09-28)
+ */
+ public function setSRGBmode($mode=false) {
+ $this->force_srgb = $mode ? true : false;
+ }
+
+ /**
+ * Turn on/off Unicode mode for document information dictionary (meta tags).
+ * This has effect only when unicode mode is set to false.
+ * @param $unicode (boolean) if true set the meta information in Unicode
+ * @since 5.9.027 (2010-12-01)
+ * @public
+ */
+ public function SetDocInfoUnicode($unicode=true) {
+ $this->docinfounicode = $unicode ? true : false;
+ }
+
+ /**
+ * Defines the title of the document.
+ * @param $title (string) The title.
+ * @public
+ * @since 1.2
+ * @see SetAuthor(), SetCreator(), SetKeywords(), SetSubject()
+ */
+ public function SetTitle($title) {
+ $this->title = $title;
+ }
+
+ /**
+ * Defines the subject of the document.
+ * @param $subject (string) The subject.
+ * @public
+ * @since 1.2
+ * @see SetAuthor(), SetCreator(), SetKeywords(), SetTitle()
+ */
+ public function SetSubject($subject) {
+ $this->subject = $subject;
+ }
+
+ /**
+ * Defines the author of the document.
+ * @param $author (string) The name of the author.
+ * @public
+ * @since 1.2
+ * @see SetCreator(), SetKeywords(), SetSubject(), SetTitle()
+ */
+ public function SetAuthor($author) {
+ $this->author = $author;
+ }
+
+ /**
+ * Associates keywords with the document, generally in the form 'keyword1 keyword2 ...'.
+ * @param $keywords (string) The list of keywords.
+ * @public
+ * @since 1.2
+ * @see SetAuthor(), SetCreator(), SetSubject(), SetTitle()
+ */
+ public function SetKeywords($keywords) {
+ $this->keywords = $keywords;
+ }
+
+ /**
+ * Defines the creator of the document. This is typically the name of the application that generates the PDF.
+ * @param $creator (string) The name of the creator.
+ * @public
+ * @since 1.2
+ * @see SetAuthor(), SetKeywords(), SetSubject(), SetTitle()
+ */
+ public function SetCreator($creator) {
+ $this->creator = $creator;
+ }
+
+ /**
+ * Throw an exception or print an error message and die if the K_TCPDF_PARSER_THROW_EXCEPTION_ERROR constant is set to true.
+ * @param $msg (string) The error message
+ * @public
+ * @since 1.0
+ */
+ public function Error($msg) {
+ // unset all class variables
+ $this->_destroy(true);
+ if (defined('K_TCPDF_THROW_EXCEPTION_ERROR') AND !K_TCPDF_THROW_EXCEPTION_ERROR) {
+ die('<strong>TCPDF ERROR: </strong>'.$msg);
+ } else {
+ throw new Exception('TCPDF ERROR: '.$msg);
+ }
+ }
+
+ /**
+ * This method begins the generation of the PDF document.
+ * It is not necessary to call it explicitly because AddPage() does it automatically.
+ * Note: no page is created by this method
+ * @public
+ * @since 1.0
+ * @see AddPage(), Close()
+ */
+ public function Open() {
+ $this->state = 1;
+ }
+
+ /**
+ * Terminates the PDF document.
+ * It is not necessary to call this method explicitly because Output() does it automatically.
+ * If the document contains no page, AddPage() is called to prevent from getting an invalid document.
+ * @public
+ * @since 1.0
+ * @see Open(), Output()
+ */
+ public function Close() {
+ if ($this->state == 3) {
+ return;
+ }
+ if ($this->page == 0) {
+ $this->AddPage();
+ }
+ $this->endLayer();
+ if ($this->tcpdflink) {
+ // save current graphic settings
+ $gvars = $this->getGraphicVars();
+ $this->setEqualColumns();
+ $this->lastpage(true);
+ $this->SetAutoPageBreak(false);
+ $this->x = 0;
+ $this->y = $this->h - (1 / $this->k);
+ $this->lMargin = 0;
+ $this->_out('q');
+ $font = defined('PDF_FONT_NAME_MAIN')?PDF_FONT_NAME_MAIN:'helvetica';
+ $this->SetFont($font, '', 1);
+ $this->setTextRenderingMode(0, false, false);
+ $msg = "\x50\x6f\x77\x65\x72\x65\x64\x20\x62\x79\x20\x54\x43\x50\x44\x46\x20\x28\x77\x77\x77\x2e\x74\x63\x70\x64\x66\x2e\x6f\x72\x67\x29";
+ $lnk = "\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x74\x63\x70\x64\x66\x2e\x6f\x72\x67";
+ $this->Cell(0, 0, $msg, 0, 0, 'L', 0, $lnk, 0, false, 'D', 'B');
+ $this->_out('Q');
+ // restore graphic settings
+ $this->setGraphicVars($gvars);
+ }
+ // close page
+ $this->endPage();
+ // close document
+ $this->_enddoc();
+ // unset all class variables (except critical ones)
+ $this->_destroy(false);
+ }
+
+ /**
+ * Move pointer at the specified document page and update page dimensions.
+ * @param $pnum (int) page number (1 ... numpages)
+ * @param $resetmargins (boolean) if true reset left, right, top margins and Y position.
+ * @public
+ * @since 2.1.000 (2008-01-07)
+ * @see getPage(), lastpage(), getNumPages()
+ */
+ public function setPage($pnum, $resetmargins=false) {
+ if (($pnum == $this->page) AND ($this->state == 2)) {
+ return;
+ }
+ if (($pnum > 0) AND ($pnum <= $this->numpages)) {
+ $this->state = 2;
+ // save current graphic settings
+ //$gvars = $this->getGraphicVars();
+ $oldpage = $this->page;
+ $this->page = $pnum;
+ $this->wPt = $this->pagedim[$this->page]['w'];
+ $this->hPt = $this->pagedim[$this->page]['h'];
+ $this->w = $this->pagedim[$this->page]['wk'];
+ $this->h = $this->pagedim[$this->page]['hk'];
+ $this->tMargin = $this->pagedim[$this->page]['tm'];
+ $this->bMargin = $this->pagedim[$this->page]['bm'];
+ $this->original_lMargin = $this->pagedim[$this->page]['olm'];
+ $this->original_rMargin = $this->pagedim[$this->page]['orm'];
+ $this->AutoPageBreak = $this->pagedim[$this->page]['pb'];
+ $this->CurOrientation = $this->pagedim[$this->page]['or'];
+ $this->SetAutoPageBreak($this->AutoPageBreak, $this->bMargin);
+ // restore graphic settings
+ //$this->setGraphicVars($gvars);
+ if ($resetmargins) {
+ $this->lMargin = $this->pagedim[$this->page]['olm'];
+ $this->rMargin = $this->pagedim[$this->page]['orm'];
+ $this->SetY($this->tMargin);
+ } else {
+ // account for booklet mode
+ if ($this->pagedim[$this->page]['olm'] != $this->pagedim[$oldpage]['olm']) {
+ $deltam = $this->pagedim[$this->page]['olm'] - $this->pagedim[$this->page]['orm'];
+ $this->lMargin += $deltam;
+ $this->rMargin -= $deltam;
+ }
+ }
+ } else {
+ $this->Error('Wrong page number on setPage() function: '.$pnum);
+ }
+ }
+
+ /**
+ * Reset pointer to the last document page.
+ * @param $resetmargins (boolean) if true reset left, right, top margins and Y position.
+ * @public
+ * @since 2.0.000 (2008-01-04)
+ * @see setPage(), getPage(), getNumPages()
+ */
+ public function lastPage($resetmargins=false) {
+ $this->setPage($this->getNumPages(), $resetmargins);
+ }
+
+ /**
+ * Get current document page number.
+ * @return int page number
+ * @public
+ * @since 2.1.000 (2008-01-07)
+ * @see setPage(), lastpage(), getNumPages()
+ */
+ public function getPage() {
+ return $this->page;
+ }
+
+ /**
+ * Get the total number of insered pages.
+ * @return int number of pages
+ * @public
+ * @since 2.1.000 (2008-01-07)
+ * @see setPage(), getPage(), lastpage()
+ */
+ public function getNumPages() {
+ return $this->numpages;
+ }
+
+ /**
+ * Adds a new TOC (Table Of Content) page to the document.
+ * @param $orientation (string) page orientation.
+ * @param $format (mixed) The format used for pages. It can be either: one of the string values specified at getPageSizeFromFormat() or an array of parameters specified at setPageFormat().
+ * @param $keepmargins (boolean) if true overwrites the default page margins with the current margins
+ * @public
+ * @since 5.0.001 (2010-05-06)
+ * @see AddPage(), startPage(), endPage(), endTOCPage()
+ */
+ public function addTOCPage($orientation='', $format='', $keepmargins=false) {
+ $this->AddPage($orientation, $format, $keepmargins, true);
+ }
+
+ /**
+ * Terminate the current TOC (Table Of Content) page
+ * @public
+ * @since 5.0.001 (2010-05-06)
+ * @see AddPage(), startPage(), endPage(), addTOCPage()
+ */
+ public function endTOCPage() {
+ $this->endPage(true);
+ }
+
+ /**
+ * Adds a new page to the document. If a page is already present, the Footer() method is called first to output the footer (if enabled). Then the page is added, the current position set to the top-left corner according to the left and top margins (or top-right if in RTL mode), and Header() is called to display the header (if enabled).
+ * The origin of the coordinate system is at the top-left corner (or top-right for RTL) and increasing ordinates go downwards.
+ * @param $orientation (string) page orientation. Possible values are (case insensitive):<ul><li>P or PORTRAIT (default)</li><li>L or LANDSCAPE</li></ul>
+ * @param $format (mixed) The format used for pages. It can be either: one of the string values specified at getPageSizeFromFormat() or an array of parameters specified at setPageFormat().
+ * @param $keepmargins (boolean) if true overwrites the default page margins with the current margins
+ * @param $tocpage (boolean) if true set the tocpage state to true (the added page will be used to display Table Of Content).
+ * @public
+ * @since 1.0
+ * @see startPage(), endPage(), addTOCPage(), endTOCPage(), getPageSizeFromFormat(), setPageFormat()
+ */
+ public function AddPage($orientation='', $format='', $keepmargins=false, $tocpage=false) {
+ if ($this->inxobj) {
+ // we are inside an XObject template
+ return;
+ }
+ if (!isset($this->original_lMargin) OR $keepmargins) {
+ $this->original_lMargin = $this->lMargin;
+ }
+ if (!isset($this->original_rMargin) OR $keepmargins) {
+ $this->original_rMargin = $this->rMargin;
+ }
+ // terminate previous page
+ $this->endPage();
+ // start new page
+ $this->startPage($orientation, $format, $tocpage);
+ }
+
+ /**
+ * Terminate the current page
+ * @param $tocpage (boolean) if true set the tocpage state to false (end the page used to display Table Of Content).
+ * @public
+ * @since 4.2.010 (2008-11-14)
+ * @see AddPage(), startPage(), addTOCPage(), endTOCPage()
+ */
+ public function endPage($tocpage=false) {
+ // check if page is already closed
+ if (($this->page == 0) OR ($this->numpages > $this->page) OR (!$this->pageopen[$this->page])) {
+ return;
+ }
+ // print page footer
+ $this->setFooter();
+ // close page
+ $this->_endpage();
+ // mark page as closed
+ $this->pageopen[$this->page] = false;
+ if ($tocpage) {
+ $this->tocpage = false;
+ }
+ }
+
+ /**
+ * Starts a new page to the document. The page must be closed using the endPage() function.
+ * The origin of the coordinate system is at the top-left corner and increasing ordinates go downwards.
+ * @param $orientation (string) page orientation. Possible values are (case insensitive):<ul><li>P or PORTRAIT (default)</li><li>L or LANDSCAPE</li></ul>
+ * @param $format (mixed) The format used for pages. It can be either: one of the string values specified at getPageSizeFromFormat() or an array of parameters specified at setPageFormat().
+ * @param $tocpage (boolean) if true the page is designated to contain the Table-Of-Content.
+ * @since 4.2.010 (2008-11-14)
+ * @see AddPage(), endPage(), addTOCPage(), endTOCPage(), getPageSizeFromFormat(), setPageFormat()
+ * @public
+ */
+ public function startPage($orientation='', $format='', $tocpage=false) {
+ if ($tocpage) {
+ $this->tocpage = true;
+ }
+ // move page numbers of documents to be attached
+ if ($this->tocpage) {
+ // move reference to unexistent pages (used for page attachments)
+ // adjust outlines
+ $tmpoutlines = $this->outlines;
+ foreach ($tmpoutlines as $key => $outline) {
+ if ($outline['p'] > $this->numpages) {
+ $this->outlines[$key]['p'] = ($outline['p'] + 1);
+ }
+ }
+ // adjust dests
+ $tmpdests = $this->dests;
+ foreach ($tmpdests as $key => $dest) {
+ if ($dest['p'] > $this->numpages) {
+ $this->dests[$key]['p'] = ($dest['p'] + 1);
+ }
+ }
+ // adjust links
+ $tmplinks = $this->links;
+ foreach ($tmplinks as $key => $link) {
+ if ($link[0] > $this->numpages) {
+ $this->links[$key][0] = ($link[0] + 1);
+ }
+ }
+ }
+ if ($this->numpages > $this->page) {
+ // this page has been already added
+ $this->setPage($this->page + 1);
+ $this->SetY($this->tMargin);
+ return;
+ }
+ // start a new page
+ if ($this->state == 0) {
+ $this->Open();
+ }
+ ++$this->numpages;
+ $this->swapMargins($this->booklet);
+ // save current graphic settings
+ $gvars = $this->getGraphicVars();
+ // start new page
+ $this->_beginpage($orientation, $format);
+ // mark page as open
+ $this->pageopen[$this->page] = true;
+ // restore graphic settings
+ $this->setGraphicVars($gvars);
+ // mark this point
+ $this->setPageMark();
+ // print page header
+ $this->setHeader();
+ // restore graphic settings
+ $this->setGraphicVars($gvars);
+ // mark this point
+ $this->setPageMark();
+ // print table header (if any)
+ $this->setTableHeader();
+ // set mark for empty page check
+ $this->emptypagemrk[$this->page]= $this->pagelen[$this->page];
+ }
+
+ /**
+ * Set start-writing mark on current page stream used to put borders and fills.
+ * Borders and fills are always created after content and inserted on the position marked by this method.
+ * This function must be called after calling Image() function for a background image.
+ * Background images must be always inserted before calling Multicell() or WriteHTMLCell() or WriteHTML() functions.
+ * @public
+ * @since 4.0.016 (2008-07-30)
+ */
+ public function setPageMark() {
+ $this->intmrk[$this->page] = $this->pagelen[$this->page];
+ $this->bordermrk[$this->page] = $this->intmrk[$this->page];
+ $this->setContentMark();
+ }
+
+ /**
+ * Set start-writing mark on selected page.
+ * Borders and fills are always created after content and inserted on the position marked by this method.
+ * @param $page (int) page number (default is the current page)
+ * @protected
+ * @since 4.6.021 (2009-07-20)
+ */
+ protected function setContentMark($page=0) {
+ if ($page <= 0) {
+ $page = $this->page;
+ }
+ if (isset($this->footerlen[$page])) {
+ $this->cntmrk[$page] = $this->pagelen[$page] - $this->footerlen[$page];
+ } else {
+ $this->cntmrk[$page] = $this->pagelen[$page];
+ }
+ }
+
+ /**
+ * Set header data.
+ * @param $ln (string) header image logo
+ * @param $lw (string) header image logo width in mm
+ * @param $ht (string) string to print as title on document header
+ * @param $hs (string) string to print on document header
+ * @param $tc (array) RGB array color for text.
+ * @param $lc (array) RGB array color for line.
+ * @public
+ */
+ public function setHeaderData($ln='', $lw=0, $ht='', $hs='', $tc=array(0,0,0), $lc=array(0,0,0)) {
+ $this->header_logo = $ln;
+ $this->header_logo_width = $lw;
+ $this->header_title = $ht;
+ $this->header_string = $hs;
+ $this->header_text_color = $tc;
+ $this->header_line_color = $lc;
+ }
+
+ /**
+ * Set footer data.
+ * @param $tc (array) RGB array color for text.
+ * @param $lc (array) RGB array color for line.
+ * @public
+ */
+ public function setFooterData($tc=array(0,0,0), $lc=array(0,0,0)) {
+ $this->footer_text_color = $tc;
+ $this->footer_line_color = $lc;
+ }
+
+ /**
+ * Returns header data:
+ * <ul><li>$ret['logo'] = logo image</li><li>$ret['logo_width'] = width of the image logo in user units</li><li>$ret['title'] = header title</li><li>$ret['string'] = header description string</li></ul>
+ * @return array()
+ * @public
+ * @since 4.0.012 (2008-07-24)
+ */
+ public function getHeaderData() {
+ $ret = array();
+ $ret['logo'] = $this->header_logo;
+ $ret['logo_width'] = $this->header_logo_width;
+ $ret['title'] = $this->header_title;
+ $ret['string'] = $this->header_string;
+ $ret['text_color'] = $this->header_text_color;
+ $ret['line_color'] = $this->header_line_color;
+ return $ret;
+ }
+
+ /**
+ * Set header margin.
+ * (minimum distance between header and top page margin)
+ * @param $hm (int) distance in user units
+ * @public
+ */
+ public function setHeaderMargin($hm=10) {
+ $this->header_margin = $hm;
+ }
+
+ /**
+ * Returns header margin in user units.
+ * @return float
+ * @since 4.0.012 (2008-07-24)
+ * @public
+ */
+ public function getHeaderMargin() {
+ return $this->header_margin;
+ }
+
+ /**
+ * Set footer margin.
+ * (minimum distance between footer and bottom page margin)
+ * @param $fm (int) distance in user units
+ * @public
+ */
+ public function setFooterMargin($fm=10) {
+ $this->footer_margin = $fm;
+ }
+
+ /**
+ * Returns footer margin in user units.
+ * @return float
+ * @since 4.0.012 (2008-07-24)
+ * @public
+ */
+ public function getFooterMargin() {
+ return $this->footer_margin;
+ }
+ /**
+ * Set a flag to print page header.
+ * @param $val (boolean) set to true to print the page header (default), false otherwise.
+ * @public
+ */
+ public function setPrintHeader($val=true) {
+ $this->print_header = $val ? true : false;
+ }
+
+ /**
+ * Set a flag to print page footer.
+ * @param $val (boolean) set to true to print the page footer (default), false otherwise.
+ * @public
+ */
+ public function setPrintFooter($val=true) {
+ $this->print_footer = $val ? true : false;
+ }
+
+ /**
+ * Return the right-bottom (or left-bottom for RTL) corner X coordinate of last inserted image
+ * @return float
+ * @public
+ */
+ public function getImageRBX() {
+ return $this->img_rb_x;
+ }
+
+ /**
+ * Return the right-bottom (or left-bottom for RTL) corner Y coordinate of last inserted image
+ * @return float
+ * @public
+ */
+ public function getImageRBY() {
+ return $this->img_rb_y;
+ }
+
+ /**
+ * Reset the xobject template used by Header() method.
+ * @public
+ */
+ public function resetHeaderTemplate() {
+ $this->header_xobjid = -1;
+ }
+
+ /**
+ * Set a flag to automatically reset the xobject template used by Header() method at each page.
+ * @param $val (boolean) set to true to reset Header xobject template at each page, false otherwise.
+ * @public
+ */
+ public function setHeaderTemplateAutoreset($val=true) {
+ $this->header_xobj_autoreset = $val ? true : false;
+ }
+
+ /**
+ * This method is used to render the page header.
+ * It is automatically called by AddPage() and could be overwritten in your own inherited class.
+ * @public
+ */
+ public function Header() {
+ if ($this->header_xobjid < 0) {
+ // start a new XObject Template
+ $this->header_xobjid = $this->startTemplate($this->w, $this->tMargin);
+ $headerfont = $this->getHeaderFont();
+ $headerdata = $this->getHeaderData();
+ $this->y = $this->header_margin;
+ if ($this->rtl) {
+ $this->x = $this->w - $this->original_rMargin;
+ } else {
+ $this->x = $this->original_lMargin;
+ }
+ if (($headerdata['logo']) AND ($headerdata['logo'] != K_BLANK_IMAGE)) {
+ $imgtype = TCPDF_IMAGES::getImageFileType(K_PATH_IMAGES.$headerdata['logo']);
+ if (($imgtype == 'eps') OR ($imgtype == 'ai')) {
+ $this->ImageEps(K_PATH_IMAGES.$headerdata['logo'], '', '', $headerdata['logo_width']);
+ } elseif ($imgtype == 'svg') {
+ $this->ImageSVG(K_PATH_IMAGES.$headerdata['logo'], '', '', $headerdata['logo_width']);
+ } else {
+ $this->Image(K_PATH_IMAGES.$headerdata['logo'], '', '', $headerdata['logo_width']);
+ }
+ $imgy = $this->getImageRBY();
+ } else {
+ $imgy = $this->y;
+ }
+ $cell_height = round(($this->cell_height_ratio * $headerfont[2]) / $this->k, 2);
+ // set starting margin for text data cell
+ if ($this->getRTL()) {
+ $header_x = $this->original_rMargin + ($headerdata['logo_width'] * 1.1);
+ } else {
+ $header_x = $this->original_lMargin + ($headerdata['logo_width'] * 1.1);
+ }
+ $cw = $this->w - $this->original_lMargin - $this->original_rMargin - ($headerdata['logo_width'] * 1.1);
+ $this->SetTextColorArray($this->header_text_color);
+ // header title
+ $this->SetFont($headerfont[0], 'B', $headerfont[2] + 1);
+ $this->SetX($header_x);
+ $this->Cell($cw, $cell_height, $headerdata['title'], 0, 1, '', 0, '', 0);
+ // header string
+ $this->SetFont($headerfont[0], $headerfont[1], $headerfont[2]);
+ $this->SetX($header_x);
+ $this->MultiCell($cw, $cell_height, $headerdata['string'], 0, '', 0, 1, '', '', true, 0, false, true, 0, 'T', false);
+ // print an ending header line
+ $this->SetLineStyle(array('width' => 0.85 / $this->k, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => $headerdata['line_color']));
+ $this->SetY((2.835 / $this->k) + max($imgy, $this->y));
+ if ($this->rtl) {
+ $this->SetX($this->original_rMargin);
+ } else {
+ $this->SetX($this->original_lMargin);
+ }
+ $this->Cell(($this->w - $this->original_lMargin - $this->original_rMargin), 0, '', 'T', 0, 'C');
+ $this->endTemplate();
+ }
+ // print header template
+ $x = 0;
+ $dx = 0;
+ if (!$this->header_xobj_autoreset AND $this->booklet AND (($this->page % 2) == 0)) {
+ // adjust margins for booklet mode
+ $dx = ($this->original_lMargin - $this->original_rMargin);
+ }
+ if ($this->rtl) {
+ $x = $this->w + $dx;
+ } else {
+ $x = 0 + $dx;
+ }
+ $this->printTemplate($this->header_xobjid, $x, 0, 0, 0, '', '', false);
+ if ($this->header_xobj_autoreset) {
+ // reset header xobject template at each page
+ $this->header_xobjid = -1;
+ }
+ }
+
+ /**
+ * This method is used to render the page footer.
+ * It is automatically called by AddPage() and could be overwritten in your own inherited class.
+ * @public
+ */
+ public function Footer() {
+ $cur_y = $this->y;
+ $this->SetTextColorArray($this->footer_text_color);
+ //set style for cell border
+ $line_width = (0.85 / $this->k);
+ $this->SetLineStyle(array('width' => $line_width, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => $this->footer_line_color));
+ //print document barcode
+ $barcode = $this->getBarcode();
+ if (!empty($barcode)) {
+ $this->Ln($line_width);
+ $barcode_width = round(($this->w - $this->original_lMargin - $this->original_rMargin) / 3);
+ $style = array(
+ 'position' => $this->rtl?'R':'L',
+ 'align' => $this->rtl?'R':'L',
+ 'stretch' => false,
+ 'fitwidth' => true,
+ 'cellfitalign' => '',
+ 'border' => false,
+ 'padding' => 0,
+ 'fgcolor' => array(0,0,0),
+ 'bgcolor' => false,
+ 'text' => false
+ );
+ $this->write1DBarcode($barcode, 'C128', '', $cur_y + $line_width, '', (($this->footer_margin / 3) - $line_width), 0.3, $style, '');
+ }
+ $w_page = isset($this->l['w_page']) ? $this->l['w_page'].' ' : '';
+ if (empty($this->pagegroups)) {
+ $pagenumtxt = $w_page.$this->getAliasNumPage().' / '.$this->getAliasNbPages();
+ } else {
+ $pagenumtxt = $w_page.$this->getPageNumGroupAlias().' / '.$this->getPageGroupAlias();
+ }
+ $this->SetY($cur_y);
+ //Print page number
+ if ($this->getRTL()) {
+ $this->SetX($this->original_rMargin);
+ $this->Cell(0, 0, $pagenumtxt, 'T', 0, 'L');
+ } else {
+ $this->SetX($this->original_lMargin);
+ $this->Cell(0, 0, $this->getAliasRightShift().$pagenumtxt, 'T', 0, 'R');
+ }
+ }
+
+ /**
+ * This method is used to render the page header.
+ * @protected
+ * @since 4.0.012 (2008-07-24)
+ */
+ protected function setHeader() {
+ if (!$this->print_header OR ($this->state != 2)) {
+ return;
+ }
+ $this->InHeader = true;
+ $this->setGraphicVars($this->default_graphic_vars);
+ $temp_thead = $this->thead;
+ $temp_theadMargins = $this->theadMargins;
+ $lasth = $this->lasth;
+ $this->_out('q');
+ $this->rMargin = $this->original_rMargin;
+ $this->lMargin = $this->original_lMargin;
+ $this->SetCellPadding(0);
+ //set current position
+ if ($this->rtl) {
+ $this->SetXY($this->original_rMargin, $this->header_margin);
+ } else {
+ $this->SetXY($this->original_lMargin, $this->header_margin);
+ }
+ $this->SetFont($this->header_font[0], $this->header_font[1], $this->header_font[2]);
+ $this->Header();
+ //restore position
+ if ($this->rtl) {
+ $this->SetXY($this->original_rMargin, $this->tMargin);
+ } else {
+ $this->SetXY($this->original_lMargin, $this->tMargin);
+ }
+ $this->_out('Q');
+ $this->lasth = $lasth;
+ $this->thead = $temp_thead;
+ $this->theadMargins = $temp_theadMargins;
+ $this->newline = false;
+ $this->InHeader = false;
+ }
+
+ /**
+ * This method is used to render the page footer.
+ * @protected
+ * @since 4.0.012 (2008-07-24)
+ */
+ protected function setFooter() {
+ if ($this->state != 2) {
+ return;
+ }
+ $this->InFooter = true;
+ // save current graphic settings
+ $gvars = $this->getGraphicVars();
+ // mark this point
+ $this->footerpos[$this->page] = $this->pagelen[$this->page];
+ $this->_out("\n");
+ if ($this->print_footer) {
+ $this->setGraphicVars($this->default_graphic_vars);
+ $this->current_column = 0;
+ $this->num_columns = 1;
+ $temp_thead = $this->thead;
+ $temp_theadMargins = $this->theadMargins;
+ $lasth = $this->lasth;
+ $this->_out('q');
+ $this->rMargin = $this->original_rMargin;
+ $this->lMargin = $this->original_lMargin;
+ $this->SetCellPadding(0);
+ //set current position
+ $footer_y = $this->h - $this->footer_margin;
+ if ($this->rtl) {
+ $this->SetXY($this->original_rMargin, $footer_y);
+ } else {
+ $this->SetXY($this->original_lMargin, $footer_y);
+ }
+ $this->SetFont($this->footer_font[0], $this->footer_font[1], $this->footer_font[2]);
+ $this->Footer();
+ //restore position
+ if ($this->rtl) {
+ $this->SetXY($this->original_rMargin, $this->tMargin);
+ } else {
+ $this->SetXY($this->original_lMargin, $this->tMargin);
+ }
+ $this->_out('Q');
+ $this->lasth = $lasth;
+ $this->thead = $temp_thead;
+ $this->theadMargins = $temp_theadMargins;
+ }
+ // restore graphic settings
+ $this->setGraphicVars($gvars);
+ $this->current_column = $gvars['current_column'];
+ $this->num_columns = $gvars['num_columns'];
+ // calculate footer length
+ $this->footerlen[$this->page] = $this->pagelen[$this->page] - $this->footerpos[$this->page] + 1;
+ $this->InFooter = false;
+ }
+
+ /**
+ * Check if we are on the page body (excluding page header and footer).
+ * @return true if we are not in page header nor in page footer, false otherwise.
+ * @protected
+ * @since 5.9.091 (2011-06-15)
+ */
+ protected function inPageBody() {
+ return (($this->InHeader === false) AND ($this->InFooter === false));
+ }
+
+ /**
+ * This method is used to render the table header on new page (if any).
+ * @protected
+ * @since 4.5.030 (2009-03-25)
+ */
+ protected function setTableHeader() {
+ if ($this->num_columns > 1) {
+ // multi column mode
+ return;
+ }
+ if (isset($this->theadMargins['top'])) {
+ // restore the original top-margin
+ $this->tMargin = $this->theadMargins['top'];
+ $this->pagedim[$this->page]['tm'] = $this->tMargin;
+ $this->y = $this->tMargin;
+ }
+ if (!TCPDF_STATIC::empty_string($this->thead) AND (!$this->inthead)) {
+ // set margins
+ $prev_lMargin = $this->lMargin;
+ $prev_rMargin = $this->rMargin;
+ $prev_cell_padding = $this->cell_padding;
+ $this->lMargin = $this->theadMargins['lmargin'] + ($this->pagedim[$this->page]['olm'] - $this->pagedim[$this->theadMargins['page']]['olm']);
+ $this->rMargin = $this->theadMargins['rmargin'] + ($this->pagedim[$this->page]['orm'] - $this->pagedim[$this->theadMargins['page']]['orm']);
+ $this->cell_padding = $this->theadMargins['cell_padding'];
+ if ($this->rtl) {
+ $this->x = $this->w - $this->rMargin;
+ } else {
+ $this->x = $this->lMargin;
+ }
+ // account for special "cell" mode
+ if ($this->theadMargins['cell']) {
+ if ($this->rtl) {
+ $this->x -= $this->cell_padding['R'];
+ } else {
+ $this->x += $this->cell_padding['L'];
+ }
+ }
+ // print table header
+ $this->writeHTML($this->thead, false, false, false, false, '');
+ // set new top margin to skip the table headers
+ if (!isset($this->theadMargins['top'])) {
+ $this->theadMargins['top'] = $this->tMargin;
+ }
+ // store end of header position
+ if (!isset($this->columns[0]['th'])) {
+ $this->columns[0]['th'] = array();
+ }
+ $this->columns[0]['th']['\''.$this->page.'\''] = $this->y;
+ $this->tMargin = $this->y;
+ $this->pagedim[$this->page]['tm'] = $this->tMargin;
+ $this->lasth = 0;
+ $this->lMargin = $prev_lMargin;
+ $this->rMargin = $prev_rMargin;
+ $this->cell_padding = $prev_cell_padding;
+ }
+ }
+
+ /**
+ * Returns the current page number.
+ * @return int page number
+ * @public
+ * @since 1.0
+ * @see getAliasNbPages()
+ */
+ public function PageNo() {
+ return $this->page;
+ }
+
+ /**
+ * Returns the array of spot colors.
+ * @return (array) Spot colors array.
+ * @public
+ * @since 6.0.038 (2013-09-30)
+ */
+ public function getAllSpotColors() {
+ return $this->spot_colors;
+ }
+
+ /**
+ * Defines a new spot color.
+ * It can be expressed in RGB components or gray scale.
+ * The method can be called before the first page is created and the value is retained from page to page.
+ * @param $name (string) Full name of the spot color.
+ * @param $c (float) Cyan color for CMYK. Value between 0 and 100.
+ * @param $m (float) Magenta color for CMYK. Value between 0 and 100.
+ * @param $y (float) Yellow color for CMYK. Value between 0 and 100.
+ * @param $k (float) Key (Black) color for CMYK. Value between 0 and 100.
+ * @public
+ * @since 4.0.024 (2008-09-12)
+ * @see SetDrawSpotColor(), SetFillSpotColor(), SetTextSpotColor()
+ */
+ public function AddSpotColor($name, $c, $m, $y, $k) {
+ if (!isset($this->spot_colors[$name])) {
+ $i = (1 + count($this->spot_colors));
+ $this->spot_colors[$name] = array('C' => $c, 'M' => $m, 'Y' => $y, 'K' => $k, 'name' => $name, 'i' => $i);
+ }
+ }
+
+ /**
+ * Set the spot color for the specified type ('draw', 'fill', 'text').
+ * @param $type (string) Type of object affected by this color: ('draw', 'fill', 'text').
+ * @param $name (string) Name of the spot color.
+ * @param $tint (float) Intensity of the color (from 0 to 100 ; 100 = full intensity by default).
+ * @return (string) PDF color command.
+ * @public
+ * @since 5.9.125 (2011-10-03)
+ */
+ public function setSpotColor($type, $name, $tint=100) {
+ $spotcolor = TCPDF_COLORS::getSpotColor($name, $this->spot_colors);
+ if ($spotcolor === false) {
+ $this->Error('Undefined spot color: '.$name.', you must add it using the AddSpotColor() method.');
+ }
+ $tint = (max(0, min(100, $tint)) / 100);
+ $pdfcolor = sprintf('/CS%d ', $this->spot_colors[$name]['i']);
+ switch ($type) {
+ case 'draw': {
+ $pdfcolor .= sprintf('CS %F SCN', $tint);
+ $this->DrawColor = $pdfcolor;
+ $this->strokecolor = $spotcolor;
+ break;
+ }
+ case 'fill': {
+ $pdfcolor .= sprintf('cs %F scn', $tint);
+ $this->FillColor = $pdfcolor;
+ $this->bgcolor = $spotcolor;
+ break;
+ }
+ case 'text': {
+ $pdfcolor .= sprintf('cs %F scn', $tint);
+ $this->TextColor = $pdfcolor;
+ $this->fgcolor = $spotcolor;
+ break;
+ }
+ }
+ $this->ColorFlag = ($this->FillColor != $this->TextColor);
+ if ($this->state == 2) {
+ $this->_out($pdfcolor);
+ }
+ if ($this->inxobj) {
+ // we are inside an XObject template
+ $this->xobjects[$this->xobjid]['spot_colors'][$name] = $this->spot_colors[$name];
+ }
+ return $pdfcolor;
+ }
+
+ /**
+ * Defines the spot color used for all drawing operations (lines, rectangles and cell borders).
+ * @param $name (string) Name of the spot color.
+ * @param $tint (float) Intensity of the color (from 0 to 100 ; 100 = full intensity by default).
+ * @public
+ * @since 4.0.024 (2008-09-12)
+ * @see AddSpotColor(), SetFillSpotColor(), SetTextSpotColor()
+ */
+ public function SetDrawSpotColor($name, $tint=100) {
+ $this->setSpotColor('draw', $name, $tint);
+ }
+
+ /**
+ * Defines the spot color used for all filling operations (filled rectangles and cell backgrounds).
+ * @param $name (string) Name of the spot color.
+ * @param $tint (float) Intensity of the color (from 0 to 100 ; 100 = full intensity by default).
+ * @public
+ * @since 4.0.024 (2008-09-12)
+ * @see AddSpotColor(), SetDrawSpotColor(), SetTextSpotColor()
+ */
+ public function SetFillSpotColor($name, $tint=100) {
+ $this->setSpotColor('fill', $name, $tint);
+ }
+
+ /**
+ * Defines the spot color used for text.
+ * @param $name (string) Name of the spot color.
+ * @param $tint (int) Intensity of the color (from 0 to 100 ; 100 = full intensity by default).
+ * @public
+ * @since 4.0.024 (2008-09-12)
+ * @see AddSpotColor(), SetDrawSpotColor(), SetFillSpotColor()
+ */
+ public function SetTextSpotColor($name, $tint=100) {
+ $this->setSpotColor('text', $name, $tint);
+ }
+
+ /**
+ * Set the color array for the specified type ('draw', 'fill', 'text').
+ * It can be expressed in RGB, CMYK or GRAY SCALE components.
+ * The method can be called before the first page is created and the value is retained from page to page.
+ * @param $type (string) Type of object affected by this color: ('draw', 'fill', 'text').
+ * @param $color (array) Array of colors (1=gray, 3=RGB, 4=CMYK or 5=spotcolor=CMYK+name values).
+ * @param $ret (boolean) If true do not send the PDF command.
+ * @return (string) The PDF command or empty string.
+ * @public
+ * @since 3.1.000 (2008-06-11)
+ */
+ public function setColorArray($type, $color, $ret=false) {
+ if (is_array($color)) {
+ $color = array_values($color);
+ // component: grey, RGB red or CMYK cyan
+ $c = isset($color[0]) ? $color[0] : -1;
+ // component: RGB green or CMYK magenta
+ $m = isset($color[1]) ? $color[1] : -1;
+ // component: RGB blue or CMYK yellow
+ $y = isset($color[2]) ? $color[2] : -1;
+ // component: CMYK black
+ $k = isset($color[3]) ? $color[3] : -1;
+ // color name
+ $name = isset($color[4]) ? $color[4] : '';
+ if ($c >= 0) {
+ return $this->setColor($type, $c, $m, $y, $k, $ret, $name);
+ }
+ }
+ return '';
+ }
+
+ /**
+ * Defines the color used for all drawing operations (lines, rectangles and cell borders).
+ * It can be expressed in RGB, CMYK or GRAY SCALE components.
+ * The method can be called before the first page is created and the value is retained from page to page.
+ * @param $color (array) Array of colors (1, 3 or 4 values).
+ * @param $ret (boolean) If true do not send the PDF command.
+ * @return string the PDF command
+ * @public
+ * @since 3.1.000 (2008-06-11)
+ * @see SetDrawColor()
+ */
+ public function SetDrawColorArray($color, $ret=false) {
+ return $this->setColorArray('draw', $color, $ret);
+ }
+
+ /**
+ * Defines the color used for all filling operations (filled rectangles and cell backgrounds).
+ * It can be expressed in RGB, CMYK or GRAY SCALE components.
+ * The method can be called before the first page is created and the value is retained from page to page.
+ * @param $color (array) Array of colors (1, 3 or 4 values).
+ * @param $ret (boolean) If true do not send the PDF command.
+ * @public
+ * @since 3.1.000 (2008-6-11)
+ * @see SetFillColor()
+ */
+ public function SetFillColorArray($color, $ret=false) {
+ return $this->setColorArray('fill', $color, $ret);
+ }
+
+ /**
+ * Defines the color used for text. It can be expressed in RGB components or gray scale.
+ * The method can be called before the first page is created and the value is retained from page to page.
+ * @param $color (array) Array of colors (1, 3 or 4 values).
+ * @param $ret (boolean) If true do not send the PDF command.
+ * @public
+ * @since 3.1.000 (2008-6-11)
+ * @see SetFillColor()
+ */
+ public function SetTextColorArray($color, $ret=false) {
+ return $this->setColorArray('text', $color, $ret);
+ }
+
+ /**
+ * Defines the color used by the specified type ('draw', 'fill', 'text').
+ * @param $type (string) Type of object affected by this color: ('draw', 'fill', 'text').
+ * @param $col1 (float) GRAY level for single color, or Red color for RGB (0-255), or CYAN color for CMYK (0-100).
+ * @param $col2 (float) GREEN color for RGB (0-255), or MAGENTA color for CMYK (0-100).
+ * @param $col3 (float) BLUE color for RGB (0-255), or YELLOW color for CMYK (0-100).
+ * @param $col4 (float) KEY (BLACK) color for CMYK (0-100).
+ * @param $ret (boolean) If true do not send the command.
+ * @param $name (string) spot color name (if any)
+ * @return (string) The PDF command or empty string.
+ * @public
+ * @since 5.9.125 (2011-10-03)
+ */
+ public function setColor($type, $col1=0, $col2=-1, $col3=-1, $col4=-1, $ret=false, $name='') {
+ // set default values
+ if (!is_numeric($col1)) {
+ $col1 = 0;
+ }
+ if (!is_numeric($col2)) {
+ $col2 = -1;
+ }
+ if (!is_numeric($col3)) {
+ $col3 = -1;
+ }
+ if (!is_numeric($col4)) {
+ $col4 = -1;
+ }
+ // set color by case
+ $suffix = '';
+ if (($col2 == -1) AND ($col3 == -1) AND ($col4 == -1)) {
+ // Grey scale
+ $col1 = max(0, min(255, $col1));
+ $intcolor = array('G' => $col1);
+ $pdfcolor = sprintf('%F ', ($col1 / 255));
+ $suffix = 'g';
+ } elseif ($col4 == -1) {
+ // RGB
+ $col1 = max(0, min(255, $col1));
+ $col2 = max(0, min(255, $col2));
+ $col3 = max(0, min(255, $col3));
+ $intcolor = array('R' => $col1, 'G' => $col2, 'B' => $col3);
+ $pdfcolor = sprintf('%F %F %F ', ($col1 / 255), ($col2 / 255), ($col3 / 255));
+ $suffix = 'rg';
+ } else {
+ $col1 = max(0, min(100, $col1));
+ $col2 = max(0, min(100, $col2));
+ $col3 = max(0, min(100, $col3));
+ $col4 = max(0, min(100, $col4));
+ if (empty($name)) {
+ // CMYK
+ $intcolor = array('C' => $col1, 'M' => $col2, 'Y' => $col3, 'K' => $col4);
+ $pdfcolor = sprintf('%F %F %F %F ', ($col1 / 100), ($col2 / 100), ($col3 / 100), ($col4 / 100));
+ $suffix = 'k';
+ } else {
+ // SPOT COLOR
+ $intcolor = array('C' => $col1, 'M' => $col2, 'Y' => $col3, 'K' => $col4, 'name' => $name);
+ $this->AddSpotColor($name, $col1, $col2, $col3, $col4);
+ $pdfcolor = $this->setSpotColor($type, $name, 100);
+ }
+ }
+ switch ($type) {
+ case 'draw': {
+ $pdfcolor .= strtoupper($suffix);
+ $this->DrawColor = $pdfcolor;
+ $this->strokecolor = $intcolor;
+ break;
+ }
+ case 'fill': {
+ $pdfcolor .= $suffix;
+ $this->FillColor = $pdfcolor;
+ $this->bgcolor = $intcolor;
+ break;
+ }
+ case 'text': {
+ $pdfcolor .= $suffix;
+ $this->TextColor = $pdfcolor;
+ $this->fgcolor = $intcolor;
+ break;
+ }
+ }
+ $this->ColorFlag = ($this->FillColor != $this->TextColor);
+ if (($type != 'text') AND ($this->state == 2)) {
+ if (!$ret) {
+ $this->_out($pdfcolor);
+ }
+ return $pdfcolor;
+ }
+ return '';
+ }
+
+ /**
+ * Defines the color used for all drawing operations (lines, rectangles and cell borders). It can be expressed in RGB components or gray scale. The method can be called before the first page is created and the value is retained from page to page.
+ * @param $col1 (float) GRAY level for single color, or Red color for RGB (0-255), or CYAN color for CMYK (0-100).
+ * @param $col2 (float) GREEN color for RGB (0-255), or MAGENTA color for CMYK (0-100).
+ * @param $col3 (float) BLUE color for RGB (0-255), or YELLOW color for CMYK (0-100).
+ * @param $col4 (float) KEY (BLACK) color for CMYK (0-100).
+ * @param $ret (boolean) If true do not send the command.
+ * @param $name (string) spot color name (if any)
+ * @return string the PDF command
+ * @public
+ * @since 1.3
+ * @see SetDrawColorArray(), SetFillColor(), SetTextColor(), Line(), Rect(), Cell(), MultiCell()
+ */
+ public function SetDrawColor($col1=0, $col2=-1, $col3=-1, $col4=-1, $ret=false, $name='') {
+ return $this->setColor('draw', $col1, $col2, $col3, $col4, $ret, $name);
+ }
+
+ /**
+ * Defines the color used for all filling operations (filled rectangles and cell backgrounds). It can be expressed in RGB components or gray scale. The method can be called before the first page is created and the value is retained from page to page.
+ * @param $col1 (float) GRAY level for single color, or Red color for RGB (0-255), or CYAN color for CMYK (0-100).
+ * @param $col2 (float) GREEN color for RGB (0-255), or MAGENTA color for CMYK (0-100).
+ * @param $col3 (float) BLUE color for RGB (0-255), or YELLOW color for CMYK (0-100).
+ * @param $col4 (float) KEY (BLACK) color for CMYK (0-100).
+ * @param $ret (boolean) If true do not send the command.
+ * @param $name (string) Spot color name (if any).
+ * @return (string) The PDF command.
+ * @public
+ * @since 1.3
+ * @see SetFillColorArray(), SetDrawColor(), SetTextColor(), Rect(), Cell(), MultiCell()
+ */
+ public function SetFillColor($col1=0, $col2=-1, $col3=-1, $col4=-1, $ret=false, $name='') {
+ return $this->setColor('fill', $col1, $col2, $col3, $col4, $ret, $name);
+ }
+
+ /**
+ * Defines the color used for text. It can be expressed in RGB components or gray scale. The method can be called before the first page is created and the value is retained from page to page.
+ * @param $col1 (float) GRAY level for single color, or Red color for RGB (0-255), or CYAN color for CMYK (0-100).
+ * @param $col2 (float) GREEN color for RGB (0-255), or MAGENTA color for CMYK (0-100).
+ * @param $col3 (float) BLUE color for RGB (0-255), or YELLOW color for CMYK (0-100).
+ * @param $col4 (float) KEY (BLACK) color for CMYK (0-100).
+ * @param $ret (boolean) If true do not send the command.
+ * @param $name (string) Spot color name (if any).
+ * @return (string) Empty string.
+ * @public
+ * @since 1.3
+ * @see SetTextColorArray(), SetDrawColor(), SetFillColor(), Text(), Cell(), MultiCell()
+ */
+ public function SetTextColor($col1=0, $col2=-1, $col3=-1, $col4=-1, $ret=false, $name='') {
+ return $this->setColor('text', $col1, $col2, $col3, $col4, $ret, $name);
+ }
+
+ /**
+ * Returns the length of a string in user unit. A font must be selected.<br>
+ * @param $s (string) The string whose length is to be computed
+ * @param $fontname (string) Family font. It can be either a name defined by AddFont() or one of the standard families. It is also possible to pass an empty string, in that case, the current family is retained.
+ * @param $fontstyle (string) Font style. Possible values are (case insensitive):<ul><li>empty string: regular</li><li>B: bold</li><li>I: italic</li><li>U: underline</li><li>D: line-through</li><li>O: overline</li></ul> or any combination. The default value is regular.
+ * @param $fontsize (float) Font size in points. The default value is the current size.
+ * @param $getarray (boolean) if true returns an array of characters widths, if false returns the total length.
+ * @return mixed int total string length or array of characted widths
+ * @author Nicola Asuni
+ * @public
+ * @since 1.2
+ */
+ public function GetStringWidth($s, $fontname='', $fontstyle='', $fontsize=0, $getarray=false) {
+ return $this->GetArrStringWidth(TCPDF_FONTS::utf8Bidi(TCPDF_FONTS::UTF8StringToArray($s, $this->isunicode, $this->CurrentFont), $s, $this->tmprtl, $this->isunicode, $this->CurrentFont), $fontname, $fontstyle, $fontsize, $getarray);
+ }
+
+ /**
+ * Returns the string length of an array of chars in user unit or an array of characters widths. A font must be selected.<br>
+ * @param $sa (string) The array of chars whose total length is to be computed
+ * @param $fontname (string) Family font. It can be either a name defined by AddFont() or one of the standard families. It is also possible to pass an empty string, in that case, the current family is retained.
+ * @param $fontstyle (string) Font style. Possible values are (case insensitive):<ul><li>empty string: regular</li><li>B: bold</li><li>I: italic</li><li>U: underline</li><li>D: line through</li><li>O: overline</li></ul> or any combination. The default value is regular.
+ * @param $fontsize (float) Font size in points. The default value is the current size.
+ * @param $getarray (boolean) if true returns an array of characters widths, if false returns the total length.
+ * @return mixed int total string length or array of characted widths
+ * @author Nicola Asuni
+ * @public
+ * @since 2.4.000 (2008-03-06)
+ */
+ public function GetArrStringWidth($sa, $fontname='', $fontstyle='', $fontsize=0, $getarray=false) {
+ // store current values
+ if (!TCPDF_STATIC::empty_string($fontname)) {
+ $prev_FontFamily = $this->FontFamily;
+ $prev_FontStyle = $this->FontStyle;
+ $prev_FontSizePt = $this->FontSizePt;
+ $this->SetFont($fontname, $fontstyle, $fontsize, '', 'default', false);
+ }
+ // convert UTF-8 array to Latin1 if required
+ if ($this->isunicode AND (!$this->isUnicodeFont())) {
+ $sa = TCPDF_FONTS::UTF8ArrToLatin1Arr($sa);
+ }
+ $w = 0; // total width
+ $wa = array(); // array of characters widths
+ foreach ($sa as $ck => $char) {
+ // character width
+ $cw = $this->GetCharWidth($char, isset($sa[($ck + 1)]));
+ $wa[] = $cw;
+ $w += $cw;
+ }
+ // restore previous values
+ if (!TCPDF_STATIC::empty_string($fontname)) {
+ $this->SetFont($prev_FontFamily, $prev_FontStyle, $prev_FontSizePt, '', 'default', false);
+ }
+ if ($getarray) {
+ return $wa;
+ }
+ return $w;
+ }
+
+ /**
+ * Returns the length of the char in user unit for the current font considering current stretching and spacing (tracking).
+ * @param $char (int) The char code whose length is to be returned
+ * @param $notlast (boolean) If false ignore the font-spacing.
+ * @return float char width
+ * @author Nicola Asuni
+ * @public
+ * @since 2.4.000 (2008-03-06)
+ */
+ public function GetCharWidth($char, $notlast=true) {
+ // get raw width
+ $chw = $this->getRawCharWidth($char);
+ if (($this->font_spacing < 0) OR (($this->font_spacing > 0) AND $notlast)) {
+ // increase/decrease font spacing
+ $chw += $this->font_spacing;
+ }
+ if ($this->font_stretching != 100) {
+ // fixed stretching mode
+ $chw *= ($this->font_stretching / 100);
+ }
+ return $chw;
+ }
+
+ /**
+ * Returns the length of the char in user unit for the current font.
+ * @param $char (int) The char code whose length is to be returned
+ * @return float char width
+ * @author Nicola Asuni
+ * @public
+ * @since 5.9.000 (2010-09-28)
+ */
+ public function getRawCharWidth($char) {
+ if ($char == 173) {
+ // SHY character will not be printed
+ return (0);
+ }
+ if (isset($this->CurrentFont['cw'][$char])) {
+ $w = $this->CurrentFont['cw'][$char];
+ } elseif (isset($this->CurrentFont['dw'])) {
+ // default width
+ $w = $this->CurrentFont['dw'];
+ } elseif (isset($this->CurrentFont['cw'][32])) {
+ // default width
+ $w = $this->CurrentFont['cw'][32];
+ } else {
+ $w = 600;
+ }
+ return $this->getAbsFontMeasure($w);
+ }
+
+ /**
+ * Returns the numbero of characters in a string.
+ * @param $s (string) The input string.
+ * @return int number of characters
+ * @public
+ * @since 2.0.0001 (2008-01-07)
+ */
+ public function GetNumChars($s) {
+ if ($this->isUnicodeFont()) {
+ return count(TCPDF_FONTS::UTF8StringToArray($s, $this->isunicode, $this->CurrentFont));
+ }
+ return strlen($s);
+ }
+
+ /**
+ * Fill the list of available fonts ($this->fontlist).
+ * @protected
+ * @since 4.0.013 (2008-07-28)
+ */
+ protected function getFontsList() {
+ if (($fontsdir = opendir(TCPDF_FONTS::_getfontpath())) !== false) {
+ while (($file = readdir($fontsdir)) !== false) {
+ if (substr($file, -4) == '.php') {
+ array_push($this->fontlist, strtolower(basename($file, '.php')));
+ }
+ }
+ closedir($fontsdir);
+ }
+ }
+
+ /**
+ * Returns the unicode caracter specified by the value
+ * @param $c (int) UTF-8 value
+ * @return Returns the specified character.
+ * @since 2.3.000 (2008-03-05)
+ * @public
+ * @deprecated
+ */
+ public function unichr($c) {
+ return TCPDF_FONTS::unichr($c, $this->isunicode);
+ }
+
+ /**
+ * Convert and add the selected TrueType or Type1 font to the fonts folder (that must be writeable).
+ * @param $fontfile (string) Font file (full path).
+ * @param $fonttype (string) Font type. Leave empty for autodetect mode. Valid values are: TrueTypeUnicode, TrueType, Type1, CID0JP = CID-0 Japanese, CID0KR = CID-0 Korean, CID0CS = CID-0 Chinese Simplified, CID0CT = CID-0 Chinese Traditional.
+ * @param $enc (string) Name of the encoding table to use. Leave empty for default mode. Omit this parameter for TrueType Unicode and symbolic fonts like Symbol or ZapfDingBats.
+ * @param $flags (int) Unsigned 32-bit integer containing flags specifying various characteristics of the font (PDF32000:2008 - 9.8.2 Font Descriptor Flags): +1 for fixed font; +4 for symbol or +32 for non-symbol; +64 for italic. Fixed and Italic mode are generally autodetected so you have to set it to 32 = non-symbolic font (default) or 4 = symbolic font.
+ * @param $outpath (string) Output path for generated font files (must be writeable by the web server). Leave empty for default font folder.
+ * @param $platid (int) Platform ID for CMAP table to extract (when building a Unicode font for Windows this value should be 3, for Macintosh should be 1).
+ * @param $encid (int) Encoding ID for CMAP table to extract (when building a Unicode font for Windows this value should be 1, for Macintosh should be 0). When Platform ID is 3, legal values for Encoding ID are: 0=Symbol, 1=Unicode, 2=ShiftJIS, 3=PRC, 4=Big5, 5=Wansung, 6=Johab, 7=Reserved, 8=Reserved, 9=Reserved, 10=UCS-4.
+ * @param $addcbbox (boolean) If true includes the character bounding box information on the php font file.
+ * @return (string) TCPDF font name.
+ * @author Nicola Asuni
+ * @since 5.9.123 (2010-09-30)
+ * @public
+ * @deprecated
+ */
+ public function addTTFfont($fontfile, $fonttype='', $enc='', $flags=32, $outpath='', $platid=3, $encid=1, $addcbbox=false) {
+ return TCPDF_FONTS::addTTFfont($fontfile, $fonttype, $enc, $flags, $outpath, $platid, $encid, $addcbbox);
+ }
+
+ /**
+ * Imports a TrueType, Type1, core, or CID0 font and makes it available.
+ * It is necessary to generate a font definition file first (read /fonts/utils/README.TXT).
+ * The definition file (and the font file itself when embedding) must be present either in the current directory or in the one indicated by K_PATH_FONTS if the constant is defined. If it could not be found, the error "Could not include font definition file" is generated.
+ * @param $family (string) Font family. The name can be chosen arbitrarily. If it is a standard family name, it will override the corresponding font.
+ * @param $style (string) Font style. Possible values are (case insensitive):<ul><li>empty string: regular (default)</li><li>B: bold</li><li>I: italic</li><li>BI or IB: bold italic</li></ul>
+ * @param $fontfile (string) The font definition file. By default, the name is built from the family and style, in lower case with no spaces.
+ * @return array containing the font data, or false in case of error.
+ * @param $subset (mixed) if true embedd only a subset of the font (stores only the information related to the used characters); if false embedd full font; if 'default' uses the default value set using setFontSubsetting(). This option is valid only for TrueTypeUnicode fonts. If you want to enable users to change the document, set this parameter to false. If you subset the font, the person who receives your PDF would need to have your same font in order to make changes to your PDF. The file size of the PDF would also be smaller because you are embedding only part of a font.
+ * @public
+ * @since 1.5
+ * @see SetFont(), setFontSubsetting()
+ */
+ public function AddFont($family, $style='', $fontfile='', $subset='default') {
+ if ($subset === 'default') {
+ $subset = $this->font_subsetting;
+ }
+ if ($this->pdfa_mode) {
+ $subset = false;
+ }
+ if (TCPDF_STATIC::empty_string($family)) {
+ if (!TCPDF_STATIC::empty_string($this->FontFamily)) {
+ $family = $this->FontFamily;
+ } else {
+ $this->Error('Empty font family');
+ }
+ }
+ // move embedded styles on $style
+ if (substr($family, -1) == 'I') {
+ $style .= 'I';
+ $family = substr($family, 0, -1);
+ }
+ if (substr($family, -1) == 'B') {
+ $style .= 'B';
+ $family = substr($family, 0, -1);
+ }
+ // normalize family name
+ $family = strtolower($family);
+ if ((!$this->isunicode) AND ($family == 'arial')) {
+ $family = 'helvetica';
+ }
+ if (($family == 'symbol') OR ($family == 'zapfdingbats')) {
+ $style = '';
+ }
+ if ($this->pdfa_mode AND (isset($this->CoreFonts[$family]))) {
+ // all fonts must be embedded
+ $family = 'pdfa'.$family;
+ }
+ $tempstyle = strtoupper($style);
+ $style = '';
+ // underline
+ if (strpos($tempstyle, 'U') !== false) {
+ $this->underline = true;
+ } else {
+ $this->underline = false;
+ }
+ // line-through (deleted)
+ if (strpos($tempstyle, 'D') !== false) {
+ $this->linethrough = true;
+ } else {
+ $this->linethrough = false;
+ }
+ // overline
+ if (strpos($tempstyle, 'O') !== false) {
+ $this->overline = true;
+ } else {
+ $this->overline = false;
+ }
+ // bold
+ if (strpos($tempstyle, 'B') !== false) {
+ $style .= 'B';
+ }
+ // oblique
+ if (strpos($tempstyle, 'I') !== false) {
+ $style .= 'I';
+ }
+ $bistyle = $style;
+ $fontkey = $family.$style;
+ $font_style = $style.($this->underline ? 'U' : '').($this->linethrough ? 'D' : '').($this->overline ? 'O' : '');
+ $fontdata = array('fontkey' => $fontkey, 'family' => $family, 'style' => $font_style);
+ // check if the font has been already added
+ $fb = $this->getFontBuffer($fontkey);
+ if ($fb !== false) {
+ if ($this->inxobj) {
+ // we are inside an XObject template
+ $this->xobjects[$this->xobjid]['fonts'][$fontkey] = $fb['i'];
+ }
+ return $fontdata;
+ }
+ // get specified font directory (if any)
+ $fontdir = false;
+ if (!TCPDF_STATIC::empty_string($fontfile)) {
+ $fontdir = dirname($fontfile);
+ if (TCPDF_STATIC::empty_string($fontdir) OR ($fontdir == '.')) {
+ $fontdir = '';
+ } else {
+ $fontdir .= '/';
+ }
+ }
+ // true when the font style variation is missing
+ $missing_style = false;
+ // search and include font file
+ if (TCPDF_STATIC::empty_string($fontfile) OR (!@file_exists($fontfile))) {
+ // build a standard filenames for specified font
+ $tmp_fontfile = str_replace(' ', '', $family).strtolower($style).'.php';
+ $fontfile = TCPDF_FONTS::getFontFullPath($tmp_fontfile, $fontdir);
+ if (TCPDF_STATIC::empty_string($fontfile)) {
+ $missing_style = true;
+ // try to remove the style part
+ $tmp_fontfile = str_replace(' ', '', $family).'.php';
+ $fontfile = TCPDF_FONTS::getFontFullPath($tmp_fontfile, $fontdir);
+ }
+ }
+ // include font file
+ if (!TCPDF_STATIC::empty_string($fontfile) AND (@file_exists($fontfile))) {
+ include($fontfile);
+ } else {
+ $this->Error('Could not include font definition file: '.$family.'');
+ }
+ // check font parameters
+ if ((!isset($type)) OR (!isset($cw))) {
+ $this->Error('The font definition file has a bad format: '.$fontfile.'');
+ }
+ // SET default parameters
+ if (!isset($file) OR TCPDF_STATIC::empty_string($file)) {
+ $file = '';
+ }
+ if (!isset($enc) OR TCPDF_STATIC::empty_string($enc)) {
+ $enc = '';
+ }
+ if (!isset($cidinfo) OR TCPDF_STATIC::empty_string($cidinfo)) {
+ $cidinfo = array('Registry'=>'Adobe', 'Ordering'=>'Identity', 'Supplement'=>0);
+ $cidinfo['uni2cid'] = array();
+ }
+ if (!isset($ctg) OR TCPDF_STATIC::empty_string($ctg)) {
+ $ctg = '';
+ }
+ if (!isset($desc) OR TCPDF_STATIC::empty_string($desc)) {
+ $desc = array();
+ }
+ if (!isset($up) OR TCPDF_STATIC::empty_string($up)) {
+ $up = -100;
+ }
+ if (!isset($ut) OR TCPDF_STATIC::empty_string($ut)) {
+ $ut = 50;
+ }
+ if (!isset($cw) OR TCPDF_STATIC::empty_string($cw)) {
+ $cw = array();
+ }
+ if (!isset($dw) OR TCPDF_STATIC::empty_string($dw)) {
+ // set default width
+ if (isset($desc['MissingWidth']) AND ($desc['MissingWidth'] > 0)) {
+ $dw = $desc['MissingWidth'];
+ } elseif (isset($cw[32])) {
+ $dw = $cw[32];
+ } else {
+ $dw = 600;
+ }
+ }
+ ++$this->numfonts;
+ if ($type == 'core') {
+ $name = $this->CoreFonts[$fontkey];
+ $subset = false;
+ } elseif (($type == 'TrueType') OR ($type == 'Type1')) {
+ $subset = false;
+ } elseif ($type == 'TrueTypeUnicode') {
+ $enc = 'Identity-H';
+ } elseif ($type == 'cidfont0') {
+ if ($this->pdfa_mode) {
+ $this->Error('All fonts must be embedded in PDF/A mode!');
+ }
+ } else {
+ $this->Error('Unknow font type: '.$type.'');
+ }
+ // set name if unset
+ if (!isset($name) OR empty($name)) {
+ $name = $fontkey;
+ }
+ // create artificial font style variations if missing (only works with non-embedded fonts)
+ if (($type != 'core') AND $missing_style) {
+ // style variations
+ $styles = array('' => '', 'B' => ',Bold', 'I' => ',Italic', 'BI' => ',BoldItalic');
+ $name .= $styles[$bistyle];
+ // artificial bold
+ if (strpos($bistyle, 'B') !== false) {
+ if (isset($desc['StemV'])) {
+ // from normal to bold
+ $desc['StemV'] = round($desc['StemV'] * 1.75);
+ } else {
+ // bold
+ $desc['StemV'] = 123;
+ }
+ }
+ // artificial italic
+ if (strpos($bistyle, 'I') !== false) {
+ if (isset($desc['ItalicAngle'])) {
+ $desc['ItalicAngle'] -= 11;
+ } else {
+ $desc['ItalicAngle'] = -11;
+ }
+ if (isset($desc['Flags'])) {
+ $desc['Flags'] |= 64; //bit 7
+ } else {
+ $desc['Flags'] = 64;
+ }
+ }
+ }
+ // check if the array of characters bounding boxes is defined
+ if (!isset($cbbox)) {
+ $cbbox = array();
+ }
+ // initialize subsetchars
+ $subsetchars = array_fill(0, 255, true);
+ $this->setFontBuffer($fontkey, array('fontkey' => $fontkey, 'i' => $this->numfonts, 'type' => $type, 'name' => $name, 'desc' => $desc, 'up' => $up, 'ut' => $ut, 'cw' => $cw, 'cbbox' => $cbbox, 'dw' => $dw, 'enc' => $enc, 'cidinfo' => $cidinfo, 'file' => $file, 'ctg' => $ctg, 'subset' => $subset, 'subsetchars' => $subsetchars));
+ if ($this->inxobj) {
+ // we are inside an XObject template
+ $this->xobjects[$this->xobjid]['fonts'][$fontkey] = $this->numfonts;
+ }
+ if (isset($diff) AND (!empty($diff))) {
+ //Search existing encodings
+ $d = 0;
+ $nb = count($this->diffs);
+ for ($i=1; $i <= $nb; ++$i) {
+ if ($this->diffs[$i] == $diff) {
+ $d = $i;
+ break;
+ }
+ }
+ if ($d == 0) {
+ $d = $nb + 1;
+ $this->diffs[$d] = $diff;
+ }
+ $this->setFontSubBuffer($fontkey, 'diff', $d);
+ }
+ if (!TCPDF_STATIC::empty_string($file)) {
+ if (!isset($this->FontFiles[$file])) {
+ if ((strcasecmp($type,'TrueType') == 0) OR (strcasecmp($type, 'TrueTypeUnicode') == 0)) {
+ $this->FontFiles[$file] = array('length1' => $originalsize, 'fontdir' => $fontdir, 'subset' => $subset, 'fontkeys' => array($fontkey));
+ } elseif ($type != 'core') {
+ $this->FontFiles[$file] = array('length1' => $size1, 'length2' => $size2, 'fontdir' => $fontdir, 'subset' => $subset, 'fontkeys' => array($fontkey));
+ }
+ } else {
+ // update fontkeys that are sharing this font file
+ $this->FontFiles[$file]['subset'] = ($this->FontFiles[$file]['subset'] AND $subset);
+ if (!in_array($fontkey, $this->FontFiles[$file]['fontkeys'])) {
+ $this->FontFiles[$file]['fontkeys'][] = $fontkey;
+ }
+ }
+ }
+ return $fontdata;
+ }
+
+ /**
+ * Sets the font used to print character strings.
+ * The font can be either a standard one or a font added via the AddFont() method. Standard fonts use Windows encoding cp1252 (Western Europe).
+ * The method can be called before the first page is created and the font is retained from page to page.
+ * If you just wish to change the current font size, it is simpler to call SetFontSize().
+ * Note: for the standard fonts, the font metric files must be accessible. There are three possibilities for this:<ul><li>They are in the current directory (the one where the running script lies)</li><li>They are in one of the directories defined by the include_path parameter</li><li>They are in the directory defined by the K_PATH_FONTS constant</li></ul><br />
+ * @param $family (string) Family font. It can be either a name defined by AddFont() or one of the standard Type1 families (case insensitive):<ul><li>times (Times-Roman)</li><li>timesb (Times-Bold)</li><li>timesi (Times-Italic)</li><li>timesbi (Times-BoldItalic)</li><li>helvetica (Helvetica)</li><li>helveticab (Helvetica-Bold)</li><li>helveticai (Helvetica-Oblique)</li><li>helveticabi (Helvetica-BoldOblique)</li><li>courier (Courier)</li><li>courierb (Courier-Bold)</li><li>courieri (Courier-Oblique)</li><li>courierbi (Courier-BoldOblique)</li><li>symbol (Symbol)</li><li>zapfdingbats (ZapfDingbats)</li></ul> It is also possible to pass an empty string. In that case, the current family is retained.
+ * @param $style (string) Font style. Possible values are (case insensitive):<ul><li>empty string: regular</li><li>B: bold</li><li>I: italic</li><li>U: underline</li><li>D: line through</li><li>O: overline</li></ul> or any combination. The default value is regular. Bold and italic styles do not apply to Symbol and ZapfDingbats basic fonts or other fonts when not defined.
+ * @param $size (float) Font size in points. The default value is the current size. If no size has been specified since the beginning of the document, the value taken is 12
+ * @param $fontfile (string) The font definition file. By default, the name is built from the family and style, in lower case with no spaces.
+ * @param $subset (mixed) if true embedd only a subset of the font (stores only the information related to the used characters); if false embedd full font; if 'default' uses the default value set using setFontSubsetting(). This option is valid only for TrueTypeUnicode fonts. If you want to enable users to change the document, set this parameter to false. If you subset the font, the person who receives your PDF would need to have your same font in order to make changes to your PDF. The file size of the PDF would also be smaller because you are embedding only part of a font.
+ * @param $out (boolean) if true output the font size command, otherwise only set the font properties.
+ * @author Nicola Asuni
+ * @public
+ * @since 1.0
+ * @see AddFont(), SetFontSize()
+ */
+ public function SetFont($family, $style='', $size=null, $fontfile='', $subset='default', $out=true) {
+ //Select a font; size given in points
+ if ($size === null) {
+ $size = $this->FontSizePt;
+ }
+ if ($size < 0) {
+ $size = 0;
+ }
+ // try to add font (if not already added)
+ $fontdata = $this->AddFont($family, $style, $fontfile, $subset);
+ $this->FontFamily = $fontdata['family'];
+ $this->FontStyle = $fontdata['style'];
+ if (isset($this->CurrentFont['fontkey']) AND isset($this->CurrentFont['subsetchars'])) {
+ // save subset chars of the previous font
+ $this->setFontSubBuffer($this->CurrentFont['fontkey'], 'subsetchars', $this->CurrentFont['subsetchars']);
+ }
+ $this->CurrentFont = $this->getFontBuffer($fontdata['fontkey']);
+ $this->SetFontSize($size, $out);
+ }
+
+ /**
+ * Defines the size of the current font.
+ * @param $size (float) The font size in points.
+ * @param $out (boolean) if true output the font size command, otherwise only set the font properties.
+ * @public
+ * @since 1.0
+ * @see SetFont()
+ */
+ public function SetFontSize($size, $out=true) {
+ // font size in points
+ $this->FontSizePt = $size;
+ // font size in user units
+ $this->FontSize = $size / $this->k;
+ // calculate some font metrics
+ if (isset($this->CurrentFont['desc']['FontBBox'])) {
+ $bbox = explode(' ', substr($this->CurrentFont['desc']['FontBBox'], 1, -1));
+ $font_height = ((intval($bbox[3]) - intval($bbox[1])) * $size / 1000);
+ } else {
+ $font_height = $size * 1.219;
+ }
+ if (isset($this->CurrentFont['desc']['Ascent']) AND ($this->CurrentFont['desc']['Ascent'] > 0)) {
+ $font_ascent = ($this->CurrentFont['desc']['Ascent'] * $size / 1000);
+ }
+ if (isset($this->CurrentFont['desc']['Descent']) AND ($this->CurrentFont['desc']['Descent'] <= 0)) {
+ $font_descent = (- $this->CurrentFont['desc']['Descent'] * $size / 1000);
+ }
+ if (!isset($font_ascent) AND !isset($font_descent)) {
+ // core font
+ $font_ascent = 0.76 * $font_height;
+ $font_descent = $font_height - $font_ascent;
+ } elseif (!isset($font_descent)) {
+ $font_descent = $font_height - $font_ascent;
+ } elseif (!isset($font_ascent)) {
+ $font_ascent = $font_height - $font_descent;
+ }
+ $this->FontAscent = ($font_ascent / $this->k);
+ $this->FontDescent = ($font_descent / $this->k);
+ if ($out AND ($this->page > 0) AND (isset($this->CurrentFont['i'])) AND ($this->state == 2)) {
+ $this->_out(sprintf('BT /F%d %F Tf ET', $this->CurrentFont['i'], $this->FontSizePt));
+ }
+ }
+
+ /**
+ * Returns the bounding box of the current font in user units.
+ * @return array
+ * @public
+ * @since 5.9.152 (2012-03-23)
+ */
+ public function getFontBBox() {
+ $fbbox = array();
+ if (isset($this->CurrentFont['desc']['FontBBox'])) {
+ $tmpbbox = explode(' ', substr($this->CurrentFont['desc']['FontBBox'], 1, -1));
+ $fbbox = array_map(array($this,'getAbsFontMeasure'), $tmpbbox);
+ } else {
+ // Find max width
+ if (isset($this->CurrentFont['desc']['MaxWidth'])) {
+ $maxw = $this->getAbsFontMeasure(intval($this->CurrentFont['desc']['MaxWidth']));
+ } else {
+ $maxw = 0;
+ if (isset($this->CurrentFont['desc']['MissingWidth'])) {
+ $maxw = max($maxw, $this->CurrentFont['desc']['MissingWidth']);
+ }
+ if (isset($this->CurrentFont['desc']['AvgWidth'])) {
+ $maxw = max($maxw, $this->CurrentFont['desc']['AvgWidth']);
+ }
+ if (isset($this->CurrentFont['dw'])) {
+ $maxw = max($maxw, $this->CurrentFont['dw']);
+ }
+ foreach ($this->CurrentFont['cw'] as $char => $w) {
+ $maxw = max($maxw, $w);
+ }
+ if ($maxw == 0) {
+ $maxw = 600;
+ }
+ $maxw = $this->getAbsFontMeasure($maxw);
+ }
+ $fbbox = array(0, (0 - $this->FontDescent), $maxw, $this->FontAscent);
+ }
+ return $fbbox;
+ }
+
+ /**
+ * Convert a relative font measure into absolute value.
+ * @param $s (int) Font measure.
+ * @return float Absolute measure.
+ * @since 5.9.186 (2012-09-13)
+ */
+ public function getAbsFontMeasure($s) {
+ return ($s * $this->FontSize / 1000);
+ }
+
+ /**
+ * Returns the glyph bounding box of the specified character in the current font in user units.
+ * @param $char (int) Input character code.
+ * @return mixed array(xMin, yMin, xMax, yMax) or FALSE if not defined.
+ * @since 5.9.186 (2012-09-13)
+ */
+ public function getCharBBox($char) {
+ if (isset($this->CurrentFont['cbbox'][$char])) {
+ return array_map(array($this,'getAbsFontMeasure'), $this->CurrentFont['cbbox'][intval($char)]);
+ }
+ return false;
+ }
+
+ /**
+ * Return the font descent value
+ * @param $font (string) font name
+ * @param $style (string) font style
+ * @param $size (float) The size (in points)
+ * @return int font descent
+ * @public
+ * @author Nicola Asuni
+ * @since 4.9.003 (2010-03-30)
+ */
+ public function getFontDescent($font, $style='', $size=0) {
+ $fontdata = $this->AddFont($font, $style);
+ $fontinfo = $this->getFontBuffer($fontdata['fontkey']);
+ if (isset($fontinfo['desc']['Descent']) AND ($fontinfo['desc']['Descent'] <= 0)) {
+ $descent = (- $fontinfo['desc']['Descent'] * $size / 1000);
+ } else {
+ $descent = (1.219 * 0.24 * $size);
+ }
+ return ($descent / $this->k);
+ }
+
+ /**
+ * Return the font ascent value.
+ * @param $font (string) font name
+ * @param $style (string) font style
+ * @param $size (float) The size (in points)
+ * @return int font ascent
+ * @public
+ * @author Nicola Asuni
+ * @since 4.9.003 (2010-03-30)
+ */
+ public function getFontAscent($font, $style='', $size=0) {
+ $fontdata = $this->AddFont($font, $style);
+ $fontinfo = $this->getFontBuffer($fontdata['fontkey']);
+ if (isset($fontinfo['desc']['Ascent']) AND ($fontinfo['desc']['Ascent'] > 0)) {
+ $ascent = ($fontinfo['desc']['Ascent'] * $size / 1000);
+ } else {
+ $ascent = 1.219 * 0.76 * $size;
+ }
+ return ($ascent / $this->k);
+ }
+
+ /**
+ * Return true in the character is present in the specified font.
+ * @param $char (mixed) Character to check (integer value or string)
+ * @param $font (string) Font name (family name).
+ * @param $style (string) Font style.
+ * @return (boolean) true if the char is defined, false otherwise.
+ * @public
+ * @since 5.9.153 (2012-03-28)
+ */
+ public function isCharDefined($char, $font='', $style='') {
+ if (is_string($char)) {
+ // get character code
+ $char = TCPDF_FONTS::UTF8StringToArray($char, $this->isunicode, $this->CurrentFont);
+ $char = $char[0];
+ }
+ if (TCPDF_STATIC::empty_string($font)) {
+ if (TCPDF_STATIC::empty_string($style)) {
+ return (isset($this->CurrentFont['cw'][intval($char)]));
+ }
+ $font = $this->FontFamily;
+ }
+ $fontdata = $this->AddFont($font, $style);
+ $fontinfo = $this->getFontBuffer($fontdata['fontkey']);
+ return (isset($fontinfo['cw'][intval($char)]));
+ }
+
+ /**
+ * Replace missing font characters on selected font with specified substitutions.
+ * @param $text (string) Text to process.
+ * @param $font (string) Font name (family name).
+ * @param $style (string) Font style.
+ * @param $subs (array) Array of possible character substitutions. The key is the character to check (integer value) and the value is a single intege value or an array of possible substitutes.
+ * @return (string) Processed text.
+ * @public
+ * @since 5.9.153 (2012-03-28)
+ */
+ public function replaceMissingChars($text, $font='', $style='', $subs=array()) {
+ if (empty($subs)) {
+ return $text;
+ }
+ if (TCPDF_STATIC::empty_string($font)) {
+ $font = $this->FontFamily;
+ }
+ $fontdata = $this->AddFont($font, $style);
+ $fontinfo = $this->getFontBuffer($fontdata['fontkey']);
+ $uniarr = TCPDF_FONTS::UTF8StringToArray($text, $this->isunicode, $this->CurrentFont);
+ foreach ($uniarr as $k => $chr) {
+ if (!isset($fontinfo['cw'][$chr])) {
+ // this character is missing on the selected font
+ if (isset($subs[$chr])) {
+ // we have available substitutions
+ if (is_array($subs[$chr])) {
+ foreach($subs[$chr] as $s) {
+ if (isset($fontinfo['cw'][$s])) {
+ $uniarr[$k] = $s;
+ break;
+ }
+ }
+ } elseif (isset($fontinfo['cw'][$subs[$chr]])) {
+ $uniarr[$k] = $subs[$chr];
+ }
+ }
+ }
+ }
+ return TCPDF_FONTS::UniArrSubString(TCPDF_FONTS::UTF8ArrayToUniArray($uniarr, $this->isunicode));
+ }
+
+ /**
+ * Defines the default monospaced font.
+ * @param $font (string) Font name.
+ * @public
+ * @since 4.5.025
+ */
+ public function SetDefaultMonospacedFont($font) {
+ $this->default_monospaced_font = $font;
+ }
+
+ /**
+ * Creates a new internal link and returns its identifier. An internal link is a clickable area which directs to another place within the document.<br />
+ * The identifier can then be passed to Cell(), Write(), Image() or Link(). The destination is defined with SetLink().
+ * @public
+ * @since 1.5
+ * @see Cell(), Write(), Image(), Link(), SetLink()
+ */
+ public function AddLink() {
+ //Create a new internal link
+ $n = count($this->links) + 1;
+ $this->links[$n] = array(0, 0);
+ return $n;
+ }
+
+ /**
+ * Defines the page and position a link points to.
+ * @param $link (int) The link identifier returned by AddLink()
+ * @param $y (float) Ordinate of target position; -1 indicates the current position. The default value is 0 (top of page)
+ * @param $page (int) Number of target page; -1 indicates the current page. This is the default value
+ * @public
+ * @since 1.5
+ * @see AddLink()
+ */
+ public function SetLink($link, $y=0, $page=-1) {
+ if ($y == -1) {
+ $y = $this->y;
+ }
+ if ($page == -1) {
+ $page = $this->page;
+ }
+ $this->links[$link] = array($page, $y);
+ }
+
+ /**
+ * Puts a link on a rectangular area of the page.
+ * Text or image links are generally put via Cell(), Write() or Image(), but this method can be useful for instance to define a clickable area inside an image.
+ * @param $x (float) Abscissa of the upper-left corner of the rectangle
+ * @param $y (float) Ordinate of the upper-left corner of the rectangle
+ * @param $w (float) Width of the rectangle
+ * @param $h (float) Height of the rectangle
+ * @param $link (mixed) URL or identifier returned by AddLink()
+ * @param $spaces (int) number of spaces on the text to link
+ * @public
+ * @since 1.5
+ * @see AddLink(), Annotation(), Cell(), Write(), Image()
+ */
+ public function Link($x, $y, $w, $h, $link, $spaces=0) {
+ $this->Annotation($x, $y, $w, $h, $link, array('Subtype'=>'Link'), $spaces);
+ }
+
+ /**
+ * Puts a markup annotation on a rectangular area of the page.
+ * !!!!THE ANNOTATION SUPPORT IS NOT YET FULLY IMPLEMENTED !!!!
+ * @param $x (float) Abscissa of the upper-left corner of the rectangle
+ * @param $y (float) Ordinate of the upper-left corner of the rectangle
+ * @param $w (float) Width of the rectangle
+ * @param $h (float) Height of the rectangle
+ * @param $text (string) annotation text or alternate content
+ * @param $opt (array) array of options (see section 8.4 of PDF reference 1.7).
+ * @param $spaces (int) number of spaces on the text to link
+ * @public
+ * @since 4.0.018 (2008-08-06)
+ */
+ public function Annotation($x, $y, $w, $h, $text, $opt=array('Subtype'=>'Text'), $spaces=0) {
+ if ($this->inxobj) {
+ // store parameters for later use on template
+ $this->xobjects[$this->xobjid]['annotations'][] = array('x' => $x, 'y' => $y, 'w' => $w, 'h' => $h, 'text' => $text, 'opt' => $opt, 'spaces' => $spaces);
+ return;
+ }
+ if ($x === '') {
+ $x = $this->x;
+ }
+ if ($y === '') {
+ $y = $this->y;
+ }
+ // check page for no-write regions and adapt page margins if necessary
+ list($x, $y) = $this->checkPageRegions($h, $x, $y);
+ // recalculate coordinates to account for graphic transformations
+ if (isset($this->transfmatrix) AND !empty($this->transfmatrix)) {
+ for ($i=$this->transfmatrix_key; $i > 0; --$i) {
+ $maxid = count($this->transfmatrix[$i]) - 1;
+ for ($j=$maxid; $j >= 0; --$j) {
+ $ctm = $this->transfmatrix[$i][$j];
+ if (isset($ctm['a'])) {
+ $x = $x * $this->k;
+ $y = ($this->h - $y) * $this->k;
+ $w = $w * $this->k;
+ $h = $h * $this->k;
+ // top left
+ $xt = $x;
+ $yt = $y;
+ $x1 = ($ctm['a'] * $xt) + ($ctm['c'] * $yt) + $ctm['e'];
+ $y1 = ($ctm['b'] * $xt) + ($ctm['d'] * $yt) + $ctm['f'];
+ // top right
+ $xt = $x + $w;
+ $yt = $y;
+ $x2 = ($ctm['a'] * $xt) + ($ctm['c'] * $yt) + $ctm['e'];
+ $y2 = ($ctm['b'] * $xt) + ($ctm['d'] * $yt) + $ctm['f'];
+ // bottom left
+ $xt = $x;
+ $yt = $y - $h;
+ $x3 = ($ctm['a'] * $xt) + ($ctm['c'] * $yt) + $ctm['e'];
+ $y3 = ($ctm['b'] * $xt) + ($ctm['d'] * $yt) + $ctm['f'];
+ // bottom right
+ $xt = $x + $w;
+ $yt = $y - $h;
+ $x4 = ($ctm['a'] * $xt) + ($ctm['c'] * $yt) + $ctm['e'];
+ $y4 = ($ctm['b'] * $xt) + ($ctm['d'] * $yt) + $ctm['f'];
+ // new coordinates (rectangle area)
+ $x = min($x1, $x2, $x3, $x4);
+ $y = max($y1, $y2, $y3, $y4);
+ $w = (max($x1, $x2, $x3, $x4) - $x) / $this->k;
+ $h = ($y - min($y1, $y2, $y3, $y4)) / $this->k;
+ $x = $x / $this->k;
+ $y = $this->h - ($y / $this->k);
+ }
+ }
+ }
+ }
+ if ($this->page <= 0) {
+ $page = 1;
+ } else {
+ $page = $this->page;
+ }
+ if (!isset($this->PageAnnots[$page])) {
+ $this->PageAnnots[$page] = array();
+ }
+ $this->PageAnnots[$page][] = array('n' => ++$this->n, 'x' => $x, 'y' => $y, 'w' => $w, 'h' => $h, 'txt' => $text, 'opt' => $opt, 'numspaces' => $spaces);
+ if (!$this->pdfa_mode) {
+ if ((($opt['Subtype'] == 'FileAttachment') OR ($opt['Subtype'] == 'Sound')) AND (!TCPDF_STATIC::empty_string($opt['FS']))
+ AND (@file_exists($opt['FS']) OR TCPDF_STATIC::isValidURL($opt['FS']))
+ AND (!isset($this->embeddedfiles[basename($opt['FS'])]))) {
+ $this->embeddedfiles[basename($opt['FS'])] = array('f' => ++$this->n, 'n' => ++$this->n, 'file' => $opt['FS']);
+ }
+ }
+ // Add widgets annotation's icons
+ if (isset($opt['mk']['i']) AND @file_exists($opt['mk']['i'])) {
+ $this->Image($opt['mk']['i'], '', '', 10, 10, '', '', '', false, 300, '', false, false, 0, false, true);
+ }
+ if (isset($opt['mk']['ri']) AND @file_exists($opt['mk']['ri'])) {
+ $this->Image($opt['mk']['ri'], '', '', 0, 0, '', '', '', false, 300, '', false, false, 0, false, true);
+ }
+ if (isset($opt['mk']['ix']) AND @file_exists($opt['mk']['ix'])) {
+ $this->Image($opt['mk']['ix'], '', '', 0, 0, '', '', '', false, 300, '', false, false, 0, false, true);
+ }
+ }
+
+ /**
+ * Embedd the attached files.
+ * @since 4.4.000 (2008-12-07)
+ * @protected
+ * @see Annotation()
+ */
+ protected function _putEmbeddedFiles() {
+ if ($this->pdfa_mode) {
+ // embedded files are not allowed in PDF/A mode
+ return;
+ }
+ reset($this->embeddedfiles);
+ foreach ($this->embeddedfiles as $filename => $filedata) {
+ $data = TCPDF_STATIC::fileGetContents($filedata['file']);
+ if ($data !== FALSE) {
+ $rawsize = strlen($data);
+ if ($rawsize > 0) {
+ // update name tree
+ $this->efnames[$filename] = $filedata['f'].' 0 R';
+ // embedded file specification object
+ $out = $this->_getobj($filedata['f'])."\n";
+ $out .= '<</Type /Filespec /F '.$this->_datastring($filename, $filedata['f']).' /EF <</F '.$filedata['n'].' 0 R>> >>';
+ $out .= "\n".'endobj';
+ $this->_out($out);
+ // embedded file object
+ $filter = '';
+ if ($this->compress) {
+ $data = gzcompress($data);
+ $filter = ' /Filter /FlateDecode';
+ }
+ $stream = $this->_getrawstream($data, $filedata['n']);
+ $out = $this->_getobj($filedata['n'])."\n";
+ $out .= '<< /Type /EmbeddedFile'.$filter.' /Length '.strlen($stream).' /Params <</Size '.$rawsize.'>> >>';
+ $out .= ' stream'."\n".$stream."\n".'endstream';
+ $out .= "\n".'endobj';
+ $this->_out($out);
+ }
+ }
+ }
+ }
+
+ /**
+ * Prints a text cell at the specified position.
+ * This method allows to place a string precisely on the page.
+ * @param $x (float) Abscissa of the cell origin
+ * @param $y (float) Ordinate of the cell origin
+ * @param $txt (string) String to print
+ * @param $fstroke (int) outline size in user units (false = disable)
+ * @param $fclip (boolean) if true activate clipping mode (you must call StartTransform() before this function and StopTransform() to stop the clipping tranformation).
+ * @param $ffill (boolean) if true fills the text
+ * @param $border (mixed) Indicates if borders must be drawn around the cell. The value can be a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul> or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul> or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)))
+ * @param $ln (int) Indicates where the current position should go after the call. Possible values are:<ul><li>0: to the right (or left for RTL languages)</li><li>1: to the beginning of the next line</li><li>2: below</li></ul>Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: 0.
+ * @param $align (string) Allows to center or align the text. Possible values are:<ul><li>L or empty string: left align (default value)</li><li>C: center</li><li>R: right align</li><li>J: justify</li></ul>
+ * @param $fill (boolean) Indicates if the cell background must be painted (true) or transparent (false).
+ * @param $link (mixed) URL or identifier returned by AddLink().
+ * @param $stretch (int) font stretch mode: <ul><li>0 = disabled</li><li>1 = horizontal scaling only if text is larger than cell width</li><li>2 = forced horizontal scaling to fit cell width</li><li>3 = character spacing only if text is larger than cell width</li><li>4 = forced character spacing to fit cell width</li></ul> General font stretching and scaling values will be preserved when possible.
+ * @param $ignore_min_height (boolean) if true ignore automatic minimum height value.
+ * @param $calign (string) cell vertical alignment relative to the specified Y value. Possible values are:<ul><li>T : cell top</li><li>A : font top</li><li>L : font baseline</li><li>D : font bottom</li><li>B : cell bottom</li></ul>
+ * @param $valign (string) text vertical alignment inside the cell. Possible values are:<ul><li>T : top</li><li>C : center</li><li>B : bottom</li></ul>
+ * @param $rtloff (boolean) if true uses the page top-left corner as origin of axis for $x and $y initial position.
+ * @public
+ * @since 1.0
+ * @see Cell(), Write(), MultiCell(), WriteHTML(), WriteHTMLCell()
+ */
+ public function Text($x, $y, $txt, $fstroke=false, $fclip=false, $ffill=true, $border=0, $ln=0, $align='', $fill=false, $link='', $stretch=0, $ignore_min_height=false, $calign='T', $valign='M', $rtloff=false) {
+ $textrendermode = $this->textrendermode;
+ $textstrokewidth = $this->textstrokewidth;
+ $this->setTextRenderingMode($fstroke, $ffill, $fclip);
+ $this->SetXY($x, $y, $rtloff);
+ $this->Cell(0, 0, $txt, $border, $ln, $align, $fill, $link, $stretch, $ignore_min_height, $calign, $valign);
+ // restore previous rendering mode
+ $this->textrendermode = $textrendermode;
+ $this->textstrokewidth = $textstrokewidth;
+ }
+
+ /**
+ * Whenever a page break condition is met, the method is called, and the break is issued or not depending on the returned value.
+ * The default implementation returns a value according to the mode selected by SetAutoPageBreak().<br />
+ * This method is called automatically and should not be called directly by the application.
+ * @return boolean
+ * @public
+ * @since 1.4
+ * @see SetAutoPageBreak()
+ */
+ public function AcceptPageBreak() {
+ if ($this->num_columns > 1) {
+ // multi column mode
+ if ($this->current_column < ($this->num_columns - 1)) {
+ // go to next column
+ $this->selectColumn($this->current_column + 1);
+ } elseif ($this->AutoPageBreak) {
+ // add a new page
+ $this->AddPage();
+ // set first column
+ $this->selectColumn(0);
+ }
+ // avoid page breaking from checkPageBreak()
+ return false;
+ }
+ return $this->AutoPageBreak;
+ }
+
+ /**
+ * Add page if needed.
+ * @param $h (float) Cell height. Default value: 0.
+ * @param $y (mixed) starting y position, leave empty for current position.
+ * @param $addpage (boolean) if true add a page, otherwise only return the true/false state
+ * @return boolean true in case of page break, false otherwise.
+ * @since 3.2.000 (2008-07-01)
+ * @protected
+ */
+ protected function checkPageBreak($h=0, $y='', $addpage=true) {
+ if (TCPDF_STATIC::empty_string($y)) {
+ $y = $this->y;
+ }
+ $current_page = $this->page;
+ if ((($y + $h) > $this->PageBreakTrigger) AND ($this->inPageBody()) AND ($this->AcceptPageBreak())) {
+ if ($addpage) {
+ //Automatic page break
+ $x = $this->x;
+ $this->AddPage($this->CurOrientation);
+ $this->y = $this->tMargin;
+ $oldpage = $this->page - 1;
+ if ($this->rtl) {
+ if ($this->pagedim[$this->page]['orm'] != $this->pagedim[$oldpage]['orm']) {
+ $this->x = $x - ($this->pagedim[$this->page]['orm'] - $this->pagedim[$oldpage]['orm']);
+ } else {
+ $this->x = $x;
+ }
+ } else {
+ if ($this->pagedim[$this->page]['olm'] != $this->pagedim[$oldpage]['olm']) {
+ $this->x = $x + ($this->pagedim[$this->page]['olm'] - $this->pagedim[$oldpage]['olm']);
+ } else {
+ $this->x = $x;
+ }
+ }
+ }
+ return true;
+ }
+ if ($current_page != $this->page) {
+ // account for columns mode
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Prints a cell (rectangular area) with optional borders, background color and character string. The upper-left corner of the cell corresponds to the current position. The text can be aligned or centered. After the call, the current position moves to the right or to the next line. It is possible to put a link on the text.<br />
+ * If automatic page breaking is enabled and the cell goes beyond the limit, a page break is done before outputting.
+ * @param $w (float) Cell width. If 0, the cell extends up to the right margin.
+ * @param $h (float) Cell height. Default value: 0.
+ * @param $txt (string) String to print. Default value: empty string.
+ * @param $border (mixed) Indicates if borders must be drawn around the cell. The value can be a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul> or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul> or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)))
+ * @param $ln (int) Indicates where the current position should go after the call. Possible values are:<ul><li>0: to the right (or left for RTL languages)</li><li>1: to the beginning of the next line</li><li>2: below</li></ul> Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: 0.
+ * @param $align (string) Allows to center or align the text. Possible values are:<ul><li>L or empty string: left align (default value)</li><li>C: center</li><li>R: right align</li><li>J: justify</li></ul>
+ * @param $fill (boolean) Indicates if the cell background must be painted (true) or transparent (false).
+ * @param $link (mixed) URL or identifier returned by AddLink().
+ * @param $stretch (int) font stretch mode: <ul><li>0 = disabled</li><li>1 = horizontal scaling only if text is larger than cell width</li><li>2 = forced horizontal scaling to fit cell width</li><li>3 = character spacing only if text is larger than cell width</li><li>4 = forced character spacing to fit cell width</li></ul> General font stretching and scaling values will be preserved when possible.
+ * @param $ignore_min_height (boolean) if true ignore automatic minimum height value.
+ * @param $calign (string) cell vertical alignment relative to the specified Y value. Possible values are:<ul><li>T : cell top</li><li>C : center</li><li>B : cell bottom</li><li>A : font top</li><li>L : font baseline</li><li>D : font bottom</li></ul>
+ * @param $valign (string) text vertical alignment inside the cell. Possible values are:<ul><li>T : top</li><li>C : center</li><li>B : bottom</li></ul>
+ * @public
+ * @since 1.0
+ * @see SetFont(), SetDrawColor(), SetFillColor(), SetTextColor(), SetLineWidth(), AddLink(), Ln(), MultiCell(), Write(), SetAutoPageBreak()
+ */
+ public function Cell($w, $h=0, $txt='', $border=0, $ln=0, $align='', $fill=false, $link='', $stretch=0, $ignore_min_height=false, $calign='T', $valign='M') {
+ $prev_cell_margin = $this->cell_margin;
+ $prev_cell_padding = $this->cell_padding;
+ $this->adjustCellPadding($border);
+ if (!$ignore_min_height) {
+ $min_cell_height = ($this->FontSize * $this->cell_height_ratio) + $this->cell_padding['T'] + $this->cell_padding['B'];
+ if ($h < $min_cell_height) {
+ $h = $min_cell_height;
+ }
+ }
+ $this->checkPageBreak($h + $this->cell_margin['T'] + $this->cell_margin['B']);
+ // apply text shadow if enabled
+ if ($this->txtshadow['enabled']) {
+ // save data
+ $x = $this->x;
+ $y = $this->y;
+ $bc = $this->bgcolor;
+ $fc = $this->fgcolor;
+ $sc = $this->strokecolor;
+ $alpha = $this->alpha;
+ // print shadow
+ $this->x += $this->txtshadow['depth_w'];
+ $this->y += $this->txtshadow['depth_h'];
+ $this->SetFillColorArray($this->txtshadow['color']);
+ $this->SetTextColorArray($this->txtshadow['color']);
+ $this->SetDrawColorArray($this->txtshadow['color']);
+ if ($this->txtshadow['opacity'] != $alpha['CA']) {
+ $this->setAlpha($this->txtshadow['opacity'], $this->txtshadow['blend_mode']);
+ }
+ if ($this->state == 2) {
+ $this->_out($this->getCellCode($w, $h, $txt, $border, $ln, $align, $fill, $link, $stretch, true, $calign, $valign));
+ }
+ //restore data
+ $this->x = $x;
+ $this->y = $y;
+ $this->SetFillColorArray($bc);
+ $this->SetTextColorArray($fc);
+ $this->SetDrawColorArray($sc);
+ if ($this->txtshadow['opacity'] != $alpha['CA']) {
+ $this->setAlpha($alpha['CA'], $alpha['BM'], $alpha['ca'], $alpha['AIS']);
+ }
+ }
+ if ($this->state == 2) {
+ $this->_out($this->getCellCode($w, $h, $txt, $border, $ln, $align, $fill, $link, $stretch, true, $calign, $valign));
+ }
+ $this->cell_padding = $prev_cell_padding;
+ $this->cell_margin = $prev_cell_margin;
+ }
+
+ /**
+ * Returns the PDF string code to print a cell (rectangular area) with optional borders, background color and character string. The upper-left corner of the cell corresponds to the current position. The text can be aligned or centered. After the call, the current position moves to the right or to the next line. It is possible to put a link on the text.<br />
+ * If automatic page breaking is enabled and the cell goes beyond the limit, a page break is done before outputting.
+ * @param $w (float) Cell width. If 0, the cell extends up to the right margin.
+ * @param $h (float) Cell height. Default value: 0.
+ * @param $txt (string) String to print. Default value: empty string.
+ * @param $border (mixed) Indicates if borders must be drawn around the cell. The value can be a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul> or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul> or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)))
+ * @param $ln (int) Indicates where the current position should go after the call. Possible values are:<ul><li>0: to the right (or left for RTL languages)</li><li>1: to the beginning of the next line</li><li>2: below</li></ul>Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: 0.
+ * @param $align (string) Allows to center or align the text. Possible values are:<ul><li>L or empty string: left align (default value)</li><li>C: center</li><li>R: right align</li><li>J: justify</li></ul>
+ * @param $fill (boolean) Indicates if the cell background must be painted (true) or transparent (false).
+ * @param $link (mixed) URL or identifier returned by AddLink().
+ * @param $stretch (int) font stretch mode: <ul><li>0 = disabled</li><li>1 = horizontal scaling only if text is larger than cell width</li><li>2 = forced horizontal scaling to fit cell width</li><li>3 = character spacing only if text is larger than cell width</li><li>4 = forced character spacing to fit cell width</li></ul> General font stretching and scaling values will be preserved when possible.
+ * @param $ignore_min_height (boolean) if true ignore automatic minimum height value.
+ * @param $calign (string) cell vertical alignment relative to the specified Y value. Possible values are:<ul><li>T : cell top</li><li>C : center</li><li>B : cell bottom</li><li>A : font top</li><li>L : font baseline</li><li>D : font bottom</li></ul>
+ * @param $valign (string) text vertical alignment inside the cell. Possible values are:<ul><li>T : top</li><li>M : middle</li><li>B : bottom</li></ul>
+ * @return string containing cell code
+ * @protected
+ * @since 1.0
+ * @see Cell()
+ */
+ protected function getCellCode($w, $h=0, $txt='', $border=0, $ln=0, $align='', $fill=false, $link='', $stretch=0, $ignore_min_height=false, $calign='T', $valign='M') {
+ // replace 'NO-BREAK SPACE' (U+00A0) character with a simple space
+ $txt = str_replace(TCPDF_FONTS::unichr(160, $this->isunicode), ' ', $txt);
+ $prev_cell_margin = $this->cell_margin;
+ $prev_cell_padding = $this->cell_padding;
+ $txt = TCPDF_STATIC::removeSHY($txt, $this->isunicode);
+ $rs = ''; //string to be returned
+ $this->adjustCellPadding($border);
+ if (!$ignore_min_height) {
+ $min_cell_height = ($this->FontSize * $this->cell_height_ratio) + $this->cell_padding['T'] + $this->cell_padding['B'];
+ if ($h < $min_cell_height) {
+ $h = $min_cell_height;
+ }
+ }
+ $k = $this->k;
+ // check page for no-write regions and adapt page margins if necessary
+ list($this->x, $this->y) = $this->checkPageRegions($h, $this->x, $this->y);
+ if ($this->rtl) {
+ $x = $this->x - $this->cell_margin['R'];
+ } else {
+ $x = $this->x + $this->cell_margin['L'];
+ }
+ $y = $this->y + $this->cell_margin['T'];
+ $prev_font_stretching = $this->font_stretching;
+ $prev_font_spacing = $this->font_spacing;
+ // cell vertical alignment
+ switch ($calign) {
+ case 'A': {
+ // font top
+ switch ($valign) {
+ case 'T': {
+ // top
+ $y -= $this->cell_padding['T'];
+ break;
+ }
+ case 'B': {
+ // bottom
+ $y -= ($h - $this->cell_padding['B'] - $this->FontAscent - $this->FontDescent);
+ break;
+ }
+ default:
+ case 'C':
+ case 'M': {
+ // center
+ $y -= (($h - $this->FontAscent - $this->FontDescent) / 2);
+ break;
+ }
+ }
+ break;
+ }
+ case 'L': {
+ // font baseline
+ switch ($valign) {
+ case 'T': {
+ // top
+ $y -= ($this->cell_padding['T'] + $this->FontAscent);
+ break;
+ }
+ case 'B': {
+ // bottom
+ $y -= ($h - $this->cell_padding['B'] - $this->FontDescent);
+ break;
+ }
+ default:
+ case 'C':
+ case 'M': {
+ // center
+ $y -= (($h + $this->FontAscent - $this->FontDescent) / 2);
+ break;
+ }
+ }
+ break;
+ }
+ case 'D': {
+ // font bottom
+ switch ($valign) {
+ case 'T': {
+ // top
+ $y -= ($this->cell_padding['T'] + $this->FontAscent + $this->FontDescent);
+ break;
+ }
+ case 'B': {
+ // bottom
+ $y -= ($h - $this->cell_padding['B']);
+ break;
+ }
+ default:
+ case 'C':
+ case 'M': {
+ // center
+ $y -= (($h + $this->FontAscent + $this->FontDescent) / 2);
+ break;
+ }
+ }
+ break;
+ }
+ case 'B': {
+ // cell bottom
+ $y -= $h;
+ break;
+ }
+ case 'C':
+ case 'M': {
+ // cell center
+ $y -= ($h / 2);
+ break;
+ }
+ default:
+ case 'T': {
+ // cell top
+ break;
+ }
+ }
+ // text vertical alignment
+ switch ($valign) {
+ case 'T': {
+ // top
+ $yt = $y + $this->cell_padding['T'];
+ break;
+ }
+ case 'B': {
+ // bottom
+ $yt = $y + $h - $this->cell_padding['B'] - $this->FontAscent - $this->FontDescent;
+ break;
+ }
+ default:
+ case 'C':
+ case 'M': {
+ // center
+ $yt = $y + (($h - $this->FontAscent - $this->FontDescent) / 2);
+ break;
+ }
+ }
+ $basefonty = $yt + $this->FontAscent;
+ if (TCPDF_STATIC::empty_string($w) OR ($w <= 0)) {
+ if ($this->rtl) {
+ $w = $x - $this->lMargin;
+ } else {
+ $w = $this->w - $this->rMargin - $x;
+ }
+ }
+ $s = '';
+ // fill and borders
+ if (is_string($border) AND (strlen($border) == 4)) {
+ // full border
+ $border = 1;
+ }
+ if ($fill OR ($border == 1)) {
+ if ($fill) {
+ $op = ($border == 1) ? 'B' : 'f';
+ } else {
+ $op = 'S';
+ }
+ if ($this->rtl) {
+ $xk = (($x - $w) * $k);
+ } else {
+ $xk = ($x * $k);
+ }
+ $s .= sprintf('%F %F %F %F re %s ', $xk, (($this->h - $y) * $k), ($w * $k), (-$h * $k), $op);
+ }
+ // draw borders
+ $s .= $this->getCellBorder($x, $y, $w, $h, $border);
+ if ($txt != '') {
+ $txt2 = $txt;
+ if ($this->isunicode) {
+ if (($this->CurrentFont['type'] == 'core') OR ($this->CurrentFont['type'] == 'TrueType') OR ($this->CurrentFont['type'] == 'Type1')) {
+ $txt2 = TCPDF_FONTS::UTF8ToLatin1($txt2, $this->isunicode, $this->CurrentFont);
+ } else {
+ $unicode = TCPDF_FONTS::UTF8StringToArray($txt, $this->isunicode, $this->CurrentFont); // array of UTF-8 unicode values
+ $unicode = TCPDF_FONTS::utf8Bidi($unicode, '', $this->tmprtl, $this->isunicode, $this->CurrentFont);
+ // replace thai chars (if any)
+ if (defined('K_THAI_TOPCHARS') AND (K_THAI_TOPCHARS == true)) {
+ // number of chars
+ $numchars = count($unicode);
+ // po pla, for far, for fan
+ $longtail = array(0x0e1b, 0x0e1d, 0x0e1f);
+ // do chada, to patak
+ $lowtail = array(0x0e0e, 0x0e0f);
+ // mai hun arkad, sara i, sara ii, sara ue, sara uee
+ $upvowel = array(0x0e31, 0x0e34, 0x0e35, 0x0e36, 0x0e37);
+ // mai ek, mai tho, mai tri, mai chattawa, karan
+ $tonemark = array(0x0e48, 0x0e49, 0x0e4a, 0x0e4b, 0x0e4c);
+ // sara u, sara uu, pinthu
+ $lowvowel = array(0x0e38, 0x0e39, 0x0e3a);
+ $output = array();
+ for ($i = 0; $i < $numchars; $i++) {
+ if (($unicode[$i] >= 0x0e00) && ($unicode[$i] <= 0x0e5b)) {
+ $ch0 = $unicode[$i];
+ $ch1 = ($i > 0) ? $unicode[($i - 1)] : 0;
+ $ch2 = ($i > 1) ? $unicode[($i - 2)] : 0;
+ $chn = ($i < ($numchars - 1)) ? $unicode[($i + 1)] : 0;
+ if (in_array($ch0, $tonemark)) {
+ if ($chn == 0x0e33) {
+ // sara um
+ if (in_array($ch1, $longtail)) {
+ // tonemark at upper left
+ $output[] = $this->replaceChar($ch0, (0xf713 + $ch0 - 0x0e48));
+ } else {
+ // tonemark at upper right (normal position)
+ $output[] = $ch0;
+ }
+ } elseif (in_array($ch1, $longtail) OR (in_array($ch2, $longtail) AND in_array($ch1, $lowvowel))) {
+ // tonemark at lower left
+ $output[] = $this->replaceChar($ch0, (0xf705 + $ch0 - 0x0e48));
+ } elseif (in_array($ch1, $upvowel)) {
+ if (in_array($ch2, $longtail)) {
+ // tonemark at upper left
+ $output[] = $this->replaceChar($ch0, (0xf713 + $ch0 - 0x0e48));
+ } else {
+ // tonemark at upper right (normal position)
+ $output[] = $ch0;
+ }
+ } else {
+ // tonemark at lower right
+ $output[] = $this->replaceChar($ch0, (0xf70a + $ch0 - 0x0e48));
+ }
+ } elseif (($ch0 == 0x0e33) AND (in_array($ch1, $longtail) OR (in_array($ch2, $longtail) AND in_array($ch1, $tonemark)))) {
+ // add lower left nikhahit and sara aa
+ if ($this->isCharDefined(0xf711) AND $this->isCharDefined(0x0e32)) {
+ $output[] = 0xf711;
+ $this->CurrentFont['subsetchars'][0xf711] = true;
+ $output[] = 0x0e32;
+ $this->CurrentFont['subsetchars'][0x0e32] = true;
+ } else {
+ $output[] = $ch0;
+ }
+ } elseif (in_array($ch1, $longtail)) {
+ if ($ch0 == 0x0e31) {
+ // lower left mai hun arkad
+ $output[] = $this->replaceChar($ch0, 0xf710);
+ } elseif (in_array($ch0, $upvowel)) {
+ // lower left
+ $output[] = $this->replaceChar($ch0, (0xf701 + $ch0 - 0x0e34));
+ } elseif ($ch0 == 0x0e47) {
+ // lower left mai tai koo
+ $output[] = $this->replaceChar($ch0, 0xf712);
+ } else {
+ // normal character
+ $output[] = $ch0;
+ }
+ } elseif (in_array($ch1, $lowtail) AND in_array($ch0, $lowvowel)) {
+ // lower vowel
+ $output[] = $this->replaceChar($ch0, (0xf718 + $ch0 - 0x0e38));
+ } elseif (($ch0 == 0x0e0d) AND in_array($chn, $lowvowel)) {
+ // yo ying without lower part
+ $output[] = $this->replaceChar($ch0, 0xf70f);
+ } elseif (($ch0 == 0x0e10) AND in_array($chn, $lowvowel)) {
+ // tho santan without lower part
+ $output[] = $this->replaceChar($ch0, 0xf700);
+ } else {
+ $output[] = $ch0;
+ }
+ } else {
+ // non-thai character
+ $output[] = $unicode[$i];
+ }
+ }
+ $unicode = $output;
+ // update font subsetchars
+ $this->setFontSubBuffer($this->CurrentFont['fontkey'], 'subsetchars', $this->CurrentFont['subsetchars']);
+ } // end of K_THAI_TOPCHARS
+ $txt2 = TCPDF_FONTS::arrUTF8ToUTF16BE($unicode, false);
+ }
+ }
+ $txt2 = TCPDF_STATIC::_escape($txt2);
+ // get current text width (considering general font stretching and spacing)
+ $txwidth = $this->GetStringWidth($txt);
+ $width = $txwidth;
+ // check for stretch mode
+ if ($stretch > 0) {
+ // calculate ratio between cell width and text width
+ if ($width <= 0) {
+ $ratio = 1;
+ } else {
+ $ratio = (($w - $this->cell_padding['L'] - $this->cell_padding['R']) / $width);
+ }
+ // check if stretching is required
+ if (($ratio < 1) OR (($ratio > 1) AND (($stretch % 2) == 0))) {
+ // the text will be stretched to fit cell width
+ if ($stretch > 2) {
+ // set new character spacing
+ $this->font_spacing += ($w - $this->cell_padding['L'] - $this->cell_padding['R'] - $width) / (max(($this->GetNumChars($txt) - 1), 1) * ($this->font_stretching / 100));
+ } else {
+ // set new horizontal stretching
+ $this->font_stretching *= $ratio;
+ }
+ // recalculate text width (the text fills the entire cell)
+ $width = $w - $this->cell_padding['L'] - $this->cell_padding['R'];
+ // reset alignment
+ $align = '';
+ }
+ }
+ if ($this->font_stretching != 100) {
+ // apply font stretching
+ $rs .= sprintf('BT %F Tz ET ', $this->font_stretching);
+ }
+ if ($this->font_spacing != 0) {
+ // increase/decrease font spacing
+ $rs .= sprintf('BT %F Tc ET ', ($this->font_spacing * $this->k));
+ }
+ if ($this->ColorFlag AND ($this->textrendermode < 4)) {
+ $s .= 'q '.$this->TextColor.' ';
+ }
+ // rendering mode
+ $s .= sprintf('BT %d Tr %F w ET ', $this->textrendermode, ($this->textstrokewidth * $this->k));
+ // count number of spaces
+ $ns = substr_count($txt, chr(32));
+ // Justification
+ $spacewidth = 0;
+ if (($align == 'J') AND ($ns > 0)) {
+ if ($this->isUnicodeFont()) {
+ // get string width without spaces
+ $width = $this->GetStringWidth(str_replace(' ', '', $txt));
+ // calculate average space width
+ $spacewidth = -1000 * ($w - $width - $this->cell_padding['L'] - $this->cell_padding['R']) / ($ns?$ns:1) / ($this->FontSize?$this->FontSize:1);
+ if ($this->font_stretching != 100) {
+ // word spacing is affected by stretching
+ $spacewidth /= ($this->font_stretching / 100);
+ }
+ // set word position to be used with TJ operator
+ $txt2 = str_replace(chr(0).chr(32), ') '.sprintf('%F', $spacewidth).' (', $txt2);
+ $unicode_justification = true;
+ } else {
+ // get string width
+ $width = $txwidth;
+ // new space width
+ $spacewidth = (($w - $width - $this->cell_padding['L'] - $this->cell_padding['R']) / ($ns?$ns:1)) * $this->k;
+ if ($this->font_stretching != 100) {
+ // word spacing (Tw) is affected by stretching
+ $spacewidth /= ($this->font_stretching / 100);
+ }
+ // set word spacing
+ $rs .= sprintf('BT %F Tw ET ', $spacewidth);
+ }
+ $width = $w - $this->cell_padding['L'] - $this->cell_padding['R'];
+ }
+ // replace carriage return characters
+ $txt2 = str_replace("\r", ' ', $txt2);
+ switch ($align) {
+ case 'C': {
+ $dx = ($w - $width) / 2;
+ break;
+ }
+ case 'R': {
+ if ($this->rtl) {
+ $dx = $this->cell_padding['R'];
+ } else {
+ $dx = $w - $width - $this->cell_padding['R'];
+ }
+ break;
+ }
+ case 'L': {
+ if ($this->rtl) {
+ $dx = $w - $width - $this->cell_padding['L'];
+ } else {
+ $dx = $this->cell_padding['L'];
+ }
+ break;
+ }
+ case 'J':
+ default: {
+ if ($this->rtl) {
+ $dx = $this->cell_padding['R'];
+ } else {
+ $dx = $this->cell_padding['L'];
+ }
+ break;
+ }
+ }
+ if ($this->rtl) {
+ $xdx = $x - $dx - $width;
+ } else {
+ $xdx = $x + $dx;
+ }
+ $xdk = $xdx * $k;
+ // print text
+ $s .= sprintf('BT %F %F Td [(%s)] TJ ET', $xdk, (($this->h - $basefonty) * $k), $txt2);
+ if (isset($uniblock)) {
+ // print overlapping characters as separate string
+ $xshift = 0; // horizontal shift
+ $ty = (($this->h - $basefonty + (0.2 * $this->FontSize)) * $k);
+ $spw = (($w - $txwidth - $this->cell_padding['L'] - $this->cell_padding['R']) / ($ns?$ns:1));
+ foreach ($uniblock as $uk => $uniarr) {
+ if (($uk % 2) == 0) {
+ // x space to skip
+ if ($spacewidth != 0) {
+ // justification shift
+ $xshift += (count(array_keys($uniarr, 32)) * $spw);
+ }
+ $xshift += $this->GetArrStringWidth($uniarr); // + shift justification
+ } else {
+ // character to print
+ $topchr = TCPDF_FONTS::arrUTF8ToUTF16BE($uniarr, false);
+ $topchr = TCPDF_STATIC::_escape($topchr);
+ $s .= sprintf(' BT %F %F Td [(%s)] TJ ET', ($xdk + ($xshift * $k)), $ty, $topchr);
+ }
+ }
+ }
+ if ($this->underline) {
+ $s .= ' '.$this->_dounderlinew($xdx, $basefonty, $width);
+ }
+ if ($this->linethrough) {
+ $s .= ' '.$this->_dolinethroughw($xdx, $basefonty, $width);
+ }
+ if ($this->overline) {
+ $s .= ' '.$this->_dooverlinew($xdx, $basefonty, $width);
+ }
+ if ($this->ColorFlag AND ($this->textrendermode < 4)) {
+ $s .= ' Q';
+ }
+ if ($link) {
+ $this->Link($xdx, $yt, $width, ($this->FontAscent + $this->FontDescent), $link, $ns);
+ }
+ }
+ // output cell
+ if ($s) {
+ // output cell
+ $rs .= $s;
+ if ($this->font_spacing != 0) {
+ // reset font spacing mode
+ $rs .= ' BT 0 Tc ET';
+ }
+ if ($this->font_stretching != 100) {
+ // reset font stretching mode
+ $rs .= ' BT 100 Tz ET';
+ }
+ }
+ // reset word spacing
+ if (!$this->isUnicodeFont() AND ($align == 'J')) {
+ $rs .= ' BT 0 Tw ET';
+ }
+ // reset stretching and spacing
+ $this->font_stretching = $prev_font_stretching;
+ $this->font_spacing = $prev_font_spacing;
+ $this->lasth = $h;
+ if ($ln > 0) {
+ //Go to the beginning of the next line
+ $this->y = $y + $h + $this->cell_margin['B'];
+ if ($ln == 1) {
+ if ($this->rtl) {
+ $this->x = $this->w - $this->rMargin;
+ } else {
+ $this->x = $this->lMargin;
+ }
+ }
+ } else {
+ // go left or right by case
+ if ($this->rtl) {
+ $this->x = $x - $w - $this->cell_margin['L'];
+ } else {
+ $this->x = $x + $w + $this->cell_margin['R'];
+ }
+ }
+ $gstyles = ''.$this->linestyleWidth.' '.$this->linestyleCap.' '.$this->linestyleJoin.' '.$this->linestyleDash.' '.$this->DrawColor.' '.$this->FillColor."\n";
+ $rs = $gstyles.$rs;
+ $this->cell_padding = $prev_cell_padding;
+ $this->cell_margin = $prev_cell_margin;
+ return $rs;
+ }
+
+ /**
+ * Replace a char if is defined on the current font.
+ * @param $oldchar (int) Integer code (unicode) of the character to replace.
+ * @param $newchar (int) Integer code (unicode) of the new character.
+ * @return int the replaced char or the old char in case the new char i not defined
+ * @protected
+ * @since 5.9.167 (2012-06-22)
+ */
+ protected function replaceChar($oldchar, $newchar) {
+ if ($this->isCharDefined($newchar)) {
+ // add the new char on the subset list
+ $this->CurrentFont['subsetchars'][$newchar] = true;
+ // return the new character
+ return $newchar;
+ }
+ // return the old char
+ return $oldchar;
+ }
+
+ /**
+ * Returns the code to draw the cell border
+ * @param $x (float) X coordinate.
+ * @param $y (float) Y coordinate.
+ * @param $w (float) Cell width.
+ * @param $h (float) Cell height.
+ * @param $brd (mixed) Indicates if borders must be drawn around the cell. The value can be a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul> or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul> or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)))
+ * @return string containing cell border code
+ * @protected
+ * @see SetLineStyle()
+ * @since 5.7.000 (2010-08-02)
+ */
+ protected function getCellBorder($x, $y, $w, $h, $brd) {
+ $s = ''; // string to be returned
+ if (empty($brd)) {
+ return $s;
+ }
+ if ($brd == 1) {
+ $brd = array('LRTB' => true);
+ }
+ // calculate coordinates for border
+ $k = $this->k;
+ if ($this->rtl) {
+ $xeL = ($x - $w) * $k;
+ $xeR = $x * $k;
+ } else {
+ $xeL = $x * $k;
+ $xeR = ($x + $w) * $k;
+ }
+ $yeL = (($this->h - ($y + $h)) * $k);
+ $yeT = (($this->h - $y) * $k);
+ $xeT = $xeL;
+ $xeB = $xeR;
+ $yeR = $yeT;
+ $yeB = $yeL;
+ if (is_string($brd)) {
+ // convert string to array
+ $slen = strlen($brd);
+ $newbrd = array();
+ for ($i = 0; $i < $slen; ++$i) {
+ $newbrd[$brd[$i]] = array('cap' => 'square', 'join' => 'miter');
+ }
+ $brd = $newbrd;
+ }
+ if (isset($brd['mode'])) {
+ $mode = $brd['mode'];
+ unset($brd['mode']);
+ } else {
+ $mode = 'normal';
+ }
+ foreach ($brd as $border => $style) {
+ if (is_array($style) AND !empty($style)) {
+ // apply border style
+ $prev_style = $this->linestyleWidth.' '.$this->linestyleCap.' '.$this->linestyleJoin.' '.$this->linestyleDash.' '.$this->DrawColor.' ';
+ $s .= $this->SetLineStyle($style, true)."\n";
+ }
+ switch ($mode) {
+ case 'ext': {
+ $off = (($this->LineWidth / 2) * $k);
+ $xL = $xeL - $off;
+ $xR = $xeR + $off;
+ $yT = $yeT + $off;
+ $yL = $yeL - $off;
+ $xT = $xL;
+ $xB = $xR;
+ $yR = $yT;
+ $yB = $yL;
+ $w += $this->LineWidth;
+ $h += $this->LineWidth;
+ break;
+ }
+ case 'int': {
+ $off = ($this->LineWidth / 2) * $k;
+ $xL = $xeL + $off;
+ $xR = $xeR - $off;
+ $yT = $yeT - $off;
+ $yL = $yeL + $off;
+ $xT = $xL;
+ $xB = $xR;
+ $yR = $yT;
+ $yB = $yL;
+ $w -= $this->LineWidth;
+ $h -= $this->LineWidth;
+ break;
+ }
+ case 'normal':
+ default: {
+ $xL = $xeL;
+ $xT = $xeT;
+ $xB = $xeB;
+ $xR = $xeR;
+ $yL = $yeL;
+ $yT = $yeT;
+ $yB = $yeB;
+ $yR = $yeR;
+ break;
+ }
+ }
+ // draw borders by case
+ if (strlen($border) == 4) {
+ $s .= sprintf('%F %F %F %F re S ', $xT, $yT, ($w * $k), (-$h * $k));
+ } elseif (strlen($border) == 3) {
+ if (strpos($border,'B') === false) { // LTR
+ $s .= sprintf('%F %F m ', $xL, $yL);
+ $s .= sprintf('%F %F l ', $xT, $yT);
+ $s .= sprintf('%F %F l ', $xR, $yR);
+ $s .= sprintf('%F %F l ', $xB, $yB);
+ $s .= 'S ';
+ } elseif (strpos($border,'L') === false) { // TRB
+ $s .= sprintf('%F %F m ', $xT, $yT);
+ $s .= sprintf('%F %F l ', $xR, $yR);
+ $s .= sprintf('%F %F l ', $xB, $yB);
+ $s .= sprintf('%F %F l ', $xL, $yL);
+ $s .= 'S ';
+ } elseif (strpos($border,'T') === false) { // RBL
+ $s .= sprintf('%F %F m ', $xR, $yR);
+ $s .= sprintf('%F %F l ', $xB, $yB);
+ $s .= sprintf('%F %F l ', $xL, $yL);
+ $s .= sprintf('%F %F l ', $xT, $yT);
+ $s .= 'S ';
+ } elseif (strpos($border,'R') === false) { // BLT
+ $s .= sprintf('%F %F m ', $xB, $yB);
+ $s .= sprintf('%F %F l ', $xL, $yL);
+ $s .= sprintf('%F %F l ', $xT, $yT);
+ $s .= sprintf('%F %F l ', $xR, $yR);
+ $s .= 'S ';
+ }
+ } elseif (strlen($border) == 2) {
+ if ((strpos($border,'L') !== false) AND (strpos($border,'T') !== false)) { // LT
+ $s .= sprintf('%F %F m ', $xL, $yL);
+ $s .= sprintf('%F %F l ', $xT, $yT);
+ $s .= sprintf('%F %F l ', $xR, $yR);
+ $s .= 'S ';
+ } elseif ((strpos($border,'T') !== false) AND (strpos($border,'R') !== false)) { // TR
+ $s .= sprintf('%F %F m ', $xT, $yT);
+ $s .= sprintf('%F %F l ', $xR, $yR);
+ $s .= sprintf('%F %F l ', $xB, $yB);
+ $s .= 'S ';
+ } elseif ((strpos($border,'R') !== false) AND (strpos($border,'B') !== false)) { // RB
+ $s .= sprintf('%F %F m ', $xR, $yR);
+ $s .= sprintf('%F %F l ', $xB, $yB);
+ $s .= sprintf('%F %F l ', $xL, $yL);
+ $s .= 'S ';
+ } elseif ((strpos($border,'B') !== false) AND (strpos($border,'L') !== false)) { // BL
+ $s .= sprintf('%F %F m ', $xB, $yB);
+ $s .= sprintf('%F %F l ', $xL, $yL);
+ $s .= sprintf('%F %F l ', $xT, $yT);
+ $s .= 'S ';
+ } elseif ((strpos($border,'L') !== false) AND (strpos($border,'R') !== false)) { // LR
+ $s .= sprintf('%F %F m ', $xL, $yL);
+ $s .= sprintf('%F %F l ', $xT, $yT);
+ $s .= 'S ';
+ $s .= sprintf('%F %F m ', $xR, $yR);
+ $s .= sprintf('%F %F l ', $xB, $yB);
+ $s .= 'S ';
+ } elseif ((strpos($border,'T') !== false) AND (strpos($border,'B') !== false)) { // TB
+ $s .= sprintf('%F %F m ', $xT, $yT);
+ $s .= sprintf('%F %F l ', $xR, $yR);
+ $s .= 'S ';
+ $s .= sprintf('%F %F m ', $xB, $yB);
+ $s .= sprintf('%F %F l ', $xL, $yL);
+ $s .= 'S ';
+ }
+ } else { // strlen($border) == 1
+ if (strpos($border,'L') !== false) { // L
+ $s .= sprintf('%F %F m ', $xL, $yL);
+ $s .= sprintf('%F %F l ', $xT, $yT);
+ $s .= 'S ';
+ } elseif (strpos($border,'T') !== false) { // T
+ $s .= sprintf('%F %F m ', $xT, $yT);
+ $s .= sprintf('%F %F l ', $xR, $yR);
+ $s .= 'S ';
+ } elseif (strpos($border,'R') !== false) { // R
+ $s .= sprintf('%F %F m ', $xR, $yR);
+ $s .= sprintf('%F %F l ', $xB, $yB);
+ $s .= 'S ';
+ } elseif (strpos($border,'B') !== false) { // B
+ $s .= sprintf('%F %F m ', $xB, $yB);
+ $s .= sprintf('%F %F l ', $xL, $yL);
+ $s .= 'S ';
+ }
+ }
+ if (is_array($style) AND !empty($style)) {
+ // reset border style to previous value
+ $s .= "\n".$this->linestyleWidth.' '.$this->linestyleCap.' '.$this->linestyleJoin.' '.$this->linestyleDash.' '.$this->DrawColor."\n";
+ }
+ }
+ return $s;
+ }
+
+ /**
+ * This method allows printing text with line breaks.
+ * They can be automatic (as soon as the text reaches the right border of the cell) or explicit (via the \n character). As many cells as necessary are output, one below the other.<br />
+ * Text can be aligned, centered or justified. The cell block can be framed and the background painted.
+ * @param $w (float) Width of cells. If 0, they extend up to the right margin of the page.
+ * @param $h (float) Cell minimum height. The cell extends automatically if needed.
+ * @param $txt (string) String to print
+ * @param $border (mixed) Indicates if borders must be drawn around the cell. The value can be a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul> or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul> or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)))
+ * @param $align (string) Allows to center or align the text. Possible values are:<ul><li>L or empty string: left align</li><li>C: center</li><li>R: right align</li><li>J: justification (default value when $ishtml=false)</li></ul>
+ * @param $fill (boolean) Indicates if the cell background must be painted (true) or transparent (false).
+ * @param $ln (int) Indicates where the current position should go after the call. Possible values are:<ul><li>0: to the right</li><li>1: to the beginning of the next line [DEFAULT]</li><li>2: below</li></ul>
+ * @param $x (float) x position in user units
+ * @param $y (float) y position in user units
+ * @param $reseth (boolean) if true reset the last cell height (default true).
+ * @param $stretch (int) font stretch mode: <ul><li>0 = disabled</li><li>1 = horizontal scaling only if text is larger than cell width</li><li>2 = forced horizontal scaling to fit cell width</li><li>3 = character spacing only if text is larger than cell width</li><li>4 = forced character spacing to fit cell width</li></ul> General font stretching and scaling values will be preserved when possible.
+ * @param $ishtml (boolean) INTERNAL USE ONLY -- set to true if $txt is HTML content (default = false). Never set this parameter to true, use instead writeHTMLCell() or writeHTML() methods.
+ * @param $autopadding (boolean) if true, uses internal padding and automatically adjust it to account for line width.
+ * @param $maxh (float) maximum height. It should be >= $h and less then remaining space to the bottom of the page, or 0 for disable this feature. This feature works only when $ishtml=false.
+ * @param $valign (string) Vertical alignment of text (requires $maxh = $h > 0). Possible values are:<ul><li>T: TOP</li><li>M: middle</li><li>B: bottom</li></ul>. This feature works only when $ishtml=false and the cell must fit in a single page.
+ * @param $fitcell (boolean) if true attempt to fit all the text within the cell by reducing the font size (do not work in HTML mode).
+ * @return int Return the number of cells or 1 for html mode.
+ * @public
+ * @since 1.3
+ * @see SetFont(), SetDrawColor(), SetFillColor(), SetTextColor(), SetLineWidth(), Cell(), Write(), SetAutoPageBreak()
+ */
+ public function MultiCell($w, $h, $txt, $border=0, $align='J', $fill=false, $ln=1, $x='', $y='', $reseth=true, $stretch=0, $ishtml=false, $autopadding=true, $maxh=0, $valign='T', $fitcell=false) {
+ $prev_cell_margin = $this->cell_margin;
+ $prev_cell_padding = $this->cell_padding;
+ // adjust internal padding
+ $this->adjustCellPadding($border);
+ $mc_padding = $this->cell_padding;
+ $mc_margin = $this->cell_margin;
+ $this->cell_padding['T'] = 0;
+ $this->cell_padding['B'] = 0;
+ $this->setCellMargins(0, 0, 0, 0);
+ if (TCPDF_STATIC::empty_string($this->lasth) OR $reseth) {
+ // reset row height
+ $this->resetLastH();
+ }
+ if (!TCPDF_STATIC::empty_string($y)) {
+ $this->SetY($y);
+ } else {
+ $y = $this->GetY();
+ }
+ $resth = 0;
+ if (($h > 0) AND $this->inPageBody() AND (($y + $h + $mc_margin['T'] + $mc_margin['B']) > $this->PageBreakTrigger)) {
+ // spit cell in more pages/columns
+ $newh = ($this->PageBreakTrigger - $y);
+ $resth = ($h - $newh); // cell to be printed on the next page/column
+ $h = $newh;
+ }
+ // get current page number
+ $startpage = $this->page;
+ // get current column
+ $startcolumn = $this->current_column;
+ if (!TCPDF_STATIC::empty_string($x)) {
+ $this->SetX($x);
+ } else {
+ $x = $this->GetX();
+ }
+ // check page for no-write regions and adapt page margins if necessary
+ list($x, $y) = $this->checkPageRegions(0, $x, $y);
+ // apply margins
+ $oy = $y + $mc_margin['T'];
+ if ($this->rtl) {
+ $ox = ($this->w - $x - $mc_margin['R']);
+ } else {
+ $ox = ($x + $mc_margin['L']);
+ }
+ $this->x = $ox;
+ $this->y = $oy;
+ // set width
+ if (TCPDF_STATIC::empty_string($w) OR ($w <= 0)) {
+ if ($this->rtl) {
+ $w = ($this->x - $this->lMargin - $mc_margin['L']);
+ } else {
+ $w = ($this->w - $this->x - $this->rMargin - $mc_margin['R']);
+ }
+ }
+ // store original margin values
+ $lMargin = $this->lMargin;
+ $rMargin = $this->rMargin;
+ if ($this->rtl) {
+ $this->rMargin = ($this->w - $this->x);
+ $this->lMargin = ($this->x - $w);
+ } else {
+ $this->lMargin = ($this->x);
+ $this->rMargin = ($this->w - $this->x - $w);
+ }
+ $this->clMargin = $this->lMargin;
+ $this->crMargin = $this->rMargin;
+ if ($autopadding) {
+ // add top padding
+ $this->y += $mc_padding['T'];
+ }
+ if ($ishtml) { // ******* Write HTML text
+ $this->writeHTML($txt, true, false, $reseth, true, $align);
+ $nl = 1;
+ } else { // ******* Write simple text
+ $prev_FontSizePt = $this->FontSizePt;
+ // vertical alignment
+ if ($maxh > 0) {
+ // get text height
+ $text_height = $this->getStringHeight($w, $txt, $reseth, $autopadding, $mc_padding, $border);
+ if ($fitcell) {
+ // try to reduce font size to fit text on cell (use a quick search algorithm)
+ $fmin = 1;
+ $fmax = $this->FontSizePt;
+ $prev_text_height = $text_height;
+ $maxit = 100; // max number of iterations
+ while ($maxit > 0) {
+ $fmid = (($fmax + $fmin) / 2);
+ $this->SetFontSize($fmid, false);
+ $this->resetLastH();
+ $text_height = $this->getStringHeight($w, $txt, $reseth, $autopadding, $mc_padding, $border);
+ if (($text_height == $maxh) OR (($text_height < $maxh) AND ($fmin >= ($fmax - 0.01)))) {
+ break;
+ } elseif ($text_height < $maxh) {
+ $fmin = $fmid;
+ } else {
+ $fmax = $fmid;
+ }
+ --$maxit;
+ }
+ $this->SetFontSize($this->FontSizePt);
+ }
+ if ($text_height < $maxh) {
+ if ($valign == 'M') {
+ // text vertically centered
+ $this->y += (($maxh - $text_height) / 2);
+ } elseif ($valign == 'B') {
+ // text vertically aligned on bottom
+ $this->y += ($maxh - $text_height);
+ }
+ }
+ }
+ $nl = $this->Write($this->lasth, $txt, '', 0, $align, true, $stretch, false, true, $maxh, 0, $mc_margin);
+ if ($fitcell) {
+ // restore font size
+ $this->SetFontSize($prev_FontSizePt);
+ }
+ }
+ if ($autopadding) {
+ // add bottom padding
+ $this->y += $mc_padding['B'];
+ }
+ // Get end-of-text Y position
+ $currentY = $this->y;
+ // get latest page number
+ $endpage = $this->page;
+ if ($resth > 0) {
+ $skip = ($endpage - $startpage);
+ $tmpresth = $resth;
+ while ($tmpresth > 0) {
+ if ($skip <= 0) {
+ // add a page (or trig AcceptPageBreak() for multicolumn mode)
+ $this->checkPageBreak($this->PageBreakTrigger + 1);
+ }
+ if ($this->num_columns > 1) {
+ $tmpresth -= ($this->h - $this->y - $this->bMargin);
+ } else {
+ $tmpresth -= ($this->h - $this->tMargin - $this->bMargin);
+ }
+ --$skip;
+ }
+ $currentY = $this->y;
+ $endpage = $this->page;
+ }
+ // get latest column
+ $endcolumn = $this->current_column;
+ if ($this->num_columns == 0) {
+ $this->num_columns = 1;
+ }
+ // disable page regions check
+ $check_page_regions = $this->check_page_regions;
+ $this->check_page_regions = false;
+ // get border modes
+ $border_start = TCPDF_STATIC::getBorderMode($border, $position='start', $this->opencell);
+ $border_end = TCPDF_STATIC::getBorderMode($border, $position='end', $this->opencell);
+ $border_middle = TCPDF_STATIC::getBorderMode($border, $position='middle', $this->opencell);
+ // design borders around HTML cells.
+ for ($page = $startpage; $page <= $endpage; ++$page) { // for each page
+ $ccode = '';
+ $this->setPage($page);
+ if ($this->num_columns < 2) {
+ // single-column mode
+ $this->SetX($x);
+ $this->y = $this->tMargin;
+ }
+ // account for margin changes
+ if ($page > $startpage) {
+ if (($this->rtl) AND ($this->pagedim[$page]['orm'] != $this->pagedim[$startpage]['orm'])) {
+ $this->x -= ($this->pagedim[$page]['orm'] - $this->pagedim[$startpage]['orm']);
+ } elseif ((!$this->rtl) AND ($this->pagedim[$page]['olm'] != $this->pagedim[$startpage]['olm'])) {
+ $this->x += ($this->pagedim[$page]['olm'] - $this->pagedim[$startpage]['olm']);
+ }
+ }
+ if ($startpage == $endpage) {
+ // single page
+ for ($column = $startcolumn; $column <= $endcolumn; ++$column) { // for each column
+ $this->selectColumn($column);
+ if ($this->rtl) {
+ $this->x -= $mc_margin['R'];
+ } else {
+ $this->x += $mc_margin['L'];
+ }
+ if ($startcolumn == $endcolumn) { // single column
+ $cborder = $border;
+ $h = max($h, ($currentY - $oy));
+ $this->y = $oy;
+ } elseif ($column == $startcolumn) { // first column
+ $cborder = $border_start;
+ $this->y = $oy;
+ $h = $this->h - $this->y - $this->bMargin;
+ } elseif ($column == $endcolumn) { // end column
+ $cborder = $border_end;
+ $h = $currentY - $this->y;
+ if ($resth > $h) {
+ $h = $resth;
+ }
+ } else { // middle column
+ $cborder = $border_middle;
+ $h = $this->h - $this->y - $this->bMargin;
+ $resth -= $h;
+ }
+ $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
+ } // end for each column
+ } elseif ($page == $startpage) { // first page
+ for ($column = $startcolumn; $column < $this->num_columns; ++$column) { // for each column
+ $this->selectColumn($column);
+ if ($this->rtl) {
+ $this->x -= $mc_margin['R'];
+ } else {
+ $this->x += $mc_margin['L'];
+ }
+ if ($column == $startcolumn) { // first column
+ $cborder = $border_start;
+ $this->y = $oy;
+ $h = $this->h - $this->y - $this->bMargin;
+ } else { // middle column
+ $cborder = $border_middle;
+ $h = $this->h - $this->y - $this->bMargin;
+ $resth -= $h;
+ }
+ $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
+ } // end for each column
+ } elseif ($page == $endpage) { // last page
+ for ($column = 0; $column <= $endcolumn; ++$column) { // for each column
+ $this->selectColumn($column);
+ if ($this->rtl) {
+ $this->x -= $mc_margin['R'];
+ } else {
+ $this->x += $mc_margin['L'];
+ }
+ if ($column == $endcolumn) {
+ // end column
+ $cborder = $border_end;
+ $h = $currentY - $this->y;
+ if ($resth > $h) {
+ $h = $resth;
+ }
+ } else {
+ // middle column
+ $cborder = $border_middle;
+ $h = $this->h - $this->y - $this->bMargin;
+ $resth -= $h;
+ }
+ $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
+ } // end for each column
+ } else { // middle page
+ for ($column = 0; $column < $this->num_columns; ++$column) { // for each column
+ $this->selectColumn($column);
+ if ($this->rtl) {
+ $this->x -= $mc_margin['R'];
+ } else {
+ $this->x += $mc_margin['L'];
+ }
+ $cborder = $border_middle;
+ $h = $this->h - $this->y - $this->bMargin;
+ $resth -= $h;
+ $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
+ } // end for each column
+ }
+ if ($cborder OR $fill) {
+ $offsetlen = strlen($ccode);
+ // draw border and fill
+ if ($this->inxobj) {
+ // we are inside an XObject template
+ if (end($this->xobjects[$this->xobjid]['transfmrk']) !== false) {
+ $pagemarkkey = key($this->xobjects[$this->xobjid]['transfmrk']);
+ $pagemark = $this->xobjects[$this->xobjid]['transfmrk'][$pagemarkkey];
+ $this->xobjects[$this->xobjid]['transfmrk'][$pagemarkkey] += $offsetlen;
+ } else {
+ $pagemark = $this->xobjects[$this->xobjid]['intmrk'];
+ $this->xobjects[$this->xobjid]['intmrk'] += $offsetlen;
+ }
+ $pagebuff = $this->xobjects[$this->xobjid]['outdata'];
+ $pstart = substr($pagebuff, 0, $pagemark);
+ $pend = substr($pagebuff, $pagemark);
+ $this->xobjects[$this->xobjid]['outdata'] = $pstart.$ccode.$pend;
+ } else {
+ if (end($this->transfmrk[$this->page]) !== false) {
+ $pagemarkkey = key($this->transfmrk[$this->page]);
+ $pagemark = $this->transfmrk[$this->page][$pagemarkkey];
+ $this->transfmrk[$this->page][$pagemarkkey] += $offsetlen;
+ } elseif ($this->InFooter) {
+ $pagemark = $this->footerpos[$this->page];
+ $this->footerpos[$this->page] += $offsetlen;
+ } else {
+ $pagemark = $this->intmrk[$this->page];
+ $this->intmrk[$this->page] += $offsetlen;
+ }
+ $pagebuff = $this->getPageBuffer($this->page);
+ $pstart = substr($pagebuff, 0, $pagemark);
+ $pend = substr($pagebuff, $pagemark);
+ $this->setPageBuffer($this->page, $pstart.$ccode.$pend);
+ }
+ }
+ } // end for each page
+ // restore page regions check
+ $this->check_page_regions = $check_page_regions;
+ // Get end-of-cell Y position
+ $currentY = $this->GetY();
+ // restore previous values
+ if ($this->num_columns > 1) {
+ $this->selectColumn();
+ } else {
+ // restore original margins
+ $this->lMargin = $lMargin;
+ $this->rMargin = $rMargin;
+ if ($this->page > $startpage) {
+ // check for margin variations between pages (i.e. booklet mode)
+ $dl = ($this->pagedim[$this->page]['olm'] - $this->pagedim[$startpage]['olm']);
+ $dr = ($this->pagedim[$this->page]['orm'] - $this->pagedim[$startpage]['orm']);
+ if (($dl != 0) OR ($dr != 0)) {
+ $this->lMargin += $dl;
+ $this->rMargin += $dr;
+ }
+ }
+ }
+ if ($ln > 0) {
+ //Go to the beginning of the next line
+ $this->SetY($currentY + $mc_margin['B']);
+ if ($ln == 2) {
+ $this->SetX($x + $w + $mc_margin['L'] + $mc_margin['R']);
+ }
+ } else {
+ // go left or right by case
+ $this->setPage($startpage);
+ $this->y = $y;
+ $this->SetX($x + $w + $mc_margin['L'] + $mc_margin['R']);
+ }
+ $this->setContentMark();
+ $this->cell_padding = $prev_cell_padding;
+ $this->cell_margin = $prev_cell_margin;
+ $this->clMargin = $this->lMargin;
+ $this->crMargin = $this->rMargin;
+ return $nl;
+ }
+
+ /**
+ * This method return the estimated number of lines for print a simple text string using Multicell() method.
+ * @param $txt (string) String for calculating his height
+ * @param $w (float) Width of cells. If 0, they extend up to the right margin of the page.
+ * @param $reseth (boolean) if true reset the last cell height (default false).
+ * @param $autopadding (boolean) if true, uses internal padding and automatically adjust it to account for line width (default true).
+ * @param $cellpadding (float) Internal cell padding, if empty uses default cell padding.
+ * @param $border (mixed) Indicates if borders must be drawn around the cell. The value can be a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul> or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul> or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)))
+ * @return float Return the minimal height needed for multicell method for printing the $txt param.
+ * @author Alexander Escalona Fernández, Nicola Asuni
+ * @public
+ * @since 4.5.011
+ */
+ public function getNumLines($txt, $w=0, $reseth=false, $autopadding=true, $cellpadding='', $border=0) {
+ if ($txt === '') {
+ // empty string
+ return 1;
+ }
+ // adjust internal padding
+ $prev_cell_padding = $this->cell_padding;
+ $prev_lasth = $this->lasth;
+ if (is_array($cellpadding)) {
+ $this->cell_padding = $cellpadding;
+ }
+ $this->adjustCellPadding($border);
+ if (TCPDF_STATIC::empty_string($w) OR ($w <= 0)) {
+ if ($this->rtl) {
+ $w = $this->x - $this->lMargin;
+ } else {
+ $w = $this->w - $this->rMargin - $this->x;
+ }
+ }
+ $wmax = $w - $this->cell_padding['L'] - $this->cell_padding['R'];
+ if ($reseth) {
+ // reset row height
+ $this->resetLastH();
+ }
+ $lines = 1;
+ $sum = 0;
+ $chars = TCPDF_FONTS::utf8Bidi(TCPDF_FONTS::UTF8StringToArray($txt, $this->isunicode, $this->CurrentFont), $txt, $this->tmprtl, $this->isunicode, $this->CurrentFont);
+ $charsWidth = $this->GetArrStringWidth($chars, '', '', 0, true);
+ $length = count($chars);
+ $lastSeparator = -1;
+ for ($i = 0; $i < $length; ++$i) {
+ $charWidth = $charsWidth[$i];
+ if (preg_match($this->re_spaces, TCPDF_FONTS::unichr($chars[$i], $this->isunicode))) {
+ $lastSeparator = $i;
+ }
+ if ((($sum + $charWidth) > $wmax) OR ($chars[$i] == 10)) {
+ ++$lines;
+ if ($chars[$i] == 10) {
+ $lastSeparator = -1;
+ $sum = 0;
+ } elseif ($lastSeparator != -1) {
+ $i = $lastSeparator;
+ $lastSeparator = -1;
+ $sum = 0;
+ } else {
+ $sum = $charWidth;
+ }
+ } else {
+ $sum += $charWidth;
+ }
+ }
+ if ($chars[($length - 1)] == 10) {
+ --$lines;
+ }
+ $this->cell_padding = $prev_cell_padding;
+ $this->lasth = $prev_lasth;
+ return $lines;
+ }
+
+ /**
+ * This method return the estimated height needed for printing a simple text string using the Multicell() method.
+ * Generally, if you want to know the exact height for a block of content you can use the following alternative technique:
+ * @pre
+ * // store current object
+ * $pdf->startTransaction();
+ * // store starting values
+ * $start_y = $pdf->GetY();
+ * $start_page = $pdf->getPage();
+ * // call your printing functions with your parameters
+ * // - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+ * $pdf->MultiCell($w=0, $h=0, $txt, $border=1, $align='L', $fill=false, $ln=1, $x='', $y='', $reseth=true, $stretch=0, $ishtml=false, $autopadding=true, $maxh=0);
+ * // - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+ * // get the new Y
+ * $end_y = $pdf->GetY();
+ * $end_page = $pdf->getPage();
+ * // calculate height
+ * $height = 0;
+ * if ($end_page == $start_page) {
+ * $height = $end_y - $start_y;
+ * } else {
+ * for ($page=$start_page; $page <= $end_page; ++$page) {
+ * $this->setPage($page);
+ * if ($page == $start_page) {
+ * // first page
+ * $height = $this->h - $start_y - $this->bMargin;
+ * } elseif ($page == $end_page) {
+ * // last page
+ * $height = $end_y - $this->tMargin;
+ * } else {
+ * $height = $this->h - $this->tMargin - $this->bMargin;
+ * }
+ * }
+ * }
+ * // restore previous object
+ * $pdf = $pdf->rollbackTransaction();
+ *
+ * @param $w (float) Width of cells. If 0, they extend up to the right margin of the page.
+ * @param $txt (string) String for calculating his height
+ * @param $reseth (boolean) if true reset the last cell height (default false).
+ * @param $autopadding (boolean) if true, uses internal padding and automatically adjust it to account for line width (default true).
+ * @param $cellpadding (float) Internal cell padding, if empty uses default cell padding.
+ * @param $border (mixed) Indicates if borders must be drawn around the cell. The value can be a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul> or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul> or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)))
+ * @return float Return the minimal height needed for multicell method for printing the $txt param.
+ * @author Nicola Asuni, Alexander Escalona Fernández
+ * @public
+ */
+ public function getStringHeight($w, $txt, $reseth=false, $autopadding=true, $cellpadding='', $border=0) {
+ // adjust internal padding
+ $prev_cell_padding = $this->cell_padding;
+ $prev_lasth = $this->lasth;
+ if (is_array($cellpadding)) {
+ $this->cell_padding = $cellpadding;
+ }
+ $this->adjustCellPadding($border);
+ $lines = $this->getNumLines($txt, $w, $reseth, $autopadding, $cellpadding, $border);
+ $height = $lines * ($this->FontSize * $this->cell_height_ratio);
+ if ($autopadding) {
+ // add top and bottom padding
+ $height += ($this->cell_padding['T'] + $this->cell_padding['B']);
+ }
+ $this->cell_padding = $prev_cell_padding;
+ $this->lasth = $prev_lasth;
+ return $height;
+ }
+
+ /**
+ * This method prints text from the current position.<br />
+ * @param $h (float) Line height
+ * @param $txt (string) String to print
+ * @param $link (mixed) URL or identifier returned by AddLink()
+ * @param $fill (boolean) Indicates if the cell background must be painted (true) or transparent (false).
+ * @param $align (string) Allows to center or align the text. Possible values are:<ul><li>L or empty string: left align (default value)</li><li>C: center</li><li>R: right align</li><li>J: justify</li></ul>
+ * @param $ln (boolean) if true set cursor at the bottom of the line, otherwise set cursor at the top of the line.
+ * @param $stretch (int) font stretch mode: <ul><li>0 = disabled</li><li>1 = horizontal scaling only if text is larger than cell width</li><li>2 = forced horizontal scaling to fit cell width</li><li>3 = character spacing only if text is larger than cell width</li><li>4 = forced character spacing to fit cell width</li></ul> General font stretching and scaling values will be preserved when possible.
+ * @param $firstline (boolean) if true prints only the first line and return the remaining string.
+ * @param $firstblock (boolean) if true the string is the starting of a line.
+ * @param $maxh (float) maximum height. It should be >= $h and less then remaining space to the bottom of the page, or 0 for disable this feature.
+ * @param $wadj (float) first line width will be reduced by this amount (used in HTML mode).
+ * @param $margin (array) margin array of the parent container
+ * @return mixed Return the number of cells or the remaining string if $firstline = true.
+ * @public
+ * @since 1.5
+ */
+ public function Write($h, $txt, $link='', $fill=false, $align='', $ln=false, $stretch=0, $firstline=false, $firstblock=false, $maxh=0, $wadj=0, $margin='') {
+ // check page for no-write regions and adapt page margins if necessary
+ list($this->x, $this->y) = $this->checkPageRegions($h, $this->x, $this->y);
+ if (strlen($txt) == 0) {
+ // fix empty text
+ $txt = ' ';
+ }
+ if ($margin === '') {
+ // set default margins
+ $margin = $this->cell_margin;
+ }
+ // remove carriage returns
+ $s = str_replace("\r", '', $txt);
+ // check if string contains arabic text
+ if (preg_match(TCPDF_FONT_DATA::$uni_RE_PATTERN_ARABIC, $s)) {
+ $arabic = true;
+ } else {
+ $arabic = false;
+ }
+ // check if string contains RTL text
+ if ($arabic OR ($this->tmprtl == 'R') OR preg_match(TCPDF_FONT_DATA::$uni_RE_PATTERN_RTL, $s)) {
+ $rtlmode = true;
+ } else {
+ $rtlmode = false;
+ }
+ // get a char width
+ $chrwidth = $this->GetCharWidth(46); // dot character
+ // get array of unicode values
+ $chars = TCPDF_FONTS::UTF8StringToArray($s, $this->isunicode, $this->CurrentFont);
+ // calculate maximum width for a single character on string
+ $chrw = $this->GetArrStringWidth($chars, '', '', 0, true);
+ array_walk($chrw, array($this, 'getRawCharWidth'));
+ $maxchwidth = max($chrw);
+ // get array of chars
+ $uchars = TCPDF_FONTS::UTF8ArrayToUniArray($chars, $this->isunicode);
+ // get the number of characters
+ $nb = count($chars);
+ // replacement for SHY character (minus symbol)
+ $shy_replacement = 45;
+ $shy_replacement_char = TCPDF_FONTS::unichr($shy_replacement, $this->isunicode);
+ // widht for SHY replacement
+ $shy_replacement_width = $this->GetCharWidth($shy_replacement);
+ // max Y
+ $maxy = $this->y + $maxh - $h - $this->cell_padding['T'] - $this->cell_padding['B'];
+ // page width
+ $pw = $w = $this->w - $this->lMargin - $this->rMargin;
+ // calculate remaining line width ($w)
+ if ($this->rtl) {
+ $w = $this->x - $this->lMargin;
+ } else {
+ $w = $this->w - $this->rMargin - $this->x;
+ }
+ // max column width
+ $wmax = ($w - $wadj);
+ if (!$firstline) {
+ $wmax -= ($this->cell_padding['L'] + $this->cell_padding['R']);
+ }
+ if ((!$firstline) AND (($chrwidth > $wmax) OR ($maxchwidth > $wmax))) {
+ // the maximum width character do not fit on column
+ return '';
+ }
+ // minimum row height
+ $row_height = max($h, $this->FontSize * $this->cell_height_ratio);
+ $start_page = $this->page;
+ $i = 0; // character position
+ $j = 0; // current starting position
+ $sep = -1; // position of the last blank space
+ $shy = false; // true if the last blank is a soft hypen (SHY)
+ $l = 0; // current string length
+ $nl = 0; //number of lines
+ $linebreak = false;
+ $pc = 0; // previous character
+ // for each character
+ while ($i < $nb) {
+ if (($maxh > 0) AND ($this->y >= $maxy) ) {
+ break;
+ }
+ //Get the current character
+ $c = $chars[$i];
+ if ($c == 10) { // 10 = "\n" = new line
+ //Explicit line break
+ if ($align == 'J') {
+ if ($this->rtl) {
+ $talign = 'R';
+ } else {
+ $talign = 'L';
+ }
+ } else {
+ $talign = $align;
+ }
+ $tmpstr = TCPDF_FONTS::UniArrSubString($uchars, $j, $i);
+ if ($firstline) {
+ $startx = $this->x;
+ $tmparr = array_slice($chars, $j, ($i - $j));
+ if ($rtlmode) {
+ $tmparr = TCPDF_FONTS::utf8Bidi($tmparr, $tmpstr, $this->tmprtl, $this->isunicode, $this->CurrentFont);
+ }
+ $linew = $this->GetArrStringWidth($tmparr);
+ unset($tmparr);
+ if ($this->rtl) {
+ $this->endlinex = $startx - $linew;
+ } else {
+ $this->endlinex = $startx + $linew;
+ }
+ $w = $linew;
+ $tmpcellpadding = $this->cell_padding;
+ if ($maxh == 0) {
+ $this->SetCellPadding(0);
+ }
+ }
+ if ($firstblock AND $this->isRTLTextDir()) {
+ $tmpstr = $this->stringRightTrim($tmpstr);
+ }
+ // Skip newlines at the begining of a page or column
+ if (!empty($tmpstr) OR ($this->y < ($this->PageBreakTrigger - $row_height))) {
+ $this->Cell($w, $h, $tmpstr, 0, 1, $talign, $fill, $link, $stretch);
+ }
+ unset($tmpstr);
+ if ($firstline) {
+ $this->cell_padding = $tmpcellpadding;
+ return (TCPDF_FONTS::UniArrSubString($uchars, $i));
+ }
+ ++$nl;
+ $j = $i + 1;
+ $l = 0;
+ $sep = -1;
+ $shy = false;
+ // account for margin changes
+ if ((($this->y + $this->lasth) > $this->PageBreakTrigger) AND ($this->inPageBody())) {
+ $this->AcceptPageBreak();
+ if ($this->rtl) {
+ $this->x -= $margin['R'];
+ } else {
+ $this->x += $margin['L'];
+ }
+ $this->lMargin += $margin['L'];
+ $this->rMargin += $margin['R'];
+ }
+ $w = $this->getRemainingWidth();
+ $wmax = ($w - $this->cell_padding['L'] - $this->cell_padding['R']);
+ } else {
+ // 160 is the non-breaking space.
+ // 173 is SHY (Soft Hypen).
+ // \p{Z} or \p{Separator}: any kind of Unicode whitespace or invisible separator.
+ // \p{Lo} or \p{Other_Letter}: a Unicode letter or ideograph that does not have lowercase and uppercase variants.
+ // \p{Lo} is needed because Chinese characters are packed next to each other without spaces in between.
+ if (($c != 160)
+ AND (($c == 173)
+ OR preg_match($this->re_spaces, TCPDF_FONTS::unichr($c, $this->isunicode))
+ OR (($c == 45)
+ AND ($i < ($nb - 1))
+ AND @preg_match('/[\p{L}]/'.$this->re_space['m'], TCPDF_FONTS::unichr($pc, $this->isunicode))
+ AND @preg_match('/[\p{L}]/'.$this->re_space['m'], TCPDF_FONTS::unichr($chars[($i + 1)], $this->isunicode))
+ )
+ )
+ ) {
+ // update last blank space position
+ $sep = $i;
+ // check if is a SHY
+ if (($c == 173) OR ($c == 45)) {
+ $shy = true;
+ if ($pc == 45) {
+ $tmp_shy_replacement_width = 0;
+ $tmp_shy_replacement_char = '';
+ } else {
+ $tmp_shy_replacement_width = $shy_replacement_width;
+ $tmp_shy_replacement_char = $shy_replacement_char;
+ }
+ } else {
+ $shy = false;
+ }
+ }
+ // update string length
+ if ($this->isUnicodeFont() AND ($arabic)) {
+ // with bidirectional algorithm some chars may be changed affecting the line length
+ // *** very slow ***
+ $l = $this->GetArrStringWidth(TCPDF_FONTS::utf8Bidi(array_slice($chars, $j, ($i - $j)), '', $this->tmprtl, $this->isunicode, $this->CurrentFont));
+ } else {
+ $l += $this->GetCharWidth($c);
+ }
+ if (($l > $wmax) OR (($c == 173) AND (($l + $tmp_shy_replacement_width) > $wmax)) ) {
+ // we have reached the end of column
+ if ($sep == -1) {
+ // check if the line was already started
+ if (($this->rtl AND ($this->x <= ($this->w - $this->rMargin - $this->cell_padding['R'] - $margin['R'] - $chrwidth)))
+ OR ((!$this->rtl) AND ($this->x >= ($this->lMargin + $this->cell_padding['L'] + $margin['L'] + $chrwidth)))) {
+ // print a void cell and go to next line
+ $this->Cell($w, $h, '', 0, 1);
+ $linebreak = true;
+ if ($firstline) {
+ return (TCPDF_FONTS::UniArrSubString($uchars, $j));
+ }
+ } else {
+ // truncate the word because do not fit on column
+ $tmpstr = TCPDF_FONTS::UniArrSubString($uchars, $j, $i);
+ if ($firstline) {
+ $startx = $this->x;
+ $tmparr = array_slice($chars, $j, ($i - $j));
+ if ($rtlmode) {
+ $tmparr = TCPDF_FONTS::utf8Bidi($tmparr, $tmpstr, $this->tmprtl, $this->isunicode, $this->CurrentFont);
+ }
+ $linew = $this->GetArrStringWidth($tmparr);
+ unset($tmparr);
+ if ($this->rtl) {
+ $this->endlinex = $startx - $linew;
+ } else {
+ $this->endlinex = $startx + $linew;
+ }
+ $w = $linew;
+ $tmpcellpadding = $this->cell_padding;
+ if ($maxh == 0) {
+ $this->SetCellPadding(0);
+ }
+ }
+ if ($firstblock AND $this->isRTLTextDir()) {
+ $tmpstr = $this->stringRightTrim($tmpstr);
+ }
+ $this->Cell($w, $h, $tmpstr, 0, 1, $align, $fill, $link, $stretch);
+ unset($tmpstr);
+ if ($firstline) {
+ $this->cell_padding = $tmpcellpadding;
+ return (TCPDF_FONTS::UniArrSubString($uchars, $i));
+ }
+ $j = $i;
+ --$i;
+ }
+ } else {
+ // word wrapping
+ if ($this->rtl AND (!$firstblock) AND ($sep < $i)) {
+ $endspace = 1;
+ } else {
+ $endspace = 0;
+ }
+ // check the length of the next string
+ $strrest = TCPDF_FONTS::UniArrSubString($uchars, ($sep + $endspace));
+ $nextstr = TCPDF_STATIC::pregSplit('/'.$this->re_space['p'].'/', $this->re_space['m'], $this->stringTrim($strrest));
+ if (isset($nextstr[0]) AND ($this->GetStringWidth($nextstr[0]) > $pw)) {
+ // truncate the word because do not fit on a full page width
+ $tmpstr = TCPDF_FONTS::UniArrSubString($uchars, $j, $i);
+ if ($firstline) {
+ $startx = $this->x;
+ $tmparr = array_slice($chars, $j, ($i - $j));
+ if ($rtlmode) {
+ $tmparr = TCPDF_FONTS::utf8Bidi($tmparr, $tmpstr, $this->tmprtl, $this->isunicode, $this->CurrentFont);
+ }
+ $linew = $this->GetArrStringWidth($tmparr);
+ unset($tmparr);
+ if ($this->rtl) {
+ $this->endlinex = ($startx - $linew);
+ } else {
+ $this->endlinex = ($startx + $linew);
+ }
+ $w = $linew;
+ $tmpcellpadding = $this->cell_padding;
+ if ($maxh == 0) {
+ $this->SetCellPadding(0);
+ }
+ }
+ if ($firstblock AND $this->isRTLTextDir()) {
+ $tmpstr = $this->stringRightTrim($tmpstr);
+ }
+ $this->Cell($w, $h, $tmpstr, 0, 1, $align, $fill, $link, $stretch);
+ unset($tmpstr);
+ if ($firstline) {
+ $this->cell_padding = $tmpcellpadding;
+ return (TCPDF_FONTS::UniArrSubString($uchars, $i));
+ }
+ $j = $i;
+ --$i;
+ } else {
+ // word wrapping
+ if ($shy) {
+ // add hypen (minus symbol) at the end of the line
+ $shy_width = $tmp_shy_replacement_width;
+ if ($this->rtl) {
+ $shy_char_left = $tmp_shy_replacement_char;
+ $shy_char_right = '';
+ } else {
+ $shy_char_left = '';
+ $shy_char_right = $tmp_shy_replacement_char;
+ }
+ } else {
+ $shy_width = 0;
+ $shy_char_left = '';
+ $shy_char_right = '';
+ }
+ $tmpstr = TCPDF_FONTS::UniArrSubString($uchars, $j, ($sep + $endspace));
+ if ($firstline) {
+ $startx = $this->x;
+ $tmparr = array_slice($chars, $j, (($sep + $endspace) - $j));
+ if ($rtlmode) {
+ $tmparr = TCPDF_FONTS::utf8Bidi($tmparr, $tmpstr, $this->tmprtl, $this->isunicode, $this->CurrentFont);
+ }
+ $linew = $this->GetArrStringWidth($tmparr);
+ unset($tmparr);
+ if ($this->rtl) {
+ $this->endlinex = $startx - $linew - $shy_width;
+ } else {
+ $this->endlinex = $startx + $linew + $shy_width;
+ }
+ $w = $linew;
+ $tmpcellpadding = $this->cell_padding;
+ if ($maxh == 0) {
+ $this->SetCellPadding(0);
+ }
+ }
+ // print the line
+ if ($firstblock AND $this->isRTLTextDir()) {
+ $tmpstr = $this->stringRightTrim($tmpstr);
+ }
+ $this->Cell($w, $h, $shy_char_left.$tmpstr.$shy_char_right, 0, 1, $align, $fill, $link, $stretch);
+ unset($tmpstr);
+ if ($firstline) {
+ if ($chars[$sep] == 45) {
+ $endspace += 1;
+ }
+ // return the remaining text
+ $this->cell_padding = $tmpcellpadding;
+ return (TCPDF_FONTS::UniArrSubString($uchars, ($sep + $endspace)));
+ }
+ $i = $sep;
+ $sep = -1;
+ $shy = false;
+ $j = ($i + 1);
+ }
+ }
+ // account for margin changes
+ if ((($this->y + $this->lasth) > $this->PageBreakTrigger) AND ($this->inPageBody())) {
+ $this->AcceptPageBreak();
+ if ($this->rtl) {
+ $this->x -= $margin['R'];
+ } else {
+ $this->x += $margin['L'];
+ }
+ $this->lMargin += $margin['L'];
+ $this->rMargin += $margin['R'];
+ }
+ $w = $this->getRemainingWidth();
+ $wmax = $w - $this->cell_padding['L'] - $this->cell_padding['R'];
+ if ($linebreak) {
+ $linebreak = false;
+ } else {
+ ++$nl;
+ $l = 0;
+ }
+ }
+ }
+ // save last character
+ $pc = $c;
+ ++$i;
+ } // end while i < nb
+ // print last substring (if any)
+ if ($l > 0) {
+ switch ($align) {
+ case 'J':
+ case 'C': {
+ $w = $w;
+ break;
+ }
+ case 'L': {
+ if ($this->rtl) {
+ $w = $w;
+ } else {
+ $w = $l;
+ }
+ break;
+ }
+ case 'R': {
+ if ($this->rtl) {
+ $w = $l;
+ } else {
+ $w = $w;
+ }
+ break;
+ }
+ default: {
+ $w = $l;
+ break;
+ }
+ }
+ $tmpstr = TCPDF_FONTS::UniArrSubString($uchars, $j, $nb);
+ if ($firstline) {
+ $startx = $this->x;
+ $tmparr = array_slice($chars, $j, ($nb - $j));
+ if ($rtlmode) {
+ $tmparr = TCPDF_FONTS::utf8Bidi($tmparr, $tmpstr, $this->tmprtl, $this->isunicode, $this->CurrentFont);
+ }
+ $linew = $this->GetArrStringWidth($tmparr);
+ unset($tmparr);
+ if ($this->rtl) {
+ $this->endlinex = $startx - $linew;
+ } else {
+ $this->endlinex = $startx + $linew;
+ }
+ $w = $linew;
+ $tmpcellpadding = $this->cell_padding;
+ if ($maxh == 0) {
+ $this->SetCellPadding(0);
+ }
+ }
+ if ($firstblock AND $this->isRTLTextDir()) {
+ $tmpstr = $this->stringRightTrim($tmpstr);
+ }
+ $this->Cell($w, $h, $tmpstr, 0, $ln, $align, $fill, $link, $stretch);
+ unset($tmpstr);
+ if ($firstline) {
+ $this->cell_padding = $tmpcellpadding;
+ return (TCPDF_FONTS::UniArrSubString($uchars, $nb));
+ }
+ ++$nl;
+ }
+ if ($firstline) {
+ return '';
+ }
+ return $nl;
+ }
+
+ /**
+ * Returns the remaining width between the current position and margins.
+ * @return int Return the remaining width
+ * @protected
+ */
+ protected function getRemainingWidth() {
+ list($this->x, $this->y) = $this->checkPageRegions(0, $this->x, $this->y);
+ if ($this->rtl) {
+ return ($this->x - $this->lMargin);
+ } else {
+ return ($this->w - $this->rMargin - $this->x);
+ }
+ }
+
+ /**
+ * Set the block dimensions accounting for page breaks and page/column fitting
+ * @param $w (float) width
+ * @param $h (float) height
+ * @param $x (float) X coordinate
+ * @param $y (float) Y coodiante
+ * @param $fitonpage (boolean) if true the block is resized to not exceed page dimensions.
+ * @return array($w, $h, $x, $y)
+ * @protected
+ * @since 5.5.009 (2010-07-05)
+ */
+ protected function fitBlock($w, $h, $x, $y, $fitonpage=false) {
+ if ($w <= 0) {
+ // set maximum width
+ $w = ($this->w - $this->lMargin - $this->rMargin);
+ if ($w <= 0) {
+ $w = 1;
+ }
+ }
+ if ($h <= 0) {
+ // set maximum height
+ $h = ($this->PageBreakTrigger - $this->tMargin);
+ if ($h <= 0) {
+ $h = 1;
+ }
+ }
+ // resize the block to be vertically contained on a single page or single column
+ if ($fitonpage OR $this->AutoPageBreak) {
+ $ratio_wh = ($w / $h);
+ if ($h > ($this->PageBreakTrigger - $this->tMargin)) {
+ $h = $this->PageBreakTrigger - $this->tMargin;
+ $w = ($h * $ratio_wh);
+ }
+ // resize the block to be horizontally contained on a single page or single column
+ if ($fitonpage) {
+ $maxw = ($this->w - $this->lMargin - $this->rMargin);
+ if ($w > $maxw) {
+ $w = $maxw;
+ $h = ($w / $ratio_wh);
+ }
+ }
+ }
+ // Check whether we need a new page or new column first as this does not fit
+ $prev_x = $this->x;
+ $prev_y = $this->y;
+ if ($this->checkPageBreak($h, $y) OR ($this->y < $prev_y)) {
+ $y = $this->y;
+ if ($this->rtl) {
+ $x += ($prev_x - $this->x);
+ } else {
+ $x += ($this->x - $prev_x);
+ }
+ $this->newline = true;
+ }
+ // resize the block to be contained on the remaining available page or column space
+ if ($fitonpage) {
+ $ratio_wh = ($w / $h);
+ if (($y + $h) > $this->PageBreakTrigger) {
+ $h = $this->PageBreakTrigger - $y;
+ $w = ($h * $ratio_wh);
+ }
+ if ((!$this->rtl) AND (($x + $w) > ($this->w - $this->rMargin))) {
+ $w = $this->w - $this->rMargin - $x;
+ $h = ($w / $ratio_wh);
+ } elseif (($this->rtl) AND (($x - $w) < ($this->lMargin))) {
+ $w = $x - $this->lMargin;
+ $h = ($w / $ratio_wh);
+ }
+ }
+ return array($w, $h, $x, $y);
+ }
+
+ /**
+ * Puts an image in the page.
+ * The upper-left corner must be given.
+ * The dimensions can be specified in different ways:<ul>
+ * <li>explicit width and height (expressed in user unit)</li>
+ * <li>one explicit dimension, the other being calculated automatically in order to keep the original proportions</li>
+ * <li>no explicit dimension, in which case the image is put at 72 dpi</li></ul>
+ * Supported formats are JPEG and PNG images whitout GD library and all images supported by GD: GD, GD2, GD2PART, GIF, JPEG, PNG, BMP, XBM, XPM;
+ * The format can be specified explicitly or inferred from the file extension.<br />
+ * It is possible to put a link on the image.<br />
+ * Remark: if an image is used several times, only one copy will be embedded in the file.<br />
+ * @param $file (string) Name of the file containing the image or a '@' character followed by the image data string. To link an image without embedding it on the document, set an asterisk character before the URL (i.e.: '*http://www.example.com/image.jpg').
+ * @param $x (float) Abscissa of the upper-left corner (LTR) or upper-right corner (RTL).
+ * @param $y (float) Ordinate of the upper-left corner (LTR) or upper-right corner (RTL).
+ * @param $w (float) Width of the image in the page. If not specified or equal to zero, it is automatically calculated.
+ * @param $h (float) Height of the image in the page. If not specified or equal to zero, it is automatically calculated.
+ * @param $type (string) Image format. Possible values are (case insensitive): JPEG and PNG (whitout GD library) and all images supported by GD: GD, GD2, GD2PART, GIF, JPEG, PNG, BMP, XBM, XPM;. If not specified, the type is inferred from the file extension.
+ * @param $link (mixed) URL or identifier returned by AddLink().
+ * @param $align (string) Indicates the alignment of the pointer next to image insertion relative to image height. The value can be:<ul><li>T: top-right for LTR or top-left for RTL</li><li>M: middle-right for LTR or middle-left for RTL</li><li>B: bottom-right for LTR or bottom-left for RTL</li><li>N: next line</li></ul>
+ * @param $resize (mixed) If true resize (reduce) the image to fit $w and $h (requires GD or ImageMagick library); if false do not resize; if 2 force resize in all cases (upscaling and downscaling).
+ * @param $dpi (int) dot-per-inch resolution used on resize
+ * @param $palign (string) Allows to center or align the image on the current line. Possible values are:<ul><li>L : left align</li><li>C : center</li><li>R : right align</li><li>'' : empty string : left for LTR or right for RTL</li></ul>
+ * @param $ismask (boolean) true if this image is a mask, false otherwise
+ * @param $imgmask (mixed) image object returned by this function or false
+ * @param $border (mixed) Indicates if borders must be drawn around the cell. The value can be a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul> or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul> or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)))
+ * @param $fitbox (mixed) If not false scale image dimensions proportionally to fit within the ($w, $h) box. $fitbox can be true or a 2 characters string indicating the image alignment inside the box. The first character indicate the horizontal alignment (L = left, C = center, R = right) the second character indicate the vertical algnment (T = top, M = middle, B = bottom).
+ * @param $hidden (boolean) If true do not display the image.
+ * @param $fitonpage (boolean) If true the image is resized to not exceed page dimensions.
+ * @param $alt (boolean) If true the image will be added as alternative and not directly printed (the ID of the image will be returned).
+ * @param $altimgs (array) Array of alternate images IDs. Each alternative image must be an array with two values: an integer representing the image ID (the value returned by the Image method) and a boolean value to indicate if the image is the default for printing.
+ * @return image information
+ * @public
+ * @since 1.1
+ */
+ public function Image($file, $x='', $y='', $w=0, $h=0, $type='', $link='', $align='', $resize=false, $dpi=300, $palign='', $ismask=false, $imgmask=false, $border=0, $fitbox=false, $hidden=false, $fitonpage=false, $alt=false, $altimgs=array()) {
+ if ($this->state != 2) {
+ return;
+ }
+ if ($x === '') {
+ $x = $this->x;
+ }
+ if ($y === '') {
+ $y = $this->y;
+ }
+ // check page for no-write regions and adapt page margins if necessary
+ list($x, $y) = $this->checkPageRegions($h, $x, $y);
+ $exurl = ''; // external streams
+ $imsize = FALSE;
+ // check if we are passing an image as file or string
+ if ($file[0] === '@') {
+ // image from string
+ $imgdata = substr($file, 1);
+ } else { // image file
+ if ($file{0} === '*') {
+ // image as external stream
+ $file = substr($file, 1);
+ $exurl = $file;
+ }
+ // check if is local file
+ if (!@file_exists($file)) {
+ // encode spaces on filename (file is probably an URL)
+ $file = str_replace(' ', '%20', $file);
+ }
+ if (@file_exists($file)) {
+ // get image dimensions
+ $imsize = @getimagesize($file);
+ }
+ if ($imsize === FALSE) {
+ $imgdata = TCPDF_STATIC::fileGetContents($file);
+ }
+ }
+ if (isset($imgdata) AND ($imgdata !== FALSE)) {
+ // copy image to cache
+ $file = TCPDF_STATIC::getObjFilename('img');
+ $fp = fopen($file, 'w');
+ fwrite($fp, $imgdata);
+ fclose($fp);
+ unset($imgdata);
+ $imsize = @getimagesize($file);
+ if ($imsize === FALSE) {
+ unlink($file);
+ } else {
+ $this->cached_files[] = $file;
+ }
+ }
+ if ($imsize === FALSE) {
+ if (($w > 0) AND ($h > 0)) {
+ // get measures from specified data
+ $pw = $this->getHTMLUnitToUnits($w, 0, $this->pdfunit, true) * $this->imgscale * $this->k;
+ $ph = $this->getHTMLUnitToUnits($h, 0, $this->pdfunit, true) * $this->imgscale * $this->k;
+ $imsize = array($pw, $ph);
+ } else {
+ $this->Error('[Image] Unable to get image: '.$file);
+ }
+ }
+ // file hash
+ $filehash = md5($this->file_id.$file);
+ // get original image width and height in pixels
+ list($pixw, $pixh) = $imsize;
+ // calculate image width and height on document
+ if (($w <= 0) AND ($h <= 0)) {
+ // convert image size to document unit
+ $w = $this->pixelsToUnits($pixw);
+ $h = $this->pixelsToUnits($pixh);
+ } elseif ($w <= 0) {
+ $w = $h * $pixw / $pixh;
+ } elseif ($h <= 0) {
+ $h = $w * $pixh / $pixw;
+ } elseif (($fitbox !== false) AND ($w > 0) AND ($h > 0)) {
+ if (strlen($fitbox) !== 2) {
+ // set default alignment
+ $fitbox = '--';
+ }
+ // scale image dimensions proportionally to fit within the ($w, $h) box
+ if ((($w * $pixh) / ($h * $pixw)) < 1) {
+ // store current height
+ $oldh = $h;
+ // calculate new height
+ $h = $w * $pixh / $pixw;
+ // height difference
+ $hdiff = ($oldh - $h);
+ // vertical alignment
+ switch (strtoupper($fitbox{1})) {
+ case 'T': {
+ break;
+ }
+ case 'M': {
+ $y += ($hdiff / 2);
+ break;
+ }
+ case 'B': {
+ $y += $hdiff;
+ break;
+ }
+ }
+ } else {
+ // store current width
+ $oldw = $w;
+ // calculate new width
+ $w = $h * $pixw / $pixh;
+ // width difference
+ $wdiff = ($oldw - $w);
+ // horizontal alignment
+ switch (strtoupper($fitbox{0})) {
+ case 'L': {
+ if ($this->rtl) {
+ $x -= $wdiff;
+ }
+ break;
+ }
+ case 'C': {
+ if ($this->rtl) {
+ $x -= ($wdiff / 2);
+ } else {
+ $x += ($wdiff / 2);
+ }
+ break;
+ }
+ case 'R': {
+ if (!$this->rtl) {
+ $x += $wdiff;
+ }
+ break;
+ }
+ }
+ }
+ }
+ // fit the image on available space
+ list($w, $h, $x, $y) = $this->fitBlock($w, $h, $x, $y, $fitonpage);
+ // calculate new minimum dimensions in pixels
+ $neww = round($w * $this->k * $dpi / $this->dpi);
+ $newh = round($h * $this->k * $dpi / $this->dpi);
+ // check if resize is necessary (resize is used only to reduce the image)
+ $newsize = ($neww * $newh);
+ $pixsize = ($pixw * $pixh);
+ if (intval($resize) == 2) {
+ $resize = true;
+ } elseif ($newsize >= $pixsize) {
+ $resize = false;
+ }
+ // check if image has been already added on document
+ $newimage = true;
+ if (in_array($file, $this->imagekeys)) {
+ $newimage = false;
+ // get existing image data
+ $info = $this->getImageBuffer($file);
+ if (substr($file, 0, -34) != K_PATH_CACHE.'msk') {
+ // check if the newer image is larger
+ $oldsize = ($info['w'] * $info['h']);
+ if ((($oldsize < $newsize) AND ($resize)) OR (($oldsize < $pixsize) AND (!$resize))) {
+ $newimage = true;
+ }
+ }
+ } elseif (substr($file, 0, -34) != K_PATH_CACHE.'msk') {
+ // check for cached images with alpha channel
+ $tempfile_plain = K_PATH_CACHE.'mskp_'.$filehash;
+ $tempfile_alpha = K_PATH_CACHE.'mska_'.$filehash;
+ if (in_array($tempfile_plain, $this->imagekeys)) {
+ // get existing image data
+ $info = $this->getImageBuffer($tempfile_plain);
+ // check if the newer image is larger
+ $oldsize = ($info['w'] * $info['h']);
+ if ((($oldsize < $newsize) AND ($resize)) OR (($oldsize < $pixsize) AND (!$resize))) {
+ $newimage = true;
+ } else {
+ $newimage = false;
+ // embed mask image
+ $imgmask = $this->Image($tempfile_alpha, $x, $y, $w, $h, 'PNG', '', '', $resize, $dpi, '', true, false);
+ // embed image, masked with previously embedded mask
+ return $this->Image($tempfile_plain, $x, $y, $w, $h, $type, $link, $align, $resize, $dpi, $palign, false, $imgmask);
+ }
+ }
+ }
+ if ($newimage) {
+ //First use of image, get info
+ $type = strtolower($type);
+ if ($type == '') {
+ $type = TCPDF_IMAGES::getImageFileType($file, $imsize);
+ } elseif ($type == 'jpg') {
+ $type = 'jpeg';
+ }
+ $mqr = TCPDF_STATIC::get_mqr();
+ TCPDF_STATIC::set_mqr(false);
+ // Specific image handlers (defined on TCPDF_IMAGES CLASS)
+ $mtd = '_parse'.$type;
+ // GD image handler function
+ $gdfunction = 'imagecreatefrom'.$type;
+ $info = false;
+ if ((method_exists('TCPDF_IMAGES', $mtd)) AND (!($resize AND (function_exists($gdfunction) OR extension_loaded('imagick'))))) {
+ // TCPDF image functions
+ $info = TCPDF_IMAGES::$mtd($file);
+ if (($info === 'pngalpha') OR (isset($info['trns']) AND !empty($info['trns']))) {
+ return $this->ImagePngAlpha($file, $x, $y, $pixw, $pixh, $w, $h, 'PNG', $link, $align, $resize, $dpi, $palign, $filehash);
+ }
+ }
+ if (($info === false) AND function_exists($gdfunction)) {
+ try {
+ // GD library
+ $img = $gdfunction($file);
+ if ($resize) {
+ $imgr = imagecreatetruecolor($neww, $newh);
+ if (($type == 'gif') OR ($type == 'png')) {
+ $imgr = TCPDF_IMAGES::setGDImageTransparency($imgr, $img);
+ }
+ imagecopyresampled($imgr, $img, 0, 0, 0, 0, $neww, $newh, $pixw, $pixh);
+ if (($type == 'gif') OR ($type == 'png')) {
+ $info = TCPDF_IMAGES::_toPNG($imgr);
+ } else {
+ $info = TCPDF_IMAGES::_toJPEG($imgr, $this->jpeg_quality);
+ }
+ } else {
+ if (($type == 'gif') OR ($type == 'png')) {
+ $info = TCPDF_IMAGES::_toPNG($img);
+ } else {
+ $info = TCPDF_IMAGES::_toJPEG($img, $this->jpeg_quality);
+ }
+ }
+ } catch(Exception $e) {
+ $info = false;
+ }
+ }
+ if (($info === false) AND extension_loaded('imagick')) {
+ try {
+ // ImageMagick library
+ $img = new Imagick();
+ if ($type == 'SVG') {
+ // get SVG file content
+ $svgimg = TCPDF_STATIC::fileGetContents($file);
+ if ($svgimg !== FALSE) {
+ // get width and height
+ $regs = array();
+ if (preg_match('/<svg([^\>]*)>/si', $svgimg, $regs)) {
+ $svgtag = $regs[1];
+ $tmp = array();
+ if (preg_match('/[\s]+width[\s]*=[\s]*"([^"]*)"/si', $svgtag, $tmp)) {
+ $ow = $this->getHTMLUnitToUnits($tmp[1], 1, $this->svgunit, false);
+ $owu = sprintf('%F', ($ow * $dpi / 72)).$this->pdfunit;
+ $svgtag = preg_replace('/[\s]+width[\s]*=[\s]*"[^"]*"/si', ' width="'.$owu.'"', $svgtag, 1);
+ } else {
+ $ow = $w;
+ }
+ $tmp = array();
+ if (preg_match('/[\s]+height[\s]*=[\s]*"([^"]*)"/si', $svgtag, $tmp)) {
+ $oh = $this->getHTMLUnitToUnits($tmp[1], 1, $this->svgunit, false);
+ $ohu = sprintf('%F', ($oh * $dpi / 72)).$this->pdfunit;
+ $svgtag = preg_replace('/[\s]+height[\s]*=[\s]*"[^"]*"/si', ' height="'.$ohu.'"', $svgtag, 1);
+ } else {
+ $oh = $h;
+ }
+ $tmp = array();
+ if (!preg_match('/[\s]+viewBox[\s]*=[\s]*"[\s]*([0-9\.]+)[\s]+([0-9\.]+)[\s]+([0-9\.]+)[\s]+([0-9\.]+)[\s]*"/si', $svgtag, $tmp)) {
+ $vbw = ($ow * $this->imgscale * $this->k);
+ $vbh = ($oh * $this->imgscale * $this->k);
+ $vbox = sprintf(' viewBox="0 0 %F %F" ', $vbw, $vbh);
+ $svgtag = $vbox.$svgtag;
+ }
+ $svgimg = preg_replace('/<svg([^\>]*)>/si', '<svg'.$svgtag.'>', $svgimg, 1);
+ }
+ $img->readImageBlob($svgimg);
+ }
+ } else {
+ $img->readImage($file);
+ }
+ if ($resize) {
+ $img->resizeImage($neww, $newh, 10, 1, false);
+ }
+ $img->setCompressionQuality($this->jpeg_quality);
+ $img->setImageFormat('jpeg');
+ $tempname = TCPDF_STATIC::getObjFilename('jpg');
+ $img->writeImage($tempname);
+ $info = TCPDF_IMAGES::_parsejpeg($tempname);
+ unlink($tempname);
+ $img->destroy();
+ } catch(Exception $e) {
+ $info = false;
+ }
+ }
+ if ($info === false) {
+ // unable to process image
+ return;
+ }
+ TCPDF_STATIC::set_mqr($mqr);
+ if ($ismask) {
+ // force grayscale
+ $info['cs'] = 'DeviceGray';
+ }
+ if ($imgmask !== false) {
+ $info['masked'] = $imgmask;
+ }
+ if (!empty($exurl)) {
+ $info['exurl'] = $exurl;
+ }
+ // array of alternative images
+ $info['altimgs'] = $altimgs;
+ // add image to document
+ $info['i'] = $this->setImageBuffer($file, $info);
+ }
+ // set alignment
+ $this->img_rb_y = $y + $h;
+ // set alignment
+ if ($this->rtl) {
+ if ($palign == 'L') {
+ $ximg = $this->lMargin;
+ } elseif ($palign == 'C') {
+ $ximg = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
+ } elseif ($palign == 'R') {
+ $ximg = $this->w - $this->rMargin - $w;
+ } else {
+ $ximg = $x - $w;
+ }
+ $this->img_rb_x = $ximg;
+ } else {
+ if ($palign == 'L') {
+ $ximg = $this->lMargin;
+ } elseif ($palign == 'C') {
+ $ximg = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
+ } elseif ($palign == 'R') {
+ $ximg = $this->w - $this->rMargin - $w;
+ } else {
+ $ximg = $x;
+ }
+ $this->img_rb_x = $ximg + $w;
+ }
+ if ($ismask OR $hidden) {
+ // image is not displayed
+ return $info['i'];
+ }
+ $xkimg = $ximg * $this->k;
+ if (!$alt) {
+ // only non-alternative immages will be set
+ $this->_out(sprintf('q %F 0 0 %F %F %F cm /I%u Do Q', ($w * $this->k), ($h * $this->k), $xkimg, (($this->h - ($y + $h)) * $this->k), $info['i']));
+ }
+ if (!empty($border)) {
+ $bx = $this->x;
+ $by = $this->y;
+ $this->x = $ximg;
+ if ($this->rtl) {
+ $this->x += $w;
+ }
+ $this->y = $y;
+ $this->Cell($w, $h, '', $border, 0, '', 0, '', 0, true);
+ $this->x = $bx;
+ $this->y = $by;
+ }
+ if ($link) {
+ $this->Link($ximg, $y, $w, $h, $link, 0);
+ }
+ // set pointer to align the next text/objects
+ switch($align) {
+ case 'T': {
+ $this->y = $y;
+ $this->x = $this->img_rb_x;
+ break;
+ }
+ case 'M': {
+ $this->y = $y + round($h/2);
+ $this->x = $this->img_rb_x;
+ break;
+ }
+ case 'B': {
+ $this->y = $this->img_rb_y;
+ $this->x = $this->img_rb_x;
+ break;
+ }
+ case 'N': {
+ $this->SetY($this->img_rb_y);
+ break;
+ }
+ default:{
+ break;
+ }
+ }
+ $this->endlinex = $this->img_rb_x;
+ if ($this->inxobj) {
+ // we are inside an XObject template
+ $this->xobjects[$this->xobjid]['images'][] = $info['i'];
+ }
+ return $info['i'];
+ }
+
+ /**
+ * Extract info from a PNG image with alpha channel using the Imagick or GD library.
+ * @param $file (string) Name of the file containing the image.
+ * @param $x (float) Abscissa of the upper-left corner.
+ * @param $y (float) Ordinate of the upper-left corner.
+ * @param $wpx (float) Original width of the image in pixels.
+ * @param $hpx (float) original height of the image in pixels.
+ * @param $w (float) Width of the image in the page. If not specified or equal to zero, it is automatically calculated.
+ * @param $h (float) Height of the image in the page. If not specified or equal to zero, it is automatically calculated.
+ * @param $type (string) Image format. Possible values are (case insensitive): JPEG and PNG (whitout GD library) and all images supported by GD: GD, GD2, GD2PART, GIF, JPEG, PNG, BMP, XBM, XPM;. If not specified, the type is inferred from the file extension.
+ * @param $link (mixed) URL or identifier returned by AddLink().
+ * @param $align (string) Indicates the alignment of the pointer next to image insertion relative to image height. The value can be:<ul><li>T: top-right for LTR or top-left for RTL</li><li>M: middle-right for LTR or middle-left for RTL</li><li>B: bottom-right for LTR or bottom-left for RTL</li><li>N: next line</li></ul>
+ * @param $resize (boolean) If true resize (reduce) the image to fit $w and $h (requires GD library).
+ * @param $dpi (int) dot-per-inch resolution used on resize
+ * @param $palign (string) Allows to center or align the image on the current line. Possible values are:<ul><li>L : left align</li><li>C : center</li><li>R : right align</li><li>'' : empty string : left for LTR or right for RTL</li></ul>
+ * @param $filehash (string) File hash used to build unique file names.
+ * @author Nicola Asuni
+ * @protected
+ * @since 4.3.007 (2008-12-04)
+ * @see Image()
+ */
+ protected function ImagePngAlpha($file, $x, $y, $wpx, $hpx, $w, $h, $type, $link, $align, $resize, $dpi, $palign, $filehash='') {
+ if (empty($filehash)) {
+ $filehash = md5($this->file_id.$file);
+ }
+ // create temp image file (without alpha channel)
+ $tempfile_plain = K_PATH_CACHE.'mskp_'.$filehash;
+ // create temp alpha file
+ $tempfile_alpha = K_PATH_CACHE.'mska_'.$filehash;
+ $parsed = false;
+ $parse_error = '';
+ // ImageMagick extension
+ if (($parsed === false) AND extension_loaded('imagick')) {
+ try {
+ // ImageMagick library
+ $img = new Imagick();
+ $img->readImage($file);
+ // clone image object
+ $imga = TCPDF_STATIC::objclone($img);
+ // extract alpha channel
+ if (method_exists($img, 'setImageAlphaChannel') AND defined('Imagick::ALPHACHANNEL_EXTRACT')) {
+ $img->setImageAlphaChannel(Imagick::ALPHACHANNEL_EXTRACT);
+ } else {
+ $img->separateImageChannel(8); // 8 = (imagick::CHANNEL_ALPHA | imagick::CHANNEL_OPACITY | imagick::CHANNEL_MATTE);
+ $img->negateImage(true);
+ }
+ $img->setImageFormat('png');
+ $img->writeImage($tempfile_alpha);
+ // remove alpha channel
+ if (method_exists($imga, 'setImageMatte')) {
+ $imga->setImageMatte(false);
+ } else {
+ $imga->separateImageChannel(39); // 39 = (imagick::CHANNEL_ALL & ~(imagick::CHANNEL_ALPHA | imagick::CHANNEL_OPACITY | imagick::CHANNEL_MATTE));
+ }
+ $imga->setImageFormat('png');
+ $imga->writeImage($tempfile_plain);
+ $parsed = true;
+ } catch (Exception $e) {
+ // Imagemagick fails, try with GD
+ $parse_error = 'Imagick library error: '.$e->getMessage();
+ }
+ }
+ // GD extension
+ if (($parsed === false) AND function_exists('imagecreatefrompng')) {
+ try {
+ // generate images
+ $img = imagecreatefrompng($file);
+ $imgalpha = imagecreate($wpx, $hpx);
+ // generate gray scale palette (0 -> 255)
+ for ($c = 0; $c < 256; ++$c) {
+ ImageColorAllocate($imgalpha, $c, $c, $c);
+ }
+ // extract alpha channel
+ for ($xpx = 0; $xpx < $wpx; ++$xpx) {
+ for ($ypx = 0; $ypx < $hpx; ++$ypx) {
+ $color = imagecolorat($img, $xpx, $ypx);
+ // get and correct gamma color
+ $alpha = $this->getGDgamma($img, $color);
+ imagesetpixel($imgalpha, $xpx, $ypx, $alpha);
+ }
+ }
+ imagepng($imgalpha, $tempfile_alpha);
+ imagedestroy($imgalpha);
+ // extract image without alpha channel
+ $imgplain = imagecreatetruecolor($wpx, $hpx);
+ imagecopy($imgplain, $img, 0, 0, 0, 0, $wpx, $hpx);
+ imagepng($imgplain, $tempfile_plain);
+ imagedestroy($imgplain);
+ $parsed = true;
+ } catch (Exception $e) {
+ // GD fails
+ $parse_error = 'GD library error: '.$e->getMessage();
+ }
+ }
+ if ($parsed === false) {
+ if (empty($parse_error)) {
+ $this->Error('TCPDF requires the Imagick or GD extension to handle PNG images with alpha channel.');
+ } else {
+ $this->Error($parse_error);
+ }
+ }
+ // embed mask image
+ $imgmask = $this->Image($tempfile_alpha, $x, $y, $w, $h, 'PNG', '', '', $resize, $dpi, '', true, false);
+ // embed image, masked with previously embedded mask
+ $this->Image($tempfile_plain, $x, $y, $w, $h, $type, $link, $align, $resize, $dpi, $palign, false, $imgmask);
+ // remove temp files
+ unlink($tempfile_alpha);
+ unlink($tempfile_plain);
+ }
+
+ /**
+ * Get the GD-corrected PNG gamma value from alpha color
+ * @param $img (int) GD image Resource ID.
+ * @param $c (int) alpha color
+ * @protected
+ * @since 4.3.007 (2008-12-04)
+ */
+ protected function getGDgamma($img, $c) {
+ if (!isset($this->gdgammacache['#'.$c])) {
+ $colors = imagecolorsforindex($img, $c);
+ // GD alpha is only 7 bit (0 -> 127)
+ $this->gdgammacache['#'.$c] = (((127 - $colors['alpha']) / 127) * 255);
+ // correct gamma
+ $this->gdgammacache['#'.$c] = (pow(($this->gdgammacache['#'.$c] / 255), 2.2) * 255);
+ // store the latest values on cache to improve performances
+ if (count($this->gdgammacache) > 8) {
+ // remove one element from the cache array
+ array_shift($this->gdgammacache);
+ }
+ }
+ return $this->gdgammacache['#'.$c];
+ }
+
+ /**
+ * Performs a line break.
+ * The current abscissa goes back to the left margin and the ordinate increases by the amount passed in parameter.
+ * @param $h (float) The height of the break. By default, the value equals the height of the last printed cell.
+ * @param $cell (boolean) if true add the current left (or right o for RTL) padding to the X coordinate
+ * @public
+ * @since 1.0
+ * @see Cell()
+ */
+ public function Ln($h='', $cell=false) {
+ if (($this->num_columns > 1) AND ($this->y == $this->columns[$this->current_column]['y']) AND isset($this->columns[$this->current_column]['x']) AND ($this->x == $this->columns[$this->current_column]['x'])) {
+ // revove vertical space from the top of the column
+ return;
+ }
+ if ($cell) {
+ if ($this->rtl) {
+ $cellpadding = $this->cell_padding['R'];
+ } else {
+ $cellpadding = $this->cell_padding['L'];
+ }
+ } else {
+ $cellpadding = 0;
+ }
+ if ($this->rtl) {
+ $this->x = $this->w - $this->rMargin - $cellpadding;
+ } else {
+ $this->x = $this->lMargin + $cellpadding;
+ }
+ if (is_string($h)) {
+ $this->y += $this->lasth;
+ } else {
+ $this->y += $h;
+ }
+ $this->newline = true;
+ }
+
+ /**
+ * Returns the relative X value of current position.
+ * The value is relative to the left border for LTR languages and to the right border for RTL languages.
+ * @return float
+ * @public
+ * @since 1.2
+ * @see SetX(), GetY(), SetY()
+ */
+ public function GetX() {
+ //Get x position
+ if ($this->rtl) {
+ return ($this->w - $this->x);
+ } else {
+ return $this->x;
+ }
+ }
+
+ /**
+ * Returns the absolute X value of current position.
+ * @return float
+ * @public
+ * @since 1.2
+ * @see SetX(), GetY(), SetY()
+ */
+ public function GetAbsX() {
+ return $this->x;
+ }
+
+ /**
+ * Returns the ordinate of the current position.
+ * @return float
+ * @public
+ * @since 1.0
+ * @see SetY(), GetX(), SetX()
+ */
+ public function GetY() {
+ return $this->y;
+ }
+
+ /**
+ * Defines the abscissa of the current position.
+ * If the passed value is negative, it is relative to the right of the page (or left if language is RTL).
+ * @param $x (float) The value of the abscissa in user units.
+ * @param $rtloff (boolean) if true always uses the page top-left corner as origin of axis.
+ * @public
+ * @since 1.2
+ * @see GetX(), GetY(), SetY(), SetXY()
+ */
+ public function SetX($x, $rtloff=false) {
+ $x = floatval($x);
+ if (!$rtloff AND $this->rtl) {
+ if ($x >= 0) {
+ $this->x = $this->w - $x;
+ } else {
+ $this->x = abs($x);
+ }
+ } else {
+ if ($x >= 0) {
+ $this->x = $x;
+ } else {
+ $this->x = $this->w + $x;
+ }
+ }
+ if ($this->x < 0) {
+ $this->x = 0;
+ }
+ if ($this->x > $this->w) {
+ $this->x = $this->w;
+ }
+ }
+
+ /**
+ * Moves the current abscissa back to the left margin and sets the ordinate.
+ * If the passed value is negative, it is relative to the bottom of the page.
+ * @param $y (float) The value of the ordinate in user units.
+ * @param $resetx (bool) if true (default) reset the X position.
+ * @param $rtloff (boolean) if true always uses the page top-left corner as origin of axis.
+ * @public
+ * @since 1.0
+ * @see GetX(), GetY(), SetY(), SetXY()
+ */
+ public function SetY($y, $resetx=true, $rtloff=false) {
+ $y = floatval($y);
+ if ($resetx) {
+ //reset x
+ if (!$rtloff AND $this->rtl) {
+ $this->x = $this->w - $this->rMargin;
+ } else {
+ $this->x = $this->lMargin;
+ }
+ }
+ if ($y >= 0) {
+ $this->y = $y;
+ } else {
+ $this->y = $this->h + $y;
+ }
+ if ($this->y < 0) {
+ $this->y = 0;
+ }
+ if ($this->y > $this->h) {
+ $this->y = $this->h;
+ }
+ }
+
+ /**
+ * Defines the abscissa and ordinate of the current position.
+ * If the passed values are negative, they are relative respectively to the right and bottom of the page.
+ * @param $x (float) The value of the abscissa.
+ * @param $y (float) The value of the ordinate.
+ * @param $rtloff (boolean) if true always uses the page top-left corner as origin of axis.
+ * @public
+ * @since 1.2
+ * @see SetX(), SetY()
+ */
+ public function SetXY($x, $y, $rtloff=false) {
+ $this->SetY($y, false, $rtloff);
+ $this->SetX($x, $rtloff);
+ }
+
+ /**
+ * Set the absolute X coordinate of the current pointer.
+ * @param $x (float) The value of the abscissa in user units.
+ * @public
+ * @since 5.9.186 (2012-09-13)
+ * @see setAbsX(), setAbsY(), SetAbsXY()
+ */
+ public function SetAbsX($x) {
+ $this->x = floatval($x);
+ }
+
+ /**
+ * Set the absolute Y coordinate of the current pointer.
+ * @param $y (float) (float) The value of the ordinate in user units.
+ * @public
+ * @since 5.9.186 (2012-09-13)
+ * @see setAbsX(), setAbsY(), SetAbsXY()
+ */
+ public function SetAbsY($y) {
+ $this->y = floatval($y);
+ }
+
+ /**
+ * Set the absolute X and Y coordinates of the current pointer.
+ * @param $x (float) The value of the abscissa in user units.
+ * @param $y (float) (float) The value of the ordinate in user units.
+ * @public
+ * @since 5.9.186 (2012-09-13)
+ * @see setAbsX(), setAbsY(), SetAbsXY()
+ */
+ public function SetAbsXY($x, $y) {
+ $this->SetAbsX($x);
+ $this->SetAbsY($y);
+ }
+
+ /**
+ * Send the document to a given destination: string, local file or browser.
+ * In the last case, the plug-in may be used (if present) or a download ("Save as" dialog box) may be forced.<br />
+ * The method first calls Close() if necessary to terminate the document.
+ * @param $name (string) The name of the file when saved. Note that special characters are removed and blanks characters are replaced with the underscore character.
+ * @param $dest (string) Destination where to send the document. It can take one of the following values:<ul><li>I: send the file inline to the browser (default). The plug-in is used if available. The name given by name is used when one selects the "Save as" option on the link generating the PDF.</li><li>D: send to the browser and force a file download with the name given by name.</li><li>F: save to a local server file with the name given by name.</li><li>S: return the document as a string (name is ignored).</li><li>FI: equivalent to F + I option</li><li>FD: equivalent to F + D option</li><li>E: return the document as base64 mime multi-part email attachment (RFC 2045)</li></ul>
+ * @public
+ * @since 1.0
+ * @see Close()
+ */
+ public function Output($name='doc.pdf', $dest='I') {
+ //Output PDF to some destination
+ //Finish document if necessary
+ if ($this->state < 3) {
+ $this->Close();
+ }
+ //Normalize parameters
+ if (is_bool($dest)) {
+ $dest = $dest ? 'D' : 'F';
+ }
+ $dest = strtoupper($dest);
+ if ($dest{0} != 'F') {
+ $name = preg_replace('/[\s]+/', '_', $name);
+ $name = preg_replace('/[^a-zA-Z0-9_\.-]/', '', $name);
+ }
+ if ($this->sign) {
+ // *** apply digital signature to the document ***
+ // get the document content
+ $pdfdoc = $this->getBuffer();
+ // remove last newline
+ $pdfdoc = substr($pdfdoc, 0, -1);
+ // Remove the original buffer
+ if (isset($this->diskcache) AND $this->diskcache) {
+ // remove buffer file from cache
+ unlink($this->buffer);
+ }
+ unset($this->buffer);
+ // remove filler space
+ $byterange_string_len = strlen(TCPDF_STATIC::$byterange_string);
+ // define the ByteRange
+ $byte_range = array();
+ $byte_range[0] = 0;
+ $byte_range[1] = strpos($pdfdoc, TCPDF_STATIC::$byterange_string) + $byterange_string_len + 10;
+ $byte_range[2] = $byte_range[1] + $this->signature_max_length + 2;
+ $byte_range[3] = strlen($pdfdoc) - $byte_range[2];
+ $pdfdoc = substr($pdfdoc, 0, $byte_range[1]).substr($pdfdoc, $byte_range[2]);
+ // replace the ByteRange
+ $byterange = sprintf('/ByteRange[0 %u %u %u]', $byte_range[1], $byte_range[2], $byte_range[3]);
+ $byterange .= str_repeat(' ', ($byterange_string_len - strlen($byterange)));
+ $pdfdoc = str_replace(TCPDF_STATIC::$byterange_string, $byterange, $pdfdoc);
+ // write the document to a temporary folder
+ $tempdoc = TCPDF_STATIC::getObjFilename('tmppdf');
+ $f = fopen($tempdoc, 'wb');
+ if (!$f) {
+ $this->Error('Unable to create temporary file: '.$tempdoc);
+ }
+ $pdfdoc_length = strlen($pdfdoc);
+ fwrite($f, $pdfdoc, $pdfdoc_length);
+ fclose($f);
+ // get digital signature via openssl library
+ $tempsign = TCPDF_STATIC::getObjFilename('tmpsig');
+ if (empty($this->signature_data['extracerts'])) {
+ openssl_pkcs7_sign($tempdoc, $tempsign, $this->signature_data['signcert'], array($this->signature_data['privkey'], $this->signature_data['password']), array(), PKCS7_BINARY | PKCS7_DETACHED);
+ } else {
+ openssl_pkcs7_sign($tempdoc, $tempsign, $this->signature_data['signcert'], array($this->signature_data['privkey'], $this->signature_data['password']), array(), PKCS7_BINARY | PKCS7_DETACHED, $this->signature_data['extracerts']);
+ }
+ unlink($tempdoc);
+ // read signature
+ $signature = file_get_contents($tempsign);
+ unlink($tempsign);
+ // extract signature
+ $signature = substr($signature, $pdfdoc_length);
+ $signature = substr($signature, (strpos($signature, "%%EOF\n\n------") + 13));
+ $tmparr = explode("\n\n", $signature);
+ $signature = $tmparr[1];
+ unset($tmparr);
+ // decode signature
+ $signature = base64_decode(trim($signature));
+ // convert signature to hex
+ $signature = current(unpack('H*', $signature));
+ $signature = str_pad($signature, $this->signature_max_length, '0');
+ // disable disk caching
+ $this->diskcache = false;
+ // Add signature to the document
+ $this->buffer = substr($pdfdoc, 0, $byte_range[1]).'<'.$signature.'>'.substr($pdfdoc, $byte_range[1]);
+ $this->bufferlen = strlen($this->buffer);
+ }
+ switch($dest) {
+ case 'I': {
+ // Send PDF to the standard output
+ if (ob_get_contents()) {
+ $this->Error('Some data has already been output, can\'t send PDF file');
+ }
+ if (php_sapi_name() != 'cli') {
+ // send output to a browser
+ header('Content-Type: application/pdf');
+ if (headers_sent()) {
+ $this->Error('Some data has already been output to browser, can\'t send PDF file');
+ }
+ header('Cache-Control: private, must-revalidate, post-check=0, pre-check=0, max-age=1');
+ //header('Cache-Control: public, must-revalidate, max-age=0'); // HTTP/1.1
+ header('Pragma: public');
+ header('Expires: Sat, 26 Jul 1997 05:00:00 GMT'); // Date in the past
+ header('Last-Modified: '.gmdate('D, d M Y H:i:s').' GMT');
+ header('Content-Disposition: inline; filename="'.basename($name).'"');
+ TCPDF_STATIC::sendOutputData($this->getBuffer(), $this->bufferlen);
+ } else {
+ echo $this->getBuffer();
+ }
+ break;
+ }
+ case 'D': {
+ // download PDF as file
+ if (ob_get_contents()) {
+ $this->Error('Some data has already been output, can\'t send PDF file');
+ }
+ header('Content-Description: File Transfer');
+ if (headers_sent()) {
+ $this->Error('Some data has already been output to browser, can\'t send PDF file');
+ }
+ header('Cache-Control: private, must-revalidate, post-check=0, pre-check=0, max-age=1');
+ //header('Cache-Control: public, must-revalidate, max-age=0'); // HTTP/1.1
+ header('Pragma: public');
+ header('Expires: Sat, 26 Jul 1997 05:00:00 GMT'); // Date in the past
+ header('Last-Modified: '.gmdate('D, d M Y H:i:s').' GMT');
+ // force download dialog
+ if (strpos(php_sapi_name(), 'cgi') === false) {
+ header('Content-Type: application/force-download');
+ header('Content-Type: application/octet-stream', false);
+ header('Content-Type: application/download', false);
+ header('Content-Type: application/pdf', false);
+ } else {
+ header('Content-Type: application/pdf');
+ }
+ // use the Content-Disposition header to supply a recommended filename
+ header('Content-Disposition: attachment; filename="'.basename($name).'"');
+ header('Content-Transfer-Encoding: binary');
+ TCPDF_STATIC::sendOutputData($this->getBuffer(), $this->bufferlen);
+ break;
+ }
+ case 'F':
+ case 'FI':
+ case 'FD': {
+ // save PDF to a local file
+ if ($this->diskcache) {
+ copy($this->buffer, $name);
+ } else {
+ $f = fopen($name, 'wb');
+ if (!$f) {
+ $this->Error('Unable to create output file: '.$name);
+ }
+ fwrite($f, $this->getBuffer(), $this->bufferlen);
+ fclose($f);
+ }
+ if ($dest == 'FI') {
+ // send headers to browser
+ header('Content-Type: application/pdf');
+ header('Cache-Control: private, must-revalidate, post-check=0, pre-check=0, max-age=1');
+ //header('Cache-Control: public, must-revalidate, max-age=0'); // HTTP/1.1
+ header('Pragma: public');
+ header('Expires: Sat, 26 Jul 1997 05:00:00 GMT'); // Date in the past
+ header('Last-Modified: '.gmdate('D, d M Y H:i:s').' GMT');
+ header('Content-Disposition: inline; filename="'.basename($name).'"');
+ TCPDF_STATIC::sendOutputData(file_get_contents($name), filesize($name));
+ } elseif ($dest == 'FD') {
+ // send headers to browser
+ if (ob_get_contents()) {
+ $this->Error('Some data has already been output, can\'t send PDF file');
+ }
+ header('Content-Description: File Transfer');
+ if (headers_sent()) {
+ $this->Error('Some data has already been output to browser, can\'t send PDF file');
+ }
+ header('Cache-Control: private, must-revalidate, post-check=0, pre-check=0, max-age=1');
+ header('Pragma: public');
+ header('Expires: Sat, 26 Jul 1997 05:00:00 GMT'); // Date in the past
+ header('Last-Modified: '.gmdate('D, d M Y H:i:s').' GMT');
+ // force download dialog
+ if (strpos(php_sapi_name(), 'cgi') === false) {
+ header('Content-Type: application/force-download');
+ header('Content-Type: application/octet-stream', false);
+ header('Content-Type: application/download', false);
+ header('Content-Type: application/pdf', false);
+ } else {
+ header('Content-Type: application/pdf');
+ }
+ // use the Content-Disposition header to supply a recommended filename
+ header('Content-Disposition: attachment; filename="'.basename($name).'"');
+ header('Content-Transfer-Encoding: binary');
+ TCPDF_STATIC::sendOutputData(file_get_contents($name), filesize($name));
+ }
+ break;
+ }
+ case 'E': {
+ // return PDF as base64 mime multi-part email attachment (RFC 2045)
+ $retval = 'Content-Type: application/pdf;'."\r\n";
+ $retval .= ' name="'.$name.'"'."\r\n";
+ $retval .= 'Content-Transfer-Encoding: base64'."\r\n";
+ $retval .= 'Content-Disposition: attachment;'."\r\n";
+ $retval .= ' filename="'.$name.'"'."\r\n\r\n";
+ $retval .= chunk_split(base64_encode($this->getBuffer()), 76, "\r\n");
+ return $retval;
+ }
+ case 'S': {
+ // returns PDF as a string
+ return $this->getBuffer();
+ }
+ default: {
+ $this->Error('Incorrect output destination: '.$dest);
+ }
+ }
+ return '';
+ }
+
+ /**
+ * Unset all class variables except the following critical variables.
+ * @param $destroyall (boolean) if true destroys all class variables, otherwise preserves critical variables.
+ * @param $preserve_objcopy (boolean) if true preserves the objcopy variable
+ * @public
+ * @since 4.5.016 (2009-02-24)
+ */
+ public function _destroy($destroyall=false, $preserve_objcopy=false) {
+ if ($destroyall AND isset($this->diskcache) AND $this->diskcache AND (!$preserve_objcopy) AND (!TCPDF_STATIC::empty_string($this->buffer))) {
+ // remove buffer file from cache
+ unlink($this->buffer);
+ }
+ if ($destroyall AND isset($this->cached_files) AND !empty($this->cached_files)) {
+ // remove cached files
+ foreach ($this->cached_files as $cachefile) {
+ if (is_file($cachefile)) {
+ unlink($cachefile);
+ }
+ }
+ unset($this->cached_files);
+ }
+ foreach (array_keys(get_object_vars($this)) as $val) {
+ if ($destroyall OR (
+ ($val != 'internal_encoding')
+ AND ($val != 'state')
+ AND ($val != 'bufferlen')
+ AND ($val != 'buffer')
+ AND ($val != 'diskcache')
+ AND ($val != 'cached_files')
+ AND ($val != 'sign')
+ AND ($val != 'signature_data')
+ AND ($val != 'signature_max_length')
+ AND ($val != 'byterange_string')
+ )) {
+ if ((!$preserve_objcopy OR ($val != 'objcopy')) AND isset($this->$val)) {
+ unset($this->$val);
+ }
+ }
+ }
+ }
+
+ /**
+ * Check for locale-related bug
+ * @protected
+ */
+ protected function _dochecks() {
+ //Check for locale-related bug
+ if (1.1 == 1) {
+ $this->Error('Don\'t alter the locale before including class file');
+ }
+ //Check for decimal separator
+ if (sprintf('%.1F', 1.0) != '1.0') {
+ setlocale(LC_NUMERIC, 'C');
+ }
+ }
+
+ /**
+ * Return an array containing variations for the basic page number alias.
+ * @param $a (string) Base alias.
+ * @return array of page number aliases
+ * @protected
+ */
+ protected function getInternalPageNumberAliases($a= '') {
+ $alias = array();
+ // build array of Unicode + ASCII variants (the order is important)
+ $alias = array('u' => array(), 'a' => array());
+ $u = '{'.$a.'}';
+ $alias['u'][] = TCPDF_STATIC::_escape($u);
+ if ($this->isunicode) {
+ $alias['u'][] = TCPDF_STATIC::_escape(TCPDF_FONTS::UTF8ToLatin1($u, $this->isunicode, $this->CurrentFont));
+ $alias['u'][] = TCPDF_STATIC::_escape(TCPDF_FONTS::utf8StrRev($u, false, $this->tmprtl, $this->isunicode, $this->CurrentFont));
+ $alias['a'][] = TCPDF_STATIC::_escape(TCPDF_FONTS::UTF8ToLatin1($a, $this->isunicode, $this->CurrentFont));
+ $alias['a'][] = TCPDF_STATIC::_escape(TCPDF_FONTS::utf8StrRev($a, false, $this->tmprtl, $this->isunicode, $this->CurrentFont));
+ }
+ $alias['a'][] = TCPDF_STATIC::_escape($a);
+ return $alias;
+ }
+
+ /**
+ * Return an array containing all internal page aliases.
+ * @return array of page number aliases
+ * @protected
+ */
+ protected function getAllInternalPageNumberAliases() {
+ $basic_alias = array(TCPDF_STATIC::$alias_tot_pages, TCPDF_STATIC::$alias_num_page, TCPDF_STATIC::$alias_group_tot_pages, TCPDF_STATIC::$alias_group_num_page, TCPDF_STATIC::$alias_right_shift);
+ $pnalias = array();
+ foreach($basic_alias as $k => $a) {
+ $pnalias[$k] = $this->getInternalPageNumberAliases($a);
+ }
+ return $pnalias;
+ }
+
+ /**
+ * Replace right shift page number aliases with spaces to correct right alignment.
+ * This works perfectly only when using monospaced fonts.
+ * @param $page (string) Page content.
+ * @param $aliases (array) Array of page aliases.
+ * @param $diff (int) initial difference to add.
+ * @return replaced page content.
+ * @protected
+ */
+ protected function replaceRightShiftPageNumAliases($page, $aliases, $diff) {
+ foreach ($aliases as $type => $alias) {
+ foreach ($alias as $a) {
+ // find position of compensation factor
+ $startnum = (strpos($a, ':') + 1);
+ $a = substr($a, 0, $startnum);
+ if (($pos = strpos($page, $a)) !== false) {
+ // end of alias
+ $endnum = strpos($page, '}', $pos);
+ // string to be replaced
+ $aa = substr($page, $pos, ($endnum - $pos + 1));
+ // get compensation factor
+ $ratio = substr($page, ($pos + $startnum), ($endnum - $pos - $startnum));
+ $ratio = preg_replace('/[^0-9\.]/', '', $ratio);
+ $ratio = floatval($ratio);
+ if ($type == 'u') {
+ $chrdiff = floor(($diff + 12) * $ratio);
+ $shift = str_repeat(' ', $chrdiff);
+ $shift = TCPDF_FONTS::UTF8ToUTF16BE($shift, false, $this->isunicode, $this->CurrentFont);
+ } else {
+ $chrdiff = floor(($diff + 11) * $ratio);
+ $shift = str_repeat(' ', $chrdiff);
+ }
+ $page = str_replace($aa, $shift, $page);
+ }
+ }
+ }
+ return $page;
+ }
+
+ /**
+ * Set page boxes to be included on page descriptions.
+ * @param $boxes (array) Array of page boxes to set on document: ('MediaBox', 'CropBox', 'BleedBox', 'TrimBox', 'ArtBox').
+ * @protected
+ */
+ protected function setPageBoxTypes($boxes) {
+ $this->page_boxes = array();
+ foreach ($boxes as $box) {
+ if (in_array($box, TCPDF_STATIC::$pageboxes)) {
+ $this->page_boxes[] = $box;
+ }
+ }
+ }
+
+ /**
+ * Output pages (and replace page number aliases).
+ * @protected
+ */
+ protected function _putpages() {
+ $filter = ($this->compress) ? '/Filter /FlateDecode ' : '';
+ // get internal aliases for page numbers
+ $pnalias = $this->getAllInternalPageNumberAliases();
+ $num_pages = $this->numpages;
+ $ptpa = TCPDF_STATIC::formatPageNumber(($this->starting_page_number + $num_pages - 1));
+ $ptpu = TCPDF_FONTS::UTF8ToUTF16BE($ptpa, false, $this->isunicode, $this->CurrentFont);
+ $ptp_num_chars = $this->GetNumChars($ptpa);
+ $pagegroupnum = 0;
+ $groupnum = 0;
+ $ptgu = 1;
+ $ptga = 1;
+ $ptg_num_chars = 1;
+ for ($n = 1; $n <= $num_pages; ++$n) {
+ // get current page
+ $temppage = $this->getPageBuffer($n);
+ $pagelen = strlen($temppage);
+ // set replacements for total pages number
+ $pnpa = TCPDF_STATIC::formatPageNumber(($this->starting_page_number + $n - 1));
+ $pnpu = TCPDF_FONTS::UTF8ToUTF16BE($pnpa, false, $this->isunicode, $this->CurrentFont);
+ $pnp_num_chars = $this->GetNumChars($pnpa);
+ $pdiff = 0; // difference used for right shift alignment of page numbers
+ $gdiff = 0; // difference used for right shift alignment of page group numbers
+ if (!empty($this->pagegroups)) {
+ if (isset($this->newpagegroup[$n])) {
+ $pagegroupnum = 0;
+ ++$groupnum;
+ $ptga = TCPDF_STATIC::formatPageNumber($this->pagegroups[$groupnum]);
+ $ptgu = TCPDF_FONTS::UTF8ToUTF16BE($ptga, false, $this->isunicode, $this->CurrentFont);
+ $ptg_num_chars = $this->GetNumChars($ptga);
+ }
+ ++$pagegroupnum;
+ $pnga = TCPDF_STATIC::formatPageNumber($pagegroupnum);
+ $pngu = TCPDF_FONTS::UTF8ToUTF16BE($pnga, false, $this->isunicode, $this->CurrentFont);
+ $png_num_chars = $this->GetNumChars($pnga);
+ // replace page numbers
+ $replace = array();
+ $replace[] = array($ptgu, $ptg_num_chars, 9, $pnalias[2]['u']);
+ $replace[] = array($ptga, $ptg_num_chars, 7, $pnalias[2]['a']);
+ $replace[] = array($pngu, $png_num_chars, 9, $pnalias[3]['u']);
+ $replace[] = array($pnga, $png_num_chars, 7, $pnalias[3]['a']);
+ list($temppage, $gdiff) = TCPDF_STATIC::replacePageNumAliases($temppage, $replace, $gdiff);
+ }
+ // replace page numbers
+ $replace = array();
+ $replace[] = array($ptpu, $ptp_num_chars, 9, $pnalias[0]['u']);
+ $replace[] = array($ptpa, $ptp_num_chars, 7, $pnalias[0]['a']);
+ $replace[] = array($pnpu, $pnp_num_chars, 9, $pnalias[1]['u']);
+ $replace[] = array($pnpa, $pnp_num_chars, 7, $pnalias[1]['a']);
+ list($temppage, $pdiff) = TCPDF_STATIC::replacePageNumAliases($temppage, $replace, $pdiff);
+ // replace right shift alias
+ $temppage = $this->replaceRightShiftPageNumAliases($temppage, $pnalias[4], max($pdiff, $gdiff));
+ // replace EPS marker
+ $temppage = str_replace($this->epsmarker, '', $temppage);
+ //Page
+ $this->page_obj_id[$n] = $this->_newobj();
+ $out = '<<';
+ $out .= ' /Type /Page';
+ $out .= ' /Parent 1 0 R';
+ $out .= ' /LastModified '.$this->_datestring(0, $this->doc_modification_timestamp);
+ $out .= ' /Resources 2 0 R';
+ foreach ($this->page_boxes as $box) {
+ $out .= ' /'.$box;
+ $out .= sprintf(' [%F %F %F %F]', $this->pagedim[$n][$box]['llx'], $this->pagedim[$n][$box]['lly'], $this->pagedim[$n][$box]['urx'], $this->pagedim[$n][$box]['ury']);
+ }
+ if (isset($this->pagedim[$n]['BoxColorInfo']) AND !empty($this->pagedim[$n]['BoxColorInfo'])) {
+ $out .= ' /BoxColorInfo <<';
+ foreach ($this->page_boxes as $box) {
+ if (isset($this->pagedim[$n]['BoxColorInfo'][$box])) {
+ $out .= ' /'.$box.' <<';
+ if (isset($this->pagedim[$n]['BoxColorInfo'][$box]['C'])) {
+ $color = $this->pagedim[$n]['BoxColorInfo'][$box]['C'];
+ $out .= ' /C [';
+ $out .= sprintf(' %F %F %F', ($color[0] / 255), ($color[1] / 255), ($color[2] / 255));
+ $out .= ' ]';
+ }
+ if (isset($this->pagedim[$n]['BoxColorInfo'][$box]['W'])) {
+ $out .= ' /W '.($this->pagedim[$n]['BoxColorInfo'][$box]['W'] * $this->k);
+ }
+ if (isset($this->pagedim[$n]['BoxColorInfo'][$box]['S'])) {
+ $out .= ' /S /'.$this->pagedim[$n]['BoxColorInfo'][$box]['S'];
+ }
+ if (isset($this->pagedim[$n]['BoxColorInfo'][$box]['D'])) {
+ $dashes = $this->pagedim[$n]['BoxColorInfo'][$box]['D'];
+ $out .= ' /D [';
+ foreach ($dashes as $dash) {
+ $out .= sprintf(' %F', ($dash * $this->k));
+ }
+ $out .= ' ]';
+ }
+ $out .= ' >>';
+ }
+ }
+ $out .= ' >>';
+ }
+ $out .= ' /Contents '.($this->n + 1).' 0 R';
+ $out .= ' /Rotate '.$this->pagedim[$n]['Rotate'];
+ if (!$this->pdfa_mode) {
+ $out .= ' /Group << /Type /Group /S /Transparency /CS /DeviceRGB >>';
+ }
+ if (isset($this->pagedim[$n]['trans']) AND !empty($this->pagedim[$n]['trans'])) {
+ // page transitions
+ if (isset($this->pagedim[$n]['trans']['Dur'])) {
+ $out .= ' /Dur '.$this->pagedim[$n]['trans']['Dur'];
+ }
+ $out .= ' /Trans <<';
+ $out .= ' /Type /Trans';
+ if (isset($this->pagedim[$n]['trans']['S'])) {
+ $out .= ' /S /'.$this->pagedim[$n]['trans']['S'];
+ }
+ if (isset($this->pagedim[$n]['trans']['D'])) {
+ $out .= ' /D '.$this->pagedim[$n]['trans']['D'];
+ }
+ if (isset($this->pagedim[$n]['trans']['Dm'])) {
+ $out .= ' /Dm /'.$this->pagedim[$n]['trans']['Dm'];
+ }
+ if (isset($this->pagedim[$n]['trans']['M'])) {
+ $out .= ' /M /'.$this->pagedim[$n]['trans']['M'];
+ }
+ if (isset($this->pagedim[$n]['trans']['Di'])) {
+ $out .= ' /Di '.$this->pagedim[$n]['trans']['Di'];
+ }
+ if (isset($this->pagedim[$n]['trans']['SS'])) {
+ $out .= ' /SS '.$this->pagedim[$n]['trans']['SS'];
+ }
+ if (isset($this->pagedim[$n]['trans']['B'])) {
+ $out .= ' /B '.$this->pagedim[$n]['trans']['B'];
+ }
+ $out .= ' >>';
+ }
+ $out .= $this->_getannotsrefs($n);
+ $out .= ' /PZ '.$this->pagedim[$n]['PZ'];
+ $out .= ' >>';
+ $out .= "\n".'endobj';
+ $this->_out($out);
+ //Page content
+ $p = ($this->compress) ? gzcompress($temppage) : $temppage;
+ $this->_newobj();
+ $p = $this->_getrawstream($p);
+ $this->_out('<<'.$filter.'/Length '.strlen($p).'>> stream'."\n".$p."\n".'endstream'."\n".'endobj');
+ if ($this->diskcache) {
+ // remove temporary files
+ unlink($this->pages[$n]);
+ }
+ }
+ //Pages root
+ $out = $this->_getobj(1)."\n";
+ $out .= '<< /Type /Pages /Kids [';
+ foreach($this->page_obj_id as $page_obj) {
+ $out .= ' '.$page_obj.' 0 R';
+ }
+ $out .= ' ] /Count '.$num_pages.' >>';
+ $out .= "\n".'endobj';
+ $this->_out($out);
+ }
+
+ /**
+ * Output references to page annotations
+ * @param $n (int) page number
+ * @protected
+ * @author Nicola Asuni
+ * @since 4.7.000 (2008-08-29)
+ * @deprecated
+ */
+ protected function _putannotsrefs($n) {
+ $this->_out($this->_getannotsrefs($n));
+ }
+
+ /**
+ * Get references to page annotations.
+ * @param $n (int) page number
+ * @return string
+ * @protected
+ * @author Nicola Asuni
+ * @since 5.0.010 (2010-05-17)
+ */
+ protected function _getannotsrefs($n) {
+ if (!(isset($this->PageAnnots[$n]) OR ($this->sign AND isset($this->signature_data['cert_type'])))) {
+ return '';
+ }
+ $out = ' /Annots [';
+ if (isset($this->PageAnnots[$n])) {
+ foreach ($this->PageAnnots[$n] as $key => $val) {
+ if (!in_array($val['n'], $this->radio_groups)) {
+ $out .= ' '.$val['n'].' 0 R';
+ }
+ }
+ // add radiobutton groups
+ if (isset($this->radiobutton_groups[$n])) {
+ foreach ($this->radiobutton_groups[$n] as $key => $data) {
+ if (isset($data['n'])) {
+ $out .= ' '.$data['n'].' 0 R';
+ }
+ }
+ }
+ }
+ if ($this->sign AND ($n == $this->signature_appearance['page']) AND isset($this->signature_data['cert_type'])) {
+ // set reference for signature object
+ $out .= ' '.$this->sig_obj_id.' 0 R';
+ }
+ if (!empty($this->empty_signature_appearance)) {
+ foreach ($this->empty_signature_appearance as $esa) {
+ if ($esa['page'] == $n) {
+ // set reference for empty signature objects
+ $out .= ' '.$esa['objid'].' 0 R';
+ }
+ }
+ }
+ $out .= ' ]';
+ return $out;
+ }
+
+ /**
+ * Output annotations objects for all pages.
+ * !!! THIS METHOD IS NOT YET COMPLETED !!!
+ * See section 12.5 of PDF 32000_2008 reference.
+ * @protected
+ * @author Nicola Asuni
+ * @since 4.0.018 (2008-08-06)
+ */
+ protected function _putannotsobjs() {
+ // reset object counter
+ for ($n=1; $n <= $this->numpages; ++$n) {
+ if (isset($this->PageAnnots[$n])) {
+ // set page annotations
+ foreach ($this->PageAnnots[$n] as $key => $pl) {
+ $annot_obj_id = $this->PageAnnots[$n][$key]['n'];
+ // create annotation object for grouping radiobuttons
+ if (isset($this->radiobutton_groups[$n][$pl['txt']]) AND is_array($this->radiobutton_groups[$n][$pl['txt']])) {
+ $radio_button_obj_id = $this->radiobutton_groups[$n][$pl['txt']]['n'];
+ $annots = '<<';
+ $annots .= ' /Type /Annot';
+ $annots .= ' /Subtype /Widget';
+ $annots .= ' /Rect [0 0 0 0]';
+ if ($this->radiobutton_groups[$n][$pl['txt']]['#readonly#']) {
+ // read only
+ $annots .= ' /F 68';
+ $annots .= ' /Ff 49153';
+ } else {
+ $annots .= ' /F 4'; // default print for PDF/A
+ $annots .= ' /Ff 49152';
+ }
+ $annots .= ' /T '.$this->_datastring($pl['txt'], $radio_button_obj_id);
+ if (isset($pl['opt']['tu']) AND is_string($pl['opt']['tu'])) {
+ $annots .= ' /TU '.$this->_datastring($pl['opt']['tu'], $radio_button_obj_id);
+ }
+ $annots .= ' /FT /Btn';
+ $annots .= ' /Kids [';
+ $defval = '';
+ foreach ($this->radiobutton_groups[$n][$pl['txt']] as $key => $data) {
+ if (isset($data['kid'])) {
+ $annots .= ' '.$data['kid'].' 0 R';
+ if ($data['def'] !== 'Off') {
+ $defval = $data['def'];
+ }
+ }
+ }
+ $annots .= ' ]';
+ if (!empty($defval)) {
+ $annots .= ' /V /'.$defval;
+ }
+ $annots .= ' >>';
+ $this->_out($this->_getobj($radio_button_obj_id)."\n".$annots."\n".'endobj');
+ $this->form_obj_id[] = $radio_button_obj_id;
+ // store object id to be used on Parent entry of Kids
+ $this->radiobutton_groups[$n][$pl['txt']] = $radio_button_obj_id;
+ }
+ $formfield = false;
+ $pl['opt'] = array_change_key_case($pl['opt'], CASE_LOWER);
+ $a = $pl['x'] * $this->k;
+ $b = $this->pagedim[$n]['h'] - (($pl['y'] + $pl['h']) * $this->k);
+ $c = $pl['w'] * $this->k;
+ $d = $pl['h'] * $this->k;
+ $rect = sprintf('%F %F %F %F', $a, $b, $a+$c, $b+$d);
+ // create new annotation object
+ $annots = '<</Type /Annot';
+ $annots .= ' /Subtype /'.$pl['opt']['subtype'];
+ $annots .= ' /Rect ['.$rect.']';
+ $ft = array('Btn', 'Tx', 'Ch', 'Sig');
+ if (isset($pl['opt']['ft']) AND in_array($pl['opt']['ft'], $ft)) {
+ $annots .= ' /FT /'.$pl['opt']['ft'];
+ $formfield = true;
+ }
+ $annots .= ' /Contents '.$this->_textstring($pl['txt'], $annot_obj_id);
+ $annots .= ' /P '.$this->page_obj_id[$n].' 0 R';
+ $annots .= ' /NM '.$this->_datastring(sprintf('%04u-%04u', $n, $key), $annot_obj_id);
+ $annots .= ' /M '.$this->_datestring($annot_obj_id, $this->doc_modification_timestamp);
+ if (isset($pl['opt']['f'])) {
+ $fval = 0;
+ if (is_array($pl['opt']['f'])) {
+ foreach ($pl['opt']['f'] as $f) {
+ switch (strtolower($f)) {
+ case 'invisible': {
+ $fval += 1 << 0;
+ break;
+ }
+ case 'hidden': {
+ $fval += 1 << 1;
+ break;
+ }
+ case 'print': {
+ $fval += 1 << 2;
+ break;
+ }
+ case 'nozoom': {
+ $fval += 1 << 3;
+ break;
+ }
+ case 'norotate': {
+ $fval += 1 << 4;
+ break;
+ }
+ case 'noview': {
+ $fval += 1 << 5;
+ break;
+ }
+ case 'readonly': {
+ $fval += 1 << 6;
+ break;
+ }
+ case 'locked': {
+ $fval += 1 << 8;
+ break;
+ }
+ case 'togglenoview': {
+ $fval += 1 << 9;
+ break;
+ }
+ case 'lockedcontents': {
+ $fval += 1 << 10;
+ break;
+ }
+ default: {
+ break;
+ }
+ }
+ }
+ } else {
+ $fval = intval($pl['opt']['f']);
+ }
+ } else {
+ $fval = 4;
+ }
+ if ($this->pdfa_mode) {
+ // force print flag for PDF/A mode
+ $fval |= 4;
+ }
+ $annots .= ' /F '.intval($fval);
+ if (isset($pl['opt']['as']) AND is_string($pl['opt']['as'])) {
+ $annots .= ' /AS /'.$pl['opt']['as'];
+ }
+ if (isset($pl['opt']['ap'])) {
+ // appearance stream
+ $annots .= ' /AP <<';
+ if (is_array($pl['opt']['ap'])) {
+ foreach ($pl['opt']['ap'] as $apmode => $apdef) {
+ // $apmode can be: n = normal; r = rollover; d = down;
+ $annots .= ' /'.strtoupper($apmode);
+ if (is_array($apdef)) {
+ $annots .= ' <<';
+ foreach ($apdef as $apstate => $stream) {
+ // reference to XObject that define the appearance for this mode-state
+ $apsobjid = $this->_putAPXObject($c, $d, $stream);
+ $annots .= ' /'.$apstate.' '.$apsobjid.' 0 R';
+ }
+ $annots .= ' >>';
+ } else {
+ // reference to XObject that define the appearance for this mode
+ $apsobjid = $this->_putAPXObject($c, $d, $apdef);
+ $annots .= ' '.$apsobjid.' 0 R';
+ }
+ }
+ } else {
+ $annots .= $pl['opt']['ap'];
+ }
+ $annots .= ' >>';
+ }
+ if (isset($pl['opt']['bs']) AND (is_array($pl['opt']['bs']))) {
+ $annots .= ' /BS <<';
+ $annots .= ' /Type /Border';
+ if (isset($pl['opt']['bs']['w'])) {
+ $annots .= ' /W '.intval($pl['opt']['bs']['w']);
+ }
+ $bstyles = array('S', 'D', 'B', 'I', 'U');
+ if (isset($pl['opt']['bs']['s']) AND in_array($pl['opt']['bs']['s'], $bstyles)) {
+ $annots .= ' /S /'.$pl['opt']['bs']['s'];
+ }
+ if (isset($pl['opt']['bs']['d']) AND (is_array($pl['opt']['bs']['d']))) {
+ $annots .= ' /D [';
+ foreach ($pl['opt']['bs']['d'] as $cord) {
+ $annots .= ' '.intval($cord);
+ }
+ $annots .= ']';
+ }
+ $annots .= ' >>';
+ } else {
+ $annots .= ' /Border [';
+ if (isset($pl['opt']['border']) AND (count($pl['opt']['border']) >= 3)) {
+ $annots .= intval($pl['opt']['border'][0]).' ';
+ $annots .= intval($pl['opt']['border'][1]).' ';
+ $annots .= intval($pl['opt']['border'][2]);
+ if (isset($pl['opt']['border'][3]) AND is_array($pl['opt']['border'][3])) {
+ $annots .= ' [';
+ foreach ($pl['opt']['border'][3] as $dash) {
+ $annots .= intval($dash).' ';
+ }
+ $annots .= ']';
+ }
+ } else {
+ $annots .= '0 0 0';
+ }
+ $annots .= ']';
+ }
+ if (isset($pl['opt']['be']) AND (is_array($pl['opt']['be']))) {
+ $annots .= ' /BE <<';
+ $bstyles = array('S', 'C');
+ if (isset($pl['opt']['be']['s']) AND in_array($pl['opt']['be']['s'], $bstyles)) {
+ $annots .= ' /S /'.$pl['opt']['bs']['s'];
+ } else {
+ $annots .= ' /S /S';
+ }
+ if (isset($pl['opt']['be']['i']) AND ($pl['opt']['be']['i'] >= 0) AND ($pl['opt']['be']['i'] <= 2)) {
+ $annots .= ' /I '.sprintf(' %F', $pl['opt']['be']['i']);
+ }
+ $annots .= '>>';
+ }
+ if (isset($pl['opt']['c']) AND (is_array($pl['opt']['c'])) AND !empty($pl['opt']['c'])) {
+ $annots .= ' /C '.TCPDF_COLORS::getColorStringFromArray($pl['opt']['c']);
+ }
+ //$annots .= ' /StructParent ';
+ //$annots .= ' /OC ';
+ $markups = array('text', 'freetext', 'line', 'square', 'circle', 'polygon', 'polyline', 'highlight', 'underline', 'squiggly', 'strikeout', 'stamp', 'caret', 'ink', 'fileattachment', 'sound');
+ if (in_array(strtolower($pl['opt']['subtype']), $markups)) {
+ // this is a markup type
+ if (isset($pl['opt']['t']) AND is_string($pl['opt']['t'])) {
+ $annots .= ' /T '.$this->_textstring($pl['opt']['t'], $annot_obj_id);
+ }
+ //$annots .= ' /Popup ';
+ if (isset($pl['opt']['ca'])) {
+ $annots .= ' /CA '.sprintf('%F', floatval($pl['opt']['ca']));
+ }
+ if (isset($pl['opt']['rc'])) {
+ $annots .= ' /RC '.$this->_textstring($pl['opt']['rc'], $annot_obj_id);
+ }
+ $annots .= ' /CreationDate '.$this->_datestring($annot_obj_id, $this->doc_creation_timestamp);
+ //$annots .= ' /IRT ';
+ if (isset($pl['opt']['subj'])) {
+ $annots .= ' /Subj '.$this->_textstring($pl['opt']['subj'], $annot_obj_id);
+ }
+ //$annots .= ' /RT ';
+ //$annots .= ' /IT ';
+ //$annots .= ' /ExData ';
+ }
+ $lineendings = array('Square', 'Circle', 'Diamond', 'OpenArrow', 'ClosedArrow', 'None', 'Butt', 'ROpenArrow', 'RClosedArrow', 'Slash');
+ // Annotation types
+ switch (strtolower($pl['opt']['subtype'])) {
+ case 'text': {
+ if (isset($pl['opt']['open'])) {
+ $annots .= ' /Open '. (strtolower($pl['opt']['open']) == 'true' ? 'true' : 'false');
+ }
+ $iconsapp = array('Comment', 'Help', 'Insert', 'Key', 'NewParagraph', 'Note', 'Paragraph');
+ if (isset($pl['opt']['name']) AND in_array($pl['opt']['name'], $iconsapp)) {
+ $annots .= ' /Name /'.$pl['opt']['name'];
+ } else {
+ $annots .= ' /Name /Note';
+ }
+ $statemodels = array('Marked', 'Review');
+ if (isset($pl['opt']['statemodel']) AND in_array($pl['opt']['statemodel'], $statemodels)) {
+ $annots .= ' /StateModel /'.$pl['opt']['statemodel'];
+ } else {
+ $pl['opt']['statemodel'] = 'Marked';
+ $annots .= ' /StateModel /'.$pl['opt']['statemodel'];
+ }
+ if ($pl['opt']['statemodel'] == 'Marked') {
+ $states = array('Accepted', 'Unmarked');
+ } else {
+ $states = array('Accepted', 'Rejected', 'Cancelled', 'Completed', 'None');
+ }
+ if (isset($pl['opt']['state']) AND in_array($pl['opt']['state'], $states)) {
+ $annots .= ' /State /'.$pl['opt']['state'];
+ } else {
+ if ($pl['opt']['statemodel'] == 'Marked') {
+ $annots .= ' /State /Unmarked';
+ } else {
+ $annots .= ' /State /None';
+ }
+ }
+ break;
+ }
+ case 'link': {
+ if (is_string($pl['txt'])) {
+ if ($pl['txt'][0] == '#') {
+ // internal destination
+ $annots .= ' /Dest /'.TCPDF_STATIC::encodeNameObject(substr($pl['txt'], 1));
+ } elseif ($pl['txt'][0] == '%') {
+ // embedded PDF file
+ $filename = basename(substr($pl['txt'], 1));
+ $annots .= ' /A << /S /GoToE /D [0 /Fit] /NewWindow true /T << /R /C /P '.($n - 1).' /A '.$this->embeddedfiles[$filename]['a'].' >> >>';
+ } elseif ($pl['txt'][0] == '*') {
+ // embedded generic file
+ $filename = basename(substr($pl['txt'], 1));
+ $jsa = 'var D=event.target.doc;var MyData=D.dataObjects;for (var i in MyData) if (MyData[i].path=="'.$filename.'") D.exportDataObject( { cName : MyData[i].name, nLaunch : 2});';
+ $annots .= ' /A << /S /JavaScript /JS '.$this->_textstring($jsa, $annot_obj_id).'>>';
+ } else {
+ // external URI link
+ $annots .= ' /A <</S /URI /URI '.$this->_datastring($this->unhtmlentities($pl['txt']), $annot_obj_id).'>>';
+ }
+ } elseif (isset($this->links[$pl['txt']])) {
+ // internal link ID
+ $l = $this->links[$pl['txt']];
+ if (isset($this->page_obj_id[($l[0])])) {
+ $annots .= sprintf(' /Dest [%u 0 R /XYZ 0 %F null]', $this->page_obj_id[($l[0])], ($this->pagedim[$l[0]]['h'] - ($l[1] * $this->k)));
+ }
+ }
+ $hmodes = array('N', 'I', 'O', 'P');
+ if (isset($pl['opt']['h']) AND in_array($pl['opt']['h'], $hmodes)) {
+ $annots .= ' /H /'.$pl['opt']['h'];
+ } else {
+ $annots .= ' /H /I';
+ }
+ //$annots .= ' /PA ';
+ //$annots .= ' /Quadpoints ';
+ break;
+ }
+ case 'freetext': {
+ if (isset($pl['opt']['da']) AND !empty($pl['opt']['da'])) {
+ $annots .= ' /DA ('.$pl['opt']['da'].')';
+ }
+ if (isset($pl['opt']['q']) AND ($pl['opt']['q'] >= 0) AND ($pl['opt']['q'] <= 2)) {
+ $annots .= ' /Q '.intval($pl['opt']['q']);
+ }
+ if (isset($pl['opt']['rc'])) {
+ $annots .= ' /RC '.$this->_textstring($pl['opt']['rc'], $annot_obj_id);
+ }
+ if (isset($pl['opt']['ds'])) {
+ $annots .= ' /DS '.$this->_textstring($pl['opt']['ds'], $annot_obj_id);
+ }
+ if (isset($pl['opt']['cl']) AND is_array($pl['opt']['cl'])) {
+ $annots .= ' /CL [';
+ foreach ($pl['opt']['cl'] as $cl) {
+ $annots .= sprintf('%F ', $cl * $this->k);
+ }
+ $annots .= ']';
+ }
+ $tfit = array('FreeText', 'FreeTextCallout', 'FreeTextTypeWriter');
+ if (isset($pl['opt']['it']) AND in_array($pl['opt']['it'], $tfit)) {
+ $annots .= ' /IT /'.$pl['opt']['it'];
+ }
+ if (isset($pl['opt']['rd']) AND is_array($pl['opt']['rd'])) {
+ $l = $pl['opt']['rd'][0] * $this->k;
+ $r = $pl['opt']['rd'][1] * $this->k;
+ $t = $pl['opt']['rd'][2] * $this->k;
+ $b = $pl['opt']['rd'][3] * $this->k;
+ $annots .= ' /RD ['.sprintf('%F %F %F %F', $l, $r, $t, $b).']';
+ }
+ if (isset($pl['opt']['le']) AND in_array($pl['opt']['le'], $lineendings)) {
+ $annots .= ' /LE /'.$pl['opt']['le'];
+ }
+ break;
+ }
+ case 'line': {
+ break;
+ }
+ case 'square': {
+ break;
+ }
+ case 'circle': {
+ break;
+ }
+ case 'polygon': {
+ break;
+ }
+ case 'polyline': {
+ break;
+ }
+ case 'highlight': {
+ break;
+ }
+ case 'underline': {
+ break;
+ }
+ case 'squiggly': {
+ break;
+ }
+ case 'strikeout': {
+ break;
+ }
+ case 'stamp': {
+ break;
+ }
+ case 'caret': {
+ break;
+ }
+ case 'ink': {
+ break;
+ }
+ case 'popup': {
+ break;
+ }
+ case 'fileattachment': {
+ if ($this->pdfa_mode) {
+ // embedded files are not allowed in PDF/A mode
+ break;
+ }
+ if (!isset($pl['opt']['fs'])) {
+ break;
+ }
+ $filename = basename($pl['opt']['fs']);
+ if (isset($this->embeddedfiles[$filename]['f'])) {
+ $annots .= ' /FS '.$this->embeddedfiles[$filename]['f'].' 0 R';
+ $iconsapp = array('Graph', 'Paperclip', 'PushPin', 'Tag');
+ if (isset($pl['opt']['name']) AND in_array($pl['opt']['name'], $iconsapp)) {
+ $annots .= ' /Name /'.$pl['opt']['name'];
+ } else {
+ $annots .= ' /Name /PushPin';
+ }
+ // index (zero-based) of the annotation in the Annots array of this page
+ $this->embeddedfiles[$filename]['a'] = $key;
+ }
+ break;
+ }
+ case 'sound': {
+ if (!isset($pl['opt']['fs'])) {
+ break;
+ }
+ $filename = basename($pl['opt']['fs']);
+ if (isset($this->embeddedfiles[$filename]['f'])) {
+ // ... TO BE COMPLETED ...
+ // /R /C /B /E /CO /CP
+ $annots .= ' /Sound '.$this->embeddedfiles[$filename]['f'].' 0 R';
+ $iconsapp = array('Speaker', 'Mic');
+ if (isset($pl['opt']['name']) AND in_array($pl['opt']['name'], $iconsapp)) {
+ $annots .= ' /Name /'.$pl['opt']['name'];
+ } else {
+ $annots .= ' /Name /Speaker';
+ }
+ }
+ break;
+ }
+ case 'movie': {
+ break;
+ }
+ case 'widget': {
+ $hmode = array('N', 'I', 'O', 'P', 'T');
+ if (isset($pl['opt']['h']) AND in_array($pl['opt']['h'], $hmode)) {
+ $annots .= ' /H /'.$pl['opt']['h'];
+ }
+ if (isset($pl['opt']['mk']) AND (is_array($pl['opt']['mk'])) AND !empty($pl['opt']['mk'])) {
+ $annots .= ' /MK <<';
+ if (isset($pl['opt']['mk']['r'])) {
+ $annots .= ' /R '.$pl['opt']['mk']['r'];
+ }
+ if (isset($pl['opt']['mk']['bc']) AND (is_array($pl['opt']['mk']['bc']))) {
+ $annots .= ' /BC '.TCPDF_COLORS::getColorStringFromArray($pl['opt']['mk']['bc']);
+ }
+ if (isset($pl['opt']['mk']['bg']) AND (is_array($pl['opt']['mk']['bg']))) {
+ $annots .= ' /BG '.TCPDF_COLORS::getColorStringFromArray($pl['opt']['mk']['bg']);
+ }
+ if (isset($pl['opt']['mk']['ca'])) {
+ $annots .= ' /CA '.$pl['opt']['mk']['ca'];
+ }
+ if (isset($pl['opt']['mk']['rc'])) {
+ $annots .= ' /RC '.$pl['opt']['mk']['rc'];
+ }
+ if (isset($pl['opt']['mk']['ac'])) {
+ $annots .= ' /AC '.$pl['opt']['mk']['ac'];
+ }
+ if (isset($pl['opt']['mk']['i'])) {
+ $info = $this->getImageBuffer($pl['opt']['mk']['i']);
+ if ($info !== false) {
+ $annots .= ' /I '.$info['n'].' 0 R';
+ }
+ }
+ if (isset($pl['opt']['mk']['ri'])) {
+ $info = $this->getImageBuffer($pl['opt']['mk']['ri']);
+ if ($info !== false) {
+ $annots .= ' /RI '.$info['n'].' 0 R';
+ }
+ }
+ if (isset($pl['opt']['mk']['ix'])) {
+ $info = $this->getImageBuffer($pl['opt']['mk']['ix']);
+ if ($info !== false) {
+ $annots .= ' /IX '.$info['n'].' 0 R';
+ }
+ }
+ if (isset($pl['opt']['mk']['if']) AND (is_array($pl['opt']['mk']['if'])) AND !empty($pl['opt']['mk']['if'])) {
+ $annots .= ' /IF <<';
+ $if_sw = array('A', 'B', 'S', 'N');
+ if (isset($pl['opt']['mk']['if']['sw']) AND in_array($pl['opt']['mk']['if']['sw'], $if_sw)) {
+ $annots .= ' /SW /'.$pl['opt']['mk']['if']['sw'];
+ }
+ $if_s = array('A', 'P');
+ if (isset($pl['opt']['mk']['if']['s']) AND in_array($pl['opt']['mk']['if']['s'], $if_s)) {
+ $annots .= ' /S /'.$pl['opt']['mk']['if']['s'];
+ }
+ if (isset($pl['opt']['mk']['if']['a']) AND (is_array($pl['opt']['mk']['if']['a'])) AND !empty($pl['opt']['mk']['if']['a'])) {
+ $annots .= sprintf(' /A [%F %F]', $pl['opt']['mk']['if']['a'][0], $pl['opt']['mk']['if']['a'][1]);
+ }
+ if (isset($pl['opt']['mk']['if']['fb']) AND ($pl['opt']['mk']['if']['fb'])) {
+ $annots .= ' /FB true';
+ }
+ $annots .= '>>';
+ }
+ if (isset($pl['opt']['mk']['tp']) AND ($pl['opt']['mk']['tp'] >= 0) AND ($pl['opt']['mk']['tp'] <= 6)) {
+ $annots .= ' /TP '.intval($pl['opt']['mk']['tp']);
+ }
+ $annots .= '>>';
+ } // end MK
+ // --- Entries for field dictionaries ---
+ if (isset($this->radiobutton_groups[$n][$pl['txt']])) {
+ // set parent
+ $annots .= ' /Parent '.$this->radiobutton_groups[$n][$pl['txt']].' 0 R';
+ }
+ if (isset($pl['opt']['t']) AND is_string($pl['opt']['t'])) {
+ $annots .= ' /T '.$this->_datastring($pl['opt']['t'], $annot_obj_id);
+ }
+ if (isset($pl['opt']['tu']) AND is_string($pl['opt']['tu'])) {
+ $annots .= ' /TU '.$this->_datastring($pl['opt']['tu'], $annot_obj_id);
+ }
+ if (isset($pl['opt']['tm']) AND is_string($pl['opt']['tm'])) {
+ $annots .= ' /TM '.$this->_datastring($pl['opt']['tm'], $annot_obj_id);
+ }
+ if (isset($pl['opt']['ff'])) {
+ if (is_array($pl['opt']['ff'])) {
+ // array of bit settings
+ $flag = 0;
+ foreach($pl['opt']['ff'] as $val) {
+ $flag += 1 << ($val - 1);
+ }
+ } else {
+ $flag = intval($pl['opt']['ff']);
+ }
+ $annots .= ' /Ff '.$flag;
+ }
+ if (isset($pl['opt']['maxlen'])) {
+ $annots .= ' /MaxLen '.intval($pl['opt']['maxlen']);
+ }
+ if (isset($pl['opt']['v'])) {
+ $annots .= ' /V';
+ if (is_array($pl['opt']['v'])) {
+ foreach ($pl['opt']['v'] AS $optval) {
+ if (is_float($optval)) {
+ $optval = sprintf('%F', $optval);
+ }
+ $annots .= ' '.$optval;
+ }
+ } else {
+ $annots .= ' '.$this->_textstring($pl['opt']['v'], $annot_obj_id);
+ }
+ }
+ if (isset($pl['opt']['dv'])) {
+ $annots .= ' /DV';
+ if (is_array($pl['opt']['dv'])) {
+ foreach ($pl['opt']['dv'] AS $optval) {
+ if (is_float($optval)) {
+ $optval = sprintf('%F', $optval);
+ }
+ $annots .= ' '.$optval;
+ }
+ } else {
+ $annots .= ' '.$this->_textstring($pl['opt']['dv'], $annot_obj_id);
+ }
+ }
+ if (isset($pl['opt']['rv'])) {
+ $annots .= ' /RV';
+ if (is_array($pl['opt']['rv'])) {
+ foreach ($pl['opt']['rv'] AS $optval) {
+ if (is_float($optval)) {
+ $optval = sprintf('%F', $optval);
+ }
+ $annots .= ' '.$optval;
+ }
+ } else {
+ $annots .= ' '.$this->_textstring($pl['opt']['rv'], $annot_obj_id);
+ }
+ }
+ if (isset($pl['opt']['a']) AND !empty($pl['opt']['a'])) {
+ $annots .= ' /A << '.$pl['opt']['a'].' >>';
+ }
+ if (isset($pl['opt']['aa']) AND !empty($pl['opt']['aa'])) {
+ $annots .= ' /AA << '.$pl['opt']['aa'].' >>';
+ }
+ if (isset($pl['opt']['da']) AND !empty($pl['opt']['da'])) {
+ $annots .= ' /DA ('.$pl['opt']['da'].')';
+ }
+ if (isset($pl['opt']['q']) AND ($pl['opt']['q'] >= 0) AND ($pl['opt']['q'] <= 2)) {
+ $annots .= ' /Q '.intval($pl['opt']['q']);
+ }
+ if (isset($pl['opt']['opt']) AND (is_array($pl['opt']['opt'])) AND !empty($pl['opt']['opt'])) {
+ $annots .= ' /Opt [';
+ foreach($pl['opt']['opt'] AS $copt) {
+ if (is_array($copt)) {
+ $annots .= ' ['.$this->_textstring($copt[0], $annot_obj_id).' '.$this->_textstring($copt[1], $annot_obj_id).']';
+ } else {
+ $annots .= ' '.$this->_textstring($copt, $annot_obj_id);
+ }
+ }
+ $annots .= ']';
+ }
+ if (isset($pl['opt']['ti'])) {
+ $annots .= ' /TI '.intval($pl['opt']['ti']);
+ }
+ if (isset($pl['opt']['i']) AND (is_array($pl['opt']['i'])) AND !empty($pl['opt']['i'])) {
+ $annots .= ' /I [';
+ foreach($pl['opt']['i'] AS $copt) {
+ $annots .= intval($copt).' ';
+ }
+ $annots .= ']';
+ }
+ break;
+ }
+ case 'screen': {
+ break;
+ }
+ case 'printermark': {
+ break;
+ }
+ case 'trapnet': {
+ break;
+ }
+ case 'watermark': {
+ break;
+ }
+ case '3d': {
+ break;
+ }
+ default: {
+ break;
+ }
+ }
+ $annots .= '>>';
+ // create new annotation object
+ $this->_out($this->_getobj($annot_obj_id)."\n".$annots."\n".'endobj');
+ if ($formfield AND !isset($this->radiobutton_groups[$n][$pl['txt']])) {
+ // store reference of form object
+ $this->form_obj_id[] = $annot_obj_id;
+ }
+ }
+ }
+ } // end for each page
+ }
+
+ /**
+ * Put appearance streams XObject used to define annotation's appearance states.
+ * @param $w (int) annotation width
+ * @param $h (int) annotation height
+ * @param $stream (string) appearance stream
+ * @return int object ID
+ * @protected
+ * @since 4.8.001 (2009-09-09)
+ */
+ protected function _putAPXObject($w=0, $h=0, $stream='') {
+ $stream = trim($stream);
+ $out = $this->_getobj()."\n";
+ $this->xobjects['AX'.$this->n] = array('n' => $this->n);
+ $out .= '<<';
+ $out .= ' /Type /XObject';
+ $out .= ' /Subtype /Form';
+ $out .= ' /FormType 1';
+ if ($this->compress) {
+ $stream = gzcompress($stream);
+ $out .= ' /Filter /FlateDecode';
+ }
+ $rect = sprintf('%F %F', $w, $h);
+ $out .= ' /BBox [0 0 '.$rect.']';
+ $out .= ' /Matrix [1 0 0 1 0 0]';
+ $out .= ' /Resources 2 0 R';
+ $stream = $this->_getrawstream($stream);
+ $out .= ' /Length '.strlen($stream);
+ $out .= ' >>';
+ $out .= ' stream'."\n".$stream."\n".'endstream';
+ $out .= "\n".'endobj';
+ $this->_out($out);
+ return $this->n;
+ }
+
+ /**
+ * Output fonts.
+ * @author Nicola Asuni
+ * @protected
+ */
+ protected function _putfonts() {
+ $nf = $this->n;
+ foreach ($this->diffs as $diff) {
+ //Encodings
+ $this->_newobj();
+ $this->_out('<< /Type /Encoding /BaseEncoding /WinAnsiEncoding /Differences ['.$diff.'] >>'."\n".'endobj');
+ }
+ $mqr = TCPDF_STATIC::get_mqr();
+ TCPDF_STATIC::set_mqr(false);
+ foreach ($this->FontFiles as $file => $info) {
+ // search and get font file to embedd
+ $fontfile = TCPDF_FONTS::getFontFullPath($file, $info['fontdir']);
+ if (!TCPDF_STATIC::empty_string($fontfile)) {
+ $font = file_get_contents($fontfile);
+ $compressed = (substr($file, -2) == '.z');
+ if ((!$compressed) AND (isset($info['length2']))) {
+ $header = (ord($font{0}) == 128);
+ if ($header) {
+ // strip first binary header
+ $font = substr($font, 6);
+ }
+ if ($header AND (ord($font[$info['length1']]) == 128)) {
+ // strip second binary header
+ $font = substr($font, 0, $info['length1']).substr($font, ($info['length1'] + 6));
+ }
+ } elseif ($info['subset'] AND ((!$compressed) OR ($compressed AND function_exists('gzcompress')))) {
+ if ($compressed) {
+ // uncompress font
+ $font = gzuncompress($font);
+ }
+ // merge subset characters
+ $subsetchars = array(); // used chars
+ foreach ($info['fontkeys'] as $fontkey) {
+ $fontinfo = $this->getFontBuffer($fontkey);
+ $subsetchars += $fontinfo['subsetchars'];
+ }
+ // rebuild a font subset
+ $font = TCPDF_FONTS::_getTrueTypeFontSubset($font, $subsetchars);
+ // calculate new font length
+ $info['length1'] = strlen($font);
+ if ($compressed) {
+ // recompress font
+ $font = gzcompress($font);
+ }
+ }
+ $this->_newobj();
+ $this->FontFiles[$file]['n'] = $this->n;
+ $stream = $this->_getrawstream($font);
+ $out = '<< /Length '.strlen($stream);
+ if ($compressed) {
+ $out .= ' /Filter /FlateDecode';
+ }
+ $out .= ' /Length1 '.$info['length1'];
+ if (isset($info['length2'])) {
+ $out .= ' /Length2 '.$info['length2'].' /Length3 0';
+ }
+ $out .= ' >>';
+ $out .= ' stream'."\n".$stream."\n".'endstream';
+ $out .= "\n".'endobj';
+ $this->_out($out);
+ }
+ }
+ TCPDF_STATIC::set_mqr($mqr);
+ foreach ($this->fontkeys as $k) {
+ //Font objects
+ $font = $this->getFontBuffer($k);
+ $type = $font['type'];
+ $name = $font['name'];
+ if ($type == 'core') {
+ // standard core font
+ $out = $this->_getobj($this->font_obj_ids[$k])."\n";
+ $out .= '<</Type /Font';
+ $out .= ' /Subtype /Type1';
+ $out .= ' /BaseFont /'.$name;
+ $out .= ' /Name /F'.$font['i'];
+ if ((strtolower($name) != 'symbol') AND (strtolower($name) != 'zapfdingbats')) {
+ $out .= ' /Encoding /WinAnsiEncoding';
+ }
+ if ($k == 'helvetica') {
+ // add default font for annotations
+ $this->annotation_fonts[$k] = $font['i'];
+ }
+ $out .= ' >>';
+ $out .= "\n".'endobj';
+ $this->_out($out);
+ } elseif (($type == 'Type1') OR ($type == 'TrueType')) {
+ // additional Type1 or TrueType font
+ $out = $this->_getobj($this->font_obj_ids[$k])."\n";
+ $out .= '<</Type /Font';
+ $out .= ' /Subtype /'.$type;
+ $out .= ' /BaseFont /'.$name;
+ $out .= ' /Name /F'.$font['i'];
+ $out .= ' /FirstChar 32 /LastChar 255';
+ $out .= ' /Widths '.($this->n + 1).' 0 R';
+ $out .= ' /FontDescriptor '.($this->n + 2).' 0 R';
+ if ($font['enc']) {
+ if (isset($font['diff'])) {
+ $out .= ' /Encoding '.($nf + $font['diff']).' 0 R';
+ } else {
+ $out .= ' /Encoding /WinAnsiEncoding';
+ }
+ }
+ $out .= ' >>';
+ $out .= "\n".'endobj';
+ $this->_out($out);
+ // Widths
+ $this->_newobj();
+ $s = '[';
+ for ($i = 32; $i < 256; ++$i) {
+ if (isset($font['cw'][$i])) {
+ $s .= $font['cw'][$i].' ';
+ } else {
+ $s .= $font['dw'].' ';
+ }
+ }
+ $s .= ']';
+ $s .= "\n".'endobj';
+ $this->_out($s);
+ //Descriptor
+ $this->_newobj();
+ $s = '<</Type /FontDescriptor /FontName /'.$name;
+ foreach ($font['desc'] as $fdk => $fdv) {
+ if (is_float($fdv)) {
+ $fdv = sprintf('%F', $fdv);
+ }
+ $s .= ' /'.$fdk.' '.$fdv.'';
+ }
+ if (!TCPDF_STATIC::empty_string($font['file'])) {
+ $s .= ' /FontFile'.($type == 'Type1' ? '' : '2').' '.$this->FontFiles[$font['file']]['n'].' 0 R';
+ }
+ $s .= '>>';
+ $s .= "\n".'endobj';
+ $this->_out($s);
+ } else {
+ // additional types
+ $mtd = '_put'.strtolower($type);
+ if (!method_exists($this, $mtd)) {
+ $this->Error('Unsupported font type: '.$type);
+ }
+ $this->$mtd($font);
+ }
+ }
+ }
+
+ /**
+ * Adds unicode fonts.<br>
+ * Based on PDF Reference 1.3 (section 5)
+ * @param $font (array) font data
+ * @protected
+ * @author Nicola Asuni
+ * @since 1.52.0.TC005 (2005-01-05)
+ */
+ protected function _puttruetypeunicode($font) {
+ $fontname = '';
+ if ($font['subset']) {
+ // change name for font subsetting
+ $subtag = sprintf('%06u', $font['i']);
+ $subtag = strtr($subtag, '0123456789', 'ABCDEFGHIJ');
+ $fontname .= $subtag.'+';
+ }
+ $fontname .= $font['name'];
+ // Type0 Font
+ // A composite font composed of other fonts, organized hierarchically
+ $out = $this->_getobj($this->font_obj_ids[$font['fontkey']])."\n";
+ $out .= '<< /Type /Font';
+ $out .= ' /Subtype /Type0';
+ $out .= ' /BaseFont /'.$fontname;
+ $out .= ' /Name /F'.$font['i'];
+ $out .= ' /Encoding /'.$font['enc'];
+ $out .= ' /ToUnicode '.($this->n + 1).' 0 R';
+ $out .= ' /DescendantFonts ['.($this->n + 2).' 0 R]';
+ $out .= ' >>';
+ $out .= "\n".'endobj';
+ $this->_out($out);
+ // ToUnicode map for Identity-H
+ $stream = TCPDF_FONT_DATA::$uni_identity_h;
+ // ToUnicode Object
+ $this->_newobj();
+ $stream = ($this->compress) ? gzcompress($stream) : $stream;
+ $filter = ($this->compress) ? '/Filter /FlateDecode ' : '';
+ $stream = $this->_getrawstream($stream);
+ $this->_out('<<'.$filter.'/Length '.strlen($stream).'>> stream'."\n".$stream."\n".'endstream'."\n".'endobj');
+ // CIDFontType2
+ // A CIDFont whose glyph descriptions are based on TrueType font technology
+ $oid = $this->_newobj();
+ $out = '<< /Type /Font';
+ $out .= ' /Subtype /CIDFontType2';
+ $out .= ' /BaseFont /'.$fontname;
+ // A dictionary containing entries that define the character collection of the CIDFont.
+ $cidinfo = '/Registry '.$this->_datastring($font['cidinfo']['Registry'], $oid);
+ $cidinfo .= ' /Ordering '.$this->_datastring($font['cidinfo']['Ordering'], $oid);
+ $cidinfo .= ' /Supplement '.$font['cidinfo']['Supplement'];
+ $out .= ' /CIDSystemInfo << '.$cidinfo.' >>';
+ $out .= ' /FontDescriptor '.($this->n + 1).' 0 R';
+ $out .= ' /DW '.$font['dw']; // default width
+ $out .= "\n".TCPDF_FONTS::_putfontwidths($font, 0);
+ if (isset($font['ctg']) AND (!TCPDF_STATIC::empty_string($font['ctg']))) {
+ $out .= "\n".'/CIDToGIDMap '.($this->n + 2).' 0 R';
+ }
+ $out .= ' >>';
+ $out .= "\n".'endobj';
+ $this->_out($out);
+ // Font descriptor
+ // A font descriptor describing the CIDFont default metrics other than its glyph widths
+ $this->_newobj();
+ $out = '<< /Type /FontDescriptor';
+ $out .= ' /FontName /'.$fontname;
+ foreach ($font['desc'] as $key => $value) {
+ if (is_float($value)) {
+ $value = sprintf('%F', $value);
+ }
+ $out .= ' /'.$key.' '.$value;
+ }
+ $fontdir = false;
+ if (!TCPDF_STATIC::empty_string($font['file'])) {
+ // A stream containing a TrueType font
+ $out .= ' /FontFile2 '.$this->FontFiles[$font['file']]['n'].' 0 R';
+ $fontdir = $this->FontFiles[$font['file']]['fontdir'];
+ }
+ $out .= ' >>';
+ $out .= "\n".'endobj';
+ $this->_out($out);
+ if (isset($font['ctg']) AND (!TCPDF_STATIC::empty_string($font['ctg']))) {
+ $this->_newobj();
+ // Embed CIDToGIDMap
+ // A specification of the mapping from CIDs to glyph indices
+ // search and get CTG font file to embedd
+ $ctgfile = strtolower($font['ctg']);
+ // search and get ctg font file to embedd
+ $fontfile = TCPDF_FONTS::getFontFullPath($ctgfile, $fontdir);
+ if (TCPDF_STATIC::empty_string($fontfile)) {
+ $this->Error('Font file not found: '.$ctgfile);
+ }
+ $stream = $this->_getrawstream(file_get_contents($fontfile));
+ $out = '<< /Length '.strlen($stream).'';
+ if (substr($fontfile, -2) == '.z') { // check file extension
+ // Decompresses data encoded using the public-domain
+ // zlib/deflate compression method, reproducing the
+ // original text or binary data
+ $out .= ' /Filter /FlateDecode';
+ }
+ $out .= ' >>';
+ $out .= ' stream'."\n".$stream."\n".'endstream';
+ $out .= "\n".'endobj';
+ $this->_out($out);
+ }
+ }
+
+ /**
+ * Output CID-0 fonts.
+ * A Type 0 CIDFont contains glyph descriptions based on the Adobe Type 1 font format
+ * @param $font (array) font data
+ * @protected
+ * @author Andrew Whitehead, Nicola Asuni, Yukihiro Nakadaira
+ * @since 3.2.000 (2008-06-23)
+ */
+ protected function _putcidfont0($font) {
+ $cidoffset = 0;
+ if (!isset($font['cw'][1])) {
+ $cidoffset = 31;
+ }
+ if (isset($font['cidinfo']['uni2cid'])) {
+ // convert unicode to cid.
+ $uni2cid = $font['cidinfo']['uni2cid'];
+ $cw = array();
+ foreach ($font['cw'] as $uni => $width) {
+ if (isset($uni2cid[$uni])) {
+ $cw[($uni2cid[$uni] + $cidoffset)] = $width;
+ } elseif ($uni < 256) {
+ $cw[$uni] = $width;
+ } // else unknown character
+ }
+ $font = array_merge($font, array('cw' => $cw));
+ }
+ $name = $font['name'];
+ $enc = $font['enc'];
+ if ($enc) {
+ $longname = $name.'-'.$enc;
+ } else {
+ $longname = $name;
+ }
+ $out = $this->_getobj($this->font_obj_ids[$font['fontkey']])."\n";
+ $out .= '<</Type /Font';
+ $out .= ' /Subtype /Type0';
+ $out .= ' /BaseFont /'.$longname;
+ $out .= ' /Name /F'.$font['i'];
+ if ($enc) {
+ $out .= ' /Encoding /'.$enc;
+ }
+ $out .= ' /DescendantFonts ['.($this->n + 1).' 0 R]';
+ $out .= ' >>';
+ $out .= "\n".'endobj';
+ $this->_out($out);
+ $oid = $this->_newobj();
+ $out = '<</Type /Font';
+ $out .= ' /Subtype /CIDFontType0';
+ $out .= ' /BaseFont /'.$name;
+ $cidinfo = '/Registry '.$this->_datastring($font['cidinfo']['Registry'], $oid);
+ $cidinfo .= ' /Ordering '.$this->_datastring($font['cidinfo']['Ordering'], $oid);
+ $cidinfo .= ' /Supplement '.$font['cidinfo']['Supplement'];
+ $out .= ' /CIDSystemInfo <<'.$cidinfo.'>>';
+ $out .= ' /FontDescriptor '.($this->n + 1).' 0 R';
+ $out .= ' /DW '.$font['dw'];
+ $out .= "\n".TCPDF_FONTS::_putfontwidths($font, $cidoffset);
+ $out .= ' >>';
+ $out .= "\n".'endobj';
+ $this->_out($out);
+ $this->_newobj();
+ $s = '<</Type /FontDescriptor /FontName /'.$name;
+ foreach ($font['desc'] as $k => $v) {
+ if ($k != 'Style') {
+ if (is_float($v)) {
+ $v = sprintf('%F', $v);
+ }
+ $s .= ' /'.$k.' '.$v.'';
+ }
+ }
+ $s .= '>>';
+ $s .= "\n".'endobj';
+ $this->_out($s);
+ }
+
+ /**
+ * Output images.
+ * @protected
+ */
+ protected function _putimages() {
+ $filter = ($this->compress) ? '/Filter /FlateDecode ' : '';
+ foreach ($this->imagekeys as $file) {
+ $info = $this->getImageBuffer($file);
+ // set object for alternate images array
+ if ((!$this->pdfa_mode) AND isset($info['altimgs']) AND !empty($info['altimgs'])) {
+ $altoid = $this->_newobj();
+ $out = '[';
+ foreach ($info['altimgs'] as $altimage) {
+ if (isset($this->xobjects['I'.$altimage[0]]['n'])) {
+ $out .= ' << /Image '.$this->xobjects['I'.$altimage[0]]['n'].' 0 R';
+ $out .= ' /DefaultForPrinting';
+ if ($altimage[1] === true) {
+ $out .= ' true';
+ } else {
+ $out .= ' false';
+ }
+ $out .= ' >>';
+ }
+ }
+ $out .= ' ]';
+ $out .= "\n".'endobj';
+ $this->_out($out);
+ }
+ // set image object
+ $oid = $this->_newobj();
+ $this->xobjects['I'.$info['i']] = array('n' => $oid);
+ $this->setImageSubBuffer($file, 'n', $this->n);
+ $out = '<</Type /XObject';
+ $out .= ' /Subtype /Image';
+ $out .= ' /Width '.$info['w'];
+ $out .= ' /Height '.$info['h'];
+ if (array_key_exists('masked', $info)) {
+ $out .= ' /SMask '.($this->n - 1).' 0 R';
+ }
+ // set color space
+ $icc = false;
+ if (isset($info['icc']) AND ($info['icc'] !== false)) {
+ // ICC Colour Space
+ $icc = true;
+ $out .= ' /ColorSpace [/ICCBased '.($this->n + 1).' 0 R]';
+ } elseif ($info['cs'] == 'Indexed') {
+ // Indexed Colour Space
+ $out .= ' /ColorSpace [/Indexed /DeviceRGB '.((strlen($info['pal']) / 3) - 1).' '.($this->n + 1).' 0 R]';
+ } else {
+ // Device Colour Space
+ $out .= ' /ColorSpace /'.$info['cs'];
+ }
+ if ($info['cs'] == 'DeviceCMYK') {
+ $out .= ' /Decode [1 0 1 0 1 0 1 0]';
+ }
+ $out .= ' /BitsPerComponent '.$info['bpc'];
+ if (isset($altoid) AND ($altoid > 0)) {
+ // reference to alternate images dictionary
+ $out .= ' /Alternates '.$altoid.' 0 R';
+ }
+ if (isset($info['exurl']) AND !empty($info['exurl'])) {
+ // external stream
+ $out .= ' /Length 0';
+ $out .= ' /F << /FS /URL /F '.$this->_datastring($info['exurl'], $oid).' >>';
+ if (isset($info['f'])) {
+ $out .= ' /FFilter /'.$info['f'];
+ }
+ $out .= ' >>';
+ $out .= ' stream'."\n".'endstream';
+ } else {
+ if (isset($info['f'])) {
+ $out .= ' /Filter /'.$info['f'];
+ }
+ if (isset($info['parms'])) {
+ $out .= ' '.$info['parms'];
+ }
+ if (isset($info['trns']) AND is_array($info['trns'])) {
+ $trns = '';
+ $count_info = count($info['trns']);
+ for ($i=0; $i < $count_info; ++$i) {
+ $trns .= $info['trns'][$i].' '.$info['trns'][$i].' ';
+ }
+ $out .= ' /Mask ['.$trns.']';
+ }
+ $stream = $this->_getrawstream($info['data']);
+ $out .= ' /Length '.strlen($stream).' >>';
+ $out .= ' stream'."\n".$stream."\n".'endstream';
+ }
+ $out .= "\n".'endobj';
+ $this->_out($out);
+ if ($icc) {
+ // ICC colour profile
+ $this->_newobj();
+ $icc = ($this->compress) ? gzcompress($info['icc']) : $info['icc'];
+ $icc = $this->_getrawstream($icc);
+ $this->_out('<</N '.$info['ch'].' /Alternate /'.$info['cs'].' '.$filter.'/Length '.strlen($icc).'>> stream'."\n".$icc."\n".'endstream'."\n".'endobj');
+ } elseif ($info['cs'] == 'Indexed') {
+ // colour palette
+ $this->_newobj();
+ $pal = ($this->compress) ? gzcompress($info['pal']) : $info['pal'];
+ $pal = $this->_getrawstream($pal);
+ $this->_out('<<'.$filter.'/Length '.strlen($pal).'>> stream'."\n".$pal."\n".'endstream'."\n".'endobj');
+ }
+ }
+ }
+
+ /**
+ * Output Form XObjects Templates.
+ * @author Nicola Asuni
+ * @since 5.8.017 (2010-08-24)
+ * @protected
+ * @see startTemplate(), endTemplate(), printTemplate()
+ */
+ protected function _putxobjects() {
+ foreach ($this->xobjects as $key => $data) {
+ if (isset($data['outdata'])) {
+ $stream = str_replace($this->epsmarker, '', trim($data['outdata']));
+ $out = $this->_getobj($data['n'])."\n";
+ $out .= '<<';
+ $out .= ' /Type /XObject';
+ $out .= ' /Subtype /Form';
+ $out .= ' /FormType 1';
+ if ($this->compress) {
+ $stream = gzcompress($stream);
+ $out .= ' /Filter /FlateDecode';
+ }
+ $out .= sprintf(' /BBox [%F %F %F %F]', ($data['x'] * $this->k), (-$data['y'] * $this->k), (($data['w'] + $data['x']) * $this->k), (($data['h'] - $data['y']) * $this->k));
+ $out .= ' /Matrix [1 0 0 1 0 0]';
+ $out .= ' /Resources <<';
+ $out .= ' /ProcSet [/PDF /Text /ImageB /ImageC /ImageI]';
+ if (!$this->pdfa_mode) {
+ // transparency
+ if (isset($data['extgstates']) AND !empty($data['extgstates'])) {
+ $out .= ' /ExtGState <<';
+ foreach ($data['extgstates'] as $k => $extgstate) {
+ if (isset($this->extgstates[$k]['name'])) {
+ $out .= ' /'.$this->extgstates[$k]['name'];
+ } else {
+ $out .= ' /GS'.$k;
+ }
+ $out .= ' '.$this->extgstates[$k]['n'].' 0 R';
+ }
+ $out .= ' >>';
+ }
+ if (isset($data['gradients']) AND !empty($data['gradients'])) {
+ $gp = '';
+ $gs = '';
+ foreach ($data['gradients'] as $id => $grad) {
+ // gradient patterns
+ $gp .= ' /p'.$id.' '.$this->gradients[$id]['pattern'].' 0 R';
+ // gradient shadings
+ $gs .= ' /Sh'.$id.' '.$this->gradients[$id]['id'].' 0 R';
+ }
+ $out .= ' /Pattern <<'.$gp.' >>';
+ $out .= ' /Shading <<'.$gs.' >>';
+ }
+ }
+ // spot colors
+ if (isset($data['spot_colors']) AND !empty($data['spot_colors'])) {
+ $out .= ' /ColorSpace <<';
+ foreach ($data['spot_colors'] as $name => $color) {
+ $out .= ' /CS'.$color['i'].' '.$this->spot_colors[$name]['n'].' 0 R';
+ }
+ $out .= ' >>';
+ }
+ // fonts
+ if (!empty($data['fonts'])) {
+ $out .= ' /Font <<';
+ foreach ($data['fonts'] as $fontkey => $fontid) {
+ $out .= ' /F'.$fontid.' '.$this->font_obj_ids[$fontkey].' 0 R';
+ }
+ $out .= ' >>';
+ }
+ // images or nested xobjects
+ if (!empty($data['images']) OR !empty($data['xobjects'])) {
+ $out .= ' /XObject <<';
+ foreach ($data['images'] as $imgid) {
+ $out .= ' /I'.$imgid.' '.$this->xobjects['I'.$imgid]['n'].' 0 R';
+ }
+ foreach ($data['xobjects'] as $sub_id => $sub_objid) {
+ $out .= ' /'.$sub_id.' '.$sub_objid['n'].' 0 R';
+ }
+ $out .= ' >>';
+ }
+ $out .= ' >>'; //end resources
+ if (isset($data['group']) AND ($data['group'] !== false)) {
+ // set transparency group
+ $out .= ' /Group << /Type /Group /S /Transparency';
+ if (is_array($data['group'])) {
+ if (isset($data['group']['CS']) AND !empty($data['group']['CS'])) {
+ $out .= ' /CS /'.$data['group']['CS'];
+ }
+ if (isset($data['group']['I'])) {
+ $out .= ' /I /'.($data['group']['I']===true?'true':'false');
+ }
+ if (isset($data['group']['K'])) {
+ $out .= ' /K /'.($data['group']['K']===true?'true':'false');
+ }
+ }
+ $out .= ' >>';
+ }
+ $stream = $this->_getrawstream($stream, $data['n']);
+ $out .= ' /Length '.strlen($stream);
+ $out .= ' >>';
+ $out .= ' stream'."\n".$stream."\n".'endstream';
+ $out .= "\n".'endobj';
+ $this->_out($out);
+ }
+ }
+ }
+
+ /**
+ * Output Spot Colors Resources.
+ * @protected
+ * @since 4.0.024 (2008-09-12)
+ */
+ protected function _putspotcolors() {
+ foreach ($this->spot_colors as $name => $color) {
+ $this->_newobj();
+ $this->spot_colors[$name]['n'] = $this->n;
+ $out = '[/Separation /'.str_replace(' ', '#20', $name);
+ $out .= ' /DeviceCMYK <<';
+ $out .= ' /Range [0 1 0 1 0 1 0 1] /C0 [0 0 0 0]';
+ $out .= ' '.sprintf('/C1 [%F %F %F %F] ', ($color['C'] / 100), ($color['M'] / 100), ($color['Y'] / 100), ($color['K'] / 100));
+ $out .= ' /FunctionType 2 /Domain [0 1] /N 1>>]';
+ $out .= "\n".'endobj';
+ $this->_out($out);
+ }
+ }
+
+ /**
+ * Return XObjects Dictionary.
+ * @return string XObjects dictionary
+ * @protected
+ * @since 5.8.014 (2010-08-23)
+ */
+ protected function _getxobjectdict() {
+ $out = '';
+ foreach ($this->xobjects as $id => $objid) {
+ $out .= ' /'.$id.' '.$objid['n'].' 0 R';
+ }
+ return $out;
+ }
+
+ /**
+ * Output Resources Dictionary.
+ * @protected
+ */
+ protected function _putresourcedict() {
+ $out = $this->_getobj(2)."\n";
+ $out .= '<< /ProcSet [/PDF /Text /ImageB /ImageC /ImageI]';
+ $out .= ' /Font <<';
+ foreach ($this->fontkeys as $fontkey) {
+ $font = $this->getFontBuffer($fontkey);
+ $out .= ' /F'.$font['i'].' '.$font['n'].' 0 R';
+ }
+ $out .= ' >>';
+ $out .= ' /XObject <<';
+ $out .= $this->_getxobjectdict();
+ $out .= ' >>';
+ // layers
+ if (!empty($this->pdflayers)) {
+ $out .= ' /Properties <<';
+ foreach ($this->pdflayers as $layer) {
+ $out .= ' /'.$layer['layer'].' '.$layer['objid'].' 0 R';
+ }
+ $out .= ' >>';
+ }
+ if (!$this->pdfa_mode) {
+ // transparency
+ if (isset($this->extgstates) AND !empty($this->extgstates)) {
+ $out .= ' /ExtGState <<';
+ foreach ($this->extgstates as $k => $extgstate) {
+ if (isset($extgstate['name'])) {
+ $out .= ' /'.$extgstate['name'];
+ } else {
+ $out .= ' /GS'.$k;
+ }
+ $out .= ' '.$extgstate['n'].' 0 R';
+ }
+ $out .= ' >>';
+ }
+ if (isset($this->gradients) AND !empty($this->gradients)) {
+ $gp = '';
+ $gs = '';
+ foreach ($this->gradients as $id => $grad) {
+ // gradient patterns
+ $gp .= ' /p'.$id.' '.$grad['pattern'].' 0 R';
+ // gradient shadings
+ $gs .= ' /Sh'.$id.' '.$grad['id'].' 0 R';
+ }
+ $out .= ' /Pattern <<'.$gp.' >>';
+ $out .= ' /Shading <<'.$gs.' >>';
+ }
+ }
+ // spot colors
+ if (isset($this->spot_colors) AND !empty($this->spot_colors)) {
+ $out .= ' /ColorSpace <<';
+ foreach ($this->spot_colors as $color) {
+ $out .= ' /CS'.$color['i'].' '.$color['n'].' 0 R';
+ }
+ $out .= ' >>';
+ }
+ $out .= ' >>';
+ $out .= "\n".'endobj';
+ $this->_out($out);
+ }
+
+ /**
+ * Output Resources.
+ * @protected
+ */
+ protected function _putresources() {
+ $this->_putextgstates();
+ $this->_putocg();
+ $this->_putfonts();
+ $this->_putimages();
+ $this->_putspotcolors();
+ $this->_putshaders();
+ $this->_putxobjects();
+ $this->_putresourcedict();
+ $this->_putdests();
+ $this->_putEmbeddedFiles();
+ $this->_putannotsobjs();
+ $this->_putjavascript();
+ $this->_putbookmarks();
+ $this->_putencryption();
+ }
+
+ /**
+ * Adds some Metadata information (Document Information Dictionary)
+ * (see Chapter 14.3.3 Document Information Dictionary of PDF32000_2008.pdf Reference)
+ * @return int object id
+ * @protected
+ */
+ protected function _putinfo() {
+ $oid = $this->_newobj();
+ $out = '<<';
+ // store current isunicode value
+ $prev_isunicode = $this->isunicode;
+ if ($this->docinfounicode) {
+ $this->isunicode = true;
+ }
+ if (!TCPDF_STATIC::empty_string($this->title)) {
+ // The document's title.
+ $out .= ' /Title '.$this->_textstring($this->title, $oid);
+ }
+ if (!TCPDF_STATIC::empty_string($this->author)) {
+ // The name of the person who created the document.
+ $out .= ' /Author '.$this->_textstring($this->author, $oid);
+ }
+ if (!TCPDF_STATIC::empty_string($this->subject)) {
+ // The subject of the document.
+ $out .= ' /Subject '.$this->_textstring($this->subject, $oid);
+ }
+ if (!TCPDF_STATIC::empty_string($this->keywords)) {
+ // Keywords associated with the document.
+ $out .= ' /Keywords '.$this->_textstring($this->keywords.' TCPDF', $oid);
+ }
+ if (!TCPDF_STATIC::empty_string($this->creator)) {
+ // If the document was converted to PDF from another format, the name of the conforming product that created the original document from which it was converted.
+ $out .= ' /Creator '.$this->_textstring($this->creator, $oid);
+ }
+ // restore previous isunicode value
+ $this->isunicode = $prev_isunicode;
+ // default producer
+ $out .= ' /Producer '.$this->_textstring(TCPDF_STATIC::getTCPDFProducer(), $oid);
+ // The date and time the document was created, in human-readable form
+ $out .= ' /CreationDate '.$this->_datestring(0, $this->doc_creation_timestamp);
+ // The date and time the document was most recently modified, in human-readable form
+ $out .= ' /ModDate '.$this->_datestring(0, $this->doc_modification_timestamp);
+ // A name object indicating whether the document has been modified to include trapping information
+ $out .= ' /Trapped /False';
+ $out .= ' >>';
+ $out .= "\n".'endobj';
+ $this->_out($out);
+ return $oid;
+ }
+
+ /**
+ * Set additional XMP data to be added on the default XMP data just before the end of "x:xmpmeta" tag.
+ * IMPORTANT: This data is added as-is without controls, so you have to validate your data before using this method!
+ * @param $xmp (string) Custom XMP data.
+ * @since 5.9.128 (2011-10-06)
+ * @public
+ */
+ public function setExtraXMP($xmp) {
+ $this->custom_xmp = $xmp;
+ }
+
+ /**
+ * Put XMP data object and return ID.
+ * @return (int) The object ID.
+ * @since 5.9.121 (2011-09-28)
+ * @protected
+ */
+ protected function _putXMP() {
+ $oid = $this->_newobj();
+ // store current isunicode value
+ $prev_isunicode = $this->isunicode;
+ $this->isunicode = true;
+ $prev_encrypted = $this->encrypted;
+ $this->encrypted = false;
+ // set XMP data
+ $xmp = '<?xpacket begin="'.TCPDF_FONTS::unichr(0xfeff, $this->isunicode).'" id="W5M0MpCehiHzreSzNTczkc9d"?>'."\n";
+ $xmp .= '<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="Adobe XMP Core 4.2.1-c043 52.372728, 2009/01/18-15:08:04">'."\n";
+ $xmp .= "\t".'<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">'."\n";
+ $xmp .= "\t\t".'<rdf:Description rdf:about="" xmlns:dc="http://purl.org/dc/elements/1.1/">'."\n";
+ $xmp .= "\t\t\t".'<dc:format>application/pdf</dc:format>'."\n";
+ $xmp .= "\t\t\t".'<dc:title>'."\n";
+ $xmp .= "\t\t\t\t".'<rdf:Alt>'."\n";
+ $xmp .= "\t\t\t\t\t".'<rdf:li xml:lang="x-default">'.TCPDF_STATIC::_escapeXML($this->title).'</rdf:li>'."\n";
+ $xmp .= "\t\t\t\t".'</rdf:Alt>'."\n";
+ $xmp .= "\t\t\t".'</dc:title>'."\n";
+ $xmp .= "\t\t\t".'<dc:creator>'."\n";
+ $xmp .= "\t\t\t\t".'<rdf:Seq>'."\n";
+ $xmp .= "\t\t\t\t\t".'<rdf:li>'.TCPDF_STATIC::_escapeXML($this->author).'</rdf:li>'."\n";
+ $xmp .= "\t\t\t\t".'</rdf:Seq>'."\n";
+ $xmp .= "\t\t\t".'</dc:creator>'."\n";
+ $xmp .= "\t\t\t".'<dc:description>'."\n";
+ $xmp .= "\t\t\t\t".'<rdf:Alt>'."\n";
+ $xmp .= "\t\t\t\t\t".'<rdf:li xml:lang="x-default">'.TCPDF_STATIC::_escapeXML($this->subject).'</rdf:li>'."\n";
+ $xmp .= "\t\t\t\t".'</rdf:Alt>'."\n";
+ $xmp .= "\t\t\t".'</dc:description>'."\n";
+ $xmp .= "\t\t\t".'<dc:subject>'."\n";
+ $xmp .= "\t\t\t\t".'<rdf:Bag>'."\n";
+ $xmp .= "\t\t\t\t\t".'<rdf:li>'.TCPDF_STATIC::_escapeXML($this->keywords).' TCPDF</rdf:li>'."\n";
+ $xmp .= "\t\t\t\t".'</rdf:Bag>'."\n";
+ $xmp .= "\t\t\t".'</dc:subject>'."\n";
+ $xmp .= "\t\t".'</rdf:Description>'."\n";
+ // convert doc creation date format
+ $dcdate = TCPDF_STATIC::getFormattedDate($this->doc_creation_timestamp);
+ $doccreationdate = substr($dcdate, 0, 4).'-'.substr($dcdate, 4, 2).'-'.substr($dcdate, 6, 2);
+ $doccreationdate .= 'T'.substr($dcdate, 8, 2).':'.substr($dcdate, 10, 2).':'.substr($dcdate, 12, 2);
+ $doccreationdate .= '+'.substr($dcdate, 15, 2).':'.substr($dcdate, 18, 2);
+ $doccreationdate = TCPDF_STATIC::_escapeXML($doccreationdate);
+ // convert doc modification date format
+ $dmdate = TCPDF_STATIC::getFormattedDate($this->doc_modification_timestamp);
+ $docmoddate = substr($dmdate, 0, 4).'-'.substr($dmdate, 4, 2).'-'.substr($dmdate, 6, 2);
+ $docmoddate .= 'T'.substr($dmdate, 8, 2).':'.substr($dmdate, 10, 2).':'.substr($dmdate, 12, 2);
+ $docmoddate .= '+'.substr($dmdate, 15, 2).':'.substr($dmdate, 18, 2);
+ $docmoddate = TCPDF_STATIC::_escapeXML($docmoddate);
+ $xmp .= "\t\t".'<rdf:Description rdf:about="" xmlns:xmp="http://ns.adobe.com/xap/1.0/">'."\n";
+ $xmp .= "\t\t\t".'<xmp:CreateDate>'.$doccreationdate.'</xmp:CreateDate>'."\n";
+ $xmp .= "\t\t\t".'<xmp:CreatorTool>'.$this->creator.'</xmp:CreatorTool>'."\n";
+ $xmp .= "\t\t\t".'<xmp:ModifyDate>'.$docmoddate.'</xmp:ModifyDate>'."\n";
+ $xmp .= "\t\t\t".'<xmp:MetadataDate>'.$doccreationdate.'</xmp:MetadataDate>'."\n";
+ $xmp .= "\t\t".'</rdf:Description>'."\n";
+ $xmp .= "\t\t".'<rdf:Description rdf:about="" xmlns:pdf="http://ns.adobe.com/pdf/1.3/">'."\n";
+ $xmp .= "\t\t\t".'<pdf:Keywords>'.TCPDF_STATIC::_escapeXML($this->keywords).' TCPDF</pdf:Keywords>'."\n";
+ $xmp .= "\t\t\t".'<pdf:Producer>'.TCPDF_STATIC::_escapeXML(TCPDF_STATIC::getTCPDFProducer()).'</pdf:Producer>'."\n";
+ $xmp .= "\t\t".'</rdf:Description>'."\n";
+ $xmp .= "\t\t".'<rdf:Description rdf:about="" xmlns:xmpMM="http://ns.adobe.com/xap/1.0/mm/">'."\n";
+ $uuid = 'uuid:'.substr($this->file_id, 0, 8).'-'.substr($this->file_id, 8, 4).'-'.substr($this->file_id, 12, 4).'-'.substr($this->file_id, 16, 4).'-'.substr($this->file_id, 20, 12);
+ $xmp .= "\t\t\t".'<xmpMM:DocumentID>'.$uuid.'</xmpMM:DocumentID>'."\n";
+ $xmp .= "\t\t\t".'<xmpMM:InstanceID>'.$uuid.'</xmpMM:InstanceID>'."\n";
+ $xmp .= "\t\t".'</rdf:Description>'."\n";
+ if ($this->pdfa_mode) {
+ $xmp .= "\t\t".'<rdf:Description rdf:about="" xmlns:pdfaid="http://www.aiim.org/pdfa/ns/id/">'."\n";
+ $xmp .= "\t\t\t".'<pdfaid:part>1</pdfaid:part>'."\n";
+ $xmp .= "\t\t\t".'<pdfaid:conformance>B</pdfaid:conformance>'."\n";
+ $xmp .= "\t\t".'</rdf:Description>'."\n";
+ }
+ // XMP extension schemas
+ $xmp .= "\t\t".'<rdf:Description rdf:about="" xmlns:pdfaExtension="http://www.aiim.org/pdfa/ns/extension/" xmlns:pdfaSchema="http://www.aiim.org/pdfa/ns/schema#" xmlns:pdfaProperty="http://www.aiim.org/pdfa/ns/property#">'."\n";
+ $xmp .= "\t\t\t".'<pdfaExtension:schemas>'."\n";
+ $xmp .= "\t\t\t\t".'<rdf:Bag>'."\n";
+ $xmp .= "\t\t\t\t\t".'<rdf:li rdf:parseType="Resource">'."\n";
+ $xmp .= "\t\t\t\t\t\t".'<pdfaSchema:namespaceURI>http://ns.adobe.com/pdf/1.3/</pdfaSchema:namespaceURI>'."\n";
+ $xmp .= "\t\t\t\t\t\t".'<pdfaSchema:prefix>pdf</pdfaSchema:prefix>'."\n";
+ $xmp .= "\t\t\t\t\t\t".'<pdfaSchema:schema>Adobe PDF Schema</pdfaSchema:schema>'."\n";
+ $xmp .= "\t\t\t\t\t".'</rdf:li>'."\n";
+ $xmp .= "\t\t\t\t\t".'<rdf:li rdf:parseType="Resource">'."\n";
+ $xmp .= "\t\t\t\t\t\t".'<pdfaSchema:namespaceURI>http://ns.adobe.com/xap/1.0/mm/</pdfaSchema:namespaceURI>'."\n";
+ $xmp .= "\t\t\t\t\t\t".'<pdfaSchema:prefix>xmpMM</pdfaSchema:prefix>'."\n";
+ $xmp .= "\t\t\t\t\t\t".'<pdfaSchema:schema>XMP Media Management Schema</pdfaSchema:schema>'."\n";
+ $xmp .= "\t\t\t\t\t\t".'<pdfaSchema:property>'."\n";
+ $xmp .= "\t\t\t\t\t\t\t".'<rdf:Seq>'."\n";
+ $xmp .= "\t\t\t\t\t\t\t\t".'<rdf:li rdf:parseType="Resource">'."\n";
+ $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:category>internal</pdfaProperty:category>'."\n";
+ $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:description>UUID based identifier for specific incarnation of a document</pdfaProperty:description>'."\n";
+ $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:name>InstanceID</pdfaProperty:name>'."\n";
+ $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:valueType>URI</pdfaProperty:valueType>'."\n";
+ $xmp .= "\t\t\t\t\t\t\t\t".'</rdf:li>'."\n";
+ $xmp .= "\t\t\t\t\t\t\t".'</rdf:Seq>'."\n";
+ $xmp .= "\t\t\t\t\t\t".'</pdfaSchema:property>'."\n";
+ $xmp .= "\t\t\t\t\t".'</rdf:li>'."\n";
+ $xmp .= "\t\t\t\t\t".'<rdf:li rdf:parseType="Resource">'."\n";
+ $xmp .= "\t\t\t\t\t\t".'<pdfaSchema:namespaceURI>http://www.aiim.org/pdfa/ns/id/</pdfaSchema:namespaceURI>'."\n";
+ $xmp .= "\t\t\t\t\t\t".'<pdfaSchema:prefix>pdfaid</pdfaSchema:prefix>'."\n";
+ $xmp .= "\t\t\t\t\t\t".'<pdfaSchema:schema>PDF/A ID Schema</pdfaSchema:schema>'."\n";
+ $xmp .= "\t\t\t\t\t\t".'<pdfaSchema:property>'."\n";
+ $xmp .= "\t\t\t\t\t\t\t".'<rdf:Seq>'."\n";
+ $xmp .= "\t\t\t\t\t\t\t\t".'<rdf:li rdf:parseType="Resource">'."\n";
+ $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:category>internal</pdfaProperty:category>'."\n";
+ $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:description>Part of PDF/A standard</pdfaProperty:description>'."\n";
+ $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:name>part</pdfaProperty:name>'."\n";
+ $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:valueType>Integer</pdfaProperty:valueType>'."\n";
+ $xmp .= "\t\t\t\t\t\t\t\t".'</rdf:li>'."\n";
+ $xmp .= "\t\t\t\t\t\t\t\t".'<rdf:li rdf:parseType="Resource">'."\n";
+ $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:category>internal</pdfaProperty:category>'."\n";
+ $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:description>Amendment of PDF/A standard</pdfaProperty:description>'."\n";
+ $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:name>amd</pdfaProperty:name>'."\n";
+ $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:valueType>Text</pdfaProperty:valueType>'."\n";
+ $xmp .= "\t\t\t\t\t\t\t\t".'</rdf:li>'."\n";
+ $xmp .= "\t\t\t\t\t\t\t\t".'<rdf:li rdf:parseType="Resource">'."\n";
+ $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:category>internal</pdfaProperty:category>'."\n";
+ $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:description>Conformance level of PDF/A standard</pdfaProperty:description>'."\n";
+ $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:name>conformance</pdfaProperty:name>'."\n";
+ $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:valueType>Text</pdfaProperty:valueType>'."\n";
+ $xmp .= "\t\t\t\t\t\t\t\t".'</rdf:li>'."\n";
+ $xmp .= "\t\t\t\t\t\t\t".'</rdf:Seq>'."\n";
+ $xmp .= "\t\t\t\t\t\t".'</pdfaSchema:property>'."\n";
+ $xmp .= "\t\t\t\t\t".'</rdf:li>'."\n";
+ $xmp .= "\t\t\t\t".'</rdf:Bag>'."\n";
+ $xmp .= "\t\t\t".'</pdfaExtension:schemas>'."\n";
+ $xmp .= "\t\t".'</rdf:Description>'."\n";
+ $xmp .= "\t".'</rdf:RDF>'."\n";
+ $xmp .= $this->custom_xmp;
+ $xmp .= '</x:xmpmeta>'."\n";
+ $xmp .= '<?xpacket end="w"?>';
+ $out = '<< /Type /Metadata /Subtype /XML /Length '.strlen($xmp).' >> stream'."\n".$xmp."\n".'endstream'."\n".'endobj';
+ // restore previous isunicode value
+ $this->isunicode = $prev_isunicode;
+ $this->encrypted = $prev_encrypted;
+ $this->_out($out);
+ return $oid;
+ }
+
+ /**
+ * Output Catalog.
+ * @return int object id
+ * @protected
+ */
+ protected function _putcatalog() {
+ // put XMP
+ $xmpobj = $this->_putXMP();
+ // if required, add standard sRGB_IEC61966-2.1 blackscaled ICC colour profile
+ if ($this->pdfa_mode OR $this->force_srgb) {
+ $iccobj = $this->_newobj();
+ $icc = file_get_contents(dirname(__FILE__).'/include/sRGB.icc');
+ $filter = '';
+ if ($this->compress) {
+ $filter = ' /Filter /FlateDecode';
+ $icc = gzcompress($icc);
+ }
+ $icc = $this->_getrawstream($icc);
+ $this->_out('<</N 3 '.$filter.'/Length '.strlen($icc).'>> stream'."\n".$icc."\n".'endstream'."\n".'endobj');
+ }
+ // start catalog
+ $oid = $this->_newobj();
+ $out = '<< /Type /Catalog';
+ $out .= ' /Version /'.$this->PDFVersion;
+ //$out .= ' /Extensions <<>>';
+ $out .= ' /Pages 1 0 R';
+ //$out .= ' /PageLabels ' //...;
+ $out .= ' /Names <<';
+ if ((!$this->pdfa_mode) AND !empty($this->n_js)) {
+ $out .= ' /JavaScript '.$this->n_js;
+ }
+ if (!empty($this->efnames)) {
+ $out .= ' /EmbeddedFiles <</Names [';
+ foreach ($this->efnames AS $fn => $fref) {
+ $out .= ' '.$this->_datastring($fn).' '.$fref;
+ }
+ $out .= ' ]>>';
+ }
+ $out .= ' >>';
+ if (!empty($this->dests)) {
+ $out .= ' /Dests '.($this->n_dests).' 0 R';
+ }
+ $out .= $this->_putviewerpreferences();
+ if (isset($this->LayoutMode) AND (!TCPDF_STATIC::empty_string($this->LayoutMode))) {
+ $out .= ' /PageLayout /'.$this->LayoutMode;
+ }
+ if (isset($this->PageMode) AND (!TCPDF_STATIC::empty_string($this->PageMode))) {
+ $out .= ' /PageMode /'.$this->PageMode;
+ }
+ if (count($this->outlines) > 0) {
+ $out .= ' /Outlines '.$this->OutlineRoot.' 0 R';
+ $out .= ' /PageMode /UseOutlines';
+ }
+ //$out .= ' /Threads []';
+ if ($this->ZoomMode == 'fullpage') {
+ $out .= ' /OpenAction ['.$this->page_obj_id[1].' 0 R /Fit]';
+ } elseif ($this->ZoomMode == 'fullwidth') {
+ $out .= ' /OpenAction ['.$this->page_obj_id[1].' 0 R /FitH null]';
+ } elseif ($this->ZoomMode == 'real') {
+ $out .= ' /OpenAction ['.$this->page_obj_id[1].' 0 R /XYZ null null 1]';
+ } elseif (!is_string($this->ZoomMode)) {
+ $out .= sprintf(' /OpenAction ['.$this->page_obj_id[1].' 0 R /XYZ null null %F]', ($this->ZoomMode / 100));
+ }
+ //$out .= ' /AA <<>>';
+ //$out .= ' /URI <<>>';
+ $out .= ' /Metadata '.$xmpobj.' 0 R';
+ //$out .= ' /StructTreeRoot <<>>';
+ //$out .= ' /MarkInfo <<>>';
+ if (isset($this->l['a_meta_language'])) {
+ $out .= ' /Lang '.$this->_textstring($this->l['a_meta_language'], $oid);
+ }
+ //$out .= ' /SpiderInfo <<>>';
+ // set OutputIntent to sRGB IEC61966-2.1 if required
+ if ($this->pdfa_mode OR $this->force_srgb) {
+ $out .= ' /OutputIntents [<<';
+ $out .= ' /Type /OutputIntent';
+ $out .= ' /S /GTS_PDFA1';
+ $out .= ' /OutputCondition '.$this->_textstring('sRGB IEC61966-2.1', $oid);
+ $out .= ' /OutputConditionIdentifier '.$this->_textstring('sRGB IEC61966-2.1', $oid);
+ $out .= ' /RegistryName '.$this->_textstring('http://www.color.org', $oid);
+ $out .= ' /Info '.$this->_textstring('sRGB IEC61966-2.1', $oid);
+ $out .= ' /DestOutputProfile '.$iccobj.' 0 R';
+ $out .= ' >>]';
+ }
+ //$out .= ' /PieceInfo <<>>';
+ if (!empty($this->pdflayers)) {
+ $lyrobjs = '';
+ $lyrobjs_print = '';
+ $lyrobjs_view = '';
+ foreach ($this->pdflayers as $layer) {
+ $lyrobjs .= ' '.$layer['objid'].' 0 R';
+ if ($layer['print']) {
+ $lyrobjs_print .= ' '.$layer['objid'].' 0 R';
+ }
+ if ($layer['view']) {
+ $lyrobjs_view .= ' '.$layer['objid'].' 0 R';
+ }
+ }
+ $out .= ' /OCProperties << /OCGs ['.$lyrobjs.']';
+ $out .= ' /D <<';
+ $out .= ' /Name '.$this->_textstring('Layers', $oid);
+ $out .= ' /Creator '.$this->_textstring('TCPDF', $oid);
+ $out .= ' /BaseState /ON';
+ $out .= ' /ON ['.$lyrobjs_print.']';
+ $out .= ' /OFF ['.$lyrobjs_view.']';
+ $out .= ' /Intent /View';
+ $out .= ' /AS [';
+ $out .= ' << /Event /Print /OCGs ['.$lyrobjs.'] /Category [/Print] >>';
+ $out .= ' << /Event /View /OCGs ['.$lyrobjs.'] /Category [/View] >>';
+ $out .= ' ]';
+ $out .= ' /Order ['.$lyrobjs.']';
+ $out .= ' /ListMode /AllPages';
+ //$out .= ' /RBGroups ['..']';
+ //$out .= ' /Locked ['..']';
+ $out .= ' >>';
+ $out .= ' >>';
+ }
+ // AcroForm
+ if (!empty($this->form_obj_id)
+ OR ($this->sign AND isset($this->signature_data['cert_type']))
+ OR !empty($this->empty_signature_appearance)) {
+ $out .= ' /AcroForm <<';
+ $objrefs = '';
+ if ($this->sign AND isset($this->signature_data['cert_type'])) {
+ // set reference for signature object
+ $objrefs .= $this->sig_obj_id.' 0 R';
+ }
+ if (!empty($this->empty_signature_appearance)) {
+ foreach ($this->empty_signature_appearance as $esa) {
+ // set reference for empty signature objects
+ $objrefs .= ' '.$esa['objid'].' 0 R';
+ }
+ }
+ if (!empty($this->form_obj_id)) {
+ foreach($this->form_obj_id as $objid) {
+ $objrefs .= ' '.$objid.' 0 R';
+ }
+ }
+ $out .= ' /Fields ['.$objrefs.']';
+ // It's better to turn off this value and set the appearance stream for each annotation (/AP) to avoid conflicts with signature fields.
+ $out .= ' /NeedAppearances false';
+ if ($this->sign AND isset($this->signature_data['cert_type'])) {
+ if ($this->signature_data['cert_type'] > 0) {
+ $out .= ' /SigFlags 3';
+ } else {
+ $out .= ' /SigFlags 1';
+ }
+ }
+ //$out .= ' /CO ';
+ if (isset($this->annotation_fonts) AND !empty($this->annotation_fonts)) {
+ $out .= ' /DR <<';
+ $out .= ' /Font <<';
+ foreach ($this->annotation_fonts as $fontkey => $fontid) {
+ $out .= ' /F'.$fontid.' '.$this->font_obj_ids[$fontkey].' 0 R';
+ }
+ $out .= ' >> >>';
+ }
+ $font = $this->getFontBuffer('helvetica');
+ $out .= ' /DA (/F'.$font['i'].' 0 Tf 0 g)';
+ $out .= ' /Q '.(($this->rtl)?'2':'0');
+ //$out .= ' /XFA ';
+ $out .= ' >>';
+ // signatures
+ if ($this->sign AND isset($this->signature_data['cert_type'])) {
+ if ($this->signature_data['cert_type'] > 0) {
+ $out .= ' /Perms << /DocMDP '.($this->sig_obj_id + 1).' 0 R >>';
+ } else {
+ $out .= ' /Perms << /UR3 '.($this->sig_obj_id + 1).' 0 R >>';
+ }
+ }
+ }
+ //$out .= ' /Legal <<>>';
+ //$out .= ' /Requirements []';
+ //$out .= ' /Collection <<>>';
+ //$out .= ' /NeedsRendering true';
+ $out .= ' >>';
+ $out .= "\n".'endobj';
+ $this->_out($out);
+ return $oid;
+ }
+
+ /**
+ * Output viewer preferences.
+ * @return string for viewer preferences
+ * @author Nicola asuni
+ * @since 3.1.000 (2008-06-09)
+ * @protected
+ */
+ protected function _putviewerpreferences() {
+ $vp = $this->viewer_preferences;
+ $out = ' /ViewerPreferences <<';
+ if ($this->rtl) {
+ $out .= ' /Direction /R2L';
+ } else {
+ $out .= ' /Direction /L2R';
+ }
+ if (isset($vp['HideToolbar']) AND ($vp['HideToolbar'])) {
+ $out .= ' /HideToolbar true';
+ }
+ if (isset($vp['HideMenubar']) AND ($vp['HideMenubar'])) {
+ $out .= ' /HideMenubar true';
+ }
+ if (isset($vp['HideWindowUI']) AND ($vp['HideWindowUI'])) {
+ $out .= ' /HideWindowUI true';
+ }
+ if (isset($vp['FitWindow']) AND ($vp['FitWindow'])) {
+ $out .= ' /FitWindow true';
+ }
+ if (isset($vp['CenterWindow']) AND ($vp['CenterWindow'])) {
+ $out .= ' /CenterWindow true';
+ }
+ if (isset($vp['DisplayDocTitle']) AND ($vp['DisplayDocTitle'])) {
+ $out .= ' /DisplayDocTitle true';
+ }
+ if (isset($vp['NonFullScreenPageMode'])) {
+ $out .= ' /NonFullScreenPageMode /'.$vp['NonFullScreenPageMode'];
+ }
+ if (isset($vp['ViewArea'])) {
+ $out .= ' /ViewArea /'.$vp['ViewArea'];
+ }
+ if (isset($vp['ViewClip'])) {
+ $out .= ' /ViewClip /'.$vp['ViewClip'];
+ }
+ if (isset($vp['PrintArea'])) {
+ $out .= ' /PrintArea /'.$vp['PrintArea'];
+ }
+ if (isset($vp['PrintClip'])) {
+ $out .= ' /PrintClip /'.$vp['PrintClip'];
+ }
+ if (isset($vp['PrintScaling'])) {
+ $out .= ' /PrintScaling /'.$vp['PrintScaling'];
+ }
+ if (isset($vp['Duplex']) AND (!TCPDF_STATIC::empty_string($vp['Duplex']))) {
+ $out .= ' /Duplex /'.$vp['Duplex'];
+ }
+ if (isset($vp['PickTrayByPDFSize'])) {
+ if ($vp['PickTrayByPDFSize']) {
+ $out .= ' /PickTrayByPDFSize true';
+ } else {
+ $out .= ' /PickTrayByPDFSize false';
+ }
+ }
+ if (isset($vp['PrintPageRange'])) {
+ $PrintPageRangeNum = '';
+ foreach ($vp['PrintPageRange'] as $k => $v) {
+ $PrintPageRangeNum .= ' '.($v - 1).'';
+ }
+ $out .= ' /PrintPageRange ['.substr($PrintPageRangeNum,1).']';
+ }
+ if (isset($vp['NumCopies'])) {
+ $out .= ' /NumCopies '.intval($vp['NumCopies']);
+ }
+ $out .= ' >>';
+ return $out;
+ }
+
+ /**
+ * Output PDF File Header (7.5.2).
+ * @protected
+ */
+ protected function _putheader() {
+ $this->_out('%PDF-'.$this->PDFVersion);
+ $this->_out('%'.chr(0xe2).chr(0xe3).chr(0xcf).chr(0xd3));
+ }
+
+ /**
+ * Output end of document (EOF).
+ * @protected
+ */
+ protected function _enddoc() {
+ if (isset($this->CurrentFont['fontkey']) AND isset($this->CurrentFont['subsetchars'])) {
+ // save subset chars of the previous font
+ $this->setFontSubBuffer($this->CurrentFont['fontkey'], 'subsetchars', $this->CurrentFont['subsetchars']);
+ }
+ $this->state = 1;
+ $this->_putheader();
+ $this->_putpages();
+ $this->_putresources();
+ // empty signature fields
+ if (!empty($this->empty_signature_appearance)) {
+ foreach ($this->empty_signature_appearance as $key => $esa) {
+ // widget annotation for empty signature
+ $out = $this->_getobj($esa['objid'])."\n";
+ $out .= '<< /Type /Annot';
+ $out .= ' /Subtype /Widget';
+ $out .= ' /Rect ['.$esa['rect'].']';
+ $out .= ' /P '.$this->page_obj_id[($esa['page'])].' 0 R'; // link to signature appearance page
+ $out .= ' /F 4';
+ $out .= ' /FT /Sig';
+ $signame = $esa['name'].sprintf(' [%03d]', ($key + 1));
+ $out .= ' /T '.$this->_textstring($signame, $esa['objid']);
+ $out .= ' /Ff 0';
+ $out .= ' >>';
+ $out .= "\n".'endobj';
+ $this->_out($out);
+ }
+ }
+ // Signature
+ if ($this->sign AND isset($this->signature_data['cert_type'])) {
+ // widget annotation for signature
+ $out = $this->_getobj($this->sig_obj_id)."\n";
+ $out .= '<< /Type /Annot';
+ $out .= ' /Subtype /Widget';
+ $out .= ' /Rect ['.$this->signature_appearance['rect'].']';
+ $out .= ' /P '.$this->page_obj_id[($this->signature_appearance['page'])].' 0 R'; // link to signature appearance page
+ $out .= ' /F 4';
+ $out .= ' /FT /Sig';
+ $out .= ' /T '.$this->_textstring($this->signature_appearance['name'], $this->sig_obj_id);
+ $out .= ' /Ff 0';
+ $out .= ' /V '.($this->sig_obj_id + 1).' 0 R';
+ $out .= ' >>';
+ $out .= "\n".'endobj';
+ $this->_out($out);
+ // signature
+ $this->_putsignature();
+ }
+ // Info
+ $objid_info = $this->_putinfo();
+ // Catalog
+ $objid_catalog = $this->_putcatalog();
+ // Cross-ref
+ $o = $this->bufferlen;
+ // XREF section
+ $this->_out('xref');
+ $this->_out('0 '.($this->n + 1));
+ $this->_out('0000000000 65535 f ');
+ $freegen = ($this->n + 2);
+ for ($i=1; $i <= $this->n; ++$i) {
+ if (!isset($this->offsets[$i]) AND ($i > 1)) {
+ $this->_out(sprintf('0000000000 %05d f ', $freegen));
+ ++$freegen;
+ } else {
+ $this->_out(sprintf('%010d 00000 n ', $this->offsets[$i]));
+ }
+ }
+ // TRAILER
+ $out = 'trailer'."\n";
+ $out .= '<<';
+ $out .= ' /Size '.($this->n + 1);
+ $out .= ' /Root '.$objid_catalog.' 0 R';
+ $out .= ' /Info '.$objid_info.' 0 R';
+ if ($this->encrypted) {
+ $out .= ' /Encrypt '.$this->encryptdata['objid'].' 0 R';
+ }
+ $out .= ' /ID [ <'.$this->file_id.'> <'.$this->file_id.'> ]';
+ $out .= ' >>';
+ $this->_out($out);
+ $this->_out('startxref');
+ $this->_out($o);
+ $this->_out('%%EOF');
+ $this->state = 3; // end-of-doc
+ if ($this->diskcache) {
+ // remove temporary files used for images
+ foreach ($this->imagekeys as $key) {
+ // remove temporary files
+ unlink($this->images[$key]);
+ }
+ foreach ($this->fontkeys as $key) {
+ // remove temporary files
+ unlink($this->fonts[$key]);
+ }
+ }
+ }
+
+ /**
+ * Initialize a new page.
+ * @param $orientation (string) page orientation. Possible values are (case insensitive):<ul><li>P or PORTRAIT (default)</li><li>L or LANDSCAPE</li></ul>
+ * @param $format (mixed) The format used for pages. It can be either: one of the string values specified at getPageSizeFromFormat() or an array of parameters specified at setPageFormat().
+ * @protected
+ * @see getPageSizeFromFormat(), setPageFormat()
+ */
+ protected function _beginpage($orientation='', $format='') {
+ ++$this->page;
+ $this->pageobjects[$this->page] = array();
+ $this->setPageBuffer($this->page, '');
+ // initialize array for graphics tranformation positions inside a page buffer
+ $this->transfmrk[$this->page] = array();
+ $this->state = 2;
+ if (TCPDF_STATIC::empty_string($orientation)) {
+ if (isset($this->CurOrientation)) {
+ $orientation = $this->CurOrientation;
+ } elseif ($this->fwPt > $this->fhPt) {
+ // landscape
+ $orientation = 'L';
+ } else {
+ // portrait
+ $orientation = 'P';
+ }
+ }
+ if (TCPDF_STATIC::empty_string($format)) {
+ $this->pagedim[$this->page] = $this->pagedim[($this->page - 1)];
+ $this->setPageOrientation($orientation);
+ } else {
+ $this->setPageFormat($format, $orientation);
+ }
+ if ($this->rtl) {
+ $this->x = $this->w - $this->rMargin;
+ } else {
+ $this->x = $this->lMargin;
+ }
+ $this->y = $this->tMargin;
+ if (isset($this->newpagegroup[$this->page])) {
+ // start a new group
+ $this->currpagegroup = $this->newpagegroup[$this->page];
+ $this->pagegroups[$this->currpagegroup] = 1;
+ } elseif (isset($this->currpagegroup) AND ($this->currpagegroup > 0)) {
+ ++$this->pagegroups[$this->currpagegroup];
+ }
+ }
+
+ /**
+ * Mark end of page.
+ * @protected
+ */
+ protected function _endpage() {
+ $this->setVisibility('all');
+ $this->state = 1;
+ }
+
+ /**
+ * Begin a new object and return the object number.
+ * @return int object number
+ * @protected
+ */
+ protected function _newobj() {
+ $this->_out($this->_getobj());
+ return $this->n;
+ }
+
+ /**
+ * Return the starting object string for the selected object ID.
+ * @param $objid (int) Object ID (leave empty to get a new ID).
+ * @return string the starting object string
+ * @protected
+ * @since 5.8.009 (2010-08-20)
+ */
+ protected function _getobj($objid='') {
+ if ($objid === '') {
+ ++$this->n;
+ $objid = $this->n;
+ }
+ $this->offsets[$objid] = $this->bufferlen;
+ $this->pageobjects[$this->page][] = $objid;
+ return $objid.' 0 obj';
+ }
+
+ /**
+ * Underline text.
+ * @param $x (int) X coordinate
+ * @param $y (int) Y coordinate
+ * @param $txt (string) text to underline
+ * @protected
+ */
+ protected function _dounderline($x, $y, $txt) {
+ $w = $this->GetStringWidth($txt);
+ return $this->_dounderlinew($x, $y, $w);
+ }
+
+ /**
+ * Underline for rectangular text area.
+ * @param $x (int) X coordinate
+ * @param $y (int) Y coordinate
+ * @param $w (int) width to underline
+ * @protected
+ * @since 4.8.008 (2009-09-29)
+ */
+ protected function _dounderlinew($x, $y, $w) {
+ $linew = - $this->CurrentFont['ut'] / 1000 * $this->FontSizePt;
+ return sprintf('%F %F %F %F re f', $x * $this->k, ((($this->h - $y) * $this->k) + $linew), $w * $this->k, $linew);
+ }
+
+ /**
+ * Line through text.
+ * @param $x (int) X coordinate
+ * @param $y (int) Y coordinate
+ * @param $txt (string) text to linethrough
+ * @protected
+ */
+ protected function _dolinethrough($x, $y, $txt) {
+ $w = $this->GetStringWidth($txt);
+ return $this->_dolinethroughw($x, $y, $w);
+ }
+
+ /**
+ * Line through for rectangular text area.
+ * @param $x (int) X coordinate
+ * @param $y (int) Y coordinate
+ * @param $w (int) line length (width)
+ * @protected
+ * @since 4.9.008 (2009-09-29)
+ */
+ protected function _dolinethroughw($x, $y, $w) {
+ $linew = - $this->CurrentFont['ut'] / 1000 * $this->FontSizePt;
+ return sprintf('%F %F %F %F re f', $x * $this->k, ((($this->h - $y) * $this->k) + $linew + ($this->FontSizePt / 3)), $w * $this->k, $linew);
+ }
+
+ /**
+ * Overline text.
+ * @param $x (int) X coordinate
+ * @param $y (int) Y coordinate
+ * @param $txt (string) text to overline
+ * @protected
+ * @since 4.9.015 (2010-04-19)
+ */
+ protected function _dooverline($x, $y, $txt) {
+ $w = $this->GetStringWidth($txt);
+ return $this->_dooverlinew($x, $y, $w);
+ }
+
+ /**
+ * Overline for rectangular text area.
+ * @param $x (int) X coordinate
+ * @param $y (int) Y coordinate
+ * @param $w (int) width to overline
+ * @protected
+ * @since 4.9.015 (2010-04-19)
+ */
+ protected function _dooverlinew($x, $y, $w) {
+ $linew = - $this->CurrentFont['ut'] / 1000 * $this->FontSizePt;
+ return sprintf('%F %F %F %F re f', $x * $this->k, (($this->h - $y + $this->FontAscent) * $this->k) - $linew, $w * $this->k, $linew);
+
+ }
+
+ /**
+ * Format a data string for meta information
+ * @param $s (string) data string to escape.
+ * @param $n (int) object ID
+ * @return string escaped string.
+ * @protected
+ */
+ protected function _datastring($s, $n=0) {
+ if ($n == 0) {
+ $n = $this->n;
+ }
+ $s = $this->_encrypt_data($n, $s);
+ return '('. TCPDF_STATIC::_escape($s).')';
+ }
+
+ /**
+ * Set the document creation timestamp
+ * @param $time (mixed) Document creation timestamp in seconds or date-time string.
+ * @public
+ * @since 5.9.152 (2012-03-23)
+ */
+ public function setDocCreationTimestamp($time) {
+ if (is_string($time)) {
+ $time = TCPDF_STATIC::getTimestamp($time);
+ }
+ $this->doc_creation_timestamp = intval($time);
+ }
+
+ /**
+ * Set the document modification timestamp
+ * @param $time (mixed) Document modification timestamp in seconds or date-time string.
+ * @public
+ * @since 5.9.152 (2012-03-23)
+ */
+ public function setDocModificationTimestamp($time) {
+ if (is_string($time)) {
+ $time = TCPDF_STATIC::getTimestamp($time);
+ }
+ $this->doc_modification_timestamp = intval($time);
+ }
+
+ /**
+ * Returns document creation timestamp in seconds.
+ * @return (int) Creation timestamp in seconds.
+ * @public
+ * @since 5.9.152 (2012-03-23)
+ */
+ public function getDocCreationTimestamp() {
+ return $this->doc_creation_timestamp;
+ }
+
+ /**
+ * Returns document modification timestamp in seconds.
+ * @return (int) Modfication timestamp in seconds.
+ * @public
+ * @since 5.9.152 (2012-03-23)
+ */
+ public function getDocModificationTimestamp() {
+ return $this->doc_modification_timestamp;
+ }
+
+ /**
+ * Returns a formatted date for meta information
+ * @param $n (int) Object ID.
+ * @param $timestamp (int) Timestamp to convert.
+ * @return string escaped date string.
+ * @protected
+ * @since 4.6.028 (2009-08-25)
+ */
+ protected function _datestring($n=0, $timestamp=0) {
+ if ((empty($timestamp)) OR ($timestamp < 0)) {
+ $timestamp = $this->doc_creation_timestamp;
+ }
+ return $this->_datastring('D:'.TCPDF_STATIC::getFormattedDate($timestamp), $n);
+ }
+
+ /**
+ * Format a text string for meta information
+ * @param $s (string) string to escape.
+ * @param $n (int) object ID
+ * @return string escaped string.
+ * @protected
+ */
+ protected function _textstring($s, $n=0) {
+ if ($this->isunicode) {
+ //Convert string to UTF-16BE
+ $s = TCPDF_FONTS::UTF8ToUTF16BE($s, true, $this->isunicode, $this->CurrentFont);
+ }
+ return $this->_datastring($s, $n);
+ }
+
+ /**
+ * THIS METHOD IS DEPRECATED
+ * Format a text string
+ * @param $s (string) string to escape.
+ * @return string escaped string.
+ * @protected
+ * @deprecated
+ */
+ protected function _escapetext($s) {
+ if ($this->isunicode) {
+ if (($this->CurrentFont['type'] == 'core') OR ($this->CurrentFont['type'] == 'TrueType') OR ($this->CurrentFont['type'] == 'Type1')) {
+ $s = TCPDF_FONTS::UTF8ToLatin1($s, $this->isunicode, $this->CurrentFont);
+ } else {
+ //Convert string to UTF-16BE and reverse RTL language
+ $s = TCPDF_FONTS::utf8StrRev($s, false, $this->tmprtl, $this->isunicode, $this->CurrentFont);
+ }
+ }
+ return TCPDF_STATIC::_escape($s);
+ }
+
+ /**
+ * get raw output stream.
+ * @param $s (string) string to output.
+ * @param $n (int) object reference for encryption mode
+ * @protected
+ * @author Nicola Asuni
+ * @since 5.5.000 (2010-06-22)
+ */
+ protected function _getrawstream($s, $n=0) {
+ if ($n <= 0) {
+ // default to current object
+ $n = $this->n;
+ }
+ return $this->_encrypt_data($n, $s);
+ }
+
+ /**
+ * Format output stream (DEPRECATED).
+ * @param $s (string) string to output.
+ * @param $n (int) object reference for encryption mode
+ * @protected
+ * @deprecated
+ */
+ protected function _getstream($s, $n=0) {
+ return 'stream'."\n".$this->_getrawstream($s, $n)."\n".'endstream';
+ }
+
+ /**
+ * Output a stream (DEPRECATED).
+ * @param $s (string) string to output.
+ * @param $n (int) object reference for encryption mode
+ * @protected
+ * @deprecated
+ */
+ protected function _putstream($s, $n=0) {
+ $this->_out($this->_getstream($s, $n));
+ }
+
+ /**
+ * Output a string to the document.
+ * @param $s (string) string to output.
+ * @protected
+ */
+ protected function _out($s) {
+ if ($this->state == 2) {
+ if ($this->inxobj) {
+ // we are inside an XObject template
+ $this->xobjects[$this->xobjid]['outdata'] .= $s."\n";
+ } elseif ((!$this->InFooter) AND isset($this->footerlen[$this->page]) AND ($this->footerlen[$this->page] > 0)) {
+ // puts data before page footer
+ $pagebuff = $this->getPageBuffer($this->page);
+ $page = substr($pagebuff, 0, -$this->footerlen[$this->page]);
+ $footer = substr($pagebuff, -$this->footerlen[$this->page]);
+ $this->setPageBuffer($this->page, $page.$s."\n".$footer);
+ // update footer position
+ $this->footerpos[$this->page] += strlen($s."\n");
+ } else {
+ // set page data
+ $this->setPageBuffer($this->page, $s."\n", true);
+ }
+ } elseif ($this->state > 0) {
+ // set general data
+ $this->setBuffer($s."\n");
+ }
+ }
+
+ /**
+ * Set header font.
+ * @param $font (array) font
+ * @public
+ * @since 1.1
+ */
+ public function setHeaderFont($font) {
+ $this->header_font = $font;
+ }
+
+ /**
+ * Get header font.
+ * @return array()
+ * @public
+ * @since 4.0.012 (2008-07-24)
+ */
+ public function getHeaderFont() {
+ return $this->header_font;
+ }
+
+ /**
+ * Set footer font.
+ * @param $font (array) font
+ * @public
+ * @since 1.1
+ */
+ public function setFooterFont($font) {
+ $this->footer_font = $font;
+ }
+
+ /**
+ * Get Footer font.
+ * @return array()
+ * @public
+ * @since 4.0.012 (2008-07-24)
+ */
+ public function getFooterFont() {
+ return $this->footer_font;
+ }
+
+ /**
+ * Set language array.
+ * @param $language (array)
+ * @public
+ * @since 1.1
+ */
+ public function setLanguageArray($language) {
+ $this->l = $language;
+ if (isset($this->l['a_meta_dir'])) {
+ $this->rtl = $this->l['a_meta_dir']=='rtl' ? true : false;
+ } else {
+ $this->rtl = false;
+ }
+ }
+
+ /**
+ * Returns the PDF data.
+ * @public
+ */
+ public function getPDFData() {
+ if ($this->state < 3) {
+ $this->Close();
+ }
+ return $this->buffer;
+ }
+
+ /**
+ * Output anchor link.
+ * @param $url (string) link URL or internal link (i.e.: &lt;a href="#23,4.5"&gt;link to page 23 at 4.5 Y position&lt;/a&gt;)
+ * @param $name (string) link name
+ * @param $fill (boolean) Indicates if the cell background must be painted (true) or transparent (false).
+ * @param $firstline (boolean) if true prints only the first line and return the remaining string.
+ * @param $color (array) array of RGB text color
+ * @param $style (string) font style (U, D, B, I)
+ * @param $firstblock (boolean) if true the string is the starting of a line.
+ * @return the number of cells used or the remaining text if $firstline = true;
+ * @public
+ */
+ public function addHtmlLink($url, $name, $fill=false, $firstline=false, $color='', $style=-1, $firstblock=false) {
+ if (isset($url[1]) AND ($url[0] == '#') AND is_numeric($url[1])) {
+ // convert url to internal link
+ $lnkdata = explode(',', $url);
+ if (isset($lnkdata[0])) {
+ $page = intval(substr($lnkdata[0], 1));
+ if (empty($page) OR ($page <= 0)) {
+ $page = $this->page;
+ }
+ if (isset($lnkdata[1]) AND (strlen($lnkdata[1]) > 0)) {
+ $lnky = floatval($lnkdata[1]);
+ } else {
+ $lnky = 0;
+ }
+ $url = $this->AddLink();
+ $this->SetLink($url, $lnky, $page);
+ }
+ }
+ // store current settings
+ $prevcolor = $this->fgcolor;
+ $prevstyle = $this->FontStyle;
+ if (empty($color)) {
+ $this->SetTextColorArray($this->htmlLinkColorArray);
+ } else {
+ $this->SetTextColorArray($color);
+ }
+ if ($style == -1) {
+ $this->SetFont('', $this->FontStyle.$this->htmlLinkFontStyle);
+ } else {
+ $this->SetFont('', $this->FontStyle.$style);
+ }
+ $ret = $this->Write($this->lasth, $name, $url, $fill, '', false, 0, $firstline, $firstblock, 0);
+ // restore settings
+ $this->SetFont('', $prevstyle);
+ $this->SetTextColorArray($prevcolor);
+ return $ret;
+ }
+
+ /**
+ * Converts pixels to User's Units.
+ * @param $px (int) pixels
+ * @return float value in user's unit
+ * @public
+ * @see setImageScale(), getImageScale()
+ */
+ public function pixelsToUnits($px) {
+ return ($px / ($this->imgscale * $this->k));
+ }
+
+ /**
+ * Reverse function for htmlentities.
+ * Convert entities in UTF-8.
+ * @param $text_to_convert (string) Text to convert.
+ * @return string converted text string
+ * @public
+ */
+ public function unhtmlentities($text_to_convert) {
+ return @html_entity_decode($text_to_convert, ENT_QUOTES, $this->encoding);
+ }
+
+ // ENCRYPTION METHODS ----------------------------------
+
+ /**
+ * Compute encryption key depending on object number where the encrypted data is stored.
+ * This is used for all strings and streams without crypt filter specifier.
+ * @param $n (int) object number
+ * @return int object key
+ * @protected
+ * @author Nicola Asuni
+ * @since 2.0.000 (2008-01-02)
+ */
+ protected function _objectkey($n) {
+ $objkey = $this->encryptdata['key'].pack('VXxx', $n);
+ if ($this->encryptdata['mode'] == 2) { // AES-128
+ // AES padding
+ $objkey .= "\x73\x41\x6C\x54"; // sAlT
+ }
+ $objkey = substr(TCPDF_STATIC::_md5_16($objkey), 0, (($this->encryptdata['Length'] / 8) + 5));
+ $objkey = substr($objkey, 0, 16);
+ return $objkey;
+ }
+
+ /**
+ * Encrypt the input string.
+ * @param $n (int) object number
+ * @param $s (string) data string to encrypt
+ * @return encrypted string
+ * @protected
+ * @author Nicola Asuni
+ * @since 5.0.005 (2010-05-11)
+ */
+ protected function _encrypt_data($n, $s) {
+ if (!$this->encrypted) {
+ return $s;
+ }
+ switch ($this->encryptdata['mode']) {
+ case 0: // RC4-40
+ case 1: { // RC4-128
+ $s = TCPDF_STATIC::_RC4($this->_objectkey($n), $s, $this->last_enc_key, $this->last_enc_key_c);
+ break;
+ }
+ case 2: { // AES-128
+ $s = TCPDF_STATIC::_AES($this->_objectkey($n), $s);
+ break;
+ }
+ case 3: { // AES-256
+ $s = TCPDF_STATIC::_AES($this->encryptdata['key'], $s);
+ break;
+ }
+ }
+ return $s;
+ }
+
+ /**
+ * Put encryption on PDF document.
+ * @protected
+ * @author Nicola Asuni
+ * @since 2.0.000 (2008-01-02)
+ */
+ protected function _putencryption() {
+ if (!$this->encrypted) {
+ return;
+ }
+ $this->encryptdata['objid'] = $this->_newobj();
+ $out = '<<';
+ if (!isset($this->encryptdata['Filter']) OR empty($this->encryptdata['Filter'])) {
+ $this->encryptdata['Filter'] = 'Standard';
+ }
+ $out .= ' /Filter /'.$this->encryptdata['Filter'];
+ if (isset($this->encryptdata['SubFilter']) AND !empty($this->encryptdata['SubFilter'])) {
+ $out .= ' /SubFilter /'.$this->encryptdata['SubFilter'];
+ }
+ if (!isset($this->encryptdata['V']) OR empty($this->encryptdata['V'])) {
+ $this->encryptdata['V'] = 1;
+ }
+ // V is a code specifying the algorithm to be used in encrypting and decrypting the document
+ $out .= ' /V '.$this->encryptdata['V'];
+ if (isset($this->encryptdata['Length']) AND !empty($this->encryptdata['Length'])) {
+ // The length of the encryption key, in bits. The value shall be a multiple of 8, in the range 40 to 256
+ $out .= ' /Length '.$this->encryptdata['Length'];
+ } else {
+ $out .= ' /Length 40';
+ }
+ if ($this->encryptdata['V'] >= 4) {
+ if (!isset($this->encryptdata['StmF']) OR empty($this->encryptdata['StmF'])) {
+ $this->encryptdata['StmF'] = 'Identity';
+ }
+ if (!isset($this->encryptdata['StrF']) OR empty($this->encryptdata['StrF'])) {
+ // The name of the crypt filter that shall be used when decrypting all strings in the document.
+ $this->encryptdata['StrF'] = 'Identity';
+ }
+ // A dictionary whose keys shall be crypt filter names and whose values shall be the corresponding crypt filter dictionaries.
+ if (isset($this->encryptdata['CF']) AND !empty($this->encryptdata['CF'])) {
+ $out .= ' /CF <<';
+ $out .= ' /'.$this->encryptdata['StmF'].' <<';
+ $out .= ' /Type /CryptFilter';
+ if (isset($this->encryptdata['CF']['CFM']) AND !empty($this->encryptdata['CF']['CFM'])) {
+ // The method used
+ $out .= ' /CFM /'.$this->encryptdata['CF']['CFM'];
+ if ($this->encryptdata['pubkey']) {
+ $out .= ' /Recipients [';
+ foreach ($this->encryptdata['Recipients'] as $rec) {
+ $out .= ' <'.$rec.'>';
+ }
+ $out .= ' ]';
+ if (isset($this->encryptdata['CF']['EncryptMetadata']) AND (!$this->encryptdata['CF']['EncryptMetadata'])) {
+ $out .= ' /EncryptMetadata false';
+ } else {
+ $out .= ' /EncryptMetadata true';
+ }
+ }
+ } else {
+ $out .= ' /CFM /None';
+ }
+ if (isset($this->encryptdata['CF']['AuthEvent']) AND !empty($this->encryptdata['CF']['AuthEvent'])) {
+ // The event to be used to trigger the authorization that is required to access encryption keys used by this filter.
+ $out .= ' /AuthEvent /'.$this->encryptdata['CF']['AuthEvent'];
+ } else {
+ $out .= ' /AuthEvent /DocOpen';
+ }
+ if (isset($this->encryptdata['CF']['Length']) AND !empty($this->encryptdata['CF']['Length'])) {
+ // The bit length of the encryption key.
+ $out .= ' /Length '.$this->encryptdata['CF']['Length'];
+ }
+ $out .= ' >> >>';
+ }
+ // The name of the crypt filter that shall be used by default when decrypting streams.
+ $out .= ' /StmF /'.$this->encryptdata['StmF'];
+ // The name of the crypt filter that shall be used when decrypting all strings in the document.
+ $out .= ' /StrF /'.$this->encryptdata['StrF'];
+ if (isset($this->encryptdata['EFF']) AND !empty($this->encryptdata['EFF'])) {
+ // The name of the crypt filter that shall be used when encrypting embedded file streams that do not have their own crypt filter specifier.
+ $out .= ' /EFF /'.$this->encryptdata[''];
+ }
+ }
+ // Additional encryption dictionary entries for the standard security handler
+ if ($this->encryptdata['pubkey']) {
+ if (($this->encryptdata['V'] < 4) AND isset($this->encryptdata['Recipients']) AND !empty($this->encryptdata['Recipients'])) {
+ $out .= ' /Recipients [';
+ foreach ($this->encryptdata['Recipients'] as $rec) {
+ $out .= ' <'.$rec.'>';
+ }
+ $out .= ' ]';
+ }
+ } else {
+ $out .= ' /R';
+ if ($this->encryptdata['V'] == 5) { // AES-256
+ $out .= ' 5';
+ $out .= ' /OE ('.TCPDF_STATIC::_escape($this->encryptdata['OE']).')';
+ $out .= ' /UE ('.TCPDF_STATIC::_escape($this->encryptdata['UE']).')';
+ $out .= ' /Perms ('.TCPDF_STATIC::_escape($this->encryptdata['perms']).')';
+ } elseif ($this->encryptdata['V'] == 4) { // AES-128
+ $out .= ' 4';
+ } elseif ($this->encryptdata['V'] < 2) { // RC-40
+ $out .= ' 2';
+ } else { // RC-128
+ $out .= ' 3';
+ }
+ $out .= ' /O ('.TCPDF_STATIC::_escape($this->encryptdata['O']).')';
+ $out .= ' /U ('.TCPDF_STATIC::_escape($this->encryptdata['U']).')';
+ $out .= ' /P '.$this->encryptdata['P'];
+ if (isset($this->encryptdata['EncryptMetadata']) AND (!$this->encryptdata['EncryptMetadata'])) {
+ $out .= ' /EncryptMetadata false';
+ } else {
+ $out .= ' /EncryptMetadata true';
+ }
+ }
+ $out .= ' >>';
+ $out .= "\n".'endobj';
+ $this->_out($out);
+ }
+
+ /**
+ * Compute U value (used for encryption)
+ * @return string U value
+ * @protected
+ * @since 2.0.000 (2008-01-02)
+ * @author Nicola Asuni
+ */
+ protected function _Uvalue() {
+ if ($this->encryptdata['mode'] == 0) { // RC4-40
+ return TCPDF_STATIC::_RC4($this->encryptdata['key'], TCPDF_STATIC::$enc_padding, $this->last_enc_key, $this->last_enc_key_c);
+ } elseif ($this->encryptdata['mode'] < 3) { // RC4-128, AES-128
+ $tmp = TCPDF_STATIC::_md5_16(TCPDF_STATIC::$enc_padding.$this->encryptdata['fileid']);
+ $enc = TCPDF_STATIC::_RC4($this->encryptdata['key'], $tmp, $this->last_enc_key, $this->last_enc_key_c);
+ $len = strlen($tmp);
+ for ($i = 1; $i <= 19; ++$i) {
+ $ek = '';
+ for ($j = 0; $j < $len; ++$j) {
+ $ek .= chr(ord($this->encryptdata['key'][$j]) ^ $i);
+ }
+ $enc = TCPDF_STATIC::_RC4($ek, $enc, $this->last_enc_key, $this->last_enc_key_c);
+ }
+ $enc .= str_repeat("\x00", 16);
+ return substr($enc, 0, 32);
+ } elseif ($this->encryptdata['mode'] == 3) { // AES-256
+ $seed = TCPDF_STATIC::_md5_16(TCPDF_STATIC::getRandomSeed());
+ // User Validation Salt
+ $this->encryptdata['UVS'] = substr($seed, 0, 8);
+ // User Key Salt
+ $this->encryptdata['UKS'] = substr($seed, 8, 16);
+ return hash('sha256', $this->encryptdata['user_password'].$this->encryptdata['UVS'], true).$this->encryptdata['UVS'].$this->encryptdata['UKS'];
+ }
+ }
+
+ /**
+ * Compute UE value (used for encryption)
+ * @return string UE value
+ * @protected
+ * @since 5.9.006 (2010-10-19)
+ * @author Nicola Asuni
+ */
+ protected function _UEvalue() {
+ $hashkey = hash('sha256', $this->encryptdata['user_password'].$this->encryptdata['UKS'], true);
+ $iv = str_repeat("\x00", mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC));
+ return mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $hashkey, $this->encryptdata['key'], MCRYPT_MODE_CBC, $iv);
+ }
+
+ /**
+ * Compute O value (used for encryption)
+ * @return string O value
+ * @protected
+ * @since 2.0.000 (2008-01-02)
+ * @author Nicola Asuni
+ */
+ protected function _Ovalue() {
+ if ($this->encryptdata['mode'] < 3) { // RC4-40, RC4-128, AES-128
+ $tmp = TCPDF_STATIC::_md5_16($this->encryptdata['owner_password']);
+ if ($this->encryptdata['mode'] > 0) {
+ for ($i = 0; $i < 50; ++$i) {
+ $tmp = TCPDF_STATIC::_md5_16($tmp);
+ }
+ }
+ $owner_key = substr($tmp, 0, ($this->encryptdata['Length'] / 8));
+ $enc = TCPDF_STATIC::_RC4($owner_key, $this->encryptdata['user_password'], $this->last_enc_key, $this->last_enc_key_c);
+ if ($this->encryptdata['mode'] > 0) {
+ $len = strlen($owner_key);
+ for ($i = 1; $i <= 19; ++$i) {
+ $ek = '';
+ for ($j = 0; $j < $len; ++$j) {
+ $ek .= chr(ord($owner_key[$j]) ^ $i);
+ }
+ $enc = TCPDF_STATIC::_RC4($ek, $enc, $this->last_enc_key, $this->last_enc_key_c);
+ }
+ }
+ return $enc;
+ } elseif ($this->encryptdata['mode'] == 3) { // AES-256
+ $seed = TCPDF_STATIC::_md5_16(TCPDF_STATIC::getRandomSeed());
+ // Owner Validation Salt
+ $this->encryptdata['OVS'] = substr($seed, 0, 8);
+ // Owner Key Salt
+ $this->encryptdata['OKS'] = substr($seed, 8, 16);
+ return hash('sha256', $this->encryptdata['owner_password'].$this->encryptdata['OVS'].$this->encryptdata['U'], true).$this->encryptdata['OVS'].$this->encryptdata['OKS'];
+ }
+ }
+
+ /**
+ * Compute OE value (used for encryption)
+ * @return string OE value
+ * @protected
+ * @since 5.9.006 (2010-10-19)
+ * @author Nicola Asuni
+ */
+ protected function _OEvalue() {
+ $hashkey = hash('sha256', $this->encryptdata['owner_password'].$this->encryptdata['OKS'].$this->encryptdata['U'], true);
+ $iv = str_repeat("\x00", mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC));
+ return mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $hashkey, $this->encryptdata['key'], MCRYPT_MODE_CBC, $iv);
+ }
+
+ /**
+ * Convert password for AES-256 encryption mode
+ * @param $password (string) password
+ * @return string password
+ * @protected
+ * @since 5.9.006 (2010-10-19)
+ * @author Nicola Asuni
+ */
+ protected function _fixAES256Password($password) {
+ $psw = ''; // password to be returned
+ $psw_array = TCPDF_FONTS::utf8Bidi(TCPDF_FONTS::UTF8StringToArray($password, $this->isunicode, $this->CurrentFont), $password, $this->rtl, $this->isunicode, $this->CurrentFont);
+ foreach ($psw_array as $c) {
+ $psw .= TCPDF_FONTS::unichr($c, $this->isunicode);
+ }
+ return substr($psw, 0, 127);
+ }
+
+ /**
+ * Compute encryption key
+ * @protected
+ * @since 2.0.000 (2008-01-02)
+ * @author Nicola Asuni
+ */
+ protected function _generateencryptionkey() {
+ $keybytelen = ($this->encryptdata['Length'] / 8);
+ if (!$this->encryptdata['pubkey']) { // standard mode
+ if ($this->encryptdata['mode'] == 3) { // AES-256
+ // generate 256 bit random key
+ $this->encryptdata['key'] = substr(hash('sha256', TCPDF_STATIC::getRandomSeed(), true), 0, $keybytelen);
+ // truncate passwords
+ $this->encryptdata['user_password'] = $this->_fixAES256Password($this->encryptdata['user_password']);
+ $this->encryptdata['owner_password'] = $this->_fixAES256Password($this->encryptdata['owner_password']);
+ // Compute U value
+ $this->encryptdata['U'] = $this->_Uvalue();
+ // Compute UE value
+ $this->encryptdata['UE'] = $this->_UEvalue();
+ // Compute O value
+ $this->encryptdata['O'] = $this->_Ovalue();
+ // Compute OE value
+ $this->encryptdata['OE'] = $this->_OEvalue();
+ // Compute P value
+ $this->encryptdata['P'] = $this->encryptdata['protection'];
+ // Computing the encryption dictionary's Perms (permissions) value
+ $perms = TCPDF_STATIC::getEncPermissionsString($this->encryptdata['protection']); // bytes 0-3
+ $perms .= chr(255).chr(255).chr(255).chr(255); // bytes 4-7
+ if (isset($this->encryptdata['CF']['EncryptMetadata']) AND (!$this->encryptdata['CF']['EncryptMetadata'])) { // byte 8
+ $perms .= 'F';
+ } else {
+ $perms .= 'T';
+ }
+ $perms .= 'adb'; // bytes 9-11
+ $perms .= 'nick'; // bytes 12-15
+ $iv = str_repeat("\x00", mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_ECB));
+ $this->encryptdata['perms'] = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $this->encryptdata['key'], $perms, MCRYPT_MODE_ECB, $iv);
+ } else { // RC4-40, RC4-128, AES-128
+ // Pad passwords
+ $this->encryptdata['user_password'] = substr($this->encryptdata['user_password'].TCPDF_STATIC::$enc_padding, 0, 32);
+ $this->encryptdata['owner_password'] = substr($this->encryptdata['owner_password'].TCPDF_STATIC::$enc_padding, 0, 32);
+ // Compute O value
+ $this->encryptdata['O'] = $this->_Ovalue();
+ // get default permissions (reverse byte order)
+ $permissions = TCPDF_STATIC::getEncPermissionsString($this->encryptdata['protection']);
+ // Compute encryption key
+ $tmp = TCPDF_STATIC::_md5_16($this->encryptdata['user_password'].$this->encryptdata['O'].$permissions.$this->encryptdata['fileid']);
+ if ($this->encryptdata['mode'] > 0) {
+ for ($i = 0; $i < 50; ++$i) {
+ $tmp = TCPDF_STATIC::_md5_16(substr($tmp, 0, $keybytelen));
+ }
+ }
+ $this->encryptdata['key'] = substr($tmp, 0, $keybytelen);
+ // Compute U value
+ $this->encryptdata['U'] = $this->_Uvalue();
+ // Compute P value
+ $this->encryptdata['P'] = $this->encryptdata['protection'];
+ }
+ } else { // Public-Key mode
+ // random 20-byte seed
+ $seed = sha1(TCPDF_STATIC::getRandomSeed(), true);
+ $recipient_bytes = '';
+ foreach ($this->encryptdata['pubkeys'] as $pubkey) {
+ // for each public certificate
+ if (isset($pubkey['p'])) {
+ $pkprotection = TCPDF_STATIC::getUserPermissionCode($pubkey['p'], $this->encryptdata['mode']);
+ } else {
+ $pkprotection = $this->encryptdata['protection'];
+ }
+ // get default permissions (reverse byte order)
+ $pkpermissions = TCPDF_STATIC::getEncPermissionsString($pkprotection);
+ // envelope data
+ $envelope = $seed.$pkpermissions;
+ // write the envelope data to a temporary file
+ $tempkeyfile = TCPDF_STATIC::getObjFilename('tmpkey');
+ $f = fopen($tempkeyfile, 'wb');
+ if (!$f) {
+ $this->Error('Unable to create temporary key file: '.$tempkeyfile);
+ }
+ $envelope_length = strlen($envelope);
+ fwrite($f, $envelope, $envelope_length);
+ fclose($f);
+ $tempencfile = TCPDF_STATIC::getObjFilename('tmpenc');
+ if (!openssl_pkcs7_encrypt($tempkeyfile, $tempencfile, $pubkey['c'], array(), PKCS7_BINARY | PKCS7_DETACHED)) {
+ $this->Error('Unable to encrypt the file: '.$tempkeyfile);
+ }
+ unlink($tempkeyfile);
+ // read encryption signature
+ $signature = file_get_contents($tempencfile, false, null, $envelope_length);
+ unlink($tempencfile);
+ // extract signature
+ $signature = substr($signature, strpos($signature, 'Content-Disposition'));
+ $tmparr = explode("\n\n", $signature);
+ $signature = trim($tmparr[1]);
+ unset($tmparr);
+ // decode signature
+ $signature = base64_decode($signature);
+ // convert signature to hex
+ $hexsignature = current(unpack('H*', $signature));
+ // store signature on recipients array
+ $this->encryptdata['Recipients'][] = $hexsignature;
+ // The bytes of each item in the Recipients array of PKCS#7 objects in the order in which they appear in the array
+ $recipient_bytes .= $signature;
+ }
+ // calculate encryption key
+ if ($this->encryptdata['mode'] == 3) { // AES-256
+ $this->encryptdata['key'] = substr(hash('sha256', $seed.$recipient_bytes, true), 0, $keybytelen);
+ } else { // RC4-40, RC4-128, AES-128
+ $this->encryptdata['key'] = substr(sha1($seed.$recipient_bytes, true), 0, $keybytelen);
+ }
+ }
+ }
+
+ /**
+ * Set document protection
+ * Remark: the protection against modification is for people who have the full Acrobat product.
+ * If you don't set any password, the document will open as usual. If you set a user password, the PDF viewer will ask for it before displaying the document. The master password, if different from the user one, can be used to get full access.
+ * Note: protecting a document requires to encrypt it, which increases the processing time a lot. This can cause a PHP time-out in some cases, especially if the document contains images or fonts.
+ * @param $permissions (Array) the set of permissions (specify the ones you want to block):<ul><li>print : Print the document;</li><li>modify : Modify the contents of the document by operations other than those controlled by 'fill-forms', 'extract' and 'assemble';</li><li>copy : Copy or otherwise extract text and graphics from the document;</li><li>annot-forms : Add or modify text annotations, fill in interactive form fields, and, if 'modify' is also set, create or modify interactive form fields (including signature fields);</li><li>fill-forms : Fill in existing interactive form fields (including signature fields), even if 'annot-forms' is not specified;</li><li>extract : Extract text and graphics (in support of accessibility to users with disabilities or for other purposes);</li><li>assemble : Assemble the document (insert, rotate, or delete pages and create bookmarks or thumbnail images), even if 'modify' is not set;</li><li>print-high : Print the document to a representation from which a faithful digital copy of the PDF content could be generated. When this is not set, printing is limited to a low-level representation of the appearance, possibly of degraded quality.</li><li>owner : (inverted logic - only for public-key) when set permits change of encryption and enables all other permissions.</li></ul>
+ * @param $user_pass (String) user password. Empty by default.
+ * @param $owner_pass (String) owner password. If not specified, a random value is used.
+ * @param $mode (int) encryption strength: 0 = RC4 40 bit; 1 = RC4 128 bit; 2 = AES 128 bit; 3 = AES 256 bit.
+ * @param $pubkeys (String) array of recipients containing public-key certificates ('c') and permissions ('p'). For example: array(array('c' => 'file://../examples/data/cert/tcpdf.crt', 'p' => array('print')))
+ * @public
+ * @since 2.0.000 (2008-01-02)
+ * @author Nicola Asuni
+ */
+ public function SetProtection($permissions=array('print', 'modify', 'copy', 'annot-forms', 'fill-forms', 'extract', 'assemble', 'print-high'), $user_pass='', $owner_pass=null, $mode=0, $pubkeys=null) {
+ if ($this->pdfa_mode) {
+ // encryption is not allowed in PDF/A mode
+ return;
+ }
+ $this->encryptdata['protection'] = TCPDF_STATIC::getUserPermissionCode($permissions, $mode);
+ if (($pubkeys !== null) AND (is_array($pubkeys))) {
+ // public-key mode
+ $this->encryptdata['pubkeys'] = $pubkeys;
+ if ($mode == 0) {
+ // public-Key Security requires at least 128 bit
+ $mode = 1;
+ }
+ if (!function_exists('openssl_pkcs7_encrypt')) {
+ $this->Error('Public-Key Security requires openssl library.');
+ }
+ // Set Public-Key filter (availabe are: Entrust.PPKEF, Adobe.PPKLite, Adobe.PubSec)
+ $this->encryptdata['pubkey'] = true;
+ $this->encryptdata['Filter'] = 'Adobe.PubSec';
+ $this->encryptdata['StmF'] = 'DefaultCryptFilter';
+ $this->encryptdata['StrF'] = 'DefaultCryptFilter';
+ } else {
+ // standard mode (password mode)
+ $this->encryptdata['pubkey'] = false;
+ $this->encryptdata['Filter'] = 'Standard';
+ $this->encryptdata['StmF'] = 'StdCF';
+ $this->encryptdata['StrF'] = 'StdCF';
+ }
+ if ($mode > 1) { // AES
+ if (!extension_loaded('mcrypt')) {
+ $this->Error('AES encryption requires mcrypt library (http://www.php.net/manual/en/mcrypt.requirements.php).');
+ }
+ if (mcrypt_get_cipher_name(MCRYPT_RIJNDAEL_128) === false) {
+ $this->Error('AES encryption requires MCRYPT_RIJNDAEL_128 cypher.');
+ }
+ if (($mode == 3) AND !function_exists('hash')) {
+ // the Hash extension requires no external libraries and is enabled by default as of PHP 5.1.2.
+ $this->Error('AES 256 encryption requires HASH Message Digest Framework (http://www.php.net/manual/en/book.hash.php).');
+ }
+ }
+ if ($owner_pass === null) {
+ $owner_pass = md5(TCPDF_STATIC::getRandomSeed());
+ }
+ $this->encryptdata['user_password'] = $user_pass;
+ $this->encryptdata['owner_password'] = $owner_pass;
+ $this->encryptdata['mode'] = $mode;
+ switch ($mode) {
+ case 0: { // RC4 40 bit
+ $this->encryptdata['V'] = 1;
+ $this->encryptdata['Length'] = 40;
+ $this->encryptdata['CF']['CFM'] = 'V2';
+ break;
+ }
+ case 1: { // RC4 128 bit
+ $this->encryptdata['V'] = 2;
+ $this->encryptdata['Length'] = 128;
+ $this->encryptdata['CF']['CFM'] = 'V2';
+ if ($this->encryptdata['pubkey']) {
+ $this->encryptdata['SubFilter'] = 'adbe.pkcs7.s4';
+ $this->encryptdata['Recipients'] = array();
+ }
+ break;
+ }
+ case 2: { // AES 128 bit
+ $this->encryptdata['V'] = 4;
+ $this->encryptdata['Length'] = 128;
+ $this->encryptdata['CF']['CFM'] = 'AESV2';
+ $this->encryptdata['CF']['Length'] = 128;
+ if ($this->encryptdata['pubkey']) {
+ $this->encryptdata['SubFilter'] = 'adbe.pkcs7.s5';
+ $this->encryptdata['Recipients'] = array();
+ }
+ break;
+ }
+ case 3: { // AES 256 bit
+ $this->encryptdata['V'] = 5;
+ $this->encryptdata['Length'] = 256;
+ $this->encryptdata['CF']['CFM'] = 'AESV3';
+ $this->encryptdata['CF']['Length'] = 256;
+ if ($this->encryptdata['pubkey']) {
+ $this->encryptdata['SubFilter'] = 'adbe.pkcs7.s5';
+ $this->encryptdata['Recipients'] = array();
+ }
+ break;
+ }
+ }
+ $this->encrypted = true;
+ $this->encryptdata['fileid'] = TCPDF_STATIC::convertHexStringToString($this->file_id);
+ $this->_generateencryptionkey();
+ }
+
+ // END OF ENCRYPTION FUNCTIONS -------------------------
+
+ // START TRANSFORMATIONS SECTION -----------------------
+
+ /**
+ * Starts a 2D tranformation saving current graphic state.
+ * This function must be called before scaling, mirroring, translation, rotation and skewing.
+ * Use StartTransform() before, and StopTransform() after the transformations to restore the normal behavior.
+ * @public
+ * @since 2.1.000 (2008-01-07)
+ * @see StartTransform(), StopTransform()
+ */
+ public function StartTransform() {
+ if ($this->state != 2) {
+ return;
+ }
+ $this->_out('q');
+ if ($this->inxobj) {
+ // we are inside an XObject template
+ $this->xobjects[$this->xobjid]['transfmrk'][] = strlen($this->xobjects[$this->xobjid]['outdata']);
+ } else {
+ $this->transfmrk[$this->page][] = $this->pagelen[$this->page];
+ }
+ ++$this->transfmatrix_key;
+ $this->transfmatrix[$this->transfmatrix_key] = array();
+ }
+
+ /**
+ * Stops a 2D tranformation restoring previous graphic state.
+ * This function must be called after scaling, mirroring, translation, rotation and skewing.
+ * Use StartTransform() before, and StopTransform() after the transformations to restore the normal behavior.
+ * @public
+ * @since 2.1.000 (2008-01-07)
+ * @see StartTransform(), StopTransform()
+ */
+ public function StopTransform() {
+ if ($this->state != 2) {
+ return;
+ }
+ $this->_out('Q');
+ if (isset($this->transfmatrix[$this->transfmatrix_key])) {
+ array_pop($this->transfmatrix[$this->transfmatrix_key]);
+ --$this->transfmatrix_key;
+ }
+ if ($this->inxobj) {
+ // we are inside an XObject template
+ array_pop($this->xobjects[$this->xobjid]['transfmrk']);
+ } else {
+ array_pop($this->transfmrk[$this->page]);
+ }
+ }
+ /**
+ * Horizontal Scaling.
+ * @param $s_x (float) scaling factor for width as percent. 0 is not allowed.
+ * @param $x (int) abscissa of the scaling center. Default is current x position
+ * @param $y (int) ordinate of the scaling center. Default is current y position
+ * @public
+ * @since 2.1.000 (2008-01-07)
+ * @see StartTransform(), StopTransform()
+ */
+ public function ScaleX($s_x, $x='', $y='') {
+ $this->Scale($s_x, 100, $x, $y);
+ }
+
+ /**
+ * Vertical Scaling.
+ * @param $s_y (float) scaling factor for height as percent. 0 is not allowed.
+ * @param $x (int) abscissa of the scaling center. Default is current x position
+ * @param $y (int) ordinate of the scaling center. Default is current y position
+ * @public
+ * @since 2.1.000 (2008-01-07)
+ * @see StartTransform(), StopTransform()
+ */
+ public function ScaleY($s_y, $x='', $y='') {
+ $this->Scale(100, $s_y, $x, $y);
+ }
+
+ /**
+ * Vertical and horizontal proportional Scaling.
+ * @param $s (float) scaling factor for width and height as percent. 0 is not allowed.
+ * @param $x (int) abscissa of the scaling center. Default is current x position
+ * @param $y (int) ordinate of the scaling center. Default is current y position
+ * @public
+ * @since 2.1.000 (2008-01-07)
+ * @see StartTransform(), StopTransform()
+ */
+ public function ScaleXY($s, $x='', $y='') {
+ $this->Scale($s, $s, $x, $y);
+ }
+
+ /**
+ * Vertical and horizontal non-proportional Scaling.
+ * @param $s_x (float) scaling factor for width as percent. 0 is not allowed.
+ * @param $s_y (float) scaling factor for height as percent. 0 is not allowed.
+ * @param $x (int) abscissa of the scaling center. Default is current x position
+ * @param $y (int) ordinate of the scaling center. Default is current y position
+ * @public
+ * @since 2.1.000 (2008-01-07)
+ * @see StartTransform(), StopTransform()
+ */
+ public function Scale($s_x, $s_y, $x='', $y='') {
+ if ($x === '') {
+ $x = $this->x;
+ }
+ if ($y === '') {
+ $y = $this->y;
+ }
+ if (($s_x == 0) OR ($s_y == 0)) {
+ $this->Error('Please do not use values equal to zero for scaling');
+ }
+ $y = ($this->h - $y) * $this->k;
+ $x *= $this->k;
+ //calculate elements of transformation matrix
+ $s_x /= 100;
+ $s_y /= 100;
+ $tm = array();
+ $tm[0] = $s_x;
+ $tm[1] = 0;
+ $tm[2] = 0;
+ $tm[3] = $s_y;
+ $tm[4] = $x * (1 - $s_x);
+ $tm[5] = $y * (1 - $s_y);
+ //scale the coordinate system
+ $this->Transform($tm);
+ }
+
+ /**
+ * Horizontal Mirroring.
+ * @param $x (int) abscissa of the point. Default is current x position
+ * @public
+ * @since 2.1.000 (2008-01-07)
+ * @see StartTransform(), StopTransform()
+ */
+ public function MirrorH($x='') {
+ $this->Scale(-100, 100, $x);
+ }
+
+ /**
+ * Verical Mirroring.
+ * @param $y (int) ordinate of the point. Default is current y position
+ * @public
+ * @since 2.1.000 (2008-01-07)
+ * @see StartTransform(), StopTransform()
+ */
+ public function MirrorV($y='') {
+ $this->Scale(100, -100, '', $y);
+ }
+
+ /**
+ * Point reflection mirroring.
+ * @param $x (int) abscissa of the point. Default is current x position
+ * @param $y (int) ordinate of the point. Default is current y position
+ * @public
+ * @since 2.1.000 (2008-01-07)
+ * @see StartTransform(), StopTransform()
+ */
+ public function MirrorP($x='',$y='') {
+ $this->Scale(-100, -100, $x, $y);
+ }
+
+ /**
+ * Reflection against a straight line through point (x, y) with the gradient angle (angle).
+ * @param $angle (float) gradient angle of the straight line. Default is 0 (horizontal line).
+ * @param $x (int) abscissa of the point. Default is current x position
+ * @param $y (int) ordinate of the point. Default is current y position
+ * @public
+ * @since 2.1.000 (2008-01-07)
+ * @see StartTransform(), StopTransform()
+ */
+ public function MirrorL($angle=0, $x='',$y='') {
+ $this->Scale(-100, 100, $x, $y);
+ $this->Rotate(-2*($angle-90), $x, $y);
+ }
+
+ /**
+ * Translate graphic object horizontally.
+ * @param $t_x (int) movement to the right (or left for RTL)
+ * @public
+ * @since 2.1.000 (2008-01-07)
+ * @see StartTransform(), StopTransform()
+ */
+ public function TranslateX($t_x) {
+ $this->Translate($t_x, 0);
+ }
+
+ /**
+ * Translate graphic object vertically.
+ * @param $t_y (int) movement to the bottom
+ * @public
+ * @since 2.1.000 (2008-01-07)
+ * @see StartTransform(), StopTransform()
+ */
+ public function TranslateY($t_y) {
+ $this->Translate(0, $t_y);
+ }
+
+ /**
+ * Translate graphic object horizontally and vertically.
+ * @param $t_x (int) movement to the right
+ * @param $t_y (int) movement to the bottom
+ * @public
+ * @since 2.1.000 (2008-01-07)
+ * @see StartTransform(), StopTransform()
+ */
+ public function Translate($t_x, $t_y) {
+ //calculate elements of transformation matrix
+ $tm = array();
+ $tm[0] = 1;
+ $tm[1] = 0;
+ $tm[2] = 0;
+ $tm[3] = 1;
+ $tm[4] = $t_x * $this->k;
+ $tm[5] = -$t_y * $this->k;
+ //translate the coordinate system
+ $this->Transform($tm);
+ }
+
+ /**
+ * Rotate object.
+ * @param $angle (float) angle in degrees for counter-clockwise rotation
+ * @param $x (int) abscissa of the rotation center. Default is current x position
+ * @param $y (int) ordinate of the rotation center. Default is current y position
+ * @public
+ * @since 2.1.000 (2008-01-07)
+ * @see StartTransform(), StopTransform()
+ */
+ public function Rotate($angle, $x='', $y='') {
+ if ($x === '') {
+ $x = $this->x;
+ }
+ if ($y === '') {
+ $y = $this->y;
+ }
+ $y = ($this->h - $y) * $this->k;
+ $x *= $this->k;
+ //calculate elements of transformation matrix
+ $tm = array();
+ $tm[0] = cos(deg2rad($angle));
+ $tm[1] = sin(deg2rad($angle));
+ $tm[2] = -$tm[1];
+ $tm[3] = $tm[0];
+ $tm[4] = $x + ($tm[1] * $y) - ($tm[0] * $x);
+ $tm[5] = $y - ($tm[0] * $y) - ($tm[1] * $x);
+ //rotate the coordinate system around ($x,$y)
+ $this->Transform($tm);
+ }
+
+ /**
+ * Skew horizontally.
+ * @param $angle_x (float) angle in degrees between -90 (skew to the left) and 90 (skew to the right)
+ * @param $x (int) abscissa of the skewing center. default is current x position
+ * @param $y (int) ordinate of the skewing center. default is current y position
+ * @public
+ * @since 2.1.000 (2008-01-07)
+ * @see StartTransform(), StopTransform()
+ */
+ public function SkewX($angle_x, $x='', $y='') {
+ $this->Skew($angle_x, 0, $x, $y);
+ }
+
+ /**
+ * Skew vertically.
+ * @param $angle_y (float) angle in degrees between -90 (skew to the bottom) and 90 (skew to the top)
+ * @param $x (int) abscissa of the skewing center. default is current x position
+ * @param $y (int) ordinate of the skewing center. default is current y position
+ * @public
+ * @since 2.1.000 (2008-01-07)
+ * @see StartTransform(), StopTransform()
+ */
+ public function SkewY($angle_y, $x='', $y='') {
+ $this->Skew(0, $angle_y, $x, $y);
+ }
+
+ /**
+ * Skew.
+ * @param $angle_x (float) angle in degrees between -90 (skew to the left) and 90 (skew to the right)
+ * @param $angle_y (float) angle in degrees between -90 (skew to the bottom) and 90 (skew to the top)
+ * @param $x (int) abscissa of the skewing center. default is current x position
+ * @param $y (int) ordinate of the skewing center. default is current y position
+ * @public
+ * @since 2.1.000 (2008-01-07)
+ * @see StartTransform(), StopTransform()
+ */
+ public function Skew($angle_x, $angle_y, $x='', $y='') {
+ if ($x === '') {
+ $x = $this->x;
+ }
+ if ($y === '') {
+ $y = $this->y;
+ }
+ if (($angle_x <= -90) OR ($angle_x >= 90) OR ($angle_y <= -90) OR ($angle_y >= 90)) {
+ $this->Error('Please use values between -90 and +90 degrees for Skewing.');
+ }
+ $x *= $this->k;
+ $y = ($this->h - $y) * $this->k;
+ //calculate elements of transformation matrix
+ $tm = array();
+ $tm[0] = 1;
+ $tm[1] = tan(deg2rad($angle_y));
+ $tm[2] = tan(deg2rad($angle_x));
+ $tm[3] = 1;
+ $tm[4] = -$tm[2] * $y;
+ $tm[5] = -$tm[1] * $x;
+ //skew the coordinate system
+ $this->Transform($tm);
+ }
+
+ /**
+ * Apply graphic transformations.
+ * @param $tm (array) transformation matrix
+ * @protected
+ * @since 2.1.000 (2008-01-07)
+ * @see StartTransform(), StopTransform()
+ */
+ protected function Transform($tm) {
+ if ($this->state != 2) {
+ return;
+ }
+ $this->_out(sprintf('%F %F %F %F %F %F cm', $tm[0], $tm[1], $tm[2], $tm[3], $tm[4], $tm[5]));
+ // add tranformation matrix
+ $this->transfmatrix[$this->transfmatrix_key][] = array('a' => $tm[0], 'b' => $tm[1], 'c' => $tm[2], 'd' => $tm[3], 'e' => $tm[4], 'f' => $tm[5]);
+ // update transformation mark
+ if ($this->inxobj) {
+ // we are inside an XObject template
+ if (end($this->xobjects[$this->xobjid]['transfmrk']) !== false) {
+ $key = key($this->xobjects[$this->xobjid]['transfmrk']);
+ $this->xobjects[$this->xobjid]['transfmrk'][$key] = strlen($this->xobjects[$this->xobjid]['outdata']);
+ }
+ } elseif (end($this->transfmrk[$this->page]) !== false) {
+ $key = key($this->transfmrk[$this->page]);
+ $this->transfmrk[$this->page][$key] = $this->pagelen[$this->page];
+ }
+ }
+
+ // END TRANSFORMATIONS SECTION -------------------------
+
+ // START GRAPHIC FUNCTIONS SECTION ---------------------
+ // The following section is based on the code provided by David Hernandez Sanz
+
+ /**
+ * Defines the line width. By default, the value equals 0.2 mm. The method can be called before the first page is created and the value is retained from page to page.
+ * @param $width (float) The width.
+ * @public
+ * @since 1.0
+ * @see Line(), Rect(), Cell(), MultiCell()
+ */
+ public function SetLineWidth($width) {
+ //Set line width
+ $this->LineWidth = $width;
+ $this->linestyleWidth = sprintf('%F w', ($width * $this->k));
+ if ($this->state == 2) {
+ $this->_out($this->linestyleWidth);
+ }
+ }
+
+ /**
+ * Returns the current the line width.
+ * @return int Line width
+ * @public
+ * @since 2.1.000 (2008-01-07)
+ * @see Line(), SetLineWidth()
+ */
+ public function GetLineWidth() {
+ return $this->LineWidth;
+ }
+
+ /**
+ * Set line style.
+ * @param $style (array) Line style. Array with keys among the following:
+ * <ul>
+ * <li>width (float): Width of the line in user units.</li>
+ * <li>cap (string): Type of cap to put on the line. Possible values are:
+ * butt, round, square. The difference between "square" and "butt" is that
+ * "square" projects a flat end past the end of the line.</li>
+ * <li>join (string): Type of join. Possible values are: miter, round,
+ * bevel.</li>
+ * <li>dash (mixed): Dash pattern. Is 0 (without dash) or string with
+ * series of length values, which are the lengths of the on and off dashes.
+ * For example: "2" represents 2 on, 2 off, 2 on, 2 off, ...; "2,1" is 2 on,
+ * 1 off, 2 on, 1 off, ...</li>
+ * <li>phase (integer): Modifier on the dash pattern which is used to shift
+ * the point at which the pattern starts.</li>
+ * <li>color (array): Draw color. Format: array(GREY) or array(R,G,B) or array(C,M,Y,K) or array(C,M,Y,K,SpotColorName).</li>
+ * </ul>
+ * @param $ret (boolean) if true do not send the command.
+ * @return string the PDF command
+ * @public
+ * @since 2.1.000 (2008-01-08)
+ */
+ public function SetLineStyle($style, $ret=false) {
+ $s = ''; // string to be returned
+ if (!is_array($style)) {
+ return;
+ }
+ if (isset($style['width'])) {
+ $this->LineWidth = $style['width'];
+ $this->linestyleWidth = sprintf('%F w', ($style['width'] * $this->k));
+ $s .= $this->linestyleWidth.' ';
+ }
+ if (isset($style['cap'])) {
+ $ca = array('butt' => 0, 'round'=> 1, 'square' => 2);
+ if (isset($ca[$style['cap']])) {
+ $this->linestyleCap = $ca[$style['cap']].' J';
+ $s .= $this->linestyleCap.' ';
+ }
+ }
+ if (isset($style['join'])) {
+ $ja = array('miter' => 0, 'round' => 1, 'bevel' => 2);
+ if (isset($ja[$style['join']])) {
+ $this->linestyleJoin = $ja[$style['join']].' j';
+ $s .= $this->linestyleJoin.' ';
+ }
+ }
+ if (isset($style['dash'])) {
+ $dash_string = '';
+ if ($style['dash']) {
+ if (preg_match('/^.+,/', $style['dash']) > 0) {
+ $tab = explode(',', $style['dash']);
+ } else {
+ $tab = array($style['dash']);
+ }
+ $dash_string = '';
+ foreach ($tab as $i => $v) {
+ if ($i) {
+ $dash_string .= ' ';
+ }
+ $dash_string .= sprintf('%F', $v);
+ }
+ }
+ if (!isset($style['phase']) OR !$style['dash']) {
+ $style['phase'] = 0;
+ }
+ $this->linestyleDash = sprintf('[%s] %F d', $dash_string, $style['phase']);
+ $s .= $this->linestyleDash.' ';
+ }
+ if (isset($style['color'])) {
+ $s .= $this->SetDrawColorArray($style['color'], true).' ';
+ }
+ if (!$ret AND ($this->state == 2)) {
+ $this->_out($s);
+ }
+ return $s;
+ }
+
+ /**
+ * Begin a new subpath by moving the current point to coordinates (x, y), omitting any connecting line segment.
+ * @param $x (float) Abscissa of point.
+ * @param $y (float) Ordinate of point.
+ * @protected
+ * @since 2.1.000 (2008-01-08)
+ */
+ protected function _outPoint($x, $y) {
+ if ($this->state == 2) {
+ $this->_out(sprintf('%F %F m', ($x * $this->k), (($this->h - $y) * $this->k)));
+ }
+ }
+
+ /**
+ * Append a straight line segment from the current point to the point (x, y).
+ * The new current point shall be (x, y).
+ * @param $x (float) Abscissa of end point.
+ * @param $y (float) Ordinate of end point.
+ * @protected
+ * @since 2.1.000 (2008-01-08)
+ */
+ protected function _outLine($x, $y) {
+ if ($this->state == 2) {
+ $this->_out(sprintf('%F %F l', ($x * $this->k), (($this->h - $y) * $this->k)));
+ }
+ }
+
+ /**
+ * Append a rectangle to the current path as a complete subpath, with lower-left corner (x, y) and dimensions widthand height in user space.
+ * @param $x (float) Abscissa of upper-left corner.
+ * @param $y (float) Ordinate of upper-left corner.
+ * @param $w (float) Width.
+ * @param $h (float) Height.
+ * @param $op (string) options
+ * @protected
+ * @since 2.1.000 (2008-01-08)
+ */
+ protected function _outRect($x, $y, $w, $h, $op) {
+ if ($this->state == 2) {
+ $this->_out(sprintf('%F %F %F %F re %s', ($x * $this->k), (($this->h - $y) * $this->k), ($w * $this->k), (-$h * $this->k), $op));
+ }
+ }
+
+ /**
+ * Append a cubic Bézier curve to the current path. The curve shall extend from the current point to the point (x3, y3), using (x1, y1) and (x2, y2) as the Bézier control points.
+ * The new current point shall be (x3, y3).
+ * @param $x1 (float) Abscissa of control point 1.
+ * @param $y1 (float) Ordinate of control point 1.
+ * @param $x2 (float) Abscissa of control point 2.
+ * @param $y2 (float) Ordinate of control point 2.
+ * @param $x3 (float) Abscissa of end point.
+ * @param $y3 (float) Ordinate of end point.
+ * @protected
+ * @since 2.1.000 (2008-01-08)
+ */
+ protected function _outCurve($x1, $y1, $x2, $y2, $x3, $y3) {
+ if ($this->state == 2) {
+ $this->_out(sprintf('%F %F %F %F %F %F c', ($x1 * $this->k), (($this->h - $y1) * $this->k), ($x2 * $this->k), (($this->h - $y2) * $this->k), ($x3 * $this->k), (($this->h - $y3) * $this->k)));
+ }
+ }
+
+ /**
+ * Append a cubic Bézier curve to the current path. The curve shall extend from the current point to the point (x3, y3), using the current point and (x2, y2) as the Bézier control points.
+ * The new current point shall be (x3, y3).
+ * @param $x2 (float) Abscissa of control point 2.
+ * @param $y2 (float) Ordinate of control point 2.
+ * @param $x3 (float) Abscissa of end point.
+ * @param $y3 (float) Ordinate of end point.
+ * @protected
+ * @since 4.9.019 (2010-04-26)
+ */
+ protected function _outCurveV($x2, $y2, $x3, $y3) {
+ if ($this->state == 2) {
+ $this->_out(sprintf('%F %F %F %F v', ($x2 * $this->k), (($this->h - $y2) * $this->k), ($x3 * $this->k), (($this->h - $y3) * $this->k)));
+ }
+ }
+
+ /**
+ * Append a cubic Bézier curve to the current path. The curve shall extend from the current point to the point (x3, y3), using (x1, y1) and (x3, y3) as the Bézier control points.
+ * The new current point shall be (x3, y3).
+ * @param $x1 (float) Abscissa of control point 1.
+ * @param $y1 (float) Ordinate of control point 1.
+ * @param $x3 (float) Abscissa of end point.
+ * @param $y3 (float) Ordinate of end point.
+ * @protected
+ * @since 2.1.000 (2008-01-08)
+ */
+ protected function _outCurveY($x1, $y1, $x3, $y3) {
+ if ($this->state == 2) {
+ $this->_out(sprintf('%F %F %F %F y', ($x1 * $this->k), (($this->h - $y1) * $this->k), ($x3 * $this->k), (($this->h - $y3) * $this->k)));
+ }
+ }
+
+ /**
+ * Draws a line between two points.
+ * @param $x1 (float) Abscissa of first point.
+ * @param $y1 (float) Ordinate of first point.
+ * @param $x2 (float) Abscissa of second point.
+ * @param $y2 (float) Ordinate of second point.
+ * @param $style (array) Line style. Array like for SetLineStyle(). Default value: default line style (empty array).
+ * @public
+ * @since 1.0
+ * @see SetLineWidth(), SetDrawColor(), SetLineStyle()
+ */
+ public function Line($x1, $y1, $x2, $y2, $style=array()) {
+ if ($this->state != 2) {
+ return;
+ }
+ if (is_array($style)) {
+ $this->SetLineStyle($style);
+ }
+ $this->_outPoint($x1, $y1);
+ $this->_outLine($x2, $y2);
+ $this->_out('S');
+ }
+
+ /**
+ * Draws a rectangle.
+ * @param $x (float) Abscissa of upper-left corner.
+ * @param $y (float) Ordinate of upper-left corner.
+ * @param $w (float) Width.
+ * @param $h (float) Height.
+ * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
+ * @param $border_style (array) Border style of rectangle. Array with keys among the following:
+ * <ul>
+ * <li>all: Line style of all borders. Array like for SetLineStyle().</li>
+ * <li>L, T, R, B or combinations: Line style of left, top, right or bottom border. Array like for SetLineStyle().</li>
+ * </ul>
+ * If a key is not present or is null, the correspondent border is not drawn. Default value: default line style (empty array).
+ * @param $fill_color (array) Fill color. Format: array(GREY) or array(R,G,B) or array(C,M,Y,K) or array(C,M,Y,K,SpotColorName). Default value: default color (empty array).
+ * @public
+ * @since 1.0
+ * @see SetLineStyle()
+ */
+ public function Rect($x, $y, $w, $h, $style='', $border_style=array(), $fill_color=array()) {
+ if ($this->state != 2) {
+ return;
+ }
+ if (empty($style)) {
+ $style = 'S';
+ }
+ if (!(strpos($style, 'F') === false) AND !empty($fill_color)) {
+ // set background color
+ $this->SetFillColorArray($fill_color);
+ }
+ if (!empty($border_style)) {
+ if (isset($border_style['all']) AND !empty($border_style['all'])) {
+ //set global style for border
+ $this->SetLineStyle($border_style['all']);
+ $border_style = array();
+ } else {
+ // remove stroke operator from style
+ $opnostroke = array('S' => '', 'D' => '', 's' => '', 'd' => '', 'B' => 'F', 'FD' => 'F', 'DF' => 'F', 'B*' => 'F*', 'F*D' => 'F*', 'DF*' => 'F*', 'b' => 'f', 'fd' => 'f', 'df' => 'f', 'b*' => 'f*', 'f*d' => 'f*', 'df*' => 'f*' );
+ if (isset($opnostroke[$style])) {
+ $style = $opnostroke[$style];
+ }
+ }
+ }
+ if (!empty($style)) {
+ $op = TCPDF_STATIC::getPathPaintOperator($style);
+ $this->_outRect($x, $y, $w, $h, $op);
+ }
+ if (!empty($border_style)) {
+ $border_style2 = array();
+ foreach ($border_style as $line => $value) {
+ $length = strlen($line);
+ for ($i = 0; $i < $length; ++$i) {
+ $border_style2[$line[$i]] = $value;
+ }
+ }
+ $border_style = $border_style2;
+ if (isset($border_style['L']) AND $border_style['L']) {
+ $this->Line($x, $y, $x, $y + $h, $border_style['L']);
+ }
+ if (isset($border_style['T']) AND $border_style['T']) {
+ $this->Line($x, $y, $x + $w, $y, $border_style['T']);
+ }
+ if (isset($border_style['R']) AND $border_style['R']) {
+ $this->Line($x + $w, $y, $x + $w, $y + $h, $border_style['R']);
+ }
+ if (isset($border_style['B']) AND $border_style['B']) {
+ $this->Line($x, $y + $h, $x + $w, $y + $h, $border_style['B']);
+ }
+ }
+ }
+
+ /**
+ * Draws a Bezier curve.
+ * The Bezier curve is a tangent to the line between the control points at
+ * either end of the curve.
+ * @param $x0 (float) Abscissa of start point.
+ * @param $y0 (float) Ordinate of start point.
+ * @param $x1 (float) Abscissa of control point 1.
+ * @param $y1 (float) Ordinate of control point 1.
+ * @param $x2 (float) Abscissa of control point 2.
+ * @param $y2 (float) Ordinate of control point 2.
+ * @param $x3 (float) Abscissa of end point.
+ * @param $y3 (float) Ordinate of end point.
+ * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
+ * @param $line_style (array) Line style of curve. Array like for SetLineStyle(). Default value: default line style (empty array).
+ * @param $fill_color (array) Fill color. Format: array(GREY) or array(R,G,B) or array(C,M,Y,K) or array(C,M,Y,K,SpotColorName). Default value: default color (empty array).
+ * @public
+ * @see SetLineStyle()
+ * @since 2.1.000 (2008-01-08)
+ */
+ public function Curve($x0, $y0, $x1, $y1, $x2, $y2, $x3, $y3, $style='', $line_style=array(), $fill_color=array()) {
+ if ($this->state != 2) {
+ return;
+ }
+ if (!(false === strpos($style, 'F')) AND isset($fill_color)) {
+ $this->SetFillColorArray($fill_color);
+ }
+ $op = TCPDF_STATIC::getPathPaintOperator($style);
+ if ($line_style) {
+ $this->SetLineStyle($line_style);
+ }
+ $this->_outPoint($x0, $y0);
+ $this->_outCurve($x1, $y1, $x2, $y2, $x3, $y3);
+ $this->_out($op);
+ }
+
+ /**
+ * Draws a poly-Bezier curve.
+ * Each Bezier curve segment is a tangent to the line between the control points at
+ * either end of the curve.
+ * @param $x0 (float) Abscissa of start point.
+ * @param $y0 (float) Ordinate of start point.
+ * @param $segments (float) An array of bezier descriptions. Format: array(x1, y1, x2, y2, x3, y3).
+ * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
+ * @param $line_style (array) Line style of curve. Array like for SetLineStyle(). Default value: default line style (empty array).
+ * @param $fill_color (array) Fill color. Format: array(GREY) or array(R,G,B) or array(C,M,Y,K) or array(C,M,Y,K,SpotColorName). Default value: default color (empty array).
+ * @public
+ * @see SetLineStyle()
+ * @since 3.0008 (2008-05-12)
+ */
+ public function Polycurve($x0, $y0, $segments, $style='', $line_style=array(), $fill_color=array()) {
+ if ($this->state != 2) {
+ return;
+ }
+ if (!(false === strpos($style, 'F')) AND isset($fill_color)) {
+ $this->SetFillColorArray($fill_color);
+ }
+ $op = TCPDF_STATIC::getPathPaintOperator($style);
+ if ($op == 'f') {
+ $line_style = array();
+ }
+ if ($line_style) {
+ $this->SetLineStyle($line_style);
+ }
+ $this->_outPoint($x0, $y0);
+ foreach ($segments as $segment) {
+ list($x1, $y1, $x2, $y2, $x3, $y3) = $segment;
+ $this->_outCurve($x1, $y1, $x2, $y2, $x3, $y3);
+ }
+ $this->_out($op);
+ }
+
+ /**
+ * Draws an ellipse.
+ * An ellipse is formed from n Bezier curves.
+ * @param $x0 (float) Abscissa of center point.
+ * @param $y0 (float) Ordinate of center point.
+ * @param $rx (float) Horizontal radius.
+ * @param $ry (float) Vertical radius (if ry = 0 then is a circle, see Circle()). Default value: 0.
+ * @param $angle: (float) Angle oriented (anti-clockwise). Default value: 0.
+ * @param $astart: (float) Angle start of draw line. Default value: 0.
+ * @param $afinish: (float) Angle finish of draw line. Default value: 360.
+ * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
+ * @param $line_style (array) Line style of ellipse. Array like for SetLineStyle(). Default value: default line style (empty array).
+ * @param $fill_color (array) Fill color. Format: array(GREY) or array(R,G,B) or array(C,M,Y,K) or array(C,M,Y,K,SpotColorName). Default value: default color (empty array).
+ * @param $nc (integer) Number of curves used to draw a 90 degrees portion of ellipse.
+ * @author Nicola Asuni
+ * @public
+ * @since 2.1.000 (2008-01-08)
+ */
+ public function Ellipse($x0, $y0, $rx, $ry='', $angle=0, $astart=0, $afinish=360, $style='', $line_style=array(), $fill_color=array(), $nc=2) {
+ if ($this->state != 2) {
+ return;
+ }
+ if (TCPDF_STATIC::empty_string($ry) OR ($ry == 0)) {
+ $ry = $rx;
+ }
+ if (!(false === strpos($style, 'F')) AND isset($fill_color)) {
+ $this->SetFillColorArray($fill_color);
+ }
+ $op = TCPDF_STATIC::getPathPaintOperator($style);
+ if ($op == 'f') {
+ $line_style = array();
+ }
+ if ($line_style) {
+ $this->SetLineStyle($line_style);
+ }
+ $this->_outellipticalarc($x0, $y0, $rx, $ry, $angle, $astart, $afinish, false, $nc, true, true, false);
+ $this->_out($op);
+ }
+
+ /**
+ * Append an elliptical arc to the current path.
+ * An ellipse is formed from n Bezier curves.
+ * @param $xc (float) Abscissa of center point.
+ * @param $yc (float) Ordinate of center point.
+ * @param $rx (float) Horizontal radius.
+ * @param $ry (float) Vertical radius (if ry = 0 then is a circle, see Circle()). Default value: 0.
+ * @param $xang: (float) Angle between the X-axis and the major axis of the ellipse. Default value: 0.
+ * @param $angs: (float) Angle start of draw line. Default value: 0.
+ * @param $angf: (float) Angle finish of draw line. Default value: 360.
+ * @param $pie (boolean) if true do not mark the border point (used to draw pie sectors).
+ * @param $nc (integer) Number of curves used to draw a 90 degrees portion of ellipse.
+ * @param $startpoint (boolean) if true output a starting point.
+ * @param $ccw (boolean) if true draws in counter-clockwise.
+ * @param $svg (boolean) if true the angles are in svg mode (already calculated).
+ * @return array bounding box coordinates (x min, y min, x max, y max)
+ * @author Nicola Asuni
+ * @protected
+ * @since 4.9.019 (2010-04-26)
+ */
+ protected function _outellipticalarc($xc, $yc, $rx, $ry, $xang=0, $angs=0, $angf=360, $pie=false, $nc=2, $startpoint=true, $ccw=true, $svg=false) {
+ if (($rx <= 0) OR ($ry < 0)) {
+ return;
+ }
+ $k = $this->k;
+ if ($nc < 2) {
+ $nc = 2;
+ }
+ $xmin = 2147483647;
+ $ymin = 2147483647;
+ $xmax = 0;
+ $ymax = 0;
+ if ($pie) {
+ // center of the arc
+ $this->_outPoint($xc, $yc);
+ }
+ $xang = deg2rad((float) $xang);
+ $angs = deg2rad((float) $angs);
+ $angf = deg2rad((float) $angf);
+ if ($svg) {
+ $as = $angs;
+ $af = $angf;
+ } else {
+ $as = atan2((sin($angs) / $ry), (cos($angs) / $rx));
+ $af = atan2((sin($angf) / $ry), (cos($angf) / $rx));
+ }
+ if ($as < 0) {
+ $as += (2 * M_PI);
+ }
+ if ($af < 0) {
+ $af += (2 * M_PI);
+ }
+ if ($ccw AND ($as > $af)) {
+ // reverse rotation
+ $as -= (2 * M_PI);
+ } elseif (!$ccw AND ($as < $af)) {
+ // reverse rotation
+ $af -= (2 * M_PI);
+ }
+ $total_angle = ($af - $as);
+ if ($nc < 2) {
+ $nc = 2;
+ }
+ // total arcs to draw
+ $nc *= (2 * abs($total_angle) / M_PI);
+ $nc = round($nc) + 1;
+ // angle of each arc
+ $arcang = ($total_angle / $nc);
+ // center point in PDF coordinates
+ $x0 = $xc;
+ $y0 = ($this->h - $yc);
+ // starting angle
+ $ang = $as;
+ $alpha = sin($arcang) * ((sqrt(4 + (3 * pow(tan(($arcang) / 2), 2))) - 1) / 3);
+ $cos_xang = cos($xang);
+ $sin_xang = sin($xang);
+ $cos_ang = cos($ang);
+ $sin_ang = sin($ang);
+ // first arc point
+ $px1 = $x0 + ($rx * $cos_xang * $cos_ang) - ($ry * $sin_xang * $sin_ang);
+ $py1 = $y0 + ($rx * $sin_xang * $cos_ang) + ($ry * $cos_xang * $sin_ang);
+ // first Bezier control point
+ $qx1 = ($alpha * ((-$rx * $cos_xang * $sin_ang) - ($ry * $sin_xang * $cos_ang)));
+ $qy1 = ($alpha * ((-$rx * $sin_xang * $sin_ang) + ($ry * $cos_xang * $cos_ang)));
+ if ($pie) {
+ // line from center to arc starting point
+ $this->_outLine($px1, $this->h - $py1);
+ } elseif ($startpoint) {
+ // arc starting point
+ $this->_outPoint($px1, $this->h - $py1);
+ }
+ // draw arcs
+ for ($i = 1; $i <= $nc; ++$i) {
+ // starting angle
+ $ang = $as + ($i * $arcang);
+ if ($i == $nc) {
+ $ang = $af;
+ }
+ $cos_ang = cos($ang);
+ $sin_ang = sin($ang);
+ // second arc point
+ $px2 = $x0 + ($rx * $cos_xang * $cos_ang) - ($ry * $sin_xang * $sin_ang);
+ $py2 = $y0 + ($rx * $sin_xang * $cos_ang) + ($ry * $cos_xang * $sin_ang);
+ // second Bezier control point
+ $qx2 = ($alpha * ((-$rx * $cos_xang * $sin_ang) - ($ry * $sin_xang * $cos_ang)));
+ $qy2 = ($alpha * ((-$rx * $sin_xang * $sin_ang) + ($ry * $cos_xang * $cos_ang)));
+ // draw arc
+ $cx1 = ($px1 + $qx1);
+ $cy1 = ($this->h - ($py1 + $qy1));
+ $cx2 = ($px2 - $qx2);
+ $cy2 = ($this->h - ($py2 - $qy2));
+ $cx3 = $px2;
+ $cy3 = ($this->h - $py2);
+ $this->_outCurve($cx1, $cy1, $cx2, $cy2, $cx3, $cy3);
+ // get bounding box coordinates
+ $xmin = min($xmin, $cx1, $cx2, $cx3);
+ $ymin = min($ymin, $cy1, $cy2, $cy3);
+ $xmax = max($xmax, $cx1, $cx2, $cx3);
+ $ymax = max($ymax, $cy1, $cy2, $cy3);
+ // move to next point
+ $px1 = $px2;
+ $py1 = $py2;
+ $qx1 = $qx2;
+ $qy1 = $qy2;
+ }
+ if ($pie) {
+ $this->_outLine($xc, $yc);
+ // get bounding box coordinates
+ $xmin = min($xmin, $xc);
+ $ymin = min($ymin, $yc);
+ $xmax = max($xmax, $xc);
+ $ymax = max($ymax, $yc);
+ }
+ return array($xmin, $ymin, $xmax, $ymax);
+ }
+
+ /**
+ * Draws a circle.
+ * A circle is formed from n Bezier curves.
+ * @param $x0 (float) Abscissa of center point.
+ * @param $y0 (float) Ordinate of center point.
+ * @param $r (float) Radius.
+ * @param $angstr: (float) Angle start of draw line. Default value: 0.
+ * @param $angend: (float) Angle finish of draw line. Default value: 360.
+ * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
+ * @param $line_style (array) Line style of circle. Array like for SetLineStyle(). Default value: default line style (empty array).
+ * @param $fill_color (array) Fill color. Format: array(red, green, blue). Default value: default color (empty array).
+ * @param $nc (integer) Number of curves used to draw a 90 degrees portion of circle.
+ * @public
+ * @since 2.1.000 (2008-01-08)
+ */
+ public function Circle($x0, $y0, $r, $angstr=0, $angend=360, $style='', $line_style=array(), $fill_color=array(), $nc=2) {
+ $this->Ellipse($x0, $y0, $r, $r, 0, $angstr, $angend, $style, $line_style, $fill_color, $nc);
+ }
+
+ /**
+ * Draws a polygonal line
+ * @param $p (array) Points 0 to ($np - 1). Array with values (x0, y0, x1, y1,..., x(np-1), y(np - 1))
+ * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
+ * @param $line_style (array) Line style of polygon. Array with keys among the following:
+ * <ul>
+ * <li>all: Line style of all lines. Array like for SetLineStyle().</li>
+ * <li>0 to ($np - 1): Line style of each line. Array like for SetLineStyle().</li>
+ * </ul>
+ * If a key is not present or is null, not draws the line. Default value is default line style (empty array).
+ * @param $fill_color (array) Fill color. Format: array(GREY) or array(R,G,B) or array(C,M,Y,K) or array(C,M,Y,K,SpotColorName). Default value: default color (empty array).
+ * @since 4.8.003 (2009-09-15)
+ * @public
+ */
+ public function PolyLine($p, $style='', $line_style=array(), $fill_color=array()) {
+ $this->Polygon($p, $style, $line_style, $fill_color, false);
+ }
+
+ /**
+ * Draws a polygon.
+ * @param $p (array) Points 0 to ($np - 1). Array with values (x0, y0, x1, y1,..., x(np-1), y(np - 1))
+ * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
+ * @param $line_style (array) Line style of polygon. Array with keys among the following:
+ * <ul>
+ * <li>all: Line style of all lines. Array like for SetLineStyle().</li>
+ * <li>0 to ($np - 1): Line style of each line. Array like for SetLineStyle().</li>
+ * </ul>
+ * If a key is not present or is null, not draws the line. Default value is default line style (empty array).
+ * @param $fill_color (array) Fill color. Format: array(GREY) or array(R,G,B) or array(C,M,Y,K) or array(C,M,Y,K,SpotColorName). Default value: default color (empty array).
+ * @param $closed (boolean) if true the polygon is closes, otherwise will remain open
+ * @public
+ * @since 2.1.000 (2008-01-08)
+ */
+ public function Polygon($p, $style='', $line_style=array(), $fill_color=array(), $closed=true) {
+ if ($this->state != 2) {
+ return;
+ }
+ $nc = count($p); // number of coordinates
+ $np = $nc / 2; // number of points
+ if ($closed) {
+ // close polygon by adding the first 2 points at the end (one line)
+ for ($i = 0; $i < 4; ++$i) {
+ $p[$nc + $i] = $p[$i];
+ }
+ // copy style for the last added line
+ if (isset($line_style[0])) {
+ $line_style[$np] = $line_style[0];
+ }
+ $nc += 4;
+ }
+ if (!(false === strpos($style, 'F')) AND isset($fill_color)) {
+ $this->SetFillColorArray($fill_color);
+ }
+ $op = TCPDF_STATIC::getPathPaintOperator($style);
+ if ($op == 'f') {
+ $line_style = array();
+ }
+ $draw = true;
+ if ($line_style) {
+ if (isset($line_style['all'])) {
+ $this->SetLineStyle($line_style['all']);
+ } else {
+ $draw = false;
+ if ($op == 'B') {
+ // draw fill
+ $op = 'f';
+ $this->_outPoint($p[0], $p[1]);
+ for ($i = 2; $i < $nc; $i = $i + 2) {
+ $this->_outLine($p[$i], $p[$i + 1]);
+ }
+ $this->_out($op);
+ }
+ // draw outline
+ $this->_outPoint($p[0], $p[1]);
+ for ($i = 2; $i < $nc; $i = $i + 2) {
+ $line_num = ($i / 2) - 1;
+ if (isset($line_style[$line_num])) {
+ if ($line_style[$line_num] != 0) {
+ if (is_array($line_style[$line_num])) {
+ $this->_out('S');
+ $this->SetLineStyle($line_style[$line_num]);
+ $this->_outPoint($p[$i - 2], $p[$i - 1]);
+ $this->_outLine($p[$i], $p[$i + 1]);
+ $this->_out('S');
+ $this->_outPoint($p[$i], $p[$i + 1]);
+ } else {
+ $this->_outLine($p[$i], $p[$i + 1]);
+ }
+ }
+ } else {
+ $this->_outLine($p[$i], $p[$i + 1]);
+ }
+ }
+ $this->_out($op);
+ }
+ }
+ if ($draw) {
+ $this->_outPoint($p[0], $p[1]);
+ for ($i = 2; $i < $nc; $i = $i + 2) {
+ $this->_outLine($p[$i], $p[$i + 1]);
+ }
+ $this->_out($op);
+ }
+ }
+
+ /**
+ * Draws a regular polygon.
+ * @param $x0 (float) Abscissa of center point.
+ * @param $y0 (float) Ordinate of center point.
+ * @param $r: (float) Radius of inscribed circle.
+ * @param $ns (integer) Number of sides.
+ * @param $angle (float) Angle oriented (anti-clockwise). Default value: 0.
+ * @param $draw_circle (boolean) Draw inscribed circle or not. Default value: false.
+ * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
+ * @param $line_style (array) Line style of polygon sides. Array with keys among the following:
+ * <ul>
+ * <li>all: Line style of all sides. Array like for SetLineStyle().</li>
+ * <li>0 to ($ns - 1): Line style of each side. Array like for SetLineStyle().</li>
+ * </ul>
+ * If a key is not present or is null, not draws the side. Default value is default line style (empty array).
+ * @param $fill_color (array) Fill color. Format: array(red, green, blue). Default value: default color (empty array).
+ * @param $circle_style (string) Style of rendering of inscribed circle (if draws). Possible values are:
+ * <ul>
+ * <li>D or empty string: Draw (default).</li>
+ * <li>F: Fill.</li>
+ * <li>DF or FD: Draw and fill.</li>
+ * <li>CNZ: Clipping mode (using the even-odd rule to determine which regions lie inside the clipping path).</li>
+ * <li>CEO: Clipping mode (using the nonzero winding number rule to determine which regions lie inside the clipping path).</li>
+ * </ul>
+ * @param $circle_outLine_style (array) Line style of inscribed circle (if draws). Array like for SetLineStyle(). Default value: default line style (empty array).
+ * @param $circle_fill_color (array) Fill color of inscribed circle (if draws). Format: array(red, green, blue). Default value: default color (empty array).
+ * @public
+ * @since 2.1.000 (2008-01-08)
+ */
+ public function RegularPolygon($x0, $y0, $r, $ns, $angle=0, $draw_circle=false, $style='', $line_style=array(), $fill_color=array(), $circle_style='', $circle_outLine_style=array(), $circle_fill_color=array()) {
+ if (3 > $ns) {
+ $ns = 3;
+ }
+ if ($draw_circle) {
+ $this->Circle($x0, $y0, $r, 0, 360, $circle_style, $circle_outLine_style, $circle_fill_color);
+ }
+ $p = array();
+ for ($i = 0; $i < $ns; ++$i) {
+ $a = $angle + ($i * 360 / $ns);
+ $a_rad = deg2rad((float) $a);
+ $p[] = $x0 + ($r * sin($a_rad));
+ $p[] = $y0 + ($r * cos($a_rad));
+ }
+ $this->Polygon($p, $style, $line_style, $fill_color);
+ }
+
+ /**
+ * Draws a star polygon
+ * @param $x0 (float) Abscissa of center point.
+ * @param $y0 (float) Ordinate of center point.
+ * @param $r (float) Radius of inscribed circle.
+ * @param $nv (integer) Number of vertices.
+ * @param $ng (integer) Number of gap (if ($ng % $nv = 1) then is a regular polygon).
+ * @param $angle: (float) Angle oriented (anti-clockwise). Default value: 0.
+ * @param $draw_circle: (boolean) Draw inscribed circle or not. Default value is false.
+ * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
+ * @param $line_style (array) Line style of polygon sides. Array with keys among the following:
+ * <ul>
+ * <li>all: Line style of all sides. Array like for
+ * SetLineStyle().</li>
+ * <li>0 to (n - 1): Line style of each side. Array like for SetLineStyle().</li>
+ * </ul>
+ * If a key is not present or is null, not draws the side. Default value is default line style (empty array).
+ * @param $fill_color (array) Fill color. Format: array(red, green, blue). Default value: default color (empty array).
+ * @param $circle_style (string) Style of rendering of inscribed circle (if draws). Possible values are:
+ * <ul>
+ * <li>D or empty string: Draw (default).</li>
+ * <li>F: Fill.</li>
+ * <li>DF or FD: Draw and fill.</li>
+ * <li>CNZ: Clipping mode (using the even-odd rule to determine which regions lie inside the clipping path).</li>
+ * <li>CEO: Clipping mode (using the nonzero winding number rule to determine which regions lie inside the clipping path).</li>
+ * </ul>
+ * @param $circle_outLine_style (array) Line style of inscribed circle (if draws). Array like for SetLineStyle(). Default value: default line style (empty array).
+ * @param $circle_fill_color (array) Fill color of inscribed circle (if draws). Format: array(red, green, blue). Default value: default color (empty array).
+ * @public
+ * @since 2.1.000 (2008-01-08)
+ */
+ public function StarPolygon($x0, $y0, $r, $nv, $ng, $angle=0, $draw_circle=false, $style='', $line_style=array(), $fill_color=array(), $circle_style='', $circle_outLine_style=array(), $circle_fill_color=array()) {
+ if ($nv < 2) {
+ $nv = 2;
+ }
+ if ($draw_circle) {
+ $this->Circle($x0, $y0, $r, 0, 360, $circle_style, $circle_outLine_style, $circle_fill_color);
+ }
+ $p2 = array();
+ $visited = array();
+ for ($i = 0; $i < $nv; ++$i) {
+ $a = $angle + ($i * 360 / $nv);
+ $a_rad = deg2rad((float) $a);
+ $p2[] = $x0 + ($r * sin($a_rad));
+ $p2[] = $y0 + ($r * cos($a_rad));
+ $visited[] = false;
+ }
+ $p = array();
+ $i = 0;
+ do {
+ $p[] = $p2[$i * 2];
+ $p[] = $p2[($i * 2) + 1];
+ $visited[$i] = true;
+ $i += $ng;
+ $i %= $nv;
+ } while (!$visited[$i]);
+ $this->Polygon($p, $style, $line_style, $fill_color);
+ }
+
+ /**
+ * Draws a rounded rectangle.
+ * @param $x (float) Abscissa of upper-left corner.
+ * @param $y (float) Ordinate of upper-left corner.
+ * @param $w (float) Width.
+ * @param $h (float) Height.
+ * @param $r (float) the radius of the circle used to round off the corners of the rectangle.
+ * @param $round_corner (string) Draws rounded corner or not. String with a 0 (not rounded i-corner) or 1 (rounded i-corner) in i-position. Positions are, in order and begin to 0: top right, bottom right, bottom left and top left. Default value: all rounded corner ("1111").
+ * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
+ * @param $border_style (array) Border style of rectangle. Array like for SetLineStyle(). Default value: default line style (empty array).
+ * @param $fill_color (array) Fill color. Format: array(GREY) or array(R,G,B) or array(C,M,Y,K) or array(C,M,Y,K,SpotColorName). Default value: default color (empty array).
+ * @public
+ * @since 2.1.000 (2008-01-08)
+ */
+ public function RoundedRect($x, $y, $w, $h, $r, $round_corner='1111', $style='', $border_style=array(), $fill_color=array()) {
+ $this->RoundedRectXY($x, $y, $w, $h, $r, $r, $round_corner, $style, $border_style, $fill_color);
+ }
+
+ /**
+ * Draws a rounded rectangle.
+ * @param $x (float) Abscissa of upper-left corner.
+ * @param $y (float) Ordinate of upper-left corner.
+ * @param $w (float) Width.
+ * @param $h (float) Height.
+ * @param $rx (float) the x-axis radius of the ellipse used to round off the corners of the rectangle.
+ * @param $ry (float) the y-axis radius of the ellipse used to round off the corners of the rectangle.
+ * @param $round_corner (string) Draws rounded corner or not. String with a 0 (not rounded i-corner) or 1 (rounded i-corner) in i-position. Positions are, in order and begin to 0: top right, bottom right, bottom left and top left. Default value: all rounded corner ("1111").
+ * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
+ * @param $border_style (array) Border style of rectangle. Array like for SetLineStyle(). Default value: default line style (empty array).
+ * @param $fill_color (array) Fill color. Format: array(GREY) or array(R,G,B) or array(C,M,Y,K) or array(C,M,Y,K,SpotColorName). Default value: default color (empty array).
+ * @public
+ * @since 4.9.019 (2010-04-22)
+ */
+ public function RoundedRectXY($x, $y, $w, $h, $rx, $ry, $round_corner='1111', $style='', $border_style=array(), $fill_color=array()) {
+ if ($this->state != 2) {
+ return;
+ }
+ if (($round_corner == '0000') OR (($rx == $ry) AND ($rx == 0))) {
+ // Not rounded
+ $this->Rect($x, $y, $w, $h, $style, $border_style, $fill_color);
+ return;
+ }
+ // Rounded
+ if (!(false === strpos($style, 'F')) AND isset($fill_color)) {
+ $this->SetFillColorArray($fill_color);
+ }
+ $op = TCPDF_STATIC::getPathPaintOperator($style);
+ if ($op == 'f') {
+ $border_style = array();
+ }
+ if ($border_style) {
+ $this->SetLineStyle($border_style);
+ }
+ $MyArc = 4 / 3 * (sqrt(2) - 1);
+ $this->_outPoint($x + $rx, $y);
+ $xc = $x + $w - $rx;
+ $yc = $y + $ry;
+ $this->_outLine($xc, $y);
+ if ($round_corner[0]) {
+ $this->_outCurve($xc + ($rx * $MyArc), $yc - $ry, $xc + $rx, $yc - ($ry * $MyArc), $xc + $rx, $yc);
+ } else {
+ $this->_outLine($x + $w, $y);
+ }
+ $xc = $x + $w - $rx;
+ $yc = $y + $h - $ry;
+ $this->_outLine($x + $w, $yc);
+ if ($round_corner[1]) {
+ $this->_outCurve($xc + $rx, $yc + ($ry * $MyArc), $xc + ($rx * $MyArc), $yc + $ry, $xc, $yc + $ry);
+ } else {
+ $this->_outLine($x + $w, $y + $h);
+ }
+ $xc = $x + $rx;
+ $yc = $y + $h - $ry;
+ $this->_outLine($xc, $y + $h);
+ if ($round_corner[2]) {
+ $this->_outCurve($xc - ($rx * $MyArc), $yc + $ry, $xc - $rx, $yc + ($ry * $MyArc), $xc - $rx, $yc);
+ } else {
+ $this->_outLine($x, $y + $h);
+ }
+ $xc = $x + $rx;
+ $yc = $y + $ry;
+ $this->_outLine($x, $yc);
+ if ($round_corner[3]) {
+ $this->_outCurve($xc - $rx, $yc - ($ry * $MyArc), $xc - ($rx * $MyArc), $yc - $ry, $xc, $yc - $ry);
+ } else {
+ $this->_outLine($x, $y);
+ $this->_outLine($x + $rx, $y);
+ }
+ $this->_out($op);
+ }
+
+ /**
+ * Draws a grahic arrow.
+ * @param $x0 (float) Abscissa of first point.
+ * @param $y0 (float) Ordinate of first point.
+ * @param $x1 (float) Abscissa of second point.
+ * @param $y1 (float) Ordinate of second point.
+ * @param $head_style (int) (0 = draw only arrowhead arms, 1 = draw closed arrowhead, but no fill, 2 = closed and filled arrowhead, 3 = filled arrowhead)
+ * @param $arm_size (float) length of arrowhead arms
+ * @param $arm_angle (int) angle between an arm and the shaft
+ * @author Piotr Galecki, Nicola Asuni, Andy Meier
+ * @since 4.6.018 (2009-07-10)
+ */
+ public function Arrow($x0, $y0, $x1, $y1, $head_style=0, $arm_size=5, $arm_angle=15) {
+ // getting arrow direction angle
+ // 0 deg angle is when both arms go along X axis. angle grows clockwise.
+ $dir_angle = atan2(($y0 - $y1), ($x0 - $x1));
+ if ($dir_angle < 0) {
+ $dir_angle += (2 * M_PI);
+ }
+ $arm_angle = deg2rad($arm_angle);
+ $sx1 = $x1;
+ $sy1 = $y1;
+ if ($head_style > 0) {
+ // calculate the stopping point for the arrow shaft
+ $sx1 = $x1 + (($arm_size - $this->LineWidth) * cos($dir_angle));
+ $sy1 = $y1 + (($arm_size - $this->LineWidth) * sin($dir_angle));
+ }
+ // main arrow line / shaft
+ $this->Line($x0, $y0, $sx1, $sy1);
+ // left arrowhead arm tip
+ $x2L = $x1 + ($arm_size * cos($dir_angle + $arm_angle));
+ $y2L = $y1 + ($arm_size * sin($dir_angle + $arm_angle));
+ // right arrowhead arm tip
+ $x2R = $x1 + ($arm_size * cos($dir_angle - $arm_angle));
+ $y2R = $y1 + ($arm_size * sin($dir_angle - $arm_angle));
+ $mode = 'D';
+ $style = array();
+ switch ($head_style) {
+ case 0: {
+ // draw only arrowhead arms
+ $mode = 'D';
+ $style = array(1, 1, 0);
+ break;
+ }
+ case 1: {
+ // draw closed arrowhead, but no fill
+ $mode = 'D';
+ break;
+ }
+ case 2: {
+ // closed and filled arrowhead
+ $mode = 'DF';
+ break;
+ }
+ case 3: {
+ // filled arrowhead
+ $mode = 'F';
+ break;
+ }
+ }
+ $this->Polygon(array($x2L, $y2L, $x1, $y1, $x2R, $y2R), $mode, $style, array());
+ }
+
+ // END GRAPHIC FUNCTIONS SECTION -----------------------
+
+ /**
+ * Add a Named Destination.
+ * NOTE: destination names are unique, so only last entry will be saved.
+ * @param $name (string) Destination name.
+ * @param $y (float) Y position in user units of the destiantion on the selected page (default = -1 = current position; 0 = page start;).
+ * @param $page (int) Target page number (leave empty for current page).
+ * @param $x (float) X position in user units of the destiantion on the selected page (default = -1 = current position;).
+ * @return (string) Stripped named destination identifier or false in case of error.
+ * @public
+ * @author Christian Deligant, Nicola Asuni
+ * @since 5.9.097 (2011-06-23)
+ */
+ public function setDestination($name, $y=-1, $page='', $x=-1) {
+ // remove unsupported characters
+ $name = TCPDF_STATIC::encodeNameObject($name);
+ if (TCPDF_STATIC::empty_string($name)) {
+ return false;
+ }
+ if ($y == -1) {
+ $y = $this->GetY();
+ } elseif ($y < 0) {
+ $y = 0;
+ } elseif ($y > $this->h) {
+ $y = $this->h;
+ }
+ if ($x == -1) {
+ $x = $this->GetX();
+ } elseif ($x < 0) {
+ $x = 0;
+ } elseif ($x > $this->w) {
+ $x = $this->w;
+ }
+ if (empty($page)) {
+ $page = $this->PageNo();
+ if (empty($page)) {
+ return;
+ }
+ }
+ $this->dests[$name] = array('x' => $x, 'y' => $y, 'p' => $page);
+ return $name;
+ }
+
+ /**
+ * Return the Named Destination array.
+ * @return (array) Named Destination array.
+ * @public
+ * @author Nicola Asuni
+ * @since 5.9.097 (2011-06-23)
+ */
+ public function getDestination() {
+ return $this->dests;
+ }
+
+ /**
+ * Insert Named Destinations.
+ * @protected
+ * @author Johannes Güntert, Nicola Asuni
+ * @since 5.9.098 (2011-06-23)
+ */
+ protected function _putdests() {
+ if (empty($this->dests)) {
+ return;
+ }
+ $this->n_dests = $this->_newobj();
+ $out = ' <<';
+ foreach($this->dests as $name => $o) {
+ $out .= ' /'.$name.' '.sprintf('[%u 0 R /XYZ %F %F null]', $this->page_obj_id[($o['p'])], ($o['x'] * $this->k), ($this->pagedim[$o['p']]['h'] - ($o['y'] * $this->k)));
+ }
+ $out .= ' >>';
+ $out .= "\n".'endobj';
+ $this->_out($out);
+ }
+
+ /**
+ * Adds a bookmark - alias for Bookmark().
+ * @param $txt (string) Bookmark description.
+ * @param $level (int) Bookmark level (minimum value is 0).
+ * @param $y (float) Y position in user units of the bookmark on the selected page (default = -1 = current position; 0 = page start;).
+ * @param $page (int) Target page number (leave empty for current page).
+ * @param $style (string) Font style: B = Bold, I = Italic, BI = Bold + Italic.
+ * @param $color (array) RGB color array (values from 0 to 255).
+ * @param $x (float) X position in user units of the bookmark on the selected page (default = -1 = current position;).
+ * @param $link (mixed) URL, or numerical link ID, or named destination (# character followed by the destination name), or embedded file (* character followed by the file name).
+ * @public
+ */
+ public function setBookmark($txt, $level=0, $y=-1, $page='', $style='', $color=array(0,0,0), $x=-1, $link='') {
+ $this->Bookmark($txt, $level, $y, $page, $style, $color, $x, $link);
+ }
+
+ /**
+ * Adds a bookmark.
+ * @param $txt (string) Bookmark description.
+ * @param $level (int) Bookmark level (minimum value is 0).
+ * @param $y (float) Y position in user units of the bookmark on the selected page (default = -1 = current position; 0 = page start;).
+ * @param $page (int) Target page number (leave empty for current page).
+ * @param $style (string) Font style: B = Bold, I = Italic, BI = Bold + Italic.
+ * @param $color (array) RGB color array (values from 0 to 255).
+ * @param $x (float) X position in user units of the bookmark on the selected page (default = -1 = current position;).
+ * @param $link (mixed) URL, or numerical link ID, or named destination (# character followed by the destination name), or embedded file (* character followed by the file name).
+ * @public
+ * @since 2.1.002 (2008-02-12)
+ */
+ public function Bookmark($txt, $level=0, $y=-1, $page='', $style='', $color=array(0,0,0), $x=-1, $link='') {
+ if ($level < 0) {
+ $level = 0;
+ }
+ if (isset($this->outlines[0])) {
+ $lastoutline = end($this->outlines);
+ $maxlevel = $lastoutline['l'] + 1;
+ } else {
+ $maxlevel = 0;
+ }
+ if ($level > $maxlevel) {
+ $level = $maxlevel;
+ }
+ if ($y == -1) {
+ $y = $this->GetY();
+ } elseif ($y < 0) {
+ $y = 0;
+ } elseif ($y > $this->h) {
+ $y = $this->h;
+ }
+ if ($x == -1) {
+ $x = $this->GetX();
+ } elseif ($x < 0) {
+ $x = 0;
+ } elseif ($x > $this->w) {
+ $x = $this->w;
+ }
+ if (empty($page)) {
+ $page = $this->PageNo();
+ if (empty($page)) {
+ return;
+ }
+ }
+ $this->outlines[] = array('t' => $txt, 'l' => $level, 'x' => $x, 'y' => $y, 'p' => $page, 's' => strtoupper($style), 'c' => $color, 'u' => $link);
+ }
+
+ /**
+ * Sort bookmarks for page and key.
+ * @protected
+ * @since 5.9.119 (2011-09-19)
+ */
+ protected function sortBookmarks() {
+ // get sorting columns
+ $outline_p = array();
+ $outline_y = array();
+ foreach ($this->outlines as $key => $row) {
+ $outline_p[$key] = $row['p'];
+ $outline_k[$key] = $key;
+ }
+ // sort outlines by page and original position
+ array_multisort($outline_p, SORT_NUMERIC, SORT_ASC, $outline_k, SORT_NUMERIC, SORT_ASC, $this->outlines);
+ }
+
+ /**
+ * Create a bookmark PDF string.
+ * @protected
+ * @author Olivier Plathey, Nicola Asuni
+ * @since 2.1.002 (2008-02-12)
+ */
+ protected function _putbookmarks() {
+ $nb = count($this->outlines);
+ if ($nb == 0) {
+ return;
+ }
+ // sort bookmarks
+ $this->sortBookmarks();
+ $lru = array();
+ $level = 0;
+ foreach ($this->outlines as $i => $o) {
+ if ($o['l'] > 0) {
+ $parent = $lru[($o['l'] - 1)];
+ //Set parent and last pointers
+ $this->outlines[$i]['parent'] = $parent;
+ $this->outlines[$parent]['last'] = $i;
+ if ($o['l'] > $level) {
+ //Level increasing: set first pointer
+ $this->outlines[$parent]['first'] = $i;
+ }
+ } else {
+ $this->outlines[$i]['parent'] = $nb;
+ }
+ if (($o['l'] <= $level) AND ($i > 0)) {
+ //Set prev and next pointers
+ $prev = $lru[$o['l']];
+ $this->outlines[$prev]['next'] = $i;
+ $this->outlines[$i]['prev'] = $prev;
+ }
+ $lru[$o['l']] = $i;
+ $level = $o['l'];
+ }
+ //Outline items
+ $n = $this->n + 1;
+ $nltags = '/<br[\s]?\/>|<\/(blockquote|dd|dl|div|dt|h1|h2|h3|h4|h5|h6|hr|li|ol|p|pre|ul|tcpdf|table|tr|td)>/si';
+ foreach ($this->outlines as $i => $o) {
+ $oid = $this->_newobj();
+ // covert HTML title to string
+ $title = preg_replace($nltags, "\n", $o['t']);
+ $title = preg_replace("/[\r]+/si", '', $title);
+ $title = preg_replace("/[\n]+/si", "\n", $title);
+ $title = strip_tags($title);
+ $title = $this->stringTrim($title);
+ $out = '<</Title '.$this->_textstring($title, $oid);
+ $out .= ' /Parent '.($n + $o['parent']).' 0 R';
+ if (isset($o['prev'])) {
+ $out .= ' /Prev '.($n + $o['prev']).' 0 R';
+ }
+ if (isset($o['next'])) {
+ $out .= ' /Next '.($n + $o['next']).' 0 R';
+ }
+ if (isset($o['first'])) {
+ $out .= ' /First '.($n + $o['first']).' 0 R';
+ }
+ if (isset($o['last'])) {
+ $out .= ' /Last '.($n + $o['last']).' 0 R';
+ }
+ if (isset($o['u']) AND !empty($o['u'])) {
+ // link
+ if (is_string($o['u'])) {
+ if ($o['u'][0] == '#') {
+ // internal destination
+ $out .= ' /Dest /'.TCPDF_STATIC::encodeNameObject(substr($o['u'], 1));
+ } elseif ($o['u'][0] == '%') {
+ // embedded PDF file
+ $filename = basename(substr($o['u'], 1));
+ $out .= ' /A <</S /GoToE /D [0 /Fit] /NewWindow true /T << /R /C /P '.($o['p'] - 1).' /A '.$this->embeddedfiles[$filename]['a'].' >> >>';
+ } elseif ($o['u'][0] == '*') {
+ // embedded generic file
+ $filename = basename(substr($o['u'], 1));
+ $jsa = 'var D=event.target.doc;var MyData=D.dataObjects;for (var i in MyData) if (MyData[i].path=="'.$filename.'") D.exportDataObject( { cName : MyData[i].name, nLaunch : 2});';
+ $out .= ' /A <</S /JavaScript /JS '.$this->_textstring($jsa, $oid).'>>';
+ } else {
+ // external URI link
+ $out .= ' /A <</S /URI /URI '.$this->_datastring($this->unhtmlentities($o['u']), $oid).'>>';
+ }
+ } elseif (isset($this->links[$o['u']])) {
+ // internal link ID
+ $l = $this->links[$o['u']];
+ if (isset($this->page_obj_id[($l[0])])) {
+ $out .= sprintf(' /Dest [%u 0 R /XYZ 0 %F null]', $this->page_obj_id[($l[0])], ($this->pagedim[$l[0]]['h'] - ($l[1] * $this->k)));
+ }
+ }
+ } elseif (isset($this->page_obj_id[($o['p'])])) {
+ // link to a page
+ $out .= ' '.sprintf('/Dest [%u 0 R /XYZ %F %F null]', $this->page_obj_id[($o['p'])], ($o['x'] * $this->k), ($this->pagedim[$o['p']]['h'] - ($o['y'] * $this->k)));
+ }
+ // set font style
+ $style = 0;
+ if (!empty($o['s'])) {
+ // bold
+ if (strpos($o['s'], 'B') !== false) {
+ $style |= 2;
+ }
+ // oblique
+ if (strpos($o['s'], 'I') !== false) {
+ $style |= 1;
+ }
+ }
+ $out .= sprintf(' /F %d', $style);
+ // set bookmark color
+ if (isset($o['c']) AND is_array($o['c']) AND (count($o['c']) == 3)) {
+ $color = array_values($o['c']);
+ $out .= sprintf(' /C [%F %F %F]', ($color[0] / 255), ($color[1] / 255), ($color[2] / 255));
+ } else {
+ // black
+ $out .= ' /C [0.0 0.0 0.0]';
+ }
+ $out .= ' /Count 0'; // normally closed item
+ $out .= ' >>';
+ $out .= "\n".'endobj';
+ $this->_out($out);
+ }
+ //Outline root
+ $this->OutlineRoot = $this->_newobj();
+ $this->_out('<< /Type /Outlines /First '.$n.' 0 R /Last '.($n + $lru[0]).' 0 R >>'."\n".'endobj');
+ }
+
+ // --- JAVASCRIPT ------------------------------------------------------
+
+ /**
+ * Adds a javascript
+ * @param $script (string) Javascript code
+ * @public
+ * @author Johannes Güntert, Nicola Asuni
+ * @since 2.1.002 (2008-02-12)
+ */
+ public function IncludeJS($script) {
+ $this->javascript .= $script;
+ }
+
+ /**
+ * Adds a javascript object and return object ID
+ * @param $script (string) Javascript code
+ * @param $onload (boolean) if true executes this object when opening the document
+ * @return int internal object ID
+ * @public
+ * @author Nicola Asuni
+ * @since 4.8.000 (2009-09-07)
+ */
+ public function addJavascriptObject($script, $onload=false) {
+ if ($this->pdfa_mode) {
+ // javascript is not allowed in PDF/A mode
+ return false;
+ }
+ ++$this->n;
+ $this->js_objects[$this->n] = array('n' => $this->n, 'js' => $script, 'onload' => $onload);
+ return $this->n;
+ }
+
+ /**
+ * Create a javascript PDF string.
+ * @protected
+ * @author Johannes Güntert, Nicola Asuni
+ * @since 2.1.002 (2008-02-12)
+ */
+ protected function _putjavascript() {
+ if ($this->pdfa_mode OR (empty($this->javascript) AND empty($this->js_objects))) {
+ return;
+ }
+ if (strpos($this->javascript, 'this.addField') > 0) {
+ if (!$this->ur['enabled']) {
+ //$this->setUserRights();
+ }
+ // the following two lines are used to avoid form fields duplication after saving
+ // The addField method only works when releasing user rights (UR3)
+ $jsa = sprintf("ftcpdfdocsaved=this.addField('%s','%s',%d,[%F,%F,%F,%F]);", 'tcpdfdocsaved', 'text', 0, 0, 1, 0, 1);
+ $jsb = "getField('tcpdfdocsaved').value='saved';";
+ $this->javascript = $jsa."\n".$this->javascript."\n".$jsb;
+ }
+ // name tree for javascript
+ $this->n_js = '<< /Names [';
+ if (!empty($this->javascript)) {
+ $this->n_js .= ' (EmbeddedJS) '.($this->n + 1).' 0 R';
+ }
+ if (!empty($this->js_objects)) {
+ foreach ($this->js_objects as $key => $val) {
+ if ($val['onload']) {
+ $this->n_js .= ' (JS'.$key.') '.$key.' 0 R';
+ }
+ }
+ }
+ $this->n_js .= ' ] >>';
+ // default Javascript object
+ if (!empty($this->javascript)) {
+ $obj_id = $this->_newobj();
+ $out = '<< /S /JavaScript';
+ $out .= ' /JS '.$this->_textstring($this->javascript, $obj_id);
+ $out .= ' >>';
+ $out .= "\n".'endobj';
+ $this->_out($out);
+ }
+ // additional Javascript objects
+ if (!empty($this->js_objects)) {
+ foreach ($this->js_objects as $key => $val) {
+ $out = $this->_getobj($key)."\n".' << /S /JavaScript /JS '.$this->_textstring($val['js'], $key).' >>'."\n".'endobj';
+ $this->_out($out);
+ }
+ }
+ }
+
+ /**
+ * Adds a javascript form field.
+ * @param $type (string) field type
+ * @param $name (string) field name
+ * @param $x (int) horizontal position
+ * @param $y (int) vertical position
+ * @param $w (int) width
+ * @param $h (int) height
+ * @param $prop (array) javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
+ * @protected
+ * @author Denis Van Nuffelen, Nicola Asuni
+ * @since 2.1.002 (2008-02-12)
+ */
+ protected function _addfield($type, $name, $x, $y, $w, $h, $prop) {
+ if ($this->rtl) {
+ $x = $x - $w;
+ }
+ // the followind avoid fields duplication after saving the document
+ $this->javascript .= "if (getField('tcpdfdocsaved').value != 'saved') {";
+ $k = $this->k;
+ $this->javascript .= sprintf("f".$name."=this.addField('%s','%s',%u,[%F,%F,%F,%F]);", $name, $type, $this->PageNo()-1, $x*$k, ($this->h-$y)*$k+1, ($x+$w)*$k, ($this->h-$y-$h)*$k+1)."\n";
+ $this->javascript .= 'f'.$name.'.textSize='.$this->FontSizePt.";\n";
+ while (list($key, $val) = each($prop)) {
+ if (strcmp(substr($key, -5), 'Color') == 0) {
+ $val = TCPDF_COLORS::_JScolor($val);
+ } else {
+ $val = "'".$val."'";
+ }
+ $this->javascript .= 'f'.$name.'.'.$key.'='.$val.";\n";
+ }
+ if ($this->rtl) {
+ $this->x -= $w;
+ } else {
+ $this->x += $w;
+ }
+ $this->javascript .= '}';
+ }
+
+ // --- FORM FIELDS -----------------------------------------------------
+
+
+
+ /**
+ * Set default properties for form fields.
+ * @param $prop (array) javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
+ * @public
+ * @author Nicola Asuni
+ * @since 4.8.000 (2009-09-06)
+ */
+ public function setFormDefaultProp($prop=array()) {
+ $this->default_form_prop = $prop;
+ }
+
+ /**
+ * Return the default properties for form fields.
+ * @return array $prop javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
+ * @public
+ * @author Nicola Asuni
+ * @since 4.8.000 (2009-09-06)
+ */
+ public function getFormDefaultProp() {
+ return $this->default_form_prop;
+ }
+
+ /**
+ * Creates a text field
+ * @param $name (string) field name
+ * @param $w (float) Width of the rectangle
+ * @param $h (float) Height of the rectangle
+ * @param $prop (array) javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
+ * @param $opt (array) annotation parameters. Possible values are described on official PDF32000_2008 reference.
+ * @param $x (float) Abscissa of the upper-left corner of the rectangle
+ * @param $y (float) Ordinate of the upper-left corner of the rectangle
+ * @param $js (boolean) if true put the field using JavaScript (requires Acrobat Writer to be rendered).
+ * @public
+ * @author Nicola Asuni
+ * @since 4.8.000 (2009-09-07)
+ */
+ public function TextField($name, $w, $h, $prop=array(), $opt=array(), $x='', $y='', $js=false) {
+ if ($x === '') {
+ $x = $this->x;
+ }
+ if ($y === '') {
+ $y = $this->y;
+ }
+ // check page for no-write regions and adapt page margins if necessary
+ list($x, $y) = $this->checkPageRegions($h, $x, $y);
+ if ($js) {
+ $this->_addfield('text', $name, $x, $y, $w, $h, $prop);
+ return;
+ }
+ // get default style
+ $prop = array_merge($this->getFormDefaultProp(), $prop);
+ // get annotation data
+ $popt = TCPDF_STATIC::getAnnotOptFromJSProp($prop, $this->spot_colors, $this->rtl);
+ // set default appearance stream
+ $this->annotation_fonts[$this->CurrentFont['fontkey']] = $this->CurrentFont['i'];
+ $fontstyle = sprintf('/F%d %F Tf %s', $this->CurrentFont['i'], $this->FontSizePt, $this->TextColor);
+ $popt['da'] = $fontstyle;
+ // build appearance stream
+ $popt['ap'] = array();
+ $popt['ap']['n'] = '/Tx BMC q '.$fontstyle.' ';
+ $text = '';
+ if (isset($prop['value']) AND !empty($prop['value'])) {
+ $text = $prop['value'];
+ } elseif (isset($opt['v']) AND !empty($opt['v'])) {
+ $text = $opt['v'];
+ }
+ $tmpid = $this->startTemplate($w, $h, false);
+ $align = '';
+ if (isset($popt['q'])) {
+ switch ($popt['q']) {
+ case 0: {
+ $align = 'L';
+ break;
+ }
+ case 1: {
+ $align = 'C';
+ break;
+ }
+ case 2: {
+ $align = 'R';
+ break;
+ }
+ default: {
+ $align = '';
+ break;
+ }
+ }
+ }
+ $this->MultiCell($w, $h, $text, 0, $align, false, 0, 0, 0, true, 0, false, true, 0, 'T', false);
+ $this->endTemplate();
+ --$this->n;
+ $popt['ap']['n'] .= $this->xobjects[$tmpid]['outdata'];
+ unset($this->xobjects[$tmpid]);
+ $popt['ap']['n'] .= 'Q EMC';
+ // merge options
+ $opt = array_merge($popt, $opt);
+ // remove some conflicting options
+ unset($opt['bs']);
+ // set remaining annotation data
+ $opt['Subtype'] = 'Widget';
+ $opt['ft'] = 'Tx';
+ $opt['t'] = $name;
+ // Additional annotation's parameters (check _putannotsobj() method):
+ //$opt['f']
+ //$opt['as']
+ //$opt['bs']
+ //$opt['be']
+ //$opt['c']
+ //$opt['border']
+ //$opt['h']
+ //$opt['mk'];
+ //$opt['mk']['r']
+ //$opt['mk']['bc'];
+ //$opt['mk']['bg'];
+ unset($opt['mk']['ca']);
+ unset($opt['mk']['rc']);
+ unset($opt['mk']['ac']);
+ unset($opt['mk']['i']);
+ unset($opt['mk']['ri']);
+ unset($opt['mk']['ix']);
+ unset($opt['mk']['if']);
+ //$opt['mk']['if']['sw'];
+ //$opt['mk']['if']['s'];
+ //$opt['mk']['if']['a'];
+ //$opt['mk']['if']['fb'];
+ unset($opt['mk']['tp']);
+ //$opt['tu']
+ //$opt['tm']
+ //$opt['ff']
+ //$opt['v']
+ //$opt['dv']
+ //$opt['a']
+ //$opt['aa']
+ //$opt['q']
+ $this->Annotation($x, $y, $w, $h, $name, $opt, 0);
+ if ($this->rtl) {
+ $this->x -= $w;
+ } else {
+ $this->x += $w;
+ }
+ }
+
+ /**
+ * Creates a RadioButton field.
+ * @param $name (string) Field name.
+ * @param $w (int) Width of the radio button.
+ * @param $prop (array) Javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
+ * @param $opt (array) Annotation parameters. Possible values are described on official PDF32000_2008 reference.
+ * @param $onvalue (string) Value to be returned if selected.
+ * @param $checked (boolean) Define the initial state.
+ * @param $x (float) Abscissa of the upper-left corner of the rectangle
+ * @param $y (float) Ordinate of the upper-left corner of the rectangle
+ * @param $js (boolean) If true put the field using JavaScript (requires Acrobat Writer to be rendered).
+ * @public
+ * @author Nicola Asuni
+ * @since 4.8.000 (2009-09-07)
+ */
+ public function RadioButton($name, $w, $prop=array(), $opt=array(), $onvalue='On', $checked=false, $x='', $y='', $js=false) {
+ if ($x === '') {
+ $x = $this->x;
+ }
+ if ($y === '') {
+ $y = $this->y;
+ }
+ // check page for no-write regions and adapt page margins if necessary
+ list($x, $y) = $this->checkPageRegions($w, $x, $y);
+ if ($js) {
+ $this->_addfield('radiobutton', $name, $x, $y, $w, $w, $prop);
+ return;
+ }
+ if (TCPDF_STATIC::empty_string($onvalue)) {
+ $onvalue = 'On';
+ }
+ if ($checked) {
+ $defval = $onvalue;
+ } else {
+ $defval = 'Off';
+ }
+ // set font
+ $font = 'zapfdingbats';
+ if ($this->pdfa_mode) {
+ // all fonts must be embedded
+ $font = 'pdfa'.$font;
+ }
+ $this->AddFont($font);
+ $tmpfont = $this->getFontBuffer($font);
+ // set data for parent group
+ if (!isset($this->radiobutton_groups[$this->page])) {
+ $this->radiobutton_groups[$this->page] = array();
+ }
+ if (!isset($this->radiobutton_groups[$this->page][$name])) {
+ $this->radiobutton_groups[$this->page][$name] = array();
+ ++$this->n;
+ $this->radiobutton_groups[$this->page][$name]['n'] = $this->n;
+ $this->radio_groups[] = $this->n;
+ }
+ $kid = ($this->n + 1);
+ // save object ID to be added on Kids entry on parent object
+ $this->radiobutton_groups[$this->page][$name][] = array('kid' => $kid, 'def' => $defval);
+ // get default style
+ $prop = array_merge($this->getFormDefaultProp(), $prop);
+ $prop['NoToggleToOff'] = 'true';
+ $prop['Radio'] = 'true';
+ $prop['borderStyle'] = 'inset';
+ // get annotation data
+ $popt = TCPDF_STATIC::getAnnotOptFromJSProp($prop, $this->spot_colors, $this->rtl);
+ // set additional default options
+ $this->annotation_fonts[$tmpfont['fontkey']] = $tmpfont['i'];
+ $fontstyle = sprintf('/F%d %F Tf %s', $tmpfont['i'], $this->FontSizePt, $this->TextColor);
+ $popt['da'] = $fontstyle;
+ // build appearance stream
+ $popt['ap'] = array();
+ $popt['ap']['n'] = array();
+ $fx = ((($w - $this->getAbsFontMeasure($tmpfont['cw'][108])) / 2) * $this->k);
+ $fy = (($w - ((($tmpfont['desc']['Ascent'] - $tmpfont['desc']['Descent']) * $this->FontSizePt / 1000) / $this->k)) * $this->k);
+ $popt['ap']['n'][$onvalue] = sprintf('q %s BT /F%d %F Tf %F %F Td ('.chr(108).') Tj ET Q', $this->TextColor, $tmpfont['i'], $this->FontSizePt, $fx, $fy);
+ $popt['ap']['n']['Off'] = sprintf('q %s BT /F%d %F Tf %F %F Td ('.chr(109).') Tj ET Q', $this->TextColor, $tmpfont['i'], $this->FontSizePt, $fx, $fy);
+ if (!isset($popt['mk'])) {
+ $popt['mk'] = array();
+ }
+ $popt['mk']['ca'] = '(l)';
+ // merge options
+ $opt = array_merge($popt, $opt);
+ // set remaining annotation data
+ $opt['Subtype'] = 'Widget';
+ $opt['ft'] = 'Btn';
+ if ($checked) {
+ $opt['v'] = array('/'.$onvalue);
+ $opt['as'] = $onvalue;
+ } else {
+ $opt['as'] = 'Off';
+ }
+ // store readonly flag
+ if (!isset($this->radiobutton_groups[$this->page][$name]['#readonly#'])) {
+ $this->radiobutton_groups[$this->page][$name]['#readonly#'] = false;
+ }
+ $this->radiobutton_groups[$this->page][$name]['#readonly#'] |= ($opt['f'] & 64);
+ $this->Annotation($x, $y, $w, $w, $name, $opt, 0);
+ if ($this->rtl) {
+ $this->x -= $w;
+ } else {
+ $this->x += $w;
+ }
+ }
+
+ /**
+ * Creates a List-box field
+ * @param $name (string) field name
+ * @param $w (int) width
+ * @param $h (int) height
+ * @param $values (array) array containing the list of values.
+ * @param $prop (array) javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
+ * @param $opt (array) annotation parameters. Possible values are described on official PDF32000_2008 reference.
+ * @param $x (float) Abscissa of the upper-left corner of the rectangle
+ * @param $y (float) Ordinate of the upper-left corner of the rectangle
+ * @param $js (boolean) if true put the field using JavaScript (requires Acrobat Writer to be rendered).
+ * @public
+ * @author Nicola Asuni
+ * @since 4.8.000 (2009-09-07)
+ */
+ public function ListBox($name, $w, $h, $values, $prop=array(), $opt=array(), $x='', $y='', $js=false) {
+ if ($x === '') {
+ $x = $this->x;
+ }
+ if ($y === '') {
+ $y = $this->y;
+ }
+ // check page for no-write regions and adapt page margins if necessary
+ list($x, $y) = $this->checkPageRegions($h, $x, $y);
+ if ($js) {
+ $this->_addfield('listbox', $name, $x, $y, $w, $h, $prop);
+ $s = '';
+ foreach ($values as $value) {
+ if (is_array($value)) {
+ $s .= ',[\''.addslashes($value[1]).'\',\''.addslashes($value[0]).'\']';
+ } else {
+ $s .= ',[\''.addslashes($value).'\',\''.addslashes($value).'\']';
+ }
+ }
+ $this->javascript .= 'f'.$name.'.setItems('.substr($s, 1).');'."\n";
+ return;
+ }
+ // get default style
+ $prop = array_merge($this->getFormDefaultProp(), $prop);
+ // get annotation data
+ $popt = TCPDF_STATIC::getAnnotOptFromJSProp($prop, $this->spot_colors, $this->rtl);
+ // set additional default values
+ $this->annotation_fonts[$this->CurrentFont['fontkey']] = $this->CurrentFont['i'];
+ $fontstyle = sprintf('/F%d %F Tf %s', $this->CurrentFont['i'], $this->FontSizePt, $this->TextColor);
+ $popt['da'] = $fontstyle;
+ // build appearance stream
+ $popt['ap'] = array();
+ $popt['ap']['n'] = '/Tx BMC q '.$fontstyle.' ';
+ $text = '';
+ foreach($values as $item) {
+ if (is_array($item)) {
+ $text .= $item[1]."\n";
+ } else {
+ $text .= $item."\n";
+ }
+ }
+ $tmpid = $this->startTemplate($w, $h, false);
+ $this->MultiCell($w, $h, $text, 0, '', false, 0, 0, 0, true, 0, false, true, 0, 'T', false);
+ $this->endTemplate();
+ --$this->n;
+ $popt['ap']['n'] .= $this->xobjects[$tmpid]['outdata'];
+ unset($this->xobjects[$tmpid]);
+ $popt['ap']['n'] .= 'Q EMC';
+ // merge options
+ $opt = array_merge($popt, $opt);
+ // set remaining annotation data
+ $opt['Subtype'] = 'Widget';
+ $opt['ft'] = 'Ch';
+ $opt['t'] = $name;
+ $opt['opt'] = $values;
+ unset($opt['mk']['ca']);
+ unset($opt['mk']['rc']);
+ unset($opt['mk']['ac']);
+ unset($opt['mk']['i']);
+ unset($opt['mk']['ri']);
+ unset($opt['mk']['ix']);
+ unset($opt['mk']['if']);
+ unset($opt['mk']['tp']);
+ $this->Annotation($x, $y, $w, $h, $name, $opt, 0);
+ if ($this->rtl) {
+ $this->x -= $w;
+ } else {
+ $this->x += $w;
+ }
+ }
+
+ /**
+ * Creates a Combo-box field
+ * @param $name (string) field name
+ * @param $w (int) width
+ * @param $h (int) height
+ * @param $values (array) array containing the list of values.
+ * @param $prop (array) javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
+ * @param $opt (array) annotation parameters. Possible values are described on official PDF32000_2008 reference.
+ * @param $x (float) Abscissa of the upper-left corner of the rectangle
+ * @param $y (float) Ordinate of the upper-left corner of the rectangle
+ * @param $js (boolean) if true put the field using JavaScript (requires Acrobat Writer to be rendered).
+ * @public
+ * @author Nicola Asuni
+ * @since 4.8.000 (2009-09-07)
+ */
+ public function ComboBox($name, $w, $h, $values, $prop=array(), $opt=array(), $x='', $y='', $js=false) {
+ if ($x === '') {
+ $x = $this->x;
+ }
+ if ($y === '') {
+ $y = $this->y;
+ }
+ // check page for no-write regions and adapt page margins if necessary
+ list($x, $y) = $this->checkPageRegions($h, $x, $y);
+ if ($js) {
+ $this->_addfield('combobox', $name, $x, $y, $w, $h, $prop);
+ $s = '';
+ foreach ($values as $value) {
+ if (is_array($value)) {
+ $s .= ',[\''.addslashes($value[1]).'\',\''.addslashes($value[0]).'\']';
+ } else {
+ $s .= ',[\''.addslashes($value).'\',\''.addslashes($value).'\']';
+ }
+ }
+ $this->javascript .= 'f'.$name.'.setItems('.substr($s, 1).');'."\n";
+ return;
+ }
+ // get default style
+ $prop = array_merge($this->getFormDefaultProp(), $prop);
+ $prop['Combo'] = true;
+ // get annotation data
+ $popt = TCPDF_STATIC::getAnnotOptFromJSProp($prop, $this->spot_colors, $this->rtl);
+ // set additional default options
+ $this->annotation_fonts[$this->CurrentFont['fontkey']] = $this->CurrentFont['i'];
+ $fontstyle = sprintf('/F%d %F Tf %s', $this->CurrentFont['i'], $this->FontSizePt, $this->TextColor);
+ $popt['da'] = $fontstyle;
+ // build appearance stream
+ $popt['ap'] = array();
+ $popt['ap']['n'] = '/Tx BMC q '.$fontstyle.' ';
+ $text = '';
+ foreach($values as $item) {
+ if (is_array($item)) {
+ $text .= $item[1]."\n";
+ } else {
+ $text .= $item."\n";
+ }
+ }
+ $tmpid = $this->startTemplate($w, $h, false);
+ $this->MultiCell($w, $h, $text, 0, '', false, 0, 0, 0, true, 0, false, true, 0, 'T', false);
+ $this->endTemplate();
+ --$this->n;
+ $popt['ap']['n'] .= $this->xobjects[$tmpid]['outdata'];
+ unset($this->xobjects[$tmpid]);
+ $popt['ap']['n'] .= 'Q EMC';
+ // merge options
+ $opt = array_merge($popt, $opt);
+ // set remaining annotation data
+ $opt['Subtype'] = 'Widget';
+ $opt['ft'] = 'Ch';
+ $opt['t'] = $name;
+ $opt['opt'] = $values;
+ unset($opt['mk']['ca']);
+ unset($opt['mk']['rc']);
+ unset($opt['mk']['ac']);
+ unset($opt['mk']['i']);
+ unset($opt['mk']['ri']);
+ unset($opt['mk']['ix']);
+ unset($opt['mk']['if']);
+ unset($opt['mk']['tp']);
+ $this->Annotation($x, $y, $w, $h, $name, $opt, 0);
+ if ($this->rtl) {
+ $this->x -= $w;
+ } else {
+ $this->x += $w;
+ }
+ }
+
+ /**
+ * Creates a CheckBox field
+ * @param $name (string) field name
+ * @param $w (int) width
+ * @param $checked (boolean) define the initial state.
+ * @param $prop (array) javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
+ * @param $opt (array) annotation parameters. Possible values are described on official PDF32000_2008 reference.
+ * @param $onvalue (string) value to be returned if selected.
+ * @param $x (float) Abscissa of the upper-left corner of the rectangle
+ * @param $y (float) Ordinate of the upper-left corner of the rectangle
+ * @param $js (boolean) if true put the field using JavaScript (requires Acrobat Writer to be rendered).
+ * @public
+ * @author Nicola Asuni
+ * @since 4.8.000 (2009-09-07)
+ */
+ public function CheckBox($name, $w, $checked=false, $prop=array(), $opt=array(), $onvalue='Yes', $x='', $y='', $js=false) {
+ if ($x === '') {
+ $x = $this->x;
+ }
+ if ($y === '') {
+ $y = $this->y;
+ }
+ // check page for no-write regions and adapt page margins if necessary
+ list($x, $y) = $this->checkPageRegions($w, $x, $y);
+ if ($js) {
+ $this->_addfield('checkbox', $name, $x, $y, $w, $w, $prop);
+ return;
+ }
+ if (!isset($prop['value'])) {
+ $prop['value'] = array('Yes');
+ }
+ // get default style
+ $prop = array_merge($this->getFormDefaultProp(), $prop);
+ $prop['borderStyle'] = 'inset';
+ // get annotation data
+ $popt = TCPDF_STATIC::getAnnotOptFromJSProp($prop, $this->spot_colors, $this->rtl);
+ // set additional default options
+ $font = 'zapfdingbats';
+ if ($this->pdfa_mode) {
+ // all fonts must be embedded
+ $font = 'pdfa'.$font;
+ }
+ $this->AddFont($font);
+ $tmpfont = $this->getFontBuffer($font);
+ $this->annotation_fonts[$tmpfont['fontkey']] = $tmpfont['i'];
+ $fontstyle = sprintf('/F%d %F Tf %s', $tmpfont['i'], $this->FontSizePt, $this->TextColor);
+ $popt['da'] = $fontstyle;
+ // build appearance stream
+ $popt['ap'] = array();
+ $popt['ap']['n'] = array();
+ $fx = ((($w - $this->getAbsFontMeasure($tmpfont['cw'][110])) / 2) * $this->k);
+ $fy = (($w - ((($tmpfont['desc']['Ascent'] - $tmpfont['desc']['Descent']) * $this->FontSizePt / 1000) / $this->k)) * $this->k);
+ $popt['ap']['n']['Yes'] = sprintf('q %s BT /F%d %F Tf %F %F Td ('.chr(110).') Tj ET Q', $this->TextColor, $tmpfont['i'], $this->FontSizePt, $fx, $fy);
+ $popt['ap']['n']['Off'] = sprintf('q %s BT /F%d %F Tf %F %F Td ('.chr(111).') Tj ET Q', $this->TextColor, $tmpfont['i'], $this->FontSizePt, $fx, $fy);
+ // merge options
+ $opt = array_merge($popt, $opt);
+ // set remaining annotation data
+ $opt['Subtype'] = 'Widget';
+ $opt['ft'] = 'Btn';
+ $opt['t'] = $name;
+ if (TCPDF_STATIC::empty_string($onvalue)) {
+ $onvalue = 'Yes';
+ }
+ $opt['opt'] = array($onvalue);
+ if ($checked) {
+ $opt['v'] = array('/Yes');
+ $opt['as'] = 'Yes';
+ } else {
+ $opt['v'] = array('/Off');
+ $opt['as'] = 'Off';
+ }
+ $this->Annotation($x, $y, $w, $w, $name, $opt, 0);
+ if ($this->rtl) {
+ $this->x -= $w;
+ } else {
+ $this->x += $w;
+ }
+ }
+
+ /**
+ * Creates a button field
+ * @param $name (string) field name
+ * @param $w (int) width
+ * @param $h (int) height
+ * @param $caption (string) caption.
+ * @param $action (mixed) action triggered by pressing the button. Use a string to specify a javascript action. Use an array to specify a form action options as on section 12.7.5 of PDF32000_2008.
+ * @param $prop (array) javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
+ * @param $opt (array) annotation parameters. Possible values are described on official PDF32000_2008 reference.
+ * @param $x (float) Abscissa of the upper-left corner of the rectangle
+ * @param $y (float) Ordinate of the upper-left corner of the rectangle
+ * @param $js (boolean) if true put the field using JavaScript (requires Acrobat Writer to be rendered).
+ * @public
+ * @author Nicola Asuni
+ * @since 4.8.000 (2009-09-07)
+ */
+ public function Button($name, $w, $h, $caption, $action, $prop=array(), $opt=array(), $x='', $y='', $js=false) {
+ if ($x === '') {
+ $x = $this->x;
+ }
+ if ($y === '') {
+ $y = $this->y;
+ }
+ // check page for no-write regions and adapt page margins if necessary
+ list($x, $y) = $this->checkPageRegions($h, $x, $y);
+ if ($js) {
+ $this->_addfield('button', $name, $this->x, $this->y, $w, $h, $prop);
+ $this->javascript .= 'f'.$name.".buttonSetCaption('".addslashes($caption)."');\n";
+ $this->javascript .= 'f'.$name.".setAction('MouseUp','".addslashes($action)."');\n";
+ $this->javascript .= 'f'.$name.".highlight='push';\n";
+ $this->javascript .= 'f'.$name.".print=false;\n";
+ return;
+ }
+ // get default style
+ $prop = array_merge($this->getFormDefaultProp(), $prop);
+ $prop['Pushbutton'] = 'true';
+ $prop['highlight'] = 'push';
+ $prop['display'] = 'display.noPrint';
+ // get annotation data
+ $popt = TCPDF_STATIC::getAnnotOptFromJSProp($prop, $this->spot_colors, $this->rtl);
+ $this->annotation_fonts[$this->CurrentFont['fontkey']] = $this->CurrentFont['i'];
+ $fontstyle = sprintf('/F%d %F Tf %s', $this->CurrentFont['i'], $this->FontSizePt, $this->TextColor);
+ $popt['da'] = $fontstyle;
+ // build appearance stream
+ $popt['ap'] = array();
+ $popt['ap']['n'] = '/Tx BMC q '.$fontstyle.' ';
+ $tmpid = $this->startTemplate($w, $h, false);
+ $bw = (2 / $this->k); // border width
+ $border = array(
+ 'L' => array('width' => $bw, 'cap' => 'square', 'join' => 'miter', 'dash' => 0, 'color' => array(231)),
+ 'R' => array('width' => $bw, 'cap' => 'square', 'join' => 'miter', 'dash' => 0, 'color' => array(51)),
+ 'T' => array('width' => $bw, 'cap' => 'square', 'join' => 'miter', 'dash' => 0, 'color' => array(231)),
+ 'B' => array('width' => $bw, 'cap' => 'square', 'join' => 'miter', 'dash' => 0, 'color' => array(51)));
+ $this->SetFillColor(204);
+ $this->Cell($w, $h, $caption, $border, 0, 'C', true, '', 1, false, 'T', 'M');
+ $this->endTemplate();
+ --$this->n;
+ $popt['ap']['n'] .= $this->xobjects[$tmpid]['outdata'];
+ unset($this->xobjects[$tmpid]);
+ $popt['ap']['n'] .= 'Q EMC';
+ // set additional default options
+ if (!isset($popt['mk'])) {
+ $popt['mk'] = array();
+ }
+ $ann_obj_id = ($this->n + 1);
+ if (!empty($action) AND !is_array($action)) {
+ $ann_obj_id = ($this->n + 2);
+ }
+ $popt['mk']['ca'] = $this->_textstring($caption, $ann_obj_id);
+ $popt['mk']['rc'] = $this->_textstring($caption, $ann_obj_id);
+ $popt['mk']['ac'] = $this->_textstring($caption, $ann_obj_id);
+ // merge options
+ $opt = array_merge($popt, $opt);
+ // set remaining annotation data
+ $opt['Subtype'] = 'Widget';
+ $opt['ft'] = 'Btn';
+ $opt['t'] = $caption;
+ $opt['v'] = $name;
+ if (!empty($action)) {
+ if (is_array($action)) {
+ // form action options as on section 12.7.5 of PDF32000_2008.
+ $opt['aa'] = '/D <<';
+ $bmode = array('SubmitForm', 'ResetForm', 'ImportData');
+ foreach ($action AS $key => $val) {
+ if (($key == 'S') AND in_array($val, $bmode)) {
+ $opt['aa'] .= ' /S /'.$val;
+ } elseif (($key == 'F') AND (!empty($val))) {
+ $opt['aa'] .= ' /F '.$this->_datastring($val, $ann_obj_id);
+ } elseif (($key == 'Fields') AND is_array($val) AND !empty($val)) {
+ $opt['aa'] .= ' /Fields [';
+ foreach ($val AS $field) {
+ $opt['aa'] .= ' '.$this->_textstring($field, $ann_obj_id);
+ }
+ $opt['aa'] .= ']';
+ } elseif (($key == 'Flags')) {
+ $ff = 0;
+ if (is_array($val)) {
+ foreach ($val AS $flag) {
+ switch ($flag) {
+ case 'Include/Exclude': {
+ $ff += 1 << 0;
+ break;
+ }
+ case 'IncludeNoValueFields': {
+ $ff += 1 << 1;
+ break;
+ }
+ case 'ExportFormat': {
+ $ff += 1 << 2;
+ break;
+ }
+ case 'GetMethod': {
+ $ff += 1 << 3;
+ break;
+ }
+ case 'SubmitCoordinates': {
+ $ff += 1 << 4;
+ break;
+ }
+ case 'XFDF': {
+ $ff += 1 << 5;
+ break;
+ }
+ case 'IncludeAppendSaves': {
+ $ff += 1 << 6;
+ break;
+ }
+ case 'IncludeAnnotations': {
+ $ff += 1 << 7;
+ break;
+ }
+ case 'SubmitPDF': {
+ $ff += 1 << 8;
+ break;
+ }
+ case 'CanonicalFormat': {
+ $ff += 1 << 9;
+ break;
+ }
+ case 'ExclNonUserAnnots': {
+ $ff += 1 << 10;
+ break;
+ }
+ case 'ExclFKey': {
+ $ff += 1 << 11;
+ break;
+ }
+ case 'EmbedForm': {
+ $ff += 1 << 13;
+ break;
+ }
+ }
+ }
+ } else {
+ $ff = intval($val);
+ }
+ $opt['aa'] .= ' /Flags '.$ff;
+ }
+ }
+ $opt['aa'] .= ' >>';
+ } else {
+ // Javascript action or raw action command
+ $js_obj_id = $this->addJavascriptObject($action);
+ $opt['aa'] = '/D '.$js_obj_id.' 0 R';
+ }
+ }
+ $this->Annotation($x, $y, $w, $h, $name, $opt, 0);
+ if ($this->rtl) {
+ $this->x -= $w;
+ } else {
+ $this->x += $w;
+ }
+ }
+
+ // --- END FORMS FIELDS ------------------------------------------------
+
+ /**
+ * Add certification signature (DocMDP or UR3)
+ * You can set only one signature type
+ * @protected
+ * @author Nicola Asuni
+ * @since 4.6.008 (2009-05-07)
+ */
+ protected function _putsignature() {
+ if ((!$this->sign) OR (!isset($this->signature_data['cert_type']))) {
+ return;
+ }
+ $sigobjid = ($this->sig_obj_id + 1);
+ $out = $this->_getobj($sigobjid)."\n";
+ $out .= '<< /Type /Sig';
+ $out .= ' /Filter /Adobe.PPKLite';
+ $out .= ' /SubFilter /adbe.pkcs7.detached';
+ $out .= ' '.TCPDF_STATIC::$byterange_string;
+ $out .= ' /Contents<'.str_repeat('0', $this->signature_max_length).'>';
+ $out .= ' /Reference ['; // array of signature reference dictionaries
+ $out .= ' << /Type /SigRef';
+ if ($this->signature_data['cert_type'] > 0) {
+ $out .= ' /TransformMethod /DocMDP';
+ $out .= ' /TransformParams <<';
+ $out .= ' /Type /TransformParams';
+ $out .= ' /P '.$this->signature_data['cert_type'];
+ $out .= ' /V /1.2';
+ } else {
+ $out .= ' /TransformMethod /UR3';
+ $out .= ' /TransformParams <<';
+ $out .= ' /Type /TransformParams';
+ $out .= ' /V /2.2';
+ if (!TCPDF_STATIC::empty_string($this->ur['document'])) {
+ $out .= ' /Document['.$this->ur['document'].']';
+ }
+ if (!TCPDF_STATIC::empty_string($this->ur['form'])) {
+ $out .= ' /Form['.$this->ur['form'].']';
+ }
+ if (!TCPDF_STATIC::empty_string($this->ur['signature'])) {
+ $out .= ' /Signature['.$this->ur['signature'].']';
+ }
+ if (!TCPDF_STATIC::empty_string($this->ur['annots'])) {
+ $out .= ' /Annots['.$this->ur['annots'].']';
+ }
+ if (!TCPDF_STATIC::empty_string($this->ur['ef'])) {
+ $out .= ' /EF['.$this->ur['ef'].']';
+ }
+ if (!TCPDF_STATIC::empty_string($this->ur['formex'])) {
+ $out .= ' /FormEX['.$this->ur['formex'].']';
+ }
+ }
+ $out .= ' >>'; // close TransformParams
+ // optional digest data (values must be calculated and replaced later)
+ //$out .= ' /Data ********** 0 R';
+ //$out .= ' /DigestMethod/MD5';
+ //$out .= ' /DigestLocation[********** 34]';
+ //$out .= ' /DigestValue<********************************>';
+ $out .= ' >>';
+ $out .= ' ]'; // end of reference
+ if (isset($this->signature_data['info']['Name']) AND !TCPDF_STATIC::empty_string($this->signature_data['info']['Name'])) {
+ $out .= ' /Name '.$this->_textstring($this->signature_data['info']['Name'], $sigobjid);
+ }
+ if (isset($this->signature_data['info']['Location']) AND !TCPDF_STATIC::empty_string($this->signature_data['info']['Location'])) {
+ $out .= ' /Location '.$this->_textstring($this->signature_data['info']['Location'], $sigobjid);
+ }
+ if (isset($this->signature_data['info']['Reason']) AND !TCPDF_STATIC::empty_string($this->signature_data['info']['Reason'])) {
+ $out .= ' /Reason '.$this->_textstring($this->signature_data['info']['Reason'], $sigobjid);
+ }
+ if (isset($this->signature_data['info']['ContactInfo']) AND !TCPDF_STATIC::empty_string($this->signature_data['info']['ContactInfo'])) {
+ $out .= ' /ContactInfo '.$this->_textstring($this->signature_data['info']['ContactInfo'], $sigobjid);
+ }
+ $out .= ' /M '.$this->_datestring($sigobjid, $this->doc_modification_timestamp);
+ $out .= ' >>';
+ $out .= "\n".'endobj';
+ $this->_out($out);
+ }
+
+ /**
+ * Set User's Rights for PDF Reader
+ * WARNING: This is experimental and currently do not work.
+ * Check the PDF Reference 8.7.1 Transform Methods,
+ * Table 8.105 Entries in the UR transform parameters dictionary
+ * @param $enable (boolean) if true enable user's rights on PDF reader
+ * @param $document (string) Names specifying additional document-wide usage rights for the document. The only defined value is "/FullSave", which permits a user to save the document along with modified form and/or annotation data.
+ * @param $annots (string) Names specifying additional annotation-related usage rights for the document. Valid names in PDF 1.5 and later are /Create/Delete/Modify/Copy/Import/Export, which permit the user to perform the named operation on annotations.
+ * @param $form (string) Names specifying additional form-field-related usage rights for the document. Valid names are: /Add/Delete/FillIn/Import/Export/SubmitStandalone/SpawnTemplate
+ * @param $signature (string) Names specifying additional signature-related usage rights for the document. The only defined value is /Modify, which permits a user to apply a digital signature to an existing signature form field or clear a signed signature form field.
+ * @param $ef (string) Names specifying additional usage rights for named embedded files in the document. Valid names are /Create/Delete/Modify/Import, which permit the user to perform the named operation on named embedded files
+ Names specifying additional embedded-files-related usage rights for the document.
+ * @param $formex (string) Names specifying additional form-field-related usage rights. The only valid name is BarcodePlaintext, which permits text form field data to be encoded as a plaintext two-dimensional barcode.
+ * @public
+ * @author Nicola Asuni
+ * @since 2.9.000 (2008-03-26)
+ */
+ public function setUserRights(
+ $enable=true,
+ $document='/FullSave',
+ $annots='/Create/Delete/Modify/Copy/Import/Export',
+ $form='/Add/Delete/FillIn/Import/Export/SubmitStandalone/SpawnTemplate',
+ $signature='/Modify',
+ $ef='/Create/Delete/Modify/Import',
+ $formex='') {
+ $this->ur['enabled'] = $enable;
+ $this->ur['document'] = $document;
+ $this->ur['annots'] = $annots;
+ $this->ur['form'] = $form;
+ $this->ur['signature'] = $signature;
+ $this->ur['ef'] = $ef;
+ $this->ur['formex'] = $formex;
+ if (!$this->sign) {
+ $this->setSignature('', '', '', '', 0, array());
+ }
+ }
+
+ /**
+ * Enable document signature (requires the OpenSSL Library).
+ * The digital signature improve document authenticity and integrity and allows o enable extra features on Acrobat Reader.
+ * To create self-signed signature: openssl req -x509 -nodes -days 365000 -newkey rsa:1024 -keyout tcpdf.crt -out tcpdf.crt
+ * To export crt to p12: openssl pkcs12 -export -in tcpdf.crt -out tcpdf.p12
+ * To convert pfx certificate to pem: openssl pkcs12 -in tcpdf.pfx -out tcpdf.crt -nodes
+ * @param $signing_cert (mixed) signing certificate (string or filename prefixed with 'file://')
+ * @param $private_key (mixed) private key (string or filename prefixed with 'file://')
+ * @param $private_key_password (string) password
+ * @param $extracerts (string) specifies the name of a file containing a bunch of extra certificates to include in the signature which can for example be used to help the recipient to verify the certificate that you used.
+ * @param $cert_type (int) The access permissions granted for this document. Valid values shall be: 1 = No changes to the document shall be permitted; any change to the document shall invalidate the signature; 2 = Permitted changes shall be filling in forms, instantiating page templates, and signing; other changes shall invalidate the signature; 3 = Permitted changes shall be the same as for 2, as well as annotation creation, deletion, and modification; other changes shall invalidate the signature.
+ * @param $info (array) array of option information: Name, Location, Reason, ContactInfo.
+ * @public
+ * @author Nicola Asuni
+ * @since 4.6.005 (2009-04-24)
+ */
+ public function setSignature($signing_cert='', $private_key='', $private_key_password='', $extracerts='', $cert_type=2, $info=array()) {
+ // to create self-signed signature: openssl req -x509 -nodes -days 365000 -newkey rsa:1024 -keyout tcpdf.crt -out tcpdf.crt
+ // to export crt to p12: openssl pkcs12 -export -in tcpdf.crt -out tcpdf.p12
+ // to convert pfx certificate to pem: openssl
+ // OpenSSL> pkcs12 -in <cert.pfx> -out <cert.crt> -nodes
+ $this->sign = true;
+ ++$this->n;
+ $this->sig_obj_id = $this->n; // signature widget
+ ++$this->n; // signature object ($this->sig_obj_id + 1)
+ $this->signature_data = array();
+ if (strlen($signing_cert) == 0) {
+ $this->Error('Please provide a certificate file and password!');
+ }
+ if (strlen($private_key) == 0) {
+ $private_key = $signing_cert;
+ }
+ $this->signature_data['signcert'] = $signing_cert;
+ $this->signature_data['privkey'] = $private_key;
+ $this->signature_data['password'] = $private_key_password;
+ $this->signature_data['extracerts'] = $extracerts;
+ $this->signature_data['cert_type'] = $cert_type;
+ $this->signature_data['info'] = $info;
+ }
+
+ /**
+ * Set the digital signature appearance (a cliccable rectangle area to get signature properties)
+ * @param $x (float) Abscissa of the upper-left corner.
+ * @param $y (float) Ordinate of the upper-left corner.
+ * @param $w (float) Width of the signature area.
+ * @param $h (float) Height of the signature area.
+ * @param $page (int) option page number (if < 0 the current page is used).
+ * @param $name (string) Name of the signature.
+ * @public
+ * @author Nicola Asuni
+ * @since 5.3.011 (2010-06-17)
+ */
+ public function setSignatureAppearance($x=0, $y=0, $w=0, $h=0, $page=-1, $name='') {
+ $this->signature_appearance = $this->getSignatureAppearanceArray($x, $y, $w, $h, $page, $name);
+ }
+
+ /**
+ * Add an empty digital signature appearance (a cliccable rectangle area to get signature properties)
+ * @param $x (float) Abscissa of the upper-left corner.
+ * @param $y (float) Ordinate of the upper-left corner.
+ * @param $w (float) Width of the signature area.
+ * @param $h (float) Height of the signature area.
+ * @param $page (int) option page number (if < 0 the current page is used).
+ * @param $name (string) Name of the signature.
+ * @public
+ * @author Nicola Asuni
+ * @since 5.9.101 (2011-07-06)
+ */
+ public function addEmptySignatureAppearance($x=0, $y=0, $w=0, $h=0, $page=-1, $name='') {
+ ++$this->n;
+ $this->empty_signature_appearance[] = array('objid' => $this->n) + $this->getSignatureAppearanceArray($x, $y, $w, $h, $page, $name);
+ }
+
+ /**
+ * Get the array that defines the signature appearance (page and rectangle coordinates).
+ * @param $x (float) Abscissa of the upper-left corner.
+ * @param $y (float) Ordinate of the upper-left corner.
+ * @param $w (float) Width of the signature area.
+ * @param $h (float) Height of the signature area.
+ * @param $page (int) option page number (if < 0 the current page is used).
+ * @param $name (string) Name of the signature.
+ * @return (array) Array defining page and rectangle coordinates of signature appearance.
+ * @protected
+ * @author Nicola Asuni
+ * @since 5.9.101 (2011-07-06)
+ */
+ protected function getSignatureAppearanceArray($x=0, $y=0, $w=0, $h=0, $page=-1, $name='') {
+ $sigapp = array();
+ if (($page < 1) OR ($page > $this->numpages)) {
+ $sigapp['page'] = $this->page;
+ } else {
+ $sigapp['page'] = intval($page);
+ }
+ if (empty($name)) {
+ $sigapp['name'] = 'Signature';
+ } else {
+ $sigapp['name'] = $name;
+ }
+ $a = $x * $this->k;
+ $b = $this->pagedim[($sigapp['page'])]['h'] - (($y + $h) * $this->k);
+ $c = $w * $this->k;
+ $d = $h * $this->k;
+ $sigapp['rect'] = sprintf('%F %F %F %F', $a, $b, ($a + $c), ($b + $d));
+ return $sigapp;
+ }
+
+ /**
+ * Create a new page group.
+ * NOTE: call this function before calling AddPage()
+ * @param $page (int) starting group page (leave empty for next page).
+ * @public
+ * @since 3.0.000 (2008-03-27)
+ */
+ public function startPageGroup($page='') {
+ if (empty($page)) {
+ $page = $this->page + 1;
+ }
+ $this->newpagegroup[$page] = sizeof($this->newpagegroup) + 1;
+ }
+
+ /**
+ * Set the starting page number.
+ * @param $num (int) Starting page number.
+ * @since 5.9.093 (2011-06-16)
+ * @public
+ */
+ public function setStartingPageNumber($num=1) {
+ $this->starting_page_number = max(0, intval($num));
+ }
+
+ /**
+ * Returns the string alias used right align page numbers.
+ * If the current font is unicode type, the returned string wil contain an additional open curly brace.
+ * @return string
+ * @since 5.9.099 (2011-06-27)
+ * @public
+ */
+ public function getAliasRightShift() {
+ // calculate aproximatively the ratio between widths of aliases and replacements.
+ $ref = '{'.TCPDF_STATIC::$alias_right_shift.'}{'.TCPDF_STATIC::$alias_tot_pages.'}{'.TCPDF_STATIC::$alias_num_page.'}';
+ $rep = str_repeat(' ', $this->GetNumChars($ref));
+ $wrep = $this->GetStringWidth($rep);
+ if ($wrep > 0) {
+ $wdiff = max(1, ($this->GetStringWidth($ref) / $wrep));
+ } else {
+ $wdiff = 1;
+ }
+ $sdiff = sprintf('%F', $wdiff);
+ $alias = TCPDF_STATIC::$alias_right_shift.$sdiff.'}';
+ if ($this->isUnicodeFont()) {
+ $alias = '{'.$alias;
+ }
+ return $alias;
+ }
+
+ /**
+ * Returns the string alias used for the total number of pages.
+ * If the current font is unicode type, the returned string is surrounded by additional curly braces.
+ * This alias will be replaced by the total number of pages in the document.
+ * @return string
+ * @since 4.0.018 (2008-08-08)
+ * @public
+ */
+ public function getAliasNbPages() {
+ if ($this->isUnicodeFont()) {
+ return '{'.TCPDF_STATIC::$alias_tot_pages.'}';
+ }
+ return TCPDF_STATIC::$alias_tot_pages;
+ }
+
+ /**
+ * Returns the string alias used for the page number.
+ * If the current font is unicode type, the returned string is surrounded by additional curly braces.
+ * This alias will be replaced by the page number.
+ * @return string
+ * @since 4.5.000 (2009-01-02)
+ * @public
+ */
+ public function getAliasNumPage() {
+ if ($this->isUnicodeFont()) {
+ return '{'.TCPDF_STATIC::$alias_num_page.'}';
+ }
+ return TCPDF_STATIC::$alias_num_page;
+ }
+
+ /**
+ * Return the alias for the total number of pages in the current page group.
+ * If the current font is unicode type, the returned string is surrounded by additional curly braces.
+ * This alias will be replaced by the total number of pages in this group.
+ * @return alias of the current page group
+ * @public
+ * @since 3.0.000 (2008-03-27)
+ */
+ public function getPageGroupAlias() {
+ if ($this->isUnicodeFont()) {
+ return '{'.TCPDF_STATIC::$alias_group_tot_pages.'}';
+ }
+ return TCPDF_STATIC::$alias_group_tot_pages;
+ }
+
+ /**
+ * Return the alias for the page number on the current page group.
+ * If the current font is unicode type, the returned string is surrounded by additional curly braces.
+ * This alias will be replaced by the page number (relative to the belonging group).
+ * @return alias of the current page group
+ * @public
+ * @since 4.5.000 (2009-01-02)
+ */
+ public function getPageNumGroupAlias() {
+ if ($this->isUnicodeFont()) {
+ return '{'.TCPDF_STATIC::$alias_group_num_page.'}';
+ }
+ return TCPDF_STATIC::$alias_group_num_page;
+ }
+
+ /**
+ * Return the current page in the group.
+ * @return current page in the group
+ * @public
+ * @since 3.0.000 (2008-03-27)
+ */
+ public function getGroupPageNo() {
+ return $this->pagegroups[$this->currpagegroup];
+ }
+
+ /**
+ * Returns the current group page number formatted as a string.
+ * @public
+ * @since 4.3.003 (2008-11-18)
+ * @see PaneNo(), formatPageNumber()
+ */
+ public function getGroupPageNoFormatted() {
+ return TCPDF_STATIC::formatPageNumber($this->getGroupPageNo());
+ }
+
+ /**
+ * Returns the current page number formatted as a string.
+ * @public
+ * @since 4.2.005 (2008-11-06)
+ * @see PaneNo(), formatPageNumber()
+ */
+ public function PageNoFormatted() {
+ return TCPDF_STATIC::formatPageNumber($this->PageNo());
+ }
+
+ /**
+ * Put pdf layers.
+ * @protected
+ * @since 3.0.000 (2008-03-27)
+ */
+ protected function _putocg() {
+ if (empty($this->pdflayers)) {
+ return;
+ }
+ foreach ($this->pdflayers as $key => $layer) {
+ $this->pdflayers[$key]['objid'] = $this->_newobj();
+ $out = '<< /Type /OCG';
+ $out .= ' /Name '.$this->_textstring($layer['name'], $this->pdflayers[$key]['objid']);
+ $out .= ' /Usage <<';
+ $out .= ' /Print <</PrintState /'.($layer['print']?'ON':'OFF').'>>';
+ $out .= ' /View <</ViewState /'.($layer['view']?'ON':'OFF').'>>';
+ $out .= ' >> >>';
+ $out .= "\n".'endobj';
+ $this->_out($out);
+ }
+ }
+
+ /**
+ * Start a new pdf layer.
+ * @param $name (string) Layer name (only a-z letters and numbers). Leave empty for automatic name.
+ * @param $print (boolean) Set to true to print this layer.
+ * @param $view (boolean) Set to true to view this layer.
+ * @public
+ * @since 5.9.102 (2011-07-13)
+ */
+ public function startLayer($name='', $print=true, $view=true) {
+ if ($this->state != 2) {
+ return;
+ }
+ $layer = sprintf('LYR%03d', (count($this->pdflayers) + 1));
+ if (empty($name)) {
+ $name = $layer;
+ } else {
+ $name = preg_replace('/[^a-zA-Z0-9_\-]/', '', $name);
+ }
+ $this->pdflayers[] = array('layer' => $layer, 'name' => $name, 'print' => $print, 'view' => $view);
+ $this->openMarkedContent = true;
+ $this->_out('/OC /'.$layer.' BDC');
+ }
+
+ /**
+ * End the current PDF layer.
+ * @public
+ * @since 5.9.102 (2011-07-13)
+ */
+ public function endLayer() {
+ if ($this->state != 2) {
+ return;
+ }
+ if ($this->openMarkedContent) {
+ // close existing open marked-content layer
+ $this->_out('EMC');
+ $this->openMarkedContent = false;
+ }
+ }
+
+ /**
+ * Set the visibility of the successive elements.
+ * This can be useful, for instance, to put a background
+ * image or color that will show on screen but won't print.
+ * @param $v (string) visibility mode. Legal values are: all, print, screen or view.
+ * @public
+ * @since 3.0.000 (2008-03-27)
+ */
+ public function setVisibility($v) {
+ if ($this->state != 2) {
+ return;
+ }
+ $this->endLayer();
+ switch($v) {
+ case 'print': {
+ $this->startLayer('Print', true, false);
+ break;
+ }
+ case 'view':
+ case 'screen': {
+ $this->startLayer('View', false, true);
+ break;
+ }
+ case 'all': {
+ $this->_out('');
+ break;
+ }
+ default: {
+ $this->Error('Incorrect visibility: '.$v);
+ break;
+ }
+ }
+ }
+
+ /**
+ * Add transparency parameters to the current extgstate
+ * @param $parms (array) parameters
+ * @return the number of extgstates
+ * @protected
+ * @since 3.0.000 (2008-03-27)
+ */
+ protected function addExtGState($parms) {
+ if ($this->pdfa_mode) {
+ // transparencies are not allowed in PDF/A mode
+ return;
+ }
+ // check if this ExtGState already exist
+ foreach ($this->extgstates as $i => $ext) {
+ if ($ext['parms'] == $parms) {
+ if ($this->inxobj) {
+ // we are inside an XObject template
+ $this->xobjects[$this->xobjid]['extgstates'][$i] = $ext;
+ }
+ // return reference to existing ExtGState
+ return $i;
+ }
+ }
+ $n = (count($this->extgstates) + 1);
+ $this->extgstates[$n] = array('parms' => $parms);
+ if ($this->inxobj) {
+ // we are inside an XObject template
+ $this->xobjects[$this->xobjid]['extgstates'][$n] = $this->extgstates[$n];
+ }
+ return $n;
+ }
+
+ /**
+ * Add an extgstate
+ * @param $gs (array) extgstate
+ * @protected
+ * @since 3.0.000 (2008-03-27)
+ */
+ protected function setExtGState($gs) {
+ if ($this->pdfa_mode OR ($this->state != 2)) {
+ // transparency is not allowed in PDF/A mode
+ return;
+ }
+ $this->_out(sprintf('/GS%d gs', $gs));
+ }
+
+ /**
+ * Put extgstates for object transparency
+ * @protected
+ * @since 3.0.000 (2008-03-27)
+ */
+ protected function _putextgstates() {
+ foreach ($this->extgstates as $i => $ext) {
+ $this->extgstates[$i]['n'] = $this->_newobj();
+ $out = '<< /Type /ExtGState';
+ foreach ($ext['parms'] as $k => $v) {
+ if (is_float($v)) {
+ $v = sprintf('%F', $v);
+ } elseif ($v === true) {
+ $v = 'true';
+ } elseif ($v === false) {
+ $v = 'false';
+ }
+ $out .= ' /'.$k.' '.$v;
+ }
+ $out .= ' >>';
+ $out .= "\n".'endobj';
+ $this->_out($out);
+ }
+ }
+
+ /**
+ * Set overprint mode for stroking (OP) and non-stroking (op) painting operations.
+ * (Check the "Entries in a Graphics State Parameter Dictionary" on PDF 32000-1:2008).
+ * @param $stroking (boolean) If true apply overprint for stroking operations.
+ * @param $nonstroking (boolean) If true apply overprint for painting operations other than stroking.
+ * @param $mode (integer) Overprint mode: (0 = each source colour component value replaces the value previously painted for the corresponding device colorant; 1 = a tint value of 0.0 for a source colour component shall leave the corresponding component of the previously painted colour unchanged).
+ * @public
+ * @since 5.9.152 (2012-03-23)
+ */
+ public function setOverprint($stroking=true, $nonstroking='', $mode=0) {
+ if ($this->state != 2) {
+ return;
+ }
+ $stroking = $stroking ? true : false;
+ if (TCPDF_STATIC::empty_string($nonstroking)) {
+ // default value if not set
+ $nonstroking = $stroking;
+ } else {
+ $nonstroking = $nonstroking ? true : false;
+ }
+ if (($mode != 0) AND ($mode != 1)) {
+ $mode = 0;
+ }
+ $this->overprint = array('OP' => $stroking, 'op' => $nonstroking, 'OPM' => $mode);
+ $gs = $this->addExtGState($this->overprint);
+ $this->setExtGState($gs);
+ }
+
+ /**
+ * Get the overprint mode array (OP, op, OPM).
+ * (Check the "Entries in a Graphics State Parameter Dictionary" on PDF 32000-1:2008).
+ * @return array.
+ * @public
+ * @since 5.9.152 (2012-03-23)
+ */
+ public function getOverprint() {
+ return $this->overprint;
+ }
+
+ /**
+ * Set alpha for stroking (CA) and non-stroking (ca) operations.
+ * @param $stroking (float) Alpha value for stroking operations: real value from 0 (transparent) to 1 (opaque).
+ * @param $bm (string) blend mode, one of the following: Normal, Multiply, Screen, Overlay, Darken, Lighten, ColorDodge, ColorBurn, HardLight, SoftLight, Difference, Exclusion, Hue, Saturation, Color, Luminosity
+ * @param $nonstroking (float) Alpha value for non-stroking operations: real value from 0 (transparent) to 1 (opaque).
+ * @param $ais (boolean)
+ * @public
+ * @since 3.0.000 (2008-03-27)
+ */
+ public function setAlpha($stroking=1, $bm='Normal', $nonstroking='', $ais=false) {
+ if ($this->pdfa_mode) {
+ // transparency is not allowed in PDF/A mode
+ return;
+ }
+ $stroking = floatval($stroking);
+ if (TCPDF_STATIC::empty_string($nonstroking)) {
+ // default value if not set
+ $nonstroking = $stroking;
+ } else {
+ $nonstroking = floatval($nonstroking);
+ }
+ if ($bm[0] == '/') {
+ // remove trailing slash
+ $bm = substr($bm, 1);
+ }
+ if (!in_array($bm, array('Normal', 'Multiply', 'Screen', 'Overlay', 'Darken', 'Lighten', 'ColorDodge', 'ColorBurn', 'HardLight', 'SoftLight', 'Difference', 'Exclusion', 'Hue', 'Saturation', 'Color', 'Luminosity'))) {
+ $bm = 'Normal';
+ }
+ $ais = $ais ? true : false;
+ $this->alpha = array('CA' => $stroking, 'ca' => $nonstroking, 'BM' => '/'.$bm, 'AIS' => $ais);
+ $gs = $this->addExtGState($this->alpha);
+ $this->setExtGState($gs);
+ }
+
+ /**
+ * Get the alpha mode array (CA, ca, BM, AIS).
+ * (Check the "Entries in a Graphics State Parameter Dictionary" on PDF 32000-1:2008).
+ * @return array.
+ * @public
+ * @since 5.9.152 (2012-03-23)
+ */
+ public function getAlpha() {
+ return $this->alpha;
+ }
+
+ /**
+ * Set the default JPEG compression quality (1-100)
+ * @param $quality (int) JPEG quality, integer between 1 and 100
+ * @public
+ * @since 3.0.000 (2008-03-27)
+ */
+ public function setJPEGQuality($quality) {
+ if (($quality < 1) OR ($quality > 100)) {
+ $quality = 75;
+ }
+ $this->jpeg_quality = intval($quality);
+ }
+
+ /**
+ * Set the default number of columns in a row for HTML tables.
+ * @param $cols (int) number of columns
+ * @public
+ * @since 3.0.014 (2008-06-04)
+ */
+ public function setDefaultTableColumns($cols=4) {
+ $this->default_table_columns = intval($cols);
+ }
+
+ /**
+ * Set the height of the cell (line height) respect the font height.
+ * @param $h (int) cell proportion respect font height (typical value = 1.25).
+ * @public
+ * @since 3.0.014 (2008-06-04)
+ */
+ public function setCellHeightRatio($h) {
+ $this->cell_height_ratio = $h;
+ }
+
+ /**
+ * return the height of cell repect font height.
+ * @public
+ * @since 4.0.012 (2008-07-24)
+ */
+ public function getCellHeightRatio() {
+ return $this->cell_height_ratio;
+ }
+
+ /**
+ * Set the PDF version (check PDF reference for valid values).
+ * @param $version (string) PDF document version.
+ * @public
+ * @since 3.1.000 (2008-06-09)
+ */
+ public function setPDFVersion($version='1.7') {
+ if ($this->pdfa_mode) {
+ // PDF/A mode
+ $this->PDFVersion = '1.4';
+ } else {
+ $this->PDFVersion = $version;
+ }
+ }
+
+ /**
+ * Set the viewer preferences dictionary controlling the way the document is to be presented on the screen or in print.
+ * (see Section 8.1 of PDF reference, "Viewer Preferences").
+ * <ul><li>HideToolbar boolean (Optional) A flag specifying whether to hide the viewer application's tool bars when the document is active. Default value: false.</li><li>HideMenubar boolean (Optional) A flag specifying whether to hide the viewer application's menu bar when the document is active. Default value: false.</li><li>HideWindowUI boolean (Optional) A flag specifying whether to hide user interface elements in the document's window (such as scroll bars and navigation controls), leaving only the document's contents displayed. Default value: false.</li><li>FitWindow boolean (Optional) A flag specifying whether to resize the document's window to fit the size of the first displayed page. Default value: false.</li><li>CenterWindow boolean (Optional) A flag specifying whether to position the document's window in the center of the screen. Default value: false.</li><li>DisplayDocTitle boolean (Optional; PDF 1.4) A flag specifying whether the window's title bar should display the document title taken from the Title entry of the document information dictionary (see Section 10.2.1, "Document Information Dictionary"). If false, the title bar should instead display the name of the PDF file containing the document. Default value: false.</li><li>NonFullScreenPageMode name (Optional) The document's page mode, specifying how to display the document on exiting full-screen mode:<ul><li>UseNone Neither document outline nor thumbnail images visible</li><li>UseOutlines Document outline visible</li><li>UseThumbs Thumbnail images visible</li><li>UseOC Optional content group panel visible</li></ul>This entry is meaningful only if the value of the PageMode entry in the catalog dictionary (see Section 3.6.1, "Document Catalog") is FullScreen; it is ignored otherwise. Default value: UseNone.</li><li>ViewArea name (Optional; PDF 1.4) The name of the page boundary representing the area of a page to be displayed when viewing the document on the screen. Valid values are (see Section 10.10.1, "Page Boundaries").:<ul><li>MediaBox</li><li>CropBox (default)</li><li>BleedBox</li><li>TrimBox</li><li>ArtBox</li></ul></li><li>ViewClip name (Optional; PDF 1.4) The name of the page boundary to which the contents of a page are to be clipped when viewing the document on the screen. Valid values are (see Section 10.10.1, "Page Boundaries").:<ul><li>MediaBox</li><li>CropBox (default)</li><li>BleedBox</li><li>TrimBox</li><li>ArtBox</li></ul></li><li>PrintArea name (Optional; PDF 1.4) The name of the page boundary representing the area of a page to be rendered when printing the document. Valid values are (see Section 10.10.1, "Page Boundaries").:<ul><li>MediaBox</li><li>CropBox (default)</li><li>BleedBox</li><li>TrimBox</li><li>ArtBox</li></ul></li><li>PrintClip name (Optional; PDF 1.4) The name of the page boundary to which the contents of a page are to be clipped when printing the document. Valid values are (see Section 10.10.1, "Page Boundaries").:<ul><li>MediaBox</li><li>CropBox (default)</li><li>BleedBox</li><li>TrimBox</li><li>ArtBox</li></ul></li><li>PrintScaling name (Optional; PDF 1.6) The page scaling option to be selected when a print dialog is displayed for this document. Valid values are: <ul><li>None, which indicates that the print dialog should reflect no page scaling</li><li>AppDefault (default), which indicates that applications should use the current print scaling</li></ul></li><li>Duplex name (Optional; PDF 1.7) The paper handling option to use when printing the file from the print dialog. The following values are valid:<ul><li>Simplex - Print single-sided</li><li>DuplexFlipShortEdge - Duplex and flip on the short edge of the sheet</li><li>DuplexFlipLongEdge - Duplex and flip on the long edge of the sheet</li></ul>Default value: none</li><li>PickTrayByPDFSize boolean (Optional; PDF 1.7) A flag specifying whether the PDF page size is used to select the input paper tray. This setting influences only the preset values used to populate the print dialog presented by a PDF viewer application. If PickTrayByPDFSize is true, the check box in the print dialog associated with input paper tray is checked. Note: This setting has no effect on Mac OS systems, which do not provide the ability to pick the input tray by size.</li><li>PrintPageRange array (Optional; PDF 1.7) The page numbers used to initialize the print dialog box when the file is printed. The first page of the PDF file is denoted by 1. Each pair consists of the first and last pages in the sub-range. An odd number of integers causes this entry to be ignored. Negative numbers cause the entire array to be ignored. Default value: as defined by PDF viewer application</li><li>NumCopies integer (Optional; PDF 1.7) The number of copies to be printed when the print dialog is opened for this file. Supported values are the integers 2 through 5. Values outside this range are ignored. Default value: as defined by PDF viewer application, but typically 1</li></ul>
+ * @param $preferences (array) array of options.
+ * @author Nicola Asuni
+ * @public
+ * @since 3.1.000 (2008-06-09)
+ */
+ public function setViewerPreferences($preferences) {
+ $this->viewer_preferences = $preferences;
+ }
+
+ /**
+ * Paints color transition registration bars
+ * @param $x (float) abscissa of the top left corner of the rectangle.
+ * @param $y (float) ordinate of the top left corner of the rectangle.
+ * @param $w (float) width of the rectangle.
+ * @param $h (float) height of the rectangle.
+ * @param $transition (boolean) if true prints tcolor transitions to white.
+ * @param $vertical (boolean) if true prints bar vertically.
+ * @param $colors (string) colors to print separated by comma. Valid values are: A,W,R,G,B,C,M,Y,K,RGB,CMYK,ALL,ALLSPOT,<SPOT_COLOR_NAME>. Where: A = grayscale black, W = grayscale white, R = RGB red, G RGB green, B RGB blue, C = CMYK cyan, M = CMYK magenta, Y = CMYK yellow, K = CMYK key/black, RGB = RGB registration color, CMYK = CMYK registration color, ALL = Spot registration color, ALLSPOT = print all defined spot colors, <SPOT_COLOR_NAME> = name of the spot color to print.
+ * @author Nicola Asuni
+ * @since 4.9.000 (2010-03-26)
+ * @public
+ */
+ public function colorRegistrationBar($x, $y, $w, $h, $transition=true, $vertical=false, $colors='A,R,G,B,C,M,Y,K') {
+ if (strpos($colors, 'ALLSPOT') !== false) {
+ // expand spot colors
+ $spot_colors = '';
+ foreach ($this->spot_colors as $spot_color_name => $v) {
+ $spot_colors .= ','.$spot_color_name;
+ }
+ if (!empty($spot_colors)) {
+ $spot_colors = substr($spot_colors, 1);
+ $colors = str_replace('ALLSPOT', $spot_colors, $colors);
+ } else {
+ $colors = str_replace('ALLSPOT', 'NONE', $colors);
+ }
+ }
+ $bars = explode(',', $colors);
+ $numbars = count($bars); // number of bars to print
+ if ($numbars <= 0) {
+ return;
+ }
+ // set bar measures
+ if ($vertical) {
+ $coords = array(0, 0, 0, 1);
+ $wb = $w / $numbars; // bar width
+ $hb = $h; // bar height
+ $xd = $wb; // delta x
+ $yd = 0; // delta y
+ } else {
+ $coords = array(1, 0, 0, 0);
+ $wb = $w; // bar width
+ $hb = $h / $numbars; // bar height
+ $xd = 0; // delta x
+ $yd = $hb; // delta y
+ }
+ $xb = $x;
+ $yb = $y;
+ foreach ($bars as $col) {
+ switch ($col) {
+ // set transition colors
+ case 'A': { // BLACK (GRAYSCALE)
+ $col_a = array(255);
+ $col_b = array(0);
+ break;
+ }
+ case 'W': { // WHITE (GRAYSCALE)
+ $col_a = array(0);
+ $col_b = array(255);
+ break;
+ }
+ case 'R': { // RED (RGB)
+ $col_a = array(255,255,255);
+ $col_b = array(255,0,0);
+ break;
+ }
+ case 'G': { // GREEN (RGB)
+ $col_a = array(255,255,255);
+ $col_b = array(0,255,0);
+ break;
+ }
+ case 'B': { // BLUE (RGB)
+ $col_a = array(255,255,255);
+ $col_b = array(0,0,255);
+ break;
+ }
+ case 'C': { // CYAN (CMYK)
+ $col_a = array(0,0,0,0);
+ $col_b = array(100,0,0,0);
+ break;
+ }
+ case 'M': { // MAGENTA (CMYK)
+ $col_a = array(0,0,0,0);
+ $col_b = array(0,100,0,0);
+ break;
+ }
+ case 'Y': { // YELLOW (CMYK)
+ $col_a = array(0,0,0,0);
+ $col_b = array(0,0,100,0);
+ break;
+ }
+ case 'K': { // KEY - BLACK (CMYK)
+ $col_a = array(0,0,0,0);
+ $col_b = array(0,0,0,100);
+ break;
+ }
+ case 'RGB': { // BLACK REGISTRATION (RGB)
+ $col_a = array(255,255,255);
+ $col_b = array(0,0,0);
+ break;
+ }
+ case 'CMYK': { // BLACK REGISTRATION (CMYK)
+ $col_a = array(0,0,0,0);
+ $col_b = array(100,100,100,100);
+ break;
+ }
+ case 'ALL': { // SPOT COLOR REGISTRATION
+ $col_a = array(0,0,0,0,'None');
+ $col_b = array(100,100,100,100,'All');
+ break;
+ }
+ case 'NONE': { // SKIP THIS COLOR
+ $col_a = array(0,0,0,0,'None');
+ $col_b = array(0,0,0,0,'None');
+ break;
+ }
+ default: { // SPECIFIC SPOT COLOR NAME
+ $col_a = array(0,0,0,0,'None');
+ $col_b = TCPDF_COLORS::getSpotColor($col, $this->spot_colors);
+ if ($col_b === false) {
+ // in case of error defaults to the registration color
+ $col_b = array(100,100,100,100,'All');
+ }
+ break;
+ }
+ }
+ if ($col != 'NONE') {
+ if ($transition) {
+ // color gradient
+ $this->LinearGradient($xb, $yb, $wb, $hb, $col_a, $col_b, $coords);
+ } else {
+ $this->SetFillColorArray($col_b);
+ // colored rectangle
+ $this->Rect($xb, $yb, $wb, $hb, 'F', array());
+ }
+ $xb += $xd;
+ $yb += $yd;
+ }
+ }
+ }
+
+ /**
+ * Paints crop marks.
+ * @param $x (float) abscissa of the crop mark center.
+ * @param $y (float) ordinate of the crop mark center.
+ * @param $w (float) width of the crop mark.
+ * @param $h (float) height of the crop mark.
+ * @param $type (string) type of crop mark, one symbol per type separated by comma: T = TOP, F = BOTTOM, L = LEFT, R = RIGHT, TL = A = TOP-LEFT, TR = B = TOP-RIGHT, BL = C = BOTTOM-LEFT, BR = D = BOTTOM-RIGHT.
+ * @param $color (array) crop mark color (default spot registration color).
+ * @author Nicola Asuni
+ * @since 4.9.000 (2010-03-26)
+ * @public
+ */
+ public function cropMark($x, $y, $w, $h, $type='T,R,B,L', $color=array(100,100,100,100,'All')) {
+ $this->SetLineStyle(array('width' => (0.5 / $this->k), 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => $color));
+ $type = strtoupper($type);
+ $type = preg_replace('/[^A-Z\-\,]*/', '', $type);
+ // split type in single components
+ $type = str_replace('-', ',', $type);
+ $type = str_replace('TL', 'T,L', $type);
+ $type = str_replace('TR', 'T,R', $type);
+ $type = str_replace('BL', 'F,L', $type);
+ $type = str_replace('BR', 'F,R', $type);
+ $type = str_replace('A', 'T,L', $type);
+ $type = str_replace('B', 'T,R', $type);
+ $type = str_replace('T,RO', 'BO', $type);
+ $type = str_replace('C', 'F,L', $type);
+ $type = str_replace('D', 'F,R', $type);
+ $crops = explode(',', strtoupper($type));
+ // remove duplicates
+ $crops = array_unique($crops);
+ $dw = ($w / 4); // horizontal space to leave before the intersection point
+ $dh = ($h / 4); // vertical space to leave before the intersection point
+ foreach ($crops as $crop) {
+ switch ($crop) {
+ case 'T':
+ case 'TOP': {
+ $x1 = $x;
+ $y1 = ($y - $h);
+ $x2 = $x;
+ $y2 = ($y - $dh);
+ break;
+ }
+ case 'F':
+ case 'BOTTOM': {
+ $x1 = $x;
+ $y1 = ($y + $dh);
+ $x2 = $x;
+ $y2 = ($y + $h);
+ break;
+ }
+ case 'L':
+ case 'LEFT': {
+ $x1 = ($x - $w);
+ $y1 = $y;
+ $x2 = ($x - $dw);
+ $y2 = $y;
+ break;
+ }
+ case 'R':
+ case 'RIGHT': {
+ $x1 = ($x + $dw);
+ $y1 = $y;
+ $x2 = ($x + $w);
+ $y2 = $y;
+ break;
+ }
+ }
+ $this->Line($x1, $y1, $x2, $y2);
+ }
+ }
+
+ /**
+ * Paints a registration mark
+ * @param $x (float) abscissa of the registration mark center.
+ * @param $y (float) ordinate of the registration mark center.
+ * @param $r (float) radius of the crop mark.
+ * @param $double (boolean) if true print two concentric crop marks.
+ * @param $cola (array) crop mark color (default spot registration color 'All').
+ * @param $colb (array) second crop mark color (default spot registration color 'None').
+ * @author Nicola Asuni
+ * @since 4.9.000 (2010-03-26)
+ * @public
+ */
+ public function registrationMark($x, $y, $r, $double=false, $cola=array(100,100,100,100,'All'), $colb=array(0,0,0,0,'None')) {
+ $line_style = array('width' => max((0.5 / $this->k),($r / 30)), 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => $cola);
+ $this->SetFillColorArray($cola);
+ $this->PieSector($x, $y, $r, 90, 180, 'F');
+ $this->PieSector($x, $y, $r, 270, 360, 'F');
+ $this->Circle($x, $y, $r, 0, 360, 'C', $line_style, array(), 8);
+ if ($double) {
+ $ri = $r * 0.5;
+ $this->SetFillColorArray($colb);
+ $this->PieSector($x, $y, $ri, 90, 180, 'F');
+ $this->PieSector($x, $y, $ri, 270, 360, 'F');
+ $this->SetFillColorArray($cola);
+ $this->PieSector($x, $y, $ri, 0, 90, 'F');
+ $this->PieSector($x, $y, $ri, 180, 270, 'F');
+ $this->Circle($x, $y, $ri, 0, 360, 'C', $line_style, array(), 8);
+ }
+ }
+
+ /**
+ * Paints a CMYK registration mark
+ * @param $x (float) abscissa of the registration mark center.
+ * @param $y (float) ordinate of the registration mark center.
+ * @param $r (float) radius of the crop mark.
+ * @author Nicola Asuni
+ * @since 6.0.038 (2013-09-30)
+ * @public
+ */
+ public function registrationMarkCMYK($x, $y, $r) {
+ // line width
+ $lw = max((0.5 / $this->k),($r / 8));
+ // internal radius
+ $ri = ($r * 0.6);
+ // external radius
+ $re = ($r * 1.3);
+ // Cyan
+ $this->SetFillColorArray(array(100,0,0,0));
+ $this->PieSector($x, $y, $ri, 270, 360, 'F');
+ // Magenta
+ $this->SetFillColorArray(array(0,100,0,0));
+ $this->PieSector($x, $y, $ri, 0, 90, 'F');
+ // Yellow
+ $this->SetFillColorArray(array(0,0,100,0));
+ $this->PieSector($x, $y, $ri, 90, 180, 'F');
+ // Key - black
+ $this->SetFillColorArray(array(0,0,0,100));
+ $this->PieSector($x, $y, $ri, 180, 270, 'F');
+ // registration color
+ $line_style = array('width' => $lw, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(100,100,100,100,'All'));
+ $this->SetFillColorArray(array(100,100,100,100,'All'));
+ // external circle
+ $this->Circle($x, $y, $r, 0, 360, 'C', $line_style, array(), 8);
+ // cross lines
+ $this->Line($x, ($y - $re), $x, ($y - $ri));
+ $this->Line($x, ($y + $ri), $x, ($y + $re));
+ $this->Line(($x - $re), $y, ($x - $ri), $y);
+ $this->Line(($x + $ri), $y, ($x + $re), $y);
+ }
+
+ /**
+ * Paints a linear colour gradient.
+ * @param $x (float) abscissa of the top left corner of the rectangle.
+ * @param $y (float) ordinate of the top left corner of the rectangle.
+ * @param $w (float) width of the rectangle.
+ * @param $h (float) height of the rectangle.
+ * @param $col1 (array) first color (Grayscale, RGB or CMYK components).
+ * @param $col2 (array) second color (Grayscale, RGB or CMYK components).
+ * @param $coords (array) array of the form (x1, y1, x2, y2) which defines the gradient vector (see linear_gradient_coords.jpg). The default value is from left to right (x1=0, y1=0, x2=1, y2=0).
+ * @author Andreas Würmser, Nicola Asuni
+ * @since 3.1.000 (2008-06-09)
+ * @public
+ */
+ public function LinearGradient($x, $y, $w, $h, $col1=array(), $col2=array(), $coords=array(0,0,1,0)) {
+ $this->Clip($x, $y, $w, $h);
+ $this->Gradient(2, $coords, array(array('color' => $col1, 'offset' => 0, 'exponent' => 1), array('color' => $col2, 'offset' => 1, 'exponent' => 1)), array(), false);
+ }
+
+ /**
+ * Paints a radial colour gradient.
+ * @param $x (float) abscissa of the top left corner of the rectangle.
+ * @param $y (float) ordinate of the top left corner of the rectangle.
+ * @param $w (float) width of the rectangle.
+ * @param $h (float) height of the rectangle.
+ * @param $col1 (array) first color (Grayscale, RGB or CMYK components).
+ * @param $col2 (array) second color (Grayscale, RGB or CMYK components).
+ * @param $coords (array) array of the form (fx, fy, cx, cy, r) where (fx, fy) is the starting point of the gradient with color1, (cx, cy) is the center of the circle with color2, and r is the radius of the circle (see radial_gradient_coords.jpg). (fx, fy) should be inside the circle, otherwise some areas will not be defined.
+ * @author Andreas Würmser, Nicola Asuni
+ * @since 3.1.000 (2008-06-09)
+ * @public
+ */
+ public function RadialGradient($x, $y, $w, $h, $col1=array(), $col2=array(), $coords=array(0.5,0.5,0.5,0.5,1)) {
+ $this->Clip($x, $y, $w, $h);
+ $this->Gradient(3, $coords, array(array('color' => $col1, 'offset' => 0, 'exponent' => 1), array('color' => $col2, 'offset' => 1, 'exponent' => 1)), array(), false);
+ }
+
+ /**
+ * Paints a coons patch mesh.
+ * @param $x (float) abscissa of the top left corner of the rectangle.
+ * @param $y (float) ordinate of the top left corner of the rectangle.
+ * @param $w (float) width of the rectangle.
+ * @param $h (float) height of the rectangle.
+ * @param $col1 (array) first color (lower left corner) (RGB components).
+ * @param $col2 (array) second color (lower right corner) (RGB components).
+ * @param $col3 (array) third color (upper right corner) (RGB components).
+ * @param $col4 (array) fourth color (upper left corner) (RGB components).
+ * @param $coords (array) <ul><li>for one patch mesh: array(float x1, float y1, .... float x12, float y12): 12 pairs of coordinates (normally from 0 to 1) which specify the Bezier control points that define the patch. First pair is the lower left edge point, next is its right control point (control point 2). Then the other points are defined in the order: control point 1, edge point, control point 2 going counter-clockwise around the patch. Last (x12, y12) is the first edge point's left control point (control point 1).</li><li>for two or more patch meshes: array[number of patches]: arrays with the following keys for each patch: f: where to put that patch (0 = first patch, 1, 2, 3 = right, top and left of precedent patch - I didn't figure this out completely - just try and error ;-) points: 12 pairs of coordinates of the Bezier control points as above for the first patch, 8 pairs of coordinates for the following patches, ignoring the coordinates already defined by the precedent patch (I also didn't figure out the order of these - also: try and see what's happening) colors: must be 4 colors for the first patch, 2 colors for the following patches</li></ul>
+ * @param $coords_min (array) minimum value used by the coordinates. If a coordinate's value is smaller than this it will be cut to coords_min. default: 0
+ * @param $coords_max (array) maximum value used by the coordinates. If a coordinate's value is greater than this it will be cut to coords_max. default: 1
+ * @param $antialias (boolean) A flag indicating whether to filter the shading function to prevent aliasing artifacts.
+ * @author Andreas Würmser, Nicola Asuni
+ * @since 3.1.000 (2008-06-09)
+ * @public
+ */
+ public function CoonsPatchMesh($x, $y, $w, $h, $col1=array(), $col2=array(), $col3=array(), $col4=array(), $coords=array(0.00,0.0,0.33,0.00,0.67,0.00,1.00,0.00,1.00,0.33,1.00,0.67,1.00,1.00,0.67,1.00,0.33,1.00,0.00,1.00,0.00,0.67,0.00,0.33), $coords_min=0, $coords_max=1, $antialias=false) {
+ if ($this->pdfa_mode OR ($this->state != 2)) {
+ return;
+ }
+ $this->Clip($x, $y, $w, $h);
+ $n = count($this->gradients) + 1;
+ $this->gradients[$n] = array();
+ $this->gradients[$n]['type'] = 6; //coons patch mesh
+ $this->gradients[$n]['coords'] = array();
+ $this->gradients[$n]['antialias'] = $antialias;
+ $this->gradients[$n]['colors'] = array();
+ $this->gradients[$n]['transparency'] = false;
+ //check the coords array if it is the simple array or the multi patch array
+ if (!isset($coords[0]['f'])) {
+ //simple array -> convert to multi patch array
+ if (!isset($col1[1])) {
+ $col1[1] = $col1[2] = $col1[0];
+ }
+ if (!isset($col2[1])) {
+ $col2[1] = $col2[2] = $col2[0];
+ }
+ if (!isset($col3[1])) {
+ $col3[1] = $col3[2] = $col3[0];
+ }
+ if (!isset($col4[1])) {
+ $col4[1] = $col4[2] = $col4[0];
+ }
+ $patch_array[0]['f'] = 0;
+ $patch_array[0]['points'] = $coords;
+ $patch_array[0]['colors'][0]['r'] = $col1[0];
+ $patch_array[0]['colors'][0]['g'] = $col1[1];
+ $patch_array[0]['colors'][0]['b'] = $col1[2];
+ $patch_array[0]['colors'][1]['r'] = $col2[0];
+ $patch_array[0]['colors'][1]['g'] = $col2[1];
+ $patch_array[0]['colors'][1]['b'] = $col2[2];
+ $patch_array[0]['colors'][2]['r'] = $col3[0];
+ $patch_array[0]['colors'][2]['g'] = $col3[1];
+ $patch_array[0]['colors'][2]['b'] = $col3[2];
+ $patch_array[0]['colors'][3]['r'] = $col4[0];
+ $patch_array[0]['colors'][3]['g'] = $col4[1];
+ $patch_array[0]['colors'][3]['b'] = $col4[2];
+ } else {
+ //multi patch array
+ $patch_array = $coords;
+ }
+ $bpcd = 65535; //16 bits per coordinate
+ //build the data stream
+ $this->gradients[$n]['stream'] = '';
+ $count_patch = count($patch_array);
+ for ($i=0; $i < $count_patch; ++$i) {
+ $this->gradients[$n]['stream'] .= chr($patch_array[$i]['f']); //start with the edge flag as 8 bit
+ $count_points = count($patch_array[$i]['points']);
+ for ($j=0; $j < $count_points; ++$j) {
+ //each point as 16 bit
+ $patch_array[$i]['points'][$j] = (($patch_array[$i]['points'][$j] - $coords_min) / ($coords_max - $coords_min)) * $bpcd;
+ if ($patch_array[$i]['points'][$j] < 0) {
+ $patch_array[$i]['points'][$j] = 0;
+ }
+ if ($patch_array[$i]['points'][$j] > $bpcd) {
+ $patch_array[$i]['points'][$j] = $bpcd;
+ }
+ $this->gradients[$n]['stream'] .= chr(floor($patch_array[$i]['points'][$j] / 256));
+ $this->gradients[$n]['stream'] .= chr(floor($patch_array[$i]['points'][$j] % 256));
+ }
+ $count_cols = count($patch_array[$i]['colors']);
+ for ($j=0; $j < $count_cols; ++$j) {
+ //each color component as 8 bit
+ $this->gradients[$n]['stream'] .= chr($patch_array[$i]['colors'][$j]['r']);
+ $this->gradients[$n]['stream'] .= chr($patch_array[$i]['colors'][$j]['g']);
+ $this->gradients[$n]['stream'] .= chr($patch_array[$i]['colors'][$j]['b']);
+ }
+ }
+ //paint the gradient
+ $this->_out('/Sh'.$n.' sh');
+ //restore previous Graphic State
+ $this->_out('Q');
+ if ($this->inxobj) {
+ // we are inside an XObject template
+ $this->xobjects[$this->xobjid]['gradients'][$n] = $this->gradients[$n];
+ }
+ }
+
+ /**
+ * Set a rectangular clipping area.
+ * @param $x (float) abscissa of the top left corner of the rectangle (or top right corner for RTL mode).
+ * @param $y (float) ordinate of the top left corner of the rectangle.
+ * @param $w (float) width of the rectangle.
+ * @param $h (float) height of the rectangle.
+ * @author Andreas Würmser, Nicola Asuni
+ * @since 3.1.000 (2008-06-09)
+ * @protected
+ */
+ protected function Clip($x, $y, $w, $h) {
+ if ($this->state != 2) {
+ return;
+ }
+ if ($this->rtl) {
+ $x = $this->w - $x - $w;
+ }
+ //save current Graphic State
+ $s = 'q';
+ //set clipping area
+ $s .= sprintf(' %F %F %F %F re W n', $x*$this->k, ($this->h-$y)*$this->k, $w*$this->k, -$h*$this->k);
+ //set up transformation matrix for gradient
+ $s .= sprintf(' %F 0 0 %F %F %F cm', $w*$this->k, $h*$this->k, $x*$this->k, ($this->h-($y+$h))*$this->k);
+ $this->_out($s);
+ }
+
+ /**
+ * Output gradient.
+ * @param $type (int) type of gradient (1 Function-based shading; 2 Axial shading; 3 Radial shading; 4 Free-form Gouraud-shaded triangle mesh; 5 Lattice-form Gouraud-shaded triangle mesh; 6 Coons patch mesh; 7 Tensor-product patch mesh). (Not all types are currently supported)
+ * @param $coords (array) array of coordinates.
+ * @param $stops (array) array gradient color components: color = array of GRAY, RGB or CMYK color components; offset = (0 to 1) represents a location along the gradient vector; exponent = exponent of the exponential interpolation function (default = 1).
+ * @param $background (array) An array of colour components appropriate to the colour space, specifying a single background colour value.
+ * @param $antialias (boolean) A flag indicating whether to filter the shading function to prevent aliasing artifacts.
+ * @author Nicola Asuni
+ * @since 3.1.000 (2008-06-09)
+ * @public
+ */
+ public function Gradient($type, $coords, $stops, $background=array(), $antialias=false) {
+ if ($this->pdfa_mode OR ($this->state != 2)) {
+ return;
+ }
+ $n = count($this->gradients) + 1;
+ $this->gradients[$n] = array();
+ $this->gradients[$n]['type'] = $type;
+ $this->gradients[$n]['coords'] = $coords;
+ $this->gradients[$n]['antialias'] = $antialias;
+ $this->gradients[$n]['colors'] = array();
+ $this->gradients[$n]['transparency'] = false;
+ // color space
+ $numcolspace = count($stops[0]['color']);
+ $bcolor = array_values($background);
+ switch($numcolspace) {
+ case 5: // SPOT
+ case 4: { // CMYK
+ $this->gradients[$n]['colspace'] = 'DeviceCMYK';
+ if (!empty($background)) {
+ $this->gradients[$n]['background'] = sprintf('%F %F %F %F', $bcolor[0]/100, $bcolor[1]/100, $bcolor[2]/100, $bcolor[3]/100);
+ }
+ break;
+ }
+ case 3: { // RGB
+ $this->gradients[$n]['colspace'] = 'DeviceRGB';
+ if (!empty($background)) {
+ $this->gradients[$n]['background'] = sprintf('%F %F %F', $bcolor[0]/255, $bcolor[1]/255, $bcolor[2]/255);
+ }
+ break;
+ }
+ case 1: { // GRAY SCALE
+ $this->gradients[$n]['colspace'] = 'DeviceGray';
+ if (!empty($background)) {
+ $this->gradients[$n]['background'] = sprintf('%F', $bcolor[0]/255);
+ }
+ break;
+ }
+ }
+ $num_stops = count($stops);
+ $last_stop_id = $num_stops - 1;
+ foreach ($stops as $key => $stop) {
+ $this->gradients[$n]['colors'][$key] = array();
+ // offset represents a location along the gradient vector
+ if (isset($stop['offset'])) {
+ $this->gradients[$n]['colors'][$key]['offset'] = $stop['offset'];
+ } else {
+ if ($key == 0) {
+ $this->gradients[$n]['colors'][$key]['offset'] = 0;
+ } elseif ($key == $last_stop_id) {
+ $this->gradients[$n]['colors'][$key]['offset'] = 1;
+ } else {
+ $offsetstep = (1 - $this->gradients[$n]['colors'][($key - 1)]['offset']) / ($num_stops - $key);
+ $this->gradients[$n]['colors'][$key]['offset'] = $this->gradients[$n]['colors'][($key - 1)]['offset'] + $offsetstep;
+ }
+ }
+ if (isset($stop['opacity'])) {
+ $this->gradients[$n]['colors'][$key]['opacity'] = $stop['opacity'];
+ if ((!$this->pdfa_mode) AND ($stop['opacity'] < 1)) {
+ $this->gradients[$n]['transparency'] = true;
+ }
+ } else {
+ $this->gradients[$n]['colors'][$key]['opacity'] = 1;
+ }
+ // exponent for the exponential interpolation function
+ if (isset($stop['exponent'])) {
+ $this->gradients[$n]['colors'][$key]['exponent'] = $stop['exponent'];
+ } else {
+ $this->gradients[$n]['colors'][$key]['exponent'] = 1;
+ }
+ // set colors
+ $color = array_values($stop['color']);
+ switch($numcolspace) {
+ case 5: // SPOT
+ case 4: { // CMYK
+ $this->gradients[$n]['colors'][$key]['color'] = sprintf('%F %F %F %F', $color[0]/100, $color[1]/100, $color[2]/100, $color[3]/100);
+ break;
+ }
+ case 3: { // RGB
+ $this->gradients[$n]['colors'][$key]['color'] = sprintf('%F %F %F', $color[0]/255, $color[1]/255, $color[2]/255);
+ break;
+ }
+ case 1: { // GRAY SCALE
+ $this->gradients[$n]['colors'][$key]['color'] = sprintf('%F', $color[0]/255);
+ break;
+ }
+ }
+ }
+ if ($this->gradients[$n]['transparency']) {
+ // paint luminosity gradient
+ $this->_out('/TGS'.$n.' gs');
+ }
+ //paint the gradient
+ $this->_out('/Sh'.$n.' sh');
+ //restore previous Graphic State
+ $this->_out('Q');
+ if ($this->inxobj) {
+ // we are inside an XObject template
+ $this->xobjects[$this->xobjid]['gradients'][$n] = $this->gradients[$n];
+ }
+ }
+
+ /**
+ * Output gradient shaders.
+ * @author Nicola Asuni
+ * @since 3.1.000 (2008-06-09)
+ * @protected
+ */
+ function _putshaders() {
+ if ($this->pdfa_mode) {
+ return;
+ }
+ $idt = count($this->gradients); //index for transparency gradients
+ foreach ($this->gradients as $id => $grad) {
+ if (($grad['type'] == 2) OR ($grad['type'] == 3)) {
+ $fc = $this->_newobj();
+ $out = '<<';
+ $out .= ' /FunctionType 3';
+ $out .= ' /Domain [0 1]';
+ $functions = '';
+ $bounds = '';
+ $encode = '';
+ $i = 1;
+ $num_cols = count($grad['colors']);
+ $lastcols = $num_cols - 1;
+ for ($i = 1; $i < $num_cols; ++$i) {
+ $functions .= ($fc + $i).' 0 R ';
+ if ($i < $lastcols) {
+ $bounds .= sprintf('%F ', $grad['colors'][$i]['offset']);
+ }
+ $encode .= '0 1 ';
+ }
+ $out .= ' /Functions ['.trim($functions).']';
+ $out .= ' /Bounds ['.trim($bounds).']';
+ $out .= ' /Encode ['.trim($encode).']';
+ $out .= ' >>';
+ $out .= "\n".'endobj';
+ $this->_out($out);
+ for ($i = 1; $i < $num_cols; ++$i) {
+ $this->_newobj();
+ $out = '<<';
+ $out .= ' /FunctionType 2';
+ $out .= ' /Domain [0 1]';
+ $out .= ' /C0 ['.$grad['colors'][($i - 1)]['color'].']';
+ $out .= ' /C1 ['.$grad['colors'][$i]['color'].']';
+ $out .= ' /N '.$grad['colors'][$i]['exponent'];
+ $out .= ' >>';
+ $out .= "\n".'endobj';
+ $this->_out($out);
+ }
+ // set transparency fuctions
+ if ($grad['transparency']) {
+ $ft = $this->_newobj();
+ $out = '<<';
+ $out .= ' /FunctionType 3';
+ $out .= ' /Domain [0 1]';
+ $functions = '';
+ $i = 1;
+ $num_cols = count($grad['colors']);
+ for ($i = 1; $i < $num_cols; ++$i) {
+ $functions .= ($ft + $i).' 0 R ';
+ }
+ $out .= ' /Functions ['.trim($functions).']';
+ $out .= ' /Bounds ['.trim($bounds).']';
+ $out .= ' /Encode ['.trim($encode).']';
+ $out .= ' >>';
+ $out .= "\n".'endobj';
+ $this->_out($out);
+ for ($i = 1; $i < $num_cols; ++$i) {
+ $this->_newobj();
+ $out = '<<';
+ $out .= ' /FunctionType 2';
+ $out .= ' /Domain [0 1]';
+ $out .= ' /C0 ['.$grad['colors'][($i - 1)]['opacity'].']';
+ $out .= ' /C1 ['.$grad['colors'][$i]['opacity'].']';
+ $out .= ' /N '.$grad['colors'][$i]['exponent'];
+ $out .= ' >>';
+ $out .= "\n".'endobj';
+ $this->_out($out);
+ }
+ }
+ }
+ // set shading object
+ $this->_newobj();
+ $out = '<< /ShadingType '.$grad['type'];
+ if (isset($grad['colspace'])) {
+ $out .= ' /ColorSpace /'.$grad['colspace'];
+ } else {
+ $out .= ' /ColorSpace /DeviceRGB';
+ }
+ if (isset($grad['background']) AND !empty($grad['background'])) {
+ $out .= ' /Background ['.$grad['background'].']';
+ }
+ if (isset($grad['antialias']) AND ($grad['antialias'] === true)) {
+ $out .= ' /AntiAlias true';
+ }
+ if ($grad['type'] == 2) {
+ $out .= ' '.sprintf('/Coords [%F %F %F %F]', $grad['coords'][0], $grad['coords'][1], $grad['coords'][2], $grad['coords'][3]);
+ $out .= ' /Domain [0 1]';
+ $out .= ' /Function '.$fc.' 0 R';
+ $out .= ' /Extend [true true]';
+ $out .= ' >>';
+ } elseif ($grad['type'] == 3) {
+ //x0, y0, r0, x1, y1, r1
+ //at this this time radius of inner circle is 0
+ $out .= ' '.sprintf('/Coords [%F %F 0 %F %F %F]', $grad['coords'][0], $grad['coords'][1], $grad['coords'][2], $grad['coords'][3], $grad['coords'][4]);
+ $out .= ' /Domain [0 1]';
+ $out .= ' /Function '.$fc.' 0 R';
+ $out .= ' /Extend [true true]';
+ $out .= ' >>';
+ } elseif ($grad['type'] == 6) {
+ $out .= ' /BitsPerCoordinate 16';
+ $out .= ' /BitsPerComponent 8';
+ $out .= ' /Decode[0 1 0 1 0 1 0 1 0 1]';
+ $out .= ' /BitsPerFlag 8';
+ $stream = $this->_getrawstream($grad['stream']);
+ $out .= ' /Length '.strlen($stream);
+ $out .= ' >>';
+ $out .= ' stream'."\n".$stream."\n".'endstream';
+ }
+ $out .= "\n".'endobj';
+ $this->_out($out);
+ if ($grad['transparency']) {
+ $shading_transparency = preg_replace('/\/ColorSpace \/[^\s]+/si', '/ColorSpace /DeviceGray', $out);
+ $shading_transparency = preg_replace('/\/Function [0-9]+ /si', '/Function '.$ft.' ', $shading_transparency);
+ }
+ $this->gradients[$id]['id'] = $this->n;
+ // set pattern object
+ $this->_newobj();
+ $out = '<< /Type /Pattern /PatternType 2';
+ $out .= ' /Shading '.$this->gradients[$id]['id'].' 0 R';
+ $out .= ' >>';
+ $out .= "\n".'endobj';
+ $this->_out($out);
+ $this->gradients[$id]['pattern'] = $this->n;
+ // set shading and pattern for transparency mask
+ if ($grad['transparency']) {
+ // luminosity pattern
+ $idgs = $id + $idt;
+ $this->_newobj();
+ $this->_out($shading_transparency);
+ $this->gradients[$idgs]['id'] = $this->n;
+ $this->_newobj();
+ $out = '<< /Type /Pattern /PatternType 2';
+ $out .= ' /Shading '.$this->gradients[$idgs]['id'].' 0 R';
+ $out .= ' >>';
+ $out .= "\n".'endobj';
+ $this->_out($out);
+ $this->gradients[$idgs]['pattern'] = $this->n;
+ // luminosity XObject
+ $oid = $this->_newobj();
+ $this->xobjects['LX'.$oid] = array('n' => $oid);
+ $filter = '';
+ $stream = 'q /a0 gs /Pattern cs /p'.$idgs.' scn 0 0 '.$this->wPt.' '.$this->hPt.' re f Q';
+ if ($this->compress) {
+ $filter = ' /Filter /FlateDecode';
+ $stream = gzcompress($stream);
+ }
+ $stream = $this->_getrawstream($stream);
+ $out = '<< /Type /XObject /Subtype /Form /FormType 1'.$filter;
+ $out .= ' /Length '.strlen($stream);
+ $rect = sprintf('%F %F', $this->wPt, $this->hPt);
+ $out .= ' /BBox [0 0 '.$rect.']';
+ $out .= ' /Group << /Type /Group /S /Transparency /CS /DeviceGray >>';
+ $out .= ' /Resources <<';
+ $out .= ' /ExtGState << /a0 << /ca 1 /CA 1 >> >>';
+ $out .= ' /Pattern << /p'.$idgs.' '.$this->gradients[$idgs]['pattern'].' 0 R >>';
+ $out .= ' >>';
+ $out .= ' >> ';
+ $out .= ' stream'."\n".$stream."\n".'endstream';
+ $out .= "\n".'endobj';
+ $this->_out($out);
+ // SMask
+ $this->_newobj();
+ $out = '<< /Type /Mask /S /Luminosity /G '.($this->n - 1).' 0 R >>'."\n".'endobj';
+ $this->_out($out);
+ // ExtGState
+ $this->_newobj();
+ $out = '<< /Type /ExtGState /SMask '.($this->n - 1).' 0 R /AIS false >>'."\n".'endobj';
+ $this->_out($out);
+ $this->extgstates[] = array('n' => $this->n, 'name' => 'TGS'.$id);
+ }
+ }
+ }
+
+ /**
+ * Draw the sector of a circle.
+ * It can be used for instance to render pie charts.
+ * @param $xc (float) abscissa of the center.
+ * @param $yc (float) ordinate of the center.
+ * @param $r (float) radius.
+ * @param $a (float) start angle (in degrees).
+ * @param $b (float) end angle (in degrees).
+ * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
+ * @param $cw: (float) indicates whether to go clockwise (default: true).
+ * @param $o: (float) origin of angles (0 for 3 o'clock, 90 for noon, 180 for 9 o'clock, 270 for 6 o'clock). Default: 90.
+ * @author Maxime Delorme, Nicola Asuni
+ * @since 3.1.000 (2008-06-09)
+ * @public
+ */
+ public function PieSector($xc, $yc, $r, $a, $b, $style='FD', $cw=true, $o=90) {
+ $this->PieSectorXY($xc, $yc, $r, $r, $a, $b, $style, $cw, $o);
+ }
+
+ /**
+ * Draw the sector of an ellipse.
+ * It can be used for instance to render pie charts.
+ * @param $xc (float) abscissa of the center.
+ * @param $yc (float) ordinate of the center.
+ * @param $rx (float) the x-axis radius.
+ * @param $ry (float) the y-axis radius.
+ * @param $a (float) start angle (in degrees).
+ * @param $b (float) end angle (in degrees).
+ * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
+ * @param $cw: (float) indicates whether to go clockwise.
+ * @param $o: (float) origin of angles (0 for 3 o'clock, 90 for noon, 180 for 9 o'clock, 270 for 6 o'clock).
+ * @param $nc (integer) Number of curves used to draw a 90 degrees portion of arc.
+ * @author Maxime Delorme, Nicola Asuni
+ * @since 3.1.000 (2008-06-09)
+ * @public
+ */
+ public function PieSectorXY($xc, $yc, $rx, $ry, $a, $b, $style='FD', $cw=false, $o=0, $nc=2) {
+ if ($this->state != 2) {
+ return;
+ }
+ if ($this->rtl) {
+ $xc = ($this->w - $xc);
+ }
+ $op = TCPDF_STATIC::getPathPaintOperator($style);
+ if ($op == 'f') {
+ $line_style = array();
+ }
+ if ($cw) {
+ $d = $b;
+ $b = (360 - $a + $o);
+ $a = (360 - $d + $o);
+ } else {
+ $b += $o;
+ $a += $o;
+ }
+ $this->_outellipticalarc($xc, $yc, $rx, $ry, 0, $a, $b, true, $nc);
+ $this->_out($op);
+ }
+
+ /**
+ * Embed vector-based Adobe Illustrator (AI) or AI-compatible EPS files.
+ * NOTE: EPS is not yet fully implemented, use the setRasterizeVectorImages() method to enable/disable rasterization of vector images using ImageMagick library.
+ * Only vector drawing is supported, not text or bitmap.
+ * Although the script was successfully tested with various AI format versions, best results are probably achieved with files that were exported in the AI3 format (tested with Illustrator CS2, Freehand MX and Photoshop CS2).
+ * @param $file (string) Name of the file containing the image or a '@' character followed by the EPS/AI data string.
+ * @param $x (float) Abscissa of the upper-left corner.
+ * @param $y (float) Ordinate of the upper-left corner.
+ * @param $w (float) Width of the image in the page. If not specified or equal to zero, it is automatically calculated.
+ * @param $h (float) Height of the image in the page. If not specified or equal to zero, it is automatically calculated.
+ * @param $link (mixed) URL or identifier returned by AddLink().
+ * @param $useBoundingBox (boolean) specifies whether to position the bounding box (true) or the complete canvas (false) at location (x,y). Default value is true.
+ * @param $align (string) Indicates the alignment of the pointer next to image insertion relative to image height. The value can be:<ul><li>T: top-right for LTR or top-left for RTL</li><li>M: middle-right for LTR or middle-left for RTL</li><li>B: bottom-right for LTR or bottom-left for RTL</li><li>N: next line</li></ul>
+ * @param $palign (string) Allows to center or align the image on the current line. Possible values are:<ul><li>L : left align</li><li>C : center</li><li>R : right align</li><li>'' : empty string : left for LTR or right for RTL</li></ul>
+ * @param $border (mixed) Indicates if borders must be drawn around the cell. The value can be a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul> or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul> or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)))
+ * @param $fitonpage (boolean) if true the image is resized to not exceed page dimensions.
+ * @param $fixoutvals (boolean) if true remove values outside the bounding box.
+ * @author Valentin Schmidt, Nicola Asuni
+ * @since 3.1.000 (2008-06-09)
+ * @public
+ */
+ public function ImageEps($file, $x='', $y='', $w=0, $h=0, $link='', $useBoundingBox=true, $align='', $palign='', $border=0, $fitonpage=false, $fixoutvals=false) {
+ if ($this->state != 2) {
+ return;
+ }
+ if ($this->rasterize_vector_images AND ($w > 0) AND ($h > 0)) {
+ // convert EPS to raster image using GD or ImageMagick libraries
+ return $this->Image($file, $x, $y, $w, $h, 'EPS', $link, $align, true, 300, $palign, false, false, $border, false, false, $fitonpage);
+ }
+ if ($x === '') {
+ $x = $this->x;
+ }
+ if ($y === '') {
+ $y = $this->y;
+ }
+ // check page for no-write regions and adapt page margins if necessary
+ list($x, $y) = $this->checkPageRegions($h, $x, $y);
+ $k = $this->k;
+ if ($file{0} === '@') { // image from string
+ $data = substr($file, 1);
+ } else { // EPS/AI file
+ $data = TCPDF_STATIC::fileGetContents($file);
+ }
+ if ($data === FALSE) {
+ $this->Error('EPS file not found: '.$file);
+ }
+ $regs = array();
+ // EPS/AI compatibility check (only checks files created by Adobe Illustrator!)
+ preg_match("/%%Creator:([^\r\n]+)/", $data, $regs); # find Creator
+ if (count($regs) > 1) {
+ $version_str = trim($regs[1]); # e.g. "Adobe Illustrator(R) 8.0"
+ if (strpos($version_str, 'Adobe Illustrator') !== false) {
+ $versexp = explode(' ', $version_str);
+ $version = (float)array_pop($versexp);
+ if ($version >= 9) {
+ $this->Error('This version of Adobe Illustrator file is not supported: '.$file);
+ }
+ }
+ }
+ // strip binary bytes in front of PS-header
+ $start = strpos($data, '%!PS-Adobe');
+ if ($start > 0) {
+ $data = substr($data, $start);
+ }
+ // find BoundingBox params
+ preg_match("/%%BoundingBox:([^\r\n]+)/", $data, $regs);
+ if (count($regs) > 1) {
+ list($x1, $y1, $x2, $y2) = explode(' ', trim($regs[1]));
+ } else {
+ $this->Error('No BoundingBox found in EPS/AI file: '.$file);
+ }
+ $start = strpos($data, '%%EndSetup');
+ if ($start === false) {
+ $start = strpos($data, '%%EndProlog');
+ }
+ if ($start === false) {
+ $start = strpos($data, '%%BoundingBox');
+ }
+ $data = substr($data, $start);
+ $end = strpos($data, '%%PageTrailer');
+ if ($end===false) {
+ $end = strpos($data, 'showpage');
+ }
+ if ($end) {
+ $data = substr($data, 0, $end);
+ }
+ // calculate image width and height on document
+ if (($w <= 0) AND ($h <= 0)) {
+ $w = ($x2 - $x1) / $k;
+ $h = ($y2 - $y1) / $k;
+ } elseif ($w <= 0) {
+ $w = ($x2-$x1) / $k * ($h / (($y2 - $y1) / $k));
+ } elseif ($h <= 0) {
+ $h = ($y2 - $y1) / $k * ($w / (($x2 - $x1) / $k));
+ }
+ // fit the image on available space
+ list($w, $h, $x, $y) = $this->fitBlock($w, $h, $x, $y, $fitonpage);
+ if ($this->rasterize_vector_images) {
+ // convert EPS to raster image using GD or ImageMagick libraries
+ return $this->Image($file, $x, $y, $w, $h, 'EPS', $link, $align, true, 300, $palign, false, false, $border, false, false, $fitonpage);
+ }
+ // set scaling factors
+ $scale_x = $w / (($x2 - $x1) / $k);
+ $scale_y = $h / (($y2 - $y1) / $k);
+ // set alignment
+ $this->img_rb_y = $y + $h;
+ // set alignment
+ if ($this->rtl) {
+ if ($palign == 'L') {
+ $ximg = $this->lMargin;
+ } elseif ($palign == 'C') {
+ $ximg = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
+ } elseif ($palign == 'R') {
+ $ximg = $this->w - $this->rMargin - $w;
+ } else {
+ $ximg = $x - $w;
+ }
+ $this->img_rb_x = $ximg;
+ } else {
+ if ($palign == 'L') {
+ $ximg = $this->lMargin;
+ } elseif ($palign == 'C') {
+ $ximg = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
+ } elseif ($palign == 'R') {
+ $ximg = $this->w - $this->rMargin - $w;
+ } else {
+ $ximg = $x;
+ }
+ $this->img_rb_x = $ximg + $w;
+ }
+ if ($useBoundingBox) {
+ $dx = $ximg * $k - $x1;
+ $dy = $y * $k - $y1;
+ } else {
+ $dx = $ximg * $k;
+ $dy = $y * $k;
+ }
+ // save the current graphic state
+ $this->_out('q'.$this->epsmarker);
+ // translate
+ $this->_out(sprintf('%F %F %F %F %F %F cm', 1, 0, 0, 1, $dx, $dy + ($this->hPt - (2 * $y * $k) - ($y2 - $y1))));
+ // scale
+ if (isset($scale_x)) {
+ $this->_out(sprintf('%F %F %F %F %F %F cm', $scale_x, 0, 0, $scale_y, $x1 * (1 - $scale_x), $y2 * (1 - $scale_y)));
+ }
+ // handle pc/unix/mac line endings
+ $lines = preg_split('/[\r\n]+/si', $data, -1, PREG_SPLIT_NO_EMPTY);
+ $u=0;
+ $cnt = count($lines);
+ for ($i=0; $i < $cnt; ++$i) {
+ $line = $lines[$i];
+ if (($line == '') OR ($line{0} == '%')) {
+ continue;
+ }
+ $len = strlen($line);
+ // check for spot color names
+ $color_name = '';
+ if (strcasecmp('x', substr(trim($line), -1)) == 0) {
+ if (preg_match('/\([^\)]*\)/', $line, $matches) > 0) {
+ // extract spot color name
+ $color_name = $matches[0];
+ // remove color name from string
+ $line = str_replace(' '.$color_name, '', $line);
+ // remove pharentesis from color name
+ $color_name = substr($color_name, 1, -1);
+ }
+ }
+ $chunks = explode(' ', $line);
+ $cmd = trim(array_pop($chunks));
+ // RGB
+ if (($cmd == 'Xa') OR ($cmd == 'XA')) {
+ $b = array_pop($chunks);
+ $g = array_pop($chunks);
+ $r = array_pop($chunks);
+ $this->_out(''.$r.' '.$g.' '.$b.' '.($cmd=='Xa'?'rg':'RG')); //substr($line, 0, -2).'rg' -> in EPS (AI8): c m y k r g b rg!
+ continue;
+ }
+ $skip = false;
+ if ($fixoutvals) {
+ // check for values outside the bounding box
+ switch ($cmd) {
+ case 'm':
+ case 'l':
+ case 'L': {
+ // skip values outside bounding box
+ foreach ($chunks as $key => $val) {
+ if ((($key % 2) == 0) AND (($val < $x1) OR ($val > $x2))) {
+ $skip = true;
+ } elseif ((($key % 2) != 0) AND (($val < $y1) OR ($val > $y2))) {
+ $skip = true;
+ }
+ }
+ }
+ }
+ }
+ switch ($cmd) {
+ case 'm':
+ case 'l':
+ case 'v':
+ case 'y':
+ case 'c':
+ case 'k':
+ case 'K':
+ case 'g':
+ case 'G':
+ case 's':
+ case 'S':
+ case 'J':
+ case 'j':
+ case 'w':
+ case 'M':
+ case 'd':
+ case 'n': {
+ if ($skip) {
+ break;
+ }
+ $this->_out($line);
+ break;
+ }
+ case 'x': {// custom fill color
+ if (empty($color_name)) {
+ // CMYK color
+ list($col_c, $col_m, $col_y, $col_k) = $chunks;
+ $this->_out(''.$col_c.' '.$col_m.' '.$col_y.' '.$col_k.' k');
+ } else {
+ // Spot Color (CMYK + tint)
+ list($col_c, $col_m, $col_y, $col_k, $col_t) = $chunks;
+ $this->AddSpotColor($color_name, ($col_c * 100), ($col_m * 100), ($col_y * 100), ($col_k * 100));
+ $color_cmd = sprintf('/CS%d cs %F scn', $this->spot_colors[$color_name]['i'], (1 - $col_t));
+ $this->_out($color_cmd);
+ }
+ break;
+ }
+ case 'X': { // custom stroke color
+ if (empty($color_name)) {
+ // CMYK color
+ list($col_c, $col_m, $col_y, $col_k) = $chunks;
+ $this->_out(''.$col_c.' '.$col_m.' '.$col_y.' '.$col_k.' K');
+ } else {
+ // Spot Color (CMYK + tint)
+ list($col_c, $col_m, $col_y, $col_k, $col_t) = $chunks;
+ $this->AddSpotColor($color_name, ($col_c * 100), ($col_m * 100), ($col_y * 100), ($col_k * 100));
+ $color_cmd = sprintf('/CS%d CS %F SCN', $this->spot_colors[$color_name]['i'], (1 - $col_t));
+ $this->_out($color_cmd);
+ }
+ break;
+ }
+ case 'Y':
+ case 'N':
+ case 'V':
+ case 'L':
+ case 'C': {
+ if ($skip) {
+ break;
+ }
+ $line[($len - 1)] = strtolower($cmd);
+ $this->_out($line);
+ break;
+ }
+ case 'b':
+ case 'B': {
+ $this->_out($cmd . '*');
+ break;
+ }
+ case 'f':
+ case 'F': {
+ if ($u > 0) {
+ $isU = false;
+ $max = min(($i + 5), $cnt);
+ for ($j = ($i + 1); $j < $max; ++$j) {
+ $isU = ($isU OR (($lines[$j] == 'U') OR ($lines[$j] == '*U')));
+ }
+ if ($isU) {
+ $this->_out('f*');
+ }
+ } else {
+ $this->_out('f*');
+ }
+ break;
+ }
+ case '*u': {
+ ++$u;
+ break;
+ }
+ case '*U': {
+ --$u;
+ break;
+ }
+ }
+ }
+ // restore previous graphic state
+ $this->_out($this->epsmarker.'Q');
+ if (!empty($border)) {
+ $bx = $this->x;
+ $by = $this->y;
+ $this->x = $ximg;
+ if ($this->rtl) {
+ $this->x += $w;
+ }
+ $this->y = $y;
+ $this->Cell($w, $h, '', $border, 0, '', 0, '', 0, true);
+ $this->x = $bx;
+ $this->y = $by;
+ }
+ if ($link) {
+ $this->Link($ximg, $y, $w, $h, $link, 0);
+ }
+ // set pointer to align the next text/objects
+ switch($align) {
+ case 'T':{
+ $this->y = $y;
+ $this->x = $this->img_rb_x;
+ break;
+ }
+ case 'M':{
+ $this->y = $y + round($h/2);
+ $this->x = $this->img_rb_x;
+ break;
+ }
+ case 'B':{
+ $this->y = $this->img_rb_y;
+ $this->x = $this->img_rb_x;
+ break;
+ }
+ case 'N':{
+ $this->SetY($this->img_rb_y);
+ break;
+ }
+ default:{
+ break;
+ }
+ }
+ $this->endlinex = $this->img_rb_x;
+ }
+
+ /**
+ * Set document barcode.
+ * @param $bc (string) barcode
+ * @public
+ */
+ public function setBarcode($bc='') {
+ $this->barcode = $bc;
+ }
+
+ /**
+ * Get current barcode.
+ * @return string
+ * @public
+ * @since 4.0.012 (2008-07-24)
+ */
+ public function getBarcode() {
+ return $this->barcode;
+ }
+
+ /**
+ * Print a Linear Barcode.
+ * @param $code (string) code to print
+ * @param $type (string) type of barcode (see tcpdf_barcodes_1d.php for supported formats).
+ * @param $x (int) x position in user units (empty string = current x position)
+ * @param $y (int) y position in user units (empty string = current y position)
+ * @param $w (int) width in user units (empty string = remaining page width)
+ * @param $h (int) height in user units (empty string = remaining page height)
+ * @param $xres (float) width of the smallest bar in user units (empty string = default value = 0.4mm)
+ * @param $style (array) array of options:<ul>
+ * <li>boolean $style['border'] if true prints a border</li>
+ * <li>int $style['padding'] padding to leave around the barcode in user units (set to 'auto' for automatic padding)</li>
+ * <li>int $style['hpadding'] horizontal padding in user units (set to 'auto' for automatic padding)</li>
+ * <li>int $style['vpadding'] vertical padding in user units (set to 'auto' for automatic padding)</li>
+ * <li>array $style['fgcolor'] color array for bars and text</li>
+ * <li>mixed $style['bgcolor'] color array for background (set to false for transparent)</li>
+ * <li>boolean $style['text'] if true prints text below the barcode</li>
+ * <li>string $style['label'] override default label</li>
+ * <li>string $style['font'] font name for text</li><li>int $style['fontsize'] font size for text</li>
+ * <li>int $style['stretchtext']: 0 = disabled; 1 = horizontal scaling only if necessary; 2 = forced horizontal scaling; 3 = character spacing only if necessary; 4 = forced character spacing.</li>
+ * <li>string $style['position'] horizontal position of the containing barcode cell on the page: L = left margin; C = center; R = right margin.</li>
+ * <li>string $style['align'] horizontal position of the barcode on the containing rectangle: L = left; C = center; R = right.</li>
+ * <li>string $style['stretch'] if true stretch the barcode to best fit the available width, otherwise uses $xres resolution for a single bar.</li>
+ * <li>string $style['fitwidth'] if true reduce the width to fit the barcode width + padding. When this option is enabled the 'stretch' option is automatically disabled.</li>
+ * <li>string $style['cellfitalign'] this option works only when 'fitwidth' is true and 'position' is unset or empty. Set the horizontal position of the containing barcode cell inside the specified rectangle: L = left; C = center; R = right.</li></ul>
+ * @param $align (string) Indicates the alignment of the pointer next to barcode insertion relative to barcode height. The value can be:<ul><li>T: top-right for LTR or top-left for RTL</li><li>M: middle-right for LTR or middle-left for RTL</li><li>B: bottom-right for LTR or bottom-left for RTL</li><li>N: next line</li></ul>
+ * @author Nicola Asuni
+ * @since 3.1.000 (2008-06-09)
+ * @public
+ */
+ public function write1DBarcode($code, $type, $x='', $y='', $w='', $h='', $xres='', $style='', $align='') {
+ if (TCPDF_STATIC::empty_string(trim($code))) {
+ return;
+ }
+ require_once(dirname(__FILE__).'/tcpdf_barcodes_1d.php');
+ // save current graphic settings
+ $gvars = $this->getGraphicVars();
+ // create new barcode object
+ $barcodeobj = new TCPDFBarcode($code, $type);
+ $arrcode = $barcodeobj->getBarcodeArray();
+ if (($arrcode === false) OR empty($arrcode) OR ($arrcode['maxw'] <= 0)) {
+ $this->Error('Error in 1D barcode string');
+ }
+ if ($arrcode['maxh'] <= 0) {
+ $arrcode['maxh'] = 1;
+ }
+ // set default values
+ if (!isset($style['position'])) {
+ $style['position'] = '';
+ } elseif ($style['position'] == 'S') {
+ // keep this for backward compatibility
+ $style['position'] = '';
+ $style['stretch'] = true;
+ }
+ if (!isset($style['fitwidth'])) {
+ if (!isset($style['stretch'])) {
+ $style['fitwidth'] = true;
+ } else {
+ $style['fitwidth'] = false;
+ }
+ }
+ if ($style['fitwidth']) {
+ // disable stretch
+ $style['stretch'] = false;
+ }
+ if (!isset($style['stretch'])) {
+ if (($w === '') OR ($w <= 0)) {
+ $style['stretch'] = false;
+ } else {
+ $style['stretch'] = true;
+ }
+ }
+ if (!isset($style['fgcolor'])) {
+ $style['fgcolor'] = array(0,0,0); // default black
+ }
+ if (!isset($style['bgcolor'])) {
+ $style['bgcolor'] = false; // default transparent
+ }
+ if (!isset($style['border'])) {
+ $style['border'] = false;
+ }
+ $fontsize = 0;
+ if (!isset($style['text'])) {
+ $style['text'] = false;
+ }
+ if ($style['text'] AND isset($style['font'])) {
+ if (isset($style['fontsize'])) {
+ $fontsize = $style['fontsize'];
+ }
+ $this->SetFont($style['font'], '', $fontsize);
+ }
+ if (!isset($style['stretchtext'])) {
+ $style['stretchtext'] = 4;
+ }
+ if ($x === '') {
+ $x = $this->x;
+ }
+ if ($y === '') {
+ $y = $this->y;
+ }
+ // check page for no-write regions and adapt page margins if necessary
+ list($x, $y) = $this->checkPageRegions($h, $x, $y);
+ if (($w === '') OR ($w <= 0)) {
+ if ($this->rtl) {
+ $w = $x - $this->lMargin;
+ } else {
+ $w = $this->w - $this->rMargin - $x;
+ }
+ }
+ // padding
+ if (!isset($style['padding'])) {
+ $padding = 0;
+ } elseif ($style['padding'] === 'auto') {
+ $padding = 10 * ($w / ($arrcode['maxw'] + 20));
+ } else {
+ $padding = floatval($style['padding']);
+ }
+ // horizontal padding
+ if (!isset($style['hpadding'])) {
+ $hpadding = $padding;
+ } elseif ($style['hpadding'] === 'auto') {
+ $hpadding = 10 * ($w / ($arrcode['maxw'] + 20));
+ } else {
+ $hpadding = floatval($style['hpadding']);
+ }
+ // vertical padding
+ if (!isset($style['vpadding'])) {
+ $vpadding = $padding;
+ } elseif ($style['vpadding'] === 'auto') {
+ $vpadding = ($hpadding / 2);
+ } else {
+ $vpadding = floatval($style['vpadding']);
+ }
+ // calculate xres (single bar width)
+ $max_xres = ($w - (2 * $hpadding)) / $arrcode['maxw'];
+ if ($style['stretch']) {
+ $xres = $max_xres;
+ } else {
+ if (TCPDF_STATIC::empty_string($xres)) {
+ $xres = (0.141 * $this->k); // default bar width = 0.4 mm
+ }
+ if ($xres > $max_xres) {
+ // correct xres to fit on $w
+ $xres = $max_xres;
+ }
+ if ((isset($style['padding']) AND ($style['padding'] === 'auto'))
+ OR (isset($style['hpadding']) AND ($style['hpadding'] === 'auto'))) {
+ $hpadding = 10 * $xres;
+ if (isset($style['vpadding']) AND ($style['vpadding'] === 'auto')) {
+ $vpadding = ($hpadding / 2);
+ }
+ }
+ }
+ if ($style['fitwidth']) {
+ $wold = $w;
+ $w = (($arrcode['maxw'] * $xres) + (2 * $hpadding));
+ if (isset($style['cellfitalign'])) {
+ switch ($style['cellfitalign']) {
+ case 'L': {
+ if ($this->rtl) {
+ $x -= ($wold - $w);
+ }
+ break;
+ }
+ case 'R': {
+ if (!$this->rtl) {
+ $x += ($wold - $w);
+ }
+ break;
+ }
+ case 'C': {
+ if ($this->rtl) {
+ $x -= (($wold - $w) / 2);
+ } else {
+ $x += (($wold - $w) / 2);
+ }
+ break;
+ }
+ default : {
+ break;
+ }
+ }
+ }
+ }
+ $text_height = ($this->cell_height_ratio * $fontsize / $this->k);
+ // height
+ if (($h === '') OR ($h <= 0)) {
+ // set default height
+ $h = (($arrcode['maxw'] * $xres) / 3) + (2 * $vpadding) + $text_height;
+ }
+ $barh = $h - $text_height - (2 * $vpadding);
+ if ($barh <=0) {
+ // try to reduce font or padding to fit barcode on available height
+ if ($text_height > $h) {
+ $fontsize = (($h * $this->k) / (4 * $this->cell_height_ratio));
+ $text_height = ($this->cell_height_ratio * $fontsize / $this->k);
+ $this->SetFont($style['font'], '', $fontsize);
+ }
+ if ($vpadding > 0) {
+ $vpadding = (($h - $text_height) / 4);
+ }
+ $barh = $h - $text_height - (2 * $vpadding);
+ }
+ // fit the barcode on available space
+ list($w, $h, $x, $y) = $this->fitBlock($w, $h, $x, $y, false);
+ // set alignment
+ $this->img_rb_y = $y + $h;
+ // set alignment
+ if ($this->rtl) {
+ if ($style['position'] == 'L') {
+ $xpos = $this->lMargin;
+ } elseif ($style['position'] == 'C') {
+ $xpos = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
+ } elseif ($style['position'] == 'R') {
+ $xpos = $this->w - $this->rMargin - $w;
+ } else {
+ $xpos = $x - $w;
+ }
+ $this->img_rb_x = $xpos;
+ } else {
+ if ($style['position'] == 'L') {
+ $xpos = $this->lMargin;
+ } elseif ($style['position'] == 'C') {
+ $xpos = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
+ } elseif ($style['position'] == 'R') {
+ $xpos = $this->w - $this->rMargin - $w;
+ } else {
+ $xpos = $x;
+ }
+ $this->img_rb_x = $xpos + $w;
+ }
+ $xpos_rect = $xpos;
+ if (!isset($style['align'])) {
+ $style['align'] = 'C';
+ }
+ switch ($style['align']) {
+ case 'L': {
+ $xpos = $xpos_rect + $hpadding;
+ break;
+ }
+ case 'R': {
+ $xpos = $xpos_rect + ($w - ($arrcode['maxw'] * $xres)) - $hpadding;
+ break;
+ }
+ case 'C':
+ default : {
+ $xpos = $xpos_rect + (($w - ($arrcode['maxw'] * $xres)) / 2);
+ break;
+ }
+ }
+ $xpos_text = $xpos;
+ // barcode is always printed in LTR direction
+ $tempRTL = $this->rtl;
+ $this->rtl = false;
+ // print background color
+ if ($style['bgcolor']) {
+ $this->Rect($xpos_rect, $y, $w, $h, $style['border'] ? 'DF' : 'F', '', $style['bgcolor']);
+ } elseif ($style['border']) {
+ $this->Rect($xpos_rect, $y, $w, $h, 'D');
+ }
+ // set foreground color
+ $this->SetDrawColorArray($style['fgcolor']);
+ $this->SetTextColorArray($style['fgcolor']);
+ // print bars
+ foreach ($arrcode['bcode'] as $k => $v) {
+ $bw = ($v['w'] * $xres);
+ if ($v['t']) {
+ // draw a vertical bar
+ $ypos = $y + $vpadding + ($v['p'] * $barh / $arrcode['maxh']);
+ $this->Rect($xpos, $ypos, $bw, ($v['h'] * $barh / $arrcode['maxh']), 'F', array(), $style['fgcolor']);
+ }
+ $xpos += $bw;
+ }
+ // print text
+ if ($style['text']) {
+ if (isset($style['label']) AND !TCPDF_STATIC::empty_string($style['label'])) {
+ $label = $style['label'];
+ } else {
+ $label = $code;
+ }
+ $txtwidth = ($arrcode['maxw'] * $xres);
+ if ($this->GetStringWidth($label) > $txtwidth) {
+ $style['stretchtext'] = 2;
+ }
+ // print text
+ $this->x = $xpos_text;
+ $this->y = $y + $vpadding + $barh;
+ $cellpadding = $this->cell_padding;
+ $this->SetCellPadding(0);
+ $this->Cell($txtwidth, '', $label, 0, 0, 'C', false, '', $style['stretchtext'], false, 'T', 'T');
+ $this->cell_padding = $cellpadding;
+ }
+ // restore original direction
+ $this->rtl = $tempRTL;
+ // restore previous settings
+ $this->setGraphicVars($gvars);
+ // set pointer to align the next text/objects
+ switch($align) {
+ case 'T':{
+ $this->y = $y;
+ $this->x = $this->img_rb_x;
+ break;
+ }
+ case 'M':{
+ $this->y = $y + round($h / 2);
+ $this->x = $this->img_rb_x;
+ break;
+ }
+ case 'B':{
+ $this->y = $this->img_rb_y;
+ $this->x = $this->img_rb_x;
+ break;
+ }
+ case 'N':{
+ $this->SetY($this->img_rb_y);
+ break;
+ }
+ default:{
+ break;
+ }
+ }
+ $this->endlinex = $this->img_rb_x;
+ }
+
+ /**
+ * Print 2D Barcode.
+ * @param $code (string) code to print
+ * @param $type (string) type of barcode (see tcpdf_barcodes_2d.php for supported formats).
+ * @param $x (int) x position in user units
+ * @param $y (int) y position in user units
+ * @param $w (int) width in user units
+ * @param $h (int) height in user units
+ * @param $style (array) array of options:<ul>
+ * <li>boolean $style['border'] if true prints a border around the barcode</li>
+ * <li>int $style['padding'] padding to leave around the barcode in barcode units (set to 'auto' for automatic padding)</li>
+ * <li>int $style['hpadding'] horizontal padding in barcode units (set to 'auto' for automatic padding)</li>
+ * <li>int $style['vpadding'] vertical padding in barcode units (set to 'auto' for automatic padding)</li>
+ * <li>int $style['module_width'] width of a single module in points</li>
+ * <li>int $style['module_height'] height of a single module in points</li>
+ * <li>array $style['fgcolor'] color array for bars and text</li>
+ * <li>mixed $style['bgcolor'] color array for background or false for transparent</li>
+ * <li>string $style['position'] barcode position on the page: L = left margin; C = center; R = right margin; S = stretch</li><li>$style['module_width'] width of a single module in points</li>
+ * <li>$style['module_height'] height of a single module in points</li></ul>
+ * @param $align (string) Indicates the alignment of the pointer next to barcode insertion relative to barcode height. The value can be:<ul><li>T: top-right for LTR or top-left for RTL</li><li>M: middle-right for LTR or middle-left for RTL</li><li>B: bottom-right for LTR or bottom-left for RTL</li><li>N: next line</li></ul>
+ * @param $distort (boolean) if true distort the barcode to fit width and height, otherwise preserve aspect ratio
+ * @author Nicola Asuni
+ * @since 4.5.037 (2009-04-07)
+ * @public
+ */
+ public function write2DBarcode($code, $type, $x='', $y='', $w='', $h='', $style='', $align='', $distort=false) {
+ if (TCPDF_STATIC::empty_string(trim($code))) {
+ return;
+ }
+ require_once(dirname(__FILE__).'/tcpdf_barcodes_2d.php');
+ // save current graphic settings
+ $gvars = $this->getGraphicVars();
+ // create new barcode object
+ $barcodeobj = new TCPDF2DBarcode($code, $type);
+ $arrcode = $barcodeobj->getBarcodeArray();
+ if (($arrcode === false) OR empty($arrcode) OR !isset($arrcode['num_rows']) OR ($arrcode['num_rows'] == 0) OR !isset($arrcode['num_cols']) OR ($arrcode['num_cols'] == 0)) {
+ $this->Error('Error in 2D barcode string');
+ }
+ // set default values
+ if (!isset($style['position'])) {
+ $style['position'] = '';
+ }
+ if (!isset($style['fgcolor'])) {
+ $style['fgcolor'] = array(0,0,0); // default black
+ }
+ if (!isset($style['bgcolor'])) {
+ $style['bgcolor'] = false; // default transparent
+ }
+ if (!isset($style['border'])) {
+ $style['border'] = false;
+ }
+ // padding
+ if (!isset($style['padding'])) {
+ $style['padding'] = 0;
+ } elseif ($style['padding'] === 'auto') {
+ $style['padding'] = 4;
+ }
+ if (!isset($style['hpadding'])) {
+ $style['hpadding'] = $style['padding'];
+ } elseif ($style['hpadding'] === 'auto') {
+ $style['hpadding'] = 4;
+ }
+ if (!isset($style['vpadding'])) {
+ $style['vpadding'] = $style['padding'];
+ } elseif ($style['vpadding'] === 'auto') {
+ $style['vpadding'] = 4;
+ }
+ $hpad = (2 * $style['hpadding']);
+ $vpad = (2 * $style['vpadding']);
+ // cell (module) dimension
+ if (!isset($style['module_width'])) {
+ $style['module_width'] = 1; // width of a single module in points
+ }
+ if (!isset($style['module_height'])) {
+ $style['module_height'] = 1; // height of a single module in points
+ }
+ if ($x === '') {
+ $x = $this->x;
+ }
+ if ($y === '') {
+ $y = $this->y;
+ }
+ // check page for no-write regions and adapt page margins if necessary
+ list($x, $y) = $this->checkPageRegions($h, $x, $y);
+ // number of barcode columns and rows
+ $rows = $arrcode['num_rows'];
+ $cols = $arrcode['num_cols'];
+ if (($rows <= 0) || ($cols <= 0)){
+ $this->Error('Error in 2D barcode string');
+ }
+ // module width and height
+ $mw = $style['module_width'];
+ $mh = $style['module_height'];
+ if (($mw <= 0) OR ($mh <= 0)) {
+ $this->Error('Error in 2D barcode string');
+ }
+ // get max dimensions
+ if ($this->rtl) {
+ $maxw = $x - $this->lMargin;
+ } else {
+ $maxw = $this->w - $this->rMargin - $x;
+ }
+ $maxh = ($this->h - $this->tMargin - $this->bMargin);
+ $ratioHW = ((($rows * $mh) + $hpad) / (($cols * $mw) + $vpad));
+ $ratioWH = ((($cols * $mw) + $vpad) / (($rows * $mh) + $hpad));
+ if (!$distort) {
+ if (($maxw * $ratioHW) > $maxh) {
+ $maxw = $maxh * $ratioWH;
+ }
+ if (($maxh * $ratioWH) > $maxw) {
+ $maxh = $maxw * $ratioHW;
+ }
+ }
+ // set maximum dimesions
+ if ($w > $maxw) {
+ $w = $maxw;
+ }
+ if ($h > $maxh) {
+ $h = $maxh;
+ }
+ // set dimensions
+ if ((($w === '') OR ($w <= 0)) AND (($h === '') OR ($h <= 0))) {
+ $w = ($cols + $hpad) * ($mw / $this->k);
+ $h = ($rows + $vpad) * ($mh / $this->k);
+ } elseif (($w === '') OR ($w <= 0)) {
+ $w = $h * $ratioWH;
+ } elseif (($h === '') OR ($h <= 0)) {
+ $h = $w * $ratioHW;
+ }
+ // barcode size (excluding padding)
+ $bw = ($w * $cols) / ($cols + $hpad);
+ $bh = ($h * $rows) / ($rows + $vpad);
+ // dimension of single barcode cell unit
+ $cw = $bw / $cols;
+ $ch = $bh / $rows;
+ if (!$distort) {
+ if (($cw / $ch) > ($mw / $mh)) {
+ // correct horizontal distortion
+ $cw = $ch * $mw / $mh;
+ $bw = $cw * $cols;
+ $style['hpadding'] = ($w - $bw) / (2 * $cw);
+ } else {
+ // correct vertical distortion
+ $ch = $cw * $mh / $mw;
+ $bh = $ch * $rows;
+ $style['vpadding'] = ($h - $bh) / (2 * $ch);
+ }
+ }
+ // fit the barcode on available space
+ list($w, $h, $x, $y) = $this->fitBlock($w, $h, $x, $y, false);
+ // set alignment
+ $this->img_rb_y = $y + $h;
+ // set alignment
+ if ($this->rtl) {
+ if ($style['position'] == 'L') {
+ $xpos = $this->lMargin;
+ } elseif ($style['position'] == 'C') {
+ $xpos = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
+ } elseif ($style['position'] == 'R') {
+ $xpos = $this->w - $this->rMargin - $w;
+ } else {
+ $xpos = $x - $w;
+ }
+ $this->img_rb_x = $xpos;
+ } else {
+ if ($style['position'] == 'L') {
+ $xpos = $this->lMargin;
+ } elseif ($style['position'] == 'C') {
+ $xpos = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
+ } elseif ($style['position'] == 'R') {
+ $xpos = $this->w - $this->rMargin - $w;
+ } else {
+ $xpos = $x;
+ }
+ $this->img_rb_x = $xpos + $w;
+ }
+ $xstart = $xpos + ($style['hpadding'] * $cw);
+ $ystart = $y + ($style['vpadding'] * $ch);
+ // barcode is always printed in LTR direction
+ $tempRTL = $this->rtl;
+ $this->rtl = false;
+ // print background color
+ if ($style['bgcolor']) {
+ $this->Rect($xpos, $y, $w, $h, $style['border'] ? 'DF' : 'F', '', $style['bgcolor']);
+ } elseif ($style['border']) {
+ $this->Rect($xpos, $y, $w, $h, 'D');
+ }
+ // set foreground color
+ $this->SetDrawColorArray($style['fgcolor']);
+ // print barcode cells
+ // for each row
+ for ($r = 0; $r < $rows; ++$r) {
+ $xr = $xstart;
+ // for each column
+ for ($c = 0; $c < $cols; ++$c) {
+ if ($arrcode['bcode'][$r][$c] == 1) {
+ // draw a single barcode cell
+ $this->Rect($xr, $ystart, $cw, $ch, 'F', array(), $style['fgcolor']);
+ }
+ $xr += $cw;
+ }
+ $ystart += $ch;
+ }
+ // restore original direction
+ $this->rtl = $tempRTL;
+ // restore previous settings
+ $this->setGraphicVars($gvars);
+ // set pointer to align the next text/objects
+ switch($align) {
+ case 'T':{
+ $this->y = $y;
+ $this->x = $this->img_rb_x;
+ break;
+ }
+ case 'M':{
+ $this->y = $y + round($h/2);
+ $this->x = $this->img_rb_x;
+ break;
+ }
+ case 'B':{
+ $this->y = $this->img_rb_y;
+ $this->x = $this->img_rb_x;
+ break;
+ }
+ case 'N':{
+ $this->SetY($this->img_rb_y);
+ break;
+ }
+ default:{
+ break;
+ }
+ }
+ $this->endlinex = $this->img_rb_x;
+ }
+
+ /**
+ * Returns an array containing current margins:
+ * <ul>
+ <li>$ret['left'] = left margin</li>
+ <li>$ret['right'] = right margin</li>
+ <li>$ret['top'] = top margin</li>
+ <li>$ret['bottom'] = bottom margin</li>
+ <li>$ret['header'] = header margin</li>
+ <li>$ret['footer'] = footer margin</li>
+ <li>$ret['cell'] = cell padding array</li>
+ <li>$ret['padding_left'] = cell left padding</li>
+ <li>$ret['padding_top'] = cell top padding</li>
+ <li>$ret['padding_right'] = cell right padding</li>
+ <li>$ret['padding_bottom'] = cell bottom padding</li>
+ * </ul>
+ * @return array containing all margins measures
+ * @public
+ * @since 3.2.000 (2008-06-23)
+ */
+ public function getMargins() {
+ $ret = array(
+ 'left' => $this->lMargin,
+ 'right' => $this->rMargin,
+ 'top' => $this->tMargin,
+ 'bottom' => $this->bMargin,
+ 'header' => $this->header_margin,
+ 'footer' => $this->footer_margin,
+ 'cell' => $this->cell_padding,
+ 'padding_left' => $this->cell_padding['L'],
+ 'padding_top' => $this->cell_padding['T'],
+ 'padding_right' => $this->cell_padding['R'],
+ 'padding_bottom' => $this->cell_padding['B']
+ );
+ return $ret;
+ }
+
+ /**
+ * Returns an array containing original margins:
+ * <ul>
+ <li>$ret['left'] = left margin</li>
+ <li>$ret['right'] = right margin</li>
+ * </ul>
+ * @return array containing all margins measures
+ * @public
+ * @since 4.0.012 (2008-07-24)
+ */
+ public function getOriginalMargins() {
+ $ret = array(
+ 'left' => $this->original_lMargin,
+ 'right' => $this->original_rMargin
+ );
+ return $ret;
+ }
+
+ /**
+ * Returns the current font size.
+ * @return current font size
+ * @public
+ * @since 3.2.000 (2008-06-23)
+ */
+ public function getFontSize() {
+ return $this->FontSize;
+ }
+
+ /**
+ * Returns the current font size in points unit.
+ * @return current font size in points unit
+ * @public
+ * @since 3.2.000 (2008-06-23)
+ */
+ public function getFontSizePt() {
+ return $this->FontSizePt;
+ }
+
+ /**
+ * Returns the current font family name.
+ * @return string current font family name
+ * @public
+ * @since 4.3.008 (2008-12-05)
+ */
+ public function getFontFamily() {
+ return $this->FontFamily;
+ }
+
+ /**
+ * Returns the current font style.
+ * @return string current font style
+ * @public
+ * @since 4.3.008 (2008-12-05)
+ */
+ public function getFontStyle() {
+ return $this->FontStyle;
+ }
+
+ /**
+ * Cleanup HTML code (requires HTML Tidy library).
+ * @param $html (string) htmlcode to fix
+ * @param $default_css (string) CSS commands to add
+ * @param $tagvs (array) parameters for setHtmlVSpace method
+ * @param $tidy_options (array) options for tidy_parse_string function
+ * @return string XHTML code cleaned up
+ * @author Nicola Asuni
+ * @public
+ * @since 5.9.017 (2010-11-16)
+ * @see setHtmlVSpace()
+ */
+ public function fixHTMLCode($html, $default_css='', $tagvs='', $tidy_options='') {
+ return TCPDF_STATIC::fixHTMLCode($html, $default_css, $tagvs, $tidy_options, $this->tagvspaces);
+ }
+
+ /**
+ * Returns the border width from CSS property
+ * @param $width (string) border width
+ * @return int with in user units
+ * @protected
+ * @since 5.7.000 (2010-08-02)
+ */
+ protected function getCSSBorderWidth($width) {
+ if ($width == 'thin') {
+ $width = (2 / $this->k);
+ } elseif ($width == 'medium') {
+ $width = (4 / $this->k);
+ } elseif ($width == 'thick') {
+ $width = (6 / $this->k);
+ } else {
+ $width = $this->getHTMLUnitToUnits($width, 1, 'px', false);
+ }
+ return $width;
+ }
+
+ /**
+ * Returns the border dash style from CSS property
+ * @param $style (string) border style to convert
+ * @return int sash style (return -1 in case of none or hidden border)
+ * @protected
+ * @since 5.7.000 (2010-08-02)
+ */
+ protected function getCSSBorderDashStyle($style) {
+ switch (strtolower($style)) {
+ case 'none':
+ case 'hidden': {
+ $dash = -1;
+ break;
+ }
+ case 'dotted': {
+ $dash = 1;
+ break;
+ }
+ case 'dashed': {
+ $dash = 3;
+ break;
+ }
+ case 'double':
+ case 'groove':
+ case 'ridge':
+ case 'inset':
+ case 'outset':
+ case 'solid':
+ default: {
+ $dash = 0;
+ break;
+ }
+ }
+ return $dash;
+ }
+
+ /**
+ * Returns the border style array from CSS border properties
+ * @param $cssborder (string) border properties
+ * @return array containing border properties
+ * @protected
+ * @since 5.7.000 (2010-08-02)
+ */
+ protected function getCSSBorderStyle($cssborder) {
+ $bprop = preg_split('/[\s]+/', trim($cssborder));
+ $border = array(); // value to be returned
+ switch (count($bprop)) {
+ case 3: {
+ $width = $bprop[0];
+ $style = $bprop[1];
+ $color = $bprop[2];
+ break;
+ }
+ case 2: {
+ $width = 'medium';
+ $style = $bprop[0];
+ $color = $bprop[1];
+ break;
+ }
+ case 1: {
+ $width = 'medium';
+ $style = $bprop[0];
+ $color = 'black';
+ break;
+ }
+ default: {
+ $width = 'medium';
+ $style = 'solid';
+ $color = 'black';
+ break;
+ }
+ }
+ if ($style == 'none') {
+ return array();
+ }
+ $border['cap'] = 'square';
+ $border['join'] = 'miter';
+ $border['dash'] = $this->getCSSBorderDashStyle($style);
+ if ($border['dash'] < 0) {
+ return array();
+ }
+ $border['width'] = $this->getCSSBorderWidth($width);
+ $border['color'] = TCPDF_COLORS::convertHTMLColorToDec($color, $this->spot_colors);
+ return $border;
+ }
+
+ /**
+ * Get the internal Cell padding from CSS attribute.
+ * @param $csspadding (string) padding properties
+ * @param $width (float) width of the containing element
+ * @return array of cell paddings
+ * @public
+ * @since 5.9.000 (2010-10-04)
+ */
+ public function getCSSPadding($csspadding, $width=0) {
+ $padding = preg_split('/[\s]+/', trim($csspadding));
+ $cell_padding = array(); // value to be returned
+ switch (count($padding)) {
+ case 4: {
+ $cell_padding['T'] = $padding[0];
+ $cell_padding['R'] = $padding[1];
+ $cell_padding['B'] = $padding[2];
+ $cell_padding['L'] = $padding[3];
+ break;
+ }
+ case 3: {
+ $cell_padding['T'] = $padding[0];
+ $cell_padding['R'] = $padding[1];
+ $cell_padding['B'] = $padding[2];
+ $cell_padding['L'] = $padding[1];
+ break;
+ }
+ case 2: {
+ $cell_padding['T'] = $padding[0];
+ $cell_padding['R'] = $padding[1];
+ $cell_padding['B'] = $padding[0];
+ $cell_padding['L'] = $padding[1];
+ break;
+ }
+ case 1: {
+ $cell_padding['T'] = $padding[0];
+ $cell_padding['R'] = $padding[0];
+ $cell_padding['B'] = $padding[0];
+ $cell_padding['L'] = $padding[0];
+ break;
+ }
+ default: {
+ return $this->cell_padding;
+ }
+ }
+ if ($width == 0) {
+ $width = $this->w - $this->lMargin - $this->rMargin;
+ }
+ $cell_padding['T'] = $this->getHTMLUnitToUnits($cell_padding['T'], $width, 'px', false);
+ $cell_padding['R'] = $this->getHTMLUnitToUnits($cell_padding['R'], $width, 'px', false);
+ $cell_padding['B'] = $this->getHTMLUnitToUnits($cell_padding['B'], $width, 'px', false);
+ $cell_padding['L'] = $this->getHTMLUnitToUnits($cell_padding['L'], $width, 'px', false);
+ return $cell_padding;
+ }
+
+ /**
+ * Get the internal Cell margin from CSS attribute.
+ * @param $cssmargin (string) margin properties
+ * @param $width (float) width of the containing element
+ * @return array of cell margins
+ * @public
+ * @since 5.9.000 (2010-10-04)
+ */
+ public function getCSSMargin($cssmargin, $width=0) {
+ $margin = preg_split('/[\s]+/', trim($cssmargin));
+ $cell_margin = array(); // value to be returned
+ switch (count($margin)) {
+ case 4: {
+ $cell_margin['T'] = $margin[0];
+ $cell_margin['R'] = $margin[1];
+ $cell_margin['B'] = $margin[2];
+ $cell_margin['L'] = $margin[3];
+ break;
+ }
+ case 3: {
+ $cell_margin['T'] = $margin[0];
+ $cell_margin['R'] = $margin[1];
+ $cell_margin['B'] = $margin[2];
+ $cell_margin['L'] = $margin[1];
+ break;
+ }
+ case 2: {
+ $cell_margin['T'] = $margin[0];
+ $cell_margin['R'] = $margin[1];
+ $cell_margin['B'] = $margin[0];
+ $cell_margin['L'] = $margin[1];
+ break;
+ }
+ case 1: {
+ $cell_margin['T'] = $margin[0];
+ $cell_margin['R'] = $margin[0];
+ $cell_margin['B'] = $margin[0];
+ $cell_margin['L'] = $margin[0];
+ break;
+ }
+ default: {
+ return $this->cell_margin;
+ }
+ }
+ if ($width == 0) {
+ $width = $this->w - $this->lMargin - $this->rMargin;
+ }
+ $cell_margin['T'] = $this->getHTMLUnitToUnits(str_replace('auto', '0', $cell_margin['T']), $width, 'px', false);
+ $cell_margin['R'] = $this->getHTMLUnitToUnits(str_replace('auto', '0', $cell_margin['R']), $width, 'px', false);
+ $cell_margin['B'] = $this->getHTMLUnitToUnits(str_replace('auto', '0', $cell_margin['B']), $width, 'px', false);
+ $cell_margin['L'] = $this->getHTMLUnitToUnits(str_replace('auto', '0', $cell_margin['L']), $width, 'px', false);
+ return $cell_margin;
+ }
+
+ /**
+ * Get the border-spacing from CSS attribute.
+ * @param $cssbspace (string) border-spacing CSS properties
+ * @param $width (float) width of the containing element
+ * @return array of border spacings
+ * @public
+ * @since 5.9.010 (2010-10-27)
+ */
+ public function getCSSBorderMargin($cssbspace, $width=0) {
+ $space = preg_split('/[\s]+/', trim($cssbspace));
+ $border_spacing = array(); // value to be returned
+ switch (count($space)) {
+ case 2: {
+ $border_spacing['H'] = $space[0];
+ $border_spacing['V'] = $space[1];
+ break;
+ }
+ case 1: {
+ $border_spacing['H'] = $space[0];
+ $border_spacing['V'] = $space[0];
+ break;
+ }
+ default: {
+ return array('H' => 0, 'V' => 0);
+ }
+ }
+ if ($width == 0) {
+ $width = $this->w - $this->lMargin - $this->rMargin;
+ }
+ $border_spacing['H'] = $this->getHTMLUnitToUnits($border_spacing['H'], $width, 'px', false);
+ $border_spacing['V'] = $this->getHTMLUnitToUnits($border_spacing['V'], $width, 'px', false);
+ return $border_spacing;
+ }
+
+ /**
+ * Returns the letter-spacing value from CSS value
+ * @param $spacing (string) letter-spacing value
+ * @param $parent (float) font spacing (tracking) value of the parent element
+ * @return float quantity to increases or decreases the space between characters in a text.
+ * @protected
+ * @since 5.9.000 (2010-10-02)
+ */
+ protected function getCSSFontSpacing($spacing, $parent=0) {
+ $val = 0; // value to be returned
+ $spacing = trim($spacing);
+ switch ($spacing) {
+ case 'normal': {
+ $val = 0;
+ break;
+ }
+ case 'inherit': {
+ if ($parent == 'normal') {
+ $val = 0;
+ } else {
+ $val = $parent;
+ }
+ break;
+ }
+ default: {
+ $val = $this->getHTMLUnitToUnits($spacing, 0, 'px', false);
+ }
+ }
+ return $val;
+ }
+
+ /**
+ * Returns the percentage of font stretching from CSS value
+ * @param $stretch (string) stretch mode
+ * @param $parent (float) stretch value of the parent element
+ * @return float font stretching percentage
+ * @protected
+ * @since 5.9.000 (2010-10-02)
+ */
+ protected function getCSSFontStretching($stretch, $parent=100) {
+ $val = 100; // value to be returned
+ $stretch = trim($stretch);
+ switch ($stretch) {
+ case 'ultra-condensed': {
+ $val = 40;
+ break;
+ }
+ case 'extra-condensed': {
+ $val = 55;
+ break;
+ }
+ case 'condensed': {
+ $val = 70;
+ break;
+ }
+ case 'semi-condensed': {
+ $val = 85;
+ break;
+ }
+ case 'normal': {
+ $val = 100;
+ break;
+ }
+ case 'semi-expanded': {
+ $val = 115;
+ break;
+ }
+ case 'expanded': {
+ $val = 130;
+ break;
+ }
+ case 'extra-expanded': {
+ $val = 145;
+ break;
+ }
+ case 'ultra-expanded': {
+ $val = 160;
+ break;
+ }
+ case 'wider': {
+ $val = ($parent + 10);
+ break;
+ }
+ case 'narrower': {
+ $val = ($parent - 10);
+ break;
+ }
+ case 'inherit': {
+ if ($parent == 'normal') {
+ $val = 100;
+ } else {
+ $val = $parent;
+ }
+ break;
+ }
+ default: {
+ $val = $this->getHTMLUnitToUnits($stretch, 100, '%', false);
+ }
+ }
+ return $val;
+ }
+
+ /**
+ * Convert HTML string containing font size value to points
+ * @param $val (string) String containing font size value and unit.
+ * @param $refsize (float) Reference font size in points.
+ * @param $parent_size (float) Parent font size in points.
+ * @param $defaultunit (string) Default unit (can be one of the following: %, em, ex, px, in, mm, pc, pt).
+ * @return float value in points
+ * @public
+ */
+ public function getHTMLFontUnits($val, $refsize=12, $parent_size=12, $defaultunit='pt') {
+ $refsize = TCPDF_FONTS::getFontRefSize($refsize);
+ $parent_size = TCPDF_FONTS::getFontRefSize($parent_size, $refsize);
+ switch ($val) {
+ case 'xx-small': {
+ $size = ($refsize - 4);
+ break;
+ }
+ case 'x-small': {
+ $size = ($refsize - 3);
+ break;
+ }
+ case 'small': {
+ $size = ($refsize - 2);
+ break;
+ }
+ case 'medium': {
+ $size = $refsize;
+ break;
+ }
+ case 'large': {
+ $size = ($refsize + 2);
+ break;
+ }
+ case 'x-large': {
+ $size = ($refsize + 4);
+ break;
+ }
+ case 'xx-large': {
+ $size = ($refsize + 6);
+ break;
+ }
+ case 'smaller': {
+ $size = ($parent_size - 3);
+ break;
+ }
+ case 'larger': {
+ $size = ($parent_size + 3);
+ break;
+ }
+ default: {
+ $size = $this->getHTMLUnitToUnits($val, $parent_size, $defaultunit, true);
+ }
+ }
+ return $size;
+ }
+
+ /**
+ * Returns the HTML DOM array.
+ * @param $html (string) html code
+ * @return array
+ * @protected
+ * @since 3.2.000 (2008-06-20)
+ */
+ protected function getHtmlDomArray($html) {
+ // array of CSS styles ( selector => properties).
+ $css = array();
+ // get CSS array defined at previous call
+ $matches = array();
+ if (preg_match_all('/<cssarray>([^\<]*)<\/cssarray>/isU', $html, $matches) > 0) {
+ if (isset($matches[1][0])) {
+ $css = array_merge($css, unserialize($this->unhtmlentities($matches[1][0])));
+ }
+ $html = preg_replace('/<cssarray>(.*?)<\/cssarray>/isU', '', $html);
+ }
+ // extract external CSS files
+ $matches = array();
+ if (preg_match_all('/<link([^\>]*)>/isU', $html, $matches) > 0) {
+ foreach ($matches[1] as $key => $link) {
+ $type = array();
+ if (preg_match('/type[\s]*=[\s]*"text\/css"/', $link, $type)) {
+ $type = array();
+ preg_match('/media[\s]*=[\s]*"([^"]*)"/', $link, $type);
+ // get 'all' and 'print' media, other media types are discarded
+ // (all, braille, embossed, handheld, print, projection, screen, speech, tty, tv)
+ if (empty($type) OR (isset($type[1]) AND (($type[1] == 'all') OR ($type[1] == 'print')))) {
+ $type = array();
+ if (preg_match('/href[\s]*=[\s]*"([^"]*)"/', $link, $type) > 0) {
+ // read CSS data file
+ $cssdata = TCPDF_STATIC::fileGetContents(trim($type[1]));
+ if (($cssdata !== FALSE) AND (strlen($cssdata) > 0)) {
+ $css = array_merge($css, TCPDF_STATIC::extractCSSproperties($cssdata));
+ }
+ }
+ }
+ }
+ }
+ }
+ // extract style tags
+ $matches = array();
+ if (preg_match_all('/<style([^\>]*)>([^\<]*)<\/style>/isU', $html, $matches) > 0) {
+ foreach ($matches[1] as $key => $media) {
+ $type = array();
+ preg_match('/media[\s]*=[\s]*"([^"]*)"/', $media, $type);
+ // get 'all' and 'print' media, other media types are discarded
+ // (all, braille, embossed, handheld, print, projection, screen, speech, tty, tv)
+ if (empty($type) OR (isset($type[1]) AND (($type[1] == 'all') OR ($type[1] == 'print')))) {
+ $cssdata = $matches[2][$key];
+ $css = array_merge($css, TCPDF_STATIC::extractCSSproperties($cssdata));
+ }
+ }
+ }
+ // create a special tag to contain the CSS array (used for table content)
+ $csstagarray = '<cssarray>'.htmlentities(serialize($css)).'</cssarray>';
+ // remove head and style blocks
+ $html = preg_replace('/<head([^\>]*)>(.*?)<\/head>/siU', '', $html);
+ $html = preg_replace('/<style([^\>]*)>([^\<]*)<\/style>/isU', '', $html);
+ // define block tags
+ $blocktags = array('blockquote','br','dd','dl','div','dt','h1','h2','h3','h4','h5','h6','hr','li','ol','p','pre','ul','tcpdf','table','tr','td');
+ // define self-closing tags
+ $selfclosingtags = array('area','base','basefont','br','hr','input','img','link','meta');
+ // remove all unsupported tags (the line below lists all supported tags)
+ $html = strip_tags($html, '<marker/><a><b><blockquote><body><br><br/><dd><del><div><dl><dt><em><font><form><h1><h2><h3><h4><h5><h6><hr><hr/><i><img><input><label><li><ol><option><p><pre><s><select><small><span><strike><strong><sub><sup><table><tablehead><tcpdf><td><textarea><th><thead><tr><tt><u><ul>');
+ //replace some blank characters
+ $html = preg_replace('/<pre/', '<xre', $html); // preserve pre tag
+ $html = preg_replace('/<(table|tr|td|th|tcpdf|blockquote|dd|div|dl|dt|form|h1|h2|h3|h4|h5|h6|br|hr|li|ol|ul|p)([^\>]*)>[\n\r\t]+/', '<\\1\\2>', $html);
+ $html = preg_replace('@(\r\n|\r)@', "\n", $html);
+ $repTable = array("\t" => ' ', "\0" => ' ', "\x0B" => ' ', "\\" => "\\\\");
+ $html = strtr($html, $repTable);
+ $offset = 0;
+ while (($offset < strlen($html)) AND ($pos = strpos($html, '</pre>', $offset)) !== false) {
+ $html_a = substr($html, 0, $offset);
+ $html_b = substr($html, $offset, ($pos - $offset + 6));
+ while (preg_match("'<xre([^\>]*)>(.*?)\n(.*?)</pre>'si", $html_b)) {
+ // preserve newlines on <pre> tag
+ $html_b = preg_replace("'<xre([^\>]*)>(.*?)\n(.*?)</pre>'si", "<xre\\1>\\2<br />\\3</pre>", $html_b);
+ }
+ while (preg_match("'<xre([^\>]*)>(.*?)".$this->re_space['p']."(.*?)</pre>'".$this->re_space['m'], $html_b)) {
+ // preserve spaces on <pre> tag
+ $html_b = preg_replace("'<xre([^\>]*)>(.*?)".$this->re_space['p']."(.*?)</pre>'".$this->re_space['m'], "<xre\\1>\\2&nbsp;\\3</pre>", $html_b);
+ }
+ $html = $html_a.$html_b.substr($html, $pos + 6);
+ $offset = strlen($html_a.$html_b);
+ }
+ $offset = 0;
+ while (($offset < strlen($html)) AND ($pos = strpos($html, '</textarea>', $offset)) !== false) {
+ $html_a = substr($html, 0, $offset);
+ $html_b = substr($html, $offset, ($pos - $offset + 11));
+ while (preg_match("'<textarea([^\>]*)>(.*?)\n(.*?)</textarea>'si", $html_b)) {
+ // preserve newlines on <textarea> tag
+ $html_b = preg_replace("'<textarea([^\>]*)>(.*?)\n(.*?)</textarea>'si", "<textarea\\1>\\2<TBR>\\3</textarea>", $html_b);
+ $html_b = preg_replace("'<textarea([^\>]*)>(.*?)[\"](.*?)</textarea>'si", "<textarea\\1>\\2''\\3</textarea>", $html_b);
+ }
+ $html = $html_a.$html_b.substr($html, $pos + 11);
+ $offset = strlen($html_a.$html_b);
+ }
+ $html = preg_replace('/([\s]*)<option/si', '<option', $html);
+ $html = preg_replace('/<\/option>([\s]*)/si', '</option>', $html);
+ $offset = 0;
+ while (($offset < strlen($html)) AND ($pos = strpos($html, '</option>', $offset)) !== false) {
+ $html_a = substr($html, 0, $offset);
+ $html_b = substr($html, $offset, ($pos - $offset + 9));
+ while (preg_match("'<option([^\>]*)>(.*?)</option>'si", $html_b)) {
+ $html_b = preg_replace("'<option([\s]+)value=\"([^\"]*)\"([^\>]*)>(.*?)</option>'si", "\\2#!TaB!#\\4#!NwL!#", $html_b);
+ $html_b = preg_replace("'<option([^\>]*)>(.*?)</option>'si", "\\2#!NwL!#", $html_b);
+ }
+ $html = $html_a.$html_b.substr($html, $pos + 9);
+ $offset = strlen($html_a.$html_b);
+ }
+ if (preg_match("'</select'si", $html)) {
+ $html = preg_replace("'<select([^\>]*)>'si", "<select\\1 opt=\"", $html);
+ $html = preg_replace("'#!NwL!#</select>'si", "\" />", $html);
+ }
+ $html = str_replace("\n", ' ', $html);
+ // restore textarea newlines
+ $html = str_replace('<TBR>', "\n", $html);
+ // remove extra spaces from code
+ $html = preg_replace('/[\s]+<\/(table|tr|ul|ol|dl)>/', '</\\1>', $html);
+ $html = preg_replace('/'.$this->re_space['p'].'+<\/(td|th|li|dt|dd)>/'.$this->re_space['m'], '</\\1>', $html);
+ $html = preg_replace('/[\s]+<(tr|td|th|li|dt|dd)/', '<\\1', $html);
+ $html = preg_replace('/'.$this->re_space['p'].'+<(ul|ol|dl|br)/'.$this->re_space['m'], '<\\1', $html);
+ $html = preg_replace('/<\/(table|tr|td|th|blockquote|dd|dt|dl|div|dt|h1|h2|h3|h4|h5|h6|hr|li|ol|ul|p)>[\s]+</', '</\\1><', $html);
+ $html = preg_replace('/<\/(td|th)>/', '<marker style="font-size:0"/></\\1>', $html);
+ $html = preg_replace('/<\/table>([\s]*)<marker style="font-size:0"\/>/', '</table>', $html);
+ $html = preg_replace('/'.$this->re_space['p'].'+<img/'.$this->re_space['m'], chr(32).'<img', $html);
+ $html = preg_replace('/<img([^\>]*)>[\s]+([^\<])/xi', '<img\\1>&nbsp;\\2', $html);
+ $html = preg_replace('/<img([^\>]*)>/xi', '<img\\1><span><marker style="font-size:0"/></span>', $html);
+ $html = preg_replace('/<xre/', '<pre', $html); // restore pre tag
+ $html = preg_replace('/<textarea([^\>]*)>([^\<]*)<\/textarea>/xi', '<textarea\\1 value="\\2" />', $html);
+ $html = preg_replace('/<li([^\>]*)><\/li>/', '<li\\1>&nbsp;</li>', $html);
+ $html = preg_replace('/<li([^\>]*)>'.$this->re_space['p'].'*<img/'.$this->re_space['m'], '<li\\1><font size="1">&nbsp;</font><img', $html);
+ $html = preg_replace('/<([^\>\/]*)>[\s]/', '<\\1>&nbsp;', $html); // preserve some spaces
+ $html = preg_replace('/[\s]<\/([^\>]*)>/', '&nbsp;</\\1>', $html); // preserve some spaces
+ $html = preg_replace('/<su([bp])/', '<zws/><su\\1', $html); // fix sub/sup alignment
+ $html = preg_replace('/<\/su([bp])>/', '</su\\1><zws/>', $html); // fix sub/sup alignment
+ $html = preg_replace('/'.$this->re_space['p'].'+/'.$this->re_space['m'], chr(32), $html); // replace multiple spaces with a single space
+ // trim string
+ $html = $this->stringTrim($html);
+ // fix br tag after li
+ $html = preg_replace('/<li><br([^\>]*)>/', '<li> <br\\1>', $html);
+ // fix first image tag alignment
+ $html = preg_replace('/^<img/', '<span style="font-size:0"><br /></span> <img', $html, 1);
+ // pattern for generic tag
+ $tagpattern = '/(<[^>]+>)/';
+ // explodes the string
+ $a = preg_split($tagpattern, $html, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
+ // count elements
+ $maxel = count($a);
+ $elkey = 0;
+ $key = 0;
+ // create an array of elements
+ $dom = array();
+ $dom[$key] = array();
+ // set inheritable properties fot the first void element
+ // possible inheritable properties are: azimuth, border-collapse, border-spacing, caption-side, color, cursor, direction, empty-cells, font, font-family, font-stretch, font-size, font-size-adjust, font-style, font-variant, font-weight, letter-spacing, line-height, list-style, list-style-image, list-style-position, list-style-type, orphans, page, page-break-inside, quotes, speak, speak-header, text-align, text-indent, text-transform, volume, white-space, widows, word-spacing
+ $dom[$key]['tag'] = false;
+ $dom[$key]['block'] = false;
+ $dom[$key]['value'] = '';
+ $dom[$key]['parent'] = 0;
+ $dom[$key]['hide'] = false;
+ $dom[$key]['fontname'] = $this->FontFamily;
+ $dom[$key]['fontstyle'] = $this->FontStyle;
+ $dom[$key]['fontsize'] = $this->FontSizePt;
+ $dom[$key]['font-stretch'] = $this->font_stretching;
+ $dom[$key]['letter-spacing'] = $this->font_spacing;
+ $dom[$key]['stroke'] = $this->textstrokewidth;
+ $dom[$key]['fill'] = (($this->textrendermode % 2) == 0);
+ $dom[$key]['clip'] = ($this->textrendermode > 3);
+ $dom[$key]['line-height'] = $this->cell_height_ratio;
+ $dom[$key]['bgcolor'] = false;
+ $dom[$key]['fgcolor'] = $this->fgcolor; // color
+ $dom[$key]['strokecolor'] = $this->strokecolor;
+ $dom[$key]['align'] = '';
+ $dom[$key]['listtype'] = '';
+ $dom[$key]['text-indent'] = 0;
+ $dom[$key]['border'] = array();
+ $dom[$key]['dir'] = $this->rtl?'rtl':'ltr';
+ $thead = false; // true when we are inside the THEAD tag
+ ++$key;
+ $level = array();
+ array_push($level, 0); // root
+ while ($elkey < $maxel) {
+ $dom[$key] = array();
+ $element = $a[$elkey];
+ $dom[$key]['elkey'] = $elkey;
+ if (preg_match($tagpattern, $element)) {
+ // html tag
+ $element = substr($element, 1, -1);
+ // get tag name
+ preg_match('/[\/]?([a-zA-Z0-9]*)/', $element, $tag);
+ $tagname = strtolower($tag[1]);
+ // check if we are inside a table header
+ if ($tagname == 'thead') {
+ if ($element{0} == '/') {
+ $thead = false;
+ } else {
+ $thead = true;
+ }
+ ++$elkey;
+ continue;
+ }
+ $dom[$key]['tag'] = true;
+ $dom[$key]['value'] = $tagname;
+ if (in_array($dom[$key]['value'], $blocktags)) {
+ $dom[$key]['block'] = true;
+ } else {
+ $dom[$key]['block'] = false;
+ }
+ if ($element{0} == '/') {
+ // *** closing html tag
+ $dom[$key]['opening'] = false;
+ $dom[$key]['parent'] = end($level);
+ array_pop($level);
+ $dom[$key]['hide'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['hide'];
+ $dom[$key]['fontname'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['fontname'];
+ $dom[$key]['fontstyle'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['fontstyle'];
+ $dom[$key]['fontsize'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['fontsize'];
+ $dom[$key]['font-stretch'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['font-stretch'];
+ $dom[$key]['letter-spacing'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['letter-spacing'];
+ $dom[$key]['stroke'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['stroke'];
+ $dom[$key]['fill'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['fill'];
+ $dom[$key]['clip'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['clip'];
+ $dom[$key]['line-height'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['line-height'];
+ $dom[$key]['bgcolor'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['bgcolor'];
+ $dom[$key]['fgcolor'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['fgcolor'];
+ $dom[$key]['strokecolor'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['strokecolor'];
+ $dom[$key]['align'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['align'];
+ $dom[$key]['dir'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['dir'];
+ if (isset($dom[($dom[($dom[$key]['parent'])]['parent'])]['listtype'])) {
+ $dom[$key]['listtype'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['listtype'];
+ }
+ // set the number of columns in table tag
+ if (($dom[$key]['value'] == 'tr') AND (!isset($dom[($dom[($dom[$key]['parent'])]['parent'])]['cols']))) {
+ $dom[($dom[($dom[$key]['parent'])]['parent'])]['cols'] = $dom[($dom[$key]['parent'])]['cols'];
+ }
+ if (($dom[$key]['value'] == 'td') OR ($dom[$key]['value'] == 'th')) {
+ $dom[($dom[$key]['parent'])]['content'] = $csstagarray;
+ for ($i = ($dom[$key]['parent'] + 1); $i < $key; ++$i) {
+ $dom[($dom[$key]['parent'])]['content'] .= $a[$dom[$i]['elkey']];
+ }
+ $key = $i;
+ // mark nested tables
+ $dom[($dom[$key]['parent'])]['content'] = str_replace('<table', '<table nested="true"', $dom[($dom[$key]['parent'])]['content']);
+ // remove thead sections from nested tables
+ $dom[($dom[$key]['parent'])]['content'] = str_replace('<thead>', '', $dom[($dom[$key]['parent'])]['content']);
+ $dom[($dom[$key]['parent'])]['content'] = str_replace('</thead>', '', $dom[($dom[$key]['parent'])]['content']);
+ }
+ // store header rows on a new table
+ if (($dom[$key]['value'] == 'tr') AND ($dom[($dom[$key]['parent'])]['thead'] === true)) {
+ if (TCPDF_STATIC::empty_string($dom[($dom[($dom[$key]['parent'])]['parent'])]['thead'])) {
+ $dom[($dom[($dom[$key]['parent'])]['parent'])]['thead'] = $csstagarray.$a[$dom[($dom[($dom[$key]['parent'])]['parent'])]['elkey']];
+ }
+ for ($i = $dom[$key]['parent']; $i <= $key; ++$i) {
+ $dom[($dom[($dom[$key]['parent'])]['parent'])]['thead'] .= $a[$dom[$i]['elkey']];
+ }
+ if (!isset($dom[($dom[$key]['parent'])]['attribute'])) {
+ $dom[($dom[$key]['parent'])]['attribute'] = array();
+ }
+ // header elements must be always contained in a single page
+ $dom[($dom[$key]['parent'])]['attribute']['nobr'] = 'true';
+ }
+ if (($dom[$key]['value'] == 'table') AND (!TCPDF_STATIC::empty_string($dom[($dom[$key]['parent'])]['thead']))) {
+ // remove the nobr attributes from the table header
+ $dom[($dom[$key]['parent'])]['thead'] = str_replace(' nobr="true"', '', $dom[($dom[$key]['parent'])]['thead']);
+ $dom[($dom[$key]['parent'])]['thead'] .= '</tablehead>';
+ }
+ } else {
+ // *** opening or self-closing html tag
+ $dom[$key]['opening'] = true;
+ $dom[$key]['parent'] = end($level);
+ if ((substr($element, -1, 1) == '/') OR (in_array($dom[$key]['value'], $selfclosingtags))) {
+ // self-closing tag
+ $dom[$key]['self'] = true;
+ } else {
+ // opening tag
+ array_push($level, $key);
+ $dom[$key]['self'] = false;
+ }
+ // copy some values from parent
+ $parentkey = 0;
+ if ($key > 0) {
+ $parentkey = $dom[$key]['parent'];
+ $dom[$key]['hide'] = $dom[$parentkey]['hide'];
+ $dom[$key]['fontname'] = $dom[$parentkey]['fontname'];
+ $dom[$key]['fontstyle'] = $dom[$parentkey]['fontstyle'];
+ $dom[$key]['fontsize'] = $dom[$parentkey]['fontsize'];
+ $dom[$key]['font-stretch'] = $dom[$parentkey]['font-stretch'];
+ $dom[$key]['letter-spacing'] = $dom[$parentkey]['letter-spacing'];
+ $dom[$key]['stroke'] = $dom[$parentkey]['stroke'];
+ $dom[$key]['fill'] = $dom[$parentkey]['fill'];
+ $dom[$key]['clip'] = $dom[$parentkey]['clip'];
+ $dom[$key]['line-height'] = $dom[$parentkey]['line-height'];
+ $dom[$key]['bgcolor'] = $dom[$parentkey]['bgcolor'];
+ $dom[$key]['fgcolor'] = $dom[$parentkey]['fgcolor'];
+ $dom[$key]['strokecolor'] = $dom[$parentkey]['strokecolor'];
+ $dom[$key]['align'] = $dom[$parentkey]['align'];
+ $dom[$key]['listtype'] = $dom[$parentkey]['listtype'];
+ $dom[$key]['text-indent'] = $dom[$parentkey]['text-indent'];
+ $dom[$key]['border'] = array();
+ $dom[$key]['dir'] = $dom[$parentkey]['dir'];
+ }
+ // get attributes
+ preg_match_all('/([^=\s]*)[\s]*=[\s]*"([^"]*)"/', $element, $attr_array, PREG_PATTERN_ORDER);
+ $dom[$key]['attribute'] = array(); // reset attribute array
+ while (list($id, $name) = each($attr_array[1])) {
+ $dom[$key]['attribute'][strtolower($name)] = $attr_array[2][$id];
+ }
+ if (!empty($css)) {
+ // merge CSS style to current style
+ list($dom[$key]['csssel'], $dom[$key]['cssdata']) = TCPDF_STATIC::getCSSdataArray($dom, $key, $css);
+ $dom[$key]['attribute']['style'] = TCPDF_STATIC::getTagStyleFromCSSarray($dom[$key]['cssdata']);
+ }
+ // split style attributes
+ if (isset($dom[$key]['attribute']['style']) AND !empty($dom[$key]['attribute']['style'])) {
+ // get style attributes
+ preg_match_all('/([^;:\s]*):([^;]*)/', $dom[$key]['attribute']['style'], $style_array, PREG_PATTERN_ORDER);
+ $dom[$key]['style'] = array(); // reset style attribute array
+ while (list($id, $name) = each($style_array[1])) {
+ // in case of duplicate attribute the last replace the previous
+ $dom[$key]['style'][strtolower($name)] = trim($style_array[2][$id]);
+ }
+ // --- get some style attributes ---
+ // text direction
+ if (isset($dom[$key]['style']['direction'])) {
+ $dom[$key]['dir'] = $dom[$key]['style']['direction'];
+ }
+ // display
+ if (isset($dom[$key]['style']['display'])) {
+ $dom[$key]['hide'] = (trim(strtolower($dom[$key]['style']['display'])) == 'none');
+ }
+ // font family
+ if (isset($dom[$key]['style']['font-family'])) {
+ $dom[$key]['fontname'] = $this->getFontFamilyName($dom[$key]['style']['font-family']);
+ }
+ // list-style-type
+ if (isset($dom[$key]['style']['list-style-type'])) {
+ $dom[$key]['listtype'] = trim(strtolower($dom[$key]['style']['list-style-type']));
+ if ($dom[$key]['listtype'] == 'inherit') {
+ $dom[$key]['listtype'] = $dom[$parentkey]['listtype'];
+ }
+ }
+ // text-indent
+ if (isset($dom[$key]['style']['text-indent'])) {
+ $dom[$key]['text-indent'] = $this->getHTMLUnitToUnits($dom[$key]['style']['text-indent']);
+ if ($dom[$key]['text-indent'] == 'inherit') {
+ $dom[$key]['text-indent'] = $dom[$parentkey]['text-indent'];
+ }
+ }
+ // font size
+ if (isset($dom[$key]['style']['font-size'])) {
+ $fsize = trim($dom[$key]['style']['font-size']);
+ $dom[$key]['fontsize'] = $this->getHTMLFontUnits($fsize, $dom[0]['fontsize'], $dom[$parentkey]['fontsize'], 'pt');
+ }
+ // font-stretch
+ if (isset($dom[$key]['style']['font-stretch'])) {
+ $dom[$key]['font-stretch'] = $this->getCSSFontStretching($dom[$key]['style']['font-stretch'], $dom[$parentkey]['font-stretch']);
+ }
+ // letter-spacing
+ if (isset($dom[$key]['style']['letter-spacing'])) {
+ $dom[$key]['letter-spacing'] = $this->getCSSFontSpacing($dom[$key]['style']['letter-spacing'], $dom[$parentkey]['letter-spacing']);
+ }
+ // line-height
+ if (isset($dom[$key]['style']['line-height'])) {
+ $lineheight = trim($dom[$key]['style']['line-height']);
+ switch ($lineheight) {
+ // A normal line height. This is default
+ case 'normal': {
+ $dom[$key]['line-height'] = $dom[0]['line-height'];
+ break;
+ }
+ default: {
+ if (is_numeric($lineheight)) {
+ $lineheight = $lineheight * 100;
+ }
+ $dom[$key]['line-height'] = $this->getHTMLUnitToUnits($lineheight, 1, '%', true);
+ }
+ }
+ }
+ // font style
+ if (isset($dom[$key]['style']['font-weight'])) {
+ if (strtolower($dom[$key]['style']['font-weight']{0}) == 'n') {
+ if (strpos($dom[$key]['fontstyle'], 'B') !== false) {
+ $dom[$key]['fontstyle'] = str_replace('B', '', $dom[$key]['fontstyle']);
+ }
+ } elseif (strtolower($dom[$key]['style']['font-weight']{0}) == 'b') {
+ $dom[$key]['fontstyle'] .= 'B';
+ }
+ }
+ if (isset($dom[$key]['style']['font-style']) AND (strtolower($dom[$key]['style']['font-style']{0}) == 'i')) {
+ $dom[$key]['fontstyle'] .= 'I';
+ }
+ // font color
+ if (isset($dom[$key]['style']['color']) AND (!TCPDF_STATIC::empty_string($dom[$key]['style']['color']))) {
+ $dom[$key]['fgcolor'] = TCPDF_COLORS::convertHTMLColorToDec($dom[$key]['style']['color'], $this->spot_colors);
+ } elseif ($dom[$key]['value'] == 'a') {
+ $dom[$key]['fgcolor'] = $this->htmlLinkColorArray;
+ }
+ // background color
+ if (isset($dom[$key]['style']['background-color']) AND (!TCPDF_STATIC::empty_string($dom[$key]['style']['background-color']))) {
+ $dom[$key]['bgcolor'] = TCPDF_COLORS::convertHTMLColorToDec($dom[$key]['style']['background-color'], $this->spot_colors);
+ }
+ // text-decoration
+ if (isset($dom[$key]['style']['text-decoration'])) {
+ $decors = explode(' ', strtolower($dom[$key]['style']['text-decoration']));
+ foreach ($decors as $dec) {
+ $dec = trim($dec);
+ if (!TCPDF_STATIC::empty_string($dec)) {
+ if ($dec{0} == 'u') {
+ // underline
+ $dom[$key]['fontstyle'] .= 'U';
+ } elseif ($dec{0} == 'l') {
+ // line-through
+ $dom[$key]['fontstyle'] .= 'D';
+ } elseif ($dec{0} == 'o') {
+ // overline
+ $dom[$key]['fontstyle'] .= 'O';
+ }
+ }
+ }
+ } elseif ($dom[$key]['value'] == 'a') {
+ $dom[$key]['fontstyle'] = $this->htmlLinkFontStyle;
+ }
+ // check for width attribute
+ if (isset($dom[$key]['style']['width'])) {
+ $dom[$key]['width'] = $dom[$key]['style']['width'];
+ }
+ // check for height attribute
+ if (isset($dom[$key]['style']['height'])) {
+ $dom[$key]['height'] = $dom[$key]['style']['height'];
+ }
+ // check for text alignment
+ if (isset($dom[$key]['style']['text-align'])) {
+ $dom[$key]['align'] = strtoupper($dom[$key]['style']['text-align']{0});
+ }
+ // check for CSS border properties
+ if (isset($dom[$key]['style']['border'])) {
+ $borderstyle = $this->getCSSBorderStyle($dom[$key]['style']['border']);
+ if (!empty($borderstyle)) {
+ $dom[$key]['border']['LTRB'] = $borderstyle;
+ }
+ }
+ if (isset($dom[$key]['style']['border-color'])) {
+ $brd_colors = preg_split('/[\s]+/', trim($dom[$key]['style']['border-color']));
+ if (isset($brd_colors[3])) {
+ $dom[$key]['border']['L']['color'] = TCPDF_COLORS::convertHTMLColorToDec($brd_colors[3], $this->spot_colors);
+ }
+ if (isset($brd_colors[1])) {
+ $dom[$key]['border']['R']['color'] = TCPDF_COLORS::convertHTMLColorToDec($brd_colors[1], $this->spot_colors);
+ }
+ if (isset($brd_colors[0])) {
+ $dom[$key]['border']['T']['color'] = TCPDF_COLORS::convertHTMLColorToDec($brd_colors[0], $this->spot_colors);
+ }
+ if (isset($brd_colors[2])) {
+ $dom[$key]['border']['B']['color'] = TCPDF_COLORS::convertHTMLColorToDec($brd_colors[2], $this->spot_colors);
+ }
+ }
+ if (isset($dom[$key]['style']['border-width'])) {
+ $brd_widths = preg_split('/[\s]+/', trim($dom[$key]['style']['border-width']));
+ if (isset($brd_widths[3])) {
+ $dom[$key]['border']['L']['width'] = $this->getCSSBorderWidth($brd_widths[3]);
+ }
+ if (isset($brd_widths[1])) {
+ $dom[$key]['border']['R']['width'] = $this->getCSSBorderWidth($brd_widths[1]);
+ }
+ if (isset($brd_widths[0])) {
+ $dom[$key]['border']['T']['width'] = $this->getCSSBorderWidth($brd_widths[0]);
+ }
+ if (isset($brd_widths[2])) {
+ $dom[$key]['border']['B']['width'] = $this->getCSSBorderWidth($brd_widths[2]);
+ }
+ }
+ if (isset($dom[$key]['style']['border-style'])) {
+ $brd_styles = preg_split('/[\s]+/', trim($dom[$key]['style']['border-style']));
+ if (isset($brd_styles[3]) AND ($brd_styles[3]!='none')) {
+ $dom[$key]['border']['L']['cap'] = 'square';
+ $dom[$key]['border']['L']['join'] = 'miter';
+ $dom[$key]['border']['L']['dash'] = $this->getCSSBorderDashStyle($brd_styles[3]);
+ if ($dom[$key]['border']['L']['dash'] < 0) {
+ $dom[$key]['border']['L'] = array();
+ }
+ }
+ if (isset($brd_styles[1])) {
+ $dom[$key]['border']['R']['cap'] = 'square';
+ $dom[$key]['border']['R']['join'] = 'miter';
+ $dom[$key]['border']['R']['dash'] = $this->getCSSBorderDashStyle($brd_styles[1]);
+ if ($dom[$key]['border']['R']['dash'] < 0) {
+ $dom[$key]['border']['R'] = array();
+ }
+ }
+ if (isset($brd_styles[0])) {
+ $dom[$key]['border']['T']['cap'] = 'square';
+ $dom[$key]['border']['T']['join'] = 'miter';
+ $dom[$key]['border']['T']['dash'] = $this->getCSSBorderDashStyle($brd_styles[0]);
+ if ($dom[$key]['border']['T']['dash'] < 0) {
+ $dom[$key]['border']['T'] = array();
+ }
+ }
+ if (isset($brd_styles[2])) {
+ $dom[$key]['border']['B']['cap'] = 'square';
+ $dom[$key]['border']['B']['join'] = 'miter';
+ $dom[$key]['border']['B']['dash'] = $this->getCSSBorderDashStyle($brd_styles[2]);
+ if ($dom[$key]['border']['B']['dash'] < 0) {
+ $dom[$key]['border']['B'] = array();
+ }
+ }
+ }
+ $cellside = array('L' => 'left', 'R' => 'right', 'T' => 'top', 'B' => 'bottom');
+ foreach ($cellside as $bsk => $bsv) {
+ if (isset($dom[$key]['style']['border-'.$bsv])) {
+ $borderstyle = $this->getCSSBorderStyle($dom[$key]['style']['border-'.$bsv]);
+ if (!empty($borderstyle)) {
+ $dom[$key]['border'][$bsk] = $borderstyle;
+ }
+ }
+ if (isset($dom[$key]['style']['border-'.$bsv.'-color'])) {
+ $dom[$key]['border'][$bsk]['color'] = TCPDF_COLORS::convertHTMLColorToDec($dom[$key]['style']['border-'.$bsv.'-color'], $this->spot_colors);
+ }
+ if (isset($dom[$key]['style']['border-'.$bsv.'-width'])) {
+ $dom[$key]['border'][$bsk]['width'] = $this->getCSSBorderWidth($dom[$key]['style']['border-'.$bsv.'-width']);
+ }
+ if (isset($dom[$key]['style']['border-'.$bsv.'-style'])) {
+ $dom[$key]['border'][$bsk]['dash'] = $this->getCSSBorderDashStyle($dom[$key]['style']['border-'.$bsv.'-style']);
+ if ($dom[$key]['border'][$bsk]['dash'] < 0) {
+ $dom[$key]['border'][$bsk] = array();
+ }
+ }
+ }
+ // check for CSS padding properties
+ if (isset($dom[$key]['style']['padding'])) {
+ $dom[$key]['padding'] = $this->getCSSPadding($dom[$key]['style']['padding']);
+ } else {
+ $dom[$key]['padding'] = $this->cell_padding;
+ }
+ foreach ($cellside as $psk => $psv) {
+ if (isset($dom[$key]['style']['padding-'.$psv])) {
+ $dom[$key]['padding'][$psk] = $this->getHTMLUnitToUnits($dom[$key]['style']['padding-'.$psv], 0, 'px', false);
+ }
+ }
+ // check for CSS margin properties
+ if (isset($dom[$key]['style']['margin'])) {
+ $dom[$key]['margin'] = $this->getCSSMargin($dom[$key]['style']['margin']);
+ } else {
+ $dom[$key]['margin'] = $this->cell_margin;
+ }
+ foreach ($cellside as $psk => $psv) {
+ if (isset($dom[$key]['style']['margin-'.$psv])) {
+ $dom[$key]['margin'][$psk] = $this->getHTMLUnitToUnits(str_replace('auto', '0', $dom[$key]['style']['margin-'.$psv]), 0, 'px', false);
+ }
+ }
+ // check for CSS border-spacing properties
+ if (isset($dom[$key]['style']['border-spacing'])) {
+ $dom[$key]['border-spacing'] = $this->getCSSBorderMargin($dom[$key]['style']['border-spacing']);
+ }
+ // page-break-inside
+ if (isset($dom[$key]['style']['page-break-inside']) AND ($dom[$key]['style']['page-break-inside'] == 'avoid')) {
+ $dom[$key]['attribute']['nobr'] = 'true';
+ }
+ // page-break-before
+ if (isset($dom[$key]['style']['page-break-before'])) {
+ if ($dom[$key]['style']['page-break-before'] == 'always') {
+ $dom[$key]['attribute']['pagebreak'] = 'true';
+ } elseif ($dom[$key]['style']['page-break-before'] == 'left') {
+ $dom[$key]['attribute']['pagebreak'] = 'left';
+ } elseif ($dom[$key]['style']['page-break-before'] == 'right') {
+ $dom[$key]['attribute']['pagebreak'] = 'right';
+ }
+ }
+ // page-break-after
+ if (isset($dom[$key]['style']['page-break-after'])) {
+ if ($dom[$key]['style']['page-break-after'] == 'always') {
+ $dom[$key]['attribute']['pagebreakafter'] = 'true';
+ } elseif ($dom[$key]['style']['page-break-after'] == 'left') {
+ $dom[$key]['attribute']['pagebreakafter'] = 'left';
+ } elseif ($dom[$key]['style']['page-break-after'] == 'right') {
+ $dom[$key]['attribute']['pagebreakafter'] = 'right';
+ }
+ }
+ }
+ if (isset($dom[$key]['attribute']['display'])) {
+ $dom[$key]['hide'] = (trim(strtolower($dom[$key]['attribute']['display'])) == 'none');
+ }
+ if (isset($dom[$key]['attribute']['border']) AND ($dom[$key]['attribute']['border'] != 0)) {
+ $borderstyle = $this->getCSSBorderStyle($dom[$key]['attribute']['border'].' solid black');
+ if (!empty($borderstyle)) {
+ $dom[$key]['border']['LTRB'] = $borderstyle;
+ }
+ }
+ // check for font tag
+ if ($dom[$key]['value'] == 'font') {
+ // font family
+ if (isset($dom[$key]['attribute']['face'])) {
+ $dom[$key]['fontname'] = $this->getFontFamilyName($dom[$key]['attribute']['face']);
+ }
+ // font size
+ if (isset($dom[$key]['attribute']['size'])) {
+ if ($key > 0) {
+ if ($dom[$key]['attribute']['size']{0} == '+') {
+ $dom[$key]['fontsize'] = $dom[($dom[$key]['parent'])]['fontsize'] + intval(substr($dom[$key]['attribute']['size'], 1));
+ } elseif ($dom[$key]['attribute']['size']{0} == '-') {
+ $dom[$key]['fontsize'] = $dom[($dom[$key]['parent'])]['fontsize'] - intval(substr($dom[$key]['attribute']['size'], 1));
+ } else {
+ $dom[$key]['fontsize'] = intval($dom[$key]['attribute']['size']);
+ }
+ } else {
+ $dom[$key]['fontsize'] = intval($dom[$key]['attribute']['size']);
+ }
+ }
+ }
+ // force natural alignment for lists
+ if ((($dom[$key]['value'] == 'ul') OR ($dom[$key]['value'] == 'ol') OR ($dom[$key]['value'] == 'dl'))
+ AND (!isset($dom[$key]['align']) OR TCPDF_STATIC::empty_string($dom[$key]['align']) OR ($dom[$key]['align'] != 'J'))) {
+ if ($this->rtl) {
+ $dom[$key]['align'] = 'R';
+ } else {
+ $dom[$key]['align'] = 'L';
+ }
+ }
+ if (($dom[$key]['value'] == 'small') OR ($dom[$key]['value'] == 'sup') OR ($dom[$key]['value'] == 'sub')) {
+ if (!isset($dom[$key]['attribute']['size']) AND !isset($dom[$key]['style']['font-size'])) {
+ $dom[$key]['fontsize'] = $dom[$key]['fontsize'] * K_SMALL_RATIO;
+ }
+ }
+ if (($dom[$key]['value'] == 'strong') OR ($dom[$key]['value'] == 'b')) {
+ $dom[$key]['fontstyle'] .= 'B';
+ }
+ if (($dom[$key]['value'] == 'em') OR ($dom[$key]['value'] == 'i')) {
+ $dom[$key]['fontstyle'] .= 'I';
+ }
+ if ($dom[$key]['value'] == 'u') {
+ $dom[$key]['fontstyle'] .= 'U';
+ }
+ if (($dom[$key]['value'] == 'del') OR ($dom[$key]['value'] == 's') OR ($dom[$key]['value'] == 'strike')) {
+ $dom[$key]['fontstyle'] .= 'D';
+ }
+ if (!isset($dom[$key]['style']['text-decoration']) AND ($dom[$key]['value'] == 'a')) {
+ $dom[$key]['fontstyle'] = $this->htmlLinkFontStyle;
+ }
+ if (($dom[$key]['value'] == 'pre') OR ($dom[$key]['value'] == 'tt')) {
+ $dom[$key]['fontname'] = $this->default_monospaced_font;
+ }
+ if (($dom[$key]['value']{0} == 'h') AND (intval($dom[$key]['value']{1}) > 0) AND (intval($dom[$key]['value']{1}) < 7)) {
+ // headings h1, h2, h3, h4, h5, h6
+ if (!isset($dom[$key]['attribute']['size']) AND !isset($dom[$key]['style']['font-size'])) {
+ $headsize = (4 - intval($dom[$key]['value']{1})) * 2;
+ $dom[$key]['fontsize'] = $dom[0]['fontsize'] + $headsize;
+ }
+ if (!isset($dom[$key]['style']['font-weight'])) {
+ $dom[$key]['fontstyle'] .= 'B';
+ }
+ }
+ if (($dom[$key]['value'] == 'table')) {
+ $dom[$key]['rows'] = 0; // number of rows
+ $dom[$key]['trids'] = array(); // IDs of TR elements
+ $dom[$key]['thead'] = ''; // table header rows
+ }
+ if (($dom[$key]['value'] == 'tr')) {
+ $dom[$key]['cols'] = 0;
+ if ($thead) {
+ $dom[$key]['thead'] = true;
+ // rows on thead block are printed as a separate table
+ } else {
+ $dom[$key]['thead'] = false;
+ // store the number of rows on table element
+ ++$dom[($dom[$key]['parent'])]['rows'];
+ // store the TR elements IDs on table element
+ array_push($dom[($dom[$key]['parent'])]['trids'], $key);
+ }
+ }
+ if (($dom[$key]['value'] == 'th') OR ($dom[$key]['value'] == 'td')) {
+ if (isset($dom[$key]['attribute']['colspan'])) {
+ $colspan = intval($dom[$key]['attribute']['colspan']);
+ } else {
+ $colspan = 1;
+ }
+ $dom[$key]['attribute']['colspan'] = $colspan;
+ $dom[($dom[$key]['parent'])]['cols'] += $colspan;
+ }
+ // text direction
+ if (isset($dom[$key]['attribute']['dir'])) {
+ $dom[$key]['dir'] = $dom[$key]['attribute']['dir'];
+ }
+ // set foreground color attribute
+ if (isset($dom[$key]['attribute']['color']) AND (!TCPDF_STATIC::empty_string($dom[$key]['attribute']['color']))) {
+ $dom[$key]['fgcolor'] = TCPDF_COLORS::convertHTMLColorToDec($dom[$key]['attribute']['color'], $this->spot_colors);
+ } elseif (!isset($dom[$key]['style']['color']) AND ($dom[$key]['value'] == 'a')) {
+ $dom[$key]['fgcolor'] = $this->htmlLinkColorArray;
+ }
+ // set background color attribute
+ if (isset($dom[$key]['attribute']['bgcolor']) AND (!TCPDF_STATIC::empty_string($dom[$key]['attribute']['bgcolor']))) {
+ $dom[$key]['bgcolor'] = TCPDF_COLORS::convertHTMLColorToDec($dom[$key]['attribute']['bgcolor'], $this->spot_colors);
+ }
+ // set stroke color attribute
+ if (isset($dom[$key]['attribute']['strokecolor']) AND (!TCPDF_STATIC::empty_string($dom[$key]['attribute']['strokecolor']))) {
+ $dom[$key]['strokecolor'] = TCPDF_COLORS::convertHTMLColorToDec($dom[$key]['attribute']['strokecolor'], $this->spot_colors);
+ }
+ // check for width attribute
+ if (isset($dom[$key]['attribute']['width'])) {
+ $dom[$key]['width'] = $dom[$key]['attribute']['width'];
+ }
+ // check for height attribute
+ if (isset($dom[$key]['attribute']['height'])) {
+ $dom[$key]['height'] = $dom[$key]['attribute']['height'];
+ }
+ // check for text alignment
+ if (isset($dom[$key]['attribute']['align']) AND (!TCPDF_STATIC::empty_string($dom[$key]['attribute']['align'])) AND ($dom[$key]['value'] !== 'img')) {
+ $dom[$key]['align'] = strtoupper($dom[$key]['attribute']['align']{0});
+ }
+ // check for text rendering mode (the following attributes do not exist in HTML)
+ if (isset($dom[$key]['attribute']['stroke'])) {
+ // font stroke width
+ $dom[$key]['stroke'] = $this->getHTMLUnitToUnits($dom[$key]['attribute']['stroke'], $dom[$key]['fontsize'], 'pt', true);
+ }
+ if (isset($dom[$key]['attribute']['fill'])) {
+ // font fill
+ if ($dom[$key]['attribute']['fill'] == 'true') {
+ $dom[$key]['fill'] = true;
+ } else {
+ $dom[$key]['fill'] = false;
+ }
+ }
+ if (isset($dom[$key]['attribute']['clip'])) {
+ // clipping mode
+ if ($dom[$key]['attribute']['clip'] == 'true') {
+ $dom[$key]['clip'] = true;
+ } else {
+ $dom[$key]['clip'] = false;
+ }
+ }
+ } // end opening tag
+ } else {
+ // text
+ $dom[$key]['tag'] = false;
+ $dom[$key]['block'] = false;
+ //$element = str_replace('&nbsp;', TCPDF_FONTS::unichr(160, $this->isunicode), $element);
+ $dom[$key]['value'] = stripslashes($this->unhtmlentities($element));
+ $dom[$key]['parent'] = end($level);
+ $dom[$key]['dir'] = $dom[$dom[$key]['parent']]['dir'];
+ }
+ ++$elkey;
+ ++$key;
+ }
+ return $dom;
+ }
+
+ /**
+ * Returns the string used to find spaces
+ * @return string
+ * @protected
+ * @author Nicola Asuni
+ * @since 4.8.024 (2010-01-15)
+ */
+ protected function getSpaceString() {
+ $spacestr = chr(32);
+ if ($this->isUnicodeFont()) {
+ $spacestr = chr(0).chr(32);
+ }
+ return $spacestr;
+ }
+
+ /**
+ * Serialize an array of parameters to be used with TCPDF tag in HTML code.
+ * @param $pararray (array) parameters array
+ * @return sting containing serialized data
+ * @since 4.9.006 (2010-04-02)
+ * @public
+ * @deprecated
+ */
+ public function serializeTCPDFtagParameters($pararray) {
+ return TCPDF_STATIC::serializeTCPDFtagParameters($pararray);
+ }
+
+ /**
+ * Prints a cell (rectangular area) with optional borders, background color and html text string.
+ * The upper-left corner of the cell corresponds to the current position. After the call, the current position moves to the right or to the next line.<br />
+ * If automatic page breaking is enabled and the cell goes beyond the limit, a page break is done before outputting.
+ * IMPORTANT: The HTML must be well formatted - try to clean-up it using an application like HTML-Tidy before submitting.
+ * Supported tags are: a, b, blockquote, br, dd, del, div, dl, dt, em, font, h1, h2, h3, h4, h5, h6, hr, i, img, li, ol, p, pre, small, span, strong, sub, sup, table, tcpdf, td, th, thead, tr, tt, u, ul
+ * NOTE: all the HTML attributes must be enclosed in double-quote.
+ * @param $w (float) Cell width. If 0, the cell extends up to the right margin.
+ * @param $h (float) Cell minimum height. The cell extends automatically if needed.
+ * @param $x (float) upper-left corner X coordinate
+ * @param $y (float) upper-left corner Y coordinate
+ * @param $html (string) html text to print. Default value: empty string.
+ * @param $border (mixed) Indicates if borders must be drawn around the cell. The value can be a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul> or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul> or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)))
+ * @param $ln (int) Indicates where the current position should go after the call. Possible values are:<ul><li>0: to the right (or left for RTL language)</li><li>1: to the beginning of the next line</li><li>2: below</li></ul>
+Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: 0.
+ * @param $fill (boolean) Indicates if the cell background must be painted (true) or transparent (false).
+ * @param $reseth (boolean) if true reset the last cell height (default true).
+ * @param $align (string) Allows to center or align the text. Possible values are:<ul><li>L : left align</li><li>C : center</li><li>R : right align</li><li>'' : empty string : left for LTR or right for RTL</li></ul>
+ * @param $autopadding (boolean) if true, uses internal padding and automatically adjust it to account for line width.
+ * @see Multicell(), writeHTML()
+ * @public
+ */
+ public function writeHTMLCell($w, $h, $x, $y, $html='', $border=0, $ln=0, $fill=false, $reseth=true, $align='', $autopadding=true) {
+ return $this->MultiCell($w, $h, $html, $border, $align, $fill, $ln, $x, $y, $reseth, 0, true, $autopadding, 0, 'T', false);
+ }
+
+ /**
+ * Allows to preserve some HTML formatting (limited support).<br />
+ * IMPORTANT: The HTML must be well formatted - try to clean-up it using an application like HTML-Tidy before submitting.
+ * Supported tags are: a, b, blockquote, br, dd, del, div, dl, dt, em, font, h1, h2, h3, h4, h5, h6, hr, i, img, li, ol, p, pre, small, span, strong, sub, sup, table, tcpdf, td, th, thead, tr, tt, u, ul
+ * NOTE: all the HTML attributes must be enclosed in double-quote.
+ * @param $html (string) text to display
+ * @param $ln (boolean) if true add a new line after text (default = true)
+ * @param $fill (boolean) Indicates if the background must be painted (true) or transparent (false).
+ * @param $reseth (boolean) if true reset the last cell height (default false).
+ * @param $cell (boolean) if true add the current left (or right for RTL) padding to each Write (default false).
+ * @param $align (string) Allows to center or align the text. Possible values are:<ul><li>L : left align</li><li>C : center</li><li>R : right align</li><li>'' : empty string : left for LTR or right for RTL</li></ul>
+ * @public
+ */
+ public function writeHTML($html, $ln=true, $fill=false, $reseth=false, $cell=false, $align='') {
+ $gvars = $this->getGraphicVars();
+ // store current values
+ $prev_cell_margin = $this->cell_margin;
+ $prev_cell_padding = $this->cell_padding;
+ $prevPage = $this->page;
+ $prevlMargin = $this->lMargin;
+ $prevrMargin = $this->rMargin;
+ $curfontname = $this->FontFamily;
+ $curfontstyle = $this->FontStyle;
+ $curfontsize = $this->FontSizePt;
+ $curfontascent = $this->getFontAscent($curfontname, $curfontstyle, $curfontsize);
+ $curfontdescent = $this->getFontDescent($curfontname, $curfontstyle, $curfontsize);
+ $curfontstretcing = $this->font_stretching;
+ $curfonttracking = $this->font_spacing;
+ $this->newline = true;
+ $newline = true;
+ $startlinepage = $this->page;
+ $minstartliney = $this->y;
+ $maxbottomliney = 0;
+ $startlinex = $this->x;
+ $startliney = $this->y;
+ $yshift = 0;
+ $loop = 0;
+ $curpos = 0;
+ $this_method_vars = array();
+ $undo = false;
+ $fontaligned = false;
+ $reverse_dir = false; // true when the text direction is reversed
+ $this->premode = false;
+ if ($this->inxobj) {
+ // we are inside an XObject template
+ $pask = count($this->xobjects[$this->xobjid]['annotations']);
+ } elseif (isset($this->PageAnnots[$this->page])) {
+ $pask = count($this->PageAnnots[$this->page]);
+ } else {
+ $pask = 0;
+ }
+ if ($this->inxobj) {
+ // we are inside an XObject template
+ $startlinepos = strlen($this->xobjects[$this->xobjid]['outdata']);
+ } elseif (!$this->InFooter) {
+ if (isset($this->footerlen[$this->page])) {
+ $this->footerpos[$this->page] = $this->pagelen[$this->page] - $this->footerlen[$this->page];
+ } else {
+ $this->footerpos[$this->page] = $this->pagelen[$this->page];
+ }
+ $startlinepos = $this->footerpos[$this->page];
+ } else {
+ // we are inside the footer
+ $startlinepos = $this->pagelen[$this->page];
+ }
+ $lalign = $align;
+ $plalign = $align;
+ if ($this->rtl) {
+ $w = $this->x - $this->lMargin;
+ } else {
+ $w = $this->w - $this->rMargin - $this->x;
+ }
+ $w -= ($this->cell_padding['L'] + $this->cell_padding['R']);
+ if ($cell) {
+ if ($this->rtl) {
+ $this->x -= $this->cell_padding['R'];
+ $this->lMargin += $this->cell_padding['R'];
+ } else {
+ $this->x += $this->cell_padding['L'];
+ $this->rMargin += $this->cell_padding['L'];
+ }
+ }
+ if ($this->customlistindent >= 0) {
+ $this->listindent = $this->customlistindent;
+ } else {
+ $this->listindent = $this->GetStringWidth('000000');
+ }
+ $this->listindentlevel = 0;
+ // save previous states
+ $prev_cell_height_ratio = $this->cell_height_ratio;
+ $prev_listnum = $this->listnum;
+ $prev_listordered = $this->listordered;
+ $prev_listcount = $this->listcount;
+ $prev_lispacer = $this->lispacer;
+ $this->listnum = 0;
+ $this->listordered = array();
+ $this->listcount = array();
+ $this->lispacer = '';
+ if ((TCPDF_STATIC::empty_string($this->lasth)) OR ($reseth)) {
+ // reset row height
+ $this->resetLastH();
+ }
+ $dom = $this->getHtmlDomArray($html);
+ $maxel = count($dom);
+ $key = 0;
+ $hidden_node_key = -1;
+ while ($key < $maxel) {
+ if ($dom[$key]['tag']) {
+ if ($dom[$key]['opening']) {
+ if (($hidden_node_key <= 0) AND $dom[$key]['hide']) {
+ // store the node key
+ $hidden_node_key = $key;
+ }
+ } elseif (($hidden_node_key > 0) AND ($dom[$key]['parent'] == $hidden_node_key)) {
+ // we have reached the closing tag of the hidden node
+ $hidden_node_key = 0;
+ }
+ }
+ if ($hidden_node_key >= 0) {
+ // skip this node
+ ++$key;
+ if ($hidden_node_key == 0) {
+ // reset hidden mode
+ $hidden_node_key = -1;
+ }
+ continue;
+ }
+ if ($dom[$key]['tag'] AND isset($dom[$key]['attribute']['pagebreak'])) {
+ // check for pagebreak
+ if (($dom[$key]['attribute']['pagebreak'] == 'true') OR ($dom[$key]['attribute']['pagebreak'] == 'left') OR ($dom[$key]['attribute']['pagebreak'] == 'right')) {
+ // add a page (or trig AcceptPageBreak() for multicolumn mode)
+ $this->checkPageBreak($this->PageBreakTrigger + 1);
+ $this->htmlvspace = ($this->PageBreakTrigger + 1);
+ }
+ if ((($dom[$key]['attribute']['pagebreak'] == 'left') AND (((!$this->rtl) AND (($this->page % 2) == 0)) OR (($this->rtl) AND (($this->page % 2) != 0))))
+ OR (($dom[$key]['attribute']['pagebreak'] == 'right') AND (((!$this->rtl) AND (($this->page % 2) != 0)) OR (($this->rtl) AND (($this->page % 2) == 0))))) {
+ // add a page (or trig AcceptPageBreak() for multicolumn mode)
+ $this->checkPageBreak($this->PageBreakTrigger + 1);
+ $this->htmlvspace = ($this->PageBreakTrigger + 1);
+ }
+ }
+ if ($dom[$key]['tag'] AND $dom[$key]['opening'] AND isset($dom[$key]['attribute']['nobr']) AND ($dom[$key]['attribute']['nobr'] == 'true')) {
+ if (isset($dom[($dom[$key]['parent'])]['attribute']['nobr']) AND ($dom[($dom[$key]['parent'])]['attribute']['nobr'] == 'true')) {
+ $dom[$key]['attribute']['nobr'] = false;
+ } else {
+ // store current object
+ $this->startTransaction();
+ // save this method vars
+ $this_method_vars['html'] = $html;
+ $this_method_vars['ln'] = $ln;
+ $this_method_vars['fill'] = $fill;
+ $this_method_vars['reseth'] = $reseth;
+ $this_method_vars['cell'] = $cell;
+ $this_method_vars['align'] = $align;
+ $this_method_vars['gvars'] = $gvars;
+ $this_method_vars['prevPage'] = $prevPage;
+ $this_method_vars['prev_cell_margin'] = $prev_cell_margin;
+ $this_method_vars['prev_cell_padding'] = $prev_cell_padding;
+ $this_method_vars['prevlMargin'] = $prevlMargin;
+ $this_method_vars['prevrMargin'] = $prevrMargin;
+ $this_method_vars['curfontname'] = $curfontname;
+ $this_method_vars['curfontstyle'] = $curfontstyle;
+ $this_method_vars['curfontsize'] = $curfontsize;
+ $this_method_vars['curfontascent'] = $curfontascent;
+ $this_method_vars['curfontdescent'] = $curfontdescent;
+ $this_method_vars['curfontstretcing'] = $curfontstretcing;
+ $this_method_vars['curfonttracking'] = $curfonttracking;
+ $this_method_vars['minstartliney'] = $minstartliney;
+ $this_method_vars['maxbottomliney'] = $maxbottomliney;
+ $this_method_vars['yshift'] = $yshift;
+ $this_method_vars['startlinepage'] = $startlinepage;
+ $this_method_vars['startlinepos'] = $startlinepos;
+ $this_method_vars['startlinex'] = $startlinex;
+ $this_method_vars['startliney'] = $startliney;
+ $this_method_vars['newline'] = $newline;
+ $this_method_vars['loop'] = $loop;
+ $this_method_vars['curpos'] = $curpos;
+ $this_method_vars['pask'] = $pask;
+ $this_method_vars['lalign'] = $lalign;
+ $this_method_vars['plalign'] = $plalign;
+ $this_method_vars['w'] = $w;
+ $this_method_vars['prev_cell_height_ratio'] = $prev_cell_height_ratio;
+ $this_method_vars['prev_listnum'] = $prev_listnum;
+ $this_method_vars['prev_listordered'] = $prev_listordered;
+ $this_method_vars['prev_listcount'] = $prev_listcount;
+ $this_method_vars['prev_lispacer'] = $prev_lispacer;
+ $this_method_vars['fontaligned'] = $fontaligned;
+ $this_method_vars['key'] = $key;
+ $this_method_vars['dom'] = $dom;
+ }
+ }
+ // print THEAD block
+ if (($dom[$key]['value'] == 'tr') AND isset($dom[$key]['thead']) AND $dom[$key]['thead']) {
+ if (isset($dom[$key]['parent']) AND isset($dom[$dom[$key]['parent']]['thead']) AND !TCPDF_STATIC::empty_string($dom[$dom[$key]['parent']]['thead'])) {
+ $this->inthead = true;
+ // print table header (thead)
+ $this->writeHTML($this->thead, false, false, false, false, '');
+ // check if we are on a new page or on a new column
+ if (($this->y < $this->start_transaction_y) OR ($this->checkPageBreak($this->lasth, '', false))) {
+ // we are on a new page or on a new column and the total object height is less than the available vertical space.
+ // restore previous object
+ $this->rollbackTransaction(true);
+ // restore previous values
+ foreach ($this_method_vars as $vkey => $vval) {
+ $$vkey = $vval;
+ }
+ // disable table header
+ $tmp_thead = $this->thead;
+ $this->thead = '';
+ // add a page (or trig AcceptPageBreak() for multicolumn mode)
+ $pre_y = $this->y;
+ if ((!$this->checkPageBreak($this->PageBreakTrigger + 1)) AND ($this->y < $pre_y)) {
+ // fix for multicolumn mode
+ $startliney = $this->y;
+ }
+ $this->start_transaction_page = $this->page;
+ $this->start_transaction_y = $this->y;
+ // restore table header
+ $this->thead = $tmp_thead;
+ // fix table border properties
+ if (isset($dom[$dom[$key]['parent']]['attribute']['cellspacing'])) {
+ $tmp_cellspacing = $this->getHTMLUnitToUnits($dom[$dom[$key]['parent']]['attribute']['cellspacing'], 1, 'px');
+ } elseif (isset($dom[$dom[$key]['parent']]['border-spacing'])) {
+ $tmp_cellspacing = $dom[$dom[$key]['parent']]['border-spacing']['V'];
+ } else {
+ $tmp_cellspacing = 0;
+ }
+ $dom[$dom[$key]['parent']]['borderposition']['page'] = $this->page;
+ $dom[$dom[$key]['parent']]['borderposition']['column'] = $this->current_column;
+ $dom[$dom[$key]['parent']]['borderposition']['y'] = $this->y + $tmp_cellspacing;
+ $xoffset = ($this->x - $dom[$dom[$key]['parent']]['borderposition']['x']);
+ $dom[$dom[$key]['parent']]['borderposition']['x'] += $xoffset;
+ $dom[$dom[$key]['parent']]['borderposition']['xmax'] += $xoffset;
+ // print table header (thead)
+ $this->writeHTML($this->thead, false, false, false, false, '');
+ }
+ }
+ // move $key index forward to skip THEAD block
+ while ( ($key < $maxel) AND (!(
+ ($dom[$key]['tag'] AND $dom[$key]['opening'] AND ($dom[$key]['value'] == 'tr') AND (!isset($dom[$key]['thead']) OR !$dom[$key]['thead']))
+ OR ($dom[$key]['tag'] AND (!$dom[$key]['opening']) AND ($dom[$key]['value'] == 'table'))) )) {
+ ++$key;
+ }
+ }
+ if ($dom[$key]['tag'] OR ($key == 0)) {
+ if ((($dom[$key]['value'] == 'table') OR ($dom[$key]['value'] == 'tr')) AND (isset($dom[$key]['align']))) {
+ $dom[$key]['align'] = ($this->rtl) ? 'R' : 'L';
+ }
+ // vertically align image in line
+ if ((!$this->newline) AND ($dom[$key]['value'] == 'img') AND (isset($dom[$key]['height'])) AND ($dom[$key]['height'] > 0)) {
+ // get image height
+ $imgh = $this->getHTMLUnitToUnits($dom[$key]['height'], $this->lasth, 'px');
+ $autolinebreak = false;
+ if (isset($dom[$key]['width']) AND ($dom[$key]['width'] > 0)) {
+ $imgw = $this->getHTMLUnitToUnits($dom[$key]['width'], 1, 'px', false);
+ if (($imgw <= ($this->w - $this->lMargin - $this->rMargin - $this->cell_padding['L'] - $this->cell_padding['R']))
+ AND ((($this->rtl) AND (($this->x - $imgw) < ($this->lMargin + $this->cell_padding['L'])))
+ OR ((!$this->rtl) AND (($this->x + $imgw) > ($this->w - $this->rMargin - $this->cell_padding['R']))))) {
+ // add automatic line break
+ $autolinebreak = true;
+ $this->Ln('', $cell);
+ if ((!$dom[($key-1)]['tag']) AND ($dom[($key-1)]['value'] == ' ')) {
+ // go back to evaluate this line break
+ --$key;
+ }
+ }
+ }
+ if (!$autolinebreak) {
+ if ($this->inPageBody()) {
+ $pre_y = $this->y;
+ // check for page break
+ if ((!$this->checkPageBreak($imgh)) AND ($this->y < $pre_y)) {
+ // fix for multicolumn mode
+ $startliney = $this->y;
+ }
+ }
+ if ($this->page > $startlinepage) {
+ // fix line splitted over two pages
+ if (isset($this->footerlen[$startlinepage])) {
+ $curpos = $this->pagelen[$startlinepage] - $this->footerlen[$startlinepage];
+ }
+ // line to be moved one page forward
+ $pagebuff = $this->getPageBuffer($startlinepage);
+ $linebeg = substr($pagebuff, $startlinepos, ($curpos - $startlinepos));
+ $tstart = substr($pagebuff, 0, $startlinepos);
+ $tend = substr($this->getPageBuffer($startlinepage), $curpos);
+ // remove line from previous page
+ $this->setPageBuffer($startlinepage, $tstart.''.$tend);
+ $pagebuff = $this->getPageBuffer($this->page);
+ $tstart = substr($pagebuff, 0, $this->cntmrk[$this->page]);
+ $tend = substr($pagebuff, $this->cntmrk[$this->page]);
+ // add line start to current page
+ $yshift = ($minstartliney - $this->y);
+ if ($fontaligned) {
+ $yshift += ($curfontsize / $this->k);
+ }
+ $try = sprintf('1 0 0 1 0 %F cm', ($yshift * $this->k));
+ $this->setPageBuffer($this->page, $tstart."\nq\n".$try."\n".$linebeg."\nQ\n".$tend);
+ // shift the annotations and links
+ if (isset($this->PageAnnots[$this->page])) {
+ $next_pask = count($this->PageAnnots[$this->page]);
+ } else {
+ $next_pask = 0;
+ }
+ if (isset($this->PageAnnots[$startlinepage])) {
+ foreach ($this->PageAnnots[$startlinepage] as $pak => $pac) {
+ if ($pak >= $pask) {
+ $this->PageAnnots[$this->page][] = $pac;
+ unset($this->PageAnnots[$startlinepage][$pak]);
+ $npak = count($this->PageAnnots[$this->page]) - 1;
+ $this->PageAnnots[$this->page][$npak]['y'] -= $yshift;
+ }
+ }
+ }
+ $pask = $next_pask;
+ $startlinepos = $this->cntmrk[$this->page];
+ $startlinepage = $this->page;
+ $startliney = $this->y;
+ $this->newline = false;
+ }
+ $this->y += ((($curfontsize * $this->cell_height_ratio / $this->k) + $curfontascent - $curfontdescent) / 2) - $imgh;
+ $minstartliney = min($this->y, $minstartliney);
+ $maxbottomliney = ($startliney + ($this->FontSize * $this->cell_height_ratio));
+ }
+ } elseif (isset($dom[$key]['fontname']) OR isset($dom[$key]['fontstyle']) OR isset($dom[$key]['fontsize']) OR isset($dom[$key]['line-height'])) {
+ // account for different font size
+ $pfontname = $curfontname;
+ $pfontstyle = $curfontstyle;
+ $pfontsize = $curfontsize;
+ $fontname = (isset($dom[$key]['fontname']) ? $dom[$key]['fontname'] : $curfontname);
+ $fontstyle = (isset($dom[$key]['fontstyle']) ? $dom[$key]['fontstyle'] : $curfontstyle);
+ $fontsize = (isset($dom[$key]['fontsize']) ? $dom[$key]['fontsize'] : $curfontsize);
+ $fontascent = $this->getFontAscent($fontname, $fontstyle, $fontsize);
+ $fontdescent = $this->getFontDescent($fontname, $fontstyle, $fontsize);
+ if (($fontname != $curfontname) OR ($fontstyle != $curfontstyle) OR ($fontsize != $curfontsize)
+ OR ($this->cell_height_ratio != $dom[$key]['line-height'])
+ OR ($dom[$key]['tag'] AND $dom[$key]['opening'] AND ($dom[$key]['value'] == 'li')) ) {
+ if (($key < ($maxel - 1)) AND (
+ ($dom[$key]['tag'] AND $dom[$key]['opening'] AND ($dom[$key]['value'] == 'li'))
+ OR ($this->cell_height_ratio != $dom[$key]['line-height'])
+ OR (!$this->newline AND is_numeric($fontsize) AND is_numeric($curfontsize) AND ($fontsize >= 0) AND ($curfontsize >= 0) AND ($fontsize != $curfontsize))
+ )) {
+ if ($this->page > $startlinepage) {
+ // fix lines splitted over two pages
+ if (isset($this->footerlen[$startlinepage])) {
+ $curpos = $this->pagelen[$startlinepage] - $this->footerlen[$startlinepage];
+ }
+ // line to be moved one page forward
+ $pagebuff = $this->getPageBuffer($startlinepage);
+ $linebeg = substr($pagebuff, $startlinepos, ($curpos - $startlinepos));
+ $tstart = substr($pagebuff, 0, $startlinepos);
+ $tend = substr($this->getPageBuffer($startlinepage), $curpos);
+ // remove line start from previous page
+ $this->setPageBuffer($startlinepage, $tstart.''.$tend);
+ $pagebuff = $this->getPageBuffer($this->page);
+ $tstart = substr($pagebuff, 0, $this->cntmrk[$this->page]);
+ $tend = substr($pagebuff, $this->cntmrk[$this->page]);
+ // add line start to current page
+ $yshift = ($minstartliney - $this->y);
+ $try = sprintf('1 0 0 1 0 %F cm', ($yshift * $this->k));
+ $this->setPageBuffer($this->page, $tstart."\nq\n".$try."\n".$linebeg."\nQ\n".$tend);
+ // shift the annotations and links
+ if (isset($this->PageAnnots[$this->page])) {
+ $next_pask = count($this->PageAnnots[$this->page]);
+ } else {
+ $next_pask = 0;
+ }
+ if (isset($this->PageAnnots[$startlinepage])) {
+ foreach ($this->PageAnnots[$startlinepage] as $pak => $pac) {
+ if ($pak >= $pask) {
+ $this->PageAnnots[$this->page][] = $pac;
+ unset($this->PageAnnots[$startlinepage][$pak]);
+ $npak = count($this->PageAnnots[$this->page]) - 1;
+ $this->PageAnnots[$this->page][$npak]['y'] -= $yshift;
+ }
+ }
+ }
+ $pask = $next_pask;
+ $startlinepos = $this->cntmrk[$this->page];
+ $startlinepage = $this->page;
+ $startliney = $this->y;
+ }
+ if (!isset($dom[$key]['line-height'])) {
+ $dom[$key]['line-height'] = $this->cell_height_ratio;
+ }
+ if (!$dom[$key]['block']) {
+ if (!(isset($dom[($key + 1)]) AND $dom[($key + 1)]['tag'] AND (!$dom[($key + 1)]['opening']) AND ($dom[($key + 1)]['value'] != 'li') AND $dom[$key]['tag'] AND (!$dom[$key]['opening']))) {
+ $this->y += (((($curfontsize * $this->cell_height_ratio) - ($fontsize * $dom[$key]['line-height'])) / $this->k) + $curfontascent - $fontascent - $curfontdescent + $fontdescent) / 2;
+ }
+ if (($dom[$key]['value'] != 'sup') AND ($dom[$key]['value'] != 'sub')) {
+ $current_line_align_data = array($key, $minstartliney, $maxbottomliney);
+ if (isset($line_align_data) AND (($line_align_data[0] == ($key - 1)) OR (($line_align_data[0] == ($key - 2)) AND (isset($dom[($key - 1)])) AND (preg_match('/^([\s]+)$/', $dom[($key - 1)]['value']) > 0)))) {
+ $minstartliney = min($this->y, $line_align_data[1]);
+ $maxbottomliney = max(($this->y + (($fontsize * $this->cell_height_ratio) / $this->k)), $line_align_data[2]);
+ } else {
+ $minstartliney = min($this->y, $minstartliney);
+ $maxbottomliney = max(($this->y + (($fontsize * $this->cell_height_ratio) / $this->k)), $maxbottomliney);
+ }
+ $line_align_data = $current_line_align_data;
+ }
+ }
+ $this->cell_height_ratio = $dom[$key]['line-height'];
+ $fontaligned = true;
+ }
+ $this->SetFont($fontname, $fontstyle, $fontsize);
+ // reset row height
+ $this->resetLastH();
+ $curfontname = $fontname;
+ $curfontstyle = $fontstyle;
+ $curfontsize = $fontsize;
+ $curfontascent = $fontascent;
+ $curfontdescent = $fontdescent;
+ }
+ }
+ // set text rendering mode
+ $textstroke = isset($dom[$key]['stroke']) ? $dom[$key]['stroke'] : $this->textstrokewidth;
+ $textfill = isset($dom[$key]['fill']) ? $dom[$key]['fill'] : (($this->textrendermode % 2) == 0);
+ $textclip = isset($dom[$key]['clip']) ? $dom[$key]['clip'] : ($this->textrendermode > 3);
+ $this->setTextRenderingMode($textstroke, $textfill, $textclip);
+ if (isset($dom[$key]['font-stretch']) AND ($dom[$key]['font-stretch'] !== false)) {
+ $this->setFontStretching($dom[$key]['font-stretch']);
+ }
+ if (isset($dom[$key]['letter-spacing']) AND ($dom[$key]['letter-spacing'] !== false)) {
+ $this->setFontSpacing($dom[$key]['letter-spacing']);
+ }
+ if (($plalign == 'J') AND $dom[$key]['block']) {
+ $plalign = '';
+ }
+ // get current position on page buffer
+ $curpos = $this->pagelen[$startlinepage];
+ if (isset($dom[$key]['bgcolor']) AND ($dom[$key]['bgcolor'] !== false)) {
+ $this->SetFillColorArray($dom[$key]['bgcolor']);
+ $wfill = true;
+ } else {
+ $wfill = $fill | false;
+ }
+ if (isset($dom[$key]['fgcolor']) AND ($dom[$key]['fgcolor'] !== false)) {
+ $this->SetTextColorArray($dom[$key]['fgcolor']);
+ }
+ if (isset($dom[$key]['strokecolor']) AND ($dom[$key]['strokecolor'] !== false)) {
+ $this->SetDrawColorArray($dom[$key]['strokecolor']);
+ }
+ if (isset($dom[$key]['align'])) {
+ $lalign = $dom[$key]['align'];
+ }
+ if (TCPDF_STATIC::empty_string($lalign)) {
+ $lalign = $align;
+ }
+ }
+ // align lines
+ if ($this->newline AND (strlen($dom[$key]['value']) > 0) AND ($dom[$key]['value'] != 'td') AND ($dom[$key]['value'] != 'th')) {
+ $newline = true;
+ $fontaligned = false;
+ // we are at the beginning of a new line
+ if (isset($startlinex)) {
+ $yshift = ($minstartliney - $startliney);
+ if (($yshift > 0) OR ($this->page > $startlinepage)) {
+ $yshift = 0;
+ }
+ $t_x = 0;
+ // the last line must be shifted to be aligned as requested
+ $linew = abs($this->endlinex - $startlinex);
+ if ($this->inxobj) {
+ // we are inside an XObject template
+ $pstart = substr($this->xobjects[$this->xobjid]['outdata'], 0, $startlinepos);
+ if (isset($opentagpos)) {
+ $midpos = $opentagpos;
+ } else {
+ $midpos = 0;
+ }
+ if ($midpos > 0) {
+ $pmid = substr($this->xobjects[$this->xobjid]['outdata'], $startlinepos, ($midpos - $startlinepos));
+ $pend = substr($this->xobjects[$this->xobjid]['outdata'], $midpos);
+ } else {
+ $pmid = substr($this->xobjects[$this->xobjid]['outdata'], $startlinepos);
+ $pend = '';
+ }
+ } else {
+ $pstart = substr($this->getPageBuffer($startlinepage), 0, $startlinepos);
+ if (isset($opentagpos) AND isset($this->footerlen[$startlinepage]) AND (!$this->InFooter)) {
+ $this->footerpos[$startlinepage] = $this->pagelen[$startlinepage] - $this->footerlen[$startlinepage];
+ $midpos = min($opentagpos, $this->footerpos[$startlinepage]);
+ } elseif (isset($opentagpos)) {
+ $midpos = $opentagpos;
+ } elseif (isset($this->footerlen[$startlinepage]) AND (!$this->InFooter)) {
+ $this->footerpos[$startlinepage] = $this->pagelen[$startlinepage] - $this->footerlen[$startlinepage];
+ $midpos = $this->footerpos[$startlinepage];
+ } else {
+ $midpos = 0;
+ }
+ if ($midpos > 0) {
+ $pmid = substr($this->getPageBuffer($startlinepage), $startlinepos, ($midpos - $startlinepos));
+ $pend = substr($this->getPageBuffer($startlinepage), $midpos);
+ } else {
+ $pmid = substr($this->getPageBuffer($startlinepage), $startlinepos);
+ $pend = '';
+ }
+ }
+ if ((isset($plalign) AND ((($plalign == 'C') OR ($plalign == 'J') OR (($plalign == 'R') AND (!$this->rtl)) OR (($plalign == 'L') AND ($this->rtl)))))) {
+ // calculate shifting amount
+ $tw = $w;
+ if (($plalign == 'J') AND $this->isRTLTextDir() AND ($this->num_columns > 1)) {
+ $tw += $this->cell_padding['R'];
+ }
+ if ($this->lMargin != $prevlMargin) {
+ $tw += ($prevlMargin - $this->lMargin);
+ }
+ if ($this->rMargin != $prevrMargin) {
+ $tw += ($prevrMargin - $this->rMargin);
+ }
+ $one_space_width = $this->GetStringWidth(chr(32));
+ $no = 0; // number of spaces on a line contained on a single block
+ if ($this->isRTLTextDir()) { // RTL
+ // remove left space if exist
+ $pos1 = TCPDF_STATIC::revstrpos($pmid, '[(');
+ if ($pos1 > 0) {
+ $pos1 = intval($pos1);
+ if ($this->isUnicodeFont()) {
+ $pos2 = intval(TCPDF_STATIC::revstrpos($pmid, '[('.chr(0).chr(32)));
+ $spacelen = 2;
+ } else {
+ $pos2 = intval(TCPDF_STATIC::revstrpos($pmid, '[('.chr(32)));
+ $spacelen = 1;
+ }
+ if ($pos1 == $pos2) {
+ $pmid = substr($pmid, 0, ($pos1 + 2)).substr($pmid, ($pos1 + 2 + $spacelen));
+ if (substr($pmid, $pos1, 4) == '[()]') {
+ $linew -= $one_space_width;
+ } elseif ($pos1 == strpos($pmid, '[(')) {
+ $no = 1;
+ }
+ }
+ }
+ } else { // LTR
+ // remove right space if exist
+ $pos1 = TCPDF_STATIC::revstrpos($pmid, ')]');
+ if ($pos1 > 0) {
+ $pos1 = intval($pos1);
+ if ($this->isUnicodeFont()) {
+ $pos2 = intval(TCPDF_STATIC::revstrpos($pmid, chr(0).chr(32).')]')) + 2;
+ $spacelen = 2;
+ } else {
+ $pos2 = intval(TCPDF_STATIC::revstrpos($pmid, chr(32).')]')) + 1;
+ $spacelen = 1;
+ }
+ if ($pos1 == $pos2) {
+ $pmid = substr($pmid, 0, ($pos1 - $spacelen)).substr($pmid, $pos1);
+ $linew -= $one_space_width;
+ }
+ }
+ }
+ $mdiff = ($tw - $linew);
+ if ($plalign == 'C') {
+ if ($this->rtl) {
+ $t_x = -($mdiff / 2);
+ } else {
+ $t_x = ($mdiff / 2);
+ }
+ } elseif ($plalign == 'R') {
+ // right alignment on LTR document
+ $t_x = $mdiff;
+ } elseif ($plalign == 'L') {
+ // left alignment on RTL document
+ $t_x = -$mdiff;
+ } elseif (($plalign == 'J') AND ($plalign == $lalign)) {
+ // Justification
+ if ($this->isRTLTextDir()) {
+ // align text on the left
+ $t_x = -$mdiff;
+ }
+ $ns = 0; // number of spaces
+ $pmidtemp = $pmid;
+ // escape special characters
+ $pmidtemp = preg_replace('/[\\\][\(]/x', '\\#!#OP#!#', $pmidtemp);
+ $pmidtemp = preg_replace('/[\\\][\)]/x', '\\#!#CP#!#', $pmidtemp);
+ // search spaces
+ if (preg_match_all('/\[\(([^\)]*)\)\]/x', $pmidtemp, $lnstring, PREG_PATTERN_ORDER)) {
+ $spacestr = $this->getSpaceString();
+ $maxkk = count($lnstring[1]) - 1;
+ for ($kk=0; $kk <= $maxkk; ++$kk) {
+ // restore special characters
+ $lnstring[1][$kk] = str_replace('#!#OP#!#', '(', $lnstring[1][$kk]);
+ $lnstring[1][$kk] = str_replace('#!#CP#!#', ')', $lnstring[1][$kk]);
+ // store number of spaces on the strings
+ $lnstring[2][$kk] = substr_count($lnstring[1][$kk], $spacestr);
+ // count total spaces on line
+ $ns += $lnstring[2][$kk];
+ $lnstring[3][$kk] = $ns;
+ }
+ if ($ns == 0) {
+ $ns = 1;
+ }
+ // calculate additional space to add to each existing space
+ $spacewidth = ($mdiff / ($ns - $no)) * $this->k;
+ if ($this->FontSize <= 0) {
+ $this->FontSize = 1;
+ }
+ $spacewidthu = -1000 * ($mdiff + (($ns + $no) * $one_space_width)) / $ns / $this->FontSize;
+ if ($this->font_spacing != 0) {
+ // fixed spacing mode
+ $osw = -1000 * $this->font_spacing / $this->FontSize;
+ $spacewidthu += $osw;
+ }
+ $nsmax = $ns;
+ $ns = 0;
+ reset($lnstring);
+ $offset = 0;
+ $strcount = 0;
+ $prev_epsposbeg = 0;
+ $textpos = 0;
+ if ($this->isRTLTextDir()) {
+ $textpos = $this->wPt;
+ }
+ global $spacew;
+ while (preg_match('/([0-9\.\+\-]*)[\s](Td|cm|m|l|c|re)[\s]/x', $pmid, $strpiece, PREG_OFFSET_CAPTURE, $offset) == 1) {
+ // check if we are inside a string section '[( ... )]'
+ $stroffset = strpos($pmid, '[(', $offset);
+ if (($stroffset !== false) AND ($stroffset <= $strpiece[2][1])) {
+ // set offset to the end of string section
+ $offset = strpos($pmid, ')]', $stroffset);
+ while (($offset !== false) AND ($pmid[($offset - 1)] == '\\')) {
+ $offset = strpos($pmid, ')]', ($offset + 1));
+ }
+ if ($offset === false) {
+ $this->Error('HTML Justification: malformed PDF code.');
+ }
+ continue;
+ }
+ if ($this->isRTLTextDir()) {
+ $spacew = ($spacewidth * ($nsmax - $ns));
+ } else {
+ $spacew = ($spacewidth * $ns);
+ }
+ $offset = $strpiece[2][1] + strlen($strpiece[2][0]);
+ $epsposbeg = strpos($pmid, 'q'.$this->epsmarker, $offset);
+ $epsposend = strpos($pmid, $this->epsmarker.'Q', $offset) + strlen($this->epsmarker.'Q');
+ if ((($epsposbeg > 0) AND ($epsposend > 0) AND ($offset > $epsposbeg) AND ($offset < $epsposend))
+ OR (($epsposbeg === false) AND ($epsposend > 0) AND ($offset < $epsposend))) {
+ // shift EPS images
+ $trx = sprintf('1 0 0 1 %F 0 cm', $spacew);
+ $epsposbeg = strpos($pmid, 'q'.$this->epsmarker, ($prev_epsposbeg - 6));
+ $pmid_b = substr($pmid, 0, $epsposbeg);
+ $pmid_m = substr($pmid, $epsposbeg, ($epsposend - $epsposbeg));
+ $pmid_e = substr($pmid, $epsposend);
+ $pmid = $pmid_b."\nq\n".$trx."\n".$pmid_m."\nQ\n".$pmid_e;
+ $offset = $epsposend;
+ continue;
+
+ }
+ $prev_epsposbeg = $epsposbeg;
+ $currentxpos = 0;
+ // shift blocks of code
+ switch ($strpiece[2][0]) {
+ case 'Td':
+ case 'cm':
+ case 'm':
+ case 'l': {
+ // get current X position
+ preg_match('/([0-9\.\+\-]*)[\s]('.$strpiece[1][0].')[\s]('.$strpiece[2][0].')([\s]*)/x', $pmid, $xmatches);
+ $currentxpos = $xmatches[1];
+ $textpos = $currentxpos;
+ if (($strcount <= $maxkk) AND ($strpiece[2][0] == 'Td')) {
+ $ns = $lnstring[3][$strcount];
+ if ($this->isRTLTextDir()) {
+ $spacew = ($spacewidth * ($nsmax - $ns));
+ }
+ ++$strcount;
+ }
+ // justify block
+ $pmid = preg_replace_callback('/([0-9\.\+\-]*)[\s]('.$strpiece[1][0].')[\s]('.$strpiece[2][0].')([\s]*)/x',
+ create_function('$matches', 'global $spacew;
+ $newx = sprintf("%F",(floatval($matches[1]) + $spacew));
+ return "".$newx." ".$matches[2]." x*#!#*x".$matches[3].$matches[4];'), $pmid, 1);
+ break;
+ }
+ case 're': {
+ // justify block
+ if (!TCPDF_STATIC::empty_string($this->lispacer)) {
+ $this->lispacer = '';
+ continue;
+ }
+ preg_match('/([0-9\.\+\-]*)[\s]([0-9\.\+\-]*)[\s]([0-9\.\+\-]*)[\s]('.$strpiece[1][0].')[\s](re)([\s]*)/x', $pmid, $xmatches);
+ $currentxpos = $xmatches[1];
+ global $x_diff, $w_diff;
+ $x_diff = 0;
+ $w_diff = 0;
+ if ($this->isRTLTextDir()) { // RTL
+ if ($currentxpos < $textpos) {
+ $x_diff = ($spacewidth * ($nsmax - $lnstring[3][$strcount]));
+ $w_diff = ($spacewidth * $lnstring[2][$strcount]);
+ } else {
+ if ($strcount > 0) {
+ $x_diff = ($spacewidth * ($nsmax - $lnstring[3][($strcount - 1)]));
+ $w_diff = ($spacewidth * $lnstring[2][($strcount - 1)]);
+ }
+ }
+ } else { // LTR
+ if ($currentxpos > $textpos) {
+ if ($strcount > 0) {
+ $x_diff = ($spacewidth * $lnstring[3][($strcount - 1)]);
+ }
+ $w_diff = ($spacewidth * $lnstring[2][$strcount]);
+ } else {
+ if ($strcount > 1) {
+ $x_diff = ($spacewidth * $lnstring[3][($strcount - 2)]);
+ }
+ if ($strcount > 0) {
+ $w_diff = ($spacewidth * $lnstring[2][($strcount - 1)]);
+ }
+ }
+ }
+ $pmid = preg_replace_callback('/('.$xmatches[1].')[\s]('.$xmatches[2].')[\s]('.$xmatches[3].')[\s]('.$strpiece[1][0].')[\s](re)([\s]*)/x',
+ create_function('$matches', 'global $x_diff, $w_diff;
+ $newx = sprintf("%F",(floatval($matches[1]) + $x_diff));
+ $neww = sprintf("%F",(floatval($matches[3]) + $w_diff));
+ return "".$newx." ".$matches[2]." ".$neww." ".$matches[4]." x*#!#*x".$matches[5].$matches[6];'), $pmid, 1);
+ break;
+ }
+ case 'c': {
+ // get current X position
+ preg_match('/([0-9\.\+\-]*)[\s]([0-9\.\+\-]*)[\s]([0-9\.\+\-]*)[\s]([0-9\.\+\-]*)[\s]([0-9\.\+\-]*)[\s]('.$strpiece[1][0].')[\s](c)([\s]*)/x', $pmid, $xmatches);
+ $currentxpos = $xmatches[1];
+ // justify block
+ $pmid = preg_replace_callback('/('.$xmatches[1].')[\s]('.$xmatches[2].')[\s]('.$xmatches[3].')[\s]('.$xmatches[4].')[\s]('.$xmatches[5].')[\s]('.$strpiece[1][0].')[\s](c)([\s]*)/x',
+ create_function('$matches', 'global $spacew;
+ $newx1 = sprintf("%F",(floatval($matches[1]) + $spacew));
+ $newx2 = sprintf("%F",(floatval($matches[3]) + $spacew));
+ $newx3 = sprintf("%F",(floatval($matches[5]) + $spacew));
+ return "".$newx1." ".$matches[2]." ".$newx2." ".$matches[4]." ".$newx3." ".$matches[6]." x*#!#*x".$matches[7].$matches[8];'), $pmid, 1);
+ break;
+ }
+ }
+ // shift the annotations and links
+ $cxpos = ($currentxpos / $this->k);
+ $lmpos = ($this->lMargin + $this->cell_padding['L'] + $this->feps);
+ if ($this->inxobj) {
+ // we are inside an XObject template
+ foreach ($this->xobjects[$this->xobjid]['annotations'] as $pak => $pac) {
+ if (($pac['y'] >= $minstartliney) AND (($pac['x'] * $this->k) >= ($currentxpos - $this->feps)) AND (($pac['x'] * $this->k) <= ($currentxpos + $this->feps))) {
+ if ($cxpos > $lmpos) {
+ $this->xobjects[$this->xobjid]['annotations'][$pak]['x'] += ($spacew / $this->k);
+ $this->xobjects[$this->xobjid]['annotations'][$pak]['w'] += (($spacewidth * $pac['numspaces']) / $this->k);
+ } else {
+ $this->xobjects[$this->xobjid]['annotations'][$pak]['w'] += (($spacewidth * $pac['numspaces']) / $this->k);
+ }
+ break;
+ }
+ }
+ } elseif (isset($this->PageAnnots[$this->page])) {
+ foreach ($this->PageAnnots[$this->page] as $pak => $pac) {
+ if (($pac['y'] >= $minstartliney) AND (($pac['x'] * $this->k) >= ($currentxpos - $this->feps)) AND (($pac['x'] * $this->k) <= ($currentxpos + $this->feps))) {
+ if ($cxpos > $lmpos) {
+ $this->PageAnnots[$this->page][$pak]['x'] += ($spacew / $this->k);
+ $this->PageAnnots[$this->page][$pak]['w'] += (($spacewidth * $pac['numspaces']) / $this->k);
+ } else {
+ $this->PageAnnots[$this->page][$pak]['w'] += (($spacewidth * $pac['numspaces']) / $this->k);
+ }
+ break;
+ }
+ }
+ }
+ } // end of while
+ // remove markers
+ $pmid = str_replace('x*#!#*x', '', $pmid);
+ if ($this->isUnicodeFont()) {
+ // multibyte characters
+ $spacew = $spacewidthu;
+ if ($this->font_stretching != 100) {
+ // word spacing is affected by stretching
+ $spacew /= ($this->font_stretching / 100);
+ }
+ $pmidtemp = $pmid;
+ // escape special characters
+ $pmidtemp = preg_replace('/[\\\][\(]/x', '\\#!#OP#!#', $pmidtemp);
+ $pmidtemp = preg_replace('/[\\\][\)]/x', '\\#!#CP#!#', $pmidtemp);
+ $pmid = preg_replace_callback("/\[\(([^\)]*)\)\]/x",
+ create_function('$matches', 'global $spacew;
+ $matches[1] = str_replace("#!#OP#!#", "(", $matches[1]);
+ $matches[1] = str_replace("#!#CP#!#", ")", $matches[1]);
+ return "[(".str_replace(chr(0).chr(32), ") ".sprintf("%F", $spacew)." (", $matches[1]).")]";'), $pmidtemp);
+ if ($this->inxobj) {
+ // we are inside an XObject template
+ $this->xobjects[$this->xobjid]['outdata'] = $pstart."\n".$pmid."\n".$pend;
+ } else {
+ $this->setPageBuffer($startlinepage, $pstart."\n".$pmid."\n".$pend);
+ }
+ $endlinepos = strlen($pstart."\n".$pmid."\n");
+ } else {
+ // non-unicode (single-byte characters)
+ if ($this->font_stretching != 100) {
+ // word spacing (Tw) is affected by stretching
+ $spacewidth /= ($this->font_stretching / 100);
+ }
+ $rs = sprintf('%F Tw', $spacewidth);
+ $pmid = preg_replace("/\[\(/x", $rs.' [(', $pmid);
+ if ($this->inxobj) {
+ // we are inside an XObject template
+ $this->xobjects[$this->xobjid]['outdata'] = $pstart."\n".$pmid."\nBT 0 Tw ET\n".$pend;
+ } else {
+ $this->setPageBuffer($startlinepage, $pstart."\n".$pmid."\nBT 0 Tw ET\n".$pend);
+ }
+ $endlinepos = strlen($pstart."\n".$pmid."\nBT 0 Tw ET\n");
+ }
+ }
+ } // end of J
+ } // end if $startlinex
+ if (($t_x != 0) OR ($yshift < 0)) {
+ // shift the line
+ $trx = sprintf('1 0 0 1 %F %F cm', ($t_x * $this->k), ($yshift * $this->k));
+ $pstart .= "\nq\n".$trx."\n".$pmid."\nQ\n";
+ $endlinepos = strlen($pstart);
+ if ($this->inxobj) {
+ // we are inside an XObject template
+ $this->xobjects[$this->xobjid]['outdata'] = $pstart.$pend;
+ foreach ($this->xobjects[$this->xobjid]['annotations'] as $pak => $pac) {
+ if ($pak >= $pask) {
+ $this->xobjects[$this->xobjid]['annotations'][$pak]['x'] += $t_x;
+ $this->xobjects[$this->xobjid]['annotations'][$pak]['y'] -= $yshift;
+ }
+ }
+ } else {
+ $this->setPageBuffer($startlinepage, $pstart.$pend);
+ // shift the annotations and links
+ if (isset($this->PageAnnots[$this->page])) {
+ foreach ($this->PageAnnots[$this->page] as $pak => $pac) {
+ if ($pak >= $pask) {
+ $this->PageAnnots[$this->page][$pak]['x'] += $t_x;
+ $this->PageAnnots[$this->page][$pak]['y'] -= $yshift;
+ }
+ }
+ }
+ }
+ $this->y -= $yshift;
+ }
+ }
+ $pbrk = $this->checkPageBreak($this->lasth);
+ $this->newline = false;
+ $startlinex = $this->x;
+ $startliney = $this->y;
+ if ($dom[$dom[$key]['parent']]['value'] == 'sup') {
+ $startliney -= ((0.3 * $this->FontSizePt) / $this->k);
+ } elseif ($dom[$dom[$key]['parent']]['value'] == 'sub') {
+ $startliney -= (($this->FontSizePt / 0.7) / $this->k);
+ } else {
+ $minstartliney = $startliney;
+ $maxbottomliney = ($this->y + (($fontsize * $this->cell_height_ratio) / $this->k));
+ }
+ $startlinepage = $this->page;
+ if (isset($endlinepos) AND (!$pbrk)) {
+ $startlinepos = $endlinepos;
+ } else {
+ if ($this->inxobj) {
+ // we are inside an XObject template
+ $startlinepos = strlen($this->xobjects[$this->xobjid]['outdata']);
+ } elseif (!$this->InFooter) {
+ if (isset($this->footerlen[$this->page])) {
+ $this->footerpos[$this->page] = $this->pagelen[$this->page] - $this->footerlen[$this->page];
+ } else {
+ $this->footerpos[$this->page] = $this->pagelen[$this->page];
+ }
+ $startlinepos = $this->footerpos[$this->page];
+ } else {
+ $startlinepos = $this->pagelen[$this->page];
+ }
+ }
+ unset($endlinepos);
+ $plalign = $lalign;
+ if (isset($this->PageAnnots[$this->page])) {
+ $pask = count($this->PageAnnots[$this->page]);
+ } else {
+ $pask = 0;
+ }
+ if (!($dom[$key]['tag'] AND !$dom[$key]['opening'] AND ($dom[$key]['value'] == 'table')
+ AND (isset($this->emptypagemrk[$this->page]))
+ AND ($this->emptypagemrk[$this->page] == $this->pagelen[$this->page]))) {
+ $this->SetFont($fontname, $fontstyle, $fontsize);
+ if ($wfill) {
+ $this->SetFillColorArray($this->bgcolor);
+ }
+ }
+ } // end newline
+ if (isset($opentagpos)) {
+ unset($opentagpos);
+ }
+ if ($dom[$key]['tag']) {
+ if ($dom[$key]['opening']) {
+ // get text indentation (if any)
+ if (isset($dom[$key]['text-indent']) AND $dom[$key]['block']) {
+ $this->textindent = $dom[$key]['text-indent'];
+ $this->newline = true;
+ }
+ // table
+ if (($dom[$key]['value'] == 'table') AND isset($dom[$key]['cols']) AND ($dom[$key]['cols'] > 0)) {
+ // available page width
+ if ($this->rtl) {
+ $wtmp = $this->x - $this->lMargin;
+ } else {
+ $wtmp = $this->w - $this->rMargin - $this->x;
+ }
+ // get cell spacing
+ if (isset($dom[$key]['attribute']['cellspacing'])) {
+ $clsp = $this->getHTMLUnitToUnits($dom[$key]['attribute']['cellspacing'], 1, 'px');
+ $cellspacing = array('H' => $clsp, 'V' => $clsp);
+ } elseif (isset($dom[$key]['border-spacing'])) {
+ $cellspacing = $dom[$key]['border-spacing'];
+ } else {
+ $cellspacing = array('H' => 0, 'V' => 0);
+ }
+ // table width
+ if (isset($dom[$key]['width'])) {
+ $table_width = $this->getHTMLUnitToUnits($dom[$key]['width'], $wtmp, 'px');
+ } else {
+ $table_width = $wtmp;
+ }
+ $table_width -= (2 * $cellspacing['H']);
+ if (!$this->inthead) {
+ $this->y += $cellspacing['V'];
+ }
+ if ($this->rtl) {
+ $cellspacingx = -$cellspacing['H'];
+ } else {
+ $cellspacingx = $cellspacing['H'];
+ }
+ // total table width without cellspaces
+ $table_columns_width = ($table_width - ($cellspacing['H'] * ($dom[$key]['cols'] - 1)));
+ // minimum column width
+ $table_min_column_width = ($table_columns_width / $dom[$key]['cols']);
+ // array of custom column widths
+ $table_colwidths = array_fill(0, $dom[$key]['cols'], $table_min_column_width);
+ }
+ // table row
+ if ($dom[$key]['value'] == 'tr') {
+ // reset column counter
+ $colid = 0;
+ }
+ // table cell
+ if (($dom[$key]['value'] == 'td') OR ($dom[$key]['value'] == 'th')) {
+ $trid = $dom[$key]['parent'];
+ $table_el = $dom[$trid]['parent'];
+ if (!isset($dom[$table_el]['cols'])) {
+ $dom[$table_el]['cols'] = $dom[$trid]['cols'];
+ }
+ // store border info
+ $tdborder = 0;
+ if (isset($dom[$key]['border']) AND !empty($dom[$key]['border'])) {
+ $tdborder = $dom[$key]['border'];
+ }
+ $colspan = intval($dom[$key]['attribute']['colspan']);
+ if ($colspan <= 0) {
+ $colspan = 1;
+ }
+ $old_cell_padding = $this->cell_padding;
+ if (isset($dom[($dom[$trid]['parent'])]['attribute']['cellpadding'])) {
+ $crclpd = $this->getHTMLUnitToUnits($dom[($dom[$trid]['parent'])]['attribute']['cellpadding'], 1, 'px');
+ $current_cell_padding = array('L' => $crclpd, 'T' => $crclpd, 'R' => $crclpd, 'B' => $crclpd);
+ } elseif (isset($dom[($dom[$trid]['parent'])]['padding'])) {
+ $current_cell_padding = $dom[($dom[$trid]['parent'])]['padding'];
+ } else {
+ $current_cell_padding = array('L' => 0, 'T' => 0, 'R' => 0, 'B' => 0);
+ }
+ $this->cell_padding = $current_cell_padding;
+ if (isset($dom[$key]['height'])) {
+ // minimum cell height
+ $cellh = $this->getHTMLUnitToUnits($dom[$key]['height'], 0, 'px');
+ } else {
+ $cellh = 0;
+ }
+ if (isset($dom[$key]['content'])) {
+ $cell_content = stripslashes($dom[$key]['content']);
+ } else {
+ $cell_content = '&nbsp;';
+ }
+ $tagtype = $dom[$key]['value'];
+ $parentid = $key;
+ while (($key < $maxel) AND (!(($dom[$key]['tag']) AND (!$dom[$key]['opening']) AND ($dom[$key]['value'] == $tagtype) AND ($dom[$key]['parent'] == $parentid)))) {
+ // move $key index forward
+ ++$key;
+ }
+ if (!isset($dom[$trid]['startpage'])) {
+ $dom[$trid]['startpage'] = $this->page;
+ } else {
+ $this->setPage($dom[$trid]['startpage']);
+ }
+ if (!isset($dom[$trid]['startcolumn'])) {
+ $dom[$trid]['startcolumn'] = $this->current_column;
+ } elseif ($this->current_column != $dom[$trid]['startcolumn']) {
+ $tmpx = $this->x;
+ $this->selectColumn($dom[$trid]['startcolumn']);
+ $this->x = $tmpx;
+ }
+ if (!isset($dom[$trid]['starty'])) {
+ $dom[$trid]['starty'] = $this->y;
+ } else {
+ $this->y = $dom[$trid]['starty'];
+ }
+ if (!isset($dom[$trid]['startx'])) {
+ $dom[$trid]['startx'] = $this->x;
+ $this->x += $cellspacingx;
+ } else {
+ $this->x += ($cellspacingx / 2);
+ }
+ if (isset($dom[$parentid]['attribute']['rowspan'])) {
+ $rowspan = intval($dom[$parentid]['attribute']['rowspan']);
+ } else {
+ $rowspan = 1;
+ }
+ // skip row-spanned cells started on the previous rows
+ if (isset($dom[$table_el]['rowspans'])) {
+ $rsk = 0;
+ $rskmax = count($dom[$table_el]['rowspans']);
+ while ($rsk < $rskmax) {
+ $trwsp = $dom[$table_el]['rowspans'][$rsk];
+ $rsstartx = $trwsp['startx'];
+ $rsendx = $trwsp['endx'];
+ // account for margin changes
+ if ($trwsp['startpage'] < $this->page) {
+ if (($this->rtl) AND ($this->pagedim[$this->page]['orm'] != $this->pagedim[$trwsp['startpage']]['orm'])) {
+ $dl = ($this->pagedim[$this->page]['orm'] - $this->pagedim[$trwsp['startpage']]['orm']);
+ $rsstartx -= $dl;
+ $rsendx -= $dl;
+ } elseif ((!$this->rtl) AND ($this->pagedim[$this->page]['olm'] != $this->pagedim[$trwsp['startpage']]['olm'])) {
+ $dl = ($this->pagedim[$this->page]['olm'] - $this->pagedim[$trwsp['startpage']]['olm']);
+ $rsstartx += $dl;
+ $rsendx += $dl;
+ }
+ }
+ if (($trwsp['rowspan'] > 0)
+ AND ($rsstartx > ($this->x - $cellspacing['H'] - $current_cell_padding['L'] - $this->feps))
+ AND ($rsstartx < ($this->x + $cellspacing['H'] + $current_cell_padding['R'] + $this->feps))
+ AND (($trwsp['starty'] < ($this->y - $this->feps)) OR ($trwsp['startpage'] < $this->page) OR ($trwsp['startcolumn'] < $this->current_column))) {
+ // set the starting X position of the current cell
+ $this->x = $rsendx + $cellspacingx;
+ // increment column indicator
+ $colid += $trwsp['colspan'];
+ if (($trwsp['rowspan'] == 1)
+ AND (isset($dom[$trid]['endy']))
+ AND (isset($dom[$trid]['endpage']))
+ AND (isset($dom[$trid]['endcolumn']))
+ AND ($trwsp['endpage'] == $dom[$trid]['endpage'])
+ AND ($trwsp['endcolumn'] == $dom[$trid]['endcolumn'])) {
+ // set ending Y position for row
+ $dom[$table_el]['rowspans'][$rsk]['endy'] = max($dom[$trid]['endy'], $trwsp['endy']);
+ $dom[$trid]['endy'] = $dom[$table_el]['rowspans'][$rsk]['endy'];
+ }
+ $rsk = 0;
+ } else {
+ ++$rsk;
+ }
+ }
+ }
+ if (isset($dom[$parentid]['width'])) {
+ // user specified width
+ $cellw = $this->getHTMLUnitToUnits($dom[$parentid]['width'], $table_columns_width, 'px');
+ $tmpcw = ($cellw / $colspan);
+ for ($i = 0; $i < $colspan; ++$i) {
+ $table_colwidths[($colid + $i)] = $tmpcw;
+ }
+ } else {
+ // inherit column width
+ $cellw = 0;
+ for ($i = 0; $i < $colspan; ++$i) {
+ $cellw += $table_colwidths[($colid + $i)];
+ }
+ }
+ $cellw += (($colspan - 1) * $cellspacing['H']);
+ // increment column indicator
+ $colid += $colspan;
+ // add rowspan information to table element
+ if ($rowspan > 1) {
+ $trsid = array_push($dom[$table_el]['rowspans'], array('trid' => $trid, 'rowspan' => $rowspan, 'mrowspan' => $rowspan, 'colspan' => $colspan, 'startpage' => $this->page, 'startcolumn' => $this->current_column, 'startx' => $this->x, 'starty' => $this->y));
+ }
+ $cellid = array_push($dom[$trid]['cellpos'], array('startx' => $this->x));
+ if ($rowspan > 1) {
+ $dom[$trid]['cellpos'][($cellid - 1)]['rowspanid'] = ($trsid - 1);
+ }
+ // push background colors
+ if (isset($dom[$parentid]['bgcolor']) AND ($dom[$parentid]['bgcolor'] !== false)) {
+ $dom[$trid]['cellpos'][($cellid - 1)]['bgcolor'] = $dom[$parentid]['bgcolor'];
+ }
+ // store border info
+ if (isset($tdborder) AND !empty($tdborder)) {
+ $dom[$trid]['cellpos'][($cellid - 1)]['border'] = $tdborder;
+ }
+ $prevLastH = $this->lasth;
+ // store some info for multicolumn mode
+ if ($this->rtl) {
+ $this->colxshift['x'] = $this->w - $this->x - $this->rMargin;
+ } else {
+ $this->colxshift['x'] = $this->x - $this->lMargin;
+ }
+ $this->colxshift['s'] = $cellspacing;
+ $this->colxshift['p'] = $current_cell_padding;
+ // ****** write the cell content ******
+ $this->MultiCell($cellw, $cellh, $cell_content, false, $lalign, false, 2, '', '', true, 0, true, true, 0, 'T', false);
+ // restore some values
+ $this->colxshift = array('x' => 0, 's' => array('H' => 0, 'V' => 0), 'p' => array('L' => 0, 'T' => 0, 'R' => 0, 'B' => 0));
+ $this->lasth = $prevLastH;
+ $this->cell_padding = $old_cell_padding;
+ $dom[$trid]['cellpos'][($cellid - 1)]['endx'] = $this->x;
+ // update the end of row position
+ if ($rowspan <= 1) {
+ if (isset($dom[$trid]['endy'])) {
+ if (($this->page == $dom[$trid]['endpage']) AND ($this->current_column == $dom[$trid]['endcolumn'])) {
+ $dom[$trid]['endy'] = max($this->y, $dom[$trid]['endy']);
+ } elseif (($this->page > $dom[$trid]['endpage']) OR ($this->current_column > $dom[$trid]['endcolumn'])) {
+ $dom[$trid]['endy'] = $this->y;
+ }
+ } else {
+ $dom[$trid]['endy'] = $this->y;
+ }
+ if (isset($dom[$trid]['endpage'])) {
+ $dom[$trid]['endpage'] = max($this->page, $dom[$trid]['endpage']);
+ } else {
+ $dom[$trid]['endpage'] = $this->page;
+ }
+ if (isset($dom[$trid]['endcolumn'])) {
+ $dom[$trid]['endcolumn'] = max($this->current_column, $dom[$trid]['endcolumn']);
+ } else {
+ $dom[$trid]['endcolumn'] = $this->current_column;
+ }
+ } else {
+ // account for row-spanned cells
+ $dom[$table_el]['rowspans'][($trsid - 1)]['endx'] = $this->x;
+ $dom[$table_el]['rowspans'][($trsid - 1)]['endy'] = $this->y;
+ $dom[$table_el]['rowspans'][($trsid - 1)]['endpage'] = $this->page;
+ $dom[$table_el]['rowspans'][($trsid - 1)]['endcolumn'] = $this->current_column;
+ }
+ if (isset($dom[$table_el]['rowspans'])) {
+ // update endy and endpage on rowspanned cells
+ foreach ($dom[$table_el]['rowspans'] as $k => $trwsp) {
+ if ($trwsp['rowspan'] > 0) {
+ if (isset($dom[$trid]['endpage'])) {
+ if (($trwsp['endpage'] == $dom[$trid]['endpage']) AND ($trwsp['endcolumn'] == $dom[$trid]['endcolumn'])) {
+ $dom[$table_el]['rowspans'][$k]['endy'] = max($dom[$trid]['endy'], $trwsp['endy']);
+ } elseif (($trwsp['endpage'] < $dom[$trid]['endpage']) OR ($trwsp['endcolumn'] < $dom[$trid]['endcolumn'])) {
+ $dom[$table_el]['rowspans'][$k]['endy'] = $dom[$trid]['endy'];
+ $dom[$table_el]['rowspans'][$k]['endpage'] = $dom[$trid]['endpage'];
+ $dom[$table_el]['rowspans'][$k]['endcolumn'] = $dom[$trid]['endcolumn'];
+ } else {
+ $dom[$trid]['endy'] = $this->pagedim[$dom[$trid]['endpage']]['hk'] - $this->pagedim[$dom[$trid]['endpage']]['bm'];
+ }
+ }
+ }
+ }
+ }
+ $this->x += ($cellspacingx / 2);
+ } else {
+ // opening tag (or self-closing tag)
+ if (!isset($opentagpos)) {
+ if ($this->inxobj) {
+ // we are inside an XObject template
+ $opentagpos = strlen($this->xobjects[$this->xobjid]['outdata']);
+ } elseif (!$this->InFooter) {
+ if (isset($this->footerlen[$this->page])) {
+ $this->footerpos[$this->page] = $this->pagelen[$this->page] - $this->footerlen[$this->page];
+ } else {
+ $this->footerpos[$this->page] = $this->pagelen[$this->page];
+ }
+ $opentagpos = $this->footerpos[$this->page];
+ }
+ }
+ $dom = $this->openHTMLTagHandler($dom, $key, $cell);
+ }
+ } else { // closing tag
+ $prev_numpages = $this->numpages;
+ $old_bordermrk = $this->bordermrk[$this->page];
+ $dom = $this->closeHTMLTagHandler($dom, $key, $cell, $maxbottomliney);
+ if ($this->bordermrk[$this->page] > $old_bordermrk) {
+ $startlinepos += ($this->bordermrk[$this->page] - $old_bordermrk);
+ }
+ if ($prev_numpages > $this->numpages) {
+ $startlinepage = $this->page;
+ }
+ }
+ } elseif (strlen($dom[$key]['value']) > 0) {
+ // print list-item
+ if (!TCPDF_STATIC::empty_string($this->lispacer) AND ($this->lispacer != '^')) {
+ $this->SetFont($pfontname, $pfontstyle, $pfontsize);
+ $this->resetLastH();
+ $minstartliney = $this->y;
+ $maxbottomliney = ($startliney + ($this->FontSize * $this->cell_height_ratio));
+ if (is_numeric($pfontsize) AND ($pfontsize > 0)) {
+ $this->putHtmlListBullet($this->listnum, $this->lispacer, $pfontsize);
+ }
+ $this->SetFont($curfontname, $curfontstyle, $curfontsize);
+ $this->resetLastH();
+ if (is_numeric($pfontsize) AND ($pfontsize > 0) AND is_numeric($curfontsize) AND ($curfontsize > 0) AND ($pfontsize != $curfontsize)) {
+ $pfontascent = $this->getFontAscent($pfontname, $pfontstyle, $pfontsize);
+ $pfontdescent = $this->getFontDescent($pfontname, $pfontstyle, $pfontsize);
+ $this->y += ((($pfontsize - $curfontsize) * $this->cell_height_ratio / $this->k) + $pfontascent - $curfontascent - $pfontdescent + $curfontdescent) / 2;
+ $minstartliney = min($this->y, $minstartliney);
+ $maxbottomliney = max(($this->y + (($pfontsize * $this->cell_height_ratio) / $this->k)), $maxbottomliney);
+ }
+ }
+ // text
+ $this->htmlvspace = 0;
+ if ((!$this->premode) AND $this->isRTLTextDir()) {
+ // reverse spaces order
+ $lsp = ''; // left spaces
+ $rsp = ''; // right spaces
+ if (preg_match('/^('.$this->re_space['p'].'+)/'.$this->re_space['m'], $dom[$key]['value'], $matches)) {
+ $lsp = $matches[1];
+ }
+ if (preg_match('/('.$this->re_space['p'].'+)$/'.$this->re_space['m'], $dom[$key]['value'], $matches)) {
+ $rsp = $matches[1];
+ }
+ $dom[$key]['value'] = $rsp.$this->stringTrim($dom[$key]['value']).$lsp;
+ }
+ if ($newline) {
+ if (!$this->premode) {
+ $prelen = strlen($dom[$key]['value']);
+ if ($this->isRTLTextDir()) {
+ // right trim except non-breaking space
+ $dom[$key]['value'] = $this->stringRightTrim($dom[$key]['value']);
+ } else {
+ // left trim except non-breaking space
+ $dom[$key]['value'] = $this->stringLeftTrim($dom[$key]['value']);
+ }
+ $postlen = strlen($dom[$key]['value']);
+ if (($postlen == 0) AND ($prelen > 0)) {
+ $dom[$key]['trimmed_space'] = true;
+ }
+ }
+ $newline = false;
+ $firstblock = true;
+ } else {
+ $firstblock = false;
+ // replace empty multiple spaces string with a single space
+ $dom[$key]['value'] = preg_replace('/^'.$this->re_space['p'].'+$/'.$this->re_space['m'], chr(32), $dom[$key]['value']);
+ }
+ $strrest = '';
+ if ($this->rtl) {
+ $this->x -= $this->textindent;
+ } else {
+ $this->x += $this->textindent;
+ }
+ if (!isset($dom[$key]['trimmed_space']) OR !$dom[$key]['trimmed_space']) {
+ $strlinelen = $this->GetStringWidth($dom[$key]['value']);
+ if (!empty($this->HREF) AND (isset($this->HREF['url']))) {
+ // HTML <a> Link
+ $hrefcolor = '';
+ if (isset($dom[($dom[$key]['parent'])]['fgcolor']) AND ($dom[($dom[$key]['parent'])]['fgcolor'] !== false)) {
+ $hrefcolor = $dom[($dom[$key]['parent'])]['fgcolor'];
+ }
+ $hrefstyle = -1;
+ if (isset($dom[($dom[$key]['parent'])]['fontstyle']) AND ($dom[($dom[$key]['parent'])]['fontstyle'] !== false)) {
+ $hrefstyle = $dom[($dom[$key]['parent'])]['fontstyle'];
+ }
+ $strrest = $this->addHtmlLink($this->HREF['url'], $dom[$key]['value'], $wfill, true, $hrefcolor, $hrefstyle, true);
+ } else {
+ $wadj = 0; // space to leave for block continuity
+ if ($this->rtl) {
+ $cwa = ($this->x - $this->lMargin);
+ } else {
+ $cwa = ($this->w - $this->rMargin - $this->x);
+ }
+ if (($strlinelen < $cwa) AND (isset($dom[($key + 1)])) AND ($dom[($key + 1)]['tag']) AND (!$dom[($key + 1)]['block'])) {
+ // check the next text blocks for continuity
+ $nkey = ($key + 1);
+ $write_block = true;
+ $same_textdir = true;
+ $tmp_fontname = $this->FontFamily;
+ $tmp_fontstyle = $this->FontStyle;
+ $tmp_fontsize = $this->FontSizePt;
+ while ($write_block AND isset($dom[$nkey])) {
+ if ($dom[$nkey]['tag']) {
+ if ($dom[$nkey]['block']) {
+ // end of block
+ $write_block = false;
+ }
+ $tmp_fontname = isset($dom[$nkey]['fontname']) ? $dom[$nkey]['fontname'] : $this->FontFamily;
+ $tmp_fontstyle = isset($dom[$nkey]['fontstyle']) ? $dom[$nkey]['fontstyle'] : $this->FontStyle;
+ $tmp_fontsize = isset($dom[$nkey]['fontsize']) ? $dom[$nkey]['fontsize'] : $this->FontSizePt;
+ $same_textdir = ($dom[$nkey]['dir'] == $dom[$key]['dir']);
+ } else {
+ $nextstr = TCPDF_STATIC::pregSplit('/'.$this->re_space['p'].'+/', $this->re_space['m'], $dom[$nkey]['value']);
+ if (isset($nextstr[0]) AND $same_textdir) {
+ $wadj += $this->GetStringWidth($nextstr[0], $tmp_fontname, $tmp_fontstyle, $tmp_fontsize);
+ if (isset($nextstr[1])) {
+ $write_block = false;
+ }
+ }
+ }
+ ++$nkey;
+ }
+ }
+ if (($wadj > 0) AND (($strlinelen + $wadj) >= $cwa)) {
+ $wadj = 0;
+ $nextstr = TCPDF_STATIC::pregSplit('/'.$this->re_space['p'].'/', $this->re_space['m'], $dom[$key]['value']);
+ $numblks = count($nextstr);
+ if ($numblks > 1) {
+ // try to split on blank spaces
+ $wadj = ($cwa - $strlinelen + $this->GetStringWidth($nextstr[($numblks - 1)]));
+ } else {
+ // set the entire block on new line
+ $wadj = $this->GetStringWidth($nextstr[0]);
+ }
+ }
+ // check for reversed text direction
+ if (($wadj > 0) AND (($this->rtl AND ($this->tmprtl === 'L')) OR (!$this->rtl AND ($this->tmprtl === 'R')))) {
+ // LTR text on RTL direction or RTL text on LTR direction
+ $reverse_dir = true;
+ $this->rtl = !$this->rtl;
+ $revshift = ($strlinelen + $wadj + 0.000001); // add little quantity for rounding problems
+ if ($this->rtl) {
+ $this->x += $revshift;
+ } else {
+ $this->x -= $revshift;
+ }
+ $xws = $this->x;
+ }
+ // ****** write only until the end of the line and get the rest ******
+ $strrest = $this->Write($this->lasth, $dom[$key]['value'], '', $wfill, '', false, 0, true, $firstblock, 0, $wadj);
+ // restore default direction
+ if ($reverse_dir AND ($wadj == 0)) {
+ $this->x = $xws;
+ $this->rtl = !$this->rtl;
+ $reverse_dir = false;
+ }
+ }
+ }
+ $this->textindent = 0;
+ if (strlen($strrest) > 0) {
+ // store the remaining string on the previous $key position
+ $this->newline = true;
+ if ($strrest == $dom[$key]['value']) {
+ // used to avoid infinite loop
+ ++$loop;
+ } else {
+ $loop = 0;
+ }
+ $dom[$key]['value'] = $strrest;
+ if ($cell) {
+ if ($this->rtl) {
+ $this->x -= $this->cell_padding['R'];
+ } else {
+ $this->x += $this->cell_padding['L'];
+ }
+ }
+ if ($loop < 3) {
+ --$key;
+ }
+ } else {
+ $loop = 0;
+ // add the positive font spacing of the last character (if any)
+ if ($this->font_spacing > 0) {
+ if ($this->rtl) {
+ $this->x -= $this->font_spacing;
+ } else {
+ $this->x += $this->font_spacing;
+ }
+ }
+ }
+ }
+ ++$key;
+ if (isset($dom[$key]['tag']) AND $dom[$key]['tag'] AND (!isset($dom[$key]['opening']) OR !$dom[$key]['opening']) AND isset($dom[($dom[$key]['parent'])]['attribute']['nobr']) AND ($dom[($dom[$key]['parent'])]['attribute']['nobr'] == 'true')) {
+ // check if we are on a new page or on a new column
+ if ((!$undo) AND (($this->y < $this->start_transaction_y) OR (($dom[$key]['value'] == 'tr') AND ($dom[($dom[$key]['parent'])]['endy'] < $this->start_transaction_y)))) {
+ // we are on a new page or on a new column and the total object height is less than the available vertical space.
+ // restore previous object
+ $this->rollbackTransaction(true);
+ // restore previous values
+ foreach ($this_method_vars as $vkey => $vval) {
+ $$vkey = $vval;
+ }
+ // add a page (or trig AcceptPageBreak() for multicolumn mode)
+ $pre_y = $this->y;
+ if ((!$this->checkPageBreak($this->PageBreakTrigger + 1)) AND ($this->y < $pre_y)) {
+ $startliney = $this->y;
+ }
+ $undo = true; // avoid infinite loop
+ } else {
+ $undo = false;
+ }
+ }
+ } // end for each $key
+ // align the last line
+ if (isset($startlinex)) {
+ $yshift = ($minstartliney - $startliney);
+ if (($yshift > 0) OR ($this->page > $startlinepage)) {
+ $yshift = 0;
+ }
+ $t_x = 0;
+ // the last line must be shifted to be aligned as requested
+ $linew = abs($this->endlinex - $startlinex);
+ if ($this->inxobj) {
+ // we are inside an XObject template
+ $pstart = substr($this->xobjects[$this->xobjid]['outdata'], 0, $startlinepos);
+ if (isset($opentagpos)) {
+ $midpos = $opentagpos;
+ } else {
+ $midpos = 0;
+ }
+ if ($midpos > 0) {
+ $pmid = substr($this->xobjects[$this->xobjid]['outdata'], $startlinepos, ($midpos - $startlinepos));
+ $pend = substr($this->xobjects[$this->xobjid]['outdata'], $midpos);
+ } else {
+ $pmid = substr($this->xobjects[$this->xobjid]['outdata'], $startlinepos);
+ $pend = '';
+ }
+ } else {
+ $pstart = substr($this->getPageBuffer($startlinepage), 0, $startlinepos);
+ if (isset($opentagpos) AND isset($this->footerlen[$startlinepage]) AND (!$this->InFooter)) {
+ $this->footerpos[$startlinepage] = $this->pagelen[$startlinepage] - $this->footerlen[$startlinepage];
+ $midpos = min($opentagpos, $this->footerpos[$startlinepage]);
+ } elseif (isset($opentagpos)) {
+ $midpos = $opentagpos;
+ } elseif (isset($this->footerlen[$startlinepage]) AND (!$this->InFooter)) {
+ $this->footerpos[$startlinepage] = $this->pagelen[$startlinepage] - $this->footerlen[$startlinepage];
+ $midpos = $this->footerpos[$startlinepage];
+ } else {
+ $midpos = 0;
+ }
+ if ($midpos > 0) {
+ $pmid = substr($this->getPageBuffer($startlinepage), $startlinepos, ($midpos - $startlinepos));
+ $pend = substr($this->getPageBuffer($startlinepage), $midpos);
+ } else {
+ $pmid = substr($this->getPageBuffer($startlinepage), $startlinepos);
+ $pend = '';
+ }
+ }
+ if ((isset($plalign) AND ((($plalign == 'C') OR (($plalign == 'R') AND (!$this->rtl)) OR (($plalign == 'L') AND ($this->rtl)))))) {
+ // calculate shifting amount
+ $tw = $w;
+ if ($this->lMargin != $prevlMargin) {
+ $tw += ($prevlMargin - $this->lMargin);
+ }
+ if ($this->rMargin != $prevrMargin) {
+ $tw += ($prevrMargin - $this->rMargin);
+ }
+ $one_space_width = $this->GetStringWidth(chr(32));
+ $no = 0; // number of spaces on a line contained on a single block
+ if ($this->isRTLTextDir()) { // RTL
+ // remove left space if exist
+ $pos1 = TCPDF_STATIC::revstrpos($pmid, '[(');
+ if ($pos1 > 0) {
+ $pos1 = intval($pos1);
+ if ($this->isUnicodeFont()) {
+ $pos2 = intval(TCPDF_STATIC::revstrpos($pmid, '[('.chr(0).chr(32)));
+ $spacelen = 2;
+ } else {
+ $pos2 = intval(TCPDF_STATIC::revstrpos($pmid, '[('.chr(32)));
+ $spacelen = 1;
+ }
+ if ($pos1 == $pos2) {
+ $pmid = substr($pmid, 0, ($pos1 + 2)).substr($pmid, ($pos1 + 2 + $spacelen));
+ if (substr($pmid, $pos1, 4) == '[()]') {
+ $linew -= $one_space_width;
+ } elseif ($pos1 == strpos($pmid, '[(')) {
+ $no = 1;
+ }
+ }
+ }
+ } else { // LTR
+ // remove right space if exist
+ $pos1 = TCPDF_STATIC::revstrpos($pmid, ')]');
+ if ($pos1 > 0) {
+ $pos1 = intval($pos1);
+ if ($this->isUnicodeFont()) {
+ $pos2 = intval(TCPDF_STATIC::revstrpos($pmid, chr(0).chr(32).')]')) + 2;
+ $spacelen = 2;
+ } else {
+ $pos2 = intval(TCPDF_STATIC::revstrpos($pmid, chr(32).')]')) + 1;
+ $spacelen = 1;
+ }
+ if ($pos1 == $pos2) {
+ $pmid = substr($pmid, 0, ($pos1 - $spacelen)).substr($pmid, $pos1);
+ $linew -= $one_space_width;
+ }
+ }
+ }
+ $mdiff = ($tw - $linew);
+ if ($plalign == 'C') {
+ if ($this->rtl) {
+ $t_x = -($mdiff / 2);
+ } else {
+ $t_x = ($mdiff / 2);
+ }
+ } elseif ($plalign == 'R') {
+ // right alignment on LTR document
+ $t_x = $mdiff;
+ } elseif ($plalign == 'L') {
+ // left alignment on RTL document
+ $t_x = -$mdiff;
+ }
+ } // end if startlinex
+ if (($t_x != 0) OR ($yshift < 0)) {
+ // shift the line
+ $trx = sprintf('1 0 0 1 %F %F cm', ($t_x * $this->k), ($yshift * $this->k));
+ $pstart .= "\nq\n".$trx."\n".$pmid."\nQ\n";
+ $endlinepos = strlen($pstart);
+ if ($this->inxobj) {
+ // we are inside an XObject template
+ $this->xobjects[$this->xobjid]['outdata'] = $pstart.$pend;
+ foreach ($this->xobjects[$this->xobjid]['annotations'] as $pak => $pac) {
+ if ($pak >= $pask) {
+ $this->xobjects[$this->xobjid]['annotations'][$pak]['x'] += $t_x;
+ $this->xobjects[$this->xobjid]['annotations'][$pak]['y'] -= $yshift;
+ }
+ }
+ } else {
+ $this->setPageBuffer($startlinepage, $pstart.$pend);
+ // shift the annotations and links
+ if (isset($this->PageAnnots[$this->page])) {
+ foreach ($this->PageAnnots[$this->page] as $pak => $pac) {
+ if ($pak >= $pask) {
+ $this->PageAnnots[$this->page][$pak]['x'] += $t_x;
+ $this->PageAnnots[$this->page][$pak]['y'] -= $yshift;
+ }
+ }
+ }
+ }
+ $this->y -= $yshift;
+ $yshift = 0;
+ }
+ }
+ // restore previous values
+ $this->setGraphicVars($gvars);
+ if ($this->num_columns > 1) {
+ $this->selectColumn();
+ } elseif ($this->page > $prevPage) {
+ $this->lMargin = $this->pagedim[$this->page]['olm'];
+ $this->rMargin = $this->pagedim[$this->page]['orm'];
+ }
+ // restore previous list state
+ $this->cell_height_ratio = $prev_cell_height_ratio;
+ $this->listnum = $prev_listnum;
+ $this->listordered = $prev_listordered;
+ $this->listcount = $prev_listcount;
+ $this->lispacer = $prev_lispacer;
+ if ($ln AND (!($cell AND ($dom[$key-1]['value'] == 'table')))) {
+ $this->Ln($this->lasth);
+ if ($this->y < $maxbottomliney) {
+ $this->y = $maxbottomliney;
+ }
+ }
+ unset($dom);
+ }
+
+ /**
+ * Process opening tags.
+ * @param $dom (array) html dom array
+ * @param $key (int) current element id
+ * @param $cell (boolean) if true add the default left (or right if RTL) padding to each new line (default false).
+ * @return $dom array
+ * @protected
+ */
+ protected function openHTMLTagHandler($dom, $key, $cell) {
+ $tag = $dom[$key];
+ $parent = $dom[($dom[$key]['parent'])];
+ $firsttag = ($key == 1);
+ // check for text direction attribute
+ if (isset($tag['dir'])) {
+ $this->setTempRTL($tag['dir']);
+ } else {
+ $this->tmprtl = false;
+ }
+ if ($tag['block']) {
+ $hbz = 0; // distance from y to line bottom
+ $hb = 0; // vertical space between block tags
+ // calculate vertical space for block tags
+ if (isset($this->tagvspaces[$tag['value']][0]['h']) AND ($this->tagvspaces[$tag['value']][0]['h'] >= 0)) {
+ $cur_h = $this->tagvspaces[$tag['value']][0]['h'];
+ } elseif (isset($tag['fontsize'])) {
+ $cur_h = ($tag['fontsize'] / $this->k) * $this->cell_height_ratio;
+ } else {
+ $cur_h = $this->FontSize * $this->cell_height_ratio;
+ }
+ if (isset($this->tagvspaces[$tag['value']][0]['n'])) {
+ $n = $this->tagvspaces[$tag['value']][0]['n'];
+ } elseif (preg_match('/[h][0-9]/', $tag['value']) > 0) {
+ $n = 0.6;
+ } else {
+ $n = 1;
+ }
+ if ((!isset($this->tagvspaces[$tag['value']])) AND (in_array($tag['value'], array('div', 'dt', 'dd', 'li', 'br')))) {
+ $hb = 0;
+ } else {
+ $hb = ($n * $cur_h);
+ }
+ if (($this->htmlvspace <= 0) AND ($n > 0)) {
+ if (isset($parent['fontsize'])) {
+ $hbz = (($parent['fontsize'] / $this->k) * $this->cell_height_ratio);
+ } else {
+ $hbz = $this->FontSize * $this->cell_height_ratio;
+ }
+ }
+ if (isset($dom[($key - 1)]) AND ($dom[($key - 1)]['value'] == 'table')) {
+ // fix vertical space after table
+ $hbz = 0;
+ }
+ }
+ // Opening tag
+ switch($tag['value']) {
+ case 'table': {
+ $cp = 0;
+ $cs = 0;
+ $dom[$key]['rowspans'] = array();
+ if (!isset($dom[$key]['attribute']['nested']) OR ($dom[$key]['attribute']['nested'] != 'true')) {
+ $this->htmlvspace = 0;
+ // set table header
+ if (!TCPDF_STATIC::empty_string($dom[$key]['thead'])) {
+ // set table header
+ $this->thead = $dom[$key]['thead'];
+ if (!isset($this->theadMargins) OR (empty($this->theadMargins))) {
+ $this->theadMargins = array();
+ $this->theadMargins['cell_padding'] = $this->cell_padding;
+ $this->theadMargins['lmargin'] = $this->lMargin;
+ $this->theadMargins['rmargin'] = $this->rMargin;
+ $this->theadMargins['page'] = $this->page;
+ $this->theadMargins['cell'] = $cell;
+ }
+ }
+ }
+ // store current margins and page
+ $dom[$key]['old_cell_padding'] = $this->cell_padding;
+ if (isset($tag['attribute']['cellpadding'])) {
+ $pad = $this->getHTMLUnitToUnits($tag['attribute']['cellpadding'], 1, 'px');
+ $this->SetCellPadding($pad);
+ } elseif (isset($tag['padding'])) {
+ $this->cell_padding = $tag['padding'];
+ }
+ if (isset($tag['attribute']['cellspacing'])) {
+ $cs = $this->getHTMLUnitToUnits($tag['attribute']['cellspacing'], 1, 'px');
+ } elseif (isset($tag['border-spacing'])) {
+ $cs = $tag['border-spacing']['V'];
+ }
+ $prev_y = $this->y;
+ if ($this->checkPageBreak(((2 * $cp) + (2 * $cs) + $this->lasth), '', false) OR ($this->y < $prev_y)) {
+ $this->inthead = true;
+ // add a page (or trig AcceptPageBreak() for multicolumn mode)
+ $this->checkPageBreak($this->PageBreakTrigger + 1);
+ }
+ break;
+ }
+ case 'tr': {
+ // array of columns positions
+ $dom[$key]['cellpos'] = array();
+ break;
+ }
+ case 'hr': {
+ if ((isset($tag['height'])) AND ($tag['height'] != '')) {
+ $hrHeight = $this->getHTMLUnitToUnits($tag['height'], 1, 'px');
+ } else {
+ $hrHeight = $this->GetLineWidth();
+ }
+ $this->addHTMLVertSpace($hbz, ($hrHeight / 2), $cell, $firsttag);
+ $x = $this->GetX();
+ $y = $this->GetY();
+ $wtmp = $this->w - $this->lMargin - $this->rMargin;
+ if ($cell) {
+ $wtmp -= ($this->cell_padding['L'] + $this->cell_padding['R']);
+ }
+ if ((isset($tag['width'])) AND ($tag['width'] != '')) {
+ $hrWidth = $this->getHTMLUnitToUnits($tag['width'], $wtmp, 'px');
+ } else {
+ $hrWidth = $wtmp;
+ }
+ $prevlinewidth = $this->GetLineWidth();
+ $this->SetLineWidth($hrHeight);
+ $this->Line($x, $y, $x + $hrWidth, $y);
+ $this->SetLineWidth($prevlinewidth);
+ $this->addHTMLVertSpace(($hrHeight / 2), 0, $cell, !isset($dom[($key + 1)]));
+ break;
+ }
+ case 'a': {
+ if (array_key_exists('href', $tag['attribute'])) {
+ $this->HREF['url'] = $tag['attribute']['href'];
+ }
+ break;
+ }
+ case 'img': {
+ if (isset($tag['attribute']['src'])) {
+ if ($tag['attribute']['src']{0} === '@') {
+ // data stream
+ $tag['attribute']['src'] = '@'.base64_decode(substr($tag['attribute']['src'], 1));
+ $type = '';
+ } else {
+ // get image type
+ $type = TCPDF_IMAGES::getImageFileType($tag['attribute']['src']);
+ }
+ if (!isset($tag['width'])) {
+ $tag['width'] = 0;
+ }
+ if (!isset($tag['height'])) {
+ $tag['height'] = 0;
+ }
+ //if (!isset($tag['attribute']['align'])) {
+ // the only alignment supported is "bottom"
+ // further development is required for other modes.
+ $tag['attribute']['align'] = 'bottom';
+ //}
+ switch($tag['attribute']['align']) {
+ case 'top': {
+ $align = 'T';
+ break;
+ }
+ case 'middle': {
+ $align = 'M';
+ break;
+ }
+ case 'bottom': {
+ $align = 'B';
+ break;
+ }
+ default: {
+ $align = 'B';
+ break;
+ }
+ }
+ $prevy = $this->y;
+ $xpos = $this->x;
+ $imglink = '';
+ if (isset($this->HREF['url']) AND !TCPDF_STATIC::empty_string($this->HREF['url'])) {
+ $imglink = $this->HREF['url'];
+ if ($imglink{0} == '#') {
+ // convert url to internal link
+ $lnkdata = explode(',', $imglink);
+ if (isset($lnkdata[0])) {
+ $page = intval(substr($lnkdata[0], 1));
+ if (empty($page) OR ($page <= 0)) {
+ $page = $this->page;
+ }
+ if (isset($lnkdata[1]) AND (strlen($lnkdata[1]) > 0)) {
+ $lnky = floatval($lnkdata[1]);
+ } else {
+ $lnky = 0;
+ }
+ $imglink = $this->AddLink();
+ $this->SetLink($imglink, $lnky, $page);
+ }
+ }
+ }
+ $border = 0;
+ if (isset($tag['border']) AND !empty($tag['border'])) {
+ // currently only support 1 (frame) or a combination of 'LTRB'
+ $border = $tag['border'];
+ }
+ $iw = '';
+ if (isset($tag['width'])) {
+ $iw = $this->getHTMLUnitToUnits($tag['width'], 1, 'px', false);
+ }
+ $ih = '';
+ if (isset($tag['height'])) {
+ $ih = $this->getHTMLUnitToUnits($tag['height'], 1, 'px', false);
+ }
+ if (($type == 'eps') OR ($type == 'ai')) {
+ $this->ImageEps($tag['attribute']['src'], $xpos, $this->y, $iw, $ih, $imglink, true, $align, '', $border, true);
+ } elseif ($type == 'svg') {
+ $this->ImageSVG($tag['attribute']['src'], $xpos, $this->y, $iw, $ih, $imglink, $align, '', $border, true);
+ } else {
+ $this->Image($tag['attribute']['src'], $xpos, $this->y, $iw, $ih, '', $imglink, $align, false, 300, '', false, false, $border, false, false, true);
+ }
+ switch($align) {
+ case 'T': {
+ $this->y = $prevy;
+ break;
+ }
+ case 'M': {
+ $this->y = (($this->img_rb_y + $prevy - ($tag['fontsize'] / $this->k)) / 2) ;
+ break;
+ }
+ case 'B': {
+ $this->y = $this->img_rb_y - ($tag['fontsize'] / $this->k);
+ break;
+ }
+ }
+ }
+ break;
+ }
+ case 'dl': {
+ ++$this->listnum;
+ if ($this->listnum == 1) {
+ $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
+ } else {
+ $this->addHTMLVertSpace(0, 0, $cell, $firsttag);
+ }
+ break;
+ }
+ case 'dt': {
+ $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
+ break;
+ }
+ case 'dd': {
+ if ($this->rtl) {
+ $this->rMargin += $this->listindent;
+ } else {
+ $this->lMargin += $this->listindent;
+ }
+ ++$this->listindentlevel;
+ $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
+ break;
+ }
+ case 'ul':
+ case 'ol': {
+ ++$this->listnum;
+ if ($tag['value'] == 'ol') {
+ $this->listordered[$this->listnum] = true;
+ } else {
+ $this->listordered[$this->listnum] = false;
+ }
+ if (isset($tag['attribute']['start'])) {
+ $this->listcount[$this->listnum] = intval($tag['attribute']['start']) - 1;
+ } else {
+ $this->listcount[$this->listnum] = 0;
+ }
+ if ($this->rtl) {
+ $this->rMargin += $this->listindent;
+ $this->x -= $this->listindent;
+ } else {
+ $this->lMargin += $this->listindent;
+ $this->x += $this->listindent;
+ }
+ ++$this->listindentlevel;
+ if ($this->listnum == 1) {
+ if ($key > 1) {
+ $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
+ }
+ } else {
+ $this->addHTMLVertSpace(0, 0, $cell, $firsttag);
+ }
+ break;
+ }
+ case 'li': {
+ if ($key > 2) {
+ $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
+ }
+ if ($this->listordered[$this->listnum]) {
+ // ordered item
+ if (isset($parent['attribute']['type']) AND !TCPDF_STATIC::empty_string($parent['attribute']['type'])) {
+ $this->lispacer = $parent['attribute']['type'];
+ } elseif (isset($parent['listtype']) AND !TCPDF_STATIC::empty_string($parent['listtype'])) {
+ $this->lispacer = $parent['listtype'];
+ } elseif (isset($this->lisymbol) AND !TCPDF_STATIC::empty_string($this->lisymbol)) {
+ $this->lispacer = $this->lisymbol;
+ } else {
+ $this->lispacer = '#';
+ }
+ ++$this->listcount[$this->listnum];
+ if (isset($tag['attribute']['value'])) {
+ $this->listcount[$this->listnum] = intval($tag['attribute']['value']);
+ }
+ } else {
+ // unordered item
+ if (isset($parent['attribute']['type']) AND !TCPDF_STATIC::empty_string($parent['attribute']['type'])) {
+ $this->lispacer = $parent['attribute']['type'];
+ } elseif (isset($parent['listtype']) AND !TCPDF_STATIC::empty_string($parent['listtype'])) {
+ $this->lispacer = $parent['listtype'];
+ } elseif (isset($this->lisymbol) AND !TCPDF_STATIC::empty_string($this->lisymbol)) {
+ $this->lispacer = $this->lisymbol;
+ } else {
+ $this->lispacer = '!';
+ }
+ }
+ break;
+ }
+ case 'blockquote': {
+ if ($this->rtl) {
+ $this->rMargin += $this->listindent;
+ } else {
+ $this->lMargin += $this->listindent;
+ }
+ ++$this->listindentlevel;
+ $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
+ break;
+ }
+ case 'br': {
+ $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
+ break;
+ }
+ case 'div': {
+ $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
+ break;
+ }
+ case 'p': {
+ $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
+ break;
+ }
+ case 'pre': {
+ $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
+ $this->premode = true;
+ break;
+ }
+ case 'sup': {
+ $this->SetXY($this->GetX(), $this->GetY() - ((0.7 * $this->FontSizePt) / $this->k));
+ break;
+ }
+ case 'sub': {
+ $this->SetXY($this->GetX(), $this->GetY() + ((0.3 * $this->FontSizePt) / $this->k));
+ break;
+ }
+ case 'h1':
+ case 'h2':
+ case 'h3':
+ case 'h4':
+ case 'h5':
+ case 'h6': {
+ $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
+ break;
+ }
+ // Form fields (since 4.8.000 - 2009-09-07)
+ case 'form': {
+ if (isset($tag['attribute']['action'])) {
+ $this->form_action = $tag['attribute']['action'];
+ } else {
+ $this->Error('Please explicitly set action attribute path!');
+ }
+ if (isset($tag['attribute']['enctype'])) {
+ $this->form_enctype = $tag['attribute']['enctype'];
+ } else {
+ $this->form_enctype = 'application/x-www-form-urlencoded';
+ }
+ if (isset($tag['attribute']['method'])) {
+ $this->form_mode = $tag['attribute']['method'];
+ } else {
+ $this->form_mode = 'post';
+ }
+ break;
+ }
+ case 'input': {
+ if (isset($tag['attribute']['name']) AND !TCPDF_STATIC::empty_string($tag['attribute']['name'])) {
+ $name = $tag['attribute']['name'];
+ } else {
+ break;
+ }
+ $prop = array();
+ $opt = array();
+ if (isset($tag['attribute']['readonly']) AND !TCPDF_STATIC::empty_string($tag['attribute']['readonly'])) {
+ $prop['readonly'] = true;
+ }
+ if (isset($tag['attribute']['value']) AND !TCPDF_STATIC::empty_string($tag['attribute']['value'])) {
+ $value = $tag['attribute']['value'];
+ }
+ if (isset($tag['attribute']['maxlength']) AND !TCPDF_STATIC::empty_string($tag['attribute']['maxlength'])) {
+ $opt['maxlen'] = intval($tag['attribute']['maxlength']);
+ }
+ $h = $this->FontSize * $this->cell_height_ratio;
+ if (isset($tag['attribute']['size']) AND !TCPDF_STATIC::empty_string($tag['attribute']['size'])) {
+ $w = intval($tag['attribute']['size']) * $this->GetStringWidth(chr(32)) * 2;
+ } else {
+ $w = $h;
+ }
+ if (isset($tag['attribute']['checked']) AND (($tag['attribute']['checked'] == 'checked') OR ($tag['attribute']['checked'] == 'true'))) {
+ $checked = true;
+ } else {
+ $checked = false;
+ }
+ if (isset($tag['align'])) {
+ switch ($tag['align']) {
+ case 'C': {
+ $opt['q'] = 1;
+ break;
+ }
+ case 'R': {
+ $opt['q'] = 2;
+ break;
+ }
+ case 'L':
+ default: {
+ break;
+ }
+ }
+ }
+ switch ($tag['attribute']['type']) {
+ case 'text': {
+ if (isset($value)) {
+ $opt['v'] = $value;
+ }
+ $this->TextField($name, $w, $h, $prop, $opt, '', '', false);
+ break;
+ }
+ case 'password': {
+ if (isset($value)) {
+ $opt['v'] = $value;
+ }
+ $prop['password'] = 'true';
+ $this->TextField($name, $w, $h, $prop, $opt, '', '', false);
+ break;
+ }
+ case 'checkbox': {
+ if (!isset($value)) {
+ break;
+ }
+ $this->CheckBox($name, $w, $checked, $prop, $opt, $value, '', '', false);
+ break;
+ }
+ case 'radio': {
+ if (!isset($value)) {
+ break;
+ }
+ $this->RadioButton($name, $w, $prop, $opt, $value, $checked, '', '', false);
+ break;
+ }
+ case 'submit': {
+ if (!isset($value)) {
+ $value = 'submit';
+ }
+ $w = $this->GetStringWidth($value) * 1.5;
+ $h *= 1.6;
+ $prop = array('lineWidth'=>1, 'borderStyle'=>'beveled', 'fillColor'=>array(196, 196, 196), 'strokeColor'=>array(255, 255, 255));
+ $action = array();
+ $action['S'] = 'SubmitForm';
+ $action['F'] = $this->form_action;
+ if ($this->form_enctype != 'FDF') {
+ $action['Flags'] = array('ExportFormat');
+ }
+ if ($this->form_mode == 'get') {
+ $action['Flags'] = array('GetMethod');
+ }
+ $this->Button($name, $w, $h, $value, $action, $prop, $opt, '', '', false);
+ break;
+ }
+ case 'reset': {
+ if (!isset($value)) {
+ $value = 'reset';
+ }
+ $w = $this->GetStringWidth($value) * 1.5;
+ $h *= 1.6;
+ $prop = array('lineWidth'=>1, 'borderStyle'=>'beveled', 'fillColor'=>array(196, 196, 196), 'strokeColor'=>array(255, 255, 255));
+ $this->Button($name, $w, $h, $value, array('S'=>'ResetForm'), $prop, $opt, '', '', false);
+ break;
+ }
+ case 'file': {
+ $prop['fileSelect'] = 'true';
+ $this->TextField($name, $w, $h, $prop, $opt, '', '', false);
+ if (!isset($value)) {
+ $value = '*';
+ }
+ $w = $this->GetStringWidth($value) * 2;
+ $h *= 1.2;
+ $prop = array('lineWidth'=>1, 'borderStyle'=>'beveled', 'fillColor'=>array(196, 196, 196), 'strokeColor'=>array(255, 255, 255));
+ $jsaction = 'var f=this.getField(\''.$name.'\'); f.browseForFileToSubmit();';
+ $this->Button('FB_'.$name, $w, $h, $value, $jsaction, $prop, $opt, '', '', false);
+ break;
+ }
+ case 'hidden': {
+ if (isset($value)) {
+ $opt['v'] = $value;
+ }
+ $opt['f'] = array('invisible', 'hidden');
+ $this->TextField($name, 0, 0, $prop, $opt, '', '', false);
+ break;
+ }
+ case 'image': {
+ // THIS TYPE MUST BE FIXED
+ if (isset($tag['attribute']['src']) AND !TCPDF_STATIC::empty_string($tag['attribute']['src'])) {
+ $img = $tag['attribute']['src'];
+ } else {
+ break;
+ }
+ $value = 'img';
+ //$opt['mk'] = array('i'=>$img, 'tp'=>1, 'if'=>array('sw'=>'A', 's'=>'A', 'fb'=>false));
+ if (isset($tag['attribute']['onclick']) AND !empty($tag['attribute']['onclick'])) {
+ $jsaction = $tag['attribute']['onclick'];
+ } else {
+ $jsaction = '';
+ }
+ $this->Button($name, $w, $h, $value, $jsaction, $prop, $opt, '', '', false);
+ break;
+ }
+ case 'button': {
+ if (!isset($value)) {
+ $value = ' ';
+ }
+ $w = $this->GetStringWidth($value) * 1.5;
+ $h *= 1.6;
+ $prop = array('lineWidth'=>1, 'borderStyle'=>'beveled', 'fillColor'=>array(196, 196, 196), 'strokeColor'=>array(255, 255, 255));
+ if (isset($tag['attribute']['onclick']) AND !empty($tag['attribute']['onclick'])) {
+ $jsaction = $tag['attribute']['onclick'];
+ } else {
+ $jsaction = '';
+ }
+ $this->Button($name, $w, $h, $value, $jsaction, $prop, $opt, '', '', false);
+ break;
+ }
+ }
+ break;
+ }
+ case 'textarea': {
+ $prop = array();
+ $opt = array();
+ if (isset($tag['attribute']['readonly']) AND !TCPDF_STATIC::empty_string($tag['attribute']['readonly'])) {
+ $prop['readonly'] = true;
+ }
+ if (isset($tag['attribute']['name']) AND !TCPDF_STATIC::empty_string($tag['attribute']['name'])) {
+ $name = $tag['attribute']['name'];
+ } else {
+ break;
+ }
+ if (isset($tag['attribute']['value']) AND !TCPDF_STATIC::empty_string($tag['attribute']['value'])) {
+ $opt['v'] = $tag['attribute']['value'];
+ }
+ if (isset($tag['attribute']['cols']) AND !TCPDF_STATIC::empty_string($tag['attribute']['cols'])) {
+ $w = intval($tag['attribute']['cols']) * $this->GetStringWidth(chr(32)) * 2;
+ } else {
+ $w = 40;
+ }
+ if (isset($tag['attribute']['rows']) AND !TCPDF_STATIC::empty_string($tag['attribute']['rows'])) {
+ $h = intval($tag['attribute']['rows']) * $this->FontSize * $this->cell_height_ratio;
+ } else {
+ $h = 10;
+ }
+ $prop['multiline'] = 'true';
+ $this->TextField($name, $w, $h, $prop, $opt, '', '', false);
+ break;
+ }
+ case 'select': {
+ $h = $this->FontSize * $this->cell_height_ratio;
+ if (isset($tag['attribute']['size']) AND !TCPDF_STATIC::empty_string($tag['attribute']['size'])) {
+ $h *= ($tag['attribute']['size'] + 1);
+ }
+ $prop = array();
+ $opt = array();
+ if (isset($tag['attribute']['name']) AND !TCPDF_STATIC::empty_string($tag['attribute']['name'])) {
+ $name = $tag['attribute']['name'];
+ } else {
+ break;
+ }
+ $w = 0;
+ if (isset($tag['attribute']['opt']) AND !TCPDF_STATIC::empty_string($tag['attribute']['opt'])) {
+ $options = explode('#!NwL!#', $tag['attribute']['opt']);
+ $values = array();
+ foreach ($options as $val) {
+ if (strpos($val, '#!TaB!#') !== false) {
+ $opts = explode('#!TaB!#', $val);
+ $values[] = $opts;
+ $w = max($w, $this->GetStringWidth($opts[1]));
+ } else {
+ $values[] = $val;
+ $w = max($w, $this->GetStringWidth($val));
+ }
+ }
+ } else {
+ break;
+ }
+ $w *= 2;
+ if (isset($tag['attribute']['multiple']) AND ($tag['attribute']['multiple']='multiple')) {
+ $prop['multipleSelection'] = 'true';
+ $this->ListBox($name, $w, $h, $values, $prop, $opt, '', '', false);
+ } else {
+ $this->ComboBox($name, $w, $h, $values, $prop, $opt, '', '', false);
+ }
+ break;
+ }
+ case 'tcpdf': {
+ if (defined('K_TCPDF_CALLS_IN_HTML') AND (K_TCPDF_CALLS_IN_HTML === true)) {
+ // Special tag used to call TCPDF methods
+ if (isset($tag['attribute']['method'])) {
+ $tcpdf_method = $tag['attribute']['method'];
+ if (method_exists($this, $tcpdf_method)) {
+ if (isset($tag['attribute']['params']) AND (!empty($tag['attribute']['params']))) {
+ $params = unserialize(urldecode($tag['attribute']['params']));
+ call_user_func_array(array($this, $tcpdf_method), $params);
+ } else {
+ $this->$tcpdf_method();
+ }
+ $this->newline = true;
+ }
+ }
+ }
+ break;
+ }
+ default: {
+ break;
+ }
+ }
+ // define tags that support borders and background colors
+ $bordertags = array('blockquote','br','dd','dl','div','dt','h1','h2','h3','h4','h5','h6','hr','li','ol','p','pre','ul','tcpdf','table');
+ if (in_array($tag['value'], $bordertags)) {
+ // set border
+ $dom[$key]['borderposition'] = $this->getBorderStartPosition();
+ }
+ if ($dom[$key]['self'] AND isset($dom[$key]['attribute']['pagebreakafter'])) {
+ $pba = $dom[$key]['attribute']['pagebreakafter'];
+ // check for pagebreak
+ if (($pba == 'true') OR ($pba == 'left') OR ($pba == 'right')) {
+ // add a page (or trig AcceptPageBreak() for multicolumn mode)
+ $this->checkPageBreak($this->PageBreakTrigger + 1);
+ }
+ if ((($pba == 'left') AND (((!$this->rtl) AND (($this->page % 2) == 0)) OR (($this->rtl) AND (($this->page % 2) != 0))))
+ OR (($pba == 'right') AND (((!$this->rtl) AND (($this->page % 2) != 0)) OR (($this->rtl) AND (($this->page % 2) == 0))))) {
+ // add a page (or trig AcceptPageBreak() for multicolumn mode)
+ $this->checkPageBreak($this->PageBreakTrigger + 1);
+ }
+ }
+ return $dom;
+ }
+
+ /**
+ * Process closing tags.
+ * @param $dom (array) html dom array
+ * @param $key (int) current element id
+ * @param $cell (boolean) if true add the default left (or right if RTL) padding to each new line (default false).
+ * @param $maxbottomliney (int) maximum y value of current line
+ * @return $dom array
+ * @protected
+ */
+ protected function closeHTMLTagHandler($dom, $key, $cell, $maxbottomliney=0) {
+ $tag = $dom[$key];
+ $parent = $dom[($dom[$key]['parent'])];
+ $lasttag = ((!isset($dom[($key + 1)])) OR ((!isset($dom[($key + 2)])) AND ($dom[($key + 1)]['value'] == 'marker')));
+ $in_table_head = false;
+ // maximum x position (used to draw borders)
+ if ($this->rtl) {
+ $xmax = $this->w;
+ } else {
+ $xmax = 0;
+ }
+ if ($tag['block']) {
+ $hbz = 0; // distance from y to line bottom
+ $hb = 0; // vertical space between block tags
+ // calculate vertical space for block tags
+ if (isset($this->tagvspaces[$tag['value']][1]['h']) AND ($this->tagvspaces[$tag['value']][1]['h'] >= 0)) {
+ $pre_h = $this->tagvspaces[$tag['value']][1]['h'];
+ } elseif (isset($parent['fontsize'])) {
+ $pre_h = (($parent['fontsize'] / $this->k) * $this->cell_height_ratio);
+ } else {
+ $pre_h = $this->FontSize * $this->cell_height_ratio;
+ }
+ if (isset($this->tagvspaces[$tag['value']][1]['n'])) {
+ $n = $this->tagvspaces[$tag['value']][1]['n'];
+ } elseif (preg_match('/[h][0-9]/', $tag['value']) > 0) {
+ $n = 0.6;
+ } else {
+ $n = 1;
+ }
+ if ((!isset($this->tagvspaces[$tag['value']])) AND ($tag['value'] == 'div')) {
+ $hb = 0;
+ } else {
+ $hb = ($n * $pre_h);
+ }
+ if ($maxbottomliney > $this->PageBreakTrigger) {
+ $hbz = ($this->FontSize * $this->cell_height_ratio);
+ } elseif ($this->y < $maxbottomliney) {
+ $hbz = ($maxbottomliney - $this->y);
+ }
+ }
+ // Closing tag
+ switch($tag['value']) {
+ case 'tr': {
+ $table_el = $dom[($dom[$key]['parent'])]['parent'];
+ if (!isset($parent['endy'])) {
+ $dom[($dom[$key]['parent'])]['endy'] = $this->y;
+ $parent['endy'] = $this->y;
+ }
+ if (!isset($parent['endpage'])) {
+ $dom[($dom[$key]['parent'])]['endpage'] = $this->page;
+ $parent['endpage'] = $this->page;
+ }
+ if (!isset($parent['endcolumn'])) {
+ $dom[($dom[$key]['parent'])]['endcolumn'] = $this->current_column;
+ $parent['endcolumn'] = $this->current_column;
+ }
+ // update row-spanned cells
+ if (isset($dom[$table_el]['rowspans'])) {
+ foreach ($dom[$table_el]['rowspans'] as $k => $trwsp) {
+ $dom[$table_el]['rowspans'][$k]['rowspan'] -= 1;
+ if ($dom[$table_el]['rowspans'][$k]['rowspan'] == 0) {
+ if (($dom[$table_el]['rowspans'][$k]['endpage'] == $parent['endpage']) AND ($dom[$table_el]['rowspans'][$k]['endcolumn'] == $parent['endcolumn'])) {
+ $dom[($dom[$key]['parent'])]['endy'] = max($dom[$table_el]['rowspans'][$k]['endy'], $parent['endy']);
+ } elseif (($dom[$table_el]['rowspans'][$k]['endpage'] > $parent['endpage']) OR ($dom[$table_el]['rowspans'][$k]['endcolumn'] > $parent['endcolumn'])) {
+ $dom[($dom[$key]['parent'])]['endy'] = $dom[$table_el]['rowspans'][$k]['endy'];
+ $dom[($dom[$key]['parent'])]['endpage'] = $dom[$table_el]['rowspans'][$k]['endpage'];
+ $dom[($dom[$key]['parent'])]['endcolumn'] = $dom[$table_el]['rowspans'][$k]['endcolumn'];
+ }
+ }
+ }
+ // report new endy and endpage to the rowspanned cells
+ foreach ($dom[$table_el]['rowspans'] as $k => $trwsp) {
+ if ($dom[$table_el]['rowspans'][$k]['rowspan'] == 0) {
+ $dom[$table_el]['rowspans'][$k]['endpage'] = max($dom[$table_el]['rowspans'][$k]['endpage'], $dom[($dom[$key]['parent'])]['endpage']);
+ $dom[($dom[$key]['parent'])]['endpage'] = $dom[$table_el]['rowspans'][$k]['endpage'];
+ $dom[$table_el]['rowspans'][$k]['endcolumn'] = max($dom[$table_el]['rowspans'][$k]['endcolumn'], $dom[($dom[$key]['parent'])]['endcolumn']);
+ $dom[($dom[$key]['parent'])]['endcolumn'] = $dom[$table_el]['rowspans'][$k]['endcolumn'];
+ $dom[$table_el]['rowspans'][$k]['endy'] = max($dom[$table_el]['rowspans'][$k]['endy'], $dom[($dom[$key]['parent'])]['endy']);
+ $dom[($dom[$key]['parent'])]['endy'] = $dom[$table_el]['rowspans'][$k]['endy'];
+ }
+ }
+ // update remaining rowspanned cells
+ foreach ($dom[$table_el]['rowspans'] as $k => $trwsp) {
+ if ($dom[$table_el]['rowspans'][$k]['rowspan'] == 0) {
+ $dom[$table_el]['rowspans'][$k]['endpage'] = $dom[($dom[$key]['parent'])]['endpage'];
+ $dom[$table_el]['rowspans'][$k]['endcolumn'] = $dom[($dom[$key]['parent'])]['endcolumn'];
+ $dom[$table_el]['rowspans'][$k]['endy'] = $dom[($dom[$key]['parent'])]['endy'];
+ }
+ }
+ }
+ $this->setPage($dom[($dom[$key]['parent'])]['endpage']);
+ if ($this->num_columns > 1) {
+ $this->selectColumn($dom[($dom[$key]['parent'])]['endcolumn']);
+ }
+ $this->y = $dom[($dom[$key]['parent'])]['endy'];
+ if (isset($dom[$table_el]['attribute']['cellspacing'])) {
+ $this->y += $this->getHTMLUnitToUnits($dom[$table_el]['attribute']['cellspacing'], 1, 'px');
+ } elseif (isset($dom[$table_el]['border-spacing'])) {
+ $this->y += $dom[$table_el]['border-spacing']['V'];
+ }
+ $this->Ln(0, $cell);
+ if ($this->current_column == $parent['startcolumn']) {
+ $this->x = $parent['startx'];
+ }
+ // account for booklet mode
+ if ($this->page > $parent['startpage']) {
+ if (($this->rtl) AND ($this->pagedim[$this->page]['orm'] != $this->pagedim[$parent['startpage']]['orm'])) {
+ $this->x -= ($this->pagedim[$this->page]['orm'] - $this->pagedim[$parent['startpage']]['orm']);
+ } elseif ((!$this->rtl) AND ($this->pagedim[$this->page]['olm'] != $this->pagedim[$parent['startpage']]['olm'])) {
+ $this->x += ($this->pagedim[$this->page]['olm'] - $this->pagedim[$parent['startpage']]['olm']);
+ }
+ }
+ break;
+ }
+ case 'tablehead':
+ // closing tag used for the thead part
+ $in_table_head = true;
+ $this->inthead = false;
+ case 'table': {
+ $table_el = $parent;
+ // set default border
+ if (isset($table_el['attribute']['border']) AND ($table_el['attribute']['border'] > 0)) {
+ // set default border
+ $border = array('LTRB' => array('width' => $this->getCSSBorderWidth($table_el['attribute']['border']), 'cap'=>'square', 'join'=>'miter', 'dash'=> 0, 'color'=>array(0,0,0)));
+ } else {
+ $border = 0;
+ }
+ $default_border = $border;
+ // fix bottom line alignment of last line before page break
+ foreach ($dom[($dom[$key]['parent'])]['trids'] as $j => $trkey) {
+ // update row-spanned cells
+ if (isset($dom[($dom[$key]['parent'])]['rowspans'])) {
+ foreach ($dom[($dom[$key]['parent'])]['rowspans'] as $k => $trwsp) {
+ if (isset($prevtrkey) AND ($trwsp['trid'] == $prevtrkey) AND ($trwsp['mrowspan'] > 0)) {
+ $dom[($dom[$key]['parent'])]['rowspans'][$k]['trid'] = $trkey;
+ }
+ if ($dom[($dom[$key]['parent'])]['rowspans'][$k]['trid'] == $trkey) {
+ $dom[($dom[$key]['parent'])]['rowspans'][$k]['mrowspan'] -= 1;
+ }
+ }
+ }
+ if (isset($prevtrkey) AND ($dom[$trkey]['startpage'] > $dom[$prevtrkey]['endpage'])) {
+ $pgendy = $this->pagedim[$dom[$prevtrkey]['endpage']]['hk'] - $this->pagedim[$dom[$prevtrkey]['endpage']]['bm'];
+ $dom[$prevtrkey]['endy'] = $pgendy;
+ // update row-spanned cells
+ if (isset($dom[($dom[$key]['parent'])]['rowspans'])) {
+ foreach ($dom[($dom[$key]['parent'])]['rowspans'] as $k => $trwsp) {
+ if (($trwsp['trid'] == $trkey) AND ($trwsp['mrowspan'] > 1) AND ($trwsp['endpage'] == $dom[$prevtrkey]['endpage'])) {
+ $dom[($dom[$key]['parent'])]['rowspans'][$k]['endy'] = $pgendy;
+ $dom[($dom[$key]['parent'])]['rowspans'][$k]['mrowspan'] = -1;
+ }
+ }
+ }
+ }
+ $prevtrkey = $trkey;
+ $table_el = $dom[($dom[$key]['parent'])];
+ }
+ // for each row
+ if (count($table_el['trids']) > 0) {
+ unset($xmax);
+ }
+ foreach ($table_el['trids'] as $j => $trkey) {
+ $parent = $dom[$trkey];
+ if (!isset($xmax)) {
+ $xmax = $parent['cellpos'][(count($parent['cellpos']) - 1)]['endx'];
+ }
+ // for each cell on the row
+ foreach ($parent['cellpos'] as $k => $cellpos) {
+ if (isset($cellpos['rowspanid']) AND ($cellpos['rowspanid'] >= 0)) {
+ $cellpos['startx'] = $table_el['rowspans'][($cellpos['rowspanid'])]['startx'];
+ $cellpos['endx'] = $table_el['rowspans'][($cellpos['rowspanid'])]['endx'];
+ $endy = $table_el['rowspans'][($cellpos['rowspanid'])]['endy'];
+ $startpage = $table_el['rowspans'][($cellpos['rowspanid'])]['startpage'];
+ $endpage = $table_el['rowspans'][($cellpos['rowspanid'])]['endpage'];
+ $startcolumn = $table_el['rowspans'][($cellpos['rowspanid'])]['startcolumn'];
+ $endcolumn = $table_el['rowspans'][($cellpos['rowspanid'])]['endcolumn'];
+ } else {
+ $endy = $parent['endy'];
+ $startpage = $parent['startpage'];
+ $endpage = $parent['endpage'];
+ $startcolumn = $parent['startcolumn'];
+ $endcolumn = $parent['endcolumn'];
+ }
+ if ($this->num_columns == 0) {
+ $this->num_columns = 1;
+ }
+ if (isset($cellpos['border'])) {
+ $border = $cellpos['border'];
+ }
+ if (isset($cellpos['bgcolor']) AND ($cellpos['bgcolor']) !== false) {
+ $this->SetFillColorArray($cellpos['bgcolor']);
+ $fill = true;
+ } else {
+ $fill = false;
+ }
+ $x = $cellpos['startx'];
+ $y = $parent['starty'];
+ $starty = $y;
+ $w = abs($cellpos['endx'] - $cellpos['startx']);
+ // get border modes
+ $border_start = TCPDF_STATIC::getBorderMode($border, $position='start', $this->opencell);
+ $border_end = TCPDF_STATIC::getBorderMode($border, $position='end', $this->opencell);
+ $border_middle = TCPDF_STATIC::getBorderMode($border, $position='middle', $this->opencell);
+ // design borders around HTML cells.
+ for ($page = $startpage; $page <= $endpage; ++$page) { // for each page
+ $ccode = '';
+ $this->setPage($page);
+ if ($this->num_columns < 2) {
+ // single-column mode
+ $this->x = $x;
+ $this->y = $this->tMargin;
+ }
+ // account for margin changes
+ if ($page > $startpage) {
+ if (($this->rtl) AND ($this->pagedim[$page]['orm'] != $this->pagedim[$startpage]['orm'])) {
+ $this->x -= ($this->pagedim[$page]['orm'] - $this->pagedim[$startpage]['orm']);
+ } elseif ((!$this->rtl) AND ($this->pagedim[$page]['olm'] != $this->pagedim[$startpage]['olm'])) {
+ $this->x += ($this->pagedim[$page]['olm'] - $this->pagedim[$startpage]['olm']);
+ }
+ }
+ if ($startpage == $endpage) { // single page
+ $deltacol = 0;
+ $deltath = 0;
+ for ($column = $startcolumn; $column <= $endcolumn; ++$column) { // for each column
+ $this->selectColumn($column);
+ if ($startcolumn == $endcolumn) { // single column
+ $cborder = $border;
+ $h = $endy - $parent['starty'];
+ $this->y = $y;
+ $this->x = $x;
+ } elseif ($column == $startcolumn) { // first column
+ $cborder = $border_start;
+ $this->y = $starty;
+ $this->x = $x;
+ $h = $this->h - $this->y - $this->bMargin;
+ if ($this->rtl) {
+ $deltacol = $this->x + $this->rMargin - $this->w;
+ } else {
+ $deltacol = $this->x - $this->lMargin;
+ }
+ } elseif ($column == $endcolumn) { // end column
+ $cborder = $border_end;
+ if (isset($this->columns[$column]['th']['\''.$page.'\''])) {
+ $this->y = $this->columns[$column]['th']['\''.$page.'\''];
+ }
+ $this->x += $deltacol;
+ $h = $endy - $this->y;
+ } else { // middle column
+ $cborder = $border_middle;
+ if (isset($this->columns[$column]['th']['\''.$page.'\''])) {
+ $this->y = $this->columns[$column]['th']['\''.$page.'\''];
+ }
+ $this->x += $deltacol;
+ $h = $this->h - $this->y - $this->bMargin;
+ }
+ $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
+ } // end for each column
+ } elseif ($page == $startpage) { // first page
+ $deltacol = 0;
+ $deltath = 0;
+ for ($column = $startcolumn; $column < $this->num_columns; ++$column) { // for each column
+ $this->selectColumn($column);
+ if ($column == $startcolumn) { // first column
+ $cborder = $border_start;
+ $this->y = $starty;
+ $this->x = $x;
+ $h = $this->h - $this->y - $this->bMargin;
+ if ($this->rtl) {
+ $deltacol = $this->x + $this->rMargin - $this->w;
+ } else {
+ $deltacol = $this->x - $this->lMargin;
+ }
+ } else { // middle column
+ $cborder = $border_middle;
+ if (isset($this->columns[$column]['th']['\''.$page.'\''])) {
+ $this->y = $this->columns[$column]['th']['\''.$page.'\''];
+ }
+ $this->x += $deltacol;
+ $h = $this->h - $this->y - $this->bMargin;
+ }
+ $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
+ } // end for each column
+ } elseif ($page == $endpage) { // last page
+ $deltacol = 0;
+ $deltath = 0;
+ for ($column = 0; $column <= $endcolumn; ++$column) { // for each column
+ $this->selectColumn($column);
+ if ($column == $endcolumn) { // end column
+ $cborder = $border_end;
+ if (isset($this->columns[$column]['th']['\''.$page.'\''])) {
+ $this->y = $this->columns[$column]['th']['\''.$page.'\''];
+ }
+ $this->x += $deltacol;
+ $h = $endy - $this->y;
+ } else { // middle column
+ $cborder = $border_middle;
+ if (isset($this->columns[$column]['th']['\''.$page.'\''])) {
+ $this->y = $this->columns[$column]['th']['\''.$page.'\''];
+ }
+ $this->x += $deltacol;
+ $h = $this->h - $this->y - $this->bMargin;
+ }
+ $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
+ } // end for each column
+ } else { // middle page
+ $deltacol = 0;
+ $deltath = 0;
+ for ($column = 0; $column < $this->num_columns; ++$column) { // for each column
+ $this->selectColumn($column);
+ $cborder = $border_middle;
+ if (isset($this->columns[$column]['th']['\''.$page.'\''])) {
+ $this->y = $this->columns[$column]['th']['\''.$page.'\''];
+ }
+ $this->x += $deltacol;
+ $h = $this->h - $this->y - $this->bMargin;
+ $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
+ } // end for each column
+ }
+ if ($cborder OR $fill) {
+ $offsetlen = strlen($ccode);
+ // draw border and fill
+ if ($this->inxobj) {
+ // we are inside an XObject template
+ if (end($this->xobjects[$this->xobjid]['transfmrk']) !== false) {
+ $pagemarkkey = key($this->xobjects[$this->xobjid]['transfmrk']);
+ $pagemark = $this->xobjects[$this->xobjid]['transfmrk'][$pagemarkkey];
+ $this->xobjects[$this->xobjid]['transfmrk'][$pagemarkkey] += $offsetlen;
+ } else {
+ $pagemark = $this->xobjects[$this->xobjid]['intmrk'];
+ $this->xobjects[$this->xobjid]['intmrk'] += $offsetlen;
+ }
+ $pagebuff = $this->xobjects[$this->xobjid]['outdata'];
+ $pstart = substr($pagebuff, 0, $pagemark);
+ $pend = substr($pagebuff, $pagemark);
+ $this->xobjects[$this->xobjid]['outdata'] = $pstart.$ccode.$pend;
+ } else {
+ // draw border and fill
+ if (end($this->transfmrk[$this->page]) !== false) {
+ $pagemarkkey = key($this->transfmrk[$this->page]);
+ $pagemark = $this->transfmrk[$this->page][$pagemarkkey];
+ } elseif ($this->InFooter) {
+ $pagemark = $this->footerpos[$this->page];
+ } else {
+ $pagemark = $this->intmrk[$this->page];
+ }
+ $pagebuff = $this->getPageBuffer($this->page);
+ $pstart = substr($pagebuff, 0, $pagemark);
+ $pend = substr($pagebuff, $pagemark);
+ $this->setPageBuffer($this->page, $pstart.$ccode.$pend);
+ }
+ }
+ } // end for each page
+ // restore default border
+ $border = $default_border;
+ } // end for each cell on the row
+ if (isset($table_el['attribute']['cellspacing'])) {
+ $this->y += $this->getHTMLUnitToUnits($table_el['attribute']['cellspacing'], 1, 'px');
+ } elseif (isset($table_el['border-spacing'])) {
+ $this->y += $table_el['border-spacing']['V'];
+ }
+ $this->Ln(0, $cell);
+ $this->x = $parent['startx'];
+ if ($endpage > $startpage) {
+ if (($this->rtl) AND ($this->pagedim[$endpage]['orm'] != $this->pagedim[$startpage]['orm'])) {
+ $this->x += ($this->pagedim[$endpage]['orm'] - $this->pagedim[$startpage]['orm']);
+ } elseif ((!$this->rtl) AND ($this->pagedim[$endpage]['olm'] != $this->pagedim[$startpage]['olm'])) {
+ $this->x += ($this->pagedim[$endpage]['olm'] - $this->pagedim[$startpage]['olm']);
+ }
+ }
+ }
+ if (!$in_table_head) { // we are not inside a thead section
+ $this->cell_padding = $table_el['old_cell_padding'];
+ // reset row height
+ $this->resetLastH();
+ if (($this->page == ($this->numpages - 1)) AND ($this->pageopen[$this->numpages])) {
+ $plendiff = ($this->pagelen[$this->numpages] - $this->emptypagemrk[$this->numpages]);
+ if (($plendiff > 0) AND ($plendiff < 60)) {
+ $pagediff = substr($this->getPageBuffer($this->numpages), $this->emptypagemrk[$this->numpages], $plendiff);
+ if (substr($pagediff, 0, 5) == 'BT /F') {
+ // the difference is only a font setting
+ $plendiff = 0;
+ }
+ }
+ if ($plendiff == 0) {
+ // remove last blank page
+ $this->deletePage($this->numpages);
+ }
+ }
+ if (isset($this->theadMargins['top'])) {
+ // restore top margin
+ $this->tMargin = $this->theadMargins['top'];
+ }
+ if (!isset($table_el['attribute']['nested']) OR ($table_el['attribute']['nested'] != 'true')) {
+ // reset main table header
+ $this->thead = '';
+ $this->theadMargins = array();
+ $this->pagedim[$this->page]['tm'] = $this->tMargin;
+ }
+ }
+ $parent = $table_el;
+ break;
+ }
+ case 'a': {
+ $this->HREF = '';
+ break;
+ }
+ case 'sup': {
+ $this->SetXY($this->GetX(), $this->GetY() + ((0.7 * $parent['fontsize']) / $this->k));
+ break;
+ }
+ case 'sub': {
+ $this->SetXY($this->GetX(), $this->GetY() - ((0.3 * $parent['fontsize']) / $this->k));
+ break;
+ }
+ case 'div': {
+ $this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
+ break;
+ }
+ case 'blockquote': {
+ if ($this->rtl) {
+ $this->rMargin -= $this->listindent;
+ } else {
+ $this->lMargin -= $this->listindent;
+ }
+ --$this->listindentlevel;
+ $this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
+ break;
+ }
+ case 'p': {
+ $this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
+ break;
+ }
+ case 'pre': {
+ $this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
+ $this->premode = false;
+ break;
+ }
+ case 'dl': {
+ --$this->listnum;
+ if ($this->listnum <= 0) {
+ $this->listnum = 0;
+ $this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
+ } else {
+ $this->addHTMLVertSpace(0, 0, $cell, false, $lasttag);
+ }
+ $this->resetLastH();
+ break;
+ }
+ case 'dt': {
+ $this->lispacer = '';
+ $this->addHTMLVertSpace(0, 0, $cell, false, $lasttag);
+ break;
+ }
+ case 'dd': {
+ $this->lispacer = '';
+ if ($this->rtl) {
+ $this->rMargin -= $this->listindent;
+ } else {
+ $this->lMargin -= $this->listindent;
+ }
+ --$this->listindentlevel;
+ $this->addHTMLVertSpace(0, 0, $cell, false, $lasttag);
+ break;
+ }
+ case 'ul':
+ case 'ol': {
+ --$this->listnum;
+ $this->lispacer = '';
+ if ($this->rtl) {
+ $this->rMargin -= $this->listindent;
+ } else {
+ $this->lMargin -= $this->listindent;
+ }
+ --$this->listindentlevel;
+ if ($this->listnum <= 0) {
+ $this->listnum = 0;
+ $this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
+ } else {
+ $this->addHTMLVertSpace(0, 0, $cell, false, $lasttag);
+ }
+ $this->resetLastH();
+ break;
+ }
+ case 'li': {
+ $this->lispacer = '';
+ $this->addHTMLVertSpace(0, 0, $cell, false, $lasttag);
+ break;
+ }
+ case 'h1':
+ case 'h2':
+ case 'h3':
+ case 'h4':
+ case 'h5':
+ case 'h6': {
+ $this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
+ break;
+ }
+ // Form fields (since 4.8.000 - 2009-09-07)
+ case 'form': {
+ $this->form_action = '';
+ $this->form_enctype = 'application/x-www-form-urlencoded';
+ break;
+ }
+ default : {
+ break;
+ }
+ }
+ // draw border and background (if any)
+ $this->drawHTMLTagBorder($parent, $xmax);
+ if (isset($dom[($dom[$key]['parent'])]['attribute']['pagebreakafter'])) {
+ $pba = $dom[($dom[$key]['parent'])]['attribute']['pagebreakafter'];
+ // check for pagebreak
+ if (($pba == 'true') OR ($pba == 'left') OR ($pba == 'right')) {
+ // add a page (or trig AcceptPageBreak() for multicolumn mode)
+ $this->checkPageBreak($this->PageBreakTrigger + 1);
+ }
+ if ((($pba == 'left') AND (((!$this->rtl) AND (($this->page % 2) == 0)) OR (($this->rtl) AND (($this->page % 2) != 0))))
+ OR (($pba == 'right') AND (((!$this->rtl) AND (($this->page % 2) != 0)) OR (($this->rtl) AND (($this->page % 2) == 0))))) {
+ // add a page (or trig AcceptPageBreak() for multicolumn mode)
+ $this->checkPageBreak($this->PageBreakTrigger + 1);
+ }
+ }
+ $this->tmprtl = false;
+ return $dom;
+ }
+
+ /**
+ * Add vertical spaces if needed.
+ * @param $hbz (string) Distance between current y and line bottom.
+ * @param $hb (string) The height of the break.
+ * @param $cell (boolean) if true add the default left (or right if RTL) padding to each new line (default false).
+ * @param $firsttag (boolean) set to true when the tag is the first.
+ * @param $lasttag (boolean) set to true when the tag is the last.
+ * @protected
+ */
+ protected function addHTMLVertSpace($hbz=0, $hb=0, $cell=false, $firsttag=false, $lasttag=false) {
+ if ($firsttag) {
+ $this->Ln(0, $cell);
+ $this->htmlvspace = 0;
+ return;
+ }
+ if ($lasttag) {
+ $this->Ln($hbz, $cell);
+ $this->htmlvspace = 0;
+ return;
+ }
+ if ($hb < $this->htmlvspace) {
+ $hd = 0;
+ } else {
+ $hd = $hb - $this->htmlvspace;
+ $this->htmlvspace = $hb;
+ }
+ $this->Ln(($hbz + $hd), $cell);
+ }
+
+ /**
+ * Return the starting coordinates to draw an html border
+ * @return array containing top-left border coordinates
+ * @protected
+ * @since 5.7.000 (2010-08-03)
+ */
+ protected function getBorderStartPosition() {
+ if ($this->rtl) {
+ $xmax = $this->lMargin;
+ } else {
+ $xmax = $this->w - $this->rMargin;
+ }
+ return array('page' => $this->page, 'column' => $this->current_column, 'x' => $this->x, 'y' => $this->y, 'xmax' => $xmax);
+ }
+
+ /**
+ * Draw an HTML block border and fill
+ * @param $tag (array) array of tag properties.
+ * @param $xmax (int) end X coordinate for border.
+ * @protected
+ * @since 5.7.000 (2010-08-03)
+ */
+ protected function drawHTMLTagBorder($tag, $xmax) {
+ if (!isset($tag['borderposition'])) {
+ // nothing to draw
+ return;
+ }
+ $prev_x = $this->x;
+ $prev_y = $this->y;
+ $prev_lasth = $this->lasth;
+ $border = 0;
+ $fill = false;
+ $this->lasth = 0;
+ if (isset($tag['border']) AND !empty($tag['border'])) {
+ // get border style
+ $border = $tag['border'];
+ if (!TCPDF_STATIC::empty_string($this->thead) AND (!$this->inthead)) {
+ // border for table header
+ $border = TCPDF_STATIC::getBorderMode($border, $position='middle', $this->opencell);
+ }
+ }
+ if (isset($tag['bgcolor']) AND ($tag['bgcolor'] !== false)) {
+ // get background color
+ $old_bgcolor = $this->bgcolor;
+ $this->SetFillColorArray($tag['bgcolor']);
+ $fill = true;
+ }
+ if (!$border AND !$fill) {
+ // nothing to draw
+ return;
+ }
+ if (isset($tag['attribute']['cellspacing'])) {
+ $clsp = $this->getHTMLUnitToUnits($tag['attribute']['cellspacing'], 1, 'px');
+ $cellspacing = array('H' => $clsp, 'V' => $clsp);
+ } elseif (isset($tag['border-spacing'])) {
+ $cellspacing = $tag['border-spacing'];
+ } else {
+ $cellspacing = array('H' => 0, 'V' => 0);
+ }
+ if (($tag['value'] != 'table') AND (is_array($border)) AND (!empty($border))) {
+ // draw the border externally respect the sqare edge.
+ $border['mode'] = 'ext';
+ }
+ if ($this->rtl) {
+ if ($xmax >= $tag['borderposition']['x']) {
+ $xmax = $tag['borderposition']['xmax'];
+ }
+ $w = ($tag['borderposition']['x'] - $xmax);
+ } else {
+ if ($xmax <= $tag['borderposition']['x']) {
+ $xmax = $tag['borderposition']['xmax'];
+ }
+ $w = ($xmax - $tag['borderposition']['x']);
+ }
+ if ($w <= 0) {
+ return;
+ }
+ $w += $cellspacing['H'];
+ $startpage = $tag['borderposition']['page'];
+ $startcolumn = $tag['borderposition']['column'];
+ $x = $tag['borderposition']['x'];
+ $y = $tag['borderposition']['y'];
+ $endpage = $this->page;
+ $starty = $tag['borderposition']['y'] - $cellspacing['V'];
+ $currentY = $this->y;
+ $this->x = $x;
+ // get latest column
+ $endcolumn = $this->current_column;
+ if ($this->num_columns == 0) {
+ $this->num_columns = 1;
+ }
+ // get border modes
+ $border_start = TCPDF_STATIC::getBorderMode($border, $position='start', $this->opencell);
+ $border_end = TCPDF_STATIC::getBorderMode($border, $position='end', $this->opencell);
+ $border_middle = TCPDF_STATIC::getBorderMode($border, $position='middle', $this->opencell);
+ // temporary disable page regions
+ $temp_page_regions = $this->page_regions;
+ $this->page_regions = array();
+ // design borders around HTML cells.
+ for ($page = $startpage; $page <= $endpage; ++$page) { // for each page
+ $ccode = '';
+ $this->setPage($page);
+ if ($this->num_columns < 2) {
+ // single-column mode
+ $this->x = $x;
+ $this->y = $this->tMargin;
+ }
+ // account for margin changes
+ if ($page > $startpage) {
+ if (($this->rtl) AND ($this->pagedim[$page]['orm'] != $this->pagedim[$startpage]['orm'])) {
+ $this->x -= ($this->pagedim[$page]['orm'] - $this->pagedim[$startpage]['orm']);
+ } elseif ((!$this->rtl) AND ($this->pagedim[$page]['olm'] != $this->pagedim[$startpage]['olm'])) {
+ $this->x += ($this->pagedim[$page]['olm'] - $this->pagedim[$startpage]['olm']);
+ }
+ }
+ if ($startpage == $endpage) {
+ // single page
+ for ($column = $startcolumn; $column <= $endcolumn; ++$column) { // for each column
+ $this->selectColumn($column);
+ if ($startcolumn == $endcolumn) { // single column
+ $cborder = $border;
+ $h = ($currentY - $y) + $cellspacing['V'];
+ $this->y = $starty;
+ } elseif ($column == $startcolumn) { // first column
+ $cborder = $border_start;
+ $this->y = $starty;
+ $h = $this->h - $this->y - $this->bMargin;
+ } elseif ($column == $endcolumn) { // end column
+ $cborder = $border_end;
+ $h = $currentY - $this->y;
+ } else { // middle column
+ $cborder = $border_middle;
+ $h = $this->h - $this->y - $this->bMargin;
+ }
+ $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
+ } // end for each column
+ } elseif ($page == $startpage) { // first page
+ for ($column = $startcolumn; $column < $this->num_columns; ++$column) { // for each column
+ $this->selectColumn($column);
+ if ($column == $startcolumn) { // first column
+ $cborder = $border_start;
+ $this->y = $starty;
+ $h = $this->h - $this->y - $this->bMargin;
+ } else { // middle column
+ $cborder = $border_middle;
+ $h = $this->h - $this->y - $this->bMargin;
+ }
+ $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
+ } // end for each column
+ } elseif ($page == $endpage) { // last page
+ for ($column = 0; $column <= $endcolumn; ++$column) { // for each column
+ $this->selectColumn($column);
+ if ($column == $endcolumn) {
+ // end column
+ $cborder = $border_end;
+ $h = $currentY - $this->y;
+ } else {
+ // middle column
+ $cborder = $border_middle;
+ $h = $this->h - $this->y - $this->bMargin;
+ }
+ $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
+ } // end for each column
+ } else { // middle page
+ for ($column = 0; $column < $this->num_columns; ++$column) { // for each column
+ $this->selectColumn($column);
+ $cborder = $border_middle;
+ $h = $this->h - $this->y - $this->bMargin;
+ $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
+ } // end for each column
+ }
+ if ($cborder OR $fill) {
+ $offsetlen = strlen($ccode);
+ // draw border and fill
+ if ($this->inxobj) {
+ // we are inside an XObject template
+ if (end($this->xobjects[$this->xobjid]['transfmrk']) !== false) {
+ $pagemarkkey = key($this->xobjects[$this->xobjid]['transfmrk']);
+ $pagemark = $this->xobjects[$this->xobjid]['transfmrk'][$pagemarkkey];
+ $this->xobjects[$this->xobjid]['transfmrk'][$pagemarkkey] += $offsetlen;
+ } else {
+ $pagemark = $this->xobjects[$this->xobjid]['intmrk'];
+ $this->xobjects[$this->xobjid]['intmrk'] += $offsetlen;
+ }
+ $pagebuff = $this->xobjects[$this->xobjid]['outdata'];
+ $pstart = substr($pagebuff, 0, $pagemark);
+ $pend = substr($pagebuff, $pagemark);
+ $this->xobjects[$this->xobjid]['outdata'] = $pstart.$ccode.$pend;
+ } else {
+ if (end($this->transfmrk[$this->page]) !== false) {
+ $pagemarkkey = key($this->transfmrk[$this->page]);
+ $pagemark = $this->transfmrk[$this->page][$pagemarkkey];
+ } elseif ($this->InFooter) {
+ $pagemark = $this->footerpos[$this->page];
+ } else {
+ $pagemark = $this->intmrk[$this->page];
+ }
+ $pagebuff = $this->getPageBuffer($this->page);
+ $pstart = substr($pagebuff, 0, $pagemark);
+ $pend = substr($pagebuff, $pagemark);
+ $this->setPageBuffer($this->page, $pstart.$ccode.$pend);
+ $this->bordermrk[$this->page] += $offsetlen;
+ $this->cntmrk[$this->page] += $offsetlen;
+ }
+ }
+ } // end for each page
+ // restore page regions
+ $this->page_regions = $temp_page_regions;
+ if (isset($old_bgcolor)) {
+ // restore background color
+ $this->SetFillColorArray($old_bgcolor);
+ }
+ // restore pointer position
+ $this->x = $prev_x;
+ $this->y = $prev_y;
+ $this->lasth = $prev_lasth;
+ }
+
+ /**
+ * Set the default bullet to be used as LI bullet symbol
+ * @param $symbol (string) character or string to be used (legal values are: '' = automatic, '!' = auto bullet, '#' = auto numbering, 'disc', 'disc', 'circle', 'square', '1', 'decimal', 'decimal-leading-zero', 'i', 'lower-roman', 'I', 'upper-roman', 'a', 'lower-alpha', 'lower-latin', 'A', 'upper-alpha', 'upper-latin', 'lower-greek', 'img|type|width|height|image.ext')
+ * @public
+ * @since 4.0.028 (2008-09-26)
+ */
+ public function setLIsymbol($symbol='!') {
+ // check for custom image symbol
+ if (substr($symbol, 0, 4) == 'img|') {
+ $this->lisymbol = $symbol;
+ return;
+ }
+ $symbol = strtolower($symbol);
+ $valid_symbols = array('!', '#', 'disc', 'circle', 'square', '1', 'decimal', 'decimal-leading-zero', 'i', 'lower-roman', 'I', 'upper-roman', 'a', 'lower-alpha', 'lower-latin', 'A', 'upper-alpha', 'upper-latin', 'lower-greek');
+ if (in_array($symbol, $valid_symbols)) {
+ $this->lisymbol = $symbol;
+ } else {
+ $this->lisymbol = '';
+ }
+ }
+
+ /**
+ * Set the booklet mode for double-sided pages.
+ * @param $booklet (boolean) true set the booklet mode on, false otherwise.
+ * @param $inner (float) Inner page margin.
+ * @param $outer (float) Outer page margin.
+ * @public
+ * @since 4.2.000 (2008-10-29)
+ */
+ public function SetBooklet($booklet=true, $inner=-1, $outer=-1) {
+ $this->booklet = $booklet;
+ if ($inner >= 0) {
+ $this->lMargin = $inner;
+ }
+ if ($outer >= 0) {
+ $this->rMargin = $outer;
+ }
+ }
+
+ /**
+ * Swap the left and right margins.
+ * @param $reverse (boolean) if true swap left and right margins.
+ * @protected
+ * @since 4.2.000 (2008-10-29)
+ */
+ protected function swapMargins($reverse=true) {
+ if ($reverse) {
+ // swap left and right margins
+ $mtemp = $this->original_lMargin;
+ $this->original_lMargin = $this->original_rMargin;
+ $this->original_rMargin = $mtemp;
+ $deltam = $this->original_lMargin - $this->original_rMargin;
+ $this->lMargin += $deltam;
+ $this->rMargin -= $deltam;
+ }
+ }
+
+ /**
+ * Set the vertical spaces for HTML tags.
+ * The array must have the following structure (example):
+ * $tagvs = array('h1' => array(0 => array('h' => '', 'n' => 2), 1 => array('h' => 1.3, 'n' => 1)));
+ * The first array level contains the tag names,
+ * the second level contains 0 for opening tags or 1 for closing tags,
+ * the third level contains the vertical space unit (h) and the number spaces to add (n).
+ * If the h parameter is not specified, default values are used.
+ * @param $tagvs (array) array of tags and relative vertical spaces.
+ * @public
+ * @since 4.2.001 (2008-10-30)
+ */
+ public function setHtmlVSpace($tagvs) {
+ $this->tagvspaces = $tagvs;
+ }
+
+ /**
+ * Set custom width for list indentation.
+ * @param $width (float) width of the indentation. Use negative value to disable it.
+ * @public
+ * @since 4.2.007 (2008-11-12)
+ */
+ public function setListIndentWidth($width) {
+ return $this->customlistindent = floatval($width);
+ }
+
+ /**
+ * Set the top/bottom cell sides to be open or closed when the cell cross the page.
+ * @param $isopen (boolean) if true keeps the top/bottom border open for the cell sides that cross the page.
+ * @public
+ * @since 4.2.010 (2008-11-14)
+ */
+ public function setOpenCell($isopen) {
+ $this->opencell = $isopen;
+ }
+
+ /**
+ * Set the color and font style for HTML links.
+ * @param $color (array) RGB array of colors
+ * @param $fontstyle (string) additional font styles to add
+ * @public
+ * @since 4.4.003 (2008-12-09)
+ */
+ public function setHtmlLinksStyle($color=array(0,0,255), $fontstyle='U') {
+ $this->htmlLinkColorArray = $color;
+ $this->htmlLinkFontStyle = $fontstyle;
+ }
+
+ /**
+ * Convert HTML string containing value and unit of measure to user's units or points.
+ * @param $htmlval (string) String containing values and unit.
+ * @param $refsize (string) Reference value in points.
+ * @param $defaultunit (string) Default unit (can be one of the following: %, em, ex, px, in, mm, pc, pt).
+ * @param $points (boolean) If true returns points, otherwise returns value in user's units.
+ * @return float value in user's unit or point if $points=true
+ * @public
+ * @since 4.4.004 (2008-12-10)
+ */
+ public function getHTMLUnitToUnits($htmlval, $refsize=1, $defaultunit='px', $points=false) {
+ $supportedunits = array('%', 'em', 'ex', 'px', 'in', 'cm', 'mm', 'pc', 'pt');
+ $retval = 0;
+ $value = 0;
+ $unit = 'px';
+ if ($points) {
+ $k = 1;
+ } else {
+ $k = $this->k;
+ }
+ if (in_array($defaultunit, $supportedunits)) {
+ $unit = $defaultunit;
+ }
+ if (is_numeric($htmlval)) {
+ $value = floatval($htmlval);
+ } elseif (preg_match('/([0-9\.\-\+]+)/', $htmlval, $mnum)) {
+ $value = floatval($mnum[1]);
+ if (preg_match('/([a-z%]+)/', $htmlval, $munit)) {
+ if (in_array($munit[1], $supportedunits)) {
+ $unit = $munit[1];
+ }
+ }
+ }
+ switch ($unit) {
+ // percentage
+ case '%': {
+ $retval = (($value * $refsize) / 100);
+ break;
+ }
+ // relative-size
+ case 'em': {
+ $retval = ($value * $refsize);
+ break;
+ }
+ // height of lower case 'x' (about half the font-size)
+ case 'ex': {
+ $retval = ($value * ($refsize / 2));
+ break;
+ }
+ // absolute-size
+ case 'in': {
+ $retval = (($value * $this->dpi) / $k);
+ break;
+ }
+ // centimeters
+ case 'cm': {
+ $retval = (($value / 2.54 * $this->dpi) / $k);
+ break;
+ }
+ // millimeters
+ case 'mm': {
+ $retval = (($value / 25.4 * $this->dpi) / $k);
+ break;
+ }
+ // one pica is 12 points
+ case 'pc': {
+ $retval = (($value * 12) / $k);
+ break;
+ }
+ // points
+ case 'pt': {
+ $retval = ($value / $k);
+ break;
+ }
+ // pixels
+ case 'px': {
+ $retval = $this->pixelsToUnits($value);
+ if ($points) {
+ $retval *= $this->k;
+ }
+ break;
+ }
+ }
+ return $retval;
+ }
+
+ /**
+ * Output an HTML list bullet or ordered item symbol
+ * @param $listdepth (int) list nesting level
+ * @param $listtype (string) type of list
+ * @param $size (float) current font size
+ * @protected
+ * @since 4.4.004 (2008-12-10)
+ */
+ protected function putHtmlListBullet($listdepth, $listtype='', $size=10) {
+ if ($this->state != 2) {
+ return;
+ }
+ $size /= $this->k;
+ $fill = '';
+ $bgcolor = $this->bgcolor;
+ $color = $this->fgcolor;
+ $strokecolor = $this->strokecolor;
+ $width = 0;
+ $textitem = '';
+ $tmpx = $this->x;
+ $lspace = $this->GetStringWidth(' ');
+ if ($listtype == '^') {
+ // special symbol used for avoid justification of rect bullet
+ $this->lispacer = '';
+ return;
+ } elseif ($listtype == '!') {
+ // set default list type for unordered list
+ $deftypes = array('disc', 'circle', 'square');
+ $listtype = $deftypes[($listdepth - 1) % 3];
+ } elseif ($listtype == '#') {
+ // set default list type for ordered list
+ $listtype = 'decimal';
+ } elseif (substr($listtype, 0, 4) == 'img|') {
+ // custom image type ('img|type|width|height|image.ext')
+ $img = explode('|', $listtype);
+ $listtype = 'img';
+ }
+ switch ($listtype) {
+ // unordered types
+ case 'none': {
+ break;
+ }
+ case 'disc': {
+ $r = $size / 6;
+ $lspace += (2 * $r);
+ if ($this->rtl) {
+ $this->x += $lspace;
+ } else {
+ $this->x -= $lspace;
+ }
+ $this->Circle(($this->x + $r), ($this->y + ($this->lasth / 2)), $r, 0, 360, 'F', array(), $color, 8);
+ break;
+ }
+ case 'circle': {
+ $r = $size / 6;
+ $lspace += (2 * $r);
+ if ($this->rtl) {
+ $this->x += $lspace;
+ } else {
+ $this->x -= $lspace;
+ }
+ $prev_line_style = $this->linestyleWidth.' '.$this->linestyleCap.' '.$this->linestyleJoin.' '.$this->linestyleDash.' '.$this->DrawColor;
+ $new_line_style = array('width' => ($r / 3), 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'phase' => 0, 'color'=>$color);
+ $this->Circle(($this->x + $r), ($this->y + ($this->lasth / 2)), ($r * (1 - (1/6))), 0, 360, 'D', $new_line_style, array(), 8);
+ $this->_out($prev_line_style); // restore line settings
+ break;
+ }
+ case 'square': {
+ $l = $size / 3;
+ $lspace += $l;
+ if ($this->rtl) {;
+ $this->x += $lspace;
+ } else {
+ $this->x -= $lspace;
+ }
+ $this->Rect($this->x, ($this->y + (($this->lasth - $l) / 2)), $l, $l, 'F', array(), $color);
+ break;
+ }
+ case 'img': {
+ // 1=>type, 2=>width, 3=>height, 4=>image.ext
+ $lspace += $img[2];
+ if ($this->rtl) {;
+ $this->x += $lspace;
+ } else {
+ $this->x -= $lspace;
+ }
+ $imgtype = strtolower($img[1]);
+ $prev_y = $this->y;
+ switch ($imgtype) {
+ case 'svg': {
+ $this->ImageSVG($img[4], $this->x, ($this->y + (($this->lasth - $img[3]) / 2)), $img[2], $img[3], '', 'T', '', 0, false);
+ break;
+ }
+ case 'ai':
+ case 'eps': {
+ $this->ImageEps($img[4], $this->x, ($this->y + (($this->lasth - $img[3]) / 2)), $img[2], $img[3], '', true, 'T', '', 0, false);
+ break;
+ }
+ default: {
+ $this->Image($img[4], $this->x, ($this->y + (($this->lasth - $img[3]) / 2)), $img[2], $img[3], $img[1], '', 'T', false, 300, '', false, false, 0, false, false, false);
+ break;
+ }
+ }
+ $this->y = $prev_y;
+ break;
+ }
+ // ordered types
+ // $this->listcount[$this->listnum];
+ // $textitem
+ case '1':
+ case 'decimal': {
+ $textitem = $this->listcount[$this->listnum];
+ break;
+ }
+ case 'decimal-leading-zero': {
+ $textitem = sprintf('%02d', $this->listcount[$this->listnum]);
+ break;
+ }
+ case 'i':
+ case 'lower-roman': {
+ $textitem = strtolower(TCPDF_STATIC::intToRoman($this->listcount[$this->listnum]));
+ break;
+ }
+ case 'I':
+ case 'upper-roman': {
+ $textitem = TCPDF_STATIC::intToRoman($this->listcount[$this->listnum]);
+ break;
+ }
+ case 'a':
+ case 'lower-alpha':
+ case 'lower-latin': {
+ $textitem = chr(97 + $this->listcount[$this->listnum] - 1);
+ break;
+ }
+ case 'A':
+ case 'upper-alpha':
+ case 'upper-latin': {
+ $textitem = chr(65 + $this->listcount[$this->listnum] - 1);
+ break;
+ }
+ case 'lower-greek': {
+ $textitem = TCPDF_FONTS::unichr((945 + $this->listcount[$this->listnum] - 1), $this->isunicode);
+ break;
+ }
+ /*
+ // Types to be implemented (special handling)
+ case 'hebrew': {
+ break;
+ }
+ case 'armenian': {
+ break;
+ }
+ case 'georgian': {
+ break;
+ }
+ case 'cjk-ideographic': {
+ break;
+ }
+ case 'hiragana': {
+ break;
+ }
+ case 'katakana': {
+ break;
+ }
+ case 'hiragana-iroha': {
+ break;
+ }
+ case 'katakana-iroha': {
+ break;
+ }
+ */
+ default: {
+ $textitem = $this->listcount[$this->listnum];
+ }
+ }
+ if (!TCPDF_STATIC::empty_string($textitem)) {
+ // Check whether we need a new page or new column
+ $prev_y = $this->y;
+ $h = ($this->FontSize * $this->cell_height_ratio) + $this->cell_padding['T'] + $this->cell_padding['B'];
+ if ($this->checkPageBreak($h) OR ($this->y < $prev_y)) {
+ $tmpx = $this->x;
+ }
+ // print ordered item
+ if ($this->rtl) {
+ $textitem = '.'.$textitem;
+ } else {
+ $textitem = $textitem.'.';
+ }
+ $lspace += $this->GetStringWidth($textitem);
+ if ($this->rtl) {
+ $this->x += $lspace;
+ } else {
+ $this->x -= $lspace;
+ }
+ $this->Write($this->lasth, $textitem, '', false, '', false, 0, false);
+ }
+ $this->x = $tmpx;
+ $this->lispacer = '^';
+ // restore colors
+ $this->SetFillColorArray($bgcolor);
+ $this->SetDrawColorArray($strokecolor);
+ $this->SettextColorArray($color);
+ }
+
+ /**
+ * Returns current graphic variables as array.
+ * @return array of graphic variables
+ * @protected
+ * @since 4.2.010 (2008-11-14)
+ */
+ protected function getGraphicVars() {
+ $grapvars = array(
+ 'FontFamily' => $this->FontFamily,
+ 'FontStyle' => $this->FontStyle,
+ 'FontSizePt' => $this->FontSizePt,
+ 'rMargin' => $this->rMargin,
+ 'lMargin' => $this->lMargin,
+ 'cell_padding' => $this->cell_padding,
+ 'cell_margin' => $this->cell_margin,
+ 'LineWidth' => $this->LineWidth,
+ 'linestyleWidth' => $this->linestyleWidth,
+ 'linestyleCap' => $this->linestyleCap,
+ 'linestyleJoin' => $this->linestyleJoin,
+ 'linestyleDash' => $this->linestyleDash,
+ 'textrendermode' => $this->textrendermode,
+ 'textstrokewidth' => $this->textstrokewidth,
+ 'DrawColor' => $this->DrawColor,
+ 'FillColor' => $this->FillColor,
+ 'TextColor' => $this->TextColor,
+ 'ColorFlag' => $this->ColorFlag,
+ 'bgcolor' => $this->bgcolor,
+ 'fgcolor' => $this->fgcolor,
+ 'htmlvspace' => $this->htmlvspace,
+ 'listindent' => $this->listindent,
+ 'listindentlevel' => $this->listindentlevel,
+ 'listnum' => $this->listnum,
+ 'listordered' => $this->listordered,
+ 'listcount' => $this->listcount,
+ 'lispacer' => $this->lispacer,
+ 'cell_height_ratio' => $this->cell_height_ratio,
+ 'font_stretching' => $this->font_stretching,
+ 'font_spacing' => $this->font_spacing,
+ 'alpha' => $this->alpha,
+ // extended
+ 'lasth' => $this->lasth,
+ 'tMargin' => $this->tMargin,
+ 'bMargin' => $this->bMargin,
+ 'AutoPageBreak' => $this->AutoPageBreak,
+ 'PageBreakTrigger' => $this->PageBreakTrigger,
+ 'x' => $this->x,
+ 'y' => $this->y,
+ 'w' => $this->w,
+ 'h' => $this->h,
+ 'wPt' => $this->wPt,
+ 'hPt' => $this->hPt,
+ 'fwPt' => $this->fwPt,
+ 'fhPt' => $this->fhPt,
+ 'page' => $this->page,
+ 'current_column' => $this->current_column,
+ 'num_columns' => $this->num_columns
+ );
+ return $grapvars;
+ }
+
+ /**
+ * Set graphic variables.
+ * @param $gvars (array) array of graphic variablesto restore
+ * @param $extended (boolean) if true restore extended graphic variables
+ * @protected
+ * @since 4.2.010 (2008-11-14)
+ */
+ protected function setGraphicVars($gvars, $extended=false) {
+ if ($this->state != 2) {
+ return;
+ }
+ $this->FontFamily = $gvars['FontFamily'];
+ $this->FontStyle = $gvars['FontStyle'];
+ $this->FontSizePt = $gvars['FontSizePt'];
+ $this->rMargin = $gvars['rMargin'];
+ $this->lMargin = $gvars['lMargin'];
+ $this->cell_padding = $gvars['cell_padding'];
+ $this->cell_margin = $gvars['cell_margin'];
+ $this->LineWidth = $gvars['LineWidth'];
+ $this->linestyleWidth = $gvars['linestyleWidth'];
+ $this->linestyleCap = $gvars['linestyleCap'];
+ $this->linestyleJoin = $gvars['linestyleJoin'];
+ $this->linestyleDash = $gvars['linestyleDash'];
+ $this->textrendermode = $gvars['textrendermode'];
+ $this->textstrokewidth = $gvars['textstrokewidth'];
+ $this->DrawColor = $gvars['DrawColor'];
+ $this->FillColor = $gvars['FillColor'];
+ $this->TextColor = $gvars['TextColor'];
+ $this->ColorFlag = $gvars['ColorFlag'];
+ $this->bgcolor = $gvars['bgcolor'];
+ $this->fgcolor = $gvars['fgcolor'];
+ $this->htmlvspace = $gvars['htmlvspace'];
+ $this->listindent = $gvars['listindent'];
+ $this->listindentlevel = $gvars['listindentlevel'];
+ $this->listnum = $gvars['listnum'];
+ $this->listordered = $gvars['listordered'];
+ $this->listcount = $gvars['listcount'];
+ $this->lispacer = $gvars['lispacer'];
+ $this->cell_height_ratio = $gvars['cell_height_ratio'];
+ $this->font_stretching = $gvars['font_stretching'];
+ $this->font_spacing = $gvars['font_spacing'];
+ $this->alpha = $gvars['alpha'];
+ if ($extended) {
+ // restore extended values
+ $this->lasth = $gvars['lasth'];
+ $this->tMargin = $gvars['tMargin'];
+ $this->bMargin = $gvars['bMargin'];
+ $this->AutoPageBreak = $gvars['AutoPageBreak'];
+ $this->PageBreakTrigger = $gvars['PageBreakTrigger'];
+ $this->x = $gvars['x'];
+ $this->y = $gvars['y'];
+ $this->w = $gvars['w'];
+ $this->h = $gvars['h'];
+ $this->wPt = $gvars['wPt'];
+ $this->hPt = $gvars['hPt'];
+ $this->fwPt = $gvars['fwPt'];
+ $this->fhPt = $gvars['fhPt'];
+ $this->page = $gvars['page'];
+ $this->current_column = $gvars['current_column'];
+ $this->num_columns = $gvars['num_columns'];
+ }
+ $this->_out(''.$this->linestyleWidth.' '.$this->linestyleCap.' '.$this->linestyleJoin.' '.$this->linestyleDash.' '.$this->DrawColor.' '.$this->FillColor.'');
+ if (!TCPDF_STATIC::empty_string($this->FontFamily)) {
+ $this->SetFont($this->FontFamily, $this->FontStyle, $this->FontSizePt);
+ }
+ }
+
+ /**
+ * Writes data to a temporary file on filesystem.
+ * @param $filename (string) file name
+ * @param $data (mixed) data to write on file
+ * @param $append (boolean) if true append data, false replace.
+ * @since 4.5.000 (2008-12-31)
+ * @protected
+ */
+ protected function writeDiskCache($filename, $data, $append=false) {
+ if ($append) {
+ $fmode = 'ab+';
+ } else {
+ $fmode = 'wb+';
+ }
+ $f = @fopen($filename, $fmode);
+ if (!$f) {
+ $this->Error('Unable to write cache file: '.$filename);
+ } else {
+ fwrite($f, $data);
+ fclose($f);
+ }
+ // update file length (needed for transactions)
+ if (!isset($this->cache_file_length['_'.$filename])) {
+ $this->cache_file_length['_'.$filename] = strlen($data);
+ } else {
+ $this->cache_file_length['_'.$filename] += strlen($data);
+ }
+ }
+
+ /**
+ * Read data from a temporary file on filesystem.
+ * @param $filename (string) file name
+ * @return mixed retrieved data
+ * @since 4.5.000 (2008-12-31)
+ * @protected
+ */
+ protected function readDiskCache($filename) {
+ return file_get_contents($filename);
+ }
+
+ /**
+ * Set buffer content (always append data).
+ * @param $data (string) data
+ * @protected
+ * @since 4.5.000 (2009-01-02)
+ */
+ protected function setBuffer($data) {
+ $this->bufferlen += strlen($data);
+ if ($this->diskcache) {
+ if (!isset($this->buffer) OR TCPDF_STATIC::empty_string($this->buffer)) {
+ $this->buffer = TCPDF_STATIC::getObjFilename('buffer');
+ }
+ $this->writeDiskCache($this->buffer, $data, true);
+ } else {
+ $this->buffer .= $data;
+ }
+ }
+
+ /**
+ * Replace the buffer content
+ * @param $data (string) data
+ * @protected
+ * @since 5.5.000 (2010-06-22)
+ */
+ protected function replaceBuffer($data) {
+ $this->bufferlen = strlen($data);
+ if ($this->diskcache) {
+ if (!isset($this->buffer) OR TCPDF_STATIC::empty_string($this->buffer)) {
+ $this->buffer = TCPDF_STATIC::getObjFilename('buffer');
+ }
+ $this->writeDiskCache($this->buffer, $data, false);
+ } else {
+ $this->buffer = $data;
+ }
+ }
+
+ /**
+ * Get buffer content.
+ * @return string buffer content
+ * @protected
+ * @since 4.5.000 (2009-01-02)
+ */
+ protected function getBuffer() {
+ if ($this->diskcache) {
+ return $this->readDiskCache($this->buffer);
+ } else {
+ return $this->buffer;
+ }
+ }
+
+ /**
+ * Set page buffer content.
+ * @param $page (int) page number
+ * @param $data (string) page data
+ * @param $append (boolean) if true append data, false replace.
+ * @protected
+ * @since 4.5.000 (2008-12-31)
+ */
+ protected function setPageBuffer($page, $data, $append=false) {
+ if ($this->diskcache) {
+ if (!isset($this->pages[$page])) {
+ $this->pages[$page] = TCPDF_STATIC::getObjFilename('page'.$page);
+ }
+ $this->writeDiskCache($this->pages[$page], $data, $append);
+ } else {
+ if ($append) {
+ $this->pages[$page] .= $data;
+ } else {
+ $this->pages[$page] = $data;
+ }
+ }
+ if ($append AND isset($this->pagelen[$page])) {
+ $this->pagelen[$page] += strlen($data);
+ } else {
+ $this->pagelen[$page] = strlen($data);
+ }
+ }
+
+ /**
+ * Get page buffer content.
+ * @param $page (int) page number
+ * @return string page buffer content or false in case of error
+ * @protected
+ * @since 4.5.000 (2008-12-31)
+ */
+ protected function getPageBuffer($page) {
+ if ($this->diskcache) {
+ return $this->readDiskCache($this->pages[$page]);
+ } elseif (isset($this->pages[$page])) {
+ return $this->pages[$page];
+ }
+ return false;
+ }
+
+ /**
+ * Set image buffer content.
+ * @param $image (string) image key
+ * @param $data (array) image data
+ * @return int image index number
+ * @protected
+ * @since 4.5.000 (2008-12-31)
+ */
+ protected function setImageBuffer($image, $data) {
+ if (($data['i'] = array_search($image, $this->imagekeys)) === FALSE) {
+ $this->imagekeys[$this->numimages] = $image;
+ $data['i'] = $this->numimages;
+ ++$this->numimages;
+ }
+ if ($this->diskcache) {
+ if (!isset($this->images[$image])) {
+ $this->images[$image] = TCPDF_STATIC::getObjFilename('image'.$image);
+ }
+ $this->writeDiskCache($this->images[$image], serialize($data));
+ } else {
+ $this->images[$image] = $data;
+ }
+ return $data['i'];
+ }
+
+ /**
+ * Set image buffer content for a specified sub-key.
+ * @param $image (string) image key
+ * @param $key (string) image sub-key
+ * @param $data (array) image data
+ * @protected
+ * @since 4.5.000 (2008-12-31)
+ */
+ protected function setImageSubBuffer($image, $key, $data) {
+ if (!isset($this->images[$image])) {
+ $this->setImageBuffer($image, array());
+ }
+ if ($this->diskcache) {
+ $tmpimg = $this->getImageBuffer($image);
+ $tmpimg[$key] = $data;
+ $this->writeDiskCache($this->images[$image], serialize($tmpimg));
+ } else {
+ $this->images[$image][$key] = $data;
+ }
+ }
+
+ /**
+ * Get image buffer content.
+ * @param $image (string) image key
+ * @return string image buffer content or false in case of error
+ * @protected
+ * @since 4.5.000 (2008-12-31)
+ */
+ protected function getImageBuffer($image) {
+ if ($this->diskcache AND isset($this->images[$image])) {
+ return unserialize($this->readDiskCache($this->images[$image]));
+ } elseif (isset($this->images[$image])) {
+ return $this->images[$image];
+ }
+ return false;
+ }
+
+ /**
+ * Set font buffer content.
+ * @param $font (string) font key
+ * @param $data (array) font data
+ * @protected
+ * @since 4.5.000 (2009-01-02)
+ */
+ protected function setFontBuffer($font, $data) {
+ if ($this->diskcache) {
+ if (!isset($this->fonts[$font])) {
+ $this->fonts[$font] = TCPDF_STATIC::getObjFilename('font');
+ }
+ $this->writeDiskCache($this->fonts[$font], serialize($data));
+ } else {
+ $this->fonts[$font] = $data;
+ }
+ if (!in_array($font, $this->fontkeys)) {
+ $this->fontkeys[] = $font;
+ // store object ID for current font
+ ++$this->n;
+ $this->font_obj_ids[$font] = $this->n;
+ $this->setFontSubBuffer($font, 'n', $this->n);
+ }
+ }
+
+ /**
+ * Set font buffer content.
+ * @param $font (string) font key
+ * @param $key (string) font sub-key
+ * @param $data (array) font data
+ * @protected
+ * @since 4.5.000 (2009-01-02)
+ */
+ protected function setFontSubBuffer($font, $key, $data) {
+ if (!isset($this->fonts[$font])) {
+ $this->setFontBuffer($font, array());
+ }
+ if ($this->diskcache) {
+ $tmpfont = $this->getFontBuffer($font);
+ $tmpfont[$key] = $data;
+ $this->writeDiskCache($this->fonts[$font], serialize($tmpfont));
+ } else {
+ $this->fonts[$font][$key] = $data;
+ }
+ }
+
+ /**
+ * Get font buffer content.
+ * @param $font (string) font key
+ * @return string font buffer content or false in case of error
+ * @protected
+ * @since 4.5.000 (2009-01-02)
+ */
+ protected function getFontBuffer($font) {
+ if ($this->diskcache AND isset($this->fonts[$font])) {
+ return unserialize($this->readDiskCache($this->fonts[$font]));
+ } elseif (isset($this->fonts[$font])) {
+ return $this->fonts[$font];
+ }
+ return false;
+ }
+
+ /**
+ * Move a page to a previous position.
+ * @param $frompage (int) number of the source page
+ * @param $topage (int) number of the destination page (must be less than $frompage)
+ * @return true in case of success, false in case of error.
+ * @public
+ * @since 4.5.000 (2009-01-02)
+ */
+ public function movePage($frompage, $topage) {
+ if (($frompage > $this->numpages) OR ($frompage <= $topage)) {
+ return false;
+ }
+ if ($frompage == $this->page) {
+ // close the page before moving it
+ $this->endPage();
+ }
+ // move all page-related states
+ $tmppage = $this->getPageBuffer($frompage);
+ $tmppagedim = $this->pagedim[$frompage];
+ $tmppagelen = $this->pagelen[$frompage];
+ $tmpintmrk = $this->intmrk[$frompage];
+ $tmpbordermrk = $this->bordermrk[$frompage];
+ $tmpcntmrk = $this->cntmrk[$frompage];
+ $tmppageobjects = $this->pageobjects[$frompage];
+ if (isset($this->footerpos[$frompage])) {
+ $tmpfooterpos = $this->footerpos[$frompage];
+ }
+ if (isset($this->footerlen[$frompage])) {
+ $tmpfooterlen = $this->footerlen[$frompage];
+ }
+ if (isset($this->transfmrk[$frompage])) {
+ $tmptransfmrk = $this->transfmrk[$frompage];
+ }
+ if (isset($this->PageAnnots[$frompage])) {
+ $tmpannots = $this->PageAnnots[$frompage];
+ }
+ if (isset($this->newpagegroup) AND !empty($this->newpagegroup)) {
+ for ($i = $frompage; $i > $topage; --$i) {
+ if (isset($this->newpagegroup[$i]) AND (($i + $this->pagegroups[$this->newpagegroup[$i]]) > $frompage)) {
+ --$this->pagegroups[$this->newpagegroup[$i]];
+ break;
+ }
+ }
+ for ($i = $topage; $i > 0; --$i) {
+ if (isset($this->newpagegroup[$i]) AND (($i + $this->pagegroups[$this->newpagegroup[$i]]) > $topage)) {
+ ++$this->pagegroups[$this->newpagegroup[$i]];
+ break;
+ }
+ }
+ }
+ for ($i = $frompage; $i > $topage; --$i) {
+ $j = $i - 1;
+ // shift pages down
+ $this->setPageBuffer($i, $this->getPageBuffer($j));
+ $this->pagedim[$i] = $this->pagedim[$j];
+ $this->pagelen[$i] = $this->pagelen[$j];
+ $this->intmrk[$i] = $this->intmrk[$j];
+ $this->bordermrk[$i] = $this->bordermrk[$j];
+ $this->cntmrk[$i] = $this->cntmrk[$j];
+ $this->pageobjects[$i] = $this->pageobjects[$j];
+ if (isset($this->footerpos[$j])) {
+ $this->footerpos[$i] = $this->footerpos[$j];
+ } elseif (isset($this->footerpos[$i])) {
+ unset($this->footerpos[$i]);
+ }
+ if (isset($this->footerlen[$j])) {
+ $this->footerlen[$i] = $this->footerlen[$j];
+ } elseif (isset($this->footerlen[$i])) {
+ unset($this->footerlen[$i]);
+ }
+ if (isset($this->transfmrk[$j])) {
+ $this->transfmrk[$i] = $this->transfmrk[$j];
+ } elseif (isset($this->transfmrk[$i])) {
+ unset($this->transfmrk[$i]);
+ }
+ if (isset($this->PageAnnots[$j])) {
+ $this->PageAnnots[$i] = $this->PageAnnots[$j];
+ } elseif (isset($this->PageAnnots[$i])) {
+ unset($this->PageAnnots[$i]);
+ }
+ if (isset($this->newpagegroup[$j])) {
+ $this->newpagegroup[$i] = $this->newpagegroup[$j];
+ unset($this->newpagegroup[$j]);
+ }
+ if ($this->currpagegroup == $j) {
+ $this->currpagegroup = $i;
+ }
+ }
+ $this->setPageBuffer($topage, $tmppage);
+ $this->pagedim[$topage] = $tmppagedim;
+ $this->pagelen[$topage] = $tmppagelen;
+ $this->intmrk[$topage] = $tmpintmrk;
+ $this->bordermrk[$topage] = $tmpbordermrk;
+ $this->cntmrk[$topage] = $tmpcntmrk;
+ $this->pageobjects[$topage] = $tmppageobjects;
+ if (isset($tmpfooterpos)) {
+ $this->footerpos[$topage] = $tmpfooterpos;
+ } elseif (isset($this->footerpos[$topage])) {
+ unset($this->footerpos[$topage]);
+ }
+ if (isset($tmpfooterlen)) {
+ $this->footerlen[$topage] = $tmpfooterlen;
+ } elseif (isset($this->footerlen[$topage])) {
+ unset($this->footerlen[$topage]);
+ }
+ if (isset($tmptransfmrk)) {
+ $this->transfmrk[$topage] = $tmptransfmrk;
+ } elseif (isset($this->transfmrk[$topage])) {
+ unset($this->transfmrk[$topage]);
+ }
+ if (isset($tmpannots)) {
+ $this->PageAnnots[$topage] = $tmpannots;
+ } elseif (isset($this->PageAnnots[$topage])) {
+ unset($this->PageAnnots[$topage]);
+ }
+ // adjust outlines
+ $tmpoutlines = $this->outlines;
+ foreach ($tmpoutlines as $key => $outline) {
+ if (($outline['p'] >= $topage) AND ($outline['p'] < $frompage)) {
+ $this->outlines[$key]['p'] = ($outline['p'] + 1);
+ } elseif ($outline['p'] == $frompage) {
+ $this->outlines[$key]['p'] = $topage;
+ }
+ }
+ // adjust dests
+ $tmpdests = $this->dests;
+ foreach ($tmpdests as $key => $dest) {
+ if (($dest['p'] >= $topage) AND ($dest['p'] < $frompage)) {
+ $this->dests[$key]['p'] = ($dest['p'] + 1);
+ } elseif ($dest['p'] == $frompage) {
+ $this->dests[$key]['p'] = $topage;
+ }
+ }
+ // adjust links
+ $tmplinks = $this->links;
+ foreach ($tmplinks as $key => $link) {
+ if (($link[0] >= $topage) AND ($link[0] < $frompage)) {
+ $this->links[$key][0] = ($link[0] + 1);
+ } elseif ($link[0] == $frompage) {
+ $this->links[$key][0] = $topage;
+ }
+ }
+ // adjust javascript
+ $tmpjavascript = $this->javascript;
+ global $jfrompage, $jtopage;
+ $jfrompage = $frompage;
+ $jtopage = $topage;
+ $this->javascript = preg_replace_callback('/this\.addField\(\'([^\']*)\',\'([^\']*)\',([0-9]+)/',
+ create_function('$matches', 'global $jfrompage, $jtopage;
+ $pagenum = intval($matches[3]) + 1;
+ if (($pagenum >= $jtopage) AND ($pagenum < $jfrompage)) {
+ $newpage = ($pagenum + 1);
+ } elseif ($pagenum == $jfrompage) {
+ $newpage = $jtopage;
+ } else {
+ $newpage = $pagenum;
+ }
+ --$newpage;
+ return "this.addField(\'".$matches[1]."\',\'".$matches[2]."\',".$newpage."";'), $tmpjavascript);
+ // return to last page
+ $this->lastPage(true);
+ return true;
+ }
+
+ /**
+ * Remove the specified page.
+ * @param $page (int) page to remove
+ * @return true in case of success, false in case of error.
+ * @public
+ * @since 4.6.004 (2009-04-23)
+ */
+ public function deletePage($page) {
+ if (($page < 1) OR ($page > $this->numpages)) {
+ return false;
+ }
+ // delete current page
+ unset($this->pages[$page]);
+ unset($this->pagedim[$page]);
+ unset($this->pagelen[$page]);
+ unset($this->intmrk[$page]);
+ unset($this->bordermrk[$page]);
+ unset($this->cntmrk[$page]);
+ foreach ($this->pageobjects[$page] as $oid) {
+ if (isset($this->offsets[$oid])){
+ unset($this->offsets[$oid]);
+ }
+ }
+ unset($this->pageobjects[$page]);
+ if (isset($this->footerpos[$page])) {
+ unset($this->footerpos[$page]);
+ }
+ if (isset($this->footerlen[$page])) {
+ unset($this->footerlen[$page]);
+ }
+ if (isset($this->transfmrk[$page])) {
+ unset($this->transfmrk[$page]);
+ }
+ if (isset($this->PageAnnots[$page])) {
+ unset($this->PageAnnots[$page]);
+ }
+ if (isset($this->newpagegroup) AND !empty($this->newpagegroup)) {
+ for ($i = $page; $i > 0; --$i) {
+ if (isset($this->newpagegroup[$i]) AND (($i + $this->pagegroups[$this->newpagegroup[$i]]) > $page)) {
+ --$this->pagegroups[$this->newpagegroup[$i]];
+ break;
+ }
+ }
+ }
+ if (isset($this->pageopen[$page])) {
+ unset($this->pageopen[$page]);
+ }
+ if ($page < $this->numpages) {
+ // update remaining pages
+ for ($i = $page; $i < $this->numpages; ++$i) {
+ $j = $i + 1;
+ // shift pages
+ $this->setPageBuffer($i, $this->getPageBuffer($j));
+ $this->pagedim[$i] = $this->pagedim[$j];
+ $this->pagelen[$i] = $this->pagelen[$j];
+ $this->intmrk[$i] = $this->intmrk[$j];
+ $this->bordermrk[$i] = $this->bordermrk[$j];
+ $this->cntmrk[$i] = $this->cntmrk[$j];
+ $this->pageobjects[$i] = $this->pageobjects[$j];
+ if (isset($this->footerpos[$j])) {
+ $this->footerpos[$i] = $this->footerpos[$j];
+ } elseif (isset($this->footerpos[$i])) {
+ unset($this->footerpos[$i]);
+ }
+ if (isset($this->footerlen[$j])) {
+ $this->footerlen[$i] = $this->footerlen[$j];
+ } elseif (isset($this->footerlen[$i])) {
+ unset($this->footerlen[$i]);
+ }
+ if (isset($this->transfmrk[$j])) {
+ $this->transfmrk[$i] = $this->transfmrk[$j];
+ } elseif (isset($this->transfmrk[$i])) {
+ unset($this->transfmrk[$i]);
+ }
+ if (isset($this->PageAnnots[$j])) {
+ $this->PageAnnots[$i] = $this->PageAnnots[$j];
+ } elseif (isset($this->PageAnnots[$i])) {
+ unset($this->PageAnnots[$i]);
+ }
+ if (isset($this->newpagegroup[$j])) {
+ $this->newpagegroup[$i] = $this->newpagegroup[$j];
+ unset($this->newpagegroup[$j]);
+ }
+ if ($this->currpagegroup == $j) {
+ $this->currpagegroup = $i;
+ }
+ if (isset($this->pageopen[$j])) {
+ $this->pageopen[$i] = $this->pageopen[$j];
+ } elseif (isset($this->pageopen[$i])) {
+ unset($this->pageopen[$i]);
+ }
+ }
+ // remove last page
+ unset($this->pages[$this->numpages]);
+ unset($this->pagedim[$this->numpages]);
+ unset($this->pagelen[$this->numpages]);
+ unset($this->intmrk[$this->numpages]);
+ unset($this->bordermrk[$this->numpages]);
+ unset($this->cntmrk[$this->numpages]);
+ foreach ($this->pageobjects[$this->numpages] as $oid) {
+ if (isset($this->offsets[$oid])){
+ unset($this->offsets[$oid]);
+ }
+ }
+ unset($this->pageobjects[$this->numpages]);
+ if (isset($this->footerpos[$this->numpages])) {
+ unset($this->footerpos[$this->numpages]);
+ }
+ if (isset($this->footerlen[$this->numpages])) {
+ unset($this->footerlen[$this->numpages]);
+ }
+ if (isset($this->transfmrk[$this->numpages])) {
+ unset($this->transfmrk[$this->numpages]);
+ }
+ if (isset($this->PageAnnots[$this->numpages])) {
+ unset($this->PageAnnots[$this->numpages]);
+ }
+ if (isset($this->newpagegroup[$this->numpages])) {
+ unset($this->newpagegroup[$this->numpages]);
+ }
+ if ($this->currpagegroup == $this->numpages) {
+ $this->currpagegroup = ($this->numpages - 1);
+ }
+ if (isset($this->pagegroups[$this->numpages])) {
+ unset($this->pagegroups[$this->numpages]);
+ }
+ if (isset($this->pageopen[$this->numpages])) {
+ unset($this->pageopen[$this->numpages]);
+ }
+ }
+ --$this->numpages;
+ $this->page = $this->numpages;
+ // adjust outlines
+ $tmpoutlines = $this->outlines;
+ foreach ($tmpoutlines as $key => $outline) {
+ if ($outline['p'] > $page) {
+ $this->outlines[$key]['p'] = $outline['p'] - 1;
+ } elseif ($outline['p'] == $page) {
+ unset($this->outlines[$key]);
+ }
+ }
+ // adjust dests
+ $tmpdests = $this->dests;
+ foreach ($tmpdests as $key => $dest) {
+ if ($dest['p'] > $page) {
+ $this->dests[$key]['p'] = $dest['p'] - 1;
+ } elseif ($dest['p'] == $page) {
+ unset($this->dests[$key]);
+ }
+ }
+ // adjust links
+ $tmplinks = $this->links;
+ foreach ($tmplinks as $key => $link) {
+ if ($link[0] > $page) {
+ $this->links[$key][0] = $link[0] - 1;
+ } elseif ($link[0] == $page) {
+ unset($this->links[$key]);
+ }
+ }
+ // adjust javascript
+ $tmpjavascript = $this->javascript;
+ global $jpage;
+ $jpage = $page;
+ $this->javascript = preg_replace_callback('/this\.addField\(\'([^\']*)\',\'([^\']*)\',([0-9]+)/',
+ create_function('$matches', 'global $jpage;
+ $pagenum = intval($matches[3]) + 1;
+ if ($pagenum >= $jpage) {
+ $newpage = ($pagenum - 1);
+ } elseif ($pagenum == $jpage) {
+ $newpage = 1;
+ } else {
+ $newpage = $pagenum;
+ }
+ --$newpage;
+ return "this.addField(\'".$matches[1]."\',\'".$matches[2]."\',".$newpage."";'), $tmpjavascript);
+ // return to last page
+ if ($this->numpages > 0) {
+ $this->lastPage(true);
+ }
+ return true;
+ }
+
+ /**
+ * Clone the specified page to a new page.
+ * @param $page (int) number of page to copy (0 = current page)
+ * @return true in case of success, false in case of error.
+ * @public
+ * @since 4.9.015 (2010-04-20)
+ */
+ public function copyPage($page=0) {
+ if ($page == 0) {
+ // default value
+ $page = $this->page;
+ }
+ if (($page < 1) OR ($page > $this->numpages)) {
+ return false;
+ }
+ // close the last page
+ $this->endPage();
+ // copy all page-related states
+ ++$this->numpages;
+ $this->page = $this->numpages;
+ $this->setPageBuffer($this->page, $this->getPageBuffer($page));
+ $this->pagedim[$this->page] = $this->pagedim[$page];
+ $this->pagelen[$this->page] = $this->pagelen[$page];
+ $this->intmrk[$this->page] = $this->intmrk[$page];
+ $this->bordermrk[$this->page] = $this->bordermrk[$page];
+ $this->cntmrk[$this->page] = $this->cntmrk[$page];
+ $this->pageobjects[$this->page] = $this->pageobjects[$page];
+ $this->pageopen[$this->page] = false;
+ if (isset($this->footerpos[$page])) {
+ $this->footerpos[$this->page] = $this->footerpos[$page];
+ }
+ if (isset($this->footerlen[$page])) {
+ $this->footerlen[$this->page] = $this->footerlen[$page];
+ }
+ if (isset($this->transfmrk[$page])) {
+ $this->transfmrk[$this->page] = $this->transfmrk[$page];
+ }
+ if (isset($this->PageAnnots[$page])) {
+ $this->PageAnnots[$this->page] = $this->PageAnnots[$page];
+ }
+ if (isset($this->newpagegroup[$page])) {
+ // start a new group
+ $this->newpagegroup[$this->page] = sizeof($this->newpagegroup) + 1;
+ $this->currpagegroup = $this->newpagegroup[$this->page];
+ $this->pagegroups[$this->currpagegroup] = 1;
+ } elseif (isset($this->currpagegroup) AND ($this->currpagegroup > 0)) {
+ ++$this->pagegroups[$this->currpagegroup];
+ }
+ // copy outlines
+ $tmpoutlines = $this->outlines;
+ foreach ($tmpoutlines as $key => $outline) {
+ if ($outline['p'] == $page) {
+ $this->outlines[] = array('t' => $outline['t'], 'l' => $outline['l'], 'x' => $outline['x'], 'y' => $outline['y'], 'p' => $this->page, 's' => $outline['s'], 'c' => $outline['c']);
+ }
+ }
+ // copy links
+ $tmplinks = $this->links;
+ foreach ($tmplinks as $key => $link) {
+ if ($link[0] == $page) {
+ $this->links[] = array($this->page, $link[1]);
+ }
+ }
+ // return to last page
+ $this->lastPage(true);
+ return true;
+ }
+
+ /**
+ * Output a Table of Content Index (TOC).
+ * This method must be called after all Bookmarks were set.
+ * Before calling this method you have to open the page using the addTOCPage() method.
+ * After calling this method you have to call endTOCPage() to close the TOC page.
+ * You can override this method to achieve different styles.
+ * @param $page (int) page number where this TOC should be inserted (leave empty for current page).
+ * @param $numbersfont (string) set the font for page numbers (please use monospaced font for better alignment).
+ * @param $filler (string) string used to fill the space between text and page number.
+ * @param $toc_name (string) name to use for TOC bookmark.
+ * @param $style (string) Font style for title: B = Bold, I = Italic, BI = Bold + Italic.
+ * @param $color (array) RGB color array for bookmark title (values from 0 to 255).
+ * @public
+ * @author Nicola Asuni
+ * @since 4.5.000 (2009-01-02)
+ * @see addTOCPage(), endTOCPage(), addHTMLTOC()
+ */
+ public function addTOC($page='', $numbersfont='', $filler='.', $toc_name='TOC', $style='', $color=array(0,0,0)) {
+ $fontsize = $this->FontSizePt;
+ $fontfamily = $this->FontFamily;
+ $fontstyle = $this->FontStyle;
+ $w = $this->w - $this->lMargin - $this->rMargin;
+ $spacer = $this->GetStringWidth(chr(32)) * 4;
+ $lmargin = $this->lMargin;
+ $rmargin = $this->rMargin;
+ $x_start = $this->GetX();
+ $page_first = $this->page;
+ $current_page = $this->page;
+ $page_fill_start = false;
+ $page_fill_end = false;
+ $current_column = $this->current_column;
+ if (TCPDF_STATIC::empty_string($numbersfont)) {
+ $numbersfont = $this->default_monospaced_font;
+ }
+ if (TCPDF_STATIC::empty_string($filler)) {
+ $filler = ' ';
+ }
+ if (TCPDF_STATIC::empty_string($page)) {
+ $gap = ' ';
+ } else {
+ $gap = '';
+ if ($page < 1) {
+ $page = 1;
+ }
+ }
+ $this->SetFont($numbersfont, $fontstyle, $fontsize);
+ $numwidth = $this->GetStringWidth('00000');
+ $maxpage = 0; //used for pages on attached documents
+ foreach ($this->outlines as $key => $outline) {
+ // check for extra pages (used for attachments)
+ if (($this->page > $page_first) AND ($outline['p'] >= $this->numpages)) {
+ $outline['p'] += ($this->page - $page_first);
+ }
+ if ($this->rtl) {
+ $aligntext = 'R';
+ $alignnum = 'L';
+ } else {
+ $aligntext = 'L';
+ $alignnum = 'R';
+ }
+ if ($outline['l'] == 0) {
+ $this->SetFont($fontfamily, $outline['s'].'B', $fontsize);
+ } else {
+ $this->SetFont($fontfamily, $outline['s'], $fontsize - $outline['l']);
+ }
+ $this->SetTextColorArray($outline['c']);
+ // check for page break
+ $this->checkPageBreak((2 * $this->FontSize * $this->cell_height_ratio));
+ // set margins and X position
+ if (($this->page == $current_page) AND ($this->current_column == $current_column)) {
+ $this->lMargin = $lmargin;
+ $this->rMargin = $rmargin;
+ } else {
+ if ($this->current_column != $current_column) {
+ if ($this->rtl) {
+ $x_start = $this->w - $this->columns[$this->current_column]['x'];
+ } else {
+ $x_start = $this->columns[$this->current_column]['x'];
+ }
+ }
+ $lmargin = $this->lMargin;
+ $rmargin = $this->rMargin;
+ $current_page = $this->page;
+ $current_column = $this->current_column;
+ }
+ $this->SetX($x_start);
+ $indent = ($spacer * $outline['l']);
+ if ($this->rtl) {
+ $this->x -= $indent;
+ $this->rMargin = $this->w - $this->x;
+ } else {
+ $this->x += $indent;
+ $this->lMargin = $this->x;
+ }
+ $link = $this->AddLink();
+ $this->SetLink($link, $outline['y'], $outline['p']);
+ // write the text
+ if ($this->rtl) {
+ $txt = ' '.$outline['t'];
+ } else {
+ $txt = $outline['t'].' ';
+ }
+ $this->Write(0, $txt, $link, false, $aligntext, false, 0, false, false, 0, $numwidth, '');
+ if ($this->rtl) {
+ $tw = $this->x - $this->lMargin;
+ } else {
+ $tw = $this->w - $this->rMargin - $this->x;
+ }
+ $this->SetFont($numbersfont, $fontstyle, $fontsize);
+ if (TCPDF_STATIC::empty_string($page)) {
+ $pagenum = $outline['p'];
+ } else {
+ // placemark to be replaced with the correct number
+ $pagenum = '{#'.($outline['p']).'}';
+ if ($this->isUnicodeFont()) {
+ $pagenum = '{'.$pagenum.'}';
+ }
+ $maxpage = max($maxpage, $outline['p']);
+ }
+ $fw = ($tw - $this->GetStringWidth($pagenum.$filler));
+ $wfiller = $this->GetStringWidth($filler);
+ if ($wfiller > 0) {
+ $numfills = floor($fw / $wfiller);
+ } else {
+ $numfills = 0;
+ }
+ if ($numfills > 0) {
+ $rowfill = str_repeat($filler, $numfills);
+ } else {
+ $rowfill = '';
+ }
+ if ($this->rtl) {
+ $pagenum = $pagenum.$gap.$rowfill;
+ } else {
+ $pagenum = $rowfill.$gap.$pagenum;
+ }
+ // write the number
+ $this->Cell($tw, 0, $pagenum, 0, 1, $alignnum, 0, $link, 0);
+ }
+ $page_last = $this->getPage();
+ $numpages = ($page_last - $page_first + 1);
+ // account for booklet mode
+ if ($this->booklet) {
+ // check if a blank page is required before TOC
+ $page_fill_start = ((($page_first % 2) == 0) XOR (($page % 2) == 0));
+ $page_fill_end = (!((($numpages % 2) == 0) XOR ($page_fill_start)));
+ if ($page_fill_start) {
+ // add a page at the end (to be moved before TOC)
+ $this->addPage();
+ ++$page_last;
+ ++$numpages;
+ }
+ if ($page_fill_end) {
+ // add a page at the end
+ $this->addPage();
+ ++$page_last;
+ ++$numpages;
+ }
+ }
+ $maxpage = max($maxpage, $page_last);
+ if (!TCPDF_STATIC::empty_string($page)) {
+ for ($p = $page_first; $p <= $page_last; ++$p) {
+ // get page data
+ $temppage = $this->getPageBuffer($p);
+ for ($n = 1; $n <= $maxpage; ++$n) {
+ // update page numbers
+ $a = '{#'.$n.'}';
+ // get page number aliases
+ $pnalias = $this->getInternalPageNumberAliases($a);
+ // calculate replacement number
+ if (($n >= $page) AND ($n <= $this->numpages)) {
+ $np = $n + $numpages;
+ } else {
+ $np = $n;
+ }
+ $na = TCPDF_STATIC::formatTOCPageNumber(($this->starting_page_number + $np - 1));
+ $nu = TCPDF_FONTS::UTF8ToUTF16BE($na, false, $this->isunicode, $this->CurrentFont);
+ // replace aliases with numbers
+ foreach ($pnalias['u'] as $u) {
+ $sfill = str_repeat($filler, max(0, (strlen($u) - strlen($nu.' '))));
+ if ($this->rtl) {
+ $nr = $nu.TCPDF_FONTS::UTF8ToUTF16BE(' '.$sfill, false, $this->isunicode, $this->CurrentFont);
+ } else {
+ $nr = TCPDF_FONTS::UTF8ToUTF16BE($sfill.' ', false, $this->isunicode, $this->CurrentFont).$nu;
+ }
+ $temppage = str_replace($u, $nr, $temppage);
+ }
+ foreach ($pnalias['a'] as $a) {
+ $sfill = str_repeat($filler, max(0, (strlen($a) - strlen($na.' '))));
+ if ($this->rtl) {
+ $nr = $na.' '.$sfill;
+ } else {
+ $nr = $sfill.' '.$na;
+ }
+ $temppage = str_replace($a, $nr, $temppage);
+ }
+ }
+ // save changes
+ $this->setPageBuffer($p, $temppage);
+ }
+ // move pages
+ $this->Bookmark($toc_name, 0, 0, $page_first, $style, $color);
+ if ($page_fill_start) {
+ $this->movePage($page_last, $page_first);
+ }
+ for ($i = 0; $i < $numpages; ++$i) {
+ $this->movePage($page_last, $page);
+ }
+ }
+ }
+
+ /**
+ * Output a Table Of Content Index (TOC) using HTML templates.
+ * This method must be called after all Bookmarks were set.
+ * Before calling this method you have to open the page using the addTOCPage() method.
+ * After calling this method you have to call endTOCPage() to close the TOC page.
+ * @param $page (int) page number where this TOC should be inserted (leave empty for current page).
+ * @param $toc_name (string) name to use for TOC bookmark.
+ * @param $templates (array) array of html templates. Use: "#TOC_DESCRIPTION#" for bookmark title, "#TOC_PAGE_NUMBER#" for page number.
+ * @param $correct_align (boolean) if true correct the number alignment (numbers must be in monospaced font like courier and right aligned on LTR, or left aligned on RTL)
+ * @param $style (string) Font style for title: B = Bold, I = Italic, BI = Bold + Italic.
+ * @param $color (array) RGB color array for title (values from 0 to 255).
+ * @public
+ * @author Nicola Asuni
+ * @since 5.0.001 (2010-05-06)
+ * @see addTOCPage(), endTOCPage(), addTOC()
+ */
+ public function addHTMLTOC($page='', $toc_name='TOC', $templates=array(), $correct_align=true, $style='', $color=array(0,0,0)) {
+ $filler = ' ';
+ $prev_htmlLinkColorArray = $this->htmlLinkColorArray;
+ $prev_htmlLinkFontStyle = $this->htmlLinkFontStyle;
+ // set new style for link
+ $this->htmlLinkColorArray = array();
+ $this->htmlLinkFontStyle = '';
+ $page_first = $this->getPage();
+ $page_fill_start = false;
+ $page_fill_end = false;
+ // get the font type used for numbers in each template
+ $current_font = $this->FontFamily;
+ foreach ($templates as $level => $html) {
+ $dom = $this->getHtmlDomArray($html);
+ foreach ($dom as $key => $value) {
+ if ($value['value'] == '#TOC_PAGE_NUMBER#') {
+ $this->SetFont($dom[($key - 1)]['fontname']);
+ $templates['F'.$level] = $this->isUnicodeFont();
+ }
+ }
+ }
+ $this->SetFont($current_font);
+ $maxpage = 0; //used for pages on attached documents
+ foreach ($this->outlines as $key => $outline) {
+ // get HTML template
+ $row = $templates[$outline['l']];
+ if (TCPDF_STATIC::empty_string($page)) {
+ $pagenum = $outline['p'];
+ } else {
+ // placemark to be replaced with the correct number
+ $pagenum = '{#'.($outline['p']).'}';
+ if ($templates['F'.$outline['l']]) {
+ $pagenum = '{'.$pagenum.'}';
+ }
+ $maxpage = max($maxpage, $outline['p']);
+ }
+ // replace templates with current values
+ $row = str_replace('#TOC_DESCRIPTION#', $outline['t'], $row);
+ $row = str_replace('#TOC_PAGE_NUMBER#', $pagenum, $row);
+ // add link to page
+ $row = '<a href="#'.$outline['p'].','.$outline['y'].'">'.$row.'</a>';
+ // write bookmark entry
+ $this->writeHTML($row, false, false, true, false, '');
+ }
+ // restore link styles
+ $this->htmlLinkColorArray = $prev_htmlLinkColorArray;
+ $this->htmlLinkFontStyle = $prev_htmlLinkFontStyle;
+ // move TOC page and replace numbers
+ $page_last = $this->getPage();
+ $numpages = ($page_last - $page_first + 1);
+ // account for booklet mode
+ if ($this->booklet) {
+ // check if a blank page is required before TOC
+ $page_fill_start = ((($page_first % 2) == 0) XOR (($page % 2) == 0));
+ $page_fill_end = (!((($numpages % 2) == 0) XOR ($page_fill_start)));
+ if ($page_fill_start) {
+ // add a page at the end (to be moved before TOC)
+ $this->addPage();
+ ++$page_last;
+ ++$numpages;
+ }
+ if ($page_fill_end) {
+ // add a page at the end
+ $this->addPage();
+ ++$page_last;
+ ++$numpages;
+ }
+ }
+ $maxpage = max($maxpage, $page_last);
+ if (!TCPDF_STATIC::empty_string($page)) {
+ for ($p = $page_first; $p <= $page_last; ++$p) {
+ // get page data
+ $temppage = $this->getPageBuffer($p);
+ for ($n = 1; $n <= $maxpage; ++$n) {
+ // update page numbers
+ $a = '{#'.$n.'}';
+ // get page number aliases
+ $pnalias = $this->getInternalPageNumberAliases($a);
+ // calculate replacement number
+ if ($n >= $page) {
+ $np = $n + $numpages;
+ } else {
+ $np = $n;
+ }
+ $na = TCPDF_STATIC::formatTOCPageNumber(($this->starting_page_number + $np - 1));
+ $nu = TCPDF_FONTS::UTF8ToUTF16BE($na, false, $this->isunicode, $this->CurrentFont);
+ // replace aliases with numbers
+ foreach ($pnalias['u'] as $u) {
+ if ($correct_align) {
+ $sfill = str_repeat($filler, (strlen($u) - strlen($nu.' ')));
+ if ($this->rtl) {
+ $nr = $nu.TCPDF_FONTS::UTF8ToUTF16BE(' '.$sfill, false, $this->isunicode, $this->CurrentFont);
+ } else {
+ $nr = TCPDF_FONTS::UTF8ToUTF16BE($sfill.' ', false, $this->isunicode, $this->CurrentFont).$nu;
+ }
+ } else {
+ $nr = $nu;
+ }
+ $temppage = str_replace($u, $nr, $temppage);
+ }
+ foreach ($pnalias['a'] as $a) {
+ if ($correct_align) {
+ $sfill = str_repeat($filler, (strlen($a) - strlen($na.' ')));
+ if ($this->rtl) {
+ $nr = $na.' '.$sfill;
+ } else {
+ $nr = $sfill.' '.$na;
+ }
+ } else {
+ $nr = $na;
+ }
+ $temppage = str_replace($a, $nr, $temppage);
+ }
+ }
+ // save changes
+ $this->setPageBuffer($p, $temppage);
+ }
+ // move pages
+ $this->Bookmark($toc_name, 0, 0, $page_first, $style, $color);
+ if ($page_fill_start) {
+ $this->movePage($page_last, $page_first);
+ }
+ for ($i = 0; $i < $numpages; ++$i) {
+ $this->movePage($page_last, $page);
+ }
+ }
+ }
+
+ /**
+ * Stores a copy of the current TCPDF object used for undo operation.
+ * @public
+ * @since 4.5.029 (2009-03-19)
+ */
+ public function startTransaction() {
+ if (isset($this->objcopy)) {
+ // remove previous copy
+ $this->commitTransaction();
+ }
+ // record current page number and Y position
+ $this->start_transaction_page = $this->page;
+ $this->start_transaction_y = $this->y;
+ // clone current object
+ $this->objcopy = TCPDF_STATIC::objclone($this);
+ }
+
+ /**
+ * Delete the copy of the current TCPDF object used for undo operation.
+ * @public
+ * @since 4.5.029 (2009-03-19)
+ */
+ public function commitTransaction() {
+ if (isset($this->objcopy)) {
+ $this->objcopy->_destroy(true, true);
+ unset($this->objcopy);
+ }
+ }
+
+ /**
+ * This method allows to undo the latest transaction by returning the latest saved TCPDF object with startTransaction().
+ * @param $self (boolean) if true restores current class object to previous state without the need of reassignment via the returned value.
+ * @return TCPDF object.
+ * @public
+ * @since 4.5.029 (2009-03-19)
+ */
+ public function rollbackTransaction($self=false) {
+ if (isset($this->objcopy)) {
+ if (isset($this->objcopy->diskcache) AND $this->objcopy->diskcache) {
+ // truncate files to previous values
+ foreach ($this->objcopy->cache_file_length as $file => $length) {
+ $file = substr($file, 1);
+ $handle = fopen($file, 'r+');
+ ftruncate($handle, $length);
+ }
+ }
+ $this->_destroy(true, true);
+ if ($self) {
+ $objvars = get_object_vars($this->objcopy);
+ foreach ($objvars as $key => $value) {
+ $this->$key = $value;
+ }
+ }
+ return $this->objcopy;
+ }
+ return $this;
+ }
+
+ // --- MULTI COLUMNS METHODS -----------------------
+
+ /**
+ * Set multiple columns of the same size
+ * @param $numcols (int) number of columns (set to zero to disable columns mode)
+ * @param $width (int) column width
+ * @param $y (int) column starting Y position (leave empty for current Y position)
+ * @public
+ * @since 4.9.001 (2010-03-28)
+ */
+ public function setEqualColumns($numcols=0, $width=0, $y='') {
+ $this->columns = array();
+ if ($numcols < 2) {
+ $numcols = 0;
+ $this->columns = array();
+ } else {
+ // maximum column width
+ $maxwidth = ($this->w - $this->original_lMargin - $this->original_rMargin) / $numcols;
+ if (($width == 0) OR ($width > $maxwidth)) {
+ $width = $maxwidth;
+ }
+ if (TCPDF_STATIC::empty_string($y)) {
+ $y = $this->y;
+ }
+ // space between columns
+ $space = (($this->w - $this->original_lMargin - $this->original_rMargin - ($numcols * $width)) / ($numcols - 1));
+ // fill the columns array (with, space, starting Y position)
+ for ($i = 0; $i < $numcols; ++$i) {
+ $this->columns[$i] = array('w' => $width, 's' => $space, 'y' => $y);
+ }
+ }
+ $this->num_columns = $numcols;
+ $this->current_column = 0;
+ $this->column_start_page = $this->page;
+ $this->selectColumn(0);
+ }
+
+ /**
+ * Remove columns and reset page margins.
+ * @public
+ * @since 5.9.072 (2011-04-26)
+ */
+ public function resetColumns() {
+ $this->lMargin = $this->original_lMargin;
+ $this->rMargin = $this->original_rMargin;
+ $this->setEqualColumns();
+ }
+
+ /**
+ * Set columns array.
+ * Each column is represented by an array of arrays with the following keys: (w = width, s = space between columns, y = column top position).
+ * @param $columns (array)
+ * @public
+ * @since 4.9.001 (2010-03-28)
+ */
+ public function setColumnsArray($columns) {
+ $this->columns = $columns;
+ $this->num_columns = count($columns);
+ $this->current_column = 0;
+ $this->column_start_page = $this->page;
+ $this->selectColumn(0);
+ }
+
+ /**
+ * Set position at a given column
+ * @param $col (int) column number (from 0 to getNumberOfColumns()-1); empty string = current column.
+ * @public
+ * @since 4.9.001 (2010-03-28)
+ */
+ public function selectColumn($col='') {
+ if (is_string($col)) {
+ $col = $this->current_column;
+ } elseif ($col >= $this->num_columns) {
+ $col = 0;
+ }
+ $xshift = array('x' => 0, 's' => array('H' => 0, 'V' => 0), 'p' => array('L' => 0, 'T' => 0, 'R' => 0, 'B' => 0));
+ $enable_thead = false;
+ if ($this->num_columns > 1) {
+ if ($col != $this->current_column) {
+ // move Y pointer at the top of the column
+ if ($this->column_start_page == $this->page) {
+ $this->y = $this->columns[$col]['y'];
+ } else {
+ $this->y = $this->tMargin;
+ }
+ // Avoid to write table headers more than once
+ if (($this->page > $this->maxselcol['page']) OR (($this->page == $this->maxselcol['page']) AND ($col > $this->maxselcol['column']))) {
+ $enable_thead = true;
+ $this->maxselcol['page'] = $this->page;
+ $this->maxselcol['column'] = $col;
+ }
+ }
+ $xshift = $this->colxshift;
+ // set X position of the current column by case
+ $listindent = ($this->listindentlevel * $this->listindent);
+ // calculate column X position
+ $colpos = 0;
+ for ($i = 0; $i < $col; ++$i) {
+ $colpos += ($this->columns[$i]['w'] + $this->columns[$i]['s']);
+ }
+ if ($this->rtl) {
+ $x = $this->w - $this->original_rMargin - $colpos;
+ $this->rMargin = ($this->w - $x + $listindent);
+ $this->lMargin = ($x - $this->columns[$col]['w']);
+ $this->x = $x - $listindent;
+ } else {
+ $x = $this->original_lMargin + $colpos;
+ $this->lMargin = ($x + $listindent);
+ $this->rMargin = ($this->w - $x - $this->columns[$col]['w']);
+ $this->x = $x + $listindent;
+ }
+ $this->columns[$col]['x'] = $x;
+ }
+ $this->current_column = $col;
+ // fix for HTML mode
+ $this->newline = true;
+ // print HTML table header (if any)
+ if ((!TCPDF_STATIC::empty_string($this->thead)) AND (!$this->inthead)) {
+ if ($enable_thead) {
+ // print table header
+ $this->writeHTML($this->thead, false, false, false, false, '');
+ $this->y += $xshift['s']['V'];
+ // store end of header position
+ if (!isset($this->columns[$col]['th'])) {
+ $this->columns[$col]['th'] = array();
+ }
+ $this->columns[$col]['th']['\''.$this->page.'\''] = $this->y;
+ $this->lasth = 0;
+ } elseif (isset($this->columns[$col]['th']['\''.$this->page.'\''])) {
+ $this->y = $this->columns[$col]['th']['\''.$this->page.'\''];
+ }
+ }
+ // account for an html table cell over multiple columns
+ if ($this->rtl) {
+ $this->rMargin += $xshift['x'];
+ $this->x -= ($xshift['x'] + $xshift['p']['R']);
+ } else {
+ $this->lMargin += $xshift['x'];
+ $this->x += $xshift['x'] + $xshift['p']['L'];
+ }
+ }
+
+ /**
+ * Return the current column number
+ * @return int current column number
+ * @public
+ * @since 5.5.011 (2010-07-08)
+ */
+ public function getColumn() {
+ return $this->current_column;
+ }
+
+ /**
+ * Return the current number of columns.
+ * @return int number of columns
+ * @public
+ * @since 5.8.018 (2010-08-25)
+ */
+ public function getNumberOfColumns() {
+ return $this->num_columns;
+ }
+
+ /**
+ * Set Text rendering mode.
+ * @param $stroke (int) outline size in user units (0 = disable).
+ * @param $fill (boolean) if true fills the text (default).
+ * @param $clip (boolean) if true activate clipping mode
+ * @public
+ * @since 4.9.008 (2009-04-02)
+ */
+ public function setTextRenderingMode($stroke=0, $fill=true, $clip=false) {
+ // Ref.: PDF 32000-1:2008 - 9.3.6 Text Rendering Mode
+ // convert text rendering parameters
+ if ($stroke < 0) {
+ $stroke = 0;
+ }
+ if ($fill === true) {
+ if ($stroke > 0) {
+ if ($clip === true) {
+ // Fill, then stroke text and add to path for clipping
+ $textrendermode = 6;
+ } else {
+ // Fill, then stroke text
+ $textrendermode = 2;
+ }
+ $textstrokewidth = $stroke;
+ } else {
+ if ($clip === true) {
+ // Fill text and add to path for clipping
+ $textrendermode = 4;
+ } else {
+ // Fill text
+ $textrendermode = 0;
+ }
+ }
+ } else {
+ if ($stroke > 0) {
+ if ($clip === true) {
+ // Stroke text and add to path for clipping
+ $textrendermode = 5;
+ } else {
+ // Stroke text
+ $textrendermode = 1;
+ }
+ $textstrokewidth = $stroke;
+ } else {
+ if ($clip === true) {
+ // Add text to path for clipping
+ $textrendermode = 7;
+ } else {
+ // Neither fill nor stroke text (invisible)
+ $textrendermode = 3;
+ }
+ }
+ }
+ $this->textrendermode = $textrendermode;
+ $this->textstrokewidth = $stroke;
+ }
+
+ /**
+ * Set parameters for drop shadow effect for text.
+ * @param $params (array) Array of parameters: enabled (boolean) set to true to enable shadow; depth_w (float) shadow width in user units; depth_h (float) shadow height in user units; color (array) shadow color or false to use the stroke color; opacity (float) Alpha value: real value from 0 (transparent) to 1 (opaque); blend_mode (string) blend mode, one of the following: Normal, Multiply, Screen, Overlay, Darken, Lighten, ColorDodge, ColorBurn, HardLight, SoftLight, Difference, Exclusion, Hue, Saturation, Color, Luminosity.
+ * @since 5.9.174 (2012-07-25)
+ * @public
+ */
+ public function setTextShadow($params=array('enabled'=>false, 'depth_w'=>0, 'depth_h'=>0, 'color'=>false, 'opacity'=>1, 'blend_mode'=>'Normal')) {
+ if (isset($params['enabled'])) {
+ $this->txtshadow['enabled'] = $params['enabled']?true:false;
+ } else {
+ $this->txtshadow['enabled'] = false;
+ }
+ if (isset($params['depth_w'])) {
+ $this->txtshadow['depth_w'] = floatval($params['depth_w']);
+ } else {
+ $this->txtshadow['depth_w'] = 0;
+ }
+ if (isset($params['depth_h'])) {
+ $this->txtshadow['depth_h'] = floatval($params['depth_h']);
+ } else {
+ $this->txtshadow['depth_h'] = 0;
+ }
+ if (isset($params['color']) AND ($params['color'] !== false) AND is_array($params['color'])) {
+ $this->txtshadow['color'] = $params['color'];
+ } else {
+ $this->txtshadow['color'] = $this->strokecolor;
+ }
+ if (isset($params['opacity'])) {
+ $this->txtshadow['opacity'] = min(1, max(0, floatval($params['opacity'])));
+ } else {
+ $this->txtshadow['opacity'] = 1;
+ }
+ if (isset($params['blend_mode']) AND in_array($params['blend_mode'], array('Normal', 'Multiply', 'Screen', 'Overlay', 'Darken', 'Lighten', 'ColorDodge', 'ColorBurn', 'HardLight', 'SoftLight', 'Difference', 'Exclusion', 'Hue', 'Saturation', 'Color', 'Luminosity'))) {
+ $this->txtshadow['blend_mode'] = $params['blend_mode'];
+ } else {
+ $this->txtshadow['blend_mode'] = 'Normal';
+ }
+ if ((($this->txtshadow['depth_w'] == 0) AND ($this->txtshadow['depth_h'] == 0)) OR ($this->txtshadow['opacity'] == 0)) {
+ $this->txtshadow['enabled'] = false;
+ }
+ }
+
+ /**
+ * Return the text shadow parameters array.
+ * @return Array of parameters.
+ * @since 5.9.174 (2012-07-25)
+ * @public
+ */
+ public function getTextShadow() {
+ return $this->txtshadow;
+ }
+
+ /**
+ * Returns an array of chars containing soft hyphens.
+ * @param $word (array) array of chars
+ * @param $patterns (array) Array of hypenation patterns.
+ * @param $dictionary (array) Array of words to be returned without applying the hyphenation algoritm.
+ * @param $leftmin (int) Minimum number of character to leave on the left of the word without applying the hyphens.
+ * @param $rightmin (int) Minimum number of character to leave on the right of the word without applying the hyphens.
+ * @param $charmin (int) Minimum word length to apply the hyphenation algoritm.
+ * @param $charmax (int) Maximum length of broken piece of word.
+ * @return array text with soft hyphens
+ * @author Nicola Asuni
+ * @since 4.9.012 (2010-04-12)
+ * @protected
+ */
+ protected function hyphenateWord($word, $patterns, $dictionary=array(), $leftmin=1, $rightmin=2, $charmin=1, $charmax=8) {
+ $hyphenword = array(); // hyphens positions
+ $numchars = count($word);
+ if ($numchars <= $charmin) {
+ return $word;
+ }
+ $word_string = TCPDF_FONTS::UTF8ArrSubString($word, '', '', $this->isunicode);
+ // some words will be returned as-is
+ $pattern = '/^([a-zA-Z0-9_\.\-]+)@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.)|(([a-zA-Z0-9\-]+\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\]?)$/';
+ if (preg_match($pattern, $word_string) > 0) {
+ // email
+ return $word;
+ }
+ $pattern = '/(([a-zA-Z0-9\-]+\.)?)((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.)|(([a-zA-Z0-9\-]+\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\]?)$/';
+ if (preg_match($pattern, $word_string) > 0) {
+ // URL
+ return $word;
+ }
+ if (isset($dictionary[$word_string])) {
+ return TCPDF_FONTS::UTF8StringToArray($dictionary[$word_string], $this->isunicode, $this->CurrentFont);
+ }
+ // suround word with '_' characters
+ $tmpword = array_merge(array(95), $word, array(95));
+ $tmpnumchars = $numchars + 2;
+ $maxpos = $tmpnumchars - $charmin;
+ for ($pos = 0; $pos < $maxpos; ++$pos) {
+ $imax = min(($tmpnumchars - $pos), $charmax);
+ for ($i = $charmin; $i <= $imax; ++$i) {
+ $subword = strtolower(TCPDF_FONTS::UTF8ArrSubString($tmpword, $pos, ($pos + $i), $this->isunicode));
+ if (isset($patterns[$subword])) {
+ $pattern = TCPDF_FONTS::UTF8StringToArray($patterns[$subword], $this->isunicode, $this->CurrentFont);
+ $pattern_length = count($pattern);
+ $digits = 1;
+ for ($j = 0; $j < $pattern_length; ++$j) {
+ // check if $pattern[$j] is a number
+ if (($pattern[$j] >= 48) AND ($pattern[$j] <= 57)) {
+ if ($j == 0) {
+ $zero = $pos - 1;
+ } else {
+ $zero = $pos + $j - $digits;
+ }
+ if (!isset($hyphenword[$zero]) OR ($hyphenword[$zero] != $pattern[$j])) {
+ $hyphenword[$zero] = TCPDF_FONTS::unichr($pattern[$j], $this->isunicode);
+ }
+ ++$digits;
+ }
+ }
+ }
+ }
+ }
+ $inserted = 0;
+ $maxpos = $numchars - $rightmin;
+ for ($i = $leftmin; $i <= $maxpos; ++$i) {
+ if (isset($hyphenword[$i]) AND (($hyphenword[$i] % 2) != 0)) {
+ // 173 = soft hyphen character
+ array_splice($word, $i + $inserted, 0, 173);
+ ++$inserted;
+ }
+ }
+ return $word;
+ }
+
+ /**
+ * Returns text with soft hyphens.
+ * @param $text (string) text to process
+ * @param $patterns (mixed) Array of hypenation patterns or a TEX file containing hypenation patterns. TEX patterns can be downloaded from http://www.ctan.org/tex-archive/language/hyph-utf8/tex/generic/hyph-utf8/patterns/
+ * @param $dictionary (array) Array of words to be returned without applying the hyphenation algoritm.
+ * @param $leftmin (int) Minimum number of character to leave on the left of the word without applying the hyphens.
+ * @param $rightmin (int) Minimum number of character to leave on the right of the word without applying the hyphens.
+ * @param $charmin (int) Minimum word length to apply the hyphenation algoritm.
+ * @param $charmax (int) Maximum length of broken piece of word.
+ * @return array text with soft hyphens
+ * @author Nicola Asuni
+ * @since 4.9.012 (2010-04-12)
+ * @public
+ */
+ public function hyphenateText($text, $patterns, $dictionary=array(), $leftmin=1, $rightmin=2, $charmin=1, $charmax=8) {
+ $text = $this->unhtmlentities($text);
+ $word = array(); // last word
+ $txtarr = array(); // text to be returned
+ $intag = false; // true if we are inside an HTML tag
+ if (!is_array($patterns)) {
+ $patterns = TCPDF_STATIC::getHyphenPatternsFromTEX($patterns);
+ }
+ // get array of characters
+ $unichars = TCPDF_FONTS::UTF8StringToArray($text, $this->isunicode, $this->CurrentFont);
+ // for each char
+ foreach ($unichars as $char) {
+ if ((!$intag) AND TCPDF_FONT_DATA::$uni_type[$char] == 'L') {
+ // letter character
+ $word[] = $char;
+ } else {
+ // other type of character
+ if (!TCPDF_STATIC::empty_string($word)) {
+ // hypenate the word
+ $txtarr = array_merge($txtarr, $this->hyphenateWord($word, $patterns, $dictionary, $leftmin, $rightmin, $charmin, $charmax));
+ $word = array();
+ }
+ $txtarr[] = $char;
+ if (chr($char) == '<') {
+ // we are inside an HTML tag
+ $intag = true;
+ } elseif ($intag AND (chr($char) == '>')) {
+ // end of HTML tag
+ $intag = false;
+ }
+ }
+ }
+ if (!TCPDF_STATIC::empty_string($word)) {
+ // hypenate the word
+ $txtarr = array_merge($txtarr, $this->hyphenateWord($word, $patterns, $dictionary, $leftmin, $rightmin, $charmin, $charmax));
+ }
+ // convert char array to string and return
+ return TCPDF_FONTS::UTF8ArrSubString($txtarr, '', '', $this->isunicode);
+ }
+
+ /**
+ * Enable/disable rasterization of vector images using ImageMagick library.
+ * @param $mode (boolean) if true enable rasterization, false otherwise.
+ * @public
+ * @since 5.0.000 (2010-04-27)
+ */
+ public function setRasterizeVectorImages($mode) {
+ $this->rasterize_vector_images = $mode;
+ }
+
+ /**
+ * Enable or disable default option for font subsetting.
+ * @param $enable (boolean) if true enable font subsetting by default.
+ * @author Nicola Asuni
+ * @public
+ * @since 5.3.002 (2010-06-07)
+ */
+ public function setFontSubsetting($enable=true) {
+ if ($this->pdfa_mode) {
+ $this->font_subsetting = false;
+ } else {
+ $this->font_subsetting = $enable ? true : false;
+ }
+ }
+
+ /**
+ * Return the default option for font subsetting.
+ * @return boolean default font subsetting state.
+ * @author Nicola Asuni
+ * @public
+ * @since 5.3.002 (2010-06-07)
+ */
+ public function getFontSubsetting() {
+ return $this->font_subsetting;
+ }
+
+ /**
+ * Left trim the input string
+ * @param $str (string) string to trim
+ * @param $replace (string) string that replace spaces.
+ * @return left trimmed string
+ * @author Nicola Asuni
+ * @public
+ * @since 5.8.000 (2010-08-11)
+ */
+ public function stringLeftTrim($str, $replace='') {
+ return preg_replace('/^'.$this->re_space['p'].'+/'.$this->re_space['m'], $replace, $str);
+ }
+
+ /**
+ * Right trim the input string
+ * @param $str (string) string to trim
+ * @param $replace (string) string that replace spaces.
+ * @return right trimmed string
+ * @author Nicola Asuni
+ * @public
+ * @since 5.8.000 (2010-08-11)
+ */
+ public function stringRightTrim($str, $replace='') {
+ return preg_replace('/'.$this->re_space['p'].'+$/'.$this->re_space['m'], $replace, $str);
+ }
+
+ /**
+ * Trim the input string
+ * @param $str (string) string to trim
+ * @param $replace (string) string that replace spaces.
+ * @return trimmed string
+ * @author Nicola Asuni
+ * @public
+ * @since 5.8.000 (2010-08-11)
+ */
+ public function stringTrim($str, $replace='') {
+ $str = $this->stringLeftTrim($str, $replace);
+ $str = $this->stringRightTrim($str, $replace);
+ return $str;
+ }
+
+ /**
+ * Return true if the current font is unicode type.
+ * @return true for unicode font, false otherwise.
+ * @author Nicola Asuni
+ * @public
+ * @since 5.8.002 (2010-08-14)
+ */
+ public function isUnicodeFont() {
+ return (($this->CurrentFont['type'] == 'TrueTypeUnicode') OR ($this->CurrentFont['type'] == 'cidfont0'));
+ }
+
+ /**
+ * Return normalized font name
+ * @param $fontfamily (string) property string containing font family names
+ * @return string normalized font name
+ * @author Nicola Asuni
+ * @public
+ * @since 5.8.004 (2010-08-17)
+ */
+ public function getFontFamilyName($fontfamily) {
+ // remove spaces and symbols
+ $fontfamily = preg_replace('/[^a-z0-9_\,]/', '', strtolower($fontfamily));
+ // extract all font names
+ $fontslist = preg_split('/[,]/', $fontfamily);
+ // find first valid font name
+ foreach ($fontslist as $font) {
+ // replace font variations
+ $font = preg_replace('/italic$/', 'I', $font);
+ $font = preg_replace('/oblique$/', 'I', $font);
+ $font = preg_replace('/bold([I]?)$/', 'B\\1', $font);
+ // replace common family names and core fonts
+ $pattern = array();
+ $replacement = array();
+ $pattern[] = '/^serif|^cursive|^fantasy|^timesnewroman/';
+ $replacement[] = 'times';
+ $pattern[] = '/^sansserif/';
+ $replacement[] = 'helvetica';
+ $pattern[] = '/^monospace/';
+ $replacement[] = 'courier';
+ $font = preg_replace($pattern, $replacement, $font);
+ if (in_array(strtolower($font), $this->fontlist) OR in_array($font, $this->fontkeys)) {
+ return $font;
+ }
+ }
+ // return current font as default
+ return $this->CurrentFont['fontkey'];
+ }
+
+ /**
+ * Start a new XObject Template.
+ * An XObject Template is a PDF block that is a self-contained description of any sequence of graphics objects (including path objects, text objects, and sampled images).
+ * An XObject Template may be painted multiple times, either on several pages or at several locations on the same page and produces the same results each time, subject only to the graphics state at the time it is invoked.
+ * Note: X,Y coordinates will be reset to 0,0.
+ * @param $w (int) Template width in user units (empty string or zero = page width less margins).
+ * @param $h (int) Template height in user units (empty string or zero = page height less margins).
+ * @param $group (mixed) Set transparency group. Can be a boolean value or an array specifying optional parameters: 'CS' (solour space name), 'I' (boolean flag to indicate isolated group) and 'K' (boolean flag to indicate knockout group).
+ * @return int the XObject Template ID in case of success or false in case of error.
+ * @author Nicola Asuni
+ * @public
+ * @since 5.8.017 (2010-08-24)
+ * @see endTemplate(), printTemplate()
+ */
+ public function startTemplate($w=0, $h=0, $group=false) {
+ if ($this->inxobj) {
+ // we are already inside an XObject template
+ return false;
+ }
+ $this->inxobj = true;
+ ++$this->n;
+ // XObject ID
+ $this->xobjid = 'XT'.$this->n;
+ // object ID
+ $this->xobjects[$this->xobjid] = array('n' => $this->n);
+ // store current graphic state
+ $this->xobjects[$this->xobjid]['gvars'] = $this->getGraphicVars();
+ // initialize data
+ $this->xobjects[$this->xobjid]['intmrk'] = 0;
+ $this->xobjects[$this->xobjid]['transfmrk'] = array();
+ $this->xobjects[$this->xobjid]['outdata'] = '';
+ $this->xobjects[$this->xobjid]['xobjects'] = array();
+ $this->xobjects[$this->xobjid]['images'] = array();
+ $this->xobjects[$this->xobjid]['fonts'] = array();
+ $this->xobjects[$this->xobjid]['annotations'] = array();
+ $this->xobjects[$this->xobjid]['extgstates'] = array();
+ $this->xobjects[$this->xobjid]['gradients'] = array();
+ $this->xobjects[$this->xobjid]['spot_colors'] = array();
+ // set new environment
+ $this->num_columns = 1;
+ $this->current_column = 0;
+ $this->SetAutoPageBreak(false);
+ if (($w === '') OR ($w <= 0)) {
+ $w = $this->w - $this->lMargin - $this->rMargin;
+ }
+ if (($h === '') OR ($h <= 0)) {
+ $h = $this->h - $this->tMargin - $this->bMargin;
+ }
+ $this->xobjects[$this->xobjid]['x'] = 0;
+ $this->xobjects[$this->xobjid]['y'] = 0;
+ $this->xobjects[$this->xobjid]['w'] = $w;
+ $this->xobjects[$this->xobjid]['h'] = $h;
+ $this->w = $w;
+ $this->h = $h;
+ $this->wPt = $this->w * $this->k;
+ $this->hPt = $this->h * $this->k;
+ $this->fwPt = $this->wPt;
+ $this->fhPt = $this->hPt;
+ $this->x = 0;
+ $this->y = 0;
+ $this->lMargin = 0;
+ $this->rMargin = 0;
+ $this->tMargin = 0;
+ $this->bMargin = 0;
+ // set group mode
+ $this->xobjects[$this->xobjid]['group'] = $group;
+ return $this->xobjid;
+ }
+
+ /**
+ * End the current XObject Template started with startTemplate() and restore the previous graphic state.
+ * An XObject Template is a PDF block that is a self-contained description of any sequence of graphics objects (including path objects, text objects, and sampled images).
+ * An XObject Template may be painted multiple times, either on several pages or at several locations on the same page and produces the same results each time, subject only to the graphics state at the time it is invoked.
+ * @return int the XObject Template ID in case of success or false in case of error.
+ * @author Nicola Asuni
+ * @public
+ * @since 5.8.017 (2010-08-24)
+ * @see startTemplate(), printTemplate()
+ */
+ public function endTemplate() {
+ if (!$this->inxobj) {
+ // we are not inside a template
+ return false;
+ }
+ $this->inxobj = false;
+ // restore previous graphic state
+ $this->setGraphicVars($this->xobjects[$this->xobjid]['gvars'], true);
+ return $this->xobjid;
+ }
+
+ /**
+ * Print an XObject Template.
+ * You can print an XObject Template inside the currently opened Template.
+ * An XObject Template is a PDF block that is a self-contained description of any sequence of graphics objects (including path objects, text objects, and sampled images).
+ * An XObject Template may be painted multiple times, either on several pages or at several locations on the same page and produces the same results each time, subject only to the graphics state at the time it is invoked.
+ * @param $id (string) The ID of XObject Template to print.
+ * @param $x (int) X position in user units (empty string = current x position)
+ * @param $y (int) Y position in user units (empty string = current y position)
+ * @param $w (int) Width in user units (zero = remaining page width)
+ * @param $h (int) Height in user units (zero = remaining page height)
+ * @param $align (string) Indicates the alignment of the pointer next to template insertion relative to template height. The value can be:<ul><li>T: top-right for LTR or top-left for RTL</li><li>M: middle-right for LTR or middle-left for RTL</li><li>B: bottom-right for LTR or bottom-left for RTL</li><li>N: next line</li></ul>
+ * @param $palign (string) Allows to center or align the template on the current line. Possible values are:<ul><li>L : left align</li><li>C : center</li><li>R : right align</li><li>'' : empty string : left for LTR or right for RTL</li></ul>
+ * @param $fitonpage (boolean) If true the template is resized to not exceed page dimensions.
+ * @author Nicola Asuni
+ * @public
+ * @since 5.8.017 (2010-08-24)
+ * @see startTemplate(), endTemplate()
+ */
+ public function printTemplate($id, $x='', $y='', $w=0, $h=0, $align='', $palign='', $fitonpage=false) {
+ if ($this->state != 2) {
+ return;
+ }
+ if (!isset($this->xobjects[$id])) {
+ $this->Error('The XObject Template \''.$id.'\' doesn\'t exist!');
+ }
+ if ($this->inxobj) {
+ if ($id == $this->xobjid) {
+ // close current template
+ $this->endTemplate();
+ } else {
+ // use the template as resource for the template currently opened
+ $this->xobjects[$this->xobjid]['xobjects'][$id] = $this->xobjects[$id];
+ }
+ }
+ // set default values
+ if ($x === '') {
+ $x = $this->x;
+ }
+ if ($y === '') {
+ $y = $this->y;
+ }
+ // check page for no-write regions and adapt page margins if necessary
+ list($x, $y) = $this->checkPageRegions($h, $x, $y);
+ $ow = $this->xobjects[$id]['w'];
+ if ($ow <= 0) {
+ $ow = 1;
+ }
+ $oh = $this->xobjects[$id]['h'];
+ if ($oh <= 0) {
+ $oh = 1;
+ }
+ // calculate template width and height on document
+ if (($w <= 0) AND ($h <= 0)) {
+ $w = $ow;
+ $h = $oh;
+ } elseif ($w <= 0) {
+ $w = $h * $ow / $oh;
+ } elseif ($h <= 0) {
+ $h = $w * $oh / $ow;
+ }
+ // fit the template on available space
+ list($w, $h, $x, $y) = $this->fitBlock($w, $h, $x, $y, $fitonpage);
+ // set page alignment
+ $rb_y = $y + $h;
+ // set alignment
+ if ($this->rtl) {
+ if ($palign == 'L') {
+ $xt = $this->lMargin;
+ } elseif ($palign == 'C') {
+ $xt = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
+ } elseif ($palign == 'R') {
+ $xt = $this->w - $this->rMargin - $w;
+ } else {
+ $xt = $x - $w;
+ }
+ $rb_x = $xt;
+ } else {
+ if ($palign == 'L') {
+ $xt = $this->lMargin;
+ } elseif ($palign == 'C') {
+ $xt = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
+ } elseif ($palign == 'R') {
+ $xt = $this->w - $this->rMargin - $w;
+ } else {
+ $xt = $x;
+ }
+ $rb_x = $xt + $w;
+ }
+ // print XObject Template + Transformation matrix
+ $this->StartTransform();
+ // translate and scale
+ $sx = ($w / $ow);
+ $sy = ($h / $oh);
+ $tm = array();
+ $tm[0] = $sx;
+ $tm[1] = 0;
+ $tm[2] = 0;
+ $tm[3] = $sy;
+ $tm[4] = $xt * $this->k;
+ $tm[5] = ($this->h - $h - $y) * $this->k;
+ $this->Transform($tm);
+ // set object
+ $this->_out('/'.$id.' Do');
+ $this->StopTransform();
+ // add annotations
+ if (!empty($this->xobjects[$id]['annotations'])) {
+ foreach ($this->xobjects[$id]['annotations'] as $annot) {
+ // transform original coordinates
+ $coordlt = TCPDF_STATIC::getTransformationMatrixProduct($tm, array(1, 0, 0, 1, ($annot['x'] * $this->k), (-$annot['y'] * $this->k)));
+ $ax = ($coordlt[4] / $this->k);
+ $ay = ($this->h - $h - ($coordlt[5] / $this->k));
+ $coordrb = TCPDF_STATIC::getTransformationMatrixProduct($tm, array(1, 0, 0, 1, (($annot['x'] + $annot['w']) * $this->k), ((-$annot['y'] - $annot['h']) * $this->k)));
+ $aw = ($coordrb[4] / $this->k) - $ax;
+ $ah = ($this->h - $h - ($coordrb[5] / $this->k)) - $ay;
+ $this->Annotation($ax, $ay, $aw, $ah, $annot['text'], $annot['opt'], $annot['spaces']);
+ }
+ }
+ // set pointer to align the next text/objects
+ switch($align) {
+ case 'T': {
+ $this->y = $y;
+ $this->x = $rb_x;
+ break;
+ }
+ case 'M': {
+ $this->y = $y + round($h/2);
+ $this->x = $rb_x;
+ break;
+ }
+ case 'B': {
+ $this->y = $rb_y;
+ $this->x = $rb_x;
+ break;
+ }
+ case 'N': {
+ $this->SetY($rb_y);
+ break;
+ }
+ default:{
+ break;
+ }
+ }
+ }
+
+ /**
+ * Set the percentage of character stretching.
+ * @param $perc (int) percentage of stretching (100 = no stretching)
+ * @author Nicola Asuni
+ * @public
+ * @since 5.9.000 (2010-09-29)
+ */
+ public function setFontStretching($perc=100) {
+ $this->font_stretching = $perc;
+ }
+
+ /**
+ * Get the percentage of character stretching.
+ * @return float stretching value
+ * @author Nicola Asuni
+ * @public
+ * @since 5.9.000 (2010-09-29)
+ */
+ public function getFontStretching() {
+ return $this->font_stretching;
+ }
+
+ /**
+ * Set the amount to increase or decrease the space between characters in a text.
+ * @param $spacing (float) amount to increase or decrease the space between characters in a text (0 = default spacing)
+ * @author Nicola Asuni
+ * @public
+ * @since 5.9.000 (2010-09-29)
+ */
+ public function setFontSpacing($spacing=0) {
+ $this->font_spacing = $spacing;
+ }
+
+ /**
+ * Get the amount to increase or decrease the space between characters in a text.
+ * @return int font spacing (tracking) value
+ * @author Nicola Asuni
+ * @public
+ * @since 5.9.000 (2010-09-29)
+ */
+ public function getFontSpacing() {
+ return $this->font_spacing;
+ }
+
+ /**
+ * Return an array of no-write page regions
+ * @return array of no-write page regions
+ * @author Nicola Asuni
+ * @public
+ * @since 5.9.003 (2010-10-13)
+ * @see setPageRegions(), addPageRegion()
+ */
+ public function getPageRegions() {
+ return $this->page_regions;
+ }
+
+ /**
+ * Set no-write regions on page.
+ * A no-write region is a portion of the page with a rectangular or trapezium shape that will not be covered when writing text or html code.
+ * A region is always aligned on the left or right side of the page ad is defined using a vertical segment.
+ * You can set multiple regions for the same page.
+ * @param $regions (array) array of no-write regions. For each region you can define an array as follow: ('page' => page number or empy for current page, 'xt' => X top, 'yt' => Y top, 'xb' => X bottom, 'yb' => Y bottom, 'side' => page side 'L' = left or 'R' = right). Omit this parameter to remove all regions.
+ * @author Nicola Asuni
+ * @public
+ * @since 5.9.003 (2010-10-13)
+ * @see addPageRegion(), getPageRegions()
+ */
+ public function setPageRegions($regions=array()) {
+ // empty current regions array
+ $this->page_regions = array();
+ // add regions
+ foreach ($regions as $data) {
+ $this->addPageRegion($data);
+ }
+ }
+
+ /**
+ * Add a single no-write region on selected page.
+ * A no-write region is a portion of the page with a rectangular or trapezium shape that will not be covered when writing text or html code.
+ * A region is always aligned on the left or right side of the page ad is defined using a vertical segment.
+ * You can set multiple regions for the same page.
+ * @param $region (array) array of a single no-write region array: ('page' => page number or empy for current page, 'xt' => X top, 'yt' => Y top, 'xb' => X bottom, 'yb' => Y bottom, 'side' => page side 'L' = left or 'R' = right).
+ * @author Nicola Asuni
+ * @public
+ * @since 5.9.003 (2010-10-13)
+ * @see setPageRegions(), getPageRegions()
+ */
+ public function addPageRegion($region) {
+ if (!isset($region['page']) OR empty($region['page'])) {
+ $region['page'] = $this->page;
+ }
+ if (isset($region['xt']) AND isset($region['xb']) AND ($region['xt'] > 0) AND ($region['xb'] > 0)
+ AND isset($region['yt']) AND isset($region['yb']) AND ($region['yt'] >= 0) AND ($region['yt'] < $region['yb'])
+ AND isset($region['side']) AND (($region['side'] == 'L') OR ($region['side'] == 'R'))) {
+ $this->page_regions[] = $region;
+ }
+ }
+
+ /**
+ * Remove a single no-write region.
+ * @param $key (int) region key
+ * @author Nicola Asuni
+ * @public
+ * @since 5.9.003 (2010-10-13)
+ * @see setPageRegions(), getPageRegions()
+ */
+ public function removePageRegion($key) {
+ if (isset($this->page_regions[$key])) {
+ unset($this->page_regions[$key]);
+ }
+ }
+
+ /**
+ * Check page for no-write regions and adapt current coordinates and page margins if necessary.
+ * A no-write region is a portion of the page with a rectangular or trapezium shape that will not be covered when writing text or html code.
+ * A region is always aligned on the left or right side of the page ad is defined using a vertical segment.
+ * @param $h (float) height of the text/image/object to print in user units
+ * @param $x (float) current X coordinate in user units
+ * @param $y (float) current Y coordinate in user units
+ * @return array($x, $y)
+ * @author Nicola Asuni
+ * @protected
+ * @since 5.9.003 (2010-10-13)
+ */
+ protected function checkPageRegions($h, $x, $y) {
+ // set default values
+ if ($x === '') {
+ $x = $this->x;
+ }
+ if ($y === '') {
+ $y = $this->y;
+ }
+ if (!$this->check_page_regions OR empty($this->page_regions)) {
+ // no page regions defined
+ return array($x, $y);
+ }
+ if (empty($h)) {
+ $h = ($this->FontSize * $this->cell_height_ratio) + $this->cell_padding['T'] + $this->cell_padding['B'];
+ }
+ // check for page break
+ if ($this->checkPageBreak($h, $y)) {
+ // the content will be printed on a new page
+ $x = $this->x;
+ $y = $this->y;
+ }
+ if ($this->num_columns > 1) {
+ if ($this->rtl) {
+ $this->lMargin = ($this->columns[$this->current_column]['x'] - $this->columns[$this->current_column]['w']);
+ } else {
+ $this->rMargin = ($this->w - $this->columns[$this->current_column]['x'] - $this->columns[$this->current_column]['w']);
+ }
+ } else {
+ if ($this->rtl) {
+ $this->lMargin = max($this->clMargin, $this->original_lMargin);
+ } else {
+ $this->rMargin = max($this->crMargin, $this->original_rMargin);
+ }
+ }
+ // adjust coordinates and page margins
+ foreach ($this->page_regions as $regid => $regdata) {
+ if ($regdata['page'] == $this->page) {
+ // check region boundaries
+ if (($y > ($regdata['yt'] - $h)) AND ($y <= $regdata['yb'])) {
+ // Y is inside the region
+ $minv = ($regdata['xb'] - $regdata['xt']) / ($regdata['yb'] - $regdata['yt']); // inverse of angular coefficient
+ $yt = max($y, $regdata['yt']);
+ $yb = min(($yt + $h), $regdata['yb']);
+ $xt = (($yt - $regdata['yt']) * $minv) + $regdata['xt'];
+ $xb = (($yb - $regdata['yt']) * $minv) + $regdata['xt'];
+ if ($regdata['side'] == 'L') { // left side
+ $new_margin = max($xt, $xb);
+ if ($this->lMargin < $new_margin) {
+ if ($this->rtl) {
+ // adjust left page margin
+ $this->lMargin = max(0, $new_margin);
+ }
+ if ($x < $new_margin) {
+ // adjust x position
+ $x = $new_margin;
+ if ($new_margin > ($this->w - $this->rMargin)) {
+ // adjust y position
+ $y = $regdata['yb'] - $h;
+ }
+ }
+ }
+ } elseif ($regdata['side'] == 'R') { // right side
+ $new_margin = min($xt, $xb);
+ if (($this->w - $this->rMargin) > $new_margin) {
+ if (!$this->rtl) {
+ // adjust right page margin
+ $this->rMargin = max(0, ($this->w - $new_margin));
+ }
+ if ($x > $new_margin) {
+ // adjust x position
+ $x = $new_margin;
+ if ($new_margin > $this->lMargin) {
+ // adjust y position
+ $y = $regdata['yb'] - $h;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ return array($x, $y);
+ }
+
+ // --- SVG METHODS ---------------------------------------------------------
+
+ /**
+ * Embedd a Scalable Vector Graphics (SVG) image.
+ * NOTE: SVG standard is not yet fully implemented, use the setRasterizeVectorImages() method to enable/disable rasterization of vector images using ImageMagick library.
+ * @param $file (string) Name of the SVG file or a '@' character followed by the SVG data string.
+ * @param $x (float) Abscissa of the upper-left corner.
+ * @param $y (float) Ordinate of the upper-left corner.
+ * @param $w (float) Width of the image in the page. If not specified or equal to zero, it is automatically calculated.
+ * @param $h (float) Height of the image in the page. If not specified or equal to zero, it is automatically calculated.
+ * @param $link (mixed) URL or identifier returned by AddLink().
+ * @param $align (string) Indicates the alignment of the pointer next to image insertion relative to image height. The value can be:<ul><li>T: top-right for LTR or top-left for RTL</li><li>M: middle-right for LTR or middle-left for RTL</li><li>B: bottom-right for LTR or bottom-left for RTL</li><li>N: next line</li></ul> If the alignment is an empty string, then the pointer will be restored on the starting SVG position.
+ * @param $palign (string) Allows to center or align the image on the current line. Possible values are:<ul><li>L : left align</li><li>C : center</li><li>R : right align</li><li>'' : empty string : left for LTR or right for RTL</li></ul>
+ * @param $border (mixed) Indicates if borders must be drawn around the cell. The value can be a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul> or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul> or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)))
+ * @param $fitonpage (boolean) if true the image is resized to not exceed page dimensions.
+ * @author Nicola Asuni
+ * @since 5.0.000 (2010-05-02)
+ * @public
+ */
+ public function ImageSVG($file, $x='', $y='', $w=0, $h=0, $link='', $align='', $palign='', $border=0, $fitonpage=false) {
+ if ($this->state != 2) {
+ return;
+ }
+ if ($this->rasterize_vector_images AND ($w > 0) AND ($h > 0)) {
+ // convert SVG to raster image using GD or ImageMagick libraries
+ return $this->Image($file, $x, $y, $w, $h, 'SVG', $link, $align, true, 300, $palign, false, false, $border, false, false, false);
+ }
+ if ($file{0} === '@') { // image from string
+ $this->svgdir = '';
+ $svgdata = substr($file, 1);
+ } else { // SVG file
+ $this->svgdir = dirname($file);
+ $svgdata = TCPDF_STATIC::fileGetContents($file);
+ }
+ if ($svgdata === FALSE) {
+ $this->Error('SVG file not found: '.$file);
+ }
+ if ($x === '') {
+ $x = $this->x;
+ }
+ if ($y === '') {
+ $y = $this->y;
+ }
+ // check page for no-write regions and adapt page margins if necessary
+ list($x, $y) = $this->checkPageRegions($h, $x, $y);
+ $k = $this->k;
+ $ox = 0;
+ $oy = 0;
+ $ow = $w;
+ $oh = $h;
+ $aspect_ratio_align = 'xMidYMid';
+ $aspect_ratio_ms = 'meet';
+ $regs = array();
+ // get original image width and height
+ preg_match('/<svg([^\>]*)>/si', $svgdata, $regs);
+ if (isset($regs[1]) AND !empty($regs[1])) {
+ $tmp = array();
+ if (preg_match('/[\s]+x[\s]*=[\s]*"([^"]*)"/si', $regs[1], $tmp)) {
+ $ox = $this->getHTMLUnitToUnits($tmp[1], 0, $this->svgunit, false);
+ }
+ $tmp = array();
+ if (preg_match('/[\s]+y[\s]*=[\s]*"([^"]*)"/si', $regs[1], $tmp)) {
+ $oy = $this->getHTMLUnitToUnits($tmp[1], 0, $this->svgunit, false);
+ }
+ $tmp = array();
+ if (preg_match('/[\s]+width[\s]*=[\s]*"([^"]*)"/si', $regs[1], $tmp)) {
+ $ow = $this->getHTMLUnitToUnits($tmp[1], 1, $this->svgunit, false);
+ }
+ $tmp = array();
+ if (preg_match('/[\s]+height[\s]*=[\s]*"([^"]*)"/si', $regs[1], $tmp)) {
+ $oh = $this->getHTMLUnitToUnits($tmp[1], 1, $this->svgunit, false);
+ }
+ $tmp = array();
+ $view_box = array();
+ if (preg_match('/[\s]+viewBox[\s]*=[\s]*"[\s]*([0-9\.\-]+)[\s]+([0-9\.\-]+)[\s]+([0-9\.]+)[\s]+([0-9\.]+)[\s]*"/si', $regs[1], $tmp)) {
+ if (count($tmp) == 5) {
+ array_shift($tmp);
+ foreach ($tmp as $key => $val) {
+ $view_box[$key] = $this->getHTMLUnitToUnits($val, 0, $this->svgunit, false);
+ }
+ $ox = $view_box[0];
+ $oy = $view_box[1];
+ }
+ // get aspect ratio
+ $tmp = array();
+ if (preg_match('/[\s]+preserveAspectRatio[\s]*=[\s]*"([^"]*)"/si', $regs[1], $tmp)) {
+ $aspect_ratio = preg_split('/[\s]+/si', $tmp[1]);
+ switch (count($aspect_ratio)) {
+ case 3: {
+ $aspect_ratio_align = $aspect_ratio[1];
+ $aspect_ratio_ms = $aspect_ratio[2];
+ break;
+ }
+ case 2: {
+ $aspect_ratio_align = $aspect_ratio[0];
+ $aspect_ratio_ms = $aspect_ratio[1];
+ break;
+ }
+ case 1: {
+ $aspect_ratio_align = $aspect_ratio[0];
+ $aspect_ratio_ms = 'meet';
+ break;
+ }
+ }
+ }
+ }
+ }
+ if ($ow <= 0) {
+ $ow = 1;
+ }
+ if ($oh <= 0) {
+ $oh = 1;
+ }
+ // calculate image width and height on document
+ if (($w <= 0) AND ($h <= 0)) {
+ // convert image size to document unit
+ $w = $ow;
+ $h = $oh;
+ } elseif ($w <= 0) {
+ $w = $h * $ow / $oh;
+ } elseif ($h <= 0) {
+ $h = $w * $oh / $ow;
+ }
+ // fit the image on available space
+ list($w, $h, $x, $y) = $this->fitBlock($w, $h, $x, $y, $fitonpage);
+ if ($this->rasterize_vector_images) {
+ // convert SVG to raster image using GD or ImageMagick libraries
+ return $this->Image($file, $x, $y, $w, $h, 'SVG', $link, $align, true, 300, $palign, false, false, $border, false, false, false);
+ }
+ // set alignment
+ $this->img_rb_y = $y + $h;
+ // set alignment
+ if ($this->rtl) {
+ if ($palign == 'L') {
+ $ximg = $this->lMargin;
+ } elseif ($palign == 'C') {
+ $ximg = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
+ } elseif ($palign == 'R') {
+ $ximg = $this->w - $this->rMargin - $w;
+ } else {
+ $ximg = $x - $w;
+ }
+ $this->img_rb_x = $ximg;
+ } else {
+ if ($palign == 'L') {
+ $ximg = $this->lMargin;
+ } elseif ($palign == 'C') {
+ $ximg = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
+ } elseif ($palign == 'R') {
+ $ximg = $this->w - $this->rMargin - $w;
+ } else {
+ $ximg = $x;
+ }
+ $this->img_rb_x = $ximg + $w;
+ }
+ // store current graphic vars
+ $gvars = $this->getGraphicVars();
+ // store SVG position and scale factors
+ $svgoffset_x = ($ximg - $ox) * $this->k;
+ $svgoffset_y = -($y - $oy) * $this->k;
+ if (isset($view_box[2]) AND ($view_box[2] > 0) AND ($view_box[3] > 0)) {
+ $ow = $view_box[2];
+ $oh = $view_box[3];
+ } else {
+ if ($ow <= 0) {
+ $ow = $w;
+ }
+ if ($oh <= 0) {
+ $oh = $h;
+ }
+ }
+ $svgscale_x = $w / $ow;
+ $svgscale_y = $h / $oh;
+ // scaling and alignment
+ if ($aspect_ratio_align != 'none') {
+ // store current scaling values
+ $svgscale_old_x = $svgscale_x;
+ $svgscale_old_y = $svgscale_y;
+ // force uniform scaling
+ if ($aspect_ratio_ms == 'slice') {
+ // the entire viewport is covered by the viewBox
+ if ($svgscale_x > $svgscale_y) {
+ $svgscale_y = $svgscale_x;
+ } elseif ($svgscale_x < $svgscale_y) {
+ $svgscale_x = $svgscale_y;
+ }
+ } else { // meet
+ // the entire viewBox is visible within the viewport
+ if ($svgscale_x < $svgscale_y) {
+ $svgscale_y = $svgscale_x;
+ } elseif ($svgscale_x > $svgscale_y) {
+ $svgscale_x = $svgscale_y;
+ }
+ }
+ // correct X alignment
+ switch (substr($aspect_ratio_align, 1, 3)) {
+ case 'Min': {
+ // do nothing
+ break;
+ }
+ case 'Max': {
+ $svgoffset_x += (($w * $this->k) - ($ow * $this->k * $svgscale_x));
+ break;
+ }
+ default:
+ case 'Mid': {
+ $svgoffset_x += ((($w * $this->k) - ($ow * $this->k * $svgscale_x)) / 2);
+ break;
+ }
+ }
+ // correct Y alignment
+ switch (substr($aspect_ratio_align, 5)) {
+ case 'Min': {
+ // do nothing
+ break;
+ }
+ case 'Max': {
+ $svgoffset_y -= (($h * $this->k) - ($oh * $this->k * $svgscale_y));
+ break;
+ }
+ default:
+ case 'Mid': {
+ $svgoffset_y -= ((($h * $this->k) - ($oh * $this->k * $svgscale_y)) / 2);
+ break;
+ }
+ }
+ }
+ // store current page break mode
+ $page_break_mode = $this->AutoPageBreak;
+ $page_break_margin = $this->getBreakMargin();
+ $cell_padding = $this->cell_padding;
+ $this->SetCellPadding(0);
+ $this->SetAutoPageBreak(false);
+ // save the current graphic state
+ $this->_out('q'.$this->epsmarker);
+ // set initial clipping mask
+ $this->Rect($x, $y, $w, $h, 'CNZ', array(), array());
+ // scale and translate
+ $e = $ox * $this->k * (1 - $svgscale_x);
+ $f = ($this->h - $oy) * $this->k * (1 - $svgscale_y);
+ $this->_out(sprintf('%F %F %F %F %F %F cm', $svgscale_x, 0, 0, $svgscale_y, ($e + $svgoffset_x), ($f + $svgoffset_y)));
+ // creates a new XML parser to be used by the other XML functions
+ $this->parser = xml_parser_create('UTF-8');
+ // the following function allows to use parser inside object
+ xml_set_object($this->parser, $this);
+ // disable case-folding for this XML parser
+ xml_parser_set_option($this->parser, XML_OPTION_CASE_FOLDING, 0);
+ // sets the element handler functions for the XML parser
+ xml_set_element_handler($this->parser, 'startSVGElementHandler', 'endSVGElementHandler');
+ // sets the character data handler function for the XML parser
+ xml_set_character_data_handler($this->parser, 'segSVGContentHandler');
+ // start parsing an XML document
+ if (!xml_parse($this->parser, $svgdata)) {
+ $error_message = sprintf('SVG Error: %s at line %d', xml_error_string(xml_get_error_code($this->parser)), xml_get_current_line_number($this->parser));
+ $this->Error($error_message);
+ }
+ // free this XML parser
+ xml_parser_free($this->parser);
+ // restore previous graphic state
+ $this->_out($this->epsmarker.'Q');
+ // restore graphic vars
+ $this->setGraphicVars($gvars);
+ $this->lasth = $gvars['lasth'];
+ if (!empty($border)) {
+ $bx = $this->x;
+ $by = $this->y;
+ $this->x = $ximg;
+ if ($this->rtl) {
+ $this->x += $w;
+ }
+ $this->y = $y;
+ $this->Cell($w, $h, '', $border, 0, '', 0, '', 0, true);
+ $this->x = $bx;
+ $this->y = $by;
+ }
+ if ($link) {
+ $this->Link($ximg, $y, $w, $h, $link, 0);
+ }
+ // set pointer to align the next text/objects
+ switch($align) {
+ case 'T':{
+ $this->y = $y;
+ $this->x = $this->img_rb_x;
+ break;
+ }
+ case 'M':{
+ $this->y = $y + round($h/2);
+ $this->x = $this->img_rb_x;
+ break;
+ }
+ case 'B':{
+ $this->y = $this->img_rb_y;
+ $this->x = $this->img_rb_x;
+ break;
+ }
+ case 'N':{
+ $this->SetY($this->img_rb_y);
+ break;
+ }
+ default:{
+ // restore pointer to starting position
+ $this->x = $gvars['x'];
+ $this->y = $gvars['y'];
+ $this->page = $gvars['page'];
+ $this->current_column = $gvars['current_column'];
+ $this->tMargin = $gvars['tMargin'];
+ $this->bMargin = $gvars['bMargin'];
+ $this->w = $gvars['w'];
+ $this->h = $gvars['h'];
+ $this->wPt = $gvars['wPt'];
+ $this->hPt = $gvars['hPt'];
+ $this->fwPt = $gvars['fwPt'];
+ $this->fhPt = $gvars['fhPt'];
+ break;
+ }
+ }
+ $this->endlinex = $this->img_rb_x;
+ // restore page break
+ $this->SetAutoPageBreak($page_break_mode, $page_break_margin);
+ $this->cell_padding = $cell_padding;
+ }
+
+ /**
+ * Convert SVG transformation matrix to PDF.
+ * @param $tm (array) original SVG transformation matrix
+ * @return array transformation matrix
+ * @protected
+ * @since 5.0.000 (2010-05-02)
+ */
+ protected function convertSVGtMatrix($tm) {
+ $a = $tm[0];
+ $b = -$tm[1];
+ $c = -$tm[2];
+ $d = $tm[3];
+ $e = $this->getHTMLUnitToUnits($tm[4], 1, $this->svgunit, false) * $this->k;
+ $f = -$this->getHTMLUnitToUnits($tm[5], 1, $this->svgunit, false) * $this->k;
+ $x = 0;
+ $y = $this->h * $this->k;
+ $e = ($x * (1 - $a)) - ($y * $c) + $e;
+ $f = ($y * (1 - $d)) - ($x * $b) + $f;
+ return array($a, $b, $c, $d, $e, $f);
+ }
+
+ /**
+ * Apply SVG graphic transformation matrix.
+ * @param $tm (array) original SVG transformation matrix
+ * @protected
+ * @since 5.0.000 (2010-05-02)
+ */
+ protected function SVGTransform($tm) {
+ $this->Transform($this->convertSVGtMatrix($tm));
+ }
+
+ /**
+ * Apply the requested SVG styles (*** TO BE COMPLETED ***)
+ * @param $svgstyle (array) array of SVG styles to apply
+ * @param $prevsvgstyle (array) array of previous SVG style
+ * @param $x (int) X origin of the bounding box
+ * @param $y (int) Y origin of the bounding box
+ * @param $w (int) width of the bounding box
+ * @param $h (int) height of the bounding box
+ * @param $clip_function (string) clip function
+ * @param $clip_params (array) array of parameters for clipping function
+ * @return object style
+ * @author Nicola Asuni
+ * @since 5.0.000 (2010-05-02)
+ * @protected
+ */
+ protected function setSVGStyles($svgstyle, $prevsvgstyle, $x=0, $y=0, $w=1, $h=1, $clip_function='', $clip_params=array()) {
+ if ($this->state != 2) {
+ return;
+ }
+ $objstyle = '';
+ $minlen = (0.01 / $this->k); // minimum acceptable length (3 point)
+ if (!isset($svgstyle['opacity'])) {
+ return $objstyle;
+ }
+ // clip-path
+ $regs = array();
+ if (preg_match('/url\([\s]*\#([^\)]*)\)/si', $svgstyle['clip-path'], $regs)) {
+ $clip_path = $this->svgclippaths[$regs[1]];
+ foreach ($clip_path as $cp) {
+ $this->startSVGElementHandler('clip-path', $cp['name'], $cp['attribs'], $cp['tm']);
+ }
+ }
+ // opacity
+ if ($svgstyle['opacity'] != 1) {
+ $this->setAlpha($svgstyle['opacity'], 'Normal', $svgstyle['opacity'], false);
+ }
+ // color
+ $fill_color = TCPDF_COLORS::convertHTMLColorToDec($svgstyle['color'], $this->spot_colors);
+ $this->SetFillColorArray($fill_color);
+ // text color
+ $text_color = TCPDF_COLORS::convertHTMLColorToDec($svgstyle['text-color'], $this->spot_colors);
+ $this->SetTextColorArray($text_color);
+ // clip
+ if (preg_match('/rect\(([a-z0-9\-\.]*)[\s]*([a-z0-9\-\.]*)[\s]*([a-z0-9\-\.]*)[\s]*([a-z0-9\-\.]*)\)/si', $svgstyle['clip'], $regs)) {
+ $top = (isset($regs[1])?$this->getHTMLUnitToUnits($regs[1], 0, $this->svgunit, false):0);
+ $right = (isset($regs[2])?$this->getHTMLUnitToUnits($regs[2], 0, $this->svgunit, false):0);
+ $bottom = (isset($regs[3])?$this->getHTMLUnitToUnits($regs[3], 0, $this->svgunit, false):0);
+ $left = (isset($regs[4])?$this->getHTMLUnitToUnits($regs[4], 0, $this->svgunit, false):0);
+ $cx = $x + $left;
+ $cy = $y + $top;
+ $cw = $w - $left - $right;
+ $ch = $h - $top - $bottom;
+ if ($svgstyle['clip-rule'] == 'evenodd') {
+ $clip_rule = 'CNZ';
+ } else {
+ $clip_rule = 'CEO';
+ }
+ $this->Rect($cx, $cy, $cw, $ch, $clip_rule, array(), array());
+ }
+ // fill
+ $regs = array();
+ if (preg_match('/url\([\s]*\#([^\)]*)\)/si', $svgstyle['fill'], $regs)) {
+ // gradient
+ $gradient = $this->svggradients[$regs[1]];
+ if (isset($gradient['xref'])) {
+ // reference to another gradient definition
+ $newgradient = $this->svggradients[$gradient['xref']];
+ $newgradient['coords'] = $gradient['coords'];
+ $newgradient['mode'] = $gradient['mode'];
+ $newgradient['gradientUnits'] = $gradient['gradientUnits'];
+ if (isset($gradient['gradientTransform'])) {
+ $newgradient['gradientTransform'] = $gradient['gradientTransform'];
+ }
+ $gradient = $newgradient;
+ }
+ //save current Graphic State
+ $this->_out('q');
+ //set clipping area
+ if (!empty($clip_function) AND method_exists($this, $clip_function)) {
+ $bbox = call_user_func_array(array($this, $clip_function), $clip_params);
+ if (is_array($bbox) AND (count($bbox) == 4)) {
+ list($x, $y, $w, $h) = $bbox;
+ }
+ }
+ if ($gradient['mode'] == 'measure') {
+ if (isset($gradient['gradientTransform']) AND !empty($gradient['gradientTransform'])) {
+ $gtm = $gradient['gradientTransform'];
+ // apply transformation matrix
+ $xa = ($gtm[0] * $gradient['coords'][0]) + ($gtm[2] * $gradient['coords'][1]) + $gtm[4];
+ $ya = ($gtm[1] * $gradient['coords'][0]) + ($gtm[3] * $gradient['coords'][1]) + $gtm[5];
+ $xb = ($gtm[0] * $gradient['coords'][2]) + ($gtm[2] * $gradient['coords'][3]) + $gtm[4];
+ $yb = ($gtm[1] * $gradient['coords'][2]) + ($gtm[3] * $gradient['coords'][3]) + $gtm[5];
+ if (isset($gradient['coords'][4])) {
+ $gradient['coords'][4] = sqrt(pow(($gtm[0] * $gradient['coords'][4]), 2) + pow(($gtm[1] * $gradient['coords'][4]), 2));
+ }
+ $gradient['coords'][0] = $xa;
+ $gradient['coords'][1] = $ya;
+ $gradient['coords'][2] = $xb;
+ $gradient['coords'][3] = $yb;
+ }
+ // convert SVG coordinates to user units
+ $gradient['coords'][0] = $this->getHTMLUnitToUnits($gradient['coords'][0], 0, $this->svgunit, false);
+ $gradient['coords'][1] = $this->getHTMLUnitToUnits($gradient['coords'][1], 0, $this->svgunit, false);
+ $gradient['coords'][2] = $this->getHTMLUnitToUnits($gradient['coords'][2], 0, $this->svgunit, false);
+ $gradient['coords'][3] = $this->getHTMLUnitToUnits($gradient['coords'][3], 0, $this->svgunit, false);
+ if (isset($gradient['coords'][4])) {
+ $gradient['coords'][4] = $this->getHTMLUnitToUnits($gradient['coords'][4], 0, $this->svgunit, false);
+ }
+ if ($w <= $minlen) {
+ $w = $minlen;
+ }
+ if ($h <= $minlen) {
+ $h = $minlen;
+ }
+ // shift units
+ if ($gradient['gradientUnits'] == 'objectBoundingBox') {
+ // convert to SVG coordinate system
+ $gradient['coords'][0] += $x;
+ $gradient['coords'][1] += $y;
+ $gradient['coords'][2] += $x;
+ $gradient['coords'][3] += $y;
+ }
+ // calculate percentages
+ $gradient['coords'][0] = (($gradient['coords'][0] - $x) / $w);
+ $gradient['coords'][1] = (($gradient['coords'][1] - $y) / $h);
+ $gradient['coords'][2] = (($gradient['coords'][2] - $x) / $w);
+ $gradient['coords'][3] = (($gradient['coords'][3] - $y) / $h);
+ if (isset($gradient['coords'][4])) {
+ $gradient['coords'][4] /= $w;
+ }
+ } elseif ($gradient['mode'] == 'percentage') {
+ foreach($gradient['coords'] as $key => $val) {
+ $gradient['coords'][$key] = (intval($val) / 100);
+ if ($val < 0) {
+ $gradient['coords'][$key] = 0;
+ } elseif ($val > 1) {
+ $gradient['coords'][$key] = 1;
+ }
+ }
+ }
+ if (($gradient['type'] == 2) AND ($gradient['coords'][0] == $gradient['coords'][2]) AND ($gradient['coords'][1] == $gradient['coords'][3])) {
+ // single color (no shading)
+ $gradient['coords'][0] = 1;
+ $gradient['coords'][1] = 0;
+ $gradient['coords'][2] = 0.999;
+ $gradient['coords'][3] = 0;
+ }
+ // swap Y coordinates
+ $tmp = $gradient['coords'][1];
+ $gradient['coords'][1] = $gradient['coords'][3];
+ $gradient['coords'][3] = $tmp;
+ // set transformation map for gradient
+ if ($gradient['type'] == 3) {
+ // circular gradient
+ $cy = $this->h - $y - ($gradient['coords'][1] * ($w + $h));
+ $this->_out(sprintf('%F 0 0 %F %F %F cm', ($w * $this->k), ($w * $this->k), ($x * $this->k), ($cy * $this->k)));
+ } else {
+ $this->_out(sprintf('%F 0 0 %F %F %F cm', ($w * $this->k), ($h * $this->k), ($x * $this->k), (($this->h - ($y + $h)) * $this->k)));
+ }
+ if (count($gradient['stops']) > 1) {
+ $this->Gradient($gradient['type'], $gradient['coords'], $gradient['stops'], array(), false);
+ }
+ } elseif ($svgstyle['fill'] != 'none') {
+ $fill_color = TCPDF_COLORS::convertHTMLColorToDec($svgstyle['fill'], $this->spot_colors);
+ if ($svgstyle['fill-opacity'] != 1) {
+ $this->setAlpha($this->alpha['CA'], 'Normal', $svgstyle['fill-opacity'], false);
+ }
+ $this->SetFillColorArray($fill_color);
+ if ($svgstyle['fill-rule'] == 'evenodd') {
+ $objstyle .= 'F*';
+ } else {
+ $objstyle .= 'F';
+ }
+ }
+ // stroke
+ if ($svgstyle['stroke'] != 'none') {
+ if ($svgstyle['stroke-opacity'] != 1) {
+ $this->setAlpha($svgstyle['stroke-opacity'], 'Normal', $this->alpha['ca'], false);
+ }
+ $stroke_style = array(
+ 'color' => TCPDF_COLORS::convertHTMLColorToDec($svgstyle['stroke'], $this->spot_colors),
+ 'width' => $this->getHTMLUnitToUnits($svgstyle['stroke-width'], 0, $this->svgunit, false),
+ 'cap' => $svgstyle['stroke-linecap'],
+ 'join' => $svgstyle['stroke-linejoin']
+ );
+ if (isset($svgstyle['stroke-dasharray']) AND !empty($svgstyle['stroke-dasharray']) AND ($svgstyle['stroke-dasharray'] != 'none')) {
+ $stroke_style['dash'] = $svgstyle['stroke-dasharray'];
+ }
+ $this->SetLineStyle($stroke_style);
+ $objstyle .= 'D';
+ }
+ // font
+ $regs = array();
+ if (!empty($svgstyle['font'])) {
+ if (preg_match('/font-family[\s]*:[\s]*([^\;\"]*)/si', $svgstyle['font'], $regs)) {
+ $font_family = $this->getFontFamilyName($regs[1]);
+ } else {
+ $font_family = $svgstyle['font-family'];
+ }
+ if (preg_match('/font-size[\s]*:[\s]*([^\s\;\"]*)/si', $svgstyle['font'], $regs)) {
+ $font_size = trim($regs[1]);
+ } else {
+ $font_size = $svgstyle['font-size'];
+ }
+ if (preg_match('/font-style[\s]*:[\s]*([^\s\;\"]*)/si', $svgstyle['font'], $regs)) {
+ $font_style = trim($regs[1]);
+ } else {
+ $font_style = $svgstyle['font-style'];
+ }
+ if (preg_match('/font-weight[\s]*:[\s]*([^\s\;\"]*)/si', $svgstyle['font'], $regs)) {
+ $font_weight = trim($regs[1]);
+ } else {
+ $font_weight = $svgstyle['font-weight'];
+ }
+ if (preg_match('/font-stretch[\s]*:[\s]*([^\s\;\"]*)/si', $svgstyle['font'], $regs)) {
+ $font_stretch = trim($regs[1]);
+ } else {
+ $font_stretch = $svgstyle['font-stretch'];
+ }
+ if (preg_match('/letter-spacing[\s]*:[\s]*([^\s\;\"]*)/si', $svgstyle['font'], $regs)) {
+ $font_spacing = trim($regs[1]);
+ } else {
+ $font_spacing = $svgstyle['letter-spacing'];
+ }
+ } else {
+ $font_family = $this->getFontFamilyName($svgstyle['font-family']);
+ $font_size = $svgstyle['font-size'];
+ $font_style = $svgstyle['font-style'];
+ $font_weight = $svgstyle['font-weight'];
+ $font_stretch = $svgstyle['font-stretch'];
+ $font_spacing = $svgstyle['letter-spacing'];
+ }
+ $font_size = $this->getHTMLFontUnits($font_size, $this->svgstyles[0]['font-size'], $prevsvgstyle['font-size'], $this->svgunit);
+ $font_stretch = $this->getCSSFontStretching($font_stretch, $svgstyle['font-stretch']);
+ $font_spacing = $this->getCSSFontSpacing($font_spacing, $svgstyle['letter-spacing']);
+ switch ($font_style) {
+ case 'italic': {
+ $font_style = 'I';
+ break;
+ }
+ case 'oblique': {
+ $font_style = 'I';
+ break;
+ }
+ default:
+ case 'normal': {
+ $font_style = '';
+ break;
+ }
+ }
+ switch ($font_weight) {
+ case 'bold':
+ case 'bolder': {
+ $font_style .= 'B';
+ break;
+ }
+ }
+ switch ($svgstyle['text-decoration']) {
+ case 'underline': {
+ $font_style .= 'U';
+ break;
+ }
+ case 'overline': {
+ $font_style .= 'O';
+ break;
+ }
+ case 'line-through': {
+ $font_style .= 'D';
+ break;
+ }
+ default:
+ case 'none': {
+ break;
+ }
+ }
+ $this->SetFont($font_family, $font_style, $font_size);
+ $this->setFontStretching($font_stretch);
+ $this->setFontSpacing($font_spacing);
+ return $objstyle;
+ }
+
+ /**
+ * Draws an SVG path
+ * @param $d (string) attribute d of the path SVG element
+ * @param $style (string) Style of rendering. Possible values are:
+ * <ul>
+ * <li>D or empty string: Draw (default).</li>
+ * <li>F: Fill.</li>
+ * <li>F*: Fill using the even-odd rule to determine which regions lie inside the clipping path.</li>
+ * <li>DF or FD: Draw and fill.</li>
+ * <li>DF* or FD*: Draw and fill using the even-odd rule to determine which regions lie inside the clipping path.</li>
+ * <li>CNZ: Clipping mode (using the even-odd rule to determine which regions lie inside the clipping path).</li>
+ * <li>CEO: Clipping mode (using the nonzero winding number rule to determine which regions lie inside the clipping path).</li>
+ * </ul>
+ * @return array of container box measures (x, y, w, h)
+ * @author Nicola Asuni
+ * @since 5.0.000 (2010-05-02)
+ * @protected
+ */
+ protected function SVGPath($d, $style='') {
+ if ($this->state != 2) {
+ return;
+ }
+ // set fill/stroke style
+ $op = TCPDF_STATIC::getPathPaintOperator($style, '');
+ if (empty($op)) {
+ return;
+ }
+ $paths = array();
+ $d = preg_replace('/([0-9ACHLMQSTVZ])([\-\+])/si', '\\1 \\2', $d);
+ preg_match_all('/([ACHLMQSTVZ])[\s]*([^ACHLMQSTVZ\"]*)/si', $d, $paths, PREG_SET_ORDER);
+ $x = 0;
+ $y = 0;
+ $x1 = 0;
+ $y1 = 0;
+ $x2 = 0;
+ $y2 = 0;
+ $xmin = 2147483647;
+ $xmax = 0;
+ $ymin = 2147483647;
+ $ymax = 0;
+ $relcoord = false;
+ $minlen = (0.01 / $this->k); // minimum acceptable length (3 point)
+ $firstcmd = true; // used to print first point
+ // draw curve pieces
+ foreach ($paths as $key => $val) {
+ // get curve type
+ $cmd = trim($val[1]);
+ if (strtolower($cmd) == $cmd) {
+ // use relative coordinated instead of absolute
+ $relcoord = true;
+ $xoffset = $x;
+ $yoffset = $y;
+ } else {
+ $relcoord = false;
+ $xoffset = 0;
+ $yoffset = 0;
+ }
+ $params = array();
+ if (isset($val[2])) {
+ // get curve parameters
+ $rawparams = preg_split('/([\,\s]+)/si', trim($val[2]));
+ $params = array();
+ foreach ($rawparams as $ck => $cp) {
+ $params[$ck] = $this->getHTMLUnitToUnits($cp, 0, $this->svgunit, false);
+ if (abs($params[$ck]) < $minlen) {
+ // aproximate little values to zero
+ $params[$ck] = 0;
+ }
+ }
+ }
+ // store current origin point
+ $x0 = $x;
+ $y0 = $y;
+ switch (strtoupper($cmd)) {
+ case 'M': { // moveto
+ foreach ($params as $ck => $cp) {
+ if (($ck % 2) == 0) {
+ $x = $cp + $xoffset;
+ } else {
+ $y = $cp + $yoffset;
+ if ($firstcmd OR (abs($x0 - $x) >= $minlen) OR (abs($y0 - $y) >= $minlen)) {
+ if ($ck == 1) {
+ $this->_outPoint($x, $y);
+ $firstcmd = false;
+ } else {
+ $this->_outLine($x, $y);
+ }
+ $x0 = $x;
+ $y0 = $y;
+ }
+ $xmin = min($xmin, $x);
+ $ymin = min($ymin, $y);
+ $xmax = max($xmax, $x);
+ $ymax = max($ymax, $y);
+ if ($relcoord) {
+ $xoffset = $x;
+ $yoffset = $y;
+ }
+ }
+ }
+ break;
+ }
+ case 'L': { // lineto
+ foreach ($params as $ck => $cp) {
+ if (($ck % 2) == 0) {
+ $x = $cp + $xoffset;
+ } else {
+ $y = $cp + $yoffset;
+ if ((abs($x0 - $x) >= $minlen) OR (abs($y0 - $y) >= $minlen)) {
+ $this->_outLine($x, $y);
+ $x0 = $x;
+ $y0 = $y;
+ }
+ $xmin = min($xmin, $x);
+ $ymin = min($ymin, $y);
+ $xmax = max($xmax, $x);
+ $ymax = max($ymax, $y);
+ if ($relcoord) {
+ $xoffset = $x;
+ $yoffset = $y;
+ }
+ }
+ }
+ break;
+ }
+ case 'H': { // horizontal lineto
+ foreach ($params as $ck => $cp) {
+ $x = $cp + $xoffset;
+ if ((abs($x0 - $x) >= $minlen) OR (abs($y0 - $y) >= $minlen)) {
+ $this->_outLine($x, $y);
+ $x0 = $x;
+ $y0 = $y;
+ }
+ $xmin = min($xmin, $x);
+ $xmax = max($xmax, $x);
+ if ($relcoord) {
+ $xoffset = $x;
+ }
+ }
+ break;
+ }
+ case 'V': { // vertical lineto
+ foreach ($params as $ck => $cp) {
+ $y = $cp + $yoffset;
+ if ((abs($x0 - $x) >= $minlen) OR (abs($y0 - $y) >= $minlen)) {
+ $this->_outLine($x, $y);
+ $x0 = $x;
+ $y0 = $y;
+ }
+ $ymin = min($ymin, $y);
+ $ymax = max($ymax, $y);
+ if ($relcoord) {
+ $yoffset = $y;
+ }
+ }
+ break;
+ }
+ case 'C': { // curveto
+ foreach ($params as $ck => $cp) {
+ $params[$ck] = $cp;
+ if ((($ck + 1) % 6) == 0) {
+ $x1 = $params[($ck - 5)] + $xoffset;
+ $y1 = $params[($ck - 4)] + $yoffset;
+ $x2 = $params[($ck - 3)] + $xoffset;
+ $y2 = $params[($ck - 2)] + $yoffset;
+ $x = $params[($ck - 1)] + $xoffset;
+ $y = $params[($ck)] + $yoffset;
+ $this->_outCurve($x1, $y1, $x2, $y2, $x, $y);
+ $xmin = min($xmin, $x, $x1, $x2);
+ $ymin = min($ymin, $y, $y1, $y2);
+ $xmax = max($xmax, $x, $x1, $x2);
+ $ymax = max($ymax, $y, $y1, $y2);
+ if ($relcoord) {
+ $xoffset = $x;
+ $yoffset = $y;
+ }
+ }
+ }
+ break;
+ }
+ case 'S': { // shorthand/smooth curveto
+ foreach ($params as $ck => $cp) {
+ $params[$ck] = $cp;
+ if ((($ck + 1) % 4) == 0) {
+ if (($key > 0) AND ((strtoupper($paths[($key - 1)][1]) == 'C') OR (strtoupper($paths[($key - 1)][1]) == 'S'))) {
+ $x1 = (2 * $x) - $x2;
+ $y1 = (2 * $y) - $y2;
+ } else {
+ $x1 = $x;
+ $y1 = $y;
+ }
+ $x2 = $params[($ck - 3)] + $xoffset;
+ $y2 = $params[($ck - 2)] + $yoffset;
+ $x = $params[($ck - 1)] + $xoffset;
+ $y = $params[($ck)] + $yoffset;
+ $this->_outCurve($x1, $y1, $x2, $y2, $x, $y);
+ $xmin = min($xmin, $x, $x1, $x2);
+ $ymin = min($ymin, $y, $y1, $y2);
+ $xmax = max($xmax, $x, $x1, $x2);
+ $ymax = max($ymax, $y, $y1, $y2);
+ if ($relcoord) {
+ $xoffset = $x;
+ $yoffset = $y;
+ }
+ }
+ }
+ break;
+ }
+ case 'Q': { // quadratic Bézier curveto
+ foreach ($params as $ck => $cp) {
+ $params[$ck] = $cp;
+ if ((($ck + 1) % 4) == 0) {
+ // convert quadratic points to cubic points
+ $x1 = $params[($ck - 3)] + $xoffset;
+ $y1 = $params[($ck - 2)] + $yoffset;
+ $xa = ($x + (2 * $x1)) / 3;
+ $ya = ($y + (2 * $y1)) / 3;
+ $x = $params[($ck - 1)] + $xoffset;
+ $y = $params[($ck)] + $yoffset;
+ $xb = ($x + (2 * $x1)) / 3;
+ $yb = ($y + (2 * $y1)) / 3;
+ $this->_outCurve($xa, $ya, $xb, $yb, $x, $y);
+ $xmin = min($xmin, $x, $xa, $xb);
+ $ymin = min($ymin, $y, $ya, $yb);
+ $xmax = max($xmax, $x, $xa, $xb);
+ $ymax = max($ymax, $y, $ya, $yb);
+ if ($relcoord) {
+ $xoffset = $x;
+ $yoffset = $y;
+ }
+ }
+ }
+ break;
+ }
+ case 'T': { // shorthand/smooth quadratic Bézier curveto
+ foreach ($params as $ck => $cp) {
+ $params[$ck] = $cp;
+ if (($ck % 2) != 0) {
+ if (($key > 0) AND ((strtoupper($paths[($key - 1)][1]) == 'Q') OR (strtoupper($paths[($key - 1)][1]) == 'T'))) {
+ $x1 = (2 * $x) - $x1;
+ $y1 = (2 * $y) - $y1;
+ } else {
+ $x1 = $x;
+ $y1 = $y;
+ }
+ // convert quadratic points to cubic points
+ $xa = ($x + (2 * $x1)) / 3;
+ $ya = ($y + (2 * $y1)) / 3;
+ $x = $params[($ck - 1)] + $xoffset;
+ $y = $params[($ck)] + $yoffset;
+ $xb = ($x + (2 * $x1)) / 3;
+ $yb = ($y + (2 * $y1)) / 3;
+ $this->_outCurve($xa, $ya, $xb, $yb, $x, $y);
+ $xmin = min($xmin, $x, $xa, $xb);
+ $ymin = min($ymin, $y, $ya, $yb);
+ $xmax = max($xmax, $x, $xa, $xb);
+ $ymax = max($ymax, $y, $ya, $yb);
+ if ($relcoord) {
+ $xoffset = $x;
+ $yoffset = $y;
+ }
+ }
+ }
+ break;
+ }
+ case 'A': { // elliptical arc
+ foreach ($params as $ck => $cp) {
+ $params[$ck] = $cp;
+ if ((($ck + 1) % 7) == 0) {
+ $x0 = $x;
+ $y0 = $y;
+ $rx = abs($params[($ck - 6)]);
+ $ry = abs($params[($ck - 5)]);
+ $ang = -$rawparams[($ck - 4)];
+ $angle = deg2rad($ang);
+ $fa = $rawparams[($ck - 3)]; // large-arc-flag
+ $fs = $rawparams[($ck - 2)]; // sweep-flag
+ $x = $params[($ck - 1)] + $xoffset;
+ $y = $params[$ck] + $yoffset;
+ if ((abs($x0 - $x) < $minlen) AND (abs($y0 - $y) < $minlen)) {
+ // endpoints are almost identical
+ $xmin = min($xmin, $x);
+ $ymin = min($ymin, $y);
+ $xmax = max($xmax, $x);
+ $ymax = max($ymax, $y);
+ } else {
+ $cos_ang = cos($angle);
+ $sin_ang = sin($angle);
+ $a = (($x0 - $x) / 2);
+ $b = (($y0 - $y) / 2);
+ $xa = ($a * $cos_ang) - ($b * $sin_ang);
+ $ya = ($a * $sin_ang) + ($b * $cos_ang);
+ $rx2 = $rx * $rx;
+ $ry2 = $ry * $ry;
+ $xa2 = $xa * $xa;
+ $ya2 = $ya * $ya;
+ $delta = ($xa2 / $rx2) + ($ya2 / $ry2);
+ if ($delta > 1) {
+ $rx *= sqrt($delta);
+ $ry *= sqrt($delta);
+ $rx2 = $rx * $rx;
+ $ry2 = $ry * $ry;
+ }
+ $numerator = (($rx2 * $ry2) - ($rx2 * $ya2) - ($ry2 * $xa2));
+ if ($numerator < 0) {
+ $root = 0;
+ } else {
+ $root = sqrt($numerator / (($rx2 * $ya2) + ($ry2 * $xa2)));
+ }
+ if ($fa == $fs){
+ $root *= -1;
+ }
+ $cax = $root * (($rx * $ya) / $ry);
+ $cay = -$root * (($ry * $xa) / $rx);
+ // coordinates of ellipse center
+ $cx = ($cax * $cos_ang) - ($cay * $sin_ang) + (($x0 + $x) / 2);
+ $cy = ($cax * $sin_ang) + ($cay * $cos_ang) + (($y0 + $y) / 2);
+ // get angles
+ $angs = TCPDF_STATIC::getVectorsAngle(1, 0, (($xa - $cax) / $rx), (($cay - $ya) / $ry));
+ $dang = TCPDF_STATIC::getVectorsAngle((($xa - $cax) / $rx), (($ya - $cay) / $ry), ((-$xa - $cax) / $rx), ((-$ya - $cay) / $ry));
+ if (($fs == 0) AND ($dang > 0)) {
+ $dang -= (2 * M_PI);
+ } elseif (($fs == 1) AND ($dang < 0)) {
+ $dang += (2 * M_PI);
+ }
+ $angf = $angs - $dang;
+ if ((($fs == 0) AND ($angs > $angf)) OR (($fs == 1) AND ($angs < $angf))) {
+ // reverse angles
+ $tmp = $angs;
+ $angs = $angf;
+ $angf = $tmp;
+ }
+ $angs = round(rad2deg($angs), 6);
+ $angf = round(rad2deg($angf), 6);
+ // covent angles to positive values
+ if (($angs < 0) AND ($angf < 0)) {
+ $angs += 360;
+ $angf += 360;
+ }
+ $pie = false;
+ if (($key == 0) AND (isset($paths[($key + 1)][1])) AND (trim($paths[($key + 1)][1]) == 'z')) {
+ $pie = true;
+ }
+ list($axmin, $aymin, $axmax, $aymax) = $this->_outellipticalarc($cx, $cy, $rx, $ry, $ang, $angs, $angf, $pie, 2, false, ($fs == 0), true);
+ $xmin = min($xmin, $x, $axmin);
+ $ymin = min($ymin, $y, $aymin);
+ $xmax = max($xmax, $x, $axmax);
+ $ymax = max($ymax, $y, $aymax);
+ }
+ if ($relcoord) {
+ $xoffset = $x;
+ $yoffset = $y;
+ }
+ }
+ }
+ break;
+ }
+ case 'Z': {
+ $this->_out('h');
+ break;
+ }
+ }
+ $firstcmd = false;
+ } // end foreach
+ if (!empty($op)) {
+ $this->_out($op);
+ }
+ return array($xmin, $ymin, ($xmax - $xmin), ($ymax - $ymin));
+ }
+
+ /**
+ * Sets the opening SVG element handler function for the XML parser. (*** TO BE COMPLETED ***)
+ * @param $parser (resource) The first parameter, parser, is a reference to the XML parser calling the handler.
+ * @param $name (string) The second parameter, name, contains the name of the element for which this handler is called. If case-folding is in effect for this parser, the element name will be in uppercase letters.
+ * @param $attribs (array) The third parameter, attribs, contains an associative array with the element's attributes (if any). The keys of this array are the attribute names, the values are the attribute values. Attribute names are case-folded on the same criteria as element names. Attribute values are not case-folded. The original order of the attributes can be retrieved by walking through attribs the normal way, using each(). The first key in the array was the first attribute, and so on.
+ * @param $ctm (array) tranformation matrix for clipping mode (starting transformation matrix).
+ * @author Nicola Asuni
+ * @since 5.0.000 (2010-05-02)
+ * @protected
+ */
+ protected function startSVGElementHandler($parser, $name, $attribs, $ctm=array()) {
+ // check if we are in clip mode
+ if ($this->svgclipmode) {
+ $this->svgclippaths[$this->svgclipid][] = array('name' => $name, 'attribs' => $attribs, 'tm' => $this->svgcliptm[$this->svgclipid]);
+ return;
+ }
+ if ($this->svgdefsmode AND !in_array($name, array('clipPath', 'linearGradient', 'radialGradient', 'stop'))) {
+ if (!isset($attribs['id'])) {
+ $attribs['id'] = 'DF_'.(count($this->svgdefs) + 1);
+ }
+ $this->svgdefs[$attribs['id']] = array('name' => $name, 'attribs' => $attribs);
+ return;
+ }
+ $clipping = false;
+ if ($parser == 'clip-path') {
+ // set clipping mode
+ $clipping = true;
+ }
+ // get styling properties
+ $prev_svgstyle = $this->svgstyles[(count($this->svgstyles) - 1)]; // previous style
+ $svgstyle = $this->svgstyles[0]; // set default style
+ if ($clipping AND !isset($attribs['fill']) AND (!isset($attribs['style']) OR (!preg_match('/[;\"\s]{1}fill[\s]*:[\s]*([^;\"]*)/si', $attribs['style'], $attrval)))) {
+ // default fill attribute for clipping
+ $attribs['fill'] = 'none';
+ }
+ if (isset($attribs['style']) AND !TCPDF_STATIC::empty_string($attribs['style'])) {
+ // fix style for regular expression
+ $attribs['style'] = ';'.$attribs['style'];
+ }
+ foreach ($prev_svgstyle as $key => $val) {
+ if (in_array($key, TCPDF_IMAGES::$svginheritprop)) {
+ // inherit previous value
+ $svgstyle[$key] = $val;
+ }
+ if (isset($attribs[$key]) AND !TCPDF_STATIC::empty_string($attribs[$key])) {
+ // specific attribute settings
+ if ($attribs[$key] == 'inherit') {
+ $svgstyle[$key] = $val;
+ } else {
+ $svgstyle[$key] = $attribs[$key];
+ }
+ } elseif (isset($attribs['style']) AND !TCPDF_STATIC::empty_string($attribs['style'])) {
+ // CSS style syntax
+ $attrval = array();
+ if (preg_match('/[;\"\s]{1}'.$key.'[\s]*:[\s]*([^;\"]*)/si', $attribs['style'], $attrval) AND isset($attrval[1])) {
+ if ($attrval[1] == 'inherit') {
+ $svgstyle[$key] = $val;
+ } else {
+ $svgstyle[$key] = $attrval[1];
+ }
+ }
+ }
+ }
+ // transformation matrix
+ if (!empty($ctm)) {
+ $tm = $ctm;
+ } else {
+ //$tm = $this->svgstyles[(count($this->svgstyles) - 1)]['transfmatrix'];
+ $tm = array(1,0,0,1,0,0);
+ }
+ if (isset($attribs['transform']) AND !empty($attribs['transform'])) {
+ $tm = TCPDF_STATIC::getTransformationMatrixProduct($tm, TCPDF_STATIC::getSVGTransformMatrix($attribs['transform']));
+ }
+ $svgstyle['transfmatrix'] = $tm;
+ $invisible = false;
+ if (($svgstyle['visibility'] == 'hidden') OR ($svgstyle['visibility'] == 'collapse') OR ($svgstyle['display'] == 'none')) {
+ // the current graphics element is invisible (nothing is painted)
+ $invisible = true;
+ }
+ // process tag
+ switch($name) {
+ case 'defs': {
+ $this->svgdefsmode = true;
+ break;
+ }
+ // clipPath
+ case 'clipPath': {
+ if ($invisible) {
+ break;
+ }
+ $this->svgclipmode = true;
+ if (!isset($attribs['id'])) {
+ $attribs['id'] = 'CP_'.(count($this->svgcliptm) + 1);
+ }
+ $this->svgclipid = $attribs['id'];
+ $this->svgclippaths[$this->svgclipid] = array();
+ $this->svgcliptm[$this->svgclipid] = $tm;
+ break;
+ }
+ case 'svg': {
+ // start of SVG object
+ break;
+ }
+ case 'g': {
+ // group together related graphics elements
+ array_push($this->svgstyles, $svgstyle);
+ $this->StartTransform();
+ $this->SVGTransform($tm);
+ $this->setSVGStyles($svgstyle, $prev_svgstyle);
+ break;
+ }
+ case 'linearGradient': {
+ if ($this->pdfa_mode) {
+ break;
+ }
+ if (!isset($attribs['id'])) {
+ $attribs['id'] = 'GR_'.(count($this->svggradients) + 1);
+ }
+ $this->svggradientid = $attribs['id'];
+ $this->svggradients[$this->svggradientid] = array();
+ $this->svggradients[$this->svggradientid]['type'] = 2;
+ $this->svggradients[$this->svggradientid]['stops'] = array();
+ if (isset($attribs['gradientUnits'])) {
+ $this->svggradients[$this->svggradientid]['gradientUnits'] = $attribs['gradientUnits'];
+ } else {
+ $this->svggradients[$this->svggradientid]['gradientUnits'] = 'objectBoundingBox';
+ }
+ //$attribs['spreadMethod']
+ if (((!isset($attribs['x1'])) AND (!isset($attribs['y1'])) AND (!isset($attribs['x2'])) AND (!isset($attribs['y2'])))
+ OR ((isset($attribs['x1']) AND (substr($attribs['x1'], -1) == '%'))
+ OR (isset($attribs['y1']) AND (substr($attribs['y1'], -1) == '%'))
+ OR (isset($attribs['x2']) AND (substr($attribs['x2'], -1) == '%'))
+ OR (isset($attribs['y2']) AND (substr($attribs['y2'], -1) == '%')))) {
+ $this->svggradients[$this->svggradientid]['mode'] = 'percentage';
+ } else {
+ $this->svggradients[$this->svggradientid]['mode'] = 'measure';
+ }
+ $x1 = (isset($attribs['x1'])?$attribs['x1']:'0');
+ $y1 = (isset($attribs['y1'])?$attribs['y1']:'0');
+ $x2 = (isset($attribs['x2'])?$attribs['x2']:'100');
+ $y2 = (isset($attribs['y2'])?$attribs['y2']:'0');
+ if (isset($attribs['gradientTransform'])) {
+ $this->svggradients[$this->svggradientid]['gradientTransform'] = TCPDF_STATIC::getSVGTransformMatrix($attribs['gradientTransform']);
+ }
+ $this->svggradients[$this->svggradientid]['coords'] = array($x1, $y1, $x2, $y2);
+ if (isset($attribs['xlink:href']) AND !empty($attribs['xlink:href'])) {
+ // gradient is defined on another place
+ $this->svggradients[$this->svggradientid]['xref'] = substr($attribs['xlink:href'], 1);
+ }
+ break;
+ }
+ case 'radialGradient': {
+ if ($this->pdfa_mode) {
+ break;
+ }
+ if (!isset($attribs['id'])) {
+ $attribs['id'] = 'GR_'.(count($this->svggradients) + 1);
+ }
+ $this->svggradientid = $attribs['id'];
+ $this->svggradients[$this->svggradientid] = array();
+ $this->svggradients[$this->svggradientid]['type'] = 3;
+ $this->svggradients[$this->svggradientid]['stops'] = array();
+ if (isset($attribs['gradientUnits'])) {
+ $this->svggradients[$this->svggradientid]['gradientUnits'] = $attribs['gradientUnits'];
+ } else {
+ $this->svggradients[$this->svggradientid]['gradientUnits'] = 'objectBoundingBox';
+ }
+ //$attribs['spreadMethod']
+ if (((!isset($attribs['cx'])) AND (!isset($attribs['cy'])))
+ OR ((isset($attribs['cx']) AND (substr($attribs['cx'], -1) == '%'))
+ OR (isset($attribs['cy']) AND (substr($attribs['cy'], -1) == '%')) )) {
+ $this->svggradients[$this->svggradientid]['mode'] = 'percentage';
+ } else {
+ $this->svggradients[$this->svggradientid]['mode'] = 'measure';
+ }
+ $cx = (isset($attribs['cx']) ? $attribs['cx'] : 0.5);
+ $cy = (isset($attribs['cy']) ? $attribs['cy'] : 0.5);
+ $fx = (isset($attribs['fx']) ? $attribs['fx'] : $cx);
+ $fy = (isset($attribs['fy']) ? $attribs['fy'] : $cy);
+ $r = (isset($attribs['r']) ? $attribs['r'] : 0.5);
+ if (isset($attribs['gradientTransform'])) {
+ $this->svggradients[$this->svggradientid]['gradientTransform'] = TCPDF_STATIC::getSVGTransformMatrix($attribs['gradientTransform']);
+ }
+ $this->svggradients[$this->svggradientid]['coords'] = array($cx, $cy, $fx, $fy, $r);
+ if (isset($attribs['xlink:href']) AND !empty($attribs['xlink:href'])) {
+ // gradient is defined on another place
+ $this->svggradients[$this->svggradientid]['xref'] = substr($attribs['xlink:href'], 1);
+ }
+ break;
+ }
+ case 'stop': {
+ // gradient stops
+ if (substr($attribs['offset'], -1) == '%') {
+ $offset = floatval(substr($attribs['offset'], -1)) / 100;
+ } else {
+ $offset = floatval($attribs['offset']);
+ if ($offset > 1) {
+ $offset /= 100;
+ }
+ }
+ $stop_color = isset($svgstyle['stop-color'])?TCPDF_COLORS::convertHTMLColorToDec($svgstyle['stop-color'], $this->spot_colors):'black';
+ $opacity = isset($svgstyle['stop-opacity'])?$svgstyle['stop-opacity']:1;
+ $this->svggradients[$this->svggradientid]['stops'][] = array('offset' => $offset, 'color' => $stop_color, 'opacity' => $opacity);
+ break;
+ }
+ // paths
+ case 'path': {
+ if ($invisible) {
+ break;
+ }
+ if (isset($attribs['d'])) {
+ $d = trim($attribs['d']);
+ if (!empty($d)) {
+ if ($clipping) {
+ $this->SVGTransform($tm);
+ $this->SVGPath($d, 'CNZ');
+ } else {
+ $this->StartTransform();
+ $this->SVGTransform($tm);
+ $obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, 0, 0, 1, 1, 'SVGPath', array($d, 'CNZ'));
+ if (!empty($obstyle)) {
+ $this->SVGPath($d, $obstyle);
+ }
+ $this->StopTransform();
+ }
+ }
+ }
+ break;
+ }
+ // shapes
+ case 'rect': {
+ if ($invisible) {
+ break;
+ }
+ $x = (isset($attribs['x'])?$this->getHTMLUnitToUnits($attribs['x'], 0, $this->svgunit, false):0);
+ $y = (isset($attribs['y'])?$this->getHTMLUnitToUnits($attribs['y'], 0, $this->svgunit, false):0);
+ $w = (isset($attribs['width'])?$this->getHTMLUnitToUnits($attribs['width'], 0, $this->svgunit, false):0);
+ $h = (isset($attribs['height'])?$this->getHTMLUnitToUnits($attribs['height'], 0, $this->svgunit, false):0);
+ $rx = (isset($attribs['rx'])?$this->getHTMLUnitToUnits($attribs['rx'], 0, $this->svgunit, false):0);
+ $ry = (isset($attribs['ry'])?$this->getHTMLUnitToUnits($attribs['ry'], 0, $this->svgunit, false):$rx);
+ if ($clipping) {
+ $this->SVGTransform($tm);
+ $this->RoundedRectXY($x, $y, $w, $h, $rx, $ry, '1111', 'CNZ', array(), array());
+ } else {
+ $this->StartTransform();
+ $this->SVGTransform($tm);
+ $obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'RoundedRectXY', array($x, $y, $w, $h, $rx, $ry, '1111', 'CNZ'));
+ if (!empty($obstyle)) {
+ $this->RoundedRectXY($x, $y, $w, $h, $rx, $ry, '1111', $obstyle, array(), array());
+ }
+ $this->StopTransform();
+ }
+ break;
+ }
+ case 'circle': {
+ if ($invisible) {
+ break;
+ }
+ $r = (isset($attribs['r']) ? $this->getHTMLUnitToUnits($attribs['r'], 0, $this->svgunit, false) : 0);
+ $cx = (isset($attribs['cx']) ? $this->getHTMLUnitToUnits($attribs['cx'], 0, $this->svgunit, false) : (isset($attribs['x']) ? $this->getHTMLUnitToUnits($attribs['x'], 0, $this->svgunit, false) : 0));
+ $cy = (isset($attribs['cy']) ? $this->getHTMLUnitToUnits($attribs['cy'], 0, $this->svgunit, false) : (isset($attribs['y']) ? $this->getHTMLUnitToUnits($attribs['y'], 0, $this->svgunit, false) : 0));
+ $x = ($cx - $r);
+ $y = ($cy - $r);
+ $w = (2 * $r);
+ $h = $w;
+ if ($clipping) {
+ $this->SVGTransform($tm);
+ $this->Circle($cx, $cy, $r, 0, 360, 'CNZ', array(), array(), 8);
+ } else {
+ $this->StartTransform();
+ $this->SVGTransform($tm);
+ $obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'Circle', array($cx, $cy, $r, 0, 360, 'CNZ'));
+ if (!empty($obstyle)) {
+ $this->Circle($cx, $cy, $r, 0, 360, $obstyle, array(), array(), 8);
+ }
+ $this->StopTransform();
+ }
+ break;
+ }
+ case 'ellipse': {
+ if ($invisible) {
+ break;
+ }
+ $rx = (isset($attribs['rx']) ? $this->getHTMLUnitToUnits($attribs['rx'], 0, $this->svgunit, false) : 0);
+ $ry = (isset($attribs['ry']) ? $this->getHTMLUnitToUnits($attribs['ry'], 0, $this->svgunit, false) : 0);
+ $cx = (isset($attribs['cx']) ? $this->getHTMLUnitToUnits($attribs['cx'], 0, $this->svgunit, false) : (isset($attribs['x']) ? $this->getHTMLUnitToUnits($attribs['x'], 0, $this->svgunit, false) : 0));
+ $cy = (isset($attribs['cy']) ? $this->getHTMLUnitToUnits($attribs['cy'], 0, $this->svgunit, false) : (isset($attribs['y']) ? $this->getHTMLUnitToUnits($attribs['y'], 0, $this->svgunit, false) : 0));
+ $x = ($cx - $rx);
+ $y = ($cy - $ry);
+ $w = (2 * $rx);
+ $h = (2 * $ry);
+ if ($clipping) {
+ $this->SVGTransform($tm);
+ $this->Ellipse($cx, $cy, $rx, $ry, 0, 0, 360, 'CNZ', array(), array(), 8);
+ } else {
+ $this->StartTransform();
+ $this->SVGTransform($tm);
+ $obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'Ellipse', array($cx, $cy, $rx, $ry, 0, 0, 360, 'CNZ'));
+ if (!empty($obstyle)) {
+ $this->Ellipse($cx, $cy, $rx, $ry, 0, 0, 360, $obstyle, array(), array(), 8);
+ }
+ $this->StopTransform();
+ }
+ break;
+ }
+ case 'line': {
+ if ($invisible) {
+ break;
+ }
+ $x1 = (isset($attribs['x1'])?$this->getHTMLUnitToUnits($attribs['x1'], 0, $this->svgunit, false):0);
+ $y1 = (isset($attribs['y1'])?$this->getHTMLUnitToUnits($attribs['y1'], 0, $this->svgunit, false):0);
+ $x2 = (isset($attribs['x2'])?$this->getHTMLUnitToUnits($attribs['x2'], 0, $this->svgunit, false):0);
+ $y2 = (isset($attribs['y2'])?$this->getHTMLUnitToUnits($attribs['y2'], 0, $this->svgunit, false):0);
+ $x = $x1;
+ $y = $y1;
+ $w = abs($x2 - $x1);
+ $h = abs($y2 - $y1);
+ if (!$clipping) {
+ $this->StartTransform();
+ $this->SVGTransform($tm);
+ $obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'Line', array($x1, $y1, $x2, $y2));
+ $this->Line($x1, $y1, $x2, $y2);
+ $this->StopTransform();
+ }
+ break;
+ }
+ case 'polyline':
+ case 'polygon': {
+ if ($invisible) {
+ break;
+ }
+ $points = (isset($attribs['points'])?$attribs['points']:'0 0');
+ $points = trim($points);
+ // note that point may use a complex syntax not covered here
+ $points = preg_split('/[\,\s]+/si', $points);
+ if (count($points) < 4) {
+ break;
+ }
+ $p = array();
+ $xmin = 2147483647;
+ $xmax = 0;
+ $ymin = 2147483647;
+ $ymax = 0;
+ foreach ($points as $key => $val) {
+ $p[$key] = $this->getHTMLUnitToUnits($val, 0, $this->svgunit, false);
+ if (($key % 2) == 0) {
+ // X coordinate
+ $xmin = min($xmin, $p[$key]);
+ $xmax = max($xmax, $p[$key]);
+ } else {
+ // Y coordinate
+ $ymin = min($ymin, $p[$key]);
+ $ymax = max($ymax, $p[$key]);
+ }
+ }
+ $x = $xmin;
+ $y = $ymin;
+ $w = ($xmax - $xmin);
+ $h = ($ymax - $ymin);
+ if ($name == 'polyline') {
+ $this->StartTransform();
+ $this->SVGTransform($tm);
+ $obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'PolyLine', array($p, 'CNZ'));
+ if (!empty($obstyle)) {
+ $this->PolyLine($p, $obstyle, array(), array());
+ }
+ $this->StopTransform();
+ } else { // polygon
+ if ($clipping) {
+ $this->SVGTransform($tm);
+ $this->Polygon($p, 'CNZ', array(), array(), true);
+ } else {
+ $this->StartTransform();
+ $this->SVGTransform($tm);
+ $obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'Polygon', array($p, 'CNZ'));
+ if (!empty($obstyle)) {
+ $this->Polygon($p, $obstyle, array(), array(), true);
+ }
+ $this->StopTransform();
+ }
+ }
+ break;
+ }
+ // image
+ case 'image': {
+ if ($invisible) {
+ break;
+ }
+ if (!isset($attribs['xlink:href']) OR empty($attribs['xlink:href'])) {
+ break;
+ }
+ $x = (isset($attribs['x'])?$this->getHTMLUnitToUnits($attribs['x'], 0, $this->svgunit, false):0);
+ $y = (isset($attribs['y'])?$this->getHTMLUnitToUnits($attribs['y'], 0, $this->svgunit, false):0);
+ $w = (isset($attribs['width'])?$this->getHTMLUnitToUnits($attribs['width'], 0, $this->svgunit, false):0);
+ $h = (isset($attribs['height'])?$this->getHTMLUnitToUnits($attribs['height'], 0, $this->svgunit, false):0);
+ $img = $attribs['xlink:href'];
+ if (!$clipping) {
+ $this->StartTransform();
+ $this->SVGTransform($tm);
+ $obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h);
+ if (preg_match('/^data:image\/[^;]+;base64,/', $img, $m) > 0) {
+ // embedded image encoded as base64
+ $img = '@'.base64_decode(substr($img, strlen($m[0])));
+ } else {
+ // fix image path
+ if (!TCPDF_STATIC::empty_string($this->svgdir) AND (($img{0} == '.') OR (basename($img) == $img))) {
+ // replace relative path with full server path
+ $img = $this->svgdir.'/'.$img;
+ }
+ if (($img[0] == '/') AND !empty($_SERVER['DOCUMENT_ROOT']) AND ($_SERVER['DOCUMENT_ROOT'] != '/')) {
+ $findroot = strpos($img, $_SERVER['DOCUMENT_ROOT']);
+ if (($findroot === false) OR ($findroot > 1)) {
+ if (substr($_SERVER['DOCUMENT_ROOT'], -1) == '/') {
+ $img = substr($_SERVER['DOCUMENT_ROOT'], 0, -1).$img;
+ } else {
+ $img = $_SERVER['DOCUMENT_ROOT'].$img;
+ }
+ }
+ }
+ $img = urldecode($img);
+ $testscrtype = @parse_url($img);
+ if (!isset($testscrtype['query']) OR empty($testscrtype['query'])) {
+ // convert URL to server path
+ $img = str_replace(K_PATH_URL, K_PATH_MAIN, $img);
+ }
+ }
+ // get image type
+ $imgtype = TCPDF_IMAGES::getImageFileType($img);
+ if (($imgtype == 'eps') OR ($imgtype == 'ai')) {
+ $this->ImageEps($img, $x, $y, $w, $h);
+ } elseif ($imgtype == 'svg') {
+ $this->ImageSVG($img, $x, $y, $w, $h);
+ } else {
+ $this->Image($img, $x, $y, $w, $h);
+ }
+ $this->StopTransform();
+ }
+ break;
+ }
+ // text
+ case 'text':
+ case 'tspan': {
+ // only basic support - advanced features must be implemented
+ $this->svgtextmode['invisible'] = $invisible;
+ if ($invisible) {
+ break;
+ }
+ array_push($this->svgstyles, $svgstyle);
+ if (isset($attribs['x'])) {
+ $x = $this->getHTMLUnitToUnits($attribs['x'], 0, $this->svgunit, false);
+ } elseif ($name == 'tspan') {
+ $x = $this->x;
+ } else {
+ $x = 0;
+ }
+ if (isset($attribs['dx'])) {
+ $x += $this->getHTMLUnitToUnits($attribs['dx'], 0, $this->svgunit, false);
+ }
+ if (isset($attribs['y'])) {
+ $y = $this->getHTMLUnitToUnits($attribs['y'], 0, $this->svgunit, false);
+ } elseif ($name == 'tspan') {
+ $y = $this->y;
+ } else {
+ $y = 0;
+ }
+ if (isset($attribs['dy'])) {
+ $y += $this->getHTMLUnitToUnits($attribs['dy'], 0, $this->svgunit, false);
+ }
+ $svgstyle['text-color'] = $svgstyle['fill'];
+ $this->svgtext = '';
+ if (isset($svgstyle['text-anchor'])) {
+ $this->svgtextmode['text-anchor'] = $svgstyle['text-anchor'];
+ } else {
+ $this->svgtextmode['text-anchor'] = 'start';
+ }
+ if (isset($svgstyle['direction'])) {
+ if ($svgstyle['direction'] == 'rtl') {
+ $this->svgtextmode['rtl'] = true;
+ } else {
+ $this->svgtextmode['rtl'] = false;
+ }
+ } else {
+ $this->svgtextmode['rtl'] = false;
+ }
+ if (isset($svgstyle['stroke']) AND ($svgstyle['stroke'] != 'none') AND isset($svgstyle['stroke-width']) AND ($svgstyle['stroke-width'] > 0)) {
+ $this->svgtextmode['stroke'] = $this->getHTMLUnitToUnits($svgstyle['stroke-width'], 0, $this->svgunit, false);
+ } else {
+ $this->svgtextmode['stroke'] = false;
+ }
+ $this->StartTransform();
+ $this->SVGTransform($tm);
+ $obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, 1, 1);
+ $this->x = $x;
+ $this->y = $y;
+ break;
+ }
+ // use
+ case 'use': {
+ if (isset($attribs['xlink:href']) AND !empty($attribs['xlink:href'])) {
+ $svgdefid = substr($attribs['xlink:href'], 1);
+ if (isset($this->svgdefs[$svgdefid])) {
+ $use = $this->svgdefs[$svgdefid];
+ if (isset($attribs['xlink:href'])) {
+ unset($attribs['xlink:href']);
+ }
+ if (isset($attribs['id'])) {
+ unset($attribs['id']);
+ }
+ $attribs = array_merge($attribs, $use['attribs']);
+ $this->startSVGElementHandler($parser, $use['name'], $attribs);
+ }
+ }
+ break;
+ }
+ default: {
+ break;
+ }
+ } // end of switch
+ }
+
+ /**
+ * Sets the closing SVG element handler function for the XML parser.
+ * @param $parser (resource) The first parameter, parser, is a reference to the XML parser calling the handler.
+ * @param $name (string) The second parameter, name, contains the name of the element for which this handler is called. If case-folding is in effect for this parser, the element name will be in uppercase letters.
+ * @author Nicola Asuni
+ * @since 5.0.000 (2010-05-02)
+ * @protected
+ */
+ protected function endSVGElementHandler($parser, $name) {
+ switch($name) {
+ case 'defs': {
+ $this->svgdefsmode = false;
+ break;
+ }
+ // clipPath
+ case 'clipPath': {
+ $this->svgclipmode = false;
+ break;
+ }
+ case 'g': {
+ // ungroup: remove last style from array
+ array_pop($this->svgstyles);
+ $this->StopTransform();
+ break;
+ }
+ case 'text':
+ case 'tspan': {
+ if ($this->svgtextmode['invisible']) {
+ // This implementation must be fixed to following the rule:
+ // If the 'visibility' property is set to hidden on a 'tspan', 'tref' or 'altGlyph' element, then the text is invisible but still takes up space in text layout calculations.
+ break;
+ }
+ // print text
+ $text = $this->svgtext;
+ //$text = $this->stringTrim($text);
+ $textlen = $this->GetStringWidth($text);
+ if ($this->svgtextmode['text-anchor'] != 'start') {
+ // check if string is RTL text
+ if ($this->svgtextmode['text-anchor'] == 'end') {
+ if ($this->svgtextmode['rtl']) {
+ $this->x += $textlen;
+ } else {
+ $this->x -= $textlen;
+ }
+ } elseif ($this->svgtextmode['text-anchor'] == 'middle') {
+ if ($this->svgtextmode['rtl']) {
+ $this->x += ($textlen / 2);
+ } else {
+ $this->x -= ($textlen / 2);
+ }
+ }
+ }
+ $textrendermode = $this->textrendermode;
+ $textstrokewidth = $this->textstrokewidth;
+ $this->setTextRenderingMode($this->svgtextmode['stroke'], true, false);
+ if ($name == 'text') {
+ // store current coordinates
+ $tmpx = $this->x;
+ $tmpy = $this->y;
+ }
+ $this->Cell($textlen, 0, $text, 0, 0, '', false, '', 0, false, 'L', 'T');
+ if ($name == 'text') {
+ // restore coordinates
+ $this->x = $tmpx;
+ $this->y = $tmpy;
+ }
+ // restore previous rendering mode
+ $this->textrendermode = $textrendermode;
+ $this->textstrokewidth = $textstrokewidth;
+ $this->svgtext = '';
+ $this->StopTransform();
+ array_pop($this->svgstyles);
+ break;
+ }
+ default: {
+ break;
+ }
+ }
+ }
+
+ /**
+ * Sets the character data handler function for the XML parser.
+ * @param $parser (resource) The first parameter, parser, is a reference to the XML parser calling the handler.
+ * @param $data (string) The second parameter, data, contains the character data as a string.
+ * @author Nicola Asuni
+ * @since 5.0.000 (2010-05-02)
+ * @protected
+ */
+ protected function segSVGContentHandler($parser, $data) {
+ $this->svgtext .= $data;
+ }
+
+ // --- END SVG METHODS -----------------------------------------------------
+
+} // END OF TCPDF CLASS
+
+//============================================================+
+// END OF FILE
+//============================================================+
diff --git a/libraries/tcpdf/tcpdf_autoconfig.php b/libraries/tcpdf/tcpdf_autoconfig.php
new file mode 100644
index 0000000000..2fc1da6da5
--- /dev/null
+++ b/libraries/tcpdf/tcpdf_autoconfig.php
@@ -0,0 +1,233 @@
+<?php
+//============================================================+
+// File name : tcpdf_autoconfig.php
+// Version : 1.0.000
+// Begin : 2013-05-16
+// Last Update : 2013-05-16
+// Authors : Nicola Asuni - Tecnick.com LTD - www.tecnick.com - info@tecnick.com
+// License : GNU-LGPL v3 (http://www.gnu.org/copyleft/lesser.html)
+// -------------------------------------------------------------------
+// Copyright (C) 2011-2013 Nicola Asuni - Tecnick.com LTD
+//
+// This file is part of TCPDF software library.
+//
+// TCPDF 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 3 of the
+// License, or (at your option) any later version.
+//
+// TCPDF 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 License
+// along with TCPDF. If not, see
+// <http://www.tecnick.com/pagefiles/tcpdf/LICENSE.TXT>.
+//
+// See LICENSE.TXT file for more information.
+// -------------------------------------------------------------------
+//
+// Description : Try to automatically configure some TCPDF
+// constants if not defined.
+//
+//============================================================+
+
+/**
+ * @file
+ * Try to automatically configure some TCPDF constants if not defined.
+ * @package com.tecnick.tcpdf
+ * @version 1.0.000
+ */
+
+// DOCUMENT_ROOT fix for IIS Webserver
+if ((!isset($_SERVER['DOCUMENT_ROOT'])) OR (empty($_SERVER['DOCUMENT_ROOT']))) {
+ if(isset($_SERVER['SCRIPT_FILENAME'])) {
+ $_SERVER['DOCUMENT_ROOT'] = str_replace( '\\', '/', substr($_SERVER['SCRIPT_FILENAME'], 0, 0-strlen($_SERVER['PHP_SELF'])));
+ } elseif(isset($_SERVER['PATH_TRANSLATED'])) {
+ $_SERVER['DOCUMENT_ROOT'] = str_replace( '\\', '/', substr(str_replace('\\\\', '\\', $_SERVER['PATH_TRANSLATED']), 0, 0-strlen($_SERVER['PHP_SELF'])));
+ } else {
+ // define here your DOCUMENT_ROOT path if the previous fails (e.g. '/var/www')
+ $_SERVER['DOCUMENT_ROOT'] = '/';
+ }
+}
+$_SERVER['DOCUMENT_ROOT'] = str_replace('//', '/', $_SERVER['DOCUMENT_ROOT']);
+if (substr($_SERVER['DOCUMENT_ROOT'], -1) != '/') {
+ $_SERVER['DOCUMENT_ROOT'] .= '/';
+}
+
+// Load main configuration file only if the K_TCPDF_EXTERNAL_CONFIG constant is set to false.
+if (!defined('K_TCPDF_EXTERNAL_CONFIG') OR !K_TCPDF_EXTERNAL_CONFIG) {
+ // define a list of default config files in order of priority
+ $tcpdf_config_files = array(dirname(__FILE__).'/config/tcpdf_config.php', '/etc/php-tcpdf/tcpdf_config.php', '/etc/tcpdf/tcpdf_config.php', '/etc/tcpdf_config.php');
+ foreach ($tcpdf_config_files as $tcpdf_config) {
+ if (@file_exists($tcpdf_config) AND is_readable($tcpdf_config)) {
+ require_once($tcpdf_config);
+ break;
+ }
+ }
+}
+
+if (!defined('K_PATH_MAIN')) {
+ define ('K_PATH_MAIN', dirname(__FILE__).'/');
+}
+
+if (!defined('K_PATH_FONTS')) {
+ define ('K_PATH_FONTS', K_PATH_MAIN.'fonts/');
+}
+
+if (!defined('K_PATH_URL')) {
+ $k_path_url = K_PATH_MAIN; // default value for console mode
+ if (isset($_SERVER['HTTP_HOST']) AND (!empty($_SERVER['HTTP_HOST']))) {
+ if(isset($_SERVER['HTTPS']) AND (!empty($_SERVER['HTTPS'])) AND (strtolower($_SERVER['HTTPS']) != 'off')) {
+ $k_path_url = 'https://';
+ } else {
+ $k_path_url = 'http://';
+ }
+ $k_path_url .= $_SERVER['HTTP_HOST'];
+ $k_path_url .= str_replace( '\\', '/', substr(K_PATH_MAIN, (strlen($_SERVER['DOCUMENT_ROOT']) - 1)));
+ }
+ define ('K_PATH_URL', $k_path_url);
+}
+
+if (!defined('K_PATH_IMAGES')) {
+ $tcpdf_images_dirs = array(K_PATH_MAIN.'examples/images/', K_PATH_MAIN.'images/', '/usr/share/doc/php-tcpdf/examples/images/', '/usr/share/doc/tcpdf/examples/images/', '/usr/share/doc/php/tcpdf/examples/images/', '/var/www/tcpdf/images/', '/var/www/html/tcpdf/images/', '/usr/local/apache2/htdocs/tcpdf/images/', K_PATH_MAIN);
+ foreach ($tcpdf_images_dirs as $tcpdf_images_path) {
+ if (@file_exists($tcpdf_images_path)) {
+ break;
+ }
+ }
+ define ('K_PATH_IMAGES', $tcpdf_images_path);
+}
+
+if (!defined('PDF_HEADER_LOGO')) {
+ $tcpdf_header_logo = '';
+ if (@file_exists(K_PATH_IMAGES.'tcpdf_logo.jpg')) {
+ $tcpdf_header_logo = 'tcpdf_logo.jpg';
+ }
+ define ('PDF_HEADER_LOGO', $tcpdf_header_logo);
+}
+
+if (!defined('PDF_HEADER_LOGO_WIDTH')) {
+ if (!empty($tcpdf_header_logo)) {
+ define ('PDF_HEADER_LOGO_WIDTH', 30);
+ } else {
+ define ('PDF_HEADER_LOGO_WIDTH', 0);
+ }
+}
+
+if (!defined('K_PATH_CACHE')) {
+ define ('K_PATH_CACHE', sys_get_temp_dir().'/');
+}
+
+if (!defined('K_BLANK_IMAGE')) {
+ define ('K_BLANK_IMAGE', '_blank.png');
+}
+
+if (!defined('PDF_PAGE_FORMAT')) {
+ define ('PDF_PAGE_FORMAT', 'A4');
+}
+
+if (!defined('PDF_PAGE_ORIENTATION')) {
+ define ('PDF_PAGE_ORIENTATION', 'P');
+}
+
+if (!defined('PDF_CREATOR')) {
+ define ('PDF_CREATOR', 'TCPDF');
+}
+
+if (!defined('PDF_AUTHOR')) {
+ define ('PDF_AUTHOR', 'TCPDF');
+}
+
+if (!defined('PDF_HEADER_TITLE')) {
+ define ('PDF_HEADER_TITLE', 'TCPDF Example');
+}
+
+if (!defined('PDF_HEADER_STRING')) {
+ define ('PDF_HEADER_STRING', "by Nicola Asuni - Tecnick.com\nwww.tcpdf.org");
+}
+
+if (!defined('PDF_UNIT')) {
+ define ('PDF_UNIT', 'mm');
+}
+
+if (!defined('PDF_MARGIN_HEADER')) {
+ define ('PDF_MARGIN_HEADER', 5);
+}
+
+if (!defined('PDF_MARGIN_FOOTER')) {
+ define ('PDF_MARGIN_FOOTER', 10);
+}
+
+if (!defined('PDF_MARGIN_TOP')) {
+ define ('PDF_MARGIN_TOP', 27);
+}
+
+if (!defined('PDF_MARGIN_BOTTOM')) {
+ define ('PDF_MARGIN_BOTTOM', 25);
+}
+
+if (!defined('PDF_MARGIN_LEFT')) {
+ define ('PDF_MARGIN_LEFT', 15);
+}
+
+if (!defined('PDF_MARGIN_RIGHT')) {
+ define ('PDF_MARGIN_RIGHT', 15);
+}
+
+if (!defined('PDF_FONT_NAME_MAIN')) {
+ define ('PDF_FONT_NAME_MAIN', 'helvetica');
+}
+
+if (!defined('PDF_FONT_SIZE_MAIN')) {
+ define ('PDF_FONT_SIZE_MAIN', 10);
+}
+
+if (!defined('PDF_FONT_NAME_DATA')) {
+ define ('PDF_FONT_NAME_DATA', 'helvetica');
+}
+
+if (!defined('PDF_FONT_SIZE_DATA')) {
+ define ('PDF_FONT_SIZE_DATA', 8);
+}
+
+if (!defined('PDF_FONT_MONOSPACED')) {
+ define ('PDF_FONT_MONOSPACED', 'courier');
+}
+
+if (!defined('PDF_IMAGE_SCALE_RATIO')) {
+ define ('PDF_IMAGE_SCALE_RATIO', 1.25);
+}
+
+if (!defined('HEAD_MAGNIFICATION')) {
+ define('HEAD_MAGNIFICATION', 1.1);
+}
+
+if (!defined('K_CELL_HEIGHT_RATIO')) {
+ define('K_CELL_HEIGHT_RATIO', 1.25);
+}
+
+if (!defined('K_TITLE_MAGNIFICATION')) {
+ define('K_TITLE_MAGNIFICATION', 1.3);
+}
+
+if (!defined('K_SMALL_RATIO')) {
+ define('K_SMALL_RATIO', 2/3);
+}
+
+if (!defined('K_THAI_TOPCHARS')) {
+ define('K_THAI_TOPCHARS', true);
+}
+
+if (!defined('K_TCPDF_CALLS_IN_HTML')) {
+ define('K_TCPDF_CALLS_IN_HTML', true);
+}
+
+if (!defined('K_TCPDF_THROW_EXCEPTION_ERROR')) {
+ define('K_TCPDF_THROW_EXCEPTION_ERROR', false);
+}
+
+//============================================================+
+// END OF FILE
+//============================================================+
diff --git a/libraries/transformations.lib.php b/libraries/transformations.lib.php
new file mode 100644
index 0000000000..ddf1c60aac
--- /dev/null
+++ b/libraries/transformations.lib.php
@@ -0,0 +1,419 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Set of functions used with the relation and pdf feature
+ *
+ * This file also provides basic functions to use in other plungins!
+ * These are declared in the 'GLOBAL Plugin functions' section
+ *
+ * Please use short and expressive names.
+ * For now, special characters which aren't allowed in
+ * filenames or functions should not be used.
+ *
+ * Please provide a comment for your function,
+ * what it does and what parameters are available.
+ *
+ * @package PhpMyAdmin
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ * Returns array of options from string with options separated by comma,
+ * removes quotes
+ *
+ * <code>
+ * PMA_Transformation_getOptions("'option ,, quoted',abd,'2,3',");
+ * // array {
+ * // 'option ,, quoted',
+ * // 'abc',
+ * // '2,3',
+ * // '',
+ * // }
+ * </code>
+ *
+ * @param string $option_string comma separated options
+ *
+ * @return array options
+ */
+function PMA_Transformation_getOptions($option_string)
+{
+ $result = array();
+
+ if (! strlen($option_string)
+ || ! $transform_options = preg_split('/,/', $option_string)
+ ) {
+ return $result;
+ }
+
+ while (($option = array_shift($transform_options)) !== null) {
+ $trimmed = trim($option);
+ if (strlen($trimmed) > 1
+ && $trimmed[0] == "'"
+ && $trimmed[strlen($trimmed) - 1] == "'"
+ ) {
+ // '...'
+ $option = substr($trimmed, 1, -1);
+ } elseif (isset($trimmed[0]) && $trimmed[0] == "'") {
+ // '...,
+ $trimmed = ltrim($option);
+ while (($option = array_shift($transform_options)) !== null) {
+ // ...,
+ $trimmed .= ',' . $option;
+ $rtrimmed = rtrim($trimmed);
+ if ($rtrimmed[strlen($rtrimmed) - 1] == "'") {
+ // ,...'
+ break;
+ }
+ }
+ $option = substr($rtrimmed, 1, -1);
+ }
+ $result[] = stripslashes($option);
+ }
+
+ return $result;
+}
+
+/**
+ * Gets all available MIME-types
+ *
+ * @access public
+ * @staticvar array mimetypes
+ * @return array array[mimetype], array[transformation]
+ */
+function PMA_getAvailableMIMEtypes()
+{
+ static $stack = null;
+
+ if (null !== $stack) {
+ return $stack;
+ }
+
+ $stack = array();
+ $filestack = array();
+
+ $handle = opendir('./libraries/plugins/transformations');
+
+ if (! $handle) {
+ return $stack;
+ }
+
+ while ($file = readdir($handle)) {
+ $filestack[] = $file;
+ }
+
+ closedir($handle);
+ sort($filestack);
+
+ foreach ($filestack as $file) {
+ if (preg_match('|^.*_.*_.*\.class\.php$|', $file)) {
+ // File contains transformation functions.
+ $parts = explode('_', str_replace('.class.php', '', $file));
+ $mimetype = $parts[0] . "/" . $parts[1];
+ $stack['mimetype'][$mimetype] = $mimetype;
+ $stack['transformation'][] = $mimetype . ': ' . $parts[2];
+ $stack['transformation_file'][] = $file;
+
+ } elseif (preg_match('|^.*\.class.php$|', $file)) {
+ // File is a plain mimetype, no functions.
+ $base = str_replace('.class.php', '', $file);
+
+ if ($base != 'global') {
+ $mimetype = str_replace('_', '/', $base);
+ $stack['mimetype'][$mimetype] = $mimetype;
+ $stack['empty_mimetype'][$mimetype] = $mimetype;
+ }
+ }
+ }
+
+ return $stack;
+}
+
+/**
+ * Returns the description of the transformation
+ *
+ * @param string $file transformation file
+ * @param boolean $html_formatted whether the description should be formatted
+ * as HTML
+ *
+ * @return String the description of the transformation
+ */
+function PMA_getTransformationDescription($file, $html_formatted = true)
+{
+ // get the transformation class name
+ $class_name = explode(".class.php", $file);
+ $class_name = $class_name[0];
+
+ // include and instantiate the class
+ include_once 'libraries/plugins/transformations/' . $file;
+ return $class_name::getInfo();
+}
+
+/**
+ * Gets the mimetypes for all columns of a table
+ *
+ * @param string $db the name of the db to check for
+ * @param string $table the name of the table to check for
+ * @param boolean $strict whether to include only results having a mimetype set
+ *
+ * @access public
+ *
+ * @return array [field_name][field_key] = field_value
+ */
+function PMA_getMIME($db, $table, $strict = false)
+{
+ $cfgRelation = PMA_getRelationsParam();
+
+ if (! $cfgRelation['commwork']) {
+ return false;
+ }
+
+ $com_qry = '
+ SELECT `column_name`,
+ `mimetype`,
+ `transformation`,
+ `transformation_options`
+ FROM ' . PMA_Util::backquote($cfgRelation['db']) . '.'
+ . PMA_Util::backquote($cfgRelation['column_info']) . '
+ WHERE `db_name` = \'' . PMA_Util::sqlAddSlashes($db) . '\'
+ AND `table_name` = \'' . PMA_Util::sqlAddSlashes($table) . '\'
+ AND ( `mimetype` != \'\'' . (!$strict ? '
+ OR `transformation` != \'\'
+ OR `transformation_options` != \'\'' : '') . ')';
+ $result = $GLOBALS['dbi']->fetchResult(
+ $com_qry, 'column_name', null, $GLOBALS['controllink']
+ );
+
+ foreach ($result as $column => $values) {
+ // replacements in mimetype and transformation
+ $values = str_replace("jpeg", "JPEG", $values);
+ $values = str_replace("png", "PNG", $values);
+ $values = str_replace("octet-stream", "Octetstream", $values);
+
+ // convert mimetype to new format (f.e. Text_Plain, etc)
+ $delimiter_space = '- ';
+ $delimiter = "_";
+ $values['mimetype'] = str_replace(
+ $delimiter_space,
+ $delimiter,
+ ucwords(
+ str_replace(
+ $delimiter,
+ $delimiter_space,
+ $values['mimetype']
+ )
+ )
+ );
+
+ // convert transformation to new format (class name)
+ // f.e. Text_Plain_Substring.class.php
+ $values = str_replace("__", "_", $values);
+ $values = str_replace(".inc.php", ".class.php", $values);
+
+ $values['transformation'] = str_replace(
+ $delimiter_space,
+ $delimiter,
+ ucwords(
+ str_replace(
+ $delimiter,
+ $delimiter_space,
+ $values['transformation']
+ )
+ )
+ );
+
+ $result[$column] = $values;
+ }
+
+ return $result;
+} // end of the 'PMA_getMIME()' function
+
+/**
+ * Set a single mimetype to a certain value.
+ *
+ * @param string $db the name of the db
+ * @param string $table the name of the table
+ * @param string $key the name of the column
+ * @param string $mimetype the mimetype of the column
+ * @param string $transformation the transformation of the column
+ * @param string $transformation_options the transformation options of the column
+ * @param boolean $forcedelete force delete, will erase any existing
+ * comments for this column
+ *
+ * @access public
+ *
+ * @return boolean true, if comment-query was made.
+ */
+function PMA_setMIME($db, $table, $key, $mimetype, $transformation,
+ $transformation_options, $forcedelete = false
+) {
+ $cfgRelation = PMA_getRelationsParam();
+
+ if (! $cfgRelation['commwork']) {
+ return false;
+ }
+
+ // convert mimetype to old format (f.e. text_plain)
+ $mimetype = strtolower($mimetype);
+ // old format has octet-stream instead of octetstream for mimetype
+ if (strstr($mimetype, "octetstream")) {
+ $mimetype = "application_octet-stream";
+ }
+
+ // convert transformation to old format (f.e. text_plain__substring.inc.php)
+ $transformation = strtolower($transformation);
+ $transformation = str_replace(".class.php", ".inc.php", $transformation);
+ $last_pos = strrpos($transformation, "_");
+ $transformation = substr($transformation, 0, $last_pos) . "_"
+ . substr($transformation, $last_pos);
+
+ $test_qry = '
+ SELECT `mimetype`,
+ `comment`
+ FROM ' . PMA_Util::backquote($cfgRelation['db']) . '.'
+ . PMA_Util::backquote($cfgRelation['column_info']) . '
+ WHERE `db_name` = \'' . PMA_Util::sqlAddSlashes($db) . '\'
+ AND `table_name` = \'' . PMA_Util::sqlAddSlashes($table) . '\'
+ AND `column_name` = \'' . PMA_Util::sqlAddSlashes($key) . '\'';
+
+ $test_rs = PMA_queryAsControlUser(
+ $test_qry, true, PMA_DatabaseInterface::QUERY_STORE
+ );
+
+ if ($test_rs && $GLOBALS['dbi']->numRows($test_rs) > 0) {
+ $row = @$GLOBALS['dbi']->fetchAssoc($test_rs);
+ $GLOBALS['dbi']->freeResult($test_rs);
+
+ if (! $forcedelete
+ && (strlen($mimetype) || strlen($transformation)
+ || strlen($transformation_options) || strlen($row['comment']))
+ ) {
+ $upd_query = 'UPDATE ' . PMA_Util::backquote($cfgRelation['db']) . '.'
+ . PMA_Util::backquote($cfgRelation['column_info'])
+ . ' SET '
+ . '`mimetype` = \''
+ . PMA_Util::sqlAddSlashes($mimetype) . '\', '
+ . '`transformation` = \''
+ . PMA_Util::sqlAddSlashes($transformation) . '\', '
+ . '`transformation_options` = \''
+ . PMA_Util::sqlAddSlashes($transformation_options) . '\'';
+ } else {
+ $upd_query = 'DELETE FROM ' . PMA_Util::backquote($cfgRelation['db'])
+ . '.' . PMA_Util::backquote($cfgRelation['column_info']);
+ }
+ $upd_query .= '
+ WHERE `db_name` = \'' . PMA_Util::sqlAddSlashes($db) . '\'
+ AND `table_name` = \'' . PMA_Util::sqlAddSlashes($table) . '\'
+ AND `column_name` = \'' . PMA_Util::sqlAddSlashes($key) . '\'';
+ } elseif (strlen($mimetype)
+ || strlen($transformation)
+ || strlen($transformation_options)
+ ) {
+
+ $upd_query = 'INSERT INTO ' . PMA_Util::backquote($cfgRelation['db'])
+ . '.' . PMA_Util::backquote($cfgRelation['column_info'])
+ . ' (db_name, table_name, column_name, mimetype, '
+ . 'transformation, transformation_options) '
+ . ' VALUES('
+ . '\'' . PMA_Util::sqlAddSlashes($db) . '\','
+ . '\'' . PMA_Util::sqlAddSlashes($table) . '\','
+ . '\'' . PMA_Util::sqlAddSlashes($key) . '\','
+ . '\'' . PMA_Util::sqlAddSlashes($mimetype) . '\','
+ . '\'' . PMA_Util::sqlAddSlashes($transformation) . '\','
+ . '\'' . PMA_Util::sqlAddSlashes($transformation_options) . '\')';
+ }
+
+ if (isset($upd_query)) {
+ return PMA_queryAsControlUser($upd_query);
+ } else {
+ return false;
+ }
+} // end of 'PMA_setMIME()' function
+
+
+/**
+ * GLOBAL Plugin functions
+ */
+
+
+/**
+ * Replaces "[__BUFFER__]" occurences found in $options['string'] with the text
+ * in $buffer, after performing a regular expression search and replace on
+ * $buffer using $options['regex'] and $options['regex_replace'].
+ *
+ * @param string $buffer text that will be replaced in $options['string'],
+ * after being formatted
+ * @param array $options the options required to format $buffer
+ * = array (
+ * 'string' => 'string', // text containing "[__BUFFER__]"
+ * 'regex' => 'mixed', // the pattern to search for
+ * 'regex_replace' => 'mixed', // string or array of strings to replace
+ * // with
+ * );
+ *
+ * @return string containing the text with all the replacements
+ */
+function PMA_Transformation_globalHtmlReplace($buffer, $options = array())
+{
+ if ( ! isset($options['string']) ) {
+ $options['string'] = '';
+ }
+
+ if (isset($options['regex']) && isset($options['regex_replace'])) {
+ $buffer = preg_replace(
+ '@' . str_replace('@', '\@', $options['regex']) . '@si',
+ $options['regex_replace'],
+ $buffer
+ );
+ }
+
+ // Replace occurences of [__BUFFER__] with actual text
+ $return = str_replace("[__BUFFER__]", $buffer, $options['string']);
+ return $return;
+}
+
+
+/**
+ * Delete related transformation details
+ * after deleting database. table or column
+ *
+ * @param string $db Database name
+ * @param string $table Table name
+ * @param string $column Column name
+ *
+ * @return boolean State of the query execution
+ */
+function PMA_clearTransformations($db, $table = '', $column = '')
+{
+ $cfgRelation = PMA_getRelationsParam();
+
+ if (! isset($cfgRelation['column_info'])) {
+ return false;
+ }
+
+ $delete_sql = 'DELETE FROM '
+ . PMA_Util::backquote($cfgRelation['db']) . '.'
+ . PMA_Util::backquote($cfgRelation['column_info'])
+ . ' WHERE ';
+
+ if (($column != '') && ($table != '')) {
+
+ $delete_sql .= '`db_name` = \'' . $db . '\' AND '
+ . '`table_name` = \'' . $table . '\' AND '
+ . '`column_name` = \'' . $column . '\' ';
+
+ } else if ($table != '') {
+
+ $delete_sql .= '`db_name` = \'' . $db . '\' AND '
+ . '`table_name` = \'' . $table . '\' ';
+
+ } else {
+ $delete_sql .= '`db_name` = \'' . $db . '\' ';
+ }
+
+ return $GLOBALS['dbi']->tryQuery($delete_sql);
+
+}
+
+?>
diff --git a/libraries/url_generating.lib.php b/libraries/url_generating.lib.php
new file mode 100644
index 0000000000..c19af1c891
--- /dev/null
+++ b/libraries/url_generating.lib.php
@@ -0,0 +1,303 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * URL/hidden inputs generating.
+ *
+ * @package PhpMyAdmin
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ * Generates text with hidden inputs.
+ *
+ * @param string $db optional database name
+ * (can also be an array of parameters)
+ * @param string $table optional table name
+ * @param int $indent indenting level
+ * @param string|array $skip do not generate a hidden field for this parameter
+ * (can be an array of strings)
+ *
+ * @see PMA_URL_getCommon()
+ *
+ * @return string string with input fields
+ *
+ * @global string the current language
+ * @global string the current conversion charset
+ * @global string the current connection collation
+ * @global string the current server
+ * @global array the configuration array
+ * @global boolean whether recoding is allowed or not
+ *
+ * @access public
+ */
+function PMA_URL_getHiddenInputs($db = '', $table = '',
+ $indent = 0, $skip = array()
+) {
+ if (is_array($db)) {
+ $params =& $db;
+ $_indent = empty($table) ? $indent : $table;
+ $_skip = empty($indent) ? $skip : $indent;
+ $indent =& $_indent;
+ $skip =& $_skip;
+ } else {
+ $params = array();
+ if (strlen($db)) {
+ $params['db'] = $db;
+ }
+ if (strlen($table)) {
+ $params['table'] = $table;
+ }
+ }
+
+ if (! empty($GLOBALS['server'])
+ && $GLOBALS['server'] != $GLOBALS['cfg']['ServerDefault']
+ ) {
+ $params['server'] = $GLOBALS['server'];
+ }
+ if (empty($_COOKIE['pma_lang']) && ! empty($GLOBALS['lang'])) {
+ $params['lang'] = $GLOBALS['lang'];
+ }
+ if (empty($_COOKIE['pma_collation_connection'])
+ && ! empty($GLOBALS['collation_connection'])
+ ) {
+ $params['collation_connection'] = $GLOBALS['collation_connection'];
+ }
+
+ $params['token'] = $_SESSION[' PMA_token '];
+
+ if (! is_array($skip)) {
+ if (isset($params[$skip])) {
+ unset($params[$skip]);
+ }
+ } else {
+ foreach ($skip as $skipping) {
+ if (isset($params[$skipping])) {
+ unset($params[$skipping]);
+ }
+ }
+ }
+
+ return PMA_getHiddenFields($params);
+}
+
+/**
+ * create hidden form fields from array with name => value
+ *
+ * <code>
+ * $values = array(
+ * 'aaa' => aaa,
+ * 'bbb' => array(
+ * 'bbb_0',
+ * 'bbb_1',
+ * ),
+ * 'ccc' => array(
+ * 'a' => 'ccc_a',
+ * 'b' => 'ccc_b',
+ * ),
+ * );
+ * echo PMA_getHiddenFields($values);
+ *
+ * // produces:
+ * <input type="hidden" name="aaa" Value="aaa" />
+ * <input type="hidden" name="bbb[0]" Value="bbb_0" />
+ * <input type="hidden" name="bbb[1]" Value="bbb_1" />
+ * <input type="hidden" name="ccc[a]" Value="ccc_a" />
+ * <input type="hidden" name="ccc[b]" Value="ccc_b" />
+ * </code>
+ *
+ * @param array $values hidden values
+ * @param string $pre prefix
+ *
+ * @return string form fields of type hidden
+ */
+function PMA_getHiddenFields($values, $pre = '')
+{
+ $fields = '';
+
+ foreach ($values as $name => $value) {
+ if (! empty($pre)) {
+ $name = $pre. '[' . $name . ']';
+ }
+
+ if (is_array($value)) {
+ $fields .= PMA_getHiddenFields($value, $name);
+ } else {
+ // do not generate an ending "\n" because
+ // PMA_URL_getHiddenInputs() is sometimes called
+ // from a JS document.write()
+ $fields .= '<input type="hidden" name="' . htmlspecialchars($name)
+ . '" value="' . htmlspecialchars($value) . '" />';
+ }
+ }
+
+ return $fields;
+}
+
+/**
+ * Generates text with URL parameters.
+ *
+ * <code>
+ * // OLD (deprecated) style
+ * // note the ?
+ * echo 'script.php?' . PMA_URL_getCommon('mysql', 'rights');
+ * // produces with cookies enabled:
+ * // script.php?db=mysql&amp;table=rights
+ * // with cookies disabled:
+ * // script.php?server=1&amp;lang=en&amp;db=mysql&amp;table=rights
+ *
+ * // NEW style
+ * $params['myparam'] = 'myvalue';
+ * $params['db'] = 'mysql';
+ * $params['table'] = 'rights';
+ * // note the missing ?
+ * echo 'script.php' . PMA_URL_getCommon($params);
+ * // produces with cookies enabled:
+ * // script.php?myparam=myvalue&amp;db=mysql&amp;table=rights
+ * // with cookies disabled:
+ * // script.php?server=1&amp;lang=en&amp;myparam=myvalue&amp;db=mysql
+ * // &amp;table=rights
+ *
+ * // note the missing ?
+ * echo 'script.php' . PMA_URL_getCommon();
+ * // produces with cookies enabled:
+ * // script.php
+ * // with cookies disabled:
+ * // script.php?server=1&amp;lang=en
+ * </code>
+ *
+ * @param mixed assoc. array with url params or optional string with database name
+ * if first param is an array there is also an ? prefixed to the url
+ *
+ * @param string - if first param is array: 'html' to use htmlspecialchars()
+ * on the resulting URL (for a normal URL displayed in HTML)
+ * or something else to avoid using htmlspecialchars() (for
+ * a URL sent via a header); if not set,'html' is assumed
+ * - if first param is not array: optional table name
+ *
+ * @param string - if first param is array: optional character to
+ * use instead of '?'
+ * - if first param is not array: optional character to use
+ * instead of '&amp;' for dividing URL parameters
+ *
+ * @return string string with URL parameters
+ * @access public
+ */
+function PMA_URL_getCommon()
+{
+ $args = func_get_args();
+
+ if (isset($args[0]) && is_array($args[0])) {
+ // new style
+ $params = $args[0];
+
+ if (isset($args[1])) {
+ $encode = $args[1];
+ } else {
+ $encode = 'html';
+ }
+
+ if (isset($args[2])) {
+ $questionmark = $args[2];
+ } else {
+ $questionmark = '?';
+ }
+ } else {
+ // old style
+
+ if (PMA_isValid($args[0])) {
+ $params['db'] = $args[0];
+ }
+
+ if (PMA_isValid($args[1])) {
+ $params['table'] = $args[1];
+ }
+
+ if (isset($args[2]) && $args[2] !== '&amp;') {
+ $encode = 'text';
+ } else {
+ $encode = 'html';
+ }
+
+ $questionmark = '';
+ }
+
+ $separator = PMA_URL_getArgSeparator();
+
+ // avoid overwriting when creating navi panel links to servers
+ if (isset($GLOBALS['server'])
+ && $GLOBALS['server'] != $GLOBALS['cfg']['ServerDefault']
+ && ! isset($params['server'])
+ ) {
+ $params['server'] = $GLOBALS['server'];
+ }
+
+ if (empty($_COOKIE['pma_lang']) && ! empty($GLOBALS['lang'])) {
+ $params['lang'] = $GLOBALS['lang'];
+ }
+ if (empty($_COOKIE['pma_collation_connection'])
+ && ! empty($GLOBALS['collation_connection'])
+ ) {
+ $params['collation_connection'] = $GLOBALS['collation_connection'];
+ }
+
+ if (isset($_SESSION[' PMA_token '])) {
+ $params['token'] = $_SESSION[' PMA_token '];
+ }
+
+ if (empty($params)) {
+ return '';
+ }
+
+ $query = $questionmark . http_build_query($params, null, $separator);
+
+ if ($encode === 'html') {
+ $query = htmlspecialchars($query);
+ }
+
+ return $query;
+}
+
+/**
+ * Returns url separator
+ *
+ * extracted from arg_separator.input as set in php.ini
+ * we do not use arg_separator.output to avoid problems with &amp; and &
+ *
+ * @param string $encode whether to encode separator or not,
+ * currently 'none' or 'html'
+ *
+ * @return string character used for separating url parts usually ; or &
+ * @access public
+ */
+function PMA_URL_getArgSeparator($encode = 'none')
+{
+ static $separator = null;
+
+ if (null === $separator) {
+ // use separators defined by php, but prefer ';'
+ // as recommended by W3C
+ // (see http://www.w3.org/TR/1999/REC-html401-19991224/appendix/notes.html#h-B.2.2)
+ $php_arg_separator_input = ini_get('arg_separator.input');
+ if (strpos($php_arg_separator_input, ';') !== false) {
+ $separator = ';';
+ } elseif (strlen($php_arg_separator_input) > 0) {
+ $separator = $php_arg_separator_input{0};
+ } else {
+ $separator = '&';
+ }
+ }
+
+ switch ($encode) {
+ case 'html':
+ return htmlentities($separator);
+ break;
+ case 'text' :
+ case 'none' :
+ default :
+ return $separator;
+ }
+}
+
+?>
diff --git a/libraries/user_preferences.inc.php b/libraries/user_preferences.inc.php
new file mode 100644
index 0000000000..d338bcd224
--- /dev/null
+++ b/libraries/user_preferences.inc.php
@@ -0,0 +1,71 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Common header for user preferences pages
+ *
+ * @package PhpMyAdmin
+ */
+if (!defined('PHPMYADMIN')) {
+ exit;
+}
+// build user preferences menu
+
+$form_param = filter_input(INPUT_GET, 'form');
+if (! isset($forms[$form_param])) {
+ $forms_keys = array_keys($forms);
+ $form_param = array_shift($forms_keys);
+}
+$tabs_icons = array(
+ 'Features' => 'b_tblops.png',
+ 'Sql_queries' => 'b_sql.png',
+ 'Navi_panel' => 'b_select.png',
+ 'Main_panel' => 'b_props.png',
+ 'Import' => 'b_import.png',
+ 'Export' => 'b_export.png');
+echo '<ul id="topmenu2">';
+echo PMA_Util::getHtmlTab(
+ array(
+ 'link' => 'prefs_manage.php',
+ 'text' => __('Manage your settings')
+ )
+) . "\n";
+echo '<li>&nbsp; &nbsp;</li>' . "\n";
+$script_name = basename($GLOBALS['PMA_PHP_SELF']);
+foreach (array_keys($forms) as $formset) {
+ $tab = array(
+ 'link' => 'prefs_forms.php',
+ 'text' => PMA_lang('Form_' . $formset),
+ 'icon' => $tabs_icons[$formset],
+ 'active' => ($script_name == 'prefs_forms.php' && $formset == $form_param));
+ echo PMA_Util::getHtmlTab($tab, array('form' => $formset)) . "\n";
+}
+echo '</ul><div class="clearfloat"></div>';
+
+// show "configuration saved" message and reload navigation panel if needed
+if (!empty($_GET['saved'])) {
+ PMA_Message::rawSuccess(__('Configuration has been saved'))->display();
+}
+
+/* debug code
+$arr = $cf->getConfigArray();
+$arr2 = array();
+foreach ($arr as $k => $v) {
+ $arr2[] = "<b>$k</b> " . var_export($v, true);
+}
+$arr2 = implode(', ', $arr2);
+$arr2 .= '<br />Blacklist: ' . (empty($cfg['UserprefsDisallow'])
+ ? '<i>empty</i>'
+ : implode(', ', $cfg['UserprefsDisallow']));
+$msg = PMA_Message::notice('Settings: ' . $arr2);
+$msg->display();
+//*/
+
+// warn about using session storage for settings
+$cfgRelation = PMA_getRelationsParam();
+if (! $cfgRelation['userconfigwork']) {
+ $msg = __('Your preferences will be saved for current session only. Storing them permanently requires %sphpMyAdmin configuration storage%s.');
+ $msg = PMA_sanitize(
+ sprintf($msg, '[doc@linked-tables]', '[/doc]')
+ );
+ PMA_Message::notice($msg)->display();
+}
diff --git a/libraries/user_preferences.lib.php b/libraries/user_preferences.lib.php
new file mode 100644
index 0000000000..4b979145ad
--- /dev/null
+++ b/libraries/user_preferences.lib.php
@@ -0,0 +1,302 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Functions for displaying user preferences pages
+ *
+ * @package PhpMyAdmin
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ * Common initialization for user preferences modification pages
+ *
+ * @param ConfigFile $cf Config file instance
+ *
+ * @return void
+ */
+function PMA_userprefsPageInit(ConfigFile $cf)
+{
+ $forms_all_keys = PMA_readUserprefsFieldNames($GLOBALS['forms']);
+ $cf->resetConfigData(); // start with a clean instance
+ $cf->setAllowedKeys($forms_all_keys);
+ $cf->setCfgUpdateReadMapping(
+ array(
+ 'Server/hide_db' => 'Servers/1/hide_db',
+ 'Server/only_db' => 'Servers/1/only_db'
+ )
+ );
+ $cf->updateWithGlobalConfig($GLOBALS['cfg']);
+}
+
+/**
+ * Loads user preferences
+ *
+ * Returns an array:
+ * * config_data - path => value pairs
+ * * mtime - last modification time
+ * * type - 'db' (config read from pmadb) or 'session' (read from user session)
+ *
+ * @return array
+ */
+function PMA_loadUserprefs()
+{
+ $cfgRelation = PMA_getRelationsParam();
+ if (! $cfgRelation['userconfigwork']) {
+ // no pmadb table, use session storage
+ if (! isset($_SESSION['userconfig'])) {
+ $_SESSION['userconfig'] = array(
+ 'db' => array(),
+ 'ts' => time());
+ }
+ return array(
+ 'config_data' => $_SESSION['userconfig']['db'],
+ 'mtime' => $_SESSION['userconfig']['ts'],
+ 'type' => 'session');
+ }
+ // load configuration from pmadb
+ $query_table = PMA_Util::backquote($cfgRelation['db']) . '.'
+ . PMA_Util::backquote($cfgRelation['userconfig']);
+ $query = 'SELECT `config_data`, UNIX_TIMESTAMP(`timevalue`) ts'
+ . ' FROM ' . $query_table
+ . ' WHERE `username` = \''
+ . PMA_Util::sqlAddSlashes($cfgRelation['user'])
+ . '\'';
+ $row = $GLOBALS['dbi']->fetchSingleRow($query, 'ASSOC', $GLOBALS['controllink']);
+
+ return array(
+ 'config_data' => $row ? (array)json_decode($row['config_data']) : array(),
+ 'mtime' => $row ? $row['ts'] : time(),
+ 'type' => 'db');
+}
+
+/**
+ * Saves user preferences
+ *
+ * @param array $config_array configuration array
+ *
+ * @return true|PMA_Message
+ */
+function PMA_saveUserprefs(array $config_array)
+{
+ $cfgRelation = PMA_getRelationsParam();
+ $server = isset($GLOBALS['server'])
+ ? $GLOBALS['server']
+ : $GLOBALS['cfg']['ServerDefault'];
+ $cache_key = 'server_' . $server;
+ if (! $cfgRelation['userconfigwork']) {
+ // no pmadb table, use session storage
+ $_SESSION['userconfig'] = array(
+ 'db' => $config_array,
+ 'ts' => time());
+ if (isset($_SESSION['cache'][$cache_key]['userprefs'])) {
+ unset($_SESSION['cache'][$cache_key]['userprefs']);
+ }
+ return true;
+ }
+
+ // save configuration to pmadb
+ $query_table = PMA_Util::backquote($cfgRelation['db']) . '.'
+ . PMA_Util::backquote($cfgRelation['userconfig']);
+ $query = 'SELECT `username` FROM ' . $query_table
+ . ' WHERE `username` = \''
+ . PMA_Util::sqlAddSlashes($cfgRelation['user'])
+ . '\'';
+
+ $has_config = $GLOBALS['dbi']->fetchValue(
+ $query, 0, 0, $GLOBALS['controllink']
+ );
+ $config_data = json_encode($config_array);
+ if ($has_config) {
+ $query = 'UPDATE ' . $query_table
+ . ' SET `config_data` = \''
+ . PMA_Util::sqlAddSlashes($config_data)
+ . '\''
+ . ' WHERE `username` = \''
+ . PMA_Util::sqlAddSlashes($cfgRelation['user'])
+ . '\'';
+ } else {
+ $query = 'INSERT INTO ' . $query_table . ' (`username`, `config_data`) '
+ . 'VALUES (\''
+ . PMA_Util::sqlAddSlashes($cfgRelation['user']) . '\', \''
+ . PMA_Util::sqlAddSlashes($config_data) . '\')';
+ }
+ if (isset($_SESSION['cache'][$cache_key]['userprefs'])) {
+ unset($_SESSION['cache'][$cache_key]['userprefs']);
+ }
+ if (!$GLOBALS['dbi']->tryQuery($query, $GLOBALS['controllink'])) {
+ $message = PMA_Message::error(__('Could not save configuration'));
+ $message->addMessage('<br /><br />');
+ $message->addMessage(
+ PMA_Message::rawError(
+ $GLOBALS['dbi']->getError($GLOBALS['controllink'])
+ )
+ );
+ return $message;
+ }
+ return true;
+}
+
+/**
+ * Returns a user preferences array filtered by $cfg['UserprefsDisallow']
+ * (blacklist) and keys from user preferences form (whitelist)
+ *
+ * @param array $config_data path => value pairs
+ *
+ * @return array
+ */
+function PMA_applyUserprefs(array $config_data)
+{
+ $cfg = array();
+ $blacklist = array_flip($GLOBALS['cfg']['UserprefsDisallow']);
+ if (!$GLOBALS['cfg']['UserprefsDeveloperTab']) {
+ // disallow everything in the Developers tab
+ $blacklist['Error_Handler/display'] = true;
+ $blacklist['Error_Handler/gather'] = true;
+ $blacklist['DBG/sql'] = true;
+ }
+ $whitelist = array_flip(PMA_readUserprefsFieldNames());
+ // whitelist some additional fields which are custom handled
+ $whitelist['ThemeDefault'] = true;
+ $whitelist['fontsize'] = true;
+ $whitelist['lang'] = true;
+ $whitelist['collation_connection'] = true;
+ $whitelist['Server/hide_db'] = true;
+ $whitelist['Server/only_db'] = true;
+ foreach ($config_data as $path => $value) {
+ if (! isset($whitelist[$path]) || isset($blacklist[$path])) {
+ continue;
+ }
+ PMA_arrayWrite($path, $cfg, $value);
+ }
+ return $cfg;
+}
+
+/**
+ * Reads user preferences field names
+ *
+ * @param array|null $forms
+ *
+ * @return array
+ */
+function PMA_readUserprefsFieldNames(array $forms = null)
+{
+ static $names;
+
+ if (defined('TESTSUITE')) {
+ $names = null;
+ }
+
+ // return cached results
+ if ($names !== null) {
+ return $names;
+ }
+ if (is_null($forms)) {
+ $forms = array();
+ include 'libraries/config/user_preferences.forms.php';
+ }
+ $names = array();
+ foreach ($forms as $formset) {
+ foreach ($formset as $form) {
+ foreach ($form as $k => $v) {
+ $names[] = is_int($k) ? $v : $k;
+ }
+ }
+ }
+ return $names;
+}
+
+/**
+ * Updates one user preferences option (loads and saves to database).
+ *
+ * No validation is done!
+ *
+ * @param string $path configuration
+ * @param mixed $value value
+ * @param mixed $default_value default value
+ *
+ * @return void
+ */
+function PMA_persistOption($path, $value, $default_value)
+{
+ $prefs = PMA_loadUserprefs();
+ if ($value === $default_value) {
+ if (isset($prefs['config_data'][$path])) {
+ unset($prefs['config_data'][$path]);
+ } else {
+ return;
+ }
+ } else {
+ $prefs['config_data'][$path] = $value;
+ }
+ PMA_saveUserprefs($prefs['config_data']);
+}
+
+/**
+ * Redirects after saving new user preferences
+ *
+ * @param string $file_name
+ * @param array $params
+ * @param string $hash
+ *
+ * @return void
+ */
+function PMA_userprefsRedirect($file_name,
+ $params = null, $hash = null
+) {
+ // redirect
+ $url_params = array('saved' => 1);
+ if (is_array($params)) {
+ $url_params = array_merge($params, $url_params);
+ }
+ if ($hash) {
+ $hash = '#' . urlencode($hash);
+ }
+ PMA_sendHeaderLocation(
+ $GLOBALS['cfg']['PmaAbsoluteUri'] . $file_name
+ . PMA_URL_getCommon($url_params, '&') . $hash
+ );
+}
+
+/**
+ * Shows form which allows to quickly load
+ * settings stored in browser's local storage
+ *
+ * @return string
+ */
+function PMA_userprefsAutoloadGetHeader()
+{
+ $retval = '';
+
+ if (isset($_REQUEST['prefs_autoload'])
+ && $_REQUEST['prefs_autoload'] == 'hide'
+ ) {
+ $_SESSION['userprefs_autoload'] = true;
+ } else {
+ $script_name = basename(basename($GLOBALS['PMA_PHP_SELF']));
+ $return_url = htmlspecialchars(
+ $script_name . '?' . http_build_query($_GET, '', '&')
+ );
+
+ $retval .= '<div id="prefs_autoload" class="notice" style="display:none">';
+ $retval .= '<form action="prefs_manage.php" method="post">';
+ $retval .= PMA_URL_getHiddenInputs();
+ $retval .= '<input type="hidden" name="json" value="" />';
+ $retval .= '<input type="hidden" name="submit_import" value="1" />';
+ $retval .= '<input type="hidden" name="return_url" value="'
+ . $return_url . '" />';
+ $retval .= __(
+ 'Your browser has phpMyAdmin configuration for this domain. '
+ . 'Would you like to import it for current session?'
+ );
+ $retval .= '<br />';
+ $retval .= '<a href="#yes">' . __('Yes') . '</a>';
+ $retval .= ' / ';
+ $retval .= '<a href="#no">' . __('No') . '</a>';
+ $retval .= '</form>';
+ $retval .= '</div>';
+ }
+ return $retval;
+}
+?>
diff --git a/libraries/vendor_config.php b/libraries/vendor_config.php
new file mode 100644
index 0000000000..ea72b64746
--- /dev/null
+++ b/libraries/vendor_config.php
@@ -0,0 +1,83 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * File for vendor customisation, you can change here paths or some behaviour,
+ * which vendors such as Linux distibutions might want to change.
+ *
+ * For changing this file you should know what you are doing. For this reason
+ * options here are not part of normal configuration.
+ *
+ * @package PhpMyAdmin
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ * Path to changelog file, can be gzip compressed. Useful when you want to
+ * have documentation somewhere else, eg. /usr/share/doc.
+ */
+define('CHANGELOG_FILE', './ChangeLog');
+
+/**
+ * Path to license file. Useful when you want to have documentation somewhere
+ * else, eg. /usr/share/doc.
+ */
+define('LICENSE_FILE', './LICENSE');
+
+/**
+ * Path to config file generated using setup script.
+ */
+define('SETUP_CONFIG_FILE', './config/config.inc.php');
+
+/**
+ * Whether setup requires writable directory where config
+ * file will be generated.
+ */
+define('SETUP_DIR_WRITABLE', true);
+
+/**
+ * Directory where configuration files are stored.
+ * It is not used directly in code, just a convenient
+ * define used further in this file.
+ */
+define('CONFIG_DIR', './');
+
+/**
+ * Filename of a configuration file.
+ */
+define('CONFIG_FILE', CONFIG_DIR . 'config.inc.php');
+
+/**
+ * Filename of custom header file.
+ */
+define('CUSTOM_HEADER_FILE', CONFIG_DIR . 'config.header.inc.php');
+
+/**
+ * Filename of custom footer file.
+ */
+define('CUSTOM_FOOTER_FILE', CONFIG_DIR . 'config.footer.inc.php');
+
+/**
+ * Default value for check for version upgrades.
+ */
+define('VERSION_CHECK_DEFAULT', true);
+
+/**
+ * Path to gettext.inc file. Useful when you want php-gettext somewhere else,
+ * eg. /usr/share/php/gettext/gettext.inc.
+ */
+define('GETTEXT_INC', './libraries/php-gettext/gettext.inc');
+/**
+ * Path to tcpdf.php file. Useful when you want to use system tcpdf,
+ * eg. /usr/share/php/tcpdf/tcpdf.php.
+ */
+define('TCPDF_INC', './libraries/tcpdf/tcpdf.php');
+
+/**
+ * Avoid referring to nonexistent files (causes warnings when open_basedir
+ * is used)
+ */
+define('K_PATH_IMAGES', '');
+
+?>
diff --git a/libraries/zip.lib.php b/libraries/zip.lib.php
new file mode 100644
index 0000000000..cb78ed169f
--- /dev/null
+++ b/libraries/zip.lib.php
@@ -0,0 +1,211 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Zip file creation
+ *
+ * @package PhpMyAdmin
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ * Zip file creation class.
+ * Makes zip files.
+ *
+ * @access public
+ * @package PhpMyAdmin
+ * @see Official ZIP file format: http://www.pkware.com/support/zip-app-note
+ */
+class ZipFile
+{
+ /**
+ * Whether to echo zip as it's built or return as string from -> file
+ *
+ * @var boolean $doWrite
+ */
+ var $doWrite = false;
+
+ /**
+ * Array to store compressed data
+ *
+ * @var array $datasec
+ */
+ var $datasec = array();
+
+ /**
+ * Central directory
+ *
+ * @var array $ctrl_dir
+ */
+ var $ctrl_dir = array();
+
+ /**
+ * End of central directory record
+ *
+ * @var string $eof_ctrl_dir
+ */
+ var $eof_ctrl_dir = "\x50\x4b\x05\x06\x00\x00\x00\x00";
+
+ /**
+ * Last offset position
+ *
+ * @var integer $old_offset
+ */
+ var $old_offset = 0;
+
+
+ /**
+ * Sets member variable this -> doWrite to true
+ * - Should be called immediately after class instantiantion
+ * - If set to true, then ZIP archive are echo'ed to STDOUT as each
+ * file is added via this -> addfile(), and central directories are
+ * echoed to STDOUT on final call to this -> file(). Also,
+ * this -> file() returns an empty string so it is safe to issue a
+ * "echo $zipfile;" command
+ *
+ * @access public
+ *
+ * @return void
+ */
+ function setDoWrite()
+ {
+ $this -> doWrite = true;
+ } // end of the 'setDoWrite()' method
+
+ /**
+ * Converts an Unix timestamp to a four byte DOS date and time format (date
+ * in high two bytes, time in low two bytes allowing magnitude comparison).
+ *
+ * @param integer $unixtime the current Unix timestamp
+ *
+ * @return integer the current date in a four byte DOS format
+ *
+ * @access private
+ */
+ function unix2DosTime($unixtime = 0)
+ {
+ $timearray = ($unixtime == 0) ? getdate() : getdate($unixtime);
+
+ if ($timearray['year'] < 1980) {
+ $timearray['year'] = 1980;
+ $timearray['mon'] = 1;
+ $timearray['mday'] = 1;
+ $timearray['hours'] = 0;
+ $timearray['minutes'] = 0;
+ $timearray['seconds'] = 0;
+ } // end if
+
+ return (($timearray['year'] - 1980) << 25)
+ | ($timearray['mon'] << 21)
+ | ($timearray['mday'] << 16)
+ | ($timearray['hours'] << 11)
+ | ($timearray['minutes'] << 5)
+ | ($timearray['seconds'] >> 1);
+ } // end of the 'unix2DosTime()' method
+
+
+ /**
+ * Adds "file" to archive
+ *
+ * @param string $data file contents
+ * @param string $name name of the file in the archive (may contains the path)
+ * @param integer $time the current timestamp
+ *
+ * @access public
+ *
+ * @return void
+ */
+ function addFile($data, $name, $time = 0)
+ {
+ $name = str_replace('\\', '/', $name);
+
+ $hexdtime = pack('V', $this->unix2DosTime($time));
+
+ $fr = "\x50\x4b\x03\x04";
+ $fr .= "\x14\x00"; // ver needed to extract
+ $fr .= "\x00\x00"; // gen purpose bit flag
+ $fr .= "\x08\x00"; // compression method
+ $fr .= $hexdtime; // last mod time and date
+
+ // "local file header" segment
+ $unc_len = strlen($data);
+ $crc = crc32($data);
+ $zdata = gzcompress($data);
+ $zdata = substr(substr($zdata, 0, strlen($zdata) - 4), 2); // fix crc bug
+ $c_len = strlen($zdata);
+ $fr .= pack('V', $crc); // crc32
+ $fr .= pack('V', $c_len); // compressed filesize
+ $fr .= pack('V', $unc_len); // uncompressed filesize
+ $fr .= pack('v', strlen($name)); // length of filename
+ $fr .= pack('v', 0); // extra field length
+ $fr .= $name;
+
+ // "file data" segment
+ $fr .= $zdata;
+
+ // echo this entry on the fly, ...
+ if ( $this -> doWrite) {
+ echo $fr;
+ } else { // ... OR add this entry to array
+ $this -> datasec[] = $fr;
+ }
+
+ // now add to central directory record
+ $cdrec = "\x50\x4b\x01\x02";
+ $cdrec .= "\x00\x00"; // version made by
+ $cdrec .= "\x14\x00"; // version needed to extract
+ $cdrec .= "\x00\x00"; // gen purpose bit flag
+ $cdrec .= "\x08\x00"; // compression method
+ $cdrec .= $hexdtime; // last mod time & date
+ $cdrec .= pack('V', $crc); // crc32
+ $cdrec .= pack('V', $c_len); // compressed filesize
+ $cdrec .= pack('V', $unc_len); // uncompressed filesize
+ $cdrec .= pack('v', strlen($name)); // length of filename
+ $cdrec .= pack('v', 0); // extra field length
+ $cdrec .= pack('v', 0); // file comment length
+ $cdrec .= pack('v', 0); // disk number start
+ $cdrec .= pack('v', 0); // internal file attributes
+ $cdrec .= pack('V', 32); // external file attributes
+ // - 'archive' bit set
+
+ $cdrec .= pack('V', $this -> old_offset); // relative offset of local header
+ $this -> old_offset += strlen($fr);
+
+ $cdrec .= $name;
+
+ // optional extra field, file comment goes here
+ // save to central directory
+ $this -> ctrl_dir[] = $cdrec;
+ } // end of the 'addFile()' method
+
+
+ /**
+ * Echo central dir if ->doWrite==true, else build string to return
+ *
+ * @return string if ->doWrite {empty string} else the ZIP file contents
+ *
+ * @access public
+ */
+ function file()
+ {
+ $ctrldir = implode('', $this -> ctrl_dir);
+ $header = $ctrldir .
+ $this -> eof_ctrl_dir .
+ pack('v', sizeof($this -> ctrl_dir)) . //total #of entries "on this disk"
+ pack('v', sizeof($this -> ctrl_dir)) . //total #of entries overall
+ pack('V', strlen($ctrldir)) . //size of central dir
+ pack('V', $this -> old_offset) . //offset to start of central dir
+ "\x00\x00"; //.zip file comment length
+
+ if ( $this -> doWrite ) { // Send central directory & end ctrl dir to STDOUT
+ echo $header;
+ return ""; // Return empty string
+ } else { // Return entire ZIP archive as string
+ $data = implode('', $this -> datasec);
+ return $data . $header;
+ }
+ } // end of the 'file()' method
+
+} // end of the 'ZipFile' class
+?>
diff --git a/libraries/zip_extension.lib.php b/libraries/zip_extension.lib.php
new file mode 100644
index 0000000000..db493e7f43
--- /dev/null
+++ b/libraries/zip_extension.lib.php
@@ -0,0 +1,188 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Interface for the zip extension
+ *
+ * @package PhpMyAdmin
+ */
+if (! defined('PHPMYADMIN')) {
+ exit;
+}
+
+/**
+ * Gets zip file contents
+ *
+ * @param string $file zip file
+ * @param string $specific_entry regular expression to match a file
+ *
+ * @return array ($error_message, $file_data); $error_message
+ * is empty if no error
+ */
+function PMA_getZipContents($file, $specific_entry = null)
+{
+ $error_message = '';
+ $file_data = '';
+ $zip_handle = zip_open($file);
+ if (is_resource($zip_handle)) {
+ $first_zip_entry = zip_read($zip_handle);
+ if (false === $first_zip_entry) {
+ $error_message = __('No files found inside ZIP archive!');
+ } else {
+ /* Is the the zip really an ODS file? */
+ $read = zip_entry_read($first_zip_entry);
+ $ods_mime = 'application/vnd.oasis.opendocument.spreadsheet';
+ if (!strcmp($ods_mime, $read)) {
+ $specific_entry = '/^content\.xml$/';
+ }
+
+ if (isset($specific_entry)) {
+ /* Return the correct contents, not just the first entry */
+ for ( ; ; ) {
+ $entry = zip_read($zip_handle);
+ if (is_resource($entry)) {
+ if (preg_match($specific_entry, zip_entry_name($entry))) {
+ zip_entry_open($zip_handle, $entry, 'r');
+ $file_data = zip_entry_read(
+ $entry,
+ zip_entry_filesize($entry)
+ );
+ zip_entry_close($entry);
+ break;
+ }
+ } else {
+ /**
+ * Either we have reached the end of the zip and still
+ * haven't found $specific_entry or there was a parsing
+ * error that we must display
+ */
+ if ($entry === false) {
+ $error_message = __('Error in ZIP archive:')
+ . ' Could not find "' . $specific_entry . '"';
+ } else {
+ $error_message = __('Error in ZIP archive:')
+ . ' ' . PMA_getZipError($zip_handle);
+ }
+
+ break;
+ }
+ }
+ } else {
+ zip_entry_open($zip_handle, $first_zip_entry, 'r');
+ /* File pointer has already been moved,
+ * so include what was read above */
+ $file_data = $read;
+ $file_data .= zip_entry_read(
+ $first_zip_entry,
+ zip_entry_filesize($first_zip_entry)
+ );
+ zip_entry_close($first_zip_entry);
+ }
+ }
+ } else {
+ $error_message = __('Error in ZIP archive:')
+ . ' ' . PMA_getZipError($zip_handle);
+ }
+ zip_close($zip_handle);
+ return (array('error' => $error_message, 'data' => $file_data));
+}
+
+/**
+ * Returns the file name of the first file that matches the given $file_regexp.
+ *
+ * @param string $file_regexp regular expression for the file name to match
+ * @param string $file zip archive
+ *
+ * @return string the file name of the first file that matches the given regexp
+ */
+function PMA_findFileFromZipArchive ($file_regexp, $file)
+{
+ $zip_handle = zip_open($file);
+ if (is_resource($zip_handle)) {
+ $entry = zip_read($zip_handle);
+ while (is_resource($entry)) {
+ if (preg_match($file_regexp, zip_entry_name($entry))) {
+ $file_name = zip_entry_name($entry);
+ zip_close($zip_handle);
+ return $file_name;
+ }
+ $entry = zip_read($zip_handle);
+ }
+ }
+ zip_close($zip_handle);
+ return false;
+}
+
+/**
+ * Returns the number of files in the zip archive.
+ *
+ * @param string $file zip archive
+ *
+ * @return int the number of files in the zip archive
+ */
+function PMA_getNoOfFilesInZip($file)
+{
+ $count = 0;
+ $zip_handle = zip_open($file);
+ if (is_resource($zip_handle)) {
+ $entry = zip_read($zip_handle);
+ while (is_resource($entry)) {
+ $count++;
+ $entry = zip_read($zip_handle);
+ }
+ }
+ zip_close($zip_handle);
+ return $count;
+}
+
+/**
+ * Extracts a set of files from the given zip archive to a given destinations.
+ *
+ * @param string $zip_path path to the zip archive
+ * @param string $destination destination to extract files
+ * @param array $entries files in archive that should be extracted
+ *
+ * @return bool true on sucess, false otherwise
+ */
+function PMA_zipExtract($zip_path, $destination, $entries)
+{
+ $zip = new ZipArchive;
+ if ($zip->open($zip_path) === true) {
+ $zip->extractTo($destination, $entries);
+ $zip->close();
+ return true;
+ }
+ return false;
+}
+
+/**
+ * Gets zip error message
+ *
+ * @param integer $code error code
+ *
+ * @return string error message
+ */
+function PMA_getZipError($code)
+{
+ // I don't think this needs translation
+ switch ($code) {
+ case ZIPARCHIVE::ER_MULTIDISK:
+ $message = 'Multi-disk zip archives not supported';
+ break;
+ case ZIPARCHIVE::ER_READ:
+ $message = 'Read error';
+ break;
+ case ZIPARCHIVE::ER_CRC:
+ $message = 'CRC error';
+ break;
+ case ZIPARCHIVE::ER_NOZIP:
+ $message = 'Not a zip archive';
+ break;
+ case ZIPARCHIVE::ER_INCONS:
+ $message = 'Zip archive inconsistent';
+ break;
+ default:
+ $message = $code;
+ }
+ return $message;
+}
+?>
diff --git a/license.php b/license.php
new file mode 100644
index 0000000000..d076cae49f
--- /dev/null
+++ b/license.php
@@ -0,0 +1,31 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Simple script to set correct charset for the license
+ *
+ * Note: please do not fold this script into a general script
+ * that would read any file using a GET parameter, it would open a hole
+ *
+ * @package PhpMyAdmin
+ */
+
+/**
+ * Gets core libraries and defines some variables
+ */
+require 'libraries/common.inc.php';
+
+/**
+ *
+ */
+header('Content-type: text/plain; charset=utf-8');
+
+$filename = LICENSE_FILE;
+
+// Check if the file is available, some distributions remove these.
+if (is_readable($filename)) {
+ readfile($filename);
+} else {
+ printf(__('The %s file is not available on this system, please visit www.phpmyadmin.net for more information.'), $filename);
+}
+
+?>
diff --git a/locale/ar/LC_MESSAGES/phpmyadmin.mo b/locale/ar/LC_MESSAGES/phpmyadmin.mo
new file mode 100644
index 0000000000..d9309470a1
--- /dev/null
+++ b/locale/ar/LC_MESSAGES/phpmyadmin.mo
Binary files differ
diff --git a/locale/bg/LC_MESSAGES/phpmyadmin.mo b/locale/bg/LC_MESSAGES/phpmyadmin.mo
new file mode 100644
index 0000000000..fc9c3bb208
--- /dev/null
+++ b/locale/bg/LC_MESSAGES/phpmyadmin.mo
Binary files differ
diff --git a/locale/bn/LC_MESSAGES/phpmyadmin.mo b/locale/bn/LC_MESSAGES/phpmyadmin.mo
new file mode 100644
index 0000000000..b81053f257
--- /dev/null
+++ b/locale/bn/LC_MESSAGES/phpmyadmin.mo
Binary files differ
diff --git a/locale/ca/LC_MESSAGES/phpmyadmin.mo b/locale/ca/LC_MESSAGES/phpmyadmin.mo
new file mode 100644
index 0000000000..9e5c266c11
--- /dev/null
+++ b/locale/ca/LC_MESSAGES/phpmyadmin.mo
Binary files differ
diff --git a/locale/cs/LC_MESSAGES/phpmyadmin.mo b/locale/cs/LC_MESSAGES/phpmyadmin.mo
new file mode 100644
index 0000000000..3c3137623d
--- /dev/null
+++ b/locale/cs/LC_MESSAGES/phpmyadmin.mo
Binary files differ
diff --git a/locale/da/LC_MESSAGES/phpmyadmin.mo b/locale/da/LC_MESSAGES/phpmyadmin.mo
new file mode 100644
index 0000000000..86d1fb06d5
--- /dev/null
+++ b/locale/da/LC_MESSAGES/phpmyadmin.mo
Binary files differ
diff --git a/locale/de/LC_MESSAGES/phpmyadmin.mo b/locale/de/LC_MESSAGES/phpmyadmin.mo
new file mode 100644
index 0000000000..ae30fdd1e5
--- /dev/null
+++ b/locale/de/LC_MESSAGES/phpmyadmin.mo
Binary files differ
diff --git a/locale/el/LC_MESSAGES/phpmyadmin.mo b/locale/el/LC_MESSAGES/phpmyadmin.mo
new file mode 100644
index 0000000000..260eb934fb
--- /dev/null
+++ b/locale/el/LC_MESSAGES/phpmyadmin.mo
Binary files differ
diff --git a/locale/en_GB/LC_MESSAGES/phpmyadmin.mo b/locale/en_GB/LC_MESSAGES/phpmyadmin.mo
new file mode 100644
index 0000000000..89fcb1a992
--- /dev/null
+++ b/locale/en_GB/LC_MESSAGES/phpmyadmin.mo
Binary files differ
diff --git a/locale/es/LC_MESSAGES/phpmyadmin.mo b/locale/es/LC_MESSAGES/phpmyadmin.mo
new file mode 100644
index 0000000000..2ef15d5b49
--- /dev/null
+++ b/locale/es/LC_MESSAGES/phpmyadmin.mo
Binary files differ
diff --git a/locale/et/LC_MESSAGES/phpmyadmin.mo b/locale/et/LC_MESSAGES/phpmyadmin.mo
new file mode 100644
index 0000000000..aba5d7a65c
--- /dev/null
+++ b/locale/et/LC_MESSAGES/phpmyadmin.mo
Binary files differ
diff --git a/locale/fi/LC_MESSAGES/phpmyadmin.mo b/locale/fi/LC_MESSAGES/phpmyadmin.mo
new file mode 100644
index 0000000000..9493c3e92e
--- /dev/null
+++ b/locale/fi/LC_MESSAGES/phpmyadmin.mo
Binary files differ
diff --git a/locale/fr/LC_MESSAGES/phpmyadmin.mo b/locale/fr/LC_MESSAGES/phpmyadmin.mo
new file mode 100644
index 0000000000..d7c1f5160c
--- /dev/null
+++ b/locale/fr/LC_MESSAGES/phpmyadmin.mo
Binary files differ
diff --git a/locale/gl/LC_MESSAGES/phpmyadmin.mo b/locale/gl/LC_MESSAGES/phpmyadmin.mo
new file mode 100644
index 0000000000..66a50035a2
--- /dev/null
+++ b/locale/gl/LC_MESSAGES/phpmyadmin.mo
Binary files differ
diff --git a/locale/hi/LC_MESSAGES/phpmyadmin.mo b/locale/hi/LC_MESSAGES/phpmyadmin.mo
new file mode 100644
index 0000000000..7c5b497d14
--- /dev/null
+++ b/locale/hi/LC_MESSAGES/phpmyadmin.mo
Binary files differ
diff --git a/locale/hu/LC_MESSAGES/phpmyadmin.mo b/locale/hu/LC_MESSAGES/phpmyadmin.mo
new file mode 100644
index 0000000000..dfd28a25c4
--- /dev/null
+++ b/locale/hu/LC_MESSAGES/phpmyadmin.mo
Binary files differ
diff --git a/locale/ia/LC_MESSAGES/phpmyadmin.mo b/locale/ia/LC_MESSAGES/phpmyadmin.mo
new file mode 100644
index 0000000000..a52f43b525
--- /dev/null
+++ b/locale/ia/LC_MESSAGES/phpmyadmin.mo
Binary files differ
diff --git a/locale/id/LC_MESSAGES/phpmyadmin.mo b/locale/id/LC_MESSAGES/phpmyadmin.mo
new file mode 100644
index 0000000000..4b1238d5d1
--- /dev/null
+++ b/locale/id/LC_MESSAGES/phpmyadmin.mo
Binary files differ
diff --git a/locale/it/LC_MESSAGES/phpmyadmin.mo b/locale/it/LC_MESSAGES/phpmyadmin.mo
new file mode 100644
index 0000000000..f36b15bacb
--- /dev/null
+++ b/locale/it/LC_MESSAGES/phpmyadmin.mo
Binary files differ
diff --git a/locale/ja/LC_MESSAGES/phpmyadmin.mo b/locale/ja/LC_MESSAGES/phpmyadmin.mo
new file mode 100644
index 0000000000..8612a452fc
--- /dev/null
+++ b/locale/ja/LC_MESSAGES/phpmyadmin.mo
Binary files differ
diff --git a/locale/ko/LC_MESSAGES/phpmyadmin.mo b/locale/ko/LC_MESSAGES/phpmyadmin.mo
new file mode 100644
index 0000000000..20266727db
--- /dev/null
+++ b/locale/ko/LC_MESSAGES/phpmyadmin.mo
Binary files differ
diff --git a/locale/lt/LC_MESSAGES/phpmyadmin.mo b/locale/lt/LC_MESSAGES/phpmyadmin.mo
new file mode 100644
index 0000000000..7602ff041f
--- /dev/null
+++ b/locale/lt/LC_MESSAGES/phpmyadmin.mo
Binary files differ
diff --git a/locale/nb/LC_MESSAGES/phpmyadmin.mo b/locale/nb/LC_MESSAGES/phpmyadmin.mo
new file mode 100644
index 0000000000..ee2c177210
--- /dev/null
+++ b/locale/nb/LC_MESSAGES/phpmyadmin.mo
Binary files differ
diff --git a/locale/nl/LC_MESSAGES/phpmyadmin.mo b/locale/nl/LC_MESSAGES/phpmyadmin.mo
new file mode 100644
index 0000000000..1cd072373c
--- /dev/null
+++ b/locale/nl/LC_MESSAGES/phpmyadmin.mo
Binary files differ
diff --git a/locale/pl/LC_MESSAGES/phpmyadmin.mo b/locale/pl/LC_MESSAGES/phpmyadmin.mo
new file mode 100644
index 0000000000..873011c1c2
--- /dev/null
+++ b/locale/pl/LC_MESSAGES/phpmyadmin.mo
Binary files differ
diff --git a/locale/pt/LC_MESSAGES/phpmyadmin.mo b/locale/pt/LC_MESSAGES/phpmyadmin.mo
new file mode 100644
index 0000000000..5da5bc1a16
--- /dev/null
+++ b/locale/pt/LC_MESSAGES/phpmyadmin.mo
Binary files differ
diff --git a/locale/pt_BR/LC_MESSAGES/phpmyadmin.mo b/locale/pt_BR/LC_MESSAGES/phpmyadmin.mo
new file mode 100644
index 0000000000..dd148a1af1
--- /dev/null
+++ b/locale/pt_BR/LC_MESSAGES/phpmyadmin.mo
Binary files differ
diff --git a/locale/ro/LC_MESSAGES/phpmyadmin.mo b/locale/ro/LC_MESSAGES/phpmyadmin.mo
new file mode 100644
index 0000000000..f90b6e705a
--- /dev/null
+++ b/locale/ro/LC_MESSAGES/phpmyadmin.mo
Binary files differ
diff --git a/locale/ru/LC_MESSAGES/phpmyadmin.mo b/locale/ru/LC_MESSAGES/phpmyadmin.mo
new file mode 100644
index 0000000000..e034e3ff81
--- /dev/null
+++ b/locale/ru/LC_MESSAGES/phpmyadmin.mo
Binary files differ
diff --git a/locale/si/LC_MESSAGES/phpmyadmin.mo b/locale/si/LC_MESSAGES/phpmyadmin.mo
new file mode 100644
index 0000000000..fbe344483e
--- /dev/null
+++ b/locale/si/LC_MESSAGES/phpmyadmin.mo
Binary files differ
diff --git a/locale/sk/LC_MESSAGES/phpmyadmin.mo b/locale/sk/LC_MESSAGES/phpmyadmin.mo
new file mode 100644
index 0000000000..a2e64137d9
--- /dev/null
+++ b/locale/sk/LC_MESSAGES/phpmyadmin.mo
Binary files differ
diff --git a/locale/sl/LC_MESSAGES/phpmyadmin.mo b/locale/sl/LC_MESSAGES/phpmyadmin.mo
new file mode 100644
index 0000000000..47f2df5053
--- /dev/null
+++ b/locale/sl/LC_MESSAGES/phpmyadmin.mo
Binary files differ
diff --git a/locale/sr@latin/LC_MESSAGES/phpmyadmin.mo b/locale/sr@latin/LC_MESSAGES/phpmyadmin.mo
new file mode 100644
index 0000000000..df5677acec
--- /dev/null
+++ b/locale/sr@latin/LC_MESSAGES/phpmyadmin.mo
Binary files differ
diff --git a/locale/sv/LC_MESSAGES/phpmyadmin.mo b/locale/sv/LC_MESSAGES/phpmyadmin.mo
new file mode 100644
index 0000000000..1630da1d0a
--- /dev/null
+++ b/locale/sv/LC_MESSAGES/phpmyadmin.mo
Binary files differ
diff --git a/locale/th/LC_MESSAGES/phpmyadmin.mo b/locale/th/LC_MESSAGES/phpmyadmin.mo
new file mode 100644
index 0000000000..80034696b8
--- /dev/null
+++ b/locale/th/LC_MESSAGES/phpmyadmin.mo
Binary files differ
diff --git a/locale/tr/LC_MESSAGES/phpmyadmin.mo b/locale/tr/LC_MESSAGES/phpmyadmin.mo
new file mode 100644
index 0000000000..7934a03df8
--- /dev/null
+++ b/locale/tr/LC_MESSAGES/phpmyadmin.mo
Binary files differ
diff --git a/locale/uk/LC_MESSAGES/phpmyadmin.mo b/locale/uk/LC_MESSAGES/phpmyadmin.mo
new file mode 100644
index 0000000000..234dad55ca
--- /dev/null
+++ b/locale/uk/LC_MESSAGES/phpmyadmin.mo
Binary files differ
diff --git a/locale/uz/LC_MESSAGES/phpmyadmin.mo b/locale/uz/LC_MESSAGES/phpmyadmin.mo
new file mode 100644
index 0000000000..01b1591985
--- /dev/null
+++ b/locale/uz/LC_MESSAGES/phpmyadmin.mo
Binary files differ
diff --git a/locale/uz@latin/LC_MESSAGES/phpmyadmin.mo b/locale/uz@latin/LC_MESSAGES/phpmyadmin.mo
new file mode 100644
index 0000000000..b3c75a3a94
--- /dev/null
+++ b/locale/uz@latin/LC_MESSAGES/phpmyadmin.mo
Binary files differ
diff --git a/locale/zh_CN/LC_MESSAGES/phpmyadmin.mo b/locale/zh_CN/LC_MESSAGES/phpmyadmin.mo
new file mode 100644
index 0000000000..6c00e9a153
--- /dev/null
+++ b/locale/zh_CN/LC_MESSAGES/phpmyadmin.mo
Binary files differ
diff --git a/locale/zh_TW/LC_MESSAGES/phpmyadmin.mo b/locale/zh_TW/LC_MESSAGES/phpmyadmin.mo
new file mode 100644
index 0000000000..b8f0f8df43
--- /dev/null
+++ b/locale/zh_TW/LC_MESSAGES/phpmyadmin.mo
Binary files differ
diff --git a/navigation.php b/navigation.php
new file mode 100644
index 0000000000..2c35311e50
--- /dev/null
+++ b/navigation.php
@@ -0,0 +1,71 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * The navigation panel - displays server, db and table selection tree
+ *
+ * @package PhpMyAdmin-Navigation
+ */
+
+// Include common functionalities
+require_once './libraries/common.inc.php';
+
+// Also initialises the collapsible tree class
+require_once './libraries/navigation/Navigation.class.php';
+
+$response = PMA_Response::getInstance();
+$navigation = new PMA_Navigation();
+if (! $response->isAjax()) {
+ $response->addHTML(
+ PMA_Message::error(
+ __('Fatal error: The navigation can only be accessed via AJAX')
+ )
+ );
+ exit;
+}
+
+$cfgRelation = PMA_getRelationsParam();
+if ($cfgRelation['navwork']) {
+ if (isset($_REQUEST['hideNavItem'])) {
+ if (! empty($_REQUEST['itemName'])
+ && ! empty($_REQUEST['itemType'])
+ && ! empty($_REQUEST['dbName'])
+ ) {
+ $navigation->hideNavigationItem(
+ $_REQUEST['itemName'],
+ $_REQUEST['itemType'],
+ $_REQUEST['dbName'],
+ (! empty($_REQUEST['tableName']) ? $_REQUEST['tableName'] : null)
+ );
+ }
+ exit;
+ }
+
+ if (isset($_REQUEST['unhideNavItem'])) {
+ if (! empty($_REQUEST['itemName'])
+ && ! empty($_REQUEST['itemType'])
+ && ! empty($_REQUEST['dbName'])
+ ) {
+ $navigation->unhideNavigationItem(
+ $_REQUEST['itemName'],
+ $_REQUEST['itemType'],
+ $_REQUEST['dbName'],
+ (! empty($_REQUEST['tableName']) ? $_REQUEST['tableName'] : null)
+ );
+ }
+ exit;
+ }
+
+ if (isset($_REQUEST['showUnhideDialog'])) {
+ if (! empty($_REQUEST['dbName'])) {
+ $response->addJSON(
+ 'message',
+ $navigation->getItemUnhideDialog($_REQUEST['dbName'])
+ );
+ }
+ exit;
+ }
+}
+
+// Do the magic
+$response->addJSON('message', $navigation->getDisplay());
+?>
diff --git a/phpinfo.php b/phpinfo.php
new file mode 100644
index 0000000000..5854ff4c9e
--- /dev/null
+++ b/phpinfo.php
@@ -0,0 +1,21 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * phpinfo() wrapper to allow displaying only when configured to do so.
+ *
+ * @package PhpMyAdmin
+ */
+
+/**
+ * Gets core libraries and defines some variables
+ */
+require_once 'libraries/common.inc.php';
+PMA_Response::getInstance()->disable();
+
+/**
+ * Displays PHP information
+ */
+if ($GLOBALS['cfg']['ShowPhpInfo']) {
+ phpinfo();
+}
+?>
diff --git a/phpmyadmin.css.php b/phpmyadmin.css.php
new file mode 100644
index 0000000000..bfe4d47e03
--- /dev/null
+++ b/phpmyadmin.css.php
@@ -0,0 +1,31 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ *
+ * @package PhpMyAdmin
+ */
+
+/**
+ *
+ */
+
+define('PMA_MINIMUM_COMMON', true);
+require_once 'libraries/common.inc.php';
+
+// MSIE 6 (at least some unpatched versions) has problems loading CSS
+// when zlib_compression is on
+if (PMA_USR_BROWSER_AGENT == 'IE' && PMA_USR_BROWSER_VER == '6'
+ && (ini_get('zlib.output_compression'))
+) {
+ @ini_set('zlib.output_compression', 'Off');
+}
+
+// Send correct type:
+header('Content-Type: text/css; charset=UTF-8');
+
+// Cache output in client - the nocache query parameter makes sure that this
+// file is reloaded when config changes
+header('Expires: ' . gmdate('D, d M Y H:i:s', time() + 3600) . ' GMT');
+
+$_SESSION['PMA_Theme_Manager']->printCss();
+?>
diff --git a/phpunit.xml.nocoverage b/phpunit.xml.nocoverage
new file mode 100644
index 0000000000..f53e812a8e
--- /dev/null
+++ b/phpunit.xml.nocoverage
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<phpunit bootstrap="test/bootstrap-dist.php"
+ backupGlobals="true"
+ backupStaticAttributes="false"
+ strict="true"
+ timeoutForSmallTests="3"
+ colors="true"
+ verbose="true">
+
+ <selenium>
+ <browser name="Firefox on localhost"
+ browser="*firefox"
+ host="127.0.0.1"
+ port="4444"
+ timeout="30000"/>
+ </selenium>
+
+ <php>
+ <const name="TESTSUITE_SERVER" value="localhost"/>
+ <const name="TESTSUITE_USER" value="root"/>
+ <const name="TESTSUITE_PASSWORD" value=""/>
+ <const name="TESTSUITE_DATABASE" value="test"/>
+ <const name="TESTSUITE_PHPMYADMIN_HOST" value="http://localhost" />
+ <const name="TESTSUITE_PHPMYADMIN_URL" value="/phpmyadmin" />
+ </php>
+
+ <testsuites>
+ <testsuite name="Classes">
+ <directory suffix="_test.php">test/classes</directory>
+ </testsuite>
+ <testsuite name="Engines">
+ <directory suffix="_test.php">test/engines</directory>
+ </testsuite>
+ <testsuite name="Unit">
+ <file>test/Environment_test.php</file>
+ <directory suffix="_test.php">test/libraries/core</directory>
+ <directory suffix="_test.php">test/libraries/common</directory>
+ <directory suffix="_test.php">test/libraries/rte</directory>
+ <directory suffix="_test.php">test/libraries</directory>
+ </testsuite>
+ <!--<testsuite name="Selenium">-->
+ <!--<directory suffix="Test.php">test/selenium</directory>-->
+ <!--</testsuite>-->
+ </testsuites>
+
+ <logging>
+ <log type="junit" target="build/logs/junit.xml" logIncompleteSkipped="false"/>
+ </logging>
+</phpunit>
diff --git a/pmd_display_field.php b/pmd_display_field.php
new file mode 100644
index 0000000000..8a44d313d5
--- /dev/null
+++ b/pmd_display_field.php
@@ -0,0 +1,61 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * @package PhpMyAdmin-Designer
+ */
+
+/**
+ *
+ */
+require_once './libraries/common.inc.php';
+
+PMA_Response::getInstance()->disable();
+
+require_once 'libraries/pmd_common.php';
+
+$table = $_POST['T'];
+$display_field = $_POST['F'];
+
+if ($cfgRelation['displaywork']) {
+
+ $disp = PMA_getDisplayField($db, $table);
+ if ($disp) {
+ if ($display_field != $disp) {
+ $upd_query = 'UPDATE '
+ . PMA_Util::backquote($GLOBALS['cfgRelation']['db'])
+ . '.'
+ . PMA_Util::backquote($cfgRelation['table_info'])
+ . ' SET display_field = \''
+ . PMA_Util::sqlAddSlashes($display_field) . '\''
+ . ' WHERE db_name = \'' . PMA_Util::sqlAddSlashes($db) . '\''
+ . ' AND table_name = \'' . PMA_Util::sqlAddSlashes($table) . '\'';
+ } else {
+ $upd_query = 'DELETE FROM '
+ . PMA_Util::backquote($GLOBALS['cfgRelation']['db'])
+ . '.'
+ . PMA_Util::backquote($cfgRelation['table_info'])
+ . ' WHERE db_name = \'' . PMA_Util::sqlAddSlashes($db) . '\''
+ . ' AND table_name = \'' . PMA_Util::sqlAddSlashes($table) . '\'';
+ }
+ } elseif ($display_field != '') {
+ $upd_query = 'INSERT INTO '
+ . PMA_Util::backquote($GLOBALS['cfgRelation']['db'])
+ . '.'
+ . PMA_Util::backquote($cfgRelation['table_info'])
+ . '(db_name, table_name, display_field) '
+ . ' VALUES('
+ . '\'' . PMA_Util::sqlAddSlashes($db) . '\','
+ . '\'' . PMA_Util::sqlAddSlashes($table) . '\','
+ . '\'' . PMA_Util::sqlAddSlashes($display_field) . '\')';
+ }
+
+ if (isset($upd_query)) {
+ $upd_rs = PMA_queryAsControlUser($upd_query);
+ }
+} // end if
+
+header("Content-Type: text/xml; charset=utf-8");
+header("Cache-Control: no-cache");
+die("<root act='save_pos' return='"
+ . __('Modifications have been saved') . "'></root>");
+?>
diff --git a/pmd_general.php b/pmd_general.php
new file mode 100644
index 0000000000..ad66cfc015
--- /dev/null
+++ b/pmd_general.php
@@ -0,0 +1,877 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * phpMyAdmin designer general code
+ *
+ * @package PhpMyAdmin-Designer
+ */
+
+/**
+ *
+ */
+require_once 'libraries/common.inc.php';
+require_once 'libraries/pmd_common.php';
+
+$script_display_field = get_tables_info();
+$tab_column = get_columns_info();
+$script_tables = get_script_tabs();
+$script_contr = get_script_contr();
+$tab_pos = get_tab_pos();
+$tables_pk_or_unique_keys = get_pk_or_unique_keys();
+$tables_all_keys = get_all_keys();
+
+$params = array('lang' => $GLOBALS['lang']);
+if (isset($_GET['db'])) {
+ $params['db'] = $_GET['db'];
+}
+
+$response = PMA_Response::getInstance();
+$response->getFooter()->setMinimal();
+$header = $response->getHeader();
+$header->setBodyId('pmd_body');
+$scripts = $header->getScripts();
+$scripts->addFile('jquery/jquery.fullscreen.js');
+$scripts->addFile('pmd/ajax.js');
+$scripts->addFile('pmd/history.js');
+$scripts->addFile('pmd/move.js');
+$scripts->addFile('pmd/iecanvas.js', true);
+$scripts->addFile('pmd/init.js');
+
+require 'libraries/db_common.inc.php';
+require 'libraries/db_info.inc.php';
+
+// Embed some data into HTML, later it will be read
+// by pmd/init.js and converted to JS variables.
+echo '<div id="script_server" class="hide">';
+echo htmlspecialchars($GLOBALS['server']);
+echo '</div>';
+echo '<div id="script_db" class="hide">';
+echo htmlspecialchars($_GET['db']);
+echo '</div>';
+echo '<div id="script_token" class="hide">';
+echo htmlspecialchars($_GET['token']);
+echo '</div>';
+echo '<div id="script_tables" class="hide">';
+echo htmlspecialchars(json_encode($script_tables));
+echo '</div>';
+echo '<div id="script_contr" class="hide">';
+echo htmlspecialchars(json_encode($script_contr));
+echo '</div>';
+echo '<div id="script_display_field" class="hide">';
+echo htmlspecialchars(json_encode($script_display_field));
+echo '</div>';
+
+?>
+<div class="pmd_header" id="top_menu">
+ <a href="#" onclick="Show_left_menu(document.getElementById('key_Show_left_menu')); return false"
+ class="M_butt first" target="_self">
+ <img id='key_Show_left_menu' title="<?php echo __('Show/Hide left menu'); ?>" alt="v"
+ src="<?php echo $_SESSION['PMA_Theme']->getImgPath('pmd/downarrow2_m.png'); ?>" />
+ </a>
+ <a href="#" id="enterFullscreen" onclick="Enter_fullscreen(); return false" class="M_butt" target="_self">
+ <img title="<?php echo __('View in fullscreen') ?>" alt=""
+ src="<?php echo $_SESSION['PMA_Theme']->getImgPath('pmd/viewInFullscreen.png'); ?>" />
+ </a>
+ <a href="#" id="exitFullscreen" onclick="Exit_fullscreen(); return false" class="M_butt hide" target="_self">
+ <img title="<?php echo __('Exit fullscreen') ?>" alt=""
+ src="<?php echo $_SESSION['PMA_Theme']->getImgPath('pmd/exitFullscreen.png'); ?>" />
+ </a>
+ <img class="M_bord" src="<?php echo $_SESSION['PMA_Theme']->getImgPath('pmd/bord.png'); ?>" alt="" />
+ <a href="#" onclick="Save2(); return false" class="M_butt" target="_self">
+ <img title="<?php echo __('Save position') ?>" alt=""
+ src="<?php echo $_SESSION['PMA_Theme']->getImgPath('pmd/save.png'); ?>" />
+ </a>
+ <a href="#" onclick="Start_table_new(); return false"
+ class="M_butt" target="_self">
+ <img title="<?php echo __('Create table')?>" alt=""
+ src="<?php echo $_SESSION['PMA_Theme']->getImgPath('pmd/table.png'); ?>" />
+ </a>
+ <a href="#" onclick="Start_relation(); return false" class="M_butt" id="rel_button" target="_self">
+ <img title="<?php echo __('Create relation') ?>" alt=""
+ src="<?php echo $_SESSION['PMA_Theme']->getImgPath('pmd/relation.png'); ?>" />
+ </a>
+ <a href="#" onclick="Start_display_field(); return false"
+ class="M_butt" id="display_field_button" target="_self">
+ <img title="<?php echo __('Choose column to display') ?>" alt=""
+ src="<?php echo $_SESSION['PMA_Theme']->getImgPath('pmd/display_field.png'); ?>" />
+ </a>
+ <a href="#" onclick="location.reload(); return false" class="M_butt" target="_self">
+ <img title="<?php echo __('Reload'); ?>" alt=""
+ src="<?php echo $_SESSION['PMA_Theme']->getImgPath('pmd/reload.png'); ?>" />
+ </a>
+ <a href="<?php echo PMA_Util::getDocuLink('faq', 'faq6-31') ?>" target="documentation" class="M_butt" target="_self">
+ <img title="<?php echo __('Help'); ?>" alt=""
+ src="<?php echo $_SESSION['PMA_Theme']->getImgPath('pmd/help.png'); ?>" />
+ </a>
+ <img class="M_bord" src="<?php echo $_SESSION['PMA_Theme']->getImgPath('pmd/bord.png'); ?>" alt="" />
+ <a href="#" onclick="Angular_direct(); return false"
+ class="M_butt" id="angular_direct_button" target="_self">
+ <img title="<?php echo __('Angular links') . ' / ' . __('Direct links'); ?>" alt=""
+ src="<?php echo $_SESSION['PMA_Theme']->getImgPath('pmd/ang_direct.png'); ?>" />
+ </a>
+ <a href="#" onclick="Grid(); return false" class="M_butt" id="grid_button" target="_self">
+ <img title="<?php echo __('Snap to grid') ?>"
+ src="<?php echo $_SESSION['PMA_Theme']->getImgPath('pmd/grid.png'); ?>" alt="" />
+ </a>
+ <img class="M_bord" src="<?php echo $_SESSION['PMA_Theme']->getImgPath('pmd/bord.png'); ?>" alt="" />
+ <a href="#" onclick="Small_tab_all(document.getElementById('key_SB_all')); return false"
+ class="M_butt" target="_self">
+ <img id='key_SB_all' title="<?php echo __('Small/Big All'); ?>" alt="v"
+ src="<?php echo $_SESSION['PMA_Theme']->getImgPath('pmd/downarrow1.png'); ?>" />
+ </a>
+ <a href="#" onclick="Small_tab_invert(); return false" class="M_butt" target="_self" >
+ <img title="<?php echo __('Toggle small/big'); ?>" alt="key"
+ src="<?php echo $_SESSION['PMA_Theme']->getImgPath('pmd/bottom.png'); ?>" />
+ </a>
+ <a href="#" onclick="Relation_lines_invert(); return false" class="M_butt" target="_self" >
+ <img title="<?php echo __('Toggle relation lines'); ?>" alt="key"
+ src="<?php echo $_SESSION['PMA_Theme']->getImgPath('pmd/toggle_lines.png'); ?>" />
+ </a>
+ <img class="M_bord" src="<?php echo $_SESSION['PMA_Theme']->getImgPath('pmd/bord.png'); ?>" alt="" />
+ <a href="#" onclick="PDF_save(); return false" class="M_butt ajax">
+ <img src="<?php echo $_SESSION['PMA_Theme']->getImgPath('pmd/pdf.png'); ?>" alt="key"
+ width="20" height="20" title="<?php echo __('Import/Export coordinates for PDF schema'); ?>" />
+ </a>
+<?php
+if (isset($_REQUEST['query'])) {
+ echo '<a href="#" onclick="build_query(\'SQL Query on Database\', 0)" onmousedown="return false;"
+ class="M_butt" target="_self">';
+ echo '<img src="'. $_SESSION['PMA_Theme']->getImgPath('pmd/query_builder.png') . '" alt="key" width="20" height="20" title="';
+ echo __('Build Query');
+ echo '"/></a>';
+}
+?>
+ <a href="#" onclick="Top_menu_right(document.getElementById('key_Left_Right')); return false"
+ class="M_butt last" target="_self">
+ <img src="<?php echo $_SESSION['PMA_Theme']->getImgPath('pmd/2rightarrow_m.png'); ?>"
+ id="key_Left_Right" alt=">" title="<?php echo __('Move Menu'); ?>" />
+ </a>
+</div>
+
+<div id="canvas_outer">
+<form action="" method="post" name="form1">
+<div id="osn_tab">
+ <canvas class="pmd" id="canvas" width="100" height="100" onclick="Canvas_click(this)"></canvas>
+</div>
+<div id="layer_menu" style="display:none;">
+<div class="center" style="padding-top:5px;">
+ <a href="#"
+ onclick="Hide_tab_all(document.getElementById('key_HS_all')); return false" class="M_butt" target="_self">
+ <img title="<?php echo __('Hide/Show all'); ?>" alt="v"
+ src="<?php echo $_SESSION['PMA_Theme']->getImgPath('pmd/downarrow1.png'); ?>" id='key_HS_all' /></a>
+ <a href="#"
+ onclick="No_have_constr(document.getElementById('key_HS')); return false" class="M_butt" target="_self">
+ <img title="<?php echo __('Hide/Show Tables with no relation'); ?>" alt="v"
+ src="<?php echo $_SESSION['PMA_Theme']->getImgPath('pmd/downarrow2.png'); ?>" id='key_HS' /></a>
+</div>
+
+<div id="id_scroll_tab" class="scroll_tab">
+ <table width="100%" style="padding-left: 3px;">
+<?php
+$name_cnt = count($GLOBALS['PMD']['TABLE_NAME']);
+for ($i = 0; $i < $name_cnt; $i++) {
+
+ echo '<tr><td title="' . __('Structure') . '" width="1px" '
+ . 'onmouseover="this.className=\'L_butt2_2\'" '
+ . 'onmouseout="this.className=\'L_butt2_1\'" class="L_butt2_1">';
+ echo '<img '
+ . 'onclick="Start_tab_upd(\'' . $GLOBALS['PMD_URL']["TABLE_NAME_SMALL"][$i] . '\');" '
+ . 'src="' . $_SESSION['PMA_Theme']->getImgPath('pmd/exec.png') . '" alt="" />';
+ echo '</td>';
+ echo '<td width="1px">';
+ echo '<input onclick="VisibleTab(this,\'' . $GLOBALS['PMD_URL']["TABLE_NAME"][$i] . '\')" '
+ . 'title="' . __('Hide') . '" '
+ . 'id="check_vis_' . $GLOBALS['PMD_URL']["TABLE_NAME"][$i] . '" '
+ . 'style="margin:0px;" type="checkbox" '
+ . 'value="' . $GLOBALS['PMD_URL']["TABLE_NAME"][$i] . '"';
+ if (isset($tab_pos[$GLOBALS['PMD']["TABLE_NAME"][$i]])) {
+ echo $tab_pos[$GLOBALS['PMD']["TABLE_NAME"][$i]]["H"] ? 'checked="checked"' : '';
+ } else {
+ echo 'checked="checked"';
+ }
+ echo '/></td>';
+ echo '<td class="pmd_Tabs" onmouseover="this.className=\'pmd_Tabs2\'" '
+ . 'onmouseout="this.className=\'pmd_Tabs\'" '
+ . 'onclick="Select_tab(\'' . $GLOBALS['PMD_URL']["TABLE_NAME"][$i] . '\');">';
+ echo $GLOBALS['PMD_OUT']["TABLE_NAME"][$i];
+ echo '</td>';
+ echo '</tr>';
+}
+echo '</table>';
+echo '</div>';
+
+echo '<div class="center">';
+echo __('Number of tables:') . ' ' . $name_cnt;
+echo '</div>';
+echo '<div class="floatright">';
+echo '<div id="layer_menu_sizer" onmousedown="layer_menu_cur_click=1">';
+echo '</div>';
+echo '</div>';
+echo '</div>';
+
+for ($i = 0; $i < count($GLOBALS['PMD']["TABLE_NAME"]); $i++) {
+ $t_n = $GLOBALS['PMD']["TABLE_NAME"][$i];
+ $t_n_url = $GLOBALS['PMD_URL']["TABLE_NAME"][$i];
+
+ ?>
+<input name="t_x[<?php echo $t_n_url ?>]" type="hidden" id="t_x_<?php echo $t_n_url ?>_" />
+<input name="t_y[<?php echo $t_n_url ?>]" type="hidden" id="t_y_<?php echo $t_n_url ?>_" />
+<input name="t_v[<?php echo $t_n_url ?>]" type="hidden" id="t_v_<?php echo $t_n_url ?>_" />
+<input name="t_h[<?php echo $t_n_url ?>]" type="hidden" id="t_h_<?php echo $t_n_url ?>_" />
+
+<table id="<?php echo $t_n_url ?>" cellpadding="0" cellspacing="0" class="pmd_tab"
+ style="position: absolute;
+ left: <?php
+ echo isset($tab_pos[$t_n]) ? $tab_pos[$t_n]["X"] : rand(20, 700); ?>px;
+ top: <?php
+ echo isset($tab_pos[$t_n]) ? $tab_pos[$t_n]["Y"] : rand(20, 550); ?>px;
+ visibility: <?php
+ echo ! isset($tab_pos[$t_n]) || $tab_pos[$t_n]["H"]
+ ? "visible"
+ : "hidden"; ?>;
+ z-index: 1;">
+<thead>
+<tr>
+ <?php
+ if (isset($_REQUEST['query'])) {
+ echo '<td class="select_all">';
+ echo '<input type="checkbox" value="select_all_'.htmlspecialchars($t_n_url).'" style="margin: 0px;" ';
+ echo 'id="select_all_'.htmlspecialchars($t_n_url).'" title="select all" ';
+ echo 'onclick="Select_all(\''. htmlspecialchars($t_n_url) .'\',\''.htmlspecialchars($GLOBALS['PMD_OUT']["OWNER"][$i]).'\')"></td>';
+ }
+ ?>
+ <td class="small_tab" onmouseover="this.className='small_tab2';"
+ onmouseout="this.className='small_tab';"
+ id="id_hide_tbody_<?php echo $t_n_url ?>"
+ onclick="Small_tab('<?php echo $t_n_url ?>', 1)"><?php
+ // no space alloawd here, between tags and content !!!
+ // JavaScript function does require this
+ if (! isset($tab_pos[$t_n]) || ! empty($tab_pos[$t_n]["V"])) {
+ echo 'v';
+ } else {
+ echo '&gt;';
+ }
+ ?></td>
+ <td class="small_tab_pref" onmouseover="this.className='small_tab_pref2';"
+ onmouseout="this.className='small_tab_pref';"
+ onclick="Start_tab_upd('<?php echo $GLOBALS['PMD_URL']["TABLE_NAME_SMALL"][$i]; ?>');">
+ <img src="<?php echo $_SESSION['PMA_Theme']->getImgPath('pmd/exec_small.png'); ?>" alt="" /></td>
+ <td id="id_zag_<?php echo $t_n_url ?>" class="tab_zag nowrap"
+ onmousedown="cur_click=document.getElementById('<?php echo $t_n_url ?>');"/
+ onmouseover="Table_onover('<?php echo $t_n_url ?>',0,<?php echo (isset($_REQUEST['query'])? 1 : 0 )?> )"
+ onmouseout="Table_onover('<?php echo $t_n_url ?>',1,<?php echo (isset($_REQUEST['query']) ? 1 : 0 )?>)">
+ <span class='owner'>
+ <?php
+ echo $GLOBALS['PMD_OUT']["OWNER"][$i];
+ echo '.</span>';
+ echo $GLOBALS['PMD_OUT']["TABLE_NAME_SMALL"][$i];
+ ?></td>
+ <?php
+ if (isset($_REQUEST['query'])) {
+ echo '<td class="tab_zag" onmouseover="Table_onover(\''.htmlspecialchars($t_n_url).'\',0,1)" id="id_zag_'.htmlspecialchars($t_n_url).'_2"';
+ echo 'onmousedown="cur_click=document.getElementById(\''.htmlspecialchars($t_n_url).'\');"';
+ echo 'onmouseout="Table_onover(\''.htmlspecialchars($t_n_url).'\',1,1)">';
+ }
+ ?>
+</tr>
+</thead>
+<tbody id="id_tbody_<?php echo $t_n_url ?>"
+ <?php
+ if (isset($tab_pos[$t_n]) && empty($tab_pos[$t_n]["V"])) {
+ echo 'style="display: none;"';
+ }
+ echo '>';
+ $display_field = PMA_getDisplayField($_GET['db'], $GLOBALS['PMD']["TABLE_NAME_SMALL"][$i]);
+ for ($j = 0, $id_cnt = count($tab_column[$t_n]["COLUMN_ID"]); $j < $id_cnt; $j++) {
+ ?>
+<tr id="id_tr_<?php
+ echo $GLOBALS['PMD_URL']["TABLE_NAME_SMALL"][$i] . '.'
+ . urlencode($tab_column[$t_n]["COLUMN_NAME"][$j]) ?>"
+ <?php
+ if ($display_field == $tab_column[$t_n]["COLUMN_NAME"][$j]) {
+ echo ' class="tab_field_3" ';
+ } else {
+ echo ' class="tab_field" ';
+ }
+ ?>
+ onmouseover="old_class = this.className; this.className = 'tab_field_2';"
+ onmouseout="this.className = old_class;"
+ onmousedown="Click_field('<?php
+ echo $GLOBALS['PMD_URL']["TABLE_NAME_SMALL"][$i]."','".urlencode($tab_column[$t_n]["COLUMN_NAME"][$j])."',";
+ if (!PMA_Util::isForeignKeySupported($GLOBALS['PMD']['TABLE_TYPE'][$i])) {
+ echo (isset($tables_pk_or_unique_keys[$t_n . "." . $tab_column[$t_n]["COLUMN_NAME"][$j]]) ? 1 : 0);
+ } else {
+ // if foreign keys are supported, it's not necessary that the
+ // index is a primary key
+ echo (isset($tables_all_keys[$t_n.".".$tab_column[$t_n]["COLUMN_NAME"][$j]]) ? 1 : 0);
+ }
+ ?>)">
+ <?php
+ if (isset($_REQUEST['query'])) {
+ echo '<td class="select_all">';
+ echo '<input value="'.htmlspecialchars($t_n_url).urlencode($tab_column[$t_n]["COLUMN_NAME"][$j]).'"';
+ echo 'type="checkbox" id="select_'.htmlspecialchars($t_n_url).'._'.urlencode($tab_column[$t_n]["COLUMN_NAME"][$j]).'" ';
+ echo 'style="margin: 0px;" title="select_'.urlencode($tab_column[$t_n]["COLUMN_NAME"][$j]).'" ';
+ echo 'onclick="store_column(\''.urlencode($GLOBALS['PMD_OUT']["TABLE_NAME_SMALL"][$i]).'\',\''.htmlspecialchars($GLOBALS['PMD_OUT']["OWNER"][$i]).'\',\''.urlencode($tab_column[$t_n]["COLUMN_NAME"][$j]).'\')"></td>';
+ }?>
+ <td width="10px" colspan="3"
+ id="<?php echo $t_n_url.".".urlencode($tab_column[$t_n]["COLUMN_NAME"][$j]) ?>">
+ <div class="nowrap">
+ <?php
+ if (isset($tables_pk_or_unique_keys[$t_n.".".$tab_column[$t_n]["COLUMN_NAME"][$j]])) {
+ ?>
+ <img src="<?php echo $_SESSION['PMA_Theme']->getImgPath(); ?>pmd/FieldKey_small.png"
+ alt="*" />
+ <?php
+ } else {
+ ?>
+ <img src="<?php echo $_SESSION['PMA_Theme']->getImgPath(); ?>pmd/Field_small<?php
+ if (strstr($tab_column[$t_n]["TYPE"][$j], 'char')
+ || strstr($tab_column[$t_n]["TYPE"][$j], 'text')
+ ) {
+ echo '_char';
+ } elseif (strstr($tab_column[$t_n]["TYPE"][$j], 'int')
+ || strstr($tab_column[$t_n]["TYPE"][$j], 'float')
+ || strstr($tab_column[$t_n]["TYPE"][$j], 'double')
+ || strstr($tab_column[$t_n]["TYPE"][$j], 'decimal')
+ ) {
+ echo '_int';
+ } elseif (strstr($tab_column[$t_n]["TYPE"][$j], 'date')
+ || strstr($tab_column[$t_n]["TYPE"][$j], 'time')
+ || strstr($tab_column[$t_n]["TYPE"][$j], 'year')
+ ) {
+ echo '_date';
+ }
+ ?>.png" alt="*" />
+ <?php
+ }
+ echo htmlspecialchars(
+ $tab_column[$t_n]["COLUMN_NAME"][$j] . " : " . $tab_column[$t_n]["TYPE"][$j],
+ ENT_QUOTES
+ );
+ echo "</div>\n</td>\n";
+ if (isset($_REQUEST['query'])) {
+ //$temp = $GLOBALS['PMD_OUT']["OWNER"][$i].'.'.$GLOBALS['PMD_OUT']["TABLE_NAME_SMALL"][$i];
+ echo '<td class="small_tab_pref" onmouseover="this.className=\'small_tab_pref2\';"';
+ echo 'onmouseout="this.className=\'small_tab_pref\';"';
+ echo 'onclick="Click_option(\'pmd_optionse\',\''
+ . urlencode($tab_column[$t_n]["COLUMN_NAME"][$j]) . '\',\''
+ . $GLOBALS['PMD_OUT']["TABLE_NAME_SMALL"][$i].'\')" >';
+ echo '<img src="'
+ . $_SESSION['PMA_Theme']->getImgPath('pmd/exec_small.png')
+ . '" title="options" alt="" /></td> ';
+ }
+ echo "</tr>\n";
+ }
+ echo "</tbody>\n</table>\n";
+}
+?>
+</form>
+</div>
+<div id="pmd_hint"></div>
+
+<table id="layer_new_relation" style="display:none;"
+ width="5%" cellpadding="0" cellspacing="0">
+<tbody>
+<tr>
+ <td class="frams1" width="10px"></td>
+ <td class="frams5" width="99%" ></td>
+ <td class="frams2" width="10px"><div class="bor"></div></td>
+</tr>
+<tr>
+ <td class="frams8"></td>
+ <td class="input_tab">
+ <table width="168" class="center" cellpadding="2" cellspacing="0">
+ <thead>
+ <tr>
+ <td colspan="2" class="center nowrap"><strong><?php echo __('Create relation'); ?></strong></td>
+ </tr>
+ </thead>
+ <tbody id="foreign_relation">
+ <tr>
+ <td colspan="2" class="center nowrap"><strong>FOREIGN KEY</strong></td>
+ </tr>
+ <tr>
+ <td width="58" class="nowrap">on delete</td>
+ <td width="102"><select name="on_delete" id="on_delete">
+ <option value="nix" selected="selected">--</option>
+ <option value="CASCADE">CASCADE</option>
+ <option value="SET NULL">SET NULL</option>
+ <option value="NO ACTION">NO ACTION</option>
+ <option value="RESTRICT">RESTRICT</option>
+ </select>
+ </td>
+ </tr>
+ <tr>
+ <td class="nowrap">on update</td>
+ <td><select name="on_update" id="on_update">
+ <option value="nix" selected="selected">--</option>
+ <option value="CASCADE">CASCADE</option>
+ <option value="SET NULL">SET NULL</option>
+ <option value="NO ACTION">NO ACTION</option>
+ <option value="RESTRICT">RESTRICT</option>
+ </select>
+ </td>
+ </tr>
+ </tbody>
+ <tbody>
+ <tr>
+ <td colspan="2" class="center nowrap">
+ <input type="button" class="butt" name="Button"
+ value="<?php echo __('OK'); ?>" onclick="New_relation()" />
+ <input type="button" class="butt" name="Button"
+ value="<?php echo __('Cancel'); ?>"
+ onclick="document.getElementById('layer_new_relation').style.display = 'none';" />
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </td>
+ <td class="frams6"></td>
+</tr>
+<tr>
+ <td class="frams4"><div class="bor"></div></td>
+ <td class="frams7"></td>
+ <td class="frams3"></td>
+</tr>
+</tbody>
+</table>
+
+<table id="layer_upd_relation" style="display:none;"
+ width="5%" cellpadding="0" cellspacing="0">
+<tbody>
+<tr>
+ <td class="frams1" width="10px"></td>
+ <td class="frams5" width="99%"></td>
+ <td class="frams2" width="10px"><div class="bor"></div></td>
+</tr>
+<tr>
+ <td class="frams8"></td>
+ <td class="input_tab">
+ <table width="100%" class="center" cellpadding="2" cellspacing="0">
+ <tr>
+ <td colspan="3" class="center nowrap"><strong><?php echo __('Delete relation'); ?></strong></td>
+ </tr>
+ <tr>
+ <td colspan="3" class="center nowrap">
+ <input name="Button" type="button" class="butt"
+ onclick="Upd_relation()" value="<?php echo __('Delete'); ?>" />
+ <input type="button" class="butt" name="Button"
+ value="<?php echo __('Cancel'); ?>"
+ onclick="document.getElementById('layer_upd_relation').style.display = 'none'; Re_load();" />
+ </td>
+ </tr>
+ </table></td>
+ <td class="frams6"></td>
+</tr>
+<tr>
+ <td class="frams4"><div class="bor"></div></td>
+ <td class="frams7"></td>
+ <td class="frams3"></td>
+</tr>
+</tbody>
+</table>
+
+<table id="pmd_optionse" style="display:none;"
+ width="5%" cellpadding="0" cellspacing="0">
+<tbody>
+<tr>
+ <td class="frams1" width="10px"></td>
+ <td class="frams5" width="99%" ></td>
+ <td class="frams2" width="10px"><div class="bor"></div></td>
+</tr>
+<tr>
+ <td class="frams8"></td>
+ <td class="input_tab">
+ <table width="168" class="center" cellpadding="2" cellspacing="0">
+ <thead>
+ <tr>
+ <td colspan="2" rowspan="2" id="option_col_name" class="center nowrap"></td>
+ </tr>
+ </thead>
+ <tbody id="where">
+ <tr><td class="center nowrap"><b>WHERE</b></td></tr>
+ <tr>
+ <td width="58" class="nowrap"><?php echo __('Relation operator'); ?></td>
+ <td width="102"><select name="rel_opt" id="rel_opt">
+ <option value="--" selected="selected"> -- </option>
+ <option value="="> = </option>
+ <option value="&gt;"> &gt; </option>
+ <option value="&lt;"> &lt; </option>
+ <option value="&gt;="> &gt;= </option>
+ <option value="&lt;="> &lt;= </option>
+ <option value="NOT"> NOT </option>
+ <option value="IN"> IN </option>
+ <option value="EXCEPT"> <?php echo __('Except'); ?> </option>
+ <option value="NOT IN"> NOT IN </option>
+ </select>
+ </td>
+ </tr>
+ <tr>
+ <td class="nowrap"><?php echo __('Value'); ?>/<br /><?php echo __('subquery'); ?></td>
+ <td><textarea id="Query" value="" cols="18"></textarea>
+ </td>
+ </tr>
+ <tr><td class="center nowrap"><b><?php echo __('Rename to'); ?></b></td></tr>
+ <tr>
+ <td width="58" class="nowrap"><?php echo __('New name'); ?></td>
+ <td width="102"><input type="text" value="" id="new_name"/></td>
+ </tr>
+ <tr><td class="center nowrap"><b><?php echo __('Aggregate'); ?></b></td></tr>
+ <tr>
+ <td width="58" class="nowrap"><?php echo __('Operator'); ?></td>
+ <td width="102"><select name="operator" id="operator">
+ <option value="---" selected="selected">---</option>
+ <option value="sum" > SUM </option>
+ <option value="min"> MIN </option>
+ <option value="max"> MAX </option>
+ <option value="avg"> AVG </option>
+ <option value="count"> COUNT </option>
+ </select>
+ </td></tr>
+ <tr>
+ <td width="58" class="center nowrap"><b>GROUP BY</b></td>
+ <td><input type="checkbox" value="groupby" id="groupby"/></td>
+ </tr>
+ <tr>
+ <td width="58" class="center nowrap"><b>ORDER BY</b></td>
+ <td><input type="checkbox" value="orderby" id="orderby"/></td>
+ </tr>
+ <tr><td class="center nowrap"><b>HAVING</b></td></tr>
+ <tr>
+ <td width="58" class="nowrap"><?php echo __('Operator'); ?></td>
+ <td width="102"><select name="h_operator" id="h_operator">
+ <option value="---" selected="selected">---</option>
+ <option value="None" > <?php echo __('None'); ?> </option>
+ <option value="sum" > SUM </option>
+ <option value="min"> MIN </option>
+ <option value="max"> MAX </option>
+ <option value="avg"> AVG </option>
+ <option value="count"> COUNT </option>
+ </select>
+ </td></tr>
+ <tr>
+ <td width="58" class="nowrap"><?php echo __('Relation operator'); ?></td>
+ <td width="102"><select name="h_rel_opt" id="h_rel_opt">
+ <option value="--" selected="selected"> -- </option>
+ <option value="="> = </option>
+ <option value="&gt;"> &gt; </option>
+ <option value="&lt;"> &lt; </option>
+ <option value="&gt;="> &gt;= </option>
+ <option value="&lt;="> &lt;= </option>
+ <option value="NOT"> NOT </option>
+ <option value="IN"> IN </option>
+ <option value="EXCEPT"> <?php echo __('Except'); ?> </option>
+ <option value="NOT IN"> NOT IN </option>
+ </select>
+ </td>
+ </tr>
+ <tr>
+ <td width="58" class="nowrap"><?php echo __('Value'); ?>/<br/><?php echo __('subquery'); ?></td>
+ <td width="102"><textarea id="having" value="" cols="18"></textarea></td>
+ </tr>
+ </tbody>
+ <tbody>
+ <tr>
+ <td colspan="2" class="center nowrap">
+ <input type="button" class="butt" name="Button"
+ value="<?php echo __('OK'); ?>" onclick="add_object()" />
+ <input type="button" class="butt" name="Button"
+ value="<?php echo __('Cancel'); ?>"
+ onclick="Close_option()" />
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </td>
+ <td class="frams6"></td>
+</tr>
+<tr>
+ <td class="frams4"><div class="bor"></div></td>
+ <td class="frams7"></td>
+ <td class="frams3"></td>
+</tr>
+</tbody>
+</table>
+
+<table id="query_rename_to" style="display:none;"
+ width="5%" cellpadding="0" cellspacing="0">
+<tbody>
+<tr>
+ <td class="frams1" width="10px"></td>
+ <td class="frams5" width="99%" ></td>
+ <td class="frams2" width="10px"><div class="bor"></div></td>
+</tr>
+<tr>
+ <td class="frams8"></td>
+ <td class="input_tab">
+ <table width="168" class="center" cellpadding="2" cellspacing="0">
+ <thead>
+ <tr>
+ <td colspan="2" class="center nowrap"><strong><?php echo __('Rename to'); ?></strong></td>
+ </tr>
+ </thead>
+ <tbody id="rename_to">
+ <tr>
+ <td width="58" class="nowrap"><?php echo __('New name'); ?></td>
+ <td width="102">
+ <input type="text" value="" id="e_rename"/>
+ </td>
+ </tr>
+ </tbody>
+ <tbody>
+ <tr>
+ <td colspan="2" class="center nowrap">
+ <input type="button" class="butt" name="Button"
+ value="<?php echo __('OK'); ?>" onclick="edit('Rename')" />
+ <input type="button" class="butt" name="Button"
+ value="<?php echo __('Cancel'); ?>"
+ onclick="document.getElementById('query_rename_to').style.display = 'none';" />
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </td>
+ <td class="frams6"></td>
+</tr>
+<tr>
+ <td class="frams4"><div class="bor"></div></td>
+ <td class="frams7"></td>
+ <td class="frams3"></td>
+</tr>
+</tbody>
+</table>
+
+<table id="query_having" style="display:none;"
+ width="5%" cellpadding="0" cellspacing="0">
+<tbody>
+ <tr>
+ <td class="frams1" width="10px"></td>
+ <td class="frams5" width="99%" ></td>
+ <td class="frams2" width="10px"><div class="bor"></div></td>
+ </tr>
+<tr>
+ <td class="frams8"></td>
+ <td class="input_tab">
+ <table width="168" class="center" cellpadding="2" cellspacing="0">
+ <thead>
+ <tr>
+ <td colspan="2" class="center nowrap"><strong>HAVING</strong></td>
+ </tr>
+ </thead>
+ <tbody id="rename_to">
+ <tr>
+ <td width="58" class="nowrap"><?php echo __('Operator'); ?></td>
+ <td width="102"><select name="hoperator" id="hoperator">
+ <option value="---" selected="selected">---</option>
+ <option value="None" > None </option>
+ <option value="sum" > SUM </option>
+ <option value="min"> MIN </option>
+ <option value="max"> MAX </option>
+ <option value="avg"> AVG </option>
+ <option value="count"> COUNT </option>
+ </select>
+ </td></tr>
+ <tr>
+ <tr>
+ <td width="58" class="nowrap"><?php echo __('Operator'); ?></td>
+ <td width="102"><select name="hrel_opt" id="hrel_opt">
+ <option value="--" selected="selected"> -- </option>
+ <option value="="> = </option>
+ <option value="&gt;"> &gt; </option>
+ <option value="&lt;"> &lt; </option>
+ <option value="&gt;="> &gt;= </option>
+ <option value="&lt;="> &lt;= </option>
+ <option value="NOT"> NOT </option>
+ <option value="IN"> IN </option>
+ <option value="EXCEPT"> <?php echo __('Except'); ?> </option>
+ <option value="NOT IN"> NOT IN </option>
+ </select>
+ </td>
+ </tr>
+ <tr>
+ <td class="nowrap"><?php echo __('Value'); ?>/<br /><?php echo __('subquery'); ?></td>
+ <td><textarea id="hQuery" value="" cols="18"></textarea>
+ </td>
+ </tr>
+ </tbody>
+ <tbody>
+ <tr>
+ <td colspan="2" class="center nowrap">
+ <input type="button" class="butt" name="Button"
+ value="<?php echo __('OK'); ?>" onclick="edit('Having')" />
+ <input type="button" class="butt" name="Button"
+ value="<?php echo __('Cancel'); ?>"
+ onclick="document.getElementById('query_having').style.display = 'none';" />
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </td>
+ <td class="frams6"></td>
+</tr>
+<tr>
+ <td class="frams4"><div class="bor"></div></td>
+ <td class="frams7"></td>
+ <td class="frams3"></td>
+</tr>
+</tbody>
+</table>
+
+<table id="query_Aggregate" style="display:none;"
+ width="5%" cellpadding="0" cellspacing="0">
+<tbody>
+<tr>
+ <td class="frams1" width="10px"></td>
+ <td class="frams5" width="99%" ></td>
+ <td class="frams2" width="10px"><div class="bor"></div></td>
+</tr>
+<tr>
+ <td class="frams8"></td>
+ <td class="input_tab">
+ <table width="168" class="center" cellpadding="2" cellspacing="0">
+ <thead>
+ <tr>
+ <td colspan="2" class="center nowrap"><strong><?php echo __('Aggregate'); ?></strong></td>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td width="58" class="nowrap"><?php echo __('Operator'); ?></td>
+ <td width="102">
+ <select name="operator" id="e_operator">
+ <option value="---" selected="selected">---</option>
+ <option value="sum" > SUM </option>
+ <option value="min"> MIN </option>
+ <option value="max"> MAX </option>
+ <option value="avg"> AVG </option>
+ <option value="avg"> COUNT </option>
+ </select>
+ </td></tr>
+ </tbody>
+ <tbody>
+ <tr>
+ <td colspan="2" class="center nowrap">
+ <input type="button" class="butt" name="Button"
+ value="<?php echo __('OK'); ?>" onclick="edit('Aggregate')" />
+ <input type="button" class="butt" name="Button"
+ value="<?php echo __('Cancel'); ?>"
+ onclick="document.getElementById('query_Aggregate').style.display = 'none';" />
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </td>
+ <td class="frams6"></td>
+</tr>
+<tr>
+ <td class="frams4"><div class="bor"></div></td>
+ <td class="frams7"></td>
+ <td class="frams3"></td>
+</tr>
+</tbody>
+</table>
+
+<table id="query_where" style="display:none;"
+ width="5%" cellpadding="0" cellspacing="0">
+<tbody>
+ <tr>
+ <td class="frams1" width="10px"></td>
+ <td class="frams5" width="99%" ></td>
+ <td class="frams2" width="10px"><div class="bor"></div></td>
+ </tr>
+<tr>
+ <td class="frams8"></td>
+ <td class="input_tab">
+ <table width="168" class="center" cellpadding="2" cellspacing="0">
+ <thead>
+ <tr>
+ <td colspan="2" class="center nowrap"><strong>WHERE</strong></td>
+ </tr>
+ </thead>
+ <tbody id="rename_to">
+ <tr>
+ <td width="58" class="nowrap"><?php echo __('Operator'); ?></td>
+ <td width="102"><select name="erel_opt" id="erel_opt">
+ <option value="--" selected="selected"> -- </option>
+ <option value="=" > = </option>
+ <option value="&gt;"> &gt; </option>
+ <option value="&lt;"> &lt; </option>
+ <option value="&gt;="> &gt;= </option>
+ <option value="&lt;="> &lt;= </option>
+ <option value="NOT"> NOT </option>
+ <option value="IN"> IN </option>
+ <option value="EXCEPT"> <?php echo __('Except'); ?> </option>
+ <option value="NOT IN"> NOT IN </option>
+ </select>
+ </td>
+ </tr>
+ <tr>
+ <td class="nowrap"><?php echo __('Value'); ?>/<br /><?php echo __('subquery'); ?></td>
+ <td><textarea id="eQuery" value="" cols="18"></textarea>
+ </td>
+ </tr>
+ </tbody>
+ <tbody>
+ <tr>
+ <td colspan="2" class="center nowrap">
+ <input type="button" class="butt" name="Button"
+ value="<?php echo __('OK'); ?>" onclick="edit('Where')" />
+ <input type="button" class="butt" name="Button"
+ value="<?php echo __('Cancel'); ?>"
+ onclick="document.getElementById('query_where').style.display = 'none';" />
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </td>
+ <td class="frams6"></td>
+</tr>
+<tr>
+ <td class="frams4"><div class="bor"></div></td>
+ <td class="frams7"></td>
+ <td class="frams3"></td>
+</tr>
+</tbody>
+</table>
+
+<?php
+if (! empty($_REQUEST['query'])) {
+ echo '<div class="panel">';
+ echo '<div style="clear:both;"></div>';
+ echo '<div id="ab"></div>';
+ echo '<div style="clear:both;"></div>';
+ echo '</div>';
+ echo '<a class="trigger" href="#">' . __('Active options') . '</a>';
+ echo '<div id="filter"></div>';
+ echo '<div id="box">';
+ echo '<span id="boxtitle"></span>';
+ echo '<form method="post" action="db_qbe.php">';
+ echo '<textarea cols="80" name="sql_query" id="textSqlquery"'
+ . ' rows="15"></textarea><div id="tblfooter">';
+ echo ' <input type="submit" name="submit_sql" class="btn" />';
+ echo ' <input type="button" name="cancel" value="'
+ . __('Cancel') . '" onclick="closebox()" class="btn" />';
+ echo PMA_URL_getHiddenInputs($_GET['db']);
+ echo '</div></p>';
+ echo '</form></div>';
+
+} ?>
+
+
+<!-- cache images -->
+<?php
+echo '<img src="'
+ . $_SESSION['PMA_Theme']->getImgPath('pmd/2leftarrow_m.png')
+ . '" width="0" height="0" alt="" />'
+ . '<img src="'
+ . $_SESSION['PMA_Theme']->getImgPath('pmd/rightarrow1.png')
+ . '" width="0" height="0" alt="" />'
+ . '<img src="'
+ . $_SESSION['PMA_Theme']->getImgPath('pmd/rightarrow2.png')
+ . '" width="0" height="0" alt="" />'
+ . '<img src="'
+ . $_SESSION['PMA_Theme']->getImgPath('pmd/uparrow2_m.png')
+ . '" width="0" height="0" alt="" />'
+ . '<div id="PMA_disable_floating_menubar"></div>';
+?>
diff --git a/pmd_pdf.php b/pmd_pdf.php
new file mode 100644
index 0000000000..141fcd4806
--- /dev/null
+++ b/pmd_pdf.php
@@ -0,0 +1,163 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * PDF export for PMD
+ *
+ * @package PhpMyAdmin-Designer
+ */
+
+require_once './libraries/common.inc.php';
+require_once 'libraries/pmd_common.php';
+
+/**
+ * Validate vulnerable POST parameters
+ */
+if (isset($_POST['scale']) && ! PMA_isValid($_POST['scale'], 'numeric')) {
+ die('Attack stopped');
+}
+
+/**
+ * Sets globals from $_POST
+ */
+$post_params = array(
+ 'db'
+);
+
+foreach ($post_params as $one_post_param) {
+ if (isset($_POST[$one_post_param])) {
+ $GLOBALS[$one_post_param] = $_POST[$one_post_param];
+ }
+}
+
+/**
+ * If called directly from the designer, first save the positions
+ */
+if (! isset($_POST['scale'])) {
+ include_once 'pmd_save_pos.php';
+}
+
+if (isset($_POST['mode'])) {
+ if ('create_export' != $_POST['mode'] && empty($_POST['pdf_page_number'])) {
+ die("<script>alert('Pages not found!');history.go(-2);</script>");
+ }
+
+ $pmd_table = PMA_Util::backquote($GLOBALS['cfgRelation']['db']) . '.'
+ . PMA_Util::backquote($GLOBALS['cfgRelation']['designer_coords']);
+ $pma_table = PMA_Util::backquote($GLOBALS['cfgRelation']['db']) . '.'
+ . PMA_Util::backquote($cfgRelation['table_coords']);
+ $scale_q = PMA_Util::sqlAddSlashes($_POST['scale']);
+
+ if ('create_export' == $_POST['mode']) {
+ $pdf_page_number = PMA_REL_createPage($_POST['newpage'], $cfgRelation, $db);
+ if ($pdf_page_number > 0) {
+ $message = PMA_Message::success(__('Page has been created'));
+ $_POST['mode'] = 'export';
+ } else {
+ $message = PMA_Message::error(__('Page creation failed'));
+ }
+ } else {
+ $pdf_page_number = $_POST['pdf_page_number'];
+ }
+
+ $pdf_page_number_q = PMA_Util::sqlAddSlashes($pdf_page_number);
+
+ if ('export' == $_POST['mode']) {
+ $sql = "REPLACE INTO " . $pma_table
+ . " (db_name, table_name, pdf_page_number, x, y)"
+ . " SELECT db_name, table_name, " . $pdf_page_number_q . ","
+ . " ROUND(x/" . $scale_q . ") , ROUND(y/" . $scale_q . ") y"
+ . " FROM " . $pmd_table
+ . " WHERE db_name = '" . PMA_Util::sqlAddSlashes($db) . "'";
+
+ PMA_queryAsControlUser($sql, true, PMA_DatabaseInterface::QUERY_STORE);
+ }
+
+ if ('import' == $_POST['mode']) {
+ PMA_queryAsControlUser(
+ 'UPDATE ' . $pma_table . ',' . $pmd_table .
+ ' SET ' . $pmd_table . '.`x`= ' . $pma_table . '.`x` * '. $scale_q . ',
+ ' . $pmd_table . '.`y`= ' . $pma_table . '.`y` * '. $scale_q .'
+ WHERE
+ ' . $pmd_table . '.`db_name`=' . $pma_table . '.`db_name`
+ AND
+ ' . $pmd_table . '.`table_name` = ' . $pma_table . '.`table_name`
+ AND
+ ' . $pmd_table . '.`db_name`=\''. PMA_Util::sqlAddSlashes($db) . '\'
+ AND pdf_page_number = ' . $pdf_page_number_q . ';',
+ true, PMA_DatabaseInterface::QUERY_STORE
+ );
+ }
+}
+
+$response = PMA_Response::getInstance();
+$response->getFooter()->setMinimal();
+
+?>
+<br/>
+<div>
+<?php
+if (! empty($message)) {
+ $message->display();
+}
+?>
+ <form name="form1" method="post" action="pmd_pdf.php">
+<?php
+echo PMA_URL_getHiddenInputs($db);
+echo '<div>';
+echo '<fieldset><legend>' . __('Import/Export coordinates for PDF schema') . '</legend>';
+
+$choices = array();
+
+$table_info_result = PMA_queryAsControlUser(
+ 'SELECT * FROM ' . PMA_Util::backquote($GLOBALS['cfgRelation']['db'])
+ . '.' . PMA_Util::backquote($cfgRelation['pdf_pages'])
+ . ' WHERE db_name = \'' . PMA_Util::sqlAddSlashes($db) . '\''
+);
+
+if ($GLOBALS['dbi']->numRows($table_info_result) > 0) {
+ echo '<p>' . __('Page:');
+ echo '<select name="pdf_page_number">';
+
+ while ($page = $GLOBALS['dbi']->fetchAssoc($table_info_result)) {
+ echo '<option value="' . $page['page_nr'] . '">';
+ echo htmlspecialchars($page['page_descr']);
+ echo '</option>';
+ }
+ echo '</select>';
+ echo '</p>';
+ $choices['import'] = __('Import from selected page');
+ $choices['export'] = __('Export to selected page');
+}
+$choices['create_export'] = __('Create a page and export to it');
+
+if (1 == count($choices)) {
+ echo $choices['create_export'];
+ echo '<input type="hidden" name="mode" value="create_export" />';
+} else {
+ echo PMA_Util::getRadioFields(
+ 'mode', $choices, $checked_choice = '', $line_break = true,
+ $escape_label = false, $class = ''
+ );
+}
+echo '<br />';
+echo '<label for="newpage">' . __('New page name: ') . '</label>';
+echo '<input id="newpage" type="text" name="newpage" />';
+
+echo '<p>' . __('Export/Import to scale:');
+?>
+ <select name="scale">
+ <option value="1">1:1</option>
+ <option value="2">1:2</option>
+ <option value="3" selected="selected">
+ 1:3 (<?php echo __('recommended'); ?>)
+ </option>
+ <option value="4">1:4</option>
+ <option value="5">1:5</option>
+ </select>
+ </p>
+ <input type="submit" value="<?php echo __('Go'); ?>"/>
+ </fieldset>
+ </div>
+ </form>
+</div>
+
diff --git a/pmd_relation_new.php b/pmd_relation_new.php
new file mode 100644
index 0000000000..2d0657bd21
--- /dev/null
+++ b/pmd_relation_new.php
@@ -0,0 +1,127 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * PMD handler for creating new relation
+ *
+ * @package PhpMyAdmin-Designer
+ */
+
+/**
+ *
+ */
+require_once './libraries/common.inc.php';
+
+PMA_Response::getInstance()->disable();
+
+require_once 'libraries/pmd_common.php';
+$die_save_pos = 0;
+require_once 'pmd_save_pos.php';
+extract($_POST, EXTR_SKIP);
+
+$tables = $GLOBALS['dbi']->getTablesFull($db, $T1);
+$type_T1 = strtoupper($tables[$T1]['ENGINE']);
+$tables = $GLOBALS['dbi']->getTablesFull($db, $T2);
+$type_T2 = strtoupper($tables[$T2]['ENGINE']);
+
+// native foreign key
+if (PMA_Util::isForeignKeySupported($type_T1)
+ && PMA_Util::isForeignKeySupported($type_T2)
+ && $type_T1 == $type_T2
+) {
+ // relation exists?
+ $existrel_foreign = PMA_getForeigners($db, $T2, '', 'foreign');
+ if (isset($existrel_foreign[$F2])
+ && isset($existrel_foreign[$F2]['constraint'])
+ ) {
+ PMD_return_new(0, __('Error: relation already exists.'));
+ }
+ // note: in InnoDB, the index does not requires to be on a PRIMARY
+ // or UNIQUE key
+ // improve: check all other requirements for InnoDB relations
+ $result = $GLOBALS['dbi']->query(
+ 'SHOW INDEX FROM ' . PMA_Util::backquote($db)
+ . '.' . PMA_Util::backquote($T1) . ';'
+ );
+ $index_array1 = array(); // will be use to emphasis prim. keys in the table view
+ while ($row = $GLOBALS['dbi']->fetchAssoc($result)) {
+ $index_array1[$row['Column_name']] = 1;
+ }
+ $GLOBALS['dbi']->freeResult($result);
+
+ $result = $GLOBALS['dbi']->query(
+ 'SHOW INDEX FROM ' . PMA_Util::backquote($db)
+ . '.' . PMA_Util::backquote($T2) . ';'
+ );
+ // will be used to emphasis prim. keys in the table view
+ $index_array2 = array();
+ while ($row = $GLOBALS['dbi']->fetchAssoc($result)) {
+ $index_array2[$row['Column_name']] = 1;
+ }
+ $GLOBALS['dbi']->freeResult($result);
+
+ if (! empty($index_array1[$F1]) && ! empty($index_array2[$F2])) {
+ $upd_query = 'ALTER TABLE ' . PMA_Util::backquote($db)
+ . '.' . PMA_Util::backquote($T2)
+ . ' ADD FOREIGN KEY ('
+ . PMA_Util::backquote($F2) . ')'
+ . ' REFERENCES '
+ . PMA_Util::backquote($db) . '.'
+ . PMA_Util::backquote($T1) . '('
+ . PMA_Util::backquote($F1) . ')';
+
+ if ($on_delete != 'nix') {
+ $upd_query .= ' ON DELETE ' . $on_delete;
+ }
+ if ($on_update != 'nix') {
+ $upd_query .= ' ON UPDATE ' . $on_update;
+ }
+ $upd_query .= ';';
+ $GLOBALS['dbi']->tryQuery($upd_query) or PMD_return_new(0, __('Error: Relation not added.'));
+ PMD_return_new(1, __('FOREIGN KEY relation added'));
+ }
+} else { // internal (pmadb) relation
+ if ($GLOBALS['cfgRelation']['relwork'] == false) {
+ PMD_return_new(0, __('Error: Relational features are disabled!'));
+ } else {
+ // no need to recheck if the keys are primary or unique at this point,
+ // this was checked on the interface part
+
+ $q = 'INSERT INTO ' . PMA_Util::backquote($GLOBALS['cfgRelation']['db']) . '.' . PMA_Util::backquote($cfgRelation['relation'])
+ . '(master_db, master_table, master_field, foreign_db, foreign_table, foreign_field)'
+ . ' values('
+ . '\'' . PMA_Util::sqlAddSlashes($db) . '\', '
+ . '\'' . PMA_Util::sqlAddSlashes($T2) . '\', '
+ . '\'' . PMA_Util::sqlAddSlashes($F2) . '\', '
+ . '\'' . PMA_Util::sqlAddSlashes($db) . '\', '
+ . '\'' . PMA_Util::sqlAddSlashes($T1) . '\','
+ . '\'' . PMA_Util::sqlAddSlashes($F1) . '\')';
+
+ if (PMA_queryAsControlUser($q, false, PMA_DatabaseInterface::QUERY_STORE)) {
+ PMD_return_new(1, __('Internal relation added'));
+ } else {
+ PMD_return_new(0, __('Error: Relation not added.'));
+ }
+ }
+}
+
+/**
+ * Send xml
+ *
+ * @param string $b Value of attribute "b"
+ * @param string $ret Value of attribute "return"
+ */
+function PMD_return_new($b,$ret)
+{
+ global $db,$T1,$F1,$T2,$F2;
+ header("Content-Type: text/xml; charset=utf-8");
+ header("Cache-Control: no-cache");
+ die('<root act="relation_new" return="'.$ret.'" b="'.$b.
+ '" DB1="'.urlencode($db).
+ '" T1="'.urlencode($T1).
+ '" F1="'.urlencode($F1).
+ '" DB2="'.urlencode($db).
+ '" T2="'.urlencode($T2).
+ '" F2="'.urlencode($F2).
+ '"></root>');
+}
+?>
diff --git a/pmd_relation_upd.php b/pmd_relation_upd.php
new file mode 100644
index 0000000000..9dff0579b8
--- /dev/null
+++ b/pmd_relation_upd.php
@@ -0,0 +1,69 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * PMD relation update handler
+ *
+ * @package PhpMyAdmin-Designer
+ */
+
+/**
+ *
+ */
+require_once './libraries/common.inc.php';
+
+PMA_Response::getInstance()->disable();
+
+require_once 'libraries/pmd_common.php';
+extract($_POST, EXTR_SKIP);
+extract($_GET, EXTR_SKIP);
+$die_save_pos = 0;
+require_once 'pmd_save_pos.php';
+list($DB1, $T1) = explode(".", $T1);
+list($DB2, $T2) = explode(".", $T2);
+
+$tables = $GLOBALS['dbi']->getTablesFull($db, $T1);
+$type_T1 = strtoupper($tables[$T1]['ENGINE']);
+$tables = $GLOBALS['dbi']->getTablesFull($db, $T2);
+$type_T2 = strtoupper($tables[$T2]['ENGINE']);
+
+$try_to_delete_internal_relation = false;
+
+if (PMA_Util::isForeignKeySupported($type_T1)
+ && PMA_Util::isForeignKeySupported($type_T2)
+ && $type_T1 == $type_T2
+) {
+ // InnoDB
+ $existrel_foreign = PMA_getForeigners($DB2, $T2, '', 'foreign');
+
+ if (isset($existrel_foreign[$F2]['constraint'])) {
+ $upd_query = 'ALTER TABLE ' . PMA_Util::backquote($DB2)
+ . '.' . PMA_Util::backquote($T2) . ' DROP FOREIGN KEY '
+ . PMA_Util::backquote($existrel_foreign[$F2]['constraint'])
+ . ';';
+ $upd_rs = $GLOBALS['dbi']->query($upd_query);
+ } else {
+ // there can be an internal relation even if InnoDB
+ $try_to_delete_internal_relation = true;
+ }
+} else {
+ $try_to_delete_internal_relation = true;
+}
+if ($try_to_delete_internal_relation) {
+ // internal relations
+ PMA_queryAsControlUser(
+ 'DELETE FROM '
+ . PMA_Util::backquote($GLOBALS['cfgRelation']['db']) . '.'
+ . $cfg['Server']['relation'].' WHERE '
+ . 'master_db = \'' . PMA_Util::sqlAddSlashes($DB2) . '\''
+ . ' AND master_table = \'' . PMA_Util::sqlAddSlashes($T2) . '\''
+ . ' AND master_field = \'' . PMA_Util::sqlAddSlashes($F2) . '\''
+ . ' AND foreign_db = \'' . PMA_Util::sqlAddSlashes($DB1) . '\''
+ . ' AND foreign_table = \'' . PMA_Util::sqlAddSlashes($T1) . '\''
+ . ' AND foreign_field = \'' . PMA_Util::sqlAddSlashes($F1) . '\'',
+ false,
+ PMA_DatabaseInterface::QUERY_STORE
+ );
+}
+PMD_return_upd(1, __('Relation deleted'));
+
+?>
diff --git a/pmd_save_pos.php b/pmd_save_pos.php
new file mode 100644
index 0000000000..fbf64cdcfa
--- /dev/null
+++ b/pmd_save_pos.php
@@ -0,0 +1,90 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Save handler for PMD
+ *
+ * @package PhpMyAdmin-Designer
+ */
+
+/**
+ *
+ */
+require_once './libraries/common.inc.php';
+require_once 'libraries/pmd_common.php';
+
+$cfgRelation = PMA_getRelationsParam();
+
+if (! $cfgRelation['designerwork']) {
+ PMD_errorSave();
+}
+
+/**
+ * Sets globals from $_POST
+ */
+$post_params = array(
+ 'die_save_pos',
+);
+
+foreach ($post_params as $one_post_param) {
+ if (isset($_POST[$one_post_param])) {
+ $GLOBALS[$one_post_param] = $_POST[$one_post_param];
+ }
+}
+
+foreach ($_POST['t_x'] as $key => $value) {
+ // table name decode (post PDF exp/imp)
+ $KEY = empty($_POST['IS_AJAX']) ? urldecode($key) : $key;
+ list($DB,$TAB) = explode(".", $KEY);
+ PMA_queryAsControlUser(
+ 'DELETE FROM ' . PMA_Util::backquote($GLOBALS['cfgRelation']['db'])
+ . '.' . PMA_Util::backquote($GLOBALS['cfgRelation']['designer_coords'])
+ . ' WHERE `db_name` = \'' . PMA_Util::sqlAddSlashes($DB) . '\''
+ . ' AND `table_name` = \'' . PMA_Util::sqlAddSlashes($TAB) . '\'',
+ true, PMA_DatabaseInterface::QUERY_STORE
+ );
+
+ PMA_queryAsControlUser(
+ 'INSERT INTO ' . PMA_Util::backquote($GLOBALS['cfgRelation']['db'])
+ . '.' . PMA_Util::backquote($GLOBALS['cfgRelation']['designer_coords'])
+ . ' (db_name, table_name, x, y, v, h)'
+ . ' VALUES ('
+ . '\'' . PMA_Util::sqlAddSlashes($DB) . '\', '
+ . '\'' . PMA_Util::sqlAddSlashes($TAB) . '\', '
+ . '\'' . PMA_Util::sqlAddSlashes($_POST['t_x'][$key]) . '\', '
+ . '\'' . PMA_Util::sqlAddSlashes($_POST['t_y'][$key]) . '\', '
+ . '\'' . PMA_Util::sqlAddSlashes($_POST['t_v'][$key]) . '\', '
+ . '\'' . PMA_Util::sqlAddSlashes($_POST['t_h'][$key]) . '\')',
+ true, PMA_DatabaseInterface::QUERY_STORE
+ );
+}
+//----------------------------------------------------------------------------
+
+/**
+ * Error handler
+ *
+ * @return void
+ */
+function PMD_errorSave()
+{
+ global $die_save_pos; // if this file included
+ if (! empty($die_save_pos)) {
+ header("Content-Type: text/xml; charset=utf-8");
+ header("Cache-Control: no-cache");
+ die(
+ '<root act="save_pos" return="'
+ . __('Error saving coordinates for Designer.')
+ . '"></root>'
+ );
+ }
+}
+
+if (! empty($die_save_pos)) {
+ header("Content-Type: text/xml; charset=utf-8");
+ header("Cache-Control: no-cache");
+ ?>
+ <root
+ act='save_pos'
+ return='<?php echo __('Modifications have been saved'); ?>'></root>
+ <?php
+}
+?>
diff --git a/prefs_forms.php b/prefs_forms.php
new file mode 100644
index 0000000000..c44e77519f
--- /dev/null
+++ b/prefs_forms.php
@@ -0,0 +1,92 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * User preferences page
+ *
+ * @package PhpMyAdmin
+ */
+
+/**
+ * Gets some core libraries and displays a top message if required
+ */
+require_once 'libraries/common.inc.php';
+require_once 'libraries/user_preferences.lib.php';
+require_once 'libraries/config/config_functions.lib.php';
+require_once 'libraries/config/messages.inc.php';
+require_once 'libraries/config/ConfigFile.class.php';
+require_once 'libraries/config/Form.class.php';
+require_once 'libraries/config/FormDisplay.class.php';
+require 'libraries/config/user_preferences.forms.php';
+
+$cf = new ConfigFile($GLOBALS['PMA_Config']->base_settings);
+PMA_userprefsPageInit($cf);
+
+// handle form processing
+
+$form_param = filter_input(INPUT_GET, 'form');
+if (! isset($forms[$form_param])) {
+ $forms_keys = array_keys($forms);
+ $form_param = array_shift($forms_keys);
+}
+
+$form_display = new FormDisplay($cf);
+foreach ($forms[$form_param] as $form_name => $form) {
+ // skip Developer form if no setting is available
+ if ($form_name == 'Developer' && !$GLOBALS['cfg']['UserprefsDeveloperTab']) {
+ continue;
+ }
+ $form_display->registerForm($form_name, $form, 1);
+}
+
+if (isset($_POST['revert'])) {
+ // revert erroneous fields to their default values
+ $form_display->fixErrors();
+ // redirect
+ $url_params = array('form' => $form_param);
+ PMA_sendHeaderLocation(
+ $cfg['PmaAbsoluteUri'] . 'prefs_forms.php'
+ . PMA_URL_getCommon($url_params, '&')
+ );
+ exit;
+}
+
+$error = null;
+if ($form_display->process(false) && !$form_display->hasErrors()) {
+ // save settings
+ $result = PMA_saveUserprefs($cf->getConfigArray());
+ if ($result === true) {
+ // reload config
+ $GLOBALS['PMA_Config']->loadUserPreferences();
+ $hash = ltrim(filter_input(INPUT_POST, 'tab_hash'), '#');
+ PMA_userprefsRedirect(
+ 'prefs_forms.php',
+ array('form' => $form_param),
+ $hash
+ );
+ exit;
+ } else {
+ $error = $result;
+ }
+}
+
+// display forms
+$response = PMA_Response::getInstance();
+$header = $response->getHeader();
+$scripts = $header->getScripts();
+$scripts->addFile('config.js');
+
+require 'libraries/user_preferences.inc.php';
+if ($error) {
+ $error->display();
+}
+if ($form_display->hasErrors()) {
+ // form has errors
+ ?>
+ <div class="error config-form">
+ <b><?php echo __('Cannot save settings, submitted form contains errors') ?></b>
+ <?php $form_display->displayErrors(); ?>
+ </div>
+ <?php
+}
+$form_display->display(true, true);
+?>
diff --git a/prefs_manage.php b/prefs_manage.php
new file mode 100644
index 0000000000..c13a0d3ea7
--- /dev/null
+++ b/prefs_manage.php
@@ -0,0 +1,333 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * User preferences management page
+ *
+ * @package PhpMyAdmin
+ */
+
+/**
+ * Gets some core libraries and displays a top message if required
+ */
+require_once 'libraries/common.inc.php';
+require_once 'libraries/user_preferences.lib.php';
+require_once 'libraries/config/config_functions.lib.php';
+require_once 'libraries/config/messages.inc.php';
+require_once 'libraries/config/ConfigFile.class.php';
+require_once 'libraries/config/Form.class.php';
+require_once 'libraries/config/FormDisplay.class.php';
+require 'libraries/config/user_preferences.forms.php';
+
+$cf = new ConfigFile($GLOBALS['PMA_Config']->base_settings);
+PMA_userprefsPageInit($cf);
+
+$error = '';
+if (isset($_POST['submit_export'])
+ && filter_input(INPUT_POST, 'export_type') == 'text_file'
+) {
+ // export to JSON file
+ PMA_Response::getInstance()->disable();
+ $filename = 'phpMyAdmin-config-' . urlencode(PMA_getenv('HTTP_HOST')) . '.json';
+ PMA_downloadHeader($filename, 'application/json');
+ $settings = PMA_loadUserprefs();
+ echo json_encode($settings['config_data']);
+ exit;
+} else if (isset($_POST['submit_get_json'])) {
+ $settings = PMA_loadUserprefs();
+ $response = PMA_Response::getInstance();
+ $response->addJSON('prefs', json_encode($settings['config_data']));
+ $response->addJSON('mtime', $settings['mtime']);
+ exit;
+} else if (isset($_POST['submit_import'])) {
+ // load from JSON file
+ $json = '';
+ if (filter_input(INPUT_POST, 'import_type') == 'text_file'
+ && isset($_FILES['import_file'])
+ && $_FILES['import_file']['error'] == UPLOAD_ERR_OK
+ && is_uploaded_file($_FILES['import_file']['tmp_name'])
+ ) {
+ // read JSON from uploaded file
+ $open_basedir = @ini_get('open_basedir');
+ $file_to_unlink = '';
+ $import_file = $_FILES['import_file']['tmp_name'];
+
+ // If we are on a server with open_basedir, we must move the file
+ // before opening it. The doc explains how to create the "./tmp"
+ // directory
+ if (!empty($open_basedir)) {
+ $tmp_subdir = (PMA_IS_WINDOWS ? '.\\tmp\\' : 'tmp/');
+ if (is_writable($tmp_subdir)) {
+ $import_file_new = tempnam($tmp_subdir, 'prefs');
+ if (move_uploaded_file($import_file, $import_file_new)) {
+ $import_file = $import_file_new;
+ $file_to_unlink = $import_file_new;
+ }
+ }
+ }
+ $json = file_get_contents($import_file);
+ if ($file_to_unlink) {
+ unlink($file_to_unlink);
+ }
+ } else {
+ // read from POST value (json)
+ $json = filter_input(INPUT_POST, 'json');
+ }
+
+ // hide header message
+ $_SESSION['userprefs_autoload'] = true;
+
+ $config = json_decode($json, true);
+ $return_url = filter_input(INPUT_POST, 'return_url');
+ if (! is_array($config)) {
+ $error = __('Could not import configuration');
+ } else {
+ // sanitize input values: treat them as though
+ // they came from HTTP POST request
+ $form_display = new FormDisplay($cf);
+ foreach ($forms as $formset_id => $formset) {
+ foreach ($formset as $form_name => $form) {
+ $form_display->registerForm($formset_id . ': ' . $form_name, $form);
+ }
+ }
+ $new_config = $cf->getFlatDefaultConfig();
+ if (!empty($_POST['import_merge'])) {
+ $new_config = array_merge($new_config, $cf->getConfigArray());
+ }
+ $new_config = array_merge($new_config, $config);
+ $_POST_bak = $_POST;
+ foreach ($new_config as $k => $v) {
+ $_POST[str_replace('/', '-', $k)] = $v;
+ }
+ $cf->resetConfigData();
+ $all_ok = $form_display->process(true, false);
+ $all_ok = $all_ok && !$form_display->hasErrors();
+ $_POST = $_POST_bak;
+
+ if (!$all_ok && isset($_POST['fix_errors'])) {
+ $form_display->fixErrors();
+ $all_ok = true;
+ }
+ if (!$all_ok) {
+ // mimic original form and post json in a hidden field
+ include 'libraries/user_preferences.inc.php';
+ $msg = PMA_Message::error(
+ __('Configuration contains incorrect data for some fields.')
+ );
+ $msg->display();
+ echo '<div class="config-form">';
+ $form_display->displayErrors();
+ echo '</div>';
+ echo '<form action="prefs_manage.php" method="post">';
+ echo PMA_URL_getHiddenInputs() . "\n";
+ echo '<input type="hidden" name="json" value="'
+ . htmlspecialchars($json) . '" />';
+ echo '<input type="hidden" name="fix_errors" value="1" />';
+ if (! empty($_POST['import_merge'])) {
+ echo '<input type="hidden" name="import_merge" value="1" />';
+ }
+ if ($return_url) {
+ echo '<input type="hidden" name="return_url" value="'
+ . htmlspecialchars($return_url) . '" />';
+ }
+ echo '<p>';
+ echo __('Do you want to import remaining settings?');
+ echo '</p>';
+ echo '<input type="submit" name="submit_import" value="'
+ . __('Yes') . '" />';
+ echo '<input type="submit" name="submit_ignore" value="'
+ . __('No') . '" />';
+ echo '</form>';
+ exit;
+ }
+
+ // check for ThemeDefault and fontsize
+ $params = array();
+ if (isset($config['ThemeDefault'])
+ && $_SESSION['PMA_Theme_Manager']->theme->getId() != $config['ThemeDefault']
+ && $_SESSION['PMA_Theme_Manager']->checkTheme($config['ThemeDefault'])
+ ) {
+ $_SESSION['PMA_Theme_Manager']->setActiveTheme($config['ThemeDefault']);
+ $_SESSION['PMA_Theme_Manager']->setThemeCookie();
+ }
+ if (isset($config['fontsize'])
+ && $config['fontsize'] != $GLOBALS['PMA_Config']->get('fontsize')
+ ) {
+ $params['set_fontsize'] = $config['fontsize'];
+ }
+ if (isset($config['lang'])
+ && $config['lang'] != $GLOBALS['lang']
+ ) {
+ $params['lang'] = $config['lang'];
+ }
+ if (isset($config['collation_connection'])
+ && $config['collation_connection'] != $GLOBALS['collation_connection']
+ ) {
+ $params['collation_connection'] = $config['collation_connection'];
+ }
+
+ // save settings
+ $result = PMA_saveUserprefs($cf->getConfigArray());
+ if ($result === true) {
+ if ($return_url) {
+ $query = explode('&', parse_url($return_url, PHP_URL_QUERY));
+ $return_url = parse_url($return_url, PHP_URL_PATH);
+ foreach ($query as $q) {
+ $pos = strpos($q, '=');
+ $k = substr($q, 0, $pos);
+ if ($k == 'token') {
+ continue;
+ }
+ $params[$k] = substr($q, $pos+1);
+ }
+ } else {
+ $return_url = 'prefs_manage.php';
+ }
+ // reload config
+ $GLOBALS['PMA_Config']->loadUserPreferences();
+ PMA_userprefsRedirect($return_url, $params);
+ exit;
+ } else {
+ $error = $result;
+ }
+ }
+} else if (isset($_POST['submit_clear'])) {
+ $result = PMA_saveUserprefs(array());
+ if ($result === true) {
+ $params = array();
+ if ($GLOBALS['PMA_Config']->get('fontsize') != '82%') {
+ $GLOBALS['PMA_Config']->removeCookie('pma_fontsize');
+ }
+ $GLOBALS['PMA_Config']->removeCookie('pma_collaction_connection');
+ $GLOBALS['PMA_Config']->removeCookie('pma_lang');
+ PMA_userprefsRedirect('prefs_manage.php', $params);
+ exit;
+ } else {
+ $error = $result;
+ }
+ exit;
+}
+
+$response = PMA_Response::getInstance();
+$header = $response->getHeader();
+$scripts = $header->getScripts();
+$scripts->addFile('config.js');
+
+require 'libraries/user_preferences.inc.php';
+if ($error) {
+ if (!$error instanceof PMA_Message) {
+ $error = PMA_Message::error($error);
+ }
+ $error->display();
+}
+?>
+<script type="text/javascript">
+<?php
+PMA_printJsValue("PMA_messages['strSavedOn']", __('Saved on: @DATE@'));
+?>
+</script>
+<div id="maincontainer">
+ <div id="main_pane_left">
+ <div class="group">
+ <h2><?php echo __('Import') ?></h2>
+ <form class="group-cnt prefs-form disableAjax" name="prefs_import" action="prefs_manage.php" method="post" enctype="multipart/form-data">
+ <?php
+ echo PMA_Util::generateHiddenMaxFileSize($GLOBALS['max_upload_size']) . "\n";
+ echo PMA_URL_getHiddenInputs() . "\n";
+ ?>
+ <input type="hidden" name="json" value="" />
+ <input type="radio" id="import_text_file" name="import_type" value="text_file" checked="checked" />
+ <label for="import_text_file"><?php echo __('Import from file') ?></label>
+ <div id="opts_import_text_file" class="prefsmanage_opts">
+ <label for="input_import_file"><?php echo __('Browse your computer:'); ?></label>
+ <input type="file" name="import_file" id="input_import_file" />
+ </div>
+ <input type="radio" id="import_local_storage" name="import_type" value="local_storage" disabled="disabled" />
+ <label for="import_local_storage"><?php echo __('Import from browser\'s storage') ?></label>
+ <div id="opts_import_local_storage" class="prefsmanage_opts disabled">
+ <div class="localStorage-supported">
+ <?php echo __('Settings will be imported from your browser\'s local storage.') ?>
+ <br />
+ <div class="localStorage-exists">
+ <?php echo __('Saved on: @DATE@') ?>
+ </div>
+ <div class="localStorage-empty">
+ <?php PMA_Message::notice(__('You have no saved settings!'))->display() ?>
+ </div>
+ </div>
+ <div class="localStorage-unsupported">
+ <?php PMA_Message::notice(__('This feature is not supported by your web browser'))->display() ?>
+ </div>
+ </div>
+
+ <input type="checkbox" id="import_merge" name="import_merge" />
+ <label for="import_merge"><?php echo __('Merge with current configuration') ?></label>
+ <br /><br />
+ <input type="submit" name="submit_import" value="<?php echo __('Go'); ?>" />
+ </form>
+ </div>
+ <?php
+ if (file_exists('setup/index.php')) {
+ // show only if setup script is available, allows to disable this message
+ // by simply removing setup directory
+ ?>
+ <div class="group">
+ <h2><?php echo __('More settings') ?></h2>
+ <div class="group-cnt">
+ <?php
+ echo sprintf(__('You can set more settings by modifying config.inc.php, eg. by using %sSetup script%s.'), '<a href="setup/index.php" target="_blank">', '</a>');
+ echo PMA_Util::showDocu('setup', 'setup-script');
+ ?>
+ </div>
+ </div>
+ <?php
+ }
+ ?>
+ </div>
+ <div id="main_pane_right">
+ <div class="group">
+ <h2><?php echo __('Export') ?></h2>
+ <div class="click-hide-message group-cnt" style="display:none">
+ <?php
+ PMA_Message::rawSuccess(__('Configuration has been saved'))->display();
+ ?>
+ </div>
+ <form class="group-cnt prefs-form disableAjax" name="prefs_export" action="prefs_manage.php" method="post">
+ <?php echo PMA_URL_getHiddenInputs() . "\n" ?>
+ <div style="padding-bottom:0.5em">
+ <input type="radio" id="export_text_file" name="export_type" value="text_file" checked="checked" />
+ <label for="export_text_file"><?php echo __('Save as file') ?></label>
+ <br />
+ <input type="radio" id="export_local_storage" name="export_type" value="local_storage" disabled="disabled" />
+ <label for="export_local_storage"><?php echo __('Save to browser\'s storage') ?></label>
+ </div>
+ <div id="opts_export_local_storage" class="prefsmanage_opts disabled">
+ <span class="localStorage-supported">
+ <?php echo __('Settings will be saved in your browser\'s local storage.') ?>
+ <span class="localStorage-exists">
+ <br /><b><?php echo __('Existing settings will be overwritten!') ?></b>
+ </span>
+ </span>
+ <div class="localStorage-unsupported">
+ <?php PMA_Message::notice(__('This feature is not supported by your web browser'))->display() ?>
+ </div>
+ </div>
+ <br />
+ <input type="submit" name="submit_export" value="<?php echo __('Go'); ?>" />
+ </form>
+ </div>
+ <div class="group">
+<?php
+echo '<h2>' . __('Reset') . '</h2>'
+ . '<form class="group-cnt prefs-form disableAjax" name="prefs_reset"'
+ . ' action="prefs_manage.php" method="post">'
+ . PMA_URL_getHiddenInputs()
+ . __('You can reset all your settings and restore them to default values.')
+ . '<br /><br />'
+ . '<input type="submit" name="submit_clear" value="'
+ . __('Reset') . '" />'
+ . '</form>';
+?>
+ </div>
+ </div>
+ <br class="clearfloat" />
+</div>
diff --git a/print.css b/print.css
new file mode 100644
index 0000000000..1aa2f98279
--- /dev/null
+++ b/print.css
@@ -0,0 +1,92 @@
+.nowrap {
+ white-space: nowrap;
+}
+
+.hide {
+ display: none;
+}
+
+body, table, th, td {
+ color: #000;
+ background-color: #fff;
+}
+
+img {
+ border: 0;
+}
+
+table, th, td {
+ border: .1em solid #000;
+}
+
+table {
+ border-collapse: collapse;
+ border-spacing: 0;
+}
+
+th, td {
+ padding: 0.2em;
+}
+
+th {
+ font-weight: bold;
+ background-color: #e5e5e5;
+}
+
+th.vtop, td.vtop {
+ vertical-align: top;
+}
+
+th.vbottom, td.vbottom {
+ vertical-align: bottom;
+}
+
+@media print {
+ .print_ignore {
+ display: none;
+ }
+
+ .nowrap {
+ white-space: nowrap;
+ }
+
+ .hide {
+ display: none;
+ }
+
+ body, table, th, td {
+ color: #000;
+ background-color: #fff;
+ }
+
+ img {
+ border: 0;
+ }
+
+ table, th, td {
+ border: .1em solid #000;
+ }
+
+ table {
+ border-collapse: collapse;
+ border-spacing: 0;
+ }
+
+ th, td {
+ padding: 0.2em;
+ }
+
+ th {
+ font-weight: bold;
+ background-color: #e5e5e5;
+ }
+
+ th.vtop, td.vtop {
+ vertical-align: top;
+ }
+
+ th.vbottom, td.vbottom {
+ vertical-align: bottom;
+ }
+}
+
diff --git a/querywindow.php b/querywindow.php
new file mode 100644
index 0000000000..5090e3d14d
--- /dev/null
+++ b/querywindow.php
@@ -0,0 +1,207 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * this file is register_globals safe
+ *
+ * @todo move JavaScript out of here into .js files
+ * @package PhpMyAdmin
+ */
+
+/**
+ *
+ */
+require_once 'libraries/common.inc.php';
+
+$is_superuser = $GLOBALS['dbi']->isSuperuser();
+
+/**
+ * Gets a core script and starts output buffering work
+ */
+require_once 'libraries/sql_query_form.lib.php';
+
+/**
+ * load relation params
+ */
+$cfgRelation = PMA_getRelationsParam();
+
+/**
+ * load bookmark support
+ */
+require_once 'libraries/bookmark.lib.php';
+
+$querydisplay_tabs = array(
+ 'sql',
+ 'files',
+ 'history',
+ 'full',
+);
+
+if (isset($_REQUEST['querydisplay_tab'])
+ && in_array($_REQUEST['querydisplay_tab'], $querydisplay_tabs)
+) {
+ $querydisplay_tab = $_REQUEST['querydisplay_tab'];
+} else {
+ $querydisplay_tab = $GLOBALS['cfg']['QueryWindowDefTab'];
+}
+
+/**
+ * $_REQUEST['no_js'] is set if open new window by JavaScript failed
+ * so this page is loaded in main frame
+ */
+$no_js = PMA_ifSetOr($_REQUEST['no_js'], false);
+
+if ($no_js) {
+ $querydisplay_tab = 'full';
+ $tabs = false;
+} else {
+ $tabs = array();
+ $tabs['sql']['icon'] = 'b_sql.png';
+ $tabs['sql']['text'] = __('SQL');
+ $tabs['sql']['fragment'] = '#';
+ $tabs['sql']['attr'] = 'onclick="PMA_querywindowCommit(\'sql\');return false;"';
+ $tabs['sql']['active'] = (bool) ($querydisplay_tab == 'sql');
+ $tabs['import']['icon'] = 'b_import.png';
+ $tabs['import']['text'] = __('Import files');
+ $tabs['import']['fragment'] = '#';
+ $tabs['import']['attr']
+ = 'onclick="PMA_querywindowCommit(\'files\');return false;"';
+ $tabs['import']['active'] = (bool) ($querydisplay_tab == 'files');
+ $tabs['history']['icon'] = 'b_bookmark.png';
+ $tabs['history']['text'] = __('SQL history');
+ $tabs['history']['fragment'] = '#';
+ $tabs['history']['attr']
+ = 'onclick="PMA_querywindowCommit(\'history\');return false;"';
+ $tabs['history']['active'] = (bool) ($querydisplay_tab == 'history');
+
+ if ($GLOBALS['cfg']['QueryWindowDefTab'] == 'full') {
+ $tabs['all']['text'] = __('All');
+ $tabs['all']['fragment'] = '#';
+ $tabs['all']['attr']
+ = 'onclick="PMA_querywindowCommit(\'full\');return false;"';
+ $tabs['all']['active'] = (bool) ($querydisplay_tab == 'full');
+ }
+}
+
+$titles['Change'] = PMA_Util::getIcon('b_edit.png', __('Change'));
+$url_query = PMA_URL_getCommon($db, $table);
+
+if (! empty($sql_query)) {
+ $show_query = 1;
+}
+
+if ($no_js) {
+ // ... we redirect to appropriate query sql page
+ // works only full if $db and $table is also stored/grabbed from $_COOKIE
+ if (strlen($table)) {
+ include 'tbl_sql.php';
+ } elseif (strlen($db)) {
+ include 'db_sql.php';
+ } else {
+ include 'server_sql.php';
+ }
+ exit;
+}
+
+/**
+ * Defines the query to be displayed in the query textarea
+ */
+if (! empty($show_query)) {
+ $query_to_display = $sql_query;
+} else if (! empty($_REQUEST['sql_query'])) {
+ $query_to_display = htmlspecialchars($_REQUEST['sql_query']);
+ $show_query = 1;
+} else {
+ $query_to_display = '';
+}
+$sql_query = '';
+
+/**
+ * prepare JavaScript functionality
+ */
+$response = PMA_Response::getInstance();
+$response->getFooter()->setMinimal();
+$header = $response->getHeader();
+$header->disableMenu();
+$header->setBodyId('bodyquerywindow');
+$scripts = $header->getScripts();
+$scripts->addFile('common.js');
+$scripts->addFile('querywindow.js');
+
+if (PMA_isValid($_REQUEST['auto_commit'], 'identical', 'true')) {
+ $scripts->addEvent('load', 'PMA_queryAutoCommit');
+}
+// always set focus to the textarea
+if ($querydisplay_tab == 'sql' || $querydisplay_tab == 'full') {
+ $scripts->addEvent('load', 'PMA_querywindowSetFocus');
+}
+
+echo '<div id="querywindowcontainer">';
+
+if ($tabs) {
+ echo PMA_Util::getHtmlTabs($tabs, array(), 'topmenu', true);
+ unset($tabs);
+}
+
+echo PMA_getHtmlForSqlQueryForm($query_to_display, $querydisplay_tab);
+
+// Hidden forms and query frame interaction stuff
+
+$_sql_history = PMA_getHistory($GLOBALS['cfg']['Server']['user']);
+if (! empty($_sql_history)
+ && ($querydisplay_tab == 'history' || $querydisplay_tab == 'full')
+) {
+ $tab = $querydisplay_tab != 'full' ? 'sql' : 'full';
+ echo __('SQL history:') . '<br />'
+ . '<ul>';
+ foreach ($_sql_history as $query) {
+ echo '<li>' . "\n";
+
+ // edit link
+ $url_params = array(
+ 'querydisplay_tab' => $tab,
+ 'sql_query' => $query['sqlquery'],
+ 'db' => $query['db'],
+ 'table' => $query['table'],
+ );
+ echo '<a href="querywindow.php' . PMA_URL_getCommon($url_params)
+ . '">' . $titles['Change'] . '</a>';
+
+ // execute link
+ $url_params['auto_commit'] = 'true';
+ echo '<a href="import.php' . PMA_URL_getCommon($url_params) . '"'
+ . ' target="frame_content">';
+
+ if (! empty($query['db'])) {
+ echo '[';
+ echo htmlspecialchars(PMA_Util::backquote($query['db']));
+ if (! empty($query['table'])) {
+ echo '.' . htmlspecialchars(PMA_Util::backquote($query['table']));
+ }
+ echo '] ';
+ }
+ if (strlen($query['sqlquery']) > 120) {
+ echo '<span title="' . htmlspecialchars($query['sqlquery']) . '">';
+ echo htmlspecialchars(substr($query['sqlquery'], 0, 50)) . ' [...] ';
+ echo htmlspecialchars(substr($query['sqlquery'], -50));
+ echo '</span>';
+ } else {
+ echo htmlspecialchars($query['sqlquery']);
+ }
+ echo '</a>' . "\n";
+ echo '</li>' . "\n";
+ }
+ unset($tab, $_sql_history, $query);
+ echo '</ul>' . "\n";
+}
+
+echo '<form action="querywindow.php" method="post" name="hiddenqueryform"';
+echo ' id="hiddenqueryform">';
+echo PMA_URL_getHiddenInputs('', '');
+echo '<input type="hidden" name="db" value="' . htmlspecialchars($db) . '" />';
+echo '<input type="hidden" name="table" value="'
+ . htmlspecialchars($table) . '" />';
+echo '<input type="hidden" name="sql_query" value="" />';
+echo '<input type="hidden" name="querydisplay_tab" value="'
+ . $querydisplay_tab . '" />';
+echo '</form>';
+echo '</div>';
diff --git a/robots.txt b/robots.txt
new file mode 100644
index 0000000000..1f53798bb4
--- /dev/null
+++ b/robots.txt
@@ -0,0 +1,2 @@
+User-agent: *
+Disallow: /
diff --git a/schema_edit.php b/schema_edit.php
new file mode 100644
index 0000000000..210756a1c1
--- /dev/null
+++ b/schema_edit.php
@@ -0,0 +1,129 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * PDF schema editor
+ *
+ * @package PhpMyAdmin
+ */
+
+/**
+ * Gets some core libraries
+ */
+
+require_once 'libraries/common.inc.php';
+require_once 'libraries/db_common.inc.php';
+require 'libraries/StorageEngine.class.php';
+
+$active_page = 'db_operations.php';
+require_once 'libraries/db_common.inc.php';
+$url_query .= '&amp;goto=schema_edit.php';
+require_once 'libraries/db_info.inc.php';
+
+/**
+ * get all variables needed for exporting relational schema
+ * in $cfgRelation
+ */
+$cfgRelation = PMA_getRelationsParam();
+
+/**
+ * Now in ./libraries/relation.lib.php we check for all tables
+ * that we need, but if we don't find them we are quiet about it
+ * so people can't work without relational variables.
+ * This page is absolutely useless if you didn't set up your tables
+ * correctly, so it is a good place to see which tables we can and
+ * complain ;-)
+ */
+if (! $cfgRelation['relwork']) {
+ echo sprintf(__('<b>%s</b> table not found or not set in %s'), 'relation', 'config.inc.php') . '<br />' . "\n"
+ . PMA_Util::showDocu('config', 'cfg_Servers_relation') . "\n";
+ exit;
+}
+
+if (! $cfgRelation['displaywork']) {
+ echo sprintf(__('<b>%s</b> table not found or not set in %s'), 'table_info', 'config.inc.php') . '<br />' . "\n"
+ . PMA_Util::showDocu('config', 'cfg_Servers_table_info') . "\n";
+ exit;
+}
+
+if (! isset($cfgRelation['table_coords'])) {
+ echo sprintf(__('<b>%s</b> table not found or not set in %s'), 'table_coords', 'config.inc.php') . '<br />' . "\n"
+ . PMA_Util::showDocu('config', 'cfg_Servers_table_coords') . "\n";
+ exit;
+}
+if (! isset($cfgRelation['pdf_pages'])) {
+ echo sprintf(__('<b>%s</b> table not found or not set in %s'), 'pdf_page', 'config.inc.php') . '<br />' . "\n"
+ . PMA_Util::showDocu('config', 'cfg_Servers_pdf_pages') . "\n";
+ exit;
+}
+
+if ($cfgRelation['pdfwork']) {
+
+ /**
+ * User object created for presenting the HTML options
+ * so, user can interact with it and perform export of relations schema
+ */
+
+ include_once 'libraries/schema/User_Schema.class.php';
+ $user_schema = new PMA_User_Schema();
+
+ /**
+ * This function will process the user defined pages
+ * and tables which will be exported as Relational schema
+ * you can set the table positions on the paper via scratchboard
+ * for table positions, put the x,y co-ordinates
+ *
+ * @param string $do It tells what the Schema is supposed to do
+ * create and select a page, generate schema etc
+ */
+ if (isset($_REQUEST['do'])) {
+ $user_schema->setAction($_REQUEST['do']);
+ $user_schema->processUserChoice();
+ }
+
+ /**
+ * Show some possibility to select a page for the export of relation schema
+ * Lists all pages created before and can select and edit from them
+ */
+
+ $user_schema->selectPage();
+
+ /**
+ * Create a new page where relations will be drawn
+ */
+
+ $user_schema->showCreatePageDialog($db);
+
+ /**
+ * After selection of page or creating a page
+ * It will show you the list of tables
+ * A dashboard will also be shown where you can position the tables
+ */
+
+ $user_schema->showTableDashBoard();
+
+ if (isset($_REQUEST['do'])
+ && ($_REQUEST['do'] == 'edcoord'
+ || ($_REQUEST['do']== 'selectpage' && isset($user_schema->chosenPage)
+ && $user_schema->chosenPage != 0)
+ || ($_REQUEST['do'] == 'createpage' && isset($user_schema->chosenPage)
+ && $user_schema->chosenPage != 0))
+ ) {
+
+ /**
+ * show Export schema generation options
+ */
+ $user_schema->displaySchemaGenerationOptions();
+
+ if ((isset($showwysiwyg) && $showwysiwyg == '1')) {
+ ?>
+ <script type="text/javascript">
+ //<![CDATA[
+ ToggleDragDrop('pdflayout');
+ //]]>
+ </script>
+ <?php
+ }
+ } // end if
+} // end if ($cfgRelation['pdfwork'])
+
+?>
diff --git a/schema_export.php b/schema_export.php
new file mode 100644
index 0000000000..e150db190d
--- /dev/null
+++ b/schema_export.php
@@ -0,0 +1,74 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Schema export handler
+ *
+ * @package PhpMyAdmin
+ */
+
+/**
+ * Gets some core libraries
+ */
+require_once 'libraries/common.inc.php';
+require 'libraries/StorageEngine.class.php';
+
+/**
+ * Validate vulnerable POST parameters
+ */
+if (! PMA_isValid($_POST['pdf_page_number'], 'numeric')) {
+ die('Attack stopped');
+}
+
+/**
+ * get all variables needed for exporting relational schema
+ * in $cfgRelation
+ */
+$cfgRelation = PMA_getRelationsParam();
+
+require_once 'libraries/transformations.lib.php';
+require_once 'libraries/Index.class.php';
+require_once 'libraries/schema/User_Schema.class.php';
+
+/**
+ * get all the export options and verify
+ * call and include the appropriate Schema Class depending on $export_type
+ * default is PDF
+ */
+
+$post_params = array(
+ 'all_tables_same_width',
+ 'chpage',
+ 'db',
+ 'do',
+ 'export_type',
+ 'orientation',
+ 'paper',
+ 'names',
+ 'pdf_page_number',
+ 'show_color',
+ 'show_grid',
+ 'show_keys',
+ 'show_table_dimension',
+ 'with_doc'
+);
+foreach ($post_params as $one_post_param) {
+ if (isset($_POST[$one_post_param])) {
+ $GLOBALS[$one_post_param] = $_POST[$one_post_param];
+ }
+}
+
+$user_schema = new PMA_User_Schema();
+
+/**
+ * This function will process the user defined pages
+ * and tables which will be exported as Relational schema
+ * you can set the table positions on the paper via scratchboard
+ * for table positions, put the x,y co-ordinates
+ *
+ * @param string $do It tells what the Schema is supposed to do
+ * create and select a page, generate schema etc
+ */
+if (isset($_REQUEST['do'])) {
+ $user_schema->setAction($_REQUEST['do']);
+ $user_schema->processUserChoice();
+}
diff --git a/server_binlog.php b/server_binlog.php
new file mode 100644
index 0000000000..8f3b79902b
--- /dev/null
+++ b/server_binlog.php
@@ -0,0 +1,54 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * display the binary logs and the content of the selected
+ *
+ * @package PhpMyAdmin
+ */
+
+/**
+ * requirements
+ */
+require_once 'libraries/common.inc.php';
+
+/**
+ * Does the common work
+ */
+require_once 'libraries/server_common.inc.php';
+
+require_once 'libraries/server_bin_log.lib.php';
+
+/**
+ * array binary log files
+ */
+$binary_logs = PMA_DRIZZLE
+ ? null
+ : $GLOBALS['dbi']->fetchResult(
+ 'SHOW MASTER LOGS',
+ 'Log_name',
+ null,
+ null,
+ PMA_DatabaseInterface::QUERY_STORE
+ );
+
+if (! isset($_REQUEST['log'])
+ || ! array_key_exists($_REQUEST['log'], $binary_logs)
+) {
+ $_REQUEST['log'] = '';
+} else {
+ $url_params['log'] = $_REQUEST['log'];
+}
+
+if (!empty($_REQUEST['dontlimitchars'])) {
+ $url_params['dontlimitchars'] = 1;
+}
+
+$response = PMA_Response::getInstance();
+
+$response->addHTML(PMA_getHtmlForSubPageHeader('binlog'));
+$response->addHTML(PMA_getLogSelector($binary_logs, $url_params));
+$response->addHTML(PMA_getLogInfo($binary_logs, $url_params));
+
+exit;
+
+?>
diff --git a/server_collations.php b/server_collations.php
new file mode 100644
index 0000000000..b1b9c71a09
--- /dev/null
+++ b/server_collations.php
@@ -0,0 +1,39 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Server collations page
+ *
+ * @package PhpMyAdmin
+ */
+
+/**
+ * requirements
+ */
+require_once 'libraries/common.inc.php';
+
+/**
+ * Does the common work
+ */
+require_once 'libraries/server_common.inc.php';
+
+require_once 'libraries/server_collations.lib.php';
+
+/**
+ * Includes the required charset library
+ */
+require_once 'libraries/mysql_charsets.inc.php';
+
+$response = PMA_Response::getInstance();
+
+$response->addHTML(PMA_getHtmlForSubPageHeader('collations'));
+$response->addHTML(
+ PMA_getHtmlForCharsets(
+ $mysql_charsets,
+ $mysql_collations,
+ $mysql_charsets_descriptions,
+ $mysql_default_collations,
+ $mysql_collations_available
+ )
+);
+
+?>
diff --git a/server_databases.php b/server_databases.php
new file mode 100644
index 0000000000..2ee0c2b580
--- /dev/null
+++ b/server_databases.php
@@ -0,0 +1,118 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Server databases
+ *
+ * @package PhpMyAdmin
+ */
+
+/**
+ * Does the common work
+ */
+require_once 'libraries/common.inc.php';
+require_once 'libraries/server_common.inc.php';
+require_once 'libraries/server_databases.lib.php';
+
+$response = PMA_Response::getInstance();
+$header = $response->getHeader();
+$scripts = $header->getScripts();
+$scripts->addFile('server_databases.js');
+
+if (! PMA_DRIZZLE) {
+ include_once 'libraries/replication.inc.php';
+} else {
+ $replication_types = array();
+ $replication_info = null;
+}
+require 'libraries/build_html_for_db.lib.php';
+
+/**
+ * Sets globals from $_POST
+ */
+$post_params = array(
+ 'mult_btn',
+ 'query_type',
+ 'selected'
+);
+foreach ($post_params as $one_post_param) {
+ if (isset($_POST[$one_post_param])) {
+ $GLOBALS[$one_post_param] = $_POST[$one_post_param];
+ }
+}
+
+list($sort_by, $sort_order) = PMA_getListForSortDatabase();
+
+$dbstats = empty($_REQUEST['dbstats']) ? 0 : 1;
+$pos = empty($_REQUEST['pos']) ? 0 : (int) $_REQUEST['pos'];
+
+
+/**
+ * Drops multiple databases
+ */
+// workaround for IE behavior (it returns some coordinates based on where
+// the mouse was on the Drop image):
+if (isset($_REQUEST['drop_selected_dbs_x'])) {
+ $_REQUEST['drop_selected_dbs'] = true;
+}
+
+if ((isset($_REQUEST['drop_selected_dbs']) || isset($_REQUEST['query_type']))
+ && ($is_superuser || $cfg['AllowUserDropDatabase'])
+) {
+ PMA_dropMultiDatabases();
+}
+
+/**
+ * Displays the sub-page heading
+ */
+$header_type = $dbstats ? "database_statistics" : "databases";
+$response->addHTML(PMA_getHtmlForSubPageHeader($header_type));
+
+/**
+ * Displays For Create database.
+ */
+$html = '';
+if ($cfg['ShowCreateDb']) {
+ $html .= '<ul><li id="li_create_database" class="no_bullets">' . "\n";
+ include 'libraries/display_create_database.lib.php';
+ $html .= ' </li>' . "\n";
+ $html .= '</ul>' . "\n";
+}
+
+/**
+ * Gets the databases list
+ */
+if ($server > 0) {
+ $databases = $GLOBALS['dbi']->getDatabasesFull(
+ null, $dbstats, null, $sort_by, $sort_order, $pos, true
+ );
+ $databases_count = count($GLOBALS['pma']->databases);
+} else {
+ $databases_count = 0;
+}
+
+
+/**
+ * Displays the page
+ */
+if ($databases_count > 0) {
+ $html .= PMA_getHtmlForDatabase(
+ $databases,
+ $databases_count,
+ $pos,
+ $dbstats,
+ $sort_by,
+ $sort_order,
+ $is_superuser,
+ $cfg,
+ $replication_types,
+ $replication_info,
+ $url_query
+ );
+} else {
+ $html .= __('No databases');
+}
+unset($databases_count);
+
+$response->addHTML($html);
+
+?>
diff --git a/server_engines.php b/server_engines.php
new file mode 100644
index 0000000000..12320498a5
--- /dev/null
+++ b/server_engines.php
@@ -0,0 +1,34 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * display list of server engines and additional information about them
+ *
+ * @package PhpMyAdmin
+ */
+
+/**
+ * requirements
+ */
+require_once 'libraries/common.inc.php';
+
+/**
+ * Does the common work
+ */
+require 'libraries/server_common.inc.php';
+require 'libraries/StorageEngine.class.php';
+require 'libraries/server_engines.lib.php';
+
+/**
+ * Displays the sub-page heading
+ */
+$response = PMA_Response::getInstance();
+$response->addHTML(PMA_getHtmlForSubPageHeader('engines'));
+
+/**
+ * start output
+ */
+$response->addHTML(PMA_getHtmlForServerEngines());
+
+exit;
+
+?>
diff --git a/server_export.php b/server_export.php
new file mode 100644
index 0000000000..c0bffdb3e1
--- /dev/null
+++ b/server_export.php
@@ -0,0 +1,29 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * object the server export page
+ *
+ * @package PhpMyAdmin
+ */
+
+/**
+ * Does the common work
+ */
+require_once 'libraries/common.inc.php';
+require_once 'libraries/server_common.inc.php';
+require_once 'libraries/display_export.lib.php';
+
+$response = PMA_Response::getInstance();
+$header = $response->getHeader();
+$scripts = $header->getScripts();
+$scripts->addFile('export.js');
+
+$export_page_title = __('View dump (schema) of databases') . "\n";
+
+$select_item = isset($tmp_select)? $tmp_select : '';
+$multi_values = PMA_getHtmlForExportSelectOptions($select_item);
+
+$export_type = 'server';
+require_once 'libraries/display_export.inc.php';
+
+?>
diff --git a/server_import.php b/server_import.php
new file mode 100644
index 0000000000..83d27cf7c1
--- /dev/null
+++ b/server_import.php
@@ -0,0 +1,27 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Server import page
+ *
+ * @package PhpMyAdmin
+ */
+
+/**
+ *
+ */
+require_once 'libraries/common.inc.php';
+
+$response = PMA_Response::getInstance();
+$header = $response->getHeader();
+$scripts = $header->getScripts();
+$scripts->addFile('import.js');
+
+/**
+ * Does the common work
+ */
+require 'libraries/server_common.inc.php';
+
+$import_type = 'server';
+require 'libraries/display_import.inc.php';
+
+?>
diff --git a/server_plugins.php b/server_plugins.php
new file mode 100644
index 0000000000..089bfd1d7f
--- /dev/null
+++ b/server_plugins.php
@@ -0,0 +1,59 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * object the server plugin page
+ *
+ * @package PhpMyAdmin
+ */
+
+/**
+ * requirements
+ */
+require_once 'libraries/common.inc.php';
+
+/**
+ * JS includes
+ */
+$response = PMA_Response::getInstance();
+$header = $response->getHeader();
+$scripts = $header->getScripts();
+$scripts->addFile('jquery/jquery.tablesorter.js');
+$scripts->addFile('server_plugins.js');
+
+/**
+ * Does the common work
+ */
+require 'libraries/server_common.inc.php';
+require 'libraries/server_plugins.lib.php';
+
+/**
+ * Prepare plugin list
+ */
+$sql = "SELECT p.plugin_name, p.plugin_type, p.is_active, m.module_name,
+ m.module_library, m.module_version, m.module_author,
+ m.module_description, m.module_license
+ FROM data_dictionary.plugins p
+ JOIN data_dictionary.modules m USING (module_name)
+ ORDER BY m.module_name, p.plugin_type, p.plugin_name";
+$res = $GLOBALS['dbi']->query($sql);
+$plugins = array();
+$modules = array();
+while ($row = $GLOBALS['dbi']->fetchAssoc($res)) {
+ $plugins[$row['plugin_type']][] = $row;
+ $modules[$row['module_name']]['info'] = $row;
+ $modules[$row['module_name']]['plugins'][$row['plugin_type']][] = $row;
+}
+$GLOBALS['dbi']->freeResult($res);
+
+// sort plugin list (modules are already sorted)
+ksort($plugins);
+
+/**
+ * Displays the page
+ */
+$response->addHTML(PMA_getHtmlForSubPageHeader('plugins'));
+$response->addHTML(PMA_getPluginAndModuleInfo($plugins, $modules));
+
+exit;
+
+?>
diff --git a/server_privileges.php b/server_privileges.php
new file mode 100644
index 0000000000..4500f439b2
--- /dev/null
+++ b/server_privileges.php
@@ -0,0 +1,380 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Server privileges and users manipulations
+ *
+ * @package PhpMyAdmin
+ */
+
+/**
+ * include common file
+ */
+require_once 'libraries/common.inc.php';
+
+/**
+ * functions implementation for this script
+ */
+require_once 'libraries/display_change_password.lib.php';
+require_once 'libraries/server_privileges.lib.php';
+
+$cfgRelation = PMA_getRelationsParam();
+
+/**
+ * Does the common work
+ */
+$response = PMA_Response::getInstance();
+$header = $response->getHeader();
+$scripts = $header->getScripts();
+$scripts->addFile('server_privileges.js');
+
+if ((isset($_REQUEST['viewing_mode']) && $_REQUEST['viewing_mode'] == 'server')
+ && $GLOBALS['cfgRelation']['menuswork']
+) {
+ include_once 'libraries/server_users.lib.php';
+ $response->addHTML('<div>');
+ $response->addHTML(PMA_getHtmlForSubMenusOnUsersPage('server_privileges.php'));
+}
+
+/**
+ * Sets globals from $_POST patterns, for privileges and max_* vars
+ */
+
+$post_patterns = array(
+ '/_priv$/i',
+ '/^max_/i'
+);
+foreach (array_keys($_POST) as $post_key) {
+ foreach ($post_patterns as $one_post_pattern) {
+ if (preg_match($one_post_pattern, $post_key)) {
+ $GLOBALS[$post_key] = $_POST[$post_key];
+ }
+ }
+}
+
+require 'libraries/server_common.inc.php';
+
+/**
+ * Messages are built using the message name
+ */
+$strPrivDescAllPrivileges = __('Includes all privileges except GRANT.');
+$strPrivDescAlter = __('Allows altering the structure of existing tables.');
+$strPrivDescAlterRoutine = __('Allows altering and dropping stored routines.');
+$strPrivDescCreateDb = __('Allows creating new databases and tables.');
+$strPrivDescCreateRoutine = __('Allows creating stored routines.');
+$strPrivDescCreateTbl = __('Allows creating new tables.');
+$strPrivDescCreateTmpTable = __('Allows creating temporary tables.');
+$strPrivDescCreateUser = __('Allows creating, dropping and renaming user accounts.');
+$strPrivDescCreateView = __('Allows creating new views.');
+$strPrivDescDelete = __('Allows deleting data.');
+$strPrivDescDropDb = __('Allows dropping databases and tables.');
+$strPrivDescDropTbl = __('Allows dropping tables.');
+$strPrivDescEvent = __('Allows to set up events for the event scheduler');
+$strPrivDescExecute = __('Allows executing stored routines.');
+$strPrivDescFile = __('Allows importing data from and exporting data into files.');
+$strPrivDescGrant = __(
+ 'Allows adding users and privileges without reloading the privilege tables.'
+);
+$strPrivDescIndex = __('Allows creating and dropping indexes.');
+$strPrivDescInsert = __('Allows inserting and replacing data.');
+$strPrivDescLockTables = __('Allows locking tables for the current thread.');
+$strPrivDescMaxConnections = __(
+ 'Limits the number of new connections the user may open per hour.'
+);
+$strPrivDescMaxQuestions = __(
+ 'Limits the number of queries the user may send to the server per hour.'
+);
+$strPrivDescMaxUpdates = __(
+ 'Limits the number of commands that change any table or database '
+ . 'the user may execute per hour.'
+);
+$strPrivDescMaxUserConnections = __(
+ 'Limits the number of simultaneous connections the user may have.'
+);
+$strPrivDescProcess = __('Allows viewing processes of all users');
+$strPrivDescReferences = __('Has no effect in this MySQL version.');
+$strPrivDescReload = __(
+ 'Allows reloading server settings and flushing the server\'s caches.'
+);
+$strPrivDescReplClient = __(
+ 'Allows the user to ask where the slaves / masters are.'
+);
+$strPrivDescReplSlave = __('Needed for the replication slaves.');
+$strPrivDescSelect = __('Allows reading data.');
+$strPrivDescShowDb = __('Gives access to the complete list of databases.');
+$strPrivDescShowView = __('Allows performing SHOW CREATE VIEW queries.');
+$strPrivDescShutdown = __('Allows shutting down the server.');
+$strPrivDescSuper = __(
+ 'Allows connecting, even if maximum number of connections is reached; '
+ . 'required for most administrative operations like setting global variables '
+ . 'or killing threads of other users.'
+);
+$strPrivDescTrigger = __('Allows creating and dropping triggers');
+$strPrivDescUpdate = __('Allows changing data.');
+$strPrivDescUsage = __('No privileges.');
+
+$_add_user_error = false;
+/**
+ * Get DB information: username, hostname, dbname,
+ * tablename, db_and_table, dbname_is_wildcard
+ */
+list(
+ $username, $hostname, $dbname, $tablename,
+ $db_and_table, $dbname_is_wildcard
+) = PMA_getDataForDBInfo();
+
+/**
+ * Checks if the user is allowed to do what he tries to...
+ */
+if (! $is_superuser) {
+ $response->addHTML(PMA_getHtmlForSubPageHeader('privileges', '', false));
+ $response->addHTML(PMA_Message::error(__('No Privileges'))->getDisplay());
+ exit;
+}
+
+/**
+ * Changes / copies a user, part I
+ */
+list($queries, $password) = PMA_getDataForChangeOrCopyUser();
+
+/**
+ * Adds a user
+ * (Changes / copies a user, part II)
+ */
+list($ret_message, $ret_queries, $queries_for_display, $sql_query, $_add_user_error)
+ = PMA_addUser(
+ isset($dbname)? $dbname : null,
+ isset($username)? $username : null,
+ isset($hostname)? $hostname : null,
+ isset($password)? $password : null,
+ $cfgRelation['menuswork']
+ );
+//update the old variables
+if (isset($ret_queries)) {
+ $queries = $ret_queries;
+ unset($ret_queries);
+}
+if (isset($ret_message)) {
+ $message = $ret_message;
+ unset($ret_message);
+}
+
+/**
+ * Changes / copies a user, part III
+ */
+if (isset($_REQUEST['change_copy'])) {
+ $queries = PMA_getDbSpecificPrivsQueriesForChangeOrCopyUser(
+ $queries, $username, $hostname
+ );
+}
+
+/**
+ * Updates privileges
+ */
+if (! empty($_POST['update_privs'])) {
+ list($sql_query, $message) = PMA_updatePrivileges(
+ (isset($username) ? $username : ''),
+ (isset($hostname) ? $hostname : ''),
+ (isset($tablename) ? $tablename : ''),
+ (isset($dbname) ? $dbname : '')
+ );
+}
+
+/**
+ * Assign users to user groups
+ */
+if (! empty($_REQUEST['changeUserGroup']) && $cfgRelation['menuswork']) {
+ PMA_setUserGroup($username, $_REQUEST['userGroup']);
+ $message = PMA_Message::success();
+}
+
+/**
+ * Revokes Privileges
+ */
+if (isset($_REQUEST['revokeall'])) {
+ list ($message, $sql_query) = PMA_getMessageAndSqlQueryForPrivilegesRevoke(
+ $db_and_table,
+ (isset($dbname) ? $dbname : ''),
+ (isset($tablename) ? $tablename : ''),
+ $username, $hostname
+ );
+}
+
+/**
+ * Updates the password
+ */
+if (isset($_REQUEST['change_pw'])) {
+ $message = PMA_updatePassword(
+ $err_url, $username, $hostname
+ );
+}
+
+/**
+ * Deletes users
+ * (Changes / copies a user, part IV)
+ */
+if (isset($_REQUEST['delete'])
+ || (isset($_REQUEST['change_copy']) && $_REQUEST['mode'] < 4)
+) {
+ $queries = PMA_getDataForDeleteUsers($queries);
+ if (empty($_REQUEST['change_copy'])) {
+ list($sql_query, $message) = PMA_deleteUser($queries);
+ }
+}
+
+/**
+ * Changes / copies a user, part V
+ */
+if (isset($_REQUEST['change_copy'])) {
+ $queries = PMA_getDataForQueries($queries, $queries_for_display);
+ $message = PMA_Message::success();
+ $sql_query = join("\n", $queries);
+}
+
+/**
+ * Reloads the privilege tables into memory
+ */
+$message_ret = PMA_updateMessageForReload();
+if (isset($message_ret)) {
+ $message = $message_ret;
+ unset($message_ret);
+}
+
+/**
+ * If we are in an Ajax request for Create User/Edit User/Revoke User/
+ * Flush Privileges, show $message and exit.
+ */
+if ($GLOBALS['is_ajax_request']
+ && empty($_REQUEST['ajax_page_request'])
+ && ! isset($_REQUEST['export'])
+ && (! isset($_REQUEST['submit_mult']) || $_REQUEST['submit_mult'] != 'export')
+ && (! isset($_REQUEST['adduser']) || $_add_user_error)
+ && (! isset($_REQUEST['initial']) || empty($_REQUEST['initial']))
+ && ! isset($_REQUEST['showall'])
+ && ! isset($_REQUEST['edit_user_dialog'])
+ && ! isset($_REQUEST['edit_user_group_dialog'])
+ && ! isset($_REQUEST['db_specific'])
+) {
+ $extra_data = PMA_getExtraDataForAjaxBehavior(
+ (isset($password) ? $password : ''),
+ (isset($sql_query) ? $sql_query : ''),
+ (isset($hostname) ? $hostname : ''),
+ (isset($username) ? $username : '')
+ );
+
+ if (! empty($message) && $message instanceof PMA_Message) {
+ $response = PMA_Response::getInstance();
+ $response->isSuccess($message->isSuccess());
+ $response->addJSON('message', $message);
+ $response->addJSON($extra_data);
+ exit;
+ }
+}
+
+/**
+ * Displays the links
+ */
+if (isset($_REQUEST['viewing_mode']) && $_REQUEST['viewing_mode'] == 'db') {
+ $_REQUEST['db'] = $_REQUEST['checkprivsdb'];
+
+ $url_query .= '&amp;goto=db_operations.php';
+
+ // Gets the database structure
+ $sub_part = '_structure';
+ ob_start();
+ include 'libraries/db_info.inc.php';
+ $content = ob_get_contents();
+ ob_end_clean();
+ $response->addHTML($content . "\n");
+} else {
+ if (! empty($GLOBALS['message'])) {
+ $response->addHTML(PMA_Util::getMessage($GLOBALS['message']));
+ unset($GLOBALS['message']);
+ }
+}
+
+/**
+ * Displays the page
+ */
+$response->addHTML(
+ PMA_getHtmlForUserGroupDialog(
+ isset($username)? $username : null,
+ $cfgRelation['menuswork']
+ )
+);
+
+// export user definition
+if (isset($_REQUEST['export'])
+ || (isset($_REQUEST['submit_mult']) && $_REQUEST['submit_mult'] == 'export')
+) {
+ list($title, $export) = PMA_getListForExportUserDefinition(
+ isset($username) ? $username : null,
+ isset($hostname) ? $hostname : null
+ );
+
+ unset($username, $hostname, $grants, $one_grant);
+
+ $response = PMA_Response::getInstance();
+ if ($GLOBALS['is_ajax_request']) {
+ $response->addJSON('message', $export);
+ $response->addJSON('title', $title);
+ exit;
+ } else {
+ $response->addHTML("<h2>$title</h2>$export");
+ }
+}
+
+if (isset($_REQUEST['adduser'])) {
+ // Add user
+ $response->addHTML(
+ PMA_getHtmlForAddUser((isset($dbname) ? $dbname : ''))
+ );
+} elseif (isset($_REQUEST['checkprivsdb'])) {
+ if (isset($_REQUEST['checkprivstable'])) {
+ // check the privileges for a particular table.
+ $response->addHTML(
+ PMA_getHtmlForSpecificTablePrivileges(
+ $_REQUEST['checkprivsdb'], $_REQUEST['checkprivstable']
+ )
+ );
+ } else {
+ // check the privileges for a particular database.
+ $response->addHTML(
+ PMA_getHtmlForSpecificDbPrivileges($_REQUEST['checkprivsdb'])
+ );
+ }
+} else {
+ if (! isset($username)) {
+ // No username is given --> display the overview
+ $response->addHTML(
+ PMA_getHtmlForDisplayUserOverviewPage($pmaThemeImage, $text_dir)
+ );
+ } else {
+ // A user was selected -> display the user's properties
+ // In an Ajax request, prevent cached values from showing
+ if ($GLOBALS['is_ajax_request'] == true) {
+ header('Cache-Control: no-cache');
+ }
+ $url_dbname = urlencode(
+ str_replace(
+ array('\_', '\%'),
+ array('_', '%'), $_REQUEST['dbname']
+ )
+ );
+ $response->addHTML(
+ PMA_getHtmlForDisplayUserProperties(
+ ((isset ($dbname_is_wildcard)) ? $dbname_is_wildcard : ''),
+ $url_dbname, $username, $hostname,
+ (isset($dbname) ? $dbname : ''),
+ (isset($tablename) ? $tablename : '')
+ )
+ );
+ }
+}
+
+if ((isset($_REQUEST['viewing_mode']) && $_REQUEST['viewing_mode'] == 'server')
+ && $GLOBALS['cfgRelation']['menuswork']
+) {
+ $response->addHTML('</div>');
+}
+
+?>
diff --git a/server_replication.php b/server_replication.php
new file mode 100644
index 0000000000..9f1f2ae6f7
--- /dev/null
+++ b/server_replication.php
@@ -0,0 +1,83 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Server replications
+ *
+ * @package PhpMyAdmin
+ */
+
+/**
+ * include files
+ */
+require_once 'libraries/common.inc.php';
+require_once 'libraries/server_common.inc.php';
+
+require_once 'libraries/replication.inc.php';
+require_once 'libraries/replication_gui.lib.php';
+
+/**
+ * Does the common work
+ */
+$response = PMA_Response::getInstance();
+$header = $response->getHeader();
+$scripts = $header->getScripts();
+$scripts->addFile('server_privileges.js');
+$scripts->addFile('replication.js');
+
+/**
+ * Checks if the user is allowed to do what he tries to...
+ */
+if (! $is_superuser) {
+ $html = PMA_getHtmlForSubPageHeader('replication');
+ $html .= PMA_Message::error(__('No Privileges'))->getDisplay();
+ $response->addHTML($html);
+ exit;
+}
+
+//change $GLOBALS['url_params'] with $_REQUEST['url_params']
+if (isset($_REQUEST['url_params'])) {
+ $GLOBALS['url_params'] = $_REQUEST['url_params'];
+}
+/**
+ * Handling control requests
+ */
+PMA_handleControlRequest();
+
+/**
+ * start output
+ */
+$response->addHTML('<div id="replication">');
+$response->addHTML(PMA_getHtmlForSubPageHeader('replication'));
+
+// Display error messages
+$response->addHTML(PMA_getHtmlForErrorMessage());
+
+if ($server_master_status) {
+ $response->addHTML(PMA_getHtmlForMasterReplication());
+} elseif (! isset($_REQUEST['mr_configure'])
+ && ! isset($_REQUEST['repl_clear_scr'])
+) {
+ $response->addHTML(PMA_getHtmlForNotServerReplication());
+}
+
+if (isset($_REQUEST['mr_configure'])) {
+ // Render the 'Master configuration' section
+ $response->addHTML(PMA_getHtmlForMasterConfiguration());
+ exit;
+}
+
+$response->addHTML('</div>');
+
+if (! isset($_REQUEST['repl_clear_scr'])) {
+ // Render the 'Slave configuration' section
+ $response->addHTML(
+ PMA_getHtmlForSlaveConfiguration(
+ $server_slave_status,
+ $server_slave_replication
+ )
+ );
+}
+if (isset($_REQUEST['sl_configure'])) {
+ $response->addHTML(PMA_getHtmlForReplicationChangeMaster("slave_changemaster"));
+}
+?>
diff --git a/server_sql.php b/server_sql.php
new file mode 100644
index 0000000000..1d505e4305
--- /dev/null
+++ b/server_sql.php
@@ -0,0 +1,31 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Server SQL executor
+ *
+ * @package PhpMyAdmin
+ */
+
+/**
+ *
+ */
+require_once 'libraries/common.inc.php';
+
+/**
+ * Does the common work
+ */
+$response = PMA_Response::getInstance();
+$header = $response->getHeader();
+$scripts = $header->getScripts();
+$scripts->addFile('makegrid.js');
+$scripts->addFile('sql.js');
+
+require_once 'libraries/server_common.inc.php';
+require_once 'libraries/sql_query_form.lib.php';
+
+/**
+ * Query box, bookmark, insert data from textfile
+ */
+$response->addHTML(PMA_getHtmlForSqlQueryForm());
+
+?>
diff --git a/server_status.php b/server_status.php
new file mode 100644
index 0000000000..dc7b19c1d9
--- /dev/null
+++ b/server_status.php
@@ -0,0 +1,54 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * object the server status page: processes, connections and traffic
+ *
+ * @package PhpMyAdmin
+ */
+
+require_once 'libraries/common.inc.php';
+require_once 'libraries/server_common.inc.php';
+require_once 'libraries/ServerStatusData.class.php';
+require_once 'libraries/server_status.lib.php';
+
+/**
+ * Replication library
+ */
+if (PMA_DRIZZLE) {
+ $server_master_status = false;
+ $server_slave_status = false;
+} else {
+ include_once 'libraries/replication.inc.php';
+ include_once 'libraries/replication_gui.lib.php';
+}
+
+$ServerStatusData = new PMA_ServerStatusData();
+
+/**
+ * Kills a selected process
+ */
+if (! empty($_REQUEST['kill'])) {
+ if ($GLOBALS['dbi']->tryQuery('KILL ' . $_REQUEST['kill'] . ';')) {
+ $message = PMA_Message::success(__('Thread %s was successfully killed.'));
+ } else {
+ $message = PMA_Message::error(
+ __(
+ 'phpMyAdmin was unable to kill thread %s.'
+ . ' It probably has already been closed.'
+ )
+ );
+ }
+ $message->addParam($_REQUEST['kill']);
+}
+
+/**
+ * start output
+ */
+$response = PMA_Response::getInstance();
+$response->addHTML('<div>');
+$response->addHTML($ServerStatusData->getMenuHtml());
+$response->addHTML(PMA_getHtmlForServerStatus($ServerStatusData));
+$response->addHTML('</div>');
+
+exit;
+?>
diff --git a/server_status_advisor.php b/server_status_advisor.php
new file mode 100644
index 0000000000..6f882033cc
--- /dev/null
+++ b/server_status_advisor.php
@@ -0,0 +1,39 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * displays the advisor feature
+ *
+ * @package PhpMyAdmin
+ */
+
+require_once 'libraries/common.inc.php';
+require_once 'libraries/Advisor.class.php';
+require_once 'libraries/ServerStatusData.class.php';
+require_once 'libraries/server_status_advisor.lib.php';
+
+if (PMA_DRIZZLE) {
+ $server_master_status = false;
+ $server_slave_status = false;
+} else {
+ include_once 'libraries/replication.inc.php';
+ include_once 'libraries/replication_gui.lib.php';
+}
+
+$ServerStatusData = new PMA_ServerStatusData();
+
+$response = PMA_Response::getInstance();
+$scripts = $response->getHeader()->getScripts();
+$scripts->addFile('server_status_advisor.js');
+
+/**
+ * Output
+ */
+$response->addHTML('<div>');
+$response->addHTML($ServerStatusData->getMenuHtml());
+$response->addHTML(PMA_getHtmlForAdvisor());
+$response->addHTML('</div>');
+exit;
+
+
+
+?>
diff --git a/server_status_monitor.php b/server_status_monitor.php
new file mode 100644
index 0000000000..5c58c94bb8
--- /dev/null
+++ b/server_status_monitor.php
@@ -0,0 +1,116 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Server status monitor feature
+ *
+ * @package PhpMyAdmin
+ */
+
+require_once 'libraries/common.inc.php';
+require_once 'libraries/server_common.inc.php';
+require_once 'libraries/ServerStatusData.class.php';
+require_once 'libraries/server_status_monitor.lib.php';
+if (PMA_DRIZZLE) {
+ $server_master_status = false;
+ $server_slave_status = false;
+} else {
+ include_once 'libraries/replication.inc.php';
+ include_once 'libraries/replication_gui.lib.php';
+}
+
+/**
+ * Ajax request
+ */
+if (isset($_REQUEST['ajax_request']) && $_REQUEST['ajax_request'] == true) {
+ // Send with correct charset
+ header('Content-Type: text/html; charset=UTF-8');
+
+ // real-time charting data
+ if (isset($_REQUEST['chart_data'])) {
+ switch($_REQUEST['type']) {
+ case 'chartgrid': // Data for the monitor
+ $ret = PMA_getJsonForChartingData();
+ PMA_Response::getInstance()->addJSON('message', $ret);
+ exit;
+ }
+ }
+
+ if (isset($_REQUEST['log_data'])) {
+ if (PMA_MYSQL_INT_VERSION < 50106) {
+ // Table logging is only available since 5.1.6
+ exit('""');
+ }
+
+ $start = intval($_REQUEST['time_start']);
+ $end = intval($_REQUEST['time_end']);
+
+ if ($_REQUEST['type'] == 'slow') {
+ $return = PMA_getJsonForLogDataTypeSlow($start, $end);
+ PMA_Response::getInstance()->addJSON('message', $return);
+ exit;
+ }
+
+ if ($_REQUEST['type'] == 'general') {
+ $return = PMA_getJsonForLogDataTypeGeneral($start, $end);
+ PMA_Response::getInstance()->addJSON('message', $return);
+ exit;
+ }
+ }
+
+ if (isset($_REQUEST['logging_vars'])) {
+ $loggingVars = PMA_getJsonForLoggingVars();
+ PMA_Response::getInstance()->addJSON('message', $loggingVars);
+ exit;
+ }
+
+ if (isset($_REQUEST['query_analyzer'])) {
+ $return = PMA_getJsonForQueryAnalyzer();
+ PMA_Response::getInstance()->addJSON('message', $return);
+ exit;
+ }
+}
+
+/**
+ * JS Includes
+ */
+$header = $response->getHeader();
+$scripts = $header->getScripts();
+$scripts->addFile('jquery/jquery.tablesorter.js');
+$scripts->addFile('jquery/jquery.json-2.4.js');
+$scripts->addFile('jquery/jquery.sortableTable.js');
+$scripts->addFile('jquery/jquery-ui-timepicker-addon.js');
+/* < IE 9 doesn't support canvas natively */
+if (PMA_USR_BROWSER_AGENT == 'IE' && PMA_USR_BROWSER_VER < 9) {
+ $scripts->addFile('jqplot/excanvas.js');
+}
+$scripts->addFile('canvg/canvg.js');
+// for charting
+$scripts->addFile('jqplot/jquery.jqplot.js');
+$scripts->addFile('jqplot/plugins/jqplot.pieRenderer.js');
+$scripts->addFile('jqplot/plugins/jqplot.canvasTextRenderer.js');
+$scripts->addFile('jqplot/plugins/jqplot.canvasAxisLabelRenderer.js');
+$scripts->addFile('jqplot/plugins/jqplot.dateAxisRenderer.js');
+$scripts->addFile('jqplot/plugins/jqplot.highlighter.js');
+$scripts->addFile('jqplot/plugins/jqplot.cursor.js');
+$scripts->addFile('jqplot/plugins/jqplot.byteFormatter.js');
+
+$scripts->addFile('server_status_monitor.js');
+$scripts->addFile('server_status_sorter.js');
+
+
+/**
+ * start output
+ */
+$ServerStatusData = new PMA_ServerStatusData();
+
+/**
+ * Output
+ */
+$response->addHTML('<div>');
+$response->addHTML($ServerStatusData->getMenuHtml());
+$response->addHTML(PMA_getHtmlForMonitor($ServerStatusData));
+$response->addHTML(PMA_getHtmlForClientSideDataAndLinks($ServerStatusData));
+$response->addHTML('</div>');
+exit;
+
+?>
diff --git a/server_status_queries.php b/server_status_queries.php
new file mode 100644
index 0000000000..b895843292
--- /dev/null
+++ b/server_status_queries.php
@@ -0,0 +1,53 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+
+/**
+ * Displays query statistics for the server
+ *
+ * @package PhpMyAdmin
+ */
+
+require_once 'libraries/common.inc.php';
+require_once 'libraries/server_common.inc.php';
+require_once 'libraries/ServerStatusData.class.php';
+require_once 'libraries/server_status_queries.lib.php';
+
+if (PMA_DRIZZLE) {
+ $server_master_status = false;
+ $server_slave_status = false;
+} else {
+ include_once 'libraries/replication.inc.php';
+ include_once 'libraries/replication_gui.lib.php';
+}
+
+$ServerStatusData = new PMA_ServerStatusData();
+
+$response = PMA_Response::getInstance();
+$header = $response->getHeader();
+$scripts = $header->getScripts();
+$scripts->addFile('server_status_queries.js');
+
+/* < IE 9 doesn't support canvas natively */
+if (PMA_USR_BROWSER_AGENT == 'IE' && PMA_USR_BROWSER_VER < 9) {
+ $scripts->addFile('jqplot/excanvas.js');
+}
+
+// for charting
+$scripts->addFile('jqplot/jquery.jqplot.js');
+$scripts->addFile('jqplot/plugins/jqplot.pieRenderer.js');
+$scripts->addFile('jqplot/plugins/jqplot.canvasTextRenderer.js');
+$scripts->addFile('jqplot/plugins/jqplot.canvasAxisLabelRenderer.js');
+$scripts->addFile('jqplot/plugins/jqplot.dateAxisRenderer.js');
+$scripts->addFile('jqplot/plugins/jqplot.highlighter.js');
+$scripts->addFile('jqplot/plugins/jqplot.cursor.js');
+$scripts->addFile('jquery/jquery.tablesorter.js');
+$scripts->addFile('server_status_sorter.js');
+
+// Add the html content to the response
+$response->addHTML('<div>');
+$response->addHTML($ServerStatusData->getMenuHtml());
+$response->addHTML(PMA_getHtmlForQueryStatistics($ServerStatusData));
+$response->addHTML('</div>');
+exit;
+
+?>
diff --git a/server_status_variables.php b/server_status_variables.php
new file mode 100644
index 0000000000..fe93501e1c
--- /dev/null
+++ b/server_status_variables.php
@@ -0,0 +1,56 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Displays a list of server status variables
+ *
+ * @package PhpMyAdmin
+ */
+
+require_once 'libraries/common.inc.php';
+require_once 'libraries/server_common.inc.php';
+require_once 'libraries/ServerStatusData.class.php';
+require_once 'libraries/server_status_variables.lib.php';
+
+if (PMA_DRIZZLE) {
+ $server_master_status = false;
+ $server_slave_status = false;
+} else {
+ include_once 'libraries/replication.inc.php';
+ include_once 'libraries/replication_gui.lib.php';
+}
+
+/**
+ * flush status variables if requested
+ */
+if (isset($_REQUEST['flush'])) {
+ $_flush_commands = array(
+ 'STATUS',
+ 'TABLES',
+ 'QUERY CACHE',
+ );
+
+ if (in_array($_REQUEST['flush'], $_flush_commands)) {
+ $GLOBALS['dbi']->query('FLUSH ' . $_REQUEST['flush'] . ';');
+ }
+ unset($_flush_commands);
+}
+
+$ServerStatusData = new PMA_ServerStatusData();
+
+$response = PMA_Response::getInstance();
+$header = $response->getHeader();
+$scripts = $header->getScripts();
+$scripts->addFile('server_status_variables.js');
+$scripts->addFile('jquery/jquery.tablesorter.js');
+$scripts->addFile('server_status_sorter.js');
+
+$response->addHTML('<div>');
+$response->addHTML($ServerStatusData->getMenuHtml());
+$response->addHTML(PMA_getHtmlForFilter($ServerStatusData));
+$response->addHTML(PMA_getHtmlForLinkSuggestions($ServerStatusData));
+$response->addHTML(PMA_getHtmlForVariablesList($ServerStatusData));
+$response->addHTML('</div>');
+
+exit;
+
+?>
diff --git a/server_user_groups.php b/server_user_groups.php
new file mode 100644
index 0000000000..7236a91615
--- /dev/null
+++ b/server_user_groups.php
@@ -0,0 +1,64 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Displays the 'User groups' sub page under 'Users' page.
+ *
+ * @package PhpMyAdmin
+ */
+
+require_once 'libraries/common.inc.php';
+require_once 'libraries/server_users.lib.php';
+require_once 'libraries/server_user_groups.lib.php';
+
+PMA_getRelationsParam();
+if (! $GLOBALS['cfgRelation']['menuswork']) {
+ exit;
+}
+
+$response = PMA_Response::getInstance();
+$header = $response->getHeader();
+$scripts = $header->getScripts();
+$scripts->addFile('server_user_groups.js');
+
+$response->addHTML('<div>');
+$response->addHTML(PMA_getHtmlForSubMenusOnUsersPage('server_user_groups.php'));
+
+/**
+ * Delete user group
+ */
+if (! empty($_REQUEST['deleteUserGroup'])) {
+ PMA_deleteUserGroup($_REQUEST['userGroup']);
+}
+
+/**
+ * Add a new user group
+ */
+if (! empty($_REQUEST['addUserGroupSubmit'])) {
+ PMA_editUserGroup($_REQUEST['userGroup'], true);
+}
+
+/**
+ * Update a user group
+ */
+if (! empty($_REQUEST['editUserGroupSubmit'])) {
+ PMA_editUserGroup($_REQUEST['userGroup']);
+}
+
+if (isset($_REQUEST['viewUsers'])) {
+ // Display users belonging to a user group
+ $response->addHTML(PMA_getHtmlForListingUsersofAGroup($_REQUEST['userGroup']));
+}
+
+if (isset($_REQUEST['addUserGroup'])) {
+ // Display add user group dialog
+ $response->addHTML(PMA_getHtmlToEditUserGroup());
+} elseif (isset($_REQUEST['editUserGroup'])) {
+ // Display edit user group dialog
+ $response->addHTML(PMA_getHtmlToEditUserGroup($_REQUEST['userGroup']));
+} else {
+ // Display user groups table
+ $response->addHTML(PMA_getHtmlForUserGroupsTable());
+}
+
+$response->addHTML('</div>');
+?> \ No newline at end of file
diff --git a/server_variables.php b/server_variables.php
new file mode 100644
index 0000000000..b287777206
--- /dev/null
+++ b/server_variables.php
@@ -0,0 +1,60 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Server variables
+ *
+ * @package PhpMyAdmin
+ */
+
+require_once 'libraries/common.inc.php';
+require_once 'libraries/server_variables.lib.php';
+
+$response = PMA_Response::getInstance();
+$header = $response->getHeader();
+$scripts = $header->getScripts();
+$scripts->addFile('server_variables.js');
+
+/**
+ * Does the common work
+ */
+require 'libraries/server_common.inc.php';
+
+/**
+ * Array of documentation links
+ */
+$variable_doc_links = PMA_getArrayForDocumentLinks();
+
+/**
+ * Ajax request
+ */
+
+if (isset($_REQUEST['ajax_request']) && $_REQUEST['ajax_request'] == true) {
+ if (isset($_REQUEST['type'])) {
+ if ($_REQUEST['type'] === 'getval') {
+ PMA_getAjaxReturnForGetVal($variable_doc_links);
+ } else if ($_REQUEST['type'] === 'setval') {
+ PMA_getAjaxReturnForSetVal($variable_doc_links);
+ }
+ exit;
+ }
+}
+
+/**
+ * Displays the sub-page heading
+ */
+$doc_link = PMA_Util::showMySQLDocu('server_system_variables');
+$response->addHtml(PMA_getHtmlForSubPageHeader('variables', $doc_link));
+
+/**
+ * Link templates
+ */
+$response->addHtml(PMA_getHtmlForLinkTemplates());
+
+/**
+ * Displays the page
+ */
+$response->addHtml(PMA_getHtmlForServerVariables($variable_doc_links));
+
+exit;
+
+?>
diff --git a/show_config_errors.php b/show_config_errors.php
new file mode 100644
index 0000000000..0d0cdf4456
--- /dev/null
+++ b/show_config_errors.php
@@ -0,0 +1,40 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Simple wrapper just to enable error reporting and include config
+ *
+ * @package PhpMyAdmin
+ */
+
+// rfc2616 - Section 14.21
+header('Expires: ' . date(DATE_RFC1123));
+// HTTP/1.1
+header(
+ 'Cache-Control: no-store, no-cache, must-revalidate,'
+ . ' pre-check=0, post-check=0, max-age=0'
+);
+if (isset($_SERVER['HTTP_USER_AGENT'])
+ && stristr($_SERVER['HTTP_USER_AGENT'], 'MSIE')
+) {
+
+ /* FIXME: Why is this special case for IE needed? */
+ header('Pragma: public');
+} else {
+ header('Pragma: no-cache'); // HTTP/1.0
+ // test case: exporting a database into a .gz file with Safari
+ // would produce files not having the current time
+ // (added this header for Safari but should not harm other browsers)
+ header('Last-Modified: ' . date(DATE_RFC1123));
+}
+header('Content-Type: text/html; charset=utf-8');
+
+require 'libraries/vendor_config.php';
+
+error_reporting(E_ALL);
+/**
+ * Read config file.
+ */
+if (is_readable(CONFIG_FILE)) {
+ include CONFIG_FILE;
+}
+?>
diff --git a/sql.php b/sql.php
new file mode 100644
index 0000000000..5006dc8b65
--- /dev/null
+++ b/sql.php
@@ -0,0 +1,199 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * SQL executor
+ *
+ * @todo we must handle the case if sql.php is called directly with a query
+ * that returns 0 rows - to prevent cyclic redirects or includes
+ * @package PhpMyAdmin
+ */
+
+/**
+ * Gets some core libraries
+ */
+require_once 'libraries/common.inc.php';
+require_once 'libraries/Table.class.php';
+require_once 'libraries/Header.class.php';
+require_once 'libraries/check_user_privileges.lib.php';
+require_once 'libraries/bookmark.lib.php';
+require_once 'libraries/sql.lib.php';
+require_once 'libraries/sqlparser.lib.php';
+
+$response = PMA_Response::getInstance();
+$header = $response->getHeader();
+$scripts = $header->getScripts();
+$scripts->addFile('jquery/jquery-ui-timepicker-addon.js');
+$scripts->addFile('tbl_change.js');
+$scripts->addFile('indexes.js');
+$scripts->addFile('gis_data_editor.js');
+
+/**
+ * Set ajax_reload in the response if it was already set
+ */
+if (isset($ajax_reload) && $ajax_reload['reload'] === true) {
+ $response->addJSON('ajax_reload', $ajax_reload);
+}
+
+
+/**
+ * Defines the url to return to in case of error in a sql statement
+ */
+// Security checkings
+if (! empty($goto)) {
+ $is_gotofile = preg_replace('@^([^?]+).*$@s', '\\1', $goto);
+ if (! @file_exists('' . $is_gotofile)) {
+ unset($goto);
+ } else {
+ $is_gotofile = ($is_gotofile == $goto);
+ }
+} else {
+ if (empty($table)) {
+ $goto = $cfg['DefaultTabDatabase'];
+ } else {
+ $goto = $cfg['DefaultTabTable'];
+ }
+ $is_gotofile = true;
+} // end if
+
+if (! isset($err_url)) {
+ $err_url = (! empty($back) ? $back : $goto)
+ . '?' . PMA_URL_getCommon($db)
+ . ((strpos(' ' . $goto, 'db_') != 1 && strlen($table))
+ ? '&amp;table=' . urlencode($table)
+ : ''
+ );
+} // end if
+
+// Coming from a bookmark dialog
+if (isset($_POST['bkm_fields']['bkm_sql_query'])) {
+ $sql_query = $_POST['bkm_fields']['bkm_sql_query'];
+} elseif (isset($_GET['sql_query'])) {
+ $sql_query = $_GET['sql_query'];
+}
+
+// This one is just to fill $db
+if (isset($_POST['bkm_fields']['bkm_database'])) {
+ $db = $_POST['bkm_fields']['bkm_database'];
+}
+
+
+// During grid edit, if we have a relational field, show the dropdown for it.
+if (isset($_REQUEST['get_relational_values'])
+ && $_REQUEST['get_relational_values'] == true
+) {
+ PMA_getRelationalValues($db, $table, $display_field);
+ // script has exited at this point
+}
+
+// Just like above, find possible values for enum fields during grid edit.
+if (isset($_REQUEST['get_enum_values']) && $_REQUEST['get_enum_values'] == true) {
+ PMA_getEnumOrSetValues($db, $table, "enum");
+ // script has exited at this point
+}
+
+
+// Find possible values for set fields during grid edit.
+if (isset($_REQUEST['get_set_values']) && $_REQUEST['get_set_values'] == true) {
+ PMA_getEnumOrSetValues($db, $table, "set");
+ // script has exited at this point
+}
+
+/**
+ * Check ajax request to set the column order and visibility
+ */
+if (isset($_REQUEST['set_col_prefs']) && $_REQUEST['set_col_prefs'] == true) {
+ PMA_setColumnOrderOrVisibility($table, $db);
+ // script has exited at this point
+}
+
+// Default to browse if no query set and we have table
+// (needed for browsing from DefaultTabTable)
+if (empty($sql_query) && strlen($table) && strlen($db)) {
+ $sql_query = PMA_getDefaultSqlQueryForBrowse($db, $table);
+
+ // set $goto to what will be displayed if query returns 0 rows
+ $goto = '';
+} else {
+ // Now we can check the parameters
+ PMA_Util::checkParameters(array('sql_query'));
+}
+
+/**
+ * Parse and analyze the query
+ */
+require_once 'libraries/parse_analyze.inc.php';
+
+
+/**
+ * Check rights in case of DROP DATABASE
+ *
+ * This test may be bypassed if $is_js_confirmed = 1 (already checked with js)
+ * but since a malicious user may pass this variable by url/form, we don't take
+ * into account this case.
+ */
+if (PMA_hasNoRightsToDropDatabase(
+ $analyzed_sql_results, $cfg['AllowUserDropDatabase'], $is_superuser
+)) {
+ PMA_Util::mysqlDie(
+ __('"DROP DATABASE" statements are disabled.'),
+ '',
+ '',
+ $err_url
+ );
+} // end if
+
+/**
+ * Need to find the real end of rows?
+ */
+if (isset($find_real_end) && $find_real_end) {
+ $unlim_num_rows = PMA_findRealEndOfRows($db, $table);
+}
+
+
+/**
+ * Bookmark add
+ */
+if (isset($_POST['store_bkm'])) {
+ PMA_addBookmark($cfg['PmaAbsoluteUri'], $goto);
+ // script has exited at this point
+} // end if
+
+
+/**
+ * Sets or modifies the $goto variable if required
+ */
+if ($goto == 'sql.php') {
+ $is_gotofile = false;
+ $goto = 'sql.php' . PMA_URL_getCommon(
+ array(
+ 'db' => $db,
+ 'table' => $table,
+ 'sql_query' => $sql_query
+ )
+ );
+} // end if
+
+PMA_executeQueryAndSendQueryResponse(
+ $analyzed_sql_results,
+ $is_gotofile,
+ $db,
+ $table,
+ isset($find_real_end) ? $find_real_end : null,
+ isset($import_text) ? $import_text : null,
+ isset($extra_data) ? $extra_data : null,
+ $is_affected,
+ isset($message_to_show) ? $message_to_show : null,
+ isset($disp_mode) ? $disp_mode : null,
+ isset($message) ? $message : null,
+ isset($sql_data) ? $sql_data : null,
+ $goto,
+ $pmaThemeImage,
+ isset($disp_query) ? $display_query : null,
+ isset($disp_message) ? $disp_message : null,
+ isset($query_type) ? $query_type : null,
+ $sql_query,
+ isset($selected) ? $selected : null,
+ isset($complete_query) ? $complete_query : null
+);
+
+?>
diff --git a/tbl_addfield.php b/tbl_addfield.php
new file mode 100644
index 0000000000..2a0590deaa
--- /dev/null
+++ b/tbl_addfield.php
@@ -0,0 +1,117 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Displays add field form and handles it
+ *
+ * @package PhpMyAdmin
+ */
+
+/**
+ * Get some core libraries
+ */
+require_once 'libraries/common.inc.php';
+
+$response = PMA_Response::getInstance();
+$header = $response->getHeader();
+$scripts = $header->getScripts();
+$scripts->addFile('tbl_structure.js');
+
+// Check parameters
+PMA_Util::checkParameters(array('db', 'table'));
+
+
+/**
+ * Defines the url to return to in case of error in a sql statement
+ */
+$err_url = 'tbl_sql.php?' . PMA_URL_getCommon($db, $table);
+
+/**
+ * The form used to define the field to add has been submitted
+ */
+$abort = false;
+
+// check number of fields to be created
+if (isset($_REQUEST['submit_num_fields'])) {
+ if (isset($_REQUEST['orig_after_field'])) {
+ $_REQUEST['after_field'] = $_REQUEST['orig_after_field'];
+ }
+ if (isset($_REQUEST['orig_field_where'])) {
+ $_REQUEST['field_where'] = $_REQUEST['orig_field_where'];
+ }
+ $num_fields = $_REQUEST['orig_num_fields'] + $_REQUEST['added_fields'];
+ $regenerate = true;
+} elseif (isset($_REQUEST['num_fields']) && intval($_REQUEST['num_fields']) > 0) {
+ $num_fields = (int) $_REQUEST['num_fields'];
+} else {
+ $num_fields = 1;
+}
+
+if (isset($_REQUEST['do_save_data'])) {
+ //avoid an incorrect calling of PMA_updateColumns() via
+ //tbl_structure.php below
+ unset($_REQUEST['do_save_data']);
+
+ include_once 'libraries/create_addfield.lib.php';
+
+ list($result, $sql_query) = PMA_tryColumnCreationQuery($db, $table, $err_url);
+
+ if ($result === true) {
+ // If comments were sent, enable relation stuff
+ include_once 'libraries/transformations.lib.php';
+
+ // Update comment table for mime types [MIME]
+ if (isset($_REQUEST['field_mimetype'])
+ && is_array($_REQUEST['field_mimetype'])
+ && $cfg['BrowseMIME']
+ ) {
+ foreach ($_REQUEST['field_mimetype'] as $fieldindex => $mimetype) {
+ if (isset($_REQUEST['field_name'][$fieldindex])
+ && strlen($_REQUEST['field_name'][$fieldindex])
+ ) {
+ PMA_setMIME(
+ $db, $table,
+ $_REQUEST['field_name'][$fieldindex],
+ $mimetype,
+ $_REQUEST['field_transformation'][$fieldindex],
+ $_REQUEST['field_transformation_options'][$fieldindex]
+ );
+ }
+ }
+ }
+
+ // Go back to the structure sub-page
+ $message = PMA_Message::success(
+ __('Table %1$s has been altered successfully')
+ );
+ $message->addParam($table);
+ $response->addJSON('message', $message);
+ $response->addJSON(
+ 'sql_query',
+ PMA_Util::getMessage(null, $sql_query)
+ );
+ exit;
+ } else {
+ $error_message_html = PMA_Util::mysqlDie('', '', '', $err_url, false);
+ $response->addHTML($error_message_html);
+ exit;
+ }
+} // end do alter table
+
+/**
+ * Displays the form used to define the new field
+ */
+if ($abort == false) {
+ /**
+ * Gets tables informations
+ */
+ include_once 'libraries/tbl_common.inc.php';
+ include_once 'libraries/tbl_info.inc.php';
+
+ $active_page = 'tbl_structure.php';
+ /**
+ * Display the form
+ */
+ $action = 'tbl_addfield.php';
+ include_once 'libraries/tbl_columns_definition_form.inc.php';
+}
+?>
diff --git a/tbl_change.php b/tbl_change.php
new file mode 100644
index 0000000000..b42340dbd1
--- /dev/null
+++ b/tbl_change.php
@@ -0,0 +1,214 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Displays form for editing and inserting new table rows
+ *
+ * register_globals_save (mark this file save for disabling register globals)
+ *
+ * @package PhpMyAdmin
+ */
+
+/**
+ * Gets the variables sent or posted to this script and displays the header
+ */
+require_once 'libraries/common.inc.php';
+
+/**
+ * Ensures db and table are valid, else moves to the "parent" script
+ */
+require_once 'libraries/db_table_exists.lib.php';
+
+/**
+ * functions implementation for this script
+ */
+require_once 'libraries/insert_edit.lib.php';
+
+/**
+ * Determine whether Insert or Edit and set global variables
+ */
+list(
+ $insert_mode, $where_clause, $where_clause_array, $where_clauses,
+ $result, $rows, $found_unique_key, $after_insert
+) = PMA_determineInsertOrEdit(
+ isset($where_clause) ? $where_clause : null, $db, $table
+);
+
+/**
+ * file listing
+*/
+require_once 'libraries/file_listing.lib.php';
+
+/**
+ * Defines the url to return to in case of error in a sql statement
+ * (at this point, $GLOBALS['goto'] will be set but could be empty)
+ */
+if (empty($GLOBALS['goto'])) {
+ if (strlen($table)) {
+ // avoid a problem (see bug #2202709)
+ $GLOBALS['goto'] = 'tbl_sql.php';
+ } else {
+ $GLOBALS['goto'] = 'db_sql.php';
+ }
+}
+
+
+$_url_params = PMA_getUrlParameters($db, $table);
+$err_url = $GLOBALS['goto'] . PMA_URL_getCommon($_url_params);
+unset($_url_params);
+
+$comments_map = PMA_getCommentsMap($db, $table);
+
+/**
+ * START REGULAR OUTPUT
+ */
+
+/**
+ * Load JavaScript files
+ */
+$response = PMA_Response::getInstance();
+$header = $response->getHeader();
+$scripts = $header->getScripts();
+$scripts->addFile('functions.js');
+$scripts->addFile('tbl_change.js');
+$scripts->addFile('jquery/jquery-ui-timepicker-addon.js');
+$scripts->addFile('gis_data_editor.js');
+
+/**
+ * Displays the query submitted and its result
+ *
+ * $disp_message come from tbl_replace.php
+ */
+if (! empty($disp_message)) {
+ $response->addHTML(PMA_Util::getMessage($disp_message, null));
+}
+
+// used as a global by PMA_Util::getDefaultFunctionForField()
+$analyzed_sql = PMA_Table::analyzeStructure($db, $table);
+
+$table_columns = PMA_getTableColumns($db, $table);
+
+// retrieve keys into foreign fields, if any
+$foreigners = PMA_getForeigners($db, $table);
+
+// Retrieve form parameters for insert/edit form
+$_form_params = PMA_getFormParametersForInsertForm(
+ $db, $table, $where_clauses, $where_clause_array, $err_url
+);
+
+/**
+ * Displays the form
+ */
+// autocomplete feature of IE kills the "onchange" event handler and it
+// must be replaced by the "onpropertychange" one in this case
+$chg_evt_handler = (PMA_USR_BROWSER_AGENT == 'IE'
+ && PMA_USR_BROWSER_VER >= 5
+ && PMA_USR_BROWSER_VER < 7
+)
+ ? 'onpropertychange'
+ : 'onchange';
+// Had to put the URI because when hosted on an https server,
+// some browsers send wrongly this form to the http server.
+
+$html_output = '';
+// Set if we passed the first timestamp field
+$timestamp_seen = false;
+$columns_cnt = count($table_columns);
+
+$tabindex = 0;
+$tabindex_for_function = +3000;
+$tabindex_for_null = +6000;
+$tabindex_for_value = 0;
+$o_rows = 0;
+$biggest_max_file_size = 0;
+
+$url_params['db'] = $db;
+$url_params['table'] = $table;
+$url_params = PMA_urlParamsInEditMode(
+ $url_params, $where_clause_array, $where_clause
+);
+
+$has_blob_field = false;
+foreach ($table_columns as $column) {
+ if (PMA_isColumnBlob($column)) {
+ $has_blob_field = true;
+ break;
+ }
+}
+
+//Insert/Edit form
+//If table has blob fields we have to disable ajax.
+$html_output .= PMA_getHtmlForInsertEditFormHeader($has_blob_field, $is_upload);
+
+$html_output .= PMA_URL_getHiddenInputs($_form_params);
+
+$titles['Browse'] = PMA_Util::getIcon('b_browse.png', __('Browse foreign values'));
+
+// user can toggle the display of Function column and column types
+// (currently does not work for multi-edits)
+if (! $cfg['ShowFunctionFields'] || ! $cfg['ShowFieldTypesInDataEditView']) {
+ $html_output .= __('Show');
+}
+
+if (! $cfg['ShowFunctionFields']) {
+ $html_output .= PMA_showFunctionFieldsInEditMode($url_params, false);
+}
+
+if (! $cfg['ShowFieldTypesInDataEditView']) {
+ $html_output .= PMA_showColumnTypesInDataEditView($url_params, false);
+}
+
+foreach ($rows as $row_id => $current_row) {
+ if ($current_row === false) {
+ unset($current_row);
+ }
+
+ $jsvkey = $row_id;
+ $vkey = '[multi_edit][' . $jsvkey . ']';
+
+ $current_result = (isset($result) && is_array($result) && isset($result[$row_id])
+ ? $result[$row_id]
+ : $result);
+ if ($insert_mode && $row_id > 0) {
+ $html_output .= PMA_getHtmlForIgnoreOption($row_id);
+ }
+
+ $html_output .= PMA_getHtmlForInsertEditRow(
+ $url_params, $table_columns, $column, $comments_map, $timestamp_seen,
+ $current_result, $chg_evt_handler, $jsvkey, $vkey, $insert_mode,
+ isset($current_row) ? $current_row : null, $o_rows, $tabindex, $columns_cnt,
+ $is_upload, $tabindex_for_function, $foreigners, $tabindex_for_null,
+ $tabindex_for_value, $table, $db, $row_id, $titles,
+ $biggest_max_file_size, $text_dir
+ );
+} // end foreach on multi-edit
+
+$html_output .= PMA_getHtmlForGisEditor();
+
+if (! isset($after_insert)) {
+ $after_insert = 'back';
+}
+
+//action panel
+$html_output .= PMA_getActionsPanel(
+ $where_clause, $after_insert, $tabindex,
+ $tabindex_for_value, $found_unique_key
+);
+
+if ($biggest_max_file_size > 0) {
+ $html_output .= ' '
+ . PMA_Util::generateHiddenMaxFileSize(
+ $biggest_max_file_size
+ ) . "\n";
+}
+$html_output .= '</form>';
+// end Insert/Edit form
+
+if ($insert_mode) {
+ //Continue insertion form
+ $html_output .= PMA_getContinueInsertionForm(
+ $table, $db, $where_clause_array, $err_url
+ );
+}
+
+$response->addHTML($html_output);
+?>
diff --git a/tbl_chart.php b/tbl_chart.php
new file mode 100644
index 0000000000..f9e5e54b0d
--- /dev/null
+++ b/tbl_chart.php
@@ -0,0 +1,143 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * handles creation of the chart
+ *
+ * @package PhpMyAdmin
+ */
+
+require_once 'libraries/common.inc.php';
+require_once 'libraries/tbl_chart.lib.php';
+/*
+ * Execute the query and return the result
+ */
+if (isset($_REQUEST['ajax_request'])
+ && isset($_REQUEST['pos'])
+ && isset($_REQUEST['session_max_rows'])
+) {
+ $response = PMA_Response::getInstance();
+
+ if (strlen($GLOBALS['table']) && strlen($GLOBALS['db'])) {
+ include './libraries/tbl_common.inc.php';
+ } else {
+ $response->isSuccess(false);
+ $response->addJSON('message', __('Error'));
+ exit;
+ }
+
+ $sql_with_limit = 'SELECT * FROM( ' . $sql_query . ' ) AS `temp_res` LIMIT '
+ . $_REQUEST['pos'] . ', ' . $_REQUEST['session_max_rows'];
+ $data = array();
+ $result = $GLOBALS['dbi']->tryQuery($sql_with_limit);
+ while ($row = $GLOBALS['dbi']->fetchAssoc($result)) {
+ $data[] = $row;
+ }
+
+ if (empty($data)) {
+ $response->isSuccess(false);
+ $response->addJSON('message', __('No data to display'));
+ exit;
+ }
+ $sanitized_data = array();
+
+ foreach ($data as $data_row_number => $data_row) {
+ $tmp_row = array();
+ foreach ($data_row as $data_column => $data_value) {
+ $tmp_row[htmlspecialchars($data_column)] = htmlspecialchars($data_value);
+ }
+ $sanitized_data[] = $tmp_row;
+ }
+ $response->isSuccess(true);
+ $response->addJSON('message', null);
+ $response->addJSON('chartData', json_encode($sanitized_data));
+ unset($sanitized_data);
+ exit;
+}
+
+$response = PMA_Response::getInstance();
+// Throw error if no sql query is set
+if (! isset($sql_query) || $sql_query == '') {
+ $response->isSuccess(false);
+ $response->addHTML(
+ PMA_Message::error(__('No SQL query was set to fetch data.'))
+ );
+ exit;
+}
+$header = $response->getHeader();
+$scripts = $header->getScripts();
+$scripts->addFile('chart.js');
+$scripts->addFile('tbl_chart.js');
+$scripts->addFile('jqplot/jquery.jqplot.js');
+$scripts->addFile('jqplot/plugins/jqplot.barRenderer.js');
+$scripts->addFile('jqplot/plugins/jqplot.canvasAxisLabelRenderer.js');
+$scripts->addFile('jqplot/plugins/jqplot.canvasTextRenderer.js');
+$scripts->addFile('jqplot/plugins/jqplot.categoryAxisRenderer.js');
+$scripts->addFile('jqplot/plugins/jqplot.dateAxisRenderer.js');
+$scripts->addFile('jqplot/plugins/jqplot.pointLabels.js');
+$scripts->addFile('jqplot/plugins/jqplot.pieRenderer.js');
+$scripts->addFile('jqplot/plugins/jqplot.highlighter.js');
+
+/**
+ * Runs common work
+ */
+if (strlen($GLOBALS['table'])) {
+ $url_params['goto'] = $cfg['DefaultTabTable'];
+ $url_params['back'] = 'tbl_sql.php';
+ include 'libraries/tbl_common.inc.php';
+ include 'libraries/tbl_info.inc.php';
+} elseif (strlen($GLOBALS['db'])) {
+ $url_params['goto'] = $cfg['DefaultTabDatabase'];
+ $url_params['back'] = 'sql.php';
+ include 'libraries/db_common.inc.php';
+ include 'libraries/db_info.inc.php';
+} else {
+ $url_params['goto'] = $cfg['DefaultTabServer'];
+ $url_params['back'] = 'sql.php';
+ include 'libraries/server_common.inc.php';
+}
+
+$data = array();
+
+$result = $GLOBALS['dbi']->tryQuery($sql_query);
+$fields_meta = $GLOBALS['dbi']->getFieldsMeta($result);
+while ($row = $GLOBALS['dbi']->fetchAssoc($result)) {
+ $data[] = $row;
+}
+
+$keys = array_keys($data[0]);
+
+$numeric_types = array('int', 'real');
+$numeric_column_count = 0;
+foreach ($keys as $idx => $key) {
+ if (in_array($fields_meta[$idx]->type, $numeric_types)) {
+ $numeric_column_count++;
+ }
+}
+if ($numeric_column_count == 0) {
+ $response->isSuccess(false);
+ $response->addJSON(
+ 'message',
+ __('No numeric columns present in the table to plot.')
+ );
+ exit;
+}
+
+// get settings if any posted
+$chartSettings = array();
+if (PMA_isValid($_REQUEST['chartSettings'], 'array')) {
+ $chartSettings = $_REQUEST['chartSettings'];
+}
+
+$url_params['db'] = $GLOBALS['db'];
+$url_params['reload'] = 1;
+
+/**
+ * Displays the page
+ */
+$htmlString = PMA_getHtmlForTableChartDisplay(
+ $url_query, $url_params, $keys, $fields_meta, $numeric_types,
+ $numeric_column_count, $sql_query
+);
+
+$response->addHTML($htmlString);
+?>
diff --git a/tbl_create.php b/tbl_create.php
new file mode 100644
index 0000000000..c380ce8219
--- /dev/null
+++ b/tbl_create.php
@@ -0,0 +1,94 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Displays table create form and handles it
+ *
+ * @package PhpMyAdmin
+ */
+
+/**
+ * Get some core libraries
+ */
+require_once 'libraries/common.inc.php';
+require_once 'libraries/create_addfield.lib.php';
+
+// Check parameters
+PMA_Util::checkParameters(array('db'));
+
+/* Check if database name is empty */
+if (strlen($db) == 0) {
+ PMA_Util::mysqlDie(
+ __('The database name is empty!'), '', '', 'index.php'
+ );
+}
+
+/**
+ * Selects the database to work with
+ */
+if (!$GLOBALS['dbi']->selectDb($db)) {
+ PMA_Util::mysqlDie(
+ sprintf(__('\'%s\' database does not exist.'), htmlspecialchars($db)),
+ '',
+ '',
+ 'index.php'
+ );
+}
+
+if ($GLOBALS['dbi']->getColumns($db, $table)) {
+ // table exists already
+ PMA_Util::mysqlDie(
+ sprintf(__('Table %s already exists!'), htmlspecialchars($table)),
+ '',
+ '',
+ 'db_structure.php?' . PMA_URL_getCommon($db)
+ );
+}
+
+// for libraries/tbl_columns_definition_form.inc.php
+// check number of fields to be created
+$num_fields = PMA_getNumberOfFieldsFromRequest();
+
+$action = 'tbl_create.php';
+
+/**
+ * The form used to define the structure of the table has been submitted
+ */
+if (isset($_REQUEST['do_save_data'])) {
+ $sql_query = PMA_getTableCreationQuery($db, $table);
+ // Executes the query
+ $result = $GLOBALS['dbi']->tryQuery($sql_query);
+
+ if ($result) {
+ // If comments were sent, enable relation stuff
+ include_once 'libraries/transformations.lib.php';
+ // Update comment table for mime types [MIME]
+ if (isset($_REQUEST['field_mimetype'])
+ && is_array($_REQUEST['field_mimetype'])
+ && $cfg['BrowseMIME']
+ ) {
+ foreach ($_REQUEST['field_mimetype'] as $fieldindex => $mimetype) {
+ if (isset($_REQUEST['field_name'][$fieldindex])
+ && strlen($_REQUEST['field_name'][$fieldindex])
+ ) {
+ PMA_setMIME(
+ $db, $table, $_REQUEST['field_name'][$fieldindex], $mimetype,
+ $_REQUEST['field_transformation'][$fieldindex],
+ $_REQUEST['field_transformation_options'][$fieldindex]
+ );
+ }
+ }
+ }
+ } else {
+ $response = PMA_Response::getInstance();
+ $response->isSuccess(false);
+ $response->addJSON('message', $GLOBALS['dbi']->getError());
+ }
+ exit;
+} // end do create table
+
+/**
+ * Displays the form used to define the structure of the table
+ */
+require 'libraries/tbl_columns_definition_form.inc.php';
+
+?>
diff --git a/tbl_export.php b/tbl_export.php
new file mode 100644
index 0000000000..f49b7eff5e
--- /dev/null
+++ b/tbl_export.php
@@ -0,0 +1,87 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Table export
+ *
+ * @package PhpMyAdmin
+ */
+
+/**
+ *
+ */
+require_once 'libraries/common.inc.php';
+
+$response = PMA_Response::getInstance();
+$header = $response->getHeader();
+$scripts = $header->getScripts();
+$scripts->addFile('export.js');
+
+/**
+ * Gets tables informations and displays top links
+ */
+require_once 'libraries/tbl_common.inc.php';
+$url_query .= '&amp;goto=tbl_export.php&amp;back=tbl_export.php';
+require_once 'libraries/tbl_info.inc.php';
+
+// Dump of a table
+
+$export_page_title = __('View dump (schema) of table');
+
+// When we have some query, we need to remove LIMIT from that and possibly
+// generate WHERE clause (if we are asked to export specific rows)
+
+if (! empty($sql_query)) {
+ // Parse query so we can work with tokens
+ $parsed_sql = PMA_SQP_parse($sql_query);
+ $analyzed_sql = PMA_SQP_analyze($parsed_sql);
+
+ // Need to generate WHERE clause?
+ if (isset($where_clause)) {
+
+ // Regular expressions which can appear in sql query,
+ // before the sql segment which remains as it is.
+ $regex_array = array(
+ '/\bwhere\b/i', '/\bgroup by\b/i', '/\bhaving\b/i', '/\border by\b/i'
+ );
+
+ $first_occurring_regex = PMA_Util::getFirstOccurringRegularExpression(
+ $regex_array, $sql_query
+ );
+ unset($regex_array);
+
+ // The part "SELECT `id`, `name` FROM `customers`"
+ // is not modified by the next code segment, when exporting
+ // the result set from a query such as
+ // "SELECT `id`, `name` FROM `customers` WHERE id NOT IN
+ // ( SELECT id FROM companies WHERE name LIKE '%u%')"
+ if (! is_null($first_occurring_regex)) {
+ $temp_sql_array = preg_split($first_occurring_regex, $sql_query);
+ $sql_query = $temp_sql_array[0];
+ }
+ unset($first_occurring_regex, $temp_sql_array);
+
+ // Append the where clause using the primary key of each row
+ if (is_array($where_clause) && (count($where_clause) > 0)) {
+ $sql_query .= ' WHERE (' . implode(') OR (', $where_clause) . ')';
+ }
+
+ if (!empty($analyzed_sql[0]['group_by_clause'])) {
+ $sql_query .= ' GROUP BY ' . $analyzed_sql[0]['group_by_clause'];
+ }
+ if (!empty($analyzed_sql[0]['having_clause'])) {
+ $sql_query .= ' HAVING ' . $analyzed_sql[0]['having_clause'];
+ }
+ if (!empty($analyzed_sql[0]['order_by_clause'])) {
+ $sql_query .= ' ORDER BY ' . $analyzed_sql[0]['order_by_clause'];
+ }
+ } else {
+ // Just crop LIMIT clause
+ $sql_query = $analyzed_sql[0]['section_before_limit']
+ . $analyzed_sql[0]['section_after_limit'];
+ }
+ echo PMA_Util::getMessage(PMA_Message::success());
+}
+
+$export_type = 'table';
+require_once 'libraries/display_export.inc.php';
+?>
diff --git a/tbl_find_replace.php b/tbl_find_replace.php
new file mode 100644
index 0000000000..c450cf62b5
--- /dev/null
+++ b/tbl_find_replace.php
@@ -0,0 +1,63 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Handles find and replace tab
+ *
+ * Displays find and replace form, allows previewing and do the replacing
+ *
+ * @package PhpMyAdmin
+ */
+
+/**
+ * Gets some core libraries
+ */
+require_once 'libraries/common.inc.php';
+require_once 'libraries/TableSearch.class.php';
+
+$response = PMA_Response::getInstance();
+$table_search = new PMA_TableSearch($db, $table, "replace");
+
+$connectionCharSet = $GLOBALS['dbi']->fetchValue(
+ "SHOW VARIABLES LIKE 'character_set_connection'", 0, 1
+);
+if (isset($_POST['find'])) {
+ $preview = $table_search->getReplacePreview(
+ $_POST['columnIndex'],
+ $_POST['find'],
+ $_POST['replaceWith'],
+ $connectionCharSet
+ );
+ $response->addJSON('preview', $preview);
+ exit;
+}
+
+$header = $response->getHeader();
+$scripts = $header->getScripts();
+$scripts->addFile('tbl_find_replace.js');
+
+// Show secondary level of tabs
+$htmlOutput = $table_search->getSecondaryTabs();
+
+if (isset($_POST['replace'])) {
+ $htmlOutput .= $table_search->replace(
+ $_POST['columnIndex'],
+ $_POST['findString'],
+ $_POST['replaceWith'],
+ $connectionCharSet
+ );
+ $htmlOutput .= PMA_Util::getMessage(
+ __('Your SQL query has been executed successfully'),
+ null, 'success'
+ );
+}
+
+if (! isset($goto)) {
+ $goto = $GLOBALS['cfg']['DefaultTabTable'];
+}
+// Defines the url to return to in case of error in the next sql statement
+$err_url = $goto . '?' . PMA_URL_getCommon($db, $table);
+// Displays the find and replace form
+$htmlOutput .= $table_search->getSelectionForm($goto);
+$response->addHTML($htmlOutput);
+
+?>
diff --git a/tbl_get_field.php b/tbl_get_field.php
new file mode 100644
index 0000000000..060b28e4cc
--- /dev/null
+++ b/tbl_get_field.php
@@ -0,0 +1,55 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Provides download to a given field defined in parameters.
+ *
+ * @package PhpMyAdmin
+ */
+
+/**
+ * Common functions.
+ */
+// we don't want the usual PMA_Response-generated HTML above the column's data
+define('PMA_BYPASS_GET_INSTANCE', 1);
+require_once 'libraries/common.inc.php';
+require_once 'libraries/mime.lib.php';
+
+/* Check parameters */
+PMA_Util::checkParameters(
+ array('db', 'table')
+);
+
+/* Select database */
+if (!$GLOBALS['dbi']->selectDb($db)) {
+ PMA_Util::mysqlDie(
+ sprintf(__('\'%s\' database does not exist.'), htmlspecialchars($db)),
+ '', ''
+ );
+}
+
+/* Check if table exists */
+if (!$GLOBALS['dbi']->getColumns($db, $table)) {
+ PMA_Util::mysqlDie(__('Invalid table name'));
+}
+
+/* Grab data */
+$sql = 'SELECT ' . PMA_Util::backquote($_GET['transform_key'])
+ . ' FROM ' . PMA_Util::backquote($table)
+ . ' WHERE ' . $_GET['where_clause'] . ';';
+$result = $GLOBALS['dbi']->fetchValue($sql);
+
+/* Check return code */
+if ($result === false) {
+ PMA_Util::mysqlDie(__('MySQL returned an empty result set (i.e. zero rows).'), $sql);
+}
+
+/* Avoid corrupting data */
+@ini_set('url_rewriter.tags', '');
+
+PMA_downloadHeader(
+ $table . '-' . $_GET['transform_key'] . '.bin',
+ PMA_detectMIME($result),
+ strlen($result)
+);
+echo $result;
+?>
diff --git a/tbl_gis_visualization.php b/tbl_gis_visualization.php
new file mode 100644
index 0000000000..c64dd7c701
--- /dev/null
+++ b/tbl_gis_visualization.php
@@ -0,0 +1,119 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * handles creation of the GIS visualizations.
+ *
+ * @package PhpMyAdmin
+ */
+
+require_once 'libraries/common.inc.php';
+
+// Runs common work
+require_once 'libraries/db_common.inc.php';
+$url_params['goto'] = $cfg['DefaultTabDatabase'];
+$url_params['back'] = 'sql.php';
+
+// Import visualization functions
+require_once 'libraries/tbl_gis_visualization.lib.php';
+
+$response = PMA_Response::getInstance();
+// Throw error if no sql query is set
+if (! isset($sql_query) || $sql_query == '') {
+ $response->isSuccess(false);
+ $response->addHTML(
+ PMA_Message::error(__('No SQL query was set to fetch data.'))
+ );
+ exit;
+}
+
+// Execute the query and return the result
+$result = $GLOBALS['dbi']->tryQuery($sql_query);
+// Get the meta data of results
+$meta = $GLOBALS['dbi']->getFieldsMeta($result);
+
+// Find the candidate fields for label column and spatial column
+$labelCandidates = array(); $spatialCandidates = array();
+foreach ($meta as $column_meta) {
+ if ($column_meta->type == 'geometry') {
+ $spatialCandidates[] = $column_meta->name;
+ } else {
+ $labelCandidates[] = $column_meta->name;
+ }
+}
+
+// Get settings if any posted
+$visualizationSettings = array();
+if (PMA_isValid($_REQUEST['visualizationSettings'], 'array')) {
+ $visualizationSettings = $_REQUEST['visualizationSettings'];
+}
+
+if (! isset($visualizationSettings['labelColumn']) && isset($labelCandidates[0])) {
+ $visualizationSettings['labelColumn'] = '';
+}
+
+// If spatial column is not set, use first geometric colum as spatial column
+if (! isset($visualizationSettings['spatialColumn'])) {
+ $visualizationSettings['spatialColumn'] = $spatialCandidates[0];
+}
+
+// Convert geometric columns from bytes to text.
+$modified_query = PMA_GIS_modifyQuery($sql_query, $visualizationSettings);
+$modified_result = $GLOBALS['dbi']->tryQuery($modified_query);
+
+$data = array();
+while ($row = $GLOBALS['dbi']->fetchAssoc($modified_result)) {
+ $data[] = $row;
+}
+
+if (isset($_REQUEST['saveToFile'])) {
+ $response->disable();
+ $file_name = $_REQUEST['fileName'];
+ if ($file_name == '') {
+ $file_name = $visualizationSettings['spatialColumn'];
+ }
+
+ $save_format = $_REQUEST['fileFormat'];
+ PMA_GIS_saveToFile($data, $visualizationSettings, $save_format, $file_name);
+ exit();
+}
+
+$header = $response->getHeader();
+$scripts = $header->getScripts();
+$scripts->addFile('openlayers/OpenLayers.js');
+$scripts->addFile('jquery/jquery.svg.js');
+$scripts->addFile('tbl_gis_visualization.js');
+$scripts->addFile('OpenStreetMap.js');
+
+// If all the rows contain SRID, use OpenStreetMaps on the initial loading.
+if (! isset($_REQUEST['displayVisualization'])) {
+ $visualizationSettings['choice'] = 'useBaseLayer';
+ foreach ($data as $row) {
+ if ($row['srid'] == 0) {
+ unset($visualizationSettings['choice']);
+ break;
+ }
+ }
+}
+
+$svg_support = (PMA_USR_BROWSER_AGENT == 'IE' && PMA_USR_BROWSER_VER <= 8)
+ ? false : true;
+$format = $svg_support ? 'svg' : 'png';
+
+// get the chart and settings after chart generation
+$visualization = PMA_GIS_visualizationResults(
+ $data, $visualizationSettings, $format
+);
+
+/**
+ * Displays the page
+ */
+
+$html = PMA_getHtmlForGisVisualization(
+ $url_params, $labelCandidates, $spatialCandidates,
+ $visualizationSettings, $sql_query, $visualization, $svg_support,
+ $data
+);
+
+$response->addHTML($html);
+
+?>
diff --git a/tbl_import.php b/tbl_import.php
new file mode 100644
index 0000000000..f8e6e906de
--- /dev/null
+++ b/tbl_import.php
@@ -0,0 +1,31 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Table import
+ *
+ * @package PhpMyAdmin
+ */
+
+/**
+ *
+ */
+require_once 'libraries/common.inc.php';
+
+$response = PMA_Response::getInstance();
+$header = $response->getHeader();
+$scripts = $header->getScripts();
+$scripts->addFile('import.js');
+
+/**
+ * Gets tables informations and displays top links
+ */
+require_once 'libraries/tbl_common.inc.php';
+$url_query .= '&amp;goto=tbl_import.php&amp;back=tbl_import.php';
+
+require_once 'libraries/tbl_info.inc.php';
+
+$import_type = 'table';
+require_once 'libraries/display_import.inc.php';
+
+?>
+
diff --git a/tbl_indexes.php b/tbl_indexes.php
new file mode 100644
index 0000000000..e095ded833
--- /dev/null
+++ b/tbl_indexes.php
@@ -0,0 +1,48 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Displays index edit/creation form and handles it
+ *
+ * @package PhpMyAdmin
+ */
+
+/**
+ * Gets some core libraries
+ */
+require_once 'libraries/common.inc.php';
+require_once 'libraries/Index.class.php';
+require_once 'libraries/tbl_common.inc.php';
+require_once 'libraries/tbl_indexes.lib.php';
+
+
+$index = PMA_prepareFormValues($db, $table);
+/**
+ * Process the data from the edit/create index form,
+ * run the query to build the new index
+ * and moves back to "tbl_sql.php"
+ */
+if (isset($_REQUEST['do_save_data'])) {
+ PMA_handleCreateOrEditIndex($db, $table, $index);
+} // end builds the new index
+
+
+/**
+ * Display the form to edit/create an index
+ */
+require_once 'libraries/tbl_info.inc.php';
+
+$add_fields = PMA_getNumberOfFieldsForForm($index);
+
+$form_params = PMA_getFormParameters($db, $table);
+
+// Get fields and stores their name/type
+$fields = PMA_getNameAndTypeOfTheColumns($db, $table);
+
+$html = PMA_getHtmlForIndexForm($fields, $index, $form_params, $add_fields);
+
+$response = PMA_Response::getInstance();
+$response->addHTML($html);
+$header = $response->getHeader();
+$scripts = $header->getScripts();
+$scripts->addFile('indexes.js');
+?>
diff --git a/tbl_move_copy.php b/tbl_move_copy.php
new file mode 100644
index 0000000000..402da2efcb
--- /dev/null
+++ b/tbl_move_copy.php
@@ -0,0 +1,104 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Table moving and copying
+ *
+ * @package PhpMyAdmin
+ */
+
+/**
+ * Gets some core libraries
+ */
+require_once 'libraries/common.inc.php';
+
+// Check parameters
+PMA_Util::checkParameters(array('db', 'table'));
+
+/**
+ * Defines the url to return to in case of error in a sql statement
+ */
+$err_url = 'tbl_sql.php?' . PMA_URL_getCommon($db, $table);
+
+
+/**
+ * Selects the database to work with
+ */
+$GLOBALS['dbi']->selectDb($db);
+
+$goto = $cfg['DefaultTabTable'];
+
+/**
+ * $_REQUEST['target_db'] could be empty in case we came from an input field
+ * (when there are many databases, no drop-down)
+ */
+if (empty($_REQUEST['target_db'])) {
+ $_REQUEST['target_db'] = $db;
+}
+
+/**
+ * A target table name has been sent to this script -> do the work
+ */
+if (PMA_isValid($_REQUEST['new_name'])) {
+ if ($db == $_REQUEST['target_db'] && $table == $_REQUEST['new_name']) {
+ if (isset($_REQUEST['submit_move'])) {
+ $message = PMA_Message::error(__('Can\'t move table to same one!'));
+ } else {
+ $message = PMA_Message::error(__('Can\'t copy table to same one!'));
+ }
+ $result = false;
+ } else {
+ $result = PMA_Table::moveCopy(
+ $db, $table, $_REQUEST['target_db'], $_REQUEST['new_name'],
+ $_REQUEST['what'], isset($_REQUEST['submit_move']), 'one_table'
+ );
+
+ if (isset($_REQUEST['submit_move'])) {
+ $message = PMA_Message::success(__('Table %s has been moved to %s.'));
+ } else {
+ $message = PMA_Message::success(__('Table %s has been copied to %s.'));
+ }
+ $old = PMA_Util::backquote($db) . '.'
+ . PMA_Util::backquote($table);
+ $message->addParam($old);
+ $new = PMA_Util::backquote($_REQUEST['target_db']) . '.'
+ . PMA_Util::backquote($_REQUEST['new_name']);
+ $message->addParam($new);
+
+ /* Check: Work on new table or on old table? */
+ if (isset($_REQUEST['submit_move'])
+ || PMA_isValid($_REQUEST['switch_to_new'])
+ ) {
+ $db = $_REQUEST['target_db'];
+ $table = $_REQUEST['new_name'];
+ }
+ $reload = 1;
+ }
+} else {
+ /**
+ * No new name for the table!
+ */
+ $message = PMA_Message::error(__('The table name is empty!'));
+ $result = false;
+}
+
+if ($GLOBALS['is_ajax_request'] == true) {
+ $response = PMA_Response::getInstance();
+ $response->addJSON('message', $message);
+ if ($message->isSuccess()) {
+ $response->addJSON('db', $GLOBALS['db']);
+ $response->addJSON(
+ 'sql_query',
+ PMA_Util::getMessage(null, $sql_query)
+ );
+ } else {
+ $response->isSuccess(false);
+ }
+ exit;
+}
+
+/**
+ * Back to the calling script
+ */
+$_message = $message;
+unset($message);
+?>
diff --git a/tbl_operations.php b/tbl_operations.php
new file mode 100644
index 0000000000..a078ab99f6
--- /dev/null
+++ b/tbl_operations.php
@@ -0,0 +1,408 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Various table operations
+ *
+ * @package PhpMyAdmin
+ */
+
+/**
+ *
+ */
+require_once 'libraries/common.inc.php';
+
+/**
+ * functions implementation for this script
+ */
+require_once 'libraries/operations.lib.php';
+
+$pma_table = new PMA_Table($GLOBALS['table'], $GLOBALS['db']);
+$response = PMA_Response::getInstance();
+
+/**
+ * Runs common work
+ */
+require 'libraries/tbl_common.inc.php';
+$url_query .= '&amp;goto=tbl_operations.php&amp;back=tbl_operations.php';
+$url_params['goto'] = $url_params['back'] = 'tbl_operations.php';
+
+/**
+ * Gets relation settings
+ */
+$cfgRelation = PMA_getRelationsParam();
+
+/**
+ * Gets available MySQL charsets and storage engines
+ */
+require_once 'libraries/mysql_charsets.inc.php';
+require_once 'libraries/StorageEngine.class.php';
+
+/**
+ * Class for partition management
+ */
+require_once 'libraries/Partition.class.php';
+
+// reselect current db (needed in some cases probably due to
+// the calling of relation.lib.php)
+$GLOBALS['dbi']->selectDb($GLOBALS['db']);
+
+/**
+ * Gets tables informations
+ */
+require 'libraries/tbl_info.inc.php';
+
+// define some variables here, for improved syntax in the conditionals
+$is_myisam_or_aria = $is_isam = $is_innodb = $is_berkeleydb = false;
+$is_aria = $is_pbxt = false;
+// set initial value of these variables, based on the current table engine
+list($is_myisam_or_aria, $is_innodb, $is_isam,
+ $is_berkeleydb, $is_aria, $is_pbxt
+) = PMA_setGlobalVariablesForEngine($tbl_storage_engine);
+
+if ($is_aria) {
+ // the value for transactional can be implicit
+ // (no create option found, in this case it means 1)
+ // or explicit (option found with a value of 0 or 1)
+ // ($transactional may have been set by libraries/tbl_info.inc.php,
+ // from the $create_options)
+ $transactional = (isset($transactional) && $transactional == '0')
+ ? '0'
+ : '1';
+ $page_checksum = (isset($page_checksum)) ? $page_checksum : '';
+}
+
+$reread_info = false;
+$table_alters = array();
+
+/**
+ * If the table has to be moved to some other database
+ */
+if (isset($_REQUEST['submit_move']) || isset($_REQUEST['submit_copy'])) {
+ $_message = '';
+ include_once 'tbl_move_copy.php';
+}
+/**
+ * If the table has to be maintained
+ */
+if (isset($_REQUEST['table_maintenance'])) {
+ include_once 'sql.php';
+ unset($result);
+}
+/**
+ * Updates table comment, type and options if required
+ */
+if (isset($_REQUEST['submitoptions'])) {
+ $_message = '';
+ $warning_messages = array();
+
+ if (isset($_REQUEST['new_name'])) {
+ if ($pma_table->rename($_REQUEST['new_name'])) {
+ $_message .= $pma_table->getLastMessage();
+ $result = true;
+ $GLOBALS['table'] = $pma_table->getName();
+ $reread_info = true;
+ $reload = true;
+ } else {
+ $_message .= $pma_table->getLastError();
+ $result = false;
+ }
+ }
+
+ if (! empty($_REQUEST['new_tbl_storage_engine'])
+ && strtolower($_REQUEST['new_tbl_storage_engine']) !== strtolower($tbl_storage_engine)
+ ) {
+ $new_tbl_storage_engine = $_REQUEST['new_tbl_storage_engine'];
+ // reset the globals for the new engine
+ list($is_myisam_or_aria, $is_innodb, $is_isam,
+ $is_berkeleydb, $is_aria, $is_pbxt
+ ) = PMA_setGlobalVariablesForEngine($new_tbl_storage_engine);
+
+ if ($is_aria) {
+ $transactional = (isset($transactional) && $transactional == '0')
+ ? '0'
+ : '1';
+ $page_checksum = (isset($page_checksum)) ? $page_checksum : '';
+ }
+ } else {
+ $new_tbl_storage_engine = '';
+ }
+
+ $table_alters = PMA_getTableAltersArray(
+ $is_myisam_or_aria, $is_isam, $pack_keys,
+ (empty($checksum) ? '0' : '1'),
+ $is_aria,
+ ((isset($page_checksum)) ? $page_checksum : ''),
+ (empty($delay_key_write) ? '0' : '1'),
+ $is_innodb, $is_pbxt, $row_format,
+ $new_tbl_storage_engine,
+ ((isset($transactional) && $transactional == '0') ? '0' : '1'),
+ $tbl_collation
+ );
+
+ if (count($table_alters) > 0) {
+ $sql_query = 'ALTER TABLE '
+ . PMA_Util::backquote($GLOBALS['table']);
+ $sql_query .= "\r\n" . implode("\r\n", $table_alters);
+ $sql_query .= ';';
+ $result .= $GLOBALS['dbi']->query($sql_query) ? true : false;
+ $reread_info = true;
+ unset($table_alters);
+ $warning_messages = PMA_getWarningMessagesArray();
+ }
+}
+/**
+ * Reordering the table has been requested by the user
+ */
+if (isset($_REQUEST['submitorderby']) && ! empty($_REQUEST['order_field'])) {
+ list($sql_query, $result) = PMA_getQueryAndResultForReorderingTable();
+} // end if
+
+/**
+ * A partition operation has been requested by the user
+ */
+if (isset($_REQUEST['submit_partition'])
+ && ! empty($_REQUEST['partition_operation'])
+) {
+ list($sql_query, $result) = PMA_getQueryAndResultForPartition();
+} // end if
+
+if ($reread_info) {
+ // to avoid showing the old value (for example the AUTO_INCREMENT) after
+ // a change, clear the cache
+ PMA_Table::$cache = array();
+ $page_checksum = $checksum = $delay_key_write = 0;
+ include 'libraries/tbl_info.inc.php';
+}
+unset($reread_info);
+
+if (isset($result) && empty($message_to_show)) {
+ // set to success by default, because result set could be empty
+ // (for example, a table rename)
+ $_type = 'success';
+ if (empty($_message)) {
+ $_message = $result
+ ? PMA_Message::success(
+ __('Your SQL query has been executed successfully')
+ )
+ : PMA_Message::error(__('Error'));
+ // $result should exist, regardless of $_message
+ $_type = $result ? 'success' : 'error';
+
+ if (isset($GLOBALS['ajax_request'])
+ && $GLOBALS['ajax_request'] == true
+ ) {
+ $response = PMA_Response::getInstance();
+ $response->isSuccess($_message->isSuccess());
+ $response->addJSON('message', $_message);
+ $response->addJSON(
+ 'sql_query', PMA_Util::getMessage(null, $sql_query)
+ );
+ exit;
+ }
+ }
+ if (! empty($warning_messages)) {
+ $_message = new PMA_Message;
+ $_message->addMessages($warning_messages);
+ $_message->isError(true);
+ if ($GLOBALS['ajax_request'] == true) {
+ $response = PMA_Response::getInstance();
+ $response->isSuccess(false);
+ $response->addJSON('message', $_message);
+ exit;
+ }
+ unset($warning_messages);
+ }
+
+ $response->addHTML(
+ PMA_Util::getMessage($_message, $sql_query, $_type)
+ );
+ unset($_message, $_type);
+}
+
+$url_params['goto']
+ = $url_params['back']
+ = 'tbl_operations.php';
+
+/**
+ * Get columns names
+ */
+$columns = $GLOBALS['dbi']->getColumns($GLOBALS['db'], $GLOBALS['table']);
+
+/**
+ * Displays the page
+ */
+$response->addHTML('<div id="boxContainer" data-box-width="300">');
+
+/**
+ * Order the table
+ */
+$hideOrderTable = false;
+// `ALTER TABLE ORDER BY` does not make sense for InnoDB tables that contain
+// a user-defined clustered index (PRIMARY KEY or NOT NULL UNIQUE index).
+// InnoDB always orders table rows according to such an index if one is present.
+if ($tbl_storage_engine == 'INNODB') {
+ include_once 'libraries/Index.class.php';
+ $indexes = PMA_Index::getFromTable($GLOBALS['table'], $GLOBALS['db']);
+ foreach ($indexes as $name => $idx) {
+ if ($name == 'PRIMARY') {
+ $hideOrderTable = true;
+ break;
+ } elseif (! $idx->getNonUnique()) {
+ $notNull = true;
+ foreach ($idx->getColumns() as $column) {
+ if ($column->getNull()) {
+ $notNull = false;
+ break;
+ }
+ }
+ if ($notNull) {
+ $hideOrderTable = true;
+ break;
+ }
+ }
+ }
+}
+if (! $hideOrderTable) {
+ $response->addHTML(PMA_getHtmlForOrderTheTable($columns));
+}
+
+/**
+ * Move table
+ */
+$response->addHTML(PMA_getHtmlForMoveTable());
+
+if (strstr($show_comment, '; InnoDB free') === false) {
+ if (strstr($show_comment, 'InnoDB free') === false) {
+ // only user entered comment
+ $comment = $show_comment;
+ } else {
+ // here we have just InnoDB generated part
+ $comment = '';
+ }
+} else {
+ // remove InnoDB comment from end, just the minimal part (*? is non greedy)
+ $comment = preg_replace('@; InnoDB free:.*?$@', '', $show_comment);
+}
+
+// PACK_KEYS: MyISAM or ISAM
+// DELAY_KEY_WRITE, CHECKSUM, : MyISAM only
+// AUTO_INCREMENT: MyISAM and InnoDB since 5.0.3, PBXT
+
+// Here should be version check for InnoDB, however it is supported
+// in >5.0.4, >4.1.12 and >4.0.11, so I decided not to
+// check for version
+
+$response->addHTML(
+ PMA_getTableOptionDiv(
+ $comment, $tbl_collation, $tbl_storage_engine,
+ $is_myisam_or_aria, $is_isam, $pack_keys,
+ $auto_increment,
+ (empty($delay_key_write) ? '0' : '1'),
+ ((isset($transactional) && $transactional == '0') ? '0' : '1'),
+ ((isset($page_checksum)) ? $page_checksum : ''),
+ $is_innodb, $is_pbxt, $is_aria, (empty($checksum) ? '0' : '1')
+ )
+);
+
+/**
+ * Copy table
+ */
+$response->addHTML(PMA_getHtmlForCopytable());
+
+/**
+ * Table maintenance
+ */
+$response->addHTML(
+ PMA_getHtmlForTableMaintenance(
+ $is_myisam_or_aria,
+ $is_innodb,
+ $is_berkeleydb,
+ $url_params
+ )
+);
+
+if (! (isset($db_is_information_schema) && $db_is_information_schema)) {
+ $truncate_table_url_params = array();
+ $drop_table_url_params = array();
+
+ if (! $tbl_is_view
+ && ! (isset($db_is_information_schema) && $db_is_information_schema)
+ ) {
+ $this_sql_query = 'TRUNCATE TABLE '
+ . PMA_Util::backquote($GLOBALS['table']);
+ $truncate_table_url_params = array_merge(
+ $url_params,
+ array(
+ 'sql_query' => $this_sql_query,
+ 'goto' => 'tbl_structure.php',
+ 'reload' => '1',
+ 'message_to_show' => sprintf(
+ __('Table %s has been emptied'),
+ htmlspecialchars($table)
+ ),
+ )
+ );
+ }
+ if (! (isset($db_is_information_schema) && $db_is_information_schema)) {
+ $this_sql_query = 'DROP TABLE '
+ . PMA_Util::backquote($GLOBALS['table']);
+ $drop_table_url_params = array_merge(
+ $url_params,
+ array(
+ 'sql_query' => $this_sql_query,
+ 'goto' => 'db_operations.php',
+ 'reload' => '1',
+ 'purge' => '1',
+ 'message_to_show' => sprintf(
+ ($tbl_is_view
+ ? __('View %s has been dropped')
+ : __('Table %s has been dropped')
+ ),
+ htmlspecialchars($table)
+ ),
+ // table name is needed to avoid running
+ // PMA_relationsCleanupDatabase() on the whole db later
+ 'table' => $GLOBALS['table'],
+ )
+ );
+ }
+ $response->addHTML(
+ PMA_getHtmlForDeleteDataOrTable(
+ $truncate_table_url_params,
+ $drop_table_url_params
+ )
+ );
+}
+
+if (PMA_Partition::havePartitioning()) {
+ $partition_names = PMA_Partition::getPartitionNames($db, $table);
+ // show the Partition maintenance section only if we detect a partition
+ if (! is_null($partition_names[0])) {
+ $response->addHTML(
+ PMA_getHtmlForPartitionMaintenance($partition_names, $url_params)
+ );
+ } // end if
+} // end if
+unset($partition_names);
+
+// Referential integrity check
+// The Referential integrity check was intended for the non-InnoDB
+// tables for which the relations are defined in pmadb
+// so I assume that if the current table is InnoDB, I don't display
+// this choice (InnoDB maintains integrity by itself)
+
+if ($cfgRelation['relwork'] && ! $is_innodb) {
+ $GLOBALS['dbi']->selectDb($GLOBALS['db']);
+ $foreign = PMA_getForeigners($GLOBALS['db'], $GLOBALS['table']);
+
+ if ($foreign) {
+ $response->addHTML(
+ PMA_getHtmlForReferentialIntegrityCheck($foreign, $url_params)
+ );
+ } // end if ($foreign)
+
+} // end if (!empty($cfg['Server']['relation']))
+
+$response->addHTML('</div>');
+
+?>
diff --git a/tbl_printview.php b/tbl_printview.php
new file mode 100644
index 0000000000..fd500eaf46
--- /dev/null
+++ b/tbl_printview.php
@@ -0,0 +1,73 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Print view for table
+ *
+ * @package PhpMyAdmin
+ */
+
+/**
+ * include the common file
+ */
+require_once 'libraries/common.inc.php';
+
+$response = PMA_Response::getInstance();
+$response->getHeader()->enablePrintView();
+
+require 'libraries/tbl_common.inc.php';
+
+// Check parameters
+
+if (! isset($the_tables) || ! is_array($the_tables)) {
+ $the_tables = array();
+}
+
+/**
+ * Gets the relations settings
+ */
+require_once 'libraries/transformations.lib.php';
+require_once 'libraries/Index.class.php';
+require_once 'libraries/tbl_printview.lib.php';
+
+$cfgRelation = PMA_getRelationsParam();
+
+/**
+ * Defines the url to return to in case of error in a sql statement
+ */
+if (strlen($table)) {
+ $err_url = 'tbl_sql.php?' . PMA_URL_getCommon($db, $table);
+} else {
+ $err_url = 'db_sql.php?' . PMA_URL_getCommon($db);
+}
+
+
+/**
+ * Selects the database
+ */
+$GLOBALS['dbi']->selectDb($db);
+
+/**
+ * Multi-tables printview
+ */
+if (isset($_POST['selected_tbl']) && is_array($_POST['selected_tbl'])) {
+ $the_tables = $_POST['selected_tbl'];
+} elseif (strlen($table)) {
+ $the_tables[] = $table;
+}
+
+$response->addHTML(PMA_getHtmlForTablesInfo($the_tables));
+$response->addHTML(
+ PMA_getHtmlForTablesDetail(
+ $the_tables, $db, $cfg, $cfgRelation,
+ isset($pk_array)? $pk_array: array(),
+ $cell_align_left
+ )
+);
+
+/**
+ * Displays the footer
+ */
+$response->addHTML(PMA_getHtmlForPrintViewFooter());
+
+exit;
+?>
diff --git a/tbl_relation.php b/tbl_relation.php
new file mode 100644
index 0000000000..9be5f6260c
--- /dev/null
+++ b/tbl_relation.php
@@ -0,0 +1,160 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Display table relations for viewing and editing
+ *
+ * includes phpMyAdmin relations and InnoDB relations
+ *
+ * @todo fix name handling: currently names with dots (.) are not properly handled
+ * for internal relations (but foreign keys relations are correct)
+ * @todo foreign key constraints require both fields being of equal type and size
+ * @todo check foreign fields to be from same type and size, all other makes no sense
+ * @todo if above todos are fullfilled we can add all fields meet requirements
+ * in the select dropdown
+ * @package PhpMyAdmin
+ */
+
+/**
+ * Gets some core libraries
+ */
+require_once 'libraries/common.inc.php';
+require_once 'libraries/index.lib.php';
+require_once 'libraries/tbl_relation.lib.php';
+
+$response = PMA_Response::getInstance();
+
+// Send table of column names to populate corresponding dropdowns depending
+// on the current selection
+if (isset($_REQUEST['getDropdownValues'])
+ && $_REQUEST['getDropdownValues'] === 'true'
+) {
+ PMA_sendHtmlForTableOrColumnDropdownList();
+}
+
+$header = $response->getHeader();
+$scripts = $header->getScripts();
+$scripts->addFile('tbl_relation.js');
+$scripts->addFile('indexes.js');
+
+/**
+ * Sets globals from $_POST
+ */
+$post_params = array(
+ 'destination_foreign_db',
+ 'destination_foreign_table',
+ 'destination_foreign_column',
+ 'display_field',
+ 'fields_name',
+ 'on_delete',
+ 'on_update'
+);
+
+foreach ($post_params as $one_post_param) {
+ if (isset($_POST[$one_post_param])) {
+ $GLOBALS[$one_post_param] = $_POST[$one_post_param];
+ }
+}
+
+/**
+ * Gets tables informations
+ */
+require_once 'libraries/tbl_info.inc.php';
+
+$options_array = array(
+ 'CASCADE' => 'CASCADE',
+ 'SET_NULL' => 'SET NULL',
+ 'NO_ACTION' => 'NO ACTION',
+ 'RESTRICT' => 'RESTRICT',
+);
+
+/**
+ * Gets the relation settings
+ */
+$cfgRelation = PMA_getRelationsParam();
+
+/**
+ * Updates
+ */
+if ($cfgRelation['relwork']) {
+ $existrel = PMA_getForeigners($db, $table, '', 'internal');
+}
+if (PMA_Util::isForeignKeySupported($tbl_storage_engine)) {
+ $existrel_foreign = PMA_getForeigners($db, $table, '', 'foreign');
+}
+if ($cfgRelation['displaywork']) {
+ $disp = PMA_getDisplayField($db, $table);
+}
+
+// will be used in the logic for internal relations and foreign keys:
+$multi_edit_columns_name = isset($_REQUEST['fields_name'])
+ ? $_REQUEST['fields_name']
+ : null;
+
+
+// u p d a t e s f o r I n t e r n a l r e l a t i o n s
+if (isset($_POST['destination_db']) && $cfgRelation['relwork']) {
+ PMA_handleUpdatesForInternalRelations(
+ $_POST['destination_db'], $multi_edit_columns_name,
+ $_POST['destination_table'],
+ $_POST['destination_column'], $cfgRelation, $db, $table,
+ isset($existrel) ? $existrel : null
+ );
+} // end if (updates for internal relations)
+
+$html_output = '';
+
+// u p d a t e s f o r f o r e i g n k e y s
+// (for now, one index name only; we keep the definitions if the
+// foreign db is not the same)
+if (isset($destination_foreign_db)) {
+ $html_output .= PMA_handleUpdatesForForeignKeys(
+ $destination_foreign_db,
+ $multi_edit_columns_name, $destination_foreign_table,
+ $destination_foreign_column, $options_array, $table,
+ isset($existrel_foreign) ? $existrel_foreign : null
+ );
+} // end if isset($destination_foreign)
+
+
+// U p d a t e s f o r d i s p l a y f i e l d
+if ($cfgRelation['displaywork'] && isset($display_field)) {
+ PMA_handleUpdateForDisplayField(
+ $disp, $display_field, $db, $table, $cfgRelation
+ );
+} // end if
+
+// If we did an update, refresh our data
+if (isset($_POST['destination_db']) && $cfgRelation['relwork']) {
+ $existrel = PMA_getForeigners($db, $table, '', 'internal');
+}
+if (isset($destination_foreign_db)
+ && PMA_Util::isForeignKeySupported($tbl_storage_engine)
+) {
+ $existrel_foreign = PMA_getForeigners($db, $table, '', 'foreign');
+}
+
+if ($cfgRelation['displaywork']) {
+ $disp = PMA_getDisplayField($db, $table);
+}
+
+
+/**
+ * Dialog
+ */
+// Now find out the columns of our $table
+// need to use PMA_DatabaseInterface::QUERY_STORE with $GLOBALS['dbi']->numRows()
+// in mysqli
+$columns = $GLOBALS['dbi']->getColumns($db, $table);
+
+// common form
+$html_output .= PMA_getHtmlForCommonForm(
+ $db, $table, $columns, $cfgRelation, $tbl_storage_engine, $existrel,
+ isset($existrel_foreign) ? $existrel_foreign : null, $options_array
+);
+
+if (PMA_Util::isForeignKeySupported($tbl_storage_engine)) {
+ $html_output .= PMA_getHtmlForDisplayIndexes();
+}
+// Render HTML output
+PMA_Response::getInstance()->addHTML($html_output);
+?>
diff --git a/tbl_replace.php b/tbl_replace.php
new file mode 100644
index 0000000000..29da6d612f
--- /dev/null
+++ b/tbl_replace.php
@@ -0,0 +1,402 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Manipulation of table data like inserting, replacing and updating
+ *
+ * Usally called as form action from tbl_change.php to insert or update table rows
+ *
+ * @todo 'edit_next' tends to not work as expected if used ...
+ * at least there is no order by it needs the original query
+ * and the row number and than replace the LIMIT clause
+ *
+ * @package PhpMyAdmin
+ */
+
+/**
+ * Gets some core libraries
+ */
+require_once 'libraries/common.inc.php';
+
+/**
+ * functions implementation for this script
+ */
+require_once 'libraries/insert_edit.lib.php';
+
+// Check parameters
+PMA_Util::checkParameters(array('db', 'table', 'goto'));
+
+$GLOBALS['dbi']->selectDb($GLOBALS['db']);
+
+/**
+ * Initializes some variables
+ */
+$goto_include = false;
+
+$response = PMA_Response::getInstance();
+$header = $response->getHeader();
+$scripts = $header->getScripts();
+$scripts->addFile('makegrid.js');
+// Needed for generation of Inline Edit anchors
+$scripts->addFile('sql.js');
+$scripts->addFile('indexes.js');
+$scripts->addFile('gis_data_editor.js');
+
+// check whether insert row mode, if so include tbl_change.php
+PMA_isInsertRow();
+
+$after_insert_actions = array('new_insert', 'same_insert', 'edit_next');
+if (isset($_REQUEST['after_insert'])
+ && in_array($_REQUEST['after_insert'], $after_insert_actions)
+) {
+ $url_params['after_insert'] = $_REQUEST['after_insert'];
+ if (isset($_REQUEST['where_clause'])) {
+ foreach ($_REQUEST['where_clause'] as $one_where_clause) {
+ if ($_REQUEST['after_insert'] == 'same_insert') {
+ $url_params['where_clause'][] = $one_where_clause;
+ } elseif ($_REQUEST['after_insert'] == 'edit_next') {
+ PMA_setSessionForEditNext($one_where_clause);
+ }
+ }
+ }
+}
+//get $goto_include for different cases
+$goto_include = PMA_getGotoInclude($goto_include);
+
+// Defines the url to return in case of failure of the query
+$err_url = PMA_getErrorUrl($url_params);
+
+/**
+ * Prepares the update/insert of a row
+ */
+list($loop_array, $using_key, $is_insert, $is_insertignore)
+ = PMA_getParamsForUpdateOrInsert();
+
+$query = array();
+$value_sets = array();
+$func_no_param = array(
+ 'CONNECTION_ID',
+ 'CURRENT_USER',
+ 'CURDATE',
+ 'CURTIME',
+ 'CURRENT_DATE',
+ 'CURRENT_TIME',
+ 'DATABASE',
+ 'LAST_INSERT_ID',
+ 'NOW',
+ 'PI',
+ 'RAND',
+ 'SYSDATE',
+ 'UNIX_TIMESTAMP',
+ 'USER',
+ 'UTC_DATE',
+ 'UTC_TIME',
+ 'UTC_TIMESTAMP',
+ 'UUID',
+ 'UUID_SHORT',
+ 'VERSION',
+);
+$func_optional_param = array(
+ 'RAND',
+ 'UNIX_TIMESTAMP',
+);
+
+$gis_from_text_functions = array(
+ 'GeomFromText',
+ 'GeomCollFromText',
+ 'LineFromText',
+ 'MLineFromText',
+ 'PointFromText',
+ 'MPointFromText',
+ 'PolyFromText',
+ 'MPolyFromText',
+);
+
+$gis_from_wkb_functions = array(
+ 'GeomFromWKB',
+ 'GeomCollFromWKB',
+ 'LineFromWKB',
+ 'MLineFromWKB',
+ 'PointFromWKB',
+ 'MPointFromWKB',
+ 'PolyFromWKB',
+ 'MPolyFromWKB',
+);
+
+// to create an object of PMA_File class
+require_once './libraries/File.class.php';
+
+$query_fields = array();
+foreach ($loop_array as $rownumber => $where_clause) {
+ // skip fields to be ignored
+ if (! $using_key && isset($_REQUEST['insert_ignore_' . $where_clause])) {
+ continue;
+ }
+
+ // Defines the SET part of the sql query
+ $query_values = array();
+
+ // Map multi-edit keys to single-level arrays, dependent on how we got the fields
+ $multi_edit_colummns
+ = isset($_REQUEST['fields']['multi_edit'][$rownumber])
+ ? $_REQUEST['fields']['multi_edit'][$rownumber]
+ : array();
+ $multi_edit_columns_name
+ = isset($_REQUEST['fields_name']['multi_edit'][$rownumber])
+ ? $_REQUEST['fields_name']['multi_edit'][$rownumber]
+ : null;
+ $multi_edit_columns_prev
+ = isset($_REQUEST['fields_prev']['multi_edit'][$rownumber])
+ ? $_REQUEST['fields_prev']['multi_edit'][$rownumber]
+ : null;
+ $multi_edit_funcs
+ = isset($_REQUEST['funcs']['multi_edit'][$rownumber])
+ ? $_REQUEST['funcs']['multi_edit'][$rownumber]
+ : null;
+ $multi_edit_salt
+ = isset($_REQUEST['salt']['multi_edit'][$rownumber])
+ ? $_REQUEST['salt']['multi_edit'][$rownumber]
+ :null;
+ $multi_edit_columns_type
+ = isset($_REQUEST['fields_type']['multi_edit'][$rownumber])
+ ? $_REQUEST['fields_type']['multi_edit'][$rownumber]
+ : null;
+ $multi_edit_columns_null
+ = isset($_REQUEST['fields_null']['multi_edit'][$rownumber])
+ ? $_REQUEST['fields_null']['multi_edit'][$rownumber]
+ : null;
+ $multi_edit_columns_null_prev
+ = isset($_REQUEST['fields_null_prev']['multi_edit'][$rownumber])
+ ? $_REQUEST['fields_null_prev']['multi_edit'][$rownumber]
+ : null;
+ $multi_edit_auto_increment
+ = isset($_REQUEST['auto_increment']['multi_edit'][$rownumber])
+ ? $_REQUEST['auto_increment']['multi_edit'][$rownumber]
+ : null;
+
+ // When a select field is nullified, it's not present in $_REQUEST
+ // so initialize it; this way, the foreach($multi_edit_colummns) will process it
+ foreach ($multi_edit_columns_name as $key => $val) {
+ if (! isset($multi_edit_colummns[$key])) {
+ $multi_edit_colummns[$key] = '';
+ }
+ }
+
+ // Iterate in the order of $multi_edit_columns_name,
+ // not $multi_edit_colummns, to avoid problems
+ // when inserting multiple entries
+ foreach ($multi_edit_columns_name as $key => $colummn_name) {
+ $current_value = $multi_edit_colummns[$key];
+ // Note: $key is an md5 of the fieldname. The actual fieldname is
+ // available in $multi_edit_columns_name[$key]
+
+ $file_to_insert = new PMA_File();
+ $file_to_insert->checkTblChangeForm($key, $rownumber);
+
+ $possibly_uploaded_val = $file_to_insert->getContent();
+
+ if ($file_to_insert->isError()) {
+ $message .= $file_to_insert->getError();
+ }
+ // delete $file_to_insert temporary variable
+ $file_to_insert->cleanUp();
+
+ $current_value = PMA_getCurrentValueForDifferentTypes(
+ $possibly_uploaded_val, $key, $multi_edit_columns_type,
+ $current_value, $multi_edit_auto_increment,
+ $rownumber, $multi_edit_columns_name, $multi_edit_columns_null,
+ $multi_edit_columns_null_prev, $is_insert,
+ $using_key, $where_clause, $table
+ );
+
+ $current_value_as_an_array = PMA_getCurrentValueAsAnArrayForMultipleEdit(
+ $multi_edit_colummns, $multi_edit_columns_name, $multi_edit_funcs,
+ $multi_edit_salt, $gis_from_text_functions, $current_value,
+ $gis_from_wkb_functions, $func_optional_param, $func_no_param, $key
+ );
+
+ list($query_values, $query_fields)
+ = PMA_getQueryValuesForInsertAndUpdateInMultipleEdit(
+ $multi_edit_columns_name, $multi_edit_columns_null, $current_value,
+ $multi_edit_columns_prev, $multi_edit_funcs, $is_insert,
+ $query_values, $query_fields, $current_value_as_an_array,
+ $value_sets, $key, $multi_edit_columns_null_prev
+ );
+ } //end of foreach
+
+ if (count($query_values) > 0) {
+ if ($is_insert) {
+ $value_sets[] = implode(', ', $query_values);
+ } else {
+ // build update query
+ $query[] = 'UPDATE ' . PMA_Util::backquote($GLOBALS['db'])
+ . '.' . PMA_Util::backquote($GLOBALS['table'])
+ . ' SET ' . implode(', ', $query_values)
+ . ' WHERE ' . $where_clause
+ . ($_REQUEST['clause_is_unique'] ? '' : ' LIMIT 1');
+ }
+ }
+} // end foreach ($loop_array as $where_clause)
+unset($multi_edit_columns_name, $multi_edit_columns_prev, $multi_edit_funcs,
+ $multi_edit_columns_type, $multi_edit_columns_null, $func_no_param,
+ $multi_edit_auto_increment, $current_value_as_an_array, $key, $current_value,
+ $loop_array, $where_clause, $using_key, $multi_edit_columns_null_prev);
+
+// Builds the sql query
+if ($is_insert && count($value_sets) > 0) {
+ $query = PMA_buildSqlQuery($is_insertignore, $query_fields, $value_sets);
+} elseif (empty($query)) {
+ // No change -> move back to the calling script
+ //
+ // Note: logic passes here for inline edit
+ $message = PMA_Message::success(__('No change'));
+ $active_page = $goto_include;
+ include '' . PMA_securePath($goto_include);
+ exit;
+}
+unset($multi_edit_colummns, $is_insertignore);
+
+/**
+ * Executes the sql query and get the result, then move back to the calling
+ * page
+ */
+list ($url_params, $total_affected_rows, $last_messages, $warning_messages,
+ $error_messages, $return_to_sql_query)
+ = PMA_executeSqlQuery($url_params, $query);
+
+if ($is_insert && count($value_sets) > 0) {
+ $message = PMA_Message::getMessageForInsertedRows($total_affected_rows);
+} else {
+ $message = PMA_Message::getMessageForAffectedRows($total_affected_rows);
+}
+
+$message->addMessages($last_messages, '<br />');
+
+if (! empty($warning_messages)) {
+ $message->addMessages($warning_messages, '<br />');
+ $message->isError(true);
+}
+if (! empty($error_messages)) {
+ $message->addMessages($error_messages);
+ $message->isError(true);
+}
+unset(
+ $error_messages, $warning_messages, $total_affected_rows,
+ $last_messages, $last_message
+);
+
+/**
+ * The following section only applies to grid editing.
+ * However, verifying isAjax() is not enough to ensure we are coming from
+ * grid editing. If we are coming from the Edit or Copy link in Browse mode,
+ * ajax_page_request is present in the POST parameters.
+ */
+if ($response->isAjax() && ! isset($_POST['ajax_page_request'])) {
+ /**
+ * If we are in grid editing, we need to process the relational and
+ * transformed fields, if they were edited. After that, output the correct
+ * link/transformed value and exit
+ *
+ * Logic taken from libraries/DisplayResults.class.php
+ */
+
+ if (isset($_REQUEST['rel_fields_list']) && $_REQUEST['rel_fields_list'] != '') {
+
+ $map = PMA_getForeigners($db, $table, '', 'both');
+
+ $relation_fields = array();
+ parse_str($_REQUEST['rel_fields_list'], $relation_fields);
+
+ // loop for each relation cell
+ foreach ($relation_fields as $cell_index => $curr_cell_rel_field) {
+ foreach ($curr_cell_rel_field as $relation_field => $relation_field_value) {
+ $where_comparison = "='" . $relation_field_value . "'";
+ $dispval = PMA_getDisplayValueForForeignTableColumn(
+ $where_comparison, $relation_field_value, $map, $relation_field
+ );
+
+ $extra_data['relations'][$cell_index] = PMA_getLinkForRelationalDisplayField(
+ $map, $relation_field, $where_comparison,
+ $dispval, $relation_field_value
+ );
+ }
+ } // end of loop for each relation cell
+ }
+ if (isset($_REQUEST['do_transformations'])
+ && $_REQUEST['do_transformations'] == true
+ ) {
+ include_once 'libraries/transformations.lib.php';
+ //if some posted fields need to be transformed, generate them here.
+ $mime_map = PMA_getMIME($db, $table);
+
+ if ($mime_map === false) {
+ $mime_map = array();
+ }
+ $edited_values = array();
+ parse_str($_REQUEST['transform_fields_list'], $edited_values);
+
+ if (! isset($extra_data)) {
+ $extra_data = array();
+ }
+ foreach ($mime_map as $transformation) {
+ $file = PMA_securePath($transformation['transformation']);
+ // if only an underscore in the file name, nothing to transform
+ if ($file != '_') {
+ $column_name = $transformation['column_name'];
+ $extra_data = PMA_transformEditedValues(
+ $db, $table, $transformation, $edited_values, $file,
+ $column_name, $extra_data
+ );
+ }
+ } // end of loop for each $mime_map
+ }
+
+ // Need to check the inline edited value can be truncated by MySQL
+ // without informing while saving
+ $column_name = $_REQUEST['fields_name']['multi_edit'][0][0];
+
+ PMA_verifyWhetherValueCanBeTruncatedAndAppendExtraData(
+ $db, $table, $column_name, $extra_data
+ );
+
+ /**Get the total row count of the table*/
+ $extra_data['row_count'] = PMA_Table::countRecords(
+ $_REQUEST['db'], $_REQUEST['table']
+ );
+
+ $extra_data['sql_query']
+ = PMA_Util::getMessage($message, $GLOBALS['display_query']);
+
+ $response = PMA_Response::getInstance();
+ $response->isSuccess($message->isSuccess());
+ $response->addJSON('message', $message);
+ $response->addJSON($extra_data);
+ exit;
+}
+
+if (! empty($return_to_sql_query)) {
+ $disp_query = $GLOBALS['sql_query'];
+ $disp_message = $message;
+ unset($message);
+ $GLOBALS['sql_query'] = $return_to_sql_query;
+}
+
+$scripts->addFile('tbl_change.js');
+
+$active_page = $goto_include;
+
+/**
+ * If user asked for "and then Insert another new row" we have to remove
+ * WHERE clause information so that tbl_change.php does not go back
+ * to the current record
+ */
+if (isset($_REQUEST['after_insert']) && 'new_insert' == $_REQUEST['after_insert']) {
+ unset($_REQUEST['where_clause']);
+}
+
+/**
+ * Load target page.
+ */
+require '' . PMA_securePath($goto_include);
+exit;
+
+?>
diff --git a/tbl_row_action.php b/tbl_row_action.php
new file mode 100644
index 0000000000..5801386df7
--- /dev/null
+++ b/tbl_row_action.php
@@ -0,0 +1,136 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * handle row specific actions like edit, delete, export
+ *
+ * @package PhpMyAdmin
+ */
+
+/**
+ *
+ */
+require_once 'libraries/common.inc.php';
+require_once 'libraries/mysql_charsets.inc.php';
+require_once 'libraries/sql.lib.php';
+
+if (isset($_REQUEST['submit_mult'])) {
+ $submit_mult = $_REQUEST['submit_mult'];
+ // workaround for IE problem:
+} elseif (isset($_REQUEST['submit_mult_delete_x'])) {
+ $submit_mult = 'row_delete';
+} elseif (isset($_REQUEST['submit_mult_change_x'])) {
+ $submit_mult = 'row_edit';
+} elseif (isset($_REQUEST['submit_mult_export_x'])) {
+ $submit_mult = 'row_export';
+}
+
+// If the 'Ask for confirmation' button was pressed, this can only come
+// from 'delete' mode, so we set it straight away.
+if (isset($_REQUEST['mult_btn'])) {
+ $submit_mult = 'row_delete';
+}
+
+switch($submit_mult) {
+case 'row_delete':
+case 'row_edit':
+case 'row_export':
+ // leave as is
+ break;
+
+case 'export':
+ $submit_mult = 'row_export';
+ break;
+
+case 'delete':
+ $submit_mult = 'row_delete';
+ break;
+
+default:
+case 'edit':
+ $submit_mult = 'row_edit';
+ break;
+}
+
+if (!empty($submit_mult)) {
+ switch($submit_mult) {
+ case 'row_edit':
+ // As we got the rows to be edited from the
+ // 'rows_to_delete' checkbox, we use the index of it as the
+ // indicating WHERE clause. Then we build the array which is used
+ // for the tbl_change.php script.
+ $where_clause = array();
+ foreach ($_REQUEST['rows_to_delete'] as $i => $i_where_clause) {
+ $where_clause[] = urldecode($i_where_clause);
+ }
+
+ $active_page = 'tbl_change.php';
+ include 'tbl_change.php';
+ break;
+
+ case 'row_export':
+ // Needed to allow SQL export
+ $single_table = true;
+
+ // As we got the rows to be exported from the
+ // 'rows_to_delete' checkbox, we use the index of it as the
+ // indicating WHERE clause. Then we build the array which is used
+ // for the tbl_change.php script.
+ $where_clause = array();
+ foreach ($_REQUEST['rows_to_delete'] as $i => $i_where_clause) {
+ $where_clause[] = urldecode($i_where_clause);
+ }
+
+ $active_page = 'tbl_export.php';
+ include 'tbl_export.php';
+ break;
+
+ case 'row_delete':
+ default:
+ $action = 'tbl_row_action.php';
+ $err_url = 'tbl_row_action.php'
+ . PMA_URL_getCommon($GLOBALS['url_params']);
+ if (! isset($_REQUEST['mult_btn'])) {
+ $original_sql_query = $sql_query;
+ if (! empty($url_query)) {
+ $original_url_query = $url_query;
+ }
+ }
+ include 'libraries/mult_submits.inc.php';
+ $_url_params = $GLOBALS['url_params'];
+ $_url_params['goto'] = 'tbl_sql.php';
+ $url_query = PMA_URL_getCommon($_url_params);
+
+
+ /**
+ * Show result of multi submit operation
+ */
+ // sql_query is not set when user does not confirm multi-delete
+ if ((! empty($submit_mult) || isset($_REQUEST['mult_btn']))
+ && ! empty($sql_query)
+ ) {
+ $disp_message = __('Your SQL query has been executed successfully');
+ $disp_query = $sql_query;
+ }
+
+ if (isset($original_sql_query)) {
+ $sql_query = $original_sql_query;
+ }
+
+ if (isset($original_url_query)) {
+ $url_query = $original_url_query;
+ }
+
+ $active_page = 'sql.php';
+ /**
+ * Parse and analyze the query
+ */
+ include_once 'libraries/parse_analyze.inc.php';
+
+ PMA_executeQueryAndSendQueryResponse(
+ $analyzed_sql_results, false, $db, $table, null, null, null, false, null,
+ null, null, null, $goto, $pmaThemeImage, null, null, null, $sql_query,
+ null, null
+ );
+ }
+}
+?>
diff --git a/tbl_select.php b/tbl_select.php
new file mode 100644
index 0000000000..f191d592a5
--- /dev/null
+++ b/tbl_select.php
@@ -0,0 +1,71 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Handles table search tab
+ *
+ * display table search form, create SQL query from form data
+ * and call PMA_executeQueryAndSendQueryResponse() to execute it
+ *
+ * @package PhpMyAdmin
+ */
+
+/**
+ * Gets some core libraries
+ */
+require_once 'libraries/common.inc.php';
+require_once 'libraries/mysql_charsets.inc.php';
+require_once 'libraries/TableSearch.class.php';
+require_once 'libraries/sql.lib.php';
+
+$response = PMA_Response::getInstance();
+$header = $response->getHeader();
+$scripts = $header->getScripts();
+$scripts->addFile('makegrid.js');
+$scripts->addFile('sql.js');
+$scripts->addFile('tbl_select.js');
+$scripts->addFile('tbl_change.js');
+$scripts->addFile('jquery/jquery-ui-timepicker-addon.js');
+$scripts->addFile('gis_data_editor.js');
+
+$table_search = new PMA_TableSearch($db, $table, "normal");
+
+/**
+ * No selection criteria received -> display the selection form
+ */
+if (! isset($_POST['columnsToDisplay']) && ! isset($_POST['displayAllColumns'])) {
+ // Gets some core libraries
+ include_once 'libraries/tbl_common.inc.php';
+ //$err_url = 'tbl_select.php' . $err_url;
+ $url_query .= '&amp;goto=tbl_select.php&amp;back=tbl_select.php';
+ /**
+ * Gets table's information
+ */
+ include_once 'libraries/tbl_info.inc.php';
+
+ if (! isset($goto)) {
+ $goto = $GLOBALS['cfg']['DefaultTabTable'];
+ }
+ // Defines the url to return to in case of error in the next sql statement
+ $err_url = $goto . '?' . PMA_URL_getCommon($db, $table);
+ // Displays the table search form
+ $response->addHTML($table_search->getSecondaryTabs());
+ $response->addHTML($table_search->getSelectionForm($goto));
+
+} else {
+ /**
+ * Selection criteria have been submitted -> do the work
+ */
+ $sql_query = $table_search->buildSqlQuery();
+
+ /**
+ * Parse and analyze the query
+ */
+ include_once 'libraries/parse_analyze.inc.php';
+
+ PMA_executeQueryAndSendQueryResponse(
+ $analyzed_sql_results, false, $db, $table, null, null, null, false, null,
+ null, null, null, $goto, $pmaThemeImage, null, null, null, $sql_query,
+ null, null
+ );
+}
+?>
diff --git a/tbl_sql.php b/tbl_sql.php
new file mode 100644
index 0000000000..0035840199
--- /dev/null
+++ b/tbl_sql.php
@@ -0,0 +1,51 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Table SQL executor
+ *
+ * @package PhpMyAdmin
+ */
+
+/**
+ *
+ */
+require_once 'libraries/common.inc.php';
+
+/**
+ * Runs common work
+ */
+$response = PMA_Response::getInstance();
+$header = $response->getHeader();
+$scripts = $header->getScripts();
+$scripts->addFile('makegrid.js');
+$scripts->addFile('sql.js');
+
+require 'libraries/tbl_common.inc.php';
+$url_query .= '&amp;goto=tbl_sql.php&amp;back=tbl_sql.php';
+
+require_once 'libraries/sql_query_form.lib.php';
+
+$err_url = 'tbl_sql.php' . $err_url;
+// After a syntax error, we return to this script
+// with the typed query in the textarea.
+$goto = 'tbl_sql.php';
+$back = 'tbl_sql.php';
+
+/**
+ * Get table information
+ */
+require_once 'libraries/tbl_info.inc.php';
+
+/**
+ * Query box, bookmark, insert data from textfile
+ */
+$response->addHTML(
+ PMA_getHtmlForSqlQueryForm(
+ true, false,
+ isset($_REQUEST['delimiter'])
+ ? htmlspecialchars($_REQUEST['delimiter'])
+ : ';'
+ )
+);
+
+?>
diff --git a/tbl_structure.php b/tbl_structure.php
new file mode 100644
index 0000000000..b40c57dabc
--- /dev/null
+++ b/tbl_structure.php
@@ -0,0 +1,164 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Displays table structure infos like columns, indexes, size, rows
+ * and allows manipulation of indexes and columns
+ *
+ * @package PhpMyAdmin
+ */
+
+/**
+ *
+ */
+require_once 'libraries/common.inc.php';
+require_once 'libraries/mysql_charsets.inc.php';
+
+/**
+ * Function implementations for this script
+ */
+require_once 'libraries/structure.lib.php';
+require_once 'libraries/index.lib.php';
+require_once 'libraries/sql.lib.php';
+require_once 'libraries/bookmark.lib.php';
+
+$response = PMA_Response::getInstance();
+$header = $response->getHeader();
+$scripts = $header->getScripts();
+$scripts->addFile('tbl_structure.js');
+$scripts->addFile('indexes.js');
+
+/**
+ * Handle column moving
+ */
+if (isset($_REQUEST['move_columns'])
+ && is_array($_REQUEST['move_columns'])
+ && $response->isAjax()
+) {
+ PMA_moveColumns($db, $table);
+ exit;
+}
+
+/**
+ * A click on Change has been made for one column
+ */
+if (isset($_REQUEST['change_column'])) {
+ PMA_displayHtmlForColumnChange($db, $table, null, 'tbl_structure.php');
+ exit;
+}
+/**
+ * Modifications have been submitted -> updates the table
+ */
+if (isset($_REQUEST['do_save_data'])) {
+ $regenerate = PMA_updateColumns($db, $table);
+ if ($regenerate) {
+ // This happens when updating failed
+ // @todo: do something appropriate
+ } else {
+ // continue to show the table's structure
+ unset($_REQUEST['selected']);
+ }
+}
+
+/**
+ * handle multiple field commands if required
+ *
+ * submit_mult_*_x comes from IE if <input type="img" ...> is used
+ */
+$submit_mult = PMA_getMultipleFieldCommandType();
+
+if (! empty($submit_mult)) {
+ if (isset($_REQUEST['selected_fld'])) {
+ if ($submit_mult == 'browse') {
+ // browsing the table displaying only selected columns
+ PMA_displayTableBrowseForSelectedColumns(
+ $db, $table, $goto, $pmaThemeImage
+ );
+ } else {
+ // handle multiple field commands
+ // handle confirmation of deleting multiple columns
+ $action = 'tbl_structure.php';
+ include 'libraries/mult_submits.inc.php';
+ /**
+ * if $submit_mult == 'change', execution will have stopped
+ * at this point
+ */
+
+ if (empty($message)) {
+ $message = PMA_Message::success();
+ }
+ }
+ } else {
+ $response = PMA_Response::getInstance();
+ $response->isSuccess(false);
+ $response->addJSON('message', __('No column selected.'));
+ }
+}
+
+/**
+ * Gets the relation settings
+ */
+$cfgRelation = PMA_getRelationsParam();
+
+/**
+ * Runs common work
+ */
+require_once 'libraries/tbl_common.inc.php';
+$url_query .= '&amp;goto=tbl_structure.php&amp;back=tbl_structure.php';
+$url_params['goto'] = 'tbl_structure.php';
+$url_params['back'] = 'tbl_structure.php';
+
+// Check column names for MySQL reserved words
+$reserved_word_column_messages = PMA_getReservedWordColumnNameMessages($db, $table);
+$response->addHTML($reserved_word_column_messages);
+
+/**
+ * Prepares the table structure display
+ */
+
+
+/**
+ * Gets tables informations
+ */
+require_once 'libraries/tbl_info.inc.php';
+
+require_once 'libraries/Index.class.php';
+
+// 2. Gets table keys and retains them
+// @todo should be: $server->db($db)->table($table)->primary()
+$primary = PMA_Index::getPrimary($table, $db);
+
+$columns_with_unique_index = PMA_getColumnsWithUniqueIndex($db, $table);
+
+// 3. Get fields
+$fields = (array) $GLOBALS['dbi']->getColumns($db, $table, null, true);
+
+// Get more complete field information
+// For now, this is done just for MySQL 4.1.2+ new TIMESTAMP options
+// but later, if the analyser returns more information, it
+// could be executed for any MySQL version and replace
+// the info given by SHOW FULL COLUMNS FROM.
+//
+// We also need this to correctly learn if a TIMESTAMP is NOT NULL, since
+// SHOW FULL COLUMNS or INFORMATION_SCHEMA incorrectly says NULL
+// and SHOW CREATE TABLE says NOT NULL (tested
+// in MySQL 4.0.25 and 5.0.21, http://bugs.mysql.com/20910).
+
+$show_create_table = $GLOBALS['dbi']->fetchValue(
+ 'SHOW CREATE TABLE ' . PMA_Util::backquote($db) . '.'
+ . PMA_Util::backquote($table),
+ 0, 1
+);
+$analyzed_sql = PMA_SQP_analyze(PMA_SQP_parse($show_create_table));
+
+/**
+ * prepare table infos
+ */
+// action titles (image or string)
+$titles = PMA_getActionTitlesArray();
+
+// hidden action titles (image and string)
+$hidden_titles = PMA_getHiddenTitlesArray();
+
+//display table structure
+require_once 'libraries/display_structure.inc.php';
+?>
diff --git a/tbl_tracking.php b/tbl_tracking.php
new file mode 100644
index 0000000000..2abd935c73
--- /dev/null
+++ b/tbl_tracking.php
@@ -0,0 +1,153 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Table tracking page
+ *
+ * @package PhpMyAdmin
+ */
+
+// Run common work
+require_once './libraries/common.inc.php';
+
+require_once './libraries/tbl_tracking.lib.php';
+
+define('TABLE_MAY_BE_ABSENT', true);
+require './libraries/tbl_common.inc.php';
+$url_query .= '&amp;goto=tbl_tracking.php&amp;back=tbl_tracking.php';
+$url_params['goto'] = 'tbl_tracking.php';
+$url_params['back'] = 'tbl_tracking.php';
+
+// Init vars for tracking report
+if (isset($_REQUEST['report']) || isset($_REQUEST['report_export'])) {
+ $data = PMA_Tracker::getTrackedData(
+ $_REQUEST['db'], $_REQUEST['table'], $_REQUEST['version']
+ );
+
+ $selection_schema = false;
+ $selection_data = false;
+ $selection_both = false;
+
+ if (! isset($_REQUEST['logtype'])) {
+ $_REQUEST['logtype'] = 'schema_and_data';
+ }
+ if ($_REQUEST['logtype'] == 'schema') {
+ $selection_schema = true;
+ } elseif ($_REQUEST['logtype'] == 'data') {
+ $selection_data = true;
+ } else {
+ $selection_both = true;
+ }
+ if (! isset($_REQUEST['date_from'])) {
+ $_REQUEST['date_from'] = $data['date_from'];
+ }
+ if (! isset($_REQUEST['date_to'])) {
+ $_REQUEST['date_to'] = $data['date_to'];
+ }
+ if (! isset($_REQUEST['users'])) {
+ $_REQUEST['users'] = '*';
+ }
+ $filter_ts_from = strtotime($_REQUEST['date_from']);
+ $filter_ts_to = strtotime($_REQUEST['date_to']);
+ $filter_users = array_map('trim', explode(',', $_REQUEST['users']));
+}
+
+// Prepare export
+if (isset($_REQUEST['report_export'])) {
+ $entries = PMA_getEntries($data, $filter_ts_from, $filter_ts_to, $filter_users);
+}
+
+// Export as file download
+if (isset($_REQUEST['report_export'])
+ && $_REQUEST['export_type'] == 'sqldumpfile'
+) {
+ PMA_exportAsFileDownload($entries);
+}
+
+$html = '<br />';
+
+/**
+ * Actions
+ */
+
+// Create tracking version
+if (isset($_REQUEST['submit_create_version'])) {
+ PMA_createTrackingVersion();
+}
+
+// Deactivate tracking
+if (isset($_REQUEST['submit_deactivate_now'])) {
+ PMA_deactivateTracking();
+}
+
+// Activate tracking
+if (isset($_REQUEST['submit_activate_now'])) {
+ PMA_activateTracking();
+}
+
+// Export as SQL execution
+if (isset($_REQUEST['report_export']) && $_REQUEST['export_type'] == 'execution') {
+ $sql_result = PMA_exportAsSQLExecution($entries);
+}
+
+// Export as SQL dump
+if (isset($_REQUEST['report_export']) && $_REQUEST['export_type'] == 'sqldump') {
+ PMA_exportAsSQLDump($entries);
+}
+
+/*
+ * Schema snapshot
+ */
+if (isset($_REQUEST['snapshot'])) {
+ $html .= PMA_getHtmlForSchemaSnapshot($url_query);
+}
+// end of snapshot report
+
+/*
+ * Tracking report
+ */
+if (isset($_REQUEST['report'])
+ && (isset($_REQUEST['delete_ddlog']) || isset($_REQUEST['delete_dmlog']))
+) {
+ PMA_deleteTrackingReportRows($data);
+}
+
+if (isset($_REQUEST['report']) || isset($_REQUEST['report_export'])) {
+ $html .= PMA_getHtmlForTrackingReport(
+ $url_query, $data, $url_params, $selection_schema, $selection_data,
+ $selection_both, $filter_ts_to, $filter_ts_from, $filter_users
+ );
+} // end of report
+
+
+/*
+ * List selectable tables
+ */
+$selectable_tables_sql_result = PMA_getSQLResultForSelectableTables();
+if ($GLOBALS['dbi']->numRows($selectable_tables_sql_result) > 0) {
+ $html .= PMA_getHtmlForSelectableTables(
+ $selectable_tables_sql_result, $url_query
+ );
+}
+$html .= '<br />';
+
+/*
+ * List versions of current table
+ */
+$sql_result = PMA_getListOfVersionsOfTable();
+$last_version = PMA_getTableLastVersionNumber($sql_result);
+if ($last_version > 0) {
+ $html .= PMA_getHtmlForTableVersionDetails(
+ $sql_result, $last_version, $url_params, $url_query
+ );
+}
+
+$html .= PMA_getHtmlForDataDefinitionAndManipulationStatements(
+ $url_query, $last_version
+);
+
+$html .= '<br class="clearfloat"/>';
+
+$response = PMA_Response::getInstance();
+$response->addHTML($html);
+
+?>
diff --git a/tbl_triggers.php b/tbl_triggers.php
new file mode 100644
index 0000000000..9c87e676dd
--- /dev/null
+++ b/tbl_triggers.php
@@ -0,0 +1,10 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Triggers management.
+ *
+ * @package PhpMyAdmin
+ */
+
+require_once './db_triggers.php';
+?>
diff --git a/tbl_zoom_select.php b/tbl_zoom_select.php
new file mode 100644
index 0000000000..7f4ceea329
--- /dev/null
+++ b/tbl_zoom_select.php
@@ -0,0 +1,163 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Handles table zoom search tab
+ *
+ * display table zoom search form, create SQL queries from form data
+ *
+ * @package PhpMyAdmin
+ */
+
+/**
+ * Gets some core libraries
+ */
+require_once './libraries/common.inc.php';
+require_once './libraries/mysql_charsets.inc.php';
+require_once './libraries/TableSearch.class.php';
+require_once './libraries/tbl_info.inc.php';
+
+$response = PMA_Response::getInstance();
+$header = $response->getHeader();
+$scripts = $header->getScripts();
+$scripts->addFile('makegrid.js');
+$scripts->addFile('sql.js');
+$scripts->addFile('jqplot/jquery.jqplot.js');
+$scripts->addFile('jqplot/plugins/jqplot.canvasTextRenderer.js');
+$scripts->addFile('jqplot/plugins/jqplot.canvasAxisLabelRenderer.js');
+$scripts->addFile('jqplot/plugins/jqplot.dateAxisRenderer.js');
+$scripts->addFile('jqplot/plugins/jqplot.highlighter.js');
+$scripts->addFile('jqplot/plugins/jqplot.cursor.js');
+$scripts->addFile('canvg/canvg.js');
+$scripts->addFile('jquery/jquery-ui-timepicker-addon.js');
+$scripts->addFile('tbl_zoom_plot_jqplot.js');
+
+$table_search = new PMA_TableSearch($db, $table, "zoom");
+
+/**
+ * Handle AJAX request for data row on point select
+ * @var post_params Object containing parameters for the POST request
+ */
+
+if (isset($_REQUEST['get_data_row']) && $_REQUEST['get_data_row'] == true) {
+ $extra_data = array();
+ $row_info_query = 'SELECT * FROM `' . $_REQUEST['db'] . '`.`'
+ . $_REQUEST['table'] . '` WHERE ' . $_REQUEST['where_clause'];
+ $result = $GLOBALS['dbi']->query(
+ $row_info_query . ";", null, PMA_DatabaseInterface::QUERY_STORE
+ );
+ $fields_meta = $GLOBALS['dbi']->getFieldsMeta($result);
+ while ($row = $GLOBALS['dbi']->fetchAssoc($result)) {
+ // for bit fields we need to convert them to printable form
+ $i = 0;
+ foreach ($row as $col => $val) {
+ if ($fields_meta[$i]->type == 'bit') {
+ $row[$col] = PMA_Util::printableBitValue(
+ $val, $fields_meta[$i]->length
+ );
+ }
+ $i++;
+ }
+ $extra_data['row_info'] = $row;
+ }
+ PMA_Response::getInstance()->addJSON($extra_data);
+ exit;
+}
+
+/**
+ * Handle AJAX request for changing field information
+ * (value,collation,operators,field values) in input form
+ * @var post_params Object containing parameters for the POST request
+ */
+
+if (isset($_REQUEST['change_tbl_info']) && $_REQUEST['change_tbl_info'] == true) {
+ $response = PMA_Response::getInstance();
+ $field = $_REQUEST['field'];
+ if ($field == 'pma_null') {
+ $response->addJSON('field_type', '');
+ $response->addJSON('field_collation', '');
+ $response->addJSON('field_operators', '');
+ $response->addJSON('field_value', '');
+ exit;
+ }
+ $key = array_search($field, $table_search->getColumnNames());
+ $properties = $table_search->getColumnProperties($_REQUEST['it'], $key);
+ $response->addJSON('field_type', $properties['type']);
+ $response->addJSON('field_collation', $properties['collation']);
+ $response->addJSON('field_operators', $properties['func']);
+ $response->addJSON('field_value', $properties['value']);
+ exit;
+}
+
+// Gets some core libraries
+require_once './libraries/tbl_common.inc.php';
+$url_query .= '&amp;goto=tbl_select.php&amp;back=tbl_select.php';
+
+// Gets tables informations
+require_once './libraries/tbl_info.inc.php';
+
+if (! isset($goto)) {
+ $goto = $GLOBALS['cfg']['DefaultTabTable'];
+}
+// Defines the url to return to in case of error in the next sql statement
+$err_url = $goto . '?' . PMA_URL_getCommon($db, $table);
+
+//Set default datalabel if not selected
+if ( !isset($_POST['zoom_submit']) || $_POST['dataLabel'] == '') {
+ $dataLabel = PMA_getDisplayField($db, $table);
+} else {
+ $dataLabel = $_POST['dataLabel'];
+}
+
+// Displays the zoom search form
+$response->addHTML($table_search->getSecondaryTabs());
+$response->addHTML($table_search->getSelectionForm($goto, $dataLabel));
+
+/*
+ * Handle the input criteria and generate the query result
+ * Form for displaying query results
+ */
+if (isset($_POST['zoom_submit'])
+ && $_POST['criteriaColumnNames'][0] != 'pma_null'
+ && $_POST['criteriaColumnNames'][1] != 'pma_null'
+ && $_POST['criteriaColumnNames'][0] != $_POST['criteriaColumnNames'][1]
+) {
+ //Query generation part
+ $sql_query = $table_search->buildSqlQuery();
+ $sql_query .= ' LIMIT ' . $_POST['maxPlotLimit'];
+
+ //Query execution part
+ $result = $GLOBALS['dbi']->query(
+ $sql_query . ";", null, PMA_DatabaseInterface::QUERY_STORE
+ );
+ $fields_meta = $GLOBALS['dbi']->getFieldsMeta($result);
+ while ($row = $GLOBALS['dbi']->fetchAssoc($result)) {
+ //Need a row with indexes as 0,1,2 for the getUniqueCondition
+ // hence using a temporary array
+ $tmpRow = array();
+ foreach ($row as $val) {
+ $tmpRow[] = $val;
+ }
+ //Get unique conditon on each row (will be needed for row update)
+ $uniqueCondition = PMA_Util::getUniqueCondition(
+ $result, count($table_search->getColumnNames()), $fields_meta, $tmpRow,
+ true
+ );
+ //Append it to row array as where_clause
+ $row['where_clause'] = $uniqueCondition[0];
+
+ $tmpData = array(
+ $_POST['criteriaColumnNames'][0] =>
+ $row[$_POST['criteriaColumnNames'][0]],
+ $_POST['criteriaColumnNames'][1] =>
+ $row[$_POST['criteriaColumnNames'][1]],
+ 'where_clause' => $uniqueCondition[0]
+ );
+ $tmpData[$dataLabel] = ($dataLabel) ? $row[$dataLabel] : '';
+ $data[] = $tmpData;
+ }
+ unset($tmpData);
+
+ //Displays form for point data and scatter plot
+ $response->addHTML($table_search->getZoomResultsForm($goto, $data));
+}
+?>
diff --git a/themes.php b/themes.php
new file mode 100644
index 0000000000..aed65f97dd
--- /dev/null
+++ b/themes.php
@@ -0,0 +1,32 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Displays list of themes.
+ *
+ * @package PhpMyAdmin
+ */
+
+/**
+ * get some globals
+ */
+require './libraries/common.inc.php';
+$response = PMA_Response::getInstance();
+$response->getFooter()->setMinimal();
+$header = $response->getHeader();
+$header->setBodyId('bodythemes');
+$header->setTitle('phpMyAdmin - ' . __('Theme'));
+$header->disableMenu();
+
+$hash = '#pma_' . preg_replace('/([0-9]*)\.([0-9]*)\..*/', '\1_\2', PMA_VERSION);
+$url = PMA_linkURL('http://www.phpmyadmin.net/home_page/themes.php') . $hash;
+$output = '<h1>phpMyAdmin - ' . __('Theme') . '</h1>';
+$output .= '<p>';
+$output .= '<a href="' . $url . '" class="_blank">';
+$output .= __('Get more themes!');
+$output .= '</a>';
+$output .= '</p>';
+$output .= $_SESSION['PMA_Theme_Manager']->getPrintPreviews();
+
+$response->addHTML($output);
+
+?>
diff --git a/themes/dot.gif b/themes/dot.gif
new file mode 100644
index 0000000000..35d42e808f
--- /dev/null
+++ b/themes/dot.gif
Binary files differ
diff --git a/themes/original/css/common.css.php b/themes/original/css/common.css.php
new file mode 100644
index 0000000000..1483a27181
--- /dev/null
+++ b/themes/original/css/common.css.php
@@ -0,0 +1,2564 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Common styles for the original theme
+ *
+ * @package PhpMyAdmin-theme
+ * @subpackage Original
+ */
+
+// unplanned execution path
+if (! defined('PMA_MINIMUM_COMMON') && ! defined('TESTSUITE')) {
+ exit();
+}
+?>
+/******************************************************************************/
+
+/* general tags */
+html {
+ font-size: <?php echo $_SESSION['PMA_Theme']->getFontSize(); ?>
+}
+
+input,
+select,
+textarea {
+ font-size: 1em;
+}
+
+
+body {
+<?php if (! empty($GLOBALS['cfg']['FontFamily'])) { ?>
+ font-family: <?php echo $GLOBALS['cfg']['FontFamily']; ?>;
+<?php } ?>
+ padding: 0;
+ margin: 0;
+ margin-<?php echo $left; ?>: 240px;
+ color: <?php echo $GLOBALS['cfg']['MainColor']; ?>;
+ background: <?php echo $GLOBALS['cfg']['MainBackground']; ?>;
+}
+
+body#loginform {
+ margin: 0;
+}
+
+#page_content {
+ margin: 0 .5em;
+}
+
+<?php if (! empty($GLOBALS['cfg']['FontFamilyFixed'])) { ?>
+textarea, tt, pre, code {
+ font-family: <?php echo $GLOBALS['cfg']['FontFamilyFixed']; ?>;
+}
+<?php } ?>
+h1 {
+ font-size: 140%;
+ font-weight: bold;
+}
+
+h2 {
+ font-size: 120%;
+ font-weight: bold;
+}
+
+h3 {
+ font-weight: bold;
+}
+
+a, a:link,
+a:visited,
+a:active {
+ text-decoration: none;
+ color: #0000FF;
+ cursor: pointer;
+}
+
+a:hover {
+ text-decoration: underline;
+ color: #FF0000;
+}
+
+dfn {
+ font-style: normal;
+}
+
+dfn:hover {
+ font-style: normal;
+ cursor: help;
+}
+
+th {
+ font-weight: bold;
+ color: <?php echo $GLOBALS['cfg']['ThColor']; ?>;
+ background: <?php echo $GLOBALS['cfg']['ThBackground']; ?>;
+}
+
+a img {
+ border: 0;
+}
+
+hr {
+ color: <?php echo $GLOBALS['cfg']['MainColor']; ?>;
+ background-color: <?php echo $GLOBALS['cfg']['MainColor']; ?>;
+ border: 0;
+ height: 1px;
+}
+
+form {
+ padding: 0;
+ margin: 0;
+ display: inline;
+}
+
+textarea {
+ overflow: visible;
+ height: <?php echo ceil($GLOBALS['cfg']['TextareaRows'] * 1.2); ?>em;
+}
+
+textarea.char {
+ height: <?php echo ceil($GLOBALS['cfg']['CharTextareaRows'] * 1.2); ?>em;
+}
+
+fieldset {
+ margin-top: 1em;
+ border: <?php echo $GLOBALS['cfg']['MainColor']; ?> solid 1px;
+ padding: 0.5em;
+ background: <?php echo $GLOBALS['cfg']['BgOne']; ?>;
+}
+
+fieldset fieldset {
+ margin: 0.8em;
+}
+
+fieldset legend {
+ font-weight: bold;
+ color: #444444;
+ background-color: <?php echo 'OPERA' != PMA_USR_BROWSER_AGENT ? 'transparent' : $GLOBALS['cfg']['BgOne']; ?>;
+}
+
+.some-margin {
+ margin: .5em;
+ margin-top: 1em;
+}
+
+/* buttons in some browsers (eg. Konqueror) are block elements,
+ this breaks design */
+button {
+ display: inline;
+}
+
+table caption,
+table th,
+table td {
+ padding: 0.1em 0.5em 0.1em 0.5em;
+ margin: 0.1em;
+ vertical-align: top;
+}
+
+img,
+input,
+select,
+button {
+ vertical-align: middle;
+}
+
+/******************************************************************************/
+/* classes */
+.clearfloat {
+ clear: both;
+}
+
+.floatleft {
+ float: <?php echo $left; ?>;
+ margin-<?php echo $right; ?>: 1em;
+}
+
+table.nospacing {
+ border-spacing: 0;
+}
+
+table.nopadding tr th, table.nopadding tr td {
+ padding: 0;
+}
+
+th.left, td.left {
+ text-align: left;
+}
+
+th.center, td.center {
+ text-align: center;
+}
+
+th.right, td.right {
+ text-align: right;
+}
+
+tr.vtop, th.vtop, td.vtop {
+ vertical-align: top;
+}
+
+tr.vmiddle, th.vmiddle, td.vmiddle {
+ vertical-align: middle;
+}
+
+tr.vbottom, th.vbottom, td.vbottom {
+ vertical-align: bottom;
+}
+
+.paddingtop {
+ padding-top: 1em;
+}
+
+div.tools {
+ border: 1px solid #000000;
+ padding: 0.2em;
+}
+
+div.tools,
+fieldset.tblFooters {
+ margin-top: 0;
+ margin-bottom: 0.5em;
+ /* avoid a thick line since this should be used under another fieldset */
+ border-top: 0;
+ text-align: <?php echo $right; ?>;
+ float: none;
+ clear: both;
+}
+
+div.null_div {
+ height: 20px;
+ text-align: center;
+ font-style:normal;
+ min-width:50px;
+}
+
+fieldset .formelement {
+ float: <?php echo $left; ?>;
+ margin-<?php echo $right; ?>: 0.5em;
+ /* IE */
+ white-space: nowrap;
+}
+
+/* revert for Gecko */
+fieldset div[class=formelement] {
+ white-space: normal;
+}
+
+button.mult_submit {
+ border: none;
+ background-color: transparent;
+}
+
+/* odd items 1,3,5,7,... */
+table tr.odd th,
+.odd {
+ background: <?php echo $GLOBALS['cfg']['BgOne']; ?>;
+}
+
+/* even items 2,4,6,8,... */
+table tr.even th,
+.even {
+ background: <?php echo $GLOBALS['cfg']['BgTwo']; ?>;
+}
+
+/* odd table rows 1,3,5,7,... */
+table tr.odd th,
+table tr.odd,
+table tr.even th,
+table tr.even {
+ text-align: <?php echo $left; ?>;
+}
+
+<?php if ($GLOBALS['cfg']['BrowseMarkerEnable']) { ?>
+/* marked table rows */
+td.marked,
+table tr.marked td,
+table tr.marked th,
+table tr.marked {
+ background: <?php echo $GLOBALS['cfg']['BrowseMarkerBackground']; ?>;
+ color: <?php echo $GLOBALS['cfg']['BrowseMarkerColor']; ?>;
+}
+<?php } ?>
+
+<?php if ($GLOBALS['cfg']['BrowsePointerEnable']) { ?>
+/* hovered items */
+.odd:hover,
+.even:hover,
+.hover {
+ background: <?php echo $GLOBALS['cfg']['BrowsePointerBackground']; ?>;
+ color: <?php echo $GLOBALS['cfg']['BrowsePointerColor']; ?>;
+}
+
+/* hovered table rows */
+table tr.odd:hover th,
+table tr.even:hover th,
+table tr.hover th {
+ background: <?php echo $GLOBALS['cfg']['BrowsePointerBackground']; ?>;
+ color: <?php echo $GLOBALS['cfg']['BrowsePointerColor']; ?>;
+}
+<?php } ?>
+
+/**
+ * marks table rows/cells if the db field is in a where condition
+ */
+td.condition,
+th.condition {
+ border: 1px solid <?php echo $GLOBALS['cfg']['BrowseMarkerBackground']; ?>;
+}
+
+/**
+ * cells with the value NULL
+ */
+td.null {
+ font-style: italic;
+ text-align: <?php echo $right; ?>;
+}
+
+table .valueHeader {
+ text-align: <?php echo $right; ?>;
+ white-space: normal;
+}
+table .value {
+ text-align: <?php echo $right; ?>;
+ white-space: normal;
+}
+/* IE doesnt handles 'pre' right */
+table [class=value] {
+ white-space: normal;
+}
+
+
+<?php if (! empty($GLOBALS['cfg']['FontFamilyFixed'])) { ?>
+.value {
+ font-family: <?php echo $GLOBALS['cfg']['FontFamilyFixed']; ?>;
+}
+<?php } ?>
+.attention {
+ color: red;
+ font-weight: bold;
+}
+.allfine {
+ color: green;
+}
+
+
+img.lightbulb {
+ cursor: pointer;
+}
+
+.pdflayout {
+ overflow: hidden;
+ clip: inherit;
+ background-color: #FFFFFF;
+ display: none;
+ border: 1px solid #000000;
+ position: relative;
+}
+
+.pdflayout_table {
+ background: #D3DCE3;
+ color: #000000;
+ overflow: hidden;
+ clip: inherit;
+ z-index: 2;
+ display: inline;
+ visibility: inherit;
+ cursor: move;
+ position: absolute;
+ font-size: 80%;
+ border: 1px dashed #000000;
+}
+
+/* Doc links in SQL */
+.cm-sql-doc {
+ text-decoration: none;
+ border-bottom: 1px dotted #000;
+ color: inherit !important;
+}
+
+/* leave some space between icons and text */
+.icon {
+ vertical-align: middle;
+ margin-right: 0.3em;
+ margin-left: 0.3em;
+}
+
+/* no extra space in table cells */
+td .icon {
+ margin: 0;
+}
+
+.selectallarrow {
+ margin-<?php echo $right; ?>: 0.3em;
+ margin-<?php echo $left; ?>: 0.6em;
+}
+
+/* message boxes: error, confirmation */
+#pma_errors, #pma_demo {
+ padding: 0 0.5em;
+}
+
+.success h1,
+.notice h1,
+div.error h1 {
+ border-bottom: 2px solid;
+ font-weight: bold;
+ text-align: <?php echo $left; ?>;
+ margin: 0 0 0.2em 0;
+}
+
+div.success,
+div.notice,
+div.error {
+ margin: 0.3em 0 0 0;
+ border: 2px solid;
+ background-repeat: no-repeat;
+ <?php if ($GLOBALS['text_dir'] === 'ltr') { ?>
+ background-position: 10px 50%;
+ padding: 0.1em 0.1em 0.1em 36px;
+ <?php } else { ?>
+ background-position: 99% 50%;
+ padding: 0.1em 46px 0.1em 0.1em;
+ <?php } ?>
+}
+
+.success {
+ color: #000000;
+ background-color: #f0fff0;
+}
+h1.success,
+div.success {
+ border-color: #00FF00;
+}
+.success h1 {
+ border-color: #00FF00;
+}
+
+.notice {
+ color: #000000;
+ background-color: #FFFFDD;
+}
+h1.notice,
+div.notice {
+ border-color: #FFD700;
+}
+.notice h1 {
+ border-color: #FFD700;
+}
+
+.error {
+ background-color: #FFFFCC;
+ color: #ff0000;
+}
+
+h1.error,
+div.error {
+ border-color: #ff0000;
+}
+div.error h1 {
+ border-color: #ff0000;
+}
+
+.confirmation {
+ background-color: #FFFFCC;
+}
+fieldset.confirmation {
+ border: 0.1em solid #FF0000;
+}
+fieldset.confirmation legend {
+ border-left: 0.1em solid #FF0000;
+ border-right: 0.1em solid #FF0000;
+ font-weight: bold;
+ background-image: url(<?php echo $_SESSION['PMA_Theme']->getImgPath('s_really.png');?>);
+ background-repeat: no-repeat;
+ <?php if ($GLOBALS['text_dir'] === 'ltr') { ?>
+ background-position: 5px 50%;
+ padding: 0.2em 0.2em 0.2em 25px;
+ <?php } else { ?>
+ background-position: 97% 50%;
+ padding: 0.2em 25px 0.2em 0.2em;
+ <?php } ?>
+}
+/* end messageboxes */
+
+
+.tblcomment {
+ font-size: 70%;
+ font-weight: normal;
+ color: #000099;
+}
+
+.tblHeaders {
+ font-weight: bold;
+ color: <?php echo $GLOBALS['cfg']['ThColor']; ?>;
+ background: <?php echo $GLOBALS['cfg']['ThBackground']; ?>;
+}
+
+div.tools,
+.tblFooters {
+ font-weight: normal;
+ color: <?php echo $GLOBALS['cfg']['ThColor']; ?>;
+ background: <?php echo $GLOBALS['cfg']['ThBackground']; ?>;
+}
+
+.tblHeaders a:link,
+.tblHeaders a:active,
+.tblHeaders a:visited,
+div.tools a:link,
+div.tools a:visited,
+div.tools a:active,
+.tblFooters a:link,
+.tblFooters a:active,
+.tblFooters a:visited {
+ color: #0000FF;
+}
+
+.tblHeaders a:hover,
+div.tools a:hover,
+.tblFooters a:hover {
+ color: #FF0000;
+}
+
+/* forbidden, no privilegs */
+.noPrivileges {
+ color: #FF0000;
+ font-weight: bold;
+}
+
+/* disabled text */
+.disabled,
+.disabled a:link,
+.disabled a:active,
+.disabled a:visited {
+ color: #666666;
+}
+
+.disabled a:hover {
+ color: #666666;
+ text-decoration: none;
+}
+
+tr.disabled td,
+td.disabled {
+ background-color: #cccccc;
+}
+
+.nowrap {
+ white-space: nowrap;
+}
+
+/**
+ * zoom search
+ */
+div#resizer {
+ width: 600px;
+ height: 400px;
+}
+div#querychart {
+ float: left;
+ width: 600px;
+}
+
+/**
+ * login form
+ */
+body#loginform h1,
+body#loginform a.logo {
+ display: block;
+ text-align: center;
+}
+
+body#loginform {
+ margin-top: 1em;
+ text-align: center;
+}
+
+body#loginform div.container {
+ text-align: <?php echo $left; ?>;
+ width: 30em;
+ margin: 0 auto;
+}
+
+form.login label {
+ float: <?php echo $left; ?>;
+ width: 10em;
+ font-weight: bolder;
+}
+
+.commented_column {
+ border-bottom: 1px dashed black;
+}
+
+.column_attribute {
+ font-size: 70%;
+}
+
+/******************************************************************************/
+/* specific elements */
+
+/* topmenu */
+ul#topmenu, ul#topmenu2, ul.tabs {
+ font-weight: bold;
+ list-style-type: none;
+ margin: 0;
+ padding: 0;
+}
+
+ul#topmenu2 {
+ margin: 0.25em 0.5em 0;
+ height: 2em;
+ clear: both;
+}
+
+ul#topmenu li, ul#topmenu2 li {
+ float: <?php echo $left; ?>;
+ margin: 0;
+ padding: 0;
+ vertical-align: middle;
+}
+
+#topmenu img, #topmenu2 img {
+ vertical-align: middle;
+ margin-<?php echo $right; ?>: 0.1em;
+}
+
+/* default tab styles */
+ul#topmenu a, ul#topmenu span {
+ display: block;
+ margin: 2px 2px 0;
+ padding: 2px 2px 0;
+ white-space: nowrap;
+}
+
+ul#topmenu2 a {
+ display: block;
+ margin: 0.1em;
+ padding: 0.2em;
+ white-space: nowrap;
+}
+
+fieldset.caution a {
+ color: #FF0000;
+}
+fieldset.caution a:hover {
+ color: #ffffff;
+ background-color: #FF0000;
+}
+
+#topmenu {
+ margin-top: 0.5em;
+ padding: 0.1em 0.3em 0.1em 0.3em;
+}
+
+ul#topmenu ul {
+ -moz-box-shadow: 2px 2px 3px #666;
+ -webkit-box-shadow: 2px 2px 3px #666;
+ box-shadow: 2px 2px 3px #666;
+}
+
+ul#topmenu > li {
+ border-bottom: 1pt solid black;
+}
+
+/* default tab styles */
+ul#topmenu a, ul#topmenu span {
+ background-color: <?php echo $GLOBALS['cfg']['BgOne']; ?>;
+ border: 0 solid <?php echo $GLOBALS['cfg']['BgTwo']; ?>;
+ border-width: 1pt 1pt 0 1pt;
+ -moz-border-radius: 0.4em 0.4em 0 0;
+ border-radius: 0.4em 0.4em 0 0;
+}
+
+ul#topmenu ul a {
+ border-width: 1pt 0 0 0;
+ -moz-border-radius: 0;
+ border-radius: 0;
+}
+
+ul#topmenu ul li:first-child a {
+ border-width: 0;
+}
+
+/* enabled hover/active tabs */
+ul#topmenu > li > a:hover,
+ul#topmenu > li > .tabactive {
+ margin: 0;
+ padding: 2px 4px;
+ text-decoration: none;
+}
+
+ul#topmenu ul a:hover,
+ul#topmenu ul .tabactive {
+ text-decoration: none;
+}
+
+ul#topmenu a.tab:hover,
+ul#topmenu .tabactive {
+ background-color: <?php echo $GLOBALS['cfg']['MainBackground']; ?>;
+}
+
+ul#topmenu2 a.tab:hover,
+ul#topmenu2 a.tabactive {
+ background-color: <?php echo $GLOBALS['cfg']['BgOne']; ?>;
+ -moz-border-radius: 0.3em;
+ border-radius: 0.3em;
+ text-decoration: none;
+}
+
+/* to be able to cancel the bottom border, use <li class="active"> */
+ul#topmenu > li.active {
+ border-bottom: 1pt solid <?php echo $GLOBALS['cfg']['MainBackground']; ?>;
+}
+/* end topmenu */
+
+/* zoom search */
+div#dataDisplay input, div#dataDisplay select {
+ margin: 0;
+ margin-<?php echo $right; ?>: 0.5em;
+}
+div#dataDisplay th {
+ line-height: 2em;
+}
+
+/* Calendar */
+table.calendar {
+ width: 100%;
+}
+table.calendar td {
+ text-align: center;
+}
+table.calendar td a {
+ display: block;
+}
+
+table.calendar td a:hover {
+ background-color: #CCFFCC;
+}
+
+table.calendar th {
+ background-color: #D3DCE3;
+}
+
+table.calendar td.selected {
+ background-color: #FFCC99;
+}
+
+img.calendar {
+ border: none;
+}
+form.clock {
+ text-align: center;
+}
+/* end Calendar */
+
+
+/* table stats */
+div#tablestatistics table {
+ float: <?php echo $left; ?>;
+ margin-top: 0.5em;
+ margin-bottom: 0.5em;
+ margin-<?php echo $right; ?>: 0.5em;
+ min-width: 16em;
+}
+/* END table stats */
+
+
+/* server privileges */
+#tableuserrights td,
+#tablespecificuserrights td,
+#tabledatabases td {
+ vertical-align: middle;
+}
+/* END server privileges */
+
+
+
+/* Heading */
+#topmenucontainer {
+ background: white;
+ padding-<?php echo $right; ?>: 1em;
+ width: 100%;
+}
+
+#serverinfo {
+ background: white;
+ font-weight: bold;
+ padding-bottom: 0.5em;
+ width: 10000px;
+ overflow: hidden;
+}
+
+#serverinfo .item {
+ white-space: nowrap;
+}
+
+#goto_pagetop {
+ position: fixed;
+ padding: .1em .3em;
+ top: 0;
+ <?php echo $right; ?>: 0;
+ z-index: 900;
+ background: white;
+}
+
+#span_table_comment {
+ font-weight: bold;
+ font-style: italic;
+ white-space: nowrap;
+ margin-left: 10px;
+ color: #D6D6D6;
+ text-shadow: none;
+}
+
+#serverinfo img {
+ margin: 0 0.1em 0 0.2em;
+}
+
+
+#textSQLDUMP {
+ width: 95%;
+ height: 95%;
+ font-family: "Courier New", Courier, mono;
+ font-size: 110%;
+}
+
+#TooltipContainer {
+ position: absolute;
+ z-index: 99;
+ width: 20em;
+ height: auto;
+ overflow: visible;
+ visibility: hidden;
+ background-color: #ffffcc;
+ color: #006600;
+ border: 0.1em solid #000000;
+ padding: 0.5em;
+}
+
+/* user privileges */
+#fieldset_add_user_login div.item {
+ border-bottom: 1px solid silver;
+ padding-bottom: 0.3em;
+ margin-bottom: 0.3em;
+}
+
+#fieldset_add_user_login label {
+ float: <?php echo $left; ?>;
+ display: block;
+ width: 10em;
+ max-width: 100%;
+ text-align: <?php echo $right; ?>;
+ padding-<?php echo $right; ?>: 0.5em;
+}
+
+#fieldset_add_user_login span.options #select_pred_username,
+#fieldset_add_user_login span.options #select_pred_hostname,
+#fieldset_add_user_login span.options #select_pred_password {
+ width: 100%;
+ max-width: 100%;
+}
+
+#fieldset_add_user_login span.options {
+ float: <?php echo $left; ?>;
+ display: block;
+ width: 12em;
+ max-width: 100%;
+ padding-<?php echo $right; ?>: 0.5em;
+}
+
+#fieldset_add_user_login input {
+ width: 12em;
+ clear: <?php echo $right; ?>;
+ max-width: 100%;
+}
+
+#fieldset_add_user_login span.options input {
+ width: auto;
+}
+
+#fieldset_user_priv div.item {
+ float: <?php echo $left; ?>;
+ width: 9em;
+ max-width: 100%;
+}
+
+#fieldset_user_priv div.item div.item {
+ float: none;
+}
+
+#fieldset_user_priv div.item label {
+ white-space: nowrap;
+}
+
+#fieldset_user_priv div.item select {
+ width: 100%;
+}
+
+#fieldset_user_global_rights fieldset {
+ float: <?php echo $left; ?>;
+}
+
+#fieldset_user_group_rights fieldset {
+ float: <?php echo $left; ?>;
+}
+
+#fieldset_user_global_rights legend input {
+ margin-<?php echo $left; ?>: 2em;
+}
+/* END user privileges */
+
+
+/* serverstatus */
+
+.linkElem:hover {
+ text-decoration: underline;
+ color: #235a81;
+ cursor: pointer;
+}
+
+h3#serverstatusqueries span {
+ font-size:60%;
+ display:inline;
+}
+
+img.sortableIcon {
+ float:right;
+ background-repeat:no-repeat;
+ margin:0;
+}
+
+.buttonlinks {
+ float: <?php echo $right; ?>;
+ white-space: nowrap;
+}
+
+/* Also used for the variables page */
+fieldset#tableFilter {
+ margin-bottom:1em;
+}
+
+div#serverStatusTabs {
+ margin-top:1em;
+}
+
+caption a.top {
+ float: <?php echo $right; ?>;
+}
+
+div#serverstatusquerieschart {
+ float:<?php echo $left; ?>;
+ width:500px;
+ height:350px;
+ padding-<?php echo $left; ?>: 30px;
+}
+
+div#serverstatus table#serverstatusqueriesdetails {
+ float: <?php echo $left; ?>;
+}
+
+table#serverstatustraffic {
+ float: <?php echo $left; ?>;
+}
+table#serverstatusconnections {
+ float: <?php echo $left; ?>;
+ margin-<?php echo $left; ?>: 30px;
+}
+
+table#serverstatusvariables {
+ width: 100%;
+ margin-bottom: 1em;
+}
+table#serverstatusvariables .name {
+ width: 18em;
+ white-space:nowrap;
+}
+table#serverstatusvariables .value {
+ width: 6em;
+}
+table#serverstatusconnections {
+ float: <?php echo $left; ?>;
+ margin-<?php echo $left; ?>: 30px;
+}
+
+div#serverstatus table tbody td.descr a,
+div#serverstatus table .tblFooters a {
+ white-space: nowrap;
+}
+
+div.liveChart {
+ clear:both;
+ min-width:500px;
+ height:400px;
+ padding-bottom:80px;
+}
+
+#addChartDialog input[type="text"] {
+ margin: 0;
+ padding:3px;
+}
+
+div#chartVariableSettings {
+ border:1px solid #ddd;
+ background-color:#E6E6E6;
+ margin-left:10px;
+}
+
+table#chartGrid div.monitorChart {
+ background: #EBEBEB;
+ width: 400px;
+ height: 300px;
+}
+
+div#serverstatus div.tabLinks {
+ float:<?php echo $left; ?>;
+ padding-bottom: 10px;
+}
+
+.popupContent {
+ display: none;
+ position: absolute;
+ border: 1px solid #CCC;
+ margin:0;
+ padding:3px;
+ -moz-box-shadow: 1px 1px 6px #ddd;
+ -webkit-box-shadow: 2px 2px 3px #666;
+ box-shadow: 2px 2px 3px #666;
+ background-color:white;
+ z-index: 2;
+}
+
+div#logTable {
+ padding-top: 10px;
+ clear: both;
+}
+
+div#logTable table {
+ width:100%;
+}
+
+.smallIndent {
+ padding-left: 7px;
+}
+
+/* end serverstatus */
+
+/* server variables */
+#serverVariables {
+ min-width: 30em;
+}
+#serverVariables .var-row > div {
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ line-height: 2em;
+}
+
+#serverVariables .var-header {
+ font-weight: bold;
+ color: <?php echo $GLOBALS['cfg']['ThColor']; ?>;
+ background: <?php echo $GLOBALS['cfg']['ThBackground']; ?>;
+}
+#serverVariables .var-header .var-value {
+ text-align: <?php echo $left; ?>;
+}
+#serverVariables .var-row {
+ padding: 0.5em;
+ min-height: 18px;
+}
+#serverVariables .var-name {
+ width: 45%;
+ float: <?php echo $left; ?>;
+ font-weight: bold;
+}
+#serverVariables .var-name.session {
+ font-weight: normal;
+ font-style: italic;
+}
+#serverVariables .var-value {
+ width: 50%;
+ float: <?php echo $right; ?>;
+ text-align: <?php echo $right; ?>;
+}
+
+/* server variables editor */
+#serverVariables .editLink {
+ padding-<?php echo $right; ?>: 1em;
+ float: <?php echo $left; ?>;
+ font-family: sans-serif;
+}
+#serverVariables .serverVariableEditor {
+ width: 100%;
+ overflow: hidden;
+}
+#serverVariables .serverVariableEditor input {
+ width: 100%;
+ margin: 0 0.5em;
+ box-sizing: border-box;
+ -ms-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ -webkit-box-sizing: border-box;
+ height: 2.2em;
+}
+#serverVariables .serverVariableEditor div {
+ display: block;
+ overflow: hidden;
+ padding-<?php echo $right; ?>: 1em;
+}
+#serverVariables .serverVariableEditor a {
+ float: <?php echo $right; ?>;
+ margin: 0 0.5em;
+ line-height: 2em;
+}
+/* end server variables */
+
+/* querywindow */
+body#bodyquerywindow {
+ margin: 0;
+ padding: 0;
+ background-image: none;
+ background-color: #F5F5F5;
+}
+
+div#querywindowcontainer {
+ margin: 0;
+ padding: 0;
+ width: 100%;
+}
+
+div#querywindowcontainer fieldset {
+ margin-top: 0;
+}
+/* END querywindow */
+
+/* profiling */
+
+div#profilingchart {
+ width: 550px;
+ height: 370px;
+ float: left;
+}
+
+#profilingchart .jqplot-highlighter-tooltip{
+ top: auto !important;
+ left: 11px;
+ bottom:24px;
+}
+
+#profilesummarytable th.header, #profiletable th.header{
+ cursor: pointer;
+}
+
+#profilesummarytable th.header .sorticon, #profiletable th.header .sorticon{
+ width: 16px;
+ height: 16px;
+ background-repeat: no-repeat;
+ background-position: right center;
+ display: inline-block;
+ vertical-align: middle;
+ float: right;
+}
+
+#profilesummarytable th.headerSortUp .sorticon, #profiletable th.headerSortUp .sorticon{
+ background-image: url(<?php echo $_SESSION['PMA_Theme']->getImgPath('s_desc.png');?>);
+}
+
+#profilesummarytable th.headerSortDown .sorticon, #profiletable th.headerSortDown .sorticon{
+ background-image: url(<?php echo $_SESSION['PMA_Theme']->getImgPath('s_asc.png');?>);
+}
+
+/* END profiling */
+
+/* querybox */
+
+div#sqlquerycontainer {
+ float: <?php echo $left; ?>;
+ width: 69%;
+ /* height: 15em; */
+}
+
+div#tablefieldscontainer {
+ float: <?php echo $right; ?>;
+ width: 29%;
+ /* height: 15em; */
+}
+
+div#tablefieldscontainer select {
+ width: 100%;
+ /* height: 12em; */
+}
+
+textarea#sqlquery {
+ width: 100%;
+ /* height: 100%; */
+}
+textarea#sql_query_edit {
+ height: 7em;
+ width: 95%;
+ display: block;
+}
+div#queryboxcontainer div#bookmarkoptions {
+ margin-top: .5em;
+}
+/* end querybox */
+
+/* main page */
+#maincontainer {
+ background-image: url(<?php echo $_SESSION['PMA_Theme']->getImgPath('logo_right.png');?>);
+ background-position: <?php echo $right; ?> bottom;
+ background-repeat: no-repeat;
+}
+
+#mysqlmaininformation,
+#pmamaininformation {
+ float: <?php echo $left; ?>;
+ width: 49%;
+}
+
+#maincontainer ul {
+ list-style-type: disc;
+ vertical-align: middle;
+}
+
+#maincontainer li {
+ margin: 0.2em 0em;
+}
+/* END main page */
+
+/* iconic view for ul items */
+
+li.no_bullets {
+ list-style-type:none !important;
+ margin-left: -25px !important; //align with other list items which have bullets
+}
+
+/* END iconic view for ul items */
+
+#body_browse_foreigners {
+ background: <?php echo $GLOBALS['cfg']['NaviBackground']; ?>;
+ margin: .5em .5em 0 .5em;
+}
+
+#bodyquerywindow {
+ background: <?php echo $GLOBALS['cfg']['NaviBackground']; ?>;
+}
+
+#bodythemes {
+ width: 500px;
+ margin: auto;
+ text-align: center;
+}
+
+#bodythemes img {
+ border: .1em solid #000;
+}
+
+#bodythemes a:hover img {
+ border: .1em solid red;
+}
+
+#fieldset_select_fields {
+ float: <?php echo $left; ?>;
+}
+
+#selflink {
+ clear: both;
+ display: block;
+ margin-top: 1em;
+ margin-bottom: 1em;
+ width: 98%;
+ margin-left: 1%;
+ border-top: .1em solid silver;
+ text-align: <?php echo $right; ?>;
+}
+
+#table_innodb_bufferpool_usage,
+#table_innodb_bufferpool_activity {
+ float: <?php echo $left; ?>;
+}
+
+#div_mysql_charset_collations table {
+ float: <?php echo $left; ?>;
+}
+
+.operations_half_width {
+ width: 48%;
+ float: <?php echo $left; ?>;
+}
+.operations_half_width input[type=text],
+.operations_half_width input[type=password],
+.operations_half_width input[type=number],
+.operations_half_width select {
+ width: 95%;
+}
+.operations_half_width input[type=text].halfWidth,
+.operations_half_width input[type=password].halfWidth,
+.operations_half_width input[type=number].halfWidth,
+.operations_half_width select.halfWidth {
+ width: 40%;
+}
+.operations_half_width ul {
+ list-style-type: none;
+ padding: 0;
+}
+.operations_full_width {
+ width: 100%;
+ clear: both;
+}
+
+#qbe_div_table_list {
+ float: <?php echo $left; ?>;
+}
+
+#qbe_div_sql_query {
+ float: <?php echo $left; ?>;
+}
+
+label.desc {
+ width: 30em;
+ float: <?php echo $left; ?>;
+}
+
+label.desc sup {
+ position: absolute;
+}
+
+code.sql,
+div.sqlvalidate {
+ display: block;
+ padding: 0.3em;
+ margin-top: 0;
+ margin-bottom: 0;
+ max-height: 10em;
+ overflow: auto;
+}
+
+#result_query div.sqlOuter,
+div.sqlvalidate {
+ border: <?php echo $GLOBALS['cfg']['MainColor']; ?> solid 1px;
+ border-top: 0;
+ border-bottom: 0;
+ background: <?php echo $GLOBALS['cfg']['BgOne']; ?>;
+}
+
+#PMA_slidingMessage code.sql {
+ border: <?php echo $GLOBALS['cfg']['MainColor']; ?> solid 1px;
+ border-top: 0;
+ background: <?php echo $GLOBALS['cfg']['BgOne']; ?>;
+}
+
+#main_pane_left {
+ width: 60%;
+ float: <?php echo $left; ?>;
+ padding-top: 1em;
+}
+
+#main_pane_right {
+ margin-<?php echo $left; ?>: 60%;
+ padding-top: 1em;
+ padding-<?php echo $left; ?>: 1em;
+}
+
+.group {
+ border-<?php echo $left; ?>: 0.3em solid <?php echo $GLOBALS['cfg']['ThBackground']; ?>;
+ margin-bottom: 1em;
+}
+
+.group h2 {
+ background: <?php echo $GLOBALS['cfg']['ThBackground']; ?>;
+ padding: 0.1em 0.3em;
+ margin-top: 0;
+}
+
+.group-cnt {
+ padding: 0 0 0 0.5em;
+ display: inline-block;
+ width: 98%;
+}
+
+textarea#partitiondefinition {
+ height:3em;
+}
+
+
+/* for elements that should be revealed only via js */
+.hide {
+ display: none;
+}
+
+#list_server {
+ list-style-image: none;
+}
+
+/**
+ * Progress bar styles
+ */
+div.upload_progress
+{
+ width: 400px;
+ margin: 3em auto;
+ text-align: center;
+}
+
+div.upload_progress_bar_outer
+{
+ border: 1px solid #000;
+ width: 202px;
+ position: relative;
+ margin: 0 auto 1em;
+ color: <?php echo $GLOBALS['cfg']['MainColor']; ?>;
+}
+
+div.upload_progress_bar_inner
+{
+ background-color: <?php echo $GLOBALS['cfg']['NaviPointerBackground']; ?>;
+ width: 0;
+ height: 12px;
+ margin: 1px;
+ overflow: hidden;
+ color: <?php echo $GLOBALS['cfg']['BrowseMarkerColor']; ?>;
+ position: relative;
+}
+
+div.upload_progress_bar_outer div.percentage
+{
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 202px;
+}
+
+div.upload_progress_bar_inner div.percentage
+{
+ top: -1px;
+ left: -1px;
+}
+
+div#statustext {
+ margin-top: .5em;
+}
+
+table#serverconnection_src_remote,
+table#serverconnection_trg_remote,
+table#serverconnection_src_local,
+table#serverconnection_trg_local {
+ float: left;
+}
+/**
+ * Validation error message styles
+ */
+input[type=text].invalid_value,
+input[type=password].invalid_value,
+input[type=number].invalid_value,
+input[type=date].invalid_value,
+.invalid_value {
+ background: #FFCCCC;
+}
+
+/**
+ * Ajax notification styling
+ */
+ .ajax_notification {
+ top: 0; /** The notification needs to be shown on the top of the page */
+ position: fixed;
+ margin-top: 0;
+ margin-right: auto;
+ margin-bottom: 0;
+ margin-left: auto;
+ padding: 3px 5px; /** Keep a little space on the sides of the text */
+ width: 350px;
+ background-color: #FFD700;
+ z-index: 1100; /** If this is not kept at a high z-index, the jQueryUI modal dialogs (z-index:1000) might hide this */
+ text-align: center;
+ display: block;
+ left: 0;
+ right: 0;
+ background-image: url(<?php echo $_SESSION['PMA_Theme']->getImgPath('ajax_clock_small.gif');?>);
+ background-repeat: no-repeat;
+ background-position: 2%;
+ }
+
+ #loading_parent {
+ /** Need this parent to properly center the notification division */
+ position: relative;
+ width: 100%;
+ }
+/**
+ * Export and Import styles
+ */
+
+.exportoptions h3, .importoptions h3 {
+ border-bottom: 1px #999999 solid;
+ font-size: 110%;
+}
+
+.exportoptions ul, .importoptions ul, .format_specific_options ul {
+ list-style-type: none;
+ margin-bottom: 15px;
+}
+
+.exportoptions li, .importoptions li {
+ margin: 7px;
+}
+.exportoptions label, .importoptions label, .exportoptions p, .importoptions p {
+ margin: 5px;
+ float: none;
+}
+
+#csv_options label.desc, #ldi_options label.desc, #latex_options label.desc, #output label.desc{
+ float: left;
+ width: 15em;
+}
+
+.exportoptions, .importoptions {
+ margin: 20px 30px 30px 10px
+}
+
+.format_specific_options h3 {
+ margin: 10px 0 0 10px;
+ border: 0;
+}
+
+.format_specific_options {
+ border: 1px solid #999;
+ margin: 7px 0;
+ padding: 3px;
+}
+
+p.desc {
+ margin: 5px;
+}
+
+/**
+ * Export styles only
+ */
+select#db_select,
+select#table_select {
+ width: 400px;
+}
+
+.export_sub_options {
+ margin: 20px 0 0 30px;
+}
+
+.export_sub_options h4 {
+ border-bottom: 1px #999 solid;
+}
+
+.export_sub_options li.subgroup {
+ display: inline-block;
+ margin-top: 0;
+}
+
+.export_sub_options li {
+ margin-bottom: 0;
+}
+
+#quick_or_custom,
+#output_quick_export {
+ display: none;
+}
+/**
+ * Import styles only
+ */
+
+.importoptions #import_notification {
+ margin: 10px 0;
+ font-style: italic;
+}
+
+input#input_import_file {
+ margin: 5px;
+}
+
+.formelementrow {
+ margin: 5px 0 5px 0;
+}
+
+#popup_background {
+ display: none;
+ position: fixed;
+ _position: absolute; /* hack for IE6 */
+ width: 100%;
+ height: 100%;
+ top: 0;
+ left: 0;
+ background: #000;
+ z-index: 1000;
+ overflow: hidden;
+}
+
+/**
+ * Create table styles
+ */
+#create_table_form table.table-name td {
+ vertical-align: middle;
+}
+
+/**
+ * Table structure styles
+ */
+#fieldsForm ul.table-structure-actions {
+ margin: 0;
+ padding: 0;
+ list-style: none;
+}
+#fieldsForm ul.table-structure-actions li {
+ float: <?php echo $left; ?>;
+ margin-<?php echo $right; ?>: 0.5em; /* same as padding of "table td" */
+}
+#fieldsForm ul.table-structure-actions .submenu li {
+ padding: 0.3em;
+ margin: 0.1em;
+}
+
+.margin#change_column_dialog {
+ margin: 0 .5em;
+}
+
+/**
+ * Indexes
+ */
+#index_frm .index_info input,
+#index_frm .index_info select {
+ width: 100%;
+ box-sizing: border-box;
+ -ms-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ -webkit-box-sizing: border-box;
+}
+
+#index_frm .slider {
+ width: 10em;
+ margin: .6em;
+ float: <?php echo $left; ?>;
+}
+
+#index_frm .add_fields {
+ float: <?php echo $left; ?>;
+}
+
+#index_frm .add_fields input {
+ margin-<?php echo $left; ?>: 1em;
+}
+
+#index_frm input {
+ margin: 0;
+}
+
+#index_frm td {
+ vertical-align: middle;
+}
+
+table#index_columns {
+ width: 100%;
+}
+
+table#index_columns select {
+ width: 100%;
+}
+
+#move_columns_dialog div {
+ padding: 1em;
+}
+
+#move_columns_dialog ul {
+ list-style: none;
+ margin: 0;
+ padding: 0;
+}
+
+#move_columns_dialog li {
+ background: <?php echo $GLOBALS['cfg']['ThBackground']; ?>;
+ border: 1px solid #aaa;
+ color: <?php echo $GLOBALS['cfg']['ThColor']; ?>;
+ font-weight: bold;
+ margin: .4em;
+ padding: .2em;
+ -webkit-border-radius: 2px;
+ -moz-border-radius: 2px;
+ border-radius: 2px;
+}
+
+/* config forms */
+.config-form ul.tabs {
+ margin: 1.1em 0.2em 0;
+ padding: 0 0 0.3em 0;
+ list-style: none;
+ font-weight: bold;
+}
+
+.config-form ul.tabs li {
+ float: <?php echo $left; ?>;
+}
+
+.config-form ul.tabs li a {
+ display: block;
+ margin: 0.1em 0.2em 0;
+ padding: 0.1em 0.4em;
+ white-space: nowrap;
+ text-decoration: none;
+ border: 1px solid <?php echo $GLOBALS['cfg']['BgTwo']; ?>;
+ border-bottom: none;
+}
+
+.config-form ul.tabs li a:hover,
+.config-form ul.tabs li a:active,
+.config-form ul.tabs li a.active {
+ margin: 0;
+ padding: 0.1em 0.6em 0.2em;
+}
+
+.config-form ul.tabs li.active a {
+ background-color: <?php echo $GLOBALS['cfg']['BgOne']; ?>;
+}
+
+.config-form fieldset {
+ margin-top: 0;
+ padding: 0;
+ clear: both;
+ /*border-color: <?php echo $GLOBALS['cfg']['BgTwo']; ?>;*/
+}
+
+.config-form legend {
+ display: none;
+}
+
+.config-form fieldset p {
+ margin: 0;
+ padding: 0.5em;
+ background: <?php echo $GLOBALS['cfg']['BgTwo']; ?>;
+}
+
+.config-form fieldset .errors { /* form error list */
+ margin: 0 -2px 1em -2px;
+ padding: .5em 1.5em;
+ background: #FBEAD9;
+ border: 0 #C83838 solid;
+ border-width: 1px 0;
+ list-style: none;
+ font-family: sans-serif;
+ font-size: small;
+}
+
+.config-form fieldset .inline_errors { /* field error list */
+ margin: .3em .3em .3em 0;
+ padding: 0;
+ list-style: none;
+ color: #9A0000;
+ font-size: small;
+}
+
+.config-form fieldset th {
+ padding: .3em .3em .3em .5em;
+ text-align: left;
+ vertical-align: top;
+ width: 40%;
+ background: transparent;
+}
+
+.config-form fieldset .doc,
+.config-form fieldset .disabled-notice {
+ margin-left: 1em;
+}
+
+.config-form fieldset .disabled-notice {
+ font-size: 80%;
+ text-transform: uppercase;
+ color: #E00;
+ cursor: help;
+}
+
+.config-form fieldset td {
+ padding-top: .3em;
+ padding-bottom: .3em;
+ vertical-align: top;
+}
+
+.config-form fieldset th small {
+ display: block;
+ font-weight: normal;
+ font-family: sans-serif;
+ font-size: x-small;
+ color: #444;
+}
+
+.config-form fieldset th,
+.config-form fieldset td {
+ border-top: 1px <?php echo $GLOBALS['cfg']['BgTwo']; ?> solid;
+}
+
+fieldset .group-header th {
+ background: <?php echo $GLOBALS['cfg']['BgTwo']; ?>;
+}
+
+fieldset .group-header + tr th {
+ padding-top: .6em;
+}
+
+fieldset .group-field-1 th,
+fieldset .group-header-2 th {
+ padding-left: 1.5em;
+}
+
+fieldset .group-field-2 th,
+fieldset .group-header-3 th {
+ padding-left: 3em;
+}
+
+fieldset .group-field-3 th {
+ padding-left: 4.5em;
+}
+
+fieldset .disabled-field th,
+fieldset .disabled-field th small,
+fieldset .disabled-field td {
+ color: #666;
+ background-color: #ddd;
+}
+
+.config-form .lastrow {
+ border-top: 1px #000 solid;
+}
+
+.config-form .lastrow {
+ background: <?php echo $GLOBALS['cfg']['ThBackground']; ?>;
+ padding: .5em;
+ text-align: center;
+}
+
+.config-form .lastrow input {
+ font-weight: bold;
+}
+
+/* form elements */
+
+.config-form span.checkbox {
+ padding: 2px;
+ display: inline-block;
+}
+
+.config-form .custom { /* customized field */
+ background: #FFC;
+}
+
+.config-form span.checkbox.custom {
+ padding: 1px;
+ border: 1px #EDEC90 solid;
+ background: #FFC;
+}
+
+.config-form .field-error {
+ border-color: #A11 !important;
+}
+
+.config-form input[type="text"],
+.config-form input[type="password"],
+.config-form input[type="number"],
+.config-form select,
+.config-form textarea {
+ border: 1px #A7A6AA solid;
+ height: auto;
+}
+
+.config-form input[type="text"]:focus,
+.config-form input[type="password"]:focus,
+.config-form input[type="number"]:focus,
+.config-form select:focus,
+.config-form textarea:focus {
+ border: 1px #6676FF solid;
+ background: #F7FBFF;
+}
+
+.config-form .field-comment-mark {
+ font-family: serif;
+ color: #007;
+ cursor: help;
+ padding: 0 0.2em;
+ font-weight: bold;
+ font-style: italic;
+}
+
+.config-form .field-comment-warning {
+ color: #A00;
+}
+
+/* error list */
+.config-form dd {
+ margin-left: .5em;
+}
+
+.config-form dd:before {
+ content: "\25B8 ";
+}
+
+.click-hide-message {
+ cursor: pointer;
+}
+
+.prefsmanage_opts {
+ margin-<?php echo $left; ?>: 2em;
+}
+
+#prefs_autoload {
+ margin-bottom: .5em;
+}
+
+#placeholder .button {
+ position: absolute;
+ cursor: pointer;
+}
+
+#placeholder div.button {
+ font-size: smaller;
+ color: #999;
+ background-color: #eee;
+ padding: 2px;
+}
+
+.wrapper {
+ float: <?php echo $left; ?>;
+ margin-bottom: 0.5em;
+}
+.toggleButton {
+ position: relative;
+ cursor: pointer;
+ font-size: .8em;
+ text-align: center;
+ line-height: 1.4em;
+ height: 1.55em;
+ overflow: hidden;
+ border-right: .1em solid #888;
+ border-left: .1em solid #888;
+}
+.toggleButton table,
+.toggleButton td,
+.toggleButton img {
+ padding: 0;
+ position: relative;
+}
+.toggleButton .container {
+ position: absolute;
+}
+.toggleButton .toggleOn {
+ color: #fff;
+ padding: 0 1em;
+}
+.toggleButton .toggleOff {
+ padding: 0 1em;
+}
+
+.doubleFieldset fieldset {
+ width: 48%;
+ float: <?php echo $left; ?>;
+ padding: 0;
+}
+.doubleFieldset fieldset.left {
+ margin-<?php echo $right; ?>: 1%;
+}
+.doubleFieldset fieldset.right {
+ margin-<?php echo $left; ?>: 1%;
+}
+.doubleFieldset legend {
+ margin-<?php echo $left; ?>: 0.5em;
+}
+.doubleFieldset div.wrap {
+ padding: 0.5em;
+}
+
+#table_columns input[type="text"],
+#table_columns input[type="password"],
+#table_columns input[type="number"],
+#table_columns input[type="date"],
+#table_columns select {
+ width: 10em;
+ box-sizing: border-box;
+ -ms-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ -webkit-box-sizing: border-box;
+}
+
+#placeholder {
+ position: relative;
+ border: 1px solid #aaa;
+ float: right;
+ overflow: hidden;
+}
+
+.placeholderDrag {
+ cursor: move;
+}
+
+#placeholder .button {
+ position: absolute;
+}
+
+#left_arrow {
+ left: 8px;
+ top: 26px;
+}
+
+#right_arrow {
+ left: 26px;
+ top: 26px;
+}
+
+#up_arrow {
+ left: 17px;
+ top: 8px;
+}
+
+#down_arrow {
+ left: 17px;
+ top: 44px;
+}
+
+#zoom_in {
+ left: 17px;
+ top: 67px;
+}
+
+#zoom_world {
+ left: 17px;
+ top: 85px;
+}
+
+#zoom_out {
+ left: 17px;
+ top: 103px;
+}
+
+.colborder {
+ cursor: col-resize;
+ height: 100%;
+ margin-left: -5px;
+ position: absolute;
+ width: 5px;
+}
+
+.colborder_active {
+ border-right: 2px solid #a44;
+}
+
+.pma_table td {
+ position: static;
+}
+
+.pma_table th.draggable span,
+.pma_table tbody td span {
+ display: block;
+ overflow: hidden;
+}
+
+.modal-copy input {
+ display: block;
+ width: 100%;
+ margin-top: 1.5em;
+ padding: .3em 0;
+}
+
+.cRsz {
+ position: absolute;
+}
+
+.draggable {
+ cursor: move;
+}
+
+.cCpy {
+ background: #000;
+ color: #FFF;
+ font-weight: bold;
+ margin: 0.1em;
+ padding: 0.3em;
+ position: absolute;
+}
+
+.cPointer {
+ background: url(<?php echo $_SESSION['PMA_Theme']->getImgPath('col_pointer.png');?>);
+ height: 20px;
+ margin-left: -5px; /* must be minus half of its width */
+ margin-top: -10px;
+ position: absolute;
+ width: 10px;
+}
+
+.tooltip {
+ background: #333 !important;
+ opacity: .8 !important;
+ border: 1px solid #000 !important;
+ -moz-border-radius: .3em !important;
+ -webkit-border-radius: .3em !important;
+ border-radius: .3em !important;
+ text-shadow: -1px -1px #000 !important;
+ font-size: .8em !important;
+ font-weight: bold !important;
+ padding: 1px 3px !important;
+}
+
+.tooltip * {
+ background: none !important;
+ color: #FFF !important;
+}
+
+
+.data_full_width {
+ width: 100%;
+}
+
+.cDrop {
+ left: 0;
+ position: absolute;
+ top: 0;
+}
+
+.coldrop {
+ background: url(<?php echo $_SESSION['PMA_Theme']->getImgPath('col_drop.png');?>);
+ cursor: pointer;
+ height: 16px;
+ margin-left: 0.5em;
+ margin-top: 0.3em;
+ position: absolute;
+ width: 16px;
+}
+
+.coldrop:hover,
+.coldrop-hover {
+ background-color: #999;
+}
+
+.cList {
+ background: #EEE;
+ border: solid 1px #999;
+ position: absolute;
+}
+
+.cList .lDiv div {
+ padding: .2em .5em .2em .2em;
+}
+
+.cList .lDiv div:hover {
+ background: #DDD;
+ cursor: pointer;
+}
+
+.cList .lDiv div input {
+ cursor: pointer;
+}
+
+.showAllColBtn {
+ border-bottom: solid 1px #999;
+ border-top: solid 1px #999;
+ cursor: pointer;
+ font-size: .9em;
+ font-weight: bold;
+ padding: .35em 1em;
+ text-align: center;
+}
+
+.showAllColBtn:hover {
+ background: #DDD;
+}
+
+.navigation {
+ background: #E5E5E5;
+ border: 1px solid black;
+ margin: 0.8em 0;
+}
+
+.navigation td {
+ margin: 0;
+ padding: 0;
+ vertical-align: middle;
+ white-space: nowrap;
+}
+
+.navigation_separator {
+ color: #555;
+ display: inline-block;
+ text-align: center;
+ width: 1.2em;
+ text-shadow: 1px 0 #FFF;
+}
+
+.navigation input[type=submit] {
+ background: none;
+ border: 0;
+ margin: 0;
+ padding: 0.3em 0.5em;
+ min-width: 1.5em;
+ font-weight: bold;
+}
+
+.navigation input[type=submit]:hover, .navigation input.edit_mode_active {
+ background: #333;
+ color: white;
+ cursor: pointer;
+}
+
+.navigation select {
+ margin: 0 .8em;
+}
+
+.cEdit {
+ margin: 0;
+ padding: 0;
+ position: absolute;
+}
+
+.cEdit input[type=text],
+.cEdit input[type=password],
+.cEdit input[type=number] {
+ background: #FFF;
+ height: 100%;
+ margin: 0;
+ padding: 0;
+}
+
+.cEdit .edit_area {
+ background: #FFF;
+ border: 1px solid #999;
+ min-width: 10em;
+ padding: .3em .5em;
+}
+
+.cEdit .edit_area select,
+.cEdit .edit_area textarea {
+ width: 97%;
+}
+
+.cEdit .cell_edit_hint {
+ color: #555;
+ font-size: .8em;
+ margin: .3em .2em;
+}
+
+.cEdit .edit_box {
+ overflow: hidden;
+ padding: 0;
+}
+
+.cEdit .edit_box_posting {
+ background: #FFF url(<?php echo $_SESSION['PMA_Theme']->getImgPath('ajax_clock_small.gif');?>) no-repeat right center;
+ padding-right: 1.5em;
+}
+
+.cEdit .edit_area_loading {
+ background: #FFF url(<?php echo $_SESSION['PMA_Theme']->getImgPath('ajax_clock_small.gif');?>) no-repeat center;
+ height: 10em;
+}
+
+.cEdit .edit_area_right {
+ position: absolute;
+ right: 0;
+}
+
+.cEdit .goto_link {
+ background: #EEE;
+ color: #555;
+ padding: .2em .3em;
+}
+
+.saving_edited_data {
+ background: url(<?php echo $_SESSION['PMA_Theme']->getImgPath('ajax_clock_small.gif');?>) no-repeat left;
+ padding-left: 20px;
+}
+
+/* css for timepicker */
+.ui-timepicker-div .ui-widget-header { margin-bottom: 8px; }
+.ui-timepicker-div dl { text-align: left; }
+.ui-timepicker-div dl dt { height: 25px; }
+.ui-timepicker-div dl dd { margin: -25px 0 10px 65px; }
+.ui-timepicker-div td { font-size: 90%; }
+
+input.btn {
+ color: #333;
+ background-color: #D0DCE0;
+}
+
+body .ui-widget {
+ font-size: 1em;
+}
+
+.ui-dialog fieldset legend a {
+ color: #0000FF;
+}
+
+/* jqPlot */
+
+/*rules for the plot target div. These will be cascaded down to all plot elements according to css rules*/
+.jqplot-target {
+ position: relative;
+ color: #222222;
+ font-family: "Trebuchet MS", Arial, Helvetica, sans-serif;
+ font-size: 1em;
+/* height: 300px;
+ width: 400px;*/
+}
+
+/*rules applied to all axes*/
+.jqplot-axis {
+ font-size: 0.75em;
+}
+
+.jqplot-xaxis {
+ margin-top: 10px;
+}
+
+.jqplot-x2axis {
+ margin-bottom: 10px;
+}
+
+.jqplot-yaxis {
+ margin-right: 10px;
+}
+
+.jqplot-y2axis, .jqplot-y3axis, .jqplot-y4axis, .jqplot-y5axis, .jqplot-y6axis, .jqplot-y7axis, .jqplot-y8axis, .jqplot-y9axis, .jqplot-yMidAxis {
+ margin-left: 10px;
+ margin-right: 10px;
+}
+
+/*rules applied to all axis tick divs*/
+.jqplot-axis-tick, .jqplot-xaxis-tick, .jqplot-yaxis-tick, .jqplot-x2axis-tick, .jqplot-y2axis-tick, .jqplot-y3axis-tick, .jqplot-y4axis-tick, .jqplot-y5axis-tick, .jqplot-y6axis-tick, .jqplot-y7axis-tick, .jqplot-y8axis-tick, .jqplot-y9axis-tick, .jqplot-yMidAxis-tick {
+ position: absolute;
+ white-space: pre;
+}
+
+
+.jqplot-xaxis-tick {
+ top: 0px;
+ /* initial position untill tick is drawn in proper place */
+ left: 15px;
+/* padding-top: 10px;*/
+ vertical-align: top;
+}
+
+.jqplot-x2axis-tick {
+ bottom: 0px;
+ /* initial position untill tick is drawn in proper place */
+ left: 15px;
+/* padding-bottom: 10px;*/
+ vertical-align: bottom;
+}
+
+.jqplot-yaxis-tick {
+ right: 0px;
+ /* initial position untill tick is drawn in proper place */
+ top: 15px;
+/* padding-right: 10px;*/
+ text-align: right;
+}
+
+.jqplot-yaxis-tick.jqplot-breakTick {
+ right: -20px;
+ margin-right: 0px;
+ padding:1px 5px 1px 5px;
+/* background-color: white;*/
+ z-index: 2;
+ font-size: 1.5em;
+}
+
+.jqplot-y2axis-tick, .jqplot-y3axis-tick, .jqplot-y4axis-tick, .jqplot-y5axis-tick, .jqplot-y6axis-tick, .jqplot-y7axis-tick, .jqplot-y8axis-tick, .jqplot-y9axis-tick {
+ left: 0px;
+ /* initial position untill tick is drawn in proper place */
+ top: 15px;
+/* padding-left: 10px;*/
+/* padding-right: 15px;*/
+ text-align: left;
+}
+
+.jqplot-yMidAxis-tick {
+ text-align: center;
+ white-space: nowrap;
+}
+
+.jqplot-xaxis-label {
+ margin-top: 10px;
+ font-size: 11pt;
+ position: absolute;
+}
+
+.jqplot-x2axis-label {
+ margin-bottom: 10px;
+ font-size: 11pt;
+ position: absolute;
+}
+
+.jqplot-yaxis-label {
+ margin-right: 10px;
+/* text-align: center;*/
+ font-size: 11pt;
+ position: absolute;
+}
+
+.jqplot-yMidAxis-label {
+ font-size: 11pt;
+ position: absolute;
+}
+
+.jqplot-y2axis-label, .jqplot-y3axis-label, .jqplot-y4axis-label, .jqplot-y5axis-label, .jqplot-y6axis-label, .jqplot-y7axis-label, .jqplot-y8axis-label, .jqplot-y9axis-label {
+/* text-align: center;*/
+ font-size: 11pt;
+ margin-left: 10px;
+ position: absolute;
+}
+
+.jqplot-meterGauge-tick {
+ font-size: 0.75em;
+ color: #999999;
+}
+
+.jqplot-meterGauge-label {
+ font-size: 1em;
+ color: #999999;
+}
+
+table.jqplot-table-legend {
+ margin-top: 12px;
+ margin-bottom: 12px;
+ margin-left: 12px;
+ margin-right: 12px;
+}
+
+table.jqplot-table-legend, table.jqplot-cursor-legend {
+ background-color: rgba(255,255,255,0.6);
+ border: 1px solid #cccccc;
+ position: absolute;
+ font-size: 0.75em;
+}
+
+td.jqplot-table-legend {
+ vertical-align:middle;
+}
+
+/*
+These rules could be used instead of assigning
+element styles and relying on js object properties.
+*/
+
+/*
+td.jqplot-table-legend-swatch {
+ padding-top: 0.5em;
+ text-align: center;
+}
+
+tr.jqplot-table-legend:first td.jqplot-table-legend-swatch {
+ padding-top: 0px;
+}
+*/
+
+td.jqplot-seriesToggle:hover, td.jqplot-seriesToggle:active {
+ cursor: pointer;
+}
+
+.jqplot-table-legend .jqplot-series-hidden {
+ text-decoration: line-through;
+}
+
+div.jqplot-table-legend-swatch-outline {
+ border: 1px solid #cccccc;
+ padding:1px;
+}
+
+div.jqplot-table-legend-swatch {
+ width:0px;
+ height:0px;
+ border-top-width: 5px;
+ border-bottom-width: 5px;
+ border-left-width: 6px;
+ border-right-width: 6px;
+ border-top-style: solid;
+ border-bottom-style: solid;
+ border-left-style: solid;
+ border-right-style: solid;
+}
+
+.jqplot-title {
+ top: 0px;
+ left: 0px;
+ padding-bottom: 0.5em;
+ font-size: 1.2em;
+}
+
+table.jqplot-cursor-tooltip {
+ border: 1px solid #cccccc;
+ font-size: 0.75em;
+}
+
+
+.jqplot-cursor-tooltip {
+ border: 1px solid #cccccc;
+ font-size: 0.75em;
+ white-space: nowrap;
+ background: rgba(208,208,208,0.5);
+ padding: 1px;
+}
+
+.jqplot-highlighter-tooltip, .jqplot-canvasOverlay-tooltip {
+ border: 1px solid #cccccc;
+ font-size: 0.75em;
+ white-space: nowrap;
+ background: rgba(208,208,208,0.5);
+ padding: 1px;
+}
+
+.jqplot-point-label {
+ font-size: 0.75em;
+ z-index: 2;
+}
+
+td.jqplot-cursor-legend-swatch {
+ vertical-align: middle;
+ text-align: center;
+}
+
+div.jqplot-cursor-legend-swatch {
+ width: 1.2em;
+ height: 0.7em;
+}
+
+.jqplot-error {
+/* Styles added to the plot target container when there is an error go here.*/
+ text-align: center;
+}
+
+.jqplot-error-message {
+/* Styling of the custom error message div goes here.*/
+ position: relative;
+ top: 46%;
+ display: inline-block;
+}
+
+div.jqplot-bubble-label {
+ font-size: 0.8em;
+/* background: rgba(90%, 90%, 90%, 0.15);*/
+ padding-left: 2px;
+ padding-right: 2px;
+ color: rgb(20%, 20%, 20%);
+}
+
+div.jqplot-bubble-label.jqplot-bubble-label-highlight {
+ background: rgba(90%, 90%, 90%, 0.7);
+}
+
+div.jqplot-noData-container {
+ text-align: center;
+ background-color: rgba(96%, 96%, 96%, 0.3);
+}
+
+#relationalTable select {
+ width: 125px;
+ margin-right: 5px;
+}
+
+.report-data {
+ height:13em;
+ overflow:scroll;
+ width:570px;
+ border: solid 1px;
+ background: white;
+ padding: 2px;
+}
+
+.report-description {
+ height:10em;
+ width:570px;
+}
diff --git a/themes/original/css/navigation.css.php b/themes/original/css/navigation.css.php
new file mode 100644
index 0000000000..02147bc8a8
--- /dev/null
+++ b/themes/original/css/navigation.css.php
@@ -0,0 +1,296 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Navigation styles for the original theme
+ *
+ * @package PhpMyAdmin-theme
+ * @subpackage Original
+ */
+
+// unplanned execution path
+if (! defined('PMA_MINIMUM_COMMON') && ! defined('TESTSUITE')) {
+ exit();
+}
+?>
+
+/******************************************************************************/
+/* Navigation */
+
+#pma_navigation {
+ background: <?php echo $GLOBALS['cfg']['NaviBackground']; ?>;
+ color: <?php echo $GLOBALS['cfg']['NaviColor']; ?>;
+ width: <?php echo $GLOBALS['cfg']['NaviWidth']; ?>px;
+ overflow: hidden;
+ position: fixed;
+ top: 0;
+ <?php echo $left; ?>: 0;
+ height: 100%;
+ border-<?php echo $right; ?>: 1px solid gray;
+ z-index: 800;
+}
+
+#pma_navigation_content {
+ width: 100%;
+ height: 100%;
+ position: absolute;
+ top: 0;
+ <?php echo $left; ?>: 0;
+ z-index: 0;
+ padding-bottom: 1em;
+}
+
+#pma_navigation ul {
+ margin: 0;
+}
+
+#pma_navigation form {
+ margin: 0;
+ padding: 0;
+ display: inline;
+}
+
+#pma_navigation select#select_server,
+#pma_navigation select#lightm_db {
+ width: 100%;
+}
+
+/******************************************************************************/
+/* specific elements */
+
+#pma_navigation div.pageselector {
+ text-align: center;
+ margin: 0 0 0;
+ margin-<?php echo $left; ?>: 0.75em;
+ border-<?php echo $left; ?>: 1px solid #666;
+}
+
+#pma_navigation div#pmalogo {
+ <?php //better echo $GLOBALS['cfg']['logoBGC']; ?>
+ background-color: <?php echo $GLOBALS['cfg']['NaviBackground']; ?>;
+ padding: .3em;
+}
+
+#pma_navigation div#recentTableList {
+ text-align: center;
+ margin-bottom: 0.5em;
+}
+
+#pma_navigation #pmalogo,
+#pma_navigation #serverChoice,
+#pma_navigation #leftframelinks,
+#pma_navigation #recentTableList,
+#pma_navigation #databaseList,
+#pma_navigation div.pageselector.dbselector {
+ text-align: center;
+ margin-bottom: 0.3em;
+ padding-bottom: 0.3em;
+ border: 0;
+}
+
+#pma_navigation #recentTableList select,
+#pma_navigation #serverChoice select
+ {
+ width: 80%;
+}
+
+#pma_navigation #recentTableList {
+ margin-bottom: 0;
+ padding-bottom: 0;
+}
+
+#pma_navigation_content > img.throbber {
+ display: block;
+ margin: 0 auto;
+}
+
+/* Navigation tree*/
+#pma_navigation_tree {
+ margin: 0;
+ margin-<?php echo $left; ?>: 1em;
+ color: #444;
+ height: 74%;
+ position: relative;
+}
+#pma_navigation_tree_content {
+ width: 100%;
+ overflow: hidden;
+ overflow-y: auto;
+ position: absolute;
+ height: 100%;
+}
+#pma_navigation_tree a {
+ color: <?php echo $GLOBALS['cfg']['NaviColor']; ?>;
+}
+#pma_navigation_tree a:hover {
+ text-decoration: underline;
+}
+#pma_navigation_tree li.activePointer {
+ color: <?php echo $GLOBALS['cfg']['NaviPointerColor']; ?>;
+ background-color: <?php echo $GLOBALS['cfg']['NaviPointerBackground']; ?>;
+}
+#pma_navigation_tree li.selected {
+ color: <?php echo $GLOBALS['cfg']['NaviPointerColor']; ?>;
+ background-color: <?php echo $GLOBALS['cfg']['NaviPointerBackground']; ?>;
+}
+#pma_navigation_tree li .dbItemControls {
+ padding-left: 4px;
+}
+#pma_navigation_tree li .navItemControls {
+ display: none;
+ padding-left: 4px;
+}
+#pma_navigation_tree li.activePointer .navItemControls {
+ display: inline;
+ opacity: 0.5;
+}
+#pma_navigation_tree li.activePointer .navItemControls:hover {
+ display: inline;
+ opacity: 1.0;
+}
+#pma_navigation_tree ul {
+ clear: both;
+ padding: 0;
+ list-style-type: none;
+ margin: 0;
+}
+#pma_navigation_tree ul ul {
+ position: relative;
+}
+#pma_navigation_tree li {
+ white-space: nowrap;
+ clear: both;
+ min-height: 16px;
+}
+#pma_navigation_tree img {
+ margin: 0;
+}
+#pma_navigation_tree div.block {
+ position: relative;
+ width:1.5em;
+ height:1.5em;
+ min-width: 16px;
+ min-height: 16px;
+ float: <?php echo $left; ?>;
+}
+#pma_navigation_tree div.block i,
+#pma_navigation_tree div.block b {
+ width: 1.5em;
+ height: 1.5em;
+ min-width: 16px;
+ min-height: 8px;
+ position: absolute;
+ bottom: 0.7em;
+ <?php echo $left; ?>: 0.75em;
+ z-index: 0;
+}
+#pma_navigation_tree div.block i {
+ border-<?php echo $left; ?>: 1px solid #666;
+ border-bottom: 1px solid #666;
+}
+#pma_navigation_tree div.block i.first { /* Removes top segment */
+ border-<?php echo $left; ?>: 0;
+}
+/* Bottom segment for the tree element connections */
+#pma_navigation_tree div.block b {
+ display: block;
+ height: 0.75em;
+ bottom: 0;
+ <?php echo $left; ?>: 0.75em;
+ border-<?php echo $left; ?>: 1px solid #666;
+}
+#pma_navigation_tree div.block a,
+#pma_navigation_tree div.block u {
+ position: absolute;
+ <?php echo $left; ?>: 50%;
+ top: 50%;
+ z-index: 10;
+}
+#pma_navigation_tree div.block img {
+ position: relative;
+ top: -0.6em;
+ <?php echo $left; ?>: 0;
+ margin-<?php echo $left; ?>: -5px;
+}
+#pma_navigation_tree li.last > ul {
+ background: none;
+}
+#pma_navigation_tree li > a, #pma_navigation_tree li > i {
+ line-height: 1.5em;
+ height: 1.5em;
+ padding-<?php echo $left; ?>: 0.3em;
+}
+#pma_navigation_tree .list_container {
+ border-<?php echo $left; ?>: 1px solid #666;
+ margin-<?php echo $left; ?>: 0.75em;
+ padding-<?php echo $left; ?>: 0.75em;
+}
+#pma_navigation_tree .last > .list_container {
+ border-<?php echo $left; ?>: 0 solid #666;
+}
+
+/* Fast filter */
+li.fast_filter {
+ padding-<?php echo $left; ?>: 0.75em;
+ margin-<?php echo $left; ?>: 0.75em;
+ padding-<?php echo $right; ?>: 35px;
+ border-<?php echo $left; ?>: 1px solid #666;
+}
+li.fast_filter input {
+ padding-<?php echo $right; ?>: 1.7em;
+ width: 100%;
+}
+li.fast_filter span {
+ position: relative;
+ <?php echo $right; ?>: 1.5em;
+ padding: 0.2em;
+ cursor: pointer;
+ font-weight: bold;
+ color: #800;
+}
+/* IE10+ has its own reset X */
+html.ie li.fast_filter span {
+ display: none;
+}
+html.ie.ie9 li.fast_filter span,
+html.ie.ie8 li.fast_filter span {
+ display: auto;
+}
+html.ie li.fast_filter input {
+ padding-<?php echo $right; ?>: .2em;
+}
+html.ie.ie9 li.fast_filter input,
+html.ie.ie8 li.fast_filter input {
+ padding-<?php echo $right; ?>: 1.7em;
+}
+li.fast_filter.db_fast_filter {
+ border: 0;
+}
+
+/* Resize handler */
+#pma_navigation_resizer {
+ width: 3px;
+ height: 100%;
+ background-color: #aaa;
+ cursor: col-resize;
+ position: fixed;
+ top: 0;
+ <?php echo $left; ?>: 240px;
+ z-index: 801;
+}
+#pma_navigation_collapser {
+ width: 20px;
+ height: 22px;
+ line-height: 22px;
+ background: #eee;
+ color: #555;
+ font-weight: bold;
+ position: fixed;
+ top: 0;
+ <?php echo $left; ?>: <?php echo $GLOBALS['cfg']['NaviWidth']; ?>px;
+ text-align: center;
+ cursor: pointer;
+ z-index: 800;
+ text-shadow: 0px 1px 0px #fff;
+ filter: dropshadow(color=#fff, offx=0, offy=1);
+ border: 1px solid #888;
+}
diff --git a/themes/original/img/ajax_clock_small.gif b/themes/original/img/ajax_clock_small.gif
new file mode 100644
index 0000000000..bde4932c85
--- /dev/null
+++ b/themes/original/img/ajax_clock_small.gif
Binary files differ
diff --git a/themes/original/img/arrow_ltr.png b/themes/original/img/arrow_ltr.png
new file mode 100644
index 0000000000..7ff8ed9c40
--- /dev/null
+++ b/themes/original/img/arrow_ltr.png
Binary files differ
diff --git a/themes/original/img/arrow_rtl.png b/themes/original/img/arrow_rtl.png
new file mode 100644
index 0000000000..0192d10364
--- /dev/null
+++ b/themes/original/img/arrow_rtl.png
Binary files differ
diff --git a/themes/original/img/b_bookmark.png b/themes/original/img/b_bookmark.png
new file mode 100644
index 0000000000..e2afe3f8bf
--- /dev/null
+++ b/themes/original/img/b_bookmark.png
Binary files differ
diff --git a/themes/original/img/b_browse.png b/themes/original/img/b_browse.png
new file mode 100644
index 0000000000..1d88b2ae76
--- /dev/null
+++ b/themes/original/img/b_browse.png
Binary files differ
diff --git a/themes/original/img/b_calendar.png b/themes/original/img/b_calendar.png
new file mode 100644
index 0000000000..34381b3991
--- /dev/null
+++ b/themes/original/img/b_calendar.png
Binary files differ
diff --git a/themes/original/img/b_chart.png b/themes/original/img/b_chart.png
new file mode 100644
index 0000000000..0e2cc49dd8
--- /dev/null
+++ b/themes/original/img/b_chart.png
Binary files differ
diff --git a/themes/original/img/b_close.png b/themes/original/img/b_close.png
new file mode 100644
index 0000000000..2c234b6f64
--- /dev/null
+++ b/themes/original/img/b_close.png
Binary files differ
diff --git a/themes/original/img/b_column_add.png b/themes/original/img/b_column_add.png
new file mode 100644
index 0000000000..25dc54637a
--- /dev/null
+++ b/themes/original/img/b_column_add.png
Binary files differ
diff --git a/themes/original/img/b_comment.png b/themes/original/img/b_comment.png
new file mode 100644
index 0000000000..203c880528
--- /dev/null
+++ b/themes/original/img/b_comment.png
Binary files differ
diff --git a/themes/original/img/b_dbstatistics.png b/themes/original/img/b_dbstatistics.png
new file mode 100644
index 0000000000..bfb2ad5497
--- /dev/null
+++ b/themes/original/img/b_dbstatistics.png
Binary files differ
diff --git a/themes/original/img/b_deltbl.png b/themes/original/img/b_deltbl.png
new file mode 100644
index 0000000000..0e3fb2995d
--- /dev/null
+++ b/themes/original/img/b_deltbl.png
Binary files differ
diff --git a/themes/original/img/b_docs.png b/themes/original/img/b_docs.png
new file mode 100644
index 0000000000..46e2d2c25c
--- /dev/null
+++ b/themes/original/img/b_docs.png
Binary files differ
diff --git a/themes/original/img/b_drop.png b/themes/original/img/b_drop.png
new file mode 100644
index 0000000000..510bb28885
--- /dev/null
+++ b/themes/original/img/b_drop.png
Binary files differ
diff --git a/themes/original/img/b_edit.png b/themes/original/img/b_edit.png
new file mode 100644
index 0000000000..d2a5095813
--- /dev/null
+++ b/themes/original/img/b_edit.png
Binary files differ
diff --git a/themes/original/img/b_empty.png b/themes/original/img/b_empty.png
new file mode 100644
index 0000000000..6fa18adde8
--- /dev/null
+++ b/themes/original/img/b_empty.png
Binary files differ
diff --git a/themes/original/img/b_engine.png b/themes/original/img/b_engine.png
new file mode 100644
index 0000000000..c8019fdd0c
--- /dev/null
+++ b/themes/original/img/b_engine.png
Binary files differ
diff --git a/themes/original/img/b_event_add.png b/themes/original/img/b_event_add.png
new file mode 100644
index 0000000000..ef594b1098
--- /dev/null
+++ b/themes/original/img/b_event_add.png
Binary files differ
diff --git a/themes/original/img/b_events.png b/themes/original/img/b_events.png
new file mode 100644
index 0000000000..86bcc87f19
--- /dev/null
+++ b/themes/original/img/b_events.png
Binary files differ
diff --git a/themes/original/img/b_export.png b/themes/original/img/b_export.png
new file mode 100644
index 0000000000..1a6d1598df
--- /dev/null
+++ b/themes/original/img/b_export.png
Binary files differ
diff --git a/themes/original/img/b_find_replace.png b/themes/original/img/b_find_replace.png
new file mode 100644
index 0000000000..8346a0e443
--- /dev/null
+++ b/themes/original/img/b_find_replace.png
Binary files differ
diff --git a/themes/original/img/b_ftext.png b/themes/original/img/b_ftext.png
new file mode 100644
index 0000000000..6014ebc2de
--- /dev/null
+++ b/themes/original/img/b_ftext.png
Binary files differ
diff --git a/themes/original/img/b_globe.gif b/themes/original/img/b_globe.gif
new file mode 100644
index 0000000000..ef03dcf061
--- /dev/null
+++ b/themes/original/img/b_globe.gif
Binary files differ
diff --git a/themes/original/img/b_group.png b/themes/original/img/b_group.png
new file mode 100644
index 0000000000..4530760bad
--- /dev/null
+++ b/themes/original/img/b_group.png
Binary files differ
diff --git a/themes/original/img/b_help.png b/themes/original/img/b_help.png
new file mode 100644
index 0000000000..2fe513b541
--- /dev/null
+++ b/themes/original/img/b_help.png
Binary files differ
diff --git a/themes/original/img/b_home.png b/themes/original/img/b_home.png
new file mode 100644
index 0000000000..ea03206c0a
--- /dev/null
+++ b/themes/original/img/b_home.png
Binary files differ
diff --git a/themes/original/img/b_import.png b/themes/original/img/b_import.png
new file mode 100644
index 0000000000..9356a3ae6a
--- /dev/null
+++ b/themes/original/img/b_import.png
Binary files differ
diff --git a/themes/original/img/b_index.png b/themes/original/img/b_index.png
new file mode 100644
index 0000000000..11064c1b4e
--- /dev/null
+++ b/themes/original/img/b_index.png
Binary files differ
diff --git a/themes/original/img/b_index_add.png b/themes/original/img/b_index_add.png
new file mode 100644
index 0000000000..e51a622d33
--- /dev/null
+++ b/themes/original/img/b_index_add.png
Binary files differ
diff --git a/themes/original/img/b_info.png b/themes/original/img/b_info.png
new file mode 100644
index 0000000000..cfd49e5282
--- /dev/null
+++ b/themes/original/img/b_info.png
Binary files differ
diff --git a/themes/original/img/b_inline_edit.png b/themes/original/img/b_inline_edit.png
new file mode 100644
index 0000000000..01335bef80
--- /dev/null
+++ b/themes/original/img/b_inline_edit.png
Binary files differ
diff --git a/themes/original/img/b_insrow.png b/themes/original/img/b_insrow.png
new file mode 100644
index 0000000000..0532871181
--- /dev/null
+++ b/themes/original/img/b_insrow.png
Binary files differ
diff --git a/themes/original/img/b_minus.png b/themes/original/img/b_minus.png
new file mode 100644
index 0000000000..e28166fe85
--- /dev/null
+++ b/themes/original/img/b_minus.png
Binary files differ
diff --git a/themes/original/img/b_more.png b/themes/original/img/b_more.png
new file mode 100644
index 0000000000..681f862beb
--- /dev/null
+++ b/themes/original/img/b_more.png
Binary files differ
diff --git a/themes/original/img/b_move.png b/themes/original/img/b_move.png
new file mode 100644
index 0000000000..1ce3b1cb38
--- /dev/null
+++ b/themes/original/img/b_move.png
Binary files differ
diff --git a/themes/original/img/b_newdb.png b/themes/original/img/b_newdb.png
new file mode 100644
index 0000000000..fccc394944
--- /dev/null
+++ b/themes/original/img/b_newdb.png
Binary files differ
diff --git a/themes/original/img/b_newtbl.png b/themes/original/img/b_newtbl.png
new file mode 100644
index 0000000000..7402ad88c4
--- /dev/null
+++ b/themes/original/img/b_newtbl.png
Binary files differ
diff --git a/themes/original/img/b_nextpage.png b/themes/original/img/b_nextpage.png
new file mode 100644
index 0000000000..6169d5386e
--- /dev/null
+++ b/themes/original/img/b_nextpage.png
Binary files differ
diff --git a/themes/original/img/b_plus.png b/themes/original/img/b_plus.png
new file mode 100644
index 0000000000..90c15d8cad
--- /dev/null
+++ b/themes/original/img/b_plus.png
Binary files differ
diff --git a/themes/original/img/b_primary.png b/themes/original/img/b_primary.png
new file mode 100644
index 0000000000..94f2407327
--- /dev/null
+++ b/themes/original/img/b_primary.png
Binary files differ
diff --git a/themes/original/img/b_print.png b/themes/original/img/b_print.png
new file mode 100644
index 0000000000..9e5df80e79
--- /dev/null
+++ b/themes/original/img/b_print.png
Binary files differ
diff --git a/themes/original/img/b_props.png b/themes/original/img/b_props.png
new file mode 100644
index 0000000000..5ea2251b00
--- /dev/null
+++ b/themes/original/img/b_props.png
Binary files differ
diff --git a/themes/original/img/b_relations.png b/themes/original/img/b_relations.png
new file mode 100644
index 0000000000..0ef2521dc2
--- /dev/null
+++ b/themes/original/img/b_relations.png
Binary files differ
diff --git a/themes/original/img/b_routine_add.png b/themes/original/img/b_routine_add.png
new file mode 100644
index 0000000000..2d14442601
--- /dev/null
+++ b/themes/original/img/b_routine_add.png
Binary files differ
diff --git a/themes/original/img/b_routines.png b/themes/original/img/b_routines.png
new file mode 100644
index 0000000000..2cc102d3ee
--- /dev/null
+++ b/themes/original/img/b_routines.png
Binary files differ
diff --git a/themes/original/img/b_save.png b/themes/original/img/b_save.png
new file mode 100644
index 0000000000..29d03b4640
--- /dev/null
+++ b/themes/original/img/b_save.png
Binary files differ
diff --git a/themes/original/img/b_sbrowse.png b/themes/original/img/b_sbrowse.png
new file mode 100644
index 0000000000..98e1254d9e
--- /dev/null
+++ b/themes/original/img/b_sbrowse.png
Binary files differ
diff --git a/themes/original/img/b_search.png b/themes/original/img/b_search.png
new file mode 100644
index 0000000000..13f6ee85b8
--- /dev/null
+++ b/themes/original/img/b_search.png
Binary files differ
diff --git a/themes/original/img/b_selboard.png b/themes/original/img/b_selboard.png
new file mode 100644
index 0000000000..c54a20fee9
--- /dev/null
+++ b/themes/original/img/b_selboard.png
Binary files differ
diff --git a/themes/original/img/b_select.png b/themes/original/img/b_select.png
new file mode 100644
index 0000000000..7f95764eef
--- /dev/null
+++ b/themes/original/img/b_select.png
Binary files differ
diff --git a/themes/original/img/b_snewtbl.png b/themes/original/img/b_snewtbl.png
new file mode 100644
index 0000000000..f881dfd357
--- /dev/null
+++ b/themes/original/img/b_snewtbl.png
Binary files differ
diff --git a/themes/original/img/b_spatial.png b/themes/original/img/b_spatial.png
new file mode 100644
index 0000000000..5e6c8c727b
--- /dev/null
+++ b/themes/original/img/b_spatial.png
Binary files differ
diff --git a/themes/original/img/b_sql.png b/themes/original/img/b_sql.png
new file mode 100644
index 0000000000..c1c387e5bf
--- /dev/null
+++ b/themes/original/img/b_sql.png
Binary files differ
diff --git a/themes/original/img/b_sqlhelp.png b/themes/original/img/b_sqlhelp.png
new file mode 100644
index 0000000000..c9447b683c
--- /dev/null
+++ b/themes/original/img/b_sqlhelp.png
Binary files differ
diff --git a/themes/original/img/b_table_add.png b/themes/original/img/b_table_add.png
new file mode 100644
index 0000000000..e3ee208250
--- /dev/null
+++ b/themes/original/img/b_table_add.png
Binary files differ
diff --git a/themes/original/img/b_tblanalyse.png b/themes/original/img/b_tblanalyse.png
new file mode 100644
index 0000000000..604f1d5f15
--- /dev/null
+++ b/themes/original/img/b_tblanalyse.png
Binary files differ
diff --git a/themes/original/img/b_tblexport.png b/themes/original/img/b_tblexport.png
new file mode 100644
index 0000000000..15ede20562
--- /dev/null
+++ b/themes/original/img/b_tblexport.png
Binary files differ
diff --git a/themes/original/img/b_tblimport.png b/themes/original/img/b_tblimport.png
new file mode 100644
index 0000000000..4a998800f1
--- /dev/null
+++ b/themes/original/img/b_tblimport.png
Binary files differ
diff --git a/themes/original/img/b_tblops.png b/themes/original/img/b_tblops.png
new file mode 100644
index 0000000000..da5dbc16d5
--- /dev/null
+++ b/themes/original/img/b_tblops.png
Binary files differ
diff --git a/themes/original/img/b_tbloptimize.png b/themes/original/img/b_tbloptimize.png
new file mode 100644
index 0000000000..04e7c3ebd3
--- /dev/null
+++ b/themes/original/img/b_tbloptimize.png
Binary files differ
diff --git a/themes/original/img/b_tipp.png b/themes/original/img/b_tipp.png
new file mode 100644
index 0000000000..ef73a7afe4
--- /dev/null
+++ b/themes/original/img/b_tipp.png
Binary files differ
diff --git a/themes/original/img/b_trigger_add.png b/themes/original/img/b_trigger_add.png
new file mode 100644
index 0000000000..8a754dfef4
--- /dev/null
+++ b/themes/original/img/b_trigger_add.png
Binary files differ
diff --git a/themes/original/img/b_triggers.png b/themes/original/img/b_triggers.png
new file mode 100644
index 0000000000..84a89ef4ce
--- /dev/null
+++ b/themes/original/img/b_triggers.png
Binary files differ
diff --git a/themes/original/img/b_undo.png b/themes/original/img/b_undo.png
new file mode 100644
index 0000000000..498187821c
--- /dev/null
+++ b/themes/original/img/b_undo.png
Binary files differ
diff --git a/themes/original/img/b_unique.png b/themes/original/img/b_unique.png
new file mode 100644
index 0000000000..40c9b5dbbb
--- /dev/null
+++ b/themes/original/img/b_unique.png
Binary files differ
diff --git a/themes/original/img/b_usradd.png b/themes/original/img/b_usradd.png
new file mode 100644
index 0000000000..9af3f792e5
--- /dev/null
+++ b/themes/original/img/b_usradd.png
Binary files differ
diff --git a/themes/original/img/b_usrcheck.png b/themes/original/img/b_usrcheck.png
new file mode 100644
index 0000000000..96c77f4192
--- /dev/null
+++ b/themes/original/img/b_usrcheck.png
Binary files differ
diff --git a/themes/original/img/b_usrdrop.png b/themes/original/img/b_usrdrop.png
new file mode 100644
index 0000000000..8598235441
--- /dev/null
+++ b/themes/original/img/b_usrdrop.png
Binary files differ
diff --git a/themes/original/img/b_usredit.png b/themes/original/img/b_usredit.png
new file mode 100644
index 0000000000..ac4af9b362
--- /dev/null
+++ b/themes/original/img/b_usredit.png
Binary files differ
diff --git a/themes/original/img/b_usrlist.png b/themes/original/img/b_usrlist.png
new file mode 100644
index 0000000000..42d02d51aa
--- /dev/null
+++ b/themes/original/img/b_usrlist.png
Binary files differ
diff --git a/themes/original/img/b_view.png b/themes/original/img/b_view.png
new file mode 100644
index 0000000000..96f90eb2e0
--- /dev/null
+++ b/themes/original/img/b_view.png
Binary files differ
diff --git a/themes/original/img/b_view_add.png b/themes/original/img/b_view_add.png
new file mode 100644
index 0000000000..6f7a11ea2d
--- /dev/null
+++ b/themes/original/img/b_view_add.png
Binary files differ
diff --git a/themes/original/img/b_views.png b/themes/original/img/b_views.png
new file mode 100644
index 0000000000..c2d9f5baf0
--- /dev/null
+++ b/themes/original/img/b_views.png
Binary files differ
diff --git a/themes/original/img/bd_browse.png b/themes/original/img/bd_browse.png
new file mode 100644
index 0000000000..18a12111a3
--- /dev/null
+++ b/themes/original/img/bd_browse.png
Binary files differ
diff --git a/themes/original/img/bd_deltbl.png b/themes/original/img/bd_deltbl.png
new file mode 100644
index 0000000000..3f91af21eb
--- /dev/null
+++ b/themes/original/img/bd_deltbl.png
Binary files differ
diff --git a/themes/original/img/bd_drop.png b/themes/original/img/bd_drop.png
new file mode 100644
index 0000000000..97a3da5f80
--- /dev/null
+++ b/themes/original/img/bd_drop.png
Binary files differ
diff --git a/themes/original/img/bd_edit.png b/themes/original/img/bd_edit.png
new file mode 100644
index 0000000000..e81ccd854f
--- /dev/null
+++ b/themes/original/img/bd_edit.png
Binary files differ
diff --git a/themes/original/img/bd_empty.png b/themes/original/img/bd_empty.png
new file mode 100644
index 0000000000..7c4ae3e157
--- /dev/null
+++ b/themes/original/img/bd_empty.png
Binary files differ
diff --git a/themes/original/img/bd_export.png b/themes/original/img/bd_export.png
new file mode 100644
index 0000000000..bb697b074d
--- /dev/null
+++ b/themes/original/img/bd_export.png
Binary files differ
diff --git a/themes/original/img/bd_ftext.png b/themes/original/img/bd_ftext.png
new file mode 100644
index 0000000000..fb0c3026af
--- /dev/null
+++ b/themes/original/img/bd_ftext.png
Binary files differ
diff --git a/themes/original/img/bd_index.png b/themes/original/img/bd_index.png
new file mode 100644
index 0000000000..d6e417c0c8
--- /dev/null
+++ b/themes/original/img/bd_index.png
Binary files differ
diff --git a/themes/original/img/bd_insrow.png b/themes/original/img/bd_insrow.png
new file mode 100644
index 0000000000..5162577edb
--- /dev/null
+++ b/themes/original/img/bd_insrow.png
Binary files differ
diff --git a/themes/original/img/bd_nextpage.png b/themes/original/img/bd_nextpage.png
new file mode 100644
index 0000000000..f8e25f5e3e
--- /dev/null
+++ b/themes/original/img/bd_nextpage.png
Binary files differ
diff --git a/themes/original/img/bd_primary.png b/themes/original/img/bd_primary.png
new file mode 100644
index 0000000000..58f9cfdb6b
--- /dev/null
+++ b/themes/original/img/bd_primary.png
Binary files differ
diff --git a/themes/original/img/bd_sbrowse.png b/themes/original/img/bd_sbrowse.png
new file mode 100644
index 0000000000..85395990c9
--- /dev/null
+++ b/themes/original/img/bd_sbrowse.png
Binary files differ
diff --git a/themes/original/img/bd_select.png b/themes/original/img/bd_select.png
new file mode 100644
index 0000000000..1659a65330
--- /dev/null
+++ b/themes/original/img/bd_select.png
Binary files differ
diff --git a/themes/original/img/bd_spatial.png b/themes/original/img/bd_spatial.png
new file mode 100644
index 0000000000..d1ee4d62e8
--- /dev/null
+++ b/themes/original/img/bd_spatial.png
Binary files differ
diff --git a/themes/original/img/bd_unique.png b/themes/original/img/bd_unique.png
new file mode 100644
index 0000000000..ee0fde9aad
--- /dev/null
+++ b/themes/original/img/bd_unique.png
Binary files differ
diff --git a/themes/original/img/cleardot.gif b/themes/original/img/cleardot.gif
new file mode 100644
index 0000000000..a9d7bea4c4
--- /dev/null
+++ b/themes/original/img/cleardot.gif
Binary files differ
diff --git a/themes/original/img/col_drop.png b/themes/original/img/col_drop.png
new file mode 100644
index 0000000000..681f862beb
--- /dev/null
+++ b/themes/original/img/col_drop.png
Binary files differ
diff --git a/themes/original/img/col_pointer.png b/themes/original/img/col_pointer.png
new file mode 100644
index 0000000000..1fb353fea7
--- /dev/null
+++ b/themes/original/img/col_pointer.png
Binary files differ
diff --git a/themes/original/img/col_pointer_ver.png b/themes/original/img/col_pointer_ver.png
new file mode 100644
index 0000000000..02977f2e4a
--- /dev/null
+++ b/themes/original/img/col_pointer_ver.png
Binary files differ
diff --git a/themes/original/img/east-mini.png b/themes/original/img/east-mini.png
new file mode 100644
index 0000000000..bee419d6e0
--- /dev/null
+++ b/themes/original/img/east-mini.png
Binary files differ
diff --git a/themes/original/img/error.ico b/themes/original/img/error.ico
new file mode 100644
index 0000000000..b5c0618337
--- /dev/null
+++ b/themes/original/img/error.ico
Binary files differ
diff --git a/themes/original/img/eye.png b/themes/original/img/eye.png
new file mode 100644
index 0000000000..ed38db23b0
--- /dev/null
+++ b/themes/original/img/eye.png
Binary files differ
diff --git a/themes/original/img/eye_grey.png b/themes/original/img/eye_grey.png
new file mode 100644
index 0000000000..6fcae47405
--- /dev/null
+++ b/themes/original/img/eye_grey.png
Binary files differ
diff --git a/themes/original/img/lightbulb.png b/themes/original/img/lightbulb.png
new file mode 100644
index 0000000000..d22fde8ba4
--- /dev/null
+++ b/themes/original/img/lightbulb.png
Binary files differ
diff --git a/themes/original/img/lightbulb_off.png b/themes/original/img/lightbulb_off.png
new file mode 100644
index 0000000000..e95b8c5b12
--- /dev/null
+++ b/themes/original/img/lightbulb_off.png
Binary files differ
diff --git a/themes/original/img/logo_left.png b/themes/original/img/logo_left.png
new file mode 100644
index 0000000000..e24bcc3457
--- /dev/null
+++ b/themes/original/img/logo_left.png
Binary files differ
diff --git a/themes/original/img/logo_right.png b/themes/original/img/logo_right.png
new file mode 100644
index 0000000000..d61c6284ca
--- /dev/null
+++ b/themes/original/img/logo_right.png
Binary files differ
diff --git a/themes/original/img/more.png b/themes/original/img/more.png
new file mode 100644
index 0000000000..32aaf61a80
--- /dev/null
+++ b/themes/original/img/more.png
Binary files differ
diff --git a/themes/original/img/new_data.png b/themes/original/img/new_data.png
new file mode 100644
index 0000000000..6f4e1863b1
--- /dev/null
+++ b/themes/original/img/new_data.png
Binary files differ
diff --git a/themes/original/img/new_data_hovered.png b/themes/original/img/new_data_hovered.png
new file mode 100644
index 0000000000..a470dbbc55
--- /dev/null
+++ b/themes/original/img/new_data_hovered.png
Binary files differ
diff --git a/themes/original/img/new_data_selected.png b/themes/original/img/new_data_selected.png
new file mode 100644
index 0000000000..a75abe347d
--- /dev/null
+++ b/themes/original/img/new_data_selected.png
Binary files differ
diff --git a/themes/original/img/new_data_selected_hovered.png b/themes/original/img/new_data_selected_hovered.png
new file mode 100644
index 0000000000..04a2ad8424
--- /dev/null
+++ b/themes/original/img/new_data_selected_hovered.png
Binary files differ
diff --git a/themes/original/img/new_struct.png b/themes/original/img/new_struct.png
new file mode 100644
index 0000000000..6b77c137d3
--- /dev/null
+++ b/themes/original/img/new_struct.png
Binary files differ
diff --git a/themes/original/img/new_struct_hovered.png b/themes/original/img/new_struct_hovered.png
new file mode 100644
index 0000000000..9c353c65b7
--- /dev/null
+++ b/themes/original/img/new_struct_hovered.png
Binary files differ
diff --git a/themes/original/img/new_struct_selected.png b/themes/original/img/new_struct_selected.png
new file mode 100644
index 0000000000..142bf11537
--- /dev/null
+++ b/themes/original/img/new_struct_selected.png
Binary files differ
diff --git a/themes/original/img/new_struct_selected_hovered.png b/themes/original/img/new_struct_selected_hovered.png
new file mode 100644
index 0000000000..9a82bc4223
--- /dev/null
+++ b/themes/original/img/new_struct_selected_hovered.png
Binary files differ
diff --git a/themes/original/img/north-mini.png b/themes/original/img/north-mini.png
new file mode 100644
index 0000000000..8283839c96
--- /dev/null
+++ b/themes/original/img/north-mini.png
Binary files differ
diff --git a/themes/original/img/pause.png b/themes/original/img/pause.png
new file mode 100644
index 0000000000..46a6318d39
--- /dev/null
+++ b/themes/original/img/pause.png
Binary files differ
diff --git a/themes/original/img/play.png b/themes/original/img/play.png
new file mode 100644
index 0000000000..6169d5386e
--- /dev/null
+++ b/themes/original/img/play.png
Binary files differ
diff --git a/themes/original/img/s_asc.png b/themes/original/img/s_asc.png
new file mode 100644
index 0000000000..3e5050f2be
--- /dev/null
+++ b/themes/original/img/s_asc.png
Binary files differ
diff --git a/themes/original/img/s_asci.png b/themes/original/img/s_asci.png
new file mode 100644
index 0000000000..d160eb1075
--- /dev/null
+++ b/themes/original/img/s_asci.png
Binary files differ
diff --git a/themes/original/img/s_cancel.png b/themes/original/img/s_cancel.png
new file mode 100644
index 0000000000..fd782ab398
--- /dev/null
+++ b/themes/original/img/s_cancel.png
Binary files differ
diff --git a/themes/original/img/s_cog.png b/themes/original/img/s_cog.png
new file mode 100644
index 0000000000..b8c9713cf1
--- /dev/null
+++ b/themes/original/img/s_cog.png
Binary files differ
diff --git a/themes/original/img/s_db.png b/themes/original/img/s_db.png
new file mode 100644
index 0000000000..0f723a9abe
--- /dev/null
+++ b/themes/original/img/s_db.png
Binary files differ
diff --git a/themes/original/img/s_desc.png b/themes/original/img/s_desc.png
new file mode 100644
index 0000000000..7dcb98b990
--- /dev/null
+++ b/themes/original/img/s_desc.png
Binary files differ
diff --git a/themes/original/img/s_error.png b/themes/original/img/s_error.png
new file mode 100644
index 0000000000..9db50e5b93
--- /dev/null
+++ b/themes/original/img/s_error.png
Binary files differ
diff --git a/themes/original/img/s_error2.png b/themes/original/img/s_error2.png
new file mode 100644
index 0000000000..e4f02e9a87
--- /dev/null
+++ b/themes/original/img/s_error2.png
Binary files differ
diff --git a/themes/original/img/s_fulltext.png b/themes/original/img/s_fulltext.png
new file mode 100644
index 0000000000..9f8db13f81
--- /dev/null
+++ b/themes/original/img/s_fulltext.png
Binary files differ
diff --git a/themes/original/img/s_host.png b/themes/original/img/s_host.png
new file mode 100644
index 0000000000..3d30f1c5c2
--- /dev/null
+++ b/themes/original/img/s_host.png
Binary files differ
diff --git a/themes/original/img/s_info.png b/themes/original/img/s_info.png
new file mode 100644
index 0000000000..cfd49e5282
--- /dev/null
+++ b/themes/original/img/s_info.png
Binary files differ
diff --git a/themes/original/img/s_lang.png b/themes/original/img/s_lang.png
new file mode 100644
index 0000000000..d205d1940c
--- /dev/null
+++ b/themes/original/img/s_lang.png
Binary files differ
diff --git a/themes/original/img/s_loggoff.png b/themes/original/img/s_loggoff.png
new file mode 100644
index 0000000000..ec55b56eaa
--- /dev/null
+++ b/themes/original/img/s_loggoff.png
Binary files differ
diff --git a/themes/original/img/s_notice.png b/themes/original/img/s_notice.png
new file mode 100644
index 0000000000..aacc430758
--- /dev/null
+++ b/themes/original/img/s_notice.png
Binary files differ
diff --git a/themes/original/img/s_partialtext.png b/themes/original/img/s_partialtext.png
new file mode 100644
index 0000000000..e671140169
--- /dev/null
+++ b/themes/original/img/s_partialtext.png
Binary files differ
diff --git a/themes/original/img/s_passwd.png b/themes/original/img/s_passwd.png
new file mode 100644
index 0000000000..82d6f26476
--- /dev/null
+++ b/themes/original/img/s_passwd.png
Binary files differ
diff --git a/themes/original/img/s_really.png b/themes/original/img/s_really.png
new file mode 100644
index 0000000000..f9902ef07c
--- /dev/null
+++ b/themes/original/img/s_really.png
Binary files differ
diff --git a/themes/original/img/s_reload.png b/themes/original/img/s_reload.png
new file mode 100644
index 0000000000..0be96c7990
--- /dev/null
+++ b/themes/original/img/s_reload.png
Binary files differ
diff --git a/themes/original/img/s_replication.png b/themes/original/img/s_replication.png
new file mode 100644
index 0000000000..f51a177938
--- /dev/null
+++ b/themes/original/img/s_replication.png
Binary files differ
diff --git a/themes/original/img/s_rights.png b/themes/original/img/s_rights.png
new file mode 100644
index 0000000000..fce7a81326
--- /dev/null
+++ b/themes/original/img/s_rights.png
Binary files differ
diff --git a/themes/original/img/s_sortable.png b/themes/original/img/s_sortable.png
new file mode 100644
index 0000000000..361a14a9a6
--- /dev/null
+++ b/themes/original/img/s_sortable.png
Binary files differ
diff --git a/themes/original/img/s_status.png b/themes/original/img/s_status.png
new file mode 100644
index 0000000000..0680c59f50
--- /dev/null
+++ b/themes/original/img/s_status.png
Binary files differ
diff --git a/themes/original/img/s_success.png b/themes/original/img/s_success.png
new file mode 100644
index 0000000000..d2e6121077
--- /dev/null
+++ b/themes/original/img/s_success.png
Binary files differ
diff --git a/themes/original/img/s_sync.png b/themes/original/img/s_sync.png
new file mode 100644
index 0000000000..8545ba1b83
--- /dev/null
+++ b/themes/original/img/s_sync.png
Binary files differ
diff --git a/themes/original/img/s_tbl.png b/themes/original/img/s_tbl.png
new file mode 100644
index 0000000000..6ae8c4d94a
--- /dev/null
+++ b/themes/original/img/s_tbl.png
Binary files differ
diff --git a/themes/original/img/s_theme.png b/themes/original/img/s_theme.png
new file mode 100644
index 0000000000..6196810abb
--- /dev/null
+++ b/themes/original/img/s_theme.png
Binary files differ
diff --git a/themes/original/img/s_top.png b/themes/original/img/s_top.png
new file mode 100644
index 0000000000..de05174ee0
--- /dev/null
+++ b/themes/original/img/s_top.png
Binary files differ
diff --git a/themes/original/img/s_vars.png b/themes/original/img/s_vars.png
new file mode 100644
index 0000000000..5bc92cdde4
--- /dev/null
+++ b/themes/original/img/s_vars.png
Binary files differ
diff --git a/themes/original/img/s_views.png b/themes/original/img/s_views.png
new file mode 100644
index 0000000000..fc4e1515f7
--- /dev/null
+++ b/themes/original/img/s_views.png
Binary files differ
diff --git a/themes/original/img/south-mini.png b/themes/original/img/south-mini.png
new file mode 100644
index 0000000000..954c202ba4
--- /dev/null
+++ b/themes/original/img/south-mini.png
Binary files differ
diff --git a/themes/original/img/spacer.png b/themes/original/img/spacer.png
new file mode 100644
index 0000000000..c6008d7c80
--- /dev/null
+++ b/themes/original/img/spacer.png
Binary files differ
diff --git a/themes/original/img/sprites.png b/themes/original/img/sprites.png
new file mode 100644
index 0000000000..efc9f02a37
--- /dev/null
+++ b/themes/original/img/sprites.png
Binary files differ
diff --git a/themes/original/img/toggle-ltr.png b/themes/original/img/toggle-ltr.png
new file mode 100644
index 0000000000..9d08d61532
--- /dev/null
+++ b/themes/original/img/toggle-ltr.png
Binary files differ
diff --git a/themes/original/img/toggle-rtl.png b/themes/original/img/toggle-rtl.png
new file mode 100644
index 0000000000..c2ef16f8d7
--- /dev/null
+++ b/themes/original/img/toggle-rtl.png
Binary files differ
diff --git a/themes/original/img/vertical_line.png b/themes/original/img/vertical_line.png
new file mode 100644
index 0000000000..188417be81
--- /dev/null
+++ b/themes/original/img/vertical_line.png
Binary files differ
diff --git a/themes/original/img/west-mini.png b/themes/original/img/west-mini.png
new file mode 100644
index 0000000000..a13f0835cd
--- /dev/null
+++ b/themes/original/img/west-mini.png
Binary files differ
diff --git a/themes/original/img/window-new.png b/themes/original/img/window-new.png
new file mode 100644
index 0000000000..431fe851dc
--- /dev/null
+++ b/themes/original/img/window-new.png
Binary files differ
diff --git a/themes/original/img/zoom-minus-mini.png b/themes/original/img/zoom-minus-mini.png
new file mode 100644
index 0000000000..4262ad4631
--- /dev/null
+++ b/themes/original/img/zoom-minus-mini.png
Binary files differ
diff --git a/themes/original/img/zoom-plus-mini.png b/themes/original/img/zoom-plus-mini.png
new file mode 100644
index 0000000000..4fabfd1ed1
--- /dev/null
+++ b/themes/original/img/zoom-plus-mini.png
Binary files differ
diff --git a/themes/original/img/zoom-world-mini.png b/themes/original/img/zoom-world-mini.png
new file mode 100644
index 0000000000..f50ca66f2b
--- /dev/null
+++ b/themes/original/img/zoom-world-mini.png
Binary files differ
diff --git a/themes/original/info.inc.php b/themes/original/info.inc.php
new file mode 100644
index 0000000000..28866d6bf3
--- /dev/null
+++ b/themes/original/info.inc.php
@@ -0,0 +1,15 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Theme information
+ *
+ * @package PhpMyAdmin-theme
+ * @subpackage Original
+ */
+
+/**
+ *
+ */
+$theme_name = 'Original';
+$theme_full_version = '2.9';
+?>
diff --git a/themes/original/jquery/images/ui-bg_flat_0_aaaaaa_40x100.png b/themes/original/jquery/images/ui-bg_flat_0_aaaaaa_40x100.png
new file mode 100644
index 0000000000..5b5dab2ab7
--- /dev/null
+++ b/themes/original/jquery/images/ui-bg_flat_0_aaaaaa_40x100.png
Binary files differ
diff --git a/themes/original/jquery/images/ui-bg_flat_75_ffffff_40x100.png b/themes/original/jquery/images/ui-bg_flat_75_ffffff_40x100.png
new file mode 100644
index 0000000000..ac8b229af9
--- /dev/null
+++ b/themes/original/jquery/images/ui-bg_flat_75_ffffff_40x100.png
Binary files differ
diff --git a/themes/original/jquery/images/ui-bg_glass_55_fbf9ee_1x400.png b/themes/original/jquery/images/ui-bg_glass_55_fbf9ee_1x400.png
new file mode 100644
index 0000000000..ad3d6346e0
--- /dev/null
+++ b/themes/original/jquery/images/ui-bg_glass_55_fbf9ee_1x400.png
Binary files differ
diff --git a/themes/original/jquery/images/ui-bg_glass_65_ffffff_1x400.png b/themes/original/jquery/images/ui-bg_glass_65_ffffff_1x400.png
new file mode 100644
index 0000000000..42ccba269b
--- /dev/null
+++ b/themes/original/jquery/images/ui-bg_glass_65_ffffff_1x400.png
Binary files differ
diff --git a/themes/original/jquery/images/ui-bg_glass_75_dadada_1x400.png b/themes/original/jquery/images/ui-bg_glass_75_dadada_1x400.png
new file mode 100644
index 0000000000..1d43b47e60
--- /dev/null
+++ b/themes/original/jquery/images/ui-bg_glass_75_dadada_1x400.png
Binary files differ
diff --git a/themes/original/jquery/images/ui-bg_glass_75_e6e6e6_1x400.png b/themes/original/jquery/images/ui-bg_glass_75_e6e6e6_1x400.png
new file mode 100644
index 0000000000..86c2baa655
--- /dev/null
+++ b/themes/original/jquery/images/ui-bg_glass_75_e6e6e6_1x400.png
Binary files differ
diff --git a/themes/original/jquery/images/ui-bg_glass_95_fef1ec_1x400.png b/themes/original/jquery/images/ui-bg_glass_95_fef1ec_1x400.png
new file mode 100644
index 0000000000..4443fdc1a1
--- /dev/null
+++ b/themes/original/jquery/images/ui-bg_glass_95_fef1ec_1x400.png
Binary files differ
diff --git a/themes/original/jquery/images/ui-bg_highlight-soft_75_cccccc_1x100.png b/themes/original/jquery/images/ui-bg_highlight-soft_75_cccccc_1x100.png
new file mode 100644
index 0000000000..7c9fa6c6ed
--- /dev/null
+++ b/themes/original/jquery/images/ui-bg_highlight-soft_75_cccccc_1x100.png
Binary files differ
diff --git a/themes/original/jquery/images/ui-icons_222222_256x240.png b/themes/original/jquery/images/ui-icons_222222_256x240.png
new file mode 100644
index 0000000000..b273ff111d
--- /dev/null
+++ b/themes/original/jquery/images/ui-icons_222222_256x240.png
Binary files differ
diff --git a/themes/original/jquery/images/ui-icons_2e83ff_256x240.png b/themes/original/jquery/images/ui-icons_2e83ff_256x240.png
new file mode 100644
index 0000000000..09d1cdc856
--- /dev/null
+++ b/themes/original/jquery/images/ui-icons_2e83ff_256x240.png
Binary files differ
diff --git a/themes/original/jquery/images/ui-icons_454545_256x240.png b/themes/original/jquery/images/ui-icons_454545_256x240.png
new file mode 100644
index 0000000000..59bd45b907
--- /dev/null
+++ b/themes/original/jquery/images/ui-icons_454545_256x240.png
Binary files differ
diff --git a/themes/original/jquery/images/ui-icons_888888_256x240.png b/themes/original/jquery/images/ui-icons_888888_256x240.png
new file mode 100644
index 0000000000..6d02426c11
--- /dev/null
+++ b/themes/original/jquery/images/ui-icons_888888_256x240.png
Binary files differ
diff --git a/themes/original/jquery/images/ui-icons_cd0a0a_256x240.png b/themes/original/jquery/images/ui-icons_cd0a0a_256x240.png
new file mode 100644
index 0000000000..2ab019b73e
--- /dev/null
+++ b/themes/original/jquery/images/ui-icons_cd0a0a_256x240.png
Binary files differ
diff --git a/themes/original/jquery/jquery-ui-1.9.2.custom.css b/themes/original/jquery/jquery-ui-1.9.2.custom.css
new file mode 100644
index 0000000000..b08d9039a4
--- /dev/null
+++ b/themes/original/jquery/jquery-ui-1.9.2.custom.css
@@ -0,0 +1,462 @@
+/*! jQuery UI - v1.9.2 - 2012-12-19
+* http://jqueryui.com
+* Includes: jquery.ui.core.css, jquery.ui.resizable.css, jquery.ui.selectable.css, jquery.ui.accordion.css, jquery.ui.autocomplete.css, jquery.ui.button.css, jquery.ui.datepicker.css, jquery.ui.dialog.css, jquery.ui.menu.css, jquery.ui.progressbar.css, jquery.ui.slider.css, jquery.ui.spinner.css, jquery.ui.tabs.css, jquery.ui.tooltip.css
+* To view and modify this theme, visit http://jqueryui.com/themeroller/?ffDefault=Verdana%2CArial%2Csans-serif&fwDefault=normal&fsDefault=1.1em&cornerRadius=4px&bgColorHeader=cccccc&bgTextureHeader=03_highlight_soft.png&bgImgOpacityHeader=75&borderColorHeader=aaaaaa&fcHeader=222222&iconColorHeader=222222&bgColorContent=ffffff&bgTextureContent=01_flat.png&bgImgOpacityContent=75&borderColorContent=aaaaaa&fcContent=222222&iconColorContent=222222&bgColorDefault=e6e6e6&bgTextureDefault=02_glass.png&bgImgOpacityDefault=75&borderColorDefault=d3d3d3&fcDefault=555555&iconColorDefault=888888&bgColorHover=dadada&bgTextureHover=02_glass.png&bgImgOpacityHover=75&borderColorHover=999999&fcHover=212121&iconColorHover=454545&bgColorActive=ffffff&bgTextureActive=02_glass.png&bgImgOpacityActive=65&borderColorActive=aaaaaa&fcActive=212121&iconColorActive=454545&bgColorHighlight=fbf9ee&bgTextureHighlight=02_glass.png&bgImgOpacityHighlight=55&borderColorHighlight=fcefa1&fcHighlight=363636&iconColorHighlight=2e83ff&bgColorError=fef1ec&bgTextureError=02_glass.png&bgImgOpacityError=95&borderColorError=cd0a0a&fcError=cd0a0a&iconColorError=cd0a0a&bgColorOverlay=aaaaaa&bgTextureOverlay=01_flat.png&bgImgOpacityOverlay=0&opacityOverlay=30&bgColorShadow=aaaaaa&bgTextureShadow=01_flat.png&bgImgOpacityShadow=0&opacityShadow=30&thicknessShadow=8px&offsetTopShadow=-8px&offsetLeftShadow=-8px&cornerRadiusShadow=8px
+* Copyright (c) 2012 jQuery Foundation and other contributors Licensed MIT */
+
+/* Layout helpers
+----------------------------------*/
+.ui-helper-hidden { display: none; }
+.ui-helper-hidden-accessible { border: 0; clip: rect(0 0 0 0); height: 1px; margin: -1px; overflow: hidden; padding: 0; position: absolute; width: 1px; }
+.ui-helper-reset { margin: 0; padding: 0; border: 0; outline: 0; line-height: 1.3; text-decoration: none; font-size: 100%; list-style: none; }
+.ui-helper-clearfix:before, .ui-helper-clearfix:after { content: ""; display: table; }
+.ui-helper-clearfix:after { clear: both; }
+.ui-helper-clearfix { zoom: 1; }
+.ui-helper-zfix { width: 100%; height: 100%; top: 0; left: 0; position: absolute; opacity: 0; filter:Alpha(Opacity=0); }
+
+
+/* Interaction Cues
+----------------------------------*/
+.ui-state-disabled { cursor: default !important; }
+
+
+/* Icons
+----------------------------------*/
+
+/* states and images */
+.ui-icon { display: block; text-indent: -99999px; overflow: hidden; background-repeat: no-repeat; }
+
+
+/* Misc visuals
+----------------------------------*/
+
+/* Overlays */
+.ui-widget-overlay { position: absolute; top: 0; left: 0; width: 100%; height: 100%; }
+.ui-resizable { position: relative;}
+.ui-resizable-handle { position: absolute;font-size: 0.1px; display: block; }
+.ui-resizable-disabled .ui-resizable-handle, .ui-resizable-autohide .ui-resizable-handle { display: none; }
+.ui-resizable-n { cursor: n-resize; height: 7px; width: 100%; top: -5px; left: 0; }
+.ui-resizable-s { cursor: s-resize; height: 7px; width: 100%; bottom: -5px; left: 0; }
+.ui-resizable-e { cursor: e-resize; width: 7px; right: -5px; top: 0; height: 100%; }
+.ui-resizable-w { cursor: w-resize; width: 7px; left: -5px; top: 0; height: 100%; }
+.ui-resizable-se { cursor: se-resize; width: 12px; height: 12px; right: 1px; bottom: 1px; }
+.ui-resizable-sw { cursor: sw-resize; width: 9px; height: 9px; left: -5px; bottom: -5px; }
+.ui-resizable-nw { cursor: nw-resize; width: 9px; height: 9px; left: -5px; top: -5px; }
+.ui-resizable-ne { cursor: ne-resize; width: 9px; height: 9px; right: -5px; top: -5px;}.ui-selectable-helper { position: absolute; z-index: 100; border:1px dotted black; }
+.ui-accordion .ui-accordion-header { display: block; cursor: pointer; position: relative; margin-top: 2px; padding: .5em .5em .5em .7em; zoom: 1; }
+.ui-accordion .ui-accordion-icons { padding-left: 2.2em; }
+.ui-accordion .ui-accordion-noicons { padding-left: .7em; }
+.ui-accordion .ui-accordion-icons .ui-accordion-icons { padding-left: 2.2em; }
+.ui-accordion .ui-accordion-header .ui-accordion-header-icon { position: absolute; left: .5em; top: 50%; margin-top: -8px; }
+.ui-accordion .ui-accordion-content { padding: 1em 2.2em; border-top: 0; overflow: auto; zoom: 1; }
+.ui-autocomplete {
+ position: absolute;
+ top: 0;
+ left: 0;
+ cursor: default;
+}
+
+/* workarounds */
+* html .ui-autocomplete { width:1px; } /* without this, the menu expands to 100% in IE6 */
+.ui-button { display: inline-block; position: relative; padding: 0; margin-right: .1em; cursor: pointer; text-align: center; zoom: 1; overflow: visible; } /* the overflow property removes extra width in IE */
+.ui-button, .ui-button:link, .ui-button:visited, .ui-button:hover, .ui-button:active { text-decoration: none; }
+.ui-button-icon-only { width: 2.2em; } /* to make room for the icon, a width needs to be set here */
+button.ui-button-icon-only { width: 2.4em; } /* button elements seem to need a little more width */
+.ui-button-icons-only { width: 3.4em; }
+button.ui-button-icons-only { width: 3.7em; }
+
+/*button text element */
+.ui-button .ui-button-text { display: block; line-height: 1.4; }
+.ui-button-text-only .ui-button-text { padding: .4em 1em; }
+.ui-button-icon-only .ui-button-text, .ui-button-icons-only .ui-button-text { padding: .4em; text-indent: -9999999px; }
+.ui-button-text-icon-primary .ui-button-text, .ui-button-text-icons .ui-button-text { padding: .4em 1em .4em 2.1em; }
+.ui-button-text-icon-secondary .ui-button-text, .ui-button-text-icons .ui-button-text { padding: .4em 2.1em .4em 1em; }
+.ui-button-text-icons .ui-button-text { padding-left: 2.1em; padding-right: 2.1em; }
+/* no icon support for input elements, provide padding by default */
+input.ui-button { padding: .4em 1em; }
+
+/*button icon element(s) */
+.ui-button-icon-only .ui-icon, .ui-button-text-icon-primary .ui-icon, .ui-button-text-icon-secondary .ui-icon, .ui-button-text-icons .ui-icon, .ui-button-icons-only .ui-icon { position: absolute; top: 50%; margin-top: -8px; }
+.ui-button-icon-only .ui-icon { left: 50%; margin-left: -8px; }
+.ui-button-text-icon-primary .ui-button-icon-primary, .ui-button-text-icons .ui-button-icon-primary, .ui-button-icons-only .ui-button-icon-primary { left: .5em; }
+.ui-button-text-icon-secondary .ui-button-icon-secondary, .ui-button-text-icons .ui-button-icon-secondary, .ui-button-icons-only .ui-button-icon-secondary { right: .5em; }
+.ui-button-text-icons .ui-button-icon-secondary, .ui-button-icons-only .ui-button-icon-secondary { right: .5em; }
+
+/*button sets*/
+.ui-buttonset { margin-right: 7px; }
+.ui-buttonset .ui-button { margin-left: 0; margin-right: -.3em; }
+
+/* workarounds */
+button.ui-button::-moz-focus-inner { border: 0; padding: 0; } /* reset extra padding in Firefox */
+.ui-datepicker { width: 17em; padding: .2em .2em 0; display: none; }
+.ui-datepicker .ui-datepicker-header { position:relative; padding:.2em 0; }
+.ui-datepicker .ui-datepicker-prev, .ui-datepicker .ui-datepicker-next { position:absolute; top: 2px; width: 1.8em; height: 1.8em; }
+.ui-datepicker .ui-datepicker-prev-hover, .ui-datepicker .ui-datepicker-next-hover { top: 1px; }
+.ui-datepicker .ui-datepicker-prev { left:2px; }
+.ui-datepicker .ui-datepicker-next { right:2px; }
+.ui-datepicker .ui-datepicker-prev-hover { left:1px; }
+.ui-datepicker .ui-datepicker-next-hover { right:1px; }
+.ui-datepicker .ui-datepicker-prev span, .ui-datepicker .ui-datepicker-next span { display: block; position: absolute; left: 50%; margin-left: -8px; top: 50%; margin-top: -8px; }
+.ui-datepicker .ui-datepicker-title { margin: 0 2.3em; line-height: 1.8em; text-align: center; }
+.ui-datepicker .ui-datepicker-title select { font-size:1em; margin:1px 0; }
+.ui-datepicker select.ui-datepicker-month-year {width: 100%;}
+.ui-datepicker select.ui-datepicker-month,
+.ui-datepicker select.ui-datepicker-year { width: 49%;}
+.ui-datepicker table {width: 100%; font-size: .9em; border-collapse: collapse; margin:0 0 .4em; }
+.ui-datepicker th { padding: .7em .3em; text-align: center; font-weight: bold; border: 0; }
+.ui-datepicker td { border: 0; padding: 1px; }
+.ui-datepicker td span, .ui-datepicker td a { display: block; padding: .2em; text-align: right; text-decoration: none; }
+.ui-datepicker .ui-datepicker-buttonpane { background-image: none; margin: .7em 0 0 0; padding:0 .2em; border-left: 0; border-right: 0; border-bottom: 0; }
+.ui-datepicker .ui-datepicker-buttonpane button { float: right; margin: .5em .2em .4em; cursor: pointer; padding: .2em .6em .3em .6em; width:auto; overflow:visible; }
+.ui-datepicker .ui-datepicker-buttonpane button.ui-datepicker-current { float:left; }
+
+/* with multiple calendars */
+.ui-datepicker.ui-datepicker-multi { width:auto; }
+.ui-datepicker-multi .ui-datepicker-group { float:left; }
+.ui-datepicker-multi .ui-datepicker-group table { width:95%; margin:0 auto .4em; }
+.ui-datepicker-multi-2 .ui-datepicker-group { width:50%; }
+.ui-datepicker-multi-3 .ui-datepicker-group { width:33.3%; }
+.ui-datepicker-multi-4 .ui-datepicker-group { width:25%; }
+.ui-datepicker-multi .ui-datepicker-group-last .ui-datepicker-header { border-left-width:0; }
+.ui-datepicker-multi .ui-datepicker-group-middle .ui-datepicker-header { border-left-width:0; }
+.ui-datepicker-multi .ui-datepicker-buttonpane { clear:left; }
+.ui-datepicker-row-break { clear:both; width:100%; font-size:0em; }
+
+/* RTL support */
+.ui-datepicker-rtl { direction: rtl; }
+.ui-datepicker-rtl .ui-datepicker-prev { right: 2px; left: auto; }
+.ui-datepicker-rtl .ui-datepicker-next { left: 2px; right: auto; }
+.ui-datepicker-rtl .ui-datepicker-prev:hover { right: 1px; left: auto; }
+.ui-datepicker-rtl .ui-datepicker-next:hover { left: 1px; right: auto; }
+.ui-datepicker-rtl .ui-datepicker-buttonpane { clear:right; }
+.ui-datepicker-rtl .ui-datepicker-buttonpane button { float: left; }
+.ui-datepicker-rtl .ui-datepicker-buttonpane button.ui-datepicker-current { float:right; }
+.ui-datepicker-rtl .ui-datepicker-group { float:right; }
+.ui-datepicker-rtl .ui-datepicker-group-last .ui-datepicker-header { border-right-width:0; border-left-width:1px; }
+.ui-datepicker-rtl .ui-datepicker-group-middle .ui-datepicker-header { border-right-width:0; border-left-width:1px; }
+
+/* IE6 IFRAME FIX (taken from datepicker 1.5.3 */
+.ui-datepicker-cover {
+ position: absolute; /*must have*/
+ z-index: -1; /*must have*/
+ filter: mask(); /*must have*/
+ top: -4px; /*must have*/
+ left: -4px; /*must have*/
+ width: 200px; /*must have*/
+ height: 200px; /*must have*/
+}.ui-dialog { position: absolute; top: 0; left: 0; padding: .2em; width: 300px; overflow: hidden; }
+.ui-dialog .ui-dialog-titlebar { padding: .4em 1em; position: relative; }
+.ui-dialog .ui-dialog-title { float: left; margin: .1em 16px .1em 0; }
+.ui-dialog .ui-dialog-titlebar-close { position: absolute; right: .3em; top: 50%; width: 19px; margin: -10px 0 0 0; padding: 1px; height: 18px; }
+.ui-dialog .ui-dialog-titlebar-close span { display: block; margin: 1px; }
+.ui-dialog .ui-dialog-titlebar-close:hover, .ui-dialog .ui-dialog-titlebar-close:focus { padding: 0; }
+.ui-dialog .ui-dialog-content { position: relative; border: 0; padding: .5em 1em; background: none; overflow: auto; zoom: 1; }
+.ui-dialog .ui-dialog-buttonpane { text-align: left; border-width: 1px 0 0 0; background-image: none; margin: .5em 0 0 0; padding: .3em 1em .5em .4em; }
+.ui-dialog .ui-dialog-buttonpane .ui-dialog-buttonset { float: right; }
+.ui-dialog .ui-dialog-buttonpane button { margin: .5em .4em .5em 0; cursor: pointer; }
+.ui-dialog .ui-resizable-se { width: 14px; height: 14px; right: 3px; bottom: 3px; }
+.ui-draggable .ui-dialog-titlebar { cursor: move; }
+.ui-menu { list-style:none; padding: 2px; margin: 0; display:block; outline: none; }
+.ui-menu .ui-menu { margin-top: -3px; position: absolute; }
+.ui-menu .ui-menu-item { margin: 0; padding: 0; zoom: 1; width: 100%; }
+.ui-menu .ui-menu-divider { margin: 5px -2px 5px -2px; height: 0; font-size: 0; line-height: 0; border-width: 1px 0 0 0; }
+.ui-menu .ui-menu-item a { text-decoration: none; display: block; padding: 2px .4em; line-height: 1.5; zoom: 1; font-weight: normal; }
+.ui-menu .ui-menu-item a.ui-state-focus,
+.ui-menu .ui-menu-item a.ui-state-active { font-weight: normal; margin: -1px; }
+
+.ui-menu .ui-state-disabled { font-weight: normal; margin: .4em 0 .2em; line-height: 1.5; }
+.ui-menu .ui-state-disabled a { cursor: default; }
+
+/* icon support */
+.ui-menu-icons { position: relative; }
+.ui-menu-icons .ui-menu-item a { position: relative; padding-left: 2em; }
+
+/* left-aligned */
+.ui-menu .ui-icon { position: absolute; top: .2em; left: .2em; }
+
+/* right-aligned */
+.ui-menu .ui-menu-icon { position: static; float: right; }
+.ui-progressbar { height:2em; text-align: left; overflow: hidden; }
+.ui-progressbar .ui-progressbar-value {margin: -1px; height:100%; }.ui-slider { position: relative; text-align: left; }
+.ui-slider .ui-slider-handle { position: absolute; z-index: 2; width: 1.2em; height: 1.2em; cursor: default; }
+.ui-slider .ui-slider-range { position: absolute; z-index: 1; font-size: .7em; display: block; border: 0; background-position: 0 0; }
+
+.ui-slider-horizontal { height: .8em; }
+.ui-slider-horizontal .ui-slider-handle { top: -.3em; margin-left: -.6em; }
+.ui-slider-horizontal .ui-slider-range { top: 0; height: 100%; }
+.ui-slider-horizontal .ui-slider-range-min { left: 0; }
+.ui-slider-horizontal .ui-slider-range-max { right: 0; }
+
+.ui-slider-vertical { width: .8em; height: 100px; }
+.ui-slider-vertical .ui-slider-handle { left: -.3em; margin-left: 0; margin-bottom: -.6em; }
+.ui-slider-vertical .ui-slider-range { left: 0; width: 100%; }
+.ui-slider-vertical .ui-slider-range-min { bottom: 0; }
+.ui-slider-vertical .ui-slider-range-max { top: 0; }.ui-spinner { position:relative; display: inline-block; overflow: hidden; padding: 0; vertical-align: middle; }
+.ui-spinner-input { border: none; background: none; padding: 0; margin: .2em 0; vertical-align: middle; margin-left: .4em; margin-right: 22px; }
+.ui-spinner-button { width: 16px; height: 50%; font-size: .5em; padding: 0; margin: 0; text-align: center; position: absolute; cursor: default; display: block; overflow: hidden; right: 0; }
+.ui-spinner a.ui-spinner-button { border-top: none; border-bottom: none; border-right: none; } /* more specificity required here to overide default borders */
+.ui-spinner .ui-icon { position: absolute; margin-top: -8px; top: 50%; left: 0; } /* vertical centre icon */
+.ui-spinner-up { top: 0; }
+.ui-spinner-down { bottom: 0; }
+
+/* TR overrides */
+.ui-spinner .ui-icon-triangle-1-s {
+ /* need to fix icons sprite */
+ background-position:-65px -16px;
+}
+.ui-tabs { position: relative; padding: .2em; zoom: 1; } /* position: relative prevents IE scroll bug (element with position: relative inside container with overflow: auto appear as "fixed") */
+.ui-tabs .ui-tabs-nav { margin: 0; padding: .2em .2em 0; }
+.ui-tabs .ui-tabs-nav li { list-style: none; float: left; position: relative; top: 0; margin: 1px .2em 0 0; border-bottom: 0; padding: 0; white-space: nowrap; }
+.ui-tabs .ui-tabs-nav li a { float: left; padding: .5em 1em; text-decoration: none; }
+.ui-tabs .ui-tabs-nav li.ui-tabs-active { margin-bottom: -1px; padding-bottom: 1px; }
+.ui-tabs .ui-tabs-nav li.ui-tabs-active a, .ui-tabs .ui-tabs-nav li.ui-state-disabled a, .ui-tabs .ui-tabs-nav li.ui-tabs-loading a { cursor: text; }
+.ui-tabs .ui-tabs-nav li a, .ui-tabs-collapsible .ui-tabs-nav li.ui-tabs-active a { cursor: pointer; } /* first selector in group seems obsolete, but required to overcome bug in Opera applying cursor: text overall if defined elsewhere... */
+.ui-tabs .ui-tabs-panel { display: block; border-width: 0; padding: 1em 1.4em; background: none; }
+.ui-tooltip {
+ padding: 8px;
+ position: absolute;
+ z-index: 9999;
+ max-width: 300px;
+ -webkit-box-shadow: 0 0 5px #aaa;
+ box-shadow: 0 0 5px #aaa;
+}
+/* Fades and background-images don't work well together in IE6, drop the image */
+* html .ui-tooltip {
+ background-image: none;
+}
+body .ui-tooltip { border-width: 2px; }
+
+/* Component containers
+----------------------------------*/
+.ui-widget { font-family: Verdana,Arial,sans-serif; font-size: 1.1em; }
+.ui-widget .ui-widget { font-size: 1em; }
+.ui-widget input, .ui-widget select, .ui-widget textarea, .ui-widget button { font-family: Verdana,Arial,sans-serif; font-size: 1em; }
+.ui-widget-content { border: 1px solid #aaaaaa; background: #ffffff url(images/ui-bg_flat_75_ffffff_40x100.png) 50% 50% repeat-x; color: #222222; }
+.ui-widget-content a { color: #222222; }
+.ui-widget-header { border: 1px solid #aaaaaa; background: #cccccc url(images/ui-bg_highlight-soft_75_cccccc_1x100.png) 50% 50% repeat-x; color: #222222; font-weight: bold; }
+.ui-widget-header a { color: #222222; }
+
+/* Interaction states
+----------------------------------*/
+.ui-state-default, .ui-widget-content .ui-state-default, .ui-widget-header .ui-state-default { border: 1px solid #d3d3d3; background: #e6e6e6 url(images/ui-bg_glass_75_e6e6e6_1x400.png) 50% 50% repeat-x; font-weight: normal; color: #555555; }
+.ui-state-default a, .ui-state-default a:link, .ui-state-default a:visited { color: #555555; text-decoration: none; }
+.ui-state-hover, .ui-widget-content .ui-state-hover, .ui-widget-header .ui-state-hover, .ui-state-focus, .ui-widget-content .ui-state-focus, .ui-widget-header .ui-state-focus { border: 1px solid #999999; background: #dadada url(images/ui-bg_glass_75_dadada_1x400.png) 50% 50% repeat-x; font-weight: normal; color: #212121; }
+.ui-state-hover a, .ui-state-hover a:hover, .ui-state-hover a:link, .ui-state-hover a:visited { color: #212121; text-decoration: none; }
+.ui-state-active, .ui-widget-content .ui-state-active, .ui-widget-header .ui-state-active { border: 1px solid #aaaaaa; background: #ffffff url(images/ui-bg_glass_65_ffffff_1x400.png) 50% 50% repeat-x; font-weight: normal; color: #212121; }
+.ui-state-active a, .ui-state-active a:link, .ui-state-active a:visited { color: #212121; text-decoration: none; }
+
+/* Interaction Cues
+----------------------------------*/
+.ui-state-highlight, .ui-widget-content .ui-state-highlight, .ui-widget-header .ui-state-highlight {border: 1px solid #fcefa1; background: #fbf9ee url(images/ui-bg_glass_55_fbf9ee_1x400.png) 50% 50% repeat-x; color: #363636; }
+.ui-state-highlight a, .ui-widget-content .ui-state-highlight a,.ui-widget-header .ui-state-highlight a { color: #363636; }
+.ui-state-error, .ui-widget-content .ui-state-error, .ui-widget-header .ui-state-error {border: 1px solid #cd0a0a; background: #fef1ec url(images/ui-bg_glass_95_fef1ec_1x400.png) 50% 50% repeat-x; color: #cd0a0a; }
+.ui-state-error a, .ui-widget-content .ui-state-error a, .ui-widget-header .ui-state-error a { color: #cd0a0a; }
+.ui-state-error-text, .ui-widget-content .ui-state-error-text, .ui-widget-header .ui-state-error-text { color: #cd0a0a; }
+.ui-priority-primary, .ui-widget-content .ui-priority-primary, .ui-widget-header .ui-priority-primary { font-weight: bold; }
+.ui-priority-secondary, .ui-widget-content .ui-priority-secondary, .ui-widget-header .ui-priority-secondary { opacity: .7; filter:Alpha(Opacity=70); font-weight: normal; }
+.ui-state-disabled, .ui-widget-content .ui-state-disabled, .ui-widget-header .ui-state-disabled { opacity: .35; filter:Alpha(Opacity=35); background-image: none; }
+.ui-state-disabled .ui-icon { filter:Alpha(Opacity=35); } /* For IE8 - See #6059 */
+
+/* Icons
+----------------------------------*/
+
+/* states and images */
+.ui-icon { width: 16px; height: 16px; background-image: url(images/ui-icons_222222_256x240.png); }
+.ui-widget-content .ui-icon {background-image: url(images/ui-icons_222222_256x240.png); }
+.ui-widget-header .ui-icon {background-image: url(images/ui-icons_222222_256x240.png); }
+.ui-state-default .ui-icon { background-image: url(images/ui-icons_888888_256x240.png); }
+.ui-state-hover .ui-icon, .ui-state-focus .ui-icon {background-image: url(images/ui-icons_454545_256x240.png); }
+.ui-state-active .ui-icon {background-image: url(images/ui-icons_454545_256x240.png); }
+.ui-state-highlight .ui-icon {background-image: url(images/ui-icons_2e83ff_256x240.png); }
+.ui-state-error .ui-icon, .ui-state-error-text .ui-icon {background-image: url(images/ui-icons_cd0a0a_256x240.png); }
+
+/* positioning */
+.ui-icon-carat-1-n { background-position: 0 0; }
+.ui-icon-carat-1-ne { background-position: -16px 0; }
+.ui-icon-carat-1-e { background-position: -32px 0; }
+.ui-icon-carat-1-se { background-position: -48px 0; }
+.ui-icon-carat-1-s { background-position: -64px 0; }
+.ui-icon-carat-1-sw { background-position: -80px 0; }
+.ui-icon-carat-1-w { background-position: -96px 0; }
+.ui-icon-carat-1-nw { background-position: -112px 0; }
+.ui-icon-carat-2-n-s { background-position: -128px 0; }
+.ui-icon-carat-2-e-w { background-position: -144px 0; }
+.ui-icon-triangle-1-n { background-position: 0 -16px; }
+.ui-icon-triangle-1-ne { background-position: -16px -16px; }
+.ui-icon-triangle-1-e { background-position: -32px -16px; }
+.ui-icon-triangle-1-se { background-position: -48px -16px; }
+.ui-icon-triangle-1-s { background-position: -64px -16px; }
+.ui-icon-triangle-1-sw { background-position: -80px -16px; }
+.ui-icon-triangle-1-w { background-position: -96px -16px; }
+.ui-icon-triangle-1-nw { background-position: -112px -16px; }
+.ui-icon-triangle-2-n-s { background-position: -128px -16px; }
+.ui-icon-triangle-2-e-w { background-position: -144px -16px; }
+.ui-icon-arrow-1-n { background-position: 0 -32px; }
+.ui-icon-arrow-1-ne { background-position: -16px -32px; }
+.ui-icon-arrow-1-e { background-position: -32px -32px; }
+.ui-icon-arrow-1-se { background-position: -48px -32px; }
+.ui-icon-arrow-1-s { background-position: -64px -32px; }
+.ui-icon-arrow-1-sw { background-position: -80px -32px; }
+.ui-icon-arrow-1-w { background-position: -96px -32px; }
+.ui-icon-arrow-1-nw { background-position: -112px -32px; }
+.ui-icon-arrow-2-n-s { background-position: -128px -32px; }
+.ui-icon-arrow-2-ne-sw { background-position: -144px -32px; }
+.ui-icon-arrow-2-e-w { background-position: -160px -32px; }
+.ui-icon-arrow-2-se-nw { background-position: -176px -32px; }
+.ui-icon-arrowstop-1-n { background-position: -192px -32px; }
+.ui-icon-arrowstop-1-e { background-position: -208px -32px; }
+.ui-icon-arrowstop-1-s { background-position: -224px -32px; }
+.ui-icon-arrowstop-1-w { background-position: -240px -32px; }
+.ui-icon-arrowthick-1-n { background-position: 0 -48px; }
+.ui-icon-arrowthick-1-ne { background-position: -16px -48px; }
+.ui-icon-arrowthick-1-e { background-position: -32px -48px; }
+.ui-icon-arrowthick-1-se { background-position: -48px -48px; }
+.ui-icon-arrowthick-1-s { background-position: -64px -48px; }
+.ui-icon-arrowthick-1-sw { background-position: -80px -48px; }
+.ui-icon-arrowthick-1-w { background-position: -96px -48px; }
+.ui-icon-arrowthick-1-nw { background-position: -112px -48px; }
+.ui-icon-arrowthick-2-n-s { background-position: -128px -48px; }
+.ui-icon-arrowthick-2-ne-sw { background-position: -144px -48px; }
+.ui-icon-arrowthick-2-e-w { background-position: -160px -48px; }
+.ui-icon-arrowthick-2-se-nw { background-position: -176px -48px; }
+.ui-icon-arrowthickstop-1-n { background-position: -192px -48px; }
+.ui-icon-arrowthickstop-1-e { background-position: -208px -48px; }
+.ui-icon-arrowthickstop-1-s { background-position: -224px -48px; }
+.ui-icon-arrowthickstop-1-w { background-position: -240px -48px; }
+.ui-icon-arrowreturnthick-1-w { background-position: 0 -64px; }
+.ui-icon-arrowreturnthick-1-n { background-position: -16px -64px; }
+.ui-icon-arrowreturnthick-1-e { background-position: -32px -64px; }
+.ui-icon-arrowreturnthick-1-s { background-position: -48px -64px; }
+.ui-icon-arrowreturn-1-w { background-position: -64px -64px; }
+.ui-icon-arrowreturn-1-n { background-position: -80px -64px; }
+.ui-icon-arrowreturn-1-e { background-position: -96px -64px; }
+.ui-icon-arrowreturn-1-s { background-position: -112px -64px; }
+.ui-icon-arrowrefresh-1-w { background-position: -128px -64px; }
+.ui-icon-arrowrefresh-1-n { background-position: -144px -64px; }
+.ui-icon-arrowrefresh-1-e { background-position: -160px -64px; }
+.ui-icon-arrowrefresh-1-s { background-position: -176px -64px; }
+.ui-icon-arrow-4 { background-position: 0 -80px; }
+.ui-icon-arrow-4-diag { background-position: -16px -80px; }
+.ui-icon-extlink { background-position: -32px -80px; }
+.ui-icon-newwin { background-position: -48px -80px; }
+.ui-icon-refresh { background-position: -64px -80px; }
+.ui-icon-shuffle { background-position: -80px -80px; }
+.ui-icon-transfer-e-w { background-position: -96px -80px; }
+.ui-icon-transferthick-e-w { background-position: -112px -80px; }
+.ui-icon-folder-collapsed { background-position: 0 -96px; }
+.ui-icon-folder-open { background-position: -16px -96px; }
+.ui-icon-document { background-position: -32px -96px; }
+.ui-icon-document-b { background-position: -48px -96px; }
+.ui-icon-note { background-position: -64px -96px; }
+.ui-icon-mail-closed { background-position: -80px -96px; }
+.ui-icon-mail-open { background-position: -96px -96px; }
+.ui-icon-suitcase { background-position: -112px -96px; }
+.ui-icon-comment { background-position: -128px -96px; }
+.ui-icon-person { background-position: -144px -96px; }
+.ui-icon-print { background-position: -160px -96px; }
+.ui-icon-trash { background-position: -176px -96px; }
+.ui-icon-locked { background-position: -192px -96px; }
+.ui-icon-unlocked { background-position: -208px -96px; }
+.ui-icon-bookmark { background-position: -224px -96px; }
+.ui-icon-tag { background-position: -240px -96px; }
+.ui-icon-home { background-position: 0 -112px; }
+.ui-icon-flag { background-position: -16px -112px; }
+.ui-icon-calendar { background-position: -32px -112px; }
+.ui-icon-cart { background-position: -48px -112px; }
+.ui-icon-pencil { background-position: -64px -112px; }
+.ui-icon-clock { background-position: -80px -112px; }
+.ui-icon-disk { background-position: -96px -112px; }
+.ui-icon-calculator { background-position: -112px -112px; }
+.ui-icon-zoomin { background-position: -128px -112px; }
+.ui-icon-zoomout { background-position: -144px -112px; }
+.ui-icon-search { background-position: -160px -112px; }
+.ui-icon-wrench { background-position: -176px -112px; }
+.ui-icon-gear { background-position: -192px -112px; }
+.ui-icon-heart { background-position: -208px -112px; }
+.ui-icon-star { background-position: -224px -112px; }
+.ui-icon-link { background-position: -240px -112px; }
+.ui-icon-cancel { background-position: 0 -128px; }
+.ui-icon-plus { background-position: -16px -128px; }
+.ui-icon-plusthick { background-position: -32px -128px; }
+.ui-icon-minus { background-position: -48px -128px; }
+.ui-icon-minusthick { background-position: -64px -128px; }
+.ui-icon-close { background-position: -80px -128px; }
+.ui-icon-closethick { background-position: -96px -128px; }
+.ui-icon-key { background-position: -112px -128px; }
+.ui-icon-lightbulb { background-position: -128px -128px; }
+.ui-icon-scissors { background-position: -144px -128px; }
+.ui-icon-clipboard { background-position: -160px -128px; }
+.ui-icon-copy { background-position: -176px -128px; }
+.ui-icon-contact { background-position: -192px -128px; }
+.ui-icon-image { background-position: -208px -128px; }
+.ui-icon-video { background-position: -224px -128px; }
+.ui-icon-script { background-position: -240px -128px; }
+.ui-icon-alert { background-position: 0 -144px; }
+.ui-icon-info { background-position: -16px -144px; }
+.ui-icon-notice { background-position: -32px -144px; }
+.ui-icon-help { background-position: -48px -144px; }
+.ui-icon-check { background-position: -64px -144px; }
+.ui-icon-bullet { background-position: -80px -144px; }
+.ui-icon-radio-on { background-position: -96px -144px; }
+.ui-icon-radio-off { background-position: -112px -144px; }
+.ui-icon-pin-w { background-position: -128px -144px; }
+.ui-icon-pin-s { background-position: -144px -144px; }
+.ui-icon-play { background-position: 0 -160px; }
+.ui-icon-pause { background-position: -16px -160px; }
+.ui-icon-seek-next { background-position: -32px -160px; }
+.ui-icon-seek-prev { background-position: -48px -160px; }
+.ui-icon-seek-end { background-position: -64px -160px; }
+.ui-icon-seek-start { background-position: -80px -160px; }
+/* ui-icon-seek-first is deprecated, use ui-icon-seek-start instead */
+.ui-icon-seek-first { background-position: -80px -160px; }
+.ui-icon-stop { background-position: -96px -160px; }
+.ui-icon-eject { background-position: -112px -160px; }
+.ui-icon-volume-off { background-position: -128px -160px; }
+.ui-icon-volume-on { background-position: -144px -160px; }
+.ui-icon-power { background-position: 0 -176px; }
+.ui-icon-signal-diag { background-position: -16px -176px; }
+.ui-icon-signal { background-position: -32px -176px; }
+.ui-icon-battery-0 { background-position: -48px -176px; }
+.ui-icon-battery-1 { background-position: -64px -176px; }
+.ui-icon-battery-2 { background-position: -80px -176px; }
+.ui-icon-battery-3 { background-position: -96px -176px; }
+.ui-icon-circle-plus { background-position: 0 -192px; }
+.ui-icon-circle-minus { background-position: -16px -192px; }
+.ui-icon-circle-close { background-position: -32px -192px; }
+.ui-icon-circle-triangle-e { background-position: -48px -192px; }
+.ui-icon-circle-triangle-s { background-position: -64px -192px; }
+.ui-icon-circle-triangle-w { background-position: -80px -192px; }
+.ui-icon-circle-triangle-n { background-position: -96px -192px; }
+.ui-icon-circle-arrow-e { background-position: -112px -192px; }
+.ui-icon-circle-arrow-s { background-position: -128px -192px; }
+.ui-icon-circle-arrow-w { background-position: -144px -192px; }
+.ui-icon-circle-arrow-n { background-position: -160px -192px; }
+.ui-icon-circle-zoomin { background-position: -176px -192px; }
+.ui-icon-circle-zoomout { background-position: -192px -192px; }
+.ui-icon-circle-check { background-position: -208px -192px; }
+.ui-icon-circlesmall-plus { background-position: 0 -208px; }
+.ui-icon-circlesmall-minus { background-position: -16px -208px; }
+.ui-icon-circlesmall-close { background-position: -32px -208px; }
+.ui-icon-squaresmall-plus { background-position: -48px -208px; }
+.ui-icon-squaresmall-minus { background-position: -64px -208px; }
+.ui-icon-squaresmall-close { background-position: -80px -208px; }
+.ui-icon-grip-dotted-vertical { background-position: 0 -224px; }
+.ui-icon-grip-dotted-horizontal { background-position: -16px -224px; }
+.ui-icon-grip-solid-vertical { background-position: -32px -224px; }
+.ui-icon-grip-solid-horizontal { background-position: -48px -224px; }
+.ui-icon-gripsmall-diagonal-se { background-position: -64px -224px; }
+.ui-icon-grip-diagonal-se { background-position: -80px -224px; }
+
+
+/* Misc visuals
+----------------------------------*/
+
+/* Corner radius */
+.ui-corner-all, .ui-corner-top, .ui-corner-left, .ui-corner-tl { -moz-border-radius-topleft: 4px; -webkit-border-top-left-radius: 4px; -khtml-border-top-left-radius: 4px; border-top-left-radius: 4px; }
+.ui-corner-all, .ui-corner-top, .ui-corner-right, .ui-corner-tr { -moz-border-radius-topright: 4px; -webkit-border-top-right-radius: 4px; -khtml-border-top-right-radius: 4px; border-top-right-radius: 4px; }
+.ui-corner-all, .ui-corner-bottom, .ui-corner-left, .ui-corner-bl { -moz-border-radius-bottomleft: 4px; -webkit-border-bottom-left-radius: 4px; -khtml-border-bottom-left-radius: 4px; border-bottom-left-radius: 4px; }
+.ui-corner-all, .ui-corner-bottom, .ui-corner-right, .ui-corner-br { -moz-border-radius-bottomright: 4px; -webkit-border-bottom-right-radius: 4px; -khtml-border-bottom-right-radius: 4px; border-bottom-right-radius: 4px; }
+
+/* Overlays */
+.ui-widget-overlay { background: #aaaaaa url(images/ui-bg_flat_0_aaaaaa_40x100.png) 50% 50% repeat-x; opacity: .3;filter:Alpha(Opacity=30); }
+.ui-widget-shadow { margin: -8px 0 0 -8px; padding: 8px; background: #aaaaaa url(images/ui-bg_flat_0_aaaaaa_40x100.png) 50% 50% repeat-x; opacity: .3;filter:Alpha(Opacity=30); -moz-border-radius: 8px; -khtml-border-radius: 8px; -webkit-border-radius: 8px; border-radius: 8px; } \ No newline at end of file
diff --git a/themes/original/layout.inc.php b/themes/original/layout.inc.php
new file mode 100644
index 0000000000..084aeef1c0
--- /dev/null
+++ b/themes/original/layout.inc.php
@@ -0,0 +1,110 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * configures general layout
+ * for detailed layout configuration please refer to the css files
+ *
+ * @package PhpMyAdmin-theme
+ * @subpackage Original
+ */
+
+/**
+ * navi frame
+ */
+// navi frame width
+$GLOBALS['cfg']['NaviWidth'] = 240;
+
+// foreground (text) color for the navi frame
+$GLOBALS['cfg']['NaviColor'] = '#000000';
+
+// background for the navi frame
+$GLOBALS['cfg']['NaviBackground'] = '#D0DCE0';
+
+// foreground (text) color of the pointer in navi frame
+$GLOBALS['cfg']['NaviPointerColor'] = '#000000';
+// background of the pointer in navi frame
+$GLOBALS['cfg']['NaviPointerBackground'] = '#9999CC';
+
+/**
+ * main frame
+ */
+// foreground (text) color for the main frame
+$GLOBALS['cfg']['MainColor'] = '#000000';
+
+// background for the main frame
+$GLOBALS['cfg']['MainBackground'] = '#F5F5F5';
+
+// foreground (text) color of the pointer in browse mode
+$GLOBALS['cfg']['BrowsePointerColor'] = '#000000';
+
+// background of the pointer in browse mode
+$GLOBALS['cfg']['BrowsePointerBackground'] = '#CCFFCC';
+
+// foreground (text) color of the marker (visually marks row by clicking on it)
+// in browse mode
+$GLOBALS['cfg']['BrowseMarkerColor'] = '#000000';
+
+// background of the marker (visually marks row by clicking on it) in browse mode
+$GLOBALS['cfg']['BrowseMarkerBackground'] = '#FFCC99';
+
+/**
+ * fonts
+ */
+/**
+ * the font family as a valid css font family value,
+ * if not set the browser default will be used
+ * (depending on browser, DTD and system settings)
+ */
+$GLOBALS['cfg']['FontFamily'] = 'sans-serif';
+/**
+ * fixed width font family, used in textarea
+ */
+$GLOBALS['cfg']['FontFamilyFixed'] = 'monospace';
+
+/**
+ * tables
+ */
+// border
+$GLOBALS['cfg']['Border'] = 0;
+// table header and footer color
+$GLOBALS['cfg']['ThBackground'] = '#D3DCE3';
+// table header and footer background
+$GLOBALS['cfg']['ThColor'] = '#000000';
+// table data row background
+$GLOBALS['cfg']['BgOne'] = '#E5E5E5';
+// table data row background, alternate
+$GLOBALS['cfg']['BgTwo'] = '#D5D5D5';
+
+/**
+ * query window
+ */
+// Width of Query window
+$GLOBALS['cfg']['QueryWindowWidth'] = 600;
+// Height of Query window
+$GLOBALS['cfg']['QueryWindowHeight'] = 400;
+
+/**
+ * Chart colors
+ */
+
+ $GLOBALS['cfg']['chartColor'] = array(
+ 'gradientIntensity' => 0,
+ // The style of the chart title.
+ 'titleColor' => '#000000',
+ 'titleBgColor' => $GLOBALS['cfg']['ThBackground'],
+ // Chart border (0 for no border)
+ 'border' => '#CCCCCC',
+ // Chart background color.
+ 'bgColor' => $GLOBALS['cfg']['BgTwo'],
+ // when graph area gradient is used, this is the color of the graph
+ // area border
+ 'graphAreaColor' => '#D5D9DD',
+ // the background color of the inner graph area
+ 'graphAreaGradientColor' => $GLOBALS['cfg']['BgOne'],
+ // the color of the grid lines in the graph area
+ 'gridColor' => '#E6E6E6',
+ // the color of the scale and the labels
+ 'scaleColor' => '#D5D9DD',
+ );
+
+?>
diff --git a/themes/original/screen.png b/themes/original/screen.png
new file mode 100644
index 0000000000..c2f4053510
--- /dev/null
+++ b/themes/original/screen.png
Binary files differ
diff --git a/themes/original/sprites.lib.php b/themes/original/sprites.lib.php
new file mode 100644
index 0000000000..ec57007636
--- /dev/null
+++ b/themes/original/sprites.lib.php
@@ -0,0 +1,660 @@
+<?php
+/**
+ * AUTOGENERATED CONTENT - DO NOT EDIT!
+ * ALL CHANGES WILL BE UNDONE!
+ * RUN `./scripts/generate-sprites` TO UPDATE THIS FILE
+ *
+ * @package PhpMyAdmin-theme
+ */
+
+/**
+ * Returns map of sprites inside sprite.png
+ *
+ * @return array Data of sprites
+ */
+function PMA_sprites()
+{
+ return array(
+ 'b_bookmark' => array(
+ 'position' => '1',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'b_browse' => array(
+ 'position' => '2',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'b_calendar' => array(
+ 'position' => '3',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'b_chart' => array(
+ 'position' => '4',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'b_close' => array(
+ 'position' => '5',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'b_column_add' => array(
+ 'position' => '6',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'b_comment' => array(
+ 'position' => '7',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'b_dbstatistics' => array(
+ 'position' => '8',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'b_deltbl' => array(
+ 'position' => '9',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'b_docs' => array(
+ 'position' => '10',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'b_drop' => array(
+ 'position' => '11',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'b_edit' => array(
+ 'position' => '12',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'b_empty' => array(
+ 'position' => '13',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'b_engine' => array(
+ 'position' => '14',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'b_event_add' => array(
+ 'position' => '15',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'b_events' => array(
+ 'position' => '16',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'b_export' => array(
+ 'position' => '17',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'b_find_replace' => array(
+ 'position' => '18',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'b_ftext' => array(
+ 'position' => '19',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'b_group' => array(
+ 'position' => '20',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'b_help' => array(
+ 'position' => '21',
+ 'width' => '11',
+ 'height' => '11'
+ ),
+ 'b_home' => array(
+ 'position' => '22',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'b_import' => array(
+ 'position' => '23',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'b_index' => array(
+ 'position' => '24',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'b_index_add' => array(
+ 'position' => '25',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'b_info' => array(
+ 'position' => '26',
+ 'width' => '11',
+ 'height' => '11'
+ ),
+ 'b_inline_edit' => array(
+ 'position' => '27',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'b_insrow' => array(
+ 'position' => '28',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'b_minus' => array(
+ 'position' => '29',
+ 'width' => '9',
+ 'height' => '9'
+ ),
+ 'b_more' => array(
+ 'position' => '30',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'b_move' => array(
+ 'position' => '31',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'b_newdb' => array(
+ 'position' => '32',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'b_newtbl' => array(
+ 'position' => '33',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'b_nextpage' => array(
+ 'position' => '34',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'b_plus' => array(
+ 'position' => '35',
+ 'width' => '9',
+ 'height' => '9'
+ ),
+ 'b_primary' => array(
+ 'position' => '36',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'b_print' => array(
+ 'position' => '37',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'b_props' => array(
+ 'position' => '38',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'b_relations' => array(
+ 'position' => '39',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'b_routine_add' => array(
+ 'position' => '40',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'b_routines' => array(
+ 'position' => '41',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'b_save' => array(
+ 'position' => '42',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'b_sbrowse' => array(
+ 'position' => '43',
+ 'width' => '10',
+ 'height' => '10'
+ ),
+ 'b_search' => array(
+ 'position' => '44',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'b_selboard' => array(
+ 'position' => '45',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'b_select' => array(
+ 'position' => '46',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'b_snewtbl' => array(
+ 'position' => '47',
+ 'width' => '10',
+ 'height' => '10'
+ ),
+ 'b_spatial' => array(
+ 'position' => '48',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'b_sql' => array(
+ 'position' => '49',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'b_sqlhelp' => array(
+ 'position' => '50',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'b_table_add' => array(
+ 'position' => '51',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'b_tblanalyse' => array(
+ 'position' => '52',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'b_tblexport' => array(
+ 'position' => '53',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'b_tblimport' => array(
+ 'position' => '54',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'b_tblops' => array(
+ 'position' => '55',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'b_tbloptimize' => array(
+ 'position' => '56',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'b_tipp' => array(
+ 'position' => '57',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'b_trigger_add' => array(
+ 'position' => '58',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'b_triggers' => array(
+ 'position' => '59',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'b_undo' => array(
+ 'position' => '60',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'b_unique' => array(
+ 'position' => '61',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'b_usradd' => array(
+ 'position' => '62',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'b_usrcheck' => array(
+ 'position' => '63',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'b_usrdrop' => array(
+ 'position' => '64',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'b_usredit' => array(
+ 'position' => '65',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'b_usrlist' => array(
+ 'position' => '66',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'b_view' => array(
+ 'position' => '67',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'b_view_add' => array(
+ 'position' => '68',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'b_views' => array(
+ 'position' => '69',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'bd_browse' => array(
+ 'position' => '70',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'bd_deltbl' => array(
+ 'position' => '71',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'bd_drop' => array(
+ 'position' => '72',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'bd_edit' => array(
+ 'position' => '73',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'bd_empty' => array(
+ 'position' => '74',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'bd_export' => array(
+ 'position' => '75',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'bd_ftext' => array(
+ 'position' => '76',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'bd_index' => array(
+ 'position' => '77',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'bd_insrow' => array(
+ 'position' => '78',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'bd_nextpage' => array(
+ 'position' => '79',
+ 'width' => '8',
+ 'height' => '13'
+ ),
+ 'bd_primary' => array(
+ 'position' => '80',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'bd_sbrowse' => array(
+ 'position' => '81',
+ 'width' => '10',
+ 'height' => '10'
+ ),
+ 'bd_select' => array(
+ 'position' => '82',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'bd_spatial' => array(
+ 'position' => '83',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'bd_unique' => array(
+ 'position' => '84',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'col_drop' => array(
+ 'position' => '85',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'eye' => array(
+ 'position' => '86',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'eye_grey' => array(
+ 'position' => '87',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'lightbulb' => array(
+ 'position' => '88',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'lightbulb_off' => array(
+ 'position' => '89',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'more' => array(
+ 'position' => '90',
+ 'width' => '13',
+ 'height' => '16'
+ ),
+ 'new_data' => array(
+ 'position' => '91',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'new_data_hovered' => array(
+ 'position' => '92',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'new_data_selected' => array(
+ 'position' => '93',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'new_data_selected_hovered' => array(
+ 'position' => '94',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'new_struct' => array(
+ 'position' => '95',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'new_struct_hovered' => array(
+ 'position' => '96',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'new_struct_selected' => array(
+ 'position' => '97',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'new_struct_selected_hovered' => array(
+ 'position' => '98',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'pause' => array(
+ 'position' => '99',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'play' => array(
+ 'position' => '100',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 's_asc' => array(
+ 'position' => '101',
+ 'width' => '11',
+ 'height' => '9'
+ ),
+ 's_asci' => array(
+ 'position' => '102',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 's_cancel' => array(
+ 'position' => '103',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 's_cog' => array(
+ 'position' => '104',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 's_db' => array(
+ 'position' => '105',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 's_desc' => array(
+ 'position' => '106',
+ 'width' => '11',
+ 'height' => '9'
+ ),
+ 's_error' => array(
+ 'position' => '107',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 's_error2' => array(
+ 'position' => '108',
+ 'width' => '11',
+ 'height' => '11'
+ ),
+ 's_host' => array(
+ 'position' => '109',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 's_info' => array(
+ 'position' => '110',
+ 'width' => '11',
+ 'height' => '11'
+ ),
+ 's_lang' => array(
+ 'position' => '111',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 's_loggoff' => array(
+ 'position' => '112',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 's_notice' => array(
+ 'position' => '113',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 's_passwd' => array(
+ 'position' => '114',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 's_really' => array(
+ 'position' => '115',
+ 'width' => '11',
+ 'height' => '11'
+ ),
+ 's_reload' => array(
+ 'position' => '116',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 's_replication' => array(
+ 'position' => '117',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 's_rights' => array(
+ 'position' => '118',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 's_sortable' => array(
+ 'position' => '119',
+ 'width' => '11',
+ 'height' => '15'
+ ),
+ 's_status' => array(
+ 'position' => '120',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 's_success' => array(
+ 'position' => '121',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 's_sync' => array(
+ 'position' => '122',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 's_tbl' => array(
+ 'position' => '123',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 's_theme' => array(
+ 'position' => '124',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 's_top' => array(
+ 'position' => '125',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 's_vars' => array(
+ 'position' => '126',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 's_views' => array(
+ 'position' => '127',
+ 'width' => '10',
+ 'height' => '10'
+ ),
+ 'window-new' => array(
+ 'position' => '128',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ );
+}
+?>
diff --git a/themes/pmahomme/css/codemirror.css.php b/themes/pmahomme/css/codemirror.css.php
new file mode 100644
index 0000000000..6a3fe36d64
--- /dev/null
+++ b/themes/pmahomme/css/codemirror.css.php
@@ -0,0 +1,303 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Styles for CodeMirror editor
+ * for the pmahomme theme
+ *
+ * @package PhpMyAdmin-theme
+ * @subpackage PMAHomme
+ */
+
+// unplanned execution path
+if (! defined('PMA_MINIMUM_COMMON') && ! defined('TESTSUITE')) {
+ exit();
+}
+?>
+
+/* PADDING */
+
+.CodeMirror-lines {
+ padding: 4px 0; /* Vertical padding around content */
+}
+.CodeMirror pre {
+ padding: 0 4px; /* Horizontal padding of content */
+}
+
+.CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
+ background-color: white; /* The little square between H and V scrollbars */
+}
+
+/* GUTTER */
+
+.CodeMirror-gutters {
+ border-right: 1px solid #ddd;
+ background-color: #f7f7f7;
+ white-space: nowrap;
+}
+.CodeMirror-linenumbers {}
+.CodeMirror-linenumber {
+ padding: 0 3px 0 5px;
+ min-width: 20px;
+ text-align: right;
+ color: #999;
+}
+
+/* CURSOR */
+
+.CodeMirror div.CodeMirror-cursor {
+ border-left: 1px solid black;
+ z-index: 3;
+}
+/* Shown when moving in bi-directional text */
+.CodeMirror div.CodeMirror-secondarycursor {
+ border-left: 1px solid silver;
+}
+.CodeMirror.cm-keymap-fat-cursor div.CodeMirror-cursor {
+ width: auto;
+ border: 0;
+ background: #7e7;
+ z-index: 1;
+}
+/* Can style cursor different in overwrite (non-insert) mode */
+.CodeMirror div.CodeMirror-cursor.CodeMirror-overwrite {}
+
+.cm-tab { display: inline-block; }
+
+/* DEFAULT THEME */
+
+.cm-s-default .cm-keyword {color: #708;}
+.cm-s-default .cm-atom {color: #219;}
+.cm-s-default .cm-number {color: #164;}
+.cm-s-default .cm-def {color: #00f;}
+.cm-s-default .cm-variable {color: black;}
+.cm-s-default .cm-variable-2 {color: #05a;}
+.cm-s-default .cm-variable-3 {color: #085;}
+.cm-s-default .cm-property {color: black;}
+.cm-s-default .cm-operator {color: black;}
+.cm-s-default .cm-comment {color: #a50;}
+.cm-s-default .cm-string {color: #a11;}
+.cm-s-default .cm-string-2 {color: #f50;}
+.cm-s-default .cm-meta {color: #555;}
+.cm-s-default .cm-error {color: #f00;}
+.cm-s-default .cm-qualifier {color: #555;}
+.cm-s-default .cm-builtin {color: #30a;}
+.cm-s-default .cm-bracket {color: #997;}
+.cm-s-default .cm-tag {color: #170;}
+.cm-s-default .cm-attribute {color: #00c;}
+.cm-s-default .cm-header {color: blue;}
+.cm-s-default .cm-quote {color: #090;}
+.cm-s-default .cm-hr {color: #999;}
+.cm-s-default .cm-link {color: #00c;}
+
+.cm-negative {color: #d44;}
+.cm-positive {color: #292;}
+.cm-header, .cm-strong {font-weight: bold;}
+.cm-em {font-style: italic;}
+.cm-link {text-decoration: underline;}
+
+.cm-invalidchar {color: #f00;}
+
+div.CodeMirror span.CodeMirror-matchingbracket {color: #0f0;}
+div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;}
+
+/* STOP */
+
+/* The rest of this file contains styles related to the mechanics of
+ the editor. You probably shouldn't touch them. */
+
+.CodeMirror {
+ line-height: 1;
+ position: relative;
+ overflow: hidden;
+ background: white;
+ color: black;
+ font-family: monospace;
+ height: <?php echo ceil($GLOBALS['cfg']['TextareaRows'] * 1.2); ?>em;
+}
+
+#inline_editor_outer .CodeMirror {
+ height: <?php echo ceil($GLOBALS['cfg']['TextareaRows'] * 0.4); ?>em;
+}
+
+.CodeMirror-scroll {
+ /* 30px is the magic margin used to hide the element's real scrollbars */
+ /* See overflow: hidden in .CodeMirror */
+ margin-bottom: -30px; margin-right: -30px;
+ padding-bottom: 30px; padding-right: 30px;
+ height: 100%;
+ outline: none; /* Prevent dragging from highlighting the element */
+ position: relative;
+}
+.CodeMirror-sizer {
+ position: relative;
+}
+
+/* The fake, visible scrollbars. Used to force redraw during scrolling
+ before actuall scrolling happens, thus preventing shaking and
+ flickering artifacts. */
+.CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
+ position: absolute;
+ z-index: 6;
+ display: none;
+}
+.CodeMirror-vscrollbar {
+ right: 0; top: 0;
+ overflow-x: hidden;
+ overflow-y: scroll;
+}
+.CodeMirror-hscrollbar {
+ bottom: 0; left: 0;
+ overflow-y: hidden;
+ overflow-x: scroll;
+}
+.CodeMirror-scrollbar-filler {
+ right: 0; bottom: 0;
+}
+.CodeMirror-gutter-filler {
+ left: 0; bottom: 0;
+}
+
+.CodeMirror-gutters {
+ position: absolute; left: 0; top: 0;
+ padding-bottom: 30px;
+ z-index: 3;
+}
+.CodeMirror-gutter {
+ white-space: normal;
+ height: 100%;
+ padding-bottom: 30px;
+ margin-bottom: -32px;
+ display: inline-block;
+ /* Hack to make IE7 behave */
+ *zoom:1;
+ *display:inline;
+}
+.CodeMirror-gutter-elt {
+ position: absolute;
+ cursor: default;
+ z-index: 4;
+}
+
+.CodeMirror-lines {
+ cursor: text;
+}
+.CodeMirror pre {
+ /* Reset some styles that the rest of the page might have set */
+ -moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0;
+ border-width: 0;
+ background: transparent;
+ font-family: inherit;
+ font-size: inherit;
+ margin: 0;
+ white-space: pre;
+ word-wrap: normal;
+ line-height: inherit;
+ color: inherit;
+ z-index: 2;
+ position: relative;
+ overflow: visible;
+}
+.CodeMirror-wrap pre {
+ word-wrap: break-word;
+ white-space: pre-wrap;
+ word-break: normal;
+}
+.CodeMirror-code pre {
+ border-right: 30px solid transparent;
+ width: -webkit-fit-content;
+ width: -moz-fit-content;
+ width: fit-content;
+}
+.CodeMirror-wrap .CodeMirror-code pre {
+ border-right: none;
+ width: auto;
+}
+.CodeMirror-linebackground {
+ position: absolute;
+ left: 0; right: 0; top: 0; bottom: 0;
+ z-index: 0;
+}
+
+.CodeMirror-linewidget {
+ position: relative;
+ z-index: 2;
+ overflow: auto;
+}
+
+.CodeMirror-widget {
+ display: inline-block;
+}
+
+.CodeMirror-wrap .CodeMirror-scroll {
+ overflow-x: hidden;
+}
+
+.CodeMirror-measure {
+ position: absolute;
+ width: 100%; height: 0px;
+ overflow: hidden;
+ visibility: hidden;
+}
+.CodeMirror-measure pre { position: static; }
+
+.CodeMirror div.CodeMirror-cursor {
+ position: absolute;
+ visibility: hidden;
+ border-right: none;
+ width: 0;
+}
+.CodeMirror-focused div.CodeMirror-cursor {
+ visibility: visible;
+}
+
+.CodeMirror-selected { background: #d9d9d9; }
+.CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; }
+
+.cm-searching {
+ background: #ffa;
+ background: rgba(255, 255, 0, .4);
+}
+
+/* IE7 hack to prevent it from returning funny offsetTops on the spans */
+.CodeMirror span { *vertical-align: text-bottom; }
+
+@media print {
+ /* Hide the cursor when printing */
+ .CodeMirror div.CodeMirror-cursor {
+ visibility: hidden;
+ }
+}
+
+span.cm-keyword, span.cm-statement-verb {
+ color: #909;
+}
+span.cm-variable {
+ color: black;
+}
+span.cm-comment {
+ color: #808000;
+}
+span.cm-mysql-string {
+ color: #008000;
+}
+span.cm-operator {
+ color: fuchsia;
+}
+span.cm-mysql-word {
+ color: black;
+}
+span.cm-builtin {
+ color: #f00;
+}
+span.cm-variable-2 {
+ color: #f90;
+}
+span.cm-variable-3 {
+ color: #00f;
+}
+span.cm-separator {
+ color: fuchsia;
+}
+span.cm-number {
+ color: teal;
+}
diff --git a/themes/pmahomme/css/common.css.php b/themes/pmahomme/css/common.css.php
new file mode 100644
index 0000000000..9c8c43d0b4
--- /dev/null
+++ b/themes/pmahomme/css/common.css.php
@@ -0,0 +1,2797 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Common styles for the pmahomme theme
+ *
+ * @package PhpMyAdmin-theme
+ * @subpackage PMAHomme
+ */
+
+// unplanned execution path
+if (! defined('PMA_MINIMUM_COMMON') && ! defined('TESTSUITE')) {
+ exit();
+}
+?>
+/******************************************************************************/
+
+/* general tags */
+html {
+ font-size: <?php echo $_SESSION['PMA_Theme']->getFontSize(); ?>
+}
+
+input,
+select,
+textarea {
+ font-size: 1em;
+}
+
+
+body {
+<?php if (! empty($GLOBALS['cfg']['FontFamily'])) { ?>
+ font-family: <?php echo $GLOBALS['cfg']['FontFamily']; ?>;
+<?php } ?>
+ padding: 0;
+ margin: 0;
+ margin-<?php echo $left; ?>: 240px;
+ color: #444;
+ background: #fff;
+}
+
+body#loginform {
+ margin: 0;
+}
+
+#page_content {
+ margin: 0 .5em;
+}
+
+<?php if (! empty($GLOBALS['cfg']['FontFamilyFixed'])) { ?>
+textarea,
+tt,
+pre,
+code {
+ font-family: <?php echo $GLOBALS['cfg']['FontFamilyFixed']; ?>;
+}
+<?php } ?>
+
+
+h1 {
+ font-size: 140%;
+ font-weight: bold;
+}
+
+h2 {
+ font-size: 2em;
+ font-weight: normal;
+ text-shadow: 0 1px 0 #fff;
+ padding: 10px 0 10px;
+ padding-<?php echo $left; ?>: 3px;
+ color: #777;
+}
+
+/* Hiding icons in the page titles */
+h2 img {
+ display: none;
+}
+
+h2 a img {
+ display: inline;
+}
+
+.data,
+.data_full_width {
+ margin: 0 0 12px;
+}
+
+.data_full_width {
+ width: 100%;
+}
+
+#table_results td.data {
+border-right: 1px solid #bbb;
+}
+
+h3 {
+ font-weight: bold;
+}
+
+a,
+a:link,
+a:visited,
+a:active {
+ text-decoration: none;
+ color: #235a81;
+ cursor: pointer;
+ outline: none;
+
+}
+
+a:hover {
+ text-decoration: underline;
+ color: #235a81;
+}
+
+#initials_table {
+ background: #f3f3f3;
+ border: 1px solid #aaa;
+ margin-bottom: 10px;
+ -moz-border-radius: 5px;
+ -webkit-border-radius: 5px;
+ border-radius: 5px;
+}
+
+#initials_table td {
+ padding: 8px !important;
+}
+
+#initials_table a {
+ border: 1px solid #aaa;
+ background: #fff;
+ padding: 4px 8px;
+ -moz-border-radius: 5px;
+ -webkit-border-radius: 5px;
+ border-radius: 5px;
+ <?php echo $_SESSION['PMA_Theme']->getCssGradient('ffffff', 'cccccc'); ?>
+}
+
+dfn {
+ font-style: normal;
+}
+
+dfn:hover {
+ font-style: normal;
+ cursor: help;
+}
+
+th {
+ font-weight: bold;
+ color: <?php echo $GLOBALS['cfg']['ThColor']; ?>;
+ background: #f3f3f3;
+ <?php echo $_SESSION['PMA_Theme']->getCssGradient('ffffff', 'cccccc'); ?>
+}
+
+a img {
+ border: 0;
+}
+
+hr {
+ color: <?php echo $GLOBALS['cfg']['MainColor']; ?>;
+ background-color: <?php echo $GLOBALS['cfg']['MainColor']; ?>;
+ border: 0;
+ height: 1px;
+}
+
+form {
+ padding: 0;
+ margin: 0;
+ display: inline;
+}
+
+input[type=text],
+input[type=password],
+input[type=number],
+input[type=date] {
+ border-radius: 2px;
+ -moz-border-radius: 2px;
+ -webkit-border-radius: 2px;
+
+ box-shadow: 0 1px 2px #ddd;
+ -moz-box-shadow: 0 1px 2px #ddd;
+ -webkit-box-shadow: 0 1px 2px #ddd;
+
+ background: white;
+ border: 1px solid #aaa;
+ color: #555;
+ padding: 4px;
+ margin: 6px;
+
+}
+
+input[type=submit],
+button[type=submit]:not(.mult_submit) {
+ font-weight: bold !important;
+}
+
+input[type=submit],
+button[type=submit]:not(.mult_submit),
+input[type=reset],
+input[name=submit_reset],
+input.button {
+ margin-left: 14px;
+ border: 1px solid #aaa;
+ padding: 3px 7px;
+ color: #111;
+ text-decoration: none;
+ background: #ddd;
+
+ border-radius: 12px;
+ -webkit-border-radius: 12px;
+ -moz-border-radius: 12px;
+
+ text-shadow: 0 1px 0 #fff;
+
+ <?php echo $_SESSION['PMA_Theme']->getCssGradient('ffffff', 'cccccc'); ?>
+}
+
+input[type=submit]:hover,
+button[type=submit]:not(.mult_submit):hover,
+input[type=reset]:hover,
+input[name=submit_reset]:hover,
+input.button:hover {
+ position: relative;
+ <?php echo $_SESSION['PMA_Theme']->getCssGradient('cccccc', 'dddddd'); ?>
+ cursor: pointer;
+}
+
+input[type=submit]:active,
+button[type=submit]:not(.mult_submit):active,
+input[type=reset]:active,
+input[name=submit_reset]:active,
+input.button:active {
+ position: relative;
+ top: 1px;
+ left: 1px;
+}
+
+textarea {
+ overflow: visible;
+ height: <?php echo ceil($GLOBALS['cfg']['TextareaRows'] * 1.2); ?>em;
+}
+
+textarea.char {
+ height: <?php echo ceil($GLOBALS['cfg']['CharTextareaRows'] * 1.2); ?>em;
+}
+
+fieldset {
+ margin-top: 1em;
+ border-radius: 4px 4px 0 0;
+ -moz-border-radius: 4px 4px 0 0;
+ -webkit-border-radius: 4px 4px 0 0;
+ border: #aaa solid 1px;
+ padding: 1.5em;
+ background: #eee;
+ text-shadow: <?php echo $GLOBALS['text_dir'] === 'rtl' ? '-' : ''; ?>1px 1px 2px #fff inset;
+ -moz-box-shadow: <?php echo $GLOBALS['text_dir'] === 'rtl' ? '-' : ''; ?>1px 1px 2px #fff inset;
+ -webkit-box-shadow: <?php echo $GLOBALS['text_dir'] === 'rtl' ? '-' : ''; ?>1px 1px 2px #fff inset;
+ box-shadow: <?php echo $GLOBALS['text_dir'] === 'rtl' ? '-' : ''; ?>1px 1px 2px #fff inset;
+}
+
+fieldset fieldset {
+ margin: .8em;
+ background: #fff;
+ border: 1px solid #aaa;
+ background: #E8E8E8;
+
+}
+
+fieldset legend {
+ font-weight: bold;
+ color: #444;
+ padding: 5px 10px;
+ border-radius: 2px;
+ -moz-border-radius: 2px;
+ -webkit-border-radius: 2px;
+ border: 1px solid #aaa;
+ background-color: #fff;
+ -moz-box-shadow: <?php echo $GLOBALS['text_dir'] === 'rtl' ? '-' : ''; ?>3px 3px 15px #bbb;
+ -webkit-box-shadow: <?php echo $GLOBALS['text_dir'] === 'rtl' ? '-' : ''; ?>3px 3px 15px #bbb;
+ box-shadow: <?php echo $GLOBALS['text_dir'] === 'rtl' ? '-' : ''; ?>3px 3px 15px #bbb;
+ max-width: 100%;
+}
+
+.some-margin {
+ margin: 1.5em;
+}
+
+/* buttons in some browsers (eg. Konqueror) are block elements,
+ this breaks design */
+button {
+ display: inline;
+}
+
+table caption,
+table th,
+table td {
+ padding: .3em;
+ margin: .1em;
+ vertical-align: top;
+ text-shadow: 0 1px 0 #fff;
+}
+
+/* 3.4 */
+table {
+ border-collapse: collapse;
+}
+
+th {
+ border-right: 1px solid #fff;
+ text-align: left;
+}
+
+
+img,
+button {
+ vertical-align: middle;
+}
+
+input[type="checkbox"],
+input[type="radio"] {
+ vertical-align: -11%;
+}
+
+
+select {
+ -moz-border-radius: 2px;
+ -webkit-border-radius: 2px;
+ border-radius: 2px;
+
+ -moz-box-shadow: 0 1px 2px #ddd;
+ -webkit-box-shadow: 0 1px 2px #ddd;
+ box-shadow: 0 1px 2px #ddd;
+
+ border: 1px solid #aaa;
+ color: #333;
+ padding: 3px;
+ background: white;
+}
+
+select[multiple] {
+ <?php echo $_SESSION['PMA_Theme']->getCssGradient('ffffff', 'f2f2f2'); ?>
+}
+
+/******************************************************************************/
+/* classes */
+.clearfloat {
+ clear: both;
+}
+
+.floatleft {
+ float: <?php echo $left; ?>;
+ margin-<?php echo $right; ?>: 1em;
+}
+
+.floatright {
+ float: <?php echo $right; ?>;
+}
+
+.center {
+ text-align: center;
+}
+
+table.nospacing {
+ border-spacing: 0;
+}
+
+table.nopadding tr th, table.nopadding tr td {
+ padding: 0;
+}
+
+th.left, td.left {
+ text-align: left;
+}
+
+th.center, td.center {
+ text-align: center;
+}
+
+th.right, td.right {
+ text-align: right;
+}
+
+tr.vtop th, tr.vtop td, th.vtop, td.vtop {
+ vertical-align: top;
+}
+
+tr.vmiddle th, tr.vmiddle td, th.vmiddle, td.vmiddle {
+ vertical-align: middle;
+}
+
+tr.vbottom th, tr.vbottom td, th.vbottom, td.vbottom {
+ vertical-align: bottom;
+}
+
+.paddingtop {
+ padding-top: 1em;
+}
+
+.separator {
+ color: #fff;
+ text-shadow: 0 1px 0 #000;
+}
+
+div.tools {
+ /* border: 1px solid #000; */
+ padding: .2em;
+}
+
+div.tools a {
+ color: #3a7ead !important;
+}
+
+div.tools,
+fieldset.tblFooters {
+ margin-top: 0;
+ margin-bottom: .5em;
+ /* avoid a thick line since this should be used under another fieldset */
+ border-top: 0;
+ text-align: <?php echo $right; ?>;
+ float: none;
+ clear: both;
+ -webkit-border-radius: 0 0 4px 4px;
+ -moz-border-radius: 0 0 4px 4px;
+ border-radius: 0 0 4px 5px;
+}
+
+div.null_div {
+ height: 20px;
+ text-align: center;
+ font-style: normal;
+ min-width: 50px;
+}
+
+fieldset .formelement {
+ float: <?php echo $left; ?>;
+ margin-<?php echo $right; ?>: .5em;
+ /* IE */
+ white-space: nowrap;
+}
+
+/* revert for Gecko */
+fieldset div[class=formelement] {
+ white-space: normal;
+}
+
+button.mult_submit {
+ border: none;
+ background-color: transparent;
+}
+
+/* odd items 1,3,5,7,... */
+table tr.odd th,
+.odd {
+ background: #fff;
+ <?php echo $_SESSION['PMA_Theme']->getCssIEClearFilter(); ?>
+}
+
+/* even items 2,4,6,8,... */
+/* (tested on CRTs and ACLs) */
+table tr.even th,
+.even {
+ background: #DFDFDF;
+ <?php echo $_SESSION['PMA_Theme']->getCssIEClearFilter(); ?>
+}
+
+/* odd table rows 1,3,5,7,... */
+table tr.odd th,
+table tr.odd,
+table tr.even th,
+table tr.even {
+ text-align: <?php echo $left; ?>;
+}
+
+<?php if ($GLOBALS['cfg']['BrowseMarkerEnable']) { ?>
+/* marked table rows */
+td.marked,
+table tr.marked td,
+table tr.marked th,
+table tr.marked {
+ <?php echo $_SESSION['PMA_Theme']->getCssGradient('ced6df', 'b6c6d7'); ?>
+ color: <?php echo $GLOBALS['cfg']['BrowseMarkerColor']; ?>;
+}
+<?php } ?>
+
+<?php if ($GLOBALS['cfg']['BrowsePointerEnable']) { ?>
+/* hovered items */
+.odd:hover,
+.even:hover,
+.hover {
+ <?php echo $_SESSION['PMA_Theme']->getCssGradient('ced6df', 'b6c6d7'); ?>
+ color: <?php echo $GLOBALS['cfg']['BrowsePointerColor']; ?>;
+}
+
+/* hovered table rows */
+table tr.odd:hover th,
+table tr.even:hover th,
+table tr.hover th {
+ <?php echo $_SESSION['PMA_Theme']->getCssGradient('ced6df', 'b6c6d7'); ?>
+ color: <?php echo $GLOBALS['cfg']['BrowsePointerColor']; ?>;
+}
+<?php } ?>
+
+/**
+ * marks table rows/cells if the db field is in a where condition
+ */
+.condition {
+ border-color: <?php echo $GLOBALS['cfg']['BrowseMarkerBackground']; ?> !important;
+}
+
+th.condition {
+ border-width: 1px 1px 0 1px;
+ border-style: solid;
+}
+
+td.condition {
+ border-width: 0 1px 0 1px;
+ border-style: solid;
+}
+
+tr:last-child td.condition {
+ border-width: 0 1px 1px 1px;
+}
+
+<?php if ($GLOBALS['text_dir'] === 'ltr') { ?>
+/* for first th which must have right border set (ltr only) */
+.before-condition {
+ border-right: 1px solid <?php echo $GLOBALS['cfg']['BrowseMarkerBackground']; ?>;
+}
+<?php } ?>
+
+/**
+ * cells with the value NULL
+ */
+td.null {
+ font-style: italic;
+ text-align: <?php echo $right; ?>;
+}
+
+table .valueHeader {
+ text-align: <?php echo $right; ?>;
+ white-space: normal;
+}
+table .value {
+ text-align: <?php echo $right; ?>;
+ white-space: normal;
+}
+/* IE doesnt handles 'pre' right */
+table [class=value] {
+ white-space: normal;
+}
+
+
+<?php if (! empty($GLOBALS['cfg']['FontFamilyFixed'])) { ?>
+.value {
+ font-family: <?php echo $GLOBALS['cfg']['FontFamilyFixed']; ?>;
+}
+<?php } ?>
+.attention {
+ color: red;
+ font-weight: bold;
+}
+.allfine {
+ color: green;
+}
+
+
+img.lightbulb {
+ cursor: pointer;
+}
+
+.pdflayout {
+ overflow: hidden;
+ clip: inherit;
+ background-color: #fff;
+ display: none;
+ border: 1px solid #000;
+ position: relative;
+}
+
+.pdflayout_table {
+ background: #D3DCE3;
+ color: #000;
+ overflow: hidden;
+ clip: inherit;
+ z-index: 2;
+ display: inline;
+ visibility: inherit;
+ cursor: move;
+ position: absolute;
+ font-size: 80%;
+ border: 1px dashed #000;
+}
+
+/* Doc links in SQL */
+.cm-sql-doc {
+ text-decoration: none;
+ border-bottom: 1px dotted #000;
+ color: inherit !important;
+}
+
+/* no extra space in table cells */
+td .icon {
+ margin: 0;
+}
+
+.selectallarrow {
+ margin-<?php echo $right; ?>: .3em;
+ margin-<?php echo $left; ?>: .6em;
+}
+
+/* message boxes: error, confirmation */
+#pma_errors, #pma_demo {
+ padding: 0 0.5em;
+}
+
+.success h1,
+.notice h1,
+div.error h1 {
+ border-bottom: 2px solid;
+ font-weight: bold;
+ text-align: <?php echo $left; ?>;
+ margin: 0 0 .2em 0;
+}
+
+div.success,
+div.notice,
+div.error {
+ margin: .5em 0 1.3em;
+ border: 1px solid;
+ background-repeat: no-repeat;
+ <?php if ($GLOBALS['text_dir'] === 'ltr') { ?>
+ background-position: 10px 50%;
+ padding: 10px 10px 10px 10px;
+ <?php } else { ?>
+ background-position: 99% 50%;
+ padding: 10px 35px 10px 10px;
+ <?php } ?>
+
+ -moz-border-radius: 5px;
+ -webkit-border-radius: 5px;
+ border-radius: 5px;
+
+ -moz-box-shadow: 0 1px 1px #fff inset;
+ -webkit-box-shadow: 0 1px 1px #fff inset;
+ box-shadow: 0 1px 1px #fff inset;
+}
+
+.success a,
+.notice a,
+.error a {
+ text-decoration: underline;
+}
+
+.success {
+ color: #000;
+ background-color: #ebf8a4;
+}
+
+h1.success,
+div.success {
+ border-color: #a2d246;
+}
+.success h1 {
+ border-color: #00FF00;
+}
+
+.notice {
+ color: #000;
+ background-color: #e8eef1;
+}
+
+h1.notice,
+div.notice {
+ border-color: #3a6c7e;
+}
+
+.notice h1 {
+ border-color: #ffb10a;
+}
+
+.error {
+ border: 1px solid maroon !important;
+ color: #000;
+ background: pink;
+}
+
+h1.error,
+div.error {
+ border-color: #333;
+}
+
+div.error h1 {
+ border-color: #ff0000;
+}
+
+.confirmation {
+ color: #000;
+ background-color: pink;
+}
+
+fieldset.confirmation {
+}
+
+fieldset.confirmation legend {
+}
+
+/* end messageboxes */
+
+.tblcomment {
+ font-size: 70%;
+ font-weight: normal;
+ color: #000099;
+}
+
+.tblHeaders {
+ font-weight: bold;
+ color: <?php echo $GLOBALS['cfg']['ThColor']; ?>;
+ background: <?php echo $GLOBALS['cfg']['ThBackground']; ?>;
+}
+
+div.tools,
+.tblFooters {
+ font-weight: normal;
+ color: <?php echo $GLOBALS['cfg']['ThColor']; ?>;
+ background: <?php echo $GLOBALS['cfg']['ThBackground']; ?>;
+}
+
+.tblHeaders a:link,
+.tblHeaders a:active,
+.tblHeaders a:visited,
+div.tools a:link,
+div.tools a:visited,
+div.tools a:active,
+.tblFooters a:link,
+.tblFooters a:active,
+.tblFooters a:visited {
+ color: #0000FF;
+}
+
+.tblHeaders a:hover,
+div.tools a:hover,
+.tblFooters a:hover {
+ color: #FF0000;
+}
+
+/* forbidden, no privileges */
+.noPrivileges {
+ color: #FF0000;
+ font-weight: bold;
+}
+
+/* disabled text */
+.disabled,
+.disabled a:link,
+.disabled a:active,
+.disabled a:visited {
+ color: #666;
+}
+
+.disabled a:hover {
+ color: #666;
+ text-decoration: none;
+}
+
+tr.disabled td,
+td.disabled {
+ background-color: #f3f3f3;
+ color: #aaa;
+}
+
+.nowrap {
+ white-space: nowrap;
+}
+
+/**
+ * login form
+ */
+body#loginform h1,
+body#loginform a.logo {
+ display: block;
+ text-align: center;
+}
+
+body#loginform {
+ margin-top: 1em;
+ text-align: center;
+}
+
+body#loginform div.container {
+ text-align: <?php echo $left; ?>;
+ width: 30em;
+ margin: 0 auto;
+}
+
+form.login label {
+ float: <?php echo $left; ?>;
+ width: 10em;
+ font-weight: bolder;
+}
+
+.commented_column {
+ border-bottom: 1px dashed #000;
+}
+
+.column_attribute {
+ font-size: 70%;
+}
+
+/******************************************************************************/
+/* specific elements */
+
+/* topmenu */
+#topmenu a {
+ text-shadow: 0 1px 0 #fff;
+}
+
+#topmenu .error {
+ background: #eee;border: 0 !important;color: #aaa;
+}
+
+ul#topmenu,
+ul#topmenu2,
+ul.tabs {
+ font-weight: bold;
+ list-style-type: none;
+ margin: 0;
+ padding: 0;
+
+}
+
+ul#topmenu2 {
+ margin: .25em .5em 0;
+ height: 2em;
+ clear: both;
+}
+
+ul#topmenu li,
+ul#topmenu2 li {
+ float: <?php echo $left; ?>;
+ margin: 0;
+ vertical-align: middle;
+}
+
+#topmenu img,
+#topmenu2 img {
+ margin-right: .5em;
+ vertical-align: -3px;
+}
+
+.menucontainer {
+ <?php echo $_SESSION['PMA_Theme']->getCssGradient('ffffff', 'dcdcdc'); ?>
+ border-top: 1px solid #aaa;
+}
+
+/* default tab styles */
+.tabactive {
+ background: #fff !important;
+}
+
+ul#topmenu2 a {
+ display: block;
+ margin: 7px 6px 7px;
+ margin-<?php echo $left; ?>: 0;
+ padding: 4px 10px;
+ white-space: nowrap;
+ border: 1px solid #ddd;
+ border-radius: 20px;
+ -moz-border-radius: 20px;
+ -webkit-border-radius: 20px;
+ background: #f2f2f2;
+
+}
+
+fieldset.caution a {
+ color: #FF0000;
+}
+fieldset.caution a:hover {
+ color: #fff;
+ background-color: #FF0000;
+}
+
+#topmenu {
+ margin-top: .5em;
+ padding: .1em .3em;
+}
+
+ul#topmenu ul {
+ -moz-box-shadow: <?php echo $GLOBALS['text_dir'] === 'rtl' ? '-' : ''; ?>1px 1px 6px #ddd;
+ -webkit-box-shadow: <?php echo $GLOBALS['text_dir'] === 'rtl' ? '-' : ''; ?>2px 2px 3px #666;
+ box-shadow: <?php echo $GLOBALS['text_dir'] === 'rtl' ? '-' : ''; ?>2px 2px 3px #666;
+}
+
+ul#topmenu ul.only {
+ <?php echo $left; ?>: 0;
+}
+
+ul#topmenu > li {
+ border-right: 1px solid #fff;
+ border-left: 1px solid #ccc;
+}
+
+/* default tab styles */
+ul#topmenu a,
+ul#topmenu span {
+ padding: .6em;
+}
+
+ul#topmenu ul a {
+ border-width: 1pt 0 0 0;
+ -moz-border-radius: 0;
+ -webkit-border-radius: 0;
+ border-radius: 0;
+}
+
+ul#topmenu ul li:first-child a {
+ border-width: 0;
+}
+
+/* enabled hover/active tabs */
+ul#topmenu > li > a:hover,
+ul#topmenu > li > .tabactive {
+ text-decoration: none;
+}
+
+ul#topmenu ul a:hover,
+ul#topmenu ul .tabactive {
+ text-decoration: none;
+}
+
+ul#topmenu a.tab:hover,
+ul#topmenu .tabactive {
+ /* background-color: <?php echo $GLOBALS['cfg']['MainBackground']; ?>; */
+}
+
+ul#topmenu2 a.tab:hover,
+ul#topmenu2 a.tabactive {
+ background-color: <?php echo $GLOBALS['cfg']['BgOne']; ?>;
+ border-radius: .3em;
+ -moz-border-radius: .3em;
+ -webkit-border-radius: .3em;
+ text-decoration: none;
+}
+
+/* to be able to cancel the bottom border, use <li class="active"> */
+ul#topmenu > li.active {
+ /* border-bottom: 0pt solid <?php echo $GLOBALS['cfg']['MainBackground']; ?>; */
+ border-right: 0;
+}
+/* end topmenu */
+
+/* zoom search */
+div#dataDisplay input,
+div#dataDisplay select {
+ margin: 0;
+ margin-<?php echo $right; ?>: .5em;
+}
+div#dataDisplay th {
+ line-height: 2em;
+}
+
+/* Calendar */
+table.calendar {
+ width: 100%;
+}
+table.calendar td {
+ text-align: center;
+}
+table.calendar td a {
+ display: block;
+}
+
+table.calendar td a:hover {
+ background-color: #CCFFCC;
+}
+
+table.calendar th {
+ background-color: #D3DCE3;
+}
+
+table.calendar td.selected {
+ background-color: #FFCC99;
+}
+
+img.calendar {
+ border: none;
+}
+form.clock {
+ text-align: center;
+}
+/* end Calendar */
+
+
+/* table stats */
+div#tablestatistics table {
+ float: <?php echo $left; ?>;
+ margin-bottom: .5em;
+ margin-<?php echo $right; ?>: 1.5em;
+ margin-top: .5em;
+ min-width: 16em;
+}
+
+/* END table stats */
+
+
+/* server privileges */
+#tableuserrights td,
+#tablespecificuserrights td,
+#tabledatabases td {
+ vertical-align: middle;
+}
+/* END server privileges */
+
+
+/* Heading */
+#topmenucontainer {
+ padding-<?php echo $right; ?>: 1em;
+ width: 100%;
+}
+
+#serverinfo {
+ border-bottom: 1px solid #fff;
+ background: #888;
+ padding: .3em .9em;
+ padding-<?php echo $left; ?>: 2.2em;
+ text-shadow: 0 1px 0 #000;
+ width: 10000px;
+ overflow: hidden;
+}
+
+#serverinfo .item {
+ white-space: nowrap;
+ color: #fff;
+}
+
+#goto_pagetop {
+ position: fixed;
+ padding: .25em .25em .2em;
+ top: 0;
+ <?php echo $right; ?>: 0;
+ z-index: 900;
+ background: #888;
+}
+
+#span_table_comment {
+ font-weight: bold;
+ font-style: italic;
+ white-space: nowrap;
+ margin-left: 10px;
+ color: #D6D6D6;
+ text-shadow: none;
+}
+
+#serverinfo img {
+ margin: 0 .1em 0;
+ margin-<?php echo $left; ?>: .2em;
+}
+
+
+#textSQLDUMP {
+ width: 95%;
+ height: 95%;
+ font-family: Consolas, "Courier New", Courier, mono;
+ font-size: 110%;
+}
+
+#TooltipContainer {
+ position: absolute;
+ z-index: 99;
+ width: 20em;
+ height: auto;
+ overflow: visible;
+ visibility: hidden;
+ background-color: #ffffcc;
+ color: #006600;
+ border: .1em solid #000;
+ padding: .5em;
+}
+
+/* user privileges */
+#fieldset_add_user_login div.item {
+ border-bottom: 1px solid silver;
+ padding-bottom: .3em;
+ margin-bottom: .3em;
+}
+
+#fieldset_add_user_login label {
+ float: <?php echo $left; ?>;
+ display: block;
+ width: 10em;
+ max-width: 100%;
+ text-align: <?php echo $right; ?>;
+ padding-<?php echo $right; ?>: .5em;
+}
+
+#fieldset_add_user_login span.options #select_pred_username,
+#fieldset_add_user_login span.options #select_pred_hostname,
+#fieldset_add_user_login span.options #select_pred_password {
+ width: 100%;
+ max-width: 100%;
+}
+
+#fieldset_add_user_login span.options {
+ float: <?php echo $left; ?>;
+ display: block;
+ width: 12em;
+ max-width: 100%;
+ padding-<?php echo $right; ?>: .5em;
+}
+
+#fieldset_add_user_login input {
+ width: 12em;
+ clear: <?php echo $right; ?>;
+ max-width: 100%;
+}
+
+#fieldset_add_user_login span.options input {
+ width: auto;
+}
+
+#fieldset_user_priv div.item {
+ float: <?php echo $left; ?>;
+ width: 9em;
+ max-width: 100%;
+}
+
+#fieldset_user_priv div.item div.item {
+ float: none;
+}
+
+#fieldset_user_priv div.item label {
+ white-space: nowrap;
+}
+
+#fieldset_user_priv div.item select {
+ width: 100%;
+}
+
+#fieldset_user_global_rights fieldset {
+ float: <?php echo $left; ?>;
+}
+
+#fieldset_user_group_rights fieldset {
+ float: <?php echo $left; ?>;
+}
+
+#fieldset_user_global_rights legend input {
+ margin-<?php echo $left; ?>: 2em;
+}
+/* END user privileges */
+
+
+/* serverstatus */
+
+.linkElem:hover {
+ text-decoration: underline;
+ color: #235a81;
+ cursor: pointer;
+}
+
+h3#serverstatusqueries span {
+ font-size: 60%;
+ display: inline;
+}
+
+img.sortableIcon {
+ float: <?php echo $right; ?>;
+ background-repeat: no-repeat;
+ margin: 0;
+}
+
+.buttonlinks {
+ float: <?php echo $right; ?>;
+ white-space: nowrap;
+}
+
+/* Also used for the variables page */
+fieldset#tableFilter {
+ margin-bottom: 1em;
+}
+
+div#serverStatusTabs {
+ margin-top: 1em;
+}
+
+caption a.top {
+ float: <?php echo $right; ?>;
+}
+
+div#serverstatusquerieschart {
+ float: <?php echo $left; ?>;
+ width: 500px;
+ height: 350px;
+ padding-<?php echo $left; ?>: 30px;
+}
+
+table#serverstatusqueriesdetails,
+table#serverstatustraffic {
+ float: <?php echo $left; ?>;
+}
+
+table#serverstatusqueriesdetails th {
+ min-width: 35px;
+}
+
+table#serverstatusvariables {
+ width: 100%;
+ margin-bottom: 1em;
+}
+table#serverstatusvariables .name {
+ width: 18em;
+ white-space: nowrap;
+}
+table#serverstatusvariables .value {
+ width: 6em;
+}
+table#serverstatusconnections {
+ float: <?php echo $left; ?>;
+ margin-<?php echo $left; ?>: 30px;
+}
+
+div#serverstatus table tbody td.descr a,
+div#serverstatus table .tblFooters a {
+ white-space: nowrap;
+}
+
+div.liveChart {
+ clear: both;
+ min-width: 500px;
+ height: 400px;
+ padding-bottom: 80px;
+}
+
+#addChartDialog input[type="text"] {
+ margin: 0;
+ padding: 3px;
+}
+
+div#chartVariableSettings {
+ border: 1px solid #ddd;
+ background-color: #E6E6E6;
+ margin-left: 10px;
+}
+
+table#chartGrid div.monitorChart {
+ background: #EBEBEB;
+ width: 400px;
+ height: 300px;
+}
+
+div#serverstatus div.tabLinks {
+ float: <?php echo $left; ?>;
+ padding-bottom: 10px;
+}
+
+.popupContent {
+ display: none;
+ position: absolute;
+ border: 1px solid #CCC;
+ margin: 0;
+ padding: 3px;
+ -moz-box-shadow: <?php echo $GLOBALS['text_dir'] === 'rtl' ? '-' : ''; ?>2px 2px 3px #666;
+ -webkit-box-shadow: <?php echo $GLOBALS['text_dir'] === 'rtl' ? '-' : ''; ?>2px 2px 3px #666;
+ box-shadow: <?php echo $GLOBALS['text_dir'] === 'rtl' ? '-' : ''; ?>2px 2px 3px #666;
+ background-color: #fff;
+ z-index: 2;
+}
+
+div#logTable {
+ padding-top: 10px;
+ clear: both;
+}
+
+div#logTable table {
+ width: 100%;
+}
+
+div#queryAnalyzerDialog {
+ min-width: 700px;
+}
+
+div#queryAnalyzerDialog div.CodeMirror-scroll {
+ height: auto;
+}
+
+div#queryAnalyzerDialog div#queryProfiling {
+ height: 300px;
+}
+
+div#queryAnalyzerDialog td.explain {
+ width: 250px;
+}
+
+div#queryAnalyzerDialog table.queryNums {
+ display: none;
+ border: 0;
+ text-align: left;
+}
+
+.smallIndent {
+ padding-<?php echo $left; ?>: 7px;
+}
+
+/* end serverstatus */
+
+/* server variables */
+#serverVariables {
+ min-width: 30em;
+}
+#serverVariables .var-row > div {
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ line-height: 2em;
+}
+#serverVariables .var-header {
+ color: <?php echo $GLOBALS['cfg']['ThColor']; ?>;
+ background: #f3f3f3;
+ <?php echo $_SESSION['PMA_Theme']->getCssGradient('ffffff', 'cccccc'); ?>
+ font-weight: bold;
+}
+#serverVariables .var-header .var-value {
+ text-align: <?php echo $left; ?>;
+}
+#serverVariables .var-row {
+ padding: 0.5em;
+ min-height: 18px;
+}
+#serverVariables .var-name {
+ width: 45%;
+ float: <?php echo $left; ?>;
+ font-weight: bold;
+}
+#serverVariables .var-name.session {
+ font-weight: normal;
+ font-style: italic;
+}
+#serverVariables .var-value {
+ width: 50%;
+ float: <?php echo $right; ?>;
+ text-align: <?php echo $right; ?>;
+}
+#serverVariables .var-doc {
+ overflow:visible;
+ float: <?php echo $right; ?>;
+}
+
+/* server variables editor */
+#serverVariables .editLink {
+ padding-<?php echo $right; ?>: 1em;
+ float: <?php echo $left; ?>;
+ font-family: sans-serif;
+}
+#serverVariables .serverVariableEditor {
+ width: 100%;
+ overflow: hidden;
+}
+#serverVariables .serverVariableEditor input {
+ width: 100%;
+ margin: 0 0.5em;
+ box-sizing: border-box;
+ -ms-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ -webkit-box-sizing: border-box;
+ height: 2.2em;
+}
+#serverVariables .serverVariableEditor div {
+ display: block;
+ overflow: hidden;
+ padding-<?php echo $right; ?>: 1em;
+}
+#serverVariables .serverVariableEditor a {
+ float: <?php echo $right; ?>;
+ margin: 0 0.5em;
+ line-height: 2em;
+}
+/* end server variables */
+
+
+p.notice {
+ margin: 1.5em 0;
+ border: 1px solid #000;
+ background-repeat: no-repeat;
+ <?php if ($GLOBALS['text_dir'] === 'ltr') { ?>
+ background-position: 10px 50%;
+ padding: 10px 10px 10px 25px;
+ <?php } else { ?>
+ background-position: 99% 50%;
+ padding: 25px 10px 10px 10px
+ <?php } ?>
+ -moz-border-radius: 5px;
+ -webkit-border-radius: 5px;
+ border-radius: 5px;
+ -moz-box-shadow: 0 1px 2px #fff inset;
+ -webkit-box-shadow: 0 1px 2px #fff inset;
+ box-shadow: 0 1px 2px #fff inset;
+ background: #555;
+ color: #d4fb6a;
+}
+
+p.notice a {
+ color: #fff;
+ text-decoration: underline;
+}
+
+/* querywindow */
+body#bodyquerywindow {
+ margin: 0;
+ padding: 0;
+ background-image: none;
+ background-color: #F5F5F5;
+}
+
+div#querywindowcontainer {
+ margin: 0;
+ padding: 0;
+ width: 100%;
+}
+
+div#querywindowcontainer fieldset {
+ margin-top: 0;
+}
+/* END querywindow */
+
+/* profiling */
+
+div#profilingchart {
+ width: 550px;
+ height: 370px;
+ float: <?php echo $left; ?>;
+}
+
+#profilingchart .jqplot-highlighter-tooltip{
+ top: auto !important;
+ left: 11px;
+ bottom:24px;
+}
+
+#profilesummarytable th.header, #profiletable th.header{
+ cursor: pointer;
+}
+
+#profilesummarytable th.header .sorticon, #profiletable th.header .sorticon{
+ width: 16px;
+ height: 16px;
+ background-repeat: no-repeat;
+ background-position: right center;
+ display: inline-block;
+ vertical-align: middle;
+ float: right;
+}
+
+#profilesummarytable th.headerSortUp .sorticon, #profiletable th.headerSortUp .sorticon{
+ background-image: url(<?php echo $_SESSION['PMA_Theme']->getImgPath('s_desc.png');?>);
+}
+
+#profilesummarytable th.headerSortDown .sorticon, #profiletable th.headerSortDown .sorticon{
+ background-image: url(<?php echo $_SESSION['PMA_Theme']->getImgPath('s_asc.png');?>);
+}
+
+/* END profiling */
+
+/* table charting */
+
+#resizer {
+ border: 1px solid silver;
+}
+#inner-resizer { /* make room for the resize handle */
+ padding: 10px;
+}
+
+/* END table charting */
+
+/* querybox */
+
+#togglequerybox {
+ margin: 0 10px;
+}
+
+#serverstatus h3
+{
+ margin: 15px 0;
+ font-weight: normal;
+ color: #999;
+ font-size: 1.7em;
+}
+#sectionlinks {
+ padding: 16px;
+ background: #f3f3f3;
+ border: 1px solid #aaa;
+ border-radius: 5px;
+ -webkit-border-radius: 5px;
+ -moz-border-radius: 5px;
+ box-shadow: 0 1px 1px #fff inset;
+ -webkit-box-shadow: 0 1px 1px #fff inset;
+ -moz-box-shadow: 0 1px 1px #fff inset;
+}
+#sectionlinks a,
+.buttonlinks a,
+a.button {
+ font-size: .88em;
+ font-weight: bold;
+ text-shadow: 0 1px 0 #fff;
+ line-height: 35px;
+ margin-<?php echo $left; ?>: 7px;
+ border: 1px solid #aaa;
+ padding: 5px 10px;
+ color: #111;
+ text-decoration: none;
+ background: #ddd;
+ white-space: nowrap;
+ border-radius: 20px;
+ -webkit-border-radius: 20px;
+ -moz-border-radius: 20px;
+ box-shadow: <?php echo $GLOBALS['text_dir'] === 'rtl' ? '-' : ''; ?>1px 1px 2px rgba(0,0,0,.5);
+ /*
+ -webkit-box-shadow: <?php echo $GLOBALS['text_dir'] === 'rtl' ? '-' : ''; ?>1px 1px 2px rgba(0,0,0,.5);
+ -moz-box-shadow: <?php echo $GLOBALS['text_dir'] === 'rtl' ? '-' : ''; ?>1px 1px 2px rgba(0,0,0,.5);
+ text-shadow: #fff 0 1px 0;
+ */
+ <?php echo $_SESSION['PMA_Theme']->getCssGradient('ffffff', 'cccccc'); ?>
+}
+#sectionlinks a:hover,
+.buttonlinks a:hover,
+a.button:hover {
+ <?php echo $_SESSION['PMA_Theme']->getCssGradient('cccccc', 'dddddd'); ?>
+}
+
+div#sqlquerycontainer {
+ float: <?php echo $left; ?>;
+ width: 69%;
+ /* height: 15em; */
+}
+
+div#tablefieldscontainer {
+ float: <?php echo $right; ?>;
+ width: 29%;
+ /* height: 15em; */
+}
+
+div#tablefieldscontainer select {
+ width: 100%;
+ background: #fff;
+ /* height: 12em; */
+}
+
+textarea#sqlquery {
+ width: 100%;
+ /* height: 100%; */
+ -moz-border-radius: 4px;
+ -webkit-border-radius: 4px;
+ border-radius: 4px;
+ border: 1px solid #aaa;
+ padding: 5px;
+ font-family: inherit;
+}
+textarea#sql_query_edit {
+ height: 7em;
+ width: 95%;
+ display: block;
+}
+div#queryboxcontainer div#bookmarkoptions {
+ margin-top: .5em;
+}
+/* end querybox */
+
+/* main page */
+#maincontainer {
+ /* background-image: url(<?php echo $_SESSION['PMA_Theme']->getImgPath('logo_right.png');?>); */
+ /* background-position: <?php echo $right; ?> bottom; */
+ /* background-repeat: no-repeat; */
+}
+
+#mysqlmaininformation,
+#pmamaininformation {
+ float: <?php echo $left; ?>;
+ width: 49%;
+}
+
+#maincontainer ul {
+ list-style-type: disc;
+ vertical-align: middle;
+}
+
+#maincontainer li {
+ margin-bottom: .3em;
+}
+/* END main page */
+
+
+/* iconic view for ul items */
+
+li.no_bullets {
+ list-style-type:none !important;
+ margin-left: -25px !important; //align with other list items which have bullets
+}
+
+/* END iconic view for ul items */
+
+#body_browse_foreigners {
+ background: <?php echo $GLOBALS['cfg']['NaviBackground']; ?>;
+ margin: .5em .5em 0 .5em;
+}
+
+#bodyquerywindow {
+ background: <?php echo $GLOBALS['cfg']['NaviBackground']; ?>;
+}
+
+#bodythemes {
+ width: 500px;
+ margin: auto;
+ text-align: center;
+}
+
+#bodythemes img {
+ border: .1em solid #000;
+}
+
+#bodythemes a:hover img {
+ border: .1em solid red;
+}
+
+#fieldset_select_fields {
+ float: <?php echo $left; ?>;
+}
+
+#selflink {
+ clear: both;
+ display: block;
+ margin-top: 1em;
+ margin-bottom: 1em;
+ width: 98%;
+ margin-<?php echo $left; ?>: 1%;
+ border-top: .1em solid silver;
+ text-align: <?php echo $right; ?>;
+}
+
+#table_innodb_bufferpool_usage,
+#table_innodb_bufferpool_activity {
+ float: <?php echo $left; ?>;
+}
+
+#div_mysql_charset_collations table {
+ float: <?php echo $left; ?>;
+}
+
+.operations_half_width {
+ width: 48%;
+ float: <?php echo $left; ?>;
+}
+.operations_half_width input[type=text],
+.operations_half_width input[type=password],
+.operations_half_width input[type=number],
+.operations_half_width select {
+ width: 95%;
+}
+.operations_half_width input[type=text].halfWidth,
+.operations_half_width input[type=password].halfWidth,
+.operations_half_width input[type=number].halfWidth,
+.operations_half_width select.halfWidth {
+ width: 40%;
+}
+.operations_half_width ul {
+ list-style-type: none;
+ padding: 0;
+}
+.operations_full_width {
+ width: 100%;
+ clear: both;
+}
+
+#qbe_div_table_list {
+ float: <?php echo $left; ?>;
+}
+
+#qbe_div_sql_query {
+ float: <?php echo $left; ?>;
+}
+
+label.desc {
+ width: 30em;
+ float: <?php echo $left; ?>;
+}
+
+label.desc sup {
+ position: absolute;
+}
+
+code.sql,
+div.sqlvalidate {
+ display: block;
+ padding: 1em;
+ margin-top: 0;
+ margin-bottom: 0;
+ max-height: 10em;
+ overflow: auto;
+}
+
+#result_query div.sqlOuter {
+ background: <?php echo $GLOBALS['cfg']['BgOne']; ?>;
+ padding: 1em;
+}
+
+#PMA_slidingMessage code.sql,
+div.sqlvalidate {
+ background: <?php echo $GLOBALS['cfg']['BgOne']; ?>;
+}
+
+#main_pane_left {
+ width: 60%;
+ float: <?php echo $left; ?>;
+ padding-top: 1em;
+}
+
+#main_pane_right {
+ margin-<?php echo $left; ?>: 60%;
+ padding-top: 1em;
+ padding-<?php echo $left; ?>: 1em;
+}
+
+.group {
+
+ border: 1px solid #999;
+ background: #f3f3f3;
+ -moz-border-radius: 4px;
+ -webkit-border-radius: 4px;
+ border-radius: 4px;
+ -moz-box-shadow: <?php echo $GLOBALS['text_dir'] === 'rtl' ? '-' : ''; ?>2px 2px 5px #ccc;
+ -webkit-box-shadow: <?php echo $GLOBALS['text_dir'] === 'rtl' ? '-' : ''; ?>2px 2px 5px #ccc;
+ box-shadow: <?php echo $GLOBALS['text_dir'] === 'rtl' ? '-' : ''; ?>2px 2px 5px #ccc;
+ margin-bottom: 1em;
+ padding-bottom: 1em;
+}
+
+.group h2 {
+ background-color: #bbb;
+ padding: .1em .3em;
+ margin-top: 0;
+ color: #fff;
+ font-size: 1.6em;
+ font-weight: normal;
+ text-shadow: 0 1px 0 #777;
+ -moz-box-shadow: <?php echo $GLOBALS['text_dir'] === 'rtl' ? '-' : ''; ?>1px 1px 15px #999 inset;
+ -webkit-box-shadow: <?php echo $GLOBALS['text_dir'] === 'rtl' ? '-' : ''; ?>1px 1px 15px #999 inset;
+ box-shadow: <?php echo $GLOBALS['text_dir'] === 'rtl' ? '-' : ''; ?>1px 1px 15px #999 inset;
+}
+
+.group-cnt {
+ padding: 0;
+ padding-<?php echo $left; ?>: .5em;
+ display: inline-block;
+ width: 98%;
+}
+
+textarea#partitiondefinition {
+ height: 3em;
+}
+
+
+/* for elements that should be revealed only via js */
+.hide {
+ display: none;
+}
+
+#list_server {
+ list-style-image: none;
+}
+
+/**
+ * Progress bar styles
+ */
+div.upload_progress
+{
+ width: 400px;
+ margin: 3em auto;
+ text-align: center;
+}
+
+div.upload_progress_bar_outer
+{
+ border: 1px solid #000;
+ width: 202px;
+ position: relative;
+ margin: 0 auto 1em;
+ color: <?php echo $GLOBALS['cfg']['MainColor']; ?>;
+}
+
+div.upload_progress_bar_inner
+{
+ background-color: <?php echo $GLOBALS['cfg']['NaviPointerBackground']; ?>;
+ width: 0;
+ height: 12px;
+ margin: 1px;
+ overflow: hidden;
+ color: <?php echo $GLOBALS['cfg']['BrowseMarkerColor']; ?>;
+ position: relative;
+}
+
+div.upload_progress_bar_outer div.percentage
+{
+ position: absolute;
+ top: 0;
+ <?php echo $left; ?>: 0;
+ width: 202px;
+}
+
+div.upload_progress_bar_inner div.percentage
+{
+ top: -1px;
+ <?php echo $left; ?>: -1px;
+}
+
+div#statustext {
+ margin-top: .5em;
+}
+
+table#serverconnection_src_remote,
+table#serverconnection_trg_remote,
+table#serverconnection_src_local,
+table#serverconnection_trg_local {
+ float: <?php echo $left; ?>;
+}
+/**
+ * Validation error message styles
+ */
+input[type=text].invalid_value,
+input[type=password].invalid_value,
+input[type=number].invalid_value,
+input[type=date].invalid_value,
+.invalid_value {
+ background: #FFCCCC;
+}
+
+/**
+ * Ajax notification styling
+ */
+ .ajax_notification {
+ top: 0; /** The notification needs to be shown on the top of the page */
+ position: fixed;
+ margin-top: 0;
+ margin-right: auto;
+ margin-bottom: 0;
+ margin-<?php echo $left; ?>: auto;
+ padding: 5px; /** Keep a little space on the sides of the text */
+ width: 350px;
+
+ z-index: 1100; /** If this is not kept at a high z-index, the jQueryUI modal dialogs (z-index: 1000) might hide this */
+ text-align: center;
+ display: inline;
+ left: 0;
+ right: 0;
+ background-image: url(<?php echo $_SESSION['PMA_Theme']->getImgPath('ajax_clock_small.gif');?>);
+ background-repeat: no-repeat;
+ background-position: 2%;
+ border: 1px solid #e2b709;
+ }
+
+/* additional styles */
+.ajax_notification {
+ margin-top: 200px;
+ background: #ffe57e;
+ border-radius: 5px;
+ -moz-border-radius: 5px;
+ -webkit-border-radius: 5px;
+ box-shadow: 0 5px 90px #888;
+ -moz-box-shadow: 0 5px 90px #888;
+ -webkit-box-shadow: 0 5px 90px #888;
+}
+
+#loading_parent {
+ /** Need this parent to properly center the notification division */
+ position: relative;
+ width: 100%;
+ }
+/**
+ * Export and Import styles
+ */
+
+.exportoptions h3,
+.importoptions h3 {
+ border-bottom: 1px #999 solid;
+ font-size: 110%;
+}
+
+.exportoptions ul,
+.importoptions ul,
+.format_specific_options ul {
+ list-style-type: none;
+ margin-bottom: 15px;
+}
+
+.exportoptions li,
+.importoptions li {
+ margin: 7px;
+}
+.exportoptions label,
+.importoptions label,
+.exportoptions p,
+.importoptions p {
+ margin: 5px;
+ float: none;
+}
+
+#csv_options label.desc,
+#ldi_options label.desc,
+#latex_options label.desc,
+#output label.desc {
+ float: <?php echo $left; ?>;
+ width: 15em;
+}
+
+.exportoptions,
+.importoptions {
+ margin: 20px 30px 30px;
+ margin-<?php echo $left; ?>: 10px;
+}
+
+.exportoptions #buttonGo,
+.importoptions #buttonGo {
+ font-weight: bold;
+ margin-<?php echo $left; ?>: 14px;
+ border: 1px solid #aaa;
+ padding: 5px 12px;
+ color: #111;
+ text-decoration: none;
+ background: #ddd;
+
+ border-radius: 12px;
+ -webkit-border-radius: 12px;
+ -moz-border-radius: 12px;
+
+ text-shadow: 0 1px 0 #fff;
+
+ <?php echo $_SESSION['PMA_Theme']->getCssGradient('ffffff', 'cccccc'); ?>
+ cursor: pointer;
+}
+#buttonGo:hover {
+ <?php echo $_SESSION['PMA_Theme']->getCssGradient('cccccc', 'dddddd'); ?>
+}
+
+.format_specific_options h3 {
+ margin: 10px 0 0;
+ margin-<?php echo $left; ?>: 10px;
+ border: 0;
+}
+
+.format_specific_options {
+ border: 1px solid #999;
+ margin: 7px 0;
+ padding: 3px;
+}
+
+p.desc {
+ margin: 5px;
+}
+
+/**
+ * Export styles only
+ */
+select#db_select,
+select#table_select {
+ width: 400px;
+}
+
+.export_sub_options {
+ margin: 20px 0 0;
+ margin-<?php echo $left; ?>: 30px;
+}
+
+.export_sub_options h4 {
+ border-bottom: 1px #999 solid;
+}
+
+.export_sub_options li.subgroup {
+ display: inline-block;
+ margin-top: 0;
+}
+
+.export_sub_options li {
+ margin-bottom: 0;
+}
+
+#quick_or_custom,
+#output_quick_export {
+ display: none;
+}
+/**
+ * Import styles only
+ */
+
+.importoptions #import_notification {
+ margin: 10px 0;
+ font-style: italic;
+}
+
+input#input_import_file {
+ margin: 5px;
+}
+
+.formelementrow {
+ margin: 5px 0 5px 0;
+}
+
+#popup_background {
+ display: none;
+ position: fixed;
+ _position: absolute; /* hack for IE6 */
+ width: 100%;
+ height: 100%;
+ top: 0;
+ <?php echo $left; ?>: 0;
+ background: #000;
+ z-index: 1000;
+ overflow: hidden;
+}
+
+/**
+ * Table structure styles
+ */
+#fieldsForm ul.table-structure-actions {
+ margin: 0;
+ padding: 0;
+ list-style: none;
+}
+#fieldsForm ul.table-structure-actions li {
+ float: <?php echo $left; ?>;
+ margin-<?php echo $right; ?>: 0.3em; /* same as padding of "table td" */
+}
+#fieldsForm ul.table-structure-actions .submenu li {
+ padding: 0;
+ margin: 0;
+}
+#fieldsForm ul.table-structure-actions .submenu li span {
+ padding: 0.3em;
+ margin: 0.1em;
+}
+/**
+ * Indexes
+ */
+#index_frm .index_info input,
+#index_frm .index_info select {
+ width: 14em;
+ box-sizing: border-box;
+ -ms-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ -webkit-box-sizing: border-box;
+}
+
+#index_frm .index_info div {
+ padding: .2em 0;
+}
+
+#index_frm .index_info .label {
+ float: <?php echo $left; ?>;
+ min-width: 12em;
+}
+
+#index_frm .slider {
+ width: 10em;
+ margin: .6em;
+ float: <?php echo $left; ?>;
+}
+
+#index_frm .add_fields {
+ float: <?php echo $left; ?>;
+}
+
+#index_frm .add_fields input {
+ margin-<?php echo $left; ?>: 1em;
+}
+
+#index_frm input {
+ margin: 0;
+}
+
+#index_frm td {
+ vertical-align: middle;
+}
+
+table#index_columns {
+ width: 100%;
+}
+
+table#index_columns select {
+ width: 100%;
+}
+
+#move_columns_dialog div {
+ padding: 1em;
+}
+
+#move_columns_dialog ul {
+ list-style: none;
+ margin: 0;
+ padding: 0;
+}
+
+#move_columns_dialog li {
+ background: <?php echo $GLOBALS['cfg']['ThBackground']; ?>;
+ border: 1px solid #aaa;
+ color: <?php echo $GLOBALS['cfg']['ThColor']; ?>;
+ font-weight: bold;
+ margin: .4em;
+ padding: .2em;
+ -webkit-border-radius: 2px;
+ -moz-border-radius: 2px;
+ border-radius: 2px;
+}
+
+.margin#change_column_dialog {
+ margin: 0 .5em;
+}
+
+/* config forms */
+.config-form ul.tabs {
+ margin: 1.1em .2em 0;
+ padding: 0 0 .3em 0;
+ list-style: none;
+ font-weight: bold;
+}
+
+.config-form ul.tabs li {
+ float: <?php echo $left; ?>;
+ margin-bottom: -1px;
+}
+
+.config-form ul.tabs li a {
+ display: block;
+ margin: .1em .2em 0;
+ white-space: nowrap;
+ text-decoration: none;
+ border: 1px solid <?php echo $GLOBALS['cfg']['BgTwo']; ?>;
+ border-bottom: 1px solid #aaa;
+}
+
+.config-form ul.tabs li a {
+ padding: 7px 10px;
+ -webkit-border-radius: 5px 5px 0 0;
+ -moz-border-radius: 5px 5px 0 0;
+ border-radius: 5px 5px 0 0;
+ background: #f2f2f2;
+ color: #555;
+ text-shadow: 0 1px 0 #fff;
+}
+
+.config-form ul.tabs li a:hover,
+.config-form ul.tabs li a:active {
+ background: #e5e5e5;
+}
+
+.config-form ul.tabs li.active a {
+ background-color: #fff;
+ margin-top: 1px;
+ color: #000;
+ text-shadow: none;
+ border-color: #aaa;
+ border-bottom: 1px solid #fff;
+}
+
+.config-form fieldset {
+ margin-top: 0;
+ padding: 0;
+ clear: both;
+ -webkit-border-radius: 0;
+ -moz-border-radius: 0;
+ border-radius: 0;
+}
+
+.config-form legend {
+ display: none;
+}
+
+.config-form fieldset p {
+ margin: 0;
+ padding: .5em;
+ background: #fff;
+ border-top: 0;
+}
+
+.config-form fieldset .errors { /* form error list */
+ margin: 0 -2px 1em;
+ padding: .5em 1.5em;
+ background: #FBEAD9;
+ border: 0 #C83838 solid;
+ border-width: 1px 0;
+ list-style: none;
+ font-family: sans-serif;
+ font-size: small;
+}
+
+.config-form fieldset .inline_errors { /* field error list */
+ margin: .3em .3em .3em;
+ margin-<?php echo $left; ?>: 0;
+ padding: 0;
+ list-style: none;
+ color: #9A0000;
+ font-size: small;
+}
+
+.config-form fieldset th {
+ padding: .3em .3em .3em;
+ padding-<?php echo $left; ?>: .5em;
+ text-align: <?php echo $left; ?>;
+ vertical-align: top;
+ width: 40%;
+ background: transparent;
+ filter: none;
+}
+
+.config-form fieldset .doc,
+.config-form fieldset .disabled-notice {
+ margin-<?php echo $left; ?>: 1em;
+}
+
+.config-form fieldset .disabled-notice {
+ font-size: 80%;
+ text-transform: uppercase;
+ color: #E00;
+ cursor: help;
+}
+
+.config-form fieldset td {
+ padding-top: .3em;
+ padding-bottom: .3em;
+ vertical-align: top;
+}
+
+.config-form fieldset th small {
+ display: block;
+ font-weight: normal;
+ font-family: sans-serif;
+ font-size: x-small;
+ color: #444;
+}
+
+.config-form fieldset th,
+.config-form fieldset td {
+ border-top: 1px <?php echo $GLOBALS['cfg']['BgTwo']; ?> solid;
+ border-<?php echo $right; ?>: none;
+}
+
+fieldset .group-header th {
+ background: <?php echo $GLOBALS['cfg']['BgTwo']; ?>;
+}
+
+fieldset .group-header + tr th {
+ padding-top: .6em;
+}
+
+fieldset .group-field-1 th,
+fieldset .group-header-2 th {
+ padding-<?php echo $left; ?>: 1.5em;
+}
+
+fieldset .group-field-2 th,
+fieldset .group-header-3 th {
+ padding-<?php echo $left; ?>: 3em;
+}
+
+fieldset .group-field-3 th {
+ padding-<?php echo $left; ?>: 4.5em;
+}
+
+fieldset .disabled-field th,
+fieldset .disabled-field th small,
+fieldset .disabled-field td {
+ color: #666;
+ background-color: #ddd;
+}
+
+.config-form .lastrow {
+ border-top: 1px #000 solid;
+}
+
+.config-form .lastrow {
+ background: <?php echo $GLOBALS['cfg']['ThBackground']; ?>;
+ padding: .5em;
+ text-align: center;
+}
+
+.config-form .lastrow input {
+ font-weight: bold;
+}
+
+/* form elements */
+
+.config-form span.checkbox {
+ padding: 2px;
+ display: inline-block;
+}
+
+.config-form .custom { /* customized field */
+ background: #FFC;
+}
+
+.config-form span.checkbox.custom {
+ padding: 1px;
+ border: 1px #EDEC90 solid;
+ background: #FFC;
+}
+
+.config-form .field-error {
+ border-color: #A11 !important;
+}
+
+.config-form input[type="text"],
+.config-form input[type="password"],
+.config-form input[type="number"],
+.config-form select,
+.config-form textarea {
+ border: 1px #A7A6AA solid;
+ height: auto;
+}
+
+.config-form input[type="text"]:focus,
+.config-form input[type="password"]:focus,
+.config-form input[type="number"]:focus,
+.config-form select:focus,
+.config-form textarea:focus {
+ border: 1px #6676FF solid;
+ background: #F7FBFF;
+}
+
+.config-form .field-comment-mark {
+ font-family: serif;
+ color: #007;
+ cursor: help;
+ padding: 0 .2em;
+ font-weight: bold;
+ font-style: italic;
+}
+
+.config-form .field-comment-warning {
+ color: #A00;
+}
+
+/* error list */
+.config-form dd {
+ margin-<?php echo $left; ?>: .5em;
+}
+
+.config-form dd:before {
+ content: "\25B8 ";
+}
+
+.click-hide-message {
+ cursor: pointer;
+}
+
+.prefsmanage_opts {
+ margin-<?php echo $left; ?>: 2em;
+}
+
+#prefs_autoload {
+ margin-bottom: .5em;
+}
+
+#placeholder .button {
+ position: absolute;
+ cursor: pointer;
+}
+
+#placeholder div.button {
+ font-size: smaller;
+ color: #999;
+ background-color: #eee;
+ padding: 2px;
+}
+
+.wrapper {
+ float: <?php echo $left; ?>;
+ margin-bottom: 1.5em;
+}
+.toggleButton {
+ position: relative;
+ cursor: pointer;
+ font-size: .8em;
+ text-align: center;
+ line-height: 1.4em;
+ height: 1.55em;
+ overflow: hidden;
+ border-right: .1em solid #888;
+ border-left: .1em solid #888;
+ -webkit-border-radius: .3em;
+ -moz-border-radius: .3em;
+ border-radius: .3em;
+}
+.toggleButton table,
+.toggleButton td,
+.toggleButton img {
+ padding: 0;
+ position: relative;
+}
+.toggleButton .container {
+ position: absolute;
+}
+.toggleButton .toggleOn {
+ color: #fff;
+ padding: 0 1em;
+ text-shadow: 0 0 .2em #000;
+}
+.toggleButton .toggleOff {
+ padding: 0 1em;
+}
+
+.doubleFieldset fieldset {
+ width: 48%;
+ float: <?php echo $left; ?>;
+ padding: 0;
+}
+.doubleFieldset fieldset.left {
+ margin-<?php echo $right; ?>: 1%;
+}
+.doubleFieldset fieldset.right {
+ margin-<?php echo $left; ?>: 1%;
+}
+.doubleFieldset legend {
+ margin-<?php echo $left; ?>: 1.5em;
+}
+.doubleFieldset div.wrap {
+ padding: 1.5em;
+}
+
+#table_columns input[type="text"],
+#table_columns input[type="password"],
+#table_columns input[type="number"],
+#table_columns select {
+ width: 10em;
+ box-sizing: border-box;
+ -ms-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ -webkit-box-sizing: border-box;
+}
+
+#table_columns select {
+ margin: 0 6px;
+}
+
+#placeholder {
+ position: relative;
+ border: 1px solid #aaa;
+ float: <?php echo $right; ?>;
+ overflow: hidden;
+}
+
+.placeholderDrag {
+ cursor: move;
+}
+
+#placeholder .button {
+ position: absolute;
+}
+
+#left_arrow {
+ left: 8px;
+ top: 26px;
+}
+
+#right_arrow {
+ left: 26px;
+ top: 26px;
+}
+
+#up_arrow {
+ left: 17px;
+ top: 8px;
+}
+
+#down_arrow {
+ left: 17px;
+ top: 44px;
+}
+
+#zoom_in {
+ left: 17px;
+ top: 67px;
+}
+
+#zoom_world {
+ left: 17px;
+ top: 85px;
+}
+
+#zoom_out {
+ left: 17px;
+ top: 103px;
+}
+
+.colborder {
+ cursor: col-resize;
+ height: 100%;
+ margin-<?php echo $left; ?>: -6px;
+ position: absolute;
+ width: 5px;
+}
+
+.colborder_active {
+ border-<?php echo $right; ?>: 2px solid #a44;
+}
+
+.pma_table td {
+ position: static;
+}
+
+.pma_table th.draggable span,
+.pma_table tbody td span {
+ display: block;
+ overflow: hidden;
+}
+
+.modal-copy input {
+ display: block;
+ width: 100%;
+ margin-top: 1.5em;
+ padding: .3em 0;
+}
+
+.cRsz {
+ position: absolute;
+}
+
+.cCpy {
+ background: #333;
+ color: #FFF;
+ font-weight: bold;
+ margin: .1em;
+ padding: .3em;
+ position: absolute;
+ text-shadow: -1px -1px #000;
+
+ -moz-box-shadow: 0 0 .7em #000;
+ -webkit-box-shadow: 0 0 .7em #000;
+ box-shadow: 0 0 .7em #000;
+ -moz-border-radius: .3em;
+ -webkit-border-radius: .3em;
+ border-radius: .3em;
+}
+
+.cPointer {
+ background: url(<?php echo $_SESSION['PMA_Theme']->getImgPath('col_pointer.png');?>);
+ height: 20px;
+ margin-<?php echo $left; ?>: -5px; /* must be minus half of its width */
+ margin-top: -10px;
+ position: absolute;
+ width: 10px;
+}
+
+.tooltip {
+ background: #333 !important;
+ opacity: .8 !important;
+ border: 1px solid #000 !important;
+ -moz-border-radius: .3em !important;
+ -webkit-border-radius: .3em !important;
+ border-radius: .3em !important;
+ text-shadow: -1px -1px #000 !important;
+ font-size: .8em !important;
+ font-weight: bold !important;
+ padding: 1px 3px !important;
+}
+
+.tooltip * {
+ background: none !important;
+ color: #FFF !important;
+}
+
+.cDrop {
+ left: 0;
+ position: absolute;
+ top: 0;
+}
+
+.coldrop {
+ background: url(<?php echo $_SESSION['PMA_Theme']->getImgPath('col_drop.png');?>);
+ cursor: pointer;
+ height: 16px;
+ margin-<?php echo $left; ?>: .3em;
+ margin-top: .3em;
+ position: absolute;
+ width: 16px;
+}
+
+.coldrop:hover,
+.coldrop-hover {
+ background-color: #999;
+}
+
+.cList {
+ background: #EEE;
+ border: solid 1px #999;
+ position: absolute;
+ -moz-box-shadow: 0 .2em .5em #333;
+ -webkit-box-shadow: 0 .2em .5em #333;
+ box-shadow: 0 .2em .5em #333;
+}
+
+.cList .lDiv div {
+ padding: .2em .5em .2em;
+ padding-<?php echo $left; ?>: .2em;
+}
+
+.cList .lDiv div:hover {
+ background: #DDD;
+ cursor: pointer;
+}
+
+.cList .lDiv div input {
+ cursor: pointer;
+}
+
+.showAllColBtn {
+ border-bottom: solid 1px #999;
+ border-top: solid 1px #999;
+ cursor: pointer;
+ font-size: .9em;
+ font-weight: bold;
+ padding: .35em 1em;
+ text-align: center;
+}
+
+.showAllColBtn:hover {
+ background: #DDD;
+}
+
+.turnOffSelect {
+ -moz-user-select: none;
+ -khtml-user-select: none;
+ -webkit-user-select: none;
+ user-select: none;
+}
+
+#page_content {
+ background-color: white;
+}
+
+.navigation {
+ margin: .8em 0;
+
+ border-radius: 5px;
+ -webkit-border-radius: 5px;
+ -moz-border-radius: 5px;
+
+ <?php echo $_SESSION['PMA_Theme']->getCssGradient('eeeeee', 'cccccc'); ?>
+}
+
+.navigation td {
+ margin: 0;
+ padding: 0;
+ vertical-align: middle;
+ white-space: nowrap;
+}
+
+.navigation_separator {
+ color: #999;
+ display: inline-block;
+ font-size: 1.5em;
+ text-align: center;
+ height: 1.4em;
+ width: 1.2em;
+ text-shadow: 1px 0 #FFF;
+}
+
+.navigation input[type=submit] {
+ background: none;
+ border: 0;
+ filter: none;
+ margin: 0;
+ padding: .8em .5em;
+
+ border-radius: 0;
+ -webkit-border-radius: 0;
+ -moz-border-radius: 0;
+}
+
+.navigation input[type=submit]:hover,
+.navigation input.edit_mode_active {
+ color: #fff;
+ cursor: pointer;
+ text-shadow: none;
+
+ <?php echo $_SESSION['PMA_Theme']->getCssGradient('333333', '555555'); ?>
+}
+
+.navigation select {
+ margin: 0 .8em;
+}
+
+.cEdit {
+ margin: 0;
+ padding: 0;
+ position: absolute;
+}
+
+.cEdit input[type=text] {
+ background: #FFF;
+ height: 100%;
+ margin: 0;
+ padding: 0;
+}
+
+.cEdit .edit_area {
+ background: #FFF;
+ border: 1px solid #999;
+ min-width: 10em;
+ padding: .3em .5em;
+}
+
+.cEdit .edit_area select,
+.cEdit .edit_area textarea {
+ width: 97%;
+}
+
+.cEdit .cell_edit_hint {
+ color: #555;
+ font-size: .8em;
+ margin: .3em .2em;
+}
+
+.cEdit .edit_box {
+ overflow: hidden;
+ padding: 0;
+}
+
+.cEdit .edit_box_posting {
+ background: #FFF url(<?php echo $_SESSION['PMA_Theme']->getImgPath('ajax_clock_small.gif');?>) no-repeat right center;
+ padding-<?php echo $right; ?>: 1.5em;
+}
+
+.cEdit .edit_area_loading {
+ background: #FFF url(<?php echo $_SESSION['PMA_Theme']->getImgPath('ajax_clock_small.gif');?>) no-repeat center;
+ height: 10em;
+}
+
+.cEdit .edit_area_right {
+ position: absolute;
+ right: 0;
+}
+
+.cEdit .goto_link {
+ background: #EEE;
+ color: #555;
+ padding: .2em .3em;
+}
+
+.saving_edited_data {
+ background: url(<?php echo $_SESSION['PMA_Theme']->getImgPath('ajax_clock_small.gif');?>) no-repeat left;
+ padding-<?php echo $left; ?>: 20px;
+}
+
+#relationalTable select {
+ width: 125px;
+ margin-right: 5px;
+}
+
+/* css for timepicker */
+.ui-timepicker-div .ui-widget-header { margin-bottom: 8px; }
+.ui-timepicker-div dl { text-align: <?php echo $left; ?>; }
+.ui-timepicker-div dl dt { height: 25px; margin-bottom: -25px; }
+.ui-timepicker-div dl dd { margin: 0 10px 10px 65px; }
+.ui-timepicker-div td { font-size: 90%; }
+.ui-tpicker-grid-label { background: none; border: none; margin: 0; padding: 0; }
+.ui-timepicker-rtl { direction: rtl; }
+.ui-timepicker-rtl dl { text-align: right; }
+.ui-timepicker-rtl dl dd { margin: 0 65px 10px 10px; }
+
+input.btn {
+ color: #333;
+ background-color: #D0DCE0;
+}
+
+body .ui-widget {
+ font-size: 1em;
+}
+
+.ui-dialog fieldset legend a {
+ color: #235A81;
+}
+
+/* over-riding jqplot-yaxis class */
+.jqplot-yaxis {
+ left:0px !important;
+ min-width:25px;
+ width:auto;
+}
+.jqplot-axis {
+ overflow:hidden;
+}
+
+.report-data {
+ height:13em;
+ overflow:scroll;
+ width:570px;
+ border: solid 1px;
+ background: white;
+ padding: 2px;
+}
+
+.report-description {
+ height:10em;
+ width:570px;
+}
diff --git a/themes/pmahomme/css/enum_editor.css.php b/themes/pmahomme/css/enum_editor.css.php
new file mode 100644
index 0000000000..da9bc07158
--- /dev/null
+++ b/themes/pmahomme/css/enum_editor.css.php
@@ -0,0 +1,80 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * ENUM editor styles for the pmahomme theme
+ *
+ * @package PhpMyAdmin-theme
+ * @subpackage PMAHomme
+ */
+
+// unplanned execution path
+if (! defined('PMA_MINIMUM_COMMON') && ! defined('TESTSUITE')) {
+ exit();
+}
+?>
+
+/**
+ * ENUM/SET editor styles
+ */
+p.enum_notice {
+ margin: 5px 2px;
+ font-size: 80%;
+}
+
+#enum_editor p {
+ margin-top: 0;
+ font-style: italic;
+}
+
+#enum_editor .values,
+#enum_editor .add {
+ width: 100%;
+}
+
+#enum_editor .add td {
+ vertical-align: middle;
+ width: 50%;
+ padding: 0 0 0;
+ padding-<?php echo $left; ?>: 1em;
+}
+
+#enum_editor .values td.drop {
+ width: 1.8em;
+ cursor: pointer;
+ vertical-align: middle;
+}
+
+#enum_editor .values input {
+ margin: .1em 0;
+ padding-<?php echo $right; ?>: 2em;
+ width: 100%;
+}
+
+#enum_editor .values img {
+ width: 1.8em;
+ vertical-align: middle;
+}
+
+#enum_editor input.add_value {
+ margin: 0;
+ margin-<?php echo $right; ?>: 0.4em;
+}
+
+#enum_editor_output textarea {
+ width: 100%;
+ float: <?php echo $right; ?>;
+ margin: 1em 0 0 0;
+}
+
+/**
+ * ENUM/SET editor integration for the routines editor
+ */
+.enum_hint {
+ position: relative;
+}
+
+.enum_hint a {
+ position: absolute;
+ <?php echo $left; ?>: 81%;
+ bottom: .35em;
+}
diff --git a/themes/pmahomme/css/gis.css.php b/themes/pmahomme/css/gis.css.php
new file mode 100644
index 0000000000..66348e344e
--- /dev/null
+++ b/themes/pmahomme/css/gis.css.php
@@ -0,0 +1,65 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * GIS styles for the pmahomme theme
+ *
+ * @package PhpMyAdmin-theme
+ * @subpackage PMAHomme
+ */
+
+// unplanned execution path
+if (! defined('PMA_MINIMUM_COMMON') && ! defined('TESTSUITE')) {
+ exit();
+}
+?>
+
+.gis_table td {
+ vertical-align: middle;
+}
+
+.gis_table select {
+ min-width: 151px;
+ margin: 6px;
+}
+
+.gis_table .button {
+ text-align: <?php echo $right; ?>;
+}
+
+/**
+ * GIS data editor styles
+ */
+a.close_gis_editor {
+ float: <?php echo $right; ?>;
+}
+
+#gis_editor {
+ display: none;
+ position: fixed;
+ _position: absolute; /* hack for IE */
+ z-index: 1001;
+ overflow-y: auto;
+ overflow-x: hidden;
+}
+
+#gis_data {
+ min-height: 230px;
+}
+
+#gis_data_textarea {
+ height: 6em;
+}
+
+#gis_data_editor {
+ background: #D0DCE0;
+ padding: 15px;
+ min-height: 500px;
+}
+
+#gis_data_editor .choice {
+ display: none;
+}
+
+#gis_data_editor input[type="text"] {
+ width: 75px;
+}
diff --git a/themes/pmahomme/css/jqplot.css.php b/themes/pmahomme/css/jqplot.css.php
new file mode 100644
index 0000000000..e167c9fc86
--- /dev/null
+++ b/themes/pmahomme/css/jqplot.css.php
@@ -0,0 +1,277 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Styles for jqplot
+ * for the pmahomme theme
+ *
+ * @package PhpMyAdmin-theme
+ * @subpackage PMAHomme
+ */
+
+// unplanned execution path
+if (! defined('PMA_MINIMUM_COMMON') && ! defined('TESTSUITE')) {
+ exit();
+}
+?>
+
+/* jqPlot */
+
+/*rules for the plot target div. These will be cascaded down to all plot elements according to css rules*/
+.jqplot-target {
+ position: relative;
+ color: #222222;
+ font-family: "Trebuchet MS", Arial, Helvetica, sans-serif;
+ font-size: 1em;
+/* height: 300px;
+ width: 590px;*/
+}
+
+/*rules applied to all axes*/
+.jqplot-axis {
+ font-size: 0.75em;
+}
+
+.jqplot-xaxis {
+ margin-top: 10px;
+}
+
+.jqplot-x2axis {
+ margin-bottom: 10px;
+}
+
+.jqplot-yaxis {
+ margin-<?php echo $right; ?>: 10px;
+}
+
+.jqplot-y2axis, .jqplot-y3axis, .jqplot-y4axis, .jqplot-y5axis, .jqplot-y6axis, .jqplot-y7axis, .jqplot-y8axis, .jqplot-y9axis, .jqplot-yMidAxis {
+ margin-left: 10px;
+ margin-right: 10px;
+}
+
+/*rules applied to all axis tick divs*/
+.jqplot-axis-tick, .jqplot-xaxis-tick, .jqplot-yaxis-tick, .jqplot-x2axis-tick, .jqplot-y2axis-tick, .jqplot-y3axis-tick, .jqplot-y4axis-tick, .jqplot-y5axis-tick, .jqplot-y6axis-tick, .jqplot-y7axis-tick, .jqplot-y8axis-tick, .jqplot-y9axis-tick, .jqplot-yMidAxis-tick {
+ position: absolute;
+ white-space: pre;
+}
+
+
+.jqplot-xaxis-tick {
+ top: 0px;
+ /* initial position untill tick is drawn in proper place */
+ <?php echo $left; ?>: 15px;
+/* padding-top: 10px;*/
+ vertical-align: top;
+}
+
+.jqplot-x2axis-tick {
+ bottom: 0px;
+ /* initial position untill tick is drawn in proper place */
+ <?php echo $left; ?>: 15px;
+/* padding-bottom: 10px;*/
+ vertical-align: bottom;
+}
+
+.jqplot-yaxis-tick {
+ <?php echo $right; ?>: 0px;
+ /* initial position untill tick is drawn in proper place */
+ top: 15px;
+/* padding-right: 10px;*/
+ text-align: <?php echo $right; ?>;
+}
+
+.jqplot-yaxis-tick.jqplot-breakTick {
+ <?php echo $right; ?>: -20px;
+ margin-<?php echo $right; ?>: 0px;
+ padding:1px 5px 1px;
+/* background-color: white;*/
+ z-index: 2;
+ font-size: 1.5em;
+}
+
+.jqplot-y2axis-tick, .jqplot-y3axis-tick, .jqplot-y4axis-tick, .jqplot-y5axis-tick, .jqplot-y6axis-tick, .jqplot-y7axis-tick, .jqplot-y8axis-tick, .jqplot-y9axis-tick {
+ <?php echo $left; ?>: 0px;
+ /* initial position untill tick is drawn in proper place */
+ top: 15px;
+/* padding-left: 10px;*/
+/* padding-right: 15px;*/
+ text-align: <?php echo $left; ?>;
+}
+
+.jqplot-yMidAxis-tick {
+ text-align: center;
+ white-space: nowrap;
+}
+
+.jqplot-xaxis-label {
+ margin-top: 10px;
+ font-size: 11pt;
+ position: absolute;
+}
+
+.jqplot-x2axis-label {
+ margin-bottom: 10px;
+ font-size: 11pt;
+ position: absolute;
+}
+
+.jqplot-yaxis-label {
+ margin-right: 10px;
+/* text-align: center;*/
+ font-size: 11pt;
+ position: absolute;
+}
+
+.jqplot-yMidAxis-label {
+ font-size: 11pt;
+ position: absolute;
+}
+
+.jqplot-y2axis-label, .jqplot-y3axis-label, .jqplot-y4axis-label, .jqplot-y5axis-label, .jqplot-y6axis-label, .jqplot-y7axis-label, .jqplot-y8axis-label, .jqplot-y9axis-label {
+/* text-align: center;*/
+ font-size: 11pt;
+ margin-<?php echo $left; ?>: 10px;
+ position: absolute;
+}
+
+.jqplot-meterGauge-tick {
+ font-size: 0.75em;
+ color: #999999;
+}
+
+.jqplot-meterGauge-label {
+ font-size: 1em;
+ color: #999999;
+}
+
+table.jqplot-table-legend {
+ margin-top: 12px;
+ margin-bottom: 12px;
+ margin-left: 12px;
+ margin-right: 12px;
+}
+
+table.jqplot-table-legend, table.jqplot-cursor-legend {
+ background-color: rgba(255,255,255,0.6);
+ border: 1px solid #cccccc;
+ position: absolute;
+ font-size: 0.75em;
+}
+
+td.jqplot-table-legend {
+ vertical-align: middle;
+}
+
+/*
+These rules could be used instead of assigning
+element styles and relying on js object properties.
+*/
+
+/*
+td.jqplot-table-legend-swatch {
+ padding-top: 0.5em;
+ text-align: center;
+}
+
+tr.jqplot-table-legend:first td.jqplot-table-legend-swatch {
+ padding-top: 0px;
+}
+*/
+
+td.jqplot-seriesToggle:hover, td.jqplot-seriesToggle:active {
+ cursor: pointer;
+}
+
+.jqplot-table-legend .jqplot-series-hidden {
+ text-decoration: line-through;
+}
+
+div.jqplot-table-legend-swatch-outline {
+ border: 1px solid #cccccc;
+ padding: 1px;
+}
+
+div.jqplot-table-legend-swatch {
+ width: 0;
+ height: 0;
+ border-top-width: 5px;
+ border-bottom-width: 5px;
+ border-left-width: 6px;
+ border-right-width: 6px;
+ border-top-style: solid;
+ border-bottom-style: solid;
+ border-left-style: solid;
+ border-right-style: solid;
+}
+
+.jqplot-title {
+ top: 0px;
+ <?php echo $left; ?>: 0px;
+ padding-bottom: 0.5em;
+ font-size: 1.2em;
+}
+
+table.jqplot-cursor-tooltip {
+ border: 1px solid #cccccc;
+ font-size: 0.75em;
+}
+
+
+.jqplot-cursor-tooltip {
+ border: 1px solid #cccccc;
+ font-size: 0.75em;
+ white-space: nowrap;
+ background: rgba(208,208,208,0.5);
+ padding: 1px;
+}
+
+.jqplot-highlighter-tooltip, .jqplot-canvasOverlay-tooltip {
+ border: 1px solid #cccccc;
+ font-size: 0.75em;
+ white-space: nowrap;
+ background: rgba(208,208,208,0.5);
+ padding: 1px;
+}
+
+.jqplot-point-label {
+ font-size: 0.75em;
+ z-index: 2;
+}
+
+td.jqplot-cursor-legend-swatch {
+ vertical-align: middle;
+ text-align: center;
+}
+
+div.jqplot-cursor-legend-swatch {
+ width: 1.2em;
+ height: 0.7em;
+}
+
+.jqplot-error {
+/* Styles added to the plot target container when there is an error go here.*/
+ text-align: center;
+}
+
+.jqplot-error-message {
+/* Styling of the custom error message div goes here.*/
+ position: relative;
+ top: 46%;
+ display: inline-block;
+}
+
+div.jqplot-bubble-label {
+ font-size: 0.8em;
+/* background: rgba(90%, 90%, 90%, 0.15);*/
+ padding-left: 2px;
+ padding-right: 2px;
+ color: rgb(20%, 20%, 20%);
+}
+
+div.jqplot-bubble-label.jqplot-bubble-label-highlight {
+ background: rgba(90%, 90%, 90%, 0.7);
+}
+
+div.jqplot-noData-container {
+ text-align: center;
+ background-color: rgba(96%, 96%, 96%, 0.3);
+}
diff --git a/themes/pmahomme/css/navigation.css.php b/themes/pmahomme/css/navigation.css.php
new file mode 100644
index 0000000000..0217a498e6
--- /dev/null
+++ b/themes/pmahomme/css/navigation.css.php
@@ -0,0 +1,285 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Navigation styles for the pmahomme theme
+ *
+ * @package PhpMyAdmin-theme
+ * @subpackage PMAHomme
+ */
+
+// unplanned execution path
+if (! defined('PMA_MINIMUM_COMMON') && ! defined('TESTSUITE')) {
+ exit();
+}
+?>
+
+/******************************************************************************/
+/* Navigation */
+
+#pma_navigation {
+ width: <?php echo $GLOBALS['cfg']['NaviWidth']; ?>px;
+ overflow: hidden;
+ position: fixed;
+ top: 0;
+ <?php echo $left; ?>: 0;
+ height: 100%;
+ background: url(./themes/pmahomme/img/left_nav_bg.png) repeat-y right 0% <?php echo $GLOBALS['cfg']['NaviBackground']; ?>;
+ color: <?php echo $GLOBALS['cfg']['NaviColor']; ?>;
+ z-index: 800;
+}
+
+#pma_navigation_content {
+ width: 100%;
+ height: 100%;
+ position: absolute;
+ top: 0;
+ <?php echo $left; ?>: 0;
+ z-index: 0;
+}
+
+#pma_navigation ul {
+ margin: 0;
+}
+
+#pma_navigation form {
+ margin: 0;
+ padding: 0;
+ display: inline;
+}
+
+#pma_navigation select#select_server,
+#pma_navigation select#lightm_db {
+ width: 100%;
+}
+
+/******************************************************************************/
+/* specific elements */
+
+#pma_navigation div.pageselector {
+ text-align: center;
+ margin: 0;
+ margin-<?php echo $left; ?>: 0.75em;
+ border-<?php echo $left; ?>: 1px solid #666;
+}
+
+#pma_navigation div#pmalogo {
+ <?php //better echo $GLOBALS['cfg']['logoBGC']; ?>
+}
+
+#pma_navigation #pmalogo,
+#pma_navigation #serverChoice,
+#pma_navigation #leftframelinks,
+#pma_navigation #recentTableList,
+#pma_navigation #databaseList,
+#pma_navigation div.pageselector.dbselector {
+ text-align: center;
+ padding: 5px 10px 0px;
+ border: 0;
+}
+
+#pma_navigation #recentTableList select,
+#pma_navigation #serverChoice select
+ {
+ width: 80%;
+}
+
+#pma_navigation_content > img.throbber {
+ display: none;
+ margin: .3em auto 0;
+}
+
+/* Navigation tree*/
+#pma_navigation_tree {
+ margin: 0;
+ margin-<?php echo $left; ?>: 10px;
+ color: #444;
+ height: 74%;
+ position: relative;
+}
+#pma_navigation_tree_content {
+ width: 100%;
+ overflow: hidden;
+ overflow-y: auto;
+ position: absolute;
+ height: 100%;
+}
+#pma_navigation_tree a {
+ color: <?php echo $GLOBALS['cfg']['NaviColor']; ?>;
+}
+#pma_navigation_tree a:hover {
+ text-decoration: underline;
+}
+#pma_navigation_tree li.activePointer {
+ color: <?php echo $GLOBALS['cfg']['NaviPointerColor']; ?>;
+ background-color: <?php echo $GLOBALS['cfg']['NaviPointerBackground']; ?>;
+}
+#pma_navigation_tree li.selected {
+ color: <?php echo $GLOBALS['cfg']['NaviPointerColor']; ?>;
+ background-color: <?php echo $GLOBALS['cfg']['NaviPointerBackground']; ?>;
+}
+#pma_navigation_tree li .dbItemControls {
+ padding-left: 4px;
+}
+#pma_navigation_tree li .navItemControls {
+ display: none;
+ padding-left: 4px;
+}
+#pma_navigation_tree li.activePointer .navItemControls {
+ display: inline;
+ opacity: 0.5;
+}
+#pma_navigation_tree li.activePointer .navItemControls:hover {
+ display: inline;
+ opacity: 1.0;
+}
+#pma_navigation_tree ul {
+ clear: both;
+ padding: 0;
+ list-style-type: none;
+ margin: 0;
+}
+#pma_navigation_tree ul ul {
+ position: relative;
+}
+#pma_navigation_tree li {
+ white-space: nowrap;
+ clear: both;
+ min-height: 16px;
+}
+#pma_navigation_tree img {
+ margin: 0;
+}
+#pma_navigation_tree div.block {
+ position: relative;
+ width: 1.5em;
+ height: 1.5em;
+ min-width: 16px;
+ min-height: 16px;
+ float: <?php echo $left; ?>;
+}
+#pma_navigation_tree div.block i,
+#pma_navigation_tree div.block b {
+ width: 1.5em;
+ height: 1.5em;
+ min-width: 16px;
+ min-height: 8px;
+ position: absolute;
+ bottom: 0.7em;
+ <?php echo $left; ?>: 0.75em;
+ z-index: 0;
+}
+#pma_navigation_tree div.block i { /* Top and right segments for the tree element connections */
+ display: block;
+ border-<?php echo $left; ?>: 1px solid #666;
+ border-bottom: 1px solid #666;
+}
+#pma_navigation_tree div.block i.first { /* Removes top segment */
+ border-<?php echo $left; ?>: 0;
+}
+#pma_navigation_tree div.block b { /* Bottom segment for the tree element connections */
+ display: block;
+ height: 0.75em;
+ bottom: 0;
+ <?php echo $left; ?>: 0.75em;
+ border-<?php echo $left; ?>: 1px solid #666;
+}
+#pma_navigation_tree div.block a,
+#pma_navigation_tree div.block u {
+ position: absolute;
+ <?php echo $left; ?>: 50%;
+ top: 50%;
+ z-index: 10;
+}
+#pma_navigation_tree div.block img {
+ position: relative;
+ top: -0.6em;
+ <?php echo $left; ?>: 0;
+ margin-<?php echo $left; ?>: -7px;
+}
+#pma_navigation_tree div.throbber img {
+ top: 2px;
+ <?php echo $left; ?>: 2px;
+}
+#pma_navigation_tree li.last > ul {
+ background: none;
+}
+#pma_navigation_tree li > a, #pma_navigation_tree li > i {
+ line-height: 1.5em;
+ height: 1.5em;
+ padding-<?php echo $left; ?>: 0.3em;
+}
+#pma_navigation_tree .list_container {
+ border-<?php echo $left; ?>: 1px solid #666;
+ margin-<?php echo $left; ?>: 0.75em;
+ padding-<?php echo $left; ?>: 0.75em;
+}
+#pma_navigation_tree .last > .list_container {
+ border-<?php echo $left; ?>: 0 solid #666;
+}
+
+/* Fast filter */
+li.fast_filter {
+ padding-<?php echo $left; ?>: 0.75em;
+ margin-<?php echo $left; ?>: 0.75em;
+ padding-<?php echo $right; ?>: 35px;
+ border-<?php echo $left; ?>: 1px solid #666;
+}
+li.fast_filter input {
+ padding-<?php echo $right; ?>: 1.7em;
+ width: 100%;
+}
+li.fast_filter span {
+ position: relative;
+ <?php echo $right; ?>: 1.5em;
+ padding: 0.2em;
+ cursor: pointer;
+ font-weight: bold;
+ color: #800;
+}
+/* IE10+ has its own reset X */
+html.ie li.fast_filter span {
+ display: none;
+}
+html.ie.ie9 li.fast_filter span,
+html.ie.ie8 li.fast_filter span {
+ display: auto;
+}
+html.ie li.fast_filter input {
+ padding-<?php echo $right; ?>: .2em;
+}
+html.ie.ie9 li.fast_filter input,
+html.ie.ie8 li.fast_filter input {
+ padding-<?php echo $right; ?>: 1.7em;
+}
+li.fast_filter.db_fast_filter {
+ border: 0;
+}
+
+/* Resize handler */
+#pma_navigation_resizer {
+ width: 3px;
+ height: 100%;
+ background-color: #aaa;
+ cursor: col-resize;
+ position: fixed;
+ top: 0;
+ <?php echo $left; ?>: 240px;
+ z-index: 801;
+}
+#pma_navigation_collapser {
+ width: 20px;
+ height: 22px;
+ line-height: 22px;
+ background: #eee;
+ color: #555;
+ font-weight: bold;
+ position: fixed;
+ top: 0;
+ <?php echo $left; ?>: <?php echo $GLOBALS['cfg']['NaviWidth']; ?>px;
+ text-align: center;
+ cursor: pointer;
+ z-index: 800;
+ text-shadow: 0px 1px 0px #fff;
+ filter: dropshadow(color=#fff, offx=0, offy=1);
+ border: 1px solid #888;
+}
diff --git a/themes/pmahomme/css/pmd.css.php b/themes/pmahomme/css/pmd.css.php
new file mode 100644
index 0000000000..8b0414404d
--- /dev/null
+++ b/themes/pmahomme/css/pmd.css.php
@@ -0,0 +1,530 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Designer styles for the pmahomme theme
+ *
+ * @package PhpMyAdmin-theme
+ * @subpackage PMAHomme
+ */
+
+// unplanned execution path
+if (! defined('PMA_MINIMUM_COMMON') && ! defined('TESTSUITE')) {
+ exit();
+}
+?>
+
+/* Designer */
+.input_tab {
+ background-color: #A6C7E1;
+ color: #000;
+}
+
+.content_fullscreen {
+ position: relative;
+ overflow: auto;
+}
+
+#canvas_outer {
+ position: relative;
+}
+
+#canvas {
+ background-color: #fff;
+ color: #000;
+}
+
+canvas.pmd {
+ display: inline-block;
+ overflow: hidden;
+ text-align: left;
+}
+
+canvas.pmd * {
+ behavior: url(#default#VML);
+}
+
+.pmd_tab {
+ background-color: #fff;
+ color: #000;
+ border-collapse: collapse;
+ border: 1px solid #aaa;
+ z-index: 1;
+ -moz-user-select: none;
+}
+
+.tab_zag {
+ background-image: url(<?php echo $_SESSION['PMA_Theme']->getImgPath('pmd/Header.png'); ?>);
+ background-repeat: repeat-x;
+ text-align: center;
+ cursor: move;
+ padding: 1px;
+ font-weight: bold;
+}
+
+.tab_zag_2 {
+ background-image: url(<?php echo $_SESSION['PMA_Theme']->getImgPath('pmd/Header_Linked.png'); ?>);
+ background-repeat: repeat-x;
+ text-align: center;
+ cursor: move;
+ padding: 1px;
+ font-weight: bold;
+}
+
+.tab_field {
+ background: #fff;
+ color: #000;
+ cursor: default;
+}
+
+.tab_field_2 {
+ background-color: #CCFFCC;
+ color: #000;
+ background-repeat: repeat-x;
+ cursor: default;
+}
+
+.tab_field_3 {
+ background-color: #FFE6E6; /*#DDEEFF*/
+ color: #000;
+ cursor: default;
+}
+
+#pmd_hint {
+ white-space: nowrap;
+ position: absolute;
+ background-color: #99FF99;
+ color: #000;
+ <?php echo $left; ?>: 200px;
+ top: 50px;
+ z-index: 3;
+ border: #00CC66 solid 1px;
+ display: none;
+}
+
+.scroll_tab {
+ overflow: auto;
+ width: 100%;
+ height: 500px;
+}
+
+.pmd_Tabs {
+ cursor: default;
+ color: #0055bb;
+ white-space: nowrap;
+ text-decoration: none;
+ text-indent: 3px;
+ font-weight: bold;
+ margin-left: 2px;
+ text-align: <?php echo $left; ?>;
+ background-color: #fff;
+ background-image: url(<?php echo $_SESSION['PMA_Theme']->getImgPath('pmd/left_panel_butt.png'); ?>);
+ border: #ccc solid 1px;
+}
+
+.pmd_Tabs2 {
+ cursor: default;
+ color: #0055bb;
+ background: #FFEE99;
+ text-indent: 3px;
+ font-weight: bold;
+ white-space: nowrap;
+ text-decoration: none;
+ border: #9999FF solid 1px;
+ text-align: <?php echo $left; ?>;
+}
+
+.owner {
+ font-weight: normal;
+ color: #888;
+}
+
+.option_tab {
+ padding-left: 2px;
+ padding-right: 2px;
+ width: 5px;
+}
+
+.select_all {
+ vertical-align: top;
+ padding-left: 2px;
+ padding-right: 2px;
+ cursor: default;
+ width: 1px;
+ color: #000;
+ background-image: url(<?php echo $_SESSION['PMA_Theme']->getImgPath('pmd/Header.png'); ?>);
+ background-repeat: repeat-x;
+}
+
+.small_tab {
+ vertical-align: top;
+ background-color: #0064ea;
+ color: #fff;
+ background-image: url(<?php echo $_SESSION['PMA_Theme']->getImgPath('pmd/small_tab.png'); ?>);
+ cursor: default;
+ text-align: center;
+ font-weight: bold;
+ padding-left: 2px;
+ padding-right: 2px;
+ width: 1px;
+ text-decoration: none;
+}
+
+.small_tab2 {
+ vertical-align: top;
+ color: #fff;
+ background-color: #FF9966;
+ cursor: default;
+ padding-left: 2px;
+ padding-right: 2px;
+ text-align: center;
+ font-weight: bold;
+ width: 1px;
+ text-decoration: none;
+}
+
+.small_tab_pref {
+ background-image: url(<?php echo $_SESSION['PMA_Theme']->getImgPath('pmd/Header.png'); ?>);
+ background-repeat: repeat-x;
+ text-align: center;
+ width: 1px;
+}
+
+.small_tab_pref2 {
+ vertical-align: top;
+ color: #fff;
+ background-color: #FF9966;
+ cursor: default;
+ text-align: center;
+ font-weight: bold;
+ width: 1px;
+ text-decoration: none;
+}
+
+.butt {
+ border: #4477aa solid 1px;
+ font-weight: bold;
+ height: 19px;
+ width: 70px;
+ background-color: #fff;
+ color: #000;
+ vertical-align: baseline;
+}
+
+.L_butt2_1 {
+ padding: 1px;
+ text-decoration: none;
+ vertical-align: middle;
+ cursor: default;
+}
+
+.L_butt2_2 {
+ padding: 0;
+ border: #0099CC solid 1px;
+ background: #FFEE99;
+ color: #000;
+ text-decoration: none;
+ vertical-align: middle;
+ cursor: default;
+}
+
+/* ---------------------------------------------------------------------------*/
+.bor {
+ width: 10px;
+ height: 10px;
+}
+
+.frams1 {
+ background: url(<?php echo $_SESSION['PMA_Theme']->getImgPath('pmd/1.png'); ?>) no-repeat right bottom;
+}
+
+.frams2 {
+ background: url(<?php echo $_SESSION['PMA_Theme']->getImgPath('pmd/2.png'); ?>) no-repeat left bottom;
+}
+
+.frams3 {
+ background: url(<?php echo $_SESSION['PMA_Theme']->getImgPath('pmd/3.png'); ?>) no-repeat left top;
+}
+
+.frams4 {
+ background: url(<?php echo $_SESSION['PMA_Theme']->getImgPath('pmd/4.png'); ?>) no-repeat right top;
+}
+
+.frams5 {
+ background: url(<?php echo $_SESSION['PMA_Theme']->getImgPath('pmd/5.png'); ?>) repeat-x center bottom;
+}
+
+.frams6 {
+ background: url(<?php echo $_SESSION['PMA_Theme']->getImgPath('pmd/6.png'); ?>) repeat-y left;
+}
+
+.frams7 {
+ background: url(<?php echo $_SESSION['PMA_Theme']->getImgPath('pmd/7.png'); ?>) repeat-x top;
+}
+
+.frams8 {
+ background: url(<?php echo $_SESSION['PMA_Theme']->getImgPath('pmd/8.png'); ?>) repeat-y right;
+}
+
+#osn_tab {
+ background-color: #fff;
+ color: #000;
+ border: #A9A9A9 solid 1px;
+}
+
+.pmd_header {
+ background-color: #EAEEF0;
+ color: #000;
+ text-align: center;
+ font-weight: bold;
+ margin: 0;
+ padding: 0;
+ background-image: url(<?php echo $_SESSION['PMA_Theme']->getImgPath('pmd/top_panel.png'); ?>);
+ background-position: top;
+ background-repeat: repeat-x;
+ border-right: #999 solid 1px;
+ border-left: #999 solid 1px;
+ height: 28px;
+ z-index: 101;
+ width: 100%;
+ position: fixed;
+}
+
+.pmd_header a {
+ display: block;
+ float: <?php echo $left; ?>;
+ margin: 3px 1px 4px;
+ height: 20px;
+ border: 1px dotted #fff;
+}
+
+.pmd_header .M_bord {
+ display: block;
+ float: <?php echo $left; ?>;
+ margin: 4px;
+ height: 20px;
+ width: 2px;
+}
+
+.pmd_header a.first {
+ margin-right: 1em;
+}
+
+.pmd_header a.last {
+ margin-left: 1em;
+}
+
+a.M_butt_Selected_down_IE,
+a.M_butt_Selected_down {
+ border: 1px solid #C0C0BB;
+ background-color: #99FF99;
+ color: #000;
+}
+
+a.M_butt_Selected_down_IE:hover,
+a.M_butt_Selected_down:hover,
+a.M_butt:hover {
+ border: 1px solid #0099CC;
+ background-color: #FFEE99;
+ color: #000;
+}
+
+#layer_menu {
+ z-index: 100;
+ position: absolute;
+ <?php echo $left; ?>: 0;
+ background-color: #EAEEF0;
+ border: #999 solid 1px;
+}
+
+#layer_upd_relation {
+ position: absolute;
+ <?php echo $left; ?>: 637px;
+ top: 224px;
+ z-index: 100;
+}
+
+#layer_new_relation {
+ position: absolute;
+ <?php echo $left; ?>: 636px;
+ top: 85px;
+ z-index: 100;
+ width: 153px;
+}
+
+#pmd_optionse {
+ position: absolute;
+ <?php echo $left; ?>: 636px;
+ top: 85px;
+ z-index: 100;
+ width: 153px;
+}
+
+#layer_menu_sizer {
+ background-image: url(<?php echo $_SESSION['PMA_Theme']->getImgPath('pmd/resize.png'); ?>);
+ cursor: nw-resize;
+ width: 16px;
+ height: 16px;
+}
+
+.panel {
+ position: fixed;
+ top: 60px;
+ <?php echo $right; ?>: 0;
+ display: none;
+ background: #FFF;
+ border: 1px solid gray;
+ width: 350 px;
+ height: auto;
+ padding: 30px 170px 30px;
+ padding-<?php echo $left; ?>: 30px;
+ color: #FFF;
+ z-index: 102;
+}
+
+a.trigger {
+ position: fixed;
+ text-decoration: none;
+ top: 60px;
+ <?php echo $right; ?>: 0;
+ color: #fff;
+ padding: 10px 40px 10px 15px;
+ background: #333 url(<?php echo $_SESSION['PMA_Theme']->getImgPath('pmd/plus.png'); ?>) 85% 55% no-repeat;
+ border: 1px solid #444;
+ display: block;
+ z-index: 102;
+}
+
+a.trigger:hover {
+ color: #080808;
+ background: #fff696 url(<?php echo $_SESSION['PMA_Theme']->getImgPath('pmd/plus.png'); ?>) 85% 55% no-repeat;
+ border: 1px solid #999;
+}
+
+a.active.trigger {
+ background: #222 url(<?php echo $_SESSION['PMA_Theme']->getImgPath('pmd/minus.png'); ?>) 85% 55% no-repeat;
+ z-index: 999;
+}
+
+a.active.trigger:hover {
+ background: #fff696 url(<?php echo $_SESSION['PMA_Theme']->getImgPath('pmd/minus.png'); ?>) 85% 55% no-repeat;
+}
+
+h2.tiger {
+ background-repeat: repeat-x;
+ padding: 1px;
+ font-weight: bold;
+ padding: 50px 20px 50px;
+ margin: 0 0 5px 0;
+ width: 250px;
+ float: <?php echo $left; ?>;
+ color : #333;
+ text-align: center;
+}
+
+h2.tiger a {
+ background-image: url(<?php echo $_SESSION['PMA_Theme']->getImgPath('pmd/Header.png'); ?>);
+ text-align: center;
+ text-decoration: none;
+ color : #333;
+ display: block;
+}
+
+h2.tiger a:hover {
+ color: #000;
+ background-image: url(<?php echo $_SESSION['PMA_Theme']->getImgPath('pmd/Header_Linked.png'); ?>);
+}
+
+h2.active {
+ background-image: url(<?php echo $_SESSION['PMA_Theme']->getImgPath('pmd/Header.png'); ?>);
+ background-repeat: repeat-x;
+ padding: 1px;
+ background-position: left bottom;
+}
+
+.toggle_container {
+ margin: 0 0 5px;
+ padding: 0;
+ border-top: 1px solid #d6d6d6;
+ background: #FFF;
+ width: 250px;
+ overflow: hidden;
+ font-size: 1.2em;
+ clear: both;
+}
+
+.toggle_container .block {
+ background-color: #DBE4E8;
+ padding: 40px 15px 40px 15px; /*--Padding of Container--*/
+ border:1px solid #999;
+ color: #000;
+}
+
+.history_table {
+ text-align: center;
+ background-color: #9999CC;
+}
+
+.history_table2 {
+ text-align: center;
+ background-color: #DBE4E8;
+}
+
+#filter {
+ display: none;
+ position: absolute;
+ top: 0%;
+ left: 0%;
+ width: 100%;
+ height: 100%;
+ background-color: #CCA;
+ z-index: 10;
+ opacity: .5;
+ filter: alpha(opacity=50);
+}
+
+#box {
+ display: none;
+ position: absolute;
+ top: 20%;
+ <?php echo $left; ?>: 30%;
+ width: 500px;
+ height: 220px;
+ padding: 48px;
+ margin: 0;
+ border: 1px solid #000;
+ background-color: #fff;
+ z-index: 101;
+ overflow: visible;
+}
+
+#boxtitle {
+ position: absolute;
+ float: center;
+ top: 0;
+ <?php echo $left; ?>: 0;
+ width: 593px;
+ height: 20px;
+ padding: 0;
+ padding-top: 4px;
+ margin: 0;
+ border-bottom: 4px solid #3CF;
+ background-color: #D0DCE0;
+ color: black;
+ font-weight: bold;
+ padding-<?php echo $left; ?>: 2px;
+ text-align: <?php echo $left; ?>;
+}
+
+#tblfooter {
+ background-color: #D3DCE3;
+ float: <?php echo $right; ?>;
+ padding-top: 10px;
+ color: black;
+ font-weight: normal;
+}
+
+#foreignkeychk {
+ text-align: <?php echo $left; ?>;
+ position: absolute;
+ cursor: pointer;
+}
diff --git a/themes/pmahomme/css/resizable-menu.css.php b/themes/pmahomme/css/resizable-menu.css.php
new file mode 100644
index 0000000000..21d90dc545
--- /dev/null
+++ b/themes/pmahomme/css/resizable-menu.css.php
@@ -0,0 +1,57 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Styles for the resizable menus
+ *
+ * used by js/jquery/jquery.menuResizer-1.0.js
+ *
+ * @package PhpMyAdmin-theme
+ * @subpackage PMAHomme
+ */
+
+// unplanned execution path
+if (! defined('PMA_MINIMUM_COMMON') && ! defined('TESTSUITE')) {
+ exit();
+}
+?>
+ul.resizable-menu a,
+ul.resizable-menu span {
+ display: block;
+ margin: 0;
+ padding: 0;
+ white-space: nowrap;
+}
+
+ul.resizable-menu .submenu {
+ display: none;
+ position: relative;
+}
+
+ul.resizable-menu .shown {
+ display: inline-block;
+}
+
+ul.resizable-menu ul {
+ margin: 0;
+ padding: 0;
+ position: absolute;
+ list-style-type: none;
+ display: none;
+ border: 1px #ddd solid;
+ z-index: 2;
+ <?php echo $right; ?>: 0;
+}
+
+ul.resizable-menu li:hover {
+ <?php echo $_SESSION['PMA_Theme']->getCssGradient('ffffff', 'e5e5e5'); ?>
+}
+
+ul.resizable-menu li:hover ul,
+ul.resizable-menu .submenuhover ul {
+ display: block;
+ background: #fff;
+}
+
+ul.resizable-menu ul li {
+ width: 100%;
+}
diff --git a/themes/pmahomme/css/rte.css.php b/themes/pmahomme/css/rte.css.php
new file mode 100644
index 0000000000..0db21a805c
--- /dev/null
+++ b/themes/pmahomme/css/rte.css.php
@@ -0,0 +1,43 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Styles for management of Routines, Triggers and Events
+ * for the pmahomme theme
+ *
+ * @package PhpMyAdmin-theme
+ * @subpackage PMAHomme
+ */
+
+// unplanned execution path
+if (! defined('PMA_MINIMUM_COMMON') && ! defined('TESTSUITE')) {
+ exit();
+}
+?>
+
+.rte_table {
+ table-layout: fixed;
+}
+
+.rte_table td {
+ vertical-align: middle;
+ padding: 0.2em;
+}
+
+.rte_table tr td:nth-child(1) {
+ font-weight: bold;
+}
+
+.rte_table input,
+.rte_table select,
+.rte_table textarea {
+ width: 100%;
+ margin: 0;
+ box-sizing: border-box;
+ -ms-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ -webkit-box-sizing: border-box;
+}
+
+.rte_table .routine_params_table {
+ width: 100%;
+}
diff --git a/themes/pmahomme/img/ajax_clock_small.gif b/themes/pmahomme/img/ajax_clock_small.gif
new file mode 100644
index 0000000000..bde4932c85
--- /dev/null
+++ b/themes/pmahomme/img/ajax_clock_small.gif
Binary files differ
diff --git a/themes/pmahomme/img/arrow_ltr.png b/themes/pmahomme/img/arrow_ltr.png
new file mode 100644
index 0000000000..7ff8ed9c40
--- /dev/null
+++ b/themes/pmahomme/img/arrow_ltr.png
Binary files differ
diff --git a/themes/pmahomme/img/arrow_rtl.png b/themes/pmahomme/img/arrow_rtl.png
new file mode 100644
index 0000000000..0192d10364
--- /dev/null
+++ b/themes/pmahomme/img/arrow_rtl.png
Binary files differ
diff --git a/themes/pmahomme/img/asc_order.png b/themes/pmahomme/img/asc_order.png
new file mode 100644
index 0000000000..f8d09ccb0d
--- /dev/null
+++ b/themes/pmahomme/img/asc_order.png
Binary files differ
diff --git a/themes/pmahomme/img/b_bookmark.png b/themes/pmahomme/img/b_bookmark.png
new file mode 100644
index 0000000000..0bc3caf597
--- /dev/null
+++ b/themes/pmahomme/img/b_bookmark.png
Binary files differ
diff --git a/themes/pmahomme/img/b_browse.png b/themes/pmahomme/img/b_browse.png
new file mode 100644
index 0000000000..64dbe2c575
--- /dev/null
+++ b/themes/pmahomme/img/b_browse.png
Binary files differ
diff --git a/themes/pmahomme/img/b_calendar.png b/themes/pmahomme/img/b_calendar.png
new file mode 100644
index 0000000000..23254a6a1e
--- /dev/null
+++ b/themes/pmahomme/img/b_calendar.png
Binary files differ
diff --git a/themes/pmahomme/img/b_chart.png b/themes/pmahomme/img/b_chart.png
new file mode 100644
index 0000000000..741e9b5900
--- /dev/null
+++ b/themes/pmahomme/img/b_chart.png
Binary files differ
diff --git a/themes/pmahomme/img/b_close.png b/themes/pmahomme/img/b_close.png
new file mode 100644
index 0000000000..2c234b6f64
--- /dev/null
+++ b/themes/pmahomme/img/b_close.png
Binary files differ
diff --git a/themes/pmahomme/img/b_column_add.png b/themes/pmahomme/img/b_column_add.png
new file mode 100644
index 0000000000..25dc54637a
--- /dev/null
+++ b/themes/pmahomme/img/b_column_add.png
Binary files differ
diff --git a/themes/pmahomme/img/b_comment.png b/themes/pmahomme/img/b_comment.png
new file mode 100644
index 0000000000..b6c3969392
--- /dev/null
+++ b/themes/pmahomme/img/b_comment.png
Binary files differ
diff --git a/themes/pmahomme/img/b_dbstatistics.png b/themes/pmahomme/img/b_dbstatistics.png
new file mode 100644
index 0000000000..741e9b5900
--- /dev/null
+++ b/themes/pmahomme/img/b_dbstatistics.png
Binary files differ
diff --git a/themes/pmahomme/img/b_deltbl.png b/themes/pmahomme/img/b_deltbl.png
new file mode 100644
index 0000000000..867996bc0d
--- /dev/null
+++ b/themes/pmahomme/img/b_deltbl.png
Binary files differ
diff --git a/themes/pmahomme/img/b_docs.png b/themes/pmahomme/img/b_docs.png
new file mode 100644
index 0000000000..0bf056321f
--- /dev/null
+++ b/themes/pmahomme/img/b_docs.png
Binary files differ
diff --git a/themes/pmahomme/img/b_docsql.png b/themes/pmahomme/img/b_docsql.png
new file mode 100644
index 0000000000..81c8b044e8
--- /dev/null
+++ b/themes/pmahomme/img/b_docsql.png
Binary files differ
diff --git a/themes/pmahomme/img/b_drop.png b/themes/pmahomme/img/b_drop.png
new file mode 100644
index 0000000000..012acf15ff
--- /dev/null
+++ b/themes/pmahomme/img/b_drop.png
Binary files differ
diff --git a/themes/pmahomme/img/b_edit.png b/themes/pmahomme/img/b_edit.png
new file mode 100644
index 0000000000..a6426ae8a3
--- /dev/null
+++ b/themes/pmahomme/img/b_edit.png
Binary files differ
diff --git a/themes/pmahomme/img/b_empty.png b/themes/pmahomme/img/b_empty.png
new file mode 100644
index 0000000000..fd6ec8d7b7
--- /dev/null
+++ b/themes/pmahomme/img/b_empty.png
Binary files differ
diff --git a/themes/pmahomme/img/b_engine.png b/themes/pmahomme/img/b_engine.png
new file mode 100644
index 0000000000..4d5f80d9c8
--- /dev/null
+++ b/themes/pmahomme/img/b_engine.png
Binary files differ
diff --git a/themes/pmahomme/img/b_event_add.png b/themes/pmahomme/img/b_event_add.png
new file mode 100644
index 0000000000..ef594b1098
--- /dev/null
+++ b/themes/pmahomme/img/b_event_add.png
Binary files differ
diff --git a/themes/pmahomme/img/b_events.png b/themes/pmahomme/img/b_events.png
new file mode 100644
index 0000000000..86bcc87f19
--- /dev/null
+++ b/themes/pmahomme/img/b_events.png
Binary files differ
diff --git a/themes/pmahomme/img/b_export.png b/themes/pmahomme/img/b_export.png
new file mode 100644
index 0000000000..be8193ad6c
--- /dev/null
+++ b/themes/pmahomme/img/b_export.png
Binary files differ
diff --git a/themes/pmahomme/img/b_find_replace.png b/themes/pmahomme/img/b_find_replace.png
new file mode 100644
index 0000000000..6eaba80572
--- /dev/null
+++ b/themes/pmahomme/img/b_find_replace.png
Binary files differ
diff --git a/themes/pmahomme/img/b_firstpage.png b/themes/pmahomme/img/b_firstpage.png
new file mode 100644
index 0000000000..e1242e61a4
--- /dev/null
+++ b/themes/pmahomme/img/b_firstpage.png
Binary files differ
diff --git a/themes/pmahomme/img/b_ftext.png b/themes/pmahomme/img/b_ftext.png
new file mode 100644
index 0000000000..f991a4dc15
--- /dev/null
+++ b/themes/pmahomme/img/b_ftext.png
Binary files differ
diff --git a/themes/pmahomme/img/b_globe.gif b/themes/pmahomme/img/b_globe.gif
new file mode 100644
index 0000000000..ef03dcf061
--- /dev/null
+++ b/themes/pmahomme/img/b_globe.gif
Binary files differ
diff --git a/themes/pmahomme/img/b_group.png b/themes/pmahomme/img/b_group.png
new file mode 100644
index 0000000000..4530760bad
--- /dev/null
+++ b/themes/pmahomme/img/b_group.png
Binary files differ
diff --git a/themes/pmahomme/img/b_help.png b/themes/pmahomme/img/b_help.png
new file mode 100644
index 0000000000..730b7f9e51
--- /dev/null
+++ b/themes/pmahomme/img/b_help.png
Binary files differ
diff --git a/themes/pmahomme/img/b_home.png b/themes/pmahomme/img/b_home.png
new file mode 100644
index 0000000000..75966aac8e
--- /dev/null
+++ b/themes/pmahomme/img/b_home.png
Binary files differ
diff --git a/themes/pmahomme/img/b_import.png b/themes/pmahomme/img/b_import.png
new file mode 100644
index 0000000000..7b6e77934b
--- /dev/null
+++ b/themes/pmahomme/img/b_import.png
Binary files differ
diff --git a/themes/pmahomme/img/b_index.png b/themes/pmahomme/img/b_index.png
new file mode 100644
index 0000000000..93bf593cdc
--- /dev/null
+++ b/themes/pmahomme/img/b_index.png
Binary files differ
diff --git a/themes/pmahomme/img/b_index_add.png b/themes/pmahomme/img/b_index_add.png
new file mode 100644
index 0000000000..a703bd6b2b
--- /dev/null
+++ b/themes/pmahomme/img/b_index_add.png
Binary files differ
diff --git a/themes/pmahomme/img/b_info.png b/themes/pmahomme/img/b_info.png
new file mode 100644
index 0000000000..cfd49e5282
--- /dev/null
+++ b/themes/pmahomme/img/b_info.png
Binary files differ
diff --git a/themes/pmahomme/img/b_inline_edit.png b/themes/pmahomme/img/b_inline_edit.png
new file mode 100644
index 0000000000..5afdba1091
--- /dev/null
+++ b/themes/pmahomme/img/b_inline_edit.png
Binary files differ
diff --git a/themes/pmahomme/img/b_insrow.png b/themes/pmahomme/img/b_insrow.png
new file mode 100644
index 0000000000..0532871181
--- /dev/null
+++ b/themes/pmahomme/img/b_insrow.png
Binary files differ
diff --git a/themes/pmahomme/img/b_lastpage.png b/themes/pmahomme/img/b_lastpage.png
new file mode 100644
index 0000000000..03102d9b66
--- /dev/null
+++ b/themes/pmahomme/img/b_lastpage.png
Binary files differ
diff --git a/themes/pmahomme/img/b_minus.png b/themes/pmahomme/img/b_minus.png
new file mode 100644
index 0000000000..c0575ca890
--- /dev/null
+++ b/themes/pmahomme/img/b_minus.png
Binary files differ
diff --git a/themes/pmahomme/img/b_more.png b/themes/pmahomme/img/b_more.png
new file mode 100644
index 0000000000..681f862beb
--- /dev/null
+++ b/themes/pmahomme/img/b_more.png
Binary files differ
diff --git a/themes/pmahomme/img/b_move.png b/themes/pmahomme/img/b_move.png
new file mode 100644
index 0000000000..8fd0f307ae
--- /dev/null
+++ b/themes/pmahomme/img/b_move.png
Binary files differ
diff --git a/themes/pmahomme/img/b_newdb.png b/themes/pmahomme/img/b_newdb.png
new file mode 100644
index 0000000000..22d1fe0c37
--- /dev/null
+++ b/themes/pmahomme/img/b_newdb.png
Binary files differ
diff --git a/themes/pmahomme/img/b_newtbl.png b/themes/pmahomme/img/b_newtbl.png
new file mode 100644
index 0000000000..7402ad88c4
--- /dev/null
+++ b/themes/pmahomme/img/b_newtbl.png
Binary files differ
diff --git a/themes/pmahomme/img/b_nextpage.png b/themes/pmahomme/img/b_nextpage.png
new file mode 100644
index 0000000000..6169d5386e
--- /dev/null
+++ b/themes/pmahomme/img/b_nextpage.png
Binary files differ
diff --git a/themes/pmahomme/img/b_pdfdoc.png b/themes/pmahomme/img/b_pdfdoc.png
new file mode 100644
index 0000000000..f5759ea9c0
--- /dev/null
+++ b/themes/pmahomme/img/b_pdfdoc.png
Binary files differ
diff --git a/themes/pmahomme/img/b_plus.png b/themes/pmahomme/img/b_plus.png
new file mode 100644
index 0000000000..32f9fa23aa
--- /dev/null
+++ b/themes/pmahomme/img/b_plus.png
Binary files differ
diff --git a/themes/pmahomme/img/b_prevpage.png b/themes/pmahomme/img/b_prevpage.png
new file mode 100644
index 0000000000..0a3b85d024
--- /dev/null
+++ b/themes/pmahomme/img/b_prevpage.png
Binary files differ
diff --git a/themes/pmahomme/img/b_primary.png b/themes/pmahomme/img/b_primary.png
new file mode 100644
index 0000000000..d27b04b1e1
--- /dev/null
+++ b/themes/pmahomme/img/b_primary.png
Binary files differ
diff --git a/themes/pmahomme/img/b_print.png b/themes/pmahomme/img/b_print.png
new file mode 100644
index 0000000000..830defcd98
--- /dev/null
+++ b/themes/pmahomme/img/b_print.png
Binary files differ
diff --git a/themes/pmahomme/img/b_props.png b/themes/pmahomme/img/b_props.png
new file mode 100644
index 0000000000..c0895fe6df
--- /dev/null
+++ b/themes/pmahomme/img/b_props.png
Binary files differ
diff --git a/themes/pmahomme/img/b_relations.png b/themes/pmahomme/img/b_relations.png
new file mode 100644
index 0000000000..0ef2521dc2
--- /dev/null
+++ b/themes/pmahomme/img/b_relations.png
Binary files differ
diff --git a/themes/pmahomme/img/b_routine_add.png b/themes/pmahomme/img/b_routine_add.png
new file mode 100644
index 0000000000..2d14442601
--- /dev/null
+++ b/themes/pmahomme/img/b_routine_add.png
Binary files differ
diff --git a/themes/pmahomme/img/b_routines.png b/themes/pmahomme/img/b_routines.png
new file mode 100644
index 0000000000..2cc102d3ee
--- /dev/null
+++ b/themes/pmahomme/img/b_routines.png
Binary files differ
diff --git a/themes/pmahomme/img/b_save.png b/themes/pmahomme/img/b_save.png
new file mode 100644
index 0000000000..4192901a9e
--- /dev/null
+++ b/themes/pmahomme/img/b_save.png
Binary files differ
diff --git a/themes/pmahomme/img/b_sbrowse.png b/themes/pmahomme/img/b_sbrowse.png
new file mode 100644
index 0000000000..64dbe2c575
--- /dev/null
+++ b/themes/pmahomme/img/b_sbrowse.png
Binary files differ
diff --git a/themes/pmahomme/img/b_sdb.png b/themes/pmahomme/img/b_sdb.png
new file mode 100644
index 0000000000..0f57239b68
--- /dev/null
+++ b/themes/pmahomme/img/b_sdb.png
Binary files differ
diff --git a/themes/pmahomme/img/b_search.png b/themes/pmahomme/img/b_search.png
new file mode 100644
index 0000000000..b6ac5b9179
--- /dev/null
+++ b/themes/pmahomme/img/b_search.png
Binary files differ
diff --git a/themes/pmahomme/img/b_selboard.png b/themes/pmahomme/img/b_selboard.png
new file mode 100644
index 0000000000..3caa94971b
--- /dev/null
+++ b/themes/pmahomme/img/b_selboard.png
Binary files differ
diff --git a/themes/pmahomme/img/b_select.png b/themes/pmahomme/img/b_select.png
new file mode 100644
index 0000000000..0fab0e6d03
--- /dev/null
+++ b/themes/pmahomme/img/b_select.png
Binary files differ
diff --git a/themes/pmahomme/img/b_snewtbl.png b/themes/pmahomme/img/b_snewtbl.png
new file mode 100644
index 0000000000..1d8b3ef06d
--- /dev/null
+++ b/themes/pmahomme/img/b_snewtbl.png
Binary files differ
diff --git a/themes/pmahomme/img/b_spatial.png b/themes/pmahomme/img/b_spatial.png
new file mode 100644
index 0000000000..5e6c8c727b
--- /dev/null
+++ b/themes/pmahomme/img/b_spatial.png
Binary files differ
diff --git a/themes/pmahomme/img/b_sql.png b/themes/pmahomme/img/b_sql.png
new file mode 100644
index 0000000000..a4d3f02719
--- /dev/null
+++ b/themes/pmahomme/img/b_sql.png
Binary files differ
diff --git a/themes/pmahomme/img/b_sqldoc.png b/themes/pmahomme/img/b_sqldoc.png
new file mode 100644
index 0000000000..34d943b9a5
--- /dev/null
+++ b/themes/pmahomme/img/b_sqldoc.png
Binary files differ
diff --git a/themes/pmahomme/img/b_sqlhelp.png b/themes/pmahomme/img/b_sqlhelp.png
new file mode 100644
index 0000000000..08a13f21d2
--- /dev/null
+++ b/themes/pmahomme/img/b_sqlhelp.png
Binary files differ
diff --git a/themes/pmahomme/img/b_table_add.png b/themes/pmahomme/img/b_table_add.png
new file mode 100644
index 0000000000..98753b2650
--- /dev/null
+++ b/themes/pmahomme/img/b_table_add.png
Binary files differ
diff --git a/themes/pmahomme/img/b_tblanalyse.png b/themes/pmahomme/img/b_tblanalyse.png
new file mode 100644
index 0000000000..604f1d5f15
--- /dev/null
+++ b/themes/pmahomme/img/b_tblanalyse.png
Binary files differ
diff --git a/themes/pmahomme/img/b_tblexport.png b/themes/pmahomme/img/b_tblexport.png
new file mode 100644
index 0000000000..be8193ad6c
--- /dev/null
+++ b/themes/pmahomme/img/b_tblexport.png
Binary files differ
diff --git a/themes/pmahomme/img/b_tblimport.png b/themes/pmahomme/img/b_tblimport.png
new file mode 100644
index 0000000000..7b6e77934b
--- /dev/null
+++ b/themes/pmahomme/img/b_tblimport.png
Binary files differ
diff --git a/themes/pmahomme/img/b_tblops.png b/themes/pmahomme/img/b_tblops.png
new file mode 100644
index 0000000000..153b267b7e
--- /dev/null
+++ b/themes/pmahomme/img/b_tblops.png
Binary files differ
diff --git a/themes/pmahomme/img/b_tbloptimize.png b/themes/pmahomme/img/b_tbloptimize.png
new file mode 100644
index 0000000000..04e7c3ebd3
--- /dev/null
+++ b/themes/pmahomme/img/b_tbloptimize.png
Binary files differ
diff --git a/themes/pmahomme/img/b_tipp.png b/themes/pmahomme/img/b_tipp.png
new file mode 100644
index 0000000000..13e051716d
--- /dev/null
+++ b/themes/pmahomme/img/b_tipp.png
Binary files differ
diff --git a/themes/pmahomme/img/b_trigger_add.png b/themes/pmahomme/img/b_trigger_add.png
new file mode 100644
index 0000000000..8a754dfef4
--- /dev/null
+++ b/themes/pmahomme/img/b_trigger_add.png
Binary files differ
diff --git a/themes/pmahomme/img/b_triggers.png b/themes/pmahomme/img/b_triggers.png
new file mode 100644
index 0000000000..84a89ef4ce
--- /dev/null
+++ b/themes/pmahomme/img/b_triggers.png
Binary files differ
diff --git a/themes/pmahomme/img/b_undo.png b/themes/pmahomme/img/b_undo.png
new file mode 100644
index 0000000000..498187821c
--- /dev/null
+++ b/themes/pmahomme/img/b_undo.png
Binary files differ
diff --git a/themes/pmahomme/img/b_unique.png b/themes/pmahomme/img/b_unique.png
new file mode 100644
index 0000000000..0ec7d57687
--- /dev/null
+++ b/themes/pmahomme/img/b_unique.png
Binary files differ
diff --git a/themes/pmahomme/img/b_usradd.png b/themes/pmahomme/img/b_usradd.png
new file mode 100644
index 0000000000..50fc6a26d0
--- /dev/null
+++ b/themes/pmahomme/img/b_usradd.png
Binary files differ
diff --git a/themes/pmahomme/img/b_usrcheck.png b/themes/pmahomme/img/b_usrcheck.png
new file mode 100644
index 0000000000..a3edfb1cd1
--- /dev/null
+++ b/themes/pmahomme/img/b_usrcheck.png
Binary files differ
diff --git a/themes/pmahomme/img/b_usrdrop.png b/themes/pmahomme/img/b_usrdrop.png
new file mode 100644
index 0000000000..7d5e699027
--- /dev/null
+++ b/themes/pmahomme/img/b_usrdrop.png
Binary files differ
diff --git a/themes/pmahomme/img/b_usredit.png b/themes/pmahomme/img/b_usredit.png
new file mode 100644
index 0000000000..79393d6a1a
--- /dev/null
+++ b/themes/pmahomme/img/b_usredit.png
Binary files differ
diff --git a/themes/pmahomme/img/b_usrlist.png b/themes/pmahomme/img/b_usrlist.png
new file mode 100644
index 0000000000..2a67259d3c
--- /dev/null
+++ b/themes/pmahomme/img/b_usrlist.png
Binary files differ
diff --git a/themes/pmahomme/img/b_view.png b/themes/pmahomme/img/b_view.png
new file mode 100644
index 0000000000..96f90eb2e0
--- /dev/null
+++ b/themes/pmahomme/img/b_view.png
Binary files differ
diff --git a/themes/pmahomme/img/b_view_add.png b/themes/pmahomme/img/b_view_add.png
new file mode 100644
index 0000000000..9c2cff66d8
--- /dev/null
+++ b/themes/pmahomme/img/b_view_add.png
Binary files differ
diff --git a/themes/pmahomme/img/b_views.png b/themes/pmahomme/img/b_views.png
new file mode 100644
index 0000000000..b1c78e3209
--- /dev/null
+++ b/themes/pmahomme/img/b_views.png
Binary files differ
diff --git a/themes/pmahomme/img/bd_browse.png b/themes/pmahomme/img/bd_browse.png
new file mode 100644
index 0000000000..d21edab185
--- /dev/null
+++ b/themes/pmahomme/img/bd_browse.png
Binary files differ
diff --git a/themes/pmahomme/img/bd_deltbl.png b/themes/pmahomme/img/bd_deltbl.png
new file mode 100644
index 0000000000..e7e4e594c7
--- /dev/null
+++ b/themes/pmahomme/img/bd_deltbl.png
Binary files differ
diff --git a/themes/pmahomme/img/bd_drop.png b/themes/pmahomme/img/bd_drop.png
new file mode 100644
index 0000000000..4b10084964
--- /dev/null
+++ b/themes/pmahomme/img/bd_drop.png
Binary files differ
diff --git a/themes/pmahomme/img/bd_edit.png b/themes/pmahomme/img/bd_edit.png
new file mode 100644
index 0000000000..6478c24a9b
--- /dev/null
+++ b/themes/pmahomme/img/bd_edit.png
Binary files differ
diff --git a/themes/pmahomme/img/bd_empty.png b/themes/pmahomme/img/bd_empty.png
new file mode 100644
index 0000000000..cffb040fc7
--- /dev/null
+++ b/themes/pmahomme/img/bd_empty.png
Binary files differ
diff --git a/themes/pmahomme/img/bd_export.png b/themes/pmahomme/img/bd_export.png
new file mode 100644
index 0000000000..a02db90721
--- /dev/null
+++ b/themes/pmahomme/img/bd_export.png
Binary files differ
diff --git a/themes/pmahomme/img/bd_firstpage.png b/themes/pmahomme/img/bd_firstpage.png
new file mode 100644
index 0000000000..d2943fca44
--- /dev/null
+++ b/themes/pmahomme/img/bd_firstpage.png
Binary files differ
diff --git a/themes/pmahomme/img/bd_ftext.png b/themes/pmahomme/img/bd_ftext.png
new file mode 100644
index 0000000000..a0bdcc6e81
--- /dev/null
+++ b/themes/pmahomme/img/bd_ftext.png
Binary files differ
diff --git a/themes/pmahomme/img/bd_index.png b/themes/pmahomme/img/bd_index.png
new file mode 100644
index 0000000000..25141bca2b
--- /dev/null
+++ b/themes/pmahomme/img/bd_index.png
Binary files differ
diff --git a/themes/pmahomme/img/bd_insrow.png b/themes/pmahomme/img/bd_insrow.png
new file mode 100644
index 0000000000..5162577edb
--- /dev/null
+++ b/themes/pmahomme/img/bd_insrow.png
Binary files differ
diff --git a/themes/pmahomme/img/bd_lastpage.png b/themes/pmahomme/img/bd_lastpage.png
new file mode 100644
index 0000000000..1827ee9851
--- /dev/null
+++ b/themes/pmahomme/img/bd_lastpage.png
Binary files differ
diff --git a/themes/pmahomme/img/bd_nextpage.png b/themes/pmahomme/img/bd_nextpage.png
new file mode 100644
index 0000000000..244cdbd285
--- /dev/null
+++ b/themes/pmahomme/img/bd_nextpage.png
Binary files differ
diff --git a/themes/pmahomme/img/bd_prevpage.png b/themes/pmahomme/img/bd_prevpage.png
new file mode 100644
index 0000000000..e0073cfac6
--- /dev/null
+++ b/themes/pmahomme/img/bd_prevpage.png
Binary files differ
diff --git a/themes/pmahomme/img/bd_primary.png b/themes/pmahomme/img/bd_primary.png
new file mode 100644
index 0000000000..792bfa7929
--- /dev/null
+++ b/themes/pmahomme/img/bd_primary.png
Binary files differ
diff --git a/themes/pmahomme/img/bd_sbrowse.png b/themes/pmahomme/img/bd_sbrowse.png
new file mode 100644
index 0000000000..d21edab185
--- /dev/null
+++ b/themes/pmahomme/img/bd_sbrowse.png
Binary files differ
diff --git a/themes/pmahomme/img/bd_select.png b/themes/pmahomme/img/bd_select.png
new file mode 100644
index 0000000000..412926ab79
--- /dev/null
+++ b/themes/pmahomme/img/bd_select.png
Binary files differ
diff --git a/themes/pmahomme/img/bd_spatial.png b/themes/pmahomme/img/bd_spatial.png
new file mode 100644
index 0000000000..c97472088f
--- /dev/null
+++ b/themes/pmahomme/img/bd_spatial.png
Binary files differ
diff --git a/themes/pmahomme/img/bd_unique.png b/themes/pmahomme/img/bd_unique.png
new file mode 100644
index 0000000000..28227cb223
--- /dev/null
+++ b/themes/pmahomme/img/bd_unique.png
Binary files differ
diff --git a/themes/pmahomme/img/col_drop.png b/themes/pmahomme/img/col_drop.png
new file mode 100644
index 0000000000..681f862beb
--- /dev/null
+++ b/themes/pmahomme/img/col_drop.png
Binary files differ
diff --git a/themes/pmahomme/img/col_pointer.png b/themes/pmahomme/img/col_pointer.png
new file mode 100644
index 0000000000..7147edb545
--- /dev/null
+++ b/themes/pmahomme/img/col_pointer.png
Binary files differ
diff --git a/themes/pmahomme/img/col_pointer_ver.png b/themes/pmahomme/img/col_pointer_ver.png
new file mode 100644
index 0000000000..1cbadec088
--- /dev/null
+++ b/themes/pmahomme/img/col_pointer_ver.png
Binary files differ
diff --git a/themes/pmahomme/img/database.png b/themes/pmahomme/img/database.png
new file mode 100644
index 0000000000..9cc16be994
--- /dev/null
+++ b/themes/pmahomme/img/database.png
Binary files differ
diff --git a/themes/pmahomme/img/east-mini.png b/themes/pmahomme/img/east-mini.png
new file mode 100644
index 0000000000..bee419d6e0
--- /dev/null
+++ b/themes/pmahomme/img/east-mini.png
Binary files differ
diff --git a/themes/pmahomme/img/error.ico b/themes/pmahomme/img/error.ico
new file mode 100644
index 0000000000..8f4d509d65
--- /dev/null
+++ b/themes/pmahomme/img/error.ico
Binary files differ
diff --git a/themes/pmahomme/img/eye.png b/themes/pmahomme/img/eye.png
new file mode 100644
index 0000000000..ed38db23b0
--- /dev/null
+++ b/themes/pmahomme/img/eye.png
Binary files differ
diff --git a/themes/pmahomme/img/eye_grey.png b/themes/pmahomme/img/eye_grey.png
new file mode 100644
index 0000000000..6fcae47405
--- /dev/null
+++ b/themes/pmahomme/img/eye_grey.png
Binary files differ
diff --git a/themes/pmahomme/img/item.png b/themes/pmahomme/img/item.png
new file mode 100644
index 0000000000..5465aaf98b
--- /dev/null
+++ b/themes/pmahomme/img/item.png
Binary files differ
diff --git a/themes/pmahomme/img/left_nav_bg.png b/themes/pmahomme/img/left_nav_bg.png
new file mode 100644
index 0000000000..cb2a7ad0a2
--- /dev/null
+++ b/themes/pmahomme/img/left_nav_bg.png
Binary files differ
diff --git a/themes/pmahomme/img/lightbulb.png b/themes/pmahomme/img/lightbulb.png
new file mode 100644
index 0000000000..d22fde8ba4
--- /dev/null
+++ b/themes/pmahomme/img/lightbulb.png
Binary files differ
diff --git a/themes/pmahomme/img/lightbulb_off.png b/themes/pmahomme/img/lightbulb_off.png
new file mode 100644
index 0000000000..e95b8c5b12
--- /dev/null
+++ b/themes/pmahomme/img/lightbulb_off.png
Binary files differ
diff --git a/themes/pmahomme/img/logo_left.png b/themes/pmahomme/img/logo_left.png
new file mode 100644
index 0000000000..22134fcbf8
--- /dev/null
+++ b/themes/pmahomme/img/logo_left.png
Binary files differ
diff --git a/themes/pmahomme/img/logo_right.png b/themes/pmahomme/img/logo_right.png
new file mode 100644
index 0000000000..d61c6284ca
--- /dev/null
+++ b/themes/pmahomme/img/logo_right.png
Binary files differ
diff --git a/themes/pmahomme/img/more.png b/themes/pmahomme/img/more.png
new file mode 100644
index 0000000000..32aaf61a80
--- /dev/null
+++ b/themes/pmahomme/img/more.png
Binary files differ
diff --git a/themes/pmahomme/img/new_data.png b/themes/pmahomme/img/new_data.png
new file mode 100644
index 0000000000..6f4e1863b1
--- /dev/null
+++ b/themes/pmahomme/img/new_data.png
Binary files differ
diff --git a/themes/pmahomme/img/new_data_hovered.png b/themes/pmahomme/img/new_data_hovered.png
new file mode 100644
index 0000000000..a470dbbc55
--- /dev/null
+++ b/themes/pmahomme/img/new_data_hovered.png
Binary files differ
diff --git a/themes/pmahomme/img/new_data_selected.png b/themes/pmahomme/img/new_data_selected.png
new file mode 100644
index 0000000000..a75abe347d
--- /dev/null
+++ b/themes/pmahomme/img/new_data_selected.png
Binary files differ
diff --git a/themes/pmahomme/img/new_data_selected_hovered.png b/themes/pmahomme/img/new_data_selected_hovered.png
new file mode 100644
index 0000000000..04a2ad8424
--- /dev/null
+++ b/themes/pmahomme/img/new_data_selected_hovered.png
Binary files differ
diff --git a/themes/pmahomme/img/new_struct.png b/themes/pmahomme/img/new_struct.png
new file mode 100644
index 0000000000..6b77c137d3
--- /dev/null
+++ b/themes/pmahomme/img/new_struct.png
Binary files differ
diff --git a/themes/pmahomme/img/new_struct_hovered.png b/themes/pmahomme/img/new_struct_hovered.png
new file mode 100644
index 0000000000..9c353c65b7
--- /dev/null
+++ b/themes/pmahomme/img/new_struct_hovered.png
Binary files differ
diff --git a/themes/pmahomme/img/new_struct_selected.png b/themes/pmahomme/img/new_struct_selected.png
new file mode 100644
index 0000000000..142bf11537
--- /dev/null
+++ b/themes/pmahomme/img/new_struct_selected.png
Binary files differ
diff --git a/themes/pmahomme/img/new_struct_selected_hovered.png b/themes/pmahomme/img/new_struct_selected_hovered.png
new file mode 100644
index 0000000000..9a82bc4223
--- /dev/null
+++ b/themes/pmahomme/img/new_struct_selected_hovered.png
Binary files differ
diff --git a/themes/pmahomme/img/north-mini.png b/themes/pmahomme/img/north-mini.png
new file mode 100644
index 0000000000..8283839c96
--- /dev/null
+++ b/themes/pmahomme/img/north-mini.png
Binary files differ
diff --git a/themes/pmahomme/img/pause.png b/themes/pmahomme/img/pause.png
new file mode 100644
index 0000000000..46a6318d39
--- /dev/null
+++ b/themes/pmahomme/img/pause.png
Binary files differ
diff --git a/themes/pmahomme/img/php_sym.png b/themes/pmahomme/img/php_sym.png
new file mode 100644
index 0000000000..cd5e9b8a2a
--- /dev/null
+++ b/themes/pmahomme/img/php_sym.png
Binary files differ
diff --git a/themes/pmahomme/img/play.png b/themes/pmahomme/img/play.png
new file mode 100644
index 0000000000..6169d5386e
--- /dev/null
+++ b/themes/pmahomme/img/play.png
Binary files differ
diff --git a/themes/pmahomme/img/pma_logo2.png b/themes/pmahomme/img/pma_logo2.png
new file mode 100644
index 0000000000..bc17b98661
--- /dev/null
+++ b/themes/pmahomme/img/pma_logo2.png
Binary files differ
diff --git a/themes/pmahomme/img/pmd/1.png b/themes/pmahomme/img/pmd/1.png
new file mode 100644
index 0000000000..48b9d3f953
--- /dev/null
+++ b/themes/pmahomme/img/pmd/1.png
Binary files differ
diff --git a/themes/pmahomme/img/pmd/2.png b/themes/pmahomme/img/pmd/2.png
new file mode 100644
index 0000000000..7545d86fef
--- /dev/null
+++ b/themes/pmahomme/img/pmd/2.png
Binary files differ
diff --git a/themes/pmahomme/img/pmd/2leftarrow.png b/themes/pmahomme/img/pmd/2leftarrow.png
new file mode 100644
index 0000000000..c3565bcd5a
--- /dev/null
+++ b/themes/pmahomme/img/pmd/2leftarrow.png
Binary files differ
diff --git a/themes/pmahomme/img/pmd/2leftarrow_m.png b/themes/pmahomme/img/pmd/2leftarrow_m.png
new file mode 100644
index 0000000000..6ec8d81cb7
--- /dev/null
+++ b/themes/pmahomme/img/pmd/2leftarrow_m.png
Binary files differ
diff --git a/themes/pmahomme/img/pmd/2rightarrow.png b/themes/pmahomme/img/pmd/2rightarrow.png
new file mode 100644
index 0000000000..0a4e236de2
--- /dev/null
+++ b/themes/pmahomme/img/pmd/2rightarrow.png
Binary files differ
diff --git a/themes/pmahomme/img/pmd/2rightarrow_m.png b/themes/pmahomme/img/pmd/2rightarrow_m.png
new file mode 100644
index 0000000000..e5ff1f0b4d
--- /dev/null
+++ b/themes/pmahomme/img/pmd/2rightarrow_m.png
Binary files differ
diff --git a/themes/pmahomme/img/pmd/3.png b/themes/pmahomme/img/pmd/3.png
new file mode 100644
index 0000000000..46179111a3
--- /dev/null
+++ b/themes/pmahomme/img/pmd/3.png
Binary files differ
diff --git a/themes/pmahomme/img/pmd/4.png b/themes/pmahomme/img/pmd/4.png
new file mode 100644
index 0000000000..9b5345944d
--- /dev/null
+++ b/themes/pmahomme/img/pmd/4.png
Binary files differ
diff --git a/themes/pmahomme/img/pmd/5.png b/themes/pmahomme/img/pmd/5.png
new file mode 100644
index 0000000000..51f536dcb5
--- /dev/null
+++ b/themes/pmahomme/img/pmd/5.png
Binary files differ
diff --git a/themes/pmahomme/img/pmd/6.png b/themes/pmahomme/img/pmd/6.png
new file mode 100644
index 0000000000..ed93cfee52
--- /dev/null
+++ b/themes/pmahomme/img/pmd/6.png
Binary files differ
diff --git a/themes/pmahomme/img/pmd/7.png b/themes/pmahomme/img/pmd/7.png
new file mode 100644
index 0000000000..7c7530f14a
--- /dev/null
+++ b/themes/pmahomme/img/pmd/7.png
Binary files differ
diff --git a/themes/pmahomme/img/pmd/8.png b/themes/pmahomme/img/pmd/8.png
new file mode 100644
index 0000000000..451998d1e9
--- /dev/null
+++ b/themes/pmahomme/img/pmd/8.png
Binary files differ
diff --git a/themes/pmahomme/img/pmd/FieldKey_small.png b/themes/pmahomme/img/pmd/FieldKey_small.png
new file mode 100644
index 0000000000..8a55b161cf
--- /dev/null
+++ b/themes/pmahomme/img/pmd/FieldKey_small.png
Binary files differ
diff --git a/themes/pmahomme/img/pmd/Field_small.png b/themes/pmahomme/img/pmd/Field_small.png
new file mode 100644
index 0000000000..4d80059a38
--- /dev/null
+++ b/themes/pmahomme/img/pmd/Field_small.png
Binary files differ
diff --git a/themes/pmahomme/img/pmd/Field_small_char.png b/themes/pmahomme/img/pmd/Field_small_char.png
new file mode 100644
index 0000000000..dcaa1c61d2
--- /dev/null
+++ b/themes/pmahomme/img/pmd/Field_small_char.png
Binary files differ
diff --git a/themes/pmahomme/img/pmd/Field_small_date.png b/themes/pmahomme/img/pmd/Field_small_date.png
new file mode 100644
index 0000000000..259a8d98a5
--- /dev/null
+++ b/themes/pmahomme/img/pmd/Field_small_date.png
Binary files differ
diff --git a/themes/pmahomme/img/pmd/Field_small_int.png b/themes/pmahomme/img/pmd/Field_small_int.png
new file mode 100644
index 0000000000..70f3f38d1e
--- /dev/null
+++ b/themes/pmahomme/img/pmd/Field_small_int.png
Binary files differ
diff --git a/themes/pmahomme/img/pmd/Header.png b/themes/pmahomme/img/pmd/Header.png
new file mode 100644
index 0000000000..3e3e4e9187
--- /dev/null
+++ b/themes/pmahomme/img/pmd/Header.png
Binary files differ
diff --git a/themes/pmahomme/img/pmd/Header_Linked.png b/themes/pmahomme/img/pmd/Header_Linked.png
new file mode 100644
index 0000000000..cb0a4b374b
--- /dev/null
+++ b/themes/pmahomme/img/pmd/Header_Linked.png
Binary files differ
diff --git a/themes/pmahomme/img/pmd/and_icon.png b/themes/pmahomme/img/pmd/and_icon.png
new file mode 100644
index 0000000000..3767aba9b5
--- /dev/null
+++ b/themes/pmahomme/img/pmd/and_icon.png
Binary files differ
diff --git a/themes/pmahomme/img/pmd/ang_direct.png b/themes/pmahomme/img/pmd/ang_direct.png
new file mode 100644
index 0000000000..3bd28e8124
--- /dev/null
+++ b/themes/pmahomme/img/pmd/ang_direct.png
Binary files differ
diff --git a/themes/pmahomme/img/pmd/bord.png b/themes/pmahomme/img/pmd/bord.png
new file mode 100644
index 0000000000..351b9591b3
--- /dev/null
+++ b/themes/pmahomme/img/pmd/bord.png
Binary files differ
diff --git a/themes/pmahomme/img/pmd/bottom.png b/themes/pmahomme/img/pmd/bottom.png
new file mode 100644
index 0000000000..97abfc9346
--- /dev/null
+++ b/themes/pmahomme/img/pmd/bottom.png
Binary files differ
diff --git a/themes/pmahomme/img/pmd/def.png b/themes/pmahomme/img/pmd/def.png
new file mode 100644
index 0000000000..33d5593070
--- /dev/null
+++ b/themes/pmahomme/img/pmd/def.png
Binary files differ
diff --git a/themes/pmahomme/img/pmd/display_field.png b/themes/pmahomme/img/pmd/display_field.png
new file mode 100644
index 0000000000..a7d7cb4eb6
--- /dev/null
+++ b/themes/pmahomme/img/pmd/display_field.png
Binary files differ
diff --git a/themes/pmahomme/img/pmd/downarrow1.png b/themes/pmahomme/img/pmd/downarrow1.png
new file mode 100644
index 0000000000..80632dd566
--- /dev/null
+++ b/themes/pmahomme/img/pmd/downarrow1.png
Binary files differ
diff --git a/themes/pmahomme/img/pmd/downarrow2.png b/themes/pmahomme/img/pmd/downarrow2.png
new file mode 100644
index 0000000000..2c925e9dcc
--- /dev/null
+++ b/themes/pmahomme/img/pmd/downarrow2.png
Binary files differ
diff --git a/themes/pmahomme/img/pmd/downarrow2_m.png b/themes/pmahomme/img/pmd/downarrow2_m.png
new file mode 100644
index 0000000000..461eeefb85
--- /dev/null
+++ b/themes/pmahomme/img/pmd/downarrow2_m.png
Binary files differ
diff --git a/themes/pmahomme/img/pmd/exec.png b/themes/pmahomme/img/pmd/exec.png
new file mode 100644
index 0000000000..d5eaa767bb
--- /dev/null
+++ b/themes/pmahomme/img/pmd/exec.png
Binary files differ
diff --git a/themes/pmahomme/img/pmd/exec_small.png b/themes/pmahomme/img/pmd/exec_small.png
new file mode 100644
index 0000000000..d5162ea19c
--- /dev/null
+++ b/themes/pmahomme/img/pmd/exec_small.png
Binary files differ
diff --git a/themes/pmahomme/img/pmd/exitFullscreen.png b/themes/pmahomme/img/pmd/exitFullscreen.png
new file mode 100644
index 0000000000..1683d55a09
--- /dev/null
+++ b/themes/pmahomme/img/pmd/exitFullscreen.png
Binary files differ
diff --git a/themes/pmahomme/img/pmd/favicon.ico b/themes/pmahomme/img/pmd/favicon.ico
new file mode 100644
index 0000000000..29c2595b1d
--- /dev/null
+++ b/themes/pmahomme/img/pmd/favicon.ico
Binary files differ
diff --git a/themes/pmahomme/img/pmd/grid.png b/themes/pmahomme/img/pmd/grid.png
new file mode 100644
index 0000000000..6aee6e5bf7
--- /dev/null
+++ b/themes/pmahomme/img/pmd/grid.png
Binary files differ
diff --git a/themes/pmahomme/img/pmd/help.png b/themes/pmahomme/img/pmd/help.png
new file mode 100644
index 0000000000..fe200d8d3f
--- /dev/null
+++ b/themes/pmahomme/img/pmd/help.png
Binary files differ
diff --git a/themes/pmahomme/img/pmd/help_relation.png b/themes/pmahomme/img/pmd/help_relation.png
new file mode 100644
index 0000000000..8856604feb
--- /dev/null
+++ b/themes/pmahomme/img/pmd/help_relation.png
Binary files differ
diff --git a/themes/pmahomme/img/pmd/left_panel_butt.png b/themes/pmahomme/img/pmd/left_panel_butt.png
new file mode 100644
index 0000000000..98ead4e602
--- /dev/null
+++ b/themes/pmahomme/img/pmd/left_panel_butt.png
Binary files differ
diff --git a/themes/pmahomme/img/pmd/left_panel_tab.png b/themes/pmahomme/img/pmd/left_panel_tab.png
new file mode 100644
index 0000000000..733588e337
--- /dev/null
+++ b/themes/pmahomme/img/pmd/left_panel_tab.png
Binary files differ
diff --git a/themes/pmahomme/img/pmd/minus.png b/themes/pmahomme/img/pmd/minus.png
new file mode 100644
index 0000000000..664956f3ce
--- /dev/null
+++ b/themes/pmahomme/img/pmd/minus.png
Binary files differ
diff --git a/themes/pmahomme/img/pmd/or_icon.png b/themes/pmahomme/img/pmd/or_icon.png
new file mode 100644
index 0000000000..5a12061c48
--- /dev/null
+++ b/themes/pmahomme/img/pmd/or_icon.png
Binary files differ
diff --git a/themes/pmahomme/img/pmd/pdf.png b/themes/pmahomme/img/pmd/pdf.png
new file mode 100644
index 0000000000..8c110d30b9
--- /dev/null
+++ b/themes/pmahomme/img/pmd/pdf.png
Binary files differ
diff --git a/themes/pmahomme/img/pmd/plus.png b/themes/pmahomme/img/pmd/plus.png
new file mode 100644
index 0000000000..11ef002395
--- /dev/null
+++ b/themes/pmahomme/img/pmd/plus.png
Binary files differ
diff --git a/themes/pmahomme/img/pmd/query_builder.png b/themes/pmahomme/img/pmd/query_builder.png
new file mode 100644
index 0000000000..13262f92bb
--- /dev/null
+++ b/themes/pmahomme/img/pmd/query_builder.png
Binary files differ
diff --git a/themes/pmahomme/img/pmd/relation.png b/themes/pmahomme/img/pmd/relation.png
new file mode 100644
index 0000000000..ecc49fac5a
--- /dev/null
+++ b/themes/pmahomme/img/pmd/relation.png
Binary files differ
diff --git a/themes/pmahomme/img/pmd/reload.png b/themes/pmahomme/img/pmd/reload.png
new file mode 100644
index 0000000000..63f0b6c92b
--- /dev/null
+++ b/themes/pmahomme/img/pmd/reload.png
Binary files differ
diff --git a/themes/pmahomme/img/pmd/resize.png b/themes/pmahomme/img/pmd/resize.png
new file mode 100644
index 0000000000..3c0492a3f4
--- /dev/null
+++ b/themes/pmahomme/img/pmd/resize.png
Binary files differ
diff --git a/themes/pmahomme/img/pmd/rightarrow1.png b/themes/pmahomme/img/pmd/rightarrow1.png
new file mode 100644
index 0000000000..6d4f63b094
--- /dev/null
+++ b/themes/pmahomme/img/pmd/rightarrow1.png
Binary files differ
diff --git a/themes/pmahomme/img/pmd/rightarrow2.png b/themes/pmahomme/img/pmd/rightarrow2.png
new file mode 100644
index 0000000000..5cecf9eeb3
--- /dev/null
+++ b/themes/pmahomme/img/pmd/rightarrow2.png
Binary files differ
diff --git a/themes/pmahomme/img/pmd/save.png b/themes/pmahomme/img/pmd/save.png
new file mode 100644
index 0000000000..3e65b6ab42
--- /dev/null
+++ b/themes/pmahomme/img/pmd/save.png
Binary files differ
diff --git a/themes/pmahomme/img/pmd/small_tab.png b/themes/pmahomme/img/pmd/small_tab.png
new file mode 100644
index 0000000000..bfa1b5937c
--- /dev/null
+++ b/themes/pmahomme/img/pmd/small_tab.png
Binary files differ
diff --git a/themes/pmahomme/img/pmd/table.png b/themes/pmahomme/img/pmd/table.png
new file mode 100644
index 0000000000..caf214d23f
--- /dev/null
+++ b/themes/pmahomme/img/pmd/table.png
Binary files differ
diff --git a/themes/pmahomme/img/pmd/toggle_lines.png b/themes/pmahomme/img/pmd/toggle_lines.png
new file mode 100644
index 0000000000..9ab3764a60
--- /dev/null
+++ b/themes/pmahomme/img/pmd/toggle_lines.png
Binary files differ
diff --git a/themes/pmahomme/img/pmd/top_panel.png b/themes/pmahomme/img/pmd/top_panel.png
new file mode 100644
index 0000000000..6d8302f977
--- /dev/null
+++ b/themes/pmahomme/img/pmd/top_panel.png
Binary files differ
diff --git a/themes/pmahomme/img/pmd/uparrow2_m.png b/themes/pmahomme/img/pmd/uparrow2_m.png
new file mode 100644
index 0000000000..2c3b9352e6
--- /dev/null
+++ b/themes/pmahomme/img/pmd/uparrow2_m.png
Binary files differ
diff --git a/themes/pmahomme/img/pmd/viewInFullscreen.png b/themes/pmahomme/img/pmd/viewInFullscreen.png
new file mode 100644
index 0000000000..8692df67d1
--- /dev/null
+++ b/themes/pmahomme/img/pmd/viewInFullscreen.png
Binary files differ
diff --git a/themes/pmahomme/img/s_asc.png b/themes/pmahomme/img/s_asc.png
new file mode 100644
index 0000000000..d518fa682f
--- /dev/null
+++ b/themes/pmahomme/img/s_asc.png
Binary files differ
diff --git a/themes/pmahomme/img/s_asci.png b/themes/pmahomme/img/s_asci.png
new file mode 100644
index 0000000000..da50ff9f94
--- /dev/null
+++ b/themes/pmahomme/img/s_asci.png
Binary files differ
diff --git a/themes/pmahomme/img/s_attention.png b/themes/pmahomme/img/s_attention.png
new file mode 100644
index 0000000000..7f2578187c
--- /dev/null
+++ b/themes/pmahomme/img/s_attention.png
Binary files differ
diff --git a/themes/pmahomme/img/s_cancel.png b/themes/pmahomme/img/s_cancel.png
new file mode 100644
index 0000000000..199742658b
--- /dev/null
+++ b/themes/pmahomme/img/s_cancel.png
Binary files differ
diff --git a/themes/pmahomme/img/s_cancel2.png b/themes/pmahomme/img/s_cancel2.png
new file mode 100644
index 0000000000..28c60831a8
--- /dev/null
+++ b/themes/pmahomme/img/s_cancel2.png
Binary files differ
diff --git a/themes/pmahomme/img/s_cog.png b/themes/pmahomme/img/s_cog.png
new file mode 100644
index 0000000000..e90dd936a7
--- /dev/null
+++ b/themes/pmahomme/img/s_cog.png
Binary files differ
diff --git a/themes/pmahomme/img/s_db.png b/themes/pmahomme/img/s_db.png
new file mode 100644
index 0000000000..9cc16be994
--- /dev/null
+++ b/themes/pmahomme/img/s_db.png
Binary files differ
diff --git a/themes/pmahomme/img/s_desc.png b/themes/pmahomme/img/s_desc.png
new file mode 100644
index 0000000000..f8d09ccb0d
--- /dev/null
+++ b/themes/pmahomme/img/s_desc.png
Binary files differ
diff --git a/themes/pmahomme/img/s_error.png b/themes/pmahomme/img/s_error.png
new file mode 100644
index 0000000000..bb615ff3bf
--- /dev/null
+++ b/themes/pmahomme/img/s_error.png
Binary files differ
diff --git a/themes/pmahomme/img/s_error2.png b/themes/pmahomme/img/s_error2.png
new file mode 100644
index 0000000000..e4f02e9a87
--- /dev/null
+++ b/themes/pmahomme/img/s_error2.png
Binary files differ
diff --git a/themes/pmahomme/img/s_fulltext.png b/themes/pmahomme/img/s_fulltext.png
new file mode 100644
index 0000000000..9f8db13f81
--- /dev/null
+++ b/themes/pmahomme/img/s_fulltext.png
Binary files differ
diff --git a/themes/pmahomme/img/s_host.png b/themes/pmahomme/img/s_host.png
new file mode 100644
index 0000000000..f27c337d4d
--- /dev/null
+++ b/themes/pmahomme/img/s_host.png
Binary files differ
diff --git a/themes/pmahomme/img/s_info.png b/themes/pmahomme/img/s_info.png
new file mode 100644
index 0000000000..f636683e3d
--- /dev/null
+++ b/themes/pmahomme/img/s_info.png
Binary files differ
diff --git a/themes/pmahomme/img/s_lang.png b/themes/pmahomme/img/s_lang.png
new file mode 100644
index 0000000000..a62fa502d5
--- /dev/null
+++ b/themes/pmahomme/img/s_lang.png
Binary files differ
diff --git a/themes/pmahomme/img/s_loggoff.png b/themes/pmahomme/img/s_loggoff.png
new file mode 100644
index 0000000000..cc53f167ca
--- /dev/null
+++ b/themes/pmahomme/img/s_loggoff.png
Binary files differ
diff --git a/themes/pmahomme/img/s_notice.png b/themes/pmahomme/img/s_notice.png
new file mode 100644
index 0000000000..7f2578187c
--- /dev/null
+++ b/themes/pmahomme/img/s_notice.png
Binary files differ
diff --git a/themes/pmahomme/img/s_okay.png b/themes/pmahomme/img/s_okay.png
new file mode 100644
index 0000000000..5587dc636a
--- /dev/null
+++ b/themes/pmahomme/img/s_okay.png
Binary files differ
diff --git a/themes/pmahomme/img/s_partialtext.png b/themes/pmahomme/img/s_partialtext.png
new file mode 100644
index 0000000000..e671140169
--- /dev/null
+++ b/themes/pmahomme/img/s_partialtext.png
Binary files differ
diff --git a/themes/pmahomme/img/s_passwd.png b/themes/pmahomme/img/s_passwd.png
new file mode 100644
index 0000000000..82d6f26476
--- /dev/null
+++ b/themes/pmahomme/img/s_passwd.png
Binary files differ
diff --git a/themes/pmahomme/img/s_process.png b/themes/pmahomme/img/s_process.png
new file mode 100644
index 0000000000..e90dd936a7
--- /dev/null
+++ b/themes/pmahomme/img/s_process.png
Binary files differ
diff --git a/themes/pmahomme/img/s_really.png b/themes/pmahomme/img/s_really.png
new file mode 100644
index 0000000000..f9902ef07c
--- /dev/null
+++ b/themes/pmahomme/img/s_really.png
Binary files differ
diff --git a/themes/pmahomme/img/s_reload.png b/themes/pmahomme/img/s_reload.png
new file mode 100644
index 0000000000..345e687509
--- /dev/null
+++ b/themes/pmahomme/img/s_reload.png
Binary files differ
diff --git a/themes/pmahomme/img/s_replication.png b/themes/pmahomme/img/s_replication.png
new file mode 100644
index 0000000000..f51a177938
--- /dev/null
+++ b/themes/pmahomme/img/s_replication.png
Binary files differ
diff --git a/themes/pmahomme/img/s_rights.png b/themes/pmahomme/img/s_rights.png
new file mode 100644
index 0000000000..4fa395ed91
--- /dev/null
+++ b/themes/pmahomme/img/s_rights.png
Binary files differ
diff --git a/themes/pmahomme/img/s_sortable.png b/themes/pmahomme/img/s_sortable.png
new file mode 100644
index 0000000000..e318e67a71
--- /dev/null
+++ b/themes/pmahomme/img/s_sortable.png
Binary files differ
diff --git a/themes/pmahomme/img/s_status.png b/themes/pmahomme/img/s_status.png
new file mode 100644
index 0000000000..31dbb87553
--- /dev/null
+++ b/themes/pmahomme/img/s_status.png
Binary files differ
diff --git a/themes/pmahomme/img/s_success.png b/themes/pmahomme/img/s_success.png
new file mode 100644
index 0000000000..c9a2cfdbcc
--- /dev/null
+++ b/themes/pmahomme/img/s_success.png
Binary files differ
diff --git a/themes/pmahomme/img/s_sync.png b/themes/pmahomme/img/s_sync.png
new file mode 100644
index 0000000000..8545ba1b83
--- /dev/null
+++ b/themes/pmahomme/img/s_sync.png
Binary files differ
diff --git a/themes/pmahomme/img/s_tbl.png b/themes/pmahomme/img/s_tbl.png
new file mode 100644
index 0000000000..437e76d325
--- /dev/null
+++ b/themes/pmahomme/img/s_tbl.png
Binary files differ
diff --git a/themes/pmahomme/img/s_theme.png b/themes/pmahomme/img/s_theme.png
new file mode 100644
index 0000000000..ca6feca2b8
--- /dev/null
+++ b/themes/pmahomme/img/s_theme.png
Binary files differ
diff --git a/themes/pmahomme/img/s_top.png b/themes/pmahomme/img/s_top.png
new file mode 100644
index 0000000000..de05174ee0
--- /dev/null
+++ b/themes/pmahomme/img/s_top.png
Binary files differ
diff --git a/themes/pmahomme/img/s_vars.png b/themes/pmahomme/img/s_vars.png
new file mode 100644
index 0000000000..73f060b8e4
--- /dev/null
+++ b/themes/pmahomme/img/s_vars.png
Binary files differ
diff --git a/themes/pmahomme/img/s_views.png b/themes/pmahomme/img/s_views.png
new file mode 100644
index 0000000000..b1c78e3209
--- /dev/null
+++ b/themes/pmahomme/img/s_views.png
Binary files differ
diff --git a/themes/pmahomme/img/south-mini.png b/themes/pmahomme/img/south-mini.png
new file mode 100644
index 0000000000..954c202ba4
--- /dev/null
+++ b/themes/pmahomme/img/south-mini.png
Binary files differ
diff --git a/themes/pmahomme/img/spacer.png b/themes/pmahomme/img/spacer.png
new file mode 100644
index 0000000000..c6008d7c80
--- /dev/null
+++ b/themes/pmahomme/img/spacer.png
Binary files differ
diff --git a/themes/pmahomme/img/sprites.png b/themes/pmahomme/img/sprites.png
new file mode 100644
index 0000000000..fa94de8096
--- /dev/null
+++ b/themes/pmahomme/img/sprites.png
Binary files differ
diff --git a/themes/pmahomme/img/toggle-ltr.png b/themes/pmahomme/img/toggle-ltr.png
new file mode 100644
index 0000000000..964340c962
--- /dev/null
+++ b/themes/pmahomme/img/toggle-ltr.png
Binary files differ
diff --git a/themes/pmahomme/img/toggle-rtl.png b/themes/pmahomme/img/toggle-rtl.png
new file mode 100644
index 0000000000..c9b23b0b82
--- /dev/null
+++ b/themes/pmahomme/img/toggle-rtl.png
Binary files differ
diff --git a/themes/pmahomme/img/vertical_line.png b/themes/pmahomme/img/vertical_line.png
new file mode 100644
index 0000000000..188417be81
--- /dev/null
+++ b/themes/pmahomme/img/vertical_line.png
Binary files differ
diff --git a/themes/pmahomme/img/west-mini.png b/themes/pmahomme/img/west-mini.png
new file mode 100644
index 0000000000..a13f0835cd
--- /dev/null
+++ b/themes/pmahomme/img/west-mini.png
Binary files differ
diff --git a/themes/pmahomme/img/window-new.png b/themes/pmahomme/img/window-new.png
new file mode 100644
index 0000000000..431fe851dc
--- /dev/null
+++ b/themes/pmahomme/img/window-new.png
Binary files differ
diff --git a/themes/pmahomme/img/zoom-minus-mini.png b/themes/pmahomme/img/zoom-minus-mini.png
new file mode 100644
index 0000000000..4262ad4631
--- /dev/null
+++ b/themes/pmahomme/img/zoom-minus-mini.png
Binary files differ
diff --git a/themes/pmahomme/img/zoom-plus-mini.png b/themes/pmahomme/img/zoom-plus-mini.png
new file mode 100644
index 0000000000..4fabfd1ed1
--- /dev/null
+++ b/themes/pmahomme/img/zoom-plus-mini.png
Binary files differ
diff --git a/themes/pmahomme/img/zoom-world-mini.png b/themes/pmahomme/img/zoom-world-mini.png
new file mode 100644
index 0000000000..f50ca66f2b
--- /dev/null
+++ b/themes/pmahomme/img/zoom-world-mini.png
Binary files differ
diff --git a/themes/pmahomme/info.inc.php b/themes/pmahomme/info.inc.php
new file mode 100644
index 0000000000..0c32532d17
--- /dev/null
+++ b/themes/pmahomme/info.inc.php
@@ -0,0 +1,21 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Theme information
+ *
+ * @package PhpMyAdmin-theme
+ * @subpackage PMAHomme
+ */
+
+/**
+ * If you have problems or questions about this theme email
+ * mikehomme@users.sourceforge.net
+ */
+
+/**
+ * Icon set used by this theme: http://famfamfam.com/lab/icons/silk/
+ */
+
+$theme_name = 'pmahomme';
+$theme_full_version = '1.1';
+?>
diff --git a/themes/pmahomme/jquery/images/ui-bg_flat_0_aaaaaa_40x100.png b/themes/pmahomme/jquery/images/ui-bg_flat_0_aaaaaa_40x100.png
new file mode 100644
index 0000000000..5b5dab2ab7
--- /dev/null
+++ b/themes/pmahomme/jquery/images/ui-bg_flat_0_aaaaaa_40x100.png
Binary files differ
diff --git a/themes/pmahomme/jquery/images/ui-bg_flat_75_ffffff_40x100.png b/themes/pmahomme/jquery/images/ui-bg_flat_75_ffffff_40x100.png
new file mode 100644
index 0000000000..ac8b229af9
--- /dev/null
+++ b/themes/pmahomme/jquery/images/ui-bg_flat_75_ffffff_40x100.png
Binary files differ
diff --git a/themes/pmahomme/jquery/images/ui-bg_glass_55_fbf9ee_1x400.png b/themes/pmahomme/jquery/images/ui-bg_glass_55_fbf9ee_1x400.png
new file mode 100644
index 0000000000..ad3d6346e0
--- /dev/null
+++ b/themes/pmahomme/jquery/images/ui-bg_glass_55_fbf9ee_1x400.png
Binary files differ
diff --git a/themes/pmahomme/jquery/images/ui-bg_glass_65_ffffff_1x400.png b/themes/pmahomme/jquery/images/ui-bg_glass_65_ffffff_1x400.png
new file mode 100644
index 0000000000..42ccba269b
--- /dev/null
+++ b/themes/pmahomme/jquery/images/ui-bg_glass_65_ffffff_1x400.png
Binary files differ
diff --git a/themes/pmahomme/jquery/images/ui-bg_glass_75_dadada_1x400.png b/themes/pmahomme/jquery/images/ui-bg_glass_75_dadada_1x400.png
new file mode 100644
index 0000000000..1d43b47e60
--- /dev/null
+++ b/themes/pmahomme/jquery/images/ui-bg_glass_75_dadada_1x400.png
Binary files differ
diff --git a/themes/pmahomme/jquery/images/ui-bg_glass_75_e6e6e6_1x400.png b/themes/pmahomme/jquery/images/ui-bg_glass_75_e6e6e6_1x400.png
new file mode 100644
index 0000000000..86c2baa655
--- /dev/null
+++ b/themes/pmahomme/jquery/images/ui-bg_glass_75_e6e6e6_1x400.png
Binary files differ
diff --git a/themes/pmahomme/jquery/images/ui-bg_glass_95_fef1ec_1x400.png b/themes/pmahomme/jquery/images/ui-bg_glass_95_fef1ec_1x400.png
new file mode 100644
index 0000000000..4443fdc1a1
--- /dev/null
+++ b/themes/pmahomme/jquery/images/ui-bg_glass_95_fef1ec_1x400.png
Binary files differ
diff --git a/themes/pmahomme/jquery/images/ui-bg_highlight-soft_75_cccccc_1x100.png b/themes/pmahomme/jquery/images/ui-bg_highlight-soft_75_cccccc_1x100.png
new file mode 100644
index 0000000000..7c9fa6c6ed
--- /dev/null
+++ b/themes/pmahomme/jquery/images/ui-bg_highlight-soft_75_cccccc_1x100.png
Binary files differ
diff --git a/themes/pmahomme/jquery/images/ui-icons_222222_256x240.png b/themes/pmahomme/jquery/images/ui-icons_222222_256x240.png
new file mode 100644
index 0000000000..b273ff111d
--- /dev/null
+++ b/themes/pmahomme/jquery/images/ui-icons_222222_256x240.png
Binary files differ
diff --git a/themes/pmahomme/jquery/images/ui-icons_2e83ff_256x240.png b/themes/pmahomme/jquery/images/ui-icons_2e83ff_256x240.png
new file mode 100644
index 0000000000..09d1cdc856
--- /dev/null
+++ b/themes/pmahomme/jquery/images/ui-icons_2e83ff_256x240.png
Binary files differ
diff --git a/themes/pmahomme/jquery/images/ui-icons_454545_256x240.png b/themes/pmahomme/jquery/images/ui-icons_454545_256x240.png
new file mode 100644
index 0000000000..59bd45b907
--- /dev/null
+++ b/themes/pmahomme/jquery/images/ui-icons_454545_256x240.png
Binary files differ
diff --git a/themes/pmahomme/jquery/images/ui-icons_888888_256x240.png b/themes/pmahomme/jquery/images/ui-icons_888888_256x240.png
new file mode 100644
index 0000000000..6d02426c11
--- /dev/null
+++ b/themes/pmahomme/jquery/images/ui-icons_888888_256x240.png
Binary files differ
diff --git a/themes/pmahomme/jquery/images/ui-icons_cd0a0a_256x240.png b/themes/pmahomme/jquery/images/ui-icons_cd0a0a_256x240.png
new file mode 100644
index 0000000000..2ab019b73e
--- /dev/null
+++ b/themes/pmahomme/jquery/images/ui-icons_cd0a0a_256x240.png
Binary files differ
diff --git a/themes/pmahomme/jquery/jquery-ui-1.9.2.custom.css b/themes/pmahomme/jquery/jquery-ui-1.9.2.custom.css
new file mode 100644
index 0000000000..b08d9039a4
--- /dev/null
+++ b/themes/pmahomme/jquery/jquery-ui-1.9.2.custom.css
@@ -0,0 +1,462 @@
+/*! jQuery UI - v1.9.2 - 2012-12-19
+* http://jqueryui.com
+* Includes: jquery.ui.core.css, jquery.ui.resizable.css, jquery.ui.selectable.css, jquery.ui.accordion.css, jquery.ui.autocomplete.css, jquery.ui.button.css, jquery.ui.datepicker.css, jquery.ui.dialog.css, jquery.ui.menu.css, jquery.ui.progressbar.css, jquery.ui.slider.css, jquery.ui.spinner.css, jquery.ui.tabs.css, jquery.ui.tooltip.css
+* To view and modify this theme, visit http://jqueryui.com/themeroller/?ffDefault=Verdana%2CArial%2Csans-serif&fwDefault=normal&fsDefault=1.1em&cornerRadius=4px&bgColorHeader=cccccc&bgTextureHeader=03_highlight_soft.png&bgImgOpacityHeader=75&borderColorHeader=aaaaaa&fcHeader=222222&iconColorHeader=222222&bgColorContent=ffffff&bgTextureContent=01_flat.png&bgImgOpacityContent=75&borderColorContent=aaaaaa&fcContent=222222&iconColorContent=222222&bgColorDefault=e6e6e6&bgTextureDefault=02_glass.png&bgImgOpacityDefault=75&borderColorDefault=d3d3d3&fcDefault=555555&iconColorDefault=888888&bgColorHover=dadada&bgTextureHover=02_glass.png&bgImgOpacityHover=75&borderColorHover=999999&fcHover=212121&iconColorHover=454545&bgColorActive=ffffff&bgTextureActive=02_glass.png&bgImgOpacityActive=65&borderColorActive=aaaaaa&fcActive=212121&iconColorActive=454545&bgColorHighlight=fbf9ee&bgTextureHighlight=02_glass.png&bgImgOpacityHighlight=55&borderColorHighlight=fcefa1&fcHighlight=363636&iconColorHighlight=2e83ff&bgColorError=fef1ec&bgTextureError=02_glass.png&bgImgOpacityError=95&borderColorError=cd0a0a&fcError=cd0a0a&iconColorError=cd0a0a&bgColorOverlay=aaaaaa&bgTextureOverlay=01_flat.png&bgImgOpacityOverlay=0&opacityOverlay=30&bgColorShadow=aaaaaa&bgTextureShadow=01_flat.png&bgImgOpacityShadow=0&opacityShadow=30&thicknessShadow=8px&offsetTopShadow=-8px&offsetLeftShadow=-8px&cornerRadiusShadow=8px
+* Copyright (c) 2012 jQuery Foundation and other contributors Licensed MIT */
+
+/* Layout helpers
+----------------------------------*/
+.ui-helper-hidden { display: none; }
+.ui-helper-hidden-accessible { border: 0; clip: rect(0 0 0 0); height: 1px; margin: -1px; overflow: hidden; padding: 0; position: absolute; width: 1px; }
+.ui-helper-reset { margin: 0; padding: 0; border: 0; outline: 0; line-height: 1.3; text-decoration: none; font-size: 100%; list-style: none; }
+.ui-helper-clearfix:before, .ui-helper-clearfix:after { content: ""; display: table; }
+.ui-helper-clearfix:after { clear: both; }
+.ui-helper-clearfix { zoom: 1; }
+.ui-helper-zfix { width: 100%; height: 100%; top: 0; left: 0; position: absolute; opacity: 0; filter:Alpha(Opacity=0); }
+
+
+/* Interaction Cues
+----------------------------------*/
+.ui-state-disabled { cursor: default !important; }
+
+
+/* Icons
+----------------------------------*/
+
+/* states and images */
+.ui-icon { display: block; text-indent: -99999px; overflow: hidden; background-repeat: no-repeat; }
+
+
+/* Misc visuals
+----------------------------------*/
+
+/* Overlays */
+.ui-widget-overlay { position: absolute; top: 0; left: 0; width: 100%; height: 100%; }
+.ui-resizable { position: relative;}
+.ui-resizable-handle { position: absolute;font-size: 0.1px; display: block; }
+.ui-resizable-disabled .ui-resizable-handle, .ui-resizable-autohide .ui-resizable-handle { display: none; }
+.ui-resizable-n { cursor: n-resize; height: 7px; width: 100%; top: -5px; left: 0; }
+.ui-resizable-s { cursor: s-resize; height: 7px; width: 100%; bottom: -5px; left: 0; }
+.ui-resizable-e { cursor: e-resize; width: 7px; right: -5px; top: 0; height: 100%; }
+.ui-resizable-w { cursor: w-resize; width: 7px; left: -5px; top: 0; height: 100%; }
+.ui-resizable-se { cursor: se-resize; width: 12px; height: 12px; right: 1px; bottom: 1px; }
+.ui-resizable-sw { cursor: sw-resize; width: 9px; height: 9px; left: -5px; bottom: -5px; }
+.ui-resizable-nw { cursor: nw-resize; width: 9px; height: 9px; left: -5px; top: -5px; }
+.ui-resizable-ne { cursor: ne-resize; width: 9px; height: 9px; right: -5px; top: -5px;}.ui-selectable-helper { position: absolute; z-index: 100; border:1px dotted black; }
+.ui-accordion .ui-accordion-header { display: block; cursor: pointer; position: relative; margin-top: 2px; padding: .5em .5em .5em .7em; zoom: 1; }
+.ui-accordion .ui-accordion-icons { padding-left: 2.2em; }
+.ui-accordion .ui-accordion-noicons { padding-left: .7em; }
+.ui-accordion .ui-accordion-icons .ui-accordion-icons { padding-left: 2.2em; }
+.ui-accordion .ui-accordion-header .ui-accordion-header-icon { position: absolute; left: .5em; top: 50%; margin-top: -8px; }
+.ui-accordion .ui-accordion-content { padding: 1em 2.2em; border-top: 0; overflow: auto; zoom: 1; }
+.ui-autocomplete {
+ position: absolute;
+ top: 0;
+ left: 0;
+ cursor: default;
+}
+
+/* workarounds */
+* html .ui-autocomplete { width:1px; } /* without this, the menu expands to 100% in IE6 */
+.ui-button { display: inline-block; position: relative; padding: 0; margin-right: .1em; cursor: pointer; text-align: center; zoom: 1; overflow: visible; } /* the overflow property removes extra width in IE */
+.ui-button, .ui-button:link, .ui-button:visited, .ui-button:hover, .ui-button:active { text-decoration: none; }
+.ui-button-icon-only { width: 2.2em; } /* to make room for the icon, a width needs to be set here */
+button.ui-button-icon-only { width: 2.4em; } /* button elements seem to need a little more width */
+.ui-button-icons-only { width: 3.4em; }
+button.ui-button-icons-only { width: 3.7em; }
+
+/*button text element */
+.ui-button .ui-button-text { display: block; line-height: 1.4; }
+.ui-button-text-only .ui-button-text { padding: .4em 1em; }
+.ui-button-icon-only .ui-button-text, .ui-button-icons-only .ui-button-text { padding: .4em; text-indent: -9999999px; }
+.ui-button-text-icon-primary .ui-button-text, .ui-button-text-icons .ui-button-text { padding: .4em 1em .4em 2.1em; }
+.ui-button-text-icon-secondary .ui-button-text, .ui-button-text-icons .ui-button-text { padding: .4em 2.1em .4em 1em; }
+.ui-button-text-icons .ui-button-text { padding-left: 2.1em; padding-right: 2.1em; }
+/* no icon support for input elements, provide padding by default */
+input.ui-button { padding: .4em 1em; }
+
+/*button icon element(s) */
+.ui-button-icon-only .ui-icon, .ui-button-text-icon-primary .ui-icon, .ui-button-text-icon-secondary .ui-icon, .ui-button-text-icons .ui-icon, .ui-button-icons-only .ui-icon { position: absolute; top: 50%; margin-top: -8px; }
+.ui-button-icon-only .ui-icon { left: 50%; margin-left: -8px; }
+.ui-button-text-icon-primary .ui-button-icon-primary, .ui-button-text-icons .ui-button-icon-primary, .ui-button-icons-only .ui-button-icon-primary { left: .5em; }
+.ui-button-text-icon-secondary .ui-button-icon-secondary, .ui-button-text-icons .ui-button-icon-secondary, .ui-button-icons-only .ui-button-icon-secondary { right: .5em; }
+.ui-button-text-icons .ui-button-icon-secondary, .ui-button-icons-only .ui-button-icon-secondary { right: .5em; }
+
+/*button sets*/
+.ui-buttonset { margin-right: 7px; }
+.ui-buttonset .ui-button { margin-left: 0; margin-right: -.3em; }
+
+/* workarounds */
+button.ui-button::-moz-focus-inner { border: 0; padding: 0; } /* reset extra padding in Firefox */
+.ui-datepicker { width: 17em; padding: .2em .2em 0; display: none; }
+.ui-datepicker .ui-datepicker-header { position:relative; padding:.2em 0; }
+.ui-datepicker .ui-datepicker-prev, .ui-datepicker .ui-datepicker-next { position:absolute; top: 2px; width: 1.8em; height: 1.8em; }
+.ui-datepicker .ui-datepicker-prev-hover, .ui-datepicker .ui-datepicker-next-hover { top: 1px; }
+.ui-datepicker .ui-datepicker-prev { left:2px; }
+.ui-datepicker .ui-datepicker-next { right:2px; }
+.ui-datepicker .ui-datepicker-prev-hover { left:1px; }
+.ui-datepicker .ui-datepicker-next-hover { right:1px; }
+.ui-datepicker .ui-datepicker-prev span, .ui-datepicker .ui-datepicker-next span { display: block; position: absolute; left: 50%; margin-left: -8px; top: 50%; margin-top: -8px; }
+.ui-datepicker .ui-datepicker-title { margin: 0 2.3em; line-height: 1.8em; text-align: center; }
+.ui-datepicker .ui-datepicker-title select { font-size:1em; margin:1px 0; }
+.ui-datepicker select.ui-datepicker-month-year {width: 100%;}
+.ui-datepicker select.ui-datepicker-month,
+.ui-datepicker select.ui-datepicker-year { width: 49%;}
+.ui-datepicker table {width: 100%; font-size: .9em; border-collapse: collapse; margin:0 0 .4em; }
+.ui-datepicker th { padding: .7em .3em; text-align: center; font-weight: bold; border: 0; }
+.ui-datepicker td { border: 0; padding: 1px; }
+.ui-datepicker td span, .ui-datepicker td a { display: block; padding: .2em; text-align: right; text-decoration: none; }
+.ui-datepicker .ui-datepicker-buttonpane { background-image: none; margin: .7em 0 0 0; padding:0 .2em; border-left: 0; border-right: 0; border-bottom: 0; }
+.ui-datepicker .ui-datepicker-buttonpane button { float: right; margin: .5em .2em .4em; cursor: pointer; padding: .2em .6em .3em .6em; width:auto; overflow:visible; }
+.ui-datepicker .ui-datepicker-buttonpane button.ui-datepicker-current { float:left; }
+
+/* with multiple calendars */
+.ui-datepicker.ui-datepicker-multi { width:auto; }
+.ui-datepicker-multi .ui-datepicker-group { float:left; }
+.ui-datepicker-multi .ui-datepicker-group table { width:95%; margin:0 auto .4em; }
+.ui-datepicker-multi-2 .ui-datepicker-group { width:50%; }
+.ui-datepicker-multi-3 .ui-datepicker-group { width:33.3%; }
+.ui-datepicker-multi-4 .ui-datepicker-group { width:25%; }
+.ui-datepicker-multi .ui-datepicker-group-last .ui-datepicker-header { border-left-width:0; }
+.ui-datepicker-multi .ui-datepicker-group-middle .ui-datepicker-header { border-left-width:0; }
+.ui-datepicker-multi .ui-datepicker-buttonpane { clear:left; }
+.ui-datepicker-row-break { clear:both; width:100%; font-size:0em; }
+
+/* RTL support */
+.ui-datepicker-rtl { direction: rtl; }
+.ui-datepicker-rtl .ui-datepicker-prev { right: 2px; left: auto; }
+.ui-datepicker-rtl .ui-datepicker-next { left: 2px; right: auto; }
+.ui-datepicker-rtl .ui-datepicker-prev:hover { right: 1px; left: auto; }
+.ui-datepicker-rtl .ui-datepicker-next:hover { left: 1px; right: auto; }
+.ui-datepicker-rtl .ui-datepicker-buttonpane { clear:right; }
+.ui-datepicker-rtl .ui-datepicker-buttonpane button { float: left; }
+.ui-datepicker-rtl .ui-datepicker-buttonpane button.ui-datepicker-current { float:right; }
+.ui-datepicker-rtl .ui-datepicker-group { float:right; }
+.ui-datepicker-rtl .ui-datepicker-group-last .ui-datepicker-header { border-right-width:0; border-left-width:1px; }
+.ui-datepicker-rtl .ui-datepicker-group-middle .ui-datepicker-header { border-right-width:0; border-left-width:1px; }
+
+/* IE6 IFRAME FIX (taken from datepicker 1.5.3 */
+.ui-datepicker-cover {
+ position: absolute; /*must have*/
+ z-index: -1; /*must have*/
+ filter: mask(); /*must have*/
+ top: -4px; /*must have*/
+ left: -4px; /*must have*/
+ width: 200px; /*must have*/
+ height: 200px; /*must have*/
+}.ui-dialog { position: absolute; top: 0; left: 0; padding: .2em; width: 300px; overflow: hidden; }
+.ui-dialog .ui-dialog-titlebar { padding: .4em 1em; position: relative; }
+.ui-dialog .ui-dialog-title { float: left; margin: .1em 16px .1em 0; }
+.ui-dialog .ui-dialog-titlebar-close { position: absolute; right: .3em; top: 50%; width: 19px; margin: -10px 0 0 0; padding: 1px; height: 18px; }
+.ui-dialog .ui-dialog-titlebar-close span { display: block; margin: 1px; }
+.ui-dialog .ui-dialog-titlebar-close:hover, .ui-dialog .ui-dialog-titlebar-close:focus { padding: 0; }
+.ui-dialog .ui-dialog-content { position: relative; border: 0; padding: .5em 1em; background: none; overflow: auto; zoom: 1; }
+.ui-dialog .ui-dialog-buttonpane { text-align: left; border-width: 1px 0 0 0; background-image: none; margin: .5em 0 0 0; padding: .3em 1em .5em .4em; }
+.ui-dialog .ui-dialog-buttonpane .ui-dialog-buttonset { float: right; }
+.ui-dialog .ui-dialog-buttonpane button { margin: .5em .4em .5em 0; cursor: pointer; }
+.ui-dialog .ui-resizable-se { width: 14px; height: 14px; right: 3px; bottom: 3px; }
+.ui-draggable .ui-dialog-titlebar { cursor: move; }
+.ui-menu { list-style:none; padding: 2px; margin: 0; display:block; outline: none; }
+.ui-menu .ui-menu { margin-top: -3px; position: absolute; }
+.ui-menu .ui-menu-item { margin: 0; padding: 0; zoom: 1; width: 100%; }
+.ui-menu .ui-menu-divider { margin: 5px -2px 5px -2px; height: 0; font-size: 0; line-height: 0; border-width: 1px 0 0 0; }
+.ui-menu .ui-menu-item a { text-decoration: none; display: block; padding: 2px .4em; line-height: 1.5; zoom: 1; font-weight: normal; }
+.ui-menu .ui-menu-item a.ui-state-focus,
+.ui-menu .ui-menu-item a.ui-state-active { font-weight: normal; margin: -1px; }
+
+.ui-menu .ui-state-disabled { font-weight: normal; margin: .4em 0 .2em; line-height: 1.5; }
+.ui-menu .ui-state-disabled a { cursor: default; }
+
+/* icon support */
+.ui-menu-icons { position: relative; }
+.ui-menu-icons .ui-menu-item a { position: relative; padding-left: 2em; }
+
+/* left-aligned */
+.ui-menu .ui-icon { position: absolute; top: .2em; left: .2em; }
+
+/* right-aligned */
+.ui-menu .ui-menu-icon { position: static; float: right; }
+.ui-progressbar { height:2em; text-align: left; overflow: hidden; }
+.ui-progressbar .ui-progressbar-value {margin: -1px; height:100%; }.ui-slider { position: relative; text-align: left; }
+.ui-slider .ui-slider-handle { position: absolute; z-index: 2; width: 1.2em; height: 1.2em; cursor: default; }
+.ui-slider .ui-slider-range { position: absolute; z-index: 1; font-size: .7em; display: block; border: 0; background-position: 0 0; }
+
+.ui-slider-horizontal { height: .8em; }
+.ui-slider-horizontal .ui-slider-handle { top: -.3em; margin-left: -.6em; }
+.ui-slider-horizontal .ui-slider-range { top: 0; height: 100%; }
+.ui-slider-horizontal .ui-slider-range-min { left: 0; }
+.ui-slider-horizontal .ui-slider-range-max { right: 0; }
+
+.ui-slider-vertical { width: .8em; height: 100px; }
+.ui-slider-vertical .ui-slider-handle { left: -.3em; margin-left: 0; margin-bottom: -.6em; }
+.ui-slider-vertical .ui-slider-range { left: 0; width: 100%; }
+.ui-slider-vertical .ui-slider-range-min { bottom: 0; }
+.ui-slider-vertical .ui-slider-range-max { top: 0; }.ui-spinner { position:relative; display: inline-block; overflow: hidden; padding: 0; vertical-align: middle; }
+.ui-spinner-input { border: none; background: none; padding: 0; margin: .2em 0; vertical-align: middle; margin-left: .4em; margin-right: 22px; }
+.ui-spinner-button { width: 16px; height: 50%; font-size: .5em; padding: 0; margin: 0; text-align: center; position: absolute; cursor: default; display: block; overflow: hidden; right: 0; }
+.ui-spinner a.ui-spinner-button { border-top: none; border-bottom: none; border-right: none; } /* more specificity required here to overide default borders */
+.ui-spinner .ui-icon { position: absolute; margin-top: -8px; top: 50%; left: 0; } /* vertical centre icon */
+.ui-spinner-up { top: 0; }
+.ui-spinner-down { bottom: 0; }
+
+/* TR overrides */
+.ui-spinner .ui-icon-triangle-1-s {
+ /* need to fix icons sprite */
+ background-position:-65px -16px;
+}
+.ui-tabs { position: relative; padding: .2em; zoom: 1; } /* position: relative prevents IE scroll bug (element with position: relative inside container with overflow: auto appear as "fixed") */
+.ui-tabs .ui-tabs-nav { margin: 0; padding: .2em .2em 0; }
+.ui-tabs .ui-tabs-nav li { list-style: none; float: left; position: relative; top: 0; margin: 1px .2em 0 0; border-bottom: 0; padding: 0; white-space: nowrap; }
+.ui-tabs .ui-tabs-nav li a { float: left; padding: .5em 1em; text-decoration: none; }
+.ui-tabs .ui-tabs-nav li.ui-tabs-active { margin-bottom: -1px; padding-bottom: 1px; }
+.ui-tabs .ui-tabs-nav li.ui-tabs-active a, .ui-tabs .ui-tabs-nav li.ui-state-disabled a, .ui-tabs .ui-tabs-nav li.ui-tabs-loading a { cursor: text; }
+.ui-tabs .ui-tabs-nav li a, .ui-tabs-collapsible .ui-tabs-nav li.ui-tabs-active a { cursor: pointer; } /* first selector in group seems obsolete, but required to overcome bug in Opera applying cursor: text overall if defined elsewhere... */
+.ui-tabs .ui-tabs-panel { display: block; border-width: 0; padding: 1em 1.4em; background: none; }
+.ui-tooltip {
+ padding: 8px;
+ position: absolute;
+ z-index: 9999;
+ max-width: 300px;
+ -webkit-box-shadow: 0 0 5px #aaa;
+ box-shadow: 0 0 5px #aaa;
+}
+/* Fades and background-images don't work well together in IE6, drop the image */
+* html .ui-tooltip {
+ background-image: none;
+}
+body .ui-tooltip { border-width: 2px; }
+
+/* Component containers
+----------------------------------*/
+.ui-widget { font-family: Verdana,Arial,sans-serif; font-size: 1.1em; }
+.ui-widget .ui-widget { font-size: 1em; }
+.ui-widget input, .ui-widget select, .ui-widget textarea, .ui-widget button { font-family: Verdana,Arial,sans-serif; font-size: 1em; }
+.ui-widget-content { border: 1px solid #aaaaaa; background: #ffffff url(images/ui-bg_flat_75_ffffff_40x100.png) 50% 50% repeat-x; color: #222222; }
+.ui-widget-content a { color: #222222; }
+.ui-widget-header { border: 1px solid #aaaaaa; background: #cccccc url(images/ui-bg_highlight-soft_75_cccccc_1x100.png) 50% 50% repeat-x; color: #222222; font-weight: bold; }
+.ui-widget-header a { color: #222222; }
+
+/* Interaction states
+----------------------------------*/
+.ui-state-default, .ui-widget-content .ui-state-default, .ui-widget-header .ui-state-default { border: 1px solid #d3d3d3; background: #e6e6e6 url(images/ui-bg_glass_75_e6e6e6_1x400.png) 50% 50% repeat-x; font-weight: normal; color: #555555; }
+.ui-state-default a, .ui-state-default a:link, .ui-state-default a:visited { color: #555555; text-decoration: none; }
+.ui-state-hover, .ui-widget-content .ui-state-hover, .ui-widget-header .ui-state-hover, .ui-state-focus, .ui-widget-content .ui-state-focus, .ui-widget-header .ui-state-focus { border: 1px solid #999999; background: #dadada url(images/ui-bg_glass_75_dadada_1x400.png) 50% 50% repeat-x; font-weight: normal; color: #212121; }
+.ui-state-hover a, .ui-state-hover a:hover, .ui-state-hover a:link, .ui-state-hover a:visited { color: #212121; text-decoration: none; }
+.ui-state-active, .ui-widget-content .ui-state-active, .ui-widget-header .ui-state-active { border: 1px solid #aaaaaa; background: #ffffff url(images/ui-bg_glass_65_ffffff_1x400.png) 50% 50% repeat-x; font-weight: normal; color: #212121; }
+.ui-state-active a, .ui-state-active a:link, .ui-state-active a:visited { color: #212121; text-decoration: none; }
+
+/* Interaction Cues
+----------------------------------*/
+.ui-state-highlight, .ui-widget-content .ui-state-highlight, .ui-widget-header .ui-state-highlight {border: 1px solid #fcefa1; background: #fbf9ee url(images/ui-bg_glass_55_fbf9ee_1x400.png) 50% 50% repeat-x; color: #363636; }
+.ui-state-highlight a, .ui-widget-content .ui-state-highlight a,.ui-widget-header .ui-state-highlight a { color: #363636; }
+.ui-state-error, .ui-widget-content .ui-state-error, .ui-widget-header .ui-state-error {border: 1px solid #cd0a0a; background: #fef1ec url(images/ui-bg_glass_95_fef1ec_1x400.png) 50% 50% repeat-x; color: #cd0a0a; }
+.ui-state-error a, .ui-widget-content .ui-state-error a, .ui-widget-header .ui-state-error a { color: #cd0a0a; }
+.ui-state-error-text, .ui-widget-content .ui-state-error-text, .ui-widget-header .ui-state-error-text { color: #cd0a0a; }
+.ui-priority-primary, .ui-widget-content .ui-priority-primary, .ui-widget-header .ui-priority-primary { font-weight: bold; }
+.ui-priority-secondary, .ui-widget-content .ui-priority-secondary, .ui-widget-header .ui-priority-secondary { opacity: .7; filter:Alpha(Opacity=70); font-weight: normal; }
+.ui-state-disabled, .ui-widget-content .ui-state-disabled, .ui-widget-header .ui-state-disabled { opacity: .35; filter:Alpha(Opacity=35); background-image: none; }
+.ui-state-disabled .ui-icon { filter:Alpha(Opacity=35); } /* For IE8 - See #6059 */
+
+/* Icons
+----------------------------------*/
+
+/* states and images */
+.ui-icon { width: 16px; height: 16px; background-image: url(images/ui-icons_222222_256x240.png); }
+.ui-widget-content .ui-icon {background-image: url(images/ui-icons_222222_256x240.png); }
+.ui-widget-header .ui-icon {background-image: url(images/ui-icons_222222_256x240.png); }
+.ui-state-default .ui-icon { background-image: url(images/ui-icons_888888_256x240.png); }
+.ui-state-hover .ui-icon, .ui-state-focus .ui-icon {background-image: url(images/ui-icons_454545_256x240.png); }
+.ui-state-active .ui-icon {background-image: url(images/ui-icons_454545_256x240.png); }
+.ui-state-highlight .ui-icon {background-image: url(images/ui-icons_2e83ff_256x240.png); }
+.ui-state-error .ui-icon, .ui-state-error-text .ui-icon {background-image: url(images/ui-icons_cd0a0a_256x240.png); }
+
+/* positioning */
+.ui-icon-carat-1-n { background-position: 0 0; }
+.ui-icon-carat-1-ne { background-position: -16px 0; }
+.ui-icon-carat-1-e { background-position: -32px 0; }
+.ui-icon-carat-1-se { background-position: -48px 0; }
+.ui-icon-carat-1-s { background-position: -64px 0; }
+.ui-icon-carat-1-sw { background-position: -80px 0; }
+.ui-icon-carat-1-w { background-position: -96px 0; }
+.ui-icon-carat-1-nw { background-position: -112px 0; }
+.ui-icon-carat-2-n-s { background-position: -128px 0; }
+.ui-icon-carat-2-e-w { background-position: -144px 0; }
+.ui-icon-triangle-1-n { background-position: 0 -16px; }
+.ui-icon-triangle-1-ne { background-position: -16px -16px; }
+.ui-icon-triangle-1-e { background-position: -32px -16px; }
+.ui-icon-triangle-1-se { background-position: -48px -16px; }
+.ui-icon-triangle-1-s { background-position: -64px -16px; }
+.ui-icon-triangle-1-sw { background-position: -80px -16px; }
+.ui-icon-triangle-1-w { background-position: -96px -16px; }
+.ui-icon-triangle-1-nw { background-position: -112px -16px; }
+.ui-icon-triangle-2-n-s { background-position: -128px -16px; }
+.ui-icon-triangle-2-e-w { background-position: -144px -16px; }
+.ui-icon-arrow-1-n { background-position: 0 -32px; }
+.ui-icon-arrow-1-ne { background-position: -16px -32px; }
+.ui-icon-arrow-1-e { background-position: -32px -32px; }
+.ui-icon-arrow-1-se { background-position: -48px -32px; }
+.ui-icon-arrow-1-s { background-position: -64px -32px; }
+.ui-icon-arrow-1-sw { background-position: -80px -32px; }
+.ui-icon-arrow-1-w { background-position: -96px -32px; }
+.ui-icon-arrow-1-nw { background-position: -112px -32px; }
+.ui-icon-arrow-2-n-s { background-position: -128px -32px; }
+.ui-icon-arrow-2-ne-sw { background-position: -144px -32px; }
+.ui-icon-arrow-2-e-w { background-position: -160px -32px; }
+.ui-icon-arrow-2-se-nw { background-position: -176px -32px; }
+.ui-icon-arrowstop-1-n { background-position: -192px -32px; }
+.ui-icon-arrowstop-1-e { background-position: -208px -32px; }
+.ui-icon-arrowstop-1-s { background-position: -224px -32px; }
+.ui-icon-arrowstop-1-w { background-position: -240px -32px; }
+.ui-icon-arrowthick-1-n { background-position: 0 -48px; }
+.ui-icon-arrowthick-1-ne { background-position: -16px -48px; }
+.ui-icon-arrowthick-1-e { background-position: -32px -48px; }
+.ui-icon-arrowthick-1-se { background-position: -48px -48px; }
+.ui-icon-arrowthick-1-s { background-position: -64px -48px; }
+.ui-icon-arrowthick-1-sw { background-position: -80px -48px; }
+.ui-icon-arrowthick-1-w { background-position: -96px -48px; }
+.ui-icon-arrowthick-1-nw { background-position: -112px -48px; }
+.ui-icon-arrowthick-2-n-s { background-position: -128px -48px; }
+.ui-icon-arrowthick-2-ne-sw { background-position: -144px -48px; }
+.ui-icon-arrowthick-2-e-w { background-position: -160px -48px; }
+.ui-icon-arrowthick-2-se-nw { background-position: -176px -48px; }
+.ui-icon-arrowthickstop-1-n { background-position: -192px -48px; }
+.ui-icon-arrowthickstop-1-e { background-position: -208px -48px; }
+.ui-icon-arrowthickstop-1-s { background-position: -224px -48px; }
+.ui-icon-arrowthickstop-1-w { background-position: -240px -48px; }
+.ui-icon-arrowreturnthick-1-w { background-position: 0 -64px; }
+.ui-icon-arrowreturnthick-1-n { background-position: -16px -64px; }
+.ui-icon-arrowreturnthick-1-e { background-position: -32px -64px; }
+.ui-icon-arrowreturnthick-1-s { background-position: -48px -64px; }
+.ui-icon-arrowreturn-1-w { background-position: -64px -64px; }
+.ui-icon-arrowreturn-1-n { background-position: -80px -64px; }
+.ui-icon-arrowreturn-1-e { background-position: -96px -64px; }
+.ui-icon-arrowreturn-1-s { background-position: -112px -64px; }
+.ui-icon-arrowrefresh-1-w { background-position: -128px -64px; }
+.ui-icon-arrowrefresh-1-n { background-position: -144px -64px; }
+.ui-icon-arrowrefresh-1-e { background-position: -160px -64px; }
+.ui-icon-arrowrefresh-1-s { background-position: -176px -64px; }
+.ui-icon-arrow-4 { background-position: 0 -80px; }
+.ui-icon-arrow-4-diag { background-position: -16px -80px; }
+.ui-icon-extlink { background-position: -32px -80px; }
+.ui-icon-newwin { background-position: -48px -80px; }
+.ui-icon-refresh { background-position: -64px -80px; }
+.ui-icon-shuffle { background-position: -80px -80px; }
+.ui-icon-transfer-e-w { background-position: -96px -80px; }
+.ui-icon-transferthick-e-w { background-position: -112px -80px; }
+.ui-icon-folder-collapsed { background-position: 0 -96px; }
+.ui-icon-folder-open { background-position: -16px -96px; }
+.ui-icon-document { background-position: -32px -96px; }
+.ui-icon-document-b { background-position: -48px -96px; }
+.ui-icon-note { background-position: -64px -96px; }
+.ui-icon-mail-closed { background-position: -80px -96px; }
+.ui-icon-mail-open { background-position: -96px -96px; }
+.ui-icon-suitcase { background-position: -112px -96px; }
+.ui-icon-comment { background-position: -128px -96px; }
+.ui-icon-person { background-position: -144px -96px; }
+.ui-icon-print { background-position: -160px -96px; }
+.ui-icon-trash { background-position: -176px -96px; }
+.ui-icon-locked { background-position: -192px -96px; }
+.ui-icon-unlocked { background-position: -208px -96px; }
+.ui-icon-bookmark { background-position: -224px -96px; }
+.ui-icon-tag { background-position: -240px -96px; }
+.ui-icon-home { background-position: 0 -112px; }
+.ui-icon-flag { background-position: -16px -112px; }
+.ui-icon-calendar { background-position: -32px -112px; }
+.ui-icon-cart { background-position: -48px -112px; }
+.ui-icon-pencil { background-position: -64px -112px; }
+.ui-icon-clock { background-position: -80px -112px; }
+.ui-icon-disk { background-position: -96px -112px; }
+.ui-icon-calculator { background-position: -112px -112px; }
+.ui-icon-zoomin { background-position: -128px -112px; }
+.ui-icon-zoomout { background-position: -144px -112px; }
+.ui-icon-search { background-position: -160px -112px; }
+.ui-icon-wrench { background-position: -176px -112px; }
+.ui-icon-gear { background-position: -192px -112px; }
+.ui-icon-heart { background-position: -208px -112px; }
+.ui-icon-star { background-position: -224px -112px; }
+.ui-icon-link { background-position: -240px -112px; }
+.ui-icon-cancel { background-position: 0 -128px; }
+.ui-icon-plus { background-position: -16px -128px; }
+.ui-icon-plusthick { background-position: -32px -128px; }
+.ui-icon-minus { background-position: -48px -128px; }
+.ui-icon-minusthick { background-position: -64px -128px; }
+.ui-icon-close { background-position: -80px -128px; }
+.ui-icon-closethick { background-position: -96px -128px; }
+.ui-icon-key { background-position: -112px -128px; }
+.ui-icon-lightbulb { background-position: -128px -128px; }
+.ui-icon-scissors { background-position: -144px -128px; }
+.ui-icon-clipboard { background-position: -160px -128px; }
+.ui-icon-copy { background-position: -176px -128px; }
+.ui-icon-contact { background-position: -192px -128px; }
+.ui-icon-image { background-position: -208px -128px; }
+.ui-icon-video { background-position: -224px -128px; }
+.ui-icon-script { background-position: -240px -128px; }
+.ui-icon-alert { background-position: 0 -144px; }
+.ui-icon-info { background-position: -16px -144px; }
+.ui-icon-notice { background-position: -32px -144px; }
+.ui-icon-help { background-position: -48px -144px; }
+.ui-icon-check { background-position: -64px -144px; }
+.ui-icon-bullet { background-position: -80px -144px; }
+.ui-icon-radio-on { background-position: -96px -144px; }
+.ui-icon-radio-off { background-position: -112px -144px; }
+.ui-icon-pin-w { background-position: -128px -144px; }
+.ui-icon-pin-s { background-position: -144px -144px; }
+.ui-icon-play { background-position: 0 -160px; }
+.ui-icon-pause { background-position: -16px -160px; }
+.ui-icon-seek-next { background-position: -32px -160px; }
+.ui-icon-seek-prev { background-position: -48px -160px; }
+.ui-icon-seek-end { background-position: -64px -160px; }
+.ui-icon-seek-start { background-position: -80px -160px; }
+/* ui-icon-seek-first is deprecated, use ui-icon-seek-start instead */
+.ui-icon-seek-first { background-position: -80px -160px; }
+.ui-icon-stop { background-position: -96px -160px; }
+.ui-icon-eject { background-position: -112px -160px; }
+.ui-icon-volume-off { background-position: -128px -160px; }
+.ui-icon-volume-on { background-position: -144px -160px; }
+.ui-icon-power { background-position: 0 -176px; }
+.ui-icon-signal-diag { background-position: -16px -176px; }
+.ui-icon-signal { background-position: -32px -176px; }
+.ui-icon-battery-0 { background-position: -48px -176px; }
+.ui-icon-battery-1 { background-position: -64px -176px; }
+.ui-icon-battery-2 { background-position: -80px -176px; }
+.ui-icon-battery-3 { background-position: -96px -176px; }
+.ui-icon-circle-plus { background-position: 0 -192px; }
+.ui-icon-circle-minus { background-position: -16px -192px; }
+.ui-icon-circle-close { background-position: -32px -192px; }
+.ui-icon-circle-triangle-e { background-position: -48px -192px; }
+.ui-icon-circle-triangle-s { background-position: -64px -192px; }
+.ui-icon-circle-triangle-w { background-position: -80px -192px; }
+.ui-icon-circle-triangle-n { background-position: -96px -192px; }
+.ui-icon-circle-arrow-e { background-position: -112px -192px; }
+.ui-icon-circle-arrow-s { background-position: -128px -192px; }
+.ui-icon-circle-arrow-w { background-position: -144px -192px; }
+.ui-icon-circle-arrow-n { background-position: -160px -192px; }
+.ui-icon-circle-zoomin { background-position: -176px -192px; }
+.ui-icon-circle-zoomout { background-position: -192px -192px; }
+.ui-icon-circle-check { background-position: -208px -192px; }
+.ui-icon-circlesmall-plus { background-position: 0 -208px; }
+.ui-icon-circlesmall-minus { background-position: -16px -208px; }
+.ui-icon-circlesmall-close { background-position: -32px -208px; }
+.ui-icon-squaresmall-plus { background-position: -48px -208px; }
+.ui-icon-squaresmall-minus { background-position: -64px -208px; }
+.ui-icon-squaresmall-close { background-position: -80px -208px; }
+.ui-icon-grip-dotted-vertical { background-position: 0 -224px; }
+.ui-icon-grip-dotted-horizontal { background-position: -16px -224px; }
+.ui-icon-grip-solid-vertical { background-position: -32px -224px; }
+.ui-icon-grip-solid-horizontal { background-position: -48px -224px; }
+.ui-icon-gripsmall-diagonal-se { background-position: -64px -224px; }
+.ui-icon-grip-diagonal-se { background-position: -80px -224px; }
+
+
+/* Misc visuals
+----------------------------------*/
+
+/* Corner radius */
+.ui-corner-all, .ui-corner-top, .ui-corner-left, .ui-corner-tl { -moz-border-radius-topleft: 4px; -webkit-border-top-left-radius: 4px; -khtml-border-top-left-radius: 4px; border-top-left-radius: 4px; }
+.ui-corner-all, .ui-corner-top, .ui-corner-right, .ui-corner-tr { -moz-border-radius-topright: 4px; -webkit-border-top-right-radius: 4px; -khtml-border-top-right-radius: 4px; border-top-right-radius: 4px; }
+.ui-corner-all, .ui-corner-bottom, .ui-corner-left, .ui-corner-bl { -moz-border-radius-bottomleft: 4px; -webkit-border-bottom-left-radius: 4px; -khtml-border-bottom-left-radius: 4px; border-bottom-left-radius: 4px; }
+.ui-corner-all, .ui-corner-bottom, .ui-corner-right, .ui-corner-br { -moz-border-radius-bottomright: 4px; -webkit-border-bottom-right-radius: 4px; -khtml-border-bottom-right-radius: 4px; border-bottom-right-radius: 4px; }
+
+/* Overlays */
+.ui-widget-overlay { background: #aaaaaa url(images/ui-bg_flat_0_aaaaaa_40x100.png) 50% 50% repeat-x; opacity: .3;filter:Alpha(Opacity=30); }
+.ui-widget-shadow { margin: -8px 0 0 -8px; padding: 8px; background: #aaaaaa url(images/ui-bg_flat_0_aaaaaa_40x100.png) 50% 50% repeat-x; opacity: .3;filter:Alpha(Opacity=30); -moz-border-radius: 8px; -khtml-border-radius: 8px; -webkit-border-radius: 8px; border-radius: 8px; } \ No newline at end of file
diff --git a/themes/pmahomme/layout.inc.php b/themes/pmahomme/layout.inc.php
new file mode 100644
index 0000000000..c273b6b3b5
--- /dev/null
+++ b/themes/pmahomme/layout.inc.php
@@ -0,0 +1,112 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * configures general layout
+ * for detailed layout configuration please refer to the css files
+ *
+ * @package PhpMyAdmin-theme
+ * @subpackage PMAHomme
+ */
+
+/**
+ * navi frame
+ */
+// navi frame width
+$GLOBALS['cfg']['NaviWidth'] = 240;
+
+// foreground (text) color for the navi frame
+$GLOBALS['cfg']['NaviColor'] = '#000';
+
+// background for the navi frame
+$GLOBALS['cfg']['NaviBackground'] = '#f3f3f3';
+
+// foreground (text) color of the pointer in navi frame
+$GLOBALS['cfg']['NaviPointerColor'] = '#000';
+
+// background of the pointer in navi frame
+$GLOBALS['cfg']['NaviPointerBackground'] = '#ddd';
+
+/**
+ * main frame
+ */
+// foreground (text) color for the main frame
+$GLOBALS['cfg']['MainColor'] = '#000';
+
+// background for the main frame
+$GLOBALS['cfg']['MainBackground'] = '#F5F5F5';
+
+// foreground (text) color of the pointer in browse mode
+$GLOBALS['cfg']['BrowsePointerColor'] = '#000';
+
+// background of the pointer in browse mode
+$GLOBALS['cfg']['BrowsePointerBackground'] = '#cfc';
+
+// foreground (text) color of the marker (visually marks row by clicking on it)
+// in browse mode
+$GLOBALS['cfg']['BrowseMarkerColor'] = '#000';
+
+// background of the marker (visually marks row by clicking on it) in browse mode
+$GLOBALS['cfg']['BrowseMarkerBackground'] = '#fc9';
+
+/**
+ * fonts
+ */
+/**
+ * the font family as a valid css font family value,
+ * if not set the browser default will be used
+ * (depending on browser, DTD and system settings)
+ */
+$GLOBALS['cfg']['FontFamily'] = 'sans-serif';
+/**
+ * fixed width font family, used in textarea
+ */
+$GLOBALS['cfg']['FontFamilyFixed'] = 'monospace';
+
+/**
+ * tables
+ */
+// border
+$GLOBALS['cfg']['Border'] = 0;
+// table header and footer color
+$GLOBALS['cfg']['ThBackground'] = '#D3DCE3';
+// table header and footer background
+$GLOBALS['cfg']['ThColor'] = '#000';
+// table data row background
+$GLOBALS['cfg']['BgOne'] = '#E5E5E5';
+// table data row background, alternate
+$GLOBALS['cfg']['BgTwo'] = '#D5D5D5';
+
+/**
+ * query window
+ */
+// Width of Query window
+$GLOBALS['cfg']['QueryWindowWidth'] = 600;
+// Height of Query window
+$GLOBALS['cfg']['QueryWindowHeight'] = 400;
+
+/**
+ * Chart colors
+ */
+
+ $GLOBALS['cfg']['chartColor'] = array(
+ 'gradientIntensity' => 50,
+ // The style of the chart title.
+ 'titleColor' => '#000',
+ 'titleBgColor' => '#E5E5E5',
+ // Chart border (0 for no border)
+ 'border' => '#ccc',
+ // Chart background color.
+ 'bgColor' => '#FBFBFB',
+ // when graph area gradient is used, this is the color of the graph
+ // area border
+ 'graphAreaColor' => '#D5D9DD',
+ // the background color of the graph area
+ 'graphAreaGradientColor' => $GLOBALS['cfg']['BgTwo'],
+ // the color of the grid lines in the graph area
+ 'gridColor' => '#E6E6E6',
+ // the color of the scale and the labels
+ 'scaleColor' => '#D5D9DD',
+
+ );
+
+?>
diff --git a/themes/pmahomme/screen.png b/themes/pmahomme/screen.png
new file mode 100644
index 0000000000..1441fe2e5e
--- /dev/null
+++ b/themes/pmahomme/screen.png
Binary files differ
diff --git a/themes/pmahomme/sprites.lib.php b/themes/pmahomme/sprites.lib.php
new file mode 100644
index 0000000000..c3cd7d687b
--- /dev/null
+++ b/themes/pmahomme/sprites.lib.php
@@ -0,0 +1,750 @@
+<?php
+/**
+ * AUTOGENERATED CONTENT - DO NOT EDIT!
+ * ALL CHANGES WILL BE UNDONE!
+ * RUN `./scripts/generate-sprites` TO UPDATE THIS FILE
+ *
+ * @package PhpMyAdmin-theme
+ */
+
+/**
+ * Returns map of sprites inside sprite.png
+ *
+ * @return array Data of sprites
+ */
+function PMA_sprites()
+{
+ return array(
+ 'asc_order' => array(
+ 'position' => '1',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'b_bookmark' => array(
+ 'position' => '2',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'b_browse' => array(
+ 'position' => '3',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'b_calendar' => array(
+ 'position' => '4',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'b_chart' => array(
+ 'position' => '5',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'b_close' => array(
+ 'position' => '6',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'b_column_add' => array(
+ 'position' => '7',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'b_comment' => array(
+ 'position' => '8',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'b_dbstatistics' => array(
+ 'position' => '9',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'b_deltbl' => array(
+ 'position' => '10',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'b_docs' => array(
+ 'position' => '11',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'b_docsql' => array(
+ 'position' => '12',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'b_drop' => array(
+ 'position' => '13',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'b_edit' => array(
+ 'position' => '14',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'b_empty' => array(
+ 'position' => '15',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'b_engine' => array(
+ 'position' => '16',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'b_event_add' => array(
+ 'position' => '17',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'b_events' => array(
+ 'position' => '18',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'b_export' => array(
+ 'position' => '19',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'b_find_replace' => array(
+ 'position' => '20',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'b_firstpage' => array(
+ 'position' => '21',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'b_ftext' => array(
+ 'position' => '22',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'b_group' => array(
+ 'position' => '23',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'b_help' => array(
+ 'position' => '24',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'b_home' => array(
+ 'position' => '25',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'b_import' => array(
+ 'position' => '26',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'b_index' => array(
+ 'position' => '27',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'b_index_add' => array(
+ 'position' => '28',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'b_info' => array(
+ 'position' => '29',
+ 'width' => '11',
+ 'height' => '11'
+ ),
+ 'b_inline_edit' => array(
+ 'position' => '30',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'b_insrow' => array(
+ 'position' => '31',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'b_lastpage' => array(
+ 'position' => '32',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'b_minus' => array(
+ 'position' => '33',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'b_more' => array(
+ 'position' => '34',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'b_move' => array(
+ 'position' => '35',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'b_newdb' => array(
+ 'position' => '36',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'b_newtbl' => array(
+ 'position' => '37',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'b_nextpage' => array(
+ 'position' => '38',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'b_pdfdoc' => array(
+ 'position' => '39',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'b_plus' => array(
+ 'position' => '40',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'b_prevpage' => array(
+ 'position' => '41',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'b_primary' => array(
+ 'position' => '42',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'b_print' => array(
+ 'position' => '43',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'b_props' => array(
+ 'position' => '44',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'b_relations' => array(
+ 'position' => '45',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'b_routine_add' => array(
+ 'position' => '46',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'b_routines' => array(
+ 'position' => '47',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'b_save' => array(
+ 'position' => '48',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'b_sbrowse' => array(
+ 'position' => '49',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'b_sdb' => array(
+ 'position' => '50',
+ 'width' => '10',
+ 'height' => '10'
+ ),
+ 'b_search' => array(
+ 'position' => '51',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'b_selboard' => array(
+ 'position' => '52',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'b_select' => array(
+ 'position' => '53',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'b_snewtbl' => array(
+ 'position' => '54',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'b_spatial' => array(
+ 'position' => '55',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'b_sql' => array(
+ 'position' => '56',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'b_sqldoc' => array(
+ 'position' => '57',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'b_sqlhelp' => array(
+ 'position' => '58',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'b_table_add' => array(
+ 'position' => '59',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'b_tblanalyse' => array(
+ 'position' => '60',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'b_tblexport' => array(
+ 'position' => '61',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'b_tblimport' => array(
+ 'position' => '62',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'b_tblops' => array(
+ 'position' => '63',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'b_tbloptimize' => array(
+ 'position' => '64',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'b_tipp' => array(
+ 'position' => '65',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'b_trigger_add' => array(
+ 'position' => '66',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'b_triggers' => array(
+ 'position' => '67',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'b_undo' => array(
+ 'position' => '68',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'b_unique' => array(
+ 'position' => '69',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'b_usradd' => array(
+ 'position' => '70',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'b_usrcheck' => array(
+ 'position' => '71',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'b_usrdrop' => array(
+ 'position' => '72',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'b_usredit' => array(
+ 'position' => '73',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'b_usrlist' => array(
+ 'position' => '74',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'b_view' => array(
+ 'position' => '75',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'b_view_add' => array(
+ 'position' => '76',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'b_views' => array(
+ 'position' => '77',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'bd_browse' => array(
+ 'position' => '78',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'bd_deltbl' => array(
+ 'position' => '79',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'bd_drop' => array(
+ 'position' => '80',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'bd_edit' => array(
+ 'position' => '81',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'bd_empty' => array(
+ 'position' => '82',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'bd_export' => array(
+ 'position' => '83',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'bd_firstpage' => array(
+ 'position' => '84',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'bd_ftext' => array(
+ 'position' => '85',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'bd_index' => array(
+ 'position' => '86',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'bd_insrow' => array(
+ 'position' => '87',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'bd_lastpage' => array(
+ 'position' => '88',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'bd_nextpage' => array(
+ 'position' => '89',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'bd_prevpage' => array(
+ 'position' => '90',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'bd_primary' => array(
+ 'position' => '91',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'bd_sbrowse' => array(
+ 'position' => '92',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'bd_select' => array(
+ 'position' => '93',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'bd_spatial' => array(
+ 'position' => '94',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'bd_unique' => array(
+ 'position' => '95',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'col_drop' => array(
+ 'position' => '96',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'database' => array(
+ 'position' => '97',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'eye' => array(
+ 'position' => '98',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'eye_grey' => array(
+ 'position' => '99',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'item' => array(
+ 'position' => '100',
+ 'width' => '9',
+ 'height' => '9'
+ ),
+ 'lightbulb' => array(
+ 'position' => '101',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'lightbulb_off' => array(
+ 'position' => '102',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'more' => array(
+ 'position' => '103',
+ 'width' => '13',
+ 'height' => '16'
+ ),
+ 'new_data' => array(
+ 'position' => '104',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'new_data_hovered' => array(
+ 'position' => '105',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'new_data_selected' => array(
+ 'position' => '106',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'new_data_selected_hovered' => array(
+ 'position' => '107',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'new_struct' => array(
+ 'position' => '108',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'new_struct_hovered' => array(
+ 'position' => '109',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'new_struct_selected' => array(
+ 'position' => '110',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'new_struct_selected_hovered' => array(
+ 'position' => '111',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'pause' => array(
+ 'position' => '112',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'php_sym' => array(
+ 'position' => '113',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'play' => array(
+ 'position' => '114',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 's_asc' => array(
+ 'position' => '115',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 's_asci' => array(
+ 'position' => '116',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 's_attention' => array(
+ 'position' => '117',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 's_cancel' => array(
+ 'position' => '118',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 's_cancel2' => array(
+ 'position' => '119',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 's_cog' => array(
+ 'position' => '120',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 's_db' => array(
+ 'position' => '121',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 's_desc' => array(
+ 'position' => '122',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 's_error' => array(
+ 'position' => '123',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 's_error2' => array(
+ 'position' => '124',
+ 'width' => '11',
+ 'height' => '11'
+ ),
+ 's_host' => array(
+ 'position' => '125',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 's_info' => array(
+ 'position' => '126',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 's_lang' => array(
+ 'position' => '127',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 's_loggoff' => array(
+ 'position' => '128',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 's_notice' => array(
+ 'position' => '129',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 's_okay' => array(
+ 'position' => '130',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 's_passwd' => array(
+ 'position' => '131',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 's_process' => array(
+ 'position' => '132',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 's_really' => array(
+ 'position' => '133',
+ 'width' => '11',
+ 'height' => '11'
+ ),
+ 's_reload' => array(
+ 'position' => '134',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 's_replication' => array(
+ 'position' => '135',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 's_rights' => array(
+ 'position' => '136',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 's_sortable' => array(
+ 'position' => '137',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 's_status' => array(
+ 'position' => '138',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 's_success' => array(
+ 'position' => '139',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 's_sync' => array(
+ 'position' => '140',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 's_tbl' => array(
+ 'position' => '141',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 's_theme' => array(
+ 'position' => '142',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 's_top' => array(
+ 'position' => '143',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 's_vars' => array(
+ 'position' => '144',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 's_views' => array(
+ 'position' => '145',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ 'window-new' => array(
+ 'position' => '146',
+ 'width' => '16',
+ 'height' => '16'
+ ),
+ );
+}
+?>
diff --git a/themes/sprites.css.php b/themes/sprites.css.php
new file mode 100644
index 0000000000..2c12589a41
--- /dev/null
+++ b/themes/sprites.css.php
@@ -0,0 +1,82 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * This file generates the CSS code for the sprites of a theme
+ *
+ * @package PhpMyAdmin-theme
+ */
+
+// unplanned execution path
+if (! defined('PMA_MINIMUM_COMMON')) {
+ exit();
+}
+
+$bg = $_SESSION['PMA_Theme']->getImgPath() . 'sprites.png';
+/* Check if there is a valid data file for sprites */
+if (is_readable($_SESSION['PMA_Theme']->getPath() . '/sprites.lib.php')) {
+
+?>
+/* Icon sprites */
+.icon {
+ margin: 0 .3em;
+ padding: 0 !important;
+ width: 16px;
+ height: 16px;
+ background-image: url('<?php echo $bg; ?>') !important;
+ background-repeat: no-repeat !important;
+ background-position: top left !important;
+}
+<?php
+
+ include_once $_SESSION['PMA_Theme']->getPath() . '/sprites.lib.php';
+ $sprites = array();
+ if (function_exists('PMA_sprites')) {
+ $sprites = PMA_sprites();
+ }
+ $template = ".ic_%s { background-position: 0 -%upx !important;%s%s }\n";
+ foreach ($sprites as $name => $data) {
+ // generate the CSS code for each icon
+ $width = '';
+ $height = '';
+ // if either the height or width of an icon is 16px,
+ // then it's pointless to set this as a parameter,
+ //since it will be inherited from the "icon" class
+ if ($data['width'] != 16) {
+ $width = " width: " . $data['width'] . "px;";
+ }
+ if ($data['height'] != 16) {
+ $height = " height: " . $data['height'] . "px;";
+ }
+ printf(
+ $template,
+ $name,
+ ($data['position'] * 16),
+ $width,
+ $height
+ );
+ }
+ // Here we map some of the classes that we
+ // defined above to other CSS selectors.
+ // The indexes of the array correspond to
+ // already defined classes and the values
+ // are the selectors that we want to map to.
+ $elements = array(
+ 's_sortable' => 'img.sortableIcon',
+ 's_asc' => 'th.headerSortUp img.sortableIcon',
+ 's_desc' => 'th.headerSortDown img.sortableIcon'
+ );
+ $template = "%s { background-position: 0 -%upx; "
+ . "height: %upx; width: %upx; }\n";
+ foreach ($elements as $key => $value) {
+ if (isset($sprites[$key])) { // If the CSS class has been defined
+ printf(
+ $template,
+ $value,
+ ($sprites[$key]['position'] * 16),
+ $sprites[$key]['height'],
+ $sprites[$key]['width']
+ );
+ }
+ }
+}
+?>
diff --git a/themes/svg_gradient.php b/themes/svg_gradient.php
new file mode 100644
index 0000000000..7e49037293
--- /dev/null
+++ b/themes/svg_gradient.php
@@ -0,0 +1,53 @@
+<?php
+/**
+ * Theme based generator for SVG gradient.
+ *
+ * @package PhpMyAdmin-theme
+ */
+header('Content-Type: image/svg+xml');
+header('Expires: ' . gmdate('D, d M Y H:i:s', time() + 3600) . ' GMT');
+
+/**
+ * Gets single color from GET parameters validating it.
+ *
+ * @param string $get_name Name of parameter in request
+ * @param string $default Default value
+ *
+ * @return string Color name or code.
+ */
+function PMA_gradientGetColor($get_name, $default)
+{
+ // get color from GET args, only alphanumeric chcracters
+ $opts = array('options' => array('regexp' => '/^[a-z0-9]+$/i'));
+ $color = filter_input(INPUT_GET, $get_name, FILTER_VALIDATE_REGEXP, $opts);
+ if (preg_match('/^[a-f0-9]{6}$/', $color)) {
+ return '#' . $color;
+ }
+ return $color ? $color : $default;
+}
+
+$from = PMA_gradientGetColor('from', 'white');
+$to = PMA_gradientGetColor('to', 'blank');
+
+echo '<?xml version="1.0" ?>';
+?>
+<svg
+ xmlns="http://www.w3.org/2000/svg"
+ preserveAspectRatio="none"
+ version="1.0" width="100%" height="100%">
+ <defs>
+ <linearGradient id="linear-gradient" x1="0%" y1="0%" x2="0%" y2="100%">
+ <stop
+ offset="0%"
+ stop-color="<?php echo $from; ?>"
+ stop-opacity="1"
+ />
+ <stop
+ offset="100%"
+ stop-color="<?php echo $to; ?>"
+ stop-opacity="1"
+ />
+ </linearGradient>
+ </defs>
+ <rect width="100%" height="100%" style="fill:url(#linear-gradient);" />
+</svg>
diff --git a/transformation_overview.php b/transformation_overview.php
new file mode 100644
index 0000000000..807c818be6
--- /dev/null
+++ b/transformation_overview.php
@@ -0,0 +1,59 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Lists available transformation plugins
+ *
+ * @package PhpMyAdmin
+ */
+
+/**
+ * Gets some core libraries and displays a top message if required
+ */
+require_once './libraries/common.inc.php';
+require_once './libraries/transformations.lib.php';
+
+$response = PMA_Response::getInstance();
+$header = $response->getHeader();
+$header->disableMenu();
+
+$types = PMA_getAvailableMIMEtypes();
+?>
+
+<h2><?php echo __('Available MIME types'); ?></h2>
+<?php
+foreach ($types['mimetype'] as $key => $mimetype) {
+
+ if (isset($types['empty_mimetype'][$mimetype])) {
+ echo '<i>' . $mimetype . '</i><br />';
+ } else {
+ echo $mimetype . '<br />';
+ }
+
+}
+?>
+<br />
+<h2><?php echo __('Available transformations'); ?></h2>
+<table width="90%">
+<thead>
+<tr>
+ <th><?php echo __('Browser transformation'); ?></th>
+ <th><?php echo _pgettext('for MIME transformation', 'Description'); ?></th>
+</tr>
+</thead>
+<tbody>
+<?php
+$odd_row = true;
+foreach ($types['transformation'] as $key => $transform) {
+ $desc = PMA_getTransformationDescription($types['transformation_file'][$key]);
+ ?>
+ <tr class="<?php echo $odd_row ? 'odd' : 'even'; ?>">
+ <td><?php echo $transform; ?></td>
+ <td><?php echo $desc; ?></td>
+ </tr>
+ <?php
+ $odd_row = !$odd_row;
+}
+?>
+</tbody>
+</table>
+
diff --git a/transformation_wrapper.php b/transformation_wrapper.php
new file mode 100644
index 0000000000..ac0b04973d
--- /dev/null
+++ b/transformation_wrapper.php
@@ -0,0 +1,148 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Wrapper script for rendering transformations
+ *
+ * @package PhpMyAdmin
+ */
+
+/**
+ *
+ */
+define('IS_TRANSFORMATION_WRAPPER', true);
+
+/**
+ * Gets a core script and starts output buffering work
+ */
+require_once './libraries/common.inc.php';
+require_once './libraries/transformations.lib.php'; // Transformations
+$cfgRelation = PMA_getRelationsParam();
+
+/**
+ * Ensures db and table are valid, else moves to the "parent" script
+ */
+require_once './libraries/db_table_exists.lib.php';
+
+
+/**
+ * Sets globals from $_REQUEST
+ */
+$request_params = array(
+ 'cn',
+ 'ct',
+ 'sql_query',
+ 'transform_key',
+ 'where_clause'
+);
+foreach ($request_params as $one_request_param) {
+ if (isset($_REQUEST[$one_request_param])) {
+ $GLOBALS[$one_request_param] = $_REQUEST[$one_request_param];
+ }
+}
+
+
+/**
+ * Get the list of the fields of the current table
+ */
+$GLOBALS['dbi']->selectDb($db);
+if (isset($where_clause)) {
+ $result = $GLOBALS['dbi']->query(
+ 'SELECT * FROM ' . PMA_Util::backquote($table)
+ . ' WHERE ' . $where_clause . ';',
+ null,
+ PMA_DatabaseInterface::QUERY_STORE
+ );
+ $row = $GLOBALS['dbi']->fetchAssoc($result);
+} else {
+ $result = $GLOBALS['dbi']->query(
+ 'SELECT * FROM ' . PMA_Util::backquote($table) . ' LIMIT 1;',
+ null,
+ PMA_DatabaseInterface::QUERY_STORE
+ );
+ $row = $GLOBALS['dbi']->fetchAssoc($result);
+}
+
+// No row returned
+if (! $row) {
+ exit;
+} // end if (no record returned)
+
+$default_ct = 'application/octet-stream';
+
+if ($cfgRelation['commwork'] && $cfgRelation['mimework']) {
+ $mime_map = PMA_getMime($db, $table);
+ $mime_options = PMA_Transformation_getOptions(
+ isset($mime_map[$transform_key]['transformation_options'])
+ ? $mime_map[$transform_key]['transformation_options'] : ''
+ );
+
+ foreach ($mime_options as $key => $option) {
+ if (substr($option, 0, 10) == '; charset=') {
+ $mime_options['charset'] = $option;
+ }
+ }
+}
+
+// Only output the http headers
+$response = PMA_Response::getInstance();
+$response->getHeader()->sendHttpHeaders();
+
+// [MIME]
+if (isset($ct) && ! empty($ct)) {
+ $mime_type = $ct;
+} else {
+ $mime_type = (isset($mime_map[$transform_key]['mimetype'])
+ ? str_replace('_', '/', $mime_map[$transform_key]['mimetype'])
+ : $default_ct)
+ . (isset($mime_options['charset']) ? $mime_options['charset'] : '');
+}
+
+PMA_downloadHeader($cn, $mime_type);
+
+if (! isset($_REQUEST['resize'])) {
+ echo $row[$transform_key];
+} else {
+ // if image_*__inline.inc.php finds that we can resize,
+ // it sets the resize parameter to jpeg or png
+
+ $srcImage = imagecreatefromstring($row[$transform_key]);
+ $srcWidth = ImageSX($srcImage);
+ $srcHeight = ImageSY($srcImage);
+
+ // Check to see if the width > height or if width < height
+ // if so adjust accordingly to make sure the image
+ // stays smaller than the new width and new height
+
+ $ratioWidth = $srcWidth/$_REQUEST['newWidth'];
+ $ratioHeight = $srcHeight/$_REQUEST['newHeight'];
+
+ if ($ratioWidth < $ratioHeight) {
+ $destWidth = $srcWidth/$ratioHeight;
+ $destHeight = $_REQUEST['newHeight'];
+ } else {
+ $destWidth = $_REQUEST['newWidth'];
+ $destHeight = $srcHeight/$ratioWidth;
+ }
+
+ if ($_REQUEST['resize']) {
+ $destImage = ImageCreateTrueColor($destWidth, $destHeight);
+ }
+
+ // ImageCopyResized($destImage, $srcImage, 0, 0, 0, 0,
+ // $destWidth, $destHeight, $srcWidth, $srcHeight);
+ // better quality but slower:
+ ImageCopyResampled(
+ $destImage, $srcImage, 0, 0, 0, 0, $destWidth,
+ $destHeight, $srcWidth, $srcHeight
+ );
+
+ if ($_REQUEST['resize'] == 'jpeg') {
+ ImageJPEG($destImage, null, 75);
+ }
+ if ($_REQUEST['resize'] == 'png') {
+ ImagePNG($destImage);
+ }
+ ImageDestroy($srcImage);
+ ImageDestroy($destImage);
+}
+?>
diff --git a/url.php b/url.php
new file mode 100644
index 0000000000..935c2bca95
--- /dev/null
+++ b/url.php
@@ -0,0 +1,23 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * URL redirector to avoid leaking Referer with some sensitive information.
+ *
+ * @package PhpMyAdmin
+ */
+
+/**
+ * Gets core libraries and defines some variables
+ */
+define('PMA_MINIMUM_COMMON', true);
+require_once './libraries/common.inc.php';
+
+if (! PMA_isValid($_GET['url'])
+ || ! preg_match('/^https?:\/\/[^\n\r]*$/', $_GET['url'])
+) {
+ header('Location: ' . $cfg['PmaAbsoluteUri']);
+} else {
+ header('Location: ' . $_GET['url']);
+}
+die();
+?>
diff --git a/user_password.php b/user_password.php
new file mode 100644
index 0000000000..374f6ffae7
--- /dev/null
+++ b/user_password.php
@@ -0,0 +1,242 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * displays and handles the form where the user can change his password
+ * linked from index.php
+ *
+ * @package PhpMyAdmin
+ */
+
+/**
+ * Gets some core libraries
+ */
+require_once './libraries/common.inc.php';
+
+$response = PMA_Response::getInstance();
+$header = $response->getHeader();
+$scripts = $header->getScripts();
+$scripts->addFile('server_privileges.js');
+
+/**
+ * Displays an error message and exits if the user isn't allowed to use this
+ * script
+ */
+if (! $cfg['ShowChgPassword']) {
+ $cfg['ShowChgPassword'] = $GLOBALS['dbi']->selectDb('mysql');
+}
+if ($cfg['Server']['auth_type'] == 'config' || ! $cfg['ShowChgPassword']) {
+ PMA_Message::error(
+ __('You don\'t have sufficient privileges to be here right now!')
+ )->display();
+ exit;
+} // end if
+
+/**
+ * If the "change password" form has been submitted, checks for valid values
+ * and submit the query or logout
+ */
+if (isset($_REQUEST['nopass'])) {
+ if ($_REQUEST['nopass'] == '1') {
+ $password = '';
+ } else {
+ $password = $_REQUEST['pma_pw'];
+ }
+ $change_password_message = PMA_setChangePasswordMsg();
+ $msg = $change_password_message['msg'];
+ if (! $change_password_message['error']) {
+ PMA_changePassword($password, $msg, $change_password_message);
+ } else {
+ PMA_getChangePassMessage($change_password_message);
+ }
+}
+
+/**
+ * If the "change password" form hasn't been submitted or the values submitted
+ * aren't valid -> displays the form
+ */
+
+// Displays an error message if required
+if (isset($msg)) {
+ $msg->display();
+ unset($msg);
+}
+
+require_once './libraries/display_change_password.lib.php';
+echo PMA_getHtmlForChangePassword($username, $hostname);
+exit;
+
+/**
+ * Send the message as an ajax request
+ *
+ * @param array $change_password_message Message to display
+ * @param string $sql_query SQL query executed
+ *
+ * @return void
+ */
+function PMA_getChangePassMessage($change_password_message, $sql_query = '')
+{
+ if ($GLOBALS['is_ajax_request'] == true) {
+ /**
+ * If in an Ajax request, we don't need to show the rest of the page
+ */
+ $response = PMA_Response::getInstance();
+ if ($change_password_message['error']) {
+ $response->addJSON('message', $change_password_message['msg']);
+ $response->isSuccess(false);
+ } else {
+ $sql_query = PMA_Util::getMessage(
+ $change_password_message['msg'],
+ $sql_query,
+ 'success'
+ );
+ $response->addJSON('message', $sql_query);
+ }
+ exit;
+ }
+}
+
+/**
+ * Generate the message
+ *
+ * @return array error value and message
+ */
+function PMA_setChangePasswordMsg()
+{
+ $error = false;
+ $message = PMA_Message::success(__('The profile has been updated.'));
+
+ if (($_REQUEST['nopass'] != '1')) {
+ if (empty($_REQUEST['pma_pw']) || empty($_REQUEST['pma_pw2'])) {
+ $message = PMA_Message::error(__('The password is empty!'));
+ $error = true;
+ } elseif ($_REQUEST['pma_pw'] != $_REQUEST['pma_pw2']) {
+ $message = PMA_Message::error(__('The passwords aren\'t the same!'));
+ $error = true;
+ }
+ }
+ return array('error' => $error, 'msg' => $message);
+}
+
+/**
+ * Change the password
+ *
+ * @param string $password New password
+ * @param string $message Message
+ * @param array $change_password_message Message to show
+ *
+ * @return void
+ */
+function PMA_changePassword($password, $message, $change_password_message)
+{
+ // Defines the url to return to in case of error in the sql statement
+ $_url_params = array();
+ $hashing_function = PMA_changePassHashingFunction();
+ $sql_query = 'SET password = '
+ . (($password == '') ? '\'\'' : $hashing_function . '(\'***\')');
+ PMA_changePassUrlParamsAndSubmitQuery(
+ $password, $_url_params, $sql_query, $hashing_function
+ );
+
+ $new_url_params = PMA_changePassAuthType($_url_params, $password);
+ PMA_getChangePassMessage($change_password_message, $sql_query);
+ PMA_changePassDisplayPage($message, $sql_query, $new_url_params);
+}
+
+/**
+ * Generate the hashing function
+ *
+ * @return string $hashing_function
+ */
+function PMA_changePassHashingFunction()
+{
+ if (PMA_isValid($_REQUEST['pw_hash'], 'identical', 'old')) {
+ $hashing_function = 'OLD_PASSWORD';
+ } else {
+ $hashing_function = 'PASSWORD';
+ }
+ return $hashing_function;
+}
+
+/**
+ * Generate the error url and submit the query
+ *
+ * @param string $password
+ * @param array $_url_params
+ * @param string $sql_query
+ * @param string $hashing_function
+ *
+ * @return void
+ */
+function PMA_changePassUrlParamsAndSubmitQuery(
+ $password, $_url_params, $sql_query, $hashing_function
+) {
+ $err_url = 'user_password.php' . PMA_URL_getCommon($_url_params);
+ $local_query = 'SET password = ' . (($password == '')
+ ? '\'\''
+ : $hashing_function . '(\'' . PMA_Util::sqlAddSlashes($password) . '\')');
+ if (! @$GLOBALS['dbi']->tryQuery($local_query)) {
+ PMA_Util::mysqlDie($GLOBALS['dbi']->getError(), $sql_query, false, $err_url);
+ }
+}
+
+/**
+ * Change password authentication type
+ *
+ * @param array $_url_params
+ * @param string $password
+ *
+ * @return array $_url_params
+ */
+function PMA_changePassAuthType($_url_params, $password)
+{
+ /**
+ * Changes password cookie if required
+ * Duration = till the browser is closed for password
+ * (we don't want this to be saved)
+ */
+
+ // include_once "libraries/plugins/auth/AuthenticationCookie.class.php";
+ // $auth_plugin = new AuthenticationCookie();
+ // the $auth_plugin is already defined in common.inc.php when this is used
+ global $auth_plugin;
+
+ if ($GLOBALS['cfg']['Server']['auth_type'] == 'cookie') {
+ $GLOBALS['PMA_Config']->setCookie(
+ 'pmaPass-' . $GLOBALS['server'],
+ $auth_plugin->blowfishEncrypt(
+ $password,
+ $GLOBALS['cfg']['blowfish_secret']
+ )
+ );
+ }
+ /**
+ * For http auth. mode, the "back" link will also enforce new
+ * authentication
+ */
+ if ($GLOBALS['cfg']['Server']['auth_type'] == 'http') {
+ $_url_params['old_usr'] = 'relog';
+ }
+ return $_url_params;
+}
+
+/**
+ * Display the page
+ *
+ * @param string $message
+ * @param string $sql_query
+ * @param array $_url_params
+ *
+ * @return void
+ */
+function PMA_changePassDisplayPage($message, $sql_query, $_url_params)
+{
+ echo '<h1>' . __('Change password') . '</h1>' . "\n\n";
+ echo PMA_Util::getMessage(
+ $message, $sql_query, 'success'
+ );
+ echo '<a href="index.php'.PMA_URL_getCommon($_url_params)
+ .' target="_parent">'. "\n"
+ .'<strong>'.__('Back').'</strong></a>';
+ exit;
+}
+?>
diff --git a/version_check.php b/version_check.php
new file mode 100644
index 0000000000..fda43ad2f0
--- /dev/null
+++ b/version_check.php
@@ -0,0 +1,26 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * A caching proxy for retrieving version information from phpmyadmin.net
+ *
+ * @package PhpMyAdmin
+ */
+
+// Sets up the session
+define('PMA_MINIMUM_COMMON', true);
+require_once 'libraries/common.inc.php';
+require_once 'libraries/Util.class.php';
+
+// Always send the correct headers
+header('Content-type: application/json; charset=UTF-8');
+
+$version = PMA_Util::getLatestVersion();
+
+echo json_encode(
+ array(
+ 'version' => $version->version,
+ 'date' => $version->date,
+ )
+);
+
+?>
diff --git a/view_create.php b/view_create.php
new file mode 100644
index 0000000000..d59eb9c39b
--- /dev/null
+++ b/view_create.php
@@ -0,0 +1,286 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * handles creation of VIEWs
+ *
+ * @todo js error when view name is empty (strFormEmpty)
+ * @todo (also validate if js is disabled, after form submission?)
+ * @package PhpMyAdmin
+ */
+
+/**
+ *
+ */
+require_once './libraries/common.inc.php';
+
+/**
+ * Runs common work
+ */
+require './libraries/db_common.inc.php';
+$url_params['goto'] = 'tbl_structure.php';
+$url_params['back'] = 'view_create.php';
+
+$view_algorithm_options = array(
+ 'UNDEFINED',
+ 'MERGE',
+ 'TEMPTABLE',
+);
+
+$view_with_options = array(
+ 'CASCADED',
+ 'LOCAL'
+);
+
+$view_security_options = array(
+ 'DEFINER',
+ 'INVOKER'
+);
+
+if (isset($_REQUEST['createview']) || isset($_REQUEST['alterview'])) {
+ /**
+ * Creates the view
+ */
+ $sep = "\r\n";
+
+ if (isset($_REQUEST['createview'])) {
+ $sql_query = 'CREATE';
+ if (isset($_REQUEST['view']['or_replace'])) {
+ $sql_query .= ' OR REPLACE';
+ }
+ } else {
+ $sql_query = 'ALTER';
+ }
+
+ if (PMA_isValid($_REQUEST['view']['algorithm'], $view_algorithm_options)) {
+ $sql_query .= $sep . ' ALGORITHM = ' . $_REQUEST['view']['algorithm'];
+ }
+
+ if (! empty($_REQUEST['view']['definer'])) {
+ $sql_query .= $sep . ' DEFINER = ' . $_REQUEST['view']['definer'];
+ }
+
+ if (isset($_REQUEST['view']['sql_security'])) {
+ if (in_array($_REQUEST['view']['sql_security'], $view_security_options)) {
+ $sql_query .= $sep . ' SQL SECURITY '
+ . $_REQUEST['view']['sql_security'];
+ }
+ }
+
+ $sql_query .= $sep . ' VIEW ' . PMA_Util::backquote($_REQUEST['view']['name']);
+
+ if (! empty($_REQUEST['view']['column_names'])) {
+ $sql_query .= $sep . ' (' . $_REQUEST['view']['column_names'] . ')';
+ }
+
+ $sql_query .= $sep . ' AS ' . $_REQUEST['view']['as'];
+
+ if (isset($_REQUEST['view']['with'])) {
+ if (in_array($_REQUEST['view']['with'], $view_with_options)) {
+ $sql_query .= $sep . ' WITH ' . $_REQUEST['view']['with']
+ . ' CHECK OPTION';
+ }
+ }
+
+ if ($GLOBALS['dbi']->tryQuery($sql_query)) {
+
+ include_once './libraries/tbl_views.lib.php';
+
+ // If different column names defined for VIEW
+ $view_columns = array();
+ if (isset($_REQUEST['view']['column_names'])) {
+ $view_columns = explode(',', $_REQUEST['view']['column_names']);
+ }
+
+ $column_map = PMA_getColumnMap($_REQUEST['view']['as'], $view_columns);
+ $pma_tranformation_data = PMA_getExistingTranformationData($GLOBALS['db']);
+
+ if ($pma_tranformation_data !== false) {
+
+ // SQL for store new transformation details of VIEW
+ $new_transformations_sql = PMA_getNewTransformationDataSql(
+ $pma_tranformation_data, $column_map, $_REQUEST['view']['name'],
+ $GLOBALS['db']
+ );
+
+ // Store new transformations
+ if ($new_transformations_sql != '') {
+ $GLOBALS['dbi']->tryQuery($new_transformations_sql);
+ }
+
+ }
+ unset($pma_tranformation_data);
+
+ if (! isset($_REQUEST['ajax_dialog'])) {
+ $message = PMA_Message::success();
+ include 'tbl_structure.php';
+ } else {
+ $response = PMA_Response::getInstance();
+ $response->addJSON(
+ 'message',
+ PMA_Util::getMessage(
+ PMA_Message::success(), $sql_query
+ )
+ );
+ $response->isSuccess(true);
+ }
+
+ exit;
+
+ } else {
+ if (! isset($_REQUEST['ajax_dialog'])) {
+ $message = PMA_Message::rawError($GLOBALS['dbi']->getError());
+ } else {
+ $response = PMA_Response::getInstance();
+ $response->addJSON(
+ 'message',
+ PMA_Message::error(
+ "<i>" . htmlspecialchars($sql_query) . "</i><br /><br />"
+ . $GLOBALS['dbi']->getError()
+ )
+ );
+ $response->isSuccess(false);
+ exit;
+ }
+ }
+}
+
+// prefill values if not already filled from former submission
+$view = array(
+ 'operation' => 'create',
+ 'or_replace' => '',
+ 'algorithm' => '',
+ 'definer' => '',
+ 'sql_security' => '',
+ 'name' => '',
+ 'column_names' => '',
+ 'as' => $sql_query,
+ 'with' => '',
+);
+
+if (PMA_isValid($_REQUEST['view'], 'array')) {
+ $view = array_merge($view, $_REQUEST['view']);
+}
+
+$url_params['db'] = $GLOBALS['db'];
+$url_params['reload'] = 1;
+
+/**
+ * Displays the page
+ */
+$htmlString = '<!-- CREATE VIEW options -->'
+ . '<div id="div_view_options">'
+ . '<form method="post" action="view_create.php">'
+ . PMA_URL_getHiddenInputs($url_params)
+ . '<fieldset>'
+ . '<legend>'
+ . (isset($_REQUEST['ajax_dialog']) ?
+ __('Details') :
+ ($view['operation'] == 'create' ? __('Create view') : __('Edit view'))
+ )
+ . '</legend>'
+ . '<table class="rte_table">';
+
+if ($view['operation'] == 'create') {
+ $htmlString .= '<tr>'
+ . '<td class="nowrap"><label for="or_replace">OR REPLACE</label></td>'
+ . '<td><input type="checkbox" name="view[or_replace]" id="or_replace"';
+ if ($view['or_replace']) {
+ $htmlString .= ' checked="checked"';
+ }
+ $htmlString .= ' value="1" /></td></tr>';
+}
+
+$htmlString .= '<tr>'
+ . '<td class="nowrap"><label for="algorithm">ALGORITHM</label></td>'
+ . '<td><select name="view[algorithm]" id="algorithm">';
+foreach ($view_algorithm_options as $option) {
+ $htmlString .= '<option value="' . htmlspecialchars($option) . '"';
+ if ($view['algorithm'] === $option) {
+ $htmlString .= ' selected="selected"';
+ }
+ $htmlString .= '>' . htmlspecialchars($option) . '</option>';
+}
+$htmlString .= '</select>'
+ . '</td></tr>';
+
+$htmlString .= '<tr><td class="nowrap">' . __('Definer') . '</td>'
+ . '<td><input type="text" maxlength="100" size="50" name="view[definer]"'
+ . ' value="' . htmlspecialchars($view['definer']) . '" />'
+ . '</td></tr>';
+
+$htmlString .= '<tr><td class="nowrap">SQL SECURITY</td>'
+ . '<td><select name="view[sql_security]">'
+ . '<option value=""></option>';
+foreach ($view_security_options as $option) {
+ $htmlString .= '<option value="' . htmlspecialchars($option) . '"';
+ if ($option == $view['sql_security']) {
+ $htmlString .= ' selected="selected"';
+ }
+ $htmlString .= '>' . htmlspecialchars($option) . '</option>';
+}
+$htmlString .= '<select>'
+ . '</td></tr>';
+
+if ($view['operation'] == 'create') {
+ $htmlString .= '<tr><td class="nowrap">' . __('VIEW name') . '</td>'
+ . '<td><input type="text" size="20" name="view[name]"'
+ . ' onfocus="this.select()"'
+ . ' value="' . htmlspecialchars($view['name']) . '" />'
+ . '</td></tr>';
+} else {
+ $htmlString .= '<tr><td><input type="hidden" name="view[name]"'
+ . ' value="' . htmlspecialchars($view['name']) . '" />'
+ . '</td></tr>';
+}
+
+$htmlString .= '<tr><td class="nowrap">' . __('Column names') . '</td>'
+ . '<td><input type="text" maxlength="100" size="50" name="view[column_names]"'
+ . ' onfocus="this.select()"'
+ . ' value="' . htmlspecialchars($view['column_names']) . '" />'
+ . '</td></tr>';
+
+$htmlString .= '<tr><td class="nowrap">AS</td>'
+ . '<td>'
+ . '<textarea name="view[as]" rows="' . $cfg['TextareaRows'] . '"'
+ . ' cols="' . $cfg['TextareaCols'] . '" dir="' . $text_dir . '"';
+if ($GLOBALS['cfg']['TextareaAutoSelect'] || true) {
+ $htmlString .= ' onclick="selectContent(this, sql_box_locked, true)"';
+}
+$htmlString .= '>' . htmlspecialchars($view['as']) . '</textarea>'
+ . '</td></tr>';
+
+$htmlString .= '<tr><td class="nowrap">WITH CHECK OPTION</td>'
+ . '<td><select name="view[with]">'
+ . '<option value=""></option>';
+foreach ($view_with_options as $option) {
+ $htmlString .= '<option value="' . htmlspecialchars($option) . '"';
+ if ($option == $view['with']) {
+ $htmlString .= ' selected="selected"';
+ }
+ $htmlString .= '>' . htmlspecialchars($option) . '</option>';
+}
+$htmlString .= '<select>'
+ . '</td></tr>';
+
+$htmlString .= '</table>'
+ . '</fieldset>';
+
+if (! isset($_REQUEST['ajax_dialog'])) {
+ $htmlString .= '<fieldset class="tblFooters">'
+ . '<input type="hidden" name="'
+ . ($view['operation'] == 'create' ? 'createview' : 'alterview' )
+ . '" value="1" />'
+ . '<input type="submit" name="" value="' . __('Go') . '" />'
+ . '</fieldset>';
+} else {
+ $htmlString .= '<input type="hidden" name="'
+ . ($view['operation'] == 'create' ? 'createview' : 'alterview' )
+ . '" value="1" />'
+ . '<input type="hidden" name="ajax_dialog" value="1" />'
+ . '<input type="hidden" name="ajax_request" value="1" />';
+}
+
+$htmlString .= '</form>'
+ . '</div>';
+
+echo $htmlString;
diff --git a/view_operations.php b/view_operations.php
new file mode 100644
index 0000000000..f22e87a6da
--- /dev/null
+++ b/view_operations.php
@@ -0,0 +1,139 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * View manipulations
+ *
+ * @package PhpMyAdmin
+ */
+
+/**
+ *
+ */
+require_once './libraries/common.inc.php';
+
+$pma_table = new PMA_Table($GLOBALS['table'], $GLOBALS['db']);
+
+/**
+ * functions implementation for this script
+ */
+require_once 'libraries/operations.lib.php';
+
+/**
+ * Runs common work
+ */
+require './libraries/tbl_common.inc.php';
+$url_query .= '&amp;goto=view_operations.php&amp;back=view_operations.php';
+$url_params['goto'] = $url_params['back'] = 'view_operations.php';
+
+/**
+ * Gets tables informations
+ */
+
+require './libraries/tbl_info.inc.php';
+$reread_info = false;
+
+/**
+ * Updates if required
+ */
+if (isset($_REQUEST['submitoptions'])) {
+ $_message = '';
+ $warning_messages = array();
+
+ if (isset($_REQUEST['new_name'])) {
+ if ($pma_table->rename($_REQUEST['new_name'])) {
+ $_message .= $pma_table->getLastMessage();
+ $result = true;
+ $GLOBALS['table'] = $pma_table->getName();
+ $reread_info = true;
+ $reload = true;
+ } else {
+ $_message .= $pma_table->getLastError();
+ $result = false;
+ }
+ }
+}
+
+if (isset($result)) {
+ // set to success by default, because result set could be empty
+ // (for example, a table rename)
+ $_type = 'success';
+ if (empty($_message)) {
+ $_message = $result
+ ? __('Your SQL query has been executed successfully')
+ : __('Error');
+ // $result should exist, regardless of $_message
+ $_type = $result ? 'success' : 'error';
+ }
+ if (! empty($warning_messages)) {
+ $_message = new PMA_Message;
+ $_message->addMessages($warning_messages);
+ $_message->isError(true);
+ unset($warning_messages);
+ }
+ echo PMA_Util::getMessage(
+ $_message, $sql_query, $_type, $is_view = true
+ );
+ unset($_message, $_type);
+}
+
+$url_params['goto'] = 'view_operations.php';
+$url_params['back'] = 'view_operations.php';
+
+/**
+ * Displays the page
+ */
+?>
+<!-- Table operations -->
+<div class="operations_half_width">
+<form method="post" action="view_operations.php">
+<?php echo PMA_URL_getHiddenInputs($GLOBALS['db'], $GLOBALS['table']); ?>
+<input type="hidden" name="reload" value="1" />
+<fieldset>
+ <legend><?php echo __('Operations'); ?></legend>
+
+ <table>
+ <!-- Change view name -->
+ <tr><td><?php echo __('Rename view to'); ?></td>
+ <td><input type="text" size="20" name="new_name" onfocus="this.select()"
+ value="<?php echo htmlspecialchars($GLOBALS['table']); ?>"
+ required />
+ </td>
+ </tr>
+ </table>
+</fieldset>
+<fieldset class="tblFooters">
+ <input type="hidden" name="submitoptions" value="1" />
+ <input type="submit" value="<?php echo __('Go'); ?>" />
+</fieldset>
+</form>
+</div>
+<?php
+$drop_view_url_params = array_merge(
+ $url_params,
+ array(
+ 'sql_query' => 'DROP VIEW ' . PMA_Util::backquote($GLOBALS['table']),
+ 'goto' => 'tbl_structure.php',
+ 'reload' => '1',
+ 'purge' => '1',
+ 'message_to_show' => sprintf(
+ __('View %s has been dropped'),
+ htmlspecialchars($GLOBALS['table'])
+ ),
+ 'table' => $GLOBALS['table']
+ )
+);
+echo '<div class="operations_half_width">';
+echo '<fieldset class="caution">';
+echo '<legend>' . __('Delete data or table') . '</legend>';
+
+echo '<ul>';
+echo PMA_getDeleteDataOrTableLink(
+ $drop_view_url_params,
+ 'DROP VIEW',
+ __('Delete the view (DROP)'),
+ 'drop_view_anchor'
+);
+echo '</ul>';
+echo '</fieldset>';
+echo '</div>';
+
diff --git a/webapp.php b/webapp.php
new file mode 100644
index 0000000000..48da877ed1
--- /dev/null
+++ b/webapp.php
@@ -0,0 +1,55 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * generate an WebApp file for Prism / WebRunner
+ *
+ * @package PhpMyAdmin
+ * @see http://wiki.mozilla.org/Prism
+ */
+
+/**
+ * @ignore
+ */
+define('PMA_MINIMUM_COMMON', true);
+/**
+ * Gets core libraries and defines some variables
+ */
+require './libraries/common.inc.php';
+/**
+ * ZIP file handler.
+ */
+require './libraries/zip.lib.php';
+
+// ini file
+$parameters = array(
+ 'id' => 'phpMyAdmin@' . $_SERVER['HTTP_HOST'],
+ 'uri' => $GLOBALS['PMA_Config']->get('PmaAbsoluteUri'),
+ 'status' => 'yes',
+ 'location' => 'no',
+ 'sidebar' => 'no',
+ 'navigation' => 'no',
+ 'icon' => 'phpMyAdmin',
+);
+
+// dom sript file
+// none need yet
+
+// icon
+$icon = 'favicon.ico';
+
+// name
+$name = 'phpMyAdmin.webapp';
+
+$ini_file = "[Parameters]\n";
+foreach ($parameters as $key => $value) {
+ $ini_file .= $key . '=' . $value . "\n";
+}
+
+PMA_downloadHeader($name, 'application/webapp', 0, false);
+
+$zip = new ZipFile;
+$zip->setDoWrite();
+$zip->addFile($ini_file, 'webapp.ini');
+$zip->addFile(file_get_contents($icon), 'phpMyAdmin.ico');
+$zip->file();
+?>